From 43d9d8294fb4764a180bda54a18567a4dbe16be6 Mon Sep 17 00:00:00 2001 From: Kasper Baun Date: Thu, 29 Feb 2024 21:33:20 +0100 Subject: [PATCH 1/3] fix: first attempt at conditional render --- .../core/src/components/question/Question.tsx | 21 ++- packages/core/src/model/QuestionModel.ts | 25 ++-- packages/core/src/model/SlideModel.ts | 9 +- packages/core/src/model/json/Layout.ts | 34 ++--- .../core/src/services/QuickFormServices.ts | 2 +- .../defaults/DefaultModelTransformer.ts | 14 +- .../defaults/DefaultQuestionTransformer.ts | 3 +- packages/core/src/state/QuickFormContext.tsx | 9 +- packages/core/src/state/QuickformProvider.tsx | 6 +- packages/playground/src/App.tsx | 7 +- packages/playground/src/data/carp.json | 130 +++++++----------- 11 files changed, 121 insertions(+), 139 deletions(-) diff --git a/packages/core/src/components/question/Question.tsx b/packages/core/src/components/question/Question.tsx index d596280..353b0fa 100644 --- a/packages/core/src/components/question/Question.tsx +++ b/packages/core/src/components/question/Question.tsx @@ -1,4 +1,4 @@ -import { ReactNode } from "react"; +import { ReactNode, useEffect, useState } from "react"; import React from "react"; import { Paragraph, Heading } from ".."; import { QuestionModel } from "../../model/QuestionModel"; @@ -22,9 +22,26 @@ const questionStyling: React.CSSProperties = { export const Question: React.FC = ({ className, model }) => { const InputType = resolveInputComponent(model.inputType); const logger = resolveQuickFormService("logger"); + const [visible, setIsVisible] = useState(true); + const { state, getCurrentSlide } = useQuickForm(); logger.log("QuestionRender for question {@model} InputProps", model); - const { state } = useQuickForm(); + function evalInScope(js: string, contextAsScope: any) { + return new Function(`with (this) { return (${js}); }`).call(contextAsScope); + } + + + useEffect(() => { + if (model.visible && model.visible?.rule) { + const shouldRender = evalInScope(model.visible.rule, { getCurrentSlide }); + setIsVisible(shouldRender) + } + }, [getCurrentSlide().questions]) + + if (!visible) { + return null; + } + const ql = state.slides[state.currIdx].questions.length === 1 ? '' : `.${String.fromCharCode('A'.charCodeAt(0) + state.slides[state.currIdx].questions.indexOf(model))}`; const label = state.isSubmitSlide ? '' : `${state.currIdx + 1}${ql}`; diff --git a/packages/core/src/model/QuestionModel.ts b/packages/core/src/model/QuestionModel.ts index a027ae6..3b4d37f 100644 --- a/packages/core/src/model/QuestionModel.ts +++ b/packages/core/src/model/QuestionModel.ts @@ -1,18 +1,17 @@ import { InputPropertiesTypes, InputTypeMap } from "./InputType"; -export class QuestionModel { - logicalName: string = ""; - inputType: keyof InputTypeMap | "text";; - text: string = ""; - placeholder: string = ""; - paragraph: string = ""; - dataType: "string" | "number" | "boolean" = "string" - answered: boolean = false; - output: any = {}; +export type QuestionModel = { + logicalName: string; + inputType: keyof InputTypeMap | "text"; + text: string; + placeholder: string; + paragraph: string; + dataType: "string" | "number" | "boolean"; + answered: boolean; + output: any; inputProperties?: InputPropertiesTypes; - - constructor(inputType: keyof InputTypeMap | "text", inputProperties?: QuestionModel['inputProperties']) { - this.inputType = inputType; - this.inputProperties = inputProperties; + visible?: { + type: string; + rule: string; } } \ No newline at end of file diff --git a/packages/core/src/model/SlideModel.ts b/packages/core/src/model/SlideModel.ts index 4a19fae..ef6f3a8 100644 --- a/packages/core/src/model/SlideModel.ts +++ b/packages/core/src/model/SlideModel.ts @@ -17,16 +17,17 @@ export class SlideModel { return this.questions.length > 0 && this.questions.every(question => question.answered); } - addQuestion(layout: QuestionRef, question: QuestionJsonModel, payload: any) { + addQuestion(layout: QuestionRef, question: QuestionJsonModel, payload: any, visible?: { type: string; rule: string; }) { const mapJsonQuestionToModelQuestion = resolveQuickFormService("questionTransformer"); - const questionModel = mapJsonQuestionToModelQuestion(layout.ref, question, payload[question?.logicalName ?? layout.ref]) + const questionModel = mapJsonQuestionToModelQuestion(layout.ref, question, payload[question?.logicalName ?? layout.ref], visible) this.questions.push(questionModel); return { style: layout.style, type: "question", - ref: layout.ref + ref: layout.ref, + visible: visible } as QuestionLayout; } } @@ -48,7 +49,7 @@ export type Row = QuestionLayout | RowColumns export type ColumnWithRows = { style?: React.CSSProperties; - type:"column", + type: "column", rows: Row[]; } export type Column = QuestionLayout | ColumnWithRows; diff --git a/packages/core/src/model/json/Layout.ts b/packages/core/src/model/json/Layout.ts index c2a37d0..061610d 100644 --- a/packages/core/src/model/json/Layout.ts +++ b/packages/core/src/model/json/Layout.ts @@ -20,14 +20,11 @@ export type SlideElements = { [key: string]: SlideElement } - - /** * A slideElement can be either a set of columns or a question */ export type SlideElement = RowColumnsLayout | QuestionRef; - /** * The RowColumnsLayout is a row with columns * If the type property is unset its assumed to be a row @@ -40,28 +37,27 @@ export type RowColumnsLayout = { */ type?: "row"; columns: ColumnsLayoutDefinition; - } -/** - * The Question Layout is a reference to a question - */ -export type QuestionRef = { - style?: React.CSSProperties; - type: "question", - ref: string; +export type ColumnsLayoutDefinition = { + [key: string]: ColumnLayout | QuestionRef } - - - export type ColumnLayout = { style?: React.CSSProperties; - type?:"column" + type?: "column" rows: SlideElements } -export type ColumnsLayoutDefinition = { - [key: string]: ColumnLayout | QuestionRef -} - +/** + * The Question Layout is a reference to a question + */ +export type QuestionRef = { + style?: React.CSSProperties; + type: "question"; + ref: string; + visible?: { + type: string; + rule: string; + } +} \ No newline at end of file diff --git a/packages/core/src/services/QuickFormServices.ts b/packages/core/src/services/QuickFormServices.ts index dc8dd29..db96470 100644 --- a/packages/core/src/services/QuickFormServices.ts +++ b/packages/core/src/services/QuickFormServices.ts @@ -6,7 +6,7 @@ import { InputComponentType } from "./defaults/DefaultInputTypeResolver"; export type HeadingNumberDisplayProvider = () => boolean; export type QuickFormModelTransformer = (data: QuickFormDefinition, payload: any) => QuickFormModel; -export type QuestionTransformer = (key: string, question: QuestionJsonModel, value?: any) => QuestionModel; +export type QuestionTransformer = (key: string, question: QuestionJsonModel, value?: any, visible?: { type: string; rule: string; }) => QuestionModel; export type InputTypePropertiesTransformer = (questionJsonModel: QuestionJsonModel) => InputPropertiesTypes | undefined; export type RegisterInputTypeComponent = (key: string, component: InputComponentType) => void; export interface IQuickFormLogger { diff --git a/packages/core/src/services/defaults/DefaultModelTransformer.ts b/packages/core/src/services/defaults/DefaultModelTransformer.ts index 30f771c..5d3657b 100644 --- a/packages/core/src/services/defaults/DefaultModelTransformer.ts +++ b/packages/core/src/services/defaults/DefaultModelTransformer.ts @@ -26,7 +26,7 @@ function processRows(rowLayouts: SlideElements, slide: SlideModel, questions: Qu if (!question) return; - rows.push(slide.addQuestion(rowLayout, question, payload)); + rows.push(slide.addQuestion(rowLayout, question, payload, rowLayout.visible)); break; @@ -48,7 +48,7 @@ function processRows(rowLayouts: SlideElements, slide: SlideModel, questions: Qu return; } - columns.push(slide.addQuestion(columnLayout, question, payload)); + columns.push(slide.addQuestion(columnLayout, question, payload, columnLayout.visible)); } else { @@ -112,9 +112,6 @@ function defaultLayout(questions: QuickFormQuestionsDefinition, payload: any): S const slides: SlideModel[] = []; Object.keys(questions).map(logicalName => { let slide: SlideModel = createSlide({ [logicalName]: questions[logicalName] }, payload); - // if (slide.questions.length === 1) { - // slide.displayName = slide.questions[0].text - // } slides.push(slide); }); return slides; @@ -129,7 +126,7 @@ function createSlide(questions: QuickFormQuestionsDefinition, payload: any): Sli const rows: Row[] = Object.entries(questions).map(([logicalName, question]) => { const questionRef = { ref: logicalName, - type: "question", + type: "question" } as QuestionRef; return slide.addQuestion(questionRef, question, payload); @@ -144,7 +141,6 @@ function createSlide(questions: QuickFormQuestionsDefinition, payload: any): Sli function handleSubmit(submit: QuickFormSubmitDefinition, payload: any): SubmitModel { const logger = resolveQuickFormService("logger"); - const parseInputProperties = resolveQuickFormService("inputTypePropertiesTransformer"); const { submitFields: { schema, uiSchema } } = submit; logger.log("Transforming submitfields: {@schema} {@uiSchema}", schema, uiSchema); @@ -174,8 +170,6 @@ function handleSubmit(submit: QuickFormSubmitDefinition, payload: any): SubmitMo submitFields: submitFieldsArray, submitUrl: submit.submitUrl, submitMethod: submit.submitMethod, - // payload: submit.payload, - // id: submit.id }; } @@ -186,7 +180,7 @@ function handleSubmit(submit: QuickFormSubmitDefinition, payload: any): SubmitMo * With the "Layout" config, it is possible to creates "Slides"(or Pages/Sections) with multiple inputs at a time. */ const transformJSONInput: QuickFormModelTransformer = (definition, payload): QuickFormModel => { - let slides; + let slides: SlideModel[]; const logger = resolveQuickFormService("logger"); console.log(JSON.stringify(definition.questions, null, 4)); diff --git a/packages/core/src/services/defaults/DefaultQuestionTransformer.ts b/packages/core/src/services/defaults/DefaultQuestionTransformer.ts index 6471151..927b2c3 100644 --- a/packages/core/src/services/defaults/DefaultQuestionTransformer.ts +++ b/packages/core/src/services/defaults/DefaultQuestionTransformer.ts @@ -2,7 +2,7 @@ import { QuestionModel } from "../../model"; import { QuestionJsonModel } from "../../model/json/JsonDataModels"; import { registerQuickFormService, resolveQuickFormService } from "../QuickFormServices"; -function mapJsonQuestionToModelQuestion(key: string, question: QuestionJsonModel, value?: any): QuestionModel { +function mapJsonQuestionToModelQuestion(key: string, question: QuestionJsonModel, value?: any, visible?: { type: string; rule: string; }): QuestionModel { const parseInputProperties = resolveQuickFormService("inputTypePropertiesTransformer"); const logger = resolveQuickFormService("logger"); @@ -25,6 +25,7 @@ function mapJsonQuestionToModelQuestion(key: string, question: QuestionJsonModel answered: typeof (value) !== "undefined" && value !== '' && value !== null, inputProperties: parseInputProperties(question), output: value ?? '', + visible: visible } as QuestionModel; } registerQuickFormService("questionTransformer", mapJsonQuestionToModelQuestion); \ No newline at end of file diff --git a/packages/core/src/state/QuickFormContext.tsx b/packages/core/src/state/QuickFormContext.tsx index e8f24d8..a4f5f19 100644 --- a/packages/core/src/state/QuickFormContext.tsx +++ b/packages/core/src/state/QuickFormContext.tsx @@ -1,7 +1,8 @@ -"use client" +"use client"; import React, { useContext } from "react"; import { QuickformState, defaultState } from "./QuickformState"; import { QuickformAction } from "./index"; +import { SlideModel } from "../model"; interface IQuickFormContext { state: QuickformState; @@ -13,6 +14,7 @@ interface IQuickFormContext { setIntroVisited: () => void; setErrorMsg: (msg: string) => void; isFirstQuestionInCurrentSlide: (questionLogicalName: string) => boolean; + getCurrentSlide: () => SlideModel; } export const QuickFormContext = React.createContext( @@ -25,7 +27,10 @@ export const QuickFormContext = React.createContext( answerQuestion: () => { }, setIntroVisited: () => { }, setErrorMsg: () => { }, - isFirstQuestionInCurrentSlide: () => true + isFirstQuestionInCurrentSlide: () => true, + getCurrentSlide: () => ( + { questions: [], rows: [], isAnswered: false, addQuestion: () => ({ type: "question", ref: "" }) } + ), } ); diff --git a/packages/core/src/state/QuickformProvider.tsx b/packages/core/src/state/QuickformProvider.tsx index 19b446d..dd29615 100644 --- a/packages/core/src/state/QuickformProvider.tsx +++ b/packages/core/src/state/QuickformProvider.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { useMemo, useReducer } from "react"; import { quickformReducer } from "./QuickformReducer"; import { defaultState } from "./QuickformState"; @@ -37,6 +37,7 @@ export const QuickFormProvider: React.FC = ({ children, const currSlide = state.slides[state.currIdx]; return currSlide.questions && currSlide.questions.length > 0 && currSlide.questions[0].logicalName === questionLogicalName } + const getCurrentSlide = () => (state.slides[state.currIdx]); return ( = ({ children, answerQuestion, setIntroVisited, setErrorMsg, - isFirstQuestionInCurrentSlide + isFirstQuestionInCurrentSlide, + getCurrentSlide }}> {children} diff --git a/packages/playground/src/App.tsx b/packages/playground/src/App.tsx index 48b77ff..33d4c58 100644 --- a/packages/playground/src/App.tsx +++ b/packages/playground/src/App.tsx @@ -1,16 +1,17 @@ import React from 'react'; import { useState } from 'react'; import { QuickFormDefinition } from '../../core/src/model'; -import cleanTestData from "./data/clean.json"; +import carp from "./data/carp.json"; +import clean from "./data/clean.json"; import { QuickFormProvider } from '../../core/src/state'; import { Editor } from '@monaco-editor/react'; import { Button, QuickForm } from '../../core/src/components'; import "./components/slider/SliderInput"; export const App = () => { - const [selectedTemplate, setSelectedTemplate] = useState(cleanTestData as QuickFormDefinition); + const [selectedTemplate, setSelectedTemplate] = useState(carp as QuickFormDefinition); const [hackToChangeQuickForm, setHackToChangeQuickForm] = useState(0); - const [editorValue, setEditorValue] = useState(JSON.stringify(cleanTestData)); + const [editorValue, setEditorValue] = useState(JSON.stringify(carp)); const onChangeEditorValue = (value: string) => { console.log("Editor input changed"); diff --git a/packages/playground/src/data/carp.json b/packages/playground/src/data/carp.json index 575a692..28be4f1 100644 --- a/packages/playground/src/data/carp.json +++ b/packages/playground/src/data/carp.json @@ -2,7 +2,7 @@ "layout": { "slides": { "slide1": { - "title": "Calculate the price for cleaning of tiles", + "title": "Beregn prisen for rengøring af fliser", "rows": { "row1": { "columns": { @@ -18,7 +18,11 @@ }, "row1_3": { "type": "question", - "ref": "impregnateTiles" + "ref": "impregnateTiles", + "visible": { + "type": "JSEval", + "rule": "getCurrentSlide().questions.find(q => q.logicalName === 'removeAlgae').output === 'hest'" + } } } } @@ -27,7 +31,7 @@ } }, "slide2": { - "title": "Enter your details", + "title": "Indtast dine oplysninger", "rows": { "row2": { "columns": { @@ -53,51 +57,27 @@ } } }, - "intro": { - "text": "Get your cleaning quote instantly", - "paragraph": "Fill in the details to calculate the cleaning cost", - "buttonText": "Start Calculation" - }, "submit": { - "text": "Get your Quote", - "buttonText": "Calculate", + "text": "", + "buttonText": "Beregn", "paragraphs": [ - "Please review your information before submitting.", - "Make sure all the details are correct to receive an accurate quote." + "Gennemgå venligst dine oplysninger før indsendelse.", + "Sørg for, at alle detaljer er korrekte for at modtage et nøjagtigt tilbud." ], - "submitUrl": "https://example.com/post", + "submitUrl": "https://httpbin.org/post", "submitMethod": "POST", "submitFields": { "uiSchema": { - "areaToClean": { - "ui:widget": "range", - "ui:placeholder": "Area in square meters", - "ui:label": "How many square meters need to be cleaned?" - }, - "removeAlgae": { - "ui:widget": "radio", - "ui:options": { - "inline": true - }, - "ui:title": "Should algae be removed?" - }, - "impregnateTiles": { - "ui:widget": "radio", - "ui:options": { - "inline": true - }, - "ui:title": "Should the tiles be impregnated?" - }, "name": { - "ui:placeholder": "Enter your name", - "ui:label": "Name" + "ui:placeholder": "Indtast dit navn", + "ui:label": "Navn" }, "phone": { - "ui:placeholder": "Enter your phone number", - "ui:label": "Phone" + "ui:placeholder": "Indtast dit telefonnummer", + "ui:label": "Telefon" }, "email": { - "ui:placeholder": "Enter your email", + "ui:placeholder": "Indtast din e-mail", "ui:label": "E-mail" } }, @@ -105,28 +85,14 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { - "areaToClean": { - "type": "number", - "title": "Area to clean", - "minimum": 1, - "maximum": 200 - }, - "removeAlgae": { - "type": "boolean", - "title": "Algae removal" - }, - "impregnateTiles": { - "type": "boolean", - "title": "Impregnate tiles" - }, "name": { "type": "string", - "title": "Name" + "title": "Navn" }, "phone": { "type": "string", "format": "tel", - "title": "Phone" + "title": "Telefon" }, "email": { "type": "string", @@ -145,61 +111,61 @@ }, "ending": { "inputType": "ending", - "text": "Thank you for using our service", - "paragraph": "We will contact you shortly with your quote." + "text": "Tak for at du brugte vores service", + "paragraph": "Vi vil kontakte dig snarest med dit tilbud." }, "questions": { "areaToClean": { "inputType": "slider", - "text": "How many square meters need cleaning?", - "paragraph": "Please slide to select the area size in square meters", + "text": "Hvor mange kvadratmeter skal renses?", + "paragraph": "", "placeholder": "20 m2", "min": 1, "max": 200, "step": 10, "type": "number", - "lang": "EN" + "lang": "DA" }, - "algaeRemoval": { + "removeAlgae": { "inputType": "toggle", - "text": "Should algae be removed?", - "paragraph": "Select 'Yes' if algae removal is needed, otherwise select 'No'.", + "text": "Skal alger fjernes?", + "paragraph": "Vælg 'Ja', hvis der er behov for fjernelse af alger, ellers vælg 'Nej'.", "options": { - "yes": "Yes", - "no": "No" + "yes": "Ja", + "no": "Nej" }, - "lang": "EN" + "lang": "DA" }, - "impregnation": { + "impregnateTiles": { "inputType": "toggle", - "text": "Should the tiles be impregnated?", - "paragraph": "Select 'Yes' if tiles should be impregnated, otherwise select 'No'.", + "text": "Skal fliserne imprægneres?", + "paragraph": "Vælg 'Ja', hvis fliserne skal imprægneres, ellers vælg 'Nej'.", "options": { - "yes": "Yes", - "no": "No" + "yes": "Ja", + "no": "Nej" }, - "lang": "EN" + "lang": "DA" }, "name": { "inputType": "text", - "text": "What is your name?", - "paragraph": "Please enter your full name.", - "placeholder": "Enter your name", - "lang": "EN" + "text": "Hvad er dit navn?", + "paragraph": "Indtast venligst dit fulde navn.", + "placeholder": "Indtast dit navn", + "lang": "DA" }, "phone": { "inputType": "text", - "text": "What is your phone number?", - "paragraph": "Please enter your phone number for contact purposes.", - "placeholder": "Enter your phone number", - "lang": "EN" + "text": "Hvad er dit telefonnummer?", + "paragraph": "Indtast venligst dit telefonnummer til kontaktformål.", + "placeholder": "Indtast dit telefonnummer", + "lang": "DA" }, "email": { "inputType": "text", - "text": "What is your email address?", - "paragraph": "Please enter your email to receive the quotation.", - "placeholder": "Enter your email", - "lang": "EN" + "text": "Hvad er din e-mailadresse?", + "paragraph": "Indtast venligst din e-mail for at modtage tilbuddet.", + "placeholder": "Indtast din e-mail", + "lang": "DA" } } } \ No newline at end of file From 9ef949fca51ae82d3e8e00dec44a4a5aaaba21a1 Mon Sep 17 00:00:00 2001 From: Kasper Baun Date: Fri, 1 Mar 2024 12:21:23 +0100 Subject: [PATCH 2/3] fix: refactored abit for simplicity. done with removed some unused things and optimized af few components. --- packages/core/src/components/QuickForm.tsx | 11 +- .../core/src/components/button/Button.tsx | 2 +- .../core/src/components/divider/Divider.tsx | 3 +- .../src/components/ending/Ending.module.css | 10 - .../core/src/components/ending/Ending.tsx | 29 +-- .../core/src/components/heading/Heading.tsx | 17 +- .../src/components/icons/ArrowDownIcon.tsx | 8 +- .../core/src/components/icons/ArrowUpIcon.tsx | 9 +- .../core/src/components/icons/Checkmark.tsx | 12 +- .../core/src/components/icons/ErrorIcon.tsx | 20 +- .../src/components/icons/ImArrowRightIcon.tsx | 8 +- .../core/src/components/icons/RightArrow.tsx | 10 +- .../core/src/components/icons/iconProps.ts | 10 + packages/core/src/components/intro/Intro.tsx | 13 +- .../question-number/QuestionNumber.tsx | 1 - .../core/src/components/question/Question.tsx | 38 +--- .../column-renderer/ColumnRenderer.tsx | 52 +++++ .../conditional-render/ConditionalRender.tsx | 51 +++++ .../renderers/row-renderer/RowRenderer.tsx | 39 ++++ .../renderers/row-renderer/rowStyles.ts | 5 + .../slide-renderer}/Slide.module.css | 0 .../slide-renderer/SlideRenderer.tsx | 124 +++++++++++ .../slide-renderer/SlideRenderer.tsx | 39 ---- packages/core/src/components/slide/Slide.tsx | 208 +----------------- .../core/src/components/submit/Submit.tsx | 82 +------ packages/core/src/model/SlideModel.ts | 11 +- packages/core/src/model/index.ts | 4 +- .../JsonDataModels.ts | 7 +- .../{json => json-definitions}/Layout.ts | 4 - .../QuickFormDefinition.ts | 8 +- .../QuickFormQuestionsDefinition.ts | 2 +- .../QuickFormSubmitDefinition.ts | 12 +- packages/core/src/model/json/inputtype.json | 0 packages/core/src/model/json/question.json | 55 ----- .../core/src/model/legacy/QuestionModel.ts | 50 ----- .../core/src/services/QuickFormServices.ts | 2 +- .../defaults/DefaultInputTypeResolver.ts | 2 +- .../defaults/DefaultModelTransformer.ts | 11 +- .../defaults/DefaultQuestionTransformer.ts | 8 +- packages/core/src/state/QuickformProvider.tsx | 3 - packages/core/src/state/QuickformReducer.ts | 5 +- .../NavigationActionHandler.ts | 21 +- .../action-handlers/QuestionActionHandler.ts | 4 +- .../action-handlers/SubmitActionHandler.ts | 1 - packages/core/src/utils/questionUtils.ts | 5 + packages/core/tsconfig.json | 26 ++- packages/playground/src/App.tsx | 2 - 47 files changed, 418 insertions(+), 626 deletions(-) delete mode 100644 packages/core/src/components/ending/Ending.module.css create mode 100644 packages/core/src/components/icons/iconProps.ts create mode 100644 packages/core/src/components/renderers/column-renderer/ColumnRenderer.tsx create mode 100644 packages/core/src/components/renderers/conditional-render/ConditionalRender.tsx create mode 100644 packages/core/src/components/renderers/row-renderer/RowRenderer.tsx create mode 100644 packages/core/src/components/renderers/row-renderer/rowStyles.ts rename packages/core/src/components/{slide => renderers/slide-renderer}/Slide.module.css (100%) create mode 100644 packages/core/src/components/renderers/slide-renderer/SlideRenderer.tsx delete mode 100644 packages/core/src/components/slide-renderer/SlideRenderer.tsx rename packages/core/src/model/{json => json-definitions}/JsonDataModels.ts (86%) rename packages/core/src/model/{json => json-definitions}/Layout.ts (95%) rename packages/core/src/model/{ => json-definitions}/QuickFormDefinition.ts (57%) rename packages/core/src/model/{ => json-definitions}/QuickFormQuestionsDefinition.ts (59%) rename packages/core/src/model/{json => json-definitions}/QuickFormSubmitDefinition.ts (55%) delete mode 100644 packages/core/src/model/json/inputtype.json delete mode 100644 packages/core/src/model/json/question.json delete mode 100644 packages/core/src/model/legacy/QuestionModel.ts create mode 100644 packages/core/src/utils/questionUtils.ts diff --git a/packages/core/src/components/QuickForm.tsx b/packages/core/src/components/QuickForm.tsx index 195805f..19af5f2 100644 --- a/packages/core/src/components/QuickForm.tsx +++ b/packages/core/src/components/QuickForm.tsx @@ -3,20 +3,20 @@ import React from "react"; import { useQuickForm } from "../state/QuickFormContext"; import { Ending, Submit } from "./index"; import { Intro } from "./intro/Intro"; -import { SlideRenderer } from "./slide-renderer/SlideRenderer"; +import { SlideRenderer } from "./renderers/slide-renderer/SlideRenderer"; export const QuickForm: React.FC = () => { const { state, setIntroVisited } = useQuickForm(); if (state.isIntroSlide && typeof state.data.intro !== "undefined") { - return + return } if (state.isSubmitSlide) - return + return if (state.isEndingSlide) { - return + return } return ( @@ -24,5 +24,4 @@ export const QuickForm: React.FC = () => { ); -} - +} \ No newline at end of file diff --git a/packages/core/src/components/button/Button.tsx b/packages/core/src/components/button/Button.tsx index 9d09e2b..69f18ca 100644 --- a/packages/core/src/components/button/Button.tsx +++ b/packages/core/src/components/button/Button.tsx @@ -64,7 +64,7 @@ const btnContainerStyle = { margin: '30px', marginTop: '16px', - + }; const buttonStyle = { diff --git a/packages/core/src/components/divider/Divider.tsx b/packages/core/src/components/divider/Divider.tsx index 5da6aa7..62f516f 100644 --- a/packages/core/src/components/divider/Divider.tsx +++ b/packages/core/src/components/divider/Divider.tsx @@ -12,5 +12,4 @@ export const Divider: React.FC = ({ color }) => { }; return
; -}; - +}; \ No newline at end of file diff --git a/packages/core/src/components/ending/Ending.module.css b/packages/core/src/components/ending/Ending.module.css deleted file mode 100644 index 2e63b3a..0000000 --- a/packages/core/src/components/ending/Ending.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.ending { - color: var(--on-surface); - font-size: 2.4rem; - font-weight: unset; - -} - -.endingSvg{ - stroke:var(--on-surface); -} \ No newline at end of file diff --git a/packages/core/src/components/ending/Ending.tsx b/packages/core/src/components/ending/Ending.tsx index fe80183..475f02e 100644 --- a/packages/core/src/components/ending/Ending.tsx +++ b/packages/core/src/components/ending/Ending.tsx @@ -1,14 +1,12 @@ "use client"; -import React, { useEffect } from "react"; +import React from "react"; import { Heading, Paragraph } from "../index"; import { ErrorIcon, Checkmark } from "../icons/index"; -import styles from "./Ending.module.css"; -import classNames from "classnames"; import { useQuickForm } from "../../state/QuickFormContext"; import { EndingModel } from "../../model"; type EndingProps = { - data: EndingModel; + model: EndingModel; } const endingStyles: React.CSSProperties = { @@ -17,40 +15,35 @@ const endingStyles: React.CSSProperties = { flexDirection: 'column' } -export const Ending: React.FC = ({ data }) => { +export const Ending: React.FC = ({ model }) => { const { state } = useQuickForm(); + const { text, paragraph } = model; const submitStatus = state.submitStatus; - useEffect(() => { - console.log("Ending rendered..", submitStatus, classNames(styles.svgcolor), "test"); - - }, [submitStatus]); - return (
{submitStatus.isSubmitError && <> - + Der skete en fejl, prøv igen } {submitStatus.isSubmitSuccess && <> - - {data.text && + + {text && - {data.text} + {text} } - {data.paragraph && + {paragraph && - {data.paragraph} + {paragraph} } }
); -} - +} \ No newline at end of file diff --git a/packages/core/src/components/heading/Heading.tsx b/packages/core/src/components/heading/Heading.tsx index 4a47f80..9848409 100644 --- a/packages/core/src/components/heading/Heading.tsx +++ b/packages/core/src/components/heading/Heading.tsx @@ -1,8 +1,7 @@ import { HeadingNumberDisplayProvider, registerQuickFormService, resolveQuickFormService } from "../../services/QuickFormServices"; import { useQuickForm } from "../../state/QuickFormContext"; import { ImArrowRightIcon } from "../../components/icons"; -import React, { CSSProperties } from "react"; -import classNames from "classnames"; +import React from "react"; import { ReactNode } from "react"; type HeadingProps = { @@ -22,8 +21,6 @@ export function Heading({ children, label, style = {} }: HeadingProps) { color: 'var(--on-surface)', } - console.log("shouldDisplayNumber", shouldDisplayNumber); - return (

@@ -37,18 +34,6 @@ export function Heading({ children, label, style = {} }: HeadingProps) { } -const rootStyles: CSSProperties = { - position: "absolute", - left: 0, - translate: "-110px", - justifyContent: 'end', - width: '100px', - display: "flex", - alignItems: "center", - fontSize: "22px", - top: "11px" -}; - const defaultHeadingNumberDisplayProvider: HeadingNumberDisplayProvider = () => { let { state } = useQuickForm(); return !(state.isEndingSlide || state.isIntroSlide || state.isSubmitSlide) diff --git a/packages/core/src/components/icons/ArrowDownIcon.tsx b/packages/core/src/components/icons/ArrowDownIcon.tsx index 409dc2e..a3dc71e 100644 --- a/packages/core/src/components/icons/ArrowDownIcon.tsx +++ b/packages/core/src/components/icons/ArrowDownIcon.tsx @@ -1,10 +1,6 @@ import React from "react"; -interface ArrowDownProps { - className?: string; -} - -export const ArrowDownIcon: React.FC = ({ className }) => { +export const ArrowDownIcon: React.FC = ({ className }) => { return (
= ({ className }) => {
); -}; +}; \ No newline at end of file diff --git a/packages/core/src/components/icons/ArrowUpIcon.tsx b/packages/core/src/components/icons/ArrowUpIcon.tsx index ffd47ad..de95822 100644 --- a/packages/core/src/components/icons/ArrowUpIcon.tsx +++ b/packages/core/src/components/icons/ArrowUpIcon.tsx @@ -1,10 +1,6 @@ import React from "react"; -interface ArrowUpIconProps { - className?: string; -} - -export const ArrowUpIcon: React.FC = ({ className }) => { +export const ArrowUpIcon: React.FC = ({ className }) => { return (
= ({ className }) => {
); -}; - +}; \ No newline at end of file diff --git a/packages/core/src/components/icons/Checkmark.tsx b/packages/core/src/components/icons/Checkmark.tsx index b43d16c..81afe1f 100644 --- a/packages/core/src/components/icons/Checkmark.tsx +++ b/packages/core/src/components/icons/Checkmark.tsx @@ -1,10 +1,6 @@ import React from "react"; -type CheckmarkProps = { - classNames?: string; -} - -export const Checkmark: React.FC = ({ classNames }) => { +export const Checkmark: React.FC = ({ color = "green" }) => { return (
= ({ classNames }) => { xmlns="http://www.w3.org/2000/svg" > = ({ classNames }) => {
); -} - +} \ No newline at end of file diff --git a/packages/core/src/components/icons/ErrorIcon.tsx b/packages/core/src/components/icons/ErrorIcon.tsx index 7b7381d..b7e08c9 100644 --- a/packages/core/src/components/icons/ErrorIcon.tsx +++ b/packages/core/src/components/icons/ErrorIcon.tsx @@ -1,43 +1,37 @@ import React from "react"; -type ErrorIconProps = { - classNames?: string; -} - -export const ErrorIcon: React.FC = ({ classNames }) => { +export const ErrorIcon: React.FC = ({ style, color = "red", size = 100 }) => { return (
diff --git a/packages/core/src/components/icons/ImArrowRightIcon.tsx b/packages/core/src/components/icons/ImArrowRightIcon.tsx index 159ab11..c30076c 100644 --- a/packages/core/src/components/icons/ImArrowRightIcon.tsx +++ b/packages/core/src/components/icons/ImArrowRightIcon.tsx @@ -1,10 +1,6 @@ import React from "react"; -interface ImArrowRightProps { - size?: string; -} - -export const ImArrowRightIcon: React.FC = ({ size = '20px' }) => { +export const ImArrowRightIcon: React.FC = ({ size = '20px' }) => { return ( <> @@ -12,4 +8,4 @@ export const ImArrowRightIcon: React.FC = ({ size = '20px' }) ); -}; +}; \ No newline at end of file diff --git a/packages/core/src/components/icons/RightArrow.tsx b/packages/core/src/components/icons/RightArrow.tsx index 2f8dfcb..45234d9 100644 --- a/packages/core/src/components/icons/RightArrow.tsx +++ b/packages/core/src/components/icons/RightArrow.tsx @@ -1,11 +1,6 @@ import React from "react"; -interface RightArrowProps { - className?: string; - size?: string; -} - -export const RightArrow: React.FC = ({ className, size = '16px' }) => { +export const RightArrow: React.FC = ({ className, size = '16px' }) => { return (
@@ -13,5 +8,4 @@ export const RightArrow: React.FC = ({ className, size = '16px'
); -}; - +}; \ No newline at end of file diff --git a/packages/core/src/components/icons/iconProps.ts b/packages/core/src/components/icons/iconProps.ts new file mode 100644 index 0000000..95189be --- /dev/null +++ b/packages/core/src/components/icons/iconProps.ts @@ -0,0 +1,10 @@ +type IconProps = { + style?: React.CSSProperties; + color?: string; + backgroundColor?: string; + size?: number; + textColor?: string; + className?: string; + opacity?: number; + hoverColor?:string; +} \ No newline at end of file diff --git a/packages/core/src/components/intro/Intro.tsx b/packages/core/src/components/intro/Intro.tsx index bafdb9e..8370e46 100644 --- a/packages/core/src/components/intro/Intro.tsx +++ b/packages/core/src/components/intro/Intro.tsx @@ -5,12 +5,12 @@ import { IntroModel } from "../../model"; import { useHandleEnterKeypress } from "../../hooks"; type IntroProps = { - data: IntroModel; - errorMsg: string; + model: IntroModel; onBtnClick: React.Dispatch; } -export const Intro: React.FC = ({ data, errorMsg, onBtnClick }) => { +export const Intro: React.FC = ({ model, onBtnClick }) => { + const { text, paragraph, buttonText } = model; /* Listens to enter key pressed */ useHandleEnterKeypress("intro", false, onBtnClick); @@ -18,14 +18,13 @@ export const Intro: React.FC = ({ data, errorMsg, onBtnClick }) => { return (
- {data.text} + {text} - {data.paragraph} + {paragraph}
) diff --git a/packages/core/src/components/question-number/QuestionNumber.tsx b/packages/core/src/components/question-number/QuestionNumber.tsx index a057e75..ade4ae8 100644 --- a/packages/core/src/components/question-number/QuestionNumber.tsx +++ b/packages/core/src/components/question-number/QuestionNumber.tsx @@ -12,7 +12,6 @@ registerQuickFormService("headingNumberDisplayProvider", defaultHeadingNumberDis export const QuestionNumber: React.FC<{ questionNum?: number }> = ({ questionNum }) => { const shouldDisplayNumber = resolveQuickFormService("headingNumberDisplayProvider")(); - console.log("shouldDisplay", shouldDisplayNumber); if (!shouldDisplayNumber) { return null } diff --git a/packages/core/src/components/question/Question.tsx b/packages/core/src/components/question/Question.tsx index 353b0fa..3afb035 100644 --- a/packages/core/src/components/question/Question.tsx +++ b/packages/core/src/components/question/Question.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useEffect, useState } from "react"; +import { ReactNode } from "react"; import React from "react"; import { Paragraph, Heading } from ".."; import { QuestionModel } from "../../model/QuestionModel"; @@ -8,7 +8,7 @@ import { resolveInputComponent } from "../../services"; type QuestionProps = { model: QuestionModel; - className?: string, + style?: React.CSSProperties; icon?: ReactNode } @@ -19,37 +19,18 @@ const questionStyling: React.CSSProperties = { margin: '20px' } -export const Question: React.FC = ({ className, model }) => { +export const Question: React.FC = ({ model, style }) => { const InputType = resolveInputComponent(model.inputType); const logger = resolveQuickFormService("logger"); - const [visible, setIsVisible] = useState(true); - const { state, getCurrentSlide } = useQuickForm(); + const { state } = useQuickForm(); logger.log("QuestionRender for question {@model} InputProps", model); - function evalInScope(js: string, contextAsScope: any) { - return new Function(`with (this) { return (${js}); }`).call(contextAsScope); - } - - - useEffect(() => { - if (model.visible && model.visible?.rule) { - const shouldRender = evalInScope(model.visible.rule, { getCurrentSlide }); - setIsVisible(shouldRender) - } - }, [getCurrentSlide().questions]) - - if (!visible) { - return null; - } - const ql = state.slides[state.currIdx].questions.length === 1 ? '' : `.${String.fromCharCode('A'.charCodeAt(0) + state.slides[state.currIdx].questions.indexOf(model))}`; const label = state.isSubmitSlide ? '' : `${state.currIdx + 1}${ql}`; - if (!InputType || typeof InputType === "undefined") { return
Attempted to use inputtype {model.inputType} but was not able to find a matching input for question: {model.logicalName}
@@ -57,10 +38,11 @@ export const Question: React.FC = ({ className, model }) => { return (
- + {model.text} @@ -74,4 +56,4 @@ export const Question: React.FC = ({ className, model }) => { />
); -} +} \ No newline at end of file diff --git a/packages/core/src/components/renderers/column-renderer/ColumnRenderer.tsx b/packages/core/src/components/renderers/column-renderer/ColumnRenderer.tsx new file mode 100644 index 0000000..d9f50fa --- /dev/null +++ b/packages/core/src/components/renderers/column-renderer/ColumnRenderer.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { QuestionModel, Column } from "../../../model"; +import { Question } from '../../question/Question'; +import { resolveQuickFormService } from '../../../services/QuickFormServices'; +import { findQuestionByLogicalName } from '../../../utils/questionUtils'; +import { ConditionalRender } from '../conditional-render/ConditionalRender'; +import { RowRenderer } from '../row-renderer/RowRenderer'; +import { fullRowStyle } from '../row-renderer/rowStyles'; + + +type ColumnRendererProps = { + column: Column; + questions: QuestionModel[]; +} + +export const getColumnStyle = (numberOfColumns: number): React.CSSProperties => ({ + width: `${100 / numberOfColumns}%`, + display: 'flex', + flexDirection: 'column', +}); + +export const ColumnRenderer: React.FC = ({ column, questions }) => { + + const logger = resolveQuickFormService("logger"); + logger.log("Rendering column {@column}", column); + + if (column.type === "question") { + const question = findQuestionByLogicalName(column.ref!, questions); + if (!question) return null; + if (question.visible && question.visible?.rule) { + return + } + return + } + + return (<> + + {column.rows.map((innerRow, innerRowIndex) => { + if (innerRow.type === "question") { + const question = findQuestionByLogicalName(innerRow.ref!, questions); + if (!question) return null; + if (question.visible && question.visible?.rule) { + return + } + return + } else { + return + } + })} + + ) +} \ No newline at end of file diff --git a/packages/core/src/components/renderers/conditional-render/ConditionalRender.tsx b/packages/core/src/components/renderers/conditional-render/ConditionalRender.tsx new file mode 100644 index 0000000..cae9b3b --- /dev/null +++ b/packages/core/src/components/renderers/conditional-render/ConditionalRender.tsx @@ -0,0 +1,51 @@ + +import { QuestionModel } from "../../../model/QuestionModel"; +import { useEffect, useState } from "react"; +import React from "react"; +import { resolveQuickFormService } from "../../../services/QuickFormServices"; +import { useQuickForm } from "../../../state"; +import { Question } from "../../../components/question/Question"; + +type ConditionalRenderProps = { + model: QuestionModel; + style?: React.CSSProperties; +} + +export const ConditionalRender: React.FC = ({ model, style }) => { + const logger = resolveQuickFormService("logger"); + const [visible, setIsVisible] = useState(false); + const { getCurrentSlide } = useQuickForm(); + logger.log("ConditionalRender for question {@model} InputProps", model); + + // function evalInScope(js: string, contextAsScope: any) { + // return new Function(`with (this) { return (${js}); }`).call(contextAsScope); + // } + + interface Context { + [key: string]: any; + } + + function functionInScope(js: string, context: Context): boolean { + const keys = Object.keys(context); + const values = keys.map(key => context[key]); + + const func: Function = new Function(...keys, `return ${js};`); + + return (func as (...args: any[]) => any)(...values); + } + + useEffect(() => { + if (model.visible && model.visible?.rule) { + const shouldRender = functionInScope(model.visible.rule, { getCurrentSlide }); + setIsVisible(shouldRender) + } + }, [getCurrentSlide().questions]) + + if (!visible) { + return null; + } + + return ( + + ) +} \ No newline at end of file diff --git a/packages/core/src/components/renderers/row-renderer/RowRenderer.tsx b/packages/core/src/components/renderers/row-renderer/RowRenderer.tsx new file mode 100644 index 0000000..3ec2e35 --- /dev/null +++ b/packages/core/src/components/renderers/row-renderer/RowRenderer.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { QuestionModel, Row } from "../../../model"; +import { Question } from '../../question/Question'; +import { resolveQuickFormService } from '../../../services/QuickFormServices'; +import { findQuestionByLogicalName } from '../../../utils/questionUtils'; +import { ConditionalRender } from '../conditional-render/ConditionalRender'; +import { fullRowStyle } from './rowStyles'; +import { ColumnRenderer, getColumnStyle } from '../column-renderer/ColumnRenderer'; + +type RowRendererProps = { + row: Row; + questions: QuestionModel[]; +} + +export const RowRenderer: React.FC = ({ row, questions }) => { + + const logger = resolveQuickFormService("logger"); + logger.log("Rendering row {@row}", row); + + if (row.type === "row") { + return ( + <> + {row.columns.map((column, columnIndex) => ( +
+ +
+ ))} + ) + } + + const question = findQuestionByLogicalName(row.ref!, questions); + if (!question) return null; + + if (question.visible && question.visible?.rule) { + return + } + + return +} \ No newline at end of file diff --git a/packages/core/src/components/renderers/row-renderer/rowStyles.ts b/packages/core/src/components/renderers/row-renderer/rowStyles.ts new file mode 100644 index 0000000..672274b --- /dev/null +++ b/packages/core/src/components/renderers/row-renderer/rowStyles.ts @@ -0,0 +1,5 @@ +export const fullRowStyle: React.CSSProperties = { + width: '100%', + display: 'flex', + flexDirection: 'column', +}; \ No newline at end of file diff --git a/packages/core/src/components/slide/Slide.module.css b/packages/core/src/components/renderers/slide-renderer/Slide.module.css similarity index 100% rename from packages/core/src/components/slide/Slide.module.css rename to packages/core/src/components/renderers/slide-renderer/Slide.module.css diff --git a/packages/core/src/components/renderers/slide-renderer/SlideRenderer.tsx b/packages/core/src/components/renderers/slide-renderer/SlideRenderer.tsx new file mode 100644 index 0000000..341abd5 --- /dev/null +++ b/packages/core/src/components/renderers/slide-renderer/SlideRenderer.tsx @@ -0,0 +1,124 @@ +import React from 'react'; +import { useQuickForm } from '../../../state/QuickFormContext'; +import { Button } from '../../button/Button'; +import { useHandleEnterKeypress } from '../../../hooks'; +import { Slide } from '../../slide/Slide'; + + +export const SlideRenderer: React.FC = () => { + const { state, answerQuestion } = useQuickForm(); + const currentSlide = state.slides[state.currIdx]; + + /* If all questions are answered, goToNextSlide() is called by the answerQuestion function (dispatch) */ + const handleGoToNextSlideClicked = () => { + for (var q of currentSlide.questions) { + if (typeof q.output !== "undefined" && q.output !== "") { + answerQuestion(q.logicalName!, q.output); + } + } + }; + + /* Listens to enter key pressed */ + const enterKeyDisabled = currentSlide.questions.some(q => q.inputType === "multilinetext"); + useHandleEnterKeypress("slide", enterKeyDisabled, handleGoToNextSlideClicked); + + return ( +
+ +
+ ); +}; + +const slideStyling: React.CSSProperties = { + display: 'flex', + flexDirection: 'column' +} + + +// enum ViewStatus { +// InView, +// TransitioningOut, +// OutOfView, +// TransitioningIn +// } + +// const animationTimerSetting = 300; +// const useTransitionState = () => { +// const [viewStatus, setViewStatus] = useState(ViewStatus.OutOfView); +// // const { questionNumber } = useCurrentQuestion(); +// const questionBoxClasses = classNames(styles['question-box'], { +// [styles['slide-out']]: viewStatus === ViewStatus.TransitioningOut, +// [styles['slide-in']]: viewStatus === ViewStatus.TransitioningIn, +// [styles['rendered']]: viewStatus === ViewStatus.InView, + +// }); + +// useEffect(() => { + +// setViewStatus(ViewStatus.TransitioningIn); +// setTimeout(() => { + +// setViewStatus(ViewStatus.InView); +// }, animationTimerSetting); +// }, []); +// // }, [questionNumber]); + +// return { +// questionBoxClasses, +// transitionOut: (onComplete?: () => void) => { +// setViewStatus(ViewStatus.TransitioningOut); +// if (onComplete) { +// setTimeout(() => { +// onComplete(); +// }, animationTimerSetting); +// } +// } +// } +// } + + +// const handleQuestionNextBtnClick = () => { + +// /** +// * If this is the submit part and we are progress 100 (completed) +// * TODO: Make the progress === 100 a method returned from quickform +// * 'isQuestionsComplete()' +// * +// * Such the logic on if its complete (the progress===100) is actually somewhere +// * else than in the rendering part. +// * */ +// if (inputType === "submit") { +// if (progress === 100) { + +// /** +// * If its complete, we simply click the onSubmitBtnClicked() +// * +// * I think it makes sense to add some ValidateForSubmission. +// * +// * */ +// transitionOut(onQuestionBtnClicked); + + +// } +// } + +// /** +// * If its not submit, we will ask quickform if the input type should be validated +// * */ +// if (shouldValidateInputType(inputType)) { + +// /** +// * The actualy validateinput should be on usequickform i think. +// * +// * Basically library users should be able to plug in validation +// * */ +// const isValid = validateInput(); +// if (!isValid) return; +// } +// transitionOut(onQuestionBtnClicked) +// }; \ No newline at end of file diff --git a/packages/core/src/components/slide-renderer/SlideRenderer.tsx b/packages/core/src/components/slide-renderer/SlideRenderer.tsx deleted file mode 100644 index d9213d6..0000000 --- a/packages/core/src/components/slide-renderer/SlideRenderer.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import { useQuickForm } from '../../state/QuickFormContext'; -import { Button } from '../button/Button'; -import { useHandleEnterKeypress } from '../../hooks'; -import { Slide } from '../slide/Slide'; - - -export const SlideRenderer: React.FC = () => { - const { state, answerQuestion } = useQuickForm(); - const currentSlide = state.slides[state.currIdx]; - - /* If all questions are answered, goToNextSlide() is called by the answerQuestion function (dispatch) */ - const handleGoToNextSlideClicked = () => { - for (var q of currentSlide.questions) { - if (typeof q.output !== "undefined" && q.output !== "") { - answerQuestion(q.logicalName!, q.output); - } - } - }; - - /* Listens to enter key pressed */ - const enterKeyDisabled = currentSlide.questions.some(q => q.inputType === "multilinetext"); - useHandleEnterKeypress("slide", enterKeyDisabled, handleGoToNextSlideClicked); - console.log("SlideRenderer: currentSlide", currentSlide); - - return ( -
- -
- ); -}; \ No newline at end of file diff --git a/packages/core/src/components/slide/Slide.tsx b/packages/core/src/components/slide/Slide.tsx index f3a288e..7afc4f4 100644 --- a/packages/core/src/components/slide/Slide.tsx +++ b/packages/core/src/components/slide/Slide.tsx @@ -1,34 +1,12 @@ import React from 'react'; -import { SlideModel, QuestionModel, Row, Column } from "../../model"; -import { Question } from '../question/Question'; -import { useQuickForm } from '../../state/QuickFormContext'; -import { resolveQuickFormService } from '../../services/QuickFormServices'; - - - -const getColumnStyle = (numberOfColumns: number): React.CSSProperties => ({ - width: `${100 / numberOfColumns}%`, - display: 'flex', - flexDirection: 'column', -}); - -const findQuestionByLogicalName = (logicalName: string, questions: QuestionModel[]): QuestionModel | undefined => { - return questions.find(q => q.logicalName === logicalName); -}; +import { SlideModel } from "../../model"; +import { RowRenderer } from '../renderers/row-renderer/RowRenderer'; type SlideProps = { model: SlideModel; } export const Slide: React.FC = ({ model }: SlideProps) => { - const { state } = useQuickForm(); - console.log("SlideComponent", model); - const rowContainerStyling: React.CSSProperties = { - display: 'flex', - flexDirection: 'row', - width: '100%', - margin: '10px' - } return (
@@ -43,181 +21,9 @@ export const Slide: React.FC = ({ model }: SlideProps) => { ); }; - -type ColumnRendererProps = { - column: Column; - questions: QuestionModel[]; -} - - - -const fullRowStyle: React.CSSProperties = { - width: '100%', +const rowContainerStyling: React.CSSProperties = { display: 'flex', - flexDirection: 'column', -}; - -const ColumnRenderer: React.FC = ({ column, questions }) => { - - const logger = resolveQuickFormService("logger"); - - logger.log("Rendering column {@column}", column); - - if (column.type === "question") { - - const question = findQuestionByLogicalName(column.ref!, questions); - - if (!question) return null; - - return ( -
- -
- ) - } - - return (<> - - {column.rows.map((innerRow, innerRowIndex) => { - - if (innerRow.type === "question") { - - const question = findQuestionByLogicalName(innerRow.ref!, questions); - if (!question) return null; - - return ( - - ); - } else { - return - } - })} - - ) -} - -type RowRendererProps = { - row: Row; - questions: QuestionModel[]; -} - - -const RowRenderer: React.FC = ({ row, questions }) => { - - const logger = resolveQuickFormService("logger"); - - logger.log("Rendering row {@row}",row); - - - if (row.type === "row") { - return <>{row.columns.map((column, columnIndex) => ( -
- -
- ))} - } else { - const question = findQuestionByLogicalName(row.ref!, questions); - if (!question) return null; - - return ( -
- -
- ) - } - - -} - - -// enum ViewStatus { -// InView, -// TransitioningOut, -// OutOfView, -// TransitioningIn -// } - -// const animationTimerSetting = 300; -// const useTransitionState = () => { -// const [viewStatus, setViewStatus] = useState(ViewStatus.OutOfView); -// // const { questionNumber } = useCurrentQuestion(); -// const questionBoxClasses = classNames(styles['question-box'], { -// [styles['slide-out']]: viewStatus === ViewStatus.TransitioningOut, -// [styles['slide-in']]: viewStatus === ViewStatus.TransitioningIn, -// [styles['rendered']]: viewStatus === ViewStatus.InView, - -// }); - -// useEffect(() => { - -// setViewStatus(ViewStatus.TransitioningIn); -// setTimeout(() => { - -// setViewStatus(ViewStatus.InView); -// }, animationTimerSetting); -// }, []); -// // }, [questionNumber]); - -// return { -// questionBoxClasses, -// transitionOut: (onComplete?: () => void) => { -// setViewStatus(ViewStatus.TransitioningOut); -// if (onComplete) { -// setTimeout(() => { -// onComplete(); -// }, animationTimerSetting); -// } -// } -// } -// } - - -// const handleQuestionNextBtnClick = () => { - -// /** -// * If this is the submit part and we are progress 100 (completed) -// * TODO: Make the progress === 100 a method returned from quickform -// * 'isQuestionsComplete()' -// * -// * Such the logic on if its complete (the progress===100) is actually somewhere -// * else than in the rendering part. -// * */ -// if (inputType === "submit") { -// console.log("progress: ", progress); -// if (progress === 100) { - -// /** -// * If its complete, we simply click the onSubmitBtnClicked() -// * -// * I think it makes sense to add some ValidateForSubmission. -// * -// * */ -// transitionOut(onQuestionBtnClicked); - - -// } -// } - -// /** -// * If its not submit, we will ask quickform if the input type should be validated -// * */ -// if (shouldValidateInputType(inputType)) { - -// /** -// * The actualy validateinput should be on usequickform i think. -// * -// * Basically library users should be able to plug in validation -// * */ -// const isValid = validateInput(); -// if (!isValid) return; -// } -// transitionOut(onQuestionBtnClicked) -// }; - + flexDirection: 'row', + width: '100%', + margin: '10px' +} \ No newline at end of file diff --git a/packages/core/src/components/submit/Submit.tsx b/packages/core/src/components/submit/Submit.tsx index e7d14e6..63095bc 100644 --- a/packages/core/src/components/submit/Submit.tsx +++ b/packages/core/src/components/submit/Submit.tsx @@ -1,79 +1,17 @@ -// "use client" -// import React, { useCallback, useState } from "react"; -// import classNames from "classnames"; -// import styles from "./Submit.module.css"; -// import { useQuickForm } from "@/components/quickform/context/QuickFormContext"; -// import { Heading, Button, Paragraph } from ".."; -// import { useDelayedClickListener, useHandleKeypress } from "../../hooks"; -// import Spinner from "../spinner/Spinner"; -// import validator from '@rjsf/validator-ajv8'; -// import Form from '@rjsf/core'; -// import { ErrorMessage } from "../error-message/ErrorMessage"; -// import { QuickSchemaForm } from "../rjsf/QuickFormRJSFTheme"; -// import { mergeClasses } from "@griffel/react"; -// import { useTailWind } from "../../../../utils/makeTailWindStyles"; -// import type { FormProps } from "@rjsf/core"; -// import { assertSubmitModel } from "model/QuestionModel"; - import React from "react"; import { SubmitModel } from "../../model"; import { useQuickForm } from "../../state/QuickFormContext"; import { Heading, Paragraph, Button, Spinner, Question } from "../index"; import { SubmitActionHandler } from "../../state/action-handlers/SubmitActionHandler"; +import { ConditionalRender } from "../renderers/conditional-render/ConditionalRender"; -// export const Submit: React.FC = () => { - -// const tw = useTailWind(); - -// const { questionState: { submitStatus, currentQuestion }, dispatch } = useQuickForm(); -// console.log("SUBMIT", submitStatus); -// const { submitFields, output } = assertSubmitModel(currentQuestion); -// const [errorMsg, setErrorMsg] = useState(""); - -// //const handleOnSubmitBtnClicked = () => { -// // console.log("progress: ", questionState!.progress); -// // if (questionState!.progress < 100) { -// // setErrorMsg("Du skal besvare alle spørgsmål før du kan indsende"); -// // return; -// // } -// // onSubmitBtnClicked(); -// //} - -// // useHandleKeypress(handleOnSubmitBtnClicked); -// useDelayedClickListener(() => setErrorMsg("")); -// const onSubmitFieldsChange = useCallback>["onChange"]>((data, id) => { -// console.log("Changing Submit Fields", [data, id]); -// dispatch({ type: 'SET_OUTPUT', payload: data.formData }); -// }, [dispatch]); - -// if (submitStatus.isSubmitting) { -// return -// } - -// return ( -//
-// {!!submitFields && console.log('submitted')} -// onError={() => console.log('errors')} -// ><> } - -// {/* */} -// {/* Preview PDF..*/} -// {/**/} -// {errorMsg && } -//
-// ); -// } - +type SubmitProps = { + model: SubmitModel; +} -export const Submit: React.FC = ({ text, paragraph ,buttonText, submitFields = [] }: SubmitModel) => { +export const Submit: React.FC = ({ model }) => { const { state, dispatch } = useQuickForm(); + const { text, paragraph, buttonText, submitFields = [] } = model; if (state.submitStatus.isSubmitting) { return @@ -111,9 +49,11 @@ export const Submit: React.FC = ({ text, paragraph ,buttonText, sub
    {submitFields.map((sf, idx) => { - return (<> - - + if (sf.visible && sf.visible?.rule) { + return + } + return ( + ) })}
diff --git a/packages/core/src/model/SlideModel.ts b/packages/core/src/model/SlideModel.ts index ef6f3a8..cd9ad78 100644 --- a/packages/core/src/model/SlideModel.ts +++ b/packages/core/src/model/SlideModel.ts @@ -1,7 +1,7 @@ import { resolveQuickFormService } from "../services/QuickFormServices"; import { QuestionModel } from "./QuestionModel"; -import { QuestionJsonModel } from "./json/JsonDataModels"; -import { QuestionRef } from "./json/Layout"; +import { QuestionJsonModel } from "./json-definitions/JsonDataModels"; +import { QuestionRef } from "./json-definitions/Layout"; export class SlideModel { displayName?: string; @@ -17,17 +17,16 @@ export class SlideModel { return this.questions.length > 0 && this.questions.every(question => question.answered); } - addQuestion(layout: QuestionRef, question: QuestionJsonModel, payload: any, visible?: { type: string; rule: string; }) { + addQuestion(layout: QuestionRef, question: QuestionJsonModel, payload: any) { const mapJsonQuestionToModelQuestion = resolveQuickFormService("questionTransformer"); - const questionModel = mapJsonQuestionToModelQuestion(layout.ref, question, payload[question?.logicalName ?? layout.ref], visible) + const questionModel = mapJsonQuestionToModelQuestion(layout.ref, question, payload[question?.logicalName ?? layout.ref]) this.questions.push(questionModel); return { style: layout.style, type: "question", - ref: layout.ref, - visible: visible + ref: layout.ref } as QuestionLayout; } } diff --git a/packages/core/src/model/index.ts b/packages/core/src/model/index.ts index f920377..cf09f85 100644 --- a/packages/core/src/model/index.ts +++ b/packages/core/src/model/index.ts @@ -2,10 +2,10 @@ export * from "./EndingModel"; export * from "./QuickFormModel"; export * from "./InputType"; export * from "./IntroModel"; -export * from "./json/Layout"; +export * from "./json-definitions/Layout"; export * from "./QuestionModel"; export * from "./SlideModel"; export * from "./SubmitModel"; -export type { QuickFormDefinition } from "./QuickFormDefinition"; \ No newline at end of file +export type { QuickFormDefinition } from "./json-definitions/QuickFormDefinition"; \ No newline at end of file diff --git a/packages/core/src/model/json/JsonDataModels.ts b/packages/core/src/model/json-definitions/JsonDataModels.ts similarity index 86% rename from packages/core/src/model/json/JsonDataModels.ts rename to packages/core/src/model/json-definitions/JsonDataModels.ts index 585d9d8..637c6bc 100644 --- a/packages/core/src/model/json/JsonDataModels.ts +++ b/packages/core/src/model/json-definitions/JsonDataModels.ts @@ -7,6 +7,10 @@ type QuickFormQuestionDefinition = { placeholder?: string; paragraph: string; dataType?: "string" | "number" | "boolean"; + visible?: { + type: string; + rule: string; + } } export type QuestionJsonModel = @@ -16,5 +20,4 @@ export type QuestionJsonModel = QuickFormQuestionDefinition & MultilineProperties | QuickFormQuestionDefinition & RadioProperties | QuickFormQuestionDefinition & SliderProperties | - QuickFormQuestionDefinition & TextProperties - ; \ No newline at end of file + QuickFormQuestionDefinition & TextProperties; \ No newline at end of file diff --git a/packages/core/src/model/json/Layout.ts b/packages/core/src/model/json-definitions/Layout.ts similarity index 95% rename from packages/core/src/model/json/Layout.ts rename to packages/core/src/model/json-definitions/Layout.ts index 061610d..24f1f3d 100644 --- a/packages/core/src/model/json/Layout.ts +++ b/packages/core/src/model/json-definitions/Layout.ts @@ -56,8 +56,4 @@ export type QuestionRef = { style?: React.CSSProperties; type: "question"; ref: string; - visible?: { - type: string; - rule: string; - } } \ No newline at end of file diff --git a/packages/core/src/model/QuickFormDefinition.ts b/packages/core/src/model/json-definitions/QuickFormDefinition.ts similarity index 57% rename from packages/core/src/model/QuickFormDefinition.ts rename to packages/core/src/model/json-definitions/QuickFormDefinition.ts index b9c1590..d6b0627 100644 --- a/packages/core/src/model/QuickFormDefinition.ts +++ b/packages/core/src/model/json-definitions/QuickFormDefinition.ts @@ -1,8 +1,8 @@ -import { EndingModel } from "./EndingModel"; -import { IntroModel } from "./IntroModel"; +import { EndingModel } from "../EndingModel"; +import { IntroModel } from "../IntroModel"; import { QuickFormQuestionsDefinition } from "./QuickFormQuestionsDefinition"; -import { QuickFormSubmitDefinition } from "./json/QuickFormSubmitDefinition"; -import { Layout } from "./json/Layout"; +import { QuickFormSubmitDefinition } from "./QuickFormSubmitDefinition"; +import { Layout } from "./Layout"; export type QuickFormDefinition = { diff --git a/packages/core/src/model/QuickFormQuestionsDefinition.ts b/packages/core/src/model/json-definitions/QuickFormQuestionsDefinition.ts similarity index 59% rename from packages/core/src/model/QuickFormQuestionsDefinition.ts rename to packages/core/src/model/json-definitions/QuickFormQuestionsDefinition.ts index 31ba677..b16d1d0 100644 --- a/packages/core/src/model/QuickFormQuestionsDefinition.ts +++ b/packages/core/src/model/json-definitions/QuickFormQuestionsDefinition.ts @@ -1,3 +1,3 @@ -import { QuestionJsonModel } from "./json/JsonDataModels"; +import { QuestionJsonModel } from "./JsonDataModels"; export type QuickFormQuestionsDefinition = { [logicalName: string]: QuestionJsonModel; }; \ No newline at end of file diff --git a/packages/core/src/model/json/QuickFormSubmitDefinition.ts b/packages/core/src/model/json-definitions/QuickFormSubmitDefinition.ts similarity index 55% rename from packages/core/src/model/json/QuickFormSubmitDefinition.ts rename to packages/core/src/model/json-definitions/QuickFormSubmitDefinition.ts index ac2a84f..4eeaab1 100644 --- a/packages/core/src/model/json/QuickFormSubmitDefinition.ts +++ b/packages/core/src/model/json-definitions/QuickFormSubmitDefinition.ts @@ -1,15 +1,11 @@ -import { QuestionJsonModel } from "./JsonDataModels"; - - - - + export type QuickFormSubmitDefinition = { text: string; - // paragraphs: string[]; + // paragraphs: string[]; buttonText: string; submitFields: { - schema: any, uiSchema: any + schema: any, uiSchema: any }; submitUrl: string; submitMethod: string; -}; +}; \ No newline at end of file diff --git a/packages/core/src/model/json/inputtype.json b/packages/core/src/model/json/inputtype.json deleted file mode 100644 index e69de29..0000000 diff --git a/packages/core/src/model/json/question.json b/packages/core/src/model/json/question.json deleted file mode 100644 index 93520f6..0000000 --- a/packages/core/src/model/json/question.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "inputType": { - "type": "object", - "properties": { - "type": { - "type": "string" - } - }, - "required": [ - "type" - ] - }, - "text": { - "type": "string" - }, - "paragraph": { - "type": "string" - }, - "placeholder": { - "type": "string" - }, - "lang": { - "type": "string", - "enum": [ - "EN" - ] - }, - "minItems": { - "type": "integer", - "minimum": 1, - "default": 1 - }, - "maxItems": { - "type": "integer", - "maximum": 1, - "default": 1 - }, - "options": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "required": [ - "inputType", - "text", - "paragraph", - "placeholder", - "lang" - ] -} \ No newline at end of file diff --git a/packages/core/src/model/legacy/QuestionModel.ts b/packages/core/src/model/legacy/QuestionModel.ts deleted file mode 100644 index 7fa0dd4..0000000 --- a/packages/core/src/model/legacy/QuestionModel.ts +++ /dev/null @@ -1,50 +0,0 @@ -export type BaseQuestionFields = { - readonly logicalName?: string; - readonly text: string, - readonly paragraph?: string; - readonly buttonText?: string - readonly placeholder?: string; - readonly lang?: string; -} - -export type DropdownProps = { - readonly inputType: "dropdown", - readonly maxItems?: string; - readonly minItems?: string; - readonly options?: { - [key: string]: string; - } -} & BaseQuestionFields - -type ModelTypeChecker> = (q: QuestionModel) => q is T; -type ModelTypeAsserter> = (q: QuestionModel) => T; - -export function isDropDownModel(q: QuestionModel): q is QuestionModel { - return q.inputType === "dropdown"; -} - -function assertModel>(q: QuestionModel, checker: ModelTypeChecker) { - if (checker(q)) - return q; - throw new Error("Current Question Model is not submit type"); - -} -export const assertDropDownModel: ModelTypeAsserter> = (q) => assertModel(q, isDropDownModel); -export const shouldValidateInputType = (inputType: InputType2) => !(inputType === "intro" || inputType === "ending"); - -export type QuestionPropsGeneric = { - readonly inputType?: "text" | "multilinetext" | "intro" | "ending"; - readonly buttonText?: string; -} & BaseQuestionFields - -export type QuestionProps = QuestionPropsGeneric | DropdownProps; -export type IntroProps = QuestionPropsGeneric; -export type InputType2 = QuestionProps["inputType"] -export type QuestionModel = { - /* Represents the variables that we use internally to represent state in the application. */ - output: any; - questionNumber: number;// = 0; - answered?: boolean; -} & T - - diff --git a/packages/core/src/services/QuickFormServices.ts b/packages/core/src/services/QuickFormServices.ts index db96470..f06fc55 100644 --- a/packages/core/src/services/QuickFormServices.ts +++ b/packages/core/src/services/QuickFormServices.ts @@ -1,7 +1,7 @@ import { InputPropertiesTypes, QuestionModel, QuickFormModel } from "../model"; import { QuickFormDefinition } from "../model"; -import { QuestionJsonModel } from "../model/json/JsonDataModels"; +import { QuestionJsonModel } from "../model/json-definitions/JsonDataModels"; import { InputComponentType } from "./defaults/DefaultInputTypeResolver"; export type HeadingNumberDisplayProvider = () => boolean; diff --git a/packages/core/src/services/defaults/DefaultInputTypeResolver.ts b/packages/core/src/services/defaults/DefaultInputTypeResolver.ts index 352420a..ffd3b15 100644 --- a/packages/core/src/services/defaults/DefaultInputTypeResolver.ts +++ b/packages/core/src/services/defaults/DefaultInputTypeResolver.ts @@ -1,6 +1,6 @@ import { FC } from "react"; import { DropDownProperties, RadioProperties, SliderProperties, ButtonsProperties, InputPropertiesTypes, InputProps } from "../../model"; -import { QuestionJsonModel } from "../../model/json/JsonDataModels"; +import { QuestionJsonModel } from "../../model/json-definitions/JsonDataModels"; import { registerQuickFormService } from "../QuickFormServices"; import { TextInput, MultilineInput, DropDownInput } from "../../components/question/input-types/index"; diff --git a/packages/core/src/services/defaults/DefaultModelTransformer.ts b/packages/core/src/services/defaults/DefaultModelTransformer.ts index 5d3657b..e57b2b9 100644 --- a/packages/core/src/services/defaults/DefaultModelTransformer.ts +++ b/packages/core/src/services/defaults/DefaultModelTransformer.ts @@ -1,9 +1,9 @@ import { Column, Layout, QuestionModel, QuestionRef, Row, SlideElements, SlideModel, SubmitModel } from "../../model"; import { QuickFormModel } from "../../model/QuickFormModel"; -import { QuickFormQuestionsDefinition } from "../../model/QuickFormQuestionsDefinition"; +import { QuickFormQuestionsDefinition } from "../../model/json-definitions/QuickFormQuestionsDefinition"; import { QuickFormModelTransformer, registerQuickFormService, resolveQuickFormService } from "../QuickFormServices"; -import { QuestionJsonModel } from "../../model/json/JsonDataModels"; -import { QuickFormSubmitDefinition } from "../../model/json/QuickFormSubmitDefinition"; +import { QuestionJsonModel } from "../../model/json-definitions/JsonDataModels"; +import { QuickFormSubmitDefinition } from "../../model/json-definitions/QuickFormSubmitDefinition"; function isDefined(object?: object) { @@ -26,7 +26,7 @@ function processRows(rowLayouts: SlideElements, slide: SlideModel, questions: Qu if (!question) return; - rows.push(slide.addQuestion(rowLayout, question, payload, rowLayout.visible)); + rows.push(slide.addQuestion(rowLayout, question, payload)); break; @@ -48,7 +48,7 @@ function processRows(rowLayouts: SlideElements, slide: SlideModel, questions: Qu return; } - columns.push(slide.addQuestion(columnLayout, question, payload, columnLayout.visible)); + columns.push(slide.addQuestion(columnLayout, question, payload)); } else { @@ -183,7 +183,6 @@ const transformJSONInput: QuickFormModelTransformer = (definition, payload): Qui let slides: SlideModel[]; const logger = resolveQuickFormService("logger"); - console.log(JSON.stringify(definition.questions, null, 4)); logger.log("Transforming Quickform Def to Model with\n\nlayout:\n{@layout}\nquestions:\n{@questions}\nsubmit:\n{@submit}\npayload:\n{@payload}", definition.layout, definition.questions, definition.submit, payload); // Transform questions into slides with rows and columns diff --git a/packages/core/src/services/defaults/DefaultQuestionTransformer.ts b/packages/core/src/services/defaults/DefaultQuestionTransformer.ts index 927b2c3..edf0fc8 100644 --- a/packages/core/src/services/defaults/DefaultQuestionTransformer.ts +++ b/packages/core/src/services/defaults/DefaultQuestionTransformer.ts @@ -1,14 +1,12 @@ import { QuestionModel } from "../../model"; -import { QuestionJsonModel } from "../../model/json/JsonDataModels"; +import { QuestionJsonModel } from "../../model/json-definitions/JsonDataModels"; import { registerQuickFormService, resolveQuickFormService } from "../QuickFormServices"; -function mapJsonQuestionToModelQuestion(key: string, question: QuestionJsonModel, value?: any, visible?: { type: string; rule: string; }): QuestionModel { +function mapJsonQuestionToModelQuestion(key: string, question: QuestionJsonModel, value?: any): QuestionModel { const parseInputProperties = resolveQuickFormService("inputTypePropertiesTransformer"); const logger = resolveQuickFormService("logger"); - - if (question.inputType === "dropdown" && question.dataType === "boolean") value = value === true ? 'Y' : value === false ? 'N' : ''; @@ -25,7 +23,7 @@ function mapJsonQuestionToModelQuestion(key: string, question: QuestionJsonModel answered: typeof (value) !== "undefined" && value !== '' && value !== null, inputProperties: parseInputProperties(question), output: value ?? '', - visible: visible + visible: question.visible } as QuestionModel; } registerQuickFormService("questionTransformer", mapJsonQuestionToModelQuestion); \ No newline at end of file diff --git a/packages/core/src/state/QuickformProvider.tsx b/packages/core/src/state/QuickformProvider.tsx index dd29615..cd8d090 100644 --- a/packages/core/src/state/QuickformProvider.tsx +++ b/packages/core/src/state/QuickformProvider.tsx @@ -22,15 +22,12 @@ export const QuickFormProvider: React.FC = ({ children, const defaultStateObj = useMemo(() => { return defaultState(transform(definition, payload)) }, []); const [state, dispatch] = useReducer(quickformReducer, defaultStateObj); - // console.log(JSON.stringify(defaultStateObj, null, 4)); - const goToSlide = (index: number) => { dispatch({ type: 'SET_INDEX', index: index }); }; const goToNextSlide = () => { dispatch({ type: 'NEXT_SLIDE' }); }; const goToPrevSlide = () => { dispatch({ type: 'PREV_SLIDE' }); }; const answerQuestion = (logicalName: string, output: any) => { dispatch({ type: 'ANSWER_QUESTION', logicalName: logicalName, output: output }) }; const setIntroVisited = () => { dispatch({ type: 'SET_INTRO_VISITED' }) }; const setErrorMsg = (msg: string) => { - console.log("errorMsg"); dispatch({ type: "SET_ERROR_MSG", msg: msg }) }; const isFirstQuestionInCurrentSlide = (questionLogicalName: string) => { diff --git a/packages/core/src/state/QuickformReducer.ts b/packages/core/src/state/QuickformReducer.ts index baf13c3..610d569 100644 --- a/packages/core/src/state/QuickformReducer.ts +++ b/packages/core/src/state/QuickformReducer.ts @@ -29,7 +29,4 @@ export const quickformReducer = (state: QuickformState, action: QuickformAction) case "GO_TO_ENDING": return { ...state, isSubmitSlide: false, isEndingSlide: true } default: return state; } -}; - - - +}; \ No newline at end of file diff --git a/packages/core/src/state/action-handlers/NavigationActionHandler.ts b/packages/core/src/state/action-handlers/NavigationActionHandler.ts index bebad04..f90997d 100644 --- a/packages/core/src/state/action-handlers/NavigationActionHandler.ts +++ b/packages/core/src/state/action-handlers/NavigationActionHandler.ts @@ -1,17 +1,6 @@ import { QuickformState } from "../../state/QuickformState"; export class NavigationActionHandler { - static handleSetIndexAction = (state: QuickformState, newIndex: number): QuickformState => { - if (newIndex >= 0 && newIndex < state.slides.length) { - return { - ...state, - currIdx: newIndex, - currStep: newIndex + 1, - }; - } - return state; - }; - private static handleSlideChange = (state: QuickformState, direction: 'next' | 'prev') => { const currIdx = state.currIdx; const slides = state.slides; @@ -39,4 +28,14 @@ export class NavigationActionHandler { static handlePrevSlideAction = (state: QuickformState) => { return NavigationActionHandler.handleSlideChange(state, 'prev'); } + static handleSetIndexAction = (state: QuickformState, newIndex: number): QuickformState => { + if (newIndex >= 0 && newIndex < state.slides.length) { + return { + ...state, + currIdx: newIndex, + currStep: newIndex + 1, + }; + } + return state; + }; } \ No newline at end of file diff --git a/packages/core/src/state/action-handlers/QuestionActionHandler.ts b/packages/core/src/state/action-handlers/QuestionActionHandler.ts index ba88aac..6cfd55d 100644 --- a/packages/core/src/state/action-handlers/QuestionActionHandler.ts +++ b/packages/core/src/state/action-handlers/QuestionActionHandler.ts @@ -21,7 +21,7 @@ export class QuestionActionHandler { const { slideIndex, questionIndex } = this.findSlideIdxAndQuestionIdx(state, logicalName); const logger = resolveQuickFormService("logger"); - logger.log("Updating Question Property: {logicalName}, {propertyName}, {propertyValue}, {slideIndex}, {questionIndex}", logicalName, propertyName, propertyValue, slideIndex, questionIndex); + logger.log("Updating Question: {logicalName}.{propertyName} = {propertyValue}. slideIndex: {slideIndex}, questionIndex: {questionIndex}", logicalName, propertyName, propertyValue, slideIndex, questionIndex); if (state.isSubmitSlide) { const questions = state.data.submit.submitFields; @@ -63,9 +63,7 @@ export class QuestionActionHandler { }; static answerQuestion = (state: QuickformState, logicalName: string, output: any) => { - console.log(`answering question for ${logicalName} with output ${output}`); const progressUpdated = this.computeProgress(this.updateQuestionProperty(this.updateQuestionProperty(state, logicalName, 'answered', true), logicalName, 'output', output)); - console.log("progressUpdated", progressUpdated); return progressUpdated; }; diff --git a/packages/core/src/state/action-handlers/SubmitActionHandler.ts b/packages/core/src/state/action-handlers/SubmitActionHandler.ts index 390d70f..09c9075 100644 --- a/packages/core/src/state/action-handlers/SubmitActionHandler.ts +++ b/packages/core/src/state/action-handlers/SubmitActionHandler.ts @@ -89,4 +89,3 @@ export class SubmitActionHandler { // dispatch({ // type: 'PDF_PREVIEW', url: `/api/files/${document.body.document}?content-disposition=inline` // }); -// console.log("Submit data", document); \ No newline at end of file diff --git a/packages/core/src/utils/questionUtils.ts b/packages/core/src/utils/questionUtils.ts new file mode 100644 index 0000000..25679b4 --- /dev/null +++ b/packages/core/src/utils/questionUtils.ts @@ -0,0 +1,5 @@ +import { QuestionModel } from "../model/QuestionModel"; + +export const findQuestionByLogicalName = (logicalName: string, questions: QuestionModel[]): QuestionModel | undefined => { + return questions.find(q => q.logicalName === logicalName); +}; \ No newline at end of file diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index d9a4873..a21a1b3 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es2015", - "lib": [ "dom", "dom.iterable", "esnext" ], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": false, @@ -13,12 +17,18 @@ "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", - "baseUrl": "./src", - "paths": { - }, - "rootDirs": [ "src" ], + "baseUrl": "./", + "paths": {}, + "rootDirs": [ + "./" + ], "experimentalDecorators": true }, - "exclude": [ "node_modules" ], - "include": [ "**/*.ts", "**/*.tsx" ] -} + "exclude": [ + "node_modules" + ], + "include": [ + "**/*.ts", + "**/*.tsx" + ] +} \ No newline at end of file diff --git a/packages/playground/src/App.tsx b/packages/playground/src/App.tsx index 33d4c58..0399ad5 100644 --- a/packages/playground/src/App.tsx +++ b/packages/playground/src/App.tsx @@ -14,12 +14,10 @@ export const App = () => { const [editorValue, setEditorValue] = useState(JSON.stringify(carp)); const onChangeEditorValue = (value: string) => { - console.log("Editor input changed"); setEditorValue(value); } const updateQuickForm = () => { - console.log("QuickForm updated."); setSelectedTemplate(() => JSON.parse(editorValue)); setHackToChangeQuickForm(() => hackToChangeQuickForm + 1); } From ebc0f7e8aa36435aafff7446793a4ff95b846efa Mon Sep 17 00:00:00 2001 From: Kasper Baun Date: Fri, 1 Mar 2024 22:59:04 +0100 Subject: [PATCH 3/3] fix: adressed comments on pr about --- .../column-renderer/ColumnRenderer.tsx | 28 ++++++++- .../conditional-render/ConditionalRender.tsx | 59 ++++++++----------- .../renderers/row-renderer/RowRenderer.tsx | 15 ++++- .../core/src/components/submit/Submit.tsx | 10 +++- 4 files changed, 75 insertions(+), 37 deletions(-) diff --git a/packages/core/src/components/renderers/column-renderer/ColumnRenderer.tsx b/packages/core/src/components/renderers/column-renderer/ColumnRenderer.tsx index d9f50fa..89f0028 100644 --- a/packages/core/src/components/renderers/column-renderer/ColumnRenderer.tsx +++ b/packages/core/src/components/renderers/column-renderer/ColumnRenderer.tsx @@ -28,7 +28,19 @@ export const ColumnRenderer: React.FC = ({ column, question const question = findQuestionByLogicalName(column.ref!, questions); if (!question) return null; if (question.visible && question.visible?.rule) { - return + return ( + + + + ) } return } @@ -40,7 +52,19 @@ export const ColumnRenderer: React.FC = ({ column, question const question = findQuestionByLogicalName(innerRow.ref!, questions); if (!question) return null; if (question.visible && question.visible?.rule) { - return + return ( + + + + ) } return } else { diff --git a/packages/core/src/components/renderers/conditional-render/ConditionalRender.tsx b/packages/core/src/components/renderers/conditional-render/ConditionalRender.tsx index cae9b3b..beb82f1 100644 --- a/packages/core/src/components/renderers/conditional-render/ConditionalRender.tsx +++ b/packages/core/src/components/renderers/conditional-render/ConditionalRender.tsx @@ -1,51 +1,44 @@ -import { QuestionModel } from "../../../model/QuestionModel"; import { useEffect, useState } from "react"; -import React from "react"; -import { resolveQuickFormService } from "../../../services/QuickFormServices"; import { useQuickForm } from "../../../state"; -import { Question } from "../../../components/question/Question"; type ConditionalRenderProps = { - model: QuestionModel; - style?: React.CSSProperties; + engine: string; + rule: string; + children: JSX.Element; } -export const ConditionalRender: React.FC = ({ model, style }) => { - const logger = resolveQuickFormService("logger"); +export const ConditionalRender = ({ engine, rule, children }: ConditionalRenderProps) => { const [visible, setIsVisible] = useState(false); const { getCurrentSlide } = useQuickForm(); - logger.log("ConditionalRender for question {@model} InputProps", model); + // const logger = resolveQuickFormService("logger"); + // logger.log("ConditionalRender for question {@model} InputProps", model); - // function evalInScope(js: string, contextAsScope: any) { - // return new Function(`with (this) { return (${js}); }`).call(contextAsScope); - // } + useEffect(() => { + const shouldRender = functionInScope(rule, { getCurrentSlide }); + setIsVisible(shouldRender) + }, [getCurrentSlide().questions]) - interface Context { - [key: string]: any; + if (!visible) { + return null; } - function functionInScope(js: string, context: Context): boolean { - const keys = Object.keys(context); - const values = keys.map(key => context[key]); + return children; +} - const func: Function = new Function(...keys, `return ${js};`); +interface Context { + [key: string]: any; +} - return (func as (...args: any[]) => any)(...values); - } +function functionInScope(js: string, context: Context): boolean { + const keys = Object.keys(context); + const values = keys.map(key => context[key]); - useEffect(() => { - if (model.visible && model.visible?.rule) { - const shouldRender = functionInScope(model.visible.rule, { getCurrentSlide }); - setIsVisible(shouldRender) - } - }, [getCurrentSlide().questions]) + const func: Function = new Function(...keys, `return ${js};`); - if (!visible) { - return null; - } + return (func as (...args: any[]) => any)(...values); +} - return ( - - ) -} \ No newline at end of file +// function evalInScope(js: string, contextAsScope: any) { +// return new Function(`with (this) { return (${js}); }`).call(contextAsScope); +// } \ No newline at end of file diff --git a/packages/core/src/components/renderers/row-renderer/RowRenderer.tsx b/packages/core/src/components/renderers/row-renderer/RowRenderer.tsx index 3ec2e35..84c10d9 100644 --- a/packages/core/src/components/renderers/row-renderer/RowRenderer.tsx +++ b/packages/core/src/components/renderers/row-renderer/RowRenderer.tsx @@ -32,7 +32,20 @@ export const RowRenderer: React.FC = ({ row, questions }) => { if (!question) return null; if (question.visible && question.visible?.rule) { - return + + return ( + + + + ) } return diff --git a/packages/core/src/components/submit/Submit.tsx b/packages/core/src/components/submit/Submit.tsx index 63095bc..4b45461 100644 --- a/packages/core/src/components/submit/Submit.tsx +++ b/packages/core/src/components/submit/Submit.tsx @@ -50,7 +50,15 @@ export const Submit: React.FC = ({ model }) => {
    {submitFields.map((sf, idx) => { if (sf.visible && sf.visible?.rule) { - return + return ( + + + + ) } return (