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,530 @@
|
|
|
1
|
+
const libPictView = require('pict-view');
|
|
2
|
+
const libPictSectionCode = require('pict-section-code');
|
|
3
|
+
|
|
4
|
+
const _ViewConfiguration =
|
|
5
|
+
{
|
|
6
|
+
ViewIdentifier: "RetoldRemote-MediaViewer",
|
|
7
|
+
DefaultRenderable: "RetoldRemote-MediaViewer",
|
|
8
|
+
DefaultDestinationAddress: "#RetoldRemote-Viewer-Container",
|
|
9
|
+
AutoRender: false,
|
|
10
|
+
|
|
11
|
+
CSS: /*css*/`
|
|
12
|
+
.retold-remote-viewer
|
|
13
|
+
{
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
height: 100%;
|
|
17
|
+
}
|
|
18
|
+
.retold-remote-viewer-header
|
|
19
|
+
{
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: 12px;
|
|
23
|
+
padding: 8px 16px;
|
|
24
|
+
background: var(--retold-bg-secondary);
|
|
25
|
+
border-bottom: 1px solid var(--retold-border);
|
|
26
|
+
flex-shrink: 0;
|
|
27
|
+
z-index: 5;
|
|
28
|
+
}
|
|
29
|
+
.retold-remote-viewer-nav-btn
|
|
30
|
+
{
|
|
31
|
+
padding: 4px 10px;
|
|
32
|
+
border: 1px solid var(--retold-border);
|
|
33
|
+
border-radius: 3px;
|
|
34
|
+
background: transparent;
|
|
35
|
+
color: var(--retold-text-muted);
|
|
36
|
+
font-size: 0.8rem;
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
transition: color 0.15s, border-color 0.15s;
|
|
39
|
+
font-family: inherit;
|
|
40
|
+
}
|
|
41
|
+
.retold-remote-viewer-nav-btn:hover
|
|
42
|
+
{
|
|
43
|
+
color: var(--retold-text-primary);
|
|
44
|
+
border-color: var(--retold-accent);
|
|
45
|
+
}
|
|
46
|
+
.retold-remote-viewer-title
|
|
47
|
+
{
|
|
48
|
+
flex: 1;
|
|
49
|
+
font-size: 0.82rem;
|
|
50
|
+
color: var(--retold-text-secondary);
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
text-overflow: ellipsis;
|
|
53
|
+
white-space: nowrap;
|
|
54
|
+
text-align: center;
|
|
55
|
+
}
|
|
56
|
+
.retold-remote-viewer-body
|
|
57
|
+
{
|
|
58
|
+
flex: 1;
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
overflow: auto;
|
|
63
|
+
position: relative;
|
|
64
|
+
}
|
|
65
|
+
/* File info overlay */
|
|
66
|
+
.retold-remote-fileinfo-overlay
|
|
67
|
+
{
|
|
68
|
+
position: absolute;
|
|
69
|
+
top: 48px;
|
|
70
|
+
right: 16px;
|
|
71
|
+
background: var(--retold-bg-secondary);
|
|
72
|
+
border: 1px solid var(--retold-border);
|
|
73
|
+
border-radius: 6px;
|
|
74
|
+
padding: 16px;
|
|
75
|
+
color: var(--retold-text-secondary);
|
|
76
|
+
font-size: 0.78rem;
|
|
77
|
+
z-index: 10;
|
|
78
|
+
min-width: 200px;
|
|
79
|
+
display: none;
|
|
80
|
+
}
|
|
81
|
+
.retold-remote-fileinfo-row
|
|
82
|
+
{
|
|
83
|
+
display: flex;
|
|
84
|
+
justify-content: space-between;
|
|
85
|
+
padding: 3px 0;
|
|
86
|
+
}
|
|
87
|
+
.retold-remote-fileinfo-label
|
|
88
|
+
{
|
|
89
|
+
color: var(--retold-text-dim);
|
|
90
|
+
}
|
|
91
|
+
.retold-remote-fileinfo-value
|
|
92
|
+
{
|
|
93
|
+
color: var(--retold-text-primary);
|
|
94
|
+
}
|
|
95
|
+
/* Code viewer container */
|
|
96
|
+
.retold-remote-code-viewer-container
|
|
97
|
+
{
|
|
98
|
+
width: 100%;
|
|
99
|
+
height: 100%;
|
|
100
|
+
overflow: hidden;
|
|
101
|
+
}
|
|
102
|
+
.retold-remote-code-viewer-loading
|
|
103
|
+
{
|
|
104
|
+
padding: 16px 20px;
|
|
105
|
+
color: var(--retold-text-dim);
|
|
106
|
+
font-style: italic;
|
|
107
|
+
font-size: 0.82rem;
|
|
108
|
+
}
|
|
109
|
+
/* pict-section-code dark theme overrides */
|
|
110
|
+
.retold-remote-code-viewer-container .pict-code-editor-wrap
|
|
111
|
+
{
|
|
112
|
+
border: none;
|
|
113
|
+
border-radius: 0;
|
|
114
|
+
height: 100%;
|
|
115
|
+
font-family: var(--retold-font-mono, 'SFMono-Regular', 'SF Mono', 'Menlo', 'Consolas', monospace);
|
|
116
|
+
font-size: 0.82rem;
|
|
117
|
+
line-height: 1.6;
|
|
118
|
+
}
|
|
119
|
+
.retold-remote-code-viewer-container .pict-code-line-numbers
|
|
120
|
+
{
|
|
121
|
+
background: var(--retold-bg-secondary);
|
|
122
|
+
border-right: 1px solid var(--retold-border);
|
|
123
|
+
color: var(--retold-text-dim);
|
|
124
|
+
font-size: 0.78rem;
|
|
125
|
+
line-height: 1.6;
|
|
126
|
+
padding: 10px 0;
|
|
127
|
+
}
|
|
128
|
+
.retold-remote-code-viewer-container .pict-code-editor
|
|
129
|
+
{
|
|
130
|
+
background: var(--retold-bg-tertiary);
|
|
131
|
+
color: var(--retold-text-primary);
|
|
132
|
+
padding: 10px 10px 10px 12px;
|
|
133
|
+
tab-size: 4;
|
|
134
|
+
-moz-tab-size: 4;
|
|
135
|
+
caret-color: var(--retold-accent);
|
|
136
|
+
border-radius: 0;
|
|
137
|
+
}
|
|
138
|
+
/* Syntax highlighting colors for dark themes */
|
|
139
|
+
.retold-remote-code-viewer-container .pict-code-editor .keyword { color: #C678DD; }
|
|
140
|
+
.retold-remote-code-viewer-container .pict-code-editor .string { color: #98C379; }
|
|
141
|
+
.retold-remote-code-viewer-container .pict-code-editor .number { color: #D19A66; }
|
|
142
|
+
.retold-remote-code-viewer-container .pict-code-editor .comment { color: #5C6370; font-style: italic; }
|
|
143
|
+
.retold-remote-code-viewer-container .pict-code-editor .operator { color: #56B6C2; }
|
|
144
|
+
.retold-remote-code-viewer-container .pict-code-editor .punctuation { color: #ABB2BF; }
|
|
145
|
+
.retold-remote-code-viewer-container .pict-code-editor .function-name { color: #61AFEF; }
|
|
146
|
+
.retold-remote-code-viewer-container .pict-code-editor .property { color: #E06C75; }
|
|
147
|
+
.retold-remote-code-viewer-container .pict-code-editor .tag { color: #E06C75; }
|
|
148
|
+
.retold-remote-code-viewer-container .pict-code-editor .attr-name { color: #D19A66; }
|
|
149
|
+
.retold-remote-code-viewer-container .pict-code-editor .attr-value { color: #98C379; }
|
|
150
|
+
`
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
class RetoldRemoteMediaViewerView extends libPictView
|
|
154
|
+
{
|
|
155
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
156
|
+
{
|
|
157
|
+
super(pFable, pOptions, pServiceHash);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Show the media viewer for a given file.
|
|
162
|
+
*
|
|
163
|
+
* @param {string} pFilePath - Relative file path
|
|
164
|
+
* @param {string} pMediaType - 'image', 'video', 'audio', 'document', or 'other'
|
|
165
|
+
*/
|
|
166
|
+
showMedia(pFilePath, pMediaType)
|
|
167
|
+
{
|
|
168
|
+
let tmpRemote = this.pict.AppData.RetoldRemote;
|
|
169
|
+
tmpRemote.ActiveMode = 'viewer';
|
|
170
|
+
tmpRemote.CurrentViewerFile = pFilePath;
|
|
171
|
+
tmpRemote.CurrentViewerMediaType = pMediaType;
|
|
172
|
+
|
|
173
|
+
// Show viewer, hide gallery
|
|
174
|
+
let tmpGalleryContainer = document.getElementById('RetoldRemote-Gallery-Container');
|
|
175
|
+
let tmpViewerContainer = document.getElementById('RetoldRemote-Viewer-Container');
|
|
176
|
+
|
|
177
|
+
if (tmpGalleryContainer) tmpGalleryContainer.style.display = 'none';
|
|
178
|
+
if (tmpViewerContainer) tmpViewerContainer.style.display = 'block';
|
|
179
|
+
|
|
180
|
+
let tmpFileName = pFilePath.replace(/^.*\//, '');
|
|
181
|
+
let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
|
|
182
|
+
let tmpContentURL = tmpProvider ? tmpProvider.getContentURL(pFilePath) : ('/content/' + encodeURIComponent(pFilePath));
|
|
183
|
+
|
|
184
|
+
// Build the viewer HTML
|
|
185
|
+
let tmpHTML = '<div class="retold-remote-viewer">';
|
|
186
|
+
|
|
187
|
+
// Header with nav
|
|
188
|
+
tmpHTML += '<div class="retold-remote-viewer-header">';
|
|
189
|
+
tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].closeViewer()" title="Back (Esc)">← Back</button>';
|
|
190
|
+
tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].prevFile()" title="Previous (k)">‹ Prev</button>';
|
|
191
|
+
tmpHTML += '<div class="retold-remote-viewer-title">' + this._escapeHTML(tmpFileName) + '</div>';
|
|
192
|
+
tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\'].nextFile()" title="Next (j)">Next ›</button>';
|
|
193
|
+
tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._toggleFileInfo()" title="Info (i)">ⓘ</button>';
|
|
194
|
+
tmpHTML += '<button class="retold-remote-viewer-nav-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._toggleFullscreen()" title="Fullscreen (f)">▢</button>';
|
|
195
|
+
tmpHTML += '</div>';
|
|
196
|
+
|
|
197
|
+
// Body with media content
|
|
198
|
+
tmpHTML += '<div class="retold-remote-viewer-body">';
|
|
199
|
+
|
|
200
|
+
switch (pMediaType)
|
|
201
|
+
{
|
|
202
|
+
case 'image':
|
|
203
|
+
tmpHTML += this._buildImageHTML(tmpContentURL, tmpFileName);
|
|
204
|
+
break;
|
|
205
|
+
case 'video':
|
|
206
|
+
tmpHTML += this._buildVideoHTML(tmpContentURL, tmpFileName);
|
|
207
|
+
break;
|
|
208
|
+
case 'audio':
|
|
209
|
+
tmpHTML += this._buildAudioHTML(tmpContentURL, tmpFileName);
|
|
210
|
+
break;
|
|
211
|
+
case 'text':
|
|
212
|
+
tmpHTML += this._buildTextHTML(tmpContentURL, tmpFileName, pFilePath);
|
|
213
|
+
break;
|
|
214
|
+
case 'document':
|
|
215
|
+
tmpHTML += this._buildDocumentHTML(tmpContentURL, tmpFileName, pFilePath);
|
|
216
|
+
break;
|
|
217
|
+
default:
|
|
218
|
+
tmpHTML += this._buildFallbackHTML(tmpContentURL, tmpFileName);
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// File info overlay (hidden by default)
|
|
223
|
+
tmpHTML += '<div class="retold-remote-fileinfo-overlay" id="RetoldRemote-FileInfo-Overlay">';
|
|
224
|
+
tmpHTML += '<div class="retold-remote-fileinfo-row"><span class="retold-remote-fileinfo-label">Loading...</span></div>';
|
|
225
|
+
tmpHTML += '</div>';
|
|
226
|
+
|
|
227
|
+
tmpHTML += '</div>'; // end body
|
|
228
|
+
tmpHTML += '</div>'; // end viewer
|
|
229
|
+
|
|
230
|
+
if (tmpViewerContainer)
|
|
231
|
+
{
|
|
232
|
+
tmpViewerContainer.innerHTML = tmpHTML;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Fetch and populate file info
|
|
236
|
+
this._loadFileInfo(pFilePath);
|
|
237
|
+
|
|
238
|
+
// Fetch text content and initialize code viewer
|
|
239
|
+
if (pMediaType === 'text')
|
|
240
|
+
{
|
|
241
|
+
this._loadCodeViewer(tmpContentURL, pFilePath);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Update topbar
|
|
245
|
+
let tmpTopBar = this.pict.views['ContentEditor-TopBar'];
|
|
246
|
+
if (tmpTopBar)
|
|
247
|
+
{
|
|
248
|
+
tmpTopBar.updateInfo();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
_buildImageHTML(pURL, pFileName)
|
|
253
|
+
{
|
|
254
|
+
return '<img src="' + pURL + '" alt="' + this._escapeHTML(pFileName) + '" '
|
|
255
|
+
+ 'style="max-width: 100%; max-height: 100%; object-fit: contain; cursor: zoom-in;" '
|
|
256
|
+
+ 'id="RetoldRemote-ImageViewer-Img" '
|
|
257
|
+
+ 'onload="pict.views[\'RetoldRemote-ImageViewer\'].initImage()" '
|
|
258
|
+
+ 'onclick="pict.views[\'RetoldRemote-ImageViewer\'].toggleZoom()">';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
_buildVideoHTML(pURL, pFileName)
|
|
262
|
+
{
|
|
263
|
+
return '<video controls autoplay preload="metadata" '
|
|
264
|
+
+ 'style="max-width: 100%; max-height: 100%;" '
|
|
265
|
+
+ 'id="RetoldRemote-VideoPlayer">'
|
|
266
|
+
+ '<source src="' + pURL + '">'
|
|
267
|
+
+ 'Your browser does not support the video tag.'
|
|
268
|
+
+ '</video>';
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
_buildAudioHTML(pURL, pFileName)
|
|
272
|
+
{
|
|
273
|
+
let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
|
|
274
|
+
let tmpIconHTML = tmpIconProvider ? '<span class="retold-remote-icon retold-remote-icon-lg">' + tmpIconProvider.getIcon('music-note', 64) + '</span>' : '🎵';
|
|
275
|
+
return '<div style="text-align: center; padding: 40px;">'
|
|
276
|
+
+ '<div style="margin-bottom: 24px;">' + tmpIconHTML + '</div>'
|
|
277
|
+
+ '<div style="font-size: 1.1rem; color: var(--retold-text-secondary); margin-bottom: 24px;">' + this._escapeHTML(pFileName) + '</div>'
|
|
278
|
+
+ '<audio controls autoplay preload="metadata" id="RetoldRemote-AudioPlayer" style="width: 100%; max-width: 500px;">'
|
|
279
|
+
+ '<source src="' + pURL + '">'
|
|
280
|
+
+ 'Your browser does not support the audio tag.'
|
|
281
|
+
+ '</audio>'
|
|
282
|
+
+ '</div>';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
_buildDocumentHTML(pURL, pFileName, pFilePath)
|
|
286
|
+
{
|
|
287
|
+
let tmpExtension = pFilePath.replace(/^.*\./, '').toLowerCase();
|
|
288
|
+
|
|
289
|
+
if (tmpExtension === 'pdf')
|
|
290
|
+
{
|
|
291
|
+
return '<iframe src="' + pURL + '" '
|
|
292
|
+
+ 'style="width: 100%; height: 100%; border: none;">'
|
|
293
|
+
+ '</iframe>';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// For other document types, show a download link
|
|
297
|
+
let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
|
|
298
|
+
let tmpDocIconHTML = tmpIconProvider ? '<span class="retold-remote-icon retold-remote-icon-lg">' + tmpIconProvider.getIcon('document-large', 64) + '</span>' : '📄';
|
|
299
|
+
return '<div style="text-align: center; padding: 40px;">'
|
|
300
|
+
+ '<div style="margin-bottom: 24px;">' + tmpDocIconHTML + '</div>'
|
|
301
|
+
+ '<div style="font-size: 1.1rem; color: var(--retold-text-secondary); margin-bottom: 24px;">' + this._escapeHTML(pFileName) + '</div>'
|
|
302
|
+
+ '<a href="' + pURL + '" target="_blank" style="color: var(--retold-accent); font-size: 0.9rem;">Open in new tab</a>'
|
|
303
|
+
+ '</div>';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
_buildTextHTML(pURL, pFileName, pFilePath)
|
|
307
|
+
{
|
|
308
|
+
return '<div class="retold-remote-code-viewer-container" id="RetoldRemote-CodeViewer-Container">'
|
|
309
|
+
+ '<div class="retold-remote-code-viewer-loading">Loading...</div>'
|
|
310
|
+
+ '</div>';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Map a file extension to a pict-section-code highlight language.
|
|
315
|
+
*
|
|
316
|
+
* @param {string} pExtension - File extension (no dot)
|
|
317
|
+
* @returns {string} One of: javascript, json, html, css, sql
|
|
318
|
+
*/
|
|
319
|
+
_getHighlightLanguage(pExtension)
|
|
320
|
+
{
|
|
321
|
+
let tmpJSExtensions = { 'js': true, 'mjs': true, 'cjs': true, 'ts': true, 'tsx': true, 'jsx': true };
|
|
322
|
+
if (tmpJSExtensions[pExtension]) return 'javascript';
|
|
323
|
+
|
|
324
|
+
if (pExtension === 'json') return 'json';
|
|
325
|
+
|
|
326
|
+
let tmpHTMLExtensions = { 'html': true, 'htm': true, 'xml': true, 'svg': true };
|
|
327
|
+
if (tmpHTMLExtensions[pExtension]) return 'html';
|
|
328
|
+
|
|
329
|
+
let tmpCSSExtensions = { 'css': true, 'scss': true, 'sass': true, 'less': true };
|
|
330
|
+
if (tmpCSSExtensions[pExtension]) return 'css';
|
|
331
|
+
|
|
332
|
+
if (pExtension === 'sql') return 'sql';
|
|
333
|
+
|
|
334
|
+
// Default to javascript highlighting for other text files
|
|
335
|
+
return 'javascript';
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Load text content and display it using pict-section-code for
|
|
340
|
+
* syntax highlighting and line numbers.
|
|
341
|
+
*
|
|
342
|
+
* @param {string} pURL - Content URL to fetch
|
|
343
|
+
* @param {string} pFilePath - Full file path (used to derive extension)
|
|
344
|
+
*/
|
|
345
|
+
_loadCodeViewer(pURL, pFilePath)
|
|
346
|
+
{
|
|
347
|
+
let tmpSelf = this;
|
|
348
|
+
let tmpExtension = pFilePath.replace(/^.*\./, '').toLowerCase();
|
|
349
|
+
|
|
350
|
+
fetch(pURL)
|
|
351
|
+
.then((pResponse) =>
|
|
352
|
+
{
|
|
353
|
+
if (!pResponse.ok)
|
|
354
|
+
{
|
|
355
|
+
throw new Error('HTTP ' + pResponse.status);
|
|
356
|
+
}
|
|
357
|
+
return pResponse.text();
|
|
358
|
+
})
|
|
359
|
+
.then((pText) =>
|
|
360
|
+
{
|
|
361
|
+
let tmpContainer = document.getElementById('RetoldRemote-CodeViewer-Container');
|
|
362
|
+
if (!tmpContainer)
|
|
363
|
+
{
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
let tmpLanguage = tmpSelf._getHighlightLanguage(tmpExtension);
|
|
368
|
+
|
|
369
|
+
// Destroy any previous CodeJar instance
|
|
370
|
+
if (tmpSelf._activeCodeJar)
|
|
371
|
+
{
|
|
372
|
+
tmpSelf._activeCodeJar.destroy();
|
|
373
|
+
tmpSelf._activeCodeJar = null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Check that CodeJar is available
|
|
377
|
+
if (typeof (window) === 'undefined' || typeof (window.CodeJar) !== 'function')
|
|
378
|
+
{
|
|
379
|
+
tmpContainer.innerHTML = '<div class="retold-remote-code-viewer-loading">CodeJar not loaded; showing plain text.</div>';
|
|
380
|
+
let tmpPre = document.createElement('pre');
|
|
381
|
+
tmpPre.style.cssText = 'padding:16px 20px; margin:0; color:var(--retold-text-primary); font-family:monospace; white-space:pre; tab-size:4; overflow:auto; height:100%;';
|
|
382
|
+
tmpPre.textContent = pText;
|
|
383
|
+
tmpContainer.appendChild(tmpPre);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Build the editor DOM (mirrors pict-section-code _buildEditorDOM)
|
|
388
|
+
tmpContainer.innerHTML = '';
|
|
389
|
+
|
|
390
|
+
let tmpWrap = document.createElement('div');
|
|
391
|
+
tmpWrap.className = 'pict-code-editor-wrap';
|
|
392
|
+
|
|
393
|
+
let tmpLineNumbers = document.createElement('div');
|
|
394
|
+
tmpLineNumbers.className = 'pict-code-line-numbers';
|
|
395
|
+
tmpWrap.appendChild(tmpLineNumbers);
|
|
396
|
+
|
|
397
|
+
let tmpEditor = document.createElement('div');
|
|
398
|
+
tmpEditor.className = 'pict-code-editor language-' + tmpLanguage;
|
|
399
|
+
tmpWrap.appendChild(tmpEditor);
|
|
400
|
+
|
|
401
|
+
tmpContainer.appendChild(tmpWrap);
|
|
402
|
+
|
|
403
|
+
// Create the highlight function from pict-section-code
|
|
404
|
+
let tmpHighlight = libPictSectionCode.createHighlighter(tmpLanguage);
|
|
405
|
+
|
|
406
|
+
// Instantiate CodeJar
|
|
407
|
+
tmpSelf._activeCodeJar = window.CodeJar(tmpEditor, tmpHighlight,
|
|
408
|
+
{
|
|
409
|
+
tab: '\t',
|
|
410
|
+
catchTab: false,
|
|
411
|
+
addClosing: false
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Reset inline styles that CodeJar forces
|
|
415
|
+
tmpEditor.style.whiteSpace = 'pre';
|
|
416
|
+
tmpEditor.style.overflowWrap = 'normal';
|
|
417
|
+
|
|
418
|
+
// Set the content
|
|
419
|
+
tmpSelf._activeCodeJar.updateCode(pText);
|
|
420
|
+
|
|
421
|
+
// Make it read-only
|
|
422
|
+
tmpEditor.setAttribute('contenteditable', 'false');
|
|
423
|
+
|
|
424
|
+
// Render line numbers
|
|
425
|
+
let tmpLineCount = pText.split('\n').length;
|
|
426
|
+
let tmpLineHTML = '';
|
|
427
|
+
for (let i = 1; i <= tmpLineCount; i++)
|
|
428
|
+
{
|
|
429
|
+
tmpLineHTML += '<span>' + i + '</span>';
|
|
430
|
+
}
|
|
431
|
+
tmpLineNumbers.innerHTML = tmpLineHTML;
|
|
432
|
+
})
|
|
433
|
+
.catch((pError) =>
|
|
434
|
+
{
|
|
435
|
+
let tmpContainer = document.getElementById('RetoldRemote-CodeViewer-Container');
|
|
436
|
+
if (tmpContainer)
|
|
437
|
+
{
|
|
438
|
+
tmpContainer.innerHTML = '<div class="retold-remote-code-viewer-loading">Failed to load file: ' + pError.message + '</div>';
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
_buildFallbackHTML(pURL, pFileName)
|
|
444
|
+
{
|
|
445
|
+
let tmpIconProvider = this.pict.providers['RetoldRemote-Icons'];
|
|
446
|
+
let tmpFallbackIconHTML = tmpIconProvider ? '<span class="retold-remote-icon retold-remote-icon-lg">' + tmpIconProvider.getIcon('document-large', 64) + '</span>' : '📄';
|
|
447
|
+
return '<div style="text-align: center; padding: 40px;">'
|
|
448
|
+
+ '<div style="margin-bottom: 24px;">' + tmpFallbackIconHTML + '</div>'
|
|
449
|
+
+ '<div style="font-size: 1.1rem; color: var(--retold-text-secondary); margin-bottom: 24px;">' + this._escapeHTML(pFileName) + '</div>'
|
|
450
|
+
+ '<a href="' + pURL + '" target="_blank" style="color: var(--retold-accent); font-size: 0.9rem;">Download / Open in new tab</a>'
|
|
451
|
+
+ '</div>';
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Fetch file info and populate the overlay.
|
|
456
|
+
*/
|
|
457
|
+
_loadFileInfo(pFilePath)
|
|
458
|
+
{
|
|
459
|
+
let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
|
|
460
|
+
if (!tmpProvider)
|
|
461
|
+
{
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
tmpProvider.fetchMediaProbe(pFilePath,
|
|
466
|
+
(pError, pData) =>
|
|
467
|
+
{
|
|
468
|
+
let tmpOverlay = document.getElementById('RetoldRemote-FileInfo-Overlay');
|
|
469
|
+
if (!tmpOverlay || !pData)
|
|
470
|
+
{
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
let tmpHTML = '';
|
|
475
|
+
|
|
476
|
+
if (pData.Size !== undefined)
|
|
477
|
+
{
|
|
478
|
+
tmpHTML += '<div class="retold-remote-fileinfo-row"><span class="retold-remote-fileinfo-label">Size</span><span class="retold-remote-fileinfo-value">' + this._formatFileSize(pData.Size) + '</span></div>';
|
|
479
|
+
}
|
|
480
|
+
if (pData.Width && pData.Height)
|
|
481
|
+
{
|
|
482
|
+
tmpHTML += '<div class="retold-remote-fileinfo-row"><span class="retold-remote-fileinfo-label">Dimensions</span><span class="retold-remote-fileinfo-value">' + pData.Width + ' x ' + pData.Height + '</span></div>';
|
|
483
|
+
}
|
|
484
|
+
if (pData.Duration)
|
|
485
|
+
{
|
|
486
|
+
let tmpMin = Math.floor(pData.Duration / 60);
|
|
487
|
+
let tmpSec = Math.floor(pData.Duration % 60);
|
|
488
|
+
tmpHTML += '<div class="retold-remote-fileinfo-row"><span class="retold-remote-fileinfo-label">Duration</span><span class="retold-remote-fileinfo-value">' + tmpMin + ':' + (tmpSec < 10 ? '0' : '') + tmpSec + '</span></div>';
|
|
489
|
+
}
|
|
490
|
+
if (pData.Codec)
|
|
491
|
+
{
|
|
492
|
+
tmpHTML += '<div class="retold-remote-fileinfo-row"><span class="retold-remote-fileinfo-label">Codec</span><span class="retold-remote-fileinfo-value">' + pData.Codec + '</span></div>';
|
|
493
|
+
}
|
|
494
|
+
if (pData.Format)
|
|
495
|
+
{
|
|
496
|
+
tmpHTML += '<div class="retold-remote-fileinfo-row"><span class="retold-remote-fileinfo-label">Format</span><span class="retold-remote-fileinfo-value">' + pData.Format + '</span></div>';
|
|
497
|
+
}
|
|
498
|
+
if (pData.Modified)
|
|
499
|
+
{
|
|
500
|
+
tmpHTML += '<div class="retold-remote-fileinfo-row"><span class="retold-remote-fileinfo-label">Modified</span><span class="retold-remote-fileinfo-value">' + new Date(pData.Modified).toLocaleString() + '</span></div>';
|
|
501
|
+
}
|
|
502
|
+
if (pData.Path)
|
|
503
|
+
{
|
|
504
|
+
tmpHTML += '<div class="retold-remote-fileinfo-row"><span class="retold-remote-fileinfo-label">Path</span><span class="retold-remote-fileinfo-value">' + pData.Path + '</span></div>';
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
tmpOverlay.innerHTML = tmpHTML;
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
_formatFileSize(pBytes)
|
|
512
|
+
{
|
|
513
|
+
if (!pBytes || pBytes === 0) return '0 B';
|
|
514
|
+
let tmpUnits = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
515
|
+
let tmpIndex = Math.floor(Math.log(pBytes) / Math.log(1024));
|
|
516
|
+
if (tmpIndex >= tmpUnits.length) tmpIndex = tmpUnits.length - 1;
|
|
517
|
+
let tmpSize = pBytes / Math.pow(1024, tmpIndex);
|
|
518
|
+
return tmpSize.toFixed(tmpIndex === 0 ? 0 : 1) + ' ' + tmpUnits[tmpIndex];
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
_escapeHTML(pText)
|
|
522
|
+
{
|
|
523
|
+
if (!pText) return '';
|
|
524
|
+
return pText.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
RetoldRemoteMediaViewerView.default_configuration = _ViewConfiguration;
|
|
529
|
+
|
|
530
|
+
module.exports = RetoldRemoteMediaViewerView;
|