threepipe
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

TweakpaneUiPlugin.ts 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import * as TweakpaneImagePlugin from 'tweakpane-image-plugin'
  2. import {UiConfigRendererTweakpane} from 'uiconfig-tweakpane'
  3. import {
  4. Class,
  5. Color,
  6. createDiv,
  7. createStyles,
  8. CustomContextMenu,
  9. downloadBlob,
  10. getOrCall,
  11. IEvent,
  12. IViewerPlugin,
  13. IViewerPluginSync,
  14. ThreeViewer,
  15. uploadFile,
  16. Vector2,
  17. Vector3,
  18. Vector4,
  19. } from 'threepipe'
  20. import styles from './tpTheme.css'
  21. import {UiObjectConfig} from 'uiconfig.js'
  22. import {tpImageInputGenerator} from './tpImageInputGenerator'
  23. export class TweakpaneUiPlugin extends UiConfigRendererTweakpane implements IViewerPluginSync {
  24. declare ['constructor']: typeof TweakpaneUiPlugin
  25. static readonly PluginType = 'TweakpaneUi'
  26. enabled = true
  27. constructor(expanded = false, bigTheme = true, container: HTMLElement = document.body) {
  28. super(container, {
  29. expanded, autoPostFrame: false,
  30. })
  31. this.THREE = {Color, Vector4, Vector3, Vector2} as any
  32. this._root!.registerPlugin(TweakpaneImagePlugin)
  33. if (bigTheme) createStyles(styles, container)
  34. }
  35. protected _viewer?: ThreeViewer
  36. onAdded(viewer: ThreeViewer): void {
  37. this._viewer = viewer
  38. this.typeGenerators.image = tpImageInputGenerator(this._viewer)
  39. viewer.addEventListener('preRender', this._preRender)
  40. viewer.addEventListener('postRender', this._postRender)
  41. viewer.addEventListener('preFrame', this._preFrame)
  42. viewer.addEventListener('postFrame', this._postFrame)
  43. }
  44. onRemove(viewer: ThreeViewer): void {
  45. this._viewer = undefined
  46. viewer.removeEventListener('preRender', this._preRender)
  47. viewer.removeEventListener('postRender', this._postRender)
  48. viewer.removeEventListener('preFrame', this._preFrame)
  49. viewer.removeEventListener('postFrame', this._postFrame)
  50. this.dispose()
  51. }
  52. private _plugins: IViewerPlugin[] = []
  53. setupPlugins(...plugins: Class<IViewerPlugin>[]): void {
  54. plugins.forEach(plugin => this.setupPluginUi(plugin))
  55. }
  56. setupPluginUi<T extends IViewerPlugin>(plugin: T|Class<T>): UiObjectConfig | undefined {
  57. const p = (<Class<IViewerPlugin>>plugin).prototype ? this._viewer?.getPlugin<T>(<Class<T>>plugin) : <T>plugin
  58. if (!p) {
  59. console.warn('plugin not found:', plugin)
  60. return undefined
  61. }
  62. this._plugins.push(p)
  63. if (p.uiConfig && p.uiConfig.hidden === undefined) p.uiConfig.hidden = false // todo; this is a hack for now
  64. const ui = p.uiConfig
  65. this.appendChild(ui)
  66. this._setupPluginSerializationContext(ui, p)
  67. return ui
  68. }
  69. private _setupPluginSerializationContext(ui: any, p: IViewerPlugin) {
  70. // serialization
  71. if (!(ui?.uiRef && p.toJSON)) return;
  72. (p as any)._defaultState = typeof p.toJSON === 'function' ? p.toJSON() : null
  73. ;(p as any).resetDefaults = async() => {
  74. if (!(p as any)._defaultState) return
  75. await p.fromJSON?.((p as any)._defaultState)
  76. ui.uiRefresh?.(true, 'postFrame')
  77. }
  78. const topBtn = (ui.uiRef as any).controller_.view.element
  79. const opBtn = createDiv({
  80. innerHTML: '&#8942;',
  81. classList: ['pluginOptionsButton'],
  82. elementTag: 'button',
  83. })
  84. opBtn.onclick = (ev) => {
  85. const ops = {} as any
  86. if (typeof p.toJSON === 'function') {
  87. ops['download preset'] = async() => {
  88. if (!this._viewer) return
  89. const json = this._viewer.exportPluginConfig(p)
  90. await downloadBlob(new Blob([JSON.stringify(json, null, 2)], {type: 'application/json'}), 'preset.' + (p.constructor as any).PluginType + '.json')
  91. }
  92. }
  93. if (typeof p.fromJSON === 'function') {
  94. ops['upload preset'] = async() => {
  95. const files = await uploadFile(false, false)
  96. if (files.length === 0) return
  97. const file = files[0]
  98. const text = await file.text()
  99. const json = JSON.parse(text)
  100. await this._viewer?.importPluginConfig(json, p)
  101. ui.uiRefresh?.(true, 'postFrame')
  102. }
  103. if ((p as any)._defaultState) ops['reset defaults'] = () => (p as any).resetDefaults?.()
  104. }
  105. const menu = CustomContextMenu.Create(ops, topBtn.clientWidth - 120, 12)
  106. topBtn.append(menu)
  107. ev.preventDefault()
  108. }
  109. topBtn.appendChild(opBtn)
  110. }
  111. refreshPluginsEnabled() {
  112. this._plugins.forEach(p=>{
  113. const config = p.uiConfig
  114. if (config) {
  115. // const enabled = (p as any).enabled ?? true
  116. // safeSetProperty(config, 'hidden', !enabled, true)
  117. // if (config.expanded)
  118. // safeSetProperty(config, 'expanded', config.expanded && enabled, true)
  119. if (getOrCall(config.hidden) !== true)
  120. config.uiRefresh?.(true, 'postFrame')
  121. else if (config.uiRef) {
  122. config.uiRef.hidden = true
  123. }
  124. }
  125. })
  126. }
  127. private _preRender = () => this.refreshQueue('preRender')
  128. private _postRender = () => this.refreshQueue('postRender')
  129. private _postFrame = (e: IEvent<'postFrame'>) => {
  130. this.dispatchEvent(e)
  131. this.refreshQueue('postFrame')
  132. }
  133. private _preFrame = () => this.refreshQueue('preFrame')
  134. alert = async(message?: string): Promise<void> =>this._viewer ? this._viewer.dialog.alert(message) : window?.alert(message)
  135. confirm = async(message?: string): Promise<boolean> =>this._viewer ? this._viewer.dialog.confirm(message) : window?.confirm(message)
  136. prompt = async(message?: string, _default?: string, cancel = true): Promise<string | null> =>this._viewer ? this._viewer.dialog.prompt(message, _default, cancel) : window?.prompt(message, _default)
  137. }