import { SlideApplicationOption } from './SlideApplication'; import EventEmitter from 'eventemitter3'; import { kvStore } from '@netless/forge-room'; interface SideBarViewEvents { pageChange: (index: number) => void; } export class SideBarView extends EventEmitter { public readonly root: HTMLDivElement = document.createElement('div'); private isShow: boolean = false; private eventsMap: Map void)[]> = new Map(); private taskId: string = ''; private prefix: string = ''; private addImagesPool: number[] = []; private scheduleId: number = 0; private isSchedule = false; public constructor() { super(); this.root.style.backgroundColor = '#eee'; this.root.className = 'forge-slide-sidebar'; this.root.style.width = '240px'; this.root.style.height = '100%'; this.root.style.position = 'absolute'; this.root.style.left = '-240px'; this.root.style.top = '0'; this.root.style.zIndex = '5'; this.root.style.transition = 'left 0.3s ease-in-out'; this.root.style.overflow = 'auto'; this.root.style.display = 'flex'; this.root.style.flexDirection = 'column'; this.root.style.justifyContent = 'flex-start'; this.root.style.alignItems = 'center'; this.root.style.userSelect = 'none'; } get isShowSideBar(): boolean { return this.isShow; } public async getPreviewImage(imageUrl: string) { const image = await fetch(imageUrl); const blob = await image.blob(); return new Promise(resolve => { const reader = new FileReader(); reader.onloadend = () => { const base64Data = reader.result as string; const img = document.createElement('img'); img.src = base64Data; img.onload = () => { resolve({ url: imageUrl, src: base64Data, width: img.width, height: img.height, }); }; }; reader.readAsDataURL(blob); }); } private async appendPreviewImage(pageIndex: number) { const itemContainer = document.createElement('div'); itemContainer.style.width = '60%'; itemContainer.style.display = 'flex'; itemContainer.style.justifyContent = 'center'; itemContainer.style.alignItems = 'flex-start'; itemContainer.style.position = 'relative'; itemContainer.style.borderRadius = '4px'; itemContainer.style.transition = 'border-color .3s'; itemContainer.style.marginBottom = '10px'; const onMouseOverHandle = () => { itemContainer.style.borderColor = '#ccc'; }; const onMouseOutHandle = () => { itemContainer.style.borderColor = 'transparent'; }; const onClickHandle = () => { this.emit('pageChange', pageIndex); }; itemContainer.addEventListener('click', onClickHandle); itemContainer.addEventListener('mouseover', onMouseOverHandle); itemContainer.addEventListener('mouseout', onMouseOutHandle); this.eventsMap.set(itemContainer, [onClickHandle, onMouseOverHandle, onMouseOutHandle]); const pageIndexContainer = document.createElement('span'); pageIndexContainer.textContent = `${pageIndex}`; pageIndexContainer.style.position = 'absolute'; pageIndexContainer.style.top = '1px'; pageIndexContainer.style.left = '-10px'; pageIndexContainer.style.transform = 'translate(-100%)'; pageIndexContainer.style.fontSize = '12px'; pageIndexContainer.style.color = '#5f5f5f'; const previewUrl = `${this.prefix}/${this.taskId}/preview/${pageIndex}.png`; const preview = document.createElement('img'); const cachePreview = await kvStore.getItem(previewUrl); if (cachePreview) { const { src } = JSON.parse(cachePreview); preview.src = src; } else { const previewInfo = await this.getPreviewImage(previewUrl); await kvStore.setItem(previewUrl, JSON.stringify(previewInfo)); const { src } = previewInfo; preview.src = src; } preview.style.width = '100%'; preview.style.display = 'inline-block'; itemContainer.appendChild(preview); itemContainer.appendChild(pageIndexContainer); this.root.appendChild(itemContainer); } private async scheduleGetPreviewImage() { if (!this.isSchedule) { this.scheduleId = setTimeout(async () => { await this.scheduleGetPreviewImage(); }, 32) as unknown as number; return; } const pageIndex = this.addImagesPool.shift(); if (!pageIndex) { clearTimeout(this.scheduleId); this.scheduleId = 0; return; } await this.appendPreviewImage(pageIndex); this.scheduleId = setTimeout(async () => { await this.scheduleGetPreviewImage(); }, 32) as unknown as number; } public async startGetPreviewImageSchedule() { this.isSchedule = true; } public pauseGetPreviewImageSchedule() { this.isSchedule = false; } public initialize(slideCount: number, option: SlideApplicationOption): void { this.taskId = option.taskId; this.prefix = option.prefix; for (let i = 1; i <= slideCount; i++) { this.addImagesPool.push(i); } this.scheduleGetPreviewImage(); } public hidden() { if (!this.root || !this.isShow) { return; } this.root.style.left = '-240px'; this.root.style.border = 'none'; this.root.style.boxShadow = 'none'; this.isShow = false; } public show() { if (!this.root) { return; } this.root.style.left = '0'; this.root.style.border = '1px solid #ccc'; this.root.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)'; this.isShow = true; } public dispose() { this.removeAllListeners(); this.eventsMap.forEach((handlers, element) => { const [clickEvent, mouseOverEvent, mouseOutEvent] = handlers; element.removeEventListener('click', clickEvent); element.removeEventListener('mouseover', mouseOverEvent); element.removeEventListener('mouseout', mouseOutEvent); }); } } interface PreviewImage { url: string; src: string; width: number; height: number; }