| |
| |
| |
| |
|
|
| export interface TokenRenderTask { |
| tokenObj: any; |
| index: number; |
| offset: [number, number]; |
| } |
|
|
| export interface RenderCallback { |
| (task: TokenRenderTask, containerText: string, rd: any): void; |
| } |
|
|
| export interface RenderAnimatorOptions { |
| |
| enabled?: boolean; |
| |
| batchSize?: number | ((totalTokens: number) => number); |
| |
| delayBetweenBatches?: number; |
| } |
|
|
| |
| |
| |
| |
| export class RenderAnimator { |
| private options: Required<RenderAnimatorOptions>; |
|
|
| constructor(options: RenderAnimatorOptions = {}) { |
| this.options = { |
| enabled: options.enabled ?? true, |
| batchSize: options.batchSize ?? 32, |
| delayBetweenBatches: options.delayBetweenBatches ?? 8, |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public async renderWithAnimation( |
| tasks: TokenRenderTask[], |
| containerText: string, |
| rd: any, |
| renderCallback: RenderCallback, |
| baseNode?: Node |
| ): Promise<void> { |
| if (!this.options.enabled || tasks.length === 0) { |
| |
| tasks.forEach(task => { |
| renderCallback(task, containerText, rd); |
| }); |
| return; |
| } |
|
|
| const totalTokens = tasks.length; |
| |
| const initialBatchSize = typeof this.options.batchSize === 'function' |
| ? this.options.batchSize(totalTokens) |
| : this.options.batchSize; |
|
|
| |
| |
| for (let i = tasks.length - 1; i >= 0; i--) { |
| renderCallback(tasks[i], containerText, rd); |
| } |
|
|
| |
| |
| let currentIndex = 0; |
| let currentBatchSize = initialBatchSize; |
| |
| |
| await new Promise(resolve => setTimeout(resolve, this.options.delayBetweenBatches)); |
| |
| while (currentIndex < tasks.length) { |
| const batch: TokenRenderTask[] = []; |
| |
| const actualBatchSize = Math.min(currentBatchSize, tasks.length - currentIndex); |
| for (let j = 0; j < actualBatchSize; j++) { |
| batch.push(tasks[currentIndex + j]); |
| } |
|
|
| |
| this.animateBatch(batch.map(t => t.index), baseNode); |
|
|
| |
| currentIndex += actualBatchSize; |
| |
| |
| if (currentIndex < tasks.length) { |
| await new Promise(resolve => setTimeout(resolve, this.options.delayBetweenBatches)); |
| currentBatchSize = Math.floor(currentBatchSize * 1.5); |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| private animateBatch(tokenIndices: number[], baseNode?: Node): void { |
| if (!baseNode) return; |
| |
| |
| requestAnimationFrame(() => { |
| tokenIndices.forEach(tokenIndex => { |
| const tokenElement = (baseNode as Element).querySelector(`[data-token-index="${tokenIndex}"]`) as HTMLElement; |
| if (tokenElement) { |
| |
| const targetColor = tokenElement.getAttribute('data-target-color'); |
| if (targetColor) { |
| |
| tokenElement.style.transition = 'none'; |
| |
| tokenElement.style.backgroundColor = targetColor; |
| } |
| } |
| }); |
| }); |
| } |
|
|
| |
| |
| |
| public setEnabled(enabled: boolean): void { |
| this.options.enabled = enabled; |
| } |
|
|
| |
| |
| |
| public isEnabled(): boolean { |
| return this.options.enabled; |
| } |
| } |
|
|
|
|