diff --git a/src/components/Forms/Fields/MultiArrayField.tsx b/src/components/Forms/Fields/MultiArrayField.tsx new file mode 100644 index 00000000..a49fa426 --- /dev/null +++ b/src/components/Forms/Fields/MultiArrayField.tsx @@ -0,0 +1,90 @@ +import { AddIcon, ArrowForwardIcon, CloseIcon } from '@chakra-ui/icons'; +import { Button, Flex, FormControl, FormLabel, Input } from '@chakra-ui/react'; +import { useState } from 'react'; +import { + RegisterOptions, + useFieldArray, + useFormContext, +} from 'react-hook-form'; + +type FieldConfig = { + name: string; // es: "left" o "right" + placeholder?: string; // testo nell'input + constraints?: RegisterOptions; +}; + +const MultiFieldArray = ({ + label, + name, + fieldsConfig, +}: { + label: string; + name: string; // es: "pairs" o "translations" + fieldsConfig: FieldConfig[]; +}) => { + const { register, control } = useFormContext(); + const { fields, append, remove } = useFieldArray({ + control, + name, + }); + + // stato temporaneo per nuovi valori + const [newValues, setNewValues] = useState>( + Object.fromEntries(fieldsConfig.map((f) => [f.name, ''])) + ); + + const handleChange = (field: string, value: string) => { + setNewValues((prev) => ({ ...prev, [field]: value })); + }; + + const handleAdd = () => { + if (Object.values(newValues).every((v) => !v)) return; + append(newValues); + setNewValues(Object.fromEntries(fieldsConfig.map((f) => [f.name, '']))); + }; + + return ( + + {label} + + {fields.map((field, index) => ( + + {fieldsConfig.map((fc, i) => ( + + + {fieldsConfig.length === 2 && i === 0 && } + + ))} + + + ))} + + + {fieldsConfig.map((fc, i) => ( + + handleChange(fc.name, e.target.value)} + placeholder={fc.placeholder} + borderColor="grey" + /> + {fieldsConfig.length === 2 && i === 0 && } + + ))} + + + + ); +}; + +export default MultiFieldArray; diff --git a/src/components/LateralMenu/LateralMenu.tsx b/src/components/LateralMenu/LateralMenu.tsx index a6b78024..3240a31c 100644 --- a/src/components/LateralMenu/LateralMenu.tsx +++ b/src/components/LateralMenu/LateralMenu.tsx @@ -76,6 +76,7 @@ const listImplementedNodes = [ 'codingQuestionNode', 'CollaborativeModelingNode', 'UMLModelingNode', + 'CircuitNode', ]; export type LateralMenuProps = { isOpen: boolean; diff --git a/src/components/Properties/Nodes/CircuitNodeProperties.tsx b/src/components/Properties/Nodes/CircuitNodeProperties.tsx new file mode 100644 index 00000000..aa39b477 --- /dev/null +++ b/src/components/Properties/Nodes/CircuitNodeProperties.tsx @@ -0,0 +1,25 @@ +import MultiFieldArray from '../../Forms/Fields/MultiArrayField'; +import TextField from '../../Forms/Fields/TextField'; +import NodeProperties from './NodeProperties'; + +const CircuitNodeProperties = () => { + return ( + <> + + + + + ); +}; + +export default CircuitNodeProperties; diff --git a/src/components/ReactFlowNode/ReactFlowCircuitNode/ReactFlowCircuitNode.tsx b/src/components/ReactFlowNode/ReactFlowCircuitNode/ReactFlowCircuitNode.tsx new file mode 100644 index 00000000..c40c1bca --- /dev/null +++ b/src/components/ReactFlowNode/ReactFlowCircuitNode/ReactFlowCircuitNode.tsx @@ -0,0 +1,56 @@ +import { useTheme } from '@fluentui/react'; +import { Handle, Position } from 'reactflow'; +import icon from '../../../public/circuitIcon.png'; +import useStore from '../../../store'; +import { CircuitNode } from '../../../types/polyglotElements'; +import Card from '../../Card/Card'; +import { ReactFlowNodeProps } from '../ReactFlowNode'; + +type ReactFlowCircuitNodeProps = ReactFlowNodeProps & CircuitNode; + +const ReactFlowMultipleChoiceQuestionNode = ({ + id, +}: ReactFlowCircuitNodeProps) => { + const [onConnect, label] = useStore((state) => [ + state.onConnect, + state.nodeMap.get(id)?.title, + ]); + const theme = useTheme(); + + return ( + + + {label} + + + + ); +}; + +export default ReactFlowMultipleChoiceQuestionNode; diff --git a/src/components/ReactFlowNode/ReactFlowTrueFalseNode/ReactFlowTrueFalseNode.tsx b/src/components/ReactFlowNode/ReactFlowTrueFalseNode/ReactFlowTrueFalseNode.tsx index d86905c4..56c36802 100644 --- a/src/components/ReactFlowNode/ReactFlowTrueFalseNode/ReactFlowTrueFalseNode.tsx +++ b/src/components/ReactFlowNode/ReactFlowTrueFalseNode/ReactFlowTrueFalseNode.tsx @@ -2,16 +2,13 @@ import { useTheme } from '@fluentui/react'; import { Handle, Position } from 'reactflow'; import icon from '../../../public/trueFalse_icon.png'; import useStore from '../../../store'; -import { MultipleChoiceQuestionNode } from '../../../types/polyglotElements'; +import { CircuitNode } from '../../../types/polyglotElements'; import Card from '../../Card/Card'; import { ReactFlowNodeProps } from '../ReactFlowNode'; -type ReactFlowMultipleChoiceQuestionNodeProps = ReactFlowNodeProps & - MultipleChoiceQuestionNode; +type ReactFlowTrueFalseNodeProps = ReactFlowNodeProps & CircuitNode; -const ReactFlowMultipleChoiceQuestionNode = ({ - id, -}: ReactFlowMultipleChoiceQuestionNodeProps) => { +const ReactFlowTrueFalseNode = ({ id }: ReactFlowTrueFalseNodeProps) => { const [onConnect, label] = useStore((state) => [ state.onConnect, state.nodeMap.get(id)?.title, @@ -54,4 +51,4 @@ const ReactFlowMultipleChoiceQuestionNode = ({ ); }; -export default ReactFlowMultipleChoiceQuestionNode; +export default ReactFlowTrueFalseNode; diff --git a/src/components/ReactFlowNode/index.ts b/src/components/ReactFlowNode/index.ts index 78d73e5d..c8fb8ef1 100644 --- a/src/components/ReactFlowNode/index.ts +++ b/src/components/ReactFlowNode/index.ts @@ -3,6 +3,7 @@ export { default as ReactFlowAnalyzingPlottingDataNode } from './ReactFlowAnalyz export { default as ReactFlowBrainstormingNode } from './ReactFlowBrainstormingNode/ReactFlowBrainstormingNode'; export { default as ReactFlowCalculationNode } from './ReactFlowCalculationNode/ReactFlowCalculationNode'; export { default as ReactFlowCasesEvaluationNode } from './ReactFlowCasesEvaluationNode/ReactFlowCasesEvaluationNode'; +export { default as ReactFlowCircuitNode } from './ReactFlowCircuitNode/ReactFlowCircuitNode'; export { default as ReactFlowCloseEndedQuestionNode } from './ReactFlowCloseEndedQuestionNode/ReactFlowCloseEndedQuestionNode'; export { default as ReactFlowCodingQuestionNode } from './ReactFlowCodingQuestionNode/ReactFlowCodingQuestionNode'; export { default as ReactFlowCollaborativeModelingNode } from './ReactFlowCollaborativeModelingNode/ReactFlowCollaborativeModelingNode'; diff --git a/src/data/abstractExample.ts b/src/data/abstractExample.ts index bd0a979f..589f843d 100644 --- a/src/data/abstractExample.ts +++ b/src/data/abstractExample.ts @@ -89,8 +89,8 @@ const subFlow = new Map(); target: ids[1], type: 'passFailEdge', markerEnd: { - color: 'green', type: MarkerType.Arrow, + color: 'grey', // or any color you prefer width: 25, height: 25, }, diff --git a/src/data/exampleData.ts b/src/data/exampleData.ts index 5b10fecc..0491e2a2 100644 --- a/src/data/exampleData.ts +++ b/src/data/exampleData.ts @@ -155,7 +155,7 @@ const exampleFlows = new Map(); target: ids[1], type: 'passFailEdge', markerEnd: { - color: 'green', + color: 'grey', type: MarkerType.Arrow, width: 25, height: 25, diff --git a/src/public/circuitIcon.png b/src/public/circuitIcon.png new file mode 100644 index 00000000..7bb123d9 Binary files /dev/null and b/src/public/circuitIcon.png differ diff --git a/src/types/polyglotElements/nodes/CasesEvaluationNode.ts b/src/types/polyglotElements/nodes/CasesEvaluationNode.ts index 6c72c9d5..5fe58d26 100644 --- a/src/types/polyglotElements/nodes/CasesEvaluationNode.ts +++ b/src/types/polyglotElements/nodes/CasesEvaluationNode.ts @@ -31,44 +31,4 @@ polyglotNodeComponentMapping.registerMapping({ link: '', uploadLearner: false, }, - transformData: (node) => { - const oldData = node.data as CasesEvaluationNodeData; - - const data = { - ...oldData, - }; - /* - const challengeSetup: ChallengeSetup[] = [ - ` -using Polyglot.Interactive; -var kernel = Kernel.Root.FindKernelByName("multiplechoice") as MultipleChoiceKernel; -kernel.Options = new HashSet { ${data.choices - .map((_, i) => `"${i + 1}"`) - .join(', ')} }; -`, - ]; - const challengeContent: ChallengeContent[] = [ - { - type: 'multiplechoice', - content: '', - priority: 1, - }, - { - type: 'markdown', - content: - data.question + - data.choices.map((value, index) => '\n' + (index + 1) + '. ' + value), - priority: 0, - }, - ]; -*/ - return { - ...node, - data, - runtimeData: { - //challengeSetup, - //challengeContent, - }, - }; - }, }); diff --git a/src/types/polyglotElements/nodes/CircuitNode.ts b/src/types/polyglotElements/nodes/CircuitNode.ts new file mode 100644 index 00000000..da71f7aa --- /dev/null +++ b/src/types/polyglotElements/nodes/CircuitNode.ts @@ -0,0 +1,29 @@ +import CircuitNodeProperties from '../../../components/Properties/Nodes/CircuitNodeProperties'; +import { ReactFlowCircuitNode } from '../../../components/ReactFlowNode'; +import icon from '../../../public/circuitIcon.png'; +import { polyglotNodeComponentMapping } from '../elementMapping'; +import { defaultPolyglotNodeData, NodeData, PolyglotNode } from './Node'; + +export type CircuitData = NodeData & { + instructions: string; + pinsList: { pin: string; value: string }[]; +}; + +export type CircuitNode = PolyglotNode & { + type: 'CircuitNode'; + data: CircuitData; +}; + +polyglotNodeComponentMapping.registerMapping({ + elementType: 'CircuitNode', + name: 'Circuit', + icon: icon.src, + group: 'apply_assessment', + propertiesComponent: CircuitNodeProperties, + elementComponent: ReactFlowCircuitNode, + defaultData: { + ...defaultPolyglotNodeData, + pinsList: [], + instructions: '', + }, +}); diff --git a/src/types/polyglotElements/nodes/CreateKeywordsListNode.ts b/src/types/polyglotElements/nodes/CreateKeywordsListNode.ts index c172bff1..b5a6e41b 100644 --- a/src/types/polyglotElements/nodes/CreateKeywordsListNode.ts +++ b/src/types/polyglotElements/nodes/CreateKeywordsListNode.ts @@ -31,23 +31,4 @@ polyglotNodeComponentMapping.registerMapping({ instructions: '', ...defaultPolyglotNodeData, }, - transformData: (node) => { - const oldData = node as CreateKeywordsListNode; - - const challengeSetup: ChallengeSetup[] = []; - const challengeContent: ChallengeContent[] = [ - { - type: 'markdown', - content: oldData.data?.text, - }, - ]; - - return { - ...node, - runtimeData: { - challengeSetup, - challengeContent, - }, - }; - }, }); diff --git a/src/types/polyglotElements/nodes/FindSolutionNode.ts b/src/types/polyglotElements/nodes/FindSolutionNode.ts index 4cccf190..a8d29717 100644 --- a/src/types/polyglotElements/nodes/FindSolutionNode.ts +++ b/src/types/polyglotElements/nodes/FindSolutionNode.ts @@ -35,23 +35,4 @@ polyglotNodeComponentMapping.registerMapping({ uploadLearner: false, ...defaultPolyglotNodeData, }, - transformData: (node) => { - const oldData = node as FindSolutionNode; - - const challengeSetup: ChallengeSetup[] = []; - const challengeContent: ChallengeContent[] = [ - { - type: 'markdown', - content: oldData.data?.text, - }, - ]; - - return { - ...node, - runtimeData: { - challengeSetup, - challengeContent, - }, - }; - }, }); diff --git a/src/types/polyglotElements/nodes/ImageEvaluationNode.ts b/src/types/polyglotElements/nodes/ImageEvaluationNode.ts index 84d496a3..5855701e 100644 --- a/src/types/polyglotElements/nodes/ImageEvaluationNode.ts +++ b/src/types/polyglotElements/nodes/ImageEvaluationNode.ts @@ -32,53 +32,4 @@ polyglotNodeComponentMapping.registerMapping({ isAnswerCorrect: [], question: '', }, - transformData: (node) => { - const oldData = node.data as ImageEvaluationNodeData; - - const data = { - ...oldData, - correctAnswers: zip(oldData?.answers, oldData?.isAnswerCorrect).reduce( - (acc, { first, second }) => { - if (second) { - acc.push(first); - } - return acc; - }, - [] as string[] - ), - }; - /* - const challengeSetup: ChallengeSetup[] = [ - ` -using Polyglot.Interactive; -var kernel = Kernel.Root.FindKernelByName("multiplechoice") as MultipleChoiceKernel; -kernel.Options = new HashSet { ${data.choices - .map((_, i) => `"${i + 1}"`) - .join(', ')} }; -`, - ]; - const challengeContent: ChallengeContent[] = [ - { - type: 'multiplechoice', - content: '', - priority: 1, - }, - { - type: 'markdown', - content: - data.question + - data.choices.map((value, index) => '\n' + (index + 1) + '. ' + value), - priority: 0, - }, - ]; -*/ - return { - ...node, - data, - runtimeData: { - //challengeSetup, - //challengeContent, - }, - }; - }, }); diff --git a/src/types/polyglotElements/nodes/InnovationPitchNode.ts b/src/types/polyglotElements/nodes/InnovationPitchNode.ts index b482baba..870cecbf 100644 --- a/src/types/polyglotElements/nodes/InnovationPitchNode.ts +++ b/src/types/polyglotElements/nodes/InnovationPitchNode.ts @@ -31,44 +31,4 @@ polyglotNodeComponentMapping.registerMapping({ link: '', uploadLearner: false, }, - transformData: (node) => { - const oldData = node.data as InnovationPitchNodeData; - - const data = { - ...oldData, - }; - /* - const challengeSetup: ChallengeSetup[] = [ - ` -using Polyglot.Interactive; -var kernel = Kernel.Root.FindKernelByName("multiplechoice") as MultipleChoiceKernel; -kernel.Options = new HashSet { ${data.choices - .map((_, i) => `"${i + 1}"`) - .join(', ')} }; -`, - ]; - const challengeContent: ChallengeContent[] = [ - { - type: 'multiplechoice', - content: '', - priority: 1, - }, - { - type: 'markdown', - content: - data.question + - data.choices.map((value, index) => '\n' + (index + 1) + '. ' + value), - priority: 0, - }, - ]; -*/ - return { - ...node, - data, - runtimeData: { - //challengeSetup, - //challengeContent, - }, - }; - }, }); diff --git a/src/types/polyglotElements/nodes/MemoriseKeywordsListNode.ts b/src/types/polyglotElements/nodes/MemoriseKeywordsListNode.ts index 1a0010b1..9bcadb10 100644 --- a/src/types/polyglotElements/nodes/MemoriseKeywordsListNode.ts +++ b/src/types/polyglotElements/nodes/MemoriseKeywordsListNode.ts @@ -33,23 +33,4 @@ polyglotNodeComponentMapping.registerMapping({ keywords: [], ...defaultPolyglotNodeData, }, - transformData: (node) => { - const oldData = node as MemoriseKeywordsListNode; - - const challengeSetup: ChallengeSetup[] = []; - const challengeContent: ChallengeContent[] = [ - { - type: 'markdown', - content: oldData.data?.text, - }, - ]; - - return { - ...node, - runtimeData: { - challengeSetup, - challengeContent, - }, - }; - }, }); diff --git a/src/types/polyglotElements/nodes/MindMapNode.ts b/src/types/polyglotElements/nodes/MindMapNode.ts index 57cda580..87804262 100644 --- a/src/types/polyglotElements/nodes/MindMapNode.ts +++ b/src/types/polyglotElements/nodes/MindMapNode.ts @@ -35,23 +35,4 @@ polyglotNodeComponentMapping.registerMapping({ uploadLearner: false, ...defaultPolyglotNodeData, }, - transformData: (node) => { - const oldData = node as MindMapNode; - - const challengeSetup: ChallengeSetup[] = []; - const challengeContent: ChallengeContent[] = [ - { - type: 'markdown', - content: oldData.data?.text, - }, - ]; - - return { - ...node, - runtimeData: { - challengeSetup, - challengeContent, - }, - }; - }, }); diff --git a/src/types/polyglotElements/nodes/ProblemSolvingNode.ts b/src/types/polyglotElements/nodes/ProblemSolvingNode.ts index a1811ee9..40ec67e0 100644 --- a/src/types/polyglotElements/nodes/ProblemSolvingNode.ts +++ b/src/types/polyglotElements/nodes/ProblemSolvingNode.ts @@ -35,23 +35,4 @@ polyglotNodeComponentMapping.registerMapping({ uploadLearner: false, ...defaultPolyglotNodeData, }, - transformData: (node) => { - const oldData = node as ProblemSolvingNode; - - const challengeSetup: ChallengeSetup[] = []; - const challengeContent: ChallengeContent[] = [ - { - type: 'markdown', - content: oldData.data?.text, - }, - ]; - - return { - ...node, - runtimeData: { - challengeSetup, - challengeContent, - }, - }; - }, }); diff --git a/src/types/polyglotElements/nodes/PromptEngineeringNode.ts b/src/types/polyglotElements/nodes/PromptEngineeringNode.ts index 2200958d..7f27b435 100644 --- a/src/types/polyglotElements/nodes/PromptEngineeringNode.ts +++ b/src/types/polyglotElements/nodes/PromptEngineeringNode.ts @@ -40,29 +40,4 @@ int main() { language: 'csharp', ...defaultPolyglotNodeData, }, - transformData: (node) => { - const oldData = node as PromptEngineeringNode; - - const challengeSetup: ChallengeSetup[] = []; - const challengeContent: ChallengeContent[] = [ - { - type: 'markdown', - content: oldData.data?.question, - priority: 0, - }, - { - type: oldData.data?.language, - content: oldData.data?.codeTemplate, - priority: 1, - }, - ]; - - return { - ...node, - runtimeData: { - challengeSetup, - challengeContent, - }, - }; - }, }); diff --git a/src/types/polyglotElements/nodes/TrueFalseNode.ts b/src/types/polyglotElements/nodes/TrueFalseNode.ts index c8b72de2..fc4913bb 100644 --- a/src/types/polyglotElements/nodes/TrueFalseNode.ts +++ b/src/types/polyglotElements/nodes/TrueFalseNode.ts @@ -12,12 +12,12 @@ export type TrueFalseNodeData = NodeData & { positivePoints?: number; }; -export type TrueFalseNodeNode = PolyglotNode & { +export type TrueFalseNode = PolyglotNode & { type: 'TrueFalseNode'; data: TrueFalseNodeData; }; -polyglotNodeComponentMapping.registerMapping({ +polyglotNodeComponentMapping.registerMapping({ elementType: 'TrueFalseNode', name: 'True False', icon: icon.src, diff --git a/src/types/polyglotElements/nodes/index.ts b/src/types/polyglotElements/nodes/index.ts index e4595a9a..c7b2cbae 100644 --- a/src/types/polyglotElements/nodes/index.ts +++ b/src/types/polyglotElements/nodes/index.ts @@ -3,6 +3,7 @@ export * from './AnalyzingPlottingDataNode'; export * from './BrainstormingNode'; export * from './CalculationNode'; export * from './CasesEvaluationNode'; +export * from './CircuitNode'; export * from './CloseEndedQuestionNode'; export * from './CodingQuestionNode'; export * from './CollaborativeModelingNode'; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 2958e845..18088934 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -70,8 +70,13 @@ export const createNewDefaultPolyglotFlow = (): PolyglotFlow => { export const createNewDefaultPolyglotNode: ( pos: { x: number; y: number }, - nodeType?: string -) => any = (pos, nodeType = 'multipleChoiceQuestionNode') => { + nodeType?: string, + platform?: string +) => any = ( + pos, + nodeType = 'multipleChoiceQuestionNode', + platform = 'WebApp' +) => { const id = UUIDv4(); return { _id: id,