diff --git a/src/lib/renderer/index.ts b/src/lib/renderer/index.ts new file mode 100644 index 00000000..521b0aaa --- /dev/null +++ b/src/lib/renderer/index.ts @@ -0,0 +1,95 @@ +import { browser, building, dev } from '$app/environment'; +import { env } from '$env/dynamic/public'; + +type RendererExports = { + memory: WebAssembly.Memory; + OUTPUT: WebAssembly.Global<'i32'>; + drop_output(): void; + wasm_alloc(size: number, align: number): number; + wasm_dealloc(ptr: number, size: number, align: number): void; + wasm_render( + profile_data_json_ptr: number, + profile_data_json_len: number, + theme_data_ptr: number, + theme_data_len: number + ): [number, number]; +}; + +let wasm: RendererExports; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); + +const wasmImports = { + console: { + error(ptr: number, len: number) { + console.error(decoder.decode(wasm.memory.buffer.slice(ptr, ptr + len))); + } + } +}; +if (!building) { + if (browser) { + const resp = fetch('/renderers/minijinja.wasm'); + wasm = (await WebAssembly.instantiateStreaming(resp, wasmImports)).instance + .exports as RendererExports; + } else { + const filePath = dev + ? './static/renderers/minijinja.wasm' + : './client/renderers/minijinja.wasm'; + const wasmData = await (await import('fs')).promises.readFile(filePath); + wasm = (await WebAssembly.instantiate(wasmData, wasmImports)).instance + .exports as RendererExports; + } +} + +const getOutputPtrLen = () => { + const wasmDataView = new DataView( + wasm.memory.buffer.slice(wasm.OUTPUT.value, wasm.OUTPUT.value + 8) + ); + return [wasmDataView.getInt32(0, true), wasmDataView.getInt32(4, true)]; +}; + +export type ProfileData = { + instance_info: { + url: string; + }; + handle: string; + display_name?: string; + bio?: string; + tags?: string[]; + links?: { url: string; label?: string }[]; + pages?: { slug: string; name?: string }[]; +}; + +export function render( + profileData: Omit, + themeData: Uint8Array +): string { + const profileDataJson = JSON.stringify({ + ...profileData, + ...{ instance_info: { url: env.PUBLIC_URL } } + } as ProfileData); + const profileDataJsonBinary = encoder.encode(profileDataJson); + const profileDataJsonBinaryLen = profileDataJsonBinary.length; + const profileDataJsonPtr = wasm.wasm_alloc(profileDataJsonBinaryLen, 1); + + const themeDataLen = themeData.length; + const themeDataPtr = wasm.wasm_alloc(themeDataLen, 1); + + const view = new Uint8Array(wasm.memory.buffer); + view.set(profileDataJsonBinary, profileDataJsonPtr); + view.set(themeData, themeDataPtr); + + wasm.wasm_render(profileDataJsonPtr, profileDataJsonBinaryLen, themeDataPtr, themeDataLen); + const [renderedPtr, renderedLen] = getOutputPtrLen(); + + const renderedData = wasm.memory.buffer.slice(renderedPtr, renderedPtr + renderedLen); + const renderedString = decoder.decode(renderedData); + + wasm.drop_output(); + + wasm.wasm_dealloc(themeDataPtr, themeDataLen, 1); + wasm.wasm_dealloc(profileDataJsonPtr, profileDataJsonBinaryLen, 1); + + return renderedString; +} diff --git a/src/lib/themes/weird.html.j2 b/src/lib/themes/weird.html.j2 new file mode 100644 index 00000000..4801bf3c --- /dev/null +++ b/src/lib/themes/weird.html.j2 @@ -0,0 +1,3053 @@ + + + + + + + + + + + + + + + +
+ +
+
+
+ {{display_name}} avatar +
+ +

{{display_name}}

+ + {{bio | markdown}} + + {% if tags %} +

Tags

+
+ {% for tag in tags %} + + {{tag}} + + {% endfor %} +
+ {% endif %} +
+ + + + + {% if pages %} + + {% endif %} +
+ + + diff --git a/src/routes/(app)/[username]/components/ChangeHandleModal.svelte b/src/routes/(app)/[username]/components/ChangeHandleModal.svelte index 603d8cb8..84285a81 100644 --- a/src/routes/(app)/[username]/components/ChangeHandleModal.svelte +++ b/src/routes/(app)/[username]/components/ChangeHandleModal.svelte @@ -292,10 +292,13 @@ {/if} {:else} -
-

You can use your own domain as your Weird handle by configuring your DNS records.

-

Enter your domain for specific instructions.

-
+
+

+ You can use your own domain as your Weird handle by configuring your DNS + records. +

+

Enter your domain for specific instructions.

+
{/if} {/if} diff --git a/src/routes/(subsites)/subsite/[username]/+page.server.ts b/src/routes/(subsites)/subsite/[username]/+page.server.ts deleted file mode 100644 index f55501c7..00000000 --- a/src/routes/(subsites)/subsite/[username]/+page.server.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getProfileByUsername, listChildren, type Profile } from '$lib/leaf/profile'; -import { error } from '@sveltejs/kit'; -import { usernames } from '$lib/usernames'; -import { leafClient, subspace_link } from '$lib/leaf'; -import { Name } from 'leaf-proto/components'; - -export const load: PageServerLoad = async ({ - params -}): Promise<{ - profile: Profile; - pages: { slug: string; name?: string }[]; - params: typeof params; -}> => { - const subspace = await usernames.getSubspace(params.username); - if (!subspace) return error(404, `User not found: ${params.username}`); - - let profile = await getProfileByUsername(params.username); - if (!profile) return error(404, `User profile not found: ${params.username}`); - - const pageSlugs = await listChildren(subspace_link(subspace)); - const pages = ( - await Promise.all( - pageSlugs.map(async (slug) => { - const link = subspace_link(subspace, slug); - const ent = await leafClient.get_components(link, Name); - if (!ent) return undefined; - return { slug, name: ent.get(Name)?.value }; - }) - ) - ).filter((x) => x) as { slug: string; name?: string }[]; - - return { profile, pages, params: { ...params } }; -}; diff --git a/src/routes/(subsites)/subsite/[username]/+page.svelte b/src/routes/(subsites)/subsite/[username]/+page.svelte deleted file mode 100644 index 441100d9..00000000 --- a/src/routes/(subsites)/subsite/[username]/+page.svelte +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - - {display_name} - - - -{#if theme === 'minimal'} - -{:else if theme === 'retro'} - -{:else if theme === 'weird'} - -{/if} - -{#if unsavedChanges} -
-

You have unsaved changes.

- -
-{/if} -{#snippet footer()} - -{/snippet} - - diff --git a/src/routes/(subsites)/subsite/[username]/+server.ts b/src/routes/(subsites)/subsite/[username]/+server.ts new file mode 100644 index 00000000..64f01a8f --- /dev/null +++ b/src/routes/(subsites)/subsite/[username]/+server.ts @@ -0,0 +1,41 @@ +import { error, type RequestHandler } from '@sveltejs/kit'; +import { render } from '$lib/renderer'; +import weirdTheme from '$lib/themes/weird.html.j2?raw'; +import { usernames } from '$lib/usernames'; +import { getProfileByUsername, listChildren } from '$lib/leaf/profile'; +import { leafClient, subspace_link } from '$lib/leaf'; +import { Name } from 'leaf-proto/components'; + +export const GET: RequestHandler = async ({ params }) => { + const subspace = await usernames.getSubspace(params.username!); + if (!subspace) return error(404, `User not found: ${params.username}`); + + let profile = await getProfileByUsername(params.username!); + if (!profile) return error(404, `User profile not found: ${params.username}`); + + const pageSlugs = await listChildren(subspace_link(subspace)); + const pages = ( + await Promise.all( + pageSlugs.map(async (slug) => { + const link = subspace_link(subspace, slug); + const ent = await leafClient.get_components(link, Name); + if (!ent) return undefined; + return { slug, name: ent.get(Name)?.value }; + }) + ) + ).filter((x) => x) as { slug: string; name?: string }[]; + + const output = render( + { + handle: params.username!, + bio: profile.bio, + display_name: profile.display_name, + tags: profile.tags, + links: profile.links, + pages + }, + new TextEncoder().encode(weirdTheme) + ); + + return new Response(output, { headers: [['content-type', 'text/html']] }); +}; diff --git a/static/renderers/minijinja.wasm b/static/renderers/minijinja.wasm new file mode 100755 index 00000000..73f60634 Binary files /dev/null and b/static/renderers/minijinja.wasm differ diff --git a/weird.Dockerfile b/weird.Dockerfile index ed523292..9d53115b 100644 --- a/weird.Dockerfile +++ b/weird.Dockerfile @@ -17,4 +17,5 @@ COPY --from=build /build /project RUN adduser -D weird RUN chown -R weird:weird /project USER weird -CMD ["node", "/project"] +WORKDIR /project +CMD ["node", "."]