mirror of
https://github.com/narrrl/narl-workspace.git
synced 2026-11-03 16:41:27 +01:00
keys: files and uploads answer the keyboard
`gx` opens the file under the cursor in a tab of its own, as the browser shows it. `:download` (`:dl`) downloads it, after asking for the vault when the file is sealed; on the open document it is `:export`. `:source` turns a markdown file between rendered and as written, which before had only a button. A sealed file asks for the vault before `gx` instead of after, so the browser does not block the late tab as a popup. The media list gets a cursor: `j`/`k` walk it, `i` or `Enter` embeds the highlighted upload at the cursor while writing, `y` copies its address and `x` deletes it. Deleting asks through the `[y/N]` prompt, which names what would break, in place of the browser's confirm(): that one was the last modal on the page and needed the pointer to dismiss. A footer lists the keys, as the agenda's does.
This commit is contained in:
1 parent
54bc333be2
commit
37443cf083
7 files changed
+172
-28
No files matched your search
@@ -79,6 +79,7 @@ it; in the document pane it is that document. In the browse pane:
|
|||||||
| `Ctrl-D` / `Ctrl-U` | half a page down / up the open document |
|
| `Ctrl-D` / `Ctrl-U` | half a page down / up the open document |
|
||||||
| `]]` / `[[` | the next / previous heading of the open document, in the editor too |
|
| `]]` / `[[` | the next / previous heading of the open document, in the editor too |
|
||||||
| `gO` | the document's headings as a list — `j`/`k`, `Enter` jumps there (`:outline`) |
|
| `gO` | the document's headings as a list — `j`/`k`, `Enter` jumps there (`:outline`) |
|
||||||
|
| `gx` | open the file under the cursor in a tab of its own |
|
||||||
| `F` | a letter over every link and button in view; typing it follows it — links out of the workspace open in a tab of their own |
|
| `F` | a letter over every link and button in view; typing it follows it — links out of the workspace open in a tab of their own |
|
||||||
| `Enter`, `l` | open the highlighted entry; on an open keystore or sealed file, `Enter` asks for its password |
|
| `Enter`, `l` | open the highlighted entry; on an open keystore or sealed file, `Enter` asks for its password |
|
||||||
| `o` | a new document here, straight into insert mode |
|
| `o` | a new document here, straight into insert mode |
|
||||||
@@ -247,6 +248,10 @@ or a crash leaves in `uploads/`. That runs at start and once a day. A file in
|
|||||||
the tree is never one of them: it is deleted from its folder, not from
|
the tree is never one of them: it is deleted from its folder, not from
|
||||||
`:media`.
|
`:media`.
|
||||||
|
|
||||||
|
`:media` lists every upload: `j`/`k` walk it, `i` or `Enter` embeds the
|
||||||
|
highlighted one at the cursor while writing, `y` copies its address and `x`
|
||||||
|
deletes it, asking first.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
A file is a node like a document, only with bytes instead of text: it sits in
|
A file is a node like a document, only with bytes instead of text: it sits in
|
||||||
@@ -257,7 +262,7 @@ place:
|
|||||||
- PDFs, in the browser's own viewer;
|
- PDFs, in the browser's own viewer;
|
||||||
- text and source code of any kind, recognised by its name or, failing that, by
|
- text and source code of any kind, recognised by its name or, failing that, by
|
||||||
its first few kilobytes, with line numbers and the first 256 KiB of it;
|
its first few kilobytes, with line numbers and the first 256 KiB of it;
|
||||||
- markdown, rendered, with `source` to see it as written;
|
- markdown, rendered, with `source` (or `:source`) to see it as written;
|
||||||
- CSV and TSV, as a table of the first 500 rows;
|
- CSV and TSV, as a table of the first 500 rows;
|
||||||
- certificates, keys and keystores — `.pem`, `.crt`, `.cer`, `.der`, `.csr`,
|
- certificates, keys and keystores — `.pem`, `.crt`, `.cer`, `.der`, `.csr`,
|
||||||
`.key`, `.p8`, `.p12`, `.pfx` — read on the server: subject, issuer,
|
`.key`, `.p8`, `.p12`, `.pfx` — read on the server: subject, issuer,
|
||||||
@@ -266,7 +271,8 @@ place:
|
|||||||
it for that one look and is never stored. Nothing of a private key is ever
|
it for that one look and is never stored. Nothing of a private key is ever
|
||||||
sent back but its algorithm and size.
|
sent back but its algorithm and size.
|
||||||
|
|
||||||
Anything else is a download. `:name`
|
Anything else is a download: `:download` (`:dl`) fetches the file under the
|
||||||
|
cursor, and `gx` opens one in a tab of its own as the browser shows it. `:name`
|
||||||
renames it, and the download is called whatever it is called now; `:mv`, `m`
|
renames it, and the download is called whatever it is called now; `:mv`, `m`
|
||||||
and `p` move it; `x` deletes it. `:upload` picks files for the folder being
|
and `p` move it; `x` deletes it. `:upload` picks files for the folder being
|
||||||
browsed, or drop them onto the list. Each upload is written to disk as it
|
browsed, or drop them onto the list. Each upload is written to disk as it
|
||||||
|
|||||||
+55
-2
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { hooks, runCommand } from './lib/commands'
|
import { followLink, hooks, runCommand } from './lib/commands'
|
||||||
import { embedSnippet } from './lib/markdown'
|
import { embedSnippet } from './lib/markdown'
|
||||||
import Agenda from './lib/Agenda.svelte'
|
import Agenda from './lib/Agenda.svelte'
|
||||||
import Bar from './lib/Bar.svelte'
|
import Bar from './lib/Bar.svelte'
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
import Verify from './lib/Verify.svelte'
|
import Verify from './lib/Verify.svelte'
|
||||||
import Login from './lib/Login.svelte'
|
import Login from './lib/Login.svelte'
|
||||||
import Overview from './lib/Overview.svelte'
|
import Overview from './lib/Overview.svelte'
|
||||||
import MediaBrowser from './lib/MediaBrowser.svelte'
|
import MediaBrowser, { copyMedia, insertMedia, removeMedia } from './lib/MediaBrowser.svelte'
|
||||||
import Actions from './lib/Actions.svelte'
|
import Actions from './lib/Actions.svelte'
|
||||||
import Prompt from './lib/Prompt.svelte'
|
import Prompt from './lib/Prompt.svelte'
|
||||||
import SecretPrompt from './lib/SecretPrompt.svelte'
|
import SecretPrompt from './lib/SecretPrompt.svelte'
|
||||||
@@ -329,6 +329,48 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The media list's keys: j/k down the files, i inserts, y copies, x deletes. */
|
||||||
|
function mediaKey(event: KeyboardEvent): boolean {
|
||||||
|
const files = workspace.media
|
||||||
|
const last = Math.max(files.length - 1, 0)
|
||||||
|
const file = files[workspace.mediaCursor]
|
||||||
|
switch (event.key) {
|
||||||
|
case 'j':
|
||||||
|
case 'ArrowDown':
|
||||||
|
workspace.mediaCursor = Math.min(workspace.mediaCursor + 1, last)
|
||||||
|
return true
|
||||||
|
case 'k':
|
||||||
|
case 'ArrowUp':
|
||||||
|
workspace.mediaCursor = Math.max(workspace.mediaCursor - 1, 0)
|
||||||
|
return true
|
||||||
|
case 'G':
|
||||||
|
workspace.mediaCursor = last
|
||||||
|
return true
|
||||||
|
case 'Enter':
|
||||||
|
case 'i':
|
||||||
|
if (file) insertMedia(file)
|
||||||
|
return true
|
||||||
|
case 'y':
|
||||||
|
if (file) void copyMedia(file)
|
||||||
|
return true
|
||||||
|
case 'x':
|
||||||
|
if (file) void removeMedia(file)
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** `gx`: the file as the browser shows it, in a tab of its own. */
|
||||||
|
function openInTab() {
|
||||||
|
const file = workspace.target?.file
|
||||||
|
if (!file) return workspace.say('gx opens a file — put the cursor on one', 'error')
|
||||||
|
// A tab opened after the vault's question would be stopped as a popup, so
|
||||||
|
// a sealed file asks for the vault to be opened first instead.
|
||||||
|
if (file.encrypted && !workspace.vault?.unlocked) return workspace.say('sealed — :unlock first, then gx', 'error')
|
||||||
|
followLink(file.url, true)
|
||||||
|
}
|
||||||
|
|
||||||
/** Alt+h / Alt+l: focus moves between the panes, out of the editor too. */
|
/** Alt+h / Alt+l: focus moves between the panes, out of the editor too. */
|
||||||
function focusPane(pane: 'list' | 'document') {
|
function focusPane(pane: 'list' | 'document') {
|
||||||
if (pane === 'document' && !workspace.open) return
|
if (pane === 'document' && !workspace.open) return
|
||||||
@@ -448,6 +490,12 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (workspace.overlay === 'media' && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
||||||
|
if (mediaKey(event)) {
|
||||||
|
event.preventDefault()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if (workspace.overlay === 'outline' && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
if (workspace.overlay === 'outline' && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
||||||
if (outlineKey(event)) {
|
if (outlineKey(event)) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@@ -586,6 +634,11 @@
|
|||||||
void runCommand('outline')
|
void runCommand('outline')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (previous === 'g' && key === 'x') {
|
||||||
|
event.preventDefault()
|
||||||
|
openInTab()
|
||||||
|
return
|
||||||
|
}
|
||||||
if (previous === 'd' && key === 'd') {
|
if (previous === 'd' && key === 'd') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
void runCommand('d')
|
void runCommand('d')
|
||||||
|
|||||||
@@ -86,13 +86,20 @@
|
|||||||
return cert?.act() ?? false
|
return cert?.act() ?? false
|
||||||
}
|
}
|
||||||
const hints = () => reader ?? null
|
const hints = () => reader ?? null
|
||||||
|
const flip = () => {
|
||||||
|
if (kind !== 'markdown' || text === null) return false
|
||||||
|
source = !source
|
||||||
|
return true
|
||||||
|
}
|
||||||
hooks.scrollReader = scroll
|
hooks.scrollReader = scroll
|
||||||
hooks.fileAction = act
|
hooks.fileAction = act
|
||||||
hooks.hintRoot = hints
|
hooks.hintRoot = hints
|
||||||
|
hooks.toggleSource = flip
|
||||||
return () => {
|
return () => {
|
||||||
if (hooks.scrollReader === scroll) hooks.scrollReader = () => false
|
if (hooks.scrollReader === scroll) hooks.scrollReader = () => false
|
||||||
if (hooks.fileAction === act) hooks.fileAction = () => false
|
if (hooks.fileAction === act) hooks.fileAction = () => false
|
||||||
if (hooks.hintRoot === hints) hooks.hintRoot = () => null
|
if (hooks.hintRoot === hints) hooks.hintRoot = () => null
|
||||||
|
if (hooks.toggleSource === flip) hooks.toggleSource = () => false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
['gO', 'the headings as a list: j/k, Enter jumps there'],
|
['gO', 'the headings as a list: j/k, Enter jumps there'],
|
||||||
['F', 'hints over the links and buttons in view — type one to follow it'],
|
['F', 'hints over the links and buttons in view — type one to follow it'],
|
||||||
['gf / gx', 'in the editor: follow the link under the cursor / open it in a tab'],
|
['gf / gx', 'in the editor: follow the link under the cursor / open it in a tab'],
|
||||||
|
['gx', 'open the file under the cursor in a tab of its own'],
|
||||||
['Enter · l', 'open a document, or enter a folder — on an open keystore or sealed file, Enter asks for its password'],
|
['Enter · l', 'open a document, or enter a folder — on an open keystore or sealed file, Enter asks for its password'],
|
||||||
['h', 'from the document to the list, from the list up a level'],
|
['h', 'from the document to the list, from the list up a level'],
|
||||||
['o', 'new document here, straight into insert mode'],
|
['o', 'new document here, straight into insert mode'],
|
||||||
|
|||||||
@@ -1,36 +1,64 @@
|
|||||||
<script lang="ts">
|
<script lang="ts" module>
|
||||||
import { api } from './api'
|
import { api, type MediaFile } from './api'
|
||||||
import { hooks } from './commands'
|
import { hooks } from './commands'
|
||||||
import { embedSnippet } from './markdown'
|
import { embedSnippet } from './markdown'
|
||||||
import { workspace } from './store.svelte'
|
import { workspace } from './store.svelte'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* What a row's buttons do, out here so the keys in App.svelte do the same:
|
||||||
|
* i inserts, y copies, x deletes.
|
||||||
|
*/
|
||||||
|
export function insertMedia(file: MediaFile) {
|
||||||
|
if (!workspace.editing) return workspace.say('insert works while writing — i on a document first', 'error')
|
||||||
|
hooks.insertText(embedSnippet(file))
|
||||||
|
workspace.overlay = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function copyMedia(file: MediaFile) {
|
||||||
|
const copied = await workspace.copy(location.origin + file.url)
|
||||||
|
workspace.say(copied ? `copied the address of ${file.filename}` : 'the clipboard said no', copied ? 'info' : 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeMedia(file: MediaFile) {
|
||||||
|
if (file.file_node !== null) return workspace.say(`${file.filename} is a file in the tree — delete it there`, 'error')
|
||||||
|
const used = file.node_ids.length === 1 ? '1 document' : `${file.node_ids.length} documents`
|
||||||
|
const warning = file.node_ids.length > 0 ? ` it is embedded in ${used}, which will break` : ''
|
||||||
|
if (!(await workspace.ask(`delete ${file.filename}?${warning}`))) return
|
||||||
|
await workspace.guard(async () => {
|
||||||
|
await api.deleteMedia(file.id)
|
||||||
|
await workspace.loadMedia()
|
||||||
|
workspace.mediaCursor = Math.min(workspace.mediaCursor, Math.max(workspace.media.length - 1, 0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
import { formatBytes, formatStamp } from './util'
|
import { formatBytes, formatStamp } from './util'
|
||||||
|
|
||||||
|
let sheet = $state<HTMLDivElement>()
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const index = workspace.mediaCursor
|
||||||
|
sheet?.querySelector<HTMLElement>(`[data-index="${index}"]`)?.scrollIntoView({ block: 'nearest' })
|
||||||
|
})
|
||||||
|
|
||||||
const usage = (nodeIds: number[], fileNode: number | null) => {
|
const usage = (nodeIds: number[], fileNode: number | null) => {
|
||||||
const embeds = nodeIds.map((id) => `#${id}`).join(' ')
|
const embeds = nodeIds.map((id) => `#${id}`).join(' ')
|
||||||
if (fileNode !== null) return embeds ? `in the tree · ${embeds}` : 'in the tree'
|
if (fileNode !== null) return embeds ? `in the tree · ${embeds}` : 'in the tree'
|
||||||
return embeds || 'unattached'
|
return embeds || 'unattached'
|
||||||
}
|
}
|
||||||
|
|
||||||
async function remove(id: string, filename: string, nodeIds: number[]) {
|
|
||||||
const used = nodeIds.length === 1 ? '1 document' : `${nodeIds.length} documents`
|
|
||||||
const warning = nodeIds.length > 0 ? ` it is embedded in ${used}, which will break.` : ''
|
|
||||||
if (!confirm(`delete ${filename}?${warning}`)) return
|
|
||||||
await workspace.guard(async () => {
|
|
||||||
await api.deleteMedia(id)
|
|
||||||
await workspace.loadMedia()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="sheet">
|
<div class="sheet" bind:this={sheet}>
|
||||||
<header>
|
<header>
|
||||||
<span class="label rule"><span class="marker"></span><span class="title">media</span> {workspace.media.length} files</span>
|
<span class="label rule"><span class="marker"></span><span class="title">media</span> {workspace.media.length} files</span>
|
||||||
<button onclick={() => (workspace.overlay = 'none')}>esc</button>
|
<button onclick={() => (workspace.overlay = 'none')}>esc</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{#each workspace.media as file (file.id)}
|
{#each workspace.media as file, index (file.id)}
|
||||||
<li>
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||||
|
<li class:at={index === workspace.mediaCursor} data-index={index} onpointerenter={() => (workspace.mediaCursor = index)}>
|
||||||
<div class="thumb">
|
<div class="thumb">
|
||||||
{#if file.encrypted}
|
{#if file.encrypted}
|
||||||
<span class="label">sealed</span>
|
<span class="label">sealed</span>
|
||||||
@@ -51,16 +79,11 @@
|
|||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
{#if workspace.editing}
|
{#if workspace.editing}
|
||||||
<button
|
<button onclick={() => insertMedia(file)}>insert</button>
|
||||||
onclick={() => {
|
|
||||||
hooks.insertText(embedSnippet(file))
|
|
||||||
workspace.overlay = 'none'
|
|
||||||
}}>insert</button
|
|
||||||
>
|
|
||||||
{/if}
|
{/if}
|
||||||
<button onclick={() => workspace.copy(location.origin + file.url)}>copy url</button>
|
<button onclick={() => copyMedia(file)}>copy url</button>
|
||||||
{#if file.file_node === null}
|
{#if file.file_node === null}
|
||||||
<button class="danger" onclick={() => remove(file.id, file.filename, file.node_ids)}>rm</button>
|
<button class="danger" onclick={() => removeMedia(file)}>rm</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -68,6 +91,20 @@
|
|||||||
<li class="muted">nothing uploaded yet — attach a file from the editor.</li>
|
<li class="muted">nothing uploaded yet — attach a file from the editor.</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<footer class="label">
|
||||||
|
<span class="kbd">j</span><span class="kbd">k</span> move
|
||||||
|
<span class="gap"></span>
|
||||||
|
{#if workspace.editing}
|
||||||
|
<span class="kbd">i</span> insert
|
||||||
|
<span class="gap"></span>
|
||||||
|
{/if}
|
||||||
|
<span class="kbd">y</span> copy url
|
||||||
|
<span class="gap"></span>
|
||||||
|
<span class="kbd">x</span> delete
|
||||||
|
<span class="gap"></span>
|
||||||
|
<span class="kbd">q</span><span class="kbd">esc</span> close
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -101,6 +138,14 @@
|
|||||||
transition: background-color var(--t-base) var(--ease-glide);
|
transition: background-color var(--t-base) var(--ease-glide);
|
||||||
}
|
}
|
||||||
li:hover { background: var(--surface); }
|
li:hover { background: var(--surface); }
|
||||||
|
li.at { background: var(--surface-alt); box-shadow: inset 2px 0 0 var(--light); }
|
||||||
|
|
||||||
|
footer { display: flex; align-items: center; gap: 4px; font-size: 10px; }
|
||||||
|
footer .kbd { margin-right: 2px; }
|
||||||
|
footer .gap { width: 14px; }
|
||||||
|
@media (max-width: 767px), (pointer: coarse) {
|
||||||
|
footer { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
.thumb {
|
.thumb {
|
||||||
width: 52px;
|
width: 52px;
|
||||||
|
|||||||
+32
-1
@@ -24,6 +24,9 @@ export const hooks = {
|
|||||||
hintRoot: (): HTMLElement | null => null,
|
hintRoot: (): HTMLElement | null => null,
|
||||||
/* Filled in by the file pane: what Enter does there, false when nothing. */
|
/* Filled in by the file pane: what Enter does there, false when nothing. */
|
||||||
fileAction: (): boolean => false,
|
fileAction: (): boolean => false,
|
||||||
|
/* `:source`: a markdown file between rendered and raw; false when the pane
|
||||||
|
shows no markdown. */
|
||||||
|
toggleSource: (): boolean => false,
|
||||||
/* `]]` / `[[` in whatever is being read; false when it has no headings. */
|
/* `]]` / `[[` in whatever is being read; false when it has no headings. */
|
||||||
jumpHeading: (_direction: 1 | -1): boolean => false,
|
jumpHeading: (_direction: 1 | -1): boolean => false,
|
||||||
/* `gO`: the headings of what is on screen, read or being written, and the
|
/* `gO`: the headings of what is on screen, read or being written, and the
|
||||||
@@ -760,6 +763,33 @@ export const commands: CommandSpec[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'download',
|
||||||
|
aliases: ['dl'],
|
||||||
|
group: 'sharing',
|
||||||
|
help: 'download the file under the cursor, or the open one — the open document goes as :export writes it',
|
||||||
|
run: () =>
|
||||||
|
workspace.guard(async () => {
|
||||||
|
const node = workspace.target
|
||||||
|
if (!node) return workspace.say('nothing selected', 'error')
|
||||||
|
if (node.kind === 'document' && node.id === workspace.open?.id) return runCommand('export')
|
||||||
|
if (!node.file) return workspace.say(`a ${node.kind} is no file — :export! downloads everything`, 'error')
|
||||||
|
if (node.file.encrypted && !(await workspace.ensureUnlocked())) return
|
||||||
|
const anchor = document.createElement('a')
|
||||||
|
anchor.href = node.file.url
|
||||||
|
anchor.download = node.name
|
||||||
|
anchor.click()
|
||||||
|
workspace.say(`downloading ${node.name}`)
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'source',
|
||||||
|
group: 'documents',
|
||||||
|
help: 'show an open markdown file as written, or rendered again',
|
||||||
|
run: () => {
|
||||||
|
if (!hooks.toggleSource()) workspace.say('only an open markdown file has a source to show', 'error')
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'upload',
|
name: 'upload',
|
||||||
aliases: ['up'],
|
aliases: ['up'],
|
||||||
@@ -777,9 +807,10 @@ export const commands: CommandSpec[] = [
|
|||||||
{
|
{
|
||||||
name: 'media',
|
name: 'media',
|
||||||
group: 'editing',
|
group: 'editing',
|
||||||
help: 'browse everything you have uploaded',
|
help: 'browse everything you have uploaded — j/k, i inserts it while writing, y copies its address, x deletes',
|
||||||
run: () => workspace.guard(async () => {
|
run: () => workspace.guard(async () => {
|
||||||
await workspace.loadMedia()
|
await workspace.loadMedia()
|
||||||
|
workspace.mediaCursor = 0
|
||||||
workspace.overlay = 'media'
|
workspace.overlay = 'media'
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ class Workspace {
|
|||||||
/** The vault, as last heard from the server; null until asked. */
|
/** The vault, as last heard from the server; null until asked. */
|
||||||
vault = $state<VaultStatus | null>(null)
|
vault = $state<VaultStatus | null>(null)
|
||||||
media = $state<MediaFile[]>([])
|
media = $state<MediaFile[]>([])
|
||||||
|
mediaCursor = $state(0)
|
||||||
overview = $state<SpaceOverview[]>([])
|
overview = $state<SpaceOverview[]>([])
|
||||||
overviewCursor = $state(0)
|
overviewCursor = $state(0)
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user