Skip to content

Commit

Permalink
feat: implement nesting via menu operation
Browse files Browse the repository at this point in the history
  • Loading branch information
ChibiBlasphem committed Jan 9, 2025
1 parent 6f2754a commit 839dfc8
Show file tree
Hide file tree
Showing 30 changed files with 337 additions and 214 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '@app-builder/services/editor/ast-editor';
import {
useDefaultCoerceToConstant,
useGetAstNodeOption,
useGetAstNodeOperandProps,
useOperandOptions,
} from '@app-builder/services/editor/options';
import { useFormatReturnValue } from '@app-builder/services/editor/return-value';
Expand Down Expand Up @@ -82,14 +82,14 @@ export function OperandBuilderNode({
onSave?: (astNode: AstNode) => void;
}) {
const enumValues = useEnumValuesFromNeighbour(treePath);
const getAstNodeOption = useGetAstNodeOption();

const options = useOperandOptions(enumValues);
const coerceToConstant = useDefaultCoerceToConstant();

const operandProps = React.useMemo(() => {
return getAstNodeOption(astNode, { enumValues });
}, [astNode, enumValues, getAstNodeOption]);
const getAstNodeOperandProps = useGetAstNodeOperandProps();
const astNodeOperandProps = React.useMemo(() => {
return getAstNodeOperandProps(astNode, { enumValues });
}, [astNode, enumValues, getAstNodeOperandProps]);

const evaluation = useEvaluation(treePath);
const formatReturnValue = useFormatReturnValue();
Expand All @@ -110,7 +110,7 @@ export function OperandBuilderNode({
validationStatus={validationStatus}
astNodeErrors={evaluation}
returnValue={returnValue}
{...operandProps}
{...astNodeOperandProps}
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
import {
type AstNode,
isMainAstBinaryNode,
isMainAstUnaryNode,
type MainAstBinaryNode,
type MainAstUnaryNode,
NewUndefinedAstNode,
} from '@app-builder/models';
import {
useAstNodeEditorActions,
useEvaluationErrors,
} from '@app-builder/services/editor/ast-editor';
import { useMainAstOperatorFunctions } from '@app-builder/services/editor/options';
import * as React from 'react';
import type * as React from 'react';
import { useTranslation } from 'react-i18next';
import { Switch } from 'ui-design-system';
import { MenuButton, MenuItem, MenuPopover, MenuRoot } from 'ui-design-system';
import { Icon } from 'ui-icons';

import { AstBuilderNode } from './AstBuilderNode';
import { Operator } from './Operator';

// Do not forget to clean up all stuff related to previous nesting (e.g. translations, helpers, doc...)
function NewNestedChild(node: AstNode) {
return NewUndefinedAstNode({
children: [node, NewUndefinedAstNode()],
});
}

export function MainAstBinaryOperatorLine({
treePath,
mainAstNode,
Expand All @@ -38,14 +28,22 @@ export function MainAstBinaryOperatorLine({
}) {
const { setAstNodeAtPath, setOperatorAtPath } = useAstNodeEditorActions();

function addNestedChild(stringPath: string, child: AstNode) {
setAstNodeAtPath(stringPath, NewNestedChild(child));
function removeNesting() {
const nestedChild = mainAstNode.children[0];
if (!nestedChild) return;
setAstNodeAtPath(treePath, nestedChild);
}

function removeNestedChild(stringPath: string, child: AstNode) {
const nestedChild = child.children[0];
if (!nestedChild) return;
setAstNodeAtPath(stringPath, nestedChild);
function invertOperands() {
const leftChild = mainAstNode.children[0];
const rightChild = mainAstNode.children[1];

if (!leftChild || !rightChild) return;

setAstNodeAtPath(treePath, {
...mainAstNode,
children: [rightChild, leftChild],
});
}

const operators = useMainAstOperatorFunctions();
Expand All @@ -55,16 +53,17 @@ export function MainAstBinaryOperatorLine({
const right = mainAstNode.children[1];
const rightPath = `${treePath}.children.1`;

const isNestedRight = isMainAstUnaryNode(right) || isMainAstBinaryNode(right);

const evaluationErrors = useEvaluationErrors(treePath);

const children = (
<>
<div className="group/nest contents">
{!root ? (
<span className="text-grey-100 border-grey-10 flex h-10 items-center justify-center rounded border px-2">
<NestingParenthesis
invertOperands={invertOperands}
removeNesting={removeNesting}
>
(
</span>
</NestingParenthesis>
) : null}
<AstBuilderNode
treePath={leftPath}
Expand Down Expand Up @@ -92,11 +91,14 @@ export function MainAstBinaryOperatorLine({
viewOnly={viewOnly}
/>
{!root ? (
<span className="text-grey-100 border-grey-10 flex h-10 items-center justify-center rounded border px-2">
<NestingParenthesis
invertOperands={invertOperands}
removeNesting={removeNesting}
>
)
</span>
</NestingParenthesis>
) : null}
</>
</div>
);

// remove the <div> root wrapper to flatten the structure and use a single root flex-wrap
Expand All @@ -122,6 +124,12 @@ export function MainAstUnaryOperatorLine({
}) {
const { setAstNodeAtPath, setOperatorAtPath } = useAstNodeEditorActions();

function removeNesting() {
const nestedChild = mainAstNode.children[0];
if (!nestedChild) return;
setAstNodeAtPath(treePath, nestedChild);
}

const operators = useMainAstOperatorFunctions();

const left = mainAstNode.children[0];
Expand All @@ -130,47 +138,93 @@ export function MainAstUnaryOperatorLine({
const evaluationErrors = useEvaluationErrors(treePath);

return (
<div className="flex justify-between gap-2">
<div className="flex flex-row flex-wrap items-center gap-2">
{!root ? <span className="text-grey-25">(</span> : null}
<AstBuilderNode
treePath={leftPath}
astNode={left}
onSave={(astNode) => {
setAstNodeAtPath(leftPath, astNode);
}}
viewOnly={viewOnly}
/>
<Operator
value={mainAstNode.name}
setValue={(operator: (typeof operators)[number]) => {
setOperatorAtPath(treePath, operator);
}}
validationStatus={evaluationErrors.length > 0 ? 'error' : 'valid'}
viewOnly={viewOnly}
operators={operators}
/>
{!root ? <span className="text-grey-25">)</span> : null}
</div>
<div className="group/nest contents">
{!root ? (
<NestingParenthesis unary removeNesting={removeNesting}>
(
</NestingParenthesis>
) : null}
<AstBuilderNode
treePath={leftPath}
astNode={left}
onSave={(astNode) => {
setAstNodeAtPath(leftPath, astNode);
}}
viewOnly={viewOnly}
/>
<Operator
value={mainAstNode.name}
setValue={(operator: (typeof operators)[number]) => {
setOperatorAtPath(treePath, operator);
}}
validationStatus={evaluationErrors.length > 0 ? 'error' : 'valid'}
viewOnly={viewOnly}
operators={operators}
/>
{!root ? (
<NestingParenthesis unary removeNesting={removeNesting}>
)
</NestingParenthesis>
) : null}
</div>
);
}

function NestSwitch({
checked,
onCheckedChange,
}: {
checked: boolean;
onCheckedChange: (checked: boolean) => void;
}) {
type NestingParenthesisProps = {
children: React.ReactNode;
removeNesting: () => void;
} & ({ unary: true } | { unary?: false; invertOperands: () => void });

const NestingParenthesis = ({
children,
removeNesting,
...props
}: NestingParenthesisProps) => {
const { t } = useTranslation(['scenarios']);
const id = React.useId();

return (
<div className="flex h-10 items-center gap-2">
<label className="text-s" htmlFor={id}>
{t('scenarios:nest')}
</label>
<Switch id={id} checked={checked} onCheckedChange={onCheckedChange} />
</div>
<MenuRoot>
<MenuButton
render={
<button className="text-grey-100 border-grey-10 [.group\/nest:hover:not(:has(.group\/nest:hover))_>_&]:bg-grey-05 flex h-10 items-center justify-center rounded border px-2" />
}
>
{children}
</MenuButton>
<MenuPopover className="flex flex-col gap-2 p-2">
{!props.unary ? (
<MenuItem
onClick={props.invertOperands}
className="data-[active-item]:bg-purple-05 grid w-full select-none grid-cols-[20px_1fr] gap-1 rounded-sm p-2 outline-none"
>
<Icon
aria-hidden="true"
className="col-start-1 size-5 shrink-0"
icon="swap"
/>
<div className="col-start-2 flex flex-row gap-1 overflow-hidden">
<div className="text-grey-100 text-s w-full break-all text-start font-normal">
{t('scenarios:nesting.swap_operands')}
</div>
</div>
</MenuItem>
) : null}
<MenuItem
onClick={removeNesting}
className="data-[active-item]:bg-red-10 grid w-full select-none grid-cols-[20px_1fr] gap-1 rounded-sm p-2 outline-none"
>
<Icon
aria-hidden="true"
className="col-start-1 size-5 shrink-0 text-red-100"
icon="delete"
/>
<div className="col-start-2 flex flex-row gap-1 overflow-hidden">
<div className="text-grey-100 text-s w-full break-all text-start font-normal">
{t('scenarios:nesting.remove')}
</div>
</div>
</MenuItem>
</MenuPopover>
</MenuRoot>
);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type AstNodeErrors,
type ValidationStatus,
} from '@app-builder/services/validation/ast-node-validation';
import { type OperandOption } from '@app-builder/types/operand-options';
import { cva } from 'class-variance-authority';

import { OperandEditor } from './OperandEditor';
Expand Down Expand Up @@ -39,12 +40,7 @@ export function Operand({
viewOnly?: boolean;
validationStatus: ValidationStatus;
astNodeErrors?: AstNodeErrors;
options: {
astNode: AstNode;
dataType: DataType;
operandType: OperandType;
displayName: string;
}[];
options: OperandOption[];
coerceToConstant?: (searchValue: string) => {
astNode: ConstantAstNode<ConstantType>;
displayName: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '@app-builder/models/editable-operators';
import {
useDefaultCoerceToConstant,
useGetAstNodeOption,
useGetAstNodeOperandProps,
useOperandOptions,
} from '@app-builder/services/editor/options';
import {
Expand Down Expand Up @@ -201,15 +201,15 @@ function FilterValue({
// TODO: try to get enum values from the left operand
const filterOptions = useOperandOptions([]);
const coerceToConstant = useDefaultCoerceToConstant();
const getAstNodeOption = useGetAstNodeOption();
const getAstNodeOperandProps = useGetAstNodeOperandProps();

const operandProps = useMemo(() => {
return getAstNodeOption(filterValue);
}, [filterValue, getAstNodeOption]);
const astNodeOperandProps = useMemo(() => {
return getAstNodeOperandProps(filterValue);
}, [filterValue, getAstNodeOperandProps]);

return (
<Operand
{...operandProps}
{...astNodeOperandProps}
onSave={onSave}
options={filterOptions}
coerceToConstant={coerceToConstant}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type AstNode } from '@app-builder/models';
import {
useDefaultCoerceToConstant,
useGetAstNodeOption,
useGetAstNodeOperandProps,
useOperandOptions,
} from '@app-builder/services/editor/options';
import {
Expand Down Expand Up @@ -40,12 +40,10 @@ export function LeftOperand({
[defaultCoerceToConstant],
);

const getAstNodeOption = useGetAstNodeOption();

const operandProps = React.useMemo(
() => getAstNodeOption(astNode),
[astNode, getAstNodeOption],
);
const getAstNodeOperandProps = useGetAstNodeOperandProps();
const astNodeOperandProps = React.useMemo(() => {
return getAstNodeOperandProps(astNode);
}, [astNode, getAstNodeOperandProps]);

return (
<Operand
Expand All @@ -55,7 +53,7 @@ export function LeftOperand({
coerceToConstant={coerceToConstant}
validationStatus={validationStatus}
astNodeErrors={astNodeErrors}
{...operandProps}
{...astNodeOperandProps}
/>
);
}
Loading

0 comments on commit 839dfc8

Please sign in to comment.