@agent-native/core 0.155.0 → 0.157.0
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/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +3 -1
- package/corpus/templates/assets/app/i18n-data.ts +21 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +17 -13
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +102 -59
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +15 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +16 -2
- package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +98 -29
- package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +45 -91
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +94 -2
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +17 -29
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +1 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +42 -2
- package/corpus/templates/slides/app/global.css +7 -40
- package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +130 -45
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/production-agent.d.ts +30 -0
- package/dist/agent/production-agent.js +100 -8
- package/dist/agent/types.d.ts +9 -0
- package/dist/client/AgentPanel.js +12 -9
- package/dist/client/AssistantChat.d.ts +15 -1
- package/dist/client/AssistantChat.js +110 -43
- package/dist/client/MultiTabAssistantChat.js +44 -27
- package/dist/client/chat/markdown-renderer.js +4 -2
- package/dist/client/chat/message-components.d.ts +2 -2
- package/dist/client/chat/message-components.js +69 -35
- package/dist/client/chat/run-recovery.d.ts +4 -1
- package/dist/client/chat/run-recovery.js +62 -30
- package/dist/client/chat/tool-call-display.d.ts +12 -3
- package/dist/client/chat/tool-call-display.js +79 -40
- package/dist/client/chat/widgets/DataChartRenderer.js +3 -1
- package/dist/client/chat/widgets/DataChartWidget.js +8 -2
- package/dist/client/chat/widgets/DataInsightsWidget.js +3 -1
- package/dist/client/chat/widgets/DataTableWidget.js +23 -9
- package/dist/client/chat/widgets/builtin-tool-renderers.js +3 -1
- package/dist/client/components/ui/message-scroller.d.ts +1 -1
- package/dist/client/components/ui/message-scroller.js +7 -3
- package/dist/client/composer/runtime-adapters.js +3 -2
- package/dist/client/context-xray/ContextMeter.js +5 -1
- package/dist/client/context-xray/ContextXRayPanel.js +5 -5
- package/dist/client/error-format.d.ts +4 -0
- package/dist/client/error-format.js +93 -5
- package/dist/client/i18n.js +83 -17
- package/dist/client/observability/ThumbsFeedback.js +10 -8
- package/dist/client/sharing/ShareButton.js +160 -54
- package/dist/coding-tools/run-code.js +2 -1
- package/dist/coding-tools/sandbox/background.d.ts +1 -0
- package/dist/coding-tools/sandbox/background.js +8 -1
- package/dist/coding-tools/sandbox/executions-store.d.ts +2 -0
- package/dist/coding-tools/sandbox/executions-store.js +41 -3
- package/dist/collab/routes.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.d.ts +3 -0
- package/dist/localization/core-messages/ar-SA.js +541 -0
- package/dist/localization/core-messages/de-DE.d.ts +3 -0
- package/dist/localization/core-messages/de-DE.js +513 -0
- package/dist/localization/core-messages/en-US.d.ts +513 -0
- package/dist/localization/core-messages/en-US.js +513 -0
- package/dist/localization/core-messages/es-ES.d.ts +3 -0
- package/dist/localization/core-messages/es-ES.js +520 -0
- package/dist/localization/core-messages/fr-FR.d.ts +3 -0
- package/dist/localization/core-messages/fr-FR.js +520 -0
- package/dist/localization/core-messages/hi-IN.d.ts +3 -0
- package/dist/localization/core-messages/hi-IN.js +513 -0
- package/dist/localization/core-messages/ja-JP.d.ts +3 -0
- package/dist/localization/core-messages/ja-JP.js +506 -0
- package/dist/localization/core-messages/ko-KR.d.ts +3 -0
- package/dist/localization/core-messages/ko-KR.js +506 -0
- package/dist/localization/core-messages/pt-BR.d.ts +3 -0
- package/dist/localization/core-messages/pt-BR.js +520 -0
- package/dist/localization/core-messages/zh-CN.d.ts +3 -0
- package/dist/localization/core-messages/zh-CN.js +506 -0
- package/dist/localization/core-messages/zh-TW.d.ts +3 -0
- package/dist/localization/core-messages/zh-TW.js +506 -0
- package/dist/localization/core-messages.d.ts +524 -0
- package/dist/localization/core-messages.js +124 -0
- package/dist/localization/default-messages.d.ts +2 -0
- package/dist/localization/default-messages.js +2 -0
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/plugin-options.d.ts +7 -2
- package/dist/server/agent-chat-plugin.d.ts +10 -0
- package/dist/server/agent-chat-plugin.js +144 -41
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-run-context.d.ts +7 -1
- package/dist/server/agent-run-context.js +9 -2
- package/dist/server/agent-teams-run-queue.d.ts +3 -0
- package/dist/server/agent-teams.js +38 -9
- package/dist/server/index.d.ts +1 -1
- package/dist/server/request-context.d.ts +7 -0
- package/dist/server/request-context.js +15 -0
- package/docs/content/agent-surfaces.mdx +30 -0
- package/package.json +2 -2
|
@@ -159,7 +159,9 @@ export function AssetPreviewDialog({
|
|
|
159
159
|
onClick={() => setShowDetails((value) => !value)}
|
|
160
160
|
>
|
|
161
161
|
<IconInfoCircle className="h-4 w-4" />
|
|
162
|
-
{
|
|
162
|
+
{showDetails
|
|
163
|
+
? t("library.hideDetails")
|
|
164
|
+
: t("library.viewDetails")}
|
|
163
165
|
</Button>
|
|
164
166
|
<DialogClose
|
|
165
167
|
aria-label={t("library.closePreview")}
|
|
@@ -844,10 +844,11 @@ const enUS = {
|
|
|
844
844
|
checking: "Checking",
|
|
845
845
|
checkingUpload: "Checking upload",
|
|
846
846
|
clearAll: "Clear all",
|
|
847
|
+
clearAllWithCount: "Clear all candidates ({{count}})",
|
|
847
848
|
clearCandidates: "Clear candidates",
|
|
848
849
|
clearCandidatesTitle: "Clear {{count}} candidates?",
|
|
849
850
|
clearCandidatesDescription:
|
|
850
|
-
"Clears the live stage and deletes unsaved draft
|
|
851
|
+
"Clears the live stage and deletes every unsaved draft candidate. Saved library assets stay untouched.",
|
|
851
852
|
clearGeneratedCandidatesTitle: "Clear generated candidates?",
|
|
852
853
|
clearGeneratedCandidatesDescription:
|
|
853
854
|
"This removes every unsaved candidate from the thread and deletes the generated asset rows behind them. Saved library assets are not touched.",
|
|
@@ -986,6 +987,7 @@ const enUS = {
|
|
|
986
987
|
open: "Open",
|
|
987
988
|
openAssets: "Open Assets",
|
|
988
989
|
openChat: "Open chat",
|
|
990
|
+
hideDetails: "Hide details",
|
|
989
991
|
openDetails: "Open details",
|
|
990
992
|
preparing: "Preparing...",
|
|
991
993
|
previousImage: "Previous image",
|
|
@@ -3728,7 +3730,9 @@ export const messagesByLocale = {
|
|
|
3728
3730
|
checkingUpload: "正在检查上传",
|
|
3729
3731
|
clear: "清除",
|
|
3730
3732
|
clearAll: "全部清除",
|
|
3733
|
+
clearAllWithCount: "清除所有候选项({{count}})",
|
|
3731
3734
|
clearCandidates: "清除候选项",
|
|
3735
|
+
hideDetails: "隐藏详情",
|
|
3732
3736
|
clearCandidatesDescription: "清除实时阶段并删除未保存的草稿候选项。",
|
|
3733
3737
|
clearCandidatesTitle: "清除 {{count}} 个候选项?",
|
|
3734
3738
|
clearGeneratedCandidatesDescription:
|
|
@@ -4552,7 +4556,9 @@ export const messagesByLocale = {
|
|
|
4552
4556
|
checkingImageLibraries: "Comprobando bibliotecas de imágenes...",
|
|
4553
4557
|
checkingUpload: "Comprobando carga",
|
|
4554
4558
|
clearAll: "Borrar todo",
|
|
4559
|
+
clearAllWithCount: "Borrar todos los candidatos ({{count}})",
|
|
4555
4560
|
clearCandidates: "Limpiar candidatos",
|
|
4561
|
+
hideDetails: "Ocultar detalles",
|
|
4556
4562
|
clearCandidatesDescription:
|
|
4557
4563
|
"Limpia la etapa en vivo y elimina los candidatos de borrador no guardados.",
|
|
4558
4564
|
clearGeneratedCandidatesDescription:
|
|
@@ -5026,7 +5032,9 @@ export const messagesByLocale = {
|
|
|
5026
5032
|
checkingImageLibraries: "Vérification des bibliothèques d'images...",
|
|
5027
5033
|
checkingUpload: "Vérification du téléchargement",
|
|
5028
5034
|
clearAll: "Tout effacer",
|
|
5035
|
+
clearAllWithCount: "Effacer tous les candidats ({{count}})",
|
|
5029
5036
|
clearCandidates: "Effacer les candidats",
|
|
5037
|
+
hideDetails: "Masquer les détails",
|
|
5030
5038
|
clearCandidatesDescription:
|
|
5031
5039
|
"Efface l'étape en direct et supprime les candidats de brouillon non enregistrés.",
|
|
5032
5040
|
clearGeneratedCandidatesDescription:
|
|
@@ -5479,7 +5487,9 @@ export const messagesByLocale = {
|
|
|
5479
5487
|
checkingImageLibraries: "Bildbibliotheken werden geprüft...",
|
|
5480
5488
|
checkingUpload: "Upload wird überprüft",
|
|
5481
5489
|
clearAll: "Alles löschen",
|
|
5490
|
+
clearAllWithCount: "Alle Kandidaten löschen ({{count}})",
|
|
5482
5491
|
clearCandidates: "Kandidaten löschen",
|
|
5492
|
+
hideDetails: "Details ausblenden",
|
|
5483
5493
|
clearCandidatesDescription:
|
|
5484
5494
|
"Löscht die Live-Phase und entfernt nicht gespeicherte Entwurfskandidaten.",
|
|
5485
5495
|
clearGeneratedCandidatesDescription:
|
|
@@ -5837,7 +5847,9 @@ export const messagesByLocale = {
|
|
|
5837
5847
|
checkingImageLibraries: "画像ライブラリを確認中...",
|
|
5838
5848
|
checkingUpload: "アップロードを確認しています",
|
|
5839
5849
|
clearAll: "すべてクリア",
|
|
5850
|
+
clearAllWithCount: "候補をすべてクリア({{count}})",
|
|
5840
5851
|
clearCandidates: "候補をクリアする",
|
|
5852
|
+
hideDetails: "詳細を非表示",
|
|
5841
5853
|
clearCandidatesDescription:
|
|
5842
5854
|
"ライブ ステージをクリアし、保存されていないドラフト候補を削除します。",
|
|
5843
5855
|
clearGeneratedCandidatesDescription:
|
|
@@ -6186,7 +6198,9 @@ export const messagesByLocale = {
|
|
|
6186
6198
|
checkingImageLibraries: "이미지 라이브러리 확인 중...",
|
|
6187
6199
|
checkingUpload: "업로드 확인 중",
|
|
6188
6200
|
clearAll: "모두 지우기",
|
|
6201
|
+
clearAllWithCount: "후보 모두 지우기({{count}})",
|
|
6189
6202
|
clearCandidates: "후보 제거",
|
|
6203
|
+
hideDetails: "세부정보 숨기기",
|
|
6190
6204
|
clearCandidatesDescription:
|
|
6191
6205
|
"라이브 단계를 지우고 저장되지 않은 초안 후보를 삭제합니다.",
|
|
6192
6206
|
clearGeneratedCandidatesDescription:
|
|
@@ -6534,7 +6548,9 @@ export const messagesByLocale = {
|
|
|
6534
6548
|
checkingImageLibraries: "Verificando bibliotecas de imagens...",
|
|
6535
6549
|
checkingUpload: "Verificando upload",
|
|
6536
6550
|
clearAll: "Limpar tudo",
|
|
6551
|
+
clearAllWithCount: "Limpar todos os candidatos ({{count}})",
|
|
6537
6552
|
clearCandidates: "Limpar candidatos",
|
|
6553
|
+
hideDetails: "Ocultar detalhes",
|
|
6538
6554
|
clearCandidatesDescription:
|
|
6539
6555
|
"Limpa o estágio ao vivo e exclui candidatos de rascunho não salvos.",
|
|
6540
6556
|
clearGeneratedCandidatesDescription:
|
|
@@ -6985,7 +7001,9 @@ export const messagesByLocale = {
|
|
|
6985
7001
|
checkingImageLibraries: "Image libraries जांची जा रही हैं...",
|
|
6986
7002
|
checkingUpload: "अपलोड की जाँच की जा रही है",
|
|
6987
7003
|
clearAll: "सभी साफ करें",
|
|
7004
|
+
clearAllWithCount: "सभी उम्मीदवार साफ करें ({{count}})",
|
|
6988
7005
|
clearCandidates: "उम्मीदवारों को साफ करें",
|
|
7006
|
+
hideDetails: "विवरण छिपाएं",
|
|
6989
7007
|
clearCandidatesDescription:
|
|
6990
7008
|
"लाइव चरण को साफ करता है और अ सहेजे गए ड्राफ्ट उम्मीदवारों को हटाता है।",
|
|
6991
7009
|
clearGeneratedCandidatesDescription:
|
|
@@ -7331,7 +7349,9 @@ export const messagesByLocale = {
|
|
|
7331
7349
|
checkingImageLibraries: "جارٍ فحص مكتبات الصور...",
|
|
7332
7350
|
checkingUpload: "التحقق من التحميل",
|
|
7333
7351
|
clearAll: "مسح الكل",
|
|
7352
|
+
clearAllWithCount: "مسح كل المرشحين ({{count}})",
|
|
7334
7353
|
clearCandidates: "امسح المرشحين",
|
|
7354
|
+
hideDetails: "إخفاء التفاصيل",
|
|
7335
7355
|
clearCandidatesDescription:
|
|
7336
7356
|
"مسح مرحلة المرشح وحذف المرشحين الذين تم إنشاؤهم غير المحفوظة. أصول المكتبة المحفوظة تبقى دون تغيير.",
|
|
7337
7357
|
clearGeneratedCandidatesDescription:
|
|
@@ -4533,7 +4533,9 @@ function LiveCandidatesActions({
|
|
|
4533
4533
|
const hasFailed = failedCount > 0;
|
|
4534
4534
|
const isClearing = dismissSlots.isPending || deleteAssets.isPending;
|
|
4535
4535
|
const actionLabel =
|
|
4536
|
-
pending === "failed"
|
|
4536
|
+
pending === "failed"
|
|
4537
|
+
? t("library.dismissFailed")
|
|
4538
|
+
: t("library.clearAllWithCount", { count: totalCount });
|
|
4537
4539
|
const busyLabel =
|
|
4538
4540
|
pending === "failed" ? t("library.dismissing") : t("library.clearing");
|
|
4539
4541
|
|
|
@@ -4645,26 +4647,28 @@ function LiveCandidatesActions({
|
|
|
4645
4647
|
</Button>
|
|
4646
4648
|
</DropdownMenuTrigger>
|
|
4647
4649
|
<DropdownMenuContent align="end">
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
event
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4650
|
+
{hasFailed ? (
|
|
4651
|
+
<DropdownMenuItem
|
|
4652
|
+
disabled={isClearing}
|
|
4653
|
+
onSelect={(event) => {
|
|
4654
|
+
event.preventDefault();
|
|
4655
|
+
setPending("failed");
|
|
4656
|
+
}}
|
|
4657
|
+
>
|
|
4658
|
+
<IconTrash className="mr-2 h-4 w-4 shrink-0" />
|
|
4659
|
+
{t("library.dismissFailedWithCount", { count: failedCount })}
|
|
4660
|
+
</DropdownMenuItem>
|
|
4661
|
+
) : null}
|
|
4658
4662
|
<DropdownMenuItem
|
|
4659
4663
|
className="text-destructive focus:bg-destructive/10 focus:text-destructive"
|
|
4660
|
-
disabled={isClearing}
|
|
4664
|
+
disabled={isClearing || totalCount === 0}
|
|
4661
4665
|
onSelect={(event) => {
|
|
4662
4666
|
event.preventDefault();
|
|
4663
4667
|
setPending("all");
|
|
4664
4668
|
}}
|
|
4665
4669
|
>
|
|
4666
4670
|
<IconTrash className="mr-2 h-4 w-4 shrink-0" />
|
|
4667
|
-
{t("library.
|
|
4671
|
+
{t("library.clearAllWithCount", { count: totalCount })}
|
|
4668
4672
|
</DropdownMenuItem>
|
|
4669
4673
|
</DropdownMenuContent>
|
|
4670
4674
|
</DropdownMenu>
|
|
@@ -72,6 +72,13 @@ export interface ShareRecordingPopoverProps {
|
|
|
72
72
|
animatedThumbnailUrl?: string | null;
|
|
73
73
|
isLoomRecording?: boolean;
|
|
74
74
|
hasPassword?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Restricts the dialog to a bare copy-link control for viewers who can
|
|
77
|
+
* reshare a public/org clip's link but have no edit access: it skips
|
|
78
|
+
* `list-resource-shares` (which returns every individually-shared
|
|
79
|
+
* principal's email to any reader) and hides the Invite tab entirely.
|
|
80
|
+
*/
|
|
81
|
+
viewerReshareOnly?: boolean;
|
|
75
82
|
/** Trigger element rendered as the popover anchor (usually the Share button). */
|
|
76
83
|
children: ReactNode;
|
|
77
84
|
open?: boolean;
|
|
@@ -103,6 +110,7 @@ export function ShareRecordingPopover({
|
|
|
103
110
|
animatedThumbnailUrl,
|
|
104
111
|
isLoomRecording = false,
|
|
105
112
|
hasPassword,
|
|
113
|
+
viewerReshareOnly = false,
|
|
106
114
|
children,
|
|
107
115
|
open,
|
|
108
116
|
onOpenChange,
|
|
@@ -125,6 +133,7 @@ export function ShareRecordingPopover({
|
|
|
125
133
|
animatedThumbnailUrl={animatedThumbnailUrl}
|
|
126
134
|
isLoomRecording={isLoomRecording}
|
|
127
135
|
hasPassword={hasPassword}
|
|
136
|
+
viewerReshareOnly={viewerReshareOnly}
|
|
128
137
|
/>
|
|
129
138
|
</PopoverContent>
|
|
130
139
|
</Popover>
|
|
@@ -148,6 +157,7 @@ export function ShareRecordingDialog({
|
|
|
148
157
|
open,
|
|
149
158
|
onOpenChange,
|
|
150
159
|
hasPassword,
|
|
160
|
+
viewerReshareOnly = false,
|
|
151
161
|
}: ShareRecordingDialogProps) {
|
|
152
162
|
const t = useT();
|
|
153
163
|
return (
|
|
@@ -168,6 +178,7 @@ export function ShareRecordingDialog({
|
|
|
168
178
|
animatedThumbnailUrl={animatedThumbnailUrl}
|
|
169
179
|
isLoomRecording={isLoomRecording}
|
|
170
180
|
hasPassword={hasPassword}
|
|
181
|
+
viewerReshareOnly={viewerReshareOnly}
|
|
171
182
|
reserveCloseButton
|
|
172
183
|
/>
|
|
173
184
|
</DialogContent>
|
|
@@ -186,6 +197,7 @@ function ShareRecordingContent({
|
|
|
186
197
|
isLoomRecording = false,
|
|
187
198
|
reserveCloseButton = false,
|
|
188
199
|
hasPassword,
|
|
200
|
+
viewerReshareOnly = false,
|
|
189
201
|
}: {
|
|
190
202
|
recordingId: string;
|
|
191
203
|
recordingTitle?: string;
|
|
@@ -197,20 +209,34 @@ function ShareRecordingContent({
|
|
|
197
209
|
isLoomRecording?: boolean;
|
|
198
210
|
reserveCloseButton?: boolean;
|
|
199
211
|
hasPassword?: boolean;
|
|
212
|
+
viewerReshareOnly?: boolean;
|
|
200
213
|
}) {
|
|
201
214
|
const t = useT();
|
|
202
|
-
const sharesQuery = useActionQuery<SharesResponse>(
|
|
203
|
-
|
|
204
|
-
resourceId: recordingId,
|
|
205
|
-
|
|
215
|
+
const sharesQuery = useActionQuery<SharesResponse>(
|
|
216
|
+
"list-resource-shares",
|
|
217
|
+
{ resourceType: "recording", resourceId: recordingId },
|
|
218
|
+
{ enabled: !viewerReshareOnly },
|
|
219
|
+
);
|
|
206
220
|
|
|
207
|
-
const data = sharesQuery.data;
|
|
221
|
+
const data = viewerReshareOnly ? undefined : sharesQuery.data;
|
|
208
222
|
const role = data?.role ?? initialRole;
|
|
209
223
|
const canManage = role === "owner" || role === "admin";
|
|
224
|
+
// Editors could always see (read-only) who a clip is shared with; only
|
|
225
|
+
// gate the Invite tab's mutation controls behind canManage. Commenters are
|
|
226
|
+
// grouped with plain viewers here -- neither can manage shares.
|
|
227
|
+
const canViewShares =
|
|
228
|
+
role === "owner" || role === "admin" || role === "editor";
|
|
210
229
|
const visibility =
|
|
211
230
|
(data?.visibility as Visibility | null | undefined) ??
|
|
212
231
|
initialVisibility ??
|
|
213
232
|
null;
|
|
233
|
+
// A plain viewer/commenter can't produce a working embed for a non-public
|
|
234
|
+
// clip (they have no way to make it public), so don't dangle the tab in
|
|
235
|
+
// front of them only to show an "ask the owner" dead end. Owner/admin/
|
|
236
|
+
// editor keep it regardless of visibility since they can flip to public
|
|
237
|
+
// from inside it.
|
|
238
|
+
const canEmbed = canViewShares || visibility === "public";
|
|
239
|
+
const tabCount = 1 + (canViewShares ? 1 : 0) + (canEmbed ? 1 : 0);
|
|
214
240
|
|
|
215
241
|
// Attribution `via` must be a stable non-PII id, never an email. The only
|
|
216
242
|
// owner id available client-side is the *current* session's userId, which is
|
|
@@ -234,20 +260,28 @@ function ShareRecordingContent({
|
|
|
234
260
|
defaultValue="link"
|
|
235
261
|
className={cn("min-w-0 px-4 py-3", reserveCloseButton && "pe-12")}
|
|
236
262
|
>
|
|
237
|
-
|
|
238
|
-
<
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
263
|
+
{tabCount > 1 ? (
|
|
264
|
+
<TabsList
|
|
265
|
+
className={`grid w-full ${tabCount === 3 ? "grid-cols-3" : "grid-cols-2"}`}
|
|
266
|
+
>
|
|
267
|
+
<TabsTrigger value="link" className="gap-1.5">
|
|
268
|
+
<IconLink size={14} />
|
|
269
|
+
{t("shareDialog.link")}
|
|
270
|
+
</TabsTrigger>
|
|
271
|
+
{canViewShares ? (
|
|
272
|
+
<TabsTrigger value="invite" className="gap-1.5">
|
|
273
|
+
<IconMail size={14} />
|
|
274
|
+
{t("shareDialog.invite")}
|
|
275
|
+
</TabsTrigger>
|
|
276
|
+
) : null}
|
|
277
|
+
{canEmbed ? (
|
|
278
|
+
<TabsTrigger value="embed" className="gap-1.5">
|
|
279
|
+
<IconCode size={14} />
|
|
280
|
+
{t("shareDialog.embed")}
|
|
281
|
+
</TabsTrigger>
|
|
282
|
+
) : null}
|
|
283
|
+
</TabsList>
|
|
284
|
+
) : null}
|
|
251
285
|
|
|
252
286
|
<TabsContent value="link" className="mt-3">
|
|
253
287
|
<LinkTab
|
|
@@ -262,34 +296,39 @@ function ShareRecordingContent({
|
|
|
262
296
|
animatedThumbnailUrl={animatedThumbnailUrl}
|
|
263
297
|
isLoomRecording={isLoomRecording}
|
|
264
298
|
hasPassword={hasPassword}
|
|
299
|
+
canViewShares={canViewShares}
|
|
265
300
|
/>
|
|
266
301
|
</TabsContent>
|
|
267
302
|
|
|
268
|
-
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
303
|
+
{canViewShares ? (
|
|
304
|
+
<TabsContent value="invite" className="mt-3">
|
|
305
|
+
<SharePeopleTab
|
|
306
|
+
resourceType="recording"
|
|
307
|
+
resourceId={recordingId}
|
|
308
|
+
resourceUrl={absoluteAppUrl(`/r/${recordingId}`)}
|
|
309
|
+
sharesQuery={sharesQuery}
|
|
310
|
+
canManage={canManage}
|
|
311
|
+
roleCopy={{
|
|
312
|
+
commenter: {
|
|
313
|
+
label: t("shareUi.recordingCommenter.label"),
|
|
314
|
+
description: t("shareUi.recordingCommenter.description"),
|
|
315
|
+
},
|
|
316
|
+
}}
|
|
317
|
+
/>
|
|
318
|
+
</TabsContent>
|
|
319
|
+
) : null}
|
|
320
|
+
|
|
321
|
+
{canEmbed ? (
|
|
322
|
+
<TabsContent value="embed" className="mt-3">
|
|
323
|
+
<ClipsEmbedConfigurator
|
|
324
|
+
recordingId={recordingId}
|
|
325
|
+
sharesQuery={sharesQuery}
|
|
326
|
+
visibility={visibility}
|
|
327
|
+
canManage={canManage}
|
|
328
|
+
ownerViaId={ownerViaId}
|
|
329
|
+
/>
|
|
330
|
+
</TabsContent>
|
|
331
|
+
) : null}
|
|
293
332
|
</Tabs>
|
|
294
333
|
</>
|
|
295
334
|
);
|
|
@@ -311,6 +350,7 @@ function LinkTab({
|
|
|
311
350
|
animatedThumbnailUrl,
|
|
312
351
|
isLoomRecording: isLoomRecordingProp,
|
|
313
352
|
hasPassword,
|
|
353
|
+
canViewShares,
|
|
314
354
|
}: {
|
|
315
355
|
recordingId: string;
|
|
316
356
|
recordingTitle?: string;
|
|
@@ -323,6 +363,7 @@ function LinkTab({
|
|
|
323
363
|
animatedThumbnailUrl?: string | null;
|
|
324
364
|
isLoomRecording?: boolean;
|
|
325
365
|
hasPassword?: boolean;
|
|
366
|
+
canViewShares: boolean;
|
|
326
367
|
}) {
|
|
327
368
|
const t = useT();
|
|
328
369
|
const { setResourceVisibility, isPending } = useResourceVisibilityMutation(
|
|
@@ -460,21 +501,23 @@ function LinkTab({
|
|
|
460
501
|
|
|
461
502
|
return (
|
|
462
503
|
<div className="space-y-4">
|
|
463
|
-
{
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
<div className="
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
504
|
+
{canViewShares ? (
|
|
505
|
+
visibility ? (
|
|
506
|
+
<GeneralAccessSelect
|
|
507
|
+
visibility={visibility}
|
|
508
|
+
canManage={canManage}
|
|
509
|
+
isPending={visibilityPending}
|
|
510
|
+
onChange={(next) => setResourceVisibility(next)}
|
|
511
|
+
publicDescription={t("shareDialog.publicDescription")}
|
|
512
|
+
showDescription={false}
|
|
513
|
+
/>
|
|
514
|
+
) : (
|
|
515
|
+
<div className="space-y-2" aria-hidden>
|
|
516
|
+
<div className="h-3 w-24 animate-pulse rounded bg-muted" />
|
|
517
|
+
<div className="h-12 w-full animate-pulse rounded bg-muted" />
|
|
518
|
+
</div>
|
|
519
|
+
)
|
|
520
|
+
) : null}
|
|
478
521
|
|
|
479
522
|
<CopyField
|
|
480
523
|
label={
|
|
@@ -529,6 +529,18 @@ export default function RecordingPage() {
|
|
|
529
529
|
const canDownloadRecording = Boolean(
|
|
530
530
|
recording?.enableDownloads && recording.videoUrl && !isLoomEmbedBacked,
|
|
531
531
|
);
|
|
532
|
+
// Mirrors the /share/:shareId reshare restriction (same public/org scope):
|
|
533
|
+
// a plain viewer of a public or org clip must not trigger
|
|
534
|
+
// `list-resource-shares` (any read access is enough to call it, and its
|
|
535
|
+
// response includes every individually-shared principal's email) or see a
|
|
536
|
+
// raw video download/open action independent of `enableDownloads`.
|
|
537
|
+
const viewerReshareOnly =
|
|
538
|
+
(role === "viewer" || role === "commenter") &&
|
|
539
|
+
(recording?.visibility === "public" || recording?.visibility === "org");
|
|
540
|
+
const shareVideoUrl =
|
|
541
|
+
canDownloadRecording || isLoomEmbedBacked
|
|
542
|
+
? (recording?.videoUrl ?? null)
|
|
543
|
+
: null;
|
|
532
544
|
const downloadRecording = useCallback(async () => {
|
|
533
545
|
if (!recording?.videoUrl) return;
|
|
534
546
|
setDownloading(true);
|
|
@@ -988,6 +1000,7 @@ export default function RecordingPage() {
|
|
|
988
1000
|
initialVisibility={recording.visibility}
|
|
989
1001
|
initialRole={role}
|
|
990
1002
|
hasPassword={Boolean(recording.hasPassword)}
|
|
1003
|
+
viewerReshareOnly={viewerReshareOnly}
|
|
991
1004
|
>
|
|
992
1005
|
<Button className="shrink-0 gap-1.5" size="sm">
|
|
993
1006
|
{recording.visibility !== "public" ? (
|
|
@@ -1550,11 +1563,12 @@ export default function RecordingPage() {
|
|
|
1550
1563
|
recordingTitle={recording.title}
|
|
1551
1564
|
initialVisibility={recording.visibility}
|
|
1552
1565
|
initialRole={role}
|
|
1553
|
-
videoUrl={
|
|
1566
|
+
videoUrl={shareVideoUrl}
|
|
1554
1567
|
thumbnailUrl={recording.thumbnailUrl}
|
|
1555
1568
|
animatedThumbnailUrl={recording.animatedThumbnailUrl}
|
|
1556
1569
|
isLoomRecording={isLoomEmbedBacked}
|
|
1557
1570
|
hasPassword={Boolean(recording.hasPassword)}
|
|
1571
|
+
viewerReshareOnly={viewerReshareOnly}
|
|
1558
1572
|
>
|
|
1559
1573
|
<Button
|
|
1560
1574
|
className="shrink-0 gap-1.5 bg-primary text-primary-foreground hover:bg-primary/90"
|
|
@@ -537,6 +537,15 @@ export default function ShareRoute() {
|
|
|
537
537
|
viewerRole === "editor" ||
|
|
538
538
|
viewerRole === "commenter";
|
|
539
539
|
const viewerIsOwner = Boolean(dataQ.data?.data?.viewer?.isOwner);
|
|
540
|
+
const canReshareLink =
|
|
541
|
+
(viewerRole === "viewer" || viewerRole === "commenter") &&
|
|
542
|
+
(recording?.visibility === "public" || recording?.visibility === "org");
|
|
543
|
+
// A plain viewer only gets a copy-link control: it must not trigger
|
|
544
|
+
// `list-resource-shares` (any read access is enough to call it, and its
|
|
545
|
+
// response includes every individually-shared principal's email) and must
|
|
546
|
+
// not surface the raw video download/open action independent of
|
|
547
|
+
// `enableDownloads`.
|
|
548
|
+
const viewerReshareOnly = canReshareLink && !viewerCanEdit;
|
|
540
549
|
const viewerCanOpenDashboard = Boolean(
|
|
541
550
|
dataQ.data?.data?.viewer?.canOpenDashboard,
|
|
542
551
|
);
|
|
@@ -899,6 +908,10 @@ export default function ShareRoute() {
|
|
|
899
908
|
const canDownloadRecording = Boolean(
|
|
900
909
|
recording.enableDownloads && recording.videoUrl && !isLoomEmbedBacked,
|
|
901
910
|
);
|
|
911
|
+
// Loom-backed clips only ever get an "open player" link (not a raw
|
|
912
|
+
// download), so they're exempt from the enableDownloads gate here.
|
|
913
|
+
const shareVideoUrl =
|
|
914
|
+
canDownloadRecording || isLoomEmbedBacked ? recording.videoUrl : null;
|
|
902
915
|
|
|
903
916
|
return (
|
|
904
917
|
<div className="flex min-h-screen max-w-full flex-col overflow-x-hidden bg-background text-foreground lg:h-screen lg:flex-row lg:overflow-hidden">
|
|
@@ -995,17 +1008,18 @@ export default function ShareRoute() {
|
|
|
995
1008
|
onDeleted={() => navigate("/library", { replace: true })}
|
|
996
1009
|
/>
|
|
997
1010
|
) : null}
|
|
998
|
-
{viewerCanEdit ? (
|
|
1011
|
+
{viewerCanEdit || canReshareLink ? (
|
|
999
1012
|
<ShareRecordingPopover
|
|
1000
1013
|
recordingId={recording.id}
|
|
1001
1014
|
recordingTitle={recording.title}
|
|
1002
1015
|
initialVisibility={recording.visibility}
|
|
1003
1016
|
initialRole={viewerIsOwner ? "owner" : undefined}
|
|
1004
|
-
videoUrl={
|
|
1017
|
+
videoUrl={shareVideoUrl}
|
|
1005
1018
|
thumbnailUrl={recording.thumbnailUrl}
|
|
1006
1019
|
animatedThumbnailUrl={recording.animatedThumbnailUrl}
|
|
1007
1020
|
isLoomRecording={isLoomEmbedBacked}
|
|
1008
1021
|
hasPassword={Boolean(recording.hasPassword)}
|
|
1022
|
+
viewerReshareOnly={viewerReshareOnly}
|
|
1009
1023
|
>
|
|
1010
1024
|
<Button size="sm" className="shrink-0 gap-1.5">
|
|
1011
1025
|
{recording.visibility !== "public" ? (
|