blob: c55bc990b324cda206af64fb57a4860baf4ea795 [file] [log] [blame]
Adam Pardyld905ed22019-07-08 18:22:39 +02001<!-- Copyright (C) 2019 The Android Open Source Project
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14-->
15<template>
Nataniel Borgesf230a132021-07-28 07:13:45 +000016<div @dragleave="fileDragOut" @dragover="fileDragIn" @drop="handleFileDrop">
Pablo Gamito050e20b2020-07-01 16:41:56 +010017 <flat-card style="min-width: 50em">
18 <md-card-header>
19 <div class="md-title">Open files</div>
20 </md-card-header>
21 <md-card-content>
Nataniel Borges39071542021-09-22 09:19:41 +000022 <div class="dropbox">
23 <md-list style="background: none">
24 <md-list-item v-for="file in dataFiles" v-bind:key="file.filename">
25 <md-icon>{{FILE_ICONS[file.type]}}</md-icon>
26 <span class="md-list-item-text">{{file.filename}} ({{file.type}})
27 </span>
28 <md-button
29 class="md-icon-button md-accent"
30 @click="onRemoveFile(file.type)"
31 >
32 <md-icon>close</md-icon>
33 </md-button>
34 </md-list-item>
35 </md-list>
36 <md-progress-spinner
37 :md-diameter="30"
38 :md-stroke="3"
39 md-mode="indeterminate"
40 v-show="loadingFiles"
41 class="progress-spinner"
42 />
43 <input
44 type="file"
45 @change="onLoadFile"
46 v-on:drop="handleFileDrop"
47 ref="fileUpload"
48 id="dropzone"
49 v-show="false"
50 multiple
51 />
52 <p v-if="!dataReady">
53 Drag your <b>.winscope</b> or <b>.zip</b> file(s) here to begin
54 </p>
55 </div>
56
Pablo Gamito050e20b2020-07-01 16:41:56 +010057 <div class="md-layout">
58 <div class="md-layout-item md-small-size-100">
59 <md-field>
60 <md-select v-model="fileType" id="file-type" placeholder="File type">
61 <md-option value="auto">Detect type</md-option>
62 <md-option value="bugreport">Bug Report (.zip)</md-option>
Pablo Gamitobea65822020-09-02 16:35:32 +020063 <md-option
64 :value="k" v-for="(v,k) in FILE_DECODERS"
65 v-bind:key="v.name">{{v.name}}
66 ></md-option>
Pablo Gamito050e20b2020-07-01 16:41:56 +010067 </md-select>
68 </md-field>
Adam Pardyld905ed22019-07-08 18:22:39 +020069 </div>
Pablo Gamito050e20b2020-07-01 16:41:56 +010070 </div>
71 <div class="md-layout">
Pablo Gamitobea65822020-09-02 16:35:32 +020072 <md-button
73 class="md-primary md-theme-default"
74 @click="$refs.fileUpload.click()"
75 >
76 Add File
77 </md-button>
78 <md-button
79 v-if="dataReady"
80 @click="onSubmit"
81 class="md-button md-primary md-raised md-theme-default"
82 >
83 Submit
84 </md-button>
Pablo Gamito050e20b2020-07-01 16:41:56 +010085 </div>
86 </md-card-content>
Pablo Gamito82639302020-07-17 18:38:37 +010087
88 <md-snackbar
89 md-position="center"
90 :md-duration="Infinity"
91 :md-active.sync="showFetchingSnackbar"
92 md-persistent
93 >
94 <span>{{ fetchingSnackbarText }}</span>
95 </md-snackbar>
96
97 <md-snackbar
98 md-position="center"
Pablo Gamitoafbfa782020-07-17 19:26:56 +010099 :md-duration="snackbarDuration"
Pablo Gamito82639302020-07-17 18:38:37 +0100100 :md-active.sync="showSnackbar"
101 md-persistent
102 >
Nataniel Borgesf230a132021-07-28 07:13:45 +0000103 <p class="snackbar-break-words">{{ snackbarText }}</p>
Pablo Gamitoafbfa782020-07-17 19:26:56 +0100104 <div @click="hideSnackbarMessage()">
105 <md-button class="md-icon-button">
106 <md-icon style="color: white">close</md-icon>
107 </md-button>
108 </div>
Pablo Gamito82639302020-07-17 18:38:37 +0100109 </md-snackbar>
Pablo Gamito050e20b2020-07-01 16:41:56 +0100110 </flat-card>
Nataniel Borgesf230a132021-07-28 07:13:45 +0000111</div>
Adam Pardyld905ed22019-07-08 18:22:39 +0200112</template>
113<script>
Pablo Gamito050e20b2020-07-01 16:41:56 +0100114import FlatCard from './components/FlatCard.vue';
Pablo Gamitoed709402020-06-08 13:18:15 +0100115import JSZip from 'jszip';
Pablo Gamitobea65822020-09-02 16:35:32 +0200116import {
117 detectAndDecode,
118 FILE_TYPES,
119 FILE_DECODERS,
120 FILE_ICONS,
121 UndetectableFileType,
122} from './decode.js';
123import {WebContentScriptMessageType} from './utils/consts';
Pablo Gamitoed709402020-06-08 13:18:15 +0100124
Adam Pardyld905ed22019-07-08 18:22:39 +0200125export default {
126 name: 'datainput',
127 data() {
128 return {
129 FILE_TYPES,
Pablo Gamitof25221c2020-08-06 18:26:42 +0100130 FILE_DECODERS,
131 FILE_ICONS,
Pablo Gamitobea65822020-09-02 16:35:32 +0200132 fileType: 'auto',
Adam Pardyld905ed22019-07-08 18:22:39 +0200133 dataFiles: {},
Pablo Gamitoed709402020-06-08 13:18:15 +0100134 loadingFiles: false,
Pablo Gamito82639302020-07-17 18:38:37 +0100135 showFetchingSnackbar: false,
136 showSnackbar: false,
Pablo Gamitoafbfa782020-07-17 19:26:56 +0100137 snackbarDuration: 3500,
Pablo Gamito82639302020-07-17 18:38:37 +0100138 snackbarText: '',
Pablo Gamitobea65822020-09-02 16:35:32 +0200139 fetchingSnackbarText: 'Fetching files...',
140 };
Adam Pardyld905ed22019-07-08 18:22:39 +0200141 },
142 props: ['store'],
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100143 created() {
144 // Attempt to load files from extension if present
145 this.loadFilesFromExtension();
146 },
Adam Pardyld905ed22019-07-08 18:22:39 +0200147 methods: {
Pablo Gamito82639302020-07-17 18:38:37 +0100148 showSnackbarMessage(message, duration) {
Nataniel Borgesf230a132021-07-28 07:13:45 +0000149 this.snackbarText = '\n' + message + '\n';
Pablo Gamitoafbfa782020-07-17 19:26:56 +0100150 this.snackbarDuration = duration;
151 this.showSnackbar = true;
Pablo Gamito82639302020-07-17 18:38:37 +0100152 },
153 hideSnackbarMessage() {
Pablo Gamito82639302020-07-17 18:38:37 +0100154 this.showSnackbar = false;
Nataniel Borges39071542021-09-22 09:19:41 +0000155 this.buttonClicked("Hide Snackbar Message")
Pablo Gamito82639302020-07-17 18:38:37 +0100156 },
157 getFetchFilesLoadingAnimation() {
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100158 let frame = 0;
159 const fetchingStatusAnimation = () => {
160 frame++;
Pablo Gamito82639302020-07-17 18:38:37 +0100161 this.fetchingSnackbarText = `Fetching files${'.'.repeat(frame % 4)}`;
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100162 };
163 let interval = undefined;
164
165 return Object.freeze({
166 start: () => {
Pablo Gamito82639302020-07-17 18:38:37 +0100167 this.showFetchingSnackbar = true;
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100168 interval = setInterval(fetchingStatusAnimation, 500);
169 },
170 stop: () => {
Pablo Gamito82639302020-07-17 18:38:37 +0100171 this.showFetchingSnackbar = false;
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100172 clearInterval(interval);
173 },
174 });
175 },
176 /**
177 * Attempt to load files from the extension if present.
178 *
179 * If the source URL parameter is set to the extension it make a request
180 * to the extension to fetch the files from the extension.
181 */
182 loadFilesFromExtension() {
183 const urlParams = new URLSearchParams(window.location.search);
184 if (urlParams.get('source') === 'openFromExtension' && chrome) {
185 // Fetch files from extension
Pablo Gamitobea65822020-09-02 16:35:32 +0200186 const androidBugToolExtensionId = 'mbbaofdfoekifkfpgehgffcpagbbjkmj';
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100187
Pablo Gamito82639302020-07-17 18:38:37 +0100188 const loading = this.getFetchFilesLoadingAnimation();
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100189 loading.start();
190
191 // Request to convert the blob object url "blob:chrome-extension://xxx"
192 // the chrome extension has to a web downloadable url "blob:http://xxx".
193 chrome.runtime.sendMessage(androidBugToolExtensionId, {
Pablo Gamitobea65822020-09-02 16:35:32 +0200194 action: WebContentScriptMessageType.CONVERT_OBJECT_URL,
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100195 }, async (response) => {
196 switch (response.action) {
197 case WebContentScriptMessageType.CONVERT_OBJECT_URL_RESPONSE:
198 if (response.attachments?.length > 0) {
Pablo Gamitobea65822020-09-02 16:35:32 +0200199 const filesBlobPromises = response.attachments
200 .map(async (attachment) => {
201 const fileQueryResponse =
202 await fetch(attachment.objectUrl);
203 const blob = await fileQueryResponse.blob();
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100204
Pablo Gamitobea65822020-09-02 16:35:32 +0200205 /**
206 * Note: The blob's media type is not correct.
207 * It is always set to "image/png".
208 * Context: http://google3/javascript/closure/html/safeurl.js?g=0&l=256&rcl=273756987
209 */
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100210
Pablo Gamitobea65822020-09-02 16:35:32 +0200211 // Clone blob to clear media type.
212 const file = new Blob([blob]);
213 file.name = attachment.name;
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100214
Pablo Gamitobea65822020-09-02 16:35:32 +0200215 return file;
216 });
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100217
218 const files = await Promise.all(filesBlobPromises);
219
220 loading.stop();
221 this.processFiles(files);
222 } else {
Pablo Gamitobea65822020-09-02 16:35:32 +0200223 const failureMessages = 'Got no attachements from extension...';
Pablo Gamito82639302020-07-17 18:38:37 +0100224 console.warn(failureMessages);
225 this.showSnackbarMessage(failureMessages, 3500);
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100226 }
227 break;
228
229 default:
230 loading.stop();
Pablo Gamitobea65822020-09-02 16:35:32 +0200231 const failureMessages =
232 'Received unhandled response code from extension.';
Pablo Gamito82639302020-07-17 18:38:37 +0100233 console.warn(failureMessages);
234 this.showSnackbarMessage(failureMessages, 3500);
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100235 }
236 });
237 }
238 },
Nataniel Borges39071542021-09-22 09:19:41 +0000239 fileDragIn(e) {
Nataniel Borgesf230a132021-07-28 07:13:45 +0000240 e.preventDefault();
241 },
Nataniel Borges39071542021-09-22 09:19:41 +0000242 fileDragOut(e) {
Nataniel Borgesf230a132021-07-28 07:13:45 +0000243 e.preventDefault();
244 },
245 handleFileDrop(e) {
246 e.preventDefault();
247 let droppedFiles = e.dataTransfer.files;
248 if(!droppedFiles) return;
Nataniel Borges39071542021-09-22 09:19:41 +0000249 // Record analytics event
250 this.draggedAndDropped(droppedFiles);
251
Nataniel Borgesf230a132021-07-28 07:13:45 +0000252 this.processFiles(droppedFiles);
253 },
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100254 onLoadFile(e) {
Pablo Gamito3db5c7d2020-06-08 10:52:14 +0100255 const files = event.target.files || event.dataTransfer.files;
Nataniel Borges39071542021-09-22 09:19:41 +0000256 this.uploadedFileThroughFilesystem(files);
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100257 this.processFiles(files);
258 },
259 async processFiles(files) {
Pablo Gamitoed709402020-06-08 13:18:15 +0100260 let error;
261 const decodedFiles = [];
Pablo Gamito3db5c7d2020-06-08 10:52:14 +0100262 for (const file of files) {
Adam Pardyld905ed22019-07-08 18:22:39 +0200263 try {
Pablo Gamitoed709402020-06-08 13:18:15 +0100264 this.loadingFiles = true;
Pablo Gamito82639302020-07-17 18:38:37 +0100265 this.showSnackbarMessage(`Loading ${file.name}`, Infinity);
Pablo Gamito3db5c7d2020-06-08 10:52:14 +0100266 const result = await this.addFile(file);
Pablo Gamitoed709402020-06-08 13:18:15 +0100267 decodedFiles.push(...result);
Pablo Gamito82639302020-07-17 18:38:37 +0100268 this.hideSnackbarMessage();
Pablo Gamitobea65822020-09-02 16:35:32 +0200269 } catch (e) {
270 this.showSnackbarMessage(
271 `Failed to load '${file.name}'...\n${e}`, 5000);
Pablo Gamitoed709402020-06-08 13:18:15 +0100272 console.error(e);
273 error = e;
Pablo Gamito3db5c7d2020-06-08 10:52:14 +0100274 break;
Pablo Gamitoed709402020-06-08 13:18:15 +0100275 } finally {
276 this.loadingFiles = false;
Adam Pardyld905ed22019-07-08 18:22:39 +0200277 }
Adam Pardyld905ed22019-07-08 18:22:39 +0200278 }
Pablo Gamito3db5c7d2020-06-08 10:52:14 +0100279
Pablo Gamito3db5c7d2020-06-08 10:52:14 +0100280 event.target.value = '';
Pablo Gamitoed709402020-06-08 13:18:15 +0100281
282 if (error) {
283 return;
284 }
285
Pablo Gamitobea65822020-09-02 16:35:32 +0200286 // TODO: Handle the fact that we can now have multiple files of type
287 // FILE_TYPES.TRANSACTION_EVENTS_TRACE
Pablo Gamitof25221c2020-08-06 18:26:42 +0100288
Pablo Gamito586756f2020-07-17 19:19:20 +0100289 const decodedFileTypes = new Set(Object.keys(this.dataFiles));
290 // A file is overridden if a file of the same type is upload twice, as
291 // Winscope currently only support at most one file to each type
Pablo Gamitof25221c2020-08-06 18:26:42 +0100292 const overriddenFileTypes = new Set();
Pablo Gamitoe718ac52020-09-24 12:55:10 +0200293 const overriddenFiles = {}; // filetype => array of files
Pablo Gamitoed709402020-06-08 13:18:15 +0100294 for (const decodedFile of decodedFiles) {
Pablo Gamitof25221c2020-08-06 18:26:42 +0100295 const dataType = decodedFile.filetype;
Pablo Gamito586756f2020-07-17 19:19:20 +0100296
297 if (decodedFileTypes.has(dataType)) {
Pablo Gamitof25221c2020-08-06 18:26:42 +0100298 overriddenFileTypes.add(dataType);
Pablo Gamito586756f2020-07-17 19:19:20 +0100299 (overriddenFiles[dataType] = overriddenFiles[dataType] || [])
Pablo Gamitoe718ac52020-09-24 12:55:10 +0200300 .push(this.dataFiles[dataType]);
Pablo Gamito586756f2020-07-17 19:19:20 +0100301 }
302 decodedFileTypes.add(dataType);
303
Pablo Gamitoed709402020-06-08 13:18:15 +0100304 this.$set(this.dataFiles,
Pablo Gamitobea65822020-09-02 16:35:32 +0200305 dataType, decodedFile.data);
Pablo Gamito586756f2020-07-17 19:19:20 +0100306 }
307
Pablo Gamitoe718ac52020-09-24 12:55:10 +0200308 // TODO(b/169305853): Remove this once we have magic numbers or another
309 // way to detect the file type more reliably.
310 for (const dataType in overriddenFiles) {
311 if (overriddenFiles.hasOwnProperty(dataType)) {
312 const files = overriddenFiles[dataType];
313 files.push(this.dataFiles[dataType]);
Pablo Gamito586756f2020-07-17 19:19:20 +0100314
Pablo Gamitoe718ac52020-09-24 12:55:10 +0200315 const selectedFile =
316 this.getMostLikelyCandidateFile(dataType, files);
317 this.$set(this.dataFiles, dataType, selectedFile);
Pablo Gamito586756f2020-07-17 19:19:20 +0100318
Pablo Gamitoe718ac52020-09-24 12:55:10 +0200319 // Remove selected file from overriden list
320 const index = files.indexOf(selectedFile);
321 files.splice(index, 1);
Pablo Gamito586756f2020-07-17 19:19:20 +0100322 }
Pablo Gamitoed709402020-06-08 13:18:15 +0100323 }
Pablo Gamitoe718ac52020-09-24 12:55:10 +0200324
325 if (overriddenFileTypes.size > 0) {
326 this.displayFilesOverridenWarning(overriddenFiles);
327 }
Pablo Gamito3db5c7d2020-06-08 10:52:14 +0100328 },
Pablo Gamitoe718ac52020-09-24 12:55:10 +0200329
330 /**
331 * Gets the file that is most likely to be the actual file of that type out
332 * of all the candidateFiles. This is required because there are some file
333 * types that have no magic number and may lead to false positives when
334 * decoding in decode.js. (b/169305853)
335 * @param {string} dataType - The type of the candidate files.
336 * @param {files[]} candidateFiles - The list all the files detected to be
337 * of type dataType, passed in the order
338 * they are detected/uploaded in.
339 * @return {file} - the most likely candidate.
340 */
341 getMostLikelyCandidateFile(dataType, candidateFiles) {
342 const keyWordsByDataType = {
343 [FILE_TYPES.WINDOW_MANAGER_DUMP]: 'window',
344 [FILE_TYPES.SURFACE_FLINGER_DUMP]: 'surface',
345 };
346
347 if (
348 !candidateFiles ||
349 !candidateFiles.length ||
350 candidateFiles.length == 0
351 ) {
352 throw new Error('No candidate files provided');
353 }
354
355 if (!keyWordsByDataType.hasOwnProperty(dataType)) {
356 console.warn(`setMostLikelyCandidateFile doesn't know how to handle ` +
357 `candidates of dataType ${dataType} setting last candidate as ` +
358 `target file.`);
359
360 // We want to return the last candidate file so that, we always override
361 // old uploaded files with once of the latest uploaded files.
362 return candidateFiles.slice(-1)[0];
363 }
364
365 for (const file of candidateFiles) {
366 if (file.filename
367 .toLowerCase().includes(keyWordsByDataType[dataType])) {
368 return file;
369 }
370 }
371
372 // We want to return the last candidate file so that, we always override
373 // old uploaded files with once of the latest uploaded files.
374 return candidateFiles.slice(-1)[0];
375 },
376
377 /**
378 * Display a snackbar warning that files have been overriden and any
379 * relavant additional information in the logs.
380 * @param {{string: file[]}} overriddenFiles - a mapping from data types to
381 * the files of the of that datatype tha have been overriden.
382 */
383 displayFilesOverridenWarning(overriddenFiles) {
384 const overriddenFileTypes = Object.keys(overriddenFiles);
385 const overriddenCount = Object.values(overriddenFiles)
386 .map((files) => files.length).reduce((length, next) => length + next);
387
388 if (overriddenFileTypes.length === 1 && overriddenCount === 1) {
389 const type = overriddenFileTypes.values().next().value;
390 const overriddenFile = overriddenFiles[type][0].filename;
391 const keptFile = this.dataFiles[type].filename;
392 const message =
393 `'${overriddenFile}' is conflicting with '${keptFile}'. ` +
394 `Only '${keptFile}' will be kept. If you wish to display ` +
395 `'${overriddenFile}', please upload it again with no other file ` +
396 `of the same type.`;
397
398 this.showSnackbarMessage(`WARNING: ${message}`, Infinity);
399 console.warn(message);
400 } else {
401 const message = `Mutiple conflicting files have been uploaded. ` +
402 `${overriddenCount} files have been discarded. Please check the ` +
403 `developer console for more information.`;
404 this.showSnackbarMessage(`WARNING: ${message}`, Infinity);
405
406 const messageBuilder = [];
407 for (const type of overriddenFileTypes.values()) {
408 const keptFile = this.dataFiles[type].filename;
409 const overriddenFilesCount = overriddenFiles[type].length;
410
411 messageBuilder.push(`${overriddenFilesCount} file` +
412 `${overriddenFilesCount > 1 ? 's' : ''} of type ${type} ` +
413 `${overriddenFilesCount > 1 ? 'have' : 'has'} been ` +
414 `overridden. Only '${keptFile}' has been kept.`);
415 }
416
417 messageBuilder.push('');
418 messageBuilder.push('Please reupload the specific files you want ' +
419 'to read (one of each type).');
420 messageBuilder.push('');
421
422 messageBuilder.push('===============DISCARDED FILES===============');
423
424 for (const type of overriddenFileTypes.values()) {
425 const discardedFiles = overriddenFiles[type];
426
427 messageBuilder.push(`The following files of type ${type} ` +
428 `have been discarded:`);
429 for (const discardedFile of discardedFiles) {
430 messageBuilder.push(` - ${discardedFile.filename}`);
431 }
432 messageBuilder.push('');
433 }
434
435 console.warn(messageBuilder.join('\n'));
436 }
437 },
438
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100439 getFileExtensions(file) {
440 const split = file.name.split('.');
441 if (split.length > 1) {
442 return split.pop();
443 }
444
445 return undefined;
446 },
Pablo Gamitoed709402020-06-08 13:18:15 +0100447 async addFile(file) {
448 const decodedFiles = [];
449 const type = this.fileType;
Pablo Gamito3db5c7d2020-06-08 10:52:14 +0100450
Pablo Gamitoc2ff9842020-06-18 16:01:57 +0100451 const extension = this.getFileExtensions(file);
452
Pablo Gamitobea65822020-09-02 16:35:32 +0200453 // extension === 'zip' is required on top of file.type ===
454 // 'application/zip' because when loaded from the extension the type is
455 // incorrect. See comment in loadFilesFromExtension() for more
456 // information.
Pablo Gamitoed709402020-06-08 13:18:15 +0100457 if (type === 'bugreport' ||
Pablo Gamitobea65822020-09-02 16:35:32 +0200458 (type === 'auto' && (extension === 'zip' ||
459 file.type === 'application/zip'))) {
Pablo Gamitof77c4522020-07-20 11:42:50 +0100460 const results = await this.decodeArchive(file);
Pablo Gamitoed709402020-06-08 13:18:15 +0100461 decodedFiles.push(...results);
462 } else {
463 const decodedFile = await this.decodeFile(file);
464 decodedFiles.push(decodedFile);
465 }
Pablo Gamito3db5c7d2020-06-08 10:52:14 +0100466
Pablo Gamitoed709402020-06-08 13:18:15 +0100467 return decodedFiles;
468 },
469 readFile(file) {
470 return new Promise((resolve, _) => {
Pablo Gamito3db5c7d2020-06-08 10:52:14 +0100471 const reader = new FileReader();
Pablo Gamitoed709402020-06-08 13:18:15 +0100472 reader.onload = async (e) => {
Pablo Gamito3db5c7d2020-06-08 10:52:14 +0100473 const buffer = new Uint8Array(e.target.result);
Pablo Gamitoed709402020-06-08 13:18:15 +0100474 resolve(buffer);
475 };
Pablo Gamito3db5c7d2020-06-08 10:52:14 +0100476 reader.readAsArrayBuffer(file);
477 });
Adam Pardyld905ed22019-07-08 18:22:39 +0200478 },
Pablo Gamitoed709402020-06-08 13:18:15 +0100479 async decodeFile(file) {
Pablo Gamitoed709402020-06-08 13:18:15 +0100480 const buffer = await this.readFile(file);
481
Pablo Gamitof25221c2020-08-06 18:26:42 +0100482 let filetype = this.filetype;
483 let data;
484 if (filetype) {
485 const fileDecoder = FILE_DECODERS[filetype];
Pablo Gamitobea65822020-09-02 16:35:32 +0200486 data = fileDecoder.decoder(
487 buffer, fileDecoder.decoderParams, file.name, this.store);
Pablo Gamitoed709402020-06-08 13:18:15 +0100488 } else {
Pablo Gamitof25221c2020-08-06 18:26:42 +0100489 // Defaulting to auto — will attempt to detect file type
Pablo Gamitoed709402020-06-08 13:18:15 +0100490 [filetype, data] = detectAndDecode(buffer, file.name, this.store);
491 }
492
493 return {filetype, data};
494 },
Pablo Gamitof77c4522020-07-20 11:42:50 +0100495 async decodeArchive(archive) {
496 const buffer = await this.readFile(archive);
Pablo Gamitoed709402020-06-08 13:18:15 +0100497
498 const zip = new JSZip();
499 const content = await zip.loadAsync(buffer);
500
501 const decodedFiles = [];
Pablo Gamitoed709402020-06-08 13:18:15 +0100502
Pablo Gamito348275d2020-07-17 19:18:47 +0100503 for (const filename in content.files) {
Pablo Gamitobea65822020-09-02 16:35:32 +0200504 if (content.files.hasOwnProperty(filename)) {
505 const file = content.files[filename];
Pablo Gamito1ca3eda2020-09-02 17:59:52 +0200506 if (file.dir) {
507 // Ignore directories
508 continue;
509 }
Pablo Gamito348275d2020-07-17 19:18:47 +0100510
Pablo Gamitobea65822020-09-02 16:35:32 +0200511 const fileBlob = await file.async('blob');
Pablo Gamito1ca3eda2020-09-02 17:59:52 +0200512 // Get only filename and remove rest of path
513 fileBlob.name = filename.split('/').slice(-1).pop();
Pablo Gamito348275d2020-07-17 19:18:47 +0100514
Pablo Gamitobea65822020-09-02 16:35:32 +0200515 try {
516 const decodedFile = await this.decodeFile(fileBlob);
Pablo Gamitof77c4522020-07-20 11:42:50 +0100517
Pablo Gamitobea65822020-09-02 16:35:32 +0200518 decodedFiles.push(decodedFile);
519 } catch (e) {
520 if (!(e instanceof UndetectableFileType)) {
521 throw e;
522 }
Pablo Gamito348275d2020-07-17 19:18:47 +0100523 }
Pablo Gamitoed709402020-06-08 13:18:15 +0100524 }
525 }
526
Pablo Gamito82639302020-07-17 18:38:37 +0100527 if (decodedFiles.length == 0) {
Pablo Gamitobea65822020-09-02 16:35:32 +0200528 throw new Error('No matching files found in archive', archive);
Pablo Gamito82639302020-07-17 18:38:37 +0100529 }
530
Pablo Gamitoed709402020-06-08 13:18:15 +0100531 return decodedFiles;
532 },
Adam Pardyld905ed22019-07-08 18:22:39 +0200533 onRemoveFile(typeName) {
534 this.$delete(this.dataFiles, typeName);
535 },
536 onSubmit() {
Pablo Gamitobea65822020-09-02 16:35:32 +0200537 this.$emit('dataReady',
538 Object.keys(this.dataFiles).map((key) => this.dataFiles[key]));
539 },
Adam Pardyld905ed22019-07-08 18:22:39 +0200540 },
541 computed: {
Pablo Gamitobea65822020-09-02 16:35:32 +0200542 dataReady: function() {
543 return Object.keys(this.dataFiles).length > 0;
544 },
Pablo Gamito050e20b2020-07-01 16:41:56 +0100545 },
546 components: {
547 'flat-card': FlatCard,
548 },
Pablo Gamitobea65822020-09-02 16:35:32 +0200549};
Adam Pardyld905ed22019-07-08 18:22:39 +0200550
Nataniel Borges39071542021-09-22 09:19:41 +0000551</script>
552<style>
553 .dropbox:hover {
554 background: rgb(224, 224, 224);
555 }
556
557 .dropbox p {
558 font-size: 1.2em;
559 text-align: center;
560 padding: 50px 10px;
561 }
562
563 .dropbox {
564 outline: 2px dashed #448aff; /* the dash box */
565 outline-offset: -10px;
566 background: white;
567 color: #448aff;
568 padding: 10px 10px 10px 10px;
569 min-height: 200px; /* minimum height */
570 position: relative;
571 cursor: pointer;
572 }
573
574 .progress-spinner {
575 display: block;
576 }
577</style>