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,597 @@
|
|
|
1
|
+
const libPictProvider = require('pict-provider');
|
|
2
|
+
|
|
3
|
+
const _ImageExtensions = { 'png': true, 'jpg': true, 'jpeg': true, 'gif': true, 'webp': true, 'svg': true, 'bmp': true, 'ico': true, 'avif': true, 'tiff': true, 'tif': true };
|
|
4
|
+
const _VideoExtensions = { 'mp4': true, 'webm': true, 'mov': true, 'mkv': true, 'avi': true, 'wmv': true, 'flv': true, 'm4v': true };
|
|
5
|
+
const _AudioExtensions = { 'mp3': true, 'wav': true, 'ogg': true, 'flac': true, 'aac': true, 'm4a': true, 'wma': true };
|
|
6
|
+
const _DocumentExtensions = { 'pdf': true, 'epub': true, 'mobi': true };
|
|
7
|
+
|
|
8
|
+
const _DefaultProviderConfiguration =
|
|
9
|
+
{
|
|
10
|
+
ProviderIdentifier: 'RetoldRemote-GalleryFilterSort',
|
|
11
|
+
AutoInitialize: true,
|
|
12
|
+
AutoInitializeOrdinal: 0,
|
|
13
|
+
AutoSolveWithApp: false
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
class GalleryFilterSortProvider extends libPictProvider
|
|
17
|
+
{
|
|
18
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
19
|
+
{
|
|
20
|
+
super(pFable, pOptions, pServiceHash);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ──────────────────────────────────────────────
|
|
24
|
+
// Pipeline
|
|
25
|
+
// ──────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Run the full filter+sort pipeline.
|
|
29
|
+
* Reads from RawFileList, applies all active filters and sort,
|
|
30
|
+
* writes result into GalleryItems, resets cursor, and re-renders.
|
|
31
|
+
*/
|
|
32
|
+
applyFilterSort()
|
|
33
|
+
{
|
|
34
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
35
|
+
if (!tmpRemote)
|
|
36
|
+
{
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let tmpItems = (tmpRemote.RawFileList || []).slice();
|
|
41
|
+
|
|
42
|
+
// 1. Text search
|
|
43
|
+
tmpItems = this._applySearch(tmpItems, tmpRemote.SearchQuery);
|
|
44
|
+
|
|
45
|
+
// 2. Media type filter
|
|
46
|
+
let tmpFilterState = tmpRemote.FilterState || {};
|
|
47
|
+
tmpItems = this._applyMediaTypeFilter(tmpItems, tmpFilterState.MediaType || 'all');
|
|
48
|
+
|
|
49
|
+
// 3. Extension filter
|
|
50
|
+
tmpItems = this._applyExtensionFilter(tmpItems, tmpFilterState.Extensions || []);
|
|
51
|
+
|
|
52
|
+
// 4. Size range filter
|
|
53
|
+
tmpItems = this._applySizeFilter(tmpItems, tmpFilterState.SizeMin, tmpFilterState.SizeMax);
|
|
54
|
+
|
|
55
|
+
// 5. Date range filter
|
|
56
|
+
tmpItems = this._applyDateFilter(tmpItems, tmpFilterState);
|
|
57
|
+
|
|
58
|
+
// 6. Sort
|
|
59
|
+
tmpItems = this._sortItems(tmpItems, tmpRemote.SortField || 'folder-first', tmpRemote.SortDirection || 'asc');
|
|
60
|
+
|
|
61
|
+
// Write result
|
|
62
|
+
tmpRemote.GalleryItems = tmpItems;
|
|
63
|
+
tmpRemote.GalleryCursorIndex = 0;
|
|
64
|
+
|
|
65
|
+
// Re-render gallery
|
|
66
|
+
let tmpGalleryView = this.pict.views['RetoldRemote-Gallery'];
|
|
67
|
+
if (tmpGalleryView)
|
|
68
|
+
{
|
|
69
|
+
tmpGalleryView.renderGallery();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ──────────────────────────────────────────────
|
|
74
|
+
// Filter stages
|
|
75
|
+
// ──────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Search filter: substring match on item Name.
|
|
79
|
+
*/
|
|
80
|
+
_applySearch(pItems, pQuery)
|
|
81
|
+
{
|
|
82
|
+
if (!pQuery)
|
|
83
|
+
{
|
|
84
|
+
return pItems;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let tmpQuery = pQuery.toLowerCase();
|
|
88
|
+
return pItems.filter((pItem) =>
|
|
89
|
+
{
|
|
90
|
+
return pItem.Name.toLowerCase().includes(tmpQuery);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Media type filter: by category. Folders always pass.
|
|
96
|
+
*/
|
|
97
|
+
_applyMediaTypeFilter(pItems, pMediaType)
|
|
98
|
+
{
|
|
99
|
+
if (pMediaType === 'all')
|
|
100
|
+
{
|
|
101
|
+
return pItems;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return pItems.filter((pItem) =>
|
|
105
|
+
{
|
|
106
|
+
if (pItem.Type === 'folder')
|
|
107
|
+
{
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let tmpCategory = this.getCategory((pItem.Extension || '').toLowerCase());
|
|
112
|
+
|
|
113
|
+
if (pMediaType === 'images') return tmpCategory === 'image';
|
|
114
|
+
if (pMediaType === 'video') return tmpCategory === 'video';
|
|
115
|
+
if (pMediaType === 'audio') return tmpCategory === 'audio';
|
|
116
|
+
if (pMediaType === 'documents') return tmpCategory === 'document';
|
|
117
|
+
return true;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Extension filter: only matching extensions pass. Folders always pass.
|
|
123
|
+
* An empty array means "all extensions".
|
|
124
|
+
*/
|
|
125
|
+
_applyExtensionFilter(pItems, pExtensions)
|
|
126
|
+
{
|
|
127
|
+
if (!pExtensions || pExtensions.length === 0)
|
|
128
|
+
{
|
|
129
|
+
return pItems;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Build a fast lookup set
|
|
133
|
+
let tmpExtSet = {};
|
|
134
|
+
for (let i = 0; i < pExtensions.length; i++)
|
|
135
|
+
{
|
|
136
|
+
tmpExtSet[pExtensions[i].toLowerCase()] = true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return pItems.filter((pItem) =>
|
|
140
|
+
{
|
|
141
|
+
if (pItem.Type === 'folder')
|
|
142
|
+
{
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
let tmpExt = (pItem.Extension || '').replace(/^\./, '').toLowerCase();
|
|
146
|
+
return tmpExtSet[tmpExt] === true;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* File size range filter. Folders always pass.
|
|
152
|
+
*/
|
|
153
|
+
_applySizeFilter(pItems, pMin, pMax)
|
|
154
|
+
{
|
|
155
|
+
if (pMin === null && pMax === null)
|
|
156
|
+
{
|
|
157
|
+
return pItems;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return pItems.filter((pItem) =>
|
|
161
|
+
{
|
|
162
|
+
if (pItem.Type === 'folder')
|
|
163
|
+
{
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
let tmpSize = pItem.Size || 0;
|
|
167
|
+
if (pMin !== null && tmpSize < pMin)
|
|
168
|
+
{
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
if (pMax !== null && tmpSize > pMax)
|
|
172
|
+
{
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
return true;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Date range filter on Modified and/or Created fields. Folders always pass.
|
|
181
|
+
*/
|
|
182
|
+
_applyDateFilter(pItems, pDateFilters)
|
|
183
|
+
{
|
|
184
|
+
let tmpModAfter = pDateFilters.DateModifiedAfter ? new Date(pDateFilters.DateModifiedAfter).getTime() : null;
|
|
185
|
+
let tmpModBefore = pDateFilters.DateModifiedBefore ? new Date(pDateFilters.DateModifiedBefore + 'T23:59:59').getTime() : null;
|
|
186
|
+
let tmpCreatedAfter = pDateFilters.DateCreatedAfter ? new Date(pDateFilters.DateCreatedAfter).getTime() : null;
|
|
187
|
+
let tmpCreatedBefore = pDateFilters.DateCreatedBefore ? new Date(pDateFilters.DateCreatedBefore + 'T23:59:59').getTime() : null;
|
|
188
|
+
|
|
189
|
+
if (tmpModAfter === null && tmpModBefore === null && tmpCreatedAfter === null && tmpCreatedBefore === null)
|
|
190
|
+
{
|
|
191
|
+
return pItems;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return pItems.filter((pItem) =>
|
|
195
|
+
{
|
|
196
|
+
if (pItem.Type === 'folder')
|
|
197
|
+
{
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (tmpModAfter !== null || tmpModBefore !== null)
|
|
202
|
+
{
|
|
203
|
+
let tmpMod = pItem.Modified ? new Date(pItem.Modified).getTime() : 0;
|
|
204
|
+
if (tmpModAfter !== null && tmpMod < tmpModAfter) return false;
|
|
205
|
+
if (tmpModBefore !== null && tmpMod > tmpModBefore) return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (tmpCreatedAfter !== null || tmpCreatedBefore !== null)
|
|
209
|
+
{
|
|
210
|
+
let tmpCreated = pItem.Created ? new Date(pItem.Created).getTime() : 0;
|
|
211
|
+
if (tmpCreatedAfter !== null && tmpCreated < tmpCreatedAfter) return false;
|
|
212
|
+
if (tmpCreatedBefore !== null && tmpCreated > tmpCreatedBefore) return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return true;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ──────────────────────────────────────────────
|
|
220
|
+
// Sort
|
|
221
|
+
// ──────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Sort items by the specified field and direction.
|
|
225
|
+
*/
|
|
226
|
+
_sortItems(pItems, pSortField, pSortDirection)
|
|
227
|
+
{
|
|
228
|
+
let tmpDirection = (pSortDirection === 'desc') ? -1 : 1;
|
|
229
|
+
|
|
230
|
+
return pItems.slice().sort((pA, pB) =>
|
|
231
|
+
{
|
|
232
|
+
// 'folder-first' mode: folders always sort before files
|
|
233
|
+
if (pSortField === 'folder-first')
|
|
234
|
+
{
|
|
235
|
+
if (pA.Type === 'folder' && pB.Type !== 'folder') return -1;
|
|
236
|
+
if (pA.Type !== 'folder' && pB.Type === 'folder') return 1;
|
|
237
|
+
// Both same type: sort by name ascending
|
|
238
|
+
let tmpNameA = (pA.Name || '').toLowerCase();
|
|
239
|
+
let tmpNameB = (pB.Name || '').toLowerCase();
|
|
240
|
+
if (tmpNameA < tmpNameB) return -1 * tmpDirection;
|
|
241
|
+
if (tmpNameA > tmpNameB) return 1 * tmpDirection;
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (pSortField === 'name')
|
|
246
|
+
{
|
|
247
|
+
let tmpNameA = (pA.Name || '').toLowerCase();
|
|
248
|
+
let tmpNameB = (pB.Name || '').toLowerCase();
|
|
249
|
+
if (tmpNameA < tmpNameB) return -1 * tmpDirection;
|
|
250
|
+
if (tmpNameA > tmpNameB) return 1 * tmpDirection;
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (pSortField === 'modified')
|
|
255
|
+
{
|
|
256
|
+
let tmpDateA = pA.Modified ? new Date(pA.Modified).getTime() : 0;
|
|
257
|
+
let tmpDateB = pB.Modified ? new Date(pB.Modified).getTime() : 0;
|
|
258
|
+
if (tmpDateA !== tmpDateB) return (tmpDateA - tmpDateB) * tmpDirection;
|
|
259
|
+
// Tiebreaker: name ascending
|
|
260
|
+
let tmpNameA = (pA.Name || '').toLowerCase();
|
|
261
|
+
let tmpNameB = (pB.Name || '').toLowerCase();
|
|
262
|
+
return tmpNameA < tmpNameB ? -1 : (tmpNameA > tmpNameB ? 1 : 0);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (pSortField === 'created')
|
|
266
|
+
{
|
|
267
|
+
let tmpDateA = pA.Created ? new Date(pA.Created).getTime() : 0;
|
|
268
|
+
let tmpDateB = pB.Created ? new Date(pB.Created).getTime() : 0;
|
|
269
|
+
if (tmpDateA !== tmpDateB) return (tmpDateA - tmpDateB) * tmpDirection;
|
|
270
|
+
// Tiebreaker: name ascending
|
|
271
|
+
let tmpNameA = (pA.Name || '').toLowerCase();
|
|
272
|
+
let tmpNameB = (pB.Name || '').toLowerCase();
|
|
273
|
+
return tmpNameA < tmpNameB ? -1 : (tmpNameA > tmpNameB ? 1 : 0);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return 0;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ──────────────────────────────────────────────
|
|
281
|
+
// Category helper
|
|
282
|
+
// ──────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get the media category for an extension string (with or without leading dot).
|
|
286
|
+
*
|
|
287
|
+
* @param {string} pExtension - e.g. '.png' or 'png'
|
|
288
|
+
* @returns {string} 'image', 'video', 'audio', 'document', or 'other'
|
|
289
|
+
*/
|
|
290
|
+
getCategory(pExtension)
|
|
291
|
+
{
|
|
292
|
+
let tmpExt = (pExtension || '').replace(/^\./, '').toLowerCase();
|
|
293
|
+
if (_ImageExtensions[tmpExt]) return 'image';
|
|
294
|
+
if (_VideoExtensions[tmpExt]) return 'video';
|
|
295
|
+
if (_AudioExtensions[tmpExt]) return 'audio';
|
|
296
|
+
if (_DocumentExtensions[tmpExt]) return 'document';
|
|
297
|
+
return 'other';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ──────────────────────────────────────────────
|
|
301
|
+
// Extension helpers for the filter panel
|
|
302
|
+
// ──────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Get the unique set of file extensions present in RawFileList.
|
|
306
|
+
* Returns an array of { ext, category, count } grouped by category.
|
|
307
|
+
*
|
|
308
|
+
* @returns {Array}
|
|
309
|
+
*/
|
|
310
|
+
getAvailableExtensions()
|
|
311
|
+
{
|
|
312
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
313
|
+
let tmpRaw = tmpRemote ? tmpRemote.RawFileList : [];
|
|
314
|
+
let tmpExtMap = {};
|
|
315
|
+
|
|
316
|
+
for (let i = 0; i < tmpRaw.length; i++)
|
|
317
|
+
{
|
|
318
|
+
let tmpItem = tmpRaw[i];
|
|
319
|
+
if (tmpItem.Type === 'folder')
|
|
320
|
+
{
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
let tmpExt = (tmpItem.Extension || '').replace(/^\./, '').toLowerCase();
|
|
324
|
+
if (!tmpExt)
|
|
325
|
+
{
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
if (!tmpExtMap[tmpExt])
|
|
329
|
+
{
|
|
330
|
+
tmpExtMap[tmpExt] =
|
|
331
|
+
{
|
|
332
|
+
ext: tmpExt,
|
|
333
|
+
category: this.getCategory(tmpExt),
|
|
334
|
+
count: 0
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
tmpExtMap[tmpExt].count++;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Convert to array and sort by category then extension
|
|
341
|
+
let tmpCategoryOrder = { 'image': 0, 'video': 1, 'audio': 2, 'document': 3, 'other': 4 };
|
|
342
|
+
let tmpResult = Object.values(tmpExtMap);
|
|
343
|
+
tmpResult.sort((pA, pB) =>
|
|
344
|
+
{
|
|
345
|
+
let tmpCatA = tmpCategoryOrder[pA.category] || 99;
|
|
346
|
+
let tmpCatB = tmpCategoryOrder[pB.category] || 99;
|
|
347
|
+
if (tmpCatA !== tmpCatB) return tmpCatA - tmpCatB;
|
|
348
|
+
return pA.ext < pB.ext ? -1 : (pA.ext > pB.ext ? 1 : 0);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return tmpResult;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ──────────────────────────────────────────────
|
|
355
|
+
// Filter chips
|
|
356
|
+
// ──────────────────────────────────────────────
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Return an array of { key, label } for each non-default filter currently active.
|
|
360
|
+
*/
|
|
361
|
+
getActiveFilterChips()
|
|
362
|
+
{
|
|
363
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
364
|
+
if (!tmpRemote)
|
|
365
|
+
{
|
|
366
|
+
return [];
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
let tmpChips = [];
|
|
370
|
+
let tmpFilterState = tmpRemote.FilterState || {};
|
|
371
|
+
|
|
372
|
+
// Media type
|
|
373
|
+
if (tmpFilterState.MediaType && tmpFilterState.MediaType !== 'all')
|
|
374
|
+
{
|
|
375
|
+
let tmpLabels = { 'images': 'Images', 'video': 'Video', 'audio': 'Audio', 'documents': 'Docs' };
|
|
376
|
+
tmpChips.push({ key: 'mediaType', label: tmpLabels[tmpFilterState.MediaType] || tmpFilterState.MediaType });
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Extensions
|
|
380
|
+
let tmpExtensions = tmpFilterState.Extensions || [];
|
|
381
|
+
if (tmpExtensions.length > 0)
|
|
382
|
+
{
|
|
383
|
+
for (let i = 0; i < tmpExtensions.length; i++)
|
|
384
|
+
{
|
|
385
|
+
tmpChips.push({ key: 'ext:' + tmpExtensions[i], label: '.' + tmpExtensions[i] });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Size
|
|
390
|
+
if (tmpFilterState.SizeMin !== null && tmpFilterState.SizeMin !== undefined)
|
|
391
|
+
{
|
|
392
|
+
tmpChips.push({ key: 'sizeMin', label: '\u2265 ' + this._formatSizeKB(tmpFilterState.SizeMin) });
|
|
393
|
+
}
|
|
394
|
+
if (tmpFilterState.SizeMax !== null && tmpFilterState.SizeMax !== undefined)
|
|
395
|
+
{
|
|
396
|
+
tmpChips.push({ key: 'sizeMax', label: '\u2264 ' + this._formatSizeKB(tmpFilterState.SizeMax) });
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Dates
|
|
400
|
+
if (tmpFilterState.DateModifiedAfter)
|
|
401
|
+
{
|
|
402
|
+
tmpChips.push({ key: 'dateModifiedAfter', label: 'Modified after ' + tmpFilterState.DateModifiedAfter });
|
|
403
|
+
}
|
|
404
|
+
if (tmpFilterState.DateModifiedBefore)
|
|
405
|
+
{
|
|
406
|
+
tmpChips.push({ key: 'dateModifiedBefore', label: 'Modified before ' + tmpFilterState.DateModifiedBefore });
|
|
407
|
+
}
|
|
408
|
+
if (tmpFilterState.DateCreatedAfter)
|
|
409
|
+
{
|
|
410
|
+
tmpChips.push({ key: 'dateCreatedAfter', label: 'Created after ' + tmpFilterState.DateCreatedAfter });
|
|
411
|
+
}
|
|
412
|
+
if (tmpFilterState.DateCreatedBefore)
|
|
413
|
+
{
|
|
414
|
+
tmpChips.push({ key: 'dateCreatedBefore', label: 'Created before ' + tmpFilterState.DateCreatedBefore });
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Search query
|
|
418
|
+
if (tmpRemote.SearchQuery)
|
|
419
|
+
{
|
|
420
|
+
tmpChips.push({ key: 'search', label: 'Search: "' + tmpRemote.SearchQuery + '"' });
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return tmpChips;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Format bytes into a human-readable KB/MB string for chip labels.
|
|
428
|
+
*/
|
|
429
|
+
_formatSizeKB(pBytes)
|
|
430
|
+
{
|
|
431
|
+
if (pBytes >= 1048576)
|
|
432
|
+
{
|
|
433
|
+
return (pBytes / 1048576).toFixed(1) + ' MB';
|
|
434
|
+
}
|
|
435
|
+
return Math.round(pBytes / 1024) + ' KB';
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Remove a specific filter by key.
|
|
440
|
+
*
|
|
441
|
+
* @param {string} pKey - e.g. 'mediaType', 'ext:png', 'sizeMin', 'dateModifiedAfter', 'search'
|
|
442
|
+
*/
|
|
443
|
+
removeFilter(pKey)
|
|
444
|
+
{
|
|
445
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
446
|
+
if (!tmpRemote)
|
|
447
|
+
{
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
let tmpFilterState = tmpRemote.FilterState;
|
|
452
|
+
|
|
453
|
+
if (pKey === 'mediaType')
|
|
454
|
+
{
|
|
455
|
+
tmpFilterState.MediaType = 'all';
|
|
456
|
+
tmpRemote.GalleryFilter = 'all';
|
|
457
|
+
}
|
|
458
|
+
else if (pKey.startsWith('ext:'))
|
|
459
|
+
{
|
|
460
|
+
let tmpExt = pKey.substring(4);
|
|
461
|
+
tmpFilterState.Extensions = (tmpFilterState.Extensions || []).filter((e) => e !== tmpExt);
|
|
462
|
+
}
|
|
463
|
+
else if (pKey === 'sizeMin')
|
|
464
|
+
{
|
|
465
|
+
tmpFilterState.SizeMin = null;
|
|
466
|
+
}
|
|
467
|
+
else if (pKey === 'sizeMax')
|
|
468
|
+
{
|
|
469
|
+
tmpFilterState.SizeMax = null;
|
|
470
|
+
}
|
|
471
|
+
else if (pKey === 'dateModifiedAfter')
|
|
472
|
+
{
|
|
473
|
+
tmpFilterState.DateModifiedAfter = null;
|
|
474
|
+
}
|
|
475
|
+
else if (pKey === 'dateModifiedBefore')
|
|
476
|
+
{
|
|
477
|
+
tmpFilterState.DateModifiedBefore = null;
|
|
478
|
+
}
|
|
479
|
+
else if (pKey === 'dateCreatedAfter')
|
|
480
|
+
{
|
|
481
|
+
tmpFilterState.DateCreatedAfter = null;
|
|
482
|
+
}
|
|
483
|
+
else if (pKey === 'dateCreatedBefore')
|
|
484
|
+
{
|
|
485
|
+
tmpFilterState.DateCreatedBefore = null;
|
|
486
|
+
}
|
|
487
|
+
else if (pKey === 'search')
|
|
488
|
+
{
|
|
489
|
+
tmpRemote.SearchQuery = '';
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Reset all filters to defaults.
|
|
495
|
+
*/
|
|
496
|
+
clearAllFilters()
|
|
497
|
+
{
|
|
498
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
499
|
+
if (!tmpRemote)
|
|
500
|
+
{
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
tmpRemote.SearchQuery = '';
|
|
505
|
+
tmpRemote.GalleryFilter = 'all';
|
|
506
|
+
tmpRemote.FilterState =
|
|
507
|
+
{
|
|
508
|
+
MediaType: 'all',
|
|
509
|
+
Extensions: [],
|
|
510
|
+
SizeMin: null,
|
|
511
|
+
SizeMax: null,
|
|
512
|
+
DateModifiedAfter: null,
|
|
513
|
+
DateModifiedBefore: null,
|
|
514
|
+
DateCreatedAfter: null,
|
|
515
|
+
DateCreatedBefore: null
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// ──────────────────────────────────────────────
|
|
520
|
+
// Presets
|
|
521
|
+
// ──────────────────────────────────────────────
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Save current filter+sort config as a named preset.
|
|
525
|
+
*
|
|
526
|
+
* @param {string} pName
|
|
527
|
+
*/
|
|
528
|
+
savePreset(pName)
|
|
529
|
+
{
|
|
530
|
+
if (!pName)
|
|
531
|
+
{
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
536
|
+
if (!tmpRemote)
|
|
537
|
+
{
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
let tmpPreset =
|
|
542
|
+
{
|
|
543
|
+
Name: pName,
|
|
544
|
+
FilterState: JSON.parse(JSON.stringify(tmpRemote.FilterState)),
|
|
545
|
+
SortField: tmpRemote.SortField,
|
|
546
|
+
SortDirection: tmpRemote.SortDirection
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
tmpRemote.FilterPresets = tmpRemote.FilterPresets || [];
|
|
550
|
+
tmpRemote.FilterPresets.push(tmpPreset);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Load a saved preset and apply its filter+sort config.
|
|
555
|
+
*
|
|
556
|
+
* @param {number} pIndex - index into FilterPresets array
|
|
557
|
+
*/
|
|
558
|
+
loadPreset(pIndex)
|
|
559
|
+
{
|
|
560
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
561
|
+
if (!tmpRemote || !tmpRemote.FilterPresets)
|
|
562
|
+
{
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
let tmpPreset = tmpRemote.FilterPresets[pIndex];
|
|
567
|
+
if (!tmpPreset)
|
|
568
|
+
{
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
tmpRemote.FilterState = JSON.parse(JSON.stringify(tmpPreset.FilterState));
|
|
573
|
+
tmpRemote.GalleryFilter = tmpRemote.FilterState.MediaType || 'all';
|
|
574
|
+
tmpRemote.SortField = tmpPreset.SortField || 'folder-first';
|
|
575
|
+
tmpRemote.SortDirection = tmpPreset.SortDirection || 'asc';
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Delete a saved preset.
|
|
580
|
+
*
|
|
581
|
+
* @param {number} pIndex - index into FilterPresets array
|
|
582
|
+
*/
|
|
583
|
+
deletePreset(pIndex)
|
|
584
|
+
{
|
|
585
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
586
|
+
if (!tmpRemote || !tmpRemote.FilterPresets)
|
|
587
|
+
{
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
tmpRemote.FilterPresets.splice(pIndex, 1);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
GalleryFilterSortProvider.default_configuration = _DefaultProviderConfiguration;
|
|
596
|
+
|
|
597
|
+
module.exports = GalleryFilterSortProvider;
|