From 5e5aad54cb1cea9a25001bdeae0da33668b97e6c Mon Sep 17 00:00:00 2001 From: kcar Date: Sat, 21 Feb 2026 16:24:29 +0000 Subject: [PATCH] chore: remove dist artifacts, untrack .env, add vite-env types, ignore package-lock --- .env | 17 - .gitignore | 7 +- dist/.vite/manifest.json | 59 - dist/assets/index-CmYeIoD0.js | 68827 ------------------------- dist/assets/index-CmYeIoD0.js.map | 1 - dist/assets/index.css | 11178 ---- dist/assets/pdf.js | 21232 -------- dist/assets/pdf.js.map | 1 - dist/assets/pdf.worker.min.mjs | 21 - dist/assets/vendor-mui.js | 19850 ------- dist/assets/vendor-mui.js.map | 1 - dist/assets/vendor-react.js | 1901 - dist/assets/vendor-react.js.map | 1 - dist/assets/vendor-tldraw.js | 68796 ------------------------ dist/assets/vendor-tldraw.js.map | 1 - dist/assets/vendor-utils.js | 12264 ----- dist/assets/vendor-utils.js.map | 1 - dist/audioWorklet.js | 12 - dist/favicon.ico | Bin 15406 -> 0 bytes dist/icons/icon-192x192-maskable.png | Bin 54525 -> 0 bytes dist/icons/icon-192x192.png | Bin 54525 -> 0 bytes dist/icons/icon-512x512-maskable.png | Bin 265495 -> 0 bytes dist/icons/icon-512x512.png | Bin 265495 -> 0 bytes dist/icons/sticker-tool.svg | 21 - dist/index.html | 18 - dist/manifest.webmanifest | 1 - dist/offline.html | 70 - dist/registerSW.js | 1 - dist/sw.js | 3889 -- dist/sw.js.map | 1 - package-lock.json | 16525 ------ src/vite-env.d.ts | 48 + 32 files changed, 54 insertions(+), 224690 deletions(-) delete mode 100644 .env delete mode 100644 dist/.vite/manifest.json delete mode 100644 dist/assets/index-CmYeIoD0.js delete mode 100644 dist/assets/index-CmYeIoD0.js.map delete mode 100644 dist/assets/index.css delete mode 100644 dist/assets/pdf.js delete mode 100644 dist/assets/pdf.js.map delete mode 100644 dist/assets/pdf.worker.min.mjs delete mode 100644 dist/assets/vendor-mui.js delete mode 100644 dist/assets/vendor-mui.js.map delete mode 100644 dist/assets/vendor-react.js delete mode 100644 dist/assets/vendor-react.js.map delete mode 100644 dist/assets/vendor-tldraw.js delete mode 100644 dist/assets/vendor-tldraw.js.map delete mode 100644 dist/assets/vendor-utils.js delete mode 100644 dist/assets/vendor-utils.js.map delete mode 100644 dist/audioWorklet.js delete mode 100644 dist/favicon.ico delete mode 100644 dist/icons/icon-192x192-maskable.png delete mode 100644 dist/icons/icon-192x192.png delete mode 100644 dist/icons/icon-512x512-maskable.png delete mode 100644 dist/icons/icon-512x512.png delete mode 100644 dist/icons/sticker-tool.svg delete mode 100644 dist/index.html delete mode 100644 dist/manifest.webmanifest delete mode 100644 dist/offline.html delete mode 100644 dist/registerSW.js delete mode 100644 dist/sw.js delete mode 100644 dist/sw.js.map delete mode 100644 package-lock.json create mode 100644 src/vite-env.d.ts diff --git a/.env b/.env deleted file mode 100644 index cef605c..0000000 --- a/.env +++ /dev/null @@ -1,17 +0,0 @@ -PORT_FRONTEND=5173 -PORT_FRONTEND_HMR=3002 -PORT_API=800 -PORT_SUPABASE=8000 - -VITE_PORT_FRONTEND=5173 -VITE_PORT_FRONTEND_HMR=5173 - -VITE_APP_NAME=Classroom Copilot -VITE_SUPER_ADMIN_EMAIL=admin@classroomcopilot.ai -VITE_DEV=true -VITE_FRONTEND_SITE_URL=https://app.classroomcopilot.ai -VITE_APP_HMR_URL=https://app.classroomcopilot.ai -VITE_SUPABASE_URL=https://supa.classroomcopilot.ai -VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0 -VITE_API_URL=https://api.classroomcopilot.ai -VITE_API_BASE=https://api.classroomcopilot.ai \ No newline at end of file diff --git a/.gitignore b/.gitignore index a2e04e4..42277ab 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ node_modules -.env \ No newline at end of file +.env +# Build output +dist/ + +# Lock files - clean install +package-lock.json diff --git a/dist/.vite/manifest.json b/dist/.vite/manifest.json deleted file mode 100644 index 9c6d253..0000000 --- a/dist/.vite/manifest.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "_vendor-mui.js": { - "file": "assets/vendor-mui.js", - "name": "vendor-mui", - "imports": [ - "_vendor-react.js" - ] - }, - "_vendor-react.js": { - "file": "assets/vendor-react.js", - "name": "vendor-react" - }, - "_vendor-tldraw.js": { - "file": "assets/vendor-tldraw.js", - "name": "vendor-tldraw", - "imports": [ - "_vendor-react.js", - "_vendor-mui.js" - ] - }, - "_vendor-utils.js": { - "file": "assets/vendor-utils.js", - "name": "vendor-utils", - "imports": [ - "_vendor-react.js" - ] - }, - "index.html": { - "file": "assets/index-CmYeIoD0.js", - "name": "index", - "src": "index.html", - "isEntry": true, - "imports": [ - "_vendor-mui.js", - "_vendor-react.js", - "_vendor-tldraw.js", - "_vendor-utils.js" - ], - "dynamicImports": [ - "node_modules/pdfjs-dist/build/pdf.mjs" - ], - "css": [ - "assets/index.css" - ], - "assets": [ - "assets/pdf.worker.min.mjs" - ] - }, - "node_modules/pdfjs-dist/build/pdf.mjs": { - "file": "assets/pdf.js", - "name": "pdf", - "src": "node_modules/pdfjs-dist/build/pdf.mjs", - "isDynamicEntry": true - }, - "node_modules/pdfjs-dist/build/pdf.worker.min.mjs": { - "file": "assets/pdf.worker.min.mjs", - "src": "node_modules/pdfjs-dist/build/pdf.worker.min.mjs" - } -} \ No newline at end of file diff --git a/dist/assets/index-CmYeIoD0.js b/dist/assets/index-CmYeIoD0.js deleted file mode 100644 index ae369ee..0000000 --- a/dist/assets/index-CmYeIoD0.js +++ /dev/null @@ -1,68827 +0,0 @@ -import { p as propTypesExports, d as createTheme, j as jsxRuntimeExports, e as utils$7, g as styled, B as Box, i as Button, A as AccountCircleIcon, C as CalendarIcon, T as TeacherIcon, k as BusinessIcon, G as GraphIcon, m as ClassIcon, n as Tooltip, I as IconButton, q as ArrowBackIcon, H as HistoryIcon, r as ArrowForwardIcon, M as Menu$1, t as MenuItem, L as ListItemIcon, v as ListItemText, S as StudentIcon, E as ExpandMoreIcon, w as useTheme, x as AppBar, y as Toolbar$1, z as Typography, D as TLDrawDevIcon, F as DevToolsIcon, J as Divider, K as MultiplayerIcon, N as ExamIcon, O as ExamMarkerIcon, P as SettingsIcon, Q as SearchIcon, R as AdminIcon, U as LogoutIcon, V as LoginIcon, W as Alert, X as TextField, Y as Container, Z as Stack, _ as Paper, $ as CircularProgress, a0 as ButtonGroup, a1 as List, a2 as ListItem, a3 as Tabs, a4 as useMediaQuery, a5 as ThemeProvider, a6 as Tab, a7 as PushPinIcon, a8 as PushPinOutlinedIcon, a9 as ShapesIcon, aa as NodeIcon, ab as NavigationIcon, ac as YouTubeIcon, ad as SlidesIcon, ae as Snackbar } from './vendor-mui.js'; -import { r as reactExports, e as getAugmentedNamespace, a as reactDomExports, g as getDefaultExportFromCjs, u as useNavigate, b as React$2, f as useLocation, h as useSearchParams, i as Routes, j as Route, B as BrowserRouter } from './vendor-react.js'; -import { c as createShapeId, g as getSnapshot, l as loadSnapshot, B as BaseBoxShapeUtil, H as HTMLContainer, t as toDomPrecision, a as arrayOf, s as string, o as object$1, n as number, b as boolean, D as DefaultSizeStyle, d as DefaultDashStyle, e as DefaultColorStyle, f as clamp$1, h as getIndexBetween, T as TldrawUiDialogHeader, i as TldrawUiDialogTitle, j as TldrawUiDialogCloseButton, k as TldrawUiDialogBody, m as TldrawUiDialogFooter, p as TldrawUiButton, q as TldrawUiButtonLabel, u as useEditor, r as useDialogs, R as Rectangle2d, v as optional, w as DEFAULT_EMBED_DEFINITIONS, x as BindingUtil, V as Vec, y as createShapePropsMigrationSequence, z as createShapePropsMigrationIds, A as createTLSchema, C as defaultShapeSchemas, E as defaultBindingSchemas, F as createTLSchemaFromUtils, G as defaultBindingUtils, I as defaultShapeUtils, J as createTLStore, K as DefaultToolbar, L as TldrawUiMenuItem, M as DefaultToolbarContent, N as DefaultHelperButtons, O as DefaultHelperButtonsContent, P as DefaultNavigationPanel, Q as DefaultStylePanel, S as DefaultStylePanelContent, U as useRelevantStyles, W as DefaultZoomMenu, X as DefaultZoomMenuContent, Y as useTools, Z as DefaultKeyboardShortcutsDialog, _ as DefaultKeyboardShortcutsDialogContent, $ as DefaultContextMenu, a0 as DefaultContextMenuContent, a1 as DefaultDebugMenu, a2 as DefaultDebugMenuContent, a3 as useToasts, a4 as DefaultActionsMenu, a5 as DefaultActionsMenuContent, a6 as createBindingId, a7 as atom, a8 as useValue, a9 as exportToBlob, aa as Box$1, ab as computed, ac as BaseBoxShapeTool, ad as StateNode, ae as useTldrawUser, af as Tldraw, ag as DEFAULT_SUPPORT_VIDEO_TYPES, ah as DEFAULT_SUPPORTED_IMAGE_TYPES, ai as objectMapEntries, aj as objectMapValues, ak as isEqual, al as react, am as uniqueId, an as transact, ao as reverseRecordsDiff, ap as exhaustiveSwitchError, aq as fpsThrottle, ar as squashRecordDiffs, as as warnOnce, at as assert, au as registerTldrawLibraryVersion, av as useRefState, aw as useTLSchemaFromUtils, ax as useShallowObjectIdentity, ay as useAtom, az as isSignal, aA as getUserPreferences, aB as defaultUserPreferences, aC as TAB_ID, aD as createPresenceStateDerivation, aE as AssetRecordType, aF as getHashForString, aG as client } from './vendor-tldraw.js'; -import { c as createClient, a as axios$1, b as create$1, A as AxiosError, _ as __vitePreload } from './vendor-utils.js'; - -true&&(function polyfill() { - const relList = document.createElement("link").relList; - if (relList && relList.supports && relList.supports("modulepreload")) { - return; - } - for (const link of document.querySelectorAll('link[rel="modulepreload"]')) { - processPreload(link); - } - new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type !== "childList") { - continue; - } - for (const node of mutation.addedNodes) { - if (node.tagName === "LINK" && node.rel === "modulepreload") - processPreload(node); - } - } - }).observe(document, { childList: true, subtree: true }); - function getFetchOpts(link) { - const fetchOpts = {}; - if (link.integrity) fetchOpts.integrity = link.integrity; - if (link.referrerPolicy) fetchOpts.referrerPolicy = link.referrerPolicy; - if (link.crossOrigin === "use-credentials") - fetchOpts.credentials = "include"; - else if (link.crossOrigin === "anonymous") fetchOpts.credentials = "omit"; - else fetchOpts.credentials = "same-origin"; - return fetchOpts; - } - function processPreload(link) { - if (link.ep) - return; - link.ep = true; - const fetchOpts = getFetchOpts(link); - fetch(link.href, fetchOpts); - } -}()); - -const LOG_LEVELS = { - error: 0, - // Always shown if enabled - warn: 1, - // Shows warns and errors - info: 2, - // Shows info, warns, and errors - debug: 3, - // Shows debug and above - trace: 4 - // Shows everything -}; -class DebugLogger { - config = { - enabled: true, - level: "debug", - categories: [ - "system", - "navigation", - "presentation", - "selection", - "camera", - "binding", - "shape", - "tldraw-service" - ] - }; - setConfig(config) { - this.config = { ...this.config, ...config }; - } - shouldLog(level, category) { - return this.config.enabled && LOG_LEVELS[level] <= LOG_LEVELS[this.config.level] && this.config.categories.includes(category); - } - log(level, category, message, data) { - if (!this.shouldLog(level, category)) { - return; - } - const levelEmojis = { - error: "🔴", - // Red circle for errors - warn: "âš ī¸", - // Warning symbol - info: "â„šī¸", - // Information symbol - debug: "🔧", - // Wrench for debug - trace: "🔍" - // Magnifying glass for trace - }; - const prefix = `${levelEmojis[level]} [${category}]`; - switch (level) { - case "error": - if (data) { - console.error(`${prefix} ${message}`, data); - } else { - console.error(`${prefix} ${message}`); - } - break; - case "warn": - if (data) { - console.warn(`${prefix} ${message}`, data); - } else { - console.warn(`${prefix} ${message}`); - } - break; - case "info": - if (data) { - console.info(`${prefix} ${message}`, data); - } else { - console.info(`${prefix} ${message}`); - } - break; - case "debug": - if (data) { - console.debug(`${prefix} ${message}`, data); - } else { - console.debug(`${prefix} ${message}`); - } - break; - case "trace": - if (data) { - console.trace(`${prefix} ${message}`, data); - } else { - console.trace(`${prefix} ${message}`); - } - break; - } - } - // Convenience methods - error(category, message, data) { - this.log("error", category, message, data); - } - warn(category, message, data) { - this.log("warn", category, message, data); - } - info(category, message, data) { - this.log("info", category, message, data); - } - debug(category, message, data) { - this.log("debug", category, message, data); - } - trace(category, message, data) { - this.log("trace", category, message, data); - } -} -const logger = new DebugLogger(); -logger.setConfig({ - enabled: true, - level: "debug", - categories: [ - "app", - "header", - "routing", - "neo4j-context", - "auth-context", - "auth-service", - "state-management", - "local-storage", - "axios", - "system", - "navigation", - "calendar", - "presentation", - "selection", - "camera", - "binding", - "shape", - "tldraw-service", - "tldraw-events", - "signup-page", - "timetable-service", - "dev-page", - "super-admin-auth-route", - "admin-page", - "storage-service", - "user-context", - "login-form", - "super-admin-section", - "routes", - "neo4j-service", - "supabase-client", - "user-page", - "site-page", - "auth-page", - "email-signup-form", - "supabase-profile-service", - "multiplayer-page", - "snapshot-service", - "sync-service", - "slides-panel", - "local-store-service", - "shared-store-service", - "single-player-page", - "user-toolbar", - "registration-service", - "graph-service", - "graph-shape", - "calendar-shape", - "snapshot-toolbar", - "graphStateUtil", - "baseNodeShapeUtil", - "school-service", - "microphone-state-tool", - "store-service", - "morphic-page", - "not-found", - "share-handler", - "transcription-service", - "slideshow-helpers", - "slide-shape", - "graph-panel", - "cc-user-node-shape-util", - "cc-base-shape-util", - "node-canvas", - "navigation-service", - "autosave", - "cc-exam-marker", - "cc-search", - "cc-web-browser", - "neo-user-context", - "neo-institute-context", - "cc-node-snapshot-panel", - "user-neo-db", - "navigation-queue-service", - "editor-state", - "neo-shape-service", - // Add new navigation categories - "navigation-context", - "navigation-history", - "navigation-ui", - "navigation-store", - "navigation-queue", - "navigation-state", - "context-switch", - "history-management", - "node-navigation", - "navigation-panel", - "auth", - "school-context", - "database-name-service", - "tldraw", - "websocket", - "app", - "auth-service", - "storage-service", - "routing", - "auth-service", - "user-context", - "neo-user-context", - "neo-institute-context" - ] -}); - -var lib = {exports: {}}; - -var Modal$2 = {}; - -var ModalPortal = {exports: {}}; - -var focusManager = {}; - -var tabbable = {exports: {}}; - -(function (module, exports) { - - Object.defineProperty(exports, "__esModule", { - value: true - }); - exports.default = findTabbableDescendants; - /*! - * Adapted from jQuery UI core - * - * http://jqueryui.com - * - * Copyright 2014 jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - * - * http://api.jqueryui.com/category/ui-core/ - */ - - var DISPLAY_NONE = "none"; - var DISPLAY_CONTENTS = "contents"; - - // match the whole word to prevent fuzzy searching - var tabbableNode = /^(input|select|textarea|button|object|iframe)$/; - - function isNotOverflowing(element, style) { - return style.getPropertyValue("overflow") !== "visible" || - // if 'overflow: visible' set, check if there is actually any overflow - element.scrollWidth <= 0 && element.scrollHeight <= 0; - } - - function hidesContents(element) { - var zeroSize = element.offsetWidth <= 0 && element.offsetHeight <= 0; - - // If the node is empty, this is good enough - if (zeroSize && !element.innerHTML) return true; - - try { - // Otherwise we need to check some styles - var style = window.getComputedStyle(element); - var displayValue = style.getPropertyValue("display"); - return zeroSize ? displayValue !== DISPLAY_CONTENTS && isNotOverflowing(element, style) : displayValue === DISPLAY_NONE; - } catch (exception) { - // eslint-disable-next-line no-console - console.warn("Failed to inspect element style"); - return false; - } - } - - function visible(element) { - var parentElement = element; - var rootNode = element.getRootNode && element.getRootNode(); - while (parentElement) { - if (parentElement === document.body) break; - - // if we are not hidden yet, skip to checking outside the Web Component - if (rootNode && parentElement === rootNode) parentElement = rootNode.host.parentNode; - - if (hidesContents(parentElement)) return false; - parentElement = parentElement.parentNode; - } - return true; - } - - function focusable(element, isTabIndexNotNaN) { - var nodeName = element.nodeName.toLowerCase(); - var res = tabbableNode.test(nodeName) && !element.disabled || (nodeName === "a" ? element.href || isTabIndexNotNaN : isTabIndexNotNaN); - return res && visible(element); - } - - function tabbable(element) { - var tabIndex = element.getAttribute("tabindex"); - if (tabIndex === null) tabIndex = undefined; - var isTabIndexNaN = isNaN(tabIndex); - return (isTabIndexNaN || tabIndex >= 0) && focusable(element, !isTabIndexNaN); - } - - function findTabbableDescendants(element) { - var descendants = [].slice.call(element.querySelectorAll("*"), 0).reduce(function (finished, el) { - return finished.concat(!el.shadowRoot ? [el] : findTabbableDescendants(el.shadowRoot)); - }, []); - return descendants.filter(tabbable); - } - module.exports = exports["default"]; -} (tabbable, tabbable.exports)); - -var tabbableExports = tabbable.exports; - -Object.defineProperty(focusManager, "__esModule", { - value: true -}); -focusManager.resetState = resetState$4; -focusManager.log = log$4; -focusManager.handleBlur = handleBlur; -focusManager.handleFocus = handleFocus; -focusManager.markForFocusLater = markForFocusLater; -focusManager.returnFocus = returnFocus; -focusManager.popWithoutFocus = popWithoutFocus; -focusManager.setupScopedFocus = setupScopedFocus; -focusManager.teardownScopedFocus = teardownScopedFocus; -var _tabbable = tabbableExports; -var _tabbable2 = _interopRequireDefault$i(_tabbable); -function _interopRequireDefault$i(obj) { - return obj && obj.__esModule ? obj : { default: obj }; -} -var focusLaterElements = []; -var modalElement = null; -var needToFocus = false; -function resetState$4() { - focusLaterElements = []; -} -function log$4() { -} -function handleBlur() { - needToFocus = true; -} -function handleFocus() { - if (needToFocus) { - needToFocus = false; - if (!modalElement) { - return; - } - setTimeout(function() { - if (modalElement.contains(document.activeElement)) { - return; - } - var el = (0, _tabbable2.default)(modalElement)[0] || modalElement; - el.focus(); - }, 0); - } -} -function markForFocusLater() { - focusLaterElements.push(document.activeElement); -} -function returnFocus() { - var preventScroll = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : false; - var toFocus = null; - try { - if (focusLaterElements.length !== 0) { - toFocus = focusLaterElements.pop(); - toFocus.focus({ preventScroll }); - } - return; - } catch (e) { - console.warn(["You tried to return focus to", toFocus, "but it is not in the DOM anymore"].join(" ")); - } -} -function popWithoutFocus() { - focusLaterElements.length > 0 && focusLaterElements.pop(); -} -function setupScopedFocus(element) { - modalElement = element; - if (window.addEventListener) { - window.addEventListener("blur", handleBlur, false); - document.addEventListener("focus", handleFocus, true); - } else { - window.attachEvent("onBlur", handleBlur); - document.attachEvent("onFocus", handleFocus); - } -} -function teardownScopedFocus() { - modalElement = null; - if (window.addEventListener) { - window.removeEventListener("blur", handleBlur); - document.removeEventListener("focus", handleFocus); - } else { - window.detachEvent("onBlur", handleBlur); - document.detachEvent("onFocus", handleFocus); - } -} - -var scopeTab = {exports: {}}; - -(function (module, exports) { - - Object.defineProperty(exports, "__esModule", { - value: true - }); - exports.default = scopeTab; - - var _tabbable = tabbableExports; - - var _tabbable2 = _interopRequireDefault(_tabbable); - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - - function getActiveElement() { - var el = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document; - - return el.activeElement.shadowRoot ? getActiveElement(el.activeElement.shadowRoot) : el.activeElement; - } - - function scopeTab(node, event) { - var tabbable = (0, _tabbable2.default)(node); - - if (!tabbable.length) { - // Do nothing, since there are no elements that can receive focus. - event.preventDefault(); - return; - } - - var target = void 0; - - var shiftKey = event.shiftKey; - var head = tabbable[0]; - var tail = tabbable[tabbable.length - 1]; - var activeElement = getActiveElement(); - - // proceed with default browser behavior on tab. - // Focus on last element on shift + tab. - if (node === activeElement) { - if (!shiftKey) return; - target = tail; - } - - if (tail === activeElement && !shiftKey) { - target = head; - } - - if (head === activeElement && shiftKey) { - target = tail; - } - - if (target) { - event.preventDefault(); - target.focus(); - return; - } - - // Safari radio issue. - // - // Safari does not move the focus to the radio button, - // so we need to force it to really walk through all elements. - // - // This is very error prone, since we are trying to guess - // if it is a safari browser from the first occurence between - // chrome or safari. - // - // The chrome user agent contains the first ocurrence - // as the 'chrome/version' and later the 'safari/version'. - var checkSafari = /(\bChrome\b|\bSafari\b)\//.exec(navigator.userAgent); - var isSafariDesktop = checkSafari != null && checkSafari[1] != "Chrome" && /\biPod\b|\biPad\b/g.exec(navigator.userAgent) == null; - - // If we are not in safari desktop, let the browser control - // the focus - if (!isSafariDesktop) return; - - var x = tabbable.indexOf(activeElement); - - if (x > -1) { - x += shiftKey ? -1 : 1; - } - - target = tabbable[x]; - - // If the tabbable element does not exist, - // focus head/tail based on shiftKey - if (typeof target === "undefined") { - event.preventDefault(); - target = shiftKey ? tail : head; - target.focus(); - return; - } - - event.preventDefault(); - - target.focus(); - } - module.exports = exports["default"]; -} (scopeTab, scopeTab.exports)); - -var scopeTabExports = scopeTab.exports; - -var ariaAppHider$1 = {}; - -var warning = function() { -}; -var warning_1 = warning; - -var safeHTMLElement = {}; - -var exenv = {exports: {}}; - -/*! - Copyright (c) 2015 Jed Watson. - Based on code that is Copyright 2013-2015, Facebook, Inc. - All rights reserved. -*/ - -(function (module) { - /* global define */ - - (function () { - - var canUseDOM = !!( - typeof window !== 'undefined' && - window.document && - window.document.createElement - ); - - var ExecutionEnvironment = { - - canUseDOM: canUseDOM, - - canUseWorkers: typeof Worker !== 'undefined', - - canUseEventListeners: - canUseDOM && !!(window.addEventListener || window.attachEvent), - - canUseViewport: canUseDOM && !!window.screen - - }; - - if (module.exports) { - module.exports = ExecutionEnvironment; - } else { - window.ExecutionEnvironment = ExecutionEnvironment; - } - - }()); -} (exenv)); - -var exenvExports = exenv.exports; - -Object.defineProperty(safeHTMLElement, "__esModule", { - value: true -}); -safeHTMLElement.canUseDOM = safeHTMLElement.SafeNodeList = safeHTMLElement.SafeHTMLCollection = undefined; - -var _exenv = exenvExports; - -var _exenv2 = _interopRequireDefault$h(_exenv); - -function _interopRequireDefault$h(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var EE = _exenv2.default; - -var SafeHTMLElement = EE.canUseDOM ? window.HTMLElement : {}; - -safeHTMLElement.SafeHTMLCollection = EE.canUseDOM ? window.HTMLCollection : {}; - -safeHTMLElement.SafeNodeList = EE.canUseDOM ? window.NodeList : {}; - -safeHTMLElement.canUseDOM = EE.canUseDOM; - -safeHTMLElement.default = SafeHTMLElement; - -Object.defineProperty(ariaAppHider$1, "__esModule", { - value: true -}); -ariaAppHider$1.resetState = resetState$3; -ariaAppHider$1.log = log$3; -ariaAppHider$1.assertNodeList = assertNodeList; -ariaAppHider$1.setElement = setElement; -ariaAppHider$1.validateElement = validateElement; -ariaAppHider$1.hide = hide; -ariaAppHider$1.show = show; -ariaAppHider$1.documentNotReadyOrSSRTesting = documentNotReadyOrSSRTesting; -var _warning = warning_1; -var _warning2 = _interopRequireDefault$g(_warning); -var _safeHTMLElement$1 = safeHTMLElement; -function _interopRequireDefault$g(obj) { - return obj && obj.__esModule ? obj : { default: obj }; -} -var globalElement = null; -function resetState$3() { - if (globalElement) { - if (globalElement.removeAttribute) { - globalElement.removeAttribute("aria-hidden"); - } else if (globalElement.length != null) { - globalElement.forEach(function(element) { - return element.removeAttribute("aria-hidden"); - }); - } else { - document.querySelectorAll(globalElement).forEach(function(element) { - return element.removeAttribute("aria-hidden"); - }); - } - } - globalElement = null; -} -function log$3() { -} -function assertNodeList(nodeList, selector) { - if (!nodeList || !nodeList.length) { - throw new Error("react-modal: No elements were found for selector " + selector + "."); - } -} -function setElement(element) { - var useElement = element; - if (typeof useElement === "string" && _safeHTMLElement$1.canUseDOM) { - var el = document.querySelectorAll(useElement); - assertNodeList(el, useElement); - useElement = el; - } - globalElement = useElement || globalElement; - return globalElement; -} -function validateElement(appElement) { - var el = appElement || globalElement; - if (el) { - return Array.isArray(el) || el instanceof HTMLCollection || el instanceof NodeList ? el : [el]; - } else { - (0, _warning2.default)(false, ["react-modal: App element is not defined.", "Please use `Modal.setAppElement(el)` or set `appElement={el}`.", "This is needed so screen readers don't see main content", "when modal is opened. It is not recommended, but you can opt-out", "by setting `ariaHideApp={false}`."].join(" ")); - return []; - } -} -function hide(appElement) { - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = void 0; - try { - for (var _iterator = validateElement(appElement)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var el = _step.value; - el.setAttribute("aria-hidden", "true"); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } -} -function show(appElement) { - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = void 0; - try { - for (var _iterator2 = validateElement(appElement)[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var el = _step2.value; - el.removeAttribute("aria-hidden"); - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } - } -} -function documentNotReadyOrSSRTesting() { - globalElement = null; -} - -var classList$1 = {}; - -Object.defineProperty(classList$1, "__esModule", { - value: true -}); -classList$1.resetState = resetState$2; -classList$1.log = log$2; -var htmlClassList = {}; -var docBodyClassList = {}; -function removeClass(at, cls) { - at.classList.remove(cls); -} -function resetState$2() { - var htmlElement = document.getElementsByTagName("html")[0]; - for (var cls in htmlClassList) { - removeClass(htmlElement, htmlClassList[cls]); - } - var body = document.body; - for (var _cls in docBodyClassList) { - removeClass(body, docBodyClassList[_cls]); - } - htmlClassList = {}; - docBodyClassList = {}; -} -function log$2() { -} -var incrementReference = function incrementReference2(poll, className) { - if (!poll[className]) { - poll[className] = 0; - } - poll[className] += 1; - return className; -}; -var decrementReference = function decrementReference2(poll, className) { - if (poll[className]) { - poll[className] -= 1; - } - return className; -}; -var trackClass = function trackClass2(classListRef, poll, classes) { - classes.forEach(function(className) { - incrementReference(poll, className); - classListRef.add(className); - }); -}; -var untrackClass = function untrackClass2(classListRef, poll, classes) { - classes.forEach(function(className) { - decrementReference(poll, className); - poll[className] === 0 && classListRef.remove(className); - }); -}; -classList$1.add = function add2(element, classString) { - return trackClass(element.classList, element.nodeName.toLowerCase() == "html" ? htmlClassList : docBodyClassList, classString.split(" ")); -}; -classList$1.remove = function remove2(element, classString) { - return untrackClass(element.classList, element.nodeName.toLowerCase() == "html" ? htmlClassList : docBodyClassList, classString.split(" ")); -}; - -var portalOpenInstances$1 = {}; - -Object.defineProperty(portalOpenInstances$1, "__esModule", { - value: true -}); -portalOpenInstances$1.log = log$1; -portalOpenInstances$1.resetState = resetState$1; -function _classCallCheck$1(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } -} -var PortalOpenInstances = function PortalOpenInstances2() { - var _this = this; - _classCallCheck$1(this, PortalOpenInstances2); - this.register = function(openInstance) { - if (_this.openInstances.indexOf(openInstance) !== -1) { - return; - } - _this.openInstances.push(openInstance); - _this.emit("register"); - }; - this.deregister = function(openInstance) { - var index = _this.openInstances.indexOf(openInstance); - if (index === -1) { - return; - } - _this.openInstances.splice(index, 1); - _this.emit("deregister"); - }; - this.subscribe = function(callback) { - _this.subscribers.push(callback); - }; - this.emit = function(eventType) { - _this.subscribers.forEach(function(subscriber) { - return subscriber( - eventType, - // shallow copy to avoid accidental mutation - _this.openInstances.slice() - ); - }); - }; - this.openInstances = []; - this.subscribers = []; -}; -var portalOpenInstances = new PortalOpenInstances(); -function log$1() { - console.log("portalOpenInstances ----------"); - console.log(portalOpenInstances.openInstances.length); - portalOpenInstances.openInstances.forEach(function(p) { - return console.log(p); - }); - console.log("end portalOpenInstances ----------"); -} -function resetState$1() { - portalOpenInstances = new PortalOpenInstances(); -} -portalOpenInstances$1.default = portalOpenInstances; - -var bodyTrap$1 = {}; - -Object.defineProperty(bodyTrap$1, "__esModule", { - value: true -}); -bodyTrap$1.resetState = resetState; -bodyTrap$1.log = log; -var _portalOpenInstances = portalOpenInstances$1; -var _portalOpenInstances2 = _interopRequireDefault$f(_portalOpenInstances); -function _interopRequireDefault$f(obj) { - return obj && obj.__esModule ? obj : { default: obj }; -} -var before = void 0, after = void 0, instances = []; -function resetState() { - var _arr = [before, after]; - for (var _i = 0; _i < _arr.length; _i++) { - var item = _arr[_i]; - if (!item) continue; - item.parentNode && item.parentNode.removeChild(item); - } - before = after = null; - instances = []; -} -function log() { - console.log("bodyTrap ----------"); - console.log(instances.length); - var _arr2 = [before, after]; - for (var _i2 = 0; _i2 < _arr2.length; _i2++) { - var item = _arr2[_i2]; - var check = item || {}; - console.log(check.nodeName, check.className, check.id); - } - console.log("edn bodyTrap ----------"); -} -function focusContent() { - if (instances.length === 0) { - return; - } - instances[instances.length - 1].focusContent(); -} -function bodyTrap(eventType, openInstances) { - if (!before && !after) { - before = document.createElement("div"); - before.setAttribute("data-react-modal-body-trap", ""); - before.style.position = "absolute"; - before.style.opacity = "0"; - before.setAttribute("tabindex", "0"); - before.addEventListener("focus", focusContent); - after = before.cloneNode(); - after.addEventListener("focus", focusContent); - } - instances = openInstances; - if (instances.length > 0) { - if (document.body.firstChild !== before) { - document.body.insertBefore(before, document.body.firstChild); - } - if (document.body.lastChild !== after) { - document.body.appendChild(after); - } - } else { - if (before.parentElement) { - before.parentElement.removeChild(before); - } - if (after.parentElement) { - after.parentElement.removeChild(after); - } - } -} -_portalOpenInstances2.default.subscribe(bodyTrap); - -(function (module, exports) { - Object.defineProperty(exports, "__esModule", { - value: true - }); - var _extends = Object.assign || function(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - return target; - }; - var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function(obj) { - return typeof obj; - } : function(obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; - }; - var _createClass = /* @__PURE__ */ function() { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - return function(Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; - }(); - var _react = reactExports; - var _propTypes = propTypesExports; - var _propTypes2 = _interopRequireDefault(_propTypes); - var _focusManager = focusManager; - var focusManager$1 = _interopRequireWildcard(_focusManager); - var _scopeTab = scopeTabExports; - var _scopeTab2 = _interopRequireDefault(_scopeTab); - var _ariaAppHider = ariaAppHider$1; - var ariaAppHider = _interopRequireWildcard(_ariaAppHider); - var _classList = classList$1; - var classList = _interopRequireWildcard(_classList); - var _safeHTMLElement = safeHTMLElement; - var _safeHTMLElement2 = _interopRequireDefault(_safeHTMLElement); - var _portalOpenInstances = portalOpenInstances$1; - var _portalOpenInstances2 = _interopRequireDefault(_portalOpenInstances); - - function _interopRequireWildcard(obj) { - if (obj && obj.__esModule) { - return obj; - } else { - var newObj = {}; - if (obj != null) { - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; - } - } - newObj.default = obj; - return newObj; - } - } - function _interopRequireDefault(obj) { - return obj && obj.__esModule ? obj : { default: obj }; - } - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - } - function _possibleConstructorReturn(self, call) { - if (!self) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - } - return call && (typeof call === "object" || typeof call === "function") ? call : self; - } - function _inherits(subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); - } - subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); - if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; - } - var CLASS_NAMES = { - overlay: "ReactModal__Overlay", - content: "ReactModal__Content" - }; - var isTabKey = function isTabKey2(event) { - return event.code === "Tab" || event.keyCode === 9; - }; - var isEscKey = function isEscKey2(event) { - return event.code === "Escape" || event.keyCode === 27; - }; - var ariaHiddenInstances = 0; - var ModalPortal = function(_Component) { - _inherits(ModalPortal2, _Component); - function ModalPortal2(props) { - _classCallCheck(this, ModalPortal2); - var _this = _possibleConstructorReturn(this, (ModalPortal2.__proto__ || Object.getPrototypeOf(ModalPortal2)).call(this, props)); - _this.setOverlayRef = function(overlay) { - _this.overlay = overlay; - _this.props.overlayRef && _this.props.overlayRef(overlay); - }; - _this.setContentRef = function(content) { - _this.content = content; - _this.props.contentRef && _this.props.contentRef(content); - }; - _this.afterClose = function() { - var _this$props = _this.props, appElement = _this$props.appElement, ariaHideApp = _this$props.ariaHideApp, htmlOpenClassName = _this$props.htmlOpenClassName, bodyOpenClassName = _this$props.bodyOpenClassName, parentSelector = _this$props.parentSelector; - var parentDocument = parentSelector && parentSelector().ownerDocument || document; - bodyOpenClassName && classList.remove(parentDocument.body, bodyOpenClassName); - htmlOpenClassName && classList.remove(parentDocument.getElementsByTagName("html")[0], htmlOpenClassName); - if (ariaHideApp && ariaHiddenInstances > 0) { - ariaHiddenInstances -= 1; - if (ariaHiddenInstances === 0) { - ariaAppHider.show(appElement); - } - } - if (_this.props.shouldFocusAfterRender) { - if (_this.props.shouldReturnFocusAfterClose) { - focusManager$1.returnFocus(_this.props.preventScroll); - focusManager$1.teardownScopedFocus(); - } else { - focusManager$1.popWithoutFocus(); - } - } - if (_this.props.onAfterClose) { - _this.props.onAfterClose(); - } - _portalOpenInstances2.default.deregister(_this); - }; - _this.open = function() { - _this.beforeOpen(); - if (_this.state.afterOpen && _this.state.beforeClose) { - clearTimeout(_this.closeTimer); - _this.setState({ beforeClose: false }); - } else { - if (_this.props.shouldFocusAfterRender) { - focusManager$1.setupScopedFocus(_this.node); - focusManager$1.markForFocusLater(); - } - _this.setState({ isOpen: true }, function() { - _this.openAnimationFrame = requestAnimationFrame(function() { - _this.setState({ afterOpen: true }); - if (_this.props.isOpen && _this.props.onAfterOpen) { - _this.props.onAfterOpen({ - overlayEl: _this.overlay, - contentEl: _this.content - }); - } - }); - }); - } - }; - _this.close = function() { - if (_this.props.closeTimeoutMS > 0) { - _this.closeWithTimeout(); - } else { - _this.closeWithoutTimeout(); - } - }; - _this.focusContent = function() { - return _this.content && !_this.contentHasFocus() && _this.content.focus({ preventScroll: true }); - }; - _this.closeWithTimeout = function() { - var closesAt = Date.now() + _this.props.closeTimeoutMS; - _this.setState({ beforeClose: true, closesAt }, function() { - _this.closeTimer = setTimeout(_this.closeWithoutTimeout, _this.state.closesAt - Date.now()); - }); - }; - _this.closeWithoutTimeout = function() { - _this.setState({ - beforeClose: false, - isOpen: false, - afterOpen: false, - closesAt: null - }, _this.afterClose); - }; - _this.handleKeyDown = function(event) { - if (isTabKey(event)) { - (0, _scopeTab2.default)(_this.content, event); - } - if (_this.props.shouldCloseOnEsc && isEscKey(event)) { - event.stopPropagation(); - _this.requestClose(event); - } - }; - _this.handleOverlayOnClick = function(event) { - if (_this.shouldClose === null) { - _this.shouldClose = true; - } - if (_this.shouldClose && _this.props.shouldCloseOnOverlayClick) { - if (_this.ownerHandlesClose()) { - _this.requestClose(event); - } else { - _this.focusContent(); - } - } - _this.shouldClose = null; - }; - _this.handleContentOnMouseUp = function() { - _this.shouldClose = false; - }; - _this.handleOverlayOnMouseDown = function(event) { - if (!_this.props.shouldCloseOnOverlayClick && event.target == _this.overlay) { - event.preventDefault(); - } - }; - _this.handleContentOnClick = function() { - _this.shouldClose = false; - }; - _this.handleContentOnMouseDown = function() { - _this.shouldClose = false; - }; - _this.requestClose = function(event) { - return _this.ownerHandlesClose() && _this.props.onRequestClose(event); - }; - _this.ownerHandlesClose = function() { - return _this.props.onRequestClose; - }; - _this.shouldBeClosed = function() { - return !_this.state.isOpen && !_this.state.beforeClose; - }; - _this.contentHasFocus = function() { - return document.activeElement === _this.content || _this.content.contains(document.activeElement); - }; - _this.buildClassName = function(which, additional) { - var classNames = (typeof additional === "undefined" ? "undefined" : _typeof(additional)) === "object" ? additional : { - base: CLASS_NAMES[which], - afterOpen: CLASS_NAMES[which] + "--after-open", - beforeClose: CLASS_NAMES[which] + "--before-close" - }; - var className = classNames.base; - if (_this.state.afterOpen) { - className = className + " " + classNames.afterOpen; - } - if (_this.state.beforeClose) { - className = className + " " + classNames.beforeClose; - } - return typeof additional === "string" && additional ? className + " " + additional : className; - }; - _this.attributesFromObject = function(prefix, items) { - return Object.keys(items).reduce(function(acc, name) { - acc[prefix + "-" + name] = items[name]; - return acc; - }, {}); - }; - _this.state = { - afterOpen: false, - beforeClose: false - }; - _this.shouldClose = null; - _this.moveFromContentToOverlay = null; - return _this; - } - _createClass(ModalPortal2, [{ - key: "componentDidMount", - value: function componentDidMount() { - if (this.props.isOpen) { - this.open(); - } - } - }, { - key: "componentDidUpdate", - value: function componentDidUpdate(prevProps, prevState) { - if (this.props.isOpen && !prevProps.isOpen) { - this.open(); - } else if (!this.props.isOpen && prevProps.isOpen) { - this.close(); - } - if (this.props.shouldFocusAfterRender && this.state.isOpen && !prevState.isOpen) { - this.focusContent(); - } - } - }, { - key: "componentWillUnmount", - value: function componentWillUnmount() { - if (this.state.isOpen) { - this.afterClose(); - } - clearTimeout(this.closeTimer); - cancelAnimationFrame(this.openAnimationFrame); - } - }, { - key: "beforeOpen", - value: function beforeOpen() { - var _props = this.props, appElement = _props.appElement, ariaHideApp = _props.ariaHideApp, htmlOpenClassName = _props.htmlOpenClassName, bodyOpenClassName = _props.bodyOpenClassName, parentSelector = _props.parentSelector; - var parentDocument = parentSelector && parentSelector().ownerDocument || document; - bodyOpenClassName && classList.add(parentDocument.body, bodyOpenClassName); - htmlOpenClassName && classList.add(parentDocument.getElementsByTagName("html")[0], htmlOpenClassName); - if (ariaHideApp) { - ariaHiddenInstances += 1; - ariaAppHider.hide(appElement); - } - _portalOpenInstances2.default.register(this); - } - // Don't steal focus from inner elements - }, { - key: "render", - value: function render() { - var _props2 = this.props, id = _props2.id, className = _props2.className, overlayClassName = _props2.overlayClassName, defaultStyles = _props2.defaultStyles, children = _props2.children; - var contentStyles = className ? {} : defaultStyles.content; - var overlayStyles = overlayClassName ? {} : defaultStyles.overlay; - if (this.shouldBeClosed()) { - return null; - } - var overlayProps = { - ref: this.setOverlayRef, - className: this.buildClassName("overlay", overlayClassName), - style: _extends({}, overlayStyles, this.props.style.overlay), - onClick: this.handleOverlayOnClick, - onMouseDown: this.handleOverlayOnMouseDown - }; - var contentProps = _extends({ - id, - ref: this.setContentRef, - style: _extends({}, contentStyles, this.props.style.content), - className: this.buildClassName("content", className), - tabIndex: "-1", - onKeyDown: this.handleKeyDown, - onMouseDown: this.handleContentOnMouseDown, - onMouseUp: this.handleContentOnMouseUp, - onClick: this.handleContentOnClick, - role: this.props.role, - "aria-label": this.props.contentLabel - }, this.attributesFromObject("aria", _extends({ modal: true }, this.props.aria)), this.attributesFromObject("data", this.props.data || {}), { - "data-testid": this.props.testId - }); - var contentElement = this.props.contentElement(contentProps, children); - return this.props.overlayElement(overlayProps, contentElement); - } - }]); - return ModalPortal2; - }(_react.Component); - ModalPortal.defaultProps = { - style: { - overlay: {}, - content: {} - }, - defaultStyles: {} - }; - ModalPortal.propTypes = { - isOpen: _propTypes2.default.bool.isRequired, - defaultStyles: _propTypes2.default.shape({ - content: _propTypes2.default.object, - overlay: _propTypes2.default.object - }), - style: _propTypes2.default.shape({ - content: _propTypes2.default.object, - overlay: _propTypes2.default.object - }), - className: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.object]), - overlayClassName: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.object]), - parentSelector: _propTypes2.default.func, - bodyOpenClassName: _propTypes2.default.string, - htmlOpenClassName: _propTypes2.default.string, - ariaHideApp: _propTypes2.default.bool, - appElement: _propTypes2.default.oneOfType([_propTypes2.default.instanceOf(_safeHTMLElement2.default), _propTypes2.default.instanceOf(_safeHTMLElement.SafeHTMLCollection), _propTypes2.default.instanceOf(_safeHTMLElement.SafeNodeList), _propTypes2.default.arrayOf(_propTypes2.default.instanceOf(_safeHTMLElement2.default))]), - onAfterOpen: _propTypes2.default.func, - onAfterClose: _propTypes2.default.func, - onRequestClose: _propTypes2.default.func, - closeTimeoutMS: _propTypes2.default.number, - shouldFocusAfterRender: _propTypes2.default.bool, - shouldCloseOnOverlayClick: _propTypes2.default.bool, - shouldReturnFocusAfterClose: _propTypes2.default.bool, - preventScroll: _propTypes2.default.bool, - role: _propTypes2.default.string, - contentLabel: _propTypes2.default.string, - aria: _propTypes2.default.object, - data: _propTypes2.default.object, - children: _propTypes2.default.node, - shouldCloseOnEsc: _propTypes2.default.bool, - overlayRef: _propTypes2.default.func, - contentRef: _propTypes2.default.func, - id: _propTypes2.default.string, - overlayElement: _propTypes2.default.func, - contentElement: _propTypes2.default.func, - testId: _propTypes2.default.string - }; - exports.default = ModalPortal; - module.exports = exports["default"]; -} (ModalPortal, ModalPortal.exports)); - -var ModalPortalExports = ModalPortal.exports; - -/** - * Copyright (c) 2013-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -function componentWillMount() { - // Call this.constructor.gDSFP to support sub-classes. - var state = this.constructor.getDerivedStateFromProps(this.props, this.state); - if (state !== null && state !== undefined) { - this.setState(state); - } -} - -function componentWillReceiveProps(nextProps) { - // Call this.constructor.gDSFP to support sub-classes. - // Use the setState() updater to ensure state isn't stale in certain edge cases. - function updater(prevState) { - var state = this.constructor.getDerivedStateFromProps(nextProps, prevState); - return state !== null && state !== undefined ? state : null; - } - // Binding "this" is important for shallow renderer support. - this.setState(updater.bind(this)); -} - -function componentWillUpdate(nextProps, nextState) { - try { - var prevProps = this.props; - var prevState = this.state; - this.props = nextProps; - this.state = nextState; - this.__reactInternalSnapshotFlag = true; - this.__reactInternalSnapshot = this.getSnapshotBeforeUpdate( - prevProps, - prevState - ); - } finally { - this.props = prevProps; - this.state = prevState; - } -} - -// React may warn about cWM/cWRP/cWU methods being deprecated. -// Add a flag to suppress these warnings for this special case. -componentWillMount.__suppressDeprecationWarning = true; -componentWillReceiveProps.__suppressDeprecationWarning = true; -componentWillUpdate.__suppressDeprecationWarning = true; - -function polyfill(Component) { - var prototype = Component.prototype; - - if (!prototype || !prototype.isReactComponent) { - throw new Error('Can only polyfill class components'); - } - - if ( - typeof Component.getDerivedStateFromProps !== 'function' && - typeof prototype.getSnapshotBeforeUpdate !== 'function' - ) { - return Component; - } - - // If new component APIs are defined, "unsafe" lifecycles won't be called. - // Error if any of these lifecycles are present, - // Because they would work differently between older and newer (16.3+) versions of React. - var foundWillMountName = null; - var foundWillReceivePropsName = null; - var foundWillUpdateName = null; - if (typeof prototype.componentWillMount === 'function') { - foundWillMountName = 'componentWillMount'; - } else if (typeof prototype.UNSAFE_componentWillMount === 'function') { - foundWillMountName = 'UNSAFE_componentWillMount'; - } - if (typeof prototype.componentWillReceiveProps === 'function') { - foundWillReceivePropsName = 'componentWillReceiveProps'; - } else if (typeof prototype.UNSAFE_componentWillReceiveProps === 'function') { - foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps'; - } - if (typeof prototype.componentWillUpdate === 'function') { - foundWillUpdateName = 'componentWillUpdate'; - } else if (typeof prototype.UNSAFE_componentWillUpdate === 'function') { - foundWillUpdateName = 'UNSAFE_componentWillUpdate'; - } - if ( - foundWillMountName !== null || - foundWillReceivePropsName !== null || - foundWillUpdateName !== null - ) { - var componentName = Component.displayName || Component.name; - var newApiName = - typeof Component.getDerivedStateFromProps === 'function' - ? 'getDerivedStateFromProps()' - : 'getSnapshotBeforeUpdate()'; - - throw Error( - 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + - componentName + - ' uses ' + - newApiName + - ' but also contains the following legacy lifecycles:' + - (foundWillMountName !== null ? '\n ' + foundWillMountName : '') + - (foundWillReceivePropsName !== null - ? '\n ' + foundWillReceivePropsName - : '') + - (foundWillUpdateName !== null ? '\n ' + foundWillUpdateName : '') + - '\n\nThe above lifecycles should be removed. Learn more about this warning here:\n' + - 'https://fb.me/react-async-component-lifecycle-hooks' - ); - } - - // React <= 16.2 does not support static getDerivedStateFromProps. - // As a workaround, use cWM and cWRP to invoke the new static lifecycle. - // Newer versions of React will ignore these lifecycles if gDSFP exists. - if (typeof Component.getDerivedStateFromProps === 'function') { - prototype.componentWillMount = componentWillMount; - prototype.componentWillReceiveProps = componentWillReceiveProps; - } - - // React <= 16.2 does not support getSnapshotBeforeUpdate. - // As a workaround, use cWU to invoke the new lifecycle. - // Newer versions of React will ignore that lifecycle if gSBU exists. - if (typeof prototype.getSnapshotBeforeUpdate === 'function') { - if (typeof prototype.componentDidUpdate !== 'function') { - throw new Error( - 'Cannot polyfill getSnapshotBeforeUpdate() for components that do not define componentDidUpdate() on the prototype' - ); - } - - prototype.componentWillUpdate = componentWillUpdate; - - var componentDidUpdate = prototype.componentDidUpdate; - - prototype.componentDidUpdate = function componentDidUpdatePolyfill( - prevProps, - prevState, - maybeSnapshot - ) { - // 16.3+ will not execute our will-update method; - // It will pass a snapshot value to did-update though. - // Older versions will require our polyfilled will-update value. - // We need to handle both cases, but can't just check for the presence of "maybeSnapshot", - // Because for <= 15.x versions this might be a "prevContext" object. - // We also can't just check "__reactInternalSnapshot", - // Because get-snapshot might return a falsy value. - // So check for the explicit __reactInternalSnapshotFlag flag to determine behavior. - var snapshot = this.__reactInternalSnapshotFlag - ? this.__reactInternalSnapshot - : maybeSnapshot; - - componentDidUpdate.call(this, prevProps, prevState, snapshot); - }; - } - - return Component; -} - -const reactLifecyclesCompat_es = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({ - __proto__: null, - polyfill -}, Symbol.toStringTag, { value: 'Module' })); - -const require$$6 = /*@__PURE__*/getAugmentedNamespace(reactLifecyclesCompat_es); - -Object.defineProperty(Modal$2, "__esModule", { - value: true -}); -Modal$2.bodyOpenClassName = Modal$2.portalClassName = void 0; -var _extends$1 = Object.assign || function(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - return target; -}; -var _createClass = /* @__PURE__ */ function() { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - return function(Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; -}(); -var _react = reactExports; -var _react2 = _interopRequireDefault$e(_react); -var _reactDom = reactDomExports; -var _reactDom2 = _interopRequireDefault$e(_reactDom); -var _propTypes = propTypesExports; -var _propTypes2 = _interopRequireDefault$e(_propTypes); -var _ModalPortal = ModalPortalExports; -var _ModalPortal2 = _interopRequireDefault$e(_ModalPortal); -var _ariaAppHider = ariaAppHider$1; -var ariaAppHider = _interopRequireWildcard$1(_ariaAppHider); -var _safeHTMLElement = safeHTMLElement; -var _safeHTMLElement2 = _interopRequireDefault$e(_safeHTMLElement); -var _reactLifecyclesCompat = require$$6; -function _interopRequireWildcard$1(obj) { - if (obj && obj.__esModule) { - return obj; - } else { - var newObj = {}; - if (obj != null) { - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; - } - } - newObj.default = obj; - return newObj; - } -} -function _interopRequireDefault$e(obj) { - return obj && obj.__esModule ? obj : { default: obj }; -} -function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } -} -function _possibleConstructorReturn(self, call) { - if (!self) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - } - return call && (typeof call === "object" || typeof call === "function") ? call : self; -} -function _inherits(subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); - } - subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); - if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; -} -var portalClassName = Modal$2.portalClassName = "ReactModalPortal"; -var bodyOpenClassName = Modal$2.bodyOpenClassName = "ReactModal__Body--open"; -var isReact16 = _safeHTMLElement.canUseDOM && _reactDom2.default.createPortal !== void 0; -var createHTMLElement = function createHTMLElement2(name) { - return document.createElement(name); -}; -var getCreatePortal = function getCreatePortal2() { - return isReact16 ? _reactDom2.default.createPortal : _reactDom2.default.unstable_renderSubtreeIntoContainer; -}; -function getParentElement(parentSelector2) { - return parentSelector2(); -} -var Modal$1 = function(_Component) { - _inherits(Modal2, _Component); - function Modal2() { - var _ref; - var _temp, _this, _ret; - _classCallCheck(this, Modal2); - for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; - } - return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Modal2.__proto__ || Object.getPrototypeOf(Modal2)).call.apply(_ref, [this].concat(args))), _this), _this.removePortal = function() { - !isReact16 && _reactDom2.default.unmountComponentAtNode(_this.node); - var parent = getParentElement(_this.props.parentSelector); - if (parent && parent.contains(_this.node)) { - parent.removeChild(_this.node); - } else { - console.warn('React-Modal: "parentSelector" prop did not returned any DOM element. Make sure that the parent element is unmounted to avoid any memory leaks.'); - } - }, _this.portalRef = function(ref) { - _this.portal = ref; - }, _this.renderPortal = function(props) { - var createPortal = getCreatePortal(); - var portal = createPortal(_this, _react2.default.createElement(_ModalPortal2.default, _extends$1({ defaultStyles: Modal2.defaultStyles }, props)), _this.node); - _this.portalRef(portal); - }, _temp), _possibleConstructorReturn(_this, _ret); - } - _createClass(Modal2, [{ - key: "componentDidMount", - value: function componentDidMount() { - if (!_safeHTMLElement.canUseDOM) return; - if (!isReact16) { - this.node = createHTMLElement("div"); - } - this.node.className = this.props.portalClassName; - var parent = getParentElement(this.props.parentSelector); - parent.appendChild(this.node); - !isReact16 && this.renderPortal(this.props); - } - }, { - key: "getSnapshotBeforeUpdate", - value: function getSnapshotBeforeUpdate(prevProps) { - var prevParent = getParentElement(prevProps.parentSelector); - var nextParent = getParentElement(this.props.parentSelector); - return { prevParent, nextParent }; - } - }, { - key: "componentDidUpdate", - value: function componentDidUpdate(prevProps, _, snapshot) { - if (!_safeHTMLElement.canUseDOM) return; - var _props = this.props, isOpen = _props.isOpen, portalClassName2 = _props.portalClassName; - if (prevProps.portalClassName !== portalClassName2) { - this.node.className = portalClassName2; - } - var prevParent = snapshot.prevParent, nextParent = snapshot.nextParent; - if (nextParent !== prevParent) { - prevParent.removeChild(this.node); - nextParent.appendChild(this.node); - } - if (!prevProps.isOpen && !isOpen) return; - !isReact16 && this.renderPortal(this.props); - } - }, { - key: "componentWillUnmount", - value: function componentWillUnmount() { - if (!_safeHTMLElement.canUseDOM || !this.node || !this.portal) return; - var state = this.portal.state; - var now = Date.now(); - var closesAt = state.isOpen && this.props.closeTimeoutMS && (state.closesAt || now + this.props.closeTimeoutMS); - if (closesAt) { - if (!state.beforeClose) { - this.portal.closeWithTimeout(); - } - setTimeout(this.removePortal, closesAt - now); - } else { - this.removePortal(); - } - } - }, { - key: "render", - value: function render() { - if (!_safeHTMLElement.canUseDOM || !isReact16) { - return null; - } - if (!this.node && isReact16) { - this.node = createHTMLElement("div"); - } - var createPortal = getCreatePortal(); - return createPortal(_react2.default.createElement(_ModalPortal2.default, _extends$1({ - ref: this.portalRef, - defaultStyles: Modal2.defaultStyles - }, this.props)), this.node); - } - }], [{ - key: "setAppElement", - value: function setAppElement(element) { - ariaAppHider.setElement(element); - } - /* eslint-disable react/no-unused-prop-types */ - /* eslint-enable react/no-unused-prop-types */ - }]); - return Modal2; -}(_react.Component); -Modal$1.propTypes = { - isOpen: _propTypes2.default.bool.isRequired, - style: _propTypes2.default.shape({ - content: _propTypes2.default.object, - overlay: _propTypes2.default.object - }), - portalClassName: _propTypes2.default.string, - bodyOpenClassName: _propTypes2.default.string, - htmlOpenClassName: _propTypes2.default.string, - className: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.shape({ - base: _propTypes2.default.string.isRequired, - afterOpen: _propTypes2.default.string.isRequired, - beforeClose: _propTypes2.default.string.isRequired - })]), - overlayClassName: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.shape({ - base: _propTypes2.default.string.isRequired, - afterOpen: _propTypes2.default.string.isRequired, - beforeClose: _propTypes2.default.string.isRequired - })]), - appElement: _propTypes2.default.oneOfType([_propTypes2.default.instanceOf(_safeHTMLElement2.default), _propTypes2.default.instanceOf(_safeHTMLElement.SafeHTMLCollection), _propTypes2.default.instanceOf(_safeHTMLElement.SafeNodeList), _propTypes2.default.arrayOf(_propTypes2.default.instanceOf(_safeHTMLElement2.default))]), - onAfterOpen: _propTypes2.default.func, - onRequestClose: _propTypes2.default.func, - closeTimeoutMS: _propTypes2.default.number, - ariaHideApp: _propTypes2.default.bool, - shouldFocusAfterRender: _propTypes2.default.bool, - shouldCloseOnOverlayClick: _propTypes2.default.bool, - shouldReturnFocusAfterClose: _propTypes2.default.bool, - preventScroll: _propTypes2.default.bool, - parentSelector: _propTypes2.default.func, - aria: _propTypes2.default.object, - data: _propTypes2.default.object, - role: _propTypes2.default.string, - contentLabel: _propTypes2.default.string, - shouldCloseOnEsc: _propTypes2.default.bool, - overlayRef: _propTypes2.default.func, - contentRef: _propTypes2.default.func, - id: _propTypes2.default.string, - overlayElement: _propTypes2.default.func, - contentElement: _propTypes2.default.func -}; -Modal$1.defaultProps = { - isOpen: false, - portalClassName, - bodyOpenClassName, - role: "dialog", - ariaHideApp: true, - closeTimeoutMS: 0, - shouldFocusAfterRender: true, - shouldCloseOnEsc: true, - shouldCloseOnOverlayClick: true, - shouldReturnFocusAfterClose: true, - preventScroll: false, - parentSelector: function parentSelector() { - return document.body; - }, - overlayElement: function overlayElement(props, contentEl) { - return _react2.default.createElement( - "div", - props, - contentEl - ); - }, - contentElement: function contentElement(props, children) { - return _react2.default.createElement( - "div", - props, - children - ); - } -}; -Modal$1.defaultStyles = { - overlay: { - position: "fixed", - top: 0, - left: 0, - right: 0, - bottom: 0, - backgroundColor: "rgba(255, 255, 255, 0.75)" - }, - content: { - position: "absolute", - top: "40px", - left: "40px", - right: "40px", - bottom: "40px", - border: "1px solid #ccc", - background: "#fff", - overflow: "auto", - WebkitOverflowScrolling: "touch", - borderRadius: "4px", - outline: "none", - padding: "20px" - } -}; -(0, _reactLifecyclesCompat.polyfill)(Modal$1); -Modal$2.default = Modal$1; - -(function (module, exports) { - - Object.defineProperty(exports, "__esModule", { - value: true - }); - - var _Modal = Modal$2; - - var _Modal2 = _interopRequireDefault(_Modal); - - function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - - exports.default = _Modal2.default; - module.exports = exports["default"]; -} (lib, lib.exports)); - -var libExports = lib.exports; -const Modal = /*@__PURE__*/getDefaultExportFromCjs(libExports); - -let isInitialized = false; -const initializeApp = () => { - if (isInitialized) { - return; - } - logger.debug("app", "🚀 App initializing", { - isDevMode: true === "true", - environment: "production" - }); - Modal.setAppElement("#root"); - isInitialized = true; -}; - -const themeOptions = { - palette: { - primary: { - main: "#1976d2", - light: "#42a5f5", - dark: "#1565c0", - contrastText: "#ffffff" - }, - secondary: { - main: "#dc004e", - light: "#ff4081", - dark: "#c51162", - contrastText: "#ffffff" - }, - error: { - main: "#f44336", - light: "#e57373", - dark: "#d32f2f" - }, - warning: { - main: "#ff9800", - light: "#ffb74d", - dark: "#f57c00" - }, - info: { - main: "#2196f3", - light: "#64b5f6", - dark: "#1976d2" - }, - success: { - main: "#4caf50", - light: "#81c784", - dark: "#388e3c" - }, - background: { - default: "#f5f5f5", - paper: "#ffffff" - }, - text: { - primary: "rgba(0, 0, 0, 0.87)", - secondary: "rgba(0, 0, 0, 0.6)", - disabled: "rgba(0, 0, 0, 0.38)" - } - }, - typography: { - fontFamily: [ - "-apple-system", - "BlinkMacSystemFont", - '"Segoe UI"', - "Roboto", - '"Helvetica Neue"', - "Arial", - "sans-serif" - ].join(","), - h1: { - fontSize: "2.5rem", - fontWeight: 500 - }, - h2: { - fontSize: "2rem", - fontWeight: 500 - }, - h3: { - fontSize: "1.75rem", - fontWeight: 500 - }, - h4: { - fontSize: "1.5rem", - fontWeight: 500 - }, - h5: { - fontSize: "1.25rem", - fontWeight: 500 - }, - h6: { - fontSize: "1rem", - fontWeight: 500 - }, - body1: { - fontSize: "1rem", - lineHeight: 1.5 - }, - body2: { - fontSize: "0.875rem", - lineHeight: 1.43 - } - }, - shape: { - borderRadius: 4 - }, - components: { - MuiButton: { - styleOverrides: { - root: { - textTransform: "none", - borderRadius: "4px", - padding: "6px 16px" - }, - contained: { - boxShadow: "none", - "&:hover": { - boxShadow: "0px 2px 4px -1px rgba(0,0,0,0.2)" - } - } - } - }, - MuiTextField: { - styleOverrides: { - root: { - "& .MuiOutlinedInput-root": { - borderRadius: "4px" - } - } - } - }, - MuiCard: { - styleOverrides: { - root: { - borderRadius: "8px", - boxShadow: "0px 2px 4px -1px rgba(0,0,0,0.1)" - } - } - }, - MuiAppBar: { - styleOverrides: { - root: { - boxShadow: "0px 1px 3px rgba(0,0,0,0.12)" - } - } - } - }, - breakpoints: { - values: { - xs: 0, - sm: 600, - md: 960, - lg: 1280, - xl: 1920 - } - } -}; -const theme = createTheme(themeOptions); - -const supabaseUrl = "http://localhost:8000"; -const supabaseAnonKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0"; -logger.info("supabase-client", "🔄 Supabase configuration", { - url: supabaseUrl, - key: supabaseAnonKey -}); -let supabaseInstance = null; -const getSupabaseClient = () => { - if (!supabaseInstance) { - logger.info("supabase-client", "🔄 Initializing Supabase client"); - supabaseInstance = createClient( - supabaseUrl, - supabaseAnonKey, - { - auth: { - flowType: "pkce", - autoRefreshToken: true, - persistSession: true, - detectSessionInUrl: false, - storage: window.localStorage, - storageKey: "supabase.auth.token", - debug: true - }, - global: { - headers: { - "X-Client-Info": "classroom-copilot" - } - } - } - ); - logger.info("supabase-client", "🔄 Supabase client configuration loaded", { - url: supabaseUrl, - hasKey: true, - storageKey: "supabase.auth.token" - }); - } - return supabaseInstance; -}; -const supabase = new Proxy({}, { - get: (target, prop) => { - const client = getSupabaseClient(); - return client[prop]; - } -}); - -var StorageKeys = /* @__PURE__ */ ((StorageKeys2) => { - StorageKeys2["USER"] = "user"; - StorageKeys2["USER_ROLE"] = "user_role"; - StorageKeys2["SUPABASE_TOKEN"] = "supabase_token"; - StorageKeys2["MS_TOKEN"] = "msAccessToken"; - StorageKeys2["NEO4J_USER_DB"] = "neo4jUserDbName"; - StorageKeys2["NEO4J_WORKER_DB"] = "neo4jWorkerDbName"; - StorageKeys2["USER_NODES"] = "userNodes"; - StorageKeys2["CALENDAR_DATA"] = "calendarData"; - StorageKeys2["IS_NEW_REGISTRATION"] = "isNewRegistration"; - StorageKeys2["TLDRAW_PREFERENCES"] = "tldrawUserPreferences"; - StorageKeys2["TLDRAW_FILE_PATH"] = "tldrawUserFilePath"; - StorageKeys2["LOCAL_SNAPSHOT"] = "localSnapshot"; - StorageKeys2["NODE_FILE_PATH"] = "nodeFilePath"; - StorageKeys2["ONENOTE_NOTEBOOK"] = "oneNoteNotebook"; - StorageKeys2["PRESENTATION_MODE"] = "presentationMode"; - StorageKeys2["TLDRAW_USER"] = "tldrawUser"; - return StorageKeys2; -})(StorageKeys || {}); -class StorageService { - static instance; - constructor() { - } - static getInstance() { - if (!StorageService.instance) { - StorageService.instance = new StorageService(); - } - return StorageService.instance; - } - get(key) { - try { - const item = localStorage.getItem(key); - return item ? JSON.parse(item) : null; - } catch (error) { - logger.error("storage-service", `Error retrieving ${key}:`, error); - return null; - } - } - set(key, value) { - try { - const serializedValue = JSON.stringify(value); - localStorage.setItem(key, serializedValue); - logger.debug("storage-service", `Stored ${key} in localStorage`); - } catch (error) { - logger.error("storage-service", `Error storing ${key}:`, error); - } - } - remove(key) { - try { - localStorage.removeItem(key); - logger.debug("storage-service", `Removed ${key} from localStorage`); - } catch (error) { - logger.error("storage-service", `Error removing ${key}:`, error); - } - } - clearAll() { - try { - Object.values(StorageKeys).forEach((key) => { - localStorage.removeItem(key); - }); - logger.debug("storage-service", "Cleared all app items from localStorage"); - } catch (error) { - logger.error("storage-service", "Error clearing storage:", error); - } - } - // Helper method to update state and storage together - setStateAndStorage(setter, key, value) { - setter(value); - this.set(key, value); - } -} -const storageService = StorageService.getInstance(); - -class DatabaseNameService { - static CC_USERS = "cc.users"; - static CC_SCHOOLS = "cc.institutes"; - static getUserPrivateDB(userType, username) { - const dbName = `${this.CC_USERS}.${userType}.${username}`; - logger.debug("database-name-service", "đŸ“Ĩ Generating user private DB name", { - userType, - username, - dbName - }); - return dbName; - } - static getSchoolPrivateDB(schoolId) { - const dbName = `${this.CC_SCHOOLS}.${schoolId}`; - logger.debug("database-name-service", "đŸ“Ĩ Generating school private DB name", { - schoolId, - dbName - }); - return dbName; - } - static getDevelopmentSchoolDB() { - const dbName = `${this.CC_SCHOOLS}.development.default`; - logger.debug("database-name-service", "đŸ“Ĩ Getting default school DB name", { - dbName - }); - return dbName; - } - static getContextDatabase(context, userType, username) { - logger.debug("database-name-service", "đŸ“Ĩ Resolving context database", { - context, - userType, - username - }); - if (["school", "department", "class"].includes(context)) { - logger.debug("database-name-service", "✅ Using schools database for context", { - context, - dbName: this.CC_SCHOOLS - }); - return this.CC_SCHOOLS; - } - const userDb = this.getUserPrivateDB(userType, username); - logger.debug("database-name-service", "✅ Using user private database for context", { - context, - dbName: userDb - }); - return userDb; - } -} - -function convertToCCUser(user, metadata) { - const username = metadata.username || metadata.preferred_username || metadata.email?.split("@")[0] || user.email?.split("@")[0] || "user"; - const displayName = metadata.display_name || metadata.name || metadata.preferred_username || username; - const userType = metadata.user_type || "student"; - const userDbName = DatabaseNameService.getUserPrivateDB( - userType, - username - ); - const schoolDbName = DatabaseNameService.getDevelopmentSchoolDB(); - return { - id: user.id, - email: user.email, - user_type: userType, - username, - display_name: displayName, - user_db_name: userDbName, - school_db_name: schoolDbName, - created_at: user.created_at, - updated_at: user.updated_at - }; -} -class AuthService { - static instance; - constructor() { - } - onAuthStateChange(callback) { - return supabase.auth.onAuthStateChange((event, session) => { - logger.info("auth-service", "🔄 Auth state changed", { - event, - hasSession: !!session, - userId: session?.user?.id, - eventType: event - }); - if (event === "SIGNED_OUT") { - storageService.clearAll(); - } - callback(event, session); - }); - } - static getInstance() { - if (!AuthService.instance) { - AuthService.instance = new AuthService(); - } - return AuthService.instance; - } - async getCurrentSession() { - try { - const { - data: { session }, - error - } = await supabase.auth.getSession(); - if (error) { - throw error; - } - if (!session) { - return { user: null, accessToken: null, message: "No active session" }; - } - return { - user: convertToCCUser( - session.user, - session.user.user_metadata - ), - accessToken: session.access_token, - message: "Session retrieved" - }; - } catch (error) { - logger.error("auth-service", "Failed to get current session:", error); - throw error; - } - } - async getCurrentUser() { - try { - const { - data: { user }, - error - } = await supabase.auth.getUser(); - if (error || !user) { - return null; - } - return convertToCCUser(user, user.user_metadata); - } catch (error) { - logger.error("auth-service", "Failed to get current user:", error); - return null; - } - } - async login({ - email, - password, - role - }) { - try { - logger.info("auth-service", "🔄 Attempting login", { - email, - role - }); - const { data, error } = await supabase.auth.signInWithPassword({ - email, - password - }); - if (error) { - logger.error("auth-service", "❌ Supabase auth error", { - error: error.message, - status: error.status - }); - throw error; - } - if (!data.session) { - logger.error("auth-service", "❌ No session after login"); - throw new Error("No session after login"); - } - const ccUser = convertToCCUser( - data.user, - data.user.user_metadata - ); - storageService.set(StorageKeys.USER_ROLE, ccUser.user_type); - storageService.set(StorageKeys.USER, ccUser); - storageService.set(StorageKeys.SUPABASE_TOKEN, data.session.access_token); - logger.info("auth-service", "✅ Login successful", { - userId: ccUser.id, - role: ccUser.user_type, - username: ccUser.username - }); - return { - user: ccUser, - accessToken: data.session.access_token, - userRole: ccUser.user_type, - message: "Login successful" - }; - } catch (error) { - logger.error("auth-service", "❌ Login failed:", error); - throw error; - } - } - async logout() { - try { - logger.debug("auth-service", "🔄 Attempting logout"); - const { error } = await supabase.auth.signOut({ scope: "local" }); - if (error) { - logger.error("auth-service", "❌ Logout failed:", error); - throw error; - } - storageService.clearAll(); - await supabase.auth.refreshSession(); - logger.debug("auth-service", "✅ Logout successful"); - } catch (error) { - logger.error("auth-service", "❌ Logout failed:", error); - throw error; - } - } -} -const authService = AuthService.getInstance(); - -const AuthContext = reactExports.createContext({ - user: null, - user_role: null, - loading: true, - error: null, - signIn: async () => { - }, - signOut: async () => { - }, - clearError: () => { - } -}); -function AuthProvider({ children }) { - const navigate = useNavigate(); - const [user, setUser] = reactExports.useState(null); - const [user_role, setUserRole] = reactExports.useState(null); - const [loading, setLoading] = reactExports.useState(true); - const [error, setError] = reactExports.useState(null); - reactExports.useEffect(() => { - const loadUser = async () => { - try { - const { data: { user: user2 } } = await supabase.auth.getUser(); - if (user2) { - const metadata = user2.user_metadata; - setUser({ - id: user2.id, - email: user2.email, - user_type: metadata.user_type || "", - username: metadata.username || "", - display_name: metadata.display_name || "", - user_db_name: `cc.users.${metadata.user_type}.${metadata.username}`, - school_db_name: "cc.institutes.development.default", - created_at: user2.created_at, - updated_at: user2.updated_at - }); - setUserRole(metadata.user_role || null); - } else { - setUser(null); - } - } catch (error2) { - logger.error("auth-context", "❌ Failed to load user", { error: error2 }); - setError(error2 instanceof Error ? error2 : new Error("Failed to load user")); - } finally { - setLoading(false); - } - }; - loadUser(); - const { data: { subscription } } = supabase.auth.onAuthStateChange(async (event, session) => { - if (event === "SIGNED_IN" && session?.user) { - const metadata = session.user.user_metadata; - setUser({ - id: session.user.id, - email: session.user.email, - user_type: metadata.user_type || "", - username: metadata.username || "", - display_name: metadata.display_name || "", - user_db_name: `cc.users.${metadata.user_type}.${metadata.username}`, - school_db_name: "cc.institutes.development.default", - created_at: session.user.created_at, - updated_at: session.user.updated_at - }); - } else if (event === "SIGNED_OUT") { - setUser(null); - } - }); - return () => { - subscription.unsubscribe(); - }; - }, []); - const signIn = async (email, password) => { - try { - setLoading(true); - const { data, error: signInError } = await supabase.auth.signInWithPassword({ - email, - password - }); - if (signInError) throw signInError; - if (data.user) { - const metadata = data.user.user_metadata; - setUser({ - id: data.user.id, - email: data.user.email, - user_type: metadata.user_type || "", - username: metadata.username || "", - display_name: metadata.display_name || "", - user_db_name: `cc.users.${metadata.user_type}.${metadata.username}`, - school_db_name: "cc.institutes.development.default", - created_at: data.user.created_at, - updated_at: data.user.updated_at - }); - } - } catch (error2) { - logger.error("auth-context", "❌ Sign in failed", { error: error2 }); - setError(error2 instanceof Error ? error2 : new Error("Sign in failed")); - throw error2; - } finally { - setLoading(false); - } - }; - const signOut = async () => { - try { - setLoading(true); - await authService.logout(); - setUser(null); - navigate("/"); - } catch (error2) { - logger.error("auth-context", "❌ Sign out failed", { error: error2 }); - setError(error2 instanceof Error ? error2 : new Error("Sign out failed")); - throw error2; - } finally { - setLoading(false); - } - }; - const clearError = () => setError(null); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - AuthContext.Provider, - { - value: { - user, - user_role, - loading, - error, - signIn, - signOut, - clearError - }, - children - } - ); -} -const useAuth = () => reactExports.useContext(AuthContext); - -class PresentationService { - editor; - initialSlideshow = null; - cameraProxyId = createShapeId("camera-proxy"); - lastUserInteractionTime = 0; - USER_INTERACTION_DEBOUNCE = 1e3; - // 1 second - zoomLevels = /* @__PURE__ */ new Map(); - // Track zoom levels by shape dimensions - isMoving = false; - constructor(editor) { - this.editor = editor; - logger.debug("system", "đŸŽĨ PresentationService initialized"); - const style = document.createElement("style"); - style.setAttribute("data-camera-proxy", this.cameraProxyId); - style.textContent = ` - [data-shape-id="${this.cameraProxyId}"] { - opacity: 0 !important; - pointer-events: none !important; - } - `; - document.head.appendChild(style); - } - getShapeDimensionKey(width, height) { - return `${Math.round(width)}_${Math.round(height)}`; - } - async moveToShape(shape) { - if (this.isMoving) { - logger.debug("presentation", "âŗ Movement in progress, queueing next movement"); - await new Promise((resolve) => setTimeout(resolve, 100)); - return this.moveToShape(shape); - } - this.isMoving = true; - const bounds = this.editor.getShapePageBounds(shape.id); - if (!bounds) { - logger.warn("presentation", "âš ī¸ Could not get bounds for shape"); - this.isMoving = false; - return; - } - try { - this.editor.updateShape({ - id: this.cameraProxyId, - type: "frame", - x: bounds.minX, - y: bounds.minY, - props: { - w: bounds.width, - h: bounds.height, - name: "camera-proxy" - } - }); - await new Promise((resolve) => requestAnimationFrame(resolve)); - const viewport = this.editor.getViewportPageBounds(); - const padding = 32; - const dimensionKey = this.getShapeDimensionKey(bounds.width, bounds.height); - let targetZoom = this.zoomLevels.get(dimensionKey); - if (!targetZoom) { - targetZoom = Math.min( - (viewport.width - padding * 2) / bounds.width, - (viewport.height - padding * 2) / bounds.height - ); - this.zoomLevels.set(dimensionKey, targetZoom); - logger.debug("presentation", "📏 New zoom level calculated", { - dimensions: dimensionKey, - zoom: targetZoom - }); - } - this.editor.stopCameraAnimation(); - this.editor.zoomToBounds(bounds, { - animation: { - duration: 500, - easing: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2 - }, - targetZoom, - inset: padding - }); - await new Promise((resolve) => setTimeout(resolve, 500)); - } catch (error) { - logger.error("presentation", "❌ Error during shape transition", { error }); - } finally { - this.isMoving = false; - } - } - startPresentationMode() { - logger.info("presentation", "đŸŽĨ Starting presentation mode"); - this.zoomLevels.clear(); - const slideshows = this.editor.getSortedChildIdsForParent(this.editor.getCurrentPageId()).map((id) => this.editor.getShape(id)).filter((shape) => shape?.type === "cc-slideshow"); - if (slideshows.length === 0) { - logger.warn("presentation", "âš ī¸ No slideshows found"); - return () => { - }; - } - this.initialSlideshow = slideshows[0]; - if (!this.editor.getShape(this.cameraProxyId)) { - this.editor.createShape({ - id: this.cameraProxyId, - type: "frame", - x: 0, - y: 0, - props: { - w: 1, - h: 1, - name: "camera-proxy" - } - }); - } - const handleStoreChange = (event) => { - if (event.source === "user") { - const now = Date.now(); - if (now - this.lastUserInteractionTime > this.USER_INTERACTION_DEBOUNCE) { - logger.debug("presentation", "📝 User interaction received"); - this.lastUserInteractionTime = now; - } - } - if (!event.changes.updated) return; - const shapeUpdates = Object.entries(event.changes.updated).filter( - ([, [from, to]]) => from.typeName === "shape" && to.typeName === "shape" && from.type === "cc-slideshow" && to.type === "cc-slideshow" - ); - if (shapeUpdates.length === 0) return; - for (const [, [from, to]] of shapeUpdates) { - const fromShape = from; - const toShape = to; - if (!this.initialSlideshow || fromShape.id !== this.initialSlideshow.id) continue; - const fromShow = fromShape; - const toShow = toShape; - if (fromShow.props.currentSlideIndex === toShow.props.currentSlideIndex) continue; - logger.info("presentation", "🔄 Moving to new slide", { - from: fromShow.props.currentSlideIndex, - to: toShow.props.currentSlideIndex - }); - const bindings = this.editor.getBindingsFromShape(toShow, "cc-slide-layout").filter((b) => b.type === "cc-slide-layout").filter((b) => !b.props.placeholder).sort((a, b) => a.props.index > b.props.index ? 1 : -1); - const currentBinding = bindings[toShow.props.currentSlideIndex]; - if (!currentBinding) { - logger.warn("presentation", "âš ī¸ Could not find binding for target slide"); - continue; - } - const currentSlide = this.editor.getShape(currentBinding.toId); - if (!currentSlide) { - logger.warn("presentation", "âš ī¸ Could not find target slide"); - continue; - } - void this.moveToShape(currentSlide); - } - }; - const storeCleanup = this.editor.store.listen(handleStoreChange); - return () => { - logger.info("presentation", "🧹 Running presentation mode cleanup"); - storeCleanup(); - this.stopPresentationMode(); - }; - } - stopPresentationMode() { - this.zoomLevels.clear(); - this.isMoving = false; - if (this.editor.getShape(this.cameraProxyId)) { - this.editor.deleteShape(this.cameraProxyId); - } - const style = document.querySelector(`style[data-camera-proxy="${this.cameraProxyId}"]`); - if (style) { - style.remove(); - } - } - // Public method to move to any shape (slide or slideshow) - zoomToShape(shape) { - void this.moveToShape(shape); - } -} - -const TLDrawContext = reactExports.createContext({ - tldrawPreferences: null, - tldrawUserFilePath: null, - localSnapshot: null, - presentationMode: false, - sharedStore: null, - connectionStatus: "online", - presentationService: null, - setTldrawPreferences: () => { - }, - setTldrawUserFilePath: () => { - }, - handleLocalSnapshot: async () => { - }, - togglePresentationMode: () => { - }, - initializePreferences: () => { - }, - setSharedStore: () => { - }, - setConnectionStatus: () => { - } -}); -const TLDrawProvider = ({ children }) => { - const [tldrawPreferences, setTldrawPreferencesState] = reactExports.useState( - storageService.get(StorageKeys.TLDRAW_PREFERENCES) - ); - const [tldrawUserFilePath, setTldrawUserFilePathState] = reactExports.useState( - storageService.get(StorageKeys.TLDRAW_FILE_PATH) - ); - const [localSnapshot, setLocalSnapshot] = reactExports.useState( - storageService.get(StorageKeys.LOCAL_SNAPSHOT) - ); - const [presentationMode, setPresentationMode] = reactExports.useState( - storageService.get(StorageKeys.PRESENTATION_MODE) || false - ); - const [sharedStore, setSharedStore] = reactExports.useState(null); - const [connectionStatus, setConnectionStatus] = reactExports.useState("online"); - const [presentationService, setPresentationService] = reactExports.useState(null); - const initializePreferences = reactExports.useCallback((userId) => { - logger.debug("tldraw-context", "🔄 Initializing TLDraw preferences"); - const storedPrefs = storageService.get(StorageKeys.TLDRAW_PREFERENCES); - if (storedPrefs) { - logger.debug("tldraw-context", "đŸ“Ĩ Found stored preferences"); - setTldrawPreferencesState(storedPrefs); - return; - } - const defaultPrefs = { - id: userId, - name: "User", - color: `hsl(${Math.random() * 360}, 70%, 50%)`, - locale: "en", - colorScheme: "system", - isSnapMode: false, - isWrapMode: false, - isDynamicSizeMode: false, - isPasteAtCursorMode: false, - animationSpeed: 1, - edgeScrollSpeed: 1 - }; - logger.debug("tldraw-context", "📝 Creating default preferences"); - storageService.set(StorageKeys.TLDRAW_PREFERENCES, defaultPrefs); - setTldrawPreferencesState(defaultPrefs); - }, []); - const setTldrawPreferences = reactExports.useCallback((preferences) => { - logger.debug("tldraw-context", "🔄 Setting TLDraw preferences", { preferences }); - if (preferences) { - storageService.set(StorageKeys.TLDRAW_PREFERENCES, preferences); - } else { - storageService.remove(StorageKeys.TLDRAW_PREFERENCES); - } - setTldrawPreferencesState(preferences); - }, []); - const setTldrawUserFilePath = (path) => { - logger.debug("tldraw-context", "🔄 Setting TLDraw user file path"); - if (path) { - storageService.set(StorageKeys.TLDRAW_FILE_PATH, path); - } else { - storageService.remove(StorageKeys.TLDRAW_FILE_PATH); - } - setTldrawUserFilePathState(path); - }; - const handleLocalSnapshot = reactExports.useCallback(async (action, store, setLoadingState) => { - if (!store) { - setLoadingState({ status: "error", error: "Store not initialized" }); - return; - } - try { - if (sharedStore) { - if (action === "put") { - const snapshot = getSnapshot(store); - await sharedStore.saveSnapshot(snapshot, setLoadingState); - } else if (action === "get") { - const savedSnapshot = storageService.get(StorageKeys.LOCAL_SNAPSHOT); - if (savedSnapshot) { - await sharedStore.loadSnapshot(savedSnapshot, setLoadingState); - } - } - } else if (action === "put") { - logger.debug("tldraw-context", "💾 Putting snapshot into local storage"); - const snapshot = getSnapshot(store); - logger.debug("tldraw-context", "đŸ“Ļ Snapshot:", snapshot); - setLocalSnapshot(snapshot); - storageService.set(StorageKeys.LOCAL_SNAPSHOT, snapshot); - setLoadingState({ status: "ready", error: "" }); - } else if (action === "get") { - logger.debug("tldraw-context", "📂 Getting snapshot from local storage"); - setLoadingState({ status: "loading", error: "" }); - const savedSnapshot = storageService.get(StorageKeys.LOCAL_SNAPSHOT); - if (savedSnapshot && savedSnapshot.document && savedSnapshot.session) { - try { - logger.debug("tldraw-context", "đŸ“Ĩ Loading snapshot into editor"); - loadSnapshot(store, savedSnapshot); - setLoadingState({ status: "ready", error: "" }); - } catch (error) { - logger.error("tldraw-context", "❌ Failed to load snapshot:", error); - store.clear(); - setLoadingState({ status: "error", error: "Failed to load snapshot" }); - } - } else { - logger.debug("tldraw-context", "âš ī¸ No valid snapshot found in local storage"); - setLoadingState({ status: "ready", error: "" }); - } - } - } catch (error) { - logger.error("tldraw-context", "❌ Error handling local snapshot:", error); - setLoadingState({ - status: "error", - error: error instanceof Error ? error.message : "Unknown error" - }); - } - }, [sharedStore]); - const togglePresentationMode = reactExports.useCallback((editor) => { - logger.debug("tldraw-context", "🔄 Toggling presentation mode"); - setPresentationMode((prev) => { - const newValue = !prev; - storageService.set(StorageKeys.PRESENTATION_MODE, newValue); - if (newValue && editor) { - logger.info("presentation", "đŸŽĨ Initializing presentation service"); - const service = new PresentationService(editor); - setPresentationService(service); - service.startPresentationMode(); - } else if (!newValue && presentationService) { - logger.info("presentation", "🛑 Stopping presentation service"); - presentationService.stopPresentationMode(); - setPresentationService(null); - } - return newValue; - }); - }, [presentationService]); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - TLDrawContext.Provider, - { - value: { - tldrawPreferences, - tldrawUserFilePath, - localSnapshot, - presentationMode, - sharedStore, - connectionStatus, - presentationService, - setTldrawPreferences, - setTldrawUserFilePath, - handleLocalSnapshot, - togglePresentationMode, - initializePreferences, - setSharedStore, - setConnectionStatus - }, - children - } - ); -}; -const useTLDraw = () => reactExports.useContext(TLDrawContext); - -const UserContext = reactExports.createContext({ - user: null, - loading: true, - error: null, - profile: null, - preferences: {}, - isMobile: false, - isInitialized: false, - updateProfile: async () => { - }, - updatePreferences: async () => { - }, - clearError: () => { - } -}); -function UserProvider({ children }) { - const [user] = reactExports.useState(null); - const [profile, setProfile] = reactExports.useState(null); - const [preferences, setPreferences] = reactExports.useState({}); - const [loading, setLoading] = reactExports.useState(true); - const [isInitialized, setIsInitialized] = reactExports.useState(false); - const [error, setError] = reactExports.useState(null); - const [isMobile] = reactExports.useState(window.innerWidth <= 768); - reactExports.useEffect(() => { - const loadUserProfile = async () => { - try { - const { data: { user: user2 } } = await supabase.auth.getUser(); - if (!user2) { - setProfile(null); - setLoading(false); - setIsInitialized(true); - return; - } - const { data, error: error2 } = await supabase.from("profiles").select("*").eq("id", user2.id).single(); - if (error2) { - throw error2; - } - const metadata = user2.user_metadata; - const userDbName = DatabaseNameService.getUserPrivateDB(metadata.user_type || "", metadata.username || ""); - const schoolDbName = DatabaseNameService.getDevelopmentSchoolDB(); - const userProfile = { - id: user2.id, - email: user2.email, - user_type: metadata.user_type || "", - username: metadata.username || "", - display_name: metadata.display_name || "", - user_db_name: userDbName, - school_db_name: schoolDbName, - created_at: user2.created_at, - updated_at: user2.updated_at - }; - setProfile(userProfile); - logger.debug("user-context", "✅ User profile loaded", { - userId: userProfile.id, - userType: userProfile.user_type, - username: userProfile.username, - userDbName: userProfile.user_db_name, - schoolDbName: userProfile.school_db_name - }); - setPreferences({ - theme: data.theme || "system", - notifications: data.notifications_enabled || false - }); - } catch (error2) { - logger.error("user-context", "❌ Failed to load user profile", { error: error2 }); - setError(error2 instanceof Error ? error2 : new Error("Failed to load user profile")); - } finally { - setLoading(false); - setIsInitialized(true); - } - }; - loadUserProfile(); - }, []); - const updateProfile = async (updates) => { - if (!user?.id || !profile) { - return; - } - setLoading(true); - try { - const { error: error2 } = await supabase.from("profiles").update({ - ...updates, - updated_at: (/* @__PURE__ */ new Date()).toISOString() - }).eq("id", user.id); - if (error2) { - throw error2; - } - setProfile((prev) => prev ? { ...prev, ...updates } : null); - logger.info("user-context", "✅ Profile updated successfully"); - } catch (error2) { - logger.error("user-context", "❌ Failed to update profile", { error: error2 }); - setError(error2 instanceof Error ? error2 : new Error("Failed to update profile")); - throw error2; - } finally { - setLoading(false); - } - }; - const updatePreferences = async (updates) => { - if (!user?.id) { - return; - } - setLoading(true); - try { - const newPreferences = { ...preferences, ...updates }; - setPreferences(newPreferences); - const { error: error2 } = await supabase.from("profiles").update({ - preferences: newPreferences, - updated_at: (/* @__PURE__ */ new Date()).toISOString() - }).eq("id", user.id); - if (error2) { - throw error2; - } - logger.info("user-context", "✅ Preferences updated successfully"); - } catch (error2) { - logger.error("user-context", "❌ Failed to update preferences", { error: error2 }); - setError(error2 instanceof Error ? error2 : new Error("Failed to update preferences")); - throw error2; - } finally { - setLoading(false); - } - }; - return /* @__PURE__ */ jsxRuntimeExports.jsx( - UserContext.Provider, - { - value: { - user: profile, - loading, - error, - profile, - preferences, - isMobile, - isInitialized, - updateProfile, - updatePreferences, - clearError: () => setError(null) - }, - children - } - ); -} -const useUser = () => reactExports.useContext(UserContext); - -const baseURL = "http://localhost:8001"; -const instance = axios$1.create({ - baseURL, - timeout: 12e4, - // Increase timeout to 120 seconds for large files - headers: { - "Content-Type": "application/json" - } -}); -instance.interceptors.request.use( - (config) => { - if (config.headers["Content-Type"] === "application/json" && config.data instanceof FormData) { - delete config.headers["Content-Type"]; - } - logger.debug("axios", "🔄 Outgoing request", { - method: config.method, - url: config.url, - baseURL: config.baseURL - }); - return config; - }, - (error) => { - logger.error("axios", "❌ Request error", error); - return Promise.reject(error); - } -); -instance.interceptors.response.use( - (response) => { - logger.debug("axios", "✅ Response received", { - status: response.status, - url: response.config.url - }); - return response; - }, - (error) => { - if (error.response) { - logger.error("axios", "❌ Response error", { - status: error.response.status, - url: error.config.url, - data: error.response.data - }); - } else if (error.request) { - logger.error("axios", "❌ No response received", { - url: error.config.url - }); - } else { - logger.error("axios", "❌ Request setup error", error.message); - } - return Promise.reject(error); - } -); -const { isAxiosError } = axios$1; -const axios = Object.assign(instance, { isAxiosError }); - -function formatEmailForDatabase(email) { - const sanitized = email.toLowerCase().replace("@", "at").replace(/\./g, "dot").replace(/_/g, "underscore").replace(/-/g, "dash"); - return `${sanitized}`; -} - -const DEV_SCHOOL_NAME = "default"; -const DEV_SCHOOL_GROUP = "development"; -const ADMIN_USER_NAME = "kcar"; -const ADMIN_USER_GROUP = "admin"; -class UserNeoDBService { - static async fetchUserNodesData(email, userDbName, workerDbName) { - try { - if (!userDbName) { - logger.error("neo4j-service", "❌ Attempted to fetch nodes without database name"); - return null; - } - const formattedEmail = formatEmailForDatabase(email); - const uniqueId = `User_${formattedEmail}`; - logger.debug("neo4j-service", "🔄 Fetching user nodes data", { - email, - formattedEmail, - userDbName, - workerDbName, - uniqueId - }); - const userNode = await this.getDefaultNode("profile", userDbName); - if (!userNode || !userNode.data) { - throw new Error("Failed to fetch user node or node data missing"); - } - logger.debug("neo4j-service", "✅ Found user node", { - nodeId: userNode.id, - type: userNode.type, - hasData: !!userNode.data, - userDbName, - workerDbName - }); - const processedNodes = { - privateUserNode: { - ...userNode.data, - __primarylabel__: "User", - title: userNode.data.user_email || "User", - w: 200, - h: 200, - headerColor: "#3e6589", - backgroundColor: "#f0f0f0", - isLocked: false - }, - connectedNodes: {} - }; - try { - const calendarNode = await this.getDefaultNode("calendar", userDbName); - if (calendarNode?.data) { - processedNodes.connectedNodes.calendar = { - ...calendarNode.data, - __primarylabel__: "Calendar", - title: calendarNode.data.calendar_name || "Calendar", - w: 200, - h: 200, - headerColor: "#3e6589", - backgroundColor: "#f0f0f0", - isLocked: false - }; - logger.debug("neo4j-service", "✅ Found calendar node", { - nodeId: calendarNode.id, - node_storage_path: calendarNode.data.node_storage_path - }); - } else { - logger.debug("neo4j-service", "â„šī¸ No calendar node found"); - } - } catch (error) { - logger.warn("neo4j-service", "âš ī¸ Failed to fetch calendar node:", error); - } - if (workerDbName) { - try { - const teacherNode = await this.getDefaultNode("teaching", userDbName); - if (teacherNode?.data) { - processedNodes.connectedNodes.teacher = { - ...teacherNode.data, - __primarylabel__: "Teacher", - title: teacherNode.data.teacher_name_formal || "Teacher", - w: 200, - h: 200, - headerColor: "#3e6589", - backgroundColor: "#f0f0f0", - isLocked: false, - user_db_name: userDbName, - school_db_name: workerDbName - }; - logger.debug("neo4j-service", "✅ Found teacher node", { - nodeId: teacherNode.id, - node_storage_path: teacherNode.data.node_storage_path, - userDbName, - workerDbName - }); - } else { - logger.debug("neo4j-service", "â„šī¸ No teacher node found"); - } - } catch (error) { - logger.warn("neo4j-service", "âš ī¸ Failed to fetch teacher node:", error); - } - } - logger.debug("neo4j-service", "✅ Processed all user nodes", { - hasUserNode: !!processedNodes.privateUserNode, - hasCalendar: !!processedNodes.connectedNodes.calendar, - hasTeacher: !!processedNodes.connectedNodes.teacher, - teacherData: processedNodes.connectedNodes.teacher ? { - uuid_string: processedNodes.connectedNodes.teacher.uuid_string, - school_db_name: processedNodes.connectedNodes.teacher.school_db_name, - node_storage_path: processedNodes.connectedNodes.teacher.node_storage_path - } : null - }); - return processedNodes; - } catch (error) { - if (error instanceof Error) { - logger.error("neo4j-service", "❌ Failed to fetch user nodes:", error.message); - } else { - logger.error("neo4j-service", "❌ Failed to fetch user nodes:", String(error)); - } - throw error; - } - } - static getUserDatabaseName(userType, username) { - return DatabaseNameService.getUserPrivateDB(userType, username); - } - static getSchoolDatabaseName(schoolId) { - return DatabaseNameService.getSchoolPrivateDB(schoolId); - } - static getDefaultSchoolDatabaseName() { - return DatabaseNameService.getDevelopmentSchoolDB(); - } - static async fetchNodeData(nodeId, dbName) { - try { - logger.debug("neo4j-service", "🔄 Fetching node data", { nodeId, dbName }); - const response = await axios.get("/database/tools/get-node", { - params: { - uuid_string: nodeId, - db_name: dbName - } - }); - if (response.data?.status === "success" && response.data.node) { - return response.data.node; - } - return null; - } catch (error) { - logger.error("neo4j-service", "❌ Failed to fetch node data:", error); - throw error; - } - } - static getNodeDatabaseName(node) { - if (!node || !node.node_storage_path) { - logger.error("neo4j-service", "❌ Invalid node or missing node_storage_path", { - node: node ? { id: node.id, type: node.type, label: node.label } : null, - hasStoragePath: !!node?.node_storage_path - }); - throw new Error("Node is missing required storage path information"); - } - if (node.node_storage_path.startsWith("users/")) { - const parts2 = node.node_storage_path.split("/"); - if (parts2.length >= 4) { - return parts2[3]; - } - logger.warn("neo4j-service", "âš ī¸ Unexpected user path format", { path: node.node_storage_path }); - return `cc.users.${ADMIN_USER_GROUP}.${ADMIN_USER_NAME}`; - } - if (node.node_storage_path.startsWith("schools/")) { - return `cc.institutes.${DEV_SCHOOL_GROUP}.${DEV_SCHOOL_NAME}`; - } - const parts = node.node_storage_path.split("/"); - if (parts.length >= 4) { - return parts[3]; - } - logger.warn("neo4j-service", "âš ī¸ Using fallback database name", { - path: node.node_storage_path, - nodeType: node.type - }); - return `cc.users.kcar`; - } - static async getDefaultNode(context, dbName) { - try { - logger.debug("neo4j-service", "🔄 Fetching default node", { context, dbName }); - const params = { db_name: dbName }; - if (context === "overview") { - const navigationStore = useNavigationStore.getState(); - params.base_context = navigationStore.context.base; - } - const response = await axios.get( - `/database/tools/get-default-node/${context}`, - { params } - ); - if (response.data?.status === "success" && response.data.node) { - return { - id: response.data.node.id, - node_storage_path: response.data.node.node_storage_path, - type: response.data.node.type, - label: response.data.node.label, - data: response.data.node.data - }; - } - return null; - } catch (error) { - logger.error("neo4j-service", "❌ Failed to fetch default node:", error); - throw error; - } - } - static async fetchCalendarStructure(dbName) { - try { - logger.debug("navigation", "🔄 Fetching calendar structure", { dbName }); - const response = await axios.get( - `/database/calendar-structure/get-calendar-structure?db_name=${dbName}` - ); - if (response.data.status === "success") { - logger.info("navigation", "✅ Calendar structure fetched successfully"); - return response.data.data; - } - throw new Error("Failed to fetch calendar structure"); - } catch (error) { - logger.error("navigation", "❌ Failed to fetch calendar structure:", error); - throw error; - } - } - static async fetchWorkerStructure(dbName) { - try { - logger.debug("navigation", "🔄 Fetching worker structure", { dbName }); - const response = await axios.get( - `/database/worker-structure/get-worker-structure?db_name=${dbName}` - ); - if (response.data.status === "success") { - logger.info("navigation", "✅ Worker structure fetched successfully"); - return response.data.data; - } - throw new Error("Failed to fetch worker structure"); - } catch (error) { - logger.error("navigation", "❌ Failed to fetch worker structure:", error); - throw error; - } - } -} - -const getShapeType = (nodeType) => { - return `cc-${nodeType.replace(/([A-Z])/g, "-$1").toLowerCase().substring(1)}-node`; -}; -const isValidNodeType = (type) => { - return type in { - User: true, - Developer: true, - Teacher: true, - Student: true, - Calendar: true, - TeacherTimetable: true, - TimetableLesson: true, - PlannedLesson: true, - School: true, - CalendarYear: true, - CalendarMonth: true, - CalendarWeek: true, - CalendarDay: true, - CalendarTimeChunk: true, - ScienceLab: true, - KeyStageSyllabus: true, - YearGroupSyllabus: true, - CurriculumStructure: true, - Topic: true, - TopicLesson: true, - LearningStatement: true, - SchoolTimetable: true, - AcademicYear: true, - AcademicTerm: true, - AcademicWeek: true, - AcademicDay: true, - AcademicPeriod: true, - RegistrationPeriod: true, - PastoralStructure: true, - KeyStage: true, - Department: true, - Room: true, - SubjectClass: true, - DepartmentStructure: true, - UserTeacherTimetable: true, - UserTimetableLesson: true - }; -}; - -const getCurrentHistoryNode = (history) => { - const node = history.currentIndex === -1 || !history.nodes.length ? null : history.nodes[history.currentIndex]; - logger.debug("history-management", "📍 Getting current history node", { - currentIndex: history.currentIndex, - totalNodes: history.nodes.length, - node - }); - return node; -}; -const addToHistory = (history, node) => { - logger.debug("history-management", "➕ Adding node to history", { - currentIndex: history.currentIndex, - newNode: node, - existingNodes: history.nodes.length - }); - const newNodes = [...history.nodes.slice(0, history.currentIndex + 1), node]; - const newHistory = { - nodes: newNodes, - currentIndex: newNodes.length - 1 - }; - logger.debug("history-management", "✅ History updated", { - previousState: history, - newState: newHistory - }); - return newHistory; -}; -const navigateHistory = (history, index) => { - logger.debug("history-management", "🔄 Navigating history", { - currentIndex: history.currentIndex, - targetIndex: index, - totalNodes: history.nodes.length - }); - if (index < 0 || index >= history.nodes.length) { - logger.warn("history-management", "âš ī¸ Invalid history navigation index", { - requestedIndex: index, - historyLength: history.nodes.length - }); - return history; - } - const newHistory = { - nodes: history.nodes, - currentIndex: index - }; - logger.debug("history-management", "✅ History navigation complete", { - from: history.currentIndex, - to: index, - node: history.nodes[index] - }); - return newHistory; -}; -const isProfileContext = (context) => { - return ["profile", "calendar", "teaching"].includes(context); -}; -const isInstituteContext = (context) => { - return ["school", "department", "class"].includes(context); -}; -const getContextDatabase = (context, userDbName, workerDbName) => { - logger.debug("navigation-context", "🔄 Getting context database", { - mainContext: context.main, - baseContext: context.base, - userDbName, - workerDbName - }); - if (context.main === "profile") { - if (!userDbName) { - logger.error("navigation-context", "❌ Missing user database name for profile context"); - throw new Error("User database name is required for profile context"); - } - logger.debug("navigation-context", "✅ Using user database", { dbName: userDbName }); - return userDbName; - } else { - if (!workerDbName) { - logger.error("navigation-context", "❌ Missing worker database name for institute context"); - throw new Error("Worker database name is required for institute context"); - } - logger.debug("navigation-context", "✅ Using worker database", { dbName: workerDbName }); - return workerDbName; - } -}; - -const initialState = { - main: "profile", - base: "profile", - node: null, - history: { - nodes: [], - currentIndex: -1 - } -}; -function getDefaultBaseForMain(main) { - return main === "profile" ? "profile" : "school"; -} -function validateContextTransition(current, updates) { - const newState = { ...current, ...updates }; - if (updates.main) { - newState.base = getDefaultBaseForMain(updates.main); - } - if (updates.base) { - const isValid = newState.main === "profile" ? isProfileContext(updates.base) : isInstituteContext(updates.base); - if (!isValid) { - newState.base = getDefaultBaseForMain(newState.main); - } - } - return newState; -} -const useNavigationStore = create$1((set, get) => ({ - context: initialState, - isLoading: false, - error: null, - switchContext: async (contextSwitch, userDbName, workerDbName) => { - try { - if (contextSwitch.main === "profile" && !userDbName) { - logger.error("navigation-context", "❌ User database connection not initialized"); - set({ - error: "User database connection not initialized", - isLoading: false - }); - return; - } - if (contextSwitch.main === "institute" && !workerDbName) { - logger.error("navigation-context", "❌ Worker database connection not initialized"); - set({ - error: "Worker database connection not initialized", - isLoading: false - }); - return; - } - logger.debug("navigation-context", "🔄 Starting context switch", { - from: { - main: get().context.main, - base: get().context.base, - extended: contextSwitch.extended, - nodeId: get().context.node?.id - }, - to: { - main: contextSwitch.main, - base: contextSwitch.base, - extended: contextSwitch.extended - }, - skipBaseContextLoad: contextSwitch.skipBaseContextLoad - }); - set({ isLoading: true, error: null }); - const currentState = get().context; - const clearedState = { - ...currentState, - node: null - }; - set({ - context: clearedState, - isLoading: true - }); - let newState = { - ...currentState, - node: null - }; - if (contextSwitch.main) { - newState = validateContextTransition(newState, { main: contextSwitch.main }); - if (!contextSwitch.skipBaseContextLoad) { - newState.base = getDefaultBaseForMain(contextSwitch.main); - } - logger.debug("navigation-state", "✅ Main context updated", { - previous: currentState.main, - new: newState.main, - defaultBase: newState.base - }); - } - if (contextSwitch.base) { - newState = validateContextTransition(newState, { base: contextSwitch.base }); - logger.debug("navigation-state", "✅ Base context updated", { - previous: currentState.base, - new: newState.base - }); - } - logger.debug("navigation-state", "✅ Context validation complete", { - validatedState: newState, - originalState: currentState - }); - const targetContext = contextSwitch.base || contextSwitch.extended || (contextSwitch.main ? getDefaultBaseForMain(contextSwitch.main) : newState.base); - const dbName = getContextDatabase(newState, userDbName, workerDbName); - logger.debug("context-switch", "🔍 Fetching default node for context", { - targetContext, - dbName, - currentState: newState - }); - const defaultNode = await UserNeoDBService.getDefaultNode(targetContext, dbName); - if (!defaultNode) { - const errorMsg = `No default node found for context: ${targetContext}`; - logger.error("context-switch", "❌ Default node fetch failed", { targetContext }); - set({ - error: errorMsg, - isLoading: false - }); - return; - } - logger.debug("context-switch", "✨ Default node fetched", { - nodeId: defaultNode.id, - node_storage_path: defaultNode.node_storage_path, - type: defaultNode.type - }); - const newHistory = addToHistory(currentState.history, defaultNode); - logger.debug("history-management", "📚 History updated", { - previousState: currentState.history, - newState: newHistory, - addedNode: defaultNode - }); - set({ - context: { - ...newState, - node: defaultNode, - history: newHistory - }, - isLoading: false, - error: null - }); - logger.debug("navigation-context", "✅ Context switch completed", { - finalState: { - main: newState.main, - base: newState.base, - nodeId: defaultNode.id - } - }); - } catch (error) { - logger.error("navigation-context", "❌ Failed to switch context:", error); - set({ - error: error instanceof Error ? error.message : "Failed to switch context", - isLoading: false - }); - } - }, - goBack: () => { - const currentState = get().context; - if (currentState.history.currentIndex > 0) { - const newHistory = navigateHistory(currentState.history, currentState.history.currentIndex - 1); - const node = getCurrentHistoryNode(newHistory); - set({ - context: { - ...currentState, - node, - history: newHistory - } - }); - } - }, - goForward: () => { - const currentState = get().context; - if (currentState.history.currentIndex < currentState.history.nodes.length - 1) { - const newHistory = navigateHistory(currentState.history, currentState.history.currentIndex + 1); - const node = getCurrentHistoryNode(newHistory); - set({ - context: { - ...currentState, - node, - history: newHistory - } - }); - } - }, - setMainContext: async (main, userDbName, workerDbName) => { - try { - await get().switchContext({ main }, userDbName, workerDbName); - } catch (error) { - logger.error("navigation", "❌ Failed to set main context:", error); - set({ - error: error instanceof Error ? error.message : "Failed to set main context", - isLoading: false - }); - } - }, - setBaseContext: async (base, userDbName, workerDbName) => { - try { - await get().switchContext({ base }, userDbName, workerDbName); - } catch (error) { - logger.error("navigation", "❌ Failed to set base context:", error); - set({ - error: error instanceof Error ? error.message : "Failed to set base context", - isLoading: false - }); - } - }, - setExtendedContext: async (extended, userDbName, workerDbName) => { - try { - await get().switchContext({ extended }, userDbName, workerDbName); - } catch (error) { - logger.error("navigation", "❌ Failed to set extended context:", error); - set({ - error: error instanceof Error ? error.message : "Failed to set extended context", - isLoading: false - }); - } - }, - navigate: async (nodeId, dbName) => { - try { - set({ isLoading: true, error: null }); - const currentState = get().context; - const existingNodeIndex = currentState.history.nodes.findIndex((n) => n.id === nodeId); - if (existingNodeIndex !== -1) { - logger.debug("navigation", "📍 Navigating to existing node in history", { - nodeId, - historyIndex: existingNodeIndex, - currentIndex: currentState.history.currentIndex - }); - const newHistory2 = navigateHistory(currentState.history, existingNodeIndex); - const node2 = getCurrentHistoryNode(newHistory2); - set({ - context: { - ...currentState, - node: node2, - history: newHistory2 - }, - isLoading: false, - error: null - }); - return; - } - const nodeData = await UserNeoDBService.fetchNodeData(nodeId, dbName); - if (!nodeData) { - throw new Error(`Node not found: ${nodeId}`); - } - const node = { - id: nodeId, - node_storage_path: nodeData.node_data.node_storage_path || "", - label: nodeData.node_data.title || nodeData.node_data.user_name || nodeId, - type: nodeData.node_type - }; - logger.debug("navigation", "📍 Adding new node to history", { - nodeId: node.id, - type: node.type, - node_storage_path: node.node_storage_path - }); - const newHistory = addToHistory(currentState.history, node); - set({ - context: { - ...currentState, - node, - history: newHistory - }, - isLoading: false, - error: null - }); - } catch (error) { - logger.error("navigation", "❌ Failed to navigate:", error); - set({ - error: error instanceof Error ? error.message : "Failed to navigate", - isLoading: false - }); - } - }, - navigateToNode: async (node, userDbName, workerDbName) => { - try { - set({ isLoading: true, error: null }); - if (!isValidNodeType(node.type)) { - throw new Error(`Invalid node type: ${node.type}`); - } - const dbName = getContextDatabase(get().context, userDbName, workerDbName); - await get().navigate(node.id, dbName); - } catch (error) { - logger.error("navigation", "❌ Failed to navigate to node:", error); - set({ - error: error instanceof Error ? error.message : "Failed to navigate to node", - isLoading: false - }); - } - }, - refreshNavigationState: async (userDbName, workerDbName) => { - try { - set({ isLoading: true, error: null }); - const currentState = get().context; - if (currentState.node) { - const dbName = getContextDatabase(currentState, userDbName, workerDbName); - const nodeData = await UserNeoDBService.fetchNodeData(currentState.node.id, dbName); - if (nodeData) { - const node = { - id: currentState.node.id, - node_storage_path: nodeData.node_data.node_storage_path || "", - label: nodeData.node_data.title || nodeData.node_data.user_name || currentState.node.id, - type: nodeData.node_type - }; - set({ - context: { - ...currentState, - node - } - }); - } - } - set({ isLoading: false }); - } catch (error) { - logger.error("navigation", "❌ Failed to refresh navigation state:", error); - set({ - error: error instanceof Error ? error.message : "Failed to refresh navigation state", - isLoading: false - }); - } - } -})); - -const NeoUserContext = reactExports.createContext({ - userNode: null, - calendarNode: null, - workerNode: null, - userDbName: null, - workerDbName: null, - isLoading: false, - isInitialized: false, - error: null, - navigateToDay: async () => { - }, - navigateToWeek: async () => { - }, - navigateToMonth: async () => { - }, - navigateToYear: async () => { - }, - navigateToTimetable: async () => { - }, - navigateToJournal: async () => { - }, - navigateToPlanner: async () => { - }, - navigateToClass: async () => { - }, - navigateToLesson: async () => { - }, - currentCalendarNode: null, - currentWorkerNode: null, - calendarStructure: null, - workerStructure: null -}); -const NeoUserProvider = ({ children }) => { - const { user } = useAuth(); - const { profile, isInitialized: isUserInitialized } = useUser(); - const navigationStore = useNavigationStore(); - const [userNode, setUserNode] = reactExports.useState(null); - const [calendarNode] = reactExports.useState(null); - const [workerNode] = reactExports.useState(null); - const [currentCalendarNode, setCurrentCalendarNode] = reactExports.useState(null); - const [currentWorkerNode, setCurrentWorkerNode] = reactExports.useState(null); - const [calendarStructure] = reactExports.useState(null); - const [workerStructure] = reactExports.useState(null); - const [userDbName, setUserDbName] = reactExports.useState(null); - const [workerDbName, setWorkerDbName] = reactExports.useState(null); - const [isLoading, setIsLoading] = reactExports.useState(true); - const [isInitialized, setIsInitialized] = reactExports.useState(false); - const [error, setError] = reactExports.useState(null); - const initializationRef = React$2.useRef({ - hasStarted: false, - isComplete: false - }); - const getBaseNodeProps = () => ({ - title: "", - w: 200, - h: 200, - headerColor: "#000000", - backgroundColor: "#ffffff", - isLocked: false, - __primarylabel__: "UserTeacherTimetable", - uuid_string: "", - node_storage_path: "", - created: (/* @__PURE__ */ new Date()).toISOString(), - merged: (/* @__PURE__ */ new Date()).toISOString(), - state: { - parentId: null, - isPageChild: false, - hasChildren: false, - bindings: [] - }, - defaultComponent: true - }); - reactExports.useEffect(() => { - if (!isUserInitialized || !profile || isInitialized || initializationRef.current.hasStarted) { - return; - } - const initializeContext = async () => { - try { - initializationRef.current.hasStarted = true; - setIsLoading(true); - setError(null); - const userDb = profile.user_db_name || (user?.email ? `cc.users.${user.email.replace("@", "at").replace(/\./g, "dot")}` : null); - if (!userDb) { - throw new Error("No user database name available"); - } - logger.debug("neo-user-context", "🔄 Starting context initialization"); - await navigationStore.switchContext({ - main: "profile", - base: "profile", - extended: "overview" - }, userDb, profile.school_db_name); - const userNavigationNode = navigationStore.context.node; - if (userNavigationNode?.data) { - const userNodeData = { - ...getBaseNodeProps(), - __primarylabel__: "User", - uuid_string: userNavigationNode.id, - node_storage_path: userNavigationNode.node_storage_path || "", - title: String(userNavigationNode.data?.user_name || "User"), - user_name: String(userNavigationNode.data?.user_name || "User"), - user_email: user?.email || "", - user_type: "User", - user_id: userNavigationNode.id, - worker_node_data: JSON.stringify(userNavigationNode.data || {}) - }; - setUserNode(userNodeData); - } - setUserDbName(userDb); - setWorkerDbName(profile.school_db_name); - setIsInitialized(true); - setIsLoading(false); - initializationRef.current.isComplete = true; - logger.debug("neo-user-context", "✅ Context initialization complete"); - } catch (error2) { - const errorMessage = error2 instanceof Error ? error2.message : "Failed to initialize user context"; - logger.error("neo-user-context", "❌ Failed to initialize context", { error: errorMessage }); - setError(errorMessage); - setIsLoading(false); - setIsInitialized(true); - initializationRef.current.isComplete = true; - } - }; - initializeContext(); - }, [user?.email, profile, isUserInitialized, navigationStore, isInitialized]); - const navigateToDay = async (id) => { - if (!userDbName) return; - setIsLoading(true); - try { - await navigationStore.switchContext({ - base: "calendar", - extended: "day" - }, userDbName, workerDbName); - const node = navigationStore.context.node; - if (node?.data) { - const nodeData = { - ...getBaseNodeProps(), - __primarylabel__: "CalendarDay", - uuid_string: id || node.id, - node_storage_path: node.node_storage_path || "", - title: node.label, - name: node.label, - calendar_type: "day", - calendar_name: node.label, - start_date: (/* @__PURE__ */ new Date()).toISOString(), - end_date: (/* @__PURE__ */ new Date()).toISOString() - }; - setCurrentCalendarNode({ - id: id || node.id, - label: node.label, - title: node.label, - node_storage_path: node.node_storage_path || "", - type: "CalendarDay", - nodeData - }); - } - } catch (error2) { - setError(error2 instanceof Error ? error2.message : "Failed to navigate to day"); - } finally { - setIsLoading(false); - } - }; - const navigateToWeek = async (id) => { - if (!userDbName) return; - setIsLoading(true); - try { - await navigationStore.switchContext({ - base: "calendar", - extended: "week" - }, userDbName, workerDbName); - const node = navigationStore.context.node; - if (node?.data) { - const nodeData = { - ...getBaseNodeProps(), - __primarylabel__: "CalendarWeek", - uuid_string: id || node.id, - node_storage_path: node.node_storage_path || "", - title: node.label, - name: node.label, - calendar_type: "week", - calendar_name: node.label, - start_date: (/* @__PURE__ */ new Date()).toISOString(), - end_date: (/* @__PURE__ */ new Date()).toISOString() - }; - setCurrentCalendarNode({ - id: id || node.id, - label: node.label, - title: node.label, - node_storage_path: node.node_storage_path || "", - type: "CalendarWeek", - nodeData - }); - } - } catch (error2) { - setError(error2 instanceof Error ? error2.message : "Failed to navigate to week"); - } finally { - setIsLoading(false); - } - }; - const navigateToMonth = async (id) => { - if (!userDbName) return; - setIsLoading(true); - try { - await navigationStore.switchContext({ - base: "calendar", - extended: "month" - }, userDbName, workerDbName); - const node = navigationStore.context.node; - if (node?.data) { - const nodeData = { - ...getBaseNodeProps(), - __primarylabel__: "CalendarMonth", - uuid_string: id || node.id, - node_storage_path: node.node_storage_path || "", - title: node.label, - name: node.label, - calendar_type: "month", - calendar_name: node.label, - start_date: (/* @__PURE__ */ new Date()).toISOString(), - end_date: (/* @__PURE__ */ new Date()).toISOString() - }; - setCurrentCalendarNode({ - id: id || node.id, - label: node.label, - title: node.label, - node_storage_path: node.node_storage_path || "", - type: "CalendarMonth", - nodeData - }); - } - } catch (error2) { - setError(error2 instanceof Error ? error2.message : "Failed to navigate to month"); - } finally { - setIsLoading(false); - } - }; - const navigateToYear = async (id) => { - if (!userDbName) return; - setIsLoading(true); - try { - await navigationStore.switchContext({ - base: "calendar", - extended: "year" - }, userDbName, workerDbName); - const node = navigationStore.context.node; - if (node?.data) { - const nodeData = { - ...getBaseNodeProps(), - __primarylabel__: "CalendarYear", - uuid_string: id || node.id, - node_storage_path: node.node_storage_path || "", - title: node.label, - name: node.label, - calendar_type: "year", - calendar_name: node.label, - start_date: (/* @__PURE__ */ new Date()).toISOString(), - end_date: (/* @__PURE__ */ new Date()).toISOString() - }; - setCurrentCalendarNode({ - id: id || node.id, - label: node.label, - title: node.label, - node_storage_path: node.node_storage_path || "", - type: "CalendarYear", - nodeData - }); - } - } catch (error2) { - setError(error2 instanceof Error ? error2.message : "Failed to navigate to year"); - } finally { - setIsLoading(false); - } - }; - const navigateToTimetable = async (id) => { - if (!userDbName) return; - setIsLoading(true); - try { - await navigationStore.switchContext({ - base: "teaching", - extended: "timetable" - }, userDbName, workerDbName); - const node = navigationStore.context.node; - if (node?.data) { - const nodeData = { - ...getBaseNodeProps(), - __primarylabel__: "UserTeacherTimetable", - uuid_string: id || node.id, - node_storage_path: node.node_storage_path || "", - title: node.label, - school_db_name: workerDbName || "", - school_timetable_id: id || node.id - }; - setCurrentWorkerNode({ - id: id || node.id, - label: node.label, - title: node.label, - node_storage_path: node.node_storage_path || "", - type: "UserTeacherTimetable", - nodeData - }); - } - } catch (error2) { - setError(error2 instanceof Error ? error2.message : "Failed to navigate to timetable"); - } finally { - setIsLoading(false); - } - }; - const navigateToJournal = async (id) => { - if (!userDbName) return; - setIsLoading(true); - try { - await navigationStore.switchContext({ - base: "teaching", - extended: "journal" - }, userDbName, workerDbName); - const node = navigationStore.context.node; - if (node?.data) { - const nodeData = { - ...getBaseNodeProps(), - __primarylabel__: "UserTeacherTimetable", - uuid_string: id || node.id, - node_storage_path: node.node_storage_path || "", - title: node.label, - school_db_name: workerDbName || "", - school_timetable_id: id || node.id - }; - setCurrentWorkerNode({ - id: id || node.id, - label: node.label, - title: node.label, - node_storage_path: node.node_storage_path || "", - type: "UserTeacherTimetable", - nodeData - }); - } - } catch (error2) { - setError(error2 instanceof Error ? error2.message : "Failed to navigate to journal"); - } finally { - setIsLoading(false); - } - }; - const navigateToPlanner = async (id) => { - if (!userDbName) return; - setIsLoading(true); - try { - await navigationStore.switchContext({ - base: "teaching", - extended: "planner" - }, userDbName, workerDbName); - const node = navigationStore.context.node; - if (node?.data) { - const nodeData = { - ...getBaseNodeProps(), - __primarylabel__: "UserTeacherTimetable", - uuid_string: id || node.id, - node_storage_path: node.node_storage_path || "", - title: node.label, - school_db_name: workerDbName || "", - school_timetable_id: id || node.id - }; - setCurrentWorkerNode({ - id: id || node.id, - label: node.label, - title: node.label, - node_storage_path: node.node_storage_path || "", - type: "UserTeacherTimetable", - nodeData - }); - } - } catch (error2) { - setError(error2 instanceof Error ? error2.message : "Failed to navigate to planner"); - } finally { - setIsLoading(false); - } - }; - const navigateToClass = async (id) => { - if (!userDbName) return; - setIsLoading(true); - try { - await navigationStore.switchContext({ - base: "teaching", - extended: "classes" - }, userDbName, workerDbName); - await navigationStore.navigate(id, userDbName); - const node = navigationStore.context.node; - if (node?.data) { - const nodeData = { - ...getBaseNodeProps(), - __primarylabel__: "UserTeacherTimetable", - uuid_string: node.id, - node_storage_path: node.node_storage_path || "", - title: node.label, - school_db_name: workerDbName || "", - school_timetable_id: node.id - }; - setCurrentWorkerNode({ - id: node.id, - label: node.label, - title: node.label, - node_storage_path: node.node_storage_path || "", - type: "UserTeacherTimetable", - nodeData - }); - } - } catch (error2) { - setError(error2 instanceof Error ? error2.message : "Failed to navigate to class"); - } finally { - setIsLoading(false); - } - }; - const navigateToLesson = async (id) => { - if (!userDbName) return; - setIsLoading(true); - try { - await navigationStore.switchContext({ - base: "teaching", - extended: "lessons" - }, userDbName, workerDbName); - await navigationStore.navigate(id, userDbName); - const node = navigationStore.context.node; - if (node?.data) { - const nodeData = { - ...getBaseNodeProps(), - __primarylabel__: "UserTeacherTimetable", - uuid_string: node.id, - node_storage_path: node.node_storage_path || "", - title: node.label, - school_db_name: workerDbName || "", - school_timetable_id: node.id - }; - setCurrentWorkerNode({ - id: node.id, - label: node.label, - title: node.label, - node_storage_path: node.node_storage_path || "", - type: "UserTeacherTimetable", - nodeData - }); - } - } catch (error2) { - setError(error2 instanceof Error ? error2.message : "Failed to navigate to lesson"); - } finally { - setIsLoading(false); - } - }; - return /* @__PURE__ */ jsxRuntimeExports.jsx(NeoUserContext.Provider, { value: { - userNode, - calendarNode, - workerNode, - userDbName, - workerDbName, - isLoading, - isInitialized, - error, - navigateToDay, - navigateToWeek, - navigateToMonth, - navigateToYear, - navigateToTimetable, - navigateToJournal, - navigateToPlanner, - navigateToClass, - navigateToLesson, - currentCalendarNode, - currentWorkerNode, - calendarStructure, - workerStructure - }, children }); -}; -const useNeoUser = () => reactExports.useContext(NeoUserContext); - -class SchoolNeoDBService { - static async createSchools() { - logger.warn("school-service", "📤 Creating schools using default config.yaml"); - try { - const response = await axios.post( - "/database/entity/create-schools", - {}, - { - headers: { - "Content-Type": "application/json" - } - } - ); - if (response.data.status === "success" || response.data.status === "Accepted") { - logger.info("school-service", "✅ Schools successfully"); - return { - status: "success", - message: "Schools created successfully" - }; - } - throw new Error(response.data.message || "Creation failed"); - } catch (err) { - const error = err; - logger.error("school-service", "❌ Failed to create school", { - error: error.message, - details: error.response?.data - }); - throw error; - } - } - static async getSchoolNode(schoolDbName) { - logger.debug("school-service", "🔄 Fetching school node", { schoolDbName }); - try { - const response = await axios.get(`/database/tools/get-default-node/school?db_name=${schoolDbName}`); - if (response.data?.status === "success" && response.data.node) { - logger.info("school-service", "✅ School node fetched successfully"); - return response.data.node; - } - logger.warn("school-service", "âš ī¸ No school node found"); - return null; - } catch (error) { - if (error instanceof AxiosError && error.response?.status === 404) { - logger.warn("school-service", "âš ī¸ School node not found (404)", { schoolDbName }); - return null; - } - logger.error("school-service", "❌ Failed to fetch school node:", error); - throw error; - } - } -} - -const NeoInstituteContext = reactExports.createContext({ - schoolNode: null, - isLoading: true, - isInitialized: false, - error: null -}); -const NeoInstituteProvider = ({ children }) => { - const { user } = useAuth(); - const { profile, isInitialized: isUserInitialized } = useUser(); - const [schoolNode, setSchoolNode] = reactExports.useState(null); - const [isLoading, setIsLoading] = reactExports.useState(true); - const [isInitialized, setIsInitialized] = reactExports.useState(false); - const [error, setError] = reactExports.useState(null); - reactExports.useEffect(() => { - if (!isUserInitialized) { - logger.debug("neo-institute-context", "âŗ Waiting for user initialization..."); - return; - } - if (!profile || !profile.school_db_name) { - setIsLoading(false); - setIsInitialized(true); - return; - } - const loadSchoolNode = async () => { - try { - setIsLoading(true); - logger.debug("neo-institute-context", "🔄 Loading school node", { - schoolDbName: profile.school_db_name, - userEmail: user?.email - }); - const node = await SchoolNeoDBService.getSchoolNode(profile.school_db_name); - if (node) { - setSchoolNode(node); - logger.debug("neo-institute-context", "✅ School node loaded", { - schoolId: node.uuid_string, - dbName: profile.school_db_name - }); - } else { - logger.warn("neo-institute-context", "âš ī¸ No school node found"); - } - } catch (error2) { - const errorMessage = error2 instanceof Error ? error2.message : "Failed to load school node"; - logger.error("neo-institute-context", "❌ Failed to load school node", { - error: errorMessage, - schoolDbName: profile.school_db_name - }); - setError(errorMessage); - } finally { - setIsLoading(false); - setIsInitialized(true); - } - }; - loadSchoolNode(); - }, [user?.email, profile, isUserInitialized]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(NeoInstituteContext.Provider, { value: { - schoolNode, - isLoading, - isInitialized, - error - }, children }); -}; -const useNeoInstitute = () => reactExports.useContext(NeoInstituteContext); - -var Menu = {}; - -var interopRequireDefault = {exports: {}}; - -(function (module) { - function _interopRequireDefault(e) { - return e && e.__esModule ? e : { - "default": e - }; - } - module.exports = _interopRequireDefault, module.exports.__esModule = true, module.exports["default"] = module.exports; -} (interopRequireDefault)); - -var interopRequireDefaultExports = interopRequireDefault.exports; - -var createSvgIcon = {}; - -const require$$0 = /*@__PURE__*/getAugmentedNamespace(utils$7); - -var hasRequiredCreateSvgIcon; - -function requireCreateSvgIcon () { - if (hasRequiredCreateSvgIcon) return createSvgIcon; - hasRequiredCreateSvgIcon = 1; - (function (exports) { - 'use client'; - - Object.defineProperty(exports, "__esModule", { - value: true - }); - Object.defineProperty(exports, "default", { - enumerable: true, - get: function () { - return _utils.createSvgIcon; - } - }); - var _utils = require$$0; - } (createSvgIcon)); - return createSvgIcon; -} - -var _interopRequireDefault$d = interopRequireDefaultExports; -Object.defineProperty(Menu, "__esModule", { - value: true -}); -var default_1$d = Menu.default = void 0; -var _createSvgIcon$d = _interopRequireDefault$d(requireCreateSvgIcon()); -var _jsxRuntime$d = jsxRuntimeExports; -var _default$d = (0, _createSvgIcon$d.default)( /*#__PURE__*/(0, _jsxRuntime$d.jsx)("path", { - d: "M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" -}), 'Menu'); -default_1$d = Menu.default = _default$d; - -const NAVIGATION_CONTEXTS = { - // Personal Contexts - profile: { - id: "profile", - icon: "Person", - label: "User Profile", - description: "Personal workspace and settings", - defaultNodeId: "user-root", - views: [ - { - id: "overview", - icon: "Dashboard", - label: "Overview", - description: "View your profile overview" - }, - { - id: "settings", - icon: "Settings", - label: "Settings", - description: "Manage your preferences" - }, - { - id: "history", - icon: "History", - label: "History", - description: "View your activity history" - }, - { - id: "journal", - icon: "Book", - label: "Journal", - description: "Your personal journal" - }, - { - id: "planner", - icon: "Event", - label: "Planner", - description: "Plan your activities" - } - ] - }, - calendar: { - id: "calendar", - icon: "CalendarToday", - label: "Calendar", - description: "Calendar navigation and events", - defaultNodeId: "calendar-root", - views: [ - { - id: "overview", - icon: "Dashboard", - label: "Overview", - description: "Calendar overview" - }, - { - id: "day", - icon: "Today", - label: "Day View", - description: "Navigate by day" - }, - { - id: "week", - icon: "ViewWeek", - label: "Week View", - description: "Navigate by week" - }, - { - id: "month", - icon: "DateRange", - label: "Month View", - description: "Navigate by month" - }, - { - id: "year", - icon: "Event", - label: "Year View", - description: "Navigate by year" - } - ] - }, - teaching: { - id: "teaching", - icon: "School", - label: "Teaching", - description: "Teaching workspace", - defaultNodeId: "teacher-root", - views: [ - { - id: "overview", - icon: "Dashboard", - label: "Overview", - description: "Teaching overview" - }, - { - id: "timetable", - icon: "Schedule", - label: "Timetable", - description: "View your teaching schedule" - }, - { - id: "classes", - icon: "Class", - label: "Classes", - description: "Manage your classes" - }, - { - id: "lessons", - icon: "Book", - label: "Lessons", - description: "Plan and view lessons" - }, - { - id: "journal", - icon: "Book", - label: "Journal", - description: "Your teaching journal" - }, - { - id: "planner", - icon: "Event", - label: "Planner", - description: "Plan your teaching activities" - } - ] - }, - // Institutional Contexts - school: { - id: "school", - icon: "Business", - label: "School", - description: "School management", - defaultNodeId: "school-root", - views: [ - { - id: "overview", - icon: "Dashboard", - label: "Overview", - description: "School overview" - }, - { - id: "departments", - icon: "AccountTree", - label: "Departments", - description: "View departments" - }, - { - id: "staff", - icon: "People", - label: "Staff", - description: "Staff directory" - } - ] - }, - department: { - id: "department", - icon: "AccountTree", - label: "Department", - description: "Department management", - defaultNodeId: "department-root", - views: [ - { - id: "overview", - icon: "Dashboard", - label: "Overview", - description: "Department overview" - }, - { - id: "teachers", - icon: "People", - label: "Teachers", - description: "Department teachers" - }, - { - id: "subjects", - icon: "Subject", - label: "Subjects", - description: "Department subjects" - } - ] - }, - class: { - id: "class", - icon: "Class", - label: "Class", - description: "Class management", - defaultNodeId: "class-root", - views: [ - { - id: "overview", - icon: "Dashboard", - label: "Overview", - description: "Class overview" - }, - { - id: "students", - icon: "People", - label: "Students", - description: "Class students" - }, - { - id: "timetable", - icon: "Schedule", - label: "Timetable", - description: "Class schedule" - } - ] - } -}; - -const NavigationRoot = styled(Box)` - display: flex; - align-items: center; - gap: 8px; - height: 100%; - overflow: hidden; -`; -const NavigationControls = styled(Box)` - display: flex; - align-items: center; - gap: 4px; -`; -const ContextToggleContainer = styled(Box)(({ theme }) => ({ - display: "flex", - alignItems: "center", - backgroundColor: theme.palette.action.hover, - borderRadius: theme.shape.borderRadius, - padding: theme.spacing(0.5), - gap: theme.spacing(0.5), - "& .button-label": { - "@media (max-width: 500px)": { - display: "none" - } - } -})); -const ContextToggleButton = styled(Button, { - shouldForwardProp: (prop) => prop !== "active" -})(({ theme, active }) => ({ - minWidth: 0, - padding: theme.spacing(0.5, 1.5), - borderRadius: theme.shape.borderRadius, - backgroundColor: active ? theme.palette.primary.main : "transparent", - color: active ? theme.palette.primary.contrastText : theme.palette.text.primary, - textTransform: "none", - transition: theme.transitions.create(["background-color", "color"], { - duration: theme.transitions.duration.shorter - }), - "&:hover": { - backgroundColor: active ? theme.palette.primary.dark : theme.palette.action.hover - }, - "@media (max-width: 500px)": { - padding: theme.spacing(0.5) - } -})); -const GraphNavigator = () => { - const { - context, - switchContext, - goBack, - goForward, - isLoading - } = useNavigationStore(); - const { userDbName, workerDbName, isInitialized: isNeoUserInitialized } = useNeoUser(); - const [contextMenuAnchor, setContextMenuAnchor] = reactExports.useState(null); - const [historyMenuAnchor, setHistoryMenuAnchor] = reactExports.useState(null); - const rootRef = reactExports.useRef(null); - const [availableWidth, setAvailableWidth] = reactExports.useState(0); - reactExports.useEffect(() => { - const calculateAvailableSpace = () => { - if (!rootRef.current) return; - const header = rootRef.current.closest(".MuiToolbar-root"); - if (!header) return; - const title = header.querySelector(".app-title"); - const menu = header.querySelector(".menu-button"); - if (!title || !menu) return; - const headerWidth = header.clientWidth; - const titleWidth = title.clientWidth; - const menuWidth = menu.clientWidth; - const padding = 48; - const newAvailableWidth = headerWidth - titleWidth - menuWidth - padding; - console.log("Available width:", newAvailableWidth); - setAvailableWidth(newAvailableWidth); - }; - const resizeObserver = new ResizeObserver(() => { - window.requestAnimationFrame(calculateAvailableSpace); - }); - if (rootRef.current) { - const header = rootRef.current.closest(".MuiToolbar-root"); - if (header) { - resizeObserver.observe(header); - resizeObserver.observe(rootRef.current); - } - } - calculateAvailableSpace(); - return () => { - resizeObserver.disconnect(); - }; - }, []); - const getVisibility = () => { - if (availableWidth < 300) { - return { - navigation: false, - contextLabel: true, - // Keep context label visible longer - toggleLabels: false - }; - } else if (availableWidth < 450) { - return { - navigation: false, - contextLabel: true, - // Keep context label visible - toggleLabels: true - }; - } else if (availableWidth < 600) { - return { - navigation: true, - contextLabel: true, - toggleLabels: true - }; - } - return { - navigation: true, - contextLabel: true, - toggleLabels: true - }; - }; - const visibility = getVisibility(); - const handleHistoryClick = (event) => { - setHistoryMenuAnchor(event.currentTarget); - }; - const handleHistoryClose = () => { - setHistoryMenuAnchor(null); - }; - const handleHistoryItemClick = (index) => { - const { currentIndex } = context.history; - const steps = index - currentIndex; - if (steps < 0) { - for (let i = 0; i < -steps; i++) { - goBack(); - } - } else if (steps > 0) { - for (let i = 0; i < steps; i++) { - goForward(); - } - } - handleHistoryClose(); - }; - const handleContextChange = reactExports.useCallback(async (newContext) => { - try { - if (["school", "department", "class"].includes(newContext) && !workerDbName) { - logger.error("navigation", "❌ Cannot switch to institute context: missing worker database"); - return; - } - if (["profile", "calendar", "teaching"].includes(newContext) && !userDbName) { - logger.error("navigation", "❌ Cannot switch to profile context: missing user database"); - return; - } - logger.debug("navigation", "🔄 Changing main context", { - from: context.main, - to: newContext, - userDbName, - workerDbName - }); - const defaultView = getDefaultViewForContext(newContext); - await switchContext({ - main: ["profile", "calendar", "teaching"].includes(newContext) ? "profile" : "institute", - base: newContext, - extended: defaultView, - skipBaseContextLoad: false - }, userDbName, workerDbName); - } catch (error) { - logger.error("navigation", "❌ Failed to change context:", error); - } - }, [context.main, switchContext, userDbName, workerDbName]); - const getDefaultViewForContext = (context2) => { - switch (context2) { - case "calendar": - return "overview"; - case "teaching": - return "overview"; - case "school": - return "overview"; - case "department": - return "overview"; - case "class": - return "overview"; - default: - return "overview"; - } - }; - const handleContextMenu = (event) => { - setContextMenuAnchor(event.currentTarget); - }; - const handleContextSelect = reactExports.useCallback(async (context2) => { - setContextMenuAnchor(null); - try { - const contextDef = NAVIGATION_CONTEXTS[context2]; - const defaultExtended = contextDef?.views[0]?.id; - await switchContext({ - base: context2, - extended: defaultExtended - }, userDbName, workerDbName); - } catch (error) { - logger.error("navigation", "❌ Failed to select context:", error); - } - }, [switchContext, userDbName, workerDbName]); - const getContextItems = reactExports.useCallback(() => { - if (context.main === "profile") { - return [ - { id: "profile", label: "Profile", icon: AccountCircleIcon }, - { id: "calendar", label: "Calendar", icon: CalendarIcon }, - { id: "teaching", label: "Teaching", icon: TeacherIcon } - ]; - } else { - return [ - { id: "school", label: "School", icon: BusinessIcon }, - { id: "department", label: "Department", icon: GraphIcon }, - { id: "class", label: "Class", icon: ClassIcon } - ]; - } - }, [context.main]); - const getContextIcon = reactExports.useCallback((contextType) => { - switch (contextType) { - case "profile": - return /* @__PURE__ */ jsxRuntimeExports.jsx(AccountCircleIcon, {}); - case "calendar": - return /* @__PURE__ */ jsxRuntimeExports.jsx(CalendarIcon, {}); - case "teaching": - return /* @__PURE__ */ jsxRuntimeExports.jsx(TeacherIcon, {}); - case "school": - return /* @__PURE__ */ jsxRuntimeExports.jsx(BusinessIcon, {}); - case "department": - return /* @__PURE__ */ jsxRuntimeExports.jsx(GraphIcon, {}); - case "class": - return /* @__PURE__ */ jsxRuntimeExports.jsx(ClassIcon, {}); - default: - return /* @__PURE__ */ jsxRuntimeExports.jsx(AccountCircleIcon, {}); - } - }, []); - const isDisabled = !isNeoUserInitialized || isLoading; - const { history } = context; - const canGoBack = history.currentIndex > 0; - const canGoForward = history.currentIndex < history.nodes.length - 1; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(NavigationRoot, { ref: rootRef, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs(NavigationControls, { sx: { display: visibility.navigation ? "flex" : "none" }, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Back", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: /* @__PURE__ */ jsxRuntimeExports.jsx( - IconButton, - { - onClick: goBack, - disabled: !canGoBack || isDisabled, - size: "small", - children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowBackIcon, { fontSize: "small" }) - } - ) }) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "History", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: /* @__PURE__ */ jsxRuntimeExports.jsx( - IconButton, - { - onClick: handleHistoryClick, - disabled: !history.nodes.length || isDisabled, - size: "small", - children: /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryIcon, { fontSize: "small" }) - } - ) }) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Forward", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: /* @__PURE__ */ jsxRuntimeExports.jsx( - IconButton, - { - onClick: goForward, - disabled: !canGoForward || isDisabled, - size: "small", - children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowForwardIcon, { fontSize: "small" }) - } - ) }) }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - Menu$1, - { - anchorEl: historyMenuAnchor, - open: Boolean(historyMenuAnchor), - onClose: handleHistoryClose, - anchorOrigin: { - vertical: "bottom", - horizontal: "center" - }, - transformOrigin: { - vertical: "top", - horizontal: "center" - }, - children: history.nodes.map((node, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs( - MenuItem, - { - onClick: () => handleHistoryItemClick(index), - selected: index === history.currentIndex, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: getContextIcon(node.type) }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - ListItemText, - { - primary: node.label || node.id, - secondary: node.type - } - ) - ] - }, - `${node.id}-${index}` - )) - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsxs(ContextToggleContainer, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - ContextToggleButton, - { - active: context.main === "profile", - onClick: () => handleContextChange("profile"), - startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(StudentIcon, {}), - disabled: isDisabled || !userDbName, - children: visibility.toggleLabels && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "button-label", children: "Profile" }) - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - ContextToggleButton, - { - active: context.main === "institute", - onClick: () => handleContextChange("school"), - startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(TeacherIcon, {}), - disabled: isDisabled || !workerDbName, - children: visibility.toggleLabels && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "button-label", children: "Institute" }) - } - ) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: context.base, children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs( - Button, - { - onClick: handleContextMenu, - disabled: isDisabled, - sx: { - minWidth: 0, - p: 0.5, - color: "text.primary", - "&:hover": { - bgcolor: "action.hover" - } - }, - children: [ - getContextIcon(context.base), - visibility.contextLabel && /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { ml: 1 }, children: context.base }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ExpandMoreIcon, { sx: { ml: visibility.contextLabel ? 0.5 : 0 } }) - ] - } - ) }) }) }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - Menu$1, - { - anchorEl: contextMenuAnchor, - open: Boolean(contextMenuAnchor), - onClose: () => setContextMenuAnchor(null), - children: getContextItems().map((item) => /* @__PURE__ */ jsxRuntimeExports.jsxs( - MenuItem, - { - onClick: () => handleContextSelect(item.id), - disabled: isDisabled, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(item.icon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemText, { primary: item.label }) - ] - }, - item.id - )) - } - ) - ] }); -}; - -const Header = () => { - const theme = useTheme(); - const navigate = useNavigate(); - const location = useLocation(); - const { user, signOut } = useAuth(); - const [anchorEl, setAnchorEl] = reactExports.useState(null); - const [isAuthenticated, setIsAuthenticated] = reactExports.useState(!!user); - const isAdmin = user?.email === "admin@classroomcopilot.ai"; - const showGraphNavigation = location.pathname === "/single-player"; - reactExports.useEffect(() => { - const newAuthState = !!user; - setIsAuthenticated(newAuthState); - logger.debug("user-context", "🔄 User state changed in header", { - hasUser: newAuthState, - userId: user?.id, - userEmail: user?.email, - userState: newAuthState ? "logged-in" : "logged-out", - isAdmin - }); - }, [user, isAdmin]); - const handleMenuOpen = (event) => { - setAnchorEl(event.currentTarget); - }; - const handleMenuClose = () => { - setAnchorEl(null); - }; - const handleNavigation = (path) => { - navigate(path); - handleMenuClose(); - }; - const handleSignupNavigation = (role) => { - navigate("/signup", { state: { role } }); - handleMenuClose(); - }; - const handleSignOut = async () => { - try { - logger.debug("auth-service", "🔄 Signing out user", { userId: user?.id }); - await signOut(); - setIsAuthenticated(false); - setAnchorEl(null); - logger.debug("auth-service", "✅ User signed out"); - } catch (error) { - logger.error("auth-service", "❌ Error signing out:", error); - console.error("Error signing out:", error); - } - }; - return /* @__PURE__ */ jsxRuntimeExports.jsx( - AppBar, - { - position: "fixed", - sx: { - height: `${HEADER_HEIGHT}px`, - bgcolor: theme.palette.background.paper, - color: theme.palette.text.primary, - boxShadow: 1 - }, - children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Toolbar$1, { sx: { - display: "flex", - justifyContent: "space-between", - minHeight: `${HEADER_HEIGHT}px !important`, - height: `${HEADER_HEIGHT}px`, - gap: 2, - px: { xs: 1, sm: 2 } - }, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { - display: "flex", - alignItems: "center", - gap: 2, - minWidth: { xs: "auto", sm: "200px" } - }, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - Typography, - { - variant: "h6", - component: "div", - className: "app-title", - sx: { - cursor: "pointer", - color: theme.palette.text.primary, - "&:hover": { - color: theme.palette.primary.main - }, - fontSize: { xs: "1rem", sm: "1.25rem" } - }, - onClick: () => navigate(isAuthenticated ? "/single-player" : "/"), - children: "ClassroomCopilot" - } - ) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { - position: "absolute", - left: "50%", - transform: "translateX(-50%)", - display: "flex", - justifyContent: "center", - visibility: showGraphNavigation ? "visible" : "hidden", - width: { - xs: "calc(100% - 160px)", - // More space for menu and title - sm: "calc(100% - 200px)", - // Standard spacing - md: "auto" - // Full width on medium and up - }, - maxWidth: "800px", - "& .navigation-controls": { - display: { xs: "none", sm: "flex" } - }, - "& .context-section": { - display: { xs: "none", md: "flex" } - }, - "& .context-toggle": { - display: "flex" - // Always show the profile/institute toggle - } - }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(GraphNavigator, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { - display: "flex", - justifyContent: "flex-end", - minWidth: { xs: "auto", sm: "200px" }, - ml: "auto" - }, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - IconButton, - { - className: "menu-button", - color: "inherit", - onClick: handleMenuOpen, - edge: "end", - sx: { - "&:hover": { - bgcolor: theme.palette.action.hover - } - }, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(default_1$d, {}) - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - Menu$1, - { - anchorEl, - open: Boolean(anchorEl), - onClose: handleMenuClose, - slotProps: { - paper: { - elevation: 3, - sx: { - bgcolor: theme.palette.background.paper, - color: theme.palette.text.primary, - minWidth: "240px" - } - } - }, - children: isAuthenticated ? [ - // Development Tools Section - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: () => handleNavigation("/tldraw-dev"), children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(TLDrawDevIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemText, { primary: "TLDraw Dev" }) - ] }, "tldraw"), - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: () => handleNavigation("/dev"), children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(DevToolsIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemText, { primary: "Dev Tools" }) - ] }, "dev"), - /* @__PURE__ */ jsxRuntimeExports.jsx(Divider, {}, "dev-divider"), - // Main Features Section - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: () => handleNavigation("/multiplayer"), children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(MultiplayerIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemText, { primary: "Multiplayer" }) - ] }, "multiplayer"), - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: () => handleNavigation("/calendar"), children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(CalendarIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemText, { primary: "Calendar" }) - ] }, "calendar"), - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: () => handleNavigation("/teacher-planner"), children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(ExamIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemText, { primary: "Teacher Planner" }) - ] }, "planner"), - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: () => handleNavigation("/exam-marker"), children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(ExamMarkerIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemText, { primary: "Exam Marker" }) - ] }, "exam"), - /* @__PURE__ */ jsxRuntimeExports.jsx(Divider, {}, "features-divider"), - // Utilities Section - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: () => handleNavigation("/settings"), children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(SettingsIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemText, { primary: "Settings" }) - ] }, "settings"), - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: () => handleNavigation("/search"), children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(SearchIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemText, { primary: "Search" }) - ] }, "search"), - // Admin Section - ...isAdmin ? [ - /* @__PURE__ */ jsxRuntimeExports.jsx(Divider, {}, "admin-divider"), - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: () => handleNavigation("/admin"), children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(AdminIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemText, { primary: "Admin Dashboard" }) - ] }, "admin") - ] : [], - // Authentication Section - /* @__PURE__ */ jsxRuntimeExports.jsx(Divider, {}, "auth-divider"), - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: handleSignOut, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(LogoutIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemText, { primary: "Sign Out" }) - ] }, "signout") - ] : [ - // Authentication Section for Non-authenticated Users - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: () => handleNavigation("/login"), children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(LoginIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemText, { primary: "Sign In" }) - ] }, "signin"), - /* @__PURE__ */ jsxRuntimeExports.jsx(Divider, {}, "signup-divider"), - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: () => handleSignupNavigation("teacher"), children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(TeacherIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - ListItemText, - { - primary: "Sign up as Teacher", - secondary: "Create a teacher account" - } - ) - ] }, "teacher-signup"), - /* @__PURE__ */ jsxRuntimeExports.jsxs(MenuItem, { onClick: () => handleSignupNavigation("student"), children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ListItemIcon, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(StudentIcon, {}) }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - ListItemText, - { - primary: "Sign up as Student", - secondary: "Create a student account" - } - ) - ] }, "student-signup") - ] - } - ) - ] }) - ] }) - } - ); -}; - -const HEADER_HEIGHT = 40; -const Layout = ({ children }) => { - return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(Header, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx("main", { className: "main-content", style: { - paddingTop: `${HEADER_HEIGHT}px`, - height: "100vh", - width: "100%" - }, children }) - ] }); -}; - -const EmailLoginForm = ({ role, onSubmit }) => { - const [email, setEmail] = reactExports.useState(""); - const [password, setPassword] = reactExports.useState(""); - const [error, setError] = reactExports.useState(null); - const [isLoading, setIsLoading] = reactExports.useState(false); - const handleSubmit = async (e) => { - e.preventDefault(); - setError(null); - setIsLoading(true); - try { - await onSubmit({ email, password, role }); - } catch (err) { - setError(err instanceof Error ? err.message : "Failed to login"); - } finally { - setIsLoading(false); - } - }; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { component: "form", onSubmit: handleSubmit, sx: { width: "100%" }, children: [ - error && /* @__PURE__ */ jsxRuntimeExports.jsx(Alert, { severity: "error", sx: { mb: 2 }, children: error }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TextField, - { - fullWidth: true, - label: "Email", - type: "email", - value: email, - onChange: (e) => setEmail(e.target.value), - margin: "normal", - required: true - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TextField, - { - fullWidth: true, - label: "Password", - type: "password", - value: password, - onChange: (e) => setPassword(e.target.value), - margin: "normal", - required: true - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - Button, - { - type: "submit", - fullWidth: true, - variant: "contained", - color: "primary", - disabled: isLoading, - sx: { mt: 3 }, - children: isLoading ? "Logging in..." : "Login" - } - ) - ] }); -}; - -const LoginPage = () => { - const navigate = useNavigate(); - const { user, signIn } = useAuth(); - const [error, setError] = reactExports.useState(null); - logger.debug("login-page", "🔍 Login page loaded", { - hasUser: !!user - }); - reactExports.useEffect(() => { - if (user) { - navigate("/single-player"); - } - }, [user, navigate]); - const handleLogin = async (credentials) => { - try { - setError(null); - await signIn(credentials.email, credentials.password); - navigate("/single-player"); - } catch (error2) { - logger.error("login-page", "❌ Login failed", error2); - setError(error2 instanceof Error ? error2.message : "Login failed"); - throw error2; - } - }; - if (user) { - return null; - } - return /* @__PURE__ */ jsxRuntimeExports.jsxs( - Container, - { - sx: { - display: "flex", - flexDirection: "column", - alignItems: "center", - justifyContent: "center", - minHeight: "100vh", - gap: 4 - }, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h2", component: "h1", gutterBottom: true, children: "ClassroomCopilot.ai" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h4", gutterBottom: true, children: "Login" }), - error && /* @__PURE__ */ jsxRuntimeExports.jsx(Alert, { severity: "error", sx: { width: "100%", maxWidth: 400 }, children: error }), - /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { width: "100%", maxWidth: 400 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - EmailLoginForm, - { - role: "email_teacher", - onSubmit: handleLogin - } - ) }) - ] - } - ); -}; - -const EmailSignupForm = ({ - role, - onSubmit -}) => { - const [email, setEmail] = reactExports.useState(""); - const [password, setPassword] = reactExports.useState(""); - const [confirmPassword, setConfirmPassword] = reactExports.useState(""); - const [displayName, setDisplayName] = reactExports.useState(""); - const [error, setError] = reactExports.useState(null); - const [isLoading, setIsLoading] = reactExports.useState(false); - const validateForm = () => { - if (!email || !password || !confirmPassword || !displayName) { - return "All fields are required"; - } - if (password !== confirmPassword) { - return "Passwords do not match"; - } - if (password.length < 6) { - return "Password must be at least 6 characters"; - } - if (!email.includes("@")) { - return "Please enter a valid email address"; - } - return null; - }; - const handleSubmit = async (e) => { - e.preventDefault(); - const validationError = validateForm(); - if (validationError) { - setError(validationError); - return; - } - setError(null); - setIsLoading(true); - try { - logger.debug("email-signup-form", "🔄 Submitting signup form", { - email, - role, - hasDisplayName: !!displayName - }); - await onSubmit({ email, password, role }, displayName); - } catch (err) { - setError( - err instanceof Error ? err.message : "An error occurred during signup" - ); - logger.error( - "email-signup-form", - "❌ Signup form submission failed", - err - ); - } finally { - setIsLoading(false); - } - }; - return /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { component: "form", onSubmit: handleSubmit, noValidate: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Stack, { spacing: 2, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - TextField, - { - required: true, - fullWidth: true, - id: "displayName", - label: "Display Name", - name: "displayName", - autoComplete: "name", - value: displayName, - onChange: (e) => setDisplayName(e.target.value), - autoFocus: true - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TextField, - { - required: true, - fullWidth: true, - id: "email", - label: "Email Address", - name: "email", - autoComplete: "email", - value: email, - onChange: (e) => setEmail(e.target.value) - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TextField, - { - required: true, - fullWidth: true, - name: "password", - label: "Password", - type: "password", - id: "password", - autoComplete: "new-password", - value: password, - onChange: (e) => setPassword(e.target.value) - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TextField, - { - required: true, - fullWidth: true, - name: "confirmPassword", - label: "Confirm Password", - type: "password", - id: "confirmPassword", - autoComplete: "new-password", - value: confirmPassword, - onChange: (e) => setConfirmPassword(e.target.value) - } - ), - error && /* @__PURE__ */ jsxRuntimeExports.jsx(Alert, { severity: "error", children: error }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - Button, - { - type: "submit", - fullWidth: true, - variant: "contained", - disabled: isLoading || !email || !password || !confirmPassword || !displayName, - children: isLoading ? "Signing up..." : "Sign Up" - } - ) - ] }) }); -}; - -const DEV_SCHOOL_UUID = "kevlarai"; -class NeoRegistrationService { - static instance; - constructor() { - } - static getInstance() { - if (!NeoRegistrationService.instance) { - NeoRegistrationService.instance = new NeoRegistrationService(); - } - return NeoRegistrationService.instance; - } - async registerNeo4JUser(user, username, role) { - try { - let schoolNode = null; - if (role.includes("teacher") || role.includes("student")) { - schoolNode = await this.fetchSchoolNode(DEV_SCHOOL_UUID); - if (!schoolNode) { - throw new Error("Failed to fetch required school node"); - } - } - const formData = new FormData(); - formData.append("user_id", user.id); - formData.append("user_type", role); - formData.append("user_name", username); - formData.append("user_email", user.email || ""); - if (schoolNode) { - formData.append("school_uuid_string", schoolNode.uuid_string); - formData.append("school_name", schoolNode.name); - formData.append("school_website", schoolNode.website); - formData.append("school_node_storage_path", schoolNode.node_storage_path); - const workerData = role.includes("teacher") ? { - teacher_code: username, - teacher_name_formal: username, - teacher_email: user.email - } : { - student_code: username, - student_name_formal: username, - student_email: user.email - }; - formData.append("worker_data", JSON.stringify(workerData)); - } - logger.debug("neo4j-service", "🔄 Sending form data", { - userId: user.id, - userType: role, - userName: username, - userEmail: user.email, - schoolNode: schoolNode ? { - uuid_string: schoolNode.uuid_string, - name: schoolNode.name - } : null - }); - const response = await axios.post("/database/entity/create-user", formData, { - headers: { - "Content-Type": "multipart/form-data" - } - }); - if (response.data.status !== "success") { - throw new Error(`Failed to create user: ${JSON.stringify(response.data)}`); - } - const userNode = response.data.data.user_node; - const workerNode = response.data.data.worker_node; - if (response.data.data.calendar_nodes) { - logger.debug("neo4j-service", "🔄 Storing calendar data", { - calendarNodes: response.data.data.calendar_nodes - }); - storageService.set(StorageKeys.CALENDAR_DATA, response.data.data.calendar_nodes); - } - userNode.worker_node_data = JSON.stringify(workerNode); - await this.updateUserNeo4jDetails(user.id, userNode); - logger.info("neo4j-service", "✅ Neo4j user registration successful", { - userId: user.id, - nodeId: userNode.uuid_string, - hasCalendar: !!response.data.data.calendar_nodes - }); - return userNode; - } catch (error) { - logger.error("neo4j-service", "❌ Neo4j user registration failed", error); - throw error; - } - } - async updateUserNeo4jDetails(userId, userNode) { - const { error } = await supabase.from("profiles").update({ - metadata: { - ...userNode - }, - updated_at: (/* @__PURE__ */ new Date()).toISOString() - }).eq("id", userId); - if (error) { - logger.error("neo4j-service", "❌ Failed to update Neo4j details:", error); - throw error; - } - } - async fetchSchoolNode(schoolUrn) { - logger.debug("neo4j-service", "🔄 Fetching school node", { schoolUrn }); - try { - const response = await axios.get(`/database/tools/get-school-node?school_urn=${schoolUrn}`); - if (response.data?.status === "success" && response.data.school_node) { - logger.info("neo4j-service", "✅ School node fetched successfully"); - return response.data.school_node; - } - throw new Error("Failed to fetch school node: " + JSON.stringify(response.data)); - } catch (error) { - logger.error("neo4j-service", "❌ Failed to fetch school node:", error); - throw error; - } - } -} -const neoRegistrationService = NeoRegistrationService.getInstance(); - -const REGISTRATION_SERVICE = "registration-service"; -class RegistrationService { - static instance; - constructor() { - } - static getInstance() { - if (!RegistrationService.instance) { - RegistrationService.instance = new RegistrationService(); - } - return RegistrationService.instance; - } - async register(credentials, displayName) { - try { - logger.debug(REGISTRATION_SERVICE, "🔄 Starting registration", { - email: credentials.email, - role: credentials.role, - hasDisplayName: !!displayName - }); - const username = formatEmailForDatabase(credentials.email); - const { data: authData, error: signUpError } = await supabase.auth.signUp({ - email: credentials.email, - password: credentials.password, - options: { - data: { - user_type: credentials.role, - username, - display_name: displayName - } - } - }); - if (signUpError) { - logger.error(REGISTRATION_SERVICE, "❌ Supabase signup error", { error: signUpError }); - throw signUpError; - } - if (!authData.user) { - logger.error(REGISTRATION_SERVICE, "❌ No user data after registration"); - throw new Error("No user data after registration"); - } - const ccUser = convertToCCUser(authData.user, authData.user.user_metadata); - const { error: updateError } = await supabase.from("profiles").update({ - user_type: credentials.role, - username, - display_name: displayName - }).eq("id", authData.user.id).select().single(); - if (updateError) { - logger.error(REGISTRATION_SERVICE, "❌ Failed to update profile", updateError); - throw updateError; - } - storageService.set(StorageKeys.IS_NEW_REGISTRATION, true); - try { - const userNode = await neoRegistrationService.registerNeo4JUser( - ccUser, - username, - // Pass username for database operations - credentials.role - ); - logger.info(REGISTRATION_SERVICE, "✅ Registration successful with Neo4j setup", { - userId: ccUser.id, - hasUserNode: !!userNode - }); - return { - user: ccUser, - accessToken: authData.session?.access_token || null, - userRole: credentials.role, - message: "Registration successful" - }; - } catch (neo4jError) { - logger.warn(REGISTRATION_SERVICE, "âš ī¸ Neo4j setup problem", { - userId: ccUser.id, - error: neo4jError - }); - return { - user: ccUser, - accessToken: authData.session?.access_token || null, - userRole: credentials.role, - message: "Registration successful - Neo4j setup pending" - }; - } - } catch (error) { - logger.error(REGISTRATION_SERVICE, "❌ Registration failed:", error); - throw error; - } - } -} -RegistrationService.getInstance(); - -var Microsoft = {}; - -var _interopRequireDefault$c = interopRequireDefaultExports; -Object.defineProperty(Microsoft, "__esModule", { - value: true -}); -var default_1$c = Microsoft.default = void 0; -_interopRequireWildcard(reactExports); -var _createSvgIcon$c = _interopRequireDefault$c(requireCreateSvgIcon()); -var _jsxRuntime$c = jsxRuntimeExports; -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -var _default$c = (0, _createSvgIcon$c.default)( /*#__PURE__*/(0, _jsxRuntime$c.jsx)("path", { - d: "M2 3h9v9H2V3m9 19H2v-9h9v9M21 3v9h-9V3h9m0 19h-9v-9h9v9Z" -}), 'Microsoft'); -default_1$c = Microsoft.default = _default$c; - -const SignupPage = () => { - const location = useLocation(); - const navigate = useNavigate(); - const { user } = useAuth(); - const registrationService = RegistrationService.getInstance(); - const { role = "teacher" } = location.state || {}; - const roleDisplay = role === "teacher" ? "Teacher" : "Student"; - logger.debug("signup-page", "🔍 Signup page loaded", { - role, - hasUser: !!user - }); - reactExports.useEffect(() => { - if (user) { - navigate("/single-player"); - } - }, [user, navigate]); - const handleSignup = async (credentials, displayName) => { - try { - const result = await registrationService.register( - credentials, - displayName - ); - if (result.user) { - navigate("/single-player"); - } - } catch (error) { - logger.error("signup-page", "❌ Registration failed", error); - throw error; - } - }; - const switchRole = () => { - navigate("/signup", { - state: { role: role === "teacher" ? "student" : "teacher" } - }); - }; - if (user) { - return null; - } - return /* @__PURE__ */ jsxRuntimeExports.jsxs( - Container, - { - sx: { - display: "flex", - flexDirection: "column", - alignItems: "center", - justifyContent: "center", - minHeight: "100vh", - gap: 4 - }, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(Typography, { variant: "h2", component: "h1", gutterBottom: true, children: "ClassroomCopilot.ai" }), - /* @__PURE__ */ jsxRuntimeExports.jsxs(Typography, { variant: "h4", gutterBottom: true, children: [ - roleDisplay, - " Sign Up" - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { width: "100%", maxWidth: 400 }, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - Button, - { - fullWidth: true, - variant: "outlined", - startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(default_1$c, {}), - onClick: () => { - }, - sx: { mb: 3 }, - children: "Sign up with Microsoft" - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx(Divider, { sx: { my: 2 }, children: "OR" }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - EmailSignupForm, - { - role: `email_${role}`, - onSubmit: handleSignup - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - Stack, - { - direction: "row", - spacing: 2, - justifyContent: "center", - sx: { mt: 3 }, - children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "text", onClick: switchRole, children: [ - "Switch to ", - role === "teacher" ? "Student" : "Teacher", - " Sign Up" - ] }) - } - ) - ] }) - ] - } - ); -}; - -const CC_BASE_STYLE_CONSTANTS = { - // Container styles - CONTAINER: { - borderRadius: "4px", - borderWidth: "2px", - boxShadow: "0 2px 4px var(--color-muted-1)" - }, - HEADER: { - height: 32, - padding: 8}, - CONTENT: { - padding: 8, - backgroundColor: "white" - }, - COLORS: { - primary: "#3e6589", - border: "#e2e8f0"}, - // Minimum dimensions - MIN_DIMENSIONS: { - width: 100, - height: 100 - } -}; -const CC_CALENDAR_STYLE_CONSTANTS = { - // Calendar event styles - EVENT: { - mainFrame: { - backgroundColor: "transparent", - padding: "0px", - display: "flex", - alignItems: "center", - justifyContent: "center", - minHeight: "100%", - borderRadius: "4px" - }, - title: { - fontSize: "1.1em", - fontWeight: "normal", - textAlign: "center", - overflow: "hidden", - textOverflow: "ellipsis", - whiteSpace: "nowrap", - opacity: 1, - padding: "0px 0px", - width: "100%", - letterSpacing: "0.02em", - margin: "0px 0px" - } - } -}; -const CC_SLIDESHOW_STYLE_CONSTANTS = { - DEFAULT_SLIDE_WIDTH: 1280, - DEFAULT_SLIDE_HEIGHT: 720, - SLIDE_HEADER_HEIGHT: 32, - SLIDE_HEADER_PADDING: 8, - SLIDE_CONTENT_PADDING: 0, - SLIDE_BORDER_RADIUS: 4, - SLIDE_BORDER_WIDTH: 1, - SLIDE_SPACING: 16, - SLIDE_COLORS: { - background: "#ffffff", - border: "#e2e8f0", - text: "#ffffff", - secondary: "#718096" - } -}; - -class CCBaseShapeUtil extends BaseBoxShapeUtil { - indicator(shape) { - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "rect", - { - width: shape.props.w, - height: shape.props.h, - fill: "none", - rx: CC_BASE_STYLE_CONSTANTS.CONTAINER.borderRadius, - stroke: CC_BASE_STYLE_CONSTANTS.COLORS.border, - strokeWidth: CC_BASE_STYLE_CONSTANTS.CONTAINER.borderWidth - } - ); - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getToolbarItems(shape) { - return []; - } - onAfterCreate(shape) { - logger.info("cc-base-shape-util", "onAfterCreate", shape); - return shape; - } - component(shape) { - const { - props: { w, h, isLocked } - } = shape; - const toolbarItems = this.getToolbarItems(shape); - return /* @__PURE__ */ jsxRuntimeExports.jsxs( - HTMLContainer, - { - id: shape.id, - style: { - width: toDomPrecision(w), - height: toDomPrecision(h), - backgroundColor: shape.props.headerColor, - borderRadius: CC_BASE_STYLE_CONSTANTS.CONTAINER.borderRadius, - boxShadow: CC_BASE_STYLE_CONSTANTS.CONTAINER.boxShadow, - overflow: "hidden", - position: "relative" - }, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs( - "div", - { - style: { - backgroundColor: shape.props.headerColor, - padding: CC_BASE_STYLE_CONSTANTS.HEADER.padding, - height: CC_BASE_STYLE_CONSTANTS.HEADER.height, - display: "flex", - justifyContent: "space-between", - alignItems: "center", - cursor: isLocked ? "not-allowed" : "move", - pointerEvents: "all", - position: "relative", - zIndex: 1 - }, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "white", fontWeight: "bold" }, children: shape.props.title }), - /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", gap: "4px", alignItems: "center", pointerEvents: "all" }, children: [ - toolbarItems.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsx( - "button", - { - title: item.label, - onClick: (e) => { - logger.info("cc-base-shape-util", "toolbar item clicked", item.id); - e.preventDefault(); - e.stopPropagation(); - item.onClick(e, shape); - }, - onPointerDown: (e) => { - logger.info("cc-base-shape-util", "toolbar item pointer down", item.id); - e.preventDefault(); - e.stopPropagation(); - }, - style: { - background: "transparent", - border: "none", - padding: "4px", - cursor: "pointer", - color: "white", - opacity: item.isActive ? 1 : 0.7, - display: "flex", - alignItems: "center", - justifyContent: "center", - pointerEvents: "all", - fontSize: "16px", - width: "24px", - height: "24px", - zIndex: 100, - userSelect: "none", - position: "relative", - touchAction: "none" - }, - children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { pointerEvents: "none" }, children: item.icon }) - }, - item.id - )), - isLocked && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "white" }, children: "🔒" }) - ] }) - ] - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - "div", - { - style: { - position: "absolute", - top: CC_BASE_STYLE_CONSTANTS.HEADER.height, - left: 0, - right: 0, - bottom: 0, - overflow: "auto", - padding: CC_BASE_STYLE_CONSTANTS.CONTENT.padding, - backgroundColor: shape.props.backgroundColor - }, - children: this.renderContent(shape) - } - ) - ] - } - ); - } -} - -const baseShapeProps = { - title: string, - w: number, - h: number, - headerColor: string, - backgroundColor: string, - isLocked: boolean -}; -const ccShapeProps = { - calendar: { - ...baseShapeProps, - date: string, - selectedDate: string, - view: string, - events: arrayOf(object$1({ - id: string, - title: string, - start: string, - end: string, - groupId: string.optional(), - extendedProps: object$1({ - subjectClass: string, - color: string, - periodCode: string, - node_storage_path: string.optional() - }) - })) - }, - liveTranscription: { - ...baseShapeProps, - isRecording: boolean, - segments: arrayOf(object$1({ - id: string, - text: string, - completed: boolean, - start: string, - end: string - })), - currentSegment: object$1({ - id: string, - text: string, - completed: boolean, - start: string, - end: string - }).optional(), - lastProcessedSegment: string.optional() - }, - settings: { - ...baseShapeProps, - userEmail: string, - user_role: string, - isTeacher: boolean - }, - slideshow: { - ...baseShapeProps, - currentSlideIndex: number, - slidePattern: string, - numSlides: number, - slides: arrayOf(object$1({ - imageData: string, - meta: object$1({ - text: string, - format: string - }) - })).optional() - }, - slide: { - ...baseShapeProps, - imageData: string, - meta: object$1({ - text: string, - format: string - }) - }, - "cc-youtube-embed": { - ...baseShapeProps, - video_url: string, - transcript: arrayOf(object$1({ - start: number, - duration: number, - text: string - })), - transcriptVisible: boolean - }, - search: { - ...baseShapeProps, - query: string, - results: arrayOf(object$1({ - title: string, - url: string, - content: string - })), - isSearching: boolean - }, - webBrowser: { - ...baseShapeProps, - url: string, - history: arrayOf(string), - currentHistoryIndex: number, - isLoading: boolean - } -}; -({ - "cc-slide-layout": { - isMovingWithParent: boolean.optional(), - placeholder: boolean.optional()} -}); -const getDefaultCCBaseProps = () => ({ - title: "Base Shape", - w: 100, - h: 100, - headerColor: "#3e6589", - backgroundColor: "#ffffff", - isLocked: false -}); -const getDefaultCCCalendarProps = () => ({ - ...getDefaultCCBaseProps(), - date: (/* @__PURE__ */ new Date()).toISOString(), - selectedDate: (/* @__PURE__ */ new Date()).toISOString(), - view: "timeGridWeek", - events: [] -}); -const getDefaultCCLiveTranscriptionProps = () => ({ - ...getDefaultCCBaseProps(), - isRecording: false, - segments: [], - currentSegment: void 0, - lastProcessedSegment: void 0 -}); -const getDefaultCCSettingsProps = () => ({ - ...getDefaultCCBaseProps(), - userEmail: "", - user_role: "", - isTeacher: false -}); -function getDefaultCCSlideShowProps() { - const baseWidth = 1280; - const baseHeight = 720; - const totalHeight = baseHeight + CC_SLIDESHOW_STYLE_CONSTANTS.SLIDE_HEADER_HEIGHT + // Slideshow's own header - CC_SLIDESHOW_STYLE_CONSTANTS.SLIDE_SPACING * 2 + // Top and bottom spacing - CC_SLIDESHOW_STYLE_CONSTANTS.SLIDE_CONTENT_PADDING; - return { - title: "Slideshow", - w: baseWidth, - h: totalHeight, - headerColor: "#3e6589", - backgroundColor: "#0f0f0f", - isLocked: false, - currentSlideIndex: 0, - slidePattern: "horizontal", - numSlides: 3, - slides: [] - }; -} -function getDefaultCCSlideProps() { - const baseWidth = 1280; - const baseHeight = 720; - const totalHeight = baseHeight + CC_BASE_STYLE_CONSTANTS.HEADER.height; - return { - title: "Slide", - w: baseWidth, - h: totalHeight, - headerColor: "#3e6589", - backgroundColor: "#0f0f0f", - isLocked: false, - imageData: "", - meta: { - text: "", - format: "markdown" - } - }; -} -function getDefaultCCYoutubeEmbedProps() { - const videoHeight = 450; - const totalHeight = videoHeight + CC_BASE_STYLE_CONSTANTS.HEADER.height + CC_BASE_STYLE_CONSTANTS.CONTENT.padding * 2; - return { - ...getDefaultCCBaseProps(), - title: "YouTube Video", - w: 800, - h: totalHeight, - headerColor: "#ff0000", - backgroundColor: "#0f0f0f", - isLocked: false, - video_url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - transcript: [], - transcriptVisible: false - }; -} -const getDefaultCCSearchProps = () => ({ - ...getDefaultCCBaseProps(), - w: 400, - h: 500, - title: "Search", - headerColor: "#1a73e8", - backgroundColor: "#ffffff", - query: "", - results: [], - isSearching: false -}); -const getDefaultCCWebBrowserProps = () => ({ - ...getDefaultCCBaseProps(), - title: "Web Browser", - w: 800, - h: 600, - headerColor: "#1a73e8", - backgroundColor: "#ffffff", - url: "", - history: [], - currentHistoryIndex: -1, - isLoading: false -}); - -const ccShapeMigrations = { - calendar: { - firstVersion: 1, - currentVersion: 1, - migrators: { - 1: { - up: (record) => { - if (record.typeName !== "shape") return record; - const shape = record; - if (shape.type !== "cc-calendar") return record; - return { - ...shape, - props: { - ...getDefaultCCCalendarProps(), - ...shape.props - } - }; - }, - down: (record) => { - return record; - } - } - } - }, - liveTranscription: { - firstVersion: 1, - currentVersion: 1, - migrators: { - 1: { - up: (record) => { - if (record.typeName !== "shape") return record; - const shape = record; - if (shape.type !== "cc-live-transcription") return record; - return { - ...shape, - props: { - ...getDefaultCCLiveTranscriptionProps(), - ...shape.props - } - }; - }, - down: (record) => { - return record; - } - } - } - }, - settings: { - firstVersion: 1, - currentVersion: 1, - migrators: { - 1: { - up: (record) => { - if (record.typeName !== "shape") return record; - const shape = record; - if (shape.type !== "cc-settings") return record; - return { - ...shape, - props: { - ...getDefaultCCSettingsProps(), - ...shape.props - } - }; - }, - down: (record) => { - return record; - } - } - } - }, - slideshow: { - firstVersion: 1, - currentVersion: 1, - migrators: { - 1: { - up: (record) => { - if (record.typeName !== "shape") return record; - const shape = record; - if (shape.type !== "cc-slideshow") return record; - return { - ...shape, - props: { - ...getDefaultCCSlideShowProps(), - ...shape.props - } - }; - }, - down: (record) => { - return record; - } - } - } - }, - slide: { - firstVersion: 1, - currentVersion: 1, - migrators: { - 1: { - up: (record) => { - if (record.typeName !== "shape") return record; - const shape = record; - if (shape.type !== "cc-slide") return record; - return { - ...shape, - props: { - ...getDefaultCCSlideProps(), - ...shape.props - } - }; - }, - down: (record) => { - return record; - } - } - } - }, - search: { - firstVersion: 1, - currentVersion: 1, - migrators: { - 1: { - up: (record) => { - if (record.typeName !== "shape") return record; - const shape = record; - if (shape.type !== "cc-search") return record; - return { - ...shape, - props: { - ...getDefaultCCSearchProps(), - ...shape.props - } - }; - }, - down: (record) => { - return record; - } - } - } - }, - webBrowser: { - firstVersion: 1, - currentVersion: 1, - migrators: { - 1: { - up: (record) => { - if (record.typeName !== "shape") return record; - const shape = record; - if (shape.type !== "cc-web-browser") return record; - return { - ...shape, - props: { - ...getDefaultCCWebBrowserProps(), - ...shape.props - } - }; - }, - down: (record) => { - return record; - } - } - } - } -}; - -class CCSlideShowShapeUtil extends CCBaseShapeUtil { - static type = "cc-slideshow"; - static props = ccShapeProps.slideshow; - static migrations = ccShapeMigrations.slideshow; - static styles = { - color: DefaultColorStyle, - dash: DefaultDashStyle, - size: DefaultSizeStyle - }; - getDefaultProps() { - return getDefaultCCSlideShowProps(); - } - canResize = () => false; - isAspectRatioLocked = () => true; - hideResizeHandles = () => false; - hideRotateHandle = () => false; - canEdit = () => false; - // eslint-disable-next-line @typescript-eslint/no-unused-vars - canBind(args) { - return true; - } - onBeforeCreate(shape) { - return shape; - } - renderContent = () => { - return /* @__PURE__ */ jsxRuntimeExports.jsx("div", {}); - }; -} - -class CCSlideShapeUtil extends CCBaseShapeUtil { - static type = "cc-slide"; - static props = ccShapeProps.slide; - static migrations = ccShapeMigrations.slide; - static styles = { - color: DefaultColorStyle, - dash: DefaultDashStyle, - size: DefaultSizeStyle - }; - getDefaultProps() { - return getDefaultCCSlideProps(); - } - canResize = () => false; - isAspectRatioLocked = () => true; - hideResizeHandles = () => true; - hideRotateHandle = () => true; - canEdit = () => false; - canBind(args) { - return args.fromShapeType === "cc-slideshow" && args.toShapeType === "cc-slide" && args.bindingType === "cc-slide-layout"; - } - getTargetSlideshow(shape, pageAnchor) { - return this.editor.getShapeAtPoint(pageAnchor, { - hitInside: true, - filter: (otherShape) => this.editor.canBindShapes({ fromShape: otherShape, toShape: shape, binding: "cc-slide-layout" }) - }); - } - getBindingIndexForPosition(shape, slideshow, pageAnchor) { - const allBindings = this.editor.getBindingsFromShape(slideshow, "cc-slide-layout").filter((b) => !b.props.placeholder || b.toId === shape.id).sort((a, b) => a.props.index > b.props.index ? 1 : -1); - const spacing = CC_SLIDESHOW_STYLE_CONSTANTS.SLIDE_SPACING; - const headerHeight = CC_BASE_STYLE_CONSTANTS.HEADER.height; - const contentPadding = CC_SLIDESHOW_STYLE_CONSTANTS.SLIDE_CONTENT_PADDING; - let order; - if (slideshow.props.slidePattern === "horizontal") { - order = clamp$1( - Math.round((pageAnchor.x - slideshow.x - spacing) / (shape.props.w + spacing)), - 0, - allBindings.length - ); - } else if (slideshow.props.slidePattern === "vertical") { - order = clamp$1( - Math.round((pageAnchor.y - slideshow.y - headerHeight - contentPadding - spacing) / (shape.props.h + spacing)), - 0, - allBindings.length - ); - } else if (slideshow.props.slidePattern === "grid") { - const cols = Math.ceil(Math.sqrt(allBindings.length)); - const col = clamp$1( - Math.round((pageAnchor.x - slideshow.x - spacing) / (shape.props.w + spacing)), - 0, - cols - ); - const row = clamp$1( - Math.round((pageAnchor.y - slideshow.y - headerHeight - contentPadding - spacing) / (shape.props.h + spacing)), - 0, - Math.ceil(allBindings.length / cols) - ); - order = clamp$1(row * cols + col, 0, allBindings.length); - } else { - order = 0; - } - const belowSib = allBindings[order - 1]; - const aboveSib = allBindings[order]; - if (belowSib?.toId === shape.id) { - return belowSib.props.index; - } else if (aboveSib?.toId === shape.id) { - return aboveSib.props.index; - } - return getIndexBetween(belowSib?.props.index, aboveSib?.props.index); - } - onTranslateStart = (shape) => { - const bindings = this.editor.getBindingsToShape(shape.id, "cc-slide-layout"); - logger.debug("shape", "✅ onTranslateStart", { - shape, - bindings, - hasBindings: bindings.length > 0, - bindingTypes: bindings.map((b) => ({ - id: b.id, - fromId: b.fromId, - placeholder: b.props.placeholder, - isMovingWithParent: b.props.isMovingWithParent - })) - }); - this.editor.updateBindings( - bindings.map((binding) => ({ - ...binding, - props: { ...binding.props, placeholder: true } - })) - ); - }; - onTranslate = (initial, current) => { - const pageAnchor = this.editor.getShapePageTransform(current).applyToPoint({ x: current.props.w / 2, y: current.props.h / 2 }); - const targetSlideshow = this.getTargetSlideshow(current, pageAnchor); - const currentBindings = this.editor.getBindingsToShape(current.id, "cc-slide-layout"); - const currentBinding = currentBindings[0]; - const currentSlideshow = currentBinding ? this.editor.getShape(currentBinding.fromId) : void 0; - logger.debug("shape", "✅ onTranslate", { - initial, - current, - hasTargetSlideshow: !!targetSlideshow, - targetSlideshowId: targetSlideshow?.id, - currentBindings: currentBindings.map((b) => ({ - id: b.id, - fromId: b.fromId, - placeholder: b.props.placeholder, - isMovingWithParent: b.props.isMovingWithParent - })), - isInSlideshow: targetSlideshow ? this.isSlideInSlideshow(current, targetSlideshow) : false - }); - if (currentBinding && currentSlideshow && !this.isSlideInSlideshow(current, currentSlideshow)) { - logger.debug("shape", "✅ onTranslate: Moving out of slideshow", { - slideId: current.id, - slideshowId: currentSlideshow.id - }); - this.editor.deleteBindings(currentBindings); - return current; - } - if (!targetSlideshow) { - return current; - } - const index = this.getBindingIndexForPosition(current, targetSlideshow, pageAnchor); - if (currentBinding && currentBinding.fromId === targetSlideshow.id) { - if (currentBinding.props.index !== index) { - logger.debug("shape", "✅ onTranslate: Updating binding index", { - slideId: current.id, - slideshowId: targetSlideshow.id, - oldIndex: currentBinding.props.index, - newIndex: index - }); - this.editor.updateBinding({ - id: currentBinding.id, - type: currentBinding.type, - fromId: currentBinding.fromId, - toId: currentBinding.toId, - props: { - ...currentBinding.props, - index, - isMovingWithParent: true - } - }); - } - } else if (this.isSlideInSlideshow(current, targetSlideshow)) { - logger.debug("shape", "✅ onTranslate: Creating new placeholder binding", { - slideId: current.id, - slideshowId: targetSlideshow.id, - index - }); - this.editor.createBinding({ - type: "cc-slide-layout", - fromId: targetSlideshow.id, - toId: current.id, - props: { - index, - isMovingWithParent: true, - placeholder: true - } - }); - } - return current; - }; - isSlideInSlideshow(slide, slideshow) { - const slideCenter = this.editor.getShapePageTransform(slide).applyToPoint({ - x: slide.props.w / 2, - y: slide.props.h / 2 - }); - const slideshowBounds = this.editor.getShapeGeometry(slideshow).bounds; - const padding = CC_SLIDESHOW_STYLE_CONSTANTS.SLIDE_SPACING / 4; - return slideCenter.x >= slideshow.x + padding && slideCenter.x <= slideshow.x + slideshowBounds.width - padding && slideCenter.y >= slideshow.y + padding && slideCenter.y <= slideshow.y + slideshowBounds.height - padding; - } - onTranslateEnd = (shape) => { - const pageAnchor = this.editor.getShapePageTransform(shape).applyToPoint({ x: shape.props.w / 2, y: shape.props.h / 2 }); - const targetSlideshow = this.getTargetSlideshow(shape, pageAnchor); - const bindings = this.editor.getBindingsToShape(shape.id, "cc-slide-layout"); - logger.debug("shape", "✅ onTranslateEnd", { - shape, - hasTargetSlideshow: !!targetSlideshow, - targetSlideshowId: targetSlideshow?.id, - bindings: bindings.map((b) => ({ - id: b.id, - fromId: b.fromId, - placeholder: b.props.placeholder, - isMovingWithParent: b.props.isMovingWithParent - })), - isInSlideshow: targetSlideshow ? this.isSlideInSlideshow(shape, targetSlideshow) : false - }); - if (targetSlideshow && this.isSlideInSlideshow(shape, targetSlideshow)) { - const index = this.getBindingIndexForPosition(shape, targetSlideshow, pageAnchor); - const existingBinding = bindings[0]; - if (existingBinding && existingBinding.fromId === targetSlideshow.id) { - logger.debug("shape", "✅ onTranslateEnd: Updating existing binding", { - slideId: shape.id, - slideshowId: targetSlideshow.id, - bindingId: existingBinding.id, - index - }); - this.editor.updateBinding({ - id: existingBinding.id, - type: existingBinding.type, - fromId: existingBinding.fromId, - toId: existingBinding.toId, - props: { - index, - isMovingWithParent: true, - placeholder: false - } - }); - } else { - logger.debug("shape", "✅ onTranslateEnd: Creating new binding", { - slideId: shape.id, - slideshowId: targetSlideshow.id, - index - }); - this.editor.deleteBindings(bindings); - this.editor.createBinding({ - type: "cc-slide-layout", - fromId: targetSlideshow.id, - toId: shape.id, - props: { - index, - isMovingWithParent: true, - placeholder: false - } - }); - } - } else { - logger.debug("shape", "✅ onTranslateEnd: Removing bindings", { - slideId: shape.id, - bindings: bindings.map((b) => b.id) - }); - this.editor.deleteBindings(bindings); - } - }; - renderContent = (shape) => { - return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { - width: "100%", - height: "100%", - position: "relative", - backgroundColor: "white", - borderRadius: "4px", - overflow: "hidden" - }, children: shape.props.imageData && /* @__PURE__ */ jsxRuntimeExports.jsx( - "img", - { - src: shape.props.imageData, - alt: shape.props.title, - style: { - width: "100%", - height: `100%`, - objectFit: "contain", - position: "absolute", - top: 0, - left: 0 - } - } - ) }); - }; -} - -var n,l$1,u$1,i$3,t,r$1,o,f$1,e$1,c$2={},s=[],a$1=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function h(n,l){for(var u in l)n[u]=l[u];return n}function v$1(n){var l=n.parentNode;l&&l.removeChild(n);}function y(l,u,i){var t,r,o,f={};for(o in u)"key"==o?t=u[o]:"ref"==o?r=u[o]:f[o]=u[o];if(arguments.length>2&&(f.children=arguments.length>3?n.call(arguments,2):i),"function"==typeof l&&null!=l.defaultProps)for(o in l.defaultProps) void 0===f[o]&&(f[o]=l.defaultProps[o]);return p(l,f,t,r,null)}function p(n,i,t,r,o){var f={type:n,props:i,key:t,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++u$1:o};return null==o&&null!=l$1.vnode&&l$1.vnode(f),f}function d(){return {current:null}}function _(n){return n.children}function k$1(n,l,u,i,t){var r;for(r in u)"children"===r||"key"===r||r in l||g$2(n,r,null,u[r],i);for(r in l)t&&"function"!=typeof l[r]||"children"===r||"key"===r||"value"===r||"checked"===r||u[r]===l[r]||g$2(n,r,l[r],u[r],i);}function b$1(n,l,u){"-"===l[0]?n.setProperty(l,null==u?"":u):n[l]=null==u?"":"number"!=typeof u||a$1.test(l)?u:u+"px";}function g$2(n,l,u,i,t){var r;n:if("style"===l)if("string"==typeof u)n.style.cssText=u;else {if("string"==typeof i&&(n.style.cssText=i=""),i)for(l in i)u&&l in u||b$1(n.style,l,"");if(u)for(l in u)i&&u[l]===i[l]||b$1(n.style,l,u[l]);}else if("o"===l[0]&&"n"===l[1])r=l!==(l=l.replace(/Capture$/,"")),l=l.toLowerCase()in n?l.toLowerCase().slice(2):l.slice(2),n.l||(n.l={}),n.l[l+r]=u,u?i||n.addEventListener(l,r?w$2:m$1,r):n.removeEventListener(l,r?w$2:m$1,r);else if("dangerouslySetInnerHTML"!==l){if(t)l=l.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if("width"!==l&&"height"!==l&&"href"!==l&&"list"!==l&&"form"!==l&&"tabIndex"!==l&&"download"!==l&&l in n)try{n[l]=null==u?"":u;break n}catch(n){}"function"==typeof u||(null==u||false===u&&-1==l.indexOf("-")?n.removeAttribute(l):n.setAttribute(l,u));}}function m$1(n){t=true;try{return this.l[n.type+!1](l$1.event?l$1.event(n):n)}finally{t=false;}}function w$2(n){t=true;try{return this.l[n.type+!0](l$1.event?l$1.event(n):n)}finally{t=false;}}function x$1(n,l){this.props=n,this.context=l;}function A(n,l){if(null==l)return n.__?A(n.__,n.__.__k.indexOf(n)+1):null;for(var u;ll&&r$1.sort(function(n,l){return n.__v.__b-l.__v.__b}));$$1.__r=0;}function H$1(n,l,u,i,t,r,o,f,e,a){var h,v,y,d,k,b,g,m=i&&i.__k||s,w=m.length;for(u.__k=[],h=0;h0?p(d.type,d.props,d.key,d.ref?d.ref:null,d.__v):d)){if(d.__=u,d.__b=u.__b+1,null===(y=m[h])||y&&d.key==y.key&&d.type===y.type)m[h]=void 0;else for(v=0;v=0;l--)if((u=n.__k[l])&&(i=L$1(u)))return i;return null}function M(n,u,i,t,r,o,f,e,c){var s,a,v,y,p,d,k,b,g,m,w,A,P,C,T,$=u.type;if(void 0!==u.constructor)return null;null!=i.__h&&(c=i.__h,e=u.__e=i.__e,u.__h=null,o=[e]),(s=l$1.__b)&&s(u);try{n:if("function"==typeof $){if(b=u.props,g=(s=$.contextType)&&t[s.__c],m=s?g?g.props.value:s.__:t,i.__c?k=(a=u.__c=i.__c).__=a.__E:("prototype"in $&&$.prototype.render?u.__c=a=new $(b,m):(u.__c=a=new x$1(b,m),a.constructor=$,a.render=B$1),g&&g.sub(a),a.props=b,a.state||(a.state={}),a.context=m,a.__n=t,v=a.__d=!0,a.__h=[],a._sb=[]),null==a.__s&&(a.__s=a.state),null!=$.getDerivedStateFromProps&&(a.__s==a.state&&(a.__s=h({},a.__s)),h(a.__s,$.getDerivedStateFromProps(b,a.__s))),y=a.props,p=a.state,a.__v=u,v)null==$.getDerivedStateFromProps&&null!=a.componentWillMount&&a.componentWillMount(),null!=a.componentDidMount&&a.__h.push(a.componentDidMount);else {if(null==$.getDerivedStateFromProps&&b!==y&&null!=a.componentWillReceiveProps&&a.componentWillReceiveProps(b,m),!a.__e&&null!=a.shouldComponentUpdate&&!1===a.shouldComponentUpdate(b,a.__s,m)||u.__v===i.__v){for(u.__v!==i.__v&&(a.props=b,a.state=a.__s,a.__d=!1),u.__e=i.__e,u.__k=i.__k,u.__k.forEach(function(n){n&&(n.__=u);}),w=0;w3;)e.pop()();if(e[1]>>1,1),e.i.removeChild(n);}}),D$1(y(P,{context:e.context},n.__v),e.l)):e.l&&e.componentWillUnmount();}function j(n,e){var r=y($,{__v:n,i:e});return r.containerInfo=e,r}(V.prototype=new x$1).__a=function(n){var t=this,e=F(t.__v),r=t.o.get(n);return r[0]++,function(u){var o=function(){t.props.revealOrder?(r.push(u),W(t,n,r)):u();};e?e(o):o();}},V.prototype.render=function(n){this.u=null,this.o=new Map;var t=j$2(n.children);n.revealOrder&&"b"===n.revealOrder[0]&&t.reverse();for(var e=t.length;e--;)this.o.set(t[e],this.u=[1,0,this.u]);return n.children},V.prototype.componentDidUpdate=V.prototype.componentDidMount=function(){var n=this;this.o.forEach(function(t,e){W(n,e,t);});};var z="undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103,B=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,H="undefined"!=typeof document,Z=function(n){return ("undefined"!=typeof Symbol&&"symbol"==typeof Symbol()?/fil|che|rad/i:/fil|che|ra/i).test(n)};x$1.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(t){Object.defineProperty(x$1.prototype,t,{configurable:true,get:function(){return this["UNSAFE_"+t]},set:function(n){Object.defineProperty(this,t,{configurable:true,writable:true,value:n});}});});var G=l$1.event;function J(){}function K(){return this.cancelBubble}function Q(){return this.defaultPrevented}l$1.event=function(n){return G&&(n=G(n)),n.persist=J,n.isPropagationStopped=K,n.isDefaultPrevented=Q,n.nativeEvent=n};var nn={configurable:true,get:function(){return this.class}},tn=l$1.vnode;l$1.vnode=function(n){var t=n.type,e=n.props,u=e;if("string"==typeof t){var o=-1===t.indexOf("-");for(var i in u={},e){var l=e[i];H&&"children"===i&&"noscript"===t||"value"===i&&"defaultValue"in e&&null==l||("defaultValue"===i&&"value"in e&&null==e.value?i="value":"download"===i&&true===l?l="":/ondoubleclick/i.test(i)?i="ondblclick":/^onchange(textarea|input)/i.test(i+t)&&!Z(e.type)?i="oninput":/^onfocus$/i.test(i)?i="onfocusin":/^onblur$/i.test(i)?i="onfocusout":/^on(Ani|Tra|Tou|BeforeInp|Compo)/.test(i)?i=i.toLowerCase():o&&B.test(i)?i=i.replace(/[A-Z0-9]/g,"-$&").toLowerCase():null===l&&(l=void 0),/^oninput$/i.test(i)&&(i=i.toLowerCase(),u[i]&&(i="oninputCapture")),u[i]=l);}"select"==t&&u.multiple&&Array.isArray(u.value)&&(u.value=j$2(e.children).forEach(function(n){n.props.selected=-1!=u.value.indexOf(n.props.value);})),"select"==t&&null!=u.defaultValue&&(u.value=j$2(e.children).forEach(function(n){n.props.selected=u.multiple?-1!=u.defaultValue.indexOf(n.props.value):u.defaultValue==n.props.value;})),n.props=u,e.class!=e.className&&(nn.enumerable="className"in e,null!=e.className&&(u.class=e.className),Object.defineProperty(u,"className",nn));}n.$$typeof=z,tn&&tn(n);};var en=l$1.__r;l$1.__r=function(n){en&&en(n),n.__c;}; - -const styleTexts = []; -const styleEls = new Map(); -function injectStyles(styleText) { - styleTexts.push(styleText); - styleEls.forEach((styleEl) => { - appendStylesTo(styleEl, styleText); - }); -} -function ensureElHasStyles(el) { - if (el.isConnected && // sometimes true if SSR system simulates DOM - el.getRootNode // sometimes undefined if SSR system simulates DOM - ) { - registerStylesRoot(el.getRootNode()); - } -} -function registerStylesRoot(rootNode) { - let styleEl = styleEls.get(rootNode); - if (!styleEl || !styleEl.isConnected) { - styleEl = rootNode.querySelector('style[data-fullcalendar]'); - if (!styleEl) { - styleEl = document.createElement('style'); - styleEl.setAttribute('data-fullcalendar', ''); - const nonce = getNonceValue(); - if (nonce) { - styleEl.nonce = nonce; - } - const parentEl = rootNode === document ? document.head : rootNode; - const insertBefore = rootNode === document - ? parentEl.querySelector('script,link[rel=stylesheet],link[as=style],style') - : parentEl.firstChild; - parentEl.insertBefore(styleEl, insertBefore); - } - styleEls.set(rootNode, styleEl); - hydrateStylesRoot(styleEl); - } -} -function hydrateStylesRoot(styleEl) { - for (const styleText of styleTexts) { - appendStylesTo(styleEl, styleText); - } -} -function appendStylesTo(styleEl, styleText) { - const { sheet } = styleEl; - const ruleCnt = sheet.cssRules.length; - styleText.split('}').forEach((styleStr, i) => { - styleStr = styleStr.trim(); - if (styleStr) { - sheet.insertRule(styleStr + '}', ruleCnt + i); - } - }); -} -// nonce -// ------------------------------------------------------------------------------------------------- -let queriedNonceValue; -function getNonceValue() { - if (queriedNonceValue === undefined) { - queriedNonceValue = queryNonceValue(); - } - return queriedNonceValue; -} -/* -TODO: discourage meta tag and instead put nonce attribute on placeholder - - ` - } - ); -} - -function getFrameHeadingSide(editor, shape) { - const pageRotation = canonicalizeRotation(editor.getShapePageTransform(shape.id).rotation()); - const offsetRotation = pageRotation + Math.PI / 4; - const scaledRotation = (offsetRotation * (2 / Math.PI) + 4) % 4; - return Math.floor(scaledRotation); -} -function getFrameHeadingInfo(editor, shape, opts) { - const spans = editor.textMeasure.measureTextSpans( - defaultEmptyAs(shape.props.name, "Frame") + String.fromCharCode(8203), - opts - ); - const firstSpan = spans[0]; - const lastSpan = last$1(spans); - const labelTextWidth = lastSpan.box.w + lastSpan.box.x - firstSpan.box.x; - return { - box: new Box(0, -opts.height, labelTextWidth, opts.height), - spans - }; -} -function getFrameHeadingOpts(shape, color) { - return { - fontSize: 12, - fontFamily: "Inter, sans-serif", - textAlign: "start", - width: shape.props.w, - height: 32, - padding: 0, - lineHeight: 1, - fontStyle: "normal", - fontWeight: "normal", - overflow: "truncate-ellipsis", - verticalTextAlign: "middle", - fill: color, - offsetY: -34, - offsetX: 2 - }; -} -function getFrameHeadingTranslation(shape, side, isSvg) { - const u = isSvg ? "" : "px"; - const r = isSvg ? "" : "deg"; - let labelTranslate; - switch (side) { - case 0: - labelTranslate = ``; - break; - case 3: - labelTranslate = `translate(${toDomPrecision(shape.props.w)}${u}, 0${u}) rotate(90${r})`; - break; - case 2: - labelTranslate = `translate(${toDomPrecision(shape.props.w)}${u}, ${toDomPrecision( - shape.props.h - )}${u}) rotate(180${r})`; - break; - case 1: - labelTranslate = `translate(0${u}, ${toDomPrecision(shape.props.h)}${u}) rotate(270${r})`; - break; - default: - throw Error("labelSide out of bounds"); - } - return labelTranslate; -} - -const FrameLabelInput = reactExports.forwardRef(({ id, name, isEditing }, ref) => { - const editor = useEditor(); - const handleKeyDown = reactExports.useCallback( - (e) => { - if (e.key === "Enter" && !e.nativeEvent.isComposing) { - stopEventPropagation(e); - e.currentTarget.blur(); - editor.setEditingShape(null); - } - }, - [editor] - ); - const handleBlur = reactExports.useCallback( - (e) => { - const shape = editor.getShape(id); - if (!shape) return; - const name2 = shape.props.name; - const value = e.currentTarget.value.trim(); - if (name2 === value) return; - editor.updateShapes([ - { - id, - type: "frame", - props: { name: value } - } - ]); - }, - [id, editor] - ); - const handleChange = reactExports.useCallback( - (e) => { - const shape = editor.getShape(id); - if (!shape) return; - const name2 = shape.props.name; - const value = e.currentTarget.value; - if (name2 === value) return; - editor.updateShapes([ - { - id, - type: "frame", - props: { name: value } - } - ]); - }, - [id, editor] - ); - return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `tl-frame-label ${isEditing ? "tl-frame-label__editing" : ""}`, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - "input", - { - className: "tl-frame-name-input", - ref, - style: { display: isEditing ? void 0 : "none" }, - value: name, - autoFocus: true, - onKeyDown: handleKeyDown, - onBlur: handleBlur, - onChange: handleChange - } - ), - defaultEmptyAs(name, "Frame") + String.fromCharCode(8203) - ] }); -}); - -function FrameHeading({ - id, - name, - width, - height -}) { - const editor = useEditor(); - const { side, translation } = useValue( - "shape rotation", - () => { - const shape = editor.getShape(id); - if (!shape) { - return { - side: 0, - translation: "translate(0, 0)" - }; - } - const labelSide = getFrameHeadingSide(editor, shape); - return { - side: labelSide, - translation: getFrameHeadingTranslation(shape, labelSide, false) - }; - }, - [editor, id] - ); - const rInput = reactExports.useRef(null); - const isEditing = useIsEditing(id); - reactExports.useEffect(() => { - const el = rInput.current; - if (el && isEditing) { - el.focus(); - el.select(); - } - }, [rInput, isEditing]); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "div", - { - className: "tl-frame-heading", - style: { - overflow: isEditing ? "visible" : "hidden", - maxWidth: `calc(var(--tl-zoom) * ${side === 0 || side === 2 ? Math.ceil(width) : Math.ceil(height)}px + var(--space-5))`, - bottom: "100%", - transform: `${translation} scale(var(--tl-scale)) translateX(calc(-1 * var(--space-3))` - }, - children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tl-frame-heading-hit-area", children: /* @__PURE__ */ jsxRuntimeExports.jsx(FrameLabelInput, { ref: rInput, id, name, isEditing }) }) - } - ); -} - -function defaultEmptyAs(str, dflt) { - if (str.match(/^\s*$/)) { - return dflt; - } - return str; -} -class FrameShapeUtil extends BaseBoxShapeUtil { - static type = "frame"; - static props = frameShapeProps; - static migrations = frameShapeMigrations; - canEdit() { - return true; - } - getDefaultProps() { - return { w: 160 * 2, h: 90 * 2, name: "" }; - } - getGeometry(shape) { - const { editor } = this; - const z = editor.getZoomLevel(); - const opts = getFrameHeadingOpts(shape, "black"); - const headingInfo = getFrameHeadingInfo(editor, shape, opts); - const labelSide = getFrameHeadingSide(editor, shape); - let x, y, w, h; - const { w: hw, h: hh } = headingInfo.box; - const scaledW = Math.min(hw, shape.props.w * z); - const scaledH = Math.min(hh, shape.props.h * z); - switch (labelSide) { - case 0: { - x = -8 / z; - y = (-hh - 4) / z; - w = (scaledW + 16) / z; - h = hh / z; - break; - } - case 1: { - x = (-hh - 4) / z; - h = (scaledH + 16) / z; - y = shape.props.h - h + 8 / z; - w = hh / z; - break; - } - case 2: { - x = shape.props.w - (scaledW + 8) / z; - y = shape.props.h + 4 / z; - w = (scaledH + 16) / z; - h = hh / z; - break; - } - case 3: { - x = shape.props.w + 4 / z; - h = (scaledH + 16) / z; - y = -8 / z; - w = hh / z; - break; - } - } - return new Group2d({ - children: [ - new Rectangle2d({ - width: shape.props.w, - height: shape.props.h, - isFilled: false - }), - new Rectangle2d({ - x, - y, - width: w, - height: h, - isFilled: true, - isLabel: true - }) - ] - }); - } - getText(shape) { - return shape.props.name; - } - component(shape) { - const bounds = this.editor.getShapeGeometry(shape).bounds; - const theme = useDefaultColorTheme(); - const isCreating = useValue( - "is creating this shape", - () => { - const resizingState = this.editor.getStateDescendant("select.resizing"); - if (!resizingState) return false; - if (!resizingState.getIsActive()) return false; - const info = resizingState?.info; - if (!info) return false; - return info.isCreating && this.editor.getOnlySelectedShapeId() === shape.id; - }, - [shape.id] - ); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(SVGContainer, { children: /* @__PURE__ */ jsxRuntimeExports.jsx( - "rect", - { - className: classNames("tl-frame__body", { "tl-frame__creating": isCreating }), - width: bounds.width, - height: bounds.height, - fill: theme.solid, - stroke: theme.text - } - ) }), - isCreating ? null : /* @__PURE__ */ jsxRuntimeExports.jsx( - FrameHeading, - { - id: shape.id, - name: shape.props.name, - width: bounds.width, - height: bounds.height - } - ) - ] }); - } - toSvg(shape, ctx) { - const theme = getDefaultColorTheme({ isDarkMode: ctx.isDarkMode }); - const labelSide = getFrameHeadingSide(this.editor, shape); - const labelTranslate = getFrameHeadingTranslation(shape, labelSide, true); - const opts = getFrameHeadingOpts(shape, theme.text); - const { box: labelBounds, spans } = getFrameHeadingInfo(this.editor, shape, opts); - const text = createTextJsxFromSpans(this.editor, spans, opts); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - "rect", - { - width: shape.props.w, - height: shape.props.h, - fill: theme.solid, - stroke: theme.black.solid, - strokeWidth: 1, - rx: 1, - ry: 1 - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { transform: labelTranslate, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - "rect", - { - x: labelBounds.x - 8, - y: labelBounds.y - 4, - width: labelBounds.width + 20, - height: labelBounds.height, - fill: theme.background, - rx: 4, - ry: 4 - } - ), - text - ] }) - ] }); - } - indicator(shape) { - const bounds = this.editor.getShapeGeometry(shape).bounds; - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "rect", - { - width: toDomPrecision(bounds.width), - height: toDomPrecision(bounds.height), - className: `tl-frame-indicator` - } - ); - } - canReceiveNewChildrenOfType(shape, _type) { - return !shape.isLocked; - } - providesBackgroundForChildren() { - return true; - } - canDropShapes(shape, _shapes) { - return !shape.isLocked; - } - onDragShapesOver(frame, shapes) { - if (!shapes.every((child) => child.parentId === frame.id)) { - this.editor.reparentShapes(shapes, frame.id); - } - } - onDragShapesOut(_shape, shapes) { - const parent = this.editor.getShape(_shape.parentId); - const isInGroup = parent && this.editor.isShapeOfType(parent, "group"); - if (isInGroup) { - this.editor.reparentShapes(shapes, parent.id); - } else { - this.editor.reparentShapes(shapes, this.editor.getCurrentPageId()); - } - } - onResize(shape, info) { - return resizeBox(shape, info); - } - getInterpolatedProps(startShape, endShape, t) { - return { - ...(t > 0.5 ? endShape.props : startShape.props), - w: lerp(startShape.props.w, endShape.props.w, t), - h: lerp(startShape.props.h, endShape.props.h, t) - }; - } -} - -function getOvalPerimeter(h, w) { - if (h > w) return (PI$1 * (w / 2) + (h - w)) * 2; - else return (PI$1 * (h / 2) + (w - h)) * 2; -} -function getHeartPath(w, h) { - return getHeartParts(w, h).map((c, i) => c.getSvgPathData(i === 0)).join(" ") + " Z"; -} -function getDrawHeartPath(w, h, sw, id) { - const o = w / 4; - const k = h / 4; - const random = rng(id); - const mutDistance = sw * 0.75; - const mut = (v) => v.addXY(random() * mutDistance, random() * mutDistance); - const A = new Vec(w / 2, h); - const B = new Vec(0, k * 1.2); - const C = new Vec(w / 2, k * 0.9); - const D = new Vec(w, k * 1.2); - const Am = mut(new Vec(w / 2, h)); - const Bm = mut(new Vec(0, k * 1.2)); - const Cm = mut(new Vec(w / 2, k * 0.9)); - const Dm = mut(new Vec(w, k * 1.2)); - const parts = [ - new CubicBezier2d({ - start: A, - cp1: new Vec(o * 1.5, k * 3), - cp2: new Vec(0, k * 2.5), - end: B - }), - new CubicBezier2d({ - start: B, - cp1: new Vec(0, -k * 0.32), - cp2: new Vec(o * 1.85, -k * 0.32), - end: C - }), - new CubicBezier2d({ - start: C, - cp1: new Vec(o * 2.15, -k * 0.32), - cp2: new Vec(w, -k * 0.32), - end: D - }), - new CubicBezier2d({ - start: D, - cp1: new Vec(w, k * 2.5), - cp2: new Vec(o * 2.5, k * 3), - end: Am - }), - new CubicBezier2d({ - start: Am, - cp1: new Vec(o * 1.5, k * 3), - cp2: new Vec(0, k * 2.5), - end: Bm - }), - new CubicBezier2d({ - start: Bm, - cp1: new Vec(0, -k * 0.32), - cp2: new Vec(o * 1.85, -k * 0.32), - end: Cm - }), - new CubicBezier2d({ - start: Cm, - cp1: new Vec(o * 2.15, -k * 0.32), - cp2: new Vec(w, -k * 0.32), - end: Dm - }), - new CubicBezier2d({ - start: Dm, - cp1: new Vec(w, k * 2.5), - cp2: new Vec(o * 2.5, k * 3), - end: A - }) - ]; - return parts.map((c, i) => c.getSvgPathData(i === 0)).join(" ") + " Z"; -} -function getHeartParts(w, h) { - const o = w / 4; - const k = h / 4; - return [ - new CubicBezier2d({ - start: new Vec(w / 2, h), - cp1: new Vec(o * 1.5, k * 3), - cp2: new Vec(0, k * 2.5), - end: new Vec(0, k * 1.2) - }), - new CubicBezier2d({ - start: new Vec(0, k * 1.2), - cp1: new Vec(0, -k * 0.32), - cp2: new Vec(o * 1.85, -k * 0.32), - end: new Vec(w / 2, k * 0.9) - }), - new CubicBezier2d({ - start: new Vec(w / 2, k * 0.9), - cp1: new Vec(o * 2.15, -k * 0.32), - cp2: new Vec(w, -k * 0.32), - end: new Vec(w, k * 1.2) - }), - new CubicBezier2d({ - start: new Vec(w, k * 1.2), - cp1: new Vec(w, k * 2.5), - cp2: new Vec(o * 2.5, k * 3), - end: new Vec(w / 2, h) - }) - ]; -} -function getEllipseStrokeOptions(strokeWidth) { - return { - size: 1 + strokeWidth, - thinning: 0.25, - end: { taper: strokeWidth }, - start: { taper: strokeWidth }, - streamline: 0, - smoothing: 1, - simulatePressure: false - }; -} -function getEllipseStrokePoints(id, width, height, strokeWidth) { - const getRandom = rng(id); - const rx = width / 2; - const ry = height / 2; - const perimeter = perimeterOfEllipse(rx, ry); - const points = []; - const start = PI2 * getRandom(); - const length = PI2 + HALF_PI / 2 + Math.abs(getRandom()) * HALF_PI; - const count = Math.max(16, perimeter / 10); - for (let i = 0; i < count; i++) { - const t = i / (count - 1); - const r = start + t * length; - const c = Math.cos(r); - const s = Math.sin(r); - points.push( - new Vec( - rx * c + width * 0.5 + 0.05 * getRandom(), - ry * s + height / 2 + 0.05 * getRandom(), - Math.min( - 1, - 0.5 + Math.abs(0.5 - (getRandom() > 0 ? EASINGS.easeInOutSine(t) : EASINGS.easeInExpo(t))) / 2 - ) - ) - ); - } - return getStrokePoints(points, getEllipseStrokeOptions(strokeWidth)); -} -function getEllipseDrawIndicatorPath(id, width, height, strokeWidth) { - return getSvgPathFromStrokePoints(getEllipseStrokePoints(id, width, height, strokeWidth)); -} -function getRoundedInkyPolygonPath(points) { - let polylineA = `M`; - const len = points.length; - let p0; - let p1; - let p2; - for (let i = 0, n = len; i < n; i += 3) { - p0 = points[i]; - p1 = points[i + 1]; - p2 = points[i + 2]; - polylineA += `${precise(p0)}L${precise(p1)}Q${precise(p2)}`; - } - polylineA += `${precise(points[0])}`; - return polylineA; -} -function getRoundedPolygonPoints(id, outline, offset, roundness, passes) { - const results = []; - const random = rng(id); - let p0 = outline[0]; - let p1; - const len = outline.length; - for (let i = 0, n = len * passes; i < n; i++) { - p1 = Vec.AddXY(outline[(i + 1) % len], random() * offset, random() * offset); - const delta = Vec.Sub(p1, p0); - const distance = Vec.Len(delta); - const vector = Vec.Div(delta, distance).mul(Math.min(distance / 4, roundness)); - results.push(Vec.Add(p0, vector), Vec.Add(p1, vector.neg()), p1); - p0 = p1; - } - return results; -} -function getPillPoints(width, height, numPoints) { - const radius = Math.min(width, height) / 2; - const longSide = Math.max(width, height) - radius * 2; - const circumference = Math.PI * (radius * 2) + 2 * longSide; - const spacing = circumference / numPoints; - const sections = width > height ? [ - { - type: "straight", - start: new Vec(radius, 0), - delta: new Vec(1, 0) - }, - { - type: "arc", - center: new Vec(width - radius, radius), - startAngle: -PI$1 / 2 - }, - { - type: "straight", - start: new Vec(width - radius, height), - delta: new Vec(-1, 0) - }, - { - type: "arc", - center: new Vec(radius, radius), - startAngle: PI$1 / 2 - } - ] : [ - { - type: "straight", - start: new Vec(width, radius), - delta: new Vec(0, 1) - }, - { - type: "arc", - center: new Vec(radius, height - radius), - startAngle: 0 - }, - { - type: "straight", - start: new Vec(0, height - radius), - delta: new Vec(0, -1) - }, - { - type: "arc", - center: new Vec(radius, radius), - startAngle: PI$1 - } - ]; - let sectionOffset = 0; - const points = []; - for (let i = 0; i < numPoints; i++) { - const section = sections[0]; - if (section.type === "straight") { - points.push(Vec.Add(section.start, Vec.Mul(section.delta, sectionOffset))); - } else { - points.push( - getPointOnCircle(section.center, radius, section.startAngle + sectionOffset / radius) - ); - } - sectionOffset += spacing; - let sectionLength = section.type === "straight" ? longSide : PI$1 * radius; - while (sectionOffset > sectionLength) { - sectionOffset -= sectionLength; - sections.push(sections.shift()); - sectionLength = sections[0].type === "straight" ? longSide : PI$1 * radius; - } - } - return points; -} -const SIZES = { - s: 50, - m: 70, - l: 100, - xl: 130 -}; -const BUMP_PROTRUSION = 0.2; -function getCloudArcs(width, height, seed, size, scale) { - const getRandom = rng(seed); - const pillCircumference = getOvalPerimeter(width, height); - const numBumps = Math.max( - Math.ceil(pillCircumference / SIZES[size]), - 6, - Math.ceil(pillCircumference / Math.min(width, height)) - ); - const targetBumpProtrusion = pillCircumference / numBumps * BUMP_PROTRUSION; - const innerWidth = Math.max(width - targetBumpProtrusion * 2, 1); - const innerHeight = Math.max(height - targetBumpProtrusion * 2, 1); - const innerCircumference = getOvalPerimeter(innerWidth, innerHeight); - const distanceBetweenPointsOnPerimeter = innerCircumference / numBumps; - const paddingX = (width - innerWidth) / 2; - const paddingY = (height - innerHeight) / 2; - const bumpPoints = getPillPoints(innerWidth, innerHeight, numBumps).map((p) => { - return p.addXY(paddingX, paddingY); - }); - const maxWiggleX = width < 20 ? 0 : targetBumpProtrusion * 0.3; - const maxWiggleY = height < 20 ? 0 : targetBumpProtrusion * 0.3; - const wiggledPoints = bumpPoints.slice(0); - for (let i = 0; i < Math.floor(numBumps / 2); i++) { - wiggledPoints[i] = Vec.AddXY( - wiggledPoints[i], - getRandom() * maxWiggleX * scale, - getRandom() * maxWiggleY * scale - ); - wiggledPoints[numBumps - i - 1] = Vec.AddXY( - wiggledPoints[numBumps - i - 1], - getRandom() * maxWiggleX * scale, - getRandom() * maxWiggleY * scale - ); - } - const arcs = []; - for (let i = 0; i < wiggledPoints.length; i++) { - const j = i === wiggledPoints.length - 1 ? 0 : i + 1; - const leftWigglePoint = wiggledPoints[i]; - const rightWigglePoint = wiggledPoints[j]; - const leftPoint = bumpPoints[i]; - const rightPoint = bumpPoints[j]; - const distanceBetweenOriginalPoints = Vec.Dist(leftPoint, rightPoint); - const curvatureOffset = distanceBetweenPointsOnPerimeter - distanceBetweenOriginalPoints; - const distanceBetweenWigglePoints = Vec.Dist(leftWigglePoint, rightWigglePoint); - const relativeSize = distanceBetweenWigglePoints / distanceBetweenOriginalPoints; - const finalDistance = (Math.max(paddingX, paddingY) + curvatureOffset) * relativeSize; - const arcPoint = Vec.Lrp(leftPoint, rightPoint, 0.5).add( - Vec.Sub(rightPoint, leftPoint).uni().per().mul(finalDistance) - ); - if (arcPoint.x < 0) { - arcPoint.x = 0; - } else if (arcPoint.x > width) { - arcPoint.x = width; - } - if (arcPoint.y < 0) { - arcPoint.y = 0; - } else if (arcPoint.y > height) { - arcPoint.y = height; - } - const center = centerOfCircleFromThreePoints(leftWigglePoint, rightWigglePoint, arcPoint); - const radius = Vec.Dist( - center ? center : Vec.Average([leftWigglePoint, rightWigglePoint]), - leftWigglePoint - ); - arcs.push({ - leftPoint: leftWigglePoint, - rightPoint: rightWigglePoint, - arcPoint, - center, - radius - }); - } - return arcs; -} -function cloudOutline(width, height, seed, size, scale) { - const path = []; - const arcs = getCloudArcs(width, height, seed, size, scale); - for (const { center, radius, leftPoint, rightPoint } of arcs) { - path.push(...getPointsOnArc(leftPoint, rightPoint, center, radius, 10)); - } - return path; -} -function getCloudPath(width, height, seed, size, scale) { - const arcs = getCloudArcs(width, height, seed, size, scale); - let path = `M${arcs[0].leftPoint.toFixed()}`; - for (const { leftPoint, rightPoint, radius, center } of arcs) { - if (center === null) { - path += ` L${rightPoint.toFixed()}`; - continue; - } - const arc = Vec.Clockwise(leftPoint, rightPoint, center) ? "0" : "1"; - path += ` A${toDomPrecision(radius)},${toDomPrecision(radius)} 0 ${arc},1 ${rightPoint.toFixed()}`; - } - path += " Z"; - return path; -} -const DRAW_OFFSETS = { - s: 0.5, - m: 0.7, - l: 0.9, - xl: 1.6 -}; -function inkyCloudSvgPath(width, height, seed, size, scale) { - const getRandom = rng(seed); - const mutMultiplier = DRAW_OFFSETS[size] * scale; - const arcs = getCloudArcs(width, height, seed, size, scale); - const avgArcLengthSquared = arcs.reduce((sum, arc) => sum + Vec.Dist2(arc.leftPoint, arc.rightPoint), 0) / arcs.length; - const shouldMutatePoints = avgArcLengthSquared > (mutMultiplier * 15) ** 2; - const mutPoint = shouldMutatePoints ? (p) => Vec.AddXY(p, getRandom() * mutMultiplier * 2, getRandom() * mutMultiplier * 2) : (p) => p; - let pathA = `M${arcs[0].leftPoint.toFixed()}`; - let leftMutPoint = mutPoint(arcs[0].leftPoint); - let pathB = `M${leftMutPoint.toFixed()}`; - for (const { leftPoint, center, rightPoint, radius, arcPoint } of arcs) { - if (center === null) { - pathA += ` L${rightPoint.toFixed()}`; - const rightMutPoint2 = mutPoint(rightPoint); - pathB += ` L${rightMutPoint2.toFixed()}`; - leftMutPoint = rightMutPoint2; - continue; - } - const arc = Vec.Clockwise(leftPoint, rightPoint, center) ? "0" : "1"; - pathA += ` A${toDomPrecision(radius)},${toDomPrecision(radius)} 0 ${arc},1 ${rightPoint.toFixed()}`; - const rightMutPoint = mutPoint(rightPoint); - const mutArcPoint = mutPoint(arcPoint); - const mutCenter = centerOfCircleFromThreePoints(leftMutPoint, rightMutPoint, mutArcPoint); - if (!mutCenter) { - pathB += ` L${rightMutPoint.toFixed()}`; - leftMutPoint = rightMutPoint; - continue; - } - const mutRadius = Math.abs(Vec.Dist(mutCenter, leftMutPoint)); - pathB += ` A${toDomPrecision(mutRadius)},${toDomPrecision( - mutRadius - )} 0 ${arc},1 ${rightMutPoint.toFixed()}`; - leftMutPoint = rightMutPoint; - } - return pathA + pathB + " Z"; -} - -function getLines(props, sw) { - switch (props.geo) { - case "x-box": { - return getXBoxLines(props.w, props.h, sw, props.dash); - } - case "check-box": { - return getCheckBoxLines(props.w, props.h); - } - default: { - return void 0; - } - } -} -function getXBoxLines(w, h, sw, dash) { - const inset = dash === "draw" ? 0.62 : 0; - if (dash === "dashed") { - return [ - [new Vec(0, 0), new Vec(w / 2, h / 2)], - [new Vec(w, h), new Vec(w / 2, h / 2)], - [new Vec(0, h), new Vec(w / 2, h / 2)], - [new Vec(w, 0), new Vec(w / 2, h / 2)] - ]; - } - const clampX = (x) => Math.max(0, Math.min(w, x)); - const clampY = (y) => Math.max(0, Math.min(h, y)); - return [ - [ - new Vec(clampX(sw * inset), clampY(sw * inset)), - new Vec(clampX(w - sw * inset), clampY(h - sw * inset)) - ], - [ - new Vec(clampX(sw * inset), clampY(h - sw * inset)), - new Vec(clampX(w - sw * inset), clampY(sw * inset)) - ] - ]; -} -function getCheckBoxLines(w, h) { - const size = Math.min(w, h) * 0.82; - const ox = (w - size) / 2; - const oy = (h - size) / 2; - const clampX = (x) => Math.max(0, Math.min(w, x)); - const clampY = (y) => Math.max(0, Math.min(h, y)); - return [ - [ - new Vec(clampX(ox + size * 0.25), clampY(oy + size * 0.52)), - new Vec(clampX(ox + size * 0.45), clampY(oy + size * 0.82)) - ], - [ - new Vec(clampX(ox + size * 0.45), clampY(oy + size * 0.82)), - new Vec(clampX(ox + size * 0.82), clampY(oy + size * 0.22)) - ] - ]; -} - -function GeoShapeBody({ - shape, - shouldScale, - forceSolid -}) { - const scaleToUse = shouldScale ? shape.props.scale : 1; - const editor = useEditor(); - const theme = useDefaultColorTheme(); - const { id, props } = shape; - const { w, color, fill, dash, growY, size, scale } = props; - const strokeWidth = STROKE_SIZES[size] * scaleToUse; - const h = props.h + growY; - switch (props.geo) { - case "cloud": { - if (dash === "solid") { - const d = getCloudPath(w, h, id, size, scale); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ShapeFill, { theme, d, color, fill, scale: scaleToUse }), - /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d, stroke: theme[color].solid, strokeWidth, fill: "none" }) - ] }); - } else if (dash === "draw") { - const d = inkyCloudSvgPath(w, h, id, size, scale); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ShapeFill, { theme, d, color, fill, scale: scaleToUse }), - /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d, stroke: theme[color].solid, strokeWidth, fill: "none" }) - ] }); - } else { - const d = getCloudPath(w, h, id, size, scale); - const arcs = getCloudArcs(w, h, id, size, scale); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ShapeFill, { theme, d, color, fill, scale: scaleToUse }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - "g", - { - strokeWidth, - stroke: theme[color].solid, - fill: "none", - pointerEvents: "all", - children: arcs.map(({ leftPoint, rightPoint, center, radius }, i) => { - const arcLength = center ? radius * canonicalizeRotation( - canonicalizeRotation(Vec.Angle(center, rightPoint)) - canonicalizeRotation(Vec.Angle(center, leftPoint)) - ) : Vec.Dist(leftPoint, rightPoint); - const { strokeDasharray, strokeDashoffset } = getPerfectDashProps( - arcLength, - strokeWidth, - { - style: dash, - start: "outset", - end: "outset", - forceSolid - } - ); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "path", - { - d: center ? `M${leftPoint.x},${leftPoint.y}A${radius},${radius},0,0,1,${rightPoint.x},${rightPoint.y}` : `M${leftPoint.x},${leftPoint.y}L${rightPoint.x},${rightPoint.y}`, - strokeDasharray, - strokeDashoffset - }, - i - ); - }) - } - ) - ] }); - } - } - case "ellipse": { - const geometry = shouldScale ? ( - // cached - (editor.getShapeGeometry(shape)) - ) : ( - // not cached - (editor.getShapeUtil(shape).getGeometry(shape)) - ); - const d = geometry.getSvgPathData(true); - if (dash === "dashed" || dash === "dotted") { - const perimeter = geometry.length; - const { strokeDasharray, strokeDashoffset } = getPerfectDashProps( - perimeter < 64 ? perimeter * 2 : perimeter, - strokeWidth, - { - style: dash, - snap: 4, - closed: true, - forceSolid - } - ); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ShapeFill, { theme, d, color, fill, scale: scaleToUse }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - "path", - { - d, - strokeWidth, - fill: "none", - stroke: theme[color].solid, - strokeDasharray, - strokeDashoffset - } - ) - ] }); - } else { - const geometry2 = shouldScale ? ( - // cached - (editor.getShapeGeometry(shape)) - ) : ( - // not cached - (editor.getShapeUtil(shape).getGeometry(shape)) - ); - const d2 = geometry2.getSvgPathData(true); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ShapeFill, { theme, d: d2, color, fill, scale: scaleToUse }), - /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: d2, stroke: theme[color].solid, strokeWidth, fill: "none" }) - ] }); - } - } - case "oval": { - const geometry = shouldScale ? ( - // cached - (editor.getShapeGeometry(shape)) - ) : ( - // not cached - (editor.getShapeUtil(shape).getGeometry(shape)) - ); - const d = geometry.getSvgPathData(true); - if (dash === "dashed" || dash === "dotted") { - const perimeter = geometry.getLength(); - const { strokeDasharray, strokeDashoffset } = getPerfectDashProps( - perimeter < 64 ? perimeter * 2 : perimeter, - strokeWidth, - { - style: dash, - snap: 4, - start: "outset", - end: "outset", - closed: true, - forceSolid - } - ); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ShapeFill, { theme, d, color, fill, scale: scaleToUse }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - "path", - { - d, - strokeWidth, - fill: "none", - stroke: theme[color].solid, - strokeDasharray, - strokeDashoffset - } - ) - ] }); - } else { - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ShapeFill, { theme, d, color, fill, scale: scaleToUse }), - /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d, stroke: theme[color].solid, strokeWidth, fill: "none" }) - ] }); - } - } - case "heart": { - if (dash === "dashed" || dash === "dotted" || dash === "solid") { - const d = getHeartPath(w, h); - const curves = getHeartParts(w, h); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ShapeFill, { theme, d, color, fill, scale: scaleToUse }), - curves.map((c, i) => { - const { strokeDasharray, strokeDashoffset } = getPerfectDashProps( - c.length, - strokeWidth, - { - style: dash, - snap: 1, - start: "outset", - end: "outset", - closed: true, - forceSolid - } - ); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "path", - { - d: c.getSvgPathData(), - strokeWidth, - fill: "none", - stroke: theme[color].solid, - strokeDasharray, - strokeDashoffset, - pointerEvents: "all" - }, - `curve_${i}` - ); - }) - ] }); - } else { - const d = getDrawHeartPath(w, h, strokeWidth, shape.id); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ShapeFill, { theme, d, color, fill, scale: scaleToUse }), - /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d, stroke: theme[color].solid, strokeWidth, fill: "none" }) - ] }); - } - } - default: { - const geometry = shouldScale ? ( - // cached - (editor.getShapeGeometry(shape)) - ) : ( - // not cached - (editor.getShapeUtil(shape).getGeometry(shape)) - ); - const outline = geometry instanceof Group2d ? geometry.children[0].vertices : geometry.vertices; - const lines = getLines(shape.props, strokeWidth); - if (dash === "solid") { - let d = "M" + outline[0] + "L" + outline.slice(1) + "Z"; - if (lines) { - for (const [A, B] of lines) { - d += `M${A.x},${A.y}L${B.x},${B.y}`; - } - } - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ShapeFill, { theme, d, color, fill, scale: scaleToUse }), - /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d, stroke: theme[color].solid, strokeWidth, fill: "none" }) - ] }); - } else if (dash === "dashed" || dash === "dotted") { - const d = "M" + outline[0] + "L" + outline.slice(1) + "Z"; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ShapeFill, { theme, d, color, fill, scale: scaleToUse }), - /* @__PURE__ */ jsxRuntimeExports.jsxs( - "g", - { - strokeWidth, - stroke: theme[color].solid, - fill: "none", - pointerEvents: "all", - children: [ - Array.from(Array(outline.length)).map((_, i) => { - const A = Vec.ToFixed(outline[i]); - const B = Vec.ToFixed(outline[(i + 1) % outline.length]); - const dist = Vec.Dist(A, B); - const { strokeDasharray, strokeDashoffset } = getPerfectDashProps( - dist, - strokeWidth, - { - style: dash, - start: "outset", - end: "outset", - forceSolid - } - ); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "line", - { - x1: A.x, - y1: A.y, - x2: B.x, - y2: B.y, - strokeDasharray, - strokeDashoffset - }, - i - ); - }), - lines && lines.map(([A, B], i) => { - const dist = Vec.Dist(A, B); - const { strokeDasharray, strokeDashoffset } = getPerfectDashProps( - dist, - strokeWidth, - { - style: dash, - start: "skip", - end: "skip", - snap: dash === "dotted" ? 4 : void 0, - forceSolid - } - ); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "path", - { - d: `M${A.x},${A.y}L${B.x},${B.y}`, - stroke: theme[color].solid, - strokeWidth, - fill: "none", - strokeDasharray, - strokeDashoffset - }, - `line_fg_${i}` - ); - }) - ] - } - ) - ] }); - } else if (dash === "draw") { - let d = getRoundedInkyPolygonPath( - getRoundedPolygonPoints(id, outline, strokeWidth / 3, strokeWidth * 2, 2) - ); - if (lines) { - for (const [A, B] of lines) { - d += `M${A.toFixed()}L${B.toFixed()}`; - } - } - const innerPathData = getRoundedInkyPolygonPath( - getRoundedPolygonPoints(id, outline, 0, strokeWidth * 2, 1) - ); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - ShapeFill, - { - theme, - d: innerPathData, - color, - fill, - scale: scaleToUse - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d, stroke: theme[color].solid, strokeWidth, fill: "none" }) - ] }); - } - } - } -} - -const MIN_SIZE_WITH_LABEL = 17 * 3; -class GeoShapeUtil extends BaseBoxShapeUtil { - static type = "geo"; - static props = geoShapeProps; - static migrations = geoShapeMigrations; - canEdit() { - return true; - } - getDefaultProps() { - return { - w: 100, - h: 100, - geo: "rectangle", - color: "black", - labelColor: "black", - fill: "none", - dash: "draw", - size: "m", - font: "draw", - text: "", - align: "middle", - verticalAlign: "middle", - growY: 0, - url: "", - scale: 1 - }; - } - getGeometry(shape) { - const w = Math.max(1, shape.props.w); - const h = Math.max(1, shape.props.h + shape.props.growY); - const cx = w / 2; - const cy = h / 2; - const isFilled = shape.props.fill !== "none"; - let body; - switch (shape.props.geo) { - case "cloud": { - body = new Polygon2d({ - points: cloudOutline(w, h, shape.id, shape.props.size, shape.props.scale), - isFilled - }); - break; - } - case "triangle": { - body = new Polygon2d({ - points: [new Vec(cx, 0), new Vec(w, h), new Vec(0, h)], - isFilled - }); - break; - } - case "diamond": { - body = new Polygon2d({ - points: [new Vec(cx, 0), new Vec(w, cy), new Vec(cx, h), new Vec(0, cy)], - isFilled - }); - break; - } - case "pentagon": { - body = new Polygon2d({ - points: getPolygonVertices(w, h, 5), - isFilled - }); - break; - } - case "hexagon": { - body = new Polygon2d({ - points: getPolygonVertices(w, h, 6), - isFilled - }); - break; - } - case "octagon": { - body = new Polygon2d({ - points: getPolygonVertices(w, h, 8), - isFilled - }); - break; - } - case "ellipse": { - body = new Ellipse2d({ - width: w, - height: h, - isFilled - }); - break; - } - case "oval": { - body = new Stadium2d({ - width: w, - height: h, - isFilled - }); - break; - } - case "star": { - const sides = 5; - const step = PI2 / sides / 2; - const rightMostIndex = Math.floor(sides / 4) * 2; - const leftMostIndex = sides * 2 - rightMostIndex; - const topMostIndex = 0; - const bottomMostIndex = Math.floor(sides / 2) * 2; - const maxX = Math.cos(-HALF_PI + rightMostIndex * step) * w / 2; - const minX = Math.cos(-HALF_PI + leftMostIndex * step) * w / 2; - const minY = Math.sin(-HALF_PI + topMostIndex * step) * h / 2; - const maxY = Math.sin(-HALF_PI + bottomMostIndex * step) * h / 2; - const diffX = w - Math.abs(maxX - minX); - const diffY = h - Math.abs(maxY - minY); - const offsetX = w / 2 + minX - (w / 2 - maxX); - const offsetY = h / 2 + minY - (h / 2 - maxY); - const ratio = 1; - const cx2 = (w - offsetX) / 2; - const cy2 = (h - offsetY) / 2; - const ox = (w + diffX) / 2; - const oy = (h + diffY) / 2; - const ix = ox * ratio / 2; - const iy = oy * ratio / 2; - body = new Polygon2d({ - points: Array.from(Array(sides * 2)).map((_, i) => { - const theta = -HALF_PI + i * step; - return new Vec( - cx2 + (i % 2 ? ix : ox) * Math.cos(theta), - cy2 + (i % 2 ? iy : oy) * Math.sin(theta) - ); - }), - isFilled - }); - break; - } - case "rhombus": { - const offset = Math.min(w * 0.38, h * 0.38); - body = new Polygon2d({ - points: [new Vec(offset, 0), new Vec(w, 0), new Vec(w - offset, h), new Vec(0, h)], - isFilled - }); - break; - } - case "rhombus-2": { - const offset = Math.min(w * 0.38, h * 0.38); - body = new Polygon2d({ - points: [new Vec(0, 0), new Vec(w - offset, 0), new Vec(w, h), new Vec(offset, h)], - isFilled - }); - break; - } - case "trapezoid": { - const offset = Math.min(w * 0.38, h * 0.38); - body = new Polygon2d({ - points: [new Vec(offset, 0), new Vec(w - offset, 0), new Vec(w, h), new Vec(0, h)], - isFilled - }); - break; - } - case "arrow-right": { - const ox = Math.min(w, h) * 0.38; - const oy = h * 0.16; - body = new Polygon2d({ - points: [ - new Vec(0, oy), - new Vec(w - ox, oy), - new Vec(w - ox, 0), - new Vec(w, h / 2), - new Vec(w - ox, h), - new Vec(w - ox, h - oy), - new Vec(0, h - oy) - ], - isFilled - }); - break; - } - case "arrow-left": { - const ox = Math.min(w, h) * 0.38; - const oy = h * 0.16; - body = new Polygon2d({ - points: [ - new Vec(ox, 0), - new Vec(ox, oy), - new Vec(w, oy), - new Vec(w, h - oy), - new Vec(ox, h - oy), - new Vec(ox, h), - new Vec(0, h / 2) - ], - isFilled - }); - break; - } - case "arrow-up": { - const ox = w * 0.16; - const oy = Math.min(w, h) * 0.38; - body = new Polygon2d({ - points: [ - new Vec(w / 2, 0), - new Vec(w, oy), - new Vec(w - ox, oy), - new Vec(w - ox, h), - new Vec(ox, h), - new Vec(ox, oy), - new Vec(0, oy) - ], - isFilled - }); - break; - } - case "arrow-down": { - const ox = w * 0.16; - const oy = Math.min(w, h) * 0.38; - body = new Polygon2d({ - points: [ - new Vec(ox, 0), - new Vec(w - ox, 0), - new Vec(w - ox, h - oy), - new Vec(w, h - oy), - new Vec(w / 2, h), - new Vec(0, h - oy), - new Vec(ox, h - oy) - ], - isFilled - }); - break; - } - case "check-box": - case "x-box": - case "rectangle": { - body = new Rectangle2d({ - width: w, - height: h, - isFilled - }); - break; - } - case "heart": { - const parts = getHeartParts(w, h); - const points = parts.reduce((acc, part) => { - acc.push(...part.vertices); - return acc; - }, []); - body = new Polygon2d({ - points, - isFilled - }); - break; - } - default: { - exhaustiveSwitchError(shape.props.geo); - } - } - const unscaledlabelSize = getUnscaledLabelSize(this.editor, shape); - const unscaledW = w / shape.props.scale; - const unscaledH = h / shape.props.scale; - const unscaledminWidth = Math.min(100, unscaledW / 2); - const unscaledMinHeight = Math.min( - LABEL_FONT_SIZES[shape.props.size] * TEXT_PROPS.lineHeight + LABEL_PADDING * 2, - unscaledH / 2 - ); - const unscaledLabelWidth = Math.min( - unscaledW, - Math.max(unscaledlabelSize.w, Math.min(unscaledminWidth, Math.max(1, unscaledW - 8))) - ); - const unscaledLabelHeight = Math.min( - unscaledH, - Math.max(unscaledlabelSize.h, Math.min(unscaledMinHeight, Math.max(1, unscaledH - 8))) - ); - const lines = getLines(shape.props, STROKE_SIZES[shape.props.size] * shape.props.scale); - const edges = lines ? lines.map((line) => new Polyline2d({ points: line })) : []; - return new Group2d({ - children: [ - body, - new Rectangle2d({ - x: shape.props.align === "start" ? 0 : shape.props.align === "end" ? (unscaledW - unscaledLabelWidth) * shape.props.scale : (unscaledW - unscaledLabelWidth) / 2 * shape.props.scale, - y: shape.props.verticalAlign === "start" ? 0 : shape.props.verticalAlign === "end" ? (unscaledH - unscaledLabelHeight) * shape.props.scale : (unscaledH - unscaledLabelHeight) / 2 * shape.props.scale, - width: unscaledLabelWidth * shape.props.scale, - height: unscaledLabelHeight * shape.props.scale, - isFilled: true, - isLabel: true - }), - ...edges - ] - }); - } - getHandleSnapGeometry(shape) { - const geometry = this.getGeometry(shape); - const outline = geometry.children[0]; - switch (shape.props.geo) { - case "arrow-down": - case "arrow-left": - case "arrow-right": - case "arrow-up": - case "check-box": - case "diamond": - case "hexagon": - case "octagon": - case "pentagon": - case "rectangle": - case "rhombus": - case "rhombus-2": - case "star": - case "trapezoid": - case "triangle": - case "x-box": - return { outline, points: [...outline.getVertices(), geometry.bounds.center] }; - case "cloud": - case "ellipse": - case "heart": - case "oval": - return { outline, points: [geometry.bounds.center] }; - default: - exhaustiveSwitchError(shape.props.geo); - } - } - getText(shape) { - return shape.props.text; - } - onEditEnd(shape) { - const { - id, - type, - props: { text } - } = shape; - if (text.trimEnd() !== shape.props.text) { - this.editor.updateShapes([ - { - id, - type, - props: { - text: text.trimEnd() - } - } - ]); - } - } - component(shape) { - const { id, type, props } = shape; - const { fill, font, align, verticalAlign, size, text } = props; - const theme = useDefaultColorTheme(); - const { editor } = this; - const isOnlySelected = useValue( - "isGeoOnlySelected", - () => shape.id === editor.getOnlySelectedShapeId(), - [] - ); - const isEditingAnything = editor.getEditingShapeId() !== null; - const showHtmlContainer = isEditingAnything || shape.props.text; - const isForceSolid = useValue( - "force solid", - () => { - return editor.getZoomLevel() < 0.2; - }, - [editor] - ); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(SVGContainer, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(GeoShapeBody, { shape, shouldScale: true, forceSolid: isForceSolid }) }), - showHtmlContainer && /* @__PURE__ */ jsxRuntimeExports.jsx( - HTMLContainer, - { - style: { - overflow: "hidden", - width: shape.props.w, - height: shape.props.h + props.growY - }, - children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TextLabel, - { - shapeId: id, - type, - font, - fontSize: LABEL_FONT_SIZES[size] * shape.props.scale, - lineHeight: TEXT_PROPS.lineHeight, - padding: LABEL_PADDING * shape.props.scale, - fill, - align, - verticalAlign, - text, - isSelected: isOnlySelected, - labelColor: theme[props.labelColor].solid, - wrap: true - } - ) - } - ), - shape.props.url && /* @__PURE__ */ jsxRuntimeExports.jsx(HyperlinkButton, { url: shape.props.url }) - ] }); - } - indicator(shape) { - const { id, props } = shape; - const { w, size } = props; - const h = props.h + props.growY; - const strokeWidth = STROKE_SIZES[size]; - const geometry = this.editor.getShapeGeometry(shape); - switch (props.geo) { - case "ellipse": { - if (props.dash === "draw") { - return /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: getEllipseDrawIndicatorPath(id, w, h, strokeWidth) }); - } - return /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: geometry.getSvgPathData(true) }); - } - case "heart": { - return /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: getHeartPath(w, h) }); - } - case "oval": { - return /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: geometry.getSvgPathData(true) }); - } - case "cloud": { - return /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: getCloudPath(w, h, id, size, shape.props.scale) }); - } - default: { - const geometry2 = this.editor.getShapeGeometry(shape); - const outline = geometry2 instanceof Group2d ? geometry2.children[0].vertices : geometry2.vertices; - let path; - if (props.dash === "draw") { - const polygonPoints = getRoundedPolygonPoints( - id, - outline, - 0, - strokeWidth * 2 * shape.props.scale, - 1 - ); - path = getRoundedInkyPolygonPath(polygonPoints); - } else { - path = "M" + outline[0] + "L" + outline.slice(1) + "Z"; - } - const lines = getLines(shape.props, strokeWidth); - if (lines) { - for (const [A, B] of lines) { - path += `M${A.x},${A.y}L${B.x},${B.y}`; - } - } - return /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: path }); - } - } - } - toSvg(shape, ctx) { - const newShape = { - ...shape, - props: { - ...shape.props, - w: shape.props.w / shape.props.scale, - h: shape.props.h / shape.props.scale - } - }; - const props = newShape.props; - ctx.addExportDef(getFillDefForExport(props.fill)); - let textEl; - if (props.text) { - ctx.addExportDef(getFontDefForExport(props.font)); - const theme = getDefaultColorTheme(ctx); - const bounds = new Box(0, 0, props.w, props.h + props.growY); - textEl = /* @__PURE__ */ jsxRuntimeExports.jsx( - SvgTextLabel, - { - fontSize: LABEL_FONT_SIZES[props.size], - font: props.font, - align: props.align, - verticalAlign: props.verticalAlign, - text: props.text, - labelColor: theme[props.labelColor].solid, - bounds, - padding: 16 - } - ); - } - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(GeoShapeBody, { shouldScale: false, shape: newShape, forceSolid: false }), - textEl - ] }); - } - getCanvasSvgDefs() { - return [getFillDefForCanvas()]; - } - onResize(shape, { handle, newPoint, scaleX, scaleY, initialShape }) { - const unscaledInitialW = initialShape.props.w / initialShape.props.scale; - const unscaledInitialH = initialShape.props.h / initialShape.props.scale; - const unscaledGrowY = initialShape.props.growY / initialShape.props.scale; - let unscaledW = unscaledInitialW * scaleX; - let unscaledH = (unscaledInitialH + unscaledGrowY) * scaleY; - let overShrinkX = 0; - let overShrinkY = 0; - const min = MIN_SIZE_WITH_LABEL; - if (shape.props.text.trim()) { - let newW = Math.max(Math.abs(unscaledW), min); - let newH = Math.max(Math.abs(unscaledH), min); - if (newW < min && newH === min) newW = min; - if (newW === min && newH < min) newH = min; - const unscaledLabelSize = getUnscaledLabelSize(this.editor, { - ...shape, - props: { - ...shape.props, - w: newW * shape.props.scale, - h: newH * shape.props.scale - } - }); - const nextW = Math.max(Math.abs(unscaledW), unscaledLabelSize.w) * Math.sign(unscaledW); - const nextH = Math.max(Math.abs(unscaledH), unscaledLabelSize.h) * Math.sign(unscaledH); - overShrinkX = Math.abs(nextW) - Math.abs(unscaledW); - overShrinkY = Math.abs(nextH) - Math.abs(unscaledH); - unscaledW = nextW; - unscaledH = nextH; - } - const scaledW = unscaledW * shape.props.scale; - const scaledH = unscaledH * shape.props.scale; - const offset = new Vec(0, 0); - if (scaleX < 0) { - offset.x += scaledW; - } - if (handle === "left" || handle === "top_left" || handle === "bottom_left") { - offset.x += scaleX < 0 ? overShrinkX : -overShrinkX; - } - if (scaleY < 0) { - offset.y += scaledH; - } - if (handle === "top" || handle === "top_left" || handle === "top_right") { - offset.y += scaleY < 0 ? overShrinkY : -overShrinkY; - } - const { x, y } = offset.rot(shape.rotation).add(newPoint); - return { - x, - y, - props: { - w: Math.max(Math.abs(scaledW), 1), - h: Math.max(Math.abs(scaledH), 1), - growY: 0 - } - }; - } - onBeforeCreate(shape) { - if (!shape.props.text) { - if (shape.props.growY) { - return { - ...shape, - props: { - ...shape.props, - growY: 0 - } - }; - } else { - return; - } - } - const unscaledPrevHeight = shape.props.h / shape.props.scale; - const unscaledNextHeight = getUnscaledLabelSize(this.editor, shape).h; - let growY = null; - if (unscaledNextHeight > unscaledPrevHeight) { - growY = unscaledNextHeight - unscaledPrevHeight; - } else { - if (shape.props.growY) { - growY = 0; - } - } - if (growY !== null) { - return { - ...shape, - props: { - ...shape.props, - // scale the growY - growY: growY * shape.props.scale - } - }; - } - } - onBeforeUpdate(prev, next) { - const prevText = prev.props.text; - const nextText = next.props.text; - if (prevText === nextText && prev.props.font === next.props.font && prev.props.size === next.props.size) { - return; - } - if (prevText && !nextText) { - return { - ...next, - props: { - ...next.props, - growY: 0 - } - }; - } - const unscaledPrevWidth = prev.props.w / prev.props.scale; - const unscaledPrevHeight = prev.props.h / prev.props.scale; - const unscaledPrevGrowY = prev.props.growY / prev.props.scale; - const unscaledNextLabelSize = getUnscaledLabelSize(this.editor, next); - if (!prevText && nextText && nextText.length === 1) { - let unscaledW = Math.max(unscaledPrevWidth, unscaledNextLabelSize.w); - let unscaledH = Math.max(unscaledPrevHeight, unscaledNextLabelSize.h); - const min = MIN_SIZE_WITH_LABEL; - if (unscaledPrevWidth < min && unscaledPrevHeight < min) { - unscaledW = Math.max(unscaledW, min); - unscaledH = Math.max(unscaledH, min); - unscaledW = Math.max(unscaledW, unscaledH); - unscaledH = Math.max(unscaledW, unscaledH); - } - return { - ...next, - props: { - ...next.props, - // Scale the results - w: unscaledW * next.props.scale, - h: unscaledH * next.props.scale, - growY: 0 - } - }; - } - let growY = null; - if (unscaledNextLabelSize.h > unscaledPrevHeight) { - growY = unscaledNextLabelSize.h - unscaledPrevHeight; - } else { - if (unscaledPrevGrowY) { - growY = 0; - } - } - if (growY !== null) { - const unscaledNextWidth = next.props.w / next.props.scale; - return { - ...next, - props: { - ...next.props, - // Scale the results - growY: growY * next.props.scale, - w: Math.max(unscaledNextWidth, unscaledNextLabelSize.w) * next.props.scale - } - }; - } - if (unscaledNextLabelSize.w > unscaledPrevWidth) { - return { - ...next, - props: { - ...next.props, - // Scale the results - w: unscaledNextLabelSize.w * next.props.scale - } - }; - } - } - onDoubleClick(shape) { - if (this.editor.inputs.altKey) { - switch (shape.props.geo) { - case "rectangle": { - return { - ...shape, - props: { - geo: "check-box" - } - }; - } - case "check-box": { - return { - ...shape, - props: { - geo: "rectangle" - } - }; - } - } - } - return; - } - getInterpolatedProps(startShape, endShape, t) { - return { - ...(t > 0.5 ? endShape.props : startShape.props), - w: lerp(startShape.props.w, endShape.props.w, t), - h: lerp(startShape.props.h, endShape.props.h, t), - scale: lerp(startShape.props.scale, endShape.props.scale, t) - }; - } -} -function getUnscaledLabelSize(editor, shape) { - const { text, font, size, w } = shape.props; - if (!text) { - return { w: 0, h: 0 }; - } - const minSize = editor.textMeasure.measureText("w", { - ...TEXT_PROPS, - fontFamily: FONT_FAMILIES[font], - fontSize: LABEL_FONT_SIZES[size], - maxWidth: 100 - // ? - }); - const sizes = { - s: 2, - m: 3.5, - l: 5, - xl: 10 - }; - const textSize = editor.textMeasure.measureText(text, { - ...TEXT_PROPS, - fontFamily: FONT_FAMILIES[font], - fontSize: LABEL_FONT_SIZES[size], - minWidth: minSize.w, - maxWidth: Math.max( - // Guard because a DOM nodes can't be less 0 - 0, - // A 'w' width that we're setting as the min-width - Math.ceil(minSize.w + sizes[size]), - // The actual text size - Math.ceil(w / shape.props.scale - LABEL_PADDING * 2) - ) - }); - return { - w: textSize.w + LABEL_PADDING * 2, - h: textSize.h + LABEL_PADDING * 2 - }; -} - -function useColorSpace() { - const [supportsP3, setSupportsP3] = reactExports.useState(false); - reactExports.useEffect(() => { - const supportsSyntax = CSS.supports("color", "color(display-p3 1 1 1)"); - const query = matchMedia("(color-gamut: p3)"); - setSupportsP3(supportsSyntax && query.matches); - const onChange = () => setSupportsP3(supportsSyntax && query.matches); - query.addEventListener("change", onChange); - return () => query.removeEventListener("change", onChange); - }, []); - const forceSrgb = useValue(debugFlags.forceSrgb); - return forceSrgb || !supportsP3 ? "srgb" : "p3"; -} - -const OVERLAY_OPACITY = 0.35; -const UNDERLAY_OPACITY = 0.82; -class HighlightShapeUtil extends ShapeUtil { - static type = "highlight"; - static props = highlightShapeProps; - static migrations = highlightShapeMigrations; - hideResizeHandles(shape) { - return getIsDot(shape); - } - hideRotateHandle(shape) { - return getIsDot(shape); - } - hideSelectionBoundsFg(shape) { - return getIsDot(shape); - } - getDefaultProps() { - return { - segments: [], - color: "black", - size: "m", - isComplete: false, - isPen: false, - scale: 1 - }; - } - getGeometry(shape) { - const strokeWidth = getStrokeWidth(shape); - if (getIsDot(shape)) { - return new Circle2d({ - x: -strokeWidth / 2, - y: -strokeWidth / 2, - radius: strokeWidth / 2, - isFilled: true - }); - } - const { strokePoints, sw } = getHighlightStrokePoints(shape, strokeWidth, true); - const opts = getHighlightFreehandSettings({ strokeWidth: sw, showAsComplete: true }); - setStrokePointRadii(strokePoints, opts); - return new Polygon2d({ - points: getStrokeOutlinePoints(strokePoints, opts), - isFilled: true - }); - } - component(shape) { - const forceSolid = useHighlightForceSolid(this.editor, shape); - const strokeWidth = getStrokeWidth(shape); - return /* @__PURE__ */ jsxRuntimeExports.jsx(SVGContainer, { children: /* @__PURE__ */ jsxRuntimeExports.jsx( - HighlightRenderer, - { - shape, - forceSolid, - strokeWidth, - opacity: OVERLAY_OPACITY - } - ) }); - } - backgroundComponent(shape) { - const forceSolid = useHighlightForceSolid(this.editor, shape); - const strokeWidth = getStrokeWidth(shape); - return /* @__PURE__ */ jsxRuntimeExports.jsx(SVGContainer, { children: /* @__PURE__ */ jsxRuntimeExports.jsx( - HighlightRenderer, - { - shape, - forceSolid, - strokeWidth, - opacity: UNDERLAY_OPACITY - } - ) }); - } - indicator(shape) { - const forceSolid = useHighlightForceSolid(this.editor, shape); - const strokeWidth = getStrokeWidth(shape); - const { strokePoints, sw } = getHighlightStrokePoints(shape, strokeWidth, forceSolid); - const allPointsFromSegments = getPointsFromSegments(shape.props.segments); - let strokePath; - if (strokePoints.length < 2) { - strokePath = getIndicatorDot(allPointsFromSegments[0], sw); - } else { - strokePath = getSvgPathFromStrokePoints(strokePoints, false); - } - return /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: strokePath }); - } - toSvg(shape) { - const strokeWidth = getStrokeWidth(shape); - const forceSolid = strokeWidth < 1.5; - const scaleFactor = 1 / shape.props.scale; - return /* @__PURE__ */ jsxRuntimeExports.jsx("g", { transform: `scale(${scaleFactor})`, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - HighlightRenderer, - { - forceSolid, - strokeWidth, - shape, - opacity: OVERLAY_OPACITY - } - ) }); - } - toBackgroundSvg(shape) { - const strokeWidth = getStrokeWidth(shape); - const forceSolid = strokeWidth < 1.5; - const scaleFactor = 1 / shape.props.scale; - return /* @__PURE__ */ jsxRuntimeExports.jsx("g", { transform: `scale(${scaleFactor})`, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - HighlightRenderer, - { - forceSolid, - strokeWidth, - shape, - opacity: UNDERLAY_OPACITY - } - ) }); - } - onResize(shape, info) { - const { scaleX, scaleY } = info; - const newSegments = []; - for (const segment of shape.props.segments) { - newSegments.push({ - ...segment, - points: segment.points.map(({ x, y, z }) => { - return { - x: scaleX * x, - y: scaleY * y, - z - }; - }) - }); - } - return { - props: { - segments: newSegments - } - }; - } - getInterpolatedProps(startShape, endShape, t) { - return { - ...(t > 0.5 ? endShape.props : startShape.props), - ...endShape.props, - segments: interpolateSegments(startShape.props.segments, endShape.props.segments, t), - scale: lerp(startShape.props.scale, endShape.props.scale, t) - }; - } -} -function getShapeDot(point) { - const r = 0.1; - return `M ${point.x} ${point.y} m -${r}, 0 a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 -${r * 2},0`; -} -function getIndicatorDot(point, sw) { - const r = sw / 2; - return `M ${point.x} ${point.y} m -${r}, 0 a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 -${r * 2},0`; -} -function getHighlightStrokePoints(shape, strokeWidth, forceSolid) { - const allPointsFromSegments = getPointsFromSegments(shape.props.segments); - const showAsComplete = shape.props.isComplete || last$1(shape.props.segments)?.type === "straight"; - let sw = strokeWidth; - if (!forceSolid && !shape.props.isPen && allPointsFromSegments.length === 1) { - sw += rng(shape.id)() * (strokeWidth / 6); - } - const options = getHighlightFreehandSettings({ - strokeWidth: sw, - showAsComplete - }); - const strokePoints = getStrokePoints(allPointsFromSegments, options); - return { strokePoints, sw }; -} -function getStrokeWidth(shape) { - return FONT_SIZES[shape.props.size] * 1.12 * shape.props.scale; -} -function getIsDot(shape) { - return shape.props.segments.length === 1 && shape.props.segments[0].points.length < 2; -} -function HighlightRenderer({ - strokeWidth, - forceSolid, - shape, - opacity -}) { - const theme = useDefaultColorTheme(); - const allPointsFromSegments = getPointsFromSegments(shape.props.segments); - let sw = strokeWidth; - if (!forceSolid && !shape.props.isPen && allPointsFromSegments.length === 1) { - sw += rng(shape.id)() * (sw / 6); - } - const options = getHighlightFreehandSettings({ - strokeWidth: sw, - showAsComplete: shape.props.isComplete || last$1(shape.props.segments)?.type === "straight" - }); - const strokePoints = getStrokePoints(allPointsFromSegments, options); - const solidStrokePath = strokePoints.length > 1 ? getSvgPathFromStrokePoints(strokePoints, false) : getShapeDot(shape.props.segments[0].points[0]); - const colorSpace = useColorSpace(); - const color = theme[shape.props.color].highlight[colorSpace]; - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "path", - { - d: solidStrokePath, - strokeLinecap: "round", - fill: "none", - pointerEvents: "all", - stroke: color, - strokeWidth: sw, - opacity - } - ); -} -function useHighlightForceSolid(editor, shape) { - return useValue( - "forceSolid", - () => { - const sw = getStrokeWidth(shape); - const zoomLevel = editor.getZoomLevel(); - if (sw / zoomLevel < 1.5) { - return true; - } - return false; - }, - [editor] - ); -} - -function BrokenAssetIcon() { - return /* @__PURE__ */ jsxRuntimeExports.jsxs( - "svg", - { - width: "15", - height: "15", - viewBox: "0 0 30 30", - xmlns: "http://www.w3.org/2000/svg", - fill: "none", - stroke: "currentColor", - strokeLinecap: "round", - strokeLinejoin: "round", - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M3,11 L3,3 11,3", strokeWidth: "2" }), - /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M19,27 L27,27 L27,19", strokeWidth: "2" }), - /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M27,3 L3,27", strokeWidth: "2" }) - ] - } - ); -} - -function useImageOrVideoAsset({ - shapeId, - assetId -}) { - const editor = useEditor(); - const isExport = !!useSvgExportContext(); - const isReady = useDelaySvgExport(); - const resolveAssetUrlDebounced = reactExports.useMemo(() => debounce(resolveAssetUrl, 500), []); - const [result, setResult] = reactExports.useState(() => ({ - asset: assetId ? editor.getAsset(assetId) ?? null : null, - url: null - })); - const didAlreadyResolve = reactExports.useRef(false); - const previousUrl = reactExports.useRef(null); - reactExports.useEffect(() => { - if (!assetId) return; - let isCancelled = false; - let cancelDebounceFn; - const cleanupEffectScheduler = react("update state", () => { - if (!isExport && editor.getCulledShapes().has(shapeId)) return; - const asset = editor.getAsset(assetId); - if (!asset) return; - const shape = editor.getShape(shapeId); - if (!shape) return; - if (!asset.props.src) { - const preview = editor.getTemporaryAssetPreview(asset.id); - if (preview) { - if (previousUrl.current !== preview) { - previousUrl.current = preview; - setResult((prev) => ({ ...prev, isPlaceholder: true, url: preview })); - isReady(); - } - return; - } - } - const screenScale = editor.getZoomLevel() * (shape.props.w / asset.props.w); - function resolve(asset2, url) { - if (isCancelled) return; - if (previousUrl.current === url) return; - didAlreadyResolve.current = true; - previousUrl.current = url; - setResult({ asset: asset2, url }); - isReady(); - } - if (didAlreadyResolve.current) { - resolveAssetUrlDebounced( - editor, - assetId, - screenScale, - isExport, - (url) => resolve(asset, url) - ); - cancelDebounceFn = resolveAssetUrlDebounced.cancel; - } else { - resolveAssetUrl(editor, assetId, screenScale, isExport, (url) => resolve(asset, url)); - } - }); - return () => { - cleanupEffectScheduler(); - cancelDebounceFn?.(); - isCancelled = true; - }; - }, [editor, assetId, isExport, isReady, shapeId, resolveAssetUrlDebounced]); - return result; -} -function resolveAssetUrl(editor, assetId, screenScale, isExport, callback) { - editor.resolveAssetUrl(assetId, { - screenScale, - shouldResolveToOriginal: isExport - }).then((url) => { - callback(url); - }); -} - -function usePrefersReducedMotion() { - const [prefersReducedMotion, setPrefersReducedMotion] = reactExports.useState(false); - reactExports.useEffect(() => { - if (typeof window === "undefined" || !("matchMedia" in window)) return; - const mql = window.matchMedia("(prefers-reduced-motion: reduce)"); - const handler = () => { - setPrefersReducedMotion(mql.matches); - }; - handler(); - mql.addEventListener("change", handler); - return () => mql.removeEventListener("change", handler); - }, []); - return prefersReducedMotion; -} - -async function getDataURIFromURL(url) { - const response = await fetch(url); - const blob = await response.blob(); - return FileHelpers.blobToDataUrl(blob); -} -class ImageShapeUtil extends BaseBoxShapeUtil { - static type = "image"; - static props = imageShapeProps; - static migrations = imageShapeMigrations; - isAspectRatioLocked() { - return true; - } - canCrop() { - return true; - } - getDefaultProps() { - return { - w: 100, - h: 100, - assetId: null, - playing: true, - url: "", - crop: null, - flipX: false, - flipY: false - }; - } - onResize(shape, info) { - let resized = resizeBox(shape, info); - const { flipX, flipY } = info.initialShape.props; - const { scaleX, scaleY, mode } = info; - resized = { - ...resized, - props: { - ...resized.props, - flipX: scaleX < 0 !== flipX, - flipY: scaleY < 0 !== flipY - } - }; - if (!shape.props.crop) return resized; - const flipCropHorizontally = ( - // We used the flip horizontally feature - (// We resized the shape past it's bounds, so it flipped - mode === "scale_shape" && scaleX === -1 || mode === "resize_bounds" && flipX !== resized.props.flipX) - ); - const flipCropVertically = ( - // We used the flip vertically feature - (// We resized the shape past it's bounds, so it flipped - mode === "scale_shape" && scaleY === -1 || mode === "resize_bounds" && flipY !== resized.props.flipY) - ); - const { topLeft, bottomRight } = shape.props.crop; - resized.props.crop = { - topLeft: { - x: flipCropHorizontally ? 1 - bottomRight.x : topLeft.x, - y: flipCropVertically ? 1 - bottomRight.y : topLeft.y - }, - bottomRight: { - x: flipCropHorizontally ? 1 - topLeft.x : bottomRight.x, - y: flipCropVertically ? 1 - topLeft.y : bottomRight.y - } - }; - return resized; - } - component(shape) { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ImageShape, { shape }); - } - indicator(shape) { - const isCropping = this.editor.getCroppingShapeId() === shape.id; - if (isCropping) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { width: toDomPrecision(shape.props.w), height: toDomPrecision(shape.props.h) }); - } - async toSvg(shape) { - if (!shape.props.assetId) return null; - const asset = this.editor.getAsset(shape.props.assetId); - if (!asset) return null; - let src = await this.editor.resolveAssetUrl(shape.props.assetId, { - shouldResolveToOriginal: true - }); - if (!src) return null; - if (src.startsWith("blob:") || src.startsWith("http") || src.startsWith("/") || src.startsWith("./")) { - src = (await getDataURIFromURL(src)) || ""; - } - return /* @__PURE__ */ jsxRuntimeExports.jsx(SvgImage, { shape, src }); - } - onDoubleClickEdge(shape) { - const props = shape.props; - if (!props) return; - if (this.editor.getCroppingShapeId() !== shape.id) { - return; - } - const crop = structuredClone(props.crop) || { - topLeft: { x: 0, y: 0 }, - bottomRight: { x: 1, y: 1 } - }; - const w = 1 / (crop.bottomRight.x - crop.topLeft.x) * shape.props.w; - const h = 1 / (crop.bottomRight.y - crop.topLeft.y) * shape.props.h; - const pointDelta = new Vec(crop.topLeft.x * w, crop.topLeft.y * h).rot(shape.rotation); - const partial = { - id: shape.id, - type: shape.type, - x: shape.x - pointDelta.x, - y: shape.y - pointDelta.y, - props: { - crop: { - topLeft: { x: 0, y: 0 }, - bottomRight: { x: 1, y: 1 } - }, - w, - h - } - }; - this.editor.updateShapes([partial]); - } - getInterpolatedProps(startShape, endShape, t) { - function interpolateCrop(startShape2, endShape2) { - if (startShape2.props.crop === null && endShape2.props.crop === null) return null; - const startTL = startShape2.props.crop?.topLeft || { x: 0, y: 0 }; - const startBR = startShape2.props.crop?.bottomRight || { x: 1, y: 1 }; - const endTL = endShape2.props.crop?.topLeft || { x: 0, y: 0 }; - const endBR = endShape2.props.crop?.bottomRight || { x: 1, y: 1 }; - return { - topLeft: { x: lerp(startTL.x, endTL.x, t), y: lerp(startTL.y, endTL.y, t) }, - bottomRight: { x: lerp(startBR.x, endBR.x, t), y: lerp(startBR.y, endBR.y, t) } - }; - } - return { - ...(t > 0.5 ? endShape.props : startShape.props), - w: lerp(startShape.props.w, endShape.props.w, t), - h: lerp(startShape.props.h, endShape.props.h, t), - crop: interpolateCrop(startShape, endShape) - }; - } -} -const ImageShape = reactExports.memo(function ImageShape2({ shape }) { - const editor = useEditor(); - const { asset, url } = useImageOrVideoAsset({ - shapeId: shape.id, - assetId: shape.props.assetId - }); - const prefersReducedMotion = usePrefersReducedMotion(); - const [staticFrameSrc, setStaticFrameSrc] = reactExports.useState(""); - const [loadedUrl, setLoadedUrl] = reactExports.useState(null); - const isAnimated = getIsAnimated(editor, shape); - reactExports.useEffect(() => { - if (url && isAnimated) { - let cancelled = false; - const image = Image(); - image.onload = () => { - if (cancelled) return; - const canvas = document.createElement("canvas"); - canvas.width = image.width; - canvas.height = image.height; - const ctx = canvas.getContext("2d"); - if (!ctx) return; - ctx.drawImage(image, 0, 0); - setStaticFrameSrc(canvas.toDataURL()); - setLoadedUrl(url); - }; - image.crossOrigin = "anonymous"; - image.src = url; - return () => { - cancelled = true; - }; - } - }, [editor, isAnimated, prefersReducedMotion, url]); - const showCropPreview = useValue( - "show crop preview", - () => shape.id === editor.getOnlySelectedShapeId() && editor.getCroppingShapeId() === shape.id && editor.isIn("select.crop"), - [editor, shape.id] - ); - const reduceMotion = prefersReducedMotion && (asset?.props.mimeType?.includes("video") || isAnimated); - const containerStyle = getCroppedContainerStyle(shape); - const nextSrc = url === loadedUrl ? null : url; - const loadedSrc = reduceMotion ? staticFrameSrc : loadedUrl; - if (!url && !asset?.props.src) { - return /* @__PURE__ */ jsxRuntimeExports.jsxs( - HTMLContainer, - { - id: shape.id, - style: { - overflow: "hidden", - width: shape.props.w, - height: shape.props.h, - color: "var(--color-text-3)", - backgroundColor: "var(--color-low)", - border: "1px solid var(--color-low-border)" - }, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - "div", - { - className: classNames("tl-image-container", asset && "tl-image-container-loading"), - style: containerStyle, - children: asset ? null : /* @__PURE__ */ jsxRuntimeExports.jsx(BrokenAssetIcon, {}) - } - ), - "url" in shape.props && shape.props.url && /* @__PURE__ */ jsxRuntimeExports.jsx(HyperlinkButton, { url: shape.props.url }) - ] - } - ); - } - const crossOrigin = isAnimated ? "anonymous" : void 0; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - showCropPreview && loadedSrc && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: containerStyle, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - "img", - { - className: "tl-image", - style: { ...getFlipStyle(shape), opacity: 0.1 }, - crossOrigin, - src: loadedSrc, - referrerPolicy: "strict-origin-when-cross-origin", - draggable: false - } - ) }), - /* @__PURE__ */ jsxRuntimeExports.jsxs( - HTMLContainer, - { - id: shape.id, - style: { overflow: "hidden", width: shape.props.w, height: shape.props.h }, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: classNames("tl-image-container"), style: containerStyle, children: [ - loadedSrc && /* @__PURE__ */ jsxRuntimeExports.jsx( - "img", - { - className: "tl-image", - style: getFlipStyle(shape), - crossOrigin, - src: loadedSrc, - referrerPolicy: "strict-origin-when-cross-origin", - draggable: false - }, - loadedSrc - ), - nextSrc && /* @__PURE__ */ jsxRuntimeExports.jsx( - "img", - { - className: "tl-image", - style: getFlipStyle(shape), - crossOrigin, - src: nextSrc, - referrerPolicy: "strict-origin-when-cross-origin", - draggable: false, - onLoad: () => setLoadedUrl(nextSrc) - }, - nextSrc - ) - ] }), - shape.props.url && /* @__PURE__ */ jsxRuntimeExports.jsx(HyperlinkButton, { url: shape.props.url }) - ] - } - ) - ] }); -}); -function getIsAnimated(editor, shape) { - const asset = shape.props.assetId ? editor.getAsset(shape.props.assetId) : void 0; - if (!asset) return false; - return "mimeType" in asset.props && MediaHelpers.isAnimatedImageType(asset?.props.mimeType) || "isAnimated" in asset.props && asset.props.isAnimated; -} -function getCroppedContainerStyle(shape) { - const crop = shape.props.crop; - const topLeft = crop?.topLeft; - if (!topLeft) { - return { - width: shape.props.w, - height: shape.props.h - }; - } - const w = 1 / (crop.bottomRight.x - crop.topLeft.x) * shape.props.w; - const h = 1 / (crop.bottomRight.y - crop.topLeft.y) * shape.props.h; - const offsetX = -topLeft.x * w; - const offsetY = -topLeft.y * h; - return { - transform: `translate(${offsetX}px, ${offsetY}px)`, - width: w, - height: h - }; -} -function getFlipStyle(shape, size) { - const { flipX, flipY } = shape.props; - if (!flipX && !flipY) return void 0; - const scale = `scale(${flipX ? -1 : 1}, ${flipY ? -1 : 1})`; - const translate = size ? `translate(${flipX ? size.width : 0}px, ${flipY ? size.height : 0}px)` : ""; - return { - transform: `${translate} ${scale}`, - // in SVG, flipping around the center doesn't work so we use explicit width/height - transformOrigin: size ? "0 0" : "center center" - }; -} -function SvgImage({ shape, src }) { - const cropClipId = useUniqueSafeId(); - const containerStyle = getCroppedContainerStyle(shape); - const crop = shape.props.crop; - if (containerStyle.transform && crop) { - const { transform: cropTransform, width, height } = containerStyle; - const croppedWidth = (crop.bottomRight.x - crop.topLeft.x) * width; - const croppedHeight = (crop.bottomRight.y - crop.topLeft.y) * height; - const points = [ - new Vec(0, 0), - new Vec(croppedWidth, 0), - new Vec(croppedWidth, croppedHeight), - new Vec(0, croppedHeight) - ]; - const flip = getFlipStyle(shape, { width, height }); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("clipPath", { id: cropClipId, children: /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: points.map((p) => `${p.x},${p.y}`).join(" ") }) }) }), - /* @__PURE__ */ jsxRuntimeExports.jsx("g", { clipPath: `url(#${cropClipId})`, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - "image", - { - href: src, - width, - height, - style: flip ? { ...flip, transform: `${cropTransform} ${flip.transform}` } : { transform: cropTransform } - } - ) }) - ] }); - } else { - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "image", - { - href: src, - width: shape.props.w, - height: shape.props.h, - style: getFlipStyle(shape, { width: shape.props.w, height: shape.props.h }) - } - ); - } -} - -function getLineDrawFreehandOptions(strokeWidth) { - return { - size: strokeWidth, - thinning: 0.4, - streamline: 0, - smoothing: 0.5, - simulatePressure: true, - last: true - }; -} -function getLineStrokePoints(shape, spline, strokeWidth) { - const points = spline.vertices; - const options = getLineDrawFreehandOptions(strokeWidth); - return getStrokePoints(points, options); -} -function getLineDrawStrokeOutlinePoints(shape, spline, strokeWidth) { - const options = getLineDrawFreehandOptions(strokeWidth); - return getStrokeOutlinePoints( - setStrokePointRadii(getLineStrokePoints(shape, spline, strokeWidth), options), - options - ); -} -function getLineDrawPath(shape, spline, strokeWidth) { - const stroke = getLineDrawStrokeOutlinePoints(shape, spline, strokeWidth); - return getSvgPathFromPoints(stroke); -} -function getLineIndicatorPath(shape, spline, strokeWidth) { - if (shape.props.dash === "draw") { - const strokePoints = getLineStrokePoints(shape, spline, strokeWidth); - return getSvgPathFromStrokePoints(strokePoints); - } - return spline.getSvgPathData(); -} - -function getDrawLinePathData(id, outline, strokeWidth) { - let innerPathData = `M ${precise(outline[0])}L`; - let outerPathData2 = `M ${precise(outline[0])}L`; - const offset = strokeWidth / 3; - const roundness = strokeWidth * 2; - const random = rng(id); - let p0 = outline[0]; - let p1; - let s0 = outline[0]; - let s1; - const len = outline.length; - for (let i = 0, n = len - 1; i < n; i++) { - p1 = outline[i + 1]; - s1 = Vec.AddXY(outline[i + 1], random() * offset, random() * offset); - const delta = Vec.Sub(p1, p0); - const distance = Vec.Len(delta); - const vector = Vec.Div(delta, distance).mul(Math.min(distance / 4, roundness)); - const q0 = Vec.Add(p0, vector); - const q1 = Vec.Add(p1, vector.neg()); - const sDelta = Vec.Sub(s1, s0); - const sDistance = Vec.Len(sDelta); - const sVector = Vec.Div(sDelta, sDistance).mul(Math.min(sDistance / 4, roundness)); - const sq0 = Vec.Add(s0, sVector); - const sq1 = Vec.Add(s1, sVector.neg()); - if (i === n - 1) { - innerPathData += `${precise(q0)}L ${precise(p1)}`; - outerPathData2 += `${precise(sq0)}L ${precise(s1)}`; - } else { - innerPathData += `${precise(q0)}L ${precise(q1)}Q ${precise(p1)}`; - outerPathData2 += `${precise(sq0)}L ${precise(sq1)}Q ${precise(s1)}`; - p0 = p1; - s0 = s1; - } - } - return [innerPathData, innerPathData + outerPathData2]; -} - -const handlesCache = new WeakCache(); -class LineShapeUtil extends ShapeUtil { - static type = "line"; - static props = lineShapeProps; - static migrations = lineShapeMigrations; - hideResizeHandles() { - return true; - } - hideRotateHandle() { - return true; - } - hideSelectionBoundsFg() { - return true; - } - hideSelectionBoundsBg() { - return true; - } - getDefaultProps() { - const [start, end] = getIndices(2); - return { - dash: "draw", - size: "m", - color: "black", - spline: "line", - points: { - [start]: { id: start, index: start, x: 0, y: 0 }, - [end]: { id: end, index: end, x: 0.1, y: 0.1 } - }, - scale: 1 - }; - } - getGeometry(shape) { - return getGeometryForLineShape(shape); - } - getHandles(shape) { - return handlesCache.get(shape.props, () => { - const spline = getGeometryForLineShape(shape); - const points = linePointsToArray(shape); - const results = points.map((point) => ({ - ...point, - id: point.index, - type: "vertex", - canSnap: true - })); - for (let i = 0; i < points.length - 1; i++) { - const index = getIndexBetween(points[i].index, points[i + 1].index); - const segment = spline.segments[i]; - const point = segment.midPoint(); - results.push({ - id: index, - type: "create", - index, - x: point.x, - y: point.y, - canSnap: true - }); - } - return results.sort(sortByIndex$1); - }); - } - // Events - onResize(shape, info) { - const { scaleX, scaleY } = info; - return { - props: { - points: mapObjectMapValues(shape.props.points, (_, { id, index, x, y }) => ({ - id, - index, - x: x * scaleX, - y: y * scaleY - })) - } - }; - } - onBeforeCreate(next) { - const { - props: { points } - } = next; - const pointKeys = Object.keys(points); - if (pointKeys.length < 2) { - return; - } - const firstPoint = points[pointKeys[0]]; - const allSame = pointKeys.every((key) => { - const point = points[key]; - return point.x === firstPoint.x && point.y === firstPoint.y; - }); - if (allSame) { - const lastKey = pointKeys[pointKeys.length - 1]; - points[lastKey] = { - ...points[lastKey], - x: points[lastKey].x + 0.1, - y: points[lastKey].y + 0.1 - }; - return next; - } - return; - } - onHandleDrag(shape, { handle }) { - if (handle.type !== "vertex") return; - const newPoint = maybeSnapToGrid(new Vec(handle.x, handle.y), this.editor); - return { - ...shape, - props: { - ...shape.props, - points: { - ...shape.props.points, - [handle.id]: { id: handle.id, index: handle.index, x: newPoint.x, y: newPoint.y } - } - } - }; - } - component(shape) { - return /* @__PURE__ */ jsxRuntimeExports.jsx(SVGContainer, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(LineShapeSvg, { shape }) }); - } - indicator(shape) { - const strokeWidth = STROKE_SIZES$1[shape.props.size] * shape.props.scale; - const spline = getGeometryForLineShape(shape); - const { dash } = shape.props; - let path; - if (shape.props.spline === "line") { - const outline = spline.points; - if (dash === "solid" || dash === "dotted" || dash === "dashed") { - path = "M" + outline[0] + "L" + outline.slice(1); - } else { - const [innerPathData] = getDrawLinePathData(shape.id, outline, strokeWidth); - path = innerPathData; - } - } else { - path = getLineIndicatorPath(shape, spline, strokeWidth); - } - return /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: path }); - } - toSvg(shape) { - return /* @__PURE__ */ jsxRuntimeExports.jsx(LineShapeSvg, { shouldScale: true, shape }); - } - getHandleSnapGeometry(shape) { - const points = linePointsToArray(shape); - return { - points, - getSelfSnapPoints: (handle) => { - const index = this.getHandles(shape).filter((h) => h.type === "vertex").findIndex((h) => h.id === handle.id); - return points.filter((_, i) => Math.abs(i - index) > 1).map(Vec.From); - }, - getSelfSnapOutline: (handle) => { - const index = this.getHandles(shape).filter((h) => h.type === "vertex").findIndex((h) => h.id === handle.id); - const segments = getGeometryForLineShape(shape).segments.filter( - (_, i) => i !== index - 1 && i !== index - ); - if (!segments.length) return null; - return new Group2d({ children: segments }); - } - }; - } - getInterpolatedProps(startShape, endShape, t) { - const startPoints = linePointsToArray(startShape); - const endPoints = linePointsToArray(endShape); - const pointsToUseStart = []; - const pointsToUseEnd = []; - let index = ZERO_INDEX_KEY; - if (startPoints.length > endPoints.length) { - for (let i = 0; i < startPoints.length; i++) { - pointsToUseStart[i] = { ...startPoints[i] }; - if (endPoints[i] === void 0) { - pointsToUseEnd[i] = { ...endPoints[endPoints.length - 1], id: index }; - } else { - pointsToUseEnd[i] = { ...endPoints[i], id: index }; - } - index = getIndexAbove(index); - } - } else if (endPoints.length > startPoints.length) { - for (let i = 0; i < endPoints.length; i++) { - pointsToUseEnd[i] = { ...endPoints[i] }; - if (startPoints[i] === void 0) { - pointsToUseStart[i] = { - ...startPoints[startPoints.length - 1], - id: index - }; - } else { - pointsToUseStart[i] = { ...startPoints[i], id: index }; - } - index = getIndexAbove(index); - } - } else { - for (let i = 0; i < endPoints.length; i++) { - pointsToUseStart[i] = startPoints[i]; - pointsToUseEnd[i] = endPoints[i]; - } - } - return { - ...(t > 0.5 ? endShape.props : startShape.props), - points: Object.fromEntries( - pointsToUseStart.map((point, i) => { - const endPoint = pointsToUseEnd[i]; - return [ - point.id, - { - ...point, - x: lerp(point.x, endPoint.x, t), - y: lerp(point.y, endPoint.y, t) - } - ]; - }) - ), - scale: lerp(startShape.props.scale, endShape.props.scale, t) - }; - } -} -function linePointsToArray(shape) { - return Object.values(shape.props.points).sort(sortByIndex$1); -} -function getGeometryForLineShape(shape) { - const points = linePointsToArray(shape).map(Vec.From); - switch (shape.props.spline) { - case "cubic": { - return new CubicSpline2d({ points }); - } - case "line": { - return new Polyline2d({ points }); - } - } -} -function LineShapeSvg({ - shape, - shouldScale = false, - forceSolid = false -}) { - const theme = useDefaultColorTheme(); - const spline = getGeometryForLineShape(shape); - const { dash, color, size } = shape.props; - const scaleFactor = 1 / shape.props.scale; - const scale = shouldScale ? scaleFactor : 1; - const strokeWidth = STROKE_SIZES$1[size] * shape.props.scale; - if (shape.props.spline === "line") { - if (dash === "solid") { - const outline = spline.points; - const pathData = "M" + outline[0] + "L" + outline.slice(1); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "path", - { - d: pathData, - stroke: theme[color].solid, - strokeWidth, - fill: "none", - transform: `scale(${scale})` - } - ); - } - if (dash === "dashed" || dash === "dotted") { - return /* @__PURE__ */ jsxRuntimeExports.jsx("g", { stroke: theme[color].solid, strokeWidth, transform: `scale(${scale})`, children: spline.segments.map((segment, i) => { - const { strokeDasharray, strokeDashoffset } = forceSolid ? { strokeDasharray: "none", strokeDashoffset: "none" } : getPerfectDashProps(segment.length, strokeWidth, { - style: dash, - start: i > 0 ? "outset" : "none", - end: i < spline.segments.length - 1 ? "outset" : "none" - }); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "path", - { - strokeDasharray, - strokeDashoffset, - d: segment.getSvgPathData(true), - fill: "none" - }, - i - ); - }) }); - } - if (dash === "draw") { - const outline = spline.points; - const [_, outerPathData] = getDrawLinePathData(shape.id, outline, strokeWidth); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "path", - { - d: outerPathData, - stroke: theme[color].solid, - strokeWidth, - fill: "none", - transform: `scale(${scale})` - } - ); - } - } - if (shape.props.spline === "cubic") { - const splinePath = spline.getSvgPathData(); - if (dash === "solid") { - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "path", - { - strokeWidth, - stroke: theme[color].solid, - fill: "none", - d: splinePath, - transform: `scale(${scale})` - } - ); - } - if (dash === "dashed" || dash === "dotted") { - return /* @__PURE__ */ jsxRuntimeExports.jsx("g", { stroke: theme[color].solid, strokeWidth, transform: `scale(${scale})`, children: spline.segments.map((segment, i) => { - const { strokeDasharray, strokeDashoffset } = getPerfectDashProps( - segment.length, - strokeWidth, - { - style: dash, - start: i > 0 ? "outset" : "none", - end: i < spline.segments.length - 1 ? "outset" : "none", - forceSolid - } - ); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "path", - { - strokeDasharray, - strokeDashoffset, - d: segment.getSvgPathData(), - fill: "none" - }, - i - ); - }) }); - } - if (dash === "draw") { - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "path", - { - d: getLineDrawPath(shape, spline, strokeWidth), - strokeWidth: 1, - stroke: theme[color].solid, - fill: theme[color].solid, - transform: `scale(${scale})` - } - ); - } - } -} - -class NoteShapeUtil extends ShapeUtil { - static type = "note"; - static props = noteShapeProps; - static migrations = noteShapeMigrations; - canEdit() { - return true; - } - hideResizeHandles() { - return true; - } - hideSelectionBoundsFg() { - return false; - } - getDefaultProps() { - return { - color: "black", - size: "m", - text: "", - font: "draw", - align: "middle", - verticalAlign: "middle", - labelColor: "black", - growY: 0, - fontSizeAdjustment: 0, - url: "", - scale: 1 - }; - } - getGeometry(shape) { - const { labelHeight, labelWidth } = getLabelSize(this.editor, shape); - const { scale } = shape.props; - const lh = labelHeight * scale; - const lw = labelWidth * scale; - const nw = NOTE_SIZE * scale; - const nh = getNoteHeight(shape); - return new Group2d({ - children: [ - new Rectangle2d({ width: nw, height: nh, isFilled: true }), - new Rectangle2d({ - x: shape.props.align === "start" ? 0 : shape.props.align === "end" ? nw - lw : (nw - lw) / 2, - y: shape.props.verticalAlign === "start" ? 0 : shape.props.verticalAlign === "end" ? nh - lh : (nh - lh) / 2, - width: lw, - height: lh, - isFilled: true, - isLabel: true - }) - ] - }); - } - getHandles(shape) { - const { scale } = shape.props; - const isCoarsePointer = this.editor.getInstanceState().isCoarsePointer; - if (isCoarsePointer) return []; - const zoom = this.editor.getZoomLevel(); - if (zoom * scale < 0.25) return []; - const nh = getNoteHeight(shape); - const nw = NOTE_SIZE * scale; - const offset = CLONE_HANDLE_MARGIN / zoom * scale; - if (zoom * scale < 0.5) { - return [ - { - id: "bottom", - index: "a3", - type: "clone", - x: nw / 2, - y: nh + offset - } - ]; - } - return [ - { - id: "top", - index: "a1", - type: "clone", - x: nw / 2, - y: -offset - }, - { - id: "right", - index: "a2", - type: "clone", - x: nw + offset, - y: nh / 2 - }, - { - id: "bottom", - index: "a3", - type: "clone", - x: nw / 2, - y: nh + offset - }, - { - id: "left", - index: "a4", - type: "clone", - x: -offset, - y: nh / 2 - } - ]; - } - getText(shape) { - return shape.props.text; - } - component(shape) { - const { - id, - type, - props: { - labelColor, - scale, - color, - font, - size, - align, - text, - verticalAlign, - fontSizeAdjustment - } - } = shape; - const handleKeyDown = useNoteKeydownHandler(id); - const theme = useDefaultColorTheme(); - const nw = NOTE_SIZE * scale; - const nh = getNoteHeight(shape); - const rotation = useValue( - "shape rotation", - () => this.editor.getShapePageTransform(id)?.rotation() ?? 0, - [this.editor] - ); - const hideShadows = useValue("zoom", () => this.editor.getZoomLevel() < 0.35 / scale, [ - scale, - this.editor - ]); - const isDarkMode = useValue("dark mode", () => this.editor.user.getIsDarkMode(), [this.editor]); - const isSelected = shape.id === this.editor.getOnlySelectedShapeId(); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - "div", - { - id, - className: "tl-note__container", - style: { - width: nw, - height: nh, - backgroundColor: theme[color].note.fill, - borderBottom: hideShadows ? isDarkMode ? `${2 * scale}px solid rgb(20, 20, 20)` : `${2 * scale}px solid rgb(144, 144, 144)` : "none", - boxShadow: hideShadows ? "none" : getNoteShadow(shape.id, rotation, scale) - }, - children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TextLabel, - { - shapeId: id, - type, - font, - fontSize: (fontSizeAdjustment || LABEL_FONT_SIZES[size]) * scale, - lineHeight: TEXT_PROPS.lineHeight, - align, - verticalAlign, - text, - isNote: true, - isSelected, - labelColor: labelColor === "black" ? theme[color].note.text : theme[labelColor].fill, - wrap: true, - padding: 16 * scale, - onKeyDown: handleKeyDown - } - ) - } - ), - "url" in shape.props && shape.props.url && /* @__PURE__ */ jsxRuntimeExports.jsx(HyperlinkButton, { url: shape.props.url }) - ] }); - } - indicator(shape) { - const { scale } = shape.props; - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "rect", - { - rx: scale, - width: toDomPrecision(NOTE_SIZE * scale), - height: toDomPrecision(getNoteHeight(shape)) - } - ); - } - toSvg(shape, ctx) { - if (shape.props.text) ctx.addExportDef(getFontDefForExport(shape.props.font)); - const theme = getDefaultColorTheme({ isDarkMode: ctx.isDarkMode }); - const bounds = getBoundsForSVG(shape); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { x: 5, y: 5, rx: 1, width: NOTE_SIZE - 10, height: bounds.h, fill: "rgba(0,0,0,.1)" }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - "rect", - { - rx: 1, - width: NOTE_SIZE, - height: bounds.h, - fill: theme[shape.props.color].note.fill - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - SvgTextLabel, - { - fontSize: shape.props.fontSizeAdjustment || LABEL_FONT_SIZES[shape.props.size], - font: shape.props.font, - align: shape.props.align, - verticalAlign: shape.props.verticalAlign, - text: shape.props.text, - labelColor: theme[shape.props.color].note.text, - bounds, - stroke: false - } - ) - ] }); - } - onBeforeCreate(next) { - return getNoteSizeAdjustments(this.editor, next); - } - onBeforeUpdate(prev, next) { - if (prev.props.text === next.props.text && prev.props.font === next.props.font && prev.props.size === next.props.size) { - return; - } - return getNoteSizeAdjustments(this.editor, next); - } - onEditEnd(shape) { - const { - id, - type, - props: { text } - } = shape; - if (text.trimEnd() !== shape.props.text) { - this.editor.updateShapes([ - { - id, - type, - props: { - text: text.trimEnd() - } - } - ]); - } - } - getInterpolatedProps(startShape, endShape, t) { - return { - ...(t > 0.5 ? endShape.props : startShape.props), - scale: lerp(startShape.props.scale, endShape.props.scale, t) - }; - } -} -function getNoteSizeAdjustments(editor, shape) { - const { labelHeight, fontSizeAdjustment } = getLabelSize(editor, shape); - const growY = Math.max(0, labelHeight - NOTE_SIZE); - if (growY !== shape.props.growY || fontSizeAdjustment !== shape.props.fontSizeAdjustment) { - return { - ...shape, - props: { - ...shape.props, - growY, - fontSizeAdjustment - } - }; - } -} -function getNoteLabelSize(editor, shape) { - const { text } = shape.props; - if (!text) { - const minHeight = LABEL_FONT_SIZES[shape.props.size] * TEXT_PROPS.lineHeight + LABEL_PADDING * 2; - return { labelHeight: minHeight, labelWidth: 100, fontSizeAdjustment: 0 }; - } - const unadjustedFontSize = LABEL_FONT_SIZES[shape.props.size]; - let fontSizeAdjustment = 0; - let iterations = 0; - let labelHeight = NOTE_SIZE; - let labelWidth = NOTE_SIZE; - const FUZZ = 1; - do { - fontSizeAdjustment = Math.min(unadjustedFontSize, unadjustedFontSize - iterations); - const nextTextSize = editor.textMeasure.measureText(text, { - ...TEXT_PROPS, - fontFamily: FONT_FAMILIES[shape.props.font], - fontSize: fontSizeAdjustment, - maxWidth: NOTE_SIZE - LABEL_PADDING * 2 - FUZZ, - disableOverflowWrapBreaking: true - }); - labelHeight = nextTextSize.h + LABEL_PADDING * 2; - labelWidth = nextTextSize.w + LABEL_PADDING * 2; - if (fontSizeAdjustment <= 14) { - const nextTextSizeWithOverflowBreak = editor.textMeasure.measureText(text, { - ...TEXT_PROPS, - fontFamily: FONT_FAMILIES[shape.props.font], - fontSize: fontSizeAdjustment, - maxWidth: NOTE_SIZE - LABEL_PADDING * 2 - FUZZ - }); - labelHeight = nextTextSizeWithOverflowBreak.h + LABEL_PADDING * 2; - labelWidth = nextTextSizeWithOverflowBreak.w + LABEL_PADDING * 2; - break; - } - if (nextTextSize.scrollWidth.toFixed(0) === nextTextSize.w.toFixed(0)) { - break; - } - } while (iterations++ < 50); - return { - labelHeight, - labelWidth, - fontSizeAdjustment - }; -} -const labelSizesForNote = new WeakCache(); -function getLabelSize(editor, shape) { - return labelSizesForNote.get(shape, () => getNoteLabelSize(editor, shape)); -} -function useNoteKeydownHandler(id) { - const editor = useEditor(); - const translation = useCurrentTranslation(); - return reactExports.useCallback( - (e) => { - const shape = editor.getShape(id); - if (!shape) return; - const isTab = e.key === "Tab"; - const isCmdEnter = (e.metaKey || e.ctrlKey) && e.key === "Enter"; - if (isTab || isCmdEnter) { - e.preventDefault(); - const pageTransform = editor.getShapePageTransform(id); - const pageRotation = pageTransform.rotation(); - const isRTL = !!(translation.dir === "rtl" || isRightToLeftLanguage(shape.props.text)); - const offsetLength = (NOTE_SIZE + editor.options.adjacentShapeMargin + // If we're growing down, we need to account for the current shape's growY - (isCmdEnter && !e.shiftKey ? shape.props.growY : 0)) * shape.props.scale; - const adjacentCenter = new Vec( - isTab ? e.shiftKey != isRTL ? -1 : 1 : 0, - isCmdEnter ? e.shiftKey ? -1 : 1 : 0 - ).mul(offsetLength).add(NOTE_CENTER_OFFSET.clone().mul(shape.props.scale)).rot(pageRotation).add(pageTransform.point()); - const newNote = getNoteShapeForAdjacentPosition(editor, shape, adjacentCenter, pageRotation); - if (newNote) { - editor.markHistoryStoppingPoint("editing adjacent shape"); - startEditingShapeWithLabel( - editor, - newNote, - true - /* selectAll */ - ); - } - } - }, - [id, editor, translation.dir] - ); -} -function getNoteHeight(shape) { - return (NOTE_SIZE + shape.props.growY) * shape.props.scale; -} -function getNoteShadow(id, rotation, scale) { - const random = rng(id); - const lift = Math.abs(random()) + 0.5; - const oy = Math.cos(rotation); - const a = 5 * scale; - const b = 4 * scale; - const c = 6 * scale; - const d = 7 * scale; - return `0px ${a - lift}px ${a}px -${a}px rgba(15, 23, 31, .6), - 0px ${(b + lift * d) * Math.max(0, oy)}px ${c + lift * d}px -${b + lift * c}px rgba(15, 23, 31, ${(0.3 + lift * 0.1).toFixed(2)}), - 0px ${48 * scale}px ${10 * scale}px -${10 * scale}px inset rgba(15, 23, 44, ${((0.022 + random() * 5e-3) * ((1 + oy) / 2)).toFixed(2)})`; -} -function getBoundsForSVG(shape) { - return new Box(0, 0, NOTE_SIZE, NOTE_SIZE + shape.props.growY); -} - -function resizeScaled(shape, { - initialBounds, - scaleX, - scaleY, - newPoint -}) { - const scaleDelta = Math.max(0.01, Math.min(Math.abs(scaleX), Math.abs(scaleY))); - const offset = new Vec(0, 0); - if (scaleX < 0) { - offset.x = -(initialBounds.width * scaleDelta); - } - if (scaleY < 0) { - offset.y = -(initialBounds.height * scaleDelta); - } - const { x, y } = Vec.Add(newPoint, offset.rot(shape.rotation)); - return { - x, - y, - props: { - scale: scaleDelta * shape.props.scale - } - }; -} - -const sizeCache = new WeakCache(); -class TextShapeUtil extends ShapeUtil { - static type = "text"; - static props = textShapeProps; - static migrations = textShapeMigrations; - getDefaultProps() { - return { - color: "black", - size: "m", - w: 8, - text: "", - font: "draw", - textAlign: "start", - autoSize: true, - scale: 1 - }; - } - getMinDimensions(shape) { - return sizeCache.get(shape.props, (props) => getTextSize(this.editor, props)); - } - getGeometry(shape) { - const { scale } = shape.props; - const { width, height } = this.getMinDimensions(shape); - return new Rectangle2d({ - width: width * scale, - height: height * scale, - isFilled: true, - isLabel: true - }); - } - getText(shape) { - return shape.props.text; - } - canEdit() { - return true; - } - isAspectRatioLocked() { - return true; - } - // WAIT NO THIS IS HARD CODED IN THE RESIZE HANDLER - component(shape) { - const { - id, - props: { font, size, text, color, scale, textAlign } - } = shape; - const { width, height } = this.getMinDimensions(shape); - const isSelected = shape.id === this.editor.getOnlySelectedShapeId(); - const theme = useDefaultColorTheme(); - const handleKeyDown = useTextShapeKeydownHandler(id); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - TextLabel, - { - shapeId: id, - classNamePrefix: "tl-text-shape", - type: "text", - font, - fontSize: FONT_SIZES[size], - lineHeight: TEXT_PROPS.lineHeight, - align: textAlign, - verticalAlign: "middle", - text, - labelColor: theme[color].solid, - isSelected, - textWidth: width, - textHeight: height, - style: { - transform: `scale(${scale})`, - transformOrigin: "top left" - }, - wrap: true, - onKeyDown: handleKeyDown - } - ); - } - indicator(shape) { - const bounds = this.editor.getShapeGeometry(shape).bounds; - const editor = useEditor(); - if (shape.props.autoSize && editor.getEditingShapeId() === shape.id) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { width: toDomPrecision(bounds.width), height: toDomPrecision(bounds.height) }); - } - toSvg(shape, ctx) { - if (shape.props.text) ctx.addExportDef(getFontDefForExport(shape.props.font)); - const bounds = this.editor.getShapeGeometry(shape).bounds; - const width = bounds.width / (shape.props.scale ?? 1); - const height = bounds.height / (shape.props.scale ?? 1); - const theme = getDefaultColorTheme(ctx); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - SvgTextLabel, - { - fontSize: FONT_SIZES[shape.props.size], - font: shape.props.font, - align: shape.props.textAlign, - verticalAlign: "middle", - text: shape.props.text, - labelColor: theme[shape.props.color].solid, - bounds: new Box(0, 0, width, height), - padding: 0 - } - ); - } - onResize(shape, info) { - const { newPoint, initialBounds, initialShape, scaleX, handle } = info; - if (info.mode === "scale_shape" || handle !== "right" && handle !== "left") { - return { - id: shape.id, - type: shape.type, - ...resizeScaled(shape, info) - }; - } else { - const nextWidth = Math.max(1, Math.abs(initialBounds.width * scaleX)); - const { x, y } = scaleX < 0 ? Vec.Sub(newPoint, Vec.FromAngle(shape.rotation).mul(nextWidth)) : newPoint; - return { - id: shape.id, - type: shape.type, - x, - y, - props: { - w: nextWidth / initialShape.props.scale, - autoSize: false - } - }; - } - } - onEditEnd(shape) { - const { - id, - type, - props: { text } - } = shape; - const trimmedText = shape.props.text.trimEnd(); - if (trimmedText.length === 0) { - this.editor.deleteShapes([shape.id]); - } else { - if (trimmedText !== shape.props.text) { - this.editor.updateShapes([ - { - id, - type, - props: { - text: text.trimEnd() - } - } - ]); - } - } - } - onBeforeUpdate(prev, next) { - if (!next.props.autoSize) return; - const styleDidChange = prev.props.size !== next.props.size || prev.props.textAlign !== next.props.textAlign || prev.props.font !== next.props.font || prev.props.scale !== 1 && next.props.scale === 1; - const textDidChange = prev.props.text !== next.props.text; - if (!styleDidChange && !textDidChange) return; - const boundsA = this.getMinDimensions(prev); - const boundsB = getTextSize(this.editor, next.props); - const wA = boundsA.width * prev.props.scale; - const hA = boundsA.height * prev.props.scale; - const wB = boundsB.width * next.props.scale; - const hB = boundsB.height * next.props.scale; - let delta; - switch (next.props.textAlign) { - case "middle": { - delta = new Vec((wB - wA) / 2, textDidChange ? 0 : (hB - hA) / 2); - break; - } - case "end": { - delta = new Vec(wB - wA, textDidChange ? 0 : (hB - hA) / 2); - break; - } - default: { - if (textDidChange) break; - delta = new Vec(0, (hB - hA) / 2); - break; - } - } - if (delta) { - delta.rot(next.rotation); - const { x, y } = next; - return { - ...next, - x: x - delta.x, - y: y - delta.y, - props: { ...next.props, w: wB } - }; - } else { - return { - ...next, - props: { ...next.props, w: wB } - }; - } - } - // todo: The edge doubleclicking feels like a mistake more often than - // not, especially on multiline text. Removed June 16 2024 - // override onDoubleClickEdge = (shape: TLTextShape) => { - // // If the shape has a fixed width, set it to autoSize. - // if (!shape.props.autoSize) { - // return { - // id: shape.id, - // type: shape.type, - // props: { - // autoSize: true, - // }, - // } - // } - // // If the shape is scaled, reset the scale to 1. - // if (shape.props.scale !== 1) { - // return { - // id: shape.id, - // type: shape.type, - // props: { - // scale: 1, - // }, - // } - // } - // } -} -function getTextSize(editor, props) { - const { font, text, autoSize, size, w } = props; - const minWidth = autoSize ? 16 : Math.max(16, w); - const fontSize = FONT_SIZES[size]; - const cw = autoSize ? null : ( - // `measureText` floors the number so we need to do the same here to avoid issues. - (Math.floor(Math.max(minWidth, w))) - ); - const result = editor.textMeasure.measureText(text, { - ...TEXT_PROPS, - fontFamily: FONT_FAMILIES[font], - fontSize, - maxWidth: cw - }); - if (autoSize) { - result.w += 1; - } - return { - width: Math.max(minWidth, result.w), - height: Math.max(fontSize, result.h) - }; -} -function useTextShapeKeydownHandler(id) { - const editor = useEditor(); - return reactExports.useCallback( - (e) => { - if (editor.getEditingShapeId() !== id) return; - switch (e.key) { - case "Enter": { - if (e.ctrlKey || e.metaKey) { - editor.complete(); - } - break; - } - case "Tab": { - preventDefault(e); - if (e.shiftKey) { - TextHelpers.unindent(e.currentTarget); - } else { - TextHelpers.indent(e.currentTarget); - } - break; - } - } - }, - [editor, id] - ); -} - -class VideoShapeUtil extends BaseBoxShapeUtil { - static type = "video"; - static props = videoShapeProps; - static migrations = videoShapeMigrations; - canEdit() { - return true; - } - isAspectRatioLocked() { - return true; - } - getDefaultProps() { - return { - w: 100, - h: 100, - assetId: null, - time: 0, - playing: true, - url: "" - }; - } - component(shape) { - return /* @__PURE__ */ jsxRuntimeExports.jsx(VideoShape, { shape }); - } - indicator(shape) { - return /* @__PURE__ */ jsxRuntimeExports.jsx("rect", { width: toDomPrecision(shape.props.w), height: toDomPrecision(shape.props.h) }); - } - async toSvg(shape) { - const image = await serializeVideo(this.editor, shape); - if (!image) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx("image", { href: image, width: shape.props.w, height: shape.props.h }); - } -} -const VideoShape = reactExports.memo(function VideoShape2({ shape }) { - const editor = useEditor(); - const showControls = editor.getShapeGeometry(shape).bounds.w * editor.getZoomLevel() >= 110; - const isEditing = useIsEditing(shape.id); - const prefersReducedMotion = usePrefersReducedMotion(); - const { Spinner } = useEditorComponents(); - const { asset, url } = useImageOrVideoAsset({ - shapeId: shape.id, - assetId: shape.props.assetId - }); - const rVideo = reactExports.useRef(null); - const [isLoaded, setIsLoaded] = reactExports.useState(false); - const [isFullscreen, setIsFullscreen] = reactExports.useState(false); - reactExports.useEffect(() => { - const fullscreenChange = () => setIsFullscreen(document.fullscreenElement === rVideo.current); - document.addEventListener("fullscreenchange", fullscreenChange); - return () => document.removeEventListener("fullscreenchange", fullscreenChange); - }); - const handleLoadedData = reactExports.useCallback((e) => { - const video = e.currentTarget; - if (!video) return; - setIsLoaded(true); - }, []); - reactExports.useEffect(() => { - const video = rVideo.current; - if (!video) return; - if (isEditing) { - if (document.activeElement !== video) { - video.focus(); - } - } - }, [isEditing, isLoaded]); - reactExports.useEffect(() => { - if (prefersReducedMotion) { - const video = rVideo.current; - if (!video) return; - video.pause(); - video.currentTime = 0; - } - }, [rVideo, prefersReducedMotion]); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - HTMLContainer, - { - id: shape.id, - style: { - color: "var(--color-text-3)", - backgroundColor: asset ? "transparent" : "var(--color-low)", - border: asset ? "none" : "1px solid var(--color-low-border)" - }, - children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tl-counter-scaled", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tl-video-container", children: !asset ? /* @__PURE__ */ jsxRuntimeExports.jsx(BrokenAssetIcon, {}) : Spinner && !asset.props.src ? /* @__PURE__ */ jsxRuntimeExports.jsx(Spinner, {}) : url ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - "video", - { - ref: rVideo, - style: isEditing ? { pointerEvents: "all" } : !isLoaded ? { display: "none" } : void 0, - className: classNames("tl-video", `tl-video-shape-${shape.id.split(":")[1]}`, { - "tl-video-is-fullscreen": isFullscreen - }), - width: "100%", - height: "100%", - draggable: false, - playsInline: true, - autoPlay: true, - muted: true, - loop: true, - disableRemotePlayback: true, - disablePictureInPicture: true, - controls: isEditing && showControls, - onLoadedData: handleLoadedData, - hidden: !isLoaded, - children: /* @__PURE__ */ jsxRuntimeExports.jsx("source", { src: url }) - } - ), - !isLoaded && Spinner && /* @__PURE__ */ jsxRuntimeExports.jsx(Spinner, {}) - ] }) : null }) }) - } - ), - "url" in shape.props && shape.props.url && /* @__PURE__ */ jsxRuntimeExports.jsx(HyperlinkButton, { url: shape.props.url }) - ] }); -}); -async function serializeVideo(editor, shape) { - const assetUrl = await editor.resolveAssetUrl(shape.props.assetId, { - shouldResolveToOriginal: true - }); - if (!assetUrl) return null; - const video = await MediaHelpers.loadVideo(assetUrl); - return MediaHelpers.getVideoFrameAsDataUrl(video, 0); -} - -const defaultShapeUtils = [ - TextShapeUtil, - BookmarkShapeUtil, - DrawShapeUtil, - GeoShapeUtil, - NoteShapeUtil, - LineShapeUtil, - FrameShapeUtil, - ArrowShapeUtil, - HighlightShapeUtil, - EmbedShapeUtil, - ImageShapeUtil, - VideoShapeUtil -]; - -function registerDefaultSideEffects(editor) { - return editor.sideEffects.register({ - instance_page_state: { - afterChange: (prev, next) => { - if (prev.croppingShapeId !== next.croppingShapeId) { - const isInCroppingState = editor.isIn("select.crop"); - if (!prev.croppingShapeId && next.croppingShapeId) { - if (!isInCroppingState) { - editor.setCurrentTool("select.crop.idle"); - } - } else if (prev.croppingShapeId && !next.croppingShapeId) { - if (isInCroppingState) { - editor.setCurrentTool("select.idle"); - } - } - } - if (prev.editingShapeId !== next.editingShapeId) { - if (!prev.editingShapeId && next.editingShapeId) { - if (!editor.isIn("select.editing_shape")) { - const shape = editor.getEditingShape(); - if (shape && shape.type === "text" && editor.isInAny("text.pointing", "select.resizing") && editor.getInstanceState().isToolLocked) { - editor.setCurrentTool("select.editing_shape", { - isCreatingTextWhileToolLocked: true - }); - } else { - editor.setCurrentTool("select.editing_shape"); - } - } - } else if (prev.editingShapeId && !next.editingShapeId) { - if (editor.isIn("select.editing_shape")) { - editor.setCurrentTool("select.idle"); - } - } - } - } - } - }); -} - -class Erasing extends StateNode { - static id = "erasing"; - info = {}; - scribbleId = "id"; - markId = ""; - excludedShapeIds = /* @__PURE__ */ new Set(); - onEnter(info) { - this.markId = this.editor.markHistoryStoppingPoint("erase scribble begin"); - this.info = info; - const { originPagePoint } = this.editor.inputs; - this.excludedShapeIds = new Set( - this.editor.getCurrentPageShapes().filter((shape) => { - if (this.editor.isShapeOrAncestorLocked(shape)) return true; - if (this.editor.isShapeOfType(shape, "group") || this.editor.isShapeOfType(shape, "frame")) { - const pointInShapeShape = this.editor.getPointInShapeSpace(shape, originPagePoint); - const geometry = this.editor.getShapeGeometry(shape); - return geometry.bounds.containsPoint(pointInShapeShape); - } - return false; - }).map((shape) => shape.id) - ); - const scribble = this.editor.scribbles.addScribble({ - color: "muted-1", - size: 12 - }); - this.scribbleId = scribble.id; - this.update(); - } - pushPointToScribble() { - const { x, y } = this.editor.inputs.currentPagePoint; - this.editor.scribbles.addPoint(this.scribbleId, x, y); - } - onExit() { - this.editor.setErasingShapes([]); - this.editor.scribbles.stop(this.scribbleId); - } - onPointerMove() { - this.update(); - } - onPointerUp() { - this.complete(); - } - onCancel() { - this.cancel(); - } - onComplete() { - this.complete(); - } - update() { - const { editor, excludedShapeIds } = this; - const erasingShapeIds = editor.getErasingShapeIds(); - const zoomLevel = editor.getZoomLevel(); - const currentPageShapes = editor.getCurrentPageRenderingShapesSorted(); - const { - inputs: { currentPagePoint, previousPagePoint } - } = editor; - this.pushPointToScribble(); - const erasing = new Set(erasingShapeIds); - const minDist = this.editor.options.hitTestMargin / zoomLevel; - for (const shape of currentPageShapes) { - if (editor.isShapeOfType(shape, "group")) continue; - const pageMask = editor.getShapeMask(shape.id); - if (pageMask && !pointInPolygon(currentPagePoint, pageMask)) { - continue; - } - const geometry = editor.getShapeGeometry(shape); - const pageTransform = editor.getShapePageTransform(shape); - if (!geometry || !pageTransform) continue; - const pt = pageTransform.clone().invert(); - const A = pt.applyToPoint(previousPagePoint); - const B = pt.applyToPoint(currentPagePoint); - const { bounds } = geometry; - if (bounds.minX - minDist > Math.max(A.x, B.x) || bounds.minY - minDist > Math.max(A.y, B.y) || bounds.maxX + minDist < Math.min(A.x, B.x) || bounds.maxY + minDist < Math.min(A.y, B.y)) { - continue; - } - if (geometry.hitTestLineSegment(A, B, minDist)) { - erasing.add(editor.getOutermostSelectableShape(shape).id); - } - } - this.editor.setErasingShapes([...erasing].filter((id) => !excludedShapeIds.has(id))); - } - complete() { - const { editor } = this; - editor.deleteShapes(editor.getCurrentPageState().erasingShapeIds); - this.parent.transition("idle"); - } - cancel() { - const { editor } = this; - editor.bailToMark(this.markId); - this.parent.transition("idle", this.info); - } -} - -let Idle$5 = class Idle extends StateNode { - static id = "idle"; - onPointerDown(info) { - this.parent.transition("pointing", info); - } - onCancel() { - this.editor.setCurrentTool("select"); - } -}; - -let Pointing$2 = class Pointing extends StateNode { - static id = "pointing"; - onEnter() { - const zoomLevel = this.editor.getZoomLevel(); - const currentPageShapesSorted = this.editor.getCurrentPageRenderingShapesSorted(); - const { - inputs: { currentPagePoint } - } = this.editor; - const erasing = /* @__PURE__ */ new Set(); - const initialSize = erasing.size; - for (let n = currentPageShapesSorted.length, i = n - 1; i >= 0; i--) { - const shape = currentPageShapesSorted[i]; - if (this.editor.isShapeOrAncestorLocked(shape) || this.editor.isShapeOfType(shape, "group")) { - continue; - } - if (this.editor.isPointInShape(shape, currentPagePoint, { - hitInside: false, - margin: this.editor.options.hitTestMargin / zoomLevel - })) { - const hitShape = this.editor.getOutermostSelectableShape(shape); - if (this.editor.isShapeOfType(hitShape, "frame") && erasing.size > initialSize) { - break; - } - erasing.add(hitShape.id); - } - } - this.editor.setErasingShapes([...erasing]); - } - onLongPress(info) { - this.startErasing(info); - } - onExit(_info, to) { - if (to !== "erasing") { - this.editor.setErasingShapes([]); - } - } - onPointerMove(info) { - if (this.editor.inputs.isDragging) { - this.startErasing(info); - } - } - onPointerUp() { - this.complete(); - } - onCancel() { - this.cancel(); - } - onComplete() { - this.complete(); - } - onInterrupt() { - this.cancel(); - } - startErasing(info) { - this.parent.transition("erasing", info); - } - complete() { - const erasingShapeIds = this.editor.getErasingShapeIds(); - if (erasingShapeIds.length) { - this.editor.markHistoryStoppingPoint("erase end"); - this.editor.deleteShapes(erasingShapeIds); - } - this.parent.transition("idle"); - } - cancel() { - this.parent.transition("idle"); - } -}; - -class EraserTool extends StateNode { - static id = "eraser"; - static initial = "idle"; - static isLockable = false; - static children() { - return [Idle$5, Pointing$2, Erasing]; - } - onEnter() { - this.editor.setCursor({ type: "cross", rotation: 0 }); - } -} - -class Dragging extends StateNode { - static id = "dragging"; - initialCamera = new Vec(); - onEnter() { - this.initialCamera = Vec.From(this.editor.getCamera()); - this.update(); - } - onPointerMove() { - this.update(); - } - onPointerUp() { - this.complete(); - } - onCancel() { - this.parent.transition("idle"); - } - onComplete() { - this.complete(); - } - update() { - const { initialCamera, editor } = this; - const { currentScreenPoint, originScreenPoint } = editor.inputs; - const delta = Vec.Sub(currentScreenPoint, originScreenPoint).div(editor.getZoomLevel()); - if (delta.len2() === 0) return; - editor.setCamera(initialCamera.clone().add(delta)); - } - complete() { - const { editor } = this; - const { pointerVelocity } = editor.inputs; - const velocityAtPointerUp = Math.min(pointerVelocity.len(), 2); - if (velocityAtPointerUp > 0.1) { - this.editor.slideCamera({ speed: velocityAtPointerUp, direction: pointerVelocity }); - } - this.parent.transition("idle"); - } -} - -let Idle$4 = class Idle extends StateNode { - static id = "idle"; - onEnter() { - this.editor.setCursor({ type: "grab", rotation: 0 }); - } - onPointerDown(info) { - this.parent.transition("pointing", info); - } - onCancel() { - this.editor.setCurrentTool("select"); - } -}; - -let Pointing$1 = class Pointing extends StateNode { - static id = "pointing"; - onEnter() { - this.editor.stopCameraAnimation(); - this.editor.setCursor({ type: "grabbing", rotation: 0 }); - } - onLongPress() { - this.startDragging(); - } - onPointerMove() { - if (this.editor.inputs.isDragging) { - this.startDragging(); - } - } - startDragging() { - this.parent.transition("dragging"); - } - onPointerUp() { - this.complete(); - } - onCancel() { - this.complete(); - } - onComplete() { - this.complete(); - } - onInterrupt() { - this.complete(); - } - complete() { - this.parent.transition("idle"); - } -}; - -class HandTool extends StateNode { - static id = "hand"; - static initial = "idle"; - static isLockable = false; - static children() { - return [Idle$4, Pointing$1, Dragging]; - } - onDoubleClick(info) { - if (info.phase === "settle") { - const { currentScreenPoint } = this.editor.inputs; - this.editor.zoomIn(currentScreenPoint, { - animation: { duration: 220, easing: EASINGS.easeOutQuint } - }); - } - } - onTripleClick(info) { - if (info.phase === "settle") { - const { currentScreenPoint } = this.editor.inputs; - this.editor.zoomOut(currentScreenPoint, { - animation: { duration: 320, easing: EASINGS.easeOutQuint } - }); - } - } - onQuadrupleClick(info) { - if (info.phase === "settle") { - const zoomLevel = this.editor.getZoomLevel(); - const { - inputs: { currentScreenPoint } - } = this.editor; - if (zoomLevel === 1) { - this.editor.zoomToFit({ animation: { duration: 400, easing: EASINGS.easeOutQuint } }); - } else { - this.editor.resetZoom(currentScreenPoint, { - animation: { duration: 320, easing: EASINGS.easeOutQuint } - }); - } - } - } -} - -let Idle$3 = class Idle extends StateNode { - static id = "idle"; - onPointerDown(info) { - this.parent.transition("lasering", info); - } -}; - -class Lasering extends StateNode { - static id = "lasering"; - scribbleId = "id"; - onEnter() { - const scribble = this.editor.scribbles.addScribble({ - color: "laser", - opacity: 0.7, - size: 4, - delay: this.editor.options.laserDelayMs, - shrink: 0.05, - taper: true - }); - this.scribbleId = scribble.id; - this.pushPointToScribble(); - } - onExit() { - this.editor.scribbles.stop(this.scribbleId); - } - onPointerMove() { - this.pushPointToScribble(); - } - onPointerUp() { - this.complete(); - } - pushPointToScribble() { - const { x, y } = this.editor.inputs.currentPagePoint; - this.editor.scribbles.addPoint(this.scribbleId, x, y); - } - onCancel() { - this.cancel(); - } - onComplete() { - this.complete(); - } - complete() { - this.parent.transition("idle"); - } - cancel() { - this.parent.transition("idle"); - } -} - -class LaserTool extends StateNode { - static id = "laser"; - static initial = "idle"; - static children() { - return [Idle$3, Lasering]; - } - static isLockable = false; - onEnter() { - this.editor.setCursor({ type: "cross", rotation: 0 }); - } -} - -class Brushing extends StateNode { - static id = "brushing"; - info = {}; - initialSelectedShapeIds = []; - excludedShapeIds = /* @__PURE__ */ new Set(); - isWrapMode = false; - // The shape that the brush started on - initialStartShape = null; - onEnter(info) { - const { altKey, currentPagePoint } = this.editor.inputs; - this.isWrapMode = this.editor.user.getIsWrapMode(); - if (altKey) { - this.parent.transition("scribble_brushing", info); - return; - } - this.excludedShapeIds = new Set( - this.editor.getCurrentPageShapes().filter( - (shape) => this.editor.isShapeOfType(shape, "group") || this.editor.isShapeOrAncestorLocked(shape) - ).map((shape) => shape.id) - ); - this.info = info; - this.initialSelectedShapeIds = this.editor.getSelectedShapeIds().slice(); - this.initialStartShape = this.editor.getShapesAtPoint(currentPagePoint)[0]; - this.hitTestShapes(); - } - onExit() { - this.initialSelectedShapeIds = []; - this.editor.updateInstanceState({ brush: null }); - } - onTick({ elapsed }) { - const { editor } = this; - editor.edgeScrollManager.updateEdgeScrolling(elapsed); - } - onPointerMove() { - this.hitTestShapes(); - } - onPointerUp() { - this.complete(); - } - onComplete() { - this.complete(); - } - onCancel(info) { - this.editor.setSelectedShapes(this.initialSelectedShapeIds); - this.parent.transition("idle", info); - } - onKeyDown(info) { - if (this.editor.inputs.altKey) { - this.parent.transition("scribble_brushing", info); - } else { - this.hitTestShapes(); - } - } - onKeyUp() { - this.hitTestShapes(); - } - complete() { - this.hitTestShapes(); - this.parent.transition("idle"); - } - hitTestShapes() { - const { editor, excludedShapeIds, isWrapMode } = this; - const { - inputs: { originPagePoint, currentPagePoint, shiftKey, ctrlKey } - } = editor; - const results = new Set(shiftKey ? this.initialSelectedShapeIds : []); - const isWrapping = isWrapMode ? !ctrlKey : ctrlKey; - const brush = Box.FromPoints([originPagePoint, currentPagePoint]); - const { corners } = brush; - let A, B, shape, pageBounds, pageTransform, localCorners; - const currentPageShapes = editor.getCurrentPageRenderingShapesSorted(); - const currentPageId = editor.getCurrentPageId(); - testAllShapes: for (let i = 0, n = currentPageShapes.length; i < n; i++) { - shape = currentPageShapes[i]; - if (excludedShapeIds.has(shape.id) || results.has(shape.id)) continue testAllShapes; - pageBounds = editor.getShapePageBounds(shape); - if (!pageBounds) continue testAllShapes; - if (brush.contains(pageBounds)) { - this.handleHit(shape, currentPagePoint, currentPageId, results, corners); - continue testAllShapes; - } - if (isWrapping || editor.isShapeOfType(shape, "frame")) { - continue testAllShapes; - } - if (brush.collides(pageBounds)) { - pageTransform = editor.getShapePageTransform(shape); - if (!pageTransform) continue testAllShapes; - localCorners = pageTransform.clone().invert().applyToPoints(corners); - const geometry = editor.getShapeGeometry(shape); - hitTestBrushEdges: for (let i2 = 0; i2 < 4; i2++) { - A = localCorners[i2]; - B = localCorners[(i2 + 1) % 4]; - if (geometry.hitTestLineSegment(A, B, 0)) { - this.handleHit(shape, currentPagePoint, currentPageId, results, corners); - break hitTestBrushEdges; - } - } - } - } - const currentBrush = editor.getInstanceState().brush; - if (!currentBrush || !brush.equals(currentBrush)) { - editor.updateInstanceState({ brush: { ...brush.toJson() } }); - } - const current = editor.getSelectedShapeIds(); - if (current.length !== results.size || current.some((id) => !results.has(id))) { - editor.setSelectedShapes(Array.from(results)); - } - } - onInterrupt() { - this.editor.updateInstanceState({ brush: null }); - } - handleHit(shape, currentPagePoint, currentPageId, results, corners) { - if (shape.parentId === currentPageId) { - results.add(shape.id); - return; - } - const selectedShape = this.editor.getOutermostSelectableShape(shape); - const pageMask = this.editor.getShapeMask(selectedShape.id); - if (pageMask && !polygonsIntersect(pageMask, corners) && !pointInPolygon(currentPagePoint, pageMask)) { - return; - } - results.add(selectedShape.id); - } -} - -const CursorTypeMap = { - bottom: "ns-resize", - top: "ns-resize", - left: "ew-resize", - right: "ew-resize", - bottom_left: "nesw-resize", - bottom_right: "nwse-resize", - top_left: "nwse-resize", - top_right: "nesw-resize", - bottom_left_rotate: "swne-rotate", - bottom_right_rotate: "senw-rotate", - top_left_rotate: "nwse-rotate", - top_right_rotate: "nesw-rotate", - mobile_rotate: "grabbing" -}; -class PointingResizeHandle extends StateNode { - static id = "pointing_resize_handle"; - info = {}; - updateCursor() { - const selected = this.editor.getSelectedShapes(); - const cursorType = CursorTypeMap[this.info.handle]; - this.editor.setCursor({ - type: cursorType, - rotation: selected.length === 1 ? this.editor.getSelectionRotation() : 0 - }); - } - onEnter(info) { - this.info = info; - this.updateCursor(); - } - onPointerMove() { - if (this.editor.inputs.isDragging) { - this.startResizing(); - } - } - onLongPress() { - this.startResizing(); - } - startResizing() { - if (this.editor.getIsReadonly()) return; - this.parent.transition("resizing", this.info); - } - onPointerUp() { - this.complete(); - } - onCancel() { - this.cancel(); - } - onComplete() { - this.cancel(); - } - onInterrupt() { - this.cancel(); - } - complete() { - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, {}); - } else { - this.parent.transition("idle"); - } - } - cancel() { - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, {}); - } else { - this.parent.transition("idle"); - } - } -} - -const MIN_CROP_SIZE = 8; - -class Cropping extends StateNode { - static id = "cropping"; - info = {}; - markId = ""; - snapshot = {}; - onEnter(info) { - this.info = info; - this.markId = this.editor.markHistoryStoppingPoint("cropping"); - this.snapshot = this.createSnapshot(); - this.updateShapes(); - } - onPointerMove() { - this.updateShapes(); - } - onPointerUp() { - this.complete(); - } - onComplete() { - this.complete(); - } - onCancel() { - this.cancel(); - } - updateCursor() { - const selectedShape = this.editor.getSelectedShapes()[0]; - if (!selectedShape) return; - const cursorType = CursorTypeMap[this.info.handle]; - this.editor.setCursor({ type: cursorType, rotation: this.editor.getSelectionRotation() }); - } - getDefaultCrop() { - return { - topLeft: { x: 0, y: 0 }, - bottomRight: { x: 1, y: 1 } - }; - } - updateShapes() { - const { shape, cursorHandleOffset } = this.snapshot; - if (!shape) return; - const util = this.editor.getShapeUtil("image"); - if (!util) return; - const props = shape.props; - const currentPagePoint = this.editor.inputs.currentPagePoint.clone().sub(cursorHandleOffset); - const originPagePoint = this.editor.inputs.originPagePoint.clone().sub(cursorHandleOffset); - const change = currentPagePoint.clone().sub(originPagePoint).rot(-shape.rotation); - const crop = props.crop ?? this.getDefaultCrop(); - const newCrop = structuredClone(crop); - const newPoint = new Vec(shape.x, shape.y); - const pointDelta = new Vec(0, 0); - const w = 1 / (crop.bottomRight.x - crop.topLeft.x) * props.w; - const h = 1 / (crop.bottomRight.y - crop.topLeft.y) * props.h; - let hasCropChanged = false; - switch (this.info.handle) { - case "top": - case "top_left": - case "top_right": { - if (h < MIN_CROP_SIZE) break; - hasCropChanged = true; - newCrop.topLeft.y = newCrop.topLeft.y + change.y / h; - const heightAfterCrop = h * (newCrop.bottomRight.y - newCrop.topLeft.y); - if (heightAfterCrop < MIN_CROP_SIZE) { - newCrop.topLeft.y = newCrop.bottomRight.y - MIN_CROP_SIZE / h; - pointDelta.y = (newCrop.topLeft.y - crop.topLeft.y) * h; - } else { - if (newCrop.topLeft.y <= 0) { - newCrop.topLeft.y = 0; - pointDelta.y = (newCrop.topLeft.y - crop.topLeft.y) * h; - } else { - pointDelta.y = change.y; - } - } - break; - } - case "bottom": - case "bottom_left": - case "bottom_right": { - if (h < MIN_CROP_SIZE) break; - hasCropChanged = true; - newCrop.bottomRight.y = Math.min(1, newCrop.bottomRight.y + change.y / h); - const heightAfterCrop = h * (newCrop.bottomRight.y - newCrop.topLeft.y); - if (heightAfterCrop < MIN_CROP_SIZE) { - newCrop.bottomRight.y = newCrop.topLeft.y + MIN_CROP_SIZE / h; - } - break; - } - } - switch (this.info.handle) { - case "left": - case "top_left": - case "bottom_left": { - if (w < MIN_CROP_SIZE) break; - hasCropChanged = true; - newCrop.topLeft.x = newCrop.topLeft.x + change.x / w; - const widthAfterCrop = w * (newCrop.bottomRight.x - newCrop.topLeft.x); - if (widthAfterCrop < MIN_CROP_SIZE) { - newCrop.topLeft.x = newCrop.bottomRight.x - MIN_CROP_SIZE / w; - pointDelta.x = (newCrop.topLeft.x - crop.topLeft.x) * w; - } else { - if (newCrop.topLeft.x <= 0) { - newCrop.topLeft.x = 0; - pointDelta.x = (newCrop.topLeft.x - crop.topLeft.x) * w; - } else { - pointDelta.x = change.x; - } - } - break; - } - case "right": - case "top_right": - case "bottom_right": { - if (w < MIN_CROP_SIZE) break; - hasCropChanged = true; - newCrop.bottomRight.x = Math.min(1, newCrop.bottomRight.x + change.x / w); - const widthAfterCrop = w * (newCrop.bottomRight.x - newCrop.topLeft.x); - if (widthAfterCrop < MIN_CROP_SIZE) { - newCrop.bottomRight.x = newCrop.topLeft.x + MIN_CROP_SIZE / w; - } - break; - } - } - if (!hasCropChanged) return; - newPoint.add(pointDelta.rot(shape.rotation)); - const partial = { - id: shape.id, - type: shape.type, - x: newPoint.x, - y: newPoint.y, - props: { - crop: newCrop, - w: (newCrop.bottomRight.x - newCrop.topLeft.x) * w, - h: (newCrop.bottomRight.y - newCrop.topLeft.y) * h - } - }; - this.editor.updateShapes([partial]); - this.updateCursor(); - } - complete() { - this.updateShapes(); - kickoutOccludedShapes(this.editor, [this.snapshot.shape.id]); - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, this.info); - } else { - this.editor.setCroppingShape(null); - this.editor.setCurrentTool("select.idle"); - } - } - cancel() { - this.editor.bailToMark(this.markId); - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, this.info); - } else { - this.editor.setCroppingShape(null); - this.editor.setCurrentTool("select.idle"); - } - } - createSnapshot() { - const selectionRotation = this.editor.getSelectionRotation(); - const { - inputs: { originPagePoint } - } = this.editor; - const shape = this.editor.getOnlySelectedShape(); - const selectionBounds = this.editor.getSelectionRotatedPageBounds(); - const dragHandlePoint = Vec.RotWith( - selectionBounds.getHandlePoint(this.info.handle), - selectionBounds.point, - selectionRotation - ); - const cursorHandleOffset = Vec.Sub(originPagePoint, dragHandlePoint); - return { - shape, - cursorHandleOffset - }; - } -} - -function getHitShapeOnCanvasPointerDown(editor, hitLabels = false) { - const zoomLevel = editor.getZoomLevel(); - const { - inputs: { currentPagePoint } - } = editor; - return ( - // hovered shape at point - (// selected shape at point - editor.getShapeAtPoint(currentPagePoint, { - hitInside: false, - hitLabels, - margin: editor.options.hitTestMargin / zoomLevel, - renderingOnly: true - }) ?? editor.getSelectedShapeAtPoint(currentPagePoint)) - ); -} - -function getTranslateCroppedImageChange(editor, shape, delta) { - if (!shape) { - throw Error("Needs to translate a cropped shape!"); - } - const { crop: oldCrop } = shape.props; - if (!oldCrop) { - return; - } - const flatten = editor.inputs.shiftKey ? Math.abs(delta.x) < Math.abs(delta.y) ? "x" : "y" : null; - if (flatten === "x") { - delta.x = 0; - } else if (flatten === "y") { - delta.y = 0; - } - delta.rot(-shape.rotation); - const w = 1 / (oldCrop.bottomRight.x - oldCrop.topLeft.x) * shape.props.w; - const h = 1 / (oldCrop.bottomRight.y - oldCrop.topLeft.y) * shape.props.h; - const yCrop = oldCrop.bottomRight.y - oldCrop.topLeft.y; - const xCrop = oldCrop.bottomRight.x - oldCrop.topLeft.x; - const newCrop = structuredClone(oldCrop); - newCrop.topLeft.x = Math.min(1 - xCrop, Math.max(0, newCrop.topLeft.x - delta.x / w)); - newCrop.topLeft.y = Math.min(1 - yCrop, Math.max(0, newCrop.topLeft.y - delta.y / h)); - newCrop.bottomRight.x = newCrop.topLeft.x + xCrop; - newCrop.bottomRight.y = newCrop.topLeft.y + yCrop; - const partial = { - id: shape.id, - type: shape.type, - props: { - crop: newCrop - } - }; - return partial; -} - -let Idle$2 = class Idle extends StateNode { - static id = "idle"; - onEnter() { - this.editor.setCursor({ type: "default", rotation: 0 }); - const onlySelectedShape = this.editor.getOnlySelectedShape(); - if (onlySelectedShape) { - this.editor.setCroppingShape(onlySelectedShape.id); - } - } - onExit() { - this.editor.setCursor({ type: "default", rotation: 0 }); - } - onCancel() { - this.editor.setCroppingShape(null); - this.editor.setCurrentTool("select.idle", {}); - } - onPointerDown(info) { - if (info.accelKey) { - this.cancel(); - this.editor.root.handleEvent(info); - return; - } - switch (info.target) { - case "canvas": { - const hitShape = getHitShapeOnCanvasPointerDown(this.editor); - if (hitShape && !this.editor.isShapeOfType(hitShape, "group")) { - this.onPointerDown({ - ...info, - shape: hitShape, - target: "shape" - }); - return; - } - this.cancel(); - this.editor.root.handleEvent(info); - break; - } - case "shape": { - if (info.shape.id === this.editor.getCroppingShapeId()) { - this.editor.setCurrentTool("select.crop.pointing_crop", info); - return; - } else { - if (this.editor.getShapeUtil(info.shape)?.canCrop(info.shape)) { - this.editor.setCroppingShape(info.shape.id); - this.editor.setSelectedShapes([info.shape.id]); - this.editor.setCurrentTool("select.crop.pointing_crop", info); - } else { - this.cancel(); - this.editor.root.handleEvent(info); - } - } - break; - } - case "selection": { - switch (info.handle) { - case "mobile_rotate": - case "top_left_rotate": - case "top_right_rotate": - case "bottom_left_rotate": - case "bottom_right_rotate": { - this.editor.setCurrentTool("select.pointing_rotate_handle", { - ...info, - onInteractionEnd: "select.crop.idle" - }); - break; - } - case "top": - case "right": - case "bottom": - case "left": - case "top_left": - case "top_right": - case "bottom_left": - case "bottom_right": { - this.editor.setCurrentTool("select.crop.pointing_crop_handle", { - ...info, - onInteractionEnd: "select.crop.idle" - }); - break; - } - default: { - this.cancel(); - } - } - break; - } - } - } - onDoubleClick(info) { - if (this.editor.inputs.shiftKey || info.phase !== "up") return; - const croppingShapeId = this.editor.getCroppingShapeId(); - if (!croppingShapeId) return; - const shape = this.editor.getShape(croppingShapeId); - if (!shape) return; - const util = this.editor.getShapeUtil(shape); - if (!util) return; - if (info.target === "selection") { - util.onDoubleClickEdge?.(shape); - return; - } - this.cancel(); - this.editor.root.handleEvent(info); - } - onKeyDown() { - this.nudgeCroppingImage(false); - } - onKeyRepeat() { - this.nudgeCroppingImage(true); - } - onKeyUp(info) { - switch (info.code) { - case "Enter": { - this.editor.setCroppingShape(null); - this.editor.setCurrentTool("select.idle", {}); - break; - } - } - } - cancel() { - this.editor.setCroppingShape(null); - this.editor.setCurrentTool("select.idle", {}); - } - nudgeCroppingImage(ephemeral = false) { - const { - editor: { - inputs: { keys } - } - } = this; - const shiftKey = keys.has("ShiftLeft"); - const delta = new Vec(0, 0); - if (keys.has("ArrowLeft")) delta.x += 1; - if (keys.has("ArrowRight")) delta.x -= 1; - if (keys.has("ArrowUp")) delta.y += 1; - if (keys.has("ArrowDown")) delta.y -= 1; - if (delta.equals(new Vec(0, 0))) return; - if (shiftKey) delta.mul(10); - const shape = this.editor.getShape(this.editor.getCroppingShapeId()); - if (!shape) return; - const partial = getTranslateCroppedImageChange(this.editor, shape, delta); - if (partial) { - if (!ephemeral) { - this.editor.markHistoryStoppingPoint("translate crop"); - } - this.editor.updateShapes([partial]); - } - } -}; - -class PointingCrop extends StateNode { - static id = "pointing_crop"; - onCancel() { - this.editor.setCurrentTool("select.crop.idle", {}); - } - onPointerMove(info) { - if (this.editor.inputs.isDragging) { - this.editor.setCurrentTool("select.crop.translating_crop", info); - } - } - onPointerUp(info) { - this.editor.setCurrentTool("select.crop.idle", info); - } -} - -class PointingCropHandle extends StateNode { - static id = "pointing_crop_handle"; - info = {}; - onEnter(info) { - this.info = info; - this.parent.setCurrentToolIdMask(info.onInteractionEnd); - const selectedShape = this.editor.getSelectedShapes()[0]; - if (!selectedShape) return; - const cursorType = CursorTypeMap[this.info.handle]; - this.editor.setCursor({ type: cursorType, rotation: this.editor.getSelectionRotation() }); - this.editor.setCroppingShape(selectedShape.id); - } - onExit() { - this.editor.setCursor({ type: "default", rotation: 0 }); - this.parent.setCurrentToolIdMask(void 0); - } - onPointerMove() { - if (this.editor.inputs.isDragging) { - this.startCropping(); - } - } - onLongPress() { - this.startCropping(); - } - startCropping() { - if (this.editor.getIsReadonly()) return; - this.parent.transition("cropping", { - ...this.info, - onInteractionEnd: this.info.onInteractionEnd - }); - } - onPointerUp() { - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, this.info); - } else { - this.editor.setCroppingShape(null); - this.editor.setCurrentTool("select.idle"); - } - } - onCancel() { - this.cancel(); - } - onComplete() { - this.cancel(); - } - onInterrupt() { - this.cancel(); - } - cancel() { - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, this.info); - } else { - this.editor.setCroppingShape(null); - this.editor.setCurrentTool("select.idle"); - } - } -} - -class TranslatingCrop extends StateNode { - static id = "translating_crop"; - info = {}; - markId = ""; - snapshot = {}; - onEnter(info) { - this.info = info; - this.snapshot = this.createSnapshot(); - this.markId = this.editor.markHistoryStoppingPoint("translating_crop"); - this.editor.setCursor({ type: "move", rotation: 0 }); - this.updateShapes(); - } - onExit() { - this.editor.setCursor({ type: "default", rotation: 0 }); - } - onPointerMove() { - this.updateShapes(); - } - onPointerUp() { - this.complete(); - } - onComplete() { - this.complete(); - } - onCancel() { - this.cancel(); - } - onKeyDown(info) { - switch (info.key) { - case "Alt": - case "Shift": { - this.updateShapes(); - return; - } - } - } - onKeyUp(info) { - switch (info.key) { - case "Enter": { - this.complete(); - return; - } - case "Alt": - case "Shift": { - this.updateShapes(); - } - } - } - complete() { - this.updateShapes(); - this.editor.setCurrentTool("select.crop.idle", this.info); - } - cancel() { - this.editor.bailToMark(this.markId); - this.editor.setCurrentTool("select.crop.idle", this.info); - } - createSnapshot() { - const shape = this.editor.getOnlySelectedShape(); - return { shape }; - } - updateShapes() { - const shape = this.snapshot.shape; - if (!shape) return; - const { originPagePoint, currentPagePoint } = this.editor.inputs; - const delta = currentPagePoint.clone().sub(originPagePoint); - const partial = getTranslateCroppedImageChange(this.editor, shape, delta); - if (partial) { - this.editor.updateShapes([partial]); - } - } -} - -class Crop extends StateNode { - static id = "crop"; - static initial = "idle"; - static children() { - return [Idle$2, TranslatingCrop, PointingCrop, PointingCropHandle, Cropping]; - } - markId = ""; - onEnter() { - this.didExit = false; - this.markId = this.editor.markHistoryStoppingPoint("crop"); - } - didExit = false; - onExit() { - if (!this.didExit) { - this.didExit = true; - this.editor.squashToMark(this.markId); - } - } - onCancel() { - if (!this.didExit) { - this.didExit = true; - this.editor.bailToMark(this.markId); - } - } -} - -class DraggingHandle extends StateNode { - static id = "dragging_handle"; - shapeId = ""; - initialHandle = {}; - initialAdjacentHandle = null; - initialPagePoint = {}; - markId = ""; - initialPageTransform; - initialPageRotation; - info = {}; - isPrecise = false; - isPreciseId = null; - pointingId = null; - onEnter(info) { - const { shape, isCreating, creatingMarkId, handle } = info; - this.info = info; - this.parent.setCurrentToolIdMask(info.onInteractionEnd); - this.shapeId = shape.id; - this.markId = ""; - if (isCreating) { - if (creatingMarkId) { - this.markId = creatingMarkId; - } else { - const markId = this.editor.getMarkIdMatching( - `creating:${this.editor.getOnlySelectedShapeId()}` - ); - if (markId) { - this.markId = markId; - } - } - } else { - this.markId = this.editor.markHistoryStoppingPoint("dragging handle"); - } - this.initialHandle = structuredClone(handle); - if (this.editor.isShapeOfType(shape, "line")) { - if (this.initialHandle.type === "create") { - this.editor.updateShape({ - ...shape, - props: { - points: { - ...shape.props.points, - [handle.index]: { id: handle.index, index: handle.index, x: handle.x, y: handle.y } - } - } - }); - const handlesAfter = this.editor.getShapeHandles(shape); - const handleAfter = handlesAfter.find((h) => h.index === handle.index); - this.initialHandle = structuredClone(handleAfter); - } - } - this.initialPageTransform = this.editor.getShapePageTransform(shape); - this.initialPageRotation = this.initialPageTransform.rotation(); - this.initialPagePoint = this.editor.inputs.originPagePoint.clone(); - this.editor.setCursor({ type: isCreating ? "cross" : "grabbing", rotation: 0 }); - const handles = this.editor.getShapeHandles(shape).sort(sortByIndex$1); - const index = handles.findIndex((h) => h.id === info.handle.id); - this.initialAdjacentHandle = null; - for (let i = index + 1; i < handles.length; i++) { - const handle2 = handles[i]; - if (handle2.type === "vertex" && handle2.id !== "middle" && handle2.id !== info.handle.id) { - this.initialAdjacentHandle = handle2; - break; - } - } - if (!this.initialAdjacentHandle) { - for (let i = handles.length - 1; i >= 0; i--) { - const handle2 = handles[i]; - if (handle2.type === "vertex" && handle2.id !== "middle" && handle2.id !== info.handle.id) { - this.initialAdjacentHandle = handle2; - break; - } - } - } - if (this.editor.isShapeOfType(shape, "arrow")) { - const initialBinding = getArrowBindings(this.editor, shape)[info.handle.id]; - this.isPrecise = false; - if (initialBinding) { - this.editor.setHintingShapes([initialBinding.toId]); - this.isPrecise = initialBinding.props.isPrecise; - if (this.isPrecise) { - this.isPreciseId = initialBinding.toId; - } else { - this.resetExactTimeout(); - } - } else { - this.editor.setHintingShapes([]); - } - } - this.update(); - this.editor.select(this.shapeId); - } - // Only relevant to arrows - exactTimeout = -1; - // Only relevant to arrows - resetExactTimeout() { - if (this.exactTimeout !== -1) { - this.clearExactTimeout(); - } - this.exactTimeout = this.editor.timers.setTimeout(() => { - if (this.getIsActive() && !this.isPrecise) { - this.isPrecise = true; - this.isPreciseId = this.pointingId; - this.update(); - } - this.exactTimeout = -1; - }, 750); - } - // Only relevant to arrows - clearExactTimeout() { - if (this.exactTimeout !== -1) { - clearTimeout(this.exactTimeout); - this.exactTimeout = -1; - } - } - onPointerMove() { - this.update(); - } - onKeyDown() { - this.update(); - } - onKeyUp() { - this.update(); - } - onPointerUp() { - this.complete(); - } - onComplete() { - this.update(); - this.complete(); - } - onCancel() { - this.cancel(); - } - onExit() { - this.parent.setCurrentToolIdMask(void 0); - this.editor.setHintingShapes([]); - this.editor.snaps.clearIndicators(); - this.editor.setCursor({ type: "default", rotation: 0 }); - } - complete() { - this.editor.snaps.clearIndicators(); - kickoutOccludedShapes(this.editor, [this.shapeId]); - const { onInteractionEnd } = this.info; - if (this.editor.getInstanceState().isToolLocked && onInteractionEnd) { - this.editor.setCurrentTool(onInteractionEnd, { shapeId: this.shapeId }); - return; - } - this.parent.transition("idle"); - } - cancel() { - this.editor.bailToMark(this.markId); - this.editor.snaps.clearIndicators(); - const { onInteractionEnd } = this.info; - if (onInteractionEnd) { - this.editor.setCurrentTool(onInteractionEnd, { shapeId: this.shapeId }); - return; - } - this.parent.transition("idle"); - } - update() { - const { editor, shapeId, initialPagePoint } = this; - const { initialHandle, initialPageRotation, initialAdjacentHandle } = this; - const hintingShapeIds = this.editor.getHintingShapeIds(); - const isSnapMode = this.editor.user.getIsSnapMode(); - const { - snaps, - inputs: { currentPagePoint, shiftKey, ctrlKey, altKey, pointerVelocity } - } = editor; - const initial = this.info.shape; - const shape = editor.getShape(shapeId); - if (!shape) return; - const util = editor.getShapeUtil(shape); - let point = currentPagePoint.clone().sub(initialPagePoint).rot(-initialPageRotation).add(initialHandle); - if (shiftKey && initialAdjacentHandle && initialHandle.id !== "middle") { - const angle = Vec.Angle(initialAdjacentHandle, point); - const snappedAngle = snapAngle(angle, 24); - const angleDifference = snappedAngle - angle; - point = Vec.RotWith(point, initialAdjacentHandle, angleDifference); - } - editor.snaps.clearIndicators(); - let nextHandle = { ...initialHandle, x: point.x, y: point.y }; - if (initialHandle.canSnap && (isSnapMode ? !ctrlKey : ctrlKey)) { - const pageTransform = editor.getShapePageTransform(shape.id); - if (!pageTransform) throw Error("Expected a page transform"); - const snap = snaps.handles.snapHandle({ currentShapeId: shapeId, handle: nextHandle }); - if (snap) { - snap.nudge.rot(-editor.getShapeParentTransform(shape).rotation()); - point.add(snap.nudge); - nextHandle = { ...initialHandle, x: point.x, y: point.y }; - } - } - const changes = util.onHandleDrag?.(shape, { - handle: nextHandle, - isPrecise: this.isPrecise || altKey, - initial - }); - const next = { id: shape.id, type: shape.type, ...changes }; - if (initialHandle.type === "vertex" && this.editor.isShapeOfType(shape, "arrow")) { - const bindingAfter = getArrowBindings(editor, shape)[initialHandle.id]; - if (bindingAfter) { - if (hintingShapeIds[0] !== bindingAfter.toId) { - editor.setHintingShapes([bindingAfter.toId]); - this.pointingId = bindingAfter.toId; - this.isPrecise = pointerVelocity.len() < 0.5 || altKey; - this.isPreciseId = this.isPrecise ? bindingAfter.toId : null; - this.resetExactTimeout(); - } - } else { - if (hintingShapeIds.length > 0) { - editor.setHintingShapes([]); - this.pointingId = null; - this.isPrecise = false; - this.isPreciseId = null; - this.resetExactTimeout(); - } - } - } - if (changes) { - editor.updateShapes([next]); - } - } -} - -function getTextLabels(geometry) { - if (geometry.isLabel) { - return [geometry]; - } - if (geometry instanceof Group2d) { - return geometry.children.filter((child) => child.isLabel); - } - return []; -} - -class EditingShape extends StateNode { - static id = "editing_shape"; - hitShapeForPointerUp = null; - info = {}; - onEnter(info) { - const editingShape = this.editor.getEditingShape(); - if (!editingShape) throw Error("Entered editing state without an editing shape"); - this.hitShapeForPointerUp = null; - this.info = info; - if (info.isCreatingTextWhileToolLocked) { - this.parent.setCurrentToolIdMask("text"); - } - updateHoveredShapeId(this.editor); - this.editor.select(editingShape); - } - onExit() { - const { editingShapeId } = this.editor.getCurrentPageState(); - if (!editingShapeId) return; - this.editor.setEditingShape(null); - updateHoveredShapeId.cancel(); - const shape = this.editor.getShape(editingShapeId); - const util = this.editor.getShapeUtil(shape); - util.onEditEnd?.(shape); - if (this.info.isCreatingTextWhileToolLocked) { - this.parent.setCurrentToolIdMask(void 0); - this.editor.setCurrentTool("text", {}); - } - } - onPointerMove(info) { - if (this.hitShapeForPointerUp && this.editor.inputs.isDragging) { - if (this.editor.getIsReadonly()) return; - if (this.hitShapeForPointerUp.isLocked) return; - this.editor.select(this.hitShapeForPointerUp); - this.parent.transition("translating", info); - this.hitShapeForPointerUp = null; - return; - } - switch (info.target) { - case "shape": - case "canvas": { - updateHoveredShapeId(this.editor); - return; - } - } - } - onPointerDown(info) { - this.hitShapeForPointerUp = null; - switch (info.target) { - case "shape": { - const { shape: selectingShape } = info; - const editingShape = this.editor.getEditingShape(); - if (!editingShape) { - throw Error("Expected an editing shape!"); - } - const geometry = this.editor.getShapeUtil(selectingShape).getGeometry(selectingShape); - const textLabels = getTextLabels(geometry); - const textLabel = textLabels.length === 1 ? textLabels[0] : void 0; - const isEmptyTextShape = this.editor.isShapeOfType(editingShape, "text") && editingShape.props.text.trim() === ""; - if (textLabel && !isEmptyTextShape) { - const pointInShapeSpace = this.editor.getPointInShapeSpace( - selectingShape, - this.editor.inputs.currentPagePoint - ); - if (textLabel.bounds.containsPoint(pointInShapeSpace, 0) && textLabel.hitTestPoint(pointInShapeSpace)) { - if (selectingShape.id === editingShape.id) { - return; - } else { - this.hitShapeForPointerUp = selectingShape; - this.editor.markHistoryStoppingPoint("editing on pointer up"); - this.editor.select(selectingShape.id); - return; - } - } - } else { - if (selectingShape.id === editingShape.id) { - if (this.editor.isShapeOfType(selectingShape, "frame")) { - this.editor.setEditingShape(null); - this.parent.transition("idle", info); - } - } else { - this.parent.transition("pointing_shape", info); - return; - } - return; - } - break; - } - } - this.parent.transition("idle", info); - this.editor.root.handleEvent(info); - } - onPointerUp(info) { - const hitShape = this.hitShapeForPointerUp; - if (!hitShape) return; - this.hitShapeForPointerUp = null; - const util = this.editor.getShapeUtil(hitShape); - if (hitShape.isLocked) return; - if (this.editor.getIsReadonly()) { - if (!util.canEditInReadOnly(hitShape)) { - this.parent.transition("pointing_shape", info); - return; - } - } - this.editor.select(hitShape.id); - this.editor.setEditingShape(hitShape.id); - updateHoveredShapeId(this.editor); - } - onComplete(info) { - this.parent.transition("idle", info); - } - onCancel(info) { - this.parent.transition("idle", info); - } -} - -function getShouldEnterCropMode(editor) { - const onlySelectedShape = editor.getOnlySelectedShape(); - return !!(onlySelectedShape && !editor.isShapeOrAncestorLocked(onlySelectedShape) && editor.getShapeUtil(onlySelectedShape).canCrop(onlySelectedShape)); -} - -function selectOnCanvasPointerUp(editor, info) { - const selectedShapeIds = editor.getSelectedShapeIds(); - const { currentPagePoint } = editor.inputs; - const { shiftKey, altKey, accelKey } = info; - const additiveSelectionKey = shiftKey || accelKey; - const hitShape = editor.getShapeAtPoint(currentPagePoint, { - hitInside: false, - margin: editor.options.hitTestMargin / editor.getZoomLevel(), - hitLabels: true, - renderingOnly: true, - filter: (shape) => !shape.isLocked - }); - if (hitShape) { - const outermostSelectableShape = editor.getOutermostSelectableShape(hitShape); - if (additiveSelectionKey && !altKey) { - editor.cancelDoubleClick(); - if (selectedShapeIds.includes(outermostSelectableShape.id)) { - editor.markHistoryStoppingPoint("deselecting shape"); - editor.deselect(outermostSelectableShape); - } else { - editor.markHistoryStoppingPoint("shift selecting shape"); - editor.setSelectedShapes([...selectedShapeIds, outermostSelectableShape.id]); - } - } else { - let shapeToSelect = void 0; - if (outermostSelectableShape === hitShape) { - shapeToSelect = hitShape; - } else { - if (outermostSelectableShape.id === editor.getFocusedGroupId() || selectedShapeIds.includes(outermostSelectableShape.id)) { - shapeToSelect = hitShape; - } else { - shapeToSelect = outermostSelectableShape; - } - } - if (shapeToSelect && !selectedShapeIds.includes(shapeToSelect.id)) { - editor.markHistoryStoppingPoint("selecting shape"); - editor.select(shapeToSelect.id); - } - } - } else { - if (additiveSelectionKey) { - return; - } else { - if (selectedShapeIds.length > 0) { - editor.markHistoryStoppingPoint("selecting none"); - editor.selectNone(); - } - const focusedGroupId = editor.getFocusedGroupId(); - if (isShapeId(focusedGroupId)) { - const groupShape = editor.getShape(focusedGroupId); - if (!editor.isPointInShape(groupShape, currentPagePoint, { margin: 0, hitInside: true })) { - editor.setFocusedGroup(null); - } - } - } - } -} - -const SKIPPED_KEYS_FOR_AUTO_EDITING = [ - "Delete", - "Backspace", - "[", - "]", - "Enter", - " ", - "Shift", - "Tab" -]; -let Idle$1 = class Idle extends StateNode { - static id = "idle"; - onEnter() { - this.parent.setCurrentToolIdMask(void 0); - updateHoveredShapeId(this.editor); - this.editor.setCursor({ type: "default", rotation: 0 }); - } - onExit() { - updateHoveredShapeId.cancel(); - } - onPointerMove() { - updateHoveredShapeId(this.editor); - } - onPointerDown(info) { - const shouldEnterCropMode = info.ctrlKey && getShouldEnterCropMode(this.editor); - switch (info.target) { - case "canvas": { - const hitShape = getHitShapeOnCanvasPointerDown(this.editor); - if (hitShape && !hitShape.isLocked) { - this.onPointerDown({ - ...info, - shape: hitShape, - target: "shape" - }); - return; - } - const selectedShapeIds = this.editor.getSelectedShapeIds(); - const onlySelectedShape = this.editor.getOnlySelectedShape(); - const { - inputs: { currentPagePoint } - } = this.editor; - if (selectedShapeIds.length > 1 || onlySelectedShape && !this.editor.getShapeUtil(onlySelectedShape).hideSelectionBoundsBg(onlySelectedShape)) { - if (isPointInRotatedSelectionBounds(this.editor, currentPagePoint)) { - this.onPointerDown({ - ...info, - target: "selection" - }); - return; - } - } - this.parent.transition("pointing_canvas", info); - break; - } - case "shape": { - const { shape } = info; - if (this.isOverArrowLabelTest(shape)) { - this.parent.transition("pointing_arrow_label", info); - break; - } - if (this.editor.isShapeOrAncestorLocked(shape)) { - this.parent.transition("pointing_canvas", info); - break; - } - this.parent.transition("pointing_shape", info); - break; - } - case "handle": { - if (this.editor.getIsReadonly()) break; - if (this.editor.inputs.altKey) { - this.parent.transition("pointing_shape", info); - } else { - this.parent.transition("pointing_handle", info); - } - break; - } - case "selection": { - switch (info.handle) { - case "mobile_rotate": - case "top_left_rotate": - case "top_right_rotate": - case "bottom_left_rotate": - case "bottom_right_rotate": { - if (info.accelKey) { - this.parent.transition("brushing", info); - break; - } - this.parent.transition("pointing_rotate_handle", info); - break; - } - case "top": - case "right": - case "bottom": - case "left": - case "top_left": - case "top_right": - case "bottom_left": - case "bottom_right": { - if (shouldEnterCropMode) { - this.parent.transition("crop.pointing_crop_handle", info); - } else { - if (info.accelKey) { - this.parent.transition("brushing", info); - break; - } - this.parent.transition("pointing_resize_handle", info); - } - break; - } - default: { - const hoveredShape = this.editor.getHoveredShape(); - if (hoveredShape && !this.editor.getSelectedShapeIds().includes(hoveredShape.id) && !hoveredShape.isLocked) { - this.onPointerDown({ - ...info, - shape: hoveredShape, - target: "shape" - }); - return; - } - this.parent.transition("pointing_selection", info); - } - } - break; - } - } - } - onDoubleClick(info) { - if (this.editor.inputs.shiftKey || info.phase !== "up") return; - if (info.ctrlKey || info.shiftKey) return; - switch (info.target) { - case "canvas": { - const hoveredShape = this.editor.getHoveredShape(); - const hitShape = hoveredShape && !this.editor.isShapeOfType(hoveredShape, "group") ? hoveredShape : this.editor.getSelectedShapeAtPoint(this.editor.inputs.currentPagePoint) ?? this.editor.getShapeAtPoint(this.editor.inputs.currentPagePoint, { - margin: this.editor.options.hitTestMargin / this.editor.getZoomLevel(), - hitInside: false - }); - const focusedGroupId = this.editor.getFocusedGroupId(); - if (hitShape) { - if (this.editor.isShapeOfType(hitShape, "group")) { - selectOnCanvasPointerUp(this.editor, info); - return; - } else { - const parent = this.editor.getShape(hitShape.parentId); - if (parent && this.editor.isShapeOfType(parent, "group")) { - if (focusedGroupId && parent.id === focusedGroupId) ; else { - selectOnCanvasPointerUp(this.editor, info); - return; - } - } - } - this.onDoubleClick({ - ...info, - shape: hitShape, - target: "shape" - }); - return; - } - if (!this.editor.inputs.shiftKey) { - this.handleDoubleClickOnCanvas(info); - } - break; - } - case "selection": { - if (this.editor.getIsReadonly()) break; - const onlySelectedShape = this.editor.getOnlySelectedShape(); - if (onlySelectedShape) { - const util = this.editor.getShapeUtil(onlySelectedShape); - if (!this.canInteractWithShapeInReadOnly(onlySelectedShape)) { - return; - } - if (info.handle === "right" || info.handle === "left" || info.handle === "top" || info.handle === "bottom") { - const change = util.onDoubleClickEdge?.(onlySelectedShape); - if (change) { - this.editor.markHistoryStoppingPoint("double click edge"); - this.editor.updateShapes([change]); - kickoutOccludedShapes(this.editor, [onlySelectedShape.id]); - return; - } - } - if (util.canCrop(onlySelectedShape) && !this.editor.isShapeOrAncestorLocked(onlySelectedShape)) { - this.parent.transition("crop", info); - return; - } - if (this.shouldStartEditingShape(onlySelectedShape)) { - this.startEditingShape( - onlySelectedShape, - info, - true - /* select all */ - ); - } - } - break; - } - case "shape": { - const { shape } = info; - const util = this.editor.getShapeUtil(shape); - if (shape.type !== "video" && shape.type !== "embed" && this.editor.getIsReadonly()) break; - if (util.onDoubleClick) { - const change = util.onDoubleClick?.(shape); - if (change) { - this.editor.updateShapes([change]); - return; - } - } - if (util.canCrop(shape) && !this.editor.isShapeOrAncestorLocked(shape)) { - this.editor.markHistoryStoppingPoint("select and crop"); - this.editor.select(info.shape?.id); - this.parent.transition("crop", info); - return; - } - if (this.shouldStartEditingShape(shape)) { - this.startEditingShape( - shape, - info, - true - /* select all */ - ); - } else { - this.handleDoubleClickOnCanvas(info); - } - break; - } - case "handle": { - if (this.editor.getIsReadonly()) break; - const { shape, handle } = info; - const util = this.editor.getShapeUtil(shape); - const changes = util.onDoubleClickHandle?.(shape, handle); - if (changes) { - this.editor.updateShapes([changes]); - } else { - if (this.shouldStartEditingShape(shape)) { - this.startEditingShape( - shape, - info, - true - /* select all */ - ); - } - } - } - } - } - onRightClick(info) { - switch (info.target) { - case "canvas": { - const hoveredShape = this.editor.getHoveredShape(); - const hitShape = hoveredShape && !this.editor.isShapeOfType(hoveredShape, "group") ? hoveredShape : this.editor.getShapeAtPoint(this.editor.inputs.currentPagePoint, { - margin: this.editor.options.hitTestMargin / this.editor.getZoomLevel(), - hitInside: false, - hitLabels: true, - hitLocked: true, - hitFrameInside: true, - renderingOnly: true - }); - if (hitShape) { - this.onRightClick({ - ...info, - shape: hitShape, - target: "shape" - }); - return; - } - const selectedShapeIds = this.editor.getSelectedShapeIds(); - const onlySelectedShape = this.editor.getOnlySelectedShape(); - const { - inputs: { currentPagePoint } - } = this.editor; - if (selectedShapeIds.length > 1 || onlySelectedShape && !this.editor.getShapeUtil(onlySelectedShape).hideSelectionBoundsBg(onlySelectedShape)) { - if (isPointInRotatedSelectionBounds(this.editor, currentPagePoint)) { - this.onRightClick({ - ...info, - target: "selection" - }); - return; - } - } - this.editor.selectNone(); - break; - } - case "shape": { - const { selectedShapeIds } = this.editor.getCurrentPageState(); - const { shape } = info; - const targetShape = this.editor.getOutermostSelectableShape( - shape, - (parent) => !selectedShapeIds.includes(parent.id) - ); - if (!selectedShapeIds.includes(targetShape.id) && !this.editor.findShapeAncestor( - targetShape, - (shape2) => selectedShapeIds.includes(shape2.id) - )) { - this.editor.markHistoryStoppingPoint("selecting shape"); - this.editor.setSelectedShapes([targetShape.id]); - } - break; - } - } - } - onCancel() { - if (this.editor.getFocusedGroupId() !== this.editor.getCurrentPageId() && this.editor.getSelectedShapeIds().length > 0) { - this.editor.popFocusedGroupId(); - } else { - this.editor.markHistoryStoppingPoint("clearing selection"); - this.editor.selectNone(); - } - } - onKeyDown(info) { - switch (info.code) { - case "ArrowLeft": - case "ArrowRight": - case "ArrowUp": - case "ArrowDown": { - this.nudgeSelectedShapes(false); - return; - } - } - if (debugFlags["editOnType"].get()) { - if (!SKIPPED_KEYS_FOR_AUTO_EDITING.includes(info.key) && !info.altKey && !info.ctrlKey) { - const onlySelectedShape = this.editor.getOnlySelectedShape(); - if (onlySelectedShape && // If it's a note shape, then edit on type - this.editor.isShapeOfType(onlySelectedShape, "note") && // If it's not locked or anything - this.shouldStartEditingShape(onlySelectedShape)) { - this.startEditingShape( - onlySelectedShape, - { - ...info, - target: "shape", - shape: onlySelectedShape - }, - true - /* select all */ - ); - return; - } - } - } - } - onKeyRepeat(info) { - switch (info.code) { - case "ArrowLeft": - case "ArrowRight": - case "ArrowUp": - case "ArrowDown": { - this.nudgeSelectedShapes(true); - break; - } - } - } - onKeyUp(info) { - switch (info.code) { - case "Enter": { - const selectedShapes = this.editor.getSelectedShapes(); - if (selectedShapes.every((shape) => this.editor.isShapeOfType(shape, "group"))) { - this.editor.setSelectedShapes( - selectedShapes.flatMap((shape) => this.editor.getSortedChildIdsForParent(shape.id)) - ); - return; - } - const onlySelectedShape = this.editor.getOnlySelectedShape(); - if (onlySelectedShape && this.shouldStartEditingShape(onlySelectedShape)) { - this.startEditingShape( - onlySelectedShape, - { - ...info, - target: "shape", - shape: onlySelectedShape - }, - true - /* select all */ - ); - return; - } - if (getShouldEnterCropMode(this.editor)) { - this.parent.transition("crop", info); - } - break; - } - } - } - shouldStartEditingShape(shape = this.editor.getOnlySelectedShape()) { - if (!shape) return false; - if (this.editor.isShapeOrAncestorLocked(shape) && shape.type !== "embed") return false; - if (!this.canInteractWithShapeInReadOnly(shape)) return false; - return this.editor.getShapeUtil(shape).canEdit(shape); - } - startEditingShape(shape, info, shouldSelectAll) { - if (this.editor.isShapeOrAncestorLocked(shape) && shape.type !== "embed") return; - this.editor.markHistoryStoppingPoint("editing shape"); - startEditingShapeWithLabel(this.editor, shape, shouldSelectAll); - this.parent.transition("editing_shape", info); - } - isOverArrowLabelTest(shape) { - if (!shape) return false; - const pointInShapeSpace = this.editor.getPointInShapeSpace( - shape, - this.editor.inputs.currentPagePoint - ); - if (this.editor.isShapeOfType(shape, "arrow")) { - const labelGeometry = this.editor.getShapeGeometry(shape).children[1]; - if (labelGeometry && pointInPolygon(pointInShapeSpace, labelGeometry.vertices)) { - return true; - } - } - return false; - } - handleDoubleClickOnCanvas(info) { - if (this.editor.getIsReadonly()) return; - if (!this.editor.options.createTextOnCanvasDoubleClick) return; - this.editor.markHistoryStoppingPoint("creating text shape"); - const id = createShapeId(); - const { x, y } = this.editor.inputs.currentPagePoint; - this.editor.createShapes([ - { - id, - type: "text", - x, - y, - props: { - text: "", - autoSize: true - } - } - ]); - const shape = this.editor.getShape(id); - if (!shape) return; - const util = this.editor.getShapeUtil(shape); - if (this.editor.getIsReadonly()) { - if (!util.canEditInReadOnly(shape)) { - return; - } - } - this.editor.setEditingShape(id); - this.editor.select(id); - this.parent.transition("editing_shape", info); - } - nudgeSelectedShapes(ephemeral = false) { - const { - editor: { - inputs: { keys } - } - } = this; - const shiftKey = keys.has("ShiftLeft"); - const delta = new Vec(0, 0); - if (keys.has("ArrowLeft")) delta.x -= 1; - if (keys.has("ArrowRight")) delta.x += 1; - if (keys.has("ArrowUp")) delta.y -= 1; - if (keys.has("ArrowDown")) delta.y += 1; - if (delta.equals(new Vec(0, 0))) return; - if (!ephemeral) this.editor.markHistoryStoppingPoint("nudge shapes"); - const { gridSize } = this.editor.getDocumentSettings(); - const step = this.editor.getInstanceState().isGridMode ? shiftKey ? gridSize * GRID_INCREMENT : gridSize : shiftKey ? MAJOR_NUDGE_FACTOR : MINOR_NUDGE_FACTOR; - const selectedShapeIds = this.editor.getSelectedShapeIds(); - this.editor.nudgeShapes(selectedShapeIds, delta.mul(step)); - kickoutOccludedShapes(this.editor, selectedShapeIds); - } - canInteractWithShapeInReadOnly(shape) { - if (!this.editor.getIsReadonly()) return true; - const util = this.editor.getShapeUtil(shape); - if (util.canEditInReadOnly(shape)) return true; - return false; - } -}; -const MAJOR_NUDGE_FACTOR = 10; -const MINOR_NUDGE_FACTOR = 1; -const GRID_INCREMENT = 5; -function isPointInRotatedSelectionBounds(editor, point) { - const selectionBounds = editor.getSelectionRotatedPageBounds(); - if (!selectionBounds) return false; - const selectionRotation = editor.getSelectionRotation(); - if (!selectionRotation) return selectionBounds.containsPoint(point); - return pointInPolygon( - point, - selectionBounds.corners.map((c) => Vec.RotWith(c, selectionBounds.point, selectionRotation)) - ); -} - -class PointingArrowLabel extends StateNode { - static id = "pointing_arrow_label"; - shapeId = ""; - markId = ""; - wasAlreadySelected = false; - didDrag = false; - didCtrlOnEnter = false; - info = {}; - updateCursor() { - this.editor.setCursor({ type: "grabbing", rotation: 0 }); - } - onEnter(info) { - const { shape } = info; - this.parent.setCurrentToolIdMask(info.onInteractionEnd); - this.info = info; - this.shapeId = shape.id; - this.didDrag = false; - this.didCtrlOnEnter = info.accelKey; - this.wasAlreadySelected = this.editor.getOnlySelectedShapeId() === shape.id; - this.updateCursor(); - const geometry = this.editor.getShapeGeometry(shape); - const labelGeometry = geometry.children[1]; - if (!labelGeometry) { - throw Error(`Expected to find an arrow label geometry for shape: ${shape.id}`); - } - const { currentPagePoint } = this.editor.inputs; - const pointInShapeSpace = this.editor.getPointInShapeSpace(shape, currentPagePoint); - this._labelDragOffset = Vec.Sub(labelGeometry.center, pointInShapeSpace); - this.markId = this.editor.markHistoryStoppingPoint("label-drag start"); - this.editor.setSelectedShapes([this.shapeId]); - } - onExit() { - this.parent.setCurrentToolIdMask(void 0); - this.editor.setCursor({ type: "default", rotation: 0 }); - } - _labelDragOffset = new Vec(0, 0); - onPointerMove() { - const { isDragging } = this.editor.inputs; - if (!isDragging) return; - if (this.didCtrlOnEnter) { - this.parent.transition("brushing", this.info); - return; - } - const shape = this.editor.getShape(this.shapeId); - if (!shape) return; - const info = getArrowInfo(this.editor, shape); - const groupGeometry = this.editor.getShapeGeometry(shape); - const bodyGeometry = groupGeometry.children[0]; - const pointInShapeSpace = this.editor.getPointInShapeSpace( - shape, - this.editor.inputs.currentPagePoint - ); - const nearestPoint = bodyGeometry.nearestPoint( - Vec.Add(pointInShapeSpace, this._labelDragOffset) - ); - let nextLabelPosition; - if (info.isStraight) { - const lineLength = Vec.Dist(info.start.point, info.end.point); - const segmentLength = Vec.Dist(info.end.point, nearestPoint); - nextLabelPosition = 1 - segmentLength / lineLength; - } else { - const { _center, measure, angleEnd, angleStart } = groupGeometry.children[0]; - nextLabelPosition = getPointInArcT(measure, angleStart, angleEnd, _center.angle(nearestPoint)); - } - if (isNaN(nextLabelPosition)) { - nextLabelPosition = 0.5; - } - this.didDrag = true; - this.editor.updateShape({ - id: shape.id, - type: shape.type, - props: { labelPosition: nextLabelPosition } - }); - } - onPointerUp() { - const shape = this.editor.getShape(this.shapeId); - if (!shape) return; - if (this.didDrag || !this.wasAlreadySelected) { - this.complete(); - } else if (!this.editor.getIsReadonly()) { - this.editor.setEditingShape(shape.id); - this.editor.setCurrentTool("select.editing_shape"); - } - } - onCancel() { - this.cancel(); - } - onComplete() { - this.cancel(); - } - onInterrupt() { - this.cancel(); - } - complete() { - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, {}); - } else { - this.parent.transition("idle"); - } - } - cancel() { - this.editor.bailToMark(this.markId); - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, {}); - } else { - this.parent.transition("idle"); - } - } -} - -class PointingCanvas extends StateNode { - static id = "pointing_canvas"; - onEnter(info) { - const additiveSelectionKey = info.shiftKey || info.accelKey; - if (!additiveSelectionKey) { - if (this.editor.getSelectedShapeIds().length > 0) { - this.editor.markHistoryStoppingPoint("selecting none"); - this.editor.selectNone(); - } - } - } - onPointerMove(info) { - if (this.editor.inputs.isDragging) { - this.parent.transition("brushing", info); - } - } - onPointerUp(info) { - selectOnCanvasPointerUp(this.editor, info); - this.complete(); - } - onComplete() { - this.complete(); - } - onInterrupt() { - this.parent.transition("idle"); - } - complete() { - this.parent.transition("idle"); - } -} - -class PointingHandle extends StateNode { - static id = "pointing_handle"; - didCtrlOnEnter = false; - info = {}; - onEnter(info) { - this.info = info; - this.didCtrlOnEnter = info.accelKey; - const { shape } = info; - if (this.editor.isShapeOfType(shape, "arrow")) { - const initialBinding = getArrowBindings(this.editor, shape)[info.handle.id]; - if (initialBinding) { - this.editor.setHintingShapes([initialBinding.toId]); - } - } - this.editor.setCursor({ type: "grabbing", rotation: 0 }); - } - onExit() { - this.editor.setHintingShapes([]); - this.editor.setCursor({ type: "default", rotation: 0 }); - } - onPointerUp() { - const { shape, handle } = this.info; - if (this.editor.isShapeOfType(shape, "note")) { - const { editor } = this; - const nextNote = getNoteForPit(editor, shape, handle, false); - if (nextNote) { - startEditingShapeWithLabel( - editor, - nextNote, - true - /* selectAll */ - ); - return; - } - } - this.parent.transition("idle", this.info); - } - onPointerMove(info) { - const { editor } = this; - if (editor.inputs.isDragging) { - if (this.didCtrlOnEnter) { - this.parent.transition("brushing", info); - } else { - this.startDraggingHandle(); - } - } - } - onLongPress() { - this.startDraggingHandle(); - } - startDraggingHandle() { - const { editor } = this; - if (editor.getIsReadonly()) return; - const { shape, handle } = this.info; - if (editor.isShapeOfType(shape, "note")) { - const nextNote = getNoteForPit(editor, shape, handle, true); - if (nextNote) { - const centeredOnPointer = editor.getPointInParentSpace(nextNote, editor.inputs.originPagePoint).sub(Vec.Rot(NOTE_CENTER_OFFSET.clone().mul(shape.props.scale), nextNote.rotation)); - editor.updateShape({ ...nextNote, x: centeredOnPointer.x, y: centeredOnPointer.y }); - editor.setHoveredShape(nextNote.id).select(nextNote.id).setCurrentTool("select.translating", { - ...this.info, - target: "shape", - shape: editor.getShape(nextNote), - onInteractionEnd: "note", - isCreating: true, - onCreate: () => { - startEditingShapeWithLabel( - editor, - nextNote, - true - /* selectAll */ - ); - } - }); - return; - } - } - this.parent.transition("dragging_handle", this.info); - } - onCancel() { - this.cancel(); - } - onComplete() { - this.cancel(); - } - onInterrupt() { - this.cancel(); - } - cancel() { - this.parent.transition("idle"); - } -} -function getNoteForPit(editor, shape, handle, forceNew) { - const pageTransform = editor.getShapePageTransform(shape.id); - const pagePoint = pageTransform.point(); - const pageRotation = pageTransform.rotation(); - const pits = getNoteAdjacentPositions( - editor, - pagePoint, - pageRotation, - shape.props.growY, - 0, - shape.props.scale - ); - const pit = pits[handle.index]; - if (pit) { - return getNoteShapeForAdjacentPosition(editor, shape, pit, pageRotation, forceNew); - } -} - -class PointingRotateHandle extends StateNode { - static id = "pointing_rotate_handle"; - info = {}; - updateCursor() { - this.editor.setCursor({ - type: CursorTypeMap[this.info.handle], - rotation: this.editor.getSelectionRotation() - }); - } - onEnter(info) { - this.parent.setCurrentToolIdMask(info.onInteractionEnd); - this.info = info; - this.updateCursor(); - } - onExit() { - this.parent.setCurrentToolIdMask(void 0); - this.editor.setCursor({ type: "default", rotation: 0 }); - } - onPointerMove() { - if (this.editor.inputs.isDragging) { - this.startRotating(); - } - } - onLongPress() { - this.startRotating(); - } - startRotating() { - if (this.editor.getIsReadonly()) return; - this.parent.transition("rotating", this.info); - } - onPointerUp() { - this.complete(); - } - onCancel() { - this.cancel(); - } - onComplete() { - this.cancel(); - } - onInterrupt() { - this.cancel(); - } - complete() { - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, {}); - } else { - this.parent.transition("idle"); - } - } - cancel() { - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, {}); - } else { - this.parent.transition("idle"); - } - } -} - -class PointingSelection extends StateNode { - static id = "pointing_selection"; - info = {}; - onEnter(info) { - this.info = info; - } - onPointerUp(info) { - selectOnCanvasPointerUp(this.editor, info); - this.parent.transition("idle", info); - } - onPointerMove(info) { - if (this.editor.inputs.isDragging) { - this.startTranslating(info); - } - } - onLongPress(info) { - this.startTranslating(info); - } - startTranslating(info) { - if (this.editor.getIsReadonly()) return; - this.parent.transition("translating", info); - } - onDoubleClick(info) { - const hoveredShape = this.editor.getHoveredShape(); - const hitShape = hoveredShape && !this.editor.isShapeOfType(hoveredShape, "group") ? hoveredShape : this.editor.getShapeAtPoint(this.editor.inputs.currentPagePoint, { - hitInside: true, - margin: 0, - renderingOnly: true - }); - if (hitShape) { - this.parent.transition("idle"); - this.parent.onDoubleClick?.({ - ...info, - target: "shape", - shape: this.editor.getShape(hitShape) - }); - return; - } - } - onCancel() { - this.cancel(); - } - onComplete() { - this.cancel(); - } - onInterrupt() { - this.cancel(); - } - cancel() { - this.parent.transition("idle"); - } -} - -class PointingShape extends StateNode { - static id = "pointing_shape"; - hitShape = {}; - hitShapeForPointerUp = {}; - isDoubleClick = false; - didCtrlOnEnter = false; - didSelectOnEnter = false; - onEnter(info) { - const selectedShapeIds = this.editor.getSelectedShapeIds(); - const selectionBounds = this.editor.getSelectionRotatedPageBounds(); - const focusedGroupId = this.editor.getFocusedGroupId(); - const { - inputs: { currentPagePoint } - } = this.editor; - const { shiftKey, altKey, accelKey } = info; - this.hitShape = info.shape; - this.isDoubleClick = false; - this.didCtrlOnEnter = accelKey; - const outermostSelectingShape = this.editor.getOutermostSelectableShape(info.shape); - const selectedAncestor = this.editor.findShapeAncestor( - outermostSelectingShape, - (parent) => selectedShapeIds.includes(parent.id) - ); - if (this.didCtrlOnEnter || // If the shape has an onClick handler - this.editor.getShapeUtil(info.shape).onClick || // ...or if the shape is the focused layer (e.g. group) - outermostSelectingShape.id === focusedGroupId || // ...or if the shape is within the selection - selectedShapeIds.includes(outermostSelectingShape.id) || // ...or if an ancestor of the shape is selected - selectedAncestor || // ...or if the current point is NOT within the selection bounds - selectedShapeIds.length > 1 && selectionBounds?.containsPoint(currentPagePoint)) { - this.didSelectOnEnter = false; - this.hitShapeForPointerUp = outermostSelectingShape; - return; - } - this.didSelectOnEnter = true; - if (shiftKey && !altKey) { - this.editor.cancelDoubleClick(); - if (!selectedShapeIds.includes(outermostSelectingShape.id)) { - this.editor.markHistoryStoppingPoint("shift selecting shape"); - this.editor.setSelectedShapes([...selectedShapeIds, outermostSelectingShape.id]); - } - } else { - this.editor.markHistoryStoppingPoint("selecting shape"); - this.editor.setSelectedShapes([outermostSelectingShape.id]); - } - } - onPointerUp(info) { - const selectedShapeIds = this.editor.getSelectedShapeIds(); - const focusedGroupId = this.editor.getFocusedGroupId(); - const zoomLevel = this.editor.getZoomLevel(); - const { - inputs: { currentPagePoint } - } = this.editor; - const additiveSelectionKey = info.shiftKey || info.accelKey; - const hitShape = this.editor.getShapeAtPoint(currentPagePoint, { - margin: this.editor.options.hitTestMargin / zoomLevel, - hitInside: true, - renderingOnly: true - }) ?? this.hitShape; - const selectingShape = hitShape ? this.editor.getOutermostSelectableShape(hitShape) : this.hitShapeForPointerUp; - if (selectingShape) { - const util = this.editor.getShapeUtil(selectingShape); - if (util.onClick) { - const change = util.onClick?.(selectingShape); - if (change) { - this.editor.markHistoryStoppingPoint("shape on click"); - this.editor.updateShapes([change]); - this.parent.transition("idle", info); - return; - } - } - if (selectingShape.id === focusedGroupId) { - if (selectedShapeIds.length > 0) { - this.editor.markHistoryStoppingPoint("clearing shape ids"); - this.editor.setSelectedShapes([]); - } else { - this.editor.popFocusedGroupId(); - } - this.parent.transition("idle", info); - return; - } - } - if (!this.didSelectOnEnter) { - const outermostSelectableShape = this.editor.getOutermostSelectableShape( - hitShape, - // if a group is selected, we want to stop before reaching that group - // so we can drill down into the group - (parent) => !selectedShapeIds.includes(parent.id) - ); - if (selectedShapeIds.includes(outermostSelectableShape.id)) { - if (additiveSelectionKey) { - this.editor.markHistoryStoppingPoint("deselecting on pointer up"); - this.editor.deselect(selectingShape); - } else { - if (selectedShapeIds.includes(selectingShape.id)) { - if (selectedShapeIds.length === 1) { - const geometry = this.editor.getShapeUtil(selectingShape).getGeometry(selectingShape); - const textLabels = getTextLabels(geometry); - const textLabel = textLabels.length === 1 ? textLabels[0] : void 0; - if (textLabel) { - const pointInShapeSpace = this.editor.getPointInShapeSpace( - selectingShape, - currentPagePoint - ); - if (textLabel.bounds.containsPoint(pointInShapeSpace, 0) && textLabel.hitTestPoint(pointInShapeSpace)) { - this.editor.run(() => { - this.editor.markHistoryStoppingPoint("editing on pointer up"); - this.editor.select(selectingShape.id); - const util = this.editor.getShapeUtil(selectingShape); - if (this.editor.getIsReadonly()) { - if (!util.canEditInReadOnly(selectingShape)) { - return; - } - } - this.editor.setEditingShape(selectingShape.id); - this.editor.setCurrentTool("select.editing_shape"); - if (this.isDoubleClick) { - this.editor.emit("select-all-text", { shapeId: selectingShape.id }); - } - }); - return; - } - } - } - this.editor.markHistoryStoppingPoint("selecting on pointer up"); - this.editor.select(selectingShape.id); - } else { - this.editor.markHistoryStoppingPoint("selecting on pointer up"); - this.editor.select(selectingShape); - } - } - } else if (additiveSelectionKey) { - const ancestors = this.editor.getShapeAncestors(outermostSelectableShape); - this.editor.markHistoryStoppingPoint("shift deselecting on pointer up"); - this.editor.setSelectedShapes([ - ...this.editor.getSelectedShapeIds().filter((id) => !ancestors.find((a) => a.id === id)), - outermostSelectableShape.id - ]); - } else { - this.editor.markHistoryStoppingPoint("selecting on pointer up"); - this.editor.setSelectedShapes([outermostSelectableShape.id]); - } - } - this.parent.transition("idle", info); - } - onDoubleClick() { - this.isDoubleClick = true; - } - onPointerMove(info) { - if (this.editor.inputs.isDragging) { - if (this.didCtrlOnEnter) { - this.parent.transition("brushing", info); - } else { - this.startTranslating(info); - } - } - } - onLongPress(info) { - this.startTranslating(info); - } - startTranslating(info) { - if (this.editor.getIsReadonly()) return; - this.editor.focus(); - this.parent.transition("translating", info); - } - onCancel() { - this.cancel(); - } - onComplete() { - this.cancel(); - } - onInterrupt() { - this.cancel(); - } - cancel() { - this.parent.transition("idle"); - } -} - -class Resizing extends StateNode { - static id = "resizing"; - info = {}; - markId = ""; - // A switch to detect when the user is holding ctrl - didHoldCommand = false; - // we transition into the resizing state from the geo pointing state, which starts with a shape of size w: 1, h: 1, - // so if the user drags x: +50, y: +50 after mouseDown, the shape will be w: 51, h: 51, which is too many pixels, alas - // so we allow passing a further offset into this state to negate such issues - creationCursorOffset = { x: 0, y: 0 }; - snapshot = {}; - onEnter(info) { - const { isCreating = false, creatingMarkId, creationCursorOffset = { x: 0, y: 0 } } = info; - this.info = info; - this.didHoldCommand = false; - this.parent.setCurrentToolIdMask(info.onInteractionEnd); - this.creationCursorOffset = creationCursorOffset; - this.snapshot = this._createSnapshot(); - this.markId = ""; - if (isCreating) { - if (creatingMarkId) { - this.markId = creatingMarkId; - } else { - const markId = this.editor.getMarkIdMatching( - `creating:${this.editor.getOnlySelectedShapeId()}` - ); - if (markId) { - this.markId = markId; - } - } - } else { - this.markId = this.editor.markHistoryStoppingPoint("starting resizing"); - } - if (isCreating) { - this.editor.setCursor({ type: "cross", rotation: 0 }); - } - this.handleResizeStart(); - this.updateShapes(); - } - onTick({ elapsed }) { - const { editor } = this; - editor.edgeScrollManager.updateEdgeScrolling(elapsed); - } - onPointerMove() { - this.updateShapes(); - } - onKeyDown() { - this.updateShapes(); - } - onKeyUp() { - this.updateShapes(); - } - onPointerUp() { - this.complete(); - } - onComplete() { - this.complete(); - } - onCancel() { - this.cancel(); - } - cancel() { - this.editor.bailToMark(this.markId); - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, {}); - } else { - this.parent.transition("idle"); - } - } - complete() { - kickoutOccludedShapes(this.editor, this.snapshot.selectedShapeIds); - this.handleResizeEnd(); - if (this.info.isCreating && this.info.onCreate) { - this.info.onCreate?.(this.editor.getOnlySelectedShape()); - return; - } - if (this.editor.getInstanceState().isToolLocked && this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, {}); - return; - } - this.parent.transition("idle"); - } - handleResizeStart() { - const { shapeSnapshots } = this.snapshot; - const changes = []; - shapeSnapshots.forEach(({ shape }) => { - const util = this.editor.getShapeUtil(shape); - const change = util.onResizeStart?.(shape); - if (change) { - changes.push(change); - } - }); - if (changes.length > 0) { - this.editor.updateShapes(changes); - } - } - handleResizeEnd() { - const { shapeSnapshots } = this.snapshot; - const changes = []; - shapeSnapshots.forEach(({ shape }) => { - const current = this.editor.getShape(shape.id); - const util = this.editor.getShapeUtil(shape); - const change = util.onResizeEnd?.(shape, current); - if (change) { - changes.push(change); - } - }); - if (changes.length > 0) { - this.editor.updateShapes(changes); - } - } - updateShapes() { - const { altKey, shiftKey } = this.editor.inputs; - const { - frames, - shapeSnapshots, - selectionBounds, - cursorHandleOffset, - selectedShapeIds, - selectionRotation, - canShapesDeform - } = this.snapshot; - let isAspectRatioLocked = shiftKey || !canShapesDeform; - if (shapeSnapshots.size === 1) { - const onlySnapshot = [...shapeSnapshots.values()][0]; - if (this.editor.isShapeOfType(onlySnapshot.shape, "text")) { - isAspectRatioLocked = !(this.info.handle === "left" || this.info.handle === "right"); - } - } - const { ctrlKey } = this.editor.inputs; - const currentPagePoint = this.editor.inputs.currentPagePoint.clone().sub(cursorHandleOffset).sub(this.creationCursorOffset); - const originPagePoint = this.editor.inputs.originPagePoint.clone().sub(cursorHandleOffset); - if (this.editor.getInstanceState().isGridMode && !ctrlKey) { - const { gridSize } = this.editor.getDocumentSettings(); - currentPagePoint.snapToGrid(gridSize); - } - const dragHandle = this.info.handle; - const scaleOriginHandle = rotateSelectionHandle(dragHandle, Math.PI); - this.editor.snaps.clearIndicators(); - const shouldSnap = this.editor.user.getIsSnapMode() ? !ctrlKey : ctrlKey; - if (shouldSnap && selectionRotation % HALF_PI === 0) { - const { nudge } = this.editor.snaps.shapeBounds.snapResizeShapes({ - dragDelta: Vec.Sub(currentPagePoint, originPagePoint), - initialSelectionPageBounds: this.snapshot.initialSelectionPageBounds, - handle: rotateSelectionHandle(dragHandle, selectionRotation), - isAspectRatioLocked, - isResizingFromCenter: altKey - }); - currentPagePoint.add(nudge); - } - const scaleOriginPage = Vec.RotWith( - altKey ? selectionBounds.center : selectionBounds.getHandlePoint(scaleOriginHandle), - selectionBounds.point, - selectionRotation - ); - const distanceFromScaleOriginNow = Vec.Sub(currentPagePoint, scaleOriginPage).rot( - -selectionRotation - ); - const distanceFromScaleOriginAtStart = Vec.Sub(originPagePoint, scaleOriginPage).rot( - -selectionRotation - ); - const scale = Vec.DivV(distanceFromScaleOriginNow, distanceFromScaleOriginAtStart); - if (!Number.isFinite(scale.x)) scale.x = 1; - if (!Number.isFinite(scale.y)) scale.y = 1; - const isXLocked = dragHandle === "top" || dragHandle === "bottom"; - const isYLocked = dragHandle === "left" || dragHandle === "right"; - if (isAspectRatioLocked) { - if (isYLocked) { - scale.y = Math.abs(scale.x); - } else if (isXLocked) { - scale.x = Math.abs(scale.y); - } else if (Math.abs(scale.x) > Math.abs(scale.y)) { - scale.y = Math.abs(scale.x) * (scale.y < 0 ? -1 : 1); - } else { - scale.x = Math.abs(scale.y) * (scale.x < 0 ? -1 : 1); - } - } else { - if (isXLocked) { - scale.x = 1; - } - if (isYLocked) { - scale.y = 1; - } - } - if (!this.info.isCreating) { - this.updateCursor({ - dragHandle, - isFlippedX: scale.x < 0, - isFlippedY: scale.y < 0, - rotation: selectionRotation - }); - } - for (const id of shapeSnapshots.keys()) { - const snapshot = shapeSnapshots.get(id); - this.editor.resizeShape(id, scale, { - initialShape: snapshot.shape, - initialBounds: snapshot.bounds, - initialPageTransform: snapshot.pageTransform, - dragHandle, - mode: selectedShapeIds.length === 1 && id === selectedShapeIds[0] ? "resize_bounds" : "scale_shape", - scaleOrigin: scaleOriginPage, - isAspectRatioLocked, - scaleAxisRotation: selectionRotation, - skipStartAndEndCallbacks: true - }); - } - if (this.editor.inputs.ctrlKey) { - this.didHoldCommand = true; - for (const { id, children } of frames) { - if (!children.length) continue; - const initial = shapeSnapshots.get(id).shape; - const current = this.editor.getShape(id); - if (!(initial && current)) continue; - const dx = current.x - initial.x; - const dy = current.y - initial.y; - const delta = new Vec(dx, dy).rot(-initial.rotation); - if (delta.x !== 0 || delta.y !== 0) { - for (const child of children) { - this.editor.updateShape({ - id: child.id, - type: child.type, - x: child.x - delta.x, - y: child.y - delta.y - }); - } - } - } - } else if (this.didHoldCommand) { - this.didHoldCommand = false; - for (const { children } of frames) { - if (!children.length) continue; - for (const child of children) { - this.editor.updateShape({ - id: child.id, - type: child.type, - x: child.x, - y: child.y - }); - } - } - } - } - // --- - updateCursor({ - dragHandle, - isFlippedX, - isFlippedY, - rotation - }) { - const nextCursor = { ...this.editor.getInstanceState().cursor }; - switch (dragHandle) { - case "top_left": - case "bottom_right": { - nextCursor.type = "nwse-resize"; - if (isFlippedX !== isFlippedY) { - nextCursor.type = "nesw-resize"; - } - break; - } - case "top_right": - case "bottom_left": { - nextCursor.type = "nesw-resize"; - if (isFlippedX !== isFlippedY) { - nextCursor.type = "nwse-resize"; - } - break; - } - } - nextCursor.rotation = rotation; - this.editor.setCursor(nextCursor); - } - onExit() { - this.parent.setCurrentToolIdMask(void 0); - this.editor.setCursor({ type: "default", rotation: 0 }); - this.editor.snaps.clearIndicators(); - } - _createSnapshot() { - const selectedShapeIds = this.editor.getSelectedShapeIds(); - const selectionRotation = this.editor.getSelectionRotation(); - const { - inputs: { originPagePoint } - } = this.editor; - const selectionBounds = this.editor.getSelectionRotatedPageBounds(); - const dragHandlePoint = Vec.RotWith( - selectionBounds.getHandlePoint(this.info.handle), - selectionBounds.point, - selectionRotation - ); - const cursorHandleOffset = Vec.Sub(originPagePoint, dragHandlePoint); - const shapeSnapshots = /* @__PURE__ */ new Map(); - const frames = []; - selectedShapeIds.forEach((id) => { - const shape = this.editor.getShape(id); - if (shape) { - if (shape.type === "frame") { - frames.push({ - id, - children: compact( - this.editor.getSortedChildIdsForParent(shape).map((id2) => this.editor.getShape(id2)) - ) - }); - } - shapeSnapshots.set(shape.id, this._createShapeSnapshot(shape)); - if (this.editor.isShapeOfType(shape, "frame") && selectedShapeIds.length === 1) - return; - this.editor.visitDescendants(shape.id, (descendantId) => { - const descendent = this.editor.getShape(descendantId); - if (descendent) { - shapeSnapshots.set(descendent.id, this._createShapeSnapshot(descendent)); - if (this.editor.isShapeOfType(descendent, "frame")) { - return false; - } - } - }); - } - }); - const canShapesDeform = ![...shapeSnapshots.values()].some( - (shape) => !areAnglesCompatible(shape.pageRotation, selectionRotation) || shape.isAspectRatioLocked - ); - return { - shapeSnapshots, - selectionBounds, - cursorHandleOffset, - selectionRotation, - selectedShapeIds, - canShapesDeform, - initialSelectionPageBounds: this.editor.getSelectionPageBounds(), - frames - }; - } - _createShapeSnapshot(shape) { - const pageTransform = this.editor.getShapePageTransform(shape); - const util = this.editor.getShapeUtil(shape); - return { - shape, - bounds: this.editor.getShapeGeometry(shape).bounds, - pageTransform, - pageRotation: Mat.Decompose(pageTransform).rotation, - isAspectRatioLocked: util.isAspectRatioLocked(shape) - }; - } -} -const ORDERED_SELECTION_HANDLES = [ - "top", - "top_right", - "right", - "bottom_right", - "bottom", - "bottom_left", - "left", - "top_left" -]; -function rotateSelectionHandle(handle, rotation) { - rotation = rotation % PI2; - const numSteps = Math.round(rotation / (PI$1 / 4)); - const currentIndex = ORDERED_SELECTION_HANDLES.indexOf(handle); - return ORDERED_SELECTION_HANDLES[(currentIndex + numSteps) % ORDERED_SELECTION_HANDLES.length]; -} - -const ONE_DEGREE = Math.PI / 180; -class Rotating extends StateNode { - static id = "rotating"; - snapshot = {}; - info = {}; - markId = ""; - onEnter(info) { - this.info = info; - this.parent.setCurrentToolIdMask(info.onInteractionEnd); - this.markId = this.editor.markHistoryStoppingPoint("rotate start"); - const snapshot = getRotationSnapshot({ - editor: this.editor, - ids: this.editor.getSelectedShapeIds() - }); - if (!snapshot) return this.parent.transition("idle", this.info); - this.snapshot = snapshot; - const newSelectionRotation = this._getRotationFromPointerPosition({ - snapToNearestDegree: false - }); - applyRotationToSnapshotShapes({ - editor: this.editor, - delta: this._getRotationFromPointerPosition({ snapToNearestDegree: false }), - snapshot: this.snapshot, - stage: "start" - }); - this.editor.setCursor({ - type: CursorTypeMap[this.info.handle], - rotation: newSelectionRotation + this.snapshot.initialShapesRotation - }); - } - onExit() { - this.editor.setCursor({ type: "default", rotation: 0 }); - this.parent.setCurrentToolIdMask(void 0); - this.snapshot = {}; - } - onPointerMove() { - this.update(); - } - onKeyDown() { - this.update(); - } - onKeyUp() { - this.update(); - } - onPointerUp() { - this.complete(); - } - onComplete() { - this.complete(); - } - onCancel() { - this.cancel(); - } - // --- - update() { - const newSelectionRotation = this._getRotationFromPointerPosition({ - snapToNearestDegree: false - }); - applyRotationToSnapshotShapes({ - editor: this.editor, - delta: newSelectionRotation, - snapshot: this.snapshot, - stage: "update" - }); - this.editor.setCursor({ - type: CursorTypeMap[this.info.handle], - rotation: newSelectionRotation + this.snapshot.initialShapesRotation - }); - } - cancel() { - this.editor.bailToMark(this.markId); - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, this.info); - } else { - this.parent.transition("idle", this.info); - } - } - complete() { - applyRotationToSnapshotShapes({ - editor: this.editor, - delta: this._getRotationFromPointerPosition({ snapToNearestDegree: true }), - snapshot: this.snapshot, - stage: "end" - }); - kickoutOccludedShapes( - this.editor, - this.snapshot.shapeSnapshots.map((s) => s.shape.id) - ); - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd, this.info); - } else { - this.parent.transition("idle", this.info); - } - } - _getRotationFromPointerPosition({ snapToNearestDegree }) { - const selectionRotation = this.editor.getSelectionRotation(); - const selectionBounds = this.editor.getSelectionRotatedPageBounds(); - const { - inputs: { shiftKey, currentPagePoint } - } = this.editor; - const { initialCursorAngle, initialShapesRotation } = this.snapshot; - if (!selectionBounds) return initialShapesRotation; - const selectionPageCenter = selectionBounds.center.clone().rotWith(selectionBounds.point, selectionRotation); - const preSnapRotationDelta = selectionPageCenter.angle(currentPagePoint) - initialCursorAngle; - let newSelectionRotation = initialShapesRotation + preSnapRotationDelta; - if (shiftKey) { - newSelectionRotation = snapAngle(newSelectionRotation, 24); - } else if (snapToNearestDegree) { - newSelectionRotation = Math.round(newSelectionRotation / ONE_DEGREE) * ONE_DEGREE; - if (this.editor.getInstanceState().isCoarsePointer) { - const snappedToRightAngle = snapAngle(newSelectionRotation, 4); - const angleToRightAngle = shortAngleDist(newSelectionRotation, snappedToRightAngle); - if (Math.abs(angleToRightAngle) < degreesToRadians(5)) { - newSelectionRotation = snappedToRightAngle; - } - } - } - return newSelectionRotation - initialShapesRotation; - } -} - -class ScribbleBrushing extends StateNode { - static id = "scribble_brushing"; - hits = /* @__PURE__ */ new Set(); - size = 0; - scribbleId = "id"; - initialSelectedShapeIds = /* @__PURE__ */ new Set(); - newlySelectedShapeIds = /* @__PURE__ */ new Set(); - onEnter() { - this.initialSelectedShapeIds = new Set( - this.editor.inputs.shiftKey ? this.editor.getSelectedShapeIds() : [] - ); - this.newlySelectedShapeIds = /* @__PURE__ */ new Set(); - this.size = 0; - this.hits.clear(); - const scribbleItem = this.editor.scribbles.addScribble({ - color: "selection-stroke", - opacity: 0.32, - size: 12 - }); - this.scribbleId = scribbleItem.id; - this.updateScribbleSelection(true); - this.editor.updateInstanceState({ brush: null }); - } - onExit() { - this.editor.scribbles.stop(this.scribbleId); - } - onPointerMove() { - this.updateScribbleSelection(true); - } - onPointerUp() { - this.complete(); - } - onKeyDown() { - this.updateScribbleSelection(false); - } - onKeyUp() { - if (!this.editor.inputs.altKey) { - this.parent.transition("brushing"); - } else { - this.updateScribbleSelection(false); - } - } - onCancel() { - this.cancel(); - } - onComplete() { - this.complete(); - } - pushPointToScribble() { - const { x, y } = this.editor.inputs.currentPagePoint; - this.editor.scribbles.addPoint(this.scribbleId, x, y); - } - updateScribbleSelection(addPoint) { - const { editor } = this; - const currentPageShapes = this.editor.getCurrentPageRenderingShapesSorted(); - const { - inputs: { shiftKey, originPagePoint, previousPagePoint, currentPagePoint } - } = this.editor; - const { newlySelectedShapeIds, initialSelectedShapeIds } = this; - if (addPoint) { - this.pushPointToScribble(); - } - const shapes = currentPageShapes; - let shape, geometry, A, B; - const minDist = 0; - for (let i = 0, n = shapes.length; i < n; i++) { - shape = shapes[i]; - if (editor.isShapeOfType(shape, "group") || newlySelectedShapeIds.has(shape.id) || editor.isShapeOrAncestorLocked(shape)) { - continue; - } - geometry = editor.getShapeGeometry(shape); - if (editor.isShapeOfType(shape, "frame") && geometry.bounds.containsPoint(editor.getPointInShapeSpace(shape, originPagePoint))) { - continue; - } - const pageTransform = editor.getShapePageTransform(shape); - if (!geometry || !pageTransform) continue; - const pt = pageTransform.clone().invert(); - A = pt.applyToPoint(previousPagePoint); - B = pt.applyToPoint(currentPagePoint); - const { bounds } = geometry; - if (bounds.minX - minDist > Math.max(A.x, B.x) || bounds.minY - minDist > Math.max(A.y, B.y) || bounds.maxX + minDist < Math.min(A.x, B.x) || bounds.maxY + minDist < Math.min(A.y, B.y)) { - continue; - } - if (geometry.hitTestLineSegment(A, B, minDist)) { - const outermostShape = this.editor.getOutermostSelectableShape(shape); - const pageMask = this.editor.getShapeMask(outermostShape.id); - if (pageMask) { - const intersection = intersectLineSegmentPolygon( - previousPagePoint, - currentPagePoint, - pageMask - ); - if (intersection !== null) { - const isInMask = pointInPolygon(currentPagePoint, pageMask); - if (!isInMask) continue; - } - } - newlySelectedShapeIds.add(outermostShape.id); - } - } - const current = editor.getSelectedShapeIds(); - const next = new Set( - shiftKey ? [...newlySelectedShapeIds, ...initialSelectedShapeIds] : [...newlySelectedShapeIds] - ); - if (current.length !== next.size || current.some((id) => !next.has(id))) { - this.editor.setSelectedShapes(Array.from(next)); - } - } - complete() { - this.updateScribbleSelection(true); - this.parent.transition("idle"); - } - cancel() { - this.editor.setSelectedShapes([...this.initialSelectedShapeIds]); - this.parent.transition("idle"); - } -} - -var __create$2 = Object.create; -var __defProp$2 = Object.defineProperty; -var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor; -var __knownSymbol$2 = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name); -var __typeError$2 = (msg) => { - throw TypeError(msg); -}; -var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; -var __decoratorStart$2 = (base) => [, , , __create$2(null)]; -var __decoratorStrings$2 = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; -var __expectFn$2 = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$2("Function expected") : fn; -var __decoratorContext$2 = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$2[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$2("Already initialized") : fns.push(__expectFn$2(fn || null)) }); -var __decoratorMetadata$2 = (array, target) => __defNormalProp$2(target, __knownSymbol$2("metadata"), array[3]); -var __runInitializers$2 = (array, flags, self, value) => { - for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self) ; - return value; -}; -var __decorateElement$2 = (array, flags, name, decorators, target, extra) => { - var it, done, ctx, access, k = flags & 7, s = false, p = false; - var j = 2 , key = __decoratorStrings$2[k + 5]; - var extraInitializers = array[j] || (array[j] = []); - var desc = ((target = target.prototype), __getOwnPropDesc$2(target , name)); - for (var i = decorators.length - 1; i >= 0; i--) { - ctx = __decoratorContext$2(k, name, done = {}, array[3], extraInitializers); - { - ctx.static = s, ctx.private = p, access = ctx.access = { has: (x) => name in x }; - access.get = (x) => x[name]; - } - it = (0, decorators[i])(desc[key] , ctx), done._ = 1; - __expectFn$2(it) && (desc[key] = it ); - } - return desc && __defProp$2(target, name, desc), target; -}; -var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value); -var _dispose_dec, _init$2; -const INITIAL_POINTER_LAG_DURATION = 20; -const FAST_POINTER_LAG_DURATION = 100; -_dispose_dec = [bind$2]; -class DragAndDropManager { - constructor(editor) { - this.editor = editor; - __runInitializers$2(_init$2, 5, this); - __publicField$2(this, "prevDroppingShapeId", null); - __publicField$2(this, "droppingNodeTimer", null); - __publicField$2(this, "first", true); - editor.disposables.add(this.dispose); - } - updateDroppingNode(movingShapes, cb) { - if (this.first) { - this.editor.setHintingShapes( - movingShapes.map((s) => this.editor.findShapeAncestor(s, (v) => v.type !== "group")).filter((s) => s) - ); - this.prevDroppingShapeId = this.editor.getDroppingOverShape(this.editor.inputs.originPagePoint, movingShapes)?.id ?? null; - this.first = false; - } - if (this.droppingNodeTimer === null) { - this.setDragTimer(movingShapes, INITIAL_POINTER_LAG_DURATION, cb); - } else if (this.editor.inputs.pointerVelocity.len() > 0.5) { - clearTimeout(this.droppingNodeTimer); - this.setDragTimer(movingShapes, FAST_POINTER_LAG_DURATION, cb); - } - } - setDragTimer(movingShapes, duration, cb) { - this.droppingNodeTimer = this.editor.timers.setTimeout(() => { - this.editor.run(() => { - this.handleDrag(this.editor.inputs.currentPagePoint, movingShapes, cb); - }); - this.droppingNodeTimer = null; - }, duration); - } - handleDrag(point, movingShapes, cb) { - movingShapes = compact(movingShapes.map((shape) => this.editor.getShape(shape.id))); - const nextDroppingShapeId = this.editor.getDroppingOverShape(point, movingShapes)?.id ?? null; - if (nextDroppingShapeId === this.prevDroppingShapeId) { - this.hintParents(movingShapes); - return; - } - const { prevDroppingShapeId } = this; - const prevDroppingShape = prevDroppingShapeId && this.editor.getShape(prevDroppingShapeId); - const nextDroppingShape = nextDroppingShapeId && this.editor.getShape(nextDroppingShapeId); - if (prevDroppingShape) { - this.editor.getShapeUtil(prevDroppingShape).onDragShapesOut?.(prevDroppingShape, movingShapes); - } - if (nextDroppingShape) { - this.editor.getShapeUtil(nextDroppingShape).onDragShapesOver?.(nextDroppingShape, movingShapes); - } - this.hintParents(movingShapes); - cb?.(); - this.prevDroppingShapeId = nextDroppingShapeId; - } - hintParents(movingShapes) { - const shapesGroupedByAncestor = /* @__PURE__ */ new Map(); - for (const shape of movingShapes) { - const ancestor = this.editor.findShapeAncestor(shape, (v) => v.type !== "group"); - if (!ancestor) continue; - if (!shapesGroupedByAncestor.has(ancestor.id)) { - shapesGroupedByAncestor.set(ancestor.id, []); - } - shapesGroupedByAncestor.get(ancestor.id).push(shape.id); - } - const hintingShapes = []; - for (const [ancestorId, shapeIds] of shapesGroupedByAncestor) { - const ancestor = this.editor.getShape(ancestorId); - if (!ancestor) continue; - if (getOccludedChildren(this.editor, ancestor).length < shapeIds.length) { - hintingShapes.push(ancestor.id); - } - } - this.editor.setHintingShapes(hintingShapes); - } - dropShapes(shapes) { - const { prevDroppingShapeId } = this; - this.handleDrag(this.editor.inputs.currentPagePoint, shapes); - if (prevDroppingShapeId) { - const shape = this.editor.getShape(prevDroppingShapeId); - if (!shape) return; - this.editor.getShapeUtil(shape).onDropShapesOver?.(shape, shapes); - } - } - clear() { - this.prevDroppingShapeId = null; - if (this.droppingNodeTimer !== null) { - clearTimeout(this.droppingNodeTimer); - } - this.droppingNodeTimer = null; - this.editor.setHintingShapes([]); - this.first = true; - } - dispose() { - this.clear(); - } -} -_init$2 = __decoratorStart$2(); -__decorateElement$2(_init$2, 1, "dispose", _dispose_dec, DragAndDropManager); -__decoratorMetadata$2(_init$2, DragAndDropManager); - -var __create$1 = Object.create; -var __defProp$1 = Object.defineProperty; -var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor; -var __knownSymbol$1 = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name); -var __typeError$1 = (msg) => { - throw TypeError(msg); -}; -var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; -var __decoratorStart$1 = (base) => [, , , __create$1(base?.[__knownSymbol$1("metadata")] ?? null)]; -var __decoratorStrings$1 = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; -var __expectFn$1 = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$1("Function expected") : fn; -var __decoratorContext$1 = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$1[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$1("Already initialized") : fns.push(__expectFn$1(fn || null)) }); -var __decoratorMetadata$1 = (array, target) => __defNormalProp$1(target, __knownSymbol$1("metadata"), array[3]); -var __runInitializers$1 = (array, flags, self, value) => { - for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self) ; - return value; -}; -var __decorateElement$1 = (array, flags, name, decorators, target, extra) => { - var it, done, ctx, access, k = flags & 7, s = false, p = false; - var j = 2 , key = __decoratorStrings$1[k + 5]; - var extraInitializers = array[j] || (array[j] = []); - var desc = ((target = target.prototype), __getOwnPropDesc$1(target , name)); - for (var i = decorators.length - 1; i >= 0; i--) { - ctx = __decoratorContext$1(k, name, done = {}, array[3], extraInitializers); - { - ctx.static = s, ctx.private = p, access = ctx.access = { has: (x) => name in x }; - access.get = (x) => x[name]; - } - it = (0, decorators[i])(desc[key] , ctx), done._ = 1; - __expectFn$1(it) && (desc[key] = it ); - } - return desc && __defProp$1(target, name, desc), target; -}; -var __publicField$1 = (obj, key, value) => __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value); -var _updateParentTransforms_dec, _a, _init$1; -class Translating extends (_a = StateNode, _updateParentTransforms_dec = [bind$2], _a) { - constructor() { - super(...arguments); - __runInitializers$1(_init$1, 5, this); - __publicField$1(this, "info", {}); - __publicField$1(this, "selectionSnapshot", {}); - __publicField$1(this, "snapshot", {}); - __publicField$1(this, "markId", ""); - __publicField$1(this, "isCloning", false); - __publicField$1(this, "isCreating", false); - __publicField$1(this, "dragAndDropManager", new DragAndDropManager(this.editor)); - } - onCreate(_shape) { - return; - } - onEnter(info) { - const { isCreating = false, creatingMarkId, onCreate = () => void 0 } = info; - if (!this.editor.getSelectedShapeIds()?.length) { - this.parent.transition("idle"); - return; - } - this.info = info; - this.parent.setCurrentToolIdMask(info.onInteractionEnd); - this.isCreating = isCreating; - this.markId = ""; - if (isCreating) { - if (creatingMarkId) { - this.markId = creatingMarkId; - } else { - const markId = this.editor.getMarkIdMatching( - `creating:${this.editor.getOnlySelectedShapeId()}` - ); - if (markId) { - this.markId = markId; - } - } - } else { - this.markId = this.editor.markHistoryStoppingPoint("translating"); - } - this.onCreate = onCreate; - this.isCloning = false; - this.info = info; - this.editor.setCursor({ type: "move", rotation: 0 }); - this.selectionSnapshot = getTranslatingSnapshot(this.editor); - if (!this.isCreating) { - if (this.editor.inputs.altKey) { - this.startCloning(); - return; - } - } - this.snapshot = this.selectionSnapshot; - this.handleStart(); - this.updateShapes(); - } - onExit() { - this.parent.setCurrentToolIdMask(void 0); - this.selectionSnapshot = {}; - this.snapshot = {}; - this.editor.snaps.clearIndicators(); - this.editor.setCursor({ type: "default", rotation: 0 }); - this.dragAndDropManager.clear(); - } - onTick({ elapsed }) { - const { editor } = this; - this.dragAndDropManager.updateDroppingNode( - this.snapshot.movingShapes, - this.updateParentTransforms - ); - editor.edgeScrollManager.updateEdgeScrolling(elapsed); - } - onPointerMove() { - this.updateShapes(); - } - onKeyDown() { - if (this.editor.inputs.altKey && !this.isCloning) { - this.startCloning(); - return; - } - this.updateShapes(); - } - onKeyUp() { - if (!this.editor.inputs.altKey && this.isCloning) { - this.stopCloning(); - return; - } - this.updateShapes(); - } - onPointerUp() { - this.complete(); - } - onComplete() { - this.complete(); - } - onCancel() { - this.cancel(); - } - startCloning() { - if (this.isCreating) return; - this.isCloning = true; - this.reset(); - this.markId = this.editor.markHistoryStoppingPoint("translate cloning"); - this.editor.duplicateShapes(Array.from(this.editor.getSelectedShapeIds())); - this.snapshot = getTranslatingSnapshot(this.editor); - this.handleStart(); - this.updateShapes(); - } - stopCloning() { - this.isCloning = false; - this.snapshot = this.selectionSnapshot; - this.reset(); - this.markId = this.editor.markHistoryStoppingPoint("translate"); - this.updateShapes(); - } - reset() { - this.editor.bailToMark(this.markId); - } - complete() { - this.updateShapes(); - this.dragAndDropManager.dropShapes(this.snapshot.movingShapes); - kickoutOccludedShapes( - this.editor, - this.snapshot.movingShapes.map((s) => s.id) - ); - this.handleEnd(); - if (this.editor.getInstanceState().isToolLocked && this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd); - } else { - if (this.isCreating) { - this.onCreate?.(this.editor.getOnlySelectedShape()); - } else { - this.parent.transition("idle"); - } - } - } - cancel() { - this.reset(); - if (this.info.onInteractionEnd) { - this.editor.setCurrentTool(this.info.onInteractionEnd); - } else { - this.parent.transition("idle", this.info); - } - } - handleStart() { - const { movingShapes } = this.snapshot; - const changes = []; - movingShapes.forEach((shape) => { - const util = this.editor.getShapeUtil(shape); - const change = util.onTranslateStart?.(shape); - if (change) { - changes.push(change); - } - }); - if (changes.length > 0) { - this.editor.updateShapes(changes); - } - this.editor.setHoveredShape(null); - } - handleEnd() { - const { movingShapes } = this.snapshot; - if (this.isCloning && movingShapes.length > 0) { - const currentAveragePagePoint = Vec.Average( - movingShapes.map((s) => this.editor.getShapePageTransform(s.id).point()) - ); - const offset = Vec.Sub(currentAveragePagePoint, this.selectionSnapshot.averagePagePoint); - if (!Vec.IsNaN(offset)) { - this.editor.updateInstanceState({ - duplicateProps: { - shapeIds: movingShapes.map((s) => s.id), - offset: { x: offset.x, y: offset.y } - } - }); - } - } - const changes = []; - movingShapes.forEach((shape) => { - const current = this.editor.getShape(shape.id); - const util = this.editor.getShapeUtil(shape); - const change = util.onTranslateEnd?.(shape, current); - if (change) { - changes.push(change); - } - }); - if (changes.length > 0) { - this.editor.updateShapes(changes); - } - } - updateShapes() { - const { snapshot } = this; - this.dragAndDropManager.updateDroppingNode(snapshot.movingShapes, this.updateParentTransforms); - moveShapesToPoint({ - editor: this.editor, - snapshot - }); - const { movingShapes } = snapshot; - const changes = []; - movingShapes.forEach((shape) => { - const current = this.editor.getShape(shape.id); - const util = this.editor.getShapeUtil(shape); - const change = util.onTranslate?.(shape, current); - if (change) { - changes.push(change); - } - }); - if (changes.length > 0) { - this.editor.updateShapes(changes); - } - } - updateParentTransforms() { - const { - editor, - snapshot: { shapeSnapshots } - } = this; - shapeSnapshots.forEach((shapeSnapshot) => { - const shape = editor.getShape(shapeSnapshot.shape.id); - if (!shape) return null; - const parentTransform = isPageId(shape.parentId) ? null : Mat.Inverse(editor.getShapePageTransform(shape.parentId)); - shapeSnapshot.parentTransform = parentTransform; - }); - } -} -_init$1 = __decoratorStart$1(_a); -__decorateElement$1(_init$1, 1, "updateParentTransforms", _updateParentTransforms_dec, Translating); -__decoratorMetadata$1(_init$1, Translating); -__publicField$1(Translating, "id", "translating"); -function getTranslatingSnapshot(editor) { - const movingShapes = []; - const pagePoints = []; - const selectedShapeIds = editor.getSelectedShapeIds(); - const shapeSnapshots = compact( - selectedShapeIds.map((id) => { - const shape = editor.getShape(id); - if (!shape) return null; - movingShapes.push(shape); - const pageTransform = editor.getShapePageTransform(id); - const pagePoint = pageTransform.point(); - const pageRotation = pageTransform.rotation(); - pagePoints.push(pagePoint); - const parentTransform = PageRecordType.isId(shape.parentId) ? null : Mat.Inverse(editor.getShapePageTransform(shape.parentId)); - return { - shape, - pagePoint, - pageRotation, - parentTransform - }; - }) - ); - const onlySelectedShape = editor.getOnlySelectedShape(); - let initialSnapPoints = []; - if (onlySelectedShape) { - initialSnapPoints = editor.snaps.shapeBounds.getSnapPoints(onlySelectedShape.id); - } else { - const selectionPageBounds = editor.getSelectionPageBounds(); - if (selectionPageBounds) { - initialSnapPoints = selectionPageBounds.cornersAndCenter.map((p, i) => ({ - id: "selection:" + i, - x: p.x, - y: p.y - })); - } - } - let noteAdjacentPositions; - let noteSnapshot; - const { originPagePoint } = editor.inputs; - const allHoveredNotes = shapeSnapshots.filter( - (s) => editor.isShapeOfType(s.shape, "note") && editor.isPointInShape(s.shape, originPagePoint) - ); - if (allHoveredNotes.length === 0) ; else if (allHoveredNotes.length === 1) { - noteSnapshot = allHoveredNotes[0]; - } else { - const allShapesSorted = editor.getCurrentPageShapesSorted(); - noteSnapshot = allHoveredNotes.map((s) => ({ - snapshot: s, - index: allShapesSorted.findIndex((shape) => shape.id === s.shape.id) - })).sort((a, b) => b.index - a.index)[0]?.snapshot; - } - if (noteSnapshot) { - noteAdjacentPositions = getAvailableNoteAdjacentPositions( - editor, - noteSnapshot.pageRotation, - noteSnapshot.shape.props.scale, - noteSnapshot.shape.props.growY ?? 0 - ); - } - return { - averagePagePoint: Vec.Average(pagePoints), - movingShapes, - shapeSnapshots, - initialPageBounds: editor.getSelectionPageBounds(), - initialSnapPoints, - noteAdjacentPositions, - noteSnapshot - }; -} -function moveShapesToPoint({ - editor, - snapshot -}) { - const { inputs } = editor; - const { - noteSnapshot, - noteAdjacentPositions, - initialPageBounds, - initialSnapPoints, - shapeSnapshots, - averagePagePoint - } = snapshot; - const isGridMode = editor.getInstanceState().isGridMode; - const gridSize = editor.getDocumentSettings().gridSize; - const delta = Vec.Sub(inputs.currentPagePoint, inputs.originPagePoint); - const flatten = editor.inputs.shiftKey ? Math.abs(delta.x) < Math.abs(delta.y) ? "x" : "y" : null; - if (flatten === "x") { - delta.x = 0; - } else if (flatten === "y") { - delta.y = 0; - } - editor.snaps.clearIndicators(); - const isSnapping = editor.user.getIsSnapMode() ? !inputs.ctrlKey : inputs.ctrlKey; - let snappedToPit = false; - if (isSnapping && editor.inputs.pointerVelocity.len() < 0.5) { - const { nudge } = editor.snaps.shapeBounds.snapTranslateShapes({ - dragDelta: delta, - initialSelectionPageBounds: initialPageBounds, - lockedAxis: flatten, - initialSelectionSnapPoints: initialSnapPoints - }); - delta.add(nudge); - } else { - if (noteSnapshot && noteAdjacentPositions) { - const { scale } = noteSnapshot.shape.props; - const pageCenter = noteSnapshot.pagePoint.clone().add(delta).add(NOTE_CENTER_OFFSET.clone().mul(scale).rot(noteSnapshot.pageRotation)); - let min = NOTE_ADJACENT_POSITION_SNAP_RADIUS / editor.getZoomLevel(); - let offset = new Vec(0, 0); - for (const pit of noteAdjacentPositions) { - const deltaToPit = Vec.Sub(pageCenter, pit); - const dist = deltaToPit.len(); - if (dist < min) { - snappedToPit = true; - min = dist; - offset = deltaToPit; - } - } - delta.sub(offset); - } - } - const averageSnappedPoint = Vec.Add(averagePagePoint, delta); - const snapIndicators = editor.snaps.getIndicators(); - if (isGridMode && !inputs.ctrlKey && !snappedToPit && snapIndicators.length === 0) { - averageSnappedPoint.snapToGrid(gridSize); - } - const averageSnap = Vec.Sub(averageSnappedPoint, averagePagePoint); - editor.updateShapes( - compact( - shapeSnapshots.map(({ shape, pagePoint, parentTransform }) => { - const newPagePoint = Vec.Add(pagePoint, averageSnap); - const newLocalPoint = parentTransform ? Mat.applyToPoint(parentTransform, newPagePoint) : newPagePoint; - return { - id: shape.id, - type: shape.type, - x: newLocalPoint.x, - y: newLocalPoint.y - }; - }) - ) - ); -} - -class SelectTool extends StateNode { - static id = "select"; - static initial = "idle"; - static isLockable = false; - reactor = void 0; - static children() { - return [ - Crop, - Cropping, - Idle$1, - PointingCanvas, - PointingShape, - Translating, - Brushing, - ScribbleBrushing, - PointingCropHandle, - PointingSelection, - PointingResizeHandle, - EditingShape, - Resizing, - Rotating, - PointingRotateHandle, - PointingArrowLabel, - PointingHandle, - DraggingHandle - ]; - } - // We want to clean up the duplicate props when the selection changes - cleanUpDuplicateProps() { - const selectedShapeIds = this.editor.getSelectedShapeIds(); - const instance = this.editor.getInstanceState(); - if (!instance.duplicateProps) return; - const duplicatedShapes = new Set(instance.duplicateProps.shapeIds); - if (selectedShapeIds.length === duplicatedShapes.size && selectedShapeIds.every((shapeId) => duplicatedShapes.has(shapeId))) { - return; - } - this.editor.updateInstanceState({ - duplicateProps: null - }); - } - onEnter() { - this.reactor = react("clean duplicate props", () => { - try { - this.cleanUpDuplicateProps(); - } catch (e) { - { - console.error(e); - } - } - }); - } - onExit() { - this.reactor?.(); - if (this.editor.getCurrentPageState().editingShapeId) { - this.editor.setEditingShape(null); - } - } -} - -class Idle extends StateNode { - static id = "idle"; - info = {}; - onEnter(info) { - this.info = info; - } - onPointerDown() { - this.parent.transition("pointing", this.info); - } -} - -class Pointing extends StateNode { - static id = "pointing"; - info = {}; - onEnter(info) { - this.info = info; - } - onPointerUp() { - this.complete(); - } - onPointerMove() { - if (this.editor.inputs.isDragging) { - this.parent.transition("zoom_brushing", this.info); - } - } - onCancel() { - this.cancel(); - } - complete() { - const { currentScreenPoint } = this.editor.inputs; - if (this.editor.inputs.altKey) { - this.editor.zoomOut(currentScreenPoint, { animation: { duration: 220 } }); - } else { - this.editor.zoomIn(currentScreenPoint, { animation: { duration: 220 } }); - } - this.parent.transition("idle", this.info); - } - cancel() { - this.parent.transition("idle", this.info); - } -} - -class ZoomBrushing extends StateNode { - static id = "zoom_brushing"; - info = {}; - zoomBrush = new Box(); - onEnter(info) { - this.info = info; - this.update(); - } - onExit() { - this.editor.updateInstanceState({ zoomBrush: null }); - } - onPointerMove() { - this.update(); - } - onPointerUp() { - this.complete(); - } - onCancel() { - this.cancel(); - } - update() { - const { - inputs: { originPagePoint, currentPagePoint } - } = this.editor; - this.zoomBrush.setTo(Box.FromPoints([originPagePoint, currentPagePoint])); - this.editor.updateInstanceState({ zoomBrush: this.zoomBrush.toJson() }); - } - cancel() { - this.parent.transition("idle", this.info); - } - complete() { - const { zoomBrush } = this; - const threshold = 8 / this.editor.getZoomLevel(); - if (zoomBrush.width < threshold && zoomBrush.height < threshold) { - const point = this.editor.inputs.currentScreenPoint; - if (this.editor.inputs.altKey) { - this.editor.zoomOut(point, { animation: { duration: 220 } }); - } else { - this.editor.zoomIn(point, { animation: { duration: 220 } }); - } - } else { - const targetZoom = this.editor.inputs.altKey ? this.editor.getZoomLevel() / 2 : void 0; - this.editor.zoomToBounds(zoomBrush, { targetZoom, animation: { duration: 220 } }); - } - this.parent.transition("idle", this.info); - } -} - -class ZoomTool extends StateNode { - static id = "zoom"; - static initial = "idle"; - static children() { - return [Idle, ZoomBrushing, Pointing]; - } - static isLockable = false; - info = {}; - onEnter(info) { - this.info = info; - this.parent.setCurrentToolIdMask(info.onInteractionEnd); - this.updateCursor(); - } - onExit() { - this.parent.setCurrentToolIdMask(void 0); - this.editor.updateInstanceState({ zoomBrush: null, cursor: { type: "default", rotation: 0 } }); - this.parent.setCurrentToolIdMask(void 0); - } - onKeyDown() { - this.updateCursor(); - } - onKeyUp(info) { - this.updateCursor(); - if (info.code === "KeyZ") { - this.complete(); - } - } - onInterrupt() { - this.complete(); - } - complete() { - if (this.info.onInteractionEnd && this.info.onInteractionEnd !== "select") { - this.editor.setCurrentTool(this.info.onInteractionEnd, this.info); - } else { - this.parent.transition("select"); - } - } - updateCursor() { - if (this.editor.inputs.altKey) { - this.editor.setCursor({ type: "zoom-out", rotation: 0 }); - } else { - this.editor.setCursor({ type: "zoom-in", rotation: 0 }); - } - } -} - -const defaultTools = [EraserTool, HandTool, LaserTool, ZoomTool, SelectTool]; - -function FollowingIndicator() { - const editor = useEditor(); - const followingUserId = useValue("follow", () => editor.getInstanceState().followingUserId, [ - editor - ]); - if (!followingUserId) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(FollowingIndicatorInner, { userId: followingUserId }); -} -function FollowingIndicatorInner({ userId }) { - const presence = usePresence$1(userId); - if (!presence) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-following-indicator", style: { borderColor: presence.color } }); -} - -let defaultEditorAssetUrls = { - fonts: { - draw: `${getDefaultCdnBaseUrl()}/fonts/Shantell_Sans-Tldrawish.woff2`, - serif: `${getDefaultCdnBaseUrl()}/fonts/IBMPlexSerif-Medium.woff2`, - sansSerif: `${getDefaultCdnBaseUrl()}/fonts/IBMPlexSans-Medium.woff2`, - monospace: `${getDefaultCdnBaseUrl()}/fonts/IBMPlexMono-Medium.woff2` - } -}; -function useDefaultEditorAssetsWithOverrides(overrides) { - return reactExports.useMemo(() => { - if (!overrides) return defaultEditorAssetUrls; - return { - fonts: { ...defaultEditorAssetUrls.fonts, ...overrides?.fonts } - }; - }, [overrides]); -} - -const iconTypes = [ - "align-bottom", - "align-center-horizontal", - "align-center-vertical", - "align-left", - "align-right", - "align-top", - "arrow-left", - "arrowhead-arrow", - "arrowhead-bar", - "arrowhead-diamond", - "arrowhead-dot", - "arrowhead-none", - "arrowhead-square", - "arrowhead-triangle-inverted", - "arrowhead-triangle", - "blob", - "bring-forward", - "bring-to-front", - "broken", - "check-circle", - "check", - "chevron-down", - "chevron-left", - "chevron-right", - "chevron-up", - "chevrons-ne", - "chevrons-sw", - "clipboard-copied", - "clipboard-copy", - "color", - "cross-2", - "cross-circle", - "dash-dashed", - "dash-dotted", - "dash-draw", - "dash-solid", - "disconnected", - "discord", - "distribute-horizontal", - "distribute-vertical", - "dot", - "dots-horizontal", - "dots-vertical", - "drag-handle-dots", - "duplicate", - "edit", - "external-link", - "fill-fill", - "fill-none", - "fill-pattern", - "fill-semi", - "fill-solid", - "follow", - "following", - "font-draw", - "font-mono", - "font-sans", - "font-serif", - "geo-arrow-down", - "geo-arrow-left", - "geo-arrow-right", - "geo-arrow-up", - "geo-check-box", - "geo-cloud", - "geo-diamond", - "geo-ellipse", - "geo-heart", - "geo-hexagon", - "geo-octagon", - "geo-oval", - "geo-pentagon", - "geo-rectangle", - "geo-rhombus-2", - "geo-rhombus", - "geo-star", - "geo-trapezoid", - "geo-triangle", - "geo-x-box", - "github", - "group", - "horizontal-align-end", - "horizontal-align-middle", - "horizontal-align-start", - "info-circle", - "leading", - "link", - "lock", - "menu", - "minus", - "mixed", - "pack", - "plus", - "question-mark-circle", - "question-mark", - "redo", - "reset-zoom", - "rotate-ccw", - "rotate-cw", - "send-backward", - "send-to-back", - "share-1", - "size-extra-large", - "size-large", - "size-medium", - "size-small", - "spline-cubic", - "spline-line", - "stack-horizontal", - "stack-vertical", - "status-offline", - "stretch-horizontal", - "stretch-vertical", - "text-align-center", - "text-align-left", - "text-align-right", - "toggle-off", - "toggle-on", - "tool-arrow", - "tool-eraser", - "tool-frame", - "tool-hand", - "tool-highlight", - "tool-laser", - "tool-line", - "tool-media", - "tool-note", - "tool-pencil", - "tool-pointer", - "tool-screenshot", - "tool-text", - "trash", - "twitter", - "undo", - "ungroup", - "unlock", - "vertical-align-end", - "vertical-align-middle", - "vertical-align-start", - "warning-triangle", - "zoom-in", - "zoom-out" -]; - -let defaultUiAssetUrls = { - ...defaultEditorAssetUrls, - icons: Object.fromEntries( - iconTypes.map((name) => [name, `${getDefaultCdnBaseUrl()}/icons/icon/0_merged.svg#${name}`]) - ), - translations: Object.fromEntries( - LANGUAGES.map((lang) => [ - lang.locale, - `${getDefaultCdnBaseUrl()}/translations/${lang.locale}.json` - ]) - ), - embedIcons: Object.fromEntries( - DEFAULT_EMBED_DEFINITIONS.map((def) => [ - def.type, - `${getDefaultCdnBaseUrl()}/embed-icons/${def.type}.png` - ]) - ) -}; -function useDefaultUiAssetUrlsWithOverrides(overrides) { - if (!overrides) return defaultUiAssetUrls; - return { - fonts: Object.assign({ ...defaultUiAssetUrls.fonts }, { ...overrides?.fonts }), - icons: Object.assign({ ...defaultUiAssetUrls.icons }, { ...overrides?.icons }), - embedIcons: Object.assign({ ...defaultUiAssetUrls.embedIcons }, { ...overrides?.embedIcons }), - translations: Object.assign( - { ...defaultUiAssetUrls.translations }, - { ...overrides?.translations } - ) - }; -} - -var POPOVER_NAME = "Popover"; -var [createPopoverContext, createPopoverScope] = createContextScope(POPOVER_NAME, [ - createPopperScope -]); -var usePopperScope = createPopperScope(); -var [PopoverProvider, usePopoverContext] = createPopoverContext(POPOVER_NAME); -var Popover = (props) => { - const { - __scopePopover, - children, - open: openProp, - defaultOpen, - onOpenChange, - modal = false - } = props; - const popperScope = usePopperScope(__scopePopover); - const triggerRef = reactExports.useRef(null); - const [hasCustomAnchor, setHasCustomAnchor] = reactExports.useState(false); - const [open, setOpen] = useControllableState({ - prop: openProp, - defaultProp: defaultOpen ?? false, - onChange: onOpenChange, - caller: POPOVER_NAME - }); - return /* @__PURE__ */ jsxRuntimeExports.jsx(Root2$4, { ...popperScope, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - PopoverProvider, - { - scope: __scopePopover, - contentId: useId(), - triggerRef, - open, - onOpenChange: setOpen, - onOpenToggle: reactExports.useCallback(() => setOpen((prevOpen) => !prevOpen), [setOpen]), - hasCustomAnchor, - onCustomAnchorAdd: reactExports.useCallback(() => setHasCustomAnchor(true), []), - onCustomAnchorRemove: reactExports.useCallback(() => setHasCustomAnchor(false), []), - modal, - children - } - ) }); -}; -Popover.displayName = POPOVER_NAME; -var ANCHOR_NAME = "PopoverAnchor"; -var PopoverAnchor = reactExports.forwardRef( - (props, forwardedRef) => { - const { __scopePopover, ...anchorProps } = props; - const context = usePopoverContext(ANCHOR_NAME, __scopePopover); - const popperScope = usePopperScope(__scopePopover); - const { onCustomAnchorAdd, onCustomAnchorRemove } = context; - reactExports.useEffect(() => { - onCustomAnchorAdd(); - return () => onCustomAnchorRemove(); - }, [onCustomAnchorAdd, onCustomAnchorRemove]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(Anchor, { ...popperScope, ...anchorProps, ref: forwardedRef }); - } -); -PopoverAnchor.displayName = ANCHOR_NAME; -var TRIGGER_NAME = "PopoverTrigger"; -var PopoverTrigger = reactExports.forwardRef( - (props, forwardedRef) => { - const { __scopePopover, ...triggerProps } = props; - const context = usePopoverContext(TRIGGER_NAME, __scopePopover); - const popperScope = usePopperScope(__scopePopover); - const composedTriggerRef = useComposedRefs(forwardedRef, context.triggerRef); - const trigger = /* @__PURE__ */ jsxRuntimeExports.jsx( - Primitive.button, - { - type: "button", - "aria-haspopup": "dialog", - "aria-expanded": context.open, - "aria-controls": context.contentId, - "data-state": getState(context.open), - ...triggerProps, - ref: composedTriggerRef, - onClick: composeEventHandlers(props.onClick, context.onOpenToggle) - } - ); - return context.hasCustomAnchor ? trigger : /* @__PURE__ */ jsxRuntimeExports.jsx(Anchor, { asChild: true, ...popperScope, children: trigger }); - } -); -PopoverTrigger.displayName = TRIGGER_NAME; -var PORTAL_NAME = "PopoverPortal"; -var [PortalProvider, usePortalContext] = createPopoverContext(PORTAL_NAME, { - forceMount: void 0 -}); -var PopoverPortal = (props) => { - const { __scopePopover, forceMount, children, container } = props; - const context = usePopoverContext(PORTAL_NAME, __scopePopover); - return /* @__PURE__ */ jsxRuntimeExports.jsx(PortalProvider, { scope: __scopePopover, forceMount, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Presence, { present: forceMount || context.open, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Portal$3, { asChild: true, container, children }) }) }); -}; -PopoverPortal.displayName = PORTAL_NAME; -var CONTENT_NAME = "PopoverContent"; -var PopoverContent = reactExports.forwardRef( - (props, forwardedRef) => { - const portalContext = usePortalContext(CONTENT_NAME, props.__scopePopover); - const { forceMount = portalContext.forceMount, ...contentProps } = props; - const context = usePopoverContext(CONTENT_NAME, props.__scopePopover); - return /* @__PURE__ */ jsxRuntimeExports.jsx(Presence, { present: forceMount || context.open, children: context.modal ? /* @__PURE__ */ jsxRuntimeExports.jsx(PopoverContentModal, { ...contentProps, ref: forwardedRef }) : /* @__PURE__ */ jsxRuntimeExports.jsx(PopoverContentNonModal, { ...contentProps, ref: forwardedRef }) }); - } -); -PopoverContent.displayName = CONTENT_NAME; -var Slot = createSlot("PopoverContent.RemoveScroll"); -var PopoverContentModal = reactExports.forwardRef( - (props, forwardedRef) => { - const context = usePopoverContext(CONTENT_NAME, props.__scopePopover); - const contentRef = reactExports.useRef(null); - const composedRefs = useComposedRefs(forwardedRef, contentRef); - const isRightClickOutsideRef = reactExports.useRef(false); - reactExports.useEffect(() => { - const content = contentRef.current; - if (content) return hideOthers(content); - }, []); - return /* @__PURE__ */ jsxRuntimeExports.jsx(ReactRemoveScroll, { as: Slot, allowPinchZoom: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - PopoverContentImpl, - { - ...props, - ref: composedRefs, - trapFocus: context.open, - disableOutsidePointerEvents: true, - onCloseAutoFocus: composeEventHandlers(props.onCloseAutoFocus, (event) => { - event.preventDefault(); - if (!isRightClickOutsideRef.current) context.triggerRef.current?.focus(); - }), - onPointerDownOutside: composeEventHandlers( - props.onPointerDownOutside, - (event) => { - const originalEvent = event.detail.originalEvent; - const ctrlLeftClick = originalEvent.button === 0 && originalEvent.ctrlKey === true; - const isRightClick = originalEvent.button === 2 || ctrlLeftClick; - isRightClickOutsideRef.current = isRightClick; - }, - { checkForDefaultPrevented: false } - ), - onFocusOutside: composeEventHandlers( - props.onFocusOutside, - (event) => event.preventDefault(), - { checkForDefaultPrevented: false } - ) - } - ) }); - } -); -var PopoverContentNonModal = reactExports.forwardRef( - (props, forwardedRef) => { - const context = usePopoverContext(CONTENT_NAME, props.__scopePopover); - const hasInteractedOutsideRef = reactExports.useRef(false); - const hasPointerDownOutsideRef = reactExports.useRef(false); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - PopoverContentImpl, - { - ...props, - ref: forwardedRef, - trapFocus: false, - disableOutsidePointerEvents: false, - onCloseAutoFocus: (event) => { - props.onCloseAutoFocus?.(event); - if (!event.defaultPrevented) { - if (!hasInteractedOutsideRef.current) context.triggerRef.current?.focus(); - event.preventDefault(); - } - hasInteractedOutsideRef.current = false; - hasPointerDownOutsideRef.current = false; - }, - onInteractOutside: (event) => { - props.onInteractOutside?.(event); - if (!event.defaultPrevented) { - hasInteractedOutsideRef.current = true; - if (event.detail.originalEvent.type === "pointerdown") { - hasPointerDownOutsideRef.current = true; - } - } - const target = event.target; - const targetIsTrigger = context.triggerRef.current?.contains(target); - if (targetIsTrigger) event.preventDefault(); - if (event.detail.originalEvent.type === "focusin" && hasPointerDownOutsideRef.current) { - event.preventDefault(); - } - } - } - ); - } -); -var PopoverContentImpl = reactExports.forwardRef( - (props, forwardedRef) => { - const { - __scopePopover, - trapFocus, - onOpenAutoFocus, - onCloseAutoFocus, - disableOutsidePointerEvents, - onEscapeKeyDown, - onPointerDownOutside, - onFocusOutside, - onInteractOutside, - ...contentProps - } = props; - const context = usePopoverContext(CONTENT_NAME, __scopePopover); - const popperScope = usePopperScope(__scopePopover); - useFocusGuards(); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - FocusScope, - { - asChild: true, - loop: true, - trapped: trapFocus, - onMountAutoFocus: onOpenAutoFocus, - onUnmountAutoFocus: onCloseAutoFocus, - children: /* @__PURE__ */ jsxRuntimeExports.jsx( - DismissableLayer, - { - asChild: true, - disableOutsidePointerEvents, - onInteractOutside, - onEscapeKeyDown, - onPointerDownOutside, - onFocusOutside, - onDismiss: () => context.onOpenChange(false), - children: /* @__PURE__ */ jsxRuntimeExports.jsx( - Content$1, - { - "data-state": getState(context.open), - role: "dialog", - id: context.contentId, - ...popperScope, - ...contentProps, - ref: forwardedRef, - style: { - ...contentProps.style, - // re-namespace exposed content custom properties - ...{ - "--radix-popover-content-transform-origin": "var(--radix-popper-transform-origin)", - "--radix-popover-content-available-width": "var(--radix-popper-available-width)", - "--radix-popover-content-available-height": "var(--radix-popper-available-height)", - "--radix-popover-trigger-width": "var(--radix-popper-anchor-width)", - "--radix-popover-trigger-height": "var(--radix-popper-anchor-height)" - } - } - } - ) - } - ) - } - ); - } -); -var CLOSE_NAME = "PopoverClose"; -var PopoverClose = reactExports.forwardRef( - (props, forwardedRef) => { - const { __scopePopover, ...closeProps } = props; - const context = usePopoverContext(CLOSE_NAME, __scopePopover); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - Primitive.button, - { - type: "button", - ...closeProps, - ref: forwardedRef, - onClick: composeEventHandlers(props.onClick, () => context.onOpenChange(false)) - } - ); - } -); -PopoverClose.displayName = CLOSE_NAME; -var ARROW_NAME = "PopoverArrow"; -var PopoverArrow = reactExports.forwardRef( - (props, forwardedRef) => { - const { __scopePopover, ...arrowProps } = props; - const popperScope = usePopperScope(__scopePopover); - return /* @__PURE__ */ jsxRuntimeExports.jsx(Arrow, { ...popperScope, ...arrowProps, ref: forwardedRef }); - } -); -PopoverArrow.displayName = ARROW_NAME; -function getState(open) { - return open ? "open" : "closed"; -} -var Root2 = Popover; -var Trigger = PopoverTrigger; -var Portal = PopoverPortal; -var Content2 = PopoverContent; - -function TldrawUiPopover({ id, children, onOpenChange, open }) { - const [isOpen, handleOpenChange] = useMenuIsOpen(id, onOpenChange); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - Root2, - { - onOpenChange: handleOpenChange, - open: open || isOpen, - children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-popover", children }) - } - ); -} -function TldrawUiPopoverTrigger({ children }) { - return /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger, { asChild: true, dir: "ltr", children }); -} -function TldrawUiPopoverContent({ - side, - children, - align = "center", - sideOffset = 8, - alignOffset = 0, - disableEscapeKeyDown = false -}) { - const container = useContainer(); - return /* @__PURE__ */ jsxRuntimeExports.jsx(Portal, { container, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - Content2, - { - className: "tlui-popover__content", - side, - sideOffset, - align, - alignOffset, - dir: "ltr", - onEscapeKeyDown: (e) => disableEscapeKeyDown && e.preventDefault(), - children - } - ) }); -} - -function shapesWithUnboundArrows(editor) { - const selectedShapeIds = editor.getSelectedShapeIds(); - const selectedShapes = selectedShapeIds.map((id) => { - return editor.getShape(id); - }); - return selectedShapes.filter((shape) => { - if (!shape) return false; - if (editor.isShapeOfType(shape, "arrow")) { - const bindings = getArrowBindings(editor, shape); - if (bindings.start || bindings.end) return false; - } - return true; - }); -} -const useThreeStackableItems = () => { - const editor = useEditor(); - return useValue("threeStackableItems", () => shapesWithUnboundArrows(editor).length > 2, [editor]); -}; -const useIsInSelectState = () => { - const editor = useEditor(); - return useValue("isInSelectState", () => editor.isIn("select"), [editor]); -}; -const useAllowGroup = () => { - const editor = useEditor(); - return useValue( - "allow group", - () => { - const selectedShapes = editor.getSelectedShapes(); - if (selectedShapes.length < 2) return false; - for (const shape of selectedShapes) { - if (editor.isShapeOfType(shape, "arrow")) { - const bindings = getArrowBindings(editor, shape); - if (bindings.start) { - if (!selectedShapes.some((s) => s.id === bindings.start.toId)) { - return false; - } - } - if (bindings.end) { - if (!selectedShapes.some((s) => s.id === bindings.end.toId)) { - return false; - } - } - } - } - return true; - }, - [editor] - ); -}; -const useAllowUngroup = () => { - const editor = useEditor(); - return useValue( - "allowUngroup", - () => editor.getSelectedShapeIds().some((id) => editor.getShape(id)?.type === "group"), - [editor] - ); -}; -const showMenuPaste = typeof window !== "undefined" && "navigator" in window && Boolean(navigator.clipboard) && Boolean(navigator.clipboard.read); -function useAnySelectedShapesCount(min, max) { - const editor = useEditor(); - return useValue( - "selectedShapes", - () => { - const len = editor.getSelectedShapes().length; - { - { - return len >= min; - } - } - }, - [editor, min, max] - ); -} -function useUnlockedSelectedShapesCount(min, max) { - const editor = useEditor(); - return useValue( - "selectedShapes", - () => { - const len = editor.getSelectedShapes().filter((s) => !editor.isShapeOrAncestorLocked(s)).length; - if (min === void 0) { - { - return len; - } - } else { - { - return len >= min; - } - } - }, - [editor] - ); -} -function useShowAutoSizeToggle() { - const editor = useEditor(); - return useValue( - "showAutoSizeToggle", - () => { - const selectedShapes = editor.getSelectedShapes(); - return selectedShapes.length === 1 && editor.isShapeOfType(selectedShapes[0], "text") && selectedShapes[0].props.autoSize === false; - }, - [editor] - ); -} -function useHasLinkShapeSelected() { - const editor = useEditor(); - return useValue( - "hasLinkShapeSelected", - () => { - const onlySelectedShape = editor.getOnlySelectedShape(); - return !!(onlySelectedShape && onlySelectedShape.type !== "embed" && "url" in onlySelectedShape.props && !onlySelectedShape.isLocked); - }, - [editor] - ); -} -function useOnlyFlippableShape() { - const editor = useEditor(); - return useValue( - "onlyFlippableShape", - () => { - const shape = editor.getOnlySelectedShape(); - return shape && (editor.isShapeOfType(shape, "group") || editor.isShapeOfType(shape, "image") || editor.isShapeOfType(shape, "arrow") || editor.isShapeOfType(shape, "line") || editor.isShapeOfType(shape, "draw")); - }, - [editor] - ); -} -function useCanRedo() { - const editor = useEditor(); - return useValue("useCanRedo", () => editor.getCanRedo(), [editor]); -} -function useCanUndo() { - const editor = useEditor(); - return useValue("useCanUndo", () => editor.getCanUndo(), [editor]); -} - -function DefaultActionsMenuContent() { - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(AlignMenuItems, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(DistributeMenuItems, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(StackMenuItems, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ReorderMenuItems, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOrRotateMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCWMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(EditLinkMenuItem$1, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(GroupOrUngroupMenuItem, {}) - ] }); -} -function AlignMenuItems() { - const twoSelected = useUnlockedSelectedShapesCount(2); - const isInSelectState = useIsInSelectState(); - const enabled = twoSelected && isInSelectState; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-left", disabled: !enabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-center-horizontal", disabled: !enabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-right", disabled: !enabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "stretch-horizontal", disabled: !enabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-top", disabled: !enabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-center-vertical", disabled: !enabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-bottom", disabled: !enabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "stretch-vertical", disabled: !enabled }) - ] }); -} -function DistributeMenuItems() { - const threeSelected = useUnlockedSelectedShapesCount(3); - const isInSelectState = useIsInSelectState(); - const enabled = threeSelected && isInSelectState; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "distribute-horizontal", disabled: !enabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "distribute-vertical", disabled: !enabled }) - ] }); -} -function StackMenuItems() { - const threeStackableItems = useThreeStackableItems(); - const isInSelectState = useIsInSelectState(); - const enabled = threeStackableItems && isInSelectState; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "stack-horizontal", disabled: !enabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "stack-vertical", disabled: !enabled }) - ] }); -} -function ReorderMenuItems() { - const oneSelected = useUnlockedSelectedShapesCount(1); - const isInSelectState = useIsInSelectState(); - const enabled = oneSelected && isInSelectState; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "send-to-back", disabled: !enabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "send-backward", disabled: !enabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "bring-forward", disabled: !enabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "bring-to-front", disabled: !enabled }) - ] }); -} -function ZoomOrRotateMenuItem() { - const breakpoint = useBreakpoint(); - return breakpoint < PORTRAIT_BREAKPOINT.TABLET_SM ? /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomTo100MenuItem$1, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCCWMenuItem, {}); -} -function ZoomTo100MenuItem$1() { - const editor = useEditor(); - const isZoomedTo100 = useValue("zoom is 1", () => editor.getZoomLevel() === 1, [editor]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "zoom-to-100", disabled: isZoomedTo100 }); -} -function RotateCCWMenuItem() { - const oneSelected = useUnlockedSelectedShapesCount(1); - const isInSelectState = useIsInSelectState(); - const enabled = oneSelected && isInSelectState; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "rotate-ccw", disabled: !enabled }); -} -function RotateCWMenuItem() { - const oneSelected = useUnlockedSelectedShapesCount(1); - const isInSelectState = useIsInSelectState(); - const enabled = oneSelected && isInSelectState; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "rotate-cw", disabled: !enabled }); -} -function EditLinkMenuItem$1() { - const showEditLink = useHasLinkShapeSelected(); - const isInSelectState = useIsInSelectState(); - const enabled = showEditLink && isInSelectState; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "edit-link", disabled: !enabled }); -} -function GroupOrUngroupMenuItem() { - const allowGroup = useAllowGroup(); - const allowUngroup = useAllowUngroup(); - return allowGroup ? /* @__PURE__ */ jsxRuntimeExports.jsx(GroupMenuItem$1, {}) : allowUngroup ? /* @__PURE__ */ jsxRuntimeExports.jsx(UngroupMenuItem$1, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(GroupMenuItem$1, {}); -} -function GroupMenuItem$1() { - const twoSelected = useUnlockedSelectedShapesCount(2); - const isInSelectState = useIsInSelectState(); - const enabled = twoSelected && isInSelectState; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "group", disabled: !enabled }); -} -function UngroupMenuItem$1() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "ungroup" }); -} - -const DefaultActionsMenu = reactExports.memo(function DefaultActionsMenu2({ - children -}) { - const msg = useTranslation(); - const breakpoint = useBreakpoint(); - const isReadonlyMode = useReadonly(); - const ref = reactExports.useRef(null); - usePassThroughWheelEvents(ref); - const editor = useEditor(); - const isInAcceptableReadonlyState = useValue( - "should display quick actions when in readonly", - () => editor.isInAny("hand", "zoom"), - [editor] - ); - const content = children ?? /* @__PURE__ */ jsxRuntimeExports.jsx(DefaultActionsMenuContent, {}); - if (isReadonlyMode && !isInAcceptableReadonlyState) return; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiPopover, { id: "actions-menu", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiPopoverTrigger, { children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - "data-testid": "actions-menu.button", - title: msg("actions-menu.title"), - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "dots-vertical", small: true }) - } - ) }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiPopoverContent, - { - side: breakpoint >= PORTRAIT_BREAKPOINT.TABLET ? "bottom" : "top", - sideOffset: 6, - children: /* @__PURE__ */ jsxRuntimeExports.jsx( - "div", - { - ref, - className: "tlui-actions-menu tlui-buttons__grid", - "data-testid": "actions-menu.content", - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuContextProvider, { type: "icons", sourceId: "actions-menu", children: content }) - } - ) - } - ) - ] }); -}); - -function ToggleAutoSizeMenuItem() { - const shouldDisplay = useShowAutoSizeToggle(); - if (!shouldDisplay) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "toggle-auto-size" }); -} -function EditLinkMenuItem() { - const shouldDisplay = useHasLinkShapeSelected(); - if (!shouldDisplay) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "edit-link" }); -} -function DuplicateMenuItem() { - const shouldDisplay = useUnlockedSelectedShapesCount(1); - if (!shouldDisplay) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "duplicate" }); -} -function FlattenMenuItem() { - const editor = useEditor(); - const shouldDisplay = useValue( - "should display flatten option", - () => { - const selectedShapeIds = editor.getSelectedShapeIds(); - if (selectedShapeIds.length === 0) return false; - const onlySelectedShape = editor.getOnlySelectedShape(); - if (onlySelectedShape && editor.isShapeOfType(onlySelectedShape, "image")) { - return false; - } - return true; - }, - [editor] - ); - if (!shouldDisplay) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "flatten-to-image" }); -} -function GroupMenuItem() { - const shouldDisplay = useAllowGroup(); - if (!shouldDisplay) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "group" }); -} -function UngroupMenuItem() { - const shouldDisplay = useAllowUngroup(); - if (!shouldDisplay) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "ungroup" }); -} -function RemoveFrameMenuItem() { - const editor = useEditor(); - const shouldDisplay = useValue( - "allow unframe", - () => { - const selectedShapes = editor.getSelectedShapes(); - if (selectedShapes.length === 0) return false; - return selectedShapes.every((shape) => editor.isShapeOfType(shape, "frame")); - }, - [editor] - ); - if (!shouldDisplay) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "remove-frame" }); -} -function FitFrameToContentMenuItem() { - const editor = useEditor(); - const shouldDisplay = useValue( - "allow fit frame to content", - () => { - const onlySelectedShape = editor.getOnlySelectedShape(); - if (!onlySelectedShape) return false; - return editor.isShapeOfType(onlySelectedShape, "frame") && editor.getSortedChildIdsForParent(onlySelectedShape).length > 0; - }, - [editor] - ); - if (!shouldDisplay) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "fit-frame-to-content" }); -} -function ToggleLockMenuItem() { - const editor = useEditor(); - const shouldDisplay = useValue("selected shapes", () => editor.getSelectedShapes().length > 0, [ - editor - ]); - if (!shouldDisplay) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "toggle-lock" }); -} -function ToggleTransparentBgMenuItem() { - const editor = useEditor(); - const isTransparentBg = useValue( - "isTransparentBg", - () => !editor.getInstanceState().exportBackground, - [editor] - ); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuActionCheckboxItem, - { - actionId: "toggle-transparent", - checked: isTransparentBg, - toggle: true - } - ); -} -function UnlockAllMenuItem() { - const editor = useEditor(); - const shouldDisplay = useValue("any shapes", () => editor.getCurrentPageShapeIds().size > 0, [ - editor - ]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "unlock-all", disabled: !shouldDisplay }); -} -function ZoomTo100MenuItem() { - const editor = useEditor(); - const isZoomedTo100 = useValue("zoomed to 100", () => editor.getZoomLevel() === 1, [editor]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "zoom-to-100", noClose: true, disabled: isZoomedTo100 }); -} -function ZoomToFitMenuItem() { - const editor = useEditor(); - const hasShapes = useValue("has shapes", () => editor.getCurrentPageShapeIds().size > 0, [editor]); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuActionItem, - { - actionId: "zoom-to-fit", - disabled: !hasShapes, - "data-testid": "minimap.zoom-menu.zoom-to-fit", - noClose: true - } - ); -} -function ZoomToSelectionMenuItem() { - const editor = useEditor(); - const hasSelected = useValue("has shapes", () => editor.getSelectedShapeIds().length > 0, [ - editor - ]); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuActionItem, - { - actionId: "zoom-to-selection", - disabled: !hasSelected, - "data-testid": "minimap.zoom-menu.zoom-to-selection", - noClose: true - } - ); -} -function ClipboardMenuGroup() { - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "clipboard", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(CutMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(CopyMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(PasteMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(DuplicateMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(DeleteMenuItem, {}) - ] }); -} -function CopyAsMenuGroup() { - const editor = useEditor(); - const atLeastOneShapeOnPage = useValue( - "atLeastOneShapeOnPage", - () => editor.getCurrentPageShapeIds().size > 0, - [editor] - ); - return /* @__PURE__ */ jsxRuntimeExports.jsxs( - TldrawUiMenuSubmenu, - { - id: "copy-as", - label: "context-menu.copy-as", - size: "small", - disabled: !atLeastOneShapeOnPage, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "copy-as-group", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "copy-as-svg" }), - Boolean(window.navigator.clipboard?.write) && /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "copy-as-png" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "copy-as-json" }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { id: "copy-as-bg", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleTransparentBgMenuItem, {}) }) - ] - } - ); -} -function CutMenuItem() { - const shouldDisplay = useUnlockedSelectedShapesCount(1); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "cut", disabled: !shouldDisplay }); -} -function CopyMenuItem() { - const shouldDisplay = useAnySelectedShapesCount(1); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "copy", disabled: !shouldDisplay }); -} -function PasteMenuItem() { - const shouldDisplay = showMenuPaste; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "paste", disabled: !shouldDisplay }); -} -function ConversionsMenuGroup() { - const editor = useEditor(); - const atLeastOneShapeOnPage = useValue( - "atLeastOneShapeOnPage", - () => editor.getCurrentPageShapeIds().size > 0, - [editor] - ); - if (!atLeastOneShapeOnPage) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "conversions", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(CopyAsMenuGroup, {}), - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuSubmenu, { id: "export-as", label: "context-menu.export-as", size: "small", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "export-as-group", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "export-as-svg" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "export-as-png" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "export-as-json" }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { id: "export-as-bg", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleTransparentBgMenuItem, {}) }) - ] }) - ] }); -} -function SelectAllMenuItem() { - const editor = useEditor(); - const atLeastOneShapeOnPage = useValue( - "atLeastOneShapeOnPage", - () => editor.getCurrentPageShapeIds().size > 0, - [editor] - ); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "select-all", disabled: !atLeastOneShapeOnPage }); -} -function DeleteMenuItem() { - const oneSelected = useUnlockedSelectedShapesCount(1); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "delete", disabled: !oneSelected }); -} -function EditMenuSubmenu() { - const isReadonlyMode = useReadonly(); - if (!useAnySelectedShapesCount(1)) return null; - if (isReadonlyMode) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuSubmenu, { id: "edit", label: "context-menu.edit", size: "small", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(GroupMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(UngroupMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(FlattenMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(EditLinkMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(FitFrameToContentMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(RemoveFrameMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ConvertToEmbedMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ConvertToBookmarkMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleAutoSizeMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleLockMenuItem, {}) - ] }); -} -function ArrangeMenuSubmenu() { - const twoSelected = useUnlockedSelectedShapesCount(2); - const onlyFlippableShapeSelected = useOnlyFlippableShape(); - const isReadonlyMode = useReadonly(); - if (isReadonlyMode) return null; - if (!(twoSelected || onlyFlippableShapeSelected)) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuSubmenu, { id: "arrange", label: "context-menu.arrange", size: "small", children: [ - twoSelected && /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "align", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-left" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-center-horizontal" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-right" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-top" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-center-vertical" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-bottom" }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx(DistributeMenuGroup, {}), - twoSelected && /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "stretch", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "stretch-horizontal" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "stretch-vertical" }) - ] }), - (twoSelected || onlyFlippableShapeSelected) && /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "flip", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "flip-horizontal" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "flip-vertical" }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx(OrderMenuGroup, {}) - ] }); -} -function DistributeMenuGroup() { - const threeSelected = useUnlockedSelectedShapesCount(3); - if (!threeSelected) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "distribute", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "distribute-horizontal" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "distribute-vertical" }) - ] }); -} -function OrderMenuGroup() { - const twoSelected = useUnlockedSelectedShapesCount(2); - const threeStackableItems = useThreeStackableItems(); - if (!twoSelected) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "order", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "pack" }), - threeStackableItems && /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "stack-horizontal" }), - threeStackableItems && /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "stack-vertical" }) - ] }); -} -function ReorderMenuSubmenu() { - const isReadonlyMode = useReadonly(); - const oneSelected = useUnlockedSelectedShapesCount(1); - if (isReadonlyMode) return null; - if (!oneSelected) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuSubmenu, { id: "reorder", label: "context-menu.reorder", size: "small", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "reorder", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "bring-to-front" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "bring-forward" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "send-backward" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "send-to-back" }) - ] }) }); -} -function MoveToPageMenu() { - const editor = useEditor(); - const pages = useValue("pages", () => editor.getPages(), [editor]); - const currentPageId = useValue("current page id", () => editor.getCurrentPageId(), [editor]); - const { addToast } = useToasts(); - const trackEvent = useUiEvents(); - const isReadonlyMode = useReadonly(); - const oneSelected = useUnlockedSelectedShapesCount(1); - if (!oneSelected) return null; - if (isReadonlyMode) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuSubmenu, { id: "move-to-page", label: "context-menu.move-to-page", size: "small", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { id: "pages", children: pages.map((page) => /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuItem, - { - id: page.id, - disabled: currentPageId === page.id, - label: page.name.length > 30 ? `${page.name.slice(0, 30)}\u2026` : page.name, - onSelect: () => { - editor.markHistoryStoppingPoint("move_shapes_to_page"); - editor.moveShapesToPage(editor.getSelectedShapeIds(), page.id); - const toPage = editor.getPage(page.id); - if (toPage) { - addToast({ - title: "Changed Page", - description: `Moved to ${toPage.name}.`, - actions: [ - { - label: "Go Back", - type: "primary", - onClick: () => { - editor.markHistoryStoppingPoint("change-page"); - editor.setCurrentPage(currentPageId); - } - } - ] - }); - } - trackEvent("move-to-page", { source: "context-menu" }); - } - }, - page.id - )) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { id: "new-page", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "move-to-new-page" }) }) - ] }); -} -function ConvertToBookmarkMenuItem() { - const editor = useEditor(); - const oneEmbedSelected = useValue( - "oneEmbedSelected", - () => { - const onlySelectedShape = editor.getOnlySelectedShape(); - if (!onlySelectedShape) return false; - return !!(editor.isShapeOfType(onlySelectedShape, "embed") && onlySelectedShape.props.url && !editor.isShapeOrAncestorLocked(onlySelectedShape)); - }, - [editor] - ); - if (!oneEmbedSelected) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "convert-to-bookmark" }); -} -function ConvertToEmbedMenuItem() { - const editor = useEditor(); - const getEmbedDefinition = useGetEmbedDefinition(); - const oneEmbeddableBookmarkSelected = useValue( - "oneEmbeddableBookmarkSelected", - () => { - const onlySelectedShape = editor.getOnlySelectedShape(); - if (!onlySelectedShape) return false; - return !!(editor.isShapeOfType(onlySelectedShape, "bookmark") && onlySelectedShape.props.url && getEmbedDefinition(onlySelectedShape.props.url) && !editor.isShapeOrAncestorLocked(onlySelectedShape)); - }, - [editor] - ); - if (!oneEmbeddableBookmarkSelected) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "convert-to-embed" }); -} -function ToggleSnapModeItem() { - const editor = useEditor(); - const isSnapMode = useValue("isSnapMode", () => editor.user.getIsSnapMode(), [editor]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionCheckboxItem, { actionId: "toggle-snap-mode", checked: isSnapMode }); -} -function ToggleToolLockItem() { - const editor = useEditor(); - const isToolLock = useValue("isToolLock", () => editor.getInstanceState().isToolLocked, [editor]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionCheckboxItem, { actionId: "toggle-tool-lock", checked: isToolLock }); -} -function ToggleGridItem() { - const editor = useEditor(); - const isGridMode = useValue("isGridMode", () => editor.getInstanceState().isGridMode, [editor]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionCheckboxItem, { actionId: "toggle-grid", checked: isGridMode }); -} -function ToggleWrapModeItem() { - const editor = useEditor(); - const isWrapMode = useValue("isWrapMode", () => editor.user.getIsWrapMode(), [editor]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionCheckboxItem, { actionId: "toggle-wrap-mode", checked: isWrapMode }); -} -function ToggleFocusModeItem() { - const editor = useEditor(); - const isFocusMode = useValue("isFocusMode", () => editor.getInstanceState().isFocusMode, [editor]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionCheckboxItem, { actionId: "toggle-focus-mode", checked: isFocusMode }); -} -function ToggleEdgeScrollingItem() { - const editor = useEditor(); - const edgeScrollSpeed = useValue("edgeScrollSpeed", () => editor.user.getEdgeScrollSpeed(), [ - editor - ]); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuActionCheckboxItem, - { - actionId: "toggle-edge-scrolling", - checked: edgeScrollSpeed === 1 - } - ); -} -function ToggleReduceMotionItem() { - const editor = useEditor(); - const animationSpeed = useValue("animationSpeed", () => editor.user.getAnimationSpeed(), [editor]); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuActionCheckboxItem, - { - actionId: "toggle-reduce-motion", - checked: animationSpeed === 0 - } - ); -} -function ToggleDebugModeItem() { - const editor = useEditor(); - const isDebugMode = useValue("isDebugMode", () => editor.getInstanceState().isDebugMode, [editor]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionCheckboxItem, { actionId: "toggle-debug-mode", checked: isDebugMode }); -} -function ToggleDynamicSizeModeItem() { - const editor = useEditor(); - const isDynamicResizeMode = useValue( - "dynamic resize", - () => editor.user.getIsDynamicResizeMode(), - [editor] - ); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuActionCheckboxItem, - { - actionId: "toggle-dynamic-size-mode", - checked: isDynamicResizeMode - } - ); -} -function TogglePasteAtCursorItem() { - const editor = useEditor(); - const pasteAtCursor = useValue("paste at cursor", () => editor.user.getIsPasteAtCursorMode(), [ - editor - ]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionCheckboxItem, { actionId: "toggle-paste-at-cursor", checked: pasteAtCursor }); -} -function CursorChatItem() { - const editor = useEditor(); - const shouldShow = useValue( - "show cursor chat", - () => editor.getCurrentToolId() === "select" && !editor.getInstanceState().isCoarsePointer, - [editor] - ); - if (!shouldShow) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "open-cursor-chat" }); -} - -function DefaultContextMenuContent() { - const editor = useEditor(); - const showCollaborationUi = useShowCollaborationUi(); - const selectToolActive = useValue( - "isSelectToolActive", - () => editor.getCurrentToolId() === "select", - [editor] - ); - const isSinglePageMode = useValue("isSinglePageMode", () => editor.options.maxPages <= 1, [ - editor - ]); - if (!selectToolActive) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - showCollaborationUi && /* @__PURE__ */ jsxRuntimeExports.jsx(CursorChatItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "modify", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(EditMenuSubmenu, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ArrangeMenuSubmenu, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ReorderMenuSubmenu, {}), - !isSinglePageMode && /* @__PURE__ */ jsxRuntimeExports.jsx(MoveToPageMenu, {}) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ClipboardMenuGroup, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ConversionsMenuGroup, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { id: "select-all", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectAllMenuItem, {}) }) - ] }); -} - -const DefaultContextMenu = reactExports.memo(function DefaultContextMenu2({ - children, - disabled = false -}) { - const editor = useEditor(); - const { Canvas } = useEditorComponents(); - const cb = reactExports.useCallback( - (isOpen2) => { - if (!isOpen2) { - const onlySelectedShape = editor.getOnlySelectedShape(); - if (onlySelectedShape && editor.isShapeOrAncestorLocked(onlySelectedShape)) { - editor.setSelectedShapes([]); - } - } else { - if (editor.getInstanceState().isCoarsePointer) { - const selectedShapes = editor.getSelectedShapes(); - const { - inputs: { currentPagePoint } - } = editor; - const shapesAtPoint = editor.getShapesAtPoint(currentPagePoint); - if ( - // if there are no selected shapes - !editor.getSelectedShapes().length || // OR if none of the shapes at the point include the selected shape - !shapesAtPoint.some((s) => selectedShapes.includes(s)) - ) { - const lockedShapes = shapesAtPoint.filter((s) => editor.isShapeOrAncestorLocked(s)); - if (lockedShapes.length) { - editor.select(...lockedShapes.map((s) => s.id)); - } - } - } - } - }, - [editor] - ); - const container = useContainer(); - const [isOpen, handleOpenChange] = useMenuIsOpen("context menu", cb); - const content = children ?? /* @__PURE__ */ jsxRuntimeExports.jsx(DefaultContextMenuContent, {}); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(Root2$3, { dir: "ltr", onOpenChange: handleOpenChange, modal: false, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$2, { onContextMenu: void 0, dir: "ltr", disabled, children: Canvas ? /* @__PURE__ */ jsxRuntimeExports.jsx(Canvas, {}) : null }), - isOpen && /* @__PURE__ */ jsxRuntimeExports.jsx(Portal2$1, { container, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - Content2$2, - { - className: "tlui-menu scrollable", - "data-testid": "context-menu", - alignOffset: -4, - collisionPadding: 4, - onContextMenu: preventDefault, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuContextProvider, { type: "context-menu", sourceId: "context-menu", children: content }) - } - ) }) - ] }); -}); - -const CHAT_MESSAGE_TIMEOUT_CLOSING = 2e3; -const CHAT_MESSAGE_TIMEOUT_CHATTING = 5e3; -const CursorChatBubble = track(function CursorChatBubble2() { - const editor = useEditor(); - const { isChatting, chatMessage } = editor.getInstanceState(); - const rTimeout = reactExports.useRef(-1); - const [value, setValue] = reactExports.useState(""); - reactExports.useEffect(() => { - const closingUp = !isChatting && chatMessage; - if (closingUp || isChatting) { - const duration = isChatting ? CHAT_MESSAGE_TIMEOUT_CHATTING : CHAT_MESSAGE_TIMEOUT_CLOSING; - rTimeout.current = editor.timers.setTimeout(() => { - editor.updateInstanceState({ chatMessage: "", isChatting: false }); - setValue(""); - editor.focus(); - }, duration); - } - return () => { - clearTimeout(rTimeout.current); - }; - }, [editor, chatMessage, isChatting]); - if (isChatting) - return /* @__PURE__ */ jsxRuntimeExports.jsx(CursorChatInput, { value, setValue, chatMessage }); - return chatMessage.trim() ? /* @__PURE__ */ jsxRuntimeExports.jsx(NotEditingChatMessage, { chatMessage }) : null; -}); -function usePositionBubble(ref) { - const editor = useEditor(); - reactExports.useLayoutEffect(() => { - const elm = ref.current; - if (!elm) return; - const { x, y } = editor.inputs.currentScreenPoint; - ref.current?.style.setProperty("transform", `translate(${x}px, ${y}px)`); - function positionChatBubble(e) { - const { minX, minY } = editor.getViewportScreenBounds(); - ref.current?.style.setProperty( - "transform", - `translate(${e.clientX - minX}px, ${e.clientY - minY}px)` - ); - } - window.addEventListener("pointermove", positionChatBubble); - return () => { - window.removeEventListener("pointermove", positionChatBubble); - }; - }, [ref, editor]); -} -const NotEditingChatMessage = ({ chatMessage }) => { - const editor = useEditor(); - const ref = reactExports.useRef(null); - usePositionBubble(ref); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "div", - { - ref, - className: "tl-cursor-chat tl-cursor-chat__bubble", - style: { backgroundColor: editor.user.getColor() }, - children: chatMessage - } - ); -}; -const CursorChatInput = track(function CursorChatInput2({ - chatMessage, - value, - setValue -}) { - const editor = useEditor(); - const msg = useTranslation(); - const ref = reactExports.useRef(null); - const placeholder = chatMessage || msg("cursor-chat.type-to-chat"); - usePositionBubble(ref); - reactExports.useLayoutEffect(() => { - const elm = ref.current; - if (!elm) return; - const textMeasurement = editor.textMeasure.measureText(value || placeholder, { - fontFamily: "var(--font-body)", - fontSize: 12, - fontWeight: "500", - fontStyle: "normal", - maxWidth: null, - lineHeight: 1, - padding: "6px" - }); - elm.style.setProperty("width", textMeasurement.w + "px"); - }, [editor, value, placeholder]); - reactExports.useLayoutEffect(() => { - const raf = editor.timers.requestAnimationFrame(() => { - ref.current?.focus(); - }); - return () => { - cancelAnimationFrame(raf); - }; - }, [editor]); - const stopChatting = reactExports.useCallback(() => { - editor.updateInstanceState({ isChatting: false }); - editor.focus(); - }, [editor]); - const handleChange = reactExports.useCallback( - (e) => { - const { value: value2 } = e.target; - setValue(value2.slice(0, 64)); - editor.updateInstanceState({ chatMessage: value2 }); - }, - [editor, setValue] - ); - const handleKeyDown = reactExports.useCallback( - (e) => { - const elm = ref.current; - if (!elm) return; - const { value: currentValue } = elm; - switch (e.key) { - case "Enter": { - preventDefault(e); - e.stopPropagation(); - if (!currentValue) { - stopChatting(); - return; - } - setValue(""); - break; - } - case "Escape": { - preventDefault(e); - e.stopPropagation(); - stopChatting(); - break; - } - } - }, - [stopChatting, setValue] - ); - const handlePaste = reactExports.useCallback((e) => { - e.stopPropagation(); - }, []); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "input", - { - ref, - className: `tl-cursor-chat`, - style: { backgroundColor: editor.user.getColor() }, - onBlur: stopChatting, - onChange: handleChange, - onKeyDown: handleKeyDown, - onPaste: handlePaste, - value, - placeholder, - spellCheck: false - } - ); -}); - -function TldrawUiButtonCheck({ checked }) { - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiIcon, { icon: checked ? "check" : "none", className: "tlui-button__icon", small: true }); -} - -function DefaultDebugMenuContent() { - const editor = useEditor(); - const { addToast } = useToasts(); - const { addDialog } = useDialogs(); - const [error, setError] = React.useState(false); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "items", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuItem, - { - id: "add-toast", - onSelect: () => { - addToast({ - id: uniqueId(), - title: "Something good happened", - description: "Hey, attend to this thing over here. It might be important!", - keepOpen: true, - severity: "success" - // icon?: string - // title?: string - // description?: string - // actions?: TLUiToastAction[] - }); - addToast({ - id: uniqueId(), - title: "Something happened", - description: "Hey, attend to this thing over here. It might be important!", - keepOpen: true, - severity: "info", - actions: [ - { - label: "Primary", - type: "primary", - onClick: () => { - } - }, - { - label: "Normal", - type: "normal", - onClick: () => { - } - }, - { - label: "Danger", - type: "danger", - onClick: () => { - } - } - ] - // icon?: string - // title?: string - // description?: string - // actions?: TLUiToastAction[] - }); - addToast({ - id: uniqueId(), - title: "Something maybe bad happened", - description: "Hey, attend to this thing over here. It might be important!", - keepOpen: true, - severity: "warning", - actions: [ - { - label: "Primary", - type: "primary", - onClick: () => { - } - }, - { - label: "Normal", - type: "normal", - onClick: () => { - } - }, - { - label: "Danger", - type: "danger", - onClick: () => { - } - } - ] - }); - addToast({ - id: uniqueId(), - title: "Something bad happened", - severity: "error", - keepOpen: true - }); - }, - label: untranslated("Show toast") - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuItem, - { - id: "show-dialog", - label: "Show dialog", - onSelect: () => { - addDialog({ - component: ({ onClose }) => /* @__PURE__ */ jsxRuntimeExports.jsx( - ExampleDialog, - { - displayDontShowAgain: true, - onCancel: () => onClose(), - onContinue: () => onClose() - } - ), - onClose: () => { - } - }); - } - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuItem, - { - id: "create-shapes", - label: "Create 100 shapes", - onSelect: () => createNShapes(editor, 100) - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuItem, - { - id: "count-nodes", - label: "Count shapes / nodes", - onSelect: () => { - const selectedShapes = editor.getSelectedShapes(); - const shapes = selectedShapes.length === 0 ? editor.getRenderingShapes() : selectedShapes; - window.alert( - `Shapes ${shapes.length}, DOM nodes:${document.querySelector(".tl-shapes").querySelectorAll("*")?.length}` - ); - } - } - ), - (() => { - if (error) throw Error("oh no!"); - return null; - })(), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuItem, { id: "throw-error", onSelect: () => setError(true), label: "Throw error" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuItem, { id: "hard-reset", onSelect: hardResetEditor, label: "Hard reset" }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "flags", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(DebugFlags, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(FeatureFlags, {}) - ] }) - ] }); -} -function DebugFlags() { - const items = Object.values(debugFlags); - if (!items.length) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuSubmenu, { id: "debug flags", label: "Debug Flags", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { id: "debug flags", children: items.map((flag) => /* @__PURE__ */ jsxRuntimeExports.jsx(DebugFlagToggle, { flag }, flag.name)) }) }); -} -function FeatureFlags() { - const items = Object.values(featureFlags); - if (!items.length) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuSubmenu, { id: "feature flags", label: "Feature Flags", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { id: "feature flags", children: items.map((flag) => /* @__PURE__ */ jsxRuntimeExports.jsx(DebugFlagToggle, { flag }, flag.name)) }) }); -} -function ExampleDialog({ - title = "title", - body = "hello hello hello", - cancel = "Cancel", - confirm = "Continue", - displayDontShowAgain = false, - onCancel, - onContinue -}) { - const [dontShowAgain, setDontShowAgain] = React.useState(false); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiDialogHeader, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDialogTitle, { children: title }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDialogCloseButton, {}) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDialogBody, { style: { maxWidth: 350 }, children: body }), - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiDialogFooter, { className: "tlui-dialog__footer__actions", children: [ - displayDontShowAgain && /* @__PURE__ */ jsxRuntimeExports.jsxs( - TldrawUiButton, - { - type: "normal", - onClick: () => setDontShowAgain(!dontShowAgain), - style: { marginRight: "auto" }, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonCheck, { checked: dontShowAgain }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonLabel, { children: "Don\u2019t show again" }) - ] - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButton, { type: "normal", onClick: onCancel, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonLabel, { children: cancel }) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButton, { type: "primary", onClick: async () => onContinue(), children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonLabel, { children: confirm }) }) - ] }) - ] }); -} -const DebugFlagToggle = track(function DebugFlagToggle2({ - flag, - onChange -}) { - const value = flag.get(); - return ( - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuCheckboxItem, - { - id: flag.name, - title: flag.name, - label: flag.name.replace(/([a-z0-9])([A-Z])/g, (m) => `${m[0]} ${m[1].toLowerCase()}`).replace(/^[a-z]/, (m) => m.toUpperCase()), - checked: value, - onSelect: () => { - flag.set(!value); - onChange?.(!value); - } - } - ) - ); -}); -let t = 0; -function createNShapes(editor, n) { - const shapesToCreate = Array(n); - const cols = Math.floor(Math.sqrt(n)); - for (let i = 0; i < n; i++) { - t++; - shapesToCreate[i] = { - id: createShapeId("box" + t), - type: "geo", - x: i % cols * 132, - y: Math.floor(i / cols) * 132 - }; - } - editor.run(() => { - editor.createShapes(shapesToCreate).setSelectedShapes(shapesToCreate.map((s) => s.id)); - }); -} - -function DefaultDebugMenu({ children }) { - const content = children ?? /* @__PURE__ */ jsxRuntimeExports.jsx(DefaultDebugMenuContent, {}); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiDropdownMenuRoot, { id: "debug", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuTrigger, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButton, { type: "icon", title: "Debug menu", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "dots-horizontal" }) }) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuContent, { side: "top", align: "end", alignOffset: 0, children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuContextProvider, { type: "menu", sourceId: "debug-panel", children: content }) }) - ] }); -} - -const DefaultDebugPanel = reactExports.memo(function DefaultDebugPanel2() { - const { DebugMenu } = useTldrawUiComponents(); - const ref = reactExports.useRef(null); - usePassThroughWheelEvents(ref); - return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref, className: "tlui-debug-panel", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(CurrentState, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(FPS, {}), - DebugMenu && /* @__PURE__ */ jsxRuntimeExports.jsx(DebugMenu, {}) - ] }); -}); -function useTick(isEnabled = true) { - const [_, setTick] = reactExports.useState(0); - const editor = useEditor(); - reactExports.useEffect(() => { - if (!isEnabled) return; - const update = () => setTick((tick) => tick + 1); - editor.on("tick", update); - return () => { - editor.off("tick", update); - }; - }, [editor, isEnabled]); -} -const CurrentState = track(function CurrentState2() { - useTick(); - const editor = useEditor(); - const path = editor.getPath(); - const hoverShape = editor.getHoveredShape(); - const selectedShape = editor.getOnlySelectedShape(); - const shape = path === "select.idle" || !path.includes("select.") ? hoverShape : selectedShape; - const shapeInfo = shape && path.includes("select.") ? ` / ${shape.type || ""}${"geo" in shape.props ? " / " + shape.props.geo : ""} / [${Vec.ToInt(editor.getPointInShapeSpace(shape, editor.inputs.currentPagePoint))}]` : ""; - const ruler = path.startsWith("select.") && !path.includes(".idle") ? ` / [${Vec.ToInt(editor.inputs.originPagePoint)}] \u2192 [${Vec.ToInt( - editor.inputs.currentPagePoint - )}] = ${Vec.Dist(editor.inputs.originPagePoint, editor.inputs.currentPagePoint).toFixed(0)}` : ""; - return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-debug-panel__current-state", children: `${path}${shapeInfo}${ruler}` }); -}); -function FPS() { - const editor = useEditor(); - const showFps = useValue("show_fps", () => debugFlags.showFps.get(), [debugFlags]); - const fpsRef = reactExports.useRef(null); - reactExports.useEffect(() => { - if (!showFps) return; - const TICK_LENGTH = 250; - let maxKnownFps = 0; - let raf = -1; - let start = performance.now(); - let currentTickLength = 0; - let framesInCurrentTick = 0; - let isSlow = false; - function loop() { - framesInCurrentTick++; - currentTickLength = performance.now() - start; - if (currentTickLength > TICK_LENGTH) { - const fps = Math.round( - framesInCurrentTick * (TICK_LENGTH / currentTickLength) * (1e3 / TICK_LENGTH) - ); - if (fps > maxKnownFps) { - maxKnownFps = fps; - } - const slowFps = maxKnownFps * 0.75; - if (fps < slowFps && !isSlow || fps >= slowFps && isSlow) { - isSlow = !isSlow; - } - fpsRef.current.innerHTML = `FPS ${fps.toString()}`; - fpsRef.current.className = `tlui-debug-panel__fps` + (isSlow ? ` tlui-debug-panel__fps__slow` : ``); - currentTickLength -= TICK_LENGTH; - framesInCurrentTick = 0; - start = performance.now(); - } - raf = editor.timers.requestAnimationFrame(loop); - } - loop(); - return () => { - cancelAnimationFrame(raf); - }; - }, [showFps, editor]); - if (!showFps) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: fpsRef }); -} - -const DefaultMenuPanel = reactExports.memo(function MenuPanel() { - const breakpoint = useBreakpoint(); - const ref = reactExports.useRef(null); - usePassThroughWheelEvents(ref); - const { MainMenu, QuickActions, ActionsMenu, PageMenu } = useTldrawUiComponents(); - const editor = useEditor(); - const isSinglePageMode = useValue("isSinglePageMode", () => editor.options.maxPages <= 1, [ - editor - ]); - const showQuickActions = editor.options.actionShortcutsLocation === "menu" ? true : editor.options.actionShortcutsLocation === "toolbar" ? false : breakpoint >= PORTRAIT_BREAKPOINT.TABLET; - if (!MainMenu && !PageMenu && !showQuickActions) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref, className: "tlui-menu-zone", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-buttons__horizontal", children: [ - MainMenu && /* @__PURE__ */ jsxRuntimeExports.jsx(MainMenu, {}), - PageMenu && !isSinglePageMode && /* @__PURE__ */ jsxRuntimeExports.jsx(PageMenu, {}), - showQuickActions ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - QuickActions && /* @__PURE__ */ jsxRuntimeExports.jsx(QuickActions, {}), - ActionsMenu && /* @__PURE__ */ jsxRuntimeExports.jsx(ActionsMenu, {}) - ] }) : null - ] }) }); -}); - -function BackToContent() { - const editor = useEditor(); - const actions = useActions(); - const [showBackToContent, setShowBackToContent] = reactExports.useState(false); - const rIsShowing = reactExports.useRef(false); - useQuickReactor( - "toggle showback to content", - () => { - const showBackToContentPrev = rIsShowing.current; - const shapeIds = editor.getCurrentPageShapeIds(); - let showBackToContentNow = false; - if (shapeIds.size) { - showBackToContentNow = shapeIds.size === editor.getCulledShapes().size; - } - if (showBackToContentPrev !== showBackToContentNow) { - setShowBackToContent(showBackToContentNow); - rIsShowing.current = showBackToContentNow; - } - }, - [editor] - ); - if (!showBackToContent) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuActionItem, - { - actionId: "back-to-content", - onSelect: () => { - actions["back-to-content"].onSelect("helper-buttons"); - setShowBackToContent(false); - } - } - ); -} - -function ExitPenMode() { - const editor = useEditor(); - const isPenMode = useValue("is pen mode", () => editor.getInstanceState().isPenMode, [editor]); - if (!isPenMode) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "exit-pen-mode" }); -} - -function StopFollowing() { - const editor = useEditor(); - const actions = useActions(); - const followingUser = useValue( - "is following user", - () => !!editor.getInstanceState().followingUserId, - [editor] - ); - if (!followingUser) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuItem, { ...actions["stop-following"] }); -} - -function DefaultHelperButtonsContent() { - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ExitPenMode, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(BackToContent, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(StopFollowing, {}) - ] }); -} - -function DefaultHelperButtons({ children }) { - const content = children ?? /* @__PURE__ */ jsxRuntimeExports.jsx(DefaultHelperButtonsContent, {}); - return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-helper-buttons", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuContextProvider, { type: "helper-buttons", sourceId: "helper-buttons", children: content }) }); -} - -function DefaultKeyboardShortcutsDialogContent() { - const showCollaborationUi = useShowCollaborationUi(); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { label: "shortcuts-dialog.tools", id: "tools", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "toggle-tool-lock" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "insert-media" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "select" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "draw" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "eraser" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "hand" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "rectangle" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "ellipse" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "arrow" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "line" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "text" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "frame" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "note" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "laser" }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuItem, - { - id: "pointer-down", - label: "tool.pointer-down", - kbd: ",", - onSelect: () => { - } - } - ) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { label: "shortcuts-dialog.preferences", id: "preferences", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "toggle-dark-mode" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "toggle-focus-mode" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "toggle-grid" }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { label: "shortcuts-dialog.edit", id: "edit", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "undo" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "redo" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "cut" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "copy" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "paste" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "select-all" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "delete" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "duplicate" }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { label: "shortcuts-dialog.view", id: "view", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "zoom-in" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "zoom-out" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "zoom-to-100" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "zoom-to-fit" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "zoom-to-selection" }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { label: "shortcuts-dialog.transform", id: "transform", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "bring-to-front" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "bring-forward" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "send-backward" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "send-to-back" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "group" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "ungroup" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "flip-horizontal" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "flip-vertical" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-top" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-center-vertical" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-bottom" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-left" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-center-horizontal" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "align-right" }) - ] }), - showCollaborationUi && /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { label: "shortcuts-dialog.collaboration", id: "collaboration", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "open-cursor-chat" }) }) - ] }); -} - -const DefaultKeyboardShortcutsDialog = reactExports.memo(function DefaultKeyboardShortcutsDialog2({ - children -}) { - const msg = useTranslation(); - const breakpoint = useBreakpoint(); - const content = children ?? /* @__PURE__ */ jsxRuntimeExports.jsx(DefaultKeyboardShortcutsDialogContent, {}); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiDialogHeader, { className: "tlui-shortcuts-dialog__header", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDialogTitle, { children: msg("shortcuts-dialog.title") }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDialogCloseButton, {}) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiDialogBody, - { - className: classNames("tlui-shortcuts-dialog__body", { - "tlui-shortcuts-dialog__body__mobile": breakpoint <= PORTRAIT_BREAKPOINT.MOBILE_XS, - "tlui-shortcuts-dialog__body__tablet": breakpoint <= PORTRAIT_BREAKPOINT.TABLET - }), - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuContextProvider, { type: "keyboard-shortcuts", sourceId: "kbd", children: content }) - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-dialog__scrim" }) - ] }); -}); - -function LanguageMenu() { - const editor = useMaybeEditor(); - const trackEvent = useUiEvents(); - const currentLanguage = useValue("locale", () => editor?.user.getLocale(), [editor]); - if (!editor) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuSubmenu, { id: "help menu language", label: "menu.language", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { id: "languages", children: LANGUAGES.map(({ locale, label }) => /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuCheckboxItem, - { - id: `language-${locale}`, - title: locale, - label, - checked: locale === currentLanguage, - readonlyOk: true, - onSelect: () => { - editor.user.updateUserPreferences({ locale }); - trackEvent("change-language", { source: "menu", locale }); - } - }, - locale - )) }) }); -} - -function DefaultHelpMenuContent() { - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(LanguageMenu, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(KeyboardShortcutsMenuItem, {}) - ] }); -} -function KeyboardShortcutsMenuItem() { - const { KeyboardShortcutsDialog } = useTldrawUiComponents(); - const { addDialog } = useDialogs(); - const handleSelect = reactExports.useCallback(() => { - if (KeyboardShortcutsDialog) addDialog({ component: KeyboardShortcutsDialog }); - }, [addDialog, KeyboardShortcutsDialog]); - if (!KeyboardShortcutsDialog) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuItem, - { - id: "keyboard-shortcuts-button", - label: "help-menu.keyboard-shortcuts", - readonlyOk: true, - onSelect: handleSelect - } - ); -} - -function DefaultMainMenuContent() { - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(EditSubmenu, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ViewSubmenu, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ExportFileContentSubMenu, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ExtrasGroup, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(PreferencesGroup, {}) - ] }); -} -function ExportFileContentSubMenu() { - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuSubmenu, { id: "export-all-as", label: "context-menu.export-all-as", size: "small", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "export-all-as-group", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "export-all-as-svg" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "export-all-as-png" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "export-all-as-json" }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { id: "export-all-as-bg", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleTransparentBgMenuItem, {}) }) - ] }); -} -function EditSubmenu() { - const editor = useEditor(); - const selectToolActive = useValue( - "isSelectToolActive", - () => editor.getCurrentToolId() === "select", - [editor] - ); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuSubmenu, { id: "edit", label: "menu.edit", disabled: !selectToolActive, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(UndoRedoGroup, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ClipboardMenuGroup, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ConversionsMenuGroup, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(MiscMenuGroup, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(LockGroup, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { id: "select-all", children: /* @__PURE__ */ jsxRuntimeExports.jsx(SelectAllMenuItem, {}) }) - ] }); -} -function MiscMenuGroup() { - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "misc", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(GroupMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(UngroupMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(EditLinkMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleAutoSizeMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(RemoveFrameMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(FitFrameToContentMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ConvertToEmbedMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ConvertToBookmarkMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(FlattenMenuItem, {}) - ] }); -} -function LockGroup() { - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "lock", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleLockMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(UnlockAllMenuItem, {}) - ] }); -} -function UndoRedoGroup() { - const canUndo = useCanUndo(); - const canRedo = useCanRedo(); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "undo-redo", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "undo", disabled: !canUndo }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "redo", disabled: !canRedo }) - ] }); -} -function ViewSubmenu() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuSubmenu, { id: "view", label: "menu.view", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "view-actions", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "zoom-in" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "zoom-out" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomTo100MenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomToFitMenuItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomToSelectionMenuItem, {}) - ] }) }); -} -function ExtrasGroup() { - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "extras", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "insert-embed" }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "insert-media" }) - ] }); -} -function PreferencesGroup() { - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "preferences", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuSubmenu, { id: "preferences", label: "menu.preferences", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "preferences-actions", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleSnapModeItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleToolLockItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleGridItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleWrapModeItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleFocusModeItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleEdgeScrollingItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleReduceMotionItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleDynamicSizeModeItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(TogglePasteAtCursorItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ToggleDebugModeItem, {}) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { id: "color-scheme", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ColorSchemeMenu, {}) }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx(LanguageMenu, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(KeyboardShortcutsMenuItem, {}) - ] }); -} - -const DefaultMainMenu = reactExports.memo(function DefaultMainMenu2({ children }) { - const container = useContainer(); - const [isOpen, onOpenChange] = useMenuIsOpen("main menu"); - const msg = useTranslation(); - const content = children ?? /* @__PURE__ */ jsxRuntimeExports.jsx(DefaultMainMenuContent, {}); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(Root2$2, { dir: "ltr", open: isOpen, onOpenChange, modal: false, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger$1, { asChild: true, dir: "ltr", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButton, { type: "icon", "data-testid": "main-menu.button", title: msg("menu.title"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "menu", small: true }) }) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(Portal2, { container, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - Content2$1, - { - className: "tlui-menu", - side: "bottom", - align: "start", - collisionPadding: 4, - alignOffset: 0, - sideOffset: 6, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuContextProvider, { type: "menu", sourceId: "main-menu", children: content }) - } - ) }) - ] }); -}); - -const memo = {}; -function getRgba(colorString) { - if (memo[colorString]) { - return memo[colorString]; - } - const canvas = document.createElement("canvas"); - const context = canvas.getContext("2d"); - context.fillStyle = colorString; - context.fillRect(0, 0, 1, 1); - const [r, g, b, a] = context.getImageData(0, 0, 1, 1).data; - const result = new Float32Array([r / 255, g / 255, b / 255, a / 255]); - memo[colorString] = result; - return result; -} - -const numArcSegmentsPerCorner = 10; -const roundedRectangleDataSize = ( - // num triangles in corners - (// num triangles in outer rects - 4 * 6 * numArcSegmentsPerCorner + // num triangles in center rect - 12 + 4 * 12) -); -function pie(array, { - center, - radius, - numArcSegments = 20, - startAngle = 0, - endAngle = PI2, - offset = 0 -}) { - const angle = (endAngle - startAngle) / numArcSegments; - let i = offset; - for (let a = startAngle; a < endAngle; a += angle) { - array[i++] = center.x; - array[i++] = center.y; - array[i++] = center.x + Math.cos(a) * radius; - array[i++] = center.y + Math.sin(a) * radius; - array[i++] = center.x + Math.cos(a + angle) * radius; - array[i++] = center.y + Math.sin(a + angle) * radius; - } - return array; -} -function rectangle(array, offset, x, y, w, h) { - array[offset++] = x; - array[offset++] = y; - array[offset++] = x; - array[offset++] = y + h; - array[offset++] = x + w; - array[offset++] = y; - array[offset++] = x + w; - array[offset++] = y; - array[offset++] = x; - array[offset++] = y + h; - array[offset++] = x + w; - array[offset++] = y + h; -} -function roundedRectangle(data, box, radius) { - const numArcSegments = numArcSegmentsPerCorner; - radius = Math.min(radius, Math.min(box.w, box.h) / 2); - const innerBox = Box.ExpandBy(box, -radius); - if (innerBox.w <= 0 || innerBox.h <= 0) { - pie(data, { center: box.center, radius, numArcSegments: numArcSegmentsPerCorner * 4 }); - return numArcSegmentsPerCorner * 4 * 6; - } - let offset = 0; - rectangle(data, offset, innerBox.minX, innerBox.minY, innerBox.w, innerBox.h); - offset += 12; - rectangle(data, offset, innerBox.minX, box.minY, innerBox.w, radius); - offset += 12; - rectangle(data, offset, innerBox.maxX, innerBox.minY, radius, innerBox.h); - offset += 12; - rectangle(data, offset, innerBox.minX, innerBox.maxY, innerBox.w, radius); - offset += 12; - rectangle(data, offset, box.minX, innerBox.minY, radius, innerBox.h); - offset += 12; - pie(data, { - numArcSegments, - offset, - center: innerBox.point, - radius, - startAngle: PI$1, - endAngle: PI$1 * 1.5 - }); - offset += numArcSegments * 6; - pie(data, { - numArcSegments, - offset, - center: Vec.Add(innerBox.point, new Vec(innerBox.w, 0)), - radius, - startAngle: PI$1 * 1.5, - endAngle: PI2 - }); - offset += numArcSegments * 6; - pie(data, { - numArcSegments, - offset, - center: Vec.Add(innerBox.point, innerBox.size), - radius, - startAngle: 0, - endAngle: HALF_PI - }); - offset += numArcSegments * 6; - pie(data, { - numArcSegments, - offset, - center: Vec.Add(innerBox.point, new Vec(0, innerBox.h)), - radius, - startAngle: HALF_PI, - endAngle: PI$1 - }); - return roundedRectangleDataSize; -} - -function setupWebGl(canvas) { - if (!canvas) throw new Error("Canvas element not found"); - const context = canvas.getContext("webgl2", { - premultipliedAlpha: false - }); - if (!context) throw new Error("Failed to get webgl2 context"); - const vertexShaderSourceCode = `#version 300 es - precision mediump float; - - in vec2 shapeVertexPosition; - - uniform vec4 canvasPageBounds; - - // taken (with thanks) from - // https://webglfundamentals.org/webgl/lessons/webgl-2d-matrices.html - void main() { - // convert the position from pixels to 0.0 to 1.0 - vec2 zeroToOne = (shapeVertexPosition - canvasPageBounds.xy) / canvasPageBounds.zw; - - // convert from 0->1 to 0->2 - vec2 zeroToTwo = zeroToOne * 2.0; - - // convert from 0->2 to -1->+1 (clipspace) - vec2 clipSpace = zeroToTwo - 1.0; - - gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); - }`; - const vertexShader = context.createShader(context.VERTEX_SHADER); - if (!vertexShader) { - throw new Error("Failed to create vertex shader"); - } - context.shaderSource(vertexShader, vertexShaderSourceCode); - context.compileShader(vertexShader); - if (!context.getShaderParameter(vertexShader, context.COMPILE_STATUS)) { - throw new Error("Failed to compile vertex shader"); - } - const fragmentShaderSourceCode = `#version 300 es - precision mediump float; - - uniform vec4 fillColor; - out vec4 outputColor; - - void main() { - outputColor = fillColor; - }`; - const fragmentShader = context.createShader(context.FRAGMENT_SHADER); - if (!fragmentShader) { - throw new Error("Failed to create fragment shader"); - } - context.shaderSource(fragmentShader, fragmentShaderSourceCode); - context.compileShader(fragmentShader); - if (!context.getShaderParameter(fragmentShader, context.COMPILE_STATUS)) { - throw new Error("Failed to compile fragment shader"); - } - const program = context.createProgram(); - if (!program) { - throw new Error("Failed to create program"); - } - context.attachShader(program, vertexShader); - context.attachShader(program, fragmentShader); - context.linkProgram(program); - if (!context.getProgramParameter(program, context.LINK_STATUS)) { - throw new Error("Failed to link program"); - } - context.useProgram(program); - const shapeVertexPositionAttributeLocation = context.getAttribLocation( - program, - "shapeVertexPosition" - ); - if (shapeVertexPositionAttributeLocation < 0) { - throw new Error("Failed to get shapeVertexPosition attribute location"); - } - context.enableVertexAttribArray(shapeVertexPositionAttributeLocation); - const canvasPageBoundsLocation = context.getUniformLocation(program, "canvasPageBounds"); - const fillColorLocation = context.getUniformLocation(program, "fillColor"); - const selectedShapesBuffer = context.createBuffer(); - if (!selectedShapesBuffer) throw new Error("Failed to create buffer"); - const unselectedShapesBuffer = context.createBuffer(); - if (!unselectedShapesBuffer) throw new Error("Failed to create buffer"); - return { - context, - selectedShapes: allocateBuffer(context, 1024), - unselectedShapes: allocateBuffer(context, 4096), - viewport: allocateBuffer(context, roundedRectangleDataSize), - collaborators: allocateBuffer(context, 1024), - prepareTriangles(stuff, len) { - context.bindBuffer(context.ARRAY_BUFFER, stuff.buffer); - context.bufferData(context.ARRAY_BUFFER, stuff.vertices, context.STATIC_DRAW, 0, len); - context.enableVertexAttribArray(shapeVertexPositionAttributeLocation); - context.vertexAttribPointer( - shapeVertexPositionAttributeLocation, - 2, - context.FLOAT, - false, - 0, - 0 - ); - }, - drawTrianglesTransparently(len) { - context.enable(context.BLEND); - context.blendFunc(context.SRC_ALPHA, context.ONE_MINUS_SRC_ALPHA); - context.drawArrays(context.TRIANGLES, 0, len / 2); - context.disable(context.BLEND); - }, - drawTriangles(len) { - context.drawArrays(context.TRIANGLES, 0, len / 2); - }, - setFillColor(color) { - context.uniform4fv(fillColorLocation, color); - }, - setCanvasPageBounds(bounds) { - context.uniform4fv(canvasPageBoundsLocation, bounds); - } - }; -} -function allocateBuffer(context, size) { - const buffer = context.createBuffer(); - if (!buffer) throw new Error("Failed to create buffer"); - return { buffer, vertices: new Float32Array(size) }; -} -function appendVertices(bufferStuff, offset, data) { - let len = bufferStuff.vertices.length; - while (len < offset + data.length) { - len *= 2; - } - if (len != bufferStuff.vertices.length) { - const newVertices = new Float32Array(len); - newVertices.set(bufferStuff.vertices); - bufferStuff.vertices = newVertices; - } - bufferStuff.vertices.set(data, offset); -} - -var __create = Object.create; -var __defProp = Object.defineProperty; -var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name); -var __typeError = (msg) => { - throw TypeError(msg); -}; -var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; -var __decoratorStart = (base) => [, , , __create(null)]; -var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; -var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn; -var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) }); -var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]); -var __runInitializers = (array, flags, self, value) => { - for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self) ; - return value; -}; -var __decorateElement = (array, flags, name, decorators, target, extra) => { - var it, done, ctx, access, k = flags & 7, s = false, p = false; - var j = 2 , key = __decoratorStrings[k + 5]; - var extraInitializers = array[j] || (array[j] = []); - var desc = ((target = target.prototype), __getOwnPropDesc(target , name)); - for (var i = decorators.length - 1; i >= 0; i--) { - ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers); - { - ctx.static = s, ctx.private = p, access = ctx.access = { has: (x) => name in x }; - access.get = (x) => x[name]; - } - it = (0, decorators[i])(desc[key] , ctx), done._ = 1; - __expectFn(it) && (desc[key] = it ); - } - return desc && __defProp(target, name, desc), target; -}; -var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); -var _render_dec, _getCanvasPageBoundsArray_dec, _getZoom_dec, _getCanvasPageBounds_dec, _getCanvasClientPosition_dec, _getCanvasSize_dec, _getContentScreenBounds_dec, _getContentPageBounds_dec, _getDpr_dec, _close_dec, _init; -_close_dec = [bind$2], _getDpr_dec = [computed], _getContentPageBounds_dec = [computed], _getContentScreenBounds_dec = [computed], _getCanvasSize_dec = [computed], _getCanvasClientPosition_dec = [computed], _getCanvasPageBounds_dec = [computed], _getZoom_dec = [computed], _getCanvasPageBoundsArray_dec = [computed], _render_dec = [bind$2]; -class MinimapManager { - constructor(editor, elem, container) { - this.editor = editor; - this.elem = elem; - this.container = container; - __runInitializers(_init, 5, this); - __publicField(this, "disposables", []); - __publicField(this, "gl"); - __publicField(this, "shapeGeometryCache"); - __publicField(this, "colors"); - __publicField(this, "id", uniqueId()); - __publicField(this, "canvasBoundingClientRect", atom("canvasBoundingClientRect", new Box())); - __publicField(this, "originPagePoint", new Vec()); - __publicField(this, "originPageCenter", new Vec()); - __publicField(this, "isInViewport", false); - this.gl = setupWebGl(elem); - this.shapeGeometryCache = editor.store.createComputedCache("webgl-geometry", (r) => { - const bounds = editor.getShapeMaskedPageBounds(r.id); - if (!bounds) return null; - const arr = new Float32Array(12); - rectangle(arr, 0, bounds.x, bounds.y, bounds.w, bounds.h); - return arr; - }); - this.colors = this._getColors(); - this.disposables.push(this._listenForCanvasResize(), react("minimap render", this.render)); - } - close() { - return this.disposables.forEach((d) => d()); - } - _getColors() { - const style = getComputedStyle(this.editor.getContainer()); - return { - shapeFill: getRgba(style.getPropertyValue("--color-text-3").trim()), - selectFill: getRgba(style.getPropertyValue("--color-selected").trim()), - viewportFill: getRgba(style.getPropertyValue("--color-muted-1").trim()), - background: getRgba(style.getPropertyValue("--color-low").trim()) - }; - } - // this should be called after dark/light mode changes have propagated to the dom - updateColors() { - this.colors = this._getColors(); - } - getDpr() { - return this.editor.getInstanceState().devicePixelRatio; - } - getContentPageBounds() { - const viewportPageBounds = this.editor.getViewportPageBounds(); - const commonShapeBounds = this.editor.getCurrentPageBounds(); - return commonShapeBounds ? Box.Expand(commonShapeBounds, viewportPageBounds) : viewportPageBounds; - } - getContentScreenBounds() { - const contentPageBounds = this.getContentPageBounds(); - const topLeft = this.editor.pageToScreen(contentPageBounds.point); - const bottomRight = this.editor.pageToScreen( - new Vec(contentPageBounds.maxX, contentPageBounds.maxY) - ); - return new Box(topLeft.x, topLeft.y, bottomRight.x - topLeft.x, bottomRight.y - topLeft.y); - } - _getCanvasBoundingRect() { - const { x, y, width, height } = this.elem.getBoundingClientRect(); - return new Box(x, y, width, height); - } - getCanvasScreenBounds() { - return this.canvasBoundingClientRect.get(); - } - _listenForCanvasResize() { - const observer = new ResizeObserver(() => { - const rect = this._getCanvasBoundingRect(); - this.canvasBoundingClientRect.set(rect); - }); - observer.observe(this.elem); - observer.observe(this.container); - return () => observer.disconnect(); - } - getCanvasSize() { - const rect = this.canvasBoundingClientRect.get(); - const dpr = this.getDpr(); - return new Vec(rect.width * dpr, rect.height * dpr); - } - getCanvasClientPosition() { - return this.canvasBoundingClientRect.get().point; - } - getCanvasPageBounds() { - const canvasScreenBounds = this.getCanvasScreenBounds(); - const contentPageBounds = this.getContentPageBounds(); - const aspectRatio = canvasScreenBounds.width / canvasScreenBounds.height; - let targetWidth = contentPageBounds.width; - let targetHeight = targetWidth / aspectRatio; - if (targetHeight < contentPageBounds.height) { - targetHeight = contentPageBounds.height; - targetWidth = targetHeight * aspectRatio; - } - const box = new Box(0, 0, targetWidth, targetHeight); - box.center = contentPageBounds.center; - return box; - } - getZoom() { - return this.getCanvasPageBounds().width / this.getCanvasScreenBounds().width; - } - getCanvasPageBoundsArray() { - const { x, y, w, h } = this.getCanvasPageBounds(); - return new Float32Array([x, y, w, h]); - } - getMinimapPagePoint(clientX, clientY) { - const canvasPageBounds = this.getCanvasPageBounds(); - const canvasScreenBounds = this.getCanvasScreenBounds(); - let x = clientX - canvasScreenBounds.x; - let y = clientY - canvasScreenBounds.y; - x *= canvasPageBounds.width / canvasScreenBounds.width; - y *= canvasPageBounds.height / canvasScreenBounds.height; - x += canvasPageBounds.minX; - y += canvasPageBounds.minY; - return new Vec(x, y, 1); - } - minimapScreenPointToPagePoint(x, y, shiftKey = false, clampToBounds = false) { - const { editor } = this; - const vpPageBounds = editor.getViewportPageBounds(); - let { x: px, y: py } = this.getMinimapPagePoint(x, y); - if (clampToBounds) { - const shapesPageBounds = this.editor.getCurrentPageBounds() ?? new Box(); - const minX = shapesPageBounds.minX - vpPageBounds.width / 2; - const maxX = shapesPageBounds.maxX + vpPageBounds.width / 2; - const minY = shapesPageBounds.minY - vpPageBounds.height / 2; - const maxY = shapesPageBounds.maxY + vpPageBounds.height / 2; - const lx = Math.max(0, minX + vpPageBounds.width - px); - const rx = Math.max(0, -(maxX - vpPageBounds.width - px)); - const ly = Math.max(0, minY + vpPageBounds.height - py); - const ry = Math.max(0, -(maxY - vpPageBounds.height - py)); - px += (lx - rx) / 2; - py += (ly - ry) / 2; - px = clamp$2(px, minX, maxX); - py = clamp$2(py, minY, maxY); - } - if (shiftKey) { - const { originPagePoint } = this; - const dx = Math.abs(px - originPagePoint.x); - const dy = Math.abs(py - originPagePoint.y); - if (dx > dy) { - py = originPagePoint.y; - } else { - px = originPagePoint.x; - } - } - return new Vec(px, py); - } - render() { - const context = this.gl.context; - const canvasSize = this.getCanvasSize(); - this.gl.setCanvasPageBounds(this.getCanvasPageBoundsArray()); - this.elem.width = canvasSize.x; - this.elem.height = canvasSize.y; - context.viewport(0, 0, canvasSize.x, canvasSize.y); - context.clearColor( - this.colors.background[0], - this.colors.background[1], - this.colors.background[2], - 1 - ); - context.clear(context.COLOR_BUFFER_BIT); - const selectedShapes = new Set(this.editor.getSelectedShapeIds()); - const colors = this.colors; - let selectedShapeOffset = 0; - let unselectedShapeOffset = 0; - const ids = this.editor.getCurrentPageShapeIdsSorted(); - for (let i = 0, len = ids.length; i < len; i++) { - const shapeId = ids[i]; - const geometry = this.shapeGeometryCache.get(shapeId); - if (!geometry) continue; - const len2 = geometry.length; - if (selectedShapes.has(shapeId)) { - appendVertices(this.gl.selectedShapes, selectedShapeOffset, geometry); - selectedShapeOffset += len2; - } else { - appendVertices(this.gl.unselectedShapes, unselectedShapeOffset, geometry); - unselectedShapeOffset += len2; - } - } - this.drawShapes(this.gl.unselectedShapes, unselectedShapeOffset, colors.shapeFill); - this.drawShapes(this.gl.selectedShapes, selectedShapeOffset, colors.selectFill); - this.drawViewport(); - this.drawCollaborators(); - } - drawShapes(stuff, len, color) { - this.gl.prepareTriangles(stuff, len); - this.gl.setFillColor(color); - this.gl.drawTriangles(len); - } - drawViewport() { - const viewport = this.editor.getViewportPageBounds(); - const len = roundedRectangle(this.gl.viewport.vertices, viewport, 4 * this.getZoom()); - this.gl.prepareTriangles(this.gl.viewport, len); - this.gl.setFillColor(this.colors.viewportFill); - this.gl.drawTrianglesTransparently(len); - if (tlenv.isSafari) { - this.gl.drawTrianglesTransparently(len); - this.gl.drawTrianglesTransparently(len); - this.gl.drawTrianglesTransparently(len); - } - } - drawCollaborators() { - const collaborators = this.editor.getCollaboratorsOnCurrentPage(); - if (!collaborators.length) return; - const numSegmentsPerCircle = 20; - const dataSizePerCircle = numSegmentsPerCircle * 6; - const totalSize = dataSizePerCircle * collaborators.length; - if (this.gl.collaborators.vertices.length < totalSize) { - this.gl.collaborators.vertices = new Float32Array(totalSize); - } - const vertices = this.gl.collaborators.vertices; - let offset = 0; - const zoom = this.getZoom(); - for (const { cursor } of collaborators) { - pie(vertices, { - center: Vec.From(cursor), - radius: 3 * zoom, - offset, - numArcSegments: numSegmentsPerCircle - }); - offset += dataSizePerCircle; - } - this.gl.prepareTriangles(this.gl.collaborators, totalSize); - offset = 0; - for (const { color } of collaborators) { - this.gl.setFillColor(getRgba(color)); - this.gl.context.drawArrays(this.gl.context.TRIANGLES, offset / 2, dataSizePerCircle / 2); - offset += dataSizePerCircle; - } - } -} -_init = __decoratorStart(); -__decorateElement(_init, 1, "close", _close_dec, MinimapManager); -__decorateElement(_init, 1, "getDpr", _getDpr_dec, MinimapManager); -__decorateElement(_init, 1, "getContentPageBounds", _getContentPageBounds_dec, MinimapManager); -__decorateElement(_init, 1, "getContentScreenBounds", _getContentScreenBounds_dec, MinimapManager); -__decorateElement(_init, 1, "getCanvasSize", _getCanvasSize_dec, MinimapManager); -__decorateElement(_init, 1, "getCanvasClientPosition", _getCanvasClientPosition_dec, MinimapManager); -__decorateElement(_init, 1, "getCanvasPageBounds", _getCanvasPageBounds_dec, MinimapManager); -__decorateElement(_init, 1, "getZoom", _getZoom_dec, MinimapManager); -__decorateElement(_init, 1, "getCanvasPageBoundsArray", _getCanvasPageBoundsArray_dec, MinimapManager); -__decorateElement(_init, 1, "render", _render_dec, MinimapManager); -__decoratorMetadata(_init, MinimapManager); - -function DefaultMinimap() { - const editor = useEditor(); - const container = useContainer(); - const rCanvas = reactExports.useRef(null); - const rPointing = reactExports.useRef(false); - const minimapRef = reactExports.useRef(); - reactExports.useEffect(() => { - try { - const minimap = new MinimapManager(editor, rCanvas.current, container); - minimapRef.current = minimap; - return minimapRef.current.close; - } catch (e) { - editor.annotateError(e, { - origin: "minimap", - willCrashApp: false - }); - editor.timers.setTimeout(() => { - throw e; - }); - } - }, [editor, container]); - const onDoubleClick = reactExports.useCallback( - (e) => { - if (!editor.getCurrentPageShapeIds().size) return; - if (!minimapRef.current) return; - const point = minimapRef.current.minimapScreenPointToPagePoint( - e.clientX, - e.clientY, - false, - false - ); - const clampedPoint = minimapRef.current.minimapScreenPointToPagePoint( - e.clientX, - e.clientY, - false, - true - ); - minimapRef.current.originPagePoint.setTo(clampedPoint); - minimapRef.current.originPageCenter.setTo(editor.getViewportPageBounds().center); - editor.centerOnPoint(point, { animation: { duration: editor.options.animationMediumMs } }); - }, - [editor] - ); - const onPointerDown = reactExports.useCallback( - (e) => { - if (!minimapRef.current) return; - const elm = e.currentTarget; - setPointerCapture(elm, e); - if (!editor.getCurrentPageShapeIds().size) return; - rPointing.current = true; - minimapRef.current.isInViewport = false; - const point = minimapRef.current.minimapScreenPointToPagePoint( - e.clientX, - e.clientY, - false, - false - ); - const _vpPageBounds = editor.getViewportPageBounds(); - const commonBounds = minimapRef.current.getContentPageBounds(); - const allowedBounds = new Box( - commonBounds.x - _vpPageBounds.width / 2, - commonBounds.y - _vpPageBounds.height / 2, - commonBounds.width + _vpPageBounds.width, - commonBounds.height + _vpPageBounds.height - ); - if (allowedBounds.containsPoint(point) && !_vpPageBounds.containsPoint(point)) { - minimapRef.current.isInViewport = _vpPageBounds.containsPoint(point); - const delta = Vec.Sub(_vpPageBounds.center, _vpPageBounds.point); - const pagePoint = Vec.Add(point, delta); - minimapRef.current.originPagePoint.setTo(pagePoint); - minimapRef.current.originPageCenter.setTo(point); - editor.centerOnPoint(point, { animation: { duration: editor.options.animationMediumMs } }); - } else { - const clampedPoint = minimapRef.current.minimapScreenPointToPagePoint( - e.clientX, - e.clientY, - false, - true - ); - minimapRef.current.isInViewport = _vpPageBounds.containsPoint(clampedPoint); - minimapRef.current.originPagePoint.setTo(clampedPoint); - minimapRef.current.originPageCenter.setTo(_vpPageBounds.center); - } - function release(e2) { - if (elm) { - releasePointerCapture(elm, e2); - } - rPointing.current = false; - document.body.removeEventListener("pointerup", release); - } - document.body.addEventListener("pointerup", release); - }, - [editor] - ); - const onPointerMove = reactExports.useCallback( - (e) => { - if (!minimapRef.current) return; - const point = minimapRef.current.minimapScreenPointToPagePoint( - e.clientX, - e.clientY, - e.shiftKey, - true - ); - if (rPointing.current) { - if (minimapRef.current.isInViewport) { - const delta = minimapRef.current.originPagePoint.clone().sub(minimapRef.current.originPageCenter); - editor.centerOnPoint(Vec.Sub(point, delta)); - return; - } - editor.centerOnPoint(point); - } - const pagePoint = minimapRef.current.getMinimapPagePoint(e.clientX, e.clientY); - const screenPoint = editor.pageToScreen(pagePoint); - const info = { - type: "pointer", - target: "canvas", - name: "pointer_move", - ...getPointerInfo(e), - point: screenPoint, - isPen: editor.getInstanceState().isPenMode - }; - editor.dispatch(info); - }, - [editor] - ); - const onWheel = reactExports.useCallback( - (e) => { - const offset = normalizeWheel(e); - editor.dispatch({ - type: "wheel", - name: "wheel", - delta: offset, - point: new Vec(e.clientX, e.clientY), - shiftKey: e.shiftKey, - altKey: e.altKey, - ctrlKey: e.metaKey || e.ctrlKey, - metaKey: e.metaKey, - accelKey: isAccelKey(e) - }); - }, - [editor] - ); - const isDarkMode = useIsDarkMode(); - reactExports.useEffect(() => { - editor.timers.setTimeout(() => { - minimapRef.current?.updateColors(); - minimapRef.current?.render(); - }); - }, [isDarkMode, editor]); - return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-minimap", children: /* @__PURE__ */ jsxRuntimeExports.jsx( - "canvas", - { - role: "img", - "aria-label": "minimap", - ref: rCanvas, - className: "tlui-minimap__canvas", - onDoubleClick, - onPointerMove, - onPointerDown, - onWheelCapture: onWheel - } - ) }); -} - -function useLocalStorageState(key, defaultValue) { - const [state, setState] = React.useState(defaultValue); - React.useLayoutEffect(() => { - const value = getFromLocalStorage(key); - if (value) { - try { - setState(JSON.parse(value)); - } catch { - console.error(`Could not restore value ${key} from local storage.`); - } - } - }, [key]); - const updateValue = React.useCallback( - (setter) => { - setState((s) => { - const value = typeof setter === "function" ? setter(s) : setter; - setInLocalStorage(key, JSON.stringify(value)); - return value; - }); - }, - [key] - ); - return [state, updateValue]; -} - -const DefaultNavigationPanel = reactExports.memo(function DefaultNavigationPanel2() { - const actions = useActions(); - const msg = useTranslation(); - const breakpoint = useBreakpoint(); - const ref = reactExports.useRef(null); - usePassThroughWheelEvents(ref); - const [collapsed, setCollapsed] = useLocalStorageState("minimap", true); - const toggleMinimap = reactExports.useCallback(() => { - setCollapsed((s) => !s); - }, [setCollapsed]); - const { ZoomMenu, Minimap } = useTldrawUiComponents(); - if (breakpoint < PORTRAIT_BREAKPOINT.MOBILE) { - return null; - } - return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref, className: "tlui-navigation-panel", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-buttons__horizontal", children: ZoomMenu && breakpoint < PORTRAIT_BREAKPOINT.TABLET ? /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomMenu, {}) : collapsed ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - ZoomMenu && /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomMenu, {}), - Minimap && /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - "data-testid": "minimap.toggle-button", - title: msg("navigation-zone.toggle-minimap"), - className: "tlui-navigation-panel__toggle", - onClick: toggleMinimap, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: collapsed ? "chevrons-ne" : "chevrons-sw" }) - } - ) - ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - "data-testid": "minimap.zoom-out", - title: `${msg(unwrapLabel(actions["zoom-out"].label))} ${kbdStr(actions["zoom-out"].kbd)}`, - onClick: () => actions["zoom-out"].onSelect("navigation-zone"), - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "minus" }) - } - ), - ZoomMenu && /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomMenu, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - "data-testid": "minimap.zoom-in", - title: `${msg(unwrapLabel(actions["zoom-in"].label))} ${kbdStr(actions["zoom-in"].kbd)}`, - onClick: () => actions["zoom-in"].onSelect("navigation-zone"), - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "plus" }) - } - ), - Minimap && /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - "data-testid": "minimap.toggle-button", - title: msg("navigation-zone.toggle-minimap"), - className: "tlui-navigation-panel__toggle", - onClick: toggleMinimap, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: collapsed ? "chevrons-ne" : "chevrons-sw" }) - } - ) - ] }) }), - Minimap && breakpoint >= PORTRAIT_BREAKPOINT.TABLET && !collapsed && /* @__PURE__ */ jsxRuntimeExports.jsx(Minimap, {}) - ] }); -}); - -const PageItemInput = function PageItemInput2({ - name, - id, - isCurrentPage, - onCancel -}) { - const editor = useEditor(); - const trackEvent = useUiEvents(); - const rInput = reactExports.useRef(null); - const rMark = reactExports.useRef(null); - const handleFocus = reactExports.useCallback(() => { - rMark.current = editor.markHistoryStoppingPoint("rename page"); - }, [editor]); - const handleChange = reactExports.useCallback( - (value) => { - editor.renamePage(id, value || "New Page"); - trackEvent("rename-page", { source: "page-menu" }); - }, - [editor, id, trackEvent] - ); - const handleCancel = reactExports.useCallback(() => { - if (rMark.current) { - editor.bailToMark(rMark.current); - } - onCancel(); - }, [editor, onCancel]); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiInput, - { - className: "tlui-page-menu__item__input", - ref: (el) => rInput.current = el, - defaultValue: name, - onValueChange: handleChange, - onCancel: handleCancel, - onFocus: handleFocus, - shouldManuallyMaintainScrollPositionWhenFocused: true, - autoFocus: isCurrentPage, - autoSelect: true - } - ); -}; - -const onMovePage = (editor, id, from, to, trackEvent) => { - let index; - const pages = editor.getPages(); - const below = from > to ? pages[to - 1] : pages[to]; - const above = from > to ? pages[to] : pages[to + 1]; - if (below && !above) { - index = getIndexAbove(below.index); - } else if (!below && above) { - index = getIndexBelow(pages[0].index); - } else { - index = getIndexBetween(below.index, above.index); - } - if (index !== pages[from].index) { - editor.markHistoryStoppingPoint("moving page"); - editor.updatePage({ - id, - index - }); - trackEvent("move-page", { source: "page-menu" }); - } -}; - -const PageItemSubmenu = track(function PageItemSubmenu2({ - index, - listSize, - item, - onRename -}) { - const editor = useEditor(); - const msg = useTranslation(); - const pages = editor.getPages(); - const trackEvent = useUiEvents(); - const onDuplicate = reactExports.useCallback(() => { - editor.markHistoryStoppingPoint("creating page"); - const newId = PageRecordType.createId(); - editor.duplicatePage(item.id, newId); - trackEvent("duplicate-page", { source: "page-menu" }); - }, [editor, item, trackEvent]); - const onMoveUp = reactExports.useCallback(() => { - onMovePage(editor, item.id, index, index - 1, trackEvent); - }, [editor, item, index, trackEvent]); - const onMoveDown = reactExports.useCallback(() => { - onMovePage(editor, item.id, index, index + 1, trackEvent); - }, [editor, item, index, trackEvent]); - const onDelete = reactExports.useCallback(() => { - editor.markHistoryStoppingPoint("deleting page"); - editor.deletePage(item.id); - trackEvent("delete-page", { source: "page-menu" }); - }, [editor, item, trackEvent]); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiDropdownMenuRoot, { id: `page item submenu ${index}`, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuTrigger, { children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButton, { type: "icon", title: msg("page-menu.submenu.title"), children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "dots-vertical", small: true }) }) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuContent, { alignOffset: 0, side: "right", sideOffset: -4, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuContextProvider, { type: "menu", sourceId: "page-menu", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiMenuGroup, { id: "modify", children: [ - onRename && /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuItem, { id: "rename", label: "page-menu.submenu.rename", onSelect: onRename }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuItem, - { - id: "duplicate", - label: "page-menu.submenu.duplicate-page", - onSelect: onDuplicate, - disabled: pages.length >= editor.options.maxPages - } - ), - index > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuItem, - { - id: "move-up", - onSelect: onMoveUp, - label: "page-menu.submenu.move-up" - } - ), - index < listSize - 1 && /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiMenuItem, - { - id: "move-down", - label: "page-menu.submenu.move-down", - onSelect: onMoveDown - } - ) - ] }), - listSize > 1 && /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuGroup, { id: "delete", children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuItem, { id: "delete", onSelect: onDelete, label: "page-menu.submenu.delete" }) }) - ] }) }) - ] }); -}); - -const DefaultPageMenu = reactExports.memo(function DefaultPageMenu2() { - const editor = useEditor(); - const trackEvent = useUiEvents(); - const msg = useTranslation(); - const breakpoint = useBreakpoint(); - const handleOpenChange = reactExports.useCallback(() => setIsEditing(false), []); - const [isOpen, onOpenChange] = useMenuIsOpen("page-menu", handleOpenChange); - const ITEM_HEIGHT = 36; - const rSortableContainer = reactExports.useRef(null); - const pages = useValue("pages", () => editor.getPages(), [editor]); - const currentPage = useValue("currentPage", () => editor.getCurrentPage(), [editor]); - const currentPageId = useValue("currentPageId", () => editor.getCurrentPageId(), [editor]); - const isReadonlyMode = useReadonly(); - const maxPageCountReached = useValue( - "maxPageCountReached", - () => editor.getPages().length >= editor.options.maxPages, - [editor] - ); - const isCoarsePointer = useValue( - "isCoarsePointer", - () => editor.getInstanceState().isCoarsePointer, - [editor] - ); - const [isEditing, setIsEditing] = reactExports.useState(false); - const toggleEditing = reactExports.useCallback(() => { - if (isReadonlyMode) return; - setIsEditing((s) => !s); - }, [isReadonlyMode]); - const rMutables = reactExports.useRef({ - isPointing: false, - status: "idle", - pointing: null, - startY: 0, - startIndex: 0, - dragIndex: 0 - }); - const [sortablePositionItems, setSortablePositionItems] = reactExports.useState( - Object.fromEntries( - pages.map((page, i) => [page.id, { y: i * ITEM_HEIGHT, offsetY: 0, isSelected: false }]) - ) - ); - reactExports.useLayoutEffect(() => { - setSortablePositionItems( - Object.fromEntries( - pages.map((page, i) => [page.id, { y: i * ITEM_HEIGHT, offsetY: 0, isSelected: false }]) - ) - ); - }, [ITEM_HEIGHT, pages]); - reactExports.useEffect(() => { - if (!isOpen) return; - editor.timers.requestAnimationFrame(() => { - const elm = document.querySelector( - `[data-testid="page-menu-item-${currentPageId}"]` - ); - if (elm) { - const container = rSortableContainer.current; - if (!container) return; - const elmTopPosition = elm.offsetTop; - const containerScrollTopPosition = container.scrollTop; - if (elmTopPosition < containerScrollTopPosition) { - container.scrollTo({ top: elmTopPosition }); - } - const elmBottomPosition = elmTopPosition + ITEM_HEIGHT; - const containerScrollBottomPosition = container.scrollTop + container.offsetHeight; - if (elmBottomPosition > containerScrollBottomPosition) { - container.scrollTo({ top: elmBottomPosition - container.offsetHeight }); - } - } - }); - }, [ITEM_HEIGHT, currentPageId, isOpen, editor]); - const handlePointerDown = reactExports.useCallback( - (e) => { - const { clientY, currentTarget } = e; - const { - dataset: { id, index } - } = currentTarget; - if (!id || !index) return; - const mut = rMutables.current; - setPointerCapture(e.currentTarget, e); - mut.status = "pointing"; - mut.pointing = { id, index: +index }; - const current = sortablePositionItems[id]; - const dragY = current.y; - mut.startY = clientY; - mut.startIndex = Math.max(0, Math.min(Math.round(dragY / ITEM_HEIGHT), pages.length - 1)); - }, - [ITEM_HEIGHT, pages.length, sortablePositionItems] - ); - const handlePointerMove = reactExports.useCallback( - (e) => { - const mut = rMutables.current; - if (mut.status === "pointing") { - const { clientY } = e; - const offset = clientY - mut.startY; - if (Math.abs(offset) > 5) { - mut.status = "dragging"; - } - } - if (mut.status === "dragging") { - const { clientY } = e; - const offsetY = clientY - mut.startY; - const current = sortablePositionItems[mut.pointing.id]; - const { startIndex, pointing } = mut; - const dragY = current.y + offsetY; - const dragIndex = Math.max(0, Math.min(Math.round(dragY / ITEM_HEIGHT), pages.length - 1)); - const next = { ...sortablePositionItems }; - next[pointing.id] = { - y: current.y, - offsetY, - isSelected: true - }; - if (dragIndex !== mut.dragIndex) { - mut.dragIndex = dragIndex; - for (let i = 0; i < pages.length; i++) { - const item = pages[i]; - if (item.id === mut.pointing.id) { - continue; - } - let { y } = next[item.id]; - if (dragIndex === startIndex) { - y = i * ITEM_HEIGHT; - } else if (dragIndex < startIndex) { - if (dragIndex <= i && i < startIndex) { - y = (i + 1) * ITEM_HEIGHT; - } else { - y = i * ITEM_HEIGHT; - } - } else if (dragIndex > startIndex) { - if (dragIndex >= i && i > startIndex) { - y = (i - 1) * ITEM_HEIGHT; - } else { - y = i * ITEM_HEIGHT; - } - } - if (y !== next[item.id].y) { - next[item.id] = { y, offsetY: 0, isSelected: true }; - } - } - } - setSortablePositionItems(next); - } - }, - [ITEM_HEIGHT, pages, sortablePositionItems] - ); - const handlePointerUp = reactExports.useCallback( - (e) => { - const mut = rMutables.current; - if (mut.status === "dragging") { - const { id, index } = mut.pointing; - onMovePage(editor, id, index, mut.dragIndex, trackEvent); - } - releasePointerCapture(e.currentTarget, e); - mut.status = "idle"; - }, - [editor, trackEvent] - ); - const handleKeyDown = reactExports.useCallback( - (e) => { - const mut = rMutables.current; - if (e.key === "Escape") { - if (mut.status === "dragging") { - setSortablePositionItems( - Object.fromEntries( - pages.map((page, i) => [ - page.id, - { y: i * ITEM_HEIGHT, offsetY: 0, isSelected: false } - ]) - ) - ); - } - mut.status = "idle"; - } - }, - [ITEM_HEIGHT, pages] - ); - const handleCreatePageClick = reactExports.useCallback(() => { - if (isReadonlyMode) return; - editor.run(() => { - editor.markHistoryStoppingPoint("creating page"); - const newPageId = PageRecordType.createId(); - editor.createPage({ name: msg("page-menu.new-page-initial-name"), id: newPageId }); - editor.setCurrentPage(newPageId); - setIsEditing(true); - }); - trackEvent("new-page", { source: "page-menu" }); - }, [editor, msg, isReadonlyMode, trackEvent]); - const changePage = reactExports.useCallback( - (id) => { - editor.setCurrentPage(id); - trackEvent("change-page", { source: "page-menu" }); - }, - [editor, trackEvent] - ); - const renamePage = reactExports.useCallback( - (id, name) => { - editor.renamePage(id, name); - trackEvent("rename-page", { source: "page-menu" }); - }, - [editor, trackEvent] - ); - return ( - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiPopover, { id: "pages", onOpenChange, open: isOpen, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiPopoverTrigger, { "data-testid": "main.page-menu", children: /* @__PURE__ */ jsxRuntimeExports.jsxs( - TldrawUiButton, - { - type: "menu", - title: currentPage.name, - "data-testid": "page-menu.button", - className: "tlui-page-menu__trigger", - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-page-menu__name", children: currentPage.name }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "chevron-down", small: true }) - ] - } - ) }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiPopoverContent, - { - side: "bottom", - align: "start", - sideOffset: 6, - disableEscapeKeyDown: isEditing, - children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-page-menu__wrapper", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-page-menu__header", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-page-menu__header__title", children: msg("page-menu.title") }), - !isReadonlyMode && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-buttons__horizontal", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - "data-testid": "page-menu.edit", - title: msg(isEditing ? "page-menu.edit-done" : "page-menu.edit-start"), - onClick: toggleEditing, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: isEditing ? "check" : "edit" }) - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - "data-testid": "page-menu.create", - title: msg( - maxPageCountReached ? "page-menu.max-page-count-reached" : "page-menu.create-new-page" - ), - disabled: maxPageCountReached, - onClick: handleCreatePageClick, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "plus" }) - } - ) - ] }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - "div", - { - "data-testid": "page-menu.list", - className: "tlui-page-menu__list tlui-menu__group", - style: { height: ITEM_HEIGHT * pages.length + 4 }, - ref: rSortableContainer, - children: pages.map((page, index) => { - const position = sortablePositionItems[page.id] ?? { - offsetY: 0 - }; - return isEditing ? /* @__PURE__ */ jsxRuntimeExports.jsxs( - "div", - { - "data-testid": "page-menu.item", - className: "tlui-page_menu__item__sortable", - style: { - zIndex: page.id === currentPage.id ? 888 : index, - transform: `translate(0px, ${position.y + position.offsetY}px)` - }, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - tabIndex: -1, - className: "tlui-page_menu__item__sortable__handle", - onPointerDown: handlePointerDown, - onPointerUp: handlePointerUp, - onPointerMove: handlePointerMove, - onKeyDown: handleKeyDown, - "data-id": page.id, - "data-index": index, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "drag-handle-dots" }) - } - ), - breakpoint < PORTRAIT_BREAKPOINT.TABLET_SM && isCoarsePointer ? ( - // sigh, this is a workaround for iOS Safari - // because the device and the radix popover seem - // to be fighting over scroll position. Nothing - // else seems to work! - /* @__PURE__ */ (jsxRuntimeExports.jsxs(TldrawUiButton, { - type: "normal", - className: "tlui-page-menu__item__button", - onClick: () => { - const name = window.prompt("Rename page", page.name); - if (name && name !== page.name) { - renamePage(page.id, name); - } - }, - onDoubleClick: toggleEditing, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonCheck, { checked: page.id === currentPage.id }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonLabel, { children: page.name }) - ] - })) - ) : /* @__PURE__ */ jsxRuntimeExports.jsx( - "div", - { - className: "tlui-page_menu__item__sortable__title", - style: { height: ITEM_HEIGHT }, - children: /* @__PURE__ */ jsxRuntimeExports.jsx( - PageItemInput, - { - id: page.id, - name: page.name, - isCurrentPage: page.id === currentPage.id, - onCancel: () => { - setIsEditing(false); - editor.menus.clearOpenMenus(); - } - } - ) - } - ), - !isReadonlyMode && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-page_menu__item__submenu", "data-isediting": isEditing, children: /* @__PURE__ */ jsxRuntimeExports.jsx(PageItemSubmenu, { index, item: page, listSize: pages.length }) }) - ] - }, - page.id + "_editing" - ) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { "data-testid": "page-menu.item", className: "tlui-page-menu__item", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs( - TldrawUiButton, - { - type: "normal", - className: "tlui-page-menu__item__button", - onClick: () => changePage(page.id), - onDoubleClick: toggleEditing, - title: msg("page-menu.go-to-page"), - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonCheck, { checked: page.id === currentPage.id }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonLabel, { children: page.name }) - ] - } - ), - !isReadonlyMode && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-page_menu__item__submenu", children: /* @__PURE__ */ jsxRuntimeExports.jsx( - PageItemSubmenu, - { - index, - item: page, - listSize: pages.length, - onRename: () => { - if (tlenv.isIos) { - const name = window.prompt("Rename page", page.name); - if (name && name !== page.name) { - renamePage(page.id, name); - } - } else { - setIsEditing(true); - if (currentPageId !== page.id) { - changePage(page.id); - } - } - } - } - ) }) - ] }, page.id); - }) - } - ) - ] }) - } - ) - ] }) - ); -}); - -function DefaultQuickActionsContent() { - const editor = useEditor(); - const canUndo = useCanUndo(); - const canRedo = useCanRedo(); - const oneSelected = useUnlockedSelectedShapesCount(1); - const isReadonlyMode = useReadonly(); - const isInAcceptableReadonlyState = useValue( - "should display quick actions", - () => editor.isInAny("select", "hand", "zoom"), - [editor] - ); - const isInSelectState = useIsInSelectState(); - const selectDependentActionsEnabled = oneSelected && isInSelectState; - if (isReadonlyMode && !isInAcceptableReadonlyState) return; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "undo", disabled: !canUndo }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "redo", disabled: !canRedo }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "delete", disabled: !selectDependentActionsEnabled }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuActionItem, { actionId: "duplicate", disabled: !selectDependentActionsEnabled }) - ] }); -} - -const DefaultQuickActions = reactExports.memo(function DefaultQuickActions2({ - children -}) { - const content = children ?? /* @__PURE__ */ jsxRuntimeExports.jsx(DefaultQuickActionsContent, {}); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuContextProvider, { type: "small-icons", sourceId: "quick-actions", children: content }); -}); - -function PeopleMenuAvatar({ userId }) { - const presence = usePresence$1(userId); - if (!presence) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "div", - { - className: "tlui-people-menu__avatar", - style: { - backgroundColor: presence.color - }, - children: presence.userName === "New User" ? "" : presence.userName[0] ?? "" - }, - userId - ); -} - -const PeopleMenuItem = track(function PeopleMenuItem2({ userId }) { - const editor = useEditor(); - const msg = useTranslation(); - const trackEvent = useUiEvents(); - const presence = usePresence$1(userId); - const handleFollowClick = reactExports.useCallback(() => { - if (editor.getInstanceState().followingUserId === userId) { - editor.stopFollowingUser(); - trackEvent("stop-following", { source: "people-menu" }); - } else { - editor.startFollowingUser(userId); - trackEvent("start-following", { source: "people-menu" }); - } - }, [editor, userId, trackEvent]); - const theyAreFollowingYou = presence?.followingUserId === editor.user.getId(); - const youAreFollowingThem = editor.getInstanceState().followingUserId === userId; - if (!presence) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-people-menu__item tlui-buttons__horizontal", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs( - TldrawUiButton, - { - type: "menu", - className: "tlui-people-menu__item__button", - onClick: () => editor.zoomToUser(userId), - onDoubleClick: handleFollowClick, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiIcon, { icon: "color", color: presence.color }), - /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-people-menu__name", children: presence.userName ?? "New User" }) - ] - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - className: "tlui-people-menu__item__follow", - title: theyAreFollowingYou ? msg("people-menu.leading") : youAreFollowingThem ? msg("people-menu.following") : msg("people-menu.follow"), - onClick: handleFollowClick, - disabled: theyAreFollowingYou, - "data-active": youAreFollowingThem || theyAreFollowingYou, - children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButtonIcon, - { - icon: theyAreFollowingYou ? "leading" : youAreFollowingThem ? "following" : "follow" - } - ) - } - ) - ] }); -}); - -function PeopleMenuMore({ count }) { - return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-people-menu__more", children: "+" + Math.abs(count) }); -} - -const UserPresenceColorPicker = track(function UserPresenceColorPicker2() { - const editor = useEditor(); - const container = useContainer(); - const msg = useTranslation(); - const trackEvent = useUiEvents(); - const rPointing = reactExports.useRef(false); - const [isOpen, setIsOpen] = reactExports.useState(false); - const handleOpenChange = reactExports.useCallback((isOpen2) => { - setIsOpen(isOpen2); - }, []); - const value = editor.user.getColor(); - const onValueChange = reactExports.useCallback( - (item) => { - editor.user.updateUserPreferences({ color: item }); - trackEvent("set-color", { source: "people-menu" }); - }, - [editor, trackEvent] - ); - const { - handleButtonClick, - handleButtonPointerDown, - handleButtonPointerEnter, - handleButtonPointerUp - } = React.useMemo(() => { - const handlePointerUp = () => { - rPointing.current = false; - window.removeEventListener("pointerup", handlePointerUp); - }; - const handleButtonClick2 = (e) => { - const { id } = e.currentTarget.dataset; - if (!id) return; - if (value === id) return; - onValueChange(id); - }; - const handleButtonPointerDown2 = (e) => { - const { id } = e.currentTarget.dataset; - if (!id) return; - onValueChange(id); - rPointing.current = true; - window.addEventListener("pointerup", handlePointerUp); - }; - const handleButtonPointerEnter2 = (e) => { - if (!rPointing.current) return; - const { id } = e.currentTarget.dataset; - if (!id) return; - onValueChange(id); - }; - const handleButtonPointerUp2 = (e) => { - const { id } = e.currentTarget.dataset; - if (!id) return; - onValueChange(id); - }; - return { - handleButtonClick: handleButtonClick2, - handleButtonPointerDown: handleButtonPointerDown2, - handleButtonPointerEnter: handleButtonPointerEnter2, - handleButtonPointerUp: handleButtonPointerUp2 - }; - }, [value, onValueChange]); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(Root2, { onOpenChange: handleOpenChange, open: isOpen, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger, { dir: "ltr", asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - className: "tlui-people-menu__user__color", - style: { color: editor.user.getColor() }, - title: msg("people-menu.change-color"), - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "color" }) - } - ) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(Portal, { container, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - Content2, - { - dir: "ltr", - className: "tlui-menu tlui-people-menu__user__color-picker", - align: "start", - side: "left", - sideOffset: 8, - children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-buttons__grid", children: USER_COLORS.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - "data-id": item, - "data-testid": item, - "aria-label": item, - "data-state": value === item ? "hinted" : void 0, - title: item, - className: "tlui-button-grid__button", - style: { color: item }, - onPointerEnter: handleButtonPointerEnter, - onPointerDown: handleButtonPointerDown, - onPointerUp: handleButtonPointerUp, - onClick: handleButtonClick, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "color" }) - }, - item - )) }) - } - ) }) - ] }); -}); - -function UserPresenceEditor() { - const editor = useEditor(); - const trackEvent = useUiEvents(); - const userName = useValue("userName", () => editor.user.getName(), []); - const msg = useTranslation(); - const rOriginalName = reactExports.useRef(userName); - const rCurrentName = reactExports.useRef(userName); - const [isEditingName, setIsEditingName] = reactExports.useState(false); - const toggleEditingName = reactExports.useCallback(() => { - setIsEditingName((s) => !s); - }, []); - const handleValueChange = reactExports.useCallback( - (value) => { - rCurrentName.current = value; - editor.user.updateUserPreferences({ name: value }); - }, - [editor] - ); - const handleBlur = reactExports.useCallback(() => { - if (rOriginalName.current === rCurrentName.current) return; - trackEvent("change-user-name", { source: "people-menu" }); - rOriginalName.current = rCurrentName.current; - }, [trackEvent]); - const handleCancel = reactExports.useCallback(() => { - setIsEditingName(false); - editor.user.updateUserPreferences({ name: rOriginalName.current }); - editor.menus.clearOpenMenus(); - }, [editor]); - return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-people-menu__user", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(UserPresenceColorPicker, {}), - isEditingName ? /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiInput, - { - className: "tlui-people-menu__user__input", - defaultValue: userName, - onValueChange: handleValueChange, - onComplete: toggleEditingName, - onCancel: handleCancel, - onBlur: handleBlur, - shouldManuallyMaintainScrollPositionWhenFocused: true, - autoFocus: true, - autoSelect: true - } - ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - "div", - { - className: "tlui-people-menu__user__name", - onDoubleClick: () => { - if (!isEditingName) setIsEditingName(true); - }, - children: userName - } - ), - userName === "New User" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-people-menu__user__label", children: msg("people-menu.user") }) : null - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - className: "tlui-people-menu__user__edit", - "data-testid": "people-menu.change-name", - title: msg("people-menu.change-name"), - onClick: toggleEditingName, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: isEditingName ? "check" : "edit" }) - } - ) - ] }); -} - -function PeopleMenu({ displayUserWhenAlone, children }) { - const msg = useTranslation(); - const container = useContainer(); - const editor = useEditor(); - const userIds = usePeerIds(); - const userColor = useValue("user", () => editor.user.getColor(), [editor]); - const userName = useValue("user", () => editor.user.getName(), [editor]); - const [isOpen, onOpenChange] = useMenuIsOpen("people menu"); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(Root2, { onOpenChange, open: isOpen, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(Trigger, { dir: "ltr", asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "tlui-people-menu__avatars-button", title: msg("people-menu.title"), children: [ - userIds.length > 5 && /* @__PURE__ */ jsxRuntimeExports.jsx(PeopleMenuMore, { count: userIds.length - 5 }), - /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-people-menu__avatars", children: [ - userIds.slice(-5).map((userId) => /* @__PURE__ */ jsxRuntimeExports.jsx(PeopleMenuAvatar, { userId }, userId)), - (displayUserWhenAlone || userIds.length > 0) && /* @__PURE__ */ jsxRuntimeExports.jsx( - "div", - { - className: "tlui-people-menu__avatar", - style: { - backgroundColor: userColor - }, - children: userName === "New User" ? "" : userName[0] ?? "" - } - ) - ] }) - ] }) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(Portal, { container, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - Content2, - { - dir: "ltr", - className: "tlui-menu", - side: "bottom", - sideOffset: 2, - collisionPadding: 4, - onEscapeKeyDown: preventDefault, - children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-people-menu__wrapper", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-people-menu__section", children: /* @__PURE__ */ jsxRuntimeExports.jsx(UserPresenceEditor, {}) }), - userIds.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-people-menu__section", children: userIds.map((userId) => { - return /* @__PURE__ */ jsxRuntimeExports.jsx(PeopleMenuItem, { userId }, userId + "_presence"); - }) }), - children - ] }) - } - ) }) - ] }); -} - -function DefaultSharePanel() { - return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-share-zone", draggable: false, children: /* @__PURE__ */ jsxRuntimeExports.jsx(PeopleMenu, { displayUserWhenAlone: true }) }); -} - -const selectToolStyles = Object.freeze([ - DefaultColorStyle, - DefaultDashStyle, - DefaultFillStyle, - DefaultSizeStyle -]); -function useRelevantStyles(stylesToCheck = selectToolStyles) { - const editor = useEditor(); - return useValue( - "getRelevantStyles", - () => { - const styles = new SharedStyleMap(editor.getSharedStyles()); - const isInShapeSpecificTool = !!editor.root.getCurrent()?.shapeType; - const hasShapesSelected = editor.isIn("select") && editor.getSelectedShapeIds().length > 0; - if (styles.size === 0 && editor.isIn("select") && editor.getSelectedShapeIds().length === 0) { - for (const style of stylesToCheck) { - styles.applyValue(style, editor.getStyleForNextShape(style)); - } - } - if (isInShapeSpecificTool || hasShapesSelected || styles.size > 0) { - return styles; - } - return null; - }, - [editor] - ); -} - -const STYLES = { - color: [ - { value: "black", icon: "color" }, - { value: "grey", icon: "color" }, - { value: "light-violet", icon: "color" }, - { value: "violet", icon: "color" }, - { value: "blue", icon: "color" }, - { value: "light-blue", icon: "color" }, - { value: "yellow", icon: "color" }, - { value: "orange", icon: "color" }, - { value: "green", icon: "color" }, - { value: "light-green", icon: "color" }, - { value: "light-red", icon: "color" }, - { value: "red", icon: "color" } - ], - fill: [ - { value: "none", icon: "fill-none" }, - { value: "semi", icon: "fill-semi" }, - { value: "solid", icon: "fill-solid" }, - { value: "pattern", icon: "fill-pattern" } - // { value: 'fill', icon: 'fill-fill' }, - ], - dash: [ - { value: "draw", icon: "dash-draw" }, - { value: "dashed", icon: "dash-dashed" }, - { value: "dotted", icon: "dash-dotted" }, - { value: "solid", icon: "dash-solid" } - ], - size: [ - { value: "s", icon: "size-small" }, - { value: "m", icon: "size-medium" }, - { value: "l", icon: "size-large" }, - { value: "xl", icon: "size-extra-large" } - ], - font: [ - { value: "draw", icon: "font-draw" }, - { value: "sans", icon: "font-sans" }, - { value: "serif", icon: "font-serif" }, - { value: "mono", icon: "font-mono" } - ], - textAlign: [ - { value: "start", icon: "text-align-left" }, - { value: "middle", icon: "text-align-center" }, - { value: "end", icon: "text-align-right" } - ], - horizontalAlign: [ - { value: "start", icon: "horizontal-align-start" }, - { value: "middle", icon: "horizontal-align-middle" }, - { value: "end", icon: "horizontal-align-end" } - ], - verticalAlign: [ - { value: "start", icon: "vertical-align-start" }, - { value: "middle", icon: "vertical-align-middle" }, - { value: "end", icon: "vertical-align-end" } - ], - geo: [ - { value: "rectangle", icon: "geo-rectangle" }, - { value: "ellipse", icon: "geo-ellipse" }, - { value: "triangle", icon: "geo-triangle" }, - { value: "diamond", icon: "geo-diamond" }, - { value: "star", icon: "geo-star" }, - { value: "pentagon", icon: "geo-pentagon" }, - { value: "hexagon", icon: "geo-hexagon" }, - { value: "octagon", icon: "geo-octagon" }, - { value: "rhombus", icon: "geo-rhombus" }, - { value: "rhombus-2", icon: "geo-rhombus-2" }, - { value: "oval", icon: "geo-oval" }, - { value: "trapezoid", icon: "geo-trapezoid" }, - { value: "arrow-left", icon: "geo-arrow-left" }, - { value: "arrow-up", icon: "geo-arrow-up" }, - { value: "arrow-down", icon: "geo-arrow-down" }, - { value: "arrow-right", icon: "geo-arrow-right" }, - { value: "cloud", icon: "geo-cloud" }, - { value: "x-box", icon: "geo-x-box" }, - { value: "check-box", icon: "geo-check-box" }, - { value: "heart", icon: "geo-heart" } - ], - arrowheadStart: [ - { value: "none", icon: "arrowhead-none" }, - { value: "arrow", icon: "arrowhead-arrow" }, - { value: "triangle", icon: "arrowhead-triangle" }, - { value: "square", icon: "arrowhead-square" }, - { value: "dot", icon: "arrowhead-dot" }, - { value: "diamond", icon: "arrowhead-diamond" }, - { value: "inverted", icon: "arrowhead-triangle-inverted" }, - { value: "bar", icon: "arrowhead-bar" } - ], - arrowheadEnd: [ - { value: "none", icon: "arrowhead-none" }, - { value: "arrow", icon: "arrowhead-arrow" }, - { value: "triangle", icon: "arrowhead-triangle" }, - { value: "square", icon: "arrowhead-square" }, - { value: "dot", icon: "arrowhead-dot" }, - { value: "diamond", icon: "arrowhead-diamond" }, - { value: "inverted", icon: "arrowhead-triangle-inverted" }, - { value: "bar", icon: "arrowhead-bar" } - ], - spline: [ - { value: "line", icon: "spline-line" }, - { value: "cubic", icon: "spline-cubic" } - ] -}; - -const TldrawUiButtonPicker = reactExports.memo(function TldrawUiButtonPicker2(props) { - const { - uiType, - items, - title, - style, - value, - // columns = clamp(items.length, 2, 4), - onValueChange, - onHistoryMark, - theme - } = props; - const msg = useTranslation(); - const rPointing = reactExports.useRef(false); - const rPointingOriginalActiveElement = reactExports.useRef(null); - const { - handleButtonClick, - handleButtonPointerDown, - handleButtonPointerEnter, - handleButtonPointerUp - } = reactExports.useMemo(() => { - const handlePointerUp = () => { - rPointing.current = false; - window.removeEventListener("pointerup", handlePointerUp); - const origActiveEl = rPointingOriginalActiveElement.current; - if (origActiveEl && ["TEXTAREA", "INPUT"].includes(origActiveEl.nodeName)) { - origActiveEl.focus(); - } - rPointingOriginalActiveElement.current = null; - }; - const handleButtonClick2 = (e) => { - const { id } = e.currentTarget.dataset; - if (value.type === "shared" && value.value === id) return; - onHistoryMark?.("point picker item"); - onValueChange(style, id); - }; - const handleButtonPointerDown2 = (e) => { - const { id } = e.currentTarget.dataset; - onHistoryMark?.("point picker item"); - onValueChange(style, id); - rPointing.current = true; - rPointingOriginalActiveElement.current = document.activeElement; - window.addEventListener("pointerup", handlePointerUp); - }; - const handleButtonPointerEnter2 = (e) => { - if (!rPointing.current) return; - const { id } = e.currentTarget.dataset; - onValueChange(style, id); - }; - const handleButtonPointerUp2 = (e) => { - const { id } = e.currentTarget.dataset; - if (value.type === "shared" && value.value === id) return; - onValueChange(style, id); - }; - return { - handleButtonClick: handleButtonClick2, - handleButtonPointerDown: handleButtonPointerDown2, - handleButtonPointerEnter: handleButtonPointerEnter2, - handleButtonPointerUp: handleButtonPointerUp2 - }; - }, [value, onHistoryMark, onValueChange, style]); - return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { "data-testid": `style.${uiType}`, className: classNames("tlui-buttons__grid"), children: items.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - "data-id": item.value, - "data-testid": `style.${uiType}.${item.value}`, - "aria-label": item.value, - "data-state": value.type === "shared" && value.value === item.value ? "hinted" : void 0, - title: title + " \u2014 " + msg(`${uiType}-style.${item.value}`), - className: classNames("tlui-button-grid__button"), - style: style === DefaultColorStyle ? { color: theme[item.value].solid } : void 0, - onPointerEnter: handleButtonPointerEnter, - onPointerDown: handleButtonPointerDown, - onPointerUp: handleButtonPointerUp, - onClick: handleButtonClick, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: item.icon }) - }, - item.value - )) }); -}); - -// packages/core/number/src/number.ts -function clamp(value, [min, max]) { - return Math.min(max, Math.max(min, value)); -} - -// packages/react/use-previous/src/use-previous.tsx -function usePrevious(value) { - const ref = reactExports.useRef({ value, previous: value }); - return reactExports.useMemo(() => { - if (ref.current.value !== value) { - ref.current.previous = ref.current.value; - ref.current.value = value; - } - return ref.current.previous; - }, [value]); -} - -var PAGE_KEYS = ["PageUp", "PageDown"]; -var ARROW_KEYS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"]; -var BACK_KEYS = { - "from-left": ["Home", "PageDown", "ArrowDown", "ArrowLeft"], - "from-right": ["Home", "PageDown", "ArrowDown", "ArrowRight"], - "from-bottom": ["Home", "PageDown", "ArrowDown", "ArrowLeft"], - "from-top": ["Home", "PageDown", "ArrowUp", "ArrowLeft"] -}; -var SLIDER_NAME = "Slider"; -var [Collection, useCollection, createCollectionScope] = createCollection(SLIDER_NAME); -var [createSliderContext, createSliderScope] = createContextScope(SLIDER_NAME, [ - createCollectionScope -]); -var [SliderProvider, useSliderContext] = createSliderContext(SLIDER_NAME); -var Slider = reactExports.forwardRef( - (props, forwardedRef) => { - const { - name, - min = 0, - max = 100, - step = 1, - orientation = "horizontal", - disabled = false, - minStepsBetweenThumbs = 0, - defaultValue = [min], - value, - onValueChange = () => { - }, - onValueCommit = () => { - }, - inverted = false, - form, - ...sliderProps - } = props; - const thumbRefs = reactExports.useRef(/* @__PURE__ */ new Set()); - const valueIndexToChangeRef = reactExports.useRef(0); - const isHorizontal = orientation === "horizontal"; - const SliderOrientation = isHorizontal ? SliderHorizontal : SliderVertical; - const [values = [], setValues] = useControllableState({ - prop: value, - defaultProp: defaultValue, - onChange: (value2) => { - const thumbs = [...thumbRefs.current]; - thumbs[valueIndexToChangeRef.current]?.focus(); - onValueChange(value2); - } - }); - const valuesBeforeSlideStartRef = reactExports.useRef(values); - function handleSlideStart(value2) { - const closestIndex = getClosestValueIndex(values, value2); - updateValues(value2, closestIndex); - } - function handleSlideMove(value2) { - updateValues(value2, valueIndexToChangeRef.current); - } - function handleSlideEnd() { - const prevValue = valuesBeforeSlideStartRef.current[valueIndexToChangeRef.current]; - const nextValue = values[valueIndexToChangeRef.current]; - const hasChanged = nextValue !== prevValue; - if (hasChanged) onValueCommit(values); - } - function updateValues(value2, atIndex, { commit } = { commit: false }) { - const decimalCount = getDecimalCount(step); - const snapToStep = roundValue(Math.round((value2 - min) / step) * step + min, decimalCount); - const nextValue = clamp(snapToStep, [min, max]); - setValues((prevValues = []) => { - const nextValues = getNextSortedValues(prevValues, nextValue, atIndex); - if (hasMinStepsBetweenValues(nextValues, minStepsBetweenThumbs * step)) { - valueIndexToChangeRef.current = nextValues.indexOf(nextValue); - const hasChanged = String(nextValues) !== String(prevValues); - if (hasChanged && commit) onValueCommit(nextValues); - return hasChanged ? nextValues : prevValues; - } else { - return prevValues; - } - }); - } - return /* @__PURE__ */ jsxRuntimeExports.jsx( - SliderProvider, - { - scope: props.__scopeSlider, - name, - disabled, - min, - max, - valueIndexToChangeRef, - thumbs: thumbRefs.current, - values, - orientation, - form, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(Collection.Provider, { scope: props.__scopeSlider, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Collection.Slot, { scope: props.__scopeSlider, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - SliderOrientation, - { - "aria-disabled": disabled, - "data-disabled": disabled ? "" : void 0, - ...sliderProps, - ref: forwardedRef, - onPointerDown: composeEventHandlers(sliderProps.onPointerDown, () => { - if (!disabled) valuesBeforeSlideStartRef.current = values; - }), - min, - max, - inverted, - onSlideStart: disabled ? void 0 : handleSlideStart, - onSlideMove: disabled ? void 0 : handleSlideMove, - onSlideEnd: disabled ? void 0 : handleSlideEnd, - onHomeKeyDown: () => !disabled && updateValues(min, 0, { commit: true }), - onEndKeyDown: () => !disabled && updateValues(max, values.length - 1, { commit: true }), - onStepKeyDown: ({ event, direction: stepDirection }) => { - if (!disabled) { - const isPageKey = PAGE_KEYS.includes(event.key); - const isSkipKey = isPageKey || event.shiftKey && ARROW_KEYS.includes(event.key); - const multiplier = isSkipKey ? 10 : 1; - const atIndex = valueIndexToChangeRef.current; - const value2 = values[atIndex]; - const stepInDirection = step * multiplier * stepDirection; - updateValues(value2 + stepInDirection, atIndex, { commit: true }); - } - } - } - ) }) }) - } - ); - } -); -Slider.displayName = SLIDER_NAME; -var [SliderOrientationProvider, useSliderOrientationContext] = createSliderContext(SLIDER_NAME, { - startEdge: "left", - endEdge: "right", - size: "width", - direction: 1 -}); -var SliderHorizontal = reactExports.forwardRef( - (props, forwardedRef) => { - const { - min, - max, - dir, - inverted, - onSlideStart, - onSlideMove, - onSlideEnd, - onStepKeyDown, - ...sliderProps - } = props; - const [slider, setSlider] = reactExports.useState(null); - const composedRefs = useComposedRefs(forwardedRef, (node) => setSlider(node)); - const rectRef = reactExports.useRef(void 0); - const direction = useDirection(dir); - const isDirectionLTR = direction === "ltr"; - const isSlidingFromLeft = isDirectionLTR && !inverted || !isDirectionLTR && inverted; - function getValueFromPointer(pointerPosition) { - const rect = rectRef.current || slider.getBoundingClientRect(); - const input = [0, rect.width]; - const output = isSlidingFromLeft ? [min, max] : [max, min]; - const value = linearScale(input, output); - rectRef.current = rect; - return value(pointerPosition - rect.left); - } - return /* @__PURE__ */ jsxRuntimeExports.jsx( - SliderOrientationProvider, - { - scope: props.__scopeSlider, - startEdge: isSlidingFromLeft ? "left" : "right", - endEdge: isSlidingFromLeft ? "right" : "left", - direction: isSlidingFromLeft ? 1 : -1, - size: "width", - children: /* @__PURE__ */ jsxRuntimeExports.jsx( - SliderImpl, - { - dir: direction, - "data-orientation": "horizontal", - ...sliderProps, - ref: composedRefs, - style: { - ...sliderProps.style, - ["--radix-slider-thumb-transform"]: "translateX(-50%)" - }, - onSlideStart: (event) => { - const value = getValueFromPointer(event.clientX); - onSlideStart?.(value); - }, - onSlideMove: (event) => { - const value = getValueFromPointer(event.clientX); - onSlideMove?.(value); - }, - onSlideEnd: () => { - rectRef.current = void 0; - onSlideEnd?.(); - }, - onStepKeyDown: (event) => { - const slideDirection = isSlidingFromLeft ? "from-left" : "from-right"; - const isBackKey = BACK_KEYS[slideDirection].includes(event.key); - onStepKeyDown?.({ event, direction: isBackKey ? -1 : 1 }); - } - } - ) - } - ); - } -); -var SliderVertical = reactExports.forwardRef( - (props, forwardedRef) => { - const { - min, - max, - inverted, - onSlideStart, - onSlideMove, - onSlideEnd, - onStepKeyDown, - ...sliderProps - } = props; - const sliderRef = reactExports.useRef(null); - const ref = useComposedRefs(forwardedRef, sliderRef); - const rectRef = reactExports.useRef(void 0); - const isSlidingFromBottom = !inverted; - function getValueFromPointer(pointerPosition) { - const rect = rectRef.current || sliderRef.current.getBoundingClientRect(); - const input = [0, rect.height]; - const output = isSlidingFromBottom ? [max, min] : [min, max]; - const value = linearScale(input, output); - rectRef.current = rect; - return value(pointerPosition - rect.top); - } - return /* @__PURE__ */ jsxRuntimeExports.jsx( - SliderOrientationProvider, - { - scope: props.__scopeSlider, - startEdge: isSlidingFromBottom ? "bottom" : "top", - endEdge: isSlidingFromBottom ? "top" : "bottom", - size: "height", - direction: isSlidingFromBottom ? 1 : -1, - children: /* @__PURE__ */ jsxRuntimeExports.jsx( - SliderImpl, - { - "data-orientation": "vertical", - ...sliderProps, - ref, - style: { - ...sliderProps.style, - ["--radix-slider-thumb-transform"]: "translateY(50%)" - }, - onSlideStart: (event) => { - const value = getValueFromPointer(event.clientY); - onSlideStart?.(value); - }, - onSlideMove: (event) => { - const value = getValueFromPointer(event.clientY); - onSlideMove?.(value); - }, - onSlideEnd: () => { - rectRef.current = void 0; - onSlideEnd?.(); - }, - onStepKeyDown: (event) => { - const slideDirection = isSlidingFromBottom ? "from-bottom" : "from-top"; - const isBackKey = BACK_KEYS[slideDirection].includes(event.key); - onStepKeyDown?.({ event, direction: isBackKey ? -1 : 1 }); - } - } - ) - } - ); - } -); -var SliderImpl = reactExports.forwardRef( - (props, forwardedRef) => { - const { - __scopeSlider, - onSlideStart, - onSlideMove, - onSlideEnd, - onHomeKeyDown, - onEndKeyDown, - onStepKeyDown, - ...sliderProps - } = props; - const context = useSliderContext(SLIDER_NAME, __scopeSlider); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - Primitive.span, - { - ...sliderProps, - ref: forwardedRef, - onKeyDown: composeEventHandlers(props.onKeyDown, (event) => { - if (event.key === "Home") { - onHomeKeyDown(event); - event.preventDefault(); - } else if (event.key === "End") { - onEndKeyDown(event); - event.preventDefault(); - } else if (PAGE_KEYS.concat(ARROW_KEYS).includes(event.key)) { - onStepKeyDown(event); - event.preventDefault(); - } - }), - onPointerDown: composeEventHandlers(props.onPointerDown, (event) => { - const target = event.target; - target.setPointerCapture(event.pointerId); - event.preventDefault(); - if (context.thumbs.has(target)) { - target.focus(); - } else { - onSlideStart(event); - } - }), - onPointerMove: composeEventHandlers(props.onPointerMove, (event) => { - const target = event.target; - if (target.hasPointerCapture(event.pointerId)) onSlideMove(event); - }), - onPointerUp: composeEventHandlers(props.onPointerUp, (event) => { - const target = event.target; - if (target.hasPointerCapture(event.pointerId)) { - target.releasePointerCapture(event.pointerId); - onSlideEnd(event); - } - }) - } - ); - } -); -var TRACK_NAME = "SliderTrack"; -var SliderTrack = reactExports.forwardRef( - (props, forwardedRef) => { - const { __scopeSlider, ...trackProps } = props; - const context = useSliderContext(TRACK_NAME, __scopeSlider); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - Primitive.span, - { - "data-disabled": context.disabled ? "" : void 0, - "data-orientation": context.orientation, - ...trackProps, - ref: forwardedRef - } - ); - } -); -SliderTrack.displayName = TRACK_NAME; -var RANGE_NAME = "SliderRange"; -var SliderRange = reactExports.forwardRef( - (props, forwardedRef) => { - const { __scopeSlider, ...rangeProps } = props; - const context = useSliderContext(RANGE_NAME, __scopeSlider); - const orientation = useSliderOrientationContext(RANGE_NAME, __scopeSlider); - const ref = reactExports.useRef(null); - const composedRefs = useComposedRefs(forwardedRef, ref); - const valuesCount = context.values.length; - const percentages = context.values.map( - (value) => convertValueToPercentage(value, context.min, context.max) - ); - const offsetStart = valuesCount > 1 ? Math.min(...percentages) : 0; - const offsetEnd = 100 - Math.max(...percentages); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - Primitive.span, - { - "data-orientation": context.orientation, - "data-disabled": context.disabled ? "" : void 0, - ...rangeProps, - ref: composedRefs, - style: { - ...props.style, - [orientation.startEdge]: offsetStart + "%", - [orientation.endEdge]: offsetEnd + "%" - } - } - ); - } -); -SliderRange.displayName = RANGE_NAME; -var THUMB_NAME = "SliderThumb"; -var SliderThumb = reactExports.forwardRef( - (props, forwardedRef) => { - const getItems = useCollection(props.__scopeSlider); - const [thumb, setThumb] = reactExports.useState(null); - const composedRefs = useComposedRefs(forwardedRef, (node) => setThumb(node)); - const index = reactExports.useMemo( - () => thumb ? getItems().findIndex((item) => item.ref.current === thumb) : -1, - [getItems, thumb] - ); - return /* @__PURE__ */ jsxRuntimeExports.jsx(SliderThumbImpl, { ...props, ref: composedRefs, index }); - } -); -var SliderThumbImpl = reactExports.forwardRef( - (props, forwardedRef) => { - const { __scopeSlider, index, name, ...thumbProps } = props; - const context = useSliderContext(THUMB_NAME, __scopeSlider); - const orientation = useSliderOrientationContext(THUMB_NAME, __scopeSlider); - const [thumb, setThumb] = reactExports.useState(null); - const composedRefs = useComposedRefs(forwardedRef, (node) => setThumb(node)); - const isFormControl = thumb ? context.form || !!thumb.closest("form") : true; - const size = useSize(thumb); - const value = context.values[index]; - const percent = value === void 0 ? 0 : convertValueToPercentage(value, context.min, context.max); - const label = getLabel(index, context.values.length); - const orientationSize = size?.[orientation.size]; - const thumbInBoundsOffset = orientationSize ? getThumbInBoundsOffset(orientationSize, percent, orientation.direction) : 0; - reactExports.useEffect(() => { - if (thumb) { - context.thumbs.add(thumb); - return () => { - context.thumbs.delete(thumb); - }; - } - }, [thumb, context.thumbs]); - return /* @__PURE__ */ jsxRuntimeExports.jsxs( - "span", - { - style: { - transform: "var(--radix-slider-thumb-transform)", - position: "absolute", - [orientation.startEdge]: `calc(${percent}% + ${thumbInBoundsOffset}px)` - }, - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(Collection.ItemSlot, { scope: props.__scopeSlider, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - Primitive.span, - { - role: "slider", - "aria-label": props["aria-label"] || label, - "aria-valuemin": context.min, - "aria-valuenow": value, - "aria-valuemax": context.max, - "aria-orientation": context.orientation, - "data-orientation": context.orientation, - "data-disabled": context.disabled ? "" : void 0, - tabIndex: context.disabled ? void 0 : 0, - ...thumbProps, - ref: composedRefs, - style: value === void 0 ? { display: "none" } : props.style, - onFocus: composeEventHandlers(props.onFocus, () => { - context.valueIndexToChangeRef.current = index; - }) - } - ) }), - isFormControl && /* @__PURE__ */ jsxRuntimeExports.jsx( - SliderBubbleInput, - { - name: name ?? (context.name ? context.name + (context.values.length > 1 ? "[]" : "") : void 0), - form: context.form, - value - }, - index - ) - ] - } - ); - } -); -SliderThumb.displayName = THUMB_NAME; -var BUBBLE_INPUT_NAME = "RadioBubbleInput"; -var SliderBubbleInput = reactExports.forwardRef( - ({ __scopeSlider, value, ...props }, forwardedRef) => { - const ref = reactExports.useRef(null); - const composedRefs = useComposedRefs(ref, forwardedRef); - const prevValue = usePrevious(value); - reactExports.useEffect(() => { - const input = ref.current; - if (!input) return; - const inputProto = window.HTMLInputElement.prototype; - const descriptor = Object.getOwnPropertyDescriptor(inputProto, "value"); - const setValue = descriptor.set; - if (prevValue !== value && setValue) { - const event = new Event("input", { bubbles: true }); - setValue.call(input, value); - input.dispatchEvent(event); - } - }, [prevValue, value]); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - Primitive.input, - { - style: { display: "none" }, - ...props, - ref: composedRefs, - defaultValue: value - } - ); - } -); -SliderBubbleInput.displayName = BUBBLE_INPUT_NAME; -function getNextSortedValues(prevValues = [], nextValue, atIndex) { - const nextValues = [...prevValues]; - nextValues[atIndex] = nextValue; - return nextValues.sort((a, b) => a - b); -} -function convertValueToPercentage(value, min, max) { - const maxSteps = max - min; - const percentPerStep = 100 / maxSteps; - const percentage = percentPerStep * (value - min); - return clamp(percentage, [0, 100]); -} -function getLabel(index, totalValues) { - if (totalValues > 2) { - return `Value ${index + 1} of ${totalValues}`; - } else if (totalValues === 2) { - return ["Minimum", "Maximum"][index]; - } else { - return void 0; - } -} -function getClosestValueIndex(values, nextValue) { - if (values.length === 1) return 0; - const distances = values.map((value) => Math.abs(value - nextValue)); - const closestDistance = Math.min(...distances); - return distances.indexOf(closestDistance); -} -function getThumbInBoundsOffset(width, left, direction) { - const halfWidth = width / 2; - const halfPercent = 50; - const offset = linearScale([0, halfPercent], [0, halfWidth]); - return (halfWidth - offset(left) * direction) * direction; -} -function getStepsBetweenValues(values) { - return values.slice(0, -1).map((value, index) => values[index + 1] - value); -} -function hasMinStepsBetweenValues(values, minStepsBetweenValues) { - if (minStepsBetweenValues > 0) { - const stepsBetweenValues = getStepsBetweenValues(values); - const actualMinStepsBetweenValues = Math.min(...stepsBetweenValues); - return actualMinStepsBetweenValues >= minStepsBetweenValues; - } - return true; -} -function linearScale(input, output) { - return (value) => { - if (input[0] === input[1] || output[0] === output[1]) return output[0]; - const ratio = (output[1] - output[0]) / (input[1] - input[0]); - return output[0] + ratio * (value - input[0]); - }; -} -function getDecimalCount(value) { - return (String(value).split(".")[1] || "").length; -} -function roundValue(value, decimalCount) { - const rounder = Math.pow(10, decimalCount); - return Math.round(value * rounder) / rounder; -} -var Root = Slider; -var Track = SliderTrack; -var Range$1 = SliderRange; -var Thumb = SliderThumb; - -const TldrawUiSlider = reactExports.memo(function Slider({ - onHistoryMark, - title, - steps, - value, - label, - onValueChange, - ["data-testid"]: testId -}) { - const msg = useTranslation(); - const handleValueChange = reactExports.useCallback( - (value2) => { - onValueChange(value2[0]); - }, - [onValueChange] - ); - const handlePointerDown = reactExports.useCallback(() => { - onHistoryMark("click slider"); - }, [onHistoryMark]); - const handlePointerUp = reactExports.useCallback(() => { - if (!value) return; - onValueChange(value); - }, [value, onValueChange]); - return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-slider__container", children: /* @__PURE__ */ jsxRuntimeExports.jsxs( - Root, - { - "data-testid": testId, - className: "tlui-slider", - "area-label": "Opacity", - dir: "ltr", - min: 0, - max: steps, - step: 1, - value: value ? [value] : void 0, - onPointerDown: handlePointerDown, - onValueChange: handleValueChange, - onPointerUp: handlePointerUp, - title: title + " \u2014 " + msg(label), - children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(Track, { className: "tlui-slider__track", dir: "ltr", children: value !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(Range$1, { className: "tlui-slider__range", dir: "ltr" }) }), - value !== null && /* @__PURE__ */ jsxRuntimeExports.jsx(Thumb, { className: "tlui-slider__thumb", dir: "ltr" }) - ] - } - ) }); -}); - -function DoubleDropdownPickerInner({ - label, - uiTypeA, - uiTypeB, - labelA, - labelB, - itemsA, - itemsB, - styleA, - styleB, - valueA, - valueB, - onValueChange -}) { - const msg = useTranslation(); - const iconA = reactExports.useMemo( - () => itemsA.find((item) => valueA.type === "shared" && valueA.value === item.value)?.icon ?? "mixed", - [itemsA, valueA] - ); - const iconB = reactExports.useMemo( - () => itemsB.find((item) => valueB.type === "shared" && valueB.value === item.value)?.icon ?? "mixed", - [itemsB, valueB] - ); - if (valueA === void 0 && valueB === void 0) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-style-panel__double-select-picker", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx("div", { title: msg(label), className: "tlui-style-panel__double-select-picker-label", children: msg(label) }), - /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-buttons__horizontal", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiDropdownMenuRoot, { id: `style panel ${uiTypeA} A`, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuTrigger, { children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - "data-testid": `style.${uiTypeA}`, - title: msg(labelA) + " \u2014 " + (valueA === null || valueA.type === "mixed" ? msg("style-panel.mixed") : msg(`${uiTypeA}-style.${valueA.value}`)), - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: iconA, small: true, invertIcon: true }) - } - ) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuContent, { side: "left", align: "center", sideOffset: 80, alignOffset: 0, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-buttons__grid", children: itemsA.map((item, i) => { - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuItem, { "data-testid": `style.${uiTypeA}.${item.value}`, children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - onClick: () => onValueChange(styleA, item.value), - title: `${msg(labelA)} \u2014 ${msg(`${uiTypeA}-style.${item.value}`)}`, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: item.icon, invertIcon: true }) - }, - item.value - ) }, i); - }) }) }) - ] }), - /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiDropdownMenuRoot, { id: `style panel ${uiTypeB}`, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuTrigger, { children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - "data-testid": `style.${uiTypeB}`, - title: msg(labelB) + " \u2014 " + (valueB === null || valueB.type === "mixed" ? msg("style-panel.mixed") : msg(`${uiTypeB}-style.${valueB.value}`)), - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: iconB, small: true }) - } - ) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuContent, { side: "left", align: "center", sideOffset: 116, alignOffset: 0, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-buttons__grid", children: itemsB.map((item) => { - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuItem, { children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - title: `${msg(labelB)} \u2014 ${msg(`${uiTypeB}-style.${item.value}`)}`, - "data-testid": `style.${uiTypeB}.${item.value}`, - onClick: () => onValueChange(styleB, item.value), - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: item.icon }) - } - ) }, item.value); - }) }) }) - ] }) - ] }) - ] }); -} -const DoubleDropdownPicker = reactExports.memo( - DoubleDropdownPickerInner -); - -function DropdownPickerInner({ - id, - label, - uiType, - stylePanelType, - style, - items, - type, - value, - onValueChange -}) { - const msg = useTranslation(); - const editor = useEditor(); - const icon = reactExports.useMemo( - () => items.find((item) => value.type === "shared" && item.value === value.value)?.icon, - [items, value] - ); - const stylePanelName = msg(`style-panel.${stylePanelType}`); - const titleStr = value.type === "mixed" ? msg("style-panel.mixed") : stylePanelName + " \u2014 " + msg(`${uiType}-style.${value.value}`); - const labelStr = label ? msg(label) : ""; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiDropdownMenuRoot, { id: `style panel ${id}`, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuTrigger, { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiButton, { type, "data-testid": `style.${uiType}`, title: titleStr, children: [ - labelStr && /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonLabel, { children: labelStr }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: icon ?? "mixed" }) - ] }) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuContent, { side: "left", align: "center", alignOffset: 0, children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-buttons__grid", children: items.map((item) => { - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiDropdownMenuItem, { children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - "data-testid": `style.${uiType}.${item.value}`, - title: stylePanelName + " \u2014 " + msg(`${uiType}-style.${item.value}`), - onClick: () => { - editor.markHistoryStoppingPoint("select style dropdown item"); - onValueChange(style, item.value); - }, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: item.icon }) - } - ) }, item.value); - }) }) }) - ] }); -} -const DropdownPicker = reactExports.memo(DropdownPickerInner); - -function DefaultStylePanelContent({ styles }) { - const isDarkMode = useIsDarkMode(); - if (!styles) return null; - const geo = styles.get(GeoShapeGeoStyle); - const arrowheadEnd = styles.get(ArrowShapeArrowheadEndStyle); - const arrowheadStart = styles.get(ArrowShapeArrowheadStartStyle); - const spline = styles.get(LineShapeSplineStyle); - const font = styles.get(DefaultFontStyle); - const hideGeo = geo === void 0; - const hideArrowHeads = arrowheadEnd === void 0 && arrowheadStart === void 0; - const hideSpline = spline === void 0; - const hideText = font === void 0; - const theme = getDefaultColorTheme({ isDarkMode }); - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(CommonStylePickerSet, { theme, styles }), - !hideText && /* @__PURE__ */ jsxRuntimeExports.jsx(TextStylePickerSet, { theme, styles }), - !(hideGeo && hideArrowHeads && hideSpline) && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-style-panel__section", "aria-label": "style panel styles", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(GeoStylePickerSet, { styles }), - /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowheadStylePickerSet, { styles }), - /* @__PURE__ */ jsxRuntimeExports.jsx(SplineStylePickerSet, { styles }) - ] }) - ] }); -} -function useStyleChangeCallback() { - const editor = useEditor(); - const trackEvent = useUiEvents(); - return React.useMemo( - () => (function handleStyleChange(style, value) { - editor.run(() => { - if (editor.isIn("select")) { - editor.setStyleForSelectedShapes(style, value); - } - editor.setStyleForNextShapes(style, value); - editor.updateInstanceState({ isChangingStyle: true }); - }); - trackEvent("set-style", { source: "style-panel", id: style.id, value }); - }), - [editor, trackEvent] - ); -} -function CommonStylePickerSet({ styles, theme }) { - const msg = useTranslation(); - const editor = useEditor(); - const onHistoryMark = reactExports.useCallback((id) => editor.markHistoryStoppingPoint(id), [editor]); - const handleValueChange = useStyleChangeCallback(); - const color = styles.get(DefaultColorStyle); - const fill = styles.get(DefaultFillStyle); - const dash = styles.get(DefaultDashStyle); - const size = styles.get(DefaultSizeStyle); - const showPickers = fill !== void 0 || dash !== void 0 || size !== void 0; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsxs( - "div", - { - tabIndex: -1, - className: "tlui-style-panel__section__common", - "aria-label": "style panel styles", - "data-testid": "style.panel", - children: [ - color === void 0 ? null : /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButtonPicker, - { - title: msg("style-panel.color"), - uiType: "color", - style: DefaultColorStyle, - items: STYLES.color, - value: color, - onValueChange: handleValueChange, - theme, - onHistoryMark - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx(OpacitySlider, {}) - ] - } - ), - showPickers && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-style-panel__section", "aria-label": "style panel styles", children: [ - fill === void 0 ? null : /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButtonPicker, - { - title: msg("style-panel.fill"), - uiType: "fill", - style: DefaultFillStyle, - items: STYLES.fill, - value: fill, - onValueChange: handleValueChange, - theme, - onHistoryMark - } - ), - dash === void 0 ? null : /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButtonPicker, - { - title: msg("style-panel.dash"), - uiType: "dash", - style: DefaultDashStyle, - items: STYLES.dash, - value: dash, - onValueChange: handleValueChange, - theme, - onHistoryMark - } - ), - size === void 0 ? null : /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButtonPicker, - { - title: msg("style-panel.size"), - uiType: "size", - style: DefaultSizeStyle, - items: STYLES.size, - value: size, - onValueChange: (style, value) => { - handleValueChange(style, value); - const selectedShapeIds = editor.getSelectedShapeIds(); - if (selectedShapeIds.length > 0) { - kickoutOccludedShapes(editor, selectedShapeIds); - } - }, - theme, - onHistoryMark - } - ) - ] }) - ] }); -} -function TextStylePickerSet({ theme, styles }) { - const msg = useTranslation(); - const handleValueChange = useStyleChangeCallback(); - const editor = useEditor(); - const onHistoryMark = reactExports.useCallback((id) => editor.markHistoryStoppingPoint(id), [editor]); - const font = styles.get(DefaultFontStyle); - const textAlign = styles.get(DefaultTextAlignStyle); - const labelAlign = styles.get(DefaultHorizontalAlignStyle); - const verticalLabelAlign = styles.get(DefaultVerticalAlignStyle); - if (font === void 0 && labelAlign === void 0) { - return null; - } - return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-style-panel__section", "aria-label": "style panel text", children: [ - font === void 0 ? null : /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButtonPicker, - { - title: msg("style-panel.font"), - uiType: "font", - style: DefaultFontStyle, - items: STYLES.font, - value: font, - onValueChange: handleValueChange, - theme, - onHistoryMark - } - ), - textAlign === void 0 ? null : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-style-panel__row", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButtonPicker, - { - title: msg("style-panel.align"), - uiType: "align", - style: DefaultTextAlignStyle, - items: STYLES.textAlign, - value: textAlign, - onValueChange: handleValueChange, - theme, - onHistoryMark - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-style-panel__row__extra-button", children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - title: msg("style-panel.vertical-align"), - "data-testid": "vertical-align", - disabled: true, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "vertical-align-middle" }) - } - ) }) - ] }), - labelAlign === void 0 ? null : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "tlui-style-panel__row", children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButtonPicker, - { - title: msg("style-panel.label-align"), - uiType: "align", - style: DefaultHorizontalAlignStyle, - items: STYLES.horizontalAlign, - value: labelAlign, - onValueChange: handleValueChange, - theme, - onHistoryMark - } - ), - /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "tlui-style-panel__row__extra-button", children: verticalLabelAlign === void 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "icon", - title: msg("style-panel.vertical-align"), - "data-testid": "vertical-align", - disabled: true, - children: /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiButtonIcon, { icon: "vertical-align-middle" }) - } - ) : /* @__PURE__ */ jsxRuntimeExports.jsx( - DropdownPicker, - { - type: "icon", - id: "geo-vertical-alignment", - uiType: "verticalAlign", - stylePanelType: "vertical-align", - style: DefaultVerticalAlignStyle, - items: STYLES.verticalAlign, - value: verticalLabelAlign, - onValueChange: handleValueChange - } - ) }) - ] }) - ] }); -} -function GeoStylePickerSet({ styles }) { - const handleValueChange = useStyleChangeCallback(); - const geo = styles.get(GeoShapeGeoStyle); - if (geo === void 0) { - return null; - } - return /* @__PURE__ */ jsxRuntimeExports.jsx( - DropdownPicker, - { - id: "geo", - type: "menu", - label: "style-panel.geo", - uiType: "geo", - stylePanelType: "geo", - style: GeoShapeGeoStyle, - items: STYLES.geo, - value: geo, - onValueChange: handleValueChange - } - ); -} -function SplineStylePickerSet({ styles }) { - const handleValueChange = useStyleChangeCallback(); - const spline = styles.get(LineShapeSplineStyle); - if (spline === void 0) { - return null; - } - return /* @__PURE__ */ jsxRuntimeExports.jsx( - DropdownPicker, - { - id: "spline", - type: "menu", - label: "style-panel.spline", - uiType: "spline", - stylePanelType: "spline", - style: LineShapeSplineStyle, - items: STYLES.spline, - value: spline, - onValueChange: handleValueChange - } - ); -} -function ArrowheadStylePickerSet({ styles }) { - const handleValueChange = useStyleChangeCallback(); - const arrowheadEnd = styles.get(ArrowShapeArrowheadEndStyle); - const arrowheadStart = styles.get(ArrowShapeArrowheadStartStyle); - if (!arrowheadEnd || !arrowheadStart) { - return null; - } - return /* @__PURE__ */ jsxRuntimeExports.jsx( - DoubleDropdownPicker, - { - label: "style-panel.arrowheads", - uiTypeA: "arrowheadStart", - styleA: ArrowShapeArrowheadStartStyle, - itemsA: STYLES.arrowheadStart, - valueA: arrowheadStart, - uiTypeB: "arrowheadEnd", - styleB: ArrowShapeArrowheadEndStyle, - itemsB: STYLES.arrowheadEnd, - valueB: arrowheadEnd, - onValueChange: handleValueChange, - labelA: "style-panel.arrowhead-start", - labelB: "style-panel.arrowhead-end" - } - ); -} -const tldrawSupportedOpacities = [0.1, 0.25, 0.5, 0.75, 1]; -function OpacitySlider() { - const editor = useEditor(); - const onHistoryMark = reactExports.useCallback((id) => editor.markHistoryStoppingPoint(id), [editor]); - const opacity = useValue("opacity", () => editor.getSharedOpacity(), [editor]); - const trackEvent = useUiEvents(); - const msg = useTranslation(); - const handleOpacityValueChange = React.useCallback( - (value) => { - const item = tldrawSupportedOpacities[value]; - editor.run(() => { - if (editor.isIn("select")) { - editor.setOpacityForSelectedShapes(item); - } - editor.setOpacityForNextShapes(item); - editor.updateInstanceState({ isChangingStyle: true }); - }); - trackEvent("set-style", { source: "style-panel", id: "opacity", value }); - }, - [editor, trackEvent] - ); - if (opacity === void 0) return null; - const opacityIndex = opacity.type === "mixed" ? -1 : tldrawSupportedOpacities.indexOf( - minBy( - tldrawSupportedOpacities, - (supportedOpacity) => Math.abs(supportedOpacity - opacity.value) - ) - ); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiSlider, - { - "data-testid": "style.opacity", - value: opacityIndex >= 0 ? opacityIndex : tldrawSupportedOpacities.length - 1, - label: opacity.type === "mixed" ? "style-panel.mixed" : `opacity-style.${opacity.value}`, - onValueChange: handleOpacityValueChange, - steps: tldrawSupportedOpacities.length - 1, - title: msg("style-panel.opacity"), - onHistoryMark - } - ); -} - -const DefaultStylePanel = reactExports.memo(function DefaultStylePanel2({ - isMobile, - children -}) { - const editor = useEditor(); - const ref = reactExports.useRef(null); - usePassThroughWheelEvents(ref); - const styles = useRelevantStyles(); - const handlePointerOut = reactExports.useCallback(() => { - if (!isMobile) { - editor.updateInstanceState({ isChangingStyle: false }); - } - }, [editor, isMobile]); - const content = children ?? /* @__PURE__ */ jsxRuntimeExports.jsx(DefaultStylePanelContent, { styles }); - return /* @__PURE__ */ jsxRuntimeExports.jsx( - "div", - { - ref, - className: classNames("tlui-style-panel", { "tlui-style-panel__wrapper": !isMobile }), - "data-ismobile": isMobile, - onPointerLeave: handlePointerOut, - children: content - } - ); -}); - -function MobileStylePanel() { - const editor = useEditor(); - const msg = useTranslation(); - const relevantStyles = useRelevantStyles(); - const color = relevantStyles?.get(DefaultColorStyle); - const theme = getDefaultColorTheme({ isDarkMode: editor.user.getIsDarkMode() }); - const currentColor = (color?.type === "shared" ? theme[color.value] : theme.black).solid; - const disableStylePanel = useValue( - "disable style panel", - () => editor.isInAny("hand", "zoom", "eraser", "laser"), - [editor] - ); - const handleStylesOpenChange = reactExports.useCallback( - (isOpen) => { - if (!isOpen) { - editor.updateInstanceState({ isChangingStyle: false }); - } - }, - [editor] - ); - const { StylePanel } = useTldrawUiComponents(); - if (!StylePanel) return null; - return /* @__PURE__ */ jsxRuntimeExports.jsxs(TldrawUiPopover, { id: "mobile style menu", onOpenChange: handleStylesOpenChange, children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiPopoverTrigger, { children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButton, - { - type: "tool", - "data-testid": "mobile-styles.button", - style: { - color: disableStylePanel ? "var(--color-muted-1)" : currentColor - }, - title: msg("style-panel.title"), - disabled: disableStylePanel, - children: /* @__PURE__ */ jsxRuntimeExports.jsx( - TldrawUiButtonIcon, - { - icon: disableStylePanel ? "blob" : color?.type === "mixed" ? "mixed" : "blob" - } - ) - } - ) }), - /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiPopoverContent, { side: "top", align: "end", children: StylePanel && /* @__PURE__ */ jsxRuntimeExports.jsx(StylePanel, { isMobile: true }) }) - ] }); -} - -function DefaultToolbarContent() { - return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ - /* @__PURE__ */ jsxRuntimeExports.jsx(SelectToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(HandToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(DrawToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(EraserToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(TextToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(NoteToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(AssetToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(RectangleToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(EllipseToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(DiamondToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(HexagonToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(OvalToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(RhombusToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(StarToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(CloudToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(HeartToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(XBoxToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(CheckBoxToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeftToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowUpToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowDownToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowRightToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(LineToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(HighlightToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(LaserToolbarItem, {}), - /* @__PURE__ */ jsxRuntimeExports.jsx(FrameToolbarItem, {}) - ] }); -} -function useIsToolSelected(tool) { - const editor = useEditor(); - const geo = tool.meta?.geo; - return useValue( - "is tool selected", - () => { - const activeToolId = editor.getCurrentToolId(); - const geoState = editor.getSharedStyles().getAsKnownValue(GeoShapeGeoStyle); - return geo ? activeToolId === "geo" && geoState === geo : activeToolId === tool.id; - }, - [editor, tool.id, geo] - ); -} -function ToolbarItem({ tool }) { - const tools = useTools(); - const isSelected = useIsToolSelected(tools[tool]); - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: tool, isSelected }); -} -function SelectToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "select" }); -} -function HandToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "hand" }); -} -function DrawToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "draw" }); -} -function EraserToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "eraser" }); -} -function ArrowToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "arrow" }); -} -function TextToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "text" }); -} -function NoteToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "note" }); -} -function AssetToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(TldrawUiMenuToolItem, { toolId: "asset" }); -} -function RectangleToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "rectangle" }); -} -function EllipseToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "ellipse" }); -} -function DiamondToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "diamond" }); -} -function TriangleToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "triangle" }); -} -function RhombusToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "rhombus" }); -} -function HeartToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "heart" }); -} -function HexagonToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "hexagon" }); -} -function CloudToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "cloud" }); -} -function StarToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "star" }); -} -function OvalToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "oval" }); -} -function XBoxToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "x-box" }); -} -function CheckBoxToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "check-box" }); -} -function ArrowLeftToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "arrow-left" }); -} -function ArrowUpToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "arrow-up" }); -} -function ArrowDownToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "arrow-down" }); -} -function ArrowRightToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "arrow-right" }); -} -function LineToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "line" }); -} -function HighlightToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "highlight" }); -} -function FrameToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "frame" }); -} -function LaserToolbarItem() { - return /* @__PURE__ */ jsxRuntimeExports.jsx(ToolbarItem, { tool: "laser" }); -} - -/**! - * hotkeys-js v3.13.15 - * A simple micro-library for defining and dispatching keyboard shortcuts. It has no dependencies. - * - * Copyright (c) 2025 kenny wong - * https://github.com/jaywcjlove/hotkeys-js.git - * - * @website: https://jaywcjlove.github.io/hotkeys-js - - * Licensed under the MIT license - */ - -const isff = typeof navigator !== 'undefined' ? navigator.userAgent.toLowerCase().indexOf('firefox') > 0 : false; - -/** Bind event */ -function addEvent(object, event, method, useCapture) { - if (object.addEventListener) { - object.addEventListener(event, method, useCapture); - } else if (object.attachEvent) { - object.attachEvent("on".concat(event), method); - } -} -function removeEvent(object, event, method, useCapture) { - if (object.removeEventListener) { - object.removeEventListener(event, method, useCapture); - } else if (object.detachEvent) { - object.detachEvent("on".concat(event), method); - } -} - -/** Convert modifier keys to their corresponding key codes */ -function getMods(modifier, key) { - const mods = key.slice(0, key.length - 1); - for (let i = 0; i < mods.length; i++) mods[i] = modifier[mods[i].toLowerCase()]; - return mods; -} - -/** Process the input key string and convert it to an array */ -function getKeys$1(key) { - if (typeof key !== 'string') key = ''; - key = key.replace(/\s/g, ''); // Match any whitespace character, including spaces, tabs, form feeds, etc. - const keys = key.split(','); // Allow multiple shortcuts separated by ',' - let index = keys.lastIndexOf(''); - - // Shortcut may include ',' — special handling needed - for (; index >= 0;) { - keys[index - 1] += ','; - keys.splice(index, 1); - index = keys.lastIndexOf(''); - } - return keys; -} - -/** Compare arrays of modifier keys */ -function compareArray(a1, a2) { - const arr1 = a1.length >= a2.length ? a1 : a2; - const arr2 = a1.length >= a2.length ? a2 : a1; - let isIndex = true; - for (let i = 0; i < arr1.length; i++) { - if (arr2.indexOf(arr1[i]) === -1) isIndex = false; - } - return isIndex; -} - -// Special Keys -const _keyMap = { - backspace: 8, - 'âŒĢ': 8, - tab: 9, - clear: 12, - enter: 13, - '↩': 13, - return: 13, - esc: 27, - escape: 27, - space: 32, - left: 37, - up: 38, - right: 39, - down: 40, - /// https://w3c.github.io/uievents/#events-keyboard-key-location - arrowup: 38, - arrowdown: 40, - arrowleft: 37, - arrowright: 39, - del: 46, - delete: 46, - ins: 45, - insert: 45, - home: 36, - end: 35, - pageup: 33, - pagedown: 34, - capslock: 20, - num_0: 96, - num_1: 97, - num_2: 98, - num_3: 99, - num_4: 100, - num_5: 101, - num_6: 102, - num_7: 103, - num_8: 104, - num_9: 105, - num_multiply: 106, - num_add: 107, - num_enter: 108, - num_subtract: 109, - num_decimal: 110, - num_divide: 111, - 'â‡Ē': 20, - ',': 188, - '.': 190, - '/': 191, - '`': 192, - '-': isff ? 173 : 189, - '=': isff ? 61 : 187, - ';': isff ? 59 : 186, - '\'': 222, - '{': 219, - '}': 221, - '[': 219, - ']': 221, - '\\': 220 -}; - -// Modifier Keys -const _modifier = { - // shiftKey - '⇧': 16, - shift: 16, - // altKey - 'âŒĨ': 18, - alt: 18, - option: 18, - // ctrlKey - '⌃': 17, - ctrl: 17, - control: 17, - // metaKey - '⌘': 91, - cmd: 91, - meta: 91, - command: 91 -}; -const modifierMap = { - 16: 'shiftKey', - 18: 'altKey', - 17: 'ctrlKey', - 91: 'metaKey', - shiftKey: 16, - ctrlKey: 17, - altKey: 18, - metaKey: 91 -}; -const _mods = { - 16: false, - 18: false, - 17: false, - 91: false -}; -const _handlers = {}; - -// F1~F12 special key -for (let k = 1; k < 20; k++) { - _keyMap["f".concat(k)] = 111 + k; -} - -/** Record the pressed keys */ -let _downKeys = []; -/** Whether the window has already listened to the focus event */ -let winListendFocus = null; -/** Default hotkey scope */ -let _scope = 'all'; -/** Map to record elements with bound events */ -const elementEventMap = new Map(); - -/** Return key code */ -const code = x => _keyMap[x.toLowerCase()] || _modifier[x.toLowerCase()] || x.toUpperCase().charCodeAt(0); -const getKey = x => Object.keys(_keyMap).find(k => _keyMap[k] === x); -const getModifier = x => Object.keys(_modifier).find(k => _modifier[k] === x); - -/** Set or get the current scope (defaults to 'all') */ -function setScope(scope) { - _scope = scope || 'all'; -} -/** Get the current scope */ -function getScope() { - return _scope || 'all'; -} -/** Get the key codes of the currently pressed keys */ -function getPressedKeyCodes() { - return _downKeys.slice(0); -} -function getPressedKeyString() { - return _downKeys.map(c => getKey(c) || getModifier(c) || String.fromCharCode(c)); -} -function getAllKeyCodes() { - const result = []; - Object.keys(_handlers).forEach(k => { - _handlers[k].forEach(_ref => { - let { - key, - scope, - mods, - shortcut - } = _ref; - result.push({ - scope, - shortcut, - mods, - keys: key.split('+').map(v => code(v)) - }); - }); - }); - return result; -} - -/** hotkey is effective only when filter return true */ -function filter(event) { - const target = event.target || event.srcElement; - const { - tagName - } = target; - let flag = true; - const isInput = tagName === 'INPUT' && !['checkbox', 'radio', 'range', 'button', 'file', 'reset', 'submit', 'color'].includes(target.type); - // ignore: isContentEditable === 'true', and