retold-remote 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/css/retold-remote.css +83 -0
- package/html/codejar.js +511 -0
- package/html/index.html +23 -0
- package/package.json +68 -0
- package/server.js +43 -0
- package/source/Pict-Application-RetoldRemote-Configuration.json +7 -0
- package/source/Pict-Application-RetoldRemote.js +622 -0
- package/source/Pict-RetoldRemote-Bundle.js +14 -0
- package/source/cli/RetoldRemote-CLI-Program.js +15 -0
- package/source/cli/RetoldRemote-CLI-Run.js +3 -0
- package/source/cli/RetoldRemote-Server-Setup.js +257 -0
- package/source/cli/commands/RetoldRemote-Command-Serve.js +87 -0
- package/source/providers/Pict-Provider-GalleryFilterSort.js +597 -0
- package/source/providers/Pict-Provider-GalleryNavigation.js +819 -0
- package/source/providers/Pict-Provider-RetoldRemote.js +273 -0
- package/source/providers/Pict-Provider-RetoldRemoteIcons.js +640 -0
- package/source/providers/Pict-Provider-RetoldRemoteTheme.js +879 -0
- package/source/server/RetoldRemote-MediaService.js +536 -0
- package/source/server/RetoldRemote-PathRegistry.js +121 -0
- package/source/server/RetoldRemote-ThumbnailCache.js +89 -0
- package/source/server/RetoldRemote-ToolDetector.js +78 -0
- package/source/views/PictView-Remote-Gallery.js +1437 -0
- package/source/views/PictView-Remote-ImageViewer.js +363 -0
- package/source/views/PictView-Remote-Layout.js +420 -0
- package/source/views/PictView-Remote-MediaViewer.js +530 -0
- package/source/views/PictView-Remote-SettingsPanel.js +318 -0
- package/source/views/PictView-Remote-TopBar.js +206 -0
- package/web-application/codejar.js +511 -0
- package/web-application/css/retold-remote.css +83 -0
- package/web-application/index.html +23 -0
- package/web-application/js/pict.min.js +12 -0
- package/web-application/js/pict.min.js.map +1 -0
- package/web-application/retold-remote.compatible.js +5764 -0
- package/web-application/retold-remote.compatible.js.map +1 -0
- package/web-application/retold-remote.compatible.min.js +120 -0
- package/web-application/retold-remote.compatible.min.js.map +1 -0
- package/web-application/retold-remote.js +5763 -0
- package/web-application/retold-remote.js.map +1 -0
- package/web-application/retold-remote.min.js +120 -0
- package/web-application/retold-remote.min.js.map +1 -0
|
@@ -0,0 +1,1437 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
const _ViewConfiguration =
|
|
4
|
+
{
|
|
5
|
+
ViewIdentifier: "RetoldRemote-Gallery",
|
|
6
|
+
|
|
7
|
+
DefaultRenderable: "RetoldRemote-Gallery-Grid",
|
|
8
|
+
DefaultDestinationAddress: "#RetoldRemote-Gallery-Container",
|
|
9
|
+
|
|
10
|
+
AutoRender: false,
|
|
11
|
+
|
|
12
|
+
CSS: /*css*/`
|
|
13
|
+
.retold-remote-gallery-header
|
|
14
|
+
{
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: 8px;
|
|
18
|
+
margin-bottom: 8px;
|
|
19
|
+
flex-wrap: wrap;
|
|
20
|
+
}
|
|
21
|
+
.retold-remote-gallery-filter
|
|
22
|
+
{
|
|
23
|
+
display: flex;
|
|
24
|
+
gap: 4px;
|
|
25
|
+
}
|
|
26
|
+
.retold-remote-gallery-filter-btn
|
|
27
|
+
{
|
|
28
|
+
padding: 3px 10px;
|
|
29
|
+
border: 1px solid var(--retold-border);
|
|
30
|
+
border-radius: 3px;
|
|
31
|
+
background: transparent;
|
|
32
|
+
color: var(--retold-text-muted);
|
|
33
|
+
font-size: 0.72rem;
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
transition: color 0.15s, border-color 0.15s, background 0.15s;
|
|
36
|
+
font-family: inherit;
|
|
37
|
+
}
|
|
38
|
+
.retold-remote-gallery-filter-btn:hover
|
|
39
|
+
{
|
|
40
|
+
color: var(--retold-text-secondary);
|
|
41
|
+
border-color: var(--retold-scrollbar-hover);
|
|
42
|
+
}
|
|
43
|
+
.retold-remote-gallery-filter-btn.active
|
|
44
|
+
{
|
|
45
|
+
color: var(--retold-accent);
|
|
46
|
+
border-color: var(--retold-accent);
|
|
47
|
+
background: rgba(128, 128, 128, 0.1);
|
|
48
|
+
}
|
|
49
|
+
.retold-remote-gallery-filter-toggle
|
|
50
|
+
{
|
|
51
|
+
position: relative;
|
|
52
|
+
}
|
|
53
|
+
.retold-remote-gallery-filter-toggle.has-filters
|
|
54
|
+
{
|
|
55
|
+
color: var(--retold-accent);
|
|
56
|
+
border-color: var(--retold-accent);
|
|
57
|
+
}
|
|
58
|
+
.retold-remote-gallery-filter-count
|
|
59
|
+
{
|
|
60
|
+
display: inline-block;
|
|
61
|
+
min-width: 14px;
|
|
62
|
+
height: 14px;
|
|
63
|
+
line-height: 14px;
|
|
64
|
+
padding: 0 3px;
|
|
65
|
+
border-radius: 7px;
|
|
66
|
+
background: var(--retold-accent);
|
|
67
|
+
color: var(--retold-bg-tertiary);
|
|
68
|
+
font-size: 0.58rem;
|
|
69
|
+
font-weight: 700;
|
|
70
|
+
text-align: center;
|
|
71
|
+
margin-left: 4px;
|
|
72
|
+
}
|
|
73
|
+
/* Sort dropdown */
|
|
74
|
+
.retold-remote-gallery-sort
|
|
75
|
+
{
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
}
|
|
79
|
+
.retold-remote-gallery-sort-select
|
|
80
|
+
{
|
|
81
|
+
padding: 3px 6px;
|
|
82
|
+
border: 1px solid var(--retold-border);
|
|
83
|
+
border-radius: 3px;
|
|
84
|
+
background: var(--retold-bg-tertiary);
|
|
85
|
+
color: var(--retold-text-muted);
|
|
86
|
+
font-size: 0.72rem;
|
|
87
|
+
cursor: pointer;
|
|
88
|
+
font-family: inherit;
|
|
89
|
+
}
|
|
90
|
+
.retold-remote-gallery-sort-select:focus
|
|
91
|
+
{
|
|
92
|
+
outline: none;
|
|
93
|
+
border-color: var(--retold-accent);
|
|
94
|
+
}
|
|
95
|
+
.retold-remote-gallery-search
|
|
96
|
+
{
|
|
97
|
+
flex: 1;
|
|
98
|
+
max-width: 300px;
|
|
99
|
+
padding: 4px 10px;
|
|
100
|
+
border: 1px solid var(--retold-border);
|
|
101
|
+
border-radius: 3px;
|
|
102
|
+
background: var(--retold-bg-tertiary);
|
|
103
|
+
color: var(--retold-text-primary);
|
|
104
|
+
font-size: 0.78rem;
|
|
105
|
+
font-family: inherit;
|
|
106
|
+
}
|
|
107
|
+
.retold-remote-gallery-search:focus
|
|
108
|
+
{
|
|
109
|
+
outline: none;
|
|
110
|
+
border-color: var(--retold-accent);
|
|
111
|
+
}
|
|
112
|
+
.retold-remote-gallery-search::placeholder
|
|
113
|
+
{
|
|
114
|
+
color: var(--retold-text-placeholder);
|
|
115
|
+
}
|
|
116
|
+
/* Filter chips */
|
|
117
|
+
.retold-remote-filter-chips
|
|
118
|
+
{
|
|
119
|
+
display: flex;
|
|
120
|
+
flex-wrap: wrap;
|
|
121
|
+
gap: 6px;
|
|
122
|
+
margin-bottom: 8px;
|
|
123
|
+
align-items: center;
|
|
124
|
+
}
|
|
125
|
+
.retold-remote-filter-chip
|
|
126
|
+
{
|
|
127
|
+
display: inline-flex;
|
|
128
|
+
align-items: center;
|
|
129
|
+
gap: 4px;
|
|
130
|
+
padding: 2px 8px;
|
|
131
|
+
border: 1px solid var(--retold-border-light);
|
|
132
|
+
border-radius: 12px;
|
|
133
|
+
background: rgba(128, 128, 128, 0.08);
|
|
134
|
+
color: var(--retold-text-secondary);
|
|
135
|
+
font-size: 0.68rem;
|
|
136
|
+
}
|
|
137
|
+
.retold-remote-filter-chip-remove
|
|
138
|
+
{
|
|
139
|
+
background: none;
|
|
140
|
+
border: none;
|
|
141
|
+
color: var(--retold-text-muted);
|
|
142
|
+
font-size: 0.82rem;
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
padding: 0 2px;
|
|
145
|
+
line-height: 1;
|
|
146
|
+
}
|
|
147
|
+
.retold-remote-filter-chip-remove:hover
|
|
148
|
+
{
|
|
149
|
+
color: var(--retold-danger);
|
|
150
|
+
}
|
|
151
|
+
.retold-remote-filter-chip-clear
|
|
152
|
+
{
|
|
153
|
+
background: none;
|
|
154
|
+
border: none;
|
|
155
|
+
color: var(--retold-text-dim);
|
|
156
|
+
font-size: 0.68rem;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
padding: 2px 4px;
|
|
159
|
+
}
|
|
160
|
+
.retold-remote-filter-chip-clear:hover
|
|
161
|
+
{
|
|
162
|
+
color: var(--retold-danger);
|
|
163
|
+
}
|
|
164
|
+
/* Filter panel */
|
|
165
|
+
.retold-remote-filter-panel
|
|
166
|
+
{
|
|
167
|
+
margin-bottom: 12px;
|
|
168
|
+
padding: 12px;
|
|
169
|
+
border: 1px solid var(--retold-border);
|
|
170
|
+
border-radius: 6px;
|
|
171
|
+
background: var(--retold-bg-panel);
|
|
172
|
+
}
|
|
173
|
+
.retold-remote-filter-panel-grid
|
|
174
|
+
{
|
|
175
|
+
display: grid;
|
|
176
|
+
grid-template-columns: 1fr 1fr;
|
|
177
|
+
gap: 12px 24px;
|
|
178
|
+
}
|
|
179
|
+
.retold-remote-filter-section
|
|
180
|
+
{
|
|
181
|
+
margin-bottom: 0;
|
|
182
|
+
}
|
|
183
|
+
.retold-remote-filter-section-title
|
|
184
|
+
{
|
|
185
|
+
font-size: 0.68rem;
|
|
186
|
+
font-weight: 600;
|
|
187
|
+
color: var(--retold-text-muted);
|
|
188
|
+
text-transform: uppercase;
|
|
189
|
+
letter-spacing: 0.5px;
|
|
190
|
+
margin-bottom: 6px;
|
|
191
|
+
}
|
|
192
|
+
.retold-remote-filter-ext-list
|
|
193
|
+
{
|
|
194
|
+
display: flex;
|
|
195
|
+
flex-wrap: wrap;
|
|
196
|
+
gap: 2px 10px;
|
|
197
|
+
}
|
|
198
|
+
.retold-remote-filter-ext-item
|
|
199
|
+
{
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
gap: 4px;
|
|
203
|
+
font-size: 0.72rem;
|
|
204
|
+
color: var(--retold-text-secondary);
|
|
205
|
+
cursor: pointer;
|
|
206
|
+
}
|
|
207
|
+
.retold-remote-filter-ext-item input
|
|
208
|
+
{
|
|
209
|
+
accent-color: var(--retold-accent);
|
|
210
|
+
}
|
|
211
|
+
.retold-remote-filter-ext-count
|
|
212
|
+
{
|
|
213
|
+
color: var(--retold-text-dim);
|
|
214
|
+
font-size: 0.65rem;
|
|
215
|
+
}
|
|
216
|
+
.retold-remote-filter-row
|
|
217
|
+
{
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
gap: 6px;
|
|
221
|
+
}
|
|
222
|
+
.retold-remote-filter-input
|
|
223
|
+
{
|
|
224
|
+
padding: 3px 6px;
|
|
225
|
+
border: 1px solid var(--retold-border);
|
|
226
|
+
border-radius: 3px;
|
|
227
|
+
background: var(--retold-bg-tertiary);
|
|
228
|
+
color: var(--retold-text-secondary);
|
|
229
|
+
font-size: 0.72rem;
|
|
230
|
+
width: 100px;
|
|
231
|
+
font-family: inherit;
|
|
232
|
+
}
|
|
233
|
+
.retold-remote-filter-input:focus
|
|
234
|
+
{
|
|
235
|
+
outline: none;
|
|
236
|
+
border-color: var(--retold-accent);
|
|
237
|
+
}
|
|
238
|
+
.retold-remote-filter-label
|
|
239
|
+
{
|
|
240
|
+
color: var(--retold-text-dim);
|
|
241
|
+
font-size: 0.68rem;
|
|
242
|
+
}
|
|
243
|
+
.retold-remote-filter-actions
|
|
244
|
+
{
|
|
245
|
+
grid-column: 1 / -1;
|
|
246
|
+
display: flex;
|
|
247
|
+
gap: 8px;
|
|
248
|
+
align-items: center;
|
|
249
|
+
padding-top: 8px;
|
|
250
|
+
border-top: 1px solid var(--retold-border);
|
|
251
|
+
margin-top: 8px;
|
|
252
|
+
}
|
|
253
|
+
.retold-remote-filter-preset-row
|
|
254
|
+
{
|
|
255
|
+
display: flex;
|
|
256
|
+
gap: 6px;
|
|
257
|
+
align-items: center;
|
|
258
|
+
flex-wrap: wrap;
|
|
259
|
+
}
|
|
260
|
+
.retold-remote-filter-preset-select
|
|
261
|
+
{
|
|
262
|
+
padding: 3px 6px;
|
|
263
|
+
border: 1px solid var(--retold-border);
|
|
264
|
+
border-radius: 3px;
|
|
265
|
+
background: var(--retold-bg-tertiary);
|
|
266
|
+
color: var(--retold-text-secondary);
|
|
267
|
+
font-size: 0.72rem;
|
|
268
|
+
min-width: 100px;
|
|
269
|
+
font-family: inherit;
|
|
270
|
+
}
|
|
271
|
+
.retold-remote-filter-preset-input
|
|
272
|
+
{
|
|
273
|
+
padding: 3px 6px;
|
|
274
|
+
border: 1px solid var(--retold-border);
|
|
275
|
+
border-radius: 3px;
|
|
276
|
+
background: var(--retold-bg-tertiary);
|
|
277
|
+
color: var(--retold-text-secondary);
|
|
278
|
+
font-size: 0.72rem;
|
|
279
|
+
width: 120px;
|
|
280
|
+
font-family: inherit;
|
|
281
|
+
}
|
|
282
|
+
.retold-remote-filter-preset-input:focus
|
|
283
|
+
{
|
|
284
|
+
outline: none;
|
|
285
|
+
border-color: var(--retold-accent);
|
|
286
|
+
}
|
|
287
|
+
.retold-remote-filter-btn-sm
|
|
288
|
+
{
|
|
289
|
+
padding: 2px 8px;
|
|
290
|
+
border: 1px solid var(--retold-border);
|
|
291
|
+
border-radius: 3px;
|
|
292
|
+
background: transparent;
|
|
293
|
+
color: var(--retold-text-muted);
|
|
294
|
+
font-size: 0.68rem;
|
|
295
|
+
cursor: pointer;
|
|
296
|
+
font-family: inherit;
|
|
297
|
+
}
|
|
298
|
+
.retold-remote-filter-btn-sm:hover
|
|
299
|
+
{
|
|
300
|
+
color: var(--retold-text-secondary);
|
|
301
|
+
border-color: var(--retold-scrollbar-hover);
|
|
302
|
+
}
|
|
303
|
+
/* Grid layout */
|
|
304
|
+
.retold-remote-grid
|
|
305
|
+
{
|
|
306
|
+
display: grid;
|
|
307
|
+
gap: 12px;
|
|
308
|
+
}
|
|
309
|
+
.retold-remote-grid.size-small
|
|
310
|
+
{
|
|
311
|
+
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
|
312
|
+
}
|
|
313
|
+
.retold-remote-grid.size-medium
|
|
314
|
+
{
|
|
315
|
+
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
316
|
+
}
|
|
317
|
+
.retold-remote-grid.size-large
|
|
318
|
+
{
|
|
319
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
320
|
+
}
|
|
321
|
+
/* Tile */
|
|
322
|
+
.retold-remote-tile
|
|
323
|
+
{
|
|
324
|
+
background: var(--retold-bg-tertiary);
|
|
325
|
+
border: 2px solid transparent;
|
|
326
|
+
border-radius: 6px;
|
|
327
|
+
cursor: pointer;
|
|
328
|
+
overflow: hidden;
|
|
329
|
+
transition: border-color 0.15s, transform 0.1s;
|
|
330
|
+
}
|
|
331
|
+
.retold-remote-tile:hover
|
|
332
|
+
{
|
|
333
|
+
border-color: var(--retold-border-light);
|
|
334
|
+
}
|
|
335
|
+
.retold-remote-tile.selected
|
|
336
|
+
{
|
|
337
|
+
border-color: var(--retold-accent);
|
|
338
|
+
box-shadow: 0 0 0 1px rgba(128, 128, 128, 0.3);
|
|
339
|
+
}
|
|
340
|
+
.retold-remote-tile-thumb
|
|
341
|
+
{
|
|
342
|
+
position: relative;
|
|
343
|
+
width: 100%;
|
|
344
|
+
padding-bottom: 75%; /* 4:3 aspect ratio */
|
|
345
|
+
background: var(--retold-bg-thumb);
|
|
346
|
+
overflow: hidden;
|
|
347
|
+
}
|
|
348
|
+
.retold-remote-tile-thumb img
|
|
349
|
+
{
|
|
350
|
+
position: absolute;
|
|
351
|
+
top: 0;
|
|
352
|
+
left: 0;
|
|
353
|
+
width: 100%;
|
|
354
|
+
height: 100%;
|
|
355
|
+
object-fit: cover;
|
|
356
|
+
}
|
|
357
|
+
.retold-remote-tile-thumb-icon
|
|
358
|
+
{
|
|
359
|
+
position: absolute;
|
|
360
|
+
top: 50%;
|
|
361
|
+
left: 50%;
|
|
362
|
+
transform: translate(-50%, -50%);
|
|
363
|
+
font-size: 2rem;
|
|
364
|
+
color: var(--retold-text-placeholder);
|
|
365
|
+
}
|
|
366
|
+
.retold-remote-tile-badge
|
|
367
|
+
{
|
|
368
|
+
position: absolute;
|
|
369
|
+
top: 6px;
|
|
370
|
+
right: 6px;
|
|
371
|
+
padding: 1px 6px;
|
|
372
|
+
border-radius: 3px;
|
|
373
|
+
font-size: 0.6rem;
|
|
374
|
+
font-weight: 700;
|
|
375
|
+
text-transform: uppercase;
|
|
376
|
+
letter-spacing: 0.5px;
|
|
377
|
+
}
|
|
378
|
+
.retold-remote-tile-badge-image { background: rgba(102, 194, 184, 0.8); color: #fff; }
|
|
379
|
+
.retold-remote-tile-badge-video { background: rgba(180, 102, 194, 0.8); color: #fff; }
|
|
380
|
+
.retold-remote-tile-badge-audio { background: rgba(194, 160, 102, 0.8); color: #fff; }
|
|
381
|
+
.retold-remote-tile-badge-document { background: rgba(194, 102, 102, 0.8); color: #fff; }
|
|
382
|
+
.retold-remote-tile-badge-folder { background: rgba(102, 140, 194, 0.8); color: #fff; }
|
|
383
|
+
.retold-remote-tile-duration
|
|
384
|
+
{
|
|
385
|
+
position: absolute;
|
|
386
|
+
bottom: 6px;
|
|
387
|
+
right: 6px;
|
|
388
|
+
padding: 1px 6px;
|
|
389
|
+
border-radius: 3px;
|
|
390
|
+
background: rgba(0, 0, 0, 0.7);
|
|
391
|
+
font-size: 0.65rem;
|
|
392
|
+
color: var(--retold-text-primary);
|
|
393
|
+
}
|
|
394
|
+
.retold-remote-tile-label
|
|
395
|
+
{
|
|
396
|
+
padding: 6px 8px 2px 8px;
|
|
397
|
+
font-size: 0.75rem;
|
|
398
|
+
font-weight: 500;
|
|
399
|
+
color: var(--retold-text-secondary);
|
|
400
|
+
overflow: hidden;
|
|
401
|
+
text-overflow: ellipsis;
|
|
402
|
+
white-space: nowrap;
|
|
403
|
+
}
|
|
404
|
+
.retold-remote-tile-meta
|
|
405
|
+
{
|
|
406
|
+
padding: 0 8px 6px 8px;
|
|
407
|
+
font-size: 0.65rem;
|
|
408
|
+
color: var(--retold-text-dim);
|
|
409
|
+
}
|
|
410
|
+
/* List mode */
|
|
411
|
+
.retold-remote-list
|
|
412
|
+
{
|
|
413
|
+
display: flex;
|
|
414
|
+
flex-direction: column;
|
|
415
|
+
gap: 2px;
|
|
416
|
+
}
|
|
417
|
+
.retold-remote-list-row
|
|
418
|
+
{
|
|
419
|
+
display: flex;
|
|
420
|
+
align-items: center;
|
|
421
|
+
gap: 12px;
|
|
422
|
+
padding: 6px 12px;
|
|
423
|
+
border-radius: 4px;
|
|
424
|
+
cursor: pointer;
|
|
425
|
+
transition: background 0.1s;
|
|
426
|
+
}
|
|
427
|
+
.retold-remote-list-row:hover
|
|
428
|
+
{
|
|
429
|
+
background: var(--retold-bg-hover);
|
|
430
|
+
}
|
|
431
|
+
.retold-remote-list-row.selected
|
|
432
|
+
{
|
|
433
|
+
background: var(--retold-bg-selected);
|
|
434
|
+
}
|
|
435
|
+
.retold-remote-list-icon
|
|
436
|
+
{
|
|
437
|
+
flex-shrink: 0;
|
|
438
|
+
width: 24px;
|
|
439
|
+
text-align: center;
|
|
440
|
+
color: var(--retold-text-dim);
|
|
441
|
+
}
|
|
442
|
+
.retold-remote-list-name
|
|
443
|
+
{
|
|
444
|
+
flex: 1;
|
|
445
|
+
font-size: 0.82rem;
|
|
446
|
+
color: var(--retold-text-secondary);
|
|
447
|
+
overflow: hidden;
|
|
448
|
+
text-overflow: ellipsis;
|
|
449
|
+
white-space: nowrap;
|
|
450
|
+
}
|
|
451
|
+
.retold-remote-list-size
|
|
452
|
+
{
|
|
453
|
+
flex-shrink: 0;
|
|
454
|
+
width: 80px;
|
|
455
|
+
text-align: right;
|
|
456
|
+
font-size: 0.72rem;
|
|
457
|
+
color: var(--retold-text-dim);
|
|
458
|
+
}
|
|
459
|
+
.retold-remote-list-date
|
|
460
|
+
{
|
|
461
|
+
flex-shrink: 0;
|
|
462
|
+
width: 140px;
|
|
463
|
+
text-align: right;
|
|
464
|
+
font-size: 0.72rem;
|
|
465
|
+
color: var(--retold-text-dim);
|
|
466
|
+
}
|
|
467
|
+
/* Empty state */
|
|
468
|
+
.retold-remote-empty
|
|
469
|
+
{
|
|
470
|
+
text-align: center;
|
|
471
|
+
padding: 60px 20px;
|
|
472
|
+
color: var(--retold-text-dim);
|
|
473
|
+
}
|
|
474
|
+
.retold-remote-empty-icon
|
|
475
|
+
{
|
|
476
|
+
font-size: 3rem;
|
|
477
|
+
margin-bottom: 12px;
|
|
478
|
+
}
|
|
479
|
+
/* Help panel flyout */
|
|
480
|
+
#RetoldRemote-Help-Panel
|
|
481
|
+
{
|
|
482
|
+
position: fixed;
|
|
483
|
+
top: 0;
|
|
484
|
+
left: 0;
|
|
485
|
+
width: 100%;
|
|
486
|
+
height: 100%;
|
|
487
|
+
z-index: 9999;
|
|
488
|
+
}
|
|
489
|
+
.retold-remote-help-backdrop
|
|
490
|
+
{
|
|
491
|
+
position: absolute;
|
|
492
|
+
top: 0;
|
|
493
|
+
left: 0;
|
|
494
|
+
width: 100%;
|
|
495
|
+
height: 100%;
|
|
496
|
+
background: rgba(0, 0, 0, 0.5);
|
|
497
|
+
display: flex;
|
|
498
|
+
align-items: flex-start;
|
|
499
|
+
justify-content: flex-end;
|
|
500
|
+
}
|
|
501
|
+
.retold-remote-help-flyout
|
|
502
|
+
{
|
|
503
|
+
width: 340px;
|
|
504
|
+
max-height: calc(100vh - 32px);
|
|
505
|
+
margin: 16px;
|
|
506
|
+
background: var(--retold-bg-panel);
|
|
507
|
+
border: 1px solid var(--retold-border);
|
|
508
|
+
border-radius: 8px;
|
|
509
|
+
overflow-y: auto;
|
|
510
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
511
|
+
animation: retold-help-slide-in 0.15s ease-out;
|
|
512
|
+
}
|
|
513
|
+
@keyframes retold-help-slide-in
|
|
514
|
+
{
|
|
515
|
+
from { transform: translateX(20px); opacity: 0; }
|
|
516
|
+
to { transform: translateX(0); opacity: 1; }
|
|
517
|
+
}
|
|
518
|
+
.retold-remote-help-header
|
|
519
|
+
{
|
|
520
|
+
display: flex;
|
|
521
|
+
align-items: center;
|
|
522
|
+
justify-content: space-between;
|
|
523
|
+
padding: 14px 16px 10px 16px;
|
|
524
|
+
border-bottom: 1px solid var(--retold-border);
|
|
525
|
+
}
|
|
526
|
+
.retold-remote-help-title
|
|
527
|
+
{
|
|
528
|
+
font-size: 0.88rem;
|
|
529
|
+
font-weight: 600;
|
|
530
|
+
color: var(--retold-text-primary);
|
|
531
|
+
}
|
|
532
|
+
.retold-remote-help-close
|
|
533
|
+
{
|
|
534
|
+
background: none;
|
|
535
|
+
border: none;
|
|
536
|
+
color: var(--retold-text-dim);
|
|
537
|
+
font-size: 1.2rem;
|
|
538
|
+
cursor: pointer;
|
|
539
|
+
padding: 0 4px;
|
|
540
|
+
line-height: 1;
|
|
541
|
+
}
|
|
542
|
+
.retold-remote-help-close:hover
|
|
543
|
+
{
|
|
544
|
+
color: var(--retold-danger);
|
|
545
|
+
}
|
|
546
|
+
.retold-remote-help-section
|
|
547
|
+
{
|
|
548
|
+
padding: 12px 16px 8px 16px;
|
|
549
|
+
}
|
|
550
|
+
.retold-remote-help-section + .retold-remote-help-section
|
|
551
|
+
{
|
|
552
|
+
border-top: 1px solid var(--retold-border);
|
|
553
|
+
}
|
|
554
|
+
.retold-remote-help-section-title
|
|
555
|
+
{
|
|
556
|
+
font-size: 0.65rem;
|
|
557
|
+
font-weight: 600;
|
|
558
|
+
color: var(--retold-accent);
|
|
559
|
+
text-transform: uppercase;
|
|
560
|
+
letter-spacing: 0.8px;
|
|
561
|
+
margin-bottom: 8px;
|
|
562
|
+
}
|
|
563
|
+
.retold-remote-help-row
|
|
564
|
+
{
|
|
565
|
+
display: flex;
|
|
566
|
+
align-items: center;
|
|
567
|
+
gap: 12px;
|
|
568
|
+
padding: 3px 0;
|
|
569
|
+
}
|
|
570
|
+
.retold-remote-help-key
|
|
571
|
+
{
|
|
572
|
+
display: inline-block;
|
|
573
|
+
min-width: 72px;
|
|
574
|
+
padding: 2px 8px;
|
|
575
|
+
border: 1px solid var(--retold-border-light);
|
|
576
|
+
border-radius: 4px;
|
|
577
|
+
background: rgba(255, 255, 255, 0.04);
|
|
578
|
+
color: var(--retold-text-secondary);
|
|
579
|
+
font-family: var(--retold-font-mono, monospace);
|
|
580
|
+
font-size: 0.72rem;
|
|
581
|
+
text-align: center;
|
|
582
|
+
white-space: nowrap;
|
|
583
|
+
}
|
|
584
|
+
.retold-remote-help-desc
|
|
585
|
+
{
|
|
586
|
+
color: var(--retold-text-muted);
|
|
587
|
+
font-size: 0.74rem;
|
|
588
|
+
}
|
|
589
|
+
.retold-remote-help-footer
|
|
590
|
+
{
|
|
591
|
+
padding: 10px 16px;
|
|
592
|
+
border-top: 1px solid var(--retold-border);
|
|
593
|
+
font-size: 0.68rem;
|
|
594
|
+
color: var(--retold-text-dim);
|
|
595
|
+
text-align: center;
|
|
596
|
+
}
|
|
597
|
+
.retold-remote-help-footer strong
|
|
598
|
+
{
|
|
599
|
+
color: var(--retold-accent);
|
|
600
|
+
}
|
|
601
|
+
`,
|
|
602
|
+
|
|
603
|
+
Templates: [],
|
|
604
|
+
Renderables: []
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
class RetoldRemoteGalleryView extends libPictView
|
|
608
|
+
{
|
|
609
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
610
|
+
{
|
|
611
|
+
super(pFable, pOptions, pServiceHash);
|
|
612
|
+
|
|
613
|
+
this._intersectionObserver = null;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// ──────────────────────────────────────────────
|
|
617
|
+
// Gallery rendering
|
|
618
|
+
// ──────────────────────────────────────────────
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Render the gallery based on current state.
|
|
622
|
+
* GalleryItems is already filtered+sorted by the pipeline provider.
|
|
623
|
+
*/
|
|
624
|
+
renderGallery()
|
|
625
|
+
{
|
|
626
|
+
let tmpContainer = document.getElementById('RetoldRemote-Gallery-Container');
|
|
627
|
+
if (!tmpContainer)
|
|
628
|
+
{
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
633
|
+
let tmpItems = tmpRemote.GalleryItems || [];
|
|
634
|
+
let tmpViewMode = tmpRemote.ViewMode || 'list';
|
|
635
|
+
let tmpThumbnailSize = tmpRemote.ThumbnailSize || 'medium';
|
|
636
|
+
let tmpCursorIndex = tmpRemote.GalleryCursorIndex || 0;
|
|
637
|
+
|
|
638
|
+
// Build header with type filters, sort, filter toggle, and search
|
|
639
|
+
let tmpHTML = this._buildHeaderHTML(tmpRemote.FilterState ? tmpRemote.FilterState.MediaType : 'all');
|
|
640
|
+
|
|
641
|
+
// Build collapsible filter panel
|
|
642
|
+
tmpHTML += this._buildFilterPanelHTML();
|
|
643
|
+
|
|
644
|
+
// Build filter chips bar
|
|
645
|
+
tmpHTML += this._buildFilterChipsHTML();
|
|
646
|
+
|
|
647
|
+
if (tmpItems.length === 0)
|
|
648
|
+
{
|
|
649
|
+
tmpHTML += '<div class="retold-remote-empty">';
|
|
650
|
+
let tmpEmptyIconProvider = this.pict.providers['RetoldRemote-Icons'];
|
|
651
|
+
tmpHTML += '<div class="retold-remote-empty-icon"><span class="retold-remote-icon retold-remote-icon-xl">' + (tmpEmptyIconProvider ? tmpEmptyIconProvider.getIcon('gallery-empty', 96) : '') + '</span></div>';
|
|
652
|
+
tmpHTML += '<div>Empty folder</div>';
|
|
653
|
+
tmpHTML += '</div>';
|
|
654
|
+
tmpContainer.innerHTML = tmpHTML;
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Items are already filtered+sorted by the pipeline
|
|
659
|
+
if (tmpViewMode === 'gallery')
|
|
660
|
+
{
|
|
661
|
+
tmpHTML += this._buildGridHTML(tmpItems, tmpThumbnailSize, tmpCursorIndex);
|
|
662
|
+
}
|
|
663
|
+
else
|
|
664
|
+
{
|
|
665
|
+
tmpHTML += this._buildListHTML(tmpItems, tmpCursorIndex);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
tmpContainer.innerHTML = tmpHTML;
|
|
669
|
+
|
|
670
|
+
// Set up lazy loading for thumbnail images
|
|
671
|
+
this._setupLazyLoading();
|
|
672
|
+
|
|
673
|
+
// Recalculate column count for keyboard navigation
|
|
674
|
+
let tmpNavProvider = this.pict.providers['RetoldRemote-GalleryNavigation'];
|
|
675
|
+
if (tmpNavProvider)
|
|
676
|
+
{
|
|
677
|
+
tmpNavProvider.recalculateColumns();
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// ──────────────────────────────────────────────
|
|
682
|
+
// Header
|
|
683
|
+
// ──────────────────────────────────────────────
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Build the gallery header with type filters, sort dropdown, filter toggle, and search.
|
|
687
|
+
*/
|
|
688
|
+
_buildHeaderHTML(pActiveFilter)
|
|
689
|
+
{
|
|
690
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
691
|
+
let tmpFilters = [
|
|
692
|
+
{ key: 'all', label: 'All' },
|
|
693
|
+
{ key: 'images', label: 'Images' },
|
|
694
|
+
{ key: 'video', label: 'Video' },
|
|
695
|
+
{ key: 'audio', label: 'Audio' },
|
|
696
|
+
{ key: 'documents', label: 'Docs' }
|
|
697
|
+
];
|
|
698
|
+
|
|
699
|
+
let tmpHTML = '<div class="retold-remote-gallery-header">';
|
|
700
|
+
|
|
701
|
+
// Media type filter buttons
|
|
702
|
+
tmpHTML += '<div class="retold-remote-gallery-filter">';
|
|
703
|
+
for (let i = 0; i < tmpFilters.length; i++)
|
|
704
|
+
{
|
|
705
|
+
let tmpFilter = tmpFilters[i];
|
|
706
|
+
let tmpActiveClass = (tmpFilter.key === pActiveFilter) ? ' active' : '';
|
|
707
|
+
tmpHTML += '<button class="retold-remote-gallery-filter-btn' + tmpActiveClass + '" '
|
|
708
|
+
+ 'onclick="pict.views[\'RetoldRemote-Gallery\'].setFilter(\'' + tmpFilter.key + '\')">'
|
|
709
|
+
+ tmpFilter.label + '</button>';
|
|
710
|
+
}
|
|
711
|
+
tmpHTML += '</div>';
|
|
712
|
+
|
|
713
|
+
// Sort dropdown
|
|
714
|
+
tmpHTML += '<div class="retold-remote-gallery-sort">';
|
|
715
|
+
tmpHTML += '<select class="retold-remote-gallery-sort-select" id="RetoldRemote-Gallery-Sort" '
|
|
716
|
+
+ 'onchange="pict.views[\'RetoldRemote-Gallery\'].onSortChange(this.value)">';
|
|
717
|
+
let tmpSortOptions = [
|
|
718
|
+
{ value: 'folder-first:asc', label: 'Folders first' },
|
|
719
|
+
{ value: 'name:asc', label: 'Name A\u2013Z' },
|
|
720
|
+
{ value: 'name:desc', label: 'Name Z\u2013A' },
|
|
721
|
+
{ value: 'modified:desc', label: 'Newest modified' },
|
|
722
|
+
{ value: 'modified:asc', label: 'Oldest modified' },
|
|
723
|
+
{ value: 'created:desc', label: 'Newest created' },
|
|
724
|
+
{ value: 'created:asc', label: 'Oldest created' }
|
|
725
|
+
];
|
|
726
|
+
let tmpCurrentSort = (tmpRemote.SortField || 'folder-first') + ':' + (tmpRemote.SortDirection || 'asc');
|
|
727
|
+
for (let i = 0; i < tmpSortOptions.length; i++)
|
|
728
|
+
{
|
|
729
|
+
let tmpSelected = (tmpSortOptions[i].value === tmpCurrentSort) ? ' selected' : '';
|
|
730
|
+
tmpHTML += '<option value="' + tmpSortOptions[i].value + '"' + tmpSelected + '>'
|
|
731
|
+
+ tmpSortOptions[i].label + '</option>';
|
|
732
|
+
}
|
|
733
|
+
tmpHTML += '</select>';
|
|
734
|
+
tmpHTML += '</div>';
|
|
735
|
+
|
|
736
|
+
// Filter panel toggle button
|
|
737
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
738
|
+
let tmpActiveChipCount = tmpFilterSort ? tmpFilterSort.getActiveFilterChips().length : 0;
|
|
739
|
+
// Don't count search chip in the toggle badge since it's obvious from the search input
|
|
740
|
+
let tmpBadgeCount = tmpActiveChipCount;
|
|
741
|
+
let tmpHasFiltersClass = tmpBadgeCount > 0 ? ' has-filters' : '';
|
|
742
|
+
tmpHTML += '<button class="retold-remote-gallery-filter-btn retold-remote-gallery-filter-toggle' + tmpHasFiltersClass + '" '
|
|
743
|
+
+ 'onclick="pict.views[\'RetoldRemote-Gallery\'].toggleFilterPanel()">'
|
|
744
|
+
+ '\u2699 Filters';
|
|
745
|
+
if (tmpBadgeCount > 0)
|
|
746
|
+
{
|
|
747
|
+
tmpHTML += '<span class="retold-remote-gallery-filter-count">' + tmpBadgeCount + '</span>';
|
|
748
|
+
}
|
|
749
|
+
tmpHTML += '</button>';
|
|
750
|
+
|
|
751
|
+
// Search input
|
|
752
|
+
let tmpSearchValue = tmpRemote.SearchQuery || '';
|
|
753
|
+
tmpHTML += '<input type="text" class="retold-remote-gallery-search" id="RetoldRemote-Gallery-Search" '
|
|
754
|
+
+ 'placeholder="Search files... (/)" '
|
|
755
|
+
+ 'value="' + this._escapeHTML(tmpSearchValue) + '" '
|
|
756
|
+
+ 'oninput="pict.views[\'RetoldRemote-Gallery\'].onSearchInput(this.value)">';
|
|
757
|
+
tmpHTML += '</div>';
|
|
758
|
+
|
|
759
|
+
return tmpHTML;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// ──────────────────────────────────────────────
|
|
763
|
+
// Filter panel (collapsible)
|
|
764
|
+
// ──────────────────────────────────────────────
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Build the collapsible advanced filter panel HTML.
|
|
768
|
+
*/
|
|
769
|
+
_buildFilterPanelHTML()
|
|
770
|
+
{
|
|
771
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
772
|
+
if (!tmpRemote.FilterPanelOpen)
|
|
773
|
+
{
|
|
774
|
+
return '';
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
778
|
+
let tmpAvailableExtensions = tmpFilterSort ? tmpFilterSort.getAvailableExtensions() : [];
|
|
779
|
+
let tmpFilterState = tmpRemote.FilterState || {};
|
|
780
|
+
|
|
781
|
+
let tmpHTML = '<div class="retold-remote-filter-panel">';
|
|
782
|
+
tmpHTML += '<div class="retold-remote-filter-panel-grid">';
|
|
783
|
+
|
|
784
|
+
// Extension filter: checkboxes for each extension in the current folder
|
|
785
|
+
tmpHTML += '<div class="retold-remote-filter-section">';
|
|
786
|
+
tmpHTML += '<div class="retold-remote-filter-section-title">File Type</div>';
|
|
787
|
+
tmpHTML += '<div class="retold-remote-filter-ext-list">';
|
|
788
|
+
let tmpActiveExtensions = tmpFilterState.Extensions || [];
|
|
789
|
+
for (let i = 0; i < tmpAvailableExtensions.length; i++)
|
|
790
|
+
{
|
|
791
|
+
let tmpExt = tmpAvailableExtensions[i];
|
|
792
|
+
// If Extensions is empty, all are selected
|
|
793
|
+
let tmpChecked = (tmpActiveExtensions.length === 0 || tmpActiveExtensions.indexOf(tmpExt.ext) >= 0);
|
|
794
|
+
tmpHTML += '<label class="retold-remote-filter-ext-item">';
|
|
795
|
+
tmpHTML += '<input type="checkbox" ' + (tmpChecked ? 'checked ' : '')
|
|
796
|
+
+ 'onchange="pict.views[\'RetoldRemote-Gallery\'].onExtensionToggle(\'' + tmpExt.ext + '\', this.checked)">';
|
|
797
|
+
tmpHTML += ' .' + tmpExt.ext + ' <span class="retold-remote-filter-ext-count">(' + tmpExt.count + ')</span>';
|
|
798
|
+
tmpHTML += '</label>';
|
|
799
|
+
}
|
|
800
|
+
tmpHTML += '</div>';
|
|
801
|
+
tmpHTML += '</div>';
|
|
802
|
+
|
|
803
|
+
// Size range
|
|
804
|
+
tmpHTML += '<div class="retold-remote-filter-section">';
|
|
805
|
+
tmpHTML += '<div class="retold-remote-filter-section-title">File Size</div>';
|
|
806
|
+
tmpHTML += '<div class="retold-remote-filter-row">';
|
|
807
|
+
let tmpMinKB = (tmpFilterState.SizeMin !== null && tmpFilterState.SizeMin !== undefined) ? Math.round(tmpFilterState.SizeMin / 1024) : '';
|
|
808
|
+
let tmpMaxKB = (tmpFilterState.SizeMax !== null && tmpFilterState.SizeMax !== undefined) ? Math.round(tmpFilterState.SizeMax / 1024) : '';
|
|
809
|
+
tmpHTML += '<input type="number" class="retold-remote-filter-input" placeholder="Min KB" '
|
|
810
|
+
+ 'value="' + tmpMinKB + '" '
|
|
811
|
+
+ 'onchange="pict.views[\'RetoldRemote-Gallery\'].onSizeFilterChange(\'min\', this.value)">';
|
|
812
|
+
tmpHTML += '<span class="retold-remote-filter-label">to</span>';
|
|
813
|
+
tmpHTML += '<input type="number" class="retold-remote-filter-input" placeholder="Max KB" '
|
|
814
|
+
+ 'value="' + tmpMaxKB + '" '
|
|
815
|
+
+ 'onchange="pict.views[\'RetoldRemote-Gallery\'].onSizeFilterChange(\'max\', this.value)">';
|
|
816
|
+
tmpHTML += '<span class="retold-remote-filter-label">KB</span>';
|
|
817
|
+
tmpHTML += '</div>';
|
|
818
|
+
tmpHTML += '</div>';
|
|
819
|
+
|
|
820
|
+
// Modified date range
|
|
821
|
+
tmpHTML += '<div class="retold-remote-filter-section">';
|
|
822
|
+
tmpHTML += '<div class="retold-remote-filter-section-title">Modified Date</div>';
|
|
823
|
+
tmpHTML += '<div class="retold-remote-filter-row">';
|
|
824
|
+
tmpHTML += '<input type="date" class="retold-remote-filter-input" '
|
|
825
|
+
+ 'value="' + (tmpFilterState.DateModifiedAfter || '') + '" '
|
|
826
|
+
+ 'onchange="pict.views[\'RetoldRemote-Gallery\'].onDateFilterChange(\'DateModifiedAfter\', this.value)">';
|
|
827
|
+
tmpHTML += '<span class="retold-remote-filter-label">to</span>';
|
|
828
|
+
tmpHTML += '<input type="date" class="retold-remote-filter-input" '
|
|
829
|
+
+ 'value="' + (tmpFilterState.DateModifiedBefore || '') + '" '
|
|
830
|
+
+ 'onchange="pict.views[\'RetoldRemote-Gallery\'].onDateFilterChange(\'DateModifiedBefore\', this.value)">';
|
|
831
|
+
tmpHTML += '</div>';
|
|
832
|
+
tmpHTML += '</div>';
|
|
833
|
+
|
|
834
|
+
// Presets
|
|
835
|
+
tmpHTML += '<div class="retold-remote-filter-section">';
|
|
836
|
+
tmpHTML += '<div class="retold-remote-filter-section-title">Presets</div>';
|
|
837
|
+
tmpHTML += this._buildPresetControlsHTML();
|
|
838
|
+
tmpHTML += '</div>';
|
|
839
|
+
|
|
840
|
+
// Actions row
|
|
841
|
+
tmpHTML += '<div class="retold-remote-filter-actions">';
|
|
842
|
+
tmpHTML += '<button class="retold-remote-filter-btn-sm" onclick="pict.views[\'RetoldRemote-Gallery\'].clearAllFilters()">Clear All Filters</button>';
|
|
843
|
+
tmpHTML += '</div>';
|
|
844
|
+
|
|
845
|
+
tmpHTML += '</div>'; // end grid
|
|
846
|
+
tmpHTML += '</div>'; // end panel
|
|
847
|
+
|
|
848
|
+
return tmpHTML;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Build preset controls (save/load/delete).
|
|
853
|
+
*/
|
|
854
|
+
_buildPresetControlsHTML()
|
|
855
|
+
{
|
|
856
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
857
|
+
let tmpPresets = tmpRemote.FilterPresets || [];
|
|
858
|
+
|
|
859
|
+
let tmpHTML = '<div class="retold-remote-filter-preset-row">';
|
|
860
|
+
|
|
861
|
+
// Load preset dropdown
|
|
862
|
+
if (tmpPresets.length > 0)
|
|
863
|
+
{
|
|
864
|
+
tmpHTML += '<select class="retold-remote-filter-preset-select" id="RetoldRemote-Filter-PresetSelect" '
|
|
865
|
+
+ 'onchange="pict.views[\'RetoldRemote-Gallery\'].loadFilterPreset(this.value)">';
|
|
866
|
+
tmpHTML += '<option value="">Load preset...</option>';
|
|
867
|
+
for (let i = 0; i < tmpPresets.length; i++)
|
|
868
|
+
{
|
|
869
|
+
tmpHTML += '<option value="' + i + '">' + this._escapeHTML(tmpPresets[i].Name) + '</option>';
|
|
870
|
+
}
|
|
871
|
+
tmpHTML += '</select>';
|
|
872
|
+
tmpHTML += '<button class="retold-remote-filter-btn-sm" onclick="pict.views[\'RetoldRemote-Gallery\'].deleteSelectedPreset()">\u2715</button>';
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Save new preset
|
|
876
|
+
tmpHTML += '<input type="text" class="retold-remote-filter-preset-input" id="RetoldRemote-Filter-PresetName" '
|
|
877
|
+
+ 'placeholder="Preset name...">';
|
|
878
|
+
tmpHTML += '<button class="retold-remote-filter-btn-sm" onclick="pict.views[\'RetoldRemote-Gallery\'].saveFilterPreset()">Save</button>';
|
|
879
|
+
|
|
880
|
+
tmpHTML += '</div>';
|
|
881
|
+
return tmpHTML;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// ──────────────────────────────────────────────
|
|
885
|
+
// Filter chips
|
|
886
|
+
// ──────────────────────────────────────────────
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Build filter chips bar showing all active filters.
|
|
890
|
+
*/
|
|
891
|
+
_buildFilterChipsHTML()
|
|
892
|
+
{
|
|
893
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
894
|
+
if (!tmpFilterSort)
|
|
895
|
+
{
|
|
896
|
+
return '';
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
let tmpChips = tmpFilterSort.getActiveFilterChips();
|
|
900
|
+
if (tmpChips.length === 0)
|
|
901
|
+
{
|
|
902
|
+
return '';
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
let tmpHTML = '<div class="retold-remote-filter-chips">';
|
|
906
|
+
for (let i = 0; i < tmpChips.length; i++)
|
|
907
|
+
{
|
|
908
|
+
let tmpChip = tmpChips[i];
|
|
909
|
+
tmpHTML += '<span class="retold-remote-filter-chip">'
|
|
910
|
+
+ this._escapeHTML(tmpChip.label)
|
|
911
|
+
+ ' <button class="retold-remote-filter-chip-remove" '
|
|
912
|
+
+ 'onclick="pict.views[\'RetoldRemote-Gallery\'].removeFilterChip(\'' + this._escapeHTML(tmpChip.key) + '\')">×</button>'
|
|
913
|
+
+ '</span>';
|
|
914
|
+
}
|
|
915
|
+
tmpHTML += '<button class="retold-remote-filter-chip-clear" onclick="pict.views[\'RetoldRemote-Gallery\'].clearAllFilters()">Clear all</button>';
|
|
916
|
+
tmpHTML += '</div>';
|
|
917
|
+
return tmpHTML;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// ──────────────────────────────────────────────
|
|
921
|
+
// Grid and list HTML builders
|
|
922
|
+
// ──────────────────────────────────────────────
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Build the grid view HTML.
|
|
926
|
+
*/
|
|
927
|
+
_buildGridHTML(pItems, pThumbnailSize, pCursorIndex)
|
|
928
|
+
{
|
|
929
|
+
let tmpHTML = '<div class="retold-remote-grid size-' + pThumbnailSize + '">';
|
|
930
|
+
let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
|
|
931
|
+
let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
|
|
932
|
+
|
|
933
|
+
for (let i = 0; i < pItems.length; i++)
|
|
934
|
+
{
|
|
935
|
+
let tmpItem = pItems[i];
|
|
936
|
+
let tmpSelectedClass = (i === pCursorIndex) ? ' selected' : '';
|
|
937
|
+
let tmpExtension = (tmpItem.Extension || '').toLowerCase();
|
|
938
|
+
let tmpCategory = this._getCategory(tmpExtension, tmpItem.Type);
|
|
939
|
+
|
|
940
|
+
tmpHTML += '<div class="retold-remote-tile' + tmpSelectedClass + '" '
|
|
941
|
+
+ 'data-index="' + i + '" '
|
|
942
|
+
+ 'onclick="pict.views[\'RetoldRemote-Gallery\'].onTileClick(' + i + ')" '
|
|
943
|
+
+ 'ondblclick="pict.views[\'RetoldRemote-Gallery\'].onTileDoubleClick(' + i + ')">';
|
|
944
|
+
|
|
945
|
+
// Thumbnail area
|
|
946
|
+
tmpHTML += '<div class="retold-remote-tile-thumb">';
|
|
947
|
+
|
|
948
|
+
if (tmpItem.Type === 'folder')
|
|
949
|
+
{
|
|
950
|
+
tmpHTML += '<div class="retold-remote-tile-thumb-icon"><span class="retold-remote-icon retold-remote-icon-md">' + (tmpIconProvider ? tmpIconProvider.getIcon('folder', 48) : '') + '</span></div>';
|
|
951
|
+
tmpHTML += '<span class="retold-remote-tile-badge retold-remote-tile-badge-folder">Folder</span>';
|
|
952
|
+
}
|
|
953
|
+
else if (tmpCategory === 'image' && tmpProvider)
|
|
954
|
+
{
|
|
955
|
+
let tmpThumbURL = tmpProvider.getThumbnailURL(tmpItem.Path, 400, 300);
|
|
956
|
+
if (tmpThumbURL)
|
|
957
|
+
{
|
|
958
|
+
tmpHTML += '<img data-src="' + tmpThumbURL + '" alt="' + this._escapeHTML(tmpItem.Name) + '" loading="lazy">';
|
|
959
|
+
}
|
|
960
|
+
else
|
|
961
|
+
{
|
|
962
|
+
tmpHTML += '<div class="retold-remote-tile-thumb-icon"><span class="retold-remote-icon retold-remote-icon-md">' + (tmpIconProvider ? tmpIconProvider.getIcon('file-image', 48) : '') + '</span></div>';
|
|
963
|
+
}
|
|
964
|
+
tmpHTML += '<span class="retold-remote-tile-badge retold-remote-tile-badge-image">' + tmpExtension + '</span>';
|
|
965
|
+
}
|
|
966
|
+
else if (tmpCategory === 'video')
|
|
967
|
+
{
|
|
968
|
+
if (tmpProvider)
|
|
969
|
+
{
|
|
970
|
+
let tmpThumbURL = tmpProvider.getThumbnailURL(tmpItem.Path, 400, 300);
|
|
971
|
+
if (tmpThumbURL)
|
|
972
|
+
{
|
|
973
|
+
tmpHTML += '<img data-src="' + tmpThumbURL + '" alt="' + this._escapeHTML(tmpItem.Name) + '" loading="lazy">';
|
|
974
|
+
}
|
|
975
|
+
else
|
|
976
|
+
{
|
|
977
|
+
tmpHTML += '<div class="retold-remote-tile-thumb-icon"><span class="retold-remote-icon retold-remote-icon-md">' + (tmpIconProvider ? tmpIconProvider.getIcon('file-video', 48) : '') + '</span></div>';
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
else
|
|
981
|
+
{
|
|
982
|
+
tmpHTML += '<div class="retold-remote-tile-thumb-icon"><span class="retold-remote-icon retold-remote-icon-md">' + (tmpIconProvider ? tmpIconProvider.getIcon('file-video', 48) : '') + '</span></div>';
|
|
983
|
+
}
|
|
984
|
+
tmpHTML += '<span class="retold-remote-tile-badge retold-remote-tile-badge-video">Video</span>';
|
|
985
|
+
}
|
|
986
|
+
else if (tmpCategory === 'audio')
|
|
987
|
+
{
|
|
988
|
+
tmpHTML += '<div class="retold-remote-tile-thumb-icon"><span class="retold-remote-icon retold-remote-icon-md">' + (tmpIconProvider ? tmpIconProvider.getIcon('file-audio', 48) : '') + '</span></div>';
|
|
989
|
+
tmpHTML += '<span class="retold-remote-tile-badge retold-remote-tile-badge-audio">Audio</span>';
|
|
990
|
+
}
|
|
991
|
+
else if (tmpCategory === 'document')
|
|
992
|
+
{
|
|
993
|
+
tmpHTML += '<div class="retold-remote-tile-thumb-icon"><span class="retold-remote-icon retold-remote-icon-md">' + (tmpIconProvider ? tmpIconProvider.getIconForEntry(tmpItem, 48) : '') + '</span></div>';
|
|
994
|
+
tmpHTML += '<span class="retold-remote-tile-badge retold-remote-tile-badge-document">' + tmpExtension + '</span>';
|
|
995
|
+
}
|
|
996
|
+
else
|
|
997
|
+
{
|
|
998
|
+
tmpHTML += '<div class="retold-remote-tile-thumb-icon"><span class="retold-remote-icon retold-remote-icon-md">' + (tmpIconProvider ? tmpIconProvider.getIconForEntry(tmpItem, 48) : '') + '</span></div>';
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
tmpHTML += '</div>'; // end thumb
|
|
1002
|
+
|
|
1003
|
+
// Label
|
|
1004
|
+
tmpHTML += '<div class="retold-remote-tile-label" title="' + this._escapeHTML(tmpItem.Name) + '">' + this._escapeHTML(tmpItem.Name) + '</div>';
|
|
1005
|
+
|
|
1006
|
+
// Meta
|
|
1007
|
+
if (tmpItem.Type === 'file' && tmpItem.Size !== undefined)
|
|
1008
|
+
{
|
|
1009
|
+
tmpHTML += '<div class="retold-remote-tile-meta">' + this._formatFileSize(tmpItem.Size) + '</div>';
|
|
1010
|
+
}
|
|
1011
|
+
else if (tmpItem.Type === 'folder')
|
|
1012
|
+
{
|
|
1013
|
+
tmpHTML += '<div class="retold-remote-tile-meta">Folder</div>';
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
tmpHTML += '</div>'; // end tile
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
tmpHTML += '</div>'; // end grid
|
|
1020
|
+
|
|
1021
|
+
return tmpHTML;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Build the list view HTML.
|
|
1026
|
+
*/
|
|
1027
|
+
_buildListHTML(pItems, pCursorIndex)
|
|
1028
|
+
{
|
|
1029
|
+
let tmpHTML = '<div class="retold-remote-list">';
|
|
1030
|
+
let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
|
|
1031
|
+
|
|
1032
|
+
for (let i = 0; i < pItems.length; i++)
|
|
1033
|
+
{
|
|
1034
|
+
let tmpItem = pItems[i];
|
|
1035
|
+
let tmpSelectedClass = (i === pCursorIndex) ? ' selected' : '';
|
|
1036
|
+
let tmpIcon = '';
|
|
1037
|
+
if (tmpIconProvider)
|
|
1038
|
+
{
|
|
1039
|
+
tmpIcon = '<span class="retold-remote-icon retold-remote-icon-sm">' + tmpIconProvider.getIconForEntry(tmpItem, 16) + '</span>';
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
tmpHTML += '<div class="retold-remote-list-row' + tmpSelectedClass + '" '
|
|
1043
|
+
+ 'data-index="' + i + '" '
|
|
1044
|
+
+ 'onclick="pict.views[\'RetoldRemote-Gallery\'].onTileClick(' + i + ')" '
|
|
1045
|
+
+ 'ondblclick="pict.views[\'RetoldRemote-Gallery\'].onTileDoubleClick(' + i + ')">';
|
|
1046
|
+
|
|
1047
|
+
tmpHTML += '<div class="retold-remote-list-icon">' + tmpIcon + '</div>';
|
|
1048
|
+
tmpHTML += '<div class="retold-remote-list-name">' + this._escapeHTML(tmpItem.Name) + '</div>';
|
|
1049
|
+
|
|
1050
|
+
if (tmpItem.Type === 'file' && tmpItem.Size !== undefined)
|
|
1051
|
+
{
|
|
1052
|
+
tmpHTML += '<div class="retold-remote-list-size">' + this._formatFileSize(tmpItem.Size) + '</div>';
|
|
1053
|
+
}
|
|
1054
|
+
else
|
|
1055
|
+
{
|
|
1056
|
+
tmpHTML += '<div class="retold-remote-list-size"></div>';
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (tmpItem.Modified)
|
|
1060
|
+
{
|
|
1061
|
+
tmpHTML += '<div class="retold-remote-list-date">' + new Date(tmpItem.Modified).toLocaleDateString() + '</div>';
|
|
1062
|
+
}
|
|
1063
|
+
else
|
|
1064
|
+
{
|
|
1065
|
+
tmpHTML += '<div class="retold-remote-list-date"></div>';
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
tmpHTML += '</div>';
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
tmpHTML += '</div>';
|
|
1072
|
+
|
|
1073
|
+
return tmpHTML;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// ──────────────────────────────────────────────
|
|
1077
|
+
// Lazy loading
|
|
1078
|
+
// ──────────────────────────────────────────────
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Set up IntersectionObserver for lazy-loading thumbnail images.
|
|
1082
|
+
*/
|
|
1083
|
+
_setupLazyLoading()
|
|
1084
|
+
{
|
|
1085
|
+
if (this._intersectionObserver)
|
|
1086
|
+
{
|
|
1087
|
+
this._intersectionObserver.disconnect();
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
let tmpImages = document.querySelectorAll('.retold-remote-tile-thumb img[data-src]');
|
|
1091
|
+
if (tmpImages.length === 0)
|
|
1092
|
+
{
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
this._intersectionObserver = new IntersectionObserver(
|
|
1097
|
+
(pEntries) =>
|
|
1098
|
+
{
|
|
1099
|
+
for (let i = 0; i < pEntries.length; i++)
|
|
1100
|
+
{
|
|
1101
|
+
if (pEntries[i].isIntersecting)
|
|
1102
|
+
{
|
|
1103
|
+
let tmpImg = pEntries[i].target;
|
|
1104
|
+
tmpImg.src = tmpImg.getAttribute('data-src');
|
|
1105
|
+
tmpImg.removeAttribute('data-src');
|
|
1106
|
+
this._intersectionObserver.unobserve(tmpImg);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
{ rootMargin: '200px' }
|
|
1111
|
+
);
|
|
1112
|
+
|
|
1113
|
+
for (let i = 0; i < tmpImages.length; i++)
|
|
1114
|
+
{
|
|
1115
|
+
this._intersectionObserver.observe(tmpImages[i]);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// ──────────────────────────────────────────────
|
|
1120
|
+
// Event handlers
|
|
1121
|
+
// ──────────────────────────────────────────────
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Handle single click on a tile (select it).
|
|
1125
|
+
*/
|
|
1126
|
+
onTileClick(pIndex)
|
|
1127
|
+
{
|
|
1128
|
+
let tmpNavProvider = this.pict.providers['RetoldRemote-GalleryNavigation'];
|
|
1129
|
+
if (tmpNavProvider)
|
|
1130
|
+
{
|
|
1131
|
+
tmpNavProvider.moveCursor(pIndex);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Handle double click on a tile (open it).
|
|
1137
|
+
*/
|
|
1138
|
+
onTileDoubleClick(pIndex)
|
|
1139
|
+
{
|
|
1140
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
1141
|
+
tmpRemote.GalleryCursorIndex = pIndex;
|
|
1142
|
+
|
|
1143
|
+
let tmpNavProvider = this.pict.providers['RetoldRemote-GalleryNavigation'];
|
|
1144
|
+
if (tmpNavProvider)
|
|
1145
|
+
{
|
|
1146
|
+
tmpNavProvider.openCurrent();
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/**
|
|
1151
|
+
* Set the media type filter (via type filter buttons).
|
|
1152
|
+
*/
|
|
1153
|
+
setFilter(pFilter)
|
|
1154
|
+
{
|
|
1155
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
1156
|
+
tmpRemote.GalleryFilter = pFilter;
|
|
1157
|
+
tmpRemote.FilterState.MediaType = pFilter;
|
|
1158
|
+
|
|
1159
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
1160
|
+
if (tmpFilterSort)
|
|
1161
|
+
{
|
|
1162
|
+
tmpFilterSort.applyFilterSort();
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Handle search input.
|
|
1168
|
+
*/
|
|
1169
|
+
onSearchInput(pValue)
|
|
1170
|
+
{
|
|
1171
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
1172
|
+
tmpRemote.SearchQuery = (pValue || '').toLowerCase();
|
|
1173
|
+
|
|
1174
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
1175
|
+
if (tmpFilterSort)
|
|
1176
|
+
{
|
|
1177
|
+
tmpFilterSort.applyFilterSort();
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Handle sort dropdown change.
|
|
1183
|
+
*/
|
|
1184
|
+
onSortChange(pValue)
|
|
1185
|
+
{
|
|
1186
|
+
let tmpParts = pValue.split(':');
|
|
1187
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
1188
|
+
tmpRemote.SortField = tmpParts[0];
|
|
1189
|
+
tmpRemote.SortDirection = tmpParts[1] || 'asc';
|
|
1190
|
+
|
|
1191
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
1192
|
+
if (tmpFilterSort)
|
|
1193
|
+
{
|
|
1194
|
+
tmpFilterSort.applyFilterSort();
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// Persist sort preference
|
|
1198
|
+
if (this.pict.PictApplication && this.pict.PictApplication.saveSettings)
|
|
1199
|
+
{
|
|
1200
|
+
this.pict.PictApplication.saveSettings();
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* Toggle the advanced filter panel.
|
|
1206
|
+
*/
|
|
1207
|
+
toggleFilterPanel()
|
|
1208
|
+
{
|
|
1209
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
1210
|
+
tmpRemote.FilterPanelOpen = !tmpRemote.FilterPanelOpen;
|
|
1211
|
+
this.renderGallery();
|
|
1212
|
+
|
|
1213
|
+
if (this.pict.PictApplication && this.pict.PictApplication.saveSettings)
|
|
1214
|
+
{
|
|
1215
|
+
this.pict.PictApplication.saveSettings();
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/**
|
|
1220
|
+
* Handle extension checkbox toggle.
|
|
1221
|
+
*/
|
|
1222
|
+
onExtensionToggle(pExtension, pChecked)
|
|
1223
|
+
{
|
|
1224
|
+
let tmpFilterState = this.pict.AppData.RetoldRemote.FilterState;
|
|
1225
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
1226
|
+
|
|
1227
|
+
if (pChecked)
|
|
1228
|
+
{
|
|
1229
|
+
if (tmpFilterState.Extensions.length > 0)
|
|
1230
|
+
{
|
|
1231
|
+
tmpFilterState.Extensions.push(pExtension);
|
|
1232
|
+
}
|
|
1233
|
+
// If empty (all selected), adding back is a no-op
|
|
1234
|
+
}
|
|
1235
|
+
else
|
|
1236
|
+
{
|
|
1237
|
+
// If removing from "all", populate the full list minus this one
|
|
1238
|
+
if (tmpFilterState.Extensions.length === 0 && tmpFilterSort)
|
|
1239
|
+
{
|
|
1240
|
+
let tmpAll = tmpFilterSort.getAvailableExtensions();
|
|
1241
|
+
tmpFilterState.Extensions = tmpAll.map((e) => e.ext).filter((e) => e !== pExtension);
|
|
1242
|
+
}
|
|
1243
|
+
else
|
|
1244
|
+
{
|
|
1245
|
+
tmpFilterState.Extensions = tmpFilterState.Extensions.filter((e) => e !== pExtension);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (tmpFilterSort)
|
|
1250
|
+
{
|
|
1251
|
+
tmpFilterSort.applyFilterSort();
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Handle size filter change.
|
|
1257
|
+
*/
|
|
1258
|
+
onSizeFilterChange(pWhich, pValueKB)
|
|
1259
|
+
{
|
|
1260
|
+
let tmpFilterState = this.pict.AppData.RetoldRemote.FilterState;
|
|
1261
|
+
let tmpBytes = (pValueKB && pValueKB !== '') ? parseInt(pValueKB, 10) * 1024 : null;
|
|
1262
|
+
|
|
1263
|
+
if (pWhich === 'min')
|
|
1264
|
+
{
|
|
1265
|
+
tmpFilterState.SizeMin = tmpBytes;
|
|
1266
|
+
}
|
|
1267
|
+
else
|
|
1268
|
+
{
|
|
1269
|
+
tmpFilterState.SizeMax = tmpBytes;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
1273
|
+
if (tmpFilterSort)
|
|
1274
|
+
{
|
|
1275
|
+
tmpFilterSort.applyFilterSort();
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Handle date filter change.
|
|
1281
|
+
*/
|
|
1282
|
+
onDateFilterChange(pField, pValue)
|
|
1283
|
+
{
|
|
1284
|
+
let tmpFilterState = this.pict.AppData.RetoldRemote.FilterState;
|
|
1285
|
+
tmpFilterState[pField] = pValue || null;
|
|
1286
|
+
|
|
1287
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
1288
|
+
if (tmpFilterSort)
|
|
1289
|
+
{
|
|
1290
|
+
tmpFilterSort.applyFilterSort();
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* Remove a filter chip.
|
|
1296
|
+
*/
|
|
1297
|
+
removeFilterChip(pKey)
|
|
1298
|
+
{
|
|
1299
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
1300
|
+
if (tmpFilterSort)
|
|
1301
|
+
{
|
|
1302
|
+
tmpFilterSort.removeFilter(pKey);
|
|
1303
|
+
tmpFilterSort.applyFilterSort();
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
/**
|
|
1308
|
+
* Clear all filters.
|
|
1309
|
+
*/
|
|
1310
|
+
clearAllFilters()
|
|
1311
|
+
{
|
|
1312
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
1313
|
+
if (tmpFilterSort)
|
|
1314
|
+
{
|
|
1315
|
+
tmpFilterSort.clearAllFilters();
|
|
1316
|
+
tmpFilterSort.applyFilterSort();
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Save the current filter config as a named preset.
|
|
1322
|
+
*/
|
|
1323
|
+
saveFilterPreset()
|
|
1324
|
+
{
|
|
1325
|
+
let tmpInput = document.getElementById('RetoldRemote-Filter-PresetName');
|
|
1326
|
+
if (!tmpInput || !tmpInput.value.trim())
|
|
1327
|
+
{
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
1332
|
+
if (tmpFilterSort)
|
|
1333
|
+
{
|
|
1334
|
+
tmpFilterSort.savePreset(tmpInput.value.trim());
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
if (this.pict.PictApplication && this.pict.PictApplication.saveSettings)
|
|
1338
|
+
{
|
|
1339
|
+
this.pict.PictApplication.saveSettings();
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
this.renderGallery();
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
/**
|
|
1346
|
+
* Load a filter preset from the dropdown.
|
|
1347
|
+
*/
|
|
1348
|
+
loadFilterPreset(pIndex)
|
|
1349
|
+
{
|
|
1350
|
+
if (pIndex === '' || pIndex === null || pIndex === undefined)
|
|
1351
|
+
{
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
1356
|
+
if (tmpFilterSort)
|
|
1357
|
+
{
|
|
1358
|
+
tmpFilterSort.loadPreset(parseInt(pIndex, 10));
|
|
1359
|
+
tmpFilterSort.applyFilterSort();
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
if (this.pict.PictApplication && this.pict.PictApplication.saveSettings)
|
|
1363
|
+
{
|
|
1364
|
+
this.pict.PictApplication.saveSettings();
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/**
|
|
1369
|
+
* Delete the currently selected preset from the dropdown.
|
|
1370
|
+
*/
|
|
1371
|
+
deleteSelectedPreset()
|
|
1372
|
+
{
|
|
1373
|
+
let tmpSelect = document.getElementById('RetoldRemote-Filter-PresetSelect');
|
|
1374
|
+
if (!tmpSelect || tmpSelect.value === '')
|
|
1375
|
+
{
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
1380
|
+
if (tmpFilterSort)
|
|
1381
|
+
{
|
|
1382
|
+
tmpFilterSort.deletePreset(parseInt(tmpSelect.value, 10));
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
if (this.pict.PictApplication && this.pict.PictApplication.saveSettings)
|
|
1386
|
+
{
|
|
1387
|
+
this.pict.PictApplication.saveSettings();
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
this.renderGallery();
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// ──────────────────────────────────────────────
|
|
1394
|
+
// Utilities
|
|
1395
|
+
// ──────────────────────────────────────────────
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Get the media category for a file.
|
|
1399
|
+
*/
|
|
1400
|
+
_getCategory(pExtension, pType)
|
|
1401
|
+
{
|
|
1402
|
+
if (pType === 'folder') return 'folder';
|
|
1403
|
+
// Delegate to the filter/sort provider if available
|
|
1404
|
+
let tmpFilterSort = this.pict.providers['RetoldRemote-GalleryFilterSort'];
|
|
1405
|
+
if (tmpFilterSort)
|
|
1406
|
+
{
|
|
1407
|
+
return tmpFilterSort.getCategory(pExtension);
|
|
1408
|
+
}
|
|
1409
|
+
// Fallback
|
|
1410
|
+
let tmpExt = (pExtension || '').replace(/^\./, '').toLowerCase();
|
|
1411
|
+
if (tmpExt === 'png' || tmpExt === 'jpg' || tmpExt === 'jpeg' || tmpExt === 'gif' || tmpExt === 'webp') return 'image';
|
|
1412
|
+
if (tmpExt === 'mp4' || tmpExt === 'webm' || tmpExt === 'mov') return 'video';
|
|
1413
|
+
if (tmpExt === 'mp3' || tmpExt === 'wav' || tmpExt === 'ogg') return 'audio';
|
|
1414
|
+
if (tmpExt === 'pdf') return 'document';
|
|
1415
|
+
return 'other';
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
_formatFileSize(pBytes)
|
|
1419
|
+
{
|
|
1420
|
+
if (!pBytes || pBytes === 0) return '0 B';
|
|
1421
|
+
let tmpUnits = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
1422
|
+
let tmpIndex = Math.floor(Math.log(pBytes) / Math.log(1024));
|
|
1423
|
+
if (tmpIndex >= tmpUnits.length) tmpIndex = tmpUnits.length - 1;
|
|
1424
|
+
let tmpSize = pBytes / Math.pow(1024, tmpIndex);
|
|
1425
|
+
return tmpSize.toFixed(tmpIndex === 0 ? 0 : 1) + ' ' + tmpUnits[tmpIndex];
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
_escapeHTML(pText)
|
|
1429
|
+
{
|
|
1430
|
+
if (!pText) return '';
|
|
1431
|
+
return pText.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
RetoldRemoteGalleryView.default_configuration = _ViewConfiguration;
|
|
1436
|
+
|
|
1437
|
+
module.exports = RetoldRemoteGalleryView;
|