diff --git a/docker/frontend/src/Library.tsx b/docker/frontend/src/Library.tsx index 124280b..9bc22b4 100644 --- a/docker/frontend/src/Library.tsx +++ b/docker/frontend/src/Library.tsx @@ -394,7 +394,7 @@ function Catalogue({ admin, user }: { admin: boolean; user: User | null }) { const [servers, setServers] = useState([]); const [serverYears, setServerYears] = useState([]); const [addImmich, setAddImmich] = useState(false); - const [editAlbums, setEditAlbums] = useState(null); + const [editServer, setEditServer] = useState(null); useEffect(() => { if (!servers.length) return; @@ -1371,24 +1371,28 @@ function Catalogue({ admin, user }: { admin: boolean; user: User | null }) { [folders, reload] ); - // A server that has just been saved, or one whose albums have been reticked. A - // new server becomes a folder on the column the moment it is saved: nothing - // walks it, so there is nothing to wait for. An existing node keeps its name and - // its frames, which the sync below reads again for the albums it now keeps. + // A server that has just been saved, or one whose name, address or albums have + // just been changed. A new server becomes a folder on the column the moment it is + // saved: nothing walks it, so there is nothing to wait for. An existing one keeps + // its frames, and every node reading by it is walked again for the albums it now + // keeps — which is why the folder's own copy of that list has to be written before + // the sync reads it. const serverSaved = useCallback( async (saved: ImmichServer, isNew: boolean) => { setAddImmich(false); - const edited = editAlbums; - setEditAlbums(null); + setEditServer(null); // The saved list is read back rather than patched: the backend is what // holds the key, and what it says is what the dialog will show next time. const config = await immichConfig(true); setServers(config.servers); - if (!isNew && edited) { - const next: LibraryFolder = { ...edited, albums: saved.albums }; - await putFolder(next); - setFolders((prev) => prev.map((f) => (f.name === next.name ? next : f))); - await syncServer(next); + if (!isNew) { + for (const f of await listFolders()) { + if (f.server !== saved.id) continue; + const next: LibraryFolder = { ...f, albums: saved.albums }; + await putFolder(next); + await syncServer(next); + } + setFolders(await listFolders()); return; } const current = await listFolders(); @@ -1398,7 +1402,7 @@ function Catalogue({ admin, user }: { admin: boolean; user: User | null }) { setNode(folder.name); await syncServer(folder); }, - [editAlbums, syncServer] + [syncServer] ); // The shelf's search box, run. What is typed is a name or a tag, and Immich is @@ -2509,7 +2513,7 @@ function Catalogue({ admin, user }: { admin: boolean; user: User | null }) { admin={admin} onOpenStudio={open} onAddImmich={() => setAddImmich(true)} - onEditAlbums={(folder) => setEditAlbums(folder)} + onEditAlbums={(folder) => setEditServer(folder.server ?? null)} onSyncServer={syncServer} onDisconnectServer={disconnect} onFindImmich={findImmich} @@ -2528,16 +2532,26 @@ function Catalogue({ admin, user }: { admin: boolean; user: User | null }) { {addImmich ? ( { + setAddImmich(false); + setEditServer(id); + }} onForget={(id) => void forgetLink(id)} onSaved={(saved, isNew) => void serverSaved(saved, isNew)} onClose={() => setAddImmich(false)} /> ) : null} - {editAlbums ? ( + {editServer ? ( s.id === editAlbums.server) ?? null} + server={servers.find((s) => s.id === editServer) ?? null} + servers={servers} + onEdit={setEditServer} + onForget={(id) => { + setEditServer(null); + void forgetLink(id); + }} onSaved={(saved, isNew) => void serverSaved(saved, isNew)} - onClose={() => setEditAlbums(null)} + onClose={() => setEditServer(null)} /> ) : null} @@ -2599,7 +2613,7 @@ function Catalogue({ admin, user }: { admin: boolean; user: User | null }) { data-key="lib-add-immich" onClick={() => setAddImmich(true)} > - {t('lib.immich.add')} + {servers.length ? t('lib.cloud.manage') : t('lib.immich.add')} {live || scanQueue().length ? ( + + + ); + })} + + + ) : null} + + {/* The three answers are the same at the door and behind it: a server being + added and one being changed are the same name, address and key. The one + difference is the key — a saved server already has one, so what is typed + here is the *new* one, and blank means the question was not asked. */} + + setName(e.target.value)} + /> + + setUrl(e.target.value)} + /> + + setKey(e.target.value)} + /> + + {/* Checking a key is the door's own question — a saved server has already + answered it, and the save below probes again anyway. */} + {editing ? null : ( <> - {/* What the account already reads by: one row per saved address, and - the address is what makes two rows one server. Two links to the - same Immich read the same library twice, so the row says so and - the way out is here rather than in a node's own menu — a duplicate - is a fact about the list, not about a folder. */} - {servers.length ? ( -
-

{t('lib.immich.links')}

-
    - {servers.map((s) => { - const twin = servers.some((o) => o.id !== s.id && o.url === s.url); - return ( -
  • - {s.name} · {s.url} ({s.albums?.length ?? 0}) - {twin ? {t('lib.immich.dup')} : null} - -
  • - ); - })} -
-
- ) : null} - - setName(e.target.value)} - /> - - setUrl(e.target.value)} - /> - - setKey(e.target.value)} - />