@vitessce/vit-s 3.8.5 → 3.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/on-drop.js ADDED
@@ -0,0 +1,197 @@
1
+ import { getFilesFromDataTransferItems } from '@placemarkio/flat-drop-files';
2
+ import { generateConfigAlt as generateConfig, parseUrlsFromString } from '@vitessce/config';
3
+
4
+
5
+ // Stores
6
+ // This "flat" store can be slow to initialize when the Zarr store contains many files.
7
+ class FlatFileSystemStore {
8
+ constructor(files) {
9
+ this.files = files;
10
+ }
11
+
12
+ async get(key) {
13
+ // The list of files does not prefix its paths with slashes.
14
+ const file = this.files.find(f => `/${f.relpath}` === key);
15
+ if (!file) return undefined;
16
+ const buffer = await file.arrayBuffer();
17
+ return new Uint8Array(buffer);
18
+ }
19
+
20
+ // TODO: implement getRange
21
+ }
22
+
23
+ /*
24
+ // Get a file handle to a file in a directory.
25
+ async function resolveFileHandleForPath(
26
+ root, // A root directory from the Web File System API.
27
+ path, // A key to a file in the root directory.
28
+ ) {
29
+ const dirs = path.split('/');
30
+ const fname = dirs.pop();
31
+ if (!fname) {
32
+ throw new Error('Invalid path');
33
+ }
34
+ for (const dir of dirs) {
35
+ root = await root.getDirectoryHandle(dir);
36
+ }
37
+ // Returns a file handle to the file.
38
+ return root.getFileHandle(fname);
39
+ }
40
+
41
+ // TODO: use this hierarchical store to avoid the issues with flattening the file tree up-front,
42
+ // as this store only traverses/accesses the parts of the tree that are needed.
43
+ class HierarchicalFileSystemStore {
44
+ constructor(root) {
45
+ // root is a FileSystemDirectoryHandle
46
+ this.root = root;
47
+ }
48
+
49
+ async get(key) {
50
+ // TODO: better error handling
51
+ // I believe a missing file will trigger an error here, which we should explicitly
52
+ // catch and return `undefined`
53
+ const fh = await resolveFileHandleForPath(this.root, key.slice(1)).catch(
54
+ () => undefined,
55
+ );
56
+ if (!fh) {
57
+ return undefined;
58
+ }
59
+
60
+ const file = await fh.getFile();
61
+ return file.arrayBuffer();
62
+ }
63
+
64
+ // TODO: implement getRange
65
+ }
66
+ */
67
+
68
+
69
+ /**
70
+ * Create an event handler for either dropzone or input type="file" elements.
71
+ * @param {object} setters - The parameters for the drop event handler.
72
+ * @param {function} setters.setViewConfig - A function to set the view config.
73
+ * @param {function} setters.setStores - A function to set the stores.
74
+ * @param {boolean} isFileInput - Whether the drop zone is for file input.
75
+ * By default, false.
76
+ * @param {boolean} isConfigInput - Whether the drop zone is for config input.
77
+ * By default, false.
78
+ * @returns A drop event handler async function.
79
+ */
80
+ export function createOnDrop(
81
+ setters,
82
+ isFileInput = false,
83
+ isConfigInput = false,
84
+ ) {
85
+ return async (e) => {
86
+ const { setViewConfig, setStores } = setters;
87
+ let topLevelEntries;
88
+ let files;
89
+ if (isFileInput) {
90
+ // Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
91
+
92
+ // Note: e.target.files is a FileList, not an array.
93
+ // If we use the spread operator, the methods like file.arrayBuffer() get lost.
94
+ files = Array.from(e.target.files);
95
+ // Here, we use files.forEach rather than files = files.map
96
+ // so that we can modify the original array in place.
97
+ files.forEach((f, i) => {
98
+ if (!f.path) {
99
+ // eslint-disable-next-line no-param-reassign
100
+ files[i].path = files[i].webkitRelativePath;
101
+ }
102
+ });
103
+ // When a user selects a directory via a file picker dialog,
104
+ // we get only a flat list of files.
105
+ // In order to match e.dataTransfer.items behavior,
106
+ // we create fake top-level entries for each unique directory/file name.
107
+ const dirNames = new Set();
108
+ topLevelEntries = [];
109
+ Array.from(files).forEach((file) => {
110
+ const dirName = file.path.split('/')[0];
111
+ if (dirName) {
112
+ dirNames.add(dirName);
113
+ }
114
+ // If the file is at the top level (no directory),
115
+ // we need to add a top-level entry for it.
116
+ if (dirName === file.name) {
117
+ topLevelEntries.push({
118
+ isDirectory: false,
119
+ name: file.name,
120
+ });
121
+ }
122
+ });
123
+ topLevelEntries = topLevelEntries.concat(Array.from(dirNames).map(name => ({
124
+ isDirectory: true,
125
+ name,
126
+ })));
127
+ } else {
128
+ topLevelEntries = Object.values(e.dataTransfer.items)
129
+ .map(item => item.webkitGetAsEntry());
130
+ files = await getFilesFromDataTransferItems(e.dataTransfer.items);
131
+ }
132
+
133
+ if (isConfigInput) {
134
+ // We expect a single file which contains the config JSON.
135
+ if (files.length === 1) {
136
+ const file = files[0];
137
+ if (file.name.endsWith('.json')) {
138
+ const content = await file.arrayBuffer();
139
+ // Alternatively, use the FileReader API.
140
+ const json = JSON.parse(new TextDecoder().decode(content));
141
+ setViewConfig(json);
142
+ return;
143
+ }
144
+ }
145
+ }
146
+
147
+ // TODO: implement an alternative approach that does not first flatten the file tree,
148
+ // since it can be very large.
149
+ // See https://github.com/manzt/zarrita.js/pull/161/files
150
+
151
+ const stores = topLevelEntries.map((entry) => {
152
+ if (entry.isDirectory) {
153
+ // TODO: optimize by using a single loop for filter+map,
154
+ // and by using .substring (rather than split+slice+join).
155
+ const dirFiles = files
156
+ .filter(f => f.path.split('/')?.[0] === entry.name)
157
+ .map((f) => {
158
+ // eslint-disable-next-line no-param-reassign
159
+ f.relpath = f.path.split('/')?.slice(1).join('/');
160
+ return f;
161
+ });
162
+ // Create a store for each top-level item of e.dataTransfer.items.
163
+ const store = new FlatFileSystemStore(dirFiles);
164
+ return [entry.name, store];
165
+ }
166
+ // This is a single file. Check extension to determine how to create a store.
167
+ if (entry.name.endsWith('.zip')) {
168
+ // Create a zip store.
169
+
170
+ // TODO
171
+ } else if (entry.name.endsWith('.tif') || entry.name.endsWith('.tiff')) {
172
+ // Create an OME-TIFF-as-NGFF store?
173
+
174
+ // TODO
175
+ } else if (entry.name.endsWith('.csv')) {
176
+ // Create a CSV store?
177
+
178
+ // TODO
179
+ } else {
180
+ // Throw?
181
+
182
+ // TODO
183
+ }
184
+ return [entry.name, null];
185
+ });
186
+
187
+ const parsedUrls = stores.map(([name, store]) => ({
188
+ ...parseUrlsFromString(name)[0],
189
+ store,
190
+ }));
191
+ const { config, stores: storesForConfig } = await generateConfig(parsedUrls);
192
+ const newConfig = config.toJSON();
193
+
194
+ setViewConfig(newConfig);
195
+ setStores(storesForConfig);
196
+ };
197
+ }
@@ -120,10 +120,18 @@ function withDefaults(
120
120
  * @param {PluginFileType[]} fileTypes
121
121
  * @param {PluginCoordinationType[]} coordinationTypes
122
122
  * @param {object} stores Optional mapping from URLs to Zarrita stores.
123
+ * @param {QueryClient} queryClient A react-query QueryClient instance.
123
124
  * @returns {object} Mapping from dataset ID to data type to loader
124
125
  * instance.
125
126
  */
126
- export function createLoaders(datasets, configDescription, fileTypes, coordinationTypes, stores) {
127
+ export function createLoaders(
128
+ datasets,
129
+ configDescription,
130
+ fileTypes,
131
+ coordinationTypes,
132
+ stores,
133
+ queryClient,
134
+ ) {
127
135
  const result = {};
128
136
  const dataSources = new InternMap([], JSON.stringify);
129
137
  const defaultCoordinationValues = Object.fromEntries(
@@ -173,6 +181,7 @@ export function createLoaders(datasets, configDescription, fileTypes, coordinati
173
181
  // Optionally, pass a Zarrita store to the data source,
174
182
  // if one was mapped to this URL.
175
183
  store: stores?.[url],
184
+ queryClient,
176
185
  }));
177
186
  }
178
187
  const loader = new LoaderClass(dataSources.get(dataSourceKey), file);