Skip to content

Commit d933b8e

Browse files
committed
Added desktop iconview (#51)
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
1 parent 453c459 commit d933b8e

File tree

6 files changed

+386
-3
lines changed

6 files changed

+386
-3
lines changed

index.scss

+1
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@
3434
@import "./src/styles/notifications";
3535
@import "./src/styles/login";
3636
@import "./src/styles/search";
37+
@import "./src/styles/iconview";

src/adapters/ui/iconview.js

+223
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*
2+
* OS.js - JavaScript Cloud/Web Desktop Platform
3+
*
4+
* Copyright (c) 2011-2019, Anders Evenrud <[email protected]>
5+
* All rights reserved.
6+
*
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice, this
11+
* list of conditions and the following disclaimer
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20+
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23+
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
*
27+
* @author Anders Evenrud <[email protected]>
28+
* @licence Simplified BSD License
29+
*/
30+
import {EventEmitter} from '@osjs/event-emitter';
31+
import {h, app} from 'hyperapp';
32+
import {doubleTap} from '../../utils/input';
33+
34+
const tapper = doubleTap();
35+
36+
const view = (fileIcon, themeIcon, droppable) => (state, actions) =>
37+
h('div', {
38+
class: 'osjs-desktop-iconview__wrapper',
39+
oncontextmenu: ev => actions.openContextMenu({ev}),
40+
oncreate: el => {
41+
droppable(el, {
42+
ondrop: (ev, data, files) => {
43+
if (data && data.path) {
44+
actions.addEntry(data);
45+
} else if (files.length > 0) {
46+
actions.uploadEntries(files);
47+
}
48+
}
49+
});
50+
}
51+
}, state.entries.map((entry, index) => {
52+
return h('div', {
53+
class: 'osjs-desktop-iconview__entry' + (
54+
state.selected === index
55+
? ' osjs-desktop-iconview__entry--selected'
56+
: ''
57+
),
58+
oncontextmenu: ev => actions.openContextMenu({ev, entry, index}),
59+
ontouchstart: ev => tapper(ev, () => actions.openEntry({ev, entry, index})),
60+
ondblclick: ev => actions.openEntry({ev, entry, index}),
61+
onclick: ev => actions.selectEntry({ev, entry, index})
62+
}, [
63+
h('div', {
64+
class: 'osjs-desktop-iconview__entry__inner'
65+
}, [
66+
h('div', {
67+
class: 'osjs-desktop-iconview__entry__icon'
68+
}, h('img', {
69+
src: themeIcon(fileIcon(entry).name)
70+
})),
71+
h('div', {
72+
class: 'osjs-desktop-iconview__entry__label'
73+
}, entry.filename)
74+
])
75+
]);
76+
}));
77+
78+
/**
79+
* Desktop Icon View
80+
*/
81+
export class DesktopIconView extends EventEmitter {
82+
83+
/**
84+
* @param {Core} core Core reference
85+
*/
86+
constructor(core) {
87+
super('DesktopIconView');
88+
89+
this.core = core;
90+
this.$root = null;
91+
this.iconview = null;
92+
this.root = 'home:/.desktop';
93+
}
94+
95+
destroy() {
96+
if (this.$root && this.$root.parentNode) {
97+
this.$root.parentNode.removeChild(this.$root);
98+
}
99+
100+
this.iconview = null;
101+
this.$root = null;
102+
103+
this.emit('destroy');
104+
}
105+
106+
/**
107+
* @param {object} rect Rectangle from desktop
108+
*/
109+
resize(rect) {
110+
if (!this.$root) {
111+
return;
112+
}
113+
114+
this.$root.style.top = `${rect.top}px`;
115+
this.$root.style.left = `${rect.left}px`;
116+
this.$root.style.bottom = `${rect.bottom}px`;
117+
this.$root.style.right = `${rect.right}px`;
118+
}
119+
120+
_render(root) {
121+
const oldRoot = this.root;
122+
if (root) {
123+
this.root = root;
124+
}
125+
126+
if (this.$root) {
127+
if (this.root !== oldRoot) {
128+
this.iconview.reload();
129+
}
130+
131+
return false;
132+
}
133+
134+
return true;
135+
}
136+
137+
render(root) {
138+
if (!this._render(root)) {
139+
return;
140+
}
141+
142+
this.$root = document.createElement('div');
143+
this.$root.className = 'osjs-desktop-iconview';
144+
this.core.$root.appendChild(this.$root);
145+
146+
const {droppable} = this.core.make('osjs/dnd');
147+
const {icon: fileIcon} = this.core.make('osjs/fs');
148+
const {icon: themeIcon} = this.core.make('osjs/theme');
149+
const {copy, readdir, unlink} = this.core.make('osjs/vfs');
150+
const error = err => console.error(err);
151+
152+
this.iconview = app({
153+
selected: -1,
154+
entries: []
155+
}, {
156+
setEntries: entries => ({entries}),
157+
158+
openContextMenu: ({ev, entry}) => {
159+
if (entry) {
160+
this.createFileContextMenu(ev, entry);
161+
}
162+
},
163+
164+
openEntry: ({entry}) => {
165+
if (entry.isDirectory) {
166+
this.core.run('FileManager', {
167+
path: entry
168+
});
169+
} else {
170+
this.core.open(entry);
171+
}
172+
173+
return {selected: -1};
174+
},
175+
176+
selectEntry: ({index}) => ({selected: index}),
177+
178+
uploadEntries: files => {
179+
// TODO
180+
},
181+
182+
addEntry: entry => (state, actions) => {
183+
const dest = `${root}/${entry.filename}`;
184+
185+
copy(entry, dest)
186+
.then(() => actions.reload())
187+
.catch(error);
188+
189+
return {selected: -1};
190+
},
191+
192+
removeEntry: entry => (state, actions) => {
193+
unlink(entry)
194+
.then(() => actions.reload())
195+
.catch(error);
196+
197+
return {selected: -1};
198+
},
199+
200+
reload: () => (state, actions) => {
201+
readdir(root)
202+
.then(entries => entries.filter(e => e.filename !== '..'))
203+
.then(entries => actions.setEntries(entries));
204+
}
205+
206+
}, view(fileIcon, themeIcon, droppable), this.$root);
207+
208+
this.iconview.reload();
209+
}
210+
211+
createFileContextMenu(ev, entry) {
212+
this.core.make('osjs/contextmenu', {
213+
position: ev,
214+
menu: [{
215+
label: 'Open',
216+
onclick: () => this.iconview.openEntry(({entry}))
217+
}, {
218+
label: 'Remove',
219+
onclick: () => this.iconview.removeEntry(entry)
220+
}]
221+
});
222+
}
223+
}

src/config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ export const defaultConfiguration = {
177177
style: 'cover'
178178
},
179179
iconview: {
180-
enabled: false
180+
enabled: false,
181+
path: 'home:/.desktop'
181182
}
182183
}
183184
},

src/desktop.js

+35-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {EventEmitter} from '@osjs/event-emitter';
3232
import Application from './application';
3333
import {handleTabOnTextarea} from './utils/dom';
3434
import {matchKeyCombo} from './utils/input';
35+
import {DesktopIconView} from './adapters/ui/iconview';
3536
import Window from './window';
3637
import Search from './search';
3738
import merge from 'deepmerge';
@@ -121,14 +122,16 @@ export default class Desktop extends EventEmitter {
121122

122123
this.core = core;
123124
this.options = Object.assign({
124-
contextmenu: []
125+
contextmenu: [],
125126
}, options);
126127
this.$theme = [];
127128
this.$icons = [];
128129
this.$styles = document.createElement('style');
129130
this.$styles.setAttribute('type', 'text/css');
130131
this.contextmenuEntries = [];
131132
this.search = core.config('search.enabled') ? new Search(core) : null;
133+
this.iconview = new DesktopIconView(this.core);
134+
132135
this.subtract = {
133136
left: 0,
134137
top: 0,
@@ -145,9 +148,14 @@ export default class Desktop extends EventEmitter {
145148
this.search = this.search.destroy();
146149
}
147150

151+
if (this.iconview) {
152+
this.iconview.destroy();
153+
}
154+
148155
if (this.$styles && this.$styles.parentNode) {
149156
this.$styles.remove();
150157
}
158+
151159
this.$styles = null;
152160

153161
this._removeIcons();
@@ -212,6 +220,7 @@ export default class Desktop extends EventEmitter {
212220
try {
213221
this._updateCSS();
214222
Window.getWindows().forEach(w => w.clampToViewport());
223+
this._updateIconview();
215224
} catch (e) {
216225
logger.warn('Panel event error', e);
217226
}
@@ -394,7 +403,6 @@ export default class Desktop extends EventEmitter {
394403
this.core.on('osjs/settings:load', checkRTL);
395404
this.core.on('osjs/settings:save', checkRTL);
396405
this.core.on('osjs/core:started', checkRTL);
397-
398406
}
399407

400408
start() {
@@ -403,6 +411,13 @@ export default class Desktop extends EventEmitter {
403411
}
404412

405413
this._updateCSS();
414+
this._updateIconview();
415+
}
416+
417+
_updateIconview() {
418+
if (this.iconview) {
419+
this.iconview.resize(this.getRect());
420+
}
406421
}
407422

408423
/**
@@ -466,6 +481,8 @@ export default class Desktop extends EventEmitter {
466481
this.applyTheme(newSettings.theme);
467482
this.applyIcons(newSettings.icons);
468483

484+
this.applyIconView(newSettings.iconview);
485+
469486
this.core.emit('osjs/desktop:applySettings');
470487

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

526+
/**
527+
* Adds or removes the icon view
528+
*/
529+
applyIconView(settings) {
530+
if (!this.iconview) {
531+
return;
532+
}
533+
534+
if (settings.enabled) {
535+
this.iconview.render(settings.path);
536+
this.iconview.resize(this.getRect());
537+
} else {
538+
this.iconview.destroy();
539+
}
540+
}
541+
509542
/**
510543
* Sets the current icon theme from settings
511544
*/

0 commit comments

Comments
 (0)