threepipe
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

DropzonePlugin.ts 5.2KB

2 лет назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import {type ThreeViewer} from '../../viewer/'
  2. // noinspection ES6PreferShortImport
  3. import {AViewerPluginSync} from '../../viewer/AViewerPlugin'
  4. import {Dropzone} from '../../utils'
  5. import {uiButton, uiConfig, uiFolderContainer, UiObjectConfig, uiToggle} from 'uiconfig.js'
  6. import type {AddAssetOptions, ImportFilesOptions, ImportResult} from '../../assetmanager'
  7. import {serialize} from 'ts-browser-helpers'
  8. export interface DropzonePluginOptions {
  9. domElement?: HTMLElement
  10. allowedExtensions?: string[]
  11. autoImport?: boolean
  12. autoAdd?: boolean
  13. importOptions?: ImportFilesOptions
  14. addOptions?: AddAssetOptions
  15. }
  16. /**
  17. * Dropzone Plugin
  18. *
  19. * Adds a dropzone to the viewer for importing assets.
  20. *
  21. * Automatically imports and adds assets to the scene, the behavior can be configured.
  22. * @category Plugins
  23. */
  24. @uiFolderContainer('Dropzone')
  25. export class DropzonePlugin extends AViewerPluginSync<'drop'> {
  26. static readonly PluginType = 'Dropzone'
  27. uiConfig!: UiObjectConfig
  28. @uiToggle() @serialize() enabled = true
  29. private _inputEl?: HTMLInputElement
  30. private _dropzone?: Dropzone
  31. private _allowedExtensions: string[]|undefined = undefined // undefined and empty array is different.
  32. /**
  33. * Automatically import assets when dropped.
  34. */
  35. @serialize() autoImport = true
  36. /**
  37. * Automatically add dropped and imported assets to the scene.
  38. * Works only if {@link autoImport} is true.
  39. */
  40. @uiToggle() @serialize() autoAdd = true
  41. /**
  42. * Import options for the {@link AssetImporter.importFiles}
  43. */
  44. @uiConfig() @serialize() importOptions: ImportFilesOptions = {
  45. autoImportZipContents: true,
  46. forceImporterReprocess: false,
  47. }
  48. /**
  49. * Add options for the {@link RootScene.addObject}
  50. */
  51. @uiConfig() @serialize() addOptions: AddAssetOptions = {
  52. autoCenter: true,
  53. importConfig: true,
  54. autoScale: true,
  55. autoScaleRadius: 2,
  56. centerGeometries: false, // in the whole hierarchy
  57. centerGeometriesKeepPosition: true, // this centers while keeping world position
  58. license: '',
  59. clearSceneObjects: false,
  60. disposeSceneObjects: false,
  61. autoSetBackground: false,
  62. autoSetEnvironment: true,
  63. }
  64. /**
  65. * Allowed file extensions. If undefined, all files are allowed.
  66. */
  67. get allowedExtensions(): string[] | undefined {
  68. return this._allowedExtensions
  69. }
  70. set allowedExtensions(value: string[] | undefined) {
  71. this._allowedExtensions = value
  72. if (this._inputEl) this._inputEl.accept = value ? value.map(v=>'.' + v).join(', ') : ''
  73. }
  74. /**
  75. * Prompt for file selection using the browser file dialog.
  76. */
  77. @uiButton('Select files')
  78. public promptForFile(): void {
  79. if (this.isDisabled()) return
  80. this.allowedExtensions = this._allowedExtensions
  81. this._inputEl?.click()
  82. }
  83. private _domElement?: HTMLElement
  84. constructor(options?: DropzonePluginOptions) {
  85. super()
  86. if (!options) return
  87. this._domElement = options.domElement
  88. this.allowedExtensions = options.allowedExtensions
  89. this.autoImport = options.autoImport ?? this.autoImport
  90. this.autoAdd = options.autoAdd ?? this.autoAdd
  91. this.importOptions = {...this.importOptions, ...options.importOptions}
  92. this.addOptions = {...this.addOptions, ...options.addOptions}
  93. }
  94. onAdded(viewer: ThreeViewer) {
  95. super.onAdded(viewer)
  96. this._inputEl = document.createElement('input')!
  97. this._inputEl.type = 'file'
  98. if (!this._domElement) this._domElement = viewer.canvas
  99. this._dropzone = new Dropzone(this._domElement, this._inputEl, {
  100. drop: this._onFileDrop.bind(this),
  101. })
  102. this.allowedExtensions = this._allowedExtensions
  103. }
  104. onRemove(viewer: ThreeViewer) {
  105. super.onRemove(viewer)
  106. this._dropzone?.destroy()
  107. this._dropzone = undefined
  108. this._inputEl = undefined
  109. }
  110. private async _onFileDrop({files, nativeEvent}: {files: Map<string, File>, nativeEvent: DragEvent}) {
  111. if (!files) return
  112. if (this.isDisabled()) return
  113. const viewer = this._viewer
  114. if (!viewer) return
  115. if (this._allowedExtensions !== undefined) {
  116. for (const file of files.keys()) {
  117. if (!this._allowedExtensions.includes(file.split('.').pop()?.toLowerCase() ?? '')) {
  118. files.delete(file)
  119. }
  120. }
  121. }
  122. if (files.size < 1) return
  123. const manager = viewer.assetManager
  124. let imported: Map<string, (ImportResult | undefined)[]>|undefined
  125. let assets: (ImportResult | undefined)[]|undefined
  126. if (this.autoImport) {
  127. imported = await manager.importer.importFiles(files, {
  128. allowedExtensions: this.allowedExtensions, ...this.importOptions,
  129. })
  130. if (this.autoAdd) {
  131. const toAdd = [...imported?.values() ?? []].flat(2).filter(v=>!!v) ?? []
  132. assets = await manager.loadImported(toAdd, {...this.addOptions})
  133. }
  134. }
  135. this.dispatchEvent({type: 'drop', files, imported, assets, nativeEvent})
  136. }
  137. }