Skip to content
Snippets Groups Projects
Commit 6b3c8bc8 authored by Sivan Duijn's avatar Sivan Duijn
Browse files

feat(querybuilder): added attribute pill

parent e6718f57
No related branches found
No related tags found
2 merge requests!13merge develop into main,!11added query builder pills and slice
Showing
with 417 additions and 78 deletions
.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';
// }
}
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;
.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;
......
......@@ -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;
.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;
}
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>
);
}
......@@ -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' },
},
];
......
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',
},
},
},
......
......@@ -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,
},
},
}),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment