- Add `useEffect` in `ScribeLeftBar` for handling book state changes. - Extend `BooksSyncContext` with new properties and stricter typings. - Refine `Repositories` to include `lastUpdate` handling for synchronization processes. - Add comprehensive `fetchComplete*` repository methods for retrieving entity-specific sync data. - Enhance offline logic for chapters, characters, locations, and world synchronization. - Improve error handling across IPC handlers and repositories.
366 lines
14 KiB
TypeScript
366 lines
14 KiB
TypeScript
import {
|
|
SyncedBook,
|
|
SyncedChapter,
|
|
SyncedChapterContent,
|
|
SyncedChapterInfo,
|
|
SyncedCharacter,
|
|
SyncedCharacterAttribute,
|
|
SyncedLocation,
|
|
SyncedLocationElement,
|
|
SyncedLocationSubElement,
|
|
SyncedWorld,
|
|
SyncedWorldElement,
|
|
SyncedIncident,
|
|
SyncedPlotPoint,
|
|
SyncedIssue,
|
|
SyncedActSummary,
|
|
SyncedGuideLine,
|
|
SyncedAIGuideLine
|
|
} from "@/lib/models/SyncedBook";
|
|
|
|
/**
|
|
* Résultat de comparaison pour un livre
|
|
*/
|
|
export interface BookSyncDiff {
|
|
id: string;
|
|
type: string;
|
|
title: string;
|
|
subTitle: string | null;
|
|
bookNeedsUpdate: boolean; // Le livre lui-même a changé
|
|
lastUpdate: number;
|
|
chapters: SyncedChapter[];
|
|
characters: SyncedCharacter[];
|
|
locations: SyncedLocation[];
|
|
worlds: SyncedWorld[];
|
|
incidents: SyncedIncident[];
|
|
plotPoints: SyncedPlotPoint[];
|
|
issues: SyncedIssue[];
|
|
actSummaries: SyncedActSummary[];
|
|
guideLine: SyncedGuideLine | null;
|
|
aiGuideLine: SyncedAIGuideLine | null;
|
|
hasAnyChanges: boolean; // Indique si des changements existent
|
|
}
|
|
|
|
/**
|
|
* Compare en profondeur deux livres synchronisés et retourne les différences
|
|
*/
|
|
export function compareBooks(serverBook: SyncedBook, localBook: SyncedBook): BookSyncDiff {
|
|
const bookNeedsUpdate = serverBook.lastUpdate > localBook.lastUpdate;
|
|
|
|
// Comparer les chapitres
|
|
const chaptersWithChanges: SyncedChapter[] = [];
|
|
for (const serverChapter of serverBook.chapters) {
|
|
const localChapter = localBook.chapters.find(c => c.id === serverChapter.id);
|
|
|
|
if (!localChapter) {
|
|
// Chapitre n'existe pas localement
|
|
chaptersWithChanges.push(serverChapter);
|
|
} else if (serverChapter.lastUpdate > localChapter.lastUpdate) {
|
|
// Chapitre a changé
|
|
chaptersWithChanges.push(serverChapter);
|
|
} else {
|
|
// Vérifier les contenus du chapitre
|
|
const contentsWithChanges: SyncedChapterContent[] = [];
|
|
for (const serverContent of serverChapter.contents) {
|
|
const localContent = localChapter.contents.find(c => c.id === serverContent.id);
|
|
if (!localContent || serverContent.lastUpdate > localContent.lastUpdate) {
|
|
contentsWithChanges.push(serverContent);
|
|
}
|
|
}
|
|
|
|
// Vérifier l'info du chapitre
|
|
let infoNeedsUpdate = false;
|
|
if (serverChapter.info && localChapter.info) {
|
|
infoNeedsUpdate = serverChapter.info.lastUpdate > localChapter.info.lastUpdate;
|
|
} else if (serverChapter.info && !localChapter.info) {
|
|
infoNeedsUpdate = true;
|
|
}
|
|
|
|
// Si des contenus ou info ont changé, inclure le chapitre
|
|
if (contentsWithChanges.length > 0 || infoNeedsUpdate) {
|
|
chaptersWithChanges.push({
|
|
...serverChapter,
|
|
contents: contentsWithChanges.length > 0 ? contentsWithChanges : serverChapter.contents,
|
|
info: infoNeedsUpdate ? serverChapter.info : null
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Comparer les personnages
|
|
const charactersWithChanges: SyncedCharacter[] = [];
|
|
for (const serverChar of serverBook.characters) {
|
|
const localChar = localBook.characters.find(c => c.id === serverChar.id);
|
|
|
|
if (!localChar) {
|
|
charactersWithChanges.push(serverChar);
|
|
} else if (serverChar.lastUpdate > localChar.lastUpdate) {
|
|
charactersWithChanges.push(serverChar);
|
|
} else {
|
|
// Vérifier les attributs
|
|
const attributesWithChanges: SyncedCharacterAttribute[] = [];
|
|
for (const serverAttr of serverChar.attributes) {
|
|
const localAttr = localChar.attributes.find(a => a.id === serverAttr.id);
|
|
if (!localAttr || serverAttr.lastUpdate > localAttr.lastUpdate) {
|
|
attributesWithChanges.push(serverAttr);
|
|
}
|
|
}
|
|
|
|
if (attributesWithChanges.length > 0) {
|
|
charactersWithChanges.push({
|
|
...serverChar,
|
|
attributes: attributesWithChanges
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Comparer les locations
|
|
const locationsWithChanges: SyncedLocation[] = [];
|
|
for (const serverLoc of serverBook.locations) {
|
|
const localLoc = localBook.locations.find(l => l.id === serverLoc.id);
|
|
|
|
if (!localLoc) {
|
|
locationsWithChanges.push(serverLoc);
|
|
} else if (serverLoc.lastUpdate > localLoc.lastUpdate) {
|
|
locationsWithChanges.push(serverLoc);
|
|
} else {
|
|
// Vérifier les éléments
|
|
const elementsWithChanges: SyncedLocationElement[] = [];
|
|
for (const serverElem of serverLoc.elements) {
|
|
const localElem = localLoc.elements.find(e => e.id === serverElem.id);
|
|
|
|
if (!localElem) {
|
|
elementsWithChanges.push(serverElem);
|
|
} else if (serverElem.lastUpdate > localElem.lastUpdate) {
|
|
elementsWithChanges.push(serverElem);
|
|
} else {
|
|
// Vérifier les sous-éléments
|
|
const subElementsWithChanges: SyncedLocationSubElement[] = [];
|
|
for (const serverSubElem of serverElem.subElements) {
|
|
const localSubElem = localElem.subElements.find(s => s.id === serverSubElem.id);
|
|
if (!localSubElem || serverSubElem.lastUpdate > localSubElem.lastUpdate) {
|
|
subElementsWithChanges.push(serverSubElem);
|
|
}
|
|
}
|
|
|
|
if (subElementsWithChanges.length > 0) {
|
|
elementsWithChanges.push({
|
|
...serverElem,
|
|
subElements: subElementsWithChanges
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (elementsWithChanges.length > 0) {
|
|
locationsWithChanges.push({
|
|
...serverLoc,
|
|
elements: elementsWithChanges
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Comparer les mondes
|
|
const worldsWithChanges: SyncedWorld[] = [];
|
|
for (const serverWorld of serverBook.worlds) {
|
|
const localWorld = localBook.worlds.find(w => w.id === serverWorld.id);
|
|
|
|
if (!localWorld) {
|
|
worldsWithChanges.push(serverWorld);
|
|
} else if (serverWorld.lastUpdate > localWorld.lastUpdate) {
|
|
worldsWithChanges.push(serverWorld);
|
|
} else {
|
|
// Vérifier les éléments du monde
|
|
const elementsWithChanges: SyncedWorldElement[] = [];
|
|
for (const serverElem of serverWorld.elements) {
|
|
const localElem = localWorld.elements.find(e => e.id === serverElem.id);
|
|
if (!localElem || serverElem.lastUpdate > localElem.lastUpdate) {
|
|
elementsWithChanges.push(serverElem);
|
|
}
|
|
}
|
|
|
|
if (elementsWithChanges.length > 0) {
|
|
worldsWithChanges.push({
|
|
...serverWorld,
|
|
elements: elementsWithChanges
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Comparer les incidents
|
|
const incidentsWithChanges: SyncedIncident[] = [];
|
|
for (const serverIncident of serverBook.incidents) {
|
|
const localIncident = localBook.incidents.find(i => i.id === serverIncident.id);
|
|
if (!localIncident || serverIncident.lastUpdate > localIncident.lastUpdate) {
|
|
incidentsWithChanges.push(serverIncident);
|
|
}
|
|
}
|
|
|
|
// Comparer les plot points
|
|
const plotPointsWithChanges: SyncedPlotPoint[] = [];
|
|
for (const serverPlot of serverBook.plotPoints) {
|
|
const localPlot = localBook.plotPoints.find(p => p.id === serverPlot.id);
|
|
if (!localPlot || serverPlot.lastUpdate > localPlot.lastUpdate) {
|
|
plotPointsWithChanges.push(serverPlot);
|
|
}
|
|
}
|
|
|
|
// Comparer les issues
|
|
const issuesWithChanges: SyncedIssue[] = [];
|
|
for (const serverIssue of serverBook.issues) {
|
|
const localIssue = localBook.issues.find(i => i.id === serverIssue.id);
|
|
if (!localIssue || serverIssue.lastUpdate > localIssue.lastUpdate) {
|
|
issuesWithChanges.push(serverIssue);
|
|
}
|
|
}
|
|
|
|
// Comparer les act summaries
|
|
const actSummariesWithChanges: SyncedActSummary[] = [];
|
|
for (const serverAct of serverBook.actSummaries) {
|
|
const localAct = localBook.actSummaries.find(a => a.id === serverAct.id);
|
|
if (!localAct || serverAct.lastUpdate > localAct.lastUpdate) {
|
|
actSummariesWithChanges.push(serverAct);
|
|
}
|
|
}
|
|
|
|
// Comparer guideline
|
|
let guideLineNeedsUpdate: SyncedGuideLine | null = null;
|
|
if (serverBook.guideLine && localBook.guideLine) {
|
|
if (serverBook.guideLine.lastUpdate > localBook.guideLine.lastUpdate) {
|
|
guideLineNeedsUpdate = serverBook.guideLine;
|
|
}
|
|
} else if (serverBook.guideLine && !localBook.guideLine) {
|
|
guideLineNeedsUpdate = serverBook.guideLine;
|
|
}
|
|
|
|
// Comparer AI guideline
|
|
let aiGuideLineNeedsUpdate: SyncedAIGuideLine | null = null;
|
|
if (serverBook.aiGuideLine && localBook.aiGuideLine) {
|
|
if (serverBook.aiGuideLine.lastUpdate > localBook.aiGuideLine.lastUpdate) {
|
|
aiGuideLineNeedsUpdate = serverBook.aiGuideLine;
|
|
}
|
|
} else if (serverBook.aiGuideLine && !localBook.aiGuideLine) {
|
|
aiGuideLineNeedsUpdate = serverBook.aiGuideLine;
|
|
}
|
|
|
|
// Déterminer s'il y a des changements
|
|
const hasAnyChanges = bookNeedsUpdate ||
|
|
chaptersWithChanges.length > 0 ||
|
|
charactersWithChanges.length > 0 ||
|
|
locationsWithChanges.length > 0 ||
|
|
worldsWithChanges.length > 0 ||
|
|
incidentsWithChanges.length > 0 ||
|
|
plotPointsWithChanges.length > 0 ||
|
|
issuesWithChanges.length > 0 ||
|
|
actSummariesWithChanges.length > 0 ||
|
|
guideLineNeedsUpdate !== null ||
|
|
aiGuideLineNeedsUpdate !== null;
|
|
|
|
return {
|
|
id: serverBook.id,
|
|
type: serverBook.type,
|
|
title: serverBook.title,
|
|
subTitle: serverBook.subTitle,
|
|
bookNeedsUpdate,
|
|
lastUpdate: serverBook.lastUpdate,
|
|
chapters: chaptersWithChanges,
|
|
characters: charactersWithChanges,
|
|
locations: locationsWithChanges,
|
|
worlds: worldsWithChanges,
|
|
incidents: incidentsWithChanges,
|
|
plotPoints: plotPointsWithChanges,
|
|
issues: issuesWithChanges,
|
|
actSummaries: actSummariesWithChanges,
|
|
guideLine: guideLineNeedsUpdate,
|
|
aiGuideLine: aiGuideLineNeedsUpdate,
|
|
hasAnyChanges
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Compare tous les livres serveur vs locaux et retourne ceux qui ont des changements
|
|
*/
|
|
export function getBooksToSyncFromServer(serverBooks: SyncedBook[], localBooks: SyncedBook[]): BookSyncDiff[] {
|
|
const booksWithChanges: BookSyncDiff[] = [];
|
|
|
|
for (const serverBook of serverBooks) {
|
|
const localBook = localBooks.find(b => b.id === serverBook.id);
|
|
|
|
if (!localBook) {
|
|
// Livre n'existe pas localement - tout le livre doit être synchronisé
|
|
booksWithChanges.push({
|
|
id: serverBook.id,
|
|
type: serverBook.type,
|
|
title: serverBook.title,
|
|
subTitle: serverBook.subTitle,
|
|
bookNeedsUpdate: true,
|
|
lastUpdate: serverBook.lastUpdate,
|
|
chapters: serverBook.chapters,
|
|
characters: serverBook.characters,
|
|
locations: serverBook.locations,
|
|
worlds: serverBook.worlds,
|
|
incidents: serverBook.incidents,
|
|
plotPoints: serverBook.plotPoints,
|
|
issues: serverBook.issues,
|
|
actSummaries: serverBook.actSummaries,
|
|
guideLine: serverBook.guideLine,
|
|
aiGuideLine: serverBook.aiGuideLine,
|
|
hasAnyChanges: true
|
|
});
|
|
} else {
|
|
// Comparer en profondeur
|
|
const diff = compareBooks(serverBook, localBook);
|
|
if (diff.hasAnyChanges) {
|
|
booksWithChanges.push(diff);
|
|
}
|
|
}
|
|
}
|
|
|
|
return booksWithChanges;
|
|
}
|
|
|
|
/**
|
|
* Compare tous les livres locaux vs serveur et retourne ceux qui ont des changements locaux
|
|
*/
|
|
export function getBooksToSyncToServer(localBooks: SyncedBook[], serverBooks: SyncedBook[]): BookSyncDiff[] {
|
|
const booksWithChanges: BookSyncDiff[] = [];
|
|
|
|
for (const localBook of localBooks) {
|
|
const serverBook = serverBooks.find(b => b.id === localBook.id);
|
|
|
|
if (!serverBook) {
|
|
// Livre n'existe pas sur le serveur - tout le livre doit être envoyé
|
|
booksWithChanges.push({
|
|
id: localBook.id,
|
|
type: localBook.type,
|
|
title: localBook.title,
|
|
subTitle: localBook.subTitle,
|
|
bookNeedsUpdate: true,
|
|
lastUpdate: localBook.lastUpdate,
|
|
chapters: localBook.chapters,
|
|
characters: localBook.characters,
|
|
locations: localBook.locations,
|
|
worlds: localBook.worlds,
|
|
incidents: localBook.incidents,
|
|
plotPoints: localBook.plotPoints,
|
|
issues: localBook.issues,
|
|
actSummaries: localBook.actSummaries,
|
|
guideLine: localBook.guideLine,
|
|
aiGuideLine: localBook.aiGuideLine,
|
|
hasAnyChanges: true
|
|
});
|
|
} else {
|
|
// Comparer en profondeur (local vs server)
|
|
const diff = compareBooks(localBook, serverBook);
|
|
if (diff.hasAnyChanges) {
|
|
booksWithChanges.push(diff);
|
|
}
|
|
}
|
|
}
|
|
|
|
return booksWithChanges;
|
|
}
|