64688dcd8287a4f40c0472a84191ad157a43d5fd
[WebKit-https.git] / Websites / perf.webkit.org / public / v3 / components / instant-file-uploader.js
1 class InstantFileUploader extends ComponentBase {
2     constructor()
3     {
4         super('instant-file-uploader');
5         this._uploadedFiles = [];
6         this._preuploadFiles = [];
7         this._uploadProgress = new WeakMap;
8         this._fileSizeFormatter = Metric.makeFormatter('B', 3);
9
10         this._renderUploadedFilesLazily = new LazilyEvaluatedFunction(this._renderUploadedFiles.bind(this));
11         this._renderPreuploadFilesLazily = new LazilyEvaluatedFunction(this._renderPreuploadFiles.bind(this));
12     }
13
14     hasFileToUpload() { return !!this._preuploadFiles.length; }
15     uploadedFiles() { return this._uploadedFiles; }
16
17     addUploadedFile(uploadedFile)
18     {
19         console.assert(uploadedFile instanceof UploadedFile);
20         if (this._uploadedFiles.includes(uploadedFile))
21             return;
22         this._uploadedFiles.push(uploadedFile);
23         this.enqueueToRender();
24     }
25
26     didConstructShadowTree()
27     {
28         const input = this.content('file-input');
29         input.onchange = () => this._didFileInputChange(input);
30     }
31
32     render()
33     {
34         this._renderUploadedFilesLazily.evaluate(...this._uploadedFiles);
35         const uploadStatusElements = this._renderPreuploadFilesLazily.evaluate(...this._preuploadFiles);
36         this._updateUploadStatus(uploadStatusElements);
37     }
38
39     _renderUploadedFiles(...uploadedFiles)
40     {
41         const element = ComponentBase.createElement;
42         this.renderReplace(this.content('uploaded-files'), uploadedFiles.map((uploadedFile) => {
43             const authorInfo = uploadedFile.author() ? ' by ' + uploadedFile.author() : '';
44             const createdAt = Metric.formatTime(uploadedFile.createdAt());
45             const deleteButton = new CloseButton;
46             deleteButton.listenToAction('activate', () => this._removeUploadedFile(uploadedFile));
47             return element('li', [
48                 deleteButton,
49                 element('code', {class: 'filename'}, uploadedFile.filename()),
50                 ' ',
51                 element('small', {class: 'filesize'}, '(' + this._fileSizeFormatter(uploadedFile.size()) + ')'),
52                 element('small', {class: 'meta'}, `Uploaded${authorInfo} on ${createdAt}`),
53             ]);
54         }));
55     }
56
57     _renderPreuploadFiles(...preuploadFiles)
58     {
59         const element = ComponentBase.createElement;
60         const uploadStatusElements = [];
61         this.renderReplace(this.content('preupload-files'), preuploadFiles.map((file) => {
62             const progressBar = element('progress');
63             const meta = element('small', {class: 'meta'}, progressBar);
64             uploadStatusElements.push({file, meta, progressBar});
65
66             return element('li', [
67                 element('code', file.name),
68                 ' ',
69                 element('small', {class: 'filesize'}, '(' + this._fileSizeFormatter(file.size) + ')'),
70                 meta,
71             ]);
72         }));
73         return uploadStatusElements;
74     }
75
76     _updateUploadStatus(uploadStatusElements)
77     {
78         for (let entry of uploadStatusElements) {
79             const progress = this._uploadProgress.get(entry.file);
80             const progressBar = entry.progressBar;
81             if (!progress) {
82                 progressBar.removeAttribute('max');
83                 progressBar.removeAttribute('value');
84                 return;
85             }
86             if (progress.error) {
87                 entry.meta.classList.add('hasError');
88                 entry.meta.textContent = this._formatUploadError(progress.error);
89             } else {
90                 progressBar.max = progress.total;
91                 progressBar.value = progress.loaded;
92             }
93         }
94     }
95
96     _formatUploadError(error)
97     {
98         switch (error) {
99         case 'NotSupported':
100             return 'Failed: File uploading is disabled';
101         case 'FileSizeLimitExceeded':
102             return 'Failed: The uploaded file was too big';
103         case 'FileSizeQuotaExceeded':
104             return 'Failed: Exceeded file upload quota';
105         }
106         return 'Failed to upload the file';
107     }
108
109     _didFileInputChange(input)
110     {
111         if (!input.files.length)
112             return;
113         const limit = UploadedFile.fileUploadSizeLimit;
114         for (let file of input.files) {
115             if (file.size > limit) {
116                 alert(`The specified file "${file.name}" is too big (${this._fileSizeFormatter(file.size)}). It must be smaller than ${this._fileSizeFormatter(limit)}`);
117                 input.value = null;
118                 return;
119             }
120             UploadedFile.fetchUnloadedFileWithIdenticalHash(file).then((uploadedFile) => {
121                 if (uploadedFile) {
122                     this._didUploadFile(file, uploadedFile);
123                     return;
124                 }
125
126                 UploadedFile.uploadFile(file, (progress) => {
127                     this._uploadProgress.set(file, progress);
128                     this.enqueueToRender();
129                 }).then((uploadedFile) => {
130                     this._didUploadFile(file, uploadedFile);
131                 }, (error) => {
132                     this._uploadProgress.set(file, {error: error === 0 ? 'UnknownError' : error});
133                     this.enqueueToRender();
134                 });
135             });
136         }
137         this._preuploadFiles = Array.from(input.files);
138         input.value = null;
139
140         this.enqueueToRender();
141     }
142
143     _removeUploadedFile(uploadedFile)
144     {
145         // FIXME: Send a request to delete the file.
146         console.assert(uploadedFile instanceof UploadedFile);
147         const index = this._uploadedFiles.indexOf(uploadedFile);
148         if (index < 0)
149             return;
150         this._uploadedFiles.splice(index, 1);
151         this.dispatchAction('removedFile', uploadedFile);
152         this.enqueueToRender();
153     }
154
155     _didUploadFile(file, uploadedFile)
156     {
157         console.assert(file instanceof File);
158         const index = this._preuploadFiles.indexOf(file);
159         if (index >= 0)
160             this._preuploadFiles.splice(index, 1);
161         this._uploadedFiles.push(uploadedFile);
162         this.dispatchAction('uploadedFile', uploadedFile);
163         this.enqueueToRender();
164     }
165
166     static htmlTemplate()
167     {
168         return `<ul id="uploaded-files"></ul>
169             <ul id="preupload-files"></ul>
170             <input id="file-input" type="file" multiple="false">`;
171     }
172
173     static cssTemplate()
174     {
175         return `
176             ul:empty {
177                 display: none;
178             }
179
180             ul, li {
181                 margin: 0;
182                 padding: 0;
183                 list-style: none;
184             }
185
186             li {
187                 position: relative;
188                 margin-bottom: 0.25rem;
189                 padding-left: 1.5rem;
190                 padding-bottom: 0.25rem;
191                 border-bottom: solid 1px #eee;
192             }
193
194             li:last-child {
195                 border-bottom: none;
196             }
197
198             li > close-button {
199                 position: absolute;
200                 left: 0;
201                 top: 50%;
202                 margin-top: -0.5rem;
203             }
204
205             li > progress {
206                 display: block;
207             }
208
209             code {
210                 font-size: 1.1rem;
211                 font-weight: inherit;
212             }
213
214             small {
215                 font-size: 0.8rem;
216                 font-weight: inherit;
217                 color: #666;
218             }
219
220             small.meta {
221                 display: block;
222             }
223
224             .hasError {
225                 color: #c60;
226                 font-weight: normal;
227             }
228         `;
229     }
230 }
231
232 ComponentBase.defineElement('instant-file-uploader', InstantFileUploader);