From 0d81afeb468b500867fd4113de1c8879fb45ac11 Mon Sep 17 00:00:00 2001 From: jjoderis <58050428+jjoderis@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:50:19 +0100 Subject: [PATCH] MS2 Disable User Task Version Editing (#428) * Disable user task editing when inspecting a process version * Disabling everything in the user task editor when inspecting a process version * Fixed: Form elements in exported and deployed html are all disabled * Fixed formatting --- .../_user-task-builder/BuilderContext.tsx | 9 + .../_user-task-builder/Toolbar.tsx | 60 +++---- .../_user-task-builder/_sidebar/Settings.tsx | 2 +- .../_user-task-builder/_sidebar/Toolbox.tsx | 9 +- .../elements/CheckboxOrRadioGroup.tsx | 14 +- .../_user-task-builder/elements/Column.tsx | 6 +- .../_user-task-builder/elements/Container.tsx | 7 +- .../_user-task-builder/elements/Image.tsx | 8 +- .../_user-task-builder/elements/Input.tsx | 13 +- .../_user-task-builder/elements/Table.tsx | 13 +- .../_user-task-builder/elements/Text.tsx | 7 +- .../_user-task-builder/elements/utils.tsx | 29 +++- .../[processId]/_user-task-builder/index.tsx | 158 +++++++++--------- .../[processId]/_user-task-builder/utils.tsx | 22 ++- 14 files changed, 200 insertions(+), 157 deletions(-) create mode 100644 src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/BuilderContext.tsx diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/BuilderContext.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/BuilderContext.tsx new file mode 100644 index 000000000..92af0346c --- /dev/null +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/BuilderContext.tsx @@ -0,0 +1,9 @@ +import { createContext } from 'react'; + +type BuilderContextType = { + editingEnabled: boolean; +}; + +const BuilderContext = createContext({ editingEnabled: false }); + +export default BuilderContext; diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/Toolbar.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/Toolbar.tsx index f6d0e038f..1ed48f824 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/Toolbar.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/Toolbar.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { Row, Button, Divider, Col, Space } from 'antd'; @@ -13,6 +13,7 @@ import { import styles from './index.module.scss'; import { useEditor, Node } from '@craftjs/core'; +import BuilderContext from './BuilderContext'; export type EditorLayout = 'computer' | 'mobile'; @@ -27,41 +28,40 @@ export const Toolbar: React.FC = ({ iframeLayout, onLayoutChange, }) => { - const { query, actions, canUndo, canRedo, onDelete, editingEnabled } = useEditor( - (state, query) => { - const currentColumn = Array.from(state.events.selected) - .map((id) => state.nodes[id]) - .find((node) => node && node.data.name === 'Column'); + const { actions, canUndo, canRedo, onDelete } = useEditor((state, query) => { + const currentColumn = Array.from(state.events.selected) + .map((id) => state.nodes[id]) + .find((node) => node && node.data.name === 'Column'); - let onDelete; + let onDelete; - if (currentColumn) { - const parentRow = currentColumn.data.parent && state.nodes[currentColumn.data.parent]; - let deleteId = currentColumn.id; + if (currentColumn) { + const parentRow = currentColumn.data.parent && state.nodes[currentColumn.data.parent]; + let deleteId = currentColumn.id; - if (parentRow && parentRow.data.nodes.length === 1) { - deleteId = parentRow.id; - } - - const childNodeId = currentColumn.data.nodes[0]; - const childNode = state.nodes[childNodeId]; - - onDelete = async () => { - if (childNode.data.custom.onDelete) { - await (childNode.data.custom.onDelete as (node: Node) => Promise)(childNode); - } - actions.delete(deleteId!); - }; + if (parentRow && parentRow.data.nodes.length === 1) { + deleteId = parentRow.id; } - return { - onDelete, - canUndo: query.history.canUndo(), - canRedo: query.history.canRedo(), - editingEnabled: state.options.enabled, + const childNodeId = currentColumn.data.nodes[0]; + const childNode = state.nodes[childNodeId]; + + onDelete = async () => { + if (childNode.data.custom.onDelete) { + await (childNode.data.custom.onDelete as (node: Node) => Promise)(childNode); + } + actions.delete(deleteId!); }; - }, - ); + } + + return { + onDelete, + canUndo: query.history.canUndo(), + canRedo: query.history.canRedo(), + }; + }); + + const { editingEnabled } = useContext(BuilderContext); return ( diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/_sidebar/Settings.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/_sidebar/Settings.tsx index a906a1a90..af05bb376 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/_sidebar/Settings.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/_sidebar/Settings.tsx @@ -6,7 +6,7 @@ import styles from './index.module.scss'; import useBuilderStateStore from '../use-builder-state-store'; export const Settings: React.FC = () => { - const { settings, selectedNodeId, query } = useEditor((state) => { + const { settings, selectedNodeId } = useEditor((state) => { const currentColumn = Array.from(state.events.selected) .map((id) => state.nodes[id]) .find((node) => node && node.data.name === 'Column'); diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/_sidebar/Toolbox.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/_sidebar/Toolbox.tsx index df306542b..3f9e77cc7 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/_sidebar/Toolbox.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/_sidebar/Toolbox.tsx @@ -1,6 +1,6 @@ -import { Element, NodeTree, useEditor, WithoutPrivateActions } from '@craftjs/core'; +import { Element } from '@craftjs/core'; import { Button as AntButton } from 'antd'; -import { ReactNode } from 'react'; +import React, { ReactNode, useContext } from 'react'; import { LuFormInput, LuImage, LuTable, LuText } from 'react-icons/lu'; import { MdCheckBox, MdRadioButtonChecked, MdTitle, MdOutlineCheck } from 'react-icons/md'; @@ -22,6 +22,7 @@ import { } from '../elements'; import { createPortal } from 'react-dom'; +import BuilderContext from '../BuilderContext'; type CreationButtonProps = React.PropsWithChildren<{ title: string; @@ -29,9 +30,7 @@ type CreationButtonProps = React.PropsWithChildren<{ }>; const CreationButton: React.FC = ({ children, title, icon }) => { - const { editingEnabled } = useEditor((state) => { - return { editingEnabled: state.options.enabled }; - }); + const { editingEnabled } = useContext(BuilderContext); const id = `create-${title}-button`; diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/CheckboxOrRadioGroup.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/CheckboxOrRadioGroup.tsx index 1ecc55f79..f4bb38d3f 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/CheckboxOrRadioGroup.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/CheckboxOrRadioGroup.tsx @@ -1,4 +1,4 @@ -import { useEffect, useId, useMemo, useState } from 'react'; +import { useContext, useEffect, useId, useMemo, useState } from 'react'; import { Divider, Input, MenuProps, Select, Space, Tooltip } from 'antd'; import { InfoCircleOutlined } from '@ant-design/icons'; @@ -20,6 +20,7 @@ import { WithRequired } from '@/lib/typescript-utils'; import { SettingOutlined, EditOutlined } from '@ant-design/icons'; import { createPortal } from 'react-dom'; +import BuilderContext from '../BuilderContext'; const checkboxValueHint = 'This will be the value that is added to the variable associated with this group when the checkbox is checked at the time the form is submitted.'; @@ -63,10 +64,13 @@ const CheckboxOrRadioButton: React.FC = ({ const [hovered, setHovered] = useState(false); const [textEditing, setTextEditing] = useState(false); + const { editingEnabled } = useContext(BuilderContext); + return ( <> = ({ show={hovered && !textEditing} onHide={() => setHovered(false)} controls={[ - { + editingEnabled && { icon: setTextEditing(true)} />, key: 'edit', }, @@ -123,9 +127,7 @@ const CheckBoxOrRadioGroup: UserComponent = ({ variable = 'test', data, }) => { - const { query, editingEnabled } = useEditor((state) => ({ - editingEnabled: state.options.enabled, - })); + const { query } = useEditor(); const [editTarget, setEditTarget] = useState(); const [hoveredAction, setHoveredAction] = useState(); @@ -151,6 +153,8 @@ const CheckBoxOrRadioGroup: UserComponent = ({ } }, [isSelected]); + const { editingEnabled } = useContext(BuilderContext); + const handleLabelEdit = (index: number, text: string) => { if (!editingEnabled) return; setProp((props: CheckBoxOrRadioGroupProps) => { diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Column.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Column.tsx index 0889d2e0f..efa5fc34a 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Column.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Column.tsx @@ -1,9 +1,10 @@ import { UserComponent, useNode } from '@craftjs/core'; import { useDraggable } from '@dnd-kit/core'; -import { useRef } from 'react'; +import { useContext, useRef } from 'react'; import { createPortal } from 'react-dom'; import { useFrame } from 'react-frame-component'; import useBuilderStateStore from '../use-builder-state-store'; +import BuilderContext from '../BuilderContext'; /** * This component wraps every editor element provides drag handling and some styling */ @@ -24,6 +25,7 @@ const Column: UserComponent> = ({ })); const dragBlockers = useBuilderStateStore((state) => state.dragBlockers); + const { editingEnabled } = useContext(BuilderContext); const ref = useRef(); const frame = useFrame(); @@ -35,7 +37,7 @@ const Column: UserComponent> = ({ isDragging, } = useDraggable({ id: nodeId, - disabled: fixed || !!dragBlockers.length, + disabled: fixed || !!dragBlockers.length || !editingEnabled, }); return ( diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Container.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Container.tsx index 7a85e90e7..f66ba48e2 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Container.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Container.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { InputNumber, ColorPicker, Empty } from 'antd'; -import { UserComponent, useEditor, useNode } from '@craftjs/core'; +import { UserComponent, useNode } from '@craftjs/core'; import { useDroppable } from '@dnd-kit/core'; @@ -70,7 +70,6 @@ export const ContainerSettings = () => { borderThickness: node.data.props.borderThickness, borderColor: node.data.props.borderColor, })); - const { editingEnabled } = useEditor((state) => ({ editingEnabled: state.options.enabled })); return ( <> @@ -81,7 +80,6 @@ export const ContainerSettings = () => { min={0} addonAfter="px" value={padding} - disabled={!editingEnabled} onChange={(val) => setProp((props: ContainerProps) => { props.padding = val; @@ -96,7 +94,6 @@ export const ContainerSettings = () => { control={ setProp((props: ContainerProps) => { props.background = val; @@ -112,7 +109,6 @@ export const ContainerSettings = () => { min={0} addonAfter="px" value={borderThickness} - disabled={!editingEnabled} onChange={(val) => setProp((props: ContainerProps) => { props.borderThickness = val; @@ -127,7 +123,6 @@ export const ContainerSettings = () => { control={ setProp((props: ContainerProps) => { props.borderColor = val; diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Image.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Image.tsx index 16f4147fc..4622a0d8c 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Image.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Image.tsx @@ -2,13 +2,14 @@ import { useEditor, useNode, UserComponent, Node } from '@craftjs/core'; import { InputNumber } from 'antd'; -import { useEffect, useRef, useState } from 'react'; +import { useContext, useEffect, useRef, useState } from 'react'; import { fallbackImage } from '../../image-selection-section'; import { useParams } from 'next/navigation'; import { useEnvironment } from '@/components/auth-can'; import { ContextMenu, Setting } from './utils'; import ImageUpload from '@/components/image-upload'; +import BuilderContext from '../BuilderContext'; import { EntityType } from '@/lib/helpers/fileManagerHelpers'; import { useFileManager } from '@/lib/useFileManager'; import { enableUseFileManager } from 'FeatureFlags'; @@ -59,7 +60,7 @@ export const EditImage: UserComponent = ({ src, reloadParam, width } return { isHovered: !!parent && parent.events.hovered }; }); - const { editingEnabled } = useEditor((state) => ({ editingEnabled: state.options.enabled })); + const { editingEnabled } = useContext(BuilderContext); const { fileUrl: imageUrl, @@ -208,7 +209,6 @@ export const ImageSettings = () => { width: node.data.props.width, dom: node.dom, })); - const { editingEnabled } = useEditor((state) => ({ editingEnabled: state.options.enabled })); const [currentWidth, setCurrentWidth] = useState(null); @@ -237,7 +237,7 @@ export const ImageSettings = () => { label="Width" control={ = ({ connectors: { connect }, actions: { setProp }, } = useNode(); - const { editingEnabled } = useEditor((state) => ({ editingEnabled: state.options.enabled })); + const { editingEnabled } = useContext(BuilderContext); const inputId = useId(); @@ -67,7 +68,7 @@ const Input: UserComponent = ({ onMouseEnter={() => setLabelHovered(true)} > setLabelHovered(false)} controls={[ { @@ -92,6 +93,7 @@ const Input: UserComponent = ({ { labelPosition: node.data.props.labelPosition, variable: node.data.props.variable, })); - const { editingEnabled } = useEditor((state) => ({ editingEnabled: state.options.enabled })); return ( <> @@ -136,7 +137,6 @@ export const InputSettings = () => { { value: 'email', label: 'E-Mail' }, ]} value={type} - disabled={!editingEnabled} onChange={(val) => setProp((props: InputProps) => { props.type = val; @@ -156,7 +156,6 @@ export const InputSettings = () => { { value: 'none', label: 'Hidden' }, ]} value={labelPosition} - disabled={!editingEnabled} onChange={(val) => setProp((props: InputProps) => { props.labelPosition = val; diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Table.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Table.tsx index 09af415e9..c73486b1b 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Table.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Table.tsx @@ -15,8 +15,9 @@ import cn from 'classnames'; import EditableText from '../_utils/EditableText'; import { ContextMenu, MenuItemFactoryFactory, Overlay, SidebarButtonFactory } from './utils'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; import { createPortal } from 'react-dom'; +import BuilderContext from '../BuilderContext'; const defaultHeaderContent = 'Header Cell'; @@ -51,6 +52,8 @@ const TableCell: React.FC< const [hovered, setHovered] = useState(false); const [textEditing, setTextEditing] = useState(false); + const { editingEnabled } = useContext(BuilderContext); + return React.createElement( type, { @@ -64,7 +67,7 @@ const TableCell: React.FC< onMouseEnter: () => setHovered(true), }, setHovered(false)} controls={[ { @@ -138,9 +141,7 @@ const Table: UserComponent = ({ [defaultContent, defaultContent], ], }) => { - const { query, editingEnabled } = useEditor((state) => ({ - editingEnabled: state.options.enabled, - })); + const { query } = useEditor(); const { connectors: { connect }, @@ -162,6 +163,8 @@ const Table: UserComponent = ({ } }, [isSelected]); + const { editingEnabled } = useContext(BuilderContext); + const addRow = (index: number) => { if (!editingEnabled) return; setProp((props: TableProps) => { diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Text.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Text.tsx index 524584886..f9dea50e8 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Text.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/Text.tsx @@ -4,7 +4,8 @@ import { EditOutlined } from '@ant-design/icons'; import EditableText from '../_utils/EditableText'; import { ContextMenu, Overlay } from './utils'; -import { useState } from 'react'; +import { useContext, useState } from 'react'; +import BuilderContext from '../BuilderContext'; type TextProps = { text?: string; @@ -18,6 +19,8 @@ const Text: UserComponent = ({ text = '' }) => { const [hovered, setHovered] = useState(false); const [textEditing, setTextEditing] = useState(false); + const { editingEnabled } = useContext(BuilderContext); + return (
= ({ text = '' }) => { }} > setHovered(false)} controls={[{ key: 'edit', icon: setTextEditing(true)} /> }]} > diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/utils.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/utils.tsx index 2f6528796..85d118456 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/utils.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/processes/[processId]/_user-task-builder/elements/utils.tsx @@ -1,10 +1,20 @@ -import React, { ReactElement, ReactNode, useEffect, useId, useMemo, useState } from 'react'; +import React, { + ReactElement, + ReactNode, + useContext, + useEffect, + useId, + useMemo, + useState, +} from 'react'; import { createPortal } from 'react-dom'; import { Button, Menu, MenuProps } from 'antd'; import { useDndContext } from '@dnd-kit/core'; import useBuilderStateStore from '../use-builder-state-store'; +import { truthyFilter } from '@/lib/typescript-utils'; +import BuilderContext from '../BuilderContext'; export const Setting: React.FC<{ label: string; @@ -13,7 +23,9 @@ export const Setting: React.FC<{ }> = ({ label, control, style = {} }) => { const id = useId(); - const clonedControl = React.cloneElement(control, { id }); + const { editingEnabled } = useContext(BuilderContext); + + const clonedControl = React.cloneElement(control, { id, disabled: !editingEnabled }); return (
@@ -36,6 +48,8 @@ type ContextMenuProps = React.PropsWithChildren<{ export const ContextMenu: React.FC = ({ children, menu, onClose }) => { const [menuPosition, setMenuPosition] = useState<{ top: number; left: number }>(); + const { editingEnabled } = useContext(BuilderContext); + const id = useId(); const blockDragging = useBuilderStateStore((state) => state.blockDragging); const unblockDragging = useBuilderStateStore((state) => state.unblockDragging); @@ -96,7 +110,8 @@ export const ContextMenu: React.FC = ({ children, menu, onClos return ( <> - {position && + {editingEnabled && + position && createPortal( = ({ children, menu, onClos type OverlayProps = React.PropsWithChildren<{ show: boolean; onHide: () => void; - controls: { icon: ReactNode; key: string }[]; + controls: ({ icon: ReactNode; key: string } | undefined | false)[]; }>; export const Overlay: React.FC = ({ show, onHide, controls, children }) => { @@ -148,7 +163,7 @@ export const Overlay: React.FC = ({ show, onHide, controls, childr <> {show && !active && (
e.stopPropagation()}> - {controls.map(({ icon, key }) => ( + {controls.filter(truthyFilter).map(({ icon, key }) => (
{icon}
@@ -177,9 +192,11 @@ function SidebarButton({ onClick, onHovered, }: SidebarButtonProps) { + const { editingEnabled } = useContext(BuilderContext); + return (