3030import { EventEmitter } from '@osjs/event-emitter' ;
3131import { h , app } from 'hyperapp' ;
3232import { doubleTap } from '../../utils/input' ;
33+ import { pathJoin } from '../../utils/vfs' ;
3334
3435const 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+
3647const 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}
0 commit comments