feat(immich): draw a server as a folder in the library

A saved Immich server is a node on the column beside the drives: no handle,
no picker — the key lives on the backend and the page only ever calls
`/api/immich/*` on its own origin. The dialog takes the address, the name and
the key, checks the key before storing anything, and offers the albums the key
can see; a blank tick list and a blank id field both mean every album. A share
key is one album and so has no picker.

`syncImmich` turns a server's albums into the shapes the tree already draws —
rows in `photos`, names in `walked` — so the column, the wall and the strip
need nothing new. The node's menu is the disk's menu minus what a server has
no disk for.

Also fixes `saveWalkedDirs` filling its noughts in before folding the keys:
a reading that spelled its keys the way it found them (a server's album
labels) had the lower-case spelling of a counted key written in as nought,
and the fold that settles the two spellings kept the nought.
This commit is contained in:
2026-10-10 16:16:37 +07:00
parent eacb3b7341
commit 1d6a869094
9 changed files with 1529 additions and 60 deletions
+23
View File
@@ -1042,6 +1042,29 @@ button.hint:disabled { cursor: default; }
gap: 10px;
}
.modal h2 { margin: 0; font-size: 17px; }
/* The dialog that adds an Immich server (src/ui/ImmichDialog.tsx): fields, a
key that is checked, and the albums a key can see — a list that can run to
hundreds, so it scrolls inside a box rather than pushing the buttons off the
screen. The form itself is capped for the same reason. */
form[data-key='immich-modal'] {
max-height: calc(100vh - 32px);
overflow-y: auto;
}
.immich-albums {
list-style: none;
margin: 0;
padding: 0;
max-height: 40vh;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 4px;
}
.immich-albums label {
display: flex;
align-items: center;
gap: 6px;
}
/* The one row in the export menu that is not a plain button: a number to type
and the button that takes it. */
.export-custom {