Add access controls window

This commit is contained in:
2024-11-19 15:31:51 +01:00
parent 27882303d9
commit f7a0ff4538
41 changed files with 307 additions and 63 deletions

View File

@@ -29,6 +29,8 @@ export default [
file: `${builddir}/${name}.js`, file: `${builddir}/${name}.js`,
}, },
plugins: [ plugins: [
sveltePreprocess(),
svelte({ svelte({
preprocess: sveltePreprocess(), preprocess: sveltePreprocess(),
compilerOptions: { compilerOptions: {

View File

@@ -66,6 +66,7 @@ const share_tumblr = () => {
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
background: var(--shaded_background); background: var(--shaded_background);
backdrop-filter: blur(4px);
border-top-left-radius: 16px; border-top-left-radius: 16px;
border-bottom-left-radius: 16px; border-bottom-left-radius: 16px;
text-align: center; text-align: center;

View File

@@ -240,6 +240,7 @@ h1 {
flex-direction: row; flex-direction: row;
overflow: auto; overflow: auto;
background-color: var(--shaded_background); background-color: var(--shaded_background);
backdrop-filter: blur(4px);
padding: 0 2px 2px 2px; padding: 0 2px 2px 2px;
align-items: center; align-items: center;
} }
@@ -259,6 +260,7 @@ h1 {
} }
.compatibility_warning { .compatibility_warning {
background-color: var(--shaded_background); background-color: var(--shaded_background);
backdrop-filter: blur(4px);
border-bottom: 2px solid #6666FF; border-bottom: 2px solid #6666FF;
padding: 4px; padding: 4px;
} }

View File

@@ -1,5 +1,5 @@
<script> <script>
import { fs_encode_path } from "./FilesystemAPI"; import { fs_encode_path } from "./FilesystemAPI.mjs";
export let nav export let nav
</script> </script>

View File

@@ -2,7 +2,7 @@
import Chart from "../util/Chart.svelte"; import Chart from "../util/Chart.svelte";
import { formatDataVolume, formatDate, formatThousands } from "../util/Formatting.svelte"; import { formatDataVolume, formatDate, formatThousands } from "../util/Formatting.svelte";
import Modal from "../util/Modal.svelte"; import Modal from "../util/Modal.svelte";
import { fs_path_url, fs_timeseries } from "./FilesystemAPI"; import { fs_path_url, fs_timeseries } from "./FilesystemAPI.mjs";
import { generate_share_path, generate_share_url } from "./Sharebar.svelte"; import { generate_share_path, generate_share_url } from "./Sharebar.svelte";
import { color_by_name } from "../util/Util.svelte"; import { color_by_name } from "../util/Util.svelte";
import { tick } from "svelte"; import { tick } from "svelte";
@@ -121,7 +121,7 @@ let update_chart = async (base, timespan, interval) => {
</script> </script>
<Modal bind:visible={visible} title="Details" width={($nav.base.type === "file" ? 1000 : 750) + "px"}> <Modal bind:visible={visible} title="Details" width={($nav.base.type === "file" ? 1000 : 750) + "px"}>
<table style="width: 100%;"> <table>
<tbody> <tbody>
<tr> <tr>
<td>Name</td> <td>Name</td>
@@ -132,11 +132,15 @@ let update_chart = async (base, timespan, interval) => {
<td>{$nav.base.path}</td> <td>{$nav.base.path}</td>
</tr> </tr>
<tr> <tr>
<td>Created</td> <td>Created by user</td>
<td>{$nav.base.created_by}</td>
</tr>
<tr>
<td>Creation date</td>
<td>{formatDate($nav.base.created, true, true, true)}</td> <td>{formatDate($nav.base.created, true, true, true)}</td>
</tr> </tr>
<tr> <tr>
<td>Modified</td> <td>Modification date</td>
<td>{formatDate($nav.base.modified, true, true, true)}</td> <td>{formatDate($nav.base.modified, true, true, true)}</td>
</tr> </tr>
<tr> <tr>
@@ -216,6 +220,11 @@ let update_chart = async (base, timespan, interval) => {
</Modal> </Modal>
<style> <style>
table {
/* yes this sucks, sue me */
width: 98%;
margin: auto;
}
td:first-child { td:first-child {
word-break: keep-all; word-break: keep-all;
} }

View File

@@ -1,5 +1,5 @@
import { fs_get_node, fs_encode_path, fs_split_path } from "./FilesystemAPI"; import { fs_get_node, fs_encode_path, fs_split_path } from "./FilesystemAPI.mjs";
import type { FSNode, FSPath, FSPermissions, FSContext } from "./FilesystemAPI"; import type { FSNode, FSPath, FSPermissions, FSContext } from "./FilesystemAPI.mjs";
import type { Writable } from "svelte/store" import type { Writable } from "svelte/store"
export class FSNavigator { export class FSNavigator {

View File

@@ -1,7 +1,7 @@
<script> <script>
import { onMount } from "svelte"; import { onMount } from "svelte";
import { formatDataVolume, formatThousands } from "../util/Formatting.svelte" import { formatDataVolume, formatThousands } from "../util/Formatting.svelte"
import { fs_path_url } from "./FilesystemAPI"; import { fs_path_url } from "./FilesystemAPI.mjs";
export let nav export let nav

View File

@@ -7,7 +7,7 @@ import Breadcrumbs from './Breadcrumbs.svelte';
import DetailsWindow from './DetailsWindow.svelte'; import DetailsWindow from './DetailsWindow.svelte';
import FilePreview from './viewers/FilePreview.svelte'; import FilePreview from './viewers/FilePreview.svelte';
import FSUploadWidget from './upload_widget/FSUploadWidget.svelte'; import FSUploadWidget from './upload_widget/FSUploadWidget.svelte';
import { fs_path_url } from './FilesystemAPI'; import { fs_path_url } from './FilesystemAPI.mjs';
import Menu from './Menu.svelte'; import Menu from './Menu.svelte';
import { FSNavigator } from "./FSNavigator" import { FSNavigator } from "./FSNavigator"
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
@@ -226,6 +226,7 @@ const download = () => {
text-align: left; text-align: left;
box-shadow: none; box-shadow: none;
background-color: var(--shaded_background); background-color: var(--shaded_background);
backdrop-filter: blur(4px);
} }
/* File preview area (row 2) */ /* File preview area (row 2) */
@@ -243,6 +244,7 @@ const download = () => {
flex-direction: column; flex-direction: column;
text-align: center; text-align: center;
background-color: var(--shaded_background); background-color: var(--shaded_background);
backdrop-filter: blur(4px);
} }
/* This max-width needs to be synced with the .toolbar max-width in /* This max-width needs to be synced with the .toolbar max-width in

View File

@@ -22,6 +22,7 @@ export type FSNode = {
modified: Date, modified: Date,
mode_string: string, mode_string: string,
mode_octal: string, mode_octal: string,
created_by: string,
abuse_type: string | undefined, abuse_type: string | undefined,
abuse_report_time: Date | undefined, abuse_report_time: Date | undefined,
@@ -31,15 +32,16 @@ export type FSNode = {
sha256_sum: string, sha256_sum: string,
id: string | undefined, id: string | undefined,
read_password: string | undefined,
write_password: string | undefined,
properties: {} | undefined, properties: {} | undefined,
link_permissions: FSPermissions | undefined,
user_permissions: [string: FSPermissions] | undefined,
password_permissions: [string: FSPermissions] | undefined,
} }
export type FSPermissions = { export type FSPermissions = {
create: boolean, owner: boolean,
read: boolean, read: boolean,
update: boolean, write: boolean,
delete: boolean, delete: boolean,
} }
@@ -56,6 +58,12 @@ export type NodeOptions = {
modified: Date | undefined, modified: Date | undefined,
shared: boolean | undefined, shared: boolean | undefined,
// Permissions
link_permissions: FSPermissions | undefined,
user_permissions: FSPermissions | undefined,
password_permissions: FSPermissions | undefined,
// Branding
branding_enabled: boolean | undefined, branding_enabled: boolean | undefined,
brand_input_color: string | undefined, brand_input_color: string | undefined,
brand_highlight_color: string | undefined, brand_highlight_color: string | undefined,
@@ -116,8 +124,12 @@ export const fs_update = async (path: string, opts: NodeOptions) => {
form.append("action", "update") form.append("action", "update")
for (let key of Object.keys(opts)) { for (let key of Object.keys(opts)) {
if ((key === "created" || key === "modified") && opts[key] !== undefined) { if (opts[key] === undefined) {
continue
} else if ((key === "created" || key === "modified")) {
form.append(key, opts[key].toISOString()) form.append(key, opts[key].toISOString())
} else if (typeof opts[key] === "object") {
form.append(key, JSON.stringify(opts[key]))
} else { } else {
form.append(key, opts[key]) form.append(key, opts[key])
} }

View File

@@ -31,7 +31,7 @@ export const generate_share_path = path => {
</script> </script>
<script> <script>
import { fs_update, fs_encode_path } from "./FilesystemAPI"; import { fs_update, fs_encode_path } from "./FilesystemAPI.mjs";
export let visible = false export let visible = false
export let share_url = "" export let share_url = ""
@@ -134,6 +134,7 @@ const share_tumblr = () => {
overflow-x: hidden; overflow-x: hidden;
float: left; float: left;
background: var(--shaded_background); background: var(--shaded_background);
backdrop-filter: blur(4px);
text-align: center; text-align: center;
overflow: hidden; overflow: hidden;
opacity: 0; opacity: 0;

View File

@@ -106,7 +106,7 @@ let expand = e => {
{/if} {/if}
<!-- Share button is enabled when: The browser has a sharing API, or the user can edit the file (to enable sharing)--> <!-- Share button is enabled when: The browser has a sharing API, or the user can edit the file (to enable sharing)-->
{#if $nav.base.id !== "me" && (navigator.share !== undefined || $nav.permissions.update === true)} {#if $nav.base.id !== "me" && (navigator.share !== undefined || $nav.permissions.write === true)}
<button on:click={share}> <button on:click={share}>
<i class="icon">share</i> <i class="icon">share</i>
<span>Share</span> <span>Share</span>
@@ -133,7 +133,7 @@ let expand = e => {
<span>Deta<u>i</u>ls</span> <span>Deta<u>i</u>ls</span>
</button> </button>
{#if $nav.base.id !== "me" && $nav.permissions.update === true} {#if $nav.base.id !== "me" && $nav.permissions.write === true}
<button on:click={() => edit_window.edit(nav.base, true, "file")} class:button_highlight={edit_visible}> <button on:click={() => edit_window.edit(nav.base, true, "file")} class:button_highlight={edit_visible}>
<i class="icon">edit</i> <i class="icon">edit</i>
<span><u>E</u>dit</span> <span><u>E</u>dit</span>
@@ -149,6 +149,7 @@ let expand = e => {
overflow-y: scroll; overflow-y: scroll;
transition: max-height 0.3s; transition: max-height 0.3s;
background-color: var(--shaded_background); background-color: var(--shaded_background);
backdrop-filter: blur(4px);
} }
.grid { .grid {
display: grid; display: grid;

View File

@@ -0,0 +1,137 @@
<script lang="ts">
import Button from "../../layout/Button.svelte";
import type { FSNode, FSPermissions } from "../FilesystemAPI.mjs";
import PermissionButton from "./PermissionButton.svelte";
export let file: FSNode
let new_user_id = ""
let new_user_perms = <FSPermissions>{read: true}
const add_user = (e: SubmitEvent) => {
e.preventDefault()
if (file.user_permissions === undefined) {
file.user_permissions = <[string: FSPermissions]>{}
}
file.user_permissions[new_user_id] = structuredClone(new_user_perms)
}
const del_user = (id: string) => {
delete file.user_permissions[id]
file.user_permissions = file.user_permissions
}
let new_password = ""
let new_password_perms = <FSPermissions>{read: true}
const add_password = (e: SubmitEvent) => {
e.preventDefault()
if (file.password_permissions === undefined) {
file.password_permissions = <[string: FSPermissions]>{}
}
file.password_permissions[new_password] = structuredClone(new_password_perms)
}
const del_password = (pass: string) => {
delete file.password_permissions[pass]
file.password_permissions = file.password_permissions
}
</script>
<p>
Access controls are only available for users with an account currently. Even
if you set 'Anyone with the link' to write, they will need to be logged in
to write to the directory.
</p>
<p>
Users can always delete files they uploaded themselves, even if they don't
have delete permissions. You can see who the owner of a file is in the
Details window.
</p>
<h2>Link permissions</h2>
<div class="row">
<div class="grow id">
Anyone with the link can...
</div>
<div class="perms">
<PermissionButton bind:permissions={file.link_permissions}/>
</div>
</div>
<h2>User permissions</h2>
<p>
Enter a username here to give them access to this directory. The user will
not receive an e-mail invite. Giving write access to a user without giving
read access as well does not actually allow them to write anything.
</p>
<form on:submit={add_user} class="row">
<input type="text" bind:value={new_user_id} placeholder="Username" class="grow" size="1">
<Button type="submit" icon="add" label="Add"/>
<div class="perms">
<PermissionButton bind:permissions={new_user_perms}/>
</div>
</form>
{#if file.user_permissions !== undefined}
{#each Object.keys(file.user_permissions) as id (id)}
<div class="row">
<Button click={() => del_user(id)} icon="delete"/>
<div class="grow id">
{id}
</div>
<div class="perms">
<PermissionButton bind:permissions={file.user_permissions[id]}/>
</div>
</div>
{/each}
{/if}
<h2>Password permissions</h2>
<p>
Allow users to enter a password to give them access to this directory.
</p>
<p>
<b>This feature is not implemented currently!</b>
</p>
<form on:submit={add_password} class="row">
<input type="text" bind:value={new_password} placeholder="Password" class="grow" size="1">
<Button type="submit" icon="add" label="Add"/>
<div class="perms">
<PermissionButton bind:permissions={new_password_perms}/>
</div>
</form>
{#if file.password_permissions !== undefined}
{#each Object.keys(file.password_permissions) as password (password)}
<div class="row">
<Button click={() => del_password(password)} icon="delete"/>
<div class="grow id">
{password}
</div>
<div class="perms">
<PermissionButton bind:permissions={file.password_permissions[password]}/>
</div>
</div>
{/each}
{/if}
<style>
.row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
border-bottom: 1px solid var(--separator);
margin-bottom: 2px;
padding-bottom: 2px;
}
.row > .grow {
min-width: 150px;
flex: 1 1 content;
}
.row > * {
flex: 0 0 content;
}
.id {
margin-left: 0.5em;
display: flex;
align-items: center;
}
.perms {
text-align: right;
margin-left: 0.5em;
}
</style>

View File

@@ -68,7 +68,7 @@ const add_styles = (style, properties) => {
style.body_color = properties.brand_body_color style.body_color = properties.brand_body_color
style.body_background = properties.brand_body_color style.body_background = properties.brand_body_color
style.body_text_color = add_contrast(properties.brand_body_color, 75) style.body_text_color = add_contrast(properties.brand_body_color, 75)
style.shaded_background = set_alpha(properties.brand_body_color, 0.8) style.shaded_background = set_alpha(properties.brand_body_color, 0.75)
style.separator = add_contrast(properties.brand_body_color, 8) style.separator = add_contrast(properties.brand_body_color, 8)
style.shadow_color = darken(properties.brand_body_color, 0.8) style.shadow_color = darken(properties.brand_body_color, 0.8)
} }

View File

@@ -1,7 +1,7 @@
<script> <script>
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import FilePicker from "../filemanager/FilePicker.svelte"; import FilePicker from "../filemanager/FilePicker.svelte";
import { fs_update, fs_node_type } from "../FilesystemAPI"; import { fs_update, fs_node_type } from "../FilesystemAPI.mjs";
import CustomBanner from "../viewers/CustomBanner.svelte"; import CustomBanner from "../viewers/CustomBanner.svelte";
import HelpButton from "../../layout/HelpButton.svelte"; import HelpButton from "../../layout/HelpButton.svelte";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()

View File

@@ -1,10 +1,11 @@
<script> <script>
import { fs_rename, fs_update } from "../FilesystemAPI"; import { fs_rename, fs_update } from "../FilesystemAPI.mjs";
import Modal from "../../util/Modal.svelte"; import Modal from "../../util/Modal.svelte";
import BrandingOptions from "./BrandingOptions.svelte"; import BrandingOptions from "./BrandingOptions.svelte";
import { branding_from_node } from "./Branding"; import { branding_from_node } from "./Branding";
import FileOptions from "./FileOptions.svelte"; import FileOptions from "./FileOptions.svelte";
import SharingOptions from "./SharingOptions.svelte"; import SharingOptions from "./SharingOptions.svelte";
import AccessControl from "./AccessControl.svelte";
export let nav export let nav
let file = { let file = {
@@ -40,6 +41,11 @@ export const edit = (f, oae = false, open_tab = "") => {
file.properties = {} file.properties = {}
} }
if (shared && file.link_permissions === undefined) {
// Default to read-only for public links
file.link_permissions = {read: true, write: false, delete: false}
}
branding_enabled = file.properties.branding_enabled === "true" branding_enabled = file.properties.branding_enabled === "true"
if (branding_enabled) { if (branding_enabled) {
custom_css = branding_from_node(file) custom_css = branding_from_node(file)
@@ -76,7 +82,9 @@ const save = async (keep_editing = false) => {
let new_file let new_file
try { try {
nav.set_loading(true) nav.set_loading(true)
let opts = {shared: shared} let opts = {
shared: shared,
}
opts.branding_enabled = branding_enabled ? "true" : "" opts.branding_enabled = branding_enabled ? "true" : ""
@@ -89,6 +97,16 @@ const save = async (keep_editing = false) => {
} }
} }
if (shared && file.link_permissions !== undefined) {
opts.link_permissions = file.link_permissions
}
if (shared && file.user_permissions !== undefined) {
opts.user_permissions = file.user_permissions
}
if (shared && file.password_permissions !== undefined) {
opts.password_permissions = file.password_permissions
}
new_file = await fs_update(file.path, opts) new_file = await fs_update(file.path, opts)
if (new_name !== file.name) { if (new_name !== file.name) {
@@ -135,6 +153,12 @@ const save = async (keep_editing = false) => {
<i class="icon">share</i> <i class="icon">share</i>
Sharing Sharing
</button> </button>
{#if shared && $nav.permissions.owner}
<button class:button_highlight={tab === "access"} on:click={() => tab = "access"}>
<i class="icon">key</i>
Access control
</button>
{/if}
<button class:button_highlight={tab === "branding"} on:click={() => tab = "branding"}> <button class:button_highlight={tab === "branding"} on:click={() => tab = "branding"}>
<i class="icon">palette</i> <i class="icon">palette</i>
Branding Branding
@@ -153,11 +177,9 @@ const save = async (keep_editing = false) => {
bind:open_after_edit bind:open_after_edit
/> />
{:else if tab === "share"} {:else if tab === "share"}
<SharingOptions <SharingOptions bind:file bind:shared on:save={() => save(true)} />
bind:file {:else if tab === "access"}
bind:shared <AccessControl bind:file bind:shared />
on:save={() => save(true)}
/>
{:else if tab === "branding"} {:else if tab === "branding"}
<BrandingOptions <BrandingOptions
bind:enabled={branding_enabled} bind:enabled={branding_enabled}

View File

@@ -1,6 +1,6 @@
<script> <script>
import Button from "../../layout/Button.svelte"; import Button from "../../layout/Button.svelte";
import { fs_delete_all } from "../FilesystemAPI"; import { fs_delete_all } from "../FilesystemAPI.mjs";
import PathLink from "../util/PathLink.svelte"; import PathLink from "../util/PathLink.svelte";
export let nav export let nav
@@ -47,12 +47,14 @@ const delete_file = async e => {
<label for="file_name">Name</label> <label for="file_name">Name</label>
<input form="edit_form" bind:value={new_name} id="file_name" type="text" class="form_input" disabled={is_root_dir}/> <input form="edit_form" bind:value={new_name} id="file_name" type="text" class="form_input" disabled={is_root_dir}/>
</div> </div>
<h2>Delete</h2> {#if $nav.permissions.delete === true}
<p> <h2>Delete</h2>
<p>
Delete this file or directory. If this is a directory then all Delete this file or directory. If this is a directory then all
subfiles will be deleted as well. This action cannot be undone. subfiles will be deleted as well. This action cannot be undone.
</p> </p>
<Button click={delete_file} red icon="delete" label="Delete" style="align-self: flex-start;"/> <Button click={delete_file} red icon="delete" label="Delete" style="align-self: flex-start;"/>
{/if}
<style> <style>
.form_grid { .form_grid {

View File

@@ -0,0 +1,10 @@
<script lang="ts">
import ToggleButton from "../../layout/ToggleButton.svelte";
import type { FSPermissions } from "../FilesystemAPI.mjs";
export let permissions = <FSPermissions>{}
</script>
<ToggleButton group_first bind:on={permissions.read}>Read</ToggleButton>
<ToggleButton group_middle bind:on={permissions.write}>Write</ToggleButton>
<ToggleButton group_last bind:on={permissions.delete}>Delete</ToggleButton>

View File

@@ -1,6 +1,6 @@
<script> <script>
import { onMount } from "svelte"; import { onMount } from "svelte";
import { fs_mkdir } from "../FilesystemAPI.js"; import { fs_mkdir } from "../FilesystemAPI.mjs";
import Button from "../../layout/Button.svelte"; import Button from "../../layout/Button.svelte";
export let nav; export let nav;
@@ -22,7 +22,7 @@ let create_dir = async () => {
if (err.value && err.value === "node_already_exists") { if (err.value && err.value === "node_already_exists") {
error_msg = "A directory with this name already exists" error_msg = "A directory with this name already exists"
} else { } else {
error_msg = "Server returned an error: "+err error_msg = "Server returned an error: code: '"+err.value+"' message: "+err.message
} }
} finally { } finally {
nav.set_loading(false) nav.set_loading(false)

View File

@@ -1,6 +1,6 @@
<script> <script>
import FilePicker from "../../file_viewer/FilePicker.svelte"; import FilePicker from "../../file_viewer/FilePicker.svelte";
import { fs_import } from "../FilesystemAPI"; import { fs_import } from "../FilesystemAPI.mjs";
export let nav export let nav
let file_picker let file_picker

View File

@@ -1,5 +1,5 @@
<script> <script>
import { fs_delete_all, fs_rename } from './../FilesystemAPI.ts' import { fs_delete_all, fs_rename } from '../FilesystemAPI.mjs'
import { onMount } from 'svelte' import { onMount } from 'svelte'
import CreateDirectory from './CreateDirectory.svelte' import CreateDirectory from './CreateDirectory.svelte'
import ListView from './ListView.svelte' import ListView from './ListView.svelte'
@@ -298,7 +298,7 @@ onMount(() => {
</button> </button>
<div class="toolbar_spacer"></div> <div class="toolbar_spacer"></div>
{#if $nav.permissions.update} {#if $nav.permissions.write}
<button on:click={() => upload_widget.pick_files()} title="Upload files to this directory"> <button on:click={() => upload_widget.pick_files()} title="Upload files to this directory">
<i class="icon">cloud_upload</i> <i class="icon">cloud_upload</i>
</button> </button>
@@ -407,6 +407,7 @@ onMount(() => {
margin: auto; margin: auto;
padding: 0; padding: 0;
background: var(--shaded_background); background: var(--shaded_background);
backdrop-filter: blur(4px);
} }
.toolbar { .toolbar {
display: flex; display: flex;

View File

@@ -1,6 +1,6 @@
<script> <script>
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { fs_node_icon, fs_node_type, fs_encode_path } from "./../FilesystemAPI"; import { fs_node_icon, fs_node_type, fs_encode_path } from "../FilesystemAPI.mjs";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()
export let nav export let nav

View File

@@ -1,7 +1,7 @@
<script> <script>
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import { formatDataVolume } from "./../../util/Formatting.svelte"; import { formatDataVolume } from "./../../util/Formatting.svelte";
import { fs_encode_path, fs_node_icon } from "./../FilesystemAPI" import { fs_encode_path, fs_node_icon } from "../FilesystemAPI.mjs"
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()
@@ -57,7 +57,7 @@ export let hide_branding = false
<i class="icon">palette</i> <i class="icon">palette</i>
</button> </button>
{/if} {/if}
{#if $nav.permissions.update && !hide_edit} {#if $nav.permissions.write && !hide_edit}
<button class="action_button" on:click|preventDefault|stopPropagation={() => dispatch("node_settings", index)}> <button class="action_button" on:click|preventDefault|stopPropagation={() => dispatch("node_settings", index)}>
<i class="icon">edit</i> <i class="icon">edit</i>
</button> </button>
@@ -69,11 +69,11 @@ export let hide_branding = false
</div> </div>
<style> <style>
.directory { .directory {
display: table; display: table;
margin: 8px auto 16px auto; margin: 8px auto 16px auto;
background: var(--body_color); background: var(--shaded_background);
backdrop-filter: blur(4px);
border-collapse: collapse; border-collapse: collapse;
border-radius: 8px; border-radius: 8px;

View File

@@ -1,6 +1,6 @@
<script> <script>
import { onMount } from "svelte"; import { onMount } from "svelte";
import { fs_search, fs_encode_path, fs_thumbnail_url } from "../FilesystemAPI"; import { fs_search, fs_encode_path, fs_thumbnail_url } from "../FilesystemAPI.mjs";
export let nav export let nav

View File

@@ -9,7 +9,7 @@
// //
// on_error is called when the upload has failed. The parameters are the error // on_error is called when the upload has failed. The parameters are the error
import { fs_path_url, type GenericResponse } from "./../FilesystemAPI" import { fs_path_url, type GenericResponse } from "../FilesystemAPI.mjs"
// code and an error message // code and an error message
export const upload_file = ( export const upload_file = (

View File

@@ -1,6 +1,6 @@
<script> <script>
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { fs_path_url, fs_encode_path, fs_node_icon } from "../../filesystem/FilesystemAPI.ts" import { fs_path_url, fs_encode_path, fs_node_icon } from "../FilesystemAPI.mjs"
import FileTitle from "../../layout/FileTitle.svelte"; import FileTitle from "../../layout/FileTitle.svelte";
import TextBlock from "../../layout/TextBlock.svelte" import TextBlock from "../../layout/TextBlock.svelte"

View File

@@ -1,7 +1,7 @@
<script> <script>
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import IconBlock from "../../layout/IconBlock.svelte"; import IconBlock from "../../layout/IconBlock.svelte";
import { fs_thumbnail_url } from "../FilesystemAPI"; import { fs_thumbnail_url } from "../FilesystemAPI.mjs";
import TextBlock from "../../layout/TextBlock.svelte" import TextBlock from "../../layout/TextBlock.svelte"
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()

View File

@@ -1,7 +1,7 @@
<script> <script>
import { onMount, tick } from "svelte"; import { onMount, tick } from "svelte";
import Spinner from "../../util/Spinner.svelte"; import Spinner from "../../util/Spinner.svelte";
import { fs_node_type, fs_thumbnail_url } from "./../FilesystemAPI"; import { fs_node_type, fs_thumbnail_url } from "../FilesystemAPI.mjs";
import FileManager from "../filemanager/FileManager.svelte"; import FileManager from "../filemanager/FileManager.svelte";
import Audio from "./Audio.svelte"; import Audio from "./Audio.svelte";
import File from "./File.svelte"; import File from "./File.svelte";

View File

@@ -1,7 +1,7 @@
<script> <script>
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import { swipe_nav } from "../../lib/SwipeNavigate.ts"; import { swipe_nav } from "../../lib/SwipeNavigate.ts";
import { fs_path_url } from "./../FilesystemAPI.ts"; import { fs_path_url } from "../FilesystemAPI.mjs";
let dispatch = createEventDispatcher(); let dispatch = createEventDispatcher();

View File

@@ -1,5 +1,5 @@
<script> <script>
import { fs_path_url } from "../FilesystemAPI"; import { fs_path_url } from "../FilesystemAPI.mjs";
export let nav export let nav
</script> </script>

View File

@@ -1,6 +1,6 @@
<script> <script>
import { tick } from "svelte"; import { tick } from "svelte";
import { fs_path_url } from "../FilesystemAPI"; import { fs_path_url } from "../FilesystemAPI.mjs";
export let nav export let nav
let text_type = "text" let text_type = "text"

View File

@@ -5,7 +5,7 @@ import { formatDate } from "../../util/Formatting.svelte"
import TorrentItem from "./TorrentItem.svelte" import TorrentItem from "./TorrentItem.svelte"
import IconBlock from "../../layout/IconBlock.svelte"; import IconBlock from "../../layout/IconBlock.svelte";
import TextBlock from "../../layout/TextBlock.svelte" import TextBlock from "../../layout/TextBlock.svelte"
import { fs_node_icon, fs_path_url } from "../FilesystemAPI"; import { fs_node_icon, fs_path_url } from "../FilesystemAPI.mjs";
import CopyButton from "../../layout/CopyButton.svelte"; import CopyButton from "../../layout/CopyButton.svelte";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()

View File

@@ -1,6 +1,6 @@
<script> <script>
import { onMount, createEventDispatcher, tick } from "svelte"; import { onMount, createEventDispatcher, tick } from "svelte";
import { fs_path_url } from "../FilesystemAPI"; import { fs_path_url } from "../FilesystemAPI.mjs";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()
export let nav export let nav
@@ -169,6 +169,7 @@ const fullscreen = () => {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
background-color: var(--shaded_background); background-color: var(--shaded_background);
backdrop-filter: blur(4px);
padding: 0 2px 2px 2px; padding: 0 2px 2px 2px;
align-items: center; align-items: center;
} }
@@ -195,6 +196,7 @@ const fullscreen = () => {
} }
.compatibility_warning { .compatibility_warning {
background-color: var(--shaded_background); background-color: var(--shaded_background);
backdrop-filter: blur(4px);
border-bottom: 2px solid #6666FF; border-bottom: 2px solid #6666FF;
padding: 4px; padding: 4px;
} }

View File

@@ -4,7 +4,7 @@ import { formatDataVolume, formatDate } from "../../util/Formatting.svelte"
import ZipItem from "../../file_viewer/viewers/ZipItem.svelte"; import ZipItem from "../../file_viewer/viewers/ZipItem.svelte";
import IconBlock from "../../layout/IconBlock.svelte"; import IconBlock from "../../layout/IconBlock.svelte";
import TextBlock from "../../layout/TextBlock.svelte" import TextBlock from "../../layout/TextBlock.svelte"
import { fs_node_icon, fs_path_url } from "../FilesystemAPI"; import { fs_node_icon, fs_path_url } from "../FilesystemAPI.mjs";
let dispatch = createEventDispatcher() let dispatch = createEventDispatcher()

View File

@@ -8,13 +8,13 @@ export let disabled = false;
export let icon = "" export let icon = ""
export let icon_small = false; export let icon_small = false;
export let label = "" export let label = ""
export let title = "" export let title = null
export let link_href = "" export let link_href = ""
export let link_target = "_self" export let link_target = "_self"
export let click = e => {} export let click = e => {}
export let style = "" export let style = null
export let type = "" export let type = null
export let form = "" export let form = null
let click_int = e => { let click_int = e => {
if (highlight_on_click) { if (highlight_on_click) {
@@ -43,7 +43,7 @@ let click_int = e => {
style={style} style={style}
type={type} type={type}
form={form} form={form}
disabled={disabled ? "disabled":""} disabled={disabled ? "disabled":null}
> >
{#if icon !== ""} {#if icon !== ""}
<i class="icon" class:small={icon_small}>{icon}</i> <i class="icon" class:small={icon_small}>{icon}</i>
@@ -63,7 +63,7 @@ let click_int = e => {
class:flat class:flat
title={title} title={title}
style={style} style={style}
disabled={disabled ? "disabled":""} disabled={disabled ? "disabled":null}
> >
{#if icon !== ""} {#if icon !== ""}
<i class="icon" class:small={icon_small}>{icon}</i> <i class="icon" class:small={icon_small}>{icon}</i>

View File

@@ -38,6 +38,7 @@ export let width = "750px"
vertical-align: middle; vertical-align: middle;
overflow-wrap: anywhere; overflow-wrap: anywhere;
background-color: var(--shaded_background); background-color: var(--shaded_background);
backdrop-filter: blur(4px);
border-radius: 8px; border-radius: 8px;
padding: 8px; padding: 8px;
} }

View File

@@ -16,6 +16,7 @@ export let center = false
margin: 8px auto; margin: 8px auto;
background-color: var(--shaded_background); background-color: var(--shaded_background);
backdrop-filter: blur(4px);
border-radius: 8px; border-radius: 8px;
padding: 8px; padding: 8px;
} }

View File

@@ -0,0 +1,30 @@
<script lang="ts">
export let on = false
export let group_first = false
export let group_middle = false
export let group_last = false
</script>
<button
on:click={() => on = !on}
type="button"
class="button"
class:button_highlight={on}
class:group_first
class:group_middle
class:group_last
>
{#if on}
<i class="icon">check</i>
{:else}
<i class="icon">close</i>
{/if}
<slot></slot>
</button>
<style>
.button {
flex: 0 0 content;
}
</style>

View File

@@ -1,7 +1,7 @@
<script> <script>
import { onMount } from "svelte"; import { onMount } from "svelte";
import { FSNavigator } from "../../filesystem/FSNavigator.ts" import { FSNavigator } from "../../filesystem/FSNavigator.ts"
import { fs_encode_path, fs_node_icon } from "../../filesystem/FilesystemAPI.ts"; import { fs_encode_path, fs_node_icon } from "../../filesystem/FilesystemAPI.mjs";
import Button from "../../layout/Button.svelte"; import Button from "../../layout/Button.svelte";
import CreateDirectory from "../../filesystem/filemanager/CreateDirectory.svelte"; import CreateDirectory from "../../filesystem/filemanager/CreateDirectory.svelte";
import FSUploadWidget from "../../filesystem/upload_widget/FSUploadWidget.svelte"; import FSUploadWidget from "../../filesystem/upload_widget/FSUploadWidget.svelte";
@@ -17,7 +17,7 @@ onMount(() => nav.navigate("/me", false))
<div class="wrapper" use:drop_target={{upload: (files) => upload_widget.upload_files(files)}}> <div class="wrapper" use:drop_target={{upload: (files) => upload_widget.upload_files(files)}}>
<div class="toolbar"> <div class="toolbar">
{#if $nav.permissions.update} {#if $nav.permissions.write}
<Button <Button
click={() => upload_widget.pick_files()} click={() => upload_widget.pick_files()}
icon="cloud_upload" icon="cloud_upload"

6
svelte/tsconfig.json Normal file
View File

@@ -0,0 +1,6 @@
{
"compilerOptions": {
"verbatimModuleSyntax": true,
"target": "ES2017",
}
}

View File

@@ -33,6 +33,8 @@ func (wc *WebController) serveDirectory(w http.ResponseWriter, r *http.Request,
http.Redirect(w, r, "/login", http.StatusSeeOther) http.Redirect(w, r, "/login", http.StatusSeeOther)
} else if err.Error() == "unavailable_for_legal_reasons" { } else if err.Error() == "unavailable_for_legal_reasons" {
wc.serveUnavailableForLegalReasons(w, r) wc.serveUnavailableForLegalReasons(w, r)
} else if err.Error() == "permission_denied" {
wc.serveForbidden(w, r)
} else { } else {
log.Error("Failed to get path: %s", err) log.Error("Failed to get path: %s", err)
wc.templates.Run(w, r, "500", td) wc.templates.Run(w, r, "500", td)

View File

@@ -264,7 +264,7 @@ func (s styleSheet) String() string {
s.BodyBackground.CSS(), s.BodyBackground.CSS(),
s.BodyText.CSS(), s.BodyText.CSS(),
s.Separator.CSS(), s.Separator.CSS(),
s.BodyColor.WithAlpha(0.8).CSS(), // shaded_background s.BodyColor.WithAlpha(0.75).CSS(), // shaded_background
s.CardColor.CSS(), s.CardColor.CSS(),
s.Chart1.CSS(), s.Chart1.CSS(),
s.Chart2.CSS(), s.Chart2.CSS(),