Compress stored content for improved caching size and speed. Add loading component on every Content ready awaiting to reduce first render time.
This commit is contained in:
parent
1c3211d28e
commit
fde752b6ed
|
|
@ -9,7 +9,10 @@
|
|||
<div class="text-3xl">Une erreur est survenue.</div>
|
||||
</div>
|
||||
<pre class="text-center text-wrap">Erreur {{ error?.statusCode }}: {{ error?.message }}</pre>
|
||||
<Button @click="handleError">Revenir en lieu sûr</Button>
|
||||
<button class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
|
||||
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
|
||||
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
|
||||
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 p-2" @click="handleError">Revenir en lieu sûr</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -56,16 +56,34 @@ import { Content, iconByType } from '#shared/content.util';
|
|||
import { dom, icon } from '#shared/dom.util';
|
||||
import { unifySlug } from '#shared/general.util';
|
||||
import { tooltip } from '#shared/floating.util';
|
||||
import { link } from '#shared/components.util';
|
||||
import { link, loading } from '#shared/components.util';
|
||||
|
||||
const open = ref(false);
|
||||
let tree: TreeDOM | undefined;
|
||||
const { loggedIn, user } = useUserSession();
|
||||
|
||||
const route = useRouter().currentRoute;
|
||||
const path = computed(() => route.value.params.path ? decodeURIComponent(unifySlug(route.value.params.path)) : undefined);
|
||||
|
||||
await Content.init();
|
||||
const tree = new TreeDOM((item, depth) => {
|
||||
|
||||
const unmount = useRouter().afterEach((to, from, failure) => {
|
||||
if(failure)
|
||||
return;
|
||||
|
||||
to.name === 'explore-path' && (unifySlug(to.params.path ?? '').split('/').map((e, i, a) => a.slice(0, i).join('/')) ?? []).forEach(e => tree?.toggle(tree.tree.search('path', e)[0], true));
|
||||
});
|
||||
|
||||
watch(route, () => {
|
||||
open.value = false;
|
||||
});
|
||||
|
||||
const treeParent = useTemplateRef('treeParent');
|
||||
onMounted(() => {
|
||||
if(treeParent.value)
|
||||
{
|
||||
treeParent.value.replaceChildren(loading('normal'));
|
||||
Content.ready.then(() => {
|
||||
tree = new TreeDOM((item, depth) => {
|
||||
return dom('div', { class: 'group flex items-center ps-2 outline-none relative cursor-pointer', style: { 'padding-inline-start': `${depth / 1.5}em` } }, [dom('div', { class: ['flex flex-1 items-center hover:border-accent-blue hover:text-accent-purple max-w-full cursor-pointer font-medium'], attributes: { 'data-private': item.private } }, [
|
||||
icon('radix-icons:chevron-right', { class: 'h-4 w-4 transition-transform absolute group-data-[state=open]:rotate-90', style: { 'left': `${depth / 1.5 - 1}em` } }),
|
||||
dom('div', { class: 'pl-1.5 py-1.5 flex-1 truncate', text: item.title, attributes: { title: item.title } }),
|
||||
|
|
@ -78,23 +96,11 @@ const tree = new TreeDOM((item, depth) => {
|
|||
item.private ? tooltip(icon('radix-icons:lock-closed', { class: 'mx-1' }), 'Privé', 'right') : undefined,
|
||||
], { class: ['flex flex-1 items-center hover:border-accent-blue hover:text-accent-purple max-w-full'], attributes: { 'data-private': item.private }, active: 'text-accent-blue' }, item.path ? { name: 'explore-path', params: { path: item.path } } : undefined )]);
|
||||
}, (item) => item.navigable);
|
||||
(path.value?.split('/').map((e, i, a) => a.slice(0, i).join('/')) ?? []).forEach(e => tree.toggle(tree.tree.search('path', e)[0], true));
|
||||
(path.value?.split('/').map((e, i, a) => a.slice(0, i).join('/')) ?? []).forEach(e => tree?.toggle(tree.tree.search('path', e)[0], true));
|
||||
|
||||
const unmount = useRouter().afterEach((to, from, failure) => {
|
||||
if(failure)
|
||||
return;
|
||||
|
||||
to.name === 'explore-path' && (unifySlug(to.params.path ?? '').split('/').map((e, i, a) => a.slice(0, i).join('/')) ?? []).forEach(e => tree.toggle(tree.tree.search('path', e)[0], true));
|
||||
});
|
||||
|
||||
watch(route, () => {
|
||||
open.value = false;
|
||||
});
|
||||
|
||||
const treeParent = useTemplateRef('treeParent');
|
||||
onMounted(() => {
|
||||
if(treeParent.value)
|
||||
treeParent.value.appendChild(tree.container);
|
||||
treeParent.value!.replaceChildren(tree.container);
|
||||
})
|
||||
}
|
||||
})
|
||||
onUnmounted(() => {
|
||||
unmount();
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ const route = useRouter().currentRoute;
|
|||
const path = computed(() => unifySlug(route.value.params.path ?? ''));
|
||||
|
||||
onMounted(async () => {
|
||||
if(element.value && path.value && await Content.ready)
|
||||
if(element.value && path.value)
|
||||
{
|
||||
overview.value = Content.render(element.value, path.value);
|
||||
overview.value = await Content.render(element.value, path.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -86,7 +86,7 @@ function push()
|
|||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if(tree.value && container.value && await Content.ready)
|
||||
if(tree.value && container.value)
|
||||
{
|
||||
const load = loading('normal');
|
||||
tree.value.appendChild(load);
|
||||
|
|
@ -100,7 +100,7 @@ onMounted(async () => {
|
|||
|
||||
editor = new Editor();
|
||||
|
||||
tree.value.replaceChild(editor.tree.container, load);
|
||||
Content.ready.then(() => tree.value!.replaceChild(editor.tree.container, load));
|
||||
container.value.appendChild(editor.container);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Canvas, CanvasEditor } from "#shared/canvas.util";
|
|||
import render from "#shared/markdown.util";
|
||||
import { confirm, contextmenu, tooltip } from "#shared/floating.util";
|
||||
import { cancelPropagation, dom, icon, text, type Node } from "#shared/dom.util";
|
||||
import { loading } from "#shared/components.util";
|
||||
import { async, loading } from "#shared/components.util";
|
||||
import prose, { h1, h2 } from "#shared/proses";
|
||||
import { getID, parsePath } from '#shared/general.util';
|
||||
import { TreeDOM, type Recursive } from '#shared/tree';
|
||||
|
|
@ -302,8 +302,9 @@ export class Content
|
|||
const handle = await Content.root.getFileHandle(path, options);
|
||||
const file = await handle.getFile();
|
||||
|
||||
const text = await file.text();
|
||||
return text;
|
||||
//@ts-ignore
|
||||
const response = await new Response(file.stream().pipeThrough(new DecompressionStream('gzip')));
|
||||
return await response.text();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
|
|
@ -330,15 +331,18 @@ export class Content
|
|||
//Easy to use, but not very performant.
|
||||
private static async write(path: string, content: string, options?: FileSystemGetFileOptions): Promise<void>
|
||||
{
|
||||
const size = new TextEncoder().encode(content).byteLength;
|
||||
try
|
||||
{
|
||||
const parent = path.split('/').slice(0, -1).join('/'), basename = path.split('/').slice(-1).join('/');
|
||||
const handle = await (await Content.goto(parent, { create: true }) ?? Content.root).getFileHandle(basename, options);
|
||||
const file = await handle.createWritable({ keepExistingData: false });
|
||||
|
||||
await file.write(content);
|
||||
await file.close();
|
||||
await new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode(content));
|
||||
controller.close();
|
||||
}
|
||||
}).pipeThrough(new CompressionStream("gzip")).pipeTo(file);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
|
|
@ -373,26 +377,27 @@ export class Content
|
|||
return handlers[overview.type].fromString(content);
|
||||
}
|
||||
|
||||
static render(parent: HTMLElement, path: string): Omit<LocalContent, 'content'> | undefined
|
||||
static async render(parent: HTMLElement, path: string): Promise<Omit<LocalContent, 'content'> | undefined>
|
||||
{
|
||||
parent.appendChild(dom('div', { class: 'flex, flex-1 justify-center items-center' }, [loading('normal')]))
|
||||
|
||||
await Content.ready;
|
||||
|
||||
const overview = Content.getFromPath(path);
|
||||
|
||||
if(!!overview)
|
||||
{
|
||||
const load = dom('div', { class: 'flex, flex-1 justify-center items-center' }, [loading('normal')]);
|
||||
parent.appendChild(load);
|
||||
|
||||
function _render<T extends FileType>(content: LocalContent<T>): void
|
||||
{
|
||||
const el = handlers[content.type].render(content);
|
||||
el && parent.replaceChild(el, load);
|
||||
el && parent.replaceChildren(el);
|
||||
}
|
||||
|
||||
Content.getContent(overview.id).then(content => _render(content!));
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.appendChild(dom('h2', { class: 'flex-1 text-center', text: "Impossible d'afficher le contenu demandé" }));
|
||||
parent.replaceChildren(dom('h2', { class: 'flex-1 text-center', text: "Impossible d'afficher le contenu demandé" }));
|
||||
}
|
||||
|
||||
return overview;
|
||||
|
|
@ -514,7 +519,7 @@ const handlers: { [K in FileType]: ContentTypeHandler<K> } = {
|
|||
//TODO: Edition link
|
||||
]),
|
||||
]),
|
||||
render(content.content),
|
||||
render(content.content, undefined, { class: 'pb-64' }),
|
||||
])
|
||||
},
|
||||
renderEditor: (content) => {
|
||||
|
|
@ -572,7 +577,7 @@ export const iconByType: Record<FileType, string> = {
|
|||
|
||||
export class Editor
|
||||
{
|
||||
tree: TreeDOM;
|
||||
tree!: TreeDOM;
|
||||
container: HTMLDivElement;
|
||||
|
||||
selected?: Recursive<LocalContent & { element?: HTMLElement }>;
|
||||
|
|
@ -692,6 +697,7 @@ export class Editor
|
|||
},
|
||||
}, () => { this.tree.tree.each(e => Content.set(e.id, e)); Content.save(); });
|
||||
|
||||
Content.ready.then(() => {
|
||||
this.tree = new TreeDOM((item, depth) => {
|
||||
return dom('div', { class: 'group flex items-center ps-2 outline-none relative cursor-pointer', style: { 'padding-left': `${depth / 2 - 0.5}em` } }, [dom('div', { class: ['flex flex-1 items-center hover:border-accent-blue hover:text-accent-purple max-w-full cursor-pointer font-medium'], attributes: { 'data-private': item.private }, listeners: { contextmenu: (e) => this.contextmenu(e, item as LocalContent)} }, [
|
||||
icon('radix-icons:chevron-right', { class: 'h-4 w-4 transition-transform absolute group-data-[state=open]:rotate-90', style: { 'left': `${depth / 2 - 1.5}em` } }),
|
||||
|
|
@ -708,13 +714,14 @@ export class Editor
|
|||
])]);
|
||||
});
|
||||
|
||||
this.instruction = dom('div', { class: 'absolute h-full w-full top-0 right-0 border-light-50 dark:border-dark-50' });
|
||||
this.select(this.tree.tree.find(useRouter().currentRoute.value.hash.substring(1)) as Recursive<LocalContent & { element?: HTMLElement }> | undefined);
|
||||
|
||||
this.cleanup = this.setupDnD();
|
||||
});
|
||||
|
||||
this.instruction = dom('div', { class: 'absolute h-full w-full top-0 right-0 border-light-50 dark:border-dark-50' });
|
||||
|
||||
this.container = dom('div', { class: 'flex flex-1 flex-col items-start justify-start max-h-full relative' }, [dom('div', { class: 'py-4 flex-1 w-full max-h-full flex overflow-auto xl:px-12 lg:px-8 px-6 relative' })]);
|
||||
|
||||
this.select(this.tree.tree.find(useRouter().currentRoute.value.hash.substring(1)) as Recursive<LocalContent & { element?: HTMLElement }> | undefined);
|
||||
}
|
||||
private contextmenu(e: MouseEvent, item: Recursive<LocalContent>)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue