Skip to content

Commit ceba172

Browse files
committed
Improved desktop iconview and added shortcuts (#51)
1 parent aefa5c3 commit ceba172

File tree

2 files changed

+132
-18
lines changed

2 files changed

+132
-18
lines changed

src/adapters/ui/iconview.js

+124-18
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,31 @@
3030
import {EventEmitter} from '@osjs/event-emitter';
3131
import {h, app} from 'hyperapp';
3232
import {doubleTap} from '../../utils/input';
33+
import {pathJoin} from '../../utils/vfs';
3334

3435
const tapper = doubleTap();
3536

37+
const validVfsDrop = data => data && data.path;
38+
39+
const onDropAction = actions => (ev, data, files, shortcut = false) => {
40+
if (validVfsDrop(data)) {
41+
actions.addEntry({entry: data, shortcut});
42+
} else if (files.length > 0) {
43+
actions.uploadEntries(files);
44+
}
45+
};
46+
3647
const view = (fileIcon, themeIcon, droppable) => (state, actions) =>
3748
h('div', {
3849
class: 'osjs-desktop-iconview__wrapper',
3950
oncontextmenu: ev => actions.openContextMenu({ev}),
4051
oncreate: el => {
4152
droppable(el, {
4253
ondrop: (ev, data, files) => {
43-
if (data && data.path) {
44-
actions.addEntry(data);
45-
} else if (files.length > 0) {
46-
actions.uploadEntries(files);
54+
if (ev.shiftKey && validVfsDrop(data)) {
55+
actions.openDropContextMenu({ev, data, files});
56+
} else {
57+
onDropAction(actions)(ev, data, files);
4758
}
4859
}
4960
});
@@ -65,16 +76,71 @@ const view = (fileIcon, themeIcon, droppable) => (state, actions) =>
6576
}, [
6677
h('div', {
6778
class: 'osjs-desktop-iconview__entry__icon'
68-
}, h('img', {
69-
src: themeIcon(fileIcon(entry).name)
70-
})),
79+
}, [
80+
h('img', {
81+
src: themeIcon(fileIcon(entry).name),
82+
class: 'osjs-desktop-iconview__entry__icon__icon'
83+
}),
84+
entry.shortcut !== false
85+
? h('img', {
86+
src: themeIcon('emblem-symbolic-link'),
87+
class: 'osjs-desktop-iconview__entry__icon__shortcut'
88+
})
89+
: null
90+
]),
7191
h('div', {
7292
class: 'osjs-desktop-iconview__entry__label'
7393
}, entry.filename)
7494
])
7595
]);
7696
}));
7797

98+
const createShortcuts = (root, readfile, writefile) => {
99+
const read = () => {
100+
const filename = pathJoin(root, '.shortcuts.json');
101+
102+
return readfile(filename)
103+
.then(contents => JSON.parse(contents))
104+
.catch(error => ([]));
105+
};
106+
107+
const write = shortcuts => {
108+
const filename = pathJoin(root, '.shortcuts.json');
109+
const contents = JSON.stringify(shortcuts || []);
110+
111+
return writefile(filename, contents)
112+
.catch(() => 0);
113+
};
114+
115+
const add = entry => read(root)
116+
.then(shortcuts => ([...shortcuts, entry]))
117+
.then(write);
118+
119+
const remove = index => read(root)
120+
.then(shortcuts => {
121+
shortcuts.splice(index, 1);
122+
return shortcuts;
123+
})
124+
.then(write);
125+
126+
return {read, add, remove};
127+
};
128+
129+
const readDesktopFolder = (root, readdir, shortcuts) => {
130+
const read = () => readdir(root, {
131+
showHiddenFiles: false
132+
})
133+
.then(files => files.map(s => Object.assign({shortcut: false}, s)));
134+
135+
const readShortcuts = () => shortcuts.read()
136+
.then(shortcuts => shortcuts.map((s, index) => Object.assign({shortcut: index}, s)));
137+
138+
return () => {
139+
return Promise.all([readShortcuts(), read()])
140+
.then(results => [].concat(...results));
141+
};
142+
};
143+
78144
/**
79145
* Desktop Icon View
80146
*/
@@ -146,18 +212,26 @@ export class DesktopIconView extends EventEmitter {
146212
const {droppable} = this.core.make('osjs/dnd');
147213
const {icon: fileIcon} = this.core.make('osjs/fs');
148214
const {icon: themeIcon} = this.core.make('osjs/theme');
149-
const {copy, readdir, unlink} = this.core.make('osjs/vfs');
215+
const {copy, readdir, readfile, writefile, unlink, mkdir} = this.core.make('osjs/vfs');
150216
const error = err => console.error(err);
217+
const shortcuts = createShortcuts(root, readfile, writefile);
218+
const read = readDesktopFolder(root, readdir, shortcuts);
151219

152220
this.iconview = app({
153221
selected: -1,
154222
entries: []
155223
}, {
156224
setEntries: entries => ({entries}),
157225

158-
openContextMenu: ({ev, entry}) => {
226+
openDropContextMenu: ({ev, data, files}) => {
227+
this.createDropContextMenu(ev, data, files);
228+
},
229+
230+
openContextMenu: ({ev, entry, index}) => {
159231
if (entry) {
160232
this.createFileContextMenu(ev, entry);
233+
234+
return {selected: index};
161235
}
162236
},
163237

@@ -182,26 +256,41 @@ export class DesktopIconView extends EventEmitter {
182256
// TODO
183257
},
184258

185-
addEntry: entry => (state, actions) => {
259+
addEntry: ({entry, shortcut}) => (state, actions) => {
186260
const dest = `${root}/${entry.filename}`;
187261

188-
copy(entry, dest)
189-
.then(() => actions.reload())
190-
.catch(error);
262+
mkdir(root)
263+
.catch(() => true)
264+
.then(() => {
265+
if (shortcut) {
266+
return shortcuts.add(entry);
267+
}
268+
269+
return copy(entry, dest)
270+
.then(() => actions.reload())
271+
.catch(error);
272+
})
273+
.then(() => actions.reload());
191274

192275
return {selected: -1};
193276
},
194277

195278
removeEntry: entry => (state, actions) => {
196-
unlink(entry)
197-
.then(() => actions.reload())
198-
.catch(error);
279+
if (entry.shortcut !== false) {
280+
shortcuts.remove(entry.shortcut)
281+
.then(() => actions.reload())
282+
.catch(error);
283+
} else {
284+
unlink(entry)
285+
.then(() => actions.reload())
286+
.catch(error);
287+
}
199288

200289
return {selected: -1};
201290
},
202291

203292
reload: () => (state, actions) => {
204-
readdir(root)
293+
read()
205294
.then(entries => entries.filter(e => e.filename !== '..'))
206295
.then(entries => actions.setEntries(entries));
207296
}
@@ -223,9 +312,26 @@ export class DesktopIconView extends EventEmitter {
223312
label: _('LBL_OPEN_WITH'),
224313
onclick: () => this.iconview.openEntry({entry, forceDialog: true})
225314
}, {
226-
label: _('LBL_DELETE'),
315+
label: entry.shortcut !== false ? _('LBL_REMOVE') : _('LBL_DELETE'),
227316
onclick: () => this.iconview.removeEntry(entry)
228317
}]
229318
});
230319
}
320+
321+
createDropContextMenu(ev, data, files) {
322+
const _ = this.core.make('osjs/locale').translate;
323+
324+
const action = shortcut => onDropAction(this.iconview)(ev, data, files, shortcut);
325+
326+
this.core.make('osjs/contextmenu', {
327+
position: ev,
328+
menu: [{
329+
label: _('LBL_COPY'),
330+
onclick: () => action(false)
331+
}, {
332+
label: _('LBL_CREATE_SHORTCUT'),
333+
onclick: () => action(true)
334+
}]
335+
});
336+
}
231337
}

src/styles/_iconview.scss

+8
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@
6767
width: 100%;
6868
padding: 0.5em;
6969
box-sizing: border-box;
70+
position: relative;
71+
72+
&__shortcut {
73+
width: 1em;
74+
position: absolute;
75+
bottom: 0;
76+
right: 0;
77+
}
7078
}
7179

7280
&__label {

0 commit comments

Comments
 (0)