Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
1d158c9
Refactor code structure for improved readability and maintainability
Maxwell2Gyamfi Nov 19, 2025
aad58e7
Add custom activity and role type selectors with search and create fu…
Maxwell2Gyamfi Nov 19, 2025
6d5b8ac
Add hide non-participating individuals feature to ActivityDiagram
Maxwell2Gyamfi Nov 20, 2025
c8afc96
Add DiagramLegend component and update ActivityDiagramWrap to filter …
Maxwell2Gyamfi Nov 20, 2025
2e28ab5
Update HideIndividuals component to accept activitiesInView prop and …
Maxwell2Gyamfi Nov 20, 2025
cee6f1f
Enhance ActivityDiagramWrap and DiagramLegend to support parts count …
Maxwell2Gyamfi Nov 20, 2025
8a72334
Add parent activity management to SetActivity component and Model class
Maxwell2Gyamfi Nov 20, 2025
c05e9a5
Enhance parent activity display in SetActivity component
Maxwell2Gyamfi Nov 20, 2025
a9fca14
Enhance activity parent management to support ancestor expansion in S…
Maxwell2Gyamfi Nov 20, 2025
7a2bf38
Enhance DiagramLegend and ActivityDiagramWrap to support activity sel…
Maxwell2Gyamfi Nov 21, 2025
20ee411
Refactor SetActivity component buttons for improved visibility and fu…
Maxwell2Gyamfi Nov 21, 2025
dab673c
Enhance DrawParticipations rendering by adjusting rectangle height, b…
Maxwell2Gyamfi Nov 21, 2025
870a03f
Remove unused color entries from activity fill configuration for clea…
Maxwell2Gyamfi Nov 21, 2025
27923d4
Enhance SetIndividual and Model components to support entity types an…
Maxwell2Gyamfi Nov 24, 2025
585295e
Enhance ActivityDiagram and related components to support sorted indi…
Maxwell2Gyamfi Nov 24, 2025
dbecd1e
Enhance ActivityDiagram and related components to support installatio…
Maxwell2Gyamfi Nov 25, 2025
a00e05e
Refactor SetIndividual component and enhance installation management
Maxwell2Gyamfi Nov 25, 2025
b4d0a11
Enhance SetActivity and DrawIndividuals components to utilize install…
Maxwell2Gyamfi Nov 25, 2025
f9c2a6b
Enhance drawing functions to expand time range for installed componen…
Maxwell2Gyamfi Nov 25, 2025
c2669a3
Ensure installation periods start at 0; clamp beginning times in draw…
Maxwell2Gyamfi Nov 25, 2025
ee3a814
Add validation for installation time inputs; ensure beginning is non-…
Maxwell2Gyamfi Nov 26, 2025
868f770
Add overlap checking and visibility handling for installations and pa…
Maxwell2Gyamfi Nov 26, 2025
d38cbdb
Add overlap checking for activity timings against installation period…
Maxwell2Gyamfi Nov 26, 2025
afb2c1c
Implement orphaned activity filtering in drawActivities and drawParti…
Maxwell2Gyamfi Nov 26, 2025
37739c4
Add updateDataset prop to EditInstalledComponent; enhance installatio…
Maxwell2Gyamfi Nov 26, 2025
89546e7
Refactor option labels in EditInstalledComponent and SetIndividual fo…
Maxwell2Gyamfi Nov 26, 2025
1187e38
Refactor entity grouping and sorting logic in ActivityDiagramWrap for…
Maxwell2Gyamfi Nov 26, 2025
50a4af9
Add EntityTypeLegend component and integrate it into ActivityDiagramW…
Maxwell2Gyamfi Nov 26, 2025
fae358b
Enhance entity representation by adding icon support and nesting leve…
Maxwell2Gyamfi Nov 27, 2025
3283f24
Enhance drawIndividuals function to include nesting level in layout c…
Maxwell2Gyamfi Nov 27, 2025
12c3c7e
Enhance drawIndividuals function to adjust path calculations for nest…
Maxwell2Gyamfi Nov 27, 2025
786fc32
Refactor drawIndividuals function to utilize input values for time ra…
Maxwell2Gyamfi Nov 27, 2025
29c3bab
Refactor drawInstallations function to replace rectangle drawing with…
Maxwell2Gyamfi Nov 27, 2025
1d89ddf
feat: Enhance installation references and participant management
Maxwell2Gyamfi Nov 27, 2025
c0de96e
feat: Update getOriginalAndSlot and getVisibleInterval functions to s…
Maxwell2Gyamfi Nov 27, 2025
091be4b
feat: Implement separator drawing logic between SystemComponent group…
Maxwell2Gyamfi Nov 27, 2025
07ca4df
feat: Enhance slot time bounds handling across components for improve…
Maxwell2Gyamfi Nov 27, 2025
16faa58
feat: Enhance time bounds handling for SystemComponents and Individua…
Maxwell2Gyamfi Nov 27, 2025
51e4639
feat: Refine SystemComponent parent validation and compatibility chec…
Maxwell2Gyamfi Nov 27, 2025
3cf1519
feat: Update terminology from "Individual" to "Entity" for consistenc…
Maxwell2Gyamfi Nov 27, 2025
bb09fd0
Refactor installation handling in diagram and model
Maxwell2Gyamfi Nov 28, 2025
e7d6080
feat: Enhance installation management with system context support in …
Maxwell2Gyamfi Nov 28, 2025
5d21076
feat: Update EntityTypeLegend and draw functions to utilize entity ic…
Maxwell2Gyamfi Nov 28, 2025
41f7307
feat: Adjust font size in EntityTypeLegend and truncate labels for vi…
Maxwell2Gyamfi Nov 28, 2025
cd2b5a6
feat: Update DrawIndividuals to manage chevron visibility for virtual…
Maxwell2Gyamfi Nov 29, 2025
54bbea1
feat: Enhance EditInstalledComponent and EditSystemComponentInstallat…
Maxwell2Gyamfi Nov 29, 2025
f46aa32
feat: Add installation management modals for SystemComponent and Inst…
Maxwell2Gyamfi Nov 29, 2025
b4424e5
feat: Refactor EditInstalledComponent and EditSystemComponentInstalla…
Maxwell2Gyamfi Nov 29, 2025
cb58165
feat: Update ActivityDiagramWrap and EditInstalledComponent to improv…
Maxwell2Gyamfi Nov 29, 2025
76ca271
feat: Add entity type selection and related UI enhancements in SetInd…
Maxwell2Gyamfi Nov 29, 2025
1853fc9
feat: Implement diagonal hatch overlay for InstalledComponent install…
Maxwell2Gyamfi Nov 29, 2025
44789dd
feat: Enhance drawIndividuals and drawParticipations functions to imp…
Maxwell2Gyamfi Nov 29, 2025
30ed151
feat: Adjust stroke width and border radius in drawParticipations for…
Maxwell2Gyamfi Nov 29, 2025
52b704b
feat: Revamp Footer and Navbar components for improved styling and la…
Maxwell2Gyamfi Nov 29, 2025
eaa2182
feat: Refactor layout and visibility logic in Footer, HideIndividuals…
Maxwell2Gyamfi Nov 29, 2025
2471bd2
Refactor participation handling and improve virtual row management
Maxwell2Gyamfi Nov 30, 2025
45044f4
feat: Update UI components for improved layout and accessibility
Maxwell2Gyamfi Nov 30, 2025
e37ec7f
feat: Update entity type icons and descriptions in EntityTypeLegend a…
Maxwell2Gyamfi Nov 30, 2025
5b5bdf2
feat: Enhance EntityTypeLegend with hatch support for components and …
Maxwell2Gyamfi Nov 30, 2025
3967e79
feat: Improve installation management UI with enhanced empty state ha…
Maxwell2Gyamfi Dec 1, 2025
6331021
feat: Enhance labelIndividuals function to incorporate activity time …
Maxwell2Gyamfi Dec 1, 2025
9c79aa2
feat: Implement circular reference checks in installation hierarchy a…
Maxwell2Gyamfi Dec 1, 2025
23b42b8
feat: Update terminology in SortIndividuals component to use "Sort En…
Maxwell2Gyamfi Dec 1, 2025
1efdc01
feat: Enhance IndividualImpl and ActivityLib to support installations…
Maxwell2Gyamfi Dec 1, 2025
1d37ea2
feat: Enhance ActivityDiagram and DrawInstallations for improved scro…
Maxwell2Gyamfi Dec 2, 2025
e32c35c
feat: Refactor components for improved styling and responsiveness in …
Maxwell2Gyamfi Dec 2, 2025
a19ed8a
feat: Enhance ActivityDiagramWrap and DiagramLegend for improved layo…
Maxwell2Gyamfi Dec 2, 2025
345288a
feat: Refactor ActivityDiagramWrap and DiagramPersistence for improve…
Maxwell2Gyamfi Dec 2, 2025
33bc63f
feat: Implement compact mode filtering for individuals in ActivityDia…
Maxwell2Gyamfi Dec 2, 2025
d1e6c4d
feat: Add search functionality to DiagramLegend; enhance styling for …
Maxwell2Gyamfi Dec 2, 2025
829b786
feat: Enhance DiagramLegend with responsive scrollbar logic; refactor…
Maxwell2Gyamfi Dec 2, 2025
2de4110
feat: Enhance ExportSvg component to include activitiesInView and act…
Maxwell2Gyamfi Dec 2, 2025
88eced2
feat: Refactor Footer and NavBar for improved layout; add ExportJsonL…
Maxwell2Gyamfi Dec 2, 2025
a34f9df
feat: Enhance NavBar dropdown styling and active item indication for …
Maxwell2Gyamfi Dec 2, 2025
024baef
feat: Refactor font size handling in labelIndividuals for improved sc…
Maxwell2Gyamfi Dec 2, 2025
6208884
feat: Enhance SortIndividuals component to support sorting of regular…
Maxwell2Gyamfi Dec 2, 2025
a799639
feat: Implement autosave functionality for ActivityDiagramWrap; add l…
Maxwell2Gyamfi Dec 3, 2025
de548ac
feat: Add activity highlighting feature in ActivityDiagram and Diagra…
Maxwell2Gyamfi Dec 3, 2025
b2edd46
feat: Implement fixed axis rendering in ActivityDiagram; add scroll t…
Maxwell2Gyamfi Dec 3, 2025
06c8b6e
feat: Enhance activity highlighting in ActivityDiagram; improve parti…
Maxwell2Gyamfi Dec 3, 2025
ca4c990
fix: Ensure window is defined before accessing innerHeight in Activit…
Maxwell2Gyamfi Dec 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
338 changes: 325 additions & 13 deletions editor-app/components/ActivityDiagram.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { useState, useEffect, MutableRefObject, JSX } from "react";
import { useState, useEffect, MutableRefObject, JSX, useRef } from "react";
import Breadcrumb from "react-bootstrap/Breadcrumb";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import { drawActivityDiagram } from "@/diagram/DrawActivityDiagram";
import { ConfigData } from "@/diagram/config";
import { Model } from "@/lib/Model";
Expand All @@ -19,6 +17,9 @@ interface Props {
rightClickActivity: (a: Activity) => void;
rightClickParticipation: (a: Activity, p: Participation) => void;
svgRef: MutableRefObject<any>;
hideNonParticipating: boolean;
sortedIndividuals?: Individual[];
highlightedActivityId?: string | null;
}

const ActivityDiagram = (props: Props) => {
Expand All @@ -34,13 +35,59 @@ const ActivityDiagram = (props: Props) => {
rightClickActivity,
rightClickParticipation,
svgRef,
hideNonParticipating,
sortedIndividuals,
highlightedActivityId,
} = props;

const [plot, setPlot] = useState({
width: 0,
height: 0,
});

const scrollContainerRef = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null); // NEW: Ref for the outer wrapper
const [wrapperHeight, setWrapperHeight] = useState(0); // NEW: State for wrapper height
const [scrollPosition, setScrollPosition] = useState({ x: 0, y: 0 });

// Track scroll position for axis positioning
const handleScroll = () => {
if (scrollContainerRef.current) {
setScrollPosition({
x: scrollContainerRef.current.scrollLeft,
y: scrollContainerRef.current.scrollTop,
});
}
};

useEffect(() => {
const container = scrollContainerRef.current;
if (container) {
container.addEventListener("scroll", handleScroll);
return () => container.removeEventListener("scroll", handleScroll);
}
}, []);

// NEW: Measure wrapper height to draw axis correctly
useEffect(() => {
if (!wrapperRef.current) return;
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
setWrapperHeight(entry.contentRect.height);
}
});
resizeObserver.observe(wrapperRef.current);
return () => resizeObserver.disconnect();
}, []);

useEffect(() => {
const container = scrollContainerRef.current;
if (container) {
container.addEventListener("scroll", handleScroll);
return () => container.removeEventListener("scroll", handleScroll);
}
}, []);

useEffect(() => {
setPlot(
drawActivityDiagram(
Expand All @@ -53,9 +100,96 @@ const ActivityDiagram = (props: Props) => {
clickParticipation,
rightClickIndividual,
rightClickActivity,
rightClickParticipation
rightClickParticipation,
hideNonParticipating,
sortedIndividuals
)
);

// Apply highlighting logic to participation rects (the actual visible colored blocks)
const svg = svgRef.current;
if (svg) {
// Target participation-rect elements (the visible colored blocks)
const allParticipationRects = svg.querySelectorAll(".participation-rect");

if (highlightedActivityId) {
// Dim all participation rects
allParticipationRects.forEach((el: SVGElement) => {
el.style.opacity = "0.15";
el.style.stroke = "";
el.style.strokeWidth = "";
});

// Track bounding box of all highlighted rects
let minX = Infinity,
minY = Infinity,
maxX = -Infinity,
maxY = -Infinity;
let foundHighlighted = false;

// Highlight participation rects belonging to the selected activity
// Participation rect IDs are in format: p_{activityId}_{individualId}_{segStart}_{segEnd}
allParticipationRects.forEach((el: SVGElement) => {
const elId = el.getAttribute("id") || "";

// Check if this participation rect belongs to the highlighted activity
// ID format: p_{activityId}_{rest...}
if (elId.startsWith("p_" + highlightedActivityId + "_")) {
el.style.opacity = "1";
el.style.stroke = "#000";
el.style.strokeWidth = "2px";
// Bring to front
el.parentNode?.appendChild(el);

// Calculate bounding box from this element
const rect = el as SVGGraphicsElement;
const bbox = rect.getBBox?.();
if (bbox) {
foundHighlighted = true;
minX = Math.min(minX, bbox.x);
minY = Math.min(minY, bbox.y);
maxX = Math.max(maxX, bbox.x + bbox.width);
maxY = Math.max(maxY, bbox.y + bbox.height);
}
}
});

// Remove any existing highlight borders first
svg
.querySelectorAll(".highlight-border")
.forEach((el: Element) => el.remove());

// Draw a dashed border around the calculated bounding box
if (foundHighlighted && minX < Infinity) {
const ns = "http://www.w3.org/2000/svg";
const highlightRect = document.createElementNS(ns, "rect");
highlightRect.setAttribute("class", "highlight-border");
highlightRect.setAttribute("x", String(minX - 3));
highlightRect.setAttribute("y", String(minY - 3));
highlightRect.setAttribute("width", String(maxX - minX + 6));
highlightRect.setAttribute("height", String(maxY - minY + 6));
highlightRect.setAttribute("fill", "none");
highlightRect.setAttribute("stroke", "#000000");
highlightRect.setAttribute("stroke-width", "2");
highlightRect.setAttribute("stroke-dasharray", "6,3");
highlightRect.setAttribute("rx", "6");
highlightRect.setAttribute("pointer-events", "none");
svg.appendChild(highlightRect);
}
} else {
// Reset styles if nothing highlighted
allParticipationRects.forEach((el: SVGElement) => {
el.style.opacity = "";
el.style.stroke = "";
el.style.strokeWidth = "";
});

// Remove any highlight borders
svg
.querySelectorAll(".highlight-border")
.forEach((el: Element) => el.remove());
}
}
}, [
dataset,
configData,
Expand All @@ -67,6 +201,9 @@ const ActivityDiagram = (props: Props) => {
rightClickIndividual,
rightClickActivity,
rightClickParticipation,
hideNonParticipating,
sortedIndividuals,
highlightedActivityId,
]);

const buildCrumbs = () => {
Expand All @@ -78,27 +215,202 @@ const ActivityDiagram = (props: Props) => {
const text = act ? act.name : <i>{dataset.name ?? "Top"}</i>;
context.push(
<Breadcrumb.Item
active={ id == activityContext }
active={id == activityContext}
linkProps={{ onClick: () => setActivityContext(link) }}
key={id ?? "."}
>{text}</Breadcrumb.Item>);
if (id == undefined)
break;
>
{text}
</Breadcrumb.Item>
);
if (id == undefined) break;
id = act!.partOf;
}
return context.reverse();
};
const crumbs: JSX.Element[] = buildCrumbs();

// Axis configuration
const axisMargin = configData.presentation.axis.margin;
const axisWidth = configData.presentation.axis.width;
const axisColour = configData.presentation.axis.colour;
const axisEndMargin = configData.presentation.axis.endMargin;

// Calculate visible dimensions
// Use measured wrapper height, fallback to calculation if 0 (initial render)
// FIX: Check if window is defined before accessing innerHeight
const containerHeight =
wrapperHeight ||
Math.min(
plot.height,
(typeof window !== "undefined" ? window.innerHeight : 800) - 250
);
const bottomAxisHeight = axisMargin + 30;

return (
<>
<Breadcrumb>{crumbs}</Breadcrumb>
<div id="activity-diagram-scrollable-div" style={{ overflowX: "auto" }}>
<svg
viewBox={`0 0 ${plot.width} ${plot.height}`}
ref={svgRef}
style={{ minWidth: configData.viewPort.zoom * 100 + "%" }}
<div
ref={wrapperRef} // Attach ref here
style={{
position: "relative",
border: "1px solid #e0e0e0",
borderRadius: "4px",
backgroundColor: "#fafafa",
}}
>
{/* Fixed Y-Axis (Space) - left side */}
<div
style={{
position: "absolute",
left: 0,
top: 0,
width: `${axisMargin + 30}px`,
height: "100%",
backgroundColor: "#fafafa",
zIndex: 10,
pointerEvents: "none",
borderRight: "1px solid #e5e5e5",
}}
>
<svg
width={axisMargin + 30}
height="100%"
style={{ display: "block" }}
>
{/* Y-Axis arrow */}
<defs>
<marker
id="triangle-y"
refX="0"
refY="10"
markerWidth="20"
markerHeight="20"
markerUnits="userSpaceOnUse"
orient="auto"
>
<path d="M 0 0 L 20 10 L 0 20 z" fill={axisColour} />
</marker>
</defs>
<line
x1={axisMargin}
y1={containerHeight - bottomAxisHeight + axisWidth / 2} // Adjusted to stop exactly at X-axis
x2={axisMargin}
y2={axisEndMargin}
stroke={axisColour}
strokeWidth={axisWidth}
markerEnd="url(#triangle-y)"
/>
{/* Y-Axis label "Space" */}
<text
x={axisMargin + configData.presentation.axis.textOffsetY}
y={containerHeight / 2 + axisMargin}
fill="white"
stroke="white"
fontSize="0.8em"
fontWeight="200"
textAnchor="middle"
fontFamily="Roboto, Arial, sans-serif"
transform={`rotate(270 ${
axisMargin + configData.presentation.axis.textOffsetY
} ${containerHeight / 2 + axisMargin})`}
>
Space
</text>
</svg>
</div>

{/* Fixed X-Axis (Time) - bottom */}
<div
style={{
position: "absolute",
left: `${axisMargin + 30}px`,
bottom: 0,
right: 0,
height: `${bottomAxisHeight}px`,
backgroundColor: "#fafafa",
zIndex: 10,
pointerEvents: "none",
borderTop: "1px solid #e5e5e5",
}}
>
<svg
width="100%"
height={bottomAxisHeight}
style={{ display: "block" }}
>
{/* X-Axis arrow */}
<defs>
<marker
id="triangle-x"
refX="0"
refY="10"
markerWidth="20"
markerHeight="20"
markerUnits="userSpaceOnUse"
orient="auto"
>
<path d="M 0 0 L 20 10 L 0 20 z" fill={axisColour} />
</marker>
</defs>
<line
x1={0}
y1={axisMargin}
x2={`calc(100% - ${axisEndMargin}px)`}
y2={axisMargin}
stroke={axisColour}
strokeWidth={axisWidth}
markerEnd="url(#triangle-x)"
/>
{/* X-Axis label "Time" */}
<text
x="50%"
y={axisMargin + configData.presentation.axis.textOffsetX}
fill="white"
stroke="white"
fontSize="0.8em"
fontWeight="200"
textAnchor="middle"
fontFamily="Roboto, Arial, sans-serif"
>
Time
</text>
</svg>
</div>

{/* Corner piece to cover overlap */}
<div
style={{
position: "absolute",
left: 0,
bottom: 0,
width: `${axisMargin + 30}px`,
height: `${bottomAxisHeight}px`,
backgroundColor: "#fafafa",
zIndex: 11,
}}
/>

{/* Scrollable diagram content */}
<div
id="activity-diagram-scrollable-div"
ref={scrollContainerRef}
style={{
overflowX: "auto",
overflowY: "auto",
maxHeight: `calc(100vh - 250px)`,
marginLeft: `${axisMargin + 30}px`,
marginBottom: `${bottomAxisHeight}px`,
}}
>
<svg
viewBox={`0 0 ${plot.width} ${plot.height}`}
ref={svgRef}
style={{
minWidth: configData.viewPort.zoom * 100 + "%",
display: "block",
}}
/>
</div>
</div>
</>
);
Expand Down
Loading