working on fixing/reimplementing save command now that the mutations logic is more complete
This commit is contained in:
parent
6ed9c74862
commit
992bb77d2f
12 changed files with 239 additions and 58 deletions
|
@ -30,4 +30,9 @@ export default defineConfig({
|
||||||
compact: true,
|
compact: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
prerender: {
|
||||||
|
crawlLinks: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,5 +26,15 @@
|
||||||
"sizes": "2092x1295",
|
"sizes": "2092x1295",
|
||||||
"form_factor": "wide"
|
"form_factor": "wide"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"file_handlers": [
|
||||||
|
{
|
||||||
|
"action": "/edit",
|
||||||
|
"accept": {
|
||||||
|
"text/*": [
|
||||||
|
".json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -78,4 +78,12 @@ h1 {
|
||||||
|
|
||||||
p {
|
p {
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding-inline: var(--padding-s);
|
||||||
|
background-color: var(--surface-3);
|
||||||
|
border: 1px solid var(--surface-5);
|
||||||
|
border-radius: var(--radii-m);
|
||||||
}
|
}
|
|
@ -55,7 +55,7 @@ interface TreeContextType {
|
||||||
const TreeContext = createContext<TreeContextType>();
|
const TreeContext = createContext<TreeContextType>();
|
||||||
|
|
||||||
export const Tree: Component<{ entries: Entry[], children: readonly [(folder: Accessor<FolderEntry>) => JSX.Element, (file: Accessor<FileEntry>) => JSX.Element], open?: TreeContextType['open'] }> = (props) => {
|
export const Tree: Component<{ entries: Entry[], children: readonly [(folder: Accessor<FolderEntry>) => JSX.Element, (file: Accessor<FileEntry>) => JSX.Element], open?: TreeContextType['open'] }> = (props) => {
|
||||||
const [selection, setSelection] = createSignal<object[]>([]);
|
const [, setSelection] = createSignal<object[]>([]);
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
open: props.open ?? (() => { }),
|
open: props.open ?? (() => { }),
|
||||||
|
|
34
src/components/prompt.module.css
Normal file
34
src/components/prompt.module.css
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
.prompt {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--padding-m);
|
||||||
|
padding: var(--padding-m);
|
||||||
|
background-color: var(--surface-1);
|
||||||
|
color: var(--text-2);
|
||||||
|
border: 1px solid var(--surface-5);
|
||||||
|
border-radius: var(--radii-m);
|
||||||
|
|
||||||
|
&:not(&[open]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[open]::backdrop {
|
||||||
|
background-color: color(from var(--surface-1) xyz x y z / .3);
|
||||||
|
backdrop-filter: blur(.25em);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > form {
|
||||||
|
display: contents;
|
||||||
|
|
||||||
|
& > header > .title {
|
||||||
|
font-size: var(--text-l);
|
||||||
|
color: var(--text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > footer {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
justify-content: end;
|
||||||
|
gap: var(--padding-m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/components/prompt.tsx
Normal file
75
src/components/prompt.tsx
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import { createEffect, createSignal, createUniqueId, JSX, onMount, ParentComponent, Show } from "solid-js";
|
||||||
|
import css from './prompt.module.css';
|
||||||
|
|
||||||
|
export interface PromptApi {
|
||||||
|
showModal(): Promise<FormData | undefined>;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PromptCanceledError extends Error { }
|
||||||
|
|
||||||
|
export const Prompt: ParentComponent<{ api: (api: PromptApi) => any, title?: string, description?: string | JSX.Element }> = (props) => {
|
||||||
|
const [dialog, setDialog] = createSignal<HTMLDialogElement>();
|
||||||
|
const [form, setForm] = createSignal<HTMLFormElement>();
|
||||||
|
const [resolvers, setResolvers] = createSignal<[(...args: any[]) => any, (...args: any[]) => any]>();
|
||||||
|
const submitId = createUniqueId();
|
||||||
|
const cancelId = createUniqueId();
|
||||||
|
|
||||||
|
const api = {
|
||||||
|
async showModal(): Promise<FormData | undefined> {
|
||||||
|
const { promise, resolve, reject } = Promise.withResolvers();
|
||||||
|
|
||||||
|
setResolvers([resolve, reject]);
|
||||||
|
|
||||||
|
dialog()!.showModal();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
return new FormData(form());
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (!(e instanceof PromptCanceledError)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog()!.close();
|
||||||
|
setResolvers(undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (e: SubmitEvent) => {
|
||||||
|
resolvers()?.[0]();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancel = (e: Event) => {
|
||||||
|
resolvers()?.[1](new PromptCanceledError());
|
||||||
|
};
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
props.api(api);
|
||||||
|
});
|
||||||
|
|
||||||
|
return <dialog class={css.prompt} ref={setDialog} onsubmit={onSubmit} onCancel={onCancel} onReset={onCancel}>
|
||||||
|
<form method="dialog" ref={setForm}>
|
||||||
|
<Show when={props.title || props.description}>
|
||||||
|
<header>
|
||||||
|
<Show when={props.title}>{
|
||||||
|
title => <b class={css.title}>{title()}</b>
|
||||||
|
}</Show>
|
||||||
|
|
||||||
|
<Show when={props.description}>{
|
||||||
|
description => <p>{description()}</p>
|
||||||
|
}</Show>
|
||||||
|
</header>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<main>{props.children}</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<button id={submitId} type="submit">Ok</button>
|
||||||
|
<button id={cancelId} type="reset">Cancel</button>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</dialog>;
|
||||||
|
};
|
|
@ -10,7 +10,7 @@ export default createHandler(() => (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||||
{assets}
|
{assets}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { Accessor, Component, createContext, createEffect, createMemo, createRenderEffect, createSignal, createUniqueId, For, onMount, ParentComponent, Show, useContext } from "solid-js";
|
import { Accessor, Component, createContext, createEffect, createMemo, createSignal, For, ParentComponent, Show, useContext } from "solid-js";
|
||||||
import { createStore, produce, reconcile, unwrap } from "solid-js/store";
|
import { createStore, produce, unwrap } from "solid-js/store";
|
||||||
import { SelectionProvider, useSelection, selectable } from "../selectable";
|
import { SelectionProvider, useSelection, selectable } from "../selectable";
|
||||||
import { debounce, deepCopy, deepDiff, Mutation } from "~/utilities";
|
import { debounce, deepCopy, deepDiff, Mutation } from "~/utilities";
|
||||||
import css from './grid.module.css';
|
import css from './grid.module.css';
|
||||||
import diff from "microdiff";
|
|
||||||
|
|
||||||
selectable // prevents removal of import
|
selectable // prevents removal of import
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ const GridContext = createContext<GridContextType>();
|
||||||
const isLeaf = (entry: Entry | Leaf): entry is Leaf => Object.values(entry).some(v => typeof v === 'string');
|
const isLeaf = (entry: Entry | Leaf): entry is Leaf => Object.values(entry).some(v => typeof v === 'string');
|
||||||
const useGrid = () => useContext(GridContext)!;
|
const useGrid = () => useContext(GridContext)!;
|
||||||
|
|
||||||
const GridProvider: ParentComponent<{ rows: Rows }> = (props) => {
|
export const Grid: Component<{ class?: string, columns: string[], rows: Rows, api?: (api: GridApi) => any }> = (props) => {
|
||||||
const [selection, setSelection] = createSignal<SelectionItem[]>([]);
|
const [selection, setSelection] = createSignal<SelectionItem[]>([]);
|
||||||
const [state, setState] = createStore<{ rows: Record<string, Record<string, string>>, snapshot: Rows, numberOfRows: number }>({
|
const [state, setState] = createStore<{ rows: Record<string, Record<string, string>>, snapshot: Rows, numberOfRows: number }>({
|
||||||
rows: {},
|
rows: {},
|
||||||
|
@ -45,7 +44,12 @@ const GridProvider: ParentComponent<{ rows: Rows }> = (props) => {
|
||||||
numberOfRows: 0,
|
numberOfRows: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mutations = createMemo(() => deepDiff(state.snapshot, state.rows).toArray());
|
const mutations = createMemo(() => {
|
||||||
|
// enumerate all values to make sure the memo is recalculated on any change
|
||||||
|
Object.values(state.rows).map(entry => Object.values(entry));
|
||||||
|
|
||||||
|
return deepDiff(state.snapshot, state.rows).toArray();
|
||||||
|
});
|
||||||
const rows = createMemo(() => Object.fromEntries(Object.entries(state.rows).map(([key, row]) => [key, unwrap(row)] as const)));
|
const rows = createMemo(() => Object.fromEntries(Object.entries(state.rows).map(([key, row]) => [key, unwrap(row)] as const)));
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
@ -57,10 +61,6 @@ const GridProvider: ParentComponent<{ rows: Rows }> = (props) => {
|
||||||
setState('numberOfRows', Object.keys(state.rows).length);
|
setState('numberOfRows', Object.keys(state.rows).length);
|
||||||
});
|
});
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
console.log(mutations());
|
|
||||||
});
|
|
||||||
|
|
||||||
const ctx: GridContextType = {
|
const ctx: GridContextType = {
|
||||||
rows,
|
rows,
|
||||||
mutations,
|
mutations,
|
||||||
|
@ -82,7 +82,7 @@ const GridProvider: ParentComponent<{ rows: Rows }> = (props) => {
|
||||||
|
|
||||||
insert(prop: string) {
|
insert(prop: string) {
|
||||||
setState('rows', produce(rows => {
|
setState('rows', produce(rows => {
|
||||||
rows[prop] = { en: '' };
|
rows[prop] = Object.fromEntries(props.columns.slice(1).map(lang => [lang, '']));
|
||||||
|
|
||||||
return rows
|
return rows
|
||||||
}))
|
}))
|
||||||
|
@ -91,15 +91,16 @@ const GridProvider: ParentComponent<{ rows: Rows }> = (props) => {
|
||||||
|
|
||||||
return <GridContext.Provider value={ctx}>
|
return <GridContext.Provider value={ctx}>
|
||||||
<SelectionProvider selection={setSelection} multiSelect>
|
<SelectionProvider selection={setSelection} multiSelect>
|
||||||
{props.children}
|
<Api api={props.api} />
|
||||||
|
|
||||||
|
<_Grid class={props.class} columns={props.columns} rows={rows()} />
|
||||||
</SelectionProvider>
|
</SelectionProvider>
|
||||||
</GridContext.Provider>;
|
</GridContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Grid: Component<{ class?: string, columns: string[], rows: Rows, api?: (api: GridApi) => any }> = (props) => {
|
const _Grid: Component<{ class?: string, columns: string[], rows: Record<string, Record<string, string>> }> = (props) => {
|
||||||
const columnCount = createMemo(() => props.columns.length - 1);
|
const columnCount = createMemo(() => props.columns.length - 1);
|
||||||
const root = createMemo<Entry>(() => props.rows
|
const root = createMemo<Entry>(() => Object.entries(props.rows)
|
||||||
?.entries()
|
|
||||||
.reduce((aggregate, [key, value]) => {
|
.reduce((aggregate, [key, value]) => {
|
||||||
let obj: any = aggregate;
|
let obj: any = aggregate;
|
||||||
const parts = key.split('.');
|
const parts = key.split('.');
|
||||||
|
@ -121,15 +122,11 @@ export const Grid: Component<{ class?: string, columns: string[], rows: Rows, ap
|
||||||
}, {}));
|
}, {}));
|
||||||
|
|
||||||
return <section class={`${css.table} ${props.class}`} style={{ '--columns': columnCount() }}>
|
return <section class={`${css.table} ${props.class}`} style={{ '--columns': columnCount() }}>
|
||||||
<GridProvider rows={props.rows}>
|
<Head headers={props.columns} />
|
||||||
<Api api={props.api} />
|
|
||||||
|
|
||||||
<Head headers={props.columns} />
|
<main class={css.main}>
|
||||||
|
<Row entry={root()} />
|
||||||
<main class={css.main}>
|
</main>
|
||||||
<Row entry={root()} />
|
|
||||||
</main>
|
|
||||||
</GridProvider>
|
|
||||||
</section>
|
</section>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ interface InternalFilesContextType {
|
||||||
onChange(hook: (key: string, handle: FileSystemDirectoryHandle) => any): void;
|
onChange(hook: (key: string, handle: FileSystemDirectoryHandle) => any): void;
|
||||||
set(key: string, handle: FileSystemDirectoryHandle): Promise<void>;
|
set(key: string, handle: FileSystemDirectoryHandle): Promise<void>;
|
||||||
get(key: string): Promise<FileSystemDirectoryHandle | undefined>;
|
get(key: string): Promise<FileSystemDirectoryHandle | undefined>;
|
||||||
remove(key: string): Promise<void>;
|
remove(...keys: string[]): Promise<void>;
|
||||||
keys(): Promise<string[]>;
|
keys(): Promise<string[]>;
|
||||||
entries(): Promise<FileEntity[]>;
|
entries(): Promise<FileEntity[]>;
|
||||||
list(): Promise<FileSystemDirectoryHandle[]>;
|
list(): Promise<FileSystemDirectoryHandle[]>;
|
||||||
|
@ -65,8 +65,8 @@ const clientContext = (): InternalFilesContextType => {
|
||||||
async get(key: string) {
|
async get(key: string) {
|
||||||
return (await db.files.get(key))?.handle;
|
return (await db.files.get(key))?.handle;
|
||||||
},
|
},
|
||||||
async remove(key: string) {
|
async remove(...keys: string[]) {
|
||||||
return (await db.files.delete(key));
|
await Promise.all(keys.map(key => db.files.delete(key)));
|
||||||
},
|
},
|
||||||
async keys() {
|
async keys() {
|
||||||
return (await db.files.where('key').notEqual(ROOT).toArray()).map(f => f.key);
|
return (await db.files.where('key').notEqual(ROOT).toArray()).map(f => f.key);
|
||||||
|
@ -92,7 +92,7 @@ const serverContext = (): InternalFilesContextType => ({
|
||||||
get(key: string) {
|
get(key: string) {
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
},
|
},
|
||||||
remove(key: string) {
|
remove(...keys: string[]) {
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
},
|
},
|
||||||
keys() {
|
keys() {
|
||||||
|
@ -131,9 +131,12 @@ export const FilesProvider: ParentComponent = (props) => {
|
||||||
files: createMemo(() => state.openedFiles),
|
files: createMemo(() => state.openedFiles),
|
||||||
root: createMemo(() => state.root),
|
root: createMemo(() => state.root),
|
||||||
|
|
||||||
open(directory: FileSystemDirectoryHandle) {
|
async open(directory: FileSystemDirectoryHandle) {
|
||||||
|
await internal.remove(...(await internal.keys()));
|
||||||
|
|
||||||
setState('root', directory);
|
setState('root', directory);
|
||||||
internal.set(ROOT, directory);
|
|
||||||
|
await internal.set(ROOT, directory);
|
||||||
},
|
},
|
||||||
|
|
||||||
get(key: string): Accessor<FileSystemDirectoryHandle | undefined> {
|
get(key: string): Accessor<FileSystemDirectoryHandle | undefined> {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, createEffect, createMemo, createSignal, For, ParentProps, Setter, Show } from "solid-js";
|
import { Component, createEffect, createMemo, createSignal, For, onMount, ParentProps, Setter, Show } from "solid-js";
|
||||||
import { filter, MutarionKind, Mutation, splitAt } from "~/utilities";
|
import { filter, MutarionKind, Mutation, splitAt } from "~/utilities";
|
||||||
import { Sidebar } from "~/components/sidebar";
|
import { Sidebar } from "~/components/sidebar";
|
||||||
import { emptyFolder, FolderEntry, walk as fileTreeWalk, Tree } from "~/components/filetree";
|
import { emptyFolder, FolderEntry, walk as fileTreeWalk, Tree } from "~/components/filetree";
|
||||||
|
@ -9,6 +9,7 @@ import { GridApi } from "~/features/file/grid";
|
||||||
import { Tab, Tabs } from "~/components/tabs";
|
import { Tab, Tabs } from "~/components/tabs";
|
||||||
import css from "./edit.module.css";
|
import css from "./edit.module.css";
|
||||||
import { isServer } from "solid-js/web";
|
import { isServer } from "solid-js/web";
|
||||||
|
import { Prompt, PromptApi } from "~/components/prompt";
|
||||||
|
|
||||||
const isInstalledPWA = !isServer && window.matchMedia('(display-mode: standalone)').matches;
|
const isInstalledPWA = !isServer && window.matchMedia('(display-mode: standalone)').matches;
|
||||||
|
|
||||||
|
@ -60,8 +61,18 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
|
||||||
const tabs = createMemo(() => filesContext.files().map(({ key, handle }) => {
|
const tabs = createMemo(() => filesContext.files().map(({ key, handle }) => {
|
||||||
const [api, setApi] = createSignal<GridApi>();
|
const [api, setApi] = createSignal<GridApi>();
|
||||||
const [entries, setEntries] = createSignal<Entries>(new Map());
|
const [entries, setEntries] = createSignal<Entries>(new Map());
|
||||||
|
const [files, setFiles] = createSignal<Map<string, { key: string, handle: FileSystemFileHandle }>>(new Map());
|
||||||
|
|
||||||
return ({ key, handle, api, setApi, entries, setEntries });
|
(async () => {
|
||||||
|
const files = await Array.fromAsync(
|
||||||
|
filter(handle.values(), entry => entry.kind === 'file'),
|
||||||
|
async file => [file.name.split('.').at(0)!, { handle: file, key: await file.getUniqueId() }] as const
|
||||||
|
);
|
||||||
|
|
||||||
|
setFiles(new Map(files));
|
||||||
|
})();
|
||||||
|
|
||||||
|
return ({ key, handle, api, setApi, entries, setEntries, files });
|
||||||
}));
|
}));
|
||||||
const [active, setActive] = createSignal<string>();
|
const [active, setActive] = createSignal<string>();
|
||||||
const [contents, setContents] = createSignal<Map<string, Map<string, string>>>(new Map());
|
const [contents, setContents] = createSignal<Map<string, Map<string, string>>>(new Map());
|
||||||
|
@ -74,14 +85,27 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
|
||||||
const api = createMemo(() => tab()?.api());
|
const api = createMemo(() => tab()?.api());
|
||||||
const mutations = createMemo<(Mutation & { file?: { value: string, handle: FileSystemFileHandle, id: string } })[]>(() => tabs().flatMap(tab => {
|
const mutations = createMemo<(Mutation & { file?: { value: string, handle: FileSystemFileHandle, id: string } })[]>(() => tabs().flatMap(tab => {
|
||||||
const entries = tab.entries();
|
const entries = tab.entries();
|
||||||
|
const files = tab.files();
|
||||||
const mutations = tab.api()?.mutations() ?? [];
|
const mutations = tab.api()?.mutations() ?? [];
|
||||||
|
|
||||||
return mutations.map(m => {
|
return mutations.flatMap(m => {
|
||||||
const [key, lang] = splitAt(m.key, m.key.lastIndexOf('.'));
|
switch (m.kind) {
|
||||||
|
case MutarionKind.Update: {
|
||||||
|
const [key, lang] = splitAt(m.key, m.key.lastIndexOf('.'));
|
||||||
|
|
||||||
console.log(m.key, key, lang, entries);
|
return { kind: MutarionKind.Update, key, file: entries.get(key)?.[lang] };
|
||||||
|
}
|
||||||
|
|
||||||
return { ...m, key, file: entries.get(key)?.[lang] };
|
case MutarionKind.Create: {
|
||||||
|
return Object.entries(m.value).map(([lang, value]) => ({ kind: MutarionKind.Create, key: m.key, file: files.get(lang)!, value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
case MutarionKind.Delete: {
|
||||||
|
return files.values().map(file => ({ kind: MutarionKind.Delete, key: m.key, file })).toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
default: throw new Error('unreachable code');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
const mutatedFiles = createMemo(() =>
|
const mutatedFiles = createMemo(() =>
|
||||||
|
@ -149,10 +173,21 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
console.log(mutations());
|
console.log(mutatedFiles());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
console.log(mutatedData());
|
||||||
|
});
|
||||||
|
|
||||||
|
const [prompt, setPrompt] = createSignal<PromptApi>();
|
||||||
|
|
||||||
const commands = {
|
const commands = {
|
||||||
|
open: createCommand('open folder', async () => {
|
||||||
|
const directory = await window.showDirectoryPicker({ mode: 'readwrite' });
|
||||||
|
|
||||||
|
await filesContext.open(directory);
|
||||||
|
}, { key: 'o', modifier: Modifier.Control }),
|
||||||
close: createCommand('close folder', async () => {
|
close: createCommand('close folder', async () => {
|
||||||
filesContext.remove('__root__');
|
filesContext.remove('__root__');
|
||||||
}),
|
}),
|
||||||
|
@ -197,8 +232,15 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
|
||||||
|
|
||||||
remove(Object.keys(selection()));
|
remove(Object.keys(selection()));
|
||||||
}, { key: 'delete', modifier: Modifier.None }),
|
}, { key: 'delete', modifier: Modifier.None }),
|
||||||
inserNewKey: createCommand('insert new key', () => {
|
inserNewKey: createCommand('insert new key', async () => {
|
||||||
api()?.insert('this.is.some.key');
|
const formData = await prompt()?.showModal();
|
||||||
|
const key = formData?.get('key')?.toString();
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
api()?.insert(key);
|
||||||
}),
|
}),
|
||||||
inserNewLanguage: noop.withLabel('insert new language'),
|
inserNewLanguage: noop.withLabel('insert new language'),
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -214,10 +256,6 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
|
||||||
<Menu.Item label="file">
|
<Menu.Item label="file">
|
||||||
<Menu.Item command={commands.open} />
|
<Menu.Item command={commands.open} />
|
||||||
|
|
||||||
<Menu.Item command={commands.close} />
|
|
||||||
|
|
||||||
<Menu.Separator />
|
|
||||||
|
|
||||||
<Menu.Item command={commands.save} />
|
<Menu.Item command={commands.save} />
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
|
@ -240,6 +278,10 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
|
||||||
<Menu.Item command={noop.withLabel('view')} />
|
<Menu.Item command={noop.withLabel('view')} />
|
||||||
</Menu.Root>
|
</Menu.Root>
|
||||||
|
|
||||||
|
<Prompt api={setPrompt} title="Which key do you want to create?" description={<>hint: use <code>.</code> to denote nested keys,<br /> i.e. <code>this.is.some.key</code> would be a key that is four levels deep</>}>
|
||||||
|
<input name="key" value="this.is.an.awesome.key" placeholder="name of new key ()" />
|
||||||
|
</Prompt>
|
||||||
|
|
||||||
<Sidebar as="aside" label={tree().name} class={css.sidebar}>
|
<Sidebar as="aside" label={tree().name} class={css.sidebar}>
|
||||||
<Tree entries={tree().entries}>{[
|
<Tree entries={tree().entries}>{[
|
||||||
folder => {
|
folder => {
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
justify-items: start;
|
place-items: center start;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ export const splitAt = (subject: string, index: number): readonly [string, strin
|
||||||
return [subject.slice(0, index), subject.slice(index + 1)] as const;
|
return [subject.slice(0, index), subject.slice(index + 1)] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const debounce = <T extends (...args: any[]) => void>(callback: T, delay: number): T => {
|
export const debounce = <T extends (...args: any[]) => void>(callback: T, delay: number): ((...args: Parameters<T>) => void) => {
|
||||||
let handle: ReturnType<typeof setTimeout> | undefined;
|
let handle: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
|
||||||
return (...args: any[]) => {
|
return (...args: Parameters<T>) => {
|
||||||
if (handle) {
|
if (handle) {
|
||||||
clearTimeout(handle);
|
clearTimeout(handle);
|
||||||
}
|
}
|
||||||
|
@ -58,10 +58,7 @@ export function* deepDiff<T1 extends object, T2 extends object>(a: T1, b: T2, pa
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [[keyA, valueA], [keyB, valueB]] of zip(entriesOf(a), entriesOf(b)).take(10)) {
|
for (const [[keyA, valueA], [keyB, valueB]] of zip(entriesOf(a), entriesOf(b))) {
|
||||||
// console.log('deepdiff', keyA, valueA, keyB, valueB);
|
|
||||||
// continue;
|
|
||||||
|
|
||||||
if (!keyA && !keyB) {
|
if (!keyA && !keyB) {
|
||||||
throw new Error('this code should not be reachable, there is a bug with an unhandled/unknown edge case');
|
throw new Error('this code should not be reachable, there is a bug with an unhandled/unknown edge case');
|
||||||
}
|
}
|
||||||
|
@ -125,9 +122,6 @@ const zip = function* (a: Iterable<readonly [string | number, any]>, b: Iterable
|
||||||
// if we have a match on the keys of a and b we can simply consume and yield
|
// if we have a match on the keys of a and b we can simply consume and yield
|
||||||
if (iterA.current.key === iterB.current.key) {
|
if (iterA.current.key === iterB.current.key) {
|
||||||
yield [iterA.consume(), iterB.consume()];
|
yield [iterA.consume(), iterB.consume()];
|
||||||
|
|
||||||
iterA.advance();
|
|
||||||
iterB.advance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// key of a aligns with last key in buffer b
|
// key of a aligns with last key in buffer b
|
||||||
|
@ -140,8 +134,6 @@ const zip = function* (a: Iterable<readonly [string | number, any]>, b: Iterable
|
||||||
}
|
}
|
||||||
|
|
||||||
yield [a, iterB.consume()];
|
yield [a, iterB.consume()];
|
||||||
|
|
||||||
iterB.advance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the reverse case, key of b is aligns with the last key in buffer a
|
// the reverse case, key of b is aligns with the last key in buffer a
|
||||||
|
@ -154,8 +146,14 @@ const zip = function* (a: Iterable<readonly [string | number, any]>, b: Iterable
|
||||||
}
|
}
|
||||||
|
|
||||||
yield [iterA.consume(), b];
|
yield [iterA.consume(), b];
|
||||||
|
}
|
||||||
|
|
||||||
iterA.advance();
|
else if (iterA.done && !iterB.done) {
|
||||||
|
yield [EMPTY, iterB.consume()];
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (!iterA.done && iterB.done) {
|
||||||
|
yield [iterA.consume(), EMPTY];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neiter of the above cases are hit.
|
// Neiter of the above cases are hit.
|
||||||
|
@ -196,8 +194,11 @@ const bufferredIterator = <T extends readonly [string | number, any]>(subject: I
|
||||||
|
|
||||||
consume() {
|
consume() {
|
||||||
cursor = 0;
|
cursor = 0;
|
||||||
|
const value = buffer.shift()!;
|
||||||
|
|
||||||
return buffer.shift()!;
|
this.advance();
|
||||||
|
|
||||||
|
return value;
|
||||||
},
|
},
|
||||||
|
|
||||||
flush(): T[] {
|
flush(): T[] {
|
||||||
|
@ -213,7 +214,7 @@ const bufferredIterator = <T extends readonly [string | number, any]>(subject: I
|
||||||
},
|
},
|
||||||
|
|
||||||
get done() {
|
get done() {
|
||||||
return done && Math.max(0, buffer.length - 1) === cursor;
|
return done && buffer.length === 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
get top() {
|
get top() {
|
||||||
|
@ -247,3 +248,9 @@ export const filter = async function*<T, S extends T>(subject: AsyncIterableIter
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const map = async function*<TIn, TResult>(subject: AsyncIterableIterator<TIn>, predicate: (value: TIn) => TResult): AsyncGenerator<TResult, void, unknown> {
|
||||||
|
for await (const value of subject) {
|
||||||
|
yield predicate(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue