retold-remote 0.0.26 → 0.0.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retold-remote",
3
- "version": "0.0.26",
3
+ "version": "0.0.27",
4
4
  "description": "Retold Remote - NAS media browser with gallery views and keyboard navigation",
5
5
  "main": "source/Pict-RetoldRemote-Bundle.js",
6
6
  "bin": {
@@ -29,6 +29,58 @@
29
29
  "docs": "npx quack prepare-docs ./docs -d ./modules",
30
30
  "docs-serve": "npx quack docs-serve ./docs"
31
31
  },
32
+ "retoldBeacon": {
33
+ "displayName": "Retold Remote",
34
+ "description": "NAS media browser with gallery views and keyboard navigation; serves a content folder over HTTP and registers as a beacon.",
35
+ "category": "content",
36
+ "mode": "standalone-service",
37
+ "bin": "source/cli/RetoldRemote-CLI-Run.js",
38
+ "argTemplate": [
39
+ "serve",
40
+ {
41
+ "fromLabPath": "ContentPath"
42
+ },
43
+ "-p",
44
+ {
45
+ "fromLabPath": "Port"
46
+ },
47
+ "-u",
48
+ {
49
+ "fromLabPath": "UltravisorURL"
50
+ }
51
+ ],
52
+ "healthCheck": {
53
+ "path": "/"
54
+ },
55
+ "defaultPort": 7777,
56
+ "requiresUltravisor": true,
57
+ "configForm": {
58
+ "Fields": [
59
+ {
60
+ "Name": "HostContentPath",
61
+ "Label": "Host folder to serve",
62
+ "Type": "text",
63
+ "Required": true,
64
+ "Help": "Absolute path on the host whose contents will be served; bind-mounted read-only into the container at /app/content."
65
+ }
66
+ ]
67
+ },
68
+ "docker": {
69
+ "image": "retold-remote",
70
+ "dockerfile": "retold-remote.Dockerfile",
71
+ "dataMountPath": "/app/data",
72
+ "configMountPath": "/app/data/config.json",
73
+ "contentMountPath": "/app/content",
74
+ "exposedPort": 7777,
75
+ "configMounts": [
76
+ {
77
+ "ConfigField": "HostContentPath",
78
+ "Target": "/app/content",
79
+ "ReadOnly": true
80
+ }
81
+ ]
82
+ }
83
+ },
32
84
  "author": "steven velozo <steven@velozo.com>",
33
85
  "license": "MIT",
34
86
  "dependencies": {
@@ -212,10 +212,25 @@ function setupRetoldRemoteServer(pOptions, fCallback)
212
212
  });
213
213
 
214
214
  // Set up the large-image preview and DZI tile service
215
- let tmpImageService = new libRetoldRemoteImageService(tmpFable,
215
+ let tmpImageServiceOptions = { ContentPath: tmpContentPath };
216
+ let tmpDirectMaxBytes = null;
217
+ if (typeof pOptions.DirectDisplayMaxFileSize === 'number' && pOptions.DirectDisplayMaxFileSize >= 0)
216
218
  {
217
- ContentPath: tmpContentPath
218
- });
219
+ tmpDirectMaxBytes = pOptions.DirectDisplayMaxFileSize;
220
+ }
221
+ else if (process.env.RETOLD_DIRECT_IMAGE_MAX_BYTES)
222
+ {
223
+ let tmpParsed = parseInt(process.env.RETOLD_DIRECT_IMAGE_MAX_BYTES, 10);
224
+ if (!isNaN(tmpParsed) && tmpParsed >= 0)
225
+ {
226
+ tmpDirectMaxBytes = tmpParsed;
227
+ }
228
+ }
229
+ if (tmpDirectMaxBytes !== null)
230
+ {
231
+ tmpImageServiceOptions.DirectDisplayMaxFileSize = tmpDirectMaxBytes;
232
+ }
233
+ let tmpImageService = new libRetoldRemoteImageService(tmpFable, tmpImageServiceOptions);
219
234
 
220
235
  // Set up the subimage region service
221
236
  let tmpSubimageService = new libRetoldRemoteSubimageService(tmpFable,
@@ -1957,6 +1972,26 @@ function setupRetoldRemoteServer(pOptions, fCallback)
1957
1972
  return fNext();
1958
1973
  }
1959
1974
 
1975
+ // Always surface the original file size (the manifest's FileSize
1976
+ // field gets overwritten with the generated preview's size when
1977
+ // NeedsPreview=true). The client uses OrigFileSize against
1978
+ // DirectDisplayMaxFileSize to decide whether to bypass the
1979
+ // OpenSeadragon explorer / downscaled preview and load the
1980
+ // source image directly.
1981
+ if (pResult && typeof pResult === 'object')
1982
+ {
1983
+ try
1984
+ {
1985
+ let tmpStat = libFs.statSync(tmpAbsPath);
1986
+ pResult.OrigFileSize = tmpStat.size;
1987
+ }
1988
+ catch (pStatError)
1989
+ {
1990
+ // Non-fatal — client will fall through to existing logic
1991
+ }
1992
+ pResult.DirectDisplayMaxFileSize = tmpImageService.options.DirectDisplayMaxFileSize;
1993
+ }
1994
+
1960
1995
  pResponse.send(pResult);
1961
1996
  return fNext();
1962
1997
  });
@@ -35,6 +35,9 @@ class RetoldRemoteCommandServe extends libCommandLineCommand
35
35
  this.options.CommandOptions.push(
36
36
  { Name: '-l, --logfile [path]', Description: 'Write logs to a file (auto-generates timestamped name if path omitted).', Default: '' });
37
37
 
38
+ this.options.CommandOptions.push(
39
+ { Name: '--direct-image-max-mb [mb]', Description: 'Max original-file size (MB) to display an image directly instead of via OpenSeadragon. Set to 0 to always use the explorer. Default 15.', Default: '' });
40
+
38
41
  this.addCommand();
39
42
  }
40
43
 
@@ -127,16 +130,29 @@ class RetoldRemoteCommandServe extends libCommandLineCommand
127
130
  {
128
131
  let tmpSetupServer = require('../RetoldRemote-Server-Setup.js');
129
132
 
130
- tmpSetupServer(
133
+ let tmpSetupOptions =
134
+ {
135
+ ContentPath: tmpContentPath,
136
+ DistPath: tmpDistPath,
137
+ Port: tmpPort,
138
+ HashedFilenames: tmpHashedFilenames,
139
+ CacheRoot: tmpCacheRoot,
140
+ CacheServer: tmpCacheServer,
141
+ UltravisorURL: tmpUltravisorURL
142
+ };
143
+
144
+ let tmpDirectMaxMB = this.CommandOptions.directImageMaxMb;
145
+ if (typeof tmpDirectMaxMB === 'string' && tmpDirectMaxMB.length > 0)
146
+ {
147
+ let tmpMB = parseFloat(tmpDirectMaxMB);
148
+ if (!isNaN(tmpMB) && tmpMB >= 0)
131
149
  {
132
- ContentPath: tmpContentPath,
133
- DistPath: tmpDistPath,
134
- Port: tmpPort,
135
- HashedFilenames: tmpHashedFilenames,
136
- CacheRoot: tmpCacheRoot,
137
- CacheServer: tmpCacheServer,
138
- UltravisorURL: tmpUltravisorURL
139
- },
150
+ tmpSetupOptions.DirectDisplayMaxFileSize = Math.round(tmpMB * 1024 * 1024);
151
+ }
152
+ }
153
+
154
+ tmpSetupServer(
155
+ tmpSetupOptions,
140
156
  function (pError, pServerInfo)
141
157
  {
142
158
  if (pError)
@@ -49,7 +49,12 @@ const _DefaultServiceConfiguration =
49
49
  "DziQuality": 80,
50
50
  "PreviewQuality": 85,
51
51
  // Only generate preview/tiles for images larger than this (pixels on longest side)
52
- "LargeImageThreshold": 4096
52
+ "LargeImageThreshold": 4096,
53
+ // Original-file-size (bytes) at or below which the viewer will load the
54
+ // image directly instead of routing through the OpenSeadragon explorer or
55
+ // server-side downscaled preview. Raw camera formats always go through the
56
+ // preview pipeline regardless of size (browsers can't decode raw). 15 MiB.
57
+ "DirectDisplayMaxFileSize": 15 * 1024 * 1024
53
58
  };
54
59
 
55
60
  class RetoldRemoteImageService extends libFableServiceProviderBase
@@ -470,6 +470,7 @@ class RetoldRemoteMediaViewerView extends libPictView
470
470
 
471
471
  /**
472
472
  * Probe image dimensions, then decide how to display it:
473
+ * - File ≤ DirectDisplayMaxFileSize (default 15 MiB, non-raw): direct content URL
473
474
  * - ≤4096px: load direct content URL in the normal viewer
474
475
  * - 4096–8192px: load a server preview in the normal viewer, show Explore button
475
476
  * - >8192px: auto-launch the OpenSeadragon image explorer
@@ -495,6 +496,20 @@ class RetoldRemoteMediaViewerView extends libPictView
495
496
  return;
496
497
  }
497
498
 
499
+ // Small-file short-circuit: if the original is within the configured
500
+ // direct-display budget and the browser can decode it natively,
501
+ // skip both the server preview and the OpenSeadragon explorer.
502
+ // Raw camera formats always go through the preview pipeline.
503
+ if (!pResult.IsRawFormat
504
+ && typeof pResult.OrigFileSize === 'number'
505
+ && typeof pResult.DirectDisplayMaxFileSize === 'number'
506
+ && pResult.DirectDisplayMaxFileSize > 0
507
+ && pResult.OrigFileSize <= pResult.DirectDisplayMaxFileSize)
508
+ {
509
+ tmpSelf._insertImageTag(pContentURL, pFileName, false);
510
+ return;
511
+ }
512
+
498
513
  let tmpLongest = Math.max(pResult.OrigWidth || 0, pResult.OrigHeight || 0);
499
514
 
500
515
  // >8192px: auto-launch the OpenSeadragon image explorer
@@ -16128,6 +16128,7 @@ let tmpLastTap=0;let tmpOnTouchEnd=function(pEvent){let tmpNow=Date.now();if(tmp
16128
16128
  * Build a lightweight placeholder while probing image dimensions.
16129
16129
  */_buildImagePlaceholderHTML(pFileName){let tmpEscapedName=this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(pFileName);return'<div id="RetoldRemote-ImagePlaceholder" style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--retold-text-dim);font-size:0.85rem;">'+'Loading '+tmpEscapedName+'\u2026</div>';}/**
16130
16130
  * Probe image dimensions, then decide how to display it:
16131
+ * - File ≤ DirectDisplayMaxFileSize (default 15 MiB, non-raw): direct content URL
16131
16132
  * - ≤4096px: load direct content URL in the normal viewer
16132
16133
  * - 4096–8192px: load a server preview in the normal viewer, show Explore button
16133
16134
  * - >8192px: auto-launch the OpenSeadragon image explorer
@@ -16136,7 +16137,11 @@ let tmpLastTap=0;let tmpOnTouchEnd=function(pEvent){let tmpNow=Date.now();if(tmp
16136
16137
  * @param {string} pContentURL - Direct content URL (fallback)
16137
16138
  * @param {string} pFileName - Display name
16138
16139
  */_probeAndShowImage(pFilePath,pContentURL,pFileName){let tmpSelf=this;let tmpProvider=this.pict.providers['RetoldRemote-Provider'];let tmpPathParam=tmpProvider?tmpProvider._getPathParam(pFilePath):encodeURIComponent(pFilePath);fetch('/api/media/image-preview?path='+tmpPathParam).then(pResponse=>pResponse.json()).then(pResult=>{// If the probe failed or sharp isn't available, fall back to direct load
16139
- if(!pResult||!pResult.Success){tmpSelf._insertImageTag(pContentURL,pFileName,false);return;}let tmpLongest=Math.max(pResult.OrigWidth||0,pResult.OrigHeight||0);// >8192px: auto-launch the OpenSeadragon image explorer
16140
+ if(!pResult||!pResult.Success){tmpSelf._insertImageTag(pContentURL,pFileName,false);return;}// Small-file short-circuit: if the original is within the configured
16141
+ // direct-display budget and the browser can decode it natively,
16142
+ // skip both the server preview and the OpenSeadragon explorer.
16143
+ // Raw camera formats always go through the preview pipeline.
16144
+ if(!pResult.IsRawFormat&&typeof pResult.OrigFileSize==='number'&&typeof pResult.DirectDisplayMaxFileSize==='number'&&pResult.DirectDisplayMaxFileSize>0&&pResult.OrigFileSize<=pResult.DirectDisplayMaxFileSize){tmpSelf._insertImageTag(pContentURL,pFileName,false);return;}let tmpLongest=Math.max(pResult.OrigWidth||0,pResult.OrigHeight||0);// >8192px: auto-launch the OpenSeadragon image explorer
16140
16145
  if(tmpLongest>8192){let tmpIEX=tmpSelf.pict.views['RetoldRemote-ImageExplorer'];if(tmpIEX){tmpIEX.showExplorer(pFilePath);return;}// Fall through if explorer view isn't available
16141
16146
  }// 4096–8192px: use the server preview
16142
16147
  if(pResult.NeedsPreview&&pResult.CacheKey){let tmpPreviewURL='/api/media/image-preview-file/'+encodeURIComponent(pResult.CacheKey)+'/'+encodeURIComponent(pResult.OutputFilename);tmpSelf._insertImageTag(tmpPreviewURL,pFileName,true,pResult.OrigWidth,pResult.OrigHeight);return;}// ≤4096px: load the direct content URL