fix(web): drop indexer debug log, harden settings dialog, escape search-placeholder quotes
Three small cleanups bundled:
- Remove the `console.debug('[indexer]', ...)` line in the indexer
store. The PhotoPrism WS protocol is now verified; the log was a
development aid that no longer earns its console noise.
- GeneralSettingsDialog: normalize cloned PpSettings so `ui` / `search`
/ `maps` are always real objects (some deployments return them
unset), and re-clone the draft on each open instead of nulling it on
close. The previous lifecycle let Dialog's exit animation keep the
form mounted while `draft` was already null, which threw at runtime
via the `bind:value={draft.ui!.theme}` getters.
- Search-input placeholder string: rewrite as a JS expression so the
embedded `"vacation"` quotes inside the example don't terminate the
HTML attribute early. The previous form was a Svelte parse error
that stopped the dev-server module from loading.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -90,21 +90,43 @@
|
|||||||
enabled: open
|
enabled: open
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some PhotoPrism deployments return `/settings` without the
|
||||||
|
* `ui` / `search` / `maps` keys (older versions, custom edits to
|
||||||
|
* settings.yml). The form's `bind:value={draft.ui!.theme}` etc.
|
||||||
|
* non-null-asserts those sub-objects — when they're missing the
|
||||||
|
* assertion lies and the bind getter throws on the next tick. Force
|
||||||
|
* the shape on every clone so every binding has a real object to
|
||||||
|
* write into, and so `draft.ui` is never null while `draft` is non-
|
||||||
|
* null (template gates only check `draft`).
|
||||||
|
*/
|
||||||
|
function normalize(s: PpSettings): PpSettings {
|
||||||
|
return {
|
||||||
|
...s,
|
||||||
|
ui: s.ui ?? {},
|
||||||
|
search: s.search ?? {},
|
||||||
|
maps: s.maps ?? {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let draft = $state<PpSettings | null>(null);
|
let draft = $state<PpSettings | null>(null);
|
||||||
|
// Re-clone on each open so reopening the dialog shows the freshest
|
||||||
|
// server state. Eagerly nulling on close used to introduce a window
|
||||||
|
// where Dialog's exit animation kept the form mounted while draft
|
||||||
|
// was already null — and bind:value getters read null, triggering
|
||||||
|
// "$.get(...) is null" / can't access .ui at runtime. Resetting on
|
||||||
|
// open instead avoids that race entirely.
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (settingsQuery.data && draft === null) {
|
if (open && settingsQuery.data) {
|
||||||
draft = structuredClone(settingsQuery.data);
|
draft = normalize(structuredClone(settingsQuery.data));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$effect(() => {
|
|
||||||
if (!open) draft = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const saveMut = createMutation(() => ({
|
const saveMut = createMutation(() => ({
|
||||||
mutationFn: (patch: PpSettings) => saveSettings(patch),
|
mutationFn: (patch: PpSettings) => saveSettings(patch),
|
||||||
onSuccess: (next) => {
|
onSuccess: (next) => {
|
||||||
qc.setQueryData(['settings'], next);
|
qc.setQueryData(['settings'], next);
|
||||||
draft = structuredClone(next);
|
draft = normalize(structuredClone(next));
|
||||||
toast.success('Settings saved');
|
toast.success('Settings saved');
|
||||||
},
|
},
|
||||||
onError: (err) =>
|
onError: (err) =>
|
||||||
@@ -112,7 +134,7 @@
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
function resetDraft() {
|
function resetDraft() {
|
||||||
if (settingsQuery.data) draft = structuredClone(settingsQuery.data);
|
if (settingsQuery.data) draft = normalize(structuredClone(settingsQuery.data));
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectClass =
|
const selectClass =
|
||||||
|
|||||||
@@ -126,11 +126,6 @@ function handleMessage(raw: string): void {
|
|||||||
const eventName = inner.event as string | undefined;
|
const eventName = inner.event as string | undefined;
|
||||||
const data = (inner.data ?? {}) as Record<string, unknown>;
|
const data = (inner.data ?? {}) as Record<string, unknown>;
|
||||||
if (!eventName) return;
|
if (!eventName) return;
|
||||||
// PhotoPrism's WS protocol isn't a stable contract; log the live shape
|
|
||||||
// at `debug` (hidden by default in DevTools — toggle "Verbose" to see)
|
|
||||||
// so future-us can spot new indexer event names without instrumenting
|
|
||||||
// the entire app.
|
|
||||||
console.debug('[indexer]', eventName, data);
|
|
||||||
switch (eventName) {
|
switch (eventName) {
|
||||||
case 'index.indexing': {
|
case 'index.indexing': {
|
||||||
// Per-file event during the scan pass. PhotoPrism emits one
|
// Per-file event during the scan pass. PhotoPrism emits one
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user