@searpent/react-image-annotate 2.3.5 → 2.3.7

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.
@@ -750,7 +750,7 @@ var examplePhotos = [{
750
750
  "Y1": 0,
751
751
  "Y2": 1
752
752
  },
753
- "text": "[{\"key\":\"articleType\",\"value\":\"news\"}, {\"key\":\"previousArticleId\",\"value\":\"prev-article-1\"}, {\"key\":\"section\",\"value\":\"editorial\"}]",
753
+ "text": "[{\"key\":\"articleType\",\"value\":\"news\"}, {\"key\":\"section\",\"value\":\"editorial\"}]",
754
754
  "groupId": "42ba09e0-b2f5-439d-bdae-201e7c37787b",
755
755
  "id": "aba453dd-f870-4510-ae40-a19eb52eb7d6"
756
756
  }, {
@@ -762,7 +762,7 @@ var examplePhotos = [{
762
762
  "Y1": 0,
763
763
  "Y2": 1
764
764
  },
765
- "text": "[{\"key\":\"articleType\",\"value\":\"ads\"}, {\"key\":\"previousArticleId\",\"value\":\"prev-article-2\"}, {\"key\":\"section\",\"value\":\"last page\"}]",
765
+ "text": "[{\"key\":\"articleType\",\"value\":\"ads\"}, {\"key\":\"section\",\"value\":\"last page\"}]",
766
766
  "groupId": "1",
767
767
  "id": "2615bf87-7247-4bde-a0f9-45413034a6a6"
768
768
  }]
@@ -84,8 +84,7 @@ export var Annotator = function Annotator(_ref) {
84
84
  _ref$save = _ref.save,
85
85
  save = _ref$save === void 0 ? function () {} : _ref$save,
86
86
  _ref$fetchImage = _ref.fetchImage,
87
- fetchImage = _ref$fetchImage === void 0 ? function () {} : _ref$fetchImage,
88
- updatedBy = _ref.updatedBy;
87
+ fetchImage = _ref$fetchImage === void 0 ? function () {} : _ref$fetchImage;
89
88
 
90
89
  if (typeof selectedImage === "string") {
91
90
  selectedImage = (images || []).findIndex(function (img) {
@@ -215,14 +214,14 @@ export var Annotator = function Annotator(_ref) {
215
214
  }
216
215
  }
217
216
  }, [state.previouslySelectedImage, state.selectedImage, state.images, state, save]); // Automatically save image shortly after specific metadata fields change
218
- // (ArticleType, PreviousArticleId), but debounce so we don't save on every keystroke.
217
+ // (ArticleType, PreviousArticleId, Section), but debounce so we don't save on every keystroke.
219
218
 
220
219
  useEffect(function () {
221
220
  var _state$images;
222
221
 
223
222
  var lastAction = state.lastAction;
224
223
 
225
- if (!lastAction || lastAction.type !== "UPDATE_METADATA" || lastAction.name !== "articleType" && lastAction.name !== "previousArticleId" || isNaN(lastAction.imageIndex)) {
224
+ if (!lastAction || lastAction.type !== "UPDATE_METADATA" || lastAction.name !== "articleType" && lastAction.name !== "previousArticleId" && lastAction.name !== "section" || isNaN(lastAction.imageIndex)) {
226
225
  return;
227
226
  }
228
227
 
@@ -446,8 +445,7 @@ export var Annotator = function Annotator(_ref) {
446
445
  recalcActive: saveActive,
447
446
  onMetadataChange: handleMetadataChange,
448
447
  onAddGroup: handleAddGroup,
449
- onRecalcClick: handleRecalcClicked,
450
- updatedBy: updatedBy
448
+ onRecalcClick: handleRecalcClicked
451
449
  }))
452
450
  );
453
451
  };
@@ -133,8 +133,7 @@ export var MainLayout = function MainLayout(_ref5) {
133
133
  saveActive = _ref5$saveActive === void 0 ? false : _ref5$saveActive,
134
134
  onMetadataChange = _ref5.onMetadataChange,
135
135
  onAddGroup = _ref5.onAddGroup,
136
- onRecalcClick = _ref5.onRecalcClick,
137
- updatedBy = _ref5.updatedBy;
136
+ onRecalcClick = _ref5.onRecalcClick;
138
137
  var classes = useStyles();
139
138
  var settings = useSettings();
140
139
  var fullScreenHandle = useFullScreenHandle();
@@ -376,8 +375,7 @@ export var MainLayout = function MainLayout(_ref5) {
376
375
  onPageClick: handlePageClick,
377
376
  onMetadataChange: onMetadataChange,
378
377
  metadataConfigs: state.metadataConfigs || [],
379
- onRecalcClick: onRecalcClick,
380
- updatedBy: updatedBy
378
+ onRecalcClick: onRecalcClick
381
379
  }),
382
380
  /*#__PURE__*/
383
381
  React.createElement(WorkspaceWrapper, null,
@@ -1,29 +1,74 @@
1
- import React from "react";
2
- import classnames from "classnames";
1
+ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
2
+ import React, { useEffect, useMemo, useState } from "react";
3
3
  export function isUpdatedByAggregator(updatedBy) {
4
4
  if (updatedBy == null) return false;
5
5
  return String(updatedBy).trim() === "AGGREGATOR";
6
6
  }
7
+ /** Non-empty lease string or null (treat undefined / "" / whitespace as no lock). */
8
+
9
+ export function normalizeMediaPresenterLeaseUntil(raw) {
10
+ if (raw == null) return null;
11
+ var s = String(raw).trim();
12
+ return s === "" ? null : s;
13
+ }
14
+ /**
15
+ * True when Dynamo `mediaPresenterLeaseUntil` (ISO UTC string) is still in the future.
16
+ * Matches the media-presenter webhook lease written by BasicLinkingFunction.
17
+ */
18
+
19
+ export function isMediaPresenterLockActive(mediaPresenterLeaseUntil) {
20
+ var s = normalizeMediaPresenterLeaseUntil(mediaPresenterLeaseUntil);
21
+ if (s == null) return false;
22
+ var t = Date.parse(s);
23
+ if (Number.isNaN(t)) return false;
24
+ return t > Date.now();
25
+ }
7
26
  /**
8
27
  * Case-level “UPDATED BY” indicator for the page strip (left panel).
9
- * Red (left) / green (right) discs, left-aligned with the page-strip switch column; “Ready” after them.
10
- * Green lit only when value is exactly AGGREGATOR.
28
+ * Red (left) / green (right) discs, left-aligned with the page-strip switch column; status label after them.
29
+ * Green lit only when `updatedBy` is exactly AGGREGATOR and the media-presenter Dynamo lease is not active.
11
30
  * `selectionKey` should change per selected page so the control remounts when paging.
12
31
  */
13
32
 
14
33
  export default function UpdatedBySemaphore(_ref) {
15
34
  var updatedBy = _ref.updatedBy,
35
+ mediaPresenterLeaseUntil = _ref.mediaPresenterLeaseUntil,
16
36
  selectionKey = _ref.selectionKey;
17
- var green = isUpdatedByAggregator(updatedBy);
37
+ var leaseStr = useMemo(function () {
38
+ return normalizeMediaPresenterLeaseUntil(mediaPresenterLeaseUntil);
39
+ }, [mediaPresenterLeaseUntil]); // While a lease string is present, re-evaluate every second so expiry clears without a parent refetch.
40
+
41
+ var _useState = useState(0),
42
+ _useState2 = _slicedToArray(_useState, 2),
43
+ setLeasePulse = _useState2[1];
44
+
45
+ useEffect(function () {
46
+ if (leaseStr == null) return undefined;
47
+ var id = window.setInterval(function () {
48
+ return setLeasePulse(function (n) {
49
+ return n + 1;
50
+ });
51
+ }, 1000);
52
+ return function () {
53
+ return window.clearInterval(id);
54
+ };
55
+ }, [leaseStr]);
56
+ var lockActive = isMediaPresenterLockActive(leaseStr);
57
+ var green = isUpdatedByAggregator(updatedBy) && !lockActive;
18
58
  var source = updatedBy == null || updatedBy === "" ? "—" : String(updatedBy);
59
+ var leaseHint = lockActive ? " Media presenter lock active (lease until ".concat(leaseStr, ").") : "";
60
+ var statusLabel = green ? "Ready" : "Do not modify";
61
+ var title = "Case last updated by: ".concat(source, ".").concat(leaseHint, " Status: ").concat(statusLabel, ". Green lamp: safe to modify (AGGREGATOR and no active media-presenter lease). Red lamp: do not modify (other updater and/or lock).");
62
+ var redClass = "ps-semaphore-lamp ps-semaphore-red" + (green ? "" : " ps-semaphore-lit");
63
+ var greenClass = "ps-semaphore-lamp ps-semaphore-green" + (green ? " ps-semaphore-lit" : "");
19
64
  return (
20
65
  /*#__PURE__*/
21
66
  React.createElement("div", {
22
- key: selectionKey,
67
+ key: "".concat(selectionKey, "|").concat(leaseStr !== null && leaseStr !== void 0 ? leaseStr : ""),
23
68
  className: "ps-updated-by-semaphore",
24
- title: "Case last updated by: ".concat(source, ". Green disc: AGGREGATOR \u2014 OK to start manual edits. Red disc: other source \u2014 review before editing."),
69
+ title: title,
25
70
  role: "img",
26
- "aria-label": green ? "OK to start manual edits (AGGREGATOR)" : "Review before manual edits (not AGGREGATOR)"
71
+ "aria-label": statusLabel
27
72
  },
28
73
  /*#__PURE__*/
29
74
  React.createElement("span", {
@@ -36,19 +81,15 @@ export default function UpdatedBySemaphore(_ref) {
36
81
  },
37
82
  /*#__PURE__*/
38
83
  React.createElement("span", {
39
- className: classnames("ps-semaphore-lamp", "ps-semaphore-red", {
40
- "ps-semaphore-lit": !green
41
- })
84
+ className: redClass
42
85
  }),
43
86
  /*#__PURE__*/
44
87
  React.createElement("span", {
45
- className: classnames("ps-semaphore-lamp", "ps-semaphore-green", {
46
- "ps-semaphore-lit": green
47
- })
88
+ className: greenClass
48
89
  }))),
49
90
  /*#__PURE__*/
50
91
  React.createElement("span", {
51
92
  className: "ps-top-bar-label ps-updated-by-semaphore__label"
52
- }, "Ready"))
93
+ }, statusLabel))
53
94
  );
54
95
  }
@@ -4,7 +4,6 @@ import classnames from "classnames";
4
4
  import './page-selector.css';
5
5
  import Locker from '../Locker';
6
6
  import Errorer from '../Errorer';
7
- import UpdatedBySemaphore from './UpdatedBySemaphore';
8
7
 
9
8
  function PageThumbnail(_ref) {
10
9
  var _metadata$find, _metadata$find$call;
@@ -149,24 +148,17 @@ function isLocked(page) {
149
148
  }
150
149
 
151
150
  function PageSelector(_ref3) {
152
- var _ref4, _ref5;
153
-
154
151
  var pages = _ref3.pages,
155
152
  onPageClick = _ref3.onPageClick,
156
153
  onMetadataChange = _ref3.onMetadataChange,
157
154
  metadataConfigs = _ref3.metadataConfigs,
158
- onRecalcClick = _ref3.onRecalcClick,
159
- updatedBy = _ref3.updatedBy;
155
+ onRecalcClick = _ref3.onRecalcClick;
160
156
 
161
157
  var _useState = useState(false),
162
158
  _useState2 = _slicedToArray(_useState, 2),
163
159
  showMetadata = _useState2[0],
164
160
  setShowMetadata = _useState2[1];
165
161
 
166
- var activePage = pages.find(function (p) {
167
- return p.isActive;
168
- });
169
- var selectionKey = (_ref4 = (_ref5 = activePage === null || activePage === void 0 ? void 0 : activePage.id) !== null && _ref5 !== void 0 ? _ref5 : activePage === null || activePage === void 0 ? void 0 : activePage.src) !== null && _ref4 !== void 0 ? _ref4 : String(pages.length);
170
162
  return (
171
163
  /*#__PURE__*/
172
164
  React.createElement("div", {
@@ -179,10 +171,6 @@ function PageSelector(_ref3) {
179
171
  className: "top-buttons"
180
172
  },
181
173
  /*#__PURE__*/
182
- React.createElement("div", {
183
- className: "page-selector-top-controls"
184
- },
185
- /*#__PURE__*/
186
174
  React.createElement("div", {
187
175
  className: "show-metadata-wrapper"
188
176
  },
@@ -206,18 +194,7 @@ function PageSelector(_ref3) {
206
194
  className: "slider round"
207
195
  })),
208
196
  /*#__PURE__*/
209
- React.createElement("label", {
210
- className: "ps-top-bar-label"
211
- }, "Metadata")), updatedBy !== undefined && updatedBy !== null &&
212
- /*#__PURE__*/
213
- React.createElement("div", {
214
- className: "ps-semaphore-below-metadata"
215
- },
216
- /*#__PURE__*/
217
- React.createElement(UpdatedBySemaphore, {
218
- updatedBy: updatedBy,
219
- selectionKey: selectionKey
220
- })))),
197
+ React.createElement("label", null, "Metadata"))),
221
198
  /*#__PURE__*/
222
199
  React.createElement("div", {
223
200
  className: "pages"
@@ -4,6 +4,8 @@
4
4
  transition: width .5s;
5
5
  }
6
6
 
7
+ .page-selector--opened {}
8
+
7
9
  .pages {
8
10
  list-style: none;
9
11
  display: flex;
@@ -103,132 +105,6 @@
103
105
  z-index: 100;
104
106
  }
105
107
 
106
- .page-selector-top-controls {
107
- display: flex;
108
- flex-direction: column;
109
- align-items: stretch;
110
- width: 100%;
111
- gap: 0.5rem;
112
- }
113
-
114
- /* Same typography as “Metadata” label (plain label next to switch) */
115
- .ps-top-bar-label {
116
- font-family: inherit;
117
- font-size: 1rem;
118
- font-weight: 400;
119
- line-height: 1.5;
120
- color: inherit;
121
- cursor: default;
122
- }
123
-
124
- .ps-semaphore-below-metadata {
125
- display: flex;
126
- justify-content: flex-start;
127
- align-items: center;
128
- width: 100%;
129
- padding-top: 0.15rem;
130
- padding-left: 0;
131
- box-sizing: border-box;
132
- }
133
-
134
- /* Same slot as .switch (60px) + .mr-2 (1rem) — lines up with Metadata toggle row */
135
- .ps-semaphore-toggle-column {
136
- width: 60px;
137
- margin-right: 1rem;
138
- display: flex;
139
- justify-content: center;
140
- align-items: center;
141
- flex-shrink: 0;
142
- box-sizing: border-box;
143
- }
144
-
145
- /* Plain text + discs to the right — no box */
146
- .ps-updated-by-semaphore {
147
- display: flex;
148
- flex-direction: row;
149
- align-items: center;
150
- justify-content: flex-start;
151
- gap: 0.5rem;
152
- padding: 0;
153
- margin: 0;
154
- border: none;
155
- background: none;
156
- box-shadow: none;
157
- flex-shrink: 0;
158
- }
159
-
160
- .ps-updated-by-semaphore__label {
161
- text-align: left;
162
- margin: 0;
163
- white-space: nowrap;
164
- }
165
-
166
- .ps-semaphore-lamps {
167
- display: flex;
168
- flex-direction: row;
169
- align-items: center;
170
- justify-content: center;
171
- gap: 6px;
172
- }
173
-
174
- /* Same diameter as Metadata toggle knob (.slider:before: 26×26) */
175
- .ps-semaphore-lamp {
176
- box-sizing: border-box;
177
- display: inline-block;
178
- vertical-align: middle;
179
- width: 26px;
180
- height: 26px;
181
- border-radius: 50%;
182
- border: 2px solid rgba(0, 0, 0, 0.1);
183
- box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
184
- transition: opacity 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease, background 0.2s ease, border-color 0.2s ease;
185
- flex-shrink: 0;
186
- }
187
-
188
- /* Inactive: still a faint tint of the lamp’s hue */
189
- .ps-semaphore-lamp:not(.ps-semaphore-lit) {
190
- opacity: 0.72;
191
- }
192
-
193
- .ps-semaphore-lamp.ps-semaphore-lit {
194
- opacity: 1;
195
- transform: scale(1.02);
196
- }
197
-
198
- /* Inactive red: soft rose / dusty red */
199
- .ps-semaphore-red {
200
- background: linear-gradient(165deg, #f0d4d4 0%, #d9a8a8 45%, #c08080 100%);
201
- border-color: rgba(180, 90, 90, 0.35);
202
- box-shadow:
203
- inset 0 2px 5px rgba(255, 255, 255, 0.35),
204
- inset 0 -2px 6px rgba(160, 60, 60, 0.2);
205
- }
206
-
207
- .ps-semaphore-red.ps-semaphore-lit {
208
- background: linear-gradient(180deg, #ff5252 0%, #d50000 55%, #b71c1c 100%);
209
- border-color: rgba(255, 200, 200, 0.85);
210
- box-shadow:
211
- 0 0 10px rgba(213, 0, 0, 0.88),
212
- inset 0 -2px 5px rgba(0, 0, 0, 0.18);
213
- }
214
-
215
- /* Inactive green: soft mint / dusty green */
216
- .ps-semaphore-green {
217
- background: linear-gradient(165deg, #dff2df 0%, #b8dcb8 45%, #92c592 100%);
218
- border-color: rgba(80, 140, 80, 0.35);
219
- box-shadow:
220
- inset 0 2px 5px rgba(255, 255, 255, 0.4),
221
- inset 0 -2px 6px rgba(60, 120, 60, 0.18);
222
- }
223
-
224
- .ps-semaphore-green.ps-semaphore-lit {
225
- background: linear-gradient(180deg, #b9f6ca 0%, #00e676 40%, #00c853 100%);
226
- border-color: rgba(200, 255, 220, 0.9);
227
- box-shadow:
228
- 0 0 10px rgba(0, 200, 83, 0.88),
229
- inset 0 -2px 5px rgba(0, 0, 0, 0.14);
230
- }
231
-
232
108
  .top-buttons button {
233
109
  margin-bottom: 1rem;
234
110
  width: 100%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@searpent/react-image-annotate",
3
- "version": "2.3.5",
3
+ "version": "2.3.7",
4
4
  "dependencies": {
5
5
  "@editorjs/editorjs": "^2.25.0",
6
6
  "@editorjs/paragraph": "^2.8.0",