prototype: local file storage and local preview

This commit is contained in:
jimmylee 2020-06-09 11:00:36 -07:00
parent bf4761eb37
commit 3736ed162c
68 changed files with 744 additions and 1047 deletions

14
.gitignore vendored
View File

@ -1,10 +1,9 @@
.next
.data
.env
.DS_STORE
DS_STORE
package-lock.json
node_modules
DS_STORE
/**/*/package-lock.json
/**/*/.DS_STORE
@ -12,5 +11,12 @@ DS_STORE
/**/*/.next
/**/*/.data
.data/**/*
.library/**/*
.data/*
!.data/.gitkeep
public/static/files/*
!public/static/files/.gitkeep
public/static/system/*
!public/static/system/.gitkeep
!public/static/system/avatar.png

View File

@ -8,7 +8,9 @@ const REQUEST_HEADERS = {
'Content-Type': 'application/json',
};
const SERVER_PATH = 'http://localhost:1337';
const dev = process.env.NODE_ENV !== 'production';
const SERVER_PATH = dev ? 'http://localhost:1337' : 'https://filecoin.onrender.com';
export const rehydrateViewer = async () => {
const options = {

View File

@ -1,481 +1,17 @@
import * as Strings from '~/common/strings';
import * as Data from '~/common/data';
const LOCAL_CONFIG = `
"Identity": {
"PeerID": "Qma9T5YraSnpRDZqRR4krcS",
"PubKey": "CAAS4AQwggJcAgEAAoGBAMB"
"PrivKey": "CAASogEwgZ8wDQYJKoZIhvd"
}
`;
export const getInitialState = () => {
return {
name: 'Andrew Hill',
photoURL: '/static/avatar-andrew-hill.jpg',
config: LOCAL_CONFIG,
upload_bandwidth: 40023,
download_bandwidth: 12323,
settings_deal_country: `3`,
settings_deals_auto_approve: true,
settings_deal_default_duration: 1,
settings_deal_replication_factor: 1,
settings_deal_maximum_storage_payment: 100,
settings_deal_default_miners: 't0123, t0124, t0125',
notifications: [
{
id: 6,
type: 'DEFAULT',
text: 'cats-are-cool.png was retrieved from the Filecoin network.',
createdAt: '2017-01-01 00:00:00 UTC',
},
{
id: 5,
type: 'DEFAULT',
text: 'cats-are-cool.png was transfered from Miner B over to your local storage.',
createdAt: '2016-12-28 00:00:00 UTC',
},
{
id: 4,
type: 'DEFAULT',
text: 'A transfer has been initiated for cats-are-cool.png from Miner B.',
createdAt: '2016-12-27 00:00:00 UTC',
},
{
id: 3,
type: 'DEFAULT',
text: 'cats-are-cool.png retrieval deal accepted by Miner B.',
createdAt: '2016-12-26 00:00:00 UTC',
},
{
id: 2,
type: 'DEFAULT',
text: 'You have proposed a deal with Miner B for cats-are-cool.png.',
createdAt: '2016-12-25 00:00:00 UTC',
},
{
id: 1,
type: 'DEFAULT',
text: 'cats-are-cool-2.png is stored with Miner A on the Filecoin Network.',
createdAt: '2016-12-24 00:00:00 UTC',
},
{
id: 1,
type: 'DEFAULT',
text: 'cats-are-cool-2.png has been transferred to Miner A.',
createdAt: '2016-12-23 00:00:00 UTC',
},
{
id: 1,
type: 'DEFAULT',
text: 'A transfer has begun for cats-are-cool-2.png to Miner A.',
createdAt: '2016-12-22 00:00:00 UTC',
},
{
id: 1,
type: 'DEFAULT',
text: 'The storage deal for cats-are-cool-2.png has been accepted by Miner A.',
createdAt: '2016-12-21 00:00:00 UTC',
},
{
id: 1,
type: 'DEFAULT',
text: 'A storage deal for cats-are-cool-2.png has been proposed to Miner A.',
createdAt: '2016-12-20 00:00:00 UTC',
},
{
id: 1,
type: 'DEFAULT',
text: 'Searching for a Miner A to store cats-are-cool-2.png.',
createdAt: '2016-12-19 00:00:00 UTC',
},
],
payment_channels_active: [
{
id: 1,
category: 1,
'channel-id': 'example-channel-id-1',
'max-value': Strings.formatAsFilecoin(3232100),
'current-value': Strings.formatAsFilecoin(423233),
redeemable: 'Redeem',
},
{
id: 2,
category: 2,
'channel-id': 'example-channel-id-2',
'max-value': Strings.formatAsFilecoin(3232100),
'current-value': Strings.formatAsFilecoin(423233),
},
],
payment_channels_redeemed: [
{
id: 1,
category: 1,
'channel-id': 'example-channel-id-3',
'max-value': Strings.formatAsFilecoin(3232100),
'redeemed-value': Strings.formatAsFilecoin(423233),
},
{
id: 2,
category: 1,
'channel-id': 'example-channel-id-4',
'max-value': Strings.formatAsFilecoin(223100),
'redeemed-value': Strings.formatAsFilecoin(12200),
},
],
data_transfers: [
{
id: 1,
'data-cid': '44Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': '55Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'data-source': 'LOCAL',
'data-destination': 't05141',
size: Strings.bytesToSize(202000),
},
{
id: 2,
'data-cid': '66Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': '77Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'data-source': 'LOCAL',
'data-destination': 't05141',
size: Strings.bytesToSize(202000),
},
{
id: 3,
'data-cid': '44Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': '55Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'data-source': 't05141',
'data-destination': 'LOCAL',
size: Strings.bytesToSize(202000),
},
],
peers: [
{
id: 1,
'peer-avatar': '/static/avatar-adrian-lanzafame.png',
online: true,
'chain-head': 'bafy2bzacecvuycbaik2dn4ktxeyrzrtuviqxvhbk67qxt5lqgrwogxhk4twx6',
height: 8888,
location: 1,
upload: 22222,
download: 11111,
},
{
id: 2,
'peer-avatar': '/static/avatar-andrew-hill.jpg',
online: true,
'chain-head': 'bafy2bzacecvuycbaik2dn4ktxeyrzrtuviqxvhbk67qxt5lqgrwogxhk4twx6',
height: 8888,
location: 2,
upload: 22222,
download: 11111,
},
{
id: 3,
'peer-avatar': '/static/avatar-colin-evran.jpg',
'chain-head': 'bafy2bzacecvuycbaik2dn4ktxeyrzrtuviqxvhbk67qxt5lqgrwogxhk4twx6',
height: 8888,
location: 3,
upload: 22222,
download: 11111,
},
{
id: 4,
'peer-avatar': '/static/avatar-juan-benet.png',
'chain-head': 'bafy2bzacecvuycbaik2dn4ktxeyrzrtuviqxvhbk67qxt5lqgrwogxhk4twx6',
height: 8888,
location: 3,
upload: 22222,
download: 11111,
},
],
deals: [
{
id: 1,
'deal-category': 1,
'data-cid': '14Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': '23Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
miner: 'Example Miner A',
price: Strings.formatAsFilecoin(1000),
'auto-renew': 1,
remaining: null,
status: 1,
},
{
id: 2,
'deal-category': 1,
'data-cid': '34Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': '56Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
miner: 'Example Miner B',
price: Strings.formatAsFilecoin(1000),
'auto-renew': 1,
remaining: null,
status: 2,
},
{
id: 3,
'deal-category': 1,
'data-cid': '78Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': '89Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
miner: 'Example Miner C',
price: Strings.formatAsFilecoin(1000),
'auto-renew': 2,
remaining: null,
status: 3,
},
{
id: 4,
'deal-category': 1,
'data-cid': '99Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': '11Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
miner: 'Example Miner D',
price: Strings.formatAsFilecoin(1000),
'auto-renew': 2,
remaining: null,
status: 4,
},
{
id: 5,
'deal-category': 1,
'data-cid': '12Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': '34Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
miner: 'Example Miner E',
price: Strings.formatAsFilecoin(1000),
'auto-renew': 2,
remaining: null,
status: 5,
},
{
id: 6,
'deal-category': 1,
'data-cid': '56Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': '78Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
miner: 'Example Miner F',
price: Strings.formatAsFilecoin(1000),
'auto-renew': 1,
remaining: Strings.getRemainingTime(184000),
status: 6,
},
{
id: 7,
'deal-category': 2,
'data-cid': 'abY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': 'cdY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
miner: 'Example Miner A',
price: Strings.formatAsFilecoin(100),
status: 1,
},
{
id: 8,
'deal-category': 2,
'data-cid': 'efY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': 'ghY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
miner: 'Example Miner B',
price: Strings.formatAsFilecoin(100),
status: 2,
},
{
id: 9,
'deal-category': 2,
'data-cid': 'ilY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': 'qqY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
miner: 'Example Miner C',
price: Strings.formatAsFilecoin(100),
status: 3,
},
{
id: 10,
'deal-category': 2,
'data-cid': 'xxY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': 'wwY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
miner: 'Example Miner D',
price: Strings.formatAsFilecoin(100),
status: 4,
},
{
id: 11,
'deal-category': 2,
'data-cid': 'zzY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
'deal-cid': 'qzY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU',
miner: 'Example Miner E',
price: Strings.formatAsFilecoin(100),
status: 5,
},
],
addresses: [
{
value: '1',
name: 'Capricorn',
address: Data.EXAMPLE_ADDRESSES[0],
transactions: [
{
id: 1,
category: 1,
amount: Strings.formatAsFilecoin(11000),
source: Data.EXTERNAL_ADDRESSES[0],
destination: 'Capricorn',
date: Strings.toDate('2017-01-01 00:00:00 UTC'),
status: 1,
},
{
id: 2,
category: 2,
amount: Strings.formatAsFilecoin(11000),
source: 'Capricorn',
destination: Data.EXTERNAL_ADDRESSES[0],
date: Strings.toDate('2017-01-01 00:00:00 UTC'),
status: 2,
},
{
id: 3,
category: 1,
amount: Strings.formatAsFilecoin(11000),
source: Data.EXTERNAL_ADDRESSES[0],
destination: 'Capricorn',
date: Strings.toDate('2017-01-01 00:00:00 UTC'),
status: 2,
},
{
id: 4,
category: 2,
amount: Strings.formatAsFilecoin(11000),
source: 'Capricorn',
destination: Data.EXTERNAL_ADDRESSES[0],
date: Strings.toDate('2017-01-01 00:00:00 UTC'),
status: 1,
},
],
type: 'SECP256K',
balance: 10230,
deals: 42,
},
{
value: '2',
name: 'Aquarius',
address: Data.EXAMPLE_ADDRESSES[1],
transactions: [],
type: 'MULTISIG',
balance: 0,
deals: 0,
},
{
value: '3',
name: 'Pisces',
address: Data.EXAMPLE_ADDRESSES[2],
transactions: [],
type: 'BLS',
balance: 0,
deals: 0,
},
],
};
};
const getFolderById = (id) => {
for (let i = 0; i < Data.EXAMPLE_FOLDERS.length; i++) {
if (id === Data.EXAMPLE_FOLDERS[i].id) {
return Data.EXAMPLE_FOLDERS[i];
const constructFilesTreeForNavigation = (library) => {
for (let i = 0; i < library.length; i++) {
for (let j = 0; j < library[i].children.length; j++) {
let e = library[i].children[j];
if (e.decorator === 'FILE') {
library[i].children[j].ignore = true;
}
}
}
return null;
return library;
};
const getFileById = (id) => {
for (let i = 0; i < Data.EXAMPLE_FILES.length; i++) {
if (id === Data.EXAMPLE_FILES[i].id) {
return Data.EXAMPLE_FILES[i];
}
}
};
const constructFilesTreeForNavigation = () => {
const SCAFFOLD = [
{
folderId: `folder-root`,
children: [
`folder-1`,
`folder-2`,
`file-1`,
`file-2`,
`file-3`,
`file-8`,
`file-9`,
`file-10`,
`file-11`,
`file-12`,
`file-13`,
`file-14`,
`file-15`,
`file-16`,
`file-17`,
],
},
{
folderId: `folder-1`,
children: [`folder-3`, `file-4`, `file-5`, `file-6`],
},
{
folderId: `folder-2`,
children: [],
},
{
folderId: `folder-3`,
children: [`file-7`],
},
];
const navigation = {
...getFolderById('folder-root'),
};
// TODO(jim): Refactor after proving the concept this is ugly.
SCAFFOLD.forEach((o) => {
if (navigation.children) {
for (let i = 0; i < navigation.children.length; i++) {
if (navigation.children[i] && navigation.children[i].id === o.folderId) {
if (!navigation.children[i].children) {
navigation.children[i].children = o.children.map((each) => {
const folder = getFolderById(each);
if (folder) {
return folder;
}
const file = getFileById(each);
if (file) {
return { ...file, ignore: true };
}
return null;
});
}
}
}
}
if (navigation.id === o.folderId) {
if (!navigation.children) {
navigation.children = o.children.map((each) => {
const folder = getFolderById(each);
if (folder) {
return folder;
}
const file = getFileById(each);
if (file) {
return { ...file, ignore: true };
}
return null;
});
}
}
});
return navigation;
};
export const NavigationState = [
export const generateNavigationState = (library) => [
{
id: 1,
name: 'Home',
@ -488,79 +24,8 @@ export const NavigationState = [
name: 'Wallet',
pageTitle: 'your wallet and addresses',
decorator: 'WALLET',
/*
children: [
{
id: 3,
name: 'Payment Channels',
children: null,
pageTitle: 'your payment channels',
decorator: 'CHANNELS',
},
],
*/
},
constructFilesTreeForNavigation(),
{
id: 5,
name: 'Deals',
pageTitle: 'your deals',
decorator: 'DEALS',
children: [
{
id: 6,
name: 'Data Transfer',
pageTitle: 'your data transfers',
decorator: 'DATA_TRANSFER',
children: null,
},
],
},
/*
{
id: 9,
name: 'Stats',
pageTitle: 'your filecoin usage',
decorator: 'STATS',
children: [
{
id: 10,
name: 'Storage Market',
pageTitle: 'the storage market',
decorator: 'STORAGE_MARKET',
children: null,
},
{
id: 11,
name: 'Miners',
pageTitle: 'miners',
decorator: 'MINERS',
children: null,
},
],
},
{
id: 7,
name: 'Status',
pageTitle: 'your status',
decorator: 'STATUS',
children: null,
},
{
id: 8,
name: 'Peers',
pageTitle: 'your peers',
decorator: 'PEERS',
children: null,
},
{
id: 12,
name: 'Logs',
pageTitle: 'your logs',
decorator: 'LOGS',
children: null,
},
*/
...constructFilesTreeForNavigation(library),
{
id: 13,
name: 'Edit account',

View File

@ -35,13 +35,26 @@ const transformPeers = (peersList) => {
};
export const getInitialState = (props) => {
const { status, messageList, peersList, addrsList, info } = props;
const { status, messageList, peersList, addrsList, info, library } = props;
if (!info || !info.id) {
return {
id: null,
notifications: [],
payment_channels_active: [],
payment_channels_redeemed: [],
data_transfers: [],
peers: [],
deals: [],
addresses: [],
library: [],
};
}
return {
id: info.id,
name: 'New Node',
photoURL: '/static/system/avatar.png',
config: '',
upload_bandwidth: 0,
download_bandwidth: 0,
@ -53,7 +66,7 @@ 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.dealDuration,
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,
@ -68,5 +81,6 @@ export const getInitialState = (props) => {
peers: transformPeers(peersList),
deals: [],
addresses: transformAddresses(addrsList, info),
library,
};
};

View File

@ -11,6 +11,7 @@ const STYLES_AVATAR = css`
background-position: 50% 50%;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
position: relative;
background-color: ${Constants.system.black};
`;
const STYLES_AVATAR_ONLINE = css`

View File

@ -6,6 +6,16 @@ import * as System from '~/components/system';
import { css } from '@emotion/react';
const STYLES_FILE_HIDDEN = css`
height: 1px;
width: 1px;
opacity: 0;
visibility: hidden;
position: fixed;
top: -1px;
left: -1px;
`;
const STYLES_FOCUS = css`
font-size: ${Constants.typescale.lvl1};
font-family: 'inter-medium';
@ -49,13 +59,64 @@ const SELECT_MENU_MAP = {
export default class SidebarFileStorageDeal extends React.Component {
state = {
settings_deal_duration: this.props.viewer.settings_cold_default_duration,
settings_replication_factor: this.props.viewer.settings_cold_default_replication_factor,
file: null,
settings_cold_default_duration: this.props.viewer.settings_cold_default_duration,
settings_cold_default_replication_factor: this.props.viewer.settings_cold_default_replication_factor,
};
_handleSubmit = () => {
alert('TODO: Make a storage deal');
this.props.onSubmit({});
_handleUpload = async (e) => {
e.persist();
let file = e.target.files[0];
if (!file) {
alert('Something went wrong');
return;
}
let data = new FormData();
data.append('image', file);
const options = {
method: 'POST',
headers: {
Accept: 'application/json',
},
body: data,
};
const response = await fetch(`/_/storage/${file.name}`, options);
const json = await response.json();
if (json && json.success) {
this.setState({ file });
}
};
_handleMakeDeal = async (src) => {
console.log(src);
const options = {
method: 'POST',
credentials: 'include',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ src }),
};
const response = await fetch('/_/deals/storage', options);
const json = await response.json();
return json;
};
_handleSubmit = async (e) => {
e.persist();
const path = `/public/static/files/${this.state.file.name}`;
await this._handleMakeDeal(path);
await this.props.onSubmit({});
};
_handleCancel = () => {
@ -82,56 +143,68 @@ export default class SidebarFileStorageDeal extends React.Component {
return (
<React.Fragment>
<System.P style={{ fontFamily: 'inter-semi-bold' }}>Upload a file to the network</System.P>
<input css={STYLES_FILE_HIDDEN} type="file" id="file" onChange={this._handleUpload} />
<img src="/static/test-image-upload.jpg" css={STYLES_IMAGE_PREVIEW} />
{this.state.file ? (
<div>
<img src={`/static/files/${this.state.file.name}`} css={STYLES_IMAGE_PREVIEW} />
<div css={STYLES_ITEM}>
<div css={STYLES_FOCUS}>test-image-upload.jpg</div>
<div css={STYLES_SUBTEXT}>Name</div>
</div>
<div css={STYLES_ITEM}>
<div css={STYLES_FOCUS}>{this.state.file.name}</div>
<div css={STYLES_SUBTEXT}>Name</div>
</div>
<div css={STYLES_ITEM}>
<div css={STYLES_FOCUS}>42 MB</div>
<div css={STYLES_SUBTEXT}>File size</div>
</div>
<div css={STYLES_ITEM}>
<div css={STYLES_FOCUS}>{this.state.file.size}</div>
<div css={STYLES_SUBTEXT}>File size</div>
</div>
</div>
) : null}
<System.ButtonSecondaryFull style={{ marginTop: 24 }}>Change file</System.ButtonSecondaryFull>
<System.ButtonSecondaryFull type="label" htmlFor="file" style={{ marginTop: 24 }}>
Add file
</System.ButtonSecondaryFull>
<System.Input
containerStyle={{ marginTop: 48 }}
label="Deal duration"
name="settings_deal_duration"
value={this.state.settings_deal_duration}
onChange={this._handleChange}
/>
{this.state.file ? (
<System.Input
containerStyle={{ marginTop: 48 }}
label="Deal duration"
name="settings_cold_default_duration"
placeholder="Type in months"
type="number"
value={this.state.settings_cold_default_duration}
onChange={this._handleChange}
/>
) : null}
<System.Input
containerStyle={{ marginTop: 24 }}
label="Replication factor"
name="settings_replication_factor"
value={this.state.settings_replication_factor}
onChange={this._handleChange}
/>
{this.state.file ? (
<System.Input
containerStyle={{ marginTop: 24 }}
label="Replication factor"
name="settings_cold_default_replication_factor"
value={this.state.settings_cold_default_replication_factor}
onChange={this._handleChange}
/>
) : null}
<System.SelectMenuFull
containerStyle={{ marginTop: 24 }}
name="address"
label="Payment address"
value={this.props.selected.address}
category="address"
onChange={this.props.onSelectedChange}
options={this.props.viewer.addresses}>
{currentAddress.name}
</System.SelectMenuFull>
{this.state.file ? (
<System.SelectMenuFull
containerStyle={{ marginTop: 24 }}
name="address"
label="Payment address"
value={this.props.selected.address}
category="address"
onChange={this.props.onSelectedChange}
options={this.props.viewer.addresses}>
{currentAddress.name}
</System.SelectMenuFull>
) : null}
<div css={STYLES_ITEM}>
<div css={STYLES_FOCUS}>2 FIL</div>
<div css={STYLES_SUBTEXT}>Last order price</div>
</div>
<System.ButtonPrimaryFull style={{ marginTop: 48 }} onClick={this._handleSubmit}>
Make storage deal
</System.ButtonPrimaryFull>
{this.state.file ? (
<System.ButtonPrimaryFull style={{ marginTop: 48 }} onClick={this._handleSubmit}>
Make storage deal
</System.ButtonPrimaryFull>
) : null}
</React.Fragment>
);
}

View File

@ -431,11 +431,6 @@ export class Table extends React.Component {
onAction: () => console.log('No action function set'),
};
// NOTE(jim): Local state for local filtering.
state = {
data: this.props.data,
};
_handleChange = (value) => {
this.props.onChange({
target: {
@ -446,7 +441,7 @@ export class Table extends React.Component {
};
render() {
const { data } = this.state;
const { data } = this.props;
const ac = {};
@ -642,6 +637,10 @@ const STYLES_BUTTON_PRIMARY_FULL = css`
`;
export const ButtonPrimaryFull = (props) => {
if (props.type === 'label') {
return <label css={STYLES_BUTTON_PRIMARY_FULL} {...props} />;
}
return <button css={STYLES_BUTTON_PRIMARY_FULL} {...props} />;
};
@ -665,6 +664,10 @@ const STYLES_BUTTON_SECONDARY = css`
`;
export const ButtonSecondary = (props) => {
if (props.type === 'label') {
return <label css={STYLES_BUTTON_SECONDARY} {...props} />;
}
return <button css={STYLES_BUTTON_SECONDARY} {...props} />;
};
@ -688,6 +691,10 @@ const STYLES_BUTTON_SECONDARY_FULL = css`
`;
export const ButtonSecondaryFull = (props) => {
if (props.type === 'label') {
return <label css={STYLES_BUTTON_SECONDARY_FULL} {...props} />;
}
return <button css={STYLES_BUTTON_SECONDARY_FULL} {...props} />;
};

View File

@ -1,30 +1,32 @@
import * as React from "react";
import * as Constants from "~/common/constants";
import * as SVG from "~/components/system/svg";
import * as OldSVG from "~/common/svg";
import * as Strings from "~/common/strings";
import * as React from 'react';
import * as Constants from '~/common/constants';
import * as SVG from '~/components/system/svg';
import * as OldSVG from '~/common/svg';
import * as Strings from '~/common/strings';
import { css } from "@emotion/react";
import { Tooltip } from "react-tippy";
import { css } from '@emotion/react';
import { Tooltip } from 'react-tippy';
import Avatar from "~/components/core/Avatar";
import Avatar from '~/components/core/Avatar';
const STORAGE_DEAL_STATES = {
"1": "Searching for miners.",
"2": "Proposing storage deal.",
"3": "Accepted by miners.",
"4": "Data transfer in progress.",
"5": "Data transfer complete.",
"6": "Stored",
'0': 'Local file only.',
'1': 'Searching for miners.',
'2': 'Proposing storage deal.',
'3': 'Accepted by miners.',
'4': 'Data transfer in progress.',
'5': 'Data transfer complete.',
'6': 'Stored on network.',
};
const RETRIEVAL_DEAL_STATES = {
"1": "Available on network",
"2": "Retrieval deal proposed.",
"3": "Retrieval deal accepted.",
"4": "Data transfer in progress.",
"5": "Data transfer completed.",
"6": "Retrieved",
'0': 'Local file',
'1': 'Available on network',
'2': 'Retrieval deal proposed.',
'3': 'Retrieval deal accepted.',
'4': 'Data transfer in progress.',
'5': 'Data transfer completed.',
'6': 'Retrieved from network.',
};
const COMPONENTS_ICON = {
@ -34,7 +36,7 @@ const COMPONENTS_ICON = {
const STYLES_TABLE_TAG = css`
font-weight: 400;
font-family: "inter-semi-bold";
font-family: 'inter-semi-bold';
letter-spacing: 0.2px;
padding: 4px 6px 4px 6px;
font-size: 10px;
@ -46,21 +48,18 @@ const STYLES_TABLE_TAG = css`
`;
const COMPONENTS_TRANSACTION_DIRECTION = {
"1": (
'1': (
<span css={STYLES_TABLE_TAG} style={{ background: Constants.system.green }}>
+ incoming
</span>
),
"2": <span css={STYLES_TABLE_TAG}>- outgoing</span>,
'2': <span css={STYLES_TABLE_TAG}>- outgoing</span>,
};
const COMPONENTS_TRANSACTION_STATUS = {
"1": <span css={STYLES_TABLE_TAG}>complete</span>,
"2": (
<span
css={STYLES_TABLE_TAG}
style={{ background: Constants.system.yellow }}
>
'1': <span css={STYLES_TABLE_TAG}>complete</span>,
'2': (
<span css={STYLES_TABLE_TAG} style={{ background: Constants.system.yellow }}>
pending
</span>
),
@ -115,7 +114,7 @@ const STYLES_CONTENT_BUTTON = css`
`;
const STYLES_TABLE_CONTENT_LINK = css`
font-family: "inter-medium";
font-family: 'inter-medium';
text-decoration: underline;
cursor: pointer;
@ -140,10 +139,7 @@ export const TableColumn = (props) => {
) : null;
return (
<span
css={props.top ? STYLES_TOP_COLUMN : STYLES_COLUMN}
style={props.style}
>
<span css={props.top ? STYLES_TOP_COLUMN : STYLES_COLUMN} style={props.style}>
<span css={STYLES_CONTENT}>{props.children}</span>
{tooltipElement}
{copyableElement}
@ -151,94 +147,75 @@ export const TableColumn = (props) => {
);
};
export const TableContent = ({
type,
text,
action,
data = {},
onNavigateTo,
onAction,
}) => {
export const TableContent = ({ type, text, action, data = {}, onNavigateTo, onAction }) => {
const { status, online } = data;
if (Strings.isEmpty(text)) {
if (text === null || text === undefined) {
return null;
}
switch (type) {
case "DEAL_CATEGORY":
case 'DEAL_CATEGORY':
return <React.Fragment>{text == 1 ? 'Storage' : 'Retrieval'}</React.Fragment>;
case 'LOCATION':
return 'United States';
case 'BUTTON':
return (
<React.Fragment>{text == 1 ? "Storage" : "Retrieval"}</React.Fragment>
);
case "LOCATION":
return "United States";
case "BUTTON":
return (
<span
css={STYLES_TABLE_CONTENT_LINK}
onClick={() => onAction({ type: "SIDEBAR", value: action })}
>
<span css={STYLES_TABLE_CONTENT_LINK} onClick={() => onAction({ type: 'SIDEBAR', value: action })}>
{text}
</span>
);
case "TRANSACTION_DIRECTION":
case 'TRANSACTION_DIRECTION':
return COMPONENTS_TRANSACTION_DIRECTION[text];
case "TRANSACTION_STATUS":
case 'TRANSACTION_STATUS':
return COMPONENTS_TRANSACTION_STATUS[text];
case "ICON":
case 'ICON':
return COMPONENTS_ICON[text];
case "AVATAR":
case 'AVATAR':
return <Avatar url={text} size={40} online={online} />;
case "DEAL_STATUS_RETRIEVAL":
case 'DEAL_STATUS_RETRIEVAL':
return RETRIEVAL_DEAL_STATES[`${text}`];
case "DEAL_STATUS":
return data["deal-category"] === 1
? STORAGE_DEAL_STATES[`${text}`]
: RETRIEVAL_DEAL_STATES[`${text}`];
case "BANDWIDTH_UPLOAD":
case 'DEAL_STATUS':
return data['deal_category'] === 1 ? STORAGE_DEAL_STATES[`${text}`] : RETRIEVAL_DEAL_STATES[`${text}`];
case 'BANDWIDTH_UPLOAD':
return (
<React.Fragment>
<SVG.BandwidthUp height="16px" style={{ marginRight: 8 }} />
{Strings.bytesToSize(text)}
</React.Fragment>
);
case "BANDWIDTH_DOWNLOAD":
case 'BANDWIDTH_DOWNLOAD':
return (
<React.Fragment>
<SVG.BandwidthDown height="16px" style={{ marginRight: 8 }} />
{Strings.bytesToSize(text)}
</React.Fragment>
);
case "MINER_AVAILABILITY":
case 'MINER_AVAILABILITY':
return text == 1 ? (
<span
css={STYLES_TABLE_TAG}
style={{ background: Constants.system.green }}
>
<span css={STYLES_TABLE_TAG} style={{ background: Constants.system.green }}>
Online
</span>
) : null;
case "DEAL_AUTO_RENEW":
case 'DEAL_AUTO_RENEW':
return text == 1 ? (
<span
css={STYLES_TABLE_TAG}
style={{ background: Constants.system.brand }}
>
<span css={STYLES_TABLE_TAG} style={{ background: Constants.system.brand }}>
true
</span>
) : (
<span css={STYLES_TABLE_TAG}>false</span>
);
case "NOTIFICATION_ERROR":
case 'NOTIFICATION_ERROR':
return (
<span
css={STYLES_TABLE_TAG}
style={{ background: Constants.system.red }}
>
{text} {Strings.pluralize("error", text)}
<span css={STYLES_TABLE_TAG} style={{ background: Constants.system.red }}>
{text} {Strings.pluralize('error', text)}
</span>
);
case "FILE_LINK":
case 'FILE_DATE':
return Strings.toDate(text);
case 'FILE_SIZE':
return Strings.bytesToSize(text, 2);
case 'FILE_LINK':
// NOTE(jim): Special case to prevent navigation.
if (!data) {
return text;
@ -249,44 +226,45 @@ export const TableContent = ({
return (
<span
css={STYLES_TABLE_CONTENT_LINK}
onClick={() =>
onAction({ type: "NAVIGATE", value: data.folderId, data })
}
>
onClick={() => onAction({ type: 'NAVIGATE', value: data.folderId, data })}>
{text}
</span>
);
}
// NOTE(jim): Special case for navigating to a sidebar.
if (data && data["retrieval-status"] === 1) {
if (data && data['retrieval_status'] === 1) {
return (
<span
css={STYLES_TABLE_CONTENT_LINK}
onClick={() =>
onAction({
type: "SIDEBAR",
value: "SIDEBAR_FILE_STORAGE_DEAL",
type: 'SIDEBAR',
value: 'SIDEBAR_FILE_STORAGE_DEAL',
})
}
>
}>
{text}
</span>
);
}
// NOTE(jim): Special case to prevent navigation.
if (data && data["retrieval-status"] !== 6) {
if (
data &&
(data['retrieval_status'] === 5 ||
data['retrieval_status'] === 4 ||
data['retrieval_status'] === 3 ||
data['retrieval_status'] === 2)
) {
return (
<span
onClick={() =>
onAction({
name: "File does not exist",
type: "ACTION",
value: "ACTION_FILE_MISSING",
name: 'File does not exist',
type: 'ACTION',
value: 'ACTION_FILE_MISSING',
})
}
>
}>
{text}
</span>
);
@ -294,10 +272,7 @@ export const TableContent = ({
// NOTE(jim): Navigates to file.
return (
<span
css={STYLES_TABLE_CONTENT_LINK}
onClick={() => onNavigateTo({ id: 15 }, data)}
>
<span css={STYLES_TABLE_CONTENT_LINK} onClick={() => onNavigateTo({ id: 15 }, data)}>
{text}
</span>
);

View File

@ -15,7 +15,7 @@
"@emotion/css": "11.0.0-next.12",
"@emotion/react": "11.0.0-next.12",
"@emotion/server": "11.0.0-next.12",
"@textile/powergate-client": "0.1.0-beta.7",
"@textile/powergate-client": "0.1.0-beta.9",
"babel-plugin-module-resolver": "^4.0.0",
"body-parser": "^1.19.0",
"chart.js": "^2.9.3",
@ -29,6 +29,7 @@
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-tippy": "^1.3.4",
"three": "^0.108.0"
"three": "^0.108.0",
"ws": "^7.3.0"
}
}

View File

@ -64,36 +64,46 @@ const getCurrentNavigationStateById = (navigation, targetId) => {
export const getServerSideProps = async (context) => {
if (context.query && context.query.production) {
return { production: true };
return { production: true, ...context.query };
}
const data = await Actions.rehydrateViewer();
return {
props: { ...data.data },
props: { ...context.query, ...data.data },
};
};
export default class IndexPage extends React.Component {
_socket = null;
state = {
history: [{ id: 1, scrollTop: 0 }],
currentIndex: 0,
data: null,
selected: {
address: null,
address: '',
},
viewer: this.props.production ? Fixtures.getInitialState() : State.getInitialState(this.props),
viewer: State.getInitialState(this.props),
sidebar: null,
};
async componentDidMount() {
// TODO(jim): You don't really need this.
console.log(this.props);
componentDidMount() {
this._socket = new WebSocket(`ws://localhost:${this.props.wsPort}`);
this._socket.onmessage = (m) => {
console.log(m);
if (m.type === 'message') {
const parsed = JSON.parse(m.data);
if (parsed.action === 'UPDATE_VIEWER') {
this.rehydrate({ data: parsed.data });
}
}
};
}
rehydrate = async () => {
const viewer = await Actions.rehydrateViewer();
this.setState({ viewer: { ...State.getInitialState(viewer.data) } });
rehydrate = async ({ data }) => {
this.setState({ viewer: { ...State.getInitialState(data) } });
};
_handleSubmit = async (data) => {
@ -108,8 +118,6 @@ export default class IndexPage extends React.Component {
type: data.wallet_type,
makeDefault: data.makeDefault,
});
await this.rehydrate();
}
if (data.type === 'SEND_WALLET_ADDRESS_FILECOIN') {
@ -118,8 +126,6 @@ export default class IndexPage extends React.Component {
target: data.target,
amount: data.amount,
});
await this.rehydrate();
}
this._handleDismissSidebar();
@ -258,21 +264,26 @@ export default class IndexPage extends React.Component {
};
render() {
const next = this.state.history[this.state.currentIndex];
const current = getCurrentNavigationStateById(Fixtures.NavigationState, next.id);
if (this.props.production) {
return null;
}
const navigation = (
const navigation = Fixtures.generateNavigationState(this.state.viewer.library);
const next = this.state.history[this.state.currentIndex];
const current = getCurrentNavigationStateById(navigation, next.id);
const navigationElement = (
<ApplicationNavigation
viewer={this.state.viewer}
activeId={current.target.id}
activeIds={current.activeIds}
navigation={Fixtures.NavigationState}
navigation={navigation}
onNavigateTo={this._handleNavigateTo}
onAction={this._handleAction}
/>
);
const header = (
const headerElement = (
<ApplicationHeader
viewer={this.state.viewer}
pageTitle={current.target.pageTitle}
@ -286,7 +297,6 @@ export default class IndexPage extends React.Component {
);
const scene = React.cloneElement(this.scenes[current.target.decorator], {
rehydrate: this.rehydrate,
viewer: this.state.viewer,
selected: this.state.selected,
data: current.target,
@ -299,9 +309,9 @@ export default class IndexPage extends React.Component {
onForward: this._handleForward,
});
let sidebar;
let sidebarElement;
if (this.state.sidebar) {
sidebar = React.cloneElement(this.state.sidebar, {
sidebarElement = React.cloneElement(this.state.sidebar, {
viewer: this.state.viewer,
selected: this.state.selected,
onSelectedChange: this._handleSelectedChange,
@ -318,9 +328,9 @@ export default class IndexPage extends React.Component {
<React.Fragment>
<WebsitePrototypeWrapper title={title} description={description} url={url}>
<ApplicationLayout
navigation={navigation}
header={header}
sidebar={sidebar}
navigation={navigationElement}
header={headerElement}
sidebar={sidebarElement}
onDismissSidebar={this._handleDismissSidebar}>
{scene}
</ApplicationLayout>

View File

@ -1,6 +1,6 @@
import * as React from "react";
import * as React from 'react';
import { css } from "@emotion/react";
import { css } from '@emotion/react';
const STYLES_LAYOUT = css`
max-width: 928px;
@ -23,13 +23,13 @@ const STYLES_PARAGRAPH = css`
margin-bottom: 48px;
strong {
font-family: "inter-semi-bold";
font-family: 'inter-semi-bold';
font-weight: 400;
}
`;
const STYLES_NAME = css`
font-family: "inter-semi-bold";
font-family: 'inter-semi-bold';
font-size: 24px;
margin: 16px;
position: relative;
@ -88,66 +88,66 @@ const STYLES_CHOICE = css`
`;
const LOGOS = [
{ src: "idea-logo-1.jpg", votes: 1 },
{ src: "idea-logo-2.jpg", votes: 2 },
{ src: "idea-logo-3.jpg", votes: 0 },
{ src: "idea-logo-4.jpg", votes: 2 },
{ src: "idea-logo-5.jpg", votes: 1 },
{ src: "idea-logo-6.jpg", votes: 0 },
{ src: "idea-logo-7.jpg", votes: 0 },
{ src: "idea-logo-8.jpg", votes: 1 },
{ src: "idea-logo-9.jpg", votes: 3 },
{ src: "idea-logo-10.jpg", votes: 1 },
{ src: "idea-logo-11.jpg", votes: 3 },
{ src: "idea-logo-12.jpg", votes: 1 },
{ src: "idea-logo-13.jpg", votes: 2 },
{ src: "idea-logo-14.jpg", votes: 0 },
{ src: "idea-logo-15.jpg", votes: 2 },
{ src: "idea-logo-16.jpg", votes: 3 },
{ src: "idea-logo-17.jpg", votes: 0 },
{ src: "idea-logo-18.jpg", votes: 0 },
{ src: "idea-logo-19.jpg", votes: 0 },
{ src: "idea-logo-20.jpg", votes: 2 },
{ src: "idea-logo-21.jpg", votes: 0 },
{ src: "idea-logo-22.jpg", votes: 3 },
{ src: "idea-logo-23.jpg", votes: 2 },
{ src: "idea-logo-24.jpg", votes: 0 },
{ src: 'idea-logo-1.jpg', votes: 1 },
{ src: 'idea-logo-2.jpg', votes: 2 },
{ src: 'idea-logo-3.jpg', votes: 0 },
{ src: 'idea-logo-4.jpg', votes: 2 },
{ src: 'idea-logo-5.jpg', votes: 1 },
{ src: 'idea-logo-6.jpg', votes: 0 },
{ src: 'idea-logo-7.jpg', votes: 0 },
{ src: 'idea-logo-8.jpg', votes: 1 },
{ src: 'idea-logo-9.jpg', votes: 3 },
{ src: 'idea-logo-10.jpg', votes: 1 },
{ src: 'idea-logo-11.jpg', votes: 3 },
{ src: 'idea-logo-12.jpg', votes: 1 },
{ src: 'idea-logo-13.jpg', votes: 2 },
{ src: 'idea-logo-14.jpg', votes: 0 },
{ src: 'idea-logo-15.jpg', votes: 2 },
{ src: 'idea-logo-16.jpg', votes: 3 },
{ src: 'idea-logo-17.jpg', votes: 0 },
{ src: 'idea-logo-18.jpg', votes: 0 },
{ src: 'idea-logo-19.jpg', votes: 0 },
{ src: 'idea-logo-20.jpg', votes: 2 },
{ src: 'idea-logo-21.jpg', votes: 0 },
{ src: 'idea-logo-22.jpg', votes: 3 },
{ src: 'idea-logo-23.jpg', votes: 2 },
{ src: 'idea-logo-24.jpg', votes: 0 },
];
const NAMES = [
{ src: "Petal", votes: 1 },
{ src: "Petals", votes: 0 },
{ src: "Pistil", votes: 0 },
{ src: "Stamen", votes: 0 },
{ src: "Pollen", votes: 0 },
{ src: "Mission Control", votes: 0 },
{ src: "Hex", votes: 2 },
{ src: "Hive", votes: 2 },
{ src: "Hexagons", votes: 0 },
{ src: "Spheres", votes: 0 },
{ src: "FileManager", votes: 0 },
{ src: "FilePatron", votes: 0 },
{ src: "Filecoin Client", votes: 0 },
{ src: "Ponds", votes: 1 },
{ src: "Outre", votes: 1 },
{ src: "Slate", votes: 2 },
{ src: "Case", votes: 2 },
{ src: "Materials", votes: 0 },
{ src: "SeedDrive", votes: 0 },
{ src: "Monet", votes: 2 },
{ src: "Lilypad", votes: 0 },
{ src: "StoreBuddy", votes: 0 },
{ src: "FileCabinet", votes: 0 },
{ src: "Jacana", votes: 1 },
{ src: "Argo", votes: 2 },
{ src: "Octavius", votes: 0 },
{ src: "Corgi", votes: 0 },
{ src: "Filing Cabinet", votes: 0 },
{ src: "FileCorgi", votes: 0 },
{ src: "Max", votes: 0 },
{ src: "Nominal", votes: 0 },
{ src: "X AE", votes: 0 },
{ src: "A 12", votes: 0 },
{ src: 'Petal', votes: 1 },
{ src: 'Petals', votes: 0 },
{ src: 'Pistil', votes: 0 },
{ src: 'Stamen', votes: 0 },
{ src: 'Pollen', votes: 0 },
{ src: 'Mission Control', votes: 0 },
{ src: 'Hex', votes: 2 },
{ src: 'Hive', votes: 2 },
{ src: 'Hexagons', votes: 0 },
{ src: 'Spheres', votes: 0 },
{ src: 'FileManager', votes: 0 },
{ src: 'FilePatron', votes: 0 },
{ src: 'Filecoin Client', votes: 0 },
{ src: 'Ponds', votes: 1 },
{ src: 'Outre', votes: 1 },
{ src: 'Slate', votes: 2 },
{ src: 'Case', votes: 2 },
{ src: 'Materials', votes: 0 },
{ src: 'SeedDrive', votes: 0 },
{ src: 'Monet', votes: 2 },
{ src: 'Lilypad', votes: 0 },
{ src: 'StoreBuddy', votes: 0 },
{ src: 'FileCabinet', votes: 0 },
{ src: 'Jacana', votes: 1 },
{ src: 'Argo', votes: 2 },
{ src: 'Octavius', votes: 0 },
{ src: 'Corgi', votes: 0 },
{ src: 'Filing Cabinet', votes: 0 },
{ src: 'FileCorgi', votes: 0 },
{ src: 'Max', votes: 0 },
{ src: 'Nominal', votes: 0 },
{ src: 'X AE', votes: 0 },
{ src: 'A 12', votes: 0 },
];
export default class LogoNameTest extends React.Component {
@ -155,17 +155,13 @@ export default class LogoNameTest extends React.Component {
return (
<div css={STYLES_LAYOUT}>
<p css={STYLES_PARAGRAPH}>
If you are looking at this page, it is a list of name and logo ideas
(I did not make them). Names and logos do not correspond with each
other.{" "}
<strong>
Please let me know which ones you like through Slack direct message.
</strong>
If you are looking at this page, it is a list of name and logo ideas (I did not make them). Names and logos do
not correspond with each other.{' '}
<strong>Please let me know which ones you like through Slack direct message.</strong>
<br />
<br />
All votes are anonymous. We will take the votes, consolidate the
choices and then ask other designers to help out with refining what
the team prefers.
All votes are anonymous. We will take the votes, consolidate the choices and then ask other designers to help
out with refining what the team prefers.
<br />
<br />
Key
@ -183,18 +179,14 @@ export default class LogoNameTest extends React.Component {
<span
css={STYLES_PILL}
style={{
backgroundColor: "#0047FF",
bottom: "auto",
backgroundColor: '#0047FF',
bottom: 'auto',
top: -16,
}}
>
}}>
{n.votes}
</span>
) : null}
<span
css={STYLES_CHOICE}
style={{ background: "transparent", color: "#000" }}
>
<span css={STYLES_CHOICE} style={{ background: 'transparent', color: '#000' }}>
Option #{index + 1}
</span>
</span>
@ -208,20 +200,15 @@ export default class LogoNameTest extends React.Component {
<div>
{LOGOS.map((l, index) => {
return (
<span
key={l.src}
css={STYLES_LOGO}
style={{ backgroundImage: `url('/static/${l.src}')` }}
>
<span key={l.src} css={STYLES_LOGO} style={{ backgroundImage: `url('/static/temp/${l.src}')` }}>
{l.votes > 0 ? (
<span
css={STYLES_PILL}
style={{
backgroundColor: "#0047FF",
bottom: "auto",
backgroundColor: '#0047FF',
bottom: 'auto',
top: -16,
}}
>
}}>
{l.votes}
</span>
) : null}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1000 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 857 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

View File

@ -1,17 +1,17 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as Fixtures from "~/common/fixtures";
import * as System from "~/components/system";
import * as SchemaTable from "~/common/schema-table";
import * as React from 'react';
import * as Strings from '~/common/strings';
import * as Constants from '~/common/constants';
import * as Fixtures from '~/common/fixtures';
import * as System from '~/components/system';
import * as SchemaTable from '~/common/schema-table';
import { css } from "@emotion/react";
import { css } from '@emotion/react';
import ScenePage from "~/components/core/ScenePage";
import Section from "~/components/core/Section";
import ScenePage from '~/components/core/ScenePage';
import Section from '~/components/core/Section';
export default class SceneDataTransfer extends React.Component {
state = { sub_navigation: "1" };
state = { sub_navigation: '1' };
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
@ -26,19 +26,15 @@ export default class SceneDataTransfer extends React.Component {
style={{ marginTop: 24 }}
name="sub_navigation"
options={[
{ value: "1", label: "Current transfers" },
{ value: "2", label: "Past transfers" },
{ value: '1', label: 'Current transfers' },
{ value: '2', label: 'Past transfers' },
]}
value={this.state.sub_navigation}
onChange={this._handleChange}
/>
{this.state.sub_navigation === "2" ? (
<Section
title="Past transfers"
onAction={this.props.onAction}
onNavigateTo={this.props.onNavigateTo}
>
{this.state.sub_navigation === '2' ? (
<Section title="Past transfers" onAction={this.props.onAction} onNavigateTo={this.props.onNavigateTo}>
<System.Table
data={{
columns: SchemaTable.DataTransfer,
@ -53,19 +49,8 @@ export default class SceneDataTransfer extends React.Component {
</Section>
) : null}
{this.state.sub_navigation === "1" ? (
<Section
onAction={this.props.onAction}
onNavigateTo={this.props.onNavigateTo}
title="Current transfers"
buttons={[
{
name: "Cancel all",
type: "ACTION",
value: "ACTION_CANCEL_DATA_TRANSFERS",
},
]}
>
{this.state.sub_navigation === '1' ? (
<Section onAction={this.props.onAction} onNavigateTo={this.props.onNavigateTo} title="Current transfers">
<System.P style={{ padding: 24 }}>There are no transfers</System.P>
</Section>
) : null}

View File

@ -1,14 +1,14 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as Fixtures from "~/common/fixtures";
import * as System from "~/components/system";
import * as SchemaTable from "~/common/schema-table";
import * as React from 'react';
import * as Strings from '~/common/strings';
import * as Constants from '~/common/constants';
import * as Fixtures from '~/common/fixtures';
import * as System from '~/components/system';
import * as SchemaTable from '~/common/schema-table';
import { css } from "@emotion/react";
import { css } from '@emotion/react';
import Section from "~/components/core/Section";
import ScenePage from "~/components/core/ScenePage";
import Section from '~/components/core/Section';
import ScenePage from '~/components/core/ScenePage';
export default class SceneDeals extends React.Component {
state = {};
@ -20,18 +20,7 @@ export default class SceneDeals extends React.Component {
render() {
return (
<ScenePage>
<Section
onAction={this.props.onAction}
onNavigateTo={this.props.onNavigateTo}
title="All deals"
buttons={[
{
name: "Export",
type: "DOWNLOAD",
value: "CSV_ALL_DEALS",
},
]}
>
<Section onAction={this.props.onAction} onNavigateTo={this.props.onNavigateTo} title="All deals" buttons={[]}>
<System.Table
onAction={this.props.onAction}
onNavigateTo={this.props.onNavigateTo}

View File

@ -37,12 +37,7 @@ export default class SceneEditAccount extends React.Component {
body: data,
};
const response = await fetch(`/_/upload/avatar`, options);
const json = await response.json();
if (json && json.success) {
console.log('reload');
}
await fetch(`/_/upload/avatar`, options);
};
_handleChange = (e) => {
@ -64,7 +59,7 @@ export default class SceneEditAccount extends React.Component {
<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" for="file">
<System.ButtonPrimary style={{ margin: '0 16px 16px 0' }} type="label" htmlFor="file">
Upload
</System.ButtonPrimary>
</div>

View File

@ -1,14 +1,14 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as Fixtures from "~/common/fixtures";
import * as System from "~/components/system";
import * as SVG from "~/components/system/svg";
import * as React from 'react';
import * as Strings from '~/common/strings';
import * as Constants from '~/common/constants';
import * as Fixtures from '~/common/fixtures';
import * as System from '~/components/system';
import * as SVG from '~/components/system/svg';
import { css } from "@emotion/react";
import { css } from '@emotion/react';
import Section from "~/components/core/Section";
import ScenePage from "~/components/core/ScenePage";
import Section from '~/components/core/Section';
import ScenePage from '~/components/core/ScenePage';
const STYLES_FLEX = css`
display: flex;
@ -21,6 +21,7 @@ const STYLES_FLEX = css`
const STYLES_TOP = css`
background: ${Constants.system.pitchBlack};
border-bottom: 1px solid ${Constants.system.black};
color: ${Constants.system.white};
width: 100%;
padding: 12px 16px 12px 48px;
@ -53,7 +54,9 @@ const STYLES_ASSET = css`
padding: 0;
min-height: 10%;
height: 100%;
background-size: cover;
background-color: ${Constants.system.pitchBlack};
background-size: contain;
background-repeat: no-repeat;
background-position: 50% 50%;
`;
@ -69,7 +72,7 @@ const STYLES_BOTTOM = css`
`;
const STYLES_PATH = css`
font-family: "mono";
font-family: 'mono';
color: ${Constants.system.white};
font-size: 12px;
text-transform: uppercase;
@ -87,7 +90,7 @@ const STYLES_ITEM = css`
justify-content: center;
font-size: 12px;
letter-spacing: 0.2px;
font-family: "inter-semi-bold";
font-family: 'inter-semi-bold';
transition: 200ms ease all;
cursor: pointer;
background-color: ${Constants.system.brand};
@ -113,7 +116,7 @@ export default class SceneFile extends React.Component {
};
render() {
const fileURL = `/static/${this.props.file.file}`;
const fileURL = `/static/files/${this.props.file.file}`;
return (
<div css={STYLES_FLEX}>
@ -125,20 +128,7 @@ export default class SceneFile extends React.Component {
<SVG.Dismiss height="24px" />
</div>
</div>
<div
css={STYLES_ASSET}
style={{ backgroundImage: `url('${fileURL}')` }}
/>
<div css={STYLES_BOTTOM}>
<div css={STYLES_LEFT}>
<span css={STYLES_PATH}>{this.props.file["cid"]}</span>
</div>
<div css={STYLES_RIGHT}>
<span css={STYLES_ITEM}>Copy CID</span>
<span css={STYLES_ITEM}>Copy gateways</span>
<span css={STYLES_ITEM}>Copy to downloads</span>
</div>
</div>
<div css={STYLES_ASSET} style={{ backgroundImage: `url('${fileURL}')` }} />
</div>
);
}

View File

@ -1,13 +1,13 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as Fixtures from "~/common/fixtures";
import * as System from "~/components/system";
import * as React from 'react';
import * as Strings from '~/common/strings';
import * as Constants from '~/common/constants';
import * as Fixtures from '~/common/fixtures';
import * as System from '~/components/system';
import { css } from "@emotion/react";
import { css } from '@emotion/react';
import Section from "~/components/core/Section";
import ScenePage from "~/components/core/ScenePage";
import Section from '~/components/core/Section';
import ScenePage from '~/components/core/ScenePage';
export default class SceneFilesFolder extends React.Component {
state = {};
@ -24,29 +24,21 @@ export default class SceneFilesFolder extends React.Component {
const data = {
columns: [
{ key: "icon", hideLabel: true, width: "32px", type: "ICON" },
{ key: "file", name: "File", width: "100%", type: "FILE_LINK" },
{ key: "size", name: "Size", width: "140px" },
{ key: 'icon', hideLabel: true, width: '32px', type: 'ICON' },
{ key: 'file', name: 'File', width: '100%', type: 'FILE_LINK' },
{ key: 'size', name: 'Size', width: '140px', type: 'FILE_SIZE' },
{
key: "date",
name: "Date uploaded",
width: "160px",
tooltip:
"This date represents when the file was first uploaded to the network.",
key: 'date',
name: 'Date uploaded',
width: '160px',
tooltip: 'This date represents when the file was first uploaded to the network.',
type: 'FILE_DATE',
},
{
key: "remaining",
name: "Remaining time",
tooltip:
"This file will remain available to you on the network for a limited time. Once time elapses you will have to create a new storage deal.",
width: "180px",
key: 'storage_status',
name: 'Status',
type: 'DEAL_STATUS',
},
{
key: "retrieval-status",
name: "Status",
type: "DEAL_STATUS_RETRIEVAL",
},
{ key: "errors", hideLabel: true, type: "NOTIFICATION_ERROR" },
],
rows,
};
@ -59,17 +51,11 @@ export default class SceneFilesFolder extends React.Component {
title={this.props.data.name}
buttons={[
{
name: "Retrieve",
type: "SIDEBAR",
value: "SIDEBAR_FILE_RETRIEVAL_DEAL",
name: 'Store file on network',
type: 'SIDEBAR',
value: 'SIDEBAR_FILE_STORAGE_DEAL',
},
{
name: "Store file on network",
type: "SIDEBAR",
value: "SIDEBAR_FILE_STORAGE_DEAL",
},
]}
>
]}>
<System.Table
key={this.props.data.folderId}
data={data}

View File

@ -54,43 +54,50 @@ export default class SceneHome extends React.Component {
render() {
return (
<ScenePage>
<Section
onAction={this.props.onAction}
onNavigateTo={this.props.onNavigateTo}
title="Recent data"
buttons={[
{
name: 'View files',
type: 'NAVIGATE',
value: 'folder-root',
},
{
name: 'Store file on network',
type: 'SIDEBAR',
value: 'SIDEBAR_FILE_STORAGE_DEAL',
},
]}>
<System.Table
data={{
columns: [
{ key: 'file', name: 'File', type: 'FILE_LINK' },
{
key: 'date',
name: 'Date uploaded',
width: '160px',
tooltip: 'This date represents when the file was first uploaded to the network.',
},
{ key: 'remaining', name: 'Remaining time', width: '180px' },
],
rows: [],
}}
selectedRowId={this.state.data}
onChange={this._handleChange}
{this.props.viewer.library[0] ? (
<Section
onAction={this.props.onAction}
onNavigateTo={this.props.onNavigateTo}
name="data"
/>
</Section>
title="Recent data"
buttons={[
{
name: 'View files',
type: 'NAVIGATE',
value: this.props.viewer.library[0].folderId,
},
{
name: 'Store file on network',
type: 'SIDEBAR',
value: 'SIDEBAR_FILE_STORAGE_DEAL',
},
]}>
<System.Table
data={{
columns: [
{ key: 'file', name: 'File', type: 'FILE_LINK' },
{ key: 'size', name: 'Size', width: '140px', type: 'FILE_SIZE' },
{
key: 'date',
name: 'Date uploaded',
width: '160px',
type: 'FILE_DATE',
},
{
key: 'storage_status',
name: 'Status',
type: 'DEAL_STATUS',
},
],
rows: this.props.viewer.library[0].children,
}}
selectedRowId={this.state.data}
onChange={this._handleChange}
onAction={this.props.onAction}
onNavigateTo={this.props.onNavigateTo}
name="data"
/>
</Section>
) : null}
{this.props.viewer.addresses[0] ? (
<Section

View File

@ -33,7 +33,7 @@ export default class SceneSettings extends React.Component {
_deferredSave = null;
_handleSave = async () => {
const response = await Actions.setDefaultConfig({
await Actions.setDefaultConfig({
config: {
hot: {
enabled: this.props.viewer.settings_cold_enabled,
@ -46,7 +46,7 @@ export default class SceneSettings extends React.Component {
enabled: this.props.viewer.settings_cold_enabled,
filecoin: {
addr: this.props.viewer.settings_cold_default_address,
dealDuration: this.props.viewer.settings_cold_default_duration,
dealMinDuration: this.props.viewer.settings_cold_default_duration,
repFactor: this.props.viewer.settings_cold_default_replication_factor,
excludedMinersList: this.props.viewer.settings_cold_default_excluded_miners,
trustedMinersList: this.props.viewer.settings_cold_default_trusted_miners,
@ -59,8 +59,6 @@ export default class SceneSettings extends React.Component {
},
},
});
await this.props.rehydrate();
};
_handleChange = (e) => {
@ -143,6 +141,7 @@ export default class SceneSettings extends React.Component {
description="Default Filecoin deal duration settings description."
tooltip="Placeholder."
name="settings_cold_default_duration"
type="number"
value={this.props.viewer.settings_cold_default_duration}
placeholder="Type in months"
onChange={this._handleChange}

333
server.js
View File

@ -1,9 +1,10 @@
import { createPow } from '@textile/powergate-client';
import { createPow, ffs } from '@textile/powergate-client';
const host = 'http://0.0.0.0:6002';
const pow = createPow({ host });
const PowerGate = createPow({ host });
import * as Middleware from '~/common/middleware';
import * as Strings from '~/common/strings';
import FS from 'fs';
import express from 'express';
@ -11,15 +12,22 @@ import formidable from 'formidable';
import next from 'next';
import bodyParser from 'body-parser';
import compression from 'compression';
import WebSocketServer from 'ws';
const dev = process.env.NODE_ENV !== 'production';
const port = process.env.PORT || 1337;
const wsPort = 2448;
const app = next({ dev, quiet: false });
const nextRequestHandler = app.getRequestHandler();
const AVATAR_STORAGE_URL = `${__dirname}/public/static/system/`;
const FILE_STORAGE_URL = `${__dirname}/public/static/files/`;
// TODO(jim): Just a solution for testing.
// Probably should refactor this or use a database.
let client = null;
let token = null;
let library = null;
let status = null;
let messageList = null;
let peersList = null;
@ -27,22 +35,66 @@ let addrsList = null;
let info = null;
const refresh = async () => {
const Health = await pow.health.check();
const Health = await PowerGate.health.check();
status = Health.status ? Health.status : null;
messageList = Health.messageList ? Health.messageList : null;
const Peers = await pow.net.peers();
const Peers = await PowerGate.net.peers();
peersList = Peers.peersList ? Peers.peersList : null;
};
const refreshWithToken = async () => {
const Addresses = await pow.ffs.addrs();
const Addresses = await PowerGate.ffs.addrs();
addrsList = Addresses.addrsList ? Addresses.addrsList : null;
const NetworkInfo = await pow.ffs.info();
const NetworkInfo = await PowerGate.ffs.info();
info = NetworkInfo.info ? NetworkInfo.info : null;
};
const getFileName = (s) => {
let target = s;
if (target.endsWith('/')) {
target = target.substring(0, target.length - 1);
}
return target.substr(target.lastIndexOf('/') + 1);
};
const createFile = (id, data) => {
return {
decorator: 'FILE',
id: id,
icon: 'PNG',
file: getFileName(id),
miner: null,
job_id: null,
cid: null,
date: new Date(),
size: data.size,
amount: 0,
remaining: null,
deal_category: 1,
retrieval_status: 0,
storage_status: 0,
errors: [],
};
};
const createFolder = (id) => {
return {
decorator: 'FOLDER',
id,
folderId: id,
icon: 'FOLDER',
file: getFileName(id),
name: getFileName(id),
pageTitle: null,
date: null,
size: null,
children: [],
};
};
const getData = async () => {
const data = {
production: !dev,
@ -51,47 +103,134 @@ const getData = async () => {
peersList,
addrsList,
info,
library,
};
console.log('ON THE SERVER', data);
return data;
};
app.prepare().then(async () => {
try {
await refresh();
const emitStateUpdate = async () => {
await refresh();
await refreshWithToken();
const data = await getData();
client.send(JSON.stringify({ action: 'UPDATE_VIEWER', data }));
};
// NOTE(jim): This is a configuration folder with all of the client tokens.
!FS.existsSync(`./.data`) && FS.mkdirSync(`./.data`, { recursive: true });
const checkFileStatus = async () => {
// TODO(jim): Refactor this so we repeat this less often.
let write = false;
for (let i = 0; i < library.length; i++) {
for (let j = 0; j < library[i].children.length; j++) {
if (library[i].children[j].job_id) {
if (library[i].children[j].storage_status === 1) {
library[i].children[j].storage_status = 2;
write = true;
continue;
}
// NOTE(jim): This will create a token for authentication with powergate.
if (!FS.existsSync('./.data/powergate-token')) {
const FFS = await pow.ffs.create();
token = FFS.token ? FFS.token : null;
// NOTE(jim): Write a new token file.
if (token) {
FS.writeFileSync('./.data/powergate-token', token);
PowerGate.ffs.watchJobs((job) => {
console.log(job);
if (job.status === ffs.JobStatus.JOB_STATUS_SUCCESS) {
library[i].children[j].storage_status = 6;
write = true;
}
}, library[i].children[j].job_id);
}
} else {
token = FS.readFileSync('./.data/powergate-token', 'utf8');
}
if (token) {
pow.setToken(token);
}
await refreshWithToken();
} catch (e) {
console.log(e);
}
if (!token) {
throw new Error('[ prototype ] can not start client without proper auth token');
if (write) {
FS.writeFileSync('./.data/library.json', JSON.stringify({ library }));
}
};
const setIntervalViewerUpdates = async () => {
console.log('[ prototype ] checking for library deal updates.');
if (client) {
try {
await emitStateUpdate();
await checkFileStatus();
} catch (e) {}
}
setTimeout(setIntervalViewerUpdates, 5000);
};
const resetAllLocalData = async () => {
// NOTE(jim): For testing purposes.
// We wipe all of the local data each time you run the application.
console.log('[ prototype ] deleting old token and library data ');
FS.rmdirSync('./.data', { recursive: true });
console.log('[ prototype ] deleting old avatar data ');
FS.rmdirSync(AVATAR_STORAGE_URL, { recursive: true });
console.log('[ prototype ] deleting old file data ');
FS.rmdirSync(FILE_STORAGE_URL, { recursive: true });
console.log('[ prototype ] creating new avatar folder ');
FS.mkdirSync(AVATAR_STORAGE_URL, { recursive: true });
FS.writeFileSync(`${AVATAR_STORAGE_URL}.gitkeep`, '');
console.log('[ prototype ] creating new local file folder ');
FS.mkdirSync(FILE_STORAGE_URL, { recursive: true });
FS.writeFileSync(`${FILE_STORAGE_URL}.gitkeep`, '');
};
app.prepare().then(async () => {
if (dev) {
await resetAllLocalData();
try {
await refresh();
// NOTE(jim): This is a configuration folder with all of the client tokens.
!FS.existsSync(`./.data`) && FS.mkdirSync(`./.data`, { recursive: true });
// NOTE(jim): This will create a token for authentication with powergate.
if (!FS.existsSync('./.data/powergate-token')) {
const FFS = await PowerGate.ffs.create();
token = FFS.token ? FFS.token : null;
// NOTE(jim): Write a new token file.
if (token) {
FS.writeFileSync('./.data/powergate-token', token);
}
} else {
token = FS.readFileSync('./.data/powergate-token', 'utf8');
}
if (token) {
PowerGate.setToken(token);
}
await refreshWithToken();
if (!FS.existsSync('./.data/library.json')) {
const librarySchema = { library: [{ ...createFolder(FILE_STORAGE_URL), file: 'Files', name: 'Files' }] };
FS.writeFileSync('./.data/library.json', JSON.stringify(librarySchema));
library = librarySchema.library;
} else {
const parsedLibrary = FS.readFileSync('./.data/library.json', 'utf8');
library = JSON.parse(parsedLibrary).library;
}
} catch (e) {
console.log(e);
}
}
const server = express();
const WSS = new WebSocketServer.Server({ port: wsPort });
WSS.on('connection', (s) => {
// TODO(jim): Suppport more than one client.
client = s;
s.on('close', function () {
s.send(JSON.stringify({ action: null, data: 'closed' }));
});
s.send(JSON.stringify({ action: null, data: 'connected' }));
});
if (!dev) {
server.use(compression());
@ -106,8 +245,87 @@ app.prepare().then(async () => {
})
);
server.get('/health', async (req, res) => {
res.send('ok');
server.post('/_/viewer', async (req, res) => {
if (dev) {
await refresh();
await refreshWithToken();
}
return res.status(200).send({ success: true, data: await getData() });
});
server.post('/_/deals/storage', async (req, res) => {
if (Strings.isEmpty(req.body.src)) {
return res.status(500).send({ success: false });
}
const localPath = `${__dirname}${req.body.src}`;
const buffer = FS.readFileSync(localPath);
const { cid } = await PowerGate.ffs.addToHot(buffer);
const { jobId } = await PowerGate.ffs.pushConfig(cid);
// TODO(jim): Refactor this so we repeat this less often.
let write = false;
for (let i = 0; i < library.length; i++) {
for (let j = 0; j < library[i].children.length; j++) {
if (localPath === library[i].children[j].id) {
library[i].children[j].job_id = jobId;
library[i].children[j].cid = cid;
library[i].children[j].storage_status = 1;
write = true;
}
}
}
if (write) {
FS.writeFileSync('./.data/library.json', JSON.stringify({ library }));
}
await emitStateUpdate();
return res.status(200).send({ success: true, cid, jobId });
});
server.post('/_/storage/:file', async (req, res) => {
const form = formidable({ multiples: true, uploadDir: FILE_STORAGE_URL });
form.once('error', console.error);
form.on('progress', (bytesReceived, bytesExpected) => {
console.log(`[ prototype ] ${bytesReceived} / ${bytesExpected}`);
});
form.parse(req, async (error, fields, files) => {
if (error) {
return res.status(500).send({ error });
} else {
if (!files.image) {
console.error('[ prototype ] File type unspported', files);
return res.status(500).send({ error: 'File type unsupported', files });
}
const newPath = form.uploadDir + req.params.file;
FS.rename(files.image.path, newPath, function (err) {});
const localFile = createFile(newPath, files.image);
let pushed = false;
for (let i = 0; i < library.length; i++) {
const currentFolder = library[i];
if (!pushed) {
currentFolder.children.push(localFile);
pushed = true;
break;
}
}
if (pushed) {
FS.writeFileSync('./.data/library.json', JSON.stringify({ library }));
}
await emitStateUpdate();
return res.status(200).send({ success: true, file: localFile });
}
});
});
server.post('/_/upload/avatar', async (req, res) => {
@ -119,83 +337,68 @@ app.prepare().then(async () => {
console.log(`[ prototype ] ${bytesReceived} / ${bytesExpected}`);
});
form.on('fileBegin', (filename, file) => {
form.emit('data', { name: '[ prototype ] file uploading', filename, value: file });
});
form.on('file', (filename, file) => {
form.emit('data', { name: '[ prototype ] file:', key: filename, value: file });
});
form.on('field', (fieldName, fieldValue) => {
form.emit('data', { name: '[ prototype ] field:', key: fieldName, value: fieldValue });
});
form.once('end', () => {
console.log('[ prototype ] finished upload');
});
form.parse(req, (error, fields, files) => {
form.parse(req, async (error, fields, files) => {
if (error) {
return res.status(500).send({ error });
} else {
const newPath = form.uploadDir + 'avatar.png';
FS.rename(files.image.path, newPath, function (err) {});
await emitStateUpdate();
return res.status(200).send({ success: true });
}
});
});
server.post('/_/viewer', async (req, res) => {
await refresh();
await refreshWithToken();
return res.status(200).send({ success: true, data: await getData() });
});
server.post('/_/settings', async (req, res) => {
let data;
try {
data = await pow.ffs.setDefaultConfig(req.body.config);
data = await PowerGate.ffs.setDefaultConfig(req.body.config);
} catch (e) {
return res.status(500).send({ error: e.message });
}
await emitStateUpdate();
return res.status(200).send({ success: true, data });
});
server.post('/_/wallet/create', async (req, res) => {
let data;
try {
data = await pow.ffs.newAddr(req.body.name, req.body.type, req.body.makeDefault);
data = await PowerGate.ffs.newAddr(req.body.name, req.body.type, req.body.makeDefault);
} catch (e) {
return res.status(500).send({ error: e.message });
}
await emitStateUpdate();
return res.status(200).send({ success: true, data });
});
server.post('/_/wallet/send', async (req, res) => {
let data;
try {
data = await pow.ffs.sendFil(req.body.source, req.body.target, req.body.amount);
data = await PowerGate.ffs.sendFil(req.body.source, req.body.target, req.body.amount);
} catch (e) {
return res.status(500).send({ error: e.message });
}
await emitStateUpdate();
return res.status(200).send({ success: true, data: { ...data, ...req.body } });
});
server.get('/', async (req, res) => {
return app.render(req, res, '/', { production: false });
if (!dev) {
return res.redirect('https://github.com/filecoin-project/filecoin-client');
}
return app.render(req, res, '/', { production: false, wsPort });
});
server.get('*', async (req, res) => {
return nextRequestHandler(req, res, req.url);
});
server.listen(port, (err) => {
server.listen(port, async (err) => {
if (err) {
throw err;
}
@ -204,5 +407,7 @@ app.prepare().then(async () => {
console.log('[ prototype ] powergate token:', token);
console.log(`[ prototype ] listening on: http://localhost:${port}`);
console.log(`[ prototype ] avatar storage: ${AVATAR_STORAGE_URL}`);
await setIntervalViewerUpdates();
});
});