diff --git a/frontend/app-development/features/dataModelling/DataModelling.tsx b/frontend/app-development/features/dataModelling/DataModelling.tsx index 3b2077dbbeb..86efd2a365c 100644 --- a/frontend/app-development/features/dataModelling/DataModelling.tsx +++ b/frontend/app-development/features/dataModelling/DataModelling.tsx @@ -8,6 +8,7 @@ import { useDataModelsJsonQuery, useDataModelsXsdQuery } from 'app-shared/hooks/ import { useParams } from 'react-router-dom'; import { mergeQueryStatuses } from 'app-shared/utils/tanstackQueryUtils'; import { mergeJsonAndXsdData } from '../../utils/metadataUtils'; +import { DataModelContextProvider } from '@altinn/schema-editor/contexts/DataModelToolbarContext'; interface DataModellingProps { createPathOption?: boolean; @@ -35,7 +36,11 @@ export function DataModelling({ createPathOption = false }: DataModellingProps): ); case 'success': { const data = mergeJsonAndXsdData(jsonData, xsdData); - return ; + return ( + + + + ); } } } diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/SchemaEditorWithToolbar.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/SchemaEditorWithToolbar.tsx index 7c6cba15a82..737808cb270 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/SchemaEditorWithToolbar.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/SchemaEditorWithToolbar.tsx @@ -2,12 +2,12 @@ import classes from './SchemaEditorWithToolbar.module.css'; import { TopToolbar } from './TopToolbar'; import { LandingPagePanel } from './LandingPagePanel'; import React, { useEffect, useState } from 'react'; -import type { MetadataOption } from '../../../types/MetadataOption'; import { SelectedSchemaEditor } from './SelectedSchemaEditor'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import { SchemaGenerationErrorsPanel } from './SchemaGenerationErrorsPanel'; import { useAddXsdMutation } from '../../../hooks/mutations/useAddXsdMutation'; import { isXsdFile } from 'app-shared/utils/filenameUtils'; +import { useDataModelContext } from '@altinn/schema-editor/contexts/DataModelToolbarContext'; export interface SchemaEditorWithToolbarProps { createPathOption?: boolean; @@ -19,7 +19,7 @@ export const SchemaEditorWithToolbar = ({ dataModels, }: SchemaEditorWithToolbarProps) => { const [createNewOpen, setCreateNewOpen] = useState(false); - const [selectedOption, setSelectedOption] = useState(undefined); + const { selectedOption, setSelectedOption, modelPath } = useDataModelContext(); const [schemaGenerationErrorMessages, setSchemaGenerationErrorMessages] = useState([]); const { mutate: addXsdFromRepo } = useAddXsdMutation(); @@ -28,7 +28,6 @@ export const SchemaEditorWithToolbar = ({ ) ? selectedOption : undefined; - const modelPath = existingSelectedOption?.value?.repositoryRelativeUrl; useEffect(() => { dataModels.forEach((model) => { diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/SelectedSchemaEditor.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/SelectedSchemaEditor.tsx index 7059ac93b0b..0ee02a129fe 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/SelectedSchemaEditor.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/SelectedSchemaEditor.tsx @@ -1,22 +1,12 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; +import React from 'react'; import { useSchemaQuery } from '../../../hooks/queries'; -import { useSchemaMutation } from '../../../hooks/mutations'; import { StudioCenter, StudioError, StudioPageSpinner } from '@studio/components'; import { ErrorMessage, Paragraph } from '@digdir/designsystemet-react'; import { SchemaEditorApp } from '@altinn/schema-editor/SchemaEditorApp'; import { useTranslation } from 'react-i18next'; -import { AUTOSAVE_DEBOUNCE_INTERVAL_MILLISECONDS } from 'app-shared/constants'; import type { JsonSchema } from 'app-shared/types/JsonSchema'; -import { useOnUnmount } from 'app-shared/hooks/useOnUnmount'; -import type { - DataModelMetadataJson, - DataModelMetadataXsd, -} from 'app-shared/types/DataModelMetadata'; -import { useQueryClient } from '@tanstack/react-query'; -import { QueryKey } from 'app-shared/types/QueryKey'; -import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; -import { mergeJsonAndXsdData } from 'app-development/utils/metadataUtils'; -import { extractFilename, removeSchemaExtension } from 'app-shared/utils/filenameUtils'; +import { useSaveSchemaWithDebounce } from './useSaveSchemaWithDebounce'; + export interface SelectedSchemaEditorProps { modelPath: string; } @@ -53,64 +43,7 @@ interface SchemaEditorWithDebounceProps { } const SchemaEditorWithDebounce = ({ jsonSchema, modelPath }: SchemaEditorWithDebounceProps) => { - const { org, app } = useStudioEnvironmentParams(); - const { mutate } = useSchemaMutation(); - const queryClient = useQueryClient(); - const [model, setModel] = useState(jsonSchema); - const saveTimeoutRef = useRef>(); - const updatedModel = useRef(jsonSchema); - - useEffect(() => { - setModel(jsonSchema); - }, [jsonSchema]); - - const saveFunction = useCallback( - () => mutate({ modelPath, model: updatedModel.current }), - [modelPath, mutate], - ); - - const saveSchema = useCallback( - (newModel: JsonSchema) => { - setModel(newModel); - updatedModel.current = newModel; - clearTimeout(saveTimeoutRef.current); - saveTimeoutRef.current = setTimeout(() => { - saveFunction(); - }, AUTOSAVE_DEBOUNCE_INTERVAL_MILLISECONDS); - }, - [saveFunction], - ); - - const doesModelExist = useCallback(() => { - const jsonModels: DataModelMetadataJson[] = queryClient.getQueryData([ - QueryKey.DataModelsJson, - org, - app, - ]); - const xsdModels: DataModelMetadataXsd[] = queryClient.getQueryData([ - QueryKey.DataModelsXsd, - org, - app, - ]); - const metadataList = mergeJsonAndXsdData(jsonModels, xsdModels); - return metadataList.some((dataModel) => dataModel.repositoryRelativeUrl === modelPath); - }, [queryClient, org, app, modelPath]); - - useOnUnmount(() => { - clearTimeout(saveTimeoutRef.current); - if (doesModelExist()) saveFunction(); - }); - - return ( - - ); -}; + const { model, saveSchema } = useSaveSchemaWithDebounce(jsonSchema, modelPath); -const extractModelNameFromPath = (path: string): string => { - const filename = extractFilename(path); - return removeSchemaExtension(filename); + return ; }; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css index 7914b8c1c12..3eeeab9785a 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css @@ -1,4 +1,12 @@ +.selectContainer { + display: flex; + align-items: center; + gap: var(--fds-spacing-4); +} + .select { + display: flex; + flex-direction: row; cursor: pointer; min-width: 20vw; } diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx index d1ff68acf42..36439b1e28b 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx @@ -5,7 +5,8 @@ import { groupMetadataOptions, } from '../../../../utils/metadataUtils'; import type { MetadataOption } from '../../../../types/MetadataOption'; -import { NativeSelect } from '@digdir/designsystemet-react'; +import { Label } from '@digdir/designsystemet-react'; +import { StudioNativeSelect } from '@studio/components'; import classes from './SchemaSelect.module.css'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import { useTranslation } from 'react-i18next'; @@ -30,26 +31,31 @@ export const SchemaSelect = ({ setSelectedOption(findMetadataOptionByRelativeUrl(options, repositoryUrl)); return ( - handleChange(e.target.value)} - value={selectedOption?.value.repositoryRelativeUrl} - size='small' - > - {optionGroups.map((group) => ( - - {group.options.map((option) => ( - - ))} - - ))} - +
+ + handleChange(e.target.value)} + value={selectedOption?.value.repositoryRelativeUrl} + size='sm' + > + {optionGroups.map((group) => ( + + {group.options.map((option) => ( + + ))} + + ))} + +
); }; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.module.css b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.module.css index 5fdb2d3f0c4..79d96fc8c74 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.module.css +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.module.css @@ -1,27 +1,15 @@ .toolbar { - align-items: center; - background: #fff; - border-bottom: 1px solid #c9c9c9; + height: var(--subtoolbar-height); + background: white; display: flex; - padding: 8px; -} - -.toolbar > *, -.toolbar > { - margin-right: 1rem; -} - -.toolbar button[class*='selected'] { - color: #fff; -} - -.toolbar button[class*='toggle'] { - font-size: 1em; - padding-top: 4px; + align-items: center; + border-bottom: 1px solid var(--fds-semantic-border-neutral-subtle); + padding-inline: var(--fds-spacing-6); + gap: var(--fds-spacing-2); } -.generateButtonWrapper { - flex: 1; +.modelName { + /*font-weight: 400;*/ } @keyframes fadeOut { @@ -43,13 +31,21 @@ animation: fadeOut 1.5s; } -.toggleButtonGroupWrapper { - flex: 0.5; +.typeToolbar { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; +} + +.typeToolbarBackground { + background: var(--fds-colors-grey-100); + /*background: var(--fds-colors-yellow-200);*/ } -.right { +.breadcrumbs { + width: fit-content; display: flex; - flex: 3; - gap: 1rem; - justify-content: flex-end; + align-items: center; + gap: var(--fds-spacing-4); } diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx index 9139f7af038..0a7c41f0d03 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx @@ -3,15 +3,19 @@ import classes from './TopToolbar.module.css'; import { CreateNewWrapper } from './CreateNewWrapper'; import { XSDUpload } from './XSDUpload'; import { SchemaSelect } from './SchemaSelect'; -import { DeleteWrapper } from './DeleteWrapper'; +// import { DeleteWrapper } from './DeleteWrapper'; import { computeSelectedOption } from '../../../../utils/metadataUtils'; import type { CreateDataModelMutationArgs } from '../../../../hooks/mutations/useCreateDataModelMutation'; import { useCreateDataModelMutation } from '../../../../hooks/mutations'; import type { MetadataOption } from '../../../../types/MetadataOption'; import { GenerateModelsButton } from './GenerateModelsButton'; -import { usePrevious } from '@studio/components'; +import { StudioButton, StudioParagraph, usePrevious } from '@studio/components'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import { useTranslation } from 'react-i18next'; +import { Label, Link } from '@digdir/designsystemet-react'; +import { ArrowLeftIcon, ChevronRightIcon } from '@studio/icons'; +import cn from 'classnames'; +import { useDataModelContext } from '@altinn/schema-editor/contexts/DataModelToolbarContext'; export interface TopToolbarProps { createNewOpen: boolean; @@ -32,23 +36,127 @@ export function TopToolbar({ setSelectedOption, onSetSchemaGenerationErrorMessages, }: TopToolbarProps) { - const modelPath = selectedOption?.value.repositoryRelativeUrl; - - const { t } = useTranslation(); - const { mutate: createDataModel } = useCreateDataModelMutation(); const prevDataModels = usePrevious(dataModels); + const { selectedTypePointer } = useDataModelContext(); useEffect(() => { setSelectedOption(computeSelectedOption(selectedOption, dataModels, prevDataModels)); }, [selectedOption, dataModels, prevDataModels, setSelectedOption]); + const showTypeToolbar: boolean = !!selectedTypePointer; + + return ( +
+ {showTypeToolbar ? ( + + ) : ( + + )} +
+ ); +} + +type TypeToolbarProps = { + dataModelName: string; +}; + +const TypeToolbar = ({ dataModelName }: TypeToolbarProps) => { + const { setSelectedTypePointer, setSelectedUniquePointer } = useDataModelContext(); + + const navigateToDataModelRoot = () => { + setSelectedUniquePointer(undefined); + setSelectedTypePointer(undefined); + }; + + const showBreadcrumbs = false; + + return ( +
+ {showBreadcrumbs ? ( + + ) : ( + + )} +
+ ); +}; + +type BreadcrumbsToolbarProps = { + navigateToDataModelRoot: () => void; +}; + +const BreadcrumbsToolbar = ({ navigateToDataModelRoot }: BreadcrumbsToolbarProps) => { + const { selectedModelName, selectedTypeName } = useDataModelContext(); + + return ( +
+ navigateToDataModelRoot()}> + Datamodell: {selectedModelName} + + + + Type: {selectedTypeName} + +
+ ); +}; + +const BackButtonToolbar = ({ navigateToDataModelRoot }: BreadcrumbsToolbarProps) => { + const { selectedModelName, selectedTypeName } = useDataModelContext(); + + return ( + <> + + navigateToDataModelRoot()} icon={}> + Tilbake til datamodell {selectedModelName} + + + ); +}; + +type DataModelToolbarProps = { + dataModels: DataModelMetadata[]; + createNewOpen: boolean; + setCreateNewOpen: (open: boolean) => void; + createPathOption?: boolean; + onSetSchemaGenerationErrorMessages: (errorMessages: string[]) => void; +}; + +const DataModelToolbar = ({ + dataModels, + createNewOpen, + setCreateNewOpen, + createPathOption, + onSetSchemaGenerationErrorMessages, +}: DataModelToolbarProps) => { + const { selectedOption, setSelectedOption, modelPath } = useDataModelContext(); + const { t } = useTranslation(); + const { mutate: createDataModel } = useCreateDataModelMutation(); + const handleCreateSchema = (model: CreateDataModelMutationArgs) => { createDataModel(model); setCreateNewOpen(false); }; return ( -
+ <> + - - -
-
- {modelPath && ( - - onSetSchemaGenerationErrorMessages(errorMessages) - } - /> - )} -
-
-
+ {/**/} + {modelPath && ( + + onSetSchemaGenerationErrorMessages(errorMessages) + } + /> + )} + ); -} +}; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/useSaveSchemaWithDebounce.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/useSaveSchemaWithDebounce.ts new file mode 100644 index 00000000000..0296cd9c688 --- /dev/null +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/useSaveSchemaWithDebounce.ts @@ -0,0 +1,65 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useSchemaMutation } from '../../../hooks/mutations'; +import { AUTOSAVE_DEBOUNCE_INTERVAL_MILLISECONDS } from 'app-shared/constants'; +import type { JsonSchema } from 'app-shared/types/JsonSchema'; +import { useOnUnmount } from 'app-shared/hooks/useOnUnmount'; +import type { + DataModelMetadataJson, + DataModelMetadataXsd, +} from 'app-shared/types/DataModelMetadata'; +import { useQueryClient } from '@tanstack/react-query'; +import { QueryKey } from 'app-shared/types/QueryKey'; +import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; +import { mergeJsonAndXsdData } from 'app-development/utils/metadataUtils'; + +export const useSaveSchemaWithDebounce = (jsonSchema: JsonSchema, modelPath: string) => { + const { org, app } = useStudioEnvironmentParams(); + const { mutate } = useSchemaMutation(); + const queryClient = useQueryClient(); + const [model, setModel] = useState(jsonSchema); + const saveTimeoutRef = useRef>(); + const updatedModel = useRef(jsonSchema); + + useEffect(() => { + setModel(jsonSchema); + }, [jsonSchema]); + + const saveFunction = useCallback( + () => mutate({ modelPath, model: updatedModel.current }), + [modelPath, mutate], + ); + + const saveSchema = useCallback( + (newModel: JsonSchema) => { + setModel(newModel); + updatedModel.current = newModel; + clearTimeout(saveTimeoutRef.current); + saveTimeoutRef.current = setTimeout(() => { + saveFunction(); + }, AUTOSAVE_DEBOUNCE_INTERVAL_MILLISECONDS); + }, + [saveFunction], + ); + + const doesModelExist = useCallback(() => { + const jsonModels: DataModelMetadataJson[] = queryClient.getQueryData([ + QueryKey.DataModelsJson, + org, + app, + ]); + const xsdModels: DataModelMetadataXsd[] = queryClient.getQueryData([ + QueryKey.DataModelsXsd, + org, + app, + ]); + const metadataList = mergeJsonAndXsdData(jsonModels, xsdModels); + return metadataList.some((dataModel) => dataModel.repositoryRelativeUrl === modelPath); + }, [queryClient, org, app, modelPath]); + + useOnUnmount(() => { + clearTimeout(saveTimeoutRef.current); + if (doesModelExist()) saveFunction(); + }); + + return { saveSchema, model }; +}; diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 6628aa7bdc4..ff07277c6fa 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -833,7 +833,7 @@ "schema_editor.delete": "Slett", "schema_editor.delete_data_model": "Slett datamodell", "schema_editor.delete_field": "Slett felt", - "schema_editor.delete_model_confirm": "Er du sikker på at du vil slette datamodellen {{schemaName}}?", + "schema_editor.delete_model_confirm": "Er du sikker på at du vil slette datamodellen {{selectedModelName}}?", "schema_editor.depth_error": "Du kan ikke plassere gruppen her, fordi skjemaet får for mange nivåer.", "schema_editor.description": "Tekst", "schema_editor.descriptive_fields": "Beskrivende felter", diff --git a/frontend/packages/schema-editor/src/SchemaEditorApp.tsx b/frontend/packages/schema-editor/src/SchemaEditorApp.tsx index 2cd7274210f..f8dcd0eb360 100644 --- a/frontend/packages/schema-editor/src/SchemaEditorApp.tsx +++ b/frontend/packages/schema-editor/src/SchemaEditorApp.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import './App.css'; import '@digdir/design-system-tokens/brand/altinn/tokens.css'; @@ -7,16 +7,21 @@ import { SchemaEditorAppContext } from './contexts/SchemaEditorAppContext'; import type { JsonSchema } from 'app-shared/types/JsonSchema'; import { buildJsonSchema, buildUiSchema, SchemaModel } from '@altinn/schema-model'; import { SchemaEditor } from './components/SchemaEditor'; +import { useDataModelContext } from '@altinn/schema-editor/contexts/DataModelToolbarContext'; export type SchemaEditorAppProps = { jsonSchema: JsonSchema; - name: string; save: (model: JsonSchema) => void; }; -export function SchemaEditorApp({ jsonSchema, name, save }: SchemaEditorAppProps) { - const [selectedTypePointer, setSelectedTypePointer] = useState(null); - const [selectedUniquePointer, setSelectedUniquePointer] = useState(null); +export function SchemaEditorApp({ jsonSchema, save }: SchemaEditorAppProps) { + const { + selectedTypePointer, + setSelectedTypePointer, + selectedUniquePointer, + setSelectedUniquePointer, + selectedModelName, + } = useDataModelContext(); const value = useMemo( () => ({ @@ -26,9 +31,17 @@ export function SchemaEditorApp({ jsonSchema, name, save }: SchemaEditorAppProps setSelectedTypePointer, selectedUniquePointer, setSelectedUniquePointer, - name, + name: selectedModelName, }), - [jsonSchema, save, selectedTypePointer, selectedUniquePointer, name], + [ + jsonSchema, + selectedTypePointer, + setSelectedTypePointer, + selectedUniquePointer, + setSelectedUniquePointer, + selectedModelName, + save, + ], ); return ( diff --git a/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/AddNodeMenu.tsx b/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/AddNodeMenu.tsx new file mode 100644 index 00000000000..cae5aa0eaa4 --- /dev/null +++ b/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/AddNodeMenu.tsx @@ -0,0 +1,100 @@ +import { StudioDropdownMenu } from '@studio/components'; +import type { HeadingRowProps } from './HeadingRow'; +import type { ReactNode } from 'react'; +import { useTranslation } from 'react-i18next'; +import React from 'react'; +import { + BooleanIcon, + CombinationIcon, + NumberIcon, + ObjectIcon, + PlusIcon, + StringIcon, +} from '@studio/icons'; +import { useSchemaEditorAppContext } from '../../../hooks/useSchemaEditorAppContext'; +import { useAddProperty } from '../../../hooks/useAddProperty'; +import { FieldType, ObjectKind, SchemaModel } from '@altinn/schema-model'; +import type { TranslationKey } from '@altinn-studio/language/type'; + +type AddNodeMenuProps = HeadingRowProps; + +type AddNodeMenuItemProps = { + titleKey: TranslationKey; + icon: ReactNode; + action: () => void; +}; + +export const AddNodeMenu = ({ schemaPointer }: AddNodeMenuProps) => { + const { t } = useTranslation(); + const addNodeMenuItems = useAddNodeMenuItems(schemaPointer); + + return ( + , + variant: 'secondary', + children: t('schema_editor.add_node_of_type'), + }} + > + {addNodeMenuItems.map((item) => ( + + ))} + + ); +}; + +const useAddNodeMenuItems = (schemaPointer: string): AddNodeMenuItemProps[] => { + const { setSelectedUniquePointer } = useSchemaEditorAppContext(); + const addNode = useAddProperty(); + + const addAndSelectNode = (...params: Parameters) => { + const newPointer = addNode(...params); + if (newPointer) { + const newUniquePointer = SchemaModel.getUniquePointer(newPointer); + setSelectedUniquePointer(newUniquePointer); + } + }; + + return [ + { + titleKey: 'schema_editor.object', + icon: , + action: () => addAndSelectNode(ObjectKind.Field, FieldType.Object, schemaPointer), + }, + { + titleKey: 'schema_editor.string', + icon: , + action: () => addAndSelectNode(ObjectKind.Field, FieldType.String, schemaPointer), + }, + { + titleKey: 'schema_editor.integer', + icon: , + action: () => addAndSelectNode(ObjectKind.Field, FieldType.Integer, schemaPointer), + }, + { + titleKey: 'schema_editor.number', + icon: , + action: () => addAndSelectNode(ObjectKind.Field, FieldType.Number, schemaPointer), + }, + { + titleKey: 'schema_editor.boolean', + icon: , + action: () => addAndSelectNode(ObjectKind.Field, FieldType.Boolean, schemaPointer), + }, + { + titleKey: 'schema_editor.combination', + icon: , + action: () => addAndSelectNode(ObjectKind.Combination, undefined, schemaPointer), + }, + ]; +}; + +const AddNodeMenuItem = ({ titleKey, icon, action }: AddNodeMenuItemProps) => { + const { t } = useTranslation(); + return ( + + {t(titleKey)} + + ); +}; diff --git a/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/DeleteModelButton.tsx b/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/DeleteModelButton.tsx new file mode 100644 index 00000000000..2b1081269ca --- /dev/null +++ b/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/DeleteModelButton.tsx @@ -0,0 +1,35 @@ +import { useDeleteDataModelMutation } from 'app-development/hooks/mutations'; +import { useDataModelContext } from '../../../contexts/DataModelToolbarContext'; +import { useTranslation } from 'react-i18next'; +import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; +import { useUpdateBpmn } from 'app-shared/hooks/useUpdateBpmn'; +import { removeDataTypeIdsToSign } from 'app-shared/utils/bpmnUtils'; +import { StudioDeleteButton } from '@studio/components'; +import React from 'react'; + +export const DeleteModelButton = () => { + const { t } = useTranslation(); + const { selectedModelName, modelPath } = useDataModelContext(); + const { mutate } = useDeleteDataModelMutation(); + const { org, app } = useStudioEnvironmentParams(); + const updateBpmn = useUpdateBpmn(org, app); + + const handleDeleteModel = async () => { + mutate(modelPath, { + onSuccess: async () => { + await updateBpmn(removeDataTypeIdsToSign([selectedModelName])); + }, + }); + }; + + return ( + + {t('general.delete')} + + ); +}; diff --git a/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/DeleteTypeButton.tsx b/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/DeleteTypeButton.tsx new file mode 100644 index 00000000000..51ea5415362 --- /dev/null +++ b/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/DeleteTypeButton.tsx @@ -0,0 +1,32 @@ +import { StudioDeleteButton } from '@studio/components'; +import type { HeadingRowProps } from './HeadingRow'; +import { useTranslation } from 'react-i18next'; +import { useSavableSchemaModel } from '../../../hooks/useSavableSchemaModel'; +import { useSchemaEditorAppContext } from '../../../hooks/useSchemaEditorAppContext'; +import React from 'react'; + +export const DeleteTypeButton = ({ schemaPointer }: HeadingRowProps) => { + const { t } = useTranslation(); + const savableModel = useSavableSchemaModel(); + const { setSelectedUniquePointer, setSelectedTypePointer } = useSchemaEditorAppContext(); + + const isInUse = savableModel.hasReferringNodes(schemaPointer); + + const handleDeleteType = () => { + setSelectedUniquePointer(null); + setSelectedTypePointer(null); + savableModel.deleteNode(schemaPointer); + }; + + return ( + + {t('general.delete')} + + ); +}; diff --git a/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/HeadingRow.module.css b/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/HeadingRow.module.css index b0c067040c8..b808b23164f 100644 --- a/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/HeadingRow.module.css +++ b/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/HeadingRow.module.css @@ -1,26 +1,30 @@ .root { - --gap: var(--fds-spacing-1); - align-items: center; display: flex; - padding: var(--gap) 0; - gap: var(--gap); + align-items: center; + height: var(--subtoolbar-height); + gap: var(--fds-spacing-3); + border-bottom: 1px solid var(--fds-semantic-border-neutral-subtle); } -.heading { - display: contents; +.headingButton { + display: flex; + font: var(--fds-typography-paragraph-short-large); + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border: 0; } -.heading .headingButton { - border-bottom-left-radius: 0; - border-left-color: transparent; - border-left-style: solid; - border-left-width: var(--studio-treeitem-vertical-line-width); - border-top-left-radius: 0; - font: var(--fds-typography-paragraph-short-large); - min-height: var(--fds-sizing-12); +.headingButton:hover { + background-color: var(--fds-colors-blue-100); } -.root.selected .headingButton { - background-color: var(--studio-treeitem-selected-background-colour); - border-left-color: var(--studio-treeitem-vertical-line-colour-root); +.selected .headingButton { + /*background-color: var(--studio-treeitem-selected-background-colour);*/ + background: linear-gradient( + to right, + var(--studio-treeitem-vertical-line-colour-root) 0 var(--studio-treeitem-vertical-line-width), + var(--studio-treeitem-selected-background-colour) var(--studio-treeitem-vertical-line-width) + 100% + ); + /*border-left-color: var(--studio-treeitem-vertical-line-colour-root);*/ } diff --git a/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/HeadingRow.tsx b/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/HeadingRow.tsx index 1c4976365cd..86cdf9f56c7 100644 --- a/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/HeadingRow.tsx +++ b/frontend/packages/schema-editor/src/components/NodePanel/HeadingRow/HeadingRow.tsx @@ -1,31 +1,13 @@ import classes from './HeadingRow.module.css'; -import { Heading } from '@digdir/designsystemet-react'; import { NodeIcon } from '../../NodeIcon'; -import type { ReactNode } from 'react'; import React from 'react'; import { useSchemaEditorAppContext } from '../../../hooks/useSchemaEditorAppContext'; -import { - extractNameFromPointer, - FieldType, - isNodeValidParent, - ObjectKind, - ROOT_POINTER, - SchemaModel, -} from '@altinn/schema-model'; -import { useTranslation } from 'react-i18next'; -import { StudioButton, StudioDeleteButton, StudioDropdownMenu } from '@studio/components'; -import { - BooleanIcon, - CombinationIcon, - NumberIcon, - ObjectIcon, - PlusIcon, - StringIcon, -} from '@studio/icons'; -import { useSavableSchemaModel } from '../../../hooks/useSavableSchemaModel'; -import type { TranslationKey } from '@altinn-studio/language/type'; -import { useAddProperty } from '../../../hooks/useAddProperty'; +import { extractNameFromPointer, isNodeValidParent, ROOT_POINTER } from '@altinn/schema-model'; +import { StudioButton, StudioHeading } from '@studio/components'; import cn from 'classnames'; +import { AddNodeMenu } from './AddNodeMenu'; +import { DeleteTypeButton } from './DeleteTypeButton'; +import { DeleteModelButton } from './DeleteModelButton'; export interface HeadingRowProps { schemaPointer?: string; @@ -44,7 +26,7 @@ export const HeadingRow = ({ schemaPointer }: HeadingRowProps) => { return (
- + { > {title} - + {isValidParent && } - {!isDataModelRoot && } + {isDataModelRoot ? : }
); }; - -type AddNodeMenuProps = HeadingRowProps; - -type AddNodeMenuItemProps = { - titleKey: TranslationKey; - icon: ReactNode; - action: () => void; -}; - -const AddNodeMenu = ({ schemaPointer }: AddNodeMenuProps) => { - const { t } = useTranslation(); - const addNodeMenuItems = useAddNodeMenuItems(schemaPointer); - - return ( - , - variant: 'secondary', - children: t('schema_editor.add_node_of_type'), - }} - > - {addNodeMenuItems.map((item) => ( - - ))} - - ); -}; - -const useAddNodeMenuItems = (schemaPointer: string): AddNodeMenuItemProps[] => { - const { setSelectedUniquePointer } = useSchemaEditorAppContext(); - const addNode = useAddProperty(); - - const addAndSelectNode = (...params: Parameters) => { - const newPointer = addNode(...params); - if (newPointer) { - const newUniquePointer = SchemaModel.getUniquePointer(newPointer); - setSelectedUniquePointer(newUniquePointer); - } - }; - - return [ - { - titleKey: 'schema_editor.object', - icon: , - action: () => addAndSelectNode(ObjectKind.Field, FieldType.Object, schemaPointer), - }, - { - titleKey: 'schema_editor.string', - icon: , - action: () => addAndSelectNode(ObjectKind.Field, FieldType.String, schemaPointer), - }, - { - titleKey: 'schema_editor.integer', - icon: , - action: () => addAndSelectNode(ObjectKind.Field, FieldType.Integer, schemaPointer), - }, - { - titleKey: 'schema_editor.number', - icon: , - action: () => addAndSelectNode(ObjectKind.Field, FieldType.Number, schemaPointer), - }, - { - titleKey: 'schema_editor.boolean', - icon: , - action: () => addAndSelectNode(ObjectKind.Field, FieldType.Boolean, schemaPointer), - }, - { - titleKey: 'schema_editor.combination', - icon: , - action: () => addAndSelectNode(ObjectKind.Combination, undefined, schemaPointer), - }, - ]; -}; - -const AddNodeMenuItem = ({ titleKey, icon, action }: AddNodeMenuItemProps) => { - const { t } = useTranslation(); - return ( - - {t(titleKey)} - - ); -}; - -type DeleteButtonProps = HeadingRowProps; - -const DeleteButton = ({ schemaPointer }: DeleteButtonProps) => { - const { t } = useTranslation(); - const savableModel = useSavableSchemaModel(); - const { setSelectedUniquePointer, setSelectedTypePointer } = useSchemaEditorAppContext(); - - const isInUse = savableModel.hasReferringNodes(schemaPointer); - - const handleDelete = () => { - setSelectedUniquePointer(null); - setSelectedTypePointer(null); - savableModel.deleteNode(schemaPointer); - }; - - return ( - - {t('general.delete')} - - ); -}; diff --git a/frontend/packages/schema-editor/src/components/NodePanel/NodePanel.module.css b/frontend/packages/schema-editor/src/components/NodePanel/NodePanel.module.css index 1f8384e8b2b..a52c052e570 100644 --- a/frontend/packages/schema-editor/src/components/NodePanel/NodePanel.module.css +++ b/frontend/packages/schema-editor/src/components/NodePanel/NodePanel.module.css @@ -1,7 +1,3 @@ -.top { - box-shadow: var(--fds-shadow-small); -} - .backButton { background-color: var(--fds-semantic-surface-action-subtle); border: none; @@ -12,3 +8,7 @@ .backButton:hover { background-color: var(--fds-semantic-surface-action-subtle-hover); } + +.content { + padding-block: var(--fds-spacing-2); +} diff --git a/frontend/packages/schema-editor/src/components/NodePanel/NodePanel.tsx b/frontend/packages/schema-editor/src/components/NodePanel/NodePanel.tsx index 918c678d557..f42cde1d9c7 100644 --- a/frontend/packages/schema-editor/src/components/NodePanel/NodePanel.tsx +++ b/frontend/packages/schema-editor/src/components/NodePanel/NodePanel.tsx @@ -1,9 +1,6 @@ import React from 'react'; import { SchemaTree } from '../SchemaTree'; -import { Link } from '@digdir/designsystemet-react'; import { useSchemaEditorAppContext } from '../../hooks/useSchemaEditorAppContext'; -import { ArrowLeftIcon } from '@studio/icons'; -import { useTranslation } from 'react-i18next'; import classes from './NodePanel.module.css'; import { HeadingRow } from './HeadingRow'; import { isNodeValidParent } from '@altinn/schema-model'; @@ -21,30 +18,30 @@ export const NodePanel = ({ schemaPointer }: NodePanelProps) => { return ( <> -
- {!isDataModelRoot && } - + + {/*{!isDataModelRoot && }*/} +
+ {isNodeValidParent(node) && }
- {isNodeValidParent(node) && } ); }; -const BackButton = () => { - const { setSelectedUniquePointer, setSelectedTypePointer } = useSchemaEditorAppContext(); - const { t } = useTranslation(); - - const navigateToDataModelRoot = () => { - setSelectedUniquePointer(undefined); - setSelectedTypePointer(undefined); - }; - - return ( - - - - ); -}; +// const BackButton = () => { +// const { setSelectedUniquePointer, setSelectedTypePointer } = useSchemaEditorAppContext(); +// const { t } = useTranslation(); +// +// const navigateToDataModelRoot = () => { +// setSelectedUniquePointer(undefined); +// setSelectedTypePointer(undefined); +// }; +// +// return ( +// +// +// +// ); +// }; diff --git a/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.module.css b/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.module.css index ed349915d51..b7117d4a636 100644 --- a/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.module.css +++ b/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.module.css @@ -1,8 +1,8 @@ .editor { + display: flex; + flex-direction: column; + width: 100%; background-color: white; - display: grid; - grid-template-rows: auto 1fr; - flex: 1; min-height: 200px; overflow-y: auto; } @@ -13,15 +13,3 @@ overflow-y: auto; width: 100%; } - -.typeInfo { - display: flex; - flex-direction: row; - background-color: #022f51; - color: #ffffff; - align-items: center; - justify-content: center; - margin-left: auto; - margin-right: auto; - width: 100%; -} diff --git a/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.tsx b/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.tsx index 6b287d5be74..853d07505de 100644 --- a/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.tsx +++ b/frontend/packages/schema-editor/src/components/SchemaEditor/SchemaEditor.tsx @@ -14,7 +14,7 @@ import { useUserQuery } from 'app-development/hooks/queries'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; export const SchemaEditor = () => { - const { schemaModel, selectedTypePointer, selectedUniquePointer } = useSchemaEditorAppContext(); + const { schemaModel, selectedTypePointer } = useSchemaEditorAppContext(); const { org } = useStudioEnvironmentParams(); const { data: user } = useUserQuery(); const moveProperty = useMoveProperty(); @@ -35,21 +35,17 @@ export const SchemaEditor = () => { orientation='horizontal' localStorageContext={`datamodel:${user.id}:${org}`} > - + - +
- + diff --git a/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.module.css b/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.module.css index 2ea6be75947..9bc24f62495 100644 --- a/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.module.css +++ b/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.module.css @@ -4,7 +4,16 @@ height: 100%; } -.noItem { - font-weight: 500; - margin: 18px; +.noSelectionHeadingContainer { + display: flex; + flex-direction: column; + justify-content: center; + padding-inline: var(--fds-spacing-6); + height: var(--subtoolbar-height); + border-bottom: 1px solid var(--fds-semantic-border-neutral-subtle); +} + +.tabHeader { + height: var(--subtoolbar-height); + font-size: var(--fds-typography-heading-small); } diff --git a/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.test.tsx b/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.test.tsx index e272a513ecb..9583775cde0 100644 --- a/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.test.tsx +++ b/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.test.tsx @@ -42,7 +42,19 @@ const renderSchemaInspector = (uiSchemaMap: UiSchemaNodes, selectedItem?: UiSche describe('SchemaInspector', () => { afterEach(jest.clearAllMocks); - it('Saves data model when entering text in textboxes', async () => { + it('displays a message when no node is selected', () => { + renderSchemaInspector(mockUiSchema); + + const propertiesHeader = screen.getByRole('heading', { + name: textMock('schema_editor.properties'), + }); + const noSelectionParagraph = screen.getByText(textMock('schema_editor.no_item_selected')); + + expect(propertiesHeader).toBeInTheDocument(); + expect(noSelectionParagraph).toBeInTheDocument(); + }); + + it('saves data model when entering text in textboxes', async () => { renderSchemaInspector(mockUiSchema, getMockSchemaByPath('#/$defs/Kommentar2000Restriksjon')); const tablist = screen.getByRole('tablist'); expect(tablist).toBeDefined(); @@ -59,13 +71,13 @@ describe('SchemaInspector', () => { expect(saveDataModel).toHaveBeenCalled(); }); - test('renders no item if nothing is selected', () => { + it('renders no item if nothing is selected', () => { renderSchemaInspector(mockUiSchema); const textboxes = screen.queryAllByRole('textbox'); expect(textboxes).toHaveLength(0); }); - it('Saves data model correctly when changing restriction value', async () => { + it('saves data model correctly when changing restriction value', async () => { const schemaPointer = '#/$defs/Kommentar2000Restriksjon'; renderSchemaInspector(mockUiSchema, getMockSchemaByPath(schemaPointer)); @@ -93,7 +105,7 @@ describe('SchemaInspector', () => { expect(updatedNode.restrictions.minLength).toEqual(parseInt(minLength)); }); - test('Adds new object field when pressing the enter key', async () => { + it('adds new object field when pressing the enter key', async () => { const parentNodePointer = '#/properties/test'; const childNodePointer = '#/properties/test/properties/abc'; const rootNode: FieldNode = { @@ -125,7 +137,7 @@ describe('SchemaInspector', () => { }); }); - test('Adds new valid value field when pressing the enter key', async () => { + it('adds new valid value field when pressing the enter key', async () => { const itemPointer = '#/properties/test'; const enumValue = 'valid value'; const rootNode: FieldNode = { @@ -155,7 +167,7 @@ describe('SchemaInspector', () => { expect(saveDataModel).not.toHaveBeenCalled(); }); - it('Does not display the fields tab when the selected item is a combination', async () => { + it('does not display the fields tab when the selected item is a combination', async () => { const itemPointer = '#/properties/testcombination'; const rootNode: FieldNode = { ...rootNodeMock, diff --git a/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.tsx b/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.tsx index 9480c520805..5bfbe303a77 100644 --- a/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.tsx +++ b/frontend/packages/schema-editor/src/components/SchemaInspector/SchemaInspector.tsx @@ -1,3 +1,4 @@ +import type { ReactElement } from 'react'; import React from 'react'; import { Alert, Tabs } from '@digdir/designsystemet-react'; import type { UiSchemaNode } from '@altinn/schema-model'; @@ -5,44 +6,55 @@ import { isField, isObject } from '@altinn/schema-model'; import { ItemPropertiesTab } from './ItemPropertiesTab'; import { ItemFieldsTab } from './ItemFieldsTab'; import classes from './SchemaInspector.module.css'; -import { Divider } from 'app-shared/primitives'; import { useTranslation } from 'react-i18next'; import { useSchemaEditorAppContext } from '../../hooks/useSchemaEditorAppContext'; import { useSavableSchemaModel } from '../../hooks/useSavableSchemaModel'; +import { StudioCenter, StudioHeading, StudioParagraph } from '@studio/components'; -export const SchemaInspector = () => { +export const SchemaInspector = (): ReactElement => { const { t } = useTranslation(); const { selectedUniquePointer } = useSchemaEditorAppContext(); const savableModel = useSavableSchemaModel(); + const selectedItem: UiSchemaNode = selectedUniquePointer + ? savableModel.getNodeByUniquePointer(selectedUniquePointer) + : undefined; + const shouldDisplayFieldsTab = selectedItem && isField(selectedItem) && isObject(selectedItem); - if (!selectedUniquePointer) { + if (!selectedItem) { return ( -
-

{t('schema_editor.no_item_selected')}

- -
+ <> +
+ + {t('schema_editor.properties')} + +
+ + {t('schema_editor.no_item_selected')} + + ); } - const selectedItem: UiSchemaNode = savableModel.getNodeByUniquePointer(selectedUniquePointer); - const shouldDisplayFieldsTab = isField(selectedItem) && isObject(selectedItem); - return ( - + - {t('schema_editor.properties')} - {t('schema_editor.fields')} + + {t('schema_editor.properties')} + + + {t('schema_editor.fields')} + - {shouldDisplayFieldsTab ? ( - + + {shouldDisplayFieldsTab ? ( - - ) : ( - {t('app_data_modelling.fields_information')} - )} + ) : ( + {t('app_data_modelling.fields_information')} + )} + ); }; diff --git a/frontend/packages/schema-editor/src/components/TypesInspector/TypesInspector.module.css b/frontend/packages/schema-editor/src/components/TypesInspector/TypesInspector.module.css index 26856e71376..ed69e45b743 100644 --- a/frontend/packages/schema-editor/src/components/TypesInspector/TypesInspector.module.css +++ b/frontend/packages/schema-editor/src/components/TypesInspector/TypesInspector.module.css @@ -1,37 +1,20 @@ -.root { - box-sizing: border-box; - padding: 24px 24px; - min-height: 100%; - background: rgba(224, 224, 224, 0.3); - border-right: 1px solid var(--fds-semantic-border-neutral-subtle); -} - -.types { +.headingContainer { display: flex; - flex-direction: column; - align-items: stretch; - gap: 8px; -} - -.addRow { - display: grid; - grid-template-columns: repeat(3, 1fr); + justify-content: space-between; align-items: center; + height: var(--subtoolbar-height); + padding-inline: var(--fds-spacing-6); + border-bottom: 1px solid var(--fds-semantic-border-neutral-subtle); } -.addRowText { - grid-column: 1 / 3; - font-size: 18px; +.addTypeButton { + color: var(--fds-semantic-surface-neutral-inverted); } -.addRowButton { - grid-column: 3; - margin-left: auto; - margin-right: 0; - color: #000000; -} - -.noItem { - font-weight: 500; - margin: 18px; +.typesList { + display: flex; + flex-direction: column; + padding-block: var(--fds-spacing-5); + padding-inline: var(--fds-spacing-6); + gap: var(--fds-spacing-3); } diff --git a/frontend/packages/schema-editor/src/components/TypesInspector/TypesInspector.tsx b/frontend/packages/schema-editor/src/components/TypesInspector/TypesInspector.tsx index 54e6a29226d..e6f0d463224 100644 --- a/frontend/packages/schema-editor/src/components/TypesInspector/TypesInspector.tsx +++ b/frontend/packages/schema-editor/src/components/TypesInspector/TypesInspector.tsx @@ -1,11 +1,10 @@ import type { MouseEvent } from 'react'; import React from 'react'; -import { StudioButton } from '@studio/components'; +import { StudioButton, StudioHeading } from '@studio/components'; import { PlusIcon } from '@studio/icons'; import type { UiSchemaNode } from '@altinn/schema-model'; import { SchemaModel } from '@altinn/schema-model'; import classes from './TypesInspector.module.css'; -import { Divider } from 'app-shared/primitives'; import { useTranslation } from 'react-i18next'; import { TypeItem } from './TypeItem'; import { useSchemaEditorAppContext } from '../../hooks/useSchemaEditorAppContext'; @@ -38,31 +37,21 @@ export const TypesInspector = ({ schemaItems }: TypesInspectorProps) => { save(schemaModel); }; - if (!schemaItems) { - return ( -
-

- {t('schema_editor.no_item_selected')} -

- -
- ); - } - return ( -
-
-
- {t('schema_editor.types')} - } - onClick={handleAddDefinition} - title={t('schema_editor.add_type')} - /> -
- + <> +
+ + {t('schema_editor.types')} + + } + onClick={handleAddDefinition} + title={t('schema_editor.add_type')} + /> +
+
{schemaItems.map((item) => ( { /> ))}
-
+ ); }; diff --git a/frontend/packages/schema-editor/src/contexts/DataModelToolbarContext.tsx b/frontend/packages/schema-editor/src/contexts/DataModelToolbarContext.tsx new file mode 100644 index 00000000000..ac9f3bf2703 --- /dev/null +++ b/frontend/packages/schema-editor/src/contexts/DataModelToolbarContext.tsx @@ -0,0 +1,60 @@ +import React, { createContext, useContext, useState } from 'react'; +import type { MetadataOption } from '../../../../app-development/types/MetadataOption'; + +type DataModelContextProps = { + selectedTypePointer: string; + setSelectedTypePointer: React.Dispatch>; + selectedUniquePointer: string; + setSelectedUniquePointer: React.Dispatch>; + selectedOption: MetadataOption; + setSelectedOption: React.Dispatch>; + modelPath: string | undefined; + selectedModelName: string | undefined; + selectedTypeName: string | undefined; +}; + +const DataModelContext = createContext(null); + +export type DataModelToolbarContextProviderProps = { + children: React.ReactNode; +}; + +export const DataModelContextProvider = ({ children }: DataModelToolbarContextProviderProps) => { + const [selectedTypePointer, setSelectedTypePointer] = useState(null); + const [selectedUniquePointer, setSelectedUniquePointer] = useState(null); + const [selectedOption, setSelectedOption] = useState(null); + + const modelPath: string | undefined = selectedOption?.value?.repositoryRelativeUrl; + const selectedModelName: string | undefined = selectedOption?.label ?? undefined; + const selectedTypeName: string | undefined = getTypeName(selectedUniquePointer); + + const value = { + selectedTypePointer, + setSelectedTypePointer, + selectedUniquePointer, + setSelectedUniquePointer, + selectedOption, + setSelectedOption, + modelPath, + selectedModelName, + selectedTypeName, + }; + + return {children}; +}; + +const getTypeName = (uniquePointer?: string | undefined): string | undefined => { + if (uniquePointer) { + const indexOfLastDash = uniquePointer.lastIndexOf('/'); + return uniquePointer.substring(indexOfLastDash + 1); + } + return undefined; +}; + +export const useDataModelContext = (): Partial => { + const context = useContext(DataModelContext); + if (context === undefined) { + throw new Error('useDataModelToolbarContext must be used within a useDataModelContextProvider'); + } + return context; +};