feat: Add dragging and rotating state management to simulation store
- Introduced `useIsDragging` and `useIsRotating` stores to manage dragging and rotating states. - Each store maintains its own state and provides setter functions. refactor: Enhance storage unit and vehicle stores - Added `clearStorageUnits` and `clearvehicles` methods to clear respective stores. - Prevent duplicate entries in `addStorageUnit` and `addVehicle` methods by checking for existing model UUIDs. style: Update SCSS variables and improve styling consistency - Refactored background gradient variables for better readability. - Introduced log indication colors for better visual feedback. - Cleaned up and organized styles in various components for improved maintainability. chore: Remove unused ROISummary styles - Deleted `ROISummary.scss` as it was no longer needed. feat: Implement new analysis component styles - Created `analysis.scss` for the new analysis component layout and styles. - Added styles for various sections including metrics, throughput values, and progress bars. fix: Update main styles import - Adjusted imports in `main.scss` to reflect the new structure and removed obsolete imports.
This commit is contained in:
@@ -137,7 +137,7 @@ function MachineMechanics() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger />
|
||||
<Trigger selectedPointData={selectedPointData as any} type= {'Machine'} />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
@@ -280,7 +280,7 @@ function RoboticArmMechanics() {
|
||||
/>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger />
|
||||
<Trigger selectedPointData={selectedPointData as any} type= {'RoboticArm'} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -146,7 +146,7 @@ function StorageMechanics() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger />
|
||||
<Trigger selectedPointData={selectedPointData as any} type= {'StorageUnit'} />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
@@ -235,7 +235,7 @@ function VehicleMechanics() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="tirgger">
|
||||
<Trigger />
|
||||
<Trigger selectedPointData={selectedPointData as any} type= {'Vehicle'} />
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import * as THREE from "three";
|
||||
import {
|
||||
AddIcon,
|
||||
RemoveIcon,
|
||||
@@ -9,6 +10,7 @@ import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||
import { handleResize } from "../../../../../../functions/handleResizePannel";
|
||||
import { useProductStore } from "../../../../../../store/simulation/useProductStore";
|
||||
import { useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore";
|
||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi";
|
||||
|
||||
type TriggerProps = {
|
||||
selectedPointData?: PointsScheme | undefined;
|
||||
@@ -18,44 +20,223 @@ type TriggerProps = {
|
||||
const Trigger = ({ selectedPointData, type }: TriggerProps) => {
|
||||
const [currentAction, setCurrentAction] = useState<string | undefined>();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
const { getActionByUuid } = useProductStore();
|
||||
const { getActionByUuid, addTrigger, removeTrigger, updateTrigger, renameTrigger, getProductById } = useProductStore();
|
||||
const [triggers, setTriggers] = useState<TriggerSchema[]>([]);
|
||||
const [selectedTrigger, setSelectedTrigger] = useState<TriggerSchema | undefined>();
|
||||
const [activeOption, setActiveOption] = useState("onComplete");
|
||||
const [activeOption, setActiveOption] = useState<"onComplete" | "onStart" | "onStop" | "delay" | "onError">("onComplete");
|
||||
const triggersContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedPointData) return;
|
||||
if (!selectedPointData || !selectedProduct) return;
|
||||
|
||||
let actionUuid: string | undefined;
|
||||
|
||||
if (type === 'Conveyor' || type === 'Vehicle' || type === 'Machine' || type === 'StorageUnit') {
|
||||
setCurrentAction((selectedPointData as ConveyorPointSchema).action.actionUuid);
|
||||
actionUuid = (selectedPointData as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema).action?.actionUuid;
|
||||
} else if (type === 'RoboticArm') {
|
||||
actionUuid = (selectedPointData as RoboticArmPointSchema).actions[0]?.actionUuid;
|
||||
}
|
||||
}, [selectedPointData]);
|
||||
|
||||
setCurrentAction(actionUuid);
|
||||
}, [selectedPointData, selectedProduct, type]);
|
||||
|
||||
|
||||
const updateBackend = (
|
||||
productName: string,
|
||||
productId: string,
|
||||
organization: string,
|
||||
eventData: EventsSchema
|
||||
) => {
|
||||
upsertProductOrEventApi({
|
||||
productName: productName,
|
||||
productId: productId,
|
||||
organization: organization,
|
||||
eventDatas: eventData
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentAction || !selectedProduct) return;
|
||||
const action = getActionByUuid(selectedProduct.productId, currentAction);
|
||||
setTriggers(action?.triggers || []);
|
||||
setSelectedTrigger(action?.triggers[0] || undefined);
|
||||
const actionTriggers = action?.triggers || [];
|
||||
setTriggers(actionTriggers);
|
||||
setSelectedTrigger(actionTriggers[0]);
|
||||
}, [currentAction, selectedProduct]);
|
||||
|
||||
const addTrigger = (): void => {
|
||||
const handleAddTrigger = () => {
|
||||
if (!selectedProduct || !currentAction) return;
|
||||
|
||||
const newTrigger: TriggerSchema = {
|
||||
triggerUuid: THREE.MathUtils.generateUUID(),
|
||||
triggerName: `New Trigger ${triggers.length + 1}`,
|
||||
triggerType: activeOption,
|
||||
delay: 0,
|
||||
triggeredAsset: null
|
||||
};
|
||||
|
||||
addTrigger(selectedProduct.productId, currentAction, newTrigger);
|
||||
setSelectedTrigger(newTrigger);
|
||||
};
|
||||
|
||||
const removeTrigger = (triggerUuid: string): void => {
|
||||
const handleRemoveTrigger = (triggerUuid: string) => {
|
||||
if (!selectedProduct) return;
|
||||
removeTrigger(selectedProduct.productId, triggerUuid);
|
||||
if (selectedTrigger?.triggerUuid === triggerUuid) {
|
||||
const remainingTriggers = triggers.filter(t => t.triggerUuid !== triggerUuid);
|
||||
setSelectedTrigger(remainingTriggers[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTriggerRename = (triggerUuid: string, newName: string) => {
|
||||
if (!selectedProduct) return;
|
||||
renameTrigger(selectedProduct.productId, triggerUuid, newName);
|
||||
};
|
||||
|
||||
const handleTriggerTypeChange = (option: string) => {
|
||||
if (!selectedTrigger || !selectedProduct) return;
|
||||
|
||||
const validTypes: Array<TriggerSchema['triggerType']> = ["onComplete", "onStart", "onStop", "delay", "onError"];
|
||||
if (!validTypes.includes(option as TriggerSchema['triggerType'])) return;
|
||||
|
||||
setActiveOption(option as TriggerSchema['triggerType']);
|
||||
updateTrigger(selectedProduct.productId, selectedTrigger.triggerUuid, {
|
||||
triggerType: option as TriggerSchema['triggerType']
|
||||
});
|
||||
};
|
||||
|
||||
const triggeredModel = selectedTrigger?.triggeredAsset?.triggeredModel || { modelName: "Select Model", modelUuid: "" };
|
||||
const triggeredPoint = selectedTrigger?.triggeredAsset?.triggeredPoint || { pointName: "Select Point", pointUuid: "" };
|
||||
const triggeredAction = selectedTrigger?.triggeredAsset?.triggeredAction || { actionName: "Select Action", actionUuid: "" };
|
||||
|
||||
const modelOptions = getProductById(selectedProduct.productId)?.eventDatas || [];
|
||||
|
||||
const pointOptions: PointsScheme[] = useMemo(() => {
|
||||
if (!triggeredModel.modelUuid) return [];
|
||||
|
||||
const model = modelOptions.find(m => m.modelUuid === triggeredModel.modelUuid);
|
||||
if (!model) return [];
|
||||
|
||||
if ('points' in model) {
|
||||
return (model as ConveyorEventSchema).points;
|
||||
} else if ('point' in model) {
|
||||
return [(model as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point];
|
||||
}
|
||||
return [];
|
||||
}, [triggeredModel.modelUuid, modelOptions]);
|
||||
|
||||
const actionOptions: any = useMemo(() => {
|
||||
if (!triggeredPoint.pointUuid) return [];
|
||||
const point = pointOptions.find((p) => p.uuid === triggeredPoint.pointUuid);
|
||||
if (!point) return [];
|
||||
|
||||
if ('action' in point) {
|
||||
const typedPoint = point as ConveyorPointSchema | VehiclePointSchema | MachinePointSchema | StoragePointSchema;
|
||||
return typedPoint.action ? [typedPoint.action] : [];
|
||||
} else if ('actions' in point) {
|
||||
const typedPoint = point as RoboticArmPointSchema;
|
||||
return typedPoint.actions;
|
||||
}
|
||||
return [];
|
||||
}, [triggeredPoint.pointUuid, pointOptions]);
|
||||
|
||||
const handleModelSelect = (option: string, triggerUuid: string) => {
|
||||
if (!selectedProduct) return;
|
||||
|
||||
const selectedModel = modelOptions.find(m => m.modelName === option);
|
||||
if (!selectedModel) return;
|
||||
|
||||
const event = updateTrigger(selectedProduct.productId, triggerUuid, {
|
||||
triggeredAsset: {
|
||||
triggeredModel: {
|
||||
modelName: selectedModel.modelName,
|
||||
modelUuid: selectedModel.modelUuid
|
||||
},
|
||||
triggeredPoint: null,
|
||||
triggeredAction: null
|
||||
}
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productId,
|
||||
organization,
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePointSelect = (option: string, triggerUuid: string) => {
|
||||
if (!selectedProduct || !selectedTrigger) return;
|
||||
|
||||
const pointUuid = pointOptions.find(p => `Point ${p.uuid.slice(0, 5)}` === option)?.uuid;
|
||||
|
||||
if (!pointUuid) return;
|
||||
|
||||
if (selectedTrigger.triggeredAsset?.triggeredModel) {
|
||||
const event = updateTrigger(selectedProduct.productId, triggerUuid, {
|
||||
triggeredAsset: {
|
||||
...selectedTrigger.triggeredAsset,
|
||||
triggeredPoint: {
|
||||
pointName: option,
|
||||
pointUuid: pointUuid
|
||||
},
|
||||
triggeredAction: null
|
||||
}
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productId,
|
||||
organization,
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleActionSelect = (option: string, triggerUuid: string) => {
|
||||
if (!selectedProduct || !selectedTrigger) return;
|
||||
|
||||
const selectedAction = actionOptions.find((a: any) => a.actionName === option);
|
||||
|
||||
if (!selectedAction) return;
|
||||
|
||||
if (selectedTrigger.triggeredAsset?.triggeredPoint) {
|
||||
const event = updateTrigger(selectedProduct.productId, triggerUuid, {
|
||||
triggeredAsset: {
|
||||
...selectedTrigger.triggeredAsset,
|
||||
triggeredAction: {
|
||||
actionName: option,
|
||||
actionUuid: selectedAction.actionUuid
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (event) {
|
||||
updateBackend(
|
||||
selectedProduct.productName,
|
||||
selectedProduct.productId,
|
||||
organization,
|
||||
event
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="trigger-wrapper">
|
||||
<div className="header">
|
||||
<div className="title">Trigger</div>
|
||||
<button
|
||||
className="add-button"
|
||||
onClick={addTrigger}
|
||||
onClick={handleAddTrigger}
|
||||
style={{ cursor: "pointer" }}
|
||||
disabled={!currentAction}
|
||||
>
|
||||
<AddIcon /> Add
|
||||
</button>
|
||||
@@ -73,13 +254,19 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => {
|
||||
className={`list-item ${selectedTrigger?.triggerUuid === trigger.triggerUuid ? "active" : ""}`}
|
||||
onClick={() => setSelectedTrigger(trigger)}
|
||||
>
|
||||
<button className="value" onClick={() => { }}>
|
||||
<RenameInput value={trigger.triggerName} onRename={() => { }} />
|
||||
<button className="value">
|
||||
<RenameInput
|
||||
value={trigger.triggerName}
|
||||
onRename={(newName) => handleTriggerRename(trigger.triggerUuid, newName)}
|
||||
/>
|
||||
</button>
|
||||
{triggers.length > 1 && (
|
||||
<button
|
||||
className="remove-button"
|
||||
onClick={() => removeTrigger(trigger.triggerUuid)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRemoveTrigger(trigger.triggerUuid);
|
||||
}}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</button>
|
||||
@@ -95,35 +282,39 @@ const Trigger = ({ selectedPointData, type }: TriggerProps) => {
|
||||
<ResizeHeightIcon />
|
||||
</button>
|
||||
</div>
|
||||
<div className="trigger-item">
|
||||
<div className="trigger-name">{selectedTrigger?.triggerName}</div>
|
||||
<LabledDropdown
|
||||
label="Trigger Type"
|
||||
defaultOption={activeOption}
|
||||
options={["onComplete", "onStart", "onStop", "delay"]}
|
||||
onSelect={(option) => setActiveOption(option)}
|
||||
/>
|
||||
<div className="trigger-options">
|
||||
|
||||
{selectedTrigger && (
|
||||
<div className="trigger-item">
|
||||
<div className="trigger-name">{selectedTrigger.triggerName}</div>
|
||||
<LabledDropdown
|
||||
label="Triggered Object"
|
||||
defaultOption={triggeredModel.modelName}
|
||||
options={[]}
|
||||
onSelect={(option) => { }}
|
||||
/>
|
||||
<LabledDropdown
|
||||
label="Triggered Point"
|
||||
defaultOption={triggeredPoint.pointName}
|
||||
options={[]}
|
||||
onSelect={(option) => { }}
|
||||
/>
|
||||
<LabledDropdown
|
||||
label="Triggered Action"
|
||||
defaultOption={triggeredAction.actionName}
|
||||
options={[]}
|
||||
onSelect={(option) => { }}
|
||||
label="Trigger Type"
|
||||
defaultOption={selectedTrigger.triggerType}
|
||||
options={["onComplete", "onStart", "onStop", "delay", "onError"]}
|
||||
onSelect={handleTriggerTypeChange}
|
||||
/>
|
||||
|
||||
<div className="trigger-options">
|
||||
<LabledDropdown
|
||||
label="Triggered Object"
|
||||
defaultOption={triggeredModel.modelName}
|
||||
options={[...modelOptions.map((option) => (option.modelName))]}
|
||||
onSelect={(option) => { handleModelSelect(option, selectedTrigger.triggerUuid) }}
|
||||
/>
|
||||
<LabledDropdown
|
||||
label="Triggered Point"
|
||||
defaultOption={triggeredPoint.pointName}
|
||||
options={[...pointOptions.map((option) => (`Point ${option.uuid.slice(0, 5)}`))]}
|
||||
onSelect={(option) => { handlePointSelect(option, selectedTrigger.triggerUuid) }}
|
||||
/>
|
||||
<LabledDropdown
|
||||
label="Triggered Action"
|
||||
defaultOption={triggeredAction.actionName}
|
||||
options={[...actionOptions.map((option: any) => (option.actionName))]}
|
||||
onSelect={(option) => { handleActionSelect(option, selectedTrigger.triggerUuid) }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import {
|
||||
AddIcon,
|
||||
ArrowIcon,
|
||||
RemoveIcon,
|
||||
ResizeHeightIcon,
|
||||
AddIcon,
|
||||
ArrowIcon,
|
||||
RemoveIcon,
|
||||
ResizeHeightIcon,
|
||||
} from "../../../icons/ExportCommonIcons";
|
||||
import RenameInput from "../../../ui/inputs/RenameInput";
|
||||
import { handleResize } from "../../../../functions/handleResizePannel";
|
||||
import {
|
||||
useSelectedAsset,
|
||||
useSelectedProduct,
|
||||
useSelectedAsset,
|
||||
useSelectedProduct,
|
||||
} from "../../../../store/simulation/useSimulationStore";
|
||||
import { useProductStore } from "../../../../store/simulation/useProductStore";
|
||||
import { generateUUID } from "three/src/math/MathUtils";
|
||||
@@ -22,206 +22,222 @@ import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertP
|
||||
import { deleteProductApi } from "../../../../services/simulation/deleteProductApi";
|
||||
|
||||
interface Event {
|
||||
pathName: string;
|
||||
pathName: string;
|
||||
}
|
||||
|
||||
interface ListProps {
|
||||
val: Event;
|
||||
val: Event;
|
||||
}
|
||||
|
||||
const List: React.FC<ListProps> = ({ val }) => {
|
||||
return (
|
||||
<div className="process-container">
|
||||
<div className="value">{val.pathName}</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="process-container">
|
||||
<div className="value">{val.pathName}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Simulations: React.FC = () => {
|
||||
const productsContainerRef = useRef<HTMLDivElement>(null);
|
||||
const { products, addProduct, removeProduct, renameProduct, addEvent, removeEvent, } = useProductStore();
|
||||
const { selectedProduct, setSelectedProduct } = useSelectedProduct();
|
||||
const { getEventByModelUuid } = useEventsStore();
|
||||
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
const [openObjects, setOpenObjects] = useState(true);
|
||||
const productsContainerRef = useRef<HTMLDivElement>(null);
|
||||
const {
|
||||
products,
|
||||
addProduct,
|
||||
removeProduct,
|
||||
renameProduct,
|
||||
addEvent,
|
||||
removeEvent,
|
||||
} = useProductStore();
|
||||
const { selectedProduct, setSelectedProduct } = useSelectedProduct();
|
||||
const { getEventByModelUuid } = useEventsStore();
|
||||
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
const [openObjects, setOpenObjects] = useState(true);
|
||||
|
||||
const handleAddProduct = () => {
|
||||
const id = generateUUID();
|
||||
const name = `Product ${products.length + 1}`;
|
||||
addProduct(name, id);
|
||||
upsertProductOrEventApi({ productName: name, productId: id, organization: organization });
|
||||
};
|
||||
const handleAddProduct = () => {
|
||||
const id = generateUUID();
|
||||
const name = `Product ${products.length + 1}`;
|
||||
addProduct(name, id);
|
||||
upsertProductOrEventApi({
|
||||
productName: name,
|
||||
productId: id,
|
||||
organization: organization,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveProduct = (productId: string) => {
|
||||
const currentIndex = products.findIndex((p) => p.productId === productId);
|
||||
const isSelected = selectedProduct.productId === productId;
|
||||
const handleRemoveProduct = (productId: string) => {
|
||||
const currentIndex = products.findIndex((p) => p.productId === productId);
|
||||
const isSelected = selectedProduct.productId === productId;
|
||||
|
||||
const updatedProducts = products.filter((p) => p.productId !== productId);
|
||||
const updatedProducts = products.filter((p) => p.productId !== productId);
|
||||
|
||||
if (isSelected) {
|
||||
if (updatedProducts.length > 0) {
|
||||
let newSelectedIndex = currentIndex;
|
||||
if (currentIndex >= updatedProducts.length) {
|
||||
newSelectedIndex = updatedProducts.length - 1;
|
||||
}
|
||||
setSelectedProduct(
|
||||
updatedProducts[newSelectedIndex].productId,
|
||||
updatedProducts[newSelectedIndex].productName
|
||||
);
|
||||
} else {
|
||||
setSelectedProduct("", "");
|
||||
}
|
||||
if (isSelected) {
|
||||
if (updatedProducts.length > 0) {
|
||||
let newSelectedIndex = currentIndex;
|
||||
if (currentIndex >= updatedProducts.length) {
|
||||
newSelectedIndex = updatedProducts.length - 1;
|
||||
}
|
||||
setSelectedProduct(
|
||||
updatedProducts[newSelectedIndex].productId,
|
||||
updatedProducts[newSelectedIndex].productName
|
||||
);
|
||||
} else {
|
||||
setSelectedProduct("", "");
|
||||
}
|
||||
}
|
||||
|
||||
removeProduct(productId);
|
||||
deleteProductApi(productId, organization);
|
||||
};
|
||||
removeProduct(productId);
|
||||
deleteProductApi(productId, organization);
|
||||
};
|
||||
|
||||
const handleRenameProduct = (productId: string, newName: string) => {
|
||||
renameProduct(productId, newName);
|
||||
if (selectedProduct.productId === productId) {
|
||||
setSelectedProduct(productId, newName);
|
||||
}
|
||||
};
|
||||
const handleRenameProduct = (productId: string, newName: string) => {
|
||||
renameProduct(productId, newName);
|
||||
if (selectedProduct.productId === productId) {
|
||||
setSelectedProduct(productId, newName);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveEventFromProduct = () => {
|
||||
if (selectedAsset) {
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
deleteEventDataApi({
|
||||
productId: selectedProduct.productId,
|
||||
modelUuid: selectedAsset.modelUuid,
|
||||
organization: organization
|
||||
});
|
||||
removeEvent(selectedProduct.productId, selectedAsset.modelUuid);
|
||||
clearSelectedAsset();
|
||||
}
|
||||
};
|
||||
const handleRemoveEventFromProduct = () => {
|
||||
if (selectedAsset) {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
deleteEventDataApi({
|
||||
productId: selectedProduct.productId,
|
||||
modelUuid: selectedAsset.modelUuid,
|
||||
organization: organization,
|
||||
});
|
||||
removeEvent(selectedProduct.productId, selectedAsset.modelUuid);
|
||||
clearSelectedAsset();
|
||||
}
|
||||
};
|
||||
|
||||
const selectedProductData = products.find(
|
||||
(product) => product.productId === selectedProduct.productId
|
||||
);
|
||||
const selectedProductData = products.find(
|
||||
(product) => product.productId === selectedProduct.productId
|
||||
);
|
||||
|
||||
const events: Event[] = selectedProductData?.eventDatas.map((event) => ({
|
||||
pathName: event.modelName,
|
||||
const events: Event[] =
|
||||
selectedProductData?.eventDatas.map((event) => ({
|
||||
pathName: event.modelName,
|
||||
})) || [];
|
||||
|
||||
return (
|
||||
<div className="simulations-container">
|
||||
<div className="header">Simulations</div>
|
||||
<div className="add-product-container">
|
||||
<div className="actions section">
|
||||
<div className="header">
|
||||
<div className="header-value">Products</div>
|
||||
<div className="add-button" onClick={handleAddProduct}>
|
||||
<AddIcon /> Add
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="lists-main-container"
|
||||
ref={productsContainerRef}
|
||||
style={{ height: "120px" }}
|
||||
>
|
||||
<div className="list-container">
|
||||
{products.map((product, index) => (
|
||||
<div
|
||||
key={product.productId}
|
||||
className={`list-item ${selectedProduct.productId === product.productId
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className="value"
|
||||
onClick={() =>
|
||||
setSelectedProduct(product.productId, product.productName)
|
||||
}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="products"
|
||||
checked={selectedProduct.productId === product.productId}
|
||||
readOnly
|
||||
/>
|
||||
<RenameInput
|
||||
value={product.productName}
|
||||
onRename={(newName) =>
|
||||
handleRenameProduct(product.productId, newName)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{products.length > 1 && (
|
||||
<div
|
||||
className="remove-button"
|
||||
onClick={() => handleRemoveProduct(product.productId)}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className="resize-icon"
|
||||
id="action-resize"
|
||||
onMouseDown={(e) => handleResize(e, productsContainerRef)}
|
||||
>
|
||||
<ResizeHeightIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="simulation-process section">
|
||||
<button
|
||||
className="collapse-header-container"
|
||||
onClick={() => setOpenObjects(!openObjects)}
|
||||
>
|
||||
<div className="header">Events</div>
|
||||
<div className="arrow-container">
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</button>
|
||||
{openObjects &&
|
||||
events.map((event, index) => <List key={index} val={event} />)}
|
||||
</div>
|
||||
|
||||
<div className="compare-simulations-container">
|
||||
<div className="compare-simulations-header">
|
||||
Need to Compare Layout?
|
||||
</div>
|
||||
<div className="content">
|
||||
Click <span>'Compare'</span> to review and analyze the layout
|
||||
differences between them.
|
||||
</div>
|
||||
<div className="input">
|
||||
<input type="button" value={"Compare"} className="submit" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedAsset && (
|
||||
<RenderOverlay>
|
||||
<EditWidgetOption
|
||||
options={["Add to Product", "Remove from Product"]}
|
||||
onClick={(option) => {
|
||||
if (option === "Add to Product") {
|
||||
handleAddEventToProduct({
|
||||
event: getEventByModelUuid(selectedAsset.modelUuid),
|
||||
addEvent,
|
||||
selectedProduct,
|
||||
clearSelectedAsset
|
||||
});
|
||||
} else {
|
||||
handleRemoveEventFromProduct();
|
||||
}
|
||||
}}
|
||||
return (
|
||||
<div className="simulations-container">
|
||||
<div className="header">Simulations</div>
|
||||
<div className="add-product-container">
|
||||
<div className="actions section">
|
||||
<div className="header">
|
||||
<div className="header-value">Products</div>
|
||||
<button className="add-button" onClick={handleAddProduct}>
|
||||
<AddIcon /> Add
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="lists-main-container"
|
||||
ref={productsContainerRef}
|
||||
style={{ height: "120px" }}
|
||||
>
|
||||
<div className="list-container">
|
||||
{products.map((product, index) => (
|
||||
<div
|
||||
key={product.productId}
|
||||
className={`list-item ${
|
||||
selectedProduct.productId === product.productId
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{/* eslint-disable-next-line */}
|
||||
<div
|
||||
className="value"
|
||||
onClick={() =>
|
||||
setSelectedProduct(product.productId, product.productName)
|
||||
}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="products"
|
||||
checked={selectedProduct.productId === product.productId}
|
||||
readOnly
|
||||
/>
|
||||
</RenderOverlay>
|
||||
)}
|
||||
<RenameInput
|
||||
value={product.productName}
|
||||
onRename={(newName) =>
|
||||
handleRenameProduct(product.productId, newName)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{products.length > 1 && (
|
||||
<button
|
||||
className="remove-button"
|
||||
onClick={() => handleRemoveProduct(product.productId)}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
className="resize-icon"
|
||||
id="action-resize"
|
||||
onMouseDown={(e: any) => handleResize(e, productsContainerRef)}
|
||||
>
|
||||
<ResizeHeightIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
<div className="simulation-process section">
|
||||
<button
|
||||
className="collapse-header-container"
|
||||
onClick={() => setOpenObjects(!openObjects)}
|
||||
>
|
||||
<div className="header">Events</div>
|
||||
<div className="arrow-container">
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</button>
|
||||
{openObjects &&
|
||||
events.map((event, index) => (
|
||||
<List key={`${index}-${event.pathName}`} val={event} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="compare-simulations-container">
|
||||
<div className="compare-simulations-header">
|
||||
Need to Compare Layout?
|
||||
</div>
|
||||
<div className="content">
|
||||
Click '<span>Compare</span>' to review and analyze the layout
|
||||
differences between them.
|
||||
</div>
|
||||
<div className="input">
|
||||
<input type="button" value={"Compare"} className="submit" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedAsset && (
|
||||
<RenderOverlay>
|
||||
<EditWidgetOption
|
||||
options={["Add to Product", "Remove from Product"]}
|
||||
onClick={(option) => {
|
||||
if (option === "Add to Product") {
|
||||
handleAddEventToProduct({
|
||||
event: getEventByModelUuid(selectedAsset.modelUuid),
|
||||
addEvent,
|
||||
selectedProduct,
|
||||
clearSelectedAsset,
|
||||
});
|
||||
} else {
|
||||
handleRemoveEventFromProduct();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</RenderOverlay>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Simulations;
|
||||
|
||||
50
app/src/components/ui/features/RenameTooltip.tsx
Normal file
50
app/src/components/ui/features/RenameTooltip.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { useState } from "react";
|
||||
import { RenameIcon } from "../../icons/ContextMenuIcons";
|
||||
import {
|
||||
useLeftData,
|
||||
useTopData,
|
||||
} from "../../../store/visualization/useZone3DWidgetStore";
|
||||
|
||||
type RenameTooltipProps = {
|
||||
name: string;
|
||||
onSubmit: (newName: string) => void;
|
||||
};
|
||||
|
||||
const RenameTooltip: React.FC<RenameTooltipProps> = ({ name, onSubmit }) => {
|
||||
const [value, setValue] = useState(name);
|
||||
|
||||
const { top } = useTopData();
|
||||
const { left } = useLeftData();
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
onSubmit(value.trim());
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rename-tool-tip"
|
||||
style={{
|
||||
top: `${top}px`,
|
||||
left: `${left}px`,
|
||||
}}
|
||||
>
|
||||
<div className="header">
|
||||
<div className="icon">
|
||||
<RenameIcon />
|
||||
</div>
|
||||
<div className="name">Name</div>
|
||||
</div>
|
||||
<form className="input" onSubmit={handleSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenameTooltip;
|
||||
@@ -27,6 +27,7 @@ interface ZoneItem {
|
||||
id: string;
|
||||
name: string;
|
||||
assets?: Asset[];
|
||||
active?: boolean;
|
||||
}
|
||||
|
||||
interface ListProps {
|
||||
@@ -157,7 +158,7 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
|
||||
{items?.map((item) => (
|
||||
<React.Fragment key={`zone-${item.id}`}>
|
||||
<li className="list-container">
|
||||
<div className="list-item">
|
||||
<div className={`list-item ${item.active ? "active" : ""}`}>
|
||||
<div className="zone-header">
|
||||
<button
|
||||
className="value"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from "react";
|
||||
import React from "react";
|
||||
import {
|
||||
useLeftData,
|
||||
useTopData,
|
||||
@@ -16,29 +16,25 @@ const EditWidgetOption: React.FC<EditWidgetOptionProps> = ({
|
||||
const { top } = useTopData();
|
||||
const { left } = useLeftData();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
}, [top, left]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="editWidgetOptions-wrapper"
|
||||
className="context-menu-options-wrapper"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: `${top}px`,
|
||||
left: `${left}px`,
|
||||
zIndex: 10000,
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<div className="editWidgetOptions">
|
||||
<div className="context-menu-options">
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
<button
|
||||
className="option"
|
||||
key={index}
|
||||
key={`${index}-${option}`}
|
||||
onClick={() => onClick(option)}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,8 +64,6 @@ const SimulationPlayer: React.FC = () => {
|
||||
|
||||
const handleMouseDown = () => {
|
||||
isDragging.current = true;
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
@@ -80,11 +78,11 @@ const SimulationPlayer: React.FC = () => {
|
||||
|
||||
const handleMouseUp = () => {
|
||||
isDragging.current = false;
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
return () => {
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
@@ -109,24 +107,6 @@ const SimulationPlayer: React.FC = () => {
|
||||
{ name: "process 9", completed: 90 }, // 90% completed
|
||||
{ name: "process 10", completed: 30 }, // 30% completed
|
||||
];
|
||||
// Move getRandomColor out of render
|
||||
const getRandomColor = () => {
|
||||
const letters = "0123456789ABCDEF";
|
||||
let color = "#";
|
||||
for (let i = 0; i < 6; i++) {
|
||||
color += letters[Math.floor(Math.random() * 16)];
|
||||
}
|
||||
return color;
|
||||
};
|
||||
|
||||
// Store colors for each process item
|
||||
const [_, setProcessColors] = useState<string[]>([]);
|
||||
|
||||
// Generate colors on mount or when process changes
|
||||
useEffect(() => {
|
||||
const generatedColors = process.map(() => getRandomColor());
|
||||
setProcessColors(generatedColors);
|
||||
}, []);
|
||||
|
||||
const intervals = [10, 20, 30, 40, 50, 60]; // in minutes
|
||||
const totalSegments = intervals.length;
|
||||
@@ -218,7 +198,7 @@ const SimulationPlayer: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{subModule === "simulations" && (
|
||||
{subModule !== "analysis" && (
|
||||
<div className="header">
|
||||
<InfoIcon />
|
||||
{playSimulation
|
||||
@@ -281,7 +261,7 @@ const SimulationPlayer: React.FC = () => {
|
||||
const segmentProgress = (index / totalSegments) * 100;
|
||||
const isFilled = progress >= segmentProgress;
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
<React.Fragment key={`${index}-${label}`}>
|
||||
<div className="label-dot-wrapper">
|
||||
<div className="label">{label} mins</div>
|
||||
<div
|
||||
@@ -360,6 +340,7 @@ const SimulationPlayer: React.FC = () => {
|
||||
className="process-wrapper"
|
||||
style={{ padding: expand ? "0px" : "5px 35px" }}
|
||||
>
|
||||
{/* eslint-disable-next-line */}
|
||||
<div
|
||||
className="process-container"
|
||||
ref={processWrapperRef}
|
||||
@@ -367,7 +348,7 @@ const SimulationPlayer: React.FC = () => {
|
||||
>
|
||||
{process.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
key={`${index}-${item.name}`}
|
||||
className="process"
|
||||
style={{
|
||||
width: `${item.completed}%`,
|
||||
|
||||
Reference in New Issue
Block a user