]> git.immae.eu Git - perso/Immae/Projets/Nodejs/Surfer.git/blob - frontend/js/app.js
d659b180c86a1c7b3972dc4d31254228af5718b2
[perso/Immae/Projets/Nodejs/Surfer.git] / frontend / js / app.js
1 (function () {
2 'use strict';
3
4 function 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;
9 app.ready = true;
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
25 function sanitize(filePath) {
26 filePath = '/' + filePath;
27 return filePath.replace(/\/+/g, '/');
28 }
29
30 function encode(filePath) {
31 return filePath.split('/').map(encodeURIComponent).join('/');
32 }
33
34 function decode(filePath) {
35 return filePath.split('/').map(decodeURIComponent).join('/');
36 }
37
38 var 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
46 function 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
59 // simple extension detection, does not work with double extension like .tar.gz
60 function getExtension(entry) {
61 if (entry.isFile) return entry.filePath.slice(entry.filePath.lastIndexOf('.') + 1);
62 return '';
63 }
64
65 function refresh() {
66 loadDirectory(app.path);
67 }
68
69 function loadDirectory(filePath) {
70 app.busy = true;
71
72 filePath = filePath ? sanitize(filePath) : '/';
73
74 superagent.get('/api/files/' + encode(filePath)).query({ access_token: localStorage.accessToken }).end(function (error, result) {
75 app.busy = false;
76
77 if (result && result.statusCode === 401) return logout();
78 if (error) return console.error(error);
79
80 result.body.entries.sort(function (a, b) { return a.isDirectory && b.isFile ? -1 : 1; });
81 app.entries = result.body.entries.map(function (entry) {
82 entry.previewUrl = getPreviewUrl(entry, filePath);
83 entry.extension = getExtension(entry);
84 return entry;
85 });
86 app.path = filePath;
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 });
93
94 // update in case this was triggered from code
95 window.location.hash = app.path;
96 });
97 }
98
99 function open(row, event, column) {
100 var path = sanitize(app.path + '/' + row.filePath);
101
102 if (row.isDirectory) {
103 window.location.hash = path;
104 return;
105 }
106
107 window.open(encode(path));
108 }
109
110 function up() {
111 window.location.hash = sanitize(app.path.split('/').slice(0, -1).filter(function (p) { return !!p; }).join('/'));
112 }
113
114 function 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
130 superagent.post('/api/files' + path).query({ access_token: localStorage.accessToken }).send(formData).end(function (error, result) {
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
156 function dragOver(event) {
157 event.preventDefault();
158 }
159
160 function drop(event) {
161 event.preventDefault();
162 uploadFiles(event.dataTransfer.files || []);
163 }
164
165 var app = new Vue({
166 el: '#app',
167 data: {
168 ready: false,
169 busy: true,
170 uploadStatus: {
171 busy: false,
172 count: 0,
173 done: 0,
174 percentDone: 50
175 },
176 path: '/',
177 pathParts: [],
178 session: {
179 valid: false
180 },
181 folderListingEnabled: false,
182 loginData: {
183 username: '',
184 password: ''
185 },
186 entries: []
187 },
188 methods: {
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 },
319 loadDirectory: loadDirectory,
320 up: up,
321 open: open,
322 drop: drop,
323 dragOver: dragOver
324 }
325 });
326
327 getProfile(localStorage.accessToken, function (error) {
328 if (error) return console.error(error);
329
330 loadDirectory(window.location.hash.slice(1));
331 });
332
333 $(window).on('hashchange', function () {
334 loadDirectory(window.location.hash.slice(1));
335 });
336
337 })();