From 04c467f5055cd67b9300cfbcf0c4fb14cdf868a6 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Tue, 9 May 2023 11:27:19 +0900 Subject: [PATCH 01/21] prepare for ptolemy app integration --- app/page.tsx | 4 +- common/navigation.ts | 7 +- components/Application.module.scss | 2 - components/Application.tsx | 221 ------------------ components/DefaultLayout.tsx | 68 +++--- components/scenes/SceneAuth.module.scss | 7 - components/scenes/SceneAuth.tsx | 48 ---- components/scenes/SceneDatasets.module.scss | 2 - components/scenes/SceneDatasets.tsx | 71 ------ components/scenes/SceneProviders.module.scss | 69 ------ components/scenes/SceneProviders.tsx | 166 ------------- .../scenes/SceneReplications.module.scss | 2 - components/scenes/SceneReplications.tsx | 69 ------ components/scenes/SceneWallets.module.scss | 6 - components/scenes/SceneWallets.tsx | 102 -------- global.scss | 1 + 16 files changed, 47 insertions(+), 798 deletions(-) delete mode 100644 components/Application.module.scss delete mode 100644 components/Application.tsx delete mode 100644 components/scenes/SceneAuth.module.scss delete mode 100644 components/scenes/SceneAuth.tsx delete mode 100644 components/scenes/SceneDatasets.module.scss delete mode 100644 components/scenes/SceneDatasets.tsx delete mode 100644 components/scenes/SceneProviders.module.scss delete mode 100644 components/scenes/SceneProviders.tsx delete mode 100644 components/scenes/SceneReplications.module.scss delete mode 100644 components/scenes/SceneReplications.tsx delete mode 100644 components/scenes/SceneWallets.module.scss delete mode 100644 components/scenes/SceneWallets.tsx diff --git a/app/page.tsx b/app/page.tsx index 176ad97..2bf610e 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,7 +1,7 @@ import '@root/global.scss'; -import Application from '@components/Application'; +import Main from '@components/Main'; export default async function Page(props) { - return ; + return
; } diff --git a/common/navigation.ts b/common/navigation.ts index 8774944..de6845f 100644 --- a/common/navigation.ts +++ b/common/navigation.ts @@ -12,4 +12,9 @@ export const tooltipStates = { addWallet: 4, addReplication: 5, associateWallet: 6, -}; \ No newline at end of file +}; + +export const apps = { + ddm: 1, + ptolemy: 2, +} \ No newline at end of file diff --git a/components/Application.module.scss b/components/Application.module.scss deleted file mode 100644 index 6d86a06..0000000 --- a/components/Application.module.scss +++ /dev/null @@ -1,2 +0,0 @@ -.body { -} diff --git a/components/Application.tsx b/components/Application.tsx deleted file mode 100644 index f83f87c..0000000 --- a/components/Application.tsx +++ /dev/null @@ -1,221 +0,0 @@ -'use client'; - -import * as React from 'react'; -import PackageJSON from '@root/package.json'; -import { navigationStates, tooltipStates } from '@common/navigation'; -import { getCookie, setCookie } from '@modules/cookies'; -import { associateWallet, checkAuth, getDatasets, getHealth, getProviders, getReplications, getWallets } from '@data/api'; - -import styles from '@components/Application.module.scss'; - -import DefaultLayout from '@components/DefaultLayout'; - -import SceneDatasets from '@components/scenes/SceneDatasets'; -import SceneProviders from '@components/scenes/SceneProviders'; -import SceneReplications from '@components/scenes/SceneReplications'; -import SceneWallets from '@components/scenes/SceneWallets'; -import SceneAuth from '@components/scenes/SceneAuth'; - -import FormUploadData from '@components/forms/FormUploadData'; -import FormAddWallet from '@components/forms/FormAddWallet'; -import FormAddProvider from '@components/forms/FormAddProvider'; -import FormAddReplication from '@components/forms/FormAddReplication'; -import FormNewDataset from '@components/forms/FormNewDataset'; -import FormAssociateWallet from '@components/forms/FormAssociateWallet'; -import { readFileSync } from 'fs'; - - -export default function Application(props) { - const [appNavigationState, setAppNavigationState] = React.useState(1); - const [datasetSearch, setDatasetSearch] = React.useState(''); - const [providerSearch, setProviderSearch] = React.useState(''); - const [replicationSearch, setReplicationSearch] = React.useState(''); - const [selectedProvider, setSelectedProvider] = React.useState(''); - const [selectedDataset, setSelectedDataset] = React.useState(''); - const [selectedWallet, setSelectedWallet] = React.useState(''); - const [state, setState] = React.useState({ - datasets: undefined, - providers: undefined, - replications: undefined, - wallets: undefined - }); - const [health, setHealth] = React.useState(undefined); - const [commitHash, setCommitHash] = React.useState(undefined); - const [appTooltipState, setAppTooltipState] = React.useState(0); - const [authToken, setAuthTokenEphemeral] = React.useState(''); - const setAuthToken = authToken => { - setAuthTokenEphemeral(authToken); - setCookie('auth', authToken); - }; - const setDDMAddress = ddmAddress => { - setCookie('ddm-address', ddmAddress); - }; - - async function updateState() { - setState({ - datasets: await getDatasets(), - providers: await getProviders(), - replications: await getReplications(), - wallets: await getWallets(), - }); - } - - async function updateHealth() { - setHealth(await getHealth()); - } - - async function updateCommitHash() { - setCommitHash((await (await fetch('/api')).json()).commit_hash) - } - - function dismissTooltip() { - setAppTooltipState(0); - } - - React.useEffect(() => { - (async () => { - setAuthTokenEphemeral(getCookie('auth')); - - try { - if (!await checkAuth()) { - throw new Error(); - } - } catch { - setAuthTokenEphemeral(''); - return; - } - })(); - }, []); - - React.useEffect(() => { - if (authToken) { - updateState(); - updateHealth(); - updateCommitHash(); - } - }, [authToken]); - - if (!authToken) { - return ( - - ) - } - - return ( - setAppNavigationState(navigationStates.datasets)} - onClickProviders={() => setAppNavigationState(navigationStates.providers)} - onClickReplications={() => setAppNavigationState(navigationStates.replications)} - onNewDataset={() => setAppTooltipState(tooltipStates.newDataset)} - onAddProviders={() => setAppTooltipState(tooltipStates.addProvider)} - onAddReplication={() => setAppTooltipState(tooltipStates.addReplication)} - onClickWallets={() => setAppNavigationState(navigationStates.wallets)} - onAddWallet={() => setAppTooltipState(tooltipStates.addWallet)} - > - {appNavigationState === navigationStates.datasets && ( - setDatasetSearch(e.target.value)} - searchLabel="Search datasets" - placeholder="(example: university-bird-sounds)" - state={state} - onAttachContent={() => setAppTooltipState(tooltipStates.attachContent)} - selectedDataset={selectedDataset} - setSelectedDataset={setSelectedDataset} - /> - )} - {appNavigationState === navigationStates.providers && ( - setProviderSearch(e.target.value)} - providerLabel="Search providers" - placeholder="(example: f0123456)" - state={state} - updateState={updateState} - /> - )} - {appNavigationState === navigationStates.replications && ( - setReplicationSearch(e.target.value)} - searchLabel='Search replications' - placeholder='search any field (replication filtering is w.i.p.)' - selectedProvider={selectedProvider} - setSelectedProvider={() => { - alert('test'); - }} - selectedDataset={selectedDataset} - setSelectedDataset={() => { }} - state={state} - /> - )} - {appNavigationState === navigationStates.wallets && ( - setAppTooltipState(tooltipStates.associateWallet)} - setSelectedWallet={setSelectedWallet} - /> - )} - - {appTooltipState === tooltipStates.newDataset && ( - - )} - {appTooltipState === tooltipStates.addProvider && ( - - )} - {appTooltipState === tooltipStates.addWallet && ( - - )} - {appTooltipState === tooltipStates.attachContent && ( - - )} - {appTooltipState === tooltipStates.addReplication && ( - - )} - {appTooltipState === tooltipStates.associateWallet && ( - - )} - - ); -} diff --git a/components/DefaultLayout.tsx b/components/DefaultLayout.tsx index 9cfdf63..55fbf4a 100644 --- a/components/DefaultLayout.tsx +++ b/components/DefaultLayout.tsx @@ -3,48 +3,56 @@ import { navigationStates } from '@root/common/navigation'; import * as React from 'react'; -export default function DefaultLayout(props) { +export default function DefaultLayout(props: { + apps: string[], + onSwitchApp: (app: string) => void, + children: any, +}) { + let title = props.children?.find(child => child.type === AppTitle); + let version = props.children?.find(child => child.type === AppVersion); + let nav = props.children?.find(child => child.type === AppNav); + let body = props.children?.find(child => child.type === AppBody) + return (
-
{props.appTitle}
+
{title}
-
{props.appVersion}
+
{version}
- +
-
{props.children}
+
{body}
); } + +export function AppTitle(props) { + return <>{props.children}; +} + +export function AppVersion(props) { + return <>{props.children}; +} + +export function AppNav(props) { + return
{props.children}
; +} + +export function AppNavItem(props) { + return {props.children}; +} + +export function AppNavSubItem(props) { + return {props.children}; +} + +export function AppBody(props) { + return
{props.children}
; +} \ No newline at end of file diff --git a/components/scenes/SceneAuth.module.scss b/components/scenes/SceneAuth.module.scss deleted file mode 100644 index 0cd5b51..0000000 --- a/components/scenes/SceneAuth.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -.body { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); -} - diff --git a/components/scenes/SceneAuth.tsx b/components/scenes/SceneAuth.tsx deleted file mode 100644 index 260e76e..0000000 --- a/components/scenes/SceneAuth.tsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client'; - -import * as React from 'react'; -import { checkAuth, checkAuthFormat } from '@data/api'; - -import styles from './SceneAuth.module.scss'; - -import Button from '@components/Button'; -import Input from '@components/Input'; - -export default function SceneAuth(props) { - // Store a tmp auth token in the component so the main application auth token - // state doesn't update until the user submits the form - const [tmpAuthToken, setTmpAuthToken] = React.useState(props.authToken || ''); - const [tmpDDMAddress, setTmpDDMAddress] = React.useState(props.authToken || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:1314'); - const [loading, setLoading] = React.useState(false); - - async function onSubmit(e) { - e.preventDefault(); - - setLoading(true); - - try { - if (!await checkAuth(tmpAuthToken, tmpDDMAddress)) { - alert('Unauthorized token'); - return; - } - } catch (e) { - alert(e.toString()); - return; - } finally { - setLoading(false); - } - - props.setAuthToken(tmpAuthToken); - props.setDDMAddress(tmpDDMAddress); - } - - return ( -
-
- setTmpAuthToken(e.target.value)} /> - setTmpDDMAddress(e.target.value)} /> - -
-
- ) -} \ No newline at end of file diff --git a/components/scenes/SceneDatasets.module.scss b/components/scenes/SceneDatasets.module.scss deleted file mode 100644 index 6d86a06..0000000 --- a/components/scenes/SceneDatasets.module.scss +++ /dev/null @@ -1,2 +0,0 @@ -.body { -} diff --git a/components/scenes/SceneDatasets.tsx b/components/scenes/SceneDatasets.tsx deleted file mode 100644 index 14464a0..0000000 --- a/components/scenes/SceneDatasets.tsx +++ /dev/null @@ -1,71 +0,0 @@ -'use client'; - -import * as React from 'react'; -import * as Utilities from '@common/utilities'; - -import styles from './SceneDatasets.module.scss'; -import tableStyles from '@components/Table.module.scss'; - -import Input from '@components/Input'; -import LoadingIndicator from '@components/LoadingIndicator'; -import WalletRef from '@components/WalletRef'; - -export default function SceneDatasets(props) { - return (
- {props.state.datasets && -
- -
- Name - Size - Deals Made - Replication Quota - Duration - Unsealed - Indexed - Wallets -
- {props.state.datasets - .filter((dataset, i) => !props.search || dataset.name.includes(props.search)) - .map((dataset, i) => { - let progress = dataset.bytes_replicated.padded / dataset.bytes_total.padded / dataset.replication_quota; - if (Number.isNaN(progress)) progress = 0; - return ( -
-
- {dataset.name} - {Utilities.bytesToSize(dataset.bytes_total.raw)} - {dataset.count_replicated} - {dataset.replication_quota} - {dataset.deal_duration} days - {dataset.unsealed ? "true" : "false"} - {dataset.indexed ? "true" : "false"} -
{dataset.wallets?.map((wallet, i) => { - return
- })}
-
-
-
-
- {/*
➟ Make storage deals for this dataset
*/} -
{ - props.setSelectedDataset(dataset.name); - props.onAttachContent(); - }}>➟ Attach content
-
- ) - }) - } -
- } - {props.state.datasets === undefined && } -
); -} diff --git a/components/scenes/SceneProviders.module.scss b/components/scenes/SceneProviders.module.scss deleted file mode 100644 index 9e44ccf..0000000 --- a/components/scenes/SceneProviders.module.scss +++ /dev/null @@ -1,69 +0,0 @@ -@import '@components/Table.module.scss'; - -.secret { - position: relative; - padding: 4px; - margin: -4px; - width: fit-content; - font-size: 0.8rem; - - &:not(:hover) { - color: transparent; - background: var(--color-border); - - &::after { - content: "hover to reveal"; - position: absolute; - left: 4px; - color: var(--color-text); - text-transform: uppercase; - top: 50%; - transform: translateY(-50%); - } - } -} - -.columnProviderInfo { - @extend .column; - min-width: 16rem; -} - -.columnBytesReplicated { - @extend .column; - min-width: 16rem; -} - -.columnFlags { - @extend .column; - min-width: 14rem; -} - -.columnProviderKey { - @extend .column; - min-width: 26rem; -} - -.columnAllowedDatasets { - @extend .fluidColumn -} - -.providerCard { - margin: 0.5rem; - padding: 1.5rem; - border: 1px solid var(--color-border); -} - -.providerName { - font-size: 0.8em; - color: #7f7f7f; -} - -.copy { - color: var(--color-primary); - text-decoration: underline; - padding-left: 1rem; - - &:hover { - cursor: pointer; - } -} \ No newline at end of file diff --git a/components/scenes/SceneProviders.tsx b/components/scenes/SceneProviders.tsx deleted file mode 100644 index b081744..0000000 --- a/components/scenes/SceneProviders.tsx +++ /dev/null @@ -1,166 +0,0 @@ -'use client'; - -import * as React from 'react'; -import * as Utilities from '@common/utilities'; -import { updateProvider } from '@root/data/api'; - -import styles from './SceneProviders.module.scss'; -import tableStyles from '@components/Table.module.scss'; - -import Input from '@components/Input'; -import LoadingIndicator from '@components/LoadingIndicator'; -import ProviderRef from '@components/ProviderRef'; -import Button from '@components/Button'; -import TagSelect from '@components/TagSelect'; - -export default function SceneProviders(props) { - return ( -
- {props.state.providers && -
- -
- Provider Info - Bytes Replicated - Flags - Provider Key - Allowed Datasets -
- {props.state.providers - .filter( - (provider, i) => !props.search || provider.actor_id.includes(props.search) - ) - .map( - (provider, i) => { - return - } - ) - } -
- } - {props.state.providers === undefined && } -
- ); -} - -function ProviderCard(props) { - let provider = props.provider; - - let [editing, setEditing] = React.useState(false); - let [name, setName] = React.useState(provider.actor_name); - let [allowSelfService, setAllowSelfService] = React.useState(provider.allow_self_service); - let [saving, setSaving] = React.useState(false); - let [allowedDatasets, setAllowedDatasets] = React.useState(provider.allowed_datasets?.map((dataset, i) => dataset.name) || []); - - let datasetNames = props.state.datasets?.map((dataset, i) => dataset.name) || []; - - function cancelEdit() { - setAllowSelfService(provider.allow_self_service); - setName(provider.actor_name); - setAllowedDatasets(provider.allowed_datasets?.map((dataset, i) => dataset.name) || []); - setEditing(false); - } - - async function submitEdit() { - setSaving(true); - - try { - await updateProvider(provider.actor_id, name, allowSelfService, allowedDatasets); - setEditing(false); - } catch (e) { - alert('Saving provider failed: ' + e.toString()); - return; - } finally { - setSaving(false); - } - - props.updateState(); - } - - if (editing) return ( -
- - Rename {provider.actor_id}} - value={name} - onChange={e => setName(e.target.value)} - /> - - -
{Utilities.bytesToSize(provider.bytes_replicated.padded)} (padded)
-
{Utilities.bytesToSize(provider.bytes_replicated.raw)} (unpadded)
-
- - setAllowSelfService(e.target.checked)} /> - - - - - - - -
- ); - - return ( -
- -
{provider.actor_name || 'unnamed'}
- -
- -
{Utilities.bytesToSize(provider.bytes_replicated.padded)} (padded)
-
{Utilities.bytesToSize(provider.bytes_replicated.raw)} (unpadded)
-
- - - - - - - - -
- ); -} - -function ProviderKey(props) { - - const [copied, setCopied] = React.useState(false); - - function copy() { - navigator.clipboard.writeText(props.providerKey); - setCopied(true); - - setTimeout(() => setCopied(false), 3000); - } - - return ( - - {props.providerKey} - {copied ? 'copied ✓' : 'copy 📋'} - - ); -} \ No newline at end of file diff --git a/components/scenes/SceneReplications.module.scss b/components/scenes/SceneReplications.module.scss deleted file mode 100644 index 6d86a06..0000000 --- a/components/scenes/SceneReplications.module.scss +++ /dev/null @@ -1,2 +0,0 @@ -.body { -} diff --git a/components/scenes/SceneReplications.tsx b/components/scenes/SceneReplications.tsx deleted file mode 100644 index d4a6dd2..0000000 --- a/components/scenes/SceneReplications.tsx +++ /dev/null @@ -1,69 +0,0 @@ -'use client'; - -import * as React from 'react'; -import * as Utilities from '@common/utilities'; - -import styles from './SceneReplications.module.scss'; -import tableStyles from '@components/Table.module.scss'; - -import Input from '@components/Input'; -import LoadingIndicator from '@components/LoadingIndicator'; -import ProviderRef from '@components/ProviderRef'; - -export default function SceneReplications(props) { - return ( -
- {props.state.replications && -
- -
-
Dataset
-
Status
-
Provider ID
-
Self Service
-
Deal Time
-
Proposal CID
-
Piece CID (CommP)
-
Message
-
- {props.state.replications - .filter((replication, i) => { - return !props.search - || replication.status?.toLowerCase().includes(props.search.toLowerCase()) - || replication.deal_time?.toString().toLowerCase().includes(props.search.toLowerCase()) - || replication.provider_actor_id?.toLowerCase().includes(props.search.toLowerCase()) - || replication.proposal_cid?.toLowerCase().includes(props.search.toLowerCase()) - || replication.content_commp?.toLowerCase().includes(props.search.toLowerCase()) - || replication.delta_message?.toLowerCase().includes(props.search.toLowerCase()); - }) - .map((replication, i) => { - return ( -
-
-
{replication.content.dataset_name}
-
{replication.status}
-
-
{replication.is_self_service ? "true" : "false"}
-
{new Date(Date.parse(replication.deal_time)).toUTCString()}
-
{replication.proposal_cid}
-
{replication.content_commp}
-
{replication.delta_message}
-
-
- ) - }) - } -
- } - {props.state.replications === undefined && } -
- ); -} diff --git a/components/scenes/SceneWallets.module.scss b/components/scenes/SceneWallets.module.scss deleted file mode 100644 index 205744a..0000000 --- a/components/scenes/SceneWallets.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import '@components/Table.module.scss'; - -.columnAddress { - @extend .column; - min-width: 35rem; -} \ No newline at end of file diff --git a/components/scenes/SceneWallets.tsx b/components/scenes/SceneWallets.tsx deleted file mode 100644 index 9f8c38b..0000000 --- a/components/scenes/SceneWallets.tsx +++ /dev/null @@ -1,102 +0,0 @@ -'use client'; - -import styles from './SceneWallets.module.scss'; -import tableStyles from '@components/Table.module.scss'; - -import * as React from 'react'; -import * as Utilities from '@common/utilities'; - -import Input from '@components/Input'; -import LoadingIndicator from '@components/LoadingIndicator'; -import WalletRef from '@components/WalletRef'; -import TagSelect from '@components/TagSelect'; -import apiIndex from '@root/pages/api'; -import { associateWallet } from '@root/data/api'; -import Button from '../Button'; - -export default function SceneWallets(props) { - - return ( -
- {props.state.wallets && -
-
- Address - Filecoin Balance - Datacap Balance - Datasets -
- {props.state.wallets.map((wallet, i) => { - return ( -
- dataset.name)} - updateState={props.updateState} /> -
- ); - })} -
- } - {props.state.wallets === undefined && } -
- ) -} - -function WalletCard(props: { - wallet: any, - datasets: string[], - updateState: CallableFunction -}) { - let selectedDefault = () => props.wallet.datasets.map((dataset, i) => dataset.name); - - const [selected, setSelected] = React.useState(selectedDefault()); - - const [editing, setEditing] = React.useState(false); - const [saving, setSaving] = React.useState(false); - - function cancelEdit() { - setEditing(false); - - setSelected(selectedDefault()); - } - - async function submitEdit() { - setSaving(true); - - try { - await associateWallet(props.wallet.address, selected); - setEditing(false); - } catch (e) { - alert('Saving wallet failed: ' + e.toString()); - return; - } finally { - setSaving(false); - } - - props.updateState(); - } - - return ( -
- - - - {props.wallet.balance.balance_filecoin / 1000000000000000000} FIL - {Utilities.bytesToSize(props.wallet.balance.balance_datacap)} - - - - { - editing - ? <> - - - - : <> - - - } -
- ); -} \ No newline at end of file diff --git a/global.scss b/global.scss index bff5f0f..11518e4 100644 --- a/global.scss +++ b/global.scss @@ -135,6 +135,7 @@ body { ::-webkit-scrollbar { width: 8px; + height: 8px; background: var(--color-background); } } From b40abf144415b2dce0ffea616696e597d6bc9c53 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Tue, 9 May 2023 11:53:25 +0900 Subject: [PATCH 02/21] wip --- common/navigation.ts | 4 +- components/DefaultLayout.tsx | 9 +- components/Main.module.scss | 34 +++ components/Main.tsx | 19 ++ components/apps/ddm/DDM.tsx | 232 ++++++++++++++++++ components/apps/ddm/scenes/Auth.module.scss | 7 + components/apps/ddm/scenes/Auth.tsx | 48 ++++ .../apps/ddm/scenes/Datasets.module.scss | 2 + components/apps/ddm/scenes/Datasets.tsx | 71 ++++++ .../apps/ddm/scenes/Providers.module.scss | 69 ++++++ components/apps/ddm/scenes/Providers.tsx | 166 +++++++++++++ .../apps/ddm/scenes/Replications.module.scss | 2 + components/apps/ddm/scenes/Replications.tsx | 69 ++++++ .../apps/ddm/scenes/Wallets.module.scss | 6 + components/apps/ddm/scenes/Wallets.tsx | 101 ++++++++ 15 files changed, 836 insertions(+), 3 deletions(-) create mode 100644 components/Main.module.scss create mode 100644 components/Main.tsx create mode 100644 components/apps/ddm/DDM.tsx create mode 100644 components/apps/ddm/scenes/Auth.module.scss create mode 100644 components/apps/ddm/scenes/Auth.tsx create mode 100644 components/apps/ddm/scenes/Datasets.module.scss create mode 100644 components/apps/ddm/scenes/Datasets.tsx create mode 100644 components/apps/ddm/scenes/Providers.module.scss create mode 100644 components/apps/ddm/scenes/Providers.tsx create mode 100644 components/apps/ddm/scenes/Replications.module.scss create mode 100644 components/apps/ddm/scenes/Replications.tsx create mode 100644 components/apps/ddm/scenes/Wallets.module.scss create mode 100644 components/apps/ddm/scenes/Wallets.tsx diff --git a/common/navigation.ts b/common/navigation.ts index de6845f..1a4ca76 100644 --- a/common/navigation.ts +++ b/common/navigation.ts @@ -15,6 +15,6 @@ export const tooltipStates = { }; export const apps = { - ddm: 1, - ptolemy: 2, + ddm: 'ddm', + ptolemy: 'ptolemy', } \ No newline at end of file diff --git a/components/DefaultLayout.tsx b/components/DefaultLayout.tsx index 55fbf4a..e76065f 100644 --- a/components/DefaultLayout.tsx +++ b/components/DefaultLayout.tsx @@ -8,6 +8,8 @@ export default function DefaultLayout(props: { onSwitchApp: (app: string) => void, children: any, }) { + const [showApps, setShowApps] = React.useState(false); + let title = props.children?.find(child => child.type === AppTitle); let version = props.children?.find(child => child.type === AppVersion); let nav = props.children?.find(child => child.type === AppNav); @@ -17,7 +19,12 @@ export default function DefaultLayout(props: {
-
{title}
+
+ setShowApps(!showApps)}>{title} + {showApps &&
+ {props.apps.map((app, i) =>
props.onSwitchApp(app)}>{app}
)} +
} +
{version}
diff --git a/components/Main.module.scss b/components/Main.module.scss new file mode 100644 index 0000000..1fd8b0e --- /dev/null +++ b/components/Main.module.scss @@ -0,0 +1,34 @@ +.body { + display: flex; + flex-direction: column; +} + +.top { + width: 100%; + height: 4rem; + display: flex; + flex-direction: row; + + border-bottom: 1px solid var(--color-border); + margin-bottom: -1px; +} + +.bottom { + display: flex; + + flex-grow: 1; + min-height: 100vh; +} + +.left { + display: flex; + + width: 16rem; + height: inherit; + border-right: 1px solid var(--color-border); + margin-right: -1px; +} + +.right { + height: inherit; +} \ No newline at end of file diff --git a/components/Main.tsx b/components/Main.tsx new file mode 100644 index 0000000..7500dce --- /dev/null +++ b/components/Main.tsx @@ -0,0 +1,19 @@ +'use client'; + +import React from 'react'; +import { apps } from '@root/common/navigation'; + +import styles from './Main.module.scss'; + +import AppDDM from '@components/apps/ddm/DDM'; + +export default function Main(props) { + const [activeApp, setActiveApp] = React.useState(apps.ddm); + + switch (activeApp) { + case apps.ddm: + return + case apps.ptolemy: + // TODO + } +} \ No newline at end of file diff --git a/components/apps/ddm/DDM.tsx b/components/apps/ddm/DDM.tsx new file mode 100644 index 0000000..9d7c5d3 --- /dev/null +++ b/components/apps/ddm/DDM.tsx @@ -0,0 +1,232 @@ +'use client'; + +import * as React from 'react'; +import PackageJSON from '@root/package.json'; +import { navigationStates, tooltipStates } from '@common/navigation'; +import { getCookie, setCookie } from '@modules/cookies'; +import { associateWallet, checkAuth, getDatasets, getHealth, getProviders, getReplications, getWallets } from '@data/api'; + +import DefaultLayout, { AppBody, AppNav, AppNavItem, AppNavSubItem, AppTitle, AppVersion } from '@components/DefaultLayout'; + +import Datasets from '@root/components/apps/ddm/scenes/Datasets'; +import Providers from '@root/components/apps/ddm/scenes/Providers'; +import Replications from '@root/components/apps/ddm/scenes/Replications'; +import Wallets from '@components/apps/ddm/scenes/Wallets'; +import Auth from '@root/components/apps/ddm/scenes/Auth'; + +import FormUploadData from '@components/forms/FormUploadData'; +import FormAddWallet from '@components/forms/FormAddWallet'; +import FormAddProvider from '@components/forms/FormAddProvider'; +import FormAddReplication from '@components/forms/FormAddReplication'; +import FormNewDataset from '@components/forms/FormNewDataset'; +import FormAssociateWallet from '@components/forms/FormAssociateWallet'; + +export default function DDM(props) { + const [appNavigationState, setAppNavigationState] = React.useState(1); + const [datasetSearch, setDatasetSearch] = React.useState(''); + const [providerSearch, setProviderSearch] = React.useState(''); + const [replicationSearch, setReplicationSearch] = React.useState(''); + const [selectedProvider, setSelectedProvider] = React.useState(''); + const [selectedDataset, setSelectedDataset] = React.useState(''); + const [selectedWallet, setSelectedWallet] = React.useState(''); + const [state, setState] = React.useState({ + datasets: undefined, + providers: undefined, + replications: undefined, + wallets: undefined + }); + const [health, setHealth] = React.useState(undefined); + const [commitHash, setCommitHash] = React.useState(undefined); + const [appTooltipState, setAppTooltipState] = React.useState(0); + const [authToken, setAuthTokenEphemeral] = React.useState(''); + const setAuthToken = authToken => { + setAuthTokenEphemeral(authToken); + setCookie('auth', authToken); + }; + const setDDMAddress = ddmAddress => { + setCookie('ddm-address', ddmAddress); + }; + + async function updateState() { + setState({ + datasets: await getDatasets(), + providers: await getProviders(), + replications: await getReplications(), + wallets: await getWallets(), + }); + } + + async function updateHealth() { + setHealth(await getHealth()); + } + + async function updateCommitHash() { + setCommitHash((await (await fetch('/api')).json()).commit_hash) + } + + function dismissTooltip() { + setAppTooltipState(0); + } + + React.useEffect(() => { + (async () => { + setAuthTokenEphemeral(getCookie('auth')); + + try { + if (!await checkAuth()) { + throw new Error(); + } + } catch { + setAuthTokenEphemeral(''); + return; + } + })(); + }, []); + + React.useEffect(() => { + if (authToken) { + updateState(); + updateHealth(); + updateCommitHash(); + } + }, [authToken]); + + if (!authToken) { + return ( + + ) + } + + return ( + + Delta DM + {` + UUID: ${health?.uuid || 'not set'} + | + DDM Version: ${health?.ddm_info.version} (${health?.ddm_info.commit}) + | + Delta Version: ${health?.delta_info.version} (${health?.delta_info.commit}) + | + UI Version: ${PackageJSON.version} (${commitHash}) + `} + + setAppNavigationState(navigationStates.datasets)}> + Datasets {appNavigationState === navigationStates.datasets && '➝'} + + setAppTooltipState(tooltipStates.newDataset)}> + + New dataset + + setAppNavigationState(navigationStates.providers)}> + Providers {appNavigationState === navigationStates.providers && '➝'} + + setAppTooltipState(tooltipStates.addProvider)}> + + Add provider + + setAppNavigationState(navigationStates.replications)}> + Replications {appNavigationState === navigationStates.replications && '➝'} + + setAppTooltipState(tooltipStates.addReplication)}> + + Add replication + + setAppNavigationState(navigationStates.wallets)}> + Wallets {appNavigationState === navigationStates.wallets && '➝'} + + setAppTooltipState(tooltipStates.addWallet)}> + + Add wallet + + + + {appNavigationState === navigationStates.datasets && ( + setDatasetSearch(e.target.value)} + searchLabel="Search datasets" + placeholder="(example: university-bird-sounds)" + state={state} + onAttachContent={() => setAppTooltipState(tooltipStates.attachContent)} + selectedDataset={selectedDataset} + setSelectedDataset={setSelectedDataset} + /> + )} + {appNavigationState === navigationStates.providers && ( + setProviderSearch(e.target.value)} + providerLabel="Search providers" + placeholder="(example: f0123456)" + state={state} + updateState={updateState} + /> + )} + {appNavigationState === navigationStates.replications && ( + setReplicationSearch(e.target.value)} + searchLabel='Search replications' + placeholder='search any field (replication filtering is w.i.p.)' + selectedProvider={selectedProvider} + setSelectedProvider={() => { + alert('test'); + }} + selectedDataset={selectedDataset} + setSelectedDataset={() => { }} + state={state} + /> + )} + {appNavigationState === navigationStates.wallets && ( + setAppTooltipState(tooltipStates.associateWallet)} + setSelectedWallet={setSelectedWallet} + /> + )} + + {appTooltipState === tooltipStates.newDataset && ( + + )} + {appTooltipState === tooltipStates.addProvider && ( + + )} + {appTooltipState === tooltipStates.addWallet && ( + + )} + {appTooltipState === tooltipStates.attachContent && ( + + )} + {appTooltipState === tooltipStates.addReplication && ( + + )} + {appTooltipState === tooltipStates.associateWallet && ( + + )} + + + ) +} diff --git a/components/apps/ddm/scenes/Auth.module.scss b/components/apps/ddm/scenes/Auth.module.scss new file mode 100644 index 0000000..0cd5b51 --- /dev/null +++ b/components/apps/ddm/scenes/Auth.module.scss @@ -0,0 +1,7 @@ +.body { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} + diff --git a/components/apps/ddm/scenes/Auth.tsx b/components/apps/ddm/scenes/Auth.tsx new file mode 100644 index 0000000..8e57512 --- /dev/null +++ b/components/apps/ddm/scenes/Auth.tsx @@ -0,0 +1,48 @@ +'use client'; + +import * as React from 'react'; +import { checkAuth, checkAuthFormat } from '@data/api'; + +import styles from './Auth.module.scss'; + +import Button from '@components/Button'; +import Input from '@components/Input'; + +export default function Auth(props) { + // Store a tmp auth token in the component so the main application auth token + // state doesn't update until the user submits the form + const [tmpAuthToken, setTmpAuthToken] = React.useState(props.authToken || ''); + const [tmpDDMAddress, setTmpDDMAddress] = React.useState(props.authToken || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:1314'); + const [loading, setLoading] = React.useState(false); + + async function onSubmit(e) { + e.preventDefault(); + + setLoading(true); + + try { + if (!await checkAuth(tmpAuthToken, tmpDDMAddress)) { + alert('Unauthorized token'); + return; + } + } catch (e) { + alert(e.toString()); + return; + } finally { + setLoading(false); + } + + props.setAuthToken(tmpAuthToken); + props.setDDMAddress(tmpDDMAddress); + } + + return ( +
+
+ setTmpAuthToken(e.target.value)} /> + setTmpDDMAddress(e.target.value)} /> + +
+
+ ) +} \ No newline at end of file diff --git a/components/apps/ddm/scenes/Datasets.module.scss b/components/apps/ddm/scenes/Datasets.module.scss new file mode 100644 index 0000000..6d86a06 --- /dev/null +++ b/components/apps/ddm/scenes/Datasets.module.scss @@ -0,0 +1,2 @@ +.body { +} diff --git a/components/apps/ddm/scenes/Datasets.tsx b/components/apps/ddm/scenes/Datasets.tsx new file mode 100644 index 0000000..7c79351 --- /dev/null +++ b/components/apps/ddm/scenes/Datasets.tsx @@ -0,0 +1,71 @@ +'use client'; + +import * as React from 'react'; +import * as Utilities from '@common/utilities'; + +import styles from './Datasets.module.scss'; +import tableStyles from '@components/Table.module.scss'; + +import Input from '@components/Input'; +import LoadingIndicator from '@components/LoadingIndicator'; +import WalletRef from '@components/WalletRef'; + +export default function Datasets(props) { + return (
+ {props.state.datasets && +
+ +
+ Name + Size + Deals Made + Replication Quota + Duration + Unsealed + Indexed + Wallets +
+ {props.state.datasets + .filter((dataset, i) => !props.search || dataset.name.includes(props.search)) + .map((dataset, i) => { + let progress = dataset.bytes_replicated.padded / dataset.bytes_total.padded / dataset.replication_quota; + if (Number.isNaN(progress)) progress = 0; + return ( +
+
+ {dataset.name} + {Utilities.bytesToSize(dataset.bytes_total.raw)} + {dataset.count_replicated} + {dataset.replication_quota} + {dataset.deal_duration} days + {dataset.unsealed ? "true" : "false"} + {dataset.indexed ? "true" : "false"} +
{dataset.wallets?.map((wallet, i) => { + return
+ })}
+
+
+
+
+ {/*
➟ Make storage deals for this dataset
*/} +
{ + props.setSelectedDataset(dataset.name); + props.onAttachContent(); + }}>➟ Attach content
+
+ ) + }) + } +
+ } + {props.state.datasets === undefined && } +
); +} diff --git a/components/apps/ddm/scenes/Providers.module.scss b/components/apps/ddm/scenes/Providers.module.scss new file mode 100644 index 0000000..9e44ccf --- /dev/null +++ b/components/apps/ddm/scenes/Providers.module.scss @@ -0,0 +1,69 @@ +@import '@components/Table.module.scss'; + +.secret { + position: relative; + padding: 4px; + margin: -4px; + width: fit-content; + font-size: 0.8rem; + + &:not(:hover) { + color: transparent; + background: var(--color-border); + + &::after { + content: "hover to reveal"; + position: absolute; + left: 4px; + color: var(--color-text); + text-transform: uppercase; + top: 50%; + transform: translateY(-50%); + } + } +} + +.columnProviderInfo { + @extend .column; + min-width: 16rem; +} + +.columnBytesReplicated { + @extend .column; + min-width: 16rem; +} + +.columnFlags { + @extend .column; + min-width: 14rem; +} + +.columnProviderKey { + @extend .column; + min-width: 26rem; +} + +.columnAllowedDatasets { + @extend .fluidColumn +} + +.providerCard { + margin: 0.5rem; + padding: 1.5rem; + border: 1px solid var(--color-border); +} + +.providerName { + font-size: 0.8em; + color: #7f7f7f; +} + +.copy { + color: var(--color-primary); + text-decoration: underline; + padding-left: 1rem; + + &:hover { + cursor: pointer; + } +} \ No newline at end of file diff --git a/components/apps/ddm/scenes/Providers.tsx b/components/apps/ddm/scenes/Providers.tsx new file mode 100644 index 0000000..40eef28 --- /dev/null +++ b/components/apps/ddm/scenes/Providers.tsx @@ -0,0 +1,166 @@ +'use client'; + +import * as React from 'react'; +import * as Utilities from '@common/utilities'; +import { updateProvider } from '@root/data/api'; + +import styles from './Providers.module.scss'; +import tableStyles from '@components/Table.module.scss'; + +import Input from '@components/Input'; +import LoadingIndicator from '@components/LoadingIndicator'; +import ProviderRef from '@components/ProviderRef'; +import Button from '@components/Button'; +import TagSelect from '@components/TagSelect'; + +export default function Providers(props) { + return ( +
+ {props.state.providers && +
+ +
+ Provider Info + Bytes Replicated + Flags + Provider Key + Allowed Datasets +
+ {props.state.providers + .filter( + (provider, i) => !props.search || provider.actor_id.includes(props.search) + ) + .map( + (provider, i) => { + return + } + ) + } +
+ } + {props.state.providers === undefined && } +
+ ); +} + +function ProviderCard(props) { + let provider = props.provider; + + let [editing, setEditing] = React.useState(false); + let [name, setName] = React.useState(provider.actor_name); + let [allowSelfService, setAllowSelfService] = React.useState(provider.allow_self_service); + let [saving, setSaving] = React.useState(false); + let [allowedDatasets, setAllowedDatasets] = React.useState(provider.allowed_datasets?.map((dataset, i) => dataset.name) || []); + + let datasetNames = props.state.datasets?.map((dataset, i) => dataset.name) || []; + + function cancelEdit() { + setAllowSelfService(provider.allow_self_service); + setName(provider.actor_name); + setAllowedDatasets(provider.allowed_datasets?.map((dataset, i) => dataset.name) || []); + setEditing(false); + } + + async function submitEdit() { + setSaving(true); + + try { + await updateProvider(provider.actor_id, name, allowSelfService, allowedDatasets); + setEditing(false); + } catch (e) { + alert('Saving provider failed: ' + e.toString()); + return; + } finally { + setSaving(false); + } + + props.updateState(); + } + + if (editing) return ( +
+ + Rename {provider.actor_id}} + value={name} + onChange={e => setName(e.target.value)} + /> + + +
{Utilities.bytesToSize(provider.bytes_replicated.padded)} (padded)
+
{Utilities.bytesToSize(provider.bytes_replicated.raw)} (unpadded)
+
+ + setAllowSelfService(e.target.checked)} /> + + + + + + + +
+ ); + + return ( +
+ +
{provider.actor_name || 'unnamed'}
+ +
+ +
{Utilities.bytesToSize(provider.bytes_replicated.padded)} (padded)
+
{Utilities.bytesToSize(provider.bytes_replicated.raw)} (unpadded)
+
+ + + + + + + + +
+ ); +} + +function ProviderKey(props) { + + const [copied, setCopied] = React.useState(false); + + function copy() { + navigator.clipboard.writeText(props.providerKey); + setCopied(true); + + setTimeout(() => setCopied(false), 3000); + } + + return ( + + {props.providerKey} + {copied ? 'copied ✓' : 'copy 📋'} + + ); +} \ No newline at end of file diff --git a/components/apps/ddm/scenes/Replications.module.scss b/components/apps/ddm/scenes/Replications.module.scss new file mode 100644 index 0000000..6d86a06 --- /dev/null +++ b/components/apps/ddm/scenes/Replications.module.scss @@ -0,0 +1,2 @@ +.body { +} diff --git a/components/apps/ddm/scenes/Replications.tsx b/components/apps/ddm/scenes/Replications.tsx new file mode 100644 index 0000000..ddcd9a3 --- /dev/null +++ b/components/apps/ddm/scenes/Replications.tsx @@ -0,0 +1,69 @@ +'use client'; + +import * as React from 'react'; +import * as Utilities from '@common/utilities'; + +import styles from './Replications.module.scss'; +import tableStyles from '@components/Table.module.scss'; + +import Input from '@components/Input'; +import LoadingIndicator from '@components/LoadingIndicator'; +import ProviderRef from '@components/ProviderRef'; + +export default function Replications(props) { + return ( +
+ {props.state.replications && +
+ +
+
Dataset
+
Status
+
Provider ID
+
Self Service
+
Deal Time
+
Proposal CID
+
Piece CID (CommP)
+
Message
+
+ {props.state.replications + .filter((replication, i) => { + return !props.search + || replication.status?.toLowerCase().includes(props.search.toLowerCase()) + || replication.deal_time?.toString().toLowerCase().includes(props.search.toLowerCase()) + || replication.provider_actor_id?.toLowerCase().includes(props.search.toLowerCase()) + || replication.proposal_cid?.toLowerCase().includes(props.search.toLowerCase()) + || replication.content_commp?.toLowerCase().includes(props.search.toLowerCase()) + || replication.delta_message?.toLowerCase().includes(props.search.toLowerCase()); + }) + .map((replication, i) => { + return ( +
+
+
{replication.content.dataset_name}
+
{replication.status}
+
+
{replication.is_self_service ? "true" : "false"}
+
{new Date(Date.parse(replication.deal_time)).toUTCString()}
+
{replication.proposal_cid}
+
{replication.content_commp}
+
{replication.delta_message}
+
+
+ ) + }) + } +
+ } + {props.state.replications === undefined && } +
+ ); +} diff --git a/components/apps/ddm/scenes/Wallets.module.scss b/components/apps/ddm/scenes/Wallets.module.scss new file mode 100644 index 0000000..205744a --- /dev/null +++ b/components/apps/ddm/scenes/Wallets.module.scss @@ -0,0 +1,6 @@ +@import '@components/Table.module.scss'; + +.columnAddress { + @extend .column; + min-width: 35rem; +} \ No newline at end of file diff --git a/components/apps/ddm/scenes/Wallets.tsx b/components/apps/ddm/scenes/Wallets.tsx new file mode 100644 index 0000000..4c228a9 --- /dev/null +++ b/components/apps/ddm/scenes/Wallets.tsx @@ -0,0 +1,101 @@ +'use client'; + +import styles from './Wallets.module.scss'; +import tableStyles from '@components/Table.module.scss'; + +import * as React from 'react'; +import * as Utilities from '@common/utilities'; +import apiIndex from '@root/pages/api'; +import { associateWallet } from '@root/data/api'; + +import Input from '@components/Input'; +import LoadingIndicator from '@components/LoadingIndicator'; +import WalletRef from '@components/WalletRef'; +import TagSelect from '@components/TagSelect'; +import Button from '@components/Button'; + +export default function Wallets(props) { + return ( +
+ {props.state.wallets && +
+
+ Address + Filecoin Balance + Datacap Balance + Datasets +
+ {props.state.wallets.map((wallet, i) => { + return ( +
+ dataset.name)} + updateState={props.updateState} /> +
+ ); + })} +
+ } + {props.state.wallets === undefined && } +
+ ) +} + +function WalletCard(props: { + wallet: any, + datasets: string[], + updateState: CallableFunction +}) { + let selectedDefault = () => props.wallet.datasets.map((dataset, i) => dataset.name); + + const [selected, setSelected] = React.useState(selectedDefault()); + + const [editing, setEditing] = React.useState(false); + const [saving, setSaving] = React.useState(false); + + function cancelEdit() { + setEditing(false); + + setSelected(selectedDefault()); + } + + async function submitEdit() { + setSaving(true); + + try { + await associateWallet(props.wallet.address, selected); + setEditing(false); + } catch (e) { + alert('Saving wallet failed: ' + e.toString()); + return; + } finally { + setSaving(false); + } + + props.updateState(); + } + + return ( +
+ + + + {props.wallet.balance.balance_filecoin / 1000000000000000000} FIL + {Utilities.bytesToSize(props.wallet.balance.balance_datacap)} + + + + { + editing + ? <> + + + + : <> + + + } +
+ ); +} \ No newline at end of file From 08524401e87b135b1fd2e5d3f2a59073bf0f87be Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Wed, 10 May 2023 10:08:50 +0900 Subject: [PATCH 03/21] initial ptolemy page, fix render errors --- components/DefaultLayout.tsx | 19 ++++++++++++------- components/Input.tsx | 8 +++++++- components/Main.tsx | 6 ++++-- components/TagSelect.tsx | 1 + components/apps/ddm/DDM.tsx | 2 +- components/apps/ptolemy/Ptolemy.tsx | 18 ++++++++++++++++++ 6 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 components/apps/ptolemy/Ptolemy.tsx diff --git a/components/DefaultLayout.tsx b/components/DefaultLayout.tsx index e76065f..78f3f6e 100644 --- a/components/DefaultLayout.tsx +++ b/components/DefaultLayout.tsx @@ -5,25 +5,30 @@ import * as React from 'react'; export default function DefaultLayout(props: { apps: string[], + activeApp: string, onSwitchApp: (app: string) => void, children: any, }) { - const [showApps, setShowApps] = React.useState(false); - let title = props.children?.find(child => child.type === AppTitle); let version = props.children?.find(child => child.type === AppVersion); let nav = props.children?.find(child => child.type === AppNav); - let body = props.children?.find(child => child.type === AppBody) + let body = props.children?.find(child => child.type === AppBody); + + // NOTE(@elijaharita): this works right now because there are only 2 apps + // (ptolemy and ddm). It will need to be changed in the future if more apps + // are added. + function toggleApp() { + let otherApp = props.apps.filter(app => app != props.activeApp)[0]; + props.onSwitchApp(otherApp); + console.log('switching to app ' + otherApp); + } return (
- setShowApps(!showApps)}>{title} - {showApps &&
- {props.apps.map((app, i) =>
props.onSwitchApp(app)}>{app}
)} -
} + toggleApp()}>{title}
diff --git a/components/Input.tsx b/components/Input.tsx index d25d9ad..8a91510 100644 --- a/components/Input.tsx +++ b/components/Input.tsx @@ -12,12 +12,18 @@ export default function Input(props: { } & React.ComponentPropsWithRef<'input'>) { let id = (props.id ? props.id + '-' : '') + createSlug(props.label); + // Find custom props that should be forwarded to the input element by removing custom props + const filteredProps = Object.assign({}, props); + delete filteredProps.label; + delete filteredProps.inputClassName; + delete filteredProps.labelClassName; + return (
- +
); } diff --git a/components/Main.tsx b/components/Main.tsx index 7500dce..11178c0 100644 --- a/components/Main.tsx +++ b/components/Main.tsx @@ -6,14 +6,16 @@ import { apps } from '@root/common/navigation'; import styles from './Main.module.scss'; import AppDDM from '@components/apps/ddm/DDM'; +import AppPtolemy from '@components/apps/ptolemy/Ptolemy'; export default function Main(props) { const [activeApp, setActiveApp] = React.useState(apps.ddm); + const appNames = Object.keys(apps); switch (activeApp) { case apps.ddm: - return + return case apps.ptolemy: - // TODO + return } } \ No newline at end of file diff --git a/components/TagSelect.tsx b/components/TagSelect.tsx index db13c3b..63cbb1a 100644 --- a/components/TagSelect.tsx +++ b/components/TagSelect.tsx @@ -48,6 +48,7 @@ export default function TagSelect(props: { deselect(option))} + key={i} >{option} ) })} diff --git a/components/apps/ddm/DDM.tsx b/components/apps/ddm/DDM.tsx index 9d7c5d3..6d39b99 100644 --- a/components/apps/ddm/DDM.tsx +++ b/components/apps/ddm/DDM.tsx @@ -102,7 +102,7 @@ export default function DDM(props) { } return ( - + Delta DM {` UUID: ${health?.uuid || 'not set'} diff --git a/components/apps/ptolemy/Ptolemy.tsx b/components/apps/ptolemy/Ptolemy.tsx new file mode 100644 index 0000000..adbc9cd --- /dev/null +++ b/components/apps/ptolemy/Ptolemy.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; + +import DefaultLayout, { AppBody, AppNav, AppTitle, AppVersion } from '@components/DefaultLayout'; + +export default function Ptolemy(props) { + return ( + + Ptolemy + Vesion Placeholder + + Placeholder Nav + + + Placeholder Body + + + ) +} \ No newline at end of file From fac02ebe92ab1821ea713692f6a45a7147406da2 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Wed, 10 May 2023 10:14:03 +0900 Subject: [PATCH 04/21] persist active app with cookie --- components/Main.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/Main.tsx b/components/Main.tsx index 11178c0..e14676e 100644 --- a/components/Main.tsx +++ b/components/Main.tsx @@ -7,9 +7,14 @@ import styles from './Main.module.scss'; import AppDDM from '@components/apps/ddm/DDM'; import AppPtolemy from '@components/apps/ptolemy/Ptolemy'; +import { getCookie, setCookie } from '@root/modules/cookies'; export default function Main(props) { - const [activeApp, setActiveApp] = React.useState(apps.ddm); + const [activeApp, _setActiveApp] = React.useState(getCookie('active-app') || apps.ddm); + function setActiveApp(app: string) { + _setActiveApp(app); + setCookie('active-app', app); + } const appNames = Object.keys(apps); switch (activeApp) { From 71235afa7af140374a48983bc9f9c78a305b5e02 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Wed, 10 May 2023 10:35:05 +0900 Subject: [PATCH 05/21] move existing forms to ddm directory --- components/apps/ddm/DDM.tsx | 12 ++++++------ components/{ => apps/ddm}/forms/Form.module.scss | 0 .../{ => apps/ddm}/forms/FormAddProvider.module.scss | 0 components/{ => apps/ddm}/forms/FormAddProvider.tsx | 0 .../ddm}/forms/FormAddReplication.module.scss | 0 .../{ => apps/ddm}/forms/FormAddReplication.tsx | 0 .../{ => apps/ddm}/forms/FormAddWallet.module.scss | 0 components/{ => apps/ddm}/forms/FormAddWallet.tsx | 0 .../ddm}/forms/FormAssociateWallet.module.scss | 0 .../{ => apps/ddm}/forms/FormAssociateWallet.tsx | 0 .../{ => apps/ddm}/forms/FormNewDataset.module.scss | 0 components/{ => apps/ddm}/forms/FormNewDataset.tsx | 0 .../{ => apps/ddm}/forms/FormUploadData.module.scss | 0 components/{ => apps/ddm}/forms/FormUploadData.tsx | 0 components/apps/ptolemy/Ptolemy.tsx | 5 +++-- 15 files changed, 9 insertions(+), 8 deletions(-) rename components/{ => apps/ddm}/forms/Form.module.scss (100%) rename components/{ => apps/ddm}/forms/FormAddProvider.module.scss (100%) rename components/{ => apps/ddm}/forms/FormAddProvider.tsx (100%) rename components/{ => apps/ddm}/forms/FormAddReplication.module.scss (100%) rename components/{ => apps/ddm}/forms/FormAddReplication.tsx (100%) rename components/{ => apps/ddm}/forms/FormAddWallet.module.scss (100%) rename components/{ => apps/ddm}/forms/FormAddWallet.tsx (100%) rename components/{ => apps/ddm}/forms/FormAssociateWallet.module.scss (100%) rename components/{ => apps/ddm}/forms/FormAssociateWallet.tsx (100%) rename components/{ => apps/ddm}/forms/FormNewDataset.module.scss (100%) rename components/{ => apps/ddm}/forms/FormNewDataset.tsx (100%) rename components/{ => apps/ddm}/forms/FormUploadData.module.scss (100%) rename components/{ => apps/ddm}/forms/FormUploadData.tsx (100%) diff --git a/components/apps/ddm/DDM.tsx b/components/apps/ddm/DDM.tsx index 6d39b99..26c41ca 100644 --- a/components/apps/ddm/DDM.tsx +++ b/components/apps/ddm/DDM.tsx @@ -14,12 +14,12 @@ import Replications from '@root/components/apps/ddm/scenes/Replications'; import Wallets from '@components/apps/ddm/scenes/Wallets'; import Auth from '@root/components/apps/ddm/scenes/Auth'; -import FormUploadData from '@components/forms/FormUploadData'; -import FormAddWallet from '@components/forms/FormAddWallet'; -import FormAddProvider from '@components/forms/FormAddProvider'; -import FormAddReplication from '@components/forms/FormAddReplication'; -import FormNewDataset from '@components/forms/FormNewDataset'; -import FormAssociateWallet from '@components/forms/FormAssociateWallet'; +import FormUploadData from '@root/components/apps/ddm/forms/FormUploadData'; +import FormAddWallet from '@root/components/apps/ddm/forms/FormAddWallet'; +import FormAddProvider from '@root/components/apps/ddm/forms/FormAddProvider'; +import FormAddReplication from '@root/components/apps/ddm/forms/FormAddReplication'; +import FormNewDataset from '@root/components/apps/ddm/forms/FormNewDataset'; +import FormAssociateWallet from '@root/components/apps/ddm/forms/FormAssociateWallet'; export default function DDM(props) { const [appNavigationState, setAppNavigationState] = React.useState(1); diff --git a/components/forms/Form.module.scss b/components/apps/ddm/forms/Form.module.scss similarity index 100% rename from components/forms/Form.module.scss rename to components/apps/ddm/forms/Form.module.scss diff --git a/components/forms/FormAddProvider.module.scss b/components/apps/ddm/forms/FormAddProvider.module.scss similarity index 100% rename from components/forms/FormAddProvider.module.scss rename to components/apps/ddm/forms/FormAddProvider.module.scss diff --git a/components/forms/FormAddProvider.tsx b/components/apps/ddm/forms/FormAddProvider.tsx similarity index 100% rename from components/forms/FormAddProvider.tsx rename to components/apps/ddm/forms/FormAddProvider.tsx diff --git a/components/forms/FormAddReplication.module.scss b/components/apps/ddm/forms/FormAddReplication.module.scss similarity index 100% rename from components/forms/FormAddReplication.module.scss rename to components/apps/ddm/forms/FormAddReplication.module.scss diff --git a/components/forms/FormAddReplication.tsx b/components/apps/ddm/forms/FormAddReplication.tsx similarity index 100% rename from components/forms/FormAddReplication.tsx rename to components/apps/ddm/forms/FormAddReplication.tsx diff --git a/components/forms/FormAddWallet.module.scss b/components/apps/ddm/forms/FormAddWallet.module.scss similarity index 100% rename from components/forms/FormAddWallet.module.scss rename to components/apps/ddm/forms/FormAddWallet.module.scss diff --git a/components/forms/FormAddWallet.tsx b/components/apps/ddm/forms/FormAddWallet.tsx similarity index 100% rename from components/forms/FormAddWallet.tsx rename to components/apps/ddm/forms/FormAddWallet.tsx diff --git a/components/forms/FormAssociateWallet.module.scss b/components/apps/ddm/forms/FormAssociateWallet.module.scss similarity index 100% rename from components/forms/FormAssociateWallet.module.scss rename to components/apps/ddm/forms/FormAssociateWallet.module.scss diff --git a/components/forms/FormAssociateWallet.tsx b/components/apps/ddm/forms/FormAssociateWallet.tsx similarity index 100% rename from components/forms/FormAssociateWallet.tsx rename to components/apps/ddm/forms/FormAssociateWallet.tsx diff --git a/components/forms/FormNewDataset.module.scss b/components/apps/ddm/forms/FormNewDataset.module.scss similarity index 100% rename from components/forms/FormNewDataset.module.scss rename to components/apps/ddm/forms/FormNewDataset.module.scss diff --git a/components/forms/FormNewDataset.tsx b/components/apps/ddm/forms/FormNewDataset.tsx similarity index 100% rename from components/forms/FormNewDataset.tsx rename to components/apps/ddm/forms/FormNewDataset.tsx diff --git a/components/forms/FormUploadData.module.scss b/components/apps/ddm/forms/FormUploadData.module.scss similarity index 100% rename from components/forms/FormUploadData.module.scss rename to components/apps/ddm/forms/FormUploadData.module.scss diff --git a/components/forms/FormUploadData.tsx b/components/apps/ddm/forms/FormUploadData.tsx similarity index 100% rename from components/forms/FormUploadData.tsx rename to components/apps/ddm/forms/FormUploadData.tsx diff --git a/components/apps/ptolemy/Ptolemy.tsx b/components/apps/ptolemy/Ptolemy.tsx index adbc9cd..8a374b6 100644 --- a/components/apps/ptolemy/Ptolemy.tsx +++ b/components/apps/ptolemy/Ptolemy.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import DefaultLayout, { AppBody, AppNav, AppTitle, AppVersion } from '@components/DefaultLayout'; +import DefaultLayout, { AppBody, AppNav, AppNavItem, AppNavSubItem, AppTitle, AppVersion } from '@components/DefaultLayout'; export default function Ptolemy(props) { return ( @@ -8,7 +8,8 @@ export default function Ptolemy(props) { Ptolemy Vesion Placeholder - Placeholder Nav + Jobs + + Create job Placeholder Body From 23f8f7c24b52c823eb52b8e237594bdabcbda174 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Wed, 10 May 2023 12:27:40 +0900 Subject: [PATCH 06/21] new modal component --- components/DefaultLayout.module.scss | 2 - components/Modal.module.scss | 33 +++++++++ components/Modal.tsx | 72 +++++++++++++++++++ components/apps/ddm/forms/FormAddProvider.tsx | 8 +-- components/apps/ptolemy/Ptolemy.tsx | 24 ++++++- 5 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 components/Modal.module.scss create mode 100644 components/Modal.tsx diff --git a/components/DefaultLayout.module.scss b/components/DefaultLayout.module.scss index 9dc288c..b38344b 100644 --- a/components/DefaultLayout.module.scss +++ b/components/DefaultLayout.module.scss @@ -34,7 +34,6 @@ width: 100%; min-width: 10%; border-left: 1px solid var(--color-border); - position: relative; } .right { @@ -42,7 +41,6 @@ min-width: 10%; min-height: calc(100vh - 56px); border-left: 1px solid var(--color-border); - position: relative; } .appTitle { diff --git a/components/Modal.module.scss b/components/Modal.module.scss new file mode 100644 index 0000000..d494960 --- /dev/null +++ b/components/Modal.module.scss @@ -0,0 +1,33 @@ +.body { + position: absolute; + border: 1px solid var(--color-border); + background: var(--color-background); + border-radius: 0.5rem; + transform: translateY(-50%); + margin-left: 1rem; + box-shadow: 0px 0px 6px 4px #0004; + padding: 1rem; + z-index: 100; + opacity: 100%; + transition: 0.15s; + + &::before { + content: ""; + display: block; + position: fixed; + width: 8px; + height: 8px; + left: 0; + top: 50%; + transform: translate(-50%, -50%) rotate(45deg); + background: var(--color-background); + border-left: 1px solid var(--color-border); + border-bottom: 1px solid var(--color-border); + } +} + +.bodyHidden { + @extend .body; + + opacity: 0%; +} \ No newline at end of file diff --git a/components/Modal.tsx b/components/Modal.tsx new file mode 100644 index 0000000..53378eb --- /dev/null +++ b/components/Modal.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; + +import styles from './Modal.module.scss'; + +// The `onClose` function will pass the provided `id` back to the caller so it +// can be compared to avoid the case of one modal closing causing another one to +// close. +export default function Modal(props: { + modalID?: any, + anchor: React.RefObject, + children: React.ReactNode, + onClose: (modalID?: any) => void, +}) { + const fadeSeconds = 0.15; + + const ref = React.useRef(null); + + const [modalPos, setModalPos] = React.useState(null); + const [hidden, setHidden] = React.useState(true); + + function updateModalPos(node) { + let anchorRect = node.getBoundingClientRect(); + let anchorOffsetParentRect = node.offsetParent.getBoundingClientRect(); + let left = anchorRect.left - anchorOffsetParentRect.left + anchorRect.width; + let top = anchorRect.top - anchorOffsetParentRect.top + anchorRect.height * 0.5; + setModalPos({ x: left, y: top }); + } + + React.useEffect(() => { + function handleClickOutside(event) { + console.log('handle click'); + + if (ref.current && !ref.current.contains(event.target)) { + event.stopPropagation(); + + setHidden(true); + + setTimeout(() => { + props.onClose(props.modalID); + }, fadeSeconds * 1000); + } + } + + updateModalPos(props.anchor.current); + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [ref]); + + React.useEffect(() => { + setTimeout(e => setHidden(false), 10); + }, []); + + if (modalPos == null) { + return null; + } + + return ( +
+ {props.children} +
+ ); +} \ No newline at end of file diff --git a/components/apps/ddm/forms/FormAddProvider.tsx b/components/apps/ddm/forms/FormAddProvider.tsx index 0cad91d..557c269 100644 --- a/components/apps/ddm/forms/FormAddProvider.tsx +++ b/components/apps/ddm/forms/FormAddProvider.tsx @@ -10,11 +10,11 @@ import Feedback from '@components/Feedback'; export default function FormAddProvider(props) { - let [providerID, setProviderID] = React.useState(''); - let [providerName, setProviderName] = React.useState(''); + const [providerID, setProviderID] = React.useState(''); + const [providerName, setProviderName] = React.useState(''); - let [loading, setLoading] = React.useState(false); - let [feedback, setFeedback] = React.useState(); + const [loading, setLoading] = React.useState(false); + const [feedback, setFeedback] = React.useState(); async function onSubmit(e) { e.preventDefault(); diff --git a/components/apps/ptolemy/Ptolemy.tsx b/components/apps/ptolemy/Ptolemy.tsx index 8a374b6..74f8184 100644 --- a/components/apps/ptolemy/Ptolemy.tsx +++ b/components/apps/ptolemy/Ptolemy.tsx @@ -1,18 +1,38 @@ import * as React from 'react'; import DefaultLayout, { AppBody, AppNav, AppNavItem, AppNavSubItem, AppTitle, AppVersion } from '@components/DefaultLayout'; +import Modal from '@components/Modal'; + +const formStates = { + none: 0, + createJob: 1, +} export default function Ptolemy(props) { + const [formState, setFormState] = React.useState(formStates.none); + + const createJobButton = React.useRef(null); + + function onClose(modalID) { + if (modalID === formState) { + setFormState(formStates.none); + } + } + return ( Ptolemy Vesion Placeholder Jobs - + Create job + setFormState(formStates.createJob)}> + + Create job + + {formState === formStates.createJob && +
test
+
}
- Placeholder Body
) From 8bb78fca7b249cd9b403d2232b40dccab8314be6 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Wed, 10 May 2023 12:42:27 +0900 Subject: [PATCH 07/21] beginning of job creation modal --- components/FileSelect.tsx | 7 +++++- .../{apps/ddm/forms => }/Form.module.scss | 0 components/Modal.module.scss | 5 ++-- .../ddm/forms/FormAddProvider.module.scss | 2 +- .../ddm/forms/FormAddReplication.module.scss | 2 +- .../apps/ddm/forms/FormAddWallet.module.scss | 2 +- .../ddm/forms/FormAssociateWallet.module.scss | 2 +- .../apps/ddm/forms/FormNewDataset.module.scss | 2 +- .../apps/ddm/forms/FormUploadData.module.scss | 2 +- components/apps/ptolemy/Ptolemy.tsx | 25 ++++++++++++++++++- 10 files changed, 39 insertions(+), 10 deletions(-) rename components/{apps/ddm/forms => }/Form.module.scss (100%) diff --git a/components/FileSelect.tsx b/components/FileSelect.tsx index ce8bfb0..c410a22 100644 --- a/components/FileSelect.tsx +++ b/components/FileSelect.tsx @@ -2,7 +2,12 @@ import * as React from 'react'; import FileList from '@components/FileList'; -export default function FileSelect(props) { +export default function FileSelect(props: { + label: string, + root: string, + onSelectFile: (path: string) => void, + onChangeRoot: (path: string) => void, +}) { const [root, setRoot] = React.useState('/home/elijah'); return ( diff --git a/components/apps/ddm/forms/Form.module.scss b/components/Form.module.scss similarity index 100% rename from components/apps/ddm/forms/Form.module.scss rename to components/Form.module.scss diff --git a/components/Modal.module.scss b/components/Modal.module.scss index d494960..1ceb82e 100644 --- a/components/Modal.module.scss +++ b/components/Modal.module.scss @@ -4,12 +4,12 @@ background: var(--color-background); border-radius: 0.5rem; transform: translateY(-50%); - margin-left: 1rem; + margin-left: 2rem; box-shadow: 0px 0px 6px 4px #0004; - padding: 1rem; z-index: 100; opacity: 100%; transition: 0.15s; + padding: 2rem; &::before { content: ""; @@ -30,4 +30,5 @@ @extend .body; opacity: 0%; + margin-left: 1rem; } \ No newline at end of file diff --git a/components/apps/ddm/forms/FormAddProvider.module.scss b/components/apps/ddm/forms/FormAddProvider.module.scss index 868044b..d94c873 100644 --- a/components/apps/ddm/forms/FormAddProvider.module.scss +++ b/components/apps/ddm/forms/FormAddProvider.module.scss @@ -1,4 +1,4 @@ -@import 'Form.module.scss'; +@import '@components/Form.module.scss'; .body { top: 170px; diff --git a/components/apps/ddm/forms/FormAddReplication.module.scss b/components/apps/ddm/forms/FormAddReplication.module.scss index 2c8ec02..9cc69b5 100644 --- a/components/apps/ddm/forms/FormAddReplication.module.scss +++ b/components/apps/ddm/forms/FormAddReplication.module.scss @@ -1 +1 @@ -@import 'Form.module.scss'; \ No newline at end of file +@import '@components/Form.module.scss'; \ No newline at end of file diff --git a/components/apps/ddm/forms/FormAddWallet.module.scss b/components/apps/ddm/forms/FormAddWallet.module.scss index 546372e..ee10736 100644 --- a/components/apps/ddm/forms/FormAddWallet.module.scss +++ b/components/apps/ddm/forms/FormAddWallet.module.scss @@ -1,4 +1,4 @@ -@import 'Form.module.scss'; +@import '@components/Form.module.scss'; .body { top: 270px; diff --git a/components/apps/ddm/forms/FormAssociateWallet.module.scss b/components/apps/ddm/forms/FormAssociateWallet.module.scss index ebe5f2d..0fcf05a 100644 --- a/components/apps/ddm/forms/FormAssociateWallet.module.scss +++ b/components/apps/ddm/forms/FormAssociateWallet.module.scss @@ -1,4 +1,4 @@ -@import 'Form.module.scss'; +@import '@components/Form.module.scss'; .body { width: 350px; diff --git a/components/apps/ddm/forms/FormNewDataset.module.scss b/components/apps/ddm/forms/FormNewDataset.module.scss index 2c8ec02..9cc69b5 100644 --- a/components/apps/ddm/forms/FormNewDataset.module.scss +++ b/components/apps/ddm/forms/FormNewDataset.module.scss @@ -1 +1 @@ -@import 'Form.module.scss'; \ No newline at end of file +@import '@components/Form.module.scss'; \ No newline at end of file diff --git a/components/apps/ddm/forms/FormUploadData.module.scss b/components/apps/ddm/forms/FormUploadData.module.scss index 844f3a5..89a537b 100644 --- a/components/apps/ddm/forms/FormUploadData.module.scss +++ b/components/apps/ddm/forms/FormUploadData.module.scss @@ -1,4 +1,4 @@ -@import 'Form.module.scss'; +@import '@components/Form.module.scss'; .upload { // border: 2px dotted var(--color-border); diff --git a/components/apps/ptolemy/Ptolemy.tsx b/components/apps/ptolemy/Ptolemy.tsx index 74f8184..36517dd 100644 --- a/components/apps/ptolemy/Ptolemy.tsx +++ b/components/apps/ptolemy/Ptolemy.tsx @@ -1,7 +1,12 @@ import * as React from 'react'; +import styles from '@components/Form.module.scss'; + import DefaultLayout, { AppBody, AppNav, AppNavItem, AppNavSubItem, AppTitle, AppVersion } from '@components/DefaultLayout'; import Modal from '@components/Modal'; +import Input from '@components/Input'; +import Select from '@components/Select'; +import FileSelect from '@components/FileSelect'; const formStates = { none: 0, @@ -29,7 +34,25 @@ export default function Ptolemy(props) { + Create job {formState === formStates.createJob && -
test
+

Create Job

+
+ +
+
+ +
+
+ +
} From 744d770397cd8d03514d318be9e84fa7309f789b Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Wed, 10 May 2023 13:01:39 +0900 Subject: [PATCH 08/21] start to populate job creation modal --- components/FileList.tsx | 26 +++++++++++++++++++++++++- components/FileSelect.tsx | 10 +++++----- components/Modal.module.scss | 4 ++-- components/apps/ptolemy/Ptolemy.tsx | 8 ++++---- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/components/FileList.tsx b/components/FileList.tsx index 6ac81d7..a1261f5 100644 --- a/components/FileList.tsx +++ b/components/FileList.tsx @@ -5,6 +5,8 @@ import styles from './FileList.module.scss'; export default function FileList(props) { const [files, setFiles] = React.useState([]); + const [error, setError] = React.useState(null); + const [loading, setLoading] = React.useState(true); React.useEffect(() => { (async () => { @@ -28,11 +30,33 @@ export default function FileList(props) { setFiles(files); } catch (e) { console.log(e); - alert(e.toString()); + setError('Failed to get directory'); + } finally { + setLoading(false); } })() }, [props.root]); + if (loading) { + return ( +
+
+ Loading... +
+
+ ) + } + + if (error != null) { + return ( +
+
+ {error} +
+
+ ) + } + return (
{files.map((file) => ( diff --git a/components/FileSelect.tsx b/components/FileSelect.tsx index c410a22..ce78f85 100644 --- a/components/FileSelect.tsx +++ b/components/FileSelect.tsx @@ -3,12 +3,12 @@ import * as React from 'react'; import FileList from '@components/FileList'; export default function FileSelect(props: { - label: string, - root: string, - onSelectFile: (path: string) => void, - onChangeRoot: (path: string) => void, + label?: string, + root?: string, + onSelectFile?: (path: string) => void, + onChangeRoot?: (path: string) => void, }) { - const [root, setRoot] = React.useState('/home/elijah'); + const [root, setRoot] = React.useState('/'); return ( diff --git a/components/Modal.module.scss b/components/Modal.module.scss index 1ceb82e..6fe6e09 100644 --- a/components/Modal.module.scss +++ b/components/Modal.module.scss @@ -3,7 +3,7 @@ border: 1px solid var(--color-border); background: var(--color-background); border-radius: 0.5rem; - transform: translateY(-50%); + transform: translateY(-3rem); margin-left: 2rem; box-shadow: 0px 0px 6px 4px #0004; z-index: 100; @@ -13,12 +13,12 @@ &::before { content: ""; + margin-top: 1rem; display: block; position: fixed; width: 8px; height: 8px; left: 0; - top: 50%; transform: translate(-50%, -50%) rotate(45deg); background: var(--color-background); border-left: 1px solid var(--color-border); diff --git a/components/apps/ptolemy/Ptolemy.tsx b/components/apps/ptolemy/Ptolemy.tsx index 36517dd..2ec485b 100644 --- a/components/apps/ptolemy/Ptolemy.tsx +++ b/components/apps/ptolemy/Ptolemy.tsx @@ -39,16 +39,16 @@ export default function Ptolemy(props) {
-
- {/* */} +
- {/* */} +
- +
From 3f3face0fb33e41c9812e00385f8b55618c713e3 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Thu, 11 May 2023 10:26:26 +0900 Subject: [PATCH 09/21] add slider component --- components/apps/ddm/forms/FormAddProvider.tsx | 2 +- .../apps/ddm/forms/FormAddReplication.tsx | 2 +- components/apps/ddm/forms/FormNewDataset.tsx | 2 +- components/apps/ddm/scenes/Auth.tsx | 2 +- components/apps/ddm/scenes/Datasets.tsx | 2 +- components/apps/ddm/scenes/Providers.tsx | 2 +- components/apps/ddm/scenes/Replications.tsx | 2 +- components/apps/ddm/scenes/Wallets.tsx | 2 +- components/apps/ptolemy/Ptolemy.tsx | 5 ++- components/{ => basic}/Input.module.scss | 9 +---- components/{ => basic}/Input.tsx | 4 +- components/basic/Label.module.scss | 6 +++ components/basic/Slider.module.scss | 33 +++++++++++++++++ components/basic/Slider.tsx | 37 +++++++++++++++++++ 14 files changed, 91 insertions(+), 19 deletions(-) rename components/{ => basic}/Input.module.scss (93%) rename components/{ => basic}/Input.tsx (94%) create mode 100644 components/basic/Label.module.scss create mode 100644 components/basic/Slider.module.scss create mode 100644 components/basic/Slider.tsx diff --git a/components/apps/ddm/forms/FormAddProvider.tsx b/components/apps/ddm/forms/FormAddProvider.tsx index 557c269..f1a9e89 100644 --- a/components/apps/ddm/forms/FormAddProvider.tsx +++ b/components/apps/ddm/forms/FormAddProvider.tsx @@ -5,7 +5,7 @@ import styles from './FormAddProvider.module.scss'; import Dismissible from '@components/Dismissible'; import Button from '@components/Button'; -import Input from '@components/Input'; +import Input from '@components/basic/Input'; import Feedback from '@components/Feedback'; export default function FormAddProvider(props) { diff --git a/components/apps/ddm/forms/FormAddReplication.tsx b/components/apps/ddm/forms/FormAddReplication.tsx index 420a5f7..c2faee8 100644 --- a/components/apps/ddm/forms/FormAddReplication.tsx +++ b/components/apps/ddm/forms/FormAddReplication.tsx @@ -7,7 +7,7 @@ import styles from './FormAddReplication.module.scss'; import Dismissible from '@components/Dismissible'; import Button from '@components/Button'; -import Input from 'components/Input'; +import Input from 'components/basic/Input'; import ProviderSelect from '@components/ProviderSelect'; import DatasetSelect from '@components/DatasetSelect'; import Feedback from '@components/Feedback'; diff --git a/components/apps/ddm/forms/FormNewDataset.tsx b/components/apps/ddm/forms/FormNewDataset.tsx index 5ae4e21..e8f7ef0 100644 --- a/components/apps/ddm/forms/FormNewDataset.tsx +++ b/components/apps/ddm/forms/FormNewDataset.tsx @@ -7,7 +7,7 @@ import { createSlug } from '@root/common/utilities'; import styles from './FormNewDataset.module.scss'; import Dismissible from '@components/Dismissible'; -import Input from '@components/Input'; +import Input from '@components/basic/Input'; import Button from '@components/Button'; import Feedback from '@components/Feedback'; diff --git a/components/apps/ddm/scenes/Auth.tsx b/components/apps/ddm/scenes/Auth.tsx index 8e57512..2edab6d 100644 --- a/components/apps/ddm/scenes/Auth.tsx +++ b/components/apps/ddm/scenes/Auth.tsx @@ -6,7 +6,7 @@ import { checkAuth, checkAuthFormat } from '@data/api'; import styles from './Auth.module.scss'; import Button from '@components/Button'; -import Input from '@components/Input'; +import Input from '@components/basic/Input'; export default function Auth(props) { // Store a tmp auth token in the component so the main application auth token diff --git a/components/apps/ddm/scenes/Datasets.tsx b/components/apps/ddm/scenes/Datasets.tsx index 7c79351..84ea863 100644 --- a/components/apps/ddm/scenes/Datasets.tsx +++ b/components/apps/ddm/scenes/Datasets.tsx @@ -6,7 +6,7 @@ import * as Utilities from '@common/utilities'; import styles from './Datasets.module.scss'; import tableStyles from '@components/Table.module.scss'; -import Input from '@components/Input'; +import Input from '@components/basic/Input'; import LoadingIndicator from '@components/LoadingIndicator'; import WalletRef from '@components/WalletRef'; diff --git a/components/apps/ddm/scenes/Providers.tsx b/components/apps/ddm/scenes/Providers.tsx index 40eef28..45f24c6 100644 --- a/components/apps/ddm/scenes/Providers.tsx +++ b/components/apps/ddm/scenes/Providers.tsx @@ -7,7 +7,7 @@ import { updateProvider } from '@root/data/api'; import styles from './Providers.module.scss'; import tableStyles from '@components/Table.module.scss'; -import Input from '@components/Input'; +import Input from '@components/basic/Input'; import LoadingIndicator from '@components/LoadingIndicator'; import ProviderRef from '@components/ProviderRef'; import Button from '@components/Button'; diff --git a/components/apps/ddm/scenes/Replications.tsx b/components/apps/ddm/scenes/Replications.tsx index ddcd9a3..5a95ac5 100644 --- a/components/apps/ddm/scenes/Replications.tsx +++ b/components/apps/ddm/scenes/Replications.tsx @@ -6,7 +6,7 @@ import * as Utilities from '@common/utilities'; import styles from './Replications.module.scss'; import tableStyles from '@components/Table.module.scss'; -import Input from '@components/Input'; +import Input from '@components/basic/Input'; import LoadingIndicator from '@components/LoadingIndicator'; import ProviderRef from '@components/ProviderRef'; diff --git a/components/apps/ddm/scenes/Wallets.tsx b/components/apps/ddm/scenes/Wallets.tsx index 4c228a9..a68609c 100644 --- a/components/apps/ddm/scenes/Wallets.tsx +++ b/components/apps/ddm/scenes/Wallets.tsx @@ -8,7 +8,7 @@ import * as Utilities from '@common/utilities'; import apiIndex from '@root/pages/api'; import { associateWallet } from '@root/data/api'; -import Input from '@components/Input'; +import Input from '@components/basic/Input'; import LoadingIndicator from '@components/LoadingIndicator'; import WalletRef from '@components/WalletRef'; import TagSelect from '@components/TagSelect'; diff --git a/components/apps/ptolemy/Ptolemy.tsx b/components/apps/ptolemy/Ptolemy.tsx index 2ec485b..8522f36 100644 --- a/components/apps/ptolemy/Ptolemy.tsx +++ b/components/apps/ptolemy/Ptolemy.tsx @@ -4,9 +4,10 @@ import styles from '@components/Form.module.scss'; import DefaultLayout, { AppBody, AppNav, AppNavItem, AppNavSubItem, AppTitle, AppVersion } from '@components/DefaultLayout'; import Modal from '@components/Modal'; -import Input from '@components/Input'; +import Input from '@components/basic/Input'; import Select from '@components/Select'; import FileSelect from '@components/FileSelect'; +import Slider from '@components/basic/Slider'; const formStates = { none: 0, @@ -51,7 +52,7 @@ export default function Ptolemy(props) {
- +
} diff --git a/components/Input.module.scss b/components/basic/Input.module.scss similarity index 93% rename from components/Input.module.scss rename to components/basic/Input.module.scss index 35fb2c3..21e9a16 100644 --- a/components/Input.module.scss +++ b/components/basic/Input.module.scss @@ -1,3 +1,5 @@ +@import './Label.module.scss'; + .body { position: relative; } @@ -70,11 +72,4 @@ color: var(--color-primary); } } -} - -.label { - font-size: 0.8rem; - text-transform: uppercase; - display: block; - color: gray; } \ No newline at end of file diff --git a/components/Input.tsx b/components/basic/Input.tsx similarity index 94% rename from components/Input.tsx rename to components/basic/Input.tsx index 8a91510..0a7f5a7 100644 --- a/components/Input.tsx +++ b/components/basic/Input.tsx @@ -1,9 +1,9 @@ 'use client'; -import styles from '@components/Input.module.scss'; +import * as React from 'react'; import { createSlug } from '@root/common/utilities'; -import * as React from 'react'; +import styles from './Input.module.scss'; export default function Input(props: { label?: any, diff --git a/components/basic/Label.module.scss b/components/basic/Label.module.scss new file mode 100644 index 0000000..a59a359 --- /dev/null +++ b/components/basic/Label.module.scss @@ -0,0 +1,6 @@ +.label { + font-size: 0.8rem; + text-transform: uppercase; + display: block; + color: gray; +} \ No newline at end of file diff --git a/components/basic/Slider.module.scss b/components/basic/Slider.module.scss new file mode 100644 index 0000000..a0543b4 --- /dev/null +++ b/components/basic/Slider.module.scss @@ -0,0 +1,33 @@ +@import 'Label.module.scss'; + +.body { + +} + +.slider { + transition: 0.25s; + width: 100%; + display: block; + -webkit-appearance: none; + background: var(--color-border); + height: 2px; + padding: 0; + margin: 0.5rem 0; + + &::-webkit-slider-thumb { + background: var(--color-primary); + -webkit-appearance: none; + display: block; + width: 1rem; + height: 1rem; + border-radius: 0.5rem + } + + &:focus { + background: var(--color-text); + } + + &:focus { + outline: none; + } +} \ No newline at end of file diff --git a/components/basic/Slider.tsx b/components/basic/Slider.tsx new file mode 100644 index 0000000..849b5f9 --- /dev/null +++ b/components/basic/Slider.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; + +import styles from './Slider.module.scss'; + +export default function Slider(props: { + id?: string, + min: number, + max: number, + step?: number, + initialValue?: number + label?: string, + onChange?: React.ChangeEventHandler, + value?: number, +}) { + const id = props.id || 'slider-' + Math.floor(Math.random() * 100000).toString(); + + const [value, setValue] = React.useState(props.initialValue || props.min); + + return ( +
+ + { + setValue(Number.parseFloat(e.target.value)); + props.onChange && props.onChange(e); + }} + value={props.value} + step={props.step} + /> +
+ ) +} \ No newline at end of file From 249202b756d98149446e2fe2ce1c6a43269ca198 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Thu, 11 May 2023 11:33:58 +0900 Subject: [PATCH 10/21] fix add provider and add replication menus --- components/DatasetSelect.tsx | 4 +- components/ProviderSelect.tsx | 16 +++-- components/Select.tsx | 13 ---- components/apps/ddm/DDM.tsx | 37 +++++++----- components/apps/ddm/forms/FormAddProvider.tsx | 60 ++++++++++--------- .../apps/ddm/forms/FormAddReplication.tsx | 6 +- components/apps/ptolemy/Ptolemy.tsx | 4 +- components/{ => basic}/Select.module.scss | 9 +-- components/basic/Select.tsx | 32 ++++++++++ components/basic/Slider.tsx | 2 + 10 files changed, 104 insertions(+), 79 deletions(-) delete mode 100644 components/Select.tsx rename components/{ => basic}/Select.module.scss (82%) create mode 100644 components/basic/Select.tsx diff --git a/components/DatasetSelect.tsx b/components/DatasetSelect.tsx index ebd75ec..fa19a3d 100644 --- a/components/DatasetSelect.tsx +++ b/components/DatasetSelect.tsx @@ -1,8 +1,8 @@ -import Select from '@components/Select'; +import Select from '@components/basic/Select'; export default function ProviderSelect(props) { return ( - {props.datasets?.map((dataset, i) => { return ( diff --git a/components/ProviderSelect.tsx b/components/ProviderSelect.tsx index f1afa79..16cf5f6 100644 --- a/components/ProviderSelect.tsx +++ b/components/ProviderSelect.tsx @@ -1,15 +1,13 @@ -import Select from '@components/Select'; +import Select from '@components/basic/Select'; export default function ProviderSelect(props) { return ( - + {props.providers?.map((provider, i) => + + )} ); } \ No newline at end of file diff --git a/components/Select.tsx b/components/Select.tsx deleted file mode 100644 index dda10f2..0000000 --- a/components/Select.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import styles from './Select.module.scss'; - -export default function Select(props) { - return ( -
- - -
- ) -} \ No newline at end of file diff --git a/components/apps/ddm/DDM.tsx b/components/apps/ddm/DDM.tsx index 26c41ca..100a61c 100644 --- a/components/apps/ddm/DDM.tsx +++ b/components/apps/ddm/DDM.tsx @@ -7,6 +7,7 @@ import { getCookie, setCookie } from '@modules/cookies'; import { associateWallet, checkAuth, getDatasets, getHealth, getProviders, getReplications, getWallets } from '@data/api'; import DefaultLayout, { AppBody, AppNav, AppNavItem, AppNavSubItem, AppTitle, AppVersion } from '@components/DefaultLayout'; +import Modal from '@root/components/Modal'; import Datasets from '@root/components/apps/ddm/scenes/Datasets'; import Providers from '@root/components/apps/ddm/scenes/Providers'; @@ -47,6 +48,9 @@ export default function DDM(props) { setCookie('ddm-address', ddmAddress); }; + const addProviderButton = React.useRef(null); + const addReplicationButton = React.useRef(null); + async function updateState() { setState({ datasets: await getDatasets(), @@ -64,8 +68,10 @@ export default function DDM(props) { setCommitHash((await (await fetch('/api')).json()).commit_hash) } - function dismissTooltip() { - setAppTooltipState(0); + function dismissTooltip(id) { + if (appTooltipState === id) { + setAppTooltipState(0); + } } React.useEffect(() => { @@ -124,13 +130,13 @@ export default function DDM(props) { Providers {appNavigationState === navigationStates.providers && '➝'} setAppTooltipState(tooltipStates.addProvider)}> - + Add provider + + Add provider setAppNavigationState(navigationStates.replications)}> Replications {appNavigationState === navigationStates.replications && '➝'} setAppTooltipState(tooltipStates.addReplication)}> - + Add replication + + Add replication setAppNavigationState(navigationStates.wallets)}> Wallets {appNavigationState === navigationStates.wallets && '➝'} @@ -193,10 +199,11 @@ export default function DDM(props) { /> )} {appTooltipState === tooltipStates.addProvider && ( - + + + )} {appTooltipState === tooltipStates.addWallet && ( )} {appTooltipState === tooltipStates.addReplication && ( - + + + )} {appTooltipState === tooltipStates.associateWallet && ( -

Add provider

-
-
- setProviderID(e.target.value)} - autoFocus - autoComplete='new-password' - placeholder='example: f012345' - spellCheck='false' - /> -
-
- setProviderName(e.target.value)} - placeholder="a friendly name" - /> -
-
- -
-
- {feedback} - ) + return ( +
+

Add provider

+
+
+ setProviderID(e.target.value)} + autoFocus + autoComplete='new-password' + placeholder='example: f012345' + spellCheck='false' + /> +
+
+ setProviderName(e.target.value)} + placeholder="a friendly name" + /> +
+
+ +
+
+ {feedback} +
+ ) } \ No newline at end of file diff --git a/components/apps/ddm/forms/FormAddReplication.tsx b/components/apps/ddm/forms/FormAddReplication.tsx index c2faee8..df706e4 100644 --- a/components/apps/ddm/forms/FormAddReplication.tsx +++ b/components/apps/ddm/forms/FormAddReplication.tsx @@ -44,14 +44,14 @@ export default function FormAddReplication(props) { } return ( - +

Add replication

setProviderID(e.target.value)} required autoFocus />
- setDatasetName(e.target.value)} /> + setDatasetName(e.target.value)} />
{feedback} - +
) } \ No newline at end of file diff --git a/components/apps/ptolemy/Ptolemy.tsx b/components/apps/ptolemy/Ptolemy.tsx index 8522f36..6605edc 100644 --- a/components/apps/ptolemy/Ptolemy.tsx +++ b/components/apps/ptolemy/Ptolemy.tsx @@ -5,8 +5,8 @@ import styles from '@components/Form.module.scss'; import DefaultLayout, { AppBody, AppNav, AppNavItem, AppNavSubItem, AppTitle, AppVersion } from '@components/DefaultLayout'; import Modal from '@components/Modal'; import Input from '@components/basic/Input'; -import Select from '@components/Select'; import FileSelect from '@components/FileSelect'; +import Select from '@components/basic/Select'; import Slider from '@components/basic/Slider'; const formStates = { @@ -40,7 +40,7 @@ export default function Ptolemy(props) {
-
diff --git a/components/Select.module.scss b/components/basic/Select.module.scss similarity index 82% rename from components/Select.module.scss rename to components/basic/Select.module.scss index 91c7ff5..52ee1ea 100644 --- a/components/Select.module.scss +++ b/components/basic/Select.module.scss @@ -1,14 +1,9 @@ +@import './Label.module.scss'; + .body { position: relative; } -.label { - font-size: 0.8em; - text-transform: uppercase; - display: block; - color: gray; -} - .select { background: var(--color-background); border: none; diff --git a/components/basic/Select.tsx b/components/basic/Select.tsx new file mode 100644 index 0000000..6f42120 --- /dev/null +++ b/components/basic/Select.tsx @@ -0,0 +1,32 @@ +import * as React from 'react'; + +import styles from './Select.module.scss'; + +export default function Select(props: { + id?: string, + placeholder?: string, + label?: string, + onChange?: React.ChangeEventHandler, + value?: string, + required?: boolean, + disabled?: boolean + children?: React.ReactElement[], +}) { + const id = props.id || 'select-' + Math.floor(Math.random() * 100000); + + return ( +
+ + +
+ ) +} \ No newline at end of file diff --git a/components/basic/Slider.tsx b/components/basic/Slider.tsx index 849b5f9..ed0e7ab 100644 --- a/components/basic/Slider.tsx +++ b/components/basic/Slider.tsx @@ -11,6 +11,7 @@ export default function Slider(props: { label?: string, onChange?: React.ChangeEventHandler, value?: number, + required?: boolean }) { const id = props.id || 'slider-' + Math.floor(Math.random() * 100000).toString(); @@ -31,6 +32,7 @@ export default function Slider(props: { }} value={props.value} step={props.step} + required={props.required} />
) From 23eaeced9df2df98a6efe1f6bd9a2188692ae243 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Thu, 11 May 2023 11:34:41 +0900 Subject: [PATCH 11/21] use appearance instead of --webkit-appearance --- components/basic/Slider.module.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/basic/Slider.module.scss b/components/basic/Slider.module.scss index a0543b4..ade7437 100644 --- a/components/basic/Slider.module.scss +++ b/components/basic/Slider.module.scss @@ -8,7 +8,7 @@ transition: 0.25s; width: 100%; display: block; - -webkit-appearance: none; + appearance: none; background: var(--color-border); height: 2px; padding: 0; @@ -16,7 +16,7 @@ &::-webkit-slider-thumb { background: var(--color-primary); - -webkit-appearance: none; + appearance: none; display: block; width: 1rem; height: 1rem; From e07af520671bb74da7ac8a566cc91660ff6a494e Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Thu, 11 May 2023 12:07:03 +0900 Subject: [PATCH 12/21] fix add wallet and new dataset modals --- components/apps/ddm/DDM.tsx | 25 +++++++++++++------- components/apps/ddm/forms/FormAddWallet.tsx | 4 ++-- components/apps/ddm/forms/FormNewDataset.tsx | 4 ++-- components/basic/Slider.tsx | 2 +- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/components/apps/ddm/DDM.tsx b/components/apps/ddm/DDM.tsx index 100a61c..bb591b4 100644 --- a/components/apps/ddm/DDM.tsx +++ b/components/apps/ddm/DDM.tsx @@ -48,8 +48,10 @@ export default function DDM(props) { setCookie('ddm-address', ddmAddress); }; + const newDatasetButton = React.useRef(null); const addProviderButton = React.useRef(null); const addReplicationButton = React.useRef(null); + const addWalletButton = React.useRef(null); async function updateState() { setState({ @@ -69,6 +71,7 @@ export default function DDM(props) { } function dismissTooltip(id) { + console.log('closing ' + id + ', currently active ' + appTooltipState); if (appTooltipState === id) { setAppTooltipState(0); } @@ -124,7 +127,7 @@ export default function DDM(props) { Datasets {appNavigationState === navigationStates.datasets && '➝'}
setAppTooltipState(tooltipStates.newDataset)}> - + New dataset + + New dataset setAppNavigationState(navigationStates.providers)}> Providers {appNavigationState === navigationStates.providers && '➝'} @@ -142,7 +145,7 @@ export default function DDM(props) { Wallets {appNavigationState === navigationStates.wallets && '➝'} setAppTooltipState(tooltipStates.addWallet)}> - + Add wallet + + Add wallet @@ -193,10 +196,12 @@ export default function DDM(props) { )} {appTooltipState === tooltipStates.newDataset && ( - + + + )} {appTooltipState === tooltipStates.addProvider && ( @@ -206,9 +211,11 @@ export default function DDM(props) { )} {appTooltipState === tooltipStates.addWallet && ( - + + + )} {appTooltipState === tooltipStates.attachContent && ( +

Add wallet

For security reasons, please use the CLI to add a wallet:

./delta-dm wallet import --hex $(lotus wallet export <wallet address>) - +
); } diff --git a/components/apps/ddm/forms/FormNewDataset.tsx b/components/apps/ddm/forms/FormNewDataset.tsx index e8f7ef0..785cd88 100644 --- a/components/apps/ddm/forms/FormNewDataset.tsx +++ b/components/apps/ddm/forms/FormNewDataset.tsx @@ -68,7 +68,7 @@ export default function FormNewDataset(props) { } return ( - +

New dataset

@@ -91,6 +91,6 @@ export default function FormNewDataset(props) {
{feedback} - +
); } \ No newline at end of file diff --git a/components/basic/Slider.tsx b/components/basic/Slider.tsx index ed0e7ab..efda1ad 100644 --- a/components/basic/Slider.tsx +++ b/components/basic/Slider.tsx @@ -30,7 +30,7 @@ export default function Slider(props: { setValue(Number.parseFloat(e.target.value)); props.onChange && props.onChange(e); }} - value={props.value} + value={value} step={props.step} required={props.required} /> From d3d39e98ff540033d96594815a1905f0e3c1faa5 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Fri, 12 May 2023 10:31:50 +0900 Subject: [PATCH 13/21] increase slider margin --- components/basic/Slider.module.scss | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/components/basic/Slider.module.scss b/components/basic/Slider.module.scss index ade7437..31337d0 100644 --- a/components/basic/Slider.module.scss +++ b/components/basic/Slider.module.scss @@ -1,9 +1,5 @@ @import 'Label.module.scss'; -.body { - -} - .slider { transition: 0.25s; width: 100%; @@ -12,7 +8,7 @@ background: var(--color-border); height: 2px; padding: 0; - margin: 0.5rem 0; + margin: 0.75rem 0; &::-webkit-slider-thumb { background: var(--color-primary); From 8f0404ecb3145a3fb6611c534e86b9b0c52ac57a Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Fri, 12 May 2023 13:29:21 +0900 Subject: [PATCH 14/21] replication filtering wip --- .../apps/ddm/scenes/Replications.module.scss | 30 +++++++++++++ components/apps/ddm/scenes/Replications.tsx | 44 +++++++++++++++---- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/components/apps/ddm/scenes/Replications.module.scss b/components/apps/ddm/scenes/Replications.module.scss index 6d86a06..9a04128 100644 --- a/components/apps/ddm/scenes/Replications.module.scss +++ b/components/apps/ddm/scenes/Replications.module.scss @@ -1,2 +1,32 @@ .body { } + +.filterMenu { + border-bottom: 1px solid var(--color-border); + padding: 1.5rem; +} + +.filterMenuBody { + display: flex; + flex-direction: column; + margin: 0 -1rem -1.5rem; +} + +.filterMenuColumn { + flex-grow: 1; + flex-basis: 0; + margin: 0 1rem; +} + +.filterMenuRow { + display: flex; + flex-direction: row; + margin-bottom: 1.5rem; +} + +.filterMenuButtonColumn { + @extend .filterMenuColumn; + + flex-grow: 0; + min-width: 10rem; +} \ No newline at end of file diff --git a/components/apps/ddm/scenes/Replications.tsx b/components/apps/ddm/scenes/Replications.tsx index 5a95ac5..a6fe573 100644 --- a/components/apps/ddm/scenes/Replications.tsx +++ b/components/apps/ddm/scenes/Replications.tsx @@ -9,21 +9,47 @@ import tableStyles from '@components/Table.module.scss'; import Input from '@components/basic/Input'; import LoadingIndicator from '@components/LoadingIndicator'; import ProviderRef from '@components/ProviderRef'; +import Button from '@components/Button'; export default function Replications(props) { return (
{props.state.replications &&
- +
+
e.preventDefault()}> +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
Dataset
Status
From 819ee8b32cf4b85d06ac681d9a3a36c4cb7af954 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Mon, 15 May 2023 15:11:21 +0900 Subject: [PATCH 15/21] don't read cookie on server side --- components/Main.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/components/Main.tsx b/components/Main.tsx index e14676e..8485edf 100644 --- a/components/Main.tsx +++ b/components/Main.tsx @@ -10,13 +10,20 @@ import AppPtolemy from '@components/apps/ptolemy/Ptolemy'; import { getCookie, setCookie } from '@root/modules/cookies'; export default function Main(props) { - const [activeApp, _setActiveApp] = React.useState(getCookie('active-app') || apps.ddm); + const [activeApp, _setActiveApp] = React.useState(apps.ddm); function setActiveApp(app: string) { _setActiveApp(app); setCookie('active-app', app); } const appNames = Object.keys(apps); + React.useEffect(() => { + let savedState = getCookie('active-app'); + if (savedState) { + _setActiveApp(savedState); + } + }) + switch (activeApp) { case apps.ddm: return From 7ad250acf584b2891fe1a3f612892a80e928bb39 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Mon, 15 May 2023 15:43:32 +0900 Subject: [PATCH 16/21] replication filtering search state wip --- components/apps/ddm/DDM.tsx | 102 ++++++-------------- components/apps/ddm/scenes/Replications.tsx | 67 ++++++++----- 2 files changed, 73 insertions(+), 96 deletions(-) diff --git a/components/apps/ddm/DDM.tsx b/components/apps/ddm/DDM.tsx index bb591b4..84083c7 100644 --- a/components/apps/ddm/DDM.tsx +++ b/components/apps/ddm/DDM.tsx @@ -30,21 +30,20 @@ export default function DDM(props) { const [selectedProvider, setSelectedProvider] = React.useState(''); const [selectedDataset, setSelectedDataset] = React.useState(''); const [selectedWallet, setSelectedWallet] = React.useState(''); - const [state, setState] = React.useState({ - datasets: undefined, - providers: undefined, - replications: undefined, - wallets: undefined + const [state, setState] = React.useState({ + datasets: undefined, + providers: undefined, + wallets: undefined, }); const [health, setHealth] = React.useState(undefined); const [commitHash, setCommitHash] = React.useState(undefined); const [appTooltipState, setAppTooltipState] = React.useState(0); const [authToken, setAuthTokenEphemeral] = React.useState(''); - const setAuthToken = authToken => { + const setAuthToken = (authToken) => { setAuthTokenEphemeral(authToken); setCookie('auth', authToken); }; - const setDDMAddress = ddmAddress => { + const setDDMAddress = (ddmAddress) => { setCookie('ddm-address', ddmAddress); }; @@ -57,7 +56,6 @@ export default function DDM(props) { setState({ datasets: await getDatasets(), providers: await getProviders(), - replications: await getReplications(), wallets: await getWallets(), }); } @@ -67,7 +65,7 @@ export default function DDM(props) { } async function updateCommitHash() { - setCommitHash((await (await fetch('/api')).json()).commit_hash) + setCommitHash((await (await fetch('/api')).json()).commit_hash); } function dismissTooltip(id) { @@ -82,7 +80,7 @@ export default function DDM(props) { setAuthTokenEphemeral(getCookie('auth')); try { - if (!await checkAuth()) { + if (!(await checkAuth())) { throw new Error(); } } catch { @@ -101,13 +99,7 @@ export default function DDM(props) { }, [authToken]); if (!authToken) { - return ( - - ) + return ; } return ( @@ -123,28 +115,20 @@ export default function DDM(props) { UI Version: ${PackageJSON.version} (${commitHash}) `} - setAppNavigationState(navigationStates.datasets)}> - Datasets {appNavigationState === navigationStates.datasets && '➝'} - - setAppTooltipState(tooltipStates.newDataset)}> + setAppNavigationState(navigationStates.datasets)}>Datasets {appNavigationState === navigationStates.datasets && '➝'} + setAppTooltipState(tooltipStates.newDataset)}> + New dataset - setAppNavigationState(navigationStates.providers)}> - Providers {appNavigationState === navigationStates.providers && '➝'} - - setAppTooltipState(tooltipStates.addProvider)}> + setAppNavigationState(navigationStates.providers)}>Providers {appNavigationState === navigationStates.providers && '➝'} + setAppTooltipState(tooltipStates.addProvider)}> + Add provider - setAppNavigationState(navigationStates.replications)}> - Replications {appNavigationState === navigationStates.replications && '➝'} - - setAppTooltipState(tooltipStates.addReplication)}> + setAppNavigationState(navigationStates.replications)}>Replications {appNavigationState === navigationStates.replications && '➝'} + setAppTooltipState(tooltipStates.addReplication)}> + Add replication - setAppNavigationState(navigationStates.wallets)}> - Wallets {appNavigationState === navigationStates.wallets && '➝'} - - setAppTooltipState(tooltipStates.addWallet)}> + setAppNavigationState(navigationStates.wallets)}>Wallets {appNavigationState === navigationStates.wallets && '➝'} + setAppTooltipState(tooltipStates.addWallet)}> + Add wallet @@ -174,75 +158,47 @@ export default function DDM(props) { {appNavigationState === navigationStates.replications && ( setReplicationSearch(e.target.value)} - searchLabel='Search replications' - placeholder='search any field (replication filtering is w.i.p.)' + onSearchChange={(e) => setReplicationSearch(e.target.value)} + searchLabel="Search replications" + placeholder="search any field (replication filtering is w.i.p.)" selectedProvider={selectedProvider} setSelectedProvider={() => { alert('test'); }} selectedDataset={selectedDataset} - setSelectedDataset={() => { }} + setSelectedDataset={() => {}} state={state} /> )} {appNavigationState === navigationStates.wallets && ( - setAppTooltipState(tooltipStates.associateWallet)} - setSelectedWallet={setSelectedWallet} - /> + setAppTooltipState(tooltipStates.associateWallet)} setSelectedWallet={setSelectedWallet} /> )} {appTooltipState === tooltipStates.newDataset && ( - + )} {appTooltipState === tooltipStates.addProvider && ( - + )} {appTooltipState === tooltipStates.addWallet && ( - + )} - {appTooltipState === tooltipStates.attachContent && ( - - )} + {appTooltipState === tooltipStates.attachContent && } {appTooltipState === tooltipStates.addReplication && ( - + )} {appTooltipState === tooltipStates.associateWallet && ( - + )} - ) + ); } diff --git a/components/apps/ddm/scenes/Replications.tsx b/components/apps/ddm/scenes/Replications.tsx index a6fe573..8ab517c 100644 --- a/components/apps/ddm/scenes/Replications.tsx +++ b/components/apps/ddm/scenes/Replications.tsx @@ -12,40 +12,58 @@ import ProviderRef from '@components/ProviderRef'; import Button from '@components/Button'; export default function Replications(props) { + const [searchDatasets, setSearchDatasets] = React.useState(''); + const [searchProviders, setSearchProviders] = React.useState(''); + const [searchTimeMin, setSearchTimeMin] = React.useState(''); + const [searchTimeMax, setSearchTimeMax] = React.useState(''); + const [searchProposalCID, setSearchProposalCID] = React.useState(''); + const [searchPieceCID, setSearchPieceCID] = React.useState(''); + const [searchMessage, setSearchMessage] = React.useState(''); + + function applySearch() { + let datasets = searchDatasets.split(',').map(dataset => dataset.trim()); + let providers = searchProviders.split(',').map(provider => provider.trim()); + let timeMin = searchTimeMin && Date.parse(searchTimeMin); + let timeMax = searchTimeMax && Date.parse(searchTimeMax); + let proposalCID = searchProposalCID.trim(); + let pieceCID = searchPieceCID.trim(); + let message = searchMessage.trim(); + } + return (
- {props.state.replications && + {props.state.replications && (
-
e.preventDefault()}> + e.preventDefault()}>
- + setSearchMessage(e.target.value)} />
- + setSearchProviders(e.target.value)} />
- + setSearchTimeMin(e.target.value)} />
- + setSearchTimeMax(e.target.value)} />
- + setSearchProposalCID(e.target.value)} />
- + setSearchPieceCID(e.target.value)} />
- + setSearchMessage(e.target.value)} />
- +
@@ -62,13 +80,15 @@ export default function Replications(props) {
{props.state.replications .filter((replication, i) => { - return !props.search - || replication.status?.toLowerCase().includes(props.search.toLowerCase()) - || replication.deal_time?.toString().toLowerCase().includes(props.search.toLowerCase()) - || replication.provider_actor_id?.toLowerCase().includes(props.search.toLowerCase()) - || replication.proposal_cid?.toLowerCase().includes(props.search.toLowerCase()) - || replication.content_commp?.toLowerCase().includes(props.search.toLowerCase()) - || replication.delta_message?.toLowerCase().includes(props.search.toLowerCase()); + return ( + !props.search || + replication.status?.toLowerCase().includes(props.search.toLowerCase()) || + replication.deal_time?.toString().toLowerCase().includes(props.search.toLowerCase()) || + replication.provider_actor_id?.toLowerCase().includes(props.search.toLowerCase()) || + replication.proposal_cid?.toLowerCase().includes(props.search.toLowerCase()) || + replication.content_commp?.toLowerCase().includes(props.search.toLowerCase()) || + replication.delta_message?.toLowerCase().includes(props.search.toLowerCase()) + ); }) .map((replication, i) => { return ( @@ -76,19 +96,20 @@ export default function Replications(props) {
{replication.content.dataset_name}
{replication.status}
-
-
{replication.is_self_service ? "true" : "false"}
+
+ +
+
{replication.is_self_service ? 'true' : 'false'}
{new Date(Date.parse(replication.deal_time)).toUTCString()}
{replication.proposal_cid}
{replication.content_commp}
{replication.delta_message}
- ) - }) - } + ); + })}
- } + )} {props.state.replications === undefined && }
); From cf9d5cc73580d88614f44cb25bac7499e700e2b7 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Mon, 15 May 2023 17:26:56 +0900 Subject: [PATCH 17/21] simple replications filtering --- components/apps/ddm/DDM.tsx | 78 +++++------ components/apps/ddm/forms/FormAddProvider.tsx | 7 +- .../apps/ddm/forms/FormAddReplication.tsx | 10 +- .../ddm/forms/FormAssociateWallet.module.scss | 5 - .../apps/ddm/forms/FormAssociateWallet.tsx | 56 -------- components/apps/ddm/forms/FormNewDataset.tsx | 7 +- components/apps/ddm/forms/FormUploadData.tsx | 4 +- components/apps/ddm/scenes/Datasets.tsx | 16 ++- components/apps/ddm/scenes/Providers.tsx | 132 ++++++++++-------- components/apps/ddm/scenes/Replications.tsx | 86 ++++++------ components/apps/ddm/scenes/Wallets.tsx | 57 ++++---- data/api.ts | 121 +++++++++------- 12 files changed, 268 insertions(+), 311 deletions(-) delete mode 100644 components/apps/ddm/forms/FormAssociateWallet.module.scss delete mode 100644 components/apps/ddm/forms/FormAssociateWallet.tsx diff --git a/components/apps/ddm/DDM.tsx b/components/apps/ddm/DDM.tsx index 84083c7..c03b39d 100644 --- a/components/apps/ddm/DDM.tsx +++ b/components/apps/ddm/DDM.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import PackageJSON from '@root/package.json'; import { navigationStates, tooltipStates } from '@common/navigation'; import { getCookie, setCookie } from '@modules/cookies'; -import { associateWallet, checkAuth, getDatasets, getHealth, getProviders, getReplications, getWallets } from '@data/api'; +import { associateWallet, checkAuth, getDatasets, getHealth, getProviders, getReplications, GetReplicationsConfig, getWallets } from '@data/api'; import DefaultLayout, { AppBody, AppNav, AppNavItem, AppNavSubItem, AppTitle, AppVersion } from '@components/DefaultLayout'; import Modal from '@root/components/Modal'; @@ -20,7 +20,6 @@ import FormAddWallet from '@root/components/apps/ddm/forms/FormAddWallet'; import FormAddProvider from '@root/components/apps/ddm/forms/FormAddProvider'; import FormAddReplication from '@root/components/apps/ddm/forms/FormAddReplication'; import FormNewDataset from '@root/components/apps/ddm/forms/FormNewDataset'; -import FormAssociateWallet from '@root/components/apps/ddm/forms/FormAssociateWallet'; export default function DDM(props) { const [appNavigationState, setAppNavigationState] = React.useState(1); @@ -30,11 +29,10 @@ export default function DDM(props) { const [selectedProvider, setSelectedProvider] = React.useState(''); const [selectedDataset, setSelectedDataset] = React.useState(''); const [selectedWallet, setSelectedWallet] = React.useState(''); - const [state, setState] = React.useState({ - datasets: undefined, - providers: undefined, - wallets: undefined, - }); + const [datasets, setDatasets] = React.useState([]); + const [providers, setProviders] = React.useState([]); + const [replications, setReplications] = React.useState([]); + const [wallets, setWallets] = React.useState([]); const [health, setHealth] = React.useState(undefined); const [commitHash, setCommitHash] = React.useState(undefined); const [appTooltipState, setAppTooltipState] = React.useState(0); @@ -47,19 +45,19 @@ export default function DDM(props) { setCookie('ddm-address', ddmAddress); }; + const getReplicationsConfig = React.useRef(); + const setGetReplicationsConfig = (cfg: GetReplicationsConfig) => getReplicationsConfig.current = cfg; + + const updateDatasets = async () => setDatasets(await getDatasets()); + const updateProviders = async () => setProviders(await getProviders()); + const updateReplications = async () => setReplications(await getReplications(getReplicationsConfig.current)); + const updateWallets = async () => setWallets(await getWallets()); + const newDatasetButton = React.useRef(null); const addProviderButton = React.useRef(null); const addReplicationButton = React.useRef(null); const addWalletButton = React.useRef(null); - async function updateState() { - setState({ - datasets: await getDatasets(), - providers: await getProviders(), - wallets: await getWallets(), - }); - } - async function updateHealth() { setHealth(await getHealth()); } @@ -92,7 +90,11 @@ export default function DDM(props) { React.useEffect(() => { if (authToken) { - updateState(); + updateDatasets(); + updateProviders(); + updateReplications(); + updateWallets(); + updateHealth(); updateCommitHash(); } @@ -139,49 +141,42 @@ export default function DDM(props) { onSearchChange={(e) => setDatasetSearch(e.target.value)} searchLabel="Search datasets" placeholder="(example: university-bird-sounds)" - state={state} + datasets={datasets} onAttachContent={() => setAppTooltipState(tooltipStates.attachContent)} - selectedDataset={selectedDataset} + // selectedDataset={selectedDataset} setSelectedDataset={setSelectedDataset} /> )} {appNavigationState === navigationStates.providers && ( setProviderSearch(e.target.value)} providerLabel="Search providers" placeholder="(example: f0123456)" - state={state} - updateState={updateState} - /> - )} - {appNavigationState === navigationStates.replications && ( - setReplicationSearch(e.target.value)} - searchLabel="Search replications" - placeholder="search any field (replication filtering is w.i.p.)" - selectedProvider={selectedProvider} - setSelectedProvider={() => { - alert('test'); - }} - selectedDataset={selectedDataset} - setSelectedDataset={() => {}} - state={state} /> )} + {appNavigationState === navigationStates.replications && } {appNavigationState === navigationStates.wallets && ( - setAppTooltipState(tooltipStates.associateWallet)} setSelectedWallet={setSelectedWallet} /> + )} {appTooltipState === tooltipStates.newDataset && ( - + )} {appTooltipState === tooltipStates.addProvider && ( - + )} {appTooltipState === tooltipStates.addWallet && ( @@ -189,15 +184,12 @@ export default function DDM(props) { )} - {appTooltipState === tooltipStates.attachContent && } + {appTooltipState === tooltipStates.attachContent && } {appTooltipState === tooltipStates.addReplication && ( - + )} - {appTooltipState === tooltipStates.associateWallet && ( - - )} ); diff --git a/components/apps/ddm/forms/FormAddProvider.tsx b/components/apps/ddm/forms/FormAddProvider.tsx index ffacc4c..feda88c 100644 --- a/components/apps/ddm/forms/FormAddProvider.tsx +++ b/components/apps/ddm/forms/FormAddProvider.tsx @@ -8,7 +8,9 @@ import Button from '@components/Button'; import Input from '@components/basic/Input'; import Feedback from '@components/Feedback'; -export default function FormAddProvider(props) { +export default function FormAddProvider(props: { + updateProviders: () => void, +}) { const [providerID, setProviderID] = React.useState(''); const [providerName, setProviderName] = React.useState(''); @@ -24,10 +26,9 @@ export default function FormAddProvider(props) { setLoading(true); await addProvider(providerID, providerName); - props.updateState(); + props.updateProviders(); setFeedback(); - setTimeout(props.onOutsideClick, 2500); } catch (e) { setFeedback({e.toString()}); } finally { diff --git a/components/apps/ddm/forms/FormAddReplication.tsx b/components/apps/ddm/forms/FormAddReplication.tsx index df706e4..50feb69 100644 --- a/components/apps/ddm/forms/FormAddReplication.tsx +++ b/components/apps/ddm/forms/FormAddReplication.tsx @@ -12,7 +12,11 @@ import ProviderSelect from '@components/ProviderSelect'; import DatasetSelect from '@components/DatasetSelect'; import Feedback from '@components/Feedback'; -export default function FormAddReplication(props) { +export default function FormAddReplication(props: { + providers: any[], + datasets: any[], + updateReplications: () => void, +}) { const [providerID, setProviderID] = React.useState(''); const [datasetName, setDatasetName] = React.useState(''); const [numDeals, setNumDeals] = React.useState(1); @@ -31,11 +35,9 @@ export default function FormAddReplication(props) { setLoading(true); await addReplication(providerID, datasetName, numDeals, delayStartDays); - - await props.updateState(); + props.updateReplications(); setFeedback(); - setTimeout(props.onOutsideClick, 2500); } catch (e) { setFeedback({e.toString()}); } finally { diff --git a/components/apps/ddm/forms/FormAssociateWallet.module.scss b/components/apps/ddm/forms/FormAssociateWallet.module.scss deleted file mode 100644 index 0fcf05a..0000000 --- a/components/apps/ddm/forms/FormAssociateWallet.module.scss +++ /dev/null @@ -1,5 +0,0 @@ -@import '@components/Form.module.scss'; - -.body { - width: 350px; -} \ No newline at end of file diff --git a/components/apps/ddm/forms/FormAssociateWallet.tsx b/components/apps/ddm/forms/FormAssociateWallet.tsx deleted file mode 100644 index be26fdd..0000000 --- a/components/apps/ddm/forms/FormAssociateWallet.tsx +++ /dev/null @@ -1,56 +0,0 @@ -'use client'; - -import * as React from 'react'; -import { associateWallet } from '@data/api'; - -import styles from './FormAssociateWallet.module.scss'; - -import Button from '@components/Button'; -import Dismissible from '@components/Dismissible'; -import DatasetSelect from '@components/DatasetSelect'; -import TagSelect from '@components/TagSelect'; -import Feedback from '@components/Feedback'; - -export default function FormAssociateWallet(props) { - const [datasets, setDatasets] = React.useState([]); - - const [loading, setLoading] = React.useState(false); - const [feedback, setFeedback] = React.useState(); - - async function onSubmit(e) { - e.preventDefault(); - - try { - setLoading(true); - - await associateWallet(props.selectedWallet, datasets); - props.updateState(); - - setTimeout(props.onOutsideClick, 2500); - } catch (e) { - setFeedback({e.toString()}); - } finally { - setLoading(false); - } - } - - function isFormValid() { - return datasets.length !== 0; - } - - return ( - -

Associate wallet

-

{props.selectedWallet}

-
-
- dataset.name)} /> -
-
- -
-
- {feedback &&
{feedback}
} -
- ) -} \ No newline at end of file diff --git a/components/apps/ddm/forms/FormNewDataset.tsx b/components/apps/ddm/forms/FormNewDataset.tsx index 785cd88..35bd1e9 100644 --- a/components/apps/ddm/forms/FormNewDataset.tsx +++ b/components/apps/ddm/forms/FormNewDataset.tsx @@ -11,7 +11,9 @@ import Input from '@components/basic/Input'; import Button from '@components/Button'; import Feedback from '@components/Feedback'; -export default function FormNewDataset(props) { +export default function FormNewDataset(props: { + updateDatasets: () => void, +}) { let [name, setName] = React.useState(''); let [replications, setReplications] = React.useState(6); let [duration, setDuration] = React.useState(540); @@ -36,10 +38,9 @@ export default function FormNewDataset(props) { unsealed, indexed ); - props.updateState(); + props.updateDatasets(); setFeedback() - setTimeout(props.onOutsideClick, 2500); } catch (e) { setFeedback({e.toString()}); } finally { diff --git a/components/apps/ddm/forms/FormUploadData.tsx b/components/apps/ddm/forms/FormUploadData.tsx index 7c976bd..b83c3bd 100644 --- a/components/apps/ddm/forms/FormUploadData.tsx +++ b/components/apps/ddm/forms/FormUploadData.tsx @@ -14,7 +14,7 @@ import { pluralize, truncCid } from '@root/common/utilities'; export default function FormUploadData(props: { selectedDataset: string, onOutsideClick: React.MouseEventHandler, - updateState: () => void, + updateDatasets: () => void, }) { const [datasetName, setDatasetName] = React.useState(props.selectedDataset || ''); const [file, setFile] = React.useState(null); @@ -48,7 +48,7 @@ export default function FormUploadData(props: { JSON.parse(fileContents); let res = await addContents(props.selectedDataset, fileContents); - props.updateState(); + props.updateDatasets(); setFeedback( diff --git a/components/apps/ddm/scenes/Datasets.tsx b/components/apps/ddm/scenes/Datasets.tsx index 84ea863..b24daa8 100644 --- a/components/apps/ddm/scenes/Datasets.tsx +++ b/components/apps/ddm/scenes/Datasets.tsx @@ -10,9 +10,17 @@ import Input from '@components/basic/Input'; import LoadingIndicator from '@components/LoadingIndicator'; import WalletRef from '@components/WalletRef'; -export default function Datasets(props) { +export default function Datasets(props: { + datasets: any[], + searchLabel: string, + search: string, + onSearchChange: (string) => void, + placeholder: string, + setSelectedDataset: (string) => void, + onAttachContent: () => void, +}) { return (
- {props.state.datasets && + {props.datasets &&
Indexed Wallets
- {props.state.datasets + {props.datasets .filter((dataset, i) => !props.search || dataset.name.includes(props.search)) .map((dataset, i) => { let progress = dataset.bytes_replicated.padded / dataset.bytes_total.padded / dataset.replication_quota; @@ -66,6 +74,6 @@ export default function Datasets(props) { }
} - {props.state.datasets === undefined && } + {props.datasets === undefined && }
); } diff --git a/components/apps/ddm/scenes/Providers.tsx b/components/apps/ddm/scenes/Providers.tsx index 45f24c6..bb0ec59 100644 --- a/components/apps/ddm/scenes/Providers.tsx +++ b/components/apps/ddm/scenes/Providers.tsx @@ -13,10 +13,18 @@ import ProviderRef from '@components/ProviderRef'; import Button from '@components/Button'; import TagSelect from '@components/TagSelect'; -export default function Providers(props) { +export default function Providers(props: { + providers: any[], + datasets: any[], + updateDatasets: () => void, + providerLabel: string, + placeholder: string, + search: string, + onSearchChange: (string) => void, +}) { return (
- {props.state.providers && + {props.providers && (
Provider Key Allowed Datasets
- {props.state.providers - .filter( - (provider, i) => !props.search || provider.actor_id.includes(props.search) - ) - .map( - (provider, i) => { - return - } - ) - } + {props.providers + .filter((provider, i) => !props.search || provider.actor_id.includes(props.search)) + .map((provider, i) => { + return ; + })}
- } - {props.state.providers === undefined && } + )} + {props.providers === undefined && }
); } -function ProviderCard(props) { +function ProviderCard(props: { provider: any, datasets: any[], updateDatasets: () => void }) { let provider = props.provider; let [editing, setEditing] = React.useState(false); @@ -60,7 +63,7 @@ function ProviderCard(props) { let [saving, setSaving] = React.useState(false); let [allowedDatasets, setAllowedDatasets] = React.useState(provider.allowed_datasets?.map((dataset, i) => dataset.name) || []); - let datasetNames = props.state.datasets?.map((dataset, i) => dataset.name) || []; + let datasetNames = props.datasets?.map((dataset, i) => dataset.name) || []; function cancelEdit() { setAllowSelfService(provider.allow_self_service); @@ -82,40 +85,47 @@ function ProviderCard(props) { setSaving(false); } - props.updateState(); + props.updateDatasets(); } - if (editing) return ( -
- - Rename {provider.actor_id}} - value={name} - onChange={e => setName(e.target.value)} - /> - - -
{Utilities.bytesToSize(provider.bytes_replicated.padded)} (padded)
-
{Utilities.bytesToSize(provider.bytes_replicated.raw)} (unpadded)
-
- - setAllowSelfService(e.target.checked)} /> - - - - - - - -
- ); + if (editing) + return ( +
+ + + Rename {provider.actor_id} + + } + value={name} + onChange={(e) => setName(e.target.value)} + /> + + +
{Utilities.bytesToSize(provider.bytes_replicated.padded)} (padded)
+
{Utilities.bytesToSize(provider.bytes_replicated.raw)} (unpadded)
+
+ + setAllowSelfService(e.target.checked)} /> + + + + + + + + + +
+ ); return (
@@ -128,26 +138,24 @@ function ProviderCard(props) {
{Utilities.bytesToSize(provider.bytes_replicated.raw)} (unpadded)
- + + + + - - + -
); } function ProviderKey(props) { - const [copied, setCopied] = React.useState(false); function copy() { @@ -160,7 +168,9 @@ function ProviderKey(props) { return ( {props.providerKey} - {copied ? 'copied ✓' : 'copy 📋'} + + {copied ? 'copied ✓' : 'copy 📋'} + ); -} \ No newline at end of file +} diff --git a/components/apps/ddm/scenes/Replications.tsx b/components/apps/ddm/scenes/Replications.tsx index 8ab517c..6d0f19f 100644 --- a/components/apps/ddm/scenes/Replications.tsx +++ b/components/apps/ddm/scenes/Replications.tsx @@ -10,8 +10,9 @@ import Input from '@components/basic/Input'; import LoadingIndicator from '@components/LoadingIndicator'; import ProviderRef from '@components/ProviderRef'; import Button from '@components/Button'; +import { GetReplicationsConfig } from '@root/data/api'; -export default function Replications(props) { +export default function Replications(props: { replications: any[], updateReplications: () => void, setGetReplicationsConfig: (cfg: GetReplicationsConfig) => void }) { const [searchDatasets, setSearchDatasets] = React.useState(''); const [searchProviders, setSearchProviders] = React.useState(''); const [searchTimeMin, setSearchTimeMin] = React.useState(''); @@ -21,46 +22,49 @@ export default function Replications(props) { const [searchMessage, setSearchMessage] = React.useState(''); function applySearch() { - let datasets = searchDatasets.split(',').map(dataset => dataset.trim()); - let providers = searchProviders.split(',').map(provider => provider.trim()); - let timeMin = searchTimeMin && Date.parse(searchTimeMin); - let timeMax = searchTimeMax && Date.parse(searchTimeMax); - let proposalCID = searchProposalCID.trim(); - let pieceCID = searchPieceCID.trim(); - let message = searchMessage.trim(); + props.setGetReplicationsConfig({ + datasets: searchDatasets.split(',').map((dataset) => dataset.trim()), + providers: searchProviders.split(',').map((provider) => provider.trim()), + timeMin: searchTimeMin && new Date(searchTimeMin), + timeMax: searchTimeMax && new Date(searchTimeMax), + proposalCID: searchProposalCID.trim(), + pieceCID: searchPieceCID.trim(), + message: searchMessage.trim(), + }); + props.updateReplications(); } - + return (
- {props.state.replications && ( + {
e.preventDefault()}>
- setSearchMessage(e.target.value)} /> + setSearchDatasets(e.target.value)} />
- setSearchProviders(e.target.value)} /> + setSearchProviders(e.target.value)} />
- setSearchTimeMin(e.target.value)} /> + setSearchTimeMin(e.target.value)} />
- setSearchTimeMax(e.target.value)} /> + setSearchTimeMax(e.target.value)} />
- setSearchProposalCID(e.target.value)} /> + setSearchProposalCID(e.target.value)} />
- setSearchPieceCID(e.target.value)} /> + setSearchPieceCID(e.target.value)} />
- setSearchMessage(e.target.value)} /> + setSearchMessage(e.target.value)} />
@@ -78,39 +82,27 @@ export default function Replications(props) {
Piece CID (CommP)
Message
- {props.state.replications - .filter((replication, i) => { - return ( - !props.search || - replication.status?.toLowerCase().includes(props.search.toLowerCase()) || - replication.deal_time?.toString().toLowerCase().includes(props.search.toLowerCase()) || - replication.provider_actor_id?.toLowerCase().includes(props.search.toLowerCase()) || - replication.proposal_cid?.toLowerCase().includes(props.search.toLowerCase()) || - replication.content_commp?.toLowerCase().includes(props.search.toLowerCase()) || - replication.delta_message?.toLowerCase().includes(props.search.toLowerCase()) - ); - }) - .map((replication, i) => { - return ( -
-
-
{replication.content.dataset_name}
-
{replication.status}
-
- -
-
{replication.is_self_service ? 'true' : 'false'}
-
{new Date(Date.parse(replication.deal_time)).toUTCString()}
-
{replication.proposal_cid}
-
{replication.content_commp}
-
{replication.delta_message}
+ {props.replications?.map((replication, i) => { + return ( +
+
+
{replication.content.dataset_name}
+
{replication.status}
+
+
+
{replication.is_self_service ? 'true' : 'false'}
+
{new Date(Date.parse(replication.deal_time)).toUTCString()}
+
{replication.proposal_cid}
+
{replication.content_commp}
+
{replication.delta_message}
- ); - })} +
+ ); + })}
- )} - {props.state.replications === undefined && } + } + {props.replications === undefined && }
); } diff --git a/components/apps/ddm/scenes/Wallets.tsx b/components/apps/ddm/scenes/Wallets.tsx index a68609c..5c8427d 100644 --- a/components/apps/ddm/scenes/Wallets.tsx +++ b/components/apps/ddm/scenes/Wallets.tsx @@ -14,10 +14,10 @@ import WalletRef from '@components/WalletRef'; import TagSelect from '@components/TagSelect'; import Button from '@components/Button'; -export default function Wallets(props) { +export default function Wallets(props: { wallets: any[], updateWallets: () => void, datasets: any[], updateDatasets: () => void }) { return (
- {props.state.wallets && + {props.wallets && (
Address @@ -25,30 +25,23 @@ export default function Wallets(props) { Datacap Balance Datasets
- {props.state.wallets.map((wallet, i) => { + {props.wallets.map((wallet, i) => { return (
- dataset.name)} - updateState={props.updateState} /> + dataset.name)} updateDatasets={props.updateDatasets} />
); })}
- } - {props.state.wallets === undefined && } -
- ) + )} + {props.wallets === undefined && } +
+ ); } -function WalletCard(props: { - wallet: any, - datasets: string[], - updateState: CallableFunction -}) { +function WalletCard(props: { wallet: any, datasets: string[], updateDatasets: () => void }) { let selectedDefault = () => props.wallet.datasets.map((dataset, i) => dataset.name); - + const [selected, setSelected] = React.useState(selectedDefault()); const [editing, setEditing] = React.useState(false); @@ -73,7 +66,7 @@ function WalletCard(props: { setSaving(false); } - props.updateState(); + props.updateDatasets(); } return ( @@ -86,16 +79,22 @@ function WalletCard(props: { - { - editing - ? <> - - - - : <> - - - } + {editing ? ( + <> + + + + ) : ( + <> + + + )}
); -} \ No newline at end of file +} diff --git a/data/api.ts b/data/api.ts index c4056c4..81ba4d8 100644 --- a/data/api.ts +++ b/data/api.ts @@ -1,15 +1,15 @@ import { getCookie } from '@root/modules/cookies'; function apiURL() { - return (getCookie('ddm-address') || process.env.NEXT_PUBLIC_API_URL)?.replace(/\/$/, '') || "http://localhost:1314"; + return (getCookie('ddm-address') || process.env.NEXT_PUBLIC_API_URL)?.replace(/\/$/, '') || 'http://localhost:1314'; } function defaultHeaders() { return { 'Content-Type': 'application/json', - 'Authorization': 'Bearer ' + getCookie('auth'), - } -}; + Authorization: 'Bearer ' + getCookie('auth'), + }; +} // Checks only whether an auth key is in a valid format, without contacting // delta-dm API. Optionally accepts an auth key parameter - if not provided, the @@ -38,7 +38,7 @@ export async function checkAuth(auth?: string, ddmAddress?: string): Promise Date: Tue, 16 May 2023 11:51:20 +0900 Subject: [PATCH 18/21] pagination almost complete --- components/apps/ddm/DDM.tsx | 34 ++++++----- .../apps/ddm/scenes/Replications.module.scss | 10 ++++ components/apps/ddm/scenes/Replications.tsx | 58 ++++++++++++++++++- data/api.ts | 4 ++ 4 files changed, 88 insertions(+), 18 deletions(-) diff --git a/components/apps/ddm/DDM.tsx b/components/apps/ddm/DDM.tsx index c03b39d..ffe0dd5 100644 --- a/components/apps/ddm/DDM.tsx +++ b/components/apps/ddm/DDM.tsx @@ -29,10 +29,10 @@ export default function DDM(props) { const [selectedProvider, setSelectedProvider] = React.useState(''); const [selectedDataset, setSelectedDataset] = React.useState(''); const [selectedWallet, setSelectedWallet] = React.useState(''); - const [datasets, setDatasets] = React.useState([]); - const [providers, setProviders] = React.useState([]); - const [replications, setReplications] = React.useState([]); - const [wallets, setWallets] = React.useState([]); + const [datasets, setDatasets] = React.useState(undefined); + const [providers, setProviders] = React.useState(undefined); + const [replications, setReplications] = React.useState(undefined); + const [wallets, setWallets] = React.useState(undefined); const [health, setHealth] = React.useState(undefined); const [commitHash, setCommitHash] = React.useState(undefined); const [appTooltipState, setAppTooltipState] = React.useState(0); @@ -46,12 +46,12 @@ export default function DDM(props) { }; const getReplicationsConfig = React.useRef(); - const setGetReplicationsConfig = (cfg: GetReplicationsConfig) => getReplicationsConfig.current = cfg; + const setGetReplicationsConfig = (cfg: GetReplicationsConfig) => (getReplicationsConfig.current = cfg); - const updateDatasets = async () => setDatasets(await getDatasets()); - const updateProviders = async () => setProviders(await getProviders()); - const updateReplications = async () => setReplications(await getReplications(getReplicationsConfig.current)); - const updateWallets = async () => setWallets(await getWallets()); + const updateDatasets = async () => { setDatasets(undefined); setDatasets(await getDatasets()) }; + const updateProviders = async () => { setProviders(undefined); setProviders(await getProviders()) }; + const updateReplications = async () => { setReplications(undefined); setReplications(await getReplications(getReplicationsConfig.current)) }; + const updateWallets = async () => { setWallets(undefined); setWallets(await getWallets()) }; const newDatasetButton = React.useRef(null); const addProviderButton = React.useRef(null); @@ -67,10 +67,7 @@ export default function DDM(props) { } function dismissTooltip(id) { - console.log('closing ' + id + ', currently active ' + appTooltipState); - if (appTooltipState === id) { - setAppTooltipState(0); - } + setAppTooltipState((prev) => (prev === id ? 0 : prev)); } React.useEffect(() => { @@ -92,7 +89,7 @@ export default function DDM(props) { if (authToken) { updateDatasets(); updateProviders(); - updateReplications(); + // updateReplications(); updateWallets(); updateHealth(); @@ -158,7 +155,14 @@ export default function DDM(props) { placeholder="(example: f0123456)" /> )} - {appNavigationState === navigationStates.replications && } + {appNavigationState === navigationStates.replications && ( + + )} {appNavigationState === navigationStates.wallets && ( void, setGetReplicationsConfig: (cfg: GetReplicationsConfig) => void }) { +export default function Replications(props: { + replications: any, + updateReplications: () => void, + getReplicationsConfig: GetReplicationsConfig, + setGetReplicationsConfig: (cfg: GetReplicationsConfig) => void, +}) { const [searchDatasets, setSearchDatasets] = React.useState(''); const [searchProviders, setSearchProviders] = React.useState(''); const [searchTimeMin, setSearchTimeMin] = React.useState(''); @@ -20,9 +25,18 @@ export default function Replications(props: { replications: any[], updateReplica const [searchProposalCID, setSearchProposalCID] = React.useState(''); const [searchPieceCID, setSearchPieceCID] = React.useState(''); const [searchMessage, setSearchMessage] = React.useState(''); + const [offset, setOffset] = React.useState(0); + const limit = 5; + + React.useEffect(() => { + applySearch(); + props.updateReplications(); + }, [offset]); function applySearch() { props.setGetReplicationsConfig({ + offset: offset, + limit: limit, datasets: searchDatasets.split(',').map((dataset) => dataset.trim()), providers: searchProviders.split(',').map((provider) => provider.trim()), timeMin: searchTimeMin && new Date(searchTimeMin), @@ -82,7 +96,7 @@ export default function Replications(props: { replications: any[], updateReplica
Piece CID (CommP)
Message
- {props.replications?.map((replication, i) => { + {props.replications?.data.map((replication, i) => { return (
@@ -102,7 +116,45 @@ export default function Replications(props: { replications: any[], updateReplica })}
} + { + setOffset(offset); + }} + limit={limit} + total={props.replications?.totalCount} + /> {props.replications === undefined && }
); } + +function PageIndex(props: { offset: number, onChangeOffset: (number) => void, limit: number, total: number }) { + const pageCount = Math.ceil(props.total / props.limit); + const currPage = Math.floor(props.offset / props.limit); + + const itemCount = Math.min(props.limit, props.total - props.offset); + const firstItem = props.offset + 1; + const lastItem = props.offset + itemCount; + + return ( +
+ + Showing {firstItem} - {lastItem} of {props.total} results + + props.onChangeOffset(props.offset - props.limit)}> + < + + {...Array(pageCount || 1) + .fill(0) + .map((_, i) => ( + props.onChangeOffset(i * props.limit)}> + {i + 1} + + ))} + props.onChangeOffset(props.offset + props.limit)}> + > + +
+ ); +} diff --git a/data/api.ts b/data/api.ts index 81ba4d8..464b918 100644 --- a/data/api.ts +++ b/data/api.ts @@ -169,6 +169,8 @@ export async function updateProvider(id: string, name: string, allowSelfService: } export interface GetReplicationsConfig { + offset: number, + limit: number, datasets: string[]; providers: string[]; timeMin: Date; @@ -183,6 +185,8 @@ export async function getReplications(cfg: GetReplicationsConfig) { if (cfg) { path += '?' + new URLSearchParams({ + offset: cfg.offset.toString(), + limit: cfg.limit.toString(), datasets: cfg.datasets?.join(','), providers: cfg.providers?.join(','), deal_time_start: cfg.timeMin && Math.floor(cfg.timeMin.getTime() / 1000).toString(), From ebd78189e1a8e35f523dcfc81e7899ae13fc59b4 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Tue, 16 May 2023 12:13:45 +0900 Subject: [PATCH 19/21] pagination poc final --- .../apps/ddm/scenes/Replications.module.scss | 13 +++++++++++++ components/apps/ddm/scenes/Replications.tsx | 16 ++++++++-------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/components/apps/ddm/scenes/Replications.module.scss b/components/apps/ddm/scenes/Replications.module.scss index 4ffe59f..9a93546 100644 --- a/components/apps/ddm/scenes/Replications.module.scss +++ b/components/apps/ddm/scenes/Replications.module.scss @@ -39,4 +39,17 @@ &:hover { cursor: pointer; } +} + +.pageIndex { + margin: 1.5rem; +} + +.indexButtonActive { + @extend .indexButton; + + // color: var(--color-text); + font-size: 1.2em; + font-weight: bold; + text-decoration: underline; } \ No newline at end of file diff --git a/components/apps/ddm/scenes/Replications.tsx b/components/apps/ddm/scenes/Replications.tsx index 4b54d19..a484e4a 100644 --- a/components/apps/ddm/scenes/Replications.tsx +++ b/components/apps/ddm/scenes/Replications.tsx @@ -26,7 +26,7 @@ export default function Replications(props: { const [searchPieceCID, setSearchPieceCID] = React.useState(''); const [searchMessage, setSearchMessage] = React.useState(''); const [offset, setOffset] = React.useState(0); - const limit = 5; + const limit = 100; React.useEffect(() => { applySearch(); @@ -96,7 +96,7 @@ export default function Replications(props: {
Piece CID (CommP)
Message
- {props.replications?.data.map((replication, i) => { + {props.replications?.data?.map((replication, i) => { return (
@@ -138,22 +138,22 @@ function PageIndex(props: { offset: number, onChangeOffset: (number) => void, li const lastItem = props.offset + itemCount; return ( -
+
Showing {firstItem} - {lastItem} of {props.total} results - props.onChangeOffset(props.offset - props.limit)}> - < + props.onChangeOffset(0)}> + << {...Array(pageCount || 1) .fill(0) .map((_, i) => ( - props.onChangeOffset(i * props.limit)}> + props.onChangeOffset(i * props.limit)}> {i + 1} ))} - props.onChangeOffset(props.offset + props.limit)}> - > + props.onChangeOffset((pageCount - 1) * props.limit)}> + >>
); From b41122c977541f9039b3c2ba823e564dc5feb455 Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Wed, 17 May 2023 11:43:39 +0900 Subject: [PATCH 20/21] self service only search filter --- components/apps/ddm/scenes/Replications.tsx | 9 +++++++++ data/api.ts | 2 ++ 2 files changed, 11 insertions(+) diff --git a/components/apps/ddm/scenes/Replications.tsx b/components/apps/ddm/scenes/Replications.tsx index a484e4a..dbf13d4 100644 --- a/components/apps/ddm/scenes/Replications.tsx +++ b/components/apps/ddm/scenes/Replications.tsx @@ -11,6 +11,7 @@ import LoadingIndicator from '@components/LoadingIndicator'; import ProviderRef from '@components/ProviderRef'; import Button from '@components/Button'; import { GetReplicationsConfig, updateProvider } from '@root/data/api'; +import Select from '@root/components/basic/Select'; export default function Replications(props: { replications: any, @@ -22,6 +23,7 @@ export default function Replications(props: { const [searchProviders, setSearchProviders] = React.useState(''); const [searchTimeMin, setSearchTimeMin] = React.useState(''); const [searchTimeMax, setSearchTimeMax] = React.useState(''); + const [searchSelfService, setSearchSelfService] = React.useState(''); const [searchProposalCID, setSearchProposalCID] = React.useState(''); const [searchPieceCID, setSearchPieceCID] = React.useState(''); const [searchMessage, setSearchMessage] = React.useState(''); @@ -41,6 +43,7 @@ export default function Replications(props: { providers: searchProviders.split(',').map((provider) => provider.trim()), timeMin: searchTimeMin && new Date(searchTimeMin), timeMax: searchTimeMax && new Date(searchTimeMax), + selfService: !!searchSelfService, proposalCID: searchProposalCID.trim(), pieceCID: searchPieceCID.trim(), message: searchMessage.trim(), @@ -67,6 +70,12 @@ export default function Replications(props: {
setSearchTimeMax(e.target.value)} />
+
+ +
diff --git a/data/api.ts b/data/api.ts index 209d616..2a2c6cf 100644 --- a/data/api.ts +++ b/data/api.ts @@ -175,6 +175,7 @@ export interface GetReplicationsConfig { providers: string[]; timeMin: Date; timeMax: Date; + selfService: boolean, proposalCID: string; pieceCID: string; message: string; @@ -191,6 +192,7 @@ export async function getReplications(cfg: GetReplicationsConfig) { providers: cfg.providers?.join(','), deal_time_start: cfg.timeMin && Math.floor(cfg.timeMin.getTime() / 1000).toString(), deal_time_end: cfg.timeMax && Math.floor(cfg.timeMax.getTime() / 1000).toString(), + self_service: cfg.selfService.toString(), proposal_cid: cfg.proposalCID, piece_cid: cfg.pieceCID, message: cfg.message, From a96a103671792fda9279ce20de806d30609b673d Mon Sep 17 00:00:00 2001 From: Elijah Seed-Arita Date: Wed, 17 May 2023 11:45:31 +0900 Subject: [PATCH 21/21] tmp disable app switching --- components/Main.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/Main.tsx b/components/Main.tsx index 8485edf..7da61e9 100644 --- a/components/Main.tsx +++ b/components/Main.tsx @@ -12,6 +12,9 @@ import { getCookie, setCookie } from '@root/modules/cookies'; export default function Main(props) { const [activeApp, _setActiveApp] = React.useState(apps.ddm); function setActiveApp(app: string) { + // TODO: app switching temporarily disabled + return; + _setActiveApp(app); setCookie('active-app', app); }