Skip to content

Commit 62a2470

Browse files
committed
Merge pull request #309 from FriendCode/feature/copypaste
Fix #306 : Add copy/cut/paste to file context menu (files only right now)
2 parents d3d9cae + f5a6c1e commit 62a2470

5 files changed

Lines changed: 220 additions & 18 deletions

File tree

client/core/backends/vfs.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ define([
1818
'event': eventName,
1919
'data': _.extend({}, {
2020
'path': path,
21-
'change': name
21+
'change': name,
22+
'source': "fakeVfs"
2223
}, data)
2324
});
2425
};
@@ -34,7 +35,7 @@ define([
3435
"write": "put",
3536
"mkdir": "put",
3637
"create": "put",
37-
"rename": "post",
38+
"special": "post",
3839
"remove": "delete",
3940
"read": "get"
4041
};
@@ -55,9 +56,15 @@ define([
5556
case "remove":
5657
triggerWatchEvent("delete", path);
5758
break;
58-
case "rename":
59-
triggerWatchEvent("delete", args.renameFrom);
60-
triggerWatchEvent("create", path);
59+
case "special":
60+
// Rename
61+
if (args.renameFrom) {
62+
triggerWatchEvent("delete", args.renameFrom);
63+
triggerWatchEvent("create", path);
64+
} else if (args.copyFrom){
65+
triggerWatchEvent("create", path);
66+
}
67+
6168
break;
6269
case "create":
6370
triggerWatchEvent("create", path);
@@ -117,13 +124,26 @@ define([
117124
});
118125

119126
// Rename a file
120-
vfs.addMethod('rename', {
127+
vfs.addMethod('special', {
121128
fallback: function(args, options) {
122-
var to = localfs.urlToPath(options.url);
123-
var from = args.renameFrom;
129+
// Rename
130+
if (args.renameFrom) {
131+
var to = localfs.urlToPath(options.url);
132+
var from = args.renameFrom;
133+
134+
if (!from) return Q.reject("need 'renameFrom'");
135+
return localfs.mv(from, to);
136+
}
137+
// Copy
138+
else if (args.copyFrom) {
139+
var to = localfs.urlToPath(options.url);
140+
var from = args.copyFrom;
124141

125-
if (!from) return Q.reject("need 'renameFrom'");
126-
return localfs.mv(from, to);
142+
if (!from) return Q.reject("need 'copyFrom'");
143+
return localfs.cp(from, to);
144+
} else {
145+
return Q.reject("Invalid special operations");
146+
}
127147
},
128148
after: function(args, results, options) {
129149
localfs.autoSync();

client/core/localfs.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,17 @@ define([
245245
return fsCall(filer.mv, [from, '.', to], filer);
246246
});
247247

248+
/*
249+
* Copy a file
250+
*/
251+
var copy = needFsReady(function(from, to) {
252+
from = adaptPath(from);
253+
to = adaptPath(to);
254+
255+
logger.log("copy:", from, "to", to);
256+
return fsCall(filer.cp, [from, '.', to], filer);
257+
});
258+
248259
/*
249260
* Remove a file or directory
250261
*/
@@ -490,6 +501,7 @@ define([
490501
'write': writeFile,
491502
'read': readFile,
492503
'mv': move,
504+
'cp': copy,
493505
'rm': remove,
494506
'reset': syncFileBoxToLocal,
495507
'sync': sync,

client/models/file.js

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ define([
1212
"utils/filesync",
1313
"utils/dialogs",
1414
"utils/uploader",
15+
"utils/clipboard",
1516
"core/operations"
16-
], function(Q, _, hr, rpc, vfs, debugManager, Command, string, Url, Languages, FileSync, dialogs, Uploader, operations) {
17+
], function(Q, _, hr, rpc, vfs, debugManager, Command, string, Url, Languages, FileSync, dialogs, Uploader, clipboard, operations) {
1718
var logging = hr.Logger.addNamespace("files");
1819

1920
var File = hr.Model.extend({
@@ -58,6 +59,8 @@ define([
5859

5960
// Listen to codebox event
6061
this.listenTo(this.codebox, "box:watch:change", function(e) {
62+
if (!this.isValid()) return;
63+
6164
// Event on this file itself
6265
if (e.data.path == this.path()) {
6366
this.trigger("file:change:"+e.data.change, e.data);
@@ -179,7 +182,7 @@ define([
179182
if (this.get("href").length == 0) { return null; }
180183
path = Url.parse(this.get("href")).pathname.replace("/vfs", "");
181184
}
182-
185+
183186
if (string.endsWith(path, "/")) {
184187
path = path.slice(0, -1)
185188
}
@@ -379,7 +382,7 @@ define([
379382
"size": 0,
380383
"mtime": 0,
381384
"mime": "inode/directory",
382-
"href": "/vfs/",
385+
"href": location.protocol+"//"+location.host+"/vfs/",
383386
"exists": true
384387
};
385388
this.set(fileData);
@@ -529,11 +532,40 @@ define([
529532
rename: function(name) {
530533
var parentPath = this.parentPath();
531534
var newPath = parentPath+"/"+name;
532-
return this.loading(this.vfsRequest("rename", this.vfsUrl(newPath), {
535+
return this.loading(this.vfsRequest("special", this.vfsUrl(newPath), {
533536
"renameFrom": this.path()
534537
}));
535538
},
536539

540+
/*
541+
* Copy this file
542+
*
543+
* @to: folder to copy to
544+
* @newName: (optional) new name in the to folder
545+
*/
546+
copyTo: function(to, newName) {
547+
newName = newName || this.get("name");
548+
var toPath = to+"/"+newName;
549+
550+
return this.loading(this.vfsRequest("special", this.vfsUrl(toPath), {
551+
"copyFrom": this.path()
552+
}));
553+
},
554+
555+
/*
556+
* Copy a file in this folder
557+
*
558+
* @from: file to copy
559+
*/
560+
copyFile: function(from, newName) {
561+
newName = newName || from.split("/").pop();
562+
var toPath = this.path()+"/"+newName;
563+
564+
return this.loading(this.vfsRequest("special", this.vfsUrl(toPath, false), {
565+
"copyFrom": from
566+
}));
567+
},
568+
537569
/*
538570
* Run the file
539571
*/
@@ -703,14 +735,71 @@ define([
703735
});
704736
menu.push({
705737
'type': "action",
706-
'title': "Remove",
738+
'title': "Delete "+(that.isDirectory() ? "Folder" : "File"),
707739
'action': function() {
708740
return that.actionRemove();
709741
}
710742
});
711743
menu.push({ 'type': "divider" });
712744
}
713745

746+
if (!that.isDirectory()) {
747+
menu.push({
748+
'type': "action",
749+
'title': "Copy",
750+
'action': function() {
751+
clipboard.setData("file", that.path());
752+
}
753+
});
754+
menu.push({
755+
'type': "action",
756+
'title': "Cut",
757+
'action': function() {
758+
clipboard.setData("file", that.path(), {
759+
cut: true
760+
});
761+
}
762+
});
763+
} else {
764+
menu.push({
765+
'type': "action",
766+
'title': "Paste",
767+
'flags': (!clipboard.hasData("file") ? "disabled" : ""),
768+
'action': function() {
769+
if (clipboard.hasData("file")) {
770+
var path = clipboard.getData("file");
771+
var cut = clipboard.getRaw().options.cut == true;
772+
773+
// Load file we are copying
774+
var toCopy = new File();
775+
return toCopy.getByPath(path)
776+
.then(function() {
777+
// Copy file
778+
return toCopy.copyTo(that.path());
779+
})
780+
.then(function() {
781+
// If cut then delete the previsous file and clear clipboard
782+
if (cut == false) return;
783+
784+
// Clear clipboard
785+
clipboard.clear();
786+
787+
// Remove file copied
788+
return toCopy.remove();
789+
})
790+
.fail(function(err) {
791+
logging.error("error copy", err.stack, err);
792+
})
793+
.done(function() {
794+
// Clear temporary model
795+
toCopy.destroy();
796+
})
797+
}
798+
}
799+
});
800+
}
801+
menu.push({ 'type': "divider" });
802+
714803
if (that.isDirectory()) {
715804
// Directory
716805
menu.push({

client/utils/clipboard.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
define([
2+
'hr/hr',
3+
'hr/utils'
4+
], function (hr, _) {
5+
var CONVERTERS = {
6+
// from -> to
7+
'file': {
8+
// File are already representated as a string (equals path)
9+
'string': function(file) {
10+
return file;
11+
}
12+
}
13+
};
14+
15+
16+
var Clipboard = hr.Class.extend({
17+
/*
18+
* Initialize the clipboard
19+
*/
20+
initialize: function() {
21+
this.data = null;
22+
return this;
23+
},
24+
25+
/*
26+
* Check if has some data avilable and type
27+
*/
28+
hasData: function(type) {
29+
if (!this.data) return false;
30+
return !this.type || this.type == this.data.type;
31+
},
32+
33+
/*
34+
* Set data in clipboard
35+
*/
36+
setData: function(type, value, options) {
37+
this.data = {
38+
'type': type,
39+
'value': value,
40+
'options': options || {}
41+
};
42+
this.trigger("data", this.data);
43+
},
44+
45+
/*
46+
* Clear clipboard
47+
*/
48+
clear: function() {
49+
this.data = null;
50+
this.trigger("data", this.data);
51+
},
52+
53+
/*
54+
* Get raw data
55+
*/
56+
getRaw: function() {
57+
return this.data;
58+
},
59+
60+
/*
61+
* Get data from clipboard and convert to the right type
62+
*/
63+
getData: function(type) {
64+
// No data
65+
if (!this.data) return null;
66+
67+
// Correct type
68+
if (this.data.type == type) return this.data.value;
69+
70+
// Try converting
71+
if (!CONVERTERS[this.data.type] && !CONVERTERS[this.data.type][type]) {
72+
throw new Error("No converters from "+this.data.type+" to "+type);
73+
} else {
74+
return CONVERTERS[this.data.type][type](this.data.value);
75+
}
76+
77+
}
78+
});
79+
return new Clipboard();
80+
});

client/utils/keyboard.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ define([
33
'hr/utils',
44
'vendors/mousetrap'
55
], function (hr, _, Mousetrap) {
6-
var Keyboard = new (hr.Class.extend({
6+
var Keyboard = hr.Class.extend({
77
/*
88
* Initialize the keyboard navigation
99
*/
@@ -87,6 +87,7 @@ define([
8787

8888
return shortcut.toUpperCase();
8989
}
90-
}));
91-
return Keyboard;
90+
});
91+
92+
return new Keyboard();
9293
});

0 commit comments

Comments
 (0)