mirador-annotation-editor-video 1.1.5 → 1.1.6

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.
Files changed (92) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/__tests__/AnnotationCreation.test.js +62 -28
  3. package/__tests__/AnnotationExportDialog.test.js +18 -16
  4. package/__tests__/CanvasListItem.test.js +53 -19
  5. package/__tests__/LocalStorageAdapter.test.js +1 -1
  6. package/__tests__/miradorAnnotationPlugin.test.js +97 -70
  7. package/__tests__/style-mock.js +1 -0
  8. package/__tests__/test-utils.js +57 -0
  9. package/demo/src/index.js +9 -4
  10. package/demo/src/quillConfig.js +34 -0
  11. package/es/AnnotationExportDialog.js +17 -25
  12. package/es/CanvasListItem.js +8 -4
  13. package/es/IIIFUtils.js +35 -3
  14. package/es/SingleCanvasDialog.js +1 -4
  15. package/es/TextEditor.js +9 -19
  16. package/es/annotationForm/AnnotationForm.js +14 -41
  17. package/es/annotationForm/AnnotationFormBody.js +36 -27
  18. package/es/annotationForm/AnnotationFormHeader.js +3 -3
  19. package/es/annotationForm/AnnotationFormOverlay/AnnotationDrawing.js +3 -1
  20. package/es/annotationForm/AnnotationFormOverlay/AnnotationFormOverlay.js +1 -1
  21. package/es/annotationForm/AnnotationFormOverlay/AnnotationFormOverlayTool.js +3 -2
  22. package/es/annotationForm/AnnotationFormOverlay/AnnotationFormOverlayToolOptions.js +8 -6
  23. package/es/annotationForm/AnnotationFormOverlay/KonvaDrawing/KonvaUtils.js +6 -20
  24. package/es/annotationForm/AnnotationFormOverlay/KonvaDrawing/shapes/ColorPicker.js +16 -6
  25. package/es/annotationForm/AnnotationFormTemplateSelector.js +9 -9
  26. package/es/annotationForm/AnnotationFormUtils.js +3 -0
  27. package/es/annotationForm/Debug.js +1 -0
  28. package/es/annotationForm/DebugInformation.js +27 -0
  29. package/es/annotationForm/MultiTagsInput.js +4 -1
  30. package/es/annotationForm/MultipleBodyTemplate.js +7 -9
  31. package/es/annotationForm/TaggingTemplate.js +0 -1
  32. package/es/annotationForm/TargetFormSection.js +4 -3
  33. package/es/annotationForm/TargetSpatialInput.js +4 -3
  34. package/es/annotationForm/TextCommentInput.js +25 -12
  35. package/es/annotationForm/TextCommentTemplate.js +0 -1
  36. package/es/annotationForm/UnsupportedMedia.js +43 -0
  37. package/es/containers/miradorAnnotationPlugin.js +1 -50
  38. package/es/custom.css +0 -13
  39. package/es/index.js +5 -12
  40. package/es/locales/locales.js +3 -2
  41. package/es/locales/locales_en.js +1 -1
  42. package/es/playerReferences.js +2 -1
  43. package/es/plugins/annotationCreationCompanionWindow.js +5 -8
  44. package/es/plugins/annotationSaga.js +44 -0
  45. package/es/plugins/canvasAnnotationsPlugin.js +86 -61
  46. package/es/plugins/canvasAnnotationsPluginUtils.js +202 -0
  47. package/es/plugins/externalStorageAnnotationPlugin.js +6 -71
  48. package/es/plugins/miradorAnnotationPlugin.js +44 -6
  49. package/es/plugins/windowSideBarButtonsPlugin.js +8 -10
  50. package/jest.config.js +14 -3
  51. package/package.json +8 -3
  52. package/setupJest.js +1 -4
  53. package/src/AnnotationExportDialog.js +12 -24
  54. package/src/CanvasListItem.js +4 -3
  55. package/src/IIIFUtils.js +33 -5
  56. package/src/SingleCanvasDialog.js +0 -3
  57. package/src/TextEditor.js +8 -32
  58. package/src/annotationForm/AnnotationForm.js +8 -47
  59. package/src/annotationForm/AnnotationFormBody.js +62 -83
  60. package/src/annotationForm/AnnotationFormHeader.js +3 -3
  61. package/src/annotationForm/AnnotationFormOverlay/AnnotationDrawing.js +3 -9
  62. package/src/annotationForm/AnnotationFormOverlay/AnnotationFormOverlay.js +1 -1
  63. package/src/annotationForm/AnnotationFormOverlay/AnnotationFormOverlayTool.js +2 -1
  64. package/src/annotationForm/AnnotationFormOverlay/AnnotationFormOverlayToolOptions.js +10 -6
  65. package/src/annotationForm/AnnotationFormOverlay/KonvaDrawing/KonvaUtils.js +14 -20
  66. package/src/annotationForm/AnnotationFormOverlay/KonvaDrawing/shapes/ColorPicker.js +14 -12
  67. package/src/annotationForm/AnnotationFormTemplateSelector.js +5 -7
  68. package/src/annotationForm/AnnotationFormUtils.js +2 -0
  69. package/src/annotationForm/Debug.js +0 -0
  70. package/src/annotationForm/DebugInformation.js +59 -0
  71. package/src/annotationForm/MultiTagsInput.js +4 -1
  72. package/src/annotationForm/MultipleBodyTemplate.js +7 -8
  73. package/src/annotationForm/TaggingTemplate.js +0 -1
  74. package/src/annotationForm/TargetFormSection.js +2 -3
  75. package/src/annotationForm/TargetSpatialInput.js +3 -3
  76. package/src/annotationForm/TextCommentInput.js +28 -14
  77. package/src/annotationForm/TextCommentTemplate.js +0 -1
  78. package/src/annotationForm/UnsupportedMedia.js +31 -0
  79. package/src/containers/miradorAnnotationPlugin.js +0 -36
  80. package/src/custom.css +0 -13
  81. package/src/index.js +10 -15
  82. package/src/locales/locales.js +3 -1
  83. package/src/locales/locales_en.js +1 -1
  84. package/src/playerReferences.js +5 -2
  85. package/src/plugins/annotationCreationCompanionWindow.js +9 -23
  86. package/src/plugins/annotationSaga.js +50 -0
  87. package/src/plugins/canvasAnnotationsPlugin.js +122 -98
  88. package/src/plugins/canvasAnnotationsPluginUtils.js +199 -0
  89. package/src/plugins/externalStorageAnnotationPlugin.js +6 -79
  90. package/src/plugins/miradorAnnotationPlugin.js +32 -4
  91. package/src/plugins/windowSideBarButtonsPlugin.js +6 -8
  92. package/webpack.config.js +1 -0
@@ -10,10 +10,9 @@ var _canvases = require("mirador/dist/es/src/state/selectors/canvases");
10
10
  var _annotations = require("mirador/dist/es/src/state/selectors/annotations");
11
11
  var _OSDReferences = require("mirador/dist/es/src/plugins/OSDReferences");
12
12
  var _VideosReferences = require("mirador/dist/es/src/plugins/VideosReferences");
13
- var _reactI18next = require("react-i18next");
14
- var _AnnotationForm = _interopRequireDefault(require("../annotationForm/AnnotationForm"));
15
13
  var _playerReferences = require("../playerReferences");
16
14
  var _AnnotationFormUtils = require("../annotationForm/AnnotationFormUtils");
15
+ var _AnnotationForm = _interopRequireDefault(require("../annotationForm/AnnotationForm"));
17
16
  var _locales = _interopRequireDefault(require("../locales/locales"));
18
17
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
19
18
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
@@ -77,12 +76,10 @@ function mapStateToProps(state, {
77
76
  playerReferences
78
77
  };
79
78
  }
80
- const AnnotationFormWithTranslation = (0, _reactI18next.withTranslation)()(_AnnotationForm.default);
81
-
82
- // eslint-disable-next-line import/no-anonymous-default-export
83
- var _default = exports.default = {
79
+ const annotationCreationCompanionWindowPlugin = {
84
80
  companionWindowKey: 'annotationCreation',
85
- component: AnnotationFormWithTranslation,
81
+ component: _AnnotationForm.default,
86
82
  mapDispatchToProps,
87
83
  mapStateToProps
88
- };
84
+ };
85
+ var _default = exports.default = annotationCreationCompanionWindowPlugin;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _effects = require("redux-saga/effects");
8
+ var _actions = require("mirador/dist/es/src/state/actions");
9
+ var _actionTypes = _interopRequireDefault(require("mirador/dist/es/src/state/actions/action-types"));
10
+ var _selectors = require("mirador/dist/es/src/state/selectors");
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ /** Retrieves all the annotations available in the annotation adapter */
13
+ function* retrieveAnnotationsFormStore(canvasId) {
14
+ const config = yield (0, _effects.select)(_selectors.getConfig);
15
+ if (config && config.annotation.adapter) {
16
+ const storageAdapter = config.annotation.adapter(canvasId);
17
+ const annoPage = yield (0, _effects.call)([storageAdapter, storageAdapter.all]);
18
+ if (annoPage) {
19
+ yield (0, _effects.put)((0, _actions.receiveAnnotation)(canvasId, storageAdapter.annotationPageId, annoPage));
20
+ }
21
+ }
22
+ }
23
+
24
+ /**
25
+ * A generator function which takesEvery SET_CANVAS mirador action
26
+ * and fetches the associated annotations from the store.
27
+ */
28
+ function* setAnnotations(action) {
29
+ const {
30
+ canvasId
31
+ } = action;
32
+ yield (0, _effects.call)(retrieveAnnotationsFormStore, canvasId);
33
+ }
34
+
35
+ /** Annotation saga for setting inital annotations */
36
+ function* annotationSaga() {
37
+ yield (0, _effects.all)([(0, _effects.takeEvery)(_actionTypes.default.SET_CANVAS, setAnnotations)]);
38
+ }
39
+ const annotationSagaPlugin = {
40
+ mode: 'wrap',
41
+ saga: annotationSaga,
42
+ target: 'Window'
43
+ };
44
+ var _default = exports.default = annotationSagaPlugin;
@@ -14,11 +14,23 @@ var _CanvasListItem = _interopRequireDefault(require("../CanvasListItem"));
14
14
  var _AnnotationActionsContext = _interopRequireDefault(require("../AnnotationActionsContext"));
15
15
  var _SingleCanvasDialog = _interopRequireDefault(require("../SingleCanvasDialog"));
16
16
  var _locales = _interopRequireDefault(require("../locales/locales"));
17
+ var _canvasAnnotationsPluginUtils = require("./canvasAnnotationsPluginUtils");
17
18
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
18
19
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
19
20
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
20
- function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
21
- /** Functional Component */
21
+ /**
22
+ * CanvasAnnotationsWrapper
23
+ *
24
+ * Re-implements "scroll to selected annotation" inside the wrapper:
25
+ * - Observes selectedAnnotationId and scrolls the correct container so the <li> is visible.
26
+ * - Robust container resolution (ancestor/descendant/window).
27
+ * - Retries to survive focus/reflow resetting scrollTop.
28
+ *
29
+ * Props of interest:
30
+ * - targetProps.selectedAnnotationId: the currently selected annotation id.
31
+ * - scrollOffsetTop: px reserved for sticky header inside the scroller (default 96).
32
+ * - scrollRetries / scrollRetryDelay / scrollBehavior: tuning for robustness.
33
+ */
22
34
  function CanvasAnnotationsWrapper({
23
35
  addCompanionWindow,
24
36
  annotationsOnCanvases = {},
@@ -29,37 +41,56 @@ function CanvasAnnotationsWrapper({
29
41
  TargetComponent,
30
42
  targetProps,
31
43
  windowViewType,
32
- containerRef,
33
44
  annotationEditCompanionWindowIsOpened,
34
45
  t
35
46
  }) {
36
47
  const [singleCanvasDialogOpen, setSingleCanvasDialogOpen] = (0, _react.useState)(false);
37
-
38
- /** */
39
- const toggleSingleCanvasDialogOpen = () => {
40
- setSingleCanvasDialogOpen(prev => !prev);
41
- };
48
+ const wrapperRef = (0, _react.useRef)(null);
49
+ const bridgedScrollRef = (0, _react.useRef)(null);
50
+ (0, _react.useEffect)(() => {
51
+ const selId = targetProps?.selectedAnnotationId;
52
+ if (!selId) return;
53
+ const node = wrapperRef.current?.querySelector(`li[annotationid="${selId}"]`) || wrapperRef.current?.querySelector('li.MuiMenuItem-root.Mui-selected');
54
+ if (!node) return;
55
+ (0, _canvasAnnotationsPluginUtils.scrollToSelectedAnnotation)(node, bridgedScrollRef);
56
+ }, [targetProps?.selectedAnnotationId]);
57
+ /**
58
+ * Toggle the visibility state of the single canvas dialog.
59
+ *
60
+ * - Flips `singleCanvasDialogOpen` between `true` and `false`.
61
+ * - Used as the `handleClose` callback for the dialog and to open it.
62
+ *
63
+ * @function
64
+ * @returns {void}
65
+ */
66
+ const toggleSingleCanvasDialogOpen = (0, _react.useCallback)(() => setSingleCanvasDialogOpen(p => !p), []);
42
67
  const props = {
68
+ containerRef: bridgedScrollRef,
43
69
  ...targetProps,
44
70
  listContainerComponent: _CanvasListItem.default
45
71
  };
72
+ const contextValue = (0, _react.useMemo)(() => ({
73
+ addCompanionWindow,
74
+ annotationEditCompanionWindowIsOpened,
75
+ annotationsOnCanvases,
76
+ canvases,
77
+ config,
78
+ receiveAnnotation,
79
+ storageAdapter: config.annotation.adapter,
80
+ t,
81
+ toggleSingleCanvasDialogOpen,
82
+ windowId: targetProps.windowId,
83
+ windowViewType
84
+ }), [addCompanionWindow, annotationEditCompanionWindowIsOpened, annotationsOnCanvases, canvases, config, receiveAnnotation, t, toggleSingleCanvasDialogOpen, targetProps.windowId, windowViewType]);
46
85
  return /*#__PURE__*/_react.default.createElement(_AnnotationActionsContext.default.Provider, {
47
- value: {
48
- addCompanionWindow,
49
- annotationEditCompanionWindowIsOpened,
50
- annotationsOnCanvases,
51
- canvases,
52
- config,
53
- receiveAnnotation,
54
- storageAdapter: config.annotation.adapter,
55
- toggleSingleCanvasDialogOpen,
56
- windowId: targetProps.windowId,
57
- windowViewType,
58
- t
86
+ value: contextValue
87
+ }, /*#__PURE__*/_react.default.createElement("div", {
88
+ ref: wrapperRef,
89
+ style: {
90
+ height: '100%',
91
+ position: 'relative'
59
92
  }
60
- }, /*#__PURE__*/_react.default.createElement(TargetComponent, _extends({}, props, {
61
- ref: containerRef
62
- })), windowViewType !== 'single' && /*#__PURE__*/_react.default.createElement(_SingleCanvasDialog.default, {
93
+ }, /*#__PURE__*/_react.default.createElement(TargetComponent, props)), windowViewType !== 'single' && /*#__PURE__*/_react.default.createElement(_SingleCanvasDialog.default, {
63
94
  handleClose: toggleSingleCanvasDialogOpen,
64
95
  open: singleCanvasDialogOpen,
65
96
  switchToSingleCanvasView: switchToSingleCanvasView
@@ -68,27 +99,8 @@ function CanvasAnnotationsWrapper({
68
99
  CanvasAnnotationsWrapper.propTypes = {
69
100
  addCompanionWindow: _propTypes.default.func.isRequired,
70
101
  annotationEditCompanionWindowIsOpened: _propTypes.default.bool.isRequired,
71
- annotationsOnCanvases: _propTypes.default.shape({
72
- id: _propTypes.default.string,
73
- isFetching: _propTypes.default.bool,
74
- json: _propTypes.default.shape({
75
- id: _propTypes.default.string,
76
- items: _propTypes.default.arrayOf(_propTypes.default.shape({
77
- body: _propTypes.default.shape({
78
- format: _propTypes.default.string,
79
- id: _propTypes.default.string,
80
- value: _propTypes.default.string
81
- }),
82
- drawingState: _propTypes.default.string,
83
- id: _propTypes.default.string,
84
- manifestNetwork: _propTypes.default.string,
85
- motivation: _propTypes.default.string,
86
- target: _propTypes.default.string,
87
- type: _propTypes.default.string
88
- })),
89
- type: _propTypes.default.string
90
- })
91
- }).isRequired,
102
+ annotationsOnCanvases: _propTypes.default.shape({}).isRequired,
103
+ // eslint-disable-next-line max-len
92
104
  canvases: _propTypes.default.arrayOf(_propTypes.default.shape({
93
105
  id: _propTypes.default.string,
94
106
  index: _propTypes.default.number
@@ -98,9 +110,6 @@ CanvasAnnotationsWrapper.propTypes = {
98
110
  adapter: _propTypes.default.func
99
111
  })
100
112
  }).isRequired,
101
- containerRef: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.shape({
102
- current: _propTypes.default.instanceOf(Element)
103
- })]),
104
113
  receiveAnnotation: _propTypes.default.func.isRequired,
105
114
  switchToSingleCanvasView: _propTypes.default.func.isRequired,
106
115
  t: _propTypes.default.func.isRequired,
@@ -110,7 +119,7 @@ CanvasAnnotationsWrapper.propTypes = {
110
119
  windowViewType: _propTypes.default.string.isRequired
111
120
  };
112
121
 
113
- /** TODO this logic is duplicated */
122
+ /** mapStateToProps / mapDispatchToProps unchanged from your version */
114
123
  function mapStateToProps(state, {
115
124
  targetProps: {
116
125
  windowId
@@ -120,19 +129,14 @@ function mapStateToProps(state, {
120
129
  windowId
121
130
  });
122
131
  const annotationsOnCanvases = {};
123
- const annotationCreationCompanionWindows = (0, _companionWindows.getCompanionWindowsForContent)(state, {
132
+ const creation = (0, _companionWindows.getCompanionWindowsForContent)(state, {
124
133
  content: 'annotationCreation',
125
134
  windowId
126
135
  });
127
- let annotationEditCompanionWindowIsOpened = true;
128
- if (Object.keys(annotationCreationCompanionWindows).length !== 0) {
129
- annotationEditCompanionWindowIsOpened = false;
130
- }
136
+ const annotationEditCompanionWindowIsOpened = Object.keys(creation).length === 0;
131
137
  canvases.forEach(canvas => {
132
138
  const anno = state.annotations[canvas.id];
133
- if (anno) {
134
- annotationsOnCanvases[canvas.id] = anno;
135
- }
139
+ if (anno) annotationsOnCanvases[canvas.id] = anno;
136
140
  });
137
141
  return {
138
142
  annotationEditCompanionWindowIsOpened,
@@ -147,9 +151,30 @@ function mapStateToProps(state, {
147
151
  })
148
152
  };
149
153
  }
150
-
151
- /** */
152
- const mapDispatchToProps = (dispatch, props, annotationEditCompanionWindowIsOpened) => ({
154
+ /**
155
+ * Map Redux dispatch actions to props for the CanvasAnnotationsWrapper.
156
+ *
157
+ * Provides callback props that allow the wrapped component to interact
158
+ * with the Mirador Redux store, including:
159
+ *
160
+ * - `addCompanionWindow`: Open a companion window for the given window ID,
161
+ * with specified content and optional extra props.
162
+ * - `receiveAnnotation`: Add or update an annotation in the Redux store
163
+ * for a specific target.
164
+ * - `switchToSingleCanvasView`: Change the current window's view type
165
+ * to `"single"`.
166
+ *
167
+ * @function
168
+ * @param {Function} dispatch - Redux dispatch function.
169
+ * @param {object} props - The wrapper component props.
170
+ * @param {object} props.targetProps - Props passed down to the wrapped target component.
171
+ * @param {string} props.targetProps.windowId - The ID of the Mirador window.
172
+ * @returns {object} An object mapping action dispatchers to props.
173
+ * @property {function(string, object):void} addCompanionWindow
174
+ * @property {function(string, string, object):void} receiveAnnotation
175
+ * @property {function():void} switchToSingleCanvasView
176
+ */
177
+ const mapDispatchToProps = (dispatch, props) => ({
153
178
  addCompanionWindow: (content, additionalProps) => dispatch(actions.addCompanionWindow(props.targetProps.windowId, {
154
179
  content,
155
180
  ...additionalProps
@@ -157,11 +182,11 @@ const mapDispatchToProps = (dispatch, props, annotationEditCompanionWindowIsOpen
157
182
  receiveAnnotation: (targetId, id, annotation) => dispatch(actions.receiveAnnotation(targetId, id, annotation)),
158
183
  switchToSingleCanvasView: () => dispatch(actions.setWindowViewType(props.targetProps.windowId, 'single'))
159
184
  });
160
- const CanvasAnnotationsWrapperContainer = {
185
+ const canvasAnnotationsPlugin = {
161
186
  component: CanvasAnnotationsWrapper,
162
187
  mapDispatchToProps,
163
188
  mapStateToProps,
164
189
  mode: 'wrap',
165
190
  target: 'CanvasAnnotations'
166
191
  };
167
- var _default = exports.default = CanvasAnnotationsWrapperContainer;
192
+ var _default = exports.default = canvasAnnotationsPlugin;
@@ -0,0 +1,202 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.closestScrollableAncestor = void 0;
7
+ exports.computeTargetContainer = computeTargetContainer;
8
+ exports.computeTargetWindow = computeTargetWindow;
9
+ exports.scrollToSelectedAnnotation = exports.runScrollOnce = exports.isScrollable = exports.getWindowScroller = void 0;
10
+ /**
11
+ * Utilities
12
+ */
13
+ const isScrollable = el => {
14
+ if (!el) return false;
15
+ const cs = getComputedStyle(el);
16
+ const oy = cs.overflowY || cs.overflow || '';
17
+ return /(auto|scroll|overlay)/.test(oy) && el.scrollHeight > el.clientHeight;
18
+ };
19
+
20
+ /**
21
+ * Find the closest scrollable ancestor of a given DOM node.
22
+ *
23
+ * - Walks up the DOM tree from the node's parent using `parentElement`.
24
+ * - Returns the first ancestor element that is considered scrollable,
25
+ * meaning:
26
+ * - Its `overflowY` (or `overflow`) style is `auto`, `scroll`, or `overlay`,
27
+ * - And its `scrollHeight` is greater than its `clientHeight`.
28
+ * - Returns `null` if no scrollable ancestor is found before reaching the root.
29
+ *
30
+ * @param {HTMLElement|null} node - The starting DOM node.
31
+ * @returns {HTMLElement|null} The nearest scrollable ancestor, or null if none exist.
32
+ */
33
+ exports.isScrollable = isScrollable;
34
+ const closestScrollableAncestor = node => {
35
+ let cur = node?.parentElement || null;
36
+ while (cur) {
37
+ if (isScrollable(cur)) return cur;
38
+ cur = cur.parentElement;
39
+ }
40
+ return null;
41
+ };
42
+
43
+ /**
44
+ * Get the primary scrollable element for the browser window.
45
+ *
46
+ * - Modern browsers expose `document.scrollingElement` (usually the `<html>` element).
47
+ * - As a fallback, returns `document.documentElement` (the `<html>` element).
48
+ * - This element's `scrollTop` and `scrollHeight` represent the window's scroll state.
49
+ *
50
+ * @function
51
+ * @returns {HTMLElement} The DOM element representing the window's scrolling container.
52
+ */
53
+ exports.closestScrollableAncestor = closestScrollableAncestor;
54
+ const getWindowScroller = () => document.scrollingElement || document.documentElement;
55
+
56
+ /**
57
+ * Compute the vertical scroll position needed to bring a node into view
58
+ * within a given scrollable container element.
59
+ *
60
+ * - Uses bounding boxes of the node and container to calculate the node's
61
+ * position relative to the container's scroll area.
62
+ * - Considers the container's `scrollTop`, `clientHeight`, and a custom
63
+ * `offsetTop` (for sticky headers/toolbars inside the container).
64
+ * - If the node is already fully visible inside the container, returns `null`.
65
+ * - If the node is above the visible area, returns the scrollTop needed
66
+ * to align its top just below the offset.
67
+ * - If the node is below the visible area, returns the scrollTop needed
68
+ * to bring its bottom into view (but not less than aligning its top).
69
+ *
70
+ * @param {HTMLElement} container - The scrollable container element.
71
+ * @param {HTMLElement} node - The DOM element to bring into view.
72
+ * @param {number} offsetTop - Extra vertical offset in pixels to keep free at the top.
73
+ * @returns {number|null} The target scrollTop value for the container, or null if already visible.
74
+ */
75
+ exports.getWindowScroller = getWindowScroller;
76
+ function computeTargetContainer(container, node, offsetTop) {
77
+ const cRect = container.getBoundingClientRect();
78
+ const nRect = node.getBoundingClientRect();
79
+ const nodeTopInContainer = nRect.top - cRect.top + container.scrollTop;
80
+ const nodeBottomInContainer = nRect.bottom - cRect.top + container.scrollTop;
81
+ const visibleTop = container.scrollTop + offsetTop;
82
+ const visibleBottom = container.scrollTop + container.clientHeight;
83
+ const above = nodeTopInContainer < visibleTop;
84
+ const below = nodeBottomInContainer > visibleBottom;
85
+ if (!above && !below) return null;
86
+ return above ? nodeTopInContainer - offsetTop : Math.max(nodeBottomInContainer - container.clientHeight, nodeTopInContainer - offsetTop);
87
+ }
88
+
89
+ /**
90
+ * Compute the vertical scroll position needed to bring a node into view
91
+ * within the browser window.
92
+ *
93
+ * - Uses the node's bounding box relative to the viewport.
94
+ * - Considers the current `window.scrollY` and the given `offsetTop`
95
+ * (to account for sticky headers or toolbars).
96
+ * - If the node is already fully visible, returns `null`.
97
+ * - If the node is above the visible viewport, returns the scrollY needed
98
+ * to align its top just below the offset.
99
+ * - If the node is below the viewport, returns the scrollY needed to align
100
+ * its bottom into view (but not less than aligning its top).
101
+ *
102
+ * @param {HTMLElement} node - The DOM element to bring into view.
103
+ * @param {number} offsetTop - Extra vertical offset in pixels to keep free at the top.
104
+ * @returns {number|null} The target scrollY for the window, or null if already visible.
105
+ */
106
+ function computeTargetWindow(node, offsetTop) {
107
+ const rect = node.getBoundingClientRect();
108
+ const nodeTop = rect.top + window.scrollY;
109
+ const nodeBottom = rect.bottom + window.scrollY;
110
+ const viewTop = window.scrollY + offsetTop;
111
+ const viewBottom = window.scrollY + window.innerHeight;
112
+ const above = nodeTop < viewTop;
113
+ const below = nodeBottom > viewBottom;
114
+ if (!above && !below) return null;
115
+ return above ? nodeTop - offsetTop : Math.max(nodeBottom - window.innerHeight, nodeTop - offsetTop);
116
+ }
117
+
118
+ /**
119
+ * Attempt a single scroll operation to bring the selected annotation node into view.
120
+ *
121
+ * - Uses double `requestAnimationFrame` to wait until layout and paints have settled.
122
+ * - Resolves the correct scroll container (bridged ref, closest ancestor, or window).
123
+ * - Computes the target scroll position using `computeTargetWindow` or
124
+ * `computeTargetContainer`.
125
+ * - Performs the scroll with the configured `scrollBehavior`.
126
+ * - After a short delay (`scrollRetryDelay`),
127
+ * checks whether the scroll position actually changed.
128
+ *
129
+ * @param {HTMLElement} node - The DOM element to scroll into view.
130
+ * @param {React.RefObject<HTMLElement>} bridgedScrollRef - Ref to a preferred scrollable container.
131
+ * @param {number} scrollRetryDelay - Delay in ms before checking if the scroll succeeded.
132
+ * @returns {Promise<boolean>} Resolves to true if the scroll moved the container, false otherwise.
133
+ */
134
+ const runScrollOnce = (node, bridgedScrollRef, scrollRetryDelay) => new Promise(resolve => {
135
+ const scrollBehavior = 'smooth';
136
+ const scrollOffsetTop = 96;
137
+ requestAnimationFrame(() => {
138
+ // eslint-disable-next-line consistent-return
139
+ requestAnimationFrame(() => {
140
+ let container = bridgedScrollRef.current;
141
+ if (!isScrollable(container)) container = closestScrollableAncestor(node);
142
+ if (!isScrollable(container)) container = getWindowScroller();
143
+ const isWindow = container === document.body || container === document.documentElement || container === document.scrollingElement;
144
+ if (isWindow) {
145
+ const topWindow = computeTargetWindow(node, scrollOffsetTop);
146
+ if (topWindow == null) return resolve(true);
147
+ const before = window.scrollY;
148
+ window.scrollTo({
149
+ behavior: scrollBehavior,
150
+ top: topWindow
151
+ });
152
+ setTimeout(() => {
153
+ const after = window.scrollY;
154
+ resolve(Math.abs(after - before) > 0.5);
155
+ }, scrollRetryDelay);
156
+ // eslint-disable-next-line consistent-return
157
+ return;
158
+ }
159
+ const top = computeTargetContainer(container, node, scrollOffsetTop);
160
+ if (top == null) return resolve(true);
161
+ const before = container.scrollTop;
162
+ if (typeof container.scrollTo === 'function') {
163
+ container.scrollTo({
164
+ behavior: scrollBehavior,
165
+ top
166
+ });
167
+ }
168
+ setTimeout(() => {
169
+ const after = container.scrollTop;
170
+ resolve(Math.abs(after - before) > 0.5);
171
+ }, scrollRetryDelay);
172
+ });
173
+ });
174
+ });
175
+
176
+ /**
177
+ * Scroll the selected annotation into view, retrying if necessary.
178
+ * @param node
179
+ * @param bridgedScrollRef
180
+ * @returns {Promise<void>}
181
+ */
182
+ exports.runScrollOnce = runScrollOnce;
183
+ const scrollToSelectedAnnotation = async (node, bridgedScrollRef) => {
184
+ const maxScrollRetries = 3;
185
+ const scrollRetryDelay = 24;
186
+
187
+ // eslint-disable-next-line no-plusplus
188
+ for (let attempt = 0; attempt <= maxScrollRetries; attempt++) {
189
+ // eslint-disable-next-line no-await-in-loop
190
+ const ok = await runScrollOnce(node, bridgedScrollRef, scrollRetryDelay);
191
+ if (ok) {
192
+ break;
193
+ }
194
+ if (attempt < maxScrollRetries) {
195
+ // eslint-disable-next-line no-await-in-loop
196
+ await new Promise(resolve => {
197
+ setTimeout(resolve, scrollRetryDelay);
198
+ });
199
+ }
200
+ }
201
+ };
202
+ exports.scrollToSelectedAnnotation = scrollToSelectedAnnotation;
@@ -4,95 +4,30 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
- var _react = _interopRequireWildcard(require("react"));
7
+ var _react = _interopRequireDefault(require("react"));
8
8
  var _propTypes = _interopRequireDefault(require("prop-types"));
9
- var _canvases = require("mirador/dist/es/src/state/selectors/canvases");
10
- var actions = _interopRequireWildcard(require("mirador/dist/es/src/state/actions"));
11
- var _LocalStorageAdapter = _interopRequireDefault(require("../annotationAdapter/LocalStorageAdapter"));
12
- var _AnnototAdapter = _interopRequireDefault(require("../annotationAdapter/AnnototAdapter"));
13
- var _AnnotationAdapterUtils = require("../annotationAdapter/AnnotationAdapterUtils");
14
9
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
- function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
16
- function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
17
10
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
18
11
  /** Functional component version of ExternalStorageAnnotation */
19
12
  function ExternalStorageAnnotation({
20
- canvases,
21
- config,
22
- receiveAnnotation,
23
- PluginComponents,
13
+ PluginComponents = [],
24
14
  TargetComponent,
25
15
  targetProps
26
16
  }) {
27
- const retrieveAnnotations = (0, _react.useCallback)(currentCanvases => {
28
- currentCanvases.forEach(canvas => {
29
- if (typeof config.annotation.adapter === 'string' && config.annotation.adapter === _AnnotationAdapterUtils.AnnotationAdapter.LOCAL_STORAGE) {
30
- config.annotation.adapter = canvasId => new _LocalStorageAdapter.default(`localStorage://?canvasId=${canvasId}`);
31
- }
32
- if (typeof config.annotation.adapter === 'string' && config.annotation.adapter === _AnnotationAdapterUtils.AnnotationAdapter.ANNOTOT) {
33
- const endpointUrl = 'http://127.0.0.1:3000/annotations';
34
- config.annotation.adapter = canvasId => new _AnnototAdapter.default(canvasId, endpointUrl);
35
- }
36
- if (!config.annotation.adapter) {
37
- config.annotation.adapter = canvasId => new _LocalStorageAdapter.default(`localStorage://?canvasId=${canvasId}`);
38
- }
39
- const storageAdapter = config.annotation.adapter(canvas.id);
40
- storageAdapter.all().then(annoPage => {
41
- if (annoPage) {
42
- receiveAnnotation(canvas.id, storageAdapter.annotationPageId, annoPage);
43
- }
44
- });
45
- });
46
- }, [config, receiveAnnotation]);
47
- (0, _react.useEffect)(() => {
48
- retrieveAnnotations(canvases);
49
- }, [canvases, retrieveAnnotations]);
50
17
  return /*#__PURE__*/_react.default.createElement(TargetComponent, _extends({}, targetProps, {
51
18
  // eslint-disable-line react/jsx-props-no-spreading
52
19
  PluginComponents: PluginComponents
53
20
  }));
54
21
  }
55
22
  ExternalStorageAnnotation.propTypes = {
56
- canvases: _propTypes.default.arrayOf(_propTypes.default.shape({
57
- id: _propTypes.default.string,
58
- index: _propTypes.default.number
59
- })),
60
- config: _propTypes.default.shape({
61
- annotation: _propTypes.default.shape({
62
- adapter: _propTypes.default.func
63
- })
64
- }).isRequired,
65
- PluginComponents: _propTypes.default.array,
23
+ PluginComponents: _propTypes.default.array.isRequired,
66
24
  // eslint-disable-line react/forbid-prop-types
67
- receiveAnnotation: _propTypes.default.func.isRequired,
68
25
  TargetComponent: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.node]).isRequired,
69
26
  targetProps: _propTypes.default.object.isRequired // eslint-disable-line react/forbid-prop-types
70
27
  };
71
- ExternalStorageAnnotation.defaultProps = {
72
- canvases: [],
73
- PluginComponents: []
74
- };
75
-
76
- /** */
77
- const mapDispatchToProps = {
78
- receiveAnnotation: actions.receiveAnnotation
79
- };
80
-
81
- /** */
82
- function mapStateToProps(state, {
83
- targetProps
84
- }) {
85
- return {
86
- canvases: (0, _canvases.getVisibleCanvases)(state, {
87
- windowId: targetProps.windowId
88
- }),
89
- config: state.config
90
- };
91
- }
92
- var _default = exports.default = {
28
+ const externalStorageAnnotationPlugin = {
93
29
  component: ExternalStorageAnnotation,
94
- mapDispatchToProps,
95
- mapStateToProps,
96
30
  mode: 'wrap',
97
31
  target: 'Window'
98
- };
32
+ };
33
+ var _default = exports.default = externalStorageAnnotationPlugin;