diff --git a/src/extensions/cellpack/model.ts b/src/extensions/cellpack/model.ts index 31db029d681fb6640f9da7e7cde39ede5320d35a..4bf40d9f30bc97483015a51d9f24c13fff2d92c0 100644 --- a/src/extensions/cellpack/model.ts +++ b/src/extensions/cellpack/model.ts @@ -28,6 +28,7 @@ import { createModels } from '../../mol-model-formats/structure/basic/parser'; import { CellpackPackingPreset, CellpackMembranePreset } from './preset'; import { AjaxTask } from '../../mol-util/data-source'; import { CellPackInfoProvider } from './property'; +import { Asset } from '../../mol-util/assets'; function getCellPackModelUrl(fileName: string, baseUrl: string) { return `${baseUrl}/results/${fileName}`; @@ -392,7 +393,7 @@ async function loadMembrane(name: string, plugin: PluginContext, runtime: Runtim let b = state.build().toRoot(); if (fname in ingredientFiles) { const file = ingredientFiles[fname]; - b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } }); + b = b.apply(StateTransforms.Data.ReadFile, { file: Asset.File(file), isBinary: true, label: file.name }, { state: { isGhost: true } }); } else { const url = `${params.baseUrl}/membranes/${name}.bcif`; b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } }); diff --git a/src/extensions/rcsb/validation-report/prop.ts b/src/extensions/rcsb/validation-report/prop.ts index c57125afccd05a105b91b71a69a4240017679ca0..7e4fa8cfe279cee6eb566b94e6b8c1c187c18c47 100644 --- a/src/extensions/rcsb/validation-report/prop.ts +++ b/src/extensions/rcsb/validation-report/prop.ts @@ -111,8 +111,9 @@ namespace ValidationReport { } export async function open(ctx: CustomProperty.Context, model: Model, props: FileSourceProps): Promise<ValidationReport> { - if (props.input === null) throw new Error('No file given'); - const xml = await readFromFile(props.input, 'xml').runInContext(ctx.runtime); + // TODO: this should use the asset manager and release the file "somehow" + if (!(props.input?.file instanceof File)) throw new Error('No file given'); + const xml = await readFromFile(props.input.file, 'xml').runInContext(ctx.runtime); return fromXml(xml, model); } diff --git a/src/mol-plugin-state/actions/file.ts b/src/mol-plugin-state/actions/file.ts index 24de5f1f2f9c87fc5c6d926bac4a5063b2f86b4d..ecba4380c6ac0d69a527e92ee21f9045fe44047d 100644 --- a/src/mol-plugin-state/actions/file.ts +++ b/src/mol-plugin-state/actions/file.ts @@ -10,6 +10,7 @@ import { Task } from '../../mol-task'; import { getFileInfo } from '../../mol-util/file-info'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { PluginStateObject } from '../objects'; +import { Asset } from '../../mol-util/assets'; export const OpenFiles = StateAction.build({ display: { name: 'Open Files', description: 'Load one or more files and optionally create default visuals' }, @@ -35,7 +36,7 @@ export const OpenFiles = StateAction.build({ const file = params.files[i]; const info = getFileInfo(file); const isBinary = plugin.dataFormats.binaryExtensions.has(info.ext); - const { data } = await plugin.builders.data.readFile({ file, isBinary }); + const { data } = await plugin.builders.data.readFile({ file: Asset.File(file), isBinary }); const provider = params.format === 'auto' ? plugin.dataFormats.auto(info, data.cell?.obj!) : plugin.dataFormats.get(params.format); diff --git a/src/mol-plugin-state/builder/data.ts b/src/mol-plugin-state/builder/data.ts index e628bf7aa9be06d73c001a60a36d4b4aead3349a..16dec53c0615b67b0905c22e99970edb865874f2 100644 --- a/src/mol-plugin-state/builder/data.ts +++ b/src/mol-plugin-state/builder/data.ts @@ -31,7 +31,7 @@ export class DataBuilder { async readFile(params: StateTransformer.Params<ReadFile>, options?: Partial<StateTransform.Options>) { const data = await this.dataState.build().toRoot().apply(ReadFile, params, options).commit({ revertOnError: true }); - const fileInfo = getFileInfo(params.file || ''); + const fileInfo = getFileInfo(params.file?.file || ''); return { data: data, fileInfo }; } diff --git a/src/mol-plugin-state/transforms/data.ts b/src/mol-plugin-state/transforms/data.ts index 33173bb6304b4b83ed93d1b3bc57a6f5a66ef043..854b4bc82aa698b5cc0276c8ec04ede9770e2605 100644 --- a/src/mol-plugin-state/transforms/data.ts +++ b/src/mol-plugin-state/transforms/data.ts @@ -5,22 +5,33 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { PluginStateTransform } from '../objects'; -import { PluginStateObject as SO } from '../objects'; -import { Task } from '../../mol-task'; -import { CIF } from '../../mol-io/reader/cif'; -import { PluginContext } from '../../mol-plugin/context'; -import { ParamDefinition as PD } from '../../mol-util/param-definition'; -import { StateTransformer, StateObject } from '../../mol-state'; -import { readFromFile, ajaxGetMany } from '../../mol-util/data-source'; +import { isTypedArray } from '../../mol-data/db/column-helpers'; import * as CCP4 from '../../mol-io/reader/ccp4/parser'; +import { CIF } from '../../mol-io/reader/cif'; import * as DSN6 from '../../mol-io/reader/dsn6/parser'; import * as PLY from '../../mol-io/reader/ply/parser'; import { parsePsf } from '../../mol-io/reader/psf/parser'; -import { isTypedArray } from '../../mol-data/db/column-helpers'; -import { AssetManager } from '../../mol-plugin/util/asset-manager'; +import { PluginContext } from '../../mol-plugin/context'; +import { StateObject, StateTransformer } from '../../mol-state'; +import { Task } from '../../mol-task'; +import { ajaxGetMany } from '../../mol-util/data-source'; +import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { PluginStateObject as SO, PluginStateTransform } from '../objects'; +import { Asset } from '../../mol-util/assets'; export { Download }; +export { DownloadBlob }; +export { RawData }; +export { ReadFile }; +export { ParseBlob }; +export { ParseCif }; +export { ParsePsf }; +export { ParsePly }; +export { ParseCcp4 }; +export { ParseDsn6 }; +export { ImportString }; +export { ImportJson }; +export { ParseJson }; type Download = typeof Download const Download = PluginStateTransform.BuiltIn({ name: 'download', @@ -34,14 +45,19 @@ const Download = PluginStateTransform.BuiltIn({ body: PD.Optional(PD.Text('')) } })({ - apply({ params: p }, globalCtx: PluginContext) { + apply({ params: p }, plugin: PluginContext) { return Task.create('Download', async ctx => { - const data = await globalCtx.fetch({ url: p.url, type: p.isBinary ? 'binary' : 'string', body: p.body }).runInContext(ctx); + const data = await plugin.managers.asset.resolve(Asset.Url(p.url, { body: p.body }), p.isBinary ? 'binary' : 'string').runInContext(ctx); return p.isBinary ? new SO.Data.Binary(data as Uint8Array, { label: p.label ? p.label : p.url }) : new SO.Data.String(data as string, { label: p.label ? p.label : p.url }); }); }, + dispose({ params: p }, plugin: PluginContext) { + if (p) { + plugin.managers.asset.release(Asset.Url(p.url, { body: p.body })); + } + }, update({ oldParams, newParams, b }) { if (oldParams.url !== newParams.url || oldParams.isBinary !== newParams.isBinary) return StateTransformer.UpdateResult.Recreate; if (oldParams.label !== newParams.label) { @@ -52,7 +68,6 @@ const Download = PluginStateTransform.BuiltIn({ } }); -export { DownloadBlob }; type DownloadBlob = typeof DownloadBlob const DownloadBlob = PluginStateTransform.BuiltIn({ name: 'download-blob', @@ -99,7 +114,6 @@ const DownloadBlob = PluginStateTransform.BuiltIn({ // } }); -export { RawData }; type RawData = typeof RawData const RawData = PluginStateTransform.BuiltIn({ name: 'raw-data', @@ -131,7 +145,6 @@ const RawData = PluginStateTransform.BuiltIn({ } }); -export { ReadFile }; type ReadFile = typeof ReadFile const ReadFile = PluginStateTransform.BuiltIn({ name: 'read-file', @@ -151,27 +164,19 @@ const ReadFile = PluginStateTransform.BuiltIn({ return StateObject.Null; } - let file: File; - if (AssetManager.isItem(p.file)) { - if (!plugin.managers.asset.has(p.file.id)) { - throw new Error(`No asset found for '${p.file.name}'`); - } - file = plugin.managers.asset.get(p.file.id)!; - // will be added again below with new id - plugin.managers.asset.remove(p.file.id); - } else { - file = p.file; - } - - const data = await readFromFile(file, p.isBinary ? 'binary' : 'string').runInContext(ctx); + const data = await plugin.managers.asset.resolve(p.file, p.isBinary ? 'binary' : 'string').runInContext(ctx); const o = p.isBinary ? new SO.Data.Binary(data as Uint8Array, { label: p.label ? p.label : p.file.name }) : new SO.Data.String(data as string, { label: p.label ? p.label : p.file.name }); - plugin.managers.asset.set(o.id, file); return o; }); }, + dispose({ params }, plugin: PluginContext) { + if (params?.file) { + plugin.managers.asset.release(params.file); + } + }, update({ oldParams, newParams, b }) { if (oldParams.label !== newParams.label) { (b.label as string) = newParams.label || oldParams.file?.name || ''; @@ -182,7 +187,6 @@ const ReadFile = PluginStateTransform.BuiltIn({ isSerializable: () => ({ isSerializable: false, reason: 'Cannot serialize user loaded files.' }) }); -export { ParseBlob }; type ParseBlob = typeof ParseBlob const ParseBlob = PluginStateTransform.BuiltIn({ name: 'parse-blob', @@ -226,7 +230,6 @@ const ParseBlob = PluginStateTransform.BuiltIn({ // } }); -export { ParseCif }; type ParseCif = typeof ParseCif const ParseCif = PluginStateTransform.BuiltIn({ name: 'parse-cif', @@ -243,7 +246,6 @@ const ParseCif = PluginStateTransform.BuiltIn({ } }); -export { ParsePsf }; type ParsePsf = typeof ParsePsf const ParsePsf = PluginStateTransform.BuiltIn({ name: 'parse-psf', @@ -260,7 +262,6 @@ const ParsePsf = PluginStateTransform.BuiltIn({ } }); -export { ParsePly }; type ParsePly = typeof ParsePly const ParsePly = PluginStateTransform.BuiltIn({ name: 'parse-ply', @@ -277,7 +278,6 @@ const ParsePly = PluginStateTransform.BuiltIn({ } }); -export { ParseCcp4 }; type ParseCcp4 = typeof ParseCcp4 const ParseCcp4 = PluginStateTransform.BuiltIn({ name: 'parse-ccp4', @@ -294,7 +294,6 @@ const ParseCcp4 = PluginStateTransform.BuiltIn({ } }); -export { ParseDsn6 }; type ParseDsn6 = typeof ParseDsn6 const ParseDsn6 = PluginStateTransform.BuiltIn({ name: 'parse-dsn6', @@ -311,7 +310,6 @@ const ParseDsn6 = PluginStateTransform.BuiltIn({ } }); -export { ImportString }; type ImportString = typeof ImportString const ImportString = PluginStateTransform.BuiltIn({ name: 'import-string', @@ -337,7 +335,6 @@ const ImportString = PluginStateTransform.BuiltIn({ isSerializable: () => ({ isSerializable: false, reason: 'Cannot serialize user imported strings.' }) }); -export { ImportJson }; type ImportJson = typeof ImportJson const ImportJson = PluginStateTransform.BuiltIn({ name: 'import-json', @@ -363,7 +360,6 @@ const ImportJson = PluginStateTransform.BuiltIn({ isSerializable: () => ({ isSerializable: false, reason: 'Cannot serialize user imported JSON.' }) }); -export { ParseJson }; type ParseJson = typeof ParseJson const ParseJson = PluginStateTransform.BuiltIn({ name: 'parse-json', diff --git a/src/mol-plugin-ui/controls/parameters.tsx b/src/mol-plugin-ui/controls/parameters.tsx index 83f7a11dd79e84b76f2a3fc2a0ab9b5de6bd14dd..45225b3356c66e1c2430cf821246bdd28cb93c7f 100644 --- a/src/mol-plugin-ui/controls/parameters.tsx +++ b/src/mol-plugin-ui/controls/parameters.tsx @@ -25,6 +25,7 @@ import { Icon } from './icons'; import { legendFor } from './legend'; import LineGraphComponent from './line-graph/line-graph-component'; import { Slider, Slider2 } from './slider'; +import { Asset } from '../../mol-util/assets'; export type ParameterControlsCategoryFilter = string | null | (string | null)[] @@ -787,7 +788,7 @@ export class Mat4Control extends React.PureComponent<ParamProps<PD.Mat4>, { isEx export class FileControl extends React.PureComponent<ParamProps<PD.FileParam>> { change(value: File) { - this.props.onChange({ name: this.props.name, param: this.props.param, value }); + this.props.onChange({ name: this.props.name, param: this.props.param, value: Asset.File(value) }); } onChangeFile = (e: React.ChangeEvent<HTMLInputElement>) => { diff --git a/src/mol-plugin/behavior/static/state.ts b/src/mol-plugin/behavior/static/state.ts index e0fa24b31948e3af3c414c4262671baa0b7f9029..fcfce45550309b9cf6e29eb74e12459264b449f0 100644 --- a/src/mol-plugin/behavior/static/state.ts +++ b/src/mol-plugin/behavior/static/state.ts @@ -20,6 +20,7 @@ import { zip } from '../../../mol-util/zip/zip'; import { utf8Write, utf8ByteCount } from '../../../mol-io/common/utf8'; import { objectForEach } from '../../../mol-util/object'; import { UUID } from '../../../mol-util'; +import { Asset } from '../../../mol-util/assets'; export function registerDefault(ctx: PluginContext) { SyncBehaviors(ctx); @@ -184,7 +185,7 @@ export function Snapshots(ctx: PluginContext) { }); PluginCommands.State.Snapshots.DownloadToFile.subscribe(ctx, async ({ name, type }) => { - const json = JSON.stringify(ctx.state.getSnapshot(), ctx.managers.asset.replacer, 2); + const json = JSON.stringify(ctx.state.getSnapshot(), null, 2); name = `mol-star_state_${(name || getFormattedTime())}`; if (type === 'json') { @@ -197,10 +198,22 @@ export function Snapshots(ctx: PluginContext) { const zipDataObj: { [k: string]: Uint8Array } = { 'state.json': state }; - for (const [file, id] of ctx.managers.asset.list) { - zipDataObj[`${id}/${file.name}`] = new Uint8Array(await file.arrayBuffer()); + const assets: any[] = []; + + for (const { asset, file } of ctx.managers.asset.assets) { + const id = Asset.isFile(asset) ? asset.id : UUID.create22(); + assets.push([id, asset]); + zipDataObj[`assets/${id}`] = new Uint8Array(await file.arrayBuffer()); + } + + if (assets.length > 0) { + const index = JSON.stringify(assets, null, 2); + const data = new Uint8Array(utf8ByteCount(index)); + utf8Write(data, 0, index); + zipDataObj['assets.json'] = data; } + const zipFile = zip(zipDataObj); const blob = new Blob([zipFile], {type : 'application/zip'}); @@ -211,22 +224,30 @@ export function Snapshots(ctx: PluginContext) { PluginCommands.State.Snapshots.OpenFile.subscribe(ctx, async ({ file }) => { try { if (file.name.toLowerCase().endsWith('json')) { - const data = await readFromFile(file, 'string').run(); + const data = await ctx.runTask(readFromFile(file, 'string')); const snapshot = JSON.parse(data); return ctx.state.setSnapshot(snapshot); } else { - const data = await readFromFile(file, 'zip').run(); - objectForEach(data, (v, k) => { - if (k === 'state.json') return; + const data = await ctx.runTask(readFromFile(file, 'zip')); + const assets = Object.create(null); - const slash = k.indexOf('/'); - const id = k.substring(0, slash) as UUID; - const name = k.substring(slash + 1); - const file = new File([v], name); - ctx.managers.asset.set(id, file); + objectForEach(data, (v, k) => { + if (k === 'state.json' || k === 'assets.json') return; + const name = k.substring(k.indexOf('/') + 1); + assets[name] = new File([v], name); }); const stateFile = new File([data['state.json']], 'state.json'); - const stateData = await readFromFile(stateFile, 'string').run(); + const stateData = await ctx.runTask(readFromFile(stateFile, 'string')); + + if (data['assets.json']) { + const file = new File([data['assets.json']], 'assets.json'); + const json = JSON.parse(await ctx.runTask(readFromFile(file, 'string'))); + + for (const [id, asset] of json) { + ctx.managers.asset.set(asset, assets[id]); + } + } + const snapshot = JSON.parse(stateData); return ctx.state.setSnapshot(snapshot); } diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index bb7021aba8ca91777aa30803c7901bc629110080..c10b81f52f7281ee2ed41874d67d373e158372dc 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -53,7 +53,7 @@ import { TaskManager } from './util/task-manager'; import { PluginToastManager } from './util/toast'; import { ViewportScreenshotHelper } from './util/viewport-screenshot'; import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version'; -import { AssetManager } from './util/asset-manager'; +import { AssetManager } from '../mol-util/assets'; export class PluginContext { runTask = <T>(task: Task<T>) => this.tasks.run(task); @@ -152,7 +152,7 @@ export class PluginContext { camera: new CameraManager(this), lociLabels: void 0 as any as LociLabelManager, toast: new PluginToastManager(this), - asset: new AssetManager(this) + asset: new AssetManager() } as const readonly customModelProperties = new CustomProperty.Registry<Model>(); diff --git a/src/mol-plugin/util/asset-manager.ts b/src/mol-plugin/util/asset-manager.ts deleted file mode 100644 index 1e31895cba49b7a02391745b3abd8eaa6eaf7c2a..0000000000000000000000000000000000000000 --- a/src/mol-plugin/util/asset-manager.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { UUID } from '../../mol-util'; -import { PluginContext } from '../context'; -import { iterableToArray } from '../../mol-data/util'; - -export { AssetManager }; - -class AssetManager { - private files = new Map<UUID, File>() - private ids = new Map<File, UUID>() - - get list() { - return iterableToArray(this.ids.entries()); - } - - set(id: UUID, file: File) { - this.files.set(id, file); - this.ids.set(file, id); - } - - remove(id: UUID) { - if (this.files.has(id)) { - const file = this.files.get(id)!; - this.files.delete(id); - this.ids.delete(file); - } - } - - has(id: UUID) { - return this.files.has(id); - } - - get(id: UUID) { - return this.files.get(id); - } - - /** For use with `JSON.stringify` */ - replacer = (key: string, value: any) => { - if (value instanceof File) { - const id = this.ids.get(value); - if (!id) { - // TODO throw? - console.warn(`No asset found for '${value.name}'`); - } - return id ? AssetManager.Item(id, value.name) : {}; - } else { - return value; - } - } - - constructor(public ctx: PluginContext) { - ctx.state.data.events.object.removed.subscribe(e => { - const id = e.obj?.id; - if (id) ctx.managers.asset.remove(id); - }); - } -} - -namespace AssetManager { - export type Item = { kind: 'asset-item', id: UUID, name: string }; - export function Item(id: UUID, name: string): Item { - return { kind: 'asset-item', id, name }; - } - - export function isItem(x?: any): x is Item { - return !!x && x?.kind === 'asset-item'; - } -} \ No newline at end of file diff --git a/src/mol-util/assets.ts b/src/mol-util/assets.ts new file mode 100644 index 0000000000000000000000000000000000000000..14e5a2dce4d06bf5c7e96aa2b65f4442ec9c4474 --- /dev/null +++ b/src/mol-util/assets.ts @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import UUID from './uuid'; +import { iterableToArray } from '../mol-data/util'; +import { ajaxGet, DataType, DataResponse, readFromFile } from './data-source'; +import { Task } from '../mol-task'; + +export { AssetManager, Asset }; + +type _File = File; +type Asset = Asset.Url | Asset.File + +namespace Asset { + export type Url = { url: string, title?: string, body?: string } + export type File = { id: UUID, name: string, file?: _File } + + export function Url(url: string, options?: { body?: string, title?: string }): Url { + return { url, ...options }; + } + + export function File(file: _File): File { + return { id: UUID.create22(), name: file.name, file }; + } + + export function isUrl(x: Asset): x is Url { + return !!x && !!(x as any).url; + } + + export function isFile(x: Asset): x is File { + return !!x && !!(x as any).id; + } +} + +class AssetManager { + private _assets = new Map<string, { asset: Asset, file: File }>(); + + get assets() { + return iterableToArray(this._assets.values()); + } + + set(asset: Asset, file: File) { + if (Asset.isUrl(asset)) { + this._assets.set(getUrlKey(asset), { asset, file }); + } else { + this._assets.set(asset.id, { asset, file }); + } + } + + resolve<T extends DataType>(asset: Asset, type: T, store = true): Task<DataResponse<T>> { + if (Asset.isUrl(asset)) { + const key = getUrlKey(asset); + if (this._assets.has(key)) { + return readFromFile(this._assets.get(key)!.file, type); + } + + if (!store) { + return ajaxGet({ ...asset, type }); + } + + return Task.create(`Download ${asset.title || asset.url}`, async ctx => { + const data = await ajaxGet({ ...asset, type: 'binary' }).runInContext(ctx); + const file = new File([data], 'raw-data'); + this._assets.set(key, { asset, file }); + return await readFromFile(file, type).runInContext(ctx); + }); + } else { + if (this._assets.has(asset.id)) return readFromFile(this._assets.get(asset.id)!.file, type); + if (!(asset.file instanceof File)) { + return Task.fail('Resolve asset', `Cannot resolve file asset '${asset.name}' (${asset.id})`); + } + if (store) { + this._assets.set(asset.id, { asset, file: asset.file }); + } + return readFromFile(asset.file, type); + } + } + + release(asset: Asset) { + if (Asset.isFile(asset)) { + this._assets.delete(asset.id); + } else { + this._assets.delete(getUrlKey(asset)); + } + } +} + +function getUrlKey(asset: Asset.Url) { + return asset.body ? `${asset.url}_${asset.body || ''}` : asset.url; +} + +namespace AssetManager { + export type Item = { kind: 'asset-item', id: UUID, name: string }; + export function Item(id: UUID, name: string): Item { + return { kind: 'asset-item', id, name }; + } + + export function isItem(x?: any): x is Item { + return !!x && x?.kind === 'asset-item'; + } +} \ No newline at end of file diff --git a/src/mol-util/data-source.ts b/src/mol-util/data-source.ts index cfd5b85f407ea9af5e71d3ea13d1e0f82860ca81..4a322b1b8275df0f60bb94b89558a8d42e68d6fa 100644 --- a/src/mol-util/data-source.ts +++ b/src/mol-util/data-source.ts @@ -28,9 +28,9 @@ export enum DataCompressionMethod { Zip, } -type DataType = 'json' | 'xml' | 'string' | 'binary' | 'zip' -type DataValue = 'string' | any | XMLDocument | Uint8Array -type DataResponse<T extends DataType> = +export type DataType = 'json' | 'xml' | 'string' | 'binary' | 'zip' +export type DataValue = 'string' | any | XMLDocument | Uint8Array +export type DataResponse<T extends DataType> = T extends 'json' ? any : T extends 'xml' ? XMLDocument : T extends 'string' ? string : diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index 986dc1be35804aee1d667157845bc39dbcd7e870..809bf38c6703e13763eab63649167c5778922045 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -13,6 +13,7 @@ import { Script as ScriptData } from '../mol-script/script'; import { Legend } from './legend'; import { stringToWords } from './string'; import { getColorListFromName, ColorListName } from './color/lists'; +import { Asset } from './assets'; export namespace ParamDefinition { export interface Info { @@ -147,7 +148,7 @@ export namespace ParamDefinition { return setInfo<Mat4>({ type: 'mat4', defaultValue }, info); } - export interface FileParam extends Base<File | null> { + export interface FileParam extends Base<Asset.File | null> { type: 'file' accept?: string }