Files
manyfold/app/javascript/src/object_preview.ts
T

138 lines
4.3 KiB
TypeScript

import * as Comlink from 'comlink'
import 'src/comlink_event_handler'
import type { OffscreenRenderer } from 'offscreen_renderer'
export class ObjectPreview {
progressBar: HTMLDivElement | null
progressLabel: HTMLSpanElement | null
canvas: HTMLCanvasElement
renderer: any
observer: IntersectionObserver | null
loading: boolean = false
constructor (canvas) {
this.canvas = canvas
this.progressBar = this.canvas.parentElement?.getElementsByClassName('progress-bar')[0] as HTMLDivElement
this.progressLabel = this.canvas.parentElement?.getElementsByClassName('progress-label')[0] as HTMLSpanElement
}
async initializeOffscreenRenderer (): Promise<void> {
if (this.canvas.dataset.workerUrl === undefined || this.canvas.dataset.workerUrl === null) {
console.log('ERROR: Could not load worker!')
return
}
// Create offscreen renderer worker
const offscreenCanvas = this.canvas.transferControlToOffscreen()
const RemoteOffscreenRenderer = await Comlink.wrap<typeof OffscreenRenderer>(
new Worker(this.canvas.dataset.workerUrl, { type: 'module' })
)
this.renderer = await new RemoteOffscreenRenderer(
Comlink.transfer(offscreenCanvas as unknown as HTMLCanvasElement, [offscreenCanvas]), { ...this.canvas.dataset }
)
// Trigger resizing
this.onResize()
}
connect (): void {
// Handle resize events
window.addEventListener('resize', this.onResize.bind(this))
this.onResize()
// Handle interaction events
const pointerEvents = ['pointerdown', 'pointermove', 'pointerup']
pointerEvents.forEach((eventName) => {
this.canvas.addEventListener(eventName, this.onPointerEvent.bind(this))
})
const keyEvents = ['keydown', 'keyup']
keyEvents.forEach((eventName) => {
this.canvas.addEventListener(eventName, this.onKeyEvent.bind(this))
})
const otherEvents = ['wheel', 'contextmenu']
otherEvents.forEach((eventName) => {
this.canvas.addEventListener(eventName, this.onEvent.bind(this))
})
// Monitor visibility
this.observer = new window.IntersectionObserver(
this.onIntersectionChanged.bind(this), {}
)
this.observer.observe(this.canvas)
// Monitor load button click
const loadButton = this.canvas.parentElement?.getElementsByClassName('object-preview-progress')[0] as HTMLDivElement
loadButton.addEventListener('click', this.load.bind(this))
}
onIntersectionChanged (entries, observer): void {
if ((this.canvas.dataset.autoLoad === 'true') && (entries[0].isIntersecting === true)) {
void this.load()
}
}
onPointerEvent (event): void {
if (event.type === 'pointerdown') {
this.canvas.focus()
this.canvas.setPointerCapture(event.pointerId)
}
this.onEvent(event)
}
onKeyEvent (event): void {
if ([
'ArrowUp',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'Minus',
'Equal'
].includes(event.code)) {
this.onEvent(event)
}
}
onEvent (event): void {
event.preventDefault()
this.renderer?.handleEvent(event)
}
onLoadProgress (percentage: number): void {
if ((this.progressBar == null) || (this.progressLabel == null)) { return }
if (percentage === 100) {
this.progressLabel.textContent = window.i18n.t('renderer.processing')
} else {
this.progressLabel.textContent = `${percentage}%`
}
this.progressBar.style.width = `${percentage}%`
this.progressBar.ariaValueNow = percentage.toString()
}
onLoad (): void {
this.progressBar?.parentElement?.remove()
this.progressBar = null
this.progressLabel = null
}
onLoadError (): void {
if ((this.progressBar == null) || (this.progressLabel == null)) { return }
this.progressBar.classList.add('bg-danger')
this.progressBar.style.width = this.progressBar.ariaValueNow = '100%'
this.progressLabel.textContent = window.i18n.t('renderer.errors.load')
}
onResize (): void {
this.renderer?.onResize(
this.canvas.clientWidth,
this.canvas.clientHeight,
window.devicePixelRatio
)
}
async load (): Promise<void> {
if (this.loading) { return }
this.loading = true
await this.initializeOffscreenRenderer()
this.renderer.load(
Comlink.proxy(this.onLoad.bind(this)),
Comlink.proxy(this.onLoadProgress.bind(this)),
Comlink.proxy(this.onLoadError.bind(this))
)
}
}