file.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. /**
  2. * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { PluginContext } from '../../mol-plugin/context';
  7. import { StateAction } from '../../mol-state';
  8. import { Task } from '../../mol-task';
  9. import { Asset } from '../../mol-util/assets';
  10. import { getFileInfo } from '../../mol-util/file-info';
  11. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  12. import { unzip } from '../../mol-util/zip/zip';
  13. import { PluginStateObject } from '../objects';
  14. async function processFile(file: Asset.File, plugin: PluginContext, format: string, visuals: boolean) {
  15. const info = getFileInfo(file.file!);
  16. const isBinary = plugin.dataFormats.binaryExtensions.has(info.ext);
  17. const { data } = await plugin.builders.data.readFile({ file, isBinary });
  18. const provider = format === 'auto'
  19. ? plugin.dataFormats.auto(info, data.cell?.obj!)
  20. : plugin.dataFormats.get(format);
  21. if (!provider) {
  22. plugin.log.warn(`OpenFiles: could not find data provider for '${info.ext}'`);
  23. await plugin.state.data.build().delete(data).commit();
  24. return;
  25. }
  26. // need to await so that the enclosing Task finishes after the update is done.
  27. const parsed = await provider.parse(plugin, data);
  28. if (visuals) {
  29. await provider.visuals?.(plugin, parsed);
  30. }
  31. };
  32. export const OpenFiles = StateAction.build({
  33. display: { name: 'Open Files', description: 'Load one or more files and optionally create default visuals' },
  34. from: PluginStateObject.Root,
  35. params: (a, ctx: PluginContext) => {
  36. const { extensions, options } = ctx.dataFormats;
  37. return {
  38. files: PD.FileList({ accept: Array.from(extensions.values()).map(e => `.${e}`).join(',') + ',.gz,.zip', multiple: true }),
  39. format: PD.MappedStatic('auto', {
  40. auto: PD.EmptyGroup(),
  41. specific: PD.Select(options[0][0], options)
  42. }),
  43. visuals: PD.Boolean(true, { description: 'Add default visuals' }),
  44. };
  45. }
  46. })(({ params, state }, plugin: PluginContext) => Task.create('Open Files', async taskCtx => {
  47. plugin.behaviors.layout.leftPanelTabName.next('data');
  48. await state.transaction(async () => {
  49. if (params.files === null) {
  50. plugin.log.error('No file(s) selected');
  51. return;
  52. }
  53. for (const file of params.files) {
  54. try {
  55. if (file.file && file.name.toLowerCase().endsWith('.zip')) {
  56. const zippedFiles = await unzip(taskCtx, await file.file.arrayBuffer());
  57. for (const [fn, filedata] of Object.entries(zippedFiles)) {
  58. if (!(filedata instanceof Uint8Array) || filedata.length === 0) continue;
  59. const asset = Asset.File(new File([filedata], fn));
  60. await processFile(asset, plugin, 'auto', params.visuals);
  61. }
  62. } else {
  63. const format = params.format.name === 'auto' ? 'auto' : params.format.params;
  64. await processFile(file, plugin, format, params.visuals);
  65. }
  66. } catch (e) {
  67. console.error(e);
  68. plugin.log.error(`Error opening file '${file.name}'`);
  69. }
  70. }
  71. }).runInContext(taskCtx);
  72. }));
  73. export const DownloadFile = StateAction.build({
  74. display: { name: 'Download File', description: 'Load one or more file from an URL' },
  75. from: PluginStateObject.Root,
  76. params: (a, ctx: PluginContext) => {
  77. const options = [...ctx.dataFormats.options, ['zip', 'Zip'] as const, ['gzip', 'Gzip'] as const];
  78. return {
  79. url: PD.Url(''),
  80. format: PD.Select(options[0][0], options),
  81. isBinary: PD.Boolean(false),
  82. visuals: PD.Boolean(true, { description: 'Add default visuals' }),
  83. };
  84. }
  85. })(({ params, state }, plugin: PluginContext) => Task.create('Open Files', async taskCtx => {
  86. plugin.behaviors.layout.leftPanelTabName.next('data');
  87. await state.transaction(async () => {
  88. try {
  89. if (params.format === 'zip' || params.format === 'gzip') {
  90. // TODO: add ReadZipFile transformer so this can be saved as a simple state snaphot,
  91. // would need support for extracting individual files from zip
  92. const data = await plugin.builders.data.download({ url: params.url, isBinary: true });
  93. if (params.format === 'zip') {
  94. const zippedFiles = await unzip(taskCtx, (data.obj?.data as Uint8Array).buffer);
  95. for (const [fn, filedata] of Object.entries(zippedFiles)) {
  96. if (!(filedata instanceof Uint8Array) || filedata.length === 0) continue;
  97. const asset = Asset.File(new File([filedata], fn));
  98. await processFile(asset, plugin, 'auto', params.visuals);
  99. }
  100. } else {
  101. const url = Asset.getUrl(params.url);
  102. const info = getFileInfo(url);
  103. await processFile(Asset.File(new File([data.obj?.data as Uint8Array], info.name)), plugin, 'auto', params.visuals);
  104. }
  105. } else {
  106. const provider = plugin.dataFormats.get(params.format);
  107. if (!provider) {
  108. plugin.log.warn(`DownloadFile: could not find data provider for '${params.format}'`);
  109. return;
  110. }
  111. const data = await plugin.builders.data.download({ url: params.url, isBinary: params.isBinary });
  112. const parsed = await provider.parse(plugin, data);
  113. if (params.visuals) {
  114. await provider.visuals?.(plugin, parsed);
  115. }
  116. }
  117. } catch (e) {
  118. console.error(e);
  119. plugin.log.error(`Error downloading '${typeof params.url === 'string' ? params.url : params.url.url}'`);
  120. }
  121. }).runInContext(taskCtx);
  122. }));