initial commit for the application prototype

This commit is contained in:
@wwwjim 2020-04-08 14:29:13 -07:00 committed by jimmylee
parent b8f1d42f2f
commit 57876efbef
122 changed files with 63772 additions and 1244 deletions

View File

@ -7,12 +7,10 @@
"useESModules": false
}
}
]
],
"@emotion/babel-preset-css-prop",
],
"plugins": [
["emotion", {
"inline": true
}],
["module-resolver", {
"alias": {
"~": "./"

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
.next
.data
.env
.DS_STORE
package-lock.json
@ -9,4 +10,6 @@ DS_STORE
/**/*/.DS_STORE
/**/*/node_modules
/**/*/.next
/**/*/.data
.data/**/*

View File

@ -1,5 +1,48 @@
# FPS
# What is this?
Coming soon.
- An open source desktop client for using [Textile's Powergate](https://github.com/textileio/powergate/).
- An [open source design system](https://filecoin.onrender.com) for building your own applications that are compatible with [Textile's Powergate](https://github.com/textileio/powergate/).
- A kitchen sink example repository and multiple files to take example code snippets from for your own projects.
## Where are we today?
- Powergate is in the process of getting fully integrated.
- Electron application in planning!
# Getting Started
If you want to help with the development, just follow these steps:
## Satisfy system requirements
- Make sure you have [homebrew](https://brew.sh/).
- Make sure you run `xcode-select -p`, if the command does not return a response, run `xcode-select --install`.
- Make sure you run `brew install node`.
- Make sure you run `brew install go`.
## Get and Setup Docker
- `brew install docker`.
- Install [Docker for Desktop](https://www.docker.com/products/docker-desktop) if you are running MacOS.
## Setup Lotus DevNet and Powergate
- Clone the [Lotus DevNet](https://github.com/textileio/lotus-devnet) repository.
- Run `docker run --name texdevnet -e TEXLOTUSDEVNESPEED=1500 -p 1234:7777 textile/lotus-devnet`.
- Clone [Powergate](https://github.com/textileio/powergate/).
- Follow the instructions and run the commands in the README.md file.
## Run the Filecoin Client
- Clone this repository, run the following commands:
```sh
npm install
npm run dev
```
Visit `localhost:1337` in the browser.
## Getting Involved and Becoming a Contributor
TBA

View File

@ -1,10 +1,6 @@
import 'isomorphic-fetch';
import Cookies from 'universal-cookie';
import * as Constants from '~/common/constants';
const cookies = new Cookies();
import * as Strings from '~/common/strings';
const REQUEST_HEADERS = {
Accept: 'application/json',
@ -13,40 +9,46 @@ const REQUEST_HEADERS = {
const SERVER_PATH = '';
const getHeaders = () => {
const jwt = cookies.get(Constants.session.key);
if (jwt) {
return {
...REQUEST_HEADERS,
authorization: `Bearer ${jwt}`,
};
export const createWalletAddress = async (data) => {
if (Strings.isEmpty(data.name)) {
return null;
}
return REQUEST_HEADERS;
};
export const onLocalSignIn = async (e, props, auth) => {
const options = {
method: 'POST',
headers: getHeaders(),
headers: REQUEST_HEADERS,
credentials: 'include',
body: JSON.stringify({
...auth,
}),
body: JSON.stringify(data),
};
const response = await fetch(`${SERVER_PATH}/api/sign-in`, options);
const response = await fetch(`/_/wallet/create`, options);
const json = await response.json();
if (json.error) {
console.log(json.error);
return;
}
if (json.token) {
cookies.set(Constants.session.key, json.token);
}
window.location.href = '/sign-in-success';
return json;
};
export const sendWalletAddressFilecoin = async (data) => {
if (Strings.isEmpty(data.source)) {
return null;
}
if (Strings.isEmpty(data.target)) {
return null;
}
if (!data.amount) {
return null;
}
const options = {
method: 'POST',
headers: REQUEST_HEADERS,
credentials: 'include',
body: JSON.stringify(data),
};
const response = await fetch(`/_/wallet/send`, options);
const json = await response.json();
return json;
};

View File

@ -1,25 +1,44 @@
export const sizes = {
navigation: 288,
sidebar: 416,
header: 72,
};
export const zindex = {
sidebar: 1,
editor: {
menu: 2,
},
navigation: 1,
header: 5,
tooltip: 4,
sidebar: 3,
};
export const session = {
key: 'WEB_SERVICE_SESSION_KEY',
export const font = {
text: "inter-regular",
};
export const colors = {
gray: '#f2f4f8',
gray2: '#dde1e6',
gray3: '#c1c7cd',
gray4: '#a2a9b0',
black: '#000000',
white: '#ffffff',
export const typescale = {
lvl1: `1rem`,
lvl2: `1.25rem`,
lvl3: `1.563rem`,
lvl4: `1.953em`,
};
export const system = {
white: "#ffffff",
foreground: "#f7f7f7",
gray: "#e5e5e5",
border: "#d8d8d8",
darkGray: "#b2b2b2",
black: "#2D2926",
pitchBlack: "#0c0c0c",
brand: "#2935ff",
green: "#28a745",
yellow: " #FFC940",
red: "#ff0000",
};
export const theme = {
buttonBackground: '#E5E7EA',
pageBackground: colors.gray,
pageText: colors.black,
foreground: system.white,
ctaBackground: system.brand,
pageBackground: system.foreground,
pageText: system.black,
};

View File

@ -1,8 +1,3 @@
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}
export const CLIENT_ID = process.env.CLIENT_ID;
export const CLIENT_SECRET = process.env.CLIENT_SECRET;
export const REDIRECT_URIS = 'http://localhost:1337/sign-in-confirm';
export const JWT_SECRET = process.env.JWT_SECRET;

View File

@ -1,89 +1,297 @@
import * as Credentials from '~/common/credentials';
import * as Utilities from '~/common/utilities';
import * as Strings from "~/common/strings";
import DB from '~/db';
import JWT, { decode } from 'jsonwebtoken';
export const DEMO_TOOLTIP_TEXT =
"The reign of Marcus Aurelius was marked by military conflict.";
const google = require('googleapis').google;
const OAuth2 = google.auth.OAuth2;
export const EXTERNAL_ADDRESSES = [
"bafy2bzaceauqgsxjgxurysrujxmushykl52cfzjqyfbrpfjmu2zh33sg5sois",
];
const runQuery = async ({ queryFn, errorFn, label }) => {
let response;
try {
response = await queryFn();
} catch (e) {
response = errorFn(e);
}
export const EXAMPLE_ADDRESSES = [
"bafy2bzacedispf4crg5oc3mlvkrzh5yk7kbvh7ebkv7o4a3fs7vdhcctr4rys",
"aaaa2bzacedispf4crg5oc3mlvkrzh5yk7kbvh7ebkv7o4a3fs7vdhccbbbrys",
"bbbb2bzacedispf4crg5oc3mlvkrzh5yk7kbvh7ebkv7o4a3fs7vdhccbbbrys",
];
console.log('[ database-query ]', { query: label });
return response;
};
export const EXAMPLE_FOLDERS = [
{
decorator: "FOLDER",
id: `folder-root`,
folderId: "folder-root",
icon: "FOLDER",
file: "Files",
name: "Files",
pageTitle: "for files",
date: Strings.toDate("2017-01-01 00:00:00 UTC"),
size: Strings.bytesToSize(20020220),
},
{
decorator: "FOLDER",
id: `folder-1`,
folderId: "folder-1",
icon: "FOLDER",
file: "oil_paintings_2020",
name: "oil_paintings_2020",
pageTitle: "for files",
date: Strings.toDate("2017-01-01 00:00:00 UTC"),
size: Strings.bytesToSize(20020220),
},
{
decorator: "FOLDER",
id: `folder-2`,
folderId: "folder-2",
icon: "FOLDER",
file: "oil_paintings_2021",
name: "oil_paintings_2021",
pageTitle: "for files",
date: Strings.toDate("2017-01-01 00:00:00 UTC"),
size: Strings.bytesToSize(0),
},
{
decorator: "FOLDER",
id: `folder-3`,
folderId: "folder-3",
icon: "FOLDER",
file: "oil_paintings_1993",
name: "oil_paintings_1993",
pageTitle: "for files",
date: Strings.toDate("2017-01-01 00:00:00 UTC"),
size: Strings.bytesToSize(0),
},
];
export const getViewer = async (req, existingToken = undefined) => {
let viewer = {};
try {
let token = existingToken;
if (!token) {
token = Utilities.getToken(req);
}
let decode = JWT.verify(token, Credentials.JWT_SECRET);
viewer = await getUserByEmail({ email: decode.email });
} catch (e) {}
return { viewer };
};
export const getUserByEmail = async ({ email }) => {
return await runQuery({
label: 'GET_USER_BY_EMAIL',
queryFn: async () => {
const query = await DB.select('*')
.from('users')
.where({ email })
.first();
if (!query || query.error) {
return null;
}
if (query.id) {
return query;
}
return null;
},
errorFn: async e => {
return {
error: 'A new user could not be created.',
source: e,
};
},
});
};
export const createUser = async ({ email, password, salt, data = {} }) => {
return await runQuery({
label: 'createUser',
queryFn: async () => {
const query = await DB.insert({
email,
password,
salt,
data,
})
.into('users')
.returning('*');
const index = query ? query.pop() : null;
return index;
},
errorFn: async e => {
return {
error: 'A new user could not be created.',
source: e,
};
},
});
};
export const EXAMPLE_FILES = [
{
id: `file-1`,
icon: "PNG",
file: "test-image.jpg",
miner: "Example Miner A",
"deal-cid": "23Y7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-06-06 00:00:00 UTC"),
size: Strings.bytesToSize(66666, 3),
amount: Strings.formatAsFilecoin(2),
remaining: Strings.getRemainingTime(666666),
cid: "QmY7Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 6,
errors: 4,
},
{
id: `file-2`,
icon: "PNG",
file: "test-image-2.jpg",
miner: "Example Miner A",
"deal-cid": "ABCDYh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-06-07 00:00:00 UTC"),
size: Strings.bytesToSize(77777, 3),
amount: Strings.formatAsFilecoin(2.04),
remaining: Strings.getRemainingTime(777777),
cid: "w2w2Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 6,
errors: 0,
},
{
id: `file-3`,
icon: "PNG",
file: "test-image-3.jpg",
miner: "Example Miner B",
"deal-cid": "FHJKYh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-06-08 00:00:00 UTC"),
size: Strings.bytesToSize(88888, 3),
amount: Strings.formatAsFilecoin(2.08),
remaining: Strings.getRemainingTime(888888),
cid: "0707Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 6,
errors: 0,
},
{
id: `file-4`,
icon: "PNG",
file: "test-image-4.jpg",
miner: "Example Miner C",
"deal-cid": "KKKKYh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-06-09 00:00:00 UTC"),
size: Strings.bytesToSize(9999999, 3),
amount: Strings.formatAsFilecoin(4.08),
remaining: Strings.getRemainingTime(999999),
cid: "1010Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 6,
errors: 0,
},
{
id: `file-5`,
icon: "PNG",
file: "test-image-5.jpg",
miner: "Example Miner D",
"deal-cid": "WWWWWWWUquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-06-10 00:00:00 UTC"),
size: Strings.bytesToSize(4444444, 3),
amount: Strings.formatAsFilecoin(5.13),
remaining: Strings.getRemainingTime(797979),
cid: "1414Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 6,
errors: 0,
},
{
id: `file-6`,
icon: "PNG",
file: "test-image-6.jpg",
miner: "Example Miner D",
"deal-cid": "XXXXUquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-06-11 00:00:00 UTC"),
size: Strings.bytesToSize(373737, 3),
amount: Strings.formatAsFilecoin(12.13),
remaining: Strings.getRemainingTime(828282),
cid: "3030Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 6,
errors: 0,
},
{
id: `file-7`,
icon: "PNG",
file: "test-image-7.jpg",
miner: "Example Miner E",
"deal-cid": "HGHGHGXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-06-12 00:00:00 UTC"),
size: Strings.bytesToSize(373737, 3),
amount: Strings.formatAsFilecoin(12.13),
remaining: Strings.getRemainingTime(828282),
cid: "3030Yh4UquoXHLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 6,
errors: 1,
},
{
id: `file-8`,
icon: "PNG",
file: "example-painting-a-1.jpg",
miner: "Example Miner F",
"deal-cid": "12CCCCCLPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-06-12 00:00:00 UTC"),
size: Strings.bytesToSize(444444, 3),
amount: Strings.formatAsFilecoin(2.13),
remaining: Strings.getRemainingTime(8282822),
cid: "asdfadsfPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 6,
errors: 0,
},
{
id: `file-9`,
icon: "PNG",
file: "example-painting-a-2.jpg",
miner: "Example Miner F",
"deal-cid": "CGFDFASXbhXkhBvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-06-12 00:00:00 UTC"),
size: Strings.bytesToSize(44432, 3),
amount: Strings.formatAsFilecoin(20.13),
remaining: Strings.getRemainingTime(182822822),
cid: "asdfadsfPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 6,
errors: 0,
},
{
id: `file-10`,
icon: "PNG",
file: "example-painting-a-3.jpg",
miner: "Example Miner F",
"deal-cid": "HHFGFDDFGvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-07-20 00:00:00 UTC"),
size: Strings.bytesToSize(44432, 3),
amount: Strings.formatAsFilecoin(20.13),
remaining: Strings.getRemainingTime(7432123),
cid: "asdfadsfPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 6,
errors: 0,
},
{
id: `file-11`,
icon: "PNG",
file: "example-painting-a-4.jpg",
miner: "Example Miner F",
"deal-cid": "HHFGFDDFGvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-07-20 00:00:00 UTC"),
size: Strings.bytesToSize(44432, 3),
amount: Strings.formatAsFilecoin(20.13),
remaining: Strings.getRemainingTime(742988),
cid: "ppdmdfeFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 6,
errors: 0,
},
{
id: `file-12`,
icon: "PNG",
file: "example-painting-a-5.jpg",
miner: "Example Miner F",
"deal-cid": "GHREREFGvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-07-24 00:00:00 UTC"),
size: Strings.bytesToSize(44432, 3),
amount: Strings.formatAsFilecoin(20.13),
remaining: Strings.getRemainingTime(320021),
cid: "dfsffdbPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 6,
errors: 0,
},
{
id: `file-13`,
icon: "PNG",
file: "pending-file-1.jpg",
miner: "Example Miner G",
"deal-cid": "13REREFGvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-07-24 00:00:00 UTC"),
size: Strings.bytesToSize(44432, 3),
amount: Strings.formatAsFilecoin(20.13),
remaining: null,
cid: "13sffdbPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 1,
},
{
id: `file-14`,
icon: "PNG",
file: "pending-file-2.jpg",
miner: "Example Miner G",
"deal-cid": "14REREFGvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-07-24 00:00:00 UTC"),
size: Strings.bytesToSize(44432, 3),
amount: Strings.formatAsFilecoin(20.13),
remaining: null,
cid: "14sffdbPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 2,
},
{
id: `file-15`,
icon: "PNG",
file: "pending-file-3.jpg",
miner: "Example Miner H",
"deal-cid": "15REREFGvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-07-24 00:00:00 UTC"),
size: Strings.bytesToSize(44432, 3),
amount: Strings.formatAsFilecoin(20.13),
remaining: null,
cid: "15sffdbPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 3,
},
{
id: `file-16`,
icon: "PNG",
file: "pending-file-4.jpg",
miner: "Example Miner I",
"deal-cid": "16REREFGvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-07-24 00:00:00 UTC"),
size: Strings.bytesToSize(44432, 3),
amount: Strings.formatAsFilecoin(20.13),
remaining: null,
cid: "16sffdbPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 4,
},
{
id: `file-17`,
icon: "PNG",
file: "pending-file-5.jpg",
miner: "Example Miner J",
"deal-cid": "17REREFGvFoPwmQUSa92pxnxjQuPU",
date: Strings.toDate("2014-07-24 00:00:00 UTC"),
size: Strings.bytesToSize(44432, 3),
amount: Strings.formatAsFilecoin(20.13),
remaining: null,
cid: "17sffdbPFo2XbhXkhBvFoPwmQUSa92pxnxjQuPU",
"retrieval-status": 5,
},
];

587
common/fixtures.js Normal file
View File

@ -0,0 +1,587 @@
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 = (props) => {
const { status, messageList, peersList, addrsList, info } = props;
console.log(props);
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];
}
}
return null;
};
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 = [
{
id: 1,
name: 'Home',
pageTitle: 'home',
decorator: 'HOME',
children: null,
},
{
id: 2,
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,
},
{
id: 13,
name: 'Edit account',
pageTitle: 'your account',
decorator: 'EDIT_ACCOUNT',
children: null,
ignore: true,
},
{
id: 14,
name: 'Settings',
pageTitle: 'your settings',
decorator: 'SETTINGS',
children: null,
ignore: true,
},
{
id: 15,
name: null,
pageTitle: 'files',
decorator: 'FILE',
children: null,
ignore: true,
},
];

View File

@ -1,10 +1,3 @@
import * as Strings from '~/common/strings';
import * as Constants from '~/common/constants';
import * as Data from '~/common/data';
import * as Credentials from '~/common/credentials';
import JWT from 'jsonwebtoken';
export const CORS = async (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header(
@ -22,28 +15,3 @@ export const CORS = async (req, res, next) => {
next();
};
export const RequireCookieAuthentication = async (req, res, next) => {
if (Strings.isEmpty(req.headers.cookie)) {
return res.redirect('/sign-in-error');
}
const token = req.headers.cookie.replace(
/(?:(?:^|.*;\s*)WEB_SERVICE_SESSION_KEY\s*\=\s*([^;]*).*$)|^.*$/,
'$1'
);
try {
var decoded = JWT.verify(token, Credentials.JWT_SECRET);
const user = await Data.getUserByEmail({ email: decoded.email });
if (!user) {
return res.redirect('/sign-in-error');
}
} catch (err) {
console.log(err);
return res.redirect('/sign-in-error');
}
next();
};

176
common/schema-table.js Normal file
View File

@ -0,0 +1,176 @@
export const Peers = [
{
key: "peer-avatar",
hideLabel: true,
width: "56px",
type: "AVATAR",
},
{
key: "chain-head",
name: "Chain Head",
tooltip: "What is a Chain Head?",
width: "224px",
},
{
key: "height",
name: "Height",
tooltip: "Height",
width: "120px",
},
{
key: "location",
name: "Location",
width: "100%",
type: "LOCATION",
},
{
key: "upload",
name: "Upload",
width: "120px",
type: "BANDWIDTH_UPLOAD",
},
{
key: "download",
name: "Download",
width: "120px",
type: "BANDWIDTH_DOWNLOAD",
},
];
export const Wallet = [
{
key: "category",
name: "Category",
type: "TRANSACTION_DIRECTION",
width: "120px",
},
{ key: "amount", name: "Amount", width: "100%" },
{ key: "source", name: "Source", width: "196px" },
{
key: "destination",
name: "Destination",
width: "196px",
},
{ key: "date", name: "Date", width: "120px" },
{
key: "status",
name: "Status",
type: "TRANSACTION_STATUS",
width: "100px",
},
];
export const Transactions = [
{
key: "category",
name: "Category",
type: "TRANSACTION_DIRECTION",
width: "120px",
},
{ key: "amount", name: "Amount", width: "100%" },
{ key: "source", name: "Source", width: "196px" },
{
key: "destination",
name: "Destination",
width: "196px",
},
{ key: "date", name: "Date", width: "120px" },
{
key: "status",
name: "Status",
type: "TRANSACTION_STATUS",
width: "100px",
},
];
export const DataTransfer = [
{
key: "data-cid",
name: "Data CID",
copyable: true,
tooltip: "Data CID explainer.",
width: "224px",
},
{
key: "deal-cid",
name: "Deal CID",
copyable: true,
tooltip: "Deal CID explainer.",
width: "100%",
},
{
key: "data-source",
name: "Source",
width: "120px",
},
{
key: "data-destination",
name: "Destination",
width: "120px",
},
{ key: "size", name: "Size", width: "140px" },
];
export const ActivePaymentChannel = [
{
key: "category",
name: "Category",
width: "120px",
type: "TRANSACTION_DIRECTION",
},
{ key: "channel-id", name: "Channel ID", width: "100%" },
{ key: "max-value", name: "Maximum Filecoin", width: "144px" },
{ key: "current-value", name: "Current Filecoin", width: "144px" },
{
key: "redeemable",
hideLabel: true,
type: "BUTTON",
width: "144px",
action: "SIDEBAR_REDEEM_PAYMENT_CHANNEL",
},
];
export const RedeemedPaymentChannel = [
{
key: "category",
name: "Category",
width: "120px",
type: "TRANSACTION_DIRECTION",
},
{ key: "channel-id", name: "Channel ID", width: "100%" },
{ key: "max-value", name: "Maximum Filecoin", width: "144px" },
{ key: "redeemed-value", name: "Redeemed Filecoin", width: "144px" },
];
export const Deals = [
{
key: "deal-category",
name: "Deal",
width: "48px",
type: "DEAL_CATEGORY",
},
{
key: "deal-cid",
name: "Deal CID",
copyable: true,
tooltip: "CID Deal Explainer",
width: "160px",
},
{
key: "data-cid",
name: "Data CID",
copyable: true,
tooltip: "Data CID Explainer",
width: "100%",
},
{ key: "miner", name: "Miner", width: "96px" },
{ key: "price", name: "Price", width: "96px" },
{
key: "auto-renew",
name: "Auto renew",
tooltip: "Auto renew explainer",
type: "DEAL_AUTO_RENEW",
},
{ key: "remaining", name: "Remaining", width: "96px" },
{ key: "status", name: "Status", type: "DEAL_STATUS" },
];

View File

@ -1,4 +1,71 @@
export const isEmpty = string => {
const MINUTE = 60;
const HOUR = MINUTE * 60;
const DAY = HOUR * 24;
const WEEK = DAY * 7;
const MONTH = (DAY * 365) / 12;
const YEAR = DAY * 365;
export const hexToRGBA = (hex, alpha = 1) => {
hex = hex.replace("#", "");
var r = parseInt(
hex.length == 3 ? hex.slice(0, 1).repeat(2) : hex.slice(0, 2),
16
);
var g = parseInt(
hex.length == 3 ? hex.slice(1, 2).repeat(2) : hex.slice(2, 4),
16
);
var b = parseInt(
hex.length == 3 ? hex.slice(2, 3).repeat(2) : hex.slice(4, 6),
16
);
if (alpha) {
return "rgba(" + r + ", " + g + ", " + b + ", " + alpha + ")";
} else {
return "rgb(" + r + ", " + g + ", " + b + ")";
}
};
export const bytesToSize = (bytes, decimals = 2) => {
if (bytes === 0) return "0 Bytes";
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
};
export const getRemainingTime = (seconds) => {
seconds = seconds > 0 ? seconds : 1;
let [value, unit] =
seconds < MINUTE
? [Math.round(seconds), "second"]
: seconds < HOUR
? [Math.round(seconds / MINUTE), "minute"]
: seconds < DAY
? [Math.round(seconds / HOUR), "hour"]
: seconds < WEEK
? [Math.round(seconds / DAY), "day"]
: seconds < MONTH
? [Math.round(seconds / WEEK), "week"]
: seconds < YEAR
? [Math.round(seconds / MONTH), "month"]
: [Math.round(seconds / YEAR), "year"];
unit = pluralize(unit, value);
return `${value} ${unit} remaining`;
};
export const formatAsFilecoin = (number) => {
return `${number} FIL`;
};
export const isEmpty = (string) => {
return !string || !string.toString().trim();
};
@ -6,7 +73,7 @@ export const pluralize = (text, count) => {
return count > 1 || count === 0 ? `${text}s` : text;
};
export const elide = (string, length = 140, emptyState = '...') => {
export const elide = (string, length = 140, emptyState = "...") => {
if (isEmpty(string)) {
return emptyState;
}
@ -18,7 +85,11 @@ export const elide = (string, length = 140, emptyState = '...') => {
return `${string.substring(0, length)}...`;
};
export const toDate = data => {
export const toDate = (data) => {
const date = new Date(data);
return `${date.getMonth() + 1}-${date.getDate()}-${date.getFullYear()}`;
};
export const formatNumber = (x) => {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};

View File

@ -1,14 +1,171 @@
import * as Constants from '~/common/constants';
import * as Constants from "~/common/constants";
import { injectGlobal } from 'react-emotion';
import { css } from "@emotion/react";
/* prettier-ignore */
export default () => injectGlobal`
export const injectTooltipStyles = () =>
css`
.tippy-touch {
cursor: pointer !important;
}
.tippy-notransition {
transition: none !important;
}
.tippy-popper {
z-index: ${Constants.zindex.tooltip};
max-width: 400px;
perspective: 800px;
outline: 0;
transition-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1);
pointer-events: none;
}
.tippy-popper.html-template {
max-width: 96%;
max-width: calc(100% - 20px);
}
.tippy-popper[x-placement^="top"] [x-arrow] {
border-top: 7px solid ${Constants.system.white};
border-right: 7px solid transparent;
border-left: 7px solid transparent;
bottom: -7px;
margin: 0 9px;
}
.tippy-popper[x-placement^="top"] [data-animation="fade"].enter {
opacity: 1;
transform: translateY(-10px);
}
.tippy-popper[x-placement^="top"] [data-animation="fade"].leave {
opacity: 0;
transform: translateY(-10px);
}
.tippy-popper[x-placement^="bottom"] [x-arrow] {
border-bottom: 7px solid ${Constants.system.white};
border-right: 7px solid transparent;
border-left: 7px solid transparent;
top: -7px;
margin: 0 9px;
}
.tippy-popper[x-placement^="bottom"] [data-animation="fade"].enter {
opacity: 1;
transform: translateY(10px);
}
.tippy-popper[x-placement^="bottom"] [data-animation="fade"].leave {
opacity: 0;
transform: translateY(10px);
}
.tippy-popper[x-placement^="left"] [x-arrow] {
border-left: 7px solid ${Constants.system.white};
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
right: -7px;
margin: 6px 0;
}
.tippy-popper[x-placement^="left"] [data-animation="fade"].enter {
opacity: 1;
transform: translateX(-10px);
}
.tippy-popper[x-placement^="left"] [data-animation="fade"].leave {
opacity: 0;
transform: translateX(-10px);
}
.tippy-popper[x-placement^="right"] [x-arrow] {
border-right: 7px solid ${Constants.system.white};
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
left: -7px;
margin: 6px 0;
}
.tippy-popper[x-placement^="right"] [data-animation="fade"].enter {
opacity: 1;
transform: translateX(10px);
}
.tippy-popper[x-placement^="right"] [data-animation="fade"].leave {
opacity: 0;
transform: translateX(10px);
}
.tippy-tooltip {
font-family: ${Constants.font.text};
color: ${Constants.system.white};
background-color: ${Constants.system.pitchBlack};
position: relative;
border-radius: 4px;
font-size: 1rem;
padding: 12px;
text-align: center;
will-change: transform;
border: 1px solid transparent;
}
.tippy-tooltip[data-animatefill] {
overflow: hidden;
background-color: transparent;
}
.tippy-tooltip[data-interactive] {
pointer-events: auto;
}
.tippy-tooltip[data-inertia] {
transition-timing-function: cubic-bezier(0.53, 2, 0.36, 0.85);
}
.tippy-tooltip [x-arrow] {
position: absolute;
width: 0;
height: 0;
}
@media (max-width: 450px) {
.tippy-popper {
max-width: 96%;
max-width: calc(100% - 20px);
}
}
`;
/* prettier-ignore */
export const injectGlobalStyles = () => css`
@font-face {
font-family: 'mono';
src: url('/static/SFMono-Medium.woff');
}
@font-face {
font-family: 'mono-bold';
src: url('/static/SFMono-Bold.woff');
}
@font-face {
font-family: 'inter-regular';
src: url('/static/Inter-Regular.woff');
}
@font-face {
font-family: 'inter-semi-bold';
src: url('/static/Inter-SemiBold.woff');
}
@font-face {
font-family: 'inter-medium';
src: url('/static/Inter-Medium.woff');
}
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
@ -35,18 +192,38 @@ export default () => injectGlobal`
}
html, body {
background: ${Constants.theme.pageBackground};
color: ${Constants.theme.pageText};
background: ${Constants.system.foreground};
color: ${Constants.system.black};
font-size: 16px;
font-family: 'body', -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica,
ubuntu, roboto, noto, segoe ui, arial, sans-serif;
@media (max-width: 768px) {
font-size: 12px;
}
font-family: 'inter-regular', -apple-system, BlinkMacSystemFont, arial, sans-serif;
scrollbar-width: none;
-ms-overflow-style: -ms-autohiding-scrollbar;
::-webkit-scrollbar {
display: none;
}
@media(max-width: 1024px) {
#__next {
display: none;
}
:after {
background: ${Constants.system.pitchBlack};
color: ${Constants.system.white};
content: "This prototype is for desktop/laptop viewports only.";
text-align: center;
padding: 24px;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 333;
display: flex;
align-items: center;
justify-content: center;
position: fixed;
}
}
}
`;

348
common/svg.js Normal file
View File

@ -0,0 +1,348 @@
export const ExpandBox = (props) => (
<svg
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
height={props.height}
style={props.style}
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
>
<path d="m23.251 7.498v-6.75h-6.75" />
<path d="m23.25.75-15 15" />
<path d="m11.251 5.248h-9-.00000007c-.828427.00000004-1.5.671573-1.5 1.5v15-.00000023c-.00000013.828427.671573 1.5 1.5 1.5h15-.00000007c.828427.00000004 1.5-.671573 1.5-1.5v-9" />
</g>
</svg>
);
export const ExpandArrow = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
<path
d="m5.5.75 10.72 10.72c.292711.292294.293049.766535.00075431 1.05925-.00025126.00025162-.0005027.00050305-.00075431.00075431l-10.72 10.72"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
/>
</svg>
);
export const Wallet = (props) => (
<svg
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
height={props.height}
style={props.style}
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect height="21" rx="1.5" width="22.5" x=".75" y=".75" />
<rect height="15" rx="1.5" width="15" x="5.25" y="3.75" />
<path d="m3.75 21.75v1.5" />
<path d="m20.25 21.75v1.5" />
<path d="m3.75 8.25h3" />
<path d="m3.75 14.25h3" />
<circle cx="12.75" cy="11.25" r="2.7" />
<path d="m12.75 8.55v-1.8" />
<path d="m12.75 15.75v-1.8" />
<path d="m15.45 11.25h1.8" />
<path d="m8.25 11.25h1.8" />
<path d="m9.986 14.432 1.075-1.075" />
<path d="m9.986 8.068 1.075 1.075" />
<path d="m15.514 14.432-1.075-1.075" />
<path d="m15.514 8.068-1.075 1.075" />
</g>
</svg>
);
export const NavigationArrow = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
>
<path d="m.75 12h22.5" />
<path d="m12.75 22.5 10.5-10.5-10.5-10.5" fillRule="evenodd" />
</g>
</svg>
);
export const Home = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
<path
d="m19.5 8.4v-4.9a.5.5 0 0 0 -.5-.5h-2a.5.5 0 0 0 -.5.5v2.14l-4.162-3.829a.5.5 0 0 0 -.678 0l-11 10.321a.5.5 0 0 0 .34.868h2.5v9.5a1 1 0 0 0 1 1h15a1 1 0 0 0 1-1v-9.5h2.5a.5.5 0 0 0 .339-.868z"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const Image = (props) => (
<svg
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
height={props.height}
style={props.style}
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m21.207 4.5-.00000002-.00000002c.187549.187493.292943.441805.293.707v17.293c0 .552285-.447715 1-1 1h-17-.00000004c-.552285-.00000002-1-.447715-1-1v-21 .00000015c-.00000008-.552285.447715-1 1-1h13.293.00000001c.265195.00005664.519507.105451.707.293z" />
<path d="m12.826 12.366-2.8-3.74.00000001.00000002c-.165798-.22083-.479221-.265442-.700051-.0996437-.0578698.0434484-.105619.0989405-.139949.162644l-3.276 6.074.00000001-.00000002c-.130892.24315-.0398879.546371.203262.677262.0727636.0391698.154101.0596942.236738.0597376h4.181" />
<path d="m17.3284 13.1716c1.5621 1.5621 1.5621 4.09476 0 5.65685-1.5621 1.5621-4.09476 1.5621-5.65685 0-1.5621-1.5621-1.5621-4.09476 0-5.65685 1.5621-1.5621 4.09476-1.5621 5.65685 0" />
</g>
</svg>
);
export const Folder = (props) => (
<svg
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
height={props.height}
style={props.style}
>
<path
d="m11.236 6h.00000005c-.378666-.0002022-.724736-.214271-.894-.553l-.948-1.894.00000002.00000003c-.169264-.338729-.515334-.552798-.894-.553h-6.5-.00000004c-.552285.00000002-1 .447715-1 1v16 .00000015c.00000008.552285.447715 1 1 1h20-.00000004c.552285.00000002 1-.447715 1-1v-13c0-.552285-.447715-1-1-1z"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const Channels = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="16.004" cy="8" r="7.5" />
<path d="m8.53 8.526a7.5 7.5 0 1 0 6.948 6.948" />
<path d="m7.504 13.5v-1" />
<path d="m9 13.5h-2.029a1.342 1.342 0 0 0 -.5 2.587l2.064.826a1.342 1.342 0 0 1 -.5 2.587h-2.035" />
<path d="m7.504 20.5v-1" />
<path d="m16.004 5v-1" />
<path d="m17.5 5h-2.029a1.342 1.342 0 0 0 -.5 2.587l2.064.826a1.342 1.342 0 0 1 -.5 2.587h-2.035" />
<path d="m16.004 12v-1" />
</g>
</svg>
);
export const Peers = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m17 7.02 3.11-3.09" />
<path d="m22.9142 1.08579c.781049.781049.781049 2.04738 0 2.82843-.781049.781049-2.04738.781049-2.82843 0-.781049-.781049-.781049-2.04738 0-2.82843.781049-.781049 2.04738-.781049 2.82843 0" />
<path d="m17.96 17.98 2.12 2.13" />
<path d="m22.9242 20.0858c.781049.781049.781049 2.04738 0 2.82843-.781049.781049-2.04738.781049-2.82843 0-.781049-.781049-.781049-2.04738 0-2.82843.781049-.781049 2.04738-.781049 2.82843 0" />
<path d="m7 7.02-3.11-3.09" />
<path d="m3.91421 1.08579c.781049.781049.781049 2.04738 0 2.82843-.781049.781049-2.04738.781049-2.82843 0-.781049-.781049-.781049-2.04738 0-2.82843.781049-.781049 2.04738-.781049 2.82843 0" />
<path d="m6.04 17.98-2.12 2.13" />
<path d="m3.90421 20.0858c.781049.781049.781049 2.04738 0 2.82843-.781049.781049-2.04738.781049-2.82843 0-.781049-.781049-.781049-2.04738 0-2.82843.781049-.781049 2.04738-.781049 2.82843 0" />
<path d="m16.5 11.5h3" />
<path d="m22.9142 10.0858c.781049.781049.781049 2.04738 0 2.82843-.781049.781049-2.04738.781049-2.82843 0-.781049-.781049-.781049-2.04738 0-2.82843.781049-.781049 2.04738-.781049 2.82843 0" />
<path d="m7.5 11.5h-3" />
<path d="m3.91421 10.0858c.781049.781049.781049 2.04738 0 2.82843-.781049.781049-2.04738.781049-2.82843 0-.781049-.781049-.781049-2.04738 0-2.82843.781049-.781049 2.04738-.781049 2.82843 0" />
<path d="m7.51 16.5v-.00000059c.00000038-2.48528 2.01472-4.5 4.5-4.5 2.48528.00000038 4.5 2.01472 4.5 4.5z" />
<path d="m13.9545 6.30546c1.07394 1.07394 1.07394 2.81515 0 3.88909s-2.81515 1.07394-3.88909 0-1.07394-2.81515 0-3.88909 2.81515-1.07394 3.88909 0" />
</g>
</svg>
);
export const Deals = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m23.5 22-1-1.934v-4.566c.042-1.778-2.081-3.363-4-5" />
<path d="m20.019 17.5-2.551-2.607.00000002.00000002c-.476378-.495833-1.26451-.511603-1.76034-.035225-.495833.476378-.511603 1.26451-.035225 1.76034.00382974.00398614.00768599.00794673.0115685.0118815l2.816 2.87v1.5l-.00000021-.0000012c.153133.894577.493939 1.74659 1 2.5" />
<path d="m.5 2 1 1.934v4.566c-.042 1.778 2.081 3.363 4 5" />
<path d="m3.981 6.5 2.551 2.607.00000001.00000001c.476378.495833 1.26451.511603 1.76034.035225.495833-.476378.511603-1.26451.035225-1.76034-.00382974-.00398614-.00768599-.00794673-.0115685-.0118815l-2.816-2.87v-1.5l.00000019.00000112c-.153133-.894577-.493939-1.74659-1-2.5" />
<path d="m5.5 8.052v11.448.00000015c.00000008.552285.447715 1 1 1h9.5" />
<path d="m18.5 15.948v-11.448c0-.552285-.447715-1-1-1h-10" />
</g>
</svg>
);
export const DataTransfer = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m20.5 14.406a4.311 4.311 0 0 0 2.5-4.049 4.711 4.711 0 0 0 -4.954-4.635 6.706 6.706 0 0 0 -6.046-3.722 6.605 6.605 0 0 0 -6.675 6.109 3.561 3.561 0 0 0 -4.325 3.409 3.186 3.186 0 0 0 2.5 3.282" />
<path d="m6 19 3 3 3-3" />
<path d="m9 22v-9" />
<path d="m12 16 3-3 3 3" />
<path d="m15 13v9" />
</g>
</svg>
);
export const Stats = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
<path
d="m.5 12.001h6l3-10 3 19 3-14 2 5h6"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
export const Logs = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m8.5 20.5h-7a1 1 0 0 1 -1-1v-16a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v5" />
<path d="m4.5 4.5v-4" />
<path d="m8.5 4.5v-4" />
<path d="m12.5 4.5v-4" />
<path d="m17.5 20.5a.25.25 0 1 1 -.25.25.25.25 0 0 1 .25-.25" />
<path d="m17.5 18.5v-3" />
<path d="m18.338 12.5a.95.95 0 0 0 -1.676 0l-5.056 9.635a.923.923 0 0 0 .031.914.948.948 0 0 0 .807.448h10.112a.948.948 0 0 0 .807-.448.923.923 0 0 0 .031-.914z" />
</g>
</svg>
);
export const Status = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m14.061 5.243a1.5 1.5 0 0 1 0 2.121" />
<path d="m16.182 3.121a4.5 4.5 0 0 1 0 6.364" />
<path d="m16.182 3.121a4.5 4.5 0 0 1 0 6.364" />
<path d="m18.3 1a7.5 7.5 0 0 1 0 10.607" />
<path d="m18.3 1a7.5 7.5 0 0 1 0 10.607" />
<path d="m9.939 5.243a1.5 1.5 0 0 0 0 2.121" />
<path d="m7.818 3.121a4.5 4.5 0 0 0 0 6.364" />
<path d="m7.818 3.121a4.5 4.5 0 0 0 0 6.364" />
<path d="m5.7 1a7.5 7.5 0 0 0 0 10.607" />
<path d="m5.7 1a7.5 7.5 0 0 0 0 10.607" />
<path d="m23.5 19a2 2 0 0 1 -2 2h-19a2 2 0 0 1 -2-2v-2a2 2 0 0 1 2-2h19a2 2 0 0 1 2 2z" />
<path d="m4.75 17.75a.25.25 0 1 0 .25.25.25.25 0 0 0 -.25-.25z" />
<path d="m8.25 17.75a.25.25 0 1 0 .25.25.25.25 0 0 0 -.25-.25z" />
<path d="m12 15v-5" />
<path d="m4 21-1.5 2" />
<path d="m20 21 1.5 2" />
</g>
</svg>
);
export const Miners = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m2.561 23.207a1 1 0 0 1 -1.415 0l-.353-.353a1 1 0 0 1 0-1.414l13.016-13.018 1.768 1.768z" />
<path d="m23.5 16.879a17 17 0 0 0 -16.379-16.379.5.5 0 0 0 -.24.948 33.1 33.1 0 0 1 7.526 4.963l-.951.951a.5.5 0 0 0 0 .707l2.474 2.475a.5.5 0 0 0 .707 0l.952-.951a33.076 33.076 0 0 1 4.962 7.526.5.5 0 0 0 .949-.24z" />
<path d="m19.383 6.384.79-.79a1 1 0 0 0 0-1.415l-.353-.353a1 1 0 0 0 -1.414 0l-.791.791" />
</g>
</svg>
);
export const StorageMarket = (props) => (
<svg viewBox="0 0 24 24" height={props.height} style={props.style}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m23.5 22h-22.5a.5.5 0 0 1 -.5-.5v-19.5" />
<path d="m12.872 15.523c.182 1 .458 3.477 3.128 3.477" />
<path d="m3 19a3 3 0 0 0 2.947-2.46l1.2-6.571a2.4 2.4 0 0 1 3.8-1.487" />
<path d="m8 19a3 3 0 0 0 2.947-2.46l1.2-6.571a2.4 2.4 0 0 1 4.714 0l1.2 6.571a3 3 0 0 0 2.939 2.46" />
</g>
</svg>
);
export const PowerButton = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
height={props.height}
style={props.style}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M18.36 6.64a9 9 0 1 1-12.73 0" />
<line x1="12" y1="2" x2="12" y2="12" />
</svg>
);
export const Bell = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
height={props.height}
style={props.style}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
</svg>
);
export const Logo = (props) => (
<svg
viewBox="0 0 127 127"
height={props.height}
style={props.style}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m71.4 57.6c-.7 3.3-1.3 6.4-2 9.8 5.9.9 11.7 1.7 17.7 2.6-.5 1.6-.9 3.1-1.4 4.8-5.8-.8-11.5-1.7-17.3-2.5-1.1 4.2-2 8.3-3.3 12.2-1.4 4.4-3 8.7-5 12.9-2.6 5.2-6.8 8.9-12.7 10.1-3.5.7-7 .4-10-1.9-.9-.7-1.8-1.8-1.9-2.8-.1-1.1.5-2.7 1.4-3.3.6-.5 2.3-.1 3.1.5 1.1.8 1.8 2.1 2.6 3.3 1.8 2.5 4.4 2.9 6.8.8 2.9-2.5 4.4-5.8 5.3-9.3 1.9-7.3 3.6-14.8 5.3-22.2.1-.3 0-.7 0-1.3-5.4-.8-10.8-1.7-16.5-2.5.2-1.6.4-3 .6-4.8 5.6.8 11.1 1.6 17 2.4.8-3.2 1.5-6.4 2.3-9.7-6-.9-11.7-1.8-17.8-2.7.2-1.6.4-3.2.6-4.8 6.1.9 12 1.7 18.2 2.6.5-1.8.9-3.5 1.4-5.1 1.7-5.6 3.2-11.3 6.8-16.2s8.1-8.1 14.5-7.8c2.8.1 5.5.9 7.5 3.2.4.5 1 1.1 1 1.6-.1 1.2 0 2.9-.8 3.6-1.1 1.1-2.8.5-4-.6-.9-.9-1.6-1.9-2.3-2.9-1.8-2.4-4.7-2.9-6.8-.7-1.6 1.7-3.2 3.7-3.9 5.9-2.1 6.6-3.8 13.2-5.8 20.2 5.9.9 11.4 1.7 17.1 2.5-.5 1.6-.9 3.1-1.3 4.7-5.5-1.1-10.9-1.9-16.4-2.6z"
fill="currentColor"
/>
</svg>
);

View File

@ -0,0 +1,46 @@
const FRAMES_PER_SECOND = 60;
const SECOND = 1000;
export default class AnimationLoop {
_subscribers = [];
_loopId = null;
_timeNow = null;
_timeThen = null;
loop = () => {
this._timeNow = Date.now();
const timeDelta = this._timeNow - this._timeThen;
if (timeDelta > SECOND / FRAMES_PER_SECOND) {
this._timeThen =
this._timeNow - (timeDelta % (SECOND / FRAMES_PER_SECOND));
this._subscribers.forEach(callback => {
callback.call();
});
}
this._loopID = window.requestAnimationFrame(this.loop);
};
start() {
this._timeThen = Date.now();
if (!this._loopID) {
this.loop();
}
}
stop() {
this._timeThen = null;
this._timeNow = null;
window.cancelAnimationFrame(this._loopId);
}
subscribe(callback) {
return this._subscribers.push(callback);
}
unsubscribeAll() {
this._subscribers = [];
}
}

View File

@ -0,0 +1,147 @@
import * as THREE from "three";
import OrbitControls from "~/common/three/orbital.js";
import GlobePoints from "~/common/three/points-3.json";
const convertFlatCoordsToSphereCoords = ({ globe, x, y }) => {
let latitude = ((x - globe.width) / globe.width) * -180;
let longitude = ((y - globe.height) / globe.height) * -90;
latitude = (latitude * Math.PI) / 180;
longitude = (longitude * Math.PI) / 180;
const radius = Math.cos(longitude) * globe.radius;
return {
x: Math.cos(latitude) * radius,
y: Math.sin(longitude) * globe.radius,
z: Math.sin(latitude) * radius,
};
};
const getViewportData = (width, height) => {
const viewSize = height;
const aspectRatio = width / height;
return {
viewSize: viewSize,
aspectRatio: aspectRatio,
left: (-aspectRatio * viewSize) / 2,
right: (aspectRatio * viewSize) / 2,
top: viewSize / 2,
bottom: viewSize / -2,
near: -2048,
far: 2048,
};
};
export default class GLComponent {
constructor(props) {
this.state = {
mountedNodes: [],
width: props.width,
height: props.height,
scene: undefined,
renderer: undefined,
container: undefined,
camera: undefined,
controls: undefined,
measurements: undefined,
...props,
};
}
setState(newProps) {
this.state = { ...this.state, ...newProps };
}
unmount() {
this.state.mountedNodes = null;
this.state.width = null;
this.state.height = null;
this.state.scene = null;
this.state.renderer = null;
this.state.container = null;
this.state.camera = null;
this.state.controls = null;
this.state.measurements = null;
this.render = () => {
console.log("Error: If this is getting called, that is bad.");
};
}
async mount() {
this.state.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
this.state.renderer.shadowMap.enabled = true;
this.state.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.state.renderer.physicallyBasedShading = true;
this.state.renderer.setClearColor(0x000000, 0);
this.state.renderer.setPixelRatio(window.devicePixelRatio);
this.state.renderer.setSize(this.state.width, this.state.height);
this.state.scene = new THREE.Scene();
this.state.container.appendChild(this.state.renderer.domElement);
const viewport = getViewportData(this.state.width, this.state.height);
this.state.camera = new THREE.OrthographicCamera(
viewport.left,
viewport.right,
viewport.top,
viewport.bottom,
viewport.near,
viewport.far
);
this.state.camera.position.x = 0.2;
this.state.camera.position.y = 0.25;
this.state.camera.position.z = 0.2;
this.state.controls = new OrbitControls(
this.state.camera,
this.state.renderer.domElement
);
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x888888, 0.9);
this.state.scene.add(hemisphereLight);
const ambientLight = new THREE.AmbientLight(0x888888, 0.7);
this.state.scene.add(ambientLight);
}
firstRender() {
const mergedGeometry = new THREE.Geometry();
const pointGeometry = new THREE.SphereGeometry(2, 1, 1);
const pointMaterial = new THREE.MeshBasicMaterial({
color: "#0047FF",
});
const { points } = GlobePoints;
for (let point of points) {
const { x, y, z } = convertFlatCoordsToSphereCoords({
x: point.x,
y: point.y,
globe: { radius: 188, width: 4098 / 2, height: 1968 / 2 },
});
pointGeometry.translate(x, y, z);
mergedGeometry.merge(pointGeometry);
pointGeometry.translate(-x, -y, -z);
}
const globeShape = new THREE.Mesh(mergedGeometry, pointMaterial);
this.state.mountedNodes.push(globeShape);
this.state.scene.add(globeShape);
}
axis = new THREE.Vector3(0, 1, 0).normalize();
render() {
this.state.mountedNodes.forEach((n) => {
const object = this.state.scene.getObjectById(n.id);
object.rotateOnAxis(this.axis, Math.PI * 0.001);
});
this.state.renderer.render(this.state.scene, this.state.camera);
}
}

912
common/three/orbital.js Normal file
View File

@ -0,0 +1,912 @@
import {
Vector3,
MOUSE,
Quaternion,
Spherical,
Vector2,
EventDispatcher,
} from "three";
/**
* @author qiao / https://github.com/qiao
* @author mrdoob / http://mrdoob.com
* @author alteredq / http://alteredqualia.com/
* @author WestLangley / http://github.com/WestLangley
* @author erich666 / http://erichaines.com
*/
class OrbitControls extends EventDispatcher {
constructor(object, domElement) {
super();
this.object = object;
this.domElement = domElement !== undefined ? domElement : document;
// Set to false to disable this control
this.enabled = true;
// "target" sets the location of focus, where the object orbits around
this.target = new Vector3();
// How far you can dolly in and out ( PerspectiveCamera only )
this.minDistance = 0;
this.maxDistance = Infinity;
// How far you can zoom in and out ( OrthographicCamera only )
this.minZoom = 0;
this.maxZoom = Infinity;
// How far you can orbit vertically, upper and lower limits.
// Range is 0 to Math.PI radians.
this.minPolarAngle = 0; // radians
this.maxPolarAngle = Math.PI; // radians
// How far you can orbit horizontally, upper and lower limits.
// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
this.minAzimuthAngle = -Infinity; // radians
this.maxAzimuthAngle = Infinity; // radians
// Set to true to enable damping (inertia)
// If damping is enabled, you must call controls.update() in your animation loop
this.enableDamping = false;
this.dampingFactor = 0.25;
// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
// Set to false to disable zooming
this.enableZoom = false;
this.zoomSpeed = 1.0;
// Set to false to disable rotating
this.enableRotate = true;
this.rotateSpeed = 1.0;
// Set to false to disable panning
this.enablePan = false;
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
// Set to true to automatically rotate around the target
// If auto-rotate is enabled, you must call controls.update() in your animation loop
this.autoRotate = false;
this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
// Set to false to disable use of the keys
this.enableKeys = true;
// The four arrow keys
this.keys = {
LEFT: 37,
UP: 38,
RIGHT: 39,
BOTTOM: 40,
};
// Mouse buttons
this.mouseButtons = {
ORBIT: MOUSE.LEFT,
ZOOM: MOUSE.MIDDLE,
PAN: MOUSE.RIGHT,
};
// for reset
this.target0 = this.target.clone();
this.position0 = this.object.position.clone();
this.zoom0 = this.object.zoom;
//
// public methods
//
this.getPolarAngle = () => spherical.phi;
this.getAzimuthalAngle = () => spherical.theta;
this.reset = function() {
scope.target.copy(scope.target0);
scope.object.position.copy(scope.position0);
scope.object.zoom = scope.zoom0;
scope.object.updateProjectionMatrix();
scope.dispatchEvent(changeEvent);
scope.update();
state = STATE.NONE;
};
// this method is exposed, but perhaps it would be better if we can make it private...
this.update = (function() {
var offset = new Vector3();
// so camera.up is the orbit axis
var quat = new Quaternion().setFromUnitVectors(
object.up,
new Vector3(0, 1, 0)
);
var quatInverse = quat.clone().inverse();
var lastPosition = new Vector3();
var lastQuaternion = new Quaternion();
return function update() {
var position = scope.object.position;
offset.copy(position).sub(scope.target);
// rotate offset to "y-axis-is-up" space
offset.applyQuaternion(quat);
// angle from z-axis around y-axis
spherical.setFromVector3(offset);
if (scope.autoRotate && state === STATE.NONE) {
rotateLeft(getAutoRotationAngle());
}
spherical.theta += sphericalDelta.theta;
spherical.phi += sphericalDelta.phi;
// restrict theta to be between desired limits
spherical.theta = Math.max(
scope.minAzimuthAngle,
Math.min(scope.maxAzimuthAngle, spherical.theta)
);
// restrict phi to be between desired limits
spherical.phi = Math.max(
scope.minPolarAngle,
Math.min(scope.maxPolarAngle, spherical.phi)
);
spherical.makeSafe();
spherical.radius *= scale;
// restrict radius to be between desired limits
spherical.radius = Math.max(
scope.minDistance,
Math.min(scope.maxDistance, spherical.radius)
);
// move target to panned location
scope.target.add(panOffset);
offset.setFromSpherical(spherical);
// rotate offset back to "camera-up-vector-is-up" space
offset.applyQuaternion(quatInverse);
position.copy(scope.target).add(offset);
scope.object.lookAt(scope.target);
if (scope.enableDamping === true) {
sphericalDelta.theta *= 1 - scope.dampingFactor;
sphericalDelta.phi *= 1 - scope.dampingFactor;
} else {
sphericalDelta.set(0, 0, 0);
}
scale = 1;
panOffset.set(0, 0, 0);
// update condition is:
// min(camera displacement, camera rotation in radians)^2 > EPS
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
if (
zoomChanged ||
lastPosition.distanceToSquared(scope.object.position) > EPS ||
8 * (1 - lastQuaternion.dot(scope.object.quaternion)) > EPS
) {
scope.dispatchEvent(changeEvent);
lastPosition.copy(scope.object.position);
lastQuaternion.copy(scope.object.quaternion);
zoomChanged = false;
return true;
}
return false;
};
})();
this.dispose = function() {
scope.domElement.removeEventListener("contextmenu", onContextMenu, false);
scope.domElement.removeEventListener("mousedown", onMouseDown, false);
scope.domElement.removeEventListener("wheel", onMouseWheel, false);
scope.domElement.removeEventListener("touchstart", onTouchStart, false);
scope.domElement.removeEventListener("touchend", onTouchEnd, false);
scope.domElement.removeEventListener("touchmove", onTouchMove, false);
document.removeEventListener("mousemove", onMouseMove, false);
document.removeEventListener("mouseup", onMouseUp, false);
window.removeEventListener("keydown", onKeyDown, false);
// scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
};
//
// internals
//
var scope = this;
var changeEvent = {
type: "change",
};
var startEvent = {
type: "start",
};
var endEvent = {
type: "end",
};
var STATE = {
NONE: -1,
ROTATE: 0,
DOLLY: 1,
PAN: 2,
TOUCH_ROTATE: 3,
TOUCH_DOLLY: 4,
TOUCH_PAN: 5,
};
var state = STATE.NONE;
var EPS = 0.000001;
// current position in spherical coordinates
var spherical = new Spherical();
var sphericalDelta = new Spherical();
var scale = 1;
var panOffset = new Vector3();
var zoomChanged = false;
var rotateStart = new Vector2();
var rotateEnd = new Vector2();
var rotateDelta = new Vector2();
var panStart = new Vector2();
var panEnd = new Vector2();
var panDelta = new Vector2();
var dollyStart = new Vector2();
var dollyEnd = new Vector2();
var dollyDelta = new Vector2();
function getAutoRotationAngle() {
return ((2 * Math.PI) / 60 / 60) * scope.autoRotateSpeed;
}
function getZoomScale() {
return Math.pow(0.95, scope.zoomSpeed);
}
function rotateLeft(angle) {
sphericalDelta.theta -= angle;
}
function rotateUp(angle) {
sphericalDelta.phi -= angle;
}
var panLeft = (function() {
var v = new Vector3();
return function panLeft(distance, objectMatrix) {
v.setFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix
v.multiplyScalar(-distance);
panOffset.add(v);
};
})();
var panUp = (function() {
var v = new Vector3();
return function panUp(distance, objectMatrix) {
v.setFromMatrixColumn(objectMatrix, 1); // get Y column of objectMatrix
v.multiplyScalar(distance);
panOffset.add(v);
};
})();
// deltaX and deltaY are in pixels; right and down are positive
var pan = (function() {
var offset = new Vector3();
return function pan(deltaX, deltaY) {
var element =
scope.domElement === document
? scope.domElement.body
: scope.domElement;
if (Object.getPrototypeOf(scope.object).isPerspectiveCamera) {
// perspective
var position = scope.object.position;
offset.copy(position).sub(scope.target);
var targetDistance = offset.length();
// half of the fov is center to top of screen
targetDistance *= Math.tan(
((scope.object.fov / 2) * Math.PI) / 180.0
);
// we actually don't use screenWidth, since perspective camera is fixed to screen height
panLeft(
(2 * deltaX * targetDistance) / element.clientHeight,
scope.object.matrix
);
panUp(
(2 * deltaY * targetDistance) / element.clientHeight,
scope.object.matrix
);
} else if (Object.getPrototypeOf(scope.object).isOrthographicCamera) {
// orthographic
panLeft(
(deltaX * (scope.object.right - scope.object.left)) /
scope.object.zoom /
element.clientWidth,
scope.object.matrix
);
panUp(
(deltaY * (scope.object.top - scope.object.bottom)) /
scope.object.zoom /
element.clientHeight,
scope.object.matrix
);
} else {
// camera neither orthographic nor perspective
console.warn(
"WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."
);
scope.enablePan = false;
}
};
})();
function dollyIn(dollyScale) {
if (Object.getPrototypeOf(scope.object).isPerspectiveCamera) {
scale /= dollyScale;
} else if (Object.getPrototypeOf(scope.object).isOrthographicCamera) {
scope.object.zoom = Math.max(
scope.minZoom,
Math.min(scope.maxZoom, scope.object.zoom * dollyScale)
);
scope.object.updateProjectionMatrix();
zoomChanged = true;
} else {
console.warn(
"WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."
);
scope.enableZoom = false;
}
}
function dollyOut(dollyScale) {
if (Object.getPrototypeOf(scope.object).isPerspectiveCamera) {
scale *= dollyScale;
} else if (Object.getPrototypeOf(scope.object).isOrthographicCamera) {
scope.object.zoom = Math.max(
scope.minZoom,
Math.min(scope.maxZoom, scope.object.zoom / dollyScale)
);
scope.object.updateProjectionMatrix();
zoomChanged = true;
} else {
console.warn(
"WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."
);
scope.enableZoom = false;
}
}
//
// event callbacks - update the object state
//
function handleMouseDownRotate(event) {
// console.log( 'handleMouseDownRotate' );
rotateStart.set(event.clientX, event.clientY);
}
function handleMouseDownDolly(event) {
// console.log( 'handleMouseDownDolly' );
dollyStart.set(event.clientX, event.clientY);
}
function handleMouseDownPan(event) {
// console.log( 'handleMouseDownPan' );
panStart.set(event.clientX, event.clientY);
}
function handleMouseMoveRotate(event) {
// console.log( 'handleMouseMoveRotate' );
rotateEnd.set(event.clientX, event.clientY);
rotateDelta.subVectors(rotateEnd, rotateStart);
var element =
scope.domElement === document
? scope.domElement.body
: scope.domElement;
// rotating across whole screen goes 360 degrees around
rotateLeft(
((2 * Math.PI * rotateDelta.x) / element.clientWidth) *
scope.rotateSpeed
);
// rotating up and down along whole screen attempts to go 360, but limited to 180
rotateUp(
((2 * Math.PI * rotateDelta.y) / element.clientHeight) *
scope.rotateSpeed
);
rotateStart.copy(rotateEnd);
scope.update();
}
function handleMouseMoveDolly(event) {
// console.log( 'handleMouseMoveDolly' );
dollyEnd.set(event.clientX, event.clientY);
dollyDelta.subVectors(dollyEnd, dollyStart);
if (dollyDelta.y > 0) {
dollyIn(getZoomScale());
} else if (dollyDelta.y < 0) {
dollyOut(getZoomScale());
}
dollyStart.copy(dollyEnd);
scope.update();
}
function handleMouseMovePan(event) {
// console.log( 'handleMouseMovePan' );
panEnd.set(event.clientX, event.clientY);
panDelta.subVectors(panEnd, panStart);
pan(panDelta.x, panDelta.y);
panStart.copy(panEnd);
scope.update();
}
function handleMouseWheel(event) {
// console.log( 'handleMouseWheel' );
if (event.deltaY < 0) {
dollyOut(getZoomScale());
} else if (event.deltaY > 0) {
dollyIn(getZoomScale());
}
scope.update();
}
function handleKeyDown(event) {
// console.log( 'handleKeyDown' );
switch (event.keyCode) {
case scope.keys.UP:
pan(0, scope.keyPanSpeed);
scope.update();
break;
case scope.keys.BOTTOM:
pan(0, -scope.keyPanSpeed);
scope.update();
break;
case scope.keys.LEFT:
pan(scope.keyPanSpeed, 0);
scope.update();
break;
case scope.keys.RIGHT:
pan(-scope.keyPanSpeed, 0);
scope.update();
break;
}
}
function handleTouchStartRotate(event) {
// console.log( 'handleTouchStartRotate' );
rotateStart.set(event.touches[0].pageX, event.touches[0].pageY);
}
function handleTouchStartDolly(event) {
// console.log( 'handleTouchStartDolly' );
var dx = event.touches[0].pageX - event.touches[1].pageX;
var dy = event.touches[0].pageY - event.touches[1].pageY;
var distance = Math.sqrt(dx * dx + dy * dy);
dollyStart.set(0, distance);
}
function handleTouchStartPan(event) {
// console.log( 'handleTouchStartPan' );
panStart.set(event.touches[0].pageX, event.touches[0].pageY);
}
function handleTouchMoveRotate(event) {
// console.log( 'handleTouchMoveRotate' );
rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY);
rotateDelta.subVectors(rotateEnd, rotateStart);
var element =
scope.domElement === document
? scope.domElement.body
: scope.domElement;
// rotating across whole screen goes 360 degrees around
rotateLeft(
((2 * Math.PI * rotateDelta.x) / element.clientWidth) *
scope.rotateSpeed
);
// rotating up and down along whole screen attempts to go 360, but limited to 180
rotateUp(
((2 * Math.PI * rotateDelta.y) / element.clientHeight) *
scope.rotateSpeed
);
rotateStart.copy(rotateEnd);
scope.update();
}
function handleTouchMoveDolly(event) {
// console.log( 'handleTouchMoveDolly' );
var dx = event.touches[0].pageX - event.touches[1].pageX;
var dy = event.touches[0].pageY - event.touches[1].pageY;
var distance = Math.sqrt(dx * dx + dy * dy);
dollyEnd.set(0, distance);
dollyDelta.subVectors(dollyEnd, dollyStart);
if (dollyDelta.y > 0) {
dollyOut(getZoomScale());
} else if (dollyDelta.y < 0) {
dollyIn(getZoomScale());
}
dollyStart.copy(dollyEnd);
scope.update();
}
function handleTouchMovePan(event) {
// console.log( 'handleTouchMovePan' );
panEnd.set(event.touches[0].pageX, event.touches[0].pageY);
panDelta.subVectors(panEnd, panStart);
pan(panDelta.x, panDelta.y);
panStart.copy(panEnd);
scope.update();
}
//
// event handlers - FSM: listen for events and reset state
//
function onMouseDown(event) {
if (scope.enabled === false) return;
event.preventDefault();
if (event.button === scope.mouseButtons.ORBIT) {
if (scope.enableRotate === false) return;
handleMouseDownRotate(event);
state = STATE.ROTATE;
} else if (event.button === scope.mouseButtons.ZOOM) {
if (scope.enableZoom === false) return;
handleMouseDownDolly(event);
state = STATE.DOLLY;
} else if (event.button === scope.mouseButtons.PAN) {
if (scope.enablePan === false) return;
handleMouseDownPan(event);
state = STATE.PAN;
}
if (state !== STATE.NONE) {
document.addEventListener("mousemove", onMouseMove, false);
document.addEventListener("mouseup", onMouseUp, false);
scope.dispatchEvent(startEvent);
}
}
function onMouseMove(event) {
if (scope.enabled === false) return;
event.preventDefault();
if (state === STATE.ROTATE) {
if (scope.enableRotate === false) return;
handleMouseMoveRotate(event);
} else if (state === STATE.DOLLY) {
if (scope.enableZoom === false) return;
handleMouseMoveDolly(event);
} else if (state === STATE.PAN) {
if (scope.enablePan === false) return;
handleMouseMovePan(event);
}
}
function onMouseUp(event) {
if (scope.enabled === false) return;
document.removeEventListener("mousemove", onMouseMove, false);
document.removeEventListener("mouseup", onMouseUp, false);
scope.dispatchEvent(endEvent);
state = STATE.NONE;
}
function onMouseWheel(event) {
if (
scope.enabled === false ||
scope.enableZoom === false ||
(state !== STATE.NONE && state !== STATE.ROTATE)
)
return;
event.preventDefault();
event.stopPropagation();
handleMouseWheel(event);
scope.dispatchEvent(startEvent); // not sure why these are here...
scope.dispatchEvent(endEvent);
}
function onKeyDown(event) {
if (
scope.enabled === false ||
scope.enableKeys === false ||
scope.enablePan === false
)
return;
handleKeyDown(event);
}
function onTouchStart(event) {
if (scope.enabled === false) return;
switch (event.touches.length) {
case 1: // one-fingered touch: rotate
if (scope.enableRotate === false) return;
handleTouchStartRotate(event);
state = STATE.TOUCH_ROTATE;
break;
case 2: // two-fingered touch: dolly
if (scope.enableZoom === false) return;
handleTouchStartDolly(event);
state = STATE.TOUCH_DOLLY;
break;
case 3: // three-fingered touch: pan
if (scope.enablePan === false) return;
handleTouchStartPan(event);
state = STATE.TOUCH_PAN;
break;
default:
state = STATE.NONE;
}
if (state !== STATE.NONE) {
scope.dispatchEvent(startEvent);
}
}
function onTouchMove(event) {
if (scope.enabled === false) return;
event.preventDefault();
event.stopPropagation();
switch (event.touches.length) {
case 1: // one-fingered touch: rotate
if (scope.enableRotate === false) return;
if (state !== STATE.TOUCH_ROTATE) return; // is this needed?...
handleTouchMoveRotate(event);
break;
case 2: // two-fingered touch: dolly
if (scope.enableZoom === false) return;
if (state !== STATE.TOUCH_DOLLY) return; // is this needed?...
handleTouchMoveDolly(event);
break;
case 3: // three-fingered touch: pan
if (scope.enablePan === false) return;
if (state !== STATE.TOUCH_PAN) return; // is this needed?...
handleTouchMovePan(event);
break;
default:
state = STATE.NONE;
}
}
function onTouchEnd(event) {
if (scope.enabled === false) return;
scope.dispatchEvent(endEvent);
state = STATE.NONE;
}
function onContextMenu(event) {
event.preventDefault();
}
//
scope.domElement.addEventListener("contextmenu", onContextMenu, false);
scope.domElement.addEventListener("mousedown", onMouseDown, false);
scope.domElement.addEventListener("wheel", onMouseWheel, false);
scope.domElement.addEventListener("touchstart", onTouchStart, false);
scope.domElement.addEventListener("touchend", onTouchEnd, false);
scope.domElement.addEventListener("touchmove", onTouchMove, false);
window.addEventListener("keydown", onKeyDown, false);
// force an update at start
this.update();
}
get center() {
console.warn("OrbitControls: .center has been renamed to .target");
return this.target;
}
// backward compatibility
get noZoom() {
console.warn(
"OrbitControls: .noZoom has been deprecated. Use .enableZoom instead."
);
return !this.enableZoom;
}
set noZoom(value) {
console.warn(
"OrbitControls: .noZoom has been deprecated. Use .enableZoom instead."
);
this.enableZoom = !value;
}
get noRotate() {
console.warn(
"OrbitControls: .noRotate has been deprecated. Use .enableRotate instead."
);
return !this.enableRotate;
}
set noRotate(value) {
console.warn(
"OrbitControls: .noRotate has been deprecated. Use .enableRotate instead."
);
this.enableRotate = !value;
}
get noPan() {
console.warn(
"OrbitControls: .noPan has been deprecated. Use .enablePan instead."
);
return !this.enablePan;
}
set noPan(value) {
console.warn(
"OrbitControls: .noPan has been deprecated. Use .enablePan instead."
);
this.enablePan = !value;
}
get noKeys() {
console.warn(
"OrbitControls: .noKeys has been deprecated. Use .enableKeys instead."
);
return !this.enableKeys;
}
set noKeys(value) {
console.warn(
"OrbitControls: .noKeys has been deprecated. Use .enableKeys instead."
);
this.enableKeys = !value;
}
get staticMoving() {
console.warn(
"OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead."
);
return !this.enableDamping;
}
set staticMoving(value) {
console.warn(
"OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead."
);
this.enableDamping = !value;
}
get dynamicDampingFactor() {
console.warn(
"OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead."
);
return this.dampingFactor;
}
set dynamicDampingFactor(value) {
console.warn(
"OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead."
);
this.dampingFactor = value;
}
}
export default OrbitControls;

File diff suppressed because one or more lines are too long

52220
common/three/points.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,22 +0,0 @@
import * as Strings from '~/common/strings';
// TODO(jim): Refactor this Regex so you can bind the string.
export const getToken = req => {
if (Strings.isEmpty(req.headers.cookie)) {
return null;
}
return req.headers.cookie.replace(
/(?:(?:^|.*;\s*)WEB_SERVICE_SESSION_KEY\s*\=\s*([^;]*).*$)|^.*$/,
'$1'
);
};
export const parseAuthHeader = value => {
if (typeof value !== 'string') {
return null;
}
var matches = value.match(/(\S+)\s+(\S+)/);
return matches && { scheme: matches[1], value: matches[2] };
};

75
common/window.js Normal file
View File

@ -0,0 +1,75 @@
const getNavigatorAgent = userAgent => {
return userAgent ? userAgent : navigator.userAgent || navigator.vendor || window.opera;
};
export const delay = async waitMs => {
return await new Promise(resolve => setTimeout(resolve, waitMs));
};
export const checkIfElementIsVisible = el => {
const rect = el.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
const isVisible =
rect.left >= 0 &&
rect.top >= 0 &&
rect.left + rect.width <= windowWidth &&
rect.top + rect.height <= windowHeight;
return isVisible;
};
export const getViewportSize = () => {
const width = Math.max(
document.documentElement ? document.documentElement.clientWidth : 0,
window.innerWidth || 0
);
const height = Math.max(
document.documentElement ? document.documentElement.clientHeight : 0,
window.innerHeight || 0
);
return {
width,
height,
};
};
export const isDescendant = (parent, child) => {
let node = child.parentNode;
while (node != null) {
if (node == parent) {
return true;
}
node = node.parentNode;
}
return false;
};
export const getScrollDistance = () => {
return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
};
export const isAndroid = userAgent => {
const navigatorAgent = getNavigatorAgent(userAgent);
return /Android/i.test(navigatorAgent);
};
export const isIOS = userAgent => {
const navigatorAgent = getNavigatorAgent(userAgent);
return /iPhone|iPad|iPod/i.test(navigatorAgent);
};
export const isMobileBrowser = userAgent => {
const navigatorAgent = getNavigatorAgent(userAgent);
return (
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ipad|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
navigatorAgent
) ||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
navigatorAgent.substr(0, 4)
)
);
};

0
components/.gitkeep Normal file
View File

View File

@ -1,115 +0,0 @@
import * as React from 'react';
import * as Constants from '~/common/constants';
import { css } from 'react-emotion';
const STYLES_BUTTON = css`
font-weight: 700;
border: none;
margin: 0;
padding: 0 24px 0 24px;
height: 48px;
border-radius: 48px;
width: auto;
overflow: visible;
display: flex;
align-items: center;
justify-content: center;
background: ${Constants.theme.buttonBackground};
color: inherit;
font: inherit;
line-height: normal;
cursor: pointer;
white-space: nowrap;
position: relative;
text-decoration: none;
-webkit-font-smoothing: inherit;
-moz-osx-font-smoothing: inherit;
-webkit-appearance: none;
::-moz-focus-inner {
border: 0;
padding: 0;
}
min-width: 280px;
font-size: 18px;
`;
export const Button = ({ children, style, onClick, href }) => {
if (href) {
return (
<a className={STYLES_BUTTON} style={style} href={href}>
{children}
</a>
);
}
return (
<button className={STYLES_BUTTON} style={style} onClick={onClick}>
{children}
</button>
);
};
const STYLES_INPUT = css`
border: none;
outline: 0;
margin: 0;
padding: 0 24px 0 24px;
background: ${Constants.colors.gray3};
:focus {
border: 0;
outline: 0;
}
:active {
border: 0;
outline: 0;
}
-webkit-font-smoothing: inherit;
-moz-osx-font-smoothing: inherit;
-webkit-appearance: none;
::-moz-focus-inner {
border: 0;
padding: 0;
}
height: 48px;
width: 100%;
box-sizing: border-box;
font-weight: 400;
font-size: 18px;
`;
export const Input = ({
children,
style,
value,
name,
placeholder,
type = 'text',
autoComplete = 'input-autocomplete-off',
onBlur = e => {},
onFocus = e => {},
onChange = e => {},
}) => {
return (
<input
className={STYLES_INPUT}
style={style}
value={value}
onChange={onChange}
name={name}
onFocus={onFocus}
onBlur={onBlur}
type={type}
placeholder={placeholder}>
{children}
</input>
);
};

View File

@ -1,51 +0,0 @@
import * as React from 'react';
import { css } from 'react-emotion';
const STYLES_GOOGLE = css`
height: 48px;
padding: 0 24px 0 0;
border-radius: 32px;
background: #000;
color: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 600;
cursor: pointer;
text-decoration: none;
transition: 200ms ease all;
transition-property: color;
:visited {
color: #fff;
}
:hover {
color: #fff;
background: #222;
}
`;
const STYLES_LOGO = css`
height: 32px;
width: 32px;
border-radius: 32px;
display: inline-flex;
background-size: cover;
background-position: 50% 50%;
background-image: url('/static/logos/google.jpg');
margin-right: 16px;
margin-left: 8px;
`;
export default class GoogleButton extends React.Component {
render() {
return (
<a className={STYLES_GOOGLE} href={this.props.href}>
<span className={STYLES_LOGO} />
Sign in with Google
</a>
);
}
}

View File

@ -1,29 +0,0 @@
import * as React from 'react';
import * as Constants from '~/common/constants';
import { css } from 'react-emotion';
const STYLES_PAGE_STATE = css`
font-family: 'mono';
width: 100%;
background: ${Constants.colors.black};
color: ${Constants.colors.white};
font-size: 10px;
`;
const STYLES_SECTION = css`
width: 100%;
white-space: pre-wrap;
`;
const STYLES_TITLE_SECTION = css`
padding: 8px 24px 8px 24px;
`;
export default props => {
return (
<div className={STYLES_PAGE_STATE}>
<div className={STYLES_TITLE_SECTION}>{props.children}</div>
</div>
);
};

View File

@ -1,68 +0,0 @@
import * as React from 'react';
import * as Constants from '~/common/constants';
import { css } from 'react-emotion';
const MAX_WIDTH = 768;
const STYLES_HEADING = css`
overflow-wrap: break-word;
white-space: pre-wrap;
font-weight: 400;
font-size: 3.052rem;
position: relative;
max-width: ${MAX_WIDTH}px;
width: 100%;
padding: 0 24px 0 24px;
margin: 0 auto 0 auto;
`;
export const H1 = props => {
return <h1 {...props} className={STYLES_HEADING} />;
};
const STYLES_HEADING_TWO = css`
overflow-wrap: break-word;
white-space: pre-wrap;
font-weight: 400;
font-size: 1.728rem;
position: relative;
max-width: ${MAX_WIDTH}px;
width: 100%;
padding: 0 24px 0 24px;
margin: 0 auto 0 auto;
`;
export const H2 = props => {
return <h2 {...props} className={STYLES_HEADING_TWO} />;
};
const STYLES_PARAGRAPH = css`
overflow-wrap: break-word;
white-space: pre-wrap;
font-weight: 400;
font-size: 1.44rem;
line-height: 1.5;
position: relative;
max-width: ${MAX_WIDTH}px;
width: 100%;
padding: 0 24px 0 24px;
margin: 0 auto 0 auto;
`;
export const P = props => {
return <p {...props} className={STYLES_PARAGRAPH} />;
};
const STYLES_BODY_TEXT = css`
overflow-wrap: break-word;
white-space: pre-wrap;
font-weight: 400;
font-size: 1rem;
line-height: 1.5;
position: relative;
`;
export const BODY = props => {
return <div {...props} className={STYLES_BODY_TEXT} />;
};

View File

@ -0,0 +1,238 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as System from "~/components/system";
import * as SVG from "~/common/svg";
import { css } from "@emotion/react";
import Avatar from "~/components/core/Avatar";
import Pill from "~/components/core/Pill";
const STYLES_CIRCLE = css`
height: 32px;
width: 32px;
border-radius: 32px;
background-color: ${Constants.system.black};
color: ${Constants.system.white};
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
position: relative;
transition: 200ms ease all;
cursor: pointer;
user-select: none;
:hover {
color: ${Constants.system.white};
background-color: ${Constants.system.brand};
}
`;
const STYLES_ICON_ELEMENT = css`
height: 40px;
width: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
color: #565151;
user-select: none;
cursor: pointer;
:hover {
color: ${Constants.system.brand};
}
svg {
transform: rotate(0deg);
transition: 200ms ease transform;
}
`;
const STYLES_ICON_ELEMENT_CUSTOM = css`
height: 40px;
width: 40px;
border-radius: 40px;
background: ${Constants.system.brand};
display: inline-flex;
align-items: center;
justify-content: center;
color: ${Constants.system.white};
user-select: none;
cursor: pointer;
svg {
transform: rotate(0deg);
transition: 200ms ease transform;
}
`;
const STYLES_APPLICATION_HEADER = css`
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 100%;
background: ${Constants.system.foreground};
box-shadow: inset 0 -1px 0 0 ${Constants.system.border};
`;
const STYLES_LEFT = css`
flex-shrink: 0;
width: 288px;
display: flex;
align-items: center;
justify-content: flex-start;
`;
const STYLES_MIDDLE = css`
min-width: 10%;
width: 100%;
padding: 0 24px 0 48px;
`;
const STYLES_RIGHT = css`
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 16px;
`;
const STYLES_INPUT = css`
width: 100%;
max-width: 1024px;
font-size: 16px;
height: 40px;
padding: 0 16px 0 16px;
background-color: ${Constants.system.white};
border-radius: 4px;
box-shadow: inset 0 0 0 1px #e0e0e0, 0 1px 4px rgba(0, 0, 0, 0.04);
border: 0;
outline: 0;
box-sizing: border-box;
transition: 200ms ease all;
:focus {
box-shadow: 0 1px 4px rgba(0, 71, 255, 0.3),
inset 0 0 0 1px ${Constants.system.brand};
outline: 0;
}
`;
export default class ApplicationHeader extends React.Component {
render() {
const isBackDisabled =
this.props.currentIndex === 0 || this.props.history.length < 2;
const isForwardDisabled =
this.props.currentIndex === this.props.history.length - 1 ||
this.props.history.length < 2;
return (
<header css={STYLES_APPLICATION_HEADER}>
<div css={STYLES_LEFT}>
<span
css={STYLES_ICON_ELEMENT_CUSTOM}
style={{ marginRight: 16, marginLeft: 12 }}
>
<SVG.Logo height="32px" />
</span>
<span
css={STYLES_ICON_ELEMENT}
style={
isBackDisabled
? { cursor: "not-allowed", color: Constants.system.border }
: null
}
onClick={isBackDisabled ? () => {} : this.props.onBack}
>
<SVG.NavigationArrow
height="16px"
style={{ transform: `rotate(180deg)` }}
/>
</span>
<span
css={STYLES_ICON_ELEMENT}
style={
isForwardDisabled
? { cursor: "not-allowed", color: Constants.system.border }
: null
}
onClick={isForwardDisabled ? () => {} : this.props.onForward}
>
<SVG.NavigationArrow height="16px" />
</span>
</div>
<div css={STYLES_MIDDLE}>
<input
css={STYLES_INPUT}
placeholder={`Type to search ${this.props.pageTitle}`}
/>
</div>
<div css={STYLES_RIGHT}>
<System.StatUpload style={{ marginRight: 8 }}>
{Strings.bytesToSize(this.props.viewer.upload_bandwidth)}
</System.StatUpload>{" "}
<System.StatDownload style={{ marginRight: 8 }}>
{Strings.bytesToSize(this.props.viewer.download_bandwidth)}
</System.StatDownload>
<div
css={STYLES_CIRCLE}
onClick={() =>
this.props.onAction({
name: "Connection",
type: "ACTION",
value: "ACTION_TOGGLE_CONNECTION",
})
}
>
<SVG.PowerButton height="16px" />
</div>
<div
css={STYLES_CIRCLE}
style={{
marginLeft: 12,
}}
onClick={() =>
this.props.onAction({
name: "Notifications",
type: "SIDEBAR",
value: "SIDEBAR_NOTIFICATIONS",
})
}
>
<SVG.Bell height="16px" />
{this.props.viewer.notifications.length > 0 ? (
<Pill
style={{
left: 20,
top: `-4px`,
}}
>
{this.props.viewer.notifications.length}
</Pill>
) : null}
</div>
<Avatar
style={{ marginLeft: 12 }}
size={32}
url={this.props.viewer.photoURL}
popover={
<System.PopoverNavigation
style={{ right: 0, top: "48px" }}
onNavigateTo={this.props.onNavigateTo}
onAction={this.props.onAction}
navigation={[
{ text: "Edit account", value: 13 },
{ text: "Settings", value: 14 },
]}
/>
}
/>
</div>
</header>
);
}
}

View File

@ -0,0 +1,119 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as SVG from "~/components/system/svg";
import { css } from "@emotion/react";
const NAVIGATION_WIDTH = 288;
const HEADER_HEIGHT = 72;
const STYLES_BODY = css`
padding: 72px 0 0 ${Constants.sizes.navigation}px;
width: 100%;
display: flex;
align-items: flex-start;
justify-content: space-between;
`;
const STYLES_HEADER = css`
z-index: ${Constants.zindex.header};
height: ${Constants.sizes.header}px;
left: 0;
top: 0;
right: 0;
position: fixed;
`;
const STYLES_NAVIGATION = css`
z-index: ${Constants.zindex.navigation};
width: ${Constants.sizes.navigation}px;
top: ${Constants.sizes.header}px;
position: fixed;
left: 0;
bottom: 0;
`;
const STYLES_SIDEBAR = css`
width: ${Constants.sizes.sidebar}px;
padding: 0;
flex-shrink: 0;
`;
const STYLES_TRUE_SIDEBAR = css`
width: ${Constants.sizes.sidebar}px;
background: ${Constants.system.foreground};
z-index: ${Constants.zindex.sidebar};
border-left: 1px solid ${Constants.system.border};
padding: 0 0 128px 0;
position: fixed;
right: 0;
top: ${Constants.sizes.header}px;
bottom: 0;
flex-shrink: 0;
overflow-y: scroll;
scrollbar-width: none;
-ms-overflow-style: -ms-autohiding-scrollbar;
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: ${Constants.system.foreground};
}
::-webkit-scrollbar-thumb {
background: ${Constants.system.darkGray};
}
`;
const STYLES_SIDEBAR_HEADER = css`
display: flex;
justify-content: flex-end;
`;
const STYLES_BLOCK = css`
height: 56px;
width: 56px;
display: inline-flex;
align-items: center;
justify-content: center;
transition: 200ms ease all;
cursor: pointer;
:hover {
color: ${Constants.system.brand};
}
`;
const STYLES_SIDEBAR_CONTENT = css`
padding: 24px;
`;
export default class ApplicationLayout extends React.Component {
render() {
return (
<React.Fragment>
<div css={STYLES_NAVIGATION}>{this.props.navigation}</div>
<div css={STYLES_HEADER}>{this.props.header}</div>
<div css={STYLES_BODY}>
{this.props.children}
{this.props.sidebar ? (
<div css={STYLES_SIDEBAR}>
<div css={STYLES_TRUE_SIDEBAR}>
<div css={STYLES_SIDEBAR_HEADER}>
<div css={STYLES_BLOCK} onClick={this.props.onDismissSidebar}>
<SVG.Dismiss height="24px" />
</div>
</div>
<div css={STYLES_SIDEBAR_CONTENT}>{this.props.sidebar}</div>
</div>
</div>
) : null}
</div>
</React.Fragment>
);
}
}

View File

@ -0,0 +1,315 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as SVG from "~/common/svg";
import { css } from "@emotion/react";
import Pill from "~/components/core/Pill";
const IconMap = {
HOME: <SVG.Home height="20px" />,
FILE: <SVG.Image height="20px" />,
FOLDER: <SVG.Folder height="20px" />,
WALLET: <SVG.Wallet height="20px" />,
CHANNELS: <SVG.Channels height="20px" />,
DEALS: <SVG.Deals height="20px" />,
PEERS: <SVG.Peers height="20px" />,
DEALS: <SVG.Deals height="20px" />,
STATUS: <SVG.Status height="20px" />,
STATS: <SVG.Stats height="20px" />,
DATA_TRANSFER: <SVG.DataTransfer height="20px" />,
LOGS: <SVG.Logs height="20px" />,
MINERS: <SVG.Miners height="20px" />,
STORAGE_MARKET: <SVG.StorageMarket height="20px" />,
};
const STYLES_NAVIGATION = css`
width: 100%;
display; block;
padding: 24px 0 0 0;
font-size: 18px;
`;
const STYLES_NAVIGATION_ITEM = css`
display: flex;
align-items: flex-start;
justify-content: space-between;
cursor: pointer;
color: ${Constants.system.black};
:hover {
color: ${Constants.system.brand};
}
`;
const STYLES_EXPANDER = css`
flex-shrink: 0;
`;
const STYLES_ICON = css`
flex-shrink: 0;
position: relative;
`;
const STYLES_CHILDREN = css`
min-width: 10%;
width: 100%;
height: 100%;
overflow-wrap: break-word;
padding: 11px 0px 14px 8px;
font-family: "inter-semi-bold";
font-weight: 400;
font-size: 14px;
line-height: 1.225;
position: relative;
display: flex;
align-items: flex-start;
justify-content: flex-start;
`;
const STYLES_ICON_ELEMENT = css`
height: 40px;
width: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
user-select: none;
svg {
transform: rotate(0deg);
transition: 200ms ease transform;
}
`;
const Item = ({
data,
id,
activeId,
activeIds,
level,
children,
showActive,
treeChildren,
decorator,
onToggleShow,
onNavigateTo,
}) => {
return (
<span
css={STYLES_NAVIGATION_ITEM}
style={{ padding: `0 0 0 ${level * 16}px` }}
>
<span css={STYLES_EXPANDER} onClick={onToggleShow ? onToggleShow : null}>
<span
css={STYLES_ICON_ELEMENT}
style={{
color: activeIds[id] ? Constants.system.brand : null,
}}
>
{onToggleShow ? (
<SVG.ExpandArrow
height="8px"
style={showActive ? { transform: `rotate(90deg)` } : null}
/>
) : null}
</span>
</span>
<span css={STYLES_ICON} onClick={() => onNavigateTo({ id }, data)}>
<span
css={STYLES_ICON_ELEMENT}
style={{
color: activeIds[id] ? Constants.system.brand : null,
}}
>
{IconMap[decorator]}
{decorator === "LOGS" ? (
<Pill
style={{
left: 18,
top: `2px`,
background: Constants.system.black,
}}
>
56
</Pill>
) : null}
</span>
</span>
<span
css={STYLES_CHILDREN}
onClick={() => onNavigateTo({ id }, data)}
style={{
fontFamily: decorator === "FILE" ? "inter-regular" : null,
color: activeIds[id] ? Constants.system.brand : null,
}}
>
{children}
</span>
</span>
);
};
const STYLES_SMALL_LINK = css`
padding: 0 16px 0 16px;
font-size: 14px;
font-family: "inter-semi-bold";
margin-top: 11px;
color: #666;
transition: 200ms ease all;
cursor: pointer;
:hover {
color: ${Constants.system.brand};
}
`;
class NodeReference extends React.Component {
state = {
showTreeChildren: false,
};
_handleToggleShow = () => {
this.setState({ showTreeChildren: !this.state.showTreeChildren });
};
render() {
const {
id,
activeId,
activeIds,
level,
children,
treeChildren,
activePage,
decorator,
onNavigateTo,
data,
} = this.props;
const { showTreeChildren } = this.state;
let showActive = showTreeChildren || activeIds[id];
let showChildren = showActive && treeChildren && treeChildren.length;
return (
<React.Fragment>
<Item
id={id}
data={data}
activeId={activeId}
activeIds={activeIds}
level={level}
showActive={showActive}
treeChildren={treeChildren}
onNavigateTo={onNavigateTo}
decorator={decorator}
onToggleShow={
treeChildren && treeChildren.length ? this._handleToggleShow : null
}
>
{children}
</Item>
{showChildren
? treeChildren.map((child) => {
if (!child) {
return null;
}
if (child.ignore) {
return null;
}
return (
<NodeReference
data={child}
id={child.id}
activeId={activeId}
activeIds={activeIds}
key={child.id}
onNavigateTo={onNavigateTo}
level={level + 1}
treeChildren={child.children}
decorator={child.decorator}
>
{child.name}
</NodeReference>
);
})
: null}
</React.Fragment>
);
}
}
export default class ApplicationNavigation extends React.Component {
render() {
return (
<nav css={STYLES_NAVIGATION}>
{this.props.navigation.map((each) => {
if (!each) {
return null;
}
if (each.ignore) {
return null;
}
return (
<NodeReference
data={each}
id={each.id}
acitveId={this.props.activeId}
activeIds={this.props.activeIds}
key={each.id}
onNavigateTo={this.props.onNavigateTo}
level={0}
treeChildren={each.children}
decorator={each.decorator}
>
{each.name}
</NodeReference>
);
})}
<div
css={STYLES_SMALL_LINK}
onClick={() => {
window.open("https://filscan.io/");
}}
style={{ marginTop: 48 }}
>
<SVG.ExpandBox height="12px" style={{ marginRight: 14 }} />
Block Explorer
</div>
<div
css={STYLES_SMALL_LINK}
onClick={() => {
window.open("/system");
}}
>
<SVG.ExpandBox height="12px" style={{ marginRight: 14 }} />
Design System
</div>
<div
css={STYLES_SMALL_LINK}
onClick={() => {
window.open("https://docs.filecoin.io/");
}}
>
<SVG.ExpandBox height="12px" style={{ marginRight: 14 }} />
Documentation
</div>
<div
css={STYLES_SMALL_LINK}
onClick={() => {
window.open("https://filecoin.io/#community");
}}
>
<SVG.ExpandBox height="12px" style={{ marginRight: 14 }} />
Community
</div>
</nav>
);
}
}

67
components/core/Avatar.js Normal file
View File

@ -0,0 +1,67 @@
import * as React from "react";
import * as Constants from "~/common/constants";
import { css } from "@emotion/react";
import Dismissible from "~/components/core/Dismissible";
const STYLES_AVATAR = css`
display: inline-flex;
background-size: cover;
background-position: 50% 50%;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
position: relative;
`;
const STYLES_AVATAR_ONLINE = css`
height: 16px;
width: 16px;
background-color: ${Constants.system.green};
border: 2px solid ${Constants.system.white};
position: absolute;
bottom: -4px;
right: -4px;
border-radius: 16px;
`;
export default class AvatarEntity extends React.Component {
state = {};
_handleClick = (e) => {
if (this.props.popover) {
this.setState({ visible: !this.state.visible });
}
if (this.props.onClick) {
this.props.onClick(e);
}
};
_handleHide = () => {
this.setState({ visible: false });
};
render() {
return (
<Dismissible
css={STYLES_AVATAR}
captureResize={false}
captureScroll={true}
enabled={this.state.visible}
onOutsideRectEvent={this._handleHide}
onClick={this._handleClick}
style={{
...this.props.style,
width: `${this.props.size}px`,
height: `${this.props.size}px`,
borderRadius: `${this.props.size}px`,
backgroundImage: `url('${this.props.url}')`,
cursor: this.props.onClick ? "pointer" : null,
}}
>
{this.state.visible ? this.props.popover : null}
{this.props.online ? <span css={STYLES_AVATAR_ONLINE} /> : null}
</Dismissible>
);
}
}

View File

@ -0,0 +1,114 @@
import * as React from "react";
import { isMobileBrowser } from "~/common/window";
export default class WebRectBoundary extends React.PureComponent {
static defaultProps = {
className: undefined,
captureResize: true,
captureScroll: true,
children: null,
enabled: false,
isDataMenuCaptured: false,
onOutsideRectEvent: () => {},
};
_root = undefined;
componentDidMount() {
if (!this.props.enabled) {
return;
}
this._addListeners();
}
componentWillUnmount() {
this._removeListeners();
}
UNSAFE_componentWillReceiveProps(props) {
if (props.enabled) {
this._addListeners();
} else {
this._removeListeners();
}
}
_addListeners = () => {
this._removeListeners();
window.setTimeout(() => {
if (this.props.onOutsideRectEvent) {
if (isMobileBrowser()) {
window.addEventListener("touchstart", this._handleOutsideClick);
} else {
window.addEventListener("click", this._handleOutsideClick);
}
}
if (this.props.captureResize) {
window.addEventListener("resize", this._handleWindowResize);
}
if (this.props.captureScroll) {
window.addEventListener("scroll", this._handleWindowScroll);
}
});
};
_handleOutsideClick = (e) => {
// NOTE(jim): anything with `data-menu` is also ignored...
if (!e.target) {
return;
}
if (
this.props.isDataMenuCaptured &&
typeof e.target.hasAttribute === "function" &&
e.target.hasAttribute("data-menu")
) {
return;
}
if (
this.props.isDataMenuCaptured &&
e.target.parentNode &&
typeof e.target.parentNode.hasAttribute === "function" &&
e.target.parentNode.hasAttribute("data-menu")
) {
return;
}
if (this._root && !this._root.contains(e.target)) {
this._handleOutsideRectEvent(e);
}
};
_handleWindowResize = (e) => this._handleOutsideRectEvent(e);
_handleWindowScroll = (e) => this._handleOutsideRectEvent(e);
_removeListeners = () => {
window.removeEventListener("touchstart", this._handleOutsideClick);
window.removeEventListener("click", this._handleOutsideClick);
window.removeEventListener("resize", this._handleWindowResize);
window.removeEventListener("scroll", this._handleWindowScroll);
};
_handleOutsideRectEvent = (e) => {
this.props.onOutsideRectEvent(e);
};
render() {
return (
<div
className={this.props.className}
ref={(c) => {
this._root = c;
}}
style={this.props.style}
onClick={this.props.onClick}
>
{this.props.children}
</div>
);
}
}

37
components/core/Pill.js Normal file
View File

@ -0,0 +1,37 @@
import { css } from "@emotion/react";
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
const STYLES_PILL = css`
position: absolute;
padding: 0 8px 0px 8px;
height: 16px;
background: #ff0000;
font-family: "mono";
text-transform: uppercase;
font-size: 10px;
letter-spacing: 0.1px;
color: ${Constants.system.white};
border-radius: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 10px;
z-index: 3;
`;
export default class Pill extends React.Component {
render() {
return (
<figure
css={STYLES_PILL}
onClick={this.props.onClick}
style={this.props.style}
>
{this.props.children}
</figure>
);
}
}

View File

@ -0,0 +1,26 @@
import * as React from "react";
import * as Constants from "~/common/constants";
import { css } from "@emotion/react";
const STYLES_SCENE_BACKDROP = css`
background: ${Constants.system.white};
box-shadow: inset 1px 0 0 ${Constants.system.border};
width: 100%;
`;
const STYLES_SCENE = css`
max-width: 1296px;
width: 100%;
min-width: 10%;
min-height: 100vh;
padding: 48px 48px 128px 48px;
`;
export default (props) => {
return (
<div css={STYLES_SCENE_BACKDROP}>
<div css={STYLES_SCENE} {...props} />
</div>
);
};

101
components/core/Section.js Normal file
View File

@ -0,0 +1,101 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import { css } from "@emotion/react";
const STYLES_SECTION = css`
width: 100%;
box-shadow: 0 0 0 1px ${Constants.system.gray}, 0 1px 4px rgba(0, 0, 0, 0.04);
border-radius: 4px;
font-weight: 400;
margin-top: 24px;
:first-child {
margin-top: 0px;
}
`;
const STYLES_HEADER = css`
background: ${Constants.system.foreground};
border-bottom: 1px solid ${Constants.system.border};
font-family: "inter-medium";
font-size: ${Constants.typescale.lvl1};
border-radius: 4px 4px 0 0;
padding: 24px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: space-between;
`;
const STYLES_LEFT = css`
min-width: 5%;
width: 100%;
`;
const STYLES_RIGHT = css`
flex-shrink: 0;
`;
const STYLES_CHILDREN = css`
background: ${Constants.system.white};
border-radius: 0 0 4px 4px;
`;
const STYLES_BUTTON = css`
border-radius: 4px;
outline: 0;
border: 0;
min-height: 32px;
padding: 6px 16px 6px 16px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
letter-spacing: 0.2px;
font-family: "inter-semi-bold";
transition: 200ms ease all;
box-shadow: 0 0 0 1px ${Constants.system.border},
0 1px 4px rgba(0, 0, 0, 0.07);
cursor: pointer;
background-color: ${Constants.system.white};
color: ${Constants.system.black};
margin-left: 16px;
:hover {
background-color: #f2f4f8;
}
:focus {
box-shadow: inset 0 0 5px 2px rgba(0, 0, 0, 0.3);
outline: 0;
border: 0;
}
`;
export default (props) => {
return (
<div css={STYLES_SECTION} style={props.style}>
<header css={STYLES_HEADER}>
<div css={STYLES_LEFT}>{props.title}</div>
{props.buttons ? (
<div css={STYLES_RIGHT}>
{props.buttons.map((b) => {
return (
<span
key={b.name}
css={STYLES_BUTTON}
onClick={() => props.onAction(b)}
>
{b.name}
</span>
);
})}
</div>
) : null}
</header>
<div css={STYLES_CHILDREN}>{props.children}</div>
</div>
);
};

View File

@ -0,0 +1,54 @@
import Head from "next/head";
import * as React from "react";
export default class WebsitePrototypeWrapper extends React.Component {
render() {
return (
<React.Fragment>
<Head>
<title>{this.props.title}</title>
<meta name="title" content={this.props.title} />
<meta name="description" content={this.props.description} />
<meta property="og:type" content="website" />
<meta property="og:url" content={this.props.url} />
<meta property="og:title" content={this.props.title} />
<meta property="og:description" content={this.props.description} />
<meta property="og:image" content="/static/social.png" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={this.props.url} />
<meta property="twitter:title" content={this.props.title} />
<meta
property="twitter:description"
content={this.props.description}
/>
<meta property="twitter:image" content="/static/social.png" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/static/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="96x96"
href="/static/favicon-96x96.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/static/favicon-16x16.png"
/>
<link rel="shortcut icon" href="/static/favicon.ico" />
</Head>
{this.props.children}
</React.Fragment>
);
}
}

View File

@ -0,0 +1,33 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as SVG from "~/components/system/svg";
import * as System from "~/components/system";
import { css } from "@emotion/react";
export default class SidebarAddMiner extends React.Component {
state = {};
_handleSubmit = () => {
this.props.onSubmit({});
};
_handleCancel = () => {
this.props.onCancel();
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<React.Fragment>
<System.P style={{ fontFamily: "inter-semi-bold" }}>
Add a miner
</System.P>
</React.Fragment>
);
}
}

View File

@ -0,0 +1,33 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as SVG from "~/components/system/svg";
import * as System from "~/components/system";
import { css } from "@emotion/react";
export default class SidebarAddPeer extends React.Component {
state = {};
_handleSubmit = () => {
this.props.onSubmit({});
};
_handleCancel = () => {
this.props.onCancel();
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<React.Fragment>
<System.P style={{ fontFamily: "inter-semi-bold" }}>
Add a peer
</System.P>
</React.Fragment>
);
}
}

View File

@ -0,0 +1,108 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as SVG from "~/components/system/svg";
import * as System from "~/components/system";
import { css } from "@emotion/react";
const STYLES_FOCUS = css`
font-size: ${Constants.typescale.lvl1};
font-family: "inter-medium";
overflow-wrap: break-word;
width: 100%;
strong {
font-family: "inter-semi-bold";
font-weight: 400;
}
`;
const STYLES_SUBTEXT = css`
margin-top: 8px;
font-size: 12px;
`;
const STYLES_ITEM = css`
margin-top: 16px;
`;
export default class SidebarCreatePaymentChannel extends React.Component {
state = { address: "", amount: "" };
_handleSubmit = () => {
alert("TODO: Create a new payment channel");
this.props.onSubmit({});
};
_handleCancel = () => {
this.props.onCancel();
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
let addresses = {};
this.props.viewer.addresses.forEach((a) => {
addresses[a.value] = a;
});
const currentAddress = addresses[this.props.selected.address];
return (
<React.Fragment>
<System.P style={{ fontFamily: "inter-semi-bold" }}>
Create a payment channel
</System.P>
<System.SelectMenuFull
containerStyle={{ marginTop: 24 }}
name="address"
label="From"
value={this.props.selected.address}
category="address"
onChange={this.props.onSelectedChange}
options={this.props.viewer.addresses}
>
{currentAddress.name}
</System.SelectMenuFull>
<System.Input
containerStyle={{ marginTop: 24 }}
label="To"
name="address"
value={this.state.address}
onChange={this._handleChange}
/>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Amount (Filecoin)"
name="amount"
value={this.state.amount}
onChange={this._handleChange}
/>
<div css={STYLES_ITEM}>
<div css={STYLES_FOCUS}>2 FIL</div>
<div css={STYLES_SUBTEXT}>Transaction Fee</div>
</div>
<div css={STYLES_ITEM}>
<div css={STYLES_FOCUS}>2</div>
<div css={STYLES_SUBTEXT}>Total Filecoin</div>
</div>
<System.ButtonPrimaryFull
style={{ marginTop: 48 }}
onClick={this._handleSubmit}
>
Send
</System.ButtonPrimaryFull>
</React.Fragment>
);
}
}

View File

@ -0,0 +1,76 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as SVG from "~/components/system/svg";
import * as System from "~/components/system";
import { css } from "@emotion/react";
const SELECT_MENU_OPTIONS = [
{ value: "1", name: "SECP256K1" },
{ value: "2", name: "BLS" },
{ value: "3", name: "MULTISIG" },
];
const SELECT_MENU_MAP = {
"1": "SECP256K1",
"2": "BLS",
"3": "MULTISIG",
};
export default class SidebarCreateWalletAddress extends React.Component {
state = {
name: "",
type: "1",
};
_handleSubmit = () => {
alert("TODO: Create a new wallet address");
this.props.onSubmit({});
};
_handleCancel = () => {
this.props.onCancel();
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<div>
<System.P style={{ fontFamily: "inter-semi-bold" }}>
Create a new address
</System.P>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Address name"
name="name"
value={this.state.name}
onChange={this._handleChange}
/>
<System.SelectMenuFull
containerStyle={{ marginTop: 24 }}
name="type"
label="Address type"
value={this.state.type}
category="type"
onChange={this._handleChange}
options={SELECT_MENU_OPTIONS}
>
{SELECT_MENU_MAP[this.state.type]}
</System.SelectMenuFull>
<System.ButtonPrimaryFull
style={{ marginTop: 48 }}
onClick={this._handleSubmit}
>
Create {this.state.name}
</System.ButtonPrimaryFull>
</div>
);
}
}

View File

@ -0,0 +1,46 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as SVG from "~/components/system/svg";
import * as System from "~/components/system";
import { css } from "@emotion/react";
export default class SidebarDeleteWalletAddress extends React.Component {
_handleSubmit = () => {
alert("TODO: Delete wallet address");
this.props.onSubmit({});
};
_handleCancel = () => {
this.props.onCancel();
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<div>
<System.P style={{ fontFamily: "inter-semi-bold" }}>
Are you sure you want to delete the selected wallet?
</System.P>
<System.ButtonPrimaryFull
style={{ marginTop: 48 }}
onClick={this._handleSubmit}
>
Delete
</System.ButtonPrimaryFull>
<System.ButtonSecondaryFull
style={{ marginTop: 16 }}
onClick={this._handleCancel}
>
Cancel
</System.ButtonSecondaryFull>
</div>
);
}
}

View File

@ -0,0 +1,33 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as SVG from "~/components/system/svg";
import * as System from "~/components/system";
import { css } from "@emotion/react";
export default class SidebarFileRetrievalDeal extends React.Component {
state = {};
_handleSubmit = () => {
this.props.onSubmit({});
};
_handleCancel = () => {
this.props.onCancel();
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<React.Fragment>
<System.P style={{ fontFamily: "inter-semi-bold" }}>
Retrieve file
</System.P>
</React.Fragment>
);
}
}

View File

@ -0,0 +1,174 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as SVG from "~/components/system/svg";
import * as System from "~/components/system";
import { css } from "@emotion/react";
const STYLES_FOCUS = css`
font-size: ${Constants.typescale.lvl1};
font-family: "inter-medium";
overflow-wrap: break-word;
width: 100%;
strong {
font-family: "inter-semi-bold";
font-weight: 400;
}
`;
const STYLES_SUBTEXT = css`
margin-top: 8px;
font-size: 12px;
`;
const STYLES_ITEM = css`
margin-top: 16px;
`;
const STYLES_IMAGE_PREVIEW = css`
display: block;
width: 100%;
margin-top: 48px;
`;
const SELECT_MENU_OPTIONS = [
{ value: "1", name: "Anywhere" },
{ value: "2", name: "China" },
{ value: "3", name: "Russia" },
{ value: "4", name: "USA" },
];
const SELECT_MENU_MAP = {
"1": "Anywhere",
"2": "China",
"3": "Russia",
"4": "USA",
};
export default class SidebarFileStorageDeal extends React.Component {
state = {
settings_deal_duration: 1,
settings_replication_factor: 1,
settings_country: "1",
settings_miners: "t111, t112, t113",
settings_confirmation: false,
};
_handleSubmit = () => {
alert("TODO: Make a storage deal");
this.props.onSubmit({});
};
_handleCancel = () => {
this.props.onCancel();
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
let addresses = {};
this.props.viewer.addresses.forEach((a) => {
addresses[a.value] = a;
});
const currentAddress = addresses[this.props.selected.address];
return (
<React.Fragment>
<System.P style={{ fontFamily: "inter-semi-bold" }}>
Upload a file to the network
</System.P>
<img src="/static/test-image-upload.jpg" 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}>42 MB</div>
<div css={STYLES_SUBTEXT}>File size</div>
</div>
<System.ButtonSecondaryFull style={{ marginTop: 24 }}>
Change file
</System.ButtonSecondaryFull>
<System.Input
containerStyle={{ marginTop: 48 }}
label="Deal duration"
name="settings_deal_duration"
value={this.state.settings_deal_duration}
onChange={this._handleChange}
/>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Replication factor"
name="settings_replication_factor"
value={this.state.settings_replication_factor}
onChange={this._handleChange}
/>
<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>
<System.SelectMenuFull
containerStyle={{ marginTop: 24 }}
name="settings_country"
label="Country"
value={this.props.settings_country}
category="miner location"
onChange={this._handleChange}
options={SELECT_MENU_OPTIONS}
>
{SELECT_MENU_MAP[this.state.settings_country]}
</System.SelectMenuFull>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Trusted miners"
name="settings_miners"
value={this.state.settings_miners}
onChange={this._handleChange}
/>
<System.CheckBox
style={{ marginTop: 24 }}
name="settings_confirmation"
value={this.state.settings_confirmation}
onChange={this._handleChange}
>
Please do not show this confirmation again.
</System.CheckBox>
<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>
</React.Fragment>
);
}
}

View File

@ -0,0 +1,56 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as SVG from "~/components/system/svg";
import * as System from "~/components/system";
import { css } from "@emotion/react";
const STYLES_NOTIFICATION = css`
margin-top: 24px;
font-size: 14px;
`;
const STYLES_NOTIFICATION_DATE = css`
color: ${Constants.system.darkGray};
`;
const STYLES_NOTIFICATION_BODY = css`
margin-top: 4px;
`;
export default class SidebarNotifications extends React.Component {
state = {};
_handleSubmit = () => {
this.props.onSubmit({});
};
_handleCancel = () => {
this.props.onCancel();
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<React.Fragment>
<System.P style={{ fontFamily: "inter-semi-bold" }}>
Notifications
</System.P>
{this.props.viewer.notifications.map((n) => {
return (
<div css={STYLES_NOTIFICATION} key={n.id}>
<div css={STYLES_NOTIFICATION_DATE}>
{Strings.toDate(n.createdAt)}
</div>
<div css={STYLES_NOTIFICATION_BODY}>{n.text}</div>
</div>
);
})}
</React.Fragment>
);
}
}

View File

@ -0,0 +1,33 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as SVG from "~/components/system/svg";
import * as System from "~/components/system";
import { css } from "@emotion/react";
export default class SidebarRedeemPaymentChannel extends React.Component {
state = {};
_handleSubmit = () => {
this.props.onSubmit({});
};
_handleCancel = () => {
this.props.onCancel();
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<React.Fragment>
<System.P style={{ fontFamily: "inter-semi-bold" }}>
Redeem Payment Channel
</System.P>
</React.Fragment>
);
}
}

View File

@ -0,0 +1,111 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as SVG from "~/components/system/svg";
import * as System from "~/components/system";
import { css } from "@emotion/react";
const STYLES_FOCUS = css`
font-size: ${Constants.typescale.lvl1};
font-family: "inter-medium";
overflow-wrap: break-word;
width: 100%;
strong {
font-family: "inter-semi-bold";
font-weight: 400;
}
`;
const STYLES_SUBTEXT = css`
margin-top: 8px;
font-size: 12px;
`;
const STYLES_ITEM = css`
margin-top: 16px;
`;
export default class SidebarWalletSendFunds extends React.Component {
state = {
address: "",
amount: "",
};
_handleSubmit = () => {
alert("TODO: Send Filecoin");
this.props.onSubmit({});
};
_handleCancel = () => {
this.props.onCancel();
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
let addresses = {};
this.props.viewer.addresses.forEach((a) => {
addresses[a.value] = a;
});
const currentAddress = addresses[this.props.selected.address];
return (
<React.Fragment>
<System.P style={{ fontFamily: "inter-semi-bold" }}>
Send Filecoin
</System.P>
<System.SelectMenuFull
containerStyle={{ marginTop: 24 }}
name="address"
label="From"
value={this.props.selected.address}
category="address"
onChange={this.props.onSelectedChange}
options={this.props.viewer.addresses}
>
{currentAddress.name}
</System.SelectMenuFull>
<System.Input
containerStyle={{ marginTop: 24 }}
label="To"
name="address"
value={this.state.address}
onChange={this._handleChange}
/>
<System.Input
containerStyle={{ marginTop: 24 }}
label="Amount (Filecoin)"
name="amount"
value={this.state.amount}
onChange={this._handleChange}
/>
<div css={STYLES_ITEM}>
<div css={STYLES_FOCUS}>2 FIL</div>
<div css={STYLES_SUBTEXT}>Transaction Fee</div>
</div>
<div css={STYLES_ITEM}>
<div css={STYLES_FOCUS}>2</div>
<div css={STYLES_SUBTEXT}>Total Filecoin</div>
</div>
<System.ButtonPrimaryFull
style={{ marginTop: 48 }}
onClick={this._handleSubmit}
>
Send
</System.ButtonPrimaryFull>
</React.Fragment>
);
}
}

View File

@ -0,0 +1,43 @@
import * as React from "react";
import * as Constants from "~/common/constants";
import { css } from "@emotion/react";
const STYLES_GROUP_CONTAINER = css`
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
border: 1px solid ${Constants.system.border};
border-radius: 4px;
`;
const STYLES_TITLE = css`
font-size: ${Constants.typescale.lvl2};
width: 100%;
margin-top: 8px;
font-family: "inter-semi-bold";
`;
const STYLES_HEADER = css`
background: ${Constants.system.gray};
padding: 24px;
border-radius: 4px 4px 0 0;
`;
const STYLES_GROUP = css`
background: ${Constants.system.white};
width: 100%;
padding: 24px 24px 72px 24px;
border-radius: 0 0 4px 4px;
`;
export default (props) => {
return (
<div css={STYLES_GROUP_CONTAINER} style={props.style} id={props.id}>
<header css={STYLES_HEADER}>
<div css={STYLES_TITLE}>{props.title}</div>
</header>
<div css={STYLES_GROUP} style={props.groupStyle}>
{props.children}
</div>
</div>
);
};

View File

@ -0,0 +1,68 @@
import Head from "next/head";
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import { css } from "@emotion/react";
const STYLES_PAGE = css`
background: #fcfcfc;
`;
const STYLES_BODY = css`
max-width: 960px;
width: 100%;
margin: 0 auto 0 auto;
padding: 88px 24px 128px 24px;
`;
export default class SystemPage extends React.Component {
render() {
const { title, description, url, children } = this.props;
return (
<div css={STYLES_PAGE}>
<Head>
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:url" content={url} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content="/static/social.png" />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={url} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content="/static/social.png" />
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/static/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="96x96"
href="/static/favicon-96x96.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/static/favicon-16x16.png"
/>
<link rel="shortcut icon" href="/static/favicon.ico" />
</Head>
<div css={STYLES_BODY}>{children}</div>
</div>
);
}
}

1742
components/system/index.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,307 @@
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 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",
};
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",
};
const COMPONENTS_ICON = {
PNG: <SVG.FileImage height="24px" />,
FOLDER: <OldSVG.Folder height="24px" />,
};
const STYLES_TABLE_TAG = css`
font-weight: 400;
font-family: "inter-semi-bold";
letter-spacing: 0.2px;
padding: 4px 6px 4px 6px;
font-size: 10px;
text-transform: uppercase;
background: ${Constants.system.black};
color: ${Constants.system.white};
border-radius: 4px;
white-space: nowrap;
`;
const COMPONENTS_TRANSACTION_DIRECTION = {
"1": (
<span css={STYLES_TABLE_TAG} style={{ background: Constants.system.green }}>
+ incoming
</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 }}
>
pending
</span>
),
};
const STYLES_COLUMN = css`
display: inline-flex;
align-items: flex-start;
justify-content: space-between;
align-self: stretch;
min-width: 10%;
`;
const STYLES_TOP_COLUMN = css`
display: inline-flex;
align-items: flex-start;
justify-content: space-between;
align-self: stretch;
min-width: 10%;
cursor: pointer;
transition: 200ms ease all;
:hover {
color: ${Constants.system.brand};
}
`;
const STYLES_CONTENT = css`
padding: 12px 12px 16px 12px;
min-width: 10%;
width: 100%;
align-self: stretch;
flex-direction: column;
word-break: break-word;
overflow-wrap: anywhere;
font-size: 12px;
`;
const STYLES_CONTENT_BUTTON = css`
flex-shrink: 0;
height: 40px;
width: 30px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: 200ms ease all;
:hover {
color: ${Constants.system.green};
}
`;
const STYLES_TABLE_CONTENT_LINK = css`
font-family: "inter-medium";
text-decoration: underline;
cursor: pointer;
:hover {
color: ${Constants.system.green};
}
`;
export const TableColumn = (props) => {
const tooltipElement = props.tooltip ? (
<Tooltip animation="fade" animateFill={false} title={props.tooltip}>
<span css={STYLES_CONTENT_BUTTON}>
<SVG.Information height="14px" />
</span>
</Tooltip>
) : null;
const copyableElement = props.copyable ? (
<span css={STYLES_CONTENT_BUTTON}>
<SVG.CopyAndPaste height="16px" />
</span>
) : null;
return (
<span
css={props.top ? STYLES_TOP_COLUMN : STYLES_COLUMN}
style={props.style}
>
<span css={STYLES_CONTENT}>{props.children}</span>
{tooltipElement}
{copyableElement}
</span>
);
};
export const TableContent = ({
type,
text,
action,
data = {},
onNavigateTo,
onAction,
}) => {
const { status, online } = data;
if (Strings.isEmpty(text)) {
return null;
}
switch (type) {
case "DEAL_CATEGORY":
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 })}
>
{text}
</span>
);
case "TRANSACTION_DIRECTION":
return COMPONENTS_TRANSACTION_DIRECTION[text];
case "TRANSACTION_STATUS":
return COMPONENTS_TRANSACTION_STATUS[text];
case "ICON":
return COMPONENTS_ICON[text];
case "AVATAR":
return <Avatar url={text} size={40} online={online} />;
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":
return (
<React.Fragment>
<SVG.BandwidthUp height="16px" style={{ marginRight: 8 }} />
{Strings.bytesToSize(text)}
</React.Fragment>
);
case "BANDWIDTH_DOWNLOAD":
return (
<React.Fragment>
<SVG.BandwidthDown height="16px" style={{ marginRight: 8 }} />
{Strings.bytesToSize(text)}
</React.Fragment>
);
case "MINER_AVAILABILITY":
return text == 1 ? (
<span
css={STYLES_TABLE_TAG}
style={{ background: Constants.system.green }}
>
Online
</span>
) : null;
case "DEAL_AUTO_RENEW":
return text == 1 ? (
<span
css={STYLES_TABLE_TAG}
style={{ background: Constants.system.brand }}
>
true
</span>
) : (
<span css={STYLES_TABLE_TAG}>false</span>
);
case "NOTIFICATION_ERROR":
return (
<span
css={STYLES_TABLE_TAG}
style={{ background: Constants.system.red }}
>
{text} {Strings.pluralize("error", text)}
</span>
);
case "FILE_LINK":
// NOTE(jim): Special case to prevent navigation.
if (!data) {
return text;
}
// NOTE(jim): Navigate to folers.
if (data && data.folderId) {
return (
<span
css={STYLES_TABLE_CONTENT_LINK}
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) {
return (
<span
css={STYLES_TABLE_CONTENT_LINK}
onClick={() =>
onAction({
type: "SIDEBAR",
value: "SIDEBAR_FILE_STORAGE_DEAL",
})
}
>
{text}
</span>
);
}
// NOTE(jim): Special case to prevent navigation.
if (data && data["retrieval-status"] !== 6) {
return (
<span
onClick={() =>
onAction({
name: "File does not exist",
type: "ACTION",
value: "ACTION_FILE_MISSING",
})
}
>
{text}
</span>
);
}
// NOTE(jim): Navigates to file.
return (
<span
css={STYLES_TABLE_CONTENT_LINK}
onClick={() => onNavigateTo({ id: 15 }, data)}
>
{text}
</span>
);
default:
return text;
}
};

197
components/system/svg.js Normal file
View File

@ -0,0 +1,197 @@
export const Dismiss = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
height={props.height}
style={props.style}
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
);
export const Privacy = (props) => (
<svg
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
height={props.height}
style={props.style}
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
>
<path d="m22.823 8.611c.570278-.632425.570278-1.59357 0-2.226-2.623-2.885-6.792-5.701-10.823-5.634-4.031-.067-8.2 2.749-10.821 5.634l-.00000003.00000004c-.569817.632603-.569817 1.5934.00000007 2.226 2.563 2.824 6.721 5.706 10.821 5.638" />
<path d="m15.75 7.5c0 2.07107-1.67893 3.75-3.75 3.75s-3.75-1.67893-3.75-3.75 1.67893-3.75 3.75-3.75h-.00000016c2.07107-.00000009 3.75 1.67893 3.75 3.75z" />
<path d="m15.75 23.25c-.82842 0-1.5-.67158-1.5-1.5v-4.5c0-.82842.67158-1.5 1.5-1.5h6c.82842 0 1.5.67158 1.5 1.5v4.5c0 .82842-.67158 1.5-1.5 1.5z" />
<path d="m18.75 11.25h-.00000013c-1.65685.00000007-3 1.34315-3 3v1.5h6v-1.5-.00000013c0-1.65685-1.34315-3-3-3-.00000004 0-.00000009 0-.00000013 0z" />
<path d="m18.75 19.154h-.00000002c.207107-.00000001.375.167893.375.375.00000001.207107-.167893.375-.375.375-.207107.00000001-.375-.167893-.375-.375v.00000006c-.00000003-.207107.167893-.375.375-.375" />
</g>
</svg>
);
export const FileImage = (props) => (
<svg
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
height={props.height}
style={props.style}
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m21.207 4.5-.00000002-.00000002c.187549.187493.292943.441805.293.707v17.293c0 .552285-.447715 1-1 1h-17-.00000004c-.552285-.00000002-1-.447715-1-1v-21 .00000015c-.00000008-.552285.447715-1 1-1h13.293.00000001c.265195.00005664.519507.105451.707.293z" />
<path d="m12.826 12.366-2.8-3.74.00000001.00000002c-.165798-.22083-.479221-.265442-.700051-.0996437-.0578698.0434484-.105619.0989405-.139949.162644l-3.276 6.074.00000001-.00000002c-.130892.24315-.0398879.546371.203262.677262.0727636.0391698.154101.0596942.236738.0597376h4.181" />
<path d="m17.3284 13.1716c1.5621 1.5621 1.5621 4.09476 0 5.65685-1.5621 1.5621-4.09476 1.5621-5.65685 0-1.5621-1.5621-1.5621-4.09476 0-5.65685 1.5621-1.5621 4.09476-1.5621 5.65685 0" />
</g>
</svg>
);
export const Information = (props) => (
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
<path
fill="currentColor"
d="m12 0h-.00000052c-6.62742.00000029-12 5.37258-12 12 .00000029 6.62742 5.37258 12 12 12 6.62742-.00000029 12-5.37258 12-12l-.00000013-.00012266c-.00723277-6.62445-5.37568-11.9928-12.0001-11.9999zm0 19h-.00000007c-.828427-.00000004-1.5-.671573-1.5-1.5.00000004-.828427.671573-1.5 1.5-1.5.828427.00000004 1.5.671573 1.5 1.5v-.00000007c0 .828427-.671573 1.5-1.5 1.5zm1.6-6.08h.00000001c-.364588.159119-.600193.519202-.6.917 0 .552285-.447715 1-1 1s-1-.447715-1-1l-.00000003-.00045412c-.00000018-1.19303.706913-2.27268 1.80042-2.74973l.00000001-.00000001c1.01225-.442058 1.47449-1.62101 1.03243-2.63326-.442058-1.01225-1.62101-1.47449-2.63326-1.03243-.728973.318347-1.19999 1.03843-1.19958 1.83388 0 .552285-.447715 1-1 1s-1-.447715-1-1v-.00005995c-.00000033-2.20914 1.79086-4 4-4 2.20914-.00000033 4 1.79086 4 4 .00000024 1.59051-.942318 3.0299-2.40005 3.66609z"
/>
</svg>
);
export const Plus = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
);
export const Logo = (props) => (
<svg
viewBox="0 0 127 127"
height={props.height}
style={props.style}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m71.4 57.6c-.7 3.3-1.3 6.4-2 9.8 5.9.9 11.7 1.7 17.7 2.6-.5 1.6-.9 3.1-1.4 4.8-5.8-.8-11.5-1.7-17.3-2.5-1.1 4.2-2 8.3-3.3 12.2-1.4 4.4-3 8.7-5 12.9-2.6 5.2-6.8 8.9-12.7 10.1-3.5.7-7 .4-10-1.9-.9-.7-1.8-1.8-1.9-2.8-.1-1.1.5-2.7 1.4-3.3.6-.5 2.3-.1 3.1.5 1.1.8 1.8 2.1 2.6 3.3 1.8 2.5 4.4 2.9 6.8.8 2.9-2.5 4.4-5.8 5.3-9.3 1.9-7.3 3.6-14.8 5.3-22.2.1-.3 0-.7 0-1.3-5.4-.8-10.8-1.7-16.5-2.5.2-1.6.4-3 .6-4.8 5.6.8 11.1 1.6 17 2.4.8-3.2 1.5-6.4 2.3-9.7-6-.9-11.7-1.8-17.8-2.7.2-1.6.4-3.2.6-4.8 6.1.9 12 1.7 18.2 2.6.5-1.8.9-3.5 1.4-5.1 1.7-5.6 3.2-11.3 6.8-16.2s8.1-8.1 14.5-7.8c2.8.1 5.5.9 7.5 3.2.4.5 1 1.1 1 1.6-.1 1.2 0 2.9-.8 3.6-1.1 1.1-2.8.5-4-.6-.9-.9-1.6-1.9-2.3-2.9-1.8-2.4-4.7-2.9-6.8-.7-1.6 1.7-3.2 3.7-3.9 5.9-2.1 6.6-3.8 13.2-5.8 20.2 5.9.9 11.4 1.7 17.1 2.5-.5 1.6-.9 3.1-1.3 4.7-5.5-1.1-10.9-1.9-16.4-2.6z"
fill="currentColor"
/>
</svg>
);
export const ChevronDown = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<polyline points="6 9 12 15 18 9" />
</svg>
);
};
export const CheckBox = (props) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<polyline points="20 6 9 17 4 12" />
</svg>
);
};
export const CopyAndPaste = (props) => {
return (
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m9.5 21.5h-8-.00000004c-.552285-.00000002-1-.447715-1-1v-16 .00000015c-.00000008-.552285.447715-1 1-1h2" />
<path d="m13.5 3.5h2-.00000004c.552285-.00000002 1 .447715 1 1v3.5" />
<path d="m9.56066.93834c.585786.585786.585786 1.53553 0 2.12132-.585786.585786-1.53553.585786-2.12132 0-.585786-.585786-.585786-1.53553 0-2.12132.585786-.585786 1.53553-.585786 2.12132 0" />
<path d="m9.915 2.5h2.585-.00000004c.552285-.00000002 1 .447715 1 1v1c0 .552285-.447715 1-1 1h-8-.00000004c-.552285-.00000002-1-.447715-1-1v-1 .00000015c-.00000008-.552285.447715-1 1-1h2.585" />
<path d="m22.5 22.5c0 .552285-.447715 1-1 1h-9-.00000004c-.552285-.00000002-1-.447715-1-1v-11.5.00000015c-.00000008-.552285.447715-1 1-1h7.086.00000001c.265195.00005664.519507.105451.707.293l1.914 1.914-.00000002-.00000002c.187549.187493.292943.441805.293.707z" />
<path d="m14.5 14.5h5" />
<path d="m14.5 17.5h5" />
</g>
</svg>
);
};
export const BandwidthDown = (props) => {
return (
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
>
<path d="m20.25 17.25h-.00000013c1.65685-.00000007 3 1.34315 3 3 .00000007 1.65685-1.34315 3-3 3h-16.5-.00000013c-1.65685-.00000007-3-1.34315-3-3 .00000007-1.65685 1.34315-3 3-3z" />
<path d="m7.5 6.751h-1.356-.00000002c-1.39991-.00004099-2.61375.968129-2.925 2.333l-2.394 10.499" />
<path d="m23.175 19.583-2.394-10.5.00000014.0000006c-.311246-1.36487-1.52509-2.33304-2.925-2.333h-1.356" />
<path d="m19.125 19.875h-.00000002c-.207107.00000001-.375.167893-.375.375.00000001.207107.167893.375.375.375.207107-.00000001.375-.167893.375-.375 0-.207107-.167893-.375-.375-.375" />
<path d="m9.75 20.25h-5.25" />
<path d="m9 9.75 3 3 3-3" />
<path d="m12 12.75v-12" />
</g>
</svg>
);
};
export const BandwidthUp = (props) => {
return (
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" {...props}>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
>
<path d="m20.25 17.25h-.00000013c1.65685-.00000007 3 1.34315 3 3 .00000007 1.65685-1.34315 3-3 3h-16.5-.00000013c-1.65685-.00000007-3-1.34315-3-3 .00000007-1.65685 1.34315-3 3-3z" />
<path d="m7.5 6.751h-1.356-.00000002c-1.39991-.00004099-2.61375.968129-2.925 2.333l-2.394 10.499" />
<path d="m23.175 19.583-2.394-10.5.00000014.0000006c-.311246-1.36487-1.52509-2.33304-2.925-2.333h-1.356" />
<path d="m19.125 19.875h-.00000002c-.207107.00000001-.375.167893-.375.375.00000001.207107.167893.375.375.375.207107-.00000001.375-.167893.375-.375 0-.207107-.167893-.375-.375-.375" />
<path d="m9.75 20.25h-5.25" />
<path d="m15 3.75-3-3-3 3" />
<path d="m12 .75v12" />
</g>
</svg>
);
};

View File

@ -0,0 +1,55 @@
import * as THREE from "three";
import * as React from "react";
import GLComponent from "~/common/three/gl-component";
import AnimationLoop from "~/common/three/animation-loop";
export default class GLRenderer extends React.Component {
_GLComponent = undefined;
_AnimationLoop = undefined;
async componentDidMount() {
this._GLComponent = new GLComponent({
width: this.props.width,
height: this.props.height,
container: this.refs.canvas,
});
this._AnimationLoop = new AnimationLoop();
await this._GLComponent.mount();
this._GLComponent.firstRender();
this._AnimationLoop.subscribe(() => {
this._GLComponent.render();
});
this._AnimationLoop.start();
}
componentWillUnmount() {
this.reset();
}
pause() {
this._AnimationLoop.stop();
}
resume() {
if (!this._GLComponent) {
return null;
}
this._AnimationLoop.start();
}
reset() {
this._AnimationLoop.stop();
this._AnimationLoop.unsubscribeAll();
this._AnimationLoop = null;
this._GLComponent.unmount();
this._GLComponent = null;
}
render() {
return <div ref="canvas" />;
}
}

13
db.js
View File

@ -1,13 +0,0 @@
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}
import configs from '~/knexfile';
import knex from 'knex';
const environment =
process.env.NODE_ENV !== 'production' ? 'development' : 'production';
const envConfig = configs[environment];
const db = knex(envConfig);
module.exports = db;

View File

@ -1,23 +0,0 @@
/* prettier-ignore */
module.exports = {
development: {
client: 'pg',
connection: {
port: 1334,
host: '127.0.0.1',
database: 'nptdb',
user: 'admin',
password: 'oblivion'
}
},
production: {
client: 'pg',
connection: {
port: 1334,
host: '127.0.0.1',
database: 'nptdb',
user: 'admin',
password: 'oblivion'
}
}
};

View File

@ -2,9 +2,9 @@
"verbose": true,
"ignore": ["node_modules", ".next"],
"watch": [
"routes/**/*",
"common/**/*",
"components/**/*",
"scenes/**/*",
"pages/**/*",
"public/**/*",
"index.js",

View File

@ -1,37 +1,33 @@
{
"name": "fps",
"version": "0.0.1",
"scripts": {
"dev": "nodemon .",
"build": "next build",
"start": "NODE_ENV=production node .",
"do-setup-database": "node ./scripts setup-database",
"do-seed-database": "node ./scripts seed-database",
"do-drop-database": "node ./scripts drop-database"
},
"dependencies": {
"@babel/preset-env": "^7.8.3",
"@babel/register": "^7.8.3",
"@loadable/component": "^5.12.0",
"babel-plugin-emotion": "^9.2.11",
"babel-plugin-module-resolver": "^4.0.0",
"bcrypt": "^3.0.8",
"body-parser": "^1.19.0",
"compression": "^1.7.4",
"cookie-parser": "^1.4.4",
"dotenv": "^8.2.0",
"emotion": "^9.2.11",
"emotion-server": "^9.2.11",
"express": "^4.17.1",
"googleapis": "^47.0.0",
"isomorphic-fetch": "^2.2.1",
"jsonwebtoken": "^8.5.1",
"knex": "^0.20.10",
"next": "^9.2.2",
"pg": "^7.18.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-emotion": "^9.2.11",
"universal-cookie": "^4.0.3"
}
"name": "filecoin-prototype",
"version": "0.0.1",
"scripts": {
"dev": "node . --unhandled-rejections=strict",
"build": "next build",
"start": "NODE_ENV=production node . --unhandled-rejections=strict"
},
"dependencies": {
"@babel/preset-env": "^7.9.0",
"@babel/register": "^7.9.0",
"@emotion/babel-preset-css-prop": "^10.0.27",
"@emotion/cache": "11.0.0-next.12",
"@emotion/core": "^10.0.28",
"@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.6",
"babel-plugin-module-resolver": "^4.0.0",
"body-parser": "^1.19.0",
"chart.js": "^2.9.3",
"chartkick": "^3.2.0",
"compression": "^1.7.4",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"isomorphic-fetch": "^2.2.1",
"next": "^9.4.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-tippy": "^1.3.4",
"three": "^0.108.0"
}
}

29
pages/_app.js Normal file
View File

@ -0,0 +1,29 @@
import * as React from "react";
import { CacheProvider, Global } from "@emotion/react";
import { cache } from "@emotion/css";
import App from "next/app";
import {
injectGlobalStyles,
injectTooltipStyles,
} from "~/common/styles/global";
// NOTE(wwwjim):
// https://nextjs.org/docs/advanced-features/custom-app
function MyApp({ Component, pageProps }) {
return (
<CacheProvider value={cache}>
<Global styles={injectGlobalStyles()} />
<Global styles={injectTooltipStyles()} />
<Component {...pageProps} />
</CacheProvider>
);
}
MyApp.getInitialProps = async (appContext) => {
const appProps = await App.getInitialProps(appContext);
return { ...appProps };
};
export default MyApp;

View File

@ -1,9 +1,5 @@
import Document, { Head, Main, NextScript } from 'next/document';
import { extractCritical } from 'emotion-server';
import injectGlobalStyles from '~/common/styles/global';
injectGlobalStyles();
import Document, { Head, Main, NextScript } from "next/document";
import { extractCritical } from "@emotion/server";
export default class MyDocument extends Document {
static getInitialProps({ renderPage }) {
@ -12,19 +8,16 @@ export default class MyDocument extends Document {
return { ...page, ...styles };
}
constructor(props) {
super(props);
const { __NEXT_DATA__, ids } = props;
if (ids) {
__NEXT_DATA__.ids = ids;
}
}
render() {
const ids = this.props.ids.join(" ");
return (
<html>
<Head>
<style dangerouslySetInnerHTML={{ __html: this.props.css }} />
<style
data-emotion-css={ids}
dangerouslySetInnerHTML={{ __html: this.props.css }}
/>
</Head>
<body>
<Main />

View File

@ -1,36 +1,300 @@
import Head from 'next/head';
import * as React from 'react';
import * as Strings from '~/common/strings';
import * as Fixtures from '~/common/fixtures';
import * as Actions from '~/common/actions';
import { H1 } from '~/components/Text';
import { Button } from '~/components/Form';
import { css } from 'react-emotion';
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';
const STYLES_BODY = css`
padding: 24px;
height: 100vh;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
`;
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';
const STYLES_TITLE = css`
font-size: 4.22rem;
font-weight: 600;
`;
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;
let activeIds = {};
const findById = (state, id) => {
for (let i = 0; i < state.length; i++) {
if (state[i].id === id) {
target = state[i];
activeIds[state[i].id] = true;
}
if (!target && state[i].children) {
activeIds[state[i].id] = true;
findById(state[i].children, id);
if (!target) {
activeIds[state[i].id] = false;
}
}
}
};
findById(navigation, targetId);
return { target, activeIds };
};
export const getServerSideProps = (context) => {
// NOTE(jim)
// will be passed to the page component as props
return {
props: { ...context.query },
};
};
export default class IndexPage extends React.Component {
state = {
history: [{ id: 1, scrollTop: 0 }],
currentIndex: 0,
data: null,
selected: {
address: '1',
},
viewer: Fixtures.getInitialState(this.props),
sidebar: null,
};
async componentDidMount() {
let response;
response = await Actions.createWalletAddress({ name: 'new wallet' });
console.log(response);
response = await Actions.sendWalletAddressFilecoin({ source: null, target: null, amount: 1000 });
console.log(response);
}
_handleSubmit = (data) => {
this._handleDismissSidebar();
};
_handleCancel = () => {
this._handleDismissSidebar();
};
_handleViewerChange = (e) => {
this.setState({
viewer: { ...this.state.viewer, [e.target.name]: e.target.value },
});
};
_handleSelectedChange = (e) => {
this.setState({
selected: { ...this.state.selected, [e.target.name]: e.target.value },
});
};
_handleDismissSidebar = () => {
this.setState({ sidebar: null });
};
_handleAction = (options) => {
if (options.type === 'NAVIGATE') {
return this._handleNavigateTo({ id: options.value }, options.data);
}
if (options.type === 'ACTION') {
return alert(JSON.stringify(options));
}
if (options.type === 'DOWNLOAD') {
return alert(JSON.stringify(options));
}
if (options.type === 'SIDEBAR') {
return this.setState({ sidebar: this.sidebars[options.value] });
}
return alert(JSON.stringify(options));
};
_handleNavigateTo = (next, data = {}) => {
this.state.history[this.state.currentIndex].scrollTop = window.scrollY;
if (this.state.currentIndex !== this.state.history.length - 1) {
const adjustedArray = [...this.state.history];
adjustedArray.length = this.state.currentIndex + 1;
return this.setState(
{
history: [...adjustedArray, next],
currentIndex: this.state.currentIndex + 1,
data,
},
() => {
window.scrollTo(0, 0);
}
);
}
this.setState(
{
history: [...this.state.history, next],
currentIndex: this.state.currentIndex + 1,
data,
},
() => {
window.scrollTo(0, 0);
}
);
};
_handleBack = () => {
this.state.history[this.state.currentIndex].scrollTop = window.scrollY;
this.setState(
{
currentIndex: this.state.currentIndex - 1,
},
() => {
const next = this.state.history[this.state.currentIndex];
console.log({ next });
window.scrollTo(0, next.scrollTop);
}
);
};
_handleForward = () => {
this.state.history[this.state.currentIndex].scrollTop = window.scrollY;
this.setState(
{
currentIndex: this.state.currentIndex + 1,
},
() => {
const next = this.state.history[this.state.currentIndex];
console.log({ next });
window.scrollTo(0, next.scrollTop);
}
);
};
sidebars = {
SIDEBAR_NOTIFICATIONS: <SidebarNotifications />,
SIDEBAR_ADD_PEER: <SidebarAddPeer />,
SIDEBAR_ADD_MINER: <SidebarAddMiner />,
SIDEBAR_CREATE_PAYMENT_CHANNEL: <SidebarCreatePaymentChannel />,
SIDEBAR_FILE_STORAGE_DEAL: <SidebarFileStorageDeal />,
SIDEBAR_FILE_RETRIEVAL_DEAL: <SidebarFileRetrievalDeal />,
SIDEBAR_WALLET_SEND_FUNDS: <SidebarWalletSendFunds />,
SIDEBAR_CREATE_WALLET_ADDRESS: <SidebarCreateWalletAddress />,
SIDEBAR_DELETE_WALLET_ADDRESS: <SidebarDeleteWalletAddress />,
SIDEBAR_REDEEM_PAYMENT_CHANNEL: <SidebarRedeemPaymentChannel />,
};
scenes = {
HOME: <SceneHome />,
WALLET: <SceneWallet />,
CHANNELS: <ScenePaymentChannels />,
FOLDER: <SceneFilesFolder />,
FILE: <SceneFile />,
DEALS: <SceneDeals />,
DATA_TRANSFER: <SceneDataTransfer />,
STATS: <SceneStats />,
STORAGE_MARKET: <SceneStorageMarket />,
MINERS: <SceneMiners />,
STATUS: <SceneStatus />,
PEERS: <ScenePeers />,
LOGS: <SceneLogs />,
SETTINGS: <SceneSettings />,
EDIT_ACCOUNT: <SceneEditAccount />,
};
render() {
console.log(this.props);
const next = this.state.history[this.state.currentIndex];
const current = getCurrentNavigationStateById(Fixtures.NavigationState, next.id);
const navigation = (
<ApplicationNavigation
viewer={this.state.viewer}
activeId={current.target.id}
activeIds={current.activeIds}
navigation={Fixtures.NavigationState}
onNavigateTo={this._handleNavigateTo}
onAction={this._handleAction}
/>
);
const header = (
<ApplicationHeader
viewer={this.state.viewer}
pageTitle={current.target.pageTitle}
currentIndex={this.state.currentIndex}
onBack={this._handleBack}
onForward={this._handleForward}
history={this.state.history}
onNavigateTo={this._handleNavigateTo}
onAction={this._handleAction}
/>
);
const scene = React.cloneElement(this.scenes[current.target.decorator], {
viewer: this.state.viewer,
selected: this.state.selected,
data: current.target,
file: this.state.data,
onNavigateTo: this._handleNavigateTo,
onSelectedChange: this._handleSelectedChange,
onViewerChange: this._handleViewerChange,
onAction: this._handleAction,
onBack: this._handleBack,
onForward: this._handleForward,
});
let sidebar;
if (this.state.sidebar) {
sidebar = React.cloneElement(this.state.sidebar, {
viewer: this.state.viewer,
selected: this.state.selected,
onSelectedChange: this._handleSelectedChange,
onSubmit: this._handleSubmit,
onCancel: this._handleCancel,
});
}
const title = `Prototype 0.0.1 : ${current.target.pageTitle}`;
const description = 'This is an early preview.';
const url = 'https://fps.onrender.com/v1';
return (
<React.Fragment>
<Head>
<title>FPS: Prototype</title>
</Head>
<div className={STYLES_BODY}>
<div className={STYLES_TITLE}>FPS: Prototype</div>
</div>
<WebsitePrototypeWrapper title={title} description={description} url={url}>
<ApplicationLayout
navigation={navigation}
header={header}
sidebar={sidebar}
onDismissSidebar={this._handleDismissSidebar}>
{scene}
</ApplicationLayout>
</WebsitePrototypeWrapper>
</React.Fragment>
);
}

View File

@ -1,50 +0,0 @@
import Head from 'next/head';
import Cookies from 'universal-cookie';
import * as React from 'react';
import * as Constants from '~/common/constants';
import { H1, H2, P } from '~/components/Text';
import { css } from 'react-emotion';
import PageState from '~/components/PageState';
const cookies = new Cookies();
const STYLES_LAYOUT = css`
padding: 24px 24px 88px 24px;
`;
function Page(props) {
React.useEffect(() => {
if (props.jwt) {
cookies.set(Constants.session.key, props.jwt);
return;
}
}, []);
return (
<React.Fragment>
<Head>
<title>FPS: Prototype</title>
</Head>
<PageState data={props} />
<div className={STYLES_LAYOUT}>
<H1 style={{ marginTop: 24 }}>Sign in confirm</H1>
<H2 style={{ marginTop: 24 }}>
<a href="/sign-in-success">View an authenticated only page.</a>
</H2>
</div>
</React.Fragment>
);
}
Page.getInitialProps = async ctx => {
return {
error: ctx.err,
viewer: ctx.query.viewer,
jwt: ctx.query.jwt,
};
};
export default Page;

View File

@ -1,45 +0,0 @@
import Head from 'next/head';
import Cookies from 'universal-cookie';
import * as React from 'react';
import * as Constants from '~/common/constants';
import { H1, H2, P } from '~/components/Text';
import { css } from 'react-emotion';
import PageState from '~/components/PageState';
const cookies = new Cookies();
const STYLES_LAYOUT = css`
padding: 24px 24px 88px 24px;
`;
function Page(props) {
return (
<React.Fragment>
<Head>
<title>FPS: Prototype</title>
</Head>
<PageState data={props} />
<div className={STYLES_LAYOUT}>
<H1 style={{ marginTop: 24 }}>Error</H1>
<H2 style={{ marginTop: 24 }}>
<a href="/sign-in">Sign in again.</a>
</H2>
<H2 style={{ marginTop: 24 }}>
<a href="/sign-in-success">View an authenticated only page.</a>
</H2>
</div>
</React.Fragment>
);
}
Page.getInitialProps = async ctx => {
return {
error: ctx.err,
viewer: ctx.query.viewer,
};
};
export default Page;

View File

@ -1,43 +0,0 @@
import Head from 'next/head';
import * as React from 'react';
import * as Constants from '~/common/constants';
import { H1, H2, P } from '~/components/Text';
import { css } from 'react-emotion';
import PageState from '~/components/PageState';
const STYLES_LAYOUT = css`
padding: 24px 24px 88px 24px;
`;
function Page(props) {
return (
<React.Fragment>
<Head>
<title>FPS: Prototype</title>
</Head>
<PageState data={props} />
<div className={STYLES_LAYOUT}>
<H1 style={{ marginTop: 24 }}>You can only see this authenticated.</H1>
<H2 style={{ marginTop: 24 }}>
<a href="/sign-in">Return to sign in page.</a>
</H2>
<H2 style={{ marginTop: 24 }}>
<a href="/sign-out">Sign out.</a>
</H2>
</div>
</React.Fragment>
);
}
Page.getInitialProps = async ctx => {
return {
error: ctx.err,
viewer: ctx.query.viewer,
data: ctx.query.data,
};
};
export default Page;

View File

@ -1,88 +0,0 @@
import Head from 'next/head';
import Cookies from 'universal-cookie';
import * as React from 'react';
import * as Actions from '~/common/actions';
import * as Constants from '~/common/constants';
import { H1, H2, P } from '~/components/Text';
import { Input, Button } from '~/components/Form';
import { css } from 'react-emotion';
import PageState from '~/components/PageState';
const STYLES_FORM = css`
padding: 24px;
width: 100%;
margin: 48px auto 0 auto;
max-width: 768px;
`;
const STYLES_TOP = css`
margin-top: 48px;
`;
const STYLES_LAYOUT = css`
padding: 24px 24px 88px 24px;
`;
function Page(props) {
const [auth, setAuth] = React.useState({ email: '', password: '' });
return (
<React.Fragment>
<Head>
<title>FPS: Prototype</title>
</Head>
<PageState data={props} />
<div className={STYLES_LAYOUT}>
<H1 style={{ marginTop: 24 }}>Sign in</H1>
<H2 style={{ marginTop: 24 }}>
<a href={props.googleURL}>Create an account through Google.</a>
</H2>
<H2 style={{ marginTop: 24 }}>
<a href="/sign-in-success">View an authenticated only page.</a>
</H2>
<H2 style={{ marginTop: 24 }}>
<a href="/sign-out">Sign out.</a>
</H2>
<div className={STYLES_FORM}>
<P style={{ marginTop: 24, padding: 0 }}>E-mail</P>
<Input
autoComplete="off"
name="email"
value={auth.email}
onChange={e =>
setAuth({ ...auth, [e.target.name]: e.target.value })
}
/>
<P style={{ marginTop: 24, padding: 0 }}>Password</P>
<Input
autoComplete="off"
name="password"
type="password"
value={auth.password}
onChange={e =>
setAuth({ ...auth, [e.target.name]: e.target.value })
}
/>
<div className={STYLES_TOP}>
<Button onClick={e => Actions.onLocalSignIn(e, props, auth)}>
Sign in or create account
</Button>
</div>
</div>
</div>
</React.Fragment>
);
}
Page.getInitialProps = async ctx => {
return {
googleURL: ctx.query.googleURL,
viewer: ctx.query.viewer,
error: ctx.err,
};
};
export default Page;

View File

@ -1,51 +0,0 @@
import Head from 'next/head';
import Cookies from 'universal-cookie';
import * as React from 'react';
import * as Constants from '~/common/constants';
import { H1, H2, P } from '~/components/Text';
import { css } from 'react-emotion';
import PageState from '~/components/PageState';
const cookies = new Cookies();
const STYLES_LAYOUT = css`
padding: 24px 24px 88px 24px;
`;
function Page(props) {
React.useEffect(() => {
const jwt = cookies.get(Constants.session.key);
if (jwt) {
cookies.remove(Constants.session.key);
return;
}
}, []);
return (
<React.Fragment>
<Head>
<title>FPS: Prototype</title>
</Head>
<PageState data={props} />
<div className={STYLES_LAYOUT}>
<H1 style={{ marginTop: 24 }}>Signed out</H1>
<H2 style={{ marginTop: 24 }}>
<a href="/sign-in">Sign in.</a>
</H2>
</div>
</React.Fragment>
);
}
Page.getInitialProps = async ctx => {
return {
error: ctx.err,
viewer: ctx.query.viewer,
jwt: ctx.query.jwt,
};
};
export default Page;

624
pages/system/index.js Normal file
View File

@ -0,0 +1,624 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as System from "~/components/system";
import * as Fixtures from "~/common/fixtures";
import * as SVG from "~/components/system/svg";
import * as OldSVG from "~/common/svg";
import * as Constants from "~/common/constants";
import { css } from "@emotion/react";
import GLRenderer from "~/components/three/GLRenderer";
import Group from "~/components/system/Group";
import SystemPage from "~/components/system/SystemPage";
const DEFAULT_SYSTEM_ICON_SIZE = "88px";
const ICONS = [
<OldSVG.Home height={DEFAULT_SYSTEM_ICON_SIZE} />,
<OldSVG.Folder height={DEFAULT_SYSTEM_ICON_SIZE} />,
<OldSVG.Wallet height={DEFAULT_SYSTEM_ICON_SIZE} />,
<OldSVG.Channels height={DEFAULT_SYSTEM_ICON_SIZE} />,
<OldSVG.Deals height={DEFAULT_SYSTEM_ICON_SIZE} />,
<OldSVG.Peers height={DEFAULT_SYSTEM_ICON_SIZE} />,
<OldSVG.Deals height={DEFAULT_SYSTEM_ICON_SIZE} />,
<OldSVG.Status height={DEFAULT_SYSTEM_ICON_SIZE} />,
<OldSVG.Stats height={DEFAULT_SYSTEM_ICON_SIZE} />,
<OldSVG.DataTransfer height={DEFAULT_SYSTEM_ICON_SIZE} />,
<OldSVG.Logs height={DEFAULT_SYSTEM_ICON_SIZE} />,
<OldSVG.Miners height={DEFAULT_SYSTEM_ICON_SIZE} />,
<OldSVG.StorageMarket height={DEFAULT_SYSTEM_ICON_SIZE} />,
<SVG.BandwidthUp height={DEFAULT_SYSTEM_ICON_SIZE} />,
<SVG.BandwidthDown height={DEFAULT_SYSTEM_ICON_SIZE} />,
<SVG.Information height={DEFAULT_SYSTEM_ICON_SIZE} />,
<SVG.CopyAndPaste height={DEFAULT_SYSTEM_ICON_SIZE} />,
<SVG.FileImage height={DEFAULT_SYSTEM_ICON_SIZE} />,
<SVG.Plus height={DEFAULT_SYSTEM_ICON_SIZE} />,
<SVG.CheckBox height={DEFAULT_SYSTEM_ICON_SIZE} />,
];
const SELECT_MENU_OPTIONS = [
{ value: "1", name: "Capricorn" },
{ value: "2", name: "Aquarius" },
{ value: "3", name: "Pisces" },
{ value: "4", name: "Aries" },
{ value: "5", name: "Taurus" },
{ value: "6", name: "Gemini" },
{ value: "7", name: "Cancer" },
{ value: "8", name: "Leo" },
{ value: "9", name: "Virgo" },
{ value: "10", name: "Libra" },
{ value: "11", name: "Scorpio" },
{ value: "12", name: "Sagittarus" },
];
const SELECT_MENU_MAP = {
"1": "Capricorn",
"2": "Aquarius",
"3": "Pisces",
"4": "Aries",
"5": "Taurus",
"6": "Gemini",
"7": "Cancer",
"8": "Leo",
"9": "Virgo",
"10": "Libra",
"11": "Scorpio",
"12": "Sagittarus",
};
const RADIO_GROUP_OPTIONS = [
{
value: "1",
label: (
<React.Fragment>
<strong>Breakfast Option</strong>
<br />I want to have cake and soda for breakfast.
</React.Fragment>
),
},
{
value: "2",
label: (
<React.Fragment>
<strong>Lunch Option</strong>
<br />I want to have cake and soda for lunch.
</React.Fragment>
),
},
{
value: "3",
label: (
<React.Fragment>
<strong>Dinner Option</strong>
<br />I want to have cake and soda for dinner.
</React.Fragment>
),
},
{
value: "4",
label:
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
},
];
const TAB_GROUP_TWO = [
{ value: "1", label: "Capricorn" },
{ value: "2", label: "Aquarius" },
];
const TAB_GROUP_THREE = [
{ value: "1", label: "Capricorn" },
{ value: "2", label: "Aquarius" },
{ value: "3", label: "Pisces" },
];
const TAB_GROUP_FOUR = [
{ value: "1", label: "Capricorn" },
{ value: "2", label: "Aquarius" },
{ value: "3", label: "Pisces" },
{ value: "4", label: "Aries" },
];
const STYLES_ICON = css`
padding: 24px;
color: ${Constants.system.white};
display: inline-flex;
transition: 200ms ease color;
:hover {
color: ${Constants.system.brand};
}
`;
const STYLES_ICON_ELEMENT = css`
height: 40px;
width: 40px;
border-radius: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
background-color: ${Constants.system.brand};
color: ${Constants.system.white};
user-select: none;
`;
const STYLES_PAGE = css`
background: #fcfcfc;
`;
const STYLES_BODY = css`
max-width: 960px;
width: 100%;
margin: 0 auto 0 auto;
padding: 88px 24px 128px 24px;
`;
const STYLES_COLOR_BAR = css`
width: 100%;
display: flex;
align-items: center;
padding: 72px 24px 0 0px;
`;
const STYLES_COLOR_TEXT = css`
display: block;
text-transform: uppercase;
letter-spacing: 0.2px;
font-size: 12px;
padding: 8px;
color: ${Constants.system.white};
background-color: rgba(0, 0, 0, 0.4);
margin-top: 8px;
`;
export default class SystemPageRoot extends React.Component {
state = {
one: "1",
two: "3",
three: true,
four: false,
five: "1",
six: false,
seven: true,
eight: "1",
nine: "1",
ten: "1",
eleven: "1",
twelve: "Replace me Cake",
thirteen: "",
fourteen: "",
fifteen: "aaaaa-bbbbb-ccccc-ddddd-eeee",
sixteen: "",
seventeen: `example
example
example
example`,
eighteen: "2",
nineteen: null,
table_data: null,
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<SystemPage
title="FCDS"
description="This is an early preview of the Filecoin Client Design System (FCDS)."
url="https://fps.onrender.com/system"
>
<System.H1>
<span css={STYLES_ICON_ELEMENT}>
<SVG.Logo height="32px" />
</span>{" "}
FCDS 1.0
</System.H1>
<br />
<System.P>
This is an early preview of the{" "}
<strong>Filecoin Client Design System (FCDS)</strong>. We are
developing our philosophy, principles and practices out in the open.
</System.P>
<br /> <br />
<System.H2>Introduction</System.H2>
<br />
<System.P>
Components here are free for you to use in any of your projects. The
components will serve as common use cases of the Filecoin Network,
making it easier to integrate the Filecoin Network into your own
applications.
<br />
<br />
Stay tuned for updates. A GitHub link will be available soon.
</System.P>
<Group
id="globe"
title="Globe"
style={{ marginTop: 48 }}
groupStyle={{ padding: 0 }}
>
<GLRenderer width={960} height={480} />
</Group>
<Group
title="Icons"
style={{ marginTop: 48 }}
groupStyle={{
padding: 0,
backgroundColor: Constants.system.pitchBlack,
}}
>
{ICONS.map((icon, i) => {
return (
<div css={STYLES_ICON} key={i}>
{icon}
</div>
);
})}
</Group>
<Group
id="colors"
title="Colors"
style={{ marginTop: 48 }}
groupStyle={{ padding: 0 }}
>
{Object.keys(Constants.system).map((each) => {
const hex = Constants.system[each];
const rgba = Strings.hexToRGBA(hex);
return (
<div
key={each}
css={STYLES_COLOR_BAR}
style={{
backgroundColor: hex,
color: Constants.system.black,
}}
>
<span css={STYLES_COLOR_TEXT}>{each.toUpperCase()}</span>
<br />
<span css={STYLES_COLOR_TEXT}>{hex}</span>
<br />
<span css={STYLES_COLOR_TEXT}>{rgba}</span>
</div>
);
})}
</Group>
<Group
id="tables"
title="Tables"
style={{ marginTop: 48 }}
groupStyle={{ padding: 0 }}
>
<System.Table
data={{
columns: [
{ key: "a", name: "Column A", type: "FILE_LINK" },
{ key: "b", name: "Column B", width: "228px" },
{ key: "c", name: "Column C" },
{
key: "d",
name: "Column D",
tooltip: "A tooltip.",
},
{
key: "e",
name: "Column E",
copyable: true,
},
{
key: "f",
name: "Column F",
},
],
rows: [
{ id: 1, a: 1, b: 1, c: 1, e: 1, f: 1, d: 1 },
{ id: 2, a: 111, b: 111, c: 111, e: 111, f: 111, d: 111 },
{
id: 3,
a: 111111,
b: 111111,
c: 111111,
e: null,
f: 111111,
d: 1111111,
},
{
id: 4,
a: 111111111111,
b: 11111111111,
c: 111111111111,
e: 11111111111,
f: 11111111111111,
d: 1111111111111111,
},
],
}}
selectedRowId={this.state.table_data}
onChange={this._handleChange}
name="table_data"
/>
</Group>
<Group id="tooltips" style={{ marginTop: 48 }} title="Tooltips">
<System.TooltipAnchor tooltip="Hello friends!!" />
</Group>
<Group id="line-charts" style={{ marginTop: 48 }} title="Line charts">
<System.StatCard
denomination="Filecoin"
value={423123121323}
data={[
["2017-01-01 00:00:00 UTC", 7],
["2017-05-01 00:00:00 UTC", 12],
["2017-20-01 00:00:00 UTC", 16],
["2017-24-01 00:00:00 UTC", 20],
[new Date(), 24],
]}
>
Amount of Filecoin
</System.StatCard>
<br />
<br />
<System.StatCard
denomination="Bitcoin"
value={12321345}
data={[
["2017-01-01 00:00:00 UTC", 27],
["2017-05-01 00:00:00 UTC", 112],
["2017-20-01 00:00:00 UTC", 416],
["2017-24-01 00:00:00 UTC", 1120],
[new Date(), 827],
]}
>
Amount of Bitcoin
</System.StatCard>
</Group>
<Group id="stats" style={{ marginTop: 48 }} title="Stats">
<System.StatUpload>40 mb</System.StatUpload>{" "}
<System.StatDownload>40 mb</System.StatDownload>
</Group>
<Group id="buttons" style={{ marginTop: 48 }} title="Buttons">
<System.ButtonPrimary>Button Primary</System.ButtonPrimary>
<br />
<br />
<System.ButtonPrimaryFull>
Button Primary Full
</System.ButtonPrimaryFull>
<br />
<br />
<System.ButtonSecondary>Button Secondary</System.ButtonSecondary>
<br />
<br />
<System.ButtonSecondaryFull>
Button Secondary Full
</System.ButtonSecondaryFull>
<br />
<br />
<System.ButtonDisabled>Button Disabled</System.ButtonDisabled>
<br />
<br />
<System.ButtonDisabledFull>Button Disabled</System.ButtonDisabledFull>
</Group>
<Group id="checkboxes" style={{ marginTop: 48 }} title="Checkboxes">
<System.CheckBox
name="six"
value={this.state.six}
onChange={this._handleChange}
>
<strong>I want to attend IPFS Pinning Summit</strong>
<br />
The IPFS Pinning Summit is a 2-day virtual conference designed for
the infrastructure and service providers of the distributed web.
</System.CheckBox>
<br />
<br />
<System.CheckBox
name="seven"
value={this.state.seven}
onChange={this._handleChange}
>
<strong>Return Cake</strong>
<br />I want Cake to become a different object.
</System.CheckBox>
</Group>
<Group id="radio" style={{ marginTop: 48 }} title="Radios">
<System.RadioGroup
name="five"
options={RADIO_GROUP_OPTIONS}
selected={this.state.five}
onChange={this._handleChange}
/>
</Group>
<Group id="card-tabs" style={{ marginTop: 48 }} title="Card tabs">
<System.CardTabGroup
name="eighteen"
options={TAB_GROUP_TWO}
value={this.state.eighteen}
onChange={this._handleChange}
/>
<br />
<br />
<System.CardTabGroup
name="nineteen"
options={TAB_GROUP_FOUR}
value={this.state.nineteen}
onChange={this._handleChange}
/>
</Group>
<Group id="tabs" style={{ marginTop: 48 }} title="Tabs">
<System.TabGroup
name="eight"
options={TAB_GROUP_TWO}
value={this.state.eight}
onChange={this._handleChange}
/>
<System.TabGroup
name="nine"
options={TAB_GROUP_THREE}
value={this.state.nine}
onChange={this._handleChange}
/>
<System.TabGroup
name="ten"
options={TAB_GROUP_FOUR}
value={this.state.ten}
onChange={this._handleChange}
/>
</Group>
<Group id="toggles" style={{ marginTop: 48 }} title="Toggles">
<System.Toggle
active={this.state.three}
name="three"
onChange={this._handleChange}
/>
<br />
<br />
<System.Toggle
active={this.state.four}
name="four"
onChange={this._handleChange}
/>
</Group>
<Group id="inputs" style={{ marginTop: 48 }} title="Inputs">
<System.Textarea
name="seventeen"
value={this.state.seventeen}
onChange={this._handleChange}
/>
<br />
<br />
<System.Input
name="twelve"
value={this.state.twelve}
onChange={this._handleChange}
/>
<br />
<br />
<System.Input
name="thireteen"
value={this.state.thirteen}
placeholder="Enter your favorite year"
onChange={this._handleChange}
/>
<br />
<br />
<System.Input
label="Location of your pastries"
description="We need to know the location of your pastries to sell them to other people."
tooltip="Hey friends."
name="fourteen"
value={this.state.fourteen}
placeholder="Pastry Location"
onChange={this._handleChange}
/>
<br />
<br />
<System.Input
label="Max length is 14"
max={14}
name="sixteen"
value={this.state.sixteen}
onChange={this._handleChange}
/>
<br />
<br />
<System.Input
label="Copy and paste (read only)"
readOnly
name="fifteen"
copyable
value={this.state.fifteen}
onChange={this._handleChange}
/>
<br />
<br />
<System.Input
label="Success"
placeholder="This is an uncontrolled input for success."
validation="SUCCESS"
/>
<br />
<br />
<System.Input
label="Warning"
placeholder="This is an uncontrolled input for warning."
validation="WARNING"
/>
<br />
<br />
<System.Input
label="Error"
placeholder="This is an uncontrolled input for error."
validation="ERROR"
/>
</Group>
<Group id="dropdowns" style={{ marginTop: 48 }} title="Dropdowns">
<System.SelectMenu
name="one"
value={this.state.one}
category="horoscope"
onChange={this._handleChange}
options={SELECT_MENU_OPTIONS}
>
{SELECT_MENU_MAP[this.state.one]}
</System.SelectMenu>
<br />
<br />
<br />
<System.SelectMenuFull
name="two"
value={this.state.two}
category="horoscope"
onChange={this._handleChange}
options={SELECT_MENU_OPTIONS}
>
{SELECT_MENU_MAP[this.state.two]}
</System.SelectMenuFull>
<br />
<br />
<br />
<System.SelectMenuFull
label="Pick a horoscope"
name="eleven"
value={this.state.eleven}
category="horoscope"
onChange={this._handleChange}
options={SELECT_MENU_OPTIONS}
>
{SELECT_MENU_MAP[this.state.eleven]}
</System.SelectMenuFull>
</Group>
<System.H2 style={{ marginTop: 128 }}>The MIT License</System.H2>
<br />
<System.P>
Copyright &copy; 2020 Protocol Labs
<br />
<br />
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
<br />
<br />
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
<br />
<br />
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</System.P>
</SystemPage>
);
}
}

View File

@ -1,78 +0,0 @@
import Head from 'next/head';
import * as React from 'react';
import * as Constants from '~/common/constants';
import { css, styled } from 'react-emotion';
import PageState from '~/components/PageState';
const STYLES_LAYOUT_ONE = css`
font-size: 64px;
width: 320px;
height: calc(100vh - 76px);
background: ${Constants.colors.gray3};
overflow-y: scroll;
padding: 24px;
::-webkit-scrollbar {
width: 0px;
}
`;
const STYLES_LAYOUT_TWO = css`
font-size: 64px;
width: 320px;
height: calc(100vh - 76px);
background: ${Constants.colors.gray2};
overflow-y: scroll;
padding: 24px;
::-webkit-scrollbar {
width: 0px;
}
`;
const STYLES_LAYOUT_THREE = css`
min-width: 20%;
width: 100%;
background: ${Constants.colors.gray};
height: calc(100vh - 76px);
overflow-y: scroll;
padding: 24px;
::-webkit-scrollbar {
width: 0px;
}
`;
const STYLES_NAVIGATION = css`
height: 48px;
padding: 8px 24px 8px 24px;
background: ${Constants.colors.gray4};
`;
const STYLES_LAYOUT = css`
display: flex;
align-items: flex-start;
justify-content: space-between;
`;
export default class IndexPage extends React.Component {
render() {
return (
<React.Fragment>
<Head>
<title>fps: prototype: home</title>
</Head>
<PageState>FPS Prototype 0.0.1 /home</PageState>
<nav className={STYLES_NAVIGATION}>&nbsp;</nav>
<div className={STYLES_LAYOUT}>
<span className={STYLES_LAYOUT_ONE}>&nbsp;</span>
<span className={STYLES_LAYOUT_TWO}>&nbsp;</span>
<span className={STYLES_LAYOUT_THREE}>&nbsp;</span>
</div>
</React.Fragment>
);
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1000 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
public/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
public/static/social.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

View File

@ -1,60 +0,0 @@
import * as Strings from '~/common/strings';
import * as Data from '~/common/data';
import * as Utilities from '~/common/utilities';
import * as Credentials from '~/common/credentials';
import JWT from 'jsonwebtoken';
import BCrypt from 'bcrypt';
export default async (req, res) => {
const authorization = Utilities.parseAuthHeader(req.headers.authorization);
// todo: check if a cookie already exists.
if (authorization && !Strings.isEmpty(authorization.value)) {
const verfied = JWT.verify(authorization.value, Credentials.JWT_SECRET);
return res.status(200).send({
message: 'You are already authenticated. Welcome back!',
token: authorization.value,
});
}
if (Strings.isEmpty(req.body.email)) {
return res
.status(500)
.send({ error: 'An e-mail address was not provided.' });
}
if (Strings.isEmpty(req.body.password)) {
return res.status(500).send({ error: 'A password was not provided.' });
}
let user = await Data.getUserByEmail({ email: req.body.email });
if (!user) {
const salt = BCrypt.genSaltSync(10);
const hash = BCrypt.hashSync(req.body.password, salt);
const double = BCrypt.hashSync(hash, salt);
const triple = BCrypt.hashSync(double, process.env.PASSWORD_SECRET);
user = await Data.createUser({
email: req.body.email,
password: triple,
salt,
});
} else {
const phaseOne = BCrypt.hashSync(req.body.password, user.salt);
const phaseTwo = BCrypt.hashSync(phaseOne, user.salt);
const phaseThree = BCrypt.hashSync(phaseTwo, process.env.PASSWORD_SECRET);
if (phaseThree !== user.password) {
return res.status(500).send({ error: 'We could not authenticate you.' });
}
}
const token = JWT.sign(
{ user: user.id, email: user.email },
Credentials.JWT_SECRET
);
return res.status(200).send({ token });
};

View File

@ -1,14 +0,0 @@
import signIn from '~/routes/sign-in';
import signInConfirm from '~/routes/sign-in-confirm';
import signInSuccess from '~/routes/sign-in-success';
import apiSignIn from '~/routes/api/sign-in';
module.exports = {
signIn,
signInConfirm,
signInSuccess,
api: {
signIn: apiSignIn,
},
};

View File

@ -1,71 +0,0 @@
import * as Credentials from '~/common/credentials';
import * as Data from '~/common/data';
import JWT from 'jsonwebtoken';
import BCrypt from 'bcrypt';
const google = require('googleapis').google;
const OAuth2 = google.auth.OAuth2;
export default async (req, res, app) => {
const client = new OAuth2(
Credentials.CLIENT_ID,
Credentials.CLIENT_SECRET,
Credentials.REDIRECT_URIS
);
if (req.query.error) {
return res.redirect('/sign-in-error');
}
client.getToken(req.query.code, async (error, token) => {
if (error) {
return res.redirect('/sign-in-error');
}
const jwt = JWT.sign(token, Credentials.JWT_SECRET);
const client = new OAuth2(
Credentials.CLIENT_ID,
Credentials.CLIENT_SECRET,
Credentials.REDIRECT_URIS
);
client.credentials = JWT.verify(jwt, Credentials.JWT_SECRET);
const people = google.people({
version: 'v1',
auth: client,
});
const response = await people.people.get({
resourceName: 'people/me',
personFields: 'emailAddresses,names,organizations,memberships',
});
const email = response.data.emailAddresses[0].value;
const name = response.data.names[0].displayName;
const password = BCrypt.genSaltSync(10);
let user = await Data.getUserByEmail({ email });
if (!user) {
const salt = BCrypt.genSaltSync(10);
const hash = BCrypt.hashSync(password, salt);
const double = BCrypt.hashSync(hash, salt);
const triple = BCrypt.hashSync(double, process.env.PASSWORD_SECRET);
user = await Data.createUser({
email,
password: triple,
salt,
data: { name },
});
}
const authToken = JWT.sign(
{ user: user.id, email: user.email },
Credentials.JWT_SECRET
);
app.render(req, res, '/sign-in-confirm', { jwt: authToken, viewer: user });
});
};

View File

@ -1,7 +0,0 @@
import * as Data from '~/common/data';
export default async (req, res, app) => {
const { viewer } = await Data.getViewer(req);
return app.render(req, res, '/sign-in-success', { viewer });
};

View File

@ -1,27 +0,0 @@
import * as Credentials from '~/common/credentials';
import * as Data from '~/common/data';
const google = require('googleapis').google;
const OAuth2 = google.auth.OAuth2;
export default async (req, res, app) => {
const client = new OAuth2(
Credentials.CLIENT_ID,
Credentials.CLIENT_SECRET,
Credentials.REDIRECT_URIS
);
const googleURL = client.generateAuthUrl({
access_type: 'offline',
scope: [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/user.organization.read',
],
prompt: 'consent',
});
const { viewer } = await Data.getViewer(req);
app.render(req, res, '/', { googleURL, viewer });
};

Some files were not shown because too many files have changed in this diff Show More