UNPKG

akurath

Version:

IDE Frontend for codepsaces.io

897 lines (802 loc) 28.7 kB
define([ "hr/promise", "hr/utils", "hr/hr", 'core/backends/rpc', "core/backends/vfs", "core/debug/manager", 'models/command', "utils/string", "utils/url", "utils/languages", "utils/filesync", "utils/dialogs", "utils/uploader", "utils/clipboard", "core/operations" ], function(Q, _, hr, rpc, vfs, debugManager, Command, string, Url, Languages, FileSync, dialogs, Uploader, clipboard, operations) { var logging = hr.Logger.addNamespace("files"); var File = hr.Model.extend({ defaults: { "name": "", "size": 0, "mtime": 0, "mime": "", "href": "", "exists": true, "offline": false, "exportUrl": null }, idAttribute: "href", /* * Initialize */ initialize: function() { File.__super__.initialize.apply(this, arguments); this.codebox = this.options.codebox || require("core/box"); // Content for new file this.newFileContent = this.options.newFileContent || ""; this.modified = false; this._loading = false; this._uniqueId = Date.now()+_.uniqueId("file"); this.read = this.download; // Change in codebox : file deleted this.on("file:change:delete", function() { logging.log("file "+this.path()+" is deleted"); this.destroy(); }, this); // Change in subfiles this.on("files:change:create files:change:delete file:change:folder", function() { logging.log("file updated in "+this.path()); this.refresh(); }, this); // Listen to codebox event this.listenTo(this.codebox, "box:watch:change", function(e) { if (!this.isValid()) return; // Event on this file itself if (e.data.path == this.path()) { this.trigger("file:change:"+e.data.change, e.data); } // Event in subfile if (_.contains(["create", "delete"], e.data.change) && this.isChild(e.data.path)) { this.trigger("files:change:"+e.data.change, e.data); } }); this.listenTo(this.codebox, "box:files:write", function(e) { if (e.data.path == this.path()) { this.trigger("file:write", e.data); } }); return this; }, /* * Set modified (and not saved) state */ modifiedState: function(state) { if (this.modified == state) return; this.modified = state; this.trigger("modified", this.modified); }, /* * Set loading state */ loading: function(state) { var that = this; if (Q.isPromise(state)) { that.loading(true); state.fin(function() { that.loading(false); }); return state; } // Boolean if (this._loading == state) return; this._loading = state; this.trigger("loading", this._loading); }, /* * VFS request */ vfsRequest: function(method, url, args) { return vfs.execute(method, args, { 'url': url }); }, /* * Check if the file can be open */ canOpen: function() { return this.get("offline") || hr.Offline.isConnected(); }, /* * Open the tab for this file */ open: function(path, options) { var files = require("core/files"); if (_.isObject(path)) { options = path; path = this; } return files.open(path, options); }, /* * Return true if file is valid */ isValid: function() { return this.get("href", "").length > 0; }, /* * Return the full url with the host */ vfsFullUrl: function() { return this.codebox.baseUrl+this.vfsUrl.apply(this, arguments); }, /* * Return url to download the file */ exportUrl: function() { if (this.get("exportUrl")) { return this.get("exportUrl"); } return this.vfsFullUrl(); }, /* * VFS url */ vfsUrl: function(path, force_directory) { path = this.path(path); var basePath = window.location.pathname; basePath.substring(0, basePath.lastIndexOf('/')+1); var url = basePath+"vfs"+path; if (force_directory == null) force_directory = this.isDirectory(); if (force_directory && !string.endsWith(url, "/")) { url = url+"/"; } return url; }, /* * Return path to the file */ path: function(path) { if (path == null) { if (this.get("href").length == 0) { return null; } path = Url.parse(this.get("href")).pathname.replace("/vfs", ""); } if (string.endsWith(path, "/")) { path = path.slice(0, -1) } if (path.length == 0) { path = "/"; } if (path[0] != '/') { path = '/'+path; } return path; }, /* * Return file mode (for ace editor) */ mode: function(file) { return Languages.get_mode_byextension(this.extension()); }, /* * Return color to represent the file */ color: function(file, def) { if (this.isDirectory()) { return def; } return Languages.get_color_byext(this.extension(), def); }, /* * Return path to the parent */ parentPath: function(path) { path = this.path(path); if (path == "/") { return "/"; } return path.split("/").slice(0, -1).join("/"); }, /* * Return filename from the path */ filename: function(path) { path = this.path(path); if (path == "/") { return "/"; } return path.split("/").slice(-1).join("/"); }, /* * Return true if is a directory */ isDirectory: function() { return (this.get("mime") == "inode/directory" || (this.get("mime") == "inode/symlink" && this.get("linkStat.mime") == "inode/directory")); }, /* * Return true if it is a new fiel (not yet on disk) */ isNewfile: function() { return !this.get("exists"); }, /* * Return true if the file is sync offline */ isOffline: function() { return this.get("offline"); }, /* * Return true if it's root directory */ isRoot: function() { return this.path() == "/"; }, /* * Return true if it's git root */ isGit: function() { return this.path() == "/.git"; }, /* * Test if a path is a direct child * * @path : path to test */ isChild: function(path) { var parts1 = _.filter(path.split("/"), function(p) { return p.length > 0; }); var parts2 = _.filter(this.path().split("/"), function(p) { return p.length > 0; }); return (parts1.length == (parts2.length+1)); }, /* * Test if a path is child * * @path : path to test */ isSublevel: function(path) { var parts1 = _.filter(path.split("/"), function(p) { return p.length > 0; }); var parts2 = _.filter(this.path().split("/"), function(p) { return p.length > 0; }); return (parts1.length > parts2.length); }, /* * Return true if this file should be hidden */ isHidden: function() { return this.get("name", "").indexOf(".") == 0;; }, /* * Return file extension */ extension: function() { return "."+this.get("name").split('.').pop(); }, /* * Return current breakpoints for this file */ getBreakpoints: function() { return debugManager.breakpoints.getFileBreakpoints(this.path()); }, /* * Set breakpoints for this file */ setBreakpoints: function(points) { return debugManager.breakpoints.setFileBreakpoints(this.path(), points); }, /* * Clear breakpoints for this file */ clearBreakpoints: function() { return this.setBreakpoints([]); }, /* * Return file sync environment id */ syncEnvId: function() { if (this.isNewfile()) { return "temporary://"+this._uniqueId; } return "file://"+this.path(); }, /* * Return icon to represent the file */ icon: function() { var extsIcon = { "picture-o": [".png", ".jpg", ".gif", ".tiff", ".jpeg", ".bmp", ".webp", ".svg"], "music": [".mp3", ".wav"], "film": [".avi", ".mp4"] } if (!this.isDirectory()) { var ext = this.extension().toLowerCase(); return _.reduce(extsIcon, function(memo, exts, icon) { if (_.contains(exts, ext)) return icon; return memo; }, "file-text-o"); } else { return "folder"; } }, /* * Return paths decompositions */ paths: function() { return _.map(this.path().split("/"), function(name, i, parts) { var partialpath = parts.slice(0, i).join("/")+"/"+name return { "path": partialpath, "url": this.codebox.vBaseUrl+partialpath, "name": name } }, this); }, /* * Load file by its path * * @path : path to the file */ getByPath: function(path) { var that = this; path = this.path(path); if (path == "/") { var fileData = { "name": this.codebox.get("name"), "size": 0, "mtime": 0, "mime": "inode/directory", "href": location.protocol+"//"+location.host+"/vfs/", "exists": true }; this.set(fileData); return Q(fileData); } var parentPath = this.parentPath(path); var filename = this.filename(path); return this.loading(this.vfsRequest("listdir", this.vfsUrl(parentPath, true)).then(function(filesData) { var fileData = _.find(filesData, function(file) { return file.name == filename; }); if (fileData != null) { fileData.exists = true; that.set(fileData); return Q(fileData); } else { return Q.reject(new Error("Can't find file")); } })); }, /* * Return a child object */ getChild: function(name) { var nPath = this.path()+"/"+name; var f = new File({ 'box': this.box }); return f.getByPath(nPath).then(function() { return f; }); }, /* * Refresh infos */ refresh: function() { var that = this; return this.getByPath(this.path()).then(function() { that.trigger("refresh"); }); }, /* * Download */ download: function(filename, options) { var url, d, that = this; if (_.isObject(filename)) { options = filename; filename = null; } if (this.isNewfile() && !filename) return Q(this.newFileContent); options = _.defaults(options || {}, { redirect: false }); if (options.redirect) { window.open(this.exportUrl(),'_blank'); } else { return this.loading(this.vfsRequest("read", this.vfsUrl(filename, false)).then(function(content) { return content; })); } }, /* * Write file ocntent */ write: function(content, filename) { var that = this; return this.loading(this.vfsRequest("write", this.vfsUrl(filename, false), content).then(function() { return that.path(filename); })); }, /* * List directory */ listdir: function(options) { var that = this; if (!this.isDirectory()) { throw "Try getting files in a non directory" } options = _.defaults(options || {}, { order: "name", group: true }); return this.loading(this.vfsRequest("listdir", this.vfsUrl(null, true)).then(function(filesData) { var files = _.map(filesData, function(file) { return new File({ "codebox": that.codebox }, file) }); files = _.sortBy(files, function(file) { return file.get(options.order).toLowerCase(); }); if (options.group) { groups = _.groupBy(files, function(file){ return file.isDirectory() ? "directory" : "file"; }); files = [].concat(groups["directory"] || []).concat(groups["file"] || []); } return Q(files); })); }, /* * Create a new file * * @name : name of the file to create */ createFile: function(name) { return this.loading(this.vfsRequest("create", this.vfsUrl(null, true)+"/"+name)); }, /* * Create a new directory * * @name : name of the directory to create */ mkdir: function(name) { return this.loading(this.vfsRequest("mkdir", this.vfsUrl(null, true)+"/"+name+"/")); }, /* * Remove the file or directory */ remove: function() { var that = this; return this.loading(this.vfsRequest("remove", this.vfsUrl(null))); }, /* * Rename the file * * @name : new name for the file */ rename: function(name) { var parentPath = this.parentPath(); var newPath = parentPath+"/"+name; return this.loading(this.vfsRequest("special", this.vfsUrl(newPath), { "renameFrom": this.path() })); }, /* * Copy this file * * @to: folder to copy to * @newName: (optional) new name in the to folder */ copyTo: function(to, newName) { newName = newName || this.get("name"); var toPath = to+"/"+newName; return this.loading(this.vfsRequest("special", this.vfsUrl(toPath), { "copyFrom": this.path() })); }, /* * Copy a file in this folder * * @from: file to copy */ copyFile: function(from, newName) { newName = newName || from.split("/").pop(); var toPath = this.path()+"/"+newName; return this.loading(this.vfsRequest("special", this.vfsUrl(toPath, false), { "copyFrom": from })); }, /* * Run the file */ run: function() { var path = this.path(); return rpc.execute("run/file", { 'file': path }).then(function(runInfos) { Command.run("terminal.open", runInfos.shellId); return Q(runInfos); }, function(err) { dialogs.alert("Error running this file", "An error occurred when trying to run this file ("+path+"): "+(err.message || err)); }); }, // (action) Run the file actionRun: function(e) { if (e) e.preventDefault(); return this.run(); }, // (action) Refresh files list actionRefresh: function(e) { if (e) e.preventDefault(); return this.refresh(); }, // (action) Create a new file actionCreate: function(e) { var that = this; if (e) e.preventDefault(); return dialogs.prompt("Create a new file", "", "newfile.txt").then(function(name) { if (name.length > 0) { return that.createFile(name); } return Q.reject(new Error("Name is too short")); }); }, // (action) Create a new directory actionMkdir: function(e) { var that = this; if (e) e.preventDefault(); dialogs.prompt("Create a new directory", "", "newdirectory").then(function(name) { if (name.length > 0) { return that.mkdir(name); } return Q.reject(new Error("Name is too short")); }); }, // (action) Rename a file actionRename: function(e) { var that = this; if (e) e.preventDefault(); return dialogs.prompt("Rename", "", this.get("name")).then(function(name) { if (name.length > 0) { return that.rename(name); } return Q.reject(new Error("Name is too short")); }); }, // (action) Delete files actionRemove: function(e) { var that = this; if (e) e.preventDefault(); return dialogs.confirm("Do your really want to remove '"+_.escape(this.get("name"))+"'?").then(function(st) { if (st != true) return Q.reject(new Error("No confirmation")); return that.remove(); }); }, // (action) Download a file actionDownload: function(e) { var that = this; if (e) e.preventDefault(); return that.download({ redirect: true }); }, // (action) Upload file actionUpload: function(options) { var that = this; options = _.defaults({}, options || {}, { 'directory': false, 'multiple': true }); // Uploader var uploader = new Uploader({ "directory": this }); var $f = $("input.cb-file-uploader"); if ($f.length == 0) { var $f = $("<input>", { "type": "file", "class": "cb-file-uploader" }); $f.appendTo($("body")); } $f.hide(); $f.prop("webkitdirectory", options.directory); $f.prop("directory", options.directory); $f.prop("multiple", options.multiple); // Create file element for selection $f.change(function(e) { e.preventDefault(); operations.start("files.upload", function(op) { return uploader.upload(e.currentTarget.files).progress(function(p) { that.trigger("uploadProgress", p); op.progress(p.percent); }).fin(function() { $f.remove(); }); }, { title: "Uploading" }); }); $f.trigger('click'); }, // Return context menu contextMenu: function() { var that = this; var files = require("core/files"); return function() { var menu = []; // Open with if (!that.isDirectory()) { var handlers = files.getHandlers(that); _.each(handlers, function(handler) { menu.push({ 'type': "action", 'title': handler.name, 'icons': { 'menu': handler.icon }, 'action': function() { return handler.open(that); } }); }); menu.push({ 'type': "divider" }); } // File or directory if (!that.isRoot()) { menu.push({ 'type': "action", 'title': "Rename...", 'action': function() { return that.actionRename(); } }); menu.push({ 'type': "action", 'title': "Delete "+(that.isDirectory() ? "Folder" : "File"), 'action': function() { return that.actionRemove(); } }); menu.push({ 'type': "divider" }); } if (!that.isDirectory()) { menu.push({ 'type': "action", 'title': "Copy", 'action': function() { clipboard.setData("file", that.path()); } }); menu.push({ 'type': "action", 'title': "Cut", 'action': function() { clipboard.setData("file", that.path(), { cut: true }); } }); } else { menu.push({ 'type': "action", 'title': "Paste", 'flags': (!clipboard.hasData("file") ? "disabled" : ""), 'action': function() { if (clipboard.hasData("file")) { var path = clipboard.getData("file"); var cut = clipboard.getRaw().options.cut == true; // Load file we are copying var toCopy = new File(); return toCopy.getByPath(path) .then(function() { // Copy file return toCopy.copyTo(that.path()); }) .then(function() { // If cut then delete the previsous file and clear clipboard if (cut == false) return; // Clear clipboard clipboard.clear(); // Remove file copied return toCopy.remove(); }) .fail(function(err) { logging.error("error copy", err.stack, err); }) .done(function() { // Clear temporary model toCopy.destroy(); }) } } }); } menu.push({ 'type': "divider" }); if (that.isDirectory()) { // Directory menu.push({ 'type': "action", 'title': "New file", 'action': function() { return that.actionCreate(); } }); menu.push({ 'type': "action", 'title': "New folder", 'action': function() { return that.actionMkdir(); } }); menu.push({ 'type': "menu", 'title': "Add", 'offline': false, 'menu': [ { 'type': "action", 'title': "Files", 'offline': false, 'action': function() { that.actionUpload(); } }, { 'type': "action", 'title': "Directories", 'offline': false, 'action': function() { that.actionUpload({ 'directory': true }); } } ] }); menu.push({ 'type': "divider" }); menu.push({ 'type': "action", 'title': "Refresh", 'action': function() { return that.actionRefresh(); } }); menu.push({ 'type': "action", 'title': "Find in Folder", 'action': function() { return Command.run("code.search", { 'path': that.path() }); } }); menu.push({ 'type': "action", 'title': "Open Terminal", 'action': function() { return Command.run("terminal.open", null, { 'cwd': that.path() }); } }); } else { menu.push({ 'type': "action", 'title': "Download", 'action': function() { that.actionDownload(); } }); menu.push({ 'type': "divider" }); menu.push({ 'type': "action", 'title': "Run", 'offline': false, 'action': function() { that.actionRun(); } }); } return menu; }; } }); return File; });