From 79dc8260847aac84e07fb7d39ab0bd49df8e3b43 Mon Sep 17 00:00:00 2001 From: Milho001 <l.milhomemfrancochristino@uu.nl> Date: Wed, 3 May 2023 10:33:45 +0000 Subject: [PATCH] feat(querybuilder): added querybuilder to the dashboard Ported querybuilder pills, edges and interactions from V1 and added stories for each pill. Ported drag-and-drop functionality between schema panel and querybuilder (plus story). Also ported Navbar for the dashboard. Solves #20, #19, #25 --- .eslintrc.js | 10 - .eslintrc.json | 35 - .gitlab-ci.yml | 12 +- apps/web/.eslintignore | 3 + apps/web/.eslintrc.json | 25 + apps/web/cssmodule.d.ts | 24 - apps/web/image.d.ts | 48 - apps/web/package.json | 10 +- apps/web/public/assets/icons/ExportIcon.png | Bin 0 -> 124133 bytes .../web/public/assets/login-screen/github.png | Bin 0 -> 7204 bytes .../web/public/assets/login-screen/google.png | Bin 0 -> 8001 bytes apps/web/src/app/app.tsx | 52 +- .../add-database-form.module.scss | 75 + .../navbar/AddDatabaseForm/index.tsx | 205 +++ apps/web/src/components/navbar/MenuList.scss | 17 + apps/web/src/components/navbar/logogp.png | Bin 0 -> 4300 bytes .../web/src/components/navbar/logogpwhite.png | Bin 0 -> 4640 bytes .../src/components/navbar/navbar.module.scss | 44 + apps/web/src/components/navbar/navbar.tsx | 325 +++++ apps/web/vite.config.ts | 22 +- libs/shared/.eslintrc.json | 7 + libs/shared/index.tsx | 2 +- libs/shared/lib/data-access/api/database.ts | 19 +- libs/shared/lib/data-access/api/index.ts | 3 +- libs/shared/lib/data-access/api/schema.ts | 7 + libs/shared/lib/data-access/api/user.ts | 4 +- .../authorization/authorizationHandler.ts | 27 +- .../authorization/authorizationHook.tsx | 41 + .../lib/data-access/authorization/index.ts | 1 + .../colorPaletteConfigSlice.ts | 25 +- .../lib/data-access/store/configSlice.ts | 26 + libs/shared/lib/data-access/store/hooks.ts | 28 +- libs/shared/lib/data-access/store/index.ts | 13 +- .../data-access/store/querybuilderSlice.ts | 37 +- .../lib/data-access/store/schemaSlice.spec.ts | 10 +- .../lib/data-access/store/schemaSlice.ts | 17 +- .../lib/data-access/store/sessionSlice.ts | 35 + libs/shared/lib/data-access/store/store.ts | 15 +- .../theme/graphPolarisThemeProvider.tsx | 2 +- .../graph-layout/layout-creator-usecase.ts | 4 +- .../graph/graphology/JSONParser.tsx | 85 ++ .../querybuilder/graph/graphology/model.ts | 49 + .../querybuilder/graph/graphology/utils.ts | 131 ++ .../graph/logic/queryFunctions.tsx | 148 ++ .../querybuilder/graph/reactflow/handles.tsx | 81 ++ .../querybuilder/graph/reactflow/model.tsx | 130 ++ .../reactflow}/pillHandles.ts | 0 .../reactflow/utils.ts} | 5 +- libs/shared/lib/querybuilder/index.ts | 2 + .../panel/nodeUtils.ts} | 0 .../panel/querybuilder.module.scss | 24 + .../panel/querybuilder.module.scss.d.ts | 3 + .../panel/querybuilder.stories.tsx | 159 ++- .../lib/querybuilder/panel/querybuilder.tsx | 289 +++- .../panel/shemaquerybuilder.stories.tsx | 78 ++ .../pills/customFlowLines/connection.tsx | 80 ++ .../pills/customFlowLines/connectionDrag.tsx | 0 .../pills/customFlowLines/index.ts | 0 .../attributepill-full.stories.tsx | 59 + .../attributepill/attributepill.module.scss | 151 ++ .../attributepill.module.scss.d.ts | 24 + .../attributepill/attributepill.stories.tsx | 10 +- .../attributepill/attributepill.tsx | 153 ++ .../attributepill}/checkInput.ts | 0 .../getAttributeBoolOperators.ts | 0 .../customFlowPills/attributepill/index.ts | 0 .../attributepill/operator/index.ts | 1 + .../operator}/operatorselect.module.scss | 2 +- .../operator}/operatorselect.module.scss.d.ts | 0 .../operator}/operatorselect.tsx | 4 +- .../attributepill/select-component.tsx | 88 ++ .../attributepill/variables.module.scss | 0 .../attributepill/variables.module.scss.d.ts} | 0 .../pills/customFlowPills/edge-line.tsx} | 37 +- .../entitypill/entitypill-full.stories.tsx | 49 + .../entitypill/entitypill.module.scss | 175 +++ .../entitypill/entitypill.module.scss.d.ts | 25 + .../entitypill/entitypill.stories.tsx | 14 +- .../customFlowPills/entitypill/entitypill.tsx | 65 + .../pills/customFlowPills/entitypill/index.ts | 0 .../functionpill/SelectFunction.tsx | 89 ++ .../functionpill/functionpill.module.scss | 167 +++ .../functionpill.module.scss.d.ts | 36 + .../functionpill/functionpill.stories.tsx | 74 + .../functionpill/functionpill.tsx | 166 +++ .../customFlowPills/functionpill/index.ts | 1 + .../pills/customFlowPills/index.ts | 0 .../modifierpill/modifierpill.module.scss | 55 + .../modifierpill.module.scss.d.ts | 19 + .../modifierpill/modifierpill.tsx | 33 + .../modifierpill/mopdifierpill.stories.tsx | 66 + .../modifierpill/select-modifier.tsx | 88 ++ .../customFlowPills/relationpill/index.ts | 0 .../relationpill/relation-full.stories.tsx | 55 + .../relationpill/relationpill copy.txt | 242 ++++ .../relationpill.module copy.scss | 304 ++++ .../relationpill/relationpill.module.scss | 317 +++++ .../relationpill.module.scss.d.ts | 36 + .../relationpill/relationpill.stories.tsx | 16 +- .../relationpill/relationpill.tsx | 220 +++ .../dragging/dragAttribute.ts | 0 .../dragging/dragAttributesAlong.ts | 0 .../dragging/dragEntity.ts | 0 .../{usecases => pills}/dragging/dragPill.ts | 4 +- .../dragging/dragRelation.ts | 0 .../dragging/getClosestPill.ts | 0 .../lib/{ui => querybuilder}/pills/index.ts | 1 - .../querybuilder/pills/querypills.module.scss | 63 + .../pills/querypills.module.scss.d.ts | 13 + .../lib/querybuilder/usecases/addPill.ts | 94 -- .../shared/lib/querybuilder/usecases/index.ts | 6 - .../usecases/querybuilder-usecases.spec.ts | 8 - .../usecases/querybuilder-usecases.ts | 3 - libs/shared/lib/schema/panel/schema.tsx | 71 +- libs/shared/lib/schema/panel/schemaOLD.tsx | 18 +- .../schema/pills/nodes/entity/entity-node.tsx | 2 +- .../pills/nodes/relation/relation-node.tsx | 4 +- .../attributepill/attributepill.module.scss | 60 - .../attributepill.module.scss.d.ts | 7 - .../attributepill/attributepill.tsx | 96 -- .../entitypill/entitypill.module.scss | 49 - .../entitypill/entitypill.module.scss.d.ts | 7 - .../customFlowPills/entitypill/entitypill.tsx | 74 - .../relationpill/relationpill.module.scss | 113 -- .../relationpill.module.scss.d.ts | 11 - .../relationpill/relationpill.tsx | 114 -- .../lib/ui/pills/shared-ui-pills.spec.tsx | 11 - libs/shared/lib/ui/pills/shared-ui-pills.tsx | 14 - libs/shared/package.json | 2 +- libs/storybook/.storybook/main.ts | 11 +- libs/storybook/package.json | 22 +- libs/workspace/ui/Button.tsx | 22 - libs/workspace/ui/index.tsx | 2 - libs/workspace/ui/package.json | 19 - libs/workspace/ui/tsconfig.json | 5 - pnpm-lock.yaml | 1240 +++++++++++++---- 136 files changed, 6061 insertions(+), 1440 deletions(-) delete mode 100644 .eslintrc.js delete mode 100644 .eslintrc.json create mode 100644 apps/web/.eslintignore create mode 100644 apps/web/.eslintrc.json delete mode 100644 apps/web/cssmodule.d.ts delete mode 100644 apps/web/image.d.ts create mode 100644 apps/web/public/assets/icons/ExportIcon.png create mode 100644 apps/web/public/assets/login-screen/github.png create mode 100644 apps/web/public/assets/login-screen/google.png create mode 100644 apps/web/src/components/navbar/AddDatabaseForm/add-database-form.module.scss create mode 100644 apps/web/src/components/navbar/AddDatabaseForm/index.tsx create mode 100644 apps/web/src/components/navbar/MenuList.scss create mode 100644 apps/web/src/components/navbar/logogp.png create mode 100644 apps/web/src/components/navbar/logogpwhite.png create mode 100644 apps/web/src/components/navbar/navbar.module.scss create mode 100644 apps/web/src/components/navbar/navbar.tsx create mode 100644 libs/shared/lib/data-access/api/schema.ts create mode 100644 libs/shared/lib/data-access/authorization/authorizationHook.tsx rename libs/shared/lib/data-access/{theme => store}/colorPaletteConfigSlice.ts (93%) create mode 100644 libs/shared/lib/data-access/store/configSlice.ts create mode 100644 libs/shared/lib/data-access/store/sessionSlice.ts create mode 100644 libs/shared/lib/querybuilder/graph/graphology/JSONParser.tsx create mode 100644 libs/shared/lib/querybuilder/graph/graphology/model.ts create mode 100644 libs/shared/lib/querybuilder/graph/graphology/utils.ts create mode 100644 libs/shared/lib/querybuilder/graph/logic/queryFunctions.tsx create mode 100644 libs/shared/lib/querybuilder/graph/reactflow/handles.tsx create mode 100644 libs/shared/lib/querybuilder/graph/reactflow/model.tsx rename libs/shared/lib/querybuilder/{usecases => graph/reactflow}/pillHandles.ts (100%) rename libs/shared/lib/querybuilder/{usecases/createReactFlowElements.ts => graph/reactflow/utils.ts} (95%) create mode 100644 libs/shared/lib/querybuilder/index.ts rename libs/shared/lib/{ui/pills/shared-ui-pills.module.scss => querybuilder/panel/nodeUtils.ts} (100%) create mode 100644 libs/shared/lib/querybuilder/panel/shemaquerybuilder.stories.tsx create mode 100644 libs/shared/lib/querybuilder/pills/customFlowLines/connection.tsx rename libs/shared/lib/{ui => querybuilder}/pills/customFlowLines/connectionDrag.tsx (100%) rename libs/shared/lib/{ui => querybuilder}/pills/customFlowLines/index.ts (100%) create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill-full.stories.tsx create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.module.scss create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.module.scss.d.ts rename libs/shared/lib/{ui => querybuilder}/pills/customFlowPills/attributepill/attributepill.stories.tsx (85%) create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.tsx rename libs/shared/lib/querybuilder/{usecases/attribute => pills/customFlowPills/attributepill}/checkInput.ts (100%) rename libs/shared/lib/querybuilder/{usecases/attribute => pills/customFlowPills/attributepill}/getAttributeBoolOperators.ts (100%) rename libs/shared/lib/{ui => querybuilder}/pills/customFlowPills/attributepill/index.ts (100%) create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/operator/index.ts rename libs/shared/lib/{ui/pills/customFlowPills/attributepill => querybuilder/pills/customFlowPills/attributepill/operator}/operatorselect.module.scss (97%) rename libs/shared/lib/{ui/pills/customFlowPills/attributepill => querybuilder/pills/customFlowPills/attributepill/operator}/operatorselect.module.scss.d.ts (100%) rename libs/shared/lib/{ui/pills/customFlowPills/attributepill => querybuilder/pills/customFlowPills/attributepill/operator}/operatorselect.tsx (96%) create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/select-component.tsx rename libs/shared/lib/{ui => querybuilder}/pills/customFlowPills/attributepill/variables.module.scss (100%) rename libs/shared/lib/{ui/pills/shared-ui-pills.module.scss.d.ts => querybuilder/pills/customFlowPills/attributepill/variables.module.scss.d.ts} (100%) rename libs/shared/lib/{ui/pills/customFlowLines/connection.tsx => querybuilder/pills/customFlowPills/edge-line.tsx} (54%) create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.module.scss create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.module.scss.d.ts rename libs/shared/lib/{ui => querybuilder}/pills/customFlowPills/entitypill/entitypill.stories.tsx (78%) create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx rename libs/shared/lib/{ui => querybuilder}/pills/customFlowPills/entitypill/index.ts (100%) create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/SelectFunction.tsx create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.module.scss create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.module.scss.d.ts create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.stories.tsx create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.tsx create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/index.ts rename libs/shared/lib/{ui => querybuilder}/pills/customFlowPills/index.ts (100%) create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/modifierpill.module.scss create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/modifierpill.module.scss.d.ts create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/modifierpill.tsx create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/mopdifierpill.stories.tsx create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/select-modifier.tsx rename libs/shared/lib/{ui => querybuilder}/pills/customFlowPills/relationpill/index.ts (100%) create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full.stories.tsx create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill copy.txt create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module copy.scss create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts rename libs/shared/lib/{ui => querybuilder}/pills/customFlowPills/relationpill/relationpill.stories.tsx (76%) create mode 100644 libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx rename libs/shared/lib/querybuilder/{usecases => pills}/dragging/dragAttribute.ts (100%) rename libs/shared/lib/querybuilder/{usecases => pills}/dragging/dragAttributesAlong.ts (100%) rename libs/shared/lib/querybuilder/{usecases => pills}/dragging/dragEntity.ts (100%) rename libs/shared/lib/querybuilder/{usecases => pills}/dragging/dragPill.ts (95%) rename libs/shared/lib/querybuilder/{usecases => pills}/dragging/dragRelation.ts (100%) rename libs/shared/lib/querybuilder/{usecases => pills}/dragging/getClosestPill.ts (100%) rename libs/shared/lib/{ui => querybuilder}/pills/index.ts (66%) create mode 100644 libs/shared/lib/querybuilder/pills/querypills.module.scss create mode 100644 libs/shared/lib/querybuilder/pills/querypills.module.scss.d.ts delete mode 100644 libs/shared/lib/querybuilder/usecases/addPill.ts delete mode 100644 libs/shared/lib/querybuilder/usecases/index.ts delete mode 100644 libs/shared/lib/querybuilder/usecases/querybuilder-usecases.spec.ts delete mode 100644 libs/shared/lib/querybuilder/usecases/querybuilder-usecases.ts delete mode 100644 libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.module.scss delete mode 100644 libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.module.scss.d.ts delete mode 100644 libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.tsx delete mode 100644 libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.module.scss delete mode 100644 libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.module.scss.d.ts delete mode 100644 libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.tsx delete mode 100644 libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.module.scss delete mode 100644 libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts delete mode 100644 libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.tsx delete mode 100644 libs/shared/lib/ui/pills/shared-ui-pills.spec.tsx delete mode 100644 libs/shared/lib/ui/pills/shared-ui-pills.tsx delete mode 100644 libs/workspace/ui/Button.tsx delete mode 100644 libs/workspace/ui/index.tsx delete mode 100644 libs/workspace/ui/package.json delete mode 100644 libs/workspace/ui/tsconfig.json diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 5b999efa4..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - root: true, - // This tells ESLint to load the config from the package `eslint-config-custom` - extends: ["custom"], - settings: { - next: { - rootDir: ["apps/*/"], - }, - }, -}; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 06cc47d9a..000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "root": true, - "ignorePatterns": ["**/*"], - "plugins": ["@nrwl/nx"], - "overrides": [ - { - "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], - "rules": { - "@nrwl/nx/enforce-module-boundaries": [ - "error", - { - "enforceBuildableLibDependency": true, - "allow": [], - "depConstraints": [ - { - "sourceTag": "*", - "onlyDependOnLibsWithTags": ["*"] - } - ] - } - ] - } - }, - { - "files": ["*.ts", "*.tsx"], - "extends": ["plugin:@nrwl/nx/typescript"], - "rules": {} - }, - { - "files": ["*.js", "*.jsx"], - "extends": ["plugin:@nrwl/nx/javascript"], - "rules": {} - } - ] -} diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 983caf67d..bb4060bde 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,12 +24,12 @@ install:js: policy: push script: - pnpm install - only: - changes: - - pnpm-lock.yaml - - .gitlab-ci.yml - - apps/**/* - - libs/**/* + # only: + # changes: + # - pnpm-lock.yaml + # - .gitlab-ci.yml + # - apps/**/* + # - libs/**/* # refs: # - main # - merge_requests diff --git a/apps/web/.eslintignore b/apps/web/.eslintignore new file mode 100644 index 000000000..3421a7d28 --- /dev/null +++ b/apps/web/.eslintignore @@ -0,0 +1,3 @@ +node_modules/* +node_modules/ +node_modules diff --git a/apps/web/.eslintrc.json b/apps/web/.eslintrc.json new file mode 100644 index 000000000..2707c1f41 --- /dev/null +++ b/apps/web/.eslintrc.json @@ -0,0 +1,25 @@ +{ + "extends": ["plugin:react-hooks/recommended"], + "parserOptions": { + "sourceType": "module", + "ecmaVersion": "latest", + "ecmaFeatures": { + "jsx": true + } + }, + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/web/cssmodule.d.ts b/apps/web/cssmodule.d.ts deleted file mode 100644 index b98cabe73..000000000 --- a/apps/web/cssmodule.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -declare module '*.module.css' { - const classes: { readonly [key: string]: string }; - export default classes; -} - -declare module '*.module.scss' { - const classes: { readonly [key: string]: string }; - export default classes; -} - -declare module '*.module.sass' { - const classes: { readonly [key: string]: string }; - export default classes; -} - -declare module '*.module.less' { - const classes: { readonly [key: string]: string }; - export default classes; -} - -declare module '*.module.styl' { - const classes: { readonly [key: string]: string }; - export default classes; -} diff --git a/apps/web/image.d.ts b/apps/web/image.d.ts deleted file mode 100644 index 9cc005a33..000000000 --- a/apps/web/image.d.ts +++ /dev/null @@ -1,48 +0,0 @@ -/// <reference types="react" /> -/// <reference types="react-dom" /> - -declare module '*.svg' { - import * as React from 'react'; - - export const ReactComponent: React.FunctionComponent< - React.SVGProps<SVGSVGElement> & { title?: string } - >; - - const src: string; - export default src; -} - -declare module '*.bmp' { - const src: string; - export default src; -} - -declare module '*.gif' { - const src: string; - export default src; -} - -declare module '*.jpg' { - const src: string; - export default src; -} - -declare module '*.jpeg' { - const src: string; - export default src; -} - -declare module '*.png' { - const src: string; - export default src; -} - -declare module '*.avif' { - const src: string; - export default src; -} - -declare module '*.webp' { - const src: string; - export default src; -} diff --git a/apps/web/package.json b/apps/web/package.json index 8dd5c441d..7b5586514 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -4,13 +4,17 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host local.graphpolaris.com --port 4200", + "dev2": "vite --host local.graphpolaris.com --port 4200", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "lint": "eslint *.ts*", + "test": "vitest run" }, "dependencies": { "@graphpolaris/shared": "workspace:*", "@mui/base": "5.0.0-alpha.118", + "@mui/icons-material": "^5.11.11", "@mui/material": "^5.11.13", "@reduxjs/toolkit": "^1.9.2", "graphology": "^0.25.1", @@ -29,11 +33,13 @@ "@types/react-dom": "^18.0.11", "@types/react-grid-layout": "^1.3.2", "@types/styled-components": "^5.1.26", + "@vitejs/plugin-basic-ssl": "^1.0.1", "@vitejs/plugin-react-swc": "^3.0.0", "graphology-types": "^0.24.7", "react-is": "^18.2.0", "typescript": "^4.9.3", "vite": "^4.2.0", + "vite-plugin-dts": "^2.1.0", "vitest": "^0.29.2" } } diff --git a/apps/web/public/assets/icons/ExportIcon.png b/apps/web/public/assets/icons/ExportIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..62976b8a7902bc100cad532959e3d9e0d5fea42c GIT binary patch literal 124133 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±i#J0# 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 X@7|YjFMhd=uB2^ZexLt8)`|ZQZUiIv literal 0 HcmV?d00001 diff --git a/apps/web/public/assets/login-screen/github.png b/apps/web/public/assets/login-screen/github.png new file mode 100644 index 0000000000000000000000000000000000000000..55f90676824d4901f6fe7a50ed11f6d4af3d6fef GIT binary patch literal 7204 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 literal 0 HcmV?d00001 diff --git a/apps/web/public/assets/login-screen/google.png b/apps/web/public/assets/login-screen/google.png new file mode 100644 index 0000000000000000000000000000000000000000..f27bb2433042aea5fc34e19fcf90944430ec331b GIT binary patch literal 8001 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 literal 0 HcmV?d00001 diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx index 3b13b1f87..9ae2badfa 100644 --- a/apps/web/src/app/app.tsx +++ b/apps/web/src/app/app.tsx @@ -5,47 +5,37 @@ import Panel from '../components/panels/panel'; import { RawJSONVis } from '@graphpolaris/shared/lib/vis/rawjsonvis/rawjsonvis'; import SemanticSubstrates from '@graphpolaris/shared/lib/vis/semanticsubstrates/semanticsubstrates'; import { Schema } from '@graphpolaris/shared/lib/schema/panel'; -import { GetUserInfo } from '@graphpolaris/shared/lib/data-access/api'; -import { QueryBuilder } from '@graphpolaris/shared/lib/querybuilder/panel'; +import { + GetAllDatabases, + GetUserInfo, +} from '@graphpolaris/shared/lib/data-access/api'; +import { QueryBuilder } from '@graphpolaris/shared/lib/querybuilder'; import { assignNewGraphQueryResult, useAppDispatch, } from '@graphpolaris/shared/lib/data-access/store'; -import { AuthorizationHandler } from '@graphpolaris/shared/lib/data-access/authorization'; - -function useIsAuthorized() { - const [userAuthorized, setUserAuthorized] = useState(false); - - const authCallback = async () => { - setUserAuthorized(true); - - // Print the user that is currently logged in - const user = await GetUserInfo(); - console.log(user); - }; - - AuthorizationHandler.instance().setCallback(authCallback); - - // Attempt to Authorize the user - const authorize = async () => { - const authorized = await AuthorizationHandler.instance().Authorize(); - setUserAuthorized(authorized); - }; - - useEffect(() => { - authorize(); - }, []); - - return userAuthorized; -} +import { + AuthorizationHandler, + useAuthorization, +} from '@graphpolaris/shared/lib/data-access/authorization'; +import { Navbar } from '../components/navbar/navbar'; export function App() { const dispatch = useAppDispatch(); - const userIsAuthorized = useIsAuthorized(); + const authorization = useAuthorization(); + + useEffect(() => { + if (authorization.userAuthorized) { + GetAllDatabases().then((d) => { + console.log(d); + }); + } + }, [authorization.userAuthorized]); return ( <> - {/* {!userIsAuthorized && <LoginScreen />} */} + {!authorization.userAuthorized && <LoginScreen />} + <Navbar /> <GridLayout className="layout" cols={10} diff --git a/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.module.scss b/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.module.scss new file mode 100644 index 000000000..97642ae37 --- /dev/null +++ b/apps/web/src/components/navbar/AddDatabaseForm/add-database-form.module.scss @@ -0,0 +1,75 @@ +.wrapper { + height: 100vh; + display: flex; + align-items: center; + width: 100vw; + background-color: rgba(0, 0, 0, 0.87); + position: absolute; + z-index: 1225; +} + +.authWrapper { + font-family: Poppins, sans-serif; + display: flex; + flex-direction: column; + flex-wrap: wrap; + justify-content: center; + max-width: 400px; + margin: auto; + overflow: auto; + min-height: 300px; + background-color: #f7f8fc; + padding: 30px; + border-radius: 5px; + padding-bottom: 300px; +} + +.header { + text-align: center; +} + +.formWrapper { +} + +.loginContainer { + margin: 5px 0px !important; +} + +.loginContainerRow { + display: flex; + flex-direction: row; + margin: 5px 0px; +} + +.loginContainerButton { + margin-top: 10px !important; + + & button { + width: 100%; + } +} + +.hostLabel { + flex: 1 0 66.66667%; + + & div { + width: 95%; + } +} + +.portLabel { + flex: 1 0 33.33334%; +} + +.userLabel { + width: 100%; +} + +passLabel { + width: 100%; +} + +cancelButton { + margin-top: 2.5%; +} + diff --git a/apps/web/src/components/navbar/AddDatabaseForm/index.tsx b/apps/web/src/components/navbar/AddDatabaseForm/index.tsx new file mode 100644 index 000000000..54c1b0ac0 --- /dev/null +++ b/apps/web/src/components/navbar/AddDatabaseForm/index.tsx @@ -0,0 +1,205 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/* istanbul ignore file */ +/* The comment above was added so the code coverage wouldn't count this file towards code coverage. + * We do not test components/renderfunctions/styling files. + * See testing plan for more details.*/ +import React, { useState } from 'react'; +import { TextField, Button, NativeSelect } from '@mui/material'; +import styles from './add-database-form.module.scss'; +import { + AddDatabaseRequest, + DatabaseType, + databaseNameMapping, +} from '@graphpolaris/shared/lib/data-access'; + +/** AddDatabaseFormProps is an interface containing the AuthViewModel. */ +export interface AddDatabaseFormProps { + open: boolean; + onClose(): void; + onSubmit(data: AddDatabaseRequest): void; +} + +/** AddDatabaseFormState is an interface containing the databasehost information. */ +export interface AddDatabaseFormState extends AddDatabaseRequest { + // username: string; + // password: string; + // hostname: string; + // port: number; + // databaseName: string; + // internalDatabase: string; + // databaseType: string; + // styles: Record<string, string>; // TODO: check if needed +} + +/** AddDatabaseForm is the View implementation for the connect screen that will be rendered. */ +export default function AddDatabaseForm(props: AddDatabaseFormProps) { + const [state, setState] = useState<AddDatabaseFormState>({ + username: 'root', + password: 'DikkeDraak', + url: 'https://datastrophe.science.uu.nl/', + port: 8529, + name: 'Tweede Kamer Dataset', + internal_database_name: 'TweedeKamer', + // styles: props.styles, // FIXME + type: DatabaseType.ArangoDB, + }); + + /** + * Validates if the port value is numerical. Only then will the state be updated. + * @param port The new port value. + */ + function handlePortChanged(port: string): void { + if (!isNaN(Number(port))) setState({ ...state, port: Number(port) }); + } + + /** Handles the submit button click. Calls the onSubmit in the props with all the fields. */ + function handleSubmitClicked(): void { + props.onSubmit(state); + } + + return props.open ? ( + <div + className={styles.wrapper} + onMouseDown={() => { + props.onClose(); + }} + > + <div + className={styles.authWrapper} + onMouseDown={(e) => { + e.stopPropagation(); + }} + > + <h1 className={styles.header}>Database Connect</h1> + <form + className={styles.formWrapper} + onSubmit={(event: React.FormEvent) => { + event.preventDefault(); + handleSubmitClicked(); + }} + > + <div className={styles.loginContainer}> + <TextField + className={styles.passLabel} + label="Database name" + type="databaseName" + value={state.name} + onChange={(event) => + setState({ + ...state, + name: event.currentTarget.value, + }) + } + required + /> + </div> + <div className={styles.loginContainer}> + <NativeSelect + className={styles.passLabel} + value={databaseNameMapping[state.type]} + onChange={(event) => { + setState({ + ...state, + type: databaseNameMapping.indexOf(event.currentTarget.value), + }); + }} + > + {databaseNameMapping.map((dbName) => ( + <option value={dbName}>{dbName}</option> + ))} + </NativeSelect> + </div> + <div className={styles.loginContainerRow}> + <TextField + className={styles.hostLabel} + label="Hostname/IP" + type="hostname" + value={state.url} + onChange={(event) => + setState({ + ...state, + url: event.currentTarget.value, + }) + } + required + /> + <TextField + className={styles.portLabel} + label="Port" + type="port" + value={state.port} + onChange={(event) => handlePortChanged(event.currentTarget.value)} + required + /> + </div> + <div className={styles.loginContainer}> + <TextField + className={styles.userLabel} + label="Username" + type="username" + value={state.username} + onChange={(event) => + setState({ + ...state, + username: event.currentTarget.value, + }) + } + required + /> + </div> + <div className={styles.loginContainer}> + <TextField + className={styles.passLabel} + label="Password" + type="password" + value={state.password} + onChange={(event) => + setState({ + ...state, + password: event.currentTarget.value, + }) + } + required + /> + </div> + <div className={styles.loginContainer}> + <TextField + className={styles.passLabel} + label="Internal database" + type="internalDatabaseName" + value={state.internal_database_name} + onChange={(event) => + setState({ + ...state, + internal_database_name: event.currentTarget.value, + }) + } + required + /> + </div> + <div className={styles.loginContainerButton}> + <Button variant="outlined" type="submit"> + Submit + </Button> + <Button + className={styles.cancelButton} + variant="outlined" + onClick={() => { + props.onClose(); + }} + > + Cancel + </Button> + </div> + </form> + </div> + </div> + ) : ( + <></> + ); +} diff --git a/apps/web/src/components/navbar/MenuList.scss b/apps/web/src/components/navbar/MenuList.scss new file mode 100644 index 000000000..af7f9f647 --- /dev/null +++ b/apps/web/src/components/navbar/MenuList.scss @@ -0,0 +1,17 @@ +/* This file is dependent on src/data/domain/entity/customization/colours.tsx +* You can match a class name with a colour palette by naming the class 'menuList-' + colour palette name +*/ + +.menuList-default{ + .MuiPaper-root{ + background-color: #fffdfa; + color: #000000; + } +} + +.menuList-dark{ + .MuiPaper-root{ + background-color: #171721; + color: #ffffff; + } +} \ No newline at end of file diff --git a/apps/web/src/components/navbar/logogp.png b/apps/web/src/components/navbar/logogp.png new file mode 100644 index 0000000000000000000000000000000000000000..cebd2d5f6a7e895c4858d7b9b0153a245fd0d7f9 GIT binary patch literal 4300 zcmV;-5Hs(IP)<h;3K|Lk000e1NJLTq006Q8001Qj1^@s6=IOJO000n^Nkl<Zc-rlp zdze(!mBoMOKI+*`^HfJsgBo#6#79sGs1QNpI6hG$5)#yJuo)#9Mx!%PC&sApF&d4J zpyGg&$oPoHCzJ6F7!g515OmNes2~s&Xz1?hTXpZrAGHcDm8$MW9R;oZef4!$)xGDQ zyU*Hduf5OhhMLu^W;LtX)?wze805YS$KwTUb?GkMT8_T_%Bbennr(AH{@ut^oYbmS zhq+OBj?H9m&G*@5+Nn3+X=|>n+2?8Qr#boV(?^k9{{CTpYx88Mt#u%U{2@_G+oBVO z9`f!(b6%@?w`N;EkRSZPamHD>a$mo>^=2;&zhN+#u+ZHJf`K;3FBma&z|#BYEU0<7 zW?L_i?>*yKC)2d%$V^-F1cJhTSYt{$#UbpT_JV^UT6Fvohb(#Ur5ZEWY%2uvz5jd? z+0}p9KbdQp=n?L1986RIuo%aXOj{Ce)YB~l1KqY@;faF}Tk^o{8klRg6#@B?F{e1G zRm%@e1i72MDC}np#&wDSwiXg8@O?1GU~JgK6vBao!NL=c8o1>5Gix%VW*u4zuzLOf z0QsTwPo_)Lhli(wmb*+)_@<#KCotgpkjh3F$82nb7z~aLdnEn*w^KI%`pH8FExq@} z`JXoElKT0HZgwlNzXN^(90Lpl-T<1n!g=ln{0p!W_{-;Ty}g0kfCk{zEnVMEwcj2- z=LDy2!|Hug!G?)$7#>h)v*g=!s<HSf@cf7{aLBhAtTlkeOBBBgJ%4dqvSIAHo;yB2 z{JJSyGQl+PHDC~MD6l;c0n34xfaigwKz<tnazEf1pr=4`IIyUb=XM9q)6bGwU<2?b zFdKOLGxEowz$3s9fyX}wzl;DT0Z##^0<E7ZkpFP*aZZ=DtA{0n=9@el^#u$Tf;PzK zA(4vk{Sp&f3V8_H48Cvi6M!*TAauONt?90f>wE1uZTL0!bOy}Z1D66P>i6>OQ2>?! z4*=H#E4~6i-W8Z8;LeSGUJvXJWPv+@8@Ch)TLF-}0>c!L3%8V!opwQd@d&nyR`$=( ze5+@}y*BBF2rm<n?i%6w#N<SgnNtlRe#&l)5(BtVuxDMgc0#Y*vcXe-I;x`p+EHny zQNO!lnRUQ=v9hOy9S>ZoMf|GRW5DUL*Y|*b0G<FY0*>1T?`bXYd*Jm?A{)JGUp#r? zQBIepcLpY++;t{h*gbCzQBZ=vC7H3fz9FB3FlRB=U=6q_NTn=c0Qpuy4seo|WHQ&c zP_Pr6mT2r#7Z0Cy#FY<Kns683=fLp-a5FF!cocX`bKVa)R&3@g6}<vX&=q?E|0qq} zqVsnG4hGf$4+EjDmjw=!ChV^3EeD>~`QOzwn*^X{pqIu^h#emTwiAnOr{h-x^MN_a z9_^9IH-G`UeyDS20)O7*!OhIZ=8t~=0Jufkd$i`%Qg)r)fp5vjRvBTovYFjN0^AYk zulu#Yhq~95`s_epR!m=a1r8D0exQ5aA9zPurnC-kYn|h&VP}okL)Tg^|9spokpFne zaO#?t4NitFcQ`iM1ILiaS^_Ll0sJ($iQ*OfRMC%N&cRDt{G<T`UJ{IB37Rd&w<OXA z;~2as=u5t7Ley*;AHDRn>Bs%zzsntXfb^0J<bbPyUzMfGKLHN{ck6h7(||w7p}PP# z2pB5_($l54AJ-y>0_4y1U7XSL(rSMMu8~%M2Y5k1?xXbG0KBhidmONDS(aM?Tn*e& zCDpsS*AbdyDN{vS@G$~IXYGC9T>-Ws2IL0dpMbFfLzAvu5Bwi+orogn#5H_ilrrJh zG*(_ddQaK04fszH%yT-g4=_>dnFY?#T<!*@OFrHT{93*(fq1aia#v+!SAqBfaG{Fj zzXkG)%a1jg;DdgK+_jF4_Si@O#^To%U2L3USAwDhE&@9h;gksm*S91ZipMyynR!vr zHxsR$khF!f|Mat=(+2(gnJD)4uo$SG)6G=^!3y9^Iiti%YbzYr4LDynAGbQ-axHMl z4;%EVSE}wW)@{#v0oN*MniPCp#pXKzR|uHDsld$LfD6Pj%M=8?1eSdSun}UQ&Su!8 z02#(I+*KOqQDCwPWKVj1oaVLzaC--Zq6?f1{1RBAb7u;;y8t7BOC)jpvKq|jvy-)E z_X5v~fo&{E;<Zp8fw)=0vrxwj1}@WgqkxY#1M-Yt9B=A_<%j#W<z^=x?v=L&TPPN5 zIEF+z!u24?fmPSS8jF{<xV|N9GlV&_G5Z<clFnG7&`@Zx5EbF<c$Q>3*ekM4w>M+P zJb%rp(}!GipA~=)jX7YdqA<Qtap$bkix`uYTT<uD0~U+nA~|%l#th}u>%}U4fPYoD z#OXxu(*^DkAl51kUZC_@C*3+tQllIgD<q|!)%($6yEO1^;3VMYP6D|<@b}70YxLQ% z%2vNr-4So~y{+!gjhf>OpuOFzeH0YaHOJ*K5WXjw@;zlCzseXpsDPYZCXXuFAhG^% zb*M`AZmf-Y5b!@6f&9{qr;uE;^jm(^a+_<zJ&g-~#*%LV8yWm`v7H76KV`{B5mCY5 zCL_FrHbGJrhzQ#pjBoMN7RNC-j)k-#-%^wte#YXs2FDh@o++%G;N{%Lc{h!ovEO-j zXQgjT4Es?TgI*?(RkNq0lPLrDtzym9dhe?nk}lo8nE5@ue?yCWzvMzk_JkI8b?p4N z#5y}GZSGS6$e}=VS1bsx*0IMb$Octl^DauYCCliX^9A4%vu1(s>$(%G^sAT4H;)N0 z^Q-7(BcJuu_{%CB(<as*tsZ$r<qEu9g><OkA6IK%zNVAK(2F*c7Ex>H`eAOk7ZvtE zPfj>4B<d`YwUP}*GISh+pRrh?B7jSKBgWvRz$8K(hnUnbc)rDSu^23l!$x2EHs9Aq zxp4u}tCSwss9ew_fbYaKaV1+_;j{I?eP!)AKNc`~Y1sG5&U;IJ%^Nyk<@GAER+klH zvs8tTm!A&=rj(t#PVy-#8}C^G8Kq&r49KSeM^xC@nk6vL(fgeQ@LOXHUC9<IV;v^& zZ>ZAKAEX*&aXVwJQ!zcD%J*+}62t&63aD2}_y4chxJ`ky>~D&_9^HF}tu0F@8&eqO zQs{3m1_NFej0=V4O|Y^CysX7b7=l)d4N8jHViJ~g+E54}%Efem59v%q6j=%_1|kDs z3nuw$-pO7aX1X<NLDt0<ry+Jzfuu%x&-Fl{Zwx@?)>1(``;r7me=n}m8Caz2raD-- zcdM{Tsg-0@$(IL2zKZ@|7dy|Xl6fYT-Qx;%16s>8c(y{2mRnAsI9Tjn4ahwduxmRQ z@7;E^c3}&O*QF{@uaNd0uONL!%)Yb~6dRq?xA6{Er+P2Wxpj>p{w(X*$W2?E1UN4E zSqp9v#7@HEB@H+RFKsY>gvDZfNMsz0Yw%JAM^XdV;-vxC;P?)1%3vL9L;M9TUPI%W zx?ScTH0IZ(9je!3sjOd2Yi)>i7)q}nsZuWi@0}`uv=vfQAr{Q_RUBE@361Q>Bub?w zzljCRXSJ>Vj@YU6nxPERS|ONhJ9;%P8xrzKHQieBnMcQuHu#<JTW8ElvyW77e4>Ks zLb3kHQZc^S#fDw{2<yF`^8z<H%4EG~og{YSriN?qvf$PUlxCC6@UjM{-r{CAjc5#b zDF>$>+&XZ)O*NXE#F9<UZBEpW$=kYDhn_###vK2!7^0LyPmDP((lI4xk5sxtY&5l# z4$8+`Q)%_A6BUZYV>;2b=m8vAVc~klHYTjn_vig*U@O&Y-LC^+byZedQiY96M6yJ3 zWrq%eqJJlXXG?6ox+7Oga(x$Ap!#BH#htw&KbvYl-hQt?=l#@$A?{p@-N+(N*5Y^D ze2Nw@2}7cr!6eEk$Fn558QhG;#=gm$<lLO!?X0HmJI@|?`P6cjnyt-^5=effHqQ<M zRYGZcl+sOFOz~gRU!Ad>QdB8@k5qw@jTKL4s$<ZZme-F3W-r!~yGUJ!QtF@k1?-ls zR3Uq$s{d-h8v^X9bluiY@INEaA3*iT8+w06JByAwOFxcD%u?1~OmQndsi3JGr9;O} zVXeE<yjCZ3Mgi|hYpf+@aZ?7@Gq`E6uE3dy@G=(H12=6TLGg3X;${qvXK+&nlLBk3 z&70)h9QBQ>61%-R^xXSw73ZHXKwIq~T?;%(@wtlo_4{wsf(f-ba?=**uSgC11u-po zgT}r?-G*!0ksRgeueUl6zm<a@1<qB_S<Uy0cqlv2efCgK`s-x?^wclELek`k4#s;} z8xfZXtjV%~*ai4Ebw@t!gG*N9T?!mkw&&r-HlLQayG4D-_($z?|8e^K?(V|znPkiF zl8NvDsfnUY$XdcYxCw)CHg!2n8bZ(Fc*XanZibt}2BhY-1`TJeO6>N^kw2Q$o?e_K zhP+-IBnegD`;;Bmq>jYpZ26;xZ>raO8?cA;@X@jF*Qy_TOxc1}>Xgh<7hyz&fSInI z^v8TL_JV-=?+Vlhbnhv($WD~+E~wH{E8m3riL&2a3fQNWalRQF>vj>trv>s9ijN`O zpnmZTWz+q&GdQy(C%yJY`pyNDnSSR<Z@SL1v2L{fMwf7aBNlhlE{O(1As24Wg9$^j z-X{z!HXrGkI0#H~ZgZ-8<2twZD@U9?sWU~-8^9TI&e4(+^#VteoH|+QuBC#Wo2h>5 z>MET9t8;&-46wVHxm9|5qN@D{0eN|qwA%vwRDeA}^K^CHm&JrjW5+jZZe0cLmMU3k zhFETXOk3xasg`U3Ao2p%n-sUaPgKxG3fB7-%x{+!qfL}O&mdbMk~v$fI8a$}jW&I5 zk+hmw*4-$r$%=O5-Yoe(9ysTyxEmm{a5dH=0{Dl}$>)(@yz3}aZ=3pLSbvvKFvKKW z5}D$&6H#c%H;32)7~kM$TpY(>ttH<Y5w=C-NzH4f>zFlm$5)1ones_0_oQBFIlDz^ zGiXO&m3H*302p;Jew|){+Fz})aZ13?>E}U`3k?G4U@bsK!Q-ev*-%05bY^Z|tjLUF z#jg>Q7CvF>PN)vZ3Va)MpM@$uOlf{$mD(U1+pK8Px|B9Cl4a+X?mJ)QdhOktr?sz( z0eLgucKZKXJbTLswoAQtU^dx2F`W+kIgZ_UdRTybTZo@@aa{wZh}9^9=H|q_rl9Lt ztD~Lf9MSmOFU$YCs08Gt)a(nhPWSo_A9pWnoZfTtsru6clXyB3i&%h^!RzH?lAAzm zi`|LW+ENXTABVesnLu2#Z5hY_hn{)A{c!zWb6cbOae13~&0>i>gOl;_k{)C|BG)PA z%P95Qy0F_>%Y)r#4;?$H2I87+JwOH+eC9;f1UoHmwhiaxo#e|V?M24b=7?(`>k+u= zc}-E*vsSh4JbTy=Ce}b)v#k%v00X{1#cl|@ytE<NYgEBWOe^gIm*hl~-?Q<fw(heJ zAN{3e;x!aE4<4)1HL2O>Y@h0%7(Rc`dGzkJuz%WHKgl=wz1yPH{HA=v*bmq3`tq<- zC)h8!-~6%`S1t2wt3U>L`SG!)S87Q=&n+~zg{eC~XzDX>$ncwMi(0d73&;SAp1T3- uHzh+u&~MOXH4xXVW;Lr>&1&|QwZ8&ujkWGCqj`n^0000<MNUMnLSTaYIa>b! literal 0 HcmV?d00001 diff --git a/apps/web/src/components/navbar/logogpwhite.png b/apps/web/src/components/navbar/logogpwhite.png new file mode 100644 index 0000000000000000000000000000000000000000..c83f02998f92e8b99c7c51416ac62968fe5fb302 GIT binary patch literal 4640 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 Wv~ddk<MA8-0000<MNUMnLSTYljI)LS literal 0 HcmV?d00001 diff --git a/apps/web/src/components/navbar/navbar.module.scss b/apps/web/src/components/navbar/navbar.module.scss new file mode 100644 index 000000000..e1faa9a78 --- /dev/null +++ b/apps/web/src/components/navbar/navbar.module.scss @@ -0,0 +1,44 @@ +.root { + display: flex; + overflow: hidden; +} + +.appBar { + display: flex; + flex-direction: column; + justify-content: center; + background-color: #fff; + height: 50px; +} + +.menubox { + display: flex; + flex-direction: row; + justify-content: flex-end; + width: 100%; +} + +.menuText { + margin: 5px; + color: #181520; + font-family: Poppins, sans-serif; + font-weight: 400; + font-size: 15px; + align-self: flex-end; + text-transform: none; +} + +.logo { + margin: auto; + display: block; +} + +.loginButton { + margin: 6px; +} + +.loginButtonText { + color: black; + font-weight: bold; +} + diff --git a/apps/web/src/components/navbar/navbar.tsx b/apps/web/src/components/navbar/navbar.tsx new file mode 100644 index 000000000..ef3d9c91e --- /dev/null +++ b/apps/web/src/components/navbar/navbar.tsx @@ -0,0 +1,325 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/* istanbul ignore file */ +/* The comment above was added so the code coverage wouldn't count this file towards code coverage. + * We do not test components/renderfunctions/styling files. + * See testing plan for more details.*/ +import React, { useState } from 'react'; +import { + AppBar, + Toolbar, + CssBaseline, + Typography, + MenuItem, + ListItemText, + Menu, + IconButton, + Button, +} from '@mui/material'; +import { AccountCircle } from '@mui/icons-material'; +import logo from './logogp.png'; +import logo_white from './logogpwhite.png'; +import AddDatabaseForm from './AddDatabaseForm'; +import { useTheme } from '@mui/material/styles'; +import styles from './navbar.module.scss'; +import { + AddDatabase, + AddDatabaseRequest, + GetAllDatabases, + RequestSchema, + useAuthorization, +} from '@graphpolaris/shared/lib/data-access'; +import { + updateCurrentDatabase, + updateDatabaseList, +} from '@graphpolaris/shared/lib/data-access/store/sessionSlice'; + +/** NavbarComponentProps is an interface containing the NavbarViewModel. */ +export interface NavbarComponentProps { + // changeColourPalette: () => void; FIXME move to redux +} + +/** NavbarComponentState is an interface containing the type of visualizations. */ +export interface NavbarComponentState { + isAuthorized: boolean; + clientID: string; + sessionID: string; + databases: string[]; + currentDatabase: string; + + // The anchor for rendering the user data (clientID, sessionID, add database, etc) menu + userMenuAnchor?: Element; + // The anchor for rendering the menu for selecting a database to use + selectDatabaseMenuAnchor?: Element; + // Determines if the addDatabaseForm will be shown + showAddDatabaseForm: boolean; +} + +/** NavbarComponent is the View implementation for Navbar */ +export const Navbar = (props: NavbarComponentProps) => { + const theme = useTheme(); + const authorization = useAuthorization(); + + // const { navbarViewModel, currentColours } = props; + // this.navbarViewModel = navbarViewModel; + + const [state, setState] = useState<NavbarComponentState>({ + isAuthorized: false, + clientID: authorization.userAuthorized ? authorization.userId || '' : '', + sessionID: authorization.sessionId || '', + databases: [], + currentDatabase: '', + + userMenuAnchor: undefined, + selectDatabaseMenuAnchor: undefined, + showAddDatabaseForm: false, + }); + + /** Closes the user menu. Also closes all nested menu's. */ + function closeUserMenu(): void { + // If a nested window is open, close the main user menu a bit later + if (state.selectDatabaseMenuAnchor != undefined) { + setState({ ...state, selectDatabaseMenuAnchor: undefined }); + setTimeout(() => setState({ ...state, userMenuAnchor: undefined }), 100); + } else setState({ ...state, userMenuAnchor: undefined }); + } + + function changeColourPalette() { + // TODO + } + + /** + * Called when the user clicks on the 'submit' button of the add database form. + */ + function onAddDatabaseFormSubmit( + request: AddDatabaseRequest + ): Promise<void | Response> { + return AddDatabase(request).then(() => + GetAllDatabases().then((databases) => { + updateDatabaseList(databases); + updateCurrentDatabase(request.name); + // When the database changes, request the new schema + console.log('databases ' + databases); + RequestSchema(request.name); + }) + ); + } + + const currentLogo = theme.palette.custom.logo == 'white' ? logo_white : logo; + + return ( + <div className={styles.root}> + <CssBaseline /> + <AppBar + title="GraphPolaris" + style={{ + zIndex: 1250, + backgroundColor: theme.palette.custom.background, + }} + position="fixed" + className={styles.appBar} + > + <Toolbar style={{ marginLeft: -5 }}> + <a href="https://graphpolaris.com/" className={styles.logo}> + <img src={currentLogo} className={styles.logo} /> + </a> + <div className={styles.menubox}> + <Button + className={styles.menuText} + style={{ color: theme.palette.custom.menuText }} + onClick={changeColourPalette} + > + Change Palette + </Button> + <Button + href="https://graphpolaris.com/" + className={styles.menuText} + style={{ color: theme.palette.custom.menuText }} + > + Home + </Button> + <Button + href="https://graphpolaris.com/index.php/products/" + className={styles.menuText} + style={{ color: theme.palette.custom.menuText }} + > + Products + </Button> + <Button + href="https://graphpolaris.com#usecases" + className={styles.menuText} + style={{ color: theme.palette.custom.menuText }} + > + Use Cases + </Button> + <Button + href="https://graphpolaris.com#earlyadoption" + className={styles.menuText} + style={{ color: theme.palette.custom.menuText }} + > + Contact + </Button> + {state.isAuthorized ? ( + <div> + <IconButton + color="inherit" + onClick={(event) => + setState({ + ...state, + userMenuAnchor: event.currentTarget, + }) + } + > + <AccountCircle htmlColor={theme.palette.custom.menuText} /> + </IconButton> + <Menu + id="user-menus" + anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} + transformOrigin={{ vertical: 'top', horizontal: 'left' }} + anchorEl={state.userMenuAnchor} + keepMounted + open={Boolean(state.userMenuAnchor)} + onClose={() => closeUserMenu()} + > + <MenuItem> + <ListItemText primary={'clientID: ' + state.clientID} /> + </MenuItem> + <MenuItem> + <ListItemText primary={'sessionID: ' + state.sessionID} /> + </MenuItem> + {/* <MenuItem + onClick={() => + navbarViewModel.handleLoginButtonClicked('google') + } + > + <ListItemText primary={'request new'} /> + </MenuItem> */} + {/*<MenuItem onClick={() => LocalStorage.instance().Reset()}>*/} + {/* <ListItemText primary={'Empty localstorage'} />*/} + {/*</MenuItem>*/} + <MenuItem + onClick={() => + setState({ + ...state, + userMenuAnchor: undefined, + showAddDatabaseForm: true, + }) + } + > + <ListItemText primary={'Add database'} /> + </MenuItem> + <MenuItem + onClick={(event) => + setState({ + ...state, + selectDatabaseMenuAnchor: event.currentTarget, + }) + } + > + <ListItemText primary={'Change database'} /> + </MenuItem> + <Menu + id="databases-menus" + anchorOrigin={{ vertical: 'top', horizontal: 'left' }} + transformOrigin={{ vertical: 'top', horizontal: 'right' }} + anchorEl={state.selectDatabaseMenuAnchor} + keepMounted + disableAutoFocusItem + open={Boolean(state.selectDatabaseMenuAnchor)} + onClose={() => closeUserMenu()} + > + {state.databases.length > 0 ? ( + state.databases.map((database) => ( + <MenuItem + key={database} + selected={database == state.currentDatabase} + onClick={() => { + if (state.currentDatabase != database) { + updateCurrentDatabase(database); + closeUserMenu(); + } + }} + > + <ListItemText primary={database} /> + </MenuItem> + )) + ) : ( + <MenuItem key="placeholder" value="" disabled> + no databases connected + </MenuItem> + )} + </Menu> + </Menu> + </div> + ) : ( + <div> + <Button + className={styles.loginButton} + onClick={(event) => + setState({ + ...state, + userMenuAnchor: event.currentTarget, + }) + } + > + <span + className={styles.loginButtonText} + style={{ + color: theme.palette.custom.menuText, + }} + > + Login + </span> + </Button> + {/* <Menu + id="user-menus" + getContentAnchorEl={null} + anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} + transformOrigin={{ vertical: 'top', horizontal: 'left' }} + anchorEl={state.userMenuAnchor} + keepMounted + open={Boolean(state.userMenuAnchor)} + onClose={() => closeUserMenu()} + > + <MenuItem + onClick={() => + navbarViewModel.handleLoginButtonClicked('google') + } + > + <ListItemText primary={'Login with Google'} /> + </MenuItem> + <MenuItem + onClick={() => + navbarViewModel.handleLoginButtonClicked('github') + } + > + <ListItemText primary={'Login with GitHub'} /> + </MenuItem> + <MenuItem + onClick={() => + navbarViewModel.handleLoginButtonClicked('free') + } + > + <ListItemText primary={'Login with free (debug)'} /> + </MenuItem> + </Menu> */} + </div> + )} + </div> + </Toolbar> + </AppBar> + <AddDatabaseForm + open={state.showAddDatabaseForm} + onClose={() => setState({ ...state, showAddDatabaseForm: false })} + onSubmit={(...params) => { + onAddDatabaseFormSubmit(...params); + setState({ ...state, showAddDatabaseForm: false }); + }} + /> + </div> + ); +}; diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 05136727d..4fcc69729 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,14 +1,22 @@ /// <reference types="vitest" /> -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react-swc' -import path from 'path' +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react-swc'; +import path from 'path'; +import basicSsl from '@vitejs/plugin-basic-ssl'; +import dts from 'vite-plugin-dts'; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), basicSsl(), + dts({ + insertTypesEntry: true, + }),], resolve: { alias: { - '@graphpolaris/shared/lib': path.resolve(__dirname, '../../libs/shared/lib') + '@graphpolaris/shared/lib': path.resolve( + __dirname, + '../../libs/shared/lib' + ), }, - } -}) + }, +}); diff --git a/libs/shared/.eslintrc.json b/libs/shared/.eslintrc.json index a2bdc3e17..2707c1f41 100644 --- a/libs/shared/.eslintrc.json +++ b/libs/shared/.eslintrc.json @@ -1,5 +1,12 @@ { "extends": ["plugin:react-hooks/recommended"], + "parserOptions": { + "sourceType": "module", + "ecmaVersion": "latest", + "ecmaFeatures": { + "jsx": true + } + }, "ignorePatterns": ["!**/*"], "overrides": [ { diff --git a/libs/shared/index.tsx b/libs/shared/index.tsx index df7053eff..1dc5085e3 100644 --- a/libs/shared/index.tsx +++ b/libs/shared/index.tsx @@ -1 +1 @@ -export * from './index' +// export * from './index'; diff --git a/libs/shared/lib/data-access/api/database.ts b/libs/shared/lib/data-access/api/database.ts index deabbe0f5..b627ba76d 100644 --- a/libs/shared/lib/data-access/api/database.ts +++ b/libs/shared/lib/data-access/api/database.ts @@ -2,6 +2,13 @@ import { AuthorizationHandler } from '@graphpolaris/shared/lib/data-access/authorization'; +export enum DatabaseType { + ArangoDB = 0, + Neo4j = 1, +} + +export const databaseNameMapping: string[] = ['arangodb', 'neo4j']; + export type AddDatabaseRequest = { name: string; internal_database_name: string; @@ -9,7 +16,7 @@ export type AddDatabaseRequest = { port: number; username: string; password: string; - type: number; // Database type. 0 = ArangoDB, 1 = Neo4j + type: DatabaseType; // Database type. 0 = ArangoDB, 1 = Neo4j }; export function AddDatabase(request: AddDatabaseRequest): Promise<void> { @@ -34,22 +41,24 @@ export function AddDatabase(request: AddDatabaseRequest): Promise<void> { export function GetAllDatabases(): Promise<Array<string>> { return new Promise<Array<string>>((resolve, reject) => { + console.log(AuthorizationHandler.instance().AccessToken()); fetch('https://api.graphpolaris.com/user/database', { method: 'GET', - credentials: 'same-origin', + // credentials: 'same-origin', headers: new Headers({ Authorization: 'Bearer ' + AuthorizationHandler.instance().AccessToken(), }), }) .then((response: Response) => { - if (!response.ok) { - reject(response.statusText); - } + // if (!response.ok) { + // reject(response.statusText); + // } return response.json(); }) .then((json: any) => { + console.log(json); resolve(json.databases); }); }); diff --git a/libs/shared/lib/data-access/api/index.ts b/libs/shared/lib/data-access/api/index.ts index 94a2ddb87..eed07d0e9 100644 --- a/libs/shared/lib/data-access/api/index.ts +++ b/libs/shared/lib/data-access/api/index.ts @@ -1,2 +1,3 @@ export * from './database' -export * from './user' \ No newline at end of file +export * from './user' +export * from './schema' \ No newline at end of file diff --git a/libs/shared/lib/data-access/api/schema.ts b/libs/shared/lib/data-access/api/schema.ts new file mode 100644 index 000000000..004a8b8e8 --- /dev/null +++ b/libs/shared/lib/data-access/api/schema.ts @@ -0,0 +1,7 @@ +// All database related API calls + +import { AuthorizationHandler } from '@graphpolaris/shared/lib/data-access/authorization'; + + +export function RequestSchema(databaseName: string) { +} \ No newline at end of file diff --git a/libs/shared/lib/data-access/api/user.ts b/libs/shared/lib/data-access/api/user.ts index 06296ae3a..825538a82 100644 --- a/libs/shared/lib/data-access/api/user.ts +++ b/libs/shared/lib/data-access/api/user.ts @@ -10,12 +10,12 @@ export type User = { export function GetUserInfo(): Promise<User> { return new Promise<User>((resolve, reject) => { + const auth = AuthorizationHandler.instance(); fetch('https://api.graphpolaris.com/user/', { method: 'GET', credentials: 'same-origin', headers: new Headers({ - Authorization: - 'Bearer ' + AuthorizationHandler.instance().AccessToken(), + Authorization: 'Bearer ' + auth.AccessToken(), }), }) .then((response: Response) => { diff --git a/libs/shared/lib/data-access/authorization/authorizationHandler.ts b/libs/shared/lib/data-access/authorization/authorizationHandler.ts index bc6001fd2..d8f2d1b74 100644 --- a/libs/shared/lib/data-access/authorization/authorizationHandler.ts +++ b/libs/shared/lib/data-access/authorization/authorizationHandler.ts @@ -36,6 +36,7 @@ export class AuthorizationHandler { // Store them this.userID = authResponse.userID ?? ''; this.sessionID = authResponse.sessionID ?? ''; + this.SetAccessToken(authResponse.accessToken ?? ''); } @@ -93,19 +94,25 @@ export class AuthorizationHandler { // Set the new access token this.accessToken = authResponse.accessToken ?? ''; - // Initialise the new refresh token - this.initialiseRefreshToken(); + if (this.accessToken !== '') { + // Initialise the new refresh token + this.initializeRefreshToken(); + } } } /** - * initialiseRefreshToken attempts to initialise a refresh token + * initializeRefreshToken attempts to initialize a refresh token */ - private async initialiseRefreshToken() { - fetch('https://api.graphpolaris.com/auth/refresh', { - method: 'POST', - credentials: 'include', - }) + private async initializeRefreshToken() { + fetch( + 'https://api.graphpolaris.com/auth/refresh?access_token=' + + this.accessToken, + { + method: 'GET', + credentials: 'include', + } + ) .then((response) => { if (!response.ok) { throw Error(response.statusText); @@ -158,10 +165,8 @@ export class AuthorizationHandler { async SetAccessToken(accessToken: string) { this.accessToken = accessToken; - console.log(this.accessToken); - // Activate the refresh token - this.initialiseRefreshToken(); + this.initializeRefreshToken(); // Start the automatic refreshing every 10 minutes setInterval(() => { diff --git a/libs/shared/lib/data-access/authorization/authorizationHook.tsx b/libs/shared/lib/data-access/authorization/authorizationHook.tsx new file mode 100644 index 000000000..4110005e8 --- /dev/null +++ b/libs/shared/lib/data-access/authorization/authorizationHook.tsx @@ -0,0 +1,41 @@ +import { useEffect, useState } from 'react'; +import { AuthorizationHandler } from '@graphpolaris/shared/lib/data-access'; + +interface useIsAuthorizedState { + userAuthorized: boolean; + userId?: string; + sessionId?: string; +} + +export function useAuthorization() { + const [state, setState] = useState<useIsAuthorizedState>({ + userAuthorized: false, + }); + const authInstance = AuthorizationHandler.instance(); + + const authCallback = async () => { + setState({ + userAuthorized: authInstance.Authorized(), + userId: authInstance.UserID(), + sessionId: authInstance.SessionID(), + }); + + // Print the user that is currently logged in + // const user = await GetUserInfo(); + // console.log(user); + }; + + authInstance.setCallback(authCallback); + + // Attempt to Authorize the user + const authorize = async () => { + const authorized = await authInstance.Authorize(); + if (authorized) authCallback(); + }; + + useEffect(() => { + authorize(); + }, []); + + return state; +} diff --git a/libs/shared/lib/data-access/authorization/index.ts b/libs/shared/lib/data-access/authorization/index.ts index 40d364806..0cf2dd57d 100644 --- a/libs/shared/lib/data-access/authorization/index.ts +++ b/libs/shared/lib/data-access/authorization/index.ts @@ -1 +1,2 @@ export { AuthorizationHandler } from './authorizationHandler'; +export * from './authorizationHook'; diff --git a/libs/shared/lib/data-access/theme/colorPaletteConfigSlice.ts b/libs/shared/lib/data-access/store/colorPaletteConfigSlice.ts similarity index 93% rename from libs/shared/lib/data-access/theme/colorPaletteConfigSlice.ts rename to libs/shared/lib/data-access/store/colorPaletteConfigSlice.ts index 6d79706f0..397305583 100644 --- a/libs/shared/lib/data-access/theme/colorPaletteConfigSlice.ts +++ b/libs/shared/lib/data-access/store/colorPaletteConfigSlice.ts @@ -1,22 +1,22 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import type { RootState } from '../store/store'; +import type { RootState } from './store'; /** Extra properties that are not present in the default PaletteOptions of mui. For autocompletion */ export interface ExtraColorsForMui5 { /** Colors that can be used for data visualisation, e.g. nodes, edges */ dataPointColors: string[]; - nodes: Array<string>, - nodesBase: [string], + nodes: Array<string>; + nodesBase: [string]; elements: { - entityBase: [string, string, string], // normal, lighter, darker - entitySecond: [string] - relation: [string, string, string], - relationBase: [string, string, string], - relationSecond: [string], - attribute: [string, string, string], - function: [string, string, string], - }, + entityBase: [string, string, string]; // normal, lighter, darker + entitySecond: [string]; + relation: [string, string, string]; + relationBase: [string, string, string]; + relationSecond: [string]; + attribute: [string, string, string]; + function: [string, string, string]; + }; visEdge: string; nodeHighlightedEdge: string; background: string; @@ -72,7 +72,6 @@ export interface ColorPaletteConfig { darkMode: boolean; } - const defaultPallete: ColorPalette = { custom: { dataPointColors: ['#ff0000', '#00ff00', '#0000ff'], @@ -147,7 +146,7 @@ const defaultPallete: ColorPalette = { main: '#9c27b0', // dark: '#7b1fa2', }, -} +}; // This looks very much like the mui Palette interface, // But we don't reference that type directly here to stay decoupled diff --git a/libs/shared/lib/data-access/store/configSlice.ts b/libs/shared/lib/data-access/store/configSlice.ts new file mode 100644 index 000000000..9e8f71a7b --- /dev/null +++ b/libs/shared/lib/data-access/store/configSlice.ts @@ -0,0 +1,26 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import type { RootState } from './store'; + +// Define the initial state using that type +export const initialState = { + queryListOpen: false, + queryStatusList: { queries: {}, queryIDsOrder: [] }, + functionsMenuOpen: false, + currentDatabaseKey: '', + elementsperDatabaseObject: {}, + autoSendQueries: true, +}; + +export const configSlice = createSlice({ + name: 'config', + // `createSlice` will infer the state type from the `initialState` argument + initialState, + reducers: {}, +}); + +export const {} = configSlice.actions; + +// Other code such as selectors can use the imported `RootState` type +export const configState = (state: RootState) => state.config; + +export default configSlice.reducer; diff --git a/libs/shared/lib/data-access/store/hooks.ts b/libs/shared/lib/data-access/store/hooks.ts index 5f3e0cfac..e1c5935f4 100644 --- a/libs/shared/lib/data-access/store/hooks.ts +++ b/libs/shared/lib/data-access/store/hooks.ts @@ -1,11 +1,20 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; import { selectGraphQueryResult } from './graphQueryResultSlice'; -import { selectSchema, selectSchemaLayout } from './schemaSlice'; -import { selectQuerybuilderNodes } from './querybuilderSlice'; +import { + schemaGraph, + schemaGraphology, + selectSchemaLayout, +} from './schemaSlice'; import type { RootState, AppDispatch } from './store'; +import { configState } from '@graphpolaris/shared/lib/data-access/store/configSlice'; +import { + selectQuerybuilderGraph, + selectQuerybuilderGraphology, +} from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; +import { sessionCacheState } from './sessionSlice'; // Use throughout your app instead of plain `useDispatch` and `useSelector` -export const useAppDispatch: () => AppDispatch = useDispatch +export const useAppDispatch: () => AppDispatch = useDispatch; // export const useAppDispatch = () => useDispatch<AppDispatch>(); export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; @@ -13,9 +22,16 @@ export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; export const useGraphQueryResult = () => useAppSelector(selectGraphQueryResult); // Gives the schema form the store (as a graphology object) -export const useSchema = () => useAppSelector(selectSchema); +export const useSchemaGraphology = () => useAppSelector(schemaGraphology); +export const useSchemaGraph = () => useAppSelector(schemaGraph); // Gives the schema form the store (as a graphology object) export const useSchemaLayout = () => useAppSelector(selectSchemaLayout); -export const useQuerybuilderNodes = () => - useAppSelector(selectQuerybuilderNodes); +export const useQuerybuilderGraphology = () => + useAppSelector(selectQuerybuilderGraphology); +export const useQuerybuilderGraph = () => + useAppSelector(selectQuerybuilderGraph); + +// Overall Configuration of the app +export const useConfig = () => useAppSelector(configState); +export const useSessionCache = () => useAppSelector(sessionCacheState); diff --git a/libs/shared/lib/data-access/store/index.ts b/libs/shared/lib/data-access/store/index.ts index ebce9ab02..43667f977 100644 --- a/libs/shared/lib/data-access/store/index.ts +++ b/libs/shared/lib/data-access/store/index.ts @@ -5,14 +5,9 @@ export { setSchema, readInSchemaFromBackend, schemaSlice, - selectSchemaLayout + selectSchemaLayout, } from './schemaSlice'; -export { - querybuilderSlice, - setQuerybuilderNodes, - updateQBAttributeOperator, - updateQBAttributeValue, -} from './querybuilderSlice'; +export { querybuilderSlice, setQuerybuilderNodes } from './querybuilderSlice'; export { selectGraphQueryResult, selectGraphQueryResultLinks, @@ -27,11 +22,11 @@ export { toggleDarkMode, selectColorPaletteConfig, colorPaletteConfigSlice, -} from '../theme/colorPaletteConfigSlice'; +} from './colorPaletteConfigSlice'; // Exported types export type { Node, Edge, GraphQueryResult } from './graphQueryResultSlice'; export type { ColorPaletteConfig, ExtraColorsForMui5, -} from '../theme/colorPaletteConfigSlice'; +} from './colorPaletteConfigSlice'; diff --git a/libs/shared/lib/data-access/store/querybuilderSlice.ts b/libs/shared/lib/data-access/store/querybuilderSlice.ts index 877444a06..f2e3cd62b 100644 --- a/libs/shared/lib/data-access/store/querybuilderSlice.ts +++ b/libs/shared/lib/data-access/store/querybuilderSlice.ts @@ -2,10 +2,14 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import type { RootState } from './store'; import { MultiGraph } from 'graphology'; import { Attributes, SerializedGraph } from 'graphology-types'; +import { + QueryMultiGraph, + QueryMultiGraphExport, +} from '@graphpolaris/shared/lib/querybuilder/graph/graphology/utils'; // Define the initial state using that type export const initialState = { - graphologySerialized: new MultiGraph().export(), + graphologySerialized: new QueryMultiGraph().export(), // schemaLayout: 'Graphology_noverlap', }; @@ -18,13 +22,15 @@ export const querybuilderSlice = createSlice({ state, action: PayloadAction<SerializedGraph<Attributes, Attributes, Attributes>> ) => { - state.graphologySerialized = action.payload; + state.graphologySerialized = QueryMultiGraph.from( + action.payload + ).export() as QueryMultiGraphExport; }, updateQBAttributeOperator: ( state, action: PayloadAction<{ id: string; operator: string }> ) => { - const graph = MultiGraph.from(state.graphologySerialized); + const graph = QueryMultiGraph.from(state.graphologySerialized); graph.setNodeAttribute( action.payload.id, 'operator', @@ -36,10 +42,13 @@ export const querybuilderSlice = createSlice({ state, action: PayloadAction<{ id: string; value: string }> ) => { - const graph = MultiGraph.from(state.graphologySerialized); + const graph = QueryMultiGraph.from(state.graphologySerialized); graph.setNodeAttribute(action.payload.id, 'value', action.payload.value); state.graphologySerialized = graph.export(); }, + clearQB: (state) => { + state.graphologySerialized = new QueryMultiGraph().export(); + }, // addQuerybuilderNode: ( // state, // action: PayloadAction<{ id: string; attributes: Attributes }> @@ -58,14 +67,26 @@ export const { setQuerybuilderNodes, updateQBAttributeOperator, updateQBAttributeValue, + clearQB, } = querybuilderSlice.actions; +/** Select the querybuilder nodes in serialized fromat */ +export const selectQuerybuilderGraphology = ( + state: RootState +): QueryMultiGraph => { + // This is really weird but for some reason all the attributes appeared as read-only otherwise + + let ret = new QueryMultiGraph(); + ret.import(MultiGraph.from(state.querybuilder.graphologySerialized).export()); + return ret; +}; + /** Select the querybuilder nodes and convert it to a graphology object */ -export const selectQuerybuilderNodes = (state: RootState): MultiGraph => { +export const selectQuerybuilderGraph = ( + state: RootState +): QueryMultiGraphExport => { // This is really weird but for some reason all the attributes appeared as read-only otherwise - return MultiGraph.from( - MultiGraph.from(state.querybuilder.graphologySerialized).export() - ); + return state.querybuilder.graphologySerialized as QueryMultiGraphExport; }; // /** diff --git a/libs/shared/lib/data-access/store/schemaSlice.spec.ts b/libs/shared/lib/data-access/store/schemaSlice.spec.ts index 2e28c3371..1cbc52aeb 100644 --- a/libs/shared/lib/data-access/store/schemaSlice.spec.ts +++ b/libs/shared/lib/data-access/store/schemaSlice.spec.ts @@ -1,10 +1,14 @@ import Graph from 'graphology'; import AbstractGraph, { DirectedGraph, MultiGraph } from 'graphology'; -import { useSchema } from '..'; -import reducer, { selectSchema, setSchema, initialState } from './schemaSlice'; +import { useSchemaGraphology } from '..'; +import reducer, { + schemaGraphology, + setSchema, + initialState, +} from './schemaSlice'; // import { deleteBook, updateBook, addNewBook } from '../redux/bookSlice'; import { store } from './store'; -import { assert, describe, expect, it } from "vitest"; +import { assert, describe, expect, it } from 'vitest'; describe('SchemaSlice Tests', () => { it('should make a graphology graph', () => { diff --git a/libs/shared/lib/data-access/store/schemaSlice.ts b/libs/shared/lib/data-access/store/schemaSlice.ts index 936b1e700..bd3d0b4b7 100644 --- a/libs/shared/lib/data-access/store/schemaSlice.ts +++ b/libs/shared/lib/data-access/store/schemaSlice.ts @@ -4,6 +4,7 @@ import { SerializedGraph } from 'graphology-types'; import { SchemaFromBackend } from '@graphpolaris/shared/lib/model/backend'; import type { RootState } from './store'; import { AllLayoutAlgorithms } from '@graphpolaris/shared/lib/graph-layout'; +import { QueryMultiGraph } from '@graphpolaris/shared/lib/querybuilder/graph/graphology/utils'; /**************************************************************** */ @@ -78,11 +79,19 @@ export const { readInSchemaFromBackend, setSchema } = schemaSlice.actions; /** * Select the schema and convert it to a graphology object * */ -export const selectSchema = (state: RootState) => { +export const schemaGraphology = (state: RootState) => { // This is really weird but for some reason all the attributes appeared as read-only otherwise - return MultiGraph.from( - MultiGraph.from(state.schema.graphologySerialized).export() - ); + let ret = new MultiGraph(); + ret.import(MultiGraph.from(state.schema.graphologySerialized).export()); + return ret; +}; + +/** + * Select the schema + * */ +export const schemaGraph = (state: RootState) => { + // This is really weird but for some reason all the attributes appeared as read-only otherwise + return state.schema.graphologySerialized; }; // /** diff --git a/libs/shared/lib/data-access/store/sessionSlice.ts b/libs/shared/lib/data-access/store/sessionSlice.ts new file mode 100644 index 000000000..979f7f028 --- /dev/null +++ b/libs/shared/lib/data-access/store/sessionSlice.ts @@ -0,0 +1,35 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import type { RootState } from './store'; + +// Define the initial state using that type +export const initialState: { + currentDatabase: string; + databases: string[]; +} = { + currentDatabase: '', + databases: [], +}; + +export const sessionSlice = createSlice({ + name: 'session', + // `createSlice` will infer the state type from the `initialState` argument + initialState, + reducers: { + updateCurrentDatabase(state, action: PayloadAction<string>) { + state.currentDatabase = action.payload; + }, + updateDatabaseList(state, action: PayloadAction<string[]>) { + state.databases = action.payload; + } + }, +}); + +export const { + updateCurrentDatabase, + updateDatabaseList +} = sessionSlice.actions; + +// Other code such as selectors can use the imported `RootState` type +export const sessionCacheState = (state: RootState) => state.sessionCache; + +export default sessionSlice.reducer; diff --git a/libs/shared/lib/data-access/store/store.ts b/libs/shared/lib/data-access/store/store.ts index a1346f58f..ef639a920 100644 --- a/libs/shared/lib/data-access/store/store.ts +++ b/libs/shared/lib/data-access/store/store.ts @@ -1,8 +1,10 @@ import { configureStore } from '@reduxjs/toolkit'; -import colorPaletteConfigSlice from '../theme/colorPaletteConfigSlice'; +import colorPaletteConfigSlice from './colorPaletteConfigSlice'; import graphQueryResultSlice from './graphQueryResultSlice'; import querybuilderSlice from './querybuilderSlice'; import schemaSlice from './schemaSlice'; +import configSlice from './configSlice'; +import sessionSlice from './sessionSlice'; export const store = configureStore({ reducer: { @@ -10,7 +12,18 @@ export const store = configureStore({ schema: schemaSlice, colorPaletteConfig: colorPaletteConfigSlice, querybuilder: querybuilderSlice, + sessionCache: sessionSlice, + config: configSlice, }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: { + ignoredPaths: [ + 'schema.graphologySerialized', + 'querybuilder.graphologySerialized', + ], + }, + }), }); // Infer the `RootState` and `AppDispatch` types from the store itself diff --git a/libs/shared/lib/data-access/theme/graphPolarisThemeProvider.tsx b/libs/shared/lib/data-access/theme/graphPolarisThemeProvider.tsx index bb04af952..191b3606f 100644 --- a/libs/shared/lib/data-access/theme/graphPolarisThemeProvider.tsx +++ b/libs/shared/lib/data-access/theme/graphPolarisThemeProvider.tsx @@ -6,7 +6,7 @@ import { useAppSelector, } from '../../data-access/store'; import { createTheme, ThemeOptions, ThemeProvider } from '@mui/material/styles'; -import { ColorPalette } from './colorPaletteConfigSlice'; +import { ColorPalette } from '../store/colorPaletteConfigSlice'; // Add custom theme variables to the mui theme types declare module '@mui/material/styles' { diff --git a/libs/shared/lib/graph-layout/layout-creator-usecase.ts b/libs/shared/lib/graph-layout/layout-creator-usecase.ts index 930200eea..7370db47f 100644 --- a/libs/shared/lib/graph-layout/layout-creator-usecase.ts +++ b/libs/shared/lib/graph-layout/layout-creator-usecase.ts @@ -49,7 +49,7 @@ export class LayoutFactory implements ILayoutFactory<AllLayoutAlgorithms> { createLayout<Algorithm extends AllLayoutAlgorithms>( layoutAlgorithm: Algorithm - ): AlgorithmToLayoutProvider<Algorithm> | null { + ): AlgorithmToLayoutProvider<Algorithm> { if ( this.isSpecificAlgorithm<GraphologyLayoutAlgorithms>( layoutAlgorithm, @@ -72,6 +72,6 @@ export class LayoutFactory implements ILayoutFactory<AllLayoutAlgorithms> { ) as AlgorithmToLayoutProvider<Algorithm>; } - return null; + throw Error('Invalid layout algorithm'); } } diff --git a/libs/shared/lib/querybuilder/graph/graphology/JSONParser.tsx b/libs/shared/lib/querybuilder/graph/graphology/JSONParser.tsx new file mode 100644 index 000000000..7d645f745 --- /dev/null +++ b/libs/shared/lib/querybuilder/graph/graphology/JSONParser.tsx @@ -0,0 +1,85 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/** JSON query format used to send a query to the backend. */ +export interface TranslatedJSONQuery { + return: { + entities: number[]; + relations: number[]; + groupBys: number[]; + }; + entities: Entity[]; + relations: Relation[]; + groupBys: GroupBy[]; + machineLearning: MachineLearning[]; + limit: number; +} + +/** Interface for an entity in the JSON for the query. */ +export interface Entity { + name: string; + ID: number; + constraints: Constraint[]; +} + +/** Interface for an relation in the JSON for the query. */ +export interface Relation { + name: string; + ID: number; + fromType: string; + fromID: number; + toType: string; + toID: number; + depth: { min: number; max: number }; + constraints: Constraint[]; +} + +/** + * Constraint datatypes created from the attributes of a relation or entity. + * + * string MatchTypes: exact/contains/startswith/endswith. + * int MatchTypes: GT/LT/EQ. + * bool MatchTypes: EQ/NEQ. + */ +export interface Constraint { + attribute: string; + dataType: string; + + matchType: string; + value: string; +} + +/** Interface for a function in the JSON for the query. */ +export interface GroupBy { + ID: number; + + groupType: string; + groupID: number[]; + groupAttribute: string; + + byType: string; + byID: number[]; + byAttribute: string; + + appliedModifier: string; + relationID: number; + constraints: Constraint[]; +} + +/** Interface for Machine Learning algorithm */ +export interface MachineLearning { + ID?: number; + queuename: string; + parameters: string[]; +} +/** Interface for what the JSON needs for link predicition */ +export interface LinkPrediction { + queuename: string; + parameters: { + key: string; + value: string; + }[]; +} diff --git a/libs/shared/lib/querybuilder/graph/graphology/model.ts b/libs/shared/lib/querybuilder/graph/graphology/model.ts new file mode 100644 index 000000000..194ed07c7 --- /dev/null +++ b/libs/shared/lib/querybuilder/graph/graphology/model.ts @@ -0,0 +1,49 @@ +import { Attributes as GAttributes } from 'graphology-types'; +import { + AttributeData, + AttributeNode, + EntityData, + EntityNode, + FunctionNode, + ModifierNode, + RelationData, + RelationNode, +} from '../reactflow/model'; +import { XYPosition } from 'reactflow'; +import { + setQuerybuilderNodes, + store, +} from '@graphpolaris/shared/lib/data-access'; +import { MultiGraph } from 'graphology'; +import { calcWidthHeightOfPill } from '@graphpolaris/shared/lib/querybuilder/graph/graphology/utils'; +import './utils'; + +// export interface Attributes extends EntityNode | RelationNode | AttributeNode | FunctionNode | ModifierNode { +// } + +export type NodeAttributes = XYPosition & + AttributeData & { + type: string; + width?: number; + height?: number; + }; + +export type EntityNodeAttributes = XYPosition & + EntityData & { + type: string; + width?: number; + height?: number; + }; + +export type RelationNodeAttributes = XYPosition & + RelationData & { + type: string; + width?: number; + height?: number; + }; + +export type QueryGraph = MultiGraph< + NodeAttributes | EntityNodeAttributes | RelationNodeAttributes, + GAttributes, + GAttributes +>; diff --git a/libs/shared/lib/querybuilder/graph/graphology/utils.ts b/libs/shared/lib/querybuilder/graph/graphology/utils.ts new file mode 100644 index 000000000..b55a4597d --- /dev/null +++ b/libs/shared/lib/querybuilder/graph/graphology/utils.ts @@ -0,0 +1,131 @@ +import { + setQuerybuilderNodes, + store, +} from '@graphpolaris/shared/lib/data-access/store'; +import Graph, { MultiGraph } from 'graphology'; +import { + Attributes as GAttributes, + Attributes, + SerializedGraph, +} from 'graphology-types'; +import { + EntityNodeAttributes, + NodeAttributes, + QueryGraph, + RelationNodeAttributes, +} from './model'; +import { XYPosition } from 'reactflow'; + +/** monospace fontsize table */ +const widthPerFontsize = { + 6: 3.6167, + 7: 4.2167, + 10: 6.0167, +}; + +export type QueryMultiGraphExport = SerializedGraph< + NodeAttributes | EntityNodeAttributes | RelationNodeAttributes, + GAttributes, + GAttributes +>; + +export class QueryMultiGraph extends MultiGraph< + NodeAttributes | EntityNodeAttributes | RelationNodeAttributes, + GAttributes, + GAttributes +> { + public addPill2Graphology( + attributes: NodeAttributes | EntityNodeAttributes | RelationNodeAttributes, + id: string = (Date.now() + Math.floor(Math.random() * 1000)).toString() + ): string | null { + const { type, name } = attributes; + if (!type || !name) return null; + let { x, y } = attributes; + + // Check if x and y are present, otherwise set them to 0 + if (!x) x = 0; + if (!y) y = 0; + + // Get the width and height of a node + const { width, height } = calcWidthHeightOfPill(attributes); + + // Add a node to the graphology object + const nodeId = this.addNode(id, { ...attributes, x, y, width, height }); + + // Set the new nodes in the query builder slice TODO: maybe remove for efficiency + store.dispatch(setQuerybuilderNodes(this.export())); + + return nodeId; + } +} + +/** Calculates the width and height of a query builder pill. + * DEPENDS ON STYLING, if styling changed, change this. + */ +export function calcWidthHeightOfPill(attributes: Attributes): { + width: number; + height: number; +} { + const { type, name } = attributes; + + let w = 0; + let h = 0; + switch (type) { + case 'entity': { + // calculate width and height of entity pill + w = Math.min(name.length, 20) * widthPerFontsize[10]; // for fontsize 10px + + const widthOfPillWithoutText = 42.1164; // WARNING: depends on styling + w += widthOfPillWithoutText; + h = 20; + break; + } + case 'relation': { + // calculate width and height of relation pill + w = Math.min(name.length, 20) * widthPerFontsize[10]; // for fontsize 10px + + const widthOfPillWithoutText = 56.0666; // WARNING: depends on styling + w += widthOfPillWithoutText; + h = 20; + break; + } + case 'attribute': { + // calculate width and height of relation pill + const pixelsPerChar = widthPerFontsize[6]; // for fontsize 10px + w = name.length * pixelsPerChar; + + const { datatype, operator } = attributes; + let value = attributes['value']; + if (!datatype || !operator) return { width: 0, height: 0 }; + if (!value) value = '?'; + + // Add width of operator + w += operator.length * widthPerFontsize[7]; + // use a max of 10, because max-width is set to 10ch; + w += Math.min(value.length, 10) * widthPerFontsize[6]; + + const widthOfPillWithoutText = 25.6666; // WARNING: depends on styling + w += widthOfPillWithoutText; + h = 12; + break; + } + } + + return { width: w, height: h }; +} + +/** Interface for x and y position of node */ +export interface NodePosition extends XYPosition {} + +/** Returns from-position of relation node */ +export function RelationPosToFromEntityPos(position: XYPosition): NodePosition { + return { x: position.x - 60, y: position.y - 40 }; +} + +/** Returns to-position of relation node */ +export function RelationPosToToEntityPos(position: XYPosition): NodePosition { + return { x: position.x + 400, y: position.y }; +} + +/** Default position; x=0, y=0 */ +export const DefaultPosition = { x: 0, y: 0 }; diff --git a/libs/shared/lib/querybuilder/graph/logic/queryFunctions.tsx b/libs/shared/lib/querybuilder/graph/logic/queryFunctions.tsx new file mode 100644 index 000000000..404f2f3ea --- /dev/null +++ b/libs/shared/lib/querybuilder/graph/logic/queryFunctions.tsx @@ -0,0 +1,148 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +import { AnyNode, FunctionArgs } from '../reactflow/model'; + +/** What functions exist + * Default is for the functions in the function bar that don't exist yet. + */ +export enum FunctionTypes { + GroupBy = 'groupBy', + link = 'linkPrediction', + communityDetection = 'communityDetection', + centrality = 'centrality', + shortestPath = 'shortestPath', + default = 'default', +} + +export enum FunctionArgTypes { + group = 'group', + by = 'by', + relation = 'relation', + modifier = 'modifier', + constraints = 'constraints', + result = 'result', + ID1 = 'ID1', + ID2 = 'ID2', +} +/** All arguments that groupby pill needs */ +export const DefaultGroupByArgs: FunctionArgs = { + group: { displayName: 'Group', connectable: true, value: '', visible: true }, + by: { displayName: 'By', connectable: true, value: '_id', visible: true }, + relation: { + displayName: 'On', + connectable: true, + value: undefined, + visible: true, + }, + modifier: { + displayName: 'Modifier: ', + connectable: false, + value: '', + visible: true, + }, + constraints: { + displayName: 'Constraints: ', + connectable: true, + value: undefined, + visible: true, + }, + result: { + displayName: 'Result: ', + connectable: true, + value: undefined, + visible: true, + }, +}; +/** All arguments that linkprediction pill needs */ +export const DefaultLinkPredictionArgs: FunctionArgs = { + linkprediction: { + //currently the querybuilder shows this name instead of the display name that needs to be changed. + displayName: 'linkprediction', + connectable: false, + value: undefined, + visible: true, + }, +}; + +/** All arguments that CommunictyDetection pill needs */ +export const DefaultCommunictyDetectionArgs: FunctionArgs = { + CommunityDetection: { + displayName: 'CommunityDetection', + connectable: false, + value: undefined, + visible: true, + }, +}; + +/** All arguments that centrality pill needs */ +export const DefaultCentralityArgs: FunctionArgs = { + centrality: { + displayName: 'centrality', + connectable: false, + value: undefined, + visible: true, + }, +}; + +/** All arguments that centrality pill needs */ +export const DefaultShortestPathArgs: FunctionArgs = { + shortestPath: { + displayName: 'shortestPath', + connectable: false, + value: undefined, + visible: true, + }, +}; + +// TODO: fix this to somehow make use of the enum +/** Returns the correct arguments depending on the type */ +export const DefaultFunctionArgs: { [type: string]: FunctionArgs } = { + groupBy: DefaultGroupByArgs, + linkPrediction: DefaultLinkPredictionArgs, + communityDetection: DefaultCommunictyDetectionArgs, + centrality: DefaultCentralityArgs, + shortestPath: DefaultShortestPathArgs, +}; + +/** Interface for what function descriptions need */ +export interface FunctionDescription { + name: string; + type: FunctionTypes; + description: string; +} + +/** All available functions in the function bar. */ +export const AvailableFunctions: Record<string, FunctionDescription> = { + centrality: { + name: 'centrality', + type: FunctionTypes.centrality, + description: 'W.I.P. Shows the importance of nodes', + }, + communityDetection: { + name: 'Community Detection', + type: FunctionTypes.communityDetection, + description: + 'Group entities connected by a relation based on how interconnected they are.', + }, + groupBy: { + name: 'Group By', + type: FunctionTypes.GroupBy, + description: + 'W.I.P. Per entity of type A, generate aggregate statistics of an attribute of either all links of a relation, or all nodes of an entity of type B connected to the type A entity by a relation.', + }, + link: { + name: 'Link Prediction', + type: FunctionTypes.link, + description: + 'For each pair of entities from a given type, determine the overlap between nodes they are connect to by a given relation.', + }, + shortestPath: { + name: 'shortestPath', + type: FunctionTypes.shortestPath, + description: 'W.I.P. shortest path. Shows the shortest path between nodes', + }, +}; diff --git a/libs/shared/lib/querybuilder/graph/reactflow/handles.tsx b/libs/shared/lib/querybuilder/graph/reactflow/handles.tsx new file mode 100644 index 000000000..71a09d206 --- /dev/null +++ b/libs/shared/lib/querybuilder/graph/reactflow/handles.tsx @@ -0,0 +1,81 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/** + * Enums for possible values for handles of nodes in the query builder. + * Possible handles for an entity node. + */ +import { FunctionArgTypes } from '../logic/queryFunctions'; +import { AnyNode, QueryElementTypes } from './model'; + +/** Links need handles to what they are connected to (and which side) */ +export enum Handles { + RelationLeft = 'leftEntityHandle', //target + RelationRight = 'rightEntityHandle', //target + ToAttribute = 'attributesHandle', //target + ToRelation = 'relationsHandle', //source + OnAttribute = 'onAttributeHandle', //source + ReceiveFunction = 'receiveFunctionHandle', //target + FunctionBase = 'functionHandle_', // + name from FunctionTypes args //source +} + +/** returns a boolean that check whether the handle is a function handle */ +export function isFunctionHandle(handle: string): boolean { + return handle.startsWith(Handles.FunctionBase); +} + +/** + * returns the functionargumenttype + * Currently only working for groupby but made that in the future other functions can use this as well. + */ +export function functionHandleToType(handle: string): FunctionArgTypes { + if (isFunctionHandle(handle)) + return handle.slice(Handles.FunctionBase.length) as FunctionArgTypes; + else + throw new Error('Incorrectly trying to assert handle to function handle'); +} +/** Creates a handle from a functiontype */ +export function typeToFunctionHandle(type: FunctionArgTypes): string { + return Handles.FunctionBase + type; +} + +/** + * Return a list of handles to which a connection can be made by dragging a node nearby + */ +export function nodeToHandlesThatCanReceiveDragconnect( + node: AnyNode +): string[] { + switch (node.type) { + case QueryElementTypes.Entity: + return [Handles.ToAttribute]; + case QueryElementTypes.Relation: + return [Handles.RelationLeft, Handles.RelationRight, Handles.ToAttribute]; + case QueryElementTypes.Function: + return [Handles.ToAttribute]; + case QueryElementTypes.Attribute: + return []; + default: + throw new Error('Unsupported node'); + } +} + +/** + * Return a list of handles from which a connection can be made while dragging the node they are on + */ +export function nodeToHandlesThatCanSendDragconnect(node: AnyNode): string[] { + switch (node.type) { + case QueryElementTypes.Entity: + return [Handles.ToRelation]; + case QueryElementTypes.Relation: + return []; + case QueryElementTypes.Function: + return []; + case QueryElementTypes.Attribute: + return [Handles.OnAttribute]; + default: + throw new Error('Unsupported node'); + } +} diff --git a/libs/shared/lib/querybuilder/graph/reactflow/model.tsx b/libs/shared/lib/querybuilder/graph/reactflow/model.tsx new file mode 100644 index 000000000..fddca14b8 --- /dev/null +++ b/libs/shared/lib/querybuilder/graph/reactflow/model.tsx @@ -0,0 +1,130 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ +import { Edge as ReactEdge, NodeProps } from 'reactflow'; + +/** Enums for the possible types of query elements */ +export enum QueryElementTypes { + Entity = 'entity', + Relation = 'relation', + Attribute = 'attribute', + Function = 'function', +} + +/** List of possible query element types */ +export const possibleTypes: string[] = [ + QueryElementTypes.Entity, + QueryElementTypes.Relation, + QueryElementTypes.Attribute, + QueryElementTypes.Function, +]; + +/** All the possible react flow nodes. */ +export type AnyNode = EntityNode | RelationNode | AttributeNode | FunctionNode; +export type Edge = ReactEdge<any>; +export type AnyElement = AnyNode | Edge; +export type AnyNodeData = + | EntityData + | RelationData + | AttributeData + | FunctionData; + +export type EntityNode = NodeProps<EntityData>; +export type RelationNode = NodeProps<RelationData>; +export type AttributeNode = NodeProps<AttributeData>; +export type FunctionNode = NodeProps<FunctionData>; +export type ModifierNode = NodeProps<ModifierData>; + +export interface NodeData { + fadeIn: boolean; +} + +export interface ModifierData { + type: string; +} + +/** Interface for the data in an entity node. */ +export interface EntityData extends NodeData { + name: string; +} + +/** Interface for the data in an relation node. */ +export interface RelationData extends NodeData { + name: string; + collection: string; + depth: { min: number; max: number }; +} + +/** Interface for the data in an attribute node. + * Can have multiple constraint datatypes. + * string MatchTypes: exact/contains/startswith/endswith. + * int MatchTypes: GT/LT/EQ. + * bool MatchTypes: EQ/NEQ. + */ +export interface AttributeData extends NodeData { + name: string; + value: string; + dataType: string; + matchType: string; +} + +export interface FunctionArg { + displayName: string; + connectable: boolean; // does this arg have a connectable handle? + value: string | undefined; // undefined means no text input + visible: boolean; // for implicit connections +} + +export interface FunctionArgs { + [name: string]: FunctionArg; +} + +/** Interface for the data in a function node. */ +export interface FunctionData extends NodeData { + functionType: string; + args: FunctionArgs; +} + +/** Interface for updating the edges. */ +export interface updateEdges { + newEdge: Edge | undefined; + removeEdge: Edge | undefined; +} + +/** + * Checks if a node is an entityNode. + * @param {AnyNode} node The node that has to checked. + * @returns True and casts if the node is an EntityNode. + */ +export function isEntityNode(node: AnyNode): node is EntityNode { + return node.type == QueryElementTypes.Entity; +} + +/** + * Checks if a node is a RelationNode. + * @param {AnyNode} node The node that has to checked. + * @returns True and casts if the node is an RelationNode. + */ +export function isRelationNode(node: AnyNode): node is RelationNode { + return node.type == QueryElementTypes.Relation; +} + +/** + * Checks if a node is an AttributeNode. + * @param {AnyNode} node The node that has to checked. + * @returns True and casts if the node is an AttributeNode. + */ +export function isAttributeNode(node: AnyNode): node is AttributeNode { + return node.type == QueryElementTypes.Attribute; +} + +/** + * Checks if a node is an FunctionNode. + * @param {AnyNode} node The node that has to checked. + * @returns True and casts if the node is an FunctionNode. + */ +export function isFunctionNode(node: AnyNode): node is FunctionNode { + return node.type == QueryElementTypes.Function; +} diff --git a/libs/shared/lib/querybuilder/usecases/pillHandles.ts b/libs/shared/lib/querybuilder/graph/reactflow/pillHandles.ts similarity index 100% rename from libs/shared/lib/querybuilder/usecases/pillHandles.ts rename to libs/shared/lib/querybuilder/graph/reactflow/pillHandles.ts diff --git a/libs/shared/lib/querybuilder/usecases/createReactFlowElements.ts b/libs/shared/lib/querybuilder/graph/reactflow/utils.ts similarity index 95% rename from libs/shared/lib/querybuilder/usecases/createReactFlowElements.ts rename to libs/shared/lib/querybuilder/graph/reactflow/utils.ts index 9b288187a..2bdf0acdb 100644 --- a/libs/shared/lib/querybuilder/usecases/createReactFlowElements.ts +++ b/libs/shared/lib/querybuilder/graph/reactflow/utils.ts @@ -54,9 +54,8 @@ export function createReactFlowElements(graph: Graph): { } // Each pill should have a name and type data = { + ...attributes, ...data, - name: attributes.name, - suggestedForConnection: attributes.suggestedForConnection, // Highlights the pill, with shadow or something }; const RFNode: Node = { @@ -71,7 +70,7 @@ export function createReactFlowElements(graph: Graph): { // Add the reactflow edges graph.forEachEdge((edge, attributes, source, target): void => { // connection from attributes don't have visible connection lines - if (attributes.type == 'attribute_connection') return; + // if (attributes.type == 'attribute_connection') return; const RFEdge: Edge = { id: edge, diff --git a/libs/shared/lib/querybuilder/index.ts b/libs/shared/lib/querybuilder/index.ts new file mode 100644 index 000000000..e9ce65c3e --- /dev/null +++ b/libs/shared/lib/querybuilder/index.ts @@ -0,0 +1,2 @@ +export * from './panel'; +export * from './pills'; \ No newline at end of file diff --git a/libs/shared/lib/ui/pills/shared-ui-pills.module.scss b/libs/shared/lib/querybuilder/panel/nodeUtils.ts similarity index 100% rename from libs/shared/lib/ui/pills/shared-ui-pills.module.scss rename to libs/shared/lib/querybuilder/panel/nodeUtils.ts diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.module.scss b/libs/shared/lib/querybuilder/panel/querybuilder.module.scss index db83f4696..e1a5d48d0 100644 --- a/libs/shared/lib/querybuilder/panel/querybuilder.module.scss +++ b/libs/shared/lib/querybuilder/panel/querybuilder.module.scss @@ -6,3 +6,27 @@ // z-index: 4 !important; // } } + + +//controls +.controls { + left: auto !important; + bottom: auto !important; + top: 10px; + right: 20px; + width: auto !important; +} +.buttons { + left: auto !important; + bottom: auto !important; + top: 10px; + right: 20px; + & svg { + transform: scale(1.4); + }; +} + +.menuText { + font-size: small; + font-family: Poppins, sans-serif; +} \ No newline at end of file diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.module.scss.d.ts b/libs/shared/lib/querybuilder/panel/querybuilder.module.scss.d.ts index f2549523c..8d93e9952 100644 --- a/libs/shared/lib/querybuilder/panel/querybuilder.module.scss.d.ts +++ b/libs/shared/lib/querybuilder/panel/querybuilder.module.scss.d.ts @@ -1,4 +1,7 @@ declare const classNames: { readonly reactflow: 'reactflow'; + readonly controls: 'controls'; + readonly buttons: 'buttons'; + readonly menuText: 'menuText'; }; export = classNames; diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.stories.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.stories.tsx index b6bb2f302..1bc0f5778 100644 --- a/libs/shared/lib/querybuilder/panel/querybuilder.stories.tsx +++ b/libs/shared/lib/querybuilder/panel/querybuilder.stories.tsx @@ -3,99 +3,130 @@ import { colorPaletteConfigSlice, querybuilderSlice, setQuerybuilderNodes, + store, } from '@graphpolaris/shared/lib/data-access/store'; import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/theme'; import { configureStore } from '@reduxjs/toolkit'; -import { Meta, ComponentStory } from '@storybook/react'; +import { Meta } from '@storybook/react'; import { Provider } from 'react-redux'; import QueryBuilder from './querybuilder'; -import { MultiGraph } from 'graphology'; -import { - addPill, - handles, -} from '@graphpolaris/shared/lib/querybuilder/usecases'; +import { Handles } from '../graph/reactflow/handles'; +import { QueryMultiGraph } from '../graph/graphology/utils'; const Component: Meta<typeof QueryBuilder> = { component: QueryBuilder, - title: 'Panels/QueryBuilder', + title: 'QueryBuilder/Panel', decorators: [ (story) => ( - <Provider store={mockStore}> - <GraphPolarisThemeProvider>{story()}</GraphPolarisThemeProvider> + <Provider store={store}> + <GraphPolarisThemeProvider> + <div + style={{ + width: '100%', + height: '95vh', + }} + > + {story()} + </div> + </GraphPolarisThemeProvider> </Provider> ), ], }; -// Mock palette store -const mockStore = configureStore({ - reducer: { - colorPaletteConfig: colorPaletteConfigSlice.reducer, - querybuilder: querybuilderSlice.reducer, - }, -}); -const graph = new MultiGraph(); -addPill('0', { type: 'entity', x: 100, y: 100, name: 'Entity Pill' }, graph); -// graph.addNode('0', { type: 'entity', x: 100, y: 100, name: 'Entity Pill' }); -addPill( - '1', - { type: 'relation', x: 140, y: 140, name: 'Relation Pill' }, - graph -); -addPill( - '2', - { - type: 'attribute', - x: 170, - y: 160, - name: 'Attr string', - datatype: 'string', - operator: 'EQ', - value: 'mark', - }, - graph +const graph = new QueryMultiGraph(); +graph.addPill2Graphology( + { type: 'entity', x: 100, y: 100, name: 'Entity Pill', fadeIn: false }, + '0' ); -addPill( - '3', - { - type: 'attribute', - x: 170, - y: 170, - name: 'Attr number', - datatype: 'float', - operator: 'EQ', - }, - graph +graph.addPill2Graphology( + { type: 'entity', x: 200, y: 200, name: 'Entity Pill 2', fadeIn: false }, + '10' ); -addPill( - '4', +// graph.addNode('0', { type: 'entity', x: 100, y: 100, name: 'Entity Pill' }); +graph.addPill2Graphology( { - type: 'attribute', - x: 130, - y: 120, - name: 'Attr bool', - datatype: 'bool', - operator: 'EQ', - value: 'true', + type: 'relation', + x: 140, + y: 140, + name: 'Relation Pill', + depth: { min: 0, max: 1 }, + fadeIn: false, }, - graph + '1' ); -console.log(graph.getNodeAttributes('2')); -graph.addEdge('2', '1', { type: 'attribute_connection' }); -graph.addEdge('3', '1', { type: 'attribute_connection' }); -graph.addEdge('4', '0', { type: 'attribute_connection' }); +// addPill2Graphology( +// '2', +// { +// type: 'attribute', +// x: 170, +// y: 160, +// name: 'Attr string', +// dataType: 'string', +// matchType: 'EQ', +// value: 'mark', +// depth: { min: 0, max: 1 }, +// fadeIn: false, +// }, +// graph +// ); +// addPill2Graphology( +// '3', +// { +// type: 'attribute', +// x: 170, +// y: 170, +// name: 'Attr number', +// dataType: 'float', +// matchType: 'EQ', +// depth: { min: 0, max: 1 }, +// fadeIn: false, +// }, +// graph +// ); +// addPill2Graphology( +// '4', +// { +// type: 'attribute', +// x: 130, +// y: 120, +// name: 'Attr bool', +// dataType: 'bool', +// matchType: 'EQ', +// value: 'true', +// depth: { min: 0, max: 1 }, +// fadeIn: false, +// }, +// graph +// ); graph.addEdge('0', '1', { - type: 'entity_relation', - targetHandle: handles.relation.fromEntity, + type: 'connection', + sourceHandle: Handles.ToRelation, + targetHandle: Handles.RelationLeft, +}); +graph.addEdge('10', '1', { + type: 'connection', + sourceHandle: Handles.ToRelation, + targetHandle: Handles.RelationRight, }); +// console.log(graph.getNodeAttributes('2')); +// graph.addEdge('2', '1', { type: 'attribute_connection' }); +// graph.addEdge('3', '1', { type: 'attribute_connection' }); +// graph.addEdge('4', '0', { type: 'attribute_connection' }); +// graph.addEdge('0', '1', { +// type: 'entity_relation', +// targetHandle: handles.relation.fromEntity, +// }); // graph.addEdge('1', '0', { // type: 'entity_relation', // sourceHandle: handles.relation.entity, // }); -mockStore.dispatch(setQuerybuilderNodes(graph.export())); export const Simple = { args: {}, + play: async () => { + store.dispatch(setQuerybuilderNodes(graph.export())); + }, }; export default Component; diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.tsx index 0892485b2..aca3c482a 100644 --- a/libs/shared/lib/querybuilder/panel/querybuilder.tsx +++ b/libs/shared/lib/querybuilder/panel/querybuilder.tsx @@ -1,13 +1,9 @@ -import { - createReactFlowElements, - dragPill, - dragPillStarted, - dragPillStopped, -} from '@graphpolaris/shared/lib/querybuilder/usecases'; import { setQuerybuilderNodes, useAppDispatch, - useQuerybuilderNodes, + useConfig, + useQuerybuilderGraph, + useQuerybuilderGraphology, } from '@graphpolaris/shared/lib/data-access/store'; import ReactFlow, { ReactFlowProvider, @@ -15,99 +11,318 @@ import ReactFlow, { Node, isNode, ReactFlowInstance, + Controls, + ControlButton, + NodeChange, + ConnectionMode, } from 'reactflow'; import styles from './querybuilder.module.scss'; + +import React, { ReactComponentElement, useMemo, useRef } from 'react'; import { - EntityRFPill, - RelationRFPill, - AttributeRFPill, + AttributePill, ConnectionDragLine, ConnectionLine, -} from '@graphpolaris/shared/lib/ui/pills'; - -import { useMemo, useRef } from 'react'; + EntityFlowElement, + RelationPill, +} from '../pills'; +import { createReactFlowElements } from '../graph/reactflow/utils'; +import { + dragPillStarted, + dragPillStopped, + movePillTo, +} from '../pills/dragging/dragPill'; +import { + Settings as SettingsIcon, + Delete as DeleteIcon, + ImportExport as ExportIcon, +} from '@mui/icons-material'; +import { clearQB } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; +import { + EntityNode, + QueryElementTypes, + RelationNode, +} from '@graphpolaris/shared/lib/querybuilder/graph/reactflow/model'; +import { + RelationPosToFromEntityPos, + RelationPosToToEntityPos, +} from '@graphpolaris/shared/lib/querybuilder/graph/graphology/utils'; +import { Handles } from '@graphpolaris/shared/lib/querybuilder/graph/reactflow/handles'; +import { useDispatch } from 'react-redux'; const nodeTypes = { - entity: EntityRFPill, - relation: RelationRFPill, - attribute: AttributeRFPill, + entity: EntityFlowElement, + relation: RelationPill, + attribute: AttributePill, }; const edgeTypes = { connection: ConnectionLine, + attribute_connection: ConnectionLine, }; -const onInit = (reactFlowInstance: ReactFlowInstance) => { - setTimeout(() => reactFlowInstance.fitView(), 0); -}; - -export const QueryBuilder = (props: any) => { - const nodes = useQuerybuilderNodes(); - const dispatch = useAppDispatch(); +/** + * This is the main querybuilder component. It is responsible for holding all pills and fire off the visual part of the querybuilder panel logic + */ +export const QueryBuilder: React.FC = () => { + const graphologyGraph = useQuerybuilderGraphology(); + const graph = useQuerybuilderGraph(); + const config = useConfig(); + const dispatch = useDispatch(); const isDraggingPill = useRef(false); - console.log('inputnodes', nodes); + // console.log('inputnodes', nodes); + const reactFlowInstanceRef = useRef<ReactFlowInstance>(); + const divRef = useRef<HTMLDivElement>(null); + + const onInit = (reactFlowInstance: ReactFlowInstance) => { + reactFlowInstanceRef.current = reactFlowInstance; + setTimeout(() => reactFlowInstance.fitView(), 0); + }; - const elements = useMemo(() => createReactFlowElements(nodes), [nodes]); + const elements = useMemo( + () => createReactFlowElements(graphologyGraph), + [graphologyGraph] + ); + /** + * Called when a node is dragged in querybuilder to allow for movement + * @param event + * @param node + */ const onNodeDrag = ( event: React.MouseEvent<Element, MouseEvent>, node: Node<any> ) => { // Get the node in the elements list to get the previous location - const pNode = elements.nodes.find((e) => e.id === node.id); + const pNode = elements.nodes.find((e) => e?.id === node?.id); if (!(pNode && isNode(pNode))) return; // This is then used to calculate the delta position const dx = node.position.x - pNode.position.x; const dy = node.position.y - pNode.position.y; + // console.log(node, pNode); // Check if we started dragging, if so, call the drag started usecase if (!isDraggingPill.current) { - dragPillStarted(node.id, nodes); + dragPillStarted(node.id, graphologyGraph); isDraggingPill.current = true; } // Call the drag usecase - dragPill(node.id, nodes, dx, dy, node.position); + movePillTo(node.id, graphologyGraph, dx, dy, node.position); // Dispatch the new graphology object, so reactflow will get rerendered - dispatch(setQuerybuilderNodes(nodes.export())); + dispatch(setQuerybuilderNodes(graphologyGraph.export())); }; + + /** + * Called when a node is released from dragging in querybuilder + * @param event + * @param node + **/ const onNodeDragStop = ( event: React.MouseEvent<Element, MouseEvent>, node: Node<any> ) => { - isDraggingPill.current = false; + // isDraggingPill.current = false; + // + // // Call the drag pill stopped usecase + // dragPillStopped(node.id, graphologyGraph); + // + // // Dispatch the new graphology object, so reactflow will get rerendered + // dispatch(setQuerybuilderNodes(graphologyGraph.export())); + }; - // Call the drag pill stopped usecase - dragPillStopped(node.id, nodes); + /** + * Clears all nodes in the graph. + */ + function clearAllNodes() { + dispatch(clearQB()); + } - // Dispatch the new graphology object, so reactflow will get rerendered - dispatch(setQuerybuilderNodes(nodes.export())); + /** + * Clears all nodes in the graph. + * TODO: only works if the node is clicked and not moved (maybe use onSelectionChange) + */ + function onNodesDelete(nodes: Node[]) { + nodes.forEach((n) => { + graphologyGraph.dropNode(n.id); + }); + + // Dispatch the new graphology object, so reactflow will get re-rendered + dispatch(setQuerybuilderNodes(graphologyGraph.export())); + } + + /** + * TODO? + */ + function onNodesChange(nodes: NodeChange[]) { + // console.log(nodes); + nodes.forEach((n) => { + // console.log(graphologyGraph.findNode((gn) => gn === n?.id)); + if (n.type !== 'position') { + // updated = true; + // graphologyGraph.updateAttributes(n.id, n.data); + } + }); + } + + const onDragOver = (event: React.DragEvent<HTMLDivElement>): void => { + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; }; - console.log(elements); + /** + * The onDrop is called when the user drops an element from the schema onto the QueryBuilder. + * In the onDrop query elements will be created based on the data stored in the drag event (datastrasfer). + * @param event Drag event. + */ + const onDrop = (event: React.DragEvent<HTMLDivElement>): void => { + event.preventDefault(); + + // The dropped element should be a valid reactflow element + const data: string = event.dataTransfer.getData('application/reactflow'); + if (data.length == 0 || !reactFlowInstanceRef?.current) return; + + const dragData = JSON.parse(data); + const reactFlow = divRef.current as HTMLDivElement; + const reactFlowBounds = reactFlow.getBoundingClientRect(); + const position = reactFlowInstanceRef.current.project({ + //TODO: this position should be centre of entity, rather than topleft + x: event.clientX - reactFlowBounds.left, + y: event.clientY - reactFlowBounds.top, + }); + + switch (dragData.type) { + case QueryElementTypes.Entity: + graphologyGraph.addPill2Graphology({ + type: QueryElementTypes.Entity, + x: position.x, + y: position.y, + name: dragData.name, + fadeIn: dragData.fadeIn, + }); + break; + // Creates a relation element and will also create the 2 related entities together with the connections + case QueryElementTypes.Relation: + const relationId = graphologyGraph.addPill2Graphology({ + type: QueryElementTypes.Relation, + x: position.x, + y: position.y, + depth: { min: 0, max: 1 }, + name: dragData.name, + fadeIn: dragData.fadeIn, + }); + const leftEntityId = graphologyGraph.addPill2Graphology({ + type: QueryElementTypes.Entity, + ...RelationPosToFromEntityPos(position), + name: dragData.from, + fadeIn: true, + }); + const rightEntityId = graphologyGraph.addPill2Graphology({ + type: QueryElementTypes.Entity, + ...RelationPosToToEntityPos(position), + name: dragData.to, + fadeIn: true, + }); + + graphologyGraph.addEdge(leftEntityId, relationId, { + type: 'connection', + sourceHandle: Handles.ToRelation, + targetHandle: Handles.RelationLeft, + }); + graphologyGraph.addEdge(rightEntityId, relationId, { + type: 'connection', + sourceHandle: Handles.ToRelation, + targetHandle: Handles.RelationRight, + }); + + if (config.autoSendQueries) { + // sendQuery(); + } + + dispatch(setQuerybuilderNodes(graphologyGraph.export())); + break; + // Creates an attribute element with the correct dataType + // case QueryElementTypes.Attribute: + // createNodeFromSchema( + // QueryElementTypes.Attribute, + // position, + // dragData.name, + // dragData.fadeIn, + // dragData.datatype + // ); + // break; + } + }; return ( <div style={{ width: '100%', - height: '100vh', + height: '100%', }} + ref={divRef} > <ReactFlowProvider> <ReactFlow edges={elements.edges} nodes={elements.nodes} snapGrid={[10, 10]} - // snapToGrid + snapToGrid nodeTypes={nodeTypes} edgeTypes={edgeTypes} connectionLineComponent={ConnectionDragLine} + // connectionMode={ConnectionMode.Loose} onInit={onInit} onNodeDrag={onNodeDrag} onNodeDragStop={onNodeDragStop} + onDragOver={onDragOver} + // onConnect={this.queryBuilderViewModel.onConnect} + onDrop={onDrop} + onNodesDelete={onNodesDelete} + onNodesChange={onNodesChange} + deleteKeyCode="Backspace" className={styles.reactflow} > <Background gap={10} size={0.7} /> + <Controls + showZoom={false} + showInteractive={false} + className={styles.controls} + > + <ControlButton + className={styles.buttons} + title={'Remove all elements'} + onClick={() => clearAllNodes()} + > + <DeleteIcon /> + </ControlButton> + <ControlButton + className={styles.buttons} + title={'Export querybuilder'} + onClick={(event) => { + event.stopPropagation(); + // this.setState({ + // ...this.state, + // exportMenuAnchor: event.currentTarget, + // }); + }} + > + <ExportIcon /> + </ControlButton> + <ControlButton + className={styles.buttons} + title={'Other settings'} + onClick={(event) => { + event.stopPropagation(); + // this.setState({ + // ...this.state, + // settingsMenuAnchor: event.currentTarget, + // }); + }} + > + <SettingsIcon /> + </ControlButton> + </Controls> </ReactFlow> </ReactFlowProvider> </div> diff --git a/libs/shared/lib/querybuilder/panel/shemaquerybuilder.stories.tsx b/libs/shared/lib/querybuilder/panel/shemaquerybuilder.stories.tsx new file mode 100644 index 000000000..61625236b --- /dev/null +++ b/libs/shared/lib/querybuilder/panel/shemaquerybuilder.stories.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { Meta } from '@storybook/react'; +import { Provider } from 'react-redux'; +import { + colorPaletteConfigSlice, + graphQueryResultSlice, + querybuilderSlice, + schemaSlice, + setQuerybuilderNodes, + setSchema, + store, +} from '@graphpolaris/shared/lib/data-access/store'; +import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/theme'; +import { SchemaUtils } from '@graphpolaris/shared/lib/schema/schema-utils'; +import { Schema } from '@graphpolaris/shared/lib/schema/panel'; +import { movieSchemaRaw } from '@graphpolaris/shared/lib/mock-data'; +import { QueryBuilder } from '@graphpolaris/shared/lib/querybuilder'; +import { configureStore } from '@reduxjs/toolkit'; +import { configSlice } from '@graphpolaris/shared/lib/data-access/store/configSlice'; +import { QueryGraph } from '@graphpolaris/shared/lib/querybuilder/graph/graphology/model'; +import { MultiGraph } from 'graphology'; +import { QueryMultiGraph } from '@graphpolaris/shared/lib/querybuilder/graph/graphology/utils'; + +const SchemaAndQueryBuilder = () => { + return ( + <div + style={{ + display: 'flex', + height: '100%', + flexDirection: 'row', + width: '100%', + columnGap: '20px', + }} + > + <div style={{ width: '100%', height: '100%' }}> + <Schema /> + </div> + <div style={{ width: '100%', height: '100%' }}> + <QueryBuilder /> + </div> + </div> + ); +}; + +const Component: Meta = { + component: SchemaAndQueryBuilder, + title: 'Panel', + decorators: [ + // using the real store here + (story) => ( + <Provider store={store}> + <GraphPolarisThemeProvider> + <div + style={{ + width: '100%', + height: '95vh', + }} + > + {story()} + </div> + </GraphPolarisThemeProvider> + </Provider> + ), + ], +}; + +export const SchemaAndQueryBuilderInteractivity = { + play: async () => { + const dispatch = store.dispatch; + const schema = SchemaUtils.schemaBackend2Graphology(movieSchemaRaw); + + const graph = new QueryMultiGraph(); + dispatch(setQuerybuilderNodes(graph.export())); + dispatch(setSchema(schema.export())); + }, +}; + +export default Component; diff --git a/libs/shared/lib/querybuilder/pills/customFlowLines/connection.tsx b/libs/shared/lib/querybuilder/pills/customFlowLines/connection.tsx new file mode 100644 index 000000000..50625840f --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowLines/connection.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { EdgeProps, getSmoothStepPath, Position } from 'reactflow'; +import { handles } from '../../graph/reactflow/pillHandles'; + +/** + * A custom query element edge line component. + * @param {EdgeProps} param0 The coordinates for the start and end point, the id and the style. + */ +// export const EntityRFPill = React.memo(({ data }: { data: any }) => { +export function ConnectionLine({ + id, + sourceX, + sourceY, + targetX, + targetY, + style, + sourceHandleId, + targetHandleId, + source, + target, + sourcePosition, + targetPosition, +}: EdgeProps) { + //Centering the line + // sourceY -= 3; + // targetY -= 3; + + // Correct line positions with hardcoded numbers, because react flow lacks this functionality + // if (sourceHandleId == ) sourceX += 2; + + // if (targetHandleId == Handles.ToAttributeHandle) targetX += 2; + + let spos: Position = sourcePosition; + // if (sourceHandleId == handles.relation.fromEntity) { + // spos = Position.Left; + // sourceX += 7; + // sourceY += 3; + // } else if (sourceHandleId == handles.relation.toEntity) { + // spos = Position.Right; + // sourceX -= 2; + // sourceY -= 3; + // } else if ( + // sourceHandleId !== undefined && + // sourceHandleId !== null && + // sourceHandleId.includes('functionHandle') + // ) { + // spos = Position.Top; + // sourceX -= 4; + // sourceY += 3; + // } + + let tpos: Position = targetPosition; + // if (targetHandleId == handles.relation.fromEntity) { + // tpos = Position.Left; + // targetX += 7; + // targetY += 3; + // } else if (targetHandleId == handles.relation.toEntity) { + // tpos = Position.Right; + // targetX -= 2; + // targetY -= 3; + // } + + // Create smoothstep line + const path = getSmoothStepPath({ + sourceX: sourceX, + sourceY: sourceY, + sourcePosition: spos, + targetX: targetX, + targetY: targetY, + targetPosition: tpos, + }); + + // console.log(source, target, path); + + return ( + <g stroke="#2e2e2e"> + <path id={id} fill="none" strokeWidth={3} style={style} d={path[0]} /> + </g> + ); +} diff --git a/libs/shared/lib/ui/pills/customFlowLines/connectionDrag.tsx b/libs/shared/lib/querybuilder/pills/customFlowLines/connectionDrag.tsx similarity index 100% rename from libs/shared/lib/ui/pills/customFlowLines/connectionDrag.tsx rename to libs/shared/lib/querybuilder/pills/customFlowLines/connectionDrag.tsx diff --git a/libs/shared/lib/ui/pills/customFlowLines/index.ts b/libs/shared/lib/querybuilder/pills/customFlowLines/index.ts similarity index 100% rename from libs/shared/lib/ui/pills/customFlowLines/index.ts rename to libs/shared/lib/querybuilder/pills/customFlowLines/index.ts diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill-full.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill-full.stories.tsx new file mode 100644 index 000000000..196afa73c --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill-full.stories.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { + colorPaletteConfigSlice, + querybuilderSlice, + setQuerybuilderNodes, +} from '@graphpolaris/shared/lib/data-access/store'; +import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/theme'; +import { configureStore } from '@reduxjs/toolkit'; +import { Meta } from '@storybook/react'; +import { Provider } from 'react-redux'; +import { MultiGraph } from 'graphology'; +import { QueryBuilder } from '../../../panel'; +import { QueryGraph } from '../../../graph/graphology/model'; +import { circular } from 'graphology-layout'; +import { QueryMultiGraph } from '@graphpolaris/shared/lib/querybuilder/graph/graphology/utils'; + +const Component: Meta<typeof QueryBuilder> = { + component: QueryBuilder, + title: 'Querybuilder/Pills/AttributePill', + decorators: [ + (story) => ( + <Provider store={mockStore}> + <GraphPolarisThemeProvider>{story()}</GraphPolarisThemeProvider> + </Provider> + ), + ], +}; + +// Mock palette store +const mockStore = configureStore({ + reducer: { + colorPaletteConfig: colorPaletteConfigSlice.reducer, + querybuilder: querybuilderSlice.reducer, + }, +}); +const graph = new QueryMultiGraph(); +graph.addPill2Graphology( + { + type: 'attribute', + x: 170, + y: 160, + name: 'Attr string', + dataType: 'string', + matchType: 'NEQ', + value: 'mark', + fadeIn: false, + // depth: { min: 0, max: 1 }, + }, + '2' +); +console.log(graph.export()); + +mockStore.dispatch(setQuerybuilderNodes(graph.export())); + +export const Flow = { + args: {}, +}; + +export default Component; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.module.scss b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.module.scss new file mode 100644 index 000000000..880216ca8 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.module.scss @@ -0,0 +1,151 @@ +@use './variables.module.scss'; +@import '../../querypills.module.scss'; + +.attribute { + display: flex; + font-family: monospace; + font-weight: bold; + font-size: variables.$fontsize; + border-radius: 2px; +} + +// .handle { +// border: 0px; +// border-radius: 10px; +// left: 12px; +// width: 7px; +// height: 7px; +// margin-bottom: 11px; +// background: rgba(255; 255; 255; 0.6); +// box-shadow: 0 0 0 1px rgba(0; 0; 0; 0.3); +// transform-origin: center; +// } + +.contentWrapper { + display: flex; + align-items: center; + + .content { + padding: variables.$ypad 0 variables.$ypad 1ch; + max-width: 15ch; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } +} + +// Attribute Element +.attributeMain { + background: #bfb6af; + color: #fff; + border-radius: 18px; + font-family: monospace; + font-weight: bold; + font-size: 11px; + padding-top: 0.1em; + padding-right: 0.5em; + padding-bottom: 0.1em; + padding-left: 0.5em; +} + +.attributeHandleLeft { + left: 4px !important; + background: rgba(0, 0, 0, 0.3); + &::before { + content: ''; + width: 6; + height: 6; + left: 1; + bottom: 1; + border: 0; + border-radius: 5; + background: rgba(255, 255, 255, 0.6); + z-index: -1; + display: inline-block; + position: fixed; + } +} + +.attributeInput { + float: right; + padding: 0 1ch 0 0; + display: flex; + align-items: center; + + input { + background-color: rgba(100, 100, 100, 0.1); + font-family: monospace; + font-size: variables.$fontsize; + border: 1px solid rgba(100, 100, 100, 0.3); + border-radius: 2px; + height: variables.$height; + outline: none; + transition: border 0.3s; + color: black; + &::placeholder { + color: black; + } + + &:focus { + border: 1px solid rgba(0, 0, 0, 0.3); + } + } +} + +.attributeWrapper { + height: 100%; + color: black; + display: flex; + align-items: center; + gap: 0.6em; + margin-left: 0.8em; +} +.attributeWrapperSpan { + margin-left: 4px; +} + +// Attribute select component +.matchTypeSelect { + background-color: rgba(255, 255, 255, 0.6); + border-radius: 2px; + display: flex; + pointer-events: all; + cursor: pointer; + & select { + background: transparent; + border: none; + appearance: none; + + font-family: monospace; + font-size: 11px; + text-align: center; + } + & option { + font-family: monospace; + font-size: 11px; + } +} +.matchModifierTypeSelect { + background-color: rgba(255, 255, 255, 0.6); + border-radius: 2px; + + text-align: center; + & select { + background: transparent; + border: none; + appearance: none; + font-family: monospace; + font-weight: bolder; + color: black; + font-size: 11; + } + & option { + font-family: monospace; + font-size: 11px; + } +} + +.disable { + opacity: 1 !important; + pointer-events: none; +} diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.module.scss.d.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.module.scss.d.ts new file mode 100644 index 000000000..d44a6b42f --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.module.scss.d.ts @@ -0,0 +1,24 @@ +declare const classNames: { + readonly 'react-flow__node': 'react-flow__node'; + readonly selected: 'selected'; + readonly entityWrapper: 'entityWrapper'; + readonly hidden: 'hidden'; + readonly 'react-flow__edges': 'react-flow__edges'; + readonly 'react-flow__edge-default': 'react-flow__edge-default'; + readonly handleConnectedFill: 'handleConnectedFill'; + readonly handleConnectedBorderRight: 'handleConnectedBorderRight'; + readonly handleConnectedBorderLeft: 'handleConnectedBorderLeft'; + readonly handleFunction: 'handleFunction'; + readonly attribute: 'attribute'; + readonly contentWrapper: 'contentWrapper'; + readonly content: 'content'; + readonly attributeMain: 'attributeMain'; + readonly attributeHandleLeft: 'attributeHandleLeft'; + readonly attributeInput: 'attributeInput'; + readonly attributeWrapper: 'attributeWrapper'; + readonly attributeWrapperSpan: 'attributeWrapperSpan'; + readonly matchTypeSelect: 'matchTypeSelect'; + readonly matchModifierTypeSelect: 'matchModifierTypeSelect'; + readonly disable: 'disable'; +}; +export = classNames; diff --git a/libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.stories.tsx similarity index 85% rename from libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.stories.tsx rename to libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.stories.tsx index 501349916..eec408f5a 100644 --- a/libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.stories.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Meta } from '@storybook/react'; -import AttributeRFPill from './attributepill'; +import AttributePill from './attributepill'; import { configureStore } from '@reduxjs/toolkit'; import { Provider } from 'react-redux'; import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/theme'; @@ -12,13 +12,13 @@ import { } from '@graphpolaris/shared/lib/data-access/store'; import { ReactFlowProvider } from 'reactflow'; -const Component: Meta<typeof AttributeRFPill> = { +const Component: Meta<typeof AttributePill> = { /* 👇 The title prop is optional. * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading * to learn how to generate automatic titles */ - title: 'Components/Pills/AttributeRFPill', - component: AttributeRFPill, + title: 'Querybuilder/Pills/AttributePill', + component: AttributePill, decorators: [ (story) => ( <Provider store={Mockstore}> @@ -41,7 +41,7 @@ const Mockstore = configureStore({ }, }); -export const Default = { +export const Simple = { args: { data: { name: 'TestEntity', diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.tsx new file mode 100644 index 000000000..26d4bb0bc --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/attributepill.tsx @@ -0,0 +1,153 @@ +import { useTheme } from '@mui/material'; +import React, { useMemo, useState } from 'react'; +import styles from './attributepill.module.scss'; +import { Handle, NodeProps, Position } from 'reactflow'; +import { AttributeOperatorSelect } from './operator'; +import Select from './select-component'; +import { AttributeNode } from '../../../graph/reactflow/model'; +import { Handles } from '../../../graph/reactflow/handles'; + +/** + * Component to render an attribute flow element + * @param {FlowElement<EntityData>)} param0 The data of an entity flow element. + */ +export const AttributePill = React.memo(({ id, data }: AttributeNode) => { + const theme = useTheme(); + const [read, setRead] = useState(true); + // console.log('AttributePill', data); + + /** + * Check if the pressed key is enter in order to send the new query. + * @param event Key press event. + */ + const _onKeyDown = (event: any): void => { + if (event.key == 'Enter') setRead(true); + }; + + /** + * Checks if the string input is a number. + * @param x String input. + * @returns {boolean} True if input is a number. + */ + const isNumber = (x: string): boolean => { + { + if (typeof x != 'string') return false; + return !Number.isNaN(x) && !Number.isNaN(parseFloat(x)); + } + }; + + /** + * Calculates the width of an element based on the length of a monospaced font. + * @param str Input string. + * @returns {string} Containing the length in css format. + */ + const calcWidth = (str: string) => { + if (str == '') { + return 1.5 + 'ch'; + } + return str.length + 0.5 + 'ch'; + }; + + /** + * Input contraint checker for the attribute input fields. + * @param type Data.dataType. + * @param str Input string. + * @returns {string} Result string after the contraints are applied. + */ + const inputConstraint = (type: string, str: string): string => { + let res = ''; + switch (type) { + case 'string': + res = str; + break; + case 'bool': + res = str; + break; // TODO: only false and true live update will break since it will not allow to write more that 1 letter + case 'int': + isNumber(str) ? (res = str) : (res = ''); + break; // TODO: check if letters after number + default: + res = str; + break; + } + return res; + }; + + //TODO: docstrings + const className = + styles.attributeHandleLeft + + ' ' + + (false ? styles.handleConnectedFill : ''); + + const onChange = (e: any) => { + if (data != undefined) { + data.value = inputConstraint(data.dataType, e.target.value); + e.target.style.maxWidth = calcWidth(data.value); + } + }; + + /**Constraint datatypes back end. + * string MatchTypes: EQ/NEQ/contains/excludes. + * int MatchTypes: EQ/NEQ/GT/LT/GET/LET. + * bool MatchTypes: EQ/NEQ. + */ + //TODO: fix use of relation boilerplate styling + + return ( + <div + className={styles.attributeMain} + style={{ backgroundColor: theme.palette.custom.elements.attribute[0] }} + > + <Handle + id={Handles.OnAttribute} + type="source" + position={Position.Left} + className={ + styles.attributeHandleLeft + + ' ' + + (false ? styles.handleConnectedFill : '') + } + style={{ backgroundColor: theme.palette.custom.elements.attribute[1] }} + /> + <Handle + id={Handles.ToAttribute} + type="source" + position={Position.Left} + className={ + styles.attributeHandleLeft + + ' ' + + (false ? styles.handleConnectedFill : '') + } + style={{ + backgroundColor: theme.palette.custom.elements.attribute[1], + left: 50, + }} + /> + <div className={styles.attributeWrapper}> + <span className={styles.attributeWrapperSpan}>{data?.name}</span> + <Select data={data} /> + <span className={styles.attributeInput}> + <input type="hidden"></input> + <input + style={{ maxWidth: calcWidth(data?.value || '') }} + type="string" + readOnly={read} + placeholder={'?'} + value={data?.value || ''} + onChange={onChange} + onDoubleClick={() => { + setRead(false); + }} + onBlur={() => { + setRead(true); + }} + onKeyDown={_onKeyDown} + ></input> + </span> + </div> + </div> + ); +}); +AttributePill.displayName = 'AttributePill'; + +export default AttributePill; diff --git a/libs/shared/lib/querybuilder/usecases/attribute/checkInput.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/checkInput.ts similarity index 100% rename from libs/shared/lib/querybuilder/usecases/attribute/checkInput.ts rename to libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/checkInput.ts diff --git a/libs/shared/lib/querybuilder/usecases/attribute/getAttributeBoolOperators.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/getAttributeBoolOperators.ts similarity index 100% rename from libs/shared/lib/querybuilder/usecases/attribute/getAttributeBoolOperators.ts rename to libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/getAttributeBoolOperators.ts diff --git a/libs/shared/lib/ui/pills/customFlowPills/attributepill/index.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/index.ts similarity index 100% rename from libs/shared/lib/ui/pills/customFlowPills/attributepill/index.ts rename to libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/index.ts diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/operator/index.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/operator/index.ts new file mode 100644 index 000000000..fe9dde909 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/operator/index.ts @@ -0,0 +1 @@ +export * from './operatorselect'; diff --git a/libs/shared/lib/ui/pills/customFlowPills/attributepill/operatorselect.module.scss b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/operator/operatorselect.module.scss similarity index 97% rename from libs/shared/lib/ui/pills/customFlowPills/attributepill/operatorselect.module.scss rename to libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/operator/operatorselect.module.scss index 1c31d2744..286fa1da4 100644 --- a/libs/shared/lib/ui/pills/customFlowPills/attributepill/operatorselect.module.scss +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/operator/operatorselect.module.scss @@ -1,4 +1,4 @@ -@use './variables.module.scss'; +@use '../variables.module.scss'; .container { position: relative; diff --git a/libs/shared/lib/ui/pills/customFlowPills/attributepill/operatorselect.module.scss.d.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/operator/operatorselect.module.scss.d.ts similarity index 100% rename from libs/shared/lib/ui/pills/customFlowPills/attributepill/operatorselect.module.scss.d.ts rename to libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/operator/operatorselect.module.scss.d.ts diff --git a/libs/shared/lib/ui/pills/customFlowPills/attributepill/operatorselect.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/operator/operatorselect.tsx similarity index 96% rename from libs/shared/lib/ui/pills/customFlowPills/attributepill/operatorselect.tsx rename to libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/operator/operatorselect.tsx index cf1d9726a..d62e39a1d 100644 --- a/libs/shared/lib/ui/pills/customFlowPills/attributepill/operatorselect.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/operator/operatorselect.tsx @@ -21,7 +21,7 @@ interface Props { changed?: (newSelected: SelectOption<string>) => void; } -function AttributeOperatorSelect({ +export function AttributeOperatorSelect({ options, selected, changed = () => { @@ -83,5 +83,3 @@ function AttributeOperatorSelect({ </div> ); } - -export default AttributeOperatorSelect; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/select-component.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/select-component.tsx new file mode 100644 index 000000000..38a35b85e --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/select-component.tsx @@ -0,0 +1,88 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/* istanbul ignore file */ +/* The comment above was added so the code coverage wouldn't count this file towards code coverage. + * We do not test components/renderfunctions/styling files. + * See testing plan for more details.*/ +import { useTheme } from '@mui/material'; +import React, { useState } from 'react'; +import styles from './attributepill.module.scss'; +import { AttributeData } from '../../../graph/reactflow/model'; + +export default function SelectComponent({ data }: { data: AttributeData }) { + const theme = useTheme(); + + /** + * Calculate the width of the select element based on the displayed value. + * @param str Input string. + * @returns {string} Containing the length in css format. + */ + const calcSelectWidth = (str: string): string => { + if (str == '') { + return 1.5 + 'ch'; + } + return str.length + 1.5 + 'ch'; + }; + + /** + * Constant switch to append the right options for the select element based on the data.dataType. + * @returns {JSX.Element} Option list using React.Fragment as parent element. + */ + const list = (): JSX.Element => { + switch (data.dataType) { + case 'string': + return ( + <React.Fragment> + <option value="EQ">==</option> + <option value="NEQ">!=</option> + <option value="contains">contains</option> + <option value="excludes">excludes</option> + </React.Fragment> + ); + case 'int': + case 'float': + return ( + <React.Fragment> + <option value="EQ">==</option> + <option value="NEQ">!=</option> + <option value="GT">{'>'}</option> + <option value="LT">{'<'}</option> + <option value="GET">{'>='}</option> + <option value="LET">{'<='}</option> + </React.Fragment> + ); + case 'bool': + return ( + <React.Fragment> + <option value="EQ">==</option> + <option value="NEQ">!=</option> + </React.Fragment> + ); + default: + return <option>Error</option>; + } + }; + + return ( + <div + className={styles.matchTypeSelect} + style={{ backgroundColor: theme.palette.custom.elements.attribute[1] }} + > + <select + style={{ maxWidth: calcSelectWidth('==') }} + value={data.matchType} + name="operators" + onChange={(e) => { + data.matchType = e.target.value; + e.target.style.maxWidth = calcSelectWidth(e.target.value); + }} + > + {list()} + </select> + </div> + ); +} diff --git a/libs/shared/lib/ui/pills/customFlowPills/attributepill/variables.module.scss b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/variables.module.scss similarity index 100% rename from libs/shared/lib/ui/pills/customFlowPills/attributepill/variables.module.scss rename to libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/variables.module.scss diff --git a/libs/shared/lib/ui/pills/shared-ui-pills.module.scss.d.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/variables.module.scss.d.ts similarity index 100% rename from libs/shared/lib/ui/pills/shared-ui-pills.module.scss.d.ts rename to libs/shared/lib/querybuilder/pills/customFlowPills/attributepill/variables.module.scss.d.ts diff --git a/libs/shared/lib/ui/pills/customFlowLines/connection.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/edge-line.tsx similarity index 54% rename from libs/shared/lib/ui/pills/customFlowLines/connection.tsx rename to libs/shared/lib/querybuilder/pills/customFlowPills/edge-line.tsx index 646b6f4f2..2d9006f67 100644 --- a/libs/shared/lib/ui/pills/customFlowLines/connection.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/edge-line.tsx @@ -1,13 +1,22 @@ -import { handles } from '@graphpolaris/shared/lib/querybuilder/usecases'; +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/* istanbul ignore file */ +/* The comment above was added so the code coverage wouldn't count this file towards code coverage. + * We do not test components/renderfunctions/styling files. + * See testing plan for more details.*/ import React from 'react'; import { EdgeProps, getSmoothStepPath, Position } from 'reactflow'; +import { Handles } from '../../graph-reactflow/handles'; /** * A custom query element edge line component. * @param {EdgeProps} param0 The coordinates for the start and end point, the id and the style. */ -// export const EntityRFPill = React.memo(({ data }: { data: any }) => { -export function ConnectionLine({ +export default function EdgeLine({ id, sourceX, sourceY, @@ -22,16 +31,16 @@ export function ConnectionLine({ targetY -= 3; // Correct line positions with hardcoded numbers, because react flow lacks this functionality - // if (sourceHandleId == ) sourceX += 2; + if (sourceHandleId == Handles.ToAttributeHandle) sourceX += 2; - // if (targetHandleId == Handles.ToAttributeHandle) targetX += 2; + if (targetHandleId == Handles.ToAttributeHandle) targetX += 2; let spos: Position = Position.Bottom; - if (sourceHandleId == handles.relation.fromEntity) { + if (sourceHandleId == Handles.RelationLeft) { spos = Position.Left; sourceX += 7; sourceY += 3; - } else if (sourceHandleId == handles.relation.toEntity) { + } else if (sourceHandleId == Handles.RelationRight) { spos = Position.Right; sourceX -= 2; sourceY -= 3; @@ -46,11 +55,11 @@ export function ConnectionLine({ } let tpos: Position = Position.Bottom; - if (targetHandleId == handles.relation.fromEntity) { + if (targetHandleId == Handles.RelationLeft) { tpos = Position.Left; targetX += 7; targetY += 3; - } else if (targetHandleId == handles.relation.toEntity) { + } else if (targetHandleId == Handles.RelationRight) { tpos = Position.Right; targetX -= 2; targetY -= 3; @@ -67,8 +76,14 @@ export function ConnectionLine({ }); return ( - <g stroke="#2e2e2e"> - <path id={id} fill="none" strokeWidth={3} style={style} d={path[0]} /> + <g stroke="black"> + <path + id={id} + fill="none" + strokeWidth={5} + style={style} + d={path.toString()} //TODO: Check + /> </g> ); } diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx new file mode 100644 index 000000000..18b65e8be --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill-full.stories.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { + colorPaletteConfigSlice, + querybuilderSlice, + setQuerybuilderNodes, +} from '@graphpolaris/shared/lib/data-access/store'; +import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/theme'; +import { configureStore } from '@reduxjs/toolkit'; +import { Meta } from '@storybook/react'; +import { Provider } from 'react-redux'; +import { MultiGraph } from 'graphology'; +import { QueryBuilder } from '../../../panel'; +import { QueryGraph } from '../../../graph/graphology/model'; +import { circular } from 'graphology-layout'; +import { QueryMultiGraph } from '@graphpolaris/shared/lib/querybuilder/graph/graphology/utils'; + +const Component: Meta<typeof QueryBuilder> = { + component: QueryBuilder, + title: 'Querybuilder/Pills/EntityPill', + decorators: [ + (story) => ( + <Provider store={mockStore}> + <GraphPolarisThemeProvider>{story()}</GraphPolarisThemeProvider> + </Provider> + ), + ], +}; + +// Mock palette store +const mockStore = configureStore({ + reducer: { + colorPaletteConfig: colorPaletteConfigSlice.reducer, + querybuilder: querybuilderSlice.reducer, + }, +}); +const graph = new QueryMultiGraph(); +graph.addPill2Graphology( + { type: 'entity', x: 100, y: 100, name: 'Entity Pill', fadeIn: false }, + '2' +); +console.log(graph.export()); + +mockStore.dispatch(setQuerybuilderNodes(graph.export())); + +export const Flow = { + args: {}, +}; + +export default Component; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.module.scss b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.module.scss new file mode 100644 index 000000000..79e336437 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.module.scss @@ -0,0 +1,175 @@ +@import '../../querypills.module.scss'; + +.entity { + display: flex; + font-family: monospace; + font-weight: bold; + font-size: 10px; + padding: 4px 2ch; + border-radius: 3px; +} + +.highlighted { + box-shadow: black 0 0 2px; +} + +.handleLeft { + border: 0px; + border-radius: 0px; + left: 12px; + width: 7px; + height: 7px; + margin-bottom: 11px; + background: rgba(255, 255, 255, 0.6); + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3); + transform-origin: center; +} + +// .handleBottom { +// border: 0px; +// border-radius: 0px; +// width: 7px; +// height: 7px; +// left: 27.5px; +// margin-bottom: 11px; +// background: rgba(255, 255, 255, 0.6); +// box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3); +// transform: rotate(-45deg); +// transform-origin: center; +// } + +.react-flow__edges { + zindex: 3; +} +.react-flow__nodes { +} +.react-flow__pane { +} +.react-flow__edge-default .selected { + stroke: gray !important; +} + +.contentWrapper { + margin-left: 3ch; + + span { + max-width: 20ch; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + display: block; + } +} + +// Entity element +.entity { + background: #e9e9e9; + display: flex; + border-radius: 1px; + font-family: monospace; + font-weight: bold; + font-size: 11px; + color: black; + padding-left: 45px; +} + +.entityFade { + opacity: 1; + animation-iteration-count: 1; + animation: fade-id, 600ms, cubic-bezier(0.4, 0, 1, 1); + @keyframes fade-id { + 0% { + opacity: 0; + } + 40% { + opacity: 0; + } + 100% { + opacity: 1; + } + } +} + +.entityHandleLeft { + border: 0; + border-radius: 0; + left: 12; + width: 8; + height: 8; + margin-bottom: 15; + background: rgba(0, 0, 0, 0.3); + transform-origin: center; + &::before { + content: ''; + width: 6; + height: 6; + left: 1; + bottom: 1; + border: 0; + border-radius: 0; + background: rgba(255, 255, 255, 0.6); + z-index: -1; + display: inline-block; + position: fixed; + } +} + +.entityHandleBottom { + border: 0; + border-radius: 0; + width: 8; + height: 8; + left: 27.5; + margin-bottom: 15; + background: rgba(0, 0, 0, 0.3); + transform: rotate(-45deg); + transform-origin: center; + &::before { + content: ''; + width: 6; + height: 6; + left: 1; + bottom: 1; + border: 0; + border-radius: 0; + background: rgba(255, 255, 255, 0.6); + z-index: -1; + display: inline-block; + position: fixed; + } +} + +.entityWrapper { + display: block; +} + +.entitySpan { + display: block; +} + +// General style +.ToRelationHandle { + border-radius: 1px !important; + left: 10px !important; + top: 35% !important; + background: rgba(0, 0, 0, 0.3) !important; +} + +.ToAttributeHandle { + border-radius: 1px !important; + left: 20px !important; + top: 35% !important; + background: rgba(0, 0, 0, 0.3) !important; + transform: rotate(45deg) scale(0.9) !important; + transform-origin: center, center; +} + +.ReceiveFunctionHandle { + left: 37px !important; + top: 35% !important; + background: rgba(0, 0, 0, 0.3) !important; +} + +.handleFunctionEntity { + margin-left: 5px; +} diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.module.scss.d.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.module.scss.d.ts new file mode 100644 index 000000000..bc2a7aa4b --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.module.scss.d.ts @@ -0,0 +1,25 @@ +declare const classNames: { + readonly 'react-flow__node': 'react-flow__node'; + readonly selected: 'selected'; + readonly entityWrapper: 'entityWrapper'; + readonly hidden: 'hidden'; + readonly 'react-flow__edges': 'react-flow__edges'; + readonly 'react-flow__edge-default': 'react-flow__edge-default'; + readonly handleConnectedFill: 'handleConnectedFill'; + readonly handleConnectedBorderRight: 'handleConnectedBorderRight'; + readonly handleConnectedBorderLeft: 'handleConnectedBorderLeft'; + readonly handleFunction: 'handleFunction'; + readonly entity: 'entity'; + readonly highlighted: 'highlighted'; + readonly handleLeft: 'handleLeft'; + readonly contentWrapper: 'contentWrapper'; + readonly entityFade: 'entityFade'; + readonly entityHandleLeft: 'entityHandleLeft'; + readonly entityHandleBottom: 'entityHandleBottom'; + readonly entitySpan: 'entitySpan'; + readonly ToRelationHandle: 'ToRelationHandle'; + readonly ToAttributeHandle: 'ToAttributeHandle'; + readonly ReceiveFunctionHandle: 'ReceiveFunctionHandle'; + readonly handleFunctionEntity: 'handleFunctionEntity'; +}; +export = classNames; diff --git a/libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.stories.tsx similarity index 78% rename from libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.stories.tsx rename to libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.stories.tsx index 6c79fefe6..87d7a9e8c 100644 --- a/libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.stories.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Meta } from '@storybook/react'; -import EntityRFPill from './entitypill'; +import { Meta, StoryObj } from '@storybook/react'; +import EntityFlowElement from './entitypill'; import { configureStore } from '@reduxjs/toolkit'; import { Provider } from 'react-redux'; import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/theme'; @@ -11,14 +11,15 @@ import { schemaSlice, } from '@graphpolaris/shared/lib/data-access/store'; import { ReactFlowProvider } from 'reactflow'; +import { EntityData, EntityNode } from '../../../graph-reactflow/model'; -const Component: Meta<typeof EntityRFPill> = { +const Component: Meta<typeof EntityFlowElement> = { /* 👇 The title prop is optional. * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading * to learn how to generate automatic titles */ - title: 'Components/Pills/EntityRFPill', - component: EntityRFPill, + title: 'Querybuilder/Pills/EntityPill', + component: EntityFlowElement, decorators: [ (story) => ( <Provider store={Mockstore}> @@ -43,10 +44,11 @@ const Mockstore = configureStore({ // const Template = (args: any) => <EntityRFPill {...args} />; -export const Default = { +export const Default: StoryObj<{ data: EntityData }> = { args: { data: { name: 'TestEntity', + fadeIn: true, }, }, }; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx new file mode 100644 index 000000000..51f8aa074 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/entitypill.tsx @@ -0,0 +1,65 @@ +// import { handles } from '@graphpolaris/shared/lib/querybuilder/usecases'; +import { useTheme } from '@mui/material'; +import React, { useEffect } from 'react'; +import { ReactFlow, Handle, Position } from 'reactflow'; +import styles from './entitypill.module.scss'; +import { EntityNode } from '../../../graph/reactflow/model'; +import { Handles } from '../../../graph/reactflow/handles'; + +/** + * Component to render an entity flow element + * @param {NodeProps} param0 The data of an entity flow element. + */ +export const EntityFlowElement = React.memo(({ data }: EntityNode) => { + const theme = useTheme(); + // console.log('EntityPill', data); + + // TODO: Change flow element width when text overflows + const animation = data.fadeIn ? styles.entityFade : ''; + + return ( + <div + className={`${styles.entity} ${animation} query_builder-entity`} + style={{ backgroundColor: theme.palette.custom.elements.entityBase[0] }} + > + <Handle + id={Handles.ToRelation} + type="source" + position={Position.Bottom} + className={ + styles.ToRelationHandle + + ' ' + + (false ? styles.handleConnectedFill : '') + } + /> + <Handle + id={Handles.ToAttribute} + type="target" + position={Position.Bottom} + className={ + styles.ToAttributeHandle + + ' ' + + (false ? styles.handleConnectedFill : '') + } + /> + <Handle + id={Handles.ReceiveFunction} + type="target" + position={Position.Bottom} + className={ + styles.ReceiveFunctionHandle + + ' ' + + (false ? styles.handleConnectedFill : '') + } + /> + + <div className={styles.entityWrapper}> + <span className={styles.entitySpan}>{data.name}</span> + </div> + </div> + ); +}); + +EntityFlowElement.displayName = 'EntityFlowElement'; + +export default EntityFlowElement; diff --git a/libs/shared/lib/ui/pills/customFlowPills/entitypill/index.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/index.ts similarity index 100% rename from libs/shared/lib/ui/pills/customFlowPills/entitypill/index.ts rename to libs/shared/lib/querybuilder/pills/customFlowPills/entitypill/index.ts diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/SelectFunction.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/SelectFunction.tsx new file mode 100644 index 000000000..60e40cbb5 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/SelectFunction.tsx @@ -0,0 +1,89 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/* istanbul ignore file */ +/* The comment above was added so the code coverage wouldn't count this file towards code coverage. + * We do not test components/renderfunctions/styling files. + * See testing plan for more details.*/ +import React, { useState } from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import { useStyles } from '../../QueryBuilderStylesheet'; + +// Create style constant to prevent rereaction of styles +const madeStyles = makeStyles(useStyles); + +/** + * The flow element for the modifier. + * @param param0 The data of the modifier flow element. + */ +export default function ModifierFlowElement({ data }: any) { + const styles = madeStyles(); + const [disable, setDisable] = useState(true); + const [disClass, setDisClass] = useState(styles.disable); + + /** + * Calculate the width of the select element based on the displayed value. + * @param str Input string. + * @returns String containg the length in css format. + */ + const calcSelectWidth = (str: string): string => { + if (str == '') return 1.5 + 'ch'; + return str.length + 1.5 + 'ch'; + }; + + /** Disable the select field */ + const disableSelect = (): void => { + setDisable(true); + setDisClass(styles.disable); + }; + + /** Enable the select field */ + const enableSelect = (): void => { + setDisable(false); + setDisClass(''); + }; + + /** + * Constant switch to append the right options for the select element based on the data.type. + * @returns {JSX.Element} Option list using React.Fragment as parent element. + */ + const list = (): JSX.Element => { + return ( + <React.Fragment> + <option color="black" value="COUNT"> + COUNT + </option> + <option value="SUM">SUM</option> + <option value="MIN">MIN</option> + <option value="MAX">MAX</option> + </React.Fragment> + ); + }; + + return ( + <div + className={styles.matchModifierTypeSelect} + onBlur={disableSelect} + onDoubleClick={enableSelect} + > + <select + style={{ + color: disable ? 'black' : 'black', + maxWidth: calcSelectWidth('COUNT'), + }} + name="operators" + className={disClass} + disabled={disable} + onChange={(e) => { + data.type = e.target.value; + e.target.style.maxWidth = calcSelectWidth(e.target.value); + }} + > + {list()} + </select> + </div> + ); +} diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.module.scss b/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.module.scss new file mode 100644 index 000000000..7e589ded7 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.module.scss @@ -0,0 +1,167 @@ +@import '../entitypill/entitypill.module.scss'; + +$baseColor: #8c75c9; + +// Function element +.function { + background-color: $baseColor; + display: 'flex'; + border-radius: '1px'; + font-family: monospace; + font-weight: 'bold'; + font-size: 11px; + color: black; + min-width: '140px'; + text-align: 'center'; + line-height: 20px; + padding-top: 2; + padding-right: 5; + padding-bottom: 4; + padding-left: 5; + + &::before { + position: 'absolute'; + content: '""'; + width: '100%'; + left: '0px'; + top: '0px'; + height: '100%'; + border-radius: '3px'; + z-index: -1; + background-color: $baseColor; + border-bottom: 'none'; + } + + &::after { + position: 'absolute'; + content: '""'; + width: '100%'; + left: '0px'; + top: '0'; + height: '100%'; + border-radius: '3px'; + z-index: -1; + background-color: $baseColor; + border-top: 'none'; + } +} + +.functionWrapper { + display: 'block'; + width: 'inherit'; + align-items: 'center'; + justify-content: 'space-between'; +} +.functionHandleFiller { + flex: '1 1 0'; + display: 'flow-root'; +} +.functionHandle { + border: 0; + border-radius: 0; + width: 8; + height: 8; + left: 9; + top: 7; + background: 'rgba(0, 0, 0, 0.3)'; + transform-origin: 'center'; + position: 'relative'; + float: 'right'; + margin-right: '20px'; + &::before { + content: '""'; + width: 6; + height: 6; + left: 1; + bottom: 1; + border: 0; + border-radius: 0; + background: 'rgba(255, 255, 255, 0.6)'; + z-index: -1; + display: 'inline-block'; + position: 'fixed'; + } +} +.functionHandleBottom { + border: 0; + border-radius: 0; + width: 8; + height: 8; + left: 27.5; + margin-bottom: 10; + background-color: 'rgba(255, 255, 255, 0.6)'; + transform: 'rotate(-45deg)'; + transform-origin: 'center'; + &::before { + content: '""'; + width: 6; + height: 6; + left: 1; + bottom: 1; + border: 0; + border-radius: 0; + background-color: 'rgba(255, 255, 255, 0.6)'; + z-index: -1; + display: 'inline-block'; + position: 'fixed'; + } +} +.functionInputHolder { + display: 'flex'; + float: 'right'; + margin-right: '20px'; + margin-top: '4px'; + margin-left: '5px'; + max-width: '80px'; + background-color: 'rgba(255, 255, 255, 0.6)'; + border-radius: '2px'; + align-items: 'center'; + max-height: '12px'; +} +.functionInput { + z-index: 1; + cursor: 'text'; + min-width: '0px'; + max-width: '1.5ch'; + height: '14px'; + border: 'none'; + background-color: 'rgba(255, 255, 255, 0.6)'; + text-align: 'center'; + font-family: 'monospace'; + font-weight: 'bold'; + font-size: '11px'; + color: '#181520'; + user-select: 'none'; + font-style: 'italic'; + float: 'right'; + margin: '3px 0'; + margin-right: '10px'; + &:focus { + outline: 'none'; + user-select: 'none'; + } + &::placeholder { + outline: 'none'; + user-select: 'none'; + font-style: 'italic'; + } +} +.functionReadonly { + cursor: 'grab !important'; + color: '#181520 !important'; + user-select: 'none'; + font-style: 'normal !important'; +} +.functionSpan { + float: 'left'; + margin-left: 20; + margin-right: 20; +} +.functionSpanRight { + float: 'right'; + margin-right: 10; +} + +.functionDataWrapper { + display: block; +} diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.module.scss.d.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.module.scss.d.ts new file mode 100644 index 000000000..924514ea1 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.module.scss.d.ts @@ -0,0 +1,36 @@ +declare const classNames: { + readonly 'react-flow__node': 'react-flow__node'; + readonly selected: 'selected'; + readonly entityWrapper: 'entityWrapper'; + readonly hidden: 'hidden'; + readonly 'react-flow__edges': 'react-flow__edges'; + readonly 'react-flow__edge-default': 'react-flow__edge-default'; + readonly handleConnectedFill: 'handleConnectedFill'; + readonly handleConnectedBorderRight: 'handleConnectedBorderRight'; + readonly handleConnectedBorderLeft: 'handleConnectedBorderLeft'; + readonly handleFunction: 'handleFunction'; + readonly entity: 'entity'; + readonly highlighted: 'highlighted'; + readonly handleLeft: 'handleLeft'; + readonly contentWrapper: 'contentWrapper'; + readonly entityFade: 'entityFade'; + readonly entityHandleLeft: 'entityHandleLeft'; + readonly entityHandleBottom: 'entityHandleBottom'; + readonly entitySpan: 'entitySpan'; + readonly ToRelationHandle: 'ToRelationHandle'; + readonly ToAttributeHandle: 'ToAttributeHandle'; + readonly ReceiveFunctionHandle: 'ReceiveFunctionHandle'; + readonly handleFunctionEntity: 'handleFunctionEntity'; + readonly function: 'function'; + readonly functionWrapper: 'functionWrapper'; + readonly functionHandleFiller: 'functionHandleFiller'; + readonly functionHandle: 'functionHandle'; + readonly functionHandleBottom: 'functionHandleBottom'; + readonly functionInputHolder: 'functionInputHolder'; + readonly functionInput: 'functionInput'; + readonly functionReadonly: 'functionReadonly'; + readonly functionSpan: 'functionSpan'; + readonly functionSpanRight: 'functionSpanRight'; + readonly functionDataWrapper: 'functionDataWrapper'; +}; +export = classNames; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.stories.tsx new file mode 100644 index 000000000..2488295e0 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.stories.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { Meta, StoryObj } from '@storybook/react'; +import FunctionFlowElement from './functionpill'; +import { configureStore } from '@reduxjs/toolkit'; +import { Provider } from 'react-redux'; +import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/theme'; + +import { + colorPaletteConfigSlice, + querybuilderSlice, + schemaSlice, +} from '@graphpolaris/shared/lib/data-access/store'; +import { ReactFlowProvider } from 'reactflow'; +import { + EntityData, + EntityNode, + FunctionData, +} from '../../../graph-reactflow/model'; + +const Component: Meta<typeof FunctionFlowElement> = { + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: 'Querybuilder/Pills/FunctionPill', + component: FunctionFlowElement, + decorators: [ + (story) => ( + <Provider store={Mockstore}> + <GraphPolarisThemeProvider> + <ReactFlowProvider>{story()}</ReactFlowProvider> + </GraphPolarisThemeProvider> + </Provider> + ), + ], +}; + +export default Component; + +// A super-simple mock of a redux store +const Mockstore = configureStore({ + reducer: { + colorPaletteConfig: colorPaletteConfigSlice.reducer, + querybuilder: querybuilderSlice.reducer, + // schema: schemaSlice.reducer, + }, +}); + +// const Template = (args: any) => <EntityRFPill {...args} />; + +export const Default: StoryObj<{ data: FunctionData }> = { + args: { + data: { + functionType: 'test', + fadeIn: true, + args: { + string: { + displayName: 'testarg', + connectable: false, + value: 'testvalue', + visible: true, + }, + }, + }, + }, +}; + +// Default.decorators = [ +// (story) => ( +// <Provider store={Mockstore}> +// <GraphPolarisThemeProvider>{story()}</GraphPolarisThemeProvider> +// </Provider> +// ), +// ]; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.tsx new file mode 100644 index 000000000..c0edcaff2 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/functionpill.tsx @@ -0,0 +1,166 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/* istanbul ignore file */ +/* The comment above was added so the code coverage wouldn't count this file towards code coverage. + * We do not test components/renderfunctions/styling files. + * See testing plan for more details.*/ +import React, { useState } from 'react'; +import { Handle, Position } from 'reactflow'; +import styles from './functionpill.module.scss'; +import { useTheme } from '@mui/material'; +import { FunctionData, FunctionNode } from '../../../graph/reactflow/model'; +import { Handles } from '../../../graph/reactflow/handles'; + +const countArgs = (data: FunctionData | undefined) => { + if (data !== undefined) { + let count = 0; + + for (const name in data.args) { + if (data.args[name].visible) { + count++; + } + } + return count; + } + return 1; +}; + +/** + * Capitalize the first letter of a string. + * @param string This is the given string. + * @returns {string} This is the modified string. + */ +export const capitalizeFirstLetter = (string: string) => { + return string.charAt(0).toUpperCase() + string.slice(1); +}; + +/** + * Component to render a relation flow element + * @param { FlowElement<FunctionData>} param0 The data of a relation flow element. + */ +export default function RelationFlowElement({ data }: FunctionNode) { + const [read, setRead] = useState(true); + const theme = useTheme(); + + const numOfArgs = countArgs(data); + const height = numOfArgs * 20; + + const _onKeyDown = (event: any): void => { + if (event.key == 'Enter') setRead(true); + }; + + const getArgs = ( + styles: any, + data: FunctionData | undefined, + setRead: any + ) => { + let rows: JSX.Element[] = []; + + if (data != undefined) { + let index = 0; + + for (const name in data.args) { + const item = data.args[name]; + if (item.visible) { + rows.push( + <span className={styles.functionHandleFiller} key={name}> + <span className={styles.functionSpan}> + {capitalizeFirstLetter(name)} + </span> + <Handle + id={Handles.FunctionBase + name} + type="source" + position={Position.Top} + className={ + styles.functionHandle + + ' ' + + (false ? styles.handleConnectedFill : '') + } + style={{ + visibility: item.connectable ? 'inherit' : 'hidden', + }} + /> + {item.value !== undefined && ( + <input + className={styles.functionInput} + style={{ maxWidth: 50 }} + type="string" + placeholder={'?'} + value={item.value} + onChange={(e) => { + if (item.value != undefined) { + item.value = e.target.value; + //TODO restore SetElementsUseCase.updateFunctionCompleteness(data); + } + }} + onDoubleClick={() => { + setRead(false); + }} + onBlur={() => { + setRead(true); + }} + onKeyDown={_onKeyDown} + ></input> + )} + </span> + ); + index++; + } + } + } + + return rows; + }; + + const rows = getArgs(styles, data, setRead); + const entity = undefined; //TODO fix: data !== undefined ? data.entityName : undefined; + + return ( + <div> + <div + className={styles.function} + style={{ + minHeight: height, + background: theme.palette.custom.nodesBase[0], + borderTop: `4px solid ${theme.palette.custom.nodesBase[0]}`, + borderBottom: `6px solid ${theme.palette.custom.elements.function[0]}`, + }} + > + <div className={styles.functionWrapper}>{rows}</div> + </div> + <div + className={`${styles.entity} entityWrapper ${ + entity === undefined ? 'hidden' : '' + }`} + > + <Handle + id={Handles.ToRelation} + type="source" + position={Position.Bottom} + className={ + styles.entityHandleLeft + + ' ' + + (false ? styles.handleConnectedFill : '') + } + /> + <Handle + id={Handles.ToAttribute} + type="source" + position={Position.Bottom} + className={ + styles.entityHandleBottom + + ' ' + + (false ? styles.handleConnectedFill : '') + } + /> + <div className={styles.entityWrapper}> + <span className={styles.entitySpan}>{entity ? entity : ''}</span> + </div> + </div> + </div> + ); +} diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/index.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/index.ts new file mode 100644 index 000000000..865e20fa5 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/functionpill/index.ts @@ -0,0 +1 @@ +export * from './functionpill' \ No newline at end of file diff --git a/libs/shared/lib/ui/pills/customFlowPills/index.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/index.ts similarity index 100% rename from libs/shared/lib/ui/pills/customFlowPills/index.ts rename to libs/shared/lib/querybuilder/pills/customFlowPills/index.ts diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/modifierpill.module.scss b/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/modifierpill.module.scss new file mode 100644 index 000000000..2e259083f --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/modifierpill.module.scss @@ -0,0 +1,55 @@ +@import '../../querypills.module.scss'; + +.modifier { + color: '#181520'; + background-color: #d56a50; + border-radius: 5px; + font-family: monospace; + font-weight: bolder; + font-size: 11; + padding-top: 2; + padding-right: 5; + padding-bottom: 12; + padding-left: 5; +} +.modifierWrapper { + height: 6; + margin-left: 5; + margin-right: 5; + color: black; +} +.modifierInput { + float: right; + background-color: #ee917a; + border-radius: 2px; + padding-left: 2px; + padding-right: 2px; + display: flex; +} +.modifierSpan { + float: left; +} + +.matchModifierTypeSelect { + float: left; + background-color: rgba(255, 255, 255, 0.6); + border-radius: 2px; + text-align: center; + & select { + background: transparent; + border: none; + appearance: none; + font-family: monospace; + font-weight: bolder; + color: black; + font-size: 11; + } + & option { + font-family: monospace; + font-size: 11px; + } +} +.disable { + opacity: 1 !important; + pointer-events: none; +} diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/modifierpill.module.scss.d.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/modifierpill.module.scss.d.ts new file mode 100644 index 000000000..d22770e44 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/modifierpill.module.scss.d.ts @@ -0,0 +1,19 @@ +declare const classNames: { + readonly 'react-flow__node': 'react-flow__node'; + readonly selected: 'selected'; + readonly entityWrapper: 'entityWrapper'; + readonly hidden: 'hidden'; + readonly 'react-flow__edges': 'react-flow__edges'; + readonly 'react-flow__edge-default': 'react-flow__edge-default'; + readonly handleConnectedFill: 'handleConnectedFill'; + readonly handleConnectedBorderRight: 'handleConnectedBorderRight'; + readonly handleConnectedBorderLeft: 'handleConnectedBorderLeft'; + readonly handleFunction: 'handleFunction'; + readonly modifier: 'modifier'; + readonly modifierWrapper: 'modifierWrapper'; + readonly modifierInput: 'modifierInput'; + readonly modifierSpan: 'modifierSpan'; + readonly matchModifierTypeSelect: 'matchModifierTypeSelect'; + readonly disable: 'disable'; +}; +export = classNames; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/modifierpill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/modifierpill.tsx new file mode 100644 index 000000000..6041c775e --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/modifierpill.tsx @@ -0,0 +1,33 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/* istanbul ignore file */ +/* The comment above was added so the code coverage wouldn't count this file towards code coverage. + * We do not test components/renderfunctions/styling files. + * See testing plan for more details.*/ +import React from 'react'; +import { Handle, NodeProps, Position } from 'reactflow'; +import Select from './select-modifier'; +import styles from './modifierpill.module.scss'; +import { ModifierData, ModifierNode } from '../../../graph-reactflow/model'; + +/** + * Component to render an entity flow element + * @param param0 Data of the flow element. + */ +export default function ModifierFlowElement({ data }: ModifierNode) { + return ( + <div className={styles.modifier}> + <div className={styles.modifierWrapper}> + <span className={styles.modifierInput}> + <span className={styles.modifierSpan}> + <Select data={data} /> + </span> + </span> + </div> + </div> + ); +} diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/mopdifierpill.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/mopdifierpill.stories.tsx new file mode 100644 index 000000000..480fe054e --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/mopdifierpill.stories.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { Meta, StoryObj } from '@storybook/react'; +import ModifierPill from './modifierpill'; +import { configureStore } from '@reduxjs/toolkit'; +import { Provider } from 'react-redux'; +import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/theme'; + +import { + colorPaletteConfigSlice, + querybuilderSlice, + schemaSlice, +} from '@graphpolaris/shared/lib/data-access/store'; +import { ReactFlowProvider } from 'reactflow'; +import { + EntityData, + EntityNode, + FunctionData, + ModifierData, +} from '../../../graph-reactflow/model'; + +const Component: Meta<typeof ModifierPill> = { + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: 'Querybuilder/Pills/ModifierPill', + component: ModifierPill, + decorators: [ + (story) => ( + <Provider store={Mockstore}> + <GraphPolarisThemeProvider> + <ReactFlowProvider>{story()}</ReactFlowProvider> + </GraphPolarisThemeProvider> + </Provider> + ), + ], +}; + +export default Component; + +// A super-simple mock of a redux store +const Mockstore = configureStore({ + reducer: { + colorPaletteConfig: colorPaletteConfigSlice.reducer, + querybuilder: querybuilderSlice.reducer, + // schema: schemaSlice.reducer, + }, +}); + +// const Template = (args: any) => <EntityRFPill {...args} />; + +export const Default: StoryObj<{ data: ModifierData }> = { + args: { + data: { + type: 'SUM', + }, + }, +}; + +// Default.decorators = [ +// (story) => ( +// <Provider store={Mockstore}> +// <GraphPolarisThemeProvider>{story()}</GraphPolarisThemeProvider> +// </Provider> +// ), +// ]; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/select-modifier.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/select-modifier.tsx new file mode 100644 index 000000000..86ebf29a7 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/modifierpill/select-modifier.tsx @@ -0,0 +1,88 @@ +/** + * This program has been developed by students from the bachelor Computer Science at + * Utrecht University within the Software Project course. + * © Copyright Utrecht University (Department of Information and Computing Sciences) + */ + +/* istanbul ignore file */ +/* The comment above was added so the code coverage wouldn't count this file towards code coverage. + * We do not test components/renderfunctions/styling files. + * See testing plan for more details.*/ +import React, { useState } from 'react'; +import { NodeProps } from 'reactflow'; +import styles from './modifierpill.module.scss'; +import { ModifierData } from '../../../graph-reactflow/model'; + +// Create style constant to prevent rereaction of styles + +/** + * The flow element for the modifier. + * @param param0 The data of the modifier flow element. + */ +export default function SelectModifier({ data }: { data: ModifierData }) { + const [disable, setDisable] = useState(true); + const [disClass, setDisClass] = useState<string>(styles.disable); + + /** + * Calculate the width of the select element based on the displayed value. + * @param str Input string. + * @returns String containg the length in css format. + */ + const calcSelectWidth = (str: string): string => { + if (str == '') return 1.5 + 'ch'; + return str.length + 1.5 + 'ch'; + }; + + /** Disable the select field */ + const disableSelect = (): void => { + setDisable(true); + setDisClass(styles.disable); + }; + + /** Enable the select field */ + const enableSelect = (): void => { + setDisable(false); + setDisClass(''); + }; + + /** + * Constant switch to append the right options for the select element based on the data.type. + * @returns {JSX.Element} Option list using React.Fragment as parent element. + */ + const list = (): JSX.Element => { + return ( + <React.Fragment> + <option color="black" value="COUNT"> + COUNT + </option> + <option value="SUM">SUM</option> + <option value="MIN">MIN</option> + <option value="MAX">MAX</option> + </React.Fragment> + ); + }; + + return ( + <div + className={styles.matchModifierTypeSelect} + onBlur={disableSelect} + onDoubleClick={enableSelect} + > + <select + style={{ + color: disable ? 'black' : 'black', + maxWidth: calcSelectWidth('COUNT'), + }} + name="operators" + className={disClass} + disabled={disable} + onChange={(e) => { + data.type = e.target.value; + e.target.style.maxWidth = calcSelectWidth(e.target.value); + }} + > + {list()} + </select> + </div> + ); +} diff --git a/libs/shared/lib/ui/pills/customFlowPills/relationpill/index.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/index.ts similarity index 100% rename from libs/shared/lib/ui/pills/customFlowPills/relationpill/index.ts rename to libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/index.ts diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full.stories.tsx new file mode 100644 index 000000000..b01ffd8f3 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relation-full.stories.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { + colorPaletteConfigSlice, + querybuilderSlice, + setQuerybuilderNodes, +} from '@graphpolaris/shared/lib/data-access/store'; +import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/theme'; +import { configureStore } from '@reduxjs/toolkit'; +import { Meta } from '@storybook/react'; +import { Provider } from 'react-redux'; +import { MultiGraph } from 'graphology'; +import { QueryBuilder } from '../../../panel'; +import { QueryGraph } from '../../../graph/graphology/model'; +import { circular } from 'graphology-layout'; + +const Component: Meta<typeof QueryBuilder> = { + component: QueryBuilder, + title: 'Querybuilder/Pills/relationPill', + decorators: [ + (story) => ( + <Provider store={mockStore}> + <GraphPolarisThemeProvider>{story()}</GraphPolarisThemeProvider> + </Provider> + ), + ], +}; + +// Mock palette store +const mockStore = configureStore({ + reducer: { + colorPaletteConfig: colorPaletteConfigSlice.reducer, + querybuilder: querybuilderSlice.reducer, + }, +}); +const graph: QueryGraph = new MultiGraph(); +graph.addPill2Graphology( + { + type: 'relation', + x: 140, + y: 140, + name: 'Relation Pill', + depth: { min: 0, max: 1 }, + fadeIn: false, + }, + '2' +); +console.log(graph.export()); + +mockStore.dispatch(setQuerybuilderNodes(graph.export())); + +export const Flow = { + args: {}, +}; + +export default Component; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill copy.txt b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill copy.txt new file mode 100644 index 000000000..805e3f3fc --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill copy.txt @@ -0,0 +1,242 @@ +import React, { memo, useRef, useState } from 'react'; + +import { handles } from '@graphpolaris/shared/lib/querybuilder/usecases'; +import { useTheme } from '@mui/material'; +import { Handle, Position } from 'reactflow'; +import cn from 'classnames'; + +import styles from './relationpill.module.scss'; +import { Handles } from '../../../structures/Handles'; +import { RelationNode } from '../../../structures/Nodes'; + +// export type RelationRFPillProps = { +// data: { +// name: string; +// suggestedForConnection: any; +// isFromEntityConnected?: boolean; +// isToEntityConnected?: boolean; +// }; +// }; + +/** + * Component to render a relation flow element + * @param { FlowElement<RelationData>} param0 The data of a relation flow element. + */ +export const RelationPill = memo(({ data }: RelationNode) => { + // export default function RelationRFPill({ data }: { data: any }) { + const theme = useTheme(); + // console.log('RelationRFPill', data); + + const minRef = useRef<HTMLInputElement>(null); + const maxRef = useRef<HTMLInputElement>(null); + + const [readOnlyMin, setReadOnlyMin] = useState(true); + const [readOnlyMax, setReadOnlyMax] = useState(true); + + const onDepthChanged = (depth: string) => { + // Don't allow depth above 99 + const limit = 99; + if (data?.depth != undefined) { + data.depth.min = data.depth.min >= limit ? limit : data.depth.min; + data.depth.max = data.depth.max >= limit ? limit : data.depth.max; + + // Check for for valid depth: min <= max + if (depth == 'min') { + if (data.depth.min > data.depth.max) data.depth.max = data.depth.min; + setReadOnlyMin(true); + } else if (depth == 'max') { + if (data.depth.max < data.depth.min) data.depth.min = data.depth.max; + setReadOnlyMax(true); + } + + // Set to the correct width + if (maxRef.current) + maxRef.current.style.maxWidth = calcWidth(data.depth.max); + if (minRef.current) + minRef.current.style.maxWidth = calcWidth(data.depth.min); + } + }; + + const isNumber = (x: string) => { + { + if (typeof x != 'string') return false; + return !Number.isNaN(x) && !Number.isNaN(parseFloat(x)); + } + }; + + const calcWidth = (data: number) => { + return data.toString().length + 0.5 + 'ch'; + }; + + return ( + <div + className={styles.relation} + style={{ + background: theme.palette.custom.nodesBase[0], + borderTop: `4px solid ${theme.palette.custom.nodesBase[0]}`, + borderBottom: `6px solid ${theme.palette.custom.elements.relationBase[0]}`, + }} + > + <div className={styles.relationWrapper}> + <div + className={[ + styles.relationNodeTriangleGeneral, + styles.relationNodeTriangleLeft, + ].join(' ')} + style={{ borderRightColor: theme.palette.custom.nodesBase[0] }} + > + <span className={styles.relationHandleFiller}> + <Handle + id={Handles.RelationLeft} + type="target" + position={Position.Left} + className={ + styles.relationHandleLeft + + ' ' + + (false ? styles.handleConnectedBorderLeft : '') + } + /> + </span> + </div> + <div + className={[ + styles.relationNodeTriangleGeneral, + styles.relationNodeSmallTriangleLeft, + ].join(' ')} + style={{ + borderRightColor: theme.palette.custom.elements.relationBase[0], + }} + ></div> + <div + className={[ + styles.relationNodeTriangleGeneral, + styles.relationNodeTriangleRight, + ].join(' ')} + style={{ borderLeftColor: theme.palette.custom.nodesBase[0] }} + > + <span className={styles.relationHandleFiller}> + <Handle + id={Handles.RelationRight} + type="target" + position={Position.Right} + className={ + styles.relationHandleRight + + ' ' + + (false ? styles.handleConnectedBorderRight : '') + } + /> + </span> + </div> + <div + className={[ + styles.relationNodeTriangleGeneral, + styles.relationNodeSmallTriangleRight, + ].join(' ')} + style={{ + borderLeftColor: theme.palette.custom.elements.relationBase[0], + }} + ></div> + + <span className={styles.relationHandleFiller}> + <Handle + id={Handles.ToAttributeHandle} + type="target" + position={Position.Bottom} + className={ + styles.relationHandleBottom + + ' ' + + (false ? styles.handleConnectedFill : '') + } + /> + </span> + <div className={styles.relationDataWrapper}> + <span className={styles.relationSpan}>{data?.name}</span> + <span className={styles.relationInputHolder}> + <span>[</span> + <input + className={ + styles.relationInput + + ' ' + + (readOnlyMin ? styles.relationReadonly : '') + } + ref={minRef} + type="string" + min={0} + readOnly={readOnlyMin} + placeholder={'?'} + value={data?.depth.min} + onChange={(e) => { + if (data != undefined) { + data.depth.min = isNumber(e.target.value) + ? parseInt(e.target.value) + : 0; + e.target.style.maxWidth = calcWidth(data.depth.min); + } + }} + onDoubleClick={() => { + setReadOnlyMin(false); + }} + onBlur={(e) => { + onDepthChanged('min'); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + onDepthChanged('min'); + } + }} + ></input> + <span>..</span> + <input + className={ + styles.relationInput + + ' ' + + (readOnlyMax ? styles.relationReadonly : '') + } + ref={maxRef} + type="string" + min={0} + readOnly={readOnlyMax} + placeholder={'?'} + value={data?.depth.max} + onChange={(e) => { + if (data != undefined) { + data.depth.max = isNumber(e.target.value) + ? parseInt(e.target.value) + : 0; + e.target.style.maxWidth = calcWidth(data.depth.max); + } + }} + onDoubleClick={() => { + setReadOnlyMax(false); + }} + onBlur={(e) => { + onDepthChanged('max'); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + onDepthChanged('max'); + } + }} + ></input> + <span>]</span> + </span> + </div> + <Handle + id={Handles.ReceiveFunction} + type="target" + position={Position.Bottom} + className={ + styles.relationHandleFunction + + ' ' + + styles.handleFunction + + ' ' + + (false ? styles.handleConnectedFill : '') + } + /> + </div> + </div> + ); +}); + +RelationPill.displayName = 'RelationPill'; +export default RelationPill; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module copy.scss b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module copy.scss new file mode 100644 index 000000000..e7dd844ff --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module copy.scss @@ -0,0 +1,304 @@ +@import '../../querypills.module.scss'; + +.relation { + display: flex; + text-align: center; + font-family: monospace; + font-weight: bold; + font-size: 10px; + background-color: transparent; +} + +.highlighted { + box-shadow: black 0 0 2px; +} + +.contentWrapper { + display: flex; + align-items: center; + + .handleLeft { + position: relative; + z-index: 3; + + top: 25%; + border: 0px; + border-radius: 0px; + + background: transparent; + transform-origin: center; + + width: 0; + height: 0; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-right: rgba(255, 255, 255, 0.7) 6px solid; + + &::after { + content: ''; + display: block; + position: absolute; + width: 0; + height: 0; + border-top: 7px solid transparent; + border-bottom: 7px solid transparent; + border-right: rgba(0, 0, 0, 0.1) 8px solid; + top: -7px; + right: -7px; + } + } + .highlighted { + z-index: -1; + box-shadow: 0 0 2px 1px gray; + } + + .content { + margin: 0 2ch; + padding: 3px 0; + max-width: 20ch; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + // pointer-events: none; + } + + .handleRight { + position: relative; + top: 25%; + border: 0px; + border-radius: 0px; + + background: transparent; + transform-origin: center; + + width: 0; + height: 0; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: rgba(255, 255, 255, 0.7) 6px solid; + + &::after { + content: ''; + display: block; + position: absolute; + width: 0; + height: 0; + border-top: 7px solid transparent; + border-bottom: 7px solid transparent; + border-left: rgba(0, 0, 0, 0.1) 8px solid; + top: -7px; + left: -7px; + } + } +} + +$height: 10px; +.arrowLeft { + z-index: 2; + width: 0; + height: 0; + border-top: $height solid transparent; + border-bottom: $height solid transparent; + transform: scale(1.028) translate(0.3px 0px); + + border-right: $height solid; +} + +.arrowRight { + width: 0; + height: 0; + border-top: $height solid transparent; + border-bottom: $height solid transparent; + transform: scale(1.02) translate(-0.3px 0px); + + border-left: $height solid; +} + +// Relation element +.relation { + height: 36; + min-width: 240px; + display: flex; + text-align: center; + color: black; + line-height: 20px; + font-family: monospace; + font-weight: bold; + font-size: 11px; +} + +.relationWrapper { + display: inherit; + align-items: center; + justify-content: space-between; +} + +.relationNodeTriangleGeneral { + position: absolute; + width: 0; + height: 0; + margin: auto 0; + border-style: solid; + border-color: transparent; +} + +.relationNodeTriangleLeft { + transform: translateX(-100%); + top: 0px; + border-width: 18px 24px 18px 0; +} + +.relationNodeSmallTriangleLeft { + transform: translateX(-100%); + top: 30px; + border-width: 0 8px 6px 0; +} + +.relationNodeTriangleRight { + right: -24px; + top: 0px; + border-width: 18px 0 18px 24px; +} + +.relationNodeSmallTriangleRight { + right: -8px; + top: 30px; + border-width: 0 0 6px 8px; +} + +.relationHandleFiller { + display: block; +} + +.relationHandleLeft { + position: absolute; + top: 50%; + margin-left: 15px; + border-style: solid; + border-width: 8px 12px 8px 0; + border-radius: 0px; + left: unset; + border-color: transparent rgba(0, 0, 0, 0.3) transparent transparent; + background: transparent; + &::before { + content: ''; + border-style: solid; + border-width: 6px 8px 6px 0; + border-color: transparent rgba(255, 255, 255, 0.6) transparent transparent; + background: transparent; + z-index: -1; + display: inline-block; + position: absolute; + top: -0.5em; + left: 0.25em; + } +} + +.relationHandleRight { + position: absolute; + margin-right: 19px; + border-style: solid; + border-width: 8px 0 8px 12px; + border-radius: 0px; + left: unset; + border-color: transparent transparent transparent rgba(0, 0, 0, 0.3); + background: transparent; + &::before { + content: ''; + border-style: solid; + border-width: 6px 0 6px 8px; + border-color: transparent transparent transparent rgba(255, 255, 255, 0.6); + background: transparent; + z-index: -1; + display: inline-block; + position: absolute; + top: -0.5em; + right: 0.25em; + } +} + +.relationHandleBottom { + border: 0; + border-radius: 0; + width: 8; + height: 8; + left: unset; + margin-bottom: 18; + margin-left: 40; + background: rgba(0, 0, 0, 0.3); + transform: rotate(-45deg); + transform-origin: center; + margin: 5px; + &::before { + content: ''; + width: 6; + height: 6; + left: 1; + bottom: 1; + border: 0; + border-radius: 0; + background: rgba(255, 255, 255, 0.6); + z-index: -1; + display: inline-block; + position: fixed; + } +} + +.relationDataWrapper { + margin-left: 80; +} + +.relationSpan { + float: left; + margin-left: 5; +} + +.relationInputHolder { + display: flex; + float: right; + margin-right: 20px; + margin-top: 4px; + margin-left: 5px; + max-width: 80px; + background-color: rgba(255, 255, 255, 0.6); + border-radius: 2px; + align-items: center; + max-height: 12px; +} + +.relationInput { + z-index: 1; + cursor: text; + min-width: 0px; + max-width: 1.5ch; + border: none; + background: transparent; + text-align: center; + font-family: monospace; + font-weight: bold; + font-size: 11px; + color: #181520; + user-select: none; + font-style: italic; + &:focus { + outline: none; + user-select: none; + } + &::placeholder { + outline: none; + user-select: none; + font-style: italic; + } +} + +.relationReadonly { + cursor: grab !important; + color: #181520 !important; + user-select: none; + font-style: normal !important; +} + +.relationHandleFunction { + margin-left: 20; + margin-bottom: 18px !important; +} diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss new file mode 100644 index 000000000..1f8d81b42 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss @@ -0,0 +1,317 @@ +@import '../../querypills.module.scss'; + +// .relation { +// display: flex; +// text-align: center; +// font-family: monospace; +// font-weight: bold; +// font-size: 10px; +// background-color: blue; +// } + +.highlighted { + box-shadow: black 0 0 2px; +} + +.contentWrapper { + display: flex; + align-items: center; + + .handleLeft { + position: relative; + z-index: 3; + + top: 25%; + border: 0px; + border-radius: 0px; + + background: transparent; + transform-origin: center; + + width: 0; + height: 0; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-right: rgba(255, 255, 255, 0.7) 6px solid; + + &::after { + content: ''; + display: block; + position: absolute; + width: 0; + height: 0; + border-top: 7px solid transparent; + border-bottom: 7px solid transparent; + border-right: rgba(0, 0, 0, 0.1) 8px solid; + top: -7px; + right: -7px; + } + } + .highlighted { + z-index: -1; + box-shadow: 0 0 2px 1px gray; + } + + .content { + margin: 0 2ch; + padding: 3px 0; + max-width: 20ch; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + // pointer-events: none; + } + + .handleRight { + position: relative; + top: 25%; + border: 0px; + border-radius: 0px; + + background: transparent; + transform-origin: center; + + width: 0; + height: 0; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: rgba(255, 255, 255, 0.7) 6px solid; + + &::after { + content: ''; + display: block; + position: absolute; + width: 0; + height: 0; + border-top: 7px solid transparent; + border-bottom: 7px solid transparent; + border-left: rgba(0, 0, 0, 0.1) 8px solid; + top: -7px; + left: -7px; + } + } +} + +$height: 10px; +.arrowLeft { + z-index: 2; + width: 0; + height: 0; + border-top: $height solid transparent; + border-bottom: $height solid transparent; + transform: scale(1.028) translate(0.3px 0px); + + border-right: $height solid; +} + +.arrowRight { + width: 0; + height: 0; + border-top: $height solid transparent; + border-bottom: $height solid transparent; + transform: scale(1.02) translate(-0.3px 0px); + + border-left: $height solid; +} + +$width: 325; +// Relation element +.relation { + position: relative; + display: flex; + width: $width + px; + background: transparent; + text-align: center; + text-decoration: none; + color: black; + box-sizing: border-box; + line-height: 20px; + font-family: monospace; + font-weight: bold; + font-size: 11px; +} + +.rightArrow { + border-style: solid; + border-width: $height 0 $height $height * 1.5; + border-color: transparent transparent transparent black; + margin-top: 0.5; + background: transparent; + display: inline-block; + position: absolute; + right: -$height * 1.5; + transform: translate(-0.2px, 0px) scale(1.02); +} +.leftArrow { + border-style: solid; + border-width: $height $height * 1.5 $height 0; + border-color: transparent black transparent transparent; + margin-top: 0.5; + background: transparent; + display: inline-block; + position: absolute; + z-index: -1; + left: -$height * 1.5; + transform: translate(0.2px, 0px) scale(1.02); +} +.relationTop { + position: absolute; + content: ''; + width: inherit; + left: 0px; + height: $height + px; + z-index: -1; + background-color: #1fa2a2; + transform: perspective(15px) rotateX(5deg); + border-bottom: none; +} +.relationBottom { + position: absolute; + content: ''; + width: inherit; + left: 0px; + height: $height + px; + z-index: -1; + background-color: #1fa2a2; + top: $height + px; + transform: perspective(15px) rotateX(-5deg); + border-top: none; +} +.relationWrapper { + display: inherit; + width: inherit; + align-items: center; + justify-content: space-between; +} +.relationHandleFiller { + flex: 1 1 0; +} + +.relationHandleLeft { + // FIRST ONE + position: absolute !important; + border-style: solid !important; + border-width: 6px 10px 6px 0 !important; + border-radius: 0px !important; + border-color: transparent rgba(0, 0, 0, 0.3) transparent transparent !important; + background: transparent !important; + min-height: 0 !important; + min-width: 0 !important; + width: 0 !important; + height: 0px !important; + + &::before { + content: ''; + border-style: solid; + border-width: 4px 7.5px 4px 0; + border-color: transparent rgba(255, 255, 255, 0.6) transparent transparent; + margin-top: 0.5; + background: transparent; + z-index: -1; + display: inline-block; + position: absolute; + top: -4px; + left: 2px; + } +} + +.relationHandleAttribute { + // SECOND ONE + border-radius: 1px !important; + left: 22.5px !important; + background: rgba(255, 255, 255, 0.6) !important; + transform: rotate(45deg) translate(-68%, 0) scale(0.9) !important; + border-color: rgba(22, 110, 110, 1) !important; + border-width: 1px !important; + transform-origin: center, center; +} + +.relationHandleFunction { + // THIRD ONE + left: 39px !important; + background: rgba(255, 255, 255, 0.6) !important; + border-color: rgba(22, 110, 110, 1) !important; + border-width: 1px !important; + transform-origin: center, center; +} + +.relationHandleRight { + // LAST ONE + width: 0 !important; + height: 0 !important; + position: absolute !important; + + border-radius: 1px !important; + border-width: 6px 0px 6px 10px !important; + border-color: transparent transparent transparent rgba(0, 0, 0, 0.3) !important; + background: transparent !important; + min-height: 0 !important; + min-width: 0 !important; + + &::before { + content: ''; + border-style: solid; + border-width: 4px 0 4px 7.5px; + border-color: transparent transparent transparent rgba(255, 255, 255, 0.6); + margin-top: 0.5; + background: transparent; + z-index: -1; + display: inline-block; + position: absolute; + top: -4px; + right: 2px; + } +} + +.relationInputHolder { + display: flex; + float: right; + margin-right: 20px; + margin-top: 4px; + margin-left: 5px; + max-width: 80px; + background-color: rgba(255, 255, 255, 0.6); + border-radius: 2px; + align-items: center; + max-height: 12px; +} +.relationInput { + z-index: 1; + cursor: text; + min-width: 0px; + max-width: 1.5ch; + border: none; + background: transparent; + text-align: center; + font-family: monospace; + font-weight: bold; + font-size: 11px; + color: #181520; + user-select: none; + font-style: italic; + &:focus { + outline: none; + user-select: none; + } + &::placeholder { + outline: none; + user-select: none; + font-style: italic; + } +} +.relationReadonly { + cursor: grab !important; + color: #181520 !important; + user-select: none; + font-style: normal !important; +} +.relationSpan { + float: left; + margin-left: 5; +} + +.relationDataWrapper { + display: flex; + width: 100%; + justify-content: center; +} diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts new file mode 100644 index 000000000..a0a450e53 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts @@ -0,0 +1,36 @@ +declare const classNames: { + readonly 'react-flow__node': 'react-flow__node'; + readonly selected: 'selected'; + readonly entityWrapper: 'entityWrapper'; + readonly hidden: 'hidden'; + readonly 'react-flow__edges': 'react-flow__edges'; + readonly 'react-flow__edge-default': 'react-flow__edge-default'; + readonly handleConnectedFill: 'handleConnectedFill'; + readonly handleConnectedBorderRight: 'handleConnectedBorderRight'; + readonly handleConnectedBorderLeft: 'handleConnectedBorderLeft'; + readonly handleFunction: 'handleFunction'; + readonly highlighted: 'highlighted'; + readonly contentWrapper: 'contentWrapper'; + readonly handleLeft: 'handleLeft'; + readonly content: 'content'; + readonly handleRight: 'handleRight'; + readonly arrowLeft: 'arrowLeft'; + readonly arrowRight: 'arrowRight'; + readonly relation: 'relation'; + readonly rightArrow: 'rightArrow'; + readonly leftArrow: 'leftArrow'; + readonly relationTop: 'relationTop'; + readonly relationBottom: 'relationBottom'; + readonly relationWrapper: 'relationWrapper'; + readonly relationHandleFiller: 'relationHandleFiller'; + readonly relationHandleLeft: 'relationHandleLeft'; + readonly relationHandleAttribute: 'relationHandleAttribute'; + readonly relationHandleFunction: 'relationHandleFunction'; + readonly relationHandleRight: 'relationHandleRight'; + readonly relationInputHolder: 'relationInputHolder'; + readonly relationInput: 'relationInput'; + readonly relationReadonly: 'relationReadonly'; + readonly relationSpan: 'relationSpan'; + readonly relationDataWrapper: 'relationDataWrapper'; +}; +export = classNames; diff --git a/libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.stories.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.stories.tsx similarity index 76% rename from libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.stories.tsx rename to libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.stories.tsx index 735c0acb4..35171325c 100644 --- a/libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.stories.tsx +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Meta } from '@storybook/react'; -import RelationRFPill from './relationpill'; +import { Meta, StoryObj } from '@storybook/react'; +import RelationPill from './relationpill'; import { configureStore } from '@reduxjs/toolkit'; import { Provider } from 'react-redux'; import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/theme'; @@ -11,14 +11,15 @@ import { schemaSlice, } from '@graphpolaris/shared/lib/data-access/store'; import { ReactFlowProvider } from 'reactflow'; +import { RelationData } from '../../../graph/reactflow/model'; -const Component: Meta<typeof RelationRFPill> = { +const Component: Meta<typeof RelationPill> = { /* 👇 The title prop is optional. * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading * to learn how to generate automatic titles */ - title: 'Components/Pills/RelationRFPill', - component: RelationRFPill, + title: 'Querybuilder/Pills/RelationPill', + component: RelationPill, decorators: [ (story) => ( <Provider store={Mockstore}> @@ -43,10 +44,13 @@ const Mockstore = configureStore({ // const Template = (args: any) => <EntityRFPill {...args} />; -export const Default = { +export const Default: StoryObj<{ data: RelationData }> = { args: { data: { name: 'TestEntity', + collection: 'test', + depth: { min: 0, max: 1 }, + fadeIn: false, }, }, }; diff --git a/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx new file mode 100644 index 000000000..b1de1902c --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/customFlowPills/relationpill/relationpill.tsx @@ -0,0 +1,220 @@ +import React, { memo, useRef, useState } from 'react'; + +import { useTheme } from '@mui/material'; +import { Handle, Position } from 'reactflow'; + +import styles from './relationpill.module.scss'; +import { RelationNode } from '../../../graph/reactflow/model'; +import { Handles } from '../../../graph/reactflow/handles'; + +// export type RelationRFPillProps = { +// data: { +// name: string; +// suggestedForConnection: any; +// isFromEntityConnected?: boolean; +// isToEntityConnected?: boolean; +// }; +// }; + +/** + * Component to render a relation flow element + * @param { FlowElement<RelationData>} param0 The data of a relation flow element. + */ +export const RelationPill = memo(({ data }: RelationNode) => { + // export default function RelationRFPill({ data }: { data: any }) { + const theme = useTheme(); + // console.log('RelationRFPill', data); + + const minRef = useRef<HTMLInputElement>(null); + const maxRef = useRef<HTMLInputElement>(null); + + const [readOnlyMin, setReadOnlyMin] = useState(true); + const [readOnlyMax, setReadOnlyMax] = useState(true); + + const onDepthChanged = (depth: string) => { + // Don't allow depth above 99 + const limit = 99; + if (data != undefined) { + data.depth.min = data.depth.min >= limit ? limit : data.depth.min; + data.depth.max = data.depth.max >= limit ? limit : data.depth.max; + + // Check for for valid depth: min <= max + if (depth == 'min') { + if (data.depth.min > data.depth.max) data.depth.max = data.depth.min; + setReadOnlyMin(true); + } else if (depth == 'max') { + if (data.depth.max < data.depth.min) data.depth.min = data.depth.max; + setReadOnlyMax(true); + } + + // Set to the correct width + if (maxRef.current) + maxRef.current.style.maxWidth = calcWidth(data.depth.max); + if (minRef.current) + minRef.current.style.maxWidth = calcWidth(data.depth.min); + } + }; + + const isNumber = (x: string) => { + { + if (typeof x != 'string') return false; + return !Number.isNaN(x) && !Number.isNaN(parseFloat(x)); + } + }; + + const calcWidth = (data: number) => { + return data.toString().length + 0.5 + 'ch'; + }; + + return ( + <div + className={styles.relation} + style={{ backgroundColor: theme.palette.custom.elements.relation[0] }} + > + <div + className={styles.rightArrow} + style={{ borderLeftColor: theme.palette.custom.elements.relation[0] }} + ></div> + <div + className={styles.leftArrow} + style={{ borderRightColor: theme.palette.custom.elements.relation[0] }} + ></div> + {/* <span + className={styles.relationTop} + style={{ backgroundColor: theme.palette.custom.elements.relation[0] }} + ></span> + <span + className={styles.relationBottom} + style={{ backgroundColor: theme.palette.custom.elements.relation[0] }} + ></span> */} + <div className={styles.relationWrapper}> + <span + className={styles.relationHandleFiller} + // style={{ transform: 'translate(-100px,0)' }} + > + <Handle + id={Handles.RelationLeft} + type="target" + position={Position.Left} + className={ + styles.relationHandleLeft + + ' ' + + (false ? styles.handleConnectedBorderLeft : '') + } + /> + </span> + <span className={styles.relationHandleFiller}> + <Handle + id={Handles.ToAttribute} + type="target" + position={Position.Left} + className={ + styles.relationHandleAttribute + + ' ' + + (false ? styles.handleConnectedFill : '') + } + /> + </span> + <span className={styles.relationHandleFiller}> + <Handle + id={Handles.ReceiveFunction} + type="target" + position={Position.Left} + className={ + styles.relationHandleFunction + + ' ' + + (false ? styles.handleConnectedFill : '') + } + /> + </span> + <div className={styles.relationDataWrapper}> + <span className={styles.relationSpan}>{data?.name}</span> + <span className={styles.relationInputHolder}> + <span>[</span> + <input + className={ + styles.relationInput + + ' ' + + (readOnlyMin ? styles.relationReadonly : '') + } + ref={minRef} + type="string" + min={0} + readOnly={readOnlyMin} + placeholder={'?'} + value={data?.depth.min} + onChange={(e) => { + if (data != undefined) { + data.depth.min = isNumber(e.target.value) + ? parseInt(e.target.value) + : 0; + e.target.style.maxWidth = calcWidth(data.depth.min); + } + }} + onDoubleClick={() => { + setReadOnlyMin(false); + }} + onBlur={(e) => { + onDepthChanged('min'); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + onDepthChanged('min'); + } + }} + ></input> + <span>..</span> + <input + className={ + styles.relationInput + + ' ' + + (readOnlyMax ? styles.relationReadonly : '') + } + ref={maxRef} + type="string" + min={0} + readOnly={readOnlyMax} + placeholder={'?'} + value={data?.depth.max} + onChange={(e) => { + if (data != undefined) { + data.depth.max = isNumber(e.target.value) + ? parseInt(e.target.value) + : 0; + e.target.style.maxWidth = calcWidth(data.depth.max); + } + }} + onDoubleClick={() => { + setReadOnlyMax(false); + }} + onBlur={(e) => { + onDepthChanged('max'); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + onDepthChanged('max'); + } + }} + ></input> + <span>]</span> + </span> + </div> + <span className={styles.relationHandleFiller}> + <Handle + id={Handles.RelationRight} + type="target" + position={Position.Right} + className={ + styles.relationHandleRight + + ' ' + + (false ? styles.handleConnectedBorderRight : '') + } + /> + </span> + </div> + </div> + ); +}); + +RelationPill.displayName = 'RelationPill'; +export default RelationPill; diff --git a/libs/shared/lib/querybuilder/usecases/dragging/dragAttribute.ts b/libs/shared/lib/querybuilder/pills/dragging/dragAttribute.ts similarity index 100% rename from libs/shared/lib/querybuilder/usecases/dragging/dragAttribute.ts rename to libs/shared/lib/querybuilder/pills/dragging/dragAttribute.ts diff --git a/libs/shared/lib/querybuilder/usecases/dragging/dragAttributesAlong.ts b/libs/shared/lib/querybuilder/pills/dragging/dragAttributesAlong.ts similarity index 100% rename from libs/shared/lib/querybuilder/usecases/dragging/dragAttributesAlong.ts rename to libs/shared/lib/querybuilder/pills/dragging/dragAttributesAlong.ts diff --git a/libs/shared/lib/querybuilder/usecases/dragging/dragEntity.ts b/libs/shared/lib/querybuilder/pills/dragging/dragEntity.ts similarity index 100% rename from libs/shared/lib/querybuilder/usecases/dragging/dragEntity.ts rename to libs/shared/lib/querybuilder/pills/dragging/dragEntity.ts diff --git a/libs/shared/lib/querybuilder/usecases/dragging/dragPill.ts b/libs/shared/lib/querybuilder/pills/dragging/dragPill.ts similarity index 95% rename from libs/shared/lib/querybuilder/usecases/dragging/dragPill.ts rename to libs/shared/lib/querybuilder/pills/dragging/dragPill.ts index 1a7ae7dc9..728c5f0e3 100644 --- a/libs/shared/lib/querybuilder/usecases/dragging/dragPill.ts +++ b/libs/shared/lib/querybuilder/pills/dragging/dragPill.ts @@ -37,9 +37,9 @@ export function dragPillStarted(id: string, nodes: MultiGraph) { * @param nodes The graphology query builder nodes object * @param dx Delta x * @param dy Delta y - * @param position The already updated positiong (dx dy are already applied) + * @param position The already updated positioning (dx dy are already applied) */ -export function dragPill( +export function movePillTo( id: string, nodes: MultiGraph, dx: number, diff --git a/libs/shared/lib/querybuilder/usecases/dragging/dragRelation.ts b/libs/shared/lib/querybuilder/pills/dragging/dragRelation.ts similarity index 100% rename from libs/shared/lib/querybuilder/usecases/dragging/dragRelation.ts rename to libs/shared/lib/querybuilder/pills/dragging/dragRelation.ts diff --git a/libs/shared/lib/querybuilder/usecases/dragging/getClosestPill.ts b/libs/shared/lib/querybuilder/pills/dragging/getClosestPill.ts similarity index 100% rename from libs/shared/lib/querybuilder/usecases/dragging/getClosestPill.ts rename to libs/shared/lib/querybuilder/pills/dragging/getClosestPill.ts diff --git a/libs/shared/lib/ui/pills/index.ts b/libs/shared/lib/querybuilder/pills/index.ts similarity index 66% rename from libs/shared/lib/ui/pills/index.ts rename to libs/shared/lib/querybuilder/pills/index.ts index 5f784740f..b37ce567e 100644 --- a/libs/shared/lib/ui/pills/index.ts +++ b/libs/shared/lib/querybuilder/pills/index.ts @@ -1,3 +1,2 @@ -export * from './shared-ui-pills'; export * from './customFlowLines'; export * from './customFlowPills'; diff --git a/libs/shared/lib/querybuilder/pills/querypills.module.scss b/libs/shared/lib/querybuilder/pills/querypills.module.scss new file mode 100644 index 000000000..2cb3fedf0 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/querypills.module.scss @@ -0,0 +1,63 @@ +.react-flow__node { + &.selected { + border: #000 solid 1px; + } +} + +.entityWrapper { + &.hidden { + display: none; + } +} + +.react-flow__edges { + zindex: '3'; +} +.react-flow__nodes { +} +.react-flow__pane { +} +.react-flow__edge-default .selected { + stroke: 'gray !important'; +} + +// This is used to override the previous color of the handle, for that to work it has to be on the bottom of the file +.handleConnectedFill { + &::before { + background: #181520; + } +} +.handleConnectedBorderRight { + &::before { + border-color: transparent transparent transparent #181520; + } +} +.handleConnectedBorderLeft { + &::before { + border-color: transparent #181520 transparent transparent; + } +} + +// General style +.handleFunction { + border: 0 !important; + border-radius: 50% !important; + left: 40 !important; + width: 6 !important; + height: 6 !important; + background: rgba(0, 0, 0, 0.3) !important; + margin-bottom: 16 !important; + &::before { + content: '' !important; + width: 4 !important; + height: 4 !important; + left: 1 !important; + bottom: 1 !important; + border: 0 !important; + border-radius: 50% !important; + background: rgba(255, 255, 255, 0.6) !important; + z-index: -1 !important; + display: inline-block !important; + position: fixed !important; + } +} diff --git a/libs/shared/lib/querybuilder/pills/querypills.module.scss.d.ts b/libs/shared/lib/querybuilder/pills/querypills.module.scss.d.ts new file mode 100644 index 000000000..a040b2068 --- /dev/null +++ b/libs/shared/lib/querybuilder/pills/querypills.module.scss.d.ts @@ -0,0 +1,13 @@ +declare const classNames: { + readonly 'react-flow__node': 'react-flow__node'; + readonly selected: 'selected'; + readonly entityWrapper: 'entityWrapper'; + readonly hidden: 'hidden'; + readonly 'react-flow__edges': 'react-flow__edges'; + readonly 'react-flow__edge-default': 'react-flow__edge-default'; + readonly handleConnectedFill: 'handleConnectedFill'; + readonly handleConnectedBorderRight: 'handleConnectedBorderRight'; + readonly handleConnectedBorderLeft: 'handleConnectedBorderLeft'; + readonly handleFunction: 'handleFunction'; +}; +export = classNames; diff --git a/libs/shared/lib/querybuilder/usecases/addPill.ts b/libs/shared/lib/querybuilder/usecases/addPill.ts deleted file mode 100644 index 4dc37e18d..000000000 --- a/libs/shared/lib/querybuilder/usecases/addPill.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { - setQuerybuilderNodes, - store, -} from '@graphpolaris/shared/lib/data-access/store'; -import Graph from 'graphology'; -import { Attributes } from 'graphology-types'; - -/** monospace fontsize table */ -const widthPerFontsize = { - 6: 3.6167, - 7: 4.2167, - 10: 6.0167, -}; - -/** Adds a query builder pill to the graphology nodes object. */ -export function addPill( - id: string, - attributes: Attributes, - nodes: Graph -): boolean { - const { type, name } = attributes; - if (!type || !name) return false; - let { x, y } = attributes; - - // Check if x and y are present, otherwise set them to 0 - if (!x) x = 0; - if (!y) y = 0; - - // Get the width and height of a node - const { w, h } = calcWidthHeightOfPill(attributes); - - // Add a node to the graphology object - nodes.addNode(id, { ...attributes, x, y, w, h }); - - // Set the new nodes in the query builder slice - store.dispatch(setQuerybuilderNodes(nodes.export())); - - return true; -} - -/** Calculates the width and height of a query builder pill. - * DEPENDS ON STYLING, if styling changed, change this. - */ -function calcWidthHeightOfPill(attributes: Attributes): { - w: number; - h: number; -} { - const { type, name } = attributes; - - let w = 0; - let h = 0; - switch (type) { - case 'entity': { - // calculate width and height of entity pill - w = Math.min(name.length, 20) * widthPerFontsize[10]; // for fontsize 10px - - const widthOfPillWithoutText = 42.1164; // WARNING: depends on styling - w += widthOfPillWithoutText; - h = 20; - break; - } - case 'relation': { - // calculate width and height of relation pill - w = Math.min(name.length, 20) * widthPerFontsize[10]; // for fontsize 10px - - const widthOfPillWithoutText = 56.0666; // WARNING: depends on styling - w += widthOfPillWithoutText; - h = 20; - break; - } - case 'attribute': { - // calculate width and height of relation pill - const pixelsPerChar = widthPerFontsize[6]; // for fontsize 10px - w = name.length * pixelsPerChar; - - const { datatype, operator } = attributes; - let value = attributes['value']; - if (!datatype || !operator) return { w: 0, h: 0 }; - if (!value) value = '?'; - - // Add width of operator - w += operator.length * widthPerFontsize[7]; - // use a max of 10, because max-width is set to 10ch; - w += Math.min(value.length, 10) * widthPerFontsize[6]; - - const widthOfPillWithoutText = 25.6666; // WARNING: depends on styling - w += widthOfPillWithoutText; - h = 12; - break; - } - } - - return { w, h }; -} diff --git a/libs/shared/lib/querybuilder/usecases/index.ts b/libs/shared/lib/querybuilder/usecases/index.ts deleted file mode 100644 index 54e15f0b0..000000000 --- a/libs/shared/lib/querybuilder/usecases/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './attribute/getAttributeBoolOperators'; -export * from './attribute/checkInput'; -export * from './createReactFlowElements'; -export * from './pillHandles'; -export * from './dragging/dragPill'; -export * from './addPill'; diff --git a/libs/shared/lib/querybuilder/usecases/querybuilder-usecases.spec.ts b/libs/shared/lib/querybuilder/usecases/querybuilder-usecases.spec.ts deleted file mode 100644 index cb212e0e1..000000000 --- a/libs/shared/lib/querybuilder/usecases/querybuilder-usecases.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { querybuilderUsecases } from './querybuilder-usecases'; -import { assert, describe, expect, it } from "vitest"; - -describe('querybuilderUsecases', () => { - it('should work', () => { - expect(querybuilderUsecases()).toEqual('querybuilder-usecases'); - }); -}); diff --git a/libs/shared/lib/querybuilder/usecases/querybuilder-usecases.ts b/libs/shared/lib/querybuilder/usecases/querybuilder-usecases.ts deleted file mode 100644 index 06d687eb9..000000000 --- a/libs/shared/lib/querybuilder/usecases/querybuilder-usecases.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function querybuilderUsecases(): string { - return 'querybuilder-usecases'; -} diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx index 8dbd36ab1..68245392c 100644 --- a/libs/shared/lib/schema/panel/schema.tsx +++ b/libs/shared/lib/schema/panel/schema.tsx @@ -1,4 +1,5 @@ import { + AlgorithmToLayoutProvider, AllLayoutAlgorithms, LayoutFactory, } from '@graphpolaris/shared/lib/graph-layout'; @@ -7,12 +8,13 @@ import { schemaExpandRelation, } from '@graphpolaris/shared/lib/schema/schema-utils'; import { - useSchema, + useSchemaGraph, + useSchemaGraphology, useSchemaLayout, } from '@graphpolaris/shared/lib/data-access/store'; import { MultiGraph } from 'graphology'; // import { AllLayoutAlgorithms, LayoutFactory } from '@graphpolaris/graph-layout'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import ReactFlow, { ControlButton, Controls, @@ -29,12 +31,12 @@ import 'reactflow/dist/style.css'; import styles from './schema.module.scss'; import { - EntityRFPill, - RelationRFPill, - AttributeRFPill, + EntityFlowElement, + RelationPill, + AttributePill, ConnectionDragLine, ConnectionLine, -} from '@graphpolaris/shared/lib/ui/pills'; +} from '@graphpolaris/shared/lib/querybuilder/pills'; import { EntityNode } from '../pills/nodes/entity/entity-node'; import { RelationNode } from '../pills/nodes/relation/relation-node'; import { NodeQualityEntityPopupNode } from '../pills/nodes/popup/node-quality-entity-popup'; @@ -73,40 +75,48 @@ export const Schema = (props: Props) => { const [nodes, setNodes, onNodeChanged] = useNodesState([] as Node[]); const [edges, setEdges, onEdgeChanged] = useEdgesState([] as Edge[]); // In case the schema is updated - const dbschema = useSchema(); - // const [dbschema, setSchema] = useState(useSchema()); - const [schemaLayout, setSchemaLayout] = useState(useSchemaLayout()); + const schemaGraphology = useSchemaGraphology(); + const schemaGraph = useSchemaGraph(); + // const [schemaGraphology, setSchema] = useState(useSchema()); + const schemaLayout = useSchemaLayout(); + const layout = useRef<AlgorithmToLayoutProvider<AllLayoutAlgorithms>>(); - console.log('dbSchema', dbschema.edges()); + // console.log('dbSchema', schemaGraphology.edges()); // useEffect(() => { - // console.log('dbSchema', dbschema, dbschema.order); - // }, [dbschema]); + // console.log('dbSchema', schemaGraphology, schemaGraphology.order); + // }, [schemaGraphology]); const toggleNodeQualityPopup = (id: string) => {}; const toggleAttributeAnalyticsPopupMenu = (id: string) => {}; + function updateLayout() { + const layoutFactory = new LayoutFactory(); + layout.current = layoutFactory.createLayout( + schemaLayout as AllLayoutAlgorithms + ); // TODO: more layouts here + } + + useEffect(() => { + updateLayout(); + }, []); + useEffect(() => { - if (dbschema == undefined || dbschema.order == 0) { + if (schemaGraphology == undefined || schemaGraphology.order == 0) { return; } - // console.log('dbSchema', dbschema.edges()); - - const layoutFactory = new LayoutFactory(); - const layout = layoutFactory.createLayout( - schemaLayout as AllLayoutAlgorithms - ); + console.log(schemaGraphology.export()); - const expandedSchema = schemaExpandRelation(dbschema); - layout?.layout(expandedSchema); // TODO: more layouts here + const expandedSchema = schemaExpandRelation(schemaGraphology); + layout.current?.layout(expandedSchema); const schemaFlow = schemaGraphology2Reactflow(expandedSchema); - schemaFlow.nodes.forEach((n) => { - n.data.toggleNodeQualityPopup = toggleNodeQualityPopup; - n.data.toggleAttributeAnalyticsPopupMenu = - toggleAttributeAnalyticsPopupMenu; - }); - console.log(edges); + // schemaFlow.nodes.forEach((n) => { + // n.data.toggleNodeQualityPopup = toggleNodeQualityPopup; + // n.data.toggleAttributeAnalyticsPopupMenu = + // toggleAttributeAnalyticsPopupMenu; + // }); + // console.log(edges); // console.log( // 'schema Layout', @@ -119,11 +129,11 @@ export const Schema = (props: Props) => { setEdges(schemaFlow.edges); // console.log( // 'update schema useEffect', - // dbschema, - // dbschema.order, + // schemaGraphology, + // schemaGraphology.order, // schemaFlow // ); - }, [dbschema, schemaLayout]); + }, [schemaGraph, schemaLayout]); const graphStyles = { width: '100%', height: '500px' }; @@ -151,6 +161,7 @@ export const Schema = (props: Props) => { edges={edges} style={graphStyles} onInit={onInit} + panOnDrag={false} attributionPosition="top-right" > <Controls diff --git a/libs/shared/lib/schema/panel/schemaOLD.tsx b/libs/shared/lib/schema/panel/schemaOLD.tsx index e162b7869..f2d62bf1c 100644 --- a/libs/shared/lib/schema/panel/schemaOLD.tsx +++ b/libs/shared/lib/schema/panel/schemaOLD.tsx @@ -7,7 +7,7 @@ import { schemaExpandRelation, } from '@graphpolaris/shared/lib/schema/schema-utils'; import { - useSchema, + useSchemaGraphology, useSchemaLayout, } from '@graphpolaris/shared/lib/data-access/store'; import { MultiGraph } from 'graphology'; @@ -28,12 +28,12 @@ import 'reactflow/dist/style.css'; import styles from './schema.module.scss'; import { - EntityRFPill, - RelationRFPill, - AttributeRFPill, + EntityFlowElement, + RelationPill, + AttributePill, ConnectionDragLine, ConnectionLine, -} from '@graphpolaris/shared/lib/ui/pills'; +} from '@graphpolaris/shared/lib/querybuilder/pills'; interface Props { content?: string; @@ -44,9 +44,9 @@ const onLoad = (reactFlowInstance: any) => { }; const nodeTypes = { - entity: EntityRFPill, - relation: RelationRFPill, - attribute: AttributeRFPill, + entity: EntityFlowElement, + relation: RelationPill, + attribute: AttributePill, }; const edgeTypes = { connection: ConnectionLine, @@ -56,7 +56,7 @@ export const Schema = (props: Props) => { const [nodes, setNodes, onNodeChanged] = useNodesState([] as Node[]); const [edges, setEdges, onEdgeChanged] = useEdgesState([] as Edge[]); // In case the schema is updated - const dbschema = useSchema(); + const dbschema = useSchemaGraphology(); // const [dbschema, setSchema] = useState(useSchema()); const [schemaLayout, setSchemaLayout] = useState(useSchemaLayout()); diff --git a/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx b/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx index 9307bee9e..a33dcaceb 100644 --- a/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx +++ b/libs/shared/lib/schema/pills/nodes/entity/entity-node.tsx @@ -30,7 +30,7 @@ import { */ export const EntityNode = React.memo( ({ id, data }: NodeProps<SchemaGraphNodeWithFunctions>) => { - console.log(data); + // console.log(data); const [hidden, setHidden] = useState<boolean>(true); const theme = useTheme(); diff --git a/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx b/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx index 7217c67b3..aa5e1c849 100644 --- a/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx +++ b/libs/shared/lib/schema/pills/nodes/relation/relation-node.tsx @@ -33,7 +33,7 @@ export const RelationNode = React.memo( ({ id, data }: NodeProps<SchemaGraphRelationWithFunctions>) => { const [hidden, setHidden] = useState<boolean>(true); const theme = useTheme(); - console.log(data); + // console.log(data); /** * Adds drag functionality in order to be able to drag the relationNode to the schema. @@ -67,8 +67,6 @@ export const RelationNode = React.memo( data.toggleAttributeAnalyticsPopupMenu(data.collection); }; - console.log(theme.palette.custom.nodesBase[0]); - const widthExternalBoxes = data.attributes ? calcWidthRelationNodeBox(data.attributes.length, data.nodeCount) : 0; diff --git a/libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.module.scss b/libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.module.scss deleted file mode 100644 index 9ba5ba22c..000000000 --- a/libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.module.scss +++ /dev/null @@ -1,60 +0,0 @@ -@use './variables.module.scss'; - -.attribute { - display: flex; - font-family: monospace; - font-weight: bold; - font-size: variables.$fontsize; - border-radius: 2px; -} - -// .handle { -// border: 0px; -// border-radius: 10px; -// left: 12px; -// width: 7px; -// height: 7px; -// margin-bottom: 11px; -// background: rgba(255, 255, 255, 0.6); -// box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3); -// transform-origin: center; -// } - -.contentWrapper { - display: flex; - align-items: center; - - .content { - padding: variables.$ypad 0 variables.$ypad 1ch; - max-width: 15ch; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } -} - -.attributeInput { - float: right; - padding: 0 1ch 0 0; - display: flex; - align-items: center; - - input { - background-color: rgba(100, 100, 100, 0.1); - font-family: monospace; - font-size: variables.$fontsize; - border: 1px solid rgba(100, 100, 100, 0.3); - border-radius: 2px; - height: variables.$height; - outline: none; - transition: border 0.3s; - color: black; - &::placeholder { - color: black; - } - - &:focus { - border: 1px solid rgba(0, 0, 0, 0.3); - } - } -} diff --git a/libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.module.scss.d.ts b/libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.module.scss.d.ts deleted file mode 100644 index af1e5312f..000000000 --- a/libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.module.scss.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare const classNames: { - readonly attribute: 'attribute'; - readonly contentWrapper: 'contentWrapper'; - readonly content: 'content'; - readonly attributeInput: 'attributeInput'; -}; -export = classNames; diff --git a/libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.tsx b/libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.tsx deleted file mode 100644 index e202d2988..000000000 --- a/libs/shared/lib/ui/pills/customFlowPills/attributepill/attributepill.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { - CheckDatatypeConstraint, - GetAttributeBoolOperators, -} from '@graphpolaris/shared/lib/querybuilder/usecases'; -import { - updateQBAttributeOperator, - updateQBAttributeValue, - useAppDispatch, -} from '@graphpolaris/shared/lib/data-access/store'; -import { useTheme } from '@mui/material'; -import React, { useMemo, useState } from 'react'; -import styles from './attributepill.module.scss'; -import AttributeOperatorSelect from './operatorselect'; -import { NodeProps } from 'reactflow'; - -/** - * Component to render an attribute flow element - * @param {FlowElement<EntityData>)} param0 The data of an entity flow element. - */ -export const AttributeRFPill = React.memo(({ id, data }: NodeProps) => { - const theme = useTheme(); - const dispatch = useAppDispatch(); - const [value, setValue] = useState(data?.value || ''); - - const onChange = (e: any) => { - setValue(e.target.value); - }; - const validateInput = () => { - const newValue = CheckDatatypeConstraint(data.datatype, value); - setValue(newValue); - dispatch(updateQBAttributeValue({ id, value: newValue })); - }; - - // Calculates the size of the input - const getInputWidth = () => { - if (value == '') return 1; - else if (value.length > 10) return 10; - return value.length; - }; - - const boolOperators = useMemo( - () => GetAttributeBoolOperators(data?.datatype), - [data?.datatype] - ); - - // Determine the backgroundcolor based on if the attribute is connected to a entity or relation - let bgcolor; - if (data?.attributeOfA == 'entity') - bgcolor = theme.palette.custom.queryBuilder.entity.lighterbg; - else if (data?.attributeOfA == 'relation') - bgcolor = theme.palette.custom.queryBuilder.relation.lighterbg; - else bgcolor = theme.palette.custom.queryBuilder.attribute.background; - - return ( - <div - className={styles.attribute} - style={{ - background: bgcolor, - color: theme.palette.custom.queryBuilder.text, - }} - > - {/* <Handle - id={Handles.Attribute} - type="source" - position={Position.Bottom} - className={styles.handle} - /> */} - <div className={styles.contentWrapper}> - <span className={styles.content} title={data.name}> - {data.name} - </span> - <AttributeOperatorSelect - selected={data?.operator} - options={boolOperators} - changed={(o) => - dispatch(updateQBAttributeOperator({ id, operator: o.value })) - } - /> - <span className={styles.attributeInput}> - <input - style={{ maxWidth: `${getInputWidth()}ch` }} - type="string" - placeholder={'?'} - value={value} - onChange={onChange} - onBlur={validateInput} - onKeyDown={(e) => e.key == 'Enter' && validateInput()} - ></input> - </span> - </div> - </div> - ); -}); -AttributeRFPill.displayName = 'AttributeRFPill'; - -export default AttributeRFPill; diff --git a/libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.module.scss b/libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.module.scss deleted file mode 100644 index 755d2b41d..000000000 --- a/libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.module.scss +++ /dev/null @@ -1,49 +0,0 @@ -.entity { - display: flex; - font-family: monospace; - font-weight: bold; - font-size: 10px; - padding: 4px 2ch; - border-radius: 3px; -} - -.highlighted { - box-shadow: black 0 0 2px; -} - -.handleLeft { - border: 0px; - border-radius: 0px; - left: 12px; - width: 7px; - height: 7px; - margin-bottom: 11px; - background: rgba(255, 255, 255, 0.6); - box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3); - transform-origin: center; -} - -// .handleBottom { -// border: 0px; -// border-radius: 0px; -// width: 7px; -// height: 7px; -// left: 27.5px; -// margin-bottom: 11px; -// background: rgba(255, 255, 255, 0.6); -// box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3); -// transform: rotate(-45deg); -// transform-origin: center; -// } - -.contentWrapper { - margin-left: 3ch; - - span { - max-width: 20ch; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - display: block; - } -} diff --git a/libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.module.scss.d.ts b/libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.module.scss.d.ts deleted file mode 100644 index d39768904..000000000 --- a/libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.module.scss.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare const classNames: { - readonly entity: 'entity'; - readonly highlighted: 'highlighted'; - readonly handleLeft: 'handleLeft'; - readonly contentWrapper: 'contentWrapper'; -}; -export = classNames; diff --git a/libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.tsx b/libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.tsx deleted file mode 100644 index 3a3168c58..000000000 --- a/libs/shared/lib/ui/pills/customFlowPills/entitypill/entitypill.tsx +++ /dev/null @@ -1,74 +0,0 @@ -// import { handles } from '@graphpolaris/shared/lib/querybuilder/usecases'; -import { useTheme } from '@mui/material'; -import React, { useEffect } from 'react'; -import { ReactFlow, Handle, Position } from 'reactflow'; -import styles from './entitypill.module.scss'; -import cn from 'classnames'; - -export const Handless = { - entity: { - attributes: 'attributesHandle', - relations: 'relationsHandle', - }, -}; - -/** Links need handles to what they are connected to (and which side) */ -export enum Handles { - RelationLeft = 'leftEntityHandle', //target - RelationRight = 'rightEntityHandle', //target - ToAttributeHandle = 'attributesHandle', //target - ToRelation = 'relationsHandle', //source - Attribute = 'AttributeHandle', //source - ReceiveFunction = 'receiveFunctionHandle', //target - FunctionBase = 'functionHandle_', // + name from FunctionTypes args //source -} - -/** - * Component to render an entity flow element - * @param {ReactFlow<EntityData>)} param0 The data of an entity flow element. - */ -export const EntityRFPill = React.memo(({ data }: { data: any }) => { - const theme = useTheme(); - - console.log('EntityRFPill', data); - return ( - <div - className={cn(styles['entity'], { - [styles['highlighted']]: data.suggestedForConnection, - })} - style={{ - background: theme.palette.custom.queryBuilder.entity.background, - color: theme.palette.custom.queryBuilder.text, - }} - > - {/* <Handle - id={handles.entity.relation} - type="source" - position={Position.Bottom} - className={styles['handleLeft']} - style={data?.isConnected ? { backgroundColor: '#2e2e2e' } : {}} - /> */} - {/* <Handle - id={Handles.ToAttributeHandle} - type="target" - position={Position.Bottom} - className={styles.handleBottom} - /> */} - <Handle - type="target" - position={Position.Left} - isValidConnection={(connection) => connection.source === 'some-id'} - onConnect={(params) => console.log('handle onConnect', params)} - style={{ background: '#fff' }} - /> - <div className={styles['contentWrapper']}> - <span title={data.name}>{data.name}</span> - </div> - FooBar - </div> - ); -}); - -EntityRFPill.displayName = 'EntityRFPill'; - -export default EntityRFPill; diff --git a/libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.module.scss b/libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.module.scss deleted file mode 100644 index fa564adf3..000000000 --- a/libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.module.scss +++ /dev/null @@ -1,113 +0,0 @@ -.relation { - display: flex; - text-align: center; - font-family: monospace; - font-weight: bold; - font-size: 10px; - background-color: transparent; -} - -.highlighted { - box-shadow: black 0 0 2px; -} - -.contentWrapper { - display: flex; - align-items: center; - - .handleLeft { - position: relative; - z-index: 3; - - top: 25%; - border: 0px; - border-radius: 0px; - - background: transparent; - transform-origin: center; - - width: 0; - height: 0; - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-right: rgba(255, 255, 255, 0.7) 6px solid; - - &::after { - content: ''; - display: block; - position: absolute; - width: 0; - height: 0; - border-top: 7px solid transparent; - border-bottom: 7px solid transparent; - border-right: rgba(0, 0, 0, 0.1) 8px solid; - top: -7px; - right: -7px; - } - } - .highlighted { - z-index: -1; - box-shadow: 0 0 2px 1px gray; - } - - .content { - margin: 0 2ch; - padding: 3px 0; - max-width: 20ch; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - // pointer-events: none; - } - - .handleRight { - position: relative; - top: 25%; - border: 0px; - border-radius: 0px; - - background: transparent; - transform-origin: center; - - width: 0; - height: 0; - border-top: 5px solid transparent; - border-bottom: 5px solid transparent; - border-left: rgba(255, 255, 255, 0.7) 6px solid; - - &::after { - content: ''; - display: block; - position: absolute; - width: 0; - height: 0; - border-top: 7px solid transparent; - border-bottom: 7px solid transparent; - border-left: rgba(0, 0, 0, 0.1) 8px solid; - top: -7px; - left: -7px; - } - } -} - -$height: 10px; -.arrowLeft { - z-index: 2; - width: 0; - height: 0; - border-top: $height solid transparent; - border-bottom: $height solid transparent; - transform: scale(1.028) translate(0.3px, 0px); - - border-right: $height solid; -} - -.arrowRight { - width: 0; - height: 0; - border-top: $height solid transparent; - border-bottom: $height solid transparent; - transform: scale(1.02) translate(-0.3px, 0px); - - border-left: $height solid; -} diff --git a/libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts b/libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts deleted file mode 100644 index 68ba73f84..000000000 --- a/libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.module.scss.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -declare const classNames: { - readonly relation: 'relation'; - readonly highlighted: 'highlighted'; - readonly contentWrapper: 'contentWrapper'; - readonly handleLeft: 'handleLeft'; - readonly content: 'content'; - readonly handleRight: 'handleRight'; - readonly arrowLeft: 'arrowLeft'; - readonly arrowRight: 'arrowRight'; -}; -export = classNames; diff --git a/libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.tsx b/libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.tsx deleted file mode 100644 index 7e81ade7c..000000000 --- a/libs/shared/lib/ui/pills/customFlowPills/relationpill/relationpill.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React, { memo } from 'react'; - -import { handles } from '@graphpolaris/shared/lib/querybuilder/usecases'; -import { useTheme } from '@mui/material'; -import { Handle, Position } from 'reactflow'; -import cn from 'classnames'; - -import styles from './relationpill.module.scss'; - -export type RelationRFPillProps = { - data: { - name: string; - suggestedForConnection: any; - isFromEntityConnected?: boolean; - isToEntityConnected?: boolean; - }; -}; - -/** - * Component to render a relation flow element - * @param { FlowElement<RelationData>} param0 The data of a relation flow element. - */ -export const RelationRFPill = memo(({ data }: RelationRFPillProps) => { - // export default function RelationRFPill({ data }: { data: any }) { - const theme = useTheme(); - console.log('RelationRFPill', data); - - // const minRef = useRef<HTMLInputElement>(null); - // const maxRef = useRef<HTMLInputElement>(null); - - // const [readOnlyMin, setReadOnlyMin] = useState(true); - // const [readOnlyMax, setReadOnlyMax] = useState(true); - - // const onDepthChanged = (depth: string) => { - // // Don't allow depth above 99 - // const limit = 99; - // if (data != undefined) { - // data.depth.min = data.depth.min >= limit ? limit : data.depth.min; - // data.depth.max = data.depth.max >= limit ? limit : data.depth.max; - - // // Check for for valid depth: min <= max - // if (depth == 'min') { - // if (data.depth.min > data.depth.max) data.depth.max = data.depth.min; - // setReadOnlyMin(true); - // } else if (depth == 'max') { - // if (data.depth.max < data.depth.min) data.depth.min = data.depth.max; - // setReadOnlyMax(true); - // } - - // // Set to the correct width - // if (maxRef.current) - // maxRef.current.style.maxWidth = calcWidth(data.depth.max); - // if (minRef.current) - // minRef.current.style.maxWidth = calcWidth(data.depth.min); - // } - // }; - - // const calcWidth = (data: number) => { - // return data.toString().length + 0.5 + 'ch'; - // }; - - return ( - <div className={styles['relation']}> - <div - className={styles['arrowLeft']} - style={{ - borderRightColor: - theme.palette.custom.queryBuilder.relation.background, - }} - /> - <div - className={cn(styles['contentWrapper'], { - [styles['highlighted']]: data.suggestedForConnection, - })} - style={{ - color: theme.palette.custom.queryBuilder.text, - background: theme.palette.custom.queryBuilder.relation.background, - }} - > - <Handle - id={handles.relation.fromEntity} - type="target" - position={Position.Left} - className={styles['handleLeft']} - style={ - data?.isFromEntityConnected ? { borderRightColor: '#2e2e2e' } : {} // TODO: this should be color from theme - } - /> - <span className={styles['content']} title={data.name}> - {data.name} - </span> - <Handle - id={handles.relation.toEntity} - type="source" - position={Position.Right} - className={styles['handleRight']} - style={ - data?.isToEntityConnected ? { borderLeftColor: '#2e2e2e' } : {} - } - /> - </div> - <div - className={styles['arrowRight']} - style={{ - borderLeftColor: - theme.palette.custom.queryBuilder.relation.background, - }} - /> - </div> - ); -}); - -RelationRFPill.displayName = 'RelationRFPill'; -export default RelationRFPill; diff --git a/libs/shared/lib/ui/pills/shared-ui-pills.spec.tsx b/libs/shared/lib/ui/pills/shared-ui-pills.spec.tsx deleted file mode 100644 index 11b5289db..000000000 --- a/libs/shared/lib/ui/pills/shared-ui-pills.spec.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { render } from "@testing-library/react"; -import { assert, describe, expect, it } from "vitest"; - -import SharedUiPills from "./shared-ui-pills"; - -describe("SharedUiPills", () => { - it("should render successfully", () => { - const { baseElement } = render(<SharedUiPills />); - expect(baseElement).toBeTruthy(); - }); -}); diff --git a/libs/shared/lib/ui/pills/shared-ui-pills.tsx b/libs/shared/lib/ui/pills/shared-ui-pills.tsx deleted file mode 100644 index 6a804d611..000000000 --- a/libs/shared/lib/ui/pills/shared-ui-pills.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import './shared-ui-pills.module.scss'; - -/* eslint-disable-next-line */ -export interface SharedUiPillsProps {} - -export function SharedUiPills(props: SharedUiPillsProps) { - return ( - <div> - <h1>Welcome to SharedUiPills!</h1> - </div> - ); -} - -export default SharedUiPills; diff --git a/libs/shared/package.json b/libs/shared/package.json index f5d77dbee..717ce13fa 100644 --- a/libs/shared/package.json +++ b/libs/shared/package.json @@ -43,7 +43,7 @@ "react-grid-layout": "^1.3.4", "react-json-view": "^1.21.3", "react-router-dom": "^6.8.1", - "reactflow": "next", + "reactflow": "^11.7.0", "regenerator-runtime": "0.13.11", "sass": "^1.59.3", "scss": "^0.2.4", diff --git a/libs/storybook/.storybook/main.ts b/libs/storybook/.storybook/main.ts index f4ed50e66..e2b95a49a 100644 --- a/libs/storybook/.storybook/main.ts +++ b/libs/storybook/.storybook/main.ts @@ -9,7 +9,8 @@ const config: StorybookConfig = { // "../src/**/*.stories.@(js|jsx|ts|tsx)", // "../node_modules/@graphpolaris/**/*.stories.@(js|jsx|ts|tsx)" "../node_modules/@graphpolaris/shared/lib/**/*.stories.@(js|jsx|ts|tsx)", - "../../../apps/web/src/**/*.stories.@(js|jsx|ts|tsx)", + // "../node_modules/web/src/**/*.stories.@(js|jsx|ts|tsx)", + // "../../../apps/web/src/**/*.stories.@(js|jsx|ts|tsx)", ], addons: [ "@storybook/addon-links", @@ -18,10 +19,10 @@ const config: StorybookConfig = { { name: '@storybook/addon-styling', options: { - sass: { - // Require your Sass preprocessor here - implementation: require('sass'), - }, + // sass: { + // Require your Sass preprocessor here + // implementation: require('sass'), + // }, }, }, { diff --git a/libs/storybook/package.json b/libs/storybook/package.json index 4ae25ae42..30a556d74 100644 --- a/libs/storybook/package.json +++ b/libs/storybook/package.json @@ -10,21 +10,21 @@ }, "dependencies": { "@graphpolaris/shared": "workspace:*", + "web": "workspace:*", "postcss-scss": "^4.0.6", "react": "^18.2.0", - "react-dom": "^18.2.0", - "ui": "workspace:*" + "react-dom": "^18.2.0" }, "devDependencies": { - "@storybook/addon-essentials": "7.0.0-rc.5", - "@storybook/addon-interactions": "7.0.0-rc.5", - "@storybook/addon-links": "7.0.0-rc.5", - "@storybook/addon-styling": "^0.3.2", - "@storybook/blocks": "7.0.0-rc.5", + "@storybook/addon-essentials": "next", + "@storybook/addon-interactions": "next", + "@storybook/addon-links": "^7.0.7", + "@storybook/addon-styling": "^1.0.5", + "@storybook/blocks": "^7.0.7", "@storybook/preset-scss": "^1.0.3", - "@storybook/react": "7.0.0-rc.5", - "@storybook/react-vite": "7.0.0-rc.5", - "@storybook/testing-library": "0.0.14-next.1", + "@storybook/react": "^7.0.7", + "@storybook/react-vite": "^7.0.7", + "@storybook/testing-library": "0.1.0", "@types/node": "18.13.0", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", @@ -36,7 +36,7 @@ "prop-types": "15.8.1", "sass": "^1.59.3", "sass-loader": "^13.2.2", - "storybook": "7.0.0-rc.5", + "storybook": "^7.0.7", "typescript": "^4.9.3", "vite": "^4.2.0", "vite-plugin-sass-dts": "^1.3.2", diff --git a/libs/workspace/ui/Button.tsx b/libs/workspace/ui/Button.tsx deleted file mode 100644 index 61e9000e0..000000000 --- a/libs/workspace/ui/Button.tsx +++ /dev/null @@ -1,22 +0,0 @@ -interface Props { - primary?: boolean; - size?: "small" | "large"; - label?: string; -} - -export const Button = ({ - primary = false, - label = "Boop", - size = "small", -}: Props) => { - return ( - <button - style={{ - backgroundColor: primary ? "red" : "blue", - fontSize: size === "large" ? "24px" : "14px", - }} - > - {label} - </button> - ); -}; diff --git a/libs/workspace/ui/index.tsx b/libs/workspace/ui/index.tsx deleted file mode 100644 index 916730e8f..000000000 --- a/libs/workspace/ui/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -import * as React from "react"; -export * from "./Button"; diff --git a/libs/workspace/ui/package.json b/libs/workspace/ui/package.json deleted file mode 100644 index 17b3a320a..000000000 --- a/libs/workspace/ui/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "ui", - "version": "0.0.0", - "main": "./index.tsx", - "types": "./index.tsx", - "license": "MIT", - "scripts": { - "lint": "eslint *.ts*" - }, - "devDependencies": { - "@types/react": "^18.0.17", - "@types/react-dom": "^18.0.6", - "eslint": "^7.32.0", - "eslint-config-custom": "workspace:*", - "react": "^18.2.0", - "tsconfig": "workspace:*", - "typescript": "^4.5.2" - } -} diff --git a/libs/workspace/ui/tsconfig.json b/libs/workspace/ui/tsconfig.json deleted file mode 100644 index cd6c94d6e..000000000 --- a/libs/workspace/ui/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "tsconfig/react-library.json", - "include": ["."], - "exclude": ["dist", "build", "node_modules"] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2053e8489..14b92d621 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,10 +21,10 @@ importers: version: 8.0.3 prettier: specifier: latest - version: 2.8.7 + version: 2.8.8 turbo: specifier: latest - version: 1.8.8 + version: 1.9.3 apps/web: dependencies: @@ -34,6 +34,9 @@ importers: '@mui/base': specifier: 5.0.0-alpha.118 version: 5.0.0-alpha.118(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) + '@mui/icons-material': + specifier: ^5.11.11 + version: 5.11.11(@mui/material@5.11.13)(@types/react@18.0.28)(react@18.2.0) '@mui/material': specifier: ^5.11.13 version: 5.11.13(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) @@ -83,6 +86,9 @@ importers: '@types/styled-components': specifier: ^5.1.26 version: 5.1.26 + '@vitejs/plugin-basic-ssl': + specifier: ^1.0.1 + version: 1.0.1(vite@4.2.1) '@vitejs/plugin-react-swc': specifier: ^3.0.0 version: 3.2.0(vite@4.2.1) @@ -97,7 +103,10 @@ importers: version: 4.9.5 vite: specifier: ^4.2.0 - version: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + version: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) + vite-plugin-dts: + specifier: ^2.1.0 + version: 2.1.0(@types/node@18.13.0)(vite@4.2.1) vitest: specifier: ^0.29.2 version: 0.29.4(happy-dom@8.9.0)(jsdom@21.1.1)(sass@1.59.3) @@ -189,8 +198,8 @@ importers: specifier: ^6.8.1 version: 6.9.0(react-dom@18.2.0)(react@18.2.0) reactflow: - specifier: next - version: 11.4.0-next.1(react-dom@18.2.0)(react@18.2.0) + specifier: ^11.7.0 + version: 11.7.0(react-dom@18.2.0)(react@18.2.0) regenerator-runtime: specifier: 0.13.11 version: 0.13.11 @@ -212,7 +221,7 @@ importers: devDependencies: '@storybook/addon-styling': specifier: ^0.3.2 - version: 0.3.2(@storybook/addons@6.5.16)(@storybook/api@6.5.16)(@storybook/components@6.5.16)(@storybook/core-events@6.5.16)(@storybook/manager-api@7.0.0-rc.5)(@storybook/theming@6.5.16)(react-dom@18.2.0)(react@18.2.0)(sass-loader@13.2.2) + version: 0.3.2(@storybook/addons@6.5.16)(@storybook/api@6.5.16)(@storybook/components@6.5.16)(@storybook/core-events@6.5.16)(@storybook/manager-api@7.0.7)(@storybook/theming@6.5.16)(react-dom@18.2.0)(react@18.2.0)(sass-loader@13.2.2) '@storybook/preset-scss': specifier: ^1.0.3 version: 1.0.3(css-loader@6.7.3)(sass-loader@13.2.2)(style-loader@3.3.2) @@ -353,7 +362,7 @@ importers: version: 4.1.1(webpack@5.77.0) vite: specifier: ^4.1.0 - version: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + version: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) vite-plugin-dts: specifier: ^2.1.0 version: 2.1.0(@types/node@18.13.0)(vite@4.2.1) @@ -381,37 +390,37 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) - ui: + web: specifier: workspace:* - version: link:../workspace/ui + version: link:../../apps/web devDependencies: '@storybook/addon-essentials': - specifier: 7.0.0-rc.5 + specifier: next version: 7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-interactions': - specifier: 7.0.0-rc.5 + specifier: next version: 7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-links': - specifier: 7.0.0-rc.5 - version: 7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0) + specifier: ^7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0) '@storybook/addon-styling': - specifier: ^0.3.2 - version: 0.3.2(@storybook/addons@6.5.16)(@storybook/api@6.5.16)(@storybook/components@6.5.16)(@storybook/core-events@6.5.16)(@storybook/manager-api@7.0.0-rc.5)(@storybook/theming@6.5.16)(react-dom@18.2.0)(react@18.2.0)(sass-loader@13.2.2) + specifier: ^1.0.5 + version: 1.0.5(less@4.1.3)(postcss@8.4.21)(react-dom@18.2.0)(react@18.2.0)(sass@1.59.3)(webpack@5.77.0) '@storybook/blocks': - specifier: 7.0.0-rc.5 - version: 7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0) + specifier: ^7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0) '@storybook/preset-scss': specifier: ^1.0.3 version: 1.0.3(css-loader@6.7.3)(sass-loader@13.2.2)(style-loader@3.3.2) '@storybook/react': - specifier: 7.0.0-rc.5 - version: 7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5) + specifier: ^7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5) '@storybook/react-vite': - specifier: 7.0.0-rc.5 - version: 7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5)(vite@4.2.1) + specifier: ^7.0.7 + version: 7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5)(vite@4.2.1) '@storybook/testing-library': - specifier: 0.0.14-next.1 - version: 0.0.14-next.1 + specifier: 0.1.0 + version: 0.1.0 '@types/node': specifier: 18.13.0 version: 18.13.0 @@ -446,17 +455,17 @@ importers: specifier: ^13.2.2 version: 13.2.2(sass@1.59.3)(webpack@5.77.0) storybook: - specifier: 7.0.0-rc.5 - version: 7.0.0-rc.5 + specifier: ^7.0.7 + version: 7.0.7 typescript: specifier: ^4.9.3 version: 4.9.5 vite: specifier: ^4.2.0 - version: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + version: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) vite-plugin-sass-dts: specifier: ^1.3.2 - version: 1.3.2(postcss@8.4.21)(prettier@2.8.7)(sass@1.59.3)(vite@4.2.1) + version: 1.3.2(postcss@8.4.21)(prettier@2.8.8)(sass@1.59.3)(vite@4.2.1) vite-tsconfig-paths: specifier: ^4.0.7 version: 4.0.7(typescript@4.9.5)(vite@4.2.1) @@ -485,30 +494,6 @@ importers: libs/workspace/tsconfig: {} - libs/workspace/ui: - devDependencies: - '@types/react': - specifier: ^18.0.17 - version: 18.0.28 - '@types/react-dom': - specifier: ^18.0.6 - version: 18.0.11 - eslint: - specifier: ^7.32.0 - version: 7.32.0 - eslint-config-custom: - specifier: workspace:* - version: link:../eslint-config-custom - react: - specifier: ^18.2.0 - version: 18.2.0 - tsconfig: - specifier: workspace:* - version: link:../tsconfig - typescript: - specifier: ^4.5.2 - version: 4.9.5 - packages: /@ampproject/remapping@2.2.0: @@ -537,8 +522,8 @@ packages: dependencies: '@babel/highlight': 7.18.6 - /@babel/compat-data@7.21.0: - resolution: {integrity: sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==} + /@babel/compat-data@7.21.4: + resolution: {integrity: sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==} engines: {node: '>=6.9.0'} dev: true @@ -549,13 +534,13 @@ packages: '@ampproject/remapping': 2.2.0 '@babel/code-frame': 7.18.6 '@babel/generator': 7.21.3 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.21.3) + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) '@babel/helper-module-transforms': 7.21.2 '@babel/helpers': 7.21.0 '@babel/parser': 7.21.3 '@babel/template': 7.20.7 '@babel/traverse': 7.21.3(supports-color@5.5.0) - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 convert-source-map: 1.9.0 debug: 4.3.4(supports-color@5.5.0) gensync: 1.0.0-beta.2 @@ -569,7 +554,7 @@ packages: resolution: {integrity: sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 '@jridgewell/gen-mapping': 0.3.2 '@jridgewell/trace-mapping': 0.3.17 jsesc: 2.5.2 @@ -578,23 +563,23 @@ packages: resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 /@babel/helper-builder-binary-assignment-operator-visitor@7.18.9: resolution: {integrity: sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-explode-assignable-expression': 7.18.6 - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true - /@babel/helper-compilation-targets@7.20.7(@babel/core@7.21.3): - resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==} + /@babel/helper-compilation-targets@7.21.4(@babel/core@7.21.3): + resolution: {integrity: sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/compat-data': 7.21.0 + '@babel/compat-data': 7.21.4 '@babel/core': 7.21.3 '@babel/helper-validator-option': 7.21.0 browserslist: 4.21.5 @@ -638,7 +623,7 @@ packages: '@babel/core': ^7.4.0-0 dependencies: '@babel/core': 7.21.3 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.21.3) + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) '@babel/helper-plugin-utils': 7.20.2 debug: 4.3.4(supports-color@5.5.0) lodash.debounce: 4.0.8 @@ -656,7 +641,7 @@ packages: resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true /@babel/helper-function-name@7.21.0: @@ -664,26 +649,26 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.20.7 - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 /@babel/helper-hoist-variables@7.18.6: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 /@babel/helper-member-expression-to-functions@7.21.0: resolution: {integrity: sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true /@babel/helper-module-imports@7.18.6: resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 /@babel/helper-module-transforms@7.21.2: resolution: {integrity: sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==} @@ -696,7 +681,7 @@ packages: '@babel/helper-validator-identifier': 7.19.1 '@babel/template': 7.20.7 '@babel/traverse': 7.21.3(supports-color@5.5.0) - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 transitivePeerDependencies: - supports-color dev: true @@ -705,7 +690,7 @@ packages: resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true /@babel/helper-plugin-utils@7.20.2: @@ -723,7 +708,7 @@ packages: '@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-environment-visitor': 7.18.9 '@babel/helper-wrap-function': 7.20.5 - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 transitivePeerDependencies: - supports-color dev: true @@ -737,7 +722,7 @@ packages: '@babel/helper-optimise-call-expression': 7.18.6 '@babel/template': 7.20.7 '@babel/traverse': 7.21.3(supports-color@5.5.0) - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 transitivePeerDependencies: - supports-color dev: true @@ -746,21 +731,21 @@ packages: resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true /@babel/helper-skip-transparent-expression-wrappers@7.20.0: resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true /@babel/helper-split-export-declaration@7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 /@babel/helper-string-parser@7.19.4: resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} @@ -782,7 +767,7 @@ packages: '@babel/helper-function-name': 7.21.0 '@babel/template': 7.20.7 '@babel/traverse': 7.21.3(supports-color@5.5.0) - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 transitivePeerDependencies: - supports-color dev: true @@ -793,7 +778,7 @@ packages: dependencies: '@babel/template': 7.20.7 '@babel/traverse': 7.21.3(supports-color@5.5.0) - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 transitivePeerDependencies: - supports-color dev: true @@ -811,7 +796,7 @@ packages: engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.21.3): resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} @@ -949,9 +934,9 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.21.0 + '@babel/compat-data': 7.21.4 '@babel/core': 7.21.3 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.21.3) + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) '@babel/helper-plugin-utils': 7.20.2 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.21.3) '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.21.3) @@ -1240,7 +1225,7 @@ packages: dependencies: '@babel/core': 7.21.3 '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.21.3) + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) '@babel/helper-environment-visitor': 7.18.9 '@babel/helper-function-name': 7.21.0 '@babel/helper-optimise-call-expression': 7.18.6 @@ -1333,7 +1318,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.21.3 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.21.3) + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) '@babel/helper-function-name': 7.21.0 '@babel/helper-plugin-utils': 7.20.2 dev: true @@ -1498,7 +1483,7 @@ packages: '@babel/helper-module-imports': 7.18.6 '@babel/helper-plugin-utils': 7.20.2 '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.3) - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true /@babel/plugin-transform-regenerator@7.20.5(@babel/core@7.21.3): @@ -1609,15 +1594,15 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: true - /@babel/preset-env@7.20.2(@babel/core@7.21.3): - resolution: {integrity: sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==} + /@babel/preset-env@7.21.4(@babel/core@7.21.3): + resolution: {integrity: sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.21.0 + '@babel/compat-data': 7.21.4 '@babel/core': 7.21.3 - '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.21.3) + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3) '@babel/helper-plugin-utils': 7.20.2 '@babel/helper-validator-option': 7.21.0 '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6(@babel/core@7.21.3) @@ -1685,7 +1670,7 @@ packages: '@babel/plugin-transform-unicode-escapes': 7.18.10(@babel/core@7.21.3) '@babel/plugin-transform-unicode-regex': 7.18.6(@babel/core@7.21.3) '@babel/preset-modules': 0.1.5(@babel/core@7.21.3) - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.21.3) babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.21.3) babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.21.3) @@ -1716,7 +1701,7 @@ packages: '@babel/helper-plugin-utils': 7.20.2 '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.21.3) '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.21.3) - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 esutils: 2.0.3 dev: true @@ -1764,7 +1749,7 @@ packages: dependencies: '@babel/code-frame': 7.18.6 '@babel/parser': 7.21.3 - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 /@babel/traverse@7.21.3(supports-color@5.5.0): resolution: {integrity: sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==} @@ -1777,14 +1762,14 @@ packages: '@babel/helper-hoist-variables': 7.18.6 '@babel/helper-split-export-declaration': 7.18.6 '@babel/parser': 7.21.3 - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 debug: 4.3.4(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color - /@babel/types@7.21.3: - resolution: {integrity: sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==} + /@babel/types@7.21.4: + resolution: {integrity: sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.19.4 @@ -2642,7 +2627,7 @@ packages: magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@4.9.5) typescript: 4.9.5 - vite: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) dev: true /@jridgewell/gen-mapping@0.1.1: @@ -3349,6 +3334,21 @@ packages: - immer dev: false + /@reactflow/background@11.2.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Fd8Few2JsLuE/2GaIM6fkxEBaAJvfzi2Lc106HKi/ddX+dZs8NUsSwMsJy1Ajs8b4GbiX8v8axfKpbK6qFMV8w==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.7.0(react-dom@18.2.0)(react@18.2.0) + classcat: 5.0.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + zustand: 4.3.6(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + /@reactflow/controls@11.1.0-next.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-jqvwxI9VFc4ZPBfZE98MigwE+UJbxLHBC47y0pt1bGqnxzK1XcAMocDVcvlTMbHWbDmouFmpk+cUoYSkQx5/cQ==} peerDependencies: @@ -3364,6 +3364,20 @@ packages: - immer dev: false + /@reactflow/controls@11.1.11(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-g6WrsszhNkQjzkJ9HbVUBkGGoUy2z8dQVgH6CYQEjuoonD15cWAPGvjyg8vx8oGG7CuktUhWu5JPivL6qjECow==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.7.0(react-dom@18.2.0)(react@18.2.0) + classcat: 5.0.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + /@reactflow/core@11.4.0-next.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-OgHMl9qs7ZMidoc+pUcZ4O1TxszrpW0jcb2tZQOfB5WpJL40HmwXrGYZdk9IhG1ANo4N0nwS5MBvho2Ddo7aSw==} peerDependencies: @@ -3406,6 +3420,27 @@ packages: - immer dev: false + /@reactflow/core@11.7.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UJcpbNRSupSSoMWh5UmRp6UUr0ug7xVKmMvadnkKKiNi9584q57nz4HMfkqwN3/ESbre7LD043yh2n678d/5FQ==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@types/d3': 7.4.0 + '@types/d3-drag': 3.0.2 + '@types/d3-selection': 3.0.5 + '@types/d3-zoom': 3.0.2 + classcat: 5.0.4 + d3-drag: 3.0.0 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + zustand: 4.3.6(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + /@reactflow/minimap@11.3.0-next.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-ZNo6oLTKSLHO/rdfribRO5pmBMWT8Y5Hbn5zKkzJgjPKmaa7sG6ZjuOJNBz4LuKy7GrP5Uu2wGSc8svxNvYogA==} peerDependencies: @@ -3426,6 +3461,25 @@ packages: - immer dev: false + /@reactflow/minimap@11.5.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-n/3tlaknLpi3zaqCC+tDDPvUTOjd6jglto9V3RB1F2wlaUEbCwmuoR2GYTkiRyZMvuskKyAoQW8+0DX0+cWwsA==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.7.0(react-dom@18.2.0)(react@18.2.0) + '@types/d3-selection': 3.0.5 + '@types/d3-zoom': 3.0.2 + classcat: 5.0.4 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + zustand: 4.3.6(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + /@reactflow/node-resizer@2.1.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-DVL8nnWsltP8/iANadAcTaDB4wsEkx2mOLlBEPNE3yc5loSm3u9l5m4enXRcBym61MiMuTtDPzZMyYYQUjuYIg==} peerDependencies: @@ -3459,6 +3513,21 @@ packages: - immer dev: false + /@reactflow/node-toolbar@1.1.11(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-+hKtx+cvXwfCa9paGxE+G34rWRIIVEh68ZOqAtivClVmfqGzH/sEoGWtIOUyg9OEDNE1nEmZ1NrnpBGSmHHXFg==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.7.0(react-dom@18.2.0)(react@18.2.0) + classcat: 5.0.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + zustand: 4.3.6(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + /@reduxjs/toolkit@1.9.3(react-redux@8.0.5)(react@18.2.0): resolution: {integrity: sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==} peerDependencies: @@ -3650,7 +3719,7 @@ packages: '@storybook/csf-plugin': 7.0.0-rc.5 '@storybook/csf-tools': 7.0.0-rc.5 '@storybook/global': 5.0.0 - '@storybook/mdx2-csf': 1.0.0-next.6 + '@storybook/mdx2-csf': 1.1.0-next.1 '@storybook/node-logger': 7.0.0-rc.5 '@storybook/postinstall': 7.0.0-rc.5 '@storybook/preview-api': 7.0.0-rc.5 @@ -3732,8 +3801,8 @@ packages: - supports-color dev: true - /@storybook/addon-links@7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-MyfufFES3iNf/6fP/nTgB8op3v5jUWPvguo/L8mREUk2LfjB8KNmQuJolM4RzrKOl6cOcD6vZlqaVkuLiOSPYA==} + /@storybook/addon-links@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-DEjDxjHb3mT8Sdnx4In5Ev9gJ/XdjlHOq4iuy0wnMyrCV4wnzTQnIeSCx8nkrXFb314zc33JPnCcrb5pQoD5GQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -3743,14 +3812,14 @@ packages: react-dom: optional: true dependencies: - '@storybook/client-logger': 7.0.0-rc.5 - '@storybook/core-events': 7.0.0-rc.5 - '@storybook/csf': 0.0.2-next.11 + '@storybook/client-logger': 7.0.7 + '@storybook/core-events': 7.0.7 + '@storybook/csf': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/manager-api': 7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0) - '@storybook/preview-api': 7.0.0-rc.5 - '@storybook/router': 7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0) - '@storybook/types': 7.0.0-rc.5 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/router': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -3802,7 +3871,7 @@ packages: ts-dedent: 2.2.0 dev: true - /@storybook/addon-styling@0.3.2(@storybook/addons@6.5.16)(@storybook/api@6.5.16)(@storybook/components@6.5.16)(@storybook/core-events@6.5.16)(@storybook/manager-api@7.0.0-rc.5)(@storybook/theming@6.5.16)(react-dom@18.2.0)(react@18.2.0)(sass-loader@13.2.2): + /@storybook/addon-styling@0.3.2(@storybook/addons@6.5.16)(@storybook/api@6.5.16)(@storybook/components@6.5.16)(@storybook/core-events@6.5.16)(@storybook/manager-api@7.0.7)(@storybook/theming@6.5.16)(react-dom@18.2.0)(react@18.2.0)(sass-loader@13.2.2): resolution: {integrity: sha512-ztKy9uU2yKBtvBp4/Km4LD1JCNNFHpXS33LjbeIfho0toRv100g8tUojrdnoRX1b2KVK6cqep5mJV0z2ak9hIQ==} peerDependencies: '@storybook/addons': ^6.5.8 @@ -3829,13 +3898,50 @@ packages: '@storybook/api': 6.5.16(react-dom@18.2.0)(react@18.2.0) '@storybook/components': 6.5.16(react-dom@18.2.0)(react@18.2.0) '@storybook/core-events': 6.5.16 - '@storybook/manager-api': 7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0) + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) '@storybook/theming': 6.5.16(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) sass-loader: 13.2.2(sass@1.59.3)(webpack@5.77.0) dev: true + /@storybook/addon-styling@1.0.5(less@4.1.3)(postcss@8.4.21)(react-dom@18.2.0)(react@18.2.0)(sass@1.59.3)(webpack@5.77.0): + resolution: {integrity: sha512-Vh+kzYJnCZOd5FGAXZ4Z0t+c5UXEkSAI1BN0SGu6ps1579uDkH3byWVq+hTEVo2lh0YgSzjs5rppUIoEVQYAzw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + dependencies: + '@storybook/api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/components': 7.0.5(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.7 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/node-logger': 7.0.7 + '@storybook/preview-api': 7.0.7 + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 + css-loader: 6.7.3(webpack@5.77.0) + less-loader: 11.1.0(less@4.1.3)(webpack@5.77.0) + postcss-loader: 7.3.0(postcss@8.4.21)(webpack@5.77.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + resolve-url-loader: 5.0.0 + sass-loader: 13.2.2(sass@1.59.3)(webpack@5.77.0) + style-loader: 3.3.2(webpack@5.77.0) + transitivePeerDependencies: + - fibers + - less + - node-sass + - postcss + - sass + - sass-embedded + - webpack + dev: true + /@storybook/addon-toolbars@7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-GErLxEBVh3HaQEvUNmKlNDcNuEYpGNVT1Nr1Tsc4J8EKG1ivEfQfVu6/5fduPZE8Vt1IUAzrVEp9NYzSELH49Q==} peerDependencies: @@ -3928,6 +4034,23 @@ packages: util-deprecate: 1.0.2 dev: true + /@storybook/api@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-0++LcK6PX1Z2HsI9fyZyqvmeFrB5NDMcsbmIvJfA2NfK92UW8y7t6Ft2fq/2jUCJcWT8Jp3xpatUvYb28irfwg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + dependencies: + '@storybook/client-logger': 7.0.7 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /@storybook/blocks@7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-9ZGDExwA6DgR/BsFSk2aCe7p/AIIQAiCemV1W1Djp7lt6OOALWfLZ7r1sFUqY9ZgNkfD1N41JpmqJtPDLXejGQ==} peerDependencies: @@ -3962,58 +4085,88 @@ packages: - supports-color dev: true - /@storybook/builder-manager@7.0.0-rc.5: - resolution: {integrity: sha512-VAtkM9HT9OUl06n/GHzdrcgr2zf1DULbzayy6jJ8HxDHQe8ONM2fe+v8gF0NAcmtEi0i9C381sS7P3pnNAK4Qw==} + /@storybook/blocks@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-ehR0hAFWNHHqmrmbwYPKhLpgbIBKtyMbeoGClTRSnrVBGONciYJdmxegkCTReUklCY+HBJjtlwNowT+7+5sSaw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@storybook/channels': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/core-events': 7.0.7 + '@storybook/csf': 0.1.0 + '@storybook/docs-tools': 7.0.7 + '@storybook/global': 5.0.0 + '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/preview-api': 7.0.7 + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 + '@types/lodash': 4.14.191 + color-convert: 2.0.1 + dequal: 2.0.3 + lodash: 4.17.21 + markdown-to-jsx: 7.2.0(react@18.2.0) + memoizerific: 1.11.3 + polished: 4.2.2 + react: 18.2.0 + react-colorful: 5.6.1(react-dom@18.2.0)(react@18.2.0) + react-dom: 18.2.0(react@18.2.0) + telejson: 7.0.4 + ts-dedent: 2.2.0 + util-deprecate: 1.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@storybook/builder-manager@7.0.7: + resolution: {integrity: sha512-VI/0iEjAlzQDt1yKu8GXugNIz7t46IHIKgMNmltQ05KPypMgInUoMmbfP5AYOVddjLdSqjMLO7EK58pBLOInpw==} dependencies: '@fal-works/esbuild-plugin-global-externals': 2.1.2 - '@storybook/core-common': 7.0.0-rc.5 - '@storybook/manager': 7.0.0-rc.5 - '@storybook/node-logger': 7.0.0-rc.5 + '@storybook/core-common': 7.0.7 + '@storybook/manager': 7.0.7 + '@storybook/node-logger': 7.0.7 '@types/ejs': 3.1.2 '@types/find-cache-dir': 3.2.1 - '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.16.17) + '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.17.12) browser-assert: 1.2.1 ejs: 3.1.9 - esbuild: 0.16.17 + esbuild: 0.17.12 esbuild-plugin-alias: 0.2.1 express: 4.18.2 find-cache-dir: 3.3.2 fs-extra: 11.1.1 process: 0.11.10 - slash: 3.0.0 util: 0.12.5 transitivePeerDependencies: - supports-color dev: true - /@storybook/builder-vite@7.0.0-rc.5(typescript@4.9.5)(vite@4.2.1): - resolution: {integrity: sha512-TkT+KaUBHiyFyHQ31qeq3zFM1p5cwffu3orAJRcSWOCHkQy1hbu2H55ZApgZJRHBq9MGxtUZ1FTVYIb3OLv1jg==} + /@storybook/builder-vite@7.0.7(typescript@4.9.5)(vite@4.2.1): + resolution: {integrity: sha512-2wL6fsFWzij+R155urOLc7EjZtlVWf4FLfaSlLGAuZwRQU40N04YdMaHMp9tjd9Vdr5fxEDwTB51PnVWJMlsEw==} peerDependencies: '@preact/preset-vite': '*' - '@storybook/mdx1-csf': '>=1.0.0-next.1' typescript: '>= 4.3.x' vite: ^3.0.0 || ^4.0.0 vite-plugin-glimmerx: '*' peerDependenciesMeta: '@preact/preset-vite': optional: true - '@storybook/mdx1-csf': - optional: true typescript: optional: true vite-plugin-glimmerx: optional: true dependencies: - '@storybook/channel-postmessage': 7.0.0-rc.5 - '@storybook/channel-websocket': 7.0.0-rc.5 - '@storybook/client-logger': 7.0.0-rc.5 - '@storybook/core-common': 7.0.0-rc.5 - '@storybook/csf-plugin': 7.0.0-rc.5 - '@storybook/mdx2-csf': 1.0.0-next.6 - '@storybook/node-logger': 7.0.0-rc.5 - '@storybook/preview': 7.0.0-rc.5 - '@storybook/preview-api': 7.0.0-rc.5 - '@storybook/types': 7.0.0-rc.5 + '@storybook/channel-postmessage': 7.0.7 + '@storybook/channel-websocket': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/core-common': 7.0.7 + '@storybook/csf-plugin': 7.0.7 + '@storybook/mdx2-csf': 1.0.0 + '@storybook/node-logger': 7.0.7 + '@storybook/preview': 7.0.7 + '@storybook/preview-api': 7.0.7 + '@storybook/types': 7.0.7 browser-assert: 1.2.1 es-module-lexer: 0.9.3 express: 4.18.2 @@ -4021,41 +4174,42 @@ packages: glob: 8.1.0 glob-promise: 6.0.2(glob@8.1.0) magic-string: 0.27.0 + remark-external-links: 8.0.0 + remark-slug: 6.1.0 rollup: 3.20.0 - slash: 3.0.0 typescript: 4.9.5 - vite: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) transitivePeerDependencies: - supports-color dev: true - /@storybook/channel-postmessage@7.0.0: - resolution: {integrity: sha512-Sy3oHL/xDRjUiHnM0ncnkbOE5pK3O72MjOoiLJX4FCI90w03KM4+F/N0eU2cXl6yXHuCyI5eJisEzQxTNsaJiw==} + /@storybook/channel-postmessage@7.0.0-rc.5: + resolution: {integrity: sha512-NBnIKiACAnLpsVe7bf9B2XE4tH+4HgTJh8Mvj1Dpu1jxu2cJ3j20x3IGgELXCXSEicUbXCqr+O1Zc7CHBXYV+g==} dependencies: - '@storybook/channels': 7.0.0 - '@storybook/client-logger': 7.0.0 - '@storybook/core-events': 7.0.0 + '@storybook/channels': 7.0.0-rc.5 + '@storybook/client-logger': 7.0.0-rc.5 + '@storybook/core-events': 7.0.0-rc.5 '@storybook/global': 5.0.0 qs: 6.11.1 telejson: 7.0.4 dev: true - /@storybook/channel-postmessage@7.0.0-rc.5: - resolution: {integrity: sha512-NBnIKiACAnLpsVe7bf9B2XE4tH+4HgTJh8Mvj1Dpu1jxu2cJ3j20x3IGgELXCXSEicUbXCqr+O1Zc7CHBXYV+g==} + /@storybook/channel-postmessage@7.0.7: + resolution: {integrity: sha512-XMtYfcaE0UoY/V7K1cTu9PcWETD4iyWb/Yswc4F9VrPw0Ui4UwGS1j4iaAu8DC06yyoJs4XvxYFBMlCQmKja6A==} dependencies: - '@storybook/channels': 7.0.0-rc.5 - '@storybook/client-logger': 7.0.0-rc.5 - '@storybook/core-events': 7.0.0-rc.5 + '@storybook/channels': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/core-events': 7.0.7 '@storybook/global': 5.0.0 qs: 6.11.1 telejson: 7.0.4 dev: true - /@storybook/channel-websocket@7.0.0-rc.5: - resolution: {integrity: sha512-n8oPrbxGS9FtSkNWYMpOtEZedeeVxnxJuiEwApGRkWt0q3eWIK9u24NElIbjCoSysaZl60CXrlK615W+Ml3ujQ==} + /@storybook/channel-websocket@7.0.7: + resolution: {integrity: sha512-KDbLiQts4/dCow3qk5WJSPA6SlaX3iP9RhF0Fjj03hoG2TRskrvo+AkUiJr8gF6dpkPndfuCYUCRsO2Ml8B+AA==} dependencies: - '@storybook/channels': 7.0.0-rc.5 - '@storybook/client-logger': 7.0.0-rc.5 + '@storybook/channels': 7.0.7 + '@storybook/client-logger': 7.0.7 '@storybook/global': 5.0.0 telejson: 7.0.4 dev: true @@ -4068,28 +4222,32 @@ packages: util-deprecate: 1.0.2 dev: true - /@storybook/channels@7.0.0: - resolution: {integrity: sha512-adPIkvL4q37dGTWCpSzV8ETLdkxsg7BAgzeT9pustZJjRIZqAHGUAm7krDtGT7jbV4dU0Zw0VpUrnmyfxIkOKQ==} - dev: true - /@storybook/channels@7.0.0-rc.5: resolution: {integrity: sha512-/T4iJQsTj42bs+d2sG8aLyInKh1IjZeK0vPoJRK9gvy3YfxTj3yodZ60s2yywKJCgGjg5zJMFxYMWqSVmHIdnw==} dev: true - /@storybook/cli@7.0.0-rc.5: - resolution: {integrity: sha512-v0gCsKM2NtNBkhJJ4ZXQcNyasKj8zJxW0KRWpfrECe04ko7wuN8MCsJIZAE4AWQnmtx7OWWVYNrzfTFUEVTs6A==} + /@storybook/channels@7.0.5: + resolution: {integrity: sha512-WiSPXgOK63jAlDDmbTs1sVXoYe3r/4VjpfwhEcxSPU544YQVARF1ePtiGjlp8HVFhZh1Q7afbVGJ9w96++u98A==} + dev: true + + /@storybook/channels@7.0.7: + resolution: {integrity: sha512-Om4ovBLNw8pVrBu83MpOKgAuGO9Dpr1Coh2qp8t64WRPkejX1mxOY9IgH723//zH3igx8LCkf9rvBvcrsyaScQ==} + dev: true + + /@storybook/cli@7.0.7: + resolution: {integrity: sha512-koTkWr7wlaHF14T5moRP/tYM44+Jf4GEzQ/rqx/Jfn7EbNlVUOibdLJj4JnseMGRc7ZP6tKYku2n+B8g7hJX4w==} hasBin: true dependencies: '@babel/core': 7.21.3 - '@babel/preset-env': 7.20.2(@babel/core@7.21.3) + '@babel/preset-env': 7.21.4(@babel/core@7.21.3) '@ndelangen/get-tarball': 3.0.7 - '@storybook/codemod': 7.0.0-rc.5 - '@storybook/core-common': 7.0.0-rc.5 - '@storybook/core-server': 7.0.0-rc.5 - '@storybook/csf-tools': 7.0.0-rc.5 - '@storybook/node-logger': 7.0.0-rc.5 - '@storybook/telemetry': 7.0.0-rc.5 - '@storybook/types': 7.0.0-rc.5 + '@storybook/codemod': 7.0.7 + '@storybook/core-common': 7.0.7 + '@storybook/core-server': 7.0.7 + '@storybook/csf-tools': 7.0.7 + '@storybook/node-logger': 7.0.7 + '@storybook/telemetry': 7.0.7 + '@storybook/types': 7.0.7 '@types/semver': 7.3.13 boxen: 5.1.2 chalk: 4.1.2 @@ -4105,9 +4263,9 @@ packages: get-port: 5.1.1 giget: 1.1.2 globby: 11.1.0 - jscodeshift: 0.14.0(@babel/preset-env@7.20.2) + jscodeshift: 0.14.0(@babel/preset-env@7.21.4) leven: 3.1.0 - prettier: 2.8.7 + prettier: 2.8.8 prompts: 2.4.2 puppeteer-core: 2.1.1 read-pkg-up: 7.0.1 @@ -4132,33 +4290,39 @@ packages: global: 4.4.0 dev: true - /@storybook/client-logger@7.0.0: - resolution: {integrity: sha512-wRZZiPta37DFc8SVZ8Q3ZqyTrs5qgO6bcCuVDRLQAcO0Oz4xKEVPEVfVVxSPZU/+p2ypqdBBCP2pdL/Jy86AJg==} + /@storybook/client-logger@7.0.0-rc.5: + resolution: {integrity: sha512-YkqjJb2jK6/jT4zm9cmdMVZeOyzoDxiyK3BedhoXKMRDMz+7+E7tcOZEXsuvTGekJe459TTnwYLfvUvObaXNKw==} dependencies: '@storybook/global': 5.0.0 dev: true - /@storybook/client-logger@7.0.0-rc.5: - resolution: {integrity: sha512-YkqjJb2jK6/jT4zm9cmdMVZeOyzoDxiyK3BedhoXKMRDMz+7+E7tcOZEXsuvTGekJe459TTnwYLfvUvObaXNKw==} + /@storybook/client-logger@7.0.5: + resolution: {integrity: sha512-p8Vtb5G/l3gePNDbNjqgGsikthRqDfsPAqFEsAvBWJVZ3vq/ZSU4IsCWSLO/kdkyJyhTXMqQZnOpQ0pDXlOPcQ==} + dependencies: + '@storybook/global': 5.0.0 + dev: true + + /@storybook/client-logger@7.0.7: + resolution: {integrity: sha512-EclHjDs5HwHMKB4X2orn/KKA0DTIDmp4AXAUJGRfxb5ArpKEb7tXLHsgrRBlaoz1j5LAwKTmEyZOONh9G3etjg==} dependencies: '@storybook/global': 5.0.0 dev: true - /@storybook/codemod@7.0.0-rc.5: - resolution: {integrity: sha512-aiW7PeU9rZE9wp6tNxLxloAsfVNzeG8pI0HJrj1JALhvaPzlCphdMP8Cf2UT0a4ADjpmYQSsGX301XFgMQYFKA==} + /@storybook/codemod@7.0.7: + resolution: {integrity: sha512-VlkDlkvfbzLe+NOmzs5zGrGb4jnaeAFZqpvIkXxevr6aGcOwgeelNv8gTmgBAcy+xbGW4Pp0XA2BlMweIvKEKA==} dependencies: '@babel/core': 7.21.3 - '@babel/preset-env': 7.20.2(@babel/core@7.21.3) - '@babel/types': 7.21.3 - '@storybook/csf': 0.0.2-next.11 - '@storybook/csf-tools': 7.0.0-rc.5 - '@storybook/node-logger': 7.0.0-rc.5 - '@storybook/types': 7.0.0-rc.5 + '@babel/preset-env': 7.21.4(@babel/core@7.21.3) + '@babel/types': 7.21.4 + '@storybook/csf': 0.1.0 + '@storybook/csf-tools': 7.0.7 + '@storybook/node-logger': 7.0.7 + '@storybook/types': 7.0.7 cross-spawn: 7.0.3 globby: 11.1.0 - jscodeshift: 0.14.0(@babel/preset-env@7.20.2) + jscodeshift: 0.14.0(@babel/preset-env@7.21.4) lodash: 4.17.21 - prettier: 2.8.7 + prettier: 2.8.8 recast: 0.23.1 transitivePeerDependencies: - supports-color @@ -4200,6 +4364,42 @@ packages: util-deprecate: 1.0.2 dev: true + /@storybook/components@7.0.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-SHftxNH3FG3RZwJ5nbyBZwn5pkI3Ei2xjD7zDwxztI8bCp5hPnOTDwAnQZZCkeW7atSQUe7xFkYqlCgNmXR4PQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@storybook/client-logger': 7.0.5 + '@storybook/csf': 0.1.0 + '@storybook/global': 5.0.0 + '@storybook/theming': 7.0.5(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.5 + memoizerific: 1.11.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + use-resize-observer: 9.1.0(react-dom@18.2.0)(react@18.2.0) + util-deprecate: 1.0.2 + dev: true + + /@storybook/components@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-6PLs9LMkBuhH/w4bSJ72tYgICMbOOIHuoB/fQdVlzhsdnXL2fM/v4RVW2N7v+Oz3lYXp/JtV8V9Ub8h6eDQKXg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@storybook/client-logger': 7.0.7 + '@storybook/csf': 0.1.0 + '@storybook/global': 5.0.0 + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 + memoizerific: 1.11.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + use-resize-observer: 9.1.0(react-dom@18.2.0)(react@18.2.0) + util-deprecate: 1.0.2 + dev: true + /@storybook/core-client@7.0.0-rc.5: resolution: {integrity: sha512-jBY4kJDL5sdVcnGzz+cpruzkF01Hi+DJ/c9mpNiL+CjiDSFewtCk28Qggwccm9tKne5eAlrFiJAu5MOlbIcM+g==} dependencies: @@ -4207,6 +4407,13 @@ packages: '@storybook/preview-api': 7.0.0-rc.5 dev: true + /@storybook/core-client@7.0.7: + resolution: {integrity: sha512-eydcpR28qV3A3BwR5V6wsixoI1BRLA0SzFiwH/1ajrgX13inv+gV97gHv47Ojf/+YAZ3HqdVaUKFsUfMKwKieA==} + dependencies: + '@storybook/client-logger': 7.0.7 + '@storybook/preview-api': 7.0.7 + dev: true + /@storybook/core-common@7.0.0-rc.5: resolution: {integrity: sha512-YlkcTcDx8bkOq3/STAuBkQOBQB5i0zLj2Zb0LUPzIDDBPZlGb3mJEla0UyJoMbP/E/QCq1K8pA1l9vtTK+ROJQ==} dependencies: @@ -4234,37 +4441,63 @@ packages: - supports-color dev: true + /@storybook/core-common@7.0.7: + resolution: {integrity: sha512-c8T24wex9bnCYdZVZFNX4VV+wfhrp47OLzVONZDqxMhq6G//Bgv5zH4Awcx5UfWf/05VcP7KGF1VKj8ebRyEEA==} + dependencies: + '@storybook/node-logger': 7.0.7 + '@storybook/types': 7.0.7 + '@types/node': 16.18.16 + '@types/pretty-hrtime': 1.0.1 + chalk: 4.1.2 + esbuild: 0.17.12 + esbuild-register: 3.4.2(esbuild@0.17.12) + file-system-cache: 2.0.2 + find-up: 5.0.0 + fs-extra: 11.1.1 + glob: 8.1.0 + glob-promise: 6.0.2(glob@8.1.0) + handlebars: 4.7.7 + lazy-universal-dotenv: 4.0.0 + picomatch: 2.3.1 + pkg-dir: 5.0.0 + pretty-hrtime: 1.0.3 + resolve-from: 5.0.0 + ts-dedent: 2.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /@storybook/core-events@6.5.16: resolution: {integrity: sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g==} dependencies: core-js: 3.29.1 dev: true - /@storybook/core-events@7.0.0: - resolution: {integrity: sha512-pxzNmgEI1p90bHyAYABHDDtB2XM5pffq6CqIHboK6aSCux7Cdc16IjOYq6BJIhCKaaI+qQHaFLR4JfaFAsxwQQ==} - dev: true - /@storybook/core-events@7.0.0-rc.5: resolution: {integrity: sha512-n9+TqgrgkXN5V+mNdgdnojUVqhKOsyL3DNfOmAsbLEewhg5z6+QDYxOe/FBe1usGI2DV+ihwb/knMZzuYXN5ow==} dev: true - /@storybook/core-server@7.0.0-rc.5: - resolution: {integrity: sha512-rif4CTnaExe+GLNv2wWRkqJPNn1WN+1ZWv/YLjYUjR2zGeLQn26/XLUKVn5CCQ4DzHbqWwfMaxlPKhNAeZyodw==} + /@storybook/core-events@7.0.7: + resolution: {integrity: sha512-XNsR2RgaL2vBwuqsu+KA1DzGmB1UFfrAhpxhmyWTKDCniwtTLlaXgfKbqwcrOrPu/o1YswgIup/9UHepRHaf4A==} + dev: true + + /@storybook/core-server@7.0.7: + resolution: {integrity: sha512-PB4zoClH7aKG4XeJhxx43iK9n/C9gctXubNN5DSN6thPm4UITOas+/q4N7AHbCPyRbcMyoW7M31KtpzZu4Fjew==} dependencies: '@aw-web-design/x-default-browser': 1.4.88 '@discoveryjs/json-ext': 0.5.7 - '@storybook/builder-manager': 7.0.0-rc.5 - '@storybook/core-common': 7.0.0-rc.5 - '@storybook/core-events': 7.0.0-rc.5 - '@storybook/csf': 0.0.2-next.11 - '@storybook/csf-tools': 7.0.0-rc.5 - '@storybook/docs-mdx': 0.0.1-next.6 + '@storybook/builder-manager': 7.0.7 + '@storybook/core-common': 7.0.7 + '@storybook/core-events': 7.0.7 + '@storybook/csf': 0.1.0 + '@storybook/csf-tools': 7.0.7 + '@storybook/docs-mdx': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/manager': 7.0.0-rc.5 - '@storybook/node-logger': 7.0.0-rc.5 - '@storybook/preview-api': 7.0.0-rc.5 - '@storybook/telemetry': 7.0.0-rc.5 - '@storybook/types': 7.0.0-rc.5 + '@storybook/manager': 7.0.7 + '@storybook/node-logger': 7.0.7 + '@storybook/preview-api': 7.0.7 + '@storybook/telemetry': 7.0.7 + '@storybook/types': 7.0.7 '@types/detect-port': 1.3.2 '@types/node': 16.18.16 '@types/node-fetch': 2.6.2 @@ -4288,7 +4521,6 @@ packages: read-pkg-up: 7.0.1 semver: 7.3.8 serve-favicon: 2.5.0 - slash: 3.0.0 telejson: 7.0.4 ts-dedent: 2.2.0 util-deprecate: 1.0.2 @@ -4310,13 +4542,22 @@ packages: - supports-color dev: true + /@storybook/csf-plugin@7.0.7: + resolution: {integrity: sha512-uhf2g077gXA6ZEMXIPQ0RnX+IoOTBJbj+6+VQfT7K5tvJeop1z0Fvk0FoknNXcUe7aUA0nzA/cUQ1v4vXqbY3Q==} + dependencies: + '@storybook/csf-tools': 7.0.7 + unplugin: 0.10.2 + transitivePeerDependencies: + - supports-color + dev: true + /@storybook/csf-tools@7.0.0-rc.5: resolution: {integrity: sha512-DvcAygIZMZIL30j7WxMXeJ6a+A2/Y/FuatZItmW+3sNv0FK1J9wH2SKw7QjzEw75LsgjvO07lU2cgcsPDFhXoA==} dependencies: '@babel/generator': 7.21.3 '@babel/parser': 7.21.3 '@babel/traverse': 7.21.3(supports-color@5.5.0) - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 '@storybook/csf': 0.0.2-next.11 '@storybook/types': 7.0.0-rc.5 fs-extra: 11.1.1 @@ -4326,6 +4567,22 @@ packages: - supports-color dev: true + /@storybook/csf-tools@7.0.7: + resolution: {integrity: sha512-KbO5K2RS0oFm94eR49bAPvoyXY3Q6+ozvBek/F05RP7iAV790icQc59Xci9YDM1ONgb3afS+gSJGFBsE0h4pmg==} + dependencies: + '@babel/generator': 7.21.3 + '@babel/parser': 7.21.3 + '@babel/traverse': 7.21.3(supports-color@5.5.0) + '@babel/types': 7.21.4 + '@storybook/csf': 0.1.0 + '@storybook/types': 7.0.7 + fs-extra: 11.1.1 + recast: 0.23.1 + ts-dedent: 2.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /@storybook/csf@0.0.2--canary.4566f4d.1: resolution: {integrity: sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ==} dependencies: @@ -4338,8 +4595,14 @@ packages: type-fest: 2.19.0 dev: true - /@storybook/docs-mdx@0.0.1-next.6: - resolution: {integrity: sha512-DjoSIXADmLJtdroXAjUotFiZlcZ2usWhqrS7aeOtZs0DVR0Ws5WQjnwtpDUXt8gryTSd+OZJ0cNsDcqg4JDEvQ==} + /@storybook/csf@0.1.0: + resolution: {integrity: sha512-uk+jMXCZ8t38jSTHk2o5btI+aV2Ksbvl6DoOv3r6VaCM1KZqeuMwtwywIQdflkA8/6q/dKT8z8L+g8hC4GC3VQ==} + dependencies: + type-fest: 2.19.0 + dev: true + + /@storybook/docs-mdx@0.1.0: + resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==} dev: true /@storybook/docs-tools@7.0.0-rc.5: @@ -4356,18 +4619,22 @@ packages: - supports-color dev: true - /@storybook/global@5.0.0: - resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} + /@storybook/docs-tools@7.0.7: + resolution: {integrity: sha512-VB4Qa33DYNxXALWcXyDid246r9Q6SGM+Q+pGWOuEJsxRxDmrUspXHaHG0CO1NIjMWfbqpOoz61vquZO0GZoAAg==} + dependencies: + '@babel/core': 7.21.3 + '@storybook/core-common': 7.0.7 + '@storybook/preview-api': 7.0.7 + '@storybook/types': 7.0.7 + '@types/doctrine': 0.0.3 + doctrine: 3.0.0 + lodash: 4.17.21 + transitivePeerDependencies: + - supports-color dev: true - /@storybook/instrumenter@7.0.0: - resolution: {integrity: sha512-A7jBrV7VM3OxRgall8rpjagy3VC78A/OV1g1aYVVLpAF/+Odj+MeHHF179+fR6JBLnBgukNfsG7/ZHHGs0gL5Q==} - dependencies: - '@storybook/channels': 7.0.0 - '@storybook/client-logger': 7.0.0 - '@storybook/core-events': 7.0.0 - '@storybook/global': 5.0.0 - '@storybook/preview-api': 7.0.0 + /@storybook/global@5.0.0: + resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} dev: true /@storybook/instrumenter@7.0.0-rc.5: @@ -4380,6 +4647,16 @@ packages: '@storybook/preview-api': 7.0.0-rc.5 dev: true + /@storybook/instrumenter@7.0.7: + resolution: {integrity: sha512-0zE5lM3laKvCT4GW/XKKw8kakvI4catqK8PObZolRhfxbtGufW4VJZ2E8vXLtgA/+K3zikypjuWE6d45NLbh9w==} + dependencies: + '@storybook/channels': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/core-events': 7.0.7 + '@storybook/global': 5.0.0 + '@storybook/preview-api': 7.0.7 + dev: true + /@storybook/manager-api@7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-MsNj/cPIOlL7HJ8ReYahUvJVfvZDtNfacUYSFuQjQwdnp0u3pbC5mGZPd32tAGj7lLaLzcqqo1yR+NAgwpZUBw==} peerDependencies: @@ -4405,12 +4682,41 @@ packages: ts-dedent: 2.2.0 dev: true - /@storybook/manager@7.0.0-rc.5: - resolution: {integrity: sha512-VPTIYzcKguKaA+4HGFPAPRdDGTZ8fxKAKL71VgP+AOOJhNmdTWHCXs8Yu7GMg/2uyRZ2zSKiOeBESh+Qb8pRZg==} + /@storybook/manager-api@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-QTd/P72peAhofKqK+8yzIO9iWAEfPn8WUGGveV2KGaTlSlgbr87RLHEKilcXMZcYhBWC9izFRmjKum9ROdskrQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@storybook/channels': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/core-events': 7.0.7 + '@storybook/csf': 0.1.0 + '@storybook/global': 5.0.0 + '@storybook/router': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 + dequal: 2.0.3 + lodash: 4.17.21 + memoizerific: 1.11.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + semver: 7.3.8 + store2: 2.14.2 + telejson: 7.0.4 + ts-dedent: 2.2.0 dev: true - /@storybook/mdx2-csf@1.0.0-next.6: - resolution: {integrity: sha512-m6plojocU/rmrqWd26yvm8D+oHZPZ6PtSSFmZIgpNDEPVmc8s4fBD6LXOAB5MiPI5f8KLUr2HVhOMZ97o5pDTw==} + /@storybook/manager@7.0.7: + resolution: {integrity: sha512-FhquwUpUOHsjZROf6E6kzUnJ6EmMeJ9b+HYg6yYPyIMYVMjAhnkRKbIj0phGx2lhgKFlmxik+3pgchK5SLdcZA==} + dev: true + + /@storybook/mdx2-csf@1.0.0: + resolution: {integrity: sha512-dBAnEL4HfxxJmv7LdEYUoZlQbWj9APZNIbOaq0tgF8XkxiIbzqvgB0jhL/9UOrysSDbQWBiCRTu2wOVxedGfmw==} + dev: true + + /@storybook/mdx2-csf@1.1.0-next.1: + resolution: {integrity: sha512-ONvFBZySHsBIkUYGrUM8FCG2tDKf663TIErztPSOghOpmBGyFLjSsXJHkNWiRi4c740PoemLqJd2XZZVlXRVLQ==} dev: true /@storybook/node-logger@7.0.0-rc.5: @@ -4422,6 +4728,15 @@ packages: pretty-hrtime: 1.0.3 dev: true + /@storybook/node-logger@7.0.7: + resolution: {integrity: sha512-5Y4LLgKeCStq1ktCKZ5eNPzQQSQ+CYZAlkEdzQ3Pp//0KXaZvVxEvGtaYhAymP2HatLpI8Oneo4lHrJioRfgww==} + dependencies: + '@types/npmlog': 4.1.4 + chalk: 4.1.2 + npmlog: 5.0.1 + pretty-hrtime: 1.0.3 + dev: true + /@storybook/postinstall@7.0.0-rc.5: resolution: {integrity: sha512-F23wxKEJ2XoVnHT7oAMjCXtANWvNq7M+FmIowgI98b3FT1dxt9fFPKKY+3Lcqp0Xa6Pzezd03KR9vAxXvvK/iQ==} dev: true @@ -4438,53 +4753,63 @@ packages: style-loader: 3.3.2(webpack@5.77.0) dev: true - /@storybook/preview-api@7.0.0: - resolution: {integrity: sha512-Q0IYYH1gOmx42ClYlQfQPjuERBWM3Ey+3DFsLQaraKXDdgZ9wN7jPNuS7wxuUNylT0oa/3WjxT7qNfiGw8JtBw==} + /@storybook/preview-api@7.0.0-rc.5: + resolution: {integrity: sha512-cuyFIT/4MXfoqN6d5AK/KH7Yp0cifuOmcBj4+9xrmrPK47m4F8eHZ/mX6rXE6rVFNsWv65Al5An6WCM1CDImJg==} dependencies: - '@storybook/channel-postmessage': 7.0.0 - '@storybook/channels': 7.0.0 - '@storybook/client-logger': 7.0.0 - '@storybook/core-events': 7.0.0 + '@storybook/channel-postmessage': 7.0.0-rc.5 + '@storybook/channels': 7.0.0-rc.5 + '@storybook/client-logger': 7.0.0-rc.5 + '@storybook/core-events': 7.0.0-rc.5 '@storybook/csf': 0.0.2-next.11 '@storybook/global': 5.0.0 - '@storybook/types': 7.0.0 + '@storybook/types': 7.0.0-rc.5 '@types/qs': 6.9.7 dequal: 2.0.3 lodash: 4.17.21 memoizerific: 1.11.3 qs: 6.11.1 + slash: 3.0.0 synchronous-promise: 2.0.17 ts-dedent: 2.2.0 util-deprecate: 1.0.2 dev: true - /@storybook/preview-api@7.0.0-rc.5: - resolution: {integrity: sha512-cuyFIT/4MXfoqN6d5AK/KH7Yp0cifuOmcBj4+9xrmrPK47m4F8eHZ/mX6rXE6rVFNsWv65Al5An6WCM1CDImJg==} + /@storybook/preview-api@7.0.7: + resolution: {integrity: sha512-R5pmGTodpu6hbwEg2RM2ulWtW3d426YzsisHrZJ+FT9lecWauN1y9xHCz7HdNzEFhT8r4YOa24L9ZS3mosZ7hA==} dependencies: - '@storybook/channel-postmessage': 7.0.0-rc.5 - '@storybook/channels': 7.0.0-rc.5 - '@storybook/client-logger': 7.0.0-rc.5 - '@storybook/core-events': 7.0.0-rc.5 - '@storybook/csf': 0.0.2-next.11 + '@storybook/channel-postmessage': 7.0.7 + '@storybook/channels': 7.0.7 + '@storybook/client-logger': 7.0.7 + '@storybook/core-events': 7.0.7 + '@storybook/csf': 0.1.0 '@storybook/global': 5.0.0 - '@storybook/types': 7.0.0-rc.5 + '@storybook/types': 7.0.7 '@types/qs': 6.9.7 dequal: 2.0.3 lodash: 4.17.21 memoizerific: 1.11.3 qs: 6.11.1 - slash: 3.0.0 synchronous-promise: 2.0.17 ts-dedent: 2.2.0 util-deprecate: 1.0.2 dev: true - /@storybook/preview@7.0.0-rc.5: - resolution: {integrity: sha512-AG6vg4dsHVjbNchC3eiDqwSKfUWyFXauYDLg+Ce4F47s98J5ly+mFAIY0Vo1mwao3CVHLk0SYt+vtuQZF52WAg==} + /@storybook/preview@7.0.7: + resolution: {integrity: sha512-uL3ZcFao6UvxiSxCIcXKFakxEr9Nn0lvu0zzC2yQCVepzA7a+GDr1cK5VbZ6Mez38CnOvBmb5pkCbgRqSf/oug==} + dev: true + + /@storybook/react-dom-shim@7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Hyx8Px1LLc3+WjIUW5hNFEsbQspONnyThxBCU7w0kAivpJn7vy26HFCHp4QA1FPU6CnJUl5dVxckj6bosv/Gqg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/react-dom-shim@7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Hyx8Px1LLc3+WjIUW5hNFEsbQspONnyThxBCU7w0kAivpJn7vy26HFCHp4QA1FPU6CnJUl5dVxckj6bosv/Gqg==} + /@storybook/react-dom-shim@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-INGwFeu9M+RzpvktSKuwy8Rk/70mXGqxxsb9lPtq7phmETvfpNX7GnLJqiVazTaQiB1DkB0iAPUsK2MNbBu+Kw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -4493,8 +4818,8 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/react-vite@7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5)(vite@4.2.1): - resolution: {integrity: sha512-IW2DYK6K115B7VKBvNMaSMVe3LWyFyFBgjby1N2/wfL5jkvrwRmYH4ag5+qn1e6HgxH6F+Wd9ryLhf8jaldgVQ==} + /@storybook/react-vite@7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5)(vite@4.2.1): + resolution: {integrity: sha512-RuWfP/kiLpuHdcF9dWUUp9SOGMmO0FJ0HGV5yAOhGmi8KmTzvc8zjC+hJjj+sSgn2n71BO8pG/zqGl16FwfwVQ==} engines: {node: '>=16'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -4503,18 +4828,17 @@ packages: dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@4.9.5)(vite@4.2.1) '@rollup/pluginutils': 4.2.1 - '@storybook/builder-vite': 7.0.0-rc.5(typescript@4.9.5)(vite@4.2.1) - '@storybook/react': 7.0.0-rc.5(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5) + '@storybook/builder-vite': 7.0.7(typescript@4.9.5)(vite@4.2.1) + '@storybook/react': 7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5) '@vitejs/plugin-react': 3.1.0(vite@4.2.1) ast-types: 0.14.2 magic-string: 0.27.0 react: 18.2.0 react-docgen: 6.0.0-alpha.3 react-dom: 18.2.0(react@18.2.0) - vite: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) transitivePeerDependencies: - '@preact/preset-vite' - - '@storybook/mdx1-csf' - supports-color - typescript - vite-plugin-glimmerx @@ -4559,6 +4883,45 @@ packages: - supports-color dev: true + /@storybook/react@7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@4.9.5): + resolution: {integrity: sha512-eEsIfAGumzo7KRi/WKFpn/PGFhwLv72oiEM/8l5MMX/6poIkiekunqJLfx2BoL4cCtiS4g7OYzOdWjN01DwVCg==} + engines: {node: '>=16.0.0'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@storybook/client-logger': 7.0.7 + '@storybook/core-client': 7.0.7 + '@storybook/docs-tools': 7.0.7 + '@storybook/global': 5.0.0 + '@storybook/preview-api': 7.0.7 + '@storybook/react-dom-shim': 7.0.7(react-dom@18.2.0)(react@18.2.0) + '@storybook/types': 7.0.7 + '@types/escodegen': 0.0.6 + '@types/estree': 0.0.51 + '@types/node': 16.18.16 + acorn: 7.4.1 + acorn-jsx: 5.3.2(acorn@7.4.1) + acorn-walk: 7.2.0 + escodegen: 2.0.0 + html-tags: 3.2.0 + lodash: 4.17.21 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-element-to-jsx-string: 15.0.0(react-dom@18.2.0)(react@18.2.0) + ts-dedent: 2.2.0 + type-fest: 2.19.0 + typescript: 4.9.5 + util-deprecate: 1.0.2 + transitivePeerDependencies: + - supports-color + dev: true + /@storybook/router@6.5.16(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ==} peerDependencies: @@ -4587,6 +4950,19 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@storybook/router@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-/lM8/NHQKeshfnC3ayFuO8Y9TCSHnCAPRhIsVxvanBzcj+ILbCIyZ+TspvB3hT4MbX/Ez+JR8VrMbjXIGwmH8w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@storybook/client-logger': 7.0.7 + memoizerific: 1.11.3 + qs: 6.11.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + /@storybook/semver@7.3.2: resolution: {integrity: sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==} engines: {node: '>=10'} @@ -4596,11 +4972,11 @@ packages: find-up: 4.1.0 dev: true - /@storybook/telemetry@7.0.0-rc.5: - resolution: {integrity: sha512-So3Tb+wanyM2KMT1lVv56Kt8VEaQqwt375lWOt3TLNj15L0NYLvCmMCBRsLhtaB4arLo+uwfR4lBs372Qu7w2A==} + /@storybook/telemetry@7.0.7: + resolution: {integrity: sha512-Ka6pwWr3sWs3A/6WQ0wsoSYzXx3Mhr7eByNZZKuuCu9jnw3I8AbIOqQX2iOVzaQBLZsvXEeqvYY8iZ+GuRbbGQ==} dependencies: - '@storybook/client-logger': 7.0.0-rc.5 - '@storybook/core-common': 7.0.0-rc.5 + '@storybook/client-logger': 7.0.7 + '@storybook/core-common': 7.0.7 chalk: 4.1.2 detect-package-manager: 2.0.1 fetch-retry: 5.0.4 @@ -4613,11 +4989,11 @@ packages: - supports-color dev: true - /@storybook/testing-library@0.0.14-next.1: - resolution: {integrity: sha512-1CAl40IKIhcPaCC4pYCG0b9IiYNymktfV/jTrX7ctquRY3akaN7f4A1SippVHosksft0M+rQTFE0ccfWW581fw==} + /@storybook/testing-library@0.1.0: + resolution: {integrity: sha512-g947f4LJZw3IluBhysMKLJXByAFiSxnGuooENqU+ZPt/GTrz1I9GDBlhmoTJahuFkVbwHvziAl/8riY2Re921g==} dependencies: - '@storybook/client-logger': 7.0.0 - '@storybook/instrumenter': 7.0.0 + '@storybook/client-logger': 7.0.7 + '@storybook/instrumenter': 7.0.7 '@testing-library/dom': 8.20.0 '@testing-library/user-event': 13.5.0(@testing-library/dom@8.20.0) ts-dedent: 2.2.0 @@ -4651,13 +5027,32 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /@storybook/types@7.0.0: - resolution: {integrity: sha512-eCMW/xTVMswgD/58itibw8s8f2hZ7tciT3saRdGCymg9tPUhMC/9eGIIUSr/C+xfnCJEZm6J6DgEUo1xlifonw==} + /@storybook/theming@7.0.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-XgQXKktcVBOkJT5gXjqtjH7C2pjdreDy0BTVTaEmFzggyyw+cgFrkJ7tuB27oKwYe+svx26c/olVMSHYf+KqhA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@storybook/channels': 7.0.0 - '@types/babel__core': 7.20.0 - '@types/express': 4.17.17 - file-system-cache: 2.0.2 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) + '@storybook/client-logger': 7.0.5 + '@storybook/global': 5.0.0 + memoizerific: 1.11.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + + /@storybook/theming@7.0.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-InTZe+Sgco1NsxgiG+cyUKWQe3GsjlIyU/o5qDdtOTXcZ64HzyBuAZlAequSddqfDeMDqxRFPc2w1J28MAUHxA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) + '@storybook/client-logger': 7.0.7 + '@storybook/global': 5.0.0 + memoizerific: 1.11.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) dev: true /@storybook/types@7.0.0-rc.5: @@ -4669,6 +5064,24 @@ packages: file-system-cache: 2.0.2 dev: true + /@storybook/types@7.0.5: + resolution: {integrity: sha512-By+tF3B30QiCnzEJ+Z73M2usSCqBWEmX4OGT1KbiEzWekkrsfCfpZwfzeMw1WwdQGlB1gLKTzB8wZ1zZB8oPtQ==} + dependencies: + '@storybook/channels': 7.0.5 + '@types/babel__core': 7.20.0 + '@types/express': 4.17.17 + file-system-cache: 2.0.2 + dev: true + + /@storybook/types@7.0.7: + resolution: {integrity: sha512-v9piuwp8FvTiHXIOOi5lEyTEJKhnbcbhVxgJ3VFhhXYFd0DTz6Bst0XIIgkgs21ITb3xhkfPbCRUueMcbXO1MA==} + dependencies: + '@storybook/channels': 7.0.7 + '@types/babel__core': 7.20.0 + '@types/express': 4.17.17 + file-system-cache: 2.0.2 + dev: true + /@swc/core-darwin-arm64@1.3.42: resolution: {integrity: sha512-hM6RrZFyoCM9mX3cj/zM5oXwhAqjUdOCLXJx7KTQps7NIkv/Qjvobgvyf2gAb89j3ARNo9NdIoLjTjJ6oALtiA==} engines: {node: '>=10'} @@ -4894,7 +5307,7 @@ packages: resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==} dependencies: '@babel/parser': 7.21.3 - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.18.3 @@ -4903,20 +5316,20 @@ packages: /@types/babel__generator@7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true /@types/babel__template@7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: '@babel/parser': 7.21.3 - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true /@types/babel__traverse@7.18.3: resolution: {integrity: sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==} dependencies: - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 dev: true /@types/body-parser@1.19.2: @@ -5520,13 +5933,22 @@ packages: '@typescript-eslint/types': 5.52.0 eslint-visitor-keys: 3.3.0 + /@vitejs/plugin-basic-ssl@1.0.1(vite@4.2.1): + resolution: {integrity: sha512-pcub+YbFtFhaGRTo1832FQHQSHvMrlb43974e2eS8EKleR3p1cDdkJFPci1UhwkEf1J9Bz+wKBSzqpKp7nNj2A==} + engines: {node: '>=14.6.0'} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 + dependencies: + vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) + dev: true + /@vitejs/plugin-react-swc@3.2.0(vite@4.2.1): resolution: {integrity: sha512-IcBoXL/mcH7JdQr/nfDlDwTdIaH8Rg7LpfQDF4nAht+juHWIuv6WhpKPCSfY4+zztAaB07qdBoFz1XCZsgo3pQ==} peerDependencies: vite: ^4 dependencies: '@swc/core': 1.3.42 - vite: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) dev: true /@vitejs/plugin-react@3.1.0(vite@4.2.1): @@ -5540,7 +5962,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.3) magic-string: 0.27.0 react-refresh: 0.14.0 - vite: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) transitivePeerDependencies: - supports-color dev: true @@ -5690,13 +6112,13 @@ packages: resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} dev: true - /@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.16.17): + /@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.17.12): resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==} engines: {node: '>=14.15.0'} peerDependencies: esbuild: '>=0.10.0' dependencies: - esbuild: 0.16.17 + esbuild: 0.17.12 tslib: 2.5.0 dev: true @@ -5776,6 +6198,14 @@ packages: engines: {node: '>= 10.0.0'} dev: true + /adjust-sourcemap-loader@4.0.0: + resolution: {integrity: sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==} + engines: {node: '>=8.9'} + dependencies: + loader-utils: 2.0.4 + regex-parser: 2.2.11 + dev: true + /agent-base@5.1.1: resolution: {integrity: sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==} engines: {node: '>= 6.0.0'} @@ -6072,7 +6502,7 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/compat-data': 7.21.0 + '@babel/compat-data': 7.21.4 '@babel/core': 7.21.3 '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.21.3) semver: 6.3.0 @@ -6617,7 +7047,7 @@ packages: dev: true /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} /concat-stream@1.6.2: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} @@ -6696,6 +7126,12 @@ packages: engines: {node: '>= 0.6'} dev: true + /copy-anything@2.0.6: + resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} + dependencies: + is-what: 3.14.1 + dev: true + /core-js-compat@3.29.1: resolution: {integrity: sha512-QmchCua884D8wWskMX8tW5ydINzd8oSJVx38lx/pVkFGqztxt73GYre3pm/hyYq8bPf+MW5In4I/uRShFDsbrA==} dependencies: @@ -7545,6 +7981,15 @@ packages: hasBin: true dev: true + /errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true + requiresBuild: true + dependencies: + prr: 1.0.1 + dev: true + optional: true + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -7646,6 +8091,17 @@ packages: - supports-color dev: true + /esbuild-register@3.4.2(esbuild@0.17.12): + resolution: {integrity: sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q==} + peerDependencies: + esbuild: '>=0.12 <1' + dependencies: + debug: 4.3.4(supports-color@5.5.0) + esbuild: 0.17.12 + transitivePeerDependencies: + - supports-color + dev: true + /esbuild@0.16.17: resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} engines: {node: '>=12'} @@ -8045,7 +8501,7 @@ packages: engines: {node: '>=8.3.0'} dependencies: '@babel/traverse': 7.21.3(supports-color@5.5.0) - '@babel/types': 7.21.3 + '@babel/types': 7.21.4 c8: 7.13.0 transitivePeerDependencies: - supports-color @@ -8961,6 +9417,14 @@ packages: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} + /image-size@0.5.5: + resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} + engines: {node: '>=0.10.0'} + hasBin: true + requiresBuild: true + dev: true + optional: true + /immer@9.0.19: resolution: {integrity: sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==} dev: false @@ -9256,6 +9720,10 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.2.0 + /is-what@3.14.1: + resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} + dev: true + /is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -9404,6 +9872,11 @@ packages: supports-color: 8.1.1 dev: true + /jiti@1.18.2: + resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} + hasBin: true + dev: true + /jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} dev: true @@ -9425,7 +9898,7 @@ packages: argparse: 2.0.1 dev: true - /jscodeshift@0.14.0(@babel/preset-env@7.20.2): + /jscodeshift@0.14.0(@babel/preset-env@7.21.4): resolution: {integrity: sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==} hasBin: true peerDependencies: @@ -9437,7 +9910,7 @@ packages: '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.21.3) '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.3) '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.21.3) - '@babel/preset-env': 7.20.2(@babel/core@7.21.3) + '@babel/preset-env': 7.21.4(@babel/core@7.21.3) '@babel/preset-flow': 7.18.6(@babel/core@7.21.3) '@babel/preset-typescript': 7.21.0(@babel/core@7.21.3) '@babel/register': 7.21.0(@babel/core@7.21.3) @@ -9623,6 +10096,38 @@ packages: dotenv-expand: 10.0.0 dev: true + /less-loader@11.1.0(less@4.1.3)(webpack@5.77.0): + resolution: {integrity: sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==} + engines: {node: '>= 14.15.0'} + peerDependencies: + less: ^3.5.0 || ^4.0.0 + webpack: ^5.0.0 + dependencies: + klona: 2.0.6 + less: 4.1.3 + webpack: 5.77.0(esbuild@0.17.12) + dev: true + + /less@4.1.3: + resolution: {integrity: sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==} + engines: {node: '>=6'} + hasBin: true + dependencies: + copy-anything: 2.0.6 + parse-node-version: 1.0.1 + tslib: 2.5.0 + optionalDependencies: + errno: 0.1.8 + graceful-fs: 4.2.11 + image-size: 0.5.5 + make-dir: 2.1.0 + mime: 1.6.0 + needle: 3.2.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + dev: true + /leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -10065,6 +10570,20 @@ packages: /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + /needle@3.2.0: + resolution: {integrity: sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==} + engines: {node: '>= 4.4.x'} + hasBin: true + requiresBuild: true + dependencies: + debug: 3.2.7 + iconv-lite: 0.6.3 + sax: 1.2.4 + transitivePeerDependencies: + - supports-color + dev: true + optional: true + /negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -10376,6 +10895,11 @@ packages: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + /parse-node-version@1.0.1: + resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} + engines: {node: '>= 0.10'} + dev: true + /parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} dependencies: @@ -10562,6 +11086,21 @@ packages: yaml: 2.2.1 dev: true + /postcss-loader@7.3.0(postcss@8.4.21)(webpack@5.77.0): + resolution: {integrity: sha512-qLAFjvR2BFNz1H930P7mj1iuWJFjGey/nVhimfOAAQ1ZyPpcClAxP8+A55Sl8mBvM+K2a9Pjgdj10KpANWrNfw==} + engines: {node: '>= 14.15.0'} + peerDependencies: + postcss: ^7.0.0 || ^8.0.1 + webpack: ^5.0.0 + dependencies: + cosmiconfig: 8.1.3 + jiti: 1.18.2 + klona: 2.0.6 + postcss: 8.4.21 + semver: 7.3.8 + webpack: 5.77.0(esbuild@0.17.12) + dev: true + /postcss-modules-extract-imports@3.0.0(postcss@8.4.21): resolution: {integrity: sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==} engines: {node: ^10 || ^12 || >= 14} @@ -10671,8 +11210,8 @@ packages: hasBin: true dev: true - /prettier@2.8.7: - resolution: {integrity: sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==} + /prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} hasBin: true dev: true @@ -10737,6 +11276,11 @@ packages: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: true + /prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + dev: true + optional: true + /psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} dev: true @@ -11166,6 +11710,24 @@ packages: - immer dev: false + /reactflow@11.7.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-bjfJV1iQZ+VwIlvsnd4TbXNs6kuJ5ONscud6fNkF8RY/oU2VUZpdjA3q1zwmgnjmJcIhxuBiBI5VLGajYx/Ozg==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/background': 11.2.0(react-dom@18.2.0)(react@18.2.0) + '@reactflow/controls': 11.1.11(react-dom@18.2.0)(react@18.2.0) + '@reactflow/core': 11.7.0(react-dom@18.2.0)(react@18.2.0) + '@reactflow/minimap': 11.5.0(react-dom@18.2.0)(react@18.2.0) + '@reactflow/node-resizer': 2.1.0(react-dom@18.2.0)(react@18.2.0) + '@reactflow/node-toolbar': 1.1.11(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - immer + dev: false + /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -11281,6 +11843,10 @@ packages: '@babel/runtime': 7.21.0 dev: true + /regex-parser@2.2.11: + resolution: {integrity: sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==} + dev: true + /regexp.prototype.flags@1.4.3: resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} engines: {node: '>= 0.4'} @@ -11363,6 +11929,17 @@ packages: global-dirs: 0.1.1 dev: true + /resolve-url-loader@5.0.0: + resolution: {integrity: sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==} + engines: {node: '>=12'} + dependencies: + adjust-sourcemap-loader: 4.0.0 + convert-source-map: 1.9.0 + loader-utils: 2.0.4 + postcss: 8.4.21 + source-map: 0.6.1 + dev: true + /resolve@1.19.0: resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} dependencies: @@ -11493,6 +12070,11 @@ packages: immutable: 4.3.0 source-map-js: 1.0.2 + /sax@1.2.4: + resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} + dev: true + optional: true + /saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -11784,11 +12366,11 @@ packages: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} dev: true - /storybook@7.0.0-rc.5: - resolution: {integrity: sha512-zmp7E5O4AJoro99qrDYbOxloKaQED//5WlnjdIhZSndXxSJb82IyVl6VYZc7Ji6xUWC4EoqzrLcFbttfC4w5uQ==} + /storybook@7.0.7: + resolution: {integrity: sha512-MaFAhpPm/KsaoIQfGzapnRyXNh1VbS8l38BNZR5ZD97ejGkLukJ7TO4fFS87Hyy6whAXo6tTdtqeCByMQ9gRFA==} hasBin: true dependencies: - '@storybook/cli': 7.0.0-rc.5 + '@storybook/cli': 7.0.7 transitivePeerDependencies: - bufferutil - encoding @@ -12094,6 +12676,31 @@ packages: webpack: 5.77.0(esbuild@0.16.17) dev: true + /terser-webpack-plugin@5.3.7(esbuild@0.17.12)(webpack@5.77.0): + resolution: {integrity: sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.17 + esbuild: 0.17.12 + jest-worker: 27.5.1 + schema-utils: 3.1.1 + serialize-javascript: 6.0.1 + terser: 5.16.8 + webpack: 5.77.0(esbuild@0.17.12) + dev: true + /terser@5.16.8: resolution: {integrity: sha512-QI5g1E/ef7d+PsDifb+a6nnVgC4F22Bg6T0xrBrz6iloVB4PUkkunp6V8nzoOOZJIzjWVdAGqCdlKlhLq/TbIA==} engines: {node: '>=10'} @@ -12283,65 +12890,65 @@ packages: tslib: 1.14.1 typescript: 4.9.5 - /turbo-darwin-64@1.8.8: - resolution: {integrity: sha512-18cSeIm7aeEvIxGyq7PVoFyEnPpWDM/0CpZvXKHpQ6qMTkfNt517qVqUTAwsIYqNS8xazcKAqkNbvU1V49n65Q==} + /turbo-darwin-64@1.9.3: + resolution: {integrity: sha512-0dFc2cWXl82kRE4Z+QqPHhbEFEpUZho1msHXHWbz5+PqLxn8FY0lEVOHkq5tgKNNEd5KnGyj33gC/bHhpZOk5g==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-darwin-arm64@1.8.8: - resolution: {integrity: sha512-ruGRI9nHxojIGLQv1TPgN7ud4HO4V8mFBwSgO6oDoZTNuk5ybWybItGR+yu6fni5vJoyMHXOYA2srnxvOc7hjQ==} + /turbo-darwin-arm64@1.9.3: + resolution: {integrity: sha512-1cYbjqLBA2zYE1nbf/qVnEkrHa4PkJJbLo7hnuMuGM0bPzh4+AnTNe98gELhqI1mkTWBu/XAEeF5u6dgz0jLNA==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-linux-64@1.8.8: - resolution: {integrity: sha512-N/GkHTHeIQogXB1/6ZWfxHx+ubYeb8Jlq3b/3jnU4zLucpZzTQ8XkXIAfJG/TL3Q7ON7xQ8yGOyGLhHL7MpFRg==} + /turbo-linux-64@1.9.3: + resolution: {integrity: sha512-UuBPFefawEwpuxh5pM9Jqq3q4C8M0vYxVYlB3qea/nHQ80pxYq7ZcaLGEpb10SGnr3oMUUs1zZvkXWDNKCJb8Q==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-linux-arm64@1.8.8: - resolution: {integrity: sha512-hKqLbBHgUkYf2Ww8uBL9UYdBFQ5677a7QXdsFhONXoACbDUPvpK4BKlz3NN7G4NZ+g9dGju+OJJjQP0VXRHb5w==} + /turbo-linux-arm64@1.9.3: + resolution: {integrity: sha512-vUrNGa3hyDtRh9W0MkO+l1dzP8Co2gKnOVmlJQW0hdpOlWlIh22nHNGGlICg+xFa2f9j4PbQlWTsc22c019s8Q==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-windows-64@1.8.8: - resolution: {integrity: sha512-2ndjDJyzkNslXxLt+PQuU21AHJWc8f6MnLypXy3KsN4EyX/uKKGZS0QJWz27PeHg0JS75PVvhfFV+L9t9i+Yyg==} + /turbo-windows-64@1.9.3: + resolution: {integrity: sha512-0BZ7YaHs6r+K4ksqWus1GKK3W45DuDqlmfjm/yuUbTEVc8szmMCs12vugU2Zi5GdrdJSYfoKfEJ/PeegSLIQGQ==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /turbo-windows-arm64@1.8.8: - resolution: {integrity: sha512-xCA3oxgmW9OMqpI34AAmKfOVsfDljhD5YBwgs0ZDsn5h3kCHhC4x9W5dDk1oyQ4F5EXSH3xVym5/xl1J6WRpUg==} + /turbo-windows-arm64@1.9.3: + resolution: {integrity: sha512-QJUYLSsxdXOsR1TquiOmLdAgtYcQ/RuSRpScGvnZb1hY0oLc7JWU0llkYB81wVtWs469y8H9O0cxbKwCZGR4RQ==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /turbo@1.8.8: - resolution: {integrity: sha512-qYJ5NjoTX+591/x09KgsDOPVDUJfU9GoS+6jszQQlLp1AHrf1wRFA3Yps8U+/HTG03q0M4qouOfOLtRQP4QypA==} + /turbo@1.9.3: + resolution: {integrity: sha512-ID7mxmaLUPKG/hVkp+h0VuucB1U99RPCJD9cEuSEOdIPoSIuomcIClEJtKamUsdPLhLCud+BvapBNnhgh58Nzw==} hasBin: true requiresBuild: true optionalDependencies: - turbo-darwin-64: 1.8.8 - turbo-darwin-arm64: 1.8.8 - turbo-linux-64: 1.8.8 - turbo-linux-arm64: 1.8.8 - turbo-windows-64: 1.8.8 - turbo-windows-arm64: 1.8.8 + turbo-darwin-64: 1.9.3 + turbo-darwin-arm64: 1.9.3 + turbo-linux-64: 1.9.3 + turbo-linux-arm64: 1.9.3 + turbo-windows-64: 1.9.3 + turbo-windows-arm64: 1.9.3 dev: true /type-check@0.3.2: @@ -12710,7 +13317,7 @@ packages: mlly: 1.2.0 pathe: 1.1.0 picocolors: 1.0.0 - vite: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) transitivePeerDependencies: - '@types/node' - less @@ -12737,7 +13344,7 @@ packages: kolorist: 1.7.0 magic-string: 0.29.0 ts-morph: 17.0.1 - vite: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) transitivePeerDependencies: - '@types/node' - rollup @@ -12757,10 +13364,10 @@ packages: postcss-js: 4.0.1(postcss@8.4.21) prettier: 2.8.5 sass: 1.59.3 - vite: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) dev: true - /vite-plugin-sass-dts@1.3.2(postcss@8.4.21)(prettier@2.8.7)(sass@1.59.3)(vite@4.2.1): + /vite-plugin-sass-dts@1.3.2(postcss@8.4.21)(prettier@2.8.8)(sass@1.59.3)(vite@4.2.1): resolution: {integrity: sha512-zClOXVLQHKG//aZ+gsDXMnhLLVKJprrv3x+KQBf/8GD/dM4FHmlK4zMM5JcOr12oq3kTz+DUYCtCEYsFY8eDPQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -12771,9 +13378,9 @@ packages: dependencies: postcss: 8.4.21 postcss-js: 4.0.1(postcss@8.4.21) - prettier: 2.8.7 + prettier: 2.8.8 sass: 1.59.3 - vite: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) dev: true /vite-tsconfig-paths@4.0.7(typescript@4.9.5)(vite@4.2.1): @@ -12787,13 +13394,13 @@ packages: debug: 4.3.4(supports-color@5.5.0) globrex: 0.1.2 tsconfck: 2.1.0(typescript@4.9.5) - vite: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) transitivePeerDependencies: - supports-color - typescript dev: true - /vite@4.2.1(@types/node@18.13.0)(sass@1.59.3): + /vite@4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3): resolution: {integrity: sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -12820,6 +13427,7 @@ packages: dependencies: '@types/node': 18.13.0 esbuild: 0.17.12 + less: 4.1.3 postcss: 8.4.21 resolve: 1.22.1 rollup: 3.20.0 @@ -12879,7 +13487,7 @@ packages: tinybench: 2.4.0 tinypool: 0.4.0 tinyspy: 1.1.1 - vite: 4.2.1(@types/node@18.13.0)(sass@1.59.3) + vite: 4.2.1(@types/node@18.13.0)(less@4.1.3)(sass@1.59.3) vite-node: 0.29.4(@types/node@18.13.0)(sass@1.59.3) why-is-node-running: 2.2.2 transitivePeerDependencies: @@ -12973,6 +13581,46 @@ packages: - uglify-js dev: true + /webpack@5.77.0(esbuild@0.17.12): + resolution: {integrity: sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + dependencies: + '@types/eslint-scope': 3.7.4 + '@types/estree': 0.0.51 + '@webassemblyjs/ast': 1.11.1 + '@webassemblyjs/wasm-edit': 1.11.1 + '@webassemblyjs/wasm-parser': 1.11.1 + acorn: 8.8.2 + acorn-import-assertions: 1.8.0(acorn@8.8.2) + browserslist: 4.21.5 + chrome-trace-event: 1.0.3 + enhanced-resolve: 5.12.0 + es-module-lexer: 0.9.3 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.1.1 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.7(esbuild@0.17.12)(webpack@5.77.0) + watchpack: 2.4.0 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + dev: true + /whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} -- GitLab