@arbidocs/blocks 0.4.153 → 0.4.155
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/{ConnectedNativeDocxComparison-OFPIXNJB.js → ConnectedNativeDocxComparison-OLWRQSRS.js} +11 -11
- package/dist/{ConnectedNativeDocxComparison-OFPIXNJB.js.map → ConnectedNativeDocxComparison-OLWRQSRS.js.map} +1 -1
- package/dist/{ConnectedNativeDocxComparison-Y5BNLNH6.cjs → ConnectedNativeDocxComparison-YORMOPPN.cjs} +12 -12
- package/dist/{ConnectedNativeDocxComparison-Y5BNLNH6.cjs.map → ConnectedNativeDocxComparison-YORMOPPN.cjs.map} +1 -1
- package/dist/{ConnectedNativeDocxEditor-LRYU5HPA.js → ConnectedNativeDocxEditor-34EBIIFS.js} +10 -10
- package/dist/{ConnectedNativeDocxEditor-LRYU5HPA.js.map → ConnectedNativeDocxEditor-34EBIIFS.js.map} +1 -1
- package/dist/{ConnectedNativeDocxEditor-HTEW5JAH.cjs → ConnectedNativeDocxEditor-EHTWGGX3.cjs} +12 -12
- package/dist/{ConnectedNativeDocxEditor-HTEW5JAH.cjs.map → ConnectedNativeDocxEditor-EHTWGGX3.cjs.map} +1 -1
- package/dist/{ConnectedStreamedWordArtifact-QW74LKHN.cjs → ConnectedStreamedWordArtifact-5DK6QGYN.cjs} +12 -12
- package/dist/ConnectedStreamedWordArtifact-5DK6QGYN.cjs.map +1 -0
- package/dist/{ConnectedStreamedWordArtifact-QAUVUCSX.js → ConnectedStreamedWordArtifact-GLIDLSAS.js} +11 -11
- package/dist/{ConnectedStreamedWordArtifact-QAUVUCSX.js.map → ConnectedStreamedWordArtifact-GLIDLSAS.js.map} +1 -1
- package/dist/WorkspaceTableView-5OWX53R5.js +376 -0
- package/dist/WorkspaceTableView-5OWX53R5.js.map +1 -0
- package/dist/WorkspaceTableView-ZFMAYHUD.cjs +378 -0
- package/dist/WorkspaceTableView-ZFMAYHUD.cjs.map +1 -0
- package/dist/agents/index.cjs +282 -282
- package/dist/agents/index.js +7 -7
- package/dist/{chunk-RQ2Y7USX.cjs → chunk-2VHYOTWW.cjs} +3 -3
- package/dist/chunk-2VHYOTWW.cjs.map +1 -0
- package/dist/{chunk-DTROMMDX.cjs → chunk-3VULDTNL.cjs} +4 -4
- package/dist/{chunk-DTROMMDX.cjs.map → chunk-3VULDTNL.cjs.map} +1 -1
- package/dist/{chunk-UTTTJSQU.cjs → chunk-4RPBUMHB.cjs} +4 -4
- package/dist/{chunk-UTTTJSQU.cjs.map → chunk-4RPBUMHB.cjs.map} +1 -1
- package/dist/chunk-53RCZPMR.cjs +496 -0
- package/dist/chunk-53RCZPMR.cjs.map +1 -0
- package/dist/chunk-5TDGVOR5.js +142 -0
- package/dist/chunk-5TDGVOR5.js.map +1 -0
- package/dist/chunk-6HMCF4AJ.js +1720 -0
- package/dist/chunk-6HMCF4AJ.js.map +1 -0
- package/dist/{chunk-CU2O32Z4.cjs → chunk-6QAAIDQQ.cjs} +62 -99
- package/dist/chunk-6QAAIDQQ.cjs.map +1 -0
- package/dist/chunk-7HHYMDPE.js +431 -0
- package/dist/chunk-7HHYMDPE.js.map +1 -0
- package/dist/{chunk-SMN32BQ3.cjs → chunk-7NL6WJER.cjs} +3 -160
- package/dist/chunk-7NL6WJER.cjs.map +1 -0
- package/dist/{chunk-IXWJGOM2.js → chunk-BQ77KSXU.js} +3 -3
- package/dist/{chunk-IXWJGOM2.js.map → chunk-BQ77KSXU.js.map} +1 -1
- package/dist/chunk-BTF4AZMG.js +148 -0
- package/dist/chunk-BTF4AZMG.js.map +1 -0
- package/dist/{chunk-HALD6ZB5.cjs → chunk-C3SMTA3C.cjs} +4 -4
- package/dist/{chunk-HALD6ZB5.cjs.map → chunk-C3SMTA3C.cjs.map} +1 -1
- package/dist/{chunk-GAYMJWTC.cjs → chunk-CB4TIDEX.cjs} +6 -6
- package/dist/{chunk-GAYMJWTC.cjs.map → chunk-CB4TIDEX.cjs.map} +1 -1
- package/dist/{chunk-KPZZ5FHE.cjs → chunk-CNHUJRQ4.cjs} +10 -6
- package/dist/chunk-CNHUJRQ4.cjs.map +1 -0
- package/dist/{chunk-Q4BY2CQQ.cjs → chunk-CQCNU5KT.cjs} +28 -28
- package/dist/{chunk-Q4BY2CQQ.cjs.map → chunk-CQCNU5KT.cjs.map} +1 -1
- package/dist/{chunk-GRGS5M6O.cjs → chunk-CROGFB46.cjs} +160 -3
- package/dist/chunk-CROGFB46.cjs.map +1 -0
- package/dist/{chunk-QN47Q4N7.js → chunk-CU6LDQ57.js} +3 -3
- package/dist/{chunk-QN47Q4N7.js.map → chunk-CU6LDQ57.js.map} +1 -1
- package/dist/{chunk-EF4ZLKRM.js → chunk-DEPF5LQI.js} +3 -3
- package/dist/{chunk-EF4ZLKRM.js.map → chunk-DEPF5LQI.js.map} +1 -1
- package/dist/{chunk-JAANWQGB.js → chunk-ETZ5A3GU.js} +3 -3
- package/dist/{chunk-JAANWQGB.js.map → chunk-ETZ5A3GU.js.map} +1 -1
- package/dist/{chunk-TBEVGWY3.js → chunk-F57LNUSZ.js} +10 -6
- package/dist/chunk-F57LNUSZ.js.map +1 -0
- package/dist/{chunk-GFNELPDP.cjs → chunk-FM3IRQJW.cjs} +10 -5
- package/dist/chunk-FM3IRQJW.cjs.map +1 -0
- package/dist/chunk-FPHTSNOE.js +465 -0
- package/dist/chunk-FPHTSNOE.js.map +1 -0
- package/dist/{chunk-OVR3XMYL.js → chunk-FYZXDZM7.js} +3 -3
- package/dist/{chunk-OVR3XMYL.js.map → chunk-FYZXDZM7.js.map} +1 -1
- package/dist/{chunk-2FLWPM3B.cjs → chunk-GC2FSPSQ.cjs} +7 -7
- package/dist/{chunk-2FLWPM3B.cjs.map → chunk-GC2FSPSQ.cjs.map} +1 -1
- package/dist/{chunk-DB2OXRQC.js → chunk-GO3BN76C.js} +7 -7
- package/dist/{chunk-DB2OXRQC.js.map → chunk-GO3BN76C.js.map} +1 -1
- package/dist/{chunk-XZIYRKLW.cjs → chunk-H32DV7XS.cjs} +5 -139
- package/dist/chunk-H32DV7XS.cjs.map +1 -0
- package/dist/{chunk-4236BRZK.js → chunk-HP2C4MD3.js} +29 -3
- package/dist/chunk-HP2C4MD3.js.map +1 -0
- package/dist/{chunk-ZP6Q3KXU.cjs → chunk-HUATKA3M.cjs} +4 -4
- package/dist/{chunk-ZP6Q3KXU.cjs.map → chunk-HUATKA3M.cjs.map} +1 -1
- package/dist/chunk-ISHFVKVU.js +304 -0
- package/dist/chunk-ISHFVKVU.js.map +1 -0
- package/dist/chunk-JUUOJP7I.cjs +164 -0
- package/dist/chunk-JUUOJP7I.cjs.map +1 -0
- package/dist/{chunk-4KMLM3GY.cjs → chunk-KBO75NLM.cjs} +7 -7
- package/dist/{chunk-4KMLM3GY.cjs.map → chunk-KBO75NLM.cjs.map} +1 -1
- package/dist/{chunk-Z4NQ3TED.js → chunk-KCSIX7CY.js} +3 -3
- package/dist/{chunk-Z4NQ3TED.js.map → chunk-KCSIX7CY.js.map} +1 -1
- package/dist/{chunk-DN2KI4BU.js → chunk-KDLX5AA7.js} +3 -3
- package/dist/{chunk-DN2KI4BU.js.map → chunk-KDLX5AA7.js.map} +1 -1
- package/dist/{chunk-WFA2GVZJ.js → chunk-MF5KQ4PS.js} +3 -3
- package/dist/{chunk-WFA2GVZJ.js.map → chunk-MF5KQ4PS.js.map} +1 -1
- package/dist/{chunk-YXNNNP57.js → chunk-MNG7N6M2.js} +4 -4
- package/dist/{chunk-YXNNNP57.js.map → chunk-MNG7N6M2.js.map} +1 -1
- package/dist/chunk-MSFQDELO.cjs +482 -0
- package/dist/chunk-MSFQDELO.cjs.map +1 -0
- package/dist/{chunk-T74Q3MCZ.cjs → chunk-MUDEL7UL.cjs} +25 -884
- package/dist/chunk-MUDEL7UL.cjs.map +1 -0
- package/dist/{chunk-KSHKTOY2.js → chunk-NUSBINUM.js} +3 -3
- package/dist/chunk-NUSBINUM.js.map +1 -0
- package/dist/{chunk-EYKISBDO.cjs → chunk-OALEDG67.cjs} +5 -5
- package/dist/{chunk-EYKISBDO.cjs.map → chunk-OALEDG67.cjs.map} +1 -1
- package/dist/{chunk-COCGRJLA.cjs → chunk-OFV3PRF2.cjs} +7 -7
- package/dist/{chunk-COCGRJLA.cjs.map → chunk-OFV3PRF2.cjs.map} +1 -1
- package/dist/chunk-OOU4QLRK.cjs +319 -0
- package/dist/chunk-OOU4QLRK.cjs.map +1 -0
- package/dist/chunk-OU3RGQFX.cjs +136 -0
- package/dist/chunk-OU3RGQFX.cjs.map +1 -0
- package/dist/{chunk-3PCBYOIZ.js → chunk-P6RIBIVN.js} +38 -7
- package/dist/chunk-P6RIBIVN.js.map +1 -0
- package/dist/{chunk-LXTOOUEN.cjs → chunk-Q7CQYYZW.cjs} +5 -5
- package/dist/{chunk-LXTOOUEN.cjs.map → chunk-Q7CQYYZW.cjs.map} +1 -1
- package/dist/{chunk-CROTH2BS.js → chunk-QKR5LWDO.js} +9 -40
- package/dist/{chunk-CROTH2BS.js.map → chunk-QKR5LWDO.js.map} +1 -1
- package/dist/chunk-REH7PX3N.cjs +145 -0
- package/dist/chunk-REH7PX3N.cjs.map +1 -0
- package/dist/chunk-RQKWYKZY.js +127 -0
- package/dist/chunk-RQKWYKZY.js.map +1 -0
- package/dist/{chunk-U6JQ4KMX.cjs → chunk-SN2GZ4ZM.cjs} +29 -2
- package/dist/chunk-SN2GZ4ZM.cjs.map +1 -0
- package/dist/{chunk-R6VCPDPF.js → chunk-SZSJGZSO.js} +156 -6
- package/dist/chunk-SZSJGZSO.js.map +1 -0
- package/dist/{chunk-US3S4DCB.js → chunk-TF7HN5EU.js} +10 -5
- package/dist/chunk-TF7HN5EU.js.map +1 -0
- package/dist/{chunk-YKQ2DPIP.js → chunk-VF5EUAIV.js} +3 -3
- package/dist/{chunk-YKQ2DPIP.js.map → chunk-VF5EUAIV.js.map} +1 -1
- package/dist/{chunk-KMZWKSKO.cjs → chunk-VZPFYZZU.cjs} +4 -4
- package/dist/{chunk-KMZWKSKO.cjs.map → chunk-VZPFYZZU.cjs.map} +1 -1
- package/dist/{chunk-HUJF2XM2.js → chunk-W7CWGURO.js} +28 -873
- package/dist/chunk-W7CWGURO.js.map +1 -0
- package/dist/{chunk-ZLEYLCE4.js → chunk-WK6FVIF7.js} +6 -156
- package/dist/chunk-WK6FVIF7.js.map +1 -0
- package/dist/chunk-WV4TD2HZ.cjs +1785 -0
- package/dist/chunk-WV4TD2HZ.cjs.map +1 -0
- package/dist/{chunk-COA3S3CZ.js → chunk-XI37A7HH.js} +4 -4
- package/dist/{chunk-COA3S3CZ.js.map → chunk-XI37A7HH.js.map} +1 -1
- package/dist/{chunk-LGBAZD37.js → chunk-XRL4R2TT.js} +7 -139
- package/dist/chunk-XRL4R2TT.js.map +1 -0
- package/dist/{chunk-4JNKXOFZ.cjs → chunk-YBJ4QGNH.cjs} +11 -11
- package/dist/{chunk-4JNKXOFZ.cjs.map → chunk-YBJ4QGNH.cjs.map} +1 -1
- package/dist/{chunk-GFOEWPAP.cjs → chunk-YF3QUHKN.cjs} +49 -12
- package/dist/chunk-YF3QUHKN.cjs.map +1 -0
- package/dist/{chunk-H4D7WKDY.js → chunk-YNMO3DSR.js} +3 -3
- package/dist/{chunk-H4D7WKDY.js.map → chunk-YNMO3DSR.js.map} +1 -1
- package/dist/citations/index.cjs +10 -10
- package/dist/citations/index.js +2 -2
- package/dist/collaboration/index.cjs +77 -77
- package/dist/collaboration/index.js +2 -2
- package/dist/connect/docx.cjs +14 -14
- package/dist/connect/docx.js +10 -10
- package/dist/connect/index.cjs +2820 -1043
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.d.ts +4 -0
- package/dist/connect/index.js +2216 -504
- package/dist/connect/index.js.map +1 -1
- package/dist/connect/viewer.cjs +68 -72
- package/dist/connect/viewer.cjs.map +1 -1
- package/dist/connect/viewer.js +11 -15
- package/dist/connect/viewer.js.map +1 -1
- package/dist/connection/ConnectionProvider.d.ts +7 -0
- package/dist/connection/files/DocumentSyncGlyph.d.ts +28 -0
- package/dist/connection/files/explorer/ConnectedFilesExplorer.d.ts +56 -0
- package/dist/connection/files/explorer/FileTiles.d.ts +87 -0
- package/dist/connection/files/index.d.ts +5 -0
- package/dist/connection/files/store.d.ts +27 -0
- package/dist/connection/files/support.d.ts +38 -0
- package/dist/connection/projects/ConnectedProjectsPanel.d.ts +34 -0
- package/dist/connection/projects/index.d.ts +1 -0
- package/dist/connection/workspace-dashboard/ConnectedWorkspaceDashboard.d.ts +37 -0
- package/dist/connection/workspace-dashboard/dashboard-rows.d.ts +42 -0
- package/dist/connection/workspace-dashboard/index.d.ts +1 -0
- package/dist/connection/workspace-list/ConnectedWorkspaceList.d.ts +37 -0
- package/dist/connection/workspace-list/WorkspaceCardsView.d.ts +15 -0
- package/dist/connection/workspace-list/WorkspaceFilterMenu.d.ts +4 -0
- package/dist/connection/workspace-list/WorkspaceTableView.d.ts +13 -0
- package/dist/connection/workspace-list/index.d.ts +5 -0
- package/dist/connection/workspace-list/projectBilling.d.ts +21 -0
- package/dist/connection/workspace-list/useConnectedWorkspaceList.d.ts +251 -0
- package/dist/connection/workspace-list/workspaceTable.d.ts +58 -0
- package/dist/connection/workspace-settings/ConnectedWorkspaceMembers.d.ts +26 -0
- package/dist/connection/workspace-settings/ConnectedWorkspaceSettings.d.ts +7 -9
- package/dist/connection/workspace-settings/index.d.ts +2 -1
- package/dist/connection/workspace-settings/useConnectedWorkspaceSettings.d.ts +0 -1
- package/dist/facts/index.cjs +3 -3
- package/dist/facts/index.js +2 -2
- package/dist/files/DocStatusIcon.d.ts +5 -4
- package/dist/files/FailedStageIcon.d.ts +26 -0
- package/dist/files/folders/FolderProviderIcon.d.ts +29 -0
- package/dist/files/folders/folderPaths.d.ts +86 -0
- package/dist/files/folders/index.d.ts +2 -0
- package/dist/files/index.cjs +328 -240
- package/dist/files/index.d.ts +5 -0
- package/dist/files/index.js +4 -4
- package/dist/files/permissions.d.ts +28 -0
- package/dist/files/recently-deleted.d.ts +7 -0
- package/dist/files/visibility.d.ts +31 -0
- package/dist/home/index.cjs +119 -459
- package/dist/home/index.cjs.map +1 -1
- package/dist/home/index.js +13 -419
- package/dist/home/index.js.map +1 -1
- package/dist/index.cjs +480 -480
- package/dist/index.js +16 -16
- package/dist/provider/BlocksProvider.d.ts +9 -1
- package/dist/retrieval/index.cjs +4 -4
- package/dist/retrieval/index.js +2 -2
- package/dist/settings/index.cjs +169 -165
- package/dist/settings/index.d.ts +1 -0
- package/dist/settings/index.js +2 -2
- package/dist/settings/tri-state-filters.d.ts +13 -0
- package/dist/shell/index.cjs +44 -44
- package/dist/shell/index.js +3 -3
- package/dist/site/index.cjs +5 -5
- package/dist/site/index.js +1 -1
- package/dist/studio/index.cjs +14 -14
- package/dist/studio/index.js +6 -6
- package/dist/workflow-canvas/index.cjs +27 -27
- package/dist/workflow-canvas/index.js +9 -9
- package/dist/workspaces/index.cjs +110 -369
- package/dist/workspaces/index.cjs.map +1 -1
- package/dist/workspaces/index.d.ts +2 -0
- package/dist/workspaces/index.js +12 -350
- package/dist/workspaces/index.js.map +1 -1
- package/dist/workspaces/workspace-groups.d.ts +90 -0
- package/dist/workspaces/workspace-roles.d.ts +11 -0
- package/node_modules/@arbidocs/coedit/package.json +1 -1
- package/package.json +6 -5
- package/dist/ConnectedStreamedWordArtifact-QW74LKHN.cjs.map +0 -1
- package/dist/chunk-3PCBYOIZ.js.map +0 -1
- package/dist/chunk-4236BRZK.js.map +0 -1
- package/dist/chunk-CU2O32Z4.cjs.map +0 -1
- package/dist/chunk-EV7W64B6.js +0 -134
- package/dist/chunk-EV7W64B6.js.map +0 -1
- package/dist/chunk-GFNELPDP.cjs.map +0 -1
- package/dist/chunk-GFOEWPAP.cjs.map +0 -1
- package/dist/chunk-GRGS5M6O.cjs.map +0 -1
- package/dist/chunk-HUJF2XM2.js.map +0 -1
- package/dist/chunk-JGOST4IL.js +0 -648
- package/dist/chunk-JGOST4IL.js.map +0 -1
- package/dist/chunk-KPZZ5FHE.cjs.map +0 -1
- package/dist/chunk-KSHKTOY2.js.map +0 -1
- package/dist/chunk-LGBAZD37.js.map +0 -1
- package/dist/chunk-R6VCPDPF.js.map +0 -1
- package/dist/chunk-RQ2Y7USX.cjs.map +0 -1
- package/dist/chunk-RXK3XP62.cjs +0 -144
- package/dist/chunk-RXK3XP62.cjs.map +0 -1
- package/dist/chunk-SMN32BQ3.cjs.map +0 -1
- package/dist/chunk-T74Q3MCZ.cjs.map +0 -1
- package/dist/chunk-TBEVGWY3.js.map +0 -1
- package/dist/chunk-U6JQ4KMX.cjs.map +0 -1
- package/dist/chunk-US3S4DCB.js.map +0 -1
- package/dist/chunk-XZIYRKLW.cjs.map +0 -1
- package/dist/chunk-YAEXSCUW.cjs +0 -677
- package/dist/chunk-YAEXSCUW.cjs.map +0 -1
- package/dist/chunk-ZLEYLCE4.js.map +0 -1
package/dist/connect/index.js
CHANGED
|
@@ -1,46 +1,54 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
|
|
2
|
+
import { useBillingSupport, formatMoney, isProProject, useSelfExtId, addonCharge, formatPeriodEnd, quoteParts, taxDetail, money2, ANNUAL_PREMIUM_AI_NOTE, totalWithTaxWord, planBadgeView, DEFAULT_SPEND_CAP_PENCE, findProPrices, packPrice, calcSavingsPercent, parseBudgetPounds, getQuotaItems, PRO_PACKS, planNameForPacks, proTierName, STORAGE_BLOCK_GB, ADDON_BLOCK_AI_GBP, formatResetDate, quoteError, POSTCODE_COUNTRIES, addonPrice, checkoutBlockPriceText, quoteTotalLine, apiErrorDetail, checkoutItemsLabel, projectGroupBilling, SUGGESTED_DAILY_CAP_PER_USER, FREE_STORAGE_MB, FREE_AI_GBP, PRO_AI_GBP, PRO_STORAGE_GB, plusVat, planPreviewLines, PAY_TODAY_UNPRICED, aiUsageResetTimestamp, TAX_LOCATION_INVALID, FREE_COLLABORATORS, MAX_CHECKOUT_BLOCKS, reduceBounds, splitBlocks, autoRoom, isAddonBudgetZero, nextRemovedBlockSize, stepLabel, addStepCaption, blocksLabel, MIN_AUTO_TOPUP_THRESHOLD_GBP, USAGE_CAP_LABEL, CAP_REACHED_MESSAGE, USAGE_CAP_HELP, PLAN_CHOICE_PACKS, nextPeriodBody, addonSettingsBody, budgetSavedMessage } from '../chunk-7HHYMDPE.js';
|
|
3
|
+
export { BillingSupportProvider, CAP_REACHED_MESSAGE, ZERO_BUDGET_MESSAGE, aiUsageResetTimestamp, formatMoney, formatResetDate, getQuotaItems, isAddonBudgetZero, isProProject, planBadgeView, planName, projectGroupBilling, useBillingSupport, useRewardBalance } from '../chunk-7HHYMDPE.js';
|
|
4
|
+
import { getRoleBadgeClass, formatRole, getRoleDescription, PageLoading, WorkspacePageFrame, WorkspaceIdentityPanel, WorkspaceProjectSlot, WorkspaceAudienceLine, CommonWorkspaceToggle, ExpandableSection, LeaveFooter, WorkspaceList, WorkspaceGroup, WorkspaceCard } from '../chunk-FPHTSNOE.js';
|
|
5
|
+
import { isAgentAccount, workspaceConversationCount, workspaceDocumentCount, isListedWorkspace, WORKSPACE_CATEGORY_FILTERS, projectFilterId, workspaceFilterAttributes, groupWorkspacesByProject, byRecentUpdate } from '../chunk-BTF4AZMG.js';
|
|
6
|
+
import { HomeLoadError, HomeFrame, HomeMain, HomeCenteredScroll, HomeCenteredColumn, DashboardRoot, DashboardBand, DashboardColumns, RecentList, DashboardAction, FactsStrip, FactMark, CreateTile } from '../chunk-ISHFVKVU.js';
|
|
7
|
+
import { ProjectBandTitle, PlainBandTitle } from '../chunk-RQKWYKZY.js';
|
|
8
|
+
import '../chunk-5TDGVOR5.js';
|
|
9
|
+
import { DraggableHeader, SettingsSection, ProfileCard, AvatarPicker, IdentityVerificationCard, IdentityVerificationDialog, RequireVerified, PasswordChangeDialog, SettingsCard, SettingsActionRow, EMAIL_NOTIFICATION_KEYS, EMAIL_NOTIFICATION_CATEGORIES, NotificationCategoryList, toNotificationCategories, toActiveSessionViews, SettingsRow, ActiveSessionsDialog, LogoutDialog, toLogoutSessionViews, SettingsDangerZone, DeleteAccountDialog, toDeleteBlockerViews, SettingsPanel, PremiumModelsFrame, ConnectionsLoading, ConnectionsError, ConnectionsEmpty, SettingsPage, SyncDialogBody, SyncField, SyncSelect, SyncSmallNote, SyncLoading, SyncReconnectNotice, SyncNote, SyncTreeFrame, SyncLinkRow, SyncLinkButton, SyncOptions, SyncOptionToggle, SyncDialogFooter, InvoicesDialog, PlanDialogFrame, ProjectList, RenameDialog, SharedProjectsSection, SharedProjectRow, MoreInfo, MoreInfoList, ProjectDetailHeader, PlanBadge, ProjectDetailSection, PlanStack, PlanNotice, PlanPickerCard, PlanPickerHeader, PricingToggle, PromoCodeInput, PlanActionButton, applyTriStateFilters, PanelFrame, PanelAddButton, PanelFrameBody, PanelScroll, PanelActionsRow, ProjectRow, QuotaMeter, FilterChip, applyInvertedIndexFilter, buildInvertedIndex, serverConnectionView, ServerConnectionButton, ModelProviderLabel, SyncFolderRow, SyncFolderStatus, PlanTierOption, PlanActionRow, SubscriptionStatusView, DailyCapField, UsagePanel, UsageSummary, UsageQuotaHint, UsageToggle, UsageSection, UsageLineChart, UsageLegend, UsageProportionBar, UsageActionButton, PlanInfoPopover, PlanCardGrid, ReferralRewardSummary, CheckoutDialogFrame, ConnectionCard, ReferralRewardNote } from '../chunk-HP2C4MD3.js';
|
|
10
|
+
export { ConnectedAssistantDock, redlineStats, textToArtifact, toRedlineMarkdown, useConnectedAiTask, useConnectedChat, useSemanticSearch, useWorkspaceDocs } from '../chunk-KCSIX7CY.js';
|
|
11
|
+
import { useConnectedWorkspaceRole, ConnectedNativeDocxEditor } from '../chunk-GO3BN76C.js';
|
|
12
|
+
export { ConnectedWorkflowDocumentsField, ConnectedWorkflowLauncher, ConnectedWorkflowsTable, DEFAULT_CONNECTED_WORKFLOWS_TABLE_LABELS, NativeDocxSupportProvider, toWorkflowFieldDocument, useConnectedAgentOptions, useConnectedWorkflowScopeSource, useConnectedWorkspaceRole, useNativeDocxSupport } from '../chunk-GO3BN76C.js';
|
|
7
13
|
export { DocumentViewerSupportProvider, useDocumentViewerSupport } from '../chunk-EYJMMGXF.js';
|
|
8
|
-
import { FilterButton, FilesViewCenter, FilesFilterBar, ROW_MIN, rowHeightForLines, linesForRowHeight, RowDensityControl, FolderFilterButton, FolderPickerRow, FieldLabelsSelect } from '../chunk-
|
|
14
|
+
import { FilterButton, ExplorerFileTile, formatFolderSegment, hasFolderProviderIcon, FolderProviderIcon, ExplorerFolderTile, searchExplorerDocuments, buildExplorerEntries, collectFolderDocs, joinFolderPath, getExplorerDisplayName, RECENTLY_DELETED_HINT, RECENTLY_DELETED_HINT_PLURAL, canDeleteDocument, ExplorerFrame, ExplorerStage, ExplorerScroller, ExplorerLoadingTiles, ExplorerEmpty, ExplorerMarquee, ExplorerDropOverlay, ExplorerDragPreview, ExplorerFileTooltip, visibilityAudience, StatusPill, FailedStageIcon, FilesViewCenter, FilesFilterBar, ROW_MIN, rowHeightForLines, linesForRowHeight, RowDensityControl, FolderFilterButton, FolderPickerRow, FieldLabelsSelect } from '../chunk-6HMCF4AJ.js';
|
|
9
15
|
import '../chunk-RC6VTY2H.js';
|
|
10
|
-
import { useConnectedTags, useConnectedDocuments, useConnectedConversations } from '../chunk-
|
|
11
|
-
export { ConnectedWorkspaceDataProvider, useConnectedConversations, useConnectedDocuments, useConnectedTags } from '../chunk-
|
|
12
|
-
import { ConnectionProvider, useConnection
|
|
13
|
-
export { ConnectionProvider, SESSION_EXPIRED_MESSAGE, isAuthenticated, useConnectedWorkspace, useConnection } from '../chunk-
|
|
16
|
+
import { useConnectedTags, useConnectedDocuments, useConnectedConversations } from '../chunk-KDLX5AA7.js';
|
|
17
|
+
export { ConnectedWorkspaceDataProvider, useConnectedConversations, useConnectedDocuments, useConnectedTags } from '../chunk-KDLX5AA7.js';
|
|
18
|
+
import { useConnectedWorkspace, ConnectionProvider, useConnection } from '../chunk-TF7HN5EU.js';
|
|
19
|
+
export { ConnectionProvider, SESSION_EXPIRED_MESSAGE, isAuthenticated, useConnectedWorkspace, useConnection } from '../chunk-TF7HN5EU.js';
|
|
14
20
|
import { SignDocumentDialog } from '../chunk-2YOVK524.js';
|
|
15
21
|
import '../chunk-MTSKK3BN.js';
|
|
16
22
|
import '../chunk-265GOG4W.js';
|
|
17
23
|
import { docIdsOf, toFactCitations, FactsToolbar, FactsFieldsButton, FactComposer, FactDateInput, FactSourcesRow, FactsLoading, FactsLoadError, FactsEmptyState, FactsCalendar, FactsGanttPane, FactsBulkBar, FactSourceGroup, EMPTY_FACT_DATE_FIELDS, withoutChunk, factDateSpanToFields, FactFieldRow, FactLabelsCell, enableFactTableColumnResize, citedVersionRef, withChunk, factDateFieldsToSpan, createFactColumns, FactRowActions, withoutDoc, withDoc, sourceDocCount, DEFAULT_FACT_COLUMN_LABELS } from '../chunk-WU34CHU5.js';
|
|
18
24
|
import { ViewToggle } from '../chunk-JJA6GQKH.js';
|
|
19
25
|
import { FactSourcesPanel, FactPassageItem } from '../chunk-HFZ45FD3.js';
|
|
20
|
-
import { WorkspaceActivityList, WorkspaceNotificationMenu, ThreadFollowMenuItem, ThreadFollowButton, TagTable, TagPicker, tagsPageNouns, entitiesPhrase, ExtractEntitiesDialog, GridRowNavigator, LoadingIndicator, EntityIdentityBar, IDENTITY_NAME_MAX_LENGTH, tagTableDocx, documentCount, tagTableLabels, TagDetail, tagDetailLabels, tagNoteLabel, tagKindDescription, glossaryEntries, glossaryDocx, glossaryMarkdown, glossaryFileName } from '../chunk-
|
|
26
|
+
import { WorkspaceActivityList, WorkspaceNotificationMenu, ThreadFollowMenuItem, ThreadFollowButton, TagTable, TagPicker, tagsPageNouns, entitiesPhrase, ExtractEntitiesDialog, GridRowNavigator, LoadingIndicator, EntityIdentityBar, IDENTITY_NAME_MAX_LENGTH, tagTableDocx, documentCount, tagTableLabels, TagDetail, tagDetailLabels, tagNoteLabel, tagKindDescription, glossaryEntries, glossaryDocx, glossaryMarkdown, glossaryFileName } from '../chunk-MNG7N6M2.js';
|
|
21
27
|
import { MemberAvatarStack } from '../chunk-XNOASBGW.js';
|
|
22
28
|
import { renderSearchSnippet, SearchGroupHeader, isMeaningMatch, extractHighlightTerms, passesRelevanceCutoff, groupSearchResults, SearchPillToggle, SearchPagination, SearchPageFrame, SearchPageHeader, ApplyLabelDialog, SearchPageBody, SearchToolbarRow, SearchBar, SearchControlsBar, RelevanceThreshold, SearchTotalsHeadline, ApplyLabelButton, getSearchMatchHint, SearchResultsMain, SearchResultsInner, SearchErrorBanner, SearchResultList, SearchGroupCard, SearchNoResults, SearchEmptyState } from '../chunk-G4IYES7G.js';
|
|
23
29
|
import { UsageMeter } from '../chunk-66TA5IBN.js';
|
|
30
|
+
import '../chunk-UJE32SST.js';
|
|
24
31
|
import '../chunk-NHZK64TQ.js';
|
|
25
|
-
import { PlainMailBodyEditor, buildMailFolderTree, MailFolderTree, draftMailCompose, replyMailCompose, emptyMailCompose, MailGrid, MailMessageView, MailDocumentPicker, parseMailRecipients } from '../chunk-
|
|
32
|
+
import { PlainMailBodyEditor, MemberNameCell, MemberMutedText, MemberRoleCell, MemberListLoading, MemberBulkActions, buildMailFolderTree, MailFolderTree, draftMailCompose, replyMailCompose, emptyMailCompose, MailGrid, MailMessageView, MailDocumentPicker, parseMailRecipients } from '../chunk-SZSJGZSO.js';
|
|
33
|
+
import '../chunk-NBV56SGU.js';
|
|
26
34
|
import '../chunk-SDOEHEJP.js';
|
|
27
35
|
import { WorkspaceIdentity, workspaceKindColor } from '../chunk-A3XPS2EU.js';
|
|
28
|
-
import '../chunk-SV65E4QW.js';
|
|
36
|
+
import { buildDeepLinkPath } from '../chunk-SV65E4QW.js';
|
|
29
37
|
import '../chunk-ZCOVDK2E.js';
|
|
30
38
|
import { isDefinitionsLabel, DOCX_MIME_TYPE, toDefinitionSource, definitionLabels } from '../chunk-DIB4JYUY.js';
|
|
31
|
-
import { SearchHit } from '../chunk-
|
|
39
|
+
import { SearchHit } from '../chunk-DEPF5LQI.js';
|
|
32
40
|
import { TimelineZoomControl } from '../chunk-4EZVCGRL.js';
|
|
33
41
|
import { decodeWhitespaceEntities } from '../chunk-AF6RDAJF.js';
|
|
34
|
-
import { buildLibrarySkills, filterLibrarySkills, SKILL_SOURCE_LABELS, SKILL_TYPE_LABELS, SkillViewToggle, SkillFilterChips, SkillTile, SkillGenerateForm, SkillLibraryDetail, parseSkillFrontmatter, SKILL_MAIN_FILENAME, SkillEditForm, DEFAULT_AGENT_CAPABILITIES, stripPersonaPreamble, AgentCreator, DEFAULT_AGENT_TYPES, DEFAULT_AGENT_TEMPLATES, AgentAvatarField, isValidAgentName, AgentConnectInstructions, TaskRunsDialog, TaskActivityDialog, WorkflowStepBar, composePersona, importSkillFile, importSkillFromUrl, WorkflowRunner, skillSlug, serializeSkill, fromPlanSteps, toPlanSteps, useWorkflowView, workflowToSkillMarkdown } from '../chunk-
|
|
42
|
+
import { buildLibrarySkills, filterLibrarySkills, SKILL_SOURCE_LABELS, SKILL_TYPE_LABELS, SkillViewToggle, SkillFilterChips, SkillTile, SkillGenerateForm, SkillLibraryDetail, parseSkillFrontmatter, SKILL_MAIN_FILENAME, SkillEditForm, DEFAULT_AGENT_CAPABILITIES, stripPersonaPreamble, AgentCreator, DEFAULT_AGENT_TYPES, DEFAULT_AGENT_TEMPLATES, AgentAvatarField, isValidAgentName, AgentConnectInstructions, TaskRunsDialog, TaskActivityDialog, WorkflowStepBar, composePersona, importSkillFile, importSkillFromUrl, WorkflowRunner, skillSlug, serializeSkill, fromPlanSteps, toPlanSteps, useWorkflowView, STATUS_LABELS, asTaskStatus, workflowToSkillMarkdown } from '../chunk-P6RIBIVN.js';
|
|
35
43
|
import { MultiSelect } from '../chunk-CQCC7LE7.js';
|
|
36
|
-
import { PersonPill } from '../chunk-
|
|
37
|
-
import '../chunk-
|
|
38
|
-
import { TriStateFilterMenu, DocumentPicker } from '../chunk-
|
|
44
|
+
import { PersonPill } from '../chunk-XI37A7HH.js';
|
|
45
|
+
import { SectionLabel } from '../chunk-BQ77KSXU.js';
|
|
46
|
+
import { TriStateFilterMenu, DocumentPicker } from '../chunk-NUSBINUM.js';
|
|
39
47
|
import '../chunk-P6GHJSLR.js';
|
|
40
48
|
import '../chunk-M4S75AEJ.js';
|
|
41
49
|
import '../chunk-5LXQPP2K.js';
|
|
42
50
|
import '../chunk-RLWIUUQ5.js';
|
|
43
|
-
import '../chunk-
|
|
51
|
+
import '../chunk-VF5EUAIV.js';
|
|
44
52
|
import { SelectionBar, SelectionBarAction } from '../chunk-NUNKSZKU.js';
|
|
45
53
|
import { FilterInput } from '../chunk-YU2KUMGE.js';
|
|
46
54
|
import { GridView } from '../chunk-N5YEF3LC.js';
|
|
@@ -57,21 +65,21 @@ import { splitDocRef, stepLevels, stepDocuments, stepKindOf, initialRunInputs, w
|
|
|
57
65
|
import '../chunk-TZGBRLPK.js';
|
|
58
66
|
import '../chunk-AUS3KBHJ.js';
|
|
59
67
|
import '../chunk-AA3PT6KR.js';
|
|
60
|
-
import '../chunk-SLSBJRWW.js';
|
|
68
|
+
import { useIsPhone } from '../chunk-SLSBJRWW.js';
|
|
61
69
|
import '../chunk-NPLYHR2E.js';
|
|
62
70
|
import '../chunk-7F6CBP3Z.js';
|
|
63
|
-
import { PassagePill, useCopyText } from '../chunk-
|
|
64
|
-
import { useBlocksEnvironment, useRequestedSettingsSection } from '../chunk-
|
|
71
|
+
import { PassagePill, useCopyText } from '../chunk-ETZ5A3GU.js';
|
|
72
|
+
import { useBlocksEnvironment, useRequestedSettingsSection, BlocksProvider } from '../chunk-F57LNUSZ.js';
|
|
65
73
|
import { ModalScaffold, ModalSection } from '../chunk-TX2ZICS7.js';
|
|
66
74
|
import { fmtDate, initials } from '../chunk-OXF2IFXG.js';
|
|
67
75
|
import '../chunk-VUNV25KB.js';
|
|
68
|
-
import { lazy, memo, useState, useEffect,
|
|
76
|
+
import { lazy, memo, useState, useEffect, useCallback, createContext, useMemo, forwardRef, useRef, useLayoutEffect, useImperativeHandle, useContext, Suspense, useSyncExternalStore, useId } from 'react';
|
|
69
77
|
import { QueryClient, QueryClientProvider, useQueryClient, useQuery } from '@tanstack/react-query';
|
|
70
|
-
import { HtmlIcon, useArbiSessionState, ArbiProvider, useConversations, useRunConversation, useWorkspaceActivity, useWorkspaceUsers, isWorkspaceNotificationType, useArbi, useNotifications, useDeleteNotifications, useMarkNotificationsRead,
|
|
78
|
+
import { HtmlIcon, useMailPolicy, useProcessingTasks, useUpdateDocuments, useDeleteDocuments, useRenameFolder, useDownloadDocuments, useTags, useDocumentThumbnails, useArbiSessionState, useProcessingTask, getDocumentSubject, useWorkspaces, ArbiProvider, useConversations, useRunConversation, useWorkspaceActivity, useWorkspaceUsers, isWorkspaceNotificationType, useArbi, useNotifications, useDeleteNotifications, useMarkNotificationsRead, useWorkspaceNotificationLevel, useFollowThread, useUploadSkillFiles, useSkills, useSetSkillVisibility, useDeleteSkill, useSkillSource, useAgentModels, useMcpTools, useAgentConfig, useAgentWorkspaces, useCreateAgent, useSaveAgentConfig, useGrantAgentAccess, useRevokeAgentAccess, useGenerateAgentAvatar, useWorkflowRuns, useConversationActivity, useAgents, ArtifactCanvas, useArbiChat, useDocuments, useTagKindSchema, useCreateTag, useSetTagValues, useUpdateTagKindSchema, useFacts, useDeleteTag, useExtractEntities, useShellWorkspaces, ArbiBotHeadIcon, useProfile, useUpdateProfile, useRequestEmailChange, useConfirmEmailChange, useNameEditor, useAvatarCrop, useEmailChange, ImageCropModal, useIdentityVerification, usePasswordChange, usePasswordChangeForm, useNotificationPreferences, useSessions, useCurrentSessionId, useTerminateSession, useSignOut, useDeleteAccount, useDeleteAccountFlow, useMailClient, defaultMailAlias, useClaimNativeAlias, useMailOnline, useConnectMailAccount, useDisconnectMailAccount, useMailAccounts, useUserSettings, useUpdateUserSettings, useAvailableModels, useConnectorVendorVisibility, useConnectorCatalog, useMyConnectors, useDisconnectConnector, useSlackConnection, useDisconnectSlack, isMailboxSyncVendor, isSharePointVendor, useUpdateWorkspace, useAddWorkspaceMembers, useRemoveWorkspaceMembers, useSetWorkspaceMemberRoles, toShellWorkspace, useEnableMailboxSync, useMailboxFolders, useEnableDriveSync, useDriveFolderChildren, connectorErrorStatus, useProjectInvoices, useProjects, useUpdateProjectFields, useUserUsageToday, formatGbp, useChargeWatch, useAddonQuote, STILL_WORKING, useProjectBillingPortal, useIsSlow, useProducts, useCreateProject, useDeleteProject, useRefreshProject, useCanCreateProjects, useDeleteWorkspaces, PROJECT_CREATION_DENIED_REASON, MAX_RETRIEVE_DOC_IDS, useSemanticSearch, useAssignDocTags, useArbiWebSocket, useWorkNotifications, describeServerNotification, useMailUnlock, useMailFolders, useMailMessages, useMailDraftCompose, useMailMessage, useMailCache, MailOfflineUnavailableError, accountPath, useCalendarEvents, useCalendars, useFetchCalendarEvent, useCalendarSettings, useCalendarOverlays, useCalendarOutbox, useCalendarAccounts, useSyncAccountsOnOpen, useResumeCalendarAccountConnect, useCreateEvent, useUpdateEvent, useDeleteEvent, useMoveEvent, useRespondToEvent, useCreateCalendar, useUpdateCalendar, useDeleteCalendar, useImportCalendar, useSaveCalendarSettings, useRefreshCalendar, useCalendarEvent, useMailAttachmentText, useCalendarEventByUid, getPublicBookingPage, bookPublicSlot, PublicBookingError, useAgentSession, useStartAgentSession, useCloseAgentSession, isQueuedCalendarWrite, isAliasTakenError, nextAliasSuggestion, aliasTakenMessage, mailJson, connectorReturnTo, ArbiWebSocketProvider, isDeploymentUserNotification, getWorkspaceActionText, getWorkspaceSentPhrase, BOX_SEAT_REFUSED, BOX_SEAT_REFUSED_TITLE, documentActivityPhrase, isSignatureNotificationType, inputRequestTitle, WORK_NOTIFICATION_LABELS, documentDisplayName, getWorkspaceKind, formatRelativeTime, useCreateWork, useActOnStep, useConversationThreads, useUpdateTag, usePassageQuotes, useUpdateFact, useDeleteFact, useCreateFact, ProviderIcon, formatGbpPerMillionTokens, useDriveFolderDocCount, useChangeProjectPlan, useCancelProjectSubscription, usePreviewProjectPlanChange, useAddProjectAddonBlock, useProjectUsage, usePlanQuote, useCreateProjectCheckout, useInvalidateProjects, fileAttachment, useMailProjects, useSaveMailDraft, useSendMail, useMailDraftReplaced, useCalendarInvitations, useDecideCounter, useSearchContacts, usePublishCalendar, useCalendarShares, useShareCalendar, useUnshareCalendar, useSubscribeCalendar, useHolidayCountries, useAddHolidayCalendar, cancelPublicBooking, AiAnswer, signatureNotificationPhrase, signatureRequestMeta, getNotificationTypeMeta, useMailDraftEditor, useFreeBusy, useRooms, useFindTime, useCalendarSupport, useStartCalendarAccountOAuth, useConnectOAuthAccount, useBookingPages, useDeleteBookingPage, SHAREPOINT_VENDORS, useUpdateProjectNextPeriodAddons, useUpdateProjectAddonSettings, useSyncCalendarAccount, useUpdateCalendarAccount, useDisconnectCalendarAccount, ACCOUNT_VENDOR, useCalDavProfiles, useConnectCalDavAccount, useSaveBookingPage, useCreateRoom } from '@arbidocs/react';
|
|
71
79
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
72
|
-
import { Badge, Button, Label, Input,
|
|
80
|
+
import { getFileTypeIcon, DocumentEntity, cn, getDocumentTitle, Badge, VisibilityIcon, formatFriendlyDate, getEntityLabel, Avatar, AvatarImage, getUserDisplayName, AvatarFallback, getUserInitials, getUserAvatarColor, Button, Label, Input, Textarea, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, tagKindNouns, tagKindTitle, isImageDocument, getEntityColor, IconToggle, VisibilityToggle, Switch, ProjectEntity, DialogFooter, EntityIcon, getDocIcon, Tooltip, TooltipTrigger, TooltipContent, EntityPill, ShieldIcon, getTagTypeLabel, Popover, PopoverTrigger, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, UserEntity, PopoverContent, Tabs, TabsList, TabsTrigger, TabsContent, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, VerifiedToggle, Progress } from '@arbidocs/react/ui';
|
|
73
81
|
import { useNotify, notify, sharedSearchSession, takePendingSearch } from '@arbidocs/react/shell';
|
|
74
|
-
import { Bell, Activity, Info, UserCheck,
|
|
82
|
+
import { SquarePen, BookOpen, MessagesSquare, Pencil, Download, Mail, FolderInput, Trash2, Sparkles, Play, ArrowDownToLine, Bell, Activity, Info, UserCheck, Loader2, Upload, Link2, Search, Plus, ArrowLeft, ExternalLink, Tags, CheckCircle2, Milestone, Monitor, UserCircle, KeyRound, Radio, LogOut, Wifi, RotateCcw, User, HatGlasses, MessageSquare, PlugZap, SquareStack, Table, FileText, Tag, Settings, Video, Printer, CalendarDays, Clock, MapPin, Globe, AlertTriangle, FileX, SkipForward, CheckCircle, ArrowUpCircle, Building2, Contact, Scale, NotebookPen, Check, X, BookA, ChevronDown, Users, Paperclip, Quote, Send, Inbox, CalendarX, CalendarCheck, Minimize2, Maximize2, Minus, CalendarSearch, DoorOpen, RefreshCw } from 'lucide-react';
|
|
75
83
|
import { downloadBlob, useIsOnline, useOptionalDocumentTools, fetchDocumentPdf, DocumentToolsProvider } from '@arbidocs/react/document-tools';
|
|
76
84
|
import { useStreamedArtifact } from '@arbidocs/react/artifacts';
|
|
77
85
|
import { FLAT_TAG_KINDS, TAG_KINDS, refusalReason, getChildTags, tagUseCount, flattenTagTree, isFlatKind, isEntityTag, tagsForScope, filterDocumentTags, isLabelTag, getTagDescendantIds, getEligibleParentTags, canTagHaveParent } from '@arbidocs/sdk/browser';
|
|
@@ -79,6 +87,8 @@ import { bytesToBase64 } from '@arbidocs/client';
|
|
|
79
87
|
import { createPortal } from 'react-dom';
|
|
80
88
|
import { create } from 'zustand';
|
|
81
89
|
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
90
|
+
import { useDraggable, useDroppable, useSensors, useSensor, MouseSensor, TouchSensor, pointerWithin, rectIntersection, DndContext, DragOverlay } from '@dnd-kit/core';
|
|
91
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
82
92
|
|
|
83
93
|
function ConnectionBoundWebSocket({ children }) {
|
|
84
94
|
const { status } = useConnection();
|
|
@@ -672,13 +682,13 @@ function NotificationCenter({
|
|
|
672
682
|
var PAGE_SIZE = 100;
|
|
673
683
|
function localTime(timestamp) {
|
|
674
684
|
const date = new Date(timestamp);
|
|
675
|
-
const
|
|
685
|
+
const time2 = date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
676
686
|
const today = /* @__PURE__ */ new Date();
|
|
677
687
|
const yesterday = new Date(today);
|
|
678
688
|
yesterday.setDate(today.getDate() - 1);
|
|
679
|
-
if (date.toDateString() === today.toDateString()) return `Today, ${
|
|
680
|
-
if (date.toDateString() === yesterday.toDateString()) return `Yesterday, ${
|
|
681
|
-
return `${date.toLocaleDateString([], { month: "short", day: "numeric" })}, ${
|
|
689
|
+
if (date.toDateString() === today.toDateString()) return `Today, ${time2}`;
|
|
690
|
+
if (date.toDateString() === yesterday.toDateString()) return `Yesterday, ${time2}`;
|
|
691
|
+
return `${date.toLocaleDateString([], { month: "short", day: "numeric" })}, ${time2}`;
|
|
682
692
|
}
|
|
683
693
|
function DefaultUser({ user }) {
|
|
684
694
|
const arbi = useArbi();
|
|
@@ -6660,27 +6670,11 @@ function useConnectedWorkspaceSettings(workspaceId, { onLeft } = {}) {
|
|
|
6660
6670
|
}
|
|
6661
6671
|
return "cancelled";
|
|
6662
6672
|
}
|
|
6663
|
-
const
|
|
6664
|
-
if (!
|
|
6673
|
+
const quota = status === 402 || lower.includes("limit reached") || lower.includes("quota") || lower.includes("upgrade");
|
|
6674
|
+
if (!quota) notify14(detail, "error");
|
|
6665
6675
|
return "error";
|
|
6666
6676
|
}
|
|
6667
6677
|
};
|
|
6668
|
-
const removeMember = async (userExtId, email, name) => {
|
|
6669
|
-
if (!workspace || !isOwner) return;
|
|
6670
|
-
const ok = await confirm({
|
|
6671
|
-
message: `Remove ${name} (${email}) from the "${workspace.name}" ${getEntityLabel("WORKSPACE")}?
|
|
6672
|
-
|
|
6673
|
-
Note that ${getEntityLabel("WORKSPACEUSER", "PLURAL").toLowerCase()} may have saved private content in the workspace that you cannot see. If you remove them from the ${workspaceWord()} they will immediately lose access to any content in the ${workspaceWord()}.`,
|
|
6674
|
-
title: `Remove ${getEntityLabel("WORKSPACEUSER")}`
|
|
6675
|
-
});
|
|
6676
|
-
if (!ok) return;
|
|
6677
|
-
try {
|
|
6678
|
-
await remove.mutateAsync([userExtId]);
|
|
6679
|
-
notify14(`Successfully removed ${name} from workspace`, "success");
|
|
6680
|
-
} catch {
|
|
6681
|
-
notify14(`Failed to remove ${name} from workspace`, "error");
|
|
6682
|
-
}
|
|
6683
|
-
};
|
|
6684
6678
|
const leave = async () => {
|
|
6685
6679
|
if (!workspace || !userId || isOwner) return;
|
|
6686
6680
|
const name = workspace.name;
|
|
@@ -6708,19 +6702,246 @@ You will lose access to all content in this ${workspaceWord()}.`,
|
|
|
6708
6702
|
updateIdentity,
|
|
6709
6703
|
setPublic,
|
|
6710
6704
|
share,
|
|
6711
|
-
removeMember,
|
|
6712
6705
|
leave,
|
|
6713
6706
|
isSharing: add.isPending,
|
|
6714
6707
|
isRemoving: remove.isPending
|
|
6715
6708
|
};
|
|
6716
6709
|
}
|
|
6710
|
+
var memberName = (user) => user.family_name ? `${user.given_name} ${user.family_name}` : user.given_name;
|
|
6711
|
+
var ALL_ROLES = ["owner", "collaborator", "guest"].map((value) => ({ value, label: formatRole(value), badgeClass: getRoleBadgeClass(value) }));
|
|
6712
|
+
var BULK_ROLES = ALL_ROLES.filter((r) => r.value !== "owner");
|
|
6713
|
+
var ROLE_ORDER = { owner: 0, collaborator: 1, guest: 2 };
|
|
6714
|
+
function roleLock(targetId, selfId, isOwner) {
|
|
6715
|
+
if (!isOwner) return "Only owners can change roles";
|
|
6716
|
+
if (targetId === selfId) return "Your role can only be changed by another owner";
|
|
6717
|
+
return null;
|
|
6718
|
+
}
|
|
6719
|
+
var defaultRenderMember = (member) => {
|
|
6720
|
+
const name = memberName(member.user);
|
|
6721
|
+
return /* @__PURE__ */ jsx(
|
|
6722
|
+
UserEntity,
|
|
6723
|
+
{
|
|
6724
|
+
size: "xs",
|
|
6725
|
+
variant: "pill",
|
|
6726
|
+
showTooltip: false,
|
|
6727
|
+
interactive: false,
|
|
6728
|
+
displayName: name,
|
|
6729
|
+
initials: initials(name || member.user.email),
|
|
6730
|
+
picture: member.user.picture
|
|
6731
|
+
}
|
|
6732
|
+
);
|
|
6733
|
+
};
|
|
6734
|
+
function ConnectedWorkspaceMembers({
|
|
6735
|
+
workspace,
|
|
6736
|
+
members,
|
|
6737
|
+
selfId,
|
|
6738
|
+
isOwner: isOwnerRaw,
|
|
6739
|
+
isLoading = false,
|
|
6740
|
+
readOnly = false,
|
|
6741
|
+
renderMember = defaultRenderMember,
|
|
6742
|
+
gridState,
|
|
6743
|
+
onGridStateChange
|
|
6744
|
+
}) {
|
|
6745
|
+
const { confirm, notify: notify14 } = useBlocksEnvironment();
|
|
6746
|
+
const isOwner = isOwnerRaw && !readOnly;
|
|
6747
|
+
const [selected, setSelected] = useState([]);
|
|
6748
|
+
const updateRoles = useSetWorkspaceMemberRoles();
|
|
6749
|
+
const remove = useRemoveWorkspaceMembers();
|
|
6750
|
+
const memberWord = (count) => getEntityLabel("WORKSPACEUSER", count === 1 ? "SINGULAR" : "PLURAL");
|
|
6751
|
+
const workspaceWord2 = getEntityLabel("WORKSPACE").toLowerCase();
|
|
6752
|
+
const inWorkspace = `"${workspace.name}"`;
|
|
6753
|
+
const setRoles = async (rows2, role) => {
|
|
6754
|
+
const single = rows2.length === 1 ? memberName(rows2[0].user) : null;
|
|
6755
|
+
try {
|
|
6756
|
+
await updateRoles.mutateAsync({ userIds: rows2.map((r) => r.user.external_id), role });
|
|
6757
|
+
notify14(
|
|
6758
|
+
single ? `${single}'s role updated to ${formatRole(role)} in "${workspace.name}"` : `Updated ${rows2.length} users to ${formatRole(role)} in "${workspace.name}"`,
|
|
6759
|
+
"success"
|
|
6760
|
+
);
|
|
6761
|
+
return true;
|
|
6762
|
+
} catch {
|
|
6763
|
+
notify14(
|
|
6764
|
+
single ? `Failed to update ${single}'s role` : `Failed to update roles in "${workspace.name}"`,
|
|
6765
|
+
"error"
|
|
6766
|
+
);
|
|
6767
|
+
return false;
|
|
6768
|
+
}
|
|
6769
|
+
};
|
|
6770
|
+
const rows = useMemo(
|
|
6771
|
+
() => [...members].sort((a, b) => {
|
|
6772
|
+
if (a.user.external_id === selfId) return -1;
|
|
6773
|
+
if (b.user.external_id === selfId) return 1;
|
|
6774
|
+
return (ROLE_ORDER[a.role] ?? 3) - (ROLE_ORDER[b.role] ?? 3);
|
|
6775
|
+
}),
|
|
6776
|
+
[members, selfId]
|
|
6777
|
+
);
|
|
6778
|
+
const columnDefs = useMemo(
|
|
6779
|
+
() => [
|
|
6780
|
+
{
|
|
6781
|
+
colId: "name",
|
|
6782
|
+
headerName: "Name",
|
|
6783
|
+
flex: 1,
|
|
6784
|
+
minWidth: 150,
|
|
6785
|
+
sortable: true,
|
|
6786
|
+
valueGetter: (p) => p.data ? memberName(p.data.user) : "",
|
|
6787
|
+
cellRenderer: (p) => p.data ? /* @__PURE__ */ jsx(MemberNameCell, { children: renderMember(p.data) }) : null
|
|
6788
|
+
},
|
|
6789
|
+
{
|
|
6790
|
+
colId: "email",
|
|
6791
|
+
headerName: "Email",
|
|
6792
|
+
flex: 1,
|
|
6793
|
+
minWidth: 180,
|
|
6794
|
+
sortable: true,
|
|
6795
|
+
valueGetter: (p) => p.data?.user.email ?? "",
|
|
6796
|
+
// Plain text, not a mailto: the row's click selects it.
|
|
6797
|
+
cellRenderer: (p) => p.data?.user.email ? /* @__PURE__ */ jsx(MemberMutedText, { className: "truncate", title: p.data.user.email, children: p.data.user.email }) : null
|
|
6798
|
+
},
|
|
6799
|
+
{
|
|
6800
|
+
colId: "role",
|
|
6801
|
+
headerName: "Role",
|
|
6802
|
+
width: 145,
|
|
6803
|
+
minWidth: 130,
|
|
6804
|
+
sortable: true,
|
|
6805
|
+
valueGetter: (p) => p.data?.role ?? "",
|
|
6806
|
+
cellRenderer: (p) => {
|
|
6807
|
+
const row = p.data;
|
|
6808
|
+
if (!row) return null;
|
|
6809
|
+
const lock = roleLock(row.user.external_id, selfId, isOwner);
|
|
6810
|
+
return /* @__PURE__ */ jsx(
|
|
6811
|
+
MemberRoleCell,
|
|
6812
|
+
{
|
|
6813
|
+
role: row.role,
|
|
6814
|
+
roles: ALL_ROLES,
|
|
6815
|
+
tooltip: lock ?? getRoleDescription(row.role),
|
|
6816
|
+
canChange: !lock,
|
|
6817
|
+
isPending: updateRoles.isPending,
|
|
6818
|
+
onChange: (value) => void setRoles([row], value),
|
|
6819
|
+
temporaryHint: row.is_temporary ? "Temporary, session-scoped grant \u2014 expires with the session." : void 0
|
|
6820
|
+
}
|
|
6821
|
+
);
|
|
6822
|
+
}
|
|
6823
|
+
},
|
|
6824
|
+
{
|
|
6825
|
+
colId: "joined_at",
|
|
6826
|
+
headerName: "Joined",
|
|
6827
|
+
width: 145,
|
|
6828
|
+
minWidth: 125,
|
|
6829
|
+
sortable: true,
|
|
6830
|
+
valueGetter: (p) => p.data ? new Date(p.data.joined_at).getTime() : "",
|
|
6831
|
+
cellRenderer: (p) => p.data ? /* @__PURE__ */ jsx(MemberMutedText, { className: "cursor-default", children: formatFriendlyDate(p.data.joined_at) }) : null
|
|
6832
|
+
},
|
|
6833
|
+
{
|
|
6834
|
+
colId: "conversation_count",
|
|
6835
|
+
headerName: getEntityLabel("CONVERSATION", "PLURAL"),
|
|
6836
|
+
width: 95,
|
|
6837
|
+
minWidth: 85,
|
|
6838
|
+
sortable: true,
|
|
6839
|
+
type: "numericColumn",
|
|
6840
|
+
valueGetter: (p) => p.data?.conversation_count ?? 0,
|
|
6841
|
+
cellRenderer: (p) => p.data ? /* @__PURE__ */ jsx(MemberMutedText, { children: p.data.conversation_count }) : null
|
|
6842
|
+
},
|
|
6843
|
+
{
|
|
6844
|
+
colId: "document_count",
|
|
6845
|
+
headerName: "Docs",
|
|
6846
|
+
width: 90,
|
|
6847
|
+
minWidth: 80,
|
|
6848
|
+
sortable: true,
|
|
6849
|
+
type: "numericColumn",
|
|
6850
|
+
valueGetter: (p) => p.data?.document_count ?? 0,
|
|
6851
|
+
cellRenderer: (p) => p.data ? /* @__PURE__ */ jsx(MemberMutedText, { children: p.data.document_count }) : null
|
|
6852
|
+
},
|
|
6853
|
+
{
|
|
6854
|
+
colId: "files_mb",
|
|
6855
|
+
headerName: "Storage",
|
|
6856
|
+
width: 110,
|
|
6857
|
+
minWidth: 95,
|
|
6858
|
+
sortable: true,
|
|
6859
|
+
type: "numericColumn",
|
|
6860
|
+
valueGetter: (p) => p.data?.files_mb ?? 0,
|
|
6861
|
+
cellRenderer: (p) => {
|
|
6862
|
+
if (!p.data) return null;
|
|
6863
|
+
const mb = p.data.files_mb ?? 0;
|
|
6864
|
+
return /* @__PURE__ */ jsxs(MemberMutedText, { children: [
|
|
6865
|
+
mb >= 100 ? Math.round(mb).toLocaleString("en-US") : mb.toFixed(1),
|
|
6866
|
+
" MB"
|
|
6867
|
+
] });
|
|
6868
|
+
}
|
|
6869
|
+
}
|
|
6870
|
+
],
|
|
6871
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
6872
|
+
[selfId, isOwner, updateRoles.isPending, renderMember, workspace.name]
|
|
6873
|
+
);
|
|
6874
|
+
const removeSelected = async () => {
|
|
6875
|
+
const count = selected.length;
|
|
6876
|
+
if (count === 0) return;
|
|
6877
|
+
const names = count === 1 ? `"${memberName(selected[0].user)}"` : `${count} ${memberWord(count).toLowerCase()}`;
|
|
6878
|
+
const members2 = getEntityLabel("WORKSPACEUSER", "PLURAL").toLowerCase();
|
|
6879
|
+
const confirmed = await confirm({
|
|
6880
|
+
title: `Remove ${count} ${memberWord(count)}`,
|
|
6881
|
+
message: `Remove ${names} from ${inWorkspace}?
|
|
6882
|
+
|
|
6883
|
+
Note that ${members2} may have saved private content in the ${workspaceWord2} that you cannot see. Once removed they immediately lose access to any content in the ${workspaceWord2}.`
|
|
6884
|
+
});
|
|
6885
|
+
if (!confirmed) return;
|
|
6886
|
+
try {
|
|
6887
|
+
await remove.mutateAsync(selected.map((m) => m.user.external_id));
|
|
6888
|
+
setSelected([]);
|
|
6889
|
+
notify14(`Removed ${names} from ${inWorkspace}`, "success");
|
|
6890
|
+
} catch {
|
|
6891
|
+
notify14(`Failed to remove some users from ${inWorkspace}`, "error");
|
|
6892
|
+
}
|
|
6893
|
+
};
|
|
6894
|
+
const changeSelectedRoles = async (role) => {
|
|
6895
|
+
const changeable = selected.filter((m) => !roleLock(m.user.external_id, selfId, isOwner));
|
|
6896
|
+
if (changeable.length === 0) return;
|
|
6897
|
+
if (await setRoles(changeable, role)) setSelected([]);
|
|
6898
|
+
};
|
|
6899
|
+
const canRemoveSelected = selected.every(
|
|
6900
|
+
(m) => m.role !== "owner" && m.user.external_id !== selfId
|
|
6901
|
+
);
|
|
6902
|
+
const canChangeRoleSelected = selected.some((m) => !roleLock(m.user.external_id, selfId, isOwner));
|
|
6903
|
+
const ownerOnly = `Only available to ${workspaceWord2} owner`;
|
|
6904
|
+
if (isLoading) return /* @__PURE__ */ jsx(MemberListLoading, {});
|
|
6905
|
+
return /* @__PURE__ */ jsxs("div", { "data-testid": "workspace-users", className: "relative flex flex-col", children: [
|
|
6906
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-auto", children: /* @__PURE__ */ jsx(
|
|
6907
|
+
GridView,
|
|
6908
|
+
{
|
|
6909
|
+
items: rows,
|
|
6910
|
+
columnDefs,
|
|
6911
|
+
getItemId: (m) => m.user.external_id,
|
|
6912
|
+
selection: { mode: "multiple", selected, onSelectionChange: setSelected },
|
|
6913
|
+
persistState: !!onGridStateChange,
|
|
6914
|
+
gridId: `workspace-users-${workspace.external_id}-v6`,
|
|
6915
|
+
gridState,
|
|
6916
|
+
onGridStateChange,
|
|
6917
|
+
height: "300px",
|
|
6918
|
+
className: "w-full grid-sort-left"
|
|
6919
|
+
}
|
|
6920
|
+
) }),
|
|
6921
|
+
!readOnly && /* @__PURE__ */ jsx(SelectionBar, { count: selected.length, onClear: () => setSelected([]), children: /* @__PURE__ */ jsx(
|
|
6922
|
+
MemberBulkActions,
|
|
6923
|
+
{
|
|
6924
|
+
roles: BULK_ROLES,
|
|
6925
|
+
onChangeRole: (value) => void changeSelectedRoles(value),
|
|
6926
|
+
isRoleDisabled: !isOwner || !canChangeRoleSelected || updateRoles.isPending,
|
|
6927
|
+
roleTooltip: !isOwner ? ownerOnly : selected.length === 0 ? "Select users to change their role" : !canChangeRoleSelected ? "You cannot change your own role" : "Change role for selected users",
|
|
6928
|
+
isOwner,
|
|
6929
|
+
onRemove: () => void removeSelected(),
|
|
6930
|
+
isRemoveDisabled: !isOwner || !canRemoveSelected || remove.isPending,
|
|
6931
|
+
removeTooltip: !isOwner ? ownerOnly : "Remove selected from workspace"
|
|
6932
|
+
}
|
|
6933
|
+
) })
|
|
6934
|
+
] });
|
|
6935
|
+
}
|
|
6717
6936
|
var isAgentMember = (member) => member.user.external_id.startsWith("agt-");
|
|
6718
|
-
var
|
|
6937
|
+
var memberName2 = (member) => member.user.family_name ? `${member.user.given_name} ${member.user.family_name}` : member.user.given_name;
|
|
6719
6938
|
function ConnectedWorkspaceSettings({
|
|
6720
6939
|
workspaceId,
|
|
6721
6940
|
project,
|
|
6722
6941
|
projectLine,
|
|
6723
|
-
|
|
6942
|
+
renderMember,
|
|
6943
|
+
membersGridState,
|
|
6944
|
+
onMembersGridStateChange,
|
|
6724
6945
|
shareDialog,
|
|
6725
6946
|
children,
|
|
6726
6947
|
onExport,
|
|
@@ -6730,6 +6951,7 @@ function ConnectedWorkspaceSettings({
|
|
|
6730
6951
|
}) {
|
|
6731
6952
|
const settings = useConnectedWorkspaceSettings(workspaceId, { onLeft });
|
|
6732
6953
|
const { workspace, members, isOwner, isMember } = settings;
|
|
6954
|
+
const { userId } = useConnectedWorkspace();
|
|
6733
6955
|
const { data: userSettings } = useUserSettings();
|
|
6734
6956
|
const isDeveloper = userSettings?.developer === true;
|
|
6735
6957
|
const showAgents = userSettings?.show_agent_sessions ?? false;
|
|
@@ -6817,7 +7039,7 @@ function ConnectedWorkspaceSettings({
|
|
|
6817
7039
|
MemberAvatarStack,
|
|
6818
7040
|
{
|
|
6819
7041
|
members: visibleMembers.map((m) => ({
|
|
6820
|
-
name:
|
|
7042
|
+
name: memberName2(m),
|
|
6821
7043
|
picture: m.user.picture
|
|
6822
7044
|
})),
|
|
6823
7045
|
total: visibleMembers.length,
|
|
@@ -6838,17 +7060,23 @@ function ConnectedWorkspaceSettings({
|
|
|
6838
7060
|
]
|
|
6839
7061
|
}
|
|
6840
7062
|
),
|
|
6841
|
-
isMember &&
|
|
7063
|
+
isMember && /* @__PURE__ */ jsx(
|
|
6842
7064
|
ExpandableSection,
|
|
6843
7065
|
{
|
|
6844
7066
|
expanded: membersExpanded,
|
|
6845
7067
|
testId: "workspace-settings-members-section",
|
|
6846
|
-
children:
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
7068
|
+
children: /* @__PURE__ */ jsx(
|
|
7069
|
+
ConnectedWorkspaceMembers,
|
|
7070
|
+
{
|
|
7071
|
+
workspace,
|
|
7072
|
+
members: visibleMembers,
|
|
7073
|
+
selfId: userId,
|
|
7074
|
+
isOwner,
|
|
7075
|
+
renderMember,
|
|
7076
|
+
gridState: membersGridState,
|
|
7077
|
+
onGridStateChange: onMembersGridStateChange
|
|
7078
|
+
}
|
|
7079
|
+
)
|
|
6852
7080
|
}
|
|
6853
7081
|
)
|
|
6854
7082
|
] }),
|
|
@@ -7516,20 +7744,6 @@ function ConnectedDriveSyncDialog({
|
|
|
7516
7744
|
}
|
|
7517
7745
|
);
|
|
7518
7746
|
}
|
|
7519
|
-
var BillingSupportContext = createContext({ stripeEnabled: false });
|
|
7520
|
-
function BillingSupportProvider({
|
|
7521
|
-
value,
|
|
7522
|
-
children
|
|
7523
|
-
}) {
|
|
7524
|
-
return /* @__PURE__ */ jsx(BillingSupportContext.Provider, { value, children });
|
|
7525
|
-
}
|
|
7526
|
-
function useBillingSupport() {
|
|
7527
|
-
return useContext(BillingSupportContext);
|
|
7528
|
-
}
|
|
7529
|
-
function useSelfExtId() {
|
|
7530
|
-
const arbi = useArbi();
|
|
7531
|
-
return arbi.isLoggedIn ? arbi.user.identity()?.externalId ?? void 0 : void 0;
|
|
7532
|
-
}
|
|
7533
7747
|
var focus = null;
|
|
7534
7748
|
var listeners = /* @__PURE__ */ new Set();
|
|
7535
7749
|
function requestProjectBillingFocus(next) {
|
|
@@ -7549,363 +7763,8 @@ function BillingProjectEntity({
|
|
|
7549
7763
|
...props
|
|
7550
7764
|
}) {
|
|
7551
7765
|
const { stripeEnabled } = useBillingSupport();
|
|
7552
|
-
const
|
|
7553
|
-
return /* @__PURE__ */ jsx(ProjectEntity, { ...props, subscription, isPaid });
|
|
7554
|
-
}
|
|
7555
|
-
|
|
7556
|
-
// src/connection/billing/plan-constants.ts
|
|
7557
|
-
var PRO_AI_GBP = 10;
|
|
7558
|
-
var PRO_STORAGE_GB = 5;
|
|
7559
|
-
var ADDON_BLOCK_AI_GBP = 25;
|
|
7560
|
-
var STORAGE_BLOCK_GB = 25;
|
|
7561
|
-
var DEFAULT_SPEND_CAP_PENCE = 1e4;
|
|
7562
|
-
var MIN_AUTO_TOPUP_THRESHOLD_GBP = 1;
|
|
7563
|
-
var FREE_AI_GBP = 1;
|
|
7564
|
-
var FREE_STORAGE_MB = 50;
|
|
7565
|
-
var UNLIMITED_COLLABORATORS = 999;
|
|
7566
|
-
var SUGGESTED_DAILY_CAP_PER_USER = 10;
|
|
7567
|
-
|
|
7568
|
-
// src/connection/billing/quota-items.ts
|
|
7569
|
-
var fmt = (v) => Math.round(v).toLocaleString("en-US");
|
|
7570
|
-
function formatResetDate(unixTimestamp) {
|
|
7571
|
-
if (!unixTimestamp) return null;
|
|
7572
|
-
const date = new Date(unixTimestamp * 1e3);
|
|
7573
|
-
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
|
|
7574
|
-
}
|
|
7575
|
-
function aiUsageResetTimestamp(project) {
|
|
7576
|
-
const periodEnd = project.plan_interval === "year" ? null : project.current_period_end;
|
|
7577
|
-
const ts = project.addons?.ai_usage_reset_at ?? periodEnd ?? project.quotas?.budget_reset_at ?? null;
|
|
7578
|
-
return ts && ts * 1e3 > Date.now() ? ts : null;
|
|
7579
|
-
}
|
|
7580
|
-
var quota = (q) => ({
|
|
7581
|
-
used: q?.used ?? 0,
|
|
7582
|
-
limit: q?.limit ?? 0
|
|
7583
|
-
});
|
|
7584
|
-
function storageText(gbValue, limitGb, unit = true) {
|
|
7585
|
-
if (limitGb >= 1) {
|
|
7586
|
-
const n = Math.round(gbValue * 10) / 10;
|
|
7587
|
-
return `${n.toLocaleString("en-US")}${unit ? " GB" : ""}`;
|
|
7588
|
-
}
|
|
7589
|
-
return `${fmt(gbValue * 1e3)}${unit ? " MB" : ""}`;
|
|
7590
|
-
}
|
|
7591
|
-
var pctOf = (used, limit) => limit > 0 ? used / limit * 100 : 0;
|
|
7592
|
-
function getQuotaItems(quotas) {
|
|
7593
|
-
const aiUsage = quota(quotas?.ai_usage);
|
|
7594
|
-
const storage = quota(quotas?.storage_gb);
|
|
7595
|
-
const collaborators = quota(quotas?.collaborators);
|
|
7596
|
-
const unlimitedCollaborators = collaborators.limit >= UNLIMITED_COLLABORATORS;
|
|
7597
|
-
return [
|
|
7598
|
-
{
|
|
7599
|
-
key: "ai-usage",
|
|
7600
|
-
icon: PoundSterling,
|
|
7601
|
-
label: "AI usage",
|
|
7602
|
-
used: formatGbp(aiUsage.used),
|
|
7603
|
-
usedOfLimit: `${formatGbp(aiUsage.used)} / ${formatGbp(aiUsage.limit)}`,
|
|
7604
|
-
pct: pctOf(aiUsage.used, aiUsage.limit)
|
|
7605
|
-
},
|
|
7606
|
-
{
|
|
7607
|
-
key: "storage",
|
|
7608
|
-
icon: HardDrive,
|
|
7609
|
-
label: "Storage",
|
|
7610
|
-
used: storageText(storage.used, storage.limit),
|
|
7611
|
-
usedOfLimit: `${storageText(storage.used, storage.limit, false)} / ${storageText(
|
|
7612
|
-
storage.limit,
|
|
7613
|
-
storage.limit
|
|
7614
|
-
)}`,
|
|
7615
|
-
pct: pctOf(storage.used, storage.limit)
|
|
7616
|
-
},
|
|
7617
|
-
{
|
|
7618
|
-
key: "collaborators",
|
|
7619
|
-
icon: Users,
|
|
7620
|
-
label: "Collaborators",
|
|
7621
|
-
used: fmt(collaborators.used),
|
|
7622
|
-
usedOfLimit: unlimitedCollaborators ? `${fmt(collaborators.used)} / Unlimited` : `${fmt(collaborators.used)} / ${fmt(collaborators.limit)}`,
|
|
7623
|
-
// Zero seats (Free) IS the cap: 0 / 0 shows a full destructive bar,
|
|
7624
|
-
// because you can't add anyone — the truth the upgrade door answers.
|
|
7625
|
-
// (Only when the quota is actually present; a missing field on a
|
|
7626
|
-
// partial payload stays an empty bar, not a false alarm.)
|
|
7627
|
-
pct: unlimitedCollaborators ? 0 : quotas?.collaborators && collaborators.limit === 0 ? 100 : pctOf(collaborators.used, collaborators.limit)
|
|
7628
|
-
}
|
|
7629
|
-
];
|
|
7630
|
-
}
|
|
7631
|
-
function useRewardBalance() {
|
|
7632
|
-
const { data } = useUserSettings();
|
|
7633
|
-
const ed = data?.extra_discount;
|
|
7634
|
-
if (!ed || !ed.amount || ed.amount <= 0) return null;
|
|
7635
|
-
return { amount: ed.amount, currency: ed.currency || "gbp" };
|
|
7636
|
-
}
|
|
7637
|
-
function formatMoney(cents, currency = "gbp") {
|
|
7638
|
-
const value = cents / 100;
|
|
7639
|
-
const isWhole = Number.isInteger(value);
|
|
7640
|
-
const c = currency.toLowerCase();
|
|
7641
|
-
const symbol = c === "usd" ? "$" : c === "gbp" ? "\xA3" : c === "eur" ? "\u20AC" : currency.toUpperCase() + " ";
|
|
7642
|
-
return `${symbol}${value.toLocaleString("en-US", {
|
|
7643
|
-
minimumFractionDigits: isWhole ? 0 : 2,
|
|
7644
|
-
maximumFractionDigits: isWhole ? 0 : 2
|
|
7645
|
-
})}`;
|
|
7646
|
-
}
|
|
7647
|
-
var gbp = (pence) => formatMoney(pence, "gbp");
|
|
7648
|
-
function formatPeriodEnd(unix) {
|
|
7649
|
-
if (!unix) return null;
|
|
7650
|
-
return new Date(unix * 1e3).toLocaleDateString("en-GB", {
|
|
7651
|
-
day: "numeric",
|
|
7652
|
-
month: "short",
|
|
7653
|
-
year: "numeric"
|
|
7654
|
-
});
|
|
7655
|
-
}
|
|
7656
|
-
function addonPricePence(kind, addons) {
|
|
7657
|
-
return kind === "ai_usage" ? addons.ai_block_price_pence : addons.storage_block_price_pence;
|
|
7658
|
-
}
|
|
7659
|
-
function addonPriceExVatPence(kind, addons) {
|
|
7660
|
-
return kind === "ai_usage" ? addons.ai_block_price_ex_vat_pence : addons.storage_block_price_ex_vat_pence;
|
|
7661
|
-
}
|
|
7662
|
-
function addonSettingsBody(addons, change = {}) {
|
|
7663
|
-
return {
|
|
7664
|
-
spend_cap_pence: addons.spend_cap_pence ?? null,
|
|
7665
|
-
auto_topup_threshold_gbp: addons.auto_topup_threshold_gbp,
|
|
7666
|
-
...change
|
|
7667
|
-
};
|
|
7668
|
-
}
|
|
7669
|
-
function splitBlocks(kind, count, addons) {
|
|
7670
|
-
const granted = (kind === "ai_usage" ? addons.legacy_ai_blocks : addons.legacy_storage_blocks) ?? 0;
|
|
7671
|
-
const legacy = Math.max(0, Math.min(granted, count));
|
|
7672
|
-
return { legacy, current: count - legacy };
|
|
7673
|
-
}
|
|
7674
|
-
function blockSize(kind, addons, legacy) {
|
|
7675
|
-
if (kind === "ai_usage") {
|
|
7676
|
-
return formatGbp(legacy ? addons.legacy_block_ai_gbp ?? 10 : addons.block_ai_gbp);
|
|
7677
|
-
}
|
|
7678
|
-
return `${legacy ? addons.legacy_block_storage_gb ?? 10 : addons.block_storage_gb} GB`;
|
|
7679
|
-
}
|
|
7680
|
-
function blocksLabel(kind, count, addons) {
|
|
7681
|
-
const { legacy, current } = splitBlocks(kind, count, addons);
|
|
7682
|
-
const parts = [
|
|
7683
|
-
...legacy > 0 ? [`${legacy} \xD7 ${blockSize(kind, addons, true)}`] : [],
|
|
7684
|
-
...current > 0 || legacy === 0 ? [`${current} \xD7 ${blockSize(kind, addons, false)}`] : []
|
|
7685
|
-
];
|
|
7686
|
-
return `${parts.join(" + ")}${kind === "ai_usage" ? " of AI usage" : ""}`;
|
|
7687
|
-
}
|
|
7688
|
-
function reduceBounds(kind, addons) {
|
|
7689
|
-
const current = kind === "ai_usage" ? addons.ai_blocks : addons.storage_blocks;
|
|
7690
|
-
const pending = kind === "ai_usage" ? addons.ai_blocks_next : addons.storage_blocks_next;
|
|
7691
|
-
const min = kind === "ai_usage" ? 0 : Math.min(addons.storage_blocks_min, current);
|
|
7692
|
-
return { min, current, next: pending ?? current };
|
|
7693
|
-
}
|
|
7694
|
-
function blockMonthlyExVatPence(kind, addons) {
|
|
7695
|
-
return kind === "ai_usage" ? addons.ai_block_monthly_ex_vat_pence : addons.storage_block_monthly_ex_vat_pence;
|
|
7696
|
-
}
|
|
7697
|
-
function blockPeriodExVatPence(kind, addons) {
|
|
7698
|
-
return kind === "ai_usage" ? addons.ai_block_period_ex_vat_pence : addons.storage_block_period_ex_vat_pence;
|
|
7699
|
-
}
|
|
7700
|
-
function blockRenewalShort(kind, addons, isAnnual) {
|
|
7701
|
-
return `${gbp(blockPeriodExVatPence(kind, addons))}/${isAnnual ? "year" : "month"} + VAT`;
|
|
7702
|
-
}
|
|
7703
|
-
var ANNUAL_PREMIUM_AI_NOTE = "No markup on Premium AI on annual plans";
|
|
7704
|
-
function addonCharge(kind, addons, isAnnual, periodEnd) {
|
|
7705
|
-
return {
|
|
7706
|
-
now: gbp(addonPricePence(kind, addons)),
|
|
7707
|
-
nowExVat: gbp(addonPriceExVatPence(kind, addons)),
|
|
7708
|
-
nowVat: gbp(addonPricePence(kind, addons) - addonPriceExVatPence(kind, addons)),
|
|
7709
|
-
until: isAnnual ? formatPeriodEnd(periodEnd) : null,
|
|
7710
|
-
then: blockRenewalShort(kind, addons, isAnnual)
|
|
7711
|
-
};
|
|
7712
|
-
}
|
|
7713
|
-
var CAP_REACHED_MESSAGE = "Usage cap reached: no more automatic top-ups this month.";
|
|
7714
|
-
var ZERO_BUDGET_MESSAGE = "Automatic top-ups are off (\xA30 usage cap).";
|
|
7715
|
-
var USAGE_CAP_LABEL = "Usage cap";
|
|
7716
|
-
var USAGE_CAP_HELP = "The most automatic top-ups (AI usage added when it runs low, storage added when an upload needs it) may cost in a month. Adding or removing by hand is never limited by it; the plan and renewals are not counted. \xA30 = no automatic top-ups: AI usage and uploads stop at your allowance unless you add more. No limit = no cap.";
|
|
7717
|
-
function isAddonBudgetZero(addons) {
|
|
7718
|
-
return addons.spend_cap_pence === 0;
|
|
7719
|
-
}
|
|
7720
|
-
function autoRoom(kind, addons) {
|
|
7721
|
-
const left = addons.cap_left_pence;
|
|
7722
|
-
if (left == null) return addons.spend_cap_pence === 0 ? 0 : null;
|
|
7723
|
-
const price = addonPriceExVatPence(kind, addons);
|
|
7724
|
-
if (price <= 0) return 0;
|
|
7725
|
-
const blocks = Math.floor(left / price);
|
|
7726
|
-
return blocks * (kind === "ai_usage" ? addons.block_ai_gbp : addons.block_storage_gb);
|
|
7727
|
-
}
|
|
7728
|
-
function nextRemovedBlockSize(kind, addons) {
|
|
7729
|
-
const { next } = reduceBounds(kind, addons);
|
|
7730
|
-
const { current } = splitBlocks(kind, next, addons);
|
|
7731
|
-
if (kind === "ai_usage") {
|
|
7732
|
-
return current > 0 ? addons.block_ai_gbp : addons.legacy_block_ai_gbp ?? 10;
|
|
7733
|
-
}
|
|
7734
|
-
return current > 0 ? addons.block_storage_gb : addons.legacy_block_storage_gb ?? 10;
|
|
7735
|
-
}
|
|
7736
|
-
function parseBudgetPounds(value) {
|
|
7737
|
-
if (value.trim() === "") return null;
|
|
7738
|
-
const n = Number(value);
|
|
7739
|
-
if (!Number.isFinite(n) || n < 0) return void 0;
|
|
7740
|
-
return Math.round(n * 100);
|
|
7741
|
-
}
|
|
7742
|
-
function budgetSavedMessage(capPence) {
|
|
7743
|
-
if (capPence == null) return "Usage cap: no limit";
|
|
7744
|
-
if (capPence === 0) return "Usage cap: \xA30 (no automatic top-ups)";
|
|
7745
|
-
return `Usage cap: ${gbp(capPence)} a month`;
|
|
7746
|
-
}
|
|
7747
|
-
var gbText = (n) => `${Math.round(n * 10) / 10} GB`;
|
|
7748
|
-
function stepLabel(kind, size) {
|
|
7749
|
-
return kind === "ai_usage" ? `${formatGbp(size)} of AI usage` : gbText(size);
|
|
7750
|
-
}
|
|
7751
|
-
function nextPeriodBody(kind, keep) {
|
|
7752
|
-
return kind === "ai_usage" ? { ai_blocks: keep, storage_blocks: null } : { ai_blocks: null, storage_blocks: keep };
|
|
7753
|
-
}
|
|
7754
|
-
function addStepCaption(kind, addons) {
|
|
7755
|
-
const now = gbp(addonPriceExVatPence(kind, addons));
|
|
7756
|
-
const today = kind === "storage" ? `prorated today ${now}` : `today ${now}`;
|
|
7757
|
-
return `${gbp(blockMonthlyExVatPence(kind, addons))}/month \xB7 ${today}`;
|
|
7758
|
-
}
|
|
7759
|
-
|
|
7760
|
-
// src/connection/billing/api-error.ts
|
|
7761
|
-
function apiErrorDetail(error) {
|
|
7762
|
-
const body = error?.apiError ?? error;
|
|
7763
|
-
return body?.detail;
|
|
7764
|
-
}
|
|
7765
|
-
|
|
7766
|
-
// src/connection/billing/tax-quote.ts
|
|
7767
|
-
var POSTCODE_COUNTRIES = ["US", "CA"];
|
|
7768
|
-
var TAX_LOCATION_INVALID = "TAX_LOCATION_INVALID";
|
|
7769
|
-
function money2(pence, currency = "gbp") {
|
|
7770
|
-
const symbol = { gbp: "\xA3", usd: "$", eur: "\u20AC" }[currency.toLowerCase()] ?? `${currency} `;
|
|
7771
|
-
return `${symbol}${(pence / 100).toLocaleString("en-GB", {
|
|
7772
|
-
minimumFractionDigits: 2,
|
|
7773
|
-
maximumFractionDigits: 2
|
|
7774
|
-
})}`;
|
|
7775
|
-
}
|
|
7776
|
-
function taxKind(label) {
|
|
7777
|
-
return /^vat\b/i.test(label ?? "") ? "VAT" : "tax";
|
|
7778
|
-
}
|
|
7779
|
-
function taxDetail({ subtotal, tax, label, reverseCharge }) {
|
|
7780
|
-
if (reverseCharge) return "\xB7 no VAT (reverse charge)";
|
|
7781
|
-
if (tax <= 0) return "\xB7 no tax";
|
|
7782
|
-
return `incl. ${taxKind(label)} (${money2(subtotal)} + ${money2(tax)} ${label ?? "tax"})`;
|
|
7783
|
-
}
|
|
7784
|
-
function taxSummary(parts) {
|
|
7785
|
-
return `${money2(parts.total)} ${taxDetail(parts)}`;
|
|
7786
|
-
}
|
|
7787
|
-
function totalWithTaxWord({ total, tax, label }) {
|
|
7788
|
-
return tax > 0 ? `${money2(total)} incl. ${taxKind(label)}` : money2(total);
|
|
7789
|
-
}
|
|
7790
|
-
function quoteParts(quote) {
|
|
7791
|
-
return {
|
|
7792
|
-
subtotal: quote.subtotal_pence,
|
|
7793
|
-
tax: quote.tax_pence,
|
|
7794
|
-
total: quote.total_pence,
|
|
7795
|
-
label: quote.tax_label,
|
|
7796
|
-
reverseCharge: quote.reverse_charge
|
|
7797
|
-
};
|
|
7798
|
-
}
|
|
7799
|
-
function quoteTotalLine(quote) {
|
|
7800
|
-
return `Total today ${taxSummary(quoteParts(quote))}`;
|
|
7801
|
-
}
|
|
7802
|
-
function quoteError(error) {
|
|
7803
|
-
const detail = apiErrorDetail(error);
|
|
7804
|
-
if (detail && typeof detail === "object") {
|
|
7805
|
-
const { code, message } = detail;
|
|
7806
|
-
return {
|
|
7807
|
-
code: typeof code === "string" ? code : void 0,
|
|
7808
|
-
message: typeof message === "string" ? message : void 0
|
|
7809
|
-
};
|
|
7810
|
-
}
|
|
7811
|
-
return { message: typeof detail === "string" ? detail : void 0 };
|
|
7812
|
-
}
|
|
7813
|
-
|
|
7814
|
-
// src/connection/billing/plan-pricing.ts
|
|
7815
|
-
var gbp2 = (pence) => formatMoney(pence, "gbp");
|
|
7816
|
-
function plusVat(exVatPence, currency = "gbp") {
|
|
7817
|
-
return `${formatMoney(exVatPence, currency)} + VAT`;
|
|
7818
|
-
}
|
|
7819
|
-
var PRO_PACKS = 1;
|
|
7820
|
-
var PLAN_CHOICE_PACKS = {
|
|
7821
|
-
pro: PRO_PACKS
|
|
7822
|
-
};
|
|
7823
|
-
function planNameForPacks(packs) {
|
|
7824
|
-
const n = packs ?? 0;
|
|
7825
|
-
if (n <= 0) return "Free";
|
|
7826
|
-
if (n === PRO_PACKS) return "Pro";
|
|
7827
|
-
return `Pro \xD7${n}`;
|
|
7828
|
-
}
|
|
7829
|
-
function planPreviewLines(preview) {
|
|
7830
|
-
if (preview.unchanged) return [{ id: "no-change", text: "No change" }];
|
|
7831
|
-
const date = formatPeriodEnd(preview.next_renewal_at);
|
|
7832
|
-
const cadence = preview.interval === "year" ? "year" : "month";
|
|
7833
|
-
const lines = [
|
|
7834
|
-
preview.awaiting_confirmation ? { id: "awaiting-bank", text: "Waiting for your bank" } : {
|
|
7835
|
-
id: "pay-today",
|
|
7836
|
-
text: preview.effective_now ? `Pay today ${taxSummary({
|
|
7837
|
-
subtotal: preview.due_now_ex_vat_pence,
|
|
7838
|
-
tax: preview.due_now_tax_pence,
|
|
7839
|
-
total: preview.due_now_pence,
|
|
7840
|
-
label: preview.tax_label
|
|
7841
|
-
})}` : `Pay today ${gbp2(0)}`
|
|
7842
|
-
},
|
|
7843
|
-
{
|
|
7844
|
-
id: "from",
|
|
7845
|
-
text: `${date ? `From ${date}` : "From renewal"}: ${taxSummary({
|
|
7846
|
-
subtotal: preview.next_renewal_ex_vat_pence,
|
|
7847
|
-
tax: preview.next_renewal_tax_pence,
|
|
7848
|
-
total: preview.next_renewal_pence,
|
|
7849
|
-
label: preview.tax_label
|
|
7850
|
-
})} a ${cadence}`
|
|
7851
|
-
}
|
|
7852
|
-
];
|
|
7853
|
-
if (!preview.effective_now) {
|
|
7854
|
-
lines.push({ id: "current-until", text: `Current plan until ${date ?? "renewal"}` });
|
|
7855
|
-
}
|
|
7856
|
-
return lines;
|
|
7857
|
-
}
|
|
7858
|
-
var PAY_TODAY_UNPRICED = "Pay today: the difference for the rest of this period";
|
|
7859
|
-
function addonPrice(price, kind = "ai_usage") {
|
|
7860
|
-
return price?.addon_prices?.find((p) => p.kind === kind);
|
|
7861
|
-
}
|
|
7862
|
-
function checkoutItemsLabel(aiBlocks, storageBlocks) {
|
|
7863
|
-
return [
|
|
7864
|
-
"Pro",
|
|
7865
|
-
aiBlocks > 0 && `${aiBlocks} \xD7 ${formatGbp(ADDON_BLOCK_AI_GBP)} of AI usage`,
|
|
7866
|
-
storageBlocks > 0 && `${storageBlocks} \xD7 ${STORAGE_BLOCK_GB} GB`
|
|
7867
|
-
].filter(Boolean).join(" + ");
|
|
7868
|
-
}
|
|
7869
|
-
function packPrice(price, packs = PRO_PACKS) {
|
|
7870
|
-
return price?.pack_prices?.find((p) => p.packs === packs);
|
|
7871
|
-
}
|
|
7872
|
-
function findProPrices(products) {
|
|
7873
|
-
const prices = products?.[0]?.prices ?? [];
|
|
7874
|
-
const pick = (interval) => prices.find((p) => p.interval === interval && p.default_price) ?? prices.find((p) => p.interval === interval);
|
|
7875
|
-
return { monthly: pick("month"), annual: pick("year") };
|
|
7876
|
-
}
|
|
7877
|
-
function calcSavingsPercent(monthlyPrice, annualPrice) {
|
|
7878
|
-
const monthly = packPrice(monthlyPrice)?.amount ?? monthlyPrice?.amount;
|
|
7879
|
-
const annual = packPrice(annualPrice)?.amount ?? annualPrice?.amount;
|
|
7880
|
-
if (monthly == null || annual == null || monthly === 0) return null;
|
|
7881
|
-
const savings = Math.round((monthly * 12 - annual) / (monthly * 12) * 100);
|
|
7882
|
-
return savings > 0 ? savings : null;
|
|
7883
|
-
}
|
|
7884
|
-
var MAX_CHECKOUT_BLOCKS = 100;
|
|
7885
|
-
function checkoutBlockPriceText(price, kind) {
|
|
7886
|
-
const block = addonPrice(price, kind);
|
|
7887
|
-
if (!block) return null;
|
|
7888
|
-
const currency = price?.currency ?? "gbp";
|
|
7889
|
-
return price?.interval === "year" ? `${formatMoney(block.amount_ex_vat, currency)}/year + VAT each` : `${formatMoney(block.amount_ex_vat, currency)}/month + VAT each`;
|
|
7890
|
-
}
|
|
7891
|
-
|
|
7892
|
-
// src/connection/billing/plan-badge.ts
|
|
7893
|
-
function isProProject(project) {
|
|
7894
|
-
return project.plan_interval != null;
|
|
7895
|
-
}
|
|
7896
|
-
function proTierName(project) {
|
|
7897
|
-
return project.addons?.plan_name || planNameForPacks(Math.max(project.packs ?? 1, 1));
|
|
7898
|
-
}
|
|
7899
|
-
function planName(project) {
|
|
7900
|
-
if (project.subscription === "dev") return "Dev";
|
|
7901
|
-
if (!isProProject(project)) return "Free";
|
|
7902
|
-
return `${proTierName(project)} ${project.plan_interval === "year" ? "annual" : "monthly"}`;
|
|
7903
|
-
}
|
|
7904
|
-
function planBadgeView(project) {
|
|
7905
|
-
const { subscription } = project;
|
|
7906
|
-
const label = subscription === "canceled" ? "Cancelling" : subscription === "dev" ? "Dev" : isProProject(project) ? proTierName(project) : "Free";
|
|
7907
|
-
const tone = subscription === "dev" ? "dev" : subscription === "canceled" || subscription === "past_due" ? "pending" : isProProject(project) ? "paid" : "free";
|
|
7908
|
-
return { label, tone };
|
|
7766
|
+
const isPaid2 = stripeEnabled && (subscription === "active" || subscription === "past_due" || subscription === "dev");
|
|
7767
|
+
return /* @__PURE__ */ jsx(ProjectEntity, { ...props, subscription, isPaid: isPaid2 });
|
|
7909
7768
|
}
|
|
7910
7769
|
function formatInvoiceDate(iso) {
|
|
7911
7770
|
const d = /* @__PURE__ */ new Date(`${iso}T00:00:00`);
|
|
@@ -7925,7 +7784,7 @@ function InvoicesModal({
|
|
|
7925
7784
|
const { data, isLoading } = useProjectInvoices(open ? projectExtId : void 0);
|
|
7926
7785
|
const invoices = data?.invoices;
|
|
7927
7786
|
const invoiceViews = (invoices ?? []).map((inv, i) => {
|
|
7928
|
-
const
|
|
7787
|
+
const isPaid2 = inv.status === "paid";
|
|
7929
7788
|
return {
|
|
7930
7789
|
key: inv.number ?? String(i),
|
|
7931
7790
|
date: formatInvoiceDate(inv.date),
|
|
@@ -7933,9 +7792,9 @@ function InvoicesModal({
|
|
|
7933
7792
|
amount: formatMoney(inv.amount, inv.currency),
|
|
7934
7793
|
amountNote: inv.amount_ex_vat != null && inv.amount_ex_vat !== inv.amount ? `${formatMoney(inv.amount_ex_vat, inv.currency)} ex VAT` : void 0,
|
|
7935
7794
|
status: inv.status,
|
|
7936
|
-
isPaid,
|
|
7795
|
+
isPaid: isPaid2,
|
|
7937
7796
|
href: inv.hosted_invoice_url ?? inv.url ?? inv.pdf_url ?? void 0,
|
|
7938
|
-
linkLabel:
|
|
7797
|
+
linkLabel: isPaid2 ? "Receipt" : "Invoice"
|
|
7939
7798
|
};
|
|
7940
7799
|
});
|
|
7941
7800
|
return /* @__PURE__ */ jsx(
|
|
@@ -8134,7 +7993,7 @@ function buildPlanCards(selected, isAnnual, prices, isOnFree) {
|
|
|
8134
7993
|
features: [
|
|
8135
7994
|
`${formatGbp(FREE_AI_GBP)} of AI usage / month`,
|
|
8136
7995
|
`${FREE_STORAGE_MB.toLocaleString("en-US")} MB storage`,
|
|
8137
|
-
"Just you"
|
|
7996
|
+
FREE_COLLABORATORS === 0 ? "Just you" : `${FREE_COLLABORATORS} collaborators`
|
|
8138
7997
|
]
|
|
8139
7998
|
};
|
|
8140
7999
|
return [free, proCard(selected, isAnnual, prices)];
|
|
@@ -9349,7 +9208,7 @@ function ReduceAddonDialog({
|
|
|
9349
9208
|
}
|
|
9350
9209
|
) });
|
|
9351
9210
|
}
|
|
9352
|
-
var
|
|
9211
|
+
var gbp = (pence) => formatMoney(pence, "gbp");
|
|
9353
9212
|
var gb2 = (n) => `${Math.round(n * 10) / 10} GB`;
|
|
9354
9213
|
var pct = (used, limit) => limit > 0 ? Math.min(100, used / limit * 100) : 0;
|
|
9355
9214
|
function formatShortDate(unix) {
|
|
@@ -9475,7 +9334,7 @@ function ResourceMeter({
|
|
|
9475
9334
|
const from = periodEnd ?? "your next renewal";
|
|
9476
9335
|
const fromShort = formatShortDate(addons.period_end ?? project.current_period_end) ?? "renewal";
|
|
9477
9336
|
const { min, current, next } = reduceBounds(kind, addons);
|
|
9478
|
-
const
|
|
9337
|
+
const fmt = (n) => isAi ? formatGbp(n) : gb2(n);
|
|
9479
9338
|
const used = isAi ? addons.ai_used_gbp : addons.storage_used_gb;
|
|
9480
9339
|
const allowance = isAi ? addons.ai_allowance_gbp : addons.storage_allowance_gb;
|
|
9481
9340
|
const allowanceNext = isAi ? addons.ai_allowance_next_gbp : addons.storage_allowance_next_gb;
|
|
@@ -9503,12 +9362,12 @@ function ResourceMeter({
|
|
|
9503
9362
|
{
|
|
9504
9363
|
label: isAi ? "AI usage" : "Storage",
|
|
9505
9364
|
hint: resets ? `resets ${resets}` : void 0,
|
|
9506
|
-
value: `${
|
|
9365
|
+
value: `${fmt(used)} of ${fmt(allowance)}`,
|
|
9507
9366
|
allowance: { used, plan, topUps, autoRoom: capZero ? 0 : room },
|
|
9508
9367
|
legend: {
|
|
9509
|
-
plan: `Plan ${
|
|
9510
|
-
topUps: topUps > 0 ? `Top-ups ${
|
|
9511
|
-
autoRoom: room != null && room > 0 && !capZero ? `Automatic up to ${
|
|
9368
|
+
plan: `Plan ${fmt(plan)}`,
|
|
9369
|
+
topUps: topUps > 0 ? `Top-ups ${fmt(topUps)}${hasLegacy ? ` (${blocksLabel(kind, blocks, addons)})` : ""}` : void 0,
|
|
9370
|
+
autoRoom: room != null && room > 0 && !capZero ? `Automatic up to ${fmt(room)}` : void 0
|
|
9512
9371
|
},
|
|
9513
9372
|
testId: `usage-meter-${slug}`,
|
|
9514
9373
|
details: autoLine || allowanceNext !== allowance || children ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -9596,7 +9455,7 @@ function UsageCapEditor({ project, addons }) {
|
|
|
9596
9455
|
}
|
|
9597
9456
|
);
|
|
9598
9457
|
};
|
|
9599
|
-
const readout = savedCap == null ? `${
|
|
9458
|
+
const readout = savedCap == null ? `${gbp(spent)} this month \xB7 no limit` : savedCap === 0 ? "\xA30 \xB7 no automatic top-ups" : `${gbp(spent)} of ${gbp(savedCap)} used`;
|
|
9600
9459
|
const inputId = `spend-cap-${project.external_id}`;
|
|
9601
9460
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", ref: sectionRef, "data-testid": "project-spend-cap", children: [
|
|
9602
9461
|
/* @__PURE__ */ jsx(
|
|
@@ -9612,7 +9471,7 @@ function UsageCapEditor({ project, addons }) {
|
|
|
9612
9471
|
addons.cap_reached && savedCap != null && savedCap > 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", "data-testid": "addon-cap-reached", children: CAP_REACHED_MESSAGE }),
|
|
9613
9472
|
addons.manual_spent_pence > 0 && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", "data-testid": "addon-manual-spend", children: [
|
|
9614
9473
|
"Added by hand this month: ",
|
|
9615
|
-
|
|
9474
|
+
gbp(addons.manual_spent_pence),
|
|
9616
9475
|
" \xB7 not capped"
|
|
9617
9476
|
] }),
|
|
9618
9477
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-3", children: [
|
|
@@ -9788,7 +9647,7 @@ function formatShortDate2(unix) {
|
|
|
9788
9647
|
if (!unix) return null;
|
|
9789
9648
|
return new Date(unix * 1e3).toLocaleDateString("en-GB", { day: "numeric", month: "short" });
|
|
9790
9649
|
}
|
|
9791
|
-
var
|
|
9650
|
+
var gbp2 = (pence) => formatMoney(pence, "gbp");
|
|
9792
9651
|
var gbNum = (n) => `${Math.round(n * 10) / 10}`;
|
|
9793
9652
|
var pct2 = (used, limit) => limit > 0 ? Math.min(100, used / limit * 100) : 0;
|
|
9794
9653
|
function Meter({
|
|
@@ -9819,7 +9678,7 @@ function usePlanPrice(project) {
|
|
|
9819
9678
|
}
|
|
9820
9679
|
function paidLine(payment, addons) {
|
|
9821
9680
|
const what = payment.kind === "storage" ? `${addons.block_storage_gb} GB added` : payment.kind === "ai_usage" ? `${formatGbp(addons.block_ai_gbp)} of AI usage added` : null;
|
|
9822
|
-
const paid = payment.amount_pence != null ? `Paid ${
|
|
9681
|
+
const paid = payment.amount_pence != null ? `Paid ${gbp2(payment.amount_pence)}` : "Paid";
|
|
9823
9682
|
return what ? `${paid} \xB7 ${what}` : paid;
|
|
9824
9683
|
}
|
|
9825
9684
|
function ProjectPlanCard({
|
|
@@ -9890,11 +9749,11 @@ function ProjectPlanCard({
|
|
|
9890
9749
|
] }),
|
|
9891
9750
|
price && /* @__PURE__ */ jsxs("div", { className: "ml-auto text-right", "data-testid": "project-plan-price", children: [
|
|
9892
9751
|
/* @__PURE__ */ jsxs("div", { className: "text-lg font-semibold tabular-nums text-foreground", children: [
|
|
9893
|
-
price.ex != null ? plusVat(price.ex) :
|
|
9752
|
+
price.ex != null ? plusVat(price.ex) : gbp2(price.incl),
|
|
9894
9753
|
/* @__PURE__ */ jsx("span", { className: "text-sm font-normal text-muted-foreground", children: price.isAnnual ? "/year" : "/month" })
|
|
9895
9754
|
] }),
|
|
9896
9755
|
price.ex != null && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
|
|
9897
|
-
|
|
9756
|
+
gbp2(price.incl),
|
|
9898
9757
|
" incl. VAT"
|
|
9899
9758
|
] })
|
|
9900
9759
|
] })
|
|
@@ -9976,7 +9835,7 @@ function ProjectPlanCard({
|
|
|
9976
9835
|
addons?.billing_error && /* @__PURE__ */ jsx(PlanNotice, { tone: "error", testId: "plan-billing-error", children: addons.billing_error }),
|
|
9977
9836
|
confirmUrl ? /* @__PURE__ */ jsxs(PlanNotice, { testId: "plan-payment-state", children: [
|
|
9978
9837
|
"Waiting for your bank",
|
|
9979
|
-
pendingPayment?.amount_pence ? ` \xB7 ${
|
|
9838
|
+
pendingPayment?.amount_pence ? ` \xB7 ${gbp2(pendingPayment.amount_pence)}` : "",
|
|
9980
9839
|
" \xB7",
|
|
9981
9840
|
" ",
|
|
9982
9841
|
/* @__PURE__ */ jsxs(
|
|
@@ -10646,61 +10505,768 @@ function useAutoRefreshPendingProjects() {
|
|
|
10646
10505
|
}
|
|
10647
10506
|
}, [projects, refreshProject, currentUserExtId]);
|
|
10648
10507
|
}
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10653
|
-
|
|
10654
|
-
|
|
10655
|
-
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
|
|
10508
|
+
function toWorkspaceSummary(workspace, selfId) {
|
|
10509
|
+
const people = (workspace.users ?? []).filter((m) => !isAgentAccount(m.user));
|
|
10510
|
+
const ordered = [
|
|
10511
|
+
...people.filter((m) => m.role === "owner"),
|
|
10512
|
+
...people.filter((m) => m.role !== "owner")
|
|
10513
|
+
];
|
|
10514
|
+
const members = ordered.map((m) => ({
|
|
10515
|
+
name: m.user.family_name ? `${m.user.given_name} ${m.user.family_name}` : m.user.given_name,
|
|
10516
|
+
picture: m.user.picture
|
|
10517
|
+
}));
|
|
10518
|
+
const isMember = people.some((m) => m.user.external_id === selfId);
|
|
10519
|
+
return {
|
|
10520
|
+
id: workspace.external_id,
|
|
10521
|
+
name: workspace.name,
|
|
10522
|
+
description: workspace.description,
|
|
10523
|
+
docCount: workspaceDocumentCount(workspace),
|
|
10524
|
+
chatCount: workspaceConversationCount(workspace),
|
|
10525
|
+
memberCount: members.length,
|
|
10526
|
+
members,
|
|
10527
|
+
// A public workspace's members are hidden from someone who has not joined it.
|
|
10528
|
+
membersHidden: workspace.is_public && !isMember,
|
|
10529
|
+
updatedAt: workspace.updated_at
|
|
10530
|
+
};
|
|
10664
10531
|
}
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
|
|
10669
|
-
|
|
10670
|
-
|
|
10532
|
+
function WorkspaceCardsView({
|
|
10533
|
+
list,
|
|
10534
|
+
currentWorkspaceId,
|
|
10535
|
+
onOpenWorkspace,
|
|
10536
|
+
onOpenProject,
|
|
10537
|
+
onCreateWorkspace,
|
|
10538
|
+
createDisabledReason
|
|
10671
10539
|
}) {
|
|
10672
|
-
const
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10540
|
+
const { stripeEnabled } = useBillingSupport();
|
|
10541
|
+
const workspaceLabel = getEntityLabel("WORKSPACE");
|
|
10542
|
+
const projectLabel = getEntityLabel("PROJECT").toLowerCase();
|
|
10543
|
+
const { groups, projects, selfId } = list;
|
|
10544
|
+
if (groups.length === 0) {
|
|
10545
|
+
return /* @__PURE__ */ jsx(WorkspaceList, { testId: "workspace-cards-list", className: "gap-0", children: /* @__PURE__ */ jsx(
|
|
10546
|
+
CreateTile,
|
|
10547
|
+
{
|
|
10548
|
+
label: `New ${workspaceLabel.toLowerCase()}`,
|
|
10549
|
+
ariaLabel: `Create ${workspaceLabel}`,
|
|
10550
|
+
onSelect: () => onCreateWorkspace(),
|
|
10551
|
+
disabledReason: createDisabledReason,
|
|
10552
|
+
help: `A ${workspaceLabel.toLowerCase()} holds the documents and conversations for one matter or case.`,
|
|
10553
|
+
testId: "create-workspace-card"
|
|
10554
|
+
}
|
|
10555
|
+
) });
|
|
10556
|
+
}
|
|
10557
|
+
return /* @__PURE__ */ jsx(WorkspaceList, { testId: "workspace-cards-list", children: groups.map((group) => {
|
|
10558
|
+
const project = projects.find((p) => p.external_id === group.projectExtId);
|
|
10559
|
+
const { breakdown, readout } = projectGroupBilling(project, {
|
|
10560
|
+
isOwned: group.isOwned,
|
|
10561
|
+
stripeEnabled
|
|
10562
|
+
});
|
|
10563
|
+
const count = group.workspaces.length;
|
|
10564
|
+
return /* @__PURE__ */ jsxs(
|
|
10565
|
+
WorkspaceGroup,
|
|
10566
|
+
{
|
|
10567
|
+
title: group.projectExtId ? /* @__PURE__ */ jsx(
|
|
10568
|
+
ProjectBandTitle,
|
|
10569
|
+
{
|
|
10570
|
+
name: group.title,
|
|
10571
|
+
count,
|
|
10572
|
+
nameTestId: `project-name-${group.projectExtId}`,
|
|
10573
|
+
breakdown,
|
|
10574
|
+
readout
|
|
10575
|
+
}
|
|
10576
|
+
) : /* @__PURE__ */ jsx(
|
|
10577
|
+
PlainBandTitle,
|
|
10578
|
+
{
|
|
10579
|
+
title: group.title,
|
|
10580
|
+
count,
|
|
10581
|
+
hint: group.isCommon ? "Shared with everyone" : void 0
|
|
10582
|
+
}
|
|
10583
|
+
),
|
|
10584
|
+
onOpen: group.projectExtId ? () => onOpenProject(group.projectExtId) : void 0,
|
|
10585
|
+
openLabel: "Plans & Usage",
|
|
10586
|
+
collapsedHint: group.isCommon ? `Show ${count} shared ${getEntityLabel("WORKSPACE", "PLURAL").toLowerCase()}` : void 0,
|
|
10587
|
+
isExpanded: !list.isCollapsed(group.key),
|
|
10588
|
+
onToggle: () => list.toggleGroup(group.key),
|
|
10589
|
+
children: [
|
|
10590
|
+
group.isOwned && group.projectExtId && /* @__PURE__ */ jsx(
|
|
10591
|
+
CreateTile,
|
|
10683
10592
|
{
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10593
|
+
label: `New ${workspaceLabel.toLowerCase()}`,
|
|
10594
|
+
ariaLabel: `Create ${workspaceLabel}`,
|
|
10595
|
+
onSelect: () => onCreateWorkspace(group.projectExtId),
|
|
10596
|
+
disabledReason: createDisabledReason,
|
|
10597
|
+
help: count === 0 ? `Already have one? Open it and change its ${projectLabel} to move it here.` : void 0,
|
|
10598
|
+
testId: "add-workspace-button"
|
|
10690
10599
|
}
|
|
10691
10600
|
),
|
|
10692
|
-
/* @__PURE__ */ jsx(
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10601
|
+
[...group.workspaces].sort(byRecentUpdate).map((workspace) => /* @__PURE__ */ jsx(
|
|
10602
|
+
WorkspaceCard,
|
|
10603
|
+
{
|
|
10604
|
+
workspace: toWorkspaceSummary(workspace, selfId),
|
|
10605
|
+
current: currentWorkspaceId === workspace.external_id,
|
|
10606
|
+
onSelect: () => onOpenWorkspace(workspace),
|
|
10607
|
+
href: buildDeepLinkPath({ workspaceId: workspace.external_id }),
|
|
10608
|
+
mark: /* @__PURE__ */ jsx(
|
|
10609
|
+
ShieldIcon,
|
|
10610
|
+
{
|
|
10611
|
+
char: workspace.name.charAt(0),
|
|
10612
|
+
workspaceColor: workspaceKindColor(getWorkspaceKind(workspace)),
|
|
10613
|
+
size: 28,
|
|
10614
|
+
isSelected: true
|
|
10615
|
+
}
|
|
10616
|
+
),
|
|
10617
|
+
testId: `workspace-card-${workspace.external_id}`,
|
|
10618
|
+
openCueTestId: `workspace-open-${workspace.external_id}`
|
|
10619
|
+
},
|
|
10620
|
+
workspace.external_id
|
|
10621
|
+
))
|
|
10622
|
+
]
|
|
10623
|
+
},
|
|
10624
|
+
group.key
|
|
10625
|
+
);
|
|
10626
|
+
}) });
|
|
10627
|
+
}
|
|
10628
|
+
function WorkspaceFilterMenu({ list }) {
|
|
10629
|
+
const [open, setOpen] = useState(false);
|
|
10630
|
+
const { workspaces, projects, selfId, pinned, filters, setFilters } = list;
|
|
10631
|
+
const { options: options2, groups } = useMemo(() => {
|
|
10632
|
+
const attributes = workspaces.map((w) => workspaceFilterAttributes(w, { selfId, pinned }));
|
|
10633
|
+
const countOf = (id) => attributes.filter((a) => a[id]).length;
|
|
10634
|
+
const projectIds = projects.map((p) => projectFilterId(p.external_id));
|
|
10635
|
+
const options3 = [
|
|
10636
|
+
...projects.map((p) => ({
|
|
10637
|
+
id: projectFilterId(p.external_id),
|
|
10638
|
+
label: p.name,
|
|
10639
|
+
count: countOf(projectFilterId(p.external_id))
|
|
10640
|
+
})),
|
|
10641
|
+
...WORKSPACE_CATEGORY_FILTERS.map(({ id, label }) => ({ id, label, count: countOf(id) }))
|
|
10642
|
+
];
|
|
10643
|
+
const groups2 = [
|
|
10644
|
+
{ label: "Projects", options: projectIds },
|
|
10645
|
+
{ label: "Workspaces", options: WORKSPACE_CATEGORY_FILTERS.map((f) => f.id) }
|
|
10646
|
+
];
|
|
10647
|
+
return { options: options3, groups: groups2 };
|
|
10648
|
+
}, [workspaces, projects, selfId, pinned]);
|
|
10649
|
+
return /* @__PURE__ */ jsx(
|
|
10650
|
+
TriStateFilterMenu,
|
|
10651
|
+
{
|
|
10652
|
+
testId: "workspace-filter-button",
|
|
10653
|
+
open,
|
|
10654
|
+
onOpenChange: setOpen,
|
|
10655
|
+
title: "Filter workspaces",
|
|
10656
|
+
triggerLabel: "Filter",
|
|
10657
|
+
renderChevron: true,
|
|
10658
|
+
filterStates: filters,
|
|
10659
|
+
onFiltersChange: setFilters,
|
|
10660
|
+
options: options2,
|
|
10661
|
+
groups
|
|
10662
|
+
}
|
|
10663
|
+
);
|
|
10664
|
+
}
|
|
10665
|
+
var DEFAULT_WORKSPACE_LIST_LABELS = {
|
|
10666
|
+
recentlyDeletedHint: (count) => `You can restore ${count === 1 ? "it" : "them"} from Recently deleted (Settings > Storage) for at least 30 days.`
|
|
10667
|
+
};
|
|
10668
|
+
function useConnectedWorkspaceList({
|
|
10669
|
+
filters: controlledFilters,
|
|
10670
|
+
onFiltersChange,
|
|
10671
|
+
labels
|
|
10672
|
+
} = {}) {
|
|
10673
|
+
const recentlyDeletedHint = labels?.recentlyDeletedHint ?? DEFAULT_WORKSPACE_LIST_LABELS.recentlyDeletedHint;
|
|
10674
|
+
const { confirm, notify: notify14 } = useBlocksEnvironment();
|
|
10675
|
+
const { userId: selfId } = useConnectedWorkspace();
|
|
10676
|
+
const workspacesQuery = useWorkspaces();
|
|
10677
|
+
const { data: projects } = useProjects();
|
|
10678
|
+
const { data: settings } = useUserSettings();
|
|
10679
|
+
const canCreateProjects = useCanCreateProjects();
|
|
10680
|
+
const deleteWorkspaces = useDeleteWorkspaces();
|
|
10681
|
+
const workspaces = useMemo(
|
|
10682
|
+
() => (workspacesQuery.data ?? []).filter(isListedWorkspace),
|
|
10683
|
+
[workspacesQuery.data]
|
|
10684
|
+
);
|
|
10685
|
+
const pinned = useMemo(
|
|
10686
|
+
() => new Set(settings?.pinned_workspaces ?? []),
|
|
10687
|
+
[settings?.pinned_workspaces]
|
|
10688
|
+
);
|
|
10689
|
+
const [localFilters, setLocalFilters] = useState({});
|
|
10690
|
+
const filters = controlledFilters ?? localFilters;
|
|
10691
|
+
const filterMap = useMemo(() => {
|
|
10692
|
+
const known = new Set(WORKSPACE_CATEGORY_FILTERS.map((f) => f.id));
|
|
10693
|
+
for (const p of projects ?? []) known.add(projectFilterId(p.external_id));
|
|
10694
|
+
for (const w of workspaces) if (w.project_ext_id) known.add(projectFilterId(w.project_ext_id));
|
|
10695
|
+
return new Map(Object.entries(filters).filter(([id, state]) => state !== null && known.has(id)));
|
|
10696
|
+
}, [filters, projects, workspaces]);
|
|
10697
|
+
const setFilters = useCallback(
|
|
10698
|
+
(next) => {
|
|
10699
|
+
const record = Object.fromEntries(next);
|
|
10700
|
+
if (onFiltersChange) onFiltersChange(record);
|
|
10701
|
+
if (controlledFilters === void 0) setLocalFilters(record);
|
|
10702
|
+
},
|
|
10703
|
+
[controlledFilters, onFiltersChange]
|
|
10704
|
+
);
|
|
10705
|
+
const attributesOf = useCallback(
|
|
10706
|
+
(workspace) => workspaceFilterAttributes(workspace, { selfId, pinned }),
|
|
10707
|
+
[selfId, pinned]
|
|
10708
|
+
);
|
|
10709
|
+
const filtered = useMemo(
|
|
10710
|
+
() => applyTriStateFilters(workspaces, filterMap, attributesOf),
|
|
10711
|
+
[workspaces, filterMap, attributesOf]
|
|
10712
|
+
);
|
|
10713
|
+
const groups = useMemo(
|
|
10714
|
+
() => groupWorkspacesByProject(filtered, {
|
|
10715
|
+
selfId,
|
|
10716
|
+
pinned,
|
|
10717
|
+
projects: projects ?? [],
|
|
10718
|
+
workspacesLabel: getEntityLabel("WORKSPACE", "PLURAL")
|
|
10719
|
+
}),
|
|
10720
|
+
[filtered, selfId, pinned, projects]
|
|
10721
|
+
);
|
|
10722
|
+
const [toggled, setToggled] = useState({});
|
|
10723
|
+
const isCollapsed = useCallback(
|
|
10724
|
+
(key) => toggled[key] ?? groups.some((g) => g.key === key && g.isCommon),
|
|
10725
|
+
[toggled, groups]
|
|
10726
|
+
);
|
|
10727
|
+
const toggleGroup = useCallback(
|
|
10728
|
+
(key) => setToggled((prev) => ({ ...prev, [key]: !isCollapsed(key) })),
|
|
10729
|
+
[isCollapsed]
|
|
10730
|
+
);
|
|
10731
|
+
const [selected, setSelected] = useState([]);
|
|
10732
|
+
const canDeleteSelected = selected.every((w) => !!selfId && w.created_by_ext_id === selfId);
|
|
10733
|
+
const deleteSelected = useCallback(async () => {
|
|
10734
|
+
const count = selected.length;
|
|
10735
|
+
if (count === 0) return;
|
|
10736
|
+
const noun = getEntityLabel("WORKSPACE", count === 1 ? "SINGULAR" : "PLURAL");
|
|
10737
|
+
const confirmed = await confirm({
|
|
10738
|
+
title: `Delete ${count} ${noun}`,
|
|
10739
|
+
message: `Delete ${count} ${noun.toLowerCase()}? ${recentlyDeletedHint(count)}`
|
|
10740
|
+
});
|
|
10741
|
+
if (!confirmed) return;
|
|
10742
|
+
try {
|
|
10743
|
+
await deleteWorkspaces.mutateAsync(selected.map((w) => w.external_id));
|
|
10744
|
+
setSelected([]);
|
|
10745
|
+
notify14(`Deleted ${count} ${noun.toLowerCase()}`, "success");
|
|
10746
|
+
} catch {
|
|
10747
|
+
notify14("Failed to delete some workspaces", "error");
|
|
10748
|
+
}
|
|
10749
|
+
}, [selected, confirm, notify14, deleteWorkspaces, recentlyDeletedHint]);
|
|
10750
|
+
return {
|
|
10751
|
+
selfId,
|
|
10752
|
+
/** Every listed workspace (unfiltered): the filter menu's counts. */
|
|
10753
|
+
workspaces,
|
|
10754
|
+
projects: projects ?? [],
|
|
10755
|
+
pinned,
|
|
10756
|
+
/** The filtered workspaces grouped by project, in list order. */
|
|
10757
|
+
groups,
|
|
10758
|
+
/** How many workspaces the filters let through. */
|
|
10759
|
+
shownCount: filtered.length,
|
|
10760
|
+
isLoading: workspacesQuery.isLoading,
|
|
10761
|
+
error: workspacesQuery.error,
|
|
10762
|
+
canCreateProjects,
|
|
10763
|
+
filters: filterMap,
|
|
10764
|
+
setFilters,
|
|
10765
|
+
isCollapsed,
|
|
10766
|
+
toggleGroup,
|
|
10767
|
+
selected,
|
|
10768
|
+
setSelected,
|
|
10769
|
+
canDeleteSelected,
|
|
10770
|
+
deleteSelected,
|
|
10771
|
+
isDeleting: deleteWorkspaces.isPending
|
|
10772
|
+
};
|
|
10773
|
+
}
|
|
10774
|
+
var WorkspaceTableView = lazy(
|
|
10775
|
+
() => import('../WorkspaceTableView-5OWX53R5.js').then((m) => ({ default: m.WorkspaceTableView }))
|
|
10776
|
+
);
|
|
10777
|
+
var WORKSPACES_TAB_ID = "workspaces";
|
|
10778
|
+
var defaultRenderMember2 = (user, size) => {
|
|
10779
|
+
const name = user.family_name ? `${user.given_name} ${user.family_name}` : user.given_name;
|
|
10780
|
+
return /* @__PURE__ */ jsx(
|
|
10781
|
+
UserEntity,
|
|
10782
|
+
{
|
|
10783
|
+
size,
|
|
10784
|
+
variant: "icon",
|
|
10785
|
+
showTooltip: false,
|
|
10786
|
+
interactive: false,
|
|
10787
|
+
displayName: name,
|
|
10788
|
+
initials: initials(name || user.email),
|
|
10789
|
+
picture: user.picture
|
|
10790
|
+
}
|
|
10791
|
+
);
|
|
10792
|
+
};
|
|
10793
|
+
function ConnectedWorkspaceList({
|
|
10794
|
+
onCreateWorkspace,
|
|
10795
|
+
view: controlledView,
|
|
10796
|
+
onViewChange,
|
|
10797
|
+
filters,
|
|
10798
|
+
onFiltersChange,
|
|
10799
|
+
gridState,
|
|
10800
|
+
onGridStateChange,
|
|
10801
|
+
renderMember = defaultRenderMember2,
|
|
10802
|
+
extraTabs = [],
|
|
10803
|
+
activeTab = WORKSPACES_TAB_ID,
|
|
10804
|
+
onTabChange,
|
|
10805
|
+
children,
|
|
10806
|
+
labels
|
|
10807
|
+
}) {
|
|
10808
|
+
const list = useConnectedWorkspaceList({ filters, onFiltersChange, labels });
|
|
10809
|
+
const { openProject } = useBlocksEnvironment();
|
|
10810
|
+
const { workspaceId: currentWorkspaceId, openWorkspace } = useConnectedWorkspace();
|
|
10811
|
+
const [localView, setLocalView] = useState("cards");
|
|
10812
|
+
const view = controlledView ?? localView;
|
|
10813
|
+
const isPhone = useIsPhone();
|
|
10814
|
+
const shownView = view === "table" && !isPhone ? "table" : "cards";
|
|
10815
|
+
const showList = activeTab === WORKSPACES_TAB_ID || !extraTabs.some((t) => t.id === activeTab);
|
|
10816
|
+
const setView = (next) => {
|
|
10817
|
+
onViewChange?.(next);
|
|
10818
|
+
if (controlledView === void 0) setLocalView(next);
|
|
10819
|
+
};
|
|
10820
|
+
const open = (workspace) => void openWorkspace(workspace.external_id, { name: workspace.name });
|
|
10821
|
+
const createDisabledReason = list.canCreateProjects === false ? PROJECT_CREATION_DENIED_REASON : void 0;
|
|
10822
|
+
if (list.isLoading) return /* @__PURE__ */ jsx(LoadingIndicator, { message: "Loading workspaces..." });
|
|
10823
|
+
if (list.error) {
|
|
10824
|
+
return /* @__PURE__ */ jsx(
|
|
10825
|
+
HomeLoadError,
|
|
10826
|
+
{
|
|
10827
|
+
title: "Unable to load workspaces",
|
|
10828
|
+
body: "Failed to connect to the server. Please check your connection and try again."
|
|
10829
|
+
}
|
|
10830
|
+
);
|
|
10831
|
+
}
|
|
10832
|
+
const newButton = createDisabledReason ? (
|
|
10833
|
+
// A disabled button emits no pointer events: the span carries the tooltip.
|
|
10834
|
+
/* @__PURE__ */ jsxs(Tooltip, { delayDuration: 50, children: [
|
|
10835
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("span", { tabIndex: 0, "data-testid": "home-new-workspace-disabled", children: /* @__PURE__ */ jsxs(Button, { disabled: true, "data-testid": "home-new-workspace", children: [
|
|
10836
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-0.5 size-4" }),
|
|
10837
|
+
"New"
|
|
10838
|
+
] }) }) }),
|
|
10839
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: createDisabledReason })
|
|
10840
|
+
] })
|
|
10841
|
+
) : /* @__PURE__ */ jsxs(Button, { onClick: () => onCreateWorkspace(), "data-testid": "home-new-workspace", children: [
|
|
10842
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-0.5 size-4" }),
|
|
10843
|
+
"New"
|
|
10844
|
+
] });
|
|
10845
|
+
return /* @__PURE__ */ jsxs(HomeFrame, { children: [
|
|
10846
|
+
/* @__PURE__ */ jsx(
|
|
10847
|
+
TabHeader,
|
|
10848
|
+
{
|
|
10849
|
+
testId: "home-header",
|
|
10850
|
+
tabs: [
|
|
10851
|
+
{
|
|
10852
|
+
id: WORKSPACES_TAB_ID,
|
|
10853
|
+
label: getEntityLabel("WORKSPACE", "PLURAL"),
|
|
10854
|
+
count: list.shownCount,
|
|
10855
|
+
icon: /* @__PURE__ */ jsx(SquareStack, { className: "size-4" })
|
|
10856
|
+
},
|
|
10857
|
+
...extraTabs
|
|
10858
|
+
],
|
|
10859
|
+
activeTabId: showList ? WORKSPACES_TAB_ID : activeTab,
|
|
10860
|
+
onTabChange: (id) => onTabChange?.(id),
|
|
10861
|
+
centerContent: showList && !isPhone ? /* @__PURE__ */ jsx(
|
|
10862
|
+
ViewToggle,
|
|
10863
|
+
{
|
|
10864
|
+
options: [
|
|
10865
|
+
{ value: "cards", icon: /* @__PURE__ */ jsx(SquareStack, { className: "size-4" }), label: "Cards" },
|
|
10866
|
+
{ value: "grid", icon: /* @__PURE__ */ jsx(Table, { className: "size-4" }), label: "Table" }
|
|
10867
|
+
],
|
|
10868
|
+
value: shownView === "table" ? "grid" : "cards",
|
|
10869
|
+
onChange: (value) => setView(value === "grid" ? "table" : "cards"),
|
|
10870
|
+
testIdPrefix: "workspaces",
|
|
10871
|
+
size: "sm",
|
|
10872
|
+
showLabels: true,
|
|
10873
|
+
hideTooltips: true
|
|
10874
|
+
}
|
|
10875
|
+
) : void 0,
|
|
10876
|
+
rightContent: showList ? newButton : void 0
|
|
10877
|
+
}
|
|
10878
|
+
),
|
|
10879
|
+
showList && // Above whichever view shows: both draw the same filtered groups.
|
|
10880
|
+
/* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-6xl px-4 pb-3 sm:px-8", children: /* @__PURE__ */ jsx(WorkspaceFilterMenu, { list }) }),
|
|
10881
|
+
/* @__PURE__ */ jsx(HomeMain, { children: !showList ? children : shownView === "cards" ? (
|
|
10882
|
+
// Full-width scroller (its scrollbar rides the window edge) around a centred column.
|
|
10883
|
+
/* @__PURE__ */ jsx(HomeCenteredScroll, { children: /* @__PURE__ */ jsx(
|
|
10884
|
+
WorkspaceCardsView,
|
|
10885
|
+
{
|
|
10886
|
+
list,
|
|
10887
|
+
currentWorkspaceId,
|
|
10888
|
+
onOpenWorkspace: open,
|
|
10889
|
+
onOpenProject: openProject,
|
|
10890
|
+
onCreateWorkspace,
|
|
10891
|
+
createDisabledReason
|
|
10892
|
+
}
|
|
10893
|
+
) })
|
|
10894
|
+
) : /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(LoadingIndicator, { message: "Loading grid..." }), children: /* @__PURE__ */ jsx(HomeCenteredColumn, { children: /* @__PURE__ */ jsx(
|
|
10895
|
+
WorkspaceTableView,
|
|
10896
|
+
{
|
|
10897
|
+
list,
|
|
10898
|
+
onOpenWorkspace: open,
|
|
10899
|
+
onOpenProject: openProject,
|
|
10900
|
+
renderMember,
|
|
10901
|
+
gridState,
|
|
10902
|
+
onGridStateChange
|
|
10903
|
+
}
|
|
10904
|
+
) }) }) })
|
|
10905
|
+
] });
|
|
10906
|
+
}
|
|
10907
|
+
var PROJECTS_BASE_TAB_ID = "projects";
|
|
10908
|
+
var isPaid = (p) => p.subscription === "active" || p.subscription === "past_due";
|
|
10909
|
+
function useLocalProjectTabs() {
|
|
10910
|
+
const [openProjectIds, setOpen] = useState([]);
|
|
10911
|
+
const [activeProjectId, setActive] = useState(null);
|
|
10912
|
+
return {
|
|
10913
|
+
openProjectIds,
|
|
10914
|
+
activeProjectId,
|
|
10915
|
+
onSelect: (id) => {
|
|
10916
|
+
if (id) setOpen((open) => open.includes(id) ? open : [...open, id]);
|
|
10917
|
+
setActive(id);
|
|
10918
|
+
},
|
|
10919
|
+
onClose: (id) => {
|
|
10920
|
+
setOpen((open) => open.filter((openId) => openId !== id));
|
|
10921
|
+
setActive((active) => active === id ? null : active);
|
|
10922
|
+
}
|
|
10923
|
+
};
|
|
10924
|
+
}
|
|
10925
|
+
function ConnectedProjectsPanel({
|
|
10926
|
+
tabs: hostTabs,
|
|
10927
|
+
projectStorage,
|
|
10928
|
+
footer,
|
|
10929
|
+
headerRight,
|
|
10930
|
+
className
|
|
10931
|
+
}) {
|
|
10932
|
+
const localTabs = useLocalProjectTabs();
|
|
10933
|
+
const tabs = hostTabs ?? localTabs;
|
|
10934
|
+
const { data: projects } = useProjects();
|
|
10935
|
+
const canCreateProjects = useCanCreateProjects();
|
|
10936
|
+
const { userId } = useConnectedWorkspace();
|
|
10937
|
+
const { stripeEnabled } = useBillingSupport();
|
|
10938
|
+
const billingPortal = useOpenBillingPortal();
|
|
10939
|
+
const [newProjectOpen, setNewProjectOpen] = useState(false);
|
|
10940
|
+
useAutoRefreshPendingProjects();
|
|
10941
|
+
const ownedCount = (projects ?? []).filter((p) => p.created_by === userId).length;
|
|
10942
|
+
const activeProjectId = tabs.activeProjectId && tabs.openProjectIds.includes(tabs.activeProjectId) ? tabs.activeProjectId : null;
|
|
10943
|
+
const headerTabs = useMemo(
|
|
10944
|
+
() => [
|
|
10945
|
+
{
|
|
10946
|
+
id: PROJECTS_BASE_TAB_ID,
|
|
10947
|
+
// Named by its job; "project" is for the per-project tabs, where owning several matters.
|
|
10948
|
+
label: "Plan & Usage",
|
|
10949
|
+
icon: /* @__PURE__ */ jsx(EntityIcon, { entityType: "PROJECT" }),
|
|
10950
|
+
// Most users own one project: a count only once there are several.
|
|
10951
|
+
count: ownedCount > 1 ? ownedCount : void 0
|
|
10952
|
+
},
|
|
10953
|
+
...tabs.openProjectIds.map((id) => ({
|
|
10954
|
+
id,
|
|
10955
|
+
label: projects?.find((p) => p.external_id === id)?.name ?? "Project",
|
|
10956
|
+
icon: /* @__PURE__ */ jsx(EntityIcon, { entityType: "PROJECT" }),
|
|
10957
|
+
onClose: () => tabs.onClose(id),
|
|
10958
|
+
closeTooltip: "Close project"
|
|
10959
|
+
}))
|
|
10960
|
+
],
|
|
10961
|
+
[ownedCount, tabs, projects]
|
|
10962
|
+
);
|
|
10963
|
+
const paidProject = projects?.find(isPaid);
|
|
10964
|
+
const panel = /* @__PURE__ */ jsxs(PanelFrame, { className, testId: "projects-panel", children: [
|
|
10965
|
+
/* @__PURE__ */ jsx(DraggableHeader, { children: /* @__PURE__ */ jsx(
|
|
10966
|
+
TabHeader,
|
|
10967
|
+
{
|
|
10968
|
+
tabs: headerTabs,
|
|
10969
|
+
activeTabId: activeProjectId ?? PROJECTS_BASE_TAB_ID,
|
|
10970
|
+
onTabChange: (id) => tabs.onSelect(id === PROJECTS_BASE_TAB_ID ? null : id),
|
|
10971
|
+
afterTabs: (isCompact) => /* @__PURE__ */ jsx(
|
|
10972
|
+
PanelAddButton,
|
|
10973
|
+
{
|
|
10974
|
+
label: "New project",
|
|
10975
|
+
onClick: () => setNewProjectOpen(true),
|
|
10976
|
+
icon: /* @__PURE__ */ jsx(Plus, { className: "size-4" }),
|
|
10977
|
+
isCompact,
|
|
10978
|
+
testId: "new-project-button",
|
|
10979
|
+
disabledReason: canCreateProjects === false ? PROJECT_CREATION_DENIED_REASON : void 0
|
|
10980
|
+
}
|
|
10981
|
+
),
|
|
10982
|
+
rightContent: headerRight,
|
|
10983
|
+
variant: "underline",
|
|
10984
|
+
compactBreakpoint: 360
|
|
10985
|
+
}
|
|
10986
|
+
) }),
|
|
10987
|
+
/* @__PURE__ */ jsx(PanelFrameBody, { children: activeProjectId ? /* @__PURE__ */ jsx(PanelScroll, { children: /* @__PURE__ */ jsx(
|
|
10988
|
+
ProjectDetail,
|
|
10989
|
+
{
|
|
10990
|
+
projectExtId: activeProjectId,
|
|
10991
|
+
storage: projectStorage?.(activeProjectId)
|
|
10992
|
+
}
|
|
10993
|
+
) }) : /* @__PURE__ */ jsxs(PanelScroll, { variant: "padded", children: [
|
|
10994
|
+
/* @__PURE__ */ jsx(ProjectsTable, {}),
|
|
10995
|
+
stripeEnabled && paidProject && /* @__PURE__ */ jsx(PanelActionsRow, { children: /* @__PURE__ */ jsxs(
|
|
10996
|
+
Button,
|
|
10997
|
+
{
|
|
10998
|
+
onClick: () => billingPortal.open(paidProject.external_id),
|
|
10999
|
+
size: "sm",
|
|
11000
|
+
variant: "outline",
|
|
11001
|
+
disabled: billingPortal.isPending,
|
|
11002
|
+
className: "flex items-center gap-1",
|
|
11003
|
+
children: [
|
|
11004
|
+
billingPortal.isPending ? "Opening\u2026" : "Billing portal",
|
|
11005
|
+
!billingPortal.isPending && /* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5" })
|
|
11006
|
+
]
|
|
11007
|
+
}
|
|
11008
|
+
) }),
|
|
11009
|
+
/* @__PURE__ */ jsx(SharedProjects, {}),
|
|
11010
|
+
footer
|
|
11011
|
+
] }) }),
|
|
11012
|
+
/* @__PURE__ */ jsx(
|
|
11013
|
+
NewProjectFlow,
|
|
11014
|
+
{
|
|
11015
|
+
open: newProjectOpen,
|
|
11016
|
+
onOpenChange: setNewProjectOpen,
|
|
11017
|
+
defaultName: `Project ${(projects?.length ?? 0) + 1}`
|
|
11018
|
+
}
|
|
11019
|
+
)
|
|
11020
|
+
] });
|
|
11021
|
+
return hostTabs ? panel : /* @__PURE__ */ jsx(BlocksProvider, { openProject: localTabs.onSelect, children: panel });
|
|
11022
|
+
}
|
|
11023
|
+
|
|
11024
|
+
// src/connection/workspace-dashboard/dashboard-rows.ts
|
|
11025
|
+
var RECENT_ROWS = 5;
|
|
11026
|
+
var FACT_ROWS = 3;
|
|
11027
|
+
function shortDate(iso) {
|
|
11028
|
+
return new Date(iso).toLocaleDateString(void 0, { day: "numeric", month: "short" });
|
|
11029
|
+
}
|
|
11030
|
+
function timeAndDate(iso) {
|
|
11031
|
+
if (!iso) return "";
|
|
11032
|
+
const d = new Date(iso);
|
|
11033
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
11034
|
+
const time2 = d.toLocaleTimeString(void 0, {
|
|
11035
|
+
hour: "2-digit",
|
|
11036
|
+
minute: "2-digit",
|
|
11037
|
+
hour12: false
|
|
11038
|
+
});
|
|
11039
|
+
return `${time2} \xB7 ${shortDate(iso)}`;
|
|
11040
|
+
}
|
|
11041
|
+
function humanizeStatus(status) {
|
|
11042
|
+
const words = status.replace(/[-_]/g, " ");
|
|
11043
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
11044
|
+
}
|
|
11045
|
+
var time = (iso) => +new Date(iso ?? 0);
|
|
11046
|
+
function recentConversations(conversations) {
|
|
11047
|
+
return [...conversations].sort((a, b) => time(b.updated_at) - time(a.updated_at)).slice(0, RECENT_ROWS).map((c) => ({
|
|
11048
|
+
id: c.external_id,
|
|
11049
|
+
title: c.title?.trim() || "Untitled thread",
|
|
11050
|
+
subtitle: c.status ? [STATUS_LABELS[asTaskStatus(c.status)], c.due_at ? `due ${shortDate(c.due_at)}` : null].filter(Boolean).join(" \xB7 ") : `${c.message_count} ${c.message_count === 1 ? "message" : "messages"}`,
|
|
11051
|
+
time: timeAndDate(c.updated_at)
|
|
11052
|
+
}));
|
|
11053
|
+
}
|
|
11054
|
+
function recentDocuments(documents) {
|
|
11055
|
+
return [...documents].sort((a, b) => time(b.updated_at ?? b.created_at) - time(a.updated_at ?? a.created_at)).slice(0, RECENT_ROWS).map((d) => ({
|
|
11056
|
+
id: d.external_id,
|
|
11057
|
+
fileName: d.file_name,
|
|
11058
|
+
title: d.file_name || "Untitled document",
|
|
11059
|
+
subtitle: d.status && d.status !== "ready" && d.status !== "completed" ? humanizeStatus(d.status) : d.n_pages ? `${d.n_pages} ${d.n_pages === 1 ? "page" : "pages"}` : d.file_type ?? "Document",
|
|
11060
|
+
time: timeAndDate(d.updated_at ?? d.created_at)
|
|
11061
|
+
}));
|
|
11062
|
+
}
|
|
11063
|
+
function splitFacts(facts, now = /* @__PURE__ */ new Date()) {
|
|
11064
|
+
const startOfToday = new Date(now);
|
|
11065
|
+
startOfToday.setHours(0, 0, 0, 0);
|
|
11066
|
+
const dated = facts.filter((f) => f.date_start && new Date(f.date_start) >= startOfToday).sort((a, b) => time(a.date_start) - time(b.date_start)).slice(0, FACT_ROWS);
|
|
11067
|
+
const datedIds = new Set(dated.map((f) => f.external_id));
|
|
11068
|
+
const rest = facts.filter((f) => !datedIds.has(f.external_id)).sort((a, b) => time(b.created_at) - time(a.created_at)).slice(0, FACT_ROWS);
|
|
11069
|
+
const row = (f, stamp) => ({
|
|
11070
|
+
id: f.external_id,
|
|
11071
|
+
title: f.text,
|
|
11072
|
+
time: stamp
|
|
11073
|
+
});
|
|
11074
|
+
return {
|
|
11075
|
+
upcoming: dated.map((f) => row(f, f.date_start ? shortDate(f.date_start) : "")),
|
|
11076
|
+
recent: rest.map((f) => row(f, timeAndDate(f.created_at)))
|
|
11077
|
+
};
|
|
11078
|
+
}
|
|
11079
|
+
function ConnectedWorkspaceDashboard({
|
|
11080
|
+
actions,
|
|
11081
|
+
start,
|
|
11082
|
+
addFilesAction,
|
|
11083
|
+
aside,
|
|
11084
|
+
testId = "workspace-dashboard"
|
|
11085
|
+
}) {
|
|
11086
|
+
const { openDocument, openConversation } = useBlocksEnvironment();
|
|
11087
|
+
const { workspaceId } = useConnectedWorkspace();
|
|
11088
|
+
const { data: documents } = useConnectedDocuments();
|
|
11089
|
+
const { data: conversations } = useConnectedConversations();
|
|
11090
|
+
const { data: facts = [] } = useFacts(workspaceId);
|
|
11091
|
+
const conversationIcon = /* @__PURE__ */ jsx(EntityIcon, { entityType: "CONVERSATION", className: "mt-0.5 size-5 shrink-0" });
|
|
11092
|
+
const chatRows = useMemo(
|
|
11093
|
+
() => recentConversations(conversations).map((row) => ({
|
|
11094
|
+
...row,
|
|
11095
|
+
leading: conversationIcon,
|
|
11096
|
+
href: workspaceId ? buildDeepLinkPath({ workspaceId, conversationId: row.id }) : void 0,
|
|
11097
|
+
onSelect: () => openConversation(row.id)
|
|
11098
|
+
})),
|
|
11099
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
11100
|
+
[conversations, workspaceId, openConversation]
|
|
11101
|
+
);
|
|
11102
|
+
const docRows = useMemo(
|
|
11103
|
+
() => recentDocuments(documents).map(({ fileName, ...row }) => ({
|
|
11104
|
+
...row,
|
|
11105
|
+
// The file-type mark, bare: it carries its own identity.
|
|
11106
|
+
leading: /* @__PURE__ */ jsx("span", { className: "mt-0.5 shrink-0", children: getDocIcon(fileName, 20) }),
|
|
11107
|
+
href: workspaceId ? buildDeepLinkPath({ workspaceId, documentId: row.id }) : void 0,
|
|
11108
|
+
onSelect: () => openDocument(row.id)
|
|
11109
|
+
})),
|
|
11110
|
+
[documents, workspaceId, openDocument]
|
|
11111
|
+
);
|
|
11112
|
+
const { upcoming, recent } = useMemo(() => {
|
|
11113
|
+
const { upcoming: upcoming2, recent: recent2 } = splitFacts(facts);
|
|
11114
|
+
const toRow = (row) => ({
|
|
11115
|
+
...row,
|
|
11116
|
+
leading: /* @__PURE__ */ jsx(FactMark, {}),
|
|
11117
|
+
// The item, not the list: the Facts page scrolls to this fact.
|
|
11118
|
+
onSelect: () => actions.openFact(row.id)
|
|
11119
|
+
});
|
|
11120
|
+
return { upcoming: upcoming2.map(toRow), recent: recent2.map(toRow) };
|
|
11121
|
+
}, [facts, actions]);
|
|
11122
|
+
return /* @__PURE__ */ jsxs(DashboardRoot, { testId, children: [
|
|
11123
|
+
start && /* @__PURE__ */ jsx(DashboardBand, { kind: "start", children: start }),
|
|
11124
|
+
/* @__PURE__ */ jsxs(DashboardBand, { kind: "recent", children: [
|
|
11125
|
+
/* @__PURE__ */ jsx(SectionLabel, { testId: `${testId}-recent-caption`, children: "Recent" }),
|
|
11126
|
+
/* @__PURE__ */ jsxs(DashboardColumns, { children: [
|
|
11127
|
+
/* @__PURE__ */ jsx(
|
|
11128
|
+
RecentList,
|
|
11129
|
+
{
|
|
11130
|
+
testId: `${testId}-docs`,
|
|
11131
|
+
icon: FileText,
|
|
11132
|
+
title: getEntityLabel("DOCUMENT", "PLURAL"),
|
|
11133
|
+
count: documents.length,
|
|
11134
|
+
onOpen: () => actions.openSection("documents"),
|
|
11135
|
+
action: addFilesAction,
|
|
11136
|
+
rows: docRows,
|
|
11137
|
+
caption: "Added",
|
|
11138
|
+
empty: {
|
|
11139
|
+
icon: Upload,
|
|
11140
|
+
label: "Add files or emails",
|
|
11141
|
+
description: "From your computer, a web link, or a connected mailbox.",
|
|
11142
|
+
onSelect: actions.addFiles
|
|
11143
|
+
}
|
|
11144
|
+
}
|
|
11145
|
+
),
|
|
11146
|
+
/* @__PURE__ */ jsx(
|
|
11147
|
+
RecentList,
|
|
11148
|
+
{
|
|
11149
|
+
testId: `${testId}-chats`,
|
|
11150
|
+
icon: MessagesSquare,
|
|
11151
|
+
title: getEntityLabel("CONVERSATION", "PLURAL"),
|
|
11152
|
+
count: conversations.length,
|
|
11153
|
+
onOpen: () => actions.openSection("threads"),
|
|
11154
|
+
action: (
|
|
11155
|
+
// Quiet like every section action: the Start band is the page's first job.
|
|
11156
|
+
/* @__PURE__ */ jsx(
|
|
11157
|
+
DashboardAction,
|
|
11158
|
+
{
|
|
11159
|
+
emphasis: "quiet",
|
|
11160
|
+
icon: /* @__PURE__ */ jsx(Plus, { className: "size-4" }),
|
|
11161
|
+
label: "New",
|
|
11162
|
+
onClick: actions.newThread,
|
|
11163
|
+
testId: `${testId}-chats-new`
|
|
11164
|
+
}
|
|
11165
|
+
)
|
|
11166
|
+
),
|
|
11167
|
+
rows: chatRows,
|
|
11168
|
+
caption: "Messaged",
|
|
11169
|
+
empty: {
|
|
11170
|
+
icon: Plus,
|
|
11171
|
+
label: "Ask ARBI anything",
|
|
11172
|
+
description: "Questions are answered from this workspace's files.",
|
|
11173
|
+
onSelect: actions.ask
|
|
11174
|
+
}
|
|
11175
|
+
}
|
|
11176
|
+
)
|
|
11177
|
+
] })
|
|
11178
|
+
] }),
|
|
11179
|
+
/* @__PURE__ */ jsxs(DashboardColumns, { children: [
|
|
11180
|
+
/* @__PURE__ */ jsx(
|
|
11181
|
+
FactsStrip,
|
|
11182
|
+
{
|
|
11183
|
+
testId: `${testId}-facts`,
|
|
11184
|
+
icon: Milestone,
|
|
11185
|
+
title: getEntityLabel("FACT", "PLURAL"),
|
|
11186
|
+
count: facts.length,
|
|
11187
|
+
onOpen: () => actions.openSection("facts"),
|
|
11188
|
+
action: /* @__PURE__ */ jsx(
|
|
11189
|
+
DashboardAction,
|
|
11190
|
+
{
|
|
11191
|
+
emphasis: "quiet",
|
|
11192
|
+
icon: /* @__PURE__ */ jsx(Plus, { className: "size-4" }),
|
|
11193
|
+
label: "New fact",
|
|
11194
|
+
onClick: actions.newFact,
|
|
11195
|
+
testId: `${testId}-facts-action`
|
|
11196
|
+
}
|
|
11197
|
+
),
|
|
11198
|
+
groups: [
|
|
11199
|
+
{ caption: "Upcoming", rows: upcoming, rowTestId: `${testId}-facts-upcoming-row` },
|
|
11200
|
+
{ caption: "Created", rows: recent, rowTestId: `${testId}-facts-row` }
|
|
11201
|
+
],
|
|
11202
|
+
empty: {
|
|
11203
|
+
icon: Milestone,
|
|
11204
|
+
label: `Add your first ${getEntityLabel("FACT").toLowerCase()}`,
|
|
11205
|
+
description: "A date, a decision, a preference \u2014 anything worth remembering.",
|
|
11206
|
+
onSelect: actions.newFact,
|
|
11207
|
+
testId: `${testId}-facts-empty-action`
|
|
11208
|
+
}
|
|
11209
|
+
}
|
|
11210
|
+
),
|
|
11211
|
+
aside
|
|
11212
|
+
] })
|
|
11213
|
+
] });
|
|
11214
|
+
}
|
|
11215
|
+
var DEFAULT_SEARCH_ROW_LABELS = {
|
|
11216
|
+
meaningMatch: "Matched by meaning",
|
|
11217
|
+
meaningMatchTitle: "None of your search words appear in this passage. It matched by meaning.",
|
|
11218
|
+
page: (page) => `p.${page}`,
|
|
11219
|
+
passage: "Passage"
|
|
11220
|
+
};
|
|
11221
|
+
function useIsOpenPassage(chunkExtId) {
|
|
11222
|
+
return useArbiSessionState((s) => !!chunkExtId && s.viewingDocument?.passageId === chunkExtId);
|
|
11223
|
+
}
|
|
11224
|
+
function MeaningMatchBadge({
|
|
11225
|
+
content,
|
|
11226
|
+
terms,
|
|
11227
|
+
labels
|
|
11228
|
+
}) {
|
|
11229
|
+
return isMeaningMatch(content, terms) ? /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-muted-foreground", title: labels.meaningMatchTitle, children: labels.meaningMatch }) : null;
|
|
11230
|
+
}
|
|
11231
|
+
var FlatResultRow = memo(function FlatResultRow2({
|
|
11232
|
+
hit,
|
|
11233
|
+
position,
|
|
11234
|
+
terms,
|
|
11235
|
+
onOpen,
|
|
11236
|
+
labels
|
|
11237
|
+
}) {
|
|
11238
|
+
const handleClick = useCallback((e) => onOpen(e, hit), [onOpen, hit]);
|
|
11239
|
+
return /* @__PURE__ */ jsx(
|
|
11240
|
+
SearchHit,
|
|
11241
|
+
{
|
|
11242
|
+
active: useIsOpenPassage(hit.chunkExtId),
|
|
11243
|
+
header: (
|
|
11244
|
+
// No `onClick` on the pill: it would render a real <button> inside the card's
|
|
11245
|
+
// role="button" target (invalid nesting). The card already opens the passage.
|
|
11246
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [
|
|
11247
|
+
/* @__PURE__ */ jsx(
|
|
11248
|
+
PassagePill,
|
|
11249
|
+
{
|
|
11250
|
+
icon: /* @__PURE__ */ jsx(HtmlIcon, { size: 16, className: "flex-shrink-0" }),
|
|
11251
|
+
documentTitle: hit.docTitle,
|
|
11252
|
+
pageNumber: hit.page,
|
|
11253
|
+
passageIndex: hit.index,
|
|
11254
|
+
showTooltip: false,
|
|
11255
|
+
labelMaxWidth: "max-w-none"
|
|
11256
|
+
}
|
|
11257
|
+
),
|
|
11258
|
+
/* @__PURE__ */ jsx(MeaningMatchBadge, { content: hit.content, terms, labels })
|
|
11259
|
+
] })
|
|
11260
|
+
),
|
|
11261
|
+
content: hit.content,
|
|
11262
|
+
terms,
|
|
11263
|
+
renderSnippet: renderSearchSnippet,
|
|
11264
|
+
onClick: handleClick,
|
|
11265
|
+
testId: `search-result-flat-${position}`
|
|
11266
|
+
}
|
|
11267
|
+
);
|
|
11268
|
+
});
|
|
11269
|
+
var GroupedResultRow = memo(function GroupedResultRow2({
|
|
10704
11270
|
hit,
|
|
10705
11271
|
testId,
|
|
10706
11272
|
terms,
|
|
@@ -13823,7 +14389,7 @@ function BookingPagesPanel({ defaultTimeZone, className }) {
|
|
|
13823
14389
|
var selectClass4 = "h-9 w-full rounded-md border border-input bg-background px-2 text-sm text-foreground shadow-sm";
|
|
13824
14390
|
function CreateRoomForm({ zone, onDone }) {
|
|
13825
14391
|
const { notify: notify14 } = useBlocksEnvironment();
|
|
13826
|
-
const
|
|
14392
|
+
const create3 = useCreateRoom();
|
|
13827
14393
|
const [name, setName] = useState("");
|
|
13828
14394
|
const [type, setType] = useState("room");
|
|
13829
14395
|
const [capacity, setCapacity] = useState("");
|
|
@@ -13843,7 +14409,7 @@ function CreateRoomForm({ zone, onDone }) {
|
|
|
13843
14409
|
const cap = capacity.trim() ? Number(capacity) : null;
|
|
13844
14410
|
if (cap !== null && (!Number.isInteger(cap) || cap < 1))
|
|
13845
14411
|
return setError("Capacity must be a whole number");
|
|
13846
|
-
|
|
14412
|
+
create3.mutate(
|
|
13847
14413
|
{
|
|
13848
14414
|
name: name.trim(),
|
|
13849
14415
|
type,
|
|
@@ -13934,8 +14500,8 @@ function CreateRoomForm({ zone, onDone }) {
|
|
|
13934
14500
|
error && /* @__PURE__ */ jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }),
|
|
13935
14501
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
13936
14502
|
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: onDone, children: "Cancel" }),
|
|
13937
|
-
/* @__PURE__ */ jsxs(Button, { type: "submit", disabled:
|
|
13938
|
-
|
|
14503
|
+
/* @__PURE__ */ jsxs(Button, { type: "submit", disabled: create3.isPending, children: [
|
|
14504
|
+
create3.isPending && /* @__PURE__ */ jsx(Loader2, { className: "mr-1 size-4 animate-spin" }),
|
|
13939
14505
|
"Create"
|
|
13940
14506
|
] })
|
|
13941
14507
|
] })
|
|
@@ -15014,7 +15580,7 @@ function subscribePhone(cb) {
|
|
|
15014
15580
|
return () => mq?.removeEventListener?.("change", cb);
|
|
15015
15581
|
}
|
|
15016
15582
|
var isPhoneNow = () => typeof window !== "undefined" && !!window.matchMedia?.(PHONE_QUERY)?.matches;
|
|
15017
|
-
function
|
|
15583
|
+
function useIsPhone2() {
|
|
15018
15584
|
return useSyncExternalStore(subscribePhone, isPhoneNow, () => false);
|
|
15019
15585
|
}
|
|
15020
15586
|
var errorText3 = (err) => describeCalendarError(err);
|
|
@@ -15036,7 +15602,7 @@ function ConnectedCalendar({
|
|
|
15036
15602
|
const arbi = useArbi();
|
|
15037
15603
|
const fetchEvent = useFetchCalendarEvent();
|
|
15038
15604
|
const notifyError = (prefix, err) => notify14(`${prefix}: ${errorText3(err)}`, "error");
|
|
15039
|
-
const isPhone =
|
|
15605
|
+
const isPhone = useIsPhone2();
|
|
15040
15606
|
const { data: settingsData } = useCalendarSettings();
|
|
15041
15607
|
const settings = settingsData ?? FALLBACK_SETTINGS;
|
|
15042
15608
|
const zone = safeZone(settings.time_zone, detectTimeZone());
|
|
@@ -16251,6 +16817,1152 @@ function ConnectedPublicBooking({ slug, cancelToken }) {
|
|
|
16251
16817
|
] })
|
|
16252
16818
|
] });
|
|
16253
16819
|
}
|
|
16820
|
+
var useFilesStore = create()(
|
|
16821
|
+
persist(
|
|
16822
|
+
(set) => ({
|
|
16823
|
+
pendingFolders: {},
|
|
16824
|
+
addPendingFolder: (workspaceId, path) => set((state) => {
|
|
16825
|
+
const current = state.pendingFolders[workspaceId] ?? [];
|
|
16826
|
+
if (current.includes(path)) return state;
|
|
16827
|
+
return { pendingFolders: { ...state.pendingFolders, [workspaceId]: [...current, path] } };
|
|
16828
|
+
}),
|
|
16829
|
+
removePendingFolder: (workspaceId, path) => set((state) => {
|
|
16830
|
+
const current = state.pendingFolders[workspaceId] ?? [];
|
|
16831
|
+
if (!current.includes(path)) return state;
|
|
16832
|
+
return {
|
|
16833
|
+
pendingFolders: {
|
|
16834
|
+
...state.pendingFolders,
|
|
16835
|
+
[workspaceId]: current.filter((p) => p !== path)
|
|
16836
|
+
}
|
|
16837
|
+
};
|
|
16838
|
+
})
|
|
16839
|
+
}),
|
|
16840
|
+
{
|
|
16841
|
+
name: "arbi_explorer_pending_folders",
|
|
16842
|
+
storage: createJSONStorage(() => sessionStorage),
|
|
16843
|
+
partialize: (state) => ({ pendingFolders: state.pendingFolders })
|
|
16844
|
+
}
|
|
16845
|
+
)
|
|
16846
|
+
);
|
|
16847
|
+
var NONE = [];
|
|
16848
|
+
function usePendingFolders(workspaceId) {
|
|
16849
|
+
return useFilesStore(
|
|
16850
|
+
(state) => (workspaceId ? state.pendingFolders[workspaceId] : void 0) ?? NONE
|
|
16851
|
+
);
|
|
16852
|
+
}
|
|
16853
|
+
var FilesSupportContext = createContext({});
|
|
16854
|
+
function FilesSupportProvider({
|
|
16855
|
+
value,
|
|
16856
|
+
children
|
|
16857
|
+
}) {
|
|
16858
|
+
return /* @__PURE__ */ jsx(FilesSupportContext.Provider, { value, children });
|
|
16859
|
+
}
|
|
16860
|
+
function useFilesSupport() {
|
|
16861
|
+
return useContext(FilesSupportContext);
|
|
16862
|
+
}
|
|
16863
|
+
var NO_IDS3 = /* @__PURE__ */ new Set();
|
|
16864
|
+
function useOfflineDocumentIds() {
|
|
16865
|
+
const { useOfflineDocumentIds: useHostIds } = useFilesSupport();
|
|
16866
|
+
return useHostIds?.() ?? NO_IDS3;
|
|
16867
|
+
}
|
|
16868
|
+
function useViewingDocumentId() {
|
|
16869
|
+
const { useViewingDocumentId: useHostViewing } = useFilesSupport();
|
|
16870
|
+
return useHostViewing?.() ?? null;
|
|
16871
|
+
}
|
|
16872
|
+
function DocumentSyncGlyph({
|
|
16873
|
+
docExtId,
|
|
16874
|
+
size = 13,
|
|
16875
|
+
className,
|
|
16876
|
+
bgClassName = "bg-muted-foreground"
|
|
16877
|
+
}) {
|
|
16878
|
+
const isAvailableOffline = useOfflineDocumentIds().has(docExtId);
|
|
16879
|
+
if (!isAvailableOffline) return null;
|
|
16880
|
+
return /* @__PURE__ */ jsx(
|
|
16881
|
+
"span",
|
|
16882
|
+
{
|
|
16883
|
+
title: "Available offline (saved on this device)",
|
|
16884
|
+
"aria-label": "Available offline",
|
|
16885
|
+
"data-testid": `offline-available-badge-${docExtId}`,
|
|
16886
|
+
className: cn(
|
|
16887
|
+
"inline-flex items-center justify-center rounded-full text-white ring-1 ring-white",
|
|
16888
|
+
bgClassName,
|
|
16889
|
+
className
|
|
16890
|
+
),
|
|
16891
|
+
style: { width: size, height: size },
|
|
16892
|
+
children: /* @__PURE__ */ jsx(ArrowDownToLine, { size: Math.round(size * 0.7), strokeWidth: 2.75 })
|
|
16893
|
+
}
|
|
16894
|
+
);
|
|
16895
|
+
}
|
|
16896
|
+
var ACTIVE_STATUSES = /* @__PURE__ */ new Set([
|
|
16897
|
+
"uploading",
|
|
16898
|
+
"queued",
|
|
16899
|
+
"parsing",
|
|
16900
|
+
"encrypting",
|
|
16901
|
+
"indexing",
|
|
16902
|
+
"analysing"
|
|
16903
|
+
]);
|
|
16904
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "skipped", "empty", "low-content"]);
|
|
16905
|
+
var STATUS_LABEL = {
|
|
16906
|
+
uploading: "Uploading",
|
|
16907
|
+
queued: "Queued",
|
|
16908
|
+
parsing: "Parsing",
|
|
16909
|
+
encrypting: "Encrypting",
|
|
16910
|
+
indexing: "Indexing",
|
|
16911
|
+
analysing: "Analysing"
|
|
16912
|
+
};
|
|
16913
|
+
function isActiveDocumentStatus(status) {
|
|
16914
|
+
return ACTIVE_STATUSES.has(status.toLowerCase());
|
|
16915
|
+
}
|
|
16916
|
+
function useDocStatus(doc) {
|
|
16917
|
+
const liveStatus = useProcessingTask(doc.external_id)?.status;
|
|
16918
|
+
const rowStatus = (doc.status ?? "").toLowerCase();
|
|
16919
|
+
if (liveStatus && ACTIVE_STATUSES.has(liveStatus)) return liveStatus;
|
|
16920
|
+
if (TERMINAL_STATUSES.has(rowStatus)) return rowStatus;
|
|
16921
|
+
return liveStatus ?? rowStatus;
|
|
16922
|
+
}
|
|
16923
|
+
function TileStatusBadge({ doc, status }) {
|
|
16924
|
+
let tone = "neutral";
|
|
16925
|
+
let icon2 = null;
|
|
16926
|
+
let title = "";
|
|
16927
|
+
switch (status) {
|
|
16928
|
+
case "completed":
|
|
16929
|
+
tone = "success";
|
|
16930
|
+
icon2 = /* @__PURE__ */ jsx(CheckCircle, { className: "size-4 text-green-text", strokeWidth: 2.75 });
|
|
16931
|
+
title = "Ready";
|
|
16932
|
+
break;
|
|
16933
|
+
case "failed":
|
|
16934
|
+
tone = "danger";
|
|
16935
|
+
icon2 = /* @__PURE__ */ jsx(
|
|
16936
|
+
FailedStageIcon,
|
|
16937
|
+
{
|
|
16938
|
+
stage: doc.failed_stage ?? null,
|
|
16939
|
+
errorMessage: doc.error_message,
|
|
16940
|
+
className: "size-4 text-red-text"
|
|
16941
|
+
}
|
|
16942
|
+
);
|
|
16943
|
+
title = "Failed";
|
|
16944
|
+
break;
|
|
16945
|
+
case "skipped":
|
|
16946
|
+
icon2 = /* @__PURE__ */ jsx(SkipForward, { className: "size-4 text-muted-foreground", strokeWidth: 2.75 });
|
|
16947
|
+
title = "Skipped";
|
|
16948
|
+
break;
|
|
16949
|
+
case "empty":
|
|
16950
|
+
tone = "warning";
|
|
16951
|
+
icon2 = /* @__PURE__ */ jsx(FileX, { className: "size-4 text-yellow-text", strokeWidth: 2.75 });
|
|
16952
|
+
title = "Empty \u2014 no extractable content";
|
|
16953
|
+
break;
|
|
16954
|
+
case "low-content":
|
|
16955
|
+
tone = "warning";
|
|
16956
|
+
icon2 = /* @__PURE__ */ jsx(AlertTriangle, { className: "size-4 text-yellow-text", strokeWidth: 2.75 });
|
|
16957
|
+
title = "Low content \u2014 minimal extractable content";
|
|
16958
|
+
break;
|
|
16959
|
+
default:
|
|
16960
|
+
return null;
|
|
16961
|
+
}
|
|
16962
|
+
return /* @__PURE__ */ jsx(
|
|
16963
|
+
StatusPill,
|
|
16964
|
+
{
|
|
16965
|
+
tone,
|
|
16966
|
+
icon: icon2,
|
|
16967
|
+
title,
|
|
16968
|
+
testId: `explorer-file-status-${doc.external_id}`
|
|
16969
|
+
}
|
|
16970
|
+
);
|
|
16971
|
+
}
|
|
16972
|
+
function TileVisibilityBadge({ doc }) {
|
|
16973
|
+
const shared = !!doc.shared;
|
|
16974
|
+
return /* @__PURE__ */ jsx(
|
|
16975
|
+
StatusPill,
|
|
16976
|
+
{
|
|
16977
|
+
icon: /* @__PURE__ */ jsx(VisibilityIcon, { isShared: shared, className: "size-3.5", "aria-hidden": true }),
|
|
16978
|
+
label: shared ? getEntityLabel("WORKSPACE") : "Only you",
|
|
16979
|
+
title: visibilityAudience(shared),
|
|
16980
|
+
testId: `explorer-file-visibility-${doc.external_id}`
|
|
16981
|
+
}
|
|
16982
|
+
);
|
|
16983
|
+
}
|
|
16984
|
+
function MiniUserAvatar({ user }) {
|
|
16985
|
+
return /* @__PURE__ */ jsxs(Avatar, { className: "h-4 w-4 border border-white", children: [
|
|
16986
|
+
user.picture && /* @__PURE__ */ jsx(AvatarImage, { src: user.picture, alt: getUserDisplayName(user) }),
|
|
16987
|
+
/* @__PURE__ */ jsx(
|
|
16988
|
+
AvatarFallback,
|
|
16989
|
+
{
|
|
16990
|
+
className: cn(
|
|
16991
|
+
"text-xs font-semibold leading-none text-white",
|
|
16992
|
+
getUserAvatarColor(user.external_id)
|
|
16993
|
+
),
|
|
16994
|
+
children: getUserInitials(user)
|
|
16995
|
+
}
|
|
16996
|
+
)
|
|
16997
|
+
] });
|
|
16998
|
+
}
|
|
16999
|
+
function FileTooltipContent({
|
|
17000
|
+
doc,
|
|
17001
|
+
modifiedBy,
|
|
17002
|
+
tagNameById
|
|
17003
|
+
}) {
|
|
17004
|
+
const labels = (doc.doctags ?? []).map((dt) => tagNameById.get(dt.tag_ext_id)).filter((name) => !!name);
|
|
17005
|
+
return /* @__PURE__ */ jsx(
|
|
17006
|
+
ExplorerFileTooltip,
|
|
17007
|
+
{
|
|
17008
|
+
title: getDocumentTitle(doc, doc.file_name ?? ""),
|
|
17009
|
+
summary: getDocumentSubject(doc),
|
|
17010
|
+
modifiedLabel: doc.updated_at ? formatFriendlyDate(doc.updated_at, false) : null,
|
|
17011
|
+
modifiedByAvatar: modifiedBy && /* @__PURE__ */ jsx(MiniUserAvatar, { user: modifiedBy }),
|
|
17012
|
+
shared: !!doc.shared,
|
|
17013
|
+
visibilityIcon: /* @__PURE__ */ jsx(VisibilityIcon, { isShared: !!doc.shared, className: "h-3 w-3" }),
|
|
17014
|
+
labels,
|
|
17015
|
+
uiLabels: { shared: visibilityAudience(true), private: visibilityAudience(false) }
|
|
17016
|
+
}
|
|
17017
|
+
);
|
|
17018
|
+
}
|
|
17019
|
+
function startMenuItem(items) {
|
|
17020
|
+
if (items.length === 0) return [];
|
|
17021
|
+
return [
|
|
17022
|
+
{
|
|
17023
|
+
id: "start",
|
|
17024
|
+
label: "Start\u2026",
|
|
17025
|
+
icon: Sparkles,
|
|
17026
|
+
testId: "explorer-start-menu",
|
|
17027
|
+
onSelect: () => {
|
|
17028
|
+
},
|
|
17029
|
+
submenu: items.map((item) => ({
|
|
17030
|
+
id: item.id,
|
|
17031
|
+
label: item.title,
|
|
17032
|
+
icon: Play,
|
|
17033
|
+
onSelect: item.run,
|
|
17034
|
+
testId: `explorer-start-${item.id}`
|
|
17035
|
+
}))
|
|
17036
|
+
}
|
|
17037
|
+
];
|
|
17038
|
+
}
|
|
17039
|
+
var FileTile = memo(function FileTile2({
|
|
17040
|
+
doc,
|
|
17041
|
+
density,
|
|
17042
|
+
selected,
|
|
17043
|
+
dragging,
|
|
17044
|
+
dndActive,
|
|
17045
|
+
renaming,
|
|
17046
|
+
canDelete,
|
|
17047
|
+
thumbnailUrl,
|
|
17048
|
+
modifiedBy,
|
|
17049
|
+
tagNameById,
|
|
17050
|
+
onSelect,
|
|
17051
|
+
onToggle,
|
|
17052
|
+
onOpen,
|
|
17053
|
+
onReview,
|
|
17054
|
+
onStartRename,
|
|
17055
|
+
onCommitRename,
|
|
17056
|
+
onCancelRename,
|
|
17057
|
+
onDownload,
|
|
17058
|
+
onMove,
|
|
17059
|
+
onDelete
|
|
17060
|
+
}) {
|
|
17061
|
+
const { attributes, listeners: listeners2, setNodeRef } = useDraggable({
|
|
17062
|
+
id: `doc:${doc.external_id}`,
|
|
17063
|
+
data: { type: "doc", docId: doc.external_id }
|
|
17064
|
+
});
|
|
17065
|
+
const { notify: notify14 } = useBlocksEnvironment();
|
|
17066
|
+
const { startItemsForFile, openDocumentChat, emailDocument } = useFilesSupport();
|
|
17067
|
+
const status = useDocStatus(doc);
|
|
17068
|
+
const isProcessing = ACTIVE_STATUSES.has(status);
|
|
17069
|
+
const isOffline = useOfflineDocumentIds().has(doc.external_id);
|
|
17070
|
+
const isViewing = useViewingDocumentId() === doc.external_id;
|
|
17071
|
+
const showStatusMarker = !isProcessing && TERMINAL_STATUSES.has(status);
|
|
17072
|
+
const Icon = getFileTypeIcon(doc.file_name, { mime: doc.file_type });
|
|
17073
|
+
const { data: mailPolicy } = useMailPolicy();
|
|
17074
|
+
const shareAsEmail = async () => {
|
|
17075
|
+
try {
|
|
17076
|
+
await emailDocument?.(doc);
|
|
17077
|
+
} catch (error) {
|
|
17078
|
+
notify14(error instanceof Error ? error.message : "Could not attach document", "error");
|
|
17079
|
+
}
|
|
17080
|
+
};
|
|
17081
|
+
const startItems = startItemsForFile?.(doc);
|
|
17082
|
+
const menuItems = [
|
|
17083
|
+
{ id: "open", label: "Open", icon: SquarePen, onSelect: () => onOpen(doc) },
|
|
17084
|
+
...onReview ? [{ id: "review", label: "Open in Review", icon: BookOpen, onSelect: () => onReview(doc) }] : [],
|
|
17085
|
+
...openDocumentChat ? [
|
|
17086
|
+
{
|
|
17087
|
+
id: "chat",
|
|
17088
|
+
label: "Document chat",
|
|
17089
|
+
icon: MessagesSquare,
|
|
17090
|
+
onSelect: () => openDocumentChat(doc.external_id)
|
|
17091
|
+
}
|
|
17092
|
+
] : [],
|
|
17093
|
+
...startMenuItem(startItems ?? []),
|
|
17094
|
+
{
|
|
17095
|
+
id: "rename",
|
|
17096
|
+
label: "Rename",
|
|
17097
|
+
icon: Pencil,
|
|
17098
|
+
onSelect: () => onStartRename(doc),
|
|
17099
|
+
separatorBefore: true
|
|
17100
|
+
},
|
|
17101
|
+
{ id: "download", label: "Download", icon: Download, onSelect: () => onDownload(doc) },
|
|
17102
|
+
...emailDocument && mailPolicy?.enabled ? [
|
|
17103
|
+
{
|
|
17104
|
+
id: "email",
|
|
17105
|
+
label: "Send as email attachment",
|
|
17106
|
+
icon: Mail,
|
|
17107
|
+
onSelect: () => {
|
|
17108
|
+
void shareAsEmail();
|
|
17109
|
+
}
|
|
17110
|
+
}
|
|
17111
|
+
] : [],
|
|
17112
|
+
...onMove ? [{ id: "move", label: "Move to\u2026", icon: FolderInput, onSelect: () => onMove(doc) }] : [],
|
|
17113
|
+
...canDelete ? [
|
|
17114
|
+
{
|
|
17115
|
+
id: "delete",
|
|
17116
|
+
label: "Delete",
|
|
17117
|
+
icon: Trash2,
|
|
17118
|
+
onSelect: () => onDelete(doc),
|
|
17119
|
+
destructive: true,
|
|
17120
|
+
separatorBefore: true
|
|
17121
|
+
}
|
|
17122
|
+
] : []
|
|
17123
|
+
];
|
|
17124
|
+
const tooltip = renaming || dndActive ? void 0 : /* @__PURE__ */ jsx(FileTooltipContent, { doc, modifiedBy, tagNameById });
|
|
17125
|
+
return /* @__PURE__ */ jsx(
|
|
17126
|
+
ExplorerFileTile,
|
|
17127
|
+
{
|
|
17128
|
+
id: doc.external_id,
|
|
17129
|
+
title: getDocumentTitle(doc, doc.file_name ?? ""),
|
|
17130
|
+
density,
|
|
17131
|
+
selected,
|
|
17132
|
+
dragging,
|
|
17133
|
+
renaming,
|
|
17134
|
+
thumbnailUrl,
|
|
17135
|
+
renderFileIcon: (size) => /* @__PURE__ */ jsx(Icon, { size, className: "drop-shadow-sm" }),
|
|
17136
|
+
progress: isProcessing ? { label: STATUS_LABEL[status] ?? status, queued: status === "queued" } : void 0,
|
|
17137
|
+
statusMarker: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
17138
|
+
/* @__PURE__ */ jsx(TileVisibilityBadge, { doc }),
|
|
17139
|
+
showStatusMarker && /* @__PURE__ */ jsx(TileStatusBadge, { doc, status })
|
|
17140
|
+
] }),
|
|
17141
|
+
offlineMarker: isOffline ? /* @__PURE__ */ jsx(DocumentSyncGlyph, { docExtId: doc.external_id, size: 24, bgClassName: "bg-primary" }) : void 0,
|
|
17142
|
+
entity: /* @__PURE__ */ jsx(
|
|
17143
|
+
DocumentEntity,
|
|
17144
|
+
{
|
|
17145
|
+
doc,
|
|
17146
|
+
labelMaxWidth: "max-w-full",
|
|
17147
|
+
noClamp: true,
|
|
17148
|
+
labelClassName: "whitespace-normal line-clamp-2 break-words",
|
|
17149
|
+
viewing: isViewing,
|
|
17150
|
+
tooltipContent: /* @__PURE__ */ jsx(FileTooltipContent, { doc, modifiedBy, tagNameById }),
|
|
17151
|
+
className: cn(
|
|
17152
|
+
"w-full min-w-0 max-w-full bg-background/90 backdrop-blur-sm",
|
|
17153
|
+
density === "list" ? "text-xs" : void 0
|
|
17154
|
+
),
|
|
17155
|
+
onClick: (e) => {
|
|
17156
|
+
e.stopPropagation();
|
|
17157
|
+
onOpen(doc);
|
|
17158
|
+
}
|
|
17159
|
+
}
|
|
17160
|
+
),
|
|
17161
|
+
tooltip,
|
|
17162
|
+
menuItems,
|
|
17163
|
+
rootRef: setNodeRef,
|
|
17164
|
+
rootProps: { ...listeners2, ...attributes },
|
|
17165
|
+
onOpen: () => onOpen(doc),
|
|
17166
|
+
onSelect: (e) => onSelect(doc, e),
|
|
17167
|
+
onToggle: (e) => onToggle(doc, e),
|
|
17168
|
+
onCommitRename: (name) => onCommitRename(doc, name),
|
|
17169
|
+
onCancelRename
|
|
17170
|
+
}
|
|
17171
|
+
);
|
|
17172
|
+
});
|
|
17173
|
+
var FolderTile = memo(function FolderTile2({
|
|
17174
|
+
folder,
|
|
17175
|
+
density,
|
|
17176
|
+
editing,
|
|
17177
|
+
selected,
|
|
17178
|
+
partiallySelected,
|
|
17179
|
+
documents,
|
|
17180
|
+
onOpen,
|
|
17181
|
+
onToggleSelect,
|
|
17182
|
+
onCommitName,
|
|
17183
|
+
onCancelName,
|
|
17184
|
+
onStartRename,
|
|
17185
|
+
onDownload,
|
|
17186
|
+
onMove,
|
|
17187
|
+
onDelete
|
|
17188
|
+
}) {
|
|
17189
|
+
const { setNodeRef, isOver } = useDroppable({
|
|
17190
|
+
id: `folder:${folder.path.join("/")}`,
|
|
17191
|
+
data: { type: "folder", path: folder.path }
|
|
17192
|
+
});
|
|
17193
|
+
const { startItemsForFolder } = useFilesSupport();
|
|
17194
|
+
const tasks = useProcessingTasks();
|
|
17195
|
+
const processingCount = useMemo(
|
|
17196
|
+
() => documents.filter(
|
|
17197
|
+
(doc) => isActiveDocumentStatus(tasks[doc.external_id]?.status ?? doc.status ?? "")
|
|
17198
|
+
).length,
|
|
17199
|
+
[documents, tasks]
|
|
17200
|
+
);
|
|
17201
|
+
const isEmpty = folder.count === 0;
|
|
17202
|
+
const iconSize = density === "list" ? 34 : 48;
|
|
17203
|
+
const menuItems = [
|
|
17204
|
+
...onStartRename ? [{ id: "rename", label: "Rename", icon: Pencil, onSelect: () => onStartRename(folder) }] : [],
|
|
17205
|
+
...!isEmpty ? startMenuItem(startItemsForFolder?.(folder.path.join("/")) ?? []) : [],
|
|
17206
|
+
...onDownload && !isEmpty ? [{ id: "download", label: "Download", icon: Download, onSelect: () => onDownload(folder) }] : [],
|
|
17207
|
+
...onMove && !isEmpty ? [{ id: "move", label: "Move to\u2026", icon: FolderInput, onSelect: () => onMove(folder) }] : [],
|
|
17208
|
+
...onDelete ? [
|
|
17209
|
+
{
|
|
17210
|
+
id: "delete",
|
|
17211
|
+
label: "Delete",
|
|
17212
|
+
icon: Trash2,
|
|
17213
|
+
onSelect: () => onDelete(folder),
|
|
17214
|
+
destructive: true,
|
|
17215
|
+
separatorBefore: true
|
|
17216
|
+
}
|
|
17217
|
+
] : []
|
|
17218
|
+
];
|
|
17219
|
+
const displayName = folder.path.length === 1 ? formatFolderSegment(folder.name) : folder.name;
|
|
17220
|
+
const providerIcon = folder.path.length === 1 && hasFolderProviderIcon(folder.name) ? /* @__PURE__ */ jsx(
|
|
17221
|
+
"span",
|
|
17222
|
+
{
|
|
17223
|
+
className: "flex items-center justify-center drop-shadow-sm",
|
|
17224
|
+
style: { width: iconSize, height: iconSize },
|
|
17225
|
+
children: /* @__PURE__ */ jsx(FolderProviderIcon, { folderPath: folder.name, className: "h-full w-full" })
|
|
17226
|
+
}
|
|
17227
|
+
) : void 0;
|
|
17228
|
+
return /* @__PURE__ */ jsx(
|
|
17229
|
+
ExplorerFolderTile,
|
|
17230
|
+
{
|
|
17231
|
+
name: folder.name,
|
|
17232
|
+
displayName: displayName === "" ? null : displayName,
|
|
17233
|
+
path: folder.path,
|
|
17234
|
+
count: folder.count,
|
|
17235
|
+
density,
|
|
17236
|
+
editing,
|
|
17237
|
+
selected,
|
|
17238
|
+
partiallySelected,
|
|
17239
|
+
processingCount,
|
|
17240
|
+
isOver,
|
|
17241
|
+
icon: providerIcon,
|
|
17242
|
+
menuItems,
|
|
17243
|
+
rootRef: setNodeRef,
|
|
17244
|
+
onOpen: () => onOpen(folder),
|
|
17245
|
+
onToggleSelect: onToggleSelect ? (e) => onToggleSelect(folder, e) : void 0,
|
|
17246
|
+
onCommitName: (name) => onCommitName?.(folder, name),
|
|
17247
|
+
onCancelName
|
|
17248
|
+
}
|
|
17249
|
+
);
|
|
17250
|
+
});
|
|
17251
|
+
var GRID_GAP = 12;
|
|
17252
|
+
var SCROLL_PADDING_X = 32;
|
|
17253
|
+
var PAGE_ASPECT = 1.3;
|
|
17254
|
+
var TILE_MIN = 88;
|
|
17255
|
+
var TILE_MAX = 232;
|
|
17256
|
+
var TILE_DEFAULT = 200;
|
|
17257
|
+
var COMPACT_BELOW = 132;
|
|
17258
|
+
var NO_DOCUMENTS = [];
|
|
17259
|
+
function useFilesWorkspace() {
|
|
17260
|
+
const { workspaceId, userId, live } = useConnectedWorkspace();
|
|
17261
|
+
const { members, role } = useConnectedWorkspaceRole();
|
|
17262
|
+
const { data: workspaces } = useWorkspaces({ enabled: live });
|
|
17263
|
+
const workspaceName = workspaces?.find((w) => w.external_id === workspaceId)?.name;
|
|
17264
|
+
return {
|
|
17265
|
+
workspaceId: workspaceId ?? "",
|
|
17266
|
+
workspaceName,
|
|
17267
|
+
userId,
|
|
17268
|
+
role: role ?? null,
|
|
17269
|
+
members
|
|
17270
|
+
};
|
|
17271
|
+
}
|
|
17272
|
+
var ConnectedFilesExplorer = forwardRef(function ConnectedFilesExplorer2({
|
|
17273
|
+
documents,
|
|
17274
|
+
isLoading,
|
|
17275
|
+
canUpload,
|
|
17276
|
+
selectedIds,
|
|
17277
|
+
onSelectionChange,
|
|
17278
|
+
onOrderedFilesChange,
|
|
17279
|
+
onOpenDocument,
|
|
17280
|
+
onUploadFiles,
|
|
17281
|
+
folderPath,
|
|
17282
|
+
onFolderPathChange,
|
|
17283
|
+
search = "",
|
|
17284
|
+
sort,
|
|
17285
|
+
sortReversed,
|
|
17286
|
+
tileSize,
|
|
17287
|
+
onMoveSelected,
|
|
17288
|
+
onFolderMove,
|
|
17289
|
+
onOpenInReview,
|
|
17290
|
+
emptyWorkspace
|
|
17291
|
+
}, ref) {
|
|
17292
|
+
const { confirm, notify: notify14 } = useBlocksEnvironment();
|
|
17293
|
+
const currentPath = folderPath;
|
|
17294
|
+
const density = tileSize < COMPACT_BELOW ? "list" : "grid";
|
|
17295
|
+
const [containerWidth, setContainerWidth] = useState(0);
|
|
17296
|
+
const [activeDragId, setActiveDragId] = useState(null);
|
|
17297
|
+
const [isFileDragOver, setIsFileDragOver] = useState(false);
|
|
17298
|
+
const [renamingId, setRenamingId] = useState(null);
|
|
17299
|
+
const scrollRef = useRef(null);
|
|
17300
|
+
const anchorRef = useRef(null);
|
|
17301
|
+
const dragIdsRef = useRef([]);
|
|
17302
|
+
const fileDragDepth = useRef(0);
|
|
17303
|
+
const marqueeOverlayRef = useRef(null);
|
|
17304
|
+
const marqueeRef = useRef(
|
|
17305
|
+
null
|
|
17306
|
+
);
|
|
17307
|
+
const didMarqueeRef = useRef(false);
|
|
17308
|
+
const updateDocument = useUpdateDocuments();
|
|
17309
|
+
const deleteDocuments = useDeleteDocuments();
|
|
17310
|
+
const renameFolder = useRenameFolder();
|
|
17311
|
+
const { workspaceId: wsId, workspaceName, userId, role, members } = useFilesWorkspace();
|
|
17312
|
+
const { downloadDocuments, downloadDocument } = useDownloadDocuments({ workspaceName });
|
|
17313
|
+
const { data: workspaceTags } = useTags();
|
|
17314
|
+
const userById = useMemo(() => {
|
|
17315
|
+
const map = /* @__PURE__ */ new Map();
|
|
17316
|
+
for (const member of members) map.set(member.user.external_id, member.user);
|
|
17317
|
+
return map;
|
|
17318
|
+
}, [members]);
|
|
17319
|
+
const tagNameById = useMemo(() => {
|
|
17320
|
+
const map = /* @__PURE__ */ new Map();
|
|
17321
|
+
for (const tag of workspaceTags ?? []) map.set(tag.external_id, tag.name);
|
|
17322
|
+
return map;
|
|
17323
|
+
}, [workspaceTags]);
|
|
17324
|
+
const pendingFolders = usePendingFolders(wsId);
|
|
17325
|
+
const addPendingFolder = useFilesStore((state) => state.addPendingFolder);
|
|
17326
|
+
const removePendingFolder = useFilesStore((state) => state.removePendingFolder);
|
|
17327
|
+
const [editingFolderPath, setEditingFolderPath] = useState(null);
|
|
17328
|
+
const isSearching = search.trim().length > 0;
|
|
17329
|
+
const selectedSet = useMemo(() => new Set(selectedIds), [selectedIds]);
|
|
17330
|
+
const selectedIdsRef = useRef(selectedIds);
|
|
17331
|
+
selectedIdsRef.current = selectedIds;
|
|
17332
|
+
const { folders, files } = useMemo(() => {
|
|
17333
|
+
const result = isSearching ? {
|
|
17334
|
+
folders: [],
|
|
17335
|
+
files: searchExplorerDocuments(documents, search, sort)
|
|
17336
|
+
} : buildExplorerEntries(documents, currentPath, sort, [...pendingFolders]);
|
|
17337
|
+
if (sortReversed) result.files = [...result.files].reverse();
|
|
17338
|
+
return result;
|
|
17339
|
+
}, [documents, currentPath, sort, isSearching, search, sortReversed, pendingFolders]);
|
|
17340
|
+
const entries = useMemo(
|
|
17341
|
+
() => [...folders, ...files.map((doc) => ({ kind: "file", doc }))],
|
|
17342
|
+
[folders, files]
|
|
17343
|
+
);
|
|
17344
|
+
const orderedFileIds = useMemo(() => files.map((d) => d.external_id), [files]);
|
|
17345
|
+
const folderDocs = useMemo(() => {
|
|
17346
|
+
const map = /* @__PURE__ */ new Map();
|
|
17347
|
+
for (const f of folders) map.set(f.path.join("/"), collectFolderDocs(documents, f.path));
|
|
17348
|
+
return map;
|
|
17349
|
+
}, [folders, documents]);
|
|
17350
|
+
const folderSelection = useMemo(() => {
|
|
17351
|
+
const map = /* @__PURE__ */ new Map();
|
|
17352
|
+
for (const [key, docs] of folderDocs) {
|
|
17353
|
+
const sel = docs.reduce((n, d) => selectedSet.has(d.external_id) ? n + 1 : n, 0);
|
|
17354
|
+
map.set(key, { all: docs.length > 0 && sel === docs.length, some: sel > 0 });
|
|
17355
|
+
}
|
|
17356
|
+
return map;
|
|
17357
|
+
}, [folderDocs, selectedSet]);
|
|
17358
|
+
const contentWidth = Math.max(0, containerWidth - SCROLL_PADDING_X);
|
|
17359
|
+
const columnCount = Math.max(
|
|
17360
|
+
1,
|
|
17361
|
+
Math.floor((contentWidth + GRID_GAP) / (tileSize + GRID_GAP)) || 1
|
|
17362
|
+
);
|
|
17363
|
+
const colWidth = contentWidth > 0 ? (contentWidth - (columnCount - 1) * GRID_GAP) / columnCount : tileSize;
|
|
17364
|
+
const tileHeight = Math.max(120, Math.round(colWidth * PAGE_ASPECT));
|
|
17365
|
+
const rowHeight = tileHeight + GRID_GAP;
|
|
17366
|
+
const rowCount = Math.ceil(entries.length / columnCount);
|
|
17367
|
+
const rowVirtualizer = useVirtualizer({
|
|
17368
|
+
count: rowCount,
|
|
17369
|
+
getScrollElement: () => scrollRef.current,
|
|
17370
|
+
estimateSize: () => rowHeight,
|
|
17371
|
+
overscan: 3
|
|
17372
|
+
});
|
|
17373
|
+
const virtualRows = rowVirtualizer.getVirtualItems();
|
|
17374
|
+
const firstRow = virtualRows.length > 0 ? virtualRows[0].index : 0;
|
|
17375
|
+
const lastRow = virtualRows.length > 0 ? virtualRows[virtualRows.length - 1].index : -1;
|
|
17376
|
+
const visibleFileIds = useMemo(() => {
|
|
17377
|
+
const ids = [];
|
|
17378
|
+
for (let r = firstRow; r <= lastRow; r++) {
|
|
17379
|
+
const start = r * columnCount;
|
|
17380
|
+
for (let c = 0; c < columnCount; c++) {
|
|
17381
|
+
const entry = entries[start + c];
|
|
17382
|
+
if (entry?.kind === "file") ids.push(entry.doc.external_id);
|
|
17383
|
+
}
|
|
17384
|
+
}
|
|
17385
|
+
return ids;
|
|
17386
|
+
}, [firstRow, lastRow, columnCount, entries]);
|
|
17387
|
+
const thumbnailsById = useDocumentThumbnails(visibleFileIds);
|
|
17388
|
+
useEffect(() => {
|
|
17389
|
+
rowVirtualizer.measure();
|
|
17390
|
+
}, [rowVirtualizer, columnCount, rowHeight, entries.length]);
|
|
17391
|
+
const isWholeWorkspaceEmpty = !isLoading && documents.length === 0 && canUpload && emptyWorkspace !== void 0;
|
|
17392
|
+
useLayoutEffect(() => {
|
|
17393
|
+
const el = scrollRef.current;
|
|
17394
|
+
if (!el) return;
|
|
17395
|
+
const apply = () => {
|
|
17396
|
+
const next = Math.round(el.clientWidth);
|
|
17397
|
+
setContainerWidth((prev) => prev === next ? prev : next);
|
|
17398
|
+
};
|
|
17399
|
+
apply();
|
|
17400
|
+
const ro = new ResizeObserver(apply);
|
|
17401
|
+
ro.observe(el);
|
|
17402
|
+
return () => ro.disconnect();
|
|
17403
|
+
}, [isWholeWorkspaceEmpty]);
|
|
17404
|
+
useEffect(() => {
|
|
17405
|
+
anchorRef.current = null;
|
|
17406
|
+
if (scrollRef.current) scrollRef.current.scrollTop = 0;
|
|
17407
|
+
}, [currentPath, search]);
|
|
17408
|
+
useEffect(() => {
|
|
17409
|
+
const MARQUEE_THRESHOLD = 5;
|
|
17410
|
+
let rafId = 0;
|
|
17411
|
+
let cur = null;
|
|
17412
|
+
const paint = () => {
|
|
17413
|
+
rafId = 0;
|
|
17414
|
+
const el = marqueeOverlayRef.current;
|
|
17415
|
+
if (!el || !cur) return;
|
|
17416
|
+
el.style.display = "block";
|
|
17417
|
+
el.style.left = `${cur.left}px`;
|
|
17418
|
+
el.style.top = `${cur.top}px`;
|
|
17419
|
+
el.style.width = `${cur.right - cur.left}px`;
|
|
17420
|
+
el.style.height = `${cur.bottom - cur.top}px`;
|
|
17421
|
+
};
|
|
17422
|
+
const onMove = (e) => {
|
|
17423
|
+
const m = marqueeRef.current;
|
|
17424
|
+
if (!m) return;
|
|
17425
|
+
if (!didMarqueeRef.current && Math.hypot(e.clientX - m.x, e.clientY - m.y) < MARQUEE_THRESHOLD)
|
|
17426
|
+
return;
|
|
17427
|
+
didMarqueeRef.current = true;
|
|
17428
|
+
const b = scrollRef.current?.getBoundingClientRect();
|
|
17429
|
+
const px = b ? Math.max(b.left, Math.min(e.clientX, b.right)) : e.clientX;
|
|
17430
|
+
const py = b ? Math.max(b.top, Math.min(e.clientY, b.bottom)) : e.clientY;
|
|
17431
|
+
cur = {
|
|
17432
|
+
left: Math.min(m.x, px),
|
|
17433
|
+
top: Math.min(m.y, py),
|
|
17434
|
+
right: Math.max(m.x, px),
|
|
17435
|
+
bottom: Math.max(m.y, py)
|
|
17436
|
+
};
|
|
17437
|
+
if (!rafId) rafId = requestAnimationFrame(paint);
|
|
17438
|
+
};
|
|
17439
|
+
const onUp = () => {
|
|
17440
|
+
const m = marqueeRef.current;
|
|
17441
|
+
if (!m) return;
|
|
17442
|
+
marqueeRef.current = null;
|
|
17443
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
17444
|
+
rafId = 0;
|
|
17445
|
+
const el = marqueeOverlayRef.current;
|
|
17446
|
+
if (el) el.style.display = "none";
|
|
17447
|
+
if (!didMarqueeRef.current || !cur) return;
|
|
17448
|
+
const box = cur;
|
|
17449
|
+
const hit = [];
|
|
17450
|
+
scrollRef.current?.querySelectorAll("[data-doc-id]").forEach((node) => {
|
|
17451
|
+
const r = node.getBoundingClientRect();
|
|
17452
|
+
if (r.left < box.right && r.right > box.left && r.top < box.bottom && r.bottom > box.top) {
|
|
17453
|
+
const id = node.getAttribute("data-doc-id");
|
|
17454
|
+
if (id) hit.push(id);
|
|
17455
|
+
}
|
|
17456
|
+
});
|
|
17457
|
+
onSelectionChange(m.additive ? Array.from(/* @__PURE__ */ new Set([...m.base, ...hit])) : hit);
|
|
17458
|
+
};
|
|
17459
|
+
window.addEventListener("pointermove", onMove);
|
|
17460
|
+
window.addEventListener("pointerup", onUp);
|
|
17461
|
+
return () => {
|
|
17462
|
+
window.removeEventListener("pointermove", onMove);
|
|
17463
|
+
window.removeEventListener("pointerup", onUp);
|
|
17464
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
17465
|
+
};
|
|
17466
|
+
}, [onSelectionChange]);
|
|
17467
|
+
const handleScrollPointerDown = useCallback((e) => {
|
|
17468
|
+
if (e.button !== 0) return;
|
|
17469
|
+
const el = e.target;
|
|
17470
|
+
if (el.closest(
|
|
17471
|
+
'[data-testid="explorer-file"],[data-testid="explorer-folder"],button,input,select,a,[role="button"]'
|
|
17472
|
+
)) {
|
|
17473
|
+
return;
|
|
17474
|
+
}
|
|
17475
|
+
const additive = e.shiftKey || e.metaKey || e.ctrlKey;
|
|
17476
|
+
marqueeRef.current = {
|
|
17477
|
+
x: e.clientX,
|
|
17478
|
+
y: e.clientY,
|
|
17479
|
+
additive,
|
|
17480
|
+
base: additive ? selectedIdsRef.current : []
|
|
17481
|
+
};
|
|
17482
|
+
didMarqueeRef.current = false;
|
|
17483
|
+
}, []);
|
|
17484
|
+
const openFolder = useCallback(
|
|
17485
|
+
(folder) => onFolderPathChange(folder.path),
|
|
17486
|
+
[onFolderPathChange]
|
|
17487
|
+
);
|
|
17488
|
+
const handleNewFolder = useCallback(() => {
|
|
17489
|
+
if (!wsId) return;
|
|
17490
|
+
const siblings = new Set(folders.map((f) => f.name.toLowerCase()));
|
|
17491
|
+
const base = "New folder";
|
|
17492
|
+
let name = base;
|
|
17493
|
+
let i = 2;
|
|
17494
|
+
while (siblings.has(name.toLowerCase())) name = `${base} ${i++}`;
|
|
17495
|
+
const path = joinFolderPath([...currentPath, name]);
|
|
17496
|
+
if (!path) return;
|
|
17497
|
+
addPendingFolder(wsId, path);
|
|
17498
|
+
setEditingFolderPath(path);
|
|
17499
|
+
}, [wsId, folders, currentPath, addPendingFolder]);
|
|
17500
|
+
const lastOrderedRef = useRef([]);
|
|
17501
|
+
useEffect(() => {
|
|
17502
|
+
const prev = lastOrderedRef.current;
|
|
17503
|
+
if (prev.length === orderedFileIds.length && prev.every((id, i) => id === orderedFileIds[i])) {
|
|
17504
|
+
return;
|
|
17505
|
+
}
|
|
17506
|
+
lastOrderedRef.current = orderedFileIds;
|
|
17507
|
+
onOrderedFilesChange?.(orderedFileIds);
|
|
17508
|
+
}, [onOrderedFilesChange, orderedFileIds]);
|
|
17509
|
+
const scrollToDocument = useCallback(
|
|
17510
|
+
(docId) => {
|
|
17511
|
+
const index = entries.findIndex((e) => e.kind === "file" && e.doc.external_id === docId);
|
|
17512
|
+
if (index < 0) return;
|
|
17513
|
+
rowVirtualizer.scrollToIndex(Math.floor(index / columnCount), { align: "center" });
|
|
17514
|
+
},
|
|
17515
|
+
[entries, columnCount, rowVirtualizer]
|
|
17516
|
+
);
|
|
17517
|
+
useImperativeHandle(ref, () => ({ triggerNewFolder: handleNewFolder, scrollToDocument }), [
|
|
17518
|
+
handleNewFolder,
|
|
17519
|
+
scrollToDocument
|
|
17520
|
+
]);
|
|
17521
|
+
const commitFolderName = useCallback(
|
|
17522
|
+
async (folder, rawName) => {
|
|
17523
|
+
setEditingFolderPath(null);
|
|
17524
|
+
const name = rawName.trim();
|
|
17525
|
+
if (!wsId || !name || name === folder.name) return;
|
|
17526
|
+
const siblings = new Set(folders.filter((f) => f !== folder).map((f) => f.name.toLowerCase()));
|
|
17527
|
+
if (siblings.has(name.toLowerCase())) {
|
|
17528
|
+
notify14(`A folder named "${name}" already exists here.`, "error");
|
|
17529
|
+
return;
|
|
17530
|
+
}
|
|
17531
|
+
const oldPath = joinFolderPath(folder.path);
|
|
17532
|
+
const newPrefix = [...folder.path.slice(0, -1), name];
|
|
17533
|
+
const newPath = joinFolderPath(newPrefix);
|
|
17534
|
+
const docs = collectFolderDocs(documents, folder.path);
|
|
17535
|
+
try {
|
|
17536
|
+
if (await renameFolder(docs, folder.path, newPrefix) > 0) {
|
|
17537
|
+
notify14(`Renamed folder to "${name}"`, "success");
|
|
17538
|
+
}
|
|
17539
|
+
} catch {
|
|
17540
|
+
notify14("Failed to rename folder. Please try again.", "error");
|
|
17541
|
+
return;
|
|
17542
|
+
}
|
|
17543
|
+
if (oldPath) removePendingFolder(wsId, oldPath);
|
|
17544
|
+
if (docs.length === 0 && newPath) addPendingFolder(wsId, newPath);
|
|
17545
|
+
},
|
|
17546
|
+
[wsId, folders, documents, renameFolder, notify14, addPendingFolder, removePendingFolder]
|
|
17547
|
+
);
|
|
17548
|
+
const cancelFolderName = useCallback(() => setEditingFolderPath(null), []);
|
|
17549
|
+
const clearSelection = useCallback(() => {
|
|
17550
|
+
if (selectedIdsRef.current.length > 0) onSelectionChange([]);
|
|
17551
|
+
anchorRef.current = null;
|
|
17552
|
+
}, [onSelectionChange]);
|
|
17553
|
+
const handleSelect = useCallback(
|
|
17554
|
+
(doc, e) => {
|
|
17555
|
+
const id = doc.external_id;
|
|
17556
|
+
if (e.shiftKey) {
|
|
17557
|
+
const from = anchorRef.current ?? id;
|
|
17558
|
+
const a = orderedFileIds.indexOf(from);
|
|
17559
|
+
const b = orderedFileIds.indexOf(id);
|
|
17560
|
+
if (a !== -1 && b !== -1) {
|
|
17561
|
+
anchorRef.current = from;
|
|
17562
|
+
const [lo, hi] = a < b ? [a, b] : [b, a];
|
|
17563
|
+
onSelectionChange(orderedFileIds.slice(lo, hi + 1));
|
|
17564
|
+
return;
|
|
17565
|
+
}
|
|
17566
|
+
}
|
|
17567
|
+
if (e.metaKey || e.ctrlKey) {
|
|
17568
|
+
onSelectionChange(
|
|
17569
|
+
selectedSet.has(id) ? selectedIds.filter((s) => s !== id) : [...selectedIds, id]
|
|
17570
|
+
);
|
|
17571
|
+
anchorRef.current = id;
|
|
17572
|
+
return;
|
|
17573
|
+
}
|
|
17574
|
+
onSelectionChange([id]);
|
|
17575
|
+
anchorRef.current = id;
|
|
17576
|
+
},
|
|
17577
|
+
[onSelectionChange, orderedFileIds, selectedIds, selectedSet]
|
|
17578
|
+
);
|
|
17579
|
+
const handleToggle = useCallback(
|
|
17580
|
+
(doc, e) => {
|
|
17581
|
+
e.stopPropagation();
|
|
17582
|
+
const id = doc.external_id;
|
|
17583
|
+
onSelectionChange(
|
|
17584
|
+
selectedSet.has(id) ? selectedIds.filter((s) => s !== id) : [...selectedIds, id]
|
|
17585
|
+
);
|
|
17586
|
+
anchorRef.current = id;
|
|
17587
|
+
},
|
|
17588
|
+
[onSelectionChange, selectedIds, selectedSet]
|
|
17589
|
+
);
|
|
17590
|
+
const handleToggleFolder = useCallback(
|
|
17591
|
+
(folder, e) => {
|
|
17592
|
+
e.stopPropagation();
|
|
17593
|
+
const ids = collectFolderDocs(documents, folder.path).map((d) => d.external_id);
|
|
17594
|
+
if (ids.length === 0) return;
|
|
17595
|
+
if (ids.every((id) => selectedSet.has(id))) {
|
|
17596
|
+
const remove = new Set(ids);
|
|
17597
|
+
onSelectionChange(selectedIds.filter((id) => !remove.has(id)));
|
|
17598
|
+
} else {
|
|
17599
|
+
onSelectionChange(Array.from(/* @__PURE__ */ new Set([...selectedIds, ...ids])));
|
|
17600
|
+
}
|
|
17601
|
+
anchorRef.current = null;
|
|
17602
|
+
},
|
|
17603
|
+
[documents, onSelectionChange, selectedIds, selectedSet]
|
|
17604
|
+
);
|
|
17605
|
+
const handleOpenFile = useCallback(
|
|
17606
|
+
(doc) => onOpenDocument(doc.external_id),
|
|
17607
|
+
[onOpenDocument]
|
|
17608
|
+
);
|
|
17609
|
+
const handleOpenInReview = useMemo(
|
|
17610
|
+
() => onOpenInReview ? (doc) => onOpenInReview(doc.external_id) : void 0,
|
|
17611
|
+
[onOpenInReview]
|
|
17612
|
+
);
|
|
17613
|
+
const startRename = useCallback((doc) => setRenamingId(doc.external_id), []);
|
|
17614
|
+
const cancelRename = useCallback(() => setRenamingId(null), []);
|
|
17615
|
+
const commitRename = useCallback(
|
|
17616
|
+
async (doc, name) => {
|
|
17617
|
+
setRenamingId(null);
|
|
17618
|
+
try {
|
|
17619
|
+
await updateDocument.mutateAsync({
|
|
17620
|
+
documents: [
|
|
17621
|
+
{ external_id: doc.external_id, doc_metadata: { ...doc.doc_metadata, title: name } }
|
|
17622
|
+
]
|
|
17623
|
+
});
|
|
17624
|
+
notify14(`Renamed to "${name}"`, "success");
|
|
17625
|
+
} catch {
|
|
17626
|
+
notify14("Failed to rename. Please try again.", "error");
|
|
17627
|
+
}
|
|
17628
|
+
},
|
|
17629
|
+
[updateDocument, notify14]
|
|
17630
|
+
);
|
|
17631
|
+
useEffect(() => {
|
|
17632
|
+
const onKey = (e) => {
|
|
17633
|
+
if (e.key !== "F2" || renamingId) return;
|
|
17634
|
+
const target = e.target;
|
|
17635
|
+
if (target && /^(input|textarea|select)$/i.test(target.tagName)) return;
|
|
17636
|
+
if (selectedIdsRef.current.length === 1) setRenamingId(selectedIdsRef.current[0]);
|
|
17637
|
+
};
|
|
17638
|
+
window.addEventListener("keydown", onKey);
|
|
17639
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
17640
|
+
}, [renamingId]);
|
|
17641
|
+
const handleDownload = useCallback(
|
|
17642
|
+
(doc) => void downloadDocument(doc),
|
|
17643
|
+
[downloadDocument]
|
|
17644
|
+
);
|
|
17645
|
+
const handleMove = useMemo(
|
|
17646
|
+
() => onMoveSelected ? (doc) => {
|
|
17647
|
+
onSelectionChange([doc.external_id]);
|
|
17648
|
+
onMoveSelected();
|
|
17649
|
+
} : void 0,
|
|
17650
|
+
[onSelectionChange, onMoveSelected]
|
|
17651
|
+
);
|
|
17652
|
+
const handleDelete = useCallback(
|
|
17653
|
+
async (doc) => {
|
|
17654
|
+
const name = getExplorerDisplayName(doc);
|
|
17655
|
+
const confirmed = await confirm({
|
|
17656
|
+
message: `Delete "${name}"? ${RECENTLY_DELETED_HINT}`,
|
|
17657
|
+
title: "Delete file"
|
|
17658
|
+
});
|
|
17659
|
+
if (!confirmed) return;
|
|
17660
|
+
try {
|
|
17661
|
+
await deleteDocuments.mutateAsync([doc.external_id]);
|
|
17662
|
+
onSelectionChange(selectedIdsRef.current.filter((id) => id !== doc.external_id));
|
|
17663
|
+
notify14(`Deleted "${name}"`, "success");
|
|
17664
|
+
} catch {
|
|
17665
|
+
notify14("Failed to delete. Please try again.", "error");
|
|
17666
|
+
}
|
|
17667
|
+
},
|
|
17668
|
+
[confirm, deleteDocuments, onSelectionChange, notify14]
|
|
17669
|
+
);
|
|
17670
|
+
const startFolderRename = useCallback(
|
|
17671
|
+
(folder) => setEditingFolderPath(folder.path.join("/")),
|
|
17672
|
+
[]
|
|
17673
|
+
);
|
|
17674
|
+
const handleFolderDownload = useCallback(
|
|
17675
|
+
(folder) => {
|
|
17676
|
+
const docs = collectFolderDocs(documents, folder.path);
|
|
17677
|
+
if (docs.length > 0) void downloadDocuments(docs);
|
|
17678
|
+
},
|
|
17679
|
+
[documents, downloadDocuments]
|
|
17680
|
+
);
|
|
17681
|
+
const handleFolderMove = useMemo(() => {
|
|
17682
|
+
if (onFolderMove) return onFolderMove;
|
|
17683
|
+
if (!onMoveSelected) return void 0;
|
|
17684
|
+
return (folder) => {
|
|
17685
|
+
const ids = collectFolderDocs(documents, folder.path).map((d) => d.external_id);
|
|
17686
|
+
if (ids.length === 0) return;
|
|
17687
|
+
onSelectionChange(ids);
|
|
17688
|
+
onMoveSelected();
|
|
17689
|
+
};
|
|
17690
|
+
}, [documents, onFolderMove, onMoveSelected, onSelectionChange]);
|
|
17691
|
+
const handleFolderDelete = useCallback(
|
|
17692
|
+
async (folder) => {
|
|
17693
|
+
const docs = collectFolderDocs(documents, folder.path);
|
|
17694
|
+
if (docs.length === 0) {
|
|
17695
|
+
const path = joinFolderPath(folder.path);
|
|
17696
|
+
if (wsId && path) removePendingFolder(wsId, path);
|
|
17697
|
+
return;
|
|
17698
|
+
}
|
|
17699
|
+
const confirmed = await confirm({
|
|
17700
|
+
message: `Delete folder "${folder.name}" and its ${docs.length} item${docs.length === 1 ? "" : "s"}? ${RECENTLY_DELETED_HINT_PLURAL}`,
|
|
17701
|
+
title: "Delete folder"
|
|
17702
|
+
});
|
|
17703
|
+
if (!confirmed) return;
|
|
17704
|
+
try {
|
|
17705
|
+
const ids = docs.map((d) => d.external_id);
|
|
17706
|
+
await deleteDocuments.mutateAsync(ids);
|
|
17707
|
+
const removed = new Set(ids);
|
|
17708
|
+
onSelectionChange(selectedIdsRef.current.filter((id) => !removed.has(id)));
|
|
17709
|
+
notify14(`Deleted folder "${folder.name}"`, "success");
|
|
17710
|
+
} catch {
|
|
17711
|
+
notify14("Failed to delete folder. Please try again.", "error");
|
|
17712
|
+
}
|
|
17713
|
+
},
|
|
17714
|
+
[confirm, documents, deleteDocuments, onSelectionChange, removePendingFolder, wsId, notify14]
|
|
17715
|
+
);
|
|
17716
|
+
const canDeleteFolder = useCallback(
|
|
17717
|
+
(folder) => canUpload && collectFolderDocs(documents, folder.path).every(
|
|
17718
|
+
(doc) => canDeleteDocument(doc, userId, role)
|
|
17719
|
+
),
|
|
17720
|
+
[canUpload, documents, userId, role]
|
|
17721
|
+
);
|
|
17722
|
+
const handleBackgroundClick = useCallback(
|
|
17723
|
+
(e) => {
|
|
17724
|
+
if (didMarqueeRef.current) {
|
|
17725
|
+
didMarqueeRef.current = false;
|
|
17726
|
+
return;
|
|
17727
|
+
}
|
|
17728
|
+
if (!e.target.closest('[data-testid^="explorer-"]')) clearSelection();
|
|
17729
|
+
},
|
|
17730
|
+
[clearSelection]
|
|
17731
|
+
);
|
|
17732
|
+
const moveDocs = useCallback(
|
|
17733
|
+
async (ids, folder) => {
|
|
17734
|
+
if (ids.length === 0) return;
|
|
17735
|
+
try {
|
|
17736
|
+
await updateDocument.mutateAsync({
|
|
17737
|
+
documents: ids.map((external_id) => ({ external_id, folder }))
|
|
17738
|
+
});
|
|
17739
|
+
clearSelection();
|
|
17740
|
+
notify14(
|
|
17741
|
+
`Moved ${ids.length} item${ids.length === 1 ? "" : "s"} to "${folder ?? "Home"}"`,
|
|
17742
|
+
"success"
|
|
17743
|
+
);
|
|
17744
|
+
} catch {
|
|
17745
|
+
notify14("Failed to move. Please try again.", "error");
|
|
17746
|
+
}
|
|
17747
|
+
},
|
|
17748
|
+
[updateDocument, clearSelection, notify14]
|
|
17749
|
+
);
|
|
17750
|
+
const sensors = useSensors(
|
|
17751
|
+
useSensor(MouseSensor, { activationConstraint: { distance: 6 } }),
|
|
17752
|
+
useSensor(TouchSensor, { activationConstraint: { delay: 150, tolerance: 8 } })
|
|
17753
|
+
);
|
|
17754
|
+
const collisionDetection = useCallback((args) => {
|
|
17755
|
+
const hits = pointerWithin(args);
|
|
17756
|
+
return hits.length > 0 ? hits : rectIntersection(args);
|
|
17757
|
+
}, []);
|
|
17758
|
+
const handleDragStart = useCallback(
|
|
17759
|
+
(event) => {
|
|
17760
|
+
const docId = event.active.data.current?.docId;
|
|
17761
|
+
if (!docId) return;
|
|
17762
|
+
setActiveDragId(docId);
|
|
17763
|
+
if (!selectedIdsRef.current.includes(docId)) {
|
|
17764
|
+
onSelectionChange([docId]);
|
|
17765
|
+
dragIdsRef.current = [docId];
|
|
17766
|
+
} else {
|
|
17767
|
+
dragIdsRef.current = selectedIdsRef.current;
|
|
17768
|
+
}
|
|
17769
|
+
},
|
|
17770
|
+
[onSelectionChange]
|
|
17771
|
+
);
|
|
17772
|
+
const handleDragEnd = useCallback(
|
|
17773
|
+
(event) => {
|
|
17774
|
+
setActiveDragId(null);
|
|
17775
|
+
const data = event.over?.data.current;
|
|
17776
|
+
if (data?.type === "folder" && data.path) {
|
|
17777
|
+
const targetFolder = joinFolderPath(data.path);
|
|
17778
|
+
const moving = dragIdsRef.current.filter((id) => {
|
|
17779
|
+
const doc = documents.find((d) => d.external_id === id);
|
|
17780
|
+
return doc && (doc.folder ?? null) !== targetFolder;
|
|
17781
|
+
});
|
|
17782
|
+
void moveDocs(moving, targetFolder);
|
|
17783
|
+
}
|
|
17784
|
+
},
|
|
17785
|
+
[documents, moveDocs]
|
|
17786
|
+
);
|
|
17787
|
+
const handleNativeDragOver = useCallback(
|
|
17788
|
+
(e) => {
|
|
17789
|
+
if (!canUpload) return;
|
|
17790
|
+
if (e.dataTransfer.types.includes("Files")) {
|
|
17791
|
+
e.preventDefault();
|
|
17792
|
+
e.dataTransfer.dropEffect = "copy";
|
|
17793
|
+
}
|
|
17794
|
+
},
|
|
17795
|
+
[canUpload]
|
|
17796
|
+
);
|
|
17797
|
+
const handleNativeDragEnter = useCallback(
|
|
17798
|
+
(e) => {
|
|
17799
|
+
if (!canUpload || !e.dataTransfer.types.includes("Files")) return;
|
|
17800
|
+
fileDragDepth.current += 1;
|
|
17801
|
+
setIsFileDragOver(true);
|
|
17802
|
+
},
|
|
17803
|
+
[canUpload]
|
|
17804
|
+
);
|
|
17805
|
+
const handleNativeDragLeave = useCallback(() => {
|
|
17806
|
+
fileDragDepth.current = Math.max(0, fileDragDepth.current - 1);
|
|
17807
|
+
if (fileDragDepth.current === 0) setIsFileDragOver(false);
|
|
17808
|
+
}, []);
|
|
17809
|
+
const handleNativeDrop = useCallback(
|
|
17810
|
+
(e) => {
|
|
17811
|
+
fileDragDepth.current = 0;
|
|
17812
|
+
setIsFileDragOver(false);
|
|
17813
|
+
if (!canUpload) return;
|
|
17814
|
+
const dropped = Array.from(e.dataTransfer.files ?? []);
|
|
17815
|
+
if (dropped.length > 0) {
|
|
17816
|
+
e.preventDefault();
|
|
17817
|
+
onUploadFiles(dropped, joinFolderPath(currentPath));
|
|
17818
|
+
}
|
|
17819
|
+
},
|
|
17820
|
+
[canUpload, currentPath, onUploadFiles]
|
|
17821
|
+
);
|
|
17822
|
+
const activeDoc = activeDragId ? documents.find((d) => d.external_id === activeDragId) : null;
|
|
17823
|
+
const dragCount = dragIdsRef.current.length || 1;
|
|
17824
|
+
if (isWholeWorkspaceEmpty) return /* @__PURE__ */ jsx(Fragment, { children: emptyWorkspace });
|
|
17825
|
+
return /* @__PURE__ */ jsxs(
|
|
17826
|
+
DndContext,
|
|
17827
|
+
{
|
|
17828
|
+
sensors,
|
|
17829
|
+
collisionDetection,
|
|
17830
|
+
onDragStart: handleDragStart,
|
|
17831
|
+
onDragEnd: handleDragEnd,
|
|
17832
|
+
onDragCancel: () => setActiveDragId(null),
|
|
17833
|
+
children: [
|
|
17834
|
+
/* @__PURE__ */ jsx(ExplorerFrame, { "data-testid": "documents-explorer", children: /* @__PURE__ */ jsxs(ExplorerStage, { children: [
|
|
17835
|
+
/* @__PURE__ */ jsx(
|
|
17836
|
+
ExplorerScroller,
|
|
17837
|
+
{
|
|
17838
|
+
ref: scrollRef,
|
|
17839
|
+
onClick: handleBackgroundClick,
|
|
17840
|
+
onPointerDown: handleScrollPointerDown,
|
|
17841
|
+
onDragEnter: handleNativeDragEnter,
|
|
17842
|
+
onDragOver: handleNativeDragOver,
|
|
17843
|
+
onDragLeave: handleNativeDragLeave,
|
|
17844
|
+
onDrop: handleNativeDrop,
|
|
17845
|
+
"data-testid": "explorer-scroll",
|
|
17846
|
+
children: isLoading && documents.length === 0 ? /* @__PURE__ */ jsx(ExplorerLoadingTiles, {}) : entries.length === 0 ? /* @__PURE__ */ jsx(
|
|
17847
|
+
ExplorerEmpty,
|
|
17848
|
+
{
|
|
17849
|
+
title: isSearching ? `No files match "${search.trim()}"` : "This folder is empty",
|
|
17850
|
+
description: isSearching ? "Try a different search." : canUpload ? "Drop files here or use Upload to add documents." : "No documents here yet."
|
|
17851
|
+
}
|
|
17852
|
+
) : containerWidth === 0 ? (
|
|
17853
|
+
// Not measured yet: rendering now would flash one stretched column.
|
|
17854
|
+
/* @__PURE__ */ jsx(ExplorerLoadingTiles, {})
|
|
17855
|
+
) : /* @__PURE__ */ jsx(
|
|
17856
|
+
"div",
|
|
17857
|
+
{
|
|
17858
|
+
style: {
|
|
17859
|
+
height: rowVirtualizer.getTotalSize(),
|
|
17860
|
+
position: "relative",
|
|
17861
|
+
width: "100%"
|
|
17862
|
+
},
|
|
17863
|
+
children: virtualRows.map((virtualRow) => {
|
|
17864
|
+
const start = virtualRow.index * columnCount;
|
|
17865
|
+
const rowEntries = entries.slice(start, start + columnCount);
|
|
17866
|
+
return /* @__PURE__ */ jsx(
|
|
17867
|
+
"div",
|
|
17868
|
+
{
|
|
17869
|
+
style: {
|
|
17870
|
+
position: "absolute",
|
|
17871
|
+
top: 0,
|
|
17872
|
+
left: 0,
|
|
17873
|
+
width: "100%",
|
|
17874
|
+
height: tileHeight,
|
|
17875
|
+
transform: `translateY(${virtualRow.start}px)`,
|
|
17876
|
+
display: "grid",
|
|
17877
|
+
gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`,
|
|
17878
|
+
columnGap: GRID_GAP
|
|
17879
|
+
},
|
|
17880
|
+
children: rowEntries.map((entry) => {
|
|
17881
|
+
if (entry.kind === "folder") {
|
|
17882
|
+
const key = entry.path.join("/");
|
|
17883
|
+
return /* @__PURE__ */ jsx(
|
|
17884
|
+
FolderTile,
|
|
17885
|
+
{
|
|
17886
|
+
folder: entry,
|
|
17887
|
+
density,
|
|
17888
|
+
editing: editingFolderPath === key,
|
|
17889
|
+
selected: folderSelection.get(key)?.all,
|
|
17890
|
+
partiallySelected: folderSelection.get(key)?.some,
|
|
17891
|
+
documents: folderDocs.get(key) ?? NO_DOCUMENTS,
|
|
17892
|
+
onOpen: openFolder,
|
|
17893
|
+
onToggleSelect: handleToggleFolder,
|
|
17894
|
+
onCommitName: commitFolderName,
|
|
17895
|
+
onCancelName: cancelFolderName,
|
|
17896
|
+
onStartRename: canUpload ? startFolderRename : void 0,
|
|
17897
|
+
onDownload: handleFolderDownload,
|
|
17898
|
+
onMove: canUpload ? handleFolderMove : void 0,
|
|
17899
|
+
onDelete: canDeleteFolder(entry) ? handleFolderDelete : void 0
|
|
17900
|
+
},
|
|
17901
|
+
`folder:${key}`
|
|
17902
|
+
);
|
|
17903
|
+
}
|
|
17904
|
+
const doc = entry.doc;
|
|
17905
|
+
return /* @__PURE__ */ jsx(
|
|
17906
|
+
FileTile,
|
|
17907
|
+
{
|
|
17908
|
+
doc,
|
|
17909
|
+
density,
|
|
17910
|
+
selected: selectedSet.has(doc.external_id),
|
|
17911
|
+
dragging: activeDragId === doc.external_id,
|
|
17912
|
+
dndActive: activeDragId !== null,
|
|
17913
|
+
renaming: renamingId === doc.external_id,
|
|
17914
|
+
canDelete: canDeleteDocument(doc, userId, role),
|
|
17915
|
+
thumbnailUrl: thumbnailsById.get(doc.external_id),
|
|
17916
|
+
modifiedBy: userById.get(
|
|
17917
|
+
doc.updated_by_ext_id ?? doc.created_by_ext_id
|
|
17918
|
+
),
|
|
17919
|
+
tagNameById,
|
|
17920
|
+
onSelect: handleSelect,
|
|
17921
|
+
onToggle: handleToggle,
|
|
17922
|
+
onOpen: handleOpenFile,
|
|
17923
|
+
onReview: handleOpenInReview,
|
|
17924
|
+
onStartRename: startRename,
|
|
17925
|
+
onCommitRename: commitRename,
|
|
17926
|
+
onCancelRename: cancelRename,
|
|
17927
|
+
onDownload: handleDownload,
|
|
17928
|
+
onMove: canUpload ? handleMove : void 0,
|
|
17929
|
+
onDelete: handleDelete
|
|
17930
|
+
},
|
|
17931
|
+
doc.external_id
|
|
17932
|
+
);
|
|
17933
|
+
})
|
|
17934
|
+
},
|
|
17935
|
+
virtualRow.key
|
|
17936
|
+
);
|
|
17937
|
+
})
|
|
17938
|
+
}
|
|
17939
|
+
)
|
|
17940
|
+
}
|
|
17941
|
+
),
|
|
17942
|
+
/* @__PURE__ */ jsx(ExplorerMarquee, { ref: marqueeOverlayRef, "data-testid": "explorer-marquee" }),
|
|
17943
|
+
isFileDragOver && /* @__PURE__ */ jsx(
|
|
17944
|
+
ExplorerDropOverlay,
|
|
17945
|
+
{
|
|
17946
|
+
label: `Drop to upload${currentPath.length > 0 ? ` to "${currentPath[currentPath.length - 1]}"` : ""}`
|
|
17947
|
+
}
|
|
17948
|
+
)
|
|
17949
|
+
] }) }),
|
|
17950
|
+
/* @__PURE__ */ jsx(DragOverlay, { dropAnimation: null, children: activeDoc ? /* @__PURE__ */ jsx(DragPreview, { doc: activeDoc, count: dragCount }) : null })
|
|
17951
|
+
]
|
|
17952
|
+
}
|
|
17953
|
+
);
|
|
17954
|
+
});
|
|
17955
|
+
function DragPreview({ doc, count }) {
|
|
17956
|
+
const Icon = getFileTypeIcon(doc.file_name, { mime: doc.file_type });
|
|
17957
|
+
return /* @__PURE__ */ jsx(
|
|
17958
|
+
ExplorerDragPreview,
|
|
17959
|
+
{
|
|
17960
|
+
icon: /* @__PURE__ */ jsx(Icon, { size: 20 }),
|
|
17961
|
+
title: getDocumentTitle(doc, doc.file_name ?? "Untitled"),
|
|
17962
|
+
count
|
|
17963
|
+
}
|
|
17964
|
+
);
|
|
17965
|
+
}
|
|
16254
17966
|
function ActiveChat({ agentId, workspaceId }) {
|
|
16255
17967
|
const chat = useArbiChat({
|
|
16256
17968
|
agentId,
|
|
@@ -16318,6 +18030,6 @@ function ConnectedAgentChatPanel({
|
|
|
16318
18030
|
] });
|
|
16319
18031
|
}
|
|
16320
18032
|
|
|
16321
|
-
export { AddAddonDialog, ArbiAppProvider, BACKFILL_OPTIONS, BillingProjectEntity,
|
|
18033
|
+
export { AddAddonDialog, ArbiAppProvider, BACKFILL_OPTIONS, BillingProjectEntity, ChangePlanDialog, ConnectedAccountSettings, ConnectedAgentChatPanel, ConnectedAgentCreator, ConnectedAppsSettings, ConnectedCalendar, ConnectedDangerZone, ConnectedDriveSyncDialog, ConnectedFactsPage, ConnectedFilesExplorer, ConnectedIdentityVerification, ConnectedLogoutDialog, ConnectedMailAccounts, ConnectedMailComposer, ConnectedMailInviteCard, ConnectedMailMessage, ConnectedMailSettings, ConnectedMailSetup, ConnectedMailbox, ConnectedMailboxSyncDialog, ConnectedNotificationCenter, ConnectedNotificationPreferences, ConnectedPasswordChangeDialog, ConnectedPremiumModels, ConnectedPrivacySettings, ConnectedProfileCard, ConnectedProjectsPanel, ConnectedPublicBooking, ConnectedRequireVerified, ConnectedSearchPage, ConnectedSecuritySection, ConnectedSessionsRow, ConnectedSettingsPage, ConnectedSkillUpload, ConnectedSkillsLibrary, ConnectedTagTable, ConnectedTagsPage, ConnectedTaskAccessRequest, ConnectedTaskActivityDialog, ConnectedThreadFollow, ConnectedUpcomingEvents, ConnectedWorkflowHistory, ConnectedWorkflowRunner, ConnectedWorkspaceActivity, ConnectedWorkspaceDashboard, ConnectedWorkspaceList, ConnectedWorkspaceMembers, ConnectedWorkspaceNotificationMenu, ConnectedWorkspaceSettings, DEFAULT_CONNECTED_SEARCH_PAGE_LABELS, DEFAULT_CONNECTED_SKILL_UPLOAD_LABELS, DEFAULT_CONNECTED_WORKFLOW_RUNNER_LABELS, DEFAULT_SEARCH_ROW_LABELS, DEFAULT_SETTINGS_SECTIONS, DEFAULT_TIMELINE_LABEL_WIDTH, DEFAULT_WORKSPACE_LIST_LABELS, DocumentSyncGlyph, FACTS_GRID_ID, FileTooltipContent, FilesSupportProvider, InvoicesModal, NewProjectFlow, NotificationCenter, NotificationEvent, PROJECTS_BASE_TAB_ID, ProjectDetail, ProjectsTable, REFERRAL_OFFER, SETTINGS_SECTION_IDS, SessionStatusIcon, SharedProjects, StreamedArtifact, TILE_DEFAULT, TILE_MAX, TILE_MIN, WORKSPACES_TAB_ID, WorkflowRunThread, accountSettingsSection, bookingCancelLink, buildNotificationCenterItems, chatSettingsSection, connectedAppsSettingsSection, countNotificationTypes, defaultUnmetPasswordRequirement, filterNotificationCenterItems, formatLastSync, isActiveDocumentStatus, mailSettingsSection, openMeetingLink, privacySettingsSection, renderSessionStatusIcon, requestCalendarComposer, requestCalendarEvent, requestProjectBillingFocus, resolveMailbox, startMenuItem, toWorkspaceSummary, useAutoRefreshPendingProjects, useCalendarViewStore, useConnectedDocxNotifications, useConnectedShellWorkspaces, useConnectedWorkNotifications, useConnectedWorkspaceList, useConnectedWorkspaceSettings, useFilesStore, useFilesSupport, useOfflineDocumentIds, useOpenBillingPortal, usePendingFolders, useProjectBillingFocus, useReferralReward, useViewingDocumentId, windowLabel, workflowRunView };
|
|
16322
18034
|
//# sourceMappingURL=index.js.map
|
|
16323
18035
|
//# sourceMappingURL=index.js.map
|