Group settings into categories
This commit is contained in:
@@ -1,107 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Icon from "@iconify/svelte";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import Modal from "./Modal.svelte";
|
||||
import * as api from "$lib/ts/api";
|
||||
import type { AppInfo } from "$lib/ts/api";
|
||||
|
||||
interface Props {
|
||||
onclose: () => void;
|
||||
}
|
||||
|
||||
let { onclose }: Props = $props();
|
||||
|
||||
let info = $state<AppInfo | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
api
|
||||
.appInfo()
|
||||
.then((result) => (info = result))
|
||||
.catch(() => (info = null));
|
||||
});
|
||||
|
||||
const links = [
|
||||
{
|
||||
label: "Typst Documentation",
|
||||
url: "https://typst.app/docs/",
|
||||
icon: "ph:book-open",
|
||||
},
|
||||
{
|
||||
label: "Typst Universe",
|
||||
url: "https://typst.app/universe/",
|
||||
icon: "ph:planet",
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<Modal title="About Typst Desktop" icon="ph:info" {onclose}>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<Icon icon="ph:file-code" class="text-3xl text-[var(--color-accent)]" />
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Typst Desktop</p>
|
||||
<p class="text-xs text-[var(--color-ink-muted)]">
|
||||
A local editor for Typst documents
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if info}
|
||||
<dl class="flex flex-col gap-1 text-xs">
|
||||
<div class="flex justify-between border-b border-[var(--color-line)] py-1.5">
|
||||
<dt class="text-[var(--color-ink-muted)]">Version</dt>
|
||||
<dd class="font-medium">{info.version}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between border-b border-[var(--color-line)] py-1.5">
|
||||
<dt class="text-[var(--color-ink-muted)]">Typst</dt>
|
||||
<dd class="font-medium">{info.typst_version}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between border-b border-[var(--color-line)] py-1.5">
|
||||
<dt class="text-[var(--color-ink-muted)]">Tauri</dt>
|
||||
<dd class="font-medium">{info.tauri_version}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between border-b border-[var(--color-line)] py-1.5">
|
||||
<dt class="text-[var(--color-ink-muted)]">Author</dt>
|
||||
<dd class="font-medium">{info.authors}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between py-1.5">
|
||||
<dt class="text-[var(--color-ink-muted)]">License</dt>
|
||||
<dd class="font-medium">{info.license}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
{#each links as link}
|
||||
<button
|
||||
class="flex items-center gap-2 rounded-md border border-[var(--color-line)] px-3 py-2 text-xs transition hover:border-[var(--color-accent)] hover:bg-[var(--color-surface-muted)]"
|
||||
onclick={() => openUrl(link.url)}
|
||||
>
|
||||
<Icon icon={link.icon} class="text-base text-[var(--color-accent)]" />
|
||||
<span class="flex-1 text-left">{link.label}</span>
|
||||
<Icon
|
||||
icon="ph:arrow-square-out"
|
||||
class="text-[var(--color-ink-muted)]"
|
||||
/>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<p
|
||||
class="rounded-md border border-[var(--color-line)] bg-[var(--color-surface-muted)] p-3 text-[11px] leading-relaxed text-[var(--color-ink-muted)]"
|
||||
>
|
||||
Typst Desktop is a community application and is not affiliated with,
|
||||
endorsed by, or supported by the official Typst project. The links above
|
||||
open the official Typst website in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#snippet footer()}
|
||||
<button
|
||||
class="rounded-md bg-[var(--color-accent)] px-3 py-1.5 text-xs font-medium text-white transition hover:opacity-90"
|
||||
onclick={onclose}
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
{/snippet}
|
||||
</Modal>
|
||||
@@ -1,9 +1,11 @@
|
||||
<script lang="ts">
|
||||
import Icon from "@iconify/svelte";
|
||||
import { untrack } from "svelte";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||
import Modal from "./Modal.svelte";
|
||||
import * as api from "$lib/ts/api";
|
||||
import { untrack } from "svelte";
|
||||
import type { AppInfo } from "$lib/ts/api";
|
||||
import {
|
||||
app,
|
||||
applyTheme,
|
||||
@@ -19,11 +21,32 @@
|
||||
|
||||
let { onclose, onsignin }: Props = $props();
|
||||
|
||||
type Section = "files" | "appearance" | "account" | "about";
|
||||
|
||||
const sections: { id: Section; label: string; icon: string }[] = [
|
||||
{ id: "files", label: "Files", icon: "ph:folder" },
|
||||
{ id: "appearance", label: "Appearance", icon: "ph:palette" },
|
||||
{ id: "account", label: "Account", icon: "ph:user-circle" },
|
||||
{ id: "about", label: "About", icon: "ph:info" },
|
||||
];
|
||||
|
||||
let section = $state<Section>("files");
|
||||
|
||||
let workspaceRoot = $state(untrack(() => app.settings?.workspace_root ?? ""));
|
||||
let serverUrl = $state(untrack(() => app.settings?.server_url ?? ""));
|
||||
let autosaveSeconds = $state(untrack(() => app.settings?.autosave_seconds ?? 0));
|
||||
let syncMinutes = $state(untrack(() => app.settings?.sync_minutes ?? 0));
|
||||
let saving = $state(false);
|
||||
let info = $state<AppInfo | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (section === "about" && !info) {
|
||||
api
|
||||
.appInfo()
|
||||
.then((result) => (info = result))
|
||||
.catch(() => (info = null));
|
||||
}
|
||||
});
|
||||
|
||||
const autosaveOptions = [
|
||||
{ value: 0, label: "Off" },
|
||||
@@ -39,6 +62,19 @@
|
||||
{ value: 5, label: "5 minutes" },
|
||||
];
|
||||
|
||||
const links = [
|
||||
{
|
||||
label: "Typst Documentation",
|
||||
url: "https://typst.app/docs/",
|
||||
icon: "ph:book-open",
|
||||
},
|
||||
{
|
||||
label: "Typst Universe",
|
||||
url: "https://typst.app/universe/",
|
||||
icon: "ph:planet",
|
||||
},
|
||||
];
|
||||
|
||||
async function browse() {
|
||||
const selected = await open({ directory: true, multiple: false });
|
||||
if (typeof selected === "string") {
|
||||
@@ -75,120 +111,196 @@
|
||||
setError(error);
|
||||
}
|
||||
}
|
||||
|
||||
const fieldClass =
|
||||
"rounded-md border border-[var(--color-line)] bg-[var(--color-surface)] px-3 py-2 text-sm focus:border-[var(--color-accent)] focus:outline-none";
|
||||
</script>
|
||||
|
||||
<Modal title="Settings" icon="ph:gear-six" {onclose}>
|
||||
<div class="flex flex-col gap-5">
|
||||
<div class="flex flex-col gap-1 text-xs">
|
||||
<span class="font-medium text-[var(--color-ink-muted)]">Workspace folder</span>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
class="flex-1 rounded-md border border-[var(--color-line)] bg-[var(--color-surface)] px-3 py-2 text-sm focus:border-[var(--color-accent)] focus:outline-none"
|
||||
bind:value={workspaceRoot}
|
||||
/>
|
||||
<Modal title="Settings" icon="ph:gear-six" width="max-w-3xl" {onclose}>
|
||||
<div class="flex min-h-[360px] gap-5">
|
||||
<nav class="flex w-36 shrink-0 flex-col gap-0.5">
|
||||
{#each sections as item}
|
||||
<button
|
||||
class="flex items-center gap-1 rounded-md border border-[var(--color-line)] px-3 text-xs hover:bg-[var(--color-surface-muted)]"
|
||||
onclick={browse}
|
||||
class="flex items-center gap-2 rounded-md px-2.5 py-2 text-xs transition
|
||||
{section === item.id
|
||||
? 'bg-[var(--color-accent-soft)] font-medium text-[var(--color-accent)]'
|
||||
: 'text-[var(--color-ink-muted)] hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-ink)]'}"
|
||||
onclick={() => (section = item.id)}
|
||||
>
|
||||
<Icon icon="ph:folder-open" />
|
||||
Browse
|
||||
<Icon icon={item.icon} class="text-base" />
|
||||
{item.label}
|
||||
</button>
|
||||
</div>
|
||||
<span class="text-[var(--color-ink-muted)]">
|
||||
Projects are stored as plain folders here.
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
</nav>
|
||||
|
||||
<div class="flex flex-col gap-1 text-xs">
|
||||
<span class="font-medium text-[var(--color-ink-muted)]">TypstDrive server</span>
|
||||
<input
|
||||
class="rounded-md border border-[var(--color-line)] bg-[var(--color-surface)] px-3 py-2 text-sm focus:border-[var(--color-accent)] focus:outline-none"
|
||||
bind:value={serverUrl}
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
{#if section === "files"}
|
||||
<div class="flex flex-col gap-5">
|
||||
<div class="flex flex-col gap-1 text-xs">
|
||||
<span class="font-medium text-[var(--color-ink-muted)]">
|
||||
Workspace folder
|
||||
</span>
|
||||
<div class="flex gap-2">
|
||||
<input class="flex-1 {fieldClass}" bind:value={workspaceRoot} />
|
||||
<button
|
||||
class="flex items-center gap-1 rounded-md border border-[var(--color-line)] px-3 text-xs hover:bg-[var(--color-surface-muted)]"
|
||||
onclick={browse}
|
||||
>
|
||||
<Icon icon="ph:folder-open" />
|
||||
Browse
|
||||
</button>
|
||||
</div>
|
||||
<span class="text-[var(--color-ink-muted)]">
|
||||
Projects and documents are stored as plain files here.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1 text-xs">
|
||||
<span class="font-medium text-[var(--color-ink-muted)]">Autosave</span>
|
||||
<select
|
||||
class="rounded-md border border-[var(--color-line)] bg-[var(--color-surface)] px-3 py-2 text-sm focus:border-[var(--color-accent)] focus:outline-none"
|
||||
bind:value={autosaveSeconds}
|
||||
>
|
||||
{#each autosaveOptions as option}
|
||||
<option value={option.value}>{option.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<span class="text-[var(--color-ink-muted)]">
|
||||
Saves the file being edited after you stop typing.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1 text-xs">
|
||||
<span class="font-medium text-[var(--color-ink-muted)]">Automatic sync</span>
|
||||
<select
|
||||
class="rounded-md border border-[var(--color-line)] bg-[var(--color-surface)] px-3 py-2 text-sm focus:border-[var(--color-accent)] focus:outline-none"
|
||||
bind:value={syncMinutes}
|
||||
>
|
||||
{#each syncOptions as option}
|
||||
<option value={option.value}>{option.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<span class="text-[var(--color-ink-muted)]">
|
||||
Pulls and pushes cloud-linked projects on a timer. Conflicts pause
|
||||
syncing until they are resolved.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-3 rounded-md border border-[var(--color-line)] bg-[var(--color-surface-muted)] p-3"
|
||||
>
|
||||
<Icon
|
||||
icon={app.account ? "ph:user-circle-check" : "ph:user-circle"}
|
||||
class="text-2xl {app.account ? 'text-[var(--color-success)]' : 'text-[var(--color-ink-muted)]'}"
|
||||
/>
|
||||
<div class="flex-1 text-xs">
|
||||
{#if app.account}
|
||||
<p class="font-medium">{app.account.username}</p>
|
||||
<p class="text-[var(--color-ink-muted)]">{app.account.email}</p>
|
||||
{:else}
|
||||
<p class="font-medium">Not connected</p>
|
||||
<p class="text-[var(--color-ink-muted)]">
|
||||
Sign in to sync projects to the cloud.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if app.account}
|
||||
<button
|
||||
class="rounded-md border border-[var(--color-line)] px-3 py-1.5 text-xs hover:bg-[var(--color-surface)]"
|
||||
onclick={signOut}
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="rounded-md bg-[var(--color-accent)] px-3 py-1.5 text-xs font-medium text-white hover:opacity-90"
|
||||
onclick={onsignin}
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="font-medium text-[var(--color-ink-muted)]">Appearance</span>
|
||||
<div class="flex gap-1">
|
||||
{#each [["light", "ph:sun"], ["dark", "ph:moon"]] as [value, icon]}
|
||||
<button
|
||||
class="flex items-center gap-1.5 rounded-md border px-3 py-1.5 transition
|
||||
{app.theme === value
|
||||
? 'border-[var(--color-accent)] bg-[var(--color-accent-soft)] text-[var(--color-accent)]'
|
||||
: 'border-[var(--color-line)] text-[var(--color-ink-muted)] hover:bg-[var(--color-surface-muted)]'}"
|
||||
onclick={() => applyTheme(value as "light" | "dark")}
|
||||
<div class="flex flex-col gap-1 text-xs">
|
||||
<span class="font-medium text-[var(--color-ink-muted)]">Autosave</span>
|
||||
<select class={fieldClass} bind:value={autosaveSeconds}>
|
||||
{#each autosaveOptions as option}
|
||||
<option value={option.value}>{option.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<span class="text-[var(--color-ink-muted)]">
|
||||
Saves the file being edited after you stop typing.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{:else if section === "appearance"}
|
||||
<div class="flex flex-col gap-5">
|
||||
<div class="flex flex-col gap-2 text-xs">
|
||||
<span class="font-medium text-[var(--color-ink-muted)]">Theme</span>
|
||||
<div class="flex gap-2">
|
||||
{#each [["light", "Light", "ph:sun"], ["dark", "Dark", "ph:moon"]] as [value, label, icon]}
|
||||
<button
|
||||
class="flex flex-1 items-center justify-center gap-1.5 rounded-md border px-3 py-2.5 transition
|
||||
{app.theme === value
|
||||
? 'border-[var(--color-accent)] bg-[var(--color-accent-soft)] text-[var(--color-accent)]'
|
||||
: 'border-[var(--color-line)] text-[var(--color-ink-muted)] hover:bg-[var(--color-surface-muted)]'}"
|
||||
onclick={() => applyTheme(value as "light" | "dark")}
|
||||
>
|
||||
<Icon {icon} class="text-base" />
|
||||
{label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<span class="text-[var(--color-ink-muted)]">
|
||||
Applies immediately to the editor and preview.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{:else if section === "account"}
|
||||
<div class="flex flex-col gap-5">
|
||||
<div
|
||||
class="flex items-center gap-3 rounded-md border border-[var(--color-line)] bg-[var(--color-surface-muted)] p-3"
|
||||
>
|
||||
<Icon {icon} />
|
||||
{value === "light" ? "Light" : "Dark"}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<Icon
|
||||
icon={app.account ? "ph:user-circle-check" : "ph:user-circle"}
|
||||
class="text-2xl {app.account
|
||||
? 'text-[var(--color-success)]'
|
||||
: 'text-[var(--color-ink-muted)]'}"
|
||||
/>
|
||||
<div class="flex-1 text-xs">
|
||||
{#if app.account}
|
||||
<p class="font-medium">{app.account.username}</p>
|
||||
<p class="text-[var(--color-ink-muted)]">{app.account.email}</p>
|
||||
{:else}
|
||||
<p class="font-medium">Not connected</p>
|
||||
<p class="text-[var(--color-ink-muted)]">
|
||||
Sign in to sync projects to the cloud.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if app.account}
|
||||
<button
|
||||
class="rounded-md border border-[var(--color-line)] px-3 py-1.5 text-xs hover:bg-[var(--color-surface)]"
|
||||
onclick={signOut}
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="rounded-md bg-[var(--color-accent)] px-3 py-1.5 text-xs font-medium text-white hover:opacity-90"
|
||||
onclick={onsignin}
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1 text-xs">
|
||||
<span class="font-medium text-[var(--color-ink-muted)]">
|
||||
TypstDrive server
|
||||
</span>
|
||||
<input class={fieldClass} bind:value={serverUrl} />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1 text-xs">
|
||||
<span class="font-medium text-[var(--color-ink-muted)]">
|
||||
Automatic sync
|
||||
</span>
|
||||
<select class={fieldClass} bind:value={syncMinutes}>
|
||||
{#each syncOptions as option}
|
||||
<option value={option.value}>{option.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<span class="text-[var(--color-ink-muted)]">
|
||||
Pulls and pushes cloud-linked projects on a timer. Conflicts pause
|
||||
syncing until they are resolved.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<Icon icon="ph:file-code" class="text-3xl text-[var(--color-accent)]" />
|
||||
<div>
|
||||
<p class="text-sm font-semibold">Typst Desktop</p>
|
||||
<p class="text-xs text-[var(--color-ink-muted)]">
|
||||
A local editor for Typst documents
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if info}
|
||||
<dl class="flex flex-col text-xs">
|
||||
{#each [["Version", info.version], ["Typst", info.typst_version], ["Tauri", info.tauri_version], ["Author", info.authors], ["License", info.license]] as [label, value]}
|
||||
<div
|
||||
class="flex justify-between border-b border-[var(--color-line)] py-1.5 last:border-0"
|
||||
>
|
||||
<dt class="text-[var(--color-ink-muted)]">{label}</dt>
|
||||
<dd class="font-medium">{value}</dd>
|
||||
</div>
|
||||
{/each}
|
||||
</dl>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
{#each links as link}
|
||||
<button
|
||||
class="flex items-center gap-2 rounded-md border border-[var(--color-line)] px-3 py-2 text-xs transition hover:border-[var(--color-accent)] hover:bg-[var(--color-surface-muted)]"
|
||||
onclick={() => openUrl(link.url)}
|
||||
>
|
||||
<Icon icon={link.icon} class="text-base text-[var(--color-accent)]" />
|
||||
<span class="flex-1 text-left">{link.label}</span>
|
||||
<Icon
|
||||
icon="ph:arrow-square-out"
|
||||
class="text-[var(--color-ink-muted)]"
|
||||
/>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<p
|
||||
class="rounded-md border border-[var(--color-line)] bg-[var(--color-surface-muted)] p-3 text-[11px] leading-relaxed text-[var(--color-ink-muted)]"
|
||||
>
|
||||
Typst Desktop is a community application and is not affiliated with,
|
||||
endorsed by, or supported by the official Typst project. The links
|
||||
above open the official Typst website in your browser.
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -197,14 +309,16 @@
|
||||
class="rounded-md px-3 py-1.5 text-xs text-[var(--color-ink-muted)] hover:bg-[var(--color-surface-sunken)]"
|
||||
onclick={onclose}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="rounded-md bg-[var(--color-accent)] px-3 py-1.5 text-xs font-medium text-white transition hover:opacity-90 disabled:opacity-50"
|
||||
disabled={saving}
|
||||
onclick={save}
|
||||
>
|
||||
Save
|
||||
{section === "about" || section === "appearance" ? "Close" : "Cancel"}
|
||||
</button>
|
||||
{#if section !== "about" && section !== "appearance"}
|
||||
<button
|
||||
class="rounded-md bg-[var(--color-accent)] px-3 py-1.5 text-xs font-medium text-white transition hover:opacity-90 disabled:opacity-50"
|
||||
disabled={saving}
|
||||
onclick={save}
|
||||
>
|
||||
{saving ? "Saving..." : "Save"}
|
||||
</button>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</Modal>
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import AssetsModal from "$lib/components/AssetsModal.svelte";
|
||||
import WindowControls from "$lib/components/WindowControls.svelte";
|
||||
import ImageViewer from "$lib/components/ImageViewer.svelte";
|
||||
import InfoModal from "$lib/components/InfoModal.svelte";
|
||||
import EditorToolbar from "$lib/components/EditorToolbar.svelte";
|
||||
import PageSettingsModal from "$lib/components/PageSettingsModal.svelte";
|
||||
|
||||
@@ -67,7 +66,6 @@
|
||||
| { kind: "delete-file"; path: string }
|
||||
| { kind: "login" }
|
||||
| { kind: "settings" }
|
||||
| { kind: "info" }
|
||||
| { kind: "assets" }
|
||||
| { kind: "page-settings" }
|
||||
| { kind: "conflicts" };
|
||||
@@ -514,14 +512,6 @@
|
||||
<Icon icon="ph:gear-six" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="rounded-md p-1.5 text-[var(--color-ink-muted)] transition hover:bg-[var(--color-surface-muted)] hover:text-[var(--color-ink)]"
|
||||
onclick={() => (dialog = { kind: "info" })}
|
||||
aria-label="About"
|
||||
>
|
||||
<Icon icon="ph:info" />
|
||||
</button>
|
||||
|
||||
<div class="ml-1 h-5 w-px bg-[var(--color-line)]"></div>
|
||||
|
||||
<WindowControls />
|
||||
@@ -838,8 +828,6 @@
|
||||
/>
|
||||
{:else if dialog.kind === "settings"}
|
||||
<SettingsModal onclose={close} onsignin={() => (dialog = { kind: "login" })} />
|
||||
{:else if dialog.kind === "info"}
|
||||
<InfoModal onclose={close} />
|
||||
{:else if dialog.kind === "assets"}
|
||||
<AssetsModal
|
||||
oninsert={app.view === "editor" && editorView
|
||||
|
||||
Reference in New Issue
Block a user