- The setup form, active chapter tab, verse range selection, typed/bulk/ cross-chapter chunk creation, reorder, and delete move out of App. - Setup now opens on the chapter holding the chunk last studied (e.g. after Back to Chunks), and the Add Chapter form suggests the chapter after the project's last one instead of repeating the current one. - openNewProject/goHome/resume/import no longer reset setup state by hand; SetupPage starts fresh each time it opens. - Tests: add-chapter default, Back to Chunks chapter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
114 lines
3.9 KiB
JavaScript
114 lines
3.9 KiB
JavaScript
// The signed-in user's saved projects: opening, renaming, deleting, and keeping
|
|
// this device's copies in step with the server.
|
|
import { useEffect, useState } from 'react';
|
|
import { loadProjectIndex } from '../utils/bibleUtils.jsx';
|
|
import {
|
|
saveProjectToStorage,
|
|
deleteProjectFromStorage,
|
|
loadProjectById,
|
|
} from '../utils/projectStorage.js';
|
|
import {
|
|
saveRemoteProject,
|
|
deleteRemoteProject,
|
|
listRemoteProjects,
|
|
loadRemoteProject,
|
|
} from '../syncService.js';
|
|
|
|
export function useProjectLibrary({
|
|
authUser,
|
|
project,
|
|
setProject,
|
|
setProjectIndex,
|
|
setCurrentPage,
|
|
}) {
|
|
const [staleLocalProjects, setStaleLocalProjects] = useState([]); // projects where server is newer
|
|
const [autoRestoredCount, setAutoRestoredCount] = useState(null); // brief "synced from another device" toast
|
|
|
|
// Once we know who's signed in, reconcile the local project index against the server.
|
|
// Runs on every authUser change (including logout -> different login) so a previous
|
|
// account's stale suggestions never linger after switching users. Projects that exist
|
|
// on the server but not on this device (e.g. a brand new browser) are pulled down
|
|
// automatically instead of requiring a manual "Restore" click per project.
|
|
useEffect(() => {
|
|
if (!authUser) {
|
|
setStaleLocalProjects([]);
|
|
return;
|
|
}
|
|
let cancelled = false;
|
|
const localIndex = loadProjectIndex();
|
|
listRemoteProjects().then(async (result) => {
|
|
if (!result.ok || cancelled) return;
|
|
const localMap = new Map(localIndex.map((e) => [e.id, e]));
|
|
|
|
const missing = result.data.filter((e) => !localMap.has(e.id));
|
|
if (missing.length > 0) {
|
|
const fetched = await Promise.all(missing.map((e) => loadRemoteProject(e.id)));
|
|
fetched.filter((r) => r.ok).forEach((r) => saveProjectToStorage(r.data));
|
|
if (cancelled) return;
|
|
const restoredCount = fetched.filter((r) => r.ok).length;
|
|
if (restoredCount > 0) {
|
|
setProjectIndex(loadProjectIndex());
|
|
setAutoRestoredCount(restoredCount);
|
|
window.setTimeout(() => setAutoRestoredCount(null), 4000);
|
|
}
|
|
}
|
|
|
|
const stale = result.data.filter((e) => {
|
|
const local = localMap.get(e.id);
|
|
return local && (e.lastEdited ?? 0) > (local.lastEdited ?? 0);
|
|
});
|
|
if (!cancelled) setStaleLocalProjects(stale);
|
|
});
|
|
return () => { cancelled = true; };
|
|
}, [authUser]);
|
|
|
|
const resumeProject = (id) => {
|
|
const loaded = loadProjectById(id);
|
|
if (!loaded) return;
|
|
setProject(loaded);
|
|
const loadedChunks = loaded.chapters.flatMap((ch) => ch.chunks);
|
|
const hasSelectedChunk = loaded.selectedChunkId
|
|
&& loadedChunks.some((c) => c.id === loaded.selectedChunkId);
|
|
setCurrentPage(hasSelectedChunk ? 'study' : 'setup');
|
|
};
|
|
|
|
const renameProjectInStorage = (id, title) => {
|
|
const loaded = loadProjectById(id);
|
|
if (!loaded) return;
|
|
const updated = { ...loaded, title, lastEdited: Date.now() };
|
|
saveProjectToStorage(updated);
|
|
setProjectIndex(loadProjectIndex());
|
|
saveRemoteProject(updated);
|
|
if (project?.id === id) {
|
|
setProject((current) => (current ? { ...current, title } : current));
|
|
}
|
|
};
|
|
|
|
const deleteProject = (id) => {
|
|
if (!window.confirm('Delete this project? This cannot be undone.')) return;
|
|
deleteProjectFromStorage(id);
|
|
setProjectIndex(loadProjectIndex());
|
|
deleteRemoteProject(id); // fire-and-forget
|
|
};
|
|
|
|
const pullLatestFromServer = async (id) => {
|
|
const result = await loadRemoteProject(id);
|
|
if (!result.ok) {
|
|
alert('Could not pull latest version from server.');
|
|
return;
|
|
}
|
|
saveProjectToStorage(result.data);
|
|
setProjectIndex(loadProjectIndex());
|
|
setStaleLocalProjects((prev) => prev.filter((e) => e.id !== id));
|
|
};
|
|
|
|
return {
|
|
staleLocalProjects,
|
|
autoRestoredCount,
|
|
resumeProject,
|
|
renameProjectInStorage,
|
|
deleteProject,
|
|
pullLatestFromServer,
|
|
};
|
|
}
|