diff --git a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.module.scss b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..bab4bd1135f0cf6259c4134306ba412cf331f750 --- /dev/null +++ b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.module.scss @@ -0,0 +1,53 @@ +.attribute { + display: flex; + font-family: monospace; + font-weight: bold; + font-size: 10px; + border-radius: 20px; +} + +// .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 { + // margin-left: 2ch; + display: flex; + + .content { + padding: 4px 2ch; + } +} + +.attributeInput { + float: right; + padding: 0 2ch 0 0; + display: flex; + align-items: center; + + input { + background-color: lightgray; + font-family: monospace; + font-size: 10px; + border: 1px solid darkgrey; + border-radius: 2px; + height: 10px; + outline: none; + transition: border 0.3s; + + &:focus { + border: 1px solid grey; + } + } + // &:read-only { + // cursor: 'grab'; + // } +} diff --git a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.tsx b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b45171f0c6cf753eb00a5750b8023429d923a813 --- /dev/null +++ b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/attributepill/attributepill.tsx @@ -0,0 +1,91 @@ +import { useTheme } from '@mui/material'; +import React, { useState } from 'react'; +import { Handle, Position } from 'react-flow-renderer'; +import { Handles } from '../entitypill/entitypill'; +import styles from './attributepill.module.scss'; + +/** + * Component to render an attribute flow element + * @param {FlowElement<EntityData>)} param0 The data of an entity flow element. + */ +export const AttributeRFPill = React.memo(({ data }: { data: any }) => { + const theme = useTheme(); + const [readonly, setReadonly] = useState(true); + const [value, setValue] = useState(data?.value || ''); + + /** Checks if the string input is a number. */ + const isNumber = (x: string): boolean => { + { + if (typeof x != 'string') return false; + return !Number.isNaN(x) && !Number.isNaN(parseFloat(x)); + } + }; + + /** Checks if the provided value is the same as the datatype of the attribute. */ + 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; + }; + + const onChange = (e: any) => { + if (data != undefined) { + setValue(inputConstraint(data.datatype, e.target.value)); + } + }; + + // Calculates the size of the input + const getInputWidth = () => { + if (value == '') return 1; + else if (value.length > 10) return 10; + return value.length; + }; + + return ( + <div + className={styles.attribute} + style={{ + background: theme.palette.queryBuilder.attribute.background, + color: theme.palette.queryBuilder.text, + }} + > + {/* <Handle + id={Handles.Attribute} + type="source" + position={Position.Bottom} + className={styles.handle} + /> */} + <div className={styles.contentWrapper}> + <span className={styles.content}>{data.name}</span> + <span className={styles.attributeInput}> + <input + style={{ maxWidth: `${getInputWidth()}ch` }} + type="string" + // readOnly={readonly} + placeholder={'?'} + value={value} + onChange={onChange} + // onDoubleClick={() => setReadonly(false)} + // onBlur={() => setReadonly(true)} + onKeyDown={(e) => e.key == 'Enter' && setReadonly(true)} + ></input> + </span> + </div> + </div> + ); +}); + +export default AttributeRFPill; diff --git a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/entitypill/entitypill.module.scss b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/entitypill/entitypill.module.scss index 4ba7718163833c84c691d1d74b1cc0338b2f4db3..6eff44ad25107c18e8e53b6abee6597bb2d255fb 100644 --- a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/entitypill/entitypill.module.scss +++ b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/entitypill/entitypill.module.scss @@ -1,65 +1,39 @@ .entity { display: flex; - border-radius: 3px; + font-family: monospace; font-weight: bold; - font-size: 11px; - padding: 4px 3px; + font-size: 10px; + padding: 4px 2ch; + border-radius: 3px; } .handleLeft { border: 0px; border-radius: 0px; left: 12px; - width: 8px; - height: 8px; + width: 7px; + height: 7px; margin-bottom: 11px; - background: rgba(0, 0, 0, 0.3); + background: rgba(255, 255, 255, 0.6); + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3); transform-origin: center; - - &::before { - content: ''; - width: 6px; - height: 6px; - left: 1px; - bottom: 1px; - border: 0px; - border-radius: 0px; - background: rgba(255, 255, 255, 0.6); - z-index: -1; - display: inline-block; - position: fixed; - } } .handleBottom { border: 0px; border-radius: 0px; - width: 8px; - height: 8px; + width: 7px; + height: 7px; left: 27.5px; margin-bottom: 11px; - background: rgba(0, 0, 0, 0.3); + 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; - - &::before { - content: ''; - width: 6px; - height: 6px; - left: 1px; - bottom: 1px; - border: 0px; - border-radius: 0px; - background: rgba(255, 255, 255, 0.6); - z-index: -1; - display: inline-block; - position: fixed; - } } .contentWrapper { - margin-left: 45px; - margin-right: 5px; + margin-left: 5ch; span { float: left; diff --git a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/entitypill/entitypill.tsx b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/entitypill/entitypill.tsx index 2d34c4bb38041c1045d09588b7c84f5d77528577..07c055dcc89b9197f5f83bae453cefb8cfcc0911 100644 --- a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/entitypill/entitypill.tsx +++ b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/entitypill/entitypill.tsx @@ -16,7 +16,7 @@ export enum Handles { RelationRight = 'rightEntityHandle', //target ToAttributeHandle = 'attributesHandle', //target ToRelation = 'relationsHandle', //source - OnAttribute = 'onAttributeHandle', //source + Attribute = 'AttributeHandle', //source ReceiveFunction = 'receiveFunctionHandle', //target FunctionBase = 'functionHandle_', // + name from FunctionTypes args //source } @@ -28,19 +28,12 @@ export enum Handles { export const EntityRFPill = React.memo(({ data }: { data: any }) => { const theme = useTheme(); - // TODO: Change flow element width when text overflows - // const data = entityNode.data as EntityData; - // const animation = data.fadeIn ? styles.entityFade : ''; - return ( <div className={styles.entity} style={{ background: theme.palette.queryBuilder.entity.background, - fontFamily: 'monospace', - color: theme.palette.queryBuilder.entity.text, - // borderTop: `4px solid ${theme.palette.queryBuilder.entity.accent}`, - // borderBottom: `6px solid ${theme.palette.queryBuilder.entity.accent}`, + color: theme.palette.queryBuilder.text, }} > <Handle @@ -55,20 +48,11 @@ export const EntityRFPill = React.memo(({ data }: { data: any }) => { position={Position.Bottom} className={styles.handleBottom} /> - {/* <Handle - id={Handles.ReceiveFunction} - type="target" - position={Position.Bottom} - className={styles.handleFunction + ' ' + styles.handleFunctionEntity} - /> */} - <div - className={styles.contentWrapper} - style={{ - color: theme.palette.queryBuilder.entity.text, - }} - > + <div className={styles.contentWrapper}> <span>{data.name}</span> </div> </div> ); }); + +export default EntityRFPill; diff --git a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/relationpill/relationpill.module.scss b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/relationpill/relationpill.module.scss index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c649ef87390666179126faa50974460d8b168e54 100644 --- a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/relationpill/relationpill.module.scss +++ b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/relationpill/relationpill.module.scss @@ -0,0 +1,98 @@ +.relation { + display: flex; + text-align: center; + font-family: monospace; + font-weight: bold; + font-size: 10px; + background-color: transparent; +} + +.contentWrapper { + display: flex; + + .handleLeft { + position: relative; + + 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; + } + + .handleRight { + position: relative; + + 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 { + width: 0; + height: 0; + border-top: $height solid transparent; + border-bottom: $height solid transparent; + + border-right: $height solid; +} + +.arrowRight { + width: 0; + height: 0; + border-top: $height solid transparent; + border-bottom: $height solid transparent; + + border-left: $height solid; +} diff --git a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/relationpill/relationpill.tsx b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/relationpill/relationpill.tsx index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..10ce2310a0b7b9c4bf0b6ba9035e52ac93064ada 100644 --- a/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/relationpill/relationpill.tsx +++ b/apps/web-graphpolaris/src/components/querybuilder/customFlowPills/relationpill/relationpill.tsx @@ -0,0 +1,93 @@ +import { useTheme } from '@mui/material'; +import React, { useRef, useState } from 'react'; +import { FlowElement, Handle, Position } from 'react-flow-renderer'; +import { Handles } from '../entitypill/entitypill'; + +import styles from './relationpill.module.scss'; + +/** + * Component to render a relation flow element + * @param { FlowElement<RelationData>} param0 The data of a relation flow element. + */ +export default function RelationRFPill({ data }: { data: any }) { + const theme = useTheme(); + + 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}> + <div + className={styles.arrowLeft} + style={{ + borderRightColor: theme.palette.queryBuilder.relation.background, + }} + /> + <div + className={styles.contentWrapper} + style={{ + color: theme.palette.queryBuilder.text, + background: theme.palette.queryBuilder.relation.background, + }} + > + <Handle + id={Handles.RelationLeft} + type="source" + position={Position.Left} + className={styles.handleLeft} + /> + <span className={styles.content}>{data.name}</span> + <Handle + id={Handles.RelationRight} + type="target" + position={Position.Right} + className={styles.handleRight} + /> + </div> + <div + className={styles.arrowRight} + style={{ + borderLeftColor: theme.palette.queryBuilder.relation.background, + }} + /> + </div> + ); +} diff --git a/apps/web-graphpolaris/src/components/querybuilder/querybuilder.tsx b/apps/web-graphpolaris/src/components/querybuilder/querybuilder.tsx index 76f14bc32bdbcc618dbc76ef6428368a87c2fb3c..492a00ffe59e4d38d1a02d6df553ce43c87d8fff 100644 --- a/apps/web-graphpolaris/src/components/querybuilder/querybuilder.tsx +++ b/apps/web-graphpolaris/src/components/querybuilder/querybuilder.tsx @@ -8,18 +8,34 @@ import ReactFlow, { FlowElement, Background, } from 'react-flow-renderer'; -import { EntityRFPill } from './customFlowPills/entitypill/entitypill'; +import AttributeRFPill from './customFlowPills/attributepill/attributepill'; +import EntityRFPill from './customFlowPills/entitypill/entitypill'; +import RelationRFPill from './customFlowPills/relationpill/relationpill'; const nodeTypes = { entity: EntityRFPill, + relation: RelationRFPill, + attribute: AttributeRFPill, }; const initialElements = [ { - id: '2', + id: '0', type: 'entity', position: { x: 100, y: 100 }, - data: { name: 'entityNode' }, + data: { name: 'Entity Pill' }, + }, + { + id: '1', + type: 'relation', + position: { x: 140, y: 140 }, + data: { name: 'Relation Pill' }, + }, + { + id: '2', + type: 'attribute', + position: { x: 180, y: 180 }, + data: { name: 'Attribute Pill', datatype: 'string' }, }, ]; diff --git a/libs/shared/data-access/store/src/lib/colorPaletteConfigSlice.ts b/libs/shared/data-access/store/src/lib/colorPaletteConfigSlice.ts index c4427767fd95480845561d3e08be7bf1235c58eb..521f63b8a6355548657e37afc5541cdc321be012 100644 --- a/libs/shared/data-access/store/src/lib/colorPaletteConfigSlice.ts +++ b/libs/shared/data-access/store/src/lib/colorPaletteConfigSlice.ts @@ -1,4 +1,3 @@ -import { palette } from '@mui/system'; import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import type { RootState } from './store'; @@ -8,10 +7,15 @@ export interface ExtraColorsForMui5 { dataPointColors: string[]; queryBuilder: { + text: string; entity: { background: string; - text: string; - accent: string; + }; + relation: { + background: string; + }; + attribute: { + background: string; }; }; } @@ -56,10 +60,15 @@ export const initialState: ColorPaletteConfig = { custom: { dataPointColors: ['#ff0000', '#00ff00', '#0000ff'], queryBuilder: { + text: 'black', entity: { - background: '#ffac57', - accent: '#C48546', - text: 'black', + background: '#FC4F4F', + }, + relation: { + background: '#FF9F45', + }, + attribute: { + background: '#C7C7C7', }, }, }, @@ -80,10 +89,15 @@ export const initialState: ColorPaletteConfig = { custom: { dataPointColors: ['#ff0000', '#00ff00', '#0000ff'], queryBuilder: { + text: 'black', entity: { - background: '#e9e9e9', - accent: '#C48546', - text: 'black', + background: '#FC4F4F', + }, + relation: { + background: '#FF9F45', + }, + attribute: { + background: '#C7C7C7', }, }, }, diff --git a/libs/shared/data-access/theme/src/lib/mapColorsConfigToMuiTheme.ts b/libs/shared/data-access/theme/src/lib/mapColorsConfigToMuiTheme.ts index dd174cdb5394c663ac27cbc0f8b5aa4abb0c8902..975bb3ed04a61838b696aed1899f355ae661e9bb 100644 --- a/libs/shared/data-access/theme/src/lib/mapColorsConfigToMuiTheme.ts +++ b/libs/shared/data-access/theme/src/lib/mapColorsConfigToMuiTheme.ts @@ -15,13 +15,21 @@ export default function MapColorsConfigToMuiTheme( secondary: colorsConfig.darkPalette.secondary, dataPointColors: colorsConfig.darkPalette.custom.dataPointColors, queryBuilder: { + text: colorsConfig.darkPalette.custom.queryBuilder.text, entity: { background: colorsConfig.darkPalette.custom.queryBuilder.entity .background, - accent: - colorsConfig.darkPalette.custom.queryBuilder.entity.accent, - text: colorsConfig.darkPalette.custom.queryBuilder.entity.text, + }, + relation: { + background: + colorsConfig.darkPalette.custom.queryBuilder.relation + .background, + }, + attribute: { + background: + colorsConfig.darkPalette.custom.queryBuilder.attribute + .background, }, }, } @@ -30,13 +38,21 @@ export default function MapColorsConfigToMuiTheme( secondary: colorsConfig.lightPalette.secondary, dataPointColors: colorsConfig.lightPalette.custom.dataPointColors, queryBuilder: { + text: colorsConfig.lightPalette.custom.queryBuilder.text, entity: { background: colorsConfig.lightPalette.custom.queryBuilder.entity .background, - accent: - colorsConfig.lightPalette.custom.queryBuilder.entity.accent, - text: colorsConfig.lightPalette.custom.queryBuilder.entity.text, + }, + relation: { + background: + colorsConfig.lightPalette.custom.queryBuilder.relation + .background, + }, + attribute: { + background: + colorsConfig.lightPalette.custom.queryBuilder.attribute + .background, }, }, }),