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,363 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
|
|
3
|
+
const _ViewConfiguration =
|
|
4
|
+
{
|
|
5
|
+
ViewIdentifier: "RetoldRemote-ImageViewer",
|
|
6
|
+
DefaultRenderable: "RetoldRemote-ImageViewer",
|
|
7
|
+
DefaultDestinationAddress: "#RetoldRemote-Viewer-Container",
|
|
8
|
+
AutoRender: false,
|
|
9
|
+
|
|
10
|
+
CSS: /*css*/`
|
|
11
|
+
#RetoldRemote-ImageViewer-Img
|
|
12
|
+
{
|
|
13
|
+
image-orientation: from-image;
|
|
14
|
+
transition: width 0.15s ease, height 0.15s ease;
|
|
15
|
+
display: block;
|
|
16
|
+
}
|
|
17
|
+
.retold-remote-fit-indicator
|
|
18
|
+
{
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 50%;
|
|
21
|
+
left: 50%;
|
|
22
|
+
transform: translate(-50%, -50%);
|
|
23
|
+
background: rgba(0, 0, 0, 0.7);
|
|
24
|
+
color: #fff;
|
|
25
|
+
padding: 8px 18px;
|
|
26
|
+
border-radius: 6px;
|
|
27
|
+
font-size: 0.82rem;
|
|
28
|
+
pointer-events: none;
|
|
29
|
+
z-index: 20;
|
|
30
|
+
opacity: 0;
|
|
31
|
+
transition: opacity 0.3s ease;
|
|
32
|
+
white-space: nowrap;
|
|
33
|
+
}
|
|
34
|
+
.retold-remote-fit-indicator.visible
|
|
35
|
+
{
|
|
36
|
+
opacity: 1;
|
|
37
|
+
}
|
|
38
|
+
`
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
class RetoldRemoteImageViewerView extends libPictView
|
|
42
|
+
{
|
|
43
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
44
|
+
{
|
|
45
|
+
super(pFable, pOptions, pServiceHash);
|
|
46
|
+
|
|
47
|
+
this._zoomLevel = 1;
|
|
48
|
+
this._naturalWidth = 0;
|
|
49
|
+
this._naturalHeight = 0;
|
|
50
|
+
this._resizeHandler = null;
|
|
51
|
+
this._indicatorTimeout = null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Called when the image finishes loading. Captures the natural
|
|
56
|
+
* dimensions and applies the current fit mode.
|
|
57
|
+
*/
|
|
58
|
+
initImage()
|
|
59
|
+
{
|
|
60
|
+
let tmpImg = document.getElementById('RetoldRemote-ImageViewer-Img');
|
|
61
|
+
if (!tmpImg)
|
|
62
|
+
{
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this._naturalWidth = tmpImg.naturalWidth;
|
|
67
|
+
this._naturalHeight = tmpImg.naturalHeight;
|
|
68
|
+
this._zoomLevel = 1;
|
|
69
|
+
|
|
70
|
+
this._applyDisplay();
|
|
71
|
+
|
|
72
|
+
// Recalculate on window resize
|
|
73
|
+
if (this._resizeHandler)
|
|
74
|
+
{
|
|
75
|
+
window.removeEventListener('resize', this._resizeHandler);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let tmpSelf = this;
|
|
79
|
+
let tmpResizeTimer = null;
|
|
80
|
+
this._resizeHandler = function ()
|
|
81
|
+
{
|
|
82
|
+
clearTimeout(tmpResizeTimer);
|
|
83
|
+
tmpResizeTimer = setTimeout(function ()
|
|
84
|
+
{
|
|
85
|
+
tmpSelf._applyDisplay();
|
|
86
|
+
}, 100);
|
|
87
|
+
};
|
|
88
|
+
window.addEventListener('resize', this._resizeHandler);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the current fit mode from AppData.
|
|
93
|
+
*
|
|
94
|
+
* @returns {string} 'fit' | 'auto' | 'original'
|
|
95
|
+
*/
|
|
96
|
+
_getFitMode()
|
|
97
|
+
{
|
|
98
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
99
|
+
return tmpRemote.ImageFitMode || 'auto';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Set the fit mode and persist it.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} pMode - 'fit' | 'auto' | 'original'
|
|
106
|
+
*/
|
|
107
|
+
setFitMode(pMode)
|
|
108
|
+
{
|
|
109
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
110
|
+
tmpRemote.ImageFitMode = pMode;
|
|
111
|
+
this._zoomLevel = 1;
|
|
112
|
+
this._applyDisplay();
|
|
113
|
+
|
|
114
|
+
if (this.pict.PictApplication && this.pict.PictApplication.saveSettings)
|
|
115
|
+
{
|
|
116
|
+
this.pict.PictApplication.saveSettings();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Cycle through fit modes: fit → auto → original → fit
|
|
122
|
+
*/
|
|
123
|
+
cycleFitMode()
|
|
124
|
+
{
|
|
125
|
+
let tmpMode = this._getFitMode();
|
|
126
|
+
let tmpNext;
|
|
127
|
+
|
|
128
|
+
switch (tmpMode)
|
|
129
|
+
{
|
|
130
|
+
case 'fit':
|
|
131
|
+
tmpNext = 'auto';
|
|
132
|
+
break;
|
|
133
|
+
case 'auto':
|
|
134
|
+
tmpNext = 'original';
|
|
135
|
+
break;
|
|
136
|
+
default:
|
|
137
|
+
tmpNext = 'fit';
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.setFitMode(tmpNext);
|
|
142
|
+
this._showFitModeIndicator(tmpNext);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Toggle between 1× and 2× zoom.
|
|
147
|
+
*/
|
|
148
|
+
toggleZoom()
|
|
149
|
+
{
|
|
150
|
+
if (!this._naturalWidth)
|
|
151
|
+
{
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (this._zoomLevel === 1)
|
|
156
|
+
{
|
|
157
|
+
this._zoomLevel = 2;
|
|
158
|
+
}
|
|
159
|
+
else
|
|
160
|
+
{
|
|
161
|
+
this._zoomLevel = 1;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this._applyDisplay();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
zoomIn()
|
|
168
|
+
{
|
|
169
|
+
this._zoomLevel = Math.min(this._zoomLevel * 1.25, 8);
|
|
170
|
+
this._applyDisplay();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
zoomOut()
|
|
174
|
+
{
|
|
175
|
+
this._zoomLevel = Math.max(this._zoomLevel / 1.25, 0.25);
|
|
176
|
+
this._applyDisplay();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
zoomReset()
|
|
180
|
+
{
|
|
181
|
+
this._zoomLevel = 1;
|
|
182
|
+
this._applyDisplay();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Core display method. Calculates image width/height from the
|
|
187
|
+
* current fit mode and zoom level, then sets them explicitly so
|
|
188
|
+
* the container can scroll naturally.
|
|
189
|
+
*/
|
|
190
|
+
_applyDisplay()
|
|
191
|
+
{
|
|
192
|
+
let tmpImg = document.getElementById('RetoldRemote-ImageViewer-Img');
|
|
193
|
+
if (!tmpImg || !this._naturalWidth)
|
|
194
|
+
{
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let tmpContainer = tmpImg.parentElement;
|
|
199
|
+
if (!tmpContainer)
|
|
200
|
+
{
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let tmpBase = this._getBaseSize(tmpContainer);
|
|
205
|
+
let tmpDisplayW = Math.round(tmpBase.width * this._zoomLevel);
|
|
206
|
+
let tmpDisplayH = Math.round(tmpBase.height * this._zoomLevel);
|
|
207
|
+
|
|
208
|
+
// Clear the initial inline constraints from _buildImageHTML
|
|
209
|
+
tmpImg.style.maxWidth = 'none';
|
|
210
|
+
tmpImg.style.maxHeight = 'none';
|
|
211
|
+
tmpImg.style.objectFit = '';
|
|
212
|
+
|
|
213
|
+
tmpImg.style.width = tmpDisplayW + 'px';
|
|
214
|
+
tmpImg.style.height = tmpDisplayH + 'px';
|
|
215
|
+
tmpImg.style.transform = '';
|
|
216
|
+
|
|
217
|
+
// Update cursor based on whether zoomed
|
|
218
|
+
if (this._zoomLevel > 1 || (tmpDisplayW > tmpContainer.clientWidth || tmpDisplayH > tmpContainer.clientHeight))
|
|
219
|
+
{
|
|
220
|
+
tmpImg.style.cursor = 'zoom-out';
|
|
221
|
+
}
|
|
222
|
+
else
|
|
223
|
+
{
|
|
224
|
+
tmpImg.style.cursor = 'zoom-in';
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Calculate the base display size (before zoom) for the current
|
|
230
|
+
* fit mode.
|
|
231
|
+
*
|
|
232
|
+
* @param {HTMLElement} pContainer - The viewer body element
|
|
233
|
+
* @returns {{ width: number, height: number }}
|
|
234
|
+
*/
|
|
235
|
+
_getBaseSize(pContainer)
|
|
236
|
+
{
|
|
237
|
+
let tmpNW = this._naturalWidth;
|
|
238
|
+
let tmpNH = this._naturalHeight;
|
|
239
|
+
let tmpCW = pContainer.clientWidth;
|
|
240
|
+
let tmpCH = pContainer.clientHeight;
|
|
241
|
+
|
|
242
|
+
if (!tmpCW || !tmpCH)
|
|
243
|
+
{
|
|
244
|
+
return { width: tmpNW, height: tmpNH };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let tmpAspect = tmpNW / tmpNH;
|
|
248
|
+
let tmpMode = this._getFitMode();
|
|
249
|
+
|
|
250
|
+
switch (tmpMode)
|
|
251
|
+
{
|
|
252
|
+
case 'fit':
|
|
253
|
+
{
|
|
254
|
+
// Always scale to fill viewport (contain)
|
|
255
|
+
let tmpFitW = tmpCW;
|
|
256
|
+
let tmpFitH = tmpCW / tmpAspect;
|
|
257
|
+
|
|
258
|
+
if (tmpFitH > tmpCH)
|
|
259
|
+
{
|
|
260
|
+
tmpFitH = tmpCH;
|
|
261
|
+
tmpFitW = tmpCH * tmpAspect;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return { width: tmpFitW, height: tmpFitH };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
case 'auto':
|
|
268
|
+
{
|
|
269
|
+
// Original size if smaller, fit if larger
|
|
270
|
+
if (tmpNW <= tmpCW && tmpNH <= tmpCH)
|
|
271
|
+
{
|
|
272
|
+
return { width: tmpNW, height: tmpNH };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Scale down to fit
|
|
276
|
+
let tmpFitW = tmpCW;
|
|
277
|
+
let tmpFitH = tmpCW / tmpAspect;
|
|
278
|
+
|
|
279
|
+
if (tmpFitH > tmpCH)
|
|
280
|
+
{
|
|
281
|
+
tmpFitH = tmpCH;
|
|
282
|
+
tmpFitW = tmpCH * tmpAspect;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return { width: tmpFitW, height: tmpFitH };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
case 'original':
|
|
289
|
+
default:
|
|
290
|
+
{
|
|
291
|
+
// Always native resolution
|
|
292
|
+
return { width: tmpNW, height: tmpNH };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Show a brief overlay label indicating the current fit mode.
|
|
299
|
+
*
|
|
300
|
+
* @param {string} pMode - The mode identifier
|
|
301
|
+
*/
|
|
302
|
+
_showFitModeIndicator(pMode)
|
|
303
|
+
{
|
|
304
|
+
let tmpLabels =
|
|
305
|
+
{
|
|
306
|
+
'fit': 'Fit to Window',
|
|
307
|
+
'auto': 'Original if Smaller',
|
|
308
|
+
'original': 'Original Size'
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
let tmpLabel = tmpLabels[pMode] || pMode;
|
|
312
|
+
|
|
313
|
+
// Create or reuse the indicator element
|
|
314
|
+
let tmpIndicator = document.getElementById('RetoldRemote-FitIndicator');
|
|
315
|
+
if (!tmpIndicator)
|
|
316
|
+
{
|
|
317
|
+
tmpIndicator = document.createElement('div');
|
|
318
|
+
tmpIndicator.id = 'RetoldRemote-FitIndicator';
|
|
319
|
+
tmpIndicator.className = 'retold-remote-fit-indicator';
|
|
320
|
+
|
|
321
|
+
let tmpContainer = document.querySelector('.retold-remote-viewer-body');
|
|
322
|
+
if (tmpContainer)
|
|
323
|
+
{
|
|
324
|
+
tmpContainer.appendChild(tmpIndicator);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
tmpIndicator.textContent = tmpLabel;
|
|
329
|
+
tmpIndicator.classList.add('visible');
|
|
330
|
+
|
|
331
|
+
if (this._indicatorTimeout)
|
|
332
|
+
{
|
|
333
|
+
clearTimeout(this._indicatorTimeout);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this._indicatorTimeout = setTimeout(function ()
|
|
337
|
+
{
|
|
338
|
+
tmpIndicator.classList.remove('visible');
|
|
339
|
+
}, 1200);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Clean up resize handler when navigating away.
|
|
344
|
+
*/
|
|
345
|
+
cleanup()
|
|
346
|
+
{
|
|
347
|
+
if (this._resizeHandler)
|
|
348
|
+
{
|
|
349
|
+
window.removeEventListener('resize', this._resizeHandler);
|
|
350
|
+
this._resizeHandler = null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (this._indicatorTimeout)
|
|
354
|
+
{
|
|
355
|
+
clearTimeout(this._indicatorTimeout);
|
|
356
|
+
this._indicatorTimeout = null;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
RetoldRemoteImageViewerView.default_configuration = _ViewConfiguration;
|
|
362
|
+
|
|
363
|
+
module.exports = RetoldRemoteImageViewerView;
|