|
| 1 | +import type { INode, ITextNode, RendererMain } from '@lightningjs/renderer'; |
| 2 | +import { Component } from './Component.js'; |
| 3 | +import { loadStorage, saveStorage } from '../common/LocalStorage.js'; |
| 4 | + |
| 5 | +interface PageContainerLocalStorageData { |
| 6 | + curPage: number; |
| 7 | +} |
| 8 | + |
| 9 | +const TITLE_FONT_SIZE = 40; |
| 10 | +const PADDING = 20; |
| 11 | + |
| 12 | +interface PageContainerProps { |
| 13 | + x?: number; |
| 14 | + y?: number; |
| 15 | + width?: number; |
| 16 | + height?: number; |
| 17 | + parent?: INode | null; |
| 18 | + color?: number; |
| 19 | + |
| 20 | + // |
| 21 | + testName?: string; |
| 22 | + title?: string; |
| 23 | +} |
| 24 | + |
| 25 | +export class PageContainer extends Component { |
| 26 | + private titleNode: ITextNode; |
| 27 | + private pageNumberNode: ITextNode; |
| 28 | + private curPageNode: INode | null = null; |
| 29 | + private curPageIndex = -1; |
| 30 | + private pageConstructors: ((page: INode) => Promise<void>)[] = []; |
| 31 | + private testName?: string; |
| 32 | + |
| 33 | + constructor(renderer: RendererMain, props: PageContainerProps) { |
| 34 | + super(renderer, { |
| 35 | + x: props.x, |
| 36 | + y: props.y, |
| 37 | + color: props.color ?? 0x00000000, |
| 38 | + width: props.width, |
| 39 | + height: props.height, |
| 40 | + parent: props.parent, |
| 41 | + }); |
| 42 | + |
| 43 | + this.titleNode = renderer.createTextNode({ |
| 44 | + fontFamily: 'Ubuntu', |
| 45 | + fontSize: TITLE_FONT_SIZE, |
| 46 | + x: PADDING, |
| 47 | + y: PADDING, |
| 48 | + parent: renderer.root, |
| 49 | + text: props.title ?? '', |
| 50 | + }); |
| 51 | + |
| 52 | + this.testName = props.testName; |
| 53 | + |
| 54 | + this.pageNumberNode = renderer.createTextNode({ |
| 55 | + fontFamily: 'Ubuntu', |
| 56 | + fontSize: 30, |
| 57 | + x: PADDING, |
| 58 | + y: this.node.height - 30 - PADDING, |
| 59 | + parent: renderer.root, |
| 60 | + }); |
| 61 | + } |
| 62 | + |
| 63 | + pushPage(pageConstructor: (page: INode) => Promise<void>) { |
| 64 | + this.pageConstructors.push(pageConstructor); |
| 65 | + } |
| 66 | + |
| 67 | + finalizePages() { |
| 68 | + if (this.curPageIndex === -1 && this.pageConstructors.length > 0) { |
| 69 | + const { testName } = this; |
| 70 | + let pageNum = 0; |
| 71 | + if (testName) { |
| 72 | + const savedState = loadStorage<PageContainerLocalStorageData>( |
| 73 | + `${testName}-PageContainer`, |
| 74 | + ); |
| 75 | + if ( |
| 76 | + savedState && |
| 77 | + savedState.curPage && |
| 78 | + savedState.curPage < this.pageConstructors.length |
| 79 | + ) { |
| 80 | + pageNum = savedState.curPage; |
| 81 | + } |
| 82 | + } |
| 83 | + this.setPage(pageNum).catch(console.error); |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + async setPage(pageIndex: number) { |
| 88 | + this.pageNumberNode.text = `Page ${pageIndex + 1}/${ |
| 89 | + this.pageConstructors.length |
| 90 | + }`; |
| 91 | + this.curPageIndex = pageIndex; |
| 92 | + |
| 93 | + if (this.curPageNode) { |
| 94 | + this.curPageNode.destroy(); |
| 95 | + this.curPageNode = null; |
| 96 | + } |
| 97 | + |
| 98 | + this.curPageNode = this.renderer.createNode({ |
| 99 | + x: PADDING, |
| 100 | + y: TITLE_FONT_SIZE + PADDING, |
| 101 | + color: 0x00000000, |
| 102 | + width: this.contentWidth, |
| 103 | + height: this.contentHeight, |
| 104 | + parent: this.node, |
| 105 | + }); |
| 106 | + |
| 107 | + const { testName } = this; |
| 108 | + if (testName) { |
| 109 | + saveStorage<PageContainerLocalStorageData>(`${testName}-PageContainer`, { |
| 110 | + curPage: pageIndex, |
| 111 | + }); |
| 112 | + } |
| 113 | + |
| 114 | + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
| 115 | + await this.pageConstructors[pageIndex]!(this.curPageNode); |
| 116 | + } |
| 117 | + |
| 118 | + bindWindowKeys() { |
| 119 | + window.addEventListener('keydown', (e) => { |
| 120 | + const numPages = this.pageConstructors.length; |
| 121 | + if (e.key === 'ArrowLeft') { |
| 122 | + const newPageIndex = (this.curPageIndex + numPages - 1) % numPages; |
| 123 | + this.setPage(newPageIndex).catch(console.error); |
| 124 | + } else if (e.key === 'ArrowRight') { |
| 125 | + const newPageIndex = (this.curPageIndex + 1) % numPages; |
| 126 | + this.setPage(newPageIndex).catch(console.error); |
| 127 | + } |
| 128 | + }); |
| 129 | + // Once we've bound keys for the first time, turn this method into a noop |
| 130 | + this.bindWindowKeys = () => { |
| 131 | + /* noop */ |
| 132 | + }; |
| 133 | + } |
| 134 | + |
| 135 | + get contentHeight() { |
| 136 | + return this.node.height - TITLE_FONT_SIZE - PADDING * 2; |
| 137 | + } |
| 138 | + |
| 139 | + get contentWidth() { |
| 140 | + return this.node.width - PADDING * 2; |
| 141 | + } |
| 142 | + |
| 143 | + get padding() { |
| 144 | + return PADDING; |
| 145 | + } |
| 146 | +} |
0 commit comments