Files
ERitors-Scribe-Desktop/lib/models/SyncedSeries.ts
natreex 209dc6f85a Remove CharacterComponent and CharacterDetail components
- Deleted `CharacterComponent` and `CharacterDetail` files from the project.
- Refactored related logic to improve code maintainability and reduce redundancy.
2026-02-05 14:12:08 -05:00

281 lines
11 KiB
TypeScript

/**
* Lightweight sync structures for series comparison.
* These interfaces mirror the backend SyncedSeries* types from Book.ts
* but are used in the frontend for sync status detection.
*/
export interface SyncedSeriesBook {
bookId: string;
order: number;
lastUpdate: number;
}
export interface SyncedSeriesCharacterAttribute {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeriesCharacter {
id: string;
name: string;
lastUpdate: number;
attributes: SyncedSeriesCharacterAttribute[];
}
export interface SyncedSeriesWorldElement {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeriesWorld {
id: string;
name: string;
lastUpdate: number;
elements: SyncedSeriesWorldElement[];
}
export interface SyncedSeriesLocationSubElement {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeriesLocationElement {
id: string;
name: string;
lastUpdate: number;
subElements: SyncedSeriesLocationSubElement[];
}
export interface SyncedSeriesLocation {
id: string;
name: string;
lastUpdate: number;
elements: SyncedSeriesLocationElement[];
}
export interface SyncedSeriesSpell {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeriesSpellTag {
id: string;
name: string;
lastUpdate: number;
}
export interface SyncedSeries {
id: string;
name: string;
description: string | null;
lastUpdate: number;
books: SyncedSeriesBook[];
characters: SyncedSeriesCharacter[];
worlds: SyncedSeriesWorld[];
locations: SyncedSeriesLocation[];
spells: SyncedSeriesSpell[];
spellTags: SyncedSeriesSpellTag[];
}
/**
* Comparison result containing IDs of changed entities.
* Used for partial synchronization - only changed entities are transferred.
*/
export interface SeriesSyncCompare {
id: string;
books: string[];
characters: string[];
characterAttributes: string[];
worlds: string[];
worldElements: string[];
locations: string[];
locationElements: string[];
locationSubElements: string[];
spells: string[];
spellTags: string[];
}
/**
* Compares two versions of a series to find changed entities.
* The "newer" series is compared against the "older" one to find
* entities that have been added or modified.
*
* @param newerSeries - The series version with potentially newer data
* @param olderSeries - The series version to compare against
* @returns SeriesSyncCompare with IDs of changed entities, or null if no changes
*/
export function compareSeriesSyncs(newerSeries: SyncedSeries, olderSeries: SyncedSeries): SeriesSyncCompare | null {
const changedBookIds: string[] = [];
const changedCharacterIds: string[] = [];
const changedCharacterAttributeIds: string[] = [];
const changedWorldIds: string[] = [];
const changedWorldElementIds: string[] = [];
const changedLocationIds: string[] = [];
const changedLocationElementIds: string[] = [];
const changedLocationSubElementIds: string[] = [];
const changedSpellIds: string[] = [];
const changedSpellTagIds: string[] = [];
// Compare books
newerSeries.books.forEach((newerBook: SyncedSeriesBook): void => {
const olderBook: SyncedSeriesBook | undefined = olderSeries.books.find(
(book: SyncedSeriesBook): boolean => book.bookId === newerBook.bookId
);
if (!olderBook || newerBook.lastUpdate > olderBook.lastUpdate) {
changedBookIds.push(newerBook.bookId);
}
});
// Compare characters and their attributes
newerSeries.characters.forEach((newerCharacter: SyncedSeriesCharacter): void => {
const olderCharacter: SyncedSeriesCharacter | undefined = olderSeries.characters.find(
(character: SyncedSeriesCharacter): boolean => character.id === newerCharacter.id
);
if (!olderCharacter) {
changedCharacterIds.push(newerCharacter.id);
newerCharacter.attributes.forEach((attr: SyncedSeriesCharacterAttribute): void => {
changedCharacterAttributeIds.push(attr.id);
});
} else if (newerCharacter.lastUpdate > olderCharacter.lastUpdate) {
changedCharacterIds.push(newerCharacter.id);
} else {
// Check attributes even if character hasn't changed
newerCharacter.attributes.forEach((newerAttr: SyncedSeriesCharacterAttribute): void => {
const olderAttr: SyncedSeriesCharacterAttribute | undefined = olderCharacter.attributes.find(
(attr: SyncedSeriesCharacterAttribute): boolean => attr.id === newerAttr.id
);
if (!olderAttr || newerAttr.lastUpdate > olderAttr.lastUpdate) {
changedCharacterAttributeIds.push(newerAttr.id);
}
});
}
});
// Compare worlds and their elements
newerSeries.worlds.forEach((newerWorld: SyncedSeriesWorld): void => {
const olderWorld: SyncedSeriesWorld | undefined = olderSeries.worlds.find(
(world: SyncedSeriesWorld): boolean => world.id === newerWorld.id
);
if (!olderWorld) {
changedWorldIds.push(newerWorld.id);
newerWorld.elements.forEach((element: SyncedSeriesWorldElement): void => {
changedWorldElementIds.push(element.id);
});
} else if (newerWorld.lastUpdate > olderWorld.lastUpdate) {
changedWorldIds.push(newerWorld.id);
} else {
// Check elements even if world hasn't changed
newerWorld.elements.forEach((newerElement: SyncedSeriesWorldElement): void => {
const olderElement: SyncedSeriesWorldElement | undefined = olderWorld.elements.find(
(element: SyncedSeriesWorldElement): boolean => element.id === newerElement.id
);
if (!olderElement || newerElement.lastUpdate > olderElement.lastUpdate) {
changedWorldElementIds.push(newerElement.id);
}
});
}
});
// Compare locations, their elements, and sub-elements
newerSeries.locations.forEach((newerLocation: SyncedSeriesLocation): void => {
const olderLocation: SyncedSeriesLocation | undefined = olderSeries.locations.find(
(location: SyncedSeriesLocation): boolean => location.id === newerLocation.id
);
if (!olderLocation) {
changedLocationIds.push(newerLocation.id);
newerLocation.elements.forEach((element: SyncedSeriesLocationElement): void => {
changedLocationElementIds.push(element.id);
element.subElements.forEach((subElement: SyncedSeriesLocationSubElement): void => {
changedLocationSubElementIds.push(subElement.id);
});
});
} else if (newerLocation.lastUpdate > olderLocation.lastUpdate) {
changedLocationIds.push(newerLocation.id);
} else {
// Check elements
newerLocation.elements.forEach((newerElement: SyncedSeriesLocationElement): void => {
const olderElement: SyncedSeriesLocationElement | undefined = olderLocation.elements.find(
(element: SyncedSeriesLocationElement): boolean => element.id === newerElement.id
);
if (!olderElement) {
changedLocationElementIds.push(newerElement.id);
newerElement.subElements.forEach((subElement: SyncedSeriesLocationSubElement): void => {
changedLocationSubElementIds.push(subElement.id);
});
} else if (newerElement.lastUpdate > olderElement.lastUpdate) {
changedLocationElementIds.push(newerElement.id);
} else {
// Check sub-elements
newerElement.subElements.forEach((newerSubElement: SyncedSeriesLocationSubElement): void => {
const olderSubElement: SyncedSeriesLocationSubElement | undefined = olderElement.subElements.find(
(subElement: SyncedSeriesLocationSubElement): boolean => subElement.id === newerSubElement.id
);
if (!olderSubElement || newerSubElement.lastUpdate > olderSubElement.lastUpdate) {
changedLocationSubElementIds.push(newerSubElement.id);
}
});
}
});
}
});
// Compare spells
newerSeries.spells.forEach((newerSpell: SyncedSeriesSpell): void => {
const olderSpell: SyncedSeriesSpell | undefined = olderSeries.spells.find(
(spell: SyncedSeriesSpell): boolean => spell.id === newerSpell.id
);
if (!olderSpell || newerSpell.lastUpdate > olderSpell.lastUpdate) {
changedSpellIds.push(newerSpell.id);
}
});
// Compare spell tags
newerSeries.spellTags.forEach((newerTag: SyncedSeriesSpellTag): void => {
const olderTag: SyncedSeriesSpellTag | undefined = olderSeries.spellTags.find(
(tag: SyncedSeriesSpellTag): boolean => tag.id === newerTag.id
);
if (!olderTag || newerTag.lastUpdate > olderTag.lastUpdate) {
changedSpellTagIds.push(newerTag.id);
}
});
// Check if there are any changes
const hasChanges: boolean =
changedBookIds.length > 0 ||
changedCharacterIds.length > 0 ||
changedCharacterAttributeIds.length > 0 ||
changedWorldIds.length > 0 ||
changedWorldElementIds.length > 0 ||
changedLocationIds.length > 0 ||
changedLocationElementIds.length > 0 ||
changedLocationSubElementIds.length > 0 ||
changedSpellIds.length > 0 ||
changedSpellTagIds.length > 0;
if (!hasChanges) {
return null;
}
return {
id: newerSeries.id,
books: changedBookIds,
characters: changedCharacterIds,
characterAttributes: changedCharacterAttributeIds,
worlds: changedWorldIds,
worldElements: changedWorldElementIds,
locations: changedLocationIds,
locationElements: changedLocationElementIds,
locationSubElements: changedLocationSubElementIds,
spells: changedSpellIds,
spellTags: changedSpellTagIds
};
}