Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Desktop iconview shortcuts and general improvements #73

Merged
merged 8 commits into from
Sep 12, 2019
167 changes: 148 additions & 19 deletions src/adapters/ui/iconview.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,43 @@
import {EventEmitter} from '@osjs/event-emitter';
import {h, app} from 'hyperapp';
import {doubleTap} from '../../utils/input';
import {pathJoin} from '../../utils/vfs';

const tapper = doubleTap();

const validVfsDrop = data => data && data.path;

const onDropAction = actions => (ev, data, files, shortcut = true) => {
if (validVfsDrop(data)) {
actions.addEntry({entry: data, shortcut});
} else if (files.length > 0) {
actions.uploadEntries(files);
}
};

const isRootElement = ev =>
ev.target && ev.target.classList.contains('osjs-desktop-iconview__wrapper');

const view = (fileIcon, themeIcon, droppable) => (state, actions) =>
h('div', {
class: 'osjs-desktop-iconview__wrapper',
oncontextmenu: ev => actions.openContextMenu({ev}),
oncontextmenu: ev => {
if (isRootElement(ev)) {
actions.openContextMenu({ev});
}
},
onclick: ev => {
if (isRootElement(ev)) {
actions.selectEntry({index: -1});
}
},
oncreate: el => {
droppable(el, {
ondrop: (ev, data, files) => {
if (data && data.path) {
actions.addEntry(data);
} else if (files.length > 0) {
actions.uploadEntries(files);
if (ev.shiftKey && validVfsDrop(data)) {
actions.openDropContextMenu({ev, data, files});
} else {
onDropAction(actions)(ev, data, files);
}
}
});
Expand All @@ -65,16 +88,71 @@ const view = (fileIcon, themeIcon, droppable) => (state, actions) =>
}, [
h('div', {
class: 'osjs-desktop-iconview__entry__icon'
}, h('img', {
src: themeIcon(fileIcon(entry).name)
})),
}, [
h('img', {
src: entry.icon ? entry.icon : themeIcon(fileIcon(entry).name),
class: 'osjs-desktop-iconview__entry__icon__icon'
}),
entry.shortcut !== false
? h('img', {
src: themeIcon('emblem-symbolic-link'),
class: 'osjs-desktop-iconview__entry__icon__shortcut'
})
: null
]),
h('div', {
class: 'osjs-desktop-iconview__entry__label'
}, entry.filename)
])
]);
}));

const createShortcuts = (root, readfile, writefile) => {
const read = () => {
const filename = pathJoin(root, '.shortcuts.json');

return readfile(filename)
.then(contents => JSON.parse(contents))
.catch(error => ([]));
};

const write = shortcuts => {
const filename = pathJoin(root, '.shortcuts.json');
const contents = JSON.stringify(shortcuts || []);

return writefile(filename, contents)
.catch(() => 0);
};

const add = entry => read(root)
.then(shortcuts => ([...shortcuts, entry]))
.then(write);

const remove = index => read(root)
.then(shortcuts => {
shortcuts.splice(index, 1);
return shortcuts;
})
.then(write);

return {read, add, remove};
};

const readDesktopFolder = (root, readdir, shortcuts) => {
const read = () => readdir(root, {
showHiddenFiles: false
})
.then(files => files.map(s => Object.assign({shortcut: false}, s)));

const readShortcuts = () => shortcuts.read()
.then(shortcuts => shortcuts.map((s, index) => Object.assign({shortcut: index}, s)));

return () => {
return Promise.all([readShortcuts(), read()])
.then(results => [].concat(...results));
};
};

/**
* Desktop Icon View
*/
Expand Down Expand Up @@ -146,18 +224,30 @@ export class DesktopIconView extends EventEmitter {
const {droppable} = this.core.make('osjs/dnd');
const {icon: fileIcon} = this.core.make('osjs/fs');
const {icon: themeIcon} = this.core.make('osjs/theme');
const {copy, readdir, unlink} = this.core.make('osjs/vfs');
const {copy, readdir, readfile, writefile, unlink, mkdir} = this.core.make('osjs/vfs');
const error = err => console.error(err);
const shortcuts = createShortcuts(root, readfile, writefile);
const read = readDesktopFolder(root, readdir, shortcuts);

this.iconview = app({
selected: -1,
entries: []
}, {
setEntries: entries => ({entries}),

openContextMenu: ({ev, entry}) => {
openDropContextMenu: ({ev, data, files}) => {
this.createDropContextMenu(ev, data, files);
},

openContextMenu: ({ev, entry, index}) => {
if (entry) {
this.createFileContextMenu(ev, entry);

return {selected: index};
} else {
this.createRootContextMenu(ev);

return {selected: -1};
}
},

Expand All @@ -166,6 +256,8 @@ export class DesktopIconView extends EventEmitter {
this.core.run('FileManager', {
path: entry
});
} else if (entry.mime === 'osjs/application') {
this.core.run(entry.filename);
} else {
this.core.open(entry, {
useDefault: true,
Expand All @@ -182,26 +274,41 @@ export class DesktopIconView extends EventEmitter {
// TODO
},

addEntry: entry => (state, actions) => {
addEntry: ({entry, shortcut}) => (state, actions) => {
const dest = `${root}/${entry.filename}`;

copy(entry, dest)
.then(() => actions.reload())
.catch(error);
mkdir(root)
.catch(() => true)
.then(() => {
if (shortcut || entry.mime === 'osjs/application') {
return shortcuts.add(entry);
}

return copy(entry, dest)
.then(() => actions.reload())
.catch(error);
})
.then(() => actions.reload());

return {selected: -1};
},

removeEntry: entry => (state, actions) => {
unlink(entry)
.then(() => actions.reload())
.catch(error);
if (entry.shortcut !== false) {
shortcuts.remove(entry.shortcut)
.then(() => actions.reload())
.catch(error);
} else {
unlink(entry)
.then(() => actions.reload())
.catch(error);
}

return {selected: -1};
},

reload: () => (state, actions) => {
readdir(root)
read()
.then(entries => entries.filter(e => e.filename !== '..'))
.then(entries => actions.setEntries(entries));
}
Expand All @@ -223,9 +330,31 @@ export class DesktopIconView extends EventEmitter {
label: _('LBL_OPEN_WITH'),
onclick: () => this.iconview.openEntry({entry, forceDialog: true})
}, {
label: _('LBL_DELETE'),
label: entry.shortcut !== false ? _('LBL_REMOVE_SHORTCUT') : _('LBL_DELETE'),
onclick: () => this.iconview.removeEntry(entry)
}]
});
}

createDropContextMenu(ev, data, files) {
const _ = this.core.make('osjs/locale').translate;

const action = shortcut => onDropAction(this.iconview)(ev, data, files, shortcut);

this.core.make('osjs/contextmenu', {
position: ev,
menu: [{
label: _('LBL_COPY'),
onclick: () => action(false)
}, {
label: _('LBL_CREATE_SHORTCUT'),
onclick: () => action(true)
}]
});
}

createRootContextMenu(ev) {
this.core.make('osjs/desktop')
.openContextMenu(ev);
}
}
8 changes: 8 additions & 0 deletions src/desktop.js
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ export default class Desktop extends EventEmitter {
const lockSettings = this.core.config('desktop.lock');
const extras = [].concat(...this.contextmenuEntries.map(e => typeof e === 'function' ? e() : e));
const config = this.core.config('desktop.contextmenu');
const hasIconview = this.core.make('osjs/settings').get('osjs/desktop', 'iconview.enabled');

if (config === false || config.enabled === false) {
return;
Expand Down Expand Up @@ -681,6 +682,13 @@ export default class Desktop extends EventEmitter {
}))
}];

if (hasIconview && this.iconview) {
defaultItems.push({
label: _('LBL_REFRESH'),
onclick: () => this.iconview.iconview.reload()
});
}

const base = useDefaults === 'function'
? config.defaults(this, defaultItems)
: (useDefaults ? defaultItems : []);
Expand Down
4 changes: 3 additions & 1 deletion src/locale/en_EN.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,7 @@ export const en_EN = {
LBL_BACK: 'Back',
LBL_FORWARD: 'Forward',
LBL_UPLOAD: 'Upload',
LBL_IMAGE: 'Image'
LBL_IMAGE: 'Image',
LBL_CREATE_SHORTCUT: 'Create shortcut',
LBL_REMOVE_SHORTCUT: 'Remove shortcut'
};
4 changes: 3 additions & 1 deletion src/locale/nb_NO.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,7 @@ export const nb_NO = {
LBL_BACK: 'Tilbake',
LBL_FORWARD: 'Frem',
LBL_UPLOAD: 'Last opp',
LBL_IMAGE: 'Bilde'
LBL_IMAGE: 'Bilde',
LBL_CREATE_SHORTCUT: 'Lag til snarvei',
LBL_REMOVE_SHORTCUT: 'Fjern snarvei'
};
1 change: 1 addition & 0 deletions src/providers/desktop.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default class DesktopServiceProvider extends ServiceProvider {
this.desktop.init();

this.core.singleton('osjs/desktop', () => ({
openContextMenu: ev => this.desktop.onContextMenu(ev),
addContextMenuEntries: entries => this.desktop.addContextMenu(entries),
applySettings: settings => this.desktop.applySettings(settings),
getRect: () => this.desktop.getRect()
Expand Down
8 changes: 8 additions & 0 deletions src/styles/_iconview.scss
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@
width: 100%;
padding: 0.5em;
box-sizing: border-box;
position: relative;

&__shortcut {
width: 1em;
position: absolute;
bottom: 0;
right: 0;
}
}

&__label {
Expand Down