@agimon-ai/doompi-author 0.0.1-alpha.14 → 0.0.1-alpha.16
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/dist/extensions/web.mjs +604 -110
- package/dist/extensions/web.mjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/src/types/authorPreview.d.cts +25 -0
- package/dist/src/types/authorPreview.d.cts.map +1 -1
- package/dist/src/types/authorPreview.d.mts +25 -0
- package/dist/src/types/authorPreview.d.mts.map +1 -1
- package/package.json +9 -9
- package/src/types/authorPreview.ts +20 -0
package/dist/extensions/web.mjs
CHANGED
|
@@ -434,6 +434,9 @@ const TERMINAL_REQUEST_STATUSES = /* @__PURE__ */ new Set([
|
|
|
434
434
|
const EMPTY_SESSION = {
|
|
435
435
|
generation: 0,
|
|
436
436
|
activeTool: "select",
|
|
437
|
+
annotationsByDocument: {},
|
|
438
|
+
annotations: [],
|
|
439
|
+
candidateText: "",
|
|
437
440
|
regions: [],
|
|
438
441
|
requests: []
|
|
439
442
|
};
|
|
@@ -461,6 +464,46 @@ function authorSessionWorkspace(sessionId) {
|
|
|
461
464
|
if (sessionId === null) return EMPTY_SESSION;
|
|
462
465
|
return authorWorkspace.store.state.sessions[sessionId] ?? EMPTY_SESSION;
|
|
463
466
|
}
|
|
467
|
+
function hydrateAuthorAnnotationCollections(records) {
|
|
468
|
+
authorWorkspace.update((state) => {
|
|
469
|
+
let sessions = state.sessions;
|
|
470
|
+
for (const record of records) {
|
|
471
|
+
const path = normalizeAuthorPath(record.documentPath);
|
|
472
|
+
if (path === "") continue;
|
|
473
|
+
const session = sessions[record.sessionId] ?? EMPTY_SESSION;
|
|
474
|
+
const previous = session.annotationsByDocument[path];
|
|
475
|
+
if (previous !== void 0 && previous.updatedAt >= record.collection.updatedAt) continue;
|
|
476
|
+
const document = state.documents[authorDocumentKey(record.sessionId, path)];
|
|
477
|
+
const stale = record.collection.stale || document !== void 0 && (document.version !== record.collection.revision || document.sourceSha256 !== record.collection.sourceSha256);
|
|
478
|
+
const collection = restoreCollectionEvidence({
|
|
479
|
+
...record.collection,
|
|
480
|
+
stale
|
|
481
|
+
});
|
|
482
|
+
const visible = session.focusedDocument?.path === path && !stale;
|
|
483
|
+
const annotations = visible ? collection.annotations : session.annotations;
|
|
484
|
+
sessions = {
|
|
485
|
+
...sessions,
|
|
486
|
+
[record.sessionId]: {
|
|
487
|
+
...session,
|
|
488
|
+
annotationsByDocument: {
|
|
489
|
+
...session.annotationsByDocument,
|
|
490
|
+
[path]: collection
|
|
491
|
+
},
|
|
492
|
+
...visible ? {
|
|
493
|
+
annotations,
|
|
494
|
+
regions: annotations,
|
|
495
|
+
candidate: collection.candidate,
|
|
496
|
+
candidateText: collection.candidateText
|
|
497
|
+
} : {}
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
return sessions === state.sessions ? state : {
|
|
502
|
+
...state,
|
|
503
|
+
sessions
|
|
504
|
+
};
|
|
505
|
+
});
|
|
506
|
+
}
|
|
464
507
|
function putAuthorDocument(sessionId, input) {
|
|
465
508
|
const path = normalizeAuthorPath(input.path);
|
|
466
509
|
if (path === "") throw new Error("Author document path must not be empty");
|
|
@@ -484,9 +527,10 @@ function putAuthorDocument(sessionId, input) {
|
|
|
484
527
|
const session = state.sessions[sessionId];
|
|
485
528
|
let sessions = state.sessions;
|
|
486
529
|
if (sourceChanged && session?.focusedDocument?.path === path) {
|
|
487
|
-
staleThumbnails.push(...session.candidate?.thumbnailUrl === void 0 ? [] : [session.candidate.thumbnailUrl], ...session.
|
|
530
|
+
staleThumbnails.push(...session.candidate?.thumbnailUrl === void 0 ? [] : [session.candidate.thumbnailUrl], ...session.annotations.flatMap((annotation) => annotation.thumbnailUrl === void 0 ? [] : [annotation.thumbnailUrl]));
|
|
488
531
|
const now = Date.now();
|
|
489
532
|
const generation = session.generation + 1;
|
|
533
|
+
const staleCollection = staleAuthorCollection(session.annotationsByDocument[path], now);
|
|
490
534
|
sessions = {
|
|
491
535
|
...sessions,
|
|
492
536
|
[sessionId]: {
|
|
@@ -499,8 +543,14 @@ function putAuthorDocument(sessionId, input) {
|
|
|
499
543
|
sourceSha256: input.sourceSha256,
|
|
500
544
|
focusedAt: now
|
|
501
545
|
},
|
|
546
|
+
annotationsByDocument: {
|
|
547
|
+
...session.annotationsByDocument,
|
|
548
|
+
...staleCollection && { [path]: staleCollection }
|
|
549
|
+
},
|
|
550
|
+
annotations: [],
|
|
502
551
|
regions: [],
|
|
503
552
|
candidate: void 0,
|
|
553
|
+
candidateText: "",
|
|
504
554
|
requests: session.requests.map((request) => input.kind !== "story-preview" && request.documentPath === path && (request.status === "REQUESTED" || request.status === "CHANGING" || request.status === "CHANGED") ? {
|
|
505
555
|
...request,
|
|
506
556
|
status: "FAILED",
|
|
@@ -510,6 +560,20 @@ function putAuthorDocument(sessionId, input) {
|
|
|
510
560
|
} : request)
|
|
511
561
|
}
|
|
512
562
|
};
|
|
563
|
+
} else if (sourceChanged && session?.annotationsByDocument[path] !== void 0) {
|
|
564
|
+
const collection = session.annotationsByDocument[path];
|
|
565
|
+
if (collection.candidate?.thumbnailUrl !== void 0) staleThumbnails.push(collection.candidate.thumbnailUrl);
|
|
566
|
+
staleThumbnails.push(...collection.annotations.flatMap((annotation) => annotation.thumbnailUrl === void 0 ? [] : [annotation.thumbnailUrl]));
|
|
567
|
+
sessions = {
|
|
568
|
+
...sessions,
|
|
569
|
+
[sessionId]: {
|
|
570
|
+
...session,
|
|
571
|
+
annotationsByDocument: {
|
|
572
|
+
...session.annotationsByDocument,
|
|
573
|
+
[path]: staleAuthorCollection(collection, Date.now())
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
};
|
|
513
577
|
}
|
|
514
578
|
return {
|
|
515
579
|
documents: {
|
|
@@ -525,13 +589,13 @@ function putAuthorDocument(sessionId, input) {
|
|
|
525
589
|
/** Claims focus and returns a generation token that makes cleanup race-safe. */
|
|
526
590
|
function focusAuthorDocument(sessionId, path, revision, sourceSha256) {
|
|
527
591
|
const normalized = normalizeAuthorPath(path);
|
|
528
|
-
const staleThumbnails = [];
|
|
529
592
|
let generation = 0;
|
|
530
593
|
authorWorkspace.update((state) => {
|
|
531
594
|
const session = state.sessions[sessionId] ?? EMPTY_SESSION;
|
|
532
595
|
generation = session.generation + 1;
|
|
533
|
-
const
|
|
534
|
-
|
|
596
|
+
const stored = session.annotationsByDocument[normalized];
|
|
597
|
+
const current = stored !== void 0 && stored.revision === revision && stored.sourceSha256 === sourceSha256 && !stored.stale ? stored : void 0;
|
|
598
|
+
const annotations = current?.annotations ?? [];
|
|
535
599
|
return {
|
|
536
600
|
...state,
|
|
537
601
|
sessions: {
|
|
@@ -546,13 +610,14 @@ function focusAuthorDocument(sessionId, path, revision, sourceSha256) {
|
|
|
546
610
|
sourceSha256,
|
|
547
611
|
focusedAt: Date.now()
|
|
548
612
|
},
|
|
549
|
-
|
|
550
|
-
|
|
613
|
+
annotations,
|
|
614
|
+
regions: annotations,
|
|
615
|
+
candidate: current?.candidate,
|
|
616
|
+
candidateText: current?.candidateText ?? ""
|
|
551
617
|
}
|
|
552
618
|
}
|
|
553
619
|
};
|
|
554
620
|
});
|
|
555
|
-
staleThumbnails.forEach(revokeThumbnail);
|
|
556
621
|
return generation;
|
|
557
622
|
}
|
|
558
623
|
function syncAuthorDocumentFocus(sessionId, generation, revision, sourceSha256) {
|
|
@@ -571,18 +636,17 @@ function syncAuthorDocumentFocus(sessionId, generation, revision, sourceSha256)
|
|
|
571
636
|
});
|
|
572
637
|
}
|
|
573
638
|
function releaseAuthorDocumentFocus(sessionId, generation) {
|
|
574
|
-
const staleThumbnails = [];
|
|
575
639
|
updateSession(sessionId, (session) => {
|
|
576
640
|
if (session.focusedDocument?.generation !== generation) return session;
|
|
577
|
-
staleThumbnails.push(...session.candidate?.thumbnailUrl === void 0 ? [] : [session.candidate.thumbnailUrl], ...session.regions.flatMap((region) => region.thumbnailUrl === void 0 ? [] : [region.thumbnailUrl]));
|
|
578
641
|
return {
|
|
579
642
|
...session,
|
|
580
643
|
focusedDocument: void 0,
|
|
581
644
|
candidate: void 0,
|
|
645
|
+
candidateText: "",
|
|
646
|
+
annotations: [],
|
|
582
647
|
regions: []
|
|
583
648
|
};
|
|
584
649
|
});
|
|
585
|
-
staleThumbnails.forEach(revokeThumbnail);
|
|
586
650
|
}
|
|
587
651
|
function seekAuthorVideo(sessionId, timeSeconds) {
|
|
588
652
|
const workspace = authorSessionWorkspace(sessionId);
|
|
@@ -614,51 +678,96 @@ function setAuthorRegionCandidate(sessionId, candidate) {
|
|
|
614
678
|
if (focused === void 0 || focused.path !== normalizeAuthorPath(candidate.documentPath)) throw new Error("The Author selection does not belong to the focused document.");
|
|
615
679
|
if (focused.revision !== candidate.revision || focused.sourceSha256 !== candidate.sourceSha256) throw new Error("The Author selection is stale for the focused document.");
|
|
616
680
|
}
|
|
681
|
+
if (focused === void 0) return session;
|
|
617
682
|
staleThumbnail = session.candidate?.thumbnailUrl;
|
|
618
|
-
|
|
683
|
+
const copied = candidate === void 0 ? void 0 : copyCandidate(candidate);
|
|
684
|
+
return withFocusedCollection({
|
|
619
685
|
...session,
|
|
620
|
-
candidate:
|
|
621
|
-
};
|
|
686
|
+
candidate: copied
|
|
687
|
+
}, focused.path, { candidate: copied });
|
|
622
688
|
});
|
|
623
689
|
if (staleThumbnail !== void 0 && staleThumbnail !== candidate?.thumbnailUrl) revokeThumbnail(staleThumbnail);
|
|
624
690
|
}
|
|
691
|
+
function setAuthorCandidateText(sessionId, candidateText) {
|
|
692
|
+
if (new TextEncoder().encode(candidateText).byteLength > 2048) throw new Error("Author candidate text must not exceed 2 KiB.");
|
|
693
|
+
updateSession(sessionId, (session) => {
|
|
694
|
+
const path = session.focusedDocument?.path;
|
|
695
|
+
return path === void 0 ? session : withFocusedCollection({
|
|
696
|
+
...session,
|
|
697
|
+
candidateText
|
|
698
|
+
}, path, { candidateText });
|
|
699
|
+
});
|
|
700
|
+
}
|
|
625
701
|
function commitAuthorRegion(sessionId, comment) {
|
|
626
702
|
const id = crypto.randomUUID();
|
|
627
703
|
const session = authorSessionWorkspace(sessionId);
|
|
628
|
-
if (session.candidate === void 0) throw new Error("Select a document
|
|
704
|
+
if (session.candidate === void 0) throw new Error("Select a document annotation before adding a comment.");
|
|
629
705
|
addAuthorRegion(sessionId, {
|
|
630
706
|
...session.candidate,
|
|
631
707
|
id,
|
|
632
|
-
comment
|
|
708
|
+
comment,
|
|
709
|
+
version: 1
|
|
710
|
+
});
|
|
711
|
+
updateSession(sessionId, (current) => {
|
|
712
|
+
const path = current.focusedDocument?.path;
|
|
713
|
+
return path === void 0 ? current : withFocusedCollection({
|
|
714
|
+
...current,
|
|
715
|
+
candidate: void 0,
|
|
716
|
+
candidateText: ""
|
|
717
|
+
}, path, {
|
|
718
|
+
candidate: void 0,
|
|
719
|
+
candidateText: ""
|
|
720
|
+
});
|
|
633
721
|
});
|
|
634
|
-
updateSession(sessionId, (current) => ({
|
|
635
|
-
...current,
|
|
636
|
-
candidate: void 0
|
|
637
|
-
}));
|
|
638
722
|
return id;
|
|
639
723
|
}
|
|
640
724
|
function addAuthorRegion(sessionId, region) {
|
|
641
|
-
if (region.comment.trim() === "") throw new Error("Every Author
|
|
725
|
+
if (region.comment.trim() === "") throw new Error("Every Author annotation requires a comment.");
|
|
642
726
|
updateSession(sessionId, (session) => {
|
|
643
727
|
const focused = session.focusedDocument;
|
|
644
|
-
if (focused === void 0 || focused.path !== normalizeAuthorPath(region.documentPath)) throw new Error("The Author
|
|
645
|
-
if (focused.revision !== region.revision || focused.sourceSha256 !== region.sourceSha256) throw new Error("The Author
|
|
646
|
-
if (session.
|
|
647
|
-
if (session.
|
|
648
|
-
|
|
728
|
+
if (focused === void 0 || focused.path !== normalizeAuthorPath(region.documentPath)) throw new Error("The Author annotation does not belong to the focused document.");
|
|
729
|
+
if (focused.revision !== region.revision || focused.sourceSha256 !== region.sourceSha256) throw new Error("The Author annotation is stale for the focused document.");
|
|
730
|
+
if (session.annotations.length >= 16) throw new Error(`Author requests support at most 16 annotations.`);
|
|
731
|
+
if (session.annotations.some((candidate) => candidate.id === region.id)) throw new Error(`Author annotation '${region.id}' already exists.`);
|
|
732
|
+
const annotations = [...session.annotations, copyRegion({
|
|
733
|
+
...region,
|
|
734
|
+
version: region.version ?? 1
|
|
735
|
+
})];
|
|
736
|
+
return withFocusedCollection({
|
|
649
737
|
...session,
|
|
650
|
-
|
|
651
|
-
|
|
738
|
+
annotations,
|
|
739
|
+
regions: annotations
|
|
740
|
+
}, focused.path, { annotations });
|
|
652
741
|
});
|
|
653
742
|
}
|
|
654
|
-
function removeAuthorRegion(sessionId, regionId) {
|
|
743
|
+
function removeAuthorRegion(sessionId, regionId, expectedVersion, documentPath) {
|
|
655
744
|
let thumbnail;
|
|
656
745
|
updateSession(sessionId, (session) => {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
746
|
+
const path = documentPath === void 0 ? session.focusedDocument?.path : normalizeAuthorPath(documentPath);
|
|
747
|
+
if (path === void 0) return session;
|
|
748
|
+
const focused = session.focusedDocument?.path === path;
|
|
749
|
+
const collection = session.annotationsByDocument[path];
|
|
750
|
+
const current = focused ? session.annotations : collection?.annotations;
|
|
751
|
+
if (current === void 0) return session;
|
|
752
|
+
const annotation = current.find((candidate) => candidate.id === regionId);
|
|
753
|
+
if (annotation === void 0 || expectedVersion !== void 0 && (annotation.version ?? 1) !== expectedVersion) return session;
|
|
754
|
+
thumbnail = annotation.thumbnailUrl;
|
|
755
|
+
const annotations = current.filter((candidate) => candidate.id !== regionId);
|
|
756
|
+
if (focused) return withFocusedCollection({
|
|
757
|
+
...session,
|
|
758
|
+
annotations,
|
|
759
|
+
regions: annotations
|
|
760
|
+
}, path, { annotations });
|
|
761
|
+
return {
|
|
660
762
|
...session,
|
|
661
|
-
|
|
763
|
+
annotationsByDocument: {
|
|
764
|
+
...session.annotationsByDocument,
|
|
765
|
+
[path]: {
|
|
766
|
+
...collection,
|
|
767
|
+
annotations,
|
|
768
|
+
updatedAt: Date.now()
|
|
769
|
+
}
|
|
770
|
+
}
|
|
662
771
|
};
|
|
663
772
|
});
|
|
664
773
|
if (thumbnail !== void 0) revokeThumbnail(thumbnail);
|
|
@@ -774,10 +883,12 @@ function completeAuthorSave(sessionId, path, sourceSha256, savedVersion, savedFr
|
|
|
774
883
|
function dropAuthorSession(sessionId) {
|
|
775
884
|
const prefix = `${sessionId}\n`;
|
|
776
885
|
const session = authorWorkspace.store.state.sessions[sessionId];
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
886
|
+
for (const collection of Object.values(session?.annotationsByDocument ?? {})) {
|
|
887
|
+
if (collection.candidate?.thumbnailUrl !== void 0) revokeThumbnail(collection.candidate.thumbnailUrl);
|
|
888
|
+
collection.annotations.forEach((annotation) => {
|
|
889
|
+
if (annotation.thumbnailUrl !== void 0) revokeThumbnail(annotation.thumbnailUrl);
|
|
890
|
+
});
|
|
891
|
+
}
|
|
781
892
|
authorWorkspace.update((state) => {
|
|
782
893
|
const sessions = { ...state.sessions };
|
|
783
894
|
delete sessions[sessionId];
|
|
@@ -787,6 +898,12 @@ function dropAuthorSession(sessionId) {
|
|
|
787
898
|
};
|
|
788
899
|
});
|
|
789
900
|
}
|
|
901
|
+
function setAuthorStoryPreview(sessionId, path, storyPreview) {
|
|
902
|
+
updateDocument(sessionId, path, (document) => ({
|
|
903
|
+
...document,
|
|
904
|
+
storyPreview: structuredClone(storyPreview)
|
|
905
|
+
}));
|
|
906
|
+
}
|
|
790
907
|
function updateDocument(sessionId, path, update) {
|
|
791
908
|
const key = authorDocumentKey(sessionId, path);
|
|
792
909
|
const staleThumbnails = [];
|
|
@@ -797,7 +914,8 @@ function updateDocument(sessionId, path, update) {
|
|
|
797
914
|
const session = state.sessions[sessionId];
|
|
798
915
|
const focused = session?.focusedDocument;
|
|
799
916
|
const identityChanged = focused?.path === document.path && (focused.revision !== next.version || focused.sourceSha256 !== next.sourceSha256);
|
|
800
|
-
if (identityChanged) staleThumbnails.push(...session.candidate?.thumbnailUrl === void 0 ? [] : [session.candidate.thumbnailUrl], ...session.
|
|
917
|
+
if (identityChanged) staleThumbnails.push(...session.candidate?.thumbnailUrl === void 0 ? [] : [session.candidate.thumbnailUrl], ...session.annotations.flatMap((annotation) => annotation.thumbnailUrl === void 0 ? [] : [annotation.thumbnailUrl]));
|
|
918
|
+
const staleCollection = identityChanged ? staleAuthorCollection(session.annotationsByDocument[document.path], Date.now()) : void 0;
|
|
801
919
|
const sessions = identityChanged ? {
|
|
802
920
|
...state.sessions,
|
|
803
921
|
[sessionId]: {
|
|
@@ -807,7 +925,13 @@ function updateDocument(sessionId, path, update) {
|
|
|
807
925
|
revision: next.version,
|
|
808
926
|
sourceSha256: next.sourceSha256
|
|
809
927
|
},
|
|
928
|
+
annotationsByDocument: {
|
|
929
|
+
...session.annotationsByDocument,
|
|
930
|
+
...staleCollection && { [document.path]: staleCollection }
|
|
931
|
+
},
|
|
810
932
|
candidate: void 0,
|
|
933
|
+
candidateText: "",
|
|
934
|
+
annotations: [],
|
|
811
935
|
regions: []
|
|
812
936
|
}
|
|
813
937
|
} : state.sessions;
|
|
@@ -834,16 +958,60 @@ function updateSession(sessionId, update) {
|
|
|
834
958
|
};
|
|
835
959
|
});
|
|
836
960
|
}
|
|
837
|
-
function
|
|
838
|
-
const anchor = "rect" in
|
|
839
|
-
...
|
|
840
|
-
rect: { ...
|
|
841
|
-
} :
|
|
961
|
+
function copyCandidate(candidate) {
|
|
962
|
+
const anchor = "rect" in candidate.anchor ? {
|
|
963
|
+
...candidate.anchor,
|
|
964
|
+
rect: { ...candidate.anchor.rect }
|
|
965
|
+
} : "point" in candidate.anchor ? {
|
|
966
|
+
...candidate.anchor,
|
|
967
|
+
point: { ...candidate.anchor.point }
|
|
968
|
+
} : { ...candidate.anchor };
|
|
842
969
|
return {
|
|
843
|
-
...
|
|
970
|
+
...candidate,
|
|
844
971
|
anchor,
|
|
845
|
-
viewport: { ...
|
|
846
|
-
voiceGrid:
|
|
972
|
+
viewport: { ...candidate.viewport },
|
|
973
|
+
voiceGrid: candidate.voiceGrid && { ...candidate.voiceGrid }
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
function copyRegion(region) {
|
|
977
|
+
return copyCandidate(region);
|
|
978
|
+
}
|
|
979
|
+
function staleAuthorCollection(collection, updatedAt) {
|
|
980
|
+
if (collection === void 0) return void 0;
|
|
981
|
+
return {
|
|
982
|
+
...collection,
|
|
983
|
+
stale: true,
|
|
984
|
+
candidate: collection.candidate && {
|
|
985
|
+
...copyCandidate(collection.candidate),
|
|
986
|
+
thumbnailUrl: void 0
|
|
987
|
+
},
|
|
988
|
+
annotations: collection.annotations.map((annotation) => ({
|
|
989
|
+
...copyRegion(annotation),
|
|
990
|
+
thumbnailUrl: void 0
|
|
991
|
+
})),
|
|
992
|
+
updatedAt
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
function withFocusedCollection(session, path, update) {
|
|
996
|
+
const focused = session.focusedDocument;
|
|
997
|
+
if (focused === void 0 || focused.path !== path) return session;
|
|
998
|
+
const collection = {
|
|
999
|
+
...session.annotationsByDocument[path],
|
|
1000
|
+
revision: focused.revision,
|
|
1001
|
+
sourceSha256: focused.sourceSha256,
|
|
1002
|
+
stale: false,
|
|
1003
|
+
candidate: session.candidate,
|
|
1004
|
+
candidateText: session.candidateText,
|
|
1005
|
+
annotations: session.annotations,
|
|
1006
|
+
updatedAt: Date.now(),
|
|
1007
|
+
...update
|
|
1008
|
+
};
|
|
1009
|
+
return {
|
|
1010
|
+
...session,
|
|
1011
|
+
annotationsByDocument: {
|
|
1012
|
+
...session.annotationsByDocument,
|
|
1013
|
+
[path]: collection
|
|
1014
|
+
}
|
|
847
1015
|
};
|
|
848
1016
|
}
|
|
849
1017
|
function boundedHistoryText(value) {
|
|
@@ -873,6 +1041,30 @@ function boundHistory(records) {
|
|
|
873
1041
|
}
|
|
874
1042
|
return next;
|
|
875
1043
|
}
|
|
1044
|
+
function restoreCollectionEvidence(collection) {
|
|
1045
|
+
const restore = (item) => {
|
|
1046
|
+
const copied = copyCandidate(item);
|
|
1047
|
+
const existingVersion = Reflect.get(copied, "version");
|
|
1048
|
+
const versioned = "id" in copied ? {
|
|
1049
|
+
...copied,
|
|
1050
|
+
version: typeof existingVersion === "number" ? existingVersion : 1
|
|
1051
|
+
} : copied;
|
|
1052
|
+
if (versioned.thumbnailUrl !== void 0 || versioned.evidence === void 0) return versioned;
|
|
1053
|
+
try {
|
|
1054
|
+
return {
|
|
1055
|
+
...versioned,
|
|
1056
|
+
thumbnailUrl: URL.createObjectURL(versioned.evidence)
|
|
1057
|
+
};
|
|
1058
|
+
} catch {
|
|
1059
|
+
return versioned;
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
return {
|
|
1063
|
+
...collection,
|
|
1064
|
+
candidate: collection.candidate && restore(collection.candidate),
|
|
1065
|
+
annotations: collection.annotations.map(restore)
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
876
1068
|
function revokeThumbnail(url) {
|
|
877
1069
|
if (!url.startsWith("blob:")) return;
|
|
878
1070
|
try {
|
|
@@ -1053,6 +1245,8 @@ function createAuthorCapturePacket(captureId, capturedAt, document, regions) {
|
|
|
1053
1245
|
id: region.id,
|
|
1054
1246
|
ordinal: index + 1,
|
|
1055
1247
|
comment: region.comment,
|
|
1248
|
+
version: region.version ?? 1,
|
|
1249
|
+
mode: region.mode ?? ("point" in region.anchor ? "point" : "region"),
|
|
1056
1250
|
...region.quote === void 0 ? {} : { quote: utf8Prefix(region.quote, AUTHOR_QUOTE_MAX_BYTES) },
|
|
1057
1251
|
anchor: structuredClone(region.anchor),
|
|
1058
1252
|
viewport: { ...region.viewport },
|
|
@@ -1063,8 +1257,10 @@ function createAuthorCapturePacket(captureId, capturedAt, document, regions) {
|
|
|
1063
1257
|
if (bytes(JSON.stringify(packet)) > 65536) throw new Error("Author capture packet exceeds 64 KiB.");
|
|
1064
1258
|
return packet;
|
|
1065
1259
|
}
|
|
1260
|
+
function percent(value) {
|
|
1261
|
+
return `${String(Math.round(value * 1e3) / 10)}%`;
|
|
1262
|
+
}
|
|
1066
1263
|
function rectLocation(rect) {
|
|
1067
|
-
const percent = (value) => `${String(Math.round(value * 1e3) / 10)}%`;
|
|
1068
1264
|
return `x ${percent(rect.x)}, y ${percent(rect.y)}, w ${percent(rect.width)}, h ${percent(rect.height)}`;
|
|
1069
1265
|
}
|
|
1070
1266
|
function anchorLocation(anchor) {
|
|
@@ -1076,6 +1272,10 @@ function anchorLocation(anchor) {
|
|
|
1076
1272
|
case "pdf-page-rect": return `page ${String(anchor.page)}, ${rectLocation(anchor.rect)}`;
|
|
1077
1273
|
case "video-time-rect": return `time ${anchor.timeSeconds.toFixed(3)}s${anchor.frame === void 0 ? "" : `, frame ${String(anchor.frame)}`}, ${rectLocation(anchor.rect)}`;
|
|
1078
1274
|
case "story-preview-rect": return `story preview ${rectLocation(anchor.rect)}`;
|
|
1275
|
+
case "image-point": return `image point x ${percent(anchor.point.x)}, y ${percent(anchor.point.y)}`;
|
|
1276
|
+
case "pdf-page-point": return `page ${String(anchor.page)}, point x ${percent(anchor.point.x)}, y ${percent(anchor.point.y)}`;
|
|
1277
|
+
case "video-time-point": return `time ${anchor.timeSeconds.toFixed(3)}s, point x ${percent(anchor.point.x)}, y ${percent(anchor.point.y)}`;
|
|
1278
|
+
case "story-preview-point": return `story preview point x ${percent(anchor.point.x)}, y ${percent(anchor.point.y)}`;
|
|
1079
1279
|
}
|
|
1080
1280
|
}
|
|
1081
1281
|
function storyPreviewProvenance(packet) {
|
|
@@ -1103,7 +1303,7 @@ function authorCaptureContext(packet) {
|
|
|
1103
1303
|
kind: "author-capture",
|
|
1104
1304
|
source: "author",
|
|
1105
1305
|
id: packet.captureId,
|
|
1106
|
-
label: `${packet.regions.length}
|
|
1306
|
+
label: `${packet.regions.length} annotation${packet.regions.length === 1 ? "" : "s"} · ${packet.document.path}`,
|
|
1107
1307
|
content,
|
|
1108
1308
|
metadata
|
|
1109
1309
|
};
|
|
@@ -1208,6 +1408,19 @@ function multiRegionCaptureProvider(regions) {
|
|
|
1208
1408
|
context.strokeRect(rx, ry, rw, rh);
|
|
1209
1409
|
labelX = rx;
|
|
1210
1410
|
labelY = ry;
|
|
1411
|
+
} else if ("point" in region.anchor) {
|
|
1412
|
+
const px = drawX + region.anchor.point.x * drawWidth;
|
|
1413
|
+
const py = drawY + region.anchor.point.y * drawHeight;
|
|
1414
|
+
const radius = Math.max(6, Math.min(14, Math.min(drawWidth, drawHeight) / 12));
|
|
1415
|
+
context.beginPath();
|
|
1416
|
+
context.arc(px, py, radius, 0, Math.PI * 2);
|
|
1417
|
+
context.fillStyle = annotationColor;
|
|
1418
|
+
context.fill();
|
|
1419
|
+
context.strokeStyle = annotationForeground(annotationColor);
|
|
1420
|
+
context.lineWidth = 2;
|
|
1421
|
+
context.stroke();
|
|
1422
|
+
labelX = px + radius;
|
|
1423
|
+
labelY = py - radius;
|
|
1211
1424
|
}
|
|
1212
1425
|
}
|
|
1213
1426
|
const ordinal = String(index + 1);
|
|
@@ -1235,32 +1448,35 @@ function multiRegionCaptureProvider(regions) {
|
|
|
1235
1448
|
//#endregion
|
|
1236
1449
|
//#region src/extensions/workspaces/sessions/(frontend)/dock/_components/AuthorRegionDrafts.tsx
|
|
1237
1450
|
function AuthorRegionDrafts({ sessionId, workspace }) {
|
|
1238
|
-
const [comment, setComment] = useState("");
|
|
1239
1451
|
const [error, setError] = useState();
|
|
1240
1452
|
const add = () => {
|
|
1241
1453
|
try {
|
|
1242
|
-
commitAuthorRegion(sessionId,
|
|
1243
|
-
setComment("");
|
|
1454
|
+
commitAuthorRegion(sessionId, workspace.candidateText);
|
|
1244
1455
|
setError(void 0);
|
|
1245
1456
|
} catch (reason) {
|
|
1246
1457
|
setError(reason instanceof Error ? reason.message : String(reason));
|
|
1247
1458
|
}
|
|
1248
1459
|
};
|
|
1249
|
-
if (!workspace.candidate && workspace.
|
|
1460
|
+
if (!workspace.candidate && workspace.annotations.length === 0) return null;
|
|
1461
|
+
const candidateKind = workspace.candidate?.mode === "point" ? "point" : "region";
|
|
1250
1462
|
return /* @__PURE__ */ jsxs("section", {
|
|
1251
1463
|
className: "space-y-2",
|
|
1252
1464
|
"data-testid": "author-region-drafts",
|
|
1253
1465
|
children: [
|
|
1254
1466
|
workspace.candidate ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1255
|
-
/* @__PURE__ */
|
|
1467
|
+
/* @__PURE__ */ jsxs("h3", {
|
|
1256
1468
|
className: "text-base font-semibold text-doom-text",
|
|
1257
|
-
children: "Current
|
|
1469
|
+
children: ["Current ", candidateKind]
|
|
1258
1470
|
}),
|
|
1259
|
-
/* @__PURE__ */
|
|
1471
|
+
/* @__PURE__ */ jsxs("p", {
|
|
1260
1472
|
className: "text-base text-doom-dim sm:text-sm",
|
|
1261
|
-
children:
|
|
1473
|
+
children: [
|
|
1474
|
+
"Add feedback to keep this ",
|
|
1475
|
+
candidateKind,
|
|
1476
|
+
" as an unsent annotation."
|
|
1477
|
+
]
|
|
1262
1478
|
}),
|
|
1263
|
-
workspace.candidate.anchor.kind === "video-time-rect" ? /* @__PURE__ */ jsxs("p", {
|
|
1479
|
+
workspace.candidate.anchor.kind === "video-time-rect" || workspace.candidate.anchor.kind === "video-time-point" ? /* @__PURE__ */ jsxs("p", {
|
|
1264
1480
|
className: "text-base text-doom-dim sm:text-sm",
|
|
1265
1481
|
children: [
|
|
1266
1482
|
"Frame at ",
|
|
@@ -1269,9 +1485,9 @@ function AuthorRegionDrafts({ sessionId, workspace }) {
|
|
|
1269
1485
|
]
|
|
1270
1486
|
}) : null,
|
|
1271
1487
|
/* @__PURE__ */ jsx("textarea", {
|
|
1272
|
-
"aria-label": "
|
|
1273
|
-
value:
|
|
1274
|
-
onChange: (event) =>
|
|
1488
|
+
"aria-label": "Annotation comment",
|
|
1489
|
+
value: workspace.candidateText,
|
|
1490
|
+
onChange: (event) => setAuthorCandidateText(sessionId, event.target.value),
|
|
1275
1491
|
placeholder: "What should change here?",
|
|
1276
1492
|
rows: 3,
|
|
1277
1493
|
className: "min-h-11 w-full resize-y rounded border border-doom-border bg-doom-deep p-2 text-lg leading-normal text-doom-text focus:border-doom-red focus:outline-none sm:text-sm"
|
|
@@ -1280,49 +1496,51 @@ function AuthorRegionDrafts({ sessionId, workspace }) {
|
|
|
1280
1496
|
className: "min-h-11 min-w-11 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
|
|
1281
1497
|
variant: "outline",
|
|
1282
1498
|
onClick: add,
|
|
1283
|
-
disabled: !workspace.candidate || !
|
|
1284
|
-
children: "add
|
|
1499
|
+
disabled: !workspace.candidate || !workspace.candidateText.trim() || workspace.annotations.length >= 16,
|
|
1500
|
+
children: "add annotation"
|
|
1285
1501
|
}),
|
|
1286
1502
|
/* @__PURE__ */ jsx(Button, {
|
|
1287
1503
|
variant: "ghost",
|
|
1288
1504
|
className: "min-h-11 min-w-11 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
|
|
1289
1505
|
onClick: () => {
|
|
1290
1506
|
setAuthorRegionCandidate(sessionId, void 0);
|
|
1291
|
-
|
|
1507
|
+
setAuthorCandidateText(sessionId, "");
|
|
1292
1508
|
setError(void 0);
|
|
1293
1509
|
},
|
|
1294
|
-
children: "Discard
|
|
1510
|
+
children: "Discard annotation"
|
|
1295
1511
|
})
|
|
1296
1512
|
] }) : null,
|
|
1297
1513
|
error ? /* @__PURE__ */ jsx("output", {
|
|
1298
1514
|
className: "block text-base text-doom-red",
|
|
1299
1515
|
children: error
|
|
1300
1516
|
}) : null,
|
|
1301
|
-
workspace.
|
|
1517
|
+
workspace.annotations.length > 0 ? /* @__PURE__ */ jsxs("h3", {
|
|
1302
1518
|
className: "text-base font-semibold text-doom-text",
|
|
1303
1519
|
children: [
|
|
1304
|
-
"Unsent
|
|
1305
|
-
workspace.
|
|
1520
|
+
"Unsent annotations (",
|
|
1521
|
+
workspace.annotations.length,
|
|
1306
1522
|
")"
|
|
1307
1523
|
]
|
|
1308
1524
|
}) : null,
|
|
1309
1525
|
/* @__PURE__ */ jsx("ol", {
|
|
1310
1526
|
className: "space-y-2",
|
|
1311
|
-
children: workspace.
|
|
1527
|
+
children: workspace.annotations.map((region, index) => /* @__PURE__ */ jsxs("li", {
|
|
1312
1528
|
className: "rounded border border-doom-border bg-doom-panel p-2 text-base leading-normal text-doom-text sm:text-sm",
|
|
1313
1529
|
children: [
|
|
1314
1530
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
1315
1531
|
"(",
|
|
1316
1532
|
index + 1,
|
|
1317
1533
|
") ",
|
|
1534
|
+
region.mode === "point" ? "Point" : "Region",
|
|
1535
|
+
": ",
|
|
1318
1536
|
region.comment
|
|
1319
1537
|
] }),
|
|
1320
|
-
region.anchor.kind === "video-time-rect" ? /* @__PURE__ */ jsxs(Button, {
|
|
1538
|
+
region.anchor.kind === "video-time-rect" || region.anchor.kind === "video-time-point" ? /* @__PURE__ */ jsxs(Button, {
|
|
1321
1539
|
variant: "outline",
|
|
1322
1540
|
className: "min-h-11 min-w-11 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
|
|
1323
1541
|
disabled: workspace.candidate !== void 0,
|
|
1324
1542
|
onClick: () => {
|
|
1325
|
-
if (region.anchor.kind === "video-time-rect") seekAuthorVideo(sessionId, region.anchor.timeSeconds);
|
|
1543
|
+
if (region.anchor.kind === "video-time-rect" || region.anchor.kind === "video-time-point") seekAuthorVideo(sessionId, region.anchor.timeSeconds);
|
|
1326
1544
|
},
|
|
1327
1545
|
children: [
|
|
1328
1546
|
"Go to ",
|
|
@@ -1333,7 +1551,7 @@ function AuthorRegionDrafts({ sessionId, workspace }) {
|
|
|
1333
1551
|
/* @__PURE__ */ jsx(Button, {
|
|
1334
1552
|
className: "min-h-11 min-w-11 text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
|
|
1335
1553
|
variant: "ghost",
|
|
1336
|
-
"aria-label": `Remove
|
|
1554
|
+
"aria-label": `Remove annotation ${index + 1}`,
|
|
1337
1555
|
onClick: () => removeAuthorRegion(sessionId, region.id),
|
|
1338
1556
|
children: "remove"
|
|
1339
1557
|
})
|
|
@@ -1377,18 +1595,22 @@ function anchorLabel(anchor) {
|
|
|
1377
1595
|
case "pdf-page-rect": return `Page ${anchor.page}`;
|
|
1378
1596
|
case "video-time-rect": return `Video ${Math.floor(anchor.timeSeconds / 60)}:${Math.floor(anchor.timeSeconds % 60).toString().padStart(2, "0")}`;
|
|
1379
1597
|
case "story-preview-rect": return `${anchor.preview.storyPath}#${anchor.preview.storyExport}`;
|
|
1598
|
+
case "image-point": return "Image point";
|
|
1599
|
+
case "pdf-page-point": return `Page ${anchor.page} point`;
|
|
1600
|
+
case "video-time-point": return `Video ${Math.floor(anchor.timeSeconds / 60)}:${Math.floor(anchor.timeSeconds % 60).toString().padStart(2, "0")} point`;
|
|
1601
|
+
case "story-preview-point": return `${anchor.preview.storyPath}#${anchor.preview.storyExport} point`;
|
|
1380
1602
|
}
|
|
1381
1603
|
}
|
|
1382
1604
|
function progressLabel(request) {
|
|
1383
1605
|
const total = request.regions.length;
|
|
1384
1606
|
const pending = request.pendingRegions?.length;
|
|
1385
1607
|
if (request.status === "CHANGING" && request.currentOperation === "Agent is working on this request") return "Processing request";
|
|
1386
|
-
if (request.status === "CHANGING" && pending !== void 0) return `${total - pending} of ${total}
|
|
1608
|
+
if (request.status === "CHANGING" && pending !== void 0) return `${total - pending} of ${total} annotations applied`;
|
|
1387
1609
|
if (request.status === "CHANGED") return "Changes applied · awaiting save";
|
|
1388
1610
|
if (request.status === "COMPLETE") return "Agent finished processing this request";
|
|
1389
1611
|
if (request.status === "FAILED") return "Stopped with an error";
|
|
1390
1612
|
if (request.status === "CANCELLED") return "Stopped before completion";
|
|
1391
|
-
return `${total}
|
|
1613
|
+
return `${total} annotation${total === 1 ? "" : "s"} queued`;
|
|
1392
1614
|
}
|
|
1393
1615
|
function RequestRecord({ request, prominent = false }) {
|
|
1394
1616
|
const instruction = requestInstruction(request.requestText);
|
|
@@ -1591,8 +1813,9 @@ function AuthorToolPalette({ sessionId, kind, activeTool }) {
|
|
|
1591
1813
|
] : [],
|
|
1592
1814
|
{
|
|
1593
1815
|
label: "Comment",
|
|
1594
|
-
glyph: "
|
|
1595
|
-
action: () => setAuthorToolMode(sessionId, "
|
|
1816
|
+
glyph: "●",
|
|
1817
|
+
action: () => setAuthorToolMode(sessionId, activeTool === "comment" ? "select" : "comment"),
|
|
1818
|
+
active: activeTool === "comment"
|
|
1596
1819
|
}
|
|
1597
1820
|
];
|
|
1598
1821
|
return /* @__PURE__ */ jsxs("section", {
|
|
@@ -1623,7 +1846,7 @@ function AuthorToolPalette({ sessionId, kind, activeTool }) {
|
|
|
1623
1846
|
}),
|
|
1624
1847
|
/* @__PURE__ */ jsx("p", {
|
|
1625
1848
|
className: "text-base leading-normal text-doom-dim sm:text-sm",
|
|
1626
|
-
children: activeTool === "mark" ? "Drag over the document to mark a region." : "Choose Region to
|
|
1849
|
+
children: activeTool === "mark" ? "Drag over the document to mark a region." : activeTool === "comment" ? "Click the document to place a comment pin." : "Choose Region to drag an area, or Comment to place a point."
|
|
1627
1850
|
}),
|
|
1628
1851
|
error ? /* @__PURE__ */ jsx("output", {
|
|
1629
1852
|
className: "block text-base text-doom-red",
|
|
@@ -1661,7 +1884,7 @@ function AuthorPanel({ sessionId, activeMinorModes, submitCapture, statuses, ren
|
|
|
1661
1884
|
}),
|
|
1662
1885
|
/* @__PURE__ */ jsx("p", {
|
|
1663
1886
|
className: "text-base leading-normal text-doom-dim sm:text-sm",
|
|
1664
|
-
children: "
|
|
1887
|
+
children: "Drag a region or place a point, add feedback, then submit here. Requests queue while the agent is working."
|
|
1665
1888
|
}),
|
|
1666
1889
|
focused.kind === "video" ? /* @__PURE__ */ jsx("p", {
|
|
1667
1890
|
className: "text-base text-doom-dim sm:text-sm",
|
|
@@ -1687,7 +1910,7 @@ function AuthorPanel({ sessionId, activeMinorModes, submitCapture, statuses, ren
|
|
|
1687
1910
|
sessionId,
|
|
1688
1911
|
workspace
|
|
1689
1912
|
}, focused.path),
|
|
1690
|
-
workspace.
|
|
1913
|
+
workspace.annotations.length > 0 ? /* @__PURE__ */ jsxs("div", {
|
|
1691
1914
|
className: "space-y-1.5 border-b border-doom-border-soft pb-3",
|
|
1692
1915
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
1693
1916
|
className: "min-h-11 min-w-11 w-full text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
|
|
@@ -1699,9 +1922,9 @@ function AuthorPanel({ sessionId, activeMinorModes, submitCapture, statuses, ren
|
|
|
1699
1922
|
setCapturing(true);
|
|
1700
1923
|
setCaptureStatus("Submitting annotations…");
|
|
1701
1924
|
try {
|
|
1702
|
-
const packet = createAuthorCapturePacket(crypto.randomUUID(), Date.now(), focused, workspace.
|
|
1925
|
+
const packet = createAuthorCapturePacket(crypto.randomUUID(), Date.now(), focused, workspace.annotations);
|
|
1703
1926
|
await submitCapture({
|
|
1704
|
-
...await multiRegionCaptureProvider(workspace.
|
|
1927
|
+
...await multiRegionCaptureProvider(workspace.annotations).capture(),
|
|
1705
1928
|
context: authorCaptureContext(packet)
|
|
1706
1929
|
});
|
|
1707
1930
|
setCaptureStatus("Request submitted. You can keep annotating here.");
|
|
@@ -1711,10 +1934,10 @@ function AuthorPanel({ sessionId, activeMinorModes, submitCapture, statuses, ren
|
|
|
1711
1934
|
setCapturing(false);
|
|
1712
1935
|
}
|
|
1713
1936
|
},
|
|
1714
|
-
children: capturing ? "Submitting…" : `Submit ${workspace.
|
|
1937
|
+
children: capturing ? "Submitting…" : `Submit ${workspace.annotations.length} annotation${workspace.annotations.length === 1 ? "" : "s"}`
|
|
1715
1938
|
}), workspace.candidate ? /* @__PURE__ */ jsx("p", {
|
|
1716
1939
|
className: "text-base text-doom-dim sm:text-sm",
|
|
1717
|
-
children: "Add or discard the current
|
|
1940
|
+
children: "Add or discard the current annotation before submitting."
|
|
1718
1941
|
}) : null]
|
|
1719
1942
|
}) : null,
|
|
1720
1943
|
submitCapture === void 0 ? /* @__PURE__ */ jsx("output", {
|
|
@@ -2733,16 +2956,24 @@ function AuthorMediaView({ sessionId, document: source, activeTool, displayedReg
|
|
|
2733
2956
|
start.current = void 0;
|
|
2734
2957
|
if (!drag) return;
|
|
2735
2958
|
const bounds = drag.element.getBoundingClientRect();
|
|
2736
|
-
|
|
2959
|
+
if (bounds.width <= 0 || bounds.height <= 0) return;
|
|
2960
|
+
const point = {
|
|
2961
|
+
x: Math.min(1, Math.max(0, (event.clientX - bounds.left) / bounds.width)),
|
|
2962
|
+
y: Math.min(1, Math.max(0, (event.clientY - bounds.top) / bounds.height))
|
|
2963
|
+
};
|
|
2964
|
+
const rect = drag.mode === "mark" ? normalizedAuthorRectangle(bounds, {
|
|
2737
2965
|
left: drag.x,
|
|
2738
2966
|
top: drag.y,
|
|
2739
2967
|
right: event.clientX,
|
|
2740
2968
|
bottom: event.clientY
|
|
2741
|
-
});
|
|
2742
|
-
if (!rect) return;
|
|
2743
|
-
setSelection({
|
|
2969
|
+
}) : void 0;
|
|
2970
|
+
if (drag.mode === "mark" && !rect) return;
|
|
2971
|
+
setSelection(drag.mode === "mark" ? {
|
|
2744
2972
|
path: source.path,
|
|
2745
2973
|
rect
|
|
2974
|
+
} : {
|
|
2975
|
+
path: source.path,
|
|
2976
|
+
point
|
|
2746
2977
|
});
|
|
2747
2978
|
const focused = authorSessionWorkspace(sessionId).focusedDocument;
|
|
2748
2979
|
if (!focused || focused.path !== source.path) return;
|
|
@@ -2753,11 +2984,16 @@ function AuthorMediaView({ sessionId, document: source, activeTool, displayedReg
|
|
|
2753
2984
|
if (drag.element instanceof HTMLImageElement) {
|
|
2754
2985
|
const image = drag.element;
|
|
2755
2986
|
if (!image.naturalWidth || !image.naturalHeight) throw new Error("Image is not ready.");
|
|
2756
|
-
anchor = {
|
|
2987
|
+
anchor = drag.mode === "mark" ? {
|
|
2757
2988
|
kind: "image-rect",
|
|
2758
2989
|
rect,
|
|
2759
2990
|
naturalWidth: image.naturalWidth,
|
|
2760
2991
|
naturalHeight: image.naturalHeight
|
|
2992
|
+
} : {
|
|
2993
|
+
kind: "image-point",
|
|
2994
|
+
point,
|
|
2995
|
+
naturalWidth: image.naturalWidth,
|
|
2996
|
+
naturalHeight: image.naturalHeight
|
|
2761
2997
|
};
|
|
2762
2998
|
const canvas = document.createElement("canvas");
|
|
2763
2999
|
const scale = Math.min(1, 1600 / Math.max(image.naturalWidth, image.naturalHeight));
|
|
@@ -2770,21 +3006,31 @@ function AuthorMediaView({ sessionId, document: source, activeTool, displayedReg
|
|
|
2770
3006
|
} else if (source.kind === "pdf") {
|
|
2771
3007
|
const page = pdf.current?.getState();
|
|
2772
3008
|
if (!page?.sourceWidth) throw new Error("PDF page is not ready.");
|
|
2773
|
-
anchor = {
|
|
3009
|
+
anchor = drag.mode === "mark" ? {
|
|
2774
3010
|
kind: "pdf-page-rect",
|
|
2775
3011
|
page: page.page,
|
|
2776
3012
|
rect
|
|
3013
|
+
} : {
|
|
3014
|
+
kind: "pdf-page-point",
|
|
3015
|
+
page: page.page,
|
|
3016
|
+
point
|
|
2777
3017
|
};
|
|
2778
3018
|
blob = await pdf.current.capturePage();
|
|
2779
3019
|
} else {
|
|
2780
3020
|
const frame = await video.current?.captureFrame();
|
|
2781
3021
|
if (!frame) throw new Error("Video frame is not ready.");
|
|
2782
|
-
anchor = {
|
|
3022
|
+
anchor = drag.mode === "mark" ? {
|
|
2783
3023
|
kind: "video-time-rect",
|
|
2784
3024
|
rect,
|
|
2785
3025
|
timeSeconds: frame.timeSeconds,
|
|
2786
3026
|
intrinsicWidth: frame.width,
|
|
2787
3027
|
intrinsicHeight: frame.height
|
|
3028
|
+
} : {
|
|
3029
|
+
kind: "video-time-point",
|
|
3030
|
+
point,
|
|
3031
|
+
timeSeconds: frame.timeSeconds,
|
|
3032
|
+
intrinsicWidth: frame.width,
|
|
3033
|
+
intrinsicHeight: frame.height
|
|
2788
3034
|
};
|
|
2789
3035
|
blob = frame.blob;
|
|
2790
3036
|
}
|
|
@@ -2795,12 +3041,14 @@ function AuthorMediaView({ sessionId, document: source, activeTool, displayedReg
|
|
|
2795
3041
|
documentPath: source.path,
|
|
2796
3042
|
revision: source.version,
|
|
2797
3043
|
sourceSha256: source.sourceSha256,
|
|
3044
|
+
mode: drag.mode === "mark" ? "region" : "point",
|
|
2798
3045
|
anchor,
|
|
2799
3046
|
viewport: {
|
|
2800
3047
|
width: bounds.width,
|
|
2801
3048
|
height: bounds.height
|
|
2802
3049
|
},
|
|
2803
3050
|
thumbnailUrl,
|
|
3051
|
+
evidence: blob,
|
|
2804
3052
|
createdAt: Date.now()
|
|
2805
3053
|
});
|
|
2806
3054
|
setError(void 0);
|
|
@@ -2810,6 +3058,7 @@ function AuthorMediaView({ sessionId, document: source, activeTool, displayedReg
|
|
|
2810
3058
|
}
|
|
2811
3059
|
};
|
|
2812
3060
|
const selected = selection?.path === source.path ? selection.rect : void 0;
|
|
3061
|
+
const selectedPoint = selection?.path === source.path ? selection.point : void 0;
|
|
2813
3062
|
const selectionOverlay = selected === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
2814
3063
|
"data-testid": "author-media-selection",
|
|
2815
3064
|
className: "pointer-events-none absolute border-2 border-doom-red bg-doom-red/10",
|
|
@@ -2820,6 +3069,17 @@ function AuthorMediaView({ sessionId, document: source, activeTool, displayedReg
|
|
|
2820
3069
|
height: `${selected.height * 100}%`
|
|
2821
3070
|
}
|
|
2822
3071
|
});
|
|
3072
|
+
const pointPin = (ordinal, point, key) => /* @__PURE__ */ jsx("span", {
|
|
3073
|
+
"data-author-point": ordinal,
|
|
3074
|
+
"data-testid": ordinal === void 0 ? "author-media-point-selection" : void 0,
|
|
3075
|
+
className: `pointer-events-none absolute z-10 flex h-5 min-w-5 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border-2 px-1 text-2xs font-bold ${ordinal === void 0 ? "border-white bg-doom-red text-white" : "border-doom-deep bg-doom-yellow text-doom-deep"}`,
|
|
3076
|
+
style: {
|
|
3077
|
+
left: `${String(point.x * 100)}%`,
|
|
3078
|
+
top: `${String(point.y * 100)}%`
|
|
3079
|
+
},
|
|
3080
|
+
children: ordinal ?? "•"
|
|
3081
|
+
}, key);
|
|
3082
|
+
const pointSelectionOverlay = selectedPoint === void 0 ? null : pointPin(void 0, selectedPoint, "selection");
|
|
2823
3083
|
return /* @__PURE__ */ jsxs("div", {
|
|
2824
3084
|
ref: host,
|
|
2825
3085
|
className: "min-h-0 flex-1 overflow-auto p-4",
|
|
@@ -2829,7 +3089,7 @@ function AuthorMediaView({ sessionId, document: source, activeTool, displayedReg
|
|
|
2829
3089
|
},
|
|
2830
3090
|
onPointerMove: (event) => {
|
|
2831
3091
|
const drag = start.current;
|
|
2832
|
-
if (drag === void 0) return;
|
|
3092
|
+
if (drag === void 0 || drag.mode !== "mark") return;
|
|
2833
3093
|
const rect = normalizedAuthorRectangle(drag.element.getBoundingClientRect(), {
|
|
2834
3094
|
left: drag.x,
|
|
2835
3095
|
top: drag.y,
|
|
@@ -2842,7 +3102,7 @@ function AuthorMediaView({ sessionId, document: source, activeTool, displayedReg
|
|
|
2842
3102
|
});
|
|
2843
3103
|
},
|
|
2844
3104
|
onPointerDownCapture: (event) => {
|
|
2845
|
-
if (activeTool
|
|
3105
|
+
if (activeTool !== "mark" && activeTool !== "comment" || event.button !== 0 || pendingCandidate) return;
|
|
2846
3106
|
const element = event.target;
|
|
2847
3107
|
if (!(element instanceof HTMLImageElement || element instanceof HTMLVideoElement || element instanceof HTMLCanvasElement)) return;
|
|
2848
3108
|
event.preventDefault();
|
|
@@ -2857,6 +3117,7 @@ function AuthorMediaView({ sessionId, document: source, activeTool, displayedReg
|
|
|
2857
3117
|
start.current = {
|
|
2858
3118
|
x: event.clientX,
|
|
2859
3119
|
y: event.clientY,
|
|
3120
|
+
mode: activeTool,
|
|
2860
3121
|
element
|
|
2861
3122
|
};
|
|
2862
3123
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
@@ -2881,7 +3142,9 @@ function AuthorMediaView({ sessionId, document: source, activeTool, displayedReg
|
|
|
2881
3142
|
className: "block max-w-full"
|
|
2882
3143
|
}),
|
|
2883
3144
|
selectionOverlay,
|
|
3145
|
+
pointSelectionOverlay,
|
|
2884
3146
|
displayedRegions.flatMap(({ ordinal, region }) => {
|
|
3147
|
+
if (region.anchor.kind === "image-point") return [pointPin(ordinal, region.anchor.point, region.id)];
|
|
2885
3148
|
if (region.anchor.kind !== "image-rect") return [];
|
|
2886
3149
|
const { rect } = region.anchor;
|
|
2887
3150
|
return [/* @__PURE__ */ jsx("div", {
|
|
@@ -2917,9 +3180,32 @@ function AuthorMediaView({ sessionId, document: source, activeTool, displayedReg
|
|
|
2917
3180
|
if (state.playing) setSelection(void 0);
|
|
2918
3181
|
}
|
|
2919
3182
|
}),
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
3183
|
+
selectionOverlay,
|
|
3184
|
+
pointSelectionOverlay,
|
|
3185
|
+
displayedRegions.map(({ ordinal, region }) => {
|
|
3186
|
+
if (source.kind === "pdf") {
|
|
3187
|
+
if (region.anchor.kind !== "pdf-page-point" && region.anchor.kind !== "pdf-page-rect") return null;
|
|
3188
|
+
if (region.anchor.page !== pdf.current?.getState().page) return null;
|
|
3189
|
+
if (region.anchor.kind === "pdf-page-point") return pointPin(ordinal, region.anchor.point, region.id);
|
|
3190
|
+
const { rect } = region.anchor;
|
|
3191
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3192
|
+
"data-author-region": ordinal,
|
|
3193
|
+
className: "pointer-events-none absolute border border-doom-yellow bg-doom-yellow/10",
|
|
3194
|
+
style: {
|
|
3195
|
+
left: `${String(rect.x * 100)}%`,
|
|
3196
|
+
top: `${String(rect.y * 100)}%`,
|
|
3197
|
+
width: `${String(rect.width * 100)}%`,
|
|
3198
|
+
height: `${String(rect.height * 100)}%`
|
|
3199
|
+
},
|
|
3200
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3201
|
+
className: "bg-doom-yellow text-doom-deep",
|
|
3202
|
+
children: ordinal
|
|
3203
|
+
})
|
|
3204
|
+
}, region.id);
|
|
3205
|
+
}
|
|
3206
|
+
if (source.kind !== "video" || playback.playing) return null;
|
|
3207
|
+
if (region.anchor.kind === "video-time-point" && Math.abs(region.anchor.timeSeconds - playback.currentTime) <= .1) return pointPin(ordinal, region.anchor.point, region.id);
|
|
3208
|
+
if (region.anchor.kind !== "video-time-rect" || Math.abs(region.anchor.timeSeconds - playback.currentTime) > .1) return null;
|
|
2923
3209
|
const { rect } = region.anchor;
|
|
2924
3210
|
return /* @__PURE__ */ jsx("div", {
|
|
2925
3211
|
"data-author-region": ordinal,
|
|
@@ -2935,7 +3221,7 @@ function AuthorMediaView({ sessionId, document: source, activeTool, displayedReg
|
|
|
2935
3221
|
children: ordinal
|
|
2936
3222
|
})
|
|
2937
3223
|
}, region.id);
|
|
2938
|
-
})
|
|
3224
|
+
})
|
|
2939
3225
|
]
|
|
2940
3226
|
}),
|
|
2941
3227
|
source.kind === "video" ? /* @__PURE__ */ jsx(AuthorVideoControls, {
|
|
@@ -3267,6 +3553,20 @@ function ActiveAuthorDocumentPanel(props) {
|
|
|
3267
3553
|
};
|
|
3268
3554
|
const gridVisible = autonomousVoiceGridVisible(statuses);
|
|
3269
3555
|
const displayedRegions = displayedAuthorRegions(workspace);
|
|
3556
|
+
const displayedPreviewAnnotations = displayedRegions.flatMap(({ ordinal, region }) => {
|
|
3557
|
+
if (region.anchor.kind === "story-preview-rect") return [{
|
|
3558
|
+
ordinal,
|
|
3559
|
+
mode: "region",
|
|
3560
|
+
point: region.anchor.rect,
|
|
3561
|
+
rect: region.anchor.rect
|
|
3562
|
+
}];
|
|
3563
|
+
if (region.anchor.kind === "story-preview-point") return [{
|
|
3564
|
+
ordinal,
|
|
3565
|
+
mode: "point",
|
|
3566
|
+
point: region.anchor.point
|
|
3567
|
+
}];
|
|
3568
|
+
return [];
|
|
3569
|
+
});
|
|
3270
3570
|
const textPanel = /* @__PURE__ */ jsx(AuthorTextView, {
|
|
3271
3571
|
sessionId,
|
|
3272
3572
|
document,
|
|
@@ -3333,7 +3633,32 @@ function ActiveAuthorDocumentPanel(props) {
|
|
|
3333
3633
|
className: "relative flex min-h-0 flex-1 overflow-hidden",
|
|
3334
3634
|
children: [previewOpen && PreviewPanel !== void 0 && previewSource !== void 0 ? /* @__PURE__ */ jsx(PreviewPanel, {
|
|
3335
3635
|
...props,
|
|
3336
|
-
source: previewSource
|
|
3636
|
+
source: previewSource,
|
|
3637
|
+
activeTool: activeTool === "crop" ? "select" : activeTool,
|
|
3638
|
+
displayedAnnotations: displayedPreviewAnnotations,
|
|
3639
|
+
pendingCandidate: workspace?.candidate !== void 0,
|
|
3640
|
+
onAnnotationCandidate: (candidate) => {
|
|
3641
|
+
setAuthorStoryPreview(sessionId, document.path, candidate.preview);
|
|
3642
|
+
setAuthorRegionCandidate(sessionId, {
|
|
3643
|
+
documentPath: document.path,
|
|
3644
|
+
revision: document.version,
|
|
3645
|
+
sourceSha256: document.sourceSha256,
|
|
3646
|
+
mode: candidate.mode,
|
|
3647
|
+
anchor: candidate.mode === "region" && candidate.rect !== void 0 ? {
|
|
3648
|
+
kind: "story-preview-rect",
|
|
3649
|
+
rect: candidate.rect,
|
|
3650
|
+
preview: candidate.preview
|
|
3651
|
+
} : {
|
|
3652
|
+
kind: "story-preview-point",
|
|
3653
|
+
point: candidate.point,
|
|
3654
|
+
preview: candidate.preview
|
|
3655
|
+
},
|
|
3656
|
+
viewport: candidate.preview.viewport,
|
|
3657
|
+
evidence: candidate.evidence,
|
|
3658
|
+
thumbnailUrl: candidate.thumbnailUrl,
|
|
3659
|
+
createdAt: Date.now()
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3337
3662
|
}) : sourcePanel, previewOpen && PreviewPanel !== void 0 && previewSource !== void 0 ? null : /* @__PURE__ */ jsx(AuthorGridOverlay, {
|
|
3338
3663
|
sessionId,
|
|
3339
3664
|
document,
|
|
@@ -3395,6 +3720,157 @@ var author_toggle_web_default = defineLeaderBinding({
|
|
|
3395
3720
|
command: "minor author"
|
|
3396
3721
|
});
|
|
3397
3722
|
//#endregion
|
|
3723
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/authorAnnotationPersistence.ts
|
|
3724
|
+
const AUTHOR_ANNOTATION_DATABASE = "doompi-author-annotations";
|
|
3725
|
+
const STORE = "documents";
|
|
3726
|
+
const RECORD_VERSION = 1;
|
|
3727
|
+
function object(value) {
|
|
3728
|
+
return typeof value === "object" && value !== null;
|
|
3729
|
+
}
|
|
3730
|
+
function finite(value) {
|
|
3731
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
3732
|
+
}
|
|
3733
|
+
function point(value) {
|
|
3734
|
+
return object(value) && finite(value.x) && finite(value.y) && value.x >= 0 && value.x <= 1 && value.y >= 0 && value.y <= 1;
|
|
3735
|
+
}
|
|
3736
|
+
function rect(value) {
|
|
3737
|
+
return point(value) && object(value) && finite(value.width) && finite(value.height) && value.width > 0 && value.height > 0;
|
|
3738
|
+
}
|
|
3739
|
+
function validAnchor(value) {
|
|
3740
|
+
if (!object(value) || typeof value.kind !== "string") return false;
|
|
3741
|
+
switch (value.kind) {
|
|
3742
|
+
case "text-range": return finite(value.startOffset) && finite(value.endOffset) && finite(value.startLine) && finite(value.endLine);
|
|
3743
|
+
case "cell": return typeof value.fragmentId === "string" && typeof value.location === "string";
|
|
3744
|
+
case "slide-element": return typeof value.fragmentId === "string" && finite(value.slide) && typeof value.location === "string";
|
|
3745
|
+
case "image-rect": return rect(value.rect) && finite(value.naturalWidth) && finite(value.naturalHeight);
|
|
3746
|
+
case "pdf-page-rect": return finite(value.page) && rect(value.rect);
|
|
3747
|
+
case "video-time-rect": return finite(value.timeSeconds) && rect(value.rect);
|
|
3748
|
+
case "story-preview-rect": return rect(value.rect) && object(value.preview);
|
|
3749
|
+
case "image-point": return point(value.point) && finite(value.naturalWidth) && finite(value.naturalHeight);
|
|
3750
|
+
case "pdf-page-point": return finite(value.page) && point(value.point);
|
|
3751
|
+
case "video-time-point": return finite(value.timeSeconds) && point(value.point);
|
|
3752
|
+
case "story-preview-point": return point(value.point) && object(value.preview);
|
|
3753
|
+
default: return false;
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
function validEvidence(value) {
|
|
3757
|
+
return value === void 0 || typeof Blob !== "undefined" && value instanceof Blob && value.size <= 8388608;
|
|
3758
|
+
}
|
|
3759
|
+
function validCandidate(value) {
|
|
3760
|
+
if (!object(value)) return false;
|
|
3761
|
+
return typeof value.documentPath === "string" && normalizeAuthorPath(value.documentPath) !== "" && finite(value.revision) && (value.sourceSha256 === void 0 || typeof value.sourceSha256 === "string") && (value.mode === void 0 || value.mode === "region" || value.mode === "point") && validAnchor(value.anchor) && object(value.viewport) && finite(value.viewport.width) && finite(value.viewport.height) && finite(value.createdAt) && validEvidence(value.evidence);
|
|
3762
|
+
}
|
|
3763
|
+
function validAnnotation(value) {
|
|
3764
|
+
return validCandidate(value) && object(value) && typeof value.id === "string" && value.id !== "" && typeof value.comment === "string" && value.comment.trim() !== "" && (value.version === void 0 || Number.isInteger(value.version) && value.version >= 1);
|
|
3765
|
+
}
|
|
3766
|
+
function utf8Length(value) {
|
|
3767
|
+
return new TextEncoder().encode(value).byteLength;
|
|
3768
|
+
}
|
|
3769
|
+
function validateAuthorAnnotationRecord(input) {
|
|
3770
|
+
if (!object(input) || input.version !== RECORD_VERSION || typeof input.key !== "string") return void 0;
|
|
3771
|
+
if (typeof input.sessionId !== "string" || input.sessionId === "" || typeof input.documentPath !== "string") return void 0;
|
|
3772
|
+
const path = normalizeAuthorPath(input.documentPath);
|
|
3773
|
+
if (path === "" || input.key !== `${input.sessionId}\n${path}` || !object(input.collection)) return void 0;
|
|
3774
|
+
const collection = input.collection;
|
|
3775
|
+
if (!finite(collection.revision) || collection.sourceSha256 !== void 0 && typeof collection.sourceSha256 !== "string" || typeof collection.stale !== "boolean" || typeof collection.candidateText !== "string" || utf8Length(collection.candidateText) > 2048 || !Array.isArray(collection.annotations) || collection.annotations.length > 16 || !collection.annotations.every(validAnnotation) || collection.candidate !== void 0 && !validCandidate(collection.candidate) || !finite(collection.updatedAt)) return void 0;
|
|
3776
|
+
return input;
|
|
3777
|
+
}
|
|
3778
|
+
function durableCandidate(candidate) {
|
|
3779
|
+
const { thumbnailUrl: _thumbnailUrl, ...durable } = candidate;
|
|
3780
|
+
return durable;
|
|
3781
|
+
}
|
|
3782
|
+
function persistenceRecords() {
|
|
3783
|
+
const records = [];
|
|
3784
|
+
for (const [sessionId, session] of Object.entries(authorWorkspace.store.state.sessions)) for (const [documentPath, collection] of Object.entries(session.annotationsByDocument)) {
|
|
3785
|
+
if (collection.annotations.length === 0 && collection.candidate === void 0 && collection.candidateText === "") continue;
|
|
3786
|
+
records.push({
|
|
3787
|
+
key: `${sessionId}\n${documentPath}`,
|
|
3788
|
+
version: RECORD_VERSION,
|
|
3789
|
+
sessionId,
|
|
3790
|
+
documentPath,
|
|
3791
|
+
collection: {
|
|
3792
|
+
...collection,
|
|
3793
|
+
candidate: collection.candidate && durableCandidate(collection.candidate),
|
|
3794
|
+
annotations: collection.annotations.map(durableCandidate)
|
|
3795
|
+
}
|
|
3796
|
+
});
|
|
3797
|
+
}
|
|
3798
|
+
return records.sort((a, b) => b.collection.updatedAt - a.collection.updatedAt).slice(0, 100);
|
|
3799
|
+
}
|
|
3800
|
+
var IndexedDbAuthorAnnotationRepository = class {
|
|
3801
|
+
#database;
|
|
3802
|
+
#writes = Promise.resolve();
|
|
3803
|
+
constructor(factory) {
|
|
3804
|
+
this.#database = new Promise((resolve, reject) => {
|
|
3805
|
+
const request = factory.open(AUTHOR_ANNOTATION_DATABASE, 1);
|
|
3806
|
+
request.onupgradeneeded = () => {
|
|
3807
|
+
if (!request.result.objectStoreNames.contains(STORE)) request.result.createObjectStore(STORE, { keyPath: "key" });
|
|
3808
|
+
};
|
|
3809
|
+
request.onsuccess = () => resolve(request.result);
|
|
3810
|
+
request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("Unable to open Author annotation storage."));
|
|
3811
|
+
});
|
|
3812
|
+
}
|
|
3813
|
+
async load() {
|
|
3814
|
+
const database = await this.#database;
|
|
3815
|
+
return await new Promise((resolve, reject) => {
|
|
3816
|
+
const request = database.transaction(STORE, "readonly").objectStore(STORE).getAll();
|
|
3817
|
+
request.onsuccess = () => resolve(request.result);
|
|
3818
|
+
request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("Unable to read Author annotations."));
|
|
3819
|
+
});
|
|
3820
|
+
}
|
|
3821
|
+
replace(records) {
|
|
3822
|
+
const write = async () => {
|
|
3823
|
+
const database = await this.#database;
|
|
3824
|
+
await new Promise((resolve, reject) => {
|
|
3825
|
+
const transaction = database.transaction(STORE, "readwrite");
|
|
3826
|
+
const store = transaction.objectStore(STORE);
|
|
3827
|
+
store.clear();
|
|
3828
|
+
records.forEach((record) => store.put(record));
|
|
3829
|
+
transaction.oncomplete = () => resolve();
|
|
3830
|
+
transaction.onerror = () => reject(transaction.error ?? /* @__PURE__ */ new Error("Unable to write Author annotations."));
|
|
3831
|
+
transaction.onabort = () => reject(transaction.error ?? /* @__PURE__ */ new Error("Author annotation write was aborted."));
|
|
3832
|
+
});
|
|
3833
|
+
};
|
|
3834
|
+
this.#writes = this.#writes.catch(() => void 0).then(write);
|
|
3835
|
+
return this.#writes;
|
|
3836
|
+
}
|
|
3837
|
+
async close() {
|
|
3838
|
+
try {
|
|
3839
|
+
await this.#writes;
|
|
3840
|
+
} finally {
|
|
3841
|
+
(await this.#database).close();
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
};
|
|
3845
|
+
function startAuthorAnnotationPersistence(repository) {
|
|
3846
|
+
const factory = typeof indexedDB === "undefined" ? void 0 : indexedDB;
|
|
3847
|
+
if (repository === void 0 && factory === void 0) return async () => void 0;
|
|
3848
|
+
const storage = repository ?? new IndexedDbAuthorAnnotationRepository(factory);
|
|
3849
|
+
let stopped = false;
|
|
3850
|
+
let hydrated = false;
|
|
3851
|
+
let dirty = false;
|
|
3852
|
+
const subscription = authorWorkspace.store.subscribe(() => {
|
|
3853
|
+
if (!hydrated) dirty = true;
|
|
3854
|
+
else storage.replace(persistenceRecords()).catch(() => void 0);
|
|
3855
|
+
});
|
|
3856
|
+
storage.load().then((values) => {
|
|
3857
|
+
if (stopped) return;
|
|
3858
|
+
const records = values.map(validateAuthorAnnotationRecord).filter((record) => record !== void 0).sort((a, b) => b.collection.updatedAt - a.collection.updatedAt).slice(0, 100);
|
|
3859
|
+
hydrateAuthorAnnotationCollections(records);
|
|
3860
|
+
hydrated = true;
|
|
3861
|
+
if (dirty || records.length > 0) storage.replace(persistenceRecords()).catch(() => void 0);
|
|
3862
|
+
}).catch(() => {
|
|
3863
|
+
if (stopped) return;
|
|
3864
|
+
hydrated = true;
|
|
3865
|
+
if (dirty) storage.replace(persistenceRecords()).catch(() => void 0);
|
|
3866
|
+
});
|
|
3867
|
+
return async () => {
|
|
3868
|
+
stopped = true;
|
|
3869
|
+
subscription.unsubscribe();
|
|
3870
|
+
await storage.close();
|
|
3871
|
+
};
|
|
3872
|
+
}
|
|
3873
|
+
//#endregion
|
|
3398
3874
|
//#region src/extensions/workspaces/sessions/(frontend)/lifecycle/_lib/authorRequestLifecycle.ts
|
|
3399
3875
|
function capturePacket(content) {
|
|
3400
3876
|
try {
|
|
@@ -3417,6 +3893,8 @@ function recordAuthorComposerSubmission(submission) {
|
|
|
3417
3893
|
revision: packet.document.revision,
|
|
3418
3894
|
sourceSha256: packet.document.sourceSha256,
|
|
3419
3895
|
comment: region.comment,
|
|
3896
|
+
version: region.version ?? 1,
|
|
3897
|
+
mode: region.mode,
|
|
3420
3898
|
quote: region.quote,
|
|
3421
3899
|
anchor: structuredClone(region.anchor),
|
|
3422
3900
|
viewport: { ...region.viewport },
|
|
@@ -3436,7 +3914,7 @@ function recordAuthorComposerSubmission(submission) {
|
|
|
3436
3914
|
revision: packet.document.revision,
|
|
3437
3915
|
sourceSha256: packet.document.sourceSha256
|
|
3438
3916
|
});
|
|
3439
|
-
for (const region of packet.regions) removeAuthorRegion(submission.sessionId, region.id);
|
|
3917
|
+
for (const region of packet.regions) removeAuthorRegion(submission.sessionId, region.id, region.version ?? 1, packet.document.path);
|
|
3440
3918
|
}
|
|
3441
3919
|
}
|
|
3442
3920
|
/** Only the host's correlated execution events may settle a submitted capture. */
|
|
@@ -3461,12 +3939,14 @@ function recordAuthorCaptureStatus(event) {
|
|
|
3461
3939
|
//#region src/extensions/workspaces/sessions/(frontend)/lifecycle/_lib/authorBrowserLifecycle.ts
|
|
3462
3940
|
function startAuthorBrowserLifecycle(runtime) {
|
|
3463
3941
|
const stopBridge = startAuthorBrowserBridge(runtime);
|
|
3942
|
+
const stopPersistence = startAuthorAnnotationPersistence();
|
|
3464
3943
|
const stopSubmissions = runtime.onComposerSubmitted?.(recordAuthorComposerSubmission) ?? (() => void 0);
|
|
3465
3944
|
const stopCaptureStatus = runtime.onCaptureStatus?.(recordAuthorCaptureStatus) ?? (() => void 0);
|
|
3466
|
-
return () => {
|
|
3945
|
+
return async () => {
|
|
3467
3946
|
stopSubmissions();
|
|
3468
3947
|
stopCaptureStatus();
|
|
3469
3948
|
stopBridge();
|
|
3949
|
+
await stopPersistence().catch(() => void 0);
|
|
3470
3950
|
};
|
|
3471
3951
|
}
|
|
3472
3952
|
//#endregion
|
|
@@ -3575,13 +4055,18 @@ function resultPath(result) {
|
|
|
3575
4055
|
if (typeof result?.details !== "object" || result.details === null || !("path" in result.details)) return void 0;
|
|
3576
4056
|
return typeof result.details.path === "string" ? result.details.path : void 0;
|
|
3577
4057
|
}
|
|
3578
|
-
function OpenAuthoringFileToolCard({ args, result, running, isError, openTransientTab, openTab }) {
|
|
4058
|
+
function OpenAuthoringFileToolCard({ args, result, running, isError, output, openTransientTab, openTab }) {
|
|
3579
4059
|
const path = resultPath(result);
|
|
4060
|
+
const unavailable = !running && !isError && result !== null && path === void 0;
|
|
4061
|
+
const errorLines = isError ? (output ?? "").trim().split(/\r?\n/u).filter(Boolean).slice(0, 4).map((text) => ({
|
|
4062
|
+
text,
|
|
4063
|
+
tone: "error"
|
|
4064
|
+
})) : [];
|
|
3580
4065
|
const requestedPath = typeof args.path === "string" ? args.path : "document";
|
|
3581
4066
|
return /* @__PURE__ */ jsx(MessageItem, {
|
|
3582
4067
|
tone: toolTone({
|
|
3583
4068
|
running,
|
|
3584
|
-
isError
|
|
4069
|
+
isError: isError || unavailable
|
|
3585
4070
|
}),
|
|
3586
4071
|
children: () => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
|
|
3587
4072
|
title: "open_authoring_file",
|
|
@@ -3592,16 +4077,21 @@ function OpenAuthoringFileToolCard({ args, result, running, isError, openTransie
|
|
|
3592
4077
|
})
|
|
3593
4078
|
}), /* @__PURE__ */ jsxs(MessageItemBody, {
|
|
3594
4079
|
"data-testid": "tool-result-open_authoring_file",
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
4080
|
+
className: "flex flex-col gap-1",
|
|
4081
|
+
children: [
|
|
4082
|
+
errorLines.length > 0 ? /* @__PURE__ */ jsx(MessageLines, { lines: errorLines }) : null,
|
|
4083
|
+
/* @__PURE__ */ jsx(MessageItemStatus, {
|
|
4084
|
+
tone: isError || unavailable ? "error" : running ? "running" : void 0,
|
|
4085
|
+
children: isError ? "failed" : running ? "validating" : unavailable ? "unavailable" : path === void 0 ? "waiting" : "ready"
|
|
4086
|
+
}),
|
|
4087
|
+
!running && !isError && path !== void 0 ? /* @__PURE__ */ jsx(Button, {
|
|
4088
|
+
size: "xs",
|
|
4089
|
+
variant: "outline",
|
|
4090
|
+
"data-testid": "author-open-file",
|
|
4091
|
+
onClick: () => openAuthoringFileTab(path, openTransientTab, openTab),
|
|
4092
|
+
children: "open in Author"
|
|
4093
|
+
}) : null
|
|
4094
|
+
]
|
|
3605
4095
|
})] })
|
|
3606
4096
|
});
|
|
3607
4097
|
}
|
|
@@ -3609,7 +4099,11 @@ function OpenAuthoringFileToolCard({ args, result, running, isError, openTransie
|
|
|
3609
4099
|
//#region src/extensions/workspaces/sessions/(frontend)/tool/open-authoring-file.web.tsx
|
|
3610
4100
|
var open_authoring_file_web_default = defineToolRenderer({
|
|
3611
4101
|
tools: ["open_authoring_file"],
|
|
3612
|
-
message: OpenAuthoringFileToolCard
|
|
4102
|
+
message: OpenAuthoringFileToolCard,
|
|
4103
|
+
completionTab: ({ result }) => {
|
|
4104
|
+
const path = resultPath(result);
|
|
4105
|
+
return path === void 0 ? void 0 : authorFileTab(path);
|
|
4106
|
+
}
|
|
3613
4107
|
});
|
|
3614
4108
|
//#endregion
|
|
3615
4109
|
//#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/useAuthorToolsToolRender.ts
|