diff --git a/.changeset/brave-monkeys-remain.md b/.changeset/brave-monkeys-remain.md new file mode 100644 index 0000000000..4ef777352b --- /dev/null +++ b/.changeset/brave-monkeys-remain.md @@ -0,0 +1,29 @@ +--- +'@finos/legend-extension-dsl-data-space-studio': patch +'@finos/legend-extension-store-service-store': patch +'@finos/legend-vscode-extension-dependencies': patch +'@finos/legend-application-repl-deployment': patch +'@finos/legend-extension-store-relational': patch +'@finos/legend-server-showcase-deployment': patch +'@finos/legend-extension-dsl-persistence': patch +'@finos/legend-extension-store-flat-data': patch +'@finos/legend-extension-dsl-data-space': patch +'@finos/legend-extension-dsl-diagram': patch +'@finos/legend-extension-dsl-service': patch +'@finos/legend-application-pure-ide': patch +'@finos/legend-extension-assortment': patch +'@finos/legend-application-studio': patch +'@finos/legend-extension-dsl-text': patch +'@finos/legend-application-query': patch +'@finos/legend-application-repl': patch +'@finos/legend-server-showcase': patch +'@finos/legend-query-builder': patch +'@finos/legend-application': patch +'@finos/legend-server-sdlc': patch +'@finos/legend-dev-utils': patch +'@finos/stylelint-config-legend-studio': patch +'@finos/legend-shared': patch +'@finos/legend-graph': patch +'@finos/legend-lego': patch +'@finos/legend-art': patch +--- diff --git a/.changeset/cool-mayflies-warn.md b/.changeset/cool-mayflies-warn.md new file mode 100644 index 0000000000..740ef399f4 --- /dev/null +++ b/.changeset/cool-mayflies-warn.md @@ -0,0 +1,17 @@ +--- +'@finos/legend-extension-store-service-store': patch +'@finos/legend-application-repl-deployment': patch +'@finos/legend-extension-dsl-data-space': patch +'@finos/legend-extension-dsl-diagram': patch +'@finos/legend-application-pure-ide': patch +'@finos/legend-application-studio': patch +'@finos/legend-extension-dsl-text': patch +'@finos/legend-application-query': patch +'@finos/legend-application-repl': patch +'@finos/legend-query-builder': patch +'@finos/legend-dev-utils': patch +'@finos/stylelint-config-legend-studio': patch +'@finos/legend-shared': patch +'@finos/legend-lego': patch +'@finos/legend-art': patch +--- diff --git a/.changeset/grumpy-pens-hear.md b/.changeset/grumpy-pens-hear.md new file mode 100644 index 0000000000..2d3b153f59 --- /dev/null +++ b/.changeset/grumpy-pens-hear.md @@ -0,0 +1,4 @@ +--- +'@finos/legend-application-repl': patch +'@finos/legend-art': patch +--- diff --git a/.changeset/poor-ligers-teach.md b/.changeset/poor-ligers-teach.md new file mode 100644 index 0000000000..e5c1944636 --- /dev/null +++ b/.changeset/poor-ligers-teach.md @@ -0,0 +1,8 @@ +--- +'@finos/legend-application-repl-deployment': patch +'@finos/legend-application-repl': patch +'@finos/legend-query-builder': patch +'@finos/legend-dev-utils': patch +'@finos/legend-shared': patch +'@finos/legend-art': patch +--- diff --git a/.changeset/short-keys-flash.md b/.changeset/short-keys-flash.md new file mode 100644 index 0000000000..bd9a24ee65 --- /dev/null +++ b/.changeset/short-keys-flash.md @@ -0,0 +1,5 @@ +--- +'@finos/legend-application-repl-deployment': patch +'@finos/legend-application-repl': patch +'@finos/legend-art': patch +--- diff --git a/.vscode/copyright.code-snippets b/.vscode/copyright.code-snippets new file mode 100644 index 0000000000..7dbec1c5a0 --- /dev/null +++ b/.vscode/copyright.code-snippets @@ -0,0 +1,7 @@ +{ + "Print to console": { + "prefix": ["copyright"], + "body": ["/**\n * Copyright (c) 2020-present, Goldman Sachs\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"], + "description": "Add copyright header", + }, +} diff --git a/fixtures/legend-mock-server/package.json b/fixtures/legend-mock-server/package.json index 08d32e1c30..8983c50a74 100644 --- a/fixtures/legend-mock-server/package.json +++ b/fixtures/legend-mock-server/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@fastify/cors": "9.0.1", - "fastify": "4.28.0" + "fastify": "4.28.1" }, "devDependencies": { "@finos/legend-dev-utils": "workspace:*", diff --git a/package.json b/package.json index 049973b0c4..d0b5cdad0a 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "fix:format": "prettier --write --loglevel=warn \"(scripts|docs)/**/*.{md,json,mjs,cjs,js,ts,tsx,html,scss,css}\" \"packages/*/**/*.{md,json,mjs,cjs,js,ts,tsx,html,scss,css}\" && yarn sort-package-json \"package.json\" \"packages/*/package.json\"", "fix:js": "cross-env NODE_ENV=production FORCE_COLOR=1 eslint --cache --cache-location ./build/.eslintcache \"scripts/**/*.{mjs,cjs,js,ts,tsx}\" --report-unused-disable-directives --fix && cross-env FORCE_COLOR=1 yarn workspaces foreach --all --parallel --exclude legend-studio -vv run lint:js --fix", "fix:style": "yarn lint:style --fix", + "fix:watchman": "node ./scripts/test/fixWatchman.js", "git:install-hooks": "husky install", "git:pre-commit": "lint-staged --config ./package.json", "git:upstream:setup": "git remote add upstream https://github.com/finos/legend-studio.git", @@ -99,14 +100,14 @@ "@finos/eslint-plugin-legend-studio": "workspace:*", "@finos/legend-dev-utils": "workspace:*", "@finos/stylelint-config-legend-studio": "workspace:*", - "@types/node": "20.14.8", + "@types/node": "20.14.9", "chalk": "5.3.0", "cross-env": "7.0.3", "envinfo": "7.13.0", "eslint": "8.57.0", "fs-extra": "11.2.0", "husky": "9.0.11", - "inquirer": "9.2.23", + "inquirer": "9.3.2", "jest": "29.7.0", "lint-staged": "15.2.7", "micromatch": "4.0.7", @@ -118,7 +119,7 @@ "semver": "7.6.2", "sort-package-json": "2.10.0", "stylelint": "16.6.1", - "typedoc": "0.26.2", + "typedoc": "0.26.3", "typescript": "5.5.2", "yargs": "17.7.2" }, diff --git a/packages/legend-application-pure-ide/package.json b/packages/legend-application-pure-ide/package.json index bf24165519..9cd90f9b61 100644 --- a/packages/legend-application-pure-ide/package.json +++ b/packages/legend-application-pure-ide/package.json @@ -51,7 +51,7 @@ "@finos/legend-shared": "workspace:*", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", - "mobx": "6.12.4", + "mobx": "6.12.5", "mobx-react-lite": "4.0.7", "monaco-editor": "0.50.0", "react": "18.3.1", diff --git a/packages/legend-application-pure-ide/src/components/editor-group/DiagramEditor.tsx b/packages/legend-application-pure-ide/src/components/editor-group/DiagramEditor.tsx index de7909fa32..91483f9e40 100644 --- a/packages/legend-application-pure-ide/src/components/editor-group/DiagramEditor.tsx +++ b/packages/legend-application-pure-ide/src/components/editor-group/DiagramEditor.tsx @@ -41,7 +41,7 @@ import { clsx, DistributeHorizontalIcon, DistributeVerticalIcon, - DropdownMenu, + ControlledDropdownMenu, GoToFileIcon, MenuContent, MenuContentDivider, @@ -276,7 +276,7 @@ const DiagramEditorHeader = observer( - - +
- - +
); } diff --git a/packages/legend-application-query/src/components/QueryEditor.tsx b/packages/legend-application-query/src/components/QueryEditor.tsx index dd3d4f244b..f521ae9dac 100644 --- a/packages/legend-application-query/src/components/QueryEditor.tsx +++ b/packages/legend-application-query/src/components/QueryEditor.tsx @@ -18,7 +18,7 @@ import { Dialog, PanelLoadingIndicator, BlankPanelContent, - DropdownMenu, + ControlledDropdownMenu, MenuContent, MenuContentItem, MenuIcon, @@ -622,7 +622,7 @@ export const QueryEditor = observer(() => {
- { } > - +
Legend Query diff --git a/packages/legend-application-query/src/components/QuerySetup.tsx b/packages/legend-application-query/src/components/QuerySetup.tsx index 0953de8378..76cf3d9197 100644 --- a/packages/legend-application-query/src/components/QuerySetup.tsx +++ b/packages/legend-application-query/src/components/QuerySetup.tsx @@ -20,7 +20,7 @@ import { QuestionCircleIcon, CogIcon, MoreHorizontalIcon, - DropdownMenu, + ControlledDropdownMenu, PencilIcon, ThinChevronDownIcon, CircleIcon, @@ -214,7 +214,7 @@ const QuerySetupActionGroup = observer( )}
{(!tag || setupStore.tagToFocus === tag) && ( - } @@ -229,7 +229,7 @@ const QuerySetupActionGroup = observer(
)} - + )}
diff --git a/packages/legend-application-repl-deployment/repl.config.js b/packages/legend-application-repl-deployment/repl.config.js index d489951ded..febd292998 100644 --- a/packages/legend-application-repl-deployment/repl.config.js +++ b/packages/legend-application-repl-deployment/repl.config.js @@ -29,11 +29,11 @@ export default { */ baseUrl: '/repl/', /** - * `isRelativePathSupported` [boolean, optional] + * `useRelativePath` [boolean, optional] * Boolean flag to check if your website supports resolution of relative * paths for resources */ - isRelativePathSupported: true, + useRelativePath: true, /** * `devServerOptions` [object, optional] * Options to override `webpack-dev-server` configs. diff --git a/packages/legend-application-repl-deployment/src/index.html b/packages/legend-application-repl-deployment/src/index.html index 311f144f2f..4885d86929 100644 --- a/packages/legend-application-repl-deployment/src/index.html +++ b/packages/legend-application-repl-deployment/src/index.html @@ -1,7 +1,7 @@ - ▦ Legend DataCube + ⊞ Legend DataCube { const dataCubeStore = useREPLStore(); - const dataCubeState = dataCubeStore.dataCubeState; + const dataCube = dataCubeStore.dataCube; return ( -
- - - +
+
+ + +
+ +
+
+
+
+ {dataCube.runningTaskes.size > 0 && ( + + )} +
+
); }); const DataCubeTitleBar = observer(() => { const dataCubeStore = useREPLStore(); - const dataCubeState = dataCubeStore.dataCubeState; + const dataCube = dataCubeStore.dataCube; return (
-
{dataCubeState.editor.generalPropertiesPanel.name}
+
{dataCube.core.name}
{/* TODO: @akphi - add save icon */}
@@ -63,11 +86,11 @@ export const DataCube = observer(() => { const dataCubeStore = useREPLStore(); const ref = useRef(null); const applicationStore = useApplicationStore(); - const dataCubeState = dataCubeStore.dataCubeState; + const dataCube = dataCubeStore.dataCube; useEffect(() => { - dataCubeState.initialize().catch(applicationStore.logUnhandledError); - }, [dataCubeState, applicationStore]); + dataCube.initialize().catch(applicationStore.logUnhandledError); + }, [dataCube, applicationStore]); return (
{ - {dataCubeState.editor.isPanelOpen && ( - - )} + {dataCube.editor.isPanelOpen && }
); }); diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditor.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditor.tsx index dc51a066f9..c506434616 100644 --- a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditor.tsx +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditor.tsx @@ -28,6 +28,7 @@ import { DataCubeEditorFilterPanel } from './DataCubeEditorFilterPanel.js'; import { DataCubeEditorExtendedColumnsPanel } from './DataCubeEditorExtendedColumnsPanel.js'; import { DataCubeEditorCodePanel } from './DataCubeEditorCodePanel.js'; import { DataCubeEditorColumnPropertiesPanel } from './DataCubeEditorColumnPropertiesPanel.js'; +import { DataCubeEditorDeveloperPanel } from './DataCubeEditorDeveloperPanel.js'; const __DATA_CUBE_EDITOR_HEADER_CLASS_NAME = 'data-cube__editor__header'; const PANEL_DEFAULT_OFFSET = 50; @@ -46,7 +47,7 @@ export const DataCubeEditor = observer( height: PANEL_DEFAULT_HEIGHT, }); const replStore = useREPLStore(); - const editor = replStore.dataCubeState.editor; + const editor = replStore.dataCube.editor; const selectedTab = editor.currentTab; const tabs = [ DATA_CUBE_EDITOR_TAB.COLUMNS, @@ -58,6 +59,7 @@ export const DataCubeEditor = observer( DATA_CUBE_EDITOR_TAB.GENERAL_PROPERTIES, DATA_CUBE_EDITOR_TAB.COLUMN_PROPERTIES, DATA_CUBE_EDITOR_TAB.CODE, + DATA_CUBE_EDITOR_TAB.DEVELOPER, ]; useEffect(() => { @@ -204,6 +206,9 @@ export const DataCubeEditor = observer( {selectedTab === DATA_CUBE_EDITOR_TAB.CODE && ( )} + {selectedTab === DATA_CUBE_EDITOR_TAB.DEVELOPER && ( + + )}
diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorCodePanel.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorCodePanel.tsx index 71123ad46a..66607b384f 100644 --- a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorCodePanel.tsx +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorCodePanel.tsx @@ -143,7 +143,7 @@ import { useREPLStore } from '../../REPLStoreProvider.js'; export const DataCubeEditorCodePanel = observer(() => { const replStore = useREPLStore(); - const panel = replStore.dataCubeState.editor.sortsPanel; + const panel = replStore.dataCube.editor.sortsPanel; // const executeLambda = (): void => { // // TODO: @akphi // // flowResult(dataCubeState.executeLambda()).catch( @@ -154,7 +154,7 @@ export const DataCubeEditorCodePanel = observer(() => { useEffect(() => {}, [panel]); // TODO: @akphi - remove this dummy useEffect return ( -
+
diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorColumnPropertiesPanel.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorColumnPropertiesPanel.tsx index 8da287a8c6..f3445de141 100644 --- a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorColumnPropertiesPanel.tsx +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorColumnPropertiesPanel.tsx @@ -14,19 +14,78 @@ * limitations under the License. */ -import { DataCubeIcon } from '@finos/legend-art'; +import { cn, DataCubeIcon, useDropdownMenu } from '@finos/legend-art'; import { observer } from 'mobx-react-lite'; import { useREPLStore } from '../../REPLStoreProvider.js'; -import { useEffect } from 'react'; +import { + DataCubeEditorCheckbox, + DataCubeEditorColorPickerButton, + DataCubeEditorDropdownMenu, + DataCubeEditorDropdownMenuItem, + DataCubeEditorDropdownMenuItemSeparator, + DataCubeEditorDropdownMenuTrigger, + DataCubeEditorNumberInput, + DataCubeEditorTextInput, + WIP_Badge, +} from './DataCubeEditorShared.js'; +import { + DataCubeAggregateFunction, + DataCubeColumnDataType, + DataCubeColumnPinPlacement, + DataCubeFont, + DataCubeFontFormatUnderlinedVariant, + DataCubeFontTextAlignment, + DataCubeNumberScale, + DEFAULT_BACKGROUND_COLOR, + DEFAULT_COLUMN_MAX_WIDTH, + DEFAULT_COLUMN_MIN_WIDTH, + DEFAULT_COLUMN_WIDTH, + DEFAULT_ERROR_FOREGROUND_COLOR, + DEFAULT_FOREGROUND_COLOR, + DEFAULT_NEGATIVE_FOREGROUND_COLOR, + DEFAULT_ZERO_FOREGROUND_COLOR, +} from '../../../stores/dataCube/core/DataCubeQueryEngine.js'; export const DataCubeEditorColumnPropertiesPanel = observer(() => { const replStore = useREPLStore(); - const panel = replStore.dataCubeState.editor.sortsPanel; - - useEffect(() => {}, [panel]); // TODO: @akphi - remove this dummy useEffect + const dataCube = replStore.dataCube; + const panel = dataCube.editor.columnPropertiesPanel; + const selectedColumn = panel.selectedColumn; + const [openColumnsDropdown, closeColumnsDropdown, columnsDropdownProps] = + useDropdownMenu(); + const [ + openAggregationTypeDropdown, + closeAggregationTypeDropdown, + aggregationTypeDropdownProps, + ] = useDropdownMenu(); + const [ + openNumberScaleDropdown, + closeNumberScaleDropdown, + numberScaleDropdownProps, + ] = useDropdownMenu(); + const [ + openFontFamilyDropdown, + closeFontFamilyDropdown, + fontFamilyDropdownProps, + ] = useDropdownMenu(); + const [ + openFontSizeDropdown, + closeFontSizeDropdown, + openFontSizeDropdownProps, + ] = useDropdownMenu(); + const [ + openFontFormatUnderlinedVariantDropdown, + closeFontFormatUnderlinedVariantDropdown, + fontFormatUnderlinedVariantDropdownProps, + ] = useDropdownMenu(); + const [ + openColumnPinDropdown, + closeColumnPinDropdown, + columnPinDropdownProps, + ] = useDropdownMenu(); return ( -
+
@@ -36,7 +95,778 @@ export const DataCubeEditorColumnPropertiesPanel = observer(() => { Column Properties
-
+
+
+
+
+ Choose Column: +
+ + {selectedColumn?.name ?? '(None)'} + + + {panel.columns.map((column) => ( + { + panel.setSelectedColumnName(column.name); + closeColumnsDropdown(); + }} + > + {column.name} + + ))} + + {selectedColumn && ( + // TODO: if this is an extended column, show the info and link it + // e.g. `Extended Column (Leaf)`, `Extended Column (Group)` + // with arrow button to go to the extended column editor + <> +
+
+
+ {selectedColumn.dataType} +
+
+ {selectedColumn.kind} +
+
+ + )} +
+ +
+ + {selectedColumn && ( + <> +
+
+ Display Name: +
+ { + const value = event.target.value.trim(); + selectedColumn.setDisplayName( + value !== '' ? value : undefined, + ); + }} + /> +
+ + {selectedColumn.dataType === DataCubeColumnDataType.NUMBER && ( + <> +
+
+ Number Format: +
+ { + selectedColumn.setDecimals(value); + }} + /> +
+ Decimal places +
+ + selectedColumn.setDisplayCommas( + !selectedColumn.displayCommas, + ) + } + /> + + selectedColumn.setNegativeNumberInParens( + !selectedColumn.negativeNumberInParens, + ) + } + /> +
+ +
+
+ Number Scale: +
+ + {selectedColumn.numberScale ?? '(None)'} + + + {[ + undefined, + DataCubeNumberScale.PERCENT, + DataCubeNumberScale.BASIS_POINT, + DataCubeNumberScale.THOUSANDS, + DataCubeNumberScale.MILLIONS, + DataCubeNumberScale.BILLIONS, + DataCubeNumberScale.AUTO, + ].map((scale) => ( + { + selectedColumn.setNumberScale(scale); + closeNumberScaleDropdown(); + }} + > + {scale ?? '(None)'} + + ))} + +
+ +
+
+ Aggregation Type: +
+ + {selectedColumn.aggregateFunction ?? '(None)'} + + + {[ + DataCubeAggregateFunction.SUM, + DataCubeAggregateFunction.AVERAGE, + DataCubeAggregateFunction.COUNT, + DataCubeAggregateFunction.MIN, + DataCubeAggregateFunction.MAX, + ].map((fn) => ( + { + selectedColumn.setAggregateFunction(fn); + closeAggregationTypeDropdown(); + }} + > + {fn} + + ))} + + +
+
+ Weight Column: +
+ + {selectedColumn.weightColumn ?? '(None)'} + + +
+ +
+
+ Exclude from HPivot? +
+ + selectedColumn.setExcludedFromHPivot( + !selectedColumn.excludedFromHPivot, + ) + } + disabled={true} + /> + +
+ + )} + + {selectedColumn.dataType === DataCubeColumnDataType.TEXT && ( + <> +
+
+ Dislay as Link? +
+ + selectedColumn.setDisplayAsLink( + !selectedColumn.displayAsLink, + ) + } + /> +
+ + )} + +
+ +
+
+ Font: +
+ + {selectedColumn.fontFamily} + + + {[ + DataCubeFont.ARIAL, + DataCubeFont.ROBOTO, + DataCubeFont.ROBOTO_CONDENSED, + ].map((font) => ( + { + selectedColumn.setFontFamily(font); + closeFontFamilyDropdown(); + }} + > + {font} + + ))} + + {[ + DataCubeFont.GEORGIA, + DataCubeFont.ROBOTO_SERIF, + DataCubeFont.TIMES_NEW_ROMAN, + ].map((font) => ( + { + selectedColumn.setFontFamily(font); + closeFontFamilyDropdown(); + }} + > + {font} + + ))} + + {[ + DataCubeFont.JERBRAINS_MONO, + DataCubeFont.ROBOTO_MONO, + DataCubeFont.UBUNTU_MONO, + ].map((font) => ( + { + selectedColumn.setFontFamily(font); + closeFontFamilyDropdown(); + }} + > + {font} + + ))} + + + + {selectedColumn.fontSize} + + + {[ + 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, + 28, 32, 36, 48, 72, + ].map((size) => ( + { + selectedColumn.setFontSize(size); + closeFontSizeDropdown(); + }} + > + {size} + + ))} + + +
+ + + + + + {[ + DataCubeFontFormatUnderlinedVariant.SOLID, + DataCubeFontFormatUnderlinedVariant.DASHED, + DataCubeFontFormatUnderlinedVariant.DOTTED, + DataCubeFontFormatUnderlinedVariant.DOUBLE, + DataCubeFontFormatUnderlinedVariant.WAVY, + ].map((variant) => ( + { + selectedColumn.setFontUnderlined(variant); + closeFontFormatUnderlinedVariantDropdown(); + }} + > +
+                  +
+
+ ))} +
+ +
+ +
+ + + +
+
+ +
+
+ Colors: +
+
+
+
+
+ Normal +
+
+ Negative +
+
+ Zero +
+
+ Error +
+
+
+
+ Foreground: +
+
+ + selectedColumn.setForegroundColor(value) + } + /> +
+
+ + selectedColumn.setForegroundNegativeColor(value) + } + /> +
+
+ + selectedColumn.setForegroundZeroColor(value) + } + /> +
+
+ + selectedColumn.setForegroundErrorColor(value) + } + /> +
+
+
+
+ Background: +
+
+ + selectedColumn.setBackgroundColor(value) + } + /> +
+
+ + selectedColumn.setBackgroundNegativeColor(value) + } + /> +
+
+ + selectedColumn.setBackgroundZeroColor(value) + } + /> +
+
+ + selectedColumn.setBackgroundErrorColor(value) + } + /> +
+
+
+
+ +
+
+ Hide from View? +
+ + selectedColumn.setHideFromView(!selectedColumn.hideFromView) + } + disabled={true} + /> + +
+ +
+
+ Pin: +
+ + {selectedColumn.pinned ?? '(None)'} + + + {[ + undefined, + DataCubeColumnPinPlacement.LEFT, + DataCubeColumnPinPlacement.RIGHT, + ].map((placement) => ( + { + selectedColumn.setPinned(placement); + closeColumnPinDropdown(); + }} + > + {placement ?? '(None)'} + + ))} + +
+ +
+
+ Width: +
+ { + if ( + selectedColumn.fixedWidth === undefined && + selectedColumn.minWidth === undefined && + selectedColumn.maxWidth === undefined + ) { + selectedColumn.setFixedWidth(DEFAULT_COLUMN_WIDTH); + selectedColumn.setMinWidth(undefined); + selectedColumn.setMaxWidth(undefined); + } else { + selectedColumn.setFixedWidth(undefined); + selectedColumn.setMinWidth(undefined); + selectedColumn.setMaxWidth(undefined); + } + }} + /> + + { + selectedColumn.setFixedWidth( + selectedColumn.fixedWidth !== undefined + ? undefined + : DEFAULT_COLUMN_WIDTH, + ); + selectedColumn.setMinWidth(undefined); + selectedColumn.setMaxWidth(undefined); + }} + /> +
+ value !== undefined && value > 0} + value={selectedColumn.fixedWidth} + setValue={(value) => { + selectedColumn.setFixedWidth(value); + }} + disabled={ + selectedColumn.minWidth !== undefined || + selectedColumn.maxWidth !== undefined + } + /> + + { + if ( + selectedColumn.minWidth === undefined && + selectedColumn.maxWidth === undefined + ) { + selectedColumn.setMinWidth(DEFAULT_COLUMN_MIN_WIDTH); + selectedColumn.setMaxWidth(DEFAULT_COLUMN_MAX_WIDTH); + selectedColumn.setFixedWidth(undefined); + } else { + selectedColumn.setMinWidth(undefined); + selectedColumn.setMaxWidth(undefined); + selectedColumn.setFixedWidth(undefined); + } + }} + /> +
+ value !== undefined && value > 0} + value={selectedColumn.minWidth} + setValue={(value) => { + selectedColumn.setMinWidth(value); + }} + disabled={selectedColumn.fixedWidth !== undefined} + /> +
+ + value !== undefined && + value >= (selectedColumn.minWidth ?? 0) + } + value={selectedColumn.maxWidth} + setValue={(value) => { + selectedColumn.setMaxWidth(value); + }} + disabled={selectedColumn.fixedWidth !== undefined} + /> +
+ +
+
+ Blur Content? +
+ selectedColumn.setBlur(!selectedColumn.blur)} + disabled={true} + /> + +
+ + )} +
+
); }); diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorColumnsPanel.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorColumnsPanel.tsx index 8efde0b831..8bc57afd5f 100644 --- a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorColumnsPanel.tsx +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorColumnsPanel.tsx @@ -21,12 +21,12 @@ import { useEffect } from 'react'; export const DataCubeEditorColumnsPanel = observer(() => { const replStore = useREPLStore(); - const panel = replStore.dataCubeState.editor.sortsPanel; + const panel = replStore.dataCube.editor.sortsPanel; useEffect(() => {}, [panel]); // TODO: @akphi - remove this dummy useEffect return ( -
+
diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorColumnsSelector.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorColumnsSelector.tsx index 27e2bbc3c9..a8ef07503a 100644 --- a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorColumnsSelector.tsx +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorColumnsSelector.tsx @@ -15,20 +15,21 @@ */ import { observer } from 'mobx-react-lite'; -import { DataCubeIcon } from '@finos/legend-art'; +import { cn, DataCubeIcon } from '@finos/legend-art'; import type { ColDef, ColDefField, GridApi, - IRowNode, + ModelUpdatedEvent, RowDragEndEvent, SelectionChangedEvent, } from '@ag-grid-community/core'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { AgGridReact, type AgGridReactProps, type CustomCellRendererProps, + type CustomNoRowsOverlayProps, } from '@ag-grid-community/react'; import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model'; import type { @@ -36,6 +37,10 @@ import type { DataCubeEditorColumnsSelectorState, } from '../../../stores/dataCube/editor/DataCubeEditorColumnsSelectorState.js'; import { isNonNullable } from '@finos/legend-shared'; +import { + getDataForAllFilteredNodes, + getDataForAllNodes, +} from '../../../stores/dataCube/grid/DataCubeGridClientEngine.js'; function getBaseGridProps< T extends DataCubeEditorColumnsSelectorColumnState, @@ -55,7 +60,50 @@ function getBaseGridProps< headerHeight: 20, suppressRowHoverHighlight: false, reactiveCustomComponents: true, // TODO: remove on v32 as this would be default to `true` then - noRowsOverlayComponent: () =>
, + noRowsOverlayComponent: ( + params: CustomNoRowsOverlayProps & { + violationSeverity?: 'warning' | 'error' | undefined; + }, + ) => { + if (params.api.getQuickFilter()) { + return ( +
+
+ +
+ No match found +
+ ); + } + if (params.violationSeverity) { + return ( +
+
+ +
+ No columns selected +
+ ); + } + return
; + }, + // Show no rows overlay when there are no search results + // See https://stackoverflow.com/a/72637410 + onModelUpdated: (event: ModelUpdatedEvent) => { + event.api.getDisplayedRowCount() === 0 + ? event.api.showNoRowsOverlay() + : event.api.hideOverlay(); + }, }; } @@ -80,14 +128,54 @@ function getBaseColumnDef< } return (params.rowNode?.data as T).name; }, + getQuickFilterText: (params) => params.value, }; } +/** + * Move this display to a separate component to avoid re-rendering the header too frequently + */ +const ColumnsSearchResultCountBadge = observer( + function ColumnsSearchResultCountBadge< + T extends DataCubeEditorColumnsSelectorColumnState, + >(props: { + selector: DataCubeEditorColumnsSelectorState; + gridApi: GridApi; + scope: 'available' | 'selected'; + }) { + const { selector, gridApi, scope } = props; + return ( +
+ {`${getDataForAllFilteredNodes(gridApi).length}/${scope === 'available' ? selector.availableColumns.length : selector.selectedColumns.length}`} + + {scope === 'available' + ? // subscribing to the search text to trigger re-render as it changes + selector.availableColumnsSearchText + : selector.selectedColumnsSearchText} + +
+ ); + }, +); + export const DataCubeEditorColumnsSelector = observer( function DataCubeEditorColumnsSelector< T extends DataCubeEditorColumnsSelectorColumnState, - >(props: { selector: DataCubeEditorColumnsSelectorState }) { - const { selector } = props; + >(props: { + selector: DataCubeEditorColumnsSelectorState; + extraColumnComponent?: + | React.FC<{ + selector: DataCubeEditorColumnsSelectorState; + column: T; + }> + | undefined; + noColumnsSelectedViolationSeverity?: 'warning' | 'error' | undefined; + }) { + const { + selector, + extraColumnComponent, + noColumnsSelectedViolationSeverity, + } = props; const [selectedAvailableColumns, setSelectedAvailableColumns] = useState< T[] >([]); @@ -98,6 +186,10 @@ export const DataCubeEditorColumnsSelector = observer( useState(null); const [selectedColumnsGridApi, setSelectedColumnsGridApi] = useState(null); + const searchAvailableColumnsInputRef = useRef( + null, + ); + const searchSelectedColumnsInputRef = useRef(null); /** * Since we use managed row dragging for selected columns, @@ -107,16 +199,11 @@ export const DataCubeEditorColumnsSelector = observer( */ const onSelectedColumnsDragStop = useCallback( (params: RowDragEndEvent) => { - const newRowData: T[] = []; - params.api.forEachNode((node: IRowNode) => { - if (node.data) { - newRowData.push(node.data); - } - }); - selector.setSelectedColumns(newRowData); + const newData = getDataForAllNodes(params.api); + selector.setSelectedColumns(newData); selector.setAvailableColumns( selector.availableColumns.filter( - (column) => !newRowData.includes(column), + (column) => !newData.includes(column), ), ); }, @@ -154,17 +241,16 @@ export const DataCubeEditorColumnsSelector = observer( if (event.overIndex === -1) { return; } - const newRowData: T[] = []; - event.api.forEachNode((node: IRowNode) => { - if (node.data) { - newRowData.push(node.data); - } - }); - selector.setSelectedColumns(newRowData); + const newData = getDataForAllNodes(event.api); + selector.setSelectedColumns(newData); }, [selector], ); + /** + * Setup row drop zones for each grid to be the other + * See https://www.ag-grid.com/react-data-grid/row-dragging-to-grid/ + */ useEffect(() => { if (!availableColumnsGridApi || !selectedColumnsGridApi) { return; @@ -196,7 +282,7 @@ export const DataCubeEditorColumnsSelector = observer( ]); return ( -
+
Available columns: @@ -204,12 +290,35 @@ export const DataCubeEditorColumnsSelector = observer(
+ selector.setAvailableColumnsSearchText(event.target.value) + } + onKeyDown={(event) => { + if (event.code === 'Escape') { + event.stopPropagation(); + searchAvailableColumnsInputRef.current?.select(); + selector.setAvailableColumnsSearchText(''); + } + }} />
+
setAvailableColumnsGridApi(params.api)} - rowData={selector.availableColumns} onSelectionChanged={(event: SelectionChangedEvent) => { setSelectedAvailableColumns( event.api @@ -227,23 +335,43 @@ export const DataCubeEditorColumnsSelector = observer( .filter(isNonNullable), ); }} + // Using ag-grid quick filter is a cheap way to implement search + quickFilterText={selector.availableColumnsSearchText} + rowData={selector.availableColumns} columnDefs={[ { ...getBaseColumnDef(), + /** + * Support double-click to add all (filtered by search) columns + */ headerComponent: (params: CustomCellRendererProps) => ( -
{ - // TODO: scope this by the current search + // The columns being moved are scoped by the current search + const filteredData = getDataForAllFilteredNodes( + params.api, + ); selector.setSelectedColumns([ ...selector.selectedColumns, - ...selector.availableColumns, + ...filteredData, ]); - selector.setAvailableColumns([]); + selector.setAvailableColumns( + selector.availableColumns.filter( + (column) => !filteredData.includes(column), + ), + ); params.api.clearFocusedCell(); }} - >{`[All Columns]`}
+ > +
{`[All Columns]`}
+ + ), cellRenderer: (params: CustomCellRendererProps) => { const data = params.data; @@ -252,7 +380,7 @@ export const DataCubeEditorColumnsSelector = observer( } return (
{ selector.setSelectedColumns([ @@ -267,7 +395,15 @@ export const DataCubeEditorColumnsSelector = observer( params.api.clearFocusedCell(); }} > - {data.name} +
+ {data.name} +
+
+ {extraColumnComponent?.({ + selector, + column: data, + }) ?? null} +
); }, @@ -280,40 +416,66 @@ export const DataCubeEditorColumnsSelector = observer(
(), + /** + * Support double-click to remove all (filtered by search) columns + */ headerComponent: (params: CustomCellRendererProps) => ( -
{ - // TODO: scope this by the current search + // The columns being moved are scoped by the current search + const filteredData = getDataForAllFilteredNodes( + params.api, + ); selector.setAvailableColumns([ ...selector.availableColumns, - ...selector.selectedColumns, + ...filteredData, ]); - selector.setSelectedColumns([]); + selector.setSelectedColumns( + selector.selectedColumns.filter( + (column) => !filteredData.includes(column), + ), + ); params.api.clearFocusedCell(); }} - >{`[All Columns]`}
+ > +
{`[All Columns]`}
+ + ), cellRenderer: (params: CustomCellRendererProps) => { const data = params.data; @@ -377,7 +583,7 @@ export const DataCubeEditorColumnsSelector = observer( } return (
{ selector.setAvailableColumns([ @@ -392,7 +598,15 @@ export const DataCubeEditorColumnsSelector = observer( params.api.clearFocusedCell(); }} > - {data.name} +
+ {data.name} +
+
+ {extraColumnComponent?.({ + selector, + column: data, + }) ?? null} +
); }, diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorDeveloperPanel.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorDeveloperPanel.tsx new file mode 100644 index 0000000000..418fb88558 --- /dev/null +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorDeveloperPanel.tsx @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DataCubeIcon } from '@finos/legend-art'; +import { observer } from 'mobx-react-lite'; +import { useREPLStore } from '../../REPLStoreProvider.js'; +import { useEffect } from 'react'; + +export const DataCubeEditorDeveloperPanel = observer(() => { + const replStore = useREPLStore(); + const panel = replStore.dataCube.editor.sortsPanel; + + useEffect(() => {}, [panel]); // TODO: @akphi - remove this dummy useEffect + + return ( +
+
+
+ +
+
+ Developer +
+
+
+
+ ); +}); diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorExtendedColumnsPanel.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorExtendedColumnsPanel.tsx index c0dac62d14..658cd90a61 100644 --- a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorExtendedColumnsPanel.tsx +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorExtendedColumnsPanel.tsx @@ -21,12 +21,12 @@ import { useEffect } from 'react'; export const DataCubeEditorExtendedColumnsPanel = observer(() => { const replStore = useREPLStore(); - const panel = replStore.dataCubeState.editor.sortsPanel; + const panel = replStore.dataCube.editor.sortsPanel; useEffect(() => {}, [panel]); // TODO: @akphi - remove this dummy useEffect return ( -
+
diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorFilterPanel.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorFilterPanel.tsx index db4292e73c..822e5d4812 100644 --- a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorFilterPanel.tsx +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorFilterPanel.tsx @@ -21,12 +21,12 @@ import { useEffect } from 'react'; export const DataCubeEditorFilterPanel = observer(() => { const replStore = useREPLStore(); - const panel = replStore.dataCubeState.editor.sortsPanel; + const panel = replStore.dataCube.editor.sortsPanel; useEffect(() => {}, [panel]); // TODO: @akphi - remove this dummy useEffect return ( -
+
diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorGeneralPropertiesPanel.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorGeneralPropertiesPanel.tsx index 5507ac2d46..0c9ba467ce 100644 --- a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorGeneralPropertiesPanel.tsx +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorGeneralPropertiesPanel.tsx @@ -16,14 +16,69 @@ import { observer } from 'mobx-react-lite'; import { useREPLStore } from '../../REPLStoreProvider.js'; -import { DataCubeIcon } from '@finos/legend-art'; +import { cn, DataCubeIcon, useDropdownMenu } from '@finos/legend-art'; +import { + DataCubeFont, + DataCubeFontFormatUnderlinedVariant, + DataCubeFontTextAlignment, + DataCubeNumberScale, + DataCubeSelectionStat, + DEFAULT_BACKGROUND_COLOR, + DEFAULT_ERROR_FOREGROUND_COLOR, + DEFAULT_FOREGROUND_COLOR, + DEFAULT_NEGATIVE_FOREGROUND_COLOR, + DEFAULT_ROW_HIGHLIGHT_BACKGROUND_COLOR, + DEFAULT_ZERO_FOREGROUND_COLOR, +} from '../../../stores/dataCube/core/DataCubeQueryEngine.js'; +import { + DataCubeEditorCheckbox, + DataCubeEditorColorPickerButton, + DataCubeEditorDropdownMenu, + DataCubeEditorDropdownMenuItem, + DataCubeEditorDropdownMenuItemSeparator, + DataCubeEditorDropdownMenuTrigger, + DataCubeEditorTextInput, + DataCubeEditorNumberInput, + WIP_Badge, +} from './DataCubeEditorShared.js'; export const DataCubeEditorGeneralPropertiesPanel = observer(() => { const replStore = useREPLStore(); - const panel = replStore.dataCubeState.editor.generalPropertiesPanel; + const panel = replStore.dataCube.editor.generalPropertiesPanel; + const configuration = panel.configuration; + const [ + openInitialExpandLevelDropdown, + closeInitialExpandLevelDropdown, + initialExpandLevelDropdownProps, + ] = useDropdownMenu(); + const [ + openNumberScaleDropdown, + closeNumberScaleDropdown, + numberScaleDropdownProps, + ] = useDropdownMenu(); + const [ + openSelectionStatDropdown, + closeSelectionStatDropdown, + selectionStatDropdownProps, + ] = useDropdownMenu(); + const [ + openFontFamilyDropdown, + closeFontFamilyDropdown, + fontFamilyDropdownProps, + ] = useDropdownMenu(); + const [ + openFontSizeDropdown, + closeFontSizeDropdown, + openFontSizeDropdownProps, + ] = useDropdownMenu(); + const [ + openFontFormatUnderlinedVariantDropdown, + closeFontFormatUnderlinedVariantDropdown, + fontFormatUnderlinedVariantDropdownProps, + ] = useDropdownMenu(); return ( -
+
@@ -32,16 +87,630 @@ export const DataCubeEditorGeneralPropertiesPanel = observer(() => { General Properties
-
-
-
Report Title:
- { - panel.setName(event.target.value); - }} - /> +
+
+
+
+ Report Title: +
+ { + panel.setName(event.target.value); + }} + /> +
+ +
+
+ Initially Expand to Level: +
+ + {configuration.initialExpandLevel ?? '(None)'} + + + {[undefined, 1, 2, 3, 4, 5, 6, 7, 8].map((level) => ( + { + configuration.setInitialExpandLevel(level); + closeInitialExpandLevelDropdown(); + }} + > + {level ?? '(None)'} + + ))} + + +
+ +
+
+ Show Root Aggregation? +
+ + configuration.setShowRootAggregation( + !configuration.showRootAggregation, + ) + } + disabled={true} + /> + +
+ +
+
+ Show Leaf Count? +
+ + configuration.setShowLeafCount(!configuration.showLeafCount) + } + /> +
+ +
+
+ Show Lines? +
+ + configuration.setShowTreeLine(!configuration.showTreeLine) + } + /> + + configuration.setShowHorizontalGridLine( + !configuration.showHorizontalGridLine, + ) + } + /> + + configuration.setShowVerticalGridLine( + !configuration.showVerticalGridLine, + ) + } + /> +
+ +
+
+ Default Number Scale: +
+ + {configuration.numberScale ?? '(None)'} + + + {[ + undefined, + DataCubeNumberScale.PERCENT, + DataCubeNumberScale.BASIS_POINT, + DataCubeNumberScale.THOUSANDS, + DataCubeNumberScale.MILLIONS, + DataCubeNumberScale.BILLIONS, + DataCubeNumberScale.AUTO, + ].map((scale) => ( + { + configuration.setNumberScale(scale); + closeNumberScaleDropdown(); + }} + > + {scale ?? '(None)'} + + ))} + +
+ +
+
+ Show Selection Stats: +
+ + {'(None)'} + + + {[ + DataCubeSelectionStat.SUM, + DataCubeSelectionStat.AVERAGE, + DataCubeSelectionStat.COUNT, + DataCubeSelectionStat.MIN, + DataCubeSelectionStat.MAX, + ].map((operation) => ( + { + // TODO + closeSelectionStatDropdown(); + }} + > + ))} + + +
+ +
+
+ Row Limit: +
+ + value !== undefined && (value === -1 || value > 0) + } + setValue={(value) => panel.setLimit(value ?? -1)} + /> +
+ Truncate result to this many rows at every level. Use -1 for + unlimited. +
+
+ +
+
+ + configuration.setShowWarningForTruncatedResult( + !configuration.showWarningForTruncatedResult, + ) + } + /> +
+ +
+ +
+
+ Default Font: +
+ + {configuration.defaultFontFamily} + + + {[ + DataCubeFont.ARIAL, + DataCubeFont.ROBOTO, + DataCubeFont.ROBOTO_CONDENSED, + ].map((font) => ( + { + configuration.setDefaultFontFamily(font); + closeFontFamilyDropdown(); + }} + > + {font} + + ))} + + {[ + DataCubeFont.GEORGIA, + DataCubeFont.ROBOTO_SERIF, + DataCubeFont.TIMES_NEW_ROMAN, + ].map((font) => ( + { + configuration.setDefaultFontFamily(font); + closeFontFamilyDropdown(); + }} + > + {font} + + ))} + + {[ + DataCubeFont.JERBRAINS_MONO, + DataCubeFont.ROBOTO_MONO, + DataCubeFont.UBUNTU_MONO, + ].map((font) => ( + { + configuration.setDefaultFontFamily(font); + closeFontFamilyDropdown(); + }} + > + {font} + + ))} + + + + {configuration.defaultFontSize} + + + {[ + 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, + 32, 36, 48, 72, + ].map((size) => ( + { + configuration.setDefaultFontSize(size); + closeFontSizeDropdown(); + }} + > + {size} + + ))} + + +
+ + + + + + {[ + DataCubeFontFormatUnderlinedVariant.SOLID, + DataCubeFontFormatUnderlinedVariant.DASHED, + DataCubeFontFormatUnderlinedVariant.DOTTED, + DataCubeFontFormatUnderlinedVariant.DOUBLE, + DataCubeFontFormatUnderlinedVariant.WAVY, + ].map((variant) => ( + { + configuration.setDefaultFontUnderlined(variant); + closeFontFormatUnderlinedVariantDropdown(); + }} + > +
+                  +
+
+ ))} +
+ +
+ +
+ + + +
+
+ +
+
+ Default Colors: +
+
+
+
+
+ Normal +
+
+ Negative +
+
+ Zero +
+
+ Error +
+
+
+
+ Foreground: +
+
+ + configuration.setDefaultForegroundColor(value) + } + /> +
+
+ + configuration.setDefaultForegroundNegativeColor(value) + } + /> +
+
+ + configuration.setDefaultForegroundZeroColor(value) + } + /> +
+
+ + configuration.setDefaultForegroundErrorColor(value) + } + /> +
+
+
+
+ Background: +
+
+ + configuration.setDefaultBackgroundColor(value) + } + /> +
+
+ + configuration.setDefaultBackgroundNegativeColor(value) + } + /> +
+
+ + configuration.setDefaultBackgroundZeroColor(value) + } + /> +
+
+ + configuration.setDefaultBackgroundErrorColor(value) + } + /> +
+
+
+
+ +
+
+ Hightlight Rows: +
+ + configuration.setAlternateRows(!configuration.alternateRows) + } + /> + value !== undefined && value > 0} + value={configuration.alternateRowsCount} + setValue={(value) => + configuration.setAlternateRowsCount(value ?? 1) + } + /> +
{`row(s)`}
+ + configuration.setDefaultBackgroundErrorColor(value) + } + /> +
+
); diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorHPivotsPanel.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorHPivotsPanel.tsx index e39df680d2..6350180d5c 100644 --- a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorHPivotsPanel.tsx +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorHPivotsPanel.tsx @@ -21,12 +21,12 @@ import { useEffect } from 'react'; export const DataCubeEditorHPivotsPanel = observer(() => { const replStore = useREPLStore(); - const panel = replStore.dataCubeState.editor.sortsPanel; + const panel = replStore.dataCube.editor.sortsPanel; useEffect(() => {}, [panel]); // TODO: @akphi - remove this dummy useEffect return ( -
+
diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorShared.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorShared.tsx new file mode 100644 index 0000000000..73ba560fec --- /dev/null +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorShared.tsx @@ -0,0 +1,448 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + BasePopover, + Checkbox, + cn, + DataCubeIcon, + DropdownMenu, + DropdownMenuItem, + HexAlphaColorPicker, + HexColorInput, + TailwindCSSPalette, + type CheckboxProps, + type DropdownMenuItemProps, + type DropdownMenuProps, + type TailwindCSSColorScale, + type TailwindCSSColorScaleKey, +} from '@finos/legend-art'; +import { toNumber } from '@finos/legend-shared'; +import React, { forwardRef, useEffect, useState } from 'react'; + +export function WIP_Badge() { + return ( +
+ WIP +
+ ); +} + +export const DataCubeEditorNumberInput = forwardRef( + function DataCubeEditorBaseNumberInput( + props: React.InputHTMLAttributes & { + min?: number | undefined; + max?: number | undefined; + step?: number | undefined; + isValid?: (value: number | undefined) => boolean; + isDecimal?: boolean | undefined; + defaultValue?: number | undefined; + value: number | undefined; + setValue: (value?: number | undefined) => void; + className?: string | undefined; + }, + ref: React.Ref, + ) { + const { + min, + max, + step, + value, + setValue, + isValid, + isDecimal, + defaultValue, + disabled, + className, + } = props; + const [inputValue, setInputValue] = useState(value ?? ''); + + useEffect(() => { + setInputValue(value ?? ''); + }, [value]); + + return ( + { + const newInputValue = event.target.value; + setInputValue(newInputValue); + const numericValue = isDecimal + ? toNumber(newInputValue) + : parseInt(newInputValue, 10); + if ( + isNaN(numericValue) || + // NOTE: `toNumber` parses empty string as `0`, which is not what we want, so we want to do the explicit check here + !newInputValue || + (isValid !== undefined + ? !isValid(numericValue) + : (min !== undefined && numericValue < min) || + (max !== undefined && numericValue > max)) + ) { + return; + } + setValue(numericValue); + }} + onBlur={() => { + const numericValue = isDecimal + ? toNumber(inputValue) + : parseInt(inputValue.toString(), 10); + if ( + isNaN(numericValue) || + // NOTE: `toNumber` parses empty string as `0`, which is not what we want, so we want to do the explicit check here + !inputValue || + (isValid !== undefined + ? !isValid(numericValue) + : (min !== undefined && numericValue < min) || + (max !== undefined && numericValue > max)) + ) { + setValue(defaultValue); + setInputValue(defaultValue ?? ''); + } else { + setInputValue(value ?? ''); + } + }} + /> + ); + }, +); + +export function DataCubeEditorTextInput( + props: React.InputHTMLAttributes, +) { + const { className, ...otherProps } = props; + return ( + + ); +} + +export function DataCubeEditorCheckbox( + props: CheckboxProps & { + label?: React.ReactNode; + onChange: () => void; + }, +) { + const { label, className, onChange, disabled, ...otherProps } = props; + return ( + <> + } + checkedIcon={} + disableRipple={true} + classes={{ + root: cn( + // Make sure the icons used have consistent stroke width with other components' borders + // and that the left side is offseted to align well with other components + 'p-0 text-neutral-600 [&_*]:stroke-[1.5px] -ml-[1px]', + className, + ), + checked: 'data-cube-editor-checkbox--checked', + disabled: 'data-cube-editor-checkbox--disabled', + }} + onChange={onChange} + disabled={disabled} + {...otherProps} + /> + {Boolean(label) && ( + + )} + + ); +} + +export function DataCubeEditorDropdownMenuTrigger( + props: React.ButtonHTMLAttributes, +) { + const { children, className, ...otherProps } = props; + return ( + + ); +} + +export function DataCubeEditorDropdownMenu(props: DropdownMenuProps) { + const { className, ...otherProps } = props; + return ( + + ); +} + +export function DataCubeEditorDropdownMenuItem(props: DropdownMenuItemProps) { + const { className, ...otherProps } = props; + return ( + + ); +} + +export function DataCubeEditorDropdownMenuItemSeparator() { + return
; +} + +function DataCubeEditorColorPicker(props: { + color: string; + onChange: (value: string) => void; + onClose: () => void; + defaultColor?: string | undefined; +}) { + const { onChange, onClose, defaultColor } = props; + const [color, setColor] = useState(props.color); + + return ( +
+
+ +
+
+ {( + [ + 'slate', + 'neutral', + 'red', + 'orange', + 'amber', + 'yellow', + 'lime', + 'green', + 'emerald', + 'teal', + 'sky', + 'blue', + 'indigo', + 'violet', + 'purple', + 'fuchsia', + 'pink', + 'rose', + ] as TailwindCSSColorScaleKey[] + ).map((scale) => ( +
+ {( + [ + 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950, + ] as (keyof TailwindCSSColorScale)[] + ).map((level) => ( +
+ ))} +
+
+
+ {[ + // Colors from Better Colors - https://clrs.cc/ + '#000000', + '#111111', + '#AAAAAA', + '#DDDDDD', + '#FFFFFF', + '#FF4136', + '#FF851B', + '#FFDC00', + '#01FF70', + '#2ECC40', + '#3D9970', + '#39CCCC', + '#7FDBFF', + '#0074D9', + '#001F3F', + '#B10DC9', + '#F012BE', + '#85144B', + ].map((_color) => ( +
+
+ ))} +
+
+
+
+
+
+ +
+
+ {defaultColor !== undefined && ( + + )} + + +
+
+
+ ); +} + +export function DataCubeEditorColorPickerButton(props: { + color: string; + onChange: (value: string) => void; + className?: string | undefined; + disabled?: boolean | undefined; + defaultColor?: string | undefined; +}) { + const { color, onChange, className, disabled, defaultColor } = props; + const [anchorEl, setAnchorEl] = useState(null); + return ( + <> + + {Boolean(anchorEl) && ( + setAnchorEl(null)} + > + setAnchorEl(null)} + defaultColor={defaultColor} + /> + + )} + + ); +} diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorSortsPanel.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorSortsPanel.tsx index 073852b44d..9b17dafb8a 100644 --- a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorSortsPanel.tsx +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorSortsPanel.tsx @@ -15,16 +15,112 @@ */ import { observer } from 'mobx-react-lite'; -import { DataCubeIcon } from '@finos/legend-art'; +import { + DataCubeIcon, + useDropdownMenu, + DropdownMenuItem, + DropdownMenu, +} from '@finos/legend-art'; import { useREPLStore } from '../../REPLStoreProvider.js'; import { DataCubeEditorColumnsSelector } from './DataCubeEditorColumnsSelector.js'; +import type { DataCubeEditorColumnsSelectorState } from '../../../stores/dataCube/editor/DataCubeEditorColumnsSelectorState.js'; +import type { DataCubeEditorSortColumnState } from '../../../stores/dataCube/editor/DataCubeEditorSortsPanelState.js'; +import { DataCubeQuerySnapshotSortOperation } from '../../../stores/dataCube/core/DataCubeQuerySnapshot.js'; +import { IllegalStateError } from '@finos/legend-shared'; +import { WIP_Badge } from './DataCubeEditorShared.js'; + +function getSortDirectionLabel(operation: DataCubeQuerySnapshotSortOperation) { + switch (operation) { + case DataCubeQuerySnapshotSortOperation.ASCENDING: + return 'Ascending'; + case DataCubeQuerySnapshotSortOperation.DESCENDING: + return 'Descending'; + default: + throw new IllegalStateError(`Unsupported sort operation '${operation}'`); + } +} + +const SortDirectionDropdown = observer( + (props: { + selector: DataCubeEditorColumnsSelectorState; + column: DataCubeEditorSortColumnState; + }) => { + const { column } = props; + const [openMenu, closeMenu, menuProps] = useDropdownMenu(); + + return ( +
+
+ {getSortDirectionLabel(column.operation)} +
+ + + { + column.setOperation(DataCubeQuerySnapshotSortOperation.ASCENDING); + closeMenu(); + }} + > + Ascending + + + {`Ascending (abs)`} + + + { + column.setOperation( + DataCubeQuerySnapshotSortOperation.DESCENDING, + ); + closeMenu(); + }} + > + Descending + + + {`Descending (abs)`} + + + +
+ ); + }, +); export const DataCubeEditorSortsPanel = observer(() => { const replStore = useREPLStore(); - const panel = replStore.dataCubeState.editor.sortsPanel; + const panel = replStore.dataCube.editor.sortsPanel; return ( -
+
@@ -34,7 +130,10 @@ export const DataCubeEditorSortsPanel = observer(() => {
- + } + />
); diff --git a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorVPivotsPanel.tsx b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorVPivotsPanel.tsx index 778a0aab07..496aab524d 100644 --- a/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorVPivotsPanel.tsx +++ b/packages/legend-application-repl/src/components/dataCube/editor/DataCubeEditorVPivotsPanel.tsx @@ -21,12 +21,12 @@ import { useEffect } from 'react'; export const DataCubeEditorVPivotsPanel = observer(() => { const replStore = useREPLStore(); - const panel = replStore.dataCubeState.editor.sortsPanel; + const panel = replStore.dataCube.editor.sortsPanel; useEffect(() => {}, [panel]); // TODO: @akphi - remove this dummy useEffect return ( -
+
diff --git a/packages/legend-application-repl/src/components/dataCube/grid/DataCubeGrid.tsx b/packages/legend-application-repl/src/components/dataCube/grid/DataCubeGrid.tsx index 8500efb449..c334b3e072 100644 --- a/packages/legend-application-repl/src/components/dataCube/grid/DataCubeGrid.tsx +++ b/packages/legend-application-repl/src/components/dataCube/grid/DataCubeGrid.tsx @@ -19,6 +19,7 @@ import { LicenseManager } from '@ag-grid-enterprise/core'; import { ClientSideRowModelModule } from '@ag-grid-community/client-side-row-model'; import { ServerSideRowModelModule } from '@ag-grid-enterprise/server-side-row-model'; import { RowGroupingModule } from '@ag-grid-enterprise/row-grouping'; +import { ClipboardModule } from '@ag-grid-enterprise/clipboard'; import { MenuModule } from '@ag-grid-enterprise/menu'; import { AgGridReact } from '@ag-grid-community/react'; import { useEffect, useState } from 'react'; @@ -26,9 +27,11 @@ import { useREPLStore } from '../../REPLStoreProvider.js'; import { DataCubeIcon, Switch, cn } from '@finos/legend-art'; import { INTERNAL__GRID_CLIENT_HEADER_HEIGHT, - INTERNAL__GRID_CLIENT_ROW_BUFFER, INTERNAL__GRID_CLIENT_ROW_HEIGHT, } from '../../../stores/dataCube/grid/DataCubeGridClientEngine.js'; +import { RangeSelectionModule } from '@ag-grid-enterprise/range-selection'; +import { buildGridMenu } from './menu/DataCubeGridMenu.js'; +import { DEFAULT_ROW_BUFFER } from '../../../stores/dataCube/core/DataCubeQueryEngine.js'; // NOTE: This is a workaround to prevent ag-grid license key check from flooding the console screen // with its stack trace in Chrome. @@ -42,9 +45,8 @@ console.error = (message?: unknown, ...agrs: unknown[]): void => { export const DataCubeGrid = observer(() => { const replStore = useREPLStore(); - const dataCubeState = replStore.dataCubeState; - const grid = dataCubeState.grid; - + const dataCube = replStore.dataCube; + const grid = dataCube.grid; const [scrollHintText, setScrollHintText] = useState(''); useEffect(() => { @@ -57,44 +59,46 @@ export const DataCubeGrid = observer(() => {
{ - grid.configureClient(params.api); - // restore original error logging - console.error = __INTERNAL__original_console_error; // eslint-disable-line no-console - }} // -------------------------------------- ROW GROUPING -------------------------------------- rowGroupPanelShow="always" suppressScrollOnNewData={true} groupDisplayType="custom" // keeps the column set stable even when row grouping is used suppressRowGroupHidesColumns={true} // keeps the column set stable even when row grouping is used + // Keeps the columns stable even when aggregation is used + suppressAggFuncInHeader={true} // -------------------------------------- PIVOT -------------------------------------- // pivotPanelShow="always" // pivotMode={true} - // -------------------------------------- SERVER SIDE ROW MODEL -------------------------------------- - rowModelType="serverSide" - serverSideDatasource={grid.clientDataSource} - // -------------------------------------- GENERIC -------------------------------------- - suppressBrowserResizeObserver={true} - animateRows={false} // improve performance - // NOTE: since we shrink the spacing, more rows can be shown, as such, setting higher row - // buffer will improve scrolling performance, but compromise initial load and various - // actions performance - rowBuffer={INTERNAL__GRID_CLIENT_ROW_BUFFER} - rowHeight={INTERNAL__GRID_CLIENT_ROW_HEIGHT} - headerHeight={INTERNAL__GRID_CLIENT_HEADER_HEIGHT} + // -------------------------------------- SORT -------------------------------------- // Force multi-sorting since this is what the query supports anyway alwaysMultiSort={true} - // Keeps the columns stable even when aggregation is used - suppressAggFuncInHeader={true} + // -------------------------------------- DISPLAY & INTERACTION -------------------------------------- + className="ag-theme-balham" + rowHeight={INTERNAL__GRID_CLIENT_ROW_HEIGHT} + headerHeight={INTERNAL__GRID_CLIENT_HEADER_HEIGHT} + suppressBrowserResizeObserver={true} + reactiveCustomComponents={true} // TODO: remove on v32 as this would be default to `true` then + noRowsOverlayComponent={() => ( +
+
+ +
+ 0 rows +
+ )} + loadingOverlayComponent={() => ( +
+
+ +
+ Loading... +
+ )} + preventDefaultOnContextMenu={true} + columnMenu="new" // ensure context menu works on header + getContextMenuItems={buildGridMenu} + getMainMenuItems={buildGridMenu} + enableRangeSelection={true} // Show cursor position when scrolling onBodyScroll={(event) => { const rowCount = event.api.getDisplayedRowCount(); @@ -108,8 +112,37 @@ export const DataCubeGrid = observer(() => { Math.floor(range.bottom / INTERNAL__GRID_CLIENT_ROW_HEIGHT), ); setScrollHintText(`${start}-${end}/${rowCount}`); + event.api.hidePopupMenu(); // hide context-menu while scrolling }} onBodyScrollEnd={() => setScrollHintText('')} + // -------------------------------------- SERVER SIDE ROW MODEL -------------------------------------- + rowModelType="serverSide" + serverSideDatasource={grid.clientDataSource} + // -------------------------------------- PERFORMANCE -------------------------------------- + // NOTE: since we shrink the spacing, more rows can be shown, as such, setting higher row + // buffer will improve scrolling performance, but compromise initial load and various + // actions performance + rowBuffer={DEFAULT_ROW_BUFFER} + animateRows={false} // improve performance + // -------------------------------------- SETUP -------------------------------------- + modules={[ + // community + ClientSideRowModelModule, + // enterprise + ServerSideRowModelModule, + RowGroupingModule, + MenuModule, + ClipboardModule, + RangeSelectionModule, + ]} + onGridReady={(params): void => { + grid.configureClient(params.api); + // restore original error logging + console.error = __INTERNAL__original_console_error; // eslint-disable-line no-console + }} + context={{ + dataCube, + }} />
diff --git a/packages/legend-application-repl/src/components/dataCube/grid/DataCubeGridShared.tsx b/packages/legend-application-repl/src/components/dataCube/grid/DataCubeGridShared.tsx new file mode 100644 index 0000000000..2e5c4a06ba --- /dev/null +++ b/packages/legend-application-repl/src/components/dataCube/grid/DataCubeGridShared.tsx @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + useGridMenuItem, + type CustomMenuItemProps, +} from '@ag-grid-community/react'; +import { WIP_Badge } from '../editor/DataCubeEditorShared.js'; + +export function WIP_GridMenuItem({ name, subMenu }: CustomMenuItemProps) { + useGridMenuItem({ + configureDefaults: () => true, + }); + + return ( +
+ + + {name} + + + + + {subMenu && } + +
+ ); +} diff --git a/packages/legend-application-repl/src/components/dataCube/grid/menu/DataCubeGridMenu.tsx b/packages/legend-application-repl/src/components/dataCube/grid/menu/DataCubeGridMenu.tsx new file mode 100644 index 0000000000..bdcdf24c69 --- /dev/null +++ b/packages/legend-application-repl/src/components/dataCube/grid/menu/DataCubeGridMenu.tsx @@ -0,0 +1,394 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { + GetContextMenuItemsParams, + GetMainMenuItemsParams, + MenuItemDef, +} from '@ag-grid-community/core'; +import type { DataCubeState } from '../../../../stores/dataCube/DataCubeState.js'; +import { buildGridSortsMenu } from './DataCubeGridSortsMenu.js'; +import { WIP_GridMenuItem } from '../DataCubeGridShared.js'; + +export function buildGridMenu( + params: + | GetContextMenuItemsParams + | GetMainMenuItemsParams, +): (string | MenuItemDef)[] { + const context = params.context; + const dataCube = context.dataCube; + const editor = dataCube.editor; + const column = params.column ?? undefined; + const value: unknown = 'value' in params ? params.value : undefined; + + const result: (string | MenuItemDef)[] = [ + { + name: 'Export', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + subMenu: [ + { + name: 'HTML', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'Plain Text', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'PDF', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'Excel', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'CSV', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + 'separator', + { + name: 'DataCube Specification', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + ], + }, + { + name: 'Email', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + subMenu: [ + { + name: 'HTML', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'Plain Text', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + 'separator', + { + name: 'HTML Attachment', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'Plain Text', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'PDF Attachment', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'Excel Attachment', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'CSV Attachment', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'DataCube Specification Attachment', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + ], + }, + { + name: 'Copy', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + subMenu: [ + { + name: 'Plain Text', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'Selected Row(s) as Plain Text', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'Selected Column as Plain Text', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + ], + }, + 'separator', + buildGridSortsMenu(editor, column, value), + { + name: 'Filter', + menuItem: WIP_GridMenuItem, + disabled: true, + cssClasses: ['!opacity-100'], + subMenu: [ + ...(column && value + ? [ + { + name: `Add Filter: ${column.getColId()} = {value}`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: `More Filters on ${column.getColId()}...`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + subMenu: [], // TODO + }, + 'separator', + ] + : []), + { + name: 'Filters...', + }, + { + name: 'Clear All Filters', + }, + ], + }, + { + name: 'Pivot', + menuItem: WIP_GridMenuItem, + disabled: true, + cssClasses: ['!opacity-100'], + subMenu: [ + ...(column + ? [ + { + name: `VPivot on ${column.getColId()}`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: `Add VPivot on ${column.getColId()}`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: `Remove VPivot on ${column.getColId()}`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + 'separator', + ] + : []), + { + name: `Clear All VPivots`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + ], + }, + { + name: 'Heatmap', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: !column, + subMenu: column + ? [ + { + name: `Add to ${column.getColId()}`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: `Remove from ${column.getColId()}`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + ] + : [], + }, + { + name: 'Extended Columns', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + subMenu: [ + { + name: `Add New Column...`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: `Edit {column}`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: `Remove {column}`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + ], + }, + { + name: 'Custom Groupings', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + subMenu: [ + { + name: `Add New Grouping...`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: `Edit {column}`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: `Remove {column}`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + ], + }, + 'separator', + { + name: 'Resize', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + subMenu: [ + { + name: `Size to Fit Content`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: `Autosize`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + 'separator', + { + name: `Autosize All Columns`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + ], + }, + { + name: 'Pin', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + subMenu: [ + { + name: `Pin Left`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: `Pin Right`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + 'separator', + { + name: `Remove Pinning`, + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + ], + }, + { + name: 'Hide', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + 'separator', + { + name: 'Show Plot...', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'Show TreeMap...', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + 'separator', + { + name: 'Properties...', + disabled: editor.isPanelOpen, + action: () => { + if (!editor.isPanelOpen) { + editor.openPanel(); + } + }, + }, + ]; + return result; +} diff --git a/packages/legend-application-repl/src/components/dataCube/grid/menu/DataCubeGridSortsMenu.tsx b/packages/legend-application-repl/src/components/dataCube/grid/menu/DataCubeGridSortsMenu.tsx new file mode 100644 index 0000000000..113ccfc69c --- /dev/null +++ b/packages/legend-application-repl/src/components/dataCube/grid/menu/DataCubeGridSortsMenu.tsx @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Column, MenuItemDef } from '@ag-grid-community/core'; +import type { DataCubeEditorState } from '../../../../stores/dataCube/editor/DataCubeEditorState.js'; +import { DataCubeQuerySnapshotSortOperation } from '../../../../stores/dataCube/core/DataCubeQuerySnapshot.js'; +import { WIP_GridMenuItem } from '../DataCubeGridShared.js'; + +export function buildGridSortsMenu( + editor: DataCubeEditorState, + column: Column | undefined, + value: unknown, +): MenuItemDef { + if (!column) { + return { + name: 'Sort', + disabled: true, + subMenu: [], + }; + } + + return { + name: 'Sort', + subMenu: [ + { + name: 'Ascending', + disabled: !editor.sortsPanel.getActionableSortColumn( + column.getColId(), + DataCubeQuerySnapshotSortOperation.ASCENDING, + ), + action: () => + editor.sortsPanel.sortByColumn( + column.getColId(), + DataCubeQuerySnapshotSortOperation.ASCENDING, + ), + }, + { + name: 'Ascending Absolute', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'Descending', + disabled: !editor.sortsPanel.getActionableSortColumn( + column.getColId(), + DataCubeQuerySnapshotSortOperation.DESCENDING, + ), + action: () => + editor.sortsPanel.sortByColumn( + column.getColId(), + DataCubeQuerySnapshotSortOperation.DESCENDING, + ), + }, + { + name: 'Descending Absolute', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + 'separator', + { + name: 'Add Ascending', + disabled: !editor.sortsPanel.getActionableSortColumn( + column.getColId(), + DataCubeQuerySnapshotSortOperation.ASCENDING, + ), + action: () => + editor.sortsPanel.addSortByColumn( + column.getColId(), + DataCubeQuerySnapshotSortOperation.ASCENDING, + ), + }, + { + name: 'Add Ascending Absolute', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + { + name: 'Add Descending', + disabled: !editor.sortsPanel.getActionableSortColumn( + column.getColId(), + DataCubeQuerySnapshotSortOperation.DESCENDING, + ), + action: () => + editor.sortsPanel.addSortByColumn( + column.getColId(), + DataCubeQuerySnapshotSortOperation.DESCENDING, + ), + }, + { + name: 'Add Descending Absolute', + menuItem: WIP_GridMenuItem, + cssClasses: ['!opacity-100'], + disabled: true, + }, + 'separator', + { + name: 'Clear All Sorts', + disabled: + editor.sortsPanel.columnsSelector.selectedColumns.length === 0, + action: () => editor.sortsPanel.clearAllSorts(), + }, + ], + }; +} diff --git a/packages/legend-application-repl/src/server/models/DataCubeQuery.ts b/packages/legend-application-repl/src/server/models/DataCubeQuery.ts index 5cb08b1bd6..09924c5b0f 100644 --- a/packages/legend-application-repl/src/server/models/DataCubeQuery.ts +++ b/packages/legend-application-repl/src/server/models/DataCubeQuery.ts @@ -97,21 +97,17 @@ function serializeQuerySource( ); } -export class DataCubeConfiguration { - // TODO -} - export class DataCubeQuery { name!: string; query!: string; partialQuery!: string; source!: DataCubeQuerySource; - configuration!: DataCubeConfiguration; + configuration?: PlainObject | undefined; constructor( name: string, query: string, - configuration: DataCubeConfiguration, + configuration?: PlainObject | undefined, ) { this.name = name; this.query = query; diff --git a/packages/legend-application-repl/src/stores/dataCube/DataCubeState.ts b/packages/legend-application-repl/src/stores/dataCube/DataCubeState.ts index f7d39711c8..a7b0c42943 100644 --- a/packages/legend-application-repl/src/stores/dataCube/DataCubeState.ts +++ b/packages/legend-application-repl/src/stores/dataCube/DataCubeState.ts @@ -17,12 +17,28 @@ import type { REPLStore } from './REPLStore.js'; import { DataCubeGridState } from './grid/DataCubeGridState.js'; import { DataCubeEditorState } from './editor/DataCubeEditorState.js'; -import { ActionState, assertErrorThrown } from '@finos/legend-shared'; +import { assertErrorThrown, uuid } from '@finos/legend-shared'; import { DataCubeEngine } from './core/DataCubeEngine.js'; import { DataCubeQuerySnapshotManager } from './core/DataCubeQuerySnapshotManager.js'; import type { LegendREPLApplicationStore } from '../LegendREPLBaseStore.js'; import { DataCubeCoreState } from './core/DataCubeCoreState.js'; import { validateAndBuildQuerySnapshot } from './core/DataCubeQuerySnapshotBuilder.js'; +import { action, makeObservable, observable } from 'mobx'; + +export class DataCubeTask { + uuid = uuid(); + name: string; + startTime = Date.now(); + endTime?: number | undefined; + + constructor(name: string) { + this.name = name; + } + + end(): void { + this.endTime = Date.now(); + } +} export class DataCubeState { readonly replStore: REPLStore; @@ -34,9 +50,15 @@ export class DataCubeState { readonly grid: DataCubeGridState; readonly editor: DataCubeEditorState; - readonly initState = ActionState.create(); + readonly runningTaskes = new Map(); constructor(replStore: REPLStore) { + makeObservable(this, { + runningTaskes: observable, + newTask: action, + endTask: action, + }); + this.replStore = replStore; this.application = replStore.applicationStore; this.engine = new DataCubeEngine(this.replStore.client); @@ -48,8 +70,20 @@ export class DataCubeState { this.editor = new DataCubeEditorState(this); } + newTask(name: string): DataCubeTask { + const task = new DataCubeTask(name); + this.runningTaskes.set(task.uuid, task); + return task; + } + + endTask(task: DataCubeTask): DataCubeTask { + task.end(); + this.runningTaskes.delete(task.uuid); + return task; + } + async initialize(): Promise { - this.initState.inProgress(); + const task = this.newTask('Initializing'); try { await Promise.all( [this.core, this.editor, this.grid].map(async (state) => { @@ -65,11 +99,11 @@ export class DataCubeState { ); initialSnapshot.timestamp = result.timestamp; this.snapshotManager.broadcastSnapshot(initialSnapshot); - this.initState.complete(); } catch (error: unknown) { assertErrorThrown(error); this.application.notificationService.notifyError(error); - this.initState.fail(); + } finally { + this.endTask(task); } } } diff --git a/packages/legend-application-repl/src/stores/dataCube/REPLStore.ts b/packages/legend-application-repl/src/stores/dataCube/REPLStore.ts index ae1fd4d73d..b03f21bb87 100644 --- a/packages/legend-application-repl/src/stores/dataCube/REPLStore.ts +++ b/packages/legend-application-repl/src/stores/dataCube/REPLStore.ts @@ -25,11 +25,11 @@ export class REPLStore { readonly client: REPLServerClient; // TODO: when we support multi-view, we would need to support multiple states - dataCubeState!: DataCubeState; + dataCube!: DataCubeState; constructor(applicationStore: LegendREPLApplicationStore) { makeObservable(this, { - dataCubeState: observable, + dataCube: observable, }); this.applicationStore = applicationStore; this.client = new REPLServerClient( @@ -40,6 +40,6 @@ export class REPLStore { : this.applicationStore.config.replUrl, }), ); - this.dataCubeState = new DataCubeState(this); + this.dataCube = new DataCubeState(this); } } diff --git a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeConfiguration.ts b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeConfiguration.ts new file mode 100644 index 0000000000..bbcc6327d9 --- /dev/null +++ b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeConfiguration.ts @@ -0,0 +1,200 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TailwindCSSPalette } from '@finos/legend-art'; +import { + DataCubeColumnKind, + DataCubeFont, + DataCubeFontTextAlignment, + DEFAULT_ROW_BUFFER, + DEFAULT_BACKGROUND_COLOR, + DEFAULT_ERROR_FOREGROUND_COLOR, + DEFAULT_FOREGROUND_COLOR, + DEFAULT_NEGATIVE_FOREGROUND_COLOR, + DEFAULT_ROW_HIGHLIGHT_BACKGROUND_COLOR, + DEFAULT_ZERO_FOREGROUND_COLOR, + type DataCubeFontFormatUnderlinedVariant, + type DataCubeNumberScale, + type DataCubeSelectionStat, + type DataCubeColumnPinPlacement, +} from './DataCubeQueryEngine.js'; +import { SerializationFactory, usingModelSchema } from '@finos/legend-shared'; +import { createModelSchema, list, optional, primitive } from 'serializr'; + +export class DataCubeColumnConfiguration { + name: string; + type: string; + + kind: DataCubeColumnKind = DataCubeColumnKind.DIMENSION; + displayName?: string | undefined; + + decimals?: number | undefined; + displayCommas = false; + negativeNumberInParens = false; + numberScale?: DataCubeNumberScale | undefined; + + hPivotSortFunction?: string | undefined; + + fontFamily = DataCubeFont.ROBOTO; + fontSize = 8; + fontBold = false; + fontItalic = false; + fontUnderlined?: DataCubeFontFormatUnderlinedVariant | undefined = undefined; + fontStrikethrough = false; + textAlign = DataCubeFontTextAlignment.LEFT; + foregroundColor = TailwindCSSPalette.black; + foregroundNegativeColor = DEFAULT_NEGATIVE_FOREGROUND_COLOR; + foregroundZeroColor = DEFAULT_ZERO_FOREGROUND_COLOR; + foregroundErrorColor = DEFAULT_ERROR_FOREGROUND_COLOR; + backgroundColor = DEFAULT_BACKGROUND_COLOR; + backgroundNegativeColor = DEFAULT_BACKGROUND_COLOR; + backgroundZeroColor = DEFAULT_BACKGROUND_COLOR; + backgroundErrorColor = DEFAULT_BACKGROUND_COLOR; + + blur = false; + hideFromView = false; + + fixedWidth?: number | undefined; + minWidth?: number | undefined; + maxWidth?: number | undefined; + pinned?: DataCubeColumnPinPlacement | undefined; + displayAsLink = false; + + constructor(name: string, type: string) { + this.name = name; + this.type = type; + } + + static readonly serialization = new SerializationFactory( + createModelSchema(DataCubeColumnConfiguration, { + backgroundColor: primitive(), + backgroundErrorColor: primitive(), + backgroundNegativeColor: primitive(), + backgroundZeroColor: primitive(), + decimals: optional(primitive()), + displayAsLink: primitive(), + displayCommas: primitive(), + displayName: optional(primitive()), + fixedWidth: optional(primitive()), + foregroundColor: primitive(), + foregroundErrorColor: primitive(), + foregroundNegativeColor: primitive(), + foregroundZeroColor: primitive(), + fontBold: primitive(), + fontFamily: primitive(), + fontItalic: primitive(), + fontSize: primitive(), + fontStrikethrough: primitive(), + fontUnderlined: optional(primitive()), + hPivotSortFunction: optional(primitive()), + kind: primitive(), + maxWidth: optional(primitive()), + minWidth: optional(primitive()), + name: primitive(), + negativeNumberInParens: primitive(), + numberScale: optional(primitive()), + pinned: optional(primitive()), + textAlign: primitive(), + type: primitive(), + }), + ); +} + +export class DataCubeConfiguration { + description?: string | undefined; + columns: DataCubeColumnConfiguration[] = []; + + showTreeLine = true; + showHorizontalGridLine = false; + showVerticalGridLine = false; + defaultFontFamily = DataCubeFont.ROBOTO; + defaultFontSize = 12; + defaultFontBold = false; + defaultFontItalic = false; + defaultFontUnderlined?: DataCubeFontFormatUnderlinedVariant | undefined = + undefined; + defaultFontStrikethrough = false; + defaultTextAlign = DataCubeFontTextAlignment.LEFT; + defaultForegroundColor = DEFAULT_FOREGROUND_COLOR; + defaultForegroundNegativeColor = DEFAULT_NEGATIVE_FOREGROUND_COLOR; + defaultForegroundZeroColor = DEFAULT_ZERO_FOREGROUND_COLOR; + defaultForegroundErrorColor = DEFAULT_ERROR_FOREGROUND_COLOR; + defaultBackgroundColor = DEFAULT_BACKGROUND_COLOR; + defaultBackgroundNegativeColor = DEFAULT_BACKGROUND_COLOR; + defaultBackgroundZeroColor = DEFAULT_BACKGROUND_COLOR; + defaultBackgroundErrorColor = DEFAULT_BACKGROUND_COLOR; + alternateRows = true; + alternateRowsColor = DEFAULT_ROW_HIGHLIGHT_BACKGROUND_COLOR; + alternateRowsCount = 1; + + // manualRefresh: boolean; + numberScale?: DataCubeNumberScale | undefined; + selectionStats: DataCubeSelectionStat[] = []; + + showWarningForTruncatedResult = true; + + // aggregation + initialExpandLevel?: number | undefined; + showRootAggregation = false; + showLeafCount = true; + addPivotTotalColumn = true; + addPivotTotalColumnOnLeft = true; + treeGroupSortFunction?: string | undefined; + + // advanced + rowBuffer = DEFAULT_ROW_BUFFER; + + static readonly serialization = new SerializationFactory( + createModelSchema(DataCubeConfiguration, { + addPivotTotalColumn: primitive(), + addPivotTotalColumnOnLeft: primitive(), + alternateRows: primitive(), + alternateRowsColor: primitive(), + alternateRowsCount: primitive(), + columns: list( + usingModelSchema(DataCubeColumnConfiguration.serialization.schema), + ), + defaultBackgroundColor: primitive(), + defaultBackgroundErrorColor: primitive(), + defaultBackgroundNegativeColor: primitive(), + defaultBackgroundZeroColor: primitive(), + defaultFontBold: primitive(), + defaultFontFamily: primitive(), + defaultFontItalic: primitive(), + defaultFontSize: primitive(), + defaultFontStrikethrough: primitive(), + defaultFontUnderlined: optional(primitive()), + defaultForegroundColor: primitive(), + defaultForegroundErrorColor: primitive(), + defaultForegroundNegativeColor: primitive(), + defaultForegroundZeroColor: primitive(), + defaultTextAlign: primitive(), + description: optional(primitive()), + initialExpandLevel: optional(primitive()), + + numberScale: optional(primitive()), + rowBuffer: primitive(), + selectionStats: list(primitive()), + showHorizontalGridLine: primitive(), + showLeafCount: primitive(), + showRootAggregation: primitive(), + showTreeLine: primitive(), + showVerticalGridLine: primitive(), + showWarningForTruncatedResult: primitive(), + treeGroupSortFunction: optional(primitive()), + }), + ); +} diff --git a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeConfigurationBuilder.ts b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeConfigurationBuilder.ts new file mode 100644 index 0000000000..0469265056 --- /dev/null +++ b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeConfigurationBuilder.ts @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PRIMITIVE_TYPE } from '@finos/legend-graph'; +import { + DataCubeColumnConfiguration, + DataCubeConfiguration, +} from './DataCubeConfiguration.js'; +import { DataCubeColumnKind } from './DataCubeQueryEngine.js'; + +export function buildDefaultColumnConfiguration(column: { + name: string; + type: string; +}): DataCubeColumnConfiguration { + const { name, type } = column; + const config = new DataCubeColumnConfiguration(name, type); + switch (type) { + case PRIMITIVE_TYPE.NUMBER: + case PRIMITIVE_TYPE.INTEGER: + case PRIMITIVE_TYPE.DECIMAL: + case PRIMITIVE_TYPE.FLOAT: { + config.kind = DataCubeColumnKind.MEASURE; + config.decimals = type === PRIMITIVE_TYPE.INTEGER ? 0 : 2; + config.displayCommas = true; + config.negativeNumberInParens = true; + break; + } + default: { + break; + } + } + return config; +} + +export function buildDefaultConfiguration( + columns: { name: string; type: string }[], +): DataCubeConfiguration { + const configuration = new DataCubeConfiguration(); + configuration.columns = columns.map((column) => + buildDefaultColumnConfiguration(column), + ); + return configuration; +} diff --git a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeCoreState.ts b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeCoreState.ts index e7b0abfb7c..bec83196c3 100644 --- a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeCoreState.ts +++ b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeCoreState.ts @@ -18,13 +18,13 @@ import { action, makeObservable, observable } from 'mobx'; import type { DataCubeState } from '../DataCubeState.js'; import { DataCubeQuerySnapshotSubscriber } from './DataCubeQuerySnapshotSubscriber.js'; import type { DataCubeQuerySnapshot } from './DataCubeQuerySnapshot.js'; -import { DATA_CUBE_DEFAULT_REPORT_NAME } from '../DataCubeDefaultConfig.js'; import type { DataCubeQuery } from '../../../server/models/DataCubeQuery.js'; import { formatDate } from '@finos/legend-shared'; +import { DEFAULT_REPORT_NAME } from './DataCubeQueryEngine.js'; export class DataCubeCoreState extends DataCubeQuerySnapshotSubscriber { baseQuery!: DataCubeQuery; - name = DATA_CUBE_DEFAULT_REPORT_NAME; + name = DEFAULT_REPORT_NAME; private startTime?: number | undefined; constructor(dataCube: DataCubeState) { @@ -47,7 +47,7 @@ export class DataCubeCoreState extends DataCubeQuerySnapshotSubscriber { this.startTime = snapshot.timestamp; } this.application.layoutService.setWindowTitle( - `\u25A6 ${data.name}${this.startTime ? ` - ${formatDate(new Date(this.startTime), 'HH:mm:ss EEE MMM dd yyyy')}` : ''}`, + `\u229E ${data.name}${this.startTime ? ` - ${formatDate(new Date(this.startTime), 'HH:mm:ss EEE MMM dd yyyy')}` : ''}`, ); } diff --git a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQueryBuilder.ts b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQueryBuilder.ts index dd99acebb1..dd803bccfd 100644 --- a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQueryBuilder.ts +++ b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQueryBuilder.ts @@ -51,7 +51,7 @@ import { type DataCubeQuerySnapshotFilterCondition, type DataCubeQuerySnapshotFilter, type DataCubeQuerySnapshot, - DataCubeQuerySnapshotSortDirection, + DataCubeQuerySnapshotSortOperation, DataCubeQuerySnapshotFilterOperation, DataCubeQueryFilterGroupOperation, _findCol, @@ -347,6 +347,17 @@ export function buildExecutableQuery( ); } + // --------------------------------- SELECT --------------------------------- + + if (data.selectColumns.length) { + _process( + 'select', + _function(_name(DataCubeFunction.SELECT), [ + _cols(data.selectColumns.map((col) => _colSpec(col.name))), + ]), + ); + } + // --------------------------------- FILTER --------------------------------- if (data.filter) { @@ -398,17 +409,6 @@ export function buildExecutableQuery( ); } - // --------------------------------- SELECT --------------------------------- - - if (data.selectColumns.length) { - _process( - 'select', - _function(_name(DataCubeFunction.SELECT), [ - _cols(data.selectColumns.map((col) => _colSpec(col.name))), - ]), - ); - } - // --------------------------------- SORT --------------------------------- if (data.sortColumns.length) { @@ -419,7 +419,7 @@ export function buildExecutableQuery( data.sortColumns.map((col) => _function( _name( - col.direction === DataCubeQuerySnapshotSortDirection.ASCENDING + col.operation === DataCubeQuerySnapshotSortOperation.ASCENDING ? DataCubeFunction.ASC : DataCubeFunction.DESC, ), diff --git a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQueryEngine.ts b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQueryEngine.ts index 080c8c5adc..9136fbf8ab 100644 --- a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQueryEngine.ts +++ b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQueryEngine.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import type { V1_AppliedFunction } from '@finos/legend-graph'; +import { TailwindCSSPalette } from '@finos/legend-art'; +import { PRIMITIVE_TYPE, type V1_AppliedFunction } from '@finos/legend-graph'; export enum DataCubeFunction { // relation @@ -62,11 +63,15 @@ export enum DataCubeFunction { MAX = 'meta::pure::functions::collection::max', MIN = 'meta::pure::functions::collection::min', SUM = 'meta::pure::functions::math::sum', - STD_DEV_POPULATION = 'meta::pure::functions::math::stdDevPopulation', - STD_DEV_SAMPLE = 'meta::pure::functions::math::stdDevSample', - UNIQUE_VALUE_ONLY = 'meta::pure::functions::collection::uniqueValueOnly', + STDDEV_POP = 'meta::pure::functions::math::stdDevPopulation', + STDDEV_SAMP = 'meta::pure::functions::math::stdDevSample', + VAR_POP = 'meta::pure::functions::math::variancePopulation', + VAR_SAMP = 'meta::pure::functions::math::varianceSample', + // UNIQUE_VALUE_ONLY = 'meta::pure::functions::collection::uniqueValueOnly', + // PERCENTILE = 'meta::pure::functions::math::percentile', } +export const DEFAULT_REPORT_NAME = 'New Report'; export const DEFAULT_LAMBDA_VARIABLE_NAME = 'x'; export const PIVOT_COLUMN_NAME_VALUE_SEPARATOR = '__|__'; @@ -74,6 +79,7 @@ export const PIVOT_COLUMN_NAME_VALUE_SEPARATOR = '__|__'; // when no aggregate is specified in groupBy() or pivot() export const INTERNAL__FILLER_COUNT_AGG_COLUMN_NAME = 'INTERNAL__filler_count_agg_column'; +export const DEFAULT_ROW_BUFFER = 50; export type DataCubeQueryFunctionMap = { leafExtend?: V1_AppliedFunction | undefined; @@ -88,3 +94,129 @@ export type DataCubeQueryFunctionMap = { sort?: V1_AppliedFunction | undefined; limit?: V1_AppliedFunction | undefined; }; + +export enum DataCubeNumberScale { + BASIS_POINT = 'Basis Points (bp)', + PERCENT = 'Percent (%)', + THOUSANDS = 'Thousands (k)', + MILLIONS = 'Millions (m)', + BILLIONS = 'Billions (b)', + AUTO = 'Auto (k/m/b)', +} + +export enum DataCubeSelectionStat { + COUNT = 'Count', + SUM = 'Sum', + MIN = 'Min', + MAX = 'Max', + AVERAGE = 'Average', +} + +export enum DataCubeFont { + // sans-serif + ARIAL = 'Arial', + ROBOTO = 'Roboto', + ROBOTO_CONDENSED = 'Roboto Condensed', + + // serif + TIMES_NEW_ROMAN = 'Times New Roman', + GEORGIA = 'Georgia', + ROBOTO_SERIF = 'Roboto Serif', + + // monospaced + JERBRAINS_MONO = 'Jetbrains Mono', + ROBOTO_MONO = 'Roboto Mono', + UBUNTU_MONO = 'Ubuntu Mono', +} + +export enum DataCubeFontTextAlignment { + CENTER = 'Center', + LEFT = 'Left', + RIGHT = 'Right', +} + +export enum DataCubeColumnPinPlacement { + LEFT = 'Left', + RIGHT = 'Right', +} + +export enum DataCubeColumnDataType { + NUMBER = 'number', + DATE = 'date', + TEXT = 'text', +} + +export function getDataType(type: string): DataCubeColumnDataType { + switch (type) { + case PRIMITIVE_TYPE.NUMBER: + case PRIMITIVE_TYPE.INTEGER: + case PRIMITIVE_TYPE.DECIMAL: + case PRIMITIVE_TYPE.FLOAT: + return DataCubeColumnDataType.NUMBER; + case PRIMITIVE_TYPE.DATE: + case PRIMITIVE_TYPE.DATETIME: + case PRIMITIVE_TYPE.STRICTDATE: + return DataCubeColumnDataType.DATE; + case PRIMITIVE_TYPE.STRING: + default: + return DataCubeColumnDataType.TEXT; + } +} + +export const DEFAULT_FOREGROUND_COLOR = TailwindCSSPalette.black; +export const DEFAULT_BACKGROUND_COLOR = TailwindCSSPalette.white; +export const DEFAULT_ROW_HIGHLIGHT_BACKGROUND_COLOR = + TailwindCSSPalette.sky[100]; +export const DEFAULT_NEGATIVE_FOREGROUND_COLOR = TailwindCSSPalette.red[500]; +export const DEFAULT_ZERO_FOREGROUND_COLOR = TailwindCSSPalette.neutral[400]; +export const DEFAULT_ERROR_FOREGROUND_COLOR = TailwindCSSPalette.blue[600]; +export const DEFAULT_COLUMN_WIDTH = 300; +export const DEFAULT_COLUMN_MIN_WIDTH = 100; +export const DEFAULT_COLUMN_MAX_WIDTH = undefined; + +export enum DataCubeFontFormatUnderlinedVariant { + SOLID = 'Solid', + DASHED = 'Dashed', + DOTTED = 'Dotted', + DOUBLE = 'Double', + WAVY = 'Wavy', +} + +export enum DataCubeColumnKind { + MEASURE = 'Measure', + DIMENSION = 'Dimension', +} + +export enum DataCubeAggregateFunction { + SUM = 'sum', + AVERAGE = 'avg', + COUNT = 'count', + MIN = 'min', + MAX = 'max', + // UNIQUE = 'uniq', + FIRST = 'first', + LAST = 'last', + MEDIAN = 'median', + VAR_POP = 'var_samp', + VAR_SAMP = 'var_pop', + STDDEV_POP = 'stddev_pop', + STDDEV_SAMP = 'stddev_samp', + // STANDARD_ERROR = 'stderr', + // NULL = 'null', + // ssq + // countvalid + // countnull + // uniqunstrict + // minmagnitude + // maxmagnitude + // commonprefix + // commonprefixunstrict + // strjoin + // strjoinuniq + // splitjoin + // daterange + // wavg + // wstderr + // wsum + // custom +} diff --git a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQuerySnapshot.ts b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQuerySnapshot.ts index b176ad16b4..af277344ec 100644 --- a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQuerySnapshot.ts +++ b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQuerySnapshot.ts @@ -15,10 +15,12 @@ */ import type { V1_Lambda, V1_ValueSpecification } from '@finos/legend-graph'; -import type { DataCubeConfiguration } from '../../../server/models/DataCubeQuery.js'; +import type { DataCubeConfiguration } from './DataCubeConfiguration.js'; import { IllegalStateError, guaranteeNonNullable, + hashObject, + pruneObject, uuid, type PlainObject, type Writable, @@ -54,7 +56,7 @@ export enum DataCubeQuerySnapshotFilterOperation { ENDS_WITH = 'endsWith', } -export enum DataCubeQuerySnapshotSortDirection { +export enum DataCubeQuerySnapshotSortOperation { ASCENDING = 'ascending', DESCENDING = 'descending', } @@ -90,7 +92,7 @@ export type DataCubeQuerySnapshotExtendedColumn = }; export type DataCubeQuerySnapshotSortColumn = DataCubeQuerySnapshotColumn & { - direction: DataCubeQuerySnapshotSortDirection; + operation: DataCubeQuerySnapshotSortOperation; }; export type DataCubeQuerySnapshotAggregateColumn = @@ -113,7 +115,7 @@ export type DataCubeQuerySnapshotData = { name: string; runtime: string; sourceQuery: PlainObject; - configuration: DataCubeConfiguration; + configuration: PlainObject; originalColumns: DataCubeQuerySnapshotColumn[]; leafExtendedColumns: DataCubeQuerySnapshotExtendedColumn[]; filter?: DataCubeQuerySnapshotFilter | undefined; @@ -138,11 +140,14 @@ export class DataCubeQuerySnapshot { timestamp = Date.now(); readonly data: DataCubeQuerySnapshotData; + private _finalized = false; + private _hashCode?: string | undefined; + private constructor( name: string, runtime: string, sourceQuery: PlainObject, - configuration: DataCubeConfiguration, + configuration: PlainObject, ) { this.data = { name, @@ -165,7 +170,7 @@ export class DataCubeQuerySnapshot { name: string, runtime: string, sourceQuery: PlainObject, - configuration: DataCubeConfiguration, + configuration: PlainObject, ) { return new DataCubeQuerySnapshot(name, runtime, sourceQuery, configuration); } @@ -204,6 +209,38 @@ export class DataCubeQuerySnapshot { throw new IllegalStateError(`Unknown stage '${stage}'`); } } + + isFinalized(): boolean { + return this._finalized; + } + + finalize(): DataCubeQuerySnapshot { + if (this._finalized) { + return this; + } + this._hashCode = this.computeHashCode(); + this._finalized = true; + return this; + } + + get hashCode(): string { + if (!this._finalized || !this._hashCode) { + throw new IllegalStateError('Snapshot is not finalized'); + } + return this._hashCode; + } + + /** + * NOTE: if this becomes a performance bottleneck, we can consider + * more granular hashing strategy + * + * Here, we are just hashing the raw object, but we must ensure + * to properly prune the snapshot data object before hashing + * else there would be mismatch + */ + private computeHashCode(): string { + return hashObject(pruneObject(this.data)); + } } export function _findCol( diff --git a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQuerySnapshotBuilder.ts b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQuerySnapshotBuilder.ts index 7bb351e38f..10d896d189 100644 --- a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQuerySnapshotBuilder.ts +++ b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQuerySnapshotBuilder.ts @@ -36,7 +36,7 @@ import { import type { DataCubeQuery } from '../../../server/models/DataCubeQuery.js'; import { DataCubeQuerySnapshot, - DataCubeQuerySnapshotSortDirection, + DataCubeQuerySnapshotSortOperation, type DataCubeQuerySnapshotColumn, } from './DataCubeQuerySnapshot.js'; import { @@ -50,6 +50,8 @@ import { DataCubeFunction, type DataCubeQueryFunctionMap, } from './DataCubeQueryEngine.js'; +import { DataCubeConfiguration } from './DataCubeConfiguration.js'; +import { buildDefaultConfiguration } from './DataCubeConfigurationBuilder.js'; // --------------------------------- UTILITIES --------------------------------- @@ -133,12 +135,12 @@ const _FUNCTION_SEQUENCE_COMPOSITION_PATTERN: { required?: boolean | undefined; }[] = [ { func: DataCubeFunction.EXTEND }, + { func: DataCubeFunction.SELECT }, { func: DataCubeFunction.FILTER }, { func: DataCubeFunction.GROUP_BY }, { func: DataCubeFunction.PIVOT }, { func: DataCubeFunction.CAST }, { func: DataCubeFunction.EXTEND }, - { func: DataCubeFunction.SELECT }, { func: DataCubeFunction.SORT }, { func: DataCubeFunction.LIMIT }, ]; @@ -269,19 +271,19 @@ function extractFunctionMap( } } + const select = sequence.find((func) => + matchFunctionName(func.function, DataCubeFunction.SELECT), + ); const filter = sequence.find((func) => matchFunctionName(func.function, DataCubeFunction.FILTER), ); const groupBy = sequence.find((func) => matchFunctionName(func.function, DataCubeFunction.GROUP_BY), ); - const select = sequence.find((func) => - matchFunctionName(func.function, DataCubeFunction.SELECT), - ); const pivot = sequence.find((func) => matchFunctionName(func.function, DataCubeFunction.PIVOT), ); - const cast = sequence.find((func) => + const pivotCast = sequence.find((func) => matchFunctionName(func.function, DataCubeFunction.CAST), ); const sort = sequence.find((func) => @@ -292,12 +294,12 @@ function extractFunctionMap( ); return { leafExtend, + select, filter, groupBy, pivot, - pivotCast: cast, + pivotCast, groupExtend, - select, sort, limit, }; @@ -326,7 +328,7 @@ export function validateAndBuildQuerySnapshot( baseQuery.name, baseQuery.source.runtime, V1_serializeValueSpecification(sourceQuery, []), - baseQuery.configuration, + {}, ); const data = snapshot.data; const colsMap = new Map(); @@ -335,12 +337,25 @@ export function validateAndBuildQuerySnapshot( // --------------------------------- SOURCE --------------------------------- - data.originalColumns = baseQuery.source.columns; + data.originalColumns = baseQuery.source.columns.map((col) => ({ + name: col.name, + type: col.type, + })); data.originalColumns.map((col) => colsMap.set(col.name, col)); // --------------------------------- LEAF EXTEND --------------------------------- // TODO: @akphi - implement this + // --------------------------------- SELECT --------------------------------- + + if (funcMap.select) { + data.selectColumns = _colSpecArrayParam(funcMap.select, 0).colSpecs.map( + (colSpec) => ({ + ..._col(colSpec), + }), + ); + } + // --------------------------------- FILTER --------------------------------- // TODO: @akphi - implement this @@ -367,25 +382,15 @@ export function validateAndBuildQuerySnapshot( ]); return { ..._col(_colSpecParam(sortColFunc, 0)), - direction: + operation: _name(sortColFunc.function) === DataCubeFunction.ASC - ? DataCubeQuerySnapshotSortDirection.ASCENDING - : DataCubeQuerySnapshotSortDirection.DESCENDING, + ? DataCubeQuerySnapshotSortOperation.ASCENDING + : DataCubeQuerySnapshotSortOperation.DESCENDING, }; }, ); } - // --------------------------------- SELECT --------------------------------- - - if (funcMap.select) { - data.selectColumns = _colSpecArrayParam(funcMap.select, 0).colSpecs.map( - (colSpec) => ({ - ..._col(colSpec), - }), - ); - } - // --------------------------------- LIMIT --------------------------------- if (funcMap.limit) { @@ -394,5 +399,21 @@ export function validateAndBuildQuerySnapshot( data.limit = value.value; } - return snapshot; + // --------------------------------- CONFIGURATION --------------------------------- + // The data query takes precedence over the configuration, we do this for 2 reasons + // 1. The purpose of the configuration is to provide the layout and styling + // customization on top of the data query result grid. If conflicts happen between + // the configuration and the query, we will reconciliate by modifying the configuration + // 2. The configuration when missing, can be generated from default presets. This helps + // helps with use cases where the query might comes from a different source, such as + // Studio or Query, or another part of Engine. + + const configuration = baseQuery.configuration + ? DataCubeConfiguration.serialization.fromJson(baseQuery.configuration) + : buildDefaultConfiguration(baseQuery.source.columns); + // TODO: @akphi - implement this + data.configuration = + DataCubeConfiguration.serialization.toJson(configuration); + + return snapshot.finalize(); } diff --git a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQuerySnapshotManager.ts b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQuerySnapshotManager.ts index 4edd4ce5dd..4fcf3453a3 100644 --- a/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQuerySnapshotManager.ts +++ b/packages/legend-application-repl/src/stores/dataCube/core/DataCubeQuerySnapshotManager.ts @@ -26,7 +26,7 @@ import { } from '@finos/legend-shared'; import type { DataCubeQuery } from '../../../server/models/DataCubeQuery.js'; -// TODO: use this when we implement undo/redo +// TODO: set a stack depth when we implement undo/redo // const DATA_CUBE_MAX_SNAPSHOT_COUNT = 100; export class DataCubeQuerySnapshotManager { @@ -77,6 +77,13 @@ export class DataCubeQuerySnapshotManager { } broadcastSnapshot(snapshot: DataCubeQuerySnapshot): void { + if (!snapshot.isFinalized()) { + this.dataCube.application.logService.error( + LogEvent.create(APPLICATION_EVENT.ILLEGAL_APPLICATION_STATE_OCCURRED), + `Snapshot must be finalized before broadcasting`, + ); + return; + } this.snapshots.push(snapshot); this.subscribers.forEach((subscriber) => { const currentSnapshot = subscriber.getLatestSnapshot(); @@ -87,7 +94,7 @@ export class DataCubeQuerySnapshotManager { LogEvent.create( APPLICATION_EVENT.ILLEGAL_APPLICATION_STATE_OCCURRED, ), - `Subscribers receiving and applying new snapshot should be handled gracefully`, + `Error occured while subscribers receiving and applying new snapshot should be handled gracefully`, error, ); }); diff --git a/packages/legend-application-repl/src/stores/dataCube/core/__tests__/DataCubeQueryAnalyzer.repl-test.ts b/packages/legend-application-repl/src/stores/dataCube/core/__tests__/DataCubeQueryAnalyzer.repl-test.ts index 6ec469eadf..7d94b53ff9 100644 --- a/packages/legend-application-repl/src/stores/dataCube/core/__tests__/DataCubeQueryAnalyzer.repl-test.ts +++ b/packages/legend-application-repl/src/stores/dataCube/core/__tests__/DataCubeQueryAnalyzer.repl-test.ts @@ -57,8 +57,8 @@ const cases: BaseSnapshotAnalysisTestCase[] = [ '', ], [ - 'Valid: Usage - Column Selection: extend()->filter()->select()->sort()->limit()', - 'extend(~[a:x|1])->filter(x|$x.a==1)->select(~[a])->sort([ascending(~a)])->limit(10)', + 'Valid: Usage - Column Selection: extend()->select()->filter()->sort()->limit()', + 'extend(~[a:x|1])->select(~[a])->filter(x|$x.a==1)->sort([ascending(~a)])->limit(10)', ['a:Integer'], '', ], @@ -106,7 +106,7 @@ const cases: BaseSnapshotAnalysisTestCase[] = [ 'Invalid: Unsupported function composition: filter()->extend()->filter()', 'filter(x|$x.a==1)->extend(~[a:x|1])->filter(x|$x.a==1)', [], - 'Unsupported function composition filter()->extend()->filter() (supported composition: extend()->filter()->groupBy()->pivot()->cast()->extend()->select()->sort()->limit())', + 'Unsupported function composition filter()->extend()->filter() (supported composition: extend()->select()->filter()->groupBy()->pivot()->cast()->extend()->sort()->limit())', ], [ 'Invalid: Group-level extend() used when no aggregation/grouping presents', @@ -145,7 +145,7 @@ describe(unitTest('Analyze and build base snapshot'), () => { await ENGINE_TEST_SUPPORT__grammarToJSON_valueSpecification(code), [], ); - const baseQuery = new DataCubeQuery('', '', {}); + const baseQuery = new DataCubeQuery('', '', undefined); baseQuery.source = new DataCubeQuerySourceREPLExecutedQuery(); baseQuery.partialQuery = code; baseQuery.source.query = ''; diff --git a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorCodePanelState.ts b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorCodePanelState.ts index 64b360123c..1347813a4e 100644 --- a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorCodePanelState.ts +++ b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorCodePanelState.ts @@ -29,6 +29,7 @@ import { SourceInformation, V1_ParserError, } from '@finos/legend-graph'; +import type { DataCubeEditorState } from './DataCubeEditorState.js'; class DataCubeQueryEditorState { uuid = uuid(); @@ -79,11 +80,12 @@ class DataCubeQueryEditorState { export class DataCubeEditorCodePanelState { readonly dataCube!: DataCubeState; + readonly editor: DataCubeEditorState; queryEditorState!: DataCubeQueryEditorState; currentSubQuery?: string | undefined; - constructor(dataCube: DataCubeState) { + constructor(editor: DataCubeEditorState) { makeObservable(this, { currentSubQuery: observable, queryEditorState: observable, @@ -91,7 +93,8 @@ export class DataCubeEditorCodePanelState { parseQuery: flow, }); - this.dataCube = dataCube; + this.editor = editor; + this.dataCube = editor.dataCube; this.queryEditorState = new DataCubeQueryEditorState(''); } diff --git a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorColumnPropertiesPanelState.ts b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorColumnPropertiesPanelState.ts new file mode 100644 index 0000000000..0d7ad991f9 --- /dev/null +++ b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorColumnPropertiesPanelState.ts @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { action, computed, makeObservable, observable } from 'mobx'; +import type { DataCubeState } from '../DataCubeState.js'; +import type { DataCubeQuerySnapshot } from '../core/DataCubeQuerySnapshot.js'; +import type { DataCubeQueryEditorPanelState } from './DataCubeEditorPanelState.js'; +import type { DataCubeEditorState } from './DataCubeEditorState.js'; +import { DataCubeMutableColumnConfiguration } from './DataCubeMutableConfiguration.js'; +import { getNonNullableEntry, type PlainObject } from '@finos/legend-shared'; + +export class DataCubeEditorColumnPropertiesPanelState + implements DataCubeQueryEditorPanelState +{ + readonly dataCube!: DataCubeState; + readonly editor!: DataCubeEditorState; + + columns: DataCubeMutableColumnConfiguration[] = []; + selectedColumnName?: string | undefined; + + constructor(editor: DataCubeEditorState) { + makeObservable(this, { + columns: observable, + setColumns: action, + + selectedColumnName: observable, + setSelectedColumnName: action, + selectedColumn: computed, + }); + + this.editor = editor; + this.dataCube = editor.dataCube; + } + + setColumns(val: DataCubeMutableColumnConfiguration[]): void { + this.columns = val; + } + + setSelectedColumnName(val: string | undefined): void { + this.selectedColumnName = val; + } + + get selectedColumn(): DataCubeMutableColumnConfiguration | undefined { + return this.columns.find( + (column) => column.name === this.selectedColumnName, + ); + } + + applySnaphot(snapshot: DataCubeQuerySnapshot): void { + this.setColumns( + (snapshot.data.configuration as { columns: PlainObject[] }).columns.map( + (column) => DataCubeMutableColumnConfiguration.create(column), + ), + ); + if (!this.selectedColumn && this.columns.length) { + this.setSelectedColumnName(getNonNullableEntry(this.columns, 0).name); + } + } + + buildSnapshot( + newSnapshot: DataCubeQuerySnapshot, + baseSnapshot: DataCubeQuerySnapshot, + ): void { + newSnapshot.data.configuration = { + ...newSnapshot.data.configuration, + columns: this.columns.map((column) => column.serialize()), + }; + } +} diff --git a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorColumnsSelectorState.ts b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorColumnsSelectorState.ts index 948dd9d7d3..937895942a 100644 --- a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorColumnsSelectorState.ts +++ b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorColumnsSelectorState.ts @@ -18,6 +18,9 @@ import { makeObservable, observable, action } from 'mobx'; export abstract class DataCubeEditorColumnsSelectorColumnState { abstract get name(): string; + resetWhenMadeAvailable(): void { + // do nothing + } } export class DataCubeEditorColumnsSelectorState< @@ -45,6 +48,7 @@ export class DataCubeEditorColumnsSelectorState< this.availableColumns = val .slice() .sort((a, b) => a.name.localeCompare(b.name)); + this.availableColumns.forEach((column) => column.resetWhenMadeAvailable()); } setSelectedColumns(val: T[]): void { @@ -58,4 +62,12 @@ export class DataCubeEditorColumnsSelectorState< setSelectedColumnsSearchText(val: string): void { this.selectedColumnsSearchText = val; } + + getAvailableColumn(colName: string): T | undefined { + return this.availableColumns.find((col) => col.name === colName); + } + + getSelectedColumn(colName: string): T | undefined { + return this.selectedColumns.find((col) => col.name === colName); + } } diff --git a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorGeneralPropertiesPanelState.ts b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorGeneralPropertiesPanelState.ts index e0e0d78add..6a37ba11ee 100644 --- a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorGeneralPropertiesPanelState.ts +++ b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorGeneralPropertiesPanelState.ts @@ -18,63 +18,66 @@ import { action, makeObservable, observable } from 'mobx'; import type { DataCubeState } from '../DataCubeState.js'; import type { DataCubeQuerySnapshot } from '../core/DataCubeQuerySnapshot.js'; import type { DataCubeQueryEditorPanelState } from './DataCubeEditorPanelState.js'; +import type { DataCubeEditorState } from './DataCubeEditorState.js'; +import { DataCubeMutableConfiguration } from './DataCubeMutableConfiguration.js'; export class DataCubeEditorGeneralPropertiesPanelState implements DataCubeQueryEditorPanelState { readonly dataCube!: DataCubeState; + readonly editor!: DataCubeEditorState; + name = ''; limit = -1; + configuration = new DataCubeMutableConfiguration(); - constructor(dataCube: DataCubeState) { - this.dataCube = dataCube; - + constructor(editor: DataCubeEditorState) { makeObservable(this, { name: observable, setName: action, limit: observable, setLimit: action, + + configuration: observable, + setConfiguration: action, }); + + this.editor = editor; + this.dataCube = editor.dataCube; } setName(val: string): void { this.name = val; } - setLimit(val: number | undefined): void { - this.limit = Math.round(val === undefined || val < 0 ? -1 : val); + setLimit(val: number): void { + this.limit = val; + } + + setConfiguration(val: DataCubeMutableConfiguration): void { + this.configuration = val; } applySnaphot(snapshot: DataCubeQuerySnapshot): void { this.setName(snapshot.data.name); - this.setLimit(snapshot.data.limit); + this.setLimit( + snapshot.data.limit !== undefined && snapshot.data.limit > 0 + ? snapshot.data.limit + : -1, + ); + this.setConfiguration( + DataCubeMutableConfiguration.create(snapshot.data.configuration), + ); } buildSnapshot( newSnapshot: DataCubeQuerySnapshot, baseSnapshot: DataCubeQuerySnapshot, - ): boolean { - const data = baseSnapshot.data; - // name - if (this.name !== data.name) { - data.name = this.name; - return true; - } - - // limit - if (data.limit === undefined) { - if (this.limit !== -1) { - data.limit = this.limit; - return true; - } - } else { - if (this.limit !== data.limit) { - data.limit = this.limit; - return true; - } - } - - return false; + ): void { + const data = newSnapshot.data; + data.name = this.name; + data.limit = this.limit <= 0 ? undefined : this.limit; + data.configuration = this.configuration.serialize(); } } diff --git a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorHPivotPanelState.ts b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorHPivotPanelState.ts new file mode 100644 index 0000000000..719028a302 --- /dev/null +++ b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorHPivotPanelState.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { DataCubeState } from '../DataCubeState.js'; +import type { DataCubeQuerySnapshot } from '../core/DataCubeQuerySnapshot.js'; +import type { DataCubeQueryEditorPanelState } from './DataCubeEditorPanelState.js'; +import type { DataCubeEditorState } from './DataCubeEditorState.js'; + +export class DataCubeEditorGeneralPropertiesPanelState + implements DataCubeQueryEditorPanelState +{ + readonly dataCube!: DataCubeState; + readonly editor!: DataCubeEditorState; + + constructor(editor: DataCubeEditorState) { + this.editor = editor; + this.dataCube = editor.dataCube; + } + + applySnaphot(snapshot: DataCubeQuerySnapshot): void { + // TODO + } + + buildSnapshot( + newSnapshot: DataCubeQuerySnapshot, + baseSnapshot: DataCubeQuerySnapshot, + ): void { + // TODO + } +} diff --git a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorPanelState.ts b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorPanelState.ts index a2f39df9f5..7804f6b446 100644 --- a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorPanelState.ts +++ b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorPanelState.ts @@ -29,5 +29,5 @@ export interface DataCubeQueryEditorPanelState { buildSnapshot( newSnapshot: DataCubeQuerySnapshot, baseSnapshot: DataCubeQuerySnapshot, - ): boolean; + ): void; } diff --git a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorSortsPanelState.ts b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorSortsPanelState.ts index 6763fdb2f0..4bdcd778f2 100644 --- a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorSortsPanelState.ts +++ b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorSortsPanelState.ts @@ -17,44 +17,47 @@ import { action, makeObservable, observable } from 'mobx'; import type { DataCubeState } from '../DataCubeState.js'; import { - DataCubeQuerySnapshotSortDirection, + DataCubeQuerySnapshotSortOperation, _getCol, type DataCubeQuerySnapshot, type DataCubeQuerySnapshotColumn, - type DataCubeQuerySnapshotSortColumn, } from '../core/DataCubeQuerySnapshot.js'; import type { DataCubeQueryEditorPanelState } from './DataCubeEditorPanelState.js'; -import { deepEqual } from '@finos/legend-shared'; import { DataCubeEditorColumnsSelectorColumnState, DataCubeEditorColumnsSelectorState, } from './DataCubeEditorColumnsSelectorState.js'; +import type { DataCubeEditorState } from './DataCubeEditorState.js'; export class DataCubeEditorSortColumnState extends DataCubeEditorColumnsSelectorColumnState { readonly column: DataCubeQuerySnapshotColumn; - direction: DataCubeQuerySnapshotSortDirection; + operation: DataCubeQuerySnapshotSortOperation; constructor( column: DataCubeQuerySnapshotColumn, - direction: DataCubeQuerySnapshotSortDirection, + direction: DataCubeQuerySnapshotSortOperation, ) { super(); makeObservable(this, { - direction: observable, - setDirection: action, + operation: observable, + setOperation: action, }); this.column = column; - this.direction = direction; + this.operation = direction; } get name(): string { return this.column.name; } - setDirection(val: DataCubeQuerySnapshotSortDirection): void { - this.direction = val; + override resetWhenMadeAvailable(): void { + this.setOperation(DataCubeQuerySnapshotSortOperation.ASCENDING); + } + + setOperation(val: DataCubeQuerySnapshotSortOperation): void { + this.operation = val; } } @@ -62,13 +65,82 @@ export class DataCubeEditorSortsPanelState implements DataCubeQueryEditorPanelState { readonly dataCube!: DataCubeState; + readonly editor!: DataCubeEditorState; readonly columnsSelector!: DataCubeEditorColumnsSelectorState; - constructor(dataCube: DataCubeState) { - this.dataCube = dataCube; + constructor(editor: DataCubeEditorState) { + this.editor = editor; + this.dataCube = editor.dataCube; this.columnsSelector = new DataCubeEditorColumnsSelectorState(); } + getActionableSortColumn( + colName: string, + operation: DataCubeQuerySnapshotSortOperation, + ): DataCubeEditorSortColumnState | undefined { + let column = this.columnsSelector.getAvailableColumn(colName); + if (!column) { + const selectedColumn = this.columnsSelector.getSelectedColumn(colName); + if (selectedColumn && selectedColumn.operation !== operation) { + column = selectedColumn; + } + } + return column; + } + + sortByColumn( + colName: string, + operation: DataCubeQuerySnapshotSortOperation, + ): void { + const column = this.getActionableSortColumn(colName, operation); + if (!column) { + return; + } + column.setOperation(operation); + + this.columnsSelector.setAvailableColumns( + [ + ...this.columnsSelector.availableColumns, + ...this.columnsSelector.selectedColumns, + ].filter((col) => col.name !== colName), + ); + this.columnsSelector.setSelectedColumns([column]); + this.editor.applyChanges(); + } + + addSortByColumn( + colName: string, + operation: DataCubeQuerySnapshotSortOperation, + ): void { + const column = this.getActionableSortColumn(colName, operation); + if (!column) { + return; + } + column.setOperation(operation); + + this.columnsSelector.setAvailableColumns( + this.columnsSelector.availableColumns.filter( + (col) => col.name !== colName, + ), + ); + this.columnsSelector.setSelectedColumns([ + ...this.columnsSelector.selectedColumns, + column, + ]); + this.editor.applyChanges(); + } + + clearAllSorts(): void { + if (this.columnsSelector.selectedColumns.length !== 0) { + this.columnsSelector.setAvailableColumns([ + ...this.columnsSelector.availableColumns, + ...this.columnsSelector.selectedColumns, + ]); + this.columnsSelector.setSelectedColumns([]); + this.editor.applyChanges(); + } + } + applySnaphot(snapshot: DataCubeQuerySnapshot): void { const columns = snapshot.stageCols('sort'); const sortColumns = snapshot.data.sortColumns; @@ -81,7 +153,7 @@ export class DataCubeEditorSortsPanelState (col) => new DataCubeEditorSortColumnState( _getCol(columns, col.name), - DataCubeQuerySnapshotSortDirection.ASCENDING, + DataCubeQuerySnapshotSortOperation.ASCENDING, ), ), ); @@ -90,7 +162,7 @@ export class DataCubeEditorSortsPanelState (col) => new DataCubeEditorSortColumnState( _getCol(columns, col.name), - col.direction, + col.operation, ), ), ); @@ -99,18 +171,13 @@ export class DataCubeEditorSortsPanelState buildSnapshot( newSnapshot: DataCubeQuerySnapshot, baseSnapshot: DataCubeQuerySnapshot, - ): boolean { - const newSortColumns: DataCubeQuerySnapshotSortColumn[] = - this.columnsSelector.selectedColumns.map((sortInfo) => ({ + ): void { + newSnapshot.data.sortColumns = this.columnsSelector.selectedColumns.map( + (sortInfo) => ({ name: sortInfo.column.name, type: sortInfo.column.type, - direction: sortInfo.direction, - })); - - if (!deepEqual(newSortColumns, baseSnapshot.data.sortColumns)) { - newSnapshot.data.sortColumns = newSortColumns; - return true; - } - return false; + operation: sortInfo.operation, + }), + ); } } diff --git a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorState.ts b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorState.ts index 81d268eea0..73d4fe9814 100644 --- a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorState.ts +++ b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeEditorState.ts @@ -22,6 +22,7 @@ import { DataCubeQuerySnapshotSubscriber } from '../core/DataCubeQuerySnapshotSu import { type DataCubeQuerySnapshot } from '../core/DataCubeQuerySnapshot.js'; import { guaranteeNonNullable } from '@finos/legend-shared'; import { DataCubeEditorGeneralPropertiesPanelState } from './DataCubeEditorGeneralPropertiesPanelState.js'; +import { DataCubeEditorColumnPropertiesPanelState } from './DataCubeEditorColumnPropertiesPanelState.js'; export enum DATA_CUBE_EDITOR_TAB { COLUMNS = 'Columns', @@ -33,13 +34,13 @@ export enum DATA_CUBE_EDITOR_TAB { GENERAL_PROPERTIES = 'General Properties', COLUMN_PROPERTIES = 'Column Properties', CODE = 'Code', - // DEVELOPER_OPTIONS = 'Developer', - // PIVOT_LAYOUT = 'Pivot Layout', + DEVELOPER = 'Developer', } export class DataCubeEditorState extends DataCubeQuerySnapshotSubscriber { readonly sortsPanel: DataCubeEditorSortsPanelState; readonly generalPropertiesPanel: DataCubeEditorGeneralPropertiesPanelState; + readonly columnPropertiesPanel: DataCubeEditorColumnPropertiesPanelState; readonly codePanel: DataCubeEditorCodePanelState; isPanelOpen = false; @@ -59,11 +60,14 @@ export class DataCubeEditorState extends DataCubeQuerySnapshotSubscriber { closePanel: action, }); - this.sortsPanel = new DataCubeEditorSortsPanelState(this.dataCube); + this.sortsPanel = new DataCubeEditorSortsPanelState(this); this.generalPropertiesPanel = new DataCubeEditorGeneralPropertiesPanelState( - this.dataCube, + this, ); - this.codePanel = new DataCubeEditorCodePanelState(this.dataCube); + this.columnPropertiesPanel = new DataCubeEditorColumnPropertiesPanelState( + this, + ); + this.codePanel = new DataCubeEditorCodePanelState(this); } openPanel(): void { @@ -82,12 +86,14 @@ export class DataCubeEditorState extends DataCubeQuerySnapshotSubscriber { const baseSnapshot = guaranteeNonNullable(this.getLatestSnapshot()); const snapshot = baseSnapshot.clone(); - const createNew = [ - this.sortsPanel.buildSnapshot(snapshot, baseSnapshot), - this.generalPropertiesPanel.buildSnapshot(snapshot, baseSnapshot), - ].some(Boolean); + this.sortsPanel.buildSnapshot(snapshot, baseSnapshot); + // NOTE: snapshot must be processed first to build the container configuration + // before proceeding to process the columns' configuration + this.generalPropertiesPanel.buildSnapshot(snapshot, baseSnapshot); + this.columnPropertiesPanel.buildSnapshot(snapshot, baseSnapshot); - if (createNew) { + snapshot.finalize(); + if (snapshot.hashCode !== baseSnapshot.hashCode) { this.publishSnapshot(snapshot); } } @@ -95,6 +101,7 @@ export class DataCubeEditorState extends DataCubeQuerySnapshotSubscriber { override async applySnapshot(snapshot: DataCubeQuerySnapshot): Promise { this.sortsPanel.applySnaphot(snapshot); this.generalPropertiesPanel.applySnaphot(snapshot); + this.columnPropertiesPanel.applySnaphot(snapshot); } override async initialize(): Promise { diff --git a/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeMutableConfiguration.ts b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeMutableConfiguration.ts new file mode 100644 index 0000000000..f540b4568b --- /dev/null +++ b/packages/legend-application-repl/src/stores/dataCube/editor/DataCubeMutableConfiguration.ts @@ -0,0 +1,533 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + getDataType, + type DataCubeColumnKind, + type DataCubeFont, + type DataCubeAggregateFunction, + type DataCubeNumberScale, + type DataCubeSelectionStat, + type DataCubeFontFormatUnderlinedVariant, + type DataCubeFontTextAlignment, + type DataCubeColumnDataType, + type DataCubeColumnPinPlacement, +} from '../core/DataCubeQueryEngine.js'; +import { type PlainObject, type Writable } from '@finos/legend-shared'; +import { makeObservable, observable, action } from 'mobx'; +import { + DataCubeColumnConfiguration, + DataCubeConfiguration, +} from '../core/DataCubeConfiguration.js'; + +export class DataCubeMutableColumnConfiguration extends DataCubeColumnConfiguration { + aggregateFunction?: DataCubeAggregateFunction | undefined; + weightColumn?: string | undefined; + excludedFromHPivot = true; + + readonly dataType!: DataCubeColumnDataType; + + static create( + json: PlainObject, + ): DataCubeMutableColumnConfiguration { + const configuration = Object.assign( + new DataCubeMutableColumnConfiguration('', ''), + DataCubeColumnConfiguration.serialization.fromJson(json), + ); + (configuration as Writable).dataType = + getDataType(configuration.type); + + makeObservable(configuration, { + kind: observable, + setKind: action, + + displayName: observable, + setDisplayName: action, + + decimals: observable, + setDecimals: action, + + displayCommas: observable, + setDisplayCommas: action, + + negativeNumberInParens: observable, + setNegativeNumberInParens: action, + + numberScale: observable, + setNumberScale: action, + + hPivotSortFunction: observable, + setHPivotSortFunction: action, + + fontFamily: observable, + setFontFamily: action, + + fontSize: observable, + setFontSize: action, + + fontBold: observable, + setFontBold: action, + + fontItalic: observable, + setFontItalic: action, + + fontUnderlined: observable, + setFontUnderlined: action, + + fontStrikethrough: observable, + setFontStrikethrough: action, + + textAlign: observable, + setTextAlign: action, + + foregroundColor: observable, + setForegroundColor: action, + + foregroundNegativeColor: observable, + setForegroundNegativeColor: action, + + foregroundZeroColor: observable, + setForegroundZeroColor: action, + + foregroundErrorColor: observable, + setForegroundErrorColor: action, + + backgroundColor: observable, + setBackgroundColor: action, + + backgroundNegativeColor: observable, + setBackgroundNegativeColor: action, + + backgroundZeroColor: observable, + setBackgroundZeroColor: action, + + backgroundErrorColor: observable, + setBackgroundErrorColor: action, + + blur: observable, + setBlur: action, + + hideFromView: observable, + setHideFromView: action, + + aggregateFunction: observable, + setAggregateFunction: action, + + weightColumn: observable, + setWeightColumn: action, + + excludedFromHPivot: observable, + setExcludedFromHPivot: action, + + fixedWidth: observable, + setFixedWidth: action, + + minWidth: observable, + setMinWidth: action, + + maxWidth: observable, + setMaxWidth: action, + + pinned: observable, + setPinned: action, + + displayAsLink: observable, + setDisplayAsLink: action, + }); + + return configuration; + } + + serialize(): PlainObject { + return DataCubeColumnConfiguration.serialization.toJson(this); + } + + setKind(value: DataCubeColumnKind): void { + this.kind = value; + } + + setDisplayName(value: string | undefined): void { + this.displayName = value; + } + + setDecimals(value: number | undefined): void { + this.decimals = value; + } + + setDisplayCommas(value: boolean): void { + this.displayCommas = value; + } + + setNegativeNumberInParens(value: boolean): void { + this.negativeNumberInParens = value; + } + + setNumberScale(value: DataCubeNumberScale | undefined): void { + this.numberScale = value; + } + + setHPivotSortFunction(value: string | undefined): void { + this.hPivotSortFunction = value; + } + + setFontFamily(value: DataCubeFont): void { + this.fontFamily = value; + } + + setFontSize(value: number): void { + this.fontSize = value; + } + + setFontBold(value: boolean): void { + this.fontBold = value; + } + + setFontItalic(value: boolean): void { + this.fontItalic = value; + } + + setFontUnderlined( + value: DataCubeFontFormatUnderlinedVariant | undefined, + ): void { + this.fontUnderlined = value; + } + + setTextAlign(value: DataCubeFontTextAlignment): void { + this.textAlign = value; + } + + setFontStrikethrough(value: boolean): void { + this.fontStrikethrough = value; + } + + setForegroundColor(value: string): void { + this.foregroundColor = value; + } + + setForegroundNegativeColor(value: string): void { + this.foregroundNegativeColor = value; + } + + setForegroundZeroColor(value: string): void { + this.foregroundZeroColor = value; + } + + setForegroundErrorColor(value: string): void { + this.foregroundErrorColor = value; + } + + setBackgroundColor(value: string): void { + this.backgroundColor = value; + } + + setBackgroundNegativeColor(value: string): void { + this.backgroundNegativeColor = value; + } + + setBackgroundZeroColor(value: string): void { + this.backgroundZeroColor = value; + } + + setBackgroundErrorColor(value: string): void { + this.backgroundErrorColor = value; + } + + setBlur(value: boolean): void { + this.blur = value; + } + + setHideFromView(value: boolean): void { + this.hideFromView = value; + } + + setFixedWidth(value: number | undefined): void { + this.fixedWidth = value; + } + + setMinWidth(value: number | undefined): void { + this.minWidth = value; + } + + setMaxWidth(value: number | undefined): void { + this.maxWidth = value; + } + + setPinned(value: DataCubeColumnPinPlacement | undefined): void { + this.pinned = value; + } + + setDisplayAsLink(value: boolean): void { + this.displayAsLink = value; + } + + setAggregateFunction(value: DataCubeAggregateFunction | undefined): void { + this.aggregateFunction = value; + } + + setWeightColumn(value: string | undefined): void { + this.weightColumn = value; + } + + setExcludedFromHPivot(value: boolean): void { + this.excludedFromHPivot = value; + } +} + +export class DataCubeMutableConfiguration extends DataCubeConfiguration { + static create( + json: PlainObject, + ): DataCubeMutableConfiguration { + const configuration = Object.assign( + new DataCubeMutableConfiguration(), + DataCubeConfiguration.serialization.fromJson(json), + ); + configuration.columns = []; + + makeObservable(configuration, { + description: observable, + setDescription: action, + + showTreeLine: observable, + setShowTreeLine: action, + + showHorizontalGridLine: observable, + setShowHorizontalGridLine: action, + + showVerticalGridLine: observable, + setShowVerticalGridLine: action, + + defaultFontFamily: observable, + setDefaultFontFamily: action, + + defaultFontSize: observable, + setDefaultFontSize: action, + + defaultFontBold: observable, + setDefaultFontBold: action, + + defaultFontItalic: observable, + setDefaultFontItalic: action, + + defaultFontUnderlined: observable, + setDefaultFontUnderlined: action, + + defaultFontStrikethrough: observable, + setDefaultFontStrikethrough: action, + + defaultTextAlign: observable, + setDefaultTextAlign: action, + + defaultForegroundColor: observable, + setDefaultForegroundColor: action, + + defaultForegroundNegativeColor: observable, + setDefaultForegroundNegativeColor: action, + + defaultForegroundZeroColor: observable, + setDefaultForegroundZeroColor: action, + + defaultForegroundErrorColor: observable, + setDefaultForegroundErrorColor: action, + + defaultBackgroundColor: observable, + setDefaultBackgroundColor: action, + + defaultBackgroundNegativeColor: observable, + setDefaultBackgroundNegativeColor: action, + + defaultBackgroundZeroColor: observable, + setDefaultBackgroundZeroColor: action, + + defaultBackgroundErrorColor: observable, + setDefaultBackgroundErrorColor: action, + + alternateRows: observable, + setAlternateRows: action, + + alternateRowsColor: observable, + setAlternateRowsColor: action, + + alternateRowsCount: observable, + setAlternateRowsCount: action, + + numberScale: observable, + setNumberScale: action, + + selectionStats: observable, + setSelectionStats: action, + + rowBuffer: observable, + setRowBuffer: action, + + showWarningForTruncatedResult: observable, + setShowWarningForTruncatedResult: action, + + initialExpandLevel: observable, + setInitialExpandLevel: action, + + showRootAggregation: observable, + setShowRootAggregation: action, + + showLeafCount: observable, + setShowLeafCount: action, + + addPivotTotalColumn: observable, + setAddPivotTotalColumn: action, + + addPivotTotalColumnOnLeft: observable, + setAddPivotTotalColumnOnLeft: action, + + treeGroupSortFunction: observable, + setTreeGroupSortFunction: action, + }); + + return configuration; + } + + serialize(): PlainObject { + return DataCubeConfiguration.serialization.toJson(this); + } + + setDescription(value: string | undefined): void { + this.description = value; + } + + setShowTreeLine(value: boolean): void { + this.showTreeLine = value; + } + + setShowHorizontalGridLine(value: boolean): void { + this.showHorizontalGridLine = value; + } + + setShowVerticalGridLine(value: boolean): void { + this.showVerticalGridLine = value; + } + + setDefaultFontFamily(value: DataCubeFont): void { + this.defaultFontFamily = value; + } + + setDefaultFontSize(value: number): void { + this.defaultFontSize = value; + } + + setDefaultFontBold(value: boolean): void { + this.defaultFontBold = value; + } + + setDefaultFontItalic(value: boolean): void { + this.defaultFontItalic = value; + } + + setDefaultFontUnderlined( + value: DataCubeFontFormatUnderlinedVariant | undefined, + ): void { + this.defaultFontUnderlined = value; + } + + setDefaultFontStrikethrough(value: boolean): void { + this.defaultFontStrikethrough = value; + } + + setDefaultTextAlign(value: DataCubeFontTextAlignment): void { + this.defaultTextAlign = value; + } + + setDefaultForegroundColor(value: string): void { + this.defaultForegroundColor = value; + } + + setDefaultForegroundNegativeColor(value: string): void { + this.defaultForegroundNegativeColor = value; + } + + setDefaultForegroundZeroColor(value: string): void { + this.defaultForegroundZeroColor = value; + } + + setDefaultForegroundErrorColor(value: string): void { + this.defaultForegroundErrorColor = value; + } + + setDefaultBackgroundColor(value: string): void { + this.defaultBackgroundColor = value; + } + + setDefaultBackgroundNegativeColor(value: string): void { + this.defaultBackgroundNegativeColor = value; + } + + setDefaultBackgroundZeroColor(value: string): void { + this.defaultBackgroundZeroColor = value; + } + + setDefaultBackgroundErrorColor(value: string): void { + this.defaultBackgroundErrorColor = value; + } + + setAlternateRows(value: boolean): void { + this.alternateRows = value; + } + + setAlternateRowsColor(value: string): void { + this.alternateRowsColor = value; + } + + setAlternateRowsCount(value: number): void { + this.alternateRowsCount = value; + } + + setNumberScale(value: DataCubeNumberScale | undefined): void { + this.numberScale = value; + } + + setSelectionStats(value: DataCubeSelectionStat[]): void { + this.selectionStats = value; + } + + setRowBuffer(value: number): void { + this.rowBuffer = value; + } + + setShowWarningForTruncatedResult(value: boolean): void { + this.showWarningForTruncatedResult = value; + } + + setInitialExpandLevel(value: number | undefined): void { + this.initialExpandLevel = value; + } + + setShowRootAggregation(value: boolean): void { + this.showRootAggregation = value; + } + + setShowLeafCount(value: boolean): void { + this.showLeafCount = value; + } + + setAddPivotTotalColumn(value: boolean): void { + this.addPivotTotalColumn = value; + } + + setAddPivotTotalColumnOnLeft(value: boolean): void { + this.addPivotTotalColumnOnLeft = value; + } + + setTreeGroupSortFunction(value: string | undefined): void { + this.treeGroupSortFunction = value; + } +} diff --git a/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridClientEngine.ts b/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridClientEngine.ts index b5e863e4f7..85365903a5 100644 --- a/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridClientEngine.ts +++ b/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridClientEngine.ts @@ -15,6 +15,8 @@ */ import type { + GridApi, + IRowNode, IServerSideDatasource, IServerSideGetRowsParams, } from '@ag-grid-community/core'; @@ -39,7 +41,6 @@ type GridClientRowData = { export const INTERNAL__GRID_CLIENT_HEADER_HEIGHT = 24; export const INTERNAL__GRID_CLIENT_ROW_HEIGHT = 20; -export const INTERNAL__GRID_CLIENT_ROW_BUFFER = 50; export const INTERNAL__GRID_CLIENT_TREE_COLUMN_ID = 'INTERNAL__tree'; export const INTERNAL__GRID_CLIENT_ROW_GROUPING_COUNT_AGG_COLUMN_ID = 'INTERNAL__count'; @@ -57,6 +58,30 @@ export enum GridClientAggregateOperation { AVERAGE = 'avg', } +export function getDataForAllNodes(client: GridApi): T[] { + const rows: T[] = []; + client.forEachNode((node: IRowNode) => { + if (node.data) { + rows.push(node.data); + } + }); + return rows; +} + +/** + * NOTE: this method does not work for server-side row model. + * It only works when client-side filter is being applied + */ +export function getDataForAllFilteredNodes(client: GridApi): T[] { + const rows: T[] = []; + client.forEachNodeAfterFilter((node: IRowNode) => { + if (node.data) { + rows.push(node.data); + } + }); + return rows; +} + function TDStoRowData(tds: TabularDataSet): GridClientRowData[] { return tds.rows.map((_row, rowIdx) => { const row: GridClientRowData = {}; @@ -88,6 +113,8 @@ export class DataCubeGridClientServerSideDataSource async fetchRows( params: IServerSideGetRowsParams, ): Promise { + const task = this.grid.dataCube.newTask('Fetching data'); + // ------------------------------ GRID OPTIONS ------------------------------ // Here, we make adjustments to the grid display in response to the new // request, in case the grid action has not impacted the layout in an @@ -109,8 +136,10 @@ export class DataCubeGridClientServerSideDataSource // ------------------------------ SNAPSHOT ------------------------------ const currentSnapshot = guaranteeNonNullable(this.grid.getLatestSnapshot()); + // TODO: when we support pivoting, we should make a quick call to check for columns + // created by pivots and specify them as cast columns when pivot is activated const syncedSnapshot = buildQuerySnapshot(params.request, currentSnapshot); - if (syncedSnapshot.uuid !== currentSnapshot.uuid) { + if (syncedSnapshot.hashCode !== currentSnapshot.hashCode) { this.grid.publishSnapshot(syncedSnapshot); } @@ -161,6 +190,8 @@ export class DataCubeGridClientServerSideDataSource assertErrorThrown(error); this.grid.dataCube.application.notificationService.notifyError(error); params.fail(); + } finally { + this.grid.dataCube.endTask(task); } } diff --git a/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridQueryBuilder.ts b/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridQueryBuilder.ts index 92502932ce..a28a6ea9ac 100644 --- a/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridQueryBuilder.ts +++ b/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridQueryBuilder.ts @@ -139,16 +139,6 @@ export function generateRowGroupingDrilldownExecutableQueryPostProcessor( // --------------------------------- PIVOT --------------------------------- // TODO: @akphi - implement this and CAST - - // --------------------------------- SELECT --------------------------------- - if (funcMap.select && drilldownValues.length < groupBy.columns.length) { - funcMap.select.parameters = [ - _cols([ - ...data.selectColumns.map((col) => _colSpec(col.name)), - _colSpec(INTERNAL__GRID_CLIENT_ROW_GROUPING_COUNT_AGG_COLUMN_ID), - ]), - ]; - } } }; } diff --git a/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridQuerySnapshotAnalyzer.tsx b/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridQuerySnapshotAnalyzer.tsx index e523414b12..edd2fcd4f5 100644 --- a/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridQuerySnapshotAnalyzer.tsx +++ b/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridQuerySnapshotAnalyzer.tsx @@ -23,7 +23,7 @@ import { DataCubeQuerySnapshotAggregateFunction, - DataCubeQuerySnapshotSortDirection, + DataCubeQuerySnapshotSortOperation, _findCol, type DataCubeQuerySnapshot, type DataCubeQuerySnapshotColumn, @@ -95,7 +95,7 @@ function _sortSpec(snapshot: DataCubeQuerySnapshot, colName: string) { } return { sort: - sortCol.direction === DataCubeQuerySnapshotSortDirection.ASCENDING + sortCol.operation === DataCubeQuerySnapshotSortOperation.ASCENDING ? GridClientSortDirection.ASCENDING : GridClientSortDirection.DESCENDING, sortIndex: sortColumns.indexOf(sortCol), @@ -161,9 +161,10 @@ export function generateGridOptionsFromSnapshot( lockPosition: true, cellStyle: { flex: 1, - 'justify-content': 'space-between', + justifyContent: 'space-between', display: 'flex', }, + sortable: false, // TODO: @akphi - we can support this in the configuration } satisfies ColDef, ...data.selectColumns.map( (col) => diff --git a/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridQuerySnapshotBuilder.ts b/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridQuerySnapshotBuilder.ts index 620c4848a4..ede914c35c 100644 --- a/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridQuerySnapshotBuilder.ts +++ b/packages/legend-application-repl/src/stores/dataCube/grid/DataCubeGridQuerySnapshotBuilder.ts @@ -24,15 +24,12 @@ import type { IServerSideGetRowsRequest } from '@ag-grid-community/core'; import { type DataCubeQuerySnapshot, - type DataCubeQuerySnapshotSortColumn, - DataCubeQuerySnapshotSortDirection, + DataCubeQuerySnapshotSortOperation, _getCol, DataCubeQuerySnapshotAggregateFunction, - type DataCubeQuerySnapshotGroupBy, } from '../core/DataCubeQuerySnapshot.js'; import { IllegalStateError, - deepEqual, guaranteeNonNullable, isNonNullable, } from '@finos/legend-shared'; @@ -68,15 +65,16 @@ export function buildQuerySnapshot( request: IServerSideGetRowsRequest, baseSnapshot: DataCubeQuerySnapshot, ): DataCubeQuerySnapshot { - let createNew = false; + const snapshot = baseSnapshot.clone(); - // --------------------------------- GROUP BY --------------------------------- + // --------------------------------- SELECT --------------------------------- + // TODO: @akphi - Implement this - let groupBy: undefined | DataCubeQuerySnapshotGroupBy = undefined; + // --------------------------------- GROUP BY --------------------------------- if (request.rowGroupCols.length) { const availableCols = baseSnapshot.stageCols('aggregation'); - groupBy = { + snapshot.data.groupBy = { columns: request.rowGroupCols.map((col) => ({ name: col.id, type: _getCol(availableCols, col.id).type, @@ -89,39 +87,21 @@ export function buildQuerySnapshot( function: _aggFunc(col.aggFunc as GridClientAggregateOperation), })), }; + } else { + snapshot.data.groupBy = undefined; } // --------------------------------- SORT --------------------------------- - let sortColumns: DataCubeQuerySnapshotSortColumn[] = []; - if (request.sortModel.length) { - const availableCols = baseSnapshot.stageCols('sort'); - sortColumns = request.sortModel.map((item) => ({ - ..._getCol(availableCols, item.colId), - direction: - item.sort === GridClientSortDirection.ASCENDING - ? DataCubeQuerySnapshotSortDirection.ASCENDING - : DataCubeQuerySnapshotSortDirection.DESCENDING, - })); - } - - // --------------------------------- SELECT --------------------------------- - // TODO: @akphi - Implement this + snapshot.data.sortColumns = request.sortModel.map((item) => ({ + ..._getCol(baseSnapshot.stageCols('sort'), item.colId), + operation: + item.sort === GridClientSortDirection.ASCENDING + ? DataCubeQuerySnapshotSortOperation.ASCENDING + : DataCubeQuerySnapshotSortOperation.DESCENDING, + })); // --------------------------------- FINALIZE --------------------------------- - if ( - !deepEqual(sortColumns, baseSnapshot.data.sortColumns) || - !deepEqual(groupBy, baseSnapshot.data.groupBy) - ) { - createNew = true; - } - - if (createNew) { - const newSnapshot = baseSnapshot.clone(); - newSnapshot.data.sortColumns = sortColumns; - newSnapshot.data.groupBy = groupBy; - return newSnapshot; - } - return baseSnapshot; + return snapshot.finalize(); } diff --git a/packages/legend-application-repl/style/_repl.scss b/packages/legend-application-repl/style/_repl.scss new file mode 100644 index 0000000000..97e89501d4 --- /dev/null +++ b/packages/legend-application-repl/style/_repl.scss @@ -0,0 +1,231 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@import url('@ag-grid-community/styles/ag-grid.css'); +@import url('@ag-grid-community/styles/ag-theme-balham.css'); + +// ---------------------------------------- NORMALIZE ---------------------------------------- + +html { + box-sizing: border-box; + font-family: Roboto, sans-serif; +} + +body { + // default font size is 12px everywhere to make the app more compact + font-size: 12px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-corner { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: #00000010; + border: 0.2rem solid transparent; + background-clip: content-box; + border-radius: 0.4rem; +} + +::-webkit-scrollbar-thumb:hover { + background: #00000020; + border: 0.2rem solid transparent; + background-clip: content-box; + border-radius: 0.4rem; +} + +::-webkit-scrollbar-thumb:window-inactive { + background: #00000005; +} + +// ---------------------------------------- RESET ---------------------------------------- + +input:focus-visible, +button:focus-visible { + outline-color: var(--tw-color-sky-600); + outline-offset: -1px; + outline-width: 1.5px; + outline-style: solid; +} + +.MuiCheckbox-root.Mui-focusVisible svg { + outline-color: var(--tw-color-sky-600); + outline-offset: -2px; + outline-width: 1.5px; + outline-style: solid; +} + +.data-cube-editor-checkbox--checked svg { + color: var(--tw-color-neutral-600); +} + +.data-cube-editor-checkbox--disabled svg { + color: var(--tw-color-neutral-300); +} + +// ---------------------------------------- CUSTOMIZE ---------------------------------------- + +.data-cube-color-picker { + .react-colorful { + width: 270px; + height: 100px; + border-radius: 2px; + border: 0.5px solid var(--tw-color-neutral-600); + } + + .react-colorful__saturation { + border-radius: 2px 2px 0 0; + } + + .react-colorful__pointer { + width: 12px; + height: 12px; + } + + .react-colorful__hue { + height: 12px; + } + + .react-colorful__last-control { + border-radius: 0 0 2px 2px; + height: 12px; + } +} + +.data-cube-color-picker--disabled { + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1' preserveAspectRatio='none' viewBox='0 0 100 100' %3E%3Cpath d='M1 0 L0 1 L99 100 L100 99' stroke-width='4' stroke='%23e5e5e5' /%3E%3C/svg%3E"); +} + +.data-cube-grid .ag-theme-balham { + --ag-input-focus-border-color: var(--tw-color-sky-600); + --ag-balham-active-color: var(--tw-color-sky-600); + --ag-range-selection-border-color: var(--tw-color-sky-600); + --ag-range-selection-highlight-color: var( + --tw-color-sky-50 + ); // color for the brief highlight animation when cells are copied + + --ag-border-color: var(--tw-color-neutral-200); + --ag-header-background-color: var(--tw-color-neutral-100); + --ag-header-height: 24px; + --ag-row-height: 20px; + --ag-font-size: 12px; + --ag-grid-size: 1px; + --ag-icon-size: 12px; + --ag-font-family: 'Roboto', sans-serif; + --ag-icon-font-color: var(--tw-color-neutral-500); + --ag-row-border-color: transparent; + --ag-header-foreground-color: var(--tw-color-black); + --ag-odd-row-background-color: var(--tw-color-white); + --ag-row-hover-color: #d4d4d415; // tailwind neutral 300 + // NOTE: These are icons taken from Tabler and URI encoded + // See https://yoksel.github.io/url-encoder/ + // See https://tablericons.com/ + // See https://github.com/tabler/tabler-icons + --ag-icon-font-code-group: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-table-column' width='12' height='12' viewBox='0 0 24 24' stroke-width='1.5' stroke='%23000000' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z' /%3E%3Cpath d='M10 10h11' /%3E%3Cpath d='M10 3v18' /%3E%3Cpath d='M9 3l-6 6' /%3E%3Cpath d='M10 7l-7 7' /%3E%3Cpath d='M10 12l-7 7' /%3E%3Cpath d='M10 17l-4 4' /%3E%3C/svg%3E"); + --ag-icon-font-code-pivot: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-table-row' width='12' height='12' viewBox='0 0 24 24' stroke-width='1.5' stroke='%23000000' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z' /%3E%3Cpath d='M9 3l-6 6' /%3E%3Cpath d='M14 3l-7 7' /%3E%3Cpath d='M19 3l-7 7' /%3E%3Cpath d='M21 6l-4 4' /%3E%3Cpath d='M3 10h18' /%3E%3Cpath d='M10 10v11' /%3E%3C/svg%3E"); + + .ag-column-drop-cell { + height: 16px; + display: flex; + align-items: center; + + .ag-column-drop-cell-text { + height: 16px; + } + } + + .ag-column-drop-empty-message.ag-column-drop-horizontal-empty-message { + font-size: 0; + } + + .ag-column-drop-list.ag-column-drop-horizontal-list[aria-label='Row Groups'] + .ag-column-drop-empty-message.ag-column-drop-horizontal-empty-message::after { + content: 'Drag columns here to set vertical pivots'; + font-size: 11px; + } + + .ag-column-drop-list.ag-column-drop-horizontal-list[aria-label='Column Labels'] + .ag-column-drop-empty-message.ag-column-drop-horizontal-empty-message::after { + content: 'Drag columns here to set horizontal pivots'; + font-size: 11px; + } + + .ag-header-cell-label .ag-sort-order { + font-size: 10px; + margin-left: 5px; + margin-right: -2px; + color: var(--tw-color-neutral-500); + font-weight: 500; + } + + .ag-menu-list { + min-width: 120px; + } + + .ag-menu-option-icon { + padding: 5px 5px 0; + } + + .ag-menu-option-part { + padding: 5px 2px; + } + + .ag-menu-option-popup-pointer { + padding: 5px 5px 5px 0; + } + + // TODO: cell/range selection color +} + +.data-cube-column-selector .ag-theme-balham { + --ag-balham-active-color: var(--tw-color-sky-600); + --ag-input-focus-border-color: var(--tw-color-sky-600); + --ag-range-selection-border-color: var(--tw-color-sky-600); + --ag-selected-row-background-color: var(--tw-color-sky-200); + --ag-border-color: var(--tw-color-white); + --ag-header-background-color: var(--tw-color-white); + --ag-header-height: 20px; + --ag-row-height: 20px; + --ag-font-size: 12px; + --ag-grid-size: 1px; + --ag-icon-size: 12px; + --ag-font-family: 'Roboto', sans-serif; + --ag-icon-font-color: var(--tw-color-neutral-500); + --ag-row-border-color: transparent; + --ag-header-foreground-color: var(--tw-color-black); + --ag-row-hover-color: #d4d4d415; // tailwind neutral 300 + --ag-header-column-separator-display: none; + + .ag-header-cell { + padding-left: 20px; + } + + // ag-grid header always comes with a big padding, we want to suppress that + .ag-header-cell:focus-visible::after { + width: 100%; + height: 100%; + top: 0; + left: 0; + } + + .ag-drag-handle.ag-row-drag { + margin-right: 5px; + } +} diff --git a/packages/legend-application-repl/style/index.scss b/packages/legend-application-repl/style/index.scss index d10f639d76..716d5b1ace 100644 --- a/packages/legend-application-repl/style/index.scss +++ b/packages/legend-application-repl/style/index.scss @@ -14,131 +14,4 @@ * limitations under the License. */ -@import url('@ag-grid-community/styles/ag-grid.css'); -@import url('@ag-grid-community/styles/ag-theme-balham.css'); - -input:focus-visible, -button:focus-visible { - outline-color: var(--tw-color-sky-600); - outline-offset: -1px; - outline-width: 1.5px; - outline-style: solid; -} - -body { - // default font size is 12px everywhere to make the app more compact - font-size: 12px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-corner { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: #00000010; - border: 0.2rem solid transparent; - background-clip: content-box; - border-radius: 0.4rem; -} - -::-webkit-scrollbar-thumb:hover { - background: #00000020; - border: 0.2rem solid transparent; - background-clip: content-box; - border-radius: 0.4rem; -} - -::-webkit-scrollbar-thumb:window-inactive { - background: #00000005; -} - -.data-cube-grid .ag-theme-balham { - --ag-input-focus-border-color: var(--tw-color-sky-600); - --ag-balham-active-color: var(--tw-color-sky-600); - --ag-range-selection-border-color: var(--tw-color-sky-600); - --ag-border-color: var(--tw-color-neutral-200); - --ag-header-background-color: var(--tw-color-neutral-100); - --ag-header-height: 24px; - --ag-row-height: 20px; - --ag-font-size: 12px; - --ag-grid-size: 1px; - --ag-icon-size: 12px; - --ag-icon-font-color: var(--tw-color-neutral-500); - --ag-row-border-color: transparent; - --ag-header-foreground-color: var(--tw-color-black); - --ag-odd-row-background-color: var(--tw-color-white); - --ag-row-hover-color: #d4d4d415; // tailwind neutral 300 - // NOTE: These are icons taken from Tabler and URI encoded - // See https://yoksel.github.io/url-encoder/ - // See https://tablericons.com/ - // See https://github.com/tabler/tabler-icons - --ag-icon-font-code-group: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-table-column' width='12' height='12' viewBox='0 0 24 24' stroke-width='1.5' stroke='%23000000' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z' /%3E%3Cpath d='M10 10h11' /%3E%3Cpath d='M10 3v18' /%3E%3Cpath d='M9 3l-6 6' /%3E%3Cpath d='M10 7l-7 7' /%3E%3Cpath d='M10 12l-7 7' /%3E%3Cpath d='M10 17l-4 4' /%3E%3C/svg%3E"); - --ag-icon-font-code-pivot: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='icon icon-tabler icon-tabler-table-row' width='12' height='12' viewBox='0 0 24 24' stroke-width='1.5' stroke='%23000000' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath stroke='none' d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14z' /%3E%3Cpath d='M9 3l-6 6' /%3E%3Cpath d='M14 3l-7 7' /%3E%3Cpath d='M19 3l-7 7' /%3E%3Cpath d='M21 6l-4 4' /%3E%3Cpath d='M3 10h18' /%3E%3Cpath d='M10 10v11' /%3E%3C/svg%3E"); - - .ag-column-drop-cell { - height: 16px; - display: flex; - align-items: center; - - .ag-column-drop-cell-text { - height: 16px; - } - } - - .ag-column-drop-empty-message.ag-column-drop-horizontal-empty-message { - font-size: 0; - } - - .ag-column-drop-list.ag-column-drop-horizontal-list[aria-label='Row Groups'] - .ag-column-drop-empty-message.ag-column-drop-horizontal-empty-message::after { - content: 'Drag columns here to set vertical pivots'; - font-size: 11px; - } - - .ag-column-drop-list.ag-column-drop-horizontal-list[aria-label='Column Labels'] - .ag-column-drop-empty-message.ag-column-drop-horizontal-empty-message::after { - content: 'Drag columns here to set horizontal pivots'; - font-size: 11px; - } - - // TODO: cell/range selection color -} - -.data-cube-column-selector .ag-theme-balham { - --ag-balham-active-color: var(--tw-color-sky-600); - --ag-input-focus-border-color: var(--tw-color-sky-600); - --ag-range-selection-border-color: var(--tw-color-sky-600); - --ag-selected-row-background-color: var(--tw-color-sky-200); - --ag-border-color: var(--tw-color-white); - --ag-header-background-color: var(--tw-color-white); - --ag-header-height: 20px; - --ag-row-height: 20px; - --ag-font-size: 12px; - --ag-grid-size: 1px; - --ag-icon-size: 12px; - --ag-icon-font-color: var(--tw-color-neutral-500); - --ag-row-border-color: transparent; - --ag-header-foreground-color: var(--tw-color-black); - --ag-row-hover-color: #d4d4d415; // tailwind neutral 300 - --ag-header-column-separator-display: none; - - .ag-header-cell { - padding-left: 20px; - } - - // ag-grid header always comes with a big padding, we want to suppress that - .ag-header-cell:focus-visible::after { - width: 100%; - height: 100%; - top: 0; - left: 0; - } - - .ag-drag-handle.ag-row-drag { - margin-right: 5px; - } -} +@forward 'repl'; diff --git a/packages/legend-application-studio/package.json b/packages/legend-application-studio/package.json index 62ca613152..b6f8ac403f 100644 --- a/packages/legend-application-studio/package.json +++ b/packages/legend-application-studio/package.json @@ -60,7 +60,7 @@ "@types/react": "18.3.3", "@types/react-dom": "18.3.0", "fast-xml-parser": "4.4.0", - "mobx": "6.12.4", + "mobx": "6.12.5", "mobx-react-lite": "4.0.7", "mobx-utils": "6.0.8", "monaco-editor": "0.50.0", diff --git a/packages/legend-application-studio/src/components/editor/ActivityBar.tsx b/packages/legend-application-studio/src/components/editor/ActivityBar.tsx index 8f67918a3e..d00fffcd47 100644 --- a/packages/legend-application-studio/src/components/editor/ActivityBar.tsx +++ b/packages/legend-application-studio/src/components/editor/ActivityBar.tsx @@ -24,7 +24,7 @@ import { import { LEGEND_STUDIO_TEST_ID } from '../../__lib__/LegendStudioTesting.js'; import { clsx, - DropdownMenu, + ControlledDropdownMenu, RepoIcon, MenuContent, MenuContentItem, @@ -115,7 +115,7 @@ export const ActivityBarMenu: React.FC = () => { return ( <>
- { } > - +
@@ -431,7 +431,7 @@ export const ActivityBar = observer(() => { > - } @@ -442,7 +442,7 @@ export const ActivityBar = observer(() => { }} > - +
); }); diff --git a/packages/legend-application-studio/src/components/editor/command-center/ProjectSearchCommand.tsx b/packages/legend-application-studio/src/components/editor/command-center/ProjectSearchCommand.tsx index 570e61419a..711c0b93bb 100644 --- a/packages/legend-application-studio/src/components/editor/command-center/ProjectSearchCommand.tsx +++ b/packages/legend-application-studio/src/components/editor/command-center/ProjectSearchCommand.tsx @@ -19,7 +19,7 @@ import { observer } from 'mobx-react-lite'; import { type SelectComponent, compareLabelFn, - DropdownMenu, + ControlledDropdownMenu, NonBlockingDialog, createFilter, CustomSelectorInput, @@ -106,7 +106,7 @@ export const ProjectSearchCommand = observer(() => { className="search-modal" >
- {
-
+ {
{currentTabState instanceof ElementEditorState && ( - { {currentTabState.generationModeState?.label ?? currentTabState.editMode}
- + )} {currentTabState instanceof EntityDiffViewState && ( - {
{currentTabState.diffMode}
-
+ )}
diff --git a/packages/legend-application-studio/src/components/editor/editor-group/GrammarTextEditor.tsx b/packages/legend-application-studio/src/components/editor/editor-group/GrammarTextEditor.tsx index 1f967930f9..0a9feb3bbe 100644 --- a/packages/legend-application-studio/src/components/editor/editor-group/GrammarTextEditor.tsx +++ b/packages/legend-application-studio/src/components/editor/editor-group/GrammarTextEditor.tsx @@ -30,7 +30,7 @@ import { MenuContentItem, PanelLoadingIndicator, CaretDownIcon, - DropdownMenu, + ControlledDropdownMenu, MenuContentItemIcon, CheckIcon, MenuContentItemLabel, @@ -1251,7 +1251,7 @@ export const GrammarTextEditor = observer(() => {
- { Advanced
- +
diff --git a/packages/legend-application-studio/src/components/editor/editor-group/ModelImporter.tsx b/packages/legend-application-studio/src/components/editor/editor-group/ModelImporter.tsx index 841b72ee6b..d2d2b519ff 100644 --- a/packages/legend-application-studio/src/components/editor/editor-group/ModelImporter.tsx +++ b/packages/legend-application-studio/src/components/editor/editor-group/ModelImporter.tsx @@ -25,7 +25,7 @@ import { } from '../../../stores/editor/editor-state/ModelImporterState.js'; import { prettyCONSTName } from '@finos/legend-shared'; import { - DropdownMenu, + ControlledDropdownMenu, MenuContent, MenuContentItem, CaretDownIcon, @@ -230,7 +230,7 @@ export const ModelImporter = observer(() => {
- @@ -317,7 +317,7 @@ export const ModelImporter = observer(() => {
-
+ {modelImportEditorState.allowHardReplace && (
- - + diff --git a/packages/legend-application-studio/src/components/editor/editor-group/connection-editor/post-processor-editor/MapperPostProcessorEditor.tsx b/packages/legend-application-studio/src/components/editor/editor-group/connection-editor/post-processor-editor/MapperPostProcessorEditor.tsx index 0e80205968..368b9908c5 100644 --- a/packages/legend-application-studio/src/components/editor/editor-group/connection-editor/post-processor-editor/MapperPostProcessorEditor.tsx +++ b/packages/legend-application-studio/src/components/editor/editor-group/connection-editor/post-processor-editor/MapperPostProcessorEditor.tsx @@ -24,7 +24,7 @@ import { ContextMenu, MenuContent, MenuContentItem, - DropdownMenu, + ControlledDropdownMenu, PanelListSelectorItem, BlankPanelContent, ResizablePanelSplitterLine, @@ -137,7 +137,7 @@ export const MapperPostProcessorEditor = observer( - - + diff --git a/packages/legend-application-studio/src/components/editor/editor-group/data-editor/DataElementEditor.tsx b/packages/legend-application-studio/src/components/editor/editor-group/data-editor/DataElementEditor.tsx index 93d57bbdf3..bfc8b830b3 100644 --- a/packages/legend-application-studio/src/components/editor/editor-group/data-editor/DataElementEditor.tsx +++ b/packages/legend-application-studio/src/components/editor/editor-group/data-editor/DataElementEditor.tsx @@ -24,7 +24,7 @@ import { import { CaretDownIcon, clsx, - DropdownMenu, + ControlledDropdownMenu, InfoCircleIcon, LockIcon, MenuContent, @@ -123,7 +123,7 @@ export const ExternalFormatDataEditor = observer(
-
- +
diff --git a/packages/legend-application-studio/src/components/editor/editor-group/data-editor/EmbeddedDataEditor.tsx b/packages/legend-application-studio/src/components/editor/editor-group/data-editor/EmbeddedDataEditor.tsx index d35b50ec75..fd6c940ac9 100644 --- a/packages/legend-application-studio/src/components/editor/editor-group/data-editor/EmbeddedDataEditor.tsx +++ b/packages/legend-application-studio/src/components/editor/editor-group/data-editor/EmbeddedDataEditor.tsx @@ -21,7 +21,7 @@ import { CaretDownIcon, clsx, CustomSelectorInput, - DropdownMenu, + ControlledDropdownMenu, LockIcon, LongArrowRightIcon, MenuContent, @@ -122,7 +122,7 @@ export const ExternalFormatDataEditor = observer( > -
- +
diff --git a/packages/legend-application-studio/src/components/editor/editor-group/function-activator/FunctionEditor.tsx b/packages/legend-application-studio/src/components/editor/editor-group/function-activator/FunctionEditor.tsx index 5a6a56fd44..1e077e052f 100644 --- a/packages/legend-application-studio/src/components/editor/editor-group/function-activator/FunctionEditor.tsx +++ b/packages/legend-application-studio/src/components/editor/editor-group/function-activator/FunctionEditor.tsx @@ -51,7 +51,7 @@ import { ModalBody, ModalFooter, CaretDownIcon, - DropdownMenu, + ControlledDropdownMenu, BlankPanelContent, MenuContent, MenuContentItem, @@ -1377,7 +1377,7 @@ export const FunctionEditor = observer(() => { Run
- { }} > - + )}
diff --git a/packages/legend-application-studio/src/components/editor/editor-group/mapping-editor/MappingExecutionBuilder.tsx b/packages/legend-application-studio/src/components/editor/editor-group/mapping-editor/MappingExecutionBuilder.tsx index 91738f4646..ac7dee2c1b 100644 --- a/packages/legend-application-studio/src/components/editor/editor-group/mapping-editor/MappingExecutionBuilder.tsx +++ b/packages/legend-application-studio/src/components/editor/editor-group/mapping-editor/MappingExecutionBuilder.tsx @@ -31,7 +31,7 @@ import { FlaskIcon, ResizablePanelSplitterLine, compareLabelFn, - DropdownMenu, + ControlledDropdownMenu, MenuContent, MenuContentItem, CaretDownIcon, @@ -560,7 +560,7 @@ const RelationalMappingExecutionInputDataTypeSelector = observer( }; return ( -
- + ); }, ); @@ -819,7 +819,7 @@ export const MappingExecutionBuilder = observer( Run Query
- - + )}
diff --git a/packages/legend-application-studio/src/components/editor/editor-group/mapping-editor/MappingTestableEditor.tsx b/packages/legend-application-studio/src/components/editor/editor-group/mapping-editor/MappingTestableEditor.tsx index 4aa475cb18..ca87fd08f0 100644 --- a/packages/legend-application-studio/src/components/editor/editor-group/mapping-editor/MappingTestableEditor.tsx +++ b/packages/legend-application-studio/src/components/editor/editor-group/mapping-editor/MappingTestableEditor.tsx @@ -42,7 +42,7 @@ import { compareLabelFn, CustomSelectorInput, Dialog, - DropdownMenu, + ControlledDropdownMenu, MenuContent, MenuContentItem, Modal, @@ -704,7 +704,7 @@ const MappingTestSuiteQueryEditor = observer( Edit Query
- @@ -728,7 +728,7 @@ const MappingTestSuiteQueryEditor = observer( }} > - +
diff --git a/packages/legend-application-studio/src/components/editor/editor-group/mapping-editor/legacy/DEPRECATED__MappingTestEditor.tsx b/packages/legend-application-studio/src/components/editor/editor-group/mapping-editor/legacy/DEPRECATED__MappingTestEditor.tsx index 62fe845a2d..aaf830bd16 100644 --- a/packages/legend-application-studio/src/components/editor/editor-group/mapping-editor/legacy/DEPRECATED__MappingTestEditor.tsx +++ b/packages/legend-application-studio/src/components/editor/editor-group/mapping-editor/legacy/DEPRECATED__MappingTestEditor.tsx @@ -34,7 +34,7 @@ import { ResizablePanel, ResizablePanelSplitter, ResizablePanelSplitterLine, - DropdownMenu, + ControlledDropdownMenu, MenuContent, MenuContentItem, CaretDownIcon, @@ -274,7 +274,7 @@ const MappingTestQueryEditor = observer( Edit Query
- @@ -298,7 +298,7 @@ const MappingTestQueryEditor = observer( }} > - +
@@ -434,7 +434,7 @@ const RelationalMappingTestInputDataTypeSelector = observer( relationalInputData_setInputType(inputDataState.inputData, val); }; return ( -
- + ); }, ); @@ -814,7 +814,7 @@ export const DEPRECATED__MappingTestEditor = observer( Run Test
- - + )}
diff --git a/packages/legend-application-studio/src/components/editor/editor-group/project-configuration-editor/ProjectDependencyEditor.tsx b/packages/legend-application-studio/src/components/editor/editor-group/project-configuration-editor/ProjectDependencyEditor.tsx index 13d2baa582..e033bde7a8 100644 --- a/packages/legend-application-studio/src/components/editor/editor-group/project-configuration-editor/ProjectDependencyEditor.tsx +++ b/packages/legend-application-studio/src/components/editor/editor-group/project-configuration-editor/ProjectDependencyEditor.tsx @@ -27,7 +27,7 @@ import { CustomSelectorInput, TimesIcon, Dialog, - DropdownMenu, + ControlledDropdownMenu, CaretDownIcon, MenuContentItem, MenuContent, @@ -932,7 +932,7 @@ const ProjectVersionDependencyEditor = observer( !applicationStore.layoutService.TEMPORARY__isLightColorThemeEnabled } /> - @@ -958,7 +958,7 @@ const ProjectVersionDependencyEditor = observer( } > Go to... - + - @@ -336,7 +336,7 @@ export const ServiceExecutionQueryEditor = observer( }} > - +
{executionState.isRunningQuery ? ( @@ -366,7 +366,7 @@ export const ServiceExecutionQueryEditor = observer( Run Query
- - + )}
-
- +
diff --git a/packages/legend-application-studio/src/components/editor/editor-group/service-editor/testable/ServiceTestDataEditor.tsx b/packages/legend-application-studio/src/components/editor/editor-group/service-editor/testable/ServiceTestDataEditor.tsx index f1ba81e319..e689f2358e 100644 --- a/packages/legend-application-studio/src/components/editor/editor-group/service-editor/testable/ServiceTestDataEditor.tsx +++ b/packages/legend-application-studio/src/components/editor/editor-group/service-editor/testable/ServiceTestDataEditor.tsx @@ -21,7 +21,7 @@ import { ContextMenu, CustomSelectorInput, Dialog, - DropdownMenu, + ControlledDropdownMenu, InfoCircleIcon, MaskIcon, MenuContent, @@ -549,7 +549,7 @@ export const ConnectionTestDataEditor = observer( Generate
- @@ -581,7 +581,7 @@ export const ConnectionTestDataEditor = observer( }} > - +
- {
-
+
{ )} {!editorStore.disableGraphEditing && ( - { }} > - + )}
{(globalTestRunnerState.testableStates ?? []).map((testableState) => { diff --git a/packages/legend-application-studio/src/components/project-reviewer/ProjectReviewerPanel.tsx b/packages/legend-application-studio/src/components/project-reviewer/ProjectReviewerPanel.tsx index 67e765f1da..70995ba71c 100644 --- a/packages/legend-application-studio/src/components/project-reviewer/ProjectReviewerPanel.tsx +++ b/packages/legend-application-studio/src/components/project-reviewer/ProjectReviewerPanel.tsx @@ -17,7 +17,7 @@ import { observer } from 'mobx-react-lite'; import { clsx, - DropdownMenu, + ControlledDropdownMenu, ContextMenu, TimesIcon, MenuContentItem, @@ -154,7 +154,7 @@ export const ProjectReviewerPanel = observer(() => {
{currentTabState instanceof EntityDiffViewState && ( - {
{currentTabState.diffMode}
-
+ )}
diff --git a/packages/legend-application/package.json b/packages/legend-application/package.json index b0e7d82e6a..dd59060934 100644 --- a/packages/legend-application/package.json +++ b/packages/legend-application/package.json @@ -51,7 +51,7 @@ "@types/react-dom": "18.3.0", "@types/react-router-dom": "5.3.3", "history": "5.3.0", - "mobx": "6.12.4", + "mobx": "6.12.5", "mobx-react-lite": "4.0.7", "react": "18.3.1", "react-dnd": "16.0.1", diff --git a/packages/legend-art/package.json b/packages/legend-art/package.json index 6beaeaafe2..6c425975ac 100644 --- a/packages/legend-art/package.json +++ b/packages/legend-art/package.json @@ -26,7 +26,8 @@ "./markdown/test/MockedReactMarkdown.js": "./lib/markdown/__test-utils__/MockedReactMarkdown.js", "./markdown/test/MockedRemarkGFM.js": "./lib/markdown/__test-utils__/MockedRemarkGFM.js", "./lib/index.css": "./lib/index.css", - "./lib/normalize.css": "./lib/normalize.css" + "./lib/normalize.css": "./lib/normalize.css", + "./lib/fonts.css": "./lib/fonts.css" }, "module": "lib/index.js", "types": "lib/index.d.ts", @@ -50,19 +51,24 @@ "@emotion/react": "11.11.4", "@emotion/styled": "11.11.5", "@finos/legend-shared": "workspace:*", + "@fontsource/jetbrains-mono": "5.0.20", "@fontsource/raleway": "5.0.19", "@fontsource/roboto": "5.0.13", "@fontsource/roboto-condensed": "5.0.16", "@fontsource/roboto-mono": "5.0.18", - "@mui/material": "5.15.20", + "@fontsource/roboto-serif": "5.0.13", + "@fontsource/ubuntu-mono": "5.0.20", + "@mui/material": "5.15.21", "@types/react": "18.3.3", "@types/react-select": "4.0.18", "@types/react-window": "1.8.8", "clsx": "2.1.1", + "color-parse": "2.0.2", "mermaid": "10.9.1", - "mobx": "6.12.4", + "mobx": "6.12.5", "mobx-react-lite": "4.0.7", "react": "18.3.1", + "react-colorful": "5.6.1", "react-dnd": "16.0.1", "react-dnd-html5-backend": "16.0.1", "react-dom": "18.3.1", diff --git a/packages/legend-art/src/button/Button.tsx b/packages/legend-art/src/button/Button.tsx index 9604518aea..7a62f2f2ca 100644 --- a/packages/legend-art/src/button/Button.tsx +++ b/packages/legend-art/src/button/Button.tsx @@ -66,3 +66,5 @@ export const Button: React.FC<{ ); }; + +export { Checkbox, type CheckboxProps } from '@mui/material'; diff --git a/packages/legend-application-repl/src/stores/dataCube/DataCubeDefaultConfig.ts b/packages/legend-art/src/color/ColorPicker.ts similarity index 86% rename from packages/legend-application-repl/src/stores/dataCube/DataCubeDefaultConfig.ts rename to packages/legend-art/src/color/ColorPicker.ts index eafd69f266..874554f806 100644 --- a/packages/legend-application-repl/src/stores/dataCube/DataCubeDefaultConfig.ts +++ b/packages/legend-art/src/color/ColorPicker.ts @@ -14,4 +14,8 @@ * limitations under the License. */ -export const DATA_CUBE_DEFAULT_REPORT_NAME = 'New Report'; +export { + HexColorPicker, + HexAlphaColorPicker, + HexColorInput, +} from 'react-colorful'; diff --git a/packages/legend-art/src/color/TailwindCSSPalette.ts b/packages/legend-art/src/color/TailwindCSSPalette.ts index 98008d6c9e..c4ba61dc4c 100644 --- a/packages/legend-art/src/color/TailwindCSSPalette.ts +++ b/packages/legend-art/src/color/TailwindCSSPalette.ts @@ -16,9 +16,13 @@ // Clone Tailwind CSS color palette to use for component code // Source: https://github.com/tailwindlabs/tailwindcss/blob/master/src/public/colors.js + +export type TailwindCSSColorScaleKey = keyof typeof TailwindCSSPalette; +export type TailwindCSSColorScale = (typeof TailwindCSSPalette)['slate']; + export const TailwindCSSPalette = { - black: '#000', - white: '#fff', + black: '#000000', + white: '#ffffff', slate: { 50: '#f8fafc', 100: '#f1f5f9', diff --git a/packages/legend-art/src/icon/DataCubeIcon.tsx b/packages/legend-art/src/icon/DataCubeIcon.tsx index d00ffd80d8..67119e8691 100644 --- a/packages/legend-art/src/icon/DataCubeIcon.tsx +++ b/packages/legend-art/src/icon/DataCubeIcon.tsx @@ -18,8 +18,20 @@ // merging them in the main icon registry, we should review them import { GoBrowser, GoPin, GoX } from 'react-icons/go'; -import { MdOutlineInsertPageBreak } from 'react-icons/md'; -import { PiMouseScroll } from 'react-icons/pi'; +import { GrCheckbox, GrCheckboxSelected } from 'react-icons/gr'; +import { + MdOutlineFormatBold, + MdOutlineFormatItalic, + MdOutlineStrikethroughS, + MdOutlineFormatUnderlined, + MdOutlineInsertPageBreak, +} from 'react-icons/md'; +import { + PiMouseScroll, + PiTextAlignCenter, + PiTextAlignLeft, + PiTextAlignRight, +} from 'react-icons/pi'; import { TbBook, TbCube, @@ -44,17 +56,33 @@ import { TbChevronLeft, TbChevronRight, TbSearch, + TbAlertTriangle, + TbExclamationCircle, + TbCaretDownFilled, + TbLoader2, + TbSettingsBolt, + TbSettings, } from 'react-icons/tb'; export const DataCubeIcon = { + AdvancedSettings: TbSettingsBolt, + CaretDown: TbCaretDownFilled, + Checkbox: GrCheckbox, + CheckboxSelected: GrCheckboxSelected, ChevronLeft: TbChevronLeft, ChevronRight: TbChevronRight, Code: TbCode, Cube: TbCube, Documentation: TbBook, + FontBold: MdOutlineFormatBold, + FontItalic: MdOutlineFormatItalic, + FontUnderlined: MdOutlineFormatUnderlined, + FontStrikethrough: MdOutlineStrikethroughS, + Loader: TbLoader2, Note: TbNote, Pin: GoPin, Search: TbSearch, + Settings: TbSettings, Table: TbTable, TableColumn: TbFreezeColumn, TableColumnOptions__Settings: TbSettingsFilled, @@ -73,6 +101,11 @@ export const DataCubeIcon = { TableRowColumn: TbFreezeRowColumn, TableScroll: PiMouseScroll, TableSort: TbTableDown, + TextAlignLeft: PiTextAlignLeft, + TextAlignCenter: PiTextAlignCenter, + TextAlignRight: PiTextAlignRight, Window: GoBrowser, X: GoX, + Warning: TbAlertTriangle, + WarningCircle: TbExclamationCircle, }; diff --git a/packages/legend-art/src/index.ts b/packages/legend-art/src/index.ts index cb4295a538..232b9656ad 100644 --- a/packages/legend-art/src/index.ts +++ b/packages/legend-art/src/index.ts @@ -42,6 +42,7 @@ export * from './dialog/Modal.js'; export * from './divider/Divider.js'; +export * from './menu/BaseMenu.js'; export * from './menu/MenuContent.js'; export * from './menu/ContextMenu.js'; export * from './menu/DropdownMenu.js'; @@ -78,3 +79,6 @@ export * from './utils/ComponentUtils.js'; export * from './markdown/MarkdownTextViewer.js'; export * from './Interaction.js'; + +export * from './color/TailwindCSSPalette.js'; +export * from './color/ColorPicker.js'; diff --git a/packages/legend-art/src/menu/BaseMenu.tsx b/packages/legend-art/src/menu/BaseMenu.tsx index ecf6c51380..a902e53bfa 100644 --- a/packages/legend-art/src/menu/BaseMenu.tsx +++ b/packages/legend-art/src/menu/BaseMenu.tsx @@ -14,9 +14,13 @@ * limitations under the License. */ -import { type MenuProps as MuiMenuProps, Menu as MuiMenu } from '@mui/material'; +import { + type MenuProps as MuiMenuProps, + Menu as MuiMenu, + MenuItem as MuiMenuItem, +} from '@mui/material'; -export const BaseMenu: React.FC = (props) => { +export const Menu: React.FC = (props) => { const { children, classes, ...otherProps } = props; return ( @@ -33,3 +37,5 @@ export const BaseMenu: React.FC = (props) => { ); }; + +export { MuiMenuItem as BaseMenuItem, MuiMenu as BaseMenu }; diff --git a/packages/legend-art/src/menu/ContextMenu.tsx b/packages/legend-art/src/menu/ContextMenu.tsx index 941494815e..ae5052c2a9 100644 --- a/packages/legend-art/src/menu/ContextMenu.tsx +++ b/packages/legend-art/src/menu/ContextMenu.tsx @@ -16,7 +16,7 @@ import { useState, useRef } from 'react'; import type { MenuProps as MuiMenuProps } from '@mui/material'; -import { BaseMenu } from './BaseMenu.js'; +import { Menu } from './BaseMenu.js'; export const ContextMenu: React.FC<{ children: React.ReactNode; @@ -100,7 +100,7 @@ export const ContextMenu: React.FC<{ onContextMenu={onContextMenu} > {children} - {isOpen && content} - +
); }; diff --git a/packages/legend-art/src/menu/DropdownMenu.tsx b/packages/legend-art/src/menu/DropdownMenu.tsx index bf05d68205..46f52c39a0 100644 --- a/packages/legend-art/src/menu/DropdownMenu.tsx +++ b/packages/legend-art/src/menu/DropdownMenu.tsx @@ -15,10 +15,10 @@ */ import { useEffect, useRef, useState } from 'react'; -import type { MenuProps as MuiMenuProps } from '@mui/material'; -import { BaseMenu } from './BaseMenu.js'; +import type { MenuItemProps, MenuProps as MuiMenuProps } from '@mui/material'; +import { BaseMenu, BaseMenuItem, Menu } from './BaseMenu.js'; -export const DropdownMenu: React.FC<{ +export const ControlledDropdownMenu: React.FC<{ children: React.ReactNode; open?: boolean | undefined; menuProps?: Partial | undefined; @@ -28,6 +28,7 @@ export const DropdownMenu: React.FC<{ title?: string | undefined; onOpen?: (() => void) | undefined; onClose?: (() => void) | undefined; + useCapture?: boolean | undefined; }> = (props) => { const { open, @@ -39,6 +40,7 @@ export const DropdownMenu: React.FC<{ disabled, onClose, onOpen, + useCapture, } = props; const triggerRef = useRef(null); const [anchorEl, setAnchorEl] = useState(null); @@ -48,12 +50,7 @@ export const DropdownMenu: React.FC<{ if (disabled) { return; } - if (anchorEl) { - // if the trigger is clicked and the menu is already opened, close it - onClose?.(); - setAnchorEl(null); - } else if (triggerRef.current) { - // if the trigger is clicked, open the dropdown menu + if (triggerRef.current) { onOpen?.(); setAnchorEl(triggerRef.current); } @@ -73,30 +70,115 @@ export const DropdownMenu: React.FC<{ }, [anchorEl, open]); return ( - + { + onClose?.(); + setAnchorEl(null); + }} {...menuProps} > {content} - - + + ); }; + +export function useDropdownMenu() { + const [anchorEl, setAnchorEl] = useState(null); + return [ + (event: React.MouseEvent) => setAnchorEl(event.currentTarget), + () => setAnchorEl(null), + { + anchorEl, + onClose: () => setAnchorEl(null), + }, + ] as const; +} + +export type DropdownMenuProps = { + children: React.ReactNode; + anchorEl: Element | null; + onClose: () => void; + className?: string | undefined; + menuProps?: Partial | undefined; +}; + +export function DropdownMenu(props: DropdownMenuProps) { + const { menuProps, children, onClose, anchorEl } = props; + + if (!anchorEl) { + return null; + } + return ( + + {children} + + ); +} + +export type DropdownMenuItemProps = MenuItemProps; + +export function DropdownMenuItem(props: DropdownMenuItemProps) { + return ( + + ); +} diff --git a/packages/legend-art/style/fonts.scss b/packages/legend-art/style/fonts.scss new file mode 100644 index 0000000000..fb7156761d --- /dev/null +++ b/packages/legend-art/style/fonts.scss @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Used by the application +@import url('@fontsource/roboto/100.css'); +// @import url('@fontsource/roboto/200.css'); +@import url('@fontsource/roboto/300.css'); +@import url('@fontsource/roboto/400.css'); +@import url('@fontsource/roboto/500.css'); +// @import url('@fontsource/roboto/600.css'); +@import url('@fontsource/roboto/700.css'); +// @import url('@fontsource/roboto/800.css'); +@import url('@fontsource/roboto/900.css'); + +@import url('@fontsource/roboto-mono/100.css'); +@import url('@fontsource/roboto-mono/200.css'); +@import url('@fontsource/roboto-mono/300.css'); +@import url('@fontsource/roboto-mono/400.css'); +@import url('@fontsource/roboto-mono/500.css'); +@import url('@fontsource/roboto-mono/600.css'); +@import url('@fontsource/roboto-mono/700.css'); +// @import url('@fontsource/roboto-mono/800.css'); +// @import url('@fontsource/roboto-mono/900.css'); + +// Used for rendering the grid per configuration +@import url('@fontsource/roboto-condensed/400.css'); +@import url('@fontsource/roboto-condensed/700.css'); + +@import url('@fontsource/roboto-serif/400.css'); +@import url('@fontsource/roboto-serif/700.css'); + +@import url('@fontsource/ubuntu-mono/400.css'); +@import url('@fontsource/ubuntu-mono/700.css'); + +@import url('@fontsource/jetbrains-mono/400.css'); +@import url('@fontsource/jetbrains-mono/700.css'); diff --git a/packages/legend-dev-utils/WebpackConfigUtils.js b/packages/legend-dev-utils/WebpackConfigUtils.js index 483efb59f7..20740daf46 100644 --- a/packages/legend-dev-utils/WebpackConfigUtils.js +++ b/packages/legend-dev-utils/WebpackConfigUtils.js @@ -45,7 +45,7 @@ export const getBaseWebpackConfig = ( arg, dirname, { babelConfigPath }, - isRelativePathSupported, + useRelativePath, ) => { if (!dirname) { throw new Error(`\`dirname\` is required to build Webpack config`); @@ -126,7 +126,7 @@ export const getBaseWebpackConfig = ( use: [ { loader: MiniCssExtractPlugin.loader, - options: isRelativePathSupported + options: useRelativePath ? { publicPath: '../', } @@ -260,7 +260,7 @@ export const getWebAppBaseWebpackConfig = ( { babelConfigPath, }, - appConfig.isRelativePathSupported, + appConfig.useRelativePath, ); validateAppConfig(appConfig, dirname); @@ -288,7 +288,7 @@ export const getWebAppBaseWebpackConfig = ( }`, publicPath: isEnvDevelopment ? '/' - : appConfig.isRelativePathSupported + : appConfig.useRelativePath ? './' : appConfig.baseUrl, filename: `${staticPath}/${ diff --git a/packages/legend-dev-utils/package.json b/packages/legend-dev-utils/package.json index 7743ff3f0c..e37eb83003 100644 --- a/packages/legend-dev-utils/package.json +++ b/packages/legend-dev-utils/package.json @@ -83,7 +83,7 @@ "mini-css-extract-plugin": "2.9.0", "monaco-editor": "0.50.0", "monaco-editor-webpack-plugin": "7.1.0", - "postcss": "8.4.38", + "postcss": "8.4.39", "postcss-loader": "8.1.1", "react-refresh": "0.14.2", "resolve": "1.22.8", diff --git a/packages/legend-extension-assortment/package.json b/packages/legend-extension-assortment/package.json index eafc41ac41..10761f27c8 100644 --- a/packages/legend-extension-assortment/package.json +++ b/packages/legend-extension-assortment/package.json @@ -49,7 +49,7 @@ "@finos/legend-lego": "workspace:*", "@finos/legend-shared": "workspace:*", "@types/react": "18.3.3", - "mobx": "6.12.4", + "mobx": "6.12.5", "mobx-react-lite": "4.0.7", "react": "18.3.1", "serializr": "3.0.2" diff --git a/packages/legend-extension-dsl-data-space-studio/package.json b/packages/legend-extension-dsl-data-space-studio/package.json index a5056a406e..b7a4707b09 100644 --- a/packages/legend-extension-dsl-data-space-studio/package.json +++ b/packages/legend-extension-dsl-data-space-studio/package.json @@ -50,7 +50,7 @@ "@finos/legend-server-depot": "workspace:*", "@finos/legend-server-sdlc": "workspace:*", "@finos/legend-shared": "workspace:*", - "mobx": "6.12.4", + "mobx": "6.12.5", "mobx-react-lite": "4.0.7" }, "devDependencies": { diff --git a/packages/legend-extension-dsl-data-space/package.json b/packages/legend-extension-dsl-data-space/package.json index 5b1fa0d674..e48778551d 100644 --- a/packages/legend-extension-dsl-data-space/package.json +++ b/packages/legend-extension-dsl-data-space/package.json @@ -54,7 +54,7 @@ "@finos/legend-shared": "workspace:*", "@finos/legend-storage": "workspace:*", "@types/react": "18.3.3", - "mobx": "6.12.4", + "mobx": "6.12.5", "mobx-react-lite": "4.0.7", "react": "18.3.1", "react-dom": "18.3.1", diff --git a/packages/legend-extension-dsl-data-space/src/components/DataSpaceDiagramViewer.tsx b/packages/legend-extension-dsl-data-space/src/components/DataSpaceDiagramViewer.tsx index 3fb098446c..f10e86a1bd 100644 --- a/packages/legend-extension-dsl-data-space/src/components/DataSpaceDiagramViewer.tsx +++ b/packages/legend-extension-dsl-data-space/src/components/DataSpaceDiagramViewer.tsx @@ -23,7 +23,7 @@ import { ContextMenu, CustomSelectorInput, DescriptionIcon, - DropdownMenu, + ControlledDropdownMenu, MenuContent, MenuContentDivider, MenuContentItem, @@ -432,7 +432,7 @@ const DataSpaceDiagramViewerHeader = observer(
-
- + )}
diff --git a/packages/legend-extension-dsl-data-space/src/components/DataSpaceModelsDocumentation.tsx b/packages/legend-extension-dsl-data-space/src/components/DataSpaceModelsDocumentation.tsx index fc88f829ac..7d3a5bbd33 100644 --- a/packages/legend-extension-dsl-data-space/src/components/DataSpaceModelsDocumentation.tsx +++ b/packages/legend-extension-dsl-data-space/src/components/DataSpaceModelsDocumentation.tsx @@ -22,7 +22,7 @@ import { ChevronRightIcon, ClockIcon, CogIcon, - DropdownMenu, + ControlledDropdownMenu, FilterIcon, InfoCircleIcon, MenuContent, @@ -281,7 +281,7 @@ const ElementContentCellRenderer = observer(
- - +
); @@ -326,7 +326,7 @@ const ElementContentCellRenderer = observer(
- - +
); @@ -368,7 +368,7 @@ const ElementContentCellRenderer = observer(
- - +
); @@ -454,7 +454,7 @@ const SubElementDocContentCellRenderer = observer(
- - +
); @@ -496,7 +496,7 @@ const SubElementDocContentCellRenderer = observer(
- - +
); diff --git a/packages/legend-extension-dsl-data-space/src/components/DataSpaceViewer.tsx b/packages/legend-extension-dsl-data-space/src/components/DataSpaceViewer.tsx index 8e52d864a7..eee24e7aba 100644 --- a/packages/legend-extension-dsl-data-space/src/components/DataSpaceViewer.tsx +++ b/packages/legend-extension-dsl-data-space/src/components/DataSpaceViewer.tsx @@ -18,7 +18,7 @@ import { observer } from 'mobx-react-lite'; import { CaretDownIcon, CaretUpIcon, - DropdownMenu, + ControlledDropdownMenu, MenuContent, MenuContentDivider, MenuContentItem, @@ -88,7 +88,7 @@ const DataSpaceHeader = observer( )}
-
- - + - +
diff --git a/packages/legend-extension-dsl-data-space/src/components/DataSpaceViewerActivityBar.tsx b/packages/legend-extension-dsl-data-space/src/components/DataSpaceViewerActivityBar.tsx index 8ba02c8566..a47f274594 100644 --- a/packages/legend-extension-dsl-data-space/src/components/DataSpaceViewerActivityBar.tsx +++ b/packages/legend-extension-dsl-data-space/src/components/DataSpaceViewerActivityBar.tsx @@ -32,7 +32,7 @@ import { MenuContent, MenuContentItem, MenuIcon, - DropdownMenu, + ControlledDropdownMenu, DataReadyIcon, SparkleIcon, } from '@finos/legend-art'; @@ -55,7 +55,7 @@ const ActivityBarMenu = observer( return ( <>
- - +
); diff --git a/packages/legend-extension-dsl-data-space/src/components/query-builder/DataSpaceQueryBuilder.tsx b/packages/legend-extension-dsl-data-space/src/components/query-builder/DataSpaceQueryBuilder.tsx index 7665e9129c..e0fb5b3e5f 100644 --- a/packages/legend-extension-dsl-data-space/src/components/query-builder/DataSpaceQueryBuilder.tsx +++ b/packages/legend-extension-dsl-data-space/src/components/query-builder/DataSpaceQueryBuilder.tsx @@ -17,7 +17,7 @@ import { CustomSelectorInput, createFilter, - DropdownMenu, + ControlledDropdownMenu, MenuContentItem, MenuContent, MenuContentItemIcon, @@ -229,7 +229,7 @@ const DataSpaceQueryBuilderSetupPanelContent = observer(
- - +
diff --git a/packages/legend-extension-dsl-diagram/package.json b/packages/legend-extension-dsl-diagram/package.json index 1b81678583..0472447bd4 100644 --- a/packages/legend-extension-dsl-diagram/package.json +++ b/packages/legend-extension-dsl-diagram/package.json @@ -54,7 +54,7 @@ "@finos/legend-shared": "workspace:*", "@finos/legend-storage": "workspace:*", "@types/react": "18.3.3", - "mobx": "6.12.4", + "mobx": "6.12.5", "mobx-react-lite": "4.0.7", "react": "18.3.1", "react-dnd": "16.0.1", diff --git a/packages/legend-extension-dsl-diagram/src/components/studio/DiagramEditor.tsx b/packages/legend-extension-dsl-diagram/src/components/studio/DiagramEditor.tsx index 709d02504a..03e2c7fbaa 100644 --- a/packages/legend-extension-dsl-diagram/src/components/studio/DiagramEditor.tsx +++ b/packages/legend-extension-dsl-diagram/src/components/studio/DiagramEditor.tsx @@ -44,7 +44,7 @@ import { createFilter, CustomSelectorInput, KeyboardIcon, - DropdownMenu, + ControlledDropdownMenu, MenuContent, MenuContentDivider, MenuContentItem, @@ -1401,7 +1401,7 @@ const DiagramEditorHeader = observer(
-
- +
)} - {
-
+ {isPreviewSupported ? ( - {executionPlanState.viewMode} - +
{executionPlanState.viewMode === EXECUTION_PLAN_VIEW_MODE.JSON && diff --git a/packages/legend-query-builder/src/components/explorer/QueryBuilderExplorerPanel.tsx b/packages/legend-query-builder/src/components/explorer/QueryBuilderExplorerPanel.tsx index 9940025e1e..d51aa3afff 100644 --- a/packages/legend-query-builder/src/components/explorer/QueryBuilderExplorerPanel.tsx +++ b/packages/legend-query-builder/src/components/explorer/QueryBuilderExplorerPanel.tsx @@ -26,7 +26,7 @@ import { Dialog, TreeView, BlankPanelContent, - DropdownMenu, + ControlledDropdownMenu, ContextMenu, MenuContent, MenuContentItem, @@ -928,7 +928,7 @@ export const QueryBuilderExplorerPanel = observer( - - + {propertySearchPanelState.isSearchPanelOpen && ( )}
- - +
diff --git a/packages/legend-query-builder/src/components/fetch-structure/QueryBuilderPostFilterPanel.tsx b/packages/legend-query-builder/src/components/fetch-structure/QueryBuilderPostFilterPanel.tsx index 84c11b2b32..e3bb97bf28 100644 --- a/packages/legend-query-builder/src/components/fetch-structure/QueryBuilderPostFilterPanel.tsx +++ b/packages/legend-query-builder/src/components/fetch-structure/QueryBuilderPostFilterPanel.tsx @@ -24,7 +24,7 @@ import { ChevronRightIcon, clsx, ContextMenu, - DropdownMenu, + ControlledDropdownMenu, FilledTriangleIcon, MenuContent, MenuContentItem, @@ -525,7 +525,7 @@ const QueryBuilderPostFilterConditionEditor = observer( colState={node.condition.leftConditionValue} />
- - + {renderRightVal()} @@ -1081,7 +1081,7 @@ const QueryBuilderPostFilterPanelContent = observer( )}
- - +
@@ -1196,7 +1196,7 @@ const QueryBuilderPostFilterPanelContent = observer( className="query-builder-post-filter-tree__free-drop-zone" label="Add post-filter to main group" > - <>{' '} + <> )} diff --git a/packages/legend-query-builder/src/components/fetch-structure/QueryBuilderTDSPanel.tsx b/packages/legend-query-builder/src/components/fetch-structure/QueryBuilderTDSPanel.tsx index 883617f826..8fab099855 100644 --- a/packages/legend-query-builder/src/components/fetch-structure/QueryBuilderTDSPanel.tsx +++ b/packages/legend-query-builder/src/components/fetch-structure/QueryBuilderTDSPanel.tsx @@ -21,7 +21,7 @@ import { BlankPanelPlaceholder, CalculatorIcon, TimesIcon, - DropdownMenu, + ControlledDropdownMenu, MenuContent, MenuContentItem, CaretDownIcon, @@ -1044,7 +1044,7 @@ const QueryBuilderProjectionColumnEditor = observer( )} - - +
diff --git a/packages/legend-query-builder/src/components/fetch-structure/QueryBuilderTDSWindowPanel.tsx b/packages/legend-query-builder/src/components/fetch-structure/QueryBuilderTDSWindowPanel.tsx index 5c73d901ee..c75297b036 100644 --- a/packages/legend-query-builder/src/components/fetch-structure/QueryBuilderTDSWindowPanel.tsx +++ b/packages/legend-query-builder/src/components/fetch-structure/QueryBuilderTDSWindowPanel.tsx @@ -20,7 +20,7 @@ import { PanelContent, PanelDropZone, BlankPanelPlaceholder, - DropdownMenu, + ControlledDropdownMenu, InputWithInlineValidation, MenuContent, MenuContentItem, @@ -439,7 +439,7 @@ const QueryBuilderWindowColumnModalEditor = observer( > {selectedOperatorState.operator.getLabel()}
- - + @@ -553,7 +553,7 @@ const QueryBuilderWindowColumnModalEditor = observer( {selectedSortBy.sortType.toLowerCase()} )} - - + @@ -1007,7 +1007,7 @@ const QueryBuilderWindowColumnEditor = observer( > {operationState.operator.getLabel()} - - +
@@ -1132,7 +1132,7 @@ const QueryBuilderWindowColumnEditor = observer( {sortByState.sortType.toLowerCase()}
)} - - +
diff --git a/packages/legend-query-builder/src/components/filter/QueryBuilderFilterPanel.tsx b/packages/legend-query-builder/src/components/filter/QueryBuilderFilterPanel.tsx index 488ae2219c..fa5d9fd38d 100644 --- a/packages/legend-query-builder/src/components/filter/QueryBuilderFilterPanel.tsx +++ b/packages/legend-query-builder/src/components/filter/QueryBuilderFilterPanel.tsx @@ -28,7 +28,7 @@ import { type TreeNodeViewProps, clsx, ContextMenu, - DropdownMenu, + ControlledDropdownMenu, MenuContent, MenuContentItem, BlankPanelPlaceholder, @@ -828,7 +828,7 @@ const QueryBuilderFilterConditionEditor = observer( propertyExpressionState={node.condition.propertyExpressionState} />
- - + {node.condition.value && (
- - +
diff --git a/packages/legend-query-builder/src/components/result/QueryBuilderResultPanel.tsx b/packages/legend-query-builder/src/components/result/QueryBuilderResultPanel.tsx index c3fc8f657e..05b3440c55 100644 --- a/packages/legend-query-builder/src/components/result/QueryBuilderResultPanel.tsx +++ b/packages/legend-query-builder/src/components/result/QueryBuilderResultPanel.tsx @@ -17,7 +17,7 @@ import { BlankPanelContent, PlayIcon, - DropdownMenu, + ControlledDropdownMenu, MenuContent, MenuContentItem, CaretDownIcon, @@ -517,7 +517,7 @@ export const QueryBuilderResultPanel = observer( Run Query - - + )} - - + {resultState.isQueryUsageViewerOpened && ( )} diff --git a/packages/legend-server-sdlc/package.json b/packages/legend-server-sdlc/package.json index 29ff5aa9d3..06f43a395d 100644 --- a/packages/legend-server-sdlc/package.json +++ b/packages/legend-server-sdlc/package.json @@ -41,7 +41,7 @@ "dependencies": { "@finos/legend-shared": "workspace:*", "@finos/legend-storage": "workspace:*", - "mobx": "6.12.4", + "mobx": "6.12.5", "serializr": "3.0.2" }, "devDependencies": { diff --git a/packages/legend-server-showcase-deployment/package.json b/packages/legend-server-showcase-deployment/package.json index e05e714ab0..666e2ee36d 100644 --- a/packages/legend-server-showcase-deployment/package.json +++ b/packages/legend-server-showcase-deployment/package.json @@ -53,7 +53,7 @@ "@fastify/cors": "9.0.1", "@finos/legend-server-showcase": "workspace:*", "@finos/legend-shared": "workspace:*", - "fastify": "4.28.0" + "fastify": "4.28.1" }, "devDependencies": { "@finos/legend-dev-utils": "workspace:*", diff --git a/packages/legend-server-showcase/package.json b/packages/legend-server-showcase/package.json index 05e0504ec7..6cbcfdab5c 100644 --- a/packages/legend-server-showcase/package.json +++ b/packages/legend-server-showcase/package.json @@ -39,8 +39,8 @@ }, "dependencies": { "@finos/legend-shared": "workspace:*", - "fastify": "4.28.0", - "mobx": "6.12.4", + "fastify": "4.28.1", + "mobx": "6.12.5", "serializr": "3.0.2", "yaml": "2.4.5" }, diff --git a/packages/legend-shared/package.json b/packages/legend-shared/package.json index ffa674b3b5..93600ba298 100644 --- a/packages/legend-shared/package.json +++ b/packages/legend-shared/package.json @@ -48,12 +48,13 @@ "@types/seedrandom": "3.0.8", "@types/uuid": "10.0.0", "date-fns": "3.6.0", + "deep-object-diff": "^1.1.9", "fuse.js": "7.0.0", "hash.js": "1.1.7", "http-status-codes": "2.3.0", "lodash-es": "4.17.21", "lossless-json": "4.0.1", - "mobx": "6.12.4", + "mobx": "6.12.5", "object-hash": "3.0.0", "pako": "2.1.0", "papaparse": "5.4.1", diff --git a/packages/legend-shared/src/CommonUtils.ts b/packages/legend-shared/src/CommonUtils.ts index a2087f0f96..19d4c07980 100644 --- a/packages/legend-shared/src/CommonUtils.ts +++ b/packages/legend-shared/src/CommonUtils.ts @@ -30,6 +30,7 @@ import { type DebouncedFunc, isObject, } from 'lodash-es'; +import { diff as deepDiff } from 'deep-object-diff'; import { UnsupportedOperationError } from './error/ErrorUtils.js'; import { format as prettyPrintObject } from 'pretty-format'; import { assertTrue, guaranteeNonNullable } from './error/AssertionUtils.js'; @@ -40,6 +41,7 @@ export { clone, deepClone, deepEqual, + deepDiff, findLast, isEmpty, pickBy, @@ -47,6 +49,7 @@ export { uniq, debounce, throttle, + toNumber, type DebouncedFunc, }; diff --git a/packages/legend-shared/src/network/NetworkUtils.ts b/packages/legend-shared/src/network/NetworkUtils.ts index 6c5efce5f2..dc7e775aa4 100644 --- a/packages/legend-shared/src/network/NetworkUtils.ts +++ b/packages/legend-shared/src/network/NetworkUtils.ts @@ -43,7 +43,7 @@ export const URL_SEPARATOR = '/'; * Reference: https://uibakery.io/regex-library/url */ const URL_REGEX = new RegExp( - '^https?://(?:www.)?[-a-zA-Z0-9@:%._+~#=]{1,256}.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)$', + '^(?:https?|ssh|ftp|file)://(?:www.)?[-a-zA-Z0-9@:%._+~#=]{1,256}.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)$', ); export const HttpStatus = StatusCodes; export const CHARSET = 'charset=utf-8'; diff --git a/packages/legend-vscode-extension-dependencies/package.json b/packages/legend-vscode-extension-dependencies/package.json index 426449e20d..ff42b1495d 100644 --- a/packages/legend-vscode-extension-dependencies/package.json +++ b/packages/legend-vscode-extension-dependencies/package.json @@ -44,6 +44,7 @@ "@finos/legend-storage": "workspace:*" }, "devDependencies": { + "@babel/core": "7.24.7", "@finos/legend-dev-utils": "workspace:*", "@rollup/plugin-babel": "6.0.4", "@rollup/plugin-commonjs": "26.0.1", @@ -57,7 +58,7 @@ "rollup": "4.18.0", "rollup-plugin-flow": "1.1.1", "rollup-plugin-import-css": "3.5.0", - "typescript": "5.4.5" + "typescript": "5.5.2" }, "publishConfig": { "directory": "build/publishContent" diff --git a/packages/stylelint-config/package.json b/packages/stylelint-config/package.json index a811170529..2a16c701ff 100644 --- a/packages/stylelint-config/package.json +++ b/packages/stylelint-config/package.json @@ -29,7 +29,7 @@ "publish:snapshot": "node ../../scripts/release/publishDevSnapshot.js" }, "dependencies": { - "postcss": "8.4.38", + "postcss": "8.4.39", "postcss-scss": "4.0.9", "stylelint-config-standard": "36.0.1", "stylelint-scss": "6.3.2" @@ -40,7 +40,7 @@ "rimraf": "5.0.7" }, "peerDependencies": { - "stylelint": "^15.0.0" + "stylelint": ">16.0.0" }, "publishConfig": { "directory": "build/publishContent" diff --git a/scripts/test/fixWatchman.js b/scripts/test/fixWatchman.js new file mode 100644 index 0000000000..e38cab91e7 --- /dev/null +++ b/scripts/test/fixWatchman.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2020-present, Goldman Sachs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { resolve, dirname } from 'path'; +import { execSync } from 'child_process'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +// avoid watchman recrawl +// See https://facebook.github.io/watchman/docs/troubleshooting.html#recrawl +// TODO: do we need to do this every time we re-run dev script? or should we find a way to +// reduce workload for watchman? +execSync(`watchman watch-del '${resolve(__dirname, '../../')}'`); diff --git a/yarn.lock b/yarn.lock index 38d741d6e3..b277effecf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1635,7 +1635,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.24.7, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.6, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:7.24.7, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.24.7 resolution: "@babel/runtime@npm:7.24.7" dependencies: @@ -1975,28 +1975,28 @@ __metadata: linkType: hard "@csstools/css-parser-algorithms@npm:^2.6.3": - version: 2.6.3 - resolution: "@csstools/css-parser-algorithms@npm:2.6.3" + version: 2.7.0 + resolution: "@csstools/css-parser-algorithms@npm:2.7.0" peerDependencies: - "@csstools/css-tokenizer": ^2.3.1 - checksum: 10/b893e284ebcccf37d7928be31be94fb0d6725defc544b39892d5e59ed5950b413366491817539b0add08deb9fc258c57588053d4436f84b7bd3b43bfeee67bb1 + "@csstools/css-tokenizer": ^2.3.2 + checksum: 10/b9c01c0d0b360434debc20e2767bf4fb2a469e479a41bdaf908b0b626ae58a265deda07825a4cb3ddb8aab63c4bfdd6f594ad368fef43471ee2f9aa364f09077 languageName: node linkType: hard "@csstools/css-tokenizer@npm:^2.3.1": - version: 2.3.1 - resolution: "@csstools/css-tokenizer@npm:2.3.1" - checksum: 10/25c8643151667bfc2ce653174786d9f97fea93aa38d48432937bc634d8478dfa03e5e6ad18d3fff3d6fa245e9f6578f87ca07d9fd764a274702e4bb8dd34dede + version: 2.3.2 + resolution: "@csstools/css-tokenizer@npm:2.3.2" + checksum: 10/0dee283818a4ed6480ba076dc18c842844d8d3b9e771005710606785e3163c39107a2af1ccffd2847fdec947abdcbd93deec8a924a7f1b317ac2de24e185e2a1 languageName: node linkType: hard "@csstools/media-query-list-parser@npm:^2.1.11": - version: 2.1.11 - resolution: "@csstools/media-query-list-parser@npm:2.1.11" + version: 2.1.12 + resolution: "@csstools/media-query-list-parser@npm:2.1.12" peerDependencies: - "@csstools/css-parser-algorithms": ^2.6.3 - "@csstools/css-tokenizer": ^2.3.1 - checksum: 10/23ede5583c6f1f51ec45b9293fcaf1ecac0f69c7ea750bfe2245926a66a6ae8f7dea8b3604fc4a5b8be4a25c1bccf519a357bf926d486a7ff479e89685011ff4 + "@csstools/css-parser-algorithms": ^2.7.0 + "@csstools/css-tokenizer": ^2.3.2 + checksum: 10/ee22ee6ffd0a8922fc5e1d985b338fb13dead051cdadc9835b94e1183db5aeb33e184f25caeb2f05935fb61dd96e11601c6ef4001fb625195e51c4ec7a82cbb3 languageName: node linkType: hard @@ -2181,9 +2181,9 @@ __metadata: linkType: hard "@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.6.1": - version: 4.10.1 - resolution: "@eslint-community/regexpp@npm:4.10.1" - checksum: 10/54f13817caf90545502d7a19e1b61df79087aee9584342ffc558b6d067530764a47f1c484f493f43e2c70cfdff59ccfd5f26df2af298c4ad528469e599bd1d53 + version: 4.11.0 + resolution: "@eslint-community/regexpp@npm:4.11.0" + checksum: 10/f053f371c281ba173fe6ee16dbc4fe544c84870d58035ccca08dba7f6ce1830d895ce3237a0db89ba37616524775dca82f1c502066b58e2d5712d7f87f5ba17c languageName: node linkType: hard @@ -2346,7 +2346,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" monaco-editor: "npm:0.50.0" npm-run-all: "npm:4.1.5" @@ -2432,7 +2432,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" npm-run-all: "npm:4.1.5" react: "npm:18.3.1" @@ -2477,8 +2477,10 @@ __metadata: "@ag-grid-community/core": "npm:31.3.2" "@ag-grid-community/react": "npm:31.3.2" "@ag-grid-community/styles": "npm:31.3.2" + "@ag-grid-enterprise/clipboard": "npm:31.3.2" "@ag-grid-enterprise/core": "npm:31.3.2" "@ag-grid-enterprise/menu": "npm:31.3.2" + "@ag-grid-enterprise/range-selection": "npm:31.3.2" "@ag-grid-enterprise/row-grouping": "npm:31.3.2" "@ag-grid-enterprise/server-side-row-model": "npm:31.3.2" "@finos/legend-application": "workspace:*" @@ -2494,7 +2496,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" monaco-editor: "npm:0.50.0" npm-run-all: "npm:4.1.5" @@ -2583,7 +2585,7 @@ __metadata: eslint: "npm:8.57.0" fast-xml-parser: "npm:4.4.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" mobx-utils: "npm:6.0.8" monaco-editor: "npm:0.50.0" @@ -2617,7 +2619,7 @@ __metadata: eslint: "npm:8.57.0" history: "npm:5.3.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" npm-run-all: "npm:4.1.5" react: "npm:18.3.1" @@ -2649,25 +2651,30 @@ __metadata: "@emotion/styled": "npm:11.11.5" "@finos/legend-dev-utils": "workspace:*" "@finos/legend-shared": "workspace:*" + "@fontsource/jetbrains-mono": "npm:5.0.20" "@fontsource/raleway": "npm:5.0.19" "@fontsource/roboto": "npm:5.0.13" "@fontsource/roboto-condensed": "npm:5.0.16" "@fontsource/roboto-mono": "npm:5.0.18" + "@fontsource/roboto-serif": "npm:5.0.13" + "@fontsource/ubuntu-mono": "npm:5.0.20" "@jest/globals": "npm:29.7.0" - "@mui/material": "npm:5.15.20" + "@mui/material": "npm:5.15.21" "@types/react": "npm:18.3.3" "@types/react-resizable": "npm:3.0.7" "@types/react-select": "npm:4.0.18" "@types/react-window": "npm:1.8.8" clsx: "npm:2.1.1" + color-parse: "npm:2.0.2" cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" mermaid: "npm:10.9.1" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" npm-run-all: "npm:4.1.5" react: "npm:18.3.1" + react-colorful: "npm:5.6.1" react-dnd: "npm:16.0.1" react-dnd-html5-backend: "npm:16.0.1" react-dom: "npm:18.3.1" @@ -2729,7 +2736,7 @@ __metadata: mini-css-extract-plugin: "npm:2.9.0" monaco-editor: "npm:0.50.0" monaco-editor-webpack-plugin: "npm:7.1.0" - postcss: "npm:8.4.38" + postcss: "npm:8.4.39" postcss-loader: "npm:8.1.1" react-refresh: "npm:0.14.2" resolve: "npm:1.22.8" @@ -2757,7 +2764,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" npm-run-all: "npm:4.1.5" react: "npm:18.3.1" @@ -2787,7 +2794,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" npm-run-all: "npm:4.1.5" rimraf: "npm:5.0.7" @@ -2818,7 +2825,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" npm-run-all: "npm:4.1.5" react: "npm:18.3.1" @@ -2851,7 +2858,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" npm-run-all: "npm:4.1.5" react: "npm:18.3.1" @@ -2883,7 +2890,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" npm-run-all: "npm:4.1.5" react: "npm:18.3.1" rimraf: "npm:5.0.7" @@ -2916,7 +2923,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" npm-run-all: "npm:4.1.5" react: "npm:18.3.1" @@ -2947,7 +2954,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" npm-run-all: "npm:4.1.5" react: "npm:18.3.1" @@ -2976,7 +2983,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" npm-run-all: "npm:4.1.5" react: "npm:18.3.1" @@ -3005,7 +3012,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" npm-run-all: "npm:4.1.5" react: "npm:18.3.1" @@ -3035,7 +3042,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" npm-run-all: "npm:4.1.5" react: "npm:18.3.1" @@ -3077,7 +3084,7 @@ __metadata: "@finos/legend-dev-utils": "workspace:*" cross-env: "npm:7.0.3" eslint: "npm:8.57.0" - fastify: "npm:4.28.0" + fastify: "npm:4.28.1" nodemon: "npm:3.1.4" npm-run-all: "npm:4.1.5" rimraf: "npm:5.0.7" @@ -3097,7 +3104,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" npm-run-all: "npm:4.1.5" rimraf: "npm:5.0.7" serializr: "npm:3.0.2" @@ -3148,7 +3155,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" monaco-editor: "npm:0.50.0" npm-run-all: "npm:4.1.5" @@ -3212,8 +3219,8 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mathjs: "npm:13.0.0" - mobx: "npm:6.12.4" + mathjs: "npm:13.0.1" + mobx: "npm:6.12.5" mobx-react-lite: "npm:4.0.7" monaco-editor: "npm:0.50.0" npm-run-all: "npm:4.1.5" @@ -3260,7 +3267,7 @@ __metadata: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" npm-run-all: "npm:4.1.5" rimraf: "npm:5.0.7" serializr: "npm:3.0.2" @@ -3279,7 +3286,7 @@ __metadata: "@jest/globals": "npm:29.7.0" cross-env: "npm:7.0.3" eslint: "npm:8.57.0" - fastify: "npm:4.28.0" + fastify: "npm:4.28.1" jest: "npm:29.7.0" nodemon: "npm:3.1.4" npm-run-all: "npm:4.1.5" @@ -3300,9 +3307,9 @@ __metadata: "@jest/globals": "npm:29.7.0" cross-env: "npm:7.0.3" eslint: "npm:8.57.0" - fastify: "npm:4.28.0" + fastify: "npm:4.28.1" jest: "npm:29.7.0" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" npm-run-all: "npm:4.1.5" rimraf: "npm:5.0.7" serializr: "npm:3.0.2" @@ -3326,6 +3333,7 @@ __metadata: "@types/uuid": "npm:10.0.0" cross-env: "npm:7.0.3" date-fns: "npm:3.6.0" + deep-object-diff: "npm:^1.1.9" eslint: "npm:8.57.0" fuse.js: "npm:7.0.0" hash.js: "npm:1.1.7" @@ -3334,7 +3342,7 @@ __metadata: lodash: "npm:4.17.21" lodash-es: "npm:4.17.21" lossless-json: "npm:4.0.1" - mobx: "npm:6.12.4" + mobx: "npm:6.12.5" npm-run-all: "npm:4.1.5" object-hash: "npm:3.0.0" pako: "npm:2.1.0" @@ -3370,6 +3378,7 @@ __metadata: version: 0.0.0-use.local resolution: "@finos/legend-vscode-extension-dependencies@workspace:packages/legend-vscode-extension-dependencies" dependencies: + "@babel/core": "npm:7.24.7" "@finos/legend-application-studio": "workspace:*" "@finos/legend-dev-utils": "workspace:*" "@finos/legend-extension-dsl-diagram": "workspace:*" @@ -3388,7 +3397,7 @@ __metadata: rollup: "npm:4.18.0" rollup-plugin-flow: "npm:1.1.1" rollup-plugin-import-css: "npm:3.5.0" - typescript: "npm:5.4.5" + typescript: "npm:5.5.2" languageName: unknown linkType: soft @@ -3398,51 +3407,58 @@ __metadata: dependencies: cross-env: "npm:7.0.3" eslint: "npm:8.57.0" - postcss: "npm:8.4.38" + postcss: "npm:8.4.39" postcss-scss: "npm:4.0.9" rimraf: "npm:5.0.7" stylelint-config-standard: "npm:36.0.1" stylelint-scss: "npm:6.3.2" peerDependencies: - stylelint: ^15.0.0 + stylelint: ">16.0.0" languageName: unknown linkType: soft -"@floating-ui/core@npm:^1.0.0": - version: 1.6.2 - resolution: "@floating-ui/core@npm:1.6.2" +"@floating-ui/core@npm:^1.6.0": + version: 1.6.4 + resolution: "@floating-ui/core@npm:1.6.4" dependencies: - "@floating-ui/utils": "npm:^0.2.0" - checksum: 10/5c940ef3d397aa23f859ecb033bda408dde20820af3f82090a889c35a99826cfaa7864e8131b9906a26b2c04f31fa468538a28d0715b34de541e0776e0f82d03 + "@floating-ui/utils": "npm:^0.2.4" + checksum: 10/589430cbff4bac90b9b891e2c94c57dc113d39ac163552f547d9e4c7d21f09997b9d33e82ec717759caee678c47f845f14a3f28df6f029fcfcf3ad803ba4eb7c languageName: node linkType: hard "@floating-ui/dom@npm:^1.0.0": - version: 1.6.5 - resolution: "@floating-ui/dom@npm:1.6.5" + version: 1.6.7 + resolution: "@floating-ui/dom@npm:1.6.7" dependencies: - "@floating-ui/core": "npm:^1.0.0" - "@floating-ui/utils": "npm:^0.2.0" - checksum: 10/d421e7f239e9af5a2a4c7a560c29b8ce1f29398c411c8e3bd0c33a2ce800e13a378749a1606e4f6b460830f4007c459792534821013262d24d1385476b1ba48d + "@floating-ui/core": "npm:^1.6.0" + "@floating-ui/utils": "npm:^0.2.4" + checksum: 10/a6a42bfd243c311f6040043808a6549c1db45fa36138b81cb1e615170d61fd2daf4f37accc1df3e0189405d97e3d71b12de39879c9d58ccf181c982b69cf6cf9 languageName: node linkType: hard "@floating-ui/react-dom@npm:^2.0.8": - version: 2.1.0 - resolution: "@floating-ui/react-dom@npm:2.1.0" + version: 2.1.1 + resolution: "@floating-ui/react-dom@npm:2.1.1" dependencies: "@floating-ui/dom": "npm:^1.0.0" peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 10/15be0714379c271ff01347e7c9bcdba96d6b39f3960697380e23de9b9d59fb91ba07bc75b8bdb12d72da7a9272191a9ce73f843a0d5f89939caa9f3137acd8ec + checksum: 10/cafabfb5dd0b25547863520b3bcf6faee7f087d0c3187a8779910a6838d496bf494f237bf1fe883bbfae1a7fcc399611ae52377b696065d8118bd7c1b9c0d253 languageName: node linkType: hard -"@floating-ui/utils@npm:^0.2.0": - version: 0.2.2 - resolution: "@floating-ui/utils@npm:0.2.2" - checksum: 10/28d900d2f0876b40c7090f55724700eeac608862e59110b7b14731223218cf7ce125b2091f34103edf4b0f779166151bbca21256b856236235a2be996548ed38 +"@floating-ui/utils@npm:^0.2.4": + version: 0.2.4 + resolution: "@floating-ui/utils@npm:0.2.4" + checksum: 10/7662d7a4ae39c0287e026f666297a3d28c80e588251c8c59ff66938a0aead47d380bbb9018629bd63a98f399c3919ec689d5448a5c48ffc176d545ddef705df1 + languageName: node + linkType: hard + +"@fontsource/jetbrains-mono@npm:5.0.20": + version: 5.0.20 + resolution: "@fontsource/jetbrains-mono@npm:5.0.20" + checksum: 10/18c3ff2c1245c8d34e93e9bb000bfb3685b5be405f61f218fdbeeedd6ace1dc6d03df6d470535e9614bcbb29b675a221fd1c53d35d550ad773a1ef7031995840 languageName: node linkType: hard @@ -3467,6 +3483,13 @@ __metadata: languageName: node linkType: hard +"@fontsource/roboto-serif@npm:5.0.13": + version: 5.0.13 + resolution: "@fontsource/roboto-serif@npm:5.0.13" + checksum: 10/f1280caf1518e046acb5eebeebef5136501f6e166b049022654a587369603f9d6db8e715008a27d25ea4372a1092aec3ca43235e25a032e6564e5558a7c0b480 + languageName: node + linkType: hard + "@fontsource/roboto@npm:5.0.13": version: 5.0.13 resolution: "@fontsource/roboto@npm:5.0.13" @@ -3474,6 +3497,13 @@ __metadata: languageName: node linkType: hard +"@fontsource/ubuntu-mono@npm:5.0.20": + version: 5.0.20 + resolution: "@fontsource/ubuntu-mono@npm:5.0.20" + checksum: 10/ca2a3d3b39da20186de7eecb5e6f517f74b97e00bae70fc74aaa6a7364df1de2459571ebe380a48323ee3694476dfa385b068dcecba251a671ff0bf9baacfec9 + languageName: node + linkType: hard + "@humanwhocodes/config-array@npm:^0.11.14": version: 0.11.14 resolution: "@humanwhocodes/config-array@npm:0.11.14" @@ -3875,15 +3905,6 @@ __metadata: languageName: node linkType: hard -"@ljharb/through@npm:^2.3.13": - version: 2.3.13 - resolution: "@ljharb/through@npm:2.3.13" - dependencies: - call-bind: "npm:^1.0.7" - checksum: 10/6150c6c43a726d52c26863ed6dc4ab54fa7cf625c81463a5ddec86278c99e23bf94dfc99ebf09a9ac3191332d4a27344e092f7e07f252b8cd600e2b38e645870 - languageName: node - linkType: hard - "@manypkg/find-root@npm:^1.1.0": version: 1.1.0 resolution: "@manypkg/find-root@npm:1.1.0" @@ -3932,20 +3953,20 @@ __metadata: languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^5.15.20": - version: 5.15.20 - resolution: "@mui/core-downloads-tracker@npm:5.15.20" - checksum: 10/8cc71169ce1201c5a37fad6c5c58739850694e90c7bda636c692d47a79fd4c7884eab2c24b0084d6732a3d28c9a4a6d222dd5747f9d91f5075614700a3a3282a +"@mui/core-downloads-tracker@npm:^5.15.21": + version: 5.15.21 + resolution: "@mui/core-downloads-tracker@npm:5.15.21" + checksum: 10/c6f8e2350597833a96593f65e5081930a1006b645be6a34750e93e15e111eeca6973562b42688f5d6393a050401b7196d58c2aa2de96a112ac27fb998411742a languageName: node linkType: hard -"@mui/material@npm:5.15.20": - version: 5.15.20 - resolution: "@mui/material@npm:5.15.20" +"@mui/material@npm:5.15.21": + version: 5.15.21 + resolution: "@mui/material@npm:5.15.21" dependencies: "@babel/runtime": "npm:^7.23.9" "@mui/base": "npm:5.0.0-beta.40" - "@mui/core-downloads-tracker": "npm:^5.15.20" + "@mui/core-downloads-tracker": "npm:^5.15.21" "@mui/system": "npm:^5.15.20" "@mui/types": "npm:^7.2.14" "@mui/utils": "npm:^5.15.20" @@ -3968,7 +3989,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10/1e568f82334b3a556bd248130238746bcd59951aad654e43c0df292076fae6799172113c3e2eb93fffaf49bd81023fbee0494b073e8808400ad00bea9f030e9b + checksum: 10/3369cb1d0ddd790de2d972dd5a2039eb2cd26e35ff1c515308d0d03929f9ded02d2ba3038551d94258ac5d5dc9a4433ca76ad60f81573346aa0d69075d72f403 languageName: node linkType: hard @@ -4547,10 +4568,10 @@ __metadata: languageName: node linkType: hard -"@shikijs/core@npm:1.9.0": - version: 1.9.0 - resolution: "@shikijs/core@npm:1.9.0" - checksum: 10/ec07699742f0561ab4d2fb07715397183cbed23ca42e082b14e9e5f85eb7f8647897f69698d003d8c6a1b8682da0af77861b3a2f78d366eef7581bae4415142c +"@shikijs/core@npm:1.10.0": + version: 1.10.0 + resolution: "@shikijs/core@npm:1.10.0" + checksum: 10/301436c7c4cb048b8683af74eb306fe728112e231b17e56f3c7b28f82ac664c3b56f620e54bdfc9cfd99cbab486ec29ce98e1123d22bdc43a896c4f73b9aa7d8 languageName: node linkType: hard @@ -4939,9 +4960,9 @@ __metadata: linkType: hard "@types/lodash@npm:*": - version: 4.17.5 - resolution: "@types/lodash@npm:4.17.5" - checksum: 10/10e2e9cbeb16998026f4071f9f5f2a38b651eba15302f512e0b8ab904c07c197ca0282d2821f64e53c2b692d7046af0a1ce3ead190fb077cbe4036948fce1924 + version: 4.17.6 + resolution: "@types/lodash@npm:4.17.6" + checksum: 10/6d3a68b3e795381f4aaf946855134d24eeb348ad5d66e9a44461d30026da82b215d55b92b70486d811ca45d54d4ab956aa2dced37fd04e19d49afe160ae3da2e languageName: node linkType: hard @@ -4993,12 +5014,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:20.14.8": - version: 20.14.8 - resolution: "@types/node@npm:20.14.8" +"@types/node@npm:*, @types/node@npm:20.14.9": + version: 20.14.9 + resolution: "@types/node@npm:20.14.9" dependencies: undici-types: "npm:~5.26.4" - checksum: 10/73822f66f269ce865df7e2f586787ac7444bd1169fd265cbed1e851b72787f1170517c5b616e0308ec2fbc0934ec6403b0f28d4152acbb0486071aec41167d51 + checksum: 10/f313b06c79be92f5d3541159ef813b9fc606941f951ecf826e940658c6d4952755ca2f06277b746326cef0697ed79a04676ecde053d32e1121b3352c8168d2e9 languageName: node linkType: hard @@ -6591,9 +6612,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001599, caniuse-lite@npm:^1.0.30001629": - version: 1.0.30001636 - resolution: "caniuse-lite@npm:1.0.30001636" - checksum: 10/9e6c5ab4c20df31df36720dda77cf6a781549ac2ad844bc0a416b327a793da21486358a1f85fdd6c39e22d336f70aac3b0e232f5f228cdff0ceb6e3e1c5e98fd + version: 1.0.30001639 + resolution: "caniuse-lite@npm:1.0.30001639" + checksum: 10/c163080e08982de60b37d51130f088e9f8a745533621675694f0b079f53529308168adb0966051139ff71c75af439b2ce150b7142cff15aac421cbfd126bd481 languageName: node linkType: hard @@ -6604,7 +6625,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:5.3.0, chalk@npm:^5.2.0, chalk@npm:^5.3.0, chalk@npm:~5.3.0": +"chalk@npm:5.3.0, chalk@npm:^5.2.0, chalk@npm:~5.3.0": version: 5.3.0 resolution: "chalk@npm:5.3.0" checksum: 10/6373caaab21bd64c405bfc4bd9672b145647fc9482657b5ea1d549b3b2765054e9d3d928870cdf764fb4aad67555f5061538ff247b8310f110c5c888d92397ea @@ -6904,6 +6925,22 @@ __metadata: languageName: node linkType: hard +"color-name@npm:^2.0.0": + version: 2.0.0 + resolution: "color-name@npm:2.0.0" + checksum: 10/10a1addae41de2987d6b90dbd3cfade266c2e6f680ce21749911df4493b4fae07654862c6b5358bdd13e155461acb4eedaa5e0ba172bf13542cdcca10866cf2b + languageName: node + linkType: hard + +"color-parse@npm:2.0.2": + version: 2.0.2 + resolution: "color-parse@npm:2.0.2" + dependencies: + color-name: "npm:^2.0.0" + checksum: 10/64963587057e024edbce3f009e73e69b1d1b31d6f9955eed853748b8cb14d4e3bb49064da9c7452a22f5326a1197fa6c483483a53868e815f4cba30b6b1c36cc + languageName: node + linkType: hard + "colord@npm:^2.9.3": version: 2.9.3 resolution: "colord@npm:2.9.3" @@ -7442,9 +7479,9 @@ __metadata: linkType: hard "cytoscape@npm:^3.28.1": - version: 3.29.2 - resolution: "cytoscape@npm:3.29.2" - checksum: 10/986b5126a73267cddefdc33a20556889a91365e98324a35a4714cda6efd1af51ae442689db41d4c23b2340a84471bbffeea720c0d3b3e68d6659f9744a08af1a + version: 3.30.0 + resolution: "cytoscape@npm:3.30.0" + checksum: 10/4361004b7f944b82ee26407fe834982e67e83331d1b38b97e075b6960d88eeaf3b1b8c12983e8097cf04a97e8b17a6cee64d7fdcffc8b67d4d92f931b7f66e7c languageName: node linkType: hard @@ -7955,6 +7992,13 @@ __metadata: languageName: node linkType: hard +"deep-object-diff@npm:^1.1.9": + version: 1.1.9 + resolution: "deep-object-diff@npm:1.1.9" + checksum: 10/b9771cc1ca08a34e408309eaab967bd2ab697684abdfa1262f4283ced8230a9ace966322f356364ff71a785c6e9cc356b7596582e900da5726e6b87d4b2a1463 + languageName: node + linkType: hard + "deepmerge@npm:^4.2.2": version: 4.3.1 resolution: "deepmerge@npm:4.3.1" @@ -8368,9 +8412,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.4.796": - version: 1.4.810 - resolution: "electron-to-chromium@npm:1.4.810" - checksum: 10/425de710336094330fd026cc2cfa0b383bfc9a49a2f575ceec2ac76198663ff95d0109bb45b243a43f0bf0f91a2e9c2768693a136d0968ae59728d5e8eea22ef + version: 1.4.815 + resolution: "electron-to-chromium@npm:1.4.815" + checksum: 10/84f59b76b1ac16fe1f359720bdaeadcbce34825a7e79e3551ceec6b52d4021e7bd8a56a2cfa4197ba353331df18d0bc2ab449e08c5599eb1b2a7aacaba56f711 languageName: node linkType: hard @@ -8600,9 +8644,9 @@ __metadata: linkType: hard "es-module-lexer@npm:^1.2.1": - version: 1.5.3 - resolution: "es-module-lexer@npm:1.5.3" - checksum: 10/2d80297e955f52ec6a4c7c7683ec2ee80b33c61b46af4f6ed3ef8feab16ba10fd4798141132b3fd0f5e2edb36abd4ad50c63cf3e26da2cca1c56debc68816c44 + version: 1.5.4 + resolution: "es-module-lexer@npm:1.5.4" + checksum: 10/f29c7c97a58eb17640dcbd71bd6ef754ad4f58f95c3073894573d29dae2cad43ecd2060d97ed5b866dfb7804d5590fb7de1d2c5339a5fceae8bd60b580387fc5 languageName: node linkType: hard @@ -9272,9 +9316,9 @@ __metadata: languageName: node linkType: hard -"fastify@npm:4.28.0": - version: 4.28.0 - resolution: "fastify@npm:4.28.0" +"fastify@npm:4.28.1": + version: 4.28.1 + resolution: "fastify@npm:4.28.1" dependencies: "@fastify/ajv-compiler": "npm:^3.5.0" "@fastify/error": "npm:^3.4.0" @@ -9292,7 +9336,7 @@ __metadata: secure-json-parse: "npm:^2.7.0" semver: "npm:^7.5.4" toad-cache: "npm:^3.3.0" - checksum: 10/8f3991b1ae2b5248ca9fb09f57f0fc083ca3ca26ab418fb2b0b768c5cec5c01aa4aaa9e7697018457a5f53fe43d74747bac2393509eeac96bb535d5cbba4e062 + checksum: 10/8a749dd540609579258cd0471c521696ed16bf66e34d46babbbed9d9a184bd7a378c2ec87a233e382071c1c91b223db0a1ad1a7f9dc8a8f2240aaf8e173ed597 languageName: node linkType: hard @@ -9875,8 +9919,8 @@ __metadata: linkType: hard "globby@npm:^14.0.0": - version: 14.0.1 - resolution: "globby@npm:14.0.1" + version: 14.0.2 + resolution: "globby@npm:14.0.2" dependencies: "@sindresorhus/merge-streams": "npm:^2.1.0" fast-glob: "npm:^3.3.2" @@ -9884,7 +9928,7 @@ __metadata: path-type: "npm:^5.0.0" slash: "npm:^5.1.0" unicorn-magic: "npm:^0.1.0" - checksum: 10/b36f57afc45a857a884d82657603c7e1663b1e6f3f9afbeb53d12e42230469fc5b26a7e14a01e51086f3f25c138f58a7002036fcc8f3ca054097b6dd7c71d639 + checksum: 10/67660da70fc1223f7170c1a62ba6c373385e9e39765d952b6518606dec15ed8c7958e9dae6ba5752a31dbc1e9126f146938b830ad680fe794141734ffc3fbb75 languageName: node linkType: hard @@ -10312,12 +10356,12 @@ __metadata: linkType: hard "https-proxy-agent@npm:^7.0.1": - version: 7.0.4 - resolution: "https-proxy-agent@npm:7.0.4" + version: 7.0.5 + resolution: "https-proxy-agent@npm:7.0.5" dependencies: agent-base: "npm:^7.0.2" debug: "npm:4" - checksum: 10/405fe582bba461bfe5c7e2f8d752b384036854488b828ae6df6a587c654299cbb2c50df38c4b6ab303502c3c5e029a793fbaac965d1e86ee0be03faceb554d63 + checksum: 10/6679d46159ab3f9a5509ee80c3a3fc83fba3a920a5e18d32176c3327852c3c00ad640c0c4210a8fd70ea3c4a6d3a1b375bf01942516e7df80e2646bdc77658ab languageName: node linkType: hard @@ -10487,18 +10531,14 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:9.2.23": - version: 9.2.23 - resolution: "inquirer@npm:9.2.23" +"inquirer@npm:9.3.2": + version: 9.3.2 + resolution: "inquirer@npm:9.3.2" dependencies: "@inquirer/figures": "npm:^1.0.3" - "@ljharb/through": "npm:^2.3.13" ansi-escapes: "npm:^4.3.2" - chalk: "npm:^5.3.0" - cli-cursor: "npm:^3.1.0" cli-width: "npm:^4.1.0" external-editor: "npm:^3.1.0" - lodash: "npm:^4.17.21" mute-stream: "npm:1.0.0" ora: "npm:^5.4.1" run-async: "npm:^3.0.0" @@ -10506,7 +10546,8 @@ __metadata: string-width: "npm:^4.2.3" strip-ansi: "npm:^6.0.1" wrap-ansi: "npm:^6.2.0" - checksum: 10/ccc05c4d64ee583ac6d1ad602ae1b76c25da6d6ca8923bbac9158f76bad2eb9c628b5b5201b135fcc32f9651ed1b9ed99e6489f9afa5f00db443447f1f9ad391 + yoctocolors-cjs: "npm:^2.1.1" + checksum: 10/a19e5fc3c0c802b38d1130a67e5d7062e698994e9f7334d56a36a5bca4539be80f136ebe02e2dbe4caa50620ea64827494b96919425bb1db089e807e17c96d43 languageName: node linkType: hard @@ -11116,15 +11157,15 @@ __metadata: linkType: hard "istanbul-lib-instrument@npm:^6.0.0": - version: 6.0.2 - resolution: "istanbul-lib-instrument@npm:6.0.2" + version: 6.0.3 + resolution: "istanbul-lib-instrument@npm:6.0.3" dependencies: "@babel/core": "npm:^7.23.9" "@babel/parser": "npm:^7.23.9" "@istanbuljs/schema": "npm:^0.1.3" istanbul-lib-coverage: "npm:^3.2.0" semver: "npm:^7.5.4" - checksum: 10/3aee19be199350182827679a137e1df142a306e9d7e20bb5badfd92ecc9023a7d366bc68e7c66e36983654a02a67401d75d8debf29fc6d4b83670fde69a594fc + checksum: 10/aa5271c0008dfa71b6ecc9ba1e801bf77b49dc05524e8c30d58aaf5b9505e0cd12f25f93165464d4266a518c5c75284ecb598fbd89fec081ae77d2c9d3327695 languageName: node linkType: hard @@ -12008,14 +12049,14 @@ __metadata: "@finos/eslint-plugin-legend-studio": "workspace:*" "@finos/legend-dev-utils": "workspace:*" "@finos/stylelint-config-legend-studio": "workspace:*" - "@types/node": "npm:20.14.8" + "@types/node": "npm:20.14.9" chalk: "npm:5.3.0" cross-env: "npm:7.0.3" envinfo: "npm:7.13.0" eslint: "npm:8.57.0" fs-extra: "npm:11.2.0" husky: "npm:9.0.11" - inquirer: "npm:9.2.23" + inquirer: "npm:9.3.2" jest: "npm:29.7.0" lint-staged: "npm:15.2.7" micromatch: "npm:4.0.7" @@ -12027,7 +12068,7 @@ __metadata: semver: "npm:7.6.2" sort-package-json: "npm:2.10.0" stylelint: "npm:16.6.1" - typedoc: "npm:0.26.2" + typedoc: "npm:0.26.3" typescript: "npm:5.5.2" yargs: "npm:17.7.2" languageName: unknown @@ -12315,9 +12356,9 @@ __metadata: linkType: hard "lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": - version: 10.2.2 - resolution: "lru-cache@npm:10.2.2" - checksum: 10/ff1a496d30b5eaec2c9079080965bb0cede203cf878371f7033a007f1e54cd4aa13cc8abf7ccec4c994a83a22ed5476e83a55bb57cc07e6c1547a42937e42c37 + version: 10.3.0 + resolution: "lru-cache@npm:10.3.0" + checksum: 10/37e921aedbd1f4062475d9fa6760391fa7adfaaee3a5a6cbedd1d6d0b46705c14012312c1edb2b13f119eae6584a48f73c158d118828d42475b44a7abf7d05ab languageName: node linkType: hard @@ -12426,11 +12467,11 @@ __metadata: languageName: node linkType: hard -"mathjs@npm:13.0.0": - version: 13.0.0 - resolution: "mathjs@npm:13.0.0" +"mathjs@npm:13.0.1": + version: 13.0.1 + resolution: "mathjs@npm:13.0.1" dependencies: - "@babel/runtime": "npm:^7.24.6" + "@babel/runtime": "npm:^7.24.7" complex.js: "npm:^2.1.1" decimal.js: "npm:^10.4.3" escape-latex: "npm:^1.2.0" @@ -12438,10 +12479,10 @@ __metadata: javascript-natural-sort: "npm:^0.7.1" seedrandom: "npm:^3.0.5" tiny-emitter: "npm:^2.1.0" - typed-function: "npm:^4.1.1" + typed-function: "npm:^4.2.1" bin: mathjs: bin/cli.js - checksum: 10/dff62ad0d9ad9c6f566e91b4cf304173d5d3ec1545f21ca1e4140691ca49a82b846abf8e540c34dabb1e7acd1d5474b654c5860a6b3f7c3175bc5bfc3dfbc759 + checksum: 10/abb53cdd1ed9668cedae08e696d02b1836e6c0598d89dfb346435559a42959192a445ebfb60fa5482f9dde528ab601ffe200bd0a01c910cf795a2582ea98082c languageName: node linkType: hard @@ -13450,12 +13491,12 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^9.0.4": - version: 9.0.4 - resolution: "minimatch@npm:9.0.4" +"minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" dependencies: brace-expansion: "npm:^2.0.1" - checksum: 10/4cdc18d112b164084513e890d6323370db14c22249d536ad1854539577a895e690a27513dc346392f61a4a50afbbd8abc88f3f25558bfbbbb862cd56508b20f5 + checksum: 10/dd6a8927b063aca6d910b119e1f2df6d2ce7d36eab91de83167dd136bb85e1ebff97b0d3de1cb08bd1f7e018ca170b4962479fefab5b2a69e2ae12cb2edc8348 languageName: node linkType: hard @@ -13594,10 +13635,10 @@ __metadata: languageName: node linkType: hard -"mobx@npm:6.12.4": - version: 6.12.4 - resolution: "mobx@npm:6.12.4" - checksum: 10/9096633b2a350e7908c193ae385fa6caf121bf8d963c9a1c068f6196b7b0396b2ed02e30254e669ae36e44ce9760e4ce85746c7f32b5adc84e26feeb3f0ba2cc +"mobx@npm:6.12.5": + version: 6.12.5 + resolution: "mobx@npm:6.12.5" + checksum: 10/af03258be314120153cb33953aad5cfb6e639e8536f3374d77dfad2244376c228f960b157e4133c97b8b8b6d552c6e369dff05ce43c8e2f2fe17066b0d060cb3 languageName: node linkType: hard @@ -15097,14 +15138,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.4.38, postcss@npm:^8.4.23, postcss@npm:^8.4.33, postcss@npm:^8.4.38": - version: 8.4.38 - resolution: "postcss@npm:8.4.38" +"postcss@npm:8.4.39, postcss@npm:^8.4.23, postcss@npm:^8.4.33, postcss@npm:^8.4.38": + version: 8.4.39 + resolution: "postcss@npm:8.4.39" dependencies: nanoid: "npm:^3.3.7" - picocolors: "npm:^1.0.0" + picocolors: "npm:^1.0.1" source-map-js: "npm:^1.2.0" - checksum: 10/6e44a7ed835ffa9a2b096e8d3e5dfc6bcf331a25c48aeb862dd54e3aaecadf814fa22be224fd308f87d08adf2299164f88c5fd5ab1c4ef6cbd693ceb295377f4 + checksum: 10/ad9c1add892c96433b9a5502878201ede4a20c4ce02d056251f61f8d9a3e5426dab3683fe5a086edfa78a1a19f2b4988c8cea02c5122136d29758cb5a17e2621 languageName: node linkType: hard @@ -15479,6 +15520,16 @@ __metadata: languageName: node linkType: hard +"react-colorful@npm:5.6.1": + version: 5.6.1 + resolution: "react-colorful@npm:5.6.1" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10/3e02ba013454818d0c323949bd961fb2c19ac18130dfc67a4032aa5b03787c5ffe7ff159c4b97dc3475072d576828ca0c4b8e8ce85b55eaf484180596cdf0403 + languageName: node + linkType: hard + "react-dnd-html5-backend@npm:16.0.1": version: 16.0.1 resolution: "react-dnd-html5-backend@npm:16.0.1" @@ -16742,12 +16793,12 @@ __metadata: languageName: node linkType: hard -"shiki@npm:^1.9.0": - version: 1.9.0 - resolution: "shiki@npm:1.9.0" +"shiki@npm:^1.9.1": + version: 1.10.0 + resolution: "shiki@npm:1.10.0" dependencies: - "@shikijs/core": "npm:1.9.0" - checksum: 10/ce5648b4e1a5e5e81e54e01ec9dc94cf324530f460058f29f1e5e464dbf096fee2194fbef38b29128a747f783f9134531991316b01ed168f65d0617dabf84c33 + "@shikijs/core": "npm:1.10.0" + checksum: 10/e5c844acece5eef2b796d63040ff9655fbd1070d10c50726eebaddc0506b9bdaee7fc28c7299168ef1a4eb9007db7e928bd03a80d3918c37df0fe3a96363d66b languageName: node linkType: hard @@ -16882,17 +16933,17 @@ __metadata: linkType: hard "socks-proxy-agent@npm:^8.0.3": - version: 8.0.3 - resolution: "socks-proxy-agent@npm:8.0.3" + version: 8.0.4 + resolution: "socks-proxy-agent@npm:8.0.4" dependencies: agent-base: "npm:^7.1.1" debug: "npm:^4.3.4" - socks: "npm:^2.7.1" - checksum: 10/c2112c66d6322e497d68e913c3780f3683237fd394bfd480b9283486a86e36095d0020db96145d88f8ccd9cc73261b98165b461f9c1bf5dc17abfe75c18029ce + socks: "npm:^2.8.3" + checksum: 10/c8e7c2b398338b49a0a0f4d2bae5c0602aeeca6b478b99415927b6c5db349ca258448f2c87c6958ebf83eea17d42cbc5d1af0bfecb276cac10b9658b0f07f7d7 languageName: node linkType: hard -"socks@npm:^2.7.1": +"socks@npm:^2.8.3": version: 2.8.3 resolution: "socks@npm:2.8.3" dependencies: @@ -17192,13 +17243,13 @@ __metadata: linkType: hard "string-width@npm:^7.0.0": - version: 7.1.0 - resolution: "string-width@npm:7.1.0" + version: 7.2.0 + resolution: "string-width@npm:7.2.0" dependencies: emoji-regex: "npm:^10.3.0" get-east-asian-width: "npm:^1.0.0" strip-ansi: "npm:^7.1.0" - checksum: 10/a183573fe7209e0d294f661846d33f8caf72aa86d983e5b48a0ed45ab15bcccb02c6f0344b58b571988871105457137b8207855ea536827dbc4a376a0f31bf8f + checksum: 10/42f9e82f61314904a81393f6ef75b832c39f39761797250de68c041d8ba4df2ef80db49ab6cd3a292923a6f0f409b8c9980d120f7d32c820b4a8a84a2598a295 languageName: node linkType: hard @@ -17870,11 +17921,11 @@ __metadata: linkType: hard "tree-dump@npm:^1.0.1": - version: 1.0.1 - resolution: "tree-dump@npm:1.0.1" + version: 1.0.2 + resolution: "tree-dump@npm:1.0.2" peerDependencies: tslib: 2 - checksum: 10/b19d0ce73d62dc8c83e3fae7a96c2ae77db4bebfacd6e2b225eb5c867d3fdf21ea790c64795a1777cb8e38816ea72ae12a46c76811a5e9f0d31182484f02a6da + checksum: 10/ddcde4da9ded8edc2fa77fc9153ef8d7fba9cd5f813db27c30c7039191b50e1512b7106f0f4fe7ccaa3aa69f85b4671eda7ed0b9f9d34781eb26ebe4593ad4eb languageName: node linkType: hard @@ -18040,37 +18091,27 @@ __metadata: languageName: node linkType: hard -"typed-function@npm:^4.1.1": +"typed-function@npm:^4.2.1": version: 4.2.1 resolution: "typed-function@npm:4.2.1" checksum: 10/2218d6e4a56c414c2d9c1e3cf2f0d26d6a8848d3e875cbd0eec5a791c25c4ee182cb488a6077b45b110334de7bd7f44fb049feac9e5216bef3172c22acbbf501 languageName: node linkType: hard -"typedoc@npm:0.26.2": - version: 0.26.2 - resolution: "typedoc@npm:0.26.2" +"typedoc@npm:0.26.3": + version: 0.26.3 + resolution: "typedoc@npm:0.26.3" dependencies: lunr: "npm:^2.3.9" markdown-it: "npm:^14.1.0" - minimatch: "npm:^9.0.4" - shiki: "npm:^1.9.0" + minimatch: "npm:^9.0.5" + shiki: "npm:^1.9.1" yaml: "npm:^2.4.5" peerDependencies: typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x bin: typedoc: bin/typedoc - checksum: 10/f7cbe3e4a7635fe8fef9aacd62eedf72ff008548eebdcfc9a8b8ffea3cb6c06846230a022f436ba006bf11bf669b1e7fcf375349410e1f2d920b42531cd1f5b4 - languageName: node - linkType: hard - -"typescript@npm:5.4.5": - version: 5.4.5 - resolution: "typescript@npm:5.4.5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10/d04a9e27e6d83861f2126665aa8d84847e8ebabcea9125b9ebc30370b98cb38b5dff2508d74e2326a744938191a83a69aa9fddab41f193ffa43eabfdf3f190a5 + checksum: 10/55473079de55140d29299b449c0c8de6da1c6c76553bae17c11d1be9e4521612c5f0d9d5a6ee4e18ba7103f79de9ff98223fa0fb2d0e09b2bd535e54efe3d08d languageName: node linkType: hard @@ -18084,16 +18125,6 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.4.5#optional!builtin": - version: 5.4.5 - resolution: "typescript@patch:typescript@npm%3A5.4.5#optional!builtin::version=5.4.5&hash=5adc0c" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10/760f7d92fb383dbf7dee2443bf902f4365db2117f96f875cf809167f6103d55064de973db9f78fe8f31ec08fff52b2c969aee0d310939c0a3798ec75d0bca2e1 - languageName: node - linkType: hard - "typescript@patch:typescript@npm%3A5.5.2#optional!builtin": version: 5.5.2 resolution: "typescript@patch:typescript@npm%3A5.5.2#optional!builtin::version=5.5.2&hash=b45daf" @@ -19138,9 +19169,16 @@ __metadata: linkType: hard "yocto-queue@npm:^1.0.0": - version: 1.0.0 - resolution: "yocto-queue@npm:1.0.0" - checksum: 10/2cac84540f65c64ccc1683c267edce396b26b1e931aa429660aefac8fbe0188167b7aee815a3c22fa59a28a58d898d1a2b1825048f834d8d629f4c2a5d443801 + version: 1.1.1 + resolution: "yocto-queue@npm:1.1.1" + checksum: 10/f2e05b767ed3141e6372a80af9caa4715d60969227f38b1a4370d60bffe153c9c5b33a862905609afc9b375ec57cd40999810d20e5e10229a204e8bde7ef255c + languageName: node + linkType: hard + +"yoctocolors-cjs@npm:^2.1.1": + version: 2.1.1 + resolution: "yoctocolors-cjs@npm:2.1.1" + checksum: 10/d7cbb833eb2fac85e38f5efeaf163ad2d468b7795eef595f034183a404253767d06ec6075e44ab0414436a7bbf5e4aad524e5a3f380dafbbd24e971a4ee51d37 languageName: node linkType: hard