refactor file feature
This commit is contained in:
parent
e8545fe093
commit
881608f7de
4 changed files with 179 additions and 177 deletions
174
src/features/file/context.tsx
Normal file
174
src/features/file/context.tsx
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
import Dexie, { EntityTable } from "dexie";
|
||||||
|
import { Accessor, createContext, createMemo, onMount, ParentComponent, useContext } from "solid-js";
|
||||||
|
import { createStore } from "solid-js/store";
|
||||||
|
import { isServer } from "solid-js/web";
|
||||||
|
import { json } from "./parser";
|
||||||
|
|
||||||
|
const ROOT = '__root__';
|
||||||
|
|
||||||
|
interface FileEntity {
|
||||||
|
key: string;
|
||||||
|
handle: FileSystemDirectoryHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Store = Dexie & {
|
||||||
|
files: EntityTable<FileEntity, 'key'>;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface InternalFilesContextType {
|
||||||
|
onChange(hook: (key: string, handle: FileSystemDirectoryHandle) => any): void;
|
||||||
|
set(key: string, handle: FileSystemDirectoryHandle): Promise<void>;
|
||||||
|
get(key: string): Promise<FileSystemDirectoryHandle | undefined>;
|
||||||
|
remove(...keys: string[]): Promise<void>;
|
||||||
|
keys(): Promise<string[]>;
|
||||||
|
entries(): Promise<FileEntity[]>;
|
||||||
|
list(): Promise<FileSystemDirectoryHandle[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FilesContextType {
|
||||||
|
readonly files: Accessor<FileEntity[]>,
|
||||||
|
readonly root: Accessor<FileSystemDirectoryHandle | undefined>,
|
||||||
|
readonly loading: Accessor<boolean>,
|
||||||
|
|
||||||
|
open(directory: FileSystemDirectoryHandle): Promise<void>;
|
||||||
|
close(): Promise<void>;
|
||||||
|
get(key: string): Accessor<FileSystemDirectoryHandle | undefined>
|
||||||
|
set(key: string, handle: FileSystemDirectoryHandle): Promise<void>;
|
||||||
|
remove(key: string): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FilesContext = createContext<FilesContextType>();
|
||||||
|
|
||||||
|
const clientContext = (): InternalFilesContextType => {
|
||||||
|
const db = new Dexie('Files') as Store;
|
||||||
|
|
||||||
|
db.version(1).stores({
|
||||||
|
files: 'key, handle'
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
onChange(hook: (key: string, handle: FileSystemDirectoryHandle) => any) {
|
||||||
|
const callHook = (key: string, handle: FileSystemDirectoryHandle) => {
|
||||||
|
if (!key || key === ROOT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => hook(key, handle), 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
db.files.hook('creating', (_: string, { key, handle }: FileEntity) => { callHook(key, handle); });
|
||||||
|
db.files.hook('deleting', (_: string, { key, handle }: FileEntity = { key: undefined!, handle: undefined! }) => callHook(key, handle));
|
||||||
|
db.files.hook('updating', (_1: Object, _2: string, { key, handle }: FileEntity) => callHook(key, handle));
|
||||||
|
},
|
||||||
|
|
||||||
|
async set(key: string, handle: FileSystemDirectoryHandle) {
|
||||||
|
await db.files.put({ key, handle });
|
||||||
|
},
|
||||||
|
async get(key: string) {
|
||||||
|
return (await db.files.get(key))?.handle;
|
||||||
|
},
|
||||||
|
async remove(...keys: string[]) {
|
||||||
|
await Promise.all(keys.map(key => db.files.delete(key)));
|
||||||
|
},
|
||||||
|
async keys() {
|
||||||
|
return (await db.files.where('key').notEqual(ROOT).toArray()).map(f => f.key);
|
||||||
|
},
|
||||||
|
async entries() {
|
||||||
|
return await db.files.where('key').notEqual(ROOT).toArray();
|
||||||
|
},
|
||||||
|
async list() {
|
||||||
|
const files = await db.files.where('key').notEqual(ROOT).toArray();
|
||||||
|
|
||||||
|
return files.map(f => f.handle)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const serverContext = (): InternalFilesContextType => ({
|
||||||
|
onChange(hook: (key: string, handle: FileSystemDirectoryHandle) => any) {
|
||||||
|
|
||||||
|
},
|
||||||
|
set(key: string, handle: FileSystemDirectoryHandle) {
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
get(key: string) {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
remove(...keys: string[]) {
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
},
|
||||||
|
keys() {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
},
|
||||||
|
entries() {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
},
|
||||||
|
list() {
|
||||||
|
return Promise.resolve([]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const FilesProvider: ParentComponent = (props) => {
|
||||||
|
const internal = isServer ? serverContext() : clientContext();
|
||||||
|
|
||||||
|
const [state, setState] = createStore<{ loading: boolean, openedFiles: FileEntity[], root: FileSystemDirectoryHandle | undefined }>({ loading: true, openedFiles: [], root: undefined });
|
||||||
|
|
||||||
|
internal.onChange(async () => {
|
||||||
|
setState('openedFiles', await internal.entries());
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
(async () => {
|
||||||
|
const [root, openedFiles] = await Promise.all([
|
||||||
|
internal.get(ROOT),
|
||||||
|
internal.entries(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
setState(prev => ({ ...prev, loading: false, root, openedFiles }));
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
const context: FilesContextType = {
|
||||||
|
files: createMemo(() => state.openedFiles),
|
||||||
|
root: createMemo(() => state.root),
|
||||||
|
loading: createMemo(() => state.loading),
|
||||||
|
|
||||||
|
async open(directory: FileSystemDirectoryHandle) {
|
||||||
|
await internal.remove(...(await internal.keys()));
|
||||||
|
|
||||||
|
setState('root', directory);
|
||||||
|
|
||||||
|
await internal.set(ROOT, directory);
|
||||||
|
},
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
setState('root', undefined);
|
||||||
|
|
||||||
|
await internal.remove(ROOT);
|
||||||
|
},
|
||||||
|
|
||||||
|
get(key: string): Accessor<FileSystemDirectoryHandle | undefined> {
|
||||||
|
return createMemo(() => state.openedFiles.find(entity => entity.key === key)?.handle);
|
||||||
|
},
|
||||||
|
|
||||||
|
async set(key: string, handle: FileSystemDirectoryHandle) {
|
||||||
|
await internal.set(key, handle);
|
||||||
|
},
|
||||||
|
|
||||||
|
async remove(key: string) {
|
||||||
|
await internal.remove(key);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return <FilesContext.Provider value={context}>{props.children}</FilesContext.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useFiles = () => useContext(FilesContext)!;
|
||||||
|
|
||||||
|
export const load = (file: File): Promise<Map<string, string> | undefined> => {
|
||||||
|
switch (file.type) {
|
||||||
|
case 'application/json': return json.load(file.stream())
|
||||||
|
|
||||||
|
default: return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,177 +1,4 @@
|
||||||
import Dexie, { EntityTable } from "dexie";
|
|
||||||
import { Accessor, createContext, createMemo, onMount, ParentComponent, useContext } from "solid-js";
|
|
||||||
import { createStore } from "solid-js/store";
|
|
||||||
import { isServer } from "solid-js/web";
|
|
||||||
import * as json from './parser/json';
|
|
||||||
|
|
||||||
const ROOT = '__root__';
|
|
||||||
|
|
||||||
interface FileEntity {
|
|
||||||
key: string;
|
|
||||||
handle: FileSystemDirectoryHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Store = Dexie & {
|
|
||||||
files: EntityTable<FileEntity, 'key'>;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface InternalFilesContextType {
|
|
||||||
onChange(hook: (key: string, handle: FileSystemDirectoryHandle) => any): void;
|
|
||||||
set(key: string, handle: FileSystemDirectoryHandle): Promise<void>;
|
|
||||||
get(key: string): Promise<FileSystemDirectoryHandle | undefined>;
|
|
||||||
remove(...keys: string[]): Promise<void>;
|
|
||||||
keys(): Promise<string[]>;
|
|
||||||
entries(): Promise<FileEntity[]>;
|
|
||||||
list(): Promise<FileSystemDirectoryHandle[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FilesContextType {
|
|
||||||
readonly files: Accessor<FileEntity[]>,
|
|
||||||
readonly root: Accessor<FileSystemDirectoryHandle | undefined>,
|
|
||||||
readonly loading: Accessor<boolean>,
|
|
||||||
|
|
||||||
open(directory: FileSystemDirectoryHandle): Promise<void>;
|
|
||||||
close(): Promise<void>;
|
|
||||||
get(key: string): Accessor<FileSystemDirectoryHandle | undefined>
|
|
||||||
set(key: string, handle: FileSystemDirectoryHandle): Promise<void>;
|
|
||||||
remove(key: string): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FilesContext = createContext<FilesContextType>();
|
|
||||||
|
|
||||||
const clientContext = (): InternalFilesContextType => {
|
|
||||||
const db = new Dexie('Files') as Store;
|
|
||||||
|
|
||||||
db.version(1).stores({
|
|
||||||
files: 'key, handle'
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
onChange(hook: (key: string, handle: FileSystemDirectoryHandle) => any) {
|
|
||||||
const callHook = (key: string, handle: FileSystemDirectoryHandle) => {
|
|
||||||
if (!key || key === ROOT) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => hook(key, handle), 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
db.files.hook('creating', (_: string, { key, handle }: FileEntity) => { callHook(key, handle); });
|
|
||||||
db.files.hook('deleting', (_: string, { key, handle }: FileEntity = { key: undefined!, handle: undefined! }) => callHook(key, handle));
|
|
||||||
db.files.hook('updating', (_1: Object, _2: string, { key, handle }: FileEntity) => callHook(key, handle));
|
|
||||||
},
|
|
||||||
|
|
||||||
async set(key: string, handle: FileSystemDirectoryHandle) {
|
|
||||||
await db.files.put({ key, handle });
|
|
||||||
},
|
|
||||||
async get(key: string) {
|
|
||||||
return (await db.files.get(key))?.handle;
|
|
||||||
},
|
|
||||||
async remove(...keys: string[]) {
|
|
||||||
await Promise.all(keys.map(key => db.files.delete(key)));
|
|
||||||
},
|
|
||||||
async keys() {
|
|
||||||
return (await db.files.where('key').notEqual(ROOT).toArray()).map(f => f.key);
|
|
||||||
},
|
|
||||||
async entries() {
|
|
||||||
return await db.files.where('key').notEqual(ROOT).toArray();
|
|
||||||
},
|
|
||||||
async list() {
|
|
||||||
const files = await db.files.where('key').notEqual(ROOT).toArray();
|
|
||||||
|
|
||||||
return files.map(f => f.handle)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const serverContext = (): InternalFilesContextType => ({
|
|
||||||
onChange(hook: (key: string, handle: FileSystemDirectoryHandle) => any) {
|
|
||||||
|
|
||||||
},
|
|
||||||
set(key: string, handle: FileSystemDirectoryHandle) {
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
get(key: string) {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
},
|
|
||||||
remove(...keys: string[]) {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
},
|
|
||||||
keys() {
|
|
||||||
return Promise.resolve([]);
|
|
||||||
},
|
|
||||||
entries() {
|
|
||||||
return Promise.resolve([]);
|
|
||||||
},
|
|
||||||
list() {
|
|
||||||
return Promise.resolve([]);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const FilesProvider: ParentComponent = (props) => {
|
|
||||||
const internal = isServer ? serverContext() : clientContext();
|
|
||||||
|
|
||||||
const [state, setState] = createStore<{ loading: boolean, openedFiles: FileEntity[], root: FileSystemDirectoryHandle | undefined }>({ loading: true, openedFiles: [], root: undefined });
|
|
||||||
|
|
||||||
internal.onChange(async () => {
|
|
||||||
setState('openedFiles', await internal.entries());
|
|
||||||
});
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
(async () => {
|
|
||||||
const [root, openedFiles] = await Promise.all([
|
|
||||||
internal.get(ROOT),
|
|
||||||
internal.entries(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
setState(prev => ({ ...prev, loading: false, root, openedFiles }));
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
|
|
||||||
const context: FilesContextType = {
|
|
||||||
files: createMemo(() => state.openedFiles),
|
|
||||||
root: createMemo(() => state.root),
|
|
||||||
loading: createMemo(() => state.loading),
|
|
||||||
|
|
||||||
async open(directory: FileSystemDirectoryHandle) {
|
|
||||||
await internal.remove(...(await internal.keys()));
|
|
||||||
|
|
||||||
setState('root', directory);
|
|
||||||
|
|
||||||
await internal.set(ROOT, directory);
|
|
||||||
},
|
|
||||||
|
|
||||||
async close() {
|
|
||||||
setState('root', undefined);
|
|
||||||
|
|
||||||
await internal.remove(ROOT);
|
|
||||||
},
|
|
||||||
|
|
||||||
get(key: string): Accessor<FileSystemDirectoryHandle | undefined> {
|
|
||||||
return createMemo(() => state.openedFiles.find(entity => entity.key === key)?.handle);
|
|
||||||
},
|
|
||||||
|
|
||||||
async set(key: string, handle: FileSystemDirectoryHandle) {
|
|
||||||
await internal.set(key, handle);
|
|
||||||
},
|
|
||||||
|
|
||||||
async remove(key: string) {
|
|
||||||
await internal.remove(key);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return <FilesContext.Provider value={context}>{props.children}</FilesContext.Provider>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useFiles = () => useContext(FilesContext)!;
|
|
||||||
|
|
||||||
export const load = (file: File): Promise<Map<string, string> | undefined> => {
|
|
||||||
switch (file.type) {
|
|
||||||
case 'application/json': return json.load(file.stream())
|
|
||||||
|
|
||||||
default: return Promise.resolve(undefined);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export { useFiles, FilesProvider, load } from './context';
|
||||||
export { Grid } from './grid';
|
export { Grid } from './grid';
|
||||||
export type { Entry } from './grid';
|
export type { Entry } from './grid';
|
1
src/features/file/parser/index.ts
Normal file
1
src/features/file/parser/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * as json from './json';
|
|
@ -1,16 +1,16 @@
|
||||||
import { useNavigate } from "@solidjs/router";
|
import { useNavigate } from "@solidjs/router";
|
||||||
import { createEffect } from "solid-js";
|
import { createEffect } from "solid-js";
|
||||||
import { useFiles } from "~/features/file";
|
import { isServer } from "solid-js/web";
|
||||||
|
import { useFiles } from "~/features/file/context";
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const files = useFiles();
|
const files = useFiles();
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const loading = files.loading();
|
|
||||||
const root = files.root();
|
const root = files.root();
|
||||||
|
|
||||||
if (loading) {
|
if (isServer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue