@q9labsai/chalk-react 5.0.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +84 -0
- package/dist/bindings/context.d.ts +0 -1
- package/dist/bindings/hooks.d.ts +0 -1
- package/dist/{chunk-NPOO7BOX.js → chunk-QDEKMRH2.js} +6 -5
- package/dist/{chunk-XN2E352E.js → chunk-XOHNIYSR.js} +1768 -129
- package/dist/{chunk-WRAEH24P.js → chunk-ZFWIMKUC.js} +4 -2
- package/dist/components/admission-panel/AdmissionPanel.d.ts +0 -1
- package/dist/components/atomic/AudioIndicator.d.ts +0 -1
- package/dist/components/atomic/Avatar.d.ts +0 -1
- package/dist/components/atomic/HandRaiseIndicator.d.ts +0 -1
- package/dist/components/atomic/NameTag.d.ts +0 -1
- package/dist/components/atomic/ReactionBubble.d.ts +0 -1
- package/dist/components/atomic/StatusBadge.d.ts +0 -1
- package/dist/components/atomic/VolumeSlider.d.ts +0 -1
- package/dist/components/atomic/Waveform.d.ts +0 -1
- package/dist/components/atomic/index.d.ts +0 -1
- package/dist/components/audio-output/AudioOutput.d.ts +0 -1
- package/dist/components/board/Board.d.ts +22 -0
- package/dist/components/board/BoardCursors.d.ts +21 -0
- package/dist/components/board/BoardIcon.d.ts +46 -0
- package/dist/components/board/BoardIsland.d.ts +21 -0
- package/dist/components/board/BoardMenu.d.ts +15 -0
- package/dist/components/board/BoardStyles.d.ts +2 -0
- package/dist/components/board/board-engine.d.ts +31 -0
- package/dist/components/board/board-history.d.ts +7 -0
- package/dist/components/board/board-model.d.ts +86 -0
- package/dist/components/board/board-toasts.d.ts +6 -0
- package/dist/components/board/board-view-versions.d.ts +7 -0
- package/dist/components/call-card/CallCard.d.ts +0 -1
- package/dist/components/call-card/CallCardStyles.d.ts +0 -1
- package/dist/components/call-limbo/CallLimbo.d.ts +3 -2
- package/dist/components/call-limbo/CallReady.d.ts +0 -1
- package/dist/components/call-limbo/qr-code.d.ts +0 -1
- package/dist/components/call-limbo/useCallLink.d.ts +0 -1
- package/dist/components/chalk/Chalk.d.ts +0 -1
- package/dist/components/chalk/StatusSurface.d.ts +3 -2
- package/dist/components/chalk-ui/ChalkAlert.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkBackdrop.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkBadge.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkButton.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkCopyButton.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkDialogPanel.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkDivider.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkEmptyState.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkIconButton.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkInput.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkMenu.d.ts +2 -2
- package/dist/components/chalk-ui/ChalkPanel.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkPopover.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkSegmentedControl.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkSheet.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkSpinner.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkTextarea.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkToast.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkToggle.d.ts +0 -1
- package/dist/components/chalk-ui/ChalkTooltip.d.ts +0 -1
- package/dist/components/chalk-ui/common.d.ts +0 -1
- package/dist/components/chalk-ui/index.d.ts +0 -1
- package/dist/components/composite/ChatPanel.d.ts +0 -1
- package/dist/components/composite/CommandErrorAlert.d.ts +0 -1
- package/dist/components/composite/MessageBubble.d.ts +0 -1
- package/dist/components/composite/ReactionPicker.d.ts +0 -1
- package/dist/components/composite/ReactionTray.d.ts +0 -1
- package/dist/components/composite/ReactionsOverlay.d.ts +0 -1
- package/dist/components/composite/ScreenShareView.d.ts +0 -1
- package/dist/components/composite/chat-file-upload.d.ts +0 -1
- package/dist/components/composite/chat-panel-model.d.ts +0 -1
- package/dist/components/composite/chat-types.d.ts +0 -1
- package/dist/components/composite/chat-upload-file.d.ts +0 -1
- package/dist/components/composite/reaction-float.d.ts +0 -1
- package/dist/components/composite/use-chat-attachment-previews.d.ts +0 -1
- package/dist/components/control-bar/CallLinkIcon.d.ts +0 -1
- package/dist/components/control-bar/ControlBar.d.ts +0 -1
- package/dist/components/control-bar/DockLayout.d.ts +0 -1
- package/dist/components/control-bar/LeaveMenu.d.ts +0 -1
- package/dist/components/control-bar/ShareControl.d.ts +3 -2
- package/dist/components/control-bar/ShellIcon.d.ts +0 -1
- package/dist/components/control-bar/ShellStyles.d.ts +0 -1
- package/dist/components/control-bar/command-failure.d.ts +0 -1
- package/dist/components/control-bar/control-bar-behavior.d.ts +4 -2
- package/dist/components/control-bar/control-bar-button.d.ts +0 -1
- package/dist/components/control-bar/next-camera.d.ts +0 -1
- package/dist/components/device-popover/DevicePopover.d.ts +4 -2
- package/dist/components/index.d.ts +0 -1
- package/dist/components/index.js +3 -3
- package/dist/components/lazy.d.ts +0 -1
- package/dist/components/logo/Logo.d.ts +0 -1
- package/dist/components/media-request-dialog/MediaRequestDialog.d.ts +0 -1
- package/dist/components/media-request-dialog/media-request-dialog-state.d.ts +0 -1
- package/dist/components/offline-actions/OfflineActions.d.ts +0 -1
- package/dist/components/participant-grid/ParticipantGrid.d.ts +0 -1
- package/dist/components/participant-tile/ParticipantTile.d.ts +4 -2
- package/dist/components/participant-tile/TileShell.d.ts +3 -2
- package/dist/components/participant-tile/useVideoTrack.d.ts +0 -1
- package/dist/components/participants-panel/ParticipantsPanel.d.ts +0 -1
- package/dist/components/participants-panel/index.d.ts +0 -1
- package/dist/components/participants-panel/participant-options-menu-contract.d.ts +0 -1
- package/dist/components/participants-panel/participant-options-menu.d.ts +0 -1
- package/dist/components/participants-panel/participant-row.d.ts +0 -1
- package/dist/components/participants-panel/participant-volume-context.d.ts +0 -1
- package/dist/components/reconnecting-overlay/ReconnectingOverlay.d.ts +0 -1
- package/dist/components/recording-history/RecordingHistoryPanel.d.ts +0 -1
- package/dist/components/space-header/CallDetails.d.ts +3 -3
- package/dist/components/space-header/LayoutMenu.d.ts +0 -1
- package/dist/components/space-header/SpaceHeader.d.ts +0 -1
- package/dist/components/space-header/call-diagnostics.d.ts +0 -1
- package/dist/components/space-header/share-qr.d.ts +0 -1
- package/dist/components/space-header/space-header-contract.d.ts +0 -1
- package/dist/components/space-header/useCallShortcuts.d.ts +0 -1
- package/dist/components/space-view/RecordingSpaceView.d.ts +0 -1
- package/dist/components/space-view/SpacePresentation.d.ts +0 -1
- package/dist/components/space-view/SpaceView.d.ts +3 -1
- package/dist/components/space-view/useDrawerPresence.d.ts +0 -1
- package/dist/components/stage/SpaceStage.d.ts +3 -2
- package/dist/components/stage/Stage.d.ts +5 -2
- package/dist/components/stage/StageContentTile.d.ts +0 -1
- package/dist/components/stage/StageIcon.d.ts +0 -1
- package/dist/components/stage/StageMotion.d.ts +0 -1
- package/dist/components/stage/stage-items.d.ts +0 -1
- package/dist/components/stage/stage-layout.d.ts +2 -1
- package/dist/components/stage/useStageDimensions.d.ts +0 -1
- package/dist/components/toast-stack/CallEventIcon.d.ts +0 -1
- package/dist/components/toast-stack/ToastStack.d.ts +0 -1
- package/dist/components/toast-stack/call-events.d.ts +0 -1
- package/dist/components/toast-stack/useCallToasts.d.ts +0 -1
- package/dist/components/ui/index.d.ts +0 -1
- package/dist/components/whiteboard-view/RecordingWhiteboardView.d.ts +0 -1
- package/dist/components/whiteboard-view/WhiteboardView.d.ts +0 -1
- package/dist/components/whiteboard-view/recording-whiteboard-state.d.ts +0 -1
- package/dist/headless.d.ts +0 -1
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +3 -3
- package/dist/internal/call-motion.d.ts +0 -1
- package/dist/internal/sound-cues.d.ts +0 -1
- package/dist/internal/useEpisodeDuration.d.ts +0 -1
- package/dist/internal/useHaptics.d.ts +0 -1
- package/dist/internal/useMediaQuery.d.ts +0 -1
- package/dist/internal/useMirrorSetting.d.ts +0 -1
- package/dist/internal/useSoundCues.d.ts +0 -1
- package/dist/internal/useSoundsSetting.d.ts +0 -1
- package/dist/internal/useWhiteboardSceneSubscription.d.ts +0 -1
- package/dist/public-surface.contract.d.ts +0 -1
- package/dist/selectors/space-selectors.d.ts +0 -1
- package/dist/test-support/index.d.ts +0 -1
- package/dist/test-support/index.js +25 -3
- package/dist/test-support/preview-client.d.ts +0 -1
- package/dist/test-support/preview-devices.d.ts +0 -1
- package/dist/test-support/preview-fixtures.d.ts +0 -1
- package/dist/theme.d.ts +0 -1
- package/dist/utils/cn.d.ts +0 -1
- package/dist/utils/colorGenerator.d.ts +0 -1
- package/dist/utils/icons.d.ts +0 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/theme.d.ts +0 -1
- package/dist/whiteboard/wire-adapters.d.ts +0 -1
- package/package.json +6 -5
- package/dist/bindings/context.d.ts.map +0 -1
- package/dist/bindings/hooks.d.ts.map +0 -1
- package/dist/components/admission-panel/AdmissionPanel.d.ts.map +0 -1
- package/dist/components/atomic/AudioIndicator.d.ts.map +0 -1
- package/dist/components/atomic/Avatar.d.ts.map +0 -1
- package/dist/components/atomic/HandRaiseIndicator.d.ts.map +0 -1
- package/dist/components/atomic/NameTag.d.ts.map +0 -1
- package/dist/components/atomic/ReactionBubble.d.ts.map +0 -1
- package/dist/components/atomic/StatusBadge.d.ts.map +0 -1
- package/dist/components/atomic/VolumeSlider.d.ts.map +0 -1
- package/dist/components/atomic/Waveform.d.ts.map +0 -1
- package/dist/components/atomic/index.d.ts.map +0 -1
- package/dist/components/audio-output/AudioOutput.d.ts.map +0 -1
- package/dist/components/call-card/CallCard.d.ts.map +0 -1
- package/dist/components/call-card/CallCardStyles.d.ts.map +0 -1
- package/dist/components/call-limbo/CallLimbo.d.ts.map +0 -1
- package/dist/components/call-limbo/CallReady.d.ts.map +0 -1
- package/dist/components/call-limbo/qr-code.d.ts.map +0 -1
- package/dist/components/call-limbo/useCallLink.d.ts.map +0 -1
- package/dist/components/chalk/Chalk.d.ts.map +0 -1
- package/dist/components/chalk/StatusSurface.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkAlert.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkBackdrop.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkBadge.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkButton.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkCopyButton.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkDialogPanel.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkDivider.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkEmptyState.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkIconButton.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkInput.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkMenu.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkPanel.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkPopover.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkSegmentedControl.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkSheet.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkSpinner.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkTextarea.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkToast.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkToggle.d.ts.map +0 -1
- package/dist/components/chalk-ui/ChalkTooltip.d.ts.map +0 -1
- package/dist/components/chalk-ui/common.d.ts.map +0 -1
- package/dist/components/chalk-ui/index.d.ts.map +0 -1
- package/dist/components/composite/ChatPanel.d.ts.map +0 -1
- package/dist/components/composite/CommandErrorAlert.d.ts.map +0 -1
- package/dist/components/composite/MessageBubble.d.ts.map +0 -1
- package/dist/components/composite/ReactionPicker.d.ts.map +0 -1
- package/dist/components/composite/ReactionTray.d.ts.map +0 -1
- package/dist/components/composite/ReactionsOverlay.d.ts.map +0 -1
- package/dist/components/composite/ScreenShareView.d.ts.map +0 -1
- package/dist/components/composite/chat-file-upload.d.ts.map +0 -1
- package/dist/components/composite/chat-panel-model.d.ts.map +0 -1
- package/dist/components/composite/chat-types.d.ts.map +0 -1
- package/dist/components/composite/chat-upload-file.d.ts.map +0 -1
- package/dist/components/composite/reaction-float.d.ts.map +0 -1
- package/dist/components/composite/use-chat-attachment-previews.d.ts.map +0 -1
- package/dist/components/control-bar/CallLinkIcon.d.ts.map +0 -1
- package/dist/components/control-bar/ControlBar.d.ts.map +0 -1
- package/dist/components/control-bar/DockLayout.d.ts.map +0 -1
- package/dist/components/control-bar/LeaveMenu.d.ts.map +0 -1
- package/dist/components/control-bar/ShareControl.d.ts.map +0 -1
- package/dist/components/control-bar/ShellIcon.d.ts.map +0 -1
- package/dist/components/control-bar/ShellStyles.d.ts.map +0 -1
- package/dist/components/control-bar/command-failure.d.ts.map +0 -1
- package/dist/components/control-bar/control-bar-behavior.d.ts.map +0 -1
- package/dist/components/control-bar/control-bar-button.d.ts.map +0 -1
- package/dist/components/control-bar/next-camera.d.ts.map +0 -1
- package/dist/components/device-popover/DevicePopover.d.ts.map +0 -1
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/lazy.d.ts.map +0 -1
- package/dist/components/logo/Logo.d.ts.map +0 -1
- package/dist/components/media-request-dialog/MediaRequestDialog.d.ts.map +0 -1
- package/dist/components/media-request-dialog/media-request-dialog-state.d.ts.map +0 -1
- package/dist/components/offline-actions/OfflineActions.d.ts.map +0 -1
- package/dist/components/participant-grid/ParticipantGrid.d.ts.map +0 -1
- package/dist/components/participant-tile/ParticipantTile.d.ts.map +0 -1
- package/dist/components/participant-tile/TileShell.d.ts.map +0 -1
- package/dist/components/participant-tile/useVideoTrack.d.ts.map +0 -1
- package/dist/components/participants-panel/ParticipantsPanel.d.ts.map +0 -1
- package/dist/components/participants-panel/index.d.ts.map +0 -1
- package/dist/components/participants-panel/participant-options-menu-contract.d.ts.map +0 -1
- package/dist/components/participants-panel/participant-options-menu.d.ts.map +0 -1
- package/dist/components/participants-panel/participant-row.d.ts.map +0 -1
- package/dist/components/participants-panel/participant-volume-context.d.ts.map +0 -1
- package/dist/components/reconnecting-overlay/ReconnectingOverlay.d.ts.map +0 -1
- package/dist/components/recording-history/RecordingHistoryPanel.d.ts.map +0 -1
- package/dist/components/space-header/CallDetails.d.ts.map +0 -1
- package/dist/components/space-header/LayoutMenu.d.ts.map +0 -1
- package/dist/components/space-header/SpaceHeader.d.ts.map +0 -1
- package/dist/components/space-header/call-diagnostics.d.ts.map +0 -1
- package/dist/components/space-header/share-qr.d.ts.map +0 -1
- package/dist/components/space-header/space-header-contract.d.ts.map +0 -1
- package/dist/components/space-header/useCallShortcuts.d.ts.map +0 -1
- package/dist/components/space-view/RecordingSpaceView.d.ts.map +0 -1
- package/dist/components/space-view/SpacePresentation.d.ts.map +0 -1
- package/dist/components/space-view/SpaceView.d.ts.map +0 -1
- package/dist/components/space-view/useDrawerPresence.d.ts.map +0 -1
- package/dist/components/stage/SpaceStage.d.ts.map +0 -1
- package/dist/components/stage/Stage.d.ts.map +0 -1
- package/dist/components/stage/StageContentTile.d.ts.map +0 -1
- package/dist/components/stage/StageIcon.d.ts.map +0 -1
- package/dist/components/stage/StageMotion.d.ts.map +0 -1
- package/dist/components/stage/stage-items.d.ts.map +0 -1
- package/dist/components/stage/stage-layout.d.ts.map +0 -1
- package/dist/components/stage/useStageDimensions.d.ts.map +0 -1
- package/dist/components/toast-stack/CallEventIcon.d.ts.map +0 -1
- package/dist/components/toast-stack/ToastStack.d.ts.map +0 -1
- package/dist/components/toast-stack/call-events.d.ts.map +0 -1
- package/dist/components/toast-stack/useCallToasts.d.ts.map +0 -1
- package/dist/components/ui/index.d.ts.map +0 -1
- package/dist/components/whiteboard-view/RecordingWhiteboardView.d.ts.map +0 -1
- package/dist/components/whiteboard-view/WhiteboardView.d.ts.map +0 -1
- package/dist/components/whiteboard-view/recording-whiteboard-state.d.ts.map +0 -1
- package/dist/headless.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/internal/call-motion.d.ts.map +0 -1
- package/dist/internal/sound-cues.d.ts.map +0 -1
- package/dist/internal/useEpisodeDuration.d.ts.map +0 -1
- package/dist/internal/useHaptics.d.ts.map +0 -1
- package/dist/internal/useMediaQuery.d.ts.map +0 -1
- package/dist/internal/useMirrorSetting.d.ts.map +0 -1
- package/dist/internal/useSoundCues.d.ts.map +0 -1
- package/dist/internal/useSoundsSetting.d.ts.map +0 -1
- package/dist/internal/useWhiteboardSceneSubscription.d.ts.map +0 -1
- package/dist/public-surface.contract.d.ts.map +0 -1
- package/dist/selectors/space-selectors.d.ts.map +0 -1
- package/dist/test-support/index.d.ts.map +0 -1
- package/dist/test-support/preview-client.d.ts.map +0 -1
- package/dist/test-support/preview-devices.d.ts.map +0 -1
- package/dist/test-support/preview-fixtures.d.ts.map +0 -1
- package/dist/theme.d.ts.map +0 -1
- package/dist/utils/cn.d.ts.map +0 -1
- package/dist/utils/colorGenerator.d.ts.map +0 -1
- package/dist/utils/icons.d.ts.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/theme.d.ts.map +0 -1
- package/dist/whiteboard/wire-adapters.d.ts.map +0 -1
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
gridOrder,
|
|
11
11
|
isContentItem,
|
|
12
12
|
stabilizeOrder
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-ZFWIMKUC.js";
|
|
14
14
|
import {
|
|
15
15
|
ArrowLeft01Icon,
|
|
16
16
|
ArrowRight01Icon,
|
|
@@ -278,7 +278,7 @@ ChalkBackdrop.displayName = "ChalkBackdrop";
|
|
|
278
278
|
import { Menu } from "@base-ui/react/menu";
|
|
279
279
|
import { useRef } from "react";
|
|
280
280
|
import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
281
|
-
function ChalkMenu({ trigger, children, side = "bottom", className }) {
|
|
281
|
+
function ChalkMenu({ trigger, children, side = "bottom", className, onOpenChange }) {
|
|
282
282
|
const container = useRef(null);
|
|
283
283
|
return /* @__PURE__ */ jsx12(
|
|
284
284
|
"span",
|
|
@@ -287,7 +287,7 @@ function ChalkMenu({ trigger, children, side = "bottom", className }) {
|
|
|
287
287
|
container.current = element?.closest("[data-chalk]") ?? element;
|
|
288
288
|
},
|
|
289
289
|
style: { display: "contents" },
|
|
290
|
-
children: /* @__PURE__ */ jsxs2(Menu.Root, { children: [
|
|
290
|
+
children: /* @__PURE__ */ jsxs2(Menu.Root, { onOpenChange, children: [
|
|
291
291
|
/* @__PURE__ */ jsx12(Menu.Trigger, { render: trigger }),
|
|
292
292
|
/* @__PURE__ */ jsx12(Menu.Portal, { container, children: /* @__PURE__ */ jsx12(Menu.Positioner, { side, align: side === "top" ? "center" : "end", sideOffset: side === "top" ? 10 : 6, collisionPadding: 8, className: "chalk-menu-positioner", children: /* @__PURE__ */ jsx12(Menu.Popup, { "data-part": "menu", className: cn("chalk-menu", className), children }) }) })
|
|
293
293
|
] })
|
|
@@ -644,7 +644,7 @@ function CallEventIcon({ kind, error = false }) {
|
|
|
644
644
|
|
|
645
645
|
// src/components/call-limbo/CallLimbo.tsx
|
|
646
646
|
import { jsx as jsx23, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
647
|
-
function CallLimbo({ spaceName = "Call", message, detail, action, children, overlay = false, active = true, initiallyCovered = false, theme, recorded = false, blocking = true }) {
|
|
647
|
+
function CallLimbo({ spaceName = "Call", message, detail, action, children, overlay = false, active = true, initiallyCovered = false, theme, recorded = false, blocking = true, className }) {
|
|
648
648
|
const layerRef = useRef6(null);
|
|
649
649
|
const reducedMotion = usePrefersReducedMotion();
|
|
650
650
|
const [covered, setCovered] = useState6(active || initiallyCovered);
|
|
@@ -698,7 +698,7 @@ function CallLimbo({ spaceName = "Call", message, detail, action, children, over
|
|
|
698
698
|
opacity: covered ? 1 : 0,
|
|
699
699
|
transition: reducedMotion ? "none" : "opacity 220ms cubic-bezier(.2,.8,.2,1)"
|
|
700
700
|
},
|
|
701
|
-
children: /* @__PURE__ */ jsxs10("div", { "data-part": active ? "limbo-message" : void 0, style: { borderRadius: 16 }, className: "flex max-w-full flex-col items-center gap-2 rounded-2xl border border-[var(--chalk-line)] bg-[var(--chalk-surface)] px-7 py-[22px] text-center", children: [
|
|
701
|
+
children: /* @__PURE__ */ jsxs10("div", { "data-part": active ? "limbo-message" : void 0, style: { borderRadius: 16 }, className: cn("flex max-w-full flex-col items-center gap-2 rounded-2xl border border-[var(--chalk-line)] bg-[var(--chalk-surface)] px-7 py-[22px] text-center", className), children: [
|
|
702
702
|
message ? /* @__PURE__ */ jsxs10("p", { role: active ? "status" : void 0, className: "flex items-center gap-2.5 text-[17px] font-semibold", children: [
|
|
703
703
|
message.endsWith("\u2026") ? /* @__PURE__ */ jsx23("span", { "aria-hidden": "true", className: "h-[18px] w-[18px] animate-spin rounded-full border-2 border-[var(--chalk-line-strong)] border-t-[var(--chalk-text)] motion-reduce:animate-none" }) : null,
|
|
704
704
|
message
|
|
@@ -1311,7 +1311,7 @@ Avatar.displayName = "Avatar";
|
|
|
1311
1311
|
// src/components/participant-tile/TileShell.tsx
|
|
1312
1312
|
import "react";
|
|
1313
1313
|
import { jsx as jsx29, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1314
|
-
function TileShell({ label, accentColor, pinned = false, speaking = false, audioLevel = 0.5, lift = true, onClick, onDoubleClick, className, style, dataTour, hidden = false, chip, corner, children }) {
|
|
1314
|
+
function TileShell({ label, accentColor, pinned = false, ring = false, speaking = false, audioLevel = 0.5, lift = true, onClick, onDoubleClick, className, style, dataTour, hidden = false, chip, corner, children }) {
|
|
1315
1315
|
const vars = { ...style, containerType: "size", "--chalk-participant-color": accentColor, "--chalk-voice-level": audioLevel };
|
|
1316
1316
|
return /* @__PURE__ */ jsxs14(
|
|
1317
1317
|
"div",
|
|
@@ -1351,6 +1351,7 @@ function TileShell({ label, accentColor, pinned = false, speaking = false, audio
|
|
|
1351
1351
|
style: { opacity: speaking ? 0.3 + 0.7 * audioLevel : 0, boxShadow: "inset 0 0 0 3px rgba(255,255,255,.9), inset 0 0 0 4px rgba(0,0,0,.35)" }
|
|
1352
1352
|
}
|
|
1353
1353
|
),
|
|
1354
|
+
ring ? /* @__PURE__ */ jsx29("div", { "aria-hidden": "true", "data-part": "follow-ring", className: "pointer-events-none absolute inset-0 z-30 rounded-[inherit] shadow-[inset_0_0_0_2px_var(--chalk-text)]" }) : null,
|
|
1354
1355
|
corner,
|
|
1355
1356
|
chip ? /* @__PURE__ */ jsx29("div", { "data-part": "tile-tag", className: "pointer-events-none absolute bottom-2 left-2 z-30 flex h-6 max-w-[calc(100%_-_16px)] items-center gap-[5px] rounded-[6px] bg-[rgba(12,14,18,.75)] px-2 text-[12px] leading-[1.4] font-medium text-white", children: chip }) : null
|
|
1356
1357
|
]
|
|
@@ -1425,6 +1426,8 @@ var ParticipantTileSurface = React5.memo(function ParticipantTileSurface2({
|
|
|
1425
1426
|
onClick,
|
|
1426
1427
|
onDoubleClick,
|
|
1427
1428
|
pinned,
|
|
1429
|
+
clickAction = "pin",
|
|
1430
|
+
following = false,
|
|
1428
1431
|
primary,
|
|
1429
1432
|
className,
|
|
1430
1433
|
style,
|
|
@@ -1451,17 +1454,20 @@ var ParticipantTileSurface = React5.memo(function ParticipantTileSurface2({
|
|
|
1451
1454
|
const mirrored = mirror ?? (participant.isLocal === true && localMirrored);
|
|
1452
1455
|
const colors = useMemo6(() => getParticipantColor(participant.displayName || participant.id, gradientPreference), [gradientPreference, participant.displayName, participant.id]);
|
|
1453
1456
|
const recipe = useMemo6(() => getParticipantAvatarRecipe(participant.displayName || participant.id, gradientPreference), [participant.displayName, participant.id, gradientPreference]);
|
|
1457
|
+
const first2 = participant.displayName.split(" ")[0] ?? participant.displayName;
|
|
1458
|
+
const actionLabel = clickAction === "follow" ? `${following ? "Stop following" : "Follow"} ${first2}` : `${pinned ? "Unpin" : "Pin"} ${participant.displayName}`;
|
|
1454
1459
|
const hasPoorConnection = participant.connectionQuality !== void 0 && participant.connectionQuality > 0 && participant.connectionQuality <= 2;
|
|
1455
1460
|
return /* @__PURE__ */ jsxs15(
|
|
1456
1461
|
TileShell,
|
|
1457
1462
|
{
|
|
1458
|
-
label: `Video tile for ${participant.displayName}`,
|
|
1463
|
+
label: clickAction === "follow" ? actionLabel : `Video tile for ${participant.displayName}`,
|
|
1459
1464
|
accentColor: colors.primary,
|
|
1460
|
-
pinned,
|
|
1465
|
+
pinned: clickAction === "follow" ? following : pinned,
|
|
1466
|
+
ring: following,
|
|
1461
1467
|
lift: !primary,
|
|
1462
1468
|
speaking: mediaVisible && participant.isSpeaking && !participant.isMuted,
|
|
1463
1469
|
audioLevel: participant.audioLevel,
|
|
1464
|
-
onClick: onClick && phone ? openOptions : onClick,
|
|
1470
|
+
onClick: onClick && phone && clickAction === "pin" ? openOptions : onClick,
|
|
1465
1471
|
onDoubleClick,
|
|
1466
1472
|
className: cn(aspectRatioClasses[aspectRatio], className),
|
|
1467
1473
|
style,
|
|
@@ -1473,11 +1479,7 @@ var ParticipantTileSurface = React5.memo(function ParticipantTileSurface2({
|
|
|
1473
1479
|
ChalkMenu,
|
|
1474
1480
|
{
|
|
1475
1481
|
trigger: /* @__PURE__ */ jsx30("button", { type: "button", "aria-label": `More options for ${participant.displayName}`, className: "grid h-8 w-8 place-items-center rounded-full bg-[rgba(12,14,18,.75)] text-white outline-none hover:bg-[rgba(40,42,48,.95)] focus-visible:ring-2 focus-visible:ring-[var(--chalk-focus)]", children: /* @__PURE__ */ jsx30(StageIcon, { name: "more", size: 18 }) }),
|
|
1476
|
-
children: /* @__PURE__ */
|
|
1477
|
-
pinned ? "Unpin" : "Pin",
|
|
1478
|
-
" ",
|
|
1479
|
-
participant.displayName
|
|
1480
|
-
] })
|
|
1482
|
+
children: /* @__PURE__ */ jsx30(ChalkMenuItem, { onClick, children: actionLabel })
|
|
1481
1483
|
}
|
|
1482
1484
|
) }) : null
|
|
1483
1485
|
] }),
|
|
@@ -1487,6 +1489,7 @@ var ParticipantTileSurface = React5.memo(function ParticipantTileSurface2({
|
|
|
1487
1489
|
participant.displayName,
|
|
1488
1490
|
participant.isLocal && participant.displayName !== "You" && /* @__PURE__ */ jsx30("span", { className: "text-white", children: " (You)" })
|
|
1489
1491
|
] }),
|
|
1492
|
+
following ? /* @__PURE__ */ jsx30("span", { className: "shrink-0 font-semibold opacity-80", children: "following" }) : null,
|
|
1490
1493
|
showStatus && participant.isMuted && /* @__PURE__ */ jsx30(StageIcon, { name: "mute", size: 14, className: "shrink-0 text-[#ff6b6b]", "aria-label": "Muted" }),
|
|
1491
1494
|
showStatus && participant.isHandRaised && /* @__PURE__ */ jsxs15("span", { className: "flex shrink-0 items-center text-[#e2bd3a]", role: "img", "aria-label": `Hand raised${participant.handRaiseOrder ? `, number ${participant.handRaiseOrder}` : ""}`, children: [
|
|
1492
1495
|
/* @__PURE__ */ jsx30(StageIcon, { name: "hand", size: 14 }),
|
|
@@ -1513,7 +1516,7 @@ var ParticipantTileSurface = React5.memo(function ParticipantTileSurface2({
|
|
|
1513
1516
|
colors: recipe.facehashColors
|
|
1514
1517
|
}
|
|
1515
1518
|
) : /* @__PURE__ */ jsx30(Avatar, { name: participant.displayName, src: participant.avatarUrl, size: "xl", generated: false, style: { width: "100%", height: "100%" } }) }) }),
|
|
1516
|
-
phone && onClick ? /* @__PURE__ */ jsxs15(
|
|
1519
|
+
phone && onClick && clickAction === "pin" ? /* @__PURE__ */ jsxs15(
|
|
1517
1520
|
ChalkSheet,
|
|
1518
1521
|
{
|
|
1519
1522
|
open: optionsOpen,
|
|
@@ -3147,11 +3150,9 @@ var ChatPanelSurface = React10.memo(
|
|
|
3147
3150
|
const [pickingFiles, setPickingFiles] = useState17(false);
|
|
3148
3151
|
const [composerError, setComposerError] = useState17(null);
|
|
3149
3152
|
const scrollRef = useRef12(null);
|
|
3150
|
-
const endRef = useRef12(null);
|
|
3151
3153
|
const textareaRef = useRef12(null);
|
|
3152
3154
|
const fileInputRef = useRef12(null);
|
|
3153
3155
|
const isAtBottomRef = useRef12(true);
|
|
3154
|
-
const mountedRef = useRef12(false);
|
|
3155
3156
|
const lastMarkedSequenceRef = useRef12(localReadThroughSequence);
|
|
3156
3157
|
const restoreScrollRef = useRef12(null);
|
|
3157
3158
|
const grouped = useMemo8(() => groupChatMessages(messages), [messages]);
|
|
@@ -3169,10 +3170,8 @@ var ChatPanelSurface = React10.memo(
|
|
|
3169
3170
|
return;
|
|
3170
3171
|
}
|
|
3171
3172
|
if (!isAtBottomRef.current) return;
|
|
3172
|
-
scroller.
|
|
3173
|
-
endRef.current?.scrollIntoView?.({ behavior: autoScrollBehavior ?? (mountedRef.current ? "smooth" : "auto"), block: "end" });
|
|
3173
|
+
scroller.scrollTo({ top: scroller.scrollHeight, behavior: autoScrollBehavior ?? "auto" });
|
|
3174
3174
|
if (latestSequence) markChatSequenceRead(latestSequence, lastMarkedSequenceRef, onMarkRead);
|
|
3175
|
-
mountedRef.current = true;
|
|
3176
3175
|
}, [autoScrollBehavior, latestSequence, messages.length, onMarkRead, pendingMessages.length]);
|
|
3177
3176
|
useEffect13(() => {
|
|
3178
3177
|
if (!localReadThroughSequence) return;
|
|
@@ -3387,8 +3386,7 @@ var ChatPanelSurface = React10.memo(
|
|
|
3387
3386
|
/* @__PURE__ */ jsx36("span", { children: pending.error?.message || "Not sent" }),
|
|
3388
3387
|
onRetryMessage ? /* @__PURE__ */ jsx36("button", { type: "button", className: "font-medium text-[var(--chalk-app-control-active-line)] hover:underline", onClick: () => void onRetryMessage(pending.clientMessageId), children: "Retry" }) : null
|
|
3389
3388
|
] }) : null
|
|
3390
|
-
] }, pending.clientMessageId))
|
|
3391
|
-
/* @__PURE__ */ jsx36("div", { ref: endRef })
|
|
3389
|
+
] }, pending.clientMessageId))
|
|
3392
3390
|
] }),
|
|
3393
3391
|
(error || composerError) && /* @__PURE__ */ jsx36("p", { role: "alert", className: "mx-4 mb-2 rounded-lg bg-[var(--chalk-app-danger)]/10 px-3 py-2 text-sm text-[var(--chalk-app-danger)]", children: composerError || error }),
|
|
3394
3392
|
stagedFiles.length > 0 ? /* @__PURE__ */ jsx36("div", { className: "flex flex-wrap gap-2 border-t border-[var(--chalk-app-line)] px-4 pt-3", "aria-label": "Attachments", children: stagedFiles.map((file, index) => /* @__PURE__ */ jsxs20("div", { className: "call-staged-file", children: [
|
|
@@ -4241,19 +4239,44 @@ function InputLevel({ track }) {
|
|
|
4241
4239
|
}, [track]);
|
|
4242
4240
|
return /* @__PURE__ */ jsx41("span", { className: "chalk-shell-level", role: "meter", "aria-label": "Input level", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": level, children: /* @__PURE__ */ jsx41("i", { style: { transform: `scaleX(${level / 100})` } }) });
|
|
4243
4241
|
}
|
|
4244
|
-
function CameraPreview({ track, mirrored }) {
|
|
4242
|
+
function CameraPreview({ track, mirrored, open, selectedDeviceId, captureCameraPreview }) {
|
|
4245
4243
|
const video = useRef14(null);
|
|
4244
|
+
const [unavailable, setUnavailable] = useState19(false);
|
|
4245
|
+
const trackEnabled = track?.enabled;
|
|
4246
4246
|
useEffect15(() => {
|
|
4247
4247
|
const element = video.current;
|
|
4248
|
-
if (!element || !
|
|
4249
|
-
|
|
4248
|
+
if (!element || !open) return;
|
|
4249
|
+
let cancelled = false;
|
|
4250
|
+
let preview;
|
|
4251
|
+
setUnavailable(false);
|
|
4252
|
+
if (captureCameraPreview) {
|
|
4253
|
+
void captureCameraPreview().then(
|
|
4254
|
+
(captured) => {
|
|
4255
|
+
if (cancelled) {
|
|
4256
|
+
captured.release();
|
|
4257
|
+
return;
|
|
4258
|
+
}
|
|
4259
|
+
preview = captured;
|
|
4260
|
+
element.srcObject = new MediaStream([captured.track]);
|
|
4261
|
+
},
|
|
4262
|
+
() => {
|
|
4263
|
+
if (!cancelled) setUnavailable(true);
|
|
4264
|
+
}
|
|
4265
|
+
);
|
|
4266
|
+
} else if (track?.enabled && track.readyState === "live") {
|
|
4267
|
+
element.srcObject = new MediaStream([track]);
|
|
4268
|
+
} else {
|
|
4269
|
+
setUnavailable(true);
|
|
4270
|
+
}
|
|
4250
4271
|
return () => {
|
|
4272
|
+
cancelled = true;
|
|
4251
4273
|
element.srcObject = null;
|
|
4274
|
+
preview?.release();
|
|
4252
4275
|
};
|
|
4253
|
-
}, [track]);
|
|
4276
|
+
}, [track, trackEnabled, open, selectedDeviceId, captureCameraPreview]);
|
|
4254
4277
|
return /* @__PURE__ */ jsxs23("div", { className: "chalk-shell-preview", children: [
|
|
4255
4278
|
/* @__PURE__ */ jsx41("video", { ref: video, autoPlay: true, muted: true, playsInline: true, style: { transform: mirrored ? "scaleX(-1)" : void 0 } }),
|
|
4256
|
-
/* @__PURE__ */ jsx41("small", { children:
|
|
4279
|
+
/* @__PURE__ */ jsx41("small", { children: unavailable ? "Camera preview unavailable. Check camera access." : "Only you see this preview" })
|
|
4257
4280
|
] });
|
|
4258
4281
|
}
|
|
4259
4282
|
function DeviceSwitch({ label, checked, onChange }) {
|
|
@@ -4273,6 +4296,7 @@ function DevicePopover({
|
|
|
4273
4296
|
selectedSecondaryDeviceId,
|
|
4274
4297
|
onSecondaryDeviceChange,
|
|
4275
4298
|
track,
|
|
4299
|
+
captureCameraPreview,
|
|
4276
4300
|
warning,
|
|
4277
4301
|
speakingWhileMuted,
|
|
4278
4302
|
noiseSuppression = true,
|
|
@@ -4283,8 +4307,10 @@ function DevicePopover({
|
|
|
4283
4307
|
orientation = "up",
|
|
4284
4308
|
className,
|
|
4285
4309
|
disabled,
|
|
4310
|
+
toggleDisabled,
|
|
4286
4311
|
busy
|
|
4287
4312
|
}) {
|
|
4313
|
+
const [open, setOpen] = useState19(false);
|
|
4288
4314
|
const mic = type === "mic";
|
|
4289
4315
|
const label = mic ? isActive ? "Mute microphone" : "Unmute microphone" : isActive ? "Turn off camera" : "Turn on camera";
|
|
4290
4316
|
return /* @__PURE__ */ jsxs23("div", { "data-part": `${type}-split`, className: cn("chalk-shell-split", className), "data-off": !isActive, children: [
|
|
@@ -4292,7 +4318,7 @@ function DevicePopover({
|
|
|
4292
4318
|
/* @__PURE__ */ jsx41(
|
|
4293
4319
|
ChalkTooltip,
|
|
4294
4320
|
{
|
|
4295
|
-
trigger: /* @__PURE__ */ jsx41("button", { type: "button", className: "chalk-shell-main", "aria-label": busy ? `Updating ${mic ? "microphone" : "camera"}` : label, "aria-pressed": isActive, disabled: disabled || busy, onClick: onToggle, children: /* @__PURE__ */ jsx41(ShellIcon, { name: mic ? isActive ? "mic" : "micOff" : isActive ? "cam" : "camOff" }) }),
|
|
4321
|
+
trigger: /* @__PURE__ */ jsx41("button", { type: "button", className: "chalk-shell-main", "aria-label": busy ? `Updating ${mic ? "microphone" : "camera"}` : label, "aria-pressed": isActive, disabled: disabled || toggleDisabled || busy, onClick: onToggle, children: /* @__PURE__ */ jsx41(ShellIcon, { name: mic ? isActive ? "mic" : "micOff" : isActive ? "cam" : "camOff" }) }),
|
|
4296
4322
|
children: label
|
|
4297
4323
|
}
|
|
4298
4324
|
),
|
|
@@ -4301,9 +4327,10 @@ function DevicePopover({
|
|
|
4301
4327
|
{
|
|
4302
4328
|
side: orientation === "up" ? "top" : "bottom",
|
|
4303
4329
|
className: "chalk-device-menu",
|
|
4330
|
+
onOpenChange: setOpen,
|
|
4304
4331
|
trigger: /* @__PURE__ */ jsx41("button", { type: "button", className: "chalk-shell-caret", "aria-label": `Select ${mic ? "microphone" : "camera"}`, disabled, children: /* @__PURE__ */ jsx41(ShellIcon, { name: "down" }) }),
|
|
4305
4332
|
children: [
|
|
4306
|
-
!mic ? /* @__PURE__ */ jsx41(CameraPreview, { track, mirrored }) : null,
|
|
4333
|
+
!mic ? /* @__PURE__ */ jsx41(CameraPreview, { track, mirrored, open, selectedDeviceId, captureCameraPreview }) : null,
|
|
4307
4334
|
/* @__PURE__ */ jsx41(ChalkMenuLabel, { children: mic ? "Microphone" : "Camera" }),
|
|
4308
4335
|
devices.length ? devices.map((device) => /* @__PURE__ */ jsx41(ChalkMenuItem, { checked: selectedDeviceId === device.deviceId, onClick: () => onDeviceChange(device.deviceId), closeOnClick: false, children: /* @__PURE__ */ jsxs23("span", { className: "chalk-shell-device-label", children: [
|
|
4309
4336
|
device.label || (mic ? "Microphone" : "Camera"),
|
|
@@ -4410,11 +4437,12 @@ function SourceIcon({ source }) {
|
|
|
4410
4437
|
/* @__PURE__ */ jsx45("path", { d: "M3 11h18" })
|
|
4411
4438
|
] }) });
|
|
4412
4439
|
}
|
|
4413
|
-
function ShareControl({ sharing, onToggle, onStart }) {
|
|
4440
|
+
function ShareControl({ sharing, onToggle, onStart, disabled = false, startDisabled = false }) {
|
|
4414
4441
|
const [surface, setSurface] = useState21("window");
|
|
4415
4442
|
const [audio, setAudio] = useState21(true);
|
|
4416
4443
|
const [replace, setReplace] = useState21(false);
|
|
4417
4444
|
const start = (next) => {
|
|
4445
|
+
if (disabled || startDisabled) return;
|
|
4418
4446
|
setSurface(next);
|
|
4419
4447
|
setReplace(false);
|
|
4420
4448
|
onStart?.({ surface: next, audio: next === "browser" && audio });
|
|
@@ -4423,7 +4451,7 @@ function ShareControl({ sharing, onToggle, onStart }) {
|
|
|
4423
4451
|
/* @__PURE__ */ jsx45(
|
|
4424
4452
|
ChalkTooltip,
|
|
4425
4453
|
{
|
|
4426
|
-
trigger: /* @__PURE__ */ jsx45("button", { type: "button", className: "chalk-shell-main", "aria-label": sharing ? "Stop sharing" : "Share screen", "aria-pressed": sharing, onClick: () => sharing || !onStart ? onToggle?.() : start(surface), children: /* @__PURE__ */ jsx45(ShellIcon, { name: sharing ? "shareOff" : "share" }) }),
|
|
4454
|
+
trigger: /* @__PURE__ */ jsx45("button", { type: "button", className: "chalk-shell-main", disabled, "aria-label": sharing ? "Stop sharing" : "Share screen", "aria-pressed": sharing, onClick: () => sharing || !onStart ? onToggle?.() : start(surface), children: /* @__PURE__ */ jsx45(ShellIcon, { name: sharing ? "shareOff" : "share" }) }),
|
|
4427
4455
|
children: sharing ? "Stop sharing" : "Share screen"
|
|
4428
4456
|
}
|
|
4429
4457
|
),
|
|
@@ -4432,17 +4460,17 @@ function ShareControl({ sharing, onToggle, onStart }) {
|
|
|
4432
4460
|
{
|
|
4433
4461
|
side: "top",
|
|
4434
4462
|
className: "chalk-device-menu",
|
|
4435
|
-
trigger: /* @__PURE__ */ jsx45("button", { type: "button", className: "chalk-shell-caret", "aria-label": "Share options", children: /* @__PURE__ */ jsx45(ShellIcon, { name: "down" }) }),
|
|
4463
|
+
trigger: /* @__PURE__ */ jsx45("button", { type: "button", className: "chalk-shell-caret", disabled: disabled || startDisabled, "aria-label": "Share options", children: /* @__PURE__ */ jsx45(ShellIcon, { name: "down" }) }),
|
|
4436
4464
|
children: sharing && !replace ? /* @__PURE__ */ jsxs27(Fragment14, { children: [
|
|
4437
4465
|
/* @__PURE__ */ jsx45(ChalkMenuLabel, { children: "Sharing your screen" }),
|
|
4438
|
-
/* @__PURE__ */ jsx45(Menu4.Item, { className: "chalk-menu-item", closeOnClick: false, onClick: () => setReplace(true), children: "Share something else" }),
|
|
4466
|
+
/* @__PURE__ */ jsx45(Menu4.Item, { className: "chalk-menu-item", disabled: disabled || startDisabled, closeOnClick: false, onClick: () => setReplace(true), children: "Share something else" }),
|
|
4439
4467
|
/* @__PURE__ */ jsx45(ChalkMenuItem, { onClick: onToggle, children: "Stop sharing" })
|
|
4440
4468
|
] }) : /* @__PURE__ */ jsxs27(Fragment14, { children: [
|
|
4441
4469
|
[
|
|
4442
4470
|
{ value: "monitor", label: "Entire screen", description: "Everything on one display" },
|
|
4443
4471
|
{ value: "window", label: "Window", description: "One app, like Slides or Figma" },
|
|
4444
4472
|
{ value: "browser", label: "Browser tab", description: "One tab, and its sound if you like" }
|
|
4445
|
-
].map((option) => /* @__PURE__ */ jsxs27(Menu4.Item, { className: "chalk-menu-item chalk-shell-source", role: "menuitemradio", "aria-label": option.label, "aria-checked": surface === option.value, onClick: () => start(option.value), children: [
|
|
4473
|
+
].map((option) => /* @__PURE__ */ jsxs27(Menu4.Item, { className: "chalk-menu-item chalk-shell-source", disabled: disabled || startDisabled, role: "menuitemradio", "aria-label": option.label, "aria-checked": surface === option.value, onClick: () => start(option.value), children: [
|
|
4446
4474
|
/* @__PURE__ */ jsx45(SourceIcon, { source: option.value }),
|
|
4447
4475
|
/* @__PURE__ */ jsxs27("span", { className: "chalk-shell-device-label", children: [
|
|
4448
4476
|
/* @__PURE__ */ jsx45("span", { children: option.label }),
|
|
@@ -4568,6 +4596,7 @@ function useConnectedControlBarBehavior(props) {
|
|
|
4568
4596
|
const canRaiseHand = useCan("raiseHand");
|
|
4569
4597
|
const canDrawWhiteboard = useCan("drawWhiteboard");
|
|
4570
4598
|
const canManageRecording = useCan("manageRecording");
|
|
4599
|
+
const live = useConnection().status === "live";
|
|
4571
4600
|
const microphoneEnabled = media.local.microphone.state === "enabled";
|
|
4572
4601
|
const cameraEnabled = media.local.camera.state === "enabled";
|
|
4573
4602
|
const screenSharing = media.local.screen.state === "enabled" || media.local.screen.state === "requesting";
|
|
@@ -4617,6 +4646,7 @@ function useConnectedControlBarBehavior(props) {
|
|
|
4617
4646
|
buttons,
|
|
4618
4647
|
microphoneTrack: media.local.microphone.track,
|
|
4619
4648
|
cameraTrack: media.local.camera.track,
|
|
4649
|
+
captureCameraPreview: client.media.captureCameraPreview,
|
|
4620
4650
|
mirrored: props.mirrored ?? mirrored,
|
|
4621
4651
|
onMirrorChange: props.onMirrorChange ?? (props.mirrored === void 0 ? setMirrored2 : void 0),
|
|
4622
4652
|
microphoneWarning: media.local.microphone.state === "failed" || media.local.microphone.state === "unavailable",
|
|
@@ -4632,6 +4662,8 @@ function useConnectedControlBarBehavior(props) {
|
|
|
4632
4662
|
shareUrl: props.shareUrl,
|
|
4633
4663
|
isMuted: !microphoneEnabled,
|
|
4634
4664
|
microphonePending: media.local.microphone.state === "requesting",
|
|
4665
|
+
mediaEnableDisabled: !live,
|
|
4666
|
+
reactionsDisabled: !live,
|
|
4635
4667
|
cameraPending: media.local.camera.state === "requesting",
|
|
4636
4668
|
isVideoEnabled: cameraEnabled,
|
|
4637
4669
|
isScreenSharing: screenSharing,
|
|
@@ -4727,10 +4759,10 @@ var ControlBarSurface = React12.memo((props) => {
|
|
|
4727
4759
|
return () => document.removeEventListener("fullscreenchange", changed);
|
|
4728
4760
|
}, []);
|
|
4729
4761
|
const has = (button) => buttonsToRender.includes(button);
|
|
4730
|
-
const iconButton = (label, icon, onClick, active, badge = 0, hand = false) => /* @__PURE__ */ jsx46(
|
|
4762
|
+
const iconButton = (label, icon, onClick, active, badge = 0, hand = false, disabled = false) => /* @__PURE__ */ jsx46(
|
|
4731
4763
|
ChalkTooltip,
|
|
4732
4764
|
{
|
|
4733
|
-
trigger: /* @__PURE__ */ jsxs28(ChalkIconButton, { size: "lg", "aria-label": label, "aria-pressed": active, onClick, children: [
|
|
4765
|
+
trigger: /* @__PURE__ */ jsxs28(ChalkIconButton, { size: "lg", "aria-label": label, "aria-pressed": active, onClick, disabled, children: [
|
|
4734
4766
|
icon,
|
|
4735
4767
|
badge > 0 ? /* @__PURE__ */ jsx46("span", { className: "chalk-shell-dot", "aria-label": `${badge} unread` }) : null,
|
|
4736
4768
|
hand ? /* @__PURE__ */ jsx46("span", { className: "chalk-shell-dot", "aria-label": "Hand raised" }) : null
|
|
@@ -4746,7 +4778,7 @@ var ControlBarSurface = React12.memo((props) => {
|
|
|
4746
4778
|
const chat = has("chat") && (props.onToggleChat || props.displayOnly) ? iconButton("Chat", /* @__PURE__ */ jsx46(ShellIcon, { name: "chat" }), props.onToggleChat, props.isChatOpen, props.isChatOpen ? 0 : props.unreadChatCount) : null;
|
|
4747
4779
|
const copyLink = () => void navigator.clipboard.writeText(props.shareUrl ?? location.href).catch(() => props.onCopyError?.());
|
|
4748
4780
|
const linkLabel = props.onCallLinkToggle ? props.callLinkOpen ? "Hide call link" : "Show call link" : "Copy call link";
|
|
4749
|
-
const reactions = has("reactions") && (props.onOpenReactions || props.displayOnly) ? iconButton("Reactions", /* @__PURE__ */ jsx46(ShellIcon, { name: "smile" }), props.onOpenReactions, void 0, 0, props.isHandRaised) : null;
|
|
4781
|
+
const reactions = has("reactions") && (props.onOpenReactions || props.displayOnly) ? iconButton("Reactions", /* @__PURE__ */ jsx46(ShellIcon, { name: "smile" }), props.onOpenReactions, void 0, 0, props.isHandRaised, props.reactionsDisabled) : null;
|
|
4750
4782
|
return /* @__PURE__ */ jsxs28("div", { ref: dock, "data-part": "dock", className: cn("chalk-shell-dock", props.className), role: "toolbar", "aria-label": "Space controls", children: [
|
|
4751
4783
|
/* @__PURE__ */ jsx46(ShellStyles, {}),
|
|
4752
4784
|
has("mic") ? /* @__PURE__ */ jsx46(
|
|
@@ -4755,6 +4787,7 @@ var ControlBarSurface = React12.memo((props) => {
|
|
|
4755
4787
|
type: "mic",
|
|
4756
4788
|
isActive: !props.isMuted,
|
|
4757
4789
|
busy: props.microphonePending,
|
|
4790
|
+
toggleDisabled: props.mediaEnableDisabled && props.isMuted,
|
|
4758
4791
|
onToggle: props.onToggleMute ?? (() => {
|
|
4759
4792
|
}),
|
|
4760
4793
|
devices: effectiveAudioInputDevices,
|
|
@@ -4778,6 +4811,7 @@ var ControlBarSurface = React12.memo((props) => {
|
|
|
4778
4811
|
type: "video",
|
|
4779
4812
|
isActive: props.isVideoEnabled ?? true,
|
|
4780
4813
|
busy: props.cameraPending,
|
|
4814
|
+
toggleDisabled: props.mediaEnableDisabled && !props.isVideoEnabled,
|
|
4781
4815
|
onToggle: props.onToggleVideo ?? (() => {
|
|
4782
4816
|
}),
|
|
4783
4817
|
devices: effectiveVideoInputDevices,
|
|
@@ -4785,15 +4819,16 @@ var ControlBarSurface = React12.memo((props) => {
|
|
|
4785
4819
|
onDeviceChange: props.onVideoInputChange ?? (() => {
|
|
4786
4820
|
}),
|
|
4787
4821
|
track: props.cameraTrack,
|
|
4822
|
+
captureCameraPreview: props.captureCameraPreview,
|
|
4788
4823
|
warning: props.cameraWarning,
|
|
4789
4824
|
mirrored: props.mirrored,
|
|
4790
4825
|
onMirrorChange: props.onMirrorChange
|
|
4791
4826
|
}
|
|
4792
4827
|
) : null,
|
|
4793
4828
|
/* @__PURE__ */ jsx46("span", { className: "chalk-shell-gap chalk-shell-desktop" }),
|
|
4794
|
-
has("screenshare") ? /* @__PURE__ */ jsx46(ShareControl, { sharing: props.isScreenSharing ?? false, onToggle: props.onToggleScreenShare, onStart: props.onStartScreenShare }) : null,
|
|
4829
|
+
has("screenshare") ? /* @__PURE__ */ jsx46(ShareControl, { disabled: props.mediaEnableDisabled && !props.isScreenSharing, startDisabled: props.mediaEnableDisabled, sharing: props.isScreenSharing ?? false, onToggle: props.onToggleScreenShare, onStart: props.onStartScreenShare }) : null,
|
|
4795
4830
|
reactions,
|
|
4796
|
-
!reactions && has("handraise") ? iconButton(props.isHandRaised ? "Lower hand" : "Raise hand", /* @__PURE__ */ jsx46(HandIcon, {}), props.onToggleHandRaise, props.isHandRaised) : null,
|
|
4831
|
+
(!reactions || props.reactionsDisabled) && has("handraise") ? iconButton(props.isHandRaised ? "Lower hand" : "Raise hand", /* @__PURE__ */ jsx46(HandIcon, {}), props.onToggleHandRaise, props.isHandRaised) : null,
|
|
4797
4832
|
has("whiteboard") && (props.onToggleWhiteboard || props.displayOnly) ? /* @__PURE__ */ jsx46("span", { className: "chalk-shell-desktop", children: iconButton("Board", /* @__PURE__ */ jsx46(ShellIcon, { name: "pen" }), props.onToggleWhiteboard, props.isWhiteboardOpen) }) : null,
|
|
4798
4833
|
has("record") && (props.onToggleRecording || props.displayOnly) ? /* @__PURE__ */ jsx46("span", { className: "chalk-shell-desktop", children: iconButton(props.isRecording ? "Stop recording" : "Record", /* @__PURE__ */ jsx46(ShellIcon, { name: "rec" }), props.isRecordingPending ? void 0 : props.onToggleRecording, props.isRecording) }) : null,
|
|
4799
4834
|
/* @__PURE__ */ jsx46("span", { className: "chalk-shell-desktop", children: iconButton(fullscreen ? "Exit fullscreen" : "Fullscreen", /* @__PURE__ */ jsx46(ShellIcon, { name: fullscreen ? "min" : "max" }), toggleFullscreen, fullscreen) }),
|
|
@@ -4820,7 +4855,7 @@ var ControlBarSurface = React12.memo((props) => {
|
|
|
4820
4855
|
]
|
|
4821
4856
|
}
|
|
4822
4857
|
) : null,
|
|
4823
|
-
has("screenshare") && canCaptureDisplay ? /* @__PURE__ */ jsxs28("button", { type: "button", className: "chalk-sheet-row", onClick: props.onToggleScreenShare, children: [
|
|
4858
|
+
has("screenshare") && canCaptureDisplay ? /* @__PURE__ */ jsxs28("button", { type: "button", className: "chalk-sheet-row", disabled: props.mediaEnableDisabled && !props.isScreenSharing, onClick: props.onToggleScreenShare, children: [
|
|
4824
4859
|
/* @__PURE__ */ jsx46(ShellIcon, { name: "share" }),
|
|
4825
4860
|
/* @__PURE__ */ jsx46("span", { children: props.isScreenSharing ? "Stop sharing" : "Share screen" })
|
|
4826
4861
|
] }) : null,
|
|
@@ -5339,12 +5374,24 @@ var ToastStack = memo(function ToastStack2({ toasts, onDismiss, maxVisible = 3,
|
|
|
5339
5374
|
|
|
5340
5375
|
// src/components/chalk/StatusSurface.tsx
|
|
5341
5376
|
import { Fragment as Fragment18, jsx as jsx51, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
5342
|
-
|
|
5377
|
+
var DEFAULT_FAILURE_DETAIL = "Something went wrong while joining. Try again.";
|
|
5378
|
+
var FAILURE_DETAILS = {
|
|
5379
|
+
"access.invalid": "Access to this call was rejected. Refresh the page and try again.",
|
|
5380
|
+
"access.unavailable": "Couldn\u2019t get access to the call. Try again.",
|
|
5381
|
+
"connection.sync_start_failed": "Couldn\u2019t reach the call service. Try again.",
|
|
5382
|
+
"connection.sync_hello_timeout": "The call service did not respond in time. Try again.",
|
|
5383
|
+
"connection.sync_recovery_exhausted": "The call connection could not recover. Try again.",
|
|
5384
|
+
"connection.media_start_failed": "Audio and video could not connect. Try again.",
|
|
5385
|
+
"connection.media_recovery_exhausted": "Audio and video could not reconnect. Try again.",
|
|
5386
|
+
"environment.unsupported": "This browser cannot start the call. Use an up-to-date browser over HTTPS.",
|
|
5387
|
+
"client.internal_error": DEFAULT_FAILURE_DETAIL
|
|
5388
|
+
};
|
|
5389
|
+
function StatusSurface({ phase, callName = "the call", reason, errorCode, theme, onRejoin, onHome, onRetry, onReportProblem, rejoinDisabled = false, retryPending = false }) {
|
|
5343
5390
|
const copy = {
|
|
5344
5391
|
entering: { title: `Joining ${callName}\u2026`, detail: "" },
|
|
5345
5392
|
left: { title: `You left ${callName}`, detail: "" },
|
|
5346
5393
|
"episode-ended": { title: "The call ended", detail: "" },
|
|
5347
|
-
failed: { title: `Couldn\u2019t connect to ${callName}`, detail:
|
|
5394
|
+
failed: { title: `Couldn\u2019t connect to ${callName}`, detail: (errorCode && FAILURE_DETAILS[errorCode]) ?? DEFAULT_FAILURE_DETAIL },
|
|
5348
5395
|
removed: { title: `You were removed from ${callName}`, detail: "The host removed you from this call." },
|
|
5349
5396
|
full: { title: "This call is full", detail: `${callName} has reached 100 people. Ask the host to make room, then try again.` },
|
|
5350
5397
|
elsewhere: { title: "You joined from another tab", detail: `${callName} is open in another tab or window. Use it here instead?` }
|
|
@@ -5362,6 +5409,11 @@ function StatusSurface({ phase, callName = "the call", reason, theme, onRejoin,
|
|
|
5362
5409
|
children: [
|
|
5363
5410
|
/* @__PURE__ */ jsx51("h1", { role: phase === "entering" ? "status" : void 0, style: { fontSize: 22, fontWeight: 600, lineHeight: 1.4, fontFamily: "inherit", letterSpacing: "normal", textWrap: "wrap" }, children: copy.title }),
|
|
5364
5411
|
reason || copy.detail ? /* @__PURE__ */ jsx51("p", { className: "text-[14px] text-[var(--chalk-muted-text)]", children: reason ?? copy.detail }) : null,
|
|
5412
|
+
phase === "failed" && errorCode ? /* @__PURE__ */ jsxs32("p", { className: "text-[13px] text-[var(--chalk-muted-text)]", children: [
|
|
5413
|
+
"Reference:",
|
|
5414
|
+
" ",
|
|
5415
|
+
/* @__PURE__ */ jsx51("code", { dir: "ltr", className: "break-all", children: errorCode })
|
|
5416
|
+
] }) : null,
|
|
5365
5417
|
/* @__PURE__ */ jsxs32("div", { "data-part": "status-actions", className: "mt-5 flex gap-7", children: [
|
|
5366
5418
|
canRetry && retry ? /* @__PURE__ */ jsx51(StatusAction, { icon: /* @__PURE__ */ jsx51(StatusIcon, { retry: true }), label: phase === "elsewhere" ? "Use here" : phase === "left" ? "Rejoin" : "Try again", onClick: retry, disabled: rejoinDisabled || retryPending }) : null,
|
|
5367
5419
|
phase !== "entering" && onHome ? /* @__PURE__ */ jsx51(StatusAction, { icon: /* @__PURE__ */ jsx51(StatusIcon, {}), label: "Home", onClick: onHome }) : null
|
|
@@ -5880,6 +5932,7 @@ var CALL_SHORTCUTS = [
|
|
|
5880
5932
|
];
|
|
5881
5933
|
function CallDialog({
|
|
5882
5934
|
kind,
|
|
5935
|
+
open,
|
|
5883
5936
|
onClose,
|
|
5884
5937
|
onReportProblem,
|
|
5885
5938
|
diagnostics,
|
|
@@ -5892,12 +5945,12 @@ function CallDialog({
|
|
|
5892
5945
|
const [status, setStatus] = useState28("idle");
|
|
5893
5946
|
const [error, setError] = useState28("");
|
|
5894
5947
|
useEffect21(() => {
|
|
5895
|
-
if (kind === "report") {
|
|
5948
|
+
if (open && kind === "report") {
|
|
5896
5949
|
setText("");
|
|
5897
5950
|
setError("");
|
|
5898
5951
|
setStatus("idle");
|
|
5899
5952
|
}
|
|
5900
|
-
}, [kind]);
|
|
5953
|
+
}, [kind, open]);
|
|
5901
5954
|
return /* @__PURE__ */ jsx54(
|
|
5902
5955
|
"span",
|
|
5903
5956
|
{
|
|
@@ -5908,9 +5961,9 @@ function CallDialog({
|
|
|
5908
5961
|
children: /* @__PURE__ */ jsx54(
|
|
5909
5962
|
Dialog2.Root,
|
|
5910
5963
|
{
|
|
5911
|
-
open
|
|
5912
|
-
onOpenChange: (
|
|
5913
|
-
if (!
|
|
5964
|
+
open,
|
|
5965
|
+
onOpenChange: (open2) => {
|
|
5966
|
+
if (!open2) onClose();
|
|
5914
5967
|
},
|
|
5915
5968
|
children: /* @__PURE__ */ jsxs34(Dialog2.Portal, { container, children: [
|
|
5916
5969
|
/* @__PURE__ */ jsx54(Dialog2.Backdrop, { className: "chalk-shell-modal-backdrop" }),
|
|
@@ -6134,8 +6187,8 @@ var SpaceHeader = React14.memo((props) => {
|
|
|
6134
6187
|
const [sounds, setSounds] = useSoundsSetting();
|
|
6135
6188
|
const triggerRef = useRef20(null);
|
|
6136
6189
|
const [open, setOpen] = useState29(false);
|
|
6137
|
-
const [dialog, setDialog] = useState29(
|
|
6138
|
-
const showShortcuts = useCallback6(() => setDialog("shortcuts"), []);
|
|
6190
|
+
const [dialog, setDialog] = useState29({ kind: "shortcuts", open: false });
|
|
6191
|
+
const showShortcuts = useCallback6(() => setDialog({ kind: "shortcuts", open: true }), []);
|
|
6139
6192
|
useCallShortcuts(props.shortcuts, showShortcuts);
|
|
6140
6193
|
const phone = useMediaQuery("(max-width: 640px)");
|
|
6141
6194
|
const chevron = /* @__PURE__ */ jsx55("svg", { width: phone ? "16" : "14", height: phone ? "16" : "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", "aria-hidden": "true", children: /* @__PURE__ */ jsx55("path", { d: "m6 9 6 6 6-6" }) });
|
|
@@ -6153,7 +6206,7 @@ var SpaceHeader = React14.memo((props) => {
|
|
|
6153
6206
|
onSoundsChange: setSounds,
|
|
6154
6207
|
onDialog: (kind) => {
|
|
6155
6208
|
setOpen(false);
|
|
6156
|
-
setDialog(kind);
|
|
6209
|
+
setDialog({ kind, open: true });
|
|
6157
6210
|
}
|
|
6158
6211
|
}
|
|
6159
6212
|
);
|
|
@@ -6200,7 +6253,7 @@ var SpaceHeader = React14.memo((props) => {
|
|
|
6200
6253
|
] }) : null,
|
|
6201
6254
|
onLayoutChange ? /* @__PURE__ */ jsx55(LayoutMenu, { layout, onLayoutChange }) : null
|
|
6202
6255
|
] }),
|
|
6203
|
-
/* @__PURE__ */ jsx55(CallDialog, { returnFocus: triggerRef, kind: dialog, onClose: () => setDialog(
|
|
6256
|
+
/* @__PURE__ */ jsx55(CallDialog, { returnFocus: triggerRef, kind: dialog.kind, open: dialog.open, onClose: () => setDialog((current) => ({ ...current, open: false })), onReportProblem: props.onReportProblem, diagnostics: props.diagnostics?.map(({ label, value }) => `${label}: ${value}`).join("\n") ?? "" })
|
|
6204
6257
|
] });
|
|
6205
6258
|
});
|
|
6206
6259
|
SpaceHeader.displayName = "SpaceHeader";
|
|
@@ -6218,7 +6271,7 @@ function ScreenSharePresentation({ sharedByName, media, mediaVisible, state, isL
|
|
|
6218
6271
|
const [error, setError] = useState30(null);
|
|
6219
6272
|
const status = state ?? (mediaVisible ? "live" : "connecting");
|
|
6220
6273
|
const name = isLocal ? "Your screen" : `${sharedByName}'s screen`;
|
|
6221
|
-
const source = capture?.surface === "
|
|
6274
|
+
const source = capture?.surface === "window" ? "a window" : capture?.surface === "browser" ? "a tab" : capture?.surface === "monitor" ? "your screen" : null;
|
|
6222
6275
|
const run = async (command) => {
|
|
6223
6276
|
if (pending) return;
|
|
6224
6277
|
setPending(true);
|
|
@@ -6319,10 +6372,7 @@ function ScreenSharePresentation({ sharedByName, media, mediaVisible, state, isL
|
|
|
6319
6372
|
primary ? switcher : null,
|
|
6320
6373
|
isLocal && primary ? /* @__PURE__ */ jsxs36("div", { "data-part": "share-local-overlay", role: "group", "aria-label": "Your share", className: "absolute top-2.5 right-2.5 left-2.5 z-30 flex items-center gap-2 rounded-[12px] bg-[rgba(12,14,18,.86)] p-2 pl-3.5 text-white max-[640px]:flex-wrap", children: [
|
|
6321
6374
|
/* @__PURE__ */ jsxs36("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
|
|
6322
|
-
/* @__PURE__ */
|
|
6323
|
-
"You\u2019re sharing ",
|
|
6324
|
-
source
|
|
6325
|
-
] }),
|
|
6375
|
+
/* @__PURE__ */ jsx56("b", { className: "truncate font-semibold", children: source ? `You\u2019re sharing ${source}` : "You\u2019re screen sharing" }),
|
|
6326
6376
|
capture?.surface === "monitor" || capture?.surface === "browser" ? /* @__PURE__ */ jsxs36("span", { className: "text-[12px] text-[#c9cbd1]", children: [
|
|
6327
6377
|
"Seeing a mirror effect? That\u2019s normal when you share ",
|
|
6328
6378
|
capture.surface === "browser" ? "this tab" : "your whole screen",
|
|
@@ -6375,6 +6425,7 @@ function ScreenShareViewSurface({ screenShareTrack, ...props }) {
|
|
|
6375
6425
|
}
|
|
6376
6426
|
function ScreenShareView(props) {
|
|
6377
6427
|
const client = useSpaceClient();
|
|
6428
|
+
const connection = useConnection();
|
|
6378
6429
|
const self = useSelf();
|
|
6379
6430
|
const participantsSlice = useParticipants();
|
|
6380
6431
|
const media = useMedia();
|
|
@@ -6392,8 +6443,12 @@ function ScreenShareView(props) {
|
|
|
6392
6443
|
isLocal: active.isLocal,
|
|
6393
6444
|
hasAudio: active.isLocal ? media.screenShare.hasAudio : participantsSlice.roster.find((person) => person.participantId === active.id)?.media.screenShareHasAudio,
|
|
6394
6445
|
capture: active.isLocal ? media.screenShare.capture : void 0,
|
|
6395
|
-
onStopSharing: active.isLocal ? () =>
|
|
6396
|
-
|
|
6446
|
+
onStopSharing: active.isLocal ? async () => {
|
|
6447
|
+
await client.media.setScreenShareEnabled(false);
|
|
6448
|
+
} : void 0,
|
|
6449
|
+
onChangeShare: active.isLocal && connection.status === "live" ? async (options) => {
|
|
6450
|
+
await client.media.setScreenShareEnabled(true, options);
|
|
6451
|
+
} : void 0
|
|
6397
6452
|
}
|
|
6398
6453
|
);
|
|
6399
6454
|
}
|
|
@@ -6586,6 +6641,9 @@ function Stage({
|
|
|
6586
6641
|
items,
|
|
6587
6642
|
layout,
|
|
6588
6643
|
pinnedId,
|
|
6644
|
+
followingId = null,
|
|
6645
|
+
onFollowChange,
|
|
6646
|
+
clickAction = "pin",
|
|
6589
6647
|
onPinnedChange,
|
|
6590
6648
|
onOverflowClick,
|
|
6591
6649
|
onItemClick,
|
|
@@ -6662,7 +6720,7 @@ function Stage({
|
|
|
6662
6720
|
box,
|
|
6663
6721
|
primary.id,
|
|
6664
6722
|
strip.map((item) => item.id),
|
|
6665
|
-
{ ...DEFAULT_SPOTLIGHT_OPTIONS, primaryAspect: aspect, content: primary.kind !== "participant", wideIds: primary.kind === "screen-share" ? ordered.filter((item) => item.kind === "whiteboard").map((item) => item.id) : wideIds, maxPerPage }
|
|
6723
|
+
{ ...DEFAULT_SPOTLIGHT_OPTIONS, primaryAspect: aspect, content: primary.kind !== "participant", slimPhoneStrip: primary.kind === "whiteboard", wideIds: primary.kind === "screen-share" ? ordered.filter((item) => item.kind === "whiteboard").map((item) => item.id) : wideIds, maxPerPage }
|
|
6666
6724
|
);
|
|
6667
6725
|
}, [activePinnedId, box, layout, maxPerPage, minTileWidth, ordered, primary, shareAspects]);
|
|
6668
6726
|
const framesById = useMemo12(() => new Map(geometry.frames.map((frame) => [frame.id, frame])), [geometry.frames]);
|
|
@@ -6720,14 +6778,15 @@ function Stage({
|
|
|
6720
6778
|
for (const item of ordered) {
|
|
6721
6779
|
map.set(item.id, {
|
|
6722
6780
|
onClick: () => {
|
|
6723
|
-
|
|
6781
|
+
if (clickAction === "follow" && item.kind === "participant" && !item.participant.isLocal) onFollowChange?.(item.id);
|
|
6782
|
+
else togglePin(item);
|
|
6724
6783
|
onItemClick?.(item);
|
|
6725
6784
|
},
|
|
6726
6785
|
onDoubleClick: onItemDoubleClick ? () => onItemDoubleClick(item) : void 0
|
|
6727
6786
|
});
|
|
6728
6787
|
}
|
|
6729
6788
|
return map;
|
|
6730
|
-
}, [interactive, onItemDoubleClick, onItemClick, ordered, togglePin]);
|
|
6789
|
+
}, [clickAction, interactive, onFollowChange, onItemDoubleClick, onItemClick, ordered, togglePin]);
|
|
6731
6790
|
if (ordered.length === 0) {
|
|
6732
6791
|
return /* @__PURE__ */ jsx59("div", { className: cn("flex h-full w-full items-center justify-center px-6 py-10", className), "data-tour": "video-grid", children: emptyState });
|
|
6733
6792
|
}
|
|
@@ -6752,6 +6811,8 @@ function Stage({
|
|
|
6752
6811
|
aspectRatio: "fill",
|
|
6753
6812
|
primary: frame?.role === "primary",
|
|
6754
6813
|
pinned,
|
|
6814
|
+
clickAction: item.participant.isLocal ? "pin" : clickAction,
|
|
6815
|
+
following: followingId === item.id,
|
|
6755
6816
|
onClick: click,
|
|
6756
6817
|
onDoubleClick: doubleClick,
|
|
6757
6818
|
style,
|
|
@@ -6812,6 +6873,7 @@ function Stage({
|
|
|
6812
6873
|
setOverflowOpen(false);
|
|
6813
6874
|
},
|
|
6814
6875
|
children: [
|
|
6876
|
+
clickAction === "follow" && !item.participant.isLocal ? `${followingId === item.id ? "Stop following" : "Follow"} ` : "",
|
|
6815
6877
|
item.participant.displayName,
|
|
6816
6878
|
item.participant.isMuted ? " \xB7 Muted" : ""
|
|
6817
6879
|
]
|
|
@@ -7019,7 +7081,7 @@ function SpacePresentation({ colorScheme = "dark", stageBackground, header, stag
|
|
|
7019
7081
|
// src/components/space-view/SpaceView.tsx
|
|
7020
7082
|
import { SpaceClientError as SpaceClientError2 } from "@q9labsai/chalk-client";
|
|
7021
7083
|
import { DEFAULT_QUICK_REACTIONS as DEFAULT_QUICK_REACTIONS2 } from "@q9labsai/chalk-ui/reactions";
|
|
7022
|
-
import { useCallback as
|
|
7084
|
+
import { useCallback as useCallback15, useEffect as useEffect40, useMemo as useMemo18, useRef as useRef34, useState as useState46 } from "react";
|
|
7023
7085
|
|
|
7024
7086
|
// src/components/space-header/call-diagnostics.ts
|
|
7025
7087
|
function callDiagnostics(connection, media) {
|
|
@@ -7267,6 +7329,8 @@ function ReactionTray({ isHandRaised, onToggleHandRaise, reactions, onSelect, on
|
|
|
7267
7329
|
useLayoutEffect3(() => {
|
|
7268
7330
|
returnFocus.current = document.activeElement;
|
|
7269
7331
|
const tray = listRef.current;
|
|
7332
|
+
const frame = tray?.closest("[data-part='call-frame']");
|
|
7333
|
+
const notices = frame?.querySelector("[data-part='toast-stack']");
|
|
7270
7334
|
const anchor = tray?.closest("[data-part='call-frame']")?.querySelector("[data-part='dock'] button[aria-label='Reactions']") ?? returnFocus.current;
|
|
7271
7335
|
const place = () => {
|
|
7272
7336
|
if (!tray || !(anchor instanceof HTMLElement) || !(tray.offsetParent instanceof HTMLElement)) return;
|
|
@@ -7275,7 +7339,12 @@ function ReactionTray({ isHandRaised, onToggleHandRaise, reactions, onSelect, on
|
|
|
7275
7339
|
const width = tray.offsetWidth;
|
|
7276
7340
|
const height = tray.offsetHeight;
|
|
7277
7341
|
tray.style.left = `${Math.max(8, Math.min(window.innerWidth - width - 8, trigger2.x + trigger2.width / 2 - width / 2)) - parent.x}px`;
|
|
7278
|
-
|
|
7342
|
+
const top = Math.max(8, Math.min(window.innerHeight - height - 8, position === "top" ? trigger2.y - height - 10 : trigger2.bottom + 10));
|
|
7343
|
+
tray.style.top = `${top - parent.y}px`;
|
|
7344
|
+
if (notices?.offsetParent instanceof HTMLElement) {
|
|
7345
|
+
const baseline = notices.offsetParent.getBoundingClientRect().bottom - Number.parseFloat(getComputedStyle(notices).bottom);
|
|
7346
|
+
frame?.style.setProperty("--chalk-reaction-toast-lift", `${Math.max(0, baseline - top + 12)}px`);
|
|
7347
|
+
}
|
|
7279
7348
|
};
|
|
7280
7349
|
place();
|
|
7281
7350
|
const resize = new ResizeObserver(place);
|
|
@@ -7285,6 +7354,7 @@ function ReactionTray({ isHandRaised, onToggleHandRaise, reactions, onSelect, on
|
|
|
7285
7354
|
return () => {
|
|
7286
7355
|
window.removeEventListener("resize", place);
|
|
7287
7356
|
resize.disconnect();
|
|
7357
|
+
frame?.style.removeProperty("--chalk-reaction-toast-lift");
|
|
7288
7358
|
if (returnFocus.current instanceof HTMLElement && returnFocus.current.isConnected) returnFocus.current.focus();
|
|
7289
7359
|
};
|
|
7290
7360
|
}, [position]);
|
|
@@ -7516,8 +7586,10 @@ function OfflineActions({ reconnecting: requestedReconnecting = false } = {}) {
|
|
|
7516
7586
|
const count = offline.pending.length;
|
|
7517
7587
|
const messagesOnly = offline.pending.every((action) => action.kind === "chat_message");
|
|
7518
7588
|
const pendingDetail = count ? `${count} ${messagesOnly ? count === 1 ? "message" : "messages" : count === 1 ? "action" : "actions"} will send when you\u2019re back` : void 0;
|
|
7519
|
-
|
|
7589
|
+
const notice = reconnecting ? reconnectingNoticeDue ? `${offlineNoticeText(0, online)}${pendingDetail ? ` ${pendingDetail}` : ""}` : null : connection.syncHealth === "degraded" ? "Connection is slow" : null;
|
|
7520
7590
|
if (offline.notice && offline.notice !== dismissedNotice) return /* @__PURE__ */ jsx66(ToastStack, { toasts: [{ id: "offline-notice", message: offline.notice }], onDismiss: () => setDismissedNotice(offline.notice ?? null) });
|
|
7591
|
+
if (notice)
|
|
7592
|
+
return /* @__PURE__ */ jsx66("div", { role: "status", "data-part": "connection-notice", style: { pointerEvents: "none" }, className: "absolute inset-x-4 top-16 z-50 mx-auto w-fit max-w-[calc(100%_-_2rem)] rounded-full bg-[var(--chalk-surface)] px-3 py-1 text-center text-sm text-[var(--chalk-text)]", children: notice });
|
|
7521
7593
|
return null;
|
|
7522
7594
|
}
|
|
7523
7595
|
function useTrueFor(value, milliseconds) {
|
|
@@ -7729,7 +7801,9 @@ function useCallToasts() {
|
|
|
7729
7801
|
label: "Try again",
|
|
7730
7802
|
onClick: () => {
|
|
7731
7803
|
void client.media.setScreenShareEnabled(true).then(
|
|
7732
|
-
() =>
|
|
7804
|
+
() => {
|
|
7805
|
+
setToasts((current) => current.filter((event) => event.id !== id));
|
|
7806
|
+
},
|
|
7733
7807
|
() => void 0
|
|
7734
7808
|
);
|
|
7735
7809
|
}
|
|
@@ -7760,19 +7834,1550 @@ function useCallLink(alone) {
|
|
|
7760
7834
|
return { open, toggle: () => setOpen((current) => !current), close: () => setOpen(false) };
|
|
7761
7835
|
}
|
|
7762
7836
|
|
|
7837
|
+
// src/components/board/board-toasts.ts
|
|
7838
|
+
import { createContext as createContext3, useCallback as useCallback12, useContext as useContext3, useEffect as useEffect34, useMemo as useMemo15 } from "react";
|
|
7839
|
+
var BoardToastContext = createContext3(() => void 0);
|
|
7840
|
+
function useBoardToast(lifetime) {
|
|
7841
|
+
const publish = useContext3(BoardToastContext);
|
|
7842
|
+
const scope = useMemo15(() => ({ active: true }), [lifetime]);
|
|
7843
|
+
useEffect34(() => {
|
|
7844
|
+
scope.active = true;
|
|
7845
|
+
return () => {
|
|
7846
|
+
scope.active = false;
|
|
7847
|
+
publish(null);
|
|
7848
|
+
};
|
|
7849
|
+
}, [publish, scope]);
|
|
7850
|
+
return useCallback12(
|
|
7851
|
+
(toast) => {
|
|
7852
|
+
if (scope.active) publish(toast);
|
|
7853
|
+
},
|
|
7854
|
+
[publish, scope]
|
|
7855
|
+
);
|
|
7856
|
+
}
|
|
7857
|
+
|
|
7858
|
+
// src/components/stage/SpaceStage.tsx
|
|
7859
|
+
import { useCallback as useCallback14, useEffect as useEffect38, useMemo as useMemo17, useState as useState44 } from "react";
|
|
7860
|
+
|
|
7861
|
+
// src/components/board/board-history.ts
|
|
7862
|
+
import { useEffect as useEffect35, useState as useState40 } from "react";
|
|
7863
|
+
import { observeEngineHistory } from "@q9labsai/chalk-whiteboard/react";
|
|
7864
|
+
function useEngineHistory(root, engine) {
|
|
7865
|
+
const [history, setHistory] = useState40({ canUndo: false, canRedo: false });
|
|
7866
|
+
useEffect35(() => {
|
|
7867
|
+
const canvas = root.current?.querySelector(".excalidraw");
|
|
7868
|
+
if (!engine || !canvas) return;
|
|
7869
|
+
return observeEngineHistory(engine.api, canvas, setHistory);
|
|
7870
|
+
}, [engine, root]);
|
|
7871
|
+
return history;
|
|
7872
|
+
}
|
|
7873
|
+
|
|
7874
|
+
// src/components/board/board-view-versions.ts
|
|
7875
|
+
var ViewVersions = class {
|
|
7876
|
+
#versions = /* @__PURE__ */ new Map();
|
|
7877
|
+
isNew(view) {
|
|
7878
|
+
if (!view.originParticipantId) return true;
|
|
7879
|
+
return view.originSequence > (this.#versions.get(`${view.originParticipantId}:${view.originEpoch ?? ""}`) ?? 0);
|
|
7880
|
+
}
|
|
7881
|
+
remember(view) {
|
|
7882
|
+
if (!view.originParticipantId || !this.isNew(view)) return;
|
|
7883
|
+
const origin = `${view.originParticipantId}:${view.originEpoch ?? ""}`;
|
|
7884
|
+
this.#versions.delete(origin);
|
|
7885
|
+
this.#versions.set(origin, view.originSequence);
|
|
7886
|
+
if (this.#versions.size > 256) {
|
|
7887
|
+
const oldest = this.#versions.keys().next().value;
|
|
7888
|
+
if (oldest !== void 0) this.#versions.delete(oldest);
|
|
7889
|
+
}
|
|
7890
|
+
}
|
|
7891
|
+
};
|
|
7892
|
+
|
|
7893
|
+
// src/components/board/Board.tsx
|
|
7894
|
+
import { WhiteboardCanvas as WhiteboardCanvas2 } from "@q9labsai/chalk-whiteboard/react";
|
|
7895
|
+
import { useCallback as useCallback13, useEffect as useEffect37, useMemo as useMemo16, useRef as useRef33, useState as useState43 } from "react";
|
|
7896
|
+
|
|
7897
|
+
// src/components/board/board-model.ts
|
|
7898
|
+
import { ChalkWhiteboardViewBounds } from "@q9labsai/chalk-client";
|
|
7899
|
+
var SURFACES = [
|
|
7900
|
+
{ id: "chalkboard", label: "Chalkboard" },
|
|
7901
|
+
{ id: "paper", label: "Paper" },
|
|
7902
|
+
{ id: "dots", label: "Dot grid" }
|
|
7903
|
+
];
|
|
7904
|
+
var ISLAND_PLACES = [
|
|
7905
|
+
{ id: "left", label: "Left" },
|
|
7906
|
+
{ id: "bottom", label: "Bottom" },
|
|
7907
|
+
{ id: "top", label: "Top" }
|
|
7908
|
+
];
|
|
7909
|
+
var PEN_COLORS = [
|
|
7910
|
+
{ id: "ink", label: "Ink" },
|
|
7911
|
+
{ id: "red", label: "Red" },
|
|
7912
|
+
{ id: "orange", label: "Orange" },
|
|
7913
|
+
{ id: "green", label: "Green" },
|
|
7914
|
+
{ id: "blue", label: "Blue" },
|
|
7915
|
+
{ id: "violet", label: "Violet" }
|
|
7916
|
+
];
|
|
7917
|
+
var PEN_HEX = { red: "#e23240", orange: "#c15a10", green: "#288a51", blue: "#1f7ad6", violet: "#935ae2" };
|
|
7918
|
+
var NOTE_FILL = { ink: "#f6e27a", red: "#f2a3a8", orange: "#f4be8c", green: "#a8dbb8", blue: "#a9cdf2", violet: "#cdb8f2" };
|
|
7919
|
+
var NOTE_TEXT = "#1b1c1e";
|
|
7920
|
+
var NOTE_SIZE = { width: 170, height: 150 };
|
|
7921
|
+
var NOTE_ROUNDNESS = { type: 3, value: 4 };
|
|
7922
|
+
var PEN_WIDTHS = [
|
|
7923
|
+
{ id: "thin", label: "Thin", units: 2.5 },
|
|
7924
|
+
{ id: "medium", label: "Medium", units: 4.5 },
|
|
7925
|
+
{ id: "thick", label: "Thick", units: 8 }
|
|
7926
|
+
];
|
|
7927
|
+
var SHAPES = [
|
|
7928
|
+
{ id: "rectangle", label: "Rectangle", key: "R" },
|
|
7929
|
+
{ id: "ellipse", label: "Ellipse", key: "O" },
|
|
7930
|
+
{ id: "diamond", label: "Diamond", key: "D" },
|
|
7931
|
+
{ id: "arrow", label: "Arrow", key: "A" },
|
|
7932
|
+
{ id: "line", label: "Line", key: "L" }
|
|
7933
|
+
];
|
|
7934
|
+
var TOOL_KEYS = { v: "selection", h: "hand", p: "pen", u: "highlighter", r: "rectangle", o: "ellipse", d: "diamond", a: "arrow", l: "line", t: "text", n: "note", e: "eraser", k: "laser" };
|
|
7935
|
+
var PERSON_COLORS = ["#1d70c5", "#8753d0", "#d02e3b", "#257f4b", "#b2530f", "#1b7979", "#c0307a", "#62751d", "#3b6fd4", "#a14fb5", "#b5512c", "#297f65"];
|
|
7936
|
+
function personColor(index) {
|
|
7937
|
+
return PERSON_COLORS[(index % PERSON_COLORS.length + PERSON_COLORS.length) % PERSON_COLORS.length];
|
|
7938
|
+
}
|
|
7939
|
+
var DARK_SURFACES = /* @__PURE__ */ new Set(["chalkboard", "dots-dark"]);
|
|
7940
|
+
function surfaceKey(surface, theme) {
|
|
7941
|
+
return surface === "dots" ? `dots-${theme}` : surface;
|
|
7942
|
+
}
|
|
7943
|
+
function isDarkSurface(surface, theme) {
|
|
7944
|
+
return DARK_SURFACES.has(surfaceKey(surface, theme));
|
|
7945
|
+
}
|
|
7946
|
+
function surfaceInk(surface, theme) {
|
|
7947
|
+
if (surface === "chalkboard") return "#f1f0ea";
|
|
7948
|
+
if (surface === "paper") return "#1b1c1f";
|
|
7949
|
+
return theme === "dark" ? "#ececee" : "#18181b";
|
|
7950
|
+
}
|
|
7951
|
+
function surfaceFill(surface, theme) {
|
|
7952
|
+
if (surface === "chalkboard") return "#1c2622";
|
|
7953
|
+
if (surface === "paper") return theme === "dark" ? "#eae6db" : "#f5f2ea";
|
|
7954
|
+
return theme === "dark" ? "#151619" : "#ffffff";
|
|
7955
|
+
}
|
|
7956
|
+
function selectionColor(surface, theme) {
|
|
7957
|
+
return isDarkSurface(surface, theme) ? "#7db8ff" : "#1f6fd0";
|
|
7958
|
+
}
|
|
7959
|
+
function penHex(color, surface, theme) {
|
|
7960
|
+
return color === "ink" ? surfaceInk(surface, theme) : PEN_HEX[color];
|
|
7961
|
+
}
|
|
7962
|
+
function highlighterStyle(color, width) {
|
|
7963
|
+
return { strokeColor: PEN_HEX[color === "ink" ? "orange" : color], strokeWidth: widthUnits(width) * 4, opacity: 40 };
|
|
7964
|
+
}
|
|
7965
|
+
var FREEHAND_SCALE = 4.25;
|
|
7966
|
+
function engineStrokeWidth(px, freehand) {
|
|
7967
|
+
return freehand ? px / FREEHAND_SCALE : px;
|
|
7968
|
+
}
|
|
7969
|
+
function widthUnits(width) {
|
|
7970
|
+
return PEN_WIDTHS.find((entry) => entry.id === width).units;
|
|
7971
|
+
}
|
|
7972
|
+
var MIN_ZOOM = 0.1;
|
|
7973
|
+
var MAX_ZOOM = 4;
|
|
7974
|
+
function clampZoom(zoom) {
|
|
7975
|
+
return Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));
|
|
7976
|
+
}
|
|
7977
|
+
function visibleView(camera, viewport) {
|
|
7978
|
+
const { maxCoordinate, maxExtent } = ChalkWhiteboardViewBounds;
|
|
7979
|
+
return {
|
|
7980
|
+
x: Math.max(-maxCoordinate, Math.min(maxCoordinate, -camera.scrollX)),
|
|
7981
|
+
y: Math.max(-maxCoordinate, Math.min(maxCoordinate, -camera.scrollY)),
|
|
7982
|
+
width: Math.min(maxExtent, viewport.width / camera.zoom),
|
|
7983
|
+
height: Math.min(maxExtent, viewport.height / camera.zoom)
|
|
7984
|
+
};
|
|
7985
|
+
}
|
|
7986
|
+
function cameraForView(view, viewport) {
|
|
7987
|
+
const zoom = clampZoom(Math.min(viewport.width / view.width, viewport.height / view.height));
|
|
7988
|
+
return { zoom, scrollX: viewport.width / (2 * zoom) - (view.x + view.width / 2), scrollY: viewport.height / (2 * zoom) - (view.y + view.height / 2) };
|
|
7989
|
+
}
|
|
7990
|
+
function zoomAroundCentre(camera, viewport, zoom) {
|
|
7991
|
+
const next = clampZoom(zoom);
|
|
7992
|
+
const centreX = viewport.width / 2 / camera.zoom - camera.scrollX;
|
|
7993
|
+
const centreY = viewport.height / 2 / camera.zoom - camera.scrollY;
|
|
7994
|
+
return { zoom: next, scrollX: viewport.width / 2 / next - centreX, scrollY: viewport.height / 2 / next - centreY };
|
|
7995
|
+
}
|
|
7996
|
+
function sameCamera(left, right) {
|
|
7997
|
+
return Math.abs(left.zoom - right.zoom) < 1e-4 && Math.abs(left.scrollX - right.scrollX) < 0.5 && Math.abs(left.scrollY - right.scrollY) < 0.5;
|
|
7998
|
+
}
|
|
7999
|
+
function mixCamera(from, to, progress) {
|
|
8000
|
+
const zoom = Math.exp(Math.log(from.zoom) + (Math.log(to.zoom) - Math.log(from.zoom)) * progress);
|
|
8001
|
+
return { zoom, scrollX: from.scrollX + (to.scrollX - from.scrollX) * progress, scrollY: from.scrollY + (to.scrollY - from.scrollY) * progress };
|
|
8002
|
+
}
|
|
8003
|
+
function springStep(offset, velocity, rate, seconds) {
|
|
8004
|
+
const decay = Math.exp(-rate * seconds);
|
|
8005
|
+
const drift = (velocity + rate * offset) * seconds;
|
|
8006
|
+
return [(offset + drift) * decay, (velocity - rate * drift) * decay];
|
|
8007
|
+
}
|
|
8008
|
+
function readStored(key, allowed, fallback3) {
|
|
8009
|
+
try {
|
|
8010
|
+
const value = globalThis.localStorage?.getItem(key);
|
|
8011
|
+
return allowed.find((entry) => entry.id === value)?.id ?? fallback3;
|
|
8012
|
+
} catch {
|
|
8013
|
+
return fallback3;
|
|
8014
|
+
}
|
|
8015
|
+
}
|
|
8016
|
+
function writeStored(key, value) {
|
|
8017
|
+
try {
|
|
8018
|
+
globalThis.localStorage?.setItem(key, value);
|
|
8019
|
+
} catch {
|
|
8020
|
+
}
|
|
8021
|
+
}
|
|
8022
|
+
|
|
8023
|
+
// src/components/board/board-engine.ts
|
|
8024
|
+
var TEXT_FONT = 6;
|
|
8025
|
+
var NOTE_FONT_SIZE = 18;
|
|
8026
|
+
var BOUND_TEXT_PADDING = 5;
|
|
8027
|
+
function engineTool(tool) {
|
|
8028
|
+
if (tool === "pen" || tool === "highlighter") return { type: "freedraw", locked: true };
|
|
8029
|
+
if (tool === "note" || tool === "selection") return { type: "selection" };
|
|
8030
|
+
return { type: tool, locked: true };
|
|
8031
|
+
}
|
|
8032
|
+
function penStyle(tool, pen) {
|
|
8033
|
+
if (tool === "highlighter") return highlighterStyle(pen.color, pen.width);
|
|
8034
|
+
return { strokeColor: penHex(pen.color, pen.surface, pen.theme), strokeWidth: widthUnits(pen.width), opacity: 100 };
|
|
8035
|
+
}
|
|
8036
|
+
function applyTool(engine, tool, pen) {
|
|
8037
|
+
const style = penStyle(tool, pen);
|
|
8038
|
+
engine.api.updateScene({
|
|
8039
|
+
appState: {
|
|
8040
|
+
currentItemStrokeColor: style.strokeColor,
|
|
8041
|
+
currentItemStrokeWidth: engineStrokeWidth(style.strokeWidth, tool === "pen" || tool === "highlighter"),
|
|
8042
|
+
currentItemOpacity: style.opacity,
|
|
8043
|
+
currentItemRoughness: 0,
|
|
8044
|
+
currentItemFontFamily: TEXT_FONT,
|
|
8045
|
+
currentItemBackgroundColor: "transparent"
|
|
8046
|
+
}
|
|
8047
|
+
});
|
|
8048
|
+
engine.api.setActiveTool(engineTool(tool));
|
|
8049
|
+
}
|
|
8050
|
+
function selectionStyle(element, pen, changes) {
|
|
8051
|
+
if (element.type === "rectangle" && element.customData?.chalkNote) {
|
|
8052
|
+
if (!changes.color) return {};
|
|
8053
|
+
return { strokeColor: NOTE_FILL[changes.color], backgroundColor: NOTE_FILL[changes.color] };
|
|
8054
|
+
}
|
|
8055
|
+
const style = penStyle(element.type === "freedraw" && element.opacity === 40 ? "highlighter" : "pen", pen);
|
|
8056
|
+
return {
|
|
8057
|
+
...changes.color ? { strokeColor: style.strokeColor } : {},
|
|
8058
|
+
...changes.width ? { strokeWidth: engineStrokeWidth(style.strokeWidth, element.type === "freedraw") } : {}
|
|
8059
|
+
};
|
|
8060
|
+
}
|
|
8061
|
+
function styleChanged(element, updates) {
|
|
8062
|
+
return updates.strokeColor !== void 0 && updates.strokeColor !== element.strokeColor || updates.strokeWidth !== void 0 && updates.strokeWidth !== element.strokeWidth || updates.backgroundColor !== void 0 && updates.backgroundColor !== element.backgroundColor;
|
|
8063
|
+
}
|
|
8064
|
+
function restyleSelection(engine, pen, changes) {
|
|
8065
|
+
const { api, excalidraw } = engine;
|
|
8066
|
+
const selected = api.getAppState().selectedElementIds;
|
|
8067
|
+
let changed = false;
|
|
8068
|
+
const elements = api.getSceneElementsIncludingDeleted().map((element) => {
|
|
8069
|
+
if (!selected[element.id] || element.isDeleted) return element;
|
|
8070
|
+
const updates = selectionStyle(element, pen, changes);
|
|
8071
|
+
if (!styleChanged(element, updates)) return element;
|
|
8072
|
+
changed = true;
|
|
8073
|
+
return excalidraw.newElementWith(element, updates);
|
|
8074
|
+
});
|
|
8075
|
+
if (changed) api.updateScene({ elements, captureUpdate: excalidraw.CaptureUpdateAction.IMMEDIATELY });
|
|
8076
|
+
return changed;
|
|
8077
|
+
}
|
|
8078
|
+
function placeNote(engine, container, point, color) {
|
|
8079
|
+
const { api, excalidraw } = engine;
|
|
8080
|
+
const fill = NOTE_FILL[color];
|
|
8081
|
+
const [note] = excalidraw.convertToExcalidrawElements([
|
|
8082
|
+
{ type: "rectangle", x: point.x - NOTE_SIZE.width / 2, y: point.y - NOTE_SIZE.height / 2, width: NOTE_SIZE.width, height: NOTE_SIZE.height, backgroundColor: fill, strokeColor: fill, fillStyle: "solid", strokeWidth: 1, roughness: 0, roundness: NOTE_ROUNDNESS, customData: { chalkNote: true } }
|
|
8083
|
+
]);
|
|
8084
|
+
if (!note) return;
|
|
8085
|
+
api.updateScene({
|
|
8086
|
+
elements: [...api.getSceneElementsIncludingDeleted(), note],
|
|
8087
|
+
appState: { selectedElementIds: { [note.id]: true }, currentItemStrokeColor: NOTE_TEXT, currentItemFontSize: NOTE_FONT_SIZE, currentItemTextAlign: "left", currentItemFontFamily: TEXT_FONT },
|
|
8088
|
+
captureUpdate: excalidraw.CaptureUpdateAction.IMMEDIATELY
|
|
8089
|
+
});
|
|
8090
|
+
api.setActiveTool({ type: "selection" });
|
|
8091
|
+
requestAnimationFrame(() => container.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", code: "Enter", bubbles: true })));
|
|
8092
|
+
}
|
|
8093
|
+
function settleNoteText(engine) {
|
|
8094
|
+
const { api, excalidraw } = engine;
|
|
8095
|
+
const elements = api.getSceneElementsIncludingDeleted();
|
|
8096
|
+
const notes = new Map(elements.filter((element) => element.type === "rectangle" && element.customData?.chalkNote).map((element) => [element.id, element]));
|
|
8097
|
+
let changed = false;
|
|
8098
|
+
const next = elements.map((element) => {
|
|
8099
|
+
if (element.type !== "text" || !element.containerId || element.isDeleted) return element;
|
|
8100
|
+
const note = notes.get(element.containerId);
|
|
8101
|
+
if (!note) return element;
|
|
8102
|
+
const x = note.x + BOUND_TEXT_PADDING;
|
|
8103
|
+
const y = note.y + BOUND_TEXT_PADDING;
|
|
8104
|
+
if (element.verticalAlign === "top" && element.textAlign === "left" && element.strokeColor === NOTE_TEXT && element.x === x && element.y === y) return element;
|
|
8105
|
+
changed = true;
|
|
8106
|
+
return excalidraw.newElementWith(element, { verticalAlign: "top", textAlign: "left", strokeColor: NOTE_TEXT, x, y });
|
|
8107
|
+
});
|
|
8108
|
+
if (changed) api.updateScene({ elements: next, captureUpdate: excalidraw.CaptureUpdateAction.NEVER });
|
|
8109
|
+
}
|
|
8110
|
+
var isApple = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform);
|
|
8111
|
+
function pressHistory(container, redo) {
|
|
8112
|
+
const apple = isApple();
|
|
8113
|
+
container.dispatchEvent(new KeyboardEvent("keydown", { key: redo ? "Z" : "z", code: "KeyZ", shiftKey: redo, metaKey: apple, ctrlKey: !apple, bubbles: true }));
|
|
8114
|
+
}
|
|
8115
|
+
function readCamera(engine) {
|
|
8116
|
+
const state = engine.api.getAppState();
|
|
8117
|
+
return { scrollX: state.scrollX, scrollY: state.scrollY, zoom: state.zoom.value };
|
|
8118
|
+
}
|
|
8119
|
+
function setCamera(engine, camera) {
|
|
8120
|
+
engine.api.updateScene({ appState: { scrollX: camera.scrollX, scrollY: camera.scrollY, zoom: { value: camera.zoom } } });
|
|
8121
|
+
}
|
|
8122
|
+
function hasContent(engine) {
|
|
8123
|
+
return engine.api.getSceneElements().length > 0;
|
|
8124
|
+
}
|
|
8125
|
+
async function boardImage(engine, background) {
|
|
8126
|
+
const { api, excalidraw } = engine;
|
|
8127
|
+
return excalidraw.exportToBlob({ elements: api.getSceneElements(), files: api.getFiles(), appState: { exportBackground: true, viewBackgroundColor: background }, mimeType: "image/png", exportPadding: 24 });
|
|
8128
|
+
}
|
|
8129
|
+
function sceneSignature(engine) {
|
|
8130
|
+
return engine.excalidraw.getSceneVersion(engine.api.getSceneElementsIncludingDeleted());
|
|
8131
|
+
}
|
|
8132
|
+
|
|
8133
|
+
// src/components/board/BoardCursors.tsx
|
|
8134
|
+
import { useEffect as useEffect36, useRef as useRef31, useState as useState41 } from "react";
|
|
8135
|
+
|
|
8136
|
+
// src/components/board/BoardIcon.tsx
|
|
8137
|
+
import { Fragment as Fragment28, jsx as jsx69, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
8138
|
+
var GLYPHS2 = {
|
|
8139
|
+
select: /* @__PURE__ */ jsx69("path", { d: "M5 3l14 7-6.5 2L10 19z" }),
|
|
8140
|
+
hand: /* @__PURE__ */ jsx69("path", { d: "M18 11V6a2 2 0 0 0-4 0M14 10V4a2 2 0 0 0-4 0v2M10 10.5V6a2 2 0 0 0-4 0v8M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.9-6-2.3l-3.6-3.6a2 2 0 0 1 2.8-2.8L7 15" }),
|
|
8141
|
+
pen: /* @__PURE__ */ jsx69("path", { d: "M12 20h9M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4z" }),
|
|
8142
|
+
highlighter: /* @__PURE__ */ jsxs44(Fragment28, { children: [
|
|
8143
|
+
/* @__PURE__ */ jsx69("path", { d: "M9 11l-6 6v3h9l3-3" }),
|
|
8144
|
+
/* @__PURE__ */ jsx69("path", { d: "M22 12l-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4" })
|
|
8145
|
+
] }),
|
|
8146
|
+
rectangle: /* @__PURE__ */ jsx69("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
|
|
8147
|
+
ellipse: /* @__PURE__ */ jsx69("ellipse", { cx: "12", cy: "12", rx: "9", ry: "6.5" }),
|
|
8148
|
+
diamond: /* @__PURE__ */ jsx69("path", { d: "M12 3l9 9-9 9-9-9z" }),
|
|
8149
|
+
arrow: /* @__PURE__ */ jsx69("path", { d: "M5 19L19 5M9 5h10v10" }),
|
|
8150
|
+
line: /* @__PURE__ */ jsx69("path", { d: "M4 20L20 4" }),
|
|
8151
|
+
text: /* @__PURE__ */ jsx69("path", { d: "M5 6V4h14v2M12 4v16M9 20h6" }),
|
|
8152
|
+
note: /* @__PURE__ */ jsxs44(Fragment28, { children: [
|
|
8153
|
+
/* @__PURE__ */ jsx69("path", { d: "M5 3h14a2 2 0 0 1 2 2v9l-7 7H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z" }),
|
|
8154
|
+
/* @__PURE__ */ jsx69("path", { d: "M14 21v-5a2 2 0 0 1 2-2h5" })
|
|
8155
|
+
] }),
|
|
8156
|
+
eraser: /* @__PURE__ */ jsx69("path", { d: "M7 21h10M5.5 13.5l7-7a2 2 0 0 1 2.8 0l3.2 3.2a2 2 0 0 1 0 2.8L11 19H8l-2.5-2.5a2 2 0 0 1 0-3z" }),
|
|
8157
|
+
laser: /* @__PURE__ */ jsxs44(Fragment28, { children: [
|
|
8158
|
+
/* @__PURE__ */ jsx69("circle", { cx: "12", cy: "12", r: "2.5", fill: "currentColor" }),
|
|
8159
|
+
/* @__PURE__ */ jsx69("path", { d: "M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M18.4 5.6l-2.1 2.1M7.7 16.3l-2.1 2.1" })
|
|
8160
|
+
] }),
|
|
8161
|
+
undo: /* @__PURE__ */ jsxs44(Fragment28, { children: [
|
|
8162
|
+
/* @__PURE__ */ jsx69("path", { d: "M9 14L4 9l5-5" }),
|
|
8163
|
+
/* @__PURE__ */ jsx69("path", { d: "M4 9h10.5a5.5 5.5 0 0 1 0 11H11" })
|
|
8164
|
+
] }),
|
|
8165
|
+
redo: /* @__PURE__ */ jsxs44(Fragment28, { children: [
|
|
8166
|
+
/* @__PURE__ */ jsx69("path", { d: "M15 14l5-5-5-5" }),
|
|
8167
|
+
/* @__PURE__ */ jsx69("path", { d: "M20 9H9.5a5.5 5.5 0 0 0 0 11H13" })
|
|
8168
|
+
] }),
|
|
8169
|
+
minus: /* @__PURE__ */ jsx69("path", { d: "M5 12h14" }),
|
|
8170
|
+
plus: /* @__PURE__ */ jsx69("path", { d: "M12 5v14M5 12h14" }),
|
|
8171
|
+
fit: /* @__PURE__ */ jsx69("path", { d: "M4 9V5a1 1 0 0 1 1-1h4M20 9V5a1 1 0 0 0-1-1h-4M4 15v4a1 1 0 0 0 1 1h4M20 15v4a1 1 0 0 1-1 1h-4" }),
|
|
8172
|
+
download: /* @__PURE__ */ jsx69("path", { d: "M12 3v12M7 10l5 5 5-5M4 21h16" }),
|
|
8173
|
+
copy: /* @__PURE__ */ jsxs44(Fragment28, { children: [
|
|
8174
|
+
/* @__PURE__ */ jsx69("rect", { x: "9", y: "9", width: "12", height: "12", rx: "2" }),
|
|
8175
|
+
/* @__PURE__ */ jsx69("path", { d: "M5 15V5a2 2 0 0 1 2-2h10" })
|
|
8176
|
+
] }),
|
|
8177
|
+
trash: /* @__PURE__ */ jsx69("path", { d: "M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3" }),
|
|
8178
|
+
close: /* @__PURE__ */ jsx69("path", { d: "M6 6l12 12M18 6L6 18" }),
|
|
8179
|
+
check: /* @__PURE__ */ jsx69("path", { d: "M5 12l5 5 9-10" }),
|
|
8180
|
+
target: /* @__PURE__ */ jsxs44(Fragment28, { children: [
|
|
8181
|
+
/* @__PURE__ */ jsx69("circle", { cx: "12", cy: "12", r: "9" }),
|
|
8182
|
+
/* @__PURE__ */ jsx69("circle", { cx: "12", cy: "12", r: "4" })
|
|
8183
|
+
] }),
|
|
8184
|
+
eye: /* @__PURE__ */ jsxs44(Fragment28, { children: [
|
|
8185
|
+
/* @__PURE__ */ jsx69("path", { d: "M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z" }),
|
|
8186
|
+
/* @__PURE__ */ jsx69("circle", { cx: "12", cy: "12", r: "3" })
|
|
8187
|
+
] }),
|
|
8188
|
+
wifiOff: /* @__PURE__ */ jsx69("path", { d: "M2 2l20 20M8.5 16.5a5 5 0 0 1 7 0M5 12.9a10 10 0 0 1 5-2.7M10.7 5.1A16 16 0 0 1 22 9M2 8.8a15 15 0 0 1 4.2-2.5M12 20h.01" }),
|
|
8189
|
+
lock: /* @__PURE__ */ jsxs44(Fragment28, { children: [
|
|
8190
|
+
/* @__PURE__ */ jsx69("rect", { x: "5", y: "11", width: "14", height: "9", rx: "2" }),
|
|
8191
|
+
/* @__PURE__ */ jsx69("path", { d: "M8 11V8a4 4 0 0 1 8 0v3" })
|
|
8192
|
+
] }),
|
|
8193
|
+
more: /* @__PURE__ */ jsxs44(Fragment28, { children: [
|
|
8194
|
+
/* @__PURE__ */ jsx69("circle", { cx: "5", cy: "12", r: "1.2" }),
|
|
8195
|
+
/* @__PURE__ */ jsx69("circle", { cx: "12", cy: "12", r: "1.2" }),
|
|
8196
|
+
/* @__PURE__ */ jsx69("circle", { cx: "19", cy: "12", r: "1.2" })
|
|
8197
|
+
] }),
|
|
8198
|
+
chevron: /* @__PURE__ */ jsx69("path", { d: "M6 9l6 6 6-6" })
|
|
8199
|
+
};
|
|
8200
|
+
function BoardIcon({ name, size = 20, className }) {
|
|
8201
|
+
return /* @__PURE__ */ jsx69("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": "true", children: GLYPHS2[name] });
|
|
8202
|
+
}
|
|
8203
|
+
function GripIcon({ className }) {
|
|
8204
|
+
return /* @__PURE__ */ jsx69("svg", { width: "10", height: "18", viewBox: "0 0 10 18", fill: "currentColor", className, "aria-hidden": "true", children: [3, 9, 15].flatMap((y) => [/* @__PURE__ */ jsx69("circle", { cx: "2.5", cy: y, r: "1.5" }, `l${y}`), /* @__PURE__ */ jsx69("circle", { cx: "7.5", cy: y, r: "1.5" }, `r${y}`)]) });
|
|
8205
|
+
}
|
|
8206
|
+
function CursorArrow({ color }) {
|
|
8207
|
+
const path = "M3.2 2.6V20.2L8 15.7L11.3 22.6L14.6 21L11.4 14.2L17.9 13.6Z";
|
|
8208
|
+
return /* @__PURE__ */ jsxs44("svg", { className: "chalk-board-cursor-arrow", width: "22", height: "26", viewBox: "0 0 22 26", fill: "none", "aria-hidden": "true", children: [
|
|
8209
|
+
/* @__PURE__ */ jsx69("path", { d: path, fill: "#fff", stroke: "#fff", strokeWidth: "5", strokeLinejoin: "round" }),
|
|
8210
|
+
/* @__PURE__ */ jsx69("path", { d: path, fill: color, stroke: color, strokeWidth: "2.2", strokeLinejoin: "round" })
|
|
8211
|
+
] });
|
|
8212
|
+
}
|
|
8213
|
+
|
|
8214
|
+
// src/components/board/BoardCursors.tsx
|
|
8215
|
+
import { jsx as jsx70, jsxs as jsxs45 } from "react/jsx-runtime";
|
|
8216
|
+
var REST_RATE = 16;
|
|
8217
|
+
var STALE_MS = 1e4;
|
|
8218
|
+
var NAME_HOLD_MS = 700;
|
|
8219
|
+
var MOVING_PX_PER_SECOND = 25;
|
|
8220
|
+
var HOVER_PX = 26;
|
|
8221
|
+
function rememberCursor(cursors, cursor) {
|
|
8222
|
+
const now = Date.now();
|
|
8223
|
+
for (const [id, previous] of cursors) if (now - Date.parse(previous.occurredAt) > STALE_MS) cursors.delete(id);
|
|
8224
|
+
cursors.delete(cursor.participantId);
|
|
8225
|
+
if (now - Date.parse(cursor.occurredAt) <= STALE_MS) cursors.set(cursor.participantId, cursor);
|
|
8226
|
+
if (cursors.size > 256) {
|
|
8227
|
+
const oldest = cursors.keys().next().value;
|
|
8228
|
+
if (oldest !== void 0) cursors.delete(oldest);
|
|
8229
|
+
}
|
|
8230
|
+
}
|
|
8231
|
+
function BoardCursors({ cursorsRef, cameraRef, colorFor, hidden }) {
|
|
8232
|
+
const layer = useRef31(null);
|
|
8233
|
+
const [people, setPeople] = useState41([]);
|
|
8234
|
+
const tracked = useRef31(/* @__PURE__ */ new Map());
|
|
8235
|
+
const pointer = useRef31(null);
|
|
8236
|
+
useEffect36(() => {
|
|
8237
|
+
let frame = 0;
|
|
8238
|
+
let last = performance.now();
|
|
8239
|
+
const step = (now) => {
|
|
8240
|
+
const seconds = Math.min(0.1, (now - last) / 1e3);
|
|
8241
|
+
last = now;
|
|
8242
|
+
const camera = cameraRef.current;
|
|
8243
|
+
const roster = cursorsRef.current;
|
|
8244
|
+
let rosterChanged = false;
|
|
8245
|
+
for (const [id, cursor] of roster) {
|
|
8246
|
+
if (Date.now() - Date.parse(cursor.occurredAt) > STALE_MS) {
|
|
8247
|
+
roster.delete(id);
|
|
8248
|
+
continue;
|
|
8249
|
+
}
|
|
8250
|
+
let state = tracked.current.get(id);
|
|
8251
|
+
if (!state) {
|
|
8252
|
+
state = { name: cursor.displayName, x: cursor.x, y: cursor.y, vx: 0, vy: 0, tx: cursor.x, ty: cursor.y, movingUntil: 0, lastX: cursor.x, lastY: cursor.y };
|
|
8253
|
+
tracked.current.set(id, state);
|
|
8254
|
+
rosterChanged = true;
|
|
8255
|
+
}
|
|
8256
|
+
if (state.name !== cursor.displayName) {
|
|
8257
|
+
state.name = cursor.displayName;
|
|
8258
|
+
rosterChanged = true;
|
|
8259
|
+
}
|
|
8260
|
+
if (cursor.x !== state.tx || cursor.y !== state.ty) {
|
|
8261
|
+
state.tx = cursor.x;
|
|
8262
|
+
state.ty = cursor.y;
|
|
8263
|
+
}
|
|
8264
|
+
}
|
|
8265
|
+
for (const [id, state] of tracked.current) {
|
|
8266
|
+
if (!roster.has(id)) {
|
|
8267
|
+
tracked.current.delete(id);
|
|
8268
|
+
roster.delete(id);
|
|
8269
|
+
rosterChanged = true;
|
|
8270
|
+
continue;
|
|
8271
|
+
}
|
|
8272
|
+
const [dx, vx] = springStep(state.x - state.tx, state.vx, REST_RATE, seconds);
|
|
8273
|
+
const [dy, vy] = springStep(state.y - state.ty, state.vy, REST_RATE, seconds);
|
|
8274
|
+
state.x = state.tx + dx;
|
|
8275
|
+
state.y = state.ty + dy;
|
|
8276
|
+
state.vx = vx;
|
|
8277
|
+
state.vy = vy;
|
|
8278
|
+
const screenX = (state.x + camera.scrollX) * camera.zoom;
|
|
8279
|
+
const screenY = (state.y + camera.scrollY) * camera.zoom;
|
|
8280
|
+
const speed = Math.hypot(screenX - state.lastX, screenY - state.lastY) / Math.max(seconds, 1e-3);
|
|
8281
|
+
state.lastX = screenX;
|
|
8282
|
+
state.lastY = screenY;
|
|
8283
|
+
if (speed > MOVING_PX_PER_SECOND) state.movingUntil = now + NAME_HOLD_MS;
|
|
8284
|
+
const element = layer.current?.querySelector(`[data-cursor="${CSS.escape(id)}"]`);
|
|
8285
|
+
if (!element) continue;
|
|
8286
|
+
element.style.transform = `translate3d(${screenX}px, ${screenY}px, 0)`;
|
|
8287
|
+
const near = pointer.current !== null && Math.hypot(pointer.current.x - screenX, pointer.current.y - screenY) < HOVER_PX;
|
|
8288
|
+
element.dataset.named = String(near || now < state.movingUntil);
|
|
8289
|
+
}
|
|
8290
|
+
if (rosterChanged) setPeople([...roster.values()].filter((cursor) => tracked.current.has(cursor.participantId)).map((cursor) => ({ id: cursor.participantId, name: cursor.displayName })));
|
|
8291
|
+
frame = requestAnimationFrame(step);
|
|
8292
|
+
};
|
|
8293
|
+
frame = requestAnimationFrame(step);
|
|
8294
|
+
return () => cancelAnimationFrame(frame);
|
|
8295
|
+
}, [cameraRef, cursorsRef]);
|
|
8296
|
+
useEffect36(() => {
|
|
8297
|
+
const board = layer.current?.parentElement;
|
|
8298
|
+
if (!board) return;
|
|
8299
|
+
const move = (event) => {
|
|
8300
|
+
const rect = board.getBoundingClientRect();
|
|
8301
|
+
pointer.current = { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
8302
|
+
};
|
|
8303
|
+
const leave = () => pointer.current = null;
|
|
8304
|
+
board.addEventListener("pointermove", move);
|
|
8305
|
+
board.addEventListener("pointerleave", leave);
|
|
8306
|
+
return () => {
|
|
8307
|
+
board.removeEventListener("pointermove", move);
|
|
8308
|
+
board.removeEventListener("pointerleave", leave);
|
|
8309
|
+
};
|
|
8310
|
+
}, []);
|
|
8311
|
+
return /* @__PURE__ */ jsx70("div", { ref: layer, className: "chalk-board-cursors", "aria-hidden": "true", "data-hidden": hidden || void 0, children: people.map((person) => /* @__PURE__ */ jsxs45("div", { className: "chalk-board-cursor", "data-cursor": person.id, children: [
|
|
8312
|
+
/* @__PURE__ */ jsx70(CursorArrow, { color: colorFor(person.id) }),
|
|
8313
|
+
/* @__PURE__ */ jsxs45("span", { className: "chalk-board-cursor-tag", children: [
|
|
8314
|
+
/* @__PURE__ */ jsx70("span", { className: "chalk-board-cursor-name", style: { background: colorFor(person.id) }, children: person.name.split(" ")[0] }),
|
|
8315
|
+
/* @__PURE__ */ jsx70(Avatar, { name: person.name, size: "xs", animated: false, className: "chalk-board-cursor-avatar" })
|
|
8316
|
+
] })
|
|
8317
|
+
] }, person.id)) });
|
|
8318
|
+
}
|
|
8319
|
+
|
|
8320
|
+
// src/components/board/BoardIsland.tsx
|
|
8321
|
+
import { useLayoutEffect as useLayoutEffect4, useRef as useRef32, useState as useState42 } from "react";
|
|
8322
|
+
import { Fragment as Fragment29, jsx as jsx71, jsxs as jsxs46 } from "react/jsx-runtime";
|
|
8323
|
+
var TOOLS = [
|
|
8324
|
+
[
|
|
8325
|
+
{ tool: "selection", label: "Select", key: "V", glyph: "select" },
|
|
8326
|
+
{ tool: "hand", label: "Hand", key: "H", glyph: "hand" }
|
|
8327
|
+
],
|
|
8328
|
+
[
|
|
8329
|
+
{ tool: "pen", label: "Pen", key: "P", glyph: "pen" },
|
|
8330
|
+
{ tool: "highlighter", label: "Highlighter", key: "U", glyph: "highlighter" }
|
|
8331
|
+
],
|
|
8332
|
+
[
|
|
8333
|
+
{ tool: "text", label: "Text", key: "T", glyph: "text" },
|
|
8334
|
+
{ tool: "note", label: "Sticky note", key: "N", glyph: "note" }
|
|
8335
|
+
],
|
|
8336
|
+
[
|
|
8337
|
+
{ tool: "eraser", label: "Eraser", key: "E", glyph: "eraser" },
|
|
8338
|
+
{ tool: "laser", label: "Laser pointer", key: "K", glyph: "laser" }
|
|
8339
|
+
]
|
|
8340
|
+
];
|
|
8341
|
+
var LONG_PRESS_MS = 500;
|
|
8342
|
+
var DRAG_START_PX = 4;
|
|
8343
|
+
function Tip({ label, keyName, children }) {
|
|
8344
|
+
return /* @__PURE__ */ jsxs46(ChalkTooltip, { trigger: children, children: [
|
|
8345
|
+
label,
|
|
8346
|
+
keyName ? /* @__PURE__ */ jsx71("kbd", { className: "chalk-board-kbd", children: keyName }) : null
|
|
8347
|
+
] });
|
|
8348
|
+
}
|
|
8349
|
+
function ToolButton({ entry, active, onTool }) {
|
|
8350
|
+
return /* @__PURE__ */ jsx71(Tip, { label: entry.label, keyName: entry.key, children: /* @__PURE__ */ jsx71("button", { type: "button", className: "chalk-board-tool", "aria-label": entry.label, "aria-keyshortcuts": entry.key, "aria-pressed": active, onClick: () => onTool(entry.tool), children: /* @__PURE__ */ jsx71(BoardIcon, { name: entry.glyph }) }) });
|
|
8351
|
+
}
|
|
8352
|
+
function ShapesButton({ tool, shape, onTool, side }) {
|
|
8353
|
+
const active = SHAPES.some((entry) => entry.id === tool);
|
|
8354
|
+
return /* @__PURE__ */ jsx71(
|
|
8355
|
+
ChalkMenu,
|
|
8356
|
+
{
|
|
8357
|
+
side,
|
|
8358
|
+
trigger: /* @__PURE__ */ jsxs46("button", { type: "button", className: "chalk-board-tool", "aria-label": "Shapes", "aria-keyshortcuts": "R", "aria-pressed": active, children: [
|
|
8359
|
+
/* @__PURE__ */ jsx71(BoardIcon, { name: shape }),
|
|
8360
|
+
/* @__PURE__ */ jsx71(BoardIcon, { name: "chevron", size: 9, className: "chalk-board-tool-caret" })
|
|
8361
|
+
] }),
|
|
8362
|
+
children: SHAPES.map((entry) => /* @__PURE__ */ jsxs46(ChalkMenuItem, { checked: tool === entry.id, onClick: () => onTool(entry.id), children: [
|
|
8363
|
+
/* @__PURE__ */ jsx71(BoardIcon, { name: entry.id, size: 18 }),
|
|
8364
|
+
entry.label,
|
|
8365
|
+
/* @__PURE__ */ jsx71("kbd", { className: "chalk-board-menu-kbd", children: entry.key })
|
|
8366
|
+
] }, entry.id))
|
|
8367
|
+
}
|
|
8368
|
+
);
|
|
8369
|
+
}
|
|
8370
|
+
function ColorPanel({ color, width, surface, theme, onColor, onWidth }) {
|
|
8371
|
+
return /* @__PURE__ */ jsxs46("div", { className: "chalk-board-colors", children: [
|
|
8372
|
+
/* @__PURE__ */ jsx71("div", { role: "radiogroup", "aria-label": "Colour", className: "chalk-board-swatches", children: PEN_COLORS.map((entry) => /* @__PURE__ */ jsx71("button", { type: "button", role: "radio", "aria-checked": color === entry.id, "aria-label": entry.label, className: "chalk-board-swatch", onClick: () => onColor(entry.id), children: /* @__PURE__ */ jsx71("i", { style: { background: penHex(entry.id, surface, theme) } }) }, entry.id)) }),
|
|
8373
|
+
/* @__PURE__ */ jsx71("div", { role: "radiogroup", "aria-label": "Stroke width", className: "chalk-board-swatches", children: PEN_WIDTHS.map((entry) => /* @__PURE__ */ jsx71("button", { type: "button", role: "radio", "aria-checked": width === entry.id, "aria-label": entry.label, className: "chalk-board-width", onClick: () => onWidth(entry.id), children: /* @__PURE__ */ jsx71("i", { style: { height: entry.units } }) }, entry.id)) })
|
|
8374
|
+
] });
|
|
8375
|
+
}
|
|
8376
|
+
function ColorButton(props) {
|
|
8377
|
+
const swatch = penHex(props.color, props.surface, props.theme);
|
|
8378
|
+
return /* @__PURE__ */ jsx71(
|
|
8379
|
+
ChalkPopover,
|
|
8380
|
+
{
|
|
8381
|
+
label: "Colour and width",
|
|
8382
|
+
tooltip: "Colour and width",
|
|
8383
|
+
open: props.open,
|
|
8384
|
+
onOpenChange: props.onOpenChange,
|
|
8385
|
+
className: "chalk-board-color-popover",
|
|
8386
|
+
trigger: props.trigger ?? /* @__PURE__ */ jsx71("button", { type: "button", className: "chalk-board-tool", "aria-label": `Colour: ${PEN_COLORS.find((entry) => entry.id === props.color).label}`, children: /* @__PURE__ */ jsx71("span", { className: "chalk-board-tool-swatch", style: { background: swatch } }) }),
|
|
8387
|
+
children: /* @__PURE__ */ jsx71(ColorPanel, { ...props })
|
|
8388
|
+
}
|
|
8389
|
+
);
|
|
8390
|
+
}
|
|
8391
|
+
function roveFocus(event, vertical) {
|
|
8392
|
+
const back = vertical ? "ArrowUp" : "ArrowLeft";
|
|
8393
|
+
const forward = vertical ? "ArrowDown" : "ArrowRight";
|
|
8394
|
+
if (event.key !== back && event.key !== forward && event.key !== "Home" && event.key !== "End") return;
|
|
8395
|
+
const buttons = [...event.currentTarget.querySelectorAll("button:not(:disabled)")];
|
|
8396
|
+
const index = buttons.findIndex((button) => button === document.activeElement);
|
|
8397
|
+
if (index < 0) return;
|
|
8398
|
+
event.preventDefault();
|
|
8399
|
+
event.stopPropagation();
|
|
8400
|
+
const next = event.key === "Home" ? 0 : event.key === "End" ? buttons.length - 1 : (index + (event.key === forward ? 1 : -1) + buttons.length) % buttons.length;
|
|
8401
|
+
buttons[next]?.focus();
|
|
8402
|
+
}
|
|
8403
|
+
function Separator() {
|
|
8404
|
+
return /* @__PURE__ */ jsx71("span", { className: "chalk-board-separator", "aria-hidden": "true" });
|
|
8405
|
+
}
|
|
8406
|
+
function ToolRow({ tool, shape, onTool, place, canUndo, canRedo, onUndo, onRedo, ...colors }) {
|
|
8407
|
+
const groups = TOOLS.map((group, index) => /* @__PURE__ */ jsxs46("span", { className: "chalk-board-group", children: [
|
|
8408
|
+
group.map((entry) => /* @__PURE__ */ jsx71(ToolButton, { entry, active: tool === entry.tool, onTool }, entry.tool)),
|
|
8409
|
+
index === 1 ? /* @__PURE__ */ jsx71(ShapesButton, { tool, shape, onTool, side: place === "top" ? "bottom" : "top" }) : null
|
|
8410
|
+
] }, index));
|
|
8411
|
+
return /* @__PURE__ */ jsxs46(Fragment29, { children: [
|
|
8412
|
+
groups.flatMap((group, index) => [index > 0 ? /* @__PURE__ */ jsx71(Separator, {}, `s${index}`) : null, group]),
|
|
8413
|
+
/* @__PURE__ */ jsx71(Separator, {}),
|
|
8414
|
+
/* @__PURE__ */ jsx71(Tip, { label: "Undo", keyName: "\u2318Z", children: /* @__PURE__ */ jsx71("button", { type: "button", className: "chalk-board-tool", "aria-label": "Undo", disabled: !canUndo, onClick: onUndo, children: /* @__PURE__ */ jsx71(BoardIcon, { name: "undo" }) }) }),
|
|
8415
|
+
/* @__PURE__ */ jsx71(Tip, { label: "Redo", keyName: "\u21E7\u2318Z", children: /* @__PURE__ */ jsx71("button", { type: "button", className: "chalk-board-tool", "aria-label": "Redo", disabled: !canRedo, onClick: onRedo, children: /* @__PURE__ */ jsx71(BoardIcon, { name: "redo" }) }) }),
|
|
8416
|
+
/* @__PURE__ */ jsx71(Separator, {}),
|
|
8417
|
+
/* @__PURE__ */ jsx71(ColorButton, { ...colors })
|
|
8418
|
+
] });
|
|
8419
|
+
}
|
|
8420
|
+
function nearestPlace(board, x, y) {
|
|
8421
|
+
const distances = { left: x - board.left, bottom: board.bottom - y, top: y - board.top };
|
|
8422
|
+
return ISLAND_PLACES.map((entry) => entry.id).reduce((best, place) => distances[place] < distances[best] ? place : best);
|
|
8423
|
+
}
|
|
8424
|
+
function BoardIsland(props) {
|
|
8425
|
+
const { place, onPlace, compact } = props;
|
|
8426
|
+
const island = useRef32(null);
|
|
8427
|
+
const tabStop = useRef32(null);
|
|
8428
|
+
const reduced = usePrefersReducedMotion();
|
|
8429
|
+
const [drag, setDrag] = useState42(null);
|
|
8430
|
+
const dropRect = useRef32(null);
|
|
8431
|
+
const [expanded, setExpanded] = useState42(false);
|
|
8432
|
+
const [colorsOpen, setColorsOpen] = useState42(false);
|
|
8433
|
+
const press = useRef32({ long: false });
|
|
8434
|
+
useLayoutEffect4(() => {
|
|
8435
|
+
const buttons = [...island.current?.querySelectorAll("button") ?? []];
|
|
8436
|
+
const stop = buttons.find((button) => button === tabStop.current && !button.disabled) ?? buttons.find((button) => !button.disabled);
|
|
8437
|
+
tabStop.current = stop ?? null;
|
|
8438
|
+
for (const button of buttons) button.tabIndex = button === stop ? 0 : -1;
|
|
8439
|
+
});
|
|
8440
|
+
useLayoutEffect4(() => {
|
|
8441
|
+
const element = island.current;
|
|
8442
|
+
const from = dropRect.current;
|
|
8443
|
+
dropRect.current = null;
|
|
8444
|
+
if (!element || !from || reduced) return;
|
|
8445
|
+
const to = element.getBoundingClientRect();
|
|
8446
|
+
element.animate(
|
|
8447
|
+
[
|
|
8448
|
+
{ transform: `translate(${from.left - to.left}px, ${from.top - to.top}px)`, opacity: 0.85 },
|
|
8449
|
+
{ transform: "none", opacity: 1 }
|
|
8450
|
+
],
|
|
8451
|
+
{ duration: 300, easing: "cubic-bezier(0.16, 1, 0.3, 1)" }
|
|
8452
|
+
);
|
|
8453
|
+
}, [place, reduced]);
|
|
8454
|
+
const startDrag = (event) => {
|
|
8455
|
+
const board = island.current?.parentElement;
|
|
8456
|
+
if (!board || event.button !== 0) return;
|
|
8457
|
+
const start = { x: event.clientX, y: event.clientY };
|
|
8458
|
+
let moved = false;
|
|
8459
|
+
const grip = event.currentTarget;
|
|
8460
|
+
grip.setPointerCapture(event.pointerId);
|
|
8461
|
+
const move = (next) => {
|
|
8462
|
+
const dx = next.clientX - start.x;
|
|
8463
|
+
const dy = next.clientY - start.y;
|
|
8464
|
+
if (!moved && Math.hypot(dx, dy) < DRAG_START_PX) return;
|
|
8465
|
+
moved = true;
|
|
8466
|
+
setDrag({ x: dx, y: dy, near: nearestPlace(board.getBoundingClientRect(), next.clientX, next.clientY) });
|
|
8467
|
+
};
|
|
8468
|
+
const end = (next) => {
|
|
8469
|
+
grip.removeEventListener("pointermove", move);
|
|
8470
|
+
grip.removeEventListener("pointerup", end);
|
|
8471
|
+
grip.removeEventListener("pointercancel", end);
|
|
8472
|
+
if (moved && next.type === "pointerup") {
|
|
8473
|
+
dropRect.current = island.current?.getBoundingClientRect() ?? null;
|
|
8474
|
+
onPlace(nearestPlace(board.getBoundingClientRect(), next.clientX, next.clientY), true);
|
|
8475
|
+
}
|
|
8476
|
+
setDrag(null);
|
|
8477
|
+
};
|
|
8478
|
+
grip.addEventListener("pointermove", move);
|
|
8479
|
+
grip.addEventListener("pointerup", end);
|
|
8480
|
+
grip.addEventListener("pointercancel", end);
|
|
8481
|
+
};
|
|
8482
|
+
const gripKeys = (event) => {
|
|
8483
|
+
if (!event.altKey) return;
|
|
8484
|
+
const target = { ArrowLeft: "left", ArrowDown: "bottom", ArrowUp: "top" };
|
|
8485
|
+
const next = target[event.key];
|
|
8486
|
+
if (!next) return;
|
|
8487
|
+
event.preventDefault();
|
|
8488
|
+
event.stopPropagation();
|
|
8489
|
+
if (next !== place) {
|
|
8490
|
+
dropRect.current = island.current?.getBoundingClientRect() ?? null;
|
|
8491
|
+
onPlace(next, true);
|
|
8492
|
+
}
|
|
8493
|
+
};
|
|
8494
|
+
if (compact && !expanded) {
|
|
8495
|
+
const cancelPress = () => clearTimeout(press.current.timer);
|
|
8496
|
+
return /* @__PURE__ */ jsx71("div", { ref: island, className: "chalk-board-island", "data-place": "compact", children: /* @__PURE__ */ jsx71(
|
|
8497
|
+
ColorButton,
|
|
8498
|
+
{
|
|
8499
|
+
...props,
|
|
8500
|
+
open: colorsOpen,
|
|
8501
|
+
onOpenChange: (open) => {
|
|
8502
|
+
if (!open) setColorsOpen(false);
|
|
8503
|
+
},
|
|
8504
|
+
trigger: /* @__PURE__ */ jsxs46(
|
|
8505
|
+
"button",
|
|
8506
|
+
{
|
|
8507
|
+
type: "button",
|
|
8508
|
+
className: "chalk-board-tool",
|
|
8509
|
+
"aria-label": "Drawing tools",
|
|
8510
|
+
"aria-expanded": false,
|
|
8511
|
+
onPointerDown: () => {
|
|
8512
|
+
press.current.long = false;
|
|
8513
|
+
press.current.timer = setTimeout(() => {
|
|
8514
|
+
press.current.long = true;
|
|
8515
|
+
setColorsOpen(true);
|
|
8516
|
+
}, LONG_PRESS_MS);
|
|
8517
|
+
},
|
|
8518
|
+
onPointerUp: cancelPress,
|
|
8519
|
+
onPointerLeave: cancelPress,
|
|
8520
|
+
onContextMenu: (event) => event.preventDefault(),
|
|
8521
|
+
onClick: () => {
|
|
8522
|
+
if (!press.current.long) setExpanded(true);
|
|
8523
|
+
},
|
|
8524
|
+
children: [
|
|
8525
|
+
/* @__PURE__ */ jsx71(BoardIcon, { name: "pen" }),
|
|
8526
|
+
/* @__PURE__ */ jsx71("span", { className: "chalk-board-tool-dot", style: { background: penHex(props.color, props.surface, props.theme) } })
|
|
8527
|
+
]
|
|
8528
|
+
}
|
|
8529
|
+
)
|
|
8530
|
+
}
|
|
8531
|
+
) });
|
|
8532
|
+
}
|
|
8533
|
+
const vertical = place === "left" && !compact;
|
|
8534
|
+
return /* @__PURE__ */ jsxs46(Fragment29, { children: [
|
|
8535
|
+
drag ? ISLAND_PLACES.map((entry) => /* @__PURE__ */ jsx71("div", { className: "chalk-board-dropzone", "data-zone": entry.id, "data-near": drag.near === entry.id || void 0, "aria-hidden": "true" }, entry.id)) : null,
|
|
8536
|
+
/* @__PURE__ */ jsxs46(
|
|
8537
|
+
"div",
|
|
8538
|
+
{
|
|
8539
|
+
ref: island,
|
|
8540
|
+
role: "toolbar",
|
|
8541
|
+
"aria-label": "Drawing tools",
|
|
8542
|
+
"aria-orientation": vertical ? "vertical" : "horizontal",
|
|
8543
|
+
className: "chalk-board-island",
|
|
8544
|
+
"data-place": compact ? "expanded" : place,
|
|
8545
|
+
"data-dragging": drag ? true : void 0,
|
|
8546
|
+
style: drag ? { transform: `translate(${drag.x}px, ${drag.y}px)` } : void 0,
|
|
8547
|
+
onKeyDownCapture: (event) => {
|
|
8548
|
+
if (!event.altKey) roveFocus(event, vertical);
|
|
8549
|
+
},
|
|
8550
|
+
onFocusCapture: (event) => {
|
|
8551
|
+
if (!(event.target instanceof HTMLButtonElement) || !event.currentTarget.contains(event.target)) return;
|
|
8552
|
+
tabStop.current = event.target;
|
|
8553
|
+
for (const button of event.currentTarget.querySelectorAll("button")) button.tabIndex = button === event.target ? 0 : -1;
|
|
8554
|
+
},
|
|
8555
|
+
children: [
|
|
8556
|
+
compact ? /* @__PURE__ */ jsxs46(Fragment29, { children: [
|
|
8557
|
+
/* @__PURE__ */ jsx71("button", { type: "button", className: "chalk-board-tool", "aria-label": "Close drawing tools", onClick: () => setExpanded(false), children: /* @__PURE__ */ jsx71(BoardIcon, { name: "close" }) }),
|
|
8558
|
+
/* @__PURE__ */ jsx71(Separator, {})
|
|
8559
|
+
] }) : /* @__PURE__ */ jsx71(Tip, { label: "Move the tools", children: /* @__PURE__ */ jsx71("button", { type: "button", className: "chalk-board-grip", "aria-label": "Move the tools. Use Alt and the arrow keys to dock them left, bottom or top.", onPointerDown: startDrag, onKeyDown: gripKeys, children: /* @__PURE__ */ jsx71(GripIcon, {}) }) }),
|
|
8560
|
+
/* @__PURE__ */ jsx71(ToolRow, { ...props, place: compact ? "bottom" : place })
|
|
8561
|
+
]
|
|
8562
|
+
}
|
|
8563
|
+
)
|
|
8564
|
+
] });
|
|
8565
|
+
}
|
|
8566
|
+
|
|
8567
|
+
// src/components/board/BoardMenu.tsx
|
|
8568
|
+
import { Menu as Menu5 } from "@base-ui/react/menu";
|
|
8569
|
+
import { Fragment as Fragment30, jsx as jsx72, jsxs as jsxs47 } from "react/jsx-runtime";
|
|
8570
|
+
function SurfacePreview({ surface }) {
|
|
8571
|
+
return /* @__PURE__ */ jsx72("span", { className: "chalk-board-tile-preview", "data-surface": surface, "aria-hidden": "true", children: surface === "dots" ? null : /* @__PURE__ */ jsx72("svg", { viewBox: "0 0 60 38", width: "100%", height: "100%", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsx72("path", { d: "M14 22c5-8 9-8 12 0s8 6 12-2 6-6 8 0" }) }) });
|
|
8572
|
+
}
|
|
8573
|
+
function IslandPreview({ place }) {
|
|
8574
|
+
return /* @__PURE__ */ jsx72("span", { className: "chalk-board-tile-preview", "data-island": place, "aria-hidden": "true", children: /* @__PURE__ */ jsx72("i", {}) });
|
|
8575
|
+
}
|
|
8576
|
+
function Tiles({ label, value, options, onChange, preview }) {
|
|
8577
|
+
return /* @__PURE__ */ jsxs47(Fragment30, { children: [
|
|
8578
|
+
/* @__PURE__ */ jsx72("div", { className: "chalk-board-menu-sublabel", children: label }),
|
|
8579
|
+
/* @__PURE__ */ jsx72(Menu5.RadioGroup, { value, onValueChange: (next) => onChange(next), className: "chalk-board-tiles", "aria-label": label, children: options.map((option) => /* @__PURE__ */ jsxs47(Menu5.RadioItem, { value: option.id, closeOnClick: false, className: "chalk-board-tile", children: [
|
|
8580
|
+
preview(option.id),
|
|
8581
|
+
option.label
|
|
8582
|
+
] }, option.id)) })
|
|
8583
|
+
] });
|
|
8584
|
+
}
|
|
8585
|
+
function BoardMenu({ surface, onSurface, place, onPlace, onDownload, onCopy, onBring, onClear, onClose }) {
|
|
8586
|
+
return /* @__PURE__ */ jsxs47(
|
|
8587
|
+
ChalkMenu,
|
|
8588
|
+
{
|
|
8589
|
+
className: "chalk-board-menu",
|
|
8590
|
+
trigger: /* @__PURE__ */ jsx72("button", { type: "button", className: "chalk-board-menu-button", "aria-label": "Board menu", children: /* @__PURE__ */ jsx72(BoardIcon, { name: "more" }) }),
|
|
8591
|
+
children: [
|
|
8592
|
+
/* @__PURE__ */ jsxs47(ChalkMenuItem, { onClick: onDownload, children: [
|
|
8593
|
+
/* @__PURE__ */ jsx72(BoardIcon, { name: "download", size: 18 }),
|
|
8594
|
+
"Download as PNG"
|
|
8595
|
+
] }),
|
|
8596
|
+
/* @__PURE__ */ jsxs47(ChalkMenuItem, { onClick: onCopy, children: [
|
|
8597
|
+
/* @__PURE__ */ jsx72(BoardIcon, { name: "copy", size: 18 }),
|
|
8598
|
+
"Copy as image"
|
|
8599
|
+
] }),
|
|
8600
|
+
/* @__PURE__ */ jsx72(Menu5.Separator, { className: "chalk-board-menu-rule" }),
|
|
8601
|
+
/* @__PURE__ */ jsx72(ChalkMenuLabel, { children: "Board appearance" }),
|
|
8602
|
+
/* @__PURE__ */ jsx72(Tiles, { label: "Surface", value: surface, options: SURFACES, onChange: onSurface, preview: (id) => /* @__PURE__ */ jsx72(SurfacePreview, { surface: id }) }),
|
|
8603
|
+
place ? /* @__PURE__ */ jsx72(Tiles, { label: "Tool island", value: place, options: ISLAND_PLACES, onChange: onPlace, preview: (id) => /* @__PURE__ */ jsx72(IslandPreview, { place: id }) }) : null,
|
|
8604
|
+
onBring || onClear ? /* @__PURE__ */ jsx72(Menu5.Separator, { className: "chalk-board-menu-rule" }) : null,
|
|
8605
|
+
onBring ? /* @__PURE__ */ jsxs47(ChalkMenuItem, { onClick: onBring, children: [
|
|
8606
|
+
/* @__PURE__ */ jsx72(BoardIcon, { name: "target", size: 18 }),
|
|
8607
|
+
"Bring everyone here"
|
|
8608
|
+
] }) : null,
|
|
8609
|
+
onClear ? /* @__PURE__ */ jsx72(ChalkMenuItem, { onClick: onClear, children: /* @__PURE__ */ jsxs47("span", { className: "chalk-board-danger", children: [
|
|
8610
|
+
/* @__PURE__ */ jsx72(BoardIcon, { name: "trash", size: 18 }),
|
|
8611
|
+
"Clear board"
|
|
8612
|
+
] }) }) : null,
|
|
8613
|
+
onClose ? /* @__PURE__ */ jsxs47(Fragment30, { children: [
|
|
8614
|
+
/* @__PURE__ */ jsx72(Menu5.Separator, { className: "chalk-board-menu-rule" }),
|
|
8615
|
+
/* @__PURE__ */ jsxs47(ChalkMenuItem, { onClick: onClose, children: [
|
|
8616
|
+
/* @__PURE__ */ jsx72(BoardIcon, { name: "close", size: 18 }),
|
|
8617
|
+
"Close board"
|
|
8618
|
+
] })
|
|
8619
|
+
] }) : null
|
|
8620
|
+
]
|
|
8621
|
+
}
|
|
8622
|
+
);
|
|
8623
|
+
}
|
|
8624
|
+
|
|
8625
|
+
// src/components/board/BoardStyles.tsx
|
|
8626
|
+
import { jsx as jsx73 } from "react/jsx-runtime";
|
|
8627
|
+
function BoardStyles() {
|
|
8628
|
+
return /* @__PURE__ */ jsx73("style", { href: "chalk-board", precedence: "default", children: BOARD_CSS });
|
|
8629
|
+
}
|
|
8630
|
+
var BOARD_CSS = `.chalk-board {
|
|
8631
|
+
--board-shadow: 0 0 0 1px var(--chalk-line-strong), 0 8px 24px rgba(0,0,0,.35);
|
|
8632
|
+
--board-dot-gap: 24px;
|
|
8633
|
+
--board-dot-x: 0px;
|
|
8634
|
+
--board-dot-y: 0px;
|
|
8635
|
+
position: relative;
|
|
8636
|
+
height: 100%;
|
|
8637
|
+
overflow: clip;
|
|
8638
|
+
border-radius: inherit;
|
|
8639
|
+
background-color: var(--board-fill);
|
|
8640
|
+
color: var(--board-ink);
|
|
8641
|
+
font-family: var(--chalk-font-family, Figtree, system-ui, sans-serif);
|
|
8642
|
+
isolation: isolate;
|
|
8643
|
+
}
|
|
8644
|
+
.chalk-board[hidden] {display:none;}
|
|
8645
|
+
.chalk-board[data-theme="light"] {--board-shadow: 0 0 0 1px var(--chalk-line-strong), 0 8px 24px rgba(0,0,0,.12);}
|
|
8646
|
+
.chalk-board[data-surface="chalkboard"] {
|
|
8647
|
+
--board-fill: #1c2622;
|
|
8648
|
+
--board-ink: #f1f0ea;
|
|
8649
|
+
--board-muted: #aeb9b4;
|
|
8650
|
+
background-image: radial-gradient(120% 90% at 50% 35%, rgba(255,255,255,.05), transparent 70%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .05 0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
|
|
8651
|
+
}
|
|
8652
|
+
.chalk-board[data-surface="paper"] {--board-fill: #eae6db; --board-ink: #1b1c1f; --board-muted: #5d5a52;}
|
|
8653
|
+
.chalk-board[data-surface="paper"][data-theme="light"] {--board-fill: #f5f2ea;}
|
|
8654
|
+
.chalk-board[data-surface="dots"] {
|
|
8655
|
+
--board-fill: var(--chalk-surface);
|
|
8656
|
+
--board-ink: var(--chalk-text);
|
|
8657
|
+
--board-muted: var(--chalk-muted-text);
|
|
8658
|
+
--board-dot: rgba(255,255,255,.11);
|
|
8659
|
+
background-image: radial-gradient(var(--board-dot) 1.3px, transparent 1.7px);
|
|
8660
|
+
background-size: var(--board-dot-gap) var(--board-dot-gap);
|
|
8661
|
+
background-position: var(--board-dot-x) var(--board-dot-y);
|
|
8662
|
+
}
|
|
8663
|
+
.chalk-board[data-surface="dots"][data-theme="light"] {--board-dot: rgba(0,0,0,.16);}
|
|
8664
|
+
|
|
8665
|
+
/* Excalidraw draws; none of its own interface shows. */
|
|
8666
|
+
.chalk-board-engine {position:absolute; inset:0; transition:opacity .2s ease-out;}
|
|
8667
|
+
.chalk-board:not([data-ready]) .chalk-board-engine:not(:has([role="alert"])) {opacity:0; pointer-events:none;}
|
|
8668
|
+
.chalk-board-engine > [role="alert"] {background:var(--board-fill); color:var(--board-ink);}
|
|
8669
|
+
.chalk-board:has(.chalk-board-engine > [role="alert"]) .chalk-board-loading {display:none;}
|
|
8670
|
+
.chalk-board .excalidraw {--color-selection: var(--board-selection); background: transparent;}
|
|
8671
|
+
.chalk-board .excalidraw :is(.layer-ui__wrapper, .context-menu, .HintViewer, .Modal, .welcome-screen-center, .library-menu, .sidebar, .App-toolbar, .App-bottom-bar, .FixedSideContainer, .popover, .scroll-back-to-content, .excalidraw-tooltip, .ToolIcon__hidden, .App-menu, .layer-ui__wrapper__footer) {display:none !important;}
|
|
8672
|
+
body:has(.chalk-board) :is(.excalidraw-modal-container, .excalidraw-tooltip) {display:none !important;}
|
|
8673
|
+
.chalk-board .excalidraw-wysiwyg {font-family: Nunito, var(--chalk-font-family, system-ui), sans-serif;}
|
|
8674
|
+
|
|
8675
|
+
.chalk-board-loading {position:absolute; inset:0; display:grid; place-content:center; justify-items:center; gap:12px; color:var(--board-muted); font-size:14px;}
|
|
8676
|
+
.chalk-board-spinner {width:22px; height:22px; border-radius:50%; border:2px solid var(--board-muted); border-top-color:transparent; animation:chalk-board-spin .8s linear infinite; opacity:.8;}
|
|
8677
|
+
@keyframes chalk-board-spin {to {transform:rotate(360deg);}}
|
|
8678
|
+
|
|
8679
|
+
.chalk-board-hint {position:absolute; inset:0; display:grid; place-content:center; justify-items:center; gap:14px; color:var(--board-muted); text-align:center; pointer-events:none; z-index:1;}
|
|
8680
|
+
.chalk-board-hint p {margin:0; font-size:15px; max-width:280px; line-height:1.4; text-wrap:balance;}
|
|
8681
|
+
.chalk-board-hint ul {list-style:none; margin:0; padding:0; display:flex; gap:18px; flex-wrap:wrap; justify-content:center; font-size:13px;}
|
|
8682
|
+
.chalk-board-hint li {display:flex; align-items:center; gap:6px;}
|
|
8683
|
+
.chalk-board-hint kbd {font:600 11px ui-monospace, SFMono-Regular, Menlo, monospace; padding:2px 6px; border-radius:5px; border:1px solid var(--board-muted); color:var(--board-muted);}
|
|
8684
|
+
|
|
8685
|
+
.chalk-board-note-layer {position:absolute; inset:0; z-index:2; cursor:crosshair; touch-action:none;}
|
|
8686
|
+
|
|
8687
|
+
.chalk-board-cursors {position:absolute; inset:0; z-index:3; pointer-events:none; overflow:hidden;}
|
|
8688
|
+
.chalk-board-cursors[data-hidden] {display:none;}
|
|
8689
|
+
.chalk-board-cursor {position:absolute; left:0; top:0; will-change:transform;}
|
|
8690
|
+
.chalk-board-cursor-arrow {position:absolute; left:-3px; top:-3px; width:22px; height:26px; max-width:none; display:block; filter:drop-shadow(0 2px 3px rgba(0,0,0,.35));}
|
|
8691
|
+
.chalk-board-cursor-tag {position:absolute; left:12px; top:18px; display:flex; align-items:center; height:28px;}
|
|
8692
|
+
.chalk-board-cursor-avatar {position:relative; z-index:1; box-shadow:0 0 0 2px rgba(255,255,255,.92); border-radius:50%;}
|
|
8693
|
+
.chalk-board-cursor-name {position:absolute; left:14px; top:0; height:28px; display:flex; align-items:center; padding:0 11px 0 18px; border-radius:14px; white-space:nowrap; color:#fff; font:600 13px/1 var(--chalk-font-family, Figtree, system-ui, sans-serif); box-shadow:0 0 0 1.5px rgba(255,255,255,.92), 0 3px 10px rgba(0,0,0,.28); opacity:0; transform:translateX(-10px); transition:opacity .3s ease-out, transform .3s ease-out;}
|
|
8694
|
+
.chalk-board-cursor[data-named="true"] .chalk-board-cursor-name {opacity:1; transform:none;}
|
|
8695
|
+
|
|
8696
|
+
.chalk-board :focus-visible {outline:2px solid var(--chalk-focus); outline-offset:2px;}
|
|
8697
|
+
.chalk-board button {font:inherit; color:inherit; background:none; border:0; padding:0; cursor:pointer;}
|
|
8698
|
+
|
|
8699
|
+
.chalk-board-island, .chalk-board-zoom, .chalk-board-menu-button, .chalk-board-chip, .chalk-board-view-only {background:var(--chalk-surface); color:var(--chalk-text); box-shadow:var(--board-shadow);}
|
|
8700
|
+
.chalk-board-chips {position:absolute; left:0; right:0; top:12px; z-index:5; display:flex; flex-direction:column; align-items:center; gap:8px; pointer-events:none;}
|
|
8701
|
+
.chalk-board-chips[data-low] {top:76px;}
|
|
8702
|
+
.chalk-board-chip {display:flex; align-items:center; gap:8px; height:36px; padding:0 6px 0 14px; border-radius:999px; font-size:13px; font-weight:600; pointer-events:auto;}
|
|
8703
|
+
.chalk-board-chip[data-quiet] {padding:0 14px; font-weight:500;}
|
|
8704
|
+
.chalk-board-chip svg {color:var(--chalk-muted-text);}
|
|
8705
|
+
.chalk-board-chip-button {height:28px !important; padding:0 12px !important; border-radius:999px; background:var(--chalk-hover) !important; font-weight:600; font-size:13px;}
|
|
8706
|
+
.chalk-board-view-only {position:absolute; left:12px; top:12px; z-index:5; height:30px; padding:0 12px; border-radius:999px; color:var(--chalk-soft-text); font-size:12px; font-weight:600; display:flex; align-items:center; gap:6px; box-shadow:0 0 0 1px var(--chalk-line-strong);}
|
|
8707
|
+
|
|
8708
|
+
.chalk-board-corner {position:absolute; right:12px; top:12px; z-index:5;}
|
|
8709
|
+
.chalk-board-menu-button {width:40px; height:40px; border-radius:50%; display:grid; place-items:center; transition:transform .15s ease-out;}
|
|
8710
|
+
.chalk-board-menu-button:hover {background:var(--chalk-raised);}
|
|
8711
|
+
.chalk-board-menu-button:active {transform:scale(.94);}
|
|
8712
|
+
.chalk-board-menu-button[aria-expanded="true"], .chalk-board-menu-button[data-popup-open] {background:var(--chalk-text); color:var(--chalk-canvas);}
|
|
8713
|
+
.chalk-menu.chalk-board-menu {width:304px;}
|
|
8714
|
+
.chalk-board-menu-rule {border:0; border-top:1px solid var(--chalk-line); margin:4px 0;}
|
|
8715
|
+
.chalk-board-menu-sublabel {padding:6px 10px 4px; font-size:12px; font-weight:600; color:var(--chalk-muted-text);}
|
|
8716
|
+
.chalk-board-tiles {display:grid; grid-template-columns:repeat(3, 1fr); gap:6px; padding:2px 4px 8px;}
|
|
8717
|
+
.chalk-board-tile {display:grid; gap:6px; justify-items:center; align-content:start; min-height:44px; padding:6px 4px 8px; border-radius:10px; border:2px solid transparent; font-size:12px; font-weight:600; color:var(--chalk-text); text-align:center; cursor:pointer; outline:none;}
|
|
8718
|
+
.chalk-board-tile[data-highlighted], .chalk-board-tile:hover {background:var(--chalk-hover);}
|
|
8719
|
+
.chalk-board-tile[data-checked] {border-color:var(--chalk-text);}
|
|
8720
|
+
.chalk-board-tile-preview {display:block; position:relative; width:100%; height:38px; border-radius:6px; overflow:hidden; box-shadow:0 0 0 1px var(--chalk-line-strong);}
|
|
8721
|
+
.chalk-board-tile-preview[data-surface="chalkboard"] {background:#1c2622; color:#f1f0ea;}
|
|
8722
|
+
.chalk-board-tile-preview[data-surface="paper"] {background:#f5f2ea; color:#1b1c1f;}
|
|
8723
|
+
.chalk-board-tile-preview[data-surface="dots"] {background:var(--chalk-surface) radial-gradient(var(--chalk-muted-text) 1px, transparent 1.5px) 0 0/8px 8px;}
|
|
8724
|
+
.chalk-board-tile-preview[data-island] {background:var(--chalk-raised);}
|
|
8725
|
+
.chalk-board-tile-preview[data-island] i {position:absolute; background:var(--chalk-text); border-radius:3px;}
|
|
8726
|
+
.chalk-board-tile-preview[data-island="bottom"] i {left:30%; right:30%; bottom:5px; height:5px;}
|
|
8727
|
+
.chalk-board-tile-preview[data-island="top"] i {left:30%; right:30%; top:5px; height:5px;}
|
|
8728
|
+
.chalk-board-tile-preview[data-island="left"] i {left:5px; top:20%; bottom:20%; width:5px;}
|
|
8729
|
+
.chalk-board-menu-kbd {margin-left:auto; padding-left:18px; font:600 11px ui-monospace, SFMono-Regular, Menlo, monospace; color:var(--chalk-muted-text);}
|
|
8730
|
+
.chalk-board-danger {display:contents; color:var(--chalk-danger-text);}
|
|
8731
|
+
|
|
8732
|
+
.chalk-board-zoom {position:absolute; right:12px; bottom:12px; z-index:5; height:40px; display:flex; align-items:center; border-radius:999px; padding:0 4px; transition:translate .2s ease-out;}
|
|
8733
|
+
.chalk-board-zoom button {height:32px; min-width:32px; border-radius:999px; display:grid; place-items:center; padding:0 6px !important; font-size:13px; font-weight:600; font-variant-numeric:tabular-nums;}
|
|
8734
|
+
.chalk-board-zoom button:hover {background:var(--chalk-hover);}
|
|
8735
|
+
.chalk-board-zoom-value {min-width:52px !important;}
|
|
8736
|
+
.chalk-board-zoom-rule {width:1px; height:18px; background:var(--chalk-line-strong); margin:0 2px;}
|
|
8737
|
+
|
|
8738
|
+
.chalk-board-island {position:absolute; z-index:5; display:flex; align-items:center; gap:2px; padding:6px; border-radius:26px; max-width:calc(100% - 24px);}
|
|
8739
|
+
.chalk-board-island[data-place="bottom"] {left:50%; bottom:12px; translate:-50% 0;}
|
|
8740
|
+
.chalk-board-island[data-place="top"] {left:50%; top:12px; translate:-50% 0;}
|
|
8741
|
+
.chalk-board-island[data-place="left"] {left:12px; top:50%; translate:0 -50%; flex-direction:column; max-height:calc(100% - 24px); overflow-y:auto; scrollbar-width:none;}
|
|
8742
|
+
.chalk-board-island[data-place="compact"] {left:12px; bottom:12px; padding:4px;}
|
|
8743
|
+
.chalk-board-island[data-place="expanded"] {left:12px; right:12px; bottom:12px; padding:4px; border-radius:28px; overflow-x:auto; scrollbar-width:none;}
|
|
8744
|
+
.chalk-board-island[data-dragging] {z-index:6; cursor:grabbing; box-shadow:0 0 0 1px var(--chalk-line-strong), 0 14px 36px rgba(0,0,0,.45);}
|
|
8745
|
+
.chalk-board:has(.chalk-board-island[data-place="expanded"]) .chalk-board-zoom {translate:0 -60px;}
|
|
8746
|
+
.chalk-board-group {display:contents;}
|
|
8747
|
+
.chalk-board-separator {flex:none; width:1px; height:24px; margin:0 4px; background:var(--chalk-line-strong);}
|
|
8748
|
+
.chalk-board-island[data-place="left"] .chalk-board-separator {width:24px; height:1px; margin:4px 0;}
|
|
8749
|
+
.chalk-board-tool {width:40px; height:40px; flex:none; border-radius:50%; display:grid !important; place-items:center; position:relative; transition:transform .15s ease-out;}
|
|
8750
|
+
.chalk-board-tool:hover {background:var(--chalk-hover);}
|
|
8751
|
+
.chalk-board-tool:active {transform:scale(.92);}
|
|
8752
|
+
.chalk-board-tool[aria-pressed="true"] {background:var(--chalk-text); color:var(--chalk-canvas);}
|
|
8753
|
+
.chalk-board-tool[aria-pressed="true"]:hover {background:var(--chalk-text-strong);}
|
|
8754
|
+
.chalk-board-tool:disabled {opacity:.35; cursor:default; transform:none; background:none;}
|
|
8755
|
+
.chalk-board[data-compact] .chalk-board-tool {width:44px; height:44px;}
|
|
8756
|
+
@media (pointer:coarse) {.chalk-board-tool {width:44px; height:44px;}}
|
|
8757
|
+
.chalk-board-tool-caret {position:absolute; right:3px; bottom:3px;}
|
|
8758
|
+
.chalk-board-tool-swatch {width:22px; height:22px; border-radius:50%; box-shadow:0 0 0 2px var(--chalk-surface), 0 0 0 3.5px var(--chalk-muted-text);}
|
|
8759
|
+
.chalk-board-tool-dot {position:absolute; right:5px; bottom:5px; width:11px; height:11px; border-radius:50%; box-shadow:0 0 0 2px var(--chalk-surface);}
|
|
8760
|
+
.chalk-board-grip {width:22px; height:40px; flex:none; display:grid !important; place-items:center; border-radius:12px; color:var(--chalk-muted-text) !important; cursor:grab !important; touch-action:none;}
|
|
8761
|
+
.chalk-board-grip:hover {background:var(--chalk-hover); color:var(--chalk-text) !important;}
|
|
8762
|
+
.chalk-board-island[data-place="left"] .chalk-board-grip {width:40px; height:22px;}
|
|
8763
|
+
.chalk-board-island[data-place="left"] .chalk-board-grip svg {transform:rotate(90deg);}
|
|
8764
|
+
@media (pointer:coarse) {.chalk-board-grip {width:26px; height:44px;} .chalk-board-island[data-place="left"] .chalk-board-grip {width:44px; height:26px;}}
|
|
8765
|
+
.chalk-board-kbd {font:600 11px ui-monospace, monospace; margin-left:6px; padding:1px 5px; border-radius:4px; background:rgba(127,127,127,.28);}
|
|
8766
|
+
|
|
8767
|
+
.chalk-board-dropzone {position:absolute; z-index:4; border:2px dashed var(--board-selection); border-radius:28px; background:rgba(125,184,255,.08); opacity:.45; pointer-events:none; transition:opacity .15s ease-out;}
|
|
8768
|
+
.chalk-board-dropzone[data-near] {opacity:1;}
|
|
8769
|
+
.chalk-board-dropzone[data-zone="left"] {left:6px; top:12px; bottom:12px; width:70px;}
|
|
8770
|
+
.chalk-board-dropzone[data-zone="bottom"] {left:12px; right:12px; bottom:6px; height:70px;}
|
|
8771
|
+
.chalk-board-dropzone[data-zone="top"] {left:12px; right:12px; top:6px; height:70px;}
|
|
8772
|
+
|
|
8773
|
+
.chalk-board-colors {display:grid; gap:10px; padding:4px;}
|
|
8774
|
+
.chalk-board-swatches {display:flex; gap:2px;}
|
|
8775
|
+
.chalk-board-swatch, .chalk-board-width {width:40px; height:40px; border-radius:50%; display:grid; place-items:center; flex:none; color:var(--chalk-text); background:none; border:0; cursor:pointer;}
|
|
8776
|
+
@media (pointer:coarse) {.chalk-board-swatch, .chalk-board-width {width:44px; height:44px;}}
|
|
8777
|
+
.chalk-board-swatch:hover, .chalk-board-width:hover {background:var(--chalk-hover);}
|
|
8778
|
+
.chalk-board-swatch i {width:24px; height:24px; border-radius:50%; box-shadow:0 0 0 1px var(--chalk-muted-text); display:grid; place-items:center;}
|
|
8779
|
+
.chalk-board-swatch[aria-checked="true"] i {box-shadow:0 0 0 2px var(--chalk-popover), 0 0 0 4px var(--chalk-text);}
|
|
8780
|
+
.chalk-board-width i {display:block; width:22px; border-radius:99px; background:var(--chalk-text);}
|
|
8781
|
+
.chalk-board-width[aria-checked="true"] {background:var(--chalk-text);}
|
|
8782
|
+
.chalk-board-width[aria-checked="true"] i {background:var(--chalk-canvas);}
|
|
8783
|
+
|
|
8784
|
+
.chalk-board-announcer {position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap;}
|
|
8785
|
+
|
|
8786
|
+
@media (max-width: 640px) {
|
|
8787
|
+
.chalk-board-zoom {height:52px;}
|
|
8788
|
+
.chalk-board-zoom button {height:44px; min-width:44px;}
|
|
8789
|
+
.chalk-board-menu-button {width:44px; height:44px;}
|
|
8790
|
+
}
|
|
8791
|
+
@media (prefers-reduced-motion: reduce) {
|
|
8792
|
+
.chalk-board-spinner {animation:none;}
|
|
8793
|
+
.chalk-board-engine, .chalk-board-cursor-name, .chalk-board-zoom {transition:none;}
|
|
8794
|
+
.chalk-board-cursor-name {transform:none !important;}
|
|
8795
|
+
}
|
|
8796
|
+
`;
|
|
8797
|
+
|
|
8798
|
+
// src/components/board/Board.tsx
|
|
8799
|
+
import { Fragment as Fragment31, jsx as jsx74, jsxs as jsxs48 } from "react/jsx-runtime";
|
|
8800
|
+
var SURFACE_KEY = "chalk.board.surface";
|
|
8801
|
+
var ISLAND_KEY = "chalk.board.island";
|
|
8802
|
+
var COMPACT_BELOW_PX = 1060;
|
|
8803
|
+
var VIEW_SEND_MS = 150;
|
|
8804
|
+
var VIEW_HEARTBEAT_MS = 2e3;
|
|
8805
|
+
var GLIDE_MS = 300;
|
|
8806
|
+
var ZOOM_STEP = 1.25;
|
|
8807
|
+
var NOTE_FONT_PX = 18;
|
|
8808
|
+
var EDITING_KEYS = /* @__PURE__ */ new Set(["delete", "backspace", "arrowup", "arrowdown", "arrowleft", "arrowright", "escape", " ", "enter", "shift", "alt", "control", "meta", "tab"]);
|
|
8809
|
+
var MODIFIED_ENGINE_KEYS = /* @__PURE__ */ new Set(["c", "v", "x", "a", "d", "=", "+", "-", "0"]);
|
|
8810
|
+
var first = (name) => name.split(" ")[0] ?? name;
|
|
8811
|
+
function fitContent(engine, animate) {
|
|
8812
|
+
engine.api.scrollToContent(void 0, { fitToViewport: true, viewportZoomFactor: 0.85, maxZoom: 1, animate });
|
|
8813
|
+
}
|
|
8814
|
+
function isTyping(target) {
|
|
8815
|
+
return target instanceof HTMLElement && (target.isContentEditable || target.tagName === "TEXTAREA" || target.tagName === "INPUT");
|
|
8816
|
+
}
|
|
8817
|
+
function useCamera(engine, root, onManualMove, onMoved, onViewportReady) {
|
|
8818
|
+
const cameraRef = useRef33({ scrollX: 0, scrollY: 0, zoom: 1 });
|
|
8819
|
+
const viewportRef = useRef33({ width: 1, height: 1 });
|
|
8820
|
+
const expected = useRef33(null);
|
|
8821
|
+
const glide = useRef33(0);
|
|
8822
|
+
const [zoom, setZoom] = useState43(1);
|
|
8823
|
+
const reduced = usePrefersReducedMotion();
|
|
8824
|
+
const manual = useRef33(onManualMove);
|
|
8825
|
+
manual.current = onManualMove;
|
|
8826
|
+
const moved = useRef33(onMoved);
|
|
8827
|
+
moved.current = onMoved;
|
|
8828
|
+
const resized = useRef33(onViewportReady);
|
|
8829
|
+
resized.current = onViewportReady;
|
|
8830
|
+
useEffect37(() => {
|
|
8831
|
+
if (!engine) return;
|
|
8832
|
+
const paint = (camera) => {
|
|
8833
|
+
cameraRef.current = camera;
|
|
8834
|
+
const element = root.current;
|
|
8835
|
+
if (element) {
|
|
8836
|
+
let gap = 24 * camera.zoom;
|
|
8837
|
+
while (gap < 14) gap *= 2;
|
|
8838
|
+
element.style.setProperty("--board-dot-gap", `${gap}px`);
|
|
8839
|
+
element.style.setProperty("--board-dot-x", `${camera.scrollX * camera.zoom % gap}px`);
|
|
8840
|
+
element.style.setProperty("--board-dot-y", `${camera.scrollY * camera.zoom % gap}px`);
|
|
8841
|
+
}
|
|
8842
|
+
setZoom(camera.zoom);
|
|
8843
|
+
};
|
|
8844
|
+
paint(readCamera(engine));
|
|
8845
|
+
return engine.api.onScrollChange((scrollX, scrollY, next) => {
|
|
8846
|
+
const camera = { scrollX, scrollY, zoom: next.value };
|
|
8847
|
+
if (camera.zoom !== clampZoom(camera.zoom)) {
|
|
8848
|
+
expected.current = { ...camera, zoom: clampZoom(camera.zoom) };
|
|
8849
|
+
setCamera(engine, expected.current);
|
|
8850
|
+
return;
|
|
8851
|
+
}
|
|
8852
|
+
paint(camera);
|
|
8853
|
+
if (expected.current && sameCamera(camera, expected.current)) return;
|
|
8854
|
+
expected.current = null;
|
|
8855
|
+
cancelAnimationFrame(glide.current);
|
|
8856
|
+
manual.current();
|
|
8857
|
+
moved.current();
|
|
8858
|
+
});
|
|
8859
|
+
}, [engine, root]);
|
|
8860
|
+
useEffect37(() => {
|
|
8861
|
+
const element = root.current;
|
|
8862
|
+
if (!element) return;
|
|
8863
|
+
viewportRef.current = { width: element.clientWidth || 1, height: element.clientHeight || 1 };
|
|
8864
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
8865
|
+
if (!entry) return;
|
|
8866
|
+
viewportRef.current = { width: entry.contentRect.width, height: entry.contentRect.height };
|
|
8867
|
+
if (entry.contentRect.width > 0 && entry.contentRect.height > 0) resized.current();
|
|
8868
|
+
});
|
|
8869
|
+
observer.observe(element);
|
|
8870
|
+
return () => observer.disconnect();
|
|
8871
|
+
}, [root]);
|
|
8872
|
+
useEffect37(() => () => cancelAnimationFrame(glide.current), []);
|
|
8873
|
+
const move = useCallback13(
|
|
8874
|
+
(target, animate) => {
|
|
8875
|
+
if (!engine) return;
|
|
8876
|
+
cancelAnimationFrame(glide.current);
|
|
8877
|
+
const set = (camera) => {
|
|
8878
|
+
expected.current = camera;
|
|
8879
|
+
setCamera(engine, camera);
|
|
8880
|
+
};
|
|
8881
|
+
if (!animate || reduced) {
|
|
8882
|
+
set(target);
|
|
8883
|
+
moved.current();
|
|
8884
|
+
return;
|
|
8885
|
+
}
|
|
8886
|
+
const from = cameraRef.current;
|
|
8887
|
+
const started = performance.now();
|
|
8888
|
+
const step = (now) => {
|
|
8889
|
+
const progress = Math.min(1, (now - started) / GLIDE_MS);
|
|
8890
|
+
set(mixCamera(from, target, 1 - (1 - progress) ** 3));
|
|
8891
|
+
if (progress < 1) glide.current = requestAnimationFrame(step);
|
|
8892
|
+
else moved.current();
|
|
8893
|
+
};
|
|
8894
|
+
glide.current = requestAnimationFrame(step);
|
|
8895
|
+
},
|
|
8896
|
+
[engine, reduced]
|
|
8897
|
+
);
|
|
8898
|
+
return useMemo16(() => ({ cameraRef, viewportRef, zoom, move }), [move, zoom]);
|
|
8899
|
+
}
|
|
8900
|
+
function useBoardKeys(root, handlers) {
|
|
8901
|
+
const latest = useRef33(handlers);
|
|
8902
|
+
latest.current = handlers;
|
|
8903
|
+
useEffect37(() => {
|
|
8904
|
+
const element = root.current;
|
|
8905
|
+
if (!element) return;
|
|
8906
|
+
const keydown = (event) => {
|
|
8907
|
+
if (isTyping(event.target)) return;
|
|
8908
|
+
const key = event.key.toLowerCase();
|
|
8909
|
+
if (event.altKey && key.startsWith("arrow") && event.target instanceof Element && event.target.closest(".chalk-board-grip")) return;
|
|
8910
|
+
const modified = event.metaKey || event.ctrlKey;
|
|
8911
|
+
const { onTool, onZoom, canDraw } = latest.current;
|
|
8912
|
+
if (modified && !event.altKey && key === "z") return;
|
|
8913
|
+
if (modified && !event.altKey && key === "y") return;
|
|
8914
|
+
if (modified && !event.altKey && !event.shiftKey && MODIFIED_ENGINE_KEYS.has(key)) return;
|
|
8915
|
+
if (!modified && !event.altKey) {
|
|
8916
|
+
const tool = TOOL_KEYS[key];
|
|
8917
|
+
if (tool && canDraw) {
|
|
8918
|
+
event.preventDefault();
|
|
8919
|
+
event.stopPropagation();
|
|
8920
|
+
onTool(tool);
|
|
8921
|
+
return;
|
|
8922
|
+
}
|
|
8923
|
+
if (key === "+" || key === "=" || key === "-") {
|
|
8924
|
+
event.preventDefault();
|
|
8925
|
+
event.stopPropagation();
|
|
8926
|
+
onZoom(key === "-" ? 1 / ZOOM_STEP : ZOOM_STEP);
|
|
8927
|
+
return;
|
|
8928
|
+
}
|
|
8929
|
+
if (key === "escape" && canDraw) onTool("selection");
|
|
8930
|
+
if (EDITING_KEYS.has(key)) return;
|
|
8931
|
+
}
|
|
8932
|
+
event.stopPropagation();
|
|
8933
|
+
};
|
|
8934
|
+
const blockMenu = (event) => {
|
|
8935
|
+
event.preventDefault();
|
|
8936
|
+
event.stopPropagation();
|
|
8937
|
+
};
|
|
8938
|
+
element.addEventListener("keydown", keydown, true);
|
|
8939
|
+
element.addEventListener("contextmenu", blockMenu, true);
|
|
8940
|
+
return () => {
|
|
8941
|
+
element.removeEventListener("keydown", keydown, true);
|
|
8942
|
+
element.removeEventListener("contextmenu", blockMenu, true);
|
|
8943
|
+
};
|
|
8944
|
+
}, [root]);
|
|
8945
|
+
}
|
|
8946
|
+
function useImageRefusal(root, toast) {
|
|
8947
|
+
const latest = useRef33(toast);
|
|
8948
|
+
latest.current = toast;
|
|
8949
|
+
useEffect37(() => {
|
|
8950
|
+
const element = root.current;
|
|
8951
|
+
if (!element) return;
|
|
8952
|
+
const refuse = (files, event) => {
|
|
8953
|
+
if (!files || ![...files].some((file) => file.type.startsWith("image/"))) return;
|
|
8954
|
+
event.preventDefault();
|
|
8955
|
+
event.stopPropagation();
|
|
8956
|
+
latest.current({ type: "error", message: "The board can't take images yet.", duration: 5e3 });
|
|
8957
|
+
};
|
|
8958
|
+
const paste = (event) => refuse(event.clipboardData?.files, event);
|
|
8959
|
+
const drop = (event) => refuse(event.dataTransfer?.files, event);
|
|
8960
|
+
element.addEventListener("paste", paste, true);
|
|
8961
|
+
element.addEventListener("drop", drop, true);
|
|
8962
|
+
return () => {
|
|
8963
|
+
element.removeEventListener("paste", paste, true);
|
|
8964
|
+
element.removeEventListener("drop", drop, true);
|
|
8965
|
+
};
|
|
8966
|
+
}, [root]);
|
|
8967
|
+
}
|
|
8968
|
+
function Board({ board, primary, visible, colorScheme, people, following, onFollow, sendView, onClose }) {
|
|
8969
|
+
const root = useRef33(null);
|
|
8970
|
+
const isHost = useCan("manageWhiteboard");
|
|
8971
|
+
const connection = useConnection();
|
|
8972
|
+
const boardSession = useWhiteboard().engine;
|
|
8973
|
+
const offline = connection.status === "reconnecting";
|
|
8974
|
+
const phone = useMediaQuery("(max-width: 640px)");
|
|
8975
|
+
const [engine, setEngine] = useState43(null);
|
|
8976
|
+
const toast = useBoardToast(engine);
|
|
8977
|
+
const [surface, setSurface] = useState43(() => readStored(SURFACE_KEY, SURFACES, "chalkboard"));
|
|
8978
|
+
const [place, setPlace] = useState43(() => readStored(ISLAND_KEY, ISLAND_PLACES, "bottom"));
|
|
8979
|
+
const [tool, setTool] = useState43("pen");
|
|
8980
|
+
const [shape, setShape] = useState43("rectangle");
|
|
8981
|
+
const [color, setColor] = useState43("ink");
|
|
8982
|
+
const [width, setWidth] = useState43("medium");
|
|
8983
|
+
const [elementCount, setElementCount] = useState43(0);
|
|
8984
|
+
const empty = elementCount === 0;
|
|
8985
|
+
const [narrow, setNarrow] = useState43(false);
|
|
8986
|
+
const [announcement, setAnnouncement] = useState43("");
|
|
8987
|
+
const canDraw = primary && (board.collab?.canDraw ?? board.canDraw ?? true);
|
|
8988
|
+
const compact = phone || narrow;
|
|
8989
|
+
const history = useEngineHistory(root, engine);
|
|
8990
|
+
const touched = useRef33(false);
|
|
8991
|
+
const views = useRef33(/* @__PURE__ */ new Map());
|
|
8992
|
+
const cursors = useRef33(/* @__PURE__ */ new Map());
|
|
8993
|
+
const pen = useMemo16(() => ({ color, width, surface, theme: colorScheme }), [color, width, surface, colorScheme]);
|
|
8994
|
+
const followingRef = useRef33(following);
|
|
8995
|
+
followingRef.current = following;
|
|
8996
|
+
const nameOf = useCallback13((id) => first(people.find((person) => person.id === id)?.displayName ?? "Someone"), [people]);
|
|
8997
|
+
const colorFor = useCallback13(
|
|
8998
|
+
(id) => personColor(
|
|
8999
|
+
Math.max(
|
|
9000
|
+
0,
|
|
9001
|
+
people.findIndex((person) => person.id === id)
|
|
9002
|
+
)
|
|
9003
|
+
),
|
|
9004
|
+
[people]
|
|
9005
|
+
);
|
|
9006
|
+
const stopFollowing = useCallback13(() => {
|
|
9007
|
+
const current = followingRef.current;
|
|
9008
|
+
if (!current) return;
|
|
9009
|
+
followingRef.current = null;
|
|
9010
|
+
onFollow(null);
|
|
9011
|
+
toast({ message: `Stopped following ${nameOf(current)}` });
|
|
9012
|
+
}, [nameOf, onFollow, toast]);
|
|
9013
|
+
const shareView = useRef33(() => false);
|
|
9014
|
+
const viewSender = useRef33(sendView);
|
|
9015
|
+
viewSender.current = sendView;
|
|
9016
|
+
const ownParticipantId = useSelf().participantId;
|
|
9017
|
+
const appliedVersions = useRef33(new ViewVersions());
|
|
9018
|
+
const viewsAvailable = sendView !== void 0;
|
|
9019
|
+
const reapplyView = useRef33(() => void 0);
|
|
9020
|
+
const camera = useCamera(
|
|
9021
|
+
engine,
|
|
9022
|
+
root,
|
|
9023
|
+
stopFollowing,
|
|
9024
|
+
() => shareView.current(false),
|
|
9025
|
+
() => reapplyView.current()
|
|
9026
|
+
);
|
|
9027
|
+
useEffect37(() => {
|
|
9028
|
+
if (!visible || !viewsAvailable || !engine) return;
|
|
9029
|
+
let last = 0;
|
|
9030
|
+
let pending;
|
|
9031
|
+
const send = (bring2) => {
|
|
9032
|
+
const followedView = followingRef.current ? views.current.get(followingRef.current) : void 0;
|
|
9033
|
+
if (followedView && !followedView.originParticipantId && !bring2) return true;
|
|
9034
|
+
const view = followedView ?? visibleView(camera.cameraRef.current, camera.viewportRef.current);
|
|
9035
|
+
last = performance.now();
|
|
9036
|
+
const rectangle = { x: view.x, y: view.y, width: view.width, height: view.height, bring: bring2 };
|
|
9037
|
+
const outgoing = followedView?.originParticipantId ? { ...rectangle, originParticipantId: followedView.originParticipantId, originSequence: followedView.originSequence, ...followedView.originEpoch ? { originEpoch: followedView.originEpoch } : {} } : rectangle;
|
|
9038
|
+
try {
|
|
9039
|
+
return viewSender.current?.(outgoing) ?? false;
|
|
9040
|
+
} catch (error) {
|
|
9041
|
+
console.warn("Board view dropped.", { operation: "sendView", error });
|
|
9042
|
+
return false;
|
|
9043
|
+
}
|
|
9044
|
+
};
|
|
9045
|
+
shareView.current = (bring2) => {
|
|
9046
|
+
if (bring2) return send(true);
|
|
9047
|
+
clearTimeout(pending);
|
|
9048
|
+
pending = setTimeout(() => send(false), Math.max(0, VIEW_SEND_MS - (performance.now() - last)));
|
|
9049
|
+
return true;
|
|
9050
|
+
};
|
|
9051
|
+
const heartbeat = setInterval(() => {
|
|
9052
|
+
if (!followingRef.current) send(false);
|
|
9053
|
+
}, VIEW_HEARTBEAT_MS);
|
|
9054
|
+
send(false);
|
|
9055
|
+
return () => {
|
|
9056
|
+
clearInterval(heartbeat);
|
|
9057
|
+
clearTimeout(pending);
|
|
9058
|
+
shareView.current = () => false;
|
|
9059
|
+
};
|
|
9060
|
+
}, [boardSession.sceneId, boardSession.status, camera.cameraRef, camera.viewportRef, connection.status, engine, visible, viewsAvailable]);
|
|
9061
|
+
const { move, viewportRef } = camera;
|
|
9062
|
+
const goTo = useCallback13(
|
|
9063
|
+
(participantId, animate, selected = false) => {
|
|
9064
|
+
const view = views.current.get(participantId);
|
|
9065
|
+
if (!view || viewportRef.current.width <= 0 || viewportRef.current.height <= 0) return;
|
|
9066
|
+
if (!selected && !appliedVersions.current.isNew(view)) return;
|
|
9067
|
+
appliedVersions.current.remember(view);
|
|
9068
|
+
move(cameraForView(view, viewportRef.current), animate);
|
|
9069
|
+
},
|
|
9070
|
+
[move, viewportRef]
|
|
9071
|
+
);
|
|
9072
|
+
reapplyView.current = () => {
|
|
9073
|
+
if (followingRef.current) goTo(followingRef.current, false, true);
|
|
9074
|
+
};
|
|
9075
|
+
const subscribe3 = board.collab?.subscribe;
|
|
9076
|
+
const latestView = useRef33(() => void 0);
|
|
9077
|
+
latestView.current = (event) => {
|
|
9078
|
+
const previous = views.current.get(event.participantId);
|
|
9079
|
+
if (!event.bring) {
|
|
9080
|
+
if (event.originParticipantId === ownParticipantId && ownParticipantId) return;
|
|
9081
|
+
if (!appliedVersions.current.isNew(event)) return;
|
|
9082
|
+
if (event.originParticipantId && previous?.originParticipantId === event.originParticipantId && event.originEpoch === previous.originEpoch && event.originSequence <= previous.originSequence) return;
|
|
9083
|
+
}
|
|
9084
|
+
const newPeer = !previous;
|
|
9085
|
+
views.current.delete(event.participantId);
|
|
9086
|
+
views.current.set(event.participantId, event.bring ? { x: event.x, y: event.y, width: event.width, height: event.height, bring: false } : event);
|
|
9087
|
+
if (views.current.size > 256) {
|
|
9088
|
+
const oldest = views.current.keys().next().value;
|
|
9089
|
+
if (oldest !== void 0) views.current.delete(oldest);
|
|
9090
|
+
}
|
|
9091
|
+
if (newPeer) {
|
|
9092
|
+
shareView.current(false);
|
|
9093
|
+
}
|
|
9094
|
+
if (event.bring) {
|
|
9095
|
+
followingRef.current = event.participantId;
|
|
9096
|
+
onFollow(event.participantId);
|
|
9097
|
+
toast({ message: `${nameOf(event.participantId)} brought everyone to this view` });
|
|
9098
|
+
goTo(event.participantId, true, true);
|
|
9099
|
+
return;
|
|
9100
|
+
}
|
|
9101
|
+
if (followingRef.current === event.participantId) goTo(event.participantId, true);
|
|
9102
|
+
};
|
|
9103
|
+
useEffect37(() => {
|
|
9104
|
+
if (!subscribe3) return;
|
|
9105
|
+
return subscribe3((event) => {
|
|
9106
|
+
if (event.type === "cursor") rememberCursor(cursors.current, event);
|
|
9107
|
+
else if (event.type === "view") latestView.current(event);
|
|
9108
|
+
});
|
|
9109
|
+
}, [subscribe3]);
|
|
9110
|
+
useEffect37(() => {
|
|
9111
|
+
if (following) goTo(following, true, true);
|
|
9112
|
+
}, [following, goTo]);
|
|
9113
|
+
useEffect37(() => {
|
|
9114
|
+
if (engine && canDraw) applyTool(engine, tool, pen);
|
|
9115
|
+
}, [canDraw, engine, pen, tool]);
|
|
9116
|
+
useEffect37(() => {
|
|
9117
|
+
if (engine) void document.fonts.load(`${NOTE_FONT_PX}px Nunito`);
|
|
9118
|
+
}, [engine]);
|
|
9119
|
+
useEffect37(() => {
|
|
9120
|
+
if (!engine) return;
|
|
9121
|
+
let beforeEditing = sceneSignature(engine);
|
|
9122
|
+
let wasEditing = false;
|
|
9123
|
+
setElementCount(engine.api.getSceneElements().length);
|
|
9124
|
+
if (hasContent(engine)) {
|
|
9125
|
+
touched.current = true;
|
|
9126
|
+
if (!followingRef.current) fitContent(engine, false);
|
|
9127
|
+
}
|
|
9128
|
+
const offDown = engine.api.onPointerDown(() => {
|
|
9129
|
+
touched.current = true;
|
|
9130
|
+
});
|
|
9131
|
+
const offChange = engine.api.onChange((elements, appState) => {
|
|
9132
|
+
const count = elements.filter((element) => !element.isDeleted).length;
|
|
9133
|
+
setElementCount(count);
|
|
9134
|
+
if (!touched.current && count > 0) {
|
|
9135
|
+
touched.current = true;
|
|
9136
|
+
if (!followingRef.current) requestAnimationFrame(() => fitContent(engine, false));
|
|
9137
|
+
}
|
|
9138
|
+
const editing = appState.editingTextElement !== null;
|
|
9139
|
+
if (!wasEditing && editing) beforeEditing = sceneSignature(engine);
|
|
9140
|
+
const finishedEditing = wasEditing && !editing;
|
|
9141
|
+
wasEditing = editing;
|
|
9142
|
+
if (finishedEditing && sceneSignature(engine) !== beforeEditing) {
|
|
9143
|
+
settleNoteText(engine);
|
|
9144
|
+
}
|
|
9145
|
+
});
|
|
9146
|
+
return () => {
|
|
9147
|
+
offDown();
|
|
9148
|
+
offChange();
|
|
9149
|
+
};
|
|
9150
|
+
}, [engine]);
|
|
9151
|
+
useEffect37(() => {
|
|
9152
|
+
const element = root.current;
|
|
9153
|
+
if (!element) return;
|
|
9154
|
+
const observer = new ResizeObserver(([entry]) => setNarrow((entry?.contentRect.width ?? COMPACT_BELOW_PX) < COMPACT_BELOW_PX));
|
|
9155
|
+
observer.observe(element);
|
|
9156
|
+
return () => observer.disconnect();
|
|
9157
|
+
}, []);
|
|
9158
|
+
const chooseTool = useCallback13((next) => {
|
|
9159
|
+
if (next === "rectangle" || next === "ellipse" || next === "diamond" || next === "arrow" || next === "line") setShape(next);
|
|
9160
|
+
setTool(next);
|
|
9161
|
+
}, []);
|
|
9162
|
+
const undo = useCallback13(() => {
|
|
9163
|
+
const container = root.current?.querySelector(".excalidraw");
|
|
9164
|
+
if (container) pressHistory(container, false);
|
|
9165
|
+
}, []);
|
|
9166
|
+
const redo = useCallback13(() => {
|
|
9167
|
+
const container = root.current?.querySelector(".excalidraw");
|
|
9168
|
+
if (container) pressHistory(container, true);
|
|
9169
|
+
}, []);
|
|
9170
|
+
const zoomBy = useCallback13(
|
|
9171
|
+
(factor) => {
|
|
9172
|
+
stopFollowing();
|
|
9173
|
+
camera.move(zoomAroundCentre(camera.cameraRef.current, camera.viewportRef.current, camera.cameraRef.current.zoom * factor), true);
|
|
9174
|
+
},
|
|
9175
|
+
[camera, stopFollowing]
|
|
9176
|
+
);
|
|
9177
|
+
useBoardKeys(root, { onTool: chooseTool, onZoom: zoomBy, canDraw });
|
|
9178
|
+
useImageRefusal(root, toast);
|
|
9179
|
+
const restyle = (next) => {
|
|
9180
|
+
if (next.color) setColor(next.color);
|
|
9181
|
+
if (next.width) setWidth(next.width);
|
|
9182
|
+
if (engine) restyleSelection(engine, { ...pen, ...next }, next);
|
|
9183
|
+
};
|
|
9184
|
+
const choosePlace = (next, announce) => {
|
|
9185
|
+
setPlace(next);
|
|
9186
|
+
writeStored(ISLAND_KEY, next);
|
|
9187
|
+
if (announce) setAnnouncement(`Tool island moved to ${next}`);
|
|
9188
|
+
};
|
|
9189
|
+
const chooseSurface = (next) => {
|
|
9190
|
+
setSurface(next);
|
|
9191
|
+
writeStored(SURFACE_KEY, next);
|
|
9192
|
+
};
|
|
9193
|
+
const fit = () => {
|
|
9194
|
+
stopFollowing();
|
|
9195
|
+
if (engine) fitContent(engine, true);
|
|
9196
|
+
};
|
|
9197
|
+
const download = async () => {
|
|
9198
|
+
if (!engine) return;
|
|
9199
|
+
try {
|
|
9200
|
+
const url = URL.createObjectURL(await boardImage(engine, surfaceFill(surface, colorScheme)));
|
|
9201
|
+
const link = document.createElement("a");
|
|
9202
|
+
link.href = url;
|
|
9203
|
+
link.download = "chalk-board.png";
|
|
9204
|
+
link.click();
|
|
9205
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
9206
|
+
toast({ message: "Saved chalk-board.png" });
|
|
9207
|
+
} catch {
|
|
9208
|
+
toast({ type: "error", message: "Could not save the image. Try again." });
|
|
9209
|
+
}
|
|
9210
|
+
};
|
|
9211
|
+
const copy = async () => {
|
|
9212
|
+
if (!engine) return;
|
|
9213
|
+
try {
|
|
9214
|
+
const image = await boardImage(engine, surfaceFill(surface, colorScheme));
|
|
9215
|
+
await navigator.clipboard.write([new ClipboardItem({ "image/png": image })]);
|
|
9216
|
+
toast({ message: "Copied the board as an image" });
|
|
9217
|
+
} catch {
|
|
9218
|
+
toast({ type: "error", message: "The browser blocked copying. Use Download as PNG instead." });
|
|
9219
|
+
}
|
|
9220
|
+
};
|
|
9221
|
+
const bring = () => {
|
|
9222
|
+
if (followingRef.current) {
|
|
9223
|
+
followingRef.current = null;
|
|
9224
|
+
onFollow(null);
|
|
9225
|
+
}
|
|
9226
|
+
if (shareView.current(true)) toast({ message: "Sharing your view with everyone" });
|
|
9227
|
+
else toast({ type: "error", message: "Could not share your view. Try again." });
|
|
9228
|
+
};
|
|
9229
|
+
const clear = async () => {
|
|
9230
|
+
if (!engine) return;
|
|
9231
|
+
try {
|
|
9232
|
+
const restore = await engine.clearWithUndo();
|
|
9233
|
+
toast({ message: "Board cleared", ...canDraw ? { action: { label: "Undo", onClick: () => void restore() } } : {} });
|
|
9234
|
+
} catch {
|
|
9235
|
+
toast({ type: "error", message: "Could not clear the board. Try again." });
|
|
9236
|
+
}
|
|
9237
|
+
};
|
|
9238
|
+
const placeNoteAt = (event) => {
|
|
9239
|
+
if (event.button !== 0) return;
|
|
9240
|
+
const container = root.current?.querySelector(".excalidraw");
|
|
9241
|
+
if (!engine || !container || !root.current) return;
|
|
9242
|
+
const rect = root.current.getBoundingClientRect();
|
|
9243
|
+
const { scrollX, scrollY, zoom } = camera.cameraRef.current;
|
|
9244
|
+
touched.current = true;
|
|
9245
|
+
placeNote(engine, container, { x: (event.clientX - rect.left) / zoom - scrollX, y: (event.clientY - rect.top) / zoom - scrollY }, color);
|
|
9246
|
+
setTool("selection");
|
|
9247
|
+
};
|
|
9248
|
+
const ink = surfaceInk(surface, colorScheme);
|
|
9249
|
+
const surfaceVars = { "--board-selection": selectionColor(surface, colorScheme) };
|
|
9250
|
+
const followed = following ? nameOf(following) : null;
|
|
9251
|
+
const ready = engine !== null;
|
|
9252
|
+
return /* @__PURE__ */ jsxs48("div", { ref: root, className: "chalk-board", "data-surface": surface, "data-theme": colorScheme, "data-ready": ready || void 0, "data-compact": compact || void 0, "data-primary": primary || void 0, "data-elements": elementCount, style: surfaceVars, hidden: !visible, children: [
|
|
9253
|
+
/* @__PURE__ */ jsx74(BoardStyles, {}),
|
|
9254
|
+
/* @__PURE__ */ jsx74(WhiteboardCanvas2, { ...board, headless: true, canDraw, collab: board.collab ? { ...board.collab, canDraw, canManage: isHost } : void 0, inkColor: ink, theme: "light", isVisible: visible, className: "chalk-board-engine", onEngineReady: setEngine }),
|
|
9255
|
+
!ready ? /* @__PURE__ */ jsxs48("div", { className: "chalk-board-loading", role: "status", children: [
|
|
9256
|
+
/* @__PURE__ */ jsx74("span", { className: "chalk-board-spinner", "aria-hidden": "true" }),
|
|
9257
|
+
"Opening board"
|
|
9258
|
+
] }) : null,
|
|
9259
|
+
primary && ready && empty ? /* @__PURE__ */ jsx74(BoardHint, { canDraw }) : null,
|
|
9260
|
+
primary && tool === "note" && canDraw ? /* @__PURE__ */ jsx74("div", { className: "chalk-board-note-layer", "aria-hidden": "true", onPointerDown: placeNoteAt }) : null,
|
|
9261
|
+
primary ? /* @__PURE__ */ jsx74(BoardCursors, { cursorsRef: cursors, cameraRef: camera.cameraRef, colorFor, hidden: offline }) : null,
|
|
9262
|
+
primary && ready ? /* @__PURE__ */ jsxs48(Fragment31, { children: [
|
|
9263
|
+
/* @__PURE__ */ jsxs48("div", { className: "chalk-board-chips", "data-low": !compact && place === "top" && canDraw || void 0, children: [
|
|
9264
|
+
followed ? /* @__PURE__ */ jsxs48("div", { className: "chalk-board-chip", role: "status", children: [
|
|
9265
|
+
/* @__PURE__ */ jsx74(BoardIcon, { name: "eye", size: 16 }),
|
|
9266
|
+
"Following ",
|
|
9267
|
+
followed,
|
|
9268
|
+
/* @__PURE__ */ jsx74("button", { type: "button", className: "chalk-board-chip-button", onClick: stopFollowing, children: "Stop" })
|
|
9269
|
+
] }) : null,
|
|
9270
|
+
offline ? /* @__PURE__ */ jsxs48("div", { className: "chalk-board-chip", "data-quiet": true, role: "status", children: [
|
|
9271
|
+
/* @__PURE__ */ jsx74(BoardIcon, { name: "wifiOff", size: 16 }),
|
|
9272
|
+
"Offline. Your drawing will sync."
|
|
9273
|
+
] }) : null
|
|
9274
|
+
] }),
|
|
9275
|
+
!canDraw ? /* @__PURE__ */ jsxs48("div", { className: "chalk-board-view-only", children: [
|
|
9276
|
+
/* @__PURE__ */ jsx74(BoardIcon, { name: "lock", size: 14 }),
|
|
9277
|
+
"View only"
|
|
9278
|
+
] }) : null,
|
|
9279
|
+
/* @__PURE__ */ jsx74("div", { className: "chalk-board-corner", children: /* @__PURE__ */ jsx74(
|
|
9280
|
+
BoardMenu,
|
|
9281
|
+
{
|
|
9282
|
+
surface,
|
|
9283
|
+
onSurface: chooseSurface,
|
|
9284
|
+
place: !compact && canDraw ? place : void 0,
|
|
9285
|
+
onPlace: (next) => choosePlace(next, true),
|
|
9286
|
+
onDownload: () => void download(),
|
|
9287
|
+
onCopy: () => void copy(),
|
|
9288
|
+
onBring: isHost && sendView ? bring : void 0,
|
|
9289
|
+
onClear: isHost ? () => void clear() : void 0,
|
|
9290
|
+
onClose
|
|
9291
|
+
}
|
|
9292
|
+
) }),
|
|
9293
|
+
/* @__PURE__ */ jsxs48("div", { className: "chalk-board-zoom", role: "group", "aria-label": "Zoom", children: [
|
|
9294
|
+
/* @__PURE__ */ jsx74("button", { type: "button", "aria-label": "Zoom out", onClick: () => zoomBy(1 / ZOOM_STEP), children: /* @__PURE__ */ jsx74(BoardIcon, { name: "minus", size: 16 }) }),
|
|
9295
|
+
/* @__PURE__ */ jsxs48("button", { type: "button", "aria-label": `Zoom ${Math.round(camera.zoom * 100)}%, reset to 100%`, className: "chalk-board-zoom-value", onClick: () => zoomBy(1 / camera.cameraRef.current.zoom), children: [
|
|
9296
|
+
Math.round(camera.zoom * 100),
|
|
9297
|
+
"%"
|
|
9298
|
+
] }),
|
|
9299
|
+
/* @__PURE__ */ jsx74("button", { type: "button", "aria-label": "Zoom in", onClick: () => zoomBy(ZOOM_STEP), children: /* @__PURE__ */ jsx74(BoardIcon, { name: "plus", size: 16 }) }),
|
|
9300
|
+
/* @__PURE__ */ jsx74("span", { className: "chalk-board-zoom-rule", "aria-hidden": "true" }),
|
|
9301
|
+
/* @__PURE__ */ jsx74("button", { type: "button", "aria-label": "Fit to content", onClick: fit, children: /* @__PURE__ */ jsx74(BoardIcon, { name: "fit", size: 16 }) })
|
|
9302
|
+
] }),
|
|
9303
|
+
canDraw ? /* @__PURE__ */ jsx74(
|
|
9304
|
+
BoardIsland,
|
|
9305
|
+
{
|
|
9306
|
+
tool,
|
|
9307
|
+
shape,
|
|
9308
|
+
onTool: chooseTool,
|
|
9309
|
+
color,
|
|
9310
|
+
width,
|
|
9311
|
+
onColor: (next) => restyle({ color: next }),
|
|
9312
|
+
onWidth: (next) => restyle({ width: next }),
|
|
9313
|
+
surface,
|
|
9314
|
+
theme: colorScheme,
|
|
9315
|
+
canUndo: history.canUndo,
|
|
9316
|
+
canRedo: history.canRedo,
|
|
9317
|
+
onUndo: undo,
|
|
9318
|
+
onRedo: redo,
|
|
9319
|
+
place,
|
|
9320
|
+
onPlace: choosePlace,
|
|
9321
|
+
compact
|
|
9322
|
+
}
|
|
9323
|
+
) : null,
|
|
9324
|
+
/* @__PURE__ */ jsx74("div", { className: "chalk-board-announcer", "aria-live": "polite", children: announcement })
|
|
9325
|
+
] }) : null
|
|
9326
|
+
] });
|
|
9327
|
+
}
|
|
9328
|
+
function BoardHint({ canDraw }) {
|
|
9329
|
+
if (!canDraw)
|
|
9330
|
+
return /* @__PURE__ */ jsx74("div", { className: "chalk-board-hint", children: /* @__PURE__ */ jsx74("p", { children: "Nothing here yet. When someone draws, it shows up here." }) });
|
|
9331
|
+
return /* @__PURE__ */ jsxs48("div", { className: "chalk-board-hint", children: [
|
|
9332
|
+
/* @__PURE__ */ jsx74("p", { children: "An empty board. Draw, type or add a sticky note." }),
|
|
9333
|
+
/* @__PURE__ */ jsxs48("ul", { children: [
|
|
9334
|
+
/* @__PURE__ */ jsxs48("li", { children: [
|
|
9335
|
+
/* @__PURE__ */ jsx74("kbd", { children: "P" }),
|
|
9336
|
+
" Pen"
|
|
9337
|
+
] }),
|
|
9338
|
+
/* @__PURE__ */ jsxs48("li", { children: [
|
|
9339
|
+
/* @__PURE__ */ jsx74("kbd", { children: "T" }),
|
|
9340
|
+
" Text"
|
|
9341
|
+
] }),
|
|
9342
|
+
/* @__PURE__ */ jsxs48("li", { children: [
|
|
9343
|
+
/* @__PURE__ */ jsx74("kbd", { children: "N" }),
|
|
9344
|
+
" Sticky note"
|
|
9345
|
+
] })
|
|
9346
|
+
] })
|
|
9347
|
+
] });
|
|
9348
|
+
}
|
|
9349
|
+
|
|
7763
9350
|
// src/components/stage/SpaceStage.tsx
|
|
7764
|
-
import {
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
const
|
|
7768
|
-
const [
|
|
7769
|
-
const [
|
|
9351
|
+
import { jsx as jsx75 } from "react/jsx-runtime";
|
|
9352
|
+
function SpaceStage({ tiles, layout, onLayoutChange, generatedAvatars = true, whiteboard, onOverflowClick, shareCapture, onStopSharing, onChangeShare, colorScheme = "dark", onCloseBoard, className }) {
|
|
9353
|
+
const items = useMemo17(() => buildStageItems(tiles, whiteboard?.isOpen === true), [tiles, whiteboard?.isOpen]);
|
|
9354
|
+
const [selectedShare, setSelectedShare] = useState44(null);
|
|
9355
|
+
const [pin, setPin] = useState44(null);
|
|
9356
|
+
const [following, setFollowing] = useState44(null);
|
|
9357
|
+
const boardOpen = whiteboard?.isOpen === true && Boolean(whiteboard.props);
|
|
9358
|
+
useEffect38(() => {
|
|
9359
|
+
if (!boardOpen) setFollowing(null);
|
|
9360
|
+
}, [boardOpen]);
|
|
9361
|
+
const canFollow = boardOpen && Boolean(whiteboard?.sendView);
|
|
9362
|
+
const followed = canFollow && tiles.some((tile) => tile.id === following) ? following : null;
|
|
9363
|
+
const activePin = items.some((item) => item.id === pin) ? pin : null;
|
|
7770
9364
|
const shares = items.filter((item) => item.kind === "screen-share");
|
|
7771
9365
|
const activeShare = selectedShare && shares.some((item) => item.id === selectedShare) ? selectedShare : null;
|
|
7772
|
-
|
|
9366
|
+
useEffect38(() => {
|
|
9367
|
+
if (pin !== activePin) setPin(activePin);
|
|
9368
|
+
if (selectedShare !== activeShare) setSelectedShare(activeShare);
|
|
9369
|
+
}, [pin, activePin, selectedShare, activeShare]);
|
|
9370
|
+
const follow = useCallback14((id) => {
|
|
9371
|
+
if (id) {
|
|
9372
|
+
setPin(null);
|
|
9373
|
+
setSelectedShare(null);
|
|
9374
|
+
}
|
|
9375
|
+
setFollowing(id);
|
|
9376
|
+
}, []);
|
|
9377
|
+
const renderContentTile = useCallback14(
|
|
7773
9378
|
(item, context) => {
|
|
7774
9379
|
if (item.kind === "screen-share")
|
|
7775
|
-
return /* @__PURE__ */
|
|
9380
|
+
return /* @__PURE__ */ jsx75(
|
|
7776
9381
|
StageContentTile,
|
|
7777
9382
|
{
|
|
7778
9383
|
item,
|
|
@@ -7780,7 +9385,7 @@ function SpaceStage({ tiles, layout, onLayoutChange, generatedAvatars = true, wh
|
|
|
7780
9385
|
capture: item.participant.isLocal ? shareCapture : void 0,
|
|
7781
9386
|
onStopSharing: item.participant.isLocal ? onStopSharing : void 0,
|
|
7782
9387
|
onChangeShare: item.participant.isLocal ? onChangeShare : void 0,
|
|
7783
|
-
switcher: shares.length > 1 ? /* @__PURE__ */
|
|
9388
|
+
switcher: shares.length > 1 ? /* @__PURE__ */ jsx75("div", { role: "group", "aria-label": "Whose screen is big", className: cn("absolute left-2 z-30 flex rounded-[999px] bg-[rgba(12,14,18,.82)] p-[3px]", item.participant.screenShareState === "low-quality" ? "top-11" : "top-2"), children: shares.map((share) => /* @__PURE__ */ jsx75(
|
|
7784
9389
|
"button",
|
|
7785
9390
|
{
|
|
7786
9391
|
type: "button",
|
|
@@ -7797,44 +9402,52 @@ function SpaceStage({ tiles, layout, onLayoutChange, generatedAvatars = true, wh
|
|
|
7797
9402
|
}
|
|
7798
9403
|
);
|
|
7799
9404
|
const boardProps = whiteboard?.props;
|
|
7800
|
-
return /* @__PURE__ */
|
|
9405
|
+
return /* @__PURE__ */ jsx75(
|
|
7801
9406
|
StageContentTileSurface,
|
|
7802
9407
|
{
|
|
7803
9408
|
item,
|
|
7804
9409
|
...context,
|
|
7805
9410
|
mediaVisible: Boolean(boardProps),
|
|
7806
|
-
media: boardProps ? /* @__PURE__ */
|
|
7807
|
-
WhiteboardView,
|
|
7808
|
-
{
|
|
7809
|
-
...boardProps,
|
|
7810
|
-
canDraw: context.primary ? boardProps.canDraw : false,
|
|
7811
|
-
collab: boardProps.collab ? { ...boardProps.collab, canDraw: context.primary && boardProps.collab.canDraw } : void 0,
|
|
7812
|
-
isVisible: context.active,
|
|
7813
|
-
className: cn("h-full !min-h-0", boardProps.className)
|
|
7814
|
-
}
|
|
7815
|
-
) }) : null
|
|
9411
|
+
media: boardProps ? /* @__PURE__ */ jsx75("div", { className: cn("h-full", !context.primary && "pointer-events-none"), inert: !context.primary || void 0, children: /* @__PURE__ */ jsx75(Board, { board: boardProps, primary: context.primary, visible: context.active, colorScheme, people: tiles, following: followed, onFollow: follow, sendView: canFollow ? whiteboard?.sendView : void 0, onClose: onCloseBoard }) }) : null
|
|
7816
9412
|
}
|
|
7817
9413
|
);
|
|
7818
9414
|
},
|
|
7819
|
-
[whiteboard, shares, shareCapture, onStopSharing, onChangeShare]
|
|
9415
|
+
[follow, whiteboard, shares, shareCapture, onStopSharing, onChangeShare, colorScheme, tiles, followed, canFollow, onCloseBoard]
|
|
7820
9416
|
);
|
|
7821
|
-
const focusContent =
|
|
9417
|
+
const focusContent = useCallback14(
|
|
7822
9418
|
(item) => {
|
|
7823
9419
|
if (item.kind !== "participant") onLayoutChange?.("focus");
|
|
7824
9420
|
},
|
|
7825
9421
|
[onLayoutChange]
|
|
7826
9422
|
);
|
|
7827
|
-
return /* @__PURE__ */
|
|
9423
|
+
return /* @__PURE__ */ jsx75(
|
|
9424
|
+
Stage,
|
|
9425
|
+
{
|
|
9426
|
+
items,
|
|
9427
|
+
layout,
|
|
9428
|
+
pinnedId: activePin ?? activeShare ?? (followed ? "whiteboard" : null),
|
|
9429
|
+
onPinnedChange: setPin,
|
|
9430
|
+
onFollowChange: (id) => follow(followed === id ? null : id),
|
|
9431
|
+
followingId: followed,
|
|
9432
|
+
clickAction: canFollow ? "follow" : "pin",
|
|
9433
|
+
renderContentTile,
|
|
9434
|
+
onItemClick: focusContent,
|
|
9435
|
+
onOverflowClick: canFollow ? void 0 : onOverflowClick,
|
|
9436
|
+
generatedAvatars,
|
|
9437
|
+
emptyState: /* @__PURE__ */ jsx75(QuietSpace, {}),
|
|
9438
|
+
className
|
|
9439
|
+
}
|
|
9440
|
+
);
|
|
7828
9441
|
}
|
|
7829
9442
|
SpaceStage.displayName = "SpaceStage";
|
|
7830
9443
|
|
|
7831
9444
|
// src/components/space-view/useDrawerPresence.ts
|
|
7832
|
-
import { useEffect as
|
|
9445
|
+
import { useEffect as useEffect39, useState as useState45 } from "react";
|
|
7833
9446
|
var DRAWER_EXIT_MS = 200;
|
|
7834
9447
|
function useDrawerPresence(panel, exitMs) {
|
|
7835
|
-
const [retained, setRetained] =
|
|
7836
|
-
const [state, setState] =
|
|
7837
|
-
|
|
9448
|
+
const [retained, setRetained] = useState45(panel);
|
|
9449
|
+
const [state, setState] = useState45(panel === null ? "closed" : "open");
|
|
9450
|
+
useEffect39(() => {
|
|
7838
9451
|
if (panel !== null) {
|
|
7839
9452
|
setRetained(panel);
|
|
7840
9453
|
setState("open");
|
|
@@ -7851,7 +9464,7 @@ function useDrawerPresence(panel, exitMs) {
|
|
|
7851
9464
|
}
|
|
7852
9465
|
|
|
7853
9466
|
// src/components/space-view/SpaceView.tsx
|
|
7854
|
-
import { Fragment as
|
|
9467
|
+
import { Fragment as Fragment32, jsx as jsx76, jsxs as jsxs49 } from "react/jsx-runtime";
|
|
7855
9468
|
function SpaceView({
|
|
7856
9469
|
colorScheme = "dark",
|
|
7857
9470
|
onColorSchemeChange,
|
|
@@ -7895,11 +9508,11 @@ function SpaceView({
|
|
|
7895
9508
|
const canManageAdmission = useCan("manageAdmission");
|
|
7896
9509
|
const canManageRecording = useCan("manageRecording");
|
|
7897
9510
|
const episodeDuration = useEpisodeDuration();
|
|
7898
|
-
const [uncontrolledLayout, setUncontrolledLayout] =
|
|
7899
|
-
const [activePanel, setActivePanel] =
|
|
7900
|
-
const [isReactionPickerOpen, setReactionPickerOpen] =
|
|
7901
|
-
const [commandError, setCommandError] =
|
|
7902
|
-
const [commandSource, setCommandSource] =
|
|
9511
|
+
const [uncontrolledLayout, setUncontrolledLayout] = useState46(controlledLayout ?? "focus");
|
|
9512
|
+
const [activePanel, setActivePanel] = useState46(initialPanel);
|
|
9513
|
+
const [isReactionPickerOpen, setReactionPickerOpen] = useState46(false);
|
|
9514
|
+
const [commandError, setCommandError] = useState46(null);
|
|
9515
|
+
const [commandSource, setCommandSource] = useState46();
|
|
7903
9516
|
const onCommandError = (failure, source) => {
|
|
7904
9517
|
if (!failure && commandError && typeof commandError === "object") callToasts.dismissFailure(commandError.code);
|
|
7905
9518
|
setCommandError(failure);
|
|
@@ -7909,15 +9522,15 @@ function SpaceView({
|
|
|
7909
9522
|
const layout = onLayoutChange ? controlledLayout ?? uncontrolledLayout : uncontrolledLayout;
|
|
7910
9523
|
const feature = (name) => features?.[name] !== false;
|
|
7911
9524
|
const localId = self.participantId ?? "local";
|
|
7912
|
-
const tiles =
|
|
9525
|
+
const tiles = useMemo18(
|
|
7913
9526
|
() => toVideoParticipants(participantsSlice.roster, media.remote, localId, self.displayName ?? "You", media.local).map((tile) => ({
|
|
7914
9527
|
...tile,
|
|
7915
9528
|
screenShareHasAudio: tile.isLocal ? media.screenShare.hasAudio : participantsSlice.roster.find((person) => person.participantId === tile.id)?.media.screenShareHasAudio
|
|
7916
9529
|
})),
|
|
7917
9530
|
[localId, media.local, media.remote, media.screenShare.hasAudio, participantsSlice.roster, self.displayName]
|
|
7918
9531
|
);
|
|
7919
|
-
|
|
7920
|
-
|
|
9532
|
+
useEffect40(() => setActivePanel(initialPanel), [initialPanel]);
|
|
9533
|
+
useEffect40(() => {
|
|
7921
9534
|
if (feature("admission") && feature("participants") && canManageAdmission && participantsSlice.admissionQueue.length > 0) setActivePanel("participants");
|
|
7922
9535
|
}, [canManageAdmission, participantsSlice.admissionQueue.length, features?.admission, features?.participants]);
|
|
7923
9536
|
const buttons = [
|
|
@@ -7939,7 +9552,7 @@ function SpaceView({
|
|
|
7939
9552
|
const playSound = useSoundCues(feature("sounds") && soundsEnabled);
|
|
7940
9553
|
const prefersReducedMotion = usePrefersReducedMotion();
|
|
7941
9554
|
const drawer = useDrawerPresence(activePanel, prefersReducedMotion ? 0 : DRAWER_EXIT_MS);
|
|
7942
|
-
const runCommand =
|
|
9555
|
+
const runCommand = useCallback15(async (command) => {
|
|
7943
9556
|
try {
|
|
7944
9557
|
await command();
|
|
7945
9558
|
setCommandError(null);
|
|
@@ -7958,17 +9571,32 @@ function SpaceView({
|
|
|
7958
9571
|
else if (commandSource === "microphone") await client.media.setMicrophoneEnabled(true);
|
|
7959
9572
|
onCommandError(null);
|
|
7960
9573
|
} catch (cause) {
|
|
9574
|
+
if (cause instanceof SpaceClientError2 && cause.code === "connection.invalid_state") return;
|
|
7961
9575
|
onCommandError(cause instanceof SpaceClientError2 ? cause : { code: "client.internal_error", recoverable: true, message: "Your device couldn\u2019t start. Try again." }, commandSource);
|
|
7962
9576
|
}
|
|
7963
9577
|
};
|
|
7964
9578
|
const retryableMedia = commandSource && typeof displayedCommandError === "object" && displayedCommandError?.recoverable && (commandSource === "screen" || displayedCommandError.code === "media.device_busy");
|
|
7965
9579
|
const commandToasts = mappedFailure ? [{ id: "space-command-error", ...mappedFailure, action: retryableMedia ? { label: "Try again", onClick: () => void retryMedia() } : void 0 }] : typeof displayedCommandError === "string" ? [{ id: "space-command-error", message: displayedCommandError, type: "error" }] : [];
|
|
9580
|
+
const [boardToasts, setBoardToasts] = useState46([]);
|
|
9581
|
+
const boardToastCount = useRef34(0);
|
|
9582
|
+
useEffect40(() => {
|
|
9583
|
+
if (!whiteboard?.isOpen) setBoardToasts([]);
|
|
9584
|
+
}, [whiteboard?.isOpen]);
|
|
9585
|
+
const showBoardToast = useCallback15((toast) => {
|
|
9586
|
+
if (!toast) {
|
|
9587
|
+
setBoardToasts([]);
|
|
9588
|
+
return;
|
|
9589
|
+
}
|
|
9590
|
+
boardToastCount.current += 1;
|
|
9591
|
+
const id = `board-${boardToastCount.current}`;
|
|
9592
|
+
setBoardToasts((current) => [...current.slice(-2), { ...toast, id }]);
|
|
9593
|
+
}, []);
|
|
7966
9594
|
const dismissCommandError = () => {
|
|
7967
9595
|
if (commandError && typeof commandError === "object") callToasts.dismissFailure(commandError.code);
|
|
7968
9596
|
setCommandError(null);
|
|
7969
9597
|
onDismissCommandError?.();
|
|
7970
9598
|
};
|
|
7971
|
-
return /* @__PURE__ */
|
|
9599
|
+
return /* @__PURE__ */ jsx76(ParticipantVolumeProvider, { children: /* @__PURE__ */ jsx76(BoardToastContext.Provider, { value: showBoardToast, children: /* @__PURE__ */ jsx76(
|
|
7972
9600
|
SpacePresentation,
|
|
7973
9601
|
{
|
|
7974
9602
|
colorScheme,
|
|
@@ -7987,21 +9615,24 @@ function SpaceView({
|
|
|
7987
9615
|
diagnostics: callDiagnostics(connection, media),
|
|
7988
9616
|
shortcuts: {
|
|
7989
9617
|
isMuted: media.local.microphone.state !== "enabled",
|
|
7990
|
-
onToggleMute: () => void runCommand(() => client.media.setMicrophoneEnabled(media.local.microphone.state !== "enabled")),
|
|
7991
|
-
onToggleVideo: () => void runCommand(() => client.media.setCameraEnabled(media.local.camera.state !== "enabled")),
|
|
9618
|
+
onToggleMute: connection.status === "live" || media.local.microphone.state === "enabled" ? () => void runCommand(() => client.media.setMicrophoneEnabled(media.local.microphone.state !== "enabled")) : void 0,
|
|
9619
|
+
onToggleVideo: connection.status === "live" || media.local.camera.state === "enabled" ? () => void runCommand(() => client.media.setCameraEnabled(media.local.camera.state !== "enabled")) : void 0,
|
|
7992
9620
|
onToggleHand: feature("handRaise") && canRaiseHand ? () => void runCommand(() => self.handRaised ? client.participants.lowerHand() : client.participants.raiseHand()) : void 0,
|
|
7993
9621
|
onToggleChat: feature("chat") ? () => setActivePanel((current) => current === "chat" ? null : "chat") : void 0,
|
|
7994
9622
|
onTogglePeople: feature("participants") ? () => setActivePanel((current) => current === "participants" ? null : "participants") : void 0,
|
|
7995
9623
|
onToggleLayout: () => updateLayout(layout === "grid" ? "focus" : "grid"),
|
|
7996
9624
|
onClosePanel: () => setActivePanel(null),
|
|
7997
|
-
onPushToTalk: (enabled) =>
|
|
9625
|
+
onPushToTalk: (enabled) => {
|
|
9626
|
+
if (enabled && connection.status !== "live") return;
|
|
9627
|
+
void runCommand(() => client.media.setMicrophoneEnabled(enabled));
|
|
9628
|
+
}
|
|
7998
9629
|
},
|
|
7999
9630
|
duration: episodeDuration,
|
|
8000
9631
|
isRecording: recording.current?.status === "recording",
|
|
8001
9632
|
layout,
|
|
8002
9633
|
onLayoutChange: updateLayout
|
|
8003
9634
|
},
|
|
8004
|
-
stage: /* @__PURE__ */
|
|
9635
|
+
stage: /* @__PURE__ */ jsx76(
|
|
8005
9636
|
SpaceStage,
|
|
8006
9637
|
{
|
|
8007
9638
|
tiles,
|
|
@@ -8009,16 +9640,20 @@ function SpaceView({
|
|
|
8009
9640
|
onLayoutChange: updateLayout,
|
|
8010
9641
|
generatedAvatars,
|
|
8011
9642
|
whiteboard,
|
|
9643
|
+
onCloseBoard: whiteboard?.isOpen ? onToggleWhiteboard : void 0,
|
|
9644
|
+
colorScheme,
|
|
8012
9645
|
onOverflowClick: feature("participants") ? () => setActivePanel("participants") : void 0,
|
|
8013
9646
|
shareCapture: media.screenShare.capture,
|
|
8014
|
-
onStopSharing: () =>
|
|
8015
|
-
|
|
8016
|
-
await client.media.setScreenShareEnabled(true, options);
|
|
9647
|
+
onStopSharing: async () => {
|
|
9648
|
+
await client.media.setScreenShareEnabled(false);
|
|
8017
9649
|
},
|
|
9650
|
+
onChangeShare: connection.status === "live" ? async (options) => {
|
|
9651
|
+
await client.media.setScreenShareEnabled(true, options);
|
|
9652
|
+
} : void 0,
|
|
8018
9653
|
className: "h-full"
|
|
8019
9654
|
}
|
|
8020
9655
|
),
|
|
8021
|
-
controls: /* @__PURE__ */
|
|
9656
|
+
controls: /* @__PURE__ */ jsx76(
|
|
8022
9657
|
ControlBar,
|
|
8023
9658
|
{
|
|
8024
9659
|
density: "comfortable",
|
|
@@ -8038,9 +9673,9 @@ function SpaceView({
|
|
|
8038
9673
|
onEndForEveryone: endCallForEveryone
|
|
8039
9674
|
}
|
|
8040
9675
|
),
|
|
8041
|
-
stageOverlay: /* @__PURE__ */
|
|
8042
|
-
feature("reactions") ? /* @__PURE__ */
|
|
8043
|
-
feature("reactions") && canSendReaction ? /* @__PURE__ */
|
|
9676
|
+
stageOverlay: /* @__PURE__ */ jsxs49(Fragment32, { children: [
|
|
9677
|
+
feature("reactions") ? /* @__PURE__ */ jsx76(ReactionsOverlay, {}) : null,
|
|
9678
|
+
feature("reactions") && canSendReaction && connection.status === "live" ? /* @__PURE__ */ jsx76("div", { className: "absolute bottom-24 left-1/2 z-50 -translate-x-1/2", children: /* @__PURE__ */ jsx76(
|
|
8044
9679
|
ReactionPicker,
|
|
8045
9680
|
{
|
|
8046
9681
|
isHandRaised: self.handRaised,
|
|
@@ -8053,28 +9688,32 @@ function SpaceView({
|
|
|
8053
9688
|
}
|
|
8054
9689
|
) }) : null
|
|
8055
9690
|
] }),
|
|
8056
|
-
sidebar: /* @__PURE__ */
|
|
8057
|
-
drawer.panel === "chat" && feature("chat") ? /* @__PURE__ */
|
|
8058
|
-
drawer.panel === "participants" && feature("participants") ? /* @__PURE__ */
|
|
8059
|
-
drawer.panel === "admission" && feature("admission") ? /* @__PURE__ */
|
|
9691
|
+
sidebar: /* @__PURE__ */ jsxs49(CallCard, { state: drawer.state, panel: drawer.panel, onPanelChange: setActivePanel, peopleCount: participantsSlice.roster.length, participantsEnabled: feature("participants"), chatEnabled: feature("chat"), onClose: () => setActivePanel(null), children: [
|
|
9692
|
+
drawer.panel === "chat" && feature("chat") ? /* @__PURE__ */ jsx76(ChatPanel, { variant: "mobile", generatedAvatars, onClose: () => setActivePanel(null), pickChatFiles }) : null,
|
|
9693
|
+
drawer.panel === "participants" && feature("participants") ? /* @__PURE__ */ jsx76(ParticipantsPanel, { variant: "mobile", admissionEnabled: feature("admission"), generatedAvatars, onCommandError, onClose: () => setActivePanel(null) }) : null,
|
|
9694
|
+
drawer.panel === "admission" && feature("admission") ? /* @__PURE__ */ jsx76(AdmissionPanel, { className: "h-full w-full rounded-none shadow-none", onClose: () => setActivePanel(null) }) : null
|
|
8060
9695
|
] }),
|
|
8061
|
-
toasts: /* @__PURE__ */
|
|
9696
|
+
toasts: /* @__PURE__ */ jsx76(
|
|
8062
9697
|
ToastStack,
|
|
8063
9698
|
{
|
|
8064
|
-
toasts: [...callToasts.toasts.filter((toast) => !(typeof displayedCommandError === "object" && displayedCommandError && toast.failureCode === displayedCommandError.code)), ...commandToasts],
|
|
8065
|
-
onDismiss: (id) =>
|
|
9699
|
+
toasts: [...callToasts.toasts.filter((toast) => !(typeof displayedCommandError === "object" && displayedCommandError && toast.failureCode === displayedCommandError.code)), ...whiteboard?.isOpen ? boardToasts : [], ...commandToasts],
|
|
9700
|
+
onDismiss: (id) => {
|
|
9701
|
+
if (id === "space-command-error") dismissCommandError();
|
|
9702
|
+
else if (id.startsWith("board-")) setBoardToasts((current) => current.filter((toast) => toast.id !== id));
|
|
9703
|
+
else callToasts.dismiss(id);
|
|
9704
|
+
}
|
|
8066
9705
|
}
|
|
8067
9706
|
),
|
|
8068
|
-
mediaOutput: /* @__PURE__ */
|
|
8069
|
-
overlays: /* @__PURE__ */
|
|
8070
|
-
/* @__PURE__ */
|
|
9707
|
+
mediaOutput: /* @__PURE__ */ jsx76("div", { className: "[&>div]:top-[68px] [&>div>button]:h-11 [&>div>button]:shadow-[0_8px_24px_rgba(0,0,0,.4)]", children: /* @__PURE__ */ jsx76(AudioOutput, { resumeAudioLabel: "Your browser paused the sound. Click to hear the call" }) }),
|
|
9708
|
+
overlays: /* @__PURE__ */ jsxs49(Fragment32, { children: [
|
|
9709
|
+
/* @__PURE__ */ jsx76(CallReady, { open: callLinkOpen ?? callLink.open, onClose: onCallLinkToggle ?? callLink.close, link: effectiveShareUrl }),
|
|
8071
9710
|
overlay,
|
|
8072
|
-
reconnecting && reconnecting.status !== "reconnecting" ? /* @__PURE__ */
|
|
8073
|
-
/* @__PURE__ */
|
|
9711
|
+
reconnecting && reconnecting.status !== "reconnecting" ? /* @__PURE__ */ jsx76(ReconnectingOverlay, { ...reconnecting }) : null,
|
|
9712
|
+
/* @__PURE__ */ jsx76(OfflineActions, { reconnecting: reconnecting?.status === "reconnecting" && reconnecting.isVisible })
|
|
8074
9713
|
] }),
|
|
8075
9714
|
className
|
|
8076
9715
|
}
|
|
8077
|
-
) });
|
|
9716
|
+
) }) });
|
|
8078
9717
|
}
|
|
8079
9718
|
|
|
8080
9719
|
export {
|
|
@@ -8141,6 +9780,6 @@ export {
|
|
|
8141
9780
|
Stage,
|
|
8142
9781
|
ParticipantGrid,
|
|
8143
9782
|
QuietSpace,
|
|
8144
|
-
|
|
8145
|
-
|
|
9783
|
+
SpaceView,
|
|
9784
|
+
WhiteboardView
|
|
8146
9785
|
};
|