- Deleted `CharacterComponent` and `CharacterDetail` files from the project. - Refactored related logic to improve code maintainability and reduce redundancy.
281 lines
11 KiB
TypeScript
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
|
|
};
|
|
}
|