retold-remote 0.0.22 → 0.0.25
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/css/retold-remote.css +87 -20
- package/docs/README.md +59 -11
- package/docs/_sidebar.md +1 -0
- package/docs/collections.md +30 -0
- package/docs/ebook-reader.md +75 -1
- package/docs/image-explorer.md +27 -1
- package/docs/server-setup.md +28 -18
- package/docs/stack-launcher.md +218 -0
- package/docs/ultravisor-integration.md +2 -0
- package/package.json +10 -7
- package/source/Pict-Application-RetoldRemote.js +2 -0
- package/source/RetoldRemote-ExtensionMaps.js +1 -1
- package/source/cli/RetoldRemote-Server-Setup.js +240 -2
- package/source/cli/RetoldRemote-Stack-Launcher.js +387 -0
- package/source/cli/RetoldRemote-Stack-Run.js +41 -0
- package/source/cli/commands/RetoldRemote-Command-Serve.js +129 -54
- package/source/providers/CollectionManager-AddItems.js +166 -0
- package/source/providers/Pict-Provider-GalleryNavigation.js +46 -0
- package/source/providers/keyboard-handlers/KeyHandler-ImageExplorer.js +5 -0
- package/source/providers/keyboard-handlers/KeyHandler-Viewer.js +23 -0
- package/source/server/RetoldRemote-CollectionExportService.js +696 -0
- package/source/server/RetoldRemote-CollectionService.js +5 -0
- package/source/server/RetoldRemote-EbookService.js +194 -3
- package/source/server/RetoldRemote-SubimageService.js +530 -0
- package/source/server/RetoldRemote-ToolDetector.js +50 -0
- package/source/server/RetoldRemote-UltravisorOperations.js +6 -6
- package/source/views/MediaViewer-EbookViewer.js +419 -1
- package/source/views/MediaViewer-PdfViewer.js +963 -0
- package/source/views/PictView-Remote-CollectionsPanel.js +166 -0
- package/source/views/PictView-Remote-ImageExplorer.js +606 -1
- package/source/views/PictView-Remote-ImageViewer.js +2 -2
- package/source/views/PictView-Remote-Layout.js +12 -0
- package/source/views/PictView-Remote-MediaViewer.js +83 -25
- package/source/views/PictView-Remote-SubimagesPanel.js +353 -0
- package/web-application/css/retold-remote.css +87 -20
- package/web-application/docs/README.md +59 -11
- package/web-application/docs/_sidebar.md +1 -0
- package/web-application/docs/collections.md +30 -0
- package/web-application/docs/ebook-reader.md +75 -1
- package/web-application/docs/image-explorer.md +27 -1
- package/web-application/docs/server-setup.md +28 -18
- package/web-application/docs/stack-launcher.md +218 -0
- package/web-application/docs/ultravisor-integration.md +2 -0
- package/web-application/retold-remote.js +399 -45
- package/web-application/retold-remote.js.map +1 -1
- package/web-application/retold-remote.min.js +13 -12
- package/web-application/retold-remote.min.js.map +1 -1
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* MediaViewer — Ebook Viewer Mixin
|
|
3
3
|
*
|
|
4
4
|
* EPUB/MOBI rendering using epub.js, table of contents,
|
|
5
|
-
* page navigation,
|
|
5
|
+
* page navigation, text selection capture, visual rectangle
|
|
6
|
+
* selection, and MOBI-to-EPUB server-side conversion.
|
|
6
7
|
*
|
|
7
8
|
* Mixed into RetoldRemoteMediaViewerView.prototype via Object.assign().
|
|
8
9
|
* All methods access state through `this` (the view instance).
|
|
@@ -33,6 +34,16 @@ module.exports =
|
|
|
33
34
|
+ '<button class="retold-remote-ebook-toc-btn" onclick="pict.views[\'RetoldRemote-MediaViewer\'].toggleEbookTOC()">☰ TOC</button>'
|
|
34
35
|
+ '<button class="retold-remote-ebook-page-btn" onclick="pict.views[\'RetoldRemote-MediaViewer\'].ebookPrevPage()">← Prev</button>'
|
|
35
36
|
+ '<button class="retold-remote-ebook-page-btn" onclick="pict.views[\'RetoldRemote-MediaViewer\'].ebookNextPage()">Next →</button>'
|
|
37
|
+
+ '<span style="flex:1;"></span>'
|
|
38
|
+
+ '<button class="retold-remote-ebook-page-btn" onclick="pict.views[\'RetoldRemote-MediaViewer\'].ebookSaveSelection()">💾 Save Selection</button>'
|
|
39
|
+
+ '<button class="retold-remote-ebook-page-btn" id="RetoldRemote-EbookRegionSelectBtn" onclick="pict.views[\'RetoldRemote-MediaViewer\'].ebookToggleRegionSelect()">✂ Select Region</button>'
|
|
40
|
+
+ '</div>'
|
|
41
|
+
+ '<div class="retold-remote-ebook-controls" id="RetoldRemote-EbookLabelInput" style="display:none;">'
|
|
42
|
+
+ '<input type="text" id="RetoldRemote-EbookLabelField" placeholder="Label..." '
|
|
43
|
+
+ 'style="flex:1; padding:4px 8px; background:var(--retold-bg-secondary, #2d2d2d); color:var(--retold-text-primary, #d4d4d4); border:1px solid var(--retold-border, #444); border-radius:4px;" '
|
|
44
|
+
+ 'onkeydown="if(event.key===\'Enter\'){pict.views[\'RetoldRemote-MediaViewer\'].ebookSaveLabel();}">'
|
|
45
|
+
+ '<button class="retold-remote-ebook-page-btn" onclick="pict.views[\'RetoldRemote-MediaViewer\'].ebookSaveLabel()">Save</button>'
|
|
46
|
+
+ '<button class="retold-remote-ebook-page-btn" onclick="pict.views[\'RetoldRemote-MediaViewer\'].ebookCancelSelection()">Cancel</button>'
|
|
36
47
|
+ '</div>'
|
|
37
48
|
+ '</div>'
|
|
38
49
|
+ '</div>';
|
|
@@ -288,5 +299,412 @@ module.exports =
|
|
|
288
299
|
{
|
|
289
300
|
tmpTocEl.classList.toggle('collapsed');
|
|
290
301
|
}
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Capture the current text selection from the epub.js rendition,
|
|
306
|
+
* derive a CFI, and show the label input for saving.
|
|
307
|
+
*/
|
|
308
|
+
ebookSaveSelection: function ebookSaveSelection()
|
|
309
|
+
{
|
|
310
|
+
let tmpSelf = this;
|
|
311
|
+
|
|
312
|
+
if (!this._activeRendition)
|
|
313
|
+
{
|
|
314
|
+
this.pict.providers['RetoldRemote-ToastNotification'].showToast('No ebook loaded.');
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
let tmpContents = this._activeRendition.getContents();
|
|
319
|
+
if (!tmpContents || tmpContents.length < 1)
|
|
320
|
+
{
|
|
321
|
+
this.pict.providers['RetoldRemote-ToastNotification'].showToast('Unable to access ebook contents.');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
let tmpDoc = tmpContents[0].document;
|
|
326
|
+
let tmpSelection = tmpDoc.getSelection();
|
|
327
|
+
let tmpSelectedText = tmpSelection ? tmpSelection.toString() : '';
|
|
328
|
+
|
|
329
|
+
if (!tmpSelectedText || tmpSelectedText.trim().length === 0)
|
|
330
|
+
{
|
|
331
|
+
this.pict.providers['RetoldRemote-ToastNotification'].showToast('Select text first.');
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Derive the CFI from the selection range
|
|
336
|
+
let tmpCFI = '';
|
|
337
|
+
try
|
|
338
|
+
{
|
|
339
|
+
let tmpRange = tmpSelection.getRangeAt(0);
|
|
340
|
+
tmpCFI = tmpContents[0].cfiFromRange(tmpRange);
|
|
341
|
+
}
|
|
342
|
+
catch (pError)
|
|
343
|
+
{
|
|
344
|
+
this.pict.log.warn('Could not derive CFI from selection: ' + pError.message);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Get current location for spine index
|
|
348
|
+
let tmpLocation = this._activeRendition.currentLocation();
|
|
349
|
+
let tmpSpineIndex = (tmpLocation && tmpLocation.start) ? tmpLocation.start.index : -1;
|
|
350
|
+
|
|
351
|
+
// Try to find the chapter title from the TOC
|
|
352
|
+
let tmpChapterTitle = '';
|
|
353
|
+
try
|
|
354
|
+
{
|
|
355
|
+
let tmpTocItems = document.querySelectorAll('#RetoldRemote-EbookTOCItems .retold-remote-ebook-toc-item');
|
|
356
|
+
if (tmpTocItems.length > 0 && tmpSpineIndex >= 0)
|
|
357
|
+
{
|
|
358
|
+
// Best effort: use the TOC item closest to the spine index
|
|
359
|
+
let tmpTocIndex = Math.min(tmpSpineIndex, tmpTocItems.length - 1);
|
|
360
|
+
tmpChapterTitle = tmpTocItems[tmpTocIndex].textContent.trim();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch (pError)
|
|
364
|
+
{
|
|
365
|
+
// Chapter title is best-effort; ignore errors
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Store pending selection data on the view instance
|
|
369
|
+
this._pendingEbookSelection =
|
|
370
|
+
{
|
|
371
|
+
Type: 'text-selection',
|
|
372
|
+
CFI: tmpCFI,
|
|
373
|
+
SpineIndex: tmpSpineIndex,
|
|
374
|
+
ChapterTitle: tmpChapterTitle,
|
|
375
|
+
SelectedText: tmpSelectedText
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// Show the label input
|
|
379
|
+
let tmpLabelInput = document.getElementById('RetoldRemote-EbookLabelInput');
|
|
380
|
+
if (tmpLabelInput)
|
|
381
|
+
{
|
|
382
|
+
tmpLabelInput.style.display = '';
|
|
383
|
+
}
|
|
384
|
+
let tmpLabelField = document.getElementById('RetoldRemote-EbookLabelField');
|
|
385
|
+
if (tmpLabelField)
|
|
386
|
+
{
|
|
387
|
+
tmpLabelField.value = '';
|
|
388
|
+
tmpLabelField.focus();
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Toggle visual rectangle selection mode over the ebook content area.
|
|
394
|
+
* When enabled, an overlay captures mouse events to draw a rectangle.
|
|
395
|
+
*/
|
|
396
|
+
ebookToggleRegionSelect: function ebookToggleRegionSelect()
|
|
397
|
+
{
|
|
398
|
+
let tmpSelf = this;
|
|
399
|
+
let tmpContentEl = document.getElementById('RetoldRemote-EbookContent');
|
|
400
|
+
let tmpToggleBtn = document.getElementById('RetoldRemote-EbookRegionSelectBtn');
|
|
401
|
+
|
|
402
|
+
if (!tmpContentEl)
|
|
403
|
+
{
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// If overlay already exists, remove it (toggle off)
|
|
408
|
+
let tmpExistingOverlay = document.getElementById('RetoldRemote-EbookRegionOverlay');
|
|
409
|
+
if (tmpExistingOverlay)
|
|
410
|
+
{
|
|
411
|
+
tmpExistingOverlay.remove();
|
|
412
|
+
if (tmpToggleBtn)
|
|
413
|
+
{
|
|
414
|
+
tmpToggleBtn.style.background = '';
|
|
415
|
+
}
|
|
416
|
+
this._ebookRegionActive = false;
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
this._ebookRegionActive = true;
|
|
421
|
+
if (tmpToggleBtn)
|
|
422
|
+
{
|
|
423
|
+
tmpToggleBtn.style.background = 'var(--retold-accent, #569cd6)';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Create a transparent overlay div
|
|
427
|
+
let tmpOverlay = document.createElement('div');
|
|
428
|
+
tmpOverlay.id = 'RetoldRemote-EbookRegionOverlay';
|
|
429
|
+
tmpOverlay.style.cssText = 'position:absolute; top:0; left:0; width:100%; height:100%; '
|
|
430
|
+
+ 'cursor:crosshair; z-index:100; user-select:none;';
|
|
431
|
+
tmpContentEl.style.position = 'relative';
|
|
432
|
+
tmpContentEl.appendChild(tmpOverlay);
|
|
433
|
+
|
|
434
|
+
let tmpDrawing = false;
|
|
435
|
+
let tmpStartX = 0;
|
|
436
|
+
let tmpStartY = 0;
|
|
437
|
+
let tmpRectEl = null;
|
|
438
|
+
|
|
439
|
+
tmpOverlay.addEventListener('mousedown', function (pEvent)
|
|
440
|
+
{
|
|
441
|
+
pEvent.preventDefault();
|
|
442
|
+
pEvent.stopPropagation();
|
|
443
|
+
|
|
444
|
+
// Remove any previous rectangle
|
|
445
|
+
let tmpOldRect = document.getElementById('RetoldRemote-EbookRegionRect');
|
|
446
|
+
if (tmpOldRect)
|
|
447
|
+
{
|
|
448
|
+
tmpOldRect.remove();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
let tmpBounds = tmpOverlay.getBoundingClientRect();
|
|
452
|
+
tmpStartX = pEvent.clientX - tmpBounds.left;
|
|
453
|
+
tmpStartY = pEvent.clientY - tmpBounds.top;
|
|
454
|
+
tmpDrawing = true;
|
|
455
|
+
|
|
456
|
+
tmpRectEl = document.createElement('div');
|
|
457
|
+
tmpRectEl.id = 'RetoldRemote-EbookRegionRect';
|
|
458
|
+
tmpRectEl.style.cssText = 'position:absolute; border:2px dashed var(--retold-accent, #569cd6); '
|
|
459
|
+
+ 'background:rgba(86, 156, 214, 0.15); pointer-events:none;';
|
|
460
|
+
tmpRectEl.style.left = tmpStartX + 'px';
|
|
461
|
+
tmpRectEl.style.top = tmpStartY + 'px';
|
|
462
|
+
tmpRectEl.style.width = '0px';
|
|
463
|
+
tmpRectEl.style.height = '0px';
|
|
464
|
+
tmpOverlay.appendChild(tmpRectEl);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
tmpOverlay.addEventListener('mousemove', function (pEvent)
|
|
468
|
+
{
|
|
469
|
+
if (!tmpDrawing || !tmpRectEl)
|
|
470
|
+
{
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
pEvent.preventDefault();
|
|
474
|
+
|
|
475
|
+
let tmpBounds = tmpOverlay.getBoundingClientRect();
|
|
476
|
+
let tmpCurrentX = pEvent.clientX - tmpBounds.left;
|
|
477
|
+
let tmpCurrentY = pEvent.clientY - tmpBounds.top;
|
|
478
|
+
|
|
479
|
+
let tmpLeft = Math.min(tmpStartX, tmpCurrentX);
|
|
480
|
+
let tmpTop = Math.min(tmpStartY, tmpCurrentY);
|
|
481
|
+
let tmpWidth = Math.abs(tmpCurrentX - tmpStartX);
|
|
482
|
+
let tmpHeight = Math.abs(tmpCurrentY - tmpStartY);
|
|
483
|
+
|
|
484
|
+
tmpRectEl.style.left = tmpLeft + 'px';
|
|
485
|
+
tmpRectEl.style.top = tmpTop + 'px';
|
|
486
|
+
tmpRectEl.style.width = tmpWidth + 'px';
|
|
487
|
+
tmpRectEl.style.height = tmpHeight + 'px';
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
tmpOverlay.addEventListener('mouseup', function (pEvent)
|
|
491
|
+
{
|
|
492
|
+
if (!tmpDrawing || !tmpRectEl)
|
|
493
|
+
{
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
pEvent.preventDefault();
|
|
497
|
+
tmpDrawing = false;
|
|
498
|
+
|
|
499
|
+
let tmpBounds = tmpOverlay.getBoundingClientRect();
|
|
500
|
+
let tmpEndX = pEvent.clientX - tmpBounds.left;
|
|
501
|
+
let tmpEndY = pEvent.clientY - tmpBounds.top;
|
|
502
|
+
|
|
503
|
+
let tmpRegionLeft = Math.min(tmpStartX, tmpEndX);
|
|
504
|
+
let tmpRegionTop = Math.min(tmpStartY, tmpEndY);
|
|
505
|
+
let tmpRegionWidth = Math.abs(tmpEndX - tmpStartX);
|
|
506
|
+
let tmpRegionHeight = Math.abs(tmpEndY - tmpStartY);
|
|
507
|
+
|
|
508
|
+
// Ignore tiny accidental drags
|
|
509
|
+
if (tmpRegionWidth < 5 || tmpRegionHeight < 5)
|
|
510
|
+
{
|
|
511
|
+
if (tmpRectEl)
|
|
512
|
+
{
|
|
513
|
+
tmpRectEl.remove();
|
|
514
|
+
tmpRectEl = null;
|
|
515
|
+
}
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
let tmpViewportWidth = tmpOverlay.offsetWidth;
|
|
520
|
+
let tmpViewportHeight = tmpOverlay.offsetHeight;
|
|
521
|
+
|
|
522
|
+
// Best-effort text extraction from the rectangle area
|
|
523
|
+
let tmpExtractedText = '';
|
|
524
|
+
try
|
|
525
|
+
{
|
|
526
|
+
let tmpContents = tmpSelf._activeRendition.getContents();
|
|
527
|
+
if (tmpContents && tmpContents.length > 0)
|
|
528
|
+
{
|
|
529
|
+
let tmpDoc = tmpContents[0].document;
|
|
530
|
+
let tmpBody = tmpDoc.body;
|
|
531
|
+
if (tmpBody)
|
|
532
|
+
{
|
|
533
|
+
// Walk text nodes and check if any fall within the rectangle
|
|
534
|
+
let tmpTreeWalker = tmpDoc.createTreeWalker(tmpBody, NodeFilter.SHOW_TEXT, null, false);
|
|
535
|
+
let tmpTextParts = [];
|
|
536
|
+
let tmpNode;
|
|
537
|
+
|
|
538
|
+
while ((tmpNode = tmpTreeWalker.nextNode()))
|
|
539
|
+
{
|
|
540
|
+
if (!tmpNode.textContent || tmpNode.textContent.trim().length === 0)
|
|
541
|
+
{
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
let tmpRange = tmpDoc.createRange();
|
|
545
|
+
tmpRange.selectNodeContents(tmpNode);
|
|
546
|
+
let tmpRects = tmpRange.getClientRects();
|
|
547
|
+
for (let i = 0; i < tmpRects.length; i++)
|
|
548
|
+
{
|
|
549
|
+
let tmpR = tmpRects[i];
|
|
550
|
+
// Check overlap with the drawn rectangle
|
|
551
|
+
if (tmpR.right >= tmpRegionLeft && tmpR.left <= (tmpRegionLeft + tmpRegionWidth)
|
|
552
|
+
&& tmpR.bottom >= tmpRegionTop && tmpR.top <= (tmpRegionTop + tmpRegionHeight))
|
|
553
|
+
{
|
|
554
|
+
tmpTextParts.push(tmpNode.textContent.trim());
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
tmpExtractedText = tmpTextParts.join(' ');
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
catch (pError)
|
|
564
|
+
{
|
|
565
|
+
// Text extraction from region is best-effort
|
|
566
|
+
tmpSelf.pict.log.warn('Region text extraction failed: ' + pError.message);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Get current location for spine index
|
|
570
|
+
let tmpLocation = tmpSelf._activeRendition.currentLocation();
|
|
571
|
+
let tmpSpineIndex = (tmpLocation && tmpLocation.start) ? tmpLocation.start.index : -1;
|
|
572
|
+
|
|
573
|
+
// Store pending selection data
|
|
574
|
+
tmpSelf._pendingEbookSelection =
|
|
575
|
+
{
|
|
576
|
+
Type: 'visual-region',
|
|
577
|
+
X: Math.round(tmpRegionLeft),
|
|
578
|
+
Y: Math.round(tmpRegionTop),
|
|
579
|
+
Width: Math.round(tmpRegionWidth),
|
|
580
|
+
Height: Math.round(tmpRegionHeight),
|
|
581
|
+
ViewportWidth: tmpViewportWidth,
|
|
582
|
+
ViewportHeight: tmpViewportHeight,
|
|
583
|
+
SpineIndex: tmpSpineIndex,
|
|
584
|
+
SelectedText: tmpExtractedText
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// Show the label input
|
|
588
|
+
let tmpLabelInput = document.getElementById('RetoldRemote-EbookLabelInput');
|
|
589
|
+
if (tmpLabelInput)
|
|
590
|
+
{
|
|
591
|
+
tmpLabelInput.style.display = '';
|
|
592
|
+
}
|
|
593
|
+
let tmpLabelField = document.getElementById('RetoldRemote-EbookLabelField');
|
|
594
|
+
if (tmpLabelField)
|
|
595
|
+
{
|
|
596
|
+
tmpLabelField.value = '';
|
|
597
|
+
tmpLabelField.focus();
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Cancel any in-progress selection or region, hide the label input,
|
|
604
|
+
* and remove the region overlay if present.
|
|
605
|
+
*/
|
|
606
|
+
ebookCancelSelection: function ebookCancelSelection()
|
|
607
|
+
{
|
|
608
|
+
// Hide the label input
|
|
609
|
+
let tmpLabelInput = document.getElementById('RetoldRemote-EbookLabelInput');
|
|
610
|
+
if (tmpLabelInput)
|
|
611
|
+
{
|
|
612
|
+
tmpLabelInput.style.display = 'none';
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Clear the label field
|
|
616
|
+
let tmpLabelField = document.getElementById('RetoldRemote-EbookLabelField');
|
|
617
|
+
if (tmpLabelField)
|
|
618
|
+
{
|
|
619
|
+
tmpLabelField.value = '';
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Remove region overlay and rectangle if present
|
|
623
|
+
let tmpRect = document.getElementById('RetoldRemote-EbookRegionRect');
|
|
624
|
+
if (tmpRect)
|
|
625
|
+
{
|
|
626
|
+
tmpRect.remove();
|
|
627
|
+
}
|
|
628
|
+
let tmpOverlay = document.getElementById('RetoldRemote-EbookRegionOverlay');
|
|
629
|
+
if (tmpOverlay)
|
|
630
|
+
{
|
|
631
|
+
tmpOverlay.remove();
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Reset toggle button style
|
|
635
|
+
let tmpToggleBtn = document.getElementById('RetoldRemote-EbookRegionSelectBtn');
|
|
636
|
+
if (tmpToggleBtn)
|
|
637
|
+
{
|
|
638
|
+
tmpToggleBtn.style.background = '';
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
this._ebookRegionActive = false;
|
|
642
|
+
this._pendingEbookSelection = null;
|
|
643
|
+
},
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Save the pending selection with the label from the input field.
|
|
647
|
+
* POSTs to /api/media/subimage-regions and updates the sidebar.
|
|
648
|
+
*/
|
|
649
|
+
ebookSaveLabel: function ebookSaveLabel()
|
|
650
|
+
{
|
|
651
|
+
let tmpSelf = this;
|
|
652
|
+
|
|
653
|
+
if (!this._pendingEbookSelection)
|
|
654
|
+
{
|
|
655
|
+
this.pict.providers['RetoldRemote-ToastNotification'].showToast('No selection to save.');
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
let tmpLabelField = document.getElementById('RetoldRemote-EbookLabelField');
|
|
660
|
+
let tmpLabelValue = tmpLabelField ? tmpLabelField.value.trim() : '';
|
|
661
|
+
|
|
662
|
+
if (!tmpLabelValue)
|
|
663
|
+
{
|
|
664
|
+
this.pict.providers['RetoldRemote-ToastNotification'].showToast('Enter a label for the selection.');
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
let tmpRegion = this._pendingEbookSelection;
|
|
669
|
+
tmpRegion.Label = tmpLabelValue;
|
|
670
|
+
|
|
671
|
+
let tmpPayload =
|
|
672
|
+
{
|
|
673
|
+
Path: this.pict.AppData.RetoldRemote.CurrentViewerFile,
|
|
674
|
+
Region: tmpRegion
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
fetch('/api/media/subimage-regions',
|
|
678
|
+
{
|
|
679
|
+
method: 'POST',
|
|
680
|
+
headers: { 'Content-Type': 'application/json' },
|
|
681
|
+
body: JSON.stringify(tmpPayload)
|
|
682
|
+
})
|
|
683
|
+
.then((pResponse) =>
|
|
684
|
+
{
|
|
685
|
+
if (!pResponse.ok)
|
|
686
|
+
{
|
|
687
|
+
throw new Error('HTTP ' + pResponse.status);
|
|
688
|
+
}
|
|
689
|
+
return pResponse.json();
|
|
690
|
+
})
|
|
691
|
+
.then((pData) =>
|
|
692
|
+
{
|
|
693
|
+
tmpSelf.pict.providers['RetoldRemote-ToastNotification'].showToast('Selection saved: ' + tmpLabelValue);
|
|
694
|
+
|
|
695
|
+
// Clean up the selection state and UI
|
|
696
|
+
tmpSelf.ebookCancelSelection();
|
|
697
|
+
|
|
698
|
+
// Refresh the sidebar panel if a regions panel method exists
|
|
699
|
+
if (typeof (tmpSelf.refreshSubimageRegions) === 'function')
|
|
700
|
+
{
|
|
701
|
+
tmpSelf.refreshSubimageRegions();
|
|
702
|
+
}
|
|
703
|
+
})
|
|
704
|
+
.catch((pError) =>
|
|
705
|
+
{
|
|
706
|
+
tmpSelf.pict.providers['RetoldRemote-ToastNotification'].showToast('Failed to save selection: ' + pError.message);
|
|
707
|
+
tmpSelf.pict.log.error('Ebook selection save error: ' + pError.message);
|
|
708
|
+
});
|
|
291
709
|
}
|
|
292
710
|
};
|