From 25231c5d63d63d647be2789f87cdc0f5951f1b23 Mon Sep 17 00:00:00 2001 From: ivy-edp Date: Wed, 16 Oct 2024 16:45:39 +0200 Subject: [PATCH] XIVY-15138 Add Multiselect on ctrl and remove/reorder multiselect --- .../src/mock/dataclass-client-mock.ts | 5 ++ package-lock.json | 24 +++--- packages/dataclass-editor/package.json | 6 +- .../src/master/DataClassMasterContent.tsx | 77 +++++++++++++++++-- .../src/master/ValidationRow.test.ts | 26 +------ .../src/master/ValidationRow.tsx | 42 +++++----- .../src/protocol/client-json-rpc.ts | 5 ++ .../dataclass-editor/src/protocol/types.ts | 12 ++- 8 files changed, 129 insertions(+), 68 deletions(-) diff --git a/integrations/standalone/src/mock/dataclass-client-mock.ts b/integrations/standalone/src/mock/dataclass-client-mock.ts index 7d3726a6..850de946 100644 --- a/integrations/standalone/src/mock/dataclass-client-mock.ts +++ b/integrations/standalone/src/mock/dataclass-client-mock.ts @@ -7,6 +7,7 @@ import type { ValidationMessage } from '@axonivy/dataclass-editor/src/protocol/types'; import { MetaMock } from './meta-mock'; +import type { DataClassField } from '@axonivy/dataclass-editor'; export class DataClassClientMock implements Client { private dataClassData: Data = { @@ -67,6 +68,10 @@ export class DataClassClientMock implements Client { return Promise.resolve([]); } + function(): Promise> { + return Promise.resolve([]); + } + meta(path: TMeta): Promise { switch (path) { case 'meta/scripting/ivyTypes': diff --git a/package-lock.json b/package-lock.json index f1a99aa5..d1a56164 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,17 +66,17 @@ "link": true }, "node_modules/@axonivy/jsonrpc": { - "version": "12.0.0-next.342", - "resolved": "https://npmjs-registry.ivyteam.ch/@axonivy/jsonrpc/-/jsonrpc-12.0.0-next.342.tgz", - "integrity": "sha512-hBwaxYEOQM9EuWtUey/9euK8shTmbVdK4TqGK5Y53JeYwHAZpiCdgvJq7hcLN8s1kC0nOmfgrcMn9iW79SMgaw==", + "version": "12.0.0-next.344", + "resolved": "https://npmjs-registry.ivyteam.ch/@axonivy/jsonrpc/-/jsonrpc-12.0.0-next.344.tgz", + "integrity": "sha512-Cm6LiJYkRZDMBTlzVfocVB0fO3J+qmn+NbP3/ASSIRBsLVDJdxR9HEkDzgjElAbZO++hBg/RHRheQM5kbeShKA==", "dependencies": { "vscode-jsonrpc": "^8.2.0" } }, "node_modules/@axonivy/ui-components": { - "version": "12.0.0-next.342", - "resolved": "https://npmjs-registry.ivyteam.ch/@axonivy/ui-components/-/ui-components-12.0.0-next.342.tgz", - "integrity": "sha512-qTnzT7Jna4Ql5R7+mH0w/GVrD6UxFGT4iXcfjmmpYtm40CmcMk4hFFt2BIUZPOtCaw1zU6HY1Wjeok8Gx83bzw==", + "version": "12.0.0-next.344", + "resolved": "https://npmjs-registry.ivyteam.ch/@axonivy/ui-components/-/ui-components-12.0.0-next.344.tgz", + "integrity": "sha512-vEV9o61vaHdhCYsPRNbvjYDm5voWPYNkICP+W0ajuC0fRHYWlgZoynDyzNSijPJ9q/xZ0YQGp7Pb7223LFHjAA==", "dependencies": { "@radix-ui/react-accordion": "1.2.1", "@radix-ui/react-checkbox": "1.1.2", @@ -105,9 +105,9 @@ } }, "node_modules/@axonivy/ui-icons": { - "version": "12.0.0-next.342", - "resolved": "https://npmjs-registry.ivyteam.ch/@axonivy/ui-icons/-/ui-icons-12.0.0-next.342.tgz", - "integrity": "sha512-FicD20p+T+RUvpCdTHqYAcqjJFSmty2pmoFZMoJMeDZQTpYKlwL7DoSIroV4JtMiC8jfaaq4kdXgJ33XLBE74Q==" + "version": "12.0.0-next.344", + "resolved": "https://npmjs-registry.ivyteam.ch/@axonivy/ui-icons/-/ui-icons-12.0.0-next.344.tgz", + "integrity": "sha512-fo15PPFenwF9WRuQvZW1K/e5uKfUejQ5Y+WRsFvIGrWf8dAVqesj8dhBVkHOaWgoSMDQkR/rml5yfURC/YFcdg==" }, "node_modules/@babel/code-frame": { "version": "7.24.7", @@ -15282,9 +15282,9 @@ "version": "12.0.0-next", "license": "Apache-2.0", "dependencies": { - "@axonivy/jsonrpc": "~12.0.0-next.342", - "@axonivy/ui-components": "~12.0.0-next.342", - "@axonivy/ui-icons": "~12.0.0-next.342", + "@axonivy/jsonrpc": "~12.0.0-next.344", + "@axonivy/ui-components": "~12.0.0-next.344", + "@axonivy/ui-icons": "~12.0.0-next.344", "@tanstack/react-query": "5.32.1", "@tanstack/react-query-devtools": "5.32.1", "react": "^18.2.0" diff --git a/packages/dataclass-editor/package.json b/packages/dataclass-editor/package.json index f6a2384e..69f571c2 100644 --- a/packages/dataclass-editor/package.json +++ b/packages/dataclass-editor/package.json @@ -17,9 +17,9 @@ "types": "lib/index.d.ts", "main": "lib/editor.js", "dependencies": { - "@axonivy/jsonrpc": "~12.0.0-next.342", - "@axonivy/ui-components": "~12.0.0-next.342", - "@axonivy/ui-icons": "~12.0.0-next.342", + "@axonivy/jsonrpc": "~12.0.0-next.344", + "@axonivy/ui-components": "~12.0.0-next.344", + "@axonivy/ui-icons": "~12.0.0-next.344", "@tanstack/react-query": "5.32.1", "@tanstack/react-query-devtools": "5.32.1", "react": "^18.2.0" diff --git a/packages/dataclass-editor/src/master/DataClassMasterContent.tsx b/packages/dataclass-editor/src/master/DataClassMasterContent.tsx index 850f337c..cf813776 100644 --- a/packages/dataclass-editor/src/master/DataClassMasterContent.tsx +++ b/packages/dataclass-editor/src/master/DataClassMasterContent.tsx @@ -1,16 +1,21 @@ import { + arrayMoveMultiple, BasicField, Button, - deleteFirstSelectedRow, + deleteAllSelectedRows, Flex, + handleMultiSelectOnCtrlRowClick, + indexOf, Message, ReorderHandleWrapper, + resetAndSetRowSelection, selectRow, Separator, SortableHeader, Table, TableBody, TableResizableHeader, + toast, Tooltip, TooltipContent, TooltipProvider, @@ -20,7 +25,7 @@ import { useTableSort } from '@axonivy/ui-components'; import { IvyIcons } from '@axonivy/ui-icons'; -import { getCoreRowModel, useReactTable, type ColumnDef, type Table as TanstackTable } from '@tanstack/react-table'; +import { getCoreRowModel, useReactTable, type ColumnDef, type Row, type Table as TanstackTable } from '@tanstack/react-table'; import { useEffect } from 'react'; import { useAppContext } from '../context/AppContext'; import { type DataClassField } from '../data/dataclass'; @@ -28,6 +33,10 @@ import { AddFieldDialog } from './AddFieldDialog'; import './DataClassMasterContent.css'; import { ValidationRow } from './ValidationRow'; import { useValidation } from './useValidation'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { genQueryKey } from '../query/query-client'; +import { useClient } from '../protocol/ClientContextProvider'; +import type { CombinePayload } from '../protocol/types'; const fullQualifiedClassNameRegex = /(?:[\w]+\.)+([\w]+)(?=[<,> ]|$)/g; @@ -38,14 +47,17 @@ export const simpleTypeName = (fullQualifiedType: string) => { export const useUpdateSelection = (table: TanstackTable) => { const { setSelectedField } = useAppContext(); const selectedRows = table.getSelectedRowModel().rows; - const selectedField = selectedRows.length === 0 ? undefined : selectedRows[0].index; + const selectedField = selectedRows.length === 1 ? selectedRows[0].index : undefined; useEffect(() => { setSelectedField(selectedField); }, [selectedField, setSelectedField]); }; export const DataClassMasterContent = () => { - const { dataClass, setDataClass, setSelectedField } = useAppContext(); + const { context, dataClass, setDataClass, setSelectedField } = useAppContext(); + const client = useClient(); + const queryClient = useQueryClient(); + const messages = useValidation(); const selection = useTableSelect(); @@ -85,6 +97,7 @@ export const DataClassMasterContent = () => { ]; const table = useReactTable({ ...selection.options, + enableMultiRowSelection: true, ...sort.options, data: dataClass.fields, columns, @@ -97,16 +110,61 @@ export const DataClassMasterContent = () => { useUpdateSelection(table); const deleteField = () => { - const { newData: newFields, selection } = deleteFirstSelectedRow(table, dataClass.fields); + const { newData: newFields, selection } = deleteAllSelectedRows(table, dataClass.fields); const newDataClass = structuredClone(dataClass); newDataClass.fields = newFields; setDataClass(newDataClass); setSelectedField(selection); }; + const updateOrder = (moveId: string, targetId: string) => { + const selectedRows = table.getSelectedRowModel().flatRows.map(r => r.original.name); + const moveIds = selectedRows.length > 1 ? selectedRows : [dataClass.fields[parseInt(moveId)].name]; + const newDataClass = structuredClone(dataClass); + const moveIndexes = moveIds.map(moveId => indexOf(newDataClass.fields, field => field.name === moveId)); + const toIndex = parseInt(targetId); + arrayMoveMultiple(newDataClass.fields, moveIndexes, toIndex); + + setDataClass(newDataClass); + resetAndSetRowSelection(table, newDataClass.fields, moveIds, row => row.name); + }; + + const combineFields = useMutation({ + mutationKey: genQueryKey('function', context), + mutationFn: async () => { + const selectedRows = table.getSelectedRowModel().rows; + const payload: CombinePayload = { + fieldNames: selectedRows.map(row => row.original.name) + }; + return client.function({ actionId: 'combineFields', context, payload }); + }, + onSuccess: () => { + toast.info('Fields successfully combined'); + queryClient.invalidateQueries({ queryKey: genQueryKey('data', context) }); + }, + onError: error => { + toast.error('Failed to combine fields', { description: error.message }); + } + }); + + const handleRowDrag = (row: Row) => { + if (!row.getIsSelected()) { + table.resetRowSelection(); + } + }; + const readonly = useReadonly(); const control = readonly ? null : ( + {table.getSelectedRowModel().rows.length > 0 && ( +