dsh-figmark 1.2.1 → 1.2.2

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/lib/client.pdf.js CHANGED
@@ -1189,6 +1189,9 @@ window.__ModuleLoader__.load({
1189
1189
  cancel: (0, react.useCallback)(() => {
1190
1190
  autosave.cancel();
1191
1191
  }, [autosave]),
1192
+ drained: (0, react.useCallback)(async () => {
1193
+ await autosave.drained();
1194
+ }, [autosave]),
1192
1195
  markWritten: (0, react.useCallback)((input) => {
1193
1196
  writtenSignature.current = signature(input);
1194
1197
  }, [signature]),
@@ -25086,20 +25089,21 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
25086
25089
  return ordinals(allMarks(marks));
25087
25090
  }
25088
25091
  /**
25089
- * Drop one mark from wherever in the document it was drawn.
25092
+ * Drop several marks from wherever in the document they were drawn.
25090
25093
  *
25091
- * The list shows the whole file, so its delete button can name a mark on a page the
25092
- * surface is not holding: the owner is the only one that knows every page.
25094
+ * A hand-off removes exactly the marks it carried. Doing that by repeatedly dropping
25095
+ * one mark would visit every page once per delivered mark, which matters on a large
25096
+ * document; this keeps it to one visit per page.
25093
25097
  *
25094
25098
  * @param marks - every page's marks.
25095
- * @param id - the mark to remove.
25096
- * @returns the new map, or the same one when no page carried that mark.
25099
+ * @param ids - the marks that were delivered.
25100
+ * @returns the new map, or the same one when no page carried any of them.
25097
25101
  */
25098
- function withoutMark(marks, id) {
25102
+ function withoutMarks(marks, ids) {
25099
25103
  let changed = false;
25100
25104
  const next = new Map(marks);
25101
25105
  for (const [page, pageMarks] of marks) {
25102
- const kept = pageMarks.filter((mark) => mark.id !== id);
25106
+ const kept = pageMarks.filter((mark) => !ids.has(mark.id));
25103
25107
  if (kept.length !== pageMarks.length) {
25104
25108
  changed = true;
25105
25109
  next.set(page, kept);
@@ -25167,6 +25171,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
25167
25171
  */
25168
25172
  async function discardDocument(context) {
25169
25173
  context.autosave.cancel();
25174
+ await context.autosave.drained();
25170
25175
  context.autosave.forget();
25171
25176
  try {
25172
25177
  await context.seat.discard(context.address);
@@ -25185,60 +25190,98 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
25185
25190
  * which they cannot see from the pill they pressed.
25186
25191
  *
25187
25192
  * @param context - the document being saved.
25188
- * @param target - who receives the annotation.
25189
25193
  * @param only - the single mark being sent, or undefined to send the document.
25194
+ * @returns the ready snapshot, or why there is none.
25190
25195
  */
25191
- async function saveAndDeliver(context, target, only) {
25196
+ function prepareDelivery(context, only) {
25192
25197
  const collected = context.collect();
25193
- if (collected === null) {
25194
- context.fail(context.copy.notReady);
25195
- return;
25196
- }
25198
+ if (collected === null) return { kind: "not-ready" };
25197
25199
  const input = only === void 0 ? collected : {
25198
25200
  ...collected,
25199
25201
  marks: collected.marks.filter((mark) => mark.id === only.id)
25200
25202
  };
25201
- const { pdf, bytes } = context;
25202
- if (input.marks.length === 0) {
25203
- context.fail(context.copy.nothingToSend);
25204
- return;
25205
- }
25203
+ if (input.marks.length === 0) return { kind: "nothing" };
25206
25204
  /* v8 ignore start -- a collected document always carries both, for the reason above. */
25207
- if (pdf === null || bytes === void 0) {
25208
- context.fail(context.copy.notReady);
25209
- return;
25210
- }
25205
+ const { pdf, bytes } = context;
25206
+ if (pdf === null || bytes === void 0) return { kind: "not-ready" };
25211
25207
  /* v8 ignore stop */
25212
- context.setBusy(true);
25213
- context.say(target === "agent" ? context.copy.sending : context.copy.composing);
25214
- try {
25215
- const pages = markedPages(groupByPage(input.marks));
25216
- const annotatedPdf = await context.annotate(bytes, pdf, pages, context.figureUnit);
25217
- const saved = await context.seat.save(context.address, input, annotatedPdf);
25218
- context.autosave.cancel();
25219
- context.autosave.markWritten(input);
25220
- context.setCreatedAt(input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString());
25221
- context.setStale(false);
25222
- const pageImages = await context.review(pdf, pages);
25223
- if (target === "composer") {
25224
- await context.seat.compose(input, saved, pageImages, annotatedPdf);
25225
- context.say(only === void 0 ? context.copy.composed : context.copy.composedOne);
25226
- } else {
25227
- const warnings = await context.seat.send(input, saved, pageImages, annotatedPdf);
25228
- const delivered = only === void 0 ? context.copy.sent : context.copy.sentOne;
25229
- context.say(`${delivered}${warnings.length === 0 ? "" : ` ${warnings.join(" ")}`}`);
25208
+ return {
25209
+ kind: "ready",
25210
+ value: {
25211
+ input,
25212
+ whole: only === void 0,
25213
+ pdf,
25214
+ bytes
25230
25215
  }
25231
- if (only === void 0) {
25232
- await discardDocument(context);
25233
- return;
25216
+ };
25217
+ }
25218
+ /**
25219
+ * Write and hand off one prepared snapshot.
25220
+ *
25221
+ * Failures are thrown rather than painted here: the caller owns the send card, and
25222
+ * that card can only show its retry button when the same task rejects.
25223
+ */
25224
+ async function saveAndDeliver(context, target, prepared) {
25225
+ context.setBusy(true);
25226
+ const outcome = await writePrepared(context, target, prepared).then((value) => ({
25227
+ ok: true,
25228
+ value
25229
+ }), (cause) => ({
25230
+ ok: false,
25231
+ cause
25232
+ }));
25233
+ context.setBusy(false);
25234
+ if (!outcome.ok) throw new Error(`${context.copy.saveFailed}${messageOf(outcome.cause)}`);
25235
+ return outcome.value;
25236
+ }
25237
+ /**
25238
+ * Write one prepared snapshot and update the document afterwards.
25239
+ *
25240
+ * Kept apart from the busy flag so a rejection has one unwind point: the
25241
+ * public function turns the promise into either its warnings or the sentence
25242
+ * the retryable toast card shows.
25243
+ */
25244
+ async function writePrepared(context, target, prepared) {
25245
+ const { input, whole, pdf, bytes } = prepared;
25246
+ const pages = markedPages(groupByPage(input.marks));
25247
+ const annotatedPdf = await context.annotate(bytes, pdf, pages, context.figureUnit);
25248
+ const saved = await context.seat.save(context.address, input, annotatedPdf);
25249
+ context.autosave.cancel();
25250
+ context.autosave.markWritten(input);
25251
+ context.setCreatedAt(input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString());
25252
+ context.setStale(false);
25253
+ const pageImages = await context.review(pdf, pages);
25254
+ if (target === "composer") await context.seat.compose(input, saved, pageImages);
25255
+ else {
25256
+ const warnings = await context.seat.send(input, saved, pageImages);
25257
+ if (whole) {
25258
+ const sentIds = new Set(input.marks.map((mark) => mark.id));
25259
+ const sentSummary = input.summary;
25260
+ context.autosave.cancel();
25261
+ await context.autosave.drained();
25262
+ try {
25263
+ await context.seat.discard(context.address);
25264
+ } catch (cause) {
25265
+ context.fail(`${context.copy.clearFailed}${messageOf(cause)}`);
25266
+ }
25267
+ context.delivered(sentIds, sentSummary, true);
25268
+ return warnings;
25234
25269
  }
25235
- context.autosave.forget();
25236
- context.dropped(only.id);
25270
+ context.autosave.cancel();
25271
+ await context.autosave.drained();
25272
+ context.delivered(new Set(input.marks.map((mark) => mark.id)), input.summary, false);
25273
+ return warnings;
25274
+ }
25275
+ const ids = new Set(input.marks.map((mark) => mark.id));
25276
+ context.autosave.cancel();
25277
+ await context.autosave.drained();
25278
+ if (whole) try {
25279
+ await context.seat.discard(context.address);
25237
25280
  } catch (cause) {
25238
- context.fail(`${context.copy.saveFailed}${messageOf(cause)}`);
25239
- } finally {
25240
- context.setBusy(false);
25281
+ context.fail(`${context.copy.clearFailed}${messageOf(cause)}`);
25241
25282
  }
25283
+ context.delivered(ids, input.summary, whole);
25284
+ return [];
25242
25285
  }
25243
25286
  //#endregion
25244
25287
  //#region node_modules/.pnpm/tslib@1.14.1/node_modules/tslib/tslib.es6.js
@@ -50953,7 +50996,8 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
50953
50996
  * @returns the document's toolbar and the page on screen, or why it is not there yet.
50954
50997
  */
50955
50998
  function PdfBody(props) {
50956
- const { AnnotatorBody, seat, t } = props;
50999
+ const { AnnotatorBody, seat, t, toasts, ToastStack } = props;
51000
+ const { note, notify } = toasts;
50957
51001
  const address = props.resourceAddress ?? "";
50958
51002
  const { createAutosave, annotationSignature, autosaveDelayMs, figureUnit, markOrdinals } = props;
50959
51003
  const bytes = props.content?.kind === "bytes" ? props.content.data : void 0;
@@ -50999,7 +51043,23 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
50999
51043
  */
51000
51044
  const [zoom, setZoom] = (0, react.useState)("fit");
51001
51045
  const [busy, setBusy] = (0, react.useState)(false);
51002
- const [status, setStatus] = (0, react.useState)(null);
51046
+ /**
51047
+ * The latest values a late delivery callback has to see.
51048
+ *
51049
+ * A send closes over the context that started it. Marks and notes added while the
51050
+ * session answers render into newer contexts; keeping the current values in a ref
51051
+ * lets the first context remove only its own snapshot without losing the newer work.
51052
+ */
51053
+ const latest = (0, react.useRef)({
51054
+ marks,
51055
+ summary,
51056
+ createdAt
51057
+ });
51058
+ latest.current = {
51059
+ marks,
51060
+ summary,
51061
+ createdAt
51062
+ };
51003
51063
  const zoomFactor = zoom === "fit" ? 1 : zoom;
51004
51064
  const renderWidth = renderWidthFor(pane.width, zoomFactor);
51005
51065
  /**
@@ -51021,11 +51081,11 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
51021
51081
  setCreatedAt(saved.createdAt);
51022
51082
  }, []),
51023
51083
  fail: (0, react.useCallback)((cause) => {
51024
- setStatus({
51084
+ note({
51025
51085
  tone: "error",
51026
51086
  text: `${translateRef.current("readError")}${messageOf(cause)}`
51027
51087
  });
51028
- }, [])
51088
+ }, [note])
51029
51089
  });
51030
51090
  (0, react.useEffect)(() => {
51031
51091
  if (pdf === null || renderWidth < 120) return;
@@ -51075,6 +51135,7 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
51075
51135
  const revealMark = (0, react.useCallback)((mark) => {
51076
51136
  setSelectedId(mark.id);
51077
51137
  if (mark.page !== void 0) paging.goTo(mark.page);
51138
+ setOpenPanel(null);
51078
51139
  }, [paging]);
51079
51140
  /** Everything one save persists, or null while the document is still unknown. */
51080
51141
  const collect = (0, react.useCallback)(() => collectInput(figure, pageCount, address, marks, summary, createdAt), [
@@ -51103,35 +51164,23 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
51103
51164
  annotationSignature,
51104
51165
  autosaveDelayMs,
51105
51166
  onError: (0, react.useCallback)((cause) => {
51106
- setStatus({
51167
+ note({
51107
51168
  tone: "error",
51108
51169
  text: `${translateRef.current("saveError")}${messageOf(cause)}`
51109
51170
  });
51110
- }, [])
51171
+ }, [note])
51111
51172
  }),
51112
- say: (0, react.useCallback)((text) => {
51113
- setStatus({
51114
- tone: "info",
51115
- text
51116
- });
51117
- }, []),
51118
51173
  fail: (0, react.useCallback)((text) => {
51119
- setStatus({
51174
+ note({
51120
51175
  tone: "error",
51121
51176
  text
51122
51177
  });
51123
- }, []),
51178
+ }, [note]),
51124
51179
  setBusy,
51125
51180
  copy: {
51126
51181
  saveFailed: t("saveError"),
51127
51182
  notReady: t("notReady"),
51128
51183
  nothingToSend: t("sendNothingToSend"),
51129
- sending: t("sending"),
51130
- composing: t("composing"),
51131
- composed: t("composed"),
51132
- composedOne: t("composedOne"),
51133
- sent: t("sent"),
51134
- sentOne: t("sentOne"),
51135
51184
  clearFailed: t("clearError")
51136
51185
  },
51137
51186
  cleared: (0, react.useCallback)(() => {
@@ -51141,20 +51190,49 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
51141
51190
  setStale(false);
51142
51191
  setGeneration((current) => current + 1);
51143
51192
  }, []),
51144
- dropped: (0, react.useCallback)((id) => {
51145
- setMarks((current) => withoutMark(current, id));
51193
+ delivered: (0, react.useCallback)((ids, sentSummary, summaryDelivered) => {
51194
+ const current = latest.current;
51195
+ const next = withoutMarks(current.marks, ids);
51196
+ const keptSummary = summaryDelivered && current.summary === sentSummary ? "" : current.summary;
51197
+ const remains = allMarks(next).length > 0 || keptSummary.trim() !== "";
51198
+ setMarks(next);
51199
+ setSummary(keptSummary);
51200
+ setCreatedAt(remains ? current.createdAt : null);
51201
+ setGeneration((current) => current + 1);
51146
51202
  }, []),
51147
51203
  setCreatedAt,
51148
51204
  setStale
51149
51205
  };
51150
51206
  const contextRef = (0, react.useRef)(saveContext);
51151
51207
  contextRef.current = saveContext;
51152
- const run = (0, react.useCallback)((target) => {
51153
- saveAndDeliver(contextRef.current, target);
51154
- }, []);
51208
+ const run = (0, react.useCallback)((target, only) => {
51209
+ const context = contextRef.current;
51210
+ const prepared = prepareDelivery(context, only);
51211
+ if (prepared.kind === "not-ready") {
51212
+ note({
51213
+ tone: "info",
51214
+ text: context.copy.notReady
51215
+ });
51216
+ return;
51217
+ }
51218
+ if (prepared.kind === "nothing") {
51219
+ notify("annotate.none");
51220
+ return;
51221
+ }
51222
+ const event = target === "agent" ? "annotate.send.agent" : only === void 0 ? "annotate.save.all" : "annotate.save.one";
51223
+ let warnings = [];
51224
+ notify(event, async () => {
51225
+ warnings = await saveAndDeliver(context, target, prepared.value);
51226
+ }).then((outcome) => {
51227
+ if (outcome === "ok" && warnings.length > 0) note({
51228
+ tone: "info",
51229
+ text: warnings.join(" ")
51230
+ });
51231
+ });
51232
+ }, [note, notify]);
51155
51233
  const runOne = (0, react.useCallback)((mark, target) => {
51156
- saveAndDeliver(contextRef.current, target, mark);
51157
- }, []);
51234
+ run(target, mark);
51235
+ }, [run]);
51158
51236
  const clearDocument = (0, react.useCallback)(() => {
51159
51237
  discardDocument(contextRef.current);
51160
51238
  }, []);
@@ -51210,10 +51288,10 @@ while (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan] &
51210
51288
  "data-tone": "error",
51211
51289
  children: t("stale")
51212
51290
  }) : null,
51213
- status === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
51214
- className: "da-status",
51215
- "data-tone": status.tone === "info" ? void 0 : status.tone,
51216
- children: status.text
51291
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToastStack, {
51292
+ view: toasts,
51293
+ label: t("close"),
51294
+ resendLabel: t("toastResend")
51217
51295
  }),
51218
51296
  error !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
51219
51297
  className: "da-hint",