mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-26 16:02:36 +00:00
* RunsPage redesign v0 * nit * Remove manualdatepicker * remove shadow * ui nits * nit scrollbar bg * prettier cards * nit * Remove code * command/meta multi select * Shift select * RightClickPopover * nit * Ctrl A * nit card * DropdownMenu * nit * count hint * fix stuck keys * opacity UX * error toasts pickhubscript * Improve UX * fix undefined error * keyboard nav * nit batch rerun fixes * nit fix scroll / height * Batch reruns actions + nits * nit * Cancel selected jobs * Cancel / re-run all filtered jobs * Go to job / flow / script action * nit * add batch actions back * nit * nit * bar on splitpane hover * nit * New Timeframe system * reset btn * nit fixes * dead code * nits * typecheck * naming clarity * Update frontend/src/lib/components/RightClickPopover.svelte Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> * unnecessary json stringify * dedup 'the' * Code deletion to prepare for changes * filter types * ui * fix bug with maxTs * stuck with melt * GenericDropdown * filters onclick * iterate * iter * add all filters * Descriptions * focus position * stash * TaggedTextInput works much much better * placeholder * currentTag suggestion * improve * nit * Keyboard nav * buildRunsFilterSearchbarSchema * nit naming * assignObjInPlace * Escaping + pretty dates * nit empty * fix cursor * nit space * Filter filtering * escape pasted value * nit * escape spaces * nit undefined * add space at end if right arrow * escape all spaces * arrow skips escape chars * escape \ too * delete whole escaped characters * double space to escape tag * code refactor * Ensure cursor visible * fix keyboard nav * safety * filterSchemaRecToZodSchema * URL Sync * fix readonly * fix typing * start replacing old filter logic * use new filter impl * nit * nit reactivity * nit fix * no more localStorage * Add back status and kind toggles * Nit fix * style nit * focus at end on click * clearn btn + fixes * fix broken date uri * nit * useSyncedTimeframe * negative filter button * negative filters helpers rust * Negated filters backed * nit * highlight * New useSearchParams * Accept comma separated list * nit allowNegative * openapi update * Fix trigger kind list/negation not working * nit oipenpai * Presets * DebouncedTempValue * remove presets from list when already applied * UI nit improvements * allowMultiple * hint * validateFilterInstance fn * nit fix * error highlights * nit ux selecting negative list * nit * on clear btn * SimpleEditor for JSON * nit * flop * Pass presets as param * nit delete * preventCursorMoveOnNextSync * responsive layout * Escape \n * Inline calendar input * mm/dd or dd/mm depending on US or not * onClickBehavior * infiniteRange * other nits * Wiring with runs filter * formatDateRange better * inits on right page * style * min hour support * Time input * use our components * Improve SKILL.md * dd mm yyyy numeric input * TimeframeSelect with new date picker * fixes * ensure date is in view when value changes externally * fixes * nit select all on focus * select year + nits * nit layout shift * nit negative when starting with ! * nit * SelectDropdown uses GenericDropdown now * Fix blank select dropdown rendering bug * icons * Reset btn + shorter date range formatting * overflow fix * unnecessary absolute * fix clear btn overlap * Update routes for new filters (assets, schedule, resource, variables) * update openapi * Impl for other pages * ui nits * nit fixes * Fix columns filter * super nits --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
122 lines
3.8 KiB
TypeScript
122 lines
3.8 KiB
TypeScript
// SvelteKit-specific utilities
|
|
// This file should only be imported in SvelteKit apps as it depends on $app/environment
|
|
|
|
import { z } from 'zod'
|
|
|
|
export type SearchParamsResult<S extends z.ZodType> =
|
|
S extends z.ZodObject<infer Shape>
|
|
? { -readonly [K in keyof Shape]: z.infer<Shape[K]> } & Record<string, unknown>
|
|
: z.infer<S> & Record<string, unknown>
|
|
|
|
/** Serialize a value to a URL search param string. Primitives are written as-is; anything else is JSON. */
|
|
function serializeParam(value: unknown): string {
|
|
if (typeof value === 'string') return value
|
|
if (typeof value === 'number') return String(value)
|
|
if (typeof value === 'boolean') return String(value)
|
|
return JSON.stringify(value)
|
|
}
|
|
|
|
/** Parse a raw string from the URL back to a typed value guided by the zod field schema. */
|
|
function deserializeParam(raw: string, fieldSchema: z.ZodType): unknown {
|
|
// Unwrap nullable / optional / default wrappers to get the inner type
|
|
let inner: z.ZodType = fieldSchema
|
|
while (
|
|
inner instanceof (z as any).ZodNullable ||
|
|
inner instanceof (z as any).ZodOptional ||
|
|
inner instanceof (z as any).ZodDefault
|
|
) {
|
|
inner = (inner as any)._def.innerType ?? (inner as any)._def.type
|
|
}
|
|
|
|
if (inner instanceof (z as any).ZodNumber) {
|
|
const n = Number(raw)
|
|
return isNaN(n) ? null : n
|
|
}
|
|
if (inner instanceof (z as any).ZodBoolean) {
|
|
if (raw === 'true') return true
|
|
if (raw === 'false') return false
|
|
return null
|
|
}
|
|
// ZodString, ZodEnum, ZodLiteral, ZodAny, or unknown → try as plain string first,
|
|
// fall back to JSON parse for non-string schemas
|
|
if (
|
|
inner instanceof (z as any).ZodString ||
|
|
inner instanceof (z as any).ZodEnum ||
|
|
inner instanceof (z as any).ZodLiteral
|
|
) {
|
|
return raw
|
|
}
|
|
// For any other type (object, array, …) try JSON
|
|
try {
|
|
return JSON.parse(raw)
|
|
} catch {
|
|
return raw
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a reactive object whose properties are synced to the browser's URL search params.
|
|
* Reading a property returns the current value from the URL (reactive).
|
|
* Writing a property updates the URL via history.replaceState (no navigation).
|
|
*
|
|
* Primitive types (string / number / boolean) are serialized as plain text.
|
|
* Everything else is JSON-serialized.
|
|
*/
|
|
export function useSearchParams<S extends z.ZodType>(schema: S): SearchParamsResult<S> {
|
|
const shape: Record<string, z.ZodType> = (schema as any).shape ?? {}
|
|
const keys = Object.keys(shape)
|
|
|
|
// Reactive snapshot of search params - one $state cell per key
|
|
const values: Record<string, unknown> = $state(
|
|
Object.fromEntries(
|
|
keys.map((k) => {
|
|
const raw = new URLSearchParams(window.location.search).get(k)
|
|
const parsed = raw != null ? deserializeParam(raw, shape[k]) : null
|
|
return [k, parsed]
|
|
})
|
|
)
|
|
)
|
|
|
|
function syncFromUrl() {
|
|
const sp = new URLSearchParams(window.location.search)
|
|
for (const k of keys) {
|
|
const raw = sp.get(k)
|
|
const parsed = raw != null ? deserializeParam(raw, shape[k]) : null
|
|
;(values as any)[k] = parsed
|
|
}
|
|
}
|
|
|
|
// Keep in sync when the user navigates back/forward
|
|
$effect(() => {
|
|
window.addEventListener('popstate', syncFromUrl)
|
|
return () => window.removeEventListener('popstate', syncFromUrl)
|
|
})
|
|
|
|
// Build the proxy object: reads come from $state, writes go to the URL + $state
|
|
const proxy: Record<string, unknown> = {}
|
|
for (const key of keys) {
|
|
Object.defineProperty(proxy, key, {
|
|
get() {
|
|
return (values as any)[key]
|
|
},
|
|
set(v: unknown) {
|
|
;(values as any)[key] = v
|
|
const sp = new URLSearchParams(window.location.search)
|
|
if (v == null) {
|
|
sp.delete(key)
|
|
} else {
|
|
sp.set(key, serializeParam(v))
|
|
}
|
|
const newUrl = sp.toString()
|
|
? `${window.location.pathname}?${sp}`
|
|
: window.location.pathname
|
|
history.replaceState(history.state, '', newUrl)
|
|
},
|
|
enumerable: true,
|
|
configurable: true
|
|
})
|
|
}
|
|
|
|
return proxy as SearchParamsResult<S>
|
|
}
|