diff --git a/src/dialog/browser/browser.ts b/src/dialog/browser/browser.ts index 528fd06..2e908a9 100644 --- a/src/dialog/browser/browser.ts +++ b/src/dialog/browser/browser.ts @@ -60,7 +60,7 @@ export class ProviderBasedBrowserDialog implements BrowserDialog { const parsed_path = path.parse(file_path) this.dirs.storyfile = parsed_path.dir this.dirs.working = '/usr/' + parsed_path.name.toLowerCase().trim() - await this.dialog!.update_direntry(this.dirs.working) + this.dialog!.update_direntry(this.dirs.working) return file_path } diff --git a/src/dialog/browser/common.ts b/src/dialog/browser/common.ts index 46fcaea..741376b 100644 --- a/src/dialog/browser/common.ts +++ b/src/dialog/browser/common.ts @@ -11,12 +11,12 @@ https://github.com/curiousdannii/asyncglk import path from 'path-browserify-esm' -import type {Provider, DirBrowser, FileData, DirEntry} from './interface.js' +import type {DirEntry, FileData, FilesMetadata, Provider} from './interface.js' export class NullProvider implements Provider { next: Provider = this async browse(): Promise { - return new CachingDirBrowser({}, this) + throw new Error('A NullProvider should not be browsed') } async delete(_path: string) { return null @@ -24,6 +24,9 @@ export class NullProvider implements Provider { async exists(_path: string) { return null } + metadata(): Promise { + throw new Error('Cannot get metadata from NullProvider') + } async read(_path: string) { return null } @@ -40,32 +43,23 @@ interface NestableDirEntry extends DirEntry { } /** A caching directory browser that receives the list of files once and remembers for as long as the dialog is open */ -export class CachingDirBrowser implements DirBrowser { - files: NestableDirEntry = { - children: [], - dir: true, - full_path: '/usr', - name: 'usr', - } +export class DirBrowser { + files!: NestableDirEntry provider: Provider - constructor(files: Record, provider: Provider) { + constructor(files: FilesMetadata, provider: Provider) { this.provider = provider - for (const [file_path, meta] of Object.entries(files)) { - if (file_path.startsWith('/usr/')) { - const parsed_path = path.parse(file_path) - const dir_entry = this.cd(parsed_path.dir) - dir_entry.children!.push({ - dir: false, - full_path: file_path, - name: parsed_path.base, - meta, - }) - } + this.update(files) + } + + async add_files(files: Record) { + for (const [path, data] of Object.entries(files)) { + await this.provider.write(path, data) } + this.update(await this.provider.metadata()) } - async browse(dir_path: string): Promise { + browse(dir_path: string): DirEntry[] { if (!dir_path.startsWith('/usr')) { throw new Error('Can only browse /usr') } @@ -91,4 +85,25 @@ export class CachingDirBrowser implements DirBrowser { } return dir_entry } + + private update(metadata: FilesMetadata) { + this.files = { + children: [], + dir: true, + full_path: '/usr', + name: 'usr', + } + for (const [file_path, meta] of Object.entries(metadata)) { + if (file_path.startsWith('/usr/')) { + const parsed_path = path.parse(file_path) + const dir_entry = this.cd(parsed_path.dir) + dir_entry.children!.push({ + dir: false, + full_path: file_path, + name: parsed_path.base, + meta, + }) + } + } + } } \ No newline at end of file diff --git a/src/dialog/browser/download.ts b/src/dialog/browser/download.ts index d75f434..e5adeda 100644 --- a/src/dialog/browser/download.ts +++ b/src/dialog/browser/download.ts @@ -11,8 +11,8 @@ https://github.com/curiousdannii/asyncglk // The download provider stores its own files just in a map (maybe to be cached in the future), but if files are written next to them, then they need to be done so in another provider -import {NullProvider} from './common.js' -import type {DirBrowser, DownloadOptions, ProgressCallback, Provider} from './interface.js' +import {DirBrowser, NullProvider} from './common.js' +import type {DownloadOptions, FilesMetadata, ProgressCallback, Provider} from './interface.js' import {utf8decoder} from '../../common/misc.js' export class DownloadProvider implements Provider { @@ -53,6 +53,10 @@ export class DownloadProvider implements Provider { } } + metadata(): Promise { + throw new Error('Cannot get metadata from DownloadProvider') + } + async read(path: string): Promise { if (this.store.has(path)) { return this.store.get(path)! @@ -178,6 +182,16 @@ export async function read_response(response: Response, progress_callback?: Prog return result } +/** Read an uploaded file and return it as a Uint8Array */ +export function read_uploaded_file(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.onerror = () => reject(reader.error) + reader.onload = () => resolve(new Uint8Array(reader.result as ArrayBuffer)) + reader.readAsArrayBuffer(file) + }) +} + function url_to_path(url: string) { if (url.startsWith('https:')) { return '/download/https/' + url.substring(8) diff --git a/src/dialog/browser/interface.ts b/src/dialog/browser/interface.ts index 4b878f7..71caa03 100644 --- a/src/dialog/browser/interface.ts +++ b/src/dialog/browser/interface.ts @@ -11,6 +11,8 @@ https://github.com/curiousdannii/asyncglk import type {AsyncDialog} from '../common/interface.js' +import {DirBrowser} from './common.js' + export type ProgressCallback = (bytes: number) => void export interface BrowserDialog extends AsyncDialog { @@ -36,17 +38,14 @@ export interface Provider { delete(path: string): Promise /** Check if a file exists */ exists(path: string): Promise + /** Get all file metadata */ + metadata(): Promise /** Read a file */ read(path: string): Promise /** Write a file */ write(path: string, data: Uint8Array): Promise } -/** Browse a directory; may cache all the files or request each time you change directory */ -export interface DirBrowser { - browse(path: string): Promise -} - export interface DirEntry { dir: boolean full_path: string @@ -54,6 +53,8 @@ export interface DirEntry { meta?: FileData } +export type FilesMetadata = Record + export interface FileData { atime: number etag?: string diff --git a/src/dialog/browser/storage.ts b/src/dialog/browser/storage.ts index 2fbd3e3..22d03b1 100644 --- a/src/dialog/browser/storage.ts +++ b/src/dialog/browser/storage.ts @@ -11,10 +11,10 @@ https://github.com/curiousdannii/asyncglk import {decode as base32768_decode, encode as base32768_encode} from 'base32768' -import {CachingDirBrowser, NullProvider} from './common.js' -import type {DirBrowser, FileData, Provider} from './interface.js' +import {DirBrowser, NullProvider} from './common.js' +import type {Provider} from './interface.js' -type WebStorageFileMetadata = Pick +//type WebStorageFileMetadata = Pick const METADATA_KEY = 'dialog_metadata' @@ -40,8 +40,8 @@ export class WebStorageProvider implements Provider { async browse(): Promise { if (this.browseable) { - const metadata = this.get_metadata() - return new CachingDirBrowser(metadata, this) + const metadata = this.metadata() + return new DirBrowser(metadata, this) } else { return this.next.browse() @@ -67,6 +67,10 @@ export class WebStorageProvider implements Provider { } } + metadata() { + return JSON.parse(this.store.getItem(METADATA_KEY) || '{}') + } + async read(path: string): Promise { if (path.startsWith(this.prefix)) { const res = this.store.getItem(path) @@ -93,13 +97,9 @@ export class WebStorageProvider implements Provider { } } - private get_metadata(): Record { - return JSON.parse(this.store.getItem(METADATA_KEY) || '{}') - } - private update_metadata(path: string, op: MetadataUpdateOperation) { const now = Date.now() - const metadata = this.get_metadata() + const metadata = this.metadata() switch (op) { case MetadataUpdateOperation.DELETE: delete metadata[path] diff --git a/src/dialog/browser/ui/FileDialog.svelte b/src/dialog/browser/ui/FileDialog.svelte index c5487f6..881c226 100644 --- a/src/dialog/browser/ui/FileDialog.svelte +++ b/src/dialog/browser/ui/FileDialog.svelte @@ -1,6 +1,7 @@ @@ -146,6 +176,10 @@ flex-grow: 1; margin-left: 6px; } + + #add_file { + display: none; + } - {#if saving} -
- -
- {/if} +
+ + +
+
\ No newline at end of file