edit account: fixes issues with avatar updating, adds local settings for a user

This commit is contained in:
jimmylee 2020-06-30 23:41:54 -07:00
parent 6b3b867559
commit 8251bc0ba7
7 changed files with 171 additions and 144 deletions

3
.gitignore vendored
View File

@ -18,5 +18,4 @@ public/static/files/*
!public/static/files/.gitkeep
public/static/system/*
!public/static/system/.gitkeep
!public/static/system/avatar.png
!public/static/system/.gitkeep

View File

@ -1,7 +1,7 @@
const STATIC_ADDRESS_TYPE_MAP = {
bls: "BLS",
secp256k1: "SECP256K1",
multisig: "MULTISIG",
bls: 'BLS',
secp256k1: 'SECP256K1',
multisig: 'MULTISIG',
};
const transformAddresses = (addrsList, info) => {
@ -28,8 +28,8 @@ const transformPeers = (peersList) => {
return peersList.map((each) => {
return {
id: each.addrInfo.id,
"peer-avatar": null,
"chain-head": null,
'peer-avatar': null,
'chain-head': null,
height: null,
location: null,
upload: null,
@ -39,7 +39,7 @@ const transformPeers = (peersList) => {
};
export const getInitialState = (props) => {
const { status, messageList, peersList, addrsList, info, library } = props;
const { status, messageList, peersList, addrsList, info, library, local } = props;
if (!info || !info.id) {
return {
@ -57,8 +57,8 @@ export const getInitialState = (props) => {
return {
id: info.id,
name: "New Node",
photoURL: "/static/system/avatar.png",
name: local.name,
photoURL: local.photo,
upload_bandwidth: 0,
download_bandwidth: 0,
@ -70,19 +70,13 @@ export const getInitialState = (props) => {
settings_cold_enabled: info.defaultConfig.cold.enabled,
settings_cold_default_address: info.defaultConfig.cold.filecoin.addr,
settings_cold_default_duration:
info.defaultConfig.cold.filecoin.dealMinDuration,
settings_cold_default_replication_factor:
info.defaultConfig.cold.filecoin.repFactor,
settings_cold_default_excluded_miners:
info.defaultConfig.cold.filecoin.excludedMinersList,
settings_cold_default_trusted_miners:
info.defaultConfig.cold.filecoin.trustedMinersList,
settings_cold_default_duration: info.defaultConfig.cold.filecoin.dealMinDuration,
settings_cold_default_replication_factor: info.defaultConfig.cold.filecoin.repFactor,
settings_cold_default_excluded_miners: info.defaultConfig.cold.filecoin.excludedMinersList,
settings_cold_default_trusted_miners: info.defaultConfig.cold.filecoin.trustedMinersList,
settings_cold_default_max_price: info.defaultConfig.cold.filecoin.maxPrice,
settings_cold_default_auto_renew:
info.defaultConfig.cold.filecoin.renew.enabled,
settings_cold_default_auto_renew_max_price:
info.defaultConfig.cold.filecoin.renew.threshold,
settings_cold_default_auto_renew: info.defaultConfig.cold.filecoin.renew.enabled,
settings_cold_default_auto_renew_max_price: info.defaultConfig.cold.filecoin.renew.threshold,
notifications: [],
payment_channels_active: [],

View File

@ -1,30 +1,30 @@
import * as Constants from "~/node_common/constants";
import * as Constants from '~/node_common/constants';
import FS from "fs-extra";
import FS from 'fs-extra';
export const resetFileSystem = async () => {
console.log("[ prototype ] deleting old token and library data ");
console.log('[ prototype ] deleting old token and library data ');
if (FS.existsSync(`./.data`)) {
FS.removeSync("./.data", { recursive: true });
FS.removeSync('./.data', { recursive: true });
}
console.log("[ prototype ] deleting old avatar data ");
console.log('[ prototype ] deleting old avatar data ');
if (FS.existsSync(Constants.AVATAR_STORAGE_URL)) {
FS.removeSync(Constants.AVATAR_STORAGE_URL, { recursive: true });
}
console.log("[ prototype ] deleting old file data ");
console.log('[ prototype ] deleting old file data ');
if (FS.existsSync(Constants.FILE_STORAGE_URL)) {
FS.removeSync(Constants.FILE_STORAGE_URL, { recursive: true });
}
console.log("[ prototype ] creating new avatar folder ");
console.log('[ prototype ] creating new avatar folder ');
FS.mkdirSync(Constants.AVATAR_STORAGE_URL, { recursive: true });
FS.writeFileSync(`${Constants.AVATAR_STORAGE_URL}.gitkeep`, "");
FS.writeFileSync(`${Constants.AVATAR_STORAGE_URL}.gitkeep`, '');
console.log("[ prototype ] creating new local file folder ");
console.log('[ prototype ] creating new local file folder ');
FS.mkdirSync(Constants.FILE_STORAGE_URL, { recursive: true });
FS.writeFileSync(`${Constants.FILE_STORAGE_URL}.gitkeep`, "");
FS.writeFileSync(`${Constants.FILE_STORAGE_URL}.gitkeep`, '');
return true;
};
@ -66,7 +66,7 @@ export const emitState = async ({ state, client, PG }) => {
});
if (client) {
client.send(JSON.stringify({ action: "UPDATE_VIEWER", data }));
client.send(JSON.stringify({ action: 'UPDATE_VIEWER', data }));
}
return data;
@ -74,18 +74,18 @@ export const emitState = async ({ state, client, PG }) => {
export const getFileName = (s) => {
let target = s;
if (target.endsWith("/")) {
if (target.endsWith('/')) {
target = target.substring(0, target.length - 1);
}
return target.substr(target.lastIndexOf("/") + 1);
return target.substr(target.lastIndexOf('/') + 1);
};
export const createFile = ({ id, data }) => {
return {
decorator: "FILE",
decorator: 'FILE',
id: id,
icon: "PNG",
icon: 'PNG',
file: getFileName(id),
miner: null,
job_id: null,
@ -103,10 +103,10 @@ export const createFile = ({ id, data }) => {
export const createFolder = ({ id }) => {
return {
decorator: "FOLDER",
decorator: 'FOLDER',
id,
folderId: id,
icon: "FOLDER",
icon: 'FOLDER',
file: getFileName(id),
name: getFileName(id),
pageTitle: null,
@ -130,22 +130,16 @@ export const refreshLibrary = async ({ state, PG, FFS }) => {
for (let j = 0; j < state.library[i].children.length; j++) {
if (state.library[i].children[j].job_id) {
if (state.library[i].children[j].storage_status === 1) {
console.log(
"[ prototype ] update file",
state.library[i].children[j]
);
console.log('[ prototype ] update file', state.library[i].children[j]);
state.library[i].children[j].storage_status = 2;
write = true;
continue;
}
PG.ffs.watchJobs((job) => {
console.log("[ prototype ] job status", job.status);
console.log('[ prototype ] job status', job.status);
if (job.status === FFS.JobStatus.JOB_STATUS_SUCCESS) {
console.log(
"[ prototype ] update file",
state.library[i].children[j]
);
console.log('[ prototype ] update file', state.library[i].children[j]);
state.library[i].children[j].storage_status = 6;
write = true;
}
@ -155,10 +149,7 @@ export const refreshLibrary = async ({ state, PG, FFS }) => {
}
if (write) {
FS.writeFileSync(
"./.data/library.json",
JSON.stringify({ library: state.library })
);
FS.writeFileSync('./.data/library.json', JSON.stringify({ library: state.library }));
}
return { ...state };

View File

@ -10,8 +10,8 @@
"start": "NODE_ENV=production node . --unhandled-rejections=strict"
},
"dependencies": {
"@babel/preset-env": "^7.9.0",
"@babel/register": "^7.9.0",
"@babel/preset-env": "^7.10.4",
"@babel/register": "^7.10.4",
"@emotion/babel-preset-css-prop": "^10.0.27",
"@emotion/cache": "11.0.0-next.12",
"@emotion/core": "^10.0.28",
@ -35,6 +35,7 @@
"react-dom": "^16.12.0",
"react-tippy": "^1.3.4",
"three": "^0.108.0",
"uuid": "^8.0.0",
"ws": "^7.3.0"
}
}

View File

@ -1,39 +1,39 @@
import * as React from "react";
import * as Fixtures from "~/common/fixtures";
import * as Actions from "~/common/actions";
import * as State from "~/common/state";
import * as React from 'react';
import * as Fixtures from '~/common/fixtures';
import * as Actions from '~/common/actions';
import * as State from '~/common/state';
import SceneDataTransfer from "~/scenes/SceneDataTransfer";
import SceneDeals from "~/scenes/SceneDeals";
import SceneEditAccount from "~/scenes/SceneEditAccount";
import SceneFile from "~/scenes/SceneFile";
import SceneFilesFolder from "~/scenes/SceneFilesFolder";
import SceneHome from "~/scenes/SceneHome";
import SceneLogs from "~/scenes/SceneLogs";
import SceneMiners from "~/scenes/SceneMiners";
import ScenePaymentChannels from "~/scenes/ScenePaymentChannels";
import ScenePeers from "~/scenes/ScenePeers";
import SceneSettings from "~/scenes/SceneSettings";
import SceneStats from "~/scenes/SceneStats";
import SceneStatus from "~/scenes/SceneStatus";
import SceneStorageMarket from "~/scenes/SceneStorageMarket";
import SceneWallet from "~/scenes/SceneWallet";
import SceneDataTransfer from '~/scenes/SceneDataTransfer';
import SceneDeals from '~/scenes/SceneDeals';
import SceneEditAccount from '~/scenes/SceneEditAccount';
import SceneFile from '~/scenes/SceneFile';
import SceneFilesFolder from '~/scenes/SceneFilesFolder';
import SceneHome from '~/scenes/SceneHome';
import SceneLogs from '~/scenes/SceneLogs';
import SceneMiners from '~/scenes/SceneMiners';
import ScenePaymentChannels from '~/scenes/ScenePaymentChannels';
import ScenePeers from '~/scenes/ScenePeers';
import SceneSettings from '~/scenes/SceneSettings';
import SceneStats from '~/scenes/SceneStats';
import SceneStatus from '~/scenes/SceneStatus';
import SceneStorageMarket from '~/scenes/SceneStorageMarket';
import SceneWallet from '~/scenes/SceneWallet';
import SidebarCreateWalletAddress from "~/components/sidebars/SidebarCreateWalletAddress";
import SidebarDeleteWalletAddress from "~/components/sidebars/SidebarDeleteWalletAddress";
import SidebarWalletSendFunds from "~/components/sidebars/SidebarWalletSendFunds";
import SidebarFileStorageDeal from "~/components/sidebars/SidebarFileStorageDeal";
import SidebarFileRetrievalDeal from "~/components/sidebars/SidebarFileRetrievalDeal";
import SidebarCreatePaymentChannel from "~/components/sidebars/SidebarCreatePaymentChannel";
import SidebarAddMiner from "~/components/sidebars/SidebarAddMiner";
import SidebarAddPeer from "~/components/sidebars/SidebarAddPeer";
import SidebarNotifications from "~/components/sidebars/SidebarNotifications";
import SidebarRedeemPaymentChannel from "~/components/sidebars/SidebarRedeemPaymentChannel";
import SidebarCreateWalletAddress from '~/components/sidebars/SidebarCreateWalletAddress';
import SidebarDeleteWalletAddress from '~/components/sidebars/SidebarDeleteWalletAddress';
import SidebarWalletSendFunds from '~/components/sidebars/SidebarWalletSendFunds';
import SidebarFileStorageDeal from '~/components/sidebars/SidebarFileStorageDeal';
import SidebarFileRetrievalDeal from '~/components/sidebars/SidebarFileRetrievalDeal';
import SidebarCreatePaymentChannel from '~/components/sidebars/SidebarCreatePaymentChannel';
import SidebarAddMiner from '~/components/sidebars/SidebarAddMiner';
import SidebarAddPeer from '~/components/sidebars/SidebarAddPeer';
import SidebarNotifications from '~/components/sidebars/SidebarNotifications';
import SidebarRedeemPaymentChannel from '~/components/sidebars/SidebarRedeemPaymentChannel';
import ApplicationNavigation from "~/components/core/ApplicationNavigation";
import ApplicationHeader from "~/components/core/ApplicationHeader";
import ApplicationLayout from "~/components/core/ApplicationLayout";
import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper";
import ApplicationNavigation from '~/components/core/ApplicationNavigation';
import ApplicationHeader from '~/components/core/ApplicationHeader';
import ApplicationLayout from '~/components/core/ApplicationLayout';
import WebsitePrototypeWrapper from '~/components/core/WebsitePrototypeWrapper';
const getCurrentNavigationStateById = (navigation, targetId) => {
let target = null;
@ -82,7 +82,7 @@ export default class IndexPage extends React.Component {
currentIndex: 0,
data: null,
selected: {
address: "",
address: '',
},
viewer: State.getInitialState(this.props),
sidebar: null,
@ -92,10 +92,10 @@ export default class IndexPage extends React.Component {
this._socket = new WebSocket(`ws://localhost:${this.props.wsPort}`);
this._socket.onmessage = (m) => {
console.log(m);
if (m.type === "message") {
if (m.type === 'message') {
const parsed = JSON.parse(m.data);
if (parsed.action === "UPDATE_VIEWER") {
if (parsed.action === 'UPDATE_VIEWER') {
this.rehydrate({ data: parsed.data });
}
}
@ -103,16 +103,17 @@ export default class IndexPage extends React.Component {
}
rehydrate = async ({ data }) => {
console.log(data);
this.setState({ viewer: { ...State.getInitialState(data) } });
};
_handleSubmit = async (data) => {
if (this.props.production) {
alert("TODO");
alert('TODO');
return this._handleDismissSidebar();
}
if (data.type === "CREATE_WALLET_ADDRESS") {
if (data.type === 'CREATE_WALLET_ADDRESS') {
const address = await Actions.createWalletAddress({
name: data.name,
type: data.wallet_type,
@ -120,7 +121,7 @@ export default class IndexPage extends React.Component {
});
}
if (data.type === "SEND_WALLET_ADDRESS_FILECOIN") {
if (data.type === 'SEND_WALLET_ADDRESS_FILECOIN') {
const response = await Actions.sendWalletAddressFilecoin({
source: data.source,
target: data.target,
@ -152,19 +153,19 @@ export default class IndexPage extends React.Component {
};
_handleAction = (options) => {
if (options.type === "NAVIGATE") {
if (options.type === 'NAVIGATE') {
return this._handleNavigateTo({ id: options.value }, options.data);
}
if (options.type === "ACTION") {
if (options.type === 'ACTION') {
return alert(JSON.stringify(options));
}
if (options.type === "DOWNLOAD") {
if (options.type === 'DOWNLOAD') {
return alert(JSON.stringify(options));
}
if (options.type === "SIDEBAR") {
if (options.type === 'SIDEBAR') {
return this.setState({ sidebar: this.sidebars[options.value] });
}
@ -268,9 +269,7 @@ export default class IndexPage extends React.Component {
return null;
}
const navigation = Fixtures.generateNavigationState(
this.state.viewer.library
);
const navigation = Fixtures.generateNavigationState(this.state.viewer.library);
const next = this.state.history[this.state.currentIndex];
const current = getCurrentNavigationStateById(navigation, next.id);
@ -323,22 +322,17 @@ export default class IndexPage extends React.Component {
}
const title = `Prototype 0.0.1 : ${current.target.pageTitle}`;
const description = "This is an early preview.";
const url = "https://fps.onrender.com/v1";
const description = 'This is an early preview.';
const url = 'https://fps.onrender.com/v1';
return (
<React.Fragment>
<WebsitePrototypeWrapper
title={title}
description={description}
url={url}
>
<WebsitePrototypeWrapper title={title} description={description} url={url}>
<ApplicationLayout
navigation={navigationElement}
header={headerElement}
sidebar={sidebarElement}
onDismissSidebar={this._handleDismissSidebar}
>
onDismissSidebar={this._handleDismissSidebar}>
{scene}
</ApplicationLayout>
</WebsitePrototypeWrapper>

View File

@ -1,11 +1,11 @@
import * as React from "react";
import * as System from "~/components/system";
import * as Actions from "~/common/actions";
import * as React from 'react';
import * as System from '~/components/system';
import * as Actions from '~/common/actions';
import { css } from "@emotion/react";
import { css } from '@emotion/react';
import ScenePage from "~/components/core/ScenePage";
import Avatar from "~/components/core/Avatar";
import ScenePage from '~/components/core/ScenePage';
import Avatar from '~/components/core/Avatar';
const STYLES_FILE_HIDDEN = css`
height: 1px;
@ -18,21 +18,23 @@ const STYLES_FILE_HIDDEN = css`
`;
export default class SceneEditAccount extends React.Component {
state = { name: this.props.viewer.name };
_handleUpload = async (e) => {
e.persist();
let file = e.target.files[0];
if (!file) {
alert("Something went wrong");
alert('Something went wrong');
}
let data = new FormData();
data.append("image", file);
data.append('image', file);
const options = {
method: "POST",
method: 'POST',
headers: {
Accept: "application/json",
Accept: 'application/json',
},
body: data,
};
@ -40,8 +42,23 @@ export default class SceneEditAccount extends React.Component {
await fetch(`/_/upload/avatar`, options);
};
_handleSave = async (e) => {
const options = {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({ local: { name: this.state.name } }),
};
await fetch(`/_/local-settings`, options);
};
_handleChange = (e) => {
this.props.onViewerChange(e);
e.persist();
this.setState({ [e.target.name]: e.target.value });
};
render() {
@ -55,24 +72,11 @@ export default class SceneEditAccount extends React.Component {
description="This image will appear in various lists."
/>
<Avatar
style={{ marginTop: 24 }}
size={256}
url={this.props.viewer.photoURL}
/>
<Avatar style={{ marginTop: 24 }} size={256} url={this.props.viewer.photoURL} />
<div style={{ marginTop: 24 }}>
<input
css={STYLES_FILE_HIDDEN}
type="file"
id="file"
onChange={this._handleUpload}
/>
<System.ButtonPrimary
style={{ margin: "0 16px 16px 0" }}
type="label"
htmlFor="file"
>
<input css={STYLES_FILE_HIDDEN} type="file" id="file" onChange={this._handleUpload} />
<System.ButtonPrimary style={{ margin: '0 16px 16px 0' }} type="label" htmlFor="file">
Upload
</System.ButtonPrimary>
</div>
@ -82,10 +86,14 @@ export default class SceneEditAccount extends React.Component {
label="Name"
description="The name of your Filecoin Client can be seen by your peers."
name="name"
value={this.props.viewer.name}
value={this.state.name}
placeholder="Name"
onChange={this._handleChange}
/>
<div style={{ marginTop: 24 }}>
<System.ButtonPrimary onClick={this._handleSave}>Save</System.ButtonPrimary>
</div>
</ScenePage>
);
}

View File

@ -4,6 +4,9 @@ import * as Utilities from '~/node_common/utilities';
import * as Constants from '~/node_common/constants';
import { createPow, ffs } from '@textile/powergate-client';
// NOTE(jim):
// https://github.com/textileio/js-powergate-client
const PowerGate = createPow({ host: Constants.POWERGATE_HOST });
import FS from 'fs-extra';
@ -13,6 +16,7 @@ import formidable from 'formidable';
import next from 'next';
import bodyParser from 'body-parser';
import compression from 'compression';
import { v4 as uuid } from 'uuid';
// TODO(jim): Support multiple desktop applications.
let client = null;
@ -62,12 +66,13 @@ app.prepare().then(async () => {
peersList: null,
addrsList: null,
info: null,
local: null,
};
if (!production) {
// TODO(jim): Remove later.
// We wipe all of the local data each time you run the application.
try {
// TODO(jim): Remove later.
// We wipe all of the local data each time you run the application.
await Utilities.resetFileSystem();
const updates = await Utilities.refresh({ PG: PowerGate });
@ -75,11 +80,13 @@ app.prepare().then(async () => {
console.log('[ prototype ] updated without token');
// NOTE(jim): This is a configuration folder with all of the client tokens.
// TODO(jim): Unnecessary if we use a local and remote postgres.
if (!FS.existsSync(`./.data`)) {
FS.mkdirSync(`./.data`, { recursive: true });
}
// NOTE(jim): This will create a token for authentication with powergate.
// TODO(jim): Roll this up into Postgres instead.
if (!FS.existsSync('./.data/powergate-token')) {
const FFS = await PowerGate.ffs.create();
state.token = FFS.token ? FFS.token : null;
@ -103,7 +110,9 @@ app.prepare().then(async () => {
state = await Utilities.updateStateData(state, tokenUpdates);
console.log('[ prototype ] updated with token');
// NOTE(jim): Local library retrieval or creation
// TODO(jim): Needs to support nested folders in the future.
// TODO(jim): May consider a move to buckets.
if (!FS.existsSync('./.data/library.json')) {
const librarySchema = {
library: [
@ -121,11 +130,26 @@ app.prepare().then(async () => {
const parsedLibrary = FS.readFileSync('./.data/library.json', 'utf8');
state.library = JSON.parse(parsedLibrary).library;
}
// NOTE(jim): Local settings retrieval or creation
// TODO(jim): Move this to postgres later.
if (!FS.existsSync('./.data/local-settings.json')) {
const localSettingsSchema = {
local: { photo: null, name: `node-${uuid()}` },
};
FS.writeFileSync('./.data/local-settings.json', JSON.stringify(localSettingsSchema));
state.local = localSettingsSchema.local;
} else {
const parsedLocal = FS.readFileSync('./data/local-settings.json', 'utf8');
state.local = JSON.parse(parsedLocal).local;
}
} catch (e) {
console.log('[ prototype ] "/" - WILL REDIRECT TO /SYSTEM ');
console.log('[ prototype ] SLATE WILL NOT RUN LOCALLY UNTIL YOU HAVE ');
console.log('[ prototype ] PROPERLY CONFIGURED POWERGATE AND ');
console.log('[ prototype ] CONNECTED TO THE FILECOIN NETWORK (DEVNET/TESTNET) ');
console.log(e);
console.log('[ prototype ] "/" -- WILL REDIRECT TO /SYSTEM ');
console.log('[ prototype ] SLATE WILL NOT RUN LOCALLY UNTIL YOU HAVE ');
console.log('[ prototype ] PROPERLY CONFIGURED POWERGATE AND ');
console.log('[ prototype ] CONNECTED TO THE FILECOIN NETWORK (DEVNET/TESTNET) ');
}
}
@ -196,6 +220,7 @@ app.prepare().then(async () => {
}
}
// NOTE(jim): Writes the updated deal state.
if (write) {
FS.writeFileSync('./.data/library.json', JSON.stringify({ library: state.library }));
}
@ -238,6 +263,7 @@ app.prepare().then(async () => {
}
}
// NOTE(jim): Writes the added file.
if (pushed) {
FS.writeFileSync('./.data/library.json', JSON.stringify({ library: state.library }));
}
@ -263,14 +289,20 @@ app.prepare().then(async () => {
if (error) {
return res.status(500).send({ error });
} else {
const newPath = form.uploadDir + 'avatar.png';
const newName = `avatar-${uuid()}.png`;
const newPath = form.uploadDir + newName;
FS.rename(files.image.path, newPath, function (err) {});
// NOTE(jim): updates avatar photo.
state.local.photo = `/static/system/${newName}`;
FS.writeFileSync('./.data/local-settings.json', JSON.stringify({ local: { ...state.local } }));
state = await Utilities.emitState({
state,
client,
PG: PowerGate,
});
return res.status(200).send({ success: true });
}
});
@ -288,6 +320,14 @@ app.prepare().then(async () => {
return res.status(200).send({ success: true, data });
});
server.post('/_/local-settings', async (req, res) => {
state.local = { ...state.local, ...req.body.local };
FS.writeFileSync('./.data/local-settings.json', JSON.stringify({ local: { ...state.local } }));
state = await Utilities.emitState({ state, client, PG: PowerGate });
return res.status(200).send({ success: true });
});
server.post('/_/wallet/create', async (req, res) => {
let data;
try {