]> git.immae.eu Git - perso/Immae/Projets/Nodejs/Surfer.git/blobdiff - frontend/js/app.js
Make folder listing a global setting
[perso/Immae/Projets/Nodejs/Surfer.git] / frontend / js / app.js
index 078473a9516e55901bdcd621bdbb76ef0e433009..e9d469be99d400656e7107259265c8f2503ece1d 100644 (file)
@@ -1,37 +1,42 @@
 (function () {
 'use strict';
 
-function login(username, password) {
-    username = username || app.loginData.username;
-    password = password || app.loginData.password;
+// poor man's async
+function asyncForEach(items, handler, callback) {
+    var cur = 0;
 
-    app.busy = true;
+    if (items.length === 0) return callback();
 
-    superagent.get('/api/files/').query({ username: username, password: password }).end(function (error, result) {
-        app.busy = false;
+    (function iterator() {
+        handler(items[cur], function (error) {
+            if (error) return callback(error);
+            if (cur >= items.length-1) return callback();
+            ++cur;
 
-        if (error) return console.error(error);
-        if (result.statusCode === 401) return console.error('Invalid credentials');
+            iterator();
+        });
+    })();
+}
 
-        app.session.valid = true;
-        app.session.username = username;
-        app.session.password = password;
+function getProfile(accessToken, callback) {
+    callback = callback || function (error) { if (error) console.error(error); };
 
-        // clearly not the best option
-        localStorage.username = username;
-        localStorage.password = password;
+    superagent.get('/api/profile').query({ access_token: accessToken }).end(function (error, result) {
+        app.busy = false;
+        app.ready = true;
 
-        loadDirectory(window.location.hash.slice(1));
-    });
-}
+        if (error && !error.response) return callback(error);
+        if (result.statusCode !== 200) {
+            delete localStorage.accessToken;
+            return callback('Invalid access token');
+        }
 
-function logout() {
-    app.session.valid = false;
-    app.session.username = null;
-    app.session.password = null;
+        localStorage.accessToken = accessToken;
+        app.session.username = result.body.username;
+        app.session.valid = true;
 
-    delete localStorage.username;
-    delete localStorage.password;
+        callback();
+    });
 }
 
 function sanitize(filePath) {
@@ -68,6 +73,12 @@ function getPreviewUrl(entry, basePath) {
     return path + 'unknown.png';
 }
 
+// simple extension detection, does not work with double extension like .tar.gz
+function getExtension(entry) {
+    if (entry.isFile) return entry.filePath.slice(entry.filePath.lastIndexOf('.') + 1);
+    return '';
+}
+
 function refresh() {
     loadDirectory(app.path);
 }
@@ -77,7 +88,7 @@ function loadDirectory(filePath) {
 
     filePath = filePath ? sanitize(filePath) : '/';
 
-    superagent.get('/api/files/' + encode(filePath)).query({ username: app.session.username, password: app.session.password }).end(function (error, result) {
+    superagent.get('/api/files/' + encode(filePath)).query({ access_token: localStorage.accessToken }).end(function (error, result) {
         app.busy = false;
 
         if (result && result.statusCode === 401) return logout();
@@ -86,6 +97,7 @@ function loadDirectory(filePath) {
         result.body.entries.sort(function (a, b) { return a.isDirectory && b.isFile ? -1 : 1; });
         app.entries = result.body.entries.map(function (entry) {
             entry.previewUrl = getPreviewUrl(entry, filePath);
+            entry.extension = getExtension(entry);
             return entry;
         });
         app.path = filePath;
@@ -98,19 +110,13 @@ function loadDirectory(filePath) {
 
         // update in case this was triggered from code
         window.location.hash = app.path;
-
-        Vue.nextTick(function () {
-            $(function () {
-                $('[data-toggle="tooltip"]').tooltip();
-            });
-        });
     });
 }
 
-function open(entry) {
-    var path = sanitize(app.path + '/' + entry.filePath);
+function open(row, event, column) {
+    var path = sanitize(app.path + '/' + row.filePath);
 
-    if (entry.isDirectory) {
+    if (row.isDirectory) {
         window.location.hash = path;
         return;
     }
@@ -118,157 +124,55 @@ function open(entry) {
     window.open(encode(path));
 }
 
-function up() {
-    window.location.hash = sanitize(app.path.split('/').slice(0, -1).filter(function (p) { return !!p; }).join('/'));
-}
-
-function upload() {
-    $(app.$els.upload).on('change', function () {
-
-        // detach event handler
-        $(app.$els.upload).off('change');
-
-        app.uploadStatus = {
-            busy: true,
-            count: app.$els.upload.files.length,
-            done: 0,
-            percentDone: 0
-        };
-
-        function uploadFile(file) {
-            var path = encode(sanitize(app.path + '/' + file.name));
+function uploadFiles(files) {
+    if (!files || !files.length) return;
 
-            var formData = new FormData();
-            formData.append('file', file);
+    app.uploadStatus.busy = true;
+    app.uploadStatus.count = files.length;
+    app.uploadStatus.done = 0;
+    app.uploadStatus.percentDone = 0;
 
-            superagent.post('/api/files' + path).query({ username: app.session.username, password: app.session.password }).send(formData).end(function (error, result) {
-                if (result && result.statusCode === 401) return logout();
-                if (result && result.statusCode !== 201) console.error('Error uploading file: ', result.statusCode);
-                if (error) console.error(error);
+    asyncForEach(files, function (file, callback) {
+        var path = encode(sanitize(app.path + '/' + file.name));
 
-                app.uploadStatus.done += 1;
-                app.uploadStatus.percentDone = Math.round(app.uploadStatus.done / app.uploadStatus.count * 100);
+        var formData = new FormData();
+        formData.append('file', file);
 
-                if (app.uploadStatus.done >= app.uploadStatus.count) {
-                    app.uploadStatus = {
-                        busy: false,
-                        count: 0,
-                        done: 0,
-                        percentDone: 100
-                    };
+        superagent.post('/api/files' + path).query({ access_token: localStorage.accessToken }).send(formData).end(function (error, result) {
+            if (result && result.statusCode === 401) return logout();
+            if (result && result.statusCode !== 201) return callback('Error uploading file: ', result.statusCode);
+            if (error) return callback(error);
 
-                    refresh();
-                }
-            });
-        }
-
-        for(var i = 0; i < app.uploadStatus.count; ++i) {
-            uploadFile(app.$els.upload.files[i]);
-        }
-    });
+            app.uploadStatus.done += 1;
+            app.uploadStatus.percentDone = Math.round(app.uploadStatus.done / app.uploadStatus.count * 100);
 
-    // reset the form first to make the change handler retrigger even on the same file selected
-    $('#fileUploadForm')[0].reset();
-
-    app.$els.upload.click();
-}
-
-function delAsk(entry) {
-    $('#modalDelete').modal('show');
-    app.deleteData = entry;
-}
-
-function del(entry) {
-    app.busy = true;
-
-    var path = encode(sanitize(app.path + '/' + entry.filePath));
+            callback();
+        });
+    }, function (error) {
+        if (error) console.error(error);
 
-    superagent.del('/api/files' + path).query({ username: app.session.username, password: app.session.password, recursive: true }).end(function (error, result) {
-        app.busy = false;
-
-        if (result && result.statusCode === 401) return logout();
-        if (result && result.statusCode !== 200) return console.error('Error deleting file: ', result.statusCode);
-        if (error) return console.error(error);
+        app.uploadStatus.busy = false;
+        app.uploadStatus.count = 0;
+        app.uploadStatus.done = 0;
+        app.uploadStatus.percentDone = 100;
 
         refresh();
-
-        $('#modalDelete').modal('hide');
     });
 }
 
-function renameAsk(entry) {
-    app.renameData.entry = entry;
-    app.renameData.error = null;
-    app.renameData.newFilePath = entry.filePath;
-
-    $('#modalRename').modal('show');
-}
-
-function rename(data) {
-    app.busy = true;
-
-    var path = encode(sanitize(app.path + '/' + data.entry.filePath));
-    var newFilePath = sanitize(app.path + '/' + data.newFilePath);
-
-    superagent.put('/api/files' + path).query({ username: app.session.username, password: app.session.password }).send({ newFilePath: newFilePath }).end(function (error, result) {
-        app.busy = false;
-
-        if (result && result.statusCode === 401) return logout();
-        if (result && result.statusCode !== 200) return console.error('Error renaming file: ', result.statusCode);
-        if (error) return console.error(error);
-
-        refresh();
-
-        $('#modalRename').modal('hide');
-    });
+function dragOver(event) {
+    event.preventDefault();
 }
 
-function createDirectoryAsk() {
-    $('#modalcreateDirectory').modal('show');
-    app.createDirectoryData = '';
-    app.createDirectoryError = null;
+function drop(event) {
+    event.preventDefault();
+    uploadFiles(event.dataTransfer.files || []);
 }
 
-function createDirectory(name) {
-    app.busy = true;
-    app.createDirectoryError = null;
-
-    var path = encode(sanitize(app.path + '/' + name));
-
-    superagent.post('/api/files' + path).query({ username: app.session.username, password: app.session.password, directory: true }).end(function (error, result) {
-        app.busy = false;
-
-        if (result && result.statusCode === 401) return logout();
-        if (result && result.statusCode === 403) {
-            app.createDirectoryError = 'Name not allowed';
-            return;
-        }
-        if (result && result.statusCode === 409) {
-            app.createDirectoryError = 'Directory already exists';
-            return;
-        }
-        if (result && result.statusCode !== 201) return console.error('Error creating directory: ', result.statusCode);
-        if (error) return console.error(error);
-
-        app.createDirectoryData = '';
-        refresh();
-
-        $('#modalcreateDirectory').modal('hide');
-    });
-}
-
-Vue.filter('prettyDate', function (value) {
-    var d = new Date(value);
-    return d.toDateString();
-});
-
-Vue.filter('prettyFileSize', function (value) {
-    return filesize(value);
-});
-
 var app = new Vue({
     el: '#app',
     data: {
+        ready: false,
         busy: true,
         uploadStatus: {
             busy: false,
@@ -281,46 +185,170 @@ var app = new Vue({
         session: {
             valid: false
         },
-        loginData: {},
-        deleteData: {},
-        renameData: {
-            entry: {},
-            error: null,
-            newFilePath: ''
+        folderListingEnabled: false,
+        loginData: {
+            username: '',
+            password: ''
         },
-        createDirectoryData: '',
-        createDirectoryError: null,
         entries: []
     },
     methods: {
-        login: login,
-        logout: logout,
+        onLogin: function () {
+            app.busy = true;
+
+            superagent.post('/api/login').send({ username: app.loginData.username, password: app.loginData.password }).end(function (error, result) {
+                app.busy = false;
+
+                if (error) return console.error(error);
+                if (result.statusCode === 401) return console.error('Invalid credentials');
+
+                getProfile(result.body.accessToken, function (error) {
+                    if (error) return console.error(error);
+
+                    loadDirectory(window.location.hash.slice(1));
+                });
+            });
+        },
+        onOptionsMenu: function (command) {
+            if (command === 'folderListing') {
+                superagent.put('/api/settings').send({ folderListingEnabled: this.folderListingEnabled }).query({ access_token: localStorage.accessToken }).end(function (error) {
+                    if (error) console.error(error);
+                });
+            } else if (command === 'about') {
+                this.$msgbox({
+                    title: 'About Surfer',
+                    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>.',
+                    dangerouslyUseHTMLString: true,
+                    confirmButtonText: 'OK',
+                    showCancelButton: false,
+                    type: 'info',
+                    center: true
+                  }).then(function () {}).catch(function () {});
+            } else if (command === 'logout') {
+                superagent.post('/api/logout').query({ access_token: localStorage.accessToken }).end(function (error) {
+                    if (error) console.error(error);
+
+                    app.session.valid = false;
+
+                    delete localStorage.accessToken;
+                });
+            }
+        },
+        onDownload: function (entry) {
+            if (entry.isDirectory) return;
+            window.location.href = encode('/api/files/' + sanitize(app.path + '/' + entry.filePath)) + '?access_token=' + localStorage.accessToken;
+        },
+        onUpload: function () {
+            $(app.$refs.upload).on('change', function () {
+
+                // detach event handler
+                $(app.$refs.upload).off('change');
+
+                uploadFiles(app.$refs.upload.files || []);
+            });
+
+            // reset the form first to make the change handler retrigger even on the same file selected
+            app.$refs.upload.value = '';
+            app.$refs.upload.click();
+        },
+        onDelete: function (entry) {
+            var title = 'Really delete ' + (entry.isDirectory ? 'folder ' : '') + entry.filePath;
+            this.$confirm('', title, { confirmButtonText: 'Yes', cancelButtonText: 'No' }).then(function () {
+                var path = encode(sanitize(app.path + '/' + entry.filePath));
+
+                superagent.del('/api/files' + path).query({ access_token: localStorage.accessToken, recursive: true }).end(function (error, result) {
+                    if (result && result.statusCode === 401) return logout();
+                    if (result && result.statusCode !== 200) return console.error('Error deleting file: ', result.statusCode);
+                    if (error) return console.error(error);
+
+                    refresh();
+                });
+            }).catch(function () {
+                console.log('delete error:', arguments);
+            });
+        },
+        onRename: function (entry) {
+            var title = 'Rename ' + entry.filePath;
+            this.$prompt('', title, { confirmButtonText: 'Yes', cancelButtonText: 'No', inputPlaceholder: 'new filename', inputValue: entry.filePath }).then(function (data) {
+                var path = encode(sanitize(app.path + '/' + entry.filePath));
+                var newFilePath = sanitize(app.path + '/' + data.value);
+
+                superagent.put('/api/files' + path).query({ access_token: localStorage.accessToken }).send({ newFilePath: newFilePath }).end(function (error, result) {
+                    if (result && result.statusCode === 401) return logout();
+                    if (result && result.statusCode !== 200) return console.error('Error renaming file: ', result.statusCode);
+                    if (error) return console.error(error);
+
+                    refresh();
+                });
+            }).catch(function () {
+                console.log('rename error:', arguments);
+            });
+        },
+        onNewFolder: function () {
+            var title = 'Create New Folder';
+            this.$prompt('', title, { confirmButtonText: 'Yes', cancelButtonText: 'No', inputPlaceholder: 'new foldername' }).then(function (data) {
+                var path = encode(sanitize(app.path + '/' + data.value));
+
+                superagent.post('/api/files' + path).query({ access_token: localStorage.accessToken, directory: true }).end(function (error, result) {
+                    if (result && result.statusCode === 401) return logout();
+                    if (result && result.statusCode === 403) return console.error('Name not allowed');
+                    if (result && result.statusCode === 409) return console.error('Directory already exists');
+                    if (result && result.statusCode !== 201) return console.error('Error creating directory: ', result.statusCode);
+                    if (error) return console.error(error);
+
+                    refresh();
+                });
+            }).catch(function () {
+                console.log('create folder error:', arguments);
+            });
+        },
+        prettyDate: function (row, column, cellValue, index) {
+            var date = new Date(cellValue),
+            diff = (((new Date()).getTime() - date.getTime()) / 1000),
+            day_diff = Math.floor(diff / 86400);
+
+            if (isNaN(day_diff) || day_diff < 0)
+                return;
+
+            return day_diff === 0 && (
+                diff < 60 && 'just now' ||
+                diff < 120 && '1 minute ago' ||
+                diff < 3600 && Math.floor( diff / 60 ) + ' minutes ago' ||
+                diff < 7200 && '1 hour ago' ||
+                diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') ||
+                day_diff === 1 && 'Yesterday' ||
+                day_diff < 7 && day_diff + ' days ago' ||
+                day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago' ||
+                day_diff < 365 && Math.round( day_diff / 30 ) +  ' months ago' ||
+                Math.round( day_diff / 365 ) + ' years ago';
+        },
+        prettyFileSize: function (row, column, cellValue, index) {
+            return filesize(cellValue);
+        },
         loadDirectory: loadDirectory,
+        onUp: function () {
+            window.location.hash = sanitize(app.path.split('/').slice(0, -1).filter(function (p) { return !!p; }).join('/'));
+        },
         open: open,
-        up: up,
-        upload: upload,
-        delAsk: delAsk,
-        del: del,
-        renameAsk: renameAsk,
-        rename: rename,
-        createDirectoryAsk: createDirectoryAsk,
-        createDirectory: createDirectory
+        drop: drop,
+        dragOver: dragOver
     }
 });
 
-window.app = app;
-
-login(localStorage.username, localStorage.password);
+getProfile(localStorage.accessToken, function (error) {
+    if (error) return console.error(error);
 
-$(window).on('hashchange', function () {
     loadDirectory(window.location.hash.slice(1));
-});
 
-// setup all the dialog focus handling
-['modalcreateDirectory'].forEach(function (id) {
-    $('#' + id).on('shown.bs.modal', function () {
-        $(this).find("[autofocus]:first").focus();
+    superagent.get('/api/settings').query({ access_token: localStorage.accessToken }).end(function (error, result) {
+        if (error) console.error(error);
+
+        app.folderListingEnabled = !!result.body.folderListingEnabled;
     });
 });
 
+$(window).on('hashchange', function () {
+    loadDirectory(window.location.hash.slice(1));
+});
+
 })();