From 8297163039606a752b5884a07583d071983f1791 Mon Sep 17 00:00:00 2001 From: Bart Veneman Date: Wed, 24 Jun 2026 11:05:01 +0200 Subject: [PATCH] WIP: always store CSS + settings in URL hash for easy sharing --- src/lib/components/Hero.svelte | 4 +- src/lib/components/css-form/Form.svelte | 66 +++++++++---- .../components/css-form/ShareButton.svelte | 13 +++ src/lib/components/css-form/types.ts | 6 ++ src/lib/is-online.svelte.ts | 4 +- src/lib/url-hash-state.svelte.ts | 99 +++++++++++++------ 6 files changed, 140 insertions(+), 52 deletions(-) create mode 100644 src/lib/components/css-form/ShareButton.svelte diff --git a/src/lib/components/Hero.svelte b/src/lib/components/Hero.svelte index 9312394b..64ba86b8 100644 --- a/src/lib/components/Hero.svelte +++ b/src/lib/components/Hero.svelte @@ -28,8 +28,8 @@ border-block-end: var(--space-px) solid var(--bg-300); margin-block-end: var(--space-8); background-image: - radial-gradient(circle at top right, rgb(from var(--accent-400) r g b / 0.07) 0%, transparent 90%), - radial-gradient(circle at top left, rgb(from var(--purple-300) r g b / 0.1) 0%, transparent 70%); + radial-gradient(circle at top right, rgb(from var(--accent-400) r g b / 0.07) 0%, transparent 84%), + radial-gradient(circle at top left, rgb(from var(--purple-300) r g b / 0.1) 0%, transparent 65%); @media (min-height: 33rem) { padding-block-start: var(--space-16); diff --git a/src/lib/components/css-form/Form.svelte b/src/lib/components/css-form/Form.svelte index 16918c28..de2afe68 100644 --- a/src/lib/components/css-form/Form.svelte +++ b/src/lib/components/css-form/Form.svelte @@ -2,7 +2,8 @@ import { page } from '$app/state' import { goto } from '$app/navigation' import { browser } from '$app/environment' - import type { FormSuccessEvent } from './types' + import { onMount } from 'svelte' + import type { FormSuccessEvent, CssFormHashState } from './types' import FormGroup from '$components/FormGroup.svelte' import Label from '$components/Label.svelte' import Button from '$components/Button.svelte' @@ -11,8 +12,10 @@ import Textarea from './Textarea.svelte' import UrlInput from './UrlInput.svelte' import FileInput from './FileInput.svelte' + import ShareButton from './ShareButton.svelte' import { get_css, type CssFetchNetworkError, type CssFetchApiError, type CssFetchRemoteError } from '$lib/get-css' import { get_css_state } from '$lib/css-state.svelte' + import { decodeHashState, encodeHashState } from '$lib/url-hash-state.svelte' import { IsOnline } from '$lib/is-online.svelte' import type { Snippet } from 'svelte' @@ -26,7 +29,7 @@ let { on_success = noop, on_error = noop, title: title_snippet }: Props = $props() - let status: 'idle' | 'fetching' | 'error' = $state('idle') + let status: 'idle' | 'fetching' | 'done' | 'error' = $state('idle') let error: Error | undefined = $state() let url = $state('') let css_state = get_css_state() @@ -35,6 +38,22 @@ ) let is_online = new IsOnline() + onMount(async () => { + if (!window.location.hash) return + status = 'fetching' + const state = await decodeHashState(window.location.hash) + if (!state?.origins?.length) { + status = 'idle' + return + } + + css_state.set_origins(state.origins) + css_state.prettify(state.prettify) + prettify = state.prettify + status = 'done' + on_success({ origins: state.origins, prettify: state.prettify, submit_type: state.submit_type }) + }) + $effect(() => { css_state.prettify(prettify) }) @@ -45,6 +64,8 @@ let input_val = form_data.get('raw-css') let val = String(input_val) + history.replaceState(null, '', window.location.pathname + window.location.search) + // Remove ?url= and prettify= query parameters from the URL let cleaned_url = page.url cleaned_url.searchParams.delete('url') @@ -55,13 +76,14 @@ status = 'idle' prettify = form_data.get('prettify') === '1' - on_success({ - origins: [{ css: val, type: 'raw' }], - submit_type: 'raw', - prettify - }) - css_state.set_origins([{ css: val, type: 'raw' }]) + status = 'done' + const raw_origins = [{ css: val, type: 'raw' as const }] + on_success({ origins: raw_origins, submit_type: 'raw', prettify }) + css_state.set_origins(raw_origins) css_state.url = undefined + encodeHashState({ submit_type: 'raw', origins: raw_origins, prettify }).then( + (encoded) => { window.location.hash = encoded } + ) } async function on_submit_url(event: SubmitEvent) { @@ -69,6 +91,8 @@ if (status === 'fetching') return status = 'fetching' + history.replaceState(null, '', window.location.pathname + window.location.search) + let form_data = new FormData(event.target as HTMLFormElement) let url = String(form_data.get('url')) if (!url) return @@ -85,14 +109,14 @@ status = 'idle' prettify = form_data.get('prettify') === '1' + status = 'done' css_state.prettify(prettify) css_state.set_origins(origins) css_state.url = url - on_success({ - origins, - submit_type: 'url', - prettify - }) + on_success({ origins, submit_type: 'url', prettify }) + encodeHashState({ submit_type: 'url', origins, prettify }).then( + (encoded) => { window.location.hash = encoded } + ) } catch (err: unknown) { status = 'error' error = err as CssFetchNetworkError | CssFetchApiError | CssFetchRemoteError @@ -115,6 +139,8 @@ // fail silently } + history.replaceState(null, '', window.location.pathname + window.location.search) + // Remove ?url= and prettify= query parameters from the URL let cleaned_url = page.url cleaned_url.searchParams.delete('url') @@ -125,13 +151,13 @@ status = 'idle' prettify = form_data.get('prettify') === '1' - on_success({ - origins, - submit_type: 'file', - prettify - }) + status = 'done' + on_success({ origins, submit_type: 'file', prettify }) css_state.set_origins(origins) css_state.url = undefined + encodeHashState({ submit_type: 'file', origins, prettify }).then( + (encoded) => { window.location.hash = encoded } + ) } $effect(() => { @@ -240,6 +266,10 @@ {/snippet} +{#if status === 'done'} + +{/if} +