diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 5b999efa470b056e329b4c23a73904e0794bdc2f..0000000000000000000000000000000000000000 --- 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 06cc47d9a23e1a408d6a9e8ff053e019efdf5df2..0000000000000000000000000000000000000000 --- 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 983caf67d8c2ae056ce8d26bcc6d6c5ac76aefcc..bb4060bde39a31be311a9ac6d91e004748f1ec5f 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 0000000000000000000000000000000000000000..3421a7d280fba4a93386bc69ccf6ec0b9603a864 --- /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 0000000000000000000000000000000000000000..2707c1f4120744f9e9e9e736c8aad7cf59a0862c --- /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 b98cabe7350407801f45073eb94c0f09a709f8d9..0000000000000000000000000000000000000000 --- 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 9cc005a335c64c1432ee3ebd11d5dcba9b37c0fc..0000000000000000000000000000000000000000 --- 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 8dd5c441d60f7f287683060569365d39409ec378..7b5586514ec9f3e800540e537476b8013013dec5 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 Binary files /dev/null and b/apps/web/public/assets/icons/ExportIcon.png differ 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 Binary files /dev/null and b/apps/web/public/assets/login-screen/github.png differ 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 Binary files /dev/null and b/apps/web/public/assets/login-screen/google.png differ diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx index 3b13b1f87fc0d001489670417004f1c10c2327d0..9ae2badfaf7eceda8e4dcb1a6792d14e5751e0b5 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 0000000000000000000000000000000000000000..97642ae3758a844a1c36c7afcb9185993e8c419b --- /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 0000000000000000000000000000000000000000..54c1b0ac0b9e9aa2066161670b3184a40e82907e --- /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 0000000000000000000000000000000000000000..af7f9f647753753c0c3077e7a1994ada4454fcbb --- /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 Binary files /dev/null and b/apps/web/src/components/navbar/logogp.png differ 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 Binary files /dev/null and b/apps/web/src/components/navbar/logogpwhite.png differ 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 0000000000000000000000000000000000000000..e1faa9a7863fcca1b4c52135300e3951de0e5b8e --- /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 0000000000000000000000000000000000000000..ef3d9c91ec3ddf61b1b4d975fea0285372bd95db --- /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 05136727df33393019f3b7a458b6f4b08148f23c..4fcc697290865b9615b091c8566157fe91a4f1c8 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 a2bdc3e17c8a27c5e5501052719644ccdca31270..2707c1f4120744f9e9e9e736c8aad7cf59a0862c 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 df7053eff68c1380c1c131b6851d0617cff4ccf1..1dc5085e331fb44e9d690b7269b34a92d2a7fe03 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 deabbe0f590ed058772363cb224fabd758425a88..b627ba76dd56946a53719b29f5d66f787bc67c28 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 94a2ddb8716278114077b272a51852400dbb44b9..eed07d0e97fe3d9e4353a511b074d44726399bcb 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 0000000000000000000000000000000000000000..004a8b8e87db1eae3242b84e4f0c7d2e841b66aa --- /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 06296ae3a3d7547cb1eaaa4ec9f85fe081ab2bed..825538a8278a9d8d0234c64d01a6e7d074a5c2e4 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 bc6001fd2aed8d24d60abf15411167af64629e81..d8f2d1b7457571a856eb64f1eec4c6a0e976e8ae 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 0000000000000000000000000000000000000000..4110005e881aa28f62ecda514643609294a6b0fa --- /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 40d36480605c0faae067cdd01ace9cba3252d171..0cf2dd57d11433d17c7556f071f558b458c14c2d 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 6d79706f0e881fd176d771685d404510e6d6d295..3973055833f7e1d4ee0575201a2156f8811075b2 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 0000000000000000000000000000000000000000..9e8f71a7bad08713f5b6e918e4e939be4b390260 --- /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 5f3e0cfac785fbdb47a145199026a5f98e5c8941..e1c5935f4bd900657af37422a67b5e24b670acf2 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 ebce9ab02837a640a24374307645d54b404dcae3..43667f97745a6be086df9e2adeaadc19662fa7f4 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 877444a06b9ebe70c06a866e9ecc6d28efc001b5..f2e3cd62b3cf06a8fc1bddcdfff346be228b065b 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 2e28c3371a13b348dc2ed8a38753a9bc0fa13f42..1cbc52aeb5cd2abf3671109a5edb617a587eadac 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 936b1e700ac531426e8277dd856b33111b385f57..bd3d0b4b70a92774e5fd947a83228e8f9b88d7f3 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 0000000000000000000000000000000000000000..979f7f028dd9fa46a7128671f63c92f5ae647874 --- /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 a1346f58f2c1ef877b4c5ff4c30491e6ec161655..ef639a9206b474aae0bf000ed34b2111fcadbea5 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 bb04af952f810393c468a600774cc42e60255e3c..191b3606fc76d9eb90d112640bf871c3c896f607 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 930200eea101f2b6c6c977a3705f5986ef2d864d..7370db47f1fb4b90cc43ddf62eec595c03847faf 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 0000000000000000000000000000000000000000..7d645f7457888aa32c25af47fd8868eaaa74dde5 --- /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 0000000000000000000000000000000000000000..194ed07c768ce1725b8e59171c115cd368d96567 --- /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 0000000000000000000000000000000000000000..b55a4597dc2822e03d619c09c70212c214a2f79c --- /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 0000000000000000000000000000000000000000..404f2f3ea0fbbf5e1fcc4efa7c3518441f00003c --- /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 0000000000000000000000000000000000000000..71a09d2066cd6ebdf0919769e715aa6ee90c7e89 --- /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 0000000000000000000000000000000000000000..fddca14b89b5c917ad821ff5f1257381e3801d13 --- /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 9b288187a4c8daa39507a879ac07f87572c1e2b7..2bdf0acdb9a18b0a4af7e672e876f1d4eef39ea1 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 0000000000000000000000000000000000000000..e9ce65c3e91230655ae841d7f2721a41496155ec --- /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 db83f46964a46bd398e50188c6d83bd06769b52c..e1a5d48d0e6acd88f1c7480891248c9c8fa8c2c7 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 f2549523cf5819bcf6e47ed740eb243f828124aa..8d93e99522d001e896de5c3835789220b11e33b2 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 b6bb2f30266d55e7a3db3f677336867bdde391e8..1bc0f5778bdbdbb158887faee1fb2728b71a34c5 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 0892485b2bb5d9994e1f06b7bbc2ae1c3ed6ff8a..aca3c482a87d8d899a5dfd2b3d938eb518d1bb52 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 0000000000000000000000000000000000000000..61625236bcd0383d923520946dee33e4a77bcc80 --- /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 0000000000000000000000000000000000000000..50625840f09ca900ea4aa5c0916ba04322ced175 --- /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 0000000000000000000000000000000000000000..196afa73c2f61830f6c8d6a073bf4e6d713e9f83 --- /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 0000000000000000000000000000000000000000..880216ca8cced12e4a78c98955880278d4e7a1e8 --- /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 0000000000000000000000000000000000000000..d44a6b42fca61b196da056f690d2db24b92a2e0d --- /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 5013499164e08eccdc9136807585112d043dce0a..eec408f5a00677e37e7144b00a74b3929b0cf5ca 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 0000000000000000000000000000000000000000..26d4bb0bc7c6fd19e1e286a8788e19cfa40d37e7 --- /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 0000000000000000000000000000000000000000..fe9dde909b79825b1cb1e46e3402762141b2d25f --- /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 1c31d2744f029e77c53efbfac2aae8983640770a..286fa1da405db69f96f095a6801d825502da3e6c 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 cf1d9726a11921e9988dea44cf958cd22ae07222..d62e39a1d07924099950b54f9ddc16626a18269a 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 0000000000000000000000000000000000000000..38a35b85e5ebfd44f7c68d9bb1b2a753ad4a2cca --- /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 646b6f4f24159628ca7d82aa1a85162ac0c89d2b..2d9006f6712a37cbe4b503e53e6fd991d420edd9 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 0000000000000000000000000000000000000000..18b65e8be65a8550fd1feed2a693888d604f1a37 --- /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 0000000000000000000000000000000000000000..79e336437fe02d1ddbe85db273c5488ec7bf6bce --- /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 0000000000000000000000000000000000000000..bc2a7aa4bd34d49f3d40e760930d461ab62dce48 --- /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 6c79fefe619d57705a6dac9f1c2feddb630276fa..87d7a9e8ce85b6e08c8f2e90c6d723cf761405d8 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 0000000000000000000000000000000000000000..51f8aa0742044a27707d2359d7bc86bdb7b10288 --- /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 0000000000000000000000000000000000000000..60e40cbb5cef53dc371846d23c38fda82ef7076f --- /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 0000000000000000000000000000000000000000..7e589ded73a5eb5010a10401e4f25fefc54460ad --- /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 0000000000000000000000000000000000000000..924514ea12818ede4d522342249d6035c47abe82 --- /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 0000000000000000000000000000000000000000..2488295e09b2e2b682a60dde06359a59a526640c --- /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 0000000000000000000000000000000000000000..c0edcaff21c16a0f102d965fa5da96759bc23495 --- /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 0000000000000000000000000000000000000000..865e20fa5725210913b8332ba393e3488059092a --- /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 0000000000000000000000000000000000000000..2e259083fc99e05883812b703d90b10380bcb4e6 --- /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 0000000000000000000000000000000000000000..d22770e4487c411c24d1fedf852dbe55ea42f79c --- /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 0000000000000000000000000000000000000000..6041c775e13891b6f55ad41588ddd523fbfda34d --- /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 0000000000000000000000000000000000000000..480fe054e9465947e2e0c47d8b0a981aef0c9548 --- /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 0000000000000000000000000000000000000000..86ebf29a76fea48ad15da5e287a18c719ec3f141 --- /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 0000000000000000000000000000000000000000..b01ffd8f39dff716a8c90e804f3ef9329bd1fea6 --- /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 0000000000000000000000000000000000000000..805e3f3fc78bd04389de1363e61d22592aa70044 --- /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 0000000000000000000000000000000000000000..e7dd844ff495b98f1ff9ceebf1e57b41dc9fede0 --- /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 0000000000000000000000000000000000000000..1f8d81b42bb9dd2606b65c17253f93274824547c --- /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 0000000000000000000000000000000000000000..a0a450e53cc824b7f609036a0e336970b3f601a4 --- /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 735c0acb419d063aa5f469cae92f55a73430b69d..35171325cb14ef9d80de42d8470862b9a5895e47 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 0000000000000000000000000000000000000000..b1de1902c48a34371115cf853e98de7fffaf27e2 --- /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 1a7ae7dc97567751b86f69db05f1049712ed8d26..728c5f0e34473fa14fda287b9fa3728da6547936 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 5f784740f5f732d8a2bcfdef058fb4a5e36be262..b37ce567ecf66be10946ef0072caa25307c36a5b 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 0000000000000000000000000000000000000000..2cb3fedf0b75d41e546a59118070b9145a9ff215 --- /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 0000000000000000000000000000000000000000..a040b2068e5b80b1df7cfa195509c11c47e175ad --- /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 4dc37e18d16bd55f0b405520704b97f174ef6fca..0000000000000000000000000000000000000000 --- 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 54e15f0b0d2ef473477ca7d09d970ef86eb63c29..0000000000000000000000000000000000000000 --- 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 cb212e0e122658ec13e90e4c8b9e613c51b8b8d8..0000000000000000000000000000000000000000 --- 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 06d687eb90fbdd6d6e752c90417a12a854aff52b..0000000000000000000000000000000000000000 --- 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 8dbd36ab16ba81f25cb4956419f1a13a7a11799f..68245392c939c6530a5cbd8c9ac808a4a17ca01f 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 e162b7869413f1a534e0acd3d79fcab42c001c40..f2d62bf1c65d224b203a2d34f2ebf84fd3a4e626 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 9307bee9e25218c2f1198bf95f3273d297ce08e5..a33dcaceb8b31acfcbc944521a0cc7d416e67429 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 7217c67b31e66aa720922c61edb7e07d7bb32379..aa5e1c849973ed5a7d779da77ce281708ff3d015 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 9ba5ba22c49c279617faef06e92d4256e49a36fb..0000000000000000000000000000000000000000 --- 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 af1e5312f0c30b01086c1404e45089b601240243..0000000000000000000000000000000000000000 --- 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 e202d2988984b5c215736bde581aae3efee6f128..0000000000000000000000000000000000000000 --- 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 755d2b41d564abe3f9e4eb41f56865ef7d2432f4..0000000000000000000000000000000000000000 --- 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 d397689047c7b475c20b89c9efe1de5e16da08d8..0000000000000000000000000000000000000000 --- 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 3a3168c580ad4fe62fd4c7c8af07652785cdee39..0000000000000000000000000000000000000000 --- 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 fa564adf33da3132ca7873bfa700591df8e21246..0000000000000000000000000000000000000000 --- 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 68ba73f84e0f485df7403105d19c5b1eb2018f97..0000000000000000000000000000000000000000 --- 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 7e81ade7c6a0de0669fe20804caeaa3f13974730..0000000000000000000000000000000000000000 --- 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 11b5289db01a4cb302743b4238d3eeae76baca8f..0000000000000000000000000000000000000000 --- 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 6a804d611fbd303b2d569e6c44d76d7810116ca8..0000000000000000000000000000000000000000 --- 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 f5d77dbee732734934bbf9c3d7f895e73a9abdaf..717ce13fa739af63f20a7c5a57a870df30cf00e2 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 f4ed50e661f460aca8ddbf3cf30d2588e394e0dd..e2b95a49acaf80b8c4bd6b9a599864697ee57f62 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 4ae25ae4255dedc0484cb2607ade11962348fc2f..30a556d7485f8639ca3dc720e1c0e84f1d5d823e 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 61e9000e045c2b74bea925ddb11f2ce061f51521..0000000000000000000000000000000000000000 --- 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 916730e8fa531cefbf8435583bd6d68670717a66..0000000000000000000000000000000000000000 --- 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 17b3a320abe3fe6efa54c20ed8cfd429721c19dd..0000000000000000000000000000000000000000 --- 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 cd6c94d6e8b0c3b34c21ef652ef4d56c64b4a5df..0000000000000000000000000000000000000000 --- 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 2053e84891e1902b85e8c9bceb9cc1f130c683be..14b92d621f83e7320fbe089587bac34e31210e2b 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'}