]> git.immae.eu Git - perso/Immae/Projets/Nodejs/Surfer.git/blame - frontend/js/app.js
Fix the file drop events
[perso/Immae/Projets/Nodejs/Surfer.git] / frontend / js / app.js
CommitLineData
6eb72d64
JZ
1(function () {
2'use strict';
3
4a27fce7
JZ
4function getProfile(accessToken, callback) {
5 callback = callback || function (error) { if (error) console.error(error); };
6
7 superagent.get('/api/profile').query({ access_token: accessToken }).end(function (error, result) {
8 app.busy = false;
13df9f95 9 app.ready = true;
4a27fce7
JZ
10
11 if (error && !error.response) return callback(error);
12 if (result.statusCode !== 200) {
13 delete localStorage.accessToken;
14 return callback('Invalid access token');
15 }
16
17 localStorage.accessToken = accessToken;
18 app.session.username = result.body.username;
19 app.session.valid = true;
20
21 callback();
22 });
23}
24
d3312ed1
JZ
25function sanitize(filePath) {
26 filePath = '/' + filePath;
27 return filePath.replace(/\/+/g, '/');
28}
29
537bfb04
JZ
30function encode(filePath) {
31 return filePath.split('/').map(encodeURIComponent).join('/');
32}
33
04bc2989
JZ
34function decode(filePath) {
35 return filePath.split('/').map(decodeURIComponent).join('/');
36}
37
a26d1f9b
JZ
38var mimeTypes = {
39 images: [ '.png', '.jpg', '.jpeg', '.tiff', '.gif' ],
40 text: [ '.txt', '.md' ],
41 pdf: [ '.pdf' ],
42 html: [ '.html', '.htm', '.php' ],
43 video: [ '.mp4', '.mpg', '.mpeg', '.ogg', '.mkv' ]
44};
45
46function getPreviewUrl(entry, basePath) {
47 var path = '/_admin/img/';
48
49 if (entry.isDirectory) return path + 'directory.png';
50 if (mimeTypes.images.some(function (e) { return entry.filePath.endsWith(e); })) return sanitize(basePath + '/' + entry.filePath);
51 if (mimeTypes.text.some(function (e) { return entry.filePath.endsWith(e); })) return path +'text.png';
52 if (mimeTypes.pdf.some(function (e) { return entry.filePath.endsWith(e); })) return path + 'pdf.png';
53 if (mimeTypes.html.some(function (e) { return entry.filePath.endsWith(e); })) return path + 'html.png';
54 if (mimeTypes.video.some(function (e) { return entry.filePath.endsWith(e); })) return path + 'video.png';
55
56 return path + 'unknown.png';
57}
58
8a3d0eee
JZ
59// simple extension detection, does not work with double extension like .tar.gz
60function getExtension(entry) {
61 if (entry.isFile) return entry.filePath.slice(entry.filePath.lastIndexOf('.') + 1);
62 return '';
63}
64
537bfb04
JZ
65function refresh() {
66 loadDirectory(app.path);
67}
68
d3312ed1
JZ
69function loadDirectory(filePath) {
70 app.busy = true;
71
72 filePath = filePath ? sanitize(filePath) : '/';
73
4a27fce7 74 superagent.get('/api/files/' + encode(filePath)).query({ access_token: localStorage.accessToken }).end(function (error, result) {
d3312ed1
JZ
75 app.busy = false;
76
9209abec 77 if (result && result.statusCode === 401) return logout();
d3312ed1 78 if (error) return console.error(error);
d3312ed1 79
04bc2989 80 result.body.entries.sort(function (a, b) { return a.isDirectory && b.isFile ? -1 : 1; });
a26d1f9b
JZ
81 app.entries = result.body.entries.map(function (entry) {
82 entry.previewUrl = getPreviewUrl(entry, filePath);
8a3d0eee 83 entry.extension = getExtension(entry);
a26d1f9b
JZ
84 return entry;
85 });
d3312ed1 86 app.path = filePath;
51723cdf
J
87 app.pathParts = decode(filePath).split('/').filter(function (e) { return !!e; }).map(function (e, i, a) {
88 return {
89 name: e,
90 link: '#' + sanitize('/' + a.slice(0, i).join('/') + '/' + e)
91 };
92 });
04bc2989
JZ
93
94 // update in case this was triggered from code
95 window.location.hash = app.path;
d3312ed1
JZ
96 });
97}
98
13df9f95
JZ
99function open(row, event, column) {
100 var path = sanitize(app.path + '/' + row.filePath);
d3312ed1 101
13df9f95 102 if (row.isDirectory) {
04bc2989
JZ
103 window.location.hash = path;
104 return;
105 }
d3312ed1 106
5f43935e 107 window.open(encode(path));
d3312ed1
JZ
108}
109
110function up() {
5f43935e 111 window.location.hash = sanitize(app.path.split('/').slice(0, -1).filter(function (p) { return !!p; }).join('/'));
d3312ed1
JZ
112}
113
b094fada
JZ
114function uploadFiles(files) {
115 if (!files || !files.length) return;
116
117 app.uploadStatus = {
118 busy: true,
119 count: files.length,
120 done: 0,
121 percentDone: 0
122 };
123
124 function uploadFile(file) {
125 var path = encode(sanitize(app.path + '/' + file.name));
126
127 var formData = new FormData();
128 formData.append('file', file);
129
4a27fce7 130 superagent.post('/api/files' + path).query({ access_token: localStorage.accessToken }).send(formData).end(function (error, result) {
b094fada
JZ
131 if (result && result.statusCode === 401) return logout();
132 if (result && result.statusCode !== 201) console.error('Error uploading file: ', result.statusCode);
133 if (error) console.error(error);
134
135 app.uploadStatus.done += 1;
136 app.uploadStatus.percentDone = Math.round(app.uploadStatus.done / app.uploadStatus.count * 100);
137
138 if (app.uploadStatus.done >= app.uploadStatus.count) {
139 app.uploadStatus = {
140 busy: false,
141 count: 0,
142 done: 0,
143 percentDone: 100
144 };
145
146 refresh();
147 }
148 });
149 }
150
151 for(var i = 0; i < app.uploadStatus.count; ++i) {
152 uploadFile(files[i]);
153 }
154}
155
b094fada
JZ
156function dragOver(event) {
157 event.preventDefault();
158}
159
160function drop(event) {
161 event.preventDefault();
162 uploadFiles(event.dataTransfer.files || []);
163}
164
6eb72d64
JZ
165var app = new Vue({
166 el: '#app',
167 data: {
13df9f95 168 ready: false,
6eb72d64 169 busy: true,
fea6789c
JZ
170 uploadStatus: {
171 busy: false,
172 count: 0,
173 done: 0,
174 percentDone: 50
175 },
d3312ed1
JZ
176 path: '/',
177 pathParts: [],
6eb72d64
JZ
178 session: {
179 valid: false
180 },
13df9f95
JZ
181 folderListingEnabled: false,
182 loginData: {
183 username: '',
184 password: ''
e628921a 185 },
d3312ed1 186 entries: []
6eb72d64
JZ
187 },
188 methods: {
13df9f95
JZ
189 onLogin: function () {
190 app.busy = true;
191
192 superagent.post('/api/login').send({ username: app.loginData.username, password: app.loginData.password }).end(function (error, result) {
193 app.busy = false;
194
195 if (error) return console.error(error);
196 if (result.statusCode === 401) return console.error('Invalid credentials');
197
198 getProfile(result.body.accessToken, function (error) {
199 if (error) return console.error(error);
200
201 loadDirectory(window.location.hash.slice(1));
202 });
203 });
204 },
205 onOptionsMenu: function (command) {
206 if (command === 'folderListing') {
207 console.log('Not implemented');
208 } else if (command === 'about') {
209 this.$msgbox({
210 title: 'About Surfer',
211 message: 'Surfer is a static file server written by <a href="https://cloudron.io" target="_blank">Cloudron</a>.<br/><br/>The source code is licensed under MIT and available <a href="https://git.cloudron.io/cloudron/surfer" target="_blank">here</a>.',
212 dangerouslyUseHTMLString: true,
213 confirmButtonText: 'OK',
214 showCancelButton: false,
215 type: 'info',
216 center: true
217 }).then(function () {}).catch(function () {});
218 } else if (command === 'logout') {
219 superagent.post('/api/logout').query({ access_token: localStorage.accessToken }).end(function (error) {
220 if (error) console.error(error);
221
222 app.session.valid = false;
223
224 delete localStorage.accessToken;
225 });
226 }
227 },
228 onDownload: function (entry) {
229 if (entry.isDirectory) return;
230 window.location.href = encode('/api/files/' + sanitize(app.path + '/' + entry.filePath)) + '?access_token=' + localStorage.accessToken;
231 },
232 onUpload: function () {
233 $(app.$refs.upload).on('change', function () {
234
235 // detach event handler
236 $(app.$refs.upload).off('change');
237
238 uploadFiles(app.$refs.upload.files || []);
239 });
240
241 // reset the form first to make the change handler retrigger even on the same file selected
242 app.$refs.upload.value = '';
243 app.$refs.upload.click();
244 },
245 onDelete: function (entry) {
246 var title = 'Really delete ' + (entry.isDirectory ? 'folder ' : '') + entry.filePath;
247 this.$confirm('', title, { confirmButtonText: 'Yes', cancelButtonText: 'No' }).then(function () {
248 var path = encode(sanitize(app.path + '/' + entry.filePath));
249
250 superagent.del('/api/files' + path).query({ access_token: localStorage.accessToken, recursive: true }).end(function (error, result) {
251 if (result && result.statusCode === 401) return logout();
252 if (result && result.statusCode !== 200) return console.error('Error deleting file: ', result.statusCode);
253 if (error) return console.error(error);
254
255 refresh();
256 });
257 }).catch(function () {
258 console.log('delete error:', arguments);
259 });
260 },
261 onRename: function (entry) {
262 var title = 'Rename ' + entry.filePath;
263 this.$prompt('', title, { confirmButtonText: 'Yes', cancelButtonText: 'No', inputPlaceholder: 'new filename', inputValue: entry.filePath }).then(function (data) {
264 var path = encode(sanitize(app.path + '/' + entry.filePath));
265 var newFilePath = sanitize(app.path + '/' + data.value);
266
267 superagent.put('/api/files' + path).query({ access_token: localStorage.accessToken }).send({ newFilePath: newFilePath }).end(function (error, result) {
268 if (result && result.statusCode === 401) return logout();
269 if (result && result.statusCode !== 200) return console.error('Error renaming file: ', result.statusCode);
270 if (error) return console.error(error);
271
272 refresh();
273 });
274 }).catch(function () {
275 console.log('rename error:', arguments);
276 });
277 },
278 onNewFolder: function () {
279 var title = 'Create New Folder';
280 this.$prompt('', title, { confirmButtonText: 'Yes', cancelButtonText: 'No', inputPlaceholder: 'new foldername' }).then(function (data) {
281 var path = encode(sanitize(app.path + '/' + data.value));
282
283 superagent.post('/api/files' + path).query({ access_token: localStorage.accessToken, directory: true }).end(function (error, result) {
284 if (result && result.statusCode === 401) return logout();
285 if (result && result.statusCode === 403) return console.error('Name not allowed');
286 if (result && result.statusCode === 409) return console.error('Directory already exists');
287 if (result && result.statusCode !== 201) return console.error('Error creating directory: ', result.statusCode);
288 if (error) return console.error(error);
289
290 refresh();
291 });
292 }).catch(function () {
293 console.log('create folder error:', arguments);
294 });
295 },
296 prettyDate: function (row, column, cellValue, index) {
297 var date = new Date(cellValue),
298 diff = (((new Date()).getTime() - date.getTime()) / 1000),
299 day_diff = Math.floor(diff / 86400);
300
301 if (isNaN(day_diff) || day_diff < 0)
302 return;
303
304 return day_diff === 0 && (
305 diff < 60 && 'just now' ||
306 diff < 120 && '1 minute ago' ||
307 diff < 3600 && Math.floor( diff / 60 ) + ' minutes ago' ||
308 diff < 7200 && '1 hour ago' ||
309 diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') ||
310 day_diff === 1 && 'Yesterday' ||
311 day_diff < 7 && day_diff + ' days ago' ||
312 day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago' ||
313 day_diff < 365 && Math.round( day_diff / 30 ) + ' months ago' ||
314 Math.round( day_diff / 365 ) + ' years ago';
315 },
316 prettyFileSize: function (row, column, cellValue, index) {
317 return filesize(cellValue);
318 },
d3312ed1 319 loadDirectory: loadDirectory,
537bfb04 320 up: up,
13df9f95 321 open: open,
b094fada
JZ
322 drop: drop,
323 dragOver: dragOver
6eb72d64
JZ
324 }
325});
326
906f293e
JZ
327getProfile(localStorage.accessToken, function (error) {
328 if (error) return console.error(error);
329
330 loadDirectory(window.location.hash.slice(1));
331});
6eb72d64 332
04bc2989
JZ
333$(window).on('hashchange', function () {
334 loadDirectory(window.location.hash.slice(1));
335});
336
6eb72d64 337})();