Skip to content

Commit

Permalink
Added desktop iconview (#51)
Browse files Browse the repository at this point in the history
Even though added, it's still disabled by default because there's no
Settings application integration yet. Hence no minor bump.

* Adds desktop icon view adapter
* Adds new configuration setting
* Updates Desktop implementation
* Adds tapper input utility
  • Loading branch information
andersevenrud committed Jun 14, 2019
1 parent 453c459 commit d933b8e
Show file tree
Hide file tree
Showing 6 changed files with 386 additions and 3 deletions.
1 change: 1 addition & 0 deletions index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@
@import "./src/styles/notifications";
@import "./src/styles/login";
@import "./src/styles/search";
@import "./src/styles/iconview";
223 changes: 223 additions & 0 deletions src/adapters/ui/iconview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*
* OS.js - JavaScript Cloud/Web Desktop Platform
*
* Copyright (c) 2011-2019, Anders Evenrud <andersevenrud@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @author Anders Evenrud <andersevenrud@gmail.com>
* @licence Simplified BSD License
*/
import {EventEmitter} from '@osjs/event-emitter';
import {h, app} from 'hyperapp';
import {doubleTap} from '../../utils/input';

const tapper = doubleTap();

const view = (fileIcon, themeIcon, droppable) => (state, actions) =>
h('div', {
class: 'osjs-desktop-iconview__wrapper',
oncontextmenu: ev => actions.openContextMenu({ev}),
oncreate: el => {
droppable(el, {
ondrop: (ev, data, files) => {
if (data && data.path) {
actions.addEntry(data);
} else if (files.length > 0) {
actions.uploadEntries(files);
}
}
});
}
}, state.entries.map((entry, index) => {
return h('div', {
class: 'osjs-desktop-iconview__entry' + (
state.selected === index
? ' osjs-desktop-iconview__entry--selected'
: ''
),
oncontextmenu: ev => actions.openContextMenu({ev, entry, index}),
ontouchstart: ev => tapper(ev, () => actions.openEntry({ev, entry, index})),
ondblclick: ev => actions.openEntry({ev, entry, index}),
onclick: ev => actions.selectEntry({ev, entry, index})
}, [
h('div', {
class: 'osjs-desktop-iconview__entry__inner'
}, [
h('div', {
class: 'osjs-desktop-iconview__entry__icon'
}, h('img', {
src: themeIcon(fileIcon(entry).name)
})),
h('div', {
class: 'osjs-desktop-iconview__entry__label'
}, entry.filename)
])
]);
}));

/**
* Desktop Icon View
*/
export class DesktopIconView extends EventEmitter {

/**
* @param {Core} core Core reference
*/
constructor(core) {
super('DesktopIconView');

this.core = core;
this.$root = null;
this.iconview = null;
this.root = 'home:/.desktop';
}

destroy() {
if (this.$root && this.$root.parentNode) {
this.$root.parentNode.removeChild(this.$root);
}

this.iconview = null;
this.$root = null;

this.emit('destroy');
}

/**
* @param {object} rect Rectangle from desktop
*/
resize(rect) {
if (!this.$root) {
return;
}

this.$root.style.top = `${rect.top}px`;
this.$root.style.left = `${rect.left}px`;
this.$root.style.bottom = `${rect.bottom}px`;
this.$root.style.right = `${rect.right}px`;
}

_render(root) {
const oldRoot = this.root;
if (root) {
this.root = root;
}

if (this.$root) {
if (this.root !== oldRoot) {
this.iconview.reload();
}

return false;
}

return true;
}

render(root) {
if (!this._render(root)) {
return;
}

this.$root = document.createElement('div');
this.$root.className = 'osjs-desktop-iconview';
this.core.$root.appendChild(this.$root);

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 error = err => console.error(err);

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

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

openEntry: ({entry}) => {
if (entry.isDirectory) {
this.core.run('FileManager', {
path: entry
});
} else {
this.core.open(entry);
}

return {selected: -1};
},

selectEntry: ({index}) => ({selected: index}),

uploadEntries: files => {
// TODO
},

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

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

return {selected: -1};
},

removeEntry: entry => (state, actions) => {
unlink(entry)
.then(() => actions.reload())
.catch(error);

return {selected: -1};
},

reload: () => (state, actions) => {
readdir(root)
.then(entries => entries.filter(e => e.filename !== '..'))
.then(entries => actions.setEntries(entries));
}

}, view(fileIcon, themeIcon, droppable), this.$root);

this.iconview.reload();
}

createFileContextMenu(ev, entry) {
this.core.make('osjs/contextmenu', {
position: ev,
menu: [{
label: 'Open',
onclick: () => this.iconview.openEntry(({entry}))
}, {
label: 'Remove',
onclick: () => this.iconview.removeEntry(entry)
}]
});
}
}
3 changes: 2 additions & 1 deletion src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ export const defaultConfiguration = {
style: 'cover'
},
iconview: {
enabled: false
enabled: false,
path: 'home:/.desktop'
}
}
},
Expand Down
37 changes: 35 additions & 2 deletions src/desktop.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {EventEmitter} from '@osjs/event-emitter';
import Application from './application';
import {handleTabOnTextarea} from './utils/dom';
import {matchKeyCombo} from './utils/input';
import {DesktopIconView} from './adapters/ui/iconview';
import Window from './window';
import Search from './search';
import merge from 'deepmerge';
Expand Down Expand Up @@ -121,14 +122,16 @@ export default class Desktop extends EventEmitter {

this.core = core;
this.options = Object.assign({
contextmenu: []
contextmenu: [],
}, options);
this.$theme = [];
this.$icons = [];
this.$styles = document.createElement('style');
this.$styles.setAttribute('type', 'text/css');
this.contextmenuEntries = [];
this.search = core.config('search.enabled') ? new Search(core) : null;
this.iconview = new DesktopIconView(this.core);

this.subtract = {
left: 0,
top: 0,
Expand All @@ -145,9 +148,14 @@ export default class Desktop extends EventEmitter {
this.search = this.search.destroy();
}

if (this.iconview) {
this.iconview.destroy();
}

if (this.$styles && this.$styles.parentNode) {
this.$styles.remove();
}

this.$styles = null;

this._removeIcons();
Expand Down Expand Up @@ -212,6 +220,7 @@ export default class Desktop extends EventEmitter {
try {
this._updateCSS();
Window.getWindows().forEach(w => w.clampToViewport());
this._updateIconview();
} catch (e) {
logger.warn('Panel event error', e);
}
Expand Down Expand Up @@ -394,7 +403,6 @@ export default class Desktop extends EventEmitter {
this.core.on('osjs/settings:load', checkRTL);
this.core.on('osjs/settings:save', checkRTL);
this.core.on('osjs/core:started', checkRTL);

}

start() {
Expand All @@ -403,6 +411,13 @@ export default class Desktop extends EventEmitter {
}

this._updateCSS();
this._updateIconview();
}

_updateIconview() {
if (this.iconview) {
this.iconview.resize(this.getRect());
}
}

/**
Expand Down Expand Up @@ -466,6 +481,8 @@ export default class Desktop extends EventEmitter {
this.applyTheme(newSettings.theme);
this.applyIcons(newSettings.icons);

this.applyIconView(newSettings.iconview);

this.core.emit('osjs/desktop:applySettings');

return Object.assign({}, newSettings);
Expand Down Expand Up @@ -506,6 +523,22 @@ export default class Desktop extends EventEmitter {
this.$icons = [];
}

/**
* Adds or removes the icon view
*/
applyIconView(settings) {
if (!this.iconview) {
return;
}

if (settings.enabled) {
this.iconview.render(settings.path);
this.iconview.resize(this.getRect());
} else {
this.iconview.destroy();
}
}

/**
* Sets the current icon theme from settings
*/
Expand Down
Loading

0 comments on commit d933b8e

Please sign in to comment.