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`,
},
plugins: [
sveltePreprocess(),
svelte({
preprocess: sveltePreprocess(),
compilerOptions: {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -22,6 +22,7 @@ export type FSNode = {
modified: Date,
mode_string: string,
mode_octal: string,
created_by: string,
abuse_type: string | undefined,
abuse_report_time: Date | undefined,
@@ -31,15 +32,16 @@ export type FSNode = {
sha256_sum: string,
id: string | undefined,
read_password: string | undefined,
write_password: string | undefined,
properties: {} | undefined,
link_permissions: FSPermissions | undefined,
user_permissions: [string: FSPermissions] | undefined,
password_permissions: [string: FSPermissions] | undefined,
}
export type FSPermissions = {
create: boolean,
owner: boolean,
read: boolean,
update: boolean,
write: boolean,
delete: boolean,
}
@@ -56,6 +58,12 @@ export type NodeOptions = {
modified: Date | undefined,
shared: boolean | undefined,
// Permissions
link_permissions: FSPermissions | undefined,
user_permissions: FSPermissions | undefined,
password_permissions: FSPermissions | undefined,
// Branding
branding_enabled: boolean | undefined,
brand_input_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")
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())
} else if (typeof opts[key] === "object") {
form.append(key, JSON.stringify(opts[key]))
} else {
form.append(key, opts[key])
}

View File

@@ -31,7 +31,7 @@ export const generate_share_path = path => {
</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 share_url = ""
@@ -134,6 +134,7 @@ const share_tumblr = () => {
overflow-x: hidden;
float: left;
background: var(--shaded_background);
backdrop-filter: blur(4px);
text-align: center;
overflow: hidden;
opacity: 0;

View File

@@ -106,7 +106,7 @@ let expand = e => {
{/if}
<!-- 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}>
<i class="icon">share</i>
<span>Share</span>
@@ -133,7 +133,7 @@ let expand = e => {
<span>Deta<u>i</u>ls</span>
</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}>
<i class="icon">edit</i>
<span><u>E</u>dit</span>
@@ -149,6 +149,7 @@ let expand = e => {
overflow-y: scroll;
transition: max-height 0.3s;
background-color: var(--shaded_background);
backdrop-filter: blur(4px);
}
.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_background = properties.brand_body_color
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.shadow_color = darken(properties.brand_body_color, 0.8)
}

View File

@@ -1,7 +1,7 @@
<script>
import { createEventDispatcher } from "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 HelpButton from "../../layout/HelpButton.svelte";
let dispatch = createEventDispatcher()

View File

@@ -1,10 +1,11 @@
<script>
import { fs_rename, fs_update } from "../FilesystemAPI";
import { fs_rename, fs_update } from "../FilesystemAPI.mjs";
import Modal from "../../util/Modal.svelte";
import BrandingOptions from "./BrandingOptions.svelte";
import { branding_from_node } from "./Branding";
import FileOptions from "./FileOptions.svelte";
import SharingOptions from "./SharingOptions.svelte";
import AccessControl from "./AccessControl.svelte";
export let nav
let file = {
@@ -40,6 +41,11 @@ export const edit = (f, oae = false, open_tab = "") => {
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"
if (branding_enabled) {
custom_css = branding_from_node(file)
@@ -76,7 +82,9 @@ const save = async (keep_editing = false) => {
let new_file
try {
nav.set_loading(true)
let opts = {shared: shared}
let opts = {
shared: shared,
}
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)
if (new_name !== file.name) {
@@ -135,6 +153,12 @@ const save = async (keep_editing = false) => {
<i class="icon">share</i>
Sharing
</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"}>
<i class="icon">palette</i>
Branding
@@ -153,11 +177,9 @@ const save = async (keep_editing = false) => {
bind:open_after_edit
/>
{:else if tab === "share"}
<SharingOptions
bind:file
bind:shared
on:save={() => save(true)}
/>
<SharingOptions bind:file bind:shared on:save={() => save(true)} />
{:else if tab === "access"}
<AccessControl bind:file bind:shared />
{:else if tab === "branding"}
<BrandingOptions
bind:enabled={branding_enabled}

View File

@@ -1,6 +1,6 @@
<script>
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";
export let nav
@@ -47,12 +47,14 @@ const delete_file = async e => {
<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}/>
</div>
{#if $nav.permissions.delete === true}
<h2>Delete</h2>
<p>
Delete this file or directory. If this is a directory then all
subfiles will be deleted as well. This action cannot be undone.
</p>
<Button click={delete_file} red icon="delete" label="Delete" style="align-self: flex-start;"/>
{/if}
<style>
.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>
import { onMount } from "svelte";
import { fs_mkdir } from "../FilesystemAPI.js";
import { fs_mkdir } from "../FilesystemAPI.mjs";
import Button from "../../layout/Button.svelte";
export let nav;
@@ -22,7 +22,7 @@ let create_dir = async () => {
if (err.value && err.value === "node_already_exists") {
error_msg = "A directory with this name already exists"
} else {
error_msg = "Server returned an error: "+err
error_msg = "Server returned an error: code: '"+err.value+"' message: "+err.message
}
} finally {
nav.set_loading(false)

View File

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

View File

@@ -1,5 +1,5 @@
<script>
import { fs_delete_all, fs_rename } from './../FilesystemAPI.ts'
import { fs_delete_all, fs_rename } from '../FilesystemAPI.mjs'
import { onMount } from 'svelte'
import CreateDirectory from './CreateDirectory.svelte'
import ListView from './ListView.svelte'
@@ -298,7 +298,7 @@ onMount(() => {
</button>
<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">
<i class="icon">cloud_upload</i>
</button>
@@ -407,6 +407,7 @@ onMount(() => {
margin: auto;
padding: 0;
background: var(--shaded_background);
backdrop-filter: blur(4px);
}
.toolbar {
display: flex;

View File

@@ -1,6 +1,6 @@
<script>
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()
export let nav

View File

@@ -1,7 +1,7 @@
<script>
import { createEventDispatcher } from "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()
@@ -57,7 +57,7 @@ export let hide_branding = false
<i class="icon">palette</i>
</button>
{/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)}>
<i class="icon">edit</i>
</button>
@@ -69,11 +69,11 @@ export let hide_branding = false
</div>
<style>
.directory {
display: table;
margin: 8px auto 16px auto;
background: var(--body_color);
background: var(--shaded_background);
backdrop-filter: blur(4px);
border-collapse: collapse;
border-radius: 8px;

View File

@@ -1,6 +1,6 @@
<script>
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

View File

@@ -9,7 +9,7 @@
//
// 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
export const upload_file = (

View File

@@ -1,6 +1,6 @@
<script>
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 TextBlock from "../../layout/TextBlock.svelte"

View File

@@ -1,7 +1,7 @@
<script>
import { createEventDispatcher } from "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"
let dispatch = createEventDispatcher()

View File

@@ -1,7 +1,7 @@
<script>
import { onMount, tick } from "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 Audio from "./Audio.svelte";
import File from "./File.svelte";

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import { formatDate } from "../../util/Formatting.svelte"
import TorrentItem from "./TorrentItem.svelte"
import IconBlock from "../../layout/IconBlock.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";
let dispatch = createEventDispatcher()

View File

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

View File

@@ -4,7 +4,7 @@ import { formatDataVolume, formatDate } from "../../util/Formatting.svelte"
import ZipItem from "../../file_viewer/viewers/ZipItem.svelte";
import IconBlock from "../../layout/IconBlock.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()

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ export let center = false
margin: 8px auto;
background-color: var(--shaded_background);
backdrop-filter: blur(4px);
border-radius: 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>
import { onMount } from "svelte";
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 CreateDirectory from "../../filesystem/filemanager/CreateDirectory.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="toolbar">
{#if $nav.permissions.update}
{#if $nav.permissions.write}
<Button
click={() => upload_widget.pick_files()}
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)
} else if err.Error() == "unavailable_for_legal_reasons" {
wc.serveUnavailableForLegalReasons(w, r)
} else if err.Error() == "permission_denied" {
wc.serveForbidden(w, r)
} else {
log.Error("Failed to get path: %s", err)
wc.templates.Run(w, r, "500", td)

View File

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