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
|
@@ -23,6 +23,14 @@ class RetoldRemoteImageExplorerView extends libPictView
|
|
|
23
23
|
this._dziData = null;
|
|
24
24
|
this._osdLoaded = false;
|
|
25
25
|
this._loading = false;
|
|
26
|
+
|
|
27
|
+
// Selection mode state
|
|
28
|
+
this._selectionMode = false;
|
|
29
|
+
this._selectionTracker = null;
|
|
30
|
+
this._selectionOverlay = null;
|
|
31
|
+
this._selectionRegion = null; // { X, Y, Width, Height } in image coords
|
|
32
|
+
this._selectionStart = null; // viewport point where drag began
|
|
33
|
+
this._savedRegions = []; // loaded from server
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
/**
|
|
@@ -40,6 +48,14 @@ class RetoldRemoteImageExplorerView extends libPictView
|
|
|
40
48
|
this._dziData = null;
|
|
41
49
|
this._loading = false;
|
|
42
50
|
|
|
51
|
+
// Reset selection state
|
|
52
|
+
this._selectionMode = false;
|
|
53
|
+
this._selectionTracker = null;
|
|
54
|
+
this._selectionOverlay = null;
|
|
55
|
+
this._selectionRegion = null;
|
|
56
|
+
this._selectionStart = null;
|
|
57
|
+
this._savedRegions = [];
|
|
58
|
+
|
|
43
59
|
// Clean up existing viewer
|
|
44
60
|
if (this._osdViewer)
|
|
45
61
|
{
|
|
@@ -89,6 +105,7 @@ class RetoldRemoteImageExplorerView extends libPictView
|
|
|
89
105
|
tmpHTML += '<button class="retold-remote-iex-nav-btn" onclick="pict.views[\'RetoldRemote-ImageExplorer\'].goBack()" title="Back (Esc)">← Back</button>';
|
|
90
106
|
tmpHTML += '<div class="retold-remote-iex-title">Image Explorer — ' + tmpFmt.escapeHTML(tmpFileName) + '</div>';
|
|
91
107
|
tmpHTML += '<div class="retold-remote-iex-actions">';
|
|
108
|
+
tmpHTML += '<button class="retold-remote-iex-action-btn" id="RetoldRemote-IEX-SelectBtn" onclick="pict.views[\'RetoldRemote-ImageExplorer\'].toggleSelectionMode()" title="Select a region (s)">✂ Select</button>';
|
|
92
109
|
tmpHTML += '<button class="retold-remote-iex-action-btn" onclick="pict.views[\'RetoldRemote-ImageExplorer\'].viewInBrowser()" title="View in standard viewer">🖼 View</button>';
|
|
93
110
|
tmpHTML += '</div>';
|
|
94
111
|
tmpHTML += '</div>';
|
|
@@ -111,6 +128,11 @@ class RetoldRemoteImageExplorerView extends libPictView
|
|
|
111
128
|
tmpHTML += '<button onclick="pict.views[\'RetoldRemote-ImageExplorer\'].zoomOut()" title="Zoom Out (-)">- Zoom Out</button>';
|
|
112
129
|
tmpHTML += '<button onclick="pict.views[\'RetoldRemote-ImageExplorer\'].zoomHome()" title="Fit to view (0)">Fit</button>';
|
|
113
130
|
tmpHTML += '<span style="flex:1;"></span>';
|
|
131
|
+
tmpHTML += '<span id="RetoldRemote-IEX-LabelInput" style="display:none;">';
|
|
132
|
+
tmpHTML += '<input type="text" id="RetoldRemote-IEX-LabelField" placeholder="Label this region\u2026" style="background:var(--retold-bg-input,#1e1e1e);color:var(--retold-text,#abb2bf);border:1px solid var(--retold-border,#3e4451);border-radius:4px;padding:2px 8px;font-size:0.78rem;width:180px;margin-right:4px;" onkeydown="if(event.key===\'Enter\'){pict.views[\'RetoldRemote-ImageExplorer\'].saveSelectionLabel();event.preventDefault();event.stopPropagation();}if(event.key===\'Escape\'){pict.views[\'RetoldRemote-ImageExplorer\'].cancelSelection();event.preventDefault();event.stopPropagation();}">';
|
|
133
|
+
tmpHTML += '<button onclick="pict.views[\'RetoldRemote-ImageExplorer\'].saveSelectionLabel()" style="font-size:0.75rem;padding:2px 8px;">Save</button>';
|
|
134
|
+
tmpHTML += '<button onclick="pict.views[\'RetoldRemote-ImageExplorer\'].cancelSelection()" style="font-size:0.75rem;padding:2px 8px;margin-left:2px;">Cancel</button>';
|
|
135
|
+
tmpHTML += '</span>';
|
|
114
136
|
tmpHTML += '<span id="RetoldRemote-IEX-Coords" style="color:var(--retold-text-dim);font-size:0.72rem;"></span>';
|
|
115
137
|
tmpHTML += '</div>';
|
|
116
138
|
|
|
@@ -129,9 +151,11 @@ class RetoldRemoteImageExplorerView extends libPictView
|
|
|
129
151
|
}
|
|
130
152
|
|
|
131
153
|
// Load OpenSeadragon, then decide whether to use simple image or DZI tiles
|
|
154
|
+
let tmpSelfShow = this;
|
|
132
155
|
this._ensureOSDLoaded(() =>
|
|
133
156
|
{
|
|
134
|
-
|
|
157
|
+
tmpSelfShow._probeAndShow(pFilePath);
|
|
158
|
+
tmpSelfShow._loadSavedRegions(pFilePath);
|
|
135
159
|
});
|
|
136
160
|
}
|
|
137
161
|
|
|
@@ -736,11 +760,592 @@ class RetoldRemoteImageExplorerView extends libPictView
|
|
|
736
760
|
}
|
|
737
761
|
}
|
|
738
762
|
|
|
763
|
+
// -----------------------------------------------------------------
|
|
764
|
+
// Selection mode — draw rectangles to create labeled subimage regions
|
|
765
|
+
// -----------------------------------------------------------------
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Toggle selection mode on/off.
|
|
769
|
+
*/
|
|
770
|
+
toggleSelectionMode()
|
|
771
|
+
{
|
|
772
|
+
if (this._selectionMode)
|
|
773
|
+
{
|
|
774
|
+
this._exitSelectionMode();
|
|
775
|
+
}
|
|
776
|
+
else
|
|
777
|
+
{
|
|
778
|
+
this._enterSelectionMode();
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Enter selection mode: disable panning, install drag tracker.
|
|
784
|
+
*/
|
|
785
|
+
_enterSelectionMode()
|
|
786
|
+
{
|
|
787
|
+
if (!this._osdViewer)
|
|
788
|
+
{
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
this._selectionMode = true;
|
|
793
|
+
|
|
794
|
+
// Highlight the Select button
|
|
795
|
+
let tmpBtn = document.getElementById('RetoldRemote-IEX-SelectBtn');
|
|
796
|
+
if (tmpBtn)
|
|
797
|
+
{
|
|
798
|
+
tmpBtn.style.background = 'rgba(97, 175, 239, 0.4)';
|
|
799
|
+
tmpBtn.style.color = '#fff';
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Disable OSD panning so drag draws a selection instead
|
|
803
|
+
this._osdViewer.setMouseNavEnabled(false);
|
|
804
|
+
|
|
805
|
+
let tmpSelf = this;
|
|
806
|
+
let tmpViewerDiv = document.getElementById('RetoldRemote-IEX-Viewer');
|
|
807
|
+
if (!tmpViewerDiv)
|
|
808
|
+
{
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
tmpViewerDiv.style.cursor = 'crosshair';
|
|
812
|
+
|
|
813
|
+
this._selectionTracker = new OpenSeadragon.MouseTracker(
|
|
814
|
+
{
|
|
815
|
+
element: tmpViewerDiv,
|
|
816
|
+
pressHandler: function (pEvent)
|
|
817
|
+
{
|
|
818
|
+
tmpSelf._onSelectionPress(pEvent);
|
|
819
|
+
},
|
|
820
|
+
dragHandler: function (pEvent)
|
|
821
|
+
{
|
|
822
|
+
tmpSelf._onSelectionDrag(pEvent);
|
|
823
|
+
},
|
|
824
|
+
releaseHandler: function (pEvent)
|
|
825
|
+
{
|
|
826
|
+
tmpSelf._onSelectionRelease(pEvent);
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Exit selection mode: re-enable panning, remove drag tracker.
|
|
833
|
+
*/
|
|
834
|
+
_exitSelectionMode()
|
|
835
|
+
{
|
|
836
|
+
this._selectionMode = false;
|
|
837
|
+
|
|
838
|
+
let tmpBtn = document.getElementById('RetoldRemote-IEX-SelectBtn');
|
|
839
|
+
if (tmpBtn)
|
|
840
|
+
{
|
|
841
|
+
tmpBtn.style.background = '';
|
|
842
|
+
tmpBtn.style.color = '';
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
if (this._osdViewer)
|
|
846
|
+
{
|
|
847
|
+
this._osdViewer.setMouseNavEnabled(true);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (this._selectionTracker)
|
|
851
|
+
{
|
|
852
|
+
this._selectionTracker.destroy();
|
|
853
|
+
this._selectionTracker = null;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
let tmpViewerDiv = document.getElementById('RetoldRemote-IEX-Viewer');
|
|
857
|
+
if (tmpViewerDiv)
|
|
858
|
+
{
|
|
859
|
+
tmpViewerDiv.style.cursor = '';
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Remove in-progress selection overlay (keep saved ones)
|
|
863
|
+
this._removeActiveSelectionOverlay();
|
|
864
|
+
this._selectionRegion = null;
|
|
865
|
+
this._selectionStart = null;
|
|
866
|
+
|
|
867
|
+
// Hide label input
|
|
868
|
+
let tmpLabelWrap = document.getElementById('RetoldRemote-IEX-LabelInput');
|
|
869
|
+
if (tmpLabelWrap)
|
|
870
|
+
{
|
|
871
|
+
tmpLabelWrap.style.display = 'none';
|
|
872
|
+
}
|
|
873
|
+
let tmpCoords = document.getElementById('RetoldRemote-IEX-Coords');
|
|
874
|
+
if (tmpCoords)
|
|
875
|
+
{
|
|
876
|
+
tmpCoords.style.display = '';
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Handle the start of a selection drag.
|
|
882
|
+
*/
|
|
883
|
+
_onSelectionPress(pEvent)
|
|
884
|
+
{
|
|
885
|
+
if (!this._osdViewer)
|
|
886
|
+
{
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Remove any previous in-progress selection overlay
|
|
891
|
+
this._removeActiveSelectionOverlay();
|
|
892
|
+
|
|
893
|
+
this._selectionStart = this._osdViewer.viewport.pointFromPixel(pEvent.position);
|
|
894
|
+
|
|
895
|
+
// Create the selection rectangle overlay element
|
|
896
|
+
let tmpOverlay = document.createElement('div');
|
|
897
|
+
tmpOverlay.id = 'RetoldRemote-IEX-ActiveSelection';
|
|
898
|
+
tmpOverlay.style.cssText = 'border: 2px solid rgba(97, 175, 239, 0.9); background: rgba(97, 175, 239, 0.15); pointer-events: none;';
|
|
899
|
+
this._selectionOverlay = tmpOverlay;
|
|
900
|
+
|
|
901
|
+
// Add the overlay at zero size, will expand during drag
|
|
902
|
+
this._osdViewer.addOverlay(
|
|
903
|
+
{
|
|
904
|
+
element: tmpOverlay,
|
|
905
|
+
location: new OpenSeadragon.Rect(
|
|
906
|
+
this._selectionStart.x, this._selectionStart.y, 0, 0)
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Handle selection dragging — update the rectangle size.
|
|
912
|
+
*/
|
|
913
|
+
_onSelectionDrag(pEvent)
|
|
914
|
+
{
|
|
915
|
+
if (!this._osdViewer || !this._selectionStart || !this._selectionOverlay)
|
|
916
|
+
{
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
let tmpCurrent = this._osdViewer.viewport.pointFromPixel(pEvent.position);
|
|
921
|
+
let tmpX = Math.min(this._selectionStart.x, tmpCurrent.x);
|
|
922
|
+
let tmpY = Math.min(this._selectionStart.y, tmpCurrent.y);
|
|
923
|
+
let tmpW = Math.abs(tmpCurrent.x - this._selectionStart.x);
|
|
924
|
+
let tmpH = Math.abs(tmpCurrent.y - this._selectionStart.y);
|
|
925
|
+
|
|
926
|
+
this._osdViewer.updateOverlay(
|
|
927
|
+
this._selectionOverlay,
|
|
928
|
+
new OpenSeadragon.Rect(tmpX, tmpY, tmpW, tmpH));
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Handle selection release — compute image-coordinate region and show label input.
|
|
933
|
+
*/
|
|
934
|
+
_onSelectionRelease(pEvent)
|
|
935
|
+
{
|
|
936
|
+
if (!this._osdViewer || !this._selectionStart)
|
|
937
|
+
{
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
let tmpEnd = this._osdViewer.viewport.pointFromPixel(pEvent.position);
|
|
942
|
+
|
|
943
|
+
// Convert viewport rectangle to image pixel coordinates
|
|
944
|
+
let tmpVpX = Math.min(this._selectionStart.x, tmpEnd.x);
|
|
945
|
+
let tmpVpY = Math.min(this._selectionStart.y, tmpEnd.y);
|
|
946
|
+
let tmpVpW = Math.abs(tmpEnd.x - this._selectionStart.x);
|
|
947
|
+
let tmpVpH = Math.abs(tmpEnd.y - this._selectionStart.y);
|
|
948
|
+
|
|
949
|
+
let tmpTopLeft = this._osdViewer.viewport.viewportToImageCoordinates(
|
|
950
|
+
new OpenSeadragon.Point(tmpVpX, tmpVpY));
|
|
951
|
+
let tmpBottomRight = this._osdViewer.viewport.viewportToImageCoordinates(
|
|
952
|
+
new OpenSeadragon.Point(tmpVpX + tmpVpW, tmpVpY + tmpVpH));
|
|
953
|
+
|
|
954
|
+
let tmpRegion =
|
|
955
|
+
{
|
|
956
|
+
X: Math.max(0, Math.round(tmpTopLeft.x)),
|
|
957
|
+
Y: Math.max(0, Math.round(tmpTopLeft.y)),
|
|
958
|
+
Width: Math.round(tmpBottomRight.x - tmpTopLeft.x),
|
|
959
|
+
Height: Math.round(tmpBottomRight.y - tmpTopLeft.y)
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
// Clamp to image dimensions
|
|
963
|
+
if (this._dziData)
|
|
964
|
+
{
|
|
965
|
+
if (tmpRegion.X + tmpRegion.Width > this._dziData.Width)
|
|
966
|
+
{
|
|
967
|
+
tmpRegion.Width = this._dziData.Width - tmpRegion.X;
|
|
968
|
+
}
|
|
969
|
+
if (tmpRegion.Y + tmpRegion.Height > this._dziData.Height)
|
|
970
|
+
{
|
|
971
|
+
tmpRegion.Height = this._dziData.Height - tmpRegion.Y;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Ignore tiny selections (likely accidental clicks)
|
|
976
|
+
if (tmpRegion.Width < 5 || tmpRegion.Height < 5)
|
|
977
|
+
{
|
|
978
|
+
this._removeActiveSelectionOverlay();
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
this._selectionRegion = tmpRegion;
|
|
983
|
+
|
|
984
|
+
// Show the inline label input in the controls bar
|
|
985
|
+
let tmpLabelWrap = document.getElementById('RetoldRemote-IEX-LabelInput');
|
|
986
|
+
let tmpCoords = document.getElementById('RetoldRemote-IEX-Coords');
|
|
987
|
+
if (tmpLabelWrap)
|
|
988
|
+
{
|
|
989
|
+
tmpLabelWrap.style.display = '';
|
|
990
|
+
}
|
|
991
|
+
if (tmpCoords)
|
|
992
|
+
{
|
|
993
|
+
tmpCoords.style.display = 'none';
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Focus the label field
|
|
997
|
+
let tmpField = document.getElementById('RetoldRemote-IEX-LabelField');
|
|
998
|
+
if (tmpField)
|
|
999
|
+
{
|
|
1000
|
+
tmpField.value = '';
|
|
1001
|
+
tmpField.focus();
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Save the current selection with the entered label.
|
|
1007
|
+
*/
|
|
1008
|
+
saveSelectionLabel()
|
|
1009
|
+
{
|
|
1010
|
+
if (!this._selectionRegion)
|
|
1011
|
+
{
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
let tmpField = document.getElementById('RetoldRemote-IEX-LabelField');
|
|
1016
|
+
let tmpLabel = tmpField ? tmpField.value.trim() : '';
|
|
1017
|
+
|
|
1018
|
+
let tmpSelf = this;
|
|
1019
|
+
let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
|
|
1020
|
+
let tmpPathParam = tmpProvider ? tmpProvider._getPathParam(this._currentPath) : encodeURIComponent(this._currentPath);
|
|
1021
|
+
|
|
1022
|
+
fetch('/api/media/subimage-regions',
|
|
1023
|
+
{
|
|
1024
|
+
method: 'POST',
|
|
1025
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1026
|
+
body: JSON.stringify(
|
|
1027
|
+
{
|
|
1028
|
+
Path: this._currentPath,
|
|
1029
|
+
Region:
|
|
1030
|
+
{
|
|
1031
|
+
Label: tmpLabel,
|
|
1032
|
+
X: this._selectionRegion.X,
|
|
1033
|
+
Y: this._selectionRegion.Y,
|
|
1034
|
+
Width: this._selectionRegion.Width,
|
|
1035
|
+
Height: this._selectionRegion.Height
|
|
1036
|
+
}
|
|
1037
|
+
})
|
|
1038
|
+
})
|
|
1039
|
+
.then((pResponse) => pResponse.json())
|
|
1040
|
+
.then((pResult) =>
|
|
1041
|
+
{
|
|
1042
|
+
if (pResult && pResult.Success)
|
|
1043
|
+
{
|
|
1044
|
+
tmpSelf._savedRegions = pResult.Regions || [];
|
|
1045
|
+
|
|
1046
|
+
// Remove the active selection overlay and render persistent ones
|
|
1047
|
+
tmpSelf._removeActiveSelectionOverlay();
|
|
1048
|
+
tmpSelf._renderSavedRegionOverlays();
|
|
1049
|
+
|
|
1050
|
+
// Notify the user
|
|
1051
|
+
let tmpToast = tmpSelf.pict.providers['RetoldRemote-ToastNotification'];
|
|
1052
|
+
if (tmpToast)
|
|
1053
|
+
{
|
|
1054
|
+
tmpToast.showToast('Subimage region saved' + (tmpLabel ? ': ' + tmpLabel : ''));
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// Update the sidebar panel if visible
|
|
1058
|
+
let tmpSubPanel = tmpSelf.pict.views['RetoldRemote-SubimagesPanel'];
|
|
1059
|
+
if (tmpSubPanel)
|
|
1060
|
+
{
|
|
1061
|
+
tmpSubPanel.render();
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
})
|
|
1065
|
+
.catch((pErr) =>
|
|
1066
|
+
{
|
|
1067
|
+
let tmpToast = tmpSelf.pict.providers['RetoldRemote-ToastNotification'];
|
|
1068
|
+
if (tmpToast)
|
|
1069
|
+
{
|
|
1070
|
+
tmpToast.showToast('Failed to save region: ' + pErr.message);
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
// Hide label input, show coords
|
|
1075
|
+
this._selectionRegion = null;
|
|
1076
|
+
this._selectionStart = null;
|
|
1077
|
+
|
|
1078
|
+
let tmpLabelWrap = document.getElementById('RetoldRemote-IEX-LabelInput');
|
|
1079
|
+
if (tmpLabelWrap)
|
|
1080
|
+
{
|
|
1081
|
+
tmpLabelWrap.style.display = 'none';
|
|
1082
|
+
}
|
|
1083
|
+
let tmpCoords = document.getElementById('RetoldRemote-IEX-Coords');
|
|
1084
|
+
if (tmpCoords)
|
|
1085
|
+
{
|
|
1086
|
+
tmpCoords.style.display = '';
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Cancel the current in-progress selection.
|
|
1092
|
+
*/
|
|
1093
|
+
cancelSelection()
|
|
1094
|
+
{
|
|
1095
|
+
this._removeActiveSelectionOverlay();
|
|
1096
|
+
this._selectionRegion = null;
|
|
1097
|
+
this._selectionStart = null;
|
|
1098
|
+
|
|
1099
|
+
let tmpLabelWrap = document.getElementById('RetoldRemote-IEX-LabelInput');
|
|
1100
|
+
if (tmpLabelWrap)
|
|
1101
|
+
{
|
|
1102
|
+
tmpLabelWrap.style.display = 'none';
|
|
1103
|
+
}
|
|
1104
|
+
let tmpCoords = document.getElementById('RetoldRemote-IEX-Coords');
|
|
1105
|
+
if (tmpCoords)
|
|
1106
|
+
{
|
|
1107
|
+
tmpCoords.style.display = '';
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Remove the active (in-progress) selection overlay.
|
|
1113
|
+
*/
|
|
1114
|
+
_removeActiveSelectionOverlay()
|
|
1115
|
+
{
|
|
1116
|
+
let tmpActive = document.getElementById('RetoldRemote-IEX-ActiveSelection');
|
|
1117
|
+
if (tmpActive && this._osdViewer)
|
|
1118
|
+
{
|
|
1119
|
+
try
|
|
1120
|
+
{
|
|
1121
|
+
this._osdViewer.removeOverlay(tmpActive);
|
|
1122
|
+
}
|
|
1123
|
+
catch (pErr)
|
|
1124
|
+
{
|
|
1125
|
+
// May not be an overlay; just remove from DOM
|
|
1126
|
+
if (tmpActive.parentElement)
|
|
1127
|
+
{
|
|
1128
|
+
tmpActive.parentElement.removeChild(tmpActive);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
this._selectionOverlay = null;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// -----------------------------------------------------------------
|
|
1136
|
+
// Saved region overlays
|
|
1137
|
+
// -----------------------------------------------------------------
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* Load saved subimage regions from the server.
|
|
1141
|
+
*
|
|
1142
|
+
* @param {string} pFilePath - Relative file path
|
|
1143
|
+
*/
|
|
1144
|
+
_loadSavedRegions(pFilePath)
|
|
1145
|
+
{
|
|
1146
|
+
let tmpSelf = this;
|
|
1147
|
+
let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
|
|
1148
|
+
let tmpPathParam = tmpProvider ? tmpProvider._getPathParam(pFilePath) : encodeURIComponent(pFilePath);
|
|
1149
|
+
|
|
1150
|
+
fetch('/api/media/subimage-regions?path=' + tmpPathParam)
|
|
1151
|
+
.then((pResponse) => pResponse.json())
|
|
1152
|
+
.then((pResult) =>
|
|
1153
|
+
{
|
|
1154
|
+
if (pResult && pResult.Success && Array.isArray(pResult.Regions))
|
|
1155
|
+
{
|
|
1156
|
+
tmpSelf._savedRegions = pResult.Regions;
|
|
1157
|
+
tmpSelf._renderSavedRegionOverlays();
|
|
1158
|
+
}
|
|
1159
|
+
})
|
|
1160
|
+
.catch(() =>
|
|
1161
|
+
{
|
|
1162
|
+
// Silently ignore — regions are optional
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
/**
|
|
1167
|
+
* Render all saved regions as OSD overlays with colored borders and labels.
|
|
1168
|
+
*/
|
|
1169
|
+
_renderSavedRegionOverlays()
|
|
1170
|
+
{
|
|
1171
|
+
if (!this._osdViewer)
|
|
1172
|
+
{
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Remove existing saved-region overlays
|
|
1177
|
+
let tmpExisting = document.querySelectorAll('.retold-remote-iex-region-overlay');
|
|
1178
|
+
for (let i = 0; i < tmpExisting.length; i++)
|
|
1179
|
+
{
|
|
1180
|
+
try
|
|
1181
|
+
{
|
|
1182
|
+
this._osdViewer.removeOverlay(tmpExisting[i]);
|
|
1183
|
+
}
|
|
1184
|
+
catch (pErr)
|
|
1185
|
+
{
|
|
1186
|
+
if (tmpExisting[i].parentElement)
|
|
1187
|
+
{
|
|
1188
|
+
tmpExisting[i].parentElement.removeChild(tmpExisting[i]);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Render each saved region
|
|
1194
|
+
for (let i = 0; i < this._savedRegions.length; i++)
|
|
1195
|
+
{
|
|
1196
|
+
let tmpRegion = this._savedRegions[i];
|
|
1197
|
+
this._addRegionOverlay(tmpRegion);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Add a single region overlay to the OSD viewer.
|
|
1203
|
+
*
|
|
1204
|
+
* @param {object} pRegion - { ID, Label, X, Y, Width, Height }
|
|
1205
|
+
*/
|
|
1206
|
+
_addRegionOverlay(pRegion)
|
|
1207
|
+
{
|
|
1208
|
+
if (!this._osdViewer || !this._dziData)
|
|
1209
|
+
{
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
let tmpEl = document.createElement('div');
|
|
1214
|
+
tmpEl.className = 'retold-remote-iex-region-overlay';
|
|
1215
|
+
tmpEl.setAttribute('data-region-id', pRegion.ID);
|
|
1216
|
+
tmpEl.style.cssText = 'border: 2px solid rgba(229, 192, 123, 0.85); background: rgba(229, 192, 123, 0.08); pointer-events: none; position: relative;';
|
|
1217
|
+
|
|
1218
|
+
// Label badge
|
|
1219
|
+
if (pRegion.Label)
|
|
1220
|
+
{
|
|
1221
|
+
let tmpLabelEl = document.createElement('span');
|
|
1222
|
+
tmpLabelEl.style.cssText = 'position:absolute;top:-1px;left:-1px;background:rgba(229,192,123,0.9);color:#282c34;font-size:0.65rem;padding:1px 5px;border-radius:0 0 3px 0;white-space:nowrap;pointer-events:none;';
|
|
1223
|
+
tmpLabelEl.textContent = pRegion.Label;
|
|
1224
|
+
tmpEl.appendChild(tmpLabelEl);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// Convert image coordinates to viewport coordinates
|
|
1228
|
+
let tmpImageRect = new OpenSeadragon.Rect(pRegion.X, pRegion.Y, pRegion.Width, pRegion.Height);
|
|
1229
|
+
let tmpViewportRect = this._osdViewer.viewport.imageToViewportRectangle(tmpImageRect);
|
|
1230
|
+
|
|
1231
|
+
this._osdViewer.addOverlay(
|
|
1232
|
+
{
|
|
1233
|
+
element: tmpEl,
|
|
1234
|
+
location: tmpViewportRect
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Navigate to (zoom into) a specific saved region by ID.
|
|
1240
|
+
*
|
|
1241
|
+
* @param {string} pRegionID - The region ID to navigate to
|
|
1242
|
+
*/
|
|
1243
|
+
zoomToRegion(pRegionID)
|
|
1244
|
+
{
|
|
1245
|
+
if (!this._osdViewer || !this._dziData)
|
|
1246
|
+
{
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
let tmpRegion = null;
|
|
1251
|
+
for (let i = 0; i < this._savedRegions.length; i++)
|
|
1252
|
+
{
|
|
1253
|
+
if (this._savedRegions[i].ID === pRegionID)
|
|
1254
|
+
{
|
|
1255
|
+
tmpRegion = this._savedRegions[i];
|
|
1256
|
+
break;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
if (!tmpRegion)
|
|
1261
|
+
{
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// Convert image rect to viewport rect and fit to it
|
|
1266
|
+
let tmpImageRect = new OpenSeadragon.Rect(tmpRegion.X, tmpRegion.Y, tmpRegion.Width, tmpRegion.Height);
|
|
1267
|
+
let tmpViewportRect = this._osdViewer.viewport.imageToViewportRectangle(tmpImageRect);
|
|
1268
|
+
this._osdViewer.viewport.fitBounds(tmpViewportRect);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* Delete a saved region by ID.
|
|
1273
|
+
*
|
|
1274
|
+
* @param {string} pRegionID - The region ID to delete
|
|
1275
|
+
*/
|
|
1276
|
+
deleteRegion(pRegionID)
|
|
1277
|
+
{
|
|
1278
|
+
let tmpSelf = this;
|
|
1279
|
+
let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
|
|
1280
|
+
let tmpPathParam = tmpProvider ? tmpProvider._getPathParam(this._currentPath) : encodeURIComponent(this._currentPath);
|
|
1281
|
+
|
|
1282
|
+
fetch('/api/media/subimage-regions/' + encodeURIComponent(pRegionID) + '?path=' + tmpPathParam,
|
|
1283
|
+
{
|
|
1284
|
+
method: 'DELETE'
|
|
1285
|
+
})
|
|
1286
|
+
.then((pResponse) => pResponse.json())
|
|
1287
|
+
.then((pResult) =>
|
|
1288
|
+
{
|
|
1289
|
+
if (pResult && pResult.Success)
|
|
1290
|
+
{
|
|
1291
|
+
tmpSelf._savedRegions = pResult.Regions || [];
|
|
1292
|
+
tmpSelf._renderSavedRegionOverlays();
|
|
1293
|
+
|
|
1294
|
+
let tmpToast = tmpSelf.pict.providers['RetoldRemote-ToastNotification'];
|
|
1295
|
+
if (tmpToast)
|
|
1296
|
+
{
|
|
1297
|
+
tmpToast.showToast('Region deleted');
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// Update sidebar
|
|
1301
|
+
let tmpSubPanel = tmpSelf.pict.views['RetoldRemote-SubimagesPanel'];
|
|
1302
|
+
if (tmpSubPanel)
|
|
1303
|
+
{
|
|
1304
|
+
tmpSubPanel.render();
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
})
|
|
1308
|
+
.catch(() =>
|
|
1309
|
+
{
|
|
1310
|
+
// ignore
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Get the current selection region (for use by collection add).
|
|
1316
|
+
*
|
|
1317
|
+
* @returns {object|null} The current selection or null
|
|
1318
|
+
*/
|
|
1319
|
+
getActiveSelection()
|
|
1320
|
+
{
|
|
1321
|
+
return this._selectionRegion;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* Get the saved regions array.
|
|
1326
|
+
*
|
|
1327
|
+
* @returns {Array}
|
|
1328
|
+
*/
|
|
1329
|
+
getSavedRegions()
|
|
1330
|
+
{
|
|
1331
|
+
return this._savedRegions;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// -----------------------------------------------------------------
|
|
1335
|
+
// Navigation
|
|
1336
|
+
// -----------------------------------------------------------------
|
|
1337
|
+
|
|
739
1338
|
/**
|
|
740
1339
|
* Navigate back to the gallery / file listing.
|
|
741
1340
|
*/
|
|
742
1341
|
goBack()
|
|
743
1342
|
{
|
|
1343
|
+
// Clean up selection mode
|
|
1344
|
+
if (this._selectionMode)
|
|
1345
|
+
{
|
|
1346
|
+
this._exitSelectionMode();
|
|
1347
|
+
}
|
|
1348
|
+
|
|
744
1349
|
// Destroy the OSD viewer
|
|
745
1350
|
if (this._osdViewer)
|
|
746
1351
|
{
|
|
@@ -68,11 +68,11 @@ class RetoldRemoteImageViewerView extends libPictView
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* Show the explore button
|
|
71
|
+
* Show the explore button in the header nav bar.
|
|
72
72
|
*/
|
|
73
73
|
_showExploreButton()
|
|
74
74
|
{
|
|
75
|
-
let tmpBtn = document.getElementById('RetoldRemote-
|
|
75
|
+
let tmpBtn = document.getElementById('RetoldRemote-HeaderExploreBtn');
|
|
76
76
|
if (tmpBtn)
|
|
77
77
|
{
|
|
78
78
|
tmpBtn.style.display = '';
|
|
@@ -24,6 +24,7 @@ const _ViewConfiguration =
|
|
|
24
24
|
<button class="content-editor-sidebar-tab active" data-tab="files" onclick="pict.views['ContentEditor-Layout'].switchSidebarTab('files')">Files</button>
|
|
25
25
|
<button class="content-editor-sidebar-tab" data-tab="favorites" onclick="pict.views['ContentEditor-Layout'].switchSidebarTab('favorites')">Favorites</button>
|
|
26
26
|
<button class="content-editor-sidebar-tab" data-tab="info" onclick="pict.views['ContentEditor-Layout'].switchSidebarTab('info')">Info</button>
|
|
27
|
+
<button class="content-editor-sidebar-tab" data-tab="subimages" onclick="pict.views['ContentEditor-Layout'].switchSidebarTab('subimages')">Regions</button>
|
|
27
28
|
<button class="content-editor-sidebar-tab" data-tab="settings" onclick="pict.views['ContentEditor-Layout'].switchSidebarTab('settings')">Settings</button>
|
|
28
29
|
<button class="content-editor-sidebar-tab content-editor-sidebar-tab-collections" data-tab="collections" onclick="pict.views['ContentEditor-Layout'].switchSidebarTab('collections')" style="display:none;">Collections</button>
|
|
29
30
|
</div>
|
|
@@ -32,6 +33,7 @@ const _ViewConfiguration =
|
|
|
32
33
|
<div id="RetoldRemote-Favorites-Body"></div>
|
|
33
34
|
</div>
|
|
34
35
|
<div class="content-editor-sidebar-pane" data-pane="info" id="RetoldRemote-Info-Container" style="display:none"></div>
|
|
36
|
+
<div class="content-editor-sidebar-pane" data-pane="subimages" id="RetoldRemote-Subimages-Container" style="display:none"></div>
|
|
35
37
|
<div class="content-editor-sidebar-pane" data-pane="settings" id="RetoldRemote-Settings-Container" style="display:none"></div>
|
|
36
38
|
<div class="content-editor-sidebar-pane" data-pane="collections" id="RetoldRemote-Collections-MobilePane" style="display:none"></div>
|
|
37
39
|
</div>
|
|
@@ -196,6 +198,16 @@ class RetoldRemoteLayoutView extends libPictView
|
|
|
196
198
|
pEl.style.display = (pEl.getAttribute('data-pane') === pTab) ? '' : 'none';
|
|
197
199
|
});
|
|
198
200
|
|
|
201
|
+
// Subimages tab: render the subimages panel
|
|
202
|
+
if (pTab === 'subimages')
|
|
203
|
+
{
|
|
204
|
+
let tmpSubimagesView = this.pict.views['RetoldRemote-SubimagesPanel'];
|
|
205
|
+
if (tmpSubimagesView)
|
|
206
|
+
{
|
|
207
|
+
tmpSubimagesView.render();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
199
211
|
// Render settings panel on demand
|
|
200
212
|
if (pTab === 'settings')
|
|
201
213
|
{
|