From c5d3b9214da850919eabea5124bde7a254991ddf Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Fri, 8 Apr 2022 14:30:46 -0500 Subject: [PATCH] =?UTF-8?q?feat(editor):=20=E2=9C=A8=20Zoom=20in/out?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/builder/assets/icons.tsx | 32 ++++++ .../components/editor/BoardMenuButton.tsx | 13 ++- .../components/editor/EditorSettingsModal.tsx | 97 +++++++++++++++++++ .../shared/Graph/Edges/DrawingEdge.tsx | 33 ++++--- .../shared/Graph/Edges/DropOffEdge.tsx | 11 ++- .../components/shared/Graph/Edges/Edge.tsx | 27 ++++-- .../builder/components/shared/Graph/Graph.tsx | 82 +++++++++++++--- .../Graph/Nodes/BlockNode/BlockNode.tsx | 7 +- .../Graph/Nodes/ItemNode/ItemNodesList.tsx | 6 +- .../Graph/Nodes/StepNode/StepNodesList.tsx | 5 +- .../components/shared/Graph/ZoomButtons.tsx | 37 +++++++ apps/builder/contexts/GraphDndContext.tsx | 6 +- apps/builder/contexts/UserContext.tsx | 2 +- apps/builder/services/graph.ts | 30 ++++-- .../migration.sql | 5 + packages/db/prisma/schema.prisma | 6 ++ 16 files changed, 336 insertions(+), 63 deletions(-) create mode 100644 apps/builder/components/editor/EditorSettingsModal.tsx create mode 100644 apps/builder/components/shared/Graph/ZoomButtons.tsx create mode 100644 packages/db/prisma/migrations/20220408192635_add_editor_navigation/migration.sql diff --git a/apps/builder/assets/icons.tsx b/apps/builder/assets/icons.tsx index 26fb70fef..755035744 100644 --- a/apps/builder/assets/icons.tsx +++ b/apps/builder/assets/icons.tsx @@ -403,3 +403,35 @@ export const TemplateIcon = (props: IconProps) => ( ) + +export const MinusIcon = (props: IconProps) => ( + + + +) + +export const LaptopIcon = (props: IconProps) => ( + + + + +) + +export const MouseIcon = (props: IconProps) => ( + + + +) diff --git a/apps/builder/components/editor/BoardMenuButton.tsx b/apps/builder/components/editor/BoardMenuButton.tsx index 484b25010..f1d4b03f6 100644 --- a/apps/builder/components/editor/BoardMenuButton.tsx +++ b/apps/builder/components/editor/BoardMenuButton.tsx @@ -5,16 +5,19 @@ import { MenuButtonProps, MenuItem, MenuList, + useDisclosure, } from '@chakra-ui/react' import assert from 'assert' -import { DownloadIcon, MoreVerticalIcon } from 'assets/icons' +import { DownloadIcon, MoreVerticalIcon, SettingsIcon } from 'assets/icons' import { useTypebot } from 'contexts/TypebotContext' import React, { useState } from 'react' import { parseDefaultPublicId } from 'services/typebots' +import { EditorSettingsModal } from './EditorSettingsModal' export const BoardMenuButton = (props: MenuButtonProps) => { const { typebot } = useTypebot() const [isDownloading, setIsDownloading] = useState(false) + const { isOpen, onOpen, onClose } = useDisclosure() const downloadFlow = () => { assert(typebot) @@ -36,18 +39,22 @@ export const BoardMenuButton = (props: MenuButtonProps) => { } isLoading={isDownloading} size="sm" + shadow="lg" {...props} /> + } onClick={onOpen}> + Editor settings + } onClick={downloadFlow}> Export flow + ) } diff --git a/apps/builder/components/editor/EditorSettingsModal.tsx b/apps/builder/components/editor/EditorSettingsModal.tsx new file mode 100644 index 000000000..38bdca684 --- /dev/null +++ b/apps/builder/components/editor/EditorSettingsModal.tsx @@ -0,0 +1,97 @@ +import { + Heading, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalOverlay, + Stack, + Text, + Radio, + VStack, + RadioGroup, + HStack, +} from '@chakra-ui/react' +import { LaptopIcon, MouseIcon } from 'assets/icons' +import { useUser } from 'contexts/UserContext' +import { GraphNavigation } from 'db' +import React, { useEffect, useState } from 'react' + +type Props = { + isOpen: boolean + onClose: () => void +} + +export const EditorSettingsModal = ({ isOpen, onClose }: Props) => { + return ( + + + + + + + + + + ) +} + +const EditorSettings = () => { + const { user, saveUser } = useUser() + const [value, setValue] = useState( + user?.graphNavigation ?? GraphNavigation.TRACKPAD + ) + + useEffect(() => { + if (user?.graphNavigation === value) return + saveUser({ graphNavigation: value as GraphNavigation }).then() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]) + + const options = [ + { + value: GraphNavigation.MOUSE, + label: 'Mouse', + description: + 'Move by dragging the board and zoom in/out using the scroll wheel', + icon: , + }, + { + value: GraphNavigation.TRACKPAD, + label: 'Trackpad', + description: 'Move the board using 2 fingers and zoom in/out by pinching', + icon: , + }, + ] + + return ( + + Navigation + + + {options.map((option) => ( + + {option.icon} + + {option.label} + {option.description} + + + + + ))} + + + + ) +} diff --git a/apps/builder/components/shared/Graph/Edges/DrawingEdge.tsx b/apps/builder/components/shared/Graph/Edges/DrawingEdge.tsx index a4c64151f..df581c41d 100644 --- a/apps/builder/components/shared/Graph/Edges/DrawingEdge.tsx +++ b/apps/builder/components/shared/Graph/Edges/DrawingEdge.tsx @@ -29,21 +29,23 @@ export const DrawingEdge = () => { const sourceTop = useMemo(() => { if (!connectingIds) return 0 - return getEndpointTopOffset( - sourceEndpoints, - graphPosition.y, - connectingIds.source.itemId ?? connectingIds.source.stepId - ) + return getEndpointTopOffset({ + endpoints: sourceEndpoints, + graphOffsetY: graphPosition.y, + endpointId: connectingIds.source.itemId ?? connectingIds.source.stepId, + graphScale: graphPosition.scale, + }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [connectingIds, sourceEndpoints]) const targetTop = useMemo(() => { if (!connectingIds) return 0 - return getEndpointTopOffset( - targetEndpoints, - graphPosition.y, - connectingIds.target?.stepId - ) + return getEndpointTopOffset({ + endpoints: targetEndpoints, + graphOffsetY: graphPosition.y, + endpointId: connectingIds.target?.stepId, + graphScale: graphPosition.scale, + }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [connectingIds, targetEndpoints]) @@ -56,6 +58,7 @@ export const DrawingEdge = () => { targetBlockCoordinates, sourceTop, targetTop, + graphScale: graphPosition.scale, }) : computeEdgePathToMouse({ sourceBlockCoordinates, @@ -68,13 +71,15 @@ export const DrawingEdge = () => { targetBlockCoordinates, targetTop, mousePosition, + graphPosition.scale, ]) const handleMouseMove = (e: MouseEvent) => { - setMousePosition({ - x: e.clientX - graphPosition.x, - y: e.clientY - graphPosition.y, - }) + const coordinates = { + x: (e.clientX - graphPosition.x) / graphPosition.scale, + y: (e.clientY - graphPosition.y) / graphPosition.scale, + } + setMousePosition(coordinates) } useEventListener('mousemove', handleMouseMove) useEventListener('mouseup', () => { diff --git a/apps/builder/components/shared/Graph/Edges/DropOffEdge.tsx b/apps/builder/components/shared/Graph/Edges/DropOffEdge.tsx index abde22494..0d1fefe13 100644 --- a/apps/builder/components/shared/Graph/Edges/DropOffEdge.tsx +++ b/apps/builder/components/shared/Graph/Edges/DropOffEdge.tsx @@ -58,11 +58,12 @@ export const DropOffEdge = ({ const block = publishedTypebot?.blocks.find(byId(blockId)) const sourceTop = useMemo( () => - getEndpointTopOffset( - sourceEndpoints, - graphPosition.y, - block?.steps[block.steps.length - 1].id - ), + getEndpointTopOffset({ + endpoints: sourceEndpoints, + graphOffsetY: graphPosition.y, + endpointId: block?.steps[block.steps.length - 1].id, + graphScale: graphPosition.scale, + }), // eslint-disable-next-line react-hooks/exhaustive-deps [block?.steps, sourceEndpoints, blocksCoordinates] ) diff --git a/apps/builder/components/shared/Graph/Edges/Edge.tsx b/apps/builder/components/shared/Graph/Edges/Edge.tsx index 5e88c7b2b..7b909ce64 100644 --- a/apps/builder/components/shared/Graph/Edges/Edge.tsx +++ b/apps/builder/components/shared/Graph/Edges/Edge.tsx @@ -44,11 +44,12 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => { const sourceTop = useMemo( () => - getEndpointTopOffset( - sourceEndpoints, - graphPosition.y, - getSourceEndpointId(edge) - ), + getEndpointTopOffset({ + endpoints: sourceEndpoints, + graphOffsetY: graphPosition.y, + endpointId: getSourceEndpointId(edge), + graphScale: graphPosition.scale, + }), // eslint-disable-next-line react-hooks/exhaustive-deps [sourceBlockCoordinates?.y, edge, sourceEndpoints, refreshEdge] ) @@ -58,17 +59,28 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => { }, []) const [targetTop, setTargetTop] = useState( - getEndpointTopOffset(targetEndpoints, graphPosition.y, edge?.to.stepId) + getEndpointTopOffset({ + endpoints: targetEndpoints, + graphOffsetY: graphPosition.y, + endpointId: edge?.to.stepId, + graphScale: graphPosition.scale, + }) ) useLayoutEffect(() => { setTargetTop( - getEndpointTopOffset(targetEndpoints, graphPosition.y, edge?.to.stepId) + getEndpointTopOffset({ + endpoints: targetEndpoints, + graphOffsetY: graphPosition.y, + endpointId: edge?.to.stepId, + graphScale: graphPosition.scale, + }) ) }, [ targetBlockCoordinates?.y, targetEndpoints, graphPosition.y, edge?.to.stepId, + graphPosition.scale, ]) const path = useMemo(() => { @@ -79,6 +91,7 @@ export const Edge = ({ edge }: { edge: EdgeProps }) => { targetBlockCoordinates, sourceTop, targetTop, + graphScale: graphPosition.scale, }) return computeEdgePath(anchorsPosition) // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/apps/builder/components/shared/Graph/Graph.tsx b/apps/builder/components/shared/Graph/Graph.tsx index b4b62c8d5..daffa40d6 100644 --- a/apps/builder/components/shared/Graph/Graph.tsx +++ b/apps/builder/components/shared/Graph/Graph.tsx @@ -2,20 +2,26 @@ import { Flex, FlexProps, useEventListener } from '@chakra-ui/react' import React, { useRef, useMemo, useEffect, useState } from 'react' import { blockWidth, + Coordinates, graphPositionDefaultValue, useGraph, } from 'contexts/GraphContext' import { useStepDnd } from 'contexts/GraphDndContext' import { useTypebot } from 'contexts/TypebotContext/TypebotContext' -import { headerHeight } from 'components/shared/TypebotHeader/TypebotHeader' import { DraggableStepType, PublicTypebot, Typebot } from 'models' import { AnswersCount } from 'services/analytics' import { useDebounce } from 'use-debounce' import { DraggableCore, DraggableData, DraggableEvent } from 'react-draggable' import GraphContent from './GraphContent' import cuid from 'cuid' +import { headerHeight } from '../TypebotHeader' +import { useUser } from 'contexts/UserContext' +import { GraphNavigation } from 'db' +import { ZoomButtons } from './ZoomButtons' -declare const window: { chrome: unknown | undefined } +const maxScale = 1.5 +const minScale = 0.1 +const zoomButtonsScaleStep = 0.2 export const Graph = ({ typebot, @@ -51,6 +57,7 @@ export const Graph = ({ `translate(${graphPosition.x}px, ${graphPosition.y}px) scale(${graphPosition.scale})`, [graphPosition] ) + const { user } = useUser() useEffect(() => { editorContainerRef.current = document.getElementById( @@ -65,7 +72,7 @@ export const Graph = ({ setGlobalGraphPosition({ x: left + debouncedGraphPosition.x, y: top + debouncedGraphPosition.y, - scale: 1, + scale: debouncedGraphPosition.scale, }) // eslint-disable-next-line react-hooks/exhaustive-deps }, [debouncedGraphPosition]) @@ -73,22 +80,26 @@ export const Graph = ({ const handleMouseWheel = (e: WheelEvent) => { e.preventDefault() const isPinchingTrackpad = e.ctrlKey - if (isPinchingTrackpad) return - setGraphPosition({ - ...graphPosition, - x: graphPosition.x - e.deltaX / (window.chrome ? 2 : 1), - y: graphPosition.y - e.deltaY / (window.chrome ? 2 : 1), - }) + user?.graphNavigation === GraphNavigation.MOUSE + ? zoom(-e.deltaY * 0.01, { x: e.clientX, y: e.clientY }) + : isPinchingTrackpad + ? zoom(-e.deltaY * 0.01, { x: e.clientX, y: e.clientY }) + : setGraphPosition({ + ...graphPosition, + x: graphPosition.x - e.deltaX, + y: graphPosition.y - e.deltaY, + }) } const handleMouseUp = (e: MouseEvent) => { if (!typebot) return if (draggedItem) setDraggedItem(undefined) if (!draggedStep && !draggedStepType) return - const coordinates = { - x: e.clientX - graphPosition.x - blockWidth / 3, - y: e.clientY - graphPosition.y - 20 - headerHeight, - } + + const coordinates = projectMouse( + { x: e.clientX, y: e.clientY }, + graphPosition + ) const id = cuid() updateBlockCoordinates(id, coordinates) createBlock({ @@ -121,15 +132,40 @@ export const Graph = ({ const onDrag = (_: DraggableEvent, draggableData: DraggableData) => { const { deltaX, deltaY } = draggableData setGraphPosition({ + ...graphPosition, x: graphPosition.x + deltaX, y: graphPosition.y + deltaY, - scale: 1, + }) + } + + const zoom = (delta = zoomButtonsScaleStep, mousePosition?: Coordinates) => { + const { x: mouseX, y } = mousePosition ?? { x: 0, y: 0 } + const mouseY = y - headerHeight + let scale = graphPosition.scale + delta + if ( + (scale >= maxScale && graphPosition.scale === maxScale) || + (scale <= minScale && graphPosition.scale === minScale) + ) + return + scale = scale >= maxScale ? maxScale : scale <= minScale ? minScale : scale + + const xs = (mouseX - graphPosition.x) / graphPosition.scale + const ys = (mouseY - graphPosition.y) / graphPosition.scale + setGraphPosition({ + ...graphPosition, + x: mouseX - xs * scale, + y: mouseY - ys * scale, + scale, }) } return ( + zoom(zoomButtonsScaleStep)} + onZoomOut={() => zoom(-zoomButtonsScaleStep)} + /> ) } + +const projectMouse = ( + mouseCoordinates: Coordinates, + graphPosition: Coordinates & { scale: number } +) => { + return { + x: + (mouseCoordinates.x - + graphPosition.x - + blockWidth / (3 / graphPosition.scale)) / + graphPosition.scale, + y: + (mouseCoordinates.y - + graphPosition.y - + (headerHeight + 20 * graphPosition.scale)) / + graphPosition.scale, + } +} diff --git a/apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNode.tsx b/apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNode.tsx index b6b810530..41d31c66a 100644 --- a/apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNode.tsx +++ b/apps/builder/components/shared/Graph/Nodes/BlockNode/BlockNode.tsx @@ -32,6 +32,7 @@ export const BlockNode = ({ block, blockIndex }: Props) => { isReadOnly, focusedBlockId, setFocusedBlockId, + graphPosition, } = useGraph() const { typebot, updateBlock } = useTypebot() const { setMouseOverBlock, mouseOverBlock } = useStepDnd() @@ -86,11 +87,11 @@ export const BlockNode = ({ block, blockIndex }: Props) => { if (connectingIds) setConnectingIds({ ...connectingIds, target: undefined }) } - const onDrag = (event: DraggableEvent, draggableData: DraggableData) => { + const onDrag = (_: DraggableEvent, draggableData: DraggableData) => { const { deltaX, deltaY } = draggableData updateBlockCoordinates(block.id, { - x: blockCoordinates.x + deltaX, - y: blockCoordinates.y + deltaY, + x: blockCoordinates.x + deltaX / graphPosition.scale, + y: blockCoordinates.y + deltaY / graphPosition.scale, }) } diff --git a/apps/builder/components/shared/Graph/Nodes/ItemNode/ItemNodesList.tsx b/apps/builder/components/shared/Graph/Nodes/ItemNode/ItemNodesList.tsx index 9c4be8c12..921547392 100644 --- a/apps/builder/components/shared/Graph/Nodes/ItemNode/ItemNodesList.tsx +++ b/apps/builder/components/shared/Graph/Nodes/ItemNode/ItemNodesList.tsx @@ -3,7 +3,7 @@ import { computeNearestPlaceholderIndex, useStepDnd, } from 'contexts/GraphDndContext' -import { Coordinates } from 'contexts/GraphContext' +import { Coordinates, useGraph } from 'contexts/GraphContext' import { useTypebot } from 'contexts/TypebotContext' import { ButtonItem, StepIndices, StepWithItems } from 'models' import React, { useEffect, useRef, useState } from 'react' @@ -25,6 +25,7 @@ export const ItemNodesList = ({ const { typebot, createItem, detachItemFromStep } = useTypebot() const { draggedItem, setDraggedItem, mouseOverBlock } = useStepDnd() const placeholderRefs = useRef([]) + const { graphPosition } = useGraph() const blockId = typebot?.blocks[blockIndex].id const isDraggingOnCurrentBlock = (draggedItem && mouseOverBlock?.id === blockId) ?? false @@ -182,8 +183,9 @@ export const ItemNodesList = ({ top="0" left="0" style={{ - transform: `translate(${position.x}px, ${position.y}px) rotate(-2deg)`, + transform: `translate(${position.x}px, ${position.y}px) rotate(-2deg) scale(${graphPosition.scale})`, }} + transformOrigin="0 0 0" /> )} diff --git a/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodesList.tsx b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodesList.tsx index 35641f3ce..1b245f669 100644 --- a/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodesList.tsx +++ b/apps/builder/components/shared/Graph/Nodes/StepNode/StepNodesList.tsx @@ -32,7 +32,7 @@ export const StepNodesList = ({ setDraggedStepType, } = useStepDnd() const { typebot, createStep, detachStepFromBlock } = useTypebot() - const { isReadOnly } = useGraph() + const { isReadOnly, graphPosition } = useGraph() const [expandedPlaceholderIndex, setExpandedPlaceholderIndex] = useState< number | undefined >() @@ -166,8 +166,9 @@ export const StepNodesList = ({ top="0" left="0" style={{ - transform: `translate(${position.x}px, ${position.y}px) rotate(-2deg)`, + transform: `translate(${position.x}px, ${position.y}px) rotate(-2deg) scale(${graphPosition.scale})`, }} + transformOrigin="0 0 0" /> )} diff --git a/apps/builder/components/shared/Graph/ZoomButtons.tsx b/apps/builder/components/shared/Graph/ZoomButtons.tsx new file mode 100644 index 000000000..365b21bcf --- /dev/null +++ b/apps/builder/components/shared/Graph/ZoomButtons.tsx @@ -0,0 +1,37 @@ +import { Stack, IconButton } from '@chakra-ui/react' +import { PlusIcon, MinusIcon } from 'assets/icons' +import { headerHeight } from '../TypebotHeader' + +type Props = { + onZoomIn: () => void + onZoomOut: () => void +} +export const ZoomButtons = ({ onZoomIn, onZoomOut }: Props) => ( + + } + aria-label={'Zoom out'} + size="sm" + onClick={onZoomIn} + bgColor="white" + borderBottomRadius={0} + /> + } + aria-label={'Zoom out'} + size="sm" + onClick={onZoomOut} + bgColor="white" + borderTopRadius={0} + /> + +) diff --git a/apps/builder/contexts/GraphDndContext.tsx b/apps/builder/contexts/GraphDndContext.tsx index 3b1158d1b..b12cd2d95 100644 --- a/apps/builder/contexts/GraphDndContext.tsx +++ b/apps/builder/contexts/GraphDndContext.tsx @@ -68,8 +68,10 @@ export const useDragDistance = ({ distanceTolerance?: number isDisabled: boolean }) => { - const mouseDownPosition = - useRef<{ absolute: Coordinates; relative: Coordinates }>() + const mouseDownPosition = useRef<{ + absolute: Coordinates + relative: Coordinates + }>() const handleMouseUp = () => { if (mouseDownPosition) mouseDownPosition.current = undefined diff --git a/apps/builder/contexts/UserContext.tsx b/apps/builder/contexts/UserContext.tsx index afbbc3884..16f64462a 100644 --- a/apps/builder/contexts/UserContext.tsx +++ b/apps/builder/contexts/UserContext.tsx @@ -21,7 +21,7 @@ const userContext = createContext<{ hasUnsavedChanges: boolean isOAuthProvider: boolean updateUser: (newUser: Partial) => void - saveUser: (newUser?: Partial) => void + saveUser: (newUser?: Partial) => Promise // eslint-disable-next-line @typescript-eslint/ban-ts-comment //@ts-ignore }>({}) diff --git a/apps/builder/services/graph.ts b/apps/builder/services/graph.ts index 43714443c..264b4f290 100644 --- a/apps/builder/services/graph.ts +++ b/apps/builder/services/graph.ts @@ -129,6 +129,7 @@ type GetAnchorsPositionParams = { targetBlockCoordinates: Coordinates sourceTop: number targetTop?: number + graphScale: number } export const getAnchorsPosition = ({ sourceBlockCoordinates, @@ -236,12 +237,14 @@ export const computeConnectingEdgePath = ({ targetBlockCoordinates, sourceTop, targetTop, + graphScale, }: GetAnchorsPositionParams) => { const anchorsPosition = getAnchorsPosition({ sourceBlockCoordinates, targetBlockCoordinates, sourceTop, targetTop, + graphScale, }) return computeEdgePath(anchorsPosition) } @@ -258,8 +261,8 @@ export const computeEdgePathToMouse = ({ const sourcePosition = { x: mousePosition.x - sourceBlockCoordinates.x > blockWidth / 2 - ? sourceBlockCoordinates.x + blockWidth - 40 - : sourceBlockCoordinates.x + 40, + ? sourceBlockCoordinates.x + blockWidth + : sourceBlockCoordinates.x, y: sourceTop, } const sourceType = @@ -277,19 +280,26 @@ export const computeEdgePathToMouse = ({ ).path } -export const getEndpointTopOffset = ( - endpoints: IdMap, - graphOffsetY: number, +export const getEndpointTopOffset = ({ + endpoints, + graphOffsetY, + endpointId, + graphScale, +}: { + endpoints: IdMap + graphOffsetY: number endpointId?: string -): number | undefined => { + graphScale: number +}): number | undefined => { if (!endpointId) return const endpointRef = endpoints[endpointId]?.ref if (!endpointRef?.current) return - const endpointHeight = 28 + const endpointHeight = 28 * graphScale return ( - endpointRef.current.getBoundingClientRect().top + - endpointHeight / 2 - - graphOffsetY + (endpointRef.current.getBoundingClientRect().y + + endpointHeight / 2 - + graphOffsetY) / + graphScale ) } diff --git a/packages/db/prisma/migrations/20220408192635_add_editor_navigation/migration.sql b/packages/db/prisma/migrations/20220408192635_add_editor_navigation/migration.sql new file mode 100644 index 000000000..835070b3b --- /dev/null +++ b/packages/db/prisma/migrations/20220408192635_add_editor_navigation/migration.sql @@ -0,0 +1,5 @@ +-- CreateEnum +CREATE TYPE "GraphNavigation" AS ENUM ('MOUSE', 'TRACKPAD'); + +-- AlterTable +ALTER TABLE "User" ADD COLUMN "graphNavigation" "GraphNavigation"; diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 01afc9b6d..721b1010c 100644 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -58,6 +58,12 @@ model User { CollaboratorsOnTypebots CollaboratorsOnTypebots[] company String? onboardingCategories String[] + graphNavigation GraphNavigation? +} + +enum GraphNavigation { + MOUSE + TRACKPAD } model CustomDomain {