made more parts reactive, fixing a bug with newly created files
This commit is contained in:
parent
e059b85581
commit
7ffdb2f51b
2 changed files with 95 additions and 57 deletions
|
@ -1,8 +1,9 @@
|
||||||
import { Accessor, createResource, InitializedResource, onCleanup } from "solid-js";
|
import { Accessor, createEffect, createResource, createSignal, InitializedResource, onCleanup, Resource } from "solid-js";
|
||||||
import { json } from "./parser";
|
import { json } from "./parser";
|
||||||
import { filter } from "~/utilities";
|
import { filter } from "~/utilities";
|
||||||
|
|
||||||
interface Files extends Record<string, { handle: FileSystemFileHandle, file: File }> { }
|
interface Files extends Record<string, { handle: FileSystemFileHandle, file: File }> { }
|
||||||
|
interface Contents extends Map<string, Map<string, string>> { }
|
||||||
|
|
||||||
export const read = (file: File): Promise<Map<string, string> | undefined> => {
|
export const read = (file: File): Promise<Map<string, string> | undefined> => {
|
||||||
switch (file.type) {
|
switch (file.type) {
|
||||||
|
@ -12,12 +13,10 @@ export const read = (file: File): Promise<Map<string, string> | undefined> => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const readFiles = (directory: Accessor<FileSystemDirectoryHandle>): InitializedResource<Files> => {
|
export const readFiles = (directory: Accessor<FileSystemDirectoryHandle>): Accessor<Files> => {
|
||||||
const [value, { refetch }] = createResource<Files>(async (_, { value: prev }) => {
|
return createPolled<FileSystemDirectoryHandle, Files>(directory, async (directory, prev) => {
|
||||||
prev ??= {};
|
|
||||||
|
|
||||||
const next: Files = Object.fromEntries(await Array.fromAsync(
|
const next: Files = Object.fromEntries(await Array.fromAsync(
|
||||||
filter(directory().values(), (handle): handle is FileSystemFileHandle => handle.kind === 'file' && handle.name.endsWith('.json')),
|
filter(directory.values(), (handle): handle is FileSystemFileHandle => handle.kind === 'file' && handle.name.endsWith('.json')),
|
||||||
async handle => [await handle.getUniqueId(), { file: await handle.getFile(), handle }]
|
async handle => [await handle.getUniqueId(), { file: await handle.getFile(), handle }]
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -37,15 +36,92 @@ export const readFiles = (directory: Accessor<FileSystemDirectoryHandle>): Initi
|
||||||
}
|
}
|
||||||
|
|
||||||
return prev;
|
return prev;
|
||||||
}, { initialValue: {} })
|
}, { interval: 1000, initialValue: {} });
|
||||||
|
};
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const LAST_MODIFIED = Symbol('lastModified');
|
||||||
refetch();
|
export const contentsOf = (directory: Accessor<FileSystemDirectoryHandle>): Accessor<Contents> => {
|
||||||
}, 1000);
|
return createPolled<FileSystemDirectoryHandle, Contents>(directory, async (directory, prev) => {
|
||||||
|
const files = await Array.fromAsync(walk(directory));
|
||||||
|
|
||||||
onCleanup(() => {
|
const next = async () => new Map(await Promise.all(files.map(async ({ id, file }) => {
|
||||||
clearInterval(interval);
|
const entries = (await read(file))!;
|
||||||
|
entries[LAST_MODIFIED] = file.lastModified;
|
||||||
|
|
||||||
|
return [id, entries] as const;
|
||||||
|
})));
|
||||||
|
|
||||||
|
if (files.length !== prev.size) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.every(({ id }) => prev.has(id)) === false) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.every(({ id, file }) => prev.get(id)![LAST_MODIFIED] === file.lastModified) === false) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev;
|
||||||
|
}, { interval: 1000, initialValue: new Map() });
|
||||||
|
};
|
||||||
|
|
||||||
|
function createPolled<S, T>(source: Accessor<S>, callback: (source: S, prev: T) => T | Promise<T>, options: { interval: number, initialValue: T }): Accessor<T> {
|
||||||
|
const { interval, initialValue } = options;
|
||||||
|
const [value, setValue] = createSignal(initialValue);
|
||||||
|
const tick = createTicker(interval);
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
tick();
|
||||||
|
const s = source();
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const prev = value();
|
||||||
|
const next: T = await callback(s, prev);
|
||||||
|
|
||||||
|
setValue(() => next);
|
||||||
|
})();
|
||||||
});
|
});
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function createTicker(interval: number): Accessor<boolean> {
|
||||||
|
const [tick, update] = createSignal(true);
|
||||||
|
|
||||||
|
const intervalId = setInterval(() => {
|
||||||
|
update(v => !v);
|
||||||
|
}, interval);
|
||||||
|
|
||||||
|
onCleanup(() => {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
});
|
||||||
|
|
||||||
|
return tick;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function* walk(directory: FileSystemDirectoryHandle, path: string[] = []): AsyncGenerator<{ id: string, handle: FileSystemFileHandle, path: string[], file: File }, void, never> {
|
||||||
|
for await (const handle of directory.values()) {
|
||||||
|
if (handle.kind === 'directory') {
|
||||||
|
yield* walk(handle, [...path, handle.name]);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handle.name.endsWith('.json')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = await handle.getUniqueId();
|
||||||
|
const file = await handle.getFile();
|
||||||
|
|
||||||
|
yield { id, handle, path, file };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Map<K, V> {
|
||||||
|
[LAST_MODIFIED]: number;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { Component, createEffect, createMemo, createResource, createSignal, For, onMount, ParentProps, Setter, Show } from "solid-js";
|
import { Component, createEffect, createMemo, createResource, createSignal, For, onMount, ParentProps, Setter, Show } from "solid-js";
|
||||||
import { Created, filter, MutarionKind, Mutation, splitAt } from "~/utilities";
|
import { Created, MutarionKind, Mutation, splitAt } from "~/utilities";
|
||||||
import { Sidebar } from "~/components/sidebar";
|
import { Sidebar } from "~/components/sidebar";
|
||||||
import { Menu } from "~/features/menu";
|
import { Menu } from "~/features/menu";
|
||||||
import { Grid, read, readFiles, TreeProvider, Tree, useFiles } from "~/features/file";
|
import { Grid, read, readFiles, TreeProvider, Tree, useFiles } from "~/features/file";
|
||||||
|
@ -14,32 +14,10 @@ import { makePersisted } from "@solid-primitives/storage";
|
||||||
import { writeClipboard } from "@solid-primitives/clipboard";
|
import { writeClipboard } from "@solid-primitives/clipboard";
|
||||||
import { destructure } from "@solid-primitives/destructure";
|
import { destructure } from "@solid-primitives/destructure";
|
||||||
import css from "./edit.module.css";
|
import css from "./edit.module.css";
|
||||||
|
import { contentsOf } from "~/features/file/helpers";
|
||||||
|
|
||||||
const isInstalledPWA = !isServer && window.matchMedia('(display-mode: standalone)').matches;
|
const isInstalledPWA = !isServer && window.matchMedia('(display-mode: standalone)').matches;
|
||||||
|
|
||||||
async function* walk(directory: FileSystemDirectoryHandle, path: string[] = []): AsyncGenerator<{ id: string, handle: FileSystemFileHandle, path: string[], lang: string, entries: Map<string, string> }, void, never> {
|
|
||||||
for await (const handle of directory.values()) {
|
|
||||||
if (handle.kind === 'directory') {
|
|
||||||
yield* walk(handle, [...path, handle.name]);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!handle.name.endsWith('.json')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = await handle.getUniqueId();
|
|
||||||
const file = await handle.getFile();
|
|
||||||
const lang = file.name.split('.').at(0)!;
|
|
||||||
const entries = await load(file);
|
|
||||||
|
|
||||||
if (entries !== undefined) {
|
|
||||||
yield { id, handle, path, lang, entries };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Entries extends Map<string, { key: string, } & Record<string, { value: string, handle: FileSystemFileHandle, id: string }>> { };
|
interface Entries extends Map<string, { key: string, } & Record<string, { value: string, handle: FileSystemFileHandle, id: string }>> { };
|
||||||
|
|
||||||
export default function Edit(props: ParentProps) {
|
export default function Edit(props: ParentProps) {
|
||||||
|
@ -74,25 +52,17 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const tabs = createMemo(() => filesContext.files().map(({ key, handle }) => {
|
const tabs = createMemo(() => filesContext.files().map(({ key, handle }) => {
|
||||||
const [api, setApi] = createSignal<(GridApi & { addLocale(locale: string): void })>();
|
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, { id: string, handle: FileSystemFileHandle }>>(new Map());
|
const __files = readFiles(() => handle);
|
||||||
|
const files = createMemo(() => new Map(Object.entries(__files()).map(([id, { file, handle }]) => [file.name.split('.').at(0)!, { handle, id }])));
|
||||||
(async () => {
|
|
||||||
const files = await Array.fromAsync(
|
|
||||||
filter(handle.values(), entry => entry.kind === 'file'),
|
|
||||||
async file => [file.name.split('.').at(0)!, { handle: file, id: await file.getUniqueId() }] as const
|
|
||||||
);
|
|
||||||
|
|
||||||
setFiles(new Map(files));
|
|
||||||
})();
|
|
||||||
|
|
||||||
return ({ key, handle, api, setApi, entries, setEntries, files });
|
return ({ key, handle, api, setApi, entries, setEntries, files });
|
||||||
}));
|
}));
|
||||||
const [active, setActive] = makePersisted(createSignal<string>(), { name: 'edit__aciveTab' });
|
const [active, setActive] = makePersisted(createSignal<string>(), { name: 'edit__aciveTab' });
|
||||||
const [contents, setContents] = createSignal<Map<string, Map<string, string>>>(new Map());
|
|
||||||
const [newKeyPrompt, setNewKeyPrompt] = createSignal<PromptApi>();
|
const [newKeyPrompt, setNewKeyPrompt] = createSignal<PromptApi>();
|
||||||
const [newLanguagePrompt, setNewLanguagePrompt] = createSignal<PromptApi>();
|
const [newLanguagePrompt, setNewLanguagePrompt] = createSignal<PromptApi>();
|
||||||
|
const contents = contentsOf(() => props.root);
|
||||||
|
|
||||||
const tab = createMemo(() => {
|
const tab = createMemo(() => {
|
||||||
const name = active();
|
const name = active();
|
||||||
|
@ -225,14 +195,6 @@ const Editor: Component<{ root: FileSystemDirectoryHandle }> = (props) => {
|
||||||
return existingFiles.concat(newFiles);
|
return existingFiles.concat(newFiles);
|
||||||
});
|
});
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const directory = props.root;
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
setContents(new Map(await Array.fromAsync(walk(directory), ({ id, entries }) => [id, entries] as const)))
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
|
|
||||||
const commands = {
|
const commands = {
|
||||||
open: createCommand('page.edit.command.open', async () => {
|
open: createCommand('page.edit.command.open', async () => {
|
||||||
const directory = await window.showDirectoryPicker({ mode: 'readwrite' });
|
const directory = await window.showDirectoryPicker({ mode: 'readwrite' });
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue