fix(library): keep the shell the deploy replaced out of a failed navigation, and say when another tab holds the library open

This commit is contained in:
2026-10-08 09:23:23 +07:00
parent a3bb412070
commit cda08c3908
5 changed files with 62 additions and 7 deletions
+16
View File
@@ -20,6 +20,7 @@ import {
listEditedIds,
listFolders,
normPath,
onLibraryBlocked,
pickFolder,
readPhotoFile,
readPhotos,
@@ -376,6 +377,15 @@ function Catalogue() {
// the folders and cannot tell whether the remembered subfolder still exists.
const [loaded, setLoaded] = useState(false);
// Whether another tab still holds the library database at the version it was
// opened with — a tab left open across a build. Nothing can be read until it
// goes, and the screen would otherwise sit blank with no reason on it.
const [libraryBlocked, setLibraryBlocked] = useState(false);
useEffect(() => {
onLibraryBlocked(setLibraryBlocked);
return () => onLibraryBlocked(null);
}, []);
// The folder the catalogue is copied into, and whether the browser is still
// letting this page write to it — which it stops doing when the tab closes, so
// the row below says so rather than letting a write fail quietly. The ref is
@@ -1614,6 +1624,12 @@ function Catalogue() {
) : null}
</div>
{libraryBlocked ? (
<p className="hint adm-note" data-key="lib-blocked">
{t('lib.blocked')}
</p>
) : null}
{/* The folders are on their way in for as long as the catalogue behind
them is being read back, and a screen that says there is no folder
before it has looked is a screen that says it every visit. */}
+13
View File
@@ -213,9 +213,21 @@ async function migrateLegacyDatabase(db: IDBDatabase): Promise<void> {
let dbPromise: Promise<IDBDatabase> | null = null;
// One library database per browser, so every tab shares it — and a tab left open
// across a build holds it at the version it was opened with. The browser answers
// the newer tab's open with silence until that tab goes: no error, no rows, no
// note, a LIBRARY that has simply not loaded and never says why. The wait is
// reported instead, and it ends by itself when the other tab is closed or reloaded.
let blockedSink: ((blocked: boolean) => void) | null = null;
export function onLibraryBlocked(fn: ((blocked: boolean) => void) | null): void {
blockedSink = fn;
}
function openDb(): Promise<IDBDatabase> {
dbPromise ??= new Promise<IDBDatabase>((resolve, reject) => {
const req = indexedDB.open(DB_NAME, DB_VERSION);
req.onblocked = () => blockedSink?.(true);
req.onupgradeneeded = () => {
const db = req.result;
if (!db.objectStoreNames.contains(FOLDERS)) db.createObjectStore(FOLDERS, { keyPath: 'name' });
@@ -230,6 +242,7 @@ function openDb(): Promise<IDBDatabase> {
if (!db.objectStoreNames.contains(EDITS)) db.createObjectStore(EDITS, { keyPath: 'photoId' });
};
req.onsuccess = () => {
blockedSink?.(false);
const db = req.result;
void migrateLegacyDatabase(db).then(() => resolve(db));
};
+2
View File
@@ -296,6 +296,8 @@ export const en: Dict = {
'lib.scanned': 'Scanned {folder}: {added}/{total} new frames.',
'lib.empty': 'Nothing in this folder yet.',
'lib.noFolders': 'No folder yet. Hit ADD FOLDER and pick a photo folder on this machine.',
'lib.blocked':
'Another tab of this app is holding the library open from an older build. Close that tab, or reload it, and this one picks the library up.',
'lib.missing': 'Could not open {name} — the file has been moved or deleted.',
'lib.failed': 'That folder could not be read.',
'lib.unsupported':
+2
View File
@@ -309,6 +309,8 @@ export const vi = {
'lib.scanned': 'Đã quét {folder}: {added}/{total} ảnh mới.',
'lib.empty': 'Chưa có ảnh nào trong thư mục này.',
'lib.noFolders': 'Chưa có thư mục nào. Bấm THÊM THƯ MỤC để chọn một thư mục ảnh trên máy.',
'lib.blocked':
'Một tab khác của ứng dụng đang giữ thư viện ở bản cũ hơn. Hãy đóng tab đó, hoặc tải lại nó, để tab này mở được thư viện.',
'lib.missing': 'Không mở được {name} — tệp đã bị di chuyển hoặc xoá.',
'lib.failed': 'Không đọc được thư mục này.',
'lib.unsupported':