30
30
import { EventEmitter } from '@osjs/event-emitter' ;
31
31
import { h , app } from 'hyperapp' ;
32
32
import { doubleTap } from '../../utils/input' ;
33
+ import { pathJoin } from '../../utils/vfs' ;
33
34
34
35
const tapper = doubleTap ( ) ;
35
36
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
+
36
47
const view = ( fileIcon , themeIcon , droppable ) => ( state , actions ) =>
37
48
h ( 'div' , {
38
49
class : 'osjs-desktop-iconview__wrapper' ,
39
50
oncontextmenu : ev => actions . openContextMenu ( { ev} ) ,
40
51
oncreate : el => {
41
52
droppable ( el , {
42
53
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 ) ;
47
58
}
48
59
}
49
60
} ) ;
@@ -65,16 +76,71 @@ const view = (fileIcon, themeIcon, droppable) => (state, actions) =>
65
76
} , [
66
77
h ( 'div' , {
67
78
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
+ ] ) ,
71
91
h ( 'div' , {
72
92
class : 'osjs-desktop-iconview__entry__label'
73
93
} , entry . filename )
74
94
] )
75
95
] ) ;
76
96
} ) ) ;
77
97
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
+
78
144
/**
79
145
* Desktop Icon View
80
146
*/
@@ -146,18 +212,26 @@ export class DesktopIconView extends EventEmitter {
146
212
const { droppable} = this . core . make ( 'osjs/dnd' ) ;
147
213
const { icon : fileIcon } = this . core . make ( 'osjs/fs' ) ;
148
214
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' ) ;
150
216
const error = err => console . error ( err ) ;
217
+ const shortcuts = createShortcuts ( root , readfile , writefile ) ;
218
+ const read = readDesktopFolder ( root , readdir , shortcuts ) ;
151
219
152
220
this . iconview = app ( {
153
221
selected : - 1 ,
154
222
entries : [ ]
155
223
} , {
156
224
setEntries : entries => ( { entries} ) ,
157
225
158
- openContextMenu : ( { ev, entry} ) => {
226
+ openDropContextMenu : ( { ev, data, files} ) => {
227
+ this . createDropContextMenu ( ev , data , files ) ;
228
+ } ,
229
+
230
+ openContextMenu : ( { ev, entry, index} ) => {
159
231
if ( entry ) {
160
232
this . createFileContextMenu ( ev , entry ) ;
233
+
234
+ return { selected : index } ;
161
235
}
162
236
} ,
163
237
@@ -182,26 +256,41 @@ export class DesktopIconView extends EventEmitter {
182
256
// TODO
183
257
} ,
184
258
185
- addEntry : entry => ( state , actions ) => {
259
+ addEntry : ( { entry, shortcut } ) => ( state , actions ) => {
186
260
const dest = `${ root } /${ entry . filename } ` ;
187
261
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 ( ) ) ;
191
274
192
275
return { selected : - 1 } ;
193
276
} ,
194
277
195
278
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
+ }
199
288
200
289
return { selected : - 1 } ;
201
290
} ,
202
291
203
292
reload : ( ) => ( state , actions ) => {
204
- readdir ( root )
293
+ read ( )
205
294
. then ( entries => entries . filter ( e => e . filename !== '..' ) )
206
295
. then ( entries => actions . setEntries ( entries ) ) ;
207
296
}
@@ -223,9 +312,26 @@ export class DesktopIconView extends EventEmitter {
223
312
label : _ ( 'LBL_OPEN_WITH' ) ,
224
313
onclick : ( ) => this . iconview . openEntry ( { entry, forceDialog : true } )
225
314
} , {
226
- label : _ ( 'LBL_DELETE' ) ,
315
+ label : entry . shortcut !== false ? _ ( 'LBL_REMOVE' ) : _ ( 'LBL_DELETE' ) ,
227
316
onclick : ( ) => this . iconview . removeEntry ( entry )
228
317
} ]
229
318
} ) ;
230
319
}
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
+ }
231
337
}
0 commit comments