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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/css/retold-remote.css +83 -0
  3. package/html/codejar.js +511 -0
  4. package/html/index.html +23 -0
  5. package/package.json +68 -0
  6. package/server.js +43 -0
  7. package/source/Pict-Application-RetoldRemote-Configuration.json +7 -0
  8. package/source/Pict-Application-RetoldRemote.js +622 -0
  9. package/source/Pict-RetoldRemote-Bundle.js +14 -0
  10. package/source/cli/RetoldRemote-CLI-Program.js +15 -0
  11. package/source/cli/RetoldRemote-CLI-Run.js +3 -0
  12. package/source/cli/RetoldRemote-Server-Setup.js +257 -0
  13. package/source/cli/commands/RetoldRemote-Command-Serve.js +87 -0
  14. package/source/providers/Pict-Provider-GalleryFilterSort.js +597 -0
  15. package/source/providers/Pict-Provider-GalleryNavigation.js +819 -0
  16. package/source/providers/Pict-Provider-RetoldRemote.js +273 -0
  17. package/source/providers/Pict-Provider-RetoldRemoteIcons.js +640 -0
  18. package/source/providers/Pict-Provider-RetoldRemoteTheme.js +879 -0
  19. package/source/server/RetoldRemote-MediaService.js +536 -0
  20. package/source/server/RetoldRemote-PathRegistry.js +121 -0
  21. package/source/server/RetoldRemote-ThumbnailCache.js +89 -0
  22. package/source/server/RetoldRemote-ToolDetector.js +78 -0
  23. package/source/views/PictView-Remote-Gallery.js +1437 -0
  24. package/source/views/PictView-Remote-ImageViewer.js +363 -0
  25. package/source/views/PictView-Remote-Layout.js +420 -0
  26. package/source/views/PictView-Remote-MediaViewer.js +530 -0
  27. package/source/views/PictView-Remote-SettingsPanel.js +318 -0
  28. package/source/views/PictView-Remote-TopBar.js +206 -0
  29. package/web-application/codejar.js +511 -0
  30. package/web-application/css/retold-remote.css +83 -0
  31. package/web-application/index.html +23 -0
  32. package/web-application/js/pict.min.js +12 -0
  33. package/web-application/js/pict.min.js.map +1 -0
  34. package/web-application/retold-remote.compatible.js +5764 -0
  35. package/web-application/retold-remote.compatible.js.map +1 -0
  36. package/web-application/retold-remote.compatible.min.js +120 -0
  37. package/web-application/retold-remote.compatible.min.js.map +1 -0
  38. package/web-application/retold-remote.js +5763 -0
  39. package/web-application/retold-remote.js.map +1 -0
  40. package/web-application/retold-remote.min.js +120 -0
  41. package/web-application/retold-remote.min.js.map +1 -0
package/server.js ADDED
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Retold Remote -- Standalone Server Entry Point
3
+ *
4
+ * Usage:
5
+ * npm run build (build client bundles first)
6
+ * npm start (start this server)
7
+ * Open http://localhost:8086
8
+ *
9
+ * Or point to a specific media folder:
10
+ * node server.js [path-to-media-folder]
11
+ */
12
+
13
+ const libPath = require('path');
14
+ const libSetupServer = require('./source/cli/RetoldRemote-Server-Setup.js');
15
+
16
+ let tmpContentPath = process.argv[2]
17
+ ? libPath.resolve(process.argv[2])
18
+ : libPath.join(__dirname, 'content');
19
+
20
+ let tmpPort = parseInt(process.env.PORT, 10) || 8086;
21
+ let tmpHashedFilenames = (process.env.RETOLD_HASHED_FILENAMES === 'true');
22
+
23
+ libSetupServer(
24
+ {
25
+ ContentPath: tmpContentPath,
26
+ DistPath: libPath.join(__dirname, 'web-application'),
27
+ Port: tmpPort,
28
+ HashedFilenames: tmpHashedFilenames
29
+ },
30
+ function (pError, pServerInfo)
31
+ {
32
+ if (pError)
33
+ {
34
+ console.error('Failed to start server:', pError.message);
35
+ process.exit(1);
36
+ }
37
+ pServerInfo.Fable.log.info('==========================================================');
38
+ pServerInfo.Fable.log.info(` Retold Remote running on http://localhost:${pServerInfo.Port}`);
39
+ pServerInfo.Fable.log.info('==========================================================');
40
+ pServerInfo.Fable.log.info(` Content path: ${tmpContentPath}`);
41
+ pServerInfo.Fable.log.info(` Browse: http://localhost:${pServerInfo.Port}/`);
42
+ pServerInfo.Fable.log.info('==========================================================');
43
+ });
@@ -0,0 +1,7 @@
1
+ {
2
+ "Name": "Retold Remote",
3
+ "MainViewportViewIdentifier": "ContentEditor-Layout",
4
+ "AutoSolveAfterInitialize": true,
5
+ "AutoRenderMainViewportViewAfterInitialize": false,
6
+ "AutoRenderViewsAfterInitialize": false
7
+ }
@@ -0,0 +1,622 @@
1
+ const libContentEditorApplication = require('retold-content-system').PictContentEditor;
2
+
3
+ const libPictSectionFileBrowser = require('pict-section-filebrowser');
4
+
5
+ // Providers
6
+ const libProviderRetoldRemote = require('./providers/Pict-Provider-RetoldRemote.js');
7
+ const libProviderGalleryNavigation = require('./providers/Pict-Provider-GalleryNavigation.js');
8
+ const libProviderGalleryFilterSort = require('./providers/Pict-Provider-GalleryFilterSort.js');
9
+ const libProviderRetoldRemoteIcons = require('./providers/Pict-Provider-RetoldRemoteIcons.js');
10
+ const libProviderRetoldRemoteTheme = require('./providers/Pict-Provider-RetoldRemoteTheme.js');
11
+
12
+ // Views (replace parent views)
13
+ const libViewLayout = require('./views/PictView-Remote-Layout.js');
14
+ const libViewTopBar = require('./views/PictView-Remote-TopBar.js');
15
+ const libViewSettingsPanel = require('./views/PictView-Remote-SettingsPanel.js');
16
+
17
+ // Views (new)
18
+ const libViewGallery = require('./views/PictView-Remote-Gallery.js');
19
+ const libViewMediaViewer = require('./views/PictView-Remote-MediaViewer.js');
20
+ const libViewImageViewer = require('./views/PictView-Remote-ImageViewer.js');
21
+
22
+ // Application configuration
23
+ const _DefaultConfiguration = require('./Pict-Application-RetoldRemote-Configuration.json');
24
+
25
+ /**
26
+ * Retold Remote Application
27
+ *
28
+ * A NAS media browser that extends ContentEditorApplication from
29
+ * retold-content-system. Replaces the text editor views with gallery
30
+ * and media viewer views while keeping the sidebar file browser and
31
+ * file navigation infrastructure intact.
32
+ */
33
+ class RetoldRemoteApplication extends libContentEditorApplication
34
+ {
35
+ constructor(pFable, pOptions, pServiceHash)
36
+ {
37
+ let tmpOptions = Object.assign({}, _DefaultConfiguration, pOptions);
38
+ super(pFable, tmpOptions, pServiceHash);
39
+
40
+ // Replace parent views with media-focused versions.
41
+ // Re-registering with the same ViewIdentifier replaces the parent's view.
42
+ this.pict.addView('ContentEditor-Layout', libViewLayout.default_configuration, libViewLayout);
43
+ this.pict.addView('ContentEditor-TopBar', libViewTopBar.default_configuration, libViewTopBar);
44
+
45
+ // Add new views
46
+ this.pict.addView('RetoldRemote-Gallery', libViewGallery.default_configuration, libViewGallery);
47
+ this.pict.addView('RetoldRemote-MediaViewer', libViewMediaViewer.default_configuration, libViewMediaViewer);
48
+ this.pict.addView('RetoldRemote-ImageViewer', libViewImageViewer.default_configuration, libViewImageViewer);
49
+ this.pict.addView('RetoldRemote-SettingsPanel', libViewSettingsPanel.default_configuration, libViewSettingsPanel);
50
+
51
+ // Add new providers
52
+ this.pict.addProvider('RetoldRemote-Provider', libProviderRetoldRemote.default_configuration, libProviderRetoldRemote);
53
+ this.pict.addProvider('RetoldRemote-GalleryNavigation', libProviderGalleryNavigation.default_configuration, libProviderGalleryNavigation);
54
+ this.pict.addProvider('RetoldRemote-GalleryFilterSort', libProviderGalleryFilterSort.default_configuration, libProviderGalleryFilterSort);
55
+ this.pict.addProvider('RetoldRemote-Icons', libProviderRetoldRemoteIcons.default_configuration, libProviderRetoldRemoteIcons);
56
+ this.pict.addProvider('RetoldRemote-Theme', libProviderRetoldRemoteTheme.default_configuration, libProviderRetoldRemoteTheme);
57
+ }
58
+
59
+ onAfterInitializeAsync(fCallback)
60
+ {
61
+ // Expose pict on window for inline onclick handlers
62
+ if (typeof (window) !== 'undefined')
63
+ {
64
+ window.pict = this.pict;
65
+ }
66
+
67
+ // Initialize RetoldRemote-specific state
68
+ this.pict.AppData.RetoldRemote =
69
+ {
70
+ ActiveMode: 'gallery', // 'gallery' or 'viewer'
71
+ Theme: 'twilight', // Theme key (e.g. 'twilight', 'neo-tokyo')
72
+ ViewMode: 'list', // 'gallery' or 'list'
73
+ ThumbnailSize: 'medium', // 'small', 'medium', 'large'
74
+ RawFileList: [], // Unfiltered server response
75
+ GalleryItems: [], // Filtered+sorted file list (single source of truth)
76
+ GalleryCursorIndex: 0, // Currently highlighted item
77
+ GalleryFilter: 'all', // 'all', 'images', 'video', 'audio', 'documents'
78
+ SearchQuery: '',
79
+ ServerCapabilities: {}, // From /api/media/capabilities
80
+ FolderSummary: null, // From /api/media/folder-summary
81
+ CurrentViewerFile: '', // File being viewed
82
+ CurrentViewerMediaType: '', // Media type of viewed file
83
+ HashedFilenames: false, // From /api/remote/settings
84
+ ShowHiddenFiles: false,
85
+ DistractionFreeShowNav: true,
86
+ ImageFitMode: 'auto',
87
+ SidebarCollapsed: false,
88
+ SidebarWidth: 250,
89
+
90
+ // Filter state
91
+ FilterState:
92
+ {
93
+ MediaType: 'all',
94
+ Extensions: [], // e.g. ['png', 'jpg'] -- empty = all
95
+ SizeMin: null, // bytes or null
96
+ SizeMax: null,
97
+ DateModifiedAfter: null, // ISO date string or null
98
+ DateModifiedBefore: null,
99
+ DateCreatedAfter: null,
100
+ DateCreatedBefore: null
101
+ },
102
+
103
+ // Sort state
104
+ SortField: 'folder-first', // 'name', 'folder-first', 'created', 'modified'
105
+ SortDirection: 'asc', // 'asc', 'desc'
106
+
107
+ // Filter panel UI
108
+ FilterPanelOpen: false,
109
+
110
+ // Saved filter presets
111
+ FilterPresets: [] // [{ Name, FilterState, SortField, SortDirection }]
112
+ };
113
+
114
+ // Load persisted settings
115
+ this._loadRemoteSettings();
116
+
117
+ // Apply the loaded theme (must happen after _loadRemoteSettings sets Theme)
118
+ let tmpThemeProvider = this.pict.providers['RetoldRemote-Theme'];
119
+ if (tmpThemeProvider)
120
+ {
121
+ tmpThemeProvider.applyTheme(this.pict.AppData.RetoldRemote.Theme);
122
+ }
123
+
124
+ // Initialize parent state (ContentEditor AppData)
125
+ this.pict.AppData.ContentEditor =
126
+ {
127
+ CurrentFile: '',
128
+ ActiveEditor: 'markdown',
129
+ IsDirty: false,
130
+ IsSaving: false,
131
+ IsLoading: false,
132
+ Files: [],
133
+ Document: { Segments: [{ Content: '' }] },
134
+ CodeContent: '',
135
+ SaveStatus: '',
136
+ SaveStatusClass: '',
137
+ AutoSegmentMarkdown: false,
138
+ AutoSegmentDepth: 1,
139
+ AutoContentPreview: false,
140
+ MarkdownEditingControls: true,
141
+ MarkdownWordWrap: true,
142
+ CodeWordWrap: false,
143
+ SidebarCollapsed: this.pict.AppData.RetoldRemote.SidebarCollapsed,
144
+ SidebarWidth: this.pict.AppData.RetoldRemote.SidebarWidth,
145
+ AutoPreviewImages: true,
146
+ AutoPreviewVideo: false,
147
+ AutoPreviewAudio: false,
148
+ ShowHiddenFiles: this.pict.AppData.RetoldRemote.ShowHiddenFiles,
149
+ TopicsFilePath: ''
150
+ };
151
+
152
+ // Render the layout shell
153
+ this.pict.views['ContentEditor-Layout'].render();
154
+
155
+ // Render the topbar
156
+ this.pict.views['ContentEditor-TopBar'].render();
157
+
158
+ let tmpSelf = this;
159
+
160
+ // Wire up file selection from the file browser sidebar
161
+ let tmpListProvider = this.pict.providers['Pict-FileBrowser-List'];
162
+ if (tmpListProvider)
163
+ {
164
+ let tmpOriginalSelectFile = tmpListProvider.selectFile.bind(tmpListProvider);
165
+ tmpListProvider.selectFile = function (pFileEntry)
166
+ {
167
+ tmpOriginalSelectFile(pFileEntry);
168
+ if (pFileEntry && pFileEntry.Type === 'file')
169
+ {
170
+ tmpSelf.navigateToFile(pFileEntry.Path);
171
+ }
172
+ };
173
+ }
174
+
175
+ // Wire up folder navigation
176
+ let tmpBrowseProvider = this.pict.providers['Pict-FileBrowser-Browse'];
177
+ if (tmpBrowseProvider)
178
+ {
179
+ let tmpOriginalNavigate = tmpBrowseProvider.navigateToFolder.bind(tmpBrowseProvider);
180
+ tmpBrowseProvider.navigateToFolder = function (pPath)
181
+ {
182
+ tmpOriginalNavigate(pPath);
183
+ tmpSelf.loadFileList(pPath);
184
+ };
185
+ }
186
+
187
+ // Fetch server capabilities and remote settings
188
+ let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
189
+ if (tmpProvider)
190
+ {
191
+ tmpProvider.fetchCapabilities(
192
+ (pError, pCapabilities) =>
193
+ {
194
+ if (!pError && pCapabilities)
195
+ {
196
+ tmpSelf.pict.AppData.RetoldRemote.ServerCapabilities = pCapabilities;
197
+ }
198
+ });
199
+
200
+ tmpProvider.fetchRemoteSettings(
201
+ (pError, pSettings) =>
202
+ {
203
+ if (!pError && pSettings)
204
+ {
205
+ tmpSelf.pict.AppData.RetoldRemote.HashedFilenames = !!(pSettings.HashedFilenames);
206
+ }
207
+ });
208
+ }
209
+
210
+ // Bind keyboard navigation
211
+ let tmpNavProvider = this.pict.providers['RetoldRemote-GalleryNavigation'];
212
+ if (tmpNavProvider)
213
+ {
214
+ tmpNavProvider.bindKeyboardNavigation();
215
+ }
216
+
217
+ // Sync hidden files setting and load initial file list
218
+ this.syncHiddenFilesSetting(() =>
219
+ {
220
+ tmpSelf.loadFileList(null, () =>
221
+ {
222
+ tmpSelf.resolveHash();
223
+ });
224
+ });
225
+
226
+ // Do NOT call super.onAfterInitializeAsync because we have replaced
227
+ // the full initialization flow above. Instead call the grandparent's callback.
228
+ // The parent's onAfterInitializeAsync tries to render editors and load topics
229
+ // which we don't need.
230
+ if (typeof (fCallback) === 'function')
231
+ {
232
+ return fCallback();
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Override _getMediaType to add document category.
238
+ *
239
+ * @param {string} pExtension - Lowercase extension without dot
240
+ * @returns {string} 'image', 'video', 'audio', 'document', or 'other'
241
+ */
242
+ _getMediaType(pExtension)
243
+ {
244
+ let tmpDocumentExtensions = { 'pdf': true, 'epub': true, 'mobi': true };
245
+ if (tmpDocumentExtensions[pExtension])
246
+ {
247
+ return 'document';
248
+ }
249
+
250
+ let tmpTextExtensions =
251
+ {
252
+ 'js': true, 'mjs': true, 'cjs': true, 'ts': true, 'tsx': true, 'jsx': true,
253
+ 'py': true, 'rb': true, 'java': true, 'c': true, 'cpp': true, 'h': true, 'hpp': true,
254
+ 'cs': true, 'go': true, 'rs': true, 'php': true, 'sh': true, 'bash': true, 'zsh': true,
255
+ 'pl': true, 'r': true, 'swift': true, 'kt': true, 'scala': true, 'lua': true,
256
+ 'json': true, 'xml': true, 'yaml': true, 'yml': true, 'toml': true,
257
+ 'ini': true, 'cfg': true, 'conf': true, 'env': true, 'properties': true,
258
+ 'md': true, 'markdown': true, 'txt': true, 'csv': true, 'tsv': true, 'log': true,
259
+ 'html': true, 'htm': true, 'css': true, 'scss': true, 'sass': true, 'less': true,
260
+ 'sql': true, 'graphql': true, 'gql': true,
261
+ 'makefile': true, 'dockerfile': true, 'gitignore': true, 'editorconfig': true,
262
+ 'htaccess': true, 'npmrc': true, 'eslintrc': true, 'prettierrc': true
263
+ };
264
+ if (tmpTextExtensions[pExtension])
265
+ {
266
+ return 'text';
267
+ }
268
+
269
+ return super._getMediaType(pExtension);
270
+ }
271
+
272
+ /**
273
+ * Override navigateToFile to route to media viewer instead of editor.
274
+ *
275
+ * @param {string} pFilePath - Relative file path
276
+ */
277
+ navigateToFile(pFilePath)
278
+ {
279
+ if (!pFilePath)
280
+ {
281
+ return;
282
+ }
283
+
284
+ let tmpEditorType = this.getEditorTypeForFile(pFilePath);
285
+ let tmpExtension = pFilePath.replace(/^.*\./, '').toLowerCase();
286
+ let tmpMediaType = this._getMediaType(tmpExtension);
287
+ let tmpRemote = this.pict.AppData.RetoldRemote;
288
+
289
+ // Update the hash (use hashed identifier when available)
290
+ let tmpFragProvider = this.pict.providers['RetoldRemote-Provider'];
291
+ let tmpFragId = tmpFragProvider ? tmpFragProvider.getFragmentIdentifier(pFilePath) : pFilePath;
292
+ window.location.hash = '#/view/' + tmpFragId;
293
+
294
+ // Update parent state for compatibility
295
+ this.pict.AppData.ContentEditor.CurrentFile = pFilePath;
296
+ this.pict.AppData.ContentEditor.ActiveEditor = tmpEditorType;
297
+
298
+ if (tmpEditorType === 'binary')
299
+ {
300
+ // Route binary files to the media viewer
301
+ let tmpViewer = this.pict.views['RetoldRemote-MediaViewer'];
302
+ if (tmpViewer)
303
+ {
304
+ tmpViewer.showMedia(pFilePath, tmpMediaType);
305
+ }
306
+ }
307
+ else if (tmpMediaType === 'text')
308
+ {
309
+ // Text/code files: show inline in media viewer
310
+ let tmpViewer = this.pict.views['RetoldRemote-MediaViewer'];
311
+ if (tmpViewer)
312
+ {
313
+ tmpViewer.showMedia(pFilePath, 'text');
314
+ }
315
+ }
316
+ else if (tmpMediaType === 'other')
317
+ {
318
+ // Unknown files: show fallback view
319
+ let tmpViewer = this.pict.views['RetoldRemote-MediaViewer'];
320
+ if (tmpViewer)
321
+ {
322
+ tmpViewer.showMedia(pFilePath, 'document');
323
+ }
324
+ }
325
+ else
326
+ {
327
+ // Default: use media viewer
328
+ let tmpViewer = this.pict.views['RetoldRemote-MediaViewer'];
329
+ if (tmpViewer)
330
+ {
331
+ tmpViewer.showMedia(pFilePath, tmpMediaType);
332
+ }
333
+ }
334
+
335
+ // Update the topbar
336
+ let tmpTopBar = this.pict.views['ContentEditor-TopBar'];
337
+ if (tmpTopBar)
338
+ {
339
+ tmpTopBar.updateInfo();
340
+ }
341
+ }
342
+
343
+ /**
344
+ * Override loadFileList to also populate the gallery and fetch folder summary.
345
+ */
346
+ loadFileList(pPath, fCallback)
347
+ {
348
+ let tmpCallback = (typeof (fCallback) === 'function') ? fCallback :
349
+ (typeof (pPath) === 'function') ? pPath : () => {};
350
+ let tmpSelf = this;
351
+
352
+ let tmpPath = (typeof (pPath) === 'string') ? pPath : null;
353
+ if (tmpPath === null && this.pict.AppData.PictFileBrowser && this.pict.AppData.PictFileBrowser.CurrentLocation)
354
+ {
355
+ tmpPath = this.pict.AppData.PictFileBrowser.CurrentLocation;
356
+ }
357
+
358
+ let tmpURL = '/api/filebrowser/list';
359
+ if (tmpPath && tmpPath.length > 0)
360
+ {
361
+ let tmpMediaProvider = tmpSelf.pict.providers['RetoldRemote-Provider'];
362
+ if (tmpMediaProvider)
363
+ {
364
+ tmpURL += '?path=' + tmpMediaProvider._getPathParam(tmpPath);
365
+ }
366
+ else
367
+ {
368
+ tmpURL += '?path=' + encodeURIComponent(tmpPath);
369
+ }
370
+ }
371
+
372
+ fetch(tmpURL)
373
+ .then((pResponse) =>
374
+ {
375
+ // Capture the folder hash header before parsing JSON
376
+ let tmpFolderHash = pResponse.headers.get('X-Retold-Folder-Hash');
377
+ if (tmpFolderHash && tmpPath)
378
+ {
379
+ let tmpHashProvider = tmpSelf.pict.providers['RetoldRemote-Provider'];
380
+ if (tmpHashProvider)
381
+ {
382
+ tmpHashProvider.registerHash(tmpPath, tmpFolderHash);
383
+ }
384
+ }
385
+ return pResponse.json();
386
+ })
387
+ .then((pFileList) =>
388
+ {
389
+ tmpSelf.pict.AppData.PictFileBrowser = tmpSelf.pict.AppData.PictFileBrowser || {};
390
+ tmpSelf.pict.AppData.PictFileBrowser.FileList = pFileList || [];
391
+ tmpSelf.pict.AppData.PictFileBrowser.CurrentLocation = (typeof (pPath) === 'string') ? pPath : (tmpSelf.pict.AppData.PictFileBrowser.CurrentLocation || '');
392
+
393
+ // Register hashes from file list entries (when hashed filenames is on)
394
+ let tmpHashProvider = tmpSelf.pict.providers['RetoldRemote-Provider'];
395
+ if (tmpHashProvider && Array.isArray(pFileList))
396
+ {
397
+ for (let i = 0; i < pFileList.length; i++)
398
+ {
399
+ if (pFileList[i].Hash && pFileList[i].Path)
400
+ {
401
+ tmpHashProvider.registerHash(pFileList[i].Path, pFileList[i].Hash);
402
+ }
403
+ }
404
+ }
405
+
406
+ // Render the file browser sidebar
407
+ let tmpFileBrowserView = tmpSelf.pict.views['Pict-FileBrowser'];
408
+ if (tmpFileBrowserView)
409
+ {
410
+ tmpFileBrowserView.render();
411
+ }
412
+
413
+ let tmpListDetailView = tmpSelf.pict.views['Pict-FileBrowser-ListDetail'];
414
+ if (tmpListDetailView)
415
+ {
416
+ tmpListDetailView.render();
417
+ }
418
+
419
+ // Populate raw file list and run filter pipeline
420
+ let tmpRemote = tmpSelf.pict.AppData.RetoldRemote;
421
+ tmpRemote.RawFileList = pFileList || [];
422
+ tmpRemote.ActiveMode = 'gallery';
423
+ tmpRemote.SearchQuery = '';
424
+
425
+ // Reset advanced filters on folder change (preserve MediaType preference)
426
+ tmpRemote.FilterState =
427
+ {
428
+ MediaType: tmpRemote.FilterState.MediaType,
429
+ Extensions: [],
430
+ SizeMin: null,
431
+ SizeMax: null,
432
+ DateModifiedAfter: null,
433
+ DateModifiedBefore: null,
434
+ DateCreatedAfter: null,
435
+ DateCreatedBefore: null
436
+ };
437
+
438
+ // Show the gallery, hide the viewer
439
+ let tmpGalleryContainer = document.getElementById('RetoldRemote-Gallery-Container');
440
+ let tmpViewerContainer = document.getElementById('RetoldRemote-Viewer-Container');
441
+ if (tmpGalleryContainer) tmpGalleryContainer.style.display = '';
442
+ if (tmpViewerContainer) tmpViewerContainer.style.display = 'none';
443
+
444
+ // Run the filter+sort pipeline (sets GalleryItems, resets cursor, renders gallery)
445
+ let tmpFilterSort = tmpSelf.pict.providers['RetoldRemote-GalleryFilterSort'];
446
+ if (tmpFilterSort)
447
+ {
448
+ tmpFilterSort.applyFilterSort();
449
+ }
450
+ else
451
+ {
452
+ // Fallback if provider not ready
453
+ tmpRemote.GalleryItems = pFileList || [];
454
+ tmpRemote.GalleryCursorIndex = 0;
455
+ let tmpGalleryView = tmpSelf.pict.views['RetoldRemote-Gallery'];
456
+ if (tmpGalleryView)
457
+ {
458
+ tmpGalleryView.renderGallery();
459
+ }
460
+ }
461
+
462
+ // Update the hash (use hashed identifier when available)
463
+ let tmpCurrentPath = tmpSelf.pict.AppData.PictFileBrowser.CurrentLocation || '';
464
+ let tmpBrowseFragProvider = tmpSelf.pict.providers['RetoldRemote-Provider'];
465
+ let tmpBrowseFragId = (tmpBrowseFragProvider && tmpCurrentPath) ? tmpBrowseFragProvider.getFragmentIdentifier(tmpCurrentPath) : tmpCurrentPath;
466
+ window.location.hash = tmpBrowseFragId ? '#/browse/' + tmpBrowseFragId : '#/browse/';
467
+
468
+ // Fetch folder summary for topbar info
469
+ let tmpMediaProvider = tmpSelf.pict.providers['RetoldRemote-Provider'];
470
+ if (tmpMediaProvider)
471
+ {
472
+ tmpMediaProvider.fetchFolderSummary(tmpCurrentPath,
473
+ (pError, pSummary) =>
474
+ {
475
+ if (!pError && pSummary)
476
+ {
477
+ tmpRemote.FolderSummary = pSummary;
478
+ let tmpTopBar = tmpSelf.pict.views['ContentEditor-TopBar'];
479
+ if (tmpTopBar)
480
+ {
481
+ tmpTopBar.updateLocation();
482
+ tmpTopBar.updateInfo();
483
+ }
484
+ }
485
+ });
486
+ }
487
+ else
488
+ {
489
+ let tmpTopBar = tmpSelf.pict.views['ContentEditor-TopBar'];
490
+ if (tmpTopBar)
491
+ {
492
+ tmpTopBar.updateLocation();
493
+ tmpTopBar.updateInfo();
494
+ }
495
+ }
496
+
497
+ return tmpCallback();
498
+ })
499
+ .catch((pError) =>
500
+ {
501
+ tmpSelf.log.error(`Failed to load file list: ${pError.message}`);
502
+ return tmpCallback();
503
+ });
504
+ }
505
+
506
+ /**
507
+ * Override resolveHash to handle gallery and viewer routes.
508
+ */
509
+ resolveHash()
510
+ {
511
+ let tmpHash = (window.location.hash || '').replace(/^#\/?/, '');
512
+
513
+ if (!tmpHash)
514
+ {
515
+ return;
516
+ }
517
+
518
+ let tmpParts = tmpHash.split('/');
519
+ let tmpFragProvider = this.pict.providers['RetoldRemote-Provider'];
520
+
521
+ if (tmpParts[0] === 'browse')
522
+ {
523
+ let tmpRawPath = tmpParts.slice(1).join('/');
524
+ // Resolve hash token to actual path if needed
525
+ let tmpPath = tmpFragProvider ? tmpFragProvider.resolveFragmentIdentifier(tmpRawPath) : tmpRawPath;
526
+ let tmpCurrentPath = (this.pict.AppData.PictFileBrowser && this.pict.AppData.PictFileBrowser.CurrentLocation) || '';
527
+ if (tmpPath !== tmpCurrentPath)
528
+ {
529
+ this.loadFileList(tmpPath);
530
+ }
531
+ }
532
+ else if (tmpParts[0] === 'view' && tmpParts.length >= 2)
533
+ {
534
+ let tmpRawPath = tmpParts.slice(1).join('/');
535
+ // Resolve hash token to actual path if needed
536
+ let tmpFilePath = tmpFragProvider ? tmpFragProvider.resolveFragmentIdentifier(tmpRawPath) : tmpRawPath;
537
+ if (this.pict.AppData.ContentEditor.CurrentFile === tmpFilePath)
538
+ {
539
+ return;
540
+ }
541
+ this.navigateToFile(tmpFilePath);
542
+ }
543
+ else if (tmpParts[0] === 'edit' && tmpParts.length >= 2)
544
+ {
545
+ let tmpRawPath = tmpParts.slice(1).join('/');
546
+ let tmpFilePath = tmpFragProvider ? tmpFragProvider.resolveFragmentIdentifier(tmpRawPath) : tmpRawPath;
547
+ this.navigateToFile(tmpFilePath);
548
+ }
549
+ }
550
+
551
+ /**
552
+ * Save RetoldRemote settings to localStorage.
553
+ */
554
+ saveSettings()
555
+ {
556
+ try
557
+ {
558
+ let tmpRemote = this.pict.AppData.RetoldRemote;
559
+ let tmpSettings =
560
+ {
561
+ Theme: tmpRemote.Theme,
562
+ ViewMode: tmpRemote.ViewMode,
563
+ ThumbnailSize: tmpRemote.ThumbnailSize,
564
+ GalleryFilter: tmpRemote.GalleryFilter,
565
+ ShowHiddenFiles: tmpRemote.ShowHiddenFiles,
566
+ DistractionFreeShowNav: tmpRemote.DistractionFreeShowNav,
567
+ ImageFitMode: tmpRemote.ImageFitMode,
568
+ SidebarCollapsed: tmpRemote.SidebarCollapsed,
569
+ SidebarWidth: tmpRemote.SidebarWidth,
570
+ SortField: tmpRemote.SortField,
571
+ SortDirection: tmpRemote.SortDirection,
572
+ FilterPresets: tmpRemote.FilterPresets,
573
+ FilterPanelOpen: tmpRemote.FilterPanelOpen
574
+ };
575
+ localStorage.setItem('retold-remote-settings', JSON.stringify(tmpSettings));
576
+ }
577
+ catch (pError)
578
+ {
579
+ // localStorage may not be available
580
+ }
581
+ }
582
+
583
+ /**
584
+ * Load RetoldRemote settings from localStorage.
585
+ */
586
+ _loadRemoteSettings()
587
+ {
588
+ try
589
+ {
590
+ let tmpStored = localStorage.getItem('retold-remote-settings');
591
+ if (tmpStored)
592
+ {
593
+ let tmpSettings = JSON.parse(tmpStored);
594
+ let tmpRemote = this.pict.AppData.RetoldRemote;
595
+
596
+ if (tmpSettings.Theme) tmpRemote.Theme = tmpSettings.Theme;
597
+ if (tmpSettings.ViewMode) tmpRemote.ViewMode = tmpSettings.ViewMode;
598
+ if (tmpSettings.ThumbnailSize) tmpRemote.ThumbnailSize = tmpSettings.ThumbnailSize;
599
+ if (tmpSettings.GalleryFilter)
600
+ {
601
+ tmpRemote.GalleryFilter = tmpSettings.GalleryFilter;
602
+ tmpRemote.FilterState.MediaType = tmpSettings.GalleryFilter;
603
+ }
604
+ if (typeof (tmpSettings.ShowHiddenFiles) === 'boolean') tmpRemote.ShowHiddenFiles = tmpSettings.ShowHiddenFiles;
605
+ if (typeof (tmpSettings.DistractionFreeShowNav) === 'boolean') tmpRemote.DistractionFreeShowNav = tmpSettings.DistractionFreeShowNav;
606
+ if (tmpSettings.ImageFitMode) tmpRemote.ImageFitMode = tmpSettings.ImageFitMode;
607
+ if (typeof (tmpSettings.SidebarCollapsed) === 'boolean') tmpRemote.SidebarCollapsed = tmpSettings.SidebarCollapsed;
608
+ if (tmpSettings.SidebarWidth) tmpRemote.SidebarWidth = tmpSettings.SidebarWidth;
609
+ if (tmpSettings.SortField) tmpRemote.SortField = tmpSettings.SortField;
610
+ if (tmpSettings.SortDirection) tmpRemote.SortDirection = tmpSettings.SortDirection;
611
+ if (Array.isArray(tmpSettings.FilterPresets)) tmpRemote.FilterPresets = tmpSettings.FilterPresets;
612
+ if (typeof (tmpSettings.FilterPanelOpen) === 'boolean') tmpRemote.FilterPanelOpen = tmpSettings.FilterPanelOpen;
613
+ }
614
+ }
615
+ catch (pError)
616
+ {
617
+ // localStorage may not be available
618
+ }
619
+ }
620
+ }
621
+
622
+ module.exports = RetoldRemoteApplication;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Retold Remote -- Browser Bundle Entry
3
+ *
4
+ * Exports the RetoldRemote application class for browser consumption.
5
+ */
6
+ module.exports =
7
+ {
8
+ RetoldRemoteApplication: require('./Pict-Application-RetoldRemote.js')
9
+ };
10
+
11
+ if (typeof (window) !== 'undefined')
12
+ {
13
+ window.RetoldRemoteApplication = module.exports.RetoldRemoteApplication;
14
+ }