diff --git a/index.html b/index.html index 94e7757..738a346 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@ - +

Astro Decap Collection Demo

@@ -66,8 +66,12 @@

Astro Decap Collection Demo

>
diff --git a/src/demo/demo.ts b/src/demo/demo.ts index d77b5a1..de34199 100644 --- a/src/demo/demo.ts +++ b/src/demo/demo.ts @@ -7,16 +7,26 @@ import zod from 'zod'; import { parseConfig } from '../utils/decap.utils.js'; import { formatCode } from '../utils/format.utils.js'; import { transformCollection } from '../utils/transform.utils.js'; +import { compress } from './utils/compress.utils.js'; +import { decompress } from './utils/decompress.utils.js'; declare global { interface Window { global: Window; - loadExample(path: string): void; + handleLoad(event: Event): void; + handleClick(event: PointerEvent): void; handleInput(event: InputEvent): Promise; handleScroll(event: Event): void; + + loadExample(path: string): void; + updateExample(data: string): void; updateInput(from: InputEvent): string; + updatePreview(config: string, schemas: Record): void; clearPreview(): void; + + reflectToUrl(config: string, param?: string): Promise; + initFromUrl(param?: string): Promise; } } @@ -28,12 +38,13 @@ hljs.registerLanguage('yaml', yaml); // prevent errors from libs using ancient `global` instead of `globalThis` window.global ||= window; -// loads an example from the `examples` folder -window.loadExample = async (path: string) => { - const input = document.querySelector('textarea')!; - const example = await fetch(path); - input.value = await example.text(); - input.dispatchEvent(new InputEvent('input')); +window.handleLoad = () => window.initFromUrl(); + +window.handleClick = event => { + event.preventDefault(); + const { dataset } = event.target as HTMLElement; + if (!dataset.example) return; + window.loadExample(dataset.example); }; // handle textarea input event @@ -62,10 +73,26 @@ window.updateInput = event => { preview.innerHTML = hljs.highlight(input.value, { language: 'yaml' }).value; preview.style.height = `${input.scrollHeight}px`; preview.style.width = `${input.scrollWidth}px`; + window.handleScroll(event); + window.reflectToUrl(input.value); + return input.value; }; +// loads an example from the `examples` folder +window.loadExample = async (path: string) => { + const example = await fetch(path); + const config = await example.text(); + window.updateExample(config); +}; + +window.updateExample = data => { + const input = document.querySelector('textarea')!; + input.value = data; + input.dispatchEvent(new InputEvent('input')); +}; + // update the preview code with the config and schemas window.updatePreview = (config, schemas) => { const configPreview = document.querySelector('#config code')!; @@ -90,3 +117,24 @@ window.clearPreview = () => { const schemaPreview = document.querySelector('#schemas pre')!; schemaPreview.innerHTML = '
'; }; + +// stores the given string gzip compressed in the URL +window.reflectToUrl = async (config, param = 'c') => { + const url = new URL(location.href); + if (config.trim() === '') { + url.searchParams.delete(param); + } else { + const compressed = await compress(config, 'gzip'); + url.searchParams.set(param, compressed); + } + history.replaceState({}, '', url.toString()); +}; + +window.initFromUrl = async (param = 'c') => { + const url = new URL(location.href); + const compressed = url.searchParams.get(param); + if (!compressed) return; + + const config = await decompress(compressed, 'gzip'); + window.updateExample(config); +}; diff --git a/src/demo/utils/compress.utils.ts b/src/demo/utils/compress.utils.ts new file mode 100644 index 0000000..549375e --- /dev/null +++ b/src/demo/utils/compress.utils.ts @@ -0,0 +1,28 @@ +/** + * Compress a string with browser native APIs into a binary string representation + * + * @param data - Input string that should be compressed + * @param encoding - Compression algorithm to use + * @returns The compressed binary string + */ +export async function compressRaw(data: string, encoding: CompressionFormat): Promise { + // stream the string through the compressor + const stream = new Blob([new TextEncoder().encode(data)]) + .stream() + .pipeThrough(new CompressionStream(encoding)); + // convert the stream to an array buffer + const buffer = await new Response(stream).arrayBuffer(); + // convert the array buffer to a binary string + return Array.from(new Uint8Array(buffer), x => String.fromCodePoint(x)).join(''); +} + +/** + * Compress a string with browser native APIs into a string representation + * + * @param data - Input string that should be compressed + * @param encoding - Compression algorithm to use + * @returns The compressed string + */ +export async function compress(data: string, encoding: CompressionFormat): Promise { + return btoa(await compressRaw(data, encoding)); +} diff --git a/src/demo/utils/decompress.utils.ts b/src/demo/utils/decompress.utils.ts new file mode 100644 index 0000000..36ab12f --- /dev/null +++ b/src/demo/utils/decompress.utils.ts @@ -0,0 +1,26 @@ +/** + * Decompress a binary string representation with browser native APIs in to a normal js string + * + * @param binary - Binary string that should be decompressed, e.g. the output from `atob` + * @param encoding - Decompression algorithm to use + * @returns The decompressed string + */ +export async function decompressRaw(binary: string, encoding: CompressionFormat): Promise { + // stream the string through the decompressor + const stream = new Blob([Uint8Array.from(binary, m => m.codePointAt(0)!)]) + .stream() + .pipeThrough(new DecompressionStream(encoding)); + // convert the stream to a string + return new Response(stream).text(); +} + +/** + * Decompress a string representation with browser native APIs in to a normal js string + * + * @param data - String that should be decompressed + * @param encoding - Decompression algorithm to use + * @returns The decompressed string + */ +export async function decompress(data: string, encoding: CompressionFormat): Promise { + return decompressRaw(atob(data), encoding); +}