Skip to content
31 changes: 16 additions & 15 deletions src/components/Dialogs/AddFormDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@
*
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
********************************************************************************/
import { forwardRef, useContext, useState, useImperativeHandle } from "react";
import React, {
forwardRef,
useContext,
useState,
useImperativeHandle,
} from "react";
import ReactDOM from "react-dom";
import ediTDorContext from "../../context/ediTDorContext";
import { checkIfFormIsInItem } from "../../utils/tdOperations";
import DialogTemplate from "./DialogTemplate";
import AddForm from "../App/AddForm";
import type { IExplicitForm, IInteractionAffordance } from "../../types/form";

export type OperationsType = "property" | "action" | "event" | "thing" | "";
export type OperationsMap = PropertyMap | ActionMap | EventMap | ThingMap;
Expand All @@ -36,23 +42,18 @@ type ThingMap =
]
| [];

export interface AddFormDialogRef {
export interface IAddFormDialogRef {
openModal: () => void;
close: () => void;
}

export interface ExplicitForm {
op: string[] | string;
href: string;
}

interface AddFormDialogProps {
interface IAddFormDialogProps {
type?: OperationsType;
interaction?: { forms?: ExplicitForm[]; type?: string };
interaction?: IInteractionAffordance;
interactionName?: string;
}

const AddFormDialog = forwardRef<AddFormDialogRef, AddFormDialogProps>(
const AddFormDialog = forwardRef<IAddFormDialogRef, IAddFormDialogProps>(
(props, ref) => {
const context: IEdiTDorContext = useContext(ediTDorContext);
const [display, setDisplay] = useState<boolean>(() => {
Expand All @@ -64,7 +65,7 @@ const AddFormDialog = forwardRef<AddFormDialogRef, AddFormDialogProps>(

const type: OperationsType = props.type || "";
const name = type && type[0].toUpperCase() + type.slice(1);
const interaction = props.interaction ?? {};
const interaction = props.interaction;
const interactionName = props.interactionName ?? "";

useImperativeHandle(ref, () => {
Expand Down Expand Up @@ -111,10 +112,10 @@ const AddFormDialog = forwardRef<AddFormDialogRef, AddFormDialogProps>(
}
};

const checkDuplicates = (form: ExplicitForm): boolean => {
const checkDuplicates = (form: IExplicitForm): boolean => {
const isDuplicate: boolean =
interaction.forms !== undefined
? checkIfFormIsInItem(form, interaction as { forms: ExplicitForm[] })
interaction?.forms !== undefined
? checkIfFormIsInItem(form, { forms: interaction.forms })
: false;
return isDuplicate;
};
Expand All @@ -131,7 +132,7 @@ const AddFormDialog = forwardRef<AddFormDialogRef, AddFormDialogProps>(
};

const onHandleEventRightButton = () => {
const form: ExplicitForm = {
const form: IExplicitForm = {
op: operations(type)
.map((x) => {
const element = document.getElementById(
Expand Down
8 changes: 4 additions & 4 deletions src/components/Dialogs/AddPropertyDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ import DialogTemplate from "./DialogTemplate";

const NO_TYPE = "undefined";

export interface AddPropertyDialogRef {
export interface IAddPropertyDialogRef {
openModal: () => void;
close?: () => void;
}

interface Property {
interface IProperty {
title: string;
description?: string;
type?: string;
Expand All @@ -43,7 +43,7 @@ interface Property {
properties?: Record<string, any>;
}

export const AddPropertyDialog = forwardRef<AddPropertyDialogRef, {}>(
export const AddPropertyDialog = forwardRef<IAddPropertyDialogRef, {}>(
(_, ref) => {
const context = useContext(ediTDorContext);
const [display, setDisplay] = React.useState<boolean>(() => {
Expand Down Expand Up @@ -115,7 +115,7 @@ export const AddPropertyDialog = forwardRef<AddPropertyDialogRef, {}>(
return;
}

const property: Property = {
const property: IProperty = {
title: (document.getElementById(`${type}-title`) as HTMLInputElement)
.value,
observable: (
Expand Down
136 changes: 68 additions & 68 deletions src/components/TDViewer/components/Action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
********************************************************************************/
import React, { useContext, useState } from "react";
import { Trash2 } from "react-feather";
import React, { useContext, useRef } from "react";
import ediTDorContext from "../../../context/ediTDorContext";
import {
buildAttributeListObject,
Expand All @@ -22,102 +21,103 @@ import InfoIconWrapper from "../../base/InfoIconWrapper";
import { getFormsTooltipContent } from "../../../utils/TooltipMapper";
import Form from "./Form";
import AddFormElement from "../base/AddFormElement";

import AffordanceButtons from "./AffordanceButtons";
import type { IInteractionAffordance } from "../../../types/form";
import { useCopiedAffordanceFocus } from "../../../hooks/useCopiedAffordanceFocus";
const alreadyRenderedKeys = ["title", "forms", "description"];

const Action: React.FC<any> = (props) => {
interface IAction {
action: IInteractionAffordance;
actionName: string;
copiedToken?: number;
onCopy: () => void;
}
const Action: React.FC<IAction> = ({
action,
actionName,
copiedToken,
onCopy,
}) => {
const context = useContext(ediTDorContext);

const [isExpanded, setIsExpanded] = useState(false);

const addFormDialog = React.useRef();
const handleOpenAddFormDialog = () => {
addFormDialog.current.openModal();
};

if (
Object.keys(props.action).length === 0 &&
props.action.constructor !== Object
) {
return (
<div className="text-3xl text-white">
Action could not be rendered because mandatory fields are missing.
</div>
);
}

const action = props.action;
const forms = separateForms(props.action.forms);

const addFormDialog = useRef<{ openModal: () => void; close: () => void }>(
null
);
const { containerRef, isExpanded, isHighlighted, setIsExpanded } =
useCopiedAffordanceFocus({ copiedToken });
const forms = separateForms(action.forms);
const attributeListObject = buildAttributeListObject(
{ name: props.actionName },
props.action,
{ name: actionName },
action,
alreadyRenderedKeys
);
const attributes = Object.keys(attributeListObject).map((x) => {
return (
<li key={x}>
{x} : {JSON.stringify(attributeListObject[x])}
</li>
);
});

const handleDeleteAction = () => {
context.removeOneOfAKindReducer("actions", props.actionName);
const handleDelete = () => {
context.removeOneOfAKindReducer("actions", actionName);
};

return (
<details
className="mb-1"
ref={containerRef}
id={`action-${actionName}`}
className={`mb-2 rounded-lg transition-all ${isExpanded ? "overflow-hidden bg-gray-500" : ""} ${isHighlighted ? "border-2 border-green-400 ring-2 ring-green-300/70" : ""}`}
open={isExpanded}
onToggle={() => setIsExpanded(!isExpanded)}
onToggle={(e) => setIsExpanded(e.currentTarget.open)}
>
<summary
className={`flex cursor-pointer items-center rounded-t-lg pl-2 text-xl font-bold text-white ${isExpanded ? "bg-gray-500" : ""}`}
>
<h3 className="flex-grow px-2">{action.title ?? props.actionName}</h3>
<summary className="flex cursor-pointer items-center py-1 pl-2 text-xl font-bold text-white">
<h3 className="flex-grow px-2">{action.title ?? actionName}</h3>

{isExpanded && (
<button
className="flex h-10 w-10 items-center justify-center self-stretch rounded-bl-md rounded-tr-md bg-gray-400 text-base"
onClick={handleDeleteAction}
>
<Trash2 size={16} color="white" />
</button>
<AffordanceButtons
copyTitle="Copy action"
deleteTitle="Delete action"
onCopy={(e) => {
e.preventDefault();
e.stopPropagation();
onCopy();
}}
onDelete={(e) => {
e.preventDefault();
e.stopPropagation();
handleDelete();
}}
/>
)}
</summary>

<div className="mb-4 rounded-b-lg bg-gray-500 px-2 pb-4">
<div className="px-2 pb-4">
{action.description && (
<div className="px-2 pb-2 text-lg text-gray-400">
{action.description}
</div>
)}
<ul className="list-disc pl-6 text-base text-gray-300">{attributes}</ul>

<div className="flex items-center justify-start pb-2 pt-2">
<InfoIconWrapper
className="flex-grow"
tooltip={getFormsTooltipContent()}
id="actions"
>
<h4 className="pr-1 text-lg font-bold text-white">Forms</h4>
</InfoIconWrapper>
</div>
<ul className="list-disc pl-6 text-base text-gray-300">
{Object.entries(attributeListObject).map(([k, v]) => (
<li key={k}>
{k}: {JSON.stringify(v)}
</li>
))}
</ul>

<InfoIconWrapper tooltip={getFormsTooltipContent()} id="actions">
<h4 className="text-lg font-bold text-white">Forms</h4>
</InfoIconWrapper>

<AddFormElement onClick={() => addFormDialog.current?.openModal()} />

<AddFormElement onClick={handleOpenAddFormDialog} />
<AddFormDialog
type={"action"}
type="action"
interaction={action}
interactionName={props.actionName}
interactionName={actionName}
ref={addFormDialog}
/>

{forms.map((form, i) => (
<Form
key={`${i}-${form.href}`}
key={`${i}-${form?.href}`}
form={form}
propName={props.actionName}
interactionType={"action"}
></Form>
propName={actionName}
interactionType="action"
/>
))}
</div>
</details>
Expand Down
52 changes: 52 additions & 0 deletions src/components/TDViewer/components/AffordanceButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/********************************************************************************
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and
*
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
********************************************************************************/
import React from "react";
import { Copy, Trash2 } from "react-feather";

interface IProps {
onCopy: (e: React.MouseEvent) => void;
onDelete: (e: React.MouseEvent) => void;
copyTitle: string;
deleteTitle: string;
}

const AffordanceButtons: React.FC<IProps> = ({
onCopy,
onDelete,
copyTitle,
deleteTitle,
}) => {
return (
<div className="flex self-stretch">
<button
aria-label={copyTitle}
className="flex w-14 items-center justify-center bg-gray-400 transition-colors hover:bg-gray-500"
title={copyTitle}
onClick={onCopy}
>
<Copy size={20} color="white" />
</button>

<button
aria-label={deleteTitle}
className="flex w-14 items-center justify-center rounded-tr-lg border-l border-gray-500 bg-gray-400 transition-colors hover:bg-gray-500"
title={deleteTitle}
onClick={onDelete}
>
<Trash2 size={20} color="white" />
</button>
</div>
);
};

export default AffordanceButtons;
Loading
Loading