threepipe
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

LoadingScreenPlugin.ts 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import {createDiv, createStyles, onChange, serialize, timeout} from 'ts-browser-helpers'
  2. import styles from './LoadingScreenPlugin.css?inline'
  3. import spinner1 from './loaders/spinner1.css?inline'
  4. import {uiButton, uiDropdown, uiFolderContainer, uiInput, uiSlider, uiToggle} from 'uiconfig.js'
  5. import {AAssetManagerProcessStatePlugin} from '../base/AAssetManagerProcessStatePlugin'
  6. import {ThreeViewer} from '../../viewer'
  7. /**
  8. * Loading Screen Plugin
  9. *
  10. * Shows a configurable loading screen overlay over the canvas.
  11. *
  12. * @category Plugins
  13. */
  14. @uiFolderContainer('Loading Screen')
  15. export class LoadingScreenPlugin extends AAssetManagerProcessStatePlugin {
  16. public static readonly PluginType = 'LoadingScreenPlugin'
  17. styles = styles
  18. spinners = [{
  19. styles: spinner1,
  20. html: '<span class="loader"></span>',
  21. }]
  22. refresh() {
  23. if (!this._viewer) return
  24. this._updateMainDiv(this._isPreviewing ? this._previewState : this._viewer.assetManager.processState, false)
  25. }
  26. @uiDropdown('Loader', ['Spinner 1'].map((v, i) => ({value: i, label: v})))
  27. @serialize() loader = 0
  28. @uiInput('Loading text header')
  29. @onChange(LoadingScreenPlugin.prototype.refresh)
  30. @serialize() loadingTextHeader = 'Loading Files'
  31. @uiInput('Error text header')
  32. @serialize() errorTextHeader = 'Error Loading Files'
  33. @uiToggle('Show file names')
  34. @onChange(LoadingScreenPlugin.prototype.refresh)
  35. @serialize() showFileNames = true
  36. @uiToggle('Show process states')
  37. @onChange(LoadingScreenPlugin.prototype.refresh)
  38. @serialize() showProcessStates = true
  39. @uiToggle('Show progress')
  40. @onChange(LoadingScreenPlugin.prototype.refresh)
  41. @serialize() showProgress = true
  42. @uiToggle('Hide on only errors')
  43. @serialize() hideOnOnlyErrors = true
  44. @uiToggle('Hide on files load')
  45. @serialize() hideOnFilesLoad = true
  46. @uiToggle('Hide on scene object load')
  47. @serialize() hideOnSceneObjectLoad = false
  48. /**
  49. * Minimize when scene has objects
  50. * Note: also checks for scene.environment and doesnt minimize when environment is null or undefined
  51. * @default true
  52. */
  53. @uiToggle('Minimize on scene object load')
  54. @serialize() minimizeOnSceneObjectLoad = true
  55. @uiToggle('Show when files start loading')
  56. @serialize() showOnFilesLoading = true
  57. @uiToggle('Show when scene empty')
  58. @serialize() showOnSceneEmpty = true
  59. @uiInput('Hide delay (ms)')
  60. @serialize() hideDelay = 500
  61. @uiSlider('Background Opacity', [0, 1])
  62. @onChange(LoadingScreenPlugin.prototype.refresh)
  63. @serialize() backgroundOpacity = 0.5
  64. @uiSlider('Background Blur', [0, 100])
  65. @onChange(LoadingScreenPlugin.prototype.refresh)
  66. @serialize() backgroundBlur = 24
  67. @uiInput('Background Color')
  68. @onChange(LoadingScreenPlugin.prototype.refresh)
  69. @serialize() background = '#ffffff'
  70. @uiInput('Text Color')
  71. @onChange(LoadingScreenPlugin.prototype.refresh)
  72. @serialize() textColor = '#222222'
  73. @uiInput('Logo Image')
  74. @onChange(LoadingScreenPlugin.prototype.refresh)
  75. @serialize() logoImage = 'https://static.webgi.xyz/logo.svg'
  76. private _isPreviewing = false
  77. private _previewState = new Map([['file.glb', {state: 'downloading', progress: 50}], ['environment.hdr', {state: 'adding'}]])
  78. @uiButton('Toggle preview')
  79. togglePreview() {
  80. this.maximize()
  81. this._isPreviewing = !this._isPreviewing
  82. this.refresh()
  83. if (this._isPreviewing)
  84. this.show()
  85. else
  86. this.hideWithDelay()
  87. }
  88. loadingElement = createDiv({classList: ['loadingScreenLoadingElement'], addToBody: false})
  89. filesElement = createDiv({classList: ['loadingScreenFilesElement'], addToBody: false})
  90. logoElement = createDiv({classList: ['loadingScreenLogoElement'], addToBody: false})
  91. constructor(container?: HTMLElement) {
  92. super('LoadingScreen', container)
  93. // const popupClose = createDiv({
  94. // id: 'assetManagerLoadingScreenClose',
  95. // addToBody: false,
  96. // innerHTML: '&#10005',
  97. // })
  98. // popupClose.addEventListener('click', () => {
  99. // this._mainDiv.style.display = 'none'
  100. // })
  101. // this._mainDiv.appendChild(popupClose)
  102. this._mainDiv.prepend(this.loadingElement)
  103. this._mainDiv.prepend(this.logoElement)
  104. this._mainDiv.appendChild(this.filesElement)
  105. }
  106. private _isHidden = false
  107. async hide() {
  108. this._isHidden = true
  109. this._mainDiv.style.opacity = '0'
  110. await timeout(502)
  111. if (this._isHidden) {
  112. this._mainDiv.style.display = 'none'
  113. this._showMainDiv()
  114. }
  115. }
  116. async hideWithDelay() {
  117. this._isHidden = true
  118. await timeout(this.hideDelay)
  119. if (!this._isHidden) return
  120. return this.hide()
  121. }
  122. show() {
  123. if (!this._isHidden) return
  124. this._isHidden = false
  125. this._showMainDiv()
  126. this._mainDiv.style.display = 'flex'
  127. }
  128. protected _showMainDiv() {
  129. // this._mainDiv.style.opacity = this.opacity.toString()
  130. this._mainDiv.style.opacity = '1'
  131. }
  132. @uiButton('Minimize')
  133. minimize() {
  134. this._mainDiv.classList.add('minimizedLoadingScreen')
  135. if (!this.showFileNames) this.loadingElement.style.display = 'block'
  136. }
  137. @uiButton('Maximize')
  138. maximize() {
  139. this._mainDiv.classList.remove('minimizedLoadingScreen')
  140. this.loadingElement.style.display = ''
  141. }
  142. private _setHTML(elem: HTMLElement, html:string) {
  143. if (elem.innerHTML !== html) elem.innerHTML = html
  144. }
  145. protected _updateMainDiv(processState: Map<string, {state: string, progress?: number|undefined}>, updateVisibility = true) {
  146. if (!this._viewer) return
  147. if (!this._contentDiv) return
  148. if (!this.enabled) {
  149. this._mainDiv.style.display = 'none'
  150. return
  151. }
  152. if (this.showFileNames) {
  153. let text = ''
  154. processState.forEach((v, k) => {
  155. text += (this.showProcessStates ? `<span class="loadingScreenProcessState">${v.state}</span>: ` : '') +
  156. (k || '').split('/').pop() +
  157. (this.showProgress && v.progress ? ' - ' + (v.progress.toFixed(0) + '%') : '') +
  158. '<br>'
  159. })
  160. this._setHTML(this.filesElement, text)
  161. } else {
  162. this._setHTML(this.filesElement, '')
  163. }
  164. const errors = [...processState.values()].filter(v => v.state === 'error')
  165. if (errors.length > 0 && errors.length === processState.size && !this.hideOnOnlyErrors) {
  166. this._setHTML(this._contentDiv, this.errorTextHeader)
  167. } else {
  168. this._setHTML(this._contentDiv, this.loadingTextHeader)
  169. }
  170. this._setHTML(this.loadingElement, this.spinners[this.loader].html)
  171. this._mainDiv.style.setProperty('--b-opacity', this.backgroundOpacity.toString())
  172. this._mainDiv.style.setProperty('--b-background', this.background)
  173. ;(this._mainDiv.style as any).backdropFilter = `blur(${this.backgroundBlur}px)`
  174. this._mainDiv.style.color = this.textColor
  175. this._setHTML(this.logoElement, this.logoImage ? `<img class="loadingScreenLogoImage" src="${this.logoImage}"/>` : '')
  176. if (updateVisibility) {
  177. if (this.hideOnFilesLoad && (processState.size === 0 ||
  178. errors.length === processState.size && this.hideOnOnlyErrors)) {
  179. this.hideDelay ? this.hideWithDelay() : this.hide()
  180. } else if (processState.size > 0 && this.showOnFilesLoading) {
  181. this.show()
  182. }
  183. }
  184. }
  185. private _sceneUpdate = (e: any) => {
  186. if (!this._viewer) return
  187. if (!e.hierarchyChanged) return
  188. const sceneObjects = this._viewer.scene.modelRoot.children
  189. if (sceneObjects.length === 0 && this.showOnSceneEmpty) {
  190. this.show()
  191. }
  192. // console.log(sceneObjects.length)
  193. if (sceneObjects.length > 0) {
  194. if (this.hideOnSceneObjectLoad)
  195. this.hideWithDelay()
  196. else if (this.minimizeOnSceneObjectLoad && this._viewer.scene.environment)
  197. timeout(this.hideDelay + 300).then(()=>this.minimize())
  198. } else if (this.minimizeOnSceneObjectLoad)
  199. this.maximize()
  200. }
  201. stylesheet?: HTMLStyleElement
  202. stylesheetLoader?: HTMLStyleElement[]
  203. onAdded(viewer: ThreeViewer) {
  204. this.stylesheet = createStyles(this.styles, viewer.container)
  205. this.stylesheetLoader = this.spinners.map(s => createStyles(s.styles, viewer.container))
  206. viewer.scene.addEventListener('sceneUpdate', this._sceneUpdate)
  207. super.onAdded(viewer)
  208. }
  209. onRemove(viewer: ThreeViewer) {
  210. viewer.scene.removeEventListener('sceneUpdate', this._sceneUpdate)
  211. this.stylesheet?.remove()
  212. this.stylesheet = undefined
  213. this.stylesheetLoader?.forEach(s => s.remove())
  214. this.stylesheetLoader = undefined
  215. return super.onRemove(viewer)
  216. }
  217. }