Loading editor/src/components/Select/index.tsx +3 −0 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ type Props<T extends string | number> = { buttonClassName?: string fill?: boolean placeholder?: string disabled?: boolean } const SharedSelect = <T extends string | number>({ Loading @@ -66,6 +67,7 @@ const SharedSelect = <T extends string | number>({ buttonClassName, fill = false, placeholder, disabled, }: Props<T>) => ( <Select<OptionProps<T>> items={items} Loading @@ -78,6 +80,7 @@ const SharedSelect = <T extends string | number>({ onItemSelect={onItemSelect} className={selectClassname} fill={fill} disabled={disabled} popoverProps={{ enforceFocus: false, autoFocus: false, Loading editor/src/components/Visualisation/InjectPatterns/AssignPatterns.tsx 0 → 100644 +149 −0 Original line number Diff line number Diff line import { Button, Popover } from '@blueprintjs/core' import { css } from '@emotion/css' import { notify } from '@inject/shared' import type { Dispatch, SetStateAction } from 'react' import { useEffect, useState, type FC } from 'react' import { exportInjectPattern } from '../../../importExport' import type { TreeNode } from '../../../indexeddb/types' import type { InjectPattern } from '../../../routes/create/tree-view' import { getNodeName } from '../Node/utils' import { getSelectedRec } from '../utils' const contentWrapper = css` min-width: 18rem; max-height: 20rem; padding: 1rem; overflow-y: auto; max-width: 25rem; z-index: 1; display: flex; flex-direction: column; gap: 0.2rem; ` const buttonWrapper = css` padding-top: 0.5rem; display: flex; flex-direction: column; gap: 0.5rem; ` type AssignPatternsProps = { onClick: () => void selected: boolean injectPattern: InjectPattern setInjectPattern: Dispatch<SetStateAction<InjectPattern>> selectedNodes: TreeNode[] setSelectedNodes: Dispatch<SetStateAction<TreeNode[]>> } export const AssignPatterns: FC<AssignPatternsProps> = ({ onClick, selected, injectPattern, setInjectPattern, setSelectedNodes, selectedNodes, }) => { const [opened, setOpened] = useState(false) const handleDownload = async () => { if (injectPattern.begin && injectPattern.end) { await exportInjectPattern(selectedNodes, injectPattern.begin) } } useEffect(() => { if (!selected) { setOpened(false) } }, [selected]) useEffect(() => { if (injectPattern.begin && injectPattern.end) { const children = getSelectedRec(injectPattern.begin, injectPattern.end.id) if (children.length <= 1) { notify('Wrongly selected begin and end', JSON.stringify(''), { intent: 'warning', }) } else { setSelectedNodes(children) } } else { setSelectedNodes([]) } }, [injectPattern.begin, injectPattern.end, setSelectedNodes]) return ( <div onClick={onClick}> <Popover fill minimal position={'bottom-left'} autoFocus={false} enforceFocus={false} captureDismiss={false} content={ <> {selected && ( <div className={contentWrapper}> <Button text={ injectPattern.begin ? getNodeName(injectPattern.begin) : 'Select a begin' } fill active={injectPattern.beginSelect} intent={injectPattern.beginSelect ? 'success' : 'none'} onClick={() => setInjectPattern({ ...injectPattern, beginSelect: true }) } /> <Button text={ injectPattern.end ? getNodeName(injectPattern.end) : 'Select a end' } active={!injectPattern.beginSelect} intent={!injectPattern.beginSelect ? 'success' : 'none'} fill onClick={() => setInjectPattern({ ...injectPattern, beginSelect: false }) } /> <div className={buttonWrapper}> <Button intent={'primary'} disabled={!injectPattern.begin || !injectPattern.end} onClick={handleDownload} icon='download' alignText='left' > Export Inject Pattern </Button> </div> </div> )} </> } isOpen={opened} > <Button title='Download Inject Pattern' active={opened} icon='download' alignText='left' fill minimal={!selected} onClick={() => { setOpened(prev => !prev) }} > Inject Pattern </Button> </Popover> </div> ) } editor/src/components/Visualisation/Node/EditorNode.tsx +2 −23 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import { CONTENT_HEIGHT, getControlLinks, getFilteredQuestions, getNodeName, getQuestionLinks, isControlUsed, QUESTION_HEIGHT, Loading Loading @@ -266,28 +267,6 @@ const AdditionalNodeData: FC<{ return null } const getName = (node: TreeNode) => { switch (node.type) { case TreeNodeTypes.EMAIL_ADDRESS: { return node.data.address } case TreeNodeTypes.TOOL_RESPONSE: { return ( node.data.toolName + (node.data.param.length > 0 ? ` - ${node.data.param}` : '') ) } case TreeNodeTypes.INJECT: case TreeNodeTypes.QUESTIONNAIRE: case TreeNodeTypes.MILESTONE: { return node.data.display_name ?? node.data.name } case TreeNodeTypes.START: { return node.data.name } } } type EditorNodeProps = { node: HierarchyNode<TreeNode> nodeMap: Map<string, HierarchyNode<TreeNode>> Loading @@ -301,7 +280,7 @@ export const EditorNode: FC<EditorNodeProps> = ({ selected, nodeMap, }) => { const name = getName(node.data) const name = getNodeName(node.data) let currentY = NODE_PADDING_Y Loading editor/src/components/Visualisation/Node/utils.ts +22 −0 Original line number Diff line number Diff line Loading @@ -148,3 +148,25 @@ export const getFilteredQuestions = (questionnaire: Questionnaire) => { }) return result } export const getNodeName = (node: TreeNode) => { switch (node.type) { case TreeNodeTypes.EMAIL_ADDRESS: { return node.data.address } case TreeNodeTypes.TOOL_RESPONSE: { return ( node.data.toolName + (node.data.param.length > 0 ? ` - ${node.data.param}` : '') ) } case TreeNodeTypes.INJECT: case TreeNodeTypes.QUESTIONNAIRE: case TreeNodeTypes.MILESTONE: { return node.data.display_name ?? node.data.name } case TreeNodeTypes.START: { return node.data.name } } } editor/src/components/Visualisation/Objectives/AssignLearningActivity.tsx +59 −44 Original line number Diff line number Diff line Loading @@ -3,7 +3,7 @@ import { css } from '@emotion/css' import { notify } from '@inject/shared' import { useLiveQuery } from 'dexie-react-hooks' import type { FC } from 'react' import { useState } from 'react' import { useEffect, useState } from 'react' import { assignToLearningActivity, removeAssigned, Loading Loading @@ -38,12 +38,16 @@ type AssignLearningActivityProps = { selectedNodes: TreeNode[] onClose: () => void onUpdate: () => void onClick: () => void selected: boolean } export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({ selectedNodes, onClose, onUpdate, onClick, selected, }) => { const learningActivities = useLiveQuery(() => getAllExtendedActivities(), []) Loading @@ -56,6 +60,13 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({ const [selectedLa, setSelectedLa] = useState<OptionProps<string> | null>(null) const [opened, setOpened] = useState(false) useEffect(() => { if (!selected) { setOpened(false) setSelectedLa(null) } }, [selected]) const handleAssignSelected = async () => { try { if (!selectedLa) { Loading Loading @@ -92,7 +103,7 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({ } return ( <div> <div onClick={onClick}> <Popover fill minimal Loading @@ -101,6 +112,8 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({ enforceFocus={false} captureDismiss={false} content={ <> {selected && ( <div className={contentWrapper}> <div className={selectWrapper}> <SharedSelect Loading Loading @@ -144,6 +157,8 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({ </Button> </div> </div> )} </> } isOpen={opened} > Loading @@ -153,7 +168,7 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({ icon={'swap-horizontal'} alignText='left' fill minimal minimal={!selected} onClick={() => { setOpened(prev => !prev) setSelectedLa(null) Loading Loading
editor/src/components/Select/index.tsx +3 −0 Original line number Diff line number Diff line Loading @@ -56,6 +56,7 @@ type Props<T extends string | number> = { buttonClassName?: string fill?: boolean placeholder?: string disabled?: boolean } const SharedSelect = <T extends string | number>({ Loading @@ -66,6 +67,7 @@ const SharedSelect = <T extends string | number>({ buttonClassName, fill = false, placeholder, disabled, }: Props<T>) => ( <Select<OptionProps<T>> items={items} Loading @@ -78,6 +80,7 @@ const SharedSelect = <T extends string | number>({ onItemSelect={onItemSelect} className={selectClassname} fill={fill} disabled={disabled} popoverProps={{ enforceFocus: false, autoFocus: false, Loading
editor/src/components/Visualisation/InjectPatterns/AssignPatterns.tsx 0 → 100644 +149 −0 Original line number Diff line number Diff line import { Button, Popover } from '@blueprintjs/core' import { css } from '@emotion/css' import { notify } from '@inject/shared' import type { Dispatch, SetStateAction } from 'react' import { useEffect, useState, type FC } from 'react' import { exportInjectPattern } from '../../../importExport' import type { TreeNode } from '../../../indexeddb/types' import type { InjectPattern } from '../../../routes/create/tree-view' import { getNodeName } from '../Node/utils' import { getSelectedRec } from '../utils' const contentWrapper = css` min-width: 18rem; max-height: 20rem; padding: 1rem; overflow-y: auto; max-width: 25rem; z-index: 1; display: flex; flex-direction: column; gap: 0.2rem; ` const buttonWrapper = css` padding-top: 0.5rem; display: flex; flex-direction: column; gap: 0.5rem; ` type AssignPatternsProps = { onClick: () => void selected: boolean injectPattern: InjectPattern setInjectPattern: Dispatch<SetStateAction<InjectPattern>> selectedNodes: TreeNode[] setSelectedNodes: Dispatch<SetStateAction<TreeNode[]>> } export const AssignPatterns: FC<AssignPatternsProps> = ({ onClick, selected, injectPattern, setInjectPattern, setSelectedNodes, selectedNodes, }) => { const [opened, setOpened] = useState(false) const handleDownload = async () => { if (injectPattern.begin && injectPattern.end) { await exportInjectPattern(selectedNodes, injectPattern.begin) } } useEffect(() => { if (!selected) { setOpened(false) } }, [selected]) useEffect(() => { if (injectPattern.begin && injectPattern.end) { const children = getSelectedRec(injectPattern.begin, injectPattern.end.id) if (children.length <= 1) { notify('Wrongly selected begin and end', JSON.stringify(''), { intent: 'warning', }) } else { setSelectedNodes(children) } } else { setSelectedNodes([]) } }, [injectPattern.begin, injectPattern.end, setSelectedNodes]) return ( <div onClick={onClick}> <Popover fill minimal position={'bottom-left'} autoFocus={false} enforceFocus={false} captureDismiss={false} content={ <> {selected && ( <div className={contentWrapper}> <Button text={ injectPattern.begin ? getNodeName(injectPattern.begin) : 'Select a begin' } fill active={injectPattern.beginSelect} intent={injectPattern.beginSelect ? 'success' : 'none'} onClick={() => setInjectPattern({ ...injectPattern, beginSelect: true }) } /> <Button text={ injectPattern.end ? getNodeName(injectPattern.end) : 'Select a end' } active={!injectPattern.beginSelect} intent={!injectPattern.beginSelect ? 'success' : 'none'} fill onClick={() => setInjectPattern({ ...injectPattern, beginSelect: false }) } /> <div className={buttonWrapper}> <Button intent={'primary'} disabled={!injectPattern.begin || !injectPattern.end} onClick={handleDownload} icon='download' alignText='left' > Export Inject Pattern </Button> </div> </div> )} </> } isOpen={opened} > <Button title='Download Inject Pattern' active={opened} icon='download' alignText='left' fill minimal={!selected} onClick={() => { setOpened(prev => !prev) }} > Inject Pattern </Button> </Popover> </div> ) }
editor/src/components/Visualisation/Node/EditorNode.tsx +2 −23 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import { CONTENT_HEIGHT, getControlLinks, getFilteredQuestions, getNodeName, getQuestionLinks, isControlUsed, QUESTION_HEIGHT, Loading Loading @@ -266,28 +267,6 @@ const AdditionalNodeData: FC<{ return null } const getName = (node: TreeNode) => { switch (node.type) { case TreeNodeTypes.EMAIL_ADDRESS: { return node.data.address } case TreeNodeTypes.TOOL_RESPONSE: { return ( node.data.toolName + (node.data.param.length > 0 ? ` - ${node.data.param}` : '') ) } case TreeNodeTypes.INJECT: case TreeNodeTypes.QUESTIONNAIRE: case TreeNodeTypes.MILESTONE: { return node.data.display_name ?? node.data.name } case TreeNodeTypes.START: { return node.data.name } } } type EditorNodeProps = { node: HierarchyNode<TreeNode> nodeMap: Map<string, HierarchyNode<TreeNode>> Loading @@ -301,7 +280,7 @@ export const EditorNode: FC<EditorNodeProps> = ({ selected, nodeMap, }) => { const name = getName(node.data) const name = getNodeName(node.data) let currentY = NODE_PADDING_Y Loading
editor/src/components/Visualisation/Node/utils.ts +22 −0 Original line number Diff line number Diff line Loading @@ -148,3 +148,25 @@ export const getFilteredQuestions = (questionnaire: Questionnaire) => { }) return result } export const getNodeName = (node: TreeNode) => { switch (node.type) { case TreeNodeTypes.EMAIL_ADDRESS: { return node.data.address } case TreeNodeTypes.TOOL_RESPONSE: { return ( node.data.toolName + (node.data.param.length > 0 ? ` - ${node.data.param}` : '') ) } case TreeNodeTypes.INJECT: case TreeNodeTypes.QUESTIONNAIRE: case TreeNodeTypes.MILESTONE: { return node.data.display_name ?? node.data.name } case TreeNodeTypes.START: { return node.data.name } } }
editor/src/components/Visualisation/Objectives/AssignLearningActivity.tsx +59 −44 Original line number Diff line number Diff line Loading @@ -3,7 +3,7 @@ import { css } from '@emotion/css' import { notify } from '@inject/shared' import { useLiveQuery } from 'dexie-react-hooks' import type { FC } from 'react' import { useState } from 'react' import { useEffect, useState } from 'react' import { assignToLearningActivity, removeAssigned, Loading Loading @@ -38,12 +38,16 @@ type AssignLearningActivityProps = { selectedNodes: TreeNode[] onClose: () => void onUpdate: () => void onClick: () => void selected: boolean } export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({ selectedNodes, onClose, onUpdate, onClick, selected, }) => { const learningActivities = useLiveQuery(() => getAllExtendedActivities(), []) Loading @@ -56,6 +60,13 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({ const [selectedLa, setSelectedLa] = useState<OptionProps<string> | null>(null) const [opened, setOpened] = useState(false) useEffect(() => { if (!selected) { setOpened(false) setSelectedLa(null) } }, [selected]) const handleAssignSelected = async () => { try { if (!selectedLa) { Loading Loading @@ -92,7 +103,7 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({ } return ( <div> <div onClick={onClick}> <Popover fill minimal Loading @@ -101,6 +112,8 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({ enforceFocus={false} captureDismiss={false} content={ <> {selected && ( <div className={contentWrapper}> <div className={selectWrapper}> <SharedSelect Loading Loading @@ -144,6 +157,8 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({ </Button> </div> </div> )} </> } isOpen={opened} > Loading @@ -153,7 +168,7 @@ export const AssignLearningActivity: FC<AssignLearningActivityProps> = ({ icon={'swap-horizontal'} alignText='left' fill minimal minimal={!selected} onClick={() => { setOpened(prev => !prev) setSelectedLa(null) Loading