Browse Source

Add undo for image context menu

master
Palash Bansal 11 months ago
parent
commit
7817809bf6
No account linked to committer's email address
1 changed files with 41 additions and 17 deletions
  1. 41
    17
      plugins/tweakpane/src/tpImageInputGenerator.ts

+ 41
- 17
plugins/tweakpane/src/tpImageInputGenerator.ts View File

upgradeTexture, upgradeTexture,
WebGLRenderTarget, WebGLRenderTarget,
SVGTextureLoader, SVGTextureLoader,
uploadFile,
} from 'threepipe' } from 'threepipe'
import type {UiObjectConfig} from 'uiconfig.js' import type {UiObjectConfig} from 'uiconfig.js'
import {TweakpaneUiPlugin} from './TweakpaneUiPlugin' import {TweakpaneUiPlugin} from './TweakpaneUiPlugin'
tempMap: {} as any, tempMap: {} as any,
} }


const allowedImageExtensions = ['.jpg', '.png', '.svg', '.hdr', '.ktx2',
'.exr', /* '.mp4', '.ogg', '.mov',*/ '.jpeg',
'.bmp', '.gif', '.webp', '.cube', '.ktx2', '.avif', '.ico', '.tiff'] // todo update blueprint editor with this list

function proxyGetValue(cc: any, viewer: ThreeViewer, config: UiObjectConfig) { function proxyGetValue(cc: any, viewer: ThreeViewer, config: UiObjectConfig) {
if (cc?.get) cc = cc.get() if (cc?.get) cc = cc.get()
let ret = staticData.placeholderVal let ret = staticData.placeholderVal
config.uiRefresh?.(false, 'postFrame') config.uiRefresh?.(false, 'postFrame')
} }


function setterFile(viewer: ThreeViewer, file: File, path: string | undefined, config: UiObjectConfig, renderer: TweakpaneUiPlugin) {
path = path || file.webkitRelativePath || file.name
viewer.assetManager.importer.importSingle<ITexture>({file, path: path}).then(texture => {
if (!texture) {
console.warn('Failed to load texture', file)
return
}
const ext = path?.split('?')?.[0]?.split('.').pop() ?? ''
if ((texture as any).userData) { // todo why is this required?
if (!(texture as any).userData.mimeType)
(texture as any).userData.mimeType = 'image/' + (['jpg', 'jpeg'].includes(ext) ? 'jpeg' : 'png')
}
setterTex(texture, config, renderer)
})
}

function proxySetValue(v: any, cc: any, config: UiObjectConfig, viewer: ThreeViewer, renderer: TweakpaneUiPlugin) { function proxySetValue(v: any, cc: any, config: UiObjectConfig, viewer: ThreeViewer, renderer: TweakpaneUiPlugin) {
if (typeof v === 'string') { if (typeof v === 'string') {
if (typeof cc === 'string') setterTex(v, config, renderer) if (typeof cc === 'string') setterTex(v, config, renderer)
)) return )) return


if (v instanceof File) { // v.src must be from createObjectURL. if (v instanceof File) { // v.src must be from createObjectURL.
viewer.assetManager.importer.importSingle<ITexture>({file: v, path: (v as any).src}).then(texture => {
if (!texture) return
if (texture.isDataTexture) texture.needsUpdate = true
const ext = (v as any).src?.split('?')?.[0]?.split('.').pop()
if ((texture as any).userData) {
if (!(texture as any).userData.mimeType)
(texture as any).userData.mimeType = 'image/' + (['jpg', 'jpeg'].includes(ext) ? 'jpeg' : 'png')
}
setterTex(texture, config, renderer)
})
setterFile(viewer, v, (v as any).src, config, renderer)
} else if (v.isTexture) { } else if (v.isTexture) {
setterTex(v, config, renderer) setterTex(v, config, renderer)
} else { // HTMLImageElement, ImageBitmap, HTMLVideoElement } else { // HTMLImageElement, ImageBitmap, HTMLVideoElement
tex.assetType = 'texture' tex.assetType = 'texture'
tex.needsUpdate = true tex.needsUpdate = true
// set userData.mimeType for GLTFExporter // set userData.mimeType for GLTFExporter
const ext = v.src?.split('?')?.[0]?.split('.').pop()
const ext = v.src?.split('?')?.[0]?.split('.').pop() ?? ''
if (!tex.userData.mimeType) if (!tex.userData.mimeType)
tex.userData.mimeType = 'image/' + (['jpg', 'jpeg'].includes(ext) ? 'jpeg' : 'png') tex.userData.mimeType = 'image/' + (['jpg', 'jpeg'].includes(ext) ? 'jpeg' : 'png')
setterTex(tex, config, renderer) setterTex(tex, config, renderer)
} else { } else {
url = url.trim() url = url.trim()
} }
const cc = config.__proxy.value_
const isStr = typeof cc === 'string'
const last = config.__proxy.value_
const isStr = typeof last === 'string'
if (isStr) { if (isStr) {
setterTex(url, config, renderer) setterTex(url, config, renderer)
} else { // texture } else { // texture
} }
} }


async function imageFromFile(renderer: TweakpaneUiPlugin, config: UiObjectConfig, viewer: ThreeViewer, inp: HTMLInputElement, params: any) {
const last = config.__proxy.value_
const isStr = typeof last === 'string'
if (isStr) {
inp.click()
return
}
const files = await uploadFile(false, false, params.extensions?.map((ext: string) => `image/${ext.replace(/^\./, '')}`).join(', ') ?? 'image/*')
if (!files.length) return
const file = files[0]

setterFile(viewer, file, undefined, config, renderer)
}

export const tpImageInputGenerator: (viewer: ThreeViewer) => (parent: any, config: UiObjectConfig, renderer: TweakpaneUiPlugin, params?: any) => any = (viewer: ThreeViewer) => (parent: any /* FolderApi */, config: UiObjectConfig, renderer: TweakpaneUiPlugin, params?: any) => { export const tpImageInputGenerator: (viewer: ThreeViewer) => (parent: any, config: UiObjectConfig, renderer: TweakpaneUiPlugin, params?: any) => any = (viewer: ThreeViewer) => (parent: any /* FolderApi */, config: UiObjectConfig, renderer: TweakpaneUiPlugin, params?: any) => {
// if (config.value !== undefined) throw 'Not supported yet' // if (config.value !== undefined) throw 'Not supported yet'


config.__proxy.value_ = renderer.methods.getRawValue(config) config.__proxy.value_ = renderer.methods.getRawValue(config)


params = params ?? {} params = params ?? {}
params.extensions = ['.jpg', '.png', '.svg', '.hdr', '.ktx2',
'.exr', /* '.mp4', '.ogg', '.mov',*/ '.jpeg',
'.bmp', '.gif', '.webp', '.cube', '.ktx2', '.avif', '.ico', '.tiff'] // todo update blueprint editor with this list
params.extensions = allowedImageExtensions
if (typeof params.imageFit === 'undefined') params.imageFit = 'contain' if (typeof params.imageFit === 'undefined') params.imageFit = 'contain'
if (typeof params.clickCallback === 'undefined') params.clickCallback = (ev: MouseEvent, inp: HTMLInputElement) => { if (typeof params.clickCallback === 'undefined') params.clickCallback = (ev: MouseEvent, inp: HTMLInputElement) => {
const target = ev?.target as HTMLElement const target = ev?.target as HTMLElement
['remove image']: () => removeImage(config, renderer), ['remove image']: () => removeImage(config, renderer),
}) })
if (!readOnly) Object.assign(items, { if (!readOnly) Object.assign(items, {
['set/replace image']: () => inp.click(),
['set/replace image']: async() => imageFromFile(renderer, config, viewer, inp, params),
['from url']: async() => imageFromUrl(renderer, config, viewer), ['from url']: async() => imageFromUrl(renderer, config, viewer),
}) })
const menu = CustomContextMenu.Create({ const menu = CustomContextMenu.Create({

Loading…
Cancel
Save