Implemented routing instead of internal state
All checks were successful
/ Build the server (push) Successful in 3m5s
All checks were successful
/ Build the server (push) Successful in 3m5s
This commit is contained in:
parent
7334bd8e71
commit
c8b2ae30c8
20
frontend/package-lock.json
generated
20
frontend/package-lock.json
generated
@ -11,6 +11,7 @@
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"filesize": "^10.1.0",
|
||||
"qrcode-svg": "^1.1.0",
|
||||
"svelte-spa-router": "^3.3.0",
|
||||
"tailwind-merge": "^1.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -2619,6 +2620,14 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/regexparam": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.1.tgz",
|
||||
"integrity": "sha512-zRgSaYemnNYxUv+/5SeoHI0eJIgTL/A2pUtXUPLHQxUldagouJ9p+K6IbIZ/JiQuCEv2E2B1O11SjVQy3aMCkw==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/relateurl": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
|
||||
@ -3037,6 +3046,17 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-spa-router": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-spa-router/-/svelte-spa-router-3.3.0.tgz",
|
||||
"integrity": "sha512-cwRNe7cxD43sCvSfEeaKiNZg3FCizGxeMcf7CPiWRP3jKXjEma3vxyyuDtPOam6nWbVxl9TNM3hlE/i87ZlqcQ==",
|
||||
"dependencies": {
|
||||
"regexparam": "2.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ItalyPaleAle"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.draggable.js": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
|
||||
|
@ -35,6 +35,7 @@
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"filesize": "^10.1.0",
|
||||
"qrcode-svg": "^1.1.0",
|
||||
"svelte-spa-router": "^3.3.0",
|
||||
"tailwind-merge": "^1.14.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,24 @@
|
||||
<script lang="ts">
|
||||
import {changeStateFunction, error_banner, info_banner, rpc, session, show_working, state, StateE, token, workingWrapperO} from './store';
|
||||
import {Banner, Navbar, Spinner} from 'flowbite-svelte';
|
||||
import {error_banner, info_banner, rpc, session, show_working, token, workingWrapperO} from './store';
|
||||
import {Banner, Navbar, NavBrand, Spinner} from 'flowbite-svelte';
|
||||
import Router, {replace} from 'svelte-spa-router';
|
||||
import {routes} from './routes';
|
||||
import {FileStorage} from './icons';
|
||||
import LinkButton from './components/LinkButton.svelte';
|
||||
import Login from './pages/Login.svelte';
|
||||
import Signup from './pages/Signup.svelte';
|
||||
import ResetPassword from './pages/ResetPassword.svelte';
|
||||
import Profile from './pages/Profile.svelte';
|
||||
import TfaSetup from './pages/TfaSetup.svelte';
|
||||
import Admin from './pages/Admin.svelte';
|
||||
import View from './pages/View.svelte';
|
||||
import A from './components/A.svelte';
|
||||
|
||||
const s = session.s;
|
||||
|
||||
function homeClick() {
|
||||
if ($token == null)
|
||||
$state.s = StateE.LOGIN;
|
||||
else
|
||||
$state = { s: StateE.VIEW, view_node: 0 };
|
||||
}
|
||||
|
||||
async function leaveSudo() {
|
||||
await workingWrapperO(() => rpc.Admin_unsudo($token ?? ''));
|
||||
await session.update($token);
|
||||
state.set({s: StateE.ADMIN, view_node: 0});
|
||||
await replace('/admin');
|
||||
}
|
||||
|
||||
function logout() {
|
||||
rpc.Auth_logout($token ?? '');
|
||||
token.set(null);
|
||||
}
|
||||
|
||||
homeClick();
|
||||
</script>
|
||||
|
||||
<main class="h-screen w-screen p-4 flex flex-col">
|
||||
@ -49,43 +36,22 @@
|
||||
<Banner position="absolute" dismissable={false}><Spinner size="5" class="mr-2" />Working</Banner>
|
||||
{/if}
|
||||
<Navbar class="flex-grow-0">
|
||||
<button on:click={homeClick} id="home-button" class="flex items-center">
|
||||
<NavBrand href={$token == null ? '#/login' : '#/view/0'}>
|
||||
<FileStorage width="1.5em" height="1.5em"/>
|
||||
<span id="navbar-text">MFileserver</span>
|
||||
</button>
|
||||
<span id="navbar-text" class="ml-2">MFileserver</span>
|
||||
</NavBrand>
|
||||
|
||||
{#if $token != null}
|
||||
<div class="flex md:order-2">
|
||||
<div class="flex md:order-2 gap-x-2">
|
||||
{#if $s?.sudo} <LinkButton on:click={leaveSudo}>Leave sudo</LinkButton> {/if}
|
||||
{#if $s?.admin} <LinkButton on:click={changeStateFunction(StateE.ADMIN)}>Admin</LinkButton> {/if}
|
||||
<LinkButton on:click={changeStateFunction(StateE.VIEW, 0)}>Files</LinkButton>
|
||||
<LinkButton on:click={changeStateFunction(StateE.PROFILE)}>Profile</LinkButton>
|
||||
{#if $s?.admin} <A href="#/admin">Admin</A> {/if}
|
||||
<A href="#/view/0">Files</A>
|
||||
<A href="#/profile">Profile</A>
|
||||
<LinkButton on:click={logout}>Logout</LinkButton>
|
||||
</div>
|
||||
{/if}
|
||||
</Navbar>
|
||||
<span class="grid justify-items-center mt-10">
|
||||
{#if $state.s === StateE.LOGIN } <Login/>
|
||||
{:else if $state.s === StateE.SIGNUP} <Signup/>
|
||||
{:else if $state.s === StateE.RESET_PASSWORD} <ResetPassword/>
|
||||
{:else if $state.s === StateE.PROFILE} <Profile/>
|
||||
{:else if $state.s === StateE.TFA_SETUP} <TfaSetup/>
|
||||
{:else if $state.s === StateE.ADMIN} <Admin/>
|
||||
{:else if $state.s === StateE.VIEW} <View/>
|
||||
{:else} <span>You are in state {$state.s}, which should not be possible, please report this.</span>
|
||||
{/if}
|
||||
<Router {routes} />
|
||||
</span>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
#navbar-text {
|
||||
margin-left: 0.5em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#home-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
8
frontend/src/components/A.svelte
Normal file
8
frontend/src/components/A.svelte
Normal file
@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import {A} from 'flowbite-svelte';
|
||||
export let href: string;
|
||||
</script>
|
||||
|
||||
<A {href} aClass="hover:text-primary-400 transition-colors">
|
||||
<slot></slot>
|
||||
</A>
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {rpc, show_working, state, token} from '../store';
|
||||
import {rpc, show_working, token} from '../store';
|
||||
import {Button, ButtonGroup, Modal} from 'flowbite-svelte';
|
||||
import {afterUpdate} from 'svelte';
|
||||
import {afterUpdate, createEventDispatcher} from 'svelte';
|
||||
|
||||
let show_confirm = false;
|
||||
let show_modal = false;
|
||||
@ -9,6 +9,8 @@
|
||||
let text = '';
|
||||
let nodes: number[] = [];
|
||||
|
||||
const dispatch = createEventDispatcher<{reload_node: null}>();
|
||||
|
||||
async function real_delete() {
|
||||
show_confirm = false;
|
||||
show_modal = true;
|
||||
@ -27,7 +29,7 @@
|
||||
|
||||
show_working.set(false);
|
||||
show_modal = false;
|
||||
state.update(v => v);
|
||||
dispatch('reload_node');
|
||||
}
|
||||
|
||||
export const del = async (n: number[]) => {
|
||||
|
@ -4,11 +4,12 @@
|
||||
</script>
|
||||
<script lang="ts">
|
||||
import {Checkbox, Dropdown, DropdownItem, Spinner, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell, Tooltip} from 'flowbite-svelte';
|
||||
import {Folder, FolderParent, DocumentBlank, CaretLeft} from '../icons';
|
||||
import {filesize} from 'filesize';
|
||||
import {api, changeStateFunction, download, StateE, token, rpc} from '../store';
|
||||
import {Folder, FolderParent, DocumentBlank, CaretLeft} from '../icons';
|
||||
import {api, download, token, rpc} from '../store';
|
||||
import LinkButton from './LinkButton.svelte';
|
||||
import DeleteModal from './DeleteModal.svelte';
|
||||
import A from './A.svelte';
|
||||
|
||||
export let node: api.Node;
|
||||
|
||||
@ -43,11 +44,12 @@
|
||||
$: ctx_style = `top: ${ctx_y}px; left: ${ctx_x}px; position: fixed;`;
|
||||
|
||||
function onCtxMenu(node: api.Node, e: MouseEvent) {
|
||||
console.log(e);
|
||||
e.preventDefault();
|
||||
if (!ctx_hidden)
|
||||
return ctx_hidden = true;
|
||||
ctx_x = e.pageX;
|
||||
ctx_y = e.pageY;
|
||||
ctx_x = e.clientX;
|
||||
ctx_y = e.clientY;
|
||||
ctx_node = node;
|
||||
ctx_hidden = false;
|
||||
}
|
||||
@ -70,7 +72,7 @@
|
||||
|
||||
<svelte:body on:click={() => (ctx_hidden = true)} />
|
||||
|
||||
<DeleteModal bind:del={del} />
|
||||
<DeleteModal bind:del={del} on:reload_node />
|
||||
|
||||
<Table hoverable>
|
||||
<TableHead theadClass="text-xs">
|
||||
@ -86,7 +88,7 @@
|
||||
<TableBodyRow>
|
||||
<TableBodyCell class="!p-4"></TableBodyCell>
|
||||
<TableBodyCell class="px-2 w-0"><FolderParent /></TableBodyCell>
|
||||
<TableBodyCell class="pl-0"><LinkButton on:click={changeStateFunction(StateE.VIEW, node.parent ?? 0)}>..</LinkButton></TableBodyCell>
|
||||
<TableBodyCell class="pl-0"><A href={'#/view/' + node.parent}>..</A></TableBodyCell>
|
||||
<TableBodyCell></TableBodyCell>
|
||||
</TableBodyRow>
|
||||
{/if}
|
||||
@ -94,7 +96,7 @@
|
||||
<TableBodyRow on:contextmenu={onCtxMenu.bind(null, node)}>
|
||||
<TableBodyCell class="p-2 pl-4 w-0 h-0"><Checkbox bind:group={selected} value={node.id}/></TableBodyCell>
|
||||
<TableBodyCell class="px-2 w-0"><Folder /></TableBodyCell>
|
||||
<TableBodyCell class="pl-0"><LinkButton on:click={changeStateFunction(StateE.VIEW, node.id)}>{node.name}</LinkButton></TableBodyCell>
|
||||
<TableBodyCell class="pl-0"><A href={'#/view/' + node.id}>{node.name}</A></TableBodyCell>
|
||||
<TableBodyCell></TableBodyCell>
|
||||
</TableBodyRow>
|
||||
{/each}
|
||||
@ -112,7 +114,7 @@
|
||||
<DocumentBlank />
|
||||
{/if}
|
||||
</TableBodyCell>
|
||||
<TableBodyCell class="pl-0"><LinkButton on:click={changeStateFunction(StateE.VIEW, node.id)}>{node.name}</LinkButton></TableBodyCell>
|
||||
<TableBodyCell class="pl-0"><A href={'#/view/' + node.id}>{node.name}</A></TableBodyCell>
|
||||
<TableBodyCell>{filesize(node.size ?? 0, {base: 2, standard: 'jedec'})}</TableBodyCell>
|
||||
</TableBodyRow>
|
||||
{/each}
|
||||
|
@ -16,7 +16,7 @@
|
||||
.link-button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0 0.25em;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,7 +1,11 @@
|
||||
<script lang="ts">
|
||||
import {state, token, type UploadFile} from '../store';
|
||||
import {token, type UploadFile} from '../store';
|
||||
import {Button, Modal, Progressbar} from 'flowbite-svelte';
|
||||
import {filesize} from 'filesize';
|
||||
import {createEventDispatcher} from 'svelte';
|
||||
|
||||
|
||||
const dispatch = createEventDispatcher<{reload_node: null}>();
|
||||
|
||||
interface MyFile extends UploadFile {
|
||||
waiting: boolean,
|
||||
@ -24,7 +28,7 @@
|
||||
|
||||
function close() {
|
||||
show_modal = false;
|
||||
state.update(v => v);
|
||||
dispatch('reload_node');
|
||||
}
|
||||
|
||||
async function realUpload(file: MyFile) {
|
||||
|
@ -1,9 +1,10 @@
|
||||
import "./app.pcss";
|
||||
import App from "./App.svelte";
|
||||
import {state, StateE, token} from './store';
|
||||
import {token} from './store';
|
||||
import {replace} from 'svelte-spa-router';
|
||||
|
||||
token.subscribe(v => {
|
||||
if (v == null) state.set({s: StateE.LOGIN, view_node: 0});
|
||||
if (v == null) replace('/login').then()
|
||||
});
|
||||
|
||||
const app = new App({
|
||||
|
@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import {api, rpc, session, state, StateE, token, workingWrapperO, workingWrapperR} from '../store';
|
||||
import {api, rpc, session, token, workingWrapperO, workingWrapperR} from '../store';
|
||||
import {Checkbox, Table, TableBody, TableBodyCell, TableBodyRow, TableHead, TableHeadCell} from 'flowbite-svelte';
|
||||
import {Checkmark, Error} from '../icons';
|
||||
import LinkButton from '../components/LinkButton.svelte';
|
||||
import {replace} from 'svelte-spa-router';
|
||||
|
||||
let users: api.UserInfo[] = [];
|
||||
|
||||
@ -25,7 +26,7 @@
|
||||
async function sudo(user: number) {
|
||||
if (await workingWrapperO(() => rpc.Admin_sudo($token ?? '', user))) {
|
||||
await session.update($token);
|
||||
state.set({s: StateE.VIEW, view_node: 0});
|
||||
await replace('/view/0');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import {Button, ButtonGroup, Card, Input, InputAddon} from 'flowbite-svelte';
|
||||
import {Email, OTP, Password} from '../icons';
|
||||
import {changeStateFunction, rpc, state, StateE, token, workingWrapperR, api} from '../store';
|
||||
import {rpc, token, workingWrapperR, api} from '../store';
|
||||
import {replace} from 'svelte-spa-router';
|
||||
|
||||
let ask_tfa = false;
|
||||
let username = '', password = '', tfa = '';
|
||||
@ -14,7 +15,7 @@
|
||||
return;
|
||||
}
|
||||
token.set(resp.token);
|
||||
state.set({s: StateE.VIEW, view_node: 0});
|
||||
await replace('/view/0');
|
||||
}
|
||||
|
||||
function keyUp(e: KeyboardEvent) {
|
||||
@ -41,9 +42,9 @@
|
||||
<Input type="password" placeholder="Password" bind:value={password} on:keyup={keyUp}></Input>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup class="w-full flex flex-nowrap">
|
||||
<Button class="flex-1 flex-grow" color="primary" outline on:click={changeStateFunction(StateE.SIGNUP)}>Signup</Button>
|
||||
<Button class="flex-1 flex-grow" color="primary" outline href="#/signup">Signup</Button>
|
||||
<Button class="flex-1 flex-grow" color="primary" on:click={login}>Login</Button>
|
||||
<Button class="flex-1 flex-grow" color="primary" outline on:click={changeStateFunction(StateE.RESET_PASSWORD)}>Forget password</Button>
|
||||
<Button class="flex-1 flex-grow" color="primary" outline href="#/reset_pw">Forget password</Button>
|
||||
</ButtonGroup>
|
||||
{/if}
|
||||
</Card>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {changeStateFunction, error_banner, rpc, session, StateE, token, workingWrapperO} from '../store';
|
||||
import {error_banner, rpc, session, token, workingWrapperO} from '../store';
|
||||
import {Accordion, AccordionItem, Button, ButtonGroup, Input, InputAddon} from 'flowbite-svelte';
|
||||
import {Password} from '../icons';
|
||||
import {info_banner} from '../store.js';
|
||||
@ -69,7 +69,7 @@
|
||||
{#if tfa_enabled}
|
||||
<Button class="w-full" color="red" on:click={disableTfa}>Disable</Button>
|
||||
{:else}
|
||||
<Button class="w-full" color="green" on:click={changeStateFunction(StateE.TFA_SETUP)}>Enable</Button>
|
||||
<Button class="w-full" color="green" href="#/tfa">Enable</Button>
|
||||
{/if}
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import {Button, ButtonGroup, Card, Input, InputAddon} from 'flowbite-svelte';
|
||||
import {Email, EmailNew, Password} from '../icons';
|
||||
import {changeStateFunction, error_banner, info_banner, rpc, state, StateE, workingWrapper, workingWrapperO} from '../store';
|
||||
import {error_banner, info_banner, rpc, workingWrapper, workingWrapperO} from '../store';
|
||||
import {replace} from 'svelte-spa-router';
|
||||
|
||||
let enter_key = false;
|
||||
let username = '', key = '', password = '', password2 = '';
|
||||
@ -20,7 +21,7 @@
|
||||
}
|
||||
|
||||
if (await workingWrapperO(() => rpc.Auth_reset_password(key, password)))
|
||||
$state.s = StateE.LOGIN;
|
||||
await replace('/login');
|
||||
}
|
||||
|
||||
function keyUp(e: KeyboardEvent) {
|
||||
@ -49,7 +50,7 @@
|
||||
<Input type="password" placeholder="Repeat password" bind:value={password2} on:keyup={keyUp}></Input>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup class="w-full flex flex-nowrap">
|
||||
<Button class="flex-1 flex-grow" color="primary" outline on:click={changeStateFunction(StateE.LOGIN)}>Login</Button>
|
||||
<Button class="flex-1 flex-grow" color="primary" outline href="#/login">Login</Button>
|
||||
<Button class="flex-1 flex-grow" color="primary" on:click={changePw}>Change password</Button>
|
||||
</ButtonGroup>
|
||||
{:else}
|
||||
@ -58,7 +59,7 @@
|
||||
<Input type="email" placeholder="Email" bind:value={username} on:keyup={keyUp}></Input>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup class="w-full flex flex-nowrap">
|
||||
<Button class="flex-1 flex-grow" color="primary" outline on:click={changeStateFunction(StateE.LOGIN)}>Login</Button>
|
||||
<Button class="flex-1 flex-grow" color="primary" outline href="#/login">Login</Button>
|
||||
<Button class="flex-1 flex-grow" color="primary" on:click={sendKey}>Send recovery key</Button>
|
||||
<Button class="flex-1 flex-grow" color="primary" outline on:click={() => (enter_key = true)}>Enter key</Button>
|
||||
</ButtonGroup>
|
||||
|
@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import {Button, ButtonGroup, Card, Input, InputAddon} from 'flowbite-svelte';
|
||||
import {Email, Password} from '../icons';
|
||||
import {changeStateFunction, error_banner, info_banner, rpc, state, StateE, workingWrapperO} from '../store';
|
||||
import {error_banner, info_banner, rpc, workingWrapperO} from '../store';
|
||||
import {replace} from 'svelte-spa-router';
|
||||
|
||||
let username = '', username2 = '', password = '', password2 = '';
|
||||
|
||||
@ -20,7 +21,7 @@
|
||||
|
||||
if (resp) {
|
||||
info_banner.set('Account created, please wait till an administrator approves it');
|
||||
$state.s = StateE.LOGIN;
|
||||
await replace('/login');
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +49,7 @@
|
||||
<Input type="password" placeholder="Repeat password" bind:value={password2} on:keyup={keyUp}></Input>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup class="w-full flex flex-nowrap">
|
||||
<Button class="flex-1 flex-grow" color="primary" outline on:click={changeStateFunction(StateE.LOGIN)}>Login</Button>
|
||||
<Button class="flex-1 flex-grow" color="primary" outline href="#/login">Login</Button>
|
||||
<Button class="flex-1 flex-grow" color="primary" on:click={signup}>Singup</Button>
|
||||
</ButtonGroup>
|
||||
</Card>
|
||||
|
@ -1,8 +1,9 @@
|
||||
<script lang="ts">
|
||||
import {Button, ButtonGroup, Card, Input, InputAddon, StepIndicator, Tooltip} from 'flowbite-svelte';
|
||||
import {OTP} from '../icons';
|
||||
import {info_banner, rpc, session, state, StateE, token, workingWrapperO, workingWrapperR} from '../store';
|
||||
import {info_banner, rpc, session, token, workingWrapperO, workingWrapperR} from '../store';
|
||||
import QRCode from 'qrcode-svg';
|
||||
import {replace} from 'svelte-spa-router';
|
||||
|
||||
const s = session.s;
|
||||
|
||||
@ -35,7 +36,6 @@
|
||||
async function completeSetup() {
|
||||
if (await workingWrapperO(() => rpc.Auth_tfa_complete($token ?? '', code))) {
|
||||
info_banner.set("Successfully set up two factor authentication");
|
||||
$state.s = StateE.LOGIN;
|
||||
token.set(null);
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,38 @@
|
||||
<script lang="ts">
|
||||
import {Breadcrumb, Dropzone, Modal, Progressbar} from 'flowbite-svelte';
|
||||
import {derived} from 'svelte/store';
|
||||
import {writable} from 'svelte/store';
|
||||
import {CloudUpload} from '../icons';
|
||||
import {api, changeStateFunction, rpc, state, StateE, token, type UploadFile, workingWrapperR} from '../store';
|
||||
import LinkButton from '../components/LinkButton.svelte';
|
||||
import {api, rpc, token, type UploadFile, workingWrapperR} from '../store';
|
||||
import DirViewer from '../components/DirViewer.svelte';
|
||||
import UploadModal from '../components/UploadModal.svelte';
|
||||
import FileViewer from '../components/FileViewer.svelte';
|
||||
import A from '../components/A.svelte';
|
||||
|
||||
interface Data {
|
||||
node: api.Node|null,
|
||||
segments: api.PathSegment[]
|
||||
}
|
||||
|
||||
let data = derived<typeof state, Data>(
|
||||
state,
|
||||
($state, set) => {
|
||||
(async () => {
|
||||
let node = await workingWrapperR<api.Node>(() => rpc.FS_get_node($token ?? '', $state.view_node));
|
||||
if (!node)
|
||||
return $state.view_node = 0;
|
||||
let segments = await workingWrapperR<api.PathSegment[]>(() => rpc.FS_get_path($token ?? '', node!.id));
|
||||
if (!segments)
|
||||
return $state.view_node = 0;
|
||||
set({node: node as Data['node'], segments });
|
||||
})();
|
||||
},
|
||||
{ node: null, segments: [] }
|
||||
);
|
||||
export let params: {id?: string}|undefined = {};
|
||||
$: {
|
||||
let id = 0;
|
||||
if (params && params.id) {
|
||||
id = parseInt(params.id);
|
||||
if (id >= 0)
|
||||
updateData(id);
|
||||
}
|
||||
}
|
||||
|
||||
const data = writable<Data>({node: null, segments: []});
|
||||
async function updateData(id: number) {
|
||||
let node = await workingWrapperR<api.Node>(() => rpc.FS_get_node($token ?? '', id));
|
||||
if (!node)
|
||||
return;
|
||||
let segments = await workingWrapperR<api.PathSegment[]>(() => rpc.FS_get_path($token ?? '', id));
|
||||
if (!segments)
|
||||
return;
|
||||
data.set({node: node as Data['node'], segments });
|
||||
}
|
||||
|
||||
const getFile = async (entry: FileSystemEntry) => new Promise<File>((o, e) => (entry as FileSystemFileEntry).file(o, e));
|
||||
|
||||
@ -70,7 +75,7 @@
|
||||
const files: UploadFile[] = [];
|
||||
for (const f of input.files)
|
||||
files.push({
|
||||
id: $state.view_node,
|
||||
id: $data.node?.id ?? 0,
|
||||
name: f.name,
|
||||
full_name: f.name,
|
||||
file: f,
|
||||
@ -89,7 +94,7 @@
|
||||
if (!entry)
|
||||
console.error("Failed to get entry for: ", i);
|
||||
else
|
||||
files.push(...await handleEntry($state.view_node, '', entry));
|
||||
files.push(...await handleEntry($data.node?.id ?? 0, '', entry));
|
||||
}
|
||||
await upload(files);
|
||||
}
|
||||
@ -120,7 +125,7 @@
|
||||
{#if i > 0}<li class="inline-flex items-center">/</li>{/if}
|
||||
<li class="inline-flex items-center">
|
||||
{#if segment.id !== null}
|
||||
<LinkButton on:click={changeStateFunction(StateE.VIEW, segment.id)}>{segment.id === 0 ? 'Files' : segment.name}</LinkButton>
|
||||
<A href={'#/view/' + segment.id}>{segment.id === 0 ? 'Files' : segment.name}</A>
|
||||
{:else}
|
||||
<span style="padding: 0 0.25em;">{segment.name}</span>
|
||||
{/if}
|
||||
@ -142,10 +147,10 @@
|
||||
{:else if $data.node.file}
|
||||
<FileViewer node={$data.node} />
|
||||
{:else}
|
||||
<DirViewer node={$data.node} />
|
||||
<DirViewer node={$data.node} on:reload_node={() => updateData($data.node?.id ?? 0)} />
|
||||
{/if}
|
||||
</div>
|
||||
<UploadModal bind:upload={real_upload}/>
|
||||
<UploadModal bind:upload={real_upload} on:reload_node={() => updateData($data.node?.id ?? 0)} />
|
||||
{#if upload_progress_data.current !== upload_progress_data.total}
|
||||
<Modal open dismissable={false} title="Creating files">
|
||||
<div class="mb-1 flex justify-between">
|
||||
|
17
frontend/src/routes.ts
Normal file
17
frontend/src/routes.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import Login from './pages/Login.svelte';
|
||||
import Signup from './pages/Signup.svelte';
|
||||
import ResetPassword from './pages/ResetPassword.svelte';
|
||||
import Profile from './pages/Profile.svelte';
|
||||
import TfaSetup from './pages/TfaSetup.svelte';
|
||||
import Admin from './pages/Admin.svelte';
|
||||
import View from './pages/View.svelte';
|
||||
|
||||
export const routes = {
|
||||
'/login': Login,
|
||||
'/signup': Signup,
|
||||
'/reset_pw': ResetPassword,
|
||||
'/profile': Profile,
|
||||
'/tfa': TfaSetup,
|
||||
'/admin': Admin,
|
||||
'/view/:id': View
|
||||
}
|
@ -1,14 +1,9 @@
|
||||
import {MRPCConnector, type Session, type Response} from './api';
|
||||
import {get, type Writable, writable} from 'svelte/store';
|
||||
import {type Writable, writable} from 'svelte/store';
|
||||
import {filesize} from 'filesize';
|
||||
|
||||
export * as api from './api';
|
||||
|
||||
export enum StateE { LOGIN, SIGNUP, RESET_PASSWORD, PROFILE, TFA_SETUP, ADMIN, VIEW }
|
||||
export interface State {
|
||||
s: StateE,
|
||||
view_node: number
|
||||
}
|
||||
export interface UploadFile {
|
||||
id: number,
|
||||
name: string,
|
||||
@ -21,15 +16,12 @@ export const show_working = writable<boolean>(false);
|
||||
export const info_banner = writable<string>('');
|
||||
export const error_banner = writable<string>('');
|
||||
|
||||
export const state = writable<State>({s: StateE.LOGIN, view_node: 0});
|
||||
|
||||
export const rpc = new MRPCConnector('/mrpc');
|
||||
|
||||
export const token = writable<string|null>(localStorage.getItem('token'));
|
||||
export const session: { s: Writable<Session|null>, update: (token: string|null) => Promise<void> } = {
|
||||
s: writable(null),
|
||||
update: async (t: string|null) => {
|
||||
console.log('S');
|
||||
if (t == null) {
|
||||
session.s.set(null);
|
||||
return;
|
||||
@ -50,14 +42,6 @@ token.subscribe(v => {
|
||||
localStorage.setItem('token', v);
|
||||
})
|
||||
|
||||
|
||||
export function changeStateFunction(target: StateE, node?: number): () => void {
|
||||
return () => {
|
||||
const new_node = node ?? get(state).view_node;
|
||||
state.set({s: target, view_node: new_node});
|
||||
}
|
||||
}
|
||||
|
||||
export async function workingWrapper<T>(fn: () => Promise<T>): Promise<T|null> {
|
||||
let r = null;
|
||||
error_banner.set('');
|
||||
|
Loading…
Reference in New Issue
Block a user