diff --git a/shared/src/constants.ts b/shared/src/constants.ts index 8040b9c9..30777c40 100644 --- a/shared/src/constants.ts +++ b/shared/src/constants.ts @@ -4,10 +4,11 @@ export const baseURL = : (window as any).BASE_URL; export const juteURL = (window as any).JUTE_URL === '{{JUTE_URL}}' - ? 'http://localhost:8090/' + ? 'http://localhost:8099/' : (window as any).JUTE_URL; export const aiQuestionnaireBuilderUrl = 'https://builder.emr.beda.software'; // export const aiQuestionnaireBuilderUrl = 'http://localhost:3002'; export const fhirpathMappingUrl = 'https://fhirpathmapper.emr.beda.software'; // export const fhirpathMappingUrl = 'http://localhost:8091'; +export const fhirMappingLanguageUrl = 'http://localhost:8084/matchboxv3/fhir'; diff --git a/vite.config.ts b/vite.config.ts index 609b5d8e..28f81749 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -10,7 +10,7 @@ const require = createRequire(import.meta.url); export const getBaseConfig = ({ plugins = [], build = {}, test = {} }) => defineConfig(({ command }) => ({ server: { - port: command === 'build' ? 5000 : 3000, + port: command === 'build' ? 5000 : 3001, }, plugins: [ viteCommonjs(), diff --git a/web/package.json b/web/package.json index ccf0a09e..9b86aab9 100644 --- a/web/package.json +++ b/web/package.json @@ -9,6 +9,7 @@ "@codemirror/legacy-modes": "^6.3.3", "@codemirror/state": "^6.2.1", "@codemirror/view": "^6.16.0", + "@monaco-editor/react": "^4.6.0", "@sentry/browser": "^6.19.7", "@sentry/react": "^6.19.7", "@sentry/tracing": "^6.19.7", @@ -31,6 +32,7 @@ "@types/react-toastify": "^4.1.0", "@types/yaml": "^1.9.7", "aidbox-react": "^1.4.0", + "allotment": "^1.20.2", "babel-loader": "8.1.0", "babel-plugin-import": "^1.13.3", "classnames": "^2.3.1", diff --git a/web/src/components/Cell/Cell.module.scss b/web/src/components/Cell/Cell.module.scss new file mode 100644 index 00000000..d82953b7 --- /dev/null +++ b/web/src/components/Cell/Cell.module.scss @@ -0,0 +1,33 @@ +@import 'src/styles/colors'; + +.title { + display: flex; + line-height: 32px; + margin-bottom: 3px; +} + +.container { + width: 100%; + height: 100%; + overflow: auto; + display: flex; + flex-direction: column; +} + +.evenContainer { + background-color: $base-light-color; +} + +.oddContainer { + background-color: $base-surface-color; +} + +.content { + flex: 1; + padding: 20px; + overflow-y: auto; +} + +.boxHeader { + position: relative; +} diff --git a/web/src/components/Cell/index.tsx b/web/src/components/Cell/index.tsx new file mode 100644 index 00000000..6658df63 --- /dev/null +++ b/web/src/components/Cell/index.tsx @@ -0,0 +1,23 @@ +import classNames from 'classnames'; + +import s from './Cell.module.scss'; + +interface CellProps extends React.HTMLAttributes { + title?: string; + even?: boolean; +} + +export function Cell({ title, children, even }: CellProps) { + const isEven = even ?? false; + + return ( +
+
+
+

{title}

+
+ {children} +
+
+ ); +} diff --git a/web/src/components/ExpandableElement/ExpandableElement.module.scss b/web/src/components/ExpandableElement/ExpandableElement.module.scss deleted file mode 100644 index 1ecf5ddb..00000000 --- a/web/src/components/ExpandableElement/ExpandableElement.module.scss +++ /dev/null @@ -1,74 +0,0 @@ -@import 'src/styles/colors'; - -.title { - line-height: 32px; - margin-bottom: 3px; -} - -.container { - position: relative; - display: flex; - flex-direction: column; - flex: 1; - transition: all 0.2s; - overflow-x: auto; - - &:hover .expandButton { - opacity: 1; - } - - &:nth-child(even) { - background-color: $base-surface-color; - } - - &:nth-child(odd) { - background-color: $base-light-color; - } -} - -.content { - flex: 1; - padding: 20px; - overflow-y: auto; -} - -.boxHeader { - position: relative; -} - -.expandButton { - position: absolute; - right: -20px; - top: 50%; - transform: translate(0px, -50%); - background-color: $primary-color; - color: $base-light-color; - font-size: 12px; - line-height: 1; - padding: 4px 18px 4px 8px; - border-radius: 1px 0px 0px 1px; - cursor: pointer; - opacity: 0; - transition: all 0.2s; - - &::after { - content: ''; - position: absolute; - width: 4px; - height: 4px; - top: calc(50% - 1px); - transform: rotate(45deg) translate(0px, -50%); - right: 8px; - border-top: 2px solid $base-light-color; - border-right: 2px solid $base-light-color; - } -} - -.expanded { - &::after { - border-top: 0px solid $base-light-color; - border-right: 0px solid $base-light-color; - border-bottom: 2px solid $base-light-color; - border-left: 2px solid $base-light-color; - } -} diff --git a/web/src/components/ExpandableElement/index.tsx b/web/src/components/ExpandableElement/index.tsx deleted file mode 100644 index d5d8716b..00000000 --- a/web/src/components/ExpandableElement/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import classNames from 'classnames'; -import { useRef, useState } from 'react'; - -import s from './ExpandableElement.module.scss'; - -interface ExpandableElementProps extends React.HTMLAttributes { - title?: string; -} - -export function ExpandableElement({ title, className, children }: ExpandableElementProps) { - const [expanded, setExpanded] = useState(false); - const headerRef = useRef(null); - - return ( -
-
-
-

{title}

- {expanded ? ( -
{ - if (e.target === headerRef.current) { - setExpanded((f) => !f); - } - }} - > - {expanded ? 'collapse' : 'expand'} -
- ) : ( -
{ - if (e.target === headerRef.current) { - setExpanded((f) => !f); - } - }} - > - {expanded ? 'collapse' : 'expand'} -
- )} -
- {children} -
-
- ); -} diff --git a/web/src/components/ExpandableRow/ExpandableRow.module.scss b/web/src/components/ExpandableRow/ExpandableRow.module.scss deleted file mode 100644 index a7964a91..00000000 --- a/web/src/components/ExpandableRow/ExpandableRow.module.scss +++ /dev/null @@ -1,16 +0,0 @@ -.arrow { - position: absolute; - left: 0; - cursor: pointer; - z-index: 1; - padding: 0; - margin: 0; - background: 0; - outline: none; - border: 0; - height: 24px; - width: 24px; - display: flex; - justify-content: center; - align-items: center; -} diff --git a/web/src/components/ExpandableRow/index.tsx b/web/src/components/ExpandableRow/index.tsx deleted file mode 100644 index 544751b8..00000000 --- a/web/src/components/ExpandableRow/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useState } from 'react'; - -import s from './ExpandableRow.module.scss'; -import { Arrow } from '../Icon/Arrow'; - -interface Props extends React.HTMLAttributes {} - -export function ExpandableRow(props: Props) { - const { className, children } = props; - const [expanded, setExpanded] = useState(false); - - return ( -
- - {children} -
- ); -} diff --git a/web/src/containers/Main/MappingEditor/MappingEditorEditor/index.tsx b/web/src/containers/Main/MappingEditor/MappingEditorEditor/index.tsx new file mode 100644 index 00000000..c198ac4d --- /dev/null +++ b/web/src/containers/Main/MappingEditor/MappingEditorEditor/index.tsx @@ -0,0 +1,65 @@ +import classNames from 'classnames'; +import { WithId } from 'fhir-react'; +import { Button } from 'web/src/components/Button'; +import { ResourceCodeEditor } from 'web/src/components/ResourceCodeEditor'; + +import { Mapping } from 'shared/src/contrib/aidbox'; + +import { MappingEditorEditorProps } from '../interfaces'; +import s from '../MappingEditor.module.scss'; + +export function MappingEditorEditor(props: MappingEditorEditorProps) { + const { + reload, + onChange, + updatedResource, + onSave, + setUpdatedResource, + mapping, + launchContext, + questionnaireResponseRD, + setEditorSelect, + } = props; + + return ( + <> + > + reload={() => { + reload(); + setUpdatedResource(undefined); + }} + resource={mapping} + onChange={(updatedMapping) => { + setUpdatedResource(updatedMapping); + onChange(updatedMapping); + }} + launchContext={launchContext} + questionnaireResponseRD={questionnaireResponseRD} + /> +
+ + +
+ + ); +} diff --git a/web/src/containers/Main/MappingEditor/MappingEditorError/index.tsx b/web/src/containers/Main/MappingEditor/MappingEditorError/index.tsx new file mode 100644 index 00000000..1716a0f8 --- /dev/null +++ b/web/src/containers/Main/MappingEditor/MappingEditorError/index.tsx @@ -0,0 +1,31 @@ +import { Button } from 'web/src/components/Button'; + +import { formatError } from 'fhir-react/lib/utils/error'; + +import { MappingEditorErrorProps } from '../interfaces'; +import s from '../MappingEditor.module.scss'; + +export function MappingEditorError(props: MappingEditorErrorProps) { + const { error, setEditorSelect, editorState } = props; + + return editorState !== 'ready' ? ( +
+ {formatError(error)} + {error?.id === 'not-found' ? ( +
+ +
+ ) : null} +
+ ) : ( +
+ ); +} diff --git a/web/src/containers/Main/MappingEditor/MappingEditorSelect/index.tsx b/web/src/containers/Main/MappingEditor/MappingEditorSelect/index.tsx new file mode 100644 index 00000000..54c50d18 --- /dev/null +++ b/web/src/containers/Main/MappingEditor/MappingEditorSelect/index.tsx @@ -0,0 +1,106 @@ +import { SingleValue } from 'react-select'; +import { toast } from 'react-toastify'; +import { Button } from 'web/src/components/Button'; +import { ModalCreateMapper } from 'web/src/components/ModalCreateMapper'; +import { Select } from 'web/src/components/Select'; + +import { RenderRemoteData } from 'fhir-react/lib/components/RenderRemoteData'; +import { isFailure, isSuccess } from 'fhir-react/lib/libs/remoteData'; +import { formatError } from 'fhir-react/lib/utils/error'; + +import formStyles from '../../../../components/QRFormWrapper/QuestionnaireResponseForm.module.scss'; +import { PromptForm } from '../../PromptForm'; +import { MappingEditorSelectProps } from '../interfaces'; +import s from '../MappingEditor.module.scss'; + +export function MappingEditorSelect(props: MappingEditorSelectProps) { + const { + mappingsRD, + mappingRD, + showModal, + generateMapping, + createMapping, + setShowModal, + toggleMappingMode, + onChange, + setEditorSelect, + } = props; + + return ( + + {(mappings) => ( + <> +
+
Choose mapper from the list
+ ({ - value: mapping, - label: mapping.id, - }))} - onChange={(option) => { - if (option && !Array.isArray(option)) { - setShowSelect(false); - onChange((option as SingleValue).value); - } - }} - /> -
-
- setShowSelect(false)} - label="or describe requirements to new mapper" - /> -
- - -
- - {showModal ? ( - { - const response = await createMapping({ - resourceType: 'Mapping', - id: mappingId, - body: {}, - }); - - if (isSuccess(response)) { - setShowSelect(false); - } - - if (isFailure(response)) { - toast.error(formatError(response.error)); - } - }} - closeModal={() => setShowModal(false)} - mappings={mappings} - /> - ) : null} - - )} - - ); - }; - - const renderEditor = (mapping: WithId) => { - return ( - <> - > - {...props} - reload={() => { - reload(); - setUpdatedResource(undefined); - }} - resource={mapping} - onChange={(updatedMapping) => { - setUpdatedResource(updatedMapping); - onChange(updatedMapping); - }} - /> -
- - -
- - ); - }; - - return ( -
+ mappingsRD, + showModal, + setShowModal, + setUpdatedResource, + updatedResource, + editorState, + setEditorSelect, + } = useMappingEditor(questionnaireRD, mappingRD); + + const mapEditorStateRender = { + initial: <>Please, select/create Questionnaire resource, + loading: <>Loading..., + select: ( + + ), + ready: ( - !showSelect ? ( -
- {formatError(error)} - {error?.id === 'not-found' ? ( -
- -
- ) : null} -
- ) : ( -
- ) - } + renderFailure={(error) => ( + + )} remoteData={mappingRD} > - {(mapping) => <>{!showSelect ? renderEditor(mapping) : null}} + {(mapping) => { + return ( + + ); + }} - {showSelect ? renderSelect() : null} -
- ); + ), + }; + + return
{mapEditorStateRender[editorState]}
; } diff --git a/web/src/containers/Main/MappingEditor/interfaces.ts b/web/src/containers/Main/MappingEditor/interfaces.ts new file mode 100644 index 00000000..483e79b9 --- /dev/null +++ b/web/src/containers/Main/MappingEditor/interfaces.ts @@ -0,0 +1,47 @@ +import { Questionnaire, Parameters, QuestionnaireResponse } from 'fhir/r4b'; + +import { RemoteData, RemoteDataResult } from 'fhir-react/lib/libs/remoteData'; +import { WithId } from 'fhir-react/lib/services/fhir'; + +import { Mapping } from 'shared/src/contrib/aidbox'; + +export type EditorState = 'initial' | 'loading' | 'select' | 'ready'; + +interface CommonMappingEditorProps { + onSave: (resource: WithId) => void; + onChange: (resource: WithId) => void; + reload: () => void; + createMapping: (mapping: Mapping) => Promise>; + generateMapping: (prompt: string) => Promise>; + toggleMappingMode: () => void; +} + +export interface MappingEditorProps extends CommonMappingEditorProps { + questionnaireRD: RemoteData; + mappingRD: RemoteData>; + launchContext: Parameters; + questionnaireResponseRD: RemoteData; +} + +export interface MappingEditorErrorProps { + error: any; + setEditorSelect: () => void; + editorState: EditorState; +} + +export interface MappingEditorSelectProps extends CommonMappingEditorProps { + mappingsRD: RemoteData[], any>; + mappingRD: RemoteData>; + showModal: boolean; + setShowModal: (value: boolean) => void; + setEditorSelect: () => void; +} + +export interface MappingEditorEditorProps extends CommonMappingEditorProps { + updatedResource: WithId | undefined; + setUpdatedResource: (value: WithId | undefined) => void; + mapping: WithId; + launchContext: Parameters; + questionnaireResponseRD: RemoteData; + setEditorSelect: () => void; +} diff --git a/web/src/containers/Main/MappingEditor/useMappingEditor.ts b/web/src/containers/Main/MappingEditor/useMappingEditor.ts index ba8f4217..5bf2414f 100644 --- a/web/src/containers/Main/MappingEditor/useMappingEditor.ts +++ b/web/src/containers/Main/MappingEditor/useMappingEditor.ts @@ -1,4 +1,5 @@ import { Bundle, Questionnaire } from 'fhir/r4b'; +import { useEffect, useState } from 'react'; import { getFHIRResources as getAidboxFHIRResources } from 'aidbox-react/lib/services/fhir'; @@ -7,6 +8,9 @@ import { RemoteData, RemoteDataResult, failure, + isFailure, + isLoading, + isNotAsked, isSuccess, success, } from 'fhir-react/lib/libs/remoteData'; @@ -15,9 +19,47 @@ import { mapSuccess } from 'fhir-react/lib/services/service'; import { Mapping } from 'shared/src/contrib/aidbox'; +import { EditorState } from './interfaces'; import { getMappings } from '../utils'; -export function useMappingEditor(questionnaireRD: RemoteData) { +export function useMappingEditor( + questionnaireRD: RemoteData, + mappingRD: RemoteData, +) { + const [showModal, setShowModal] = useState(false); + const [updatedResource, setUpdatedResource] = useState | undefined>(); + const [editorState, setEditorState] = useState('initial'); + + const setEditorInitial = () => setEditorState('initial'); + const setEditorLoading = () => setEditorState('loading'); + const setEditorSelect = () => setEditorState('select'); + const setEditorReady = () => setEditorState('ready'); + + useEffect(() => { + if (isNotAsked(questionnaireRD)) { + setEditorInitial(); + } + if (isLoading(questionnaireRD)) { + setEditorLoading(); + } + + if (isFailure(questionnaireRD)) { + setEditorInitial(); + } + + if (isSuccess(questionnaireRD)) { + if (isSuccess(mappingRD)) { + setEditorReady(); + } + if (isNotAsked(mappingRD)) { + setEditorInitial(); + } + if (isFailure(mappingRD)) { + setEditorSelect(); + } + } + }, [questionnaireRD, mappingRD]); + const [mappingsRD] = useService(async () => { if (isSuccess(questionnaireRD)) { let response: RemoteDataResult>> = success({ @@ -44,5 +86,16 @@ export function useMappingEditor(questionnaireRD: RemoteData) { return await Promise.resolve(failure({})); }, [questionnaireRD]); - return { mappingsRD }; + return { + mappingsRD, + setShowModal, + showModal, + updatedResource, + setUpdatedResource, + editorState, + setEditorInitial, + setEditorLoading, + setEditorReady, + setEditorSelect, + }; } diff --git a/web/src/containers/Main/index.tsx b/web/src/containers/Main/index.tsx index 684293c3..bd704dce 100644 --- a/web/src/containers/Main/index.tsx +++ b/web/src/containers/Main/index.tsx @@ -1,9 +1,11 @@ +import Editor from '@monaco-editor/react'; +import { Allotment } from 'allotment'; +import { FhirResource } from 'fhir/r4b'; import { useContext } from 'react'; import { useParams } from 'react-router-dom'; -import { ToastContainer } from 'react-toastify'; import { Button } from 'web/src/components/Button'; -import { ExpandableElement } from 'web/src/components/ExpandableElement'; -import { ExpandableRow } from 'web/src/components/ExpandableRow'; +import { Cell } from 'web/src/components/Cell'; +import { CodeEditor } from 'web/src/components/CodeEditor'; import { LaunchContextEditor } from 'web/src/components/LaunchContextEditor'; import { Logo } from 'web/src/components/Logo'; import 'react-toastify/dist/ReactToastify.css'; @@ -17,8 +19,11 @@ import { FormRenderContext } from './context'; import s from './Main.module.scss'; import { MappingEditor } from './MappingEditor'; import { QuestionnaireEditor } from './QuestionnaireEditor'; +import { useFHIRMappingLanguage } from './useFHIRMappingLanguage'; import { useMain } from './useMain'; +import 'allotment/dist/style.css'; + export function Main() { const { questionnaireId } = useParams<{ questionnaireId: string }>(); const { @@ -30,87 +35,120 @@ export function Main() { extractRD, manager, } = useMain(questionnaireId!); + const { mapString, setMapString, mappingResult, fhirMappingLangMode, toggleMappingMode } = + useFHIRMappingLanguage( + isSuccess(questionnaireResponseRD) ? questionnaireResponseRD.data : undefined, + ); const QRFormWrapper = useContext(FormRenderContext); return ( <>
- - - - - {(resource) => ( - + + + + {(resource) => ( + + )} + + + + + + + + + + + + + + + {fhirMappingLangMode ? ( + { + setMapString(value as string); + }} + value={mapString} + options={{ + formatOnPaste: true, + formatOnType: true, + autoIndent: 'full', + minimap: { + enabled: false, + }, + }} + /> + ) : ( + )} - - - - - - - - - - - - - - - - - - - {isSuccess(extractRD) && ( - - )} - - + + + {fhirMappingLangMode ? ( + + ) : ( + + )} + {isSuccess(extractRD) && ( + + )} + + +
{`v${version}`}
diff --git a/web/src/containers/Main/useFHIRMappingLanguage.ts b/web/src/containers/Main/useFHIRMappingLanguage.ts new file mode 100644 index 00000000..dc0345d4 --- /dev/null +++ b/web/src/containers/Main/useFHIRMappingLanguage.ts @@ -0,0 +1,77 @@ +import { Bundle, QuestionnaireResponse, StructureMap } from 'fhir/r4b'; +import { useCallback, useEffect, useState } from 'react'; +import { convert, createStructureMap, transform } from 'web/src/services/fhirmapping'; + +import { isSuccess } from 'fhir-react/lib/libs/remoteData'; + +export function useFHIRMappingLanguage(questionnaireResponse: QuestionnaireResponse | undefined) { + const [fhirMappingLangMode, setFhirMappingLangMode] = useState(false); + const [mapString, setMapString] = useState(''); + const [prevMapString, setPrevMapString] = useState(''); + const [structureMapData, setStructureMapData] = useState(undefined); + const [structureMap, setStructureMap] = useState(undefined); + const [structureMapUrl, setStructureMapUrl] = useState(undefined); + const [mappingResult, setMappingResult] = useState(undefined); + + const fetchConvertData = useCallback(async (mapString: string, prevMapString: string) => { + if (mapString !== prevMapString) { + const response = await convert({ mapString }); + if (isSuccess(response)) { + setStructureMapData(response.data); + } else { + setMappingResult(response.error); + } + } + }, []); + + const fetchStructureMap = useCallback(async (structureMapData: StructureMap) => { + const response = await createStructureMap({ structureMap: structureMapData }); + if (isSuccess(response)) { + const structureMapResource = response.data; + setStructureMap(structureMapResource); + setStructureMapUrl(structureMapResource.url); + } + }, []); + + const fetchMappingResult = useCallback( + async (structureMapUrl: string, questionnaireResponse: QuestionnaireResponse) => { + const response = await transform({ structureMapUrl, questionnaireResponse }); + const dataToSet = isSuccess(response) ? response.data : response.error; + setMappingResult(dataToSet); + }, + [], + ); + + useEffect(() => { + if (mapString !== prevMapString) { + fetchConvertData(mapString, prevMapString); + } + }, [mapString, fetchConvertData, prevMapString]); + + useEffect(() => { + if (structureMapData) { + fetchStructureMap(structureMapData); + } + }, [structureMapData, fetchStructureMap]); + + useEffect(() => { + if (structureMapUrl && structureMap && questionnaireResponse) { + fetchMappingResult(structureMapUrl, questionnaireResponse); + } + }, [structureMapUrl, questionnaireResponse, fetchMappingResult, structureMap]); + + const changeMapString = (value: string) => { + setPrevMapString(mapString); + setMapString(value); + }; + + const toggleMappingMode = () => setFhirMappingLangMode(!fhirMappingLangMode); + + return { + fhirMappingLangMode, + toggleMappingMode, + mapString, + setMapString: changeMapString, + mappingResult, + }; +} diff --git a/web/src/services/fhirmapping.ts b/web/src/services/fhirmapping.ts new file mode 100644 index 00000000..2b9c1f55 --- /dev/null +++ b/web/src/services/fhirmapping.ts @@ -0,0 +1,52 @@ +import { Bundle, QuestionnaireResponse, StructureMap } from 'fhir/r4b'; + +import { service } from 'fhir-react/lib/services/service'; + +import { fhirMappingLanguageUrl } from 'shared/src/constants'; + +const CONTENT_TYPE_FHIR_MAPPING = 'text/fhir-mapping'; +const CONTENT_TYPE_FHIR_JSON = 'application/fhir+json'; +const ACCEPT_FHIR_JSON = 'application/fhir+json'; + +export async function convert({ mapString }: { mapString: string }) { + return await service({ + baseURL: fhirMappingLanguageUrl, + url: `/StructureMap/$convert`, + method: 'POST', + data: mapString, + headers: { + 'Content-Type': CONTENT_TYPE_FHIR_MAPPING, + }, + }); +} + +export async function createStructureMap({ structureMap }: { structureMap: StructureMap }) { + return await service({ + baseURL: fhirMappingLanguageUrl, + url: `/StructureMap`, + method: 'POST', + data: structureMap, + }); +} + +export async function transform({ + structureMapUrl, + questionnaireResponse, +}: { + structureMapUrl: string; + questionnaireResponse: QuestionnaireResponse; +}) { + return await service({ + baseURL: fhirMappingLanguageUrl, + url: `/StructureMap/$transform`, + method: 'POST', + data: questionnaireResponse, + params: { + source: structureMapUrl, + }, + headers: { + 'Content-Type': CONTENT_TYPE_FHIR_JSON, + Accept: ACCEPT_FHIR_JSON, + }, + }); +} diff --git a/yarn.lock b/yarn.lock index 0a483c9f..1109f6c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2327,6 +2327,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@juggle/resize-observer@^3.3.1": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" + integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" @@ -2446,6 +2451,20 @@ string-to-stream "^1.1.0" xmldoc "^0.4.0" +"@monaco-editor/loader@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@monaco-editor/loader/-/loader-1.4.0.tgz#f08227057331ec890fa1e903912a5b711a2ad558" + integrity sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg== + dependencies: + state-local "^1.0.6" + +"@monaco-editor/react@^4.6.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.6.0.tgz#bcc68671e358a21c3814566b865a54b191e24119" + integrity sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw== + dependencies: + "@monaco-editor/loader" "^1.4.0" + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -4205,6 +4224,18 @@ ajv@^8.0.0, ajv@^8.6.0, ajv@^8.9.0: require-from-string "^2.0.2" uri-js "^4.2.2" +allotment@^1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/allotment/-/allotment-1.20.2.tgz#5ea3a630b3265479debb69156658244711f83843" + integrity sha512-TaCuHfYNcsJS9EPk04M7TlG5Rl3vbAdHeAyrTE9D5vbpzV+wxnRoUrulDbfnzaQcPIZKpHJNixDOoZNuzliKEA== + dependencies: + classnames "^2.3.0" + eventemitter3 "^5.0.0" + lodash.clamp "^4.0.0" + lodash.debounce "^4.0.0" + lodash.isequal "^4.5.0" + use-resize-observer "^9.0.0" + ansi-colors@^4.1.1: version "4.1.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" @@ -5134,6 +5165,11 @@ classnames@*, classnames@^2.3.1: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== +classnames@^2.3.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + clean-css@^5.2.2: version "5.3.2" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.2.tgz#70ecc7d4d4114921f5d298349ff86a31a9975224" @@ -6980,7 +7016,7 @@ eventemitter3@^4.0.0, eventemitter3@^4.0.4: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -eventemitter3@^5.0.1: +eventemitter3@^5.0.0, eventemitter3@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== @@ -10112,7 +10148,12 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.debounce@^4.0.8: +lodash.clamp@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/lodash.clamp/-/lodash.clamp-4.0.3.tgz#5c24bedeeeef0753560dc2b4cb4671f90a6ddfaa" + integrity sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg== + +lodash.debounce@^4.0.0, lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== @@ -10122,6 +10163,11 @@ lodash.get@~4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" @@ -13457,6 +13503,11 @@ stackframe@^1.3.4: resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== +state-local@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/state-local/-/state-local-1.0.7.tgz#da50211d07f05748d53009bee46307a37db386d5" + integrity sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w== + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -13530,7 +13581,16 @@ string-to-stream@^1.1.0: inherits "^2.0.1" readable-stream "^2.1.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13621,7 +13681,14 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -14402,6 +14469,13 @@ use-isomorphic-layout-effect@^1.1.2: resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== +use-resize-observer@^9.0.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-9.1.0.tgz#14735235cf3268569c1ea468f8a90c5789fc5c6c" + integrity sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow== + dependencies: + "@juggle/resize-observer" "^3.3.1" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -15031,7 +15105,7 @@ workbox-window@6.6.1: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -15049,6 +15123,15 @@ wrap-ansi@^6.0.1: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"