initial commit for the application prototype
6
.babelrc
@ -7,12 +7,10 @@
|
||||
"useESModules": false
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"@emotion/babel-preset-css-prop",
|
||||
],
|
||||
"plugins": [
|
||||
["emotion", {
|
||||
"inline": true
|
||||
}],
|
||||
["module-resolver", {
|
||||
"alias": {
|
||||
"~": "./"
|
||||
|
3
.gitignore
vendored
@ -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/**/*
|
||||
|
47
README.md
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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;
|
||||
|
376
common/data.js
@ -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
@ -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,
|
||||
},
|
||||
];
|
@ -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
@ -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" },
|
||||
];
|
@ -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, ",");
|
||||
};
|
||||
|
@ -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
@ -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>
|
||||
);
|
46
common/three/animation-loop.js
Normal 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 = [];
|
||||
}
|
||||
}
|
147
common/three/gl-component.js
Normal 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
@ -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;
|
1
common/three/points-3.json
Normal file
52220
common/three/points.json
Normal 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
@ -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
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
};
|
@ -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} />;
|
||||
};
|
238
components/core/ApplicationHeader.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
119
components/core/ApplicationLayout.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
315
components/core/ApplicationNavigation.js
Normal 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
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
114
components/core/Dismissible.js
Normal 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
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
26
components/core/ScenePage.js
Normal 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
@ -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>
|
||||
);
|
||||
};
|
54
components/core/WebsitePrototypeWrapper.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
33
components/sidebars/SidebarAddMiner.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
33
components/sidebars/SidebarAddPeer.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
108
components/sidebars/SidebarCreatePaymentChannel.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
76
components/sidebars/SidebarCreateWalletAddress.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
46
components/sidebars/SidebarDeleteWalletAddress.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
33
components/sidebars/SidebarFileRetrievalDeal.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
174
components/sidebars/SidebarFileStorageDeal.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
56
components/sidebars/SidebarNotifications.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
33
components/sidebars/SidebarRedeemPaymentChannel.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
111
components/sidebars/SidebarWalletSendFunds.js
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
43
components/system/Group.js
Normal 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>
|
||||
);
|
||||
};
|
68
components/system/SystemPage.js
Normal 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
307
components/system/sub-system.js
Normal 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
@ -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>
|
||||
);
|
||||
};
|
55
components/three/GLRenderer.js
Normal 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
@ -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;
|
23
knexfile.js
@ -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'
|
||||
}
|
||||
}
|
||||
};
|
@ -2,9 +2,9 @@
|
||||
"verbose": true,
|
||||
"ignore": ["node_modules", ".next"],
|
||||
"watch": [
|
||||
"routes/**/*",
|
||||
"common/**/*",
|
||||
"components/**/*",
|
||||
"scenes/**/*",
|
||||
"pages/**/*",
|
||||
"public/**/*",
|
||||
"index.js",
|
||||
|
66
package.json
@ -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
@ -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;
|
@ -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 />
|
||||
|
312
pages/index.js
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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;
|
@ -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
@ -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 © 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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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}> </nav>
|
||||
<div className={STYLES_LAYOUT}>
|
||||
<span className={STYLES_LAYOUT_ONE}> </span>
|
||||
<span className={STYLES_LAYOUT_TWO}> </span>
|
||||
<span className={STYLES_LAYOUT_THREE}> </span>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
BIN
public/static/Inter-Medium.woff
Normal file
BIN
public/static/Inter-Regular.woff
Normal file
BIN
public/static/Inter-SemiBold.woff
Normal file
BIN
public/static/SFMono-Bold.woff
Normal file
BIN
public/static/avatar-adrian-lanzafame.png
Normal file
After Width: | Height: | Size: 1000 KiB |
BIN
public/static/avatar-andrew-hill.jpg
Normal file
After Width: | Height: | Size: 509 KiB |
BIN
public/static/avatar-colin-evran.jpg
Normal file
After Width: | Height: | Size: 201 KiB |
BIN
public/static/avatar-juan-benet.png
Normal file
After Width: | Height: | Size: 412 KiB |
BIN
public/static/avatar-molly-mackinlay.jpg
Normal file
After Width: | Height: | Size: 182 KiB |
BIN
public/static/avatar-pooja-shah.jpg
Normal file
After Width: | Height: | Size: 501 KiB |
BIN
public/static/avatar-steven-allen.png
Normal file
After Width: | Height: | Size: 318 KiB |
BIN
public/static/avatar-yuni-graham.jpg
Normal file
After Width: | Height: | Size: 857 KiB |
BIN
public/static/example-painting-a-1.jpg
Normal file
After Width: | Height: | Size: 492 KiB |
BIN
public/static/example-painting-a-2.jpg
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
public/static/example-painting-a-3.jpg
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
public/static/example-painting-a-4.jpg
Normal file
After Width: | Height: | Size: 137 KiB |
BIN
public/static/example-painting-a-5.jpg
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
public/static/favicon-16x16.png
Normal file
After Width: | Height: | Size: 225 B |
BIN
public/static/favicon-32x32.png
Normal file
After Width: | Height: | Size: 411 B |
BIN
public/static/favicon-96x96.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
public/static/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
public/static/qr-code-example.jpg
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
public/static/social.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
public/static/test-image-2.jpg
Normal file
After Width: | Height: | Size: 234 KiB |
BIN
public/static/test-image-3.jpg
Normal file
After Width: | Height: | Size: 524 KiB |
BIN
public/static/test-image-4.jpg
Normal file
After Width: | Height: | Size: 244 KiB |
BIN
public/static/test-image-5.jpg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
public/static/test-image-6.jpg
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
public/static/test-image-7.jpg
Normal file
After Width: | Height: | Size: 193 KiB |
BIN
public/static/test-image-upload.jpg
Normal file
After Width: | Height: | Size: 159 KiB |
BIN
public/static/test-image.jpg
Normal file
After Width: | Height: | Size: 163 KiB |
@ -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 });
|
||||
};
|
@ -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,
|
||||
},
|
||||
};
|
@ -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 });
|
||||
});
|
||||
};
|
@ -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 });
|
||||
};
|
@ -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 });
|
||||
};
|