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
@@ -0,0 +1,15 @@
1
+ const libCLIProgram = require('pict-service-commandlineutility');
2
+
3
+ let _PictCLIProgram = new libCLIProgram(
4
+ {
5
+ Product: 'retold-remote',
6
+ Version: require('../../package.json').version,
7
+
8
+ Command: 'retold-remote',
9
+ Description: 'Browse and view media files on a NAS or local folder with gallery views and keyboard navigation.'
10
+ },
11
+ [
12
+ require('./commands/RetoldRemote-Command-Serve.js')
13
+ ]);
14
+
15
+ module.exports = _PictCLIProgram;
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ const libRetoldRemoteProgram = require('./RetoldRemote-CLI-Program.js');
3
+ libRetoldRemoteProgram.run();
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Retold Remote -- Orator Server Setup
3
+ *
4
+ * Composes an Orator server with:
5
+ * 1. pict-section-filebrowser service for directory browsing
6
+ * 2. Content static serving at /content/*
7
+ * 3. Media service (thumbnails, probing, folder summaries)
8
+ * 4. Static serving of the retold-remote web application
9
+ *
10
+ * This is intentionally a standalone server setup (not calling content-system's
11
+ * server setup) so that we control all routes and avoid conflicts with editor-
12
+ * specific endpoints (save, upload) that aren't needed here.
13
+ *
14
+ * @param {object} pOptions
15
+ * @param {string} pOptions.ContentPath - Absolute path to the media folder to browse
16
+ * @param {string} pOptions.DistPath - Absolute path to the built web-application folder
17
+ * @param {number} pOptions.Port - HTTP port
18
+ * @param {string} [pOptions.ThumbnailCachePath] - Override thumbnail cache location
19
+ * @param {boolean} [pOptions.HashedFilenames] - Enable hashed filenames mode
20
+ * @param {Function} fCallback - Callback(pError, { Fable, Orator, Port })
21
+ */
22
+
23
+ const libFs = require('fs');
24
+ const libPath = require('path');
25
+
26
+ const libFable = require('fable');
27
+ const libOrator = require('orator');
28
+ const libOratorServiceServerRestify = require('orator-serviceserver-restify');
29
+ const libFileBrowserService = require('pict-section-filebrowser').FileBrowserService;
30
+
31
+ const libRetoldRemoteMediaService = require('../server/RetoldRemote-MediaService.js');
32
+ const libRetoldRemotePathRegistry = require('../server/RetoldRemote-PathRegistry.js');
33
+
34
+ function setupRetoldRemoteServer(pOptions, fCallback)
35
+ {
36
+ let tmpContentPath = pOptions.ContentPath;
37
+ let tmpDistFolder = pOptions.DistPath;
38
+ let tmpPort = pOptions.Port;
39
+ let tmpHashedFilenames = !!(pOptions.HashedFilenames || process.env.RETOLD_HASHED_FILENAMES === 'true');
40
+
41
+ let tmpSettings =
42
+ {
43
+ Product: 'Retold-Remote',
44
+ ProductVersion: require('../../package.json').version,
45
+ APIServerPort: tmpPort,
46
+ ContentPath: tmpContentPath
47
+ };
48
+
49
+ let tmpFable = new libFable(tmpSettings);
50
+
51
+ // Ensure the content directory exists
52
+ if (!libFs.existsSync(tmpContentPath))
53
+ {
54
+ libFs.mkdirSync(tmpContentPath, { recursive: true });
55
+ }
56
+
57
+ tmpFable.serviceManager.addServiceType('OratorServiceServer', libOratorServiceServerRestify);
58
+ tmpFable.serviceManager.instantiateServiceProvider('OratorServiceServer');
59
+ tmpFable.serviceManager.addServiceType('Orator', libOrator);
60
+ let tmpOrator = tmpFable.serviceManager.instantiateServiceProvider('Orator');
61
+
62
+ // Set up the file browser service
63
+ let tmpFileBrowser = new libFileBrowserService(tmpFable,
64
+ {
65
+ BasePath: tmpContentPath,
66
+ APIRoutePrefix: '/api/filebrowser',
67
+ ServeWebApp: false,
68
+ IncludeHiddenFiles: false
69
+ });
70
+
71
+ // Set up the path registry (for hashed filenames)
72
+ let tmpPathRegistry = new libRetoldRemotePathRegistry(tmpFable,
73
+ {
74
+ Enabled: tmpHashedFilenames
75
+ });
76
+
77
+ if (tmpHashedFilenames)
78
+ {
79
+ tmpFable.log.info('Hashed filenames mode: ENABLED');
80
+ }
81
+
82
+ // Set up the media service
83
+ let tmpMediaService = new libRetoldRemoteMediaService(tmpFable,
84
+ {
85
+ ContentPath: tmpContentPath,
86
+ ThumbnailCachePath: pOptions.ThumbnailCachePath || null,
87
+ APIRoutePrefix: '/api/media',
88
+ PathRegistry: tmpPathRegistry
89
+ });
90
+
91
+ tmpOrator.initialize(
92
+ function ()
93
+ {
94
+ let tmpServiceServer = tmpOrator.serviceServer;
95
+
96
+ // Enable body parsing
97
+ tmpServiceServer.server.use(tmpServiceServer.bodyParser());
98
+
99
+ // Hash resolution middleware: if hashed filenames is enabled,
100
+ // intercept ?path= query params and resolve hashes to paths.
101
+ if (tmpHashedFilenames)
102
+ {
103
+ let libUrlMiddleware = require('url');
104
+ tmpServiceServer.server.use(
105
+ (pRequest, pResponse, fNext) =>
106
+ {
107
+ let tmpParsedUrl = libUrlMiddleware.parse(pRequest.url, true);
108
+ let tmpPathParam = tmpParsedUrl.query && tmpParsedUrl.query.path;
109
+ if (tmpPathParam && /^[a-f0-9]{10}$/.test(tmpPathParam))
110
+ {
111
+ let tmpResolved = tmpPathRegistry.resolve(tmpPathParam);
112
+ if (tmpResolved !== null)
113
+ {
114
+ // Replace the hash with the resolved path in the query
115
+ tmpParsedUrl.query.path = tmpResolved;
116
+ delete tmpParsedUrl.search;
117
+ pRequest.url = libUrlMiddleware.format(tmpParsedUrl);
118
+ if (pRequest.query)
119
+ {
120
+ pRequest.query.path = tmpResolved;
121
+ }
122
+ }
123
+ }
124
+ return fNext();
125
+ });
126
+ }
127
+
128
+ // --- GET /api/remote/settings ---
129
+ // Returns server settings so the client knows if hashed filenames is on.
130
+ tmpServiceServer.get('/api/remote/settings',
131
+ (pRequest, pResponse, fNext) =>
132
+ {
133
+ pResponse.send(
134
+ {
135
+ Success: true,
136
+ HashedFilenames: tmpHashedFilenames
137
+ });
138
+ return fNext();
139
+ });
140
+
141
+ // Response annotation middleware: when hashed filenames is enabled,
142
+ // wrap res.send for /api/filebrowser/list to annotate entries with hashes.
143
+ if (tmpHashedFilenames)
144
+ {
145
+ let libUrlListWrap = require('url');
146
+ tmpServiceServer.server.use(
147
+ (pRequest, pResponse, fNext) =>
148
+ {
149
+ if (!pRequest.url.startsWith('/api/filebrowser/list'))
150
+ {
151
+ return fNext();
152
+ }
153
+
154
+ let tmpOriginalSend = pResponse.send.bind(pResponse);
155
+ pResponse.send = function (pStatusOrData, pData)
156
+ {
157
+ let tmpFileList = (typeof (pStatusOrData) === 'number') ? pData : pStatusOrData;
158
+
159
+ if (Array.isArray(tmpFileList))
160
+ {
161
+ // Annotate entries with hashes
162
+ tmpPathRegistry.annotateFileList(tmpFileList);
163
+
164
+ // Register the current folder path and set header
165
+ let tmpParsedListUrl = libUrlListWrap.parse(pRequest.url, true);
166
+ let tmpFolderPath = (tmpParsedListUrl.query && tmpParsedListUrl.query.path) || '';
167
+ if (tmpFolderPath)
168
+ {
169
+ let tmpFolderHash = tmpPathRegistry.register(tmpFolderPath);
170
+ pResponse.header('X-Retold-Folder-Hash', tmpFolderHash);
171
+ }
172
+ }
173
+
174
+ return tmpOriginalSend(pStatusOrData, pData);
175
+ };
176
+
177
+ return fNext();
178
+ });
179
+ }
180
+
181
+ // Connect file browser API routes
182
+ tmpFileBrowser.connectRoutes();
183
+
184
+ // PUT /api/filebrowser/settings -- toggle hidden files at runtime
185
+ tmpServiceServer.put('/api/filebrowser/settings',
186
+ (pRequest, pResponse, fNext) =>
187
+ {
188
+ try
189
+ {
190
+ if (pRequest.body && typeof (pRequest.body.IncludeHiddenFiles) === 'boolean')
191
+ {
192
+ tmpFileBrowser.options.IncludeHiddenFiles = pRequest.body.IncludeHiddenFiles;
193
+ }
194
+ pResponse.send({ Success: true });
195
+ }
196
+ catch (pError)
197
+ {
198
+ pResponse.send(500, { Error: pError.message });
199
+ }
200
+ return fNext();
201
+ });
202
+
203
+ // Connect media service API routes
204
+ tmpMediaService.connectRoutes(tmpServiceServer);
205
+
206
+ // Content-hashed URL rewrite: resolve /content-hashed/<hash>
207
+ // to /content/<resolved-path> so the static route serves the file.
208
+ // Uses server.pre() to rewrite BEFORE route matching.
209
+ if (tmpHashedFilenames)
210
+ {
211
+ tmpServiceServer.server.pre(
212
+ (pRequest, pResponse, fNext) =>
213
+ {
214
+ let tmpMatch = /^\/content-hashed\/([a-f0-9]{10})$/.exec(pRequest.url);
215
+ if (!tmpMatch)
216
+ {
217
+ return fNext();
218
+ }
219
+
220
+ let tmpHash = tmpMatch[1];
221
+ let tmpResolvedPath = tmpPathRegistry.resolve(tmpHash);
222
+
223
+ if (!tmpResolvedPath)
224
+ {
225
+ pResponse.send(404, { Error: 'Unknown hash.' });
226
+ return fNext(false);
227
+ }
228
+
229
+ // Rewrite URL to /content/<path> before route matching
230
+ pRequest.url = '/content/' + tmpResolvedPath;
231
+ return fNext();
232
+ });
233
+ }
234
+
235
+ // Serve content files at /content/ (for direct media access)
236
+ tmpOrator.addStaticRoute(`${tmpContentPath}/`, 'index.html', '/content/*', '/content/');
237
+
238
+ // Serve the built web application (main static route)
239
+ tmpOrator.addStaticRoute(`${tmpDistFolder}/`, 'index.html');
240
+
241
+ // Start the server
242
+ tmpOrator.startService(
243
+ function ()
244
+ {
245
+ return fCallback(null,
246
+ {
247
+ Fable: tmpFable,
248
+ Orator: tmpOrator,
249
+ MediaService: tmpMediaService,
250
+ PathRegistry: tmpPathRegistry,
251
+ Port: tmpPort
252
+ });
253
+ });
254
+ });
255
+ }
256
+
257
+ module.exports = setupRetoldRemoteServer;
@@ -0,0 +1,87 @@
1
+ const libCommandLineCommand = require('pict-service-commandlineutility').ServiceCommandLineCommand;
2
+
3
+ const libFs = require('fs');
4
+ const libPath = require('path');
5
+
6
+ class RetoldRemoteCommandServe extends libCommandLineCommand
7
+ {
8
+ constructor(pFable, pManifest, pServiceHash)
9
+ {
10
+ super(pFable, pManifest, pServiceHash);
11
+
12
+ this.options.CommandKeyword = 'serve';
13
+ this.options.Description = 'Start the Retold Remote media browser for a folder.';
14
+
15
+ this.options.CommandArguments.push(
16
+ { Name: '[content-path]', Description: 'Path to the media folder to browse (defaults to current directory).' });
17
+
18
+ this.options.CommandOptions.push(
19
+ { Name: '-p, --port [port]', Description: 'Port to serve on (defaults to random 7000-7999).', Default: '0' });
20
+
21
+ this.options.CommandOptions.push(
22
+ { Name: '-H, --hashed-filenames', Description: 'Enable hashed filenames mode (short hashes instead of full paths in URLs).', Default: false });
23
+
24
+ this.addCommand();
25
+ }
26
+
27
+ onRunAsync(fCallback)
28
+ {
29
+ let tmpContentPath = libPath.resolve(this.ArgumentString || process.cwd());
30
+
31
+ let tmpDistPath = libPath.resolve(__dirname, '..', '..', '..', 'web-application');
32
+ let tmpPortOption = parseInt(this.CommandOptions.port, 10);
33
+ let tmpPort = (tmpPortOption > 0) ? tmpPortOption : (7000 + Math.floor(Math.random() * 1000));
34
+
35
+ // Validate web-application path
36
+ if (!libFs.existsSync(tmpDistPath))
37
+ {
38
+ this.log.error(`Built assets not found at ${tmpDistPath}. Run 'npm run build' in the retold-remote package first.`);
39
+ return fCallback(new Error('web-application folder not found'));
40
+ }
41
+
42
+ // Ensure content directory exists
43
+ if (!libFs.existsSync(tmpContentPath))
44
+ {
45
+ libFs.mkdirSync(tmpContentPath, { recursive: true });
46
+ this.log.info(`Created content directory: ${tmpContentPath}`);
47
+ }
48
+
49
+ let tmpSelf = this;
50
+ let tmpSetupServer = require('../RetoldRemote-Server-Setup.js');
51
+
52
+ let tmpHashedFilenames = !!(this.CommandOptions.hashedFilenames);
53
+
54
+ tmpSetupServer(
55
+ {
56
+ ContentPath: tmpContentPath,
57
+ DistPath: tmpDistPath,
58
+ Port: tmpPort,
59
+ HashedFilenames: tmpHashedFilenames
60
+ },
61
+ function (pError, pServerInfo)
62
+ {
63
+ if (pError)
64
+ {
65
+ tmpSelf.log.error(`Failed to start server: ${pError.message}`);
66
+ return fCallback(pError);
67
+ }
68
+
69
+ tmpSelf.log.info('');
70
+ tmpSelf.log.info('==========================================================');
71
+ tmpSelf.log.info(` Retold Remote running on http://localhost:${pServerInfo.Port}`);
72
+ tmpSelf.log.info('==========================================================');
73
+ tmpSelf.log.info(` Content: ${tmpContentPath}`);
74
+ tmpSelf.log.info(` Assets: ${tmpDistPath}`);
75
+ tmpSelf.log.info(` Browse: http://localhost:${pServerInfo.Port}/`);
76
+ tmpSelf.log.info('==========================================================');
77
+ tmpSelf.log.info('');
78
+ tmpSelf.log.info(' Press Ctrl+C to stop.');
79
+ tmpSelf.log.info('');
80
+
81
+ // Intentionally do NOT call fCallback() here.
82
+ // The server should keep running.
83
+ });
84
+ }
85
+ }
86
+
87
+ module.exports = RetoldRemoteCommandServe;