Refactor compare components and version management
- Updated SVG attributes in SimulationIcons to use camelCase for color interpolation filters. - Refactored import paths for ComparePopUp and CompareLayOut to new directory structure. - Enhanced VersionHistory component to utilize version store for better state management. - Removed deprecated CompareLayOut and compare components, replacing them with new versions. - Implemented VersionSaved component to display notifications for newly saved versions. - Added functionality to save and edit version names and descriptions. - Updated styles for version notifications and editing popups. - Improved AddButtons component to dynamically set IDs based on side. - Enhanced Project component to integrate new version management features.
This commit is contained in:
@@ -1040,3 +1040,189 @@ export const EyeCloseIcon = () => {
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const SaveIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14.6942 4.26907C12.9155 3.91031 11.0846 3.91031 9.30594 4.26907C7.25504 4.68274 5.77783 6.50871 5.77783 8.63016V17.8926C5.77783 19.5181 7.51712 20.5298 8.89979 19.7085L11.4179 18.2129C11.7775 17.9993 12.2226 17.9993 12.5822 18.2129L15.1003 19.7085C16.483 20.5298 18.2223 19.5181 18.2223 17.8926V8.63016C18.2223 6.50871 16.745 4.68274 14.6942 4.26907Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
export const SaveVersionIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="183"
|
||||
height="114"
|
||||
viewBox="0 0 183 114"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<foreignObject x="-38.7816" y="-60.596" width="261.775" height="235.114">
|
||||
|
||||
|
||||
</foreignObject>
|
||||
<rect
|
||||
data-figma-bg-blur-radius="60.596"
|
||||
x="22.4204"
|
||||
y="0.60596"
|
||||
width="139.371"
|
||||
height="112.709"
|
||||
rx="10.3013"
|
||||
fill="url(#paint0_linear_4816_8890)"
|
||||
stroke="#564B69"
|
||||
strokeWidth="1.21192"
|
||||
/>
|
||||
<circle cx="33.9336" cy="10.9082" r="2.42384" fill="#6F6F7A" />
|
||||
<circle cx="41.2051" cy="10.9082" r="2.42384" fill="#6F6F7A" />
|
||||
<circle cx="48.4766" cy="10.9082" r="2.42384" fill="#6F6F7A" />
|
||||
<rect
|
||||
x="132.099"
|
||||
y="8.48438"
|
||||
width="20.6026"
|
||||
height="3.63576"
|
||||
rx="1.81788"
|
||||
fill="#6F6F7A"
|
||||
/>
|
||||
<rect
|
||||
x="0.60596"
|
||||
y="21.2075"
|
||||
width="181.788"
|
||||
height="27.8742"
|
||||
rx="13.9371"
|
||||
fill="url(#paint1_linear_4816_8890)"
|
||||
stroke="#564B69"
|
||||
strokeWidth="1.21192"
|
||||
/>
|
||||
<g clipPath="url(#clip1_4816_8890)">
|
||||
<path
|
||||
d="M14.5432 43.9516C9.69069 43.9516 5.74316 40.0038 5.74316 35.1516C5.74316 30.2993 9.69069 26.3516 14.5432 26.3516C19.3956 26.3516 23.3432 30.2993 23.3432 35.1516C23.3432 40.0038 19.3956 43.9516 14.5432 43.9516Z"
|
||||
fill="#D7EBFF"
|
||||
/>
|
||||
<path
|
||||
d="M23.343 35.1516C23.343 30.2993 19.3954 26.3516 14.543 26.3516V43.9516C19.3954 43.9516 23.343 40.0038 23.343 35.1516Z"
|
||||
fill="#C4E2FF"
|
||||
/>
|
||||
<path
|
||||
d="M14.5428 42.8044C10.3233 42.8044 6.89062 39.3715 6.89062 35.1522C6.89062 30.9328 10.3233 27.5 14.5428 27.5C18.7623 27.5 22.195 30.9329 22.195 35.1522C22.195 39.3715 18.7623 42.8044 14.5428 42.8044Z"
|
||||
fill="#88CC2A"
|
||||
/>
|
||||
<g filter="url(#filter1_f_4816_8890)">
|
||||
<path
|
||||
d="M14.5428 42.8044C10.3233 42.8044 6.89062 39.3715 6.89062 35.1522C6.89062 30.9328 10.3233 27.5 14.5428 27.5C18.7623 27.5 22.195 30.9329 22.195 35.1522C22.195 39.3715 18.7623 42.8044 14.5428 42.8044Z"
|
||||
fill="#88CC2A"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M22.1952 35.1522C22.1952 30.9329 18.7625 27.5 14.543 27.5V42.8043C18.7625 42.8044 22.1952 39.3715 22.1952 35.1522Z"
|
||||
fill="#7FB335"
|
||||
/>
|
||||
<path
|
||||
d="M13.7776 38.5936C13.4839 38.5936 13.1903 38.4815 12.9661 38.2573L10.6009 35.8921C10.1526 35.4439 10.1526 34.7172 10.6009 34.269C11.0493 33.8206 11.7757 33.8206 12.2241 34.269L13.7776 35.8224L17.053 32.5472C17.5013 32.0988 18.2277 32.0988 18.6761 32.5472C19.1245 32.9954 19.1245 33.7221 18.6761 34.1703L14.5892 38.2573C14.365 38.4815 14.0713 38.5936 13.7776 38.5936Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M17.0531 32.5472L14.543 35.0572V38.295C14.5579 38.2815 14.5749 38.2716 14.5893 38.2572L18.6762 34.1703C19.1246 33.7221 19.1246 32.9954 18.6762 32.5472C18.2278 32.0988 17.5015 32.0988 17.0531 32.5472Z"
|
||||
fill="#EDF0F2"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="29.0859"
|
||||
y="31.5078"
|
||||
width="130.887"
|
||||
height="6.0596"
|
||||
rx="3.0298"
|
||||
fill="#6F6F7A"
|
||||
/>
|
||||
<path
|
||||
d="M168.457 32.1172L173.911 37.5708"
|
||||
stroke="#F65648"
|
||||
strokeWidth="1.21192"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M173.911 32.1172L168.457 37.5708"
|
||||
stroke="#F65648"
|
||||
strokeWidth="1.21192"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<defs>
|
||||
<clipPath
|
||||
id="bgblur_0_4816_8890_clip_path"
|
||||
transform="translate(38.7816 60.596)"
|
||||
>
|
||||
<rect
|
||||
x="22.4204"
|
||||
y="0.60596"
|
||||
width="139.371"
|
||||
height="112.709"
|
||||
rx="10.3013"
|
||||
/>
|
||||
</clipPath>
|
||||
<filter
|
||||
id="filter1_f_4816_8890"
|
||||
x="1.93981"
|
||||
y="22.5492"
|
||||
width="25.2058"
|
||||
height="25.2063"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feGaussianBlur
|
||||
stdDeviation="2.47541"
|
||||
result="effect1_foregroundBlur_4816_8890"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_4816_8890"
|
||||
x1="24.4428"
|
||||
y1="-3.9557e-07"
|
||||
x2="36.8899"
|
||||
y2="151.75"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#333333" />
|
||||
<stop offset="1" stopColor="#2D2437" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_4816_8890"
|
||||
x1="3.42139"
|
||||
y1="20.6016"
|
||||
x2="4.04875"
|
||||
y2="59.5968"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#333333" />
|
||||
<stop offset="1" stopColor="#2D2437" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip1_4816_8890">
|
||||
<rect
|
||||
width="24"
|
||||
height="24"
|
||||
fill="white"
|
||||
transform="translate(2.54297 23.1484)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -547,7 +547,7 @@ export function CompareLayoutIcon() {
|
||||
width="142"
|
||||
height="114"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
@@ -582,7 +582,7 @@ export function CompareLayoutIcon() {
|
||||
width="9"
|
||||
height="113.5"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
@@ -603,7 +603,7 @@ export function CompareLayoutIcon() {
|
||||
width="32"
|
||||
height="32"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
@@ -624,7 +624,7 @@ export function CompareLayoutIcon() {
|
||||
width="17.8184"
|
||||
height="17.8203"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
@@ -645,7 +645,7 @@ export function CompareLayoutIcon() {
|
||||
width="12.9082"
|
||||
height="12.9062"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
@@ -666,7 +666,7 @@ export function CompareLayoutIcon() {
|
||||
width="12.9082"
|
||||
height="12.9062"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
@@ -687,7 +687,7 @@ export function CompareLayoutIcon() {
|
||||
width="29"
|
||||
height="70"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
@@ -722,7 +722,7 @@ export function CompareLayoutIcon() {
|
||||
width="29"
|
||||
height="70"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
@@ -784,7 +784,7 @@ export const ResizerIcon = () => {
|
||||
width="26.7441"
|
||||
height="24.0703"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
|
||||
@@ -22,12 +22,12 @@ import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertP
|
||||
import { deleteProductApi } from "../../../../services/simulation/deleteProductApi";
|
||||
import { renameProductApi } from "../../../../services/simulation/renameProductApi";
|
||||
import { determineExecutionMachineSequences } from "../../../../modules/simulation/simulator/functions/determineExecutionMachineSequences";
|
||||
import ComparePopUp from "../../../ui/compare/compare";
|
||||
import ComparePopUp from "../../../ui/compareVersion/Compare";
|
||||
import {
|
||||
useCompareStore,
|
||||
useSaveVersion,
|
||||
} from "../../../../store/builder/store";
|
||||
import CompareLayOut from "../../../ui/compare/CompareLayOut";
|
||||
import CompareLayOut from "../../../ui/compareVersion/CompareLayOut";
|
||||
import useToggleStore from "../../../../store/useUIToggleStore";
|
||||
|
||||
interface Event {
|
||||
|
||||
@@ -7,25 +7,21 @@ import {
|
||||
LocationIcon,
|
||||
} from "../../../icons/ExportCommonIcons";
|
||||
import RenameInput from "../../../ui/inputs/RenameInput";
|
||||
import { useVersionStore } from "../../../../store/builder/store";
|
||||
|
||||
const VersionHistory = () => {
|
||||
const userName = localStorage.getItem("userName") ?? "Anonymous";
|
||||
|
||||
const initialVersions = [
|
||||
{
|
||||
versionName: "v1.0",
|
||||
timestamp: "April 09, 2025",
|
||||
savedBy: userName,
|
||||
},
|
||||
];
|
||||
|
||||
const [versions, setVersions] = useState(initialVersions);
|
||||
const [selectedVersion, setSelectedVersion] = useState(initialVersions[0]);
|
||||
const { versions, addVersion, setVersions } = useVersionStore();
|
||||
const [selectedVersion, setSelectedVersion] = useState(
|
||||
versions.length > 0 ? versions[0] : null
|
||||
);
|
||||
|
||||
const addNewVersion = () => {
|
||||
const newVersionNumber = versions.length + 1;
|
||||
const newVersion = {
|
||||
versionName: `v${newVersionNumber}.0`,
|
||||
id: crypto.randomUUID(),
|
||||
versionLabel: `v${versions.length + 1}.0`,
|
||||
versionName: "",
|
||||
timestamp: new Date().toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
@@ -34,21 +30,26 @@ const VersionHistory = () => {
|
||||
savedBy: userName,
|
||||
};
|
||||
|
||||
const updated = [newVersion, ...versions];
|
||||
setVersions(updated);
|
||||
const newVersions = [newVersion, ...versions];
|
||||
addVersion(newVersion);
|
||||
setSelectedVersion(newVersion);
|
||||
setVersions(newVersions); // bring new one to top
|
||||
};
|
||||
|
||||
const handleSelectVersion = (version: any) => {
|
||||
setSelectedVersion(version);
|
||||
const reordered = [version, ...versions.filter((v) => v !== version)];
|
||||
|
||||
// Move selected version to top, keep others in same order
|
||||
const reordered = [version, ...versions.filter((v) => v.id !== version.id)];
|
||||
setVersions(reordered);
|
||||
};
|
||||
|
||||
const handleTimestampChange = (newTimestamp: string, index: number) => {
|
||||
const updatedVersions = [...versions];
|
||||
updatedVersions[index].timestamp = newTimestamp;
|
||||
setVersions(updatedVersions);
|
||||
const updated = [...versions];
|
||||
updated[index].timestamp = newTimestamp;
|
||||
|
||||
console.warn("Timestamp updated locally but not persisted in store.");
|
||||
setVersions(updated); // Optional: persist timestamp change
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -82,49 +83,55 @@ const VersionHistory = () => {
|
||||
</div>
|
||||
|
||||
{/* Current Version Display */}
|
||||
<div className="version-history-location">
|
||||
<div className="location-label">
|
||||
<LocationIcon />
|
||||
</div>
|
||||
<div className="location-details">
|
||||
<div className="current-version">
|
||||
Current Version ({selectedVersion.versionName})
|
||||
{selectedVersion && (
|
||||
<div className="version-history-location">
|
||||
<div className="location-label">
|
||||
<LocationIcon />
|
||||
</div>
|
||||
<div className="saved-history-count">
|
||||
{versions.length} Saved History
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Versions List */}
|
||||
<div className="saved-versions-list">
|
||||
{versions.map((version, index) => (
|
||||
<button
|
||||
key={version.versionName}
|
||||
className="saved-version"
|
||||
id={`${version.versionName}-${index}`}
|
||||
onClick={() => handleSelectVersion(version)}
|
||||
>
|
||||
<div className="version-name">{version.versionName}</div>
|
||||
<div className="version-details">
|
||||
<div className="details">
|
||||
<span className="timestamp">
|
||||
<RenameInput
|
||||
value={version.timestamp}
|
||||
onRename={(newTimestamp) =>
|
||||
handleTimestampChange(newTimestamp, index)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<span className="saved-by">
|
||||
<div className="user-profile">{version.savedBy[0]}</div>
|
||||
<div className="user-name">{version.savedBy}</div>
|
||||
</span>
|
||||
</div>
|
||||
<ArrowIcon />
|
||||
<div className="location-details">
|
||||
<div className="current-version">
|
||||
Current Version ({selectedVersion.versionLabel})
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
<div className="saved-history-count">
|
||||
{versions.length} Saved History
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Versions List or No Versions Message */}
|
||||
<div className="saved-versions-list">
|
||||
{versions.length === 0 ? (
|
||||
<div className="no-versions-message">No saved versions</div>
|
||||
) : (
|
||||
versions.map((version, index) => (
|
||||
<button
|
||||
key={version.id}
|
||||
className="saved-version"
|
||||
id={`${version.versionName}-${index}`}
|
||||
onClick={() => handleSelectVersion(version)}
|
||||
>
|
||||
<div className="version-name">{version.versionLabel}</div>
|
||||
<div className="version-details">
|
||||
<div className="details">
|
||||
<span className="timestamp">
|
||||
<RenameInput
|
||||
value={version.timestamp}
|
||||
onRename={(newTimestamp) =>
|
||||
handleTimestampChange(newTimestamp, index)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<span className="saved-by">
|
||||
<div className="user-profile">{version.savedBy[0]}</div>
|
||||
<div className="user-name">{version.savedBy}</div>
|
||||
</span>
|
||||
</div>
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useVersionStore } from "../../../../store/builder/store";
|
||||
import {
|
||||
CloseIcon,
|
||||
SaveIcon,
|
||||
SaveVersionIcon,
|
||||
} from "../../../icons/ExportCommonIcons";
|
||||
import { RenameIcon } from "../../../icons/ContextMenuIcons";
|
||||
import RenderOverlay from "../../../templates/Overlay";
|
||||
|
||||
const VersionSaved = () => {
|
||||
const { versions, updateVersion } = useVersionStore();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [shouldDismiss, setShouldDismiss] = useState(false);
|
||||
const [showNotification, setShowNotification] = useState(false);
|
||||
const [newName, setNewName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const prevVersionCount = useRef(versions.length);
|
||||
const dismissTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const latestVersion = versions?.[0];
|
||||
|
||||
// Clear dismiss timer when component unmounts
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Handle new version notification and setup dismiss timer
|
||||
useEffect(() => {
|
||||
if (versions.length > prevVersionCount.current) {
|
||||
setShowNotification(true);
|
||||
setShouldDismiss(false);
|
||||
setIsEditing(false);
|
||||
setNewName(versions[0].versionName ?? "");
|
||||
setDescription(versions[0]?.description ?? "");
|
||||
|
||||
// Only start dismiss timer if not in edit mode
|
||||
if (!isEditing) {
|
||||
startDismissTimer();
|
||||
}
|
||||
|
||||
prevVersionCount.current = versions.length;
|
||||
} else if (versions.length < prevVersionCount.current) {
|
||||
prevVersionCount.current = versions.length;
|
||||
}
|
||||
}, [versions, isEditing]);
|
||||
|
||||
// Start or restart the dismiss timer
|
||||
const startDismissTimer = (delay = 5000) => {
|
||||
if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
|
||||
dismissTimerRef.current = setTimeout(() => {
|
||||
console.log("isEditing: ", isEditing);
|
||||
setShouldDismiss(true);
|
||||
}, delay);
|
||||
};
|
||||
|
||||
// Hide notification after dismiss animation delay
|
||||
useEffect(() => {
|
||||
if (shouldDismiss) {
|
||||
const timer = setTimeout(() => setShowNotification(false), 200);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [shouldDismiss]);
|
||||
|
||||
const handleEditName = () => {
|
||||
setIsEditing(true);
|
||||
setNewName(latestVersion?.versionName ?? "");
|
||||
setDescription(latestVersion?.description ?? "");
|
||||
|
||||
// Clear any existing dismiss timer when editing starts
|
||||
if (dismissTimerRef.current) {
|
||||
clearTimeout(dismissTimerRef.current);
|
||||
dismissTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleFinishEdit = () => {
|
||||
if (latestVersion) {
|
||||
updateVersion(latestVersion.id, {
|
||||
versionName: newName,
|
||||
description,
|
||||
});
|
||||
console.log("saved");
|
||||
startDismissTimer(); // Restart 5s timer after save
|
||||
}
|
||||
startDismissTimer(); // Restart 5s timer after save
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsEditing(false);
|
||||
startDismissTimer(); // Restart 5s timer after cancel
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setShouldDismiss(true);
|
||||
if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
|
||||
};
|
||||
|
||||
if (!showNotification || !latestVersion) return null;
|
||||
|
||||
return (
|
||||
<div className={`versionSaved ${shouldDismiss ? "dismissing" : ""}`}>
|
||||
<div className="version-header">
|
||||
<div className="header-wrapper">
|
||||
<div className="icon">
|
||||
<SaveIcon />
|
||||
</div>
|
||||
<span>Saved New Version</span>
|
||||
</div>
|
||||
<button className="close-btn" onClick={handleClose}>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="version-details">
|
||||
<SaveVersionIcon />
|
||||
<div className="details">
|
||||
<div className="details-wrapper">
|
||||
New Version Created {latestVersion.versionLabel}{" "}
|
||||
{latestVersion.timestamp.toUpperCase()}
|
||||
</div>
|
||||
<button onClick={handleEditName}>Edit name</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isEditing && (
|
||||
<RenderOverlay>
|
||||
<div className="edit-version-popup-wrapper">
|
||||
<div className="details-wrapper-popup-container">
|
||||
<div className="header-wrapper">
|
||||
<RenameIcon />
|
||||
<div className="label">Rename Version</div>
|
||||
</div>
|
||||
<div className="details-wrapper">
|
||||
<div className="version-name">
|
||||
<input
|
||||
type="text"
|
||||
value={newName}
|
||||
onChange={(e) => setNewName(e.target.value)}
|
||||
placeholder="Enter new version name"
|
||||
/>
|
||||
<div className="label">
|
||||
by @{latestVersion.savedBy}{" "}
|
||||
{new Date(latestVersion.timestamp).toLocaleString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "2-digit",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="version-description">
|
||||
{/* <input
|
||||
type="text"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Add description"
|
||||
/> */}
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Add description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="btn-wrapper">
|
||||
<button className="cancel" onClick={handleCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="save" onClick={handleFinishEdit}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RenderOverlay>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VersionSaved;
|
||||
@@ -1,9 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { InfoIcon } from "../../icons/ShortcutIcons";
|
||||
import { SaveDiskIcon } from "../../icons/ExportCommonIcons";
|
||||
import { useCompareStore } from "../../../store/builder/store";
|
||||
import OuterClick from "../../../utils/outerClick";
|
||||
import useToggleStore from "../../../store/useUIToggleStore";
|
||||
|
||||
interface ComparePopUpProps {
|
||||
onClose: () => void;
|
||||
@@ -4,6 +4,7 @@ import { ArrowIcon } from "../../icons/ExportCommonIcons";
|
||||
import { toggleTheme } from "../../../utils/theme";
|
||||
import useVersionHistoryStore, {
|
||||
useShortcutStore,
|
||||
useVersionStore,
|
||||
} from "../../../store/builder/store";
|
||||
import { useSubModuleStore } from "../../../store/useModuleStore";
|
||||
|
||||
@@ -20,6 +21,8 @@ interface MenuItem {
|
||||
}
|
||||
|
||||
const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
|
||||
const userName = localStorage.getItem("userName") ?? "Anonymous";
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [activeMenu, setActiveMenu] = useState<string | null>(null);
|
||||
const [activeSubMenu, setActiveSubMenu] = useState<string | null>(null);
|
||||
@@ -59,7 +62,32 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
|
||||
File: [
|
||||
{ label: "New File", shortcut: "Ctrl + N" },
|
||||
{ label: "Open Local File", shortcut: "Ctrl + O" },
|
||||
{ label: "Save Version" },
|
||||
{
|
||||
label: "Save Version",
|
||||
action: () => {
|
||||
const versionStore = useVersionStore.getState();
|
||||
const versionCount = versionStore.versions.length;
|
||||
|
||||
const newVersion = {
|
||||
id: crypto.randomUUID(),
|
||||
versionLabel: `v${versionCount + 1}.0`,
|
||||
timestamp: `${new Date().toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
})} ${new Date().toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "2-digit",
|
||||
})}`,
|
||||
|
||||
savedBy: userName,
|
||||
};
|
||||
|
||||
console.log("newVersion: ", newVersion);
|
||||
versionStore.addVersion(newVersion);
|
||||
},
|
||||
},
|
||||
{ label: "Make a Copy" },
|
||||
{ label: "Share" },
|
||||
{ label: "Rename" },
|
||||
|
||||
@@ -244,7 +244,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
{/* "+" Button */}
|
||||
|
||||
<button
|
||||
id="panel-add-button"
|
||||
id={`${side}-add-button`}
|
||||
className={`side-button ${side}${
|
||||
selectedZone.activeSides.includes(side) ? " active" : ""
|
||||
}`}
|
||||
@@ -280,7 +280,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
id="hide-panel-visulization"
|
||||
id={`${side}-hide-panel-visulization`}
|
||||
title={
|
||||
hiddenPanels[selectedZone.zoneId]?.includes(side)
|
||||
? "Show Panel"
|
||||
@@ -301,7 +301,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
<button
|
||||
className="icon"
|
||||
title="Clean Panel"
|
||||
id="clean-panel-visulization"
|
||||
id={`${side}-clean-panel-visulization`}
|
||||
onClick={() => cleanPanel(side)}
|
||||
style={{
|
||||
cursor:
|
||||
@@ -319,7 +319,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
className={`icon ${
|
||||
selectedZone.lockedPanels.includes(side) ? "active" : ""
|
||||
}`}
|
||||
id="lock-panel-visulization"
|
||||
id={`${side}-lock-panel-visulization`}
|
||||
title={
|
||||
selectedZone.lockedPanels.includes(side)
|
||||
? "Unlock Panel"
|
||||
|
||||
@@ -15,12 +15,13 @@ import {
|
||||
useLoadingProgress,
|
||||
useWidgetSubOption,
|
||||
useSaveVersion,
|
||||
useVersionStore,
|
||||
} from "../store/builder/store";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { usePlayButtonStore } from "../store/usePlayButtonStore";
|
||||
import MarketPlace from "../modules/market/MarketPlace";
|
||||
import LoadingPage from "../components/templates/LoadingPage";
|
||||
import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
|
||||
import SimulationPlayer from "../components/ui/simulation/SimulationPlayer";
|
||||
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
|
||||
import { useSelectedUserStore } from "../store/useCollabStore";
|
||||
import FollowPerson from "../components/templates/FollowPerson";
|
||||
@@ -34,9 +35,10 @@ import LogList from "../components/ui/log/LogList";
|
||||
import Footer from "../components/footer/Footer";
|
||||
import SelectFloorPlan from "../components/temporary/SelectFloorPlan";
|
||||
import ControlsPlayer from "../components/layout/controls/ControlsPlayer";
|
||||
import CompareLayOut from "../components/ui/compare/CompareLayOut";
|
||||
import CompareLayOut from "../components/ui/compareVersion/CompareLayOut";
|
||||
import useToggleStore from "../store/useUIToggleStore";
|
||||
import RegularDropDown from "../components/ui/inputs/RegularDropDown";
|
||||
import VersionSaved from "../components/layout/sidebarRight/versionHisory/VersionSaved";
|
||||
|
||||
const Project: React.FC = () => {
|
||||
let navigate = useNavigate();
|
||||
@@ -95,6 +97,7 @@ const Project: React.FC = () => {
|
||||
const { setFloatingWidget } = useFloatingWidget();
|
||||
|
||||
const [selectedLayout, setSelectedLayout] = useState<string | null>(null); // Track selected layout
|
||||
const { versions } = useVersionStore();
|
||||
|
||||
const dummyLayouts = [
|
||||
{ id: 1, name: "Layout 1" },
|
||||
@@ -176,6 +179,7 @@ const Project: React.FC = () => {
|
||||
<CompareLayOut dummyLayouts={dummyLayouts} />
|
||||
</>
|
||||
)}
|
||||
<VersionSaved />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -477,15 +477,52 @@ export const useCompareStore = create<CompareStore>((set) => ({
|
||||
toggleComparePopUp: () =>
|
||||
set((state) => ({ comparePopUp: !state.comparePopUp })),
|
||||
}));
|
||||
|
||||
// Define the types for the store state
|
||||
interface VersionStore {
|
||||
// Save state store
|
||||
interface SaveVersionStore {
|
||||
isVersionSaved: boolean;
|
||||
setIsVersionSaved: (value: boolean) => void;
|
||||
}
|
||||
|
||||
// Create the Zustand store
|
||||
export const useSaveVersion = create<VersionStore>((set) => ({
|
||||
isVersionSaved: false, // Default state
|
||||
setIsVersionSaved: (value: boolean) => set({ isVersionSaved: value }), // Function to update the state
|
||||
export const useSaveVersion = create<SaveVersionStore>((set) => ({
|
||||
isVersionSaved: false,
|
||||
setIsVersionSaved: (value: boolean) => set({ isVersionSaved: value }),
|
||||
}));
|
||||
|
||||
// Version object type
|
||||
export interface Version {
|
||||
id: string;
|
||||
versionLabel: string;
|
||||
versionName?: string;
|
||||
timestamp: string;
|
||||
savedBy: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// Version list store
|
||||
interface VersionListStore {
|
||||
versions: Version[];
|
||||
addVersion: (version: Version) => void;
|
||||
clearVersions: () => void;
|
||||
setVersions: (newVersions: Version[]) => void;
|
||||
updateVersion: (id: string, data: Partial<Version>) => void; // ✅ Added
|
||||
}
|
||||
|
||||
export const useVersionStore = create<VersionListStore>((set) => ({
|
||||
versions: [],
|
||||
|
||||
addVersion: (newVersion) =>
|
||||
set((state) => ({
|
||||
versions: [newVersion, ...state.versions],
|
||||
})),
|
||||
|
||||
clearVersions: () => set({ versions: [] }),
|
||||
|
||||
setVersions: (newVersions) => set({ versions: newVersions }),
|
||||
|
||||
updateVersion: (id, data) =>
|
||||
set((state) => ({
|
||||
versions: state.versions.map((version) =>
|
||||
version.id === id ? { ...version, ...data } : version
|
||||
),
|
||||
})),
|
||||
}));
|
||||
|
||||
@@ -1738,4 +1738,178 @@
|
||||
to {
|
||||
height: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.versionSaved {
|
||||
min-width: 449px;
|
||||
position: fixed;
|
||||
bottom: 45px;
|
||||
right: 10px;
|
||||
z-index: 10;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 20px;
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
|
||||
.version-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.header-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.version-details {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
|
||||
.details {
|
||||
width: 100%;
|
||||
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 20px;
|
||||
outline: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 7px 12px;
|
||||
|
||||
.details-wrapper {
|
||||
font-size: var(--font-size-small);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
button {
|
||||
|
||||
font-size: var(--font-size-small);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: var(--background-color-button);
|
||||
color: var(--text-button-color);
|
||||
border-radius: 12px;
|
||||
padding: 5px 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.dismissing {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.edit-version-popup-wrapper {
|
||||
|
||||
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background: var(--background-color-secondary);
|
||||
@include flex-center;
|
||||
|
||||
.details-wrapper-popup-container {
|
||||
min-width: 535px;
|
||||
width: 520px;
|
||||
background: var(--background-color);
|
||||
border-radius: #{$border-radius-large};
|
||||
backdrop-filter: blur(15px);
|
||||
outline: 1px solid var(--border-color);
|
||||
padding: 6px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
padding: 20px;
|
||||
|
||||
.header-wrapper {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.details-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.version-name,
|
||||
.version-description {
|
||||
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 20px;
|
||||
outline: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
position: relative;
|
||||
|
||||
.label {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
font-size: var(--font-size-tiny);
|
||||
color: var(--text-disabled);
|
||||
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.version-description {
|
||||
textarea {
|
||||
|
||||
padding: 4px 8px;
|
||||
width: 100%;
|
||||
min-height: 101px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 20px;
|
||||
|
||||
.save {
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: var(--background-color-button);
|
||||
color: var(--text-button-color);
|
||||
border-radius: 12px;
|
||||
padding: 5px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user