Checkpoint WebGL book reveal optimization

This commit is contained in:
2026-06-08 08:19:20 +02:00
parent 7abd3387f3
commit c86a304364
13 changed files with 618 additions and 112 deletions
+60 -17
View File
@@ -32,6 +32,8 @@ class BookPaginationModule extends BaseModule {
'extractLayoutLine',
'extractRemainingLayoutText',
'extractLines',
'getActiveStyleTags',
'updateStyleTagStack',
'countLineWords',
'getLineGeometry',
'getSpread',
@@ -95,8 +97,8 @@ class BookPaginationModule extends BaseModule {
this.publish();
}
async preparePendingBlock(block = {}) {
const token = ++this.refreshToken;
async preparePendingBlock(block = {}, options = {}) {
const token = options.activate === false ? this.refreshToken : ++this.refreshToken;
const gameId = block.gameId || block.metadata?.gameId || this.storyHistory?.currentGameId || null;
const latestRenderedBlockId = Math.max(0, Number(this.storyHistory?.latestRenderedBlockId || 0));
const pendingBlockId = Math.max(0, Number(block.blockId || block.metadata?.blockId || 0));
@@ -104,10 +106,13 @@ class BookPaginationModule extends BaseModule {
return null;
}
const historyBlocks = latestRenderedBlockId > 0
? await this.storyHistory.getBlocksRange(gameId, 1, latestRenderedBlockId)
const historyEndBlockId = options.includeUnrenderedHistory
? Math.max(0, pendingBlockId - 1)
: latestRenderedBlockId;
const historyBlocks = historyEndBlockId > 0
? await this.storyHistory.getBlocksRange(gameId, 1, historyEndBlockId)
: [];
if (token !== this.refreshToken) return null;
if (options.activate !== false && token !== this.refreshToken) return null;
const normalizedBlock = {
...block,
@@ -121,26 +126,30 @@ class BookPaginationModule extends BaseModule {
gameId
}
};
this.latestBlockId = pendingBlockId;
this.latestRenderedBlockId = latestRenderedBlockId;
this.spreads = this.buildSpreads([...historyBlocks, normalizedBlock]);
this.currentSpreadIndex = Math.max(0, Math.min(this.spreads.length - 1, this.currentSpreadIndex));
const targetSpread = this.spreads.find(spread => ['left', 'right'].some(side => {
const preparedSpreads = this.buildSpreads([...historyBlocks, normalizedBlock]);
const targetSpread = preparedSpreads.find(spread => ['left', 'right'].some(side => {
const lines = Array.isArray(spread?.[side]) ? spread[side] : [];
return lines.some(line => Number(line?.blockId || 0) === pendingBlockId);
}));
if (targetSpread) this.currentSpreadIndex = targetSpread.index;
this.publish();
if (options.activate !== false) {
this.latestBlockId = pendingBlockId;
this.latestRenderedBlockId = latestRenderedBlockId;
this.spreads = preparedSpreads;
this.currentSpreadIndex = Math.max(0, Math.min(this.spreads.length - 1, this.currentSpreadIndex));
if (targetSpread) this.currentSpreadIndex = targetSpread.index;
}
if (options.publish !== false) this.publish();
document.dispatchEvent(new CustomEvent('book-pagination:block-prepared', {
detail: {
blockId: pendingBlockId,
spread: this.getCurrentSpread(),
spreadIndex: this.currentSpreadIndex,
latestBlockId: this.latestBlockId,
latestRenderedBlockId: this.latestRenderedBlockId
spread: targetSpread || (options.activate === false ? preparedSpreads[0] : this.getCurrentSpread()),
spreadIndex: targetSpread?.index ?? this.currentSpreadIndex,
latestBlockId: pendingBlockId,
latestRenderedBlockId,
preloadOnly: options.activate === false
}
}));
return this.getCurrentSpread();
return targetSpread || (options.activate === false ? preparedSpreads[0] : this.getCurrentSpread());
}
buildSpreads(blocks = []) {
@@ -342,6 +351,7 @@ class BookPaginationModule extends BaseModule {
isFinal: lineIndex === layout.breaks.length - 2,
smallCaps: Boolean(metadata.smallCaps),
hyphenated: Boolean(endNode?.type === 'penalty' && endNode.penalty === 100),
activeStyleTags: this.getActiveStyleTags(layout.nodes, startBreak.position),
align: 'justify'
};
}
@@ -385,12 +395,45 @@ class BookPaginationModule extends BaseModule {
ratio: breaks[index].ratio || 0,
isFinal: index === breaks.length - 1,
hyphenated: Boolean(lineNodes.at(-1)?.type === 'penalty' && lineNodes.at(-1)?.penalty === 100),
activeStyleTags: this.getActiveStyleTags(nodes, start),
align: options.align || 'justify'
});
}
return lines;
}
getActiveStyleTags(nodes = [], endPosition = 0) {
const stack = [];
for (let index = 0; index < endPosition; index += 1) {
const node = nodes[index];
if (node?.type !== 'tag') continue;
this.updateStyleTagStack(stack, node.value);
}
return stack.map(tag => ({ ...tag }));
}
updateStyleTagStack(stack = [], value = '') {
const text = String(value || '');
if (!text.startsWith('<')) return stack;
if (text.startsWith('</')) {
if (stack.length) stack.pop();
return stack;
}
const template = document.createElement('div');
template.innerHTML = text;
const element = template.firstElementChild;
if (!element) return stack;
const tagName = element.tagName.toLowerCase();
const style = String(element.getAttribute('style') || '').toLowerCase();
const className = String(element.getAttribute('class') || '').toLowerCase();
stack.push({
tagName,
bold: tagName === 'strong' || tagName === 'b' || /font-weight\s*:\s*(bold|[6-9]00)/.test(style) || className.includes('bold'),
italic: tagName === 'em' || tagName === 'i' || /font-style\s*:\s*italic/.test(style) || className.includes('italic')
});
return stack;
}
countLineWords(line = {}) {
const nodes = Array.isArray(line.nodes) ? line.nodes : [];
let count = 0;