|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- import * as TweakpaneImagePlugin from 'tweakpane-image-plugin'
- import {UiConfigRendererTweakpane} from 'uiconfig-tweakpane'
- import {
- Class,
- Color,
- createDiv,
- createStyles,
- CustomContextMenu,
- downloadBlob,
- getOrCall,
- IEvent,
- IViewerPlugin,
- IViewerPluginSync,
- onChange,
- ThreeViewer,
- uiDropdown,
- uiFolderContainer,
- UiObjectConfig,
- uploadFile,
- Vector2,
- Vector3,
- Vector4, UndoManagerPlugin,
- } from 'threepipe'
- import styles from './tpTheme.css?inline'
- import {tpImageInputGenerator} from './tpImageInputGenerator'
-
- @uiFolderContainer('Tweakpane UI')
- export class TweakpaneUiPlugin extends UiConfigRendererTweakpane implements IViewerPluginSync {
- declare ['constructor']: typeof TweakpaneUiPlugin
- static readonly PluginType = 'TweakpaneUi'
- enabled = true
-
- static CONTAINER_SLOT = 'uiconfigMainPanelSlot'
-
- @onChange(TweakpaneUiPlugin.prototype._colorModeChanged)
- @uiDropdown('Color Mode', ['black', 'white', 'blue'].map(label=>({label})))
- colorMode: 'black'|'white'|'blue'
-
- constructor(expanded = false, bigTheme = true, container?: HTMLElement, colorMode?: 'black'|'white'|'blue') {
- super(container ?? document.getElementById(TweakpaneUiPlugin.CONTAINER_SLOT) ?? document.getElementById('tweakpaneMainPanelSlot') ?? document.body, {
- expanded, autoPostFrame: false,
- }, false)
- this.THREE = {Color, Vector4, Vector3, Vector2} as any
- this._root!.registerPlugin(TweakpaneImagePlugin as any)
- if (bigTheme) createStyles(styles, container)
- this.colorMode = colorMode ?? (localStorage ? localStorage.getItem('tpTheme') as any : 'blue') ?? 'blue'
- }
-
- protected _viewer?: ThreeViewer
-
- private _lastManager?: UndoManagerPlugin['undoManager']
-
- onAdded(viewer: ThreeViewer): void {
- this._viewer = viewer
- this.typeGenerators.image = tpImageInputGenerator(this._viewer)
- viewer.addEventListener('preRender', this._preRender)
- viewer.addEventListener('postRender', this._postRender)
- viewer.addEventListener('preFrame', this._preFrame)
- viewer.addEventListener('postFrame', this._postFrame)
- const undo = viewer.getOrAddPluginSync(UndoManagerPlugin) // yes, manual dependency
- if (undo?.undoManager) {
- this._lastManager?.dispose()
- this._lastManager = this.undoManager
- this.undoManager = undo.undoManager
- if (this._lastManager) Object.assign(this.undoManager.presets, this._lastManager.presets)
- }
- }
-
- onRemove(viewer: ThreeViewer): void {
- this._viewer = undefined
- viewer.removeEventListener('preRender', this._preRender)
- viewer.removeEventListener('postRender', this._postRender)
- viewer.removeEventListener('preFrame', this._preFrame)
- viewer.removeEventListener('postFrame', this._postFrame)
- this.undoManager = this._lastManager
- this._lastManager = undefined
- this.dispose()
- }
-
- private _plugins: IViewerPlugin[] = []
-
- setupPlugins(...plugins: Class<IViewerPlugin>[]): void {
- plugins.forEach(plugin => this.setupPluginUi(plugin))
- }
-
- setupPluginUi<T extends IViewerPlugin>(plugin: T|Class<T>, params?: Partial<UiObjectConfig>): UiObjectConfig | undefined {
- const p = (<Class<IViewerPlugin>>plugin).prototype ? this._viewer?.getPlugin<T>(<Class<T>>plugin) : <T>plugin
- if (!p) {
- console.warn('plugin not found:', plugin)
- return undefined
- }
- this._plugins.push(p)
- if (p.uiConfig && p.uiConfig.hidden === undefined) p.uiConfig.hidden = false // todo; this is a hack for now
- const ui = p.uiConfig
- this.appendChild(ui, params)
- this._setupPluginSerializationContext(ui, p)
- return ui
- }
-
- private _setupPluginSerializationContext(ui: any, p: IViewerPlugin) {
- // serialization
- if (!(ui?.uiRef && p.toJSON)) return;
-
- (p as any)._defaultState = typeof p.toJSON === 'function' ? p.toJSON() : null
- ;(p as any).resetDefaults = async() => {
- if (!(p as any)._defaultState) return
- await p.fromJSON?.((p as any)._defaultState)
- ui.uiRefresh?.(true, 'postFrame')
- }
- const topBtn = (ui.uiRef as any).controller_.view.element
- const opBtn = createDiv({
- innerHTML: '⋮',
- classList: ['pluginOptionsButton'],
- elementTag: 'button',
- })
- opBtn.onclick = (ev) => {
- const ops = {} as any
- if (typeof p.toJSON === 'function') {
- ops['download preset'] = async() => {
- if (!this._viewer) return
- const json = this._viewer.exportPluginConfig(p)
- await downloadBlob(new Blob([JSON.stringify(json, null, 2)], {type: 'application/json'}), 'preset.' + (p.constructor as any).PluginType + '.json')
- }
- }
- if (typeof p.fromJSON === 'function') {
- ops['upload preset'] = async() => {
- const files = await uploadFile(false, false)
- if (files.length === 0) return
- const file = files[0]
- const text = await file.text()
- const json = JSON.parse(text)
- await this._viewer?.importPluginConfig(json, p)
- ui.uiRefresh?.(true, 'postFrame')
- }
- if ((p as any)._defaultState) ops['reset defaults'] = () => (p as any).resetDefaults?.()
- }
- const menu = CustomContextMenu.Create(ops, topBtn.clientWidth - 120, 12)
- topBtn.append(menu)
- ev.preventDefault()
- }
- topBtn.appendChild(opBtn)
- }
-
- refreshPluginsEnabled() {
- this._plugins.forEach(p=>{
- const config = p.uiConfig
- if (config) {
- // const enabled = (p as any).enabled ?? true
- // safeSetProperty(config, 'hidden', !enabled, true)
- // if (config.expanded)
- // safeSetProperty(config, 'expanded', config.expanded && enabled, true)
- if (getOrCall(config.hidden) !== true)
- config.uiRefresh?.(true, 'postFrame')
- else if (config.uiRef) {
- config.uiRef.hidden = true
- }
- }
- })
- }
-
- private _preRender = () => this.refreshQueue('preRender')
- private _postRender = () => this.refreshQueue('postRender')
- private _postFrame = (e: IEvent<'postFrame'>) => {
- this.dispatchEvent(e)
- this.refreshQueue('postFrame')
- }
- private _preFrame = () => this.refreshQueue('preFrame')
-
- alert = async(message?: string): Promise<void> =>this._viewer ? this._viewer.dialog.alert(message) : window?.alert(message)
- confirm = async(message?: string): Promise<boolean> =>this._viewer ? this._viewer.dialog.confirm(message) : window?.confirm(message)
- prompt = async(message?: string, _default?: string, cancel = true): Promise<string | null> =>this._viewer ? this._viewer.dialog.prompt(message, _default, cancel) : window?.prompt(message, _default)
-
- protected _colorModeChanged() {
- document.body.classList.remove('tpTheme-black', 'tpTheme-white', 'tpTheme-blue')
- document.body.classList.add('tpTheme-' + this.colorMode)
- if (!localStorage) return
- localStorage.setItem('tpTheme', this.colorMode)
- }
-
- dispose() {
- this.undoManager?.dispose()
- this.unmount()
- }
-
- }
|