Refactor and enhance various components and utilities
- Updated floorItemsGroup to send unique floor items to the GLTF loader. - Modified Card component to dynamically set button ID based on AssetID. - Enhanced FilterSearch component to assign unique IDs to star buttons. - Refactored camMode to utilize a new firstPersonCamera utility for cleaner code. - Introduced firstPersonCamera utility for handling camera mode transitions. - Improved Templates component to use nullish coalescing for email retrieval. - Cleaned up AddButtons component by removing commented-out code and optimizing email retrieval. - Updated Panel component to generate unique IDs for panel wrappers. - Simplified Project component by removing unused setIsPlaying function. - Removed hardcoded backend URL in panel service. - Created useAssetStore for managing asset state and CRUD operations. - Added camera mode state management to usePlayButtonStore. - Enhanced footer styles for better layout and responsiveness. - Improved simulation styles for better control visibility and responsiveness. - Refactored tools styles for cleaner transitions and hover effects. - Updated realTimeViz styles for better layout and responsiveness. - Introduced builderTypes for better type safety in asset management. - Enhanced shortcut key handling to include camera mode toggling.
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
||||
import ShortcutHelper from "./shortcutHelper";
|
||||
import { useShortcutStore } from "../../store/builder/store";
|
||||
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
||||
import OuterClick from "../../utils/outerClick";
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
const { logs, setIsLogListVisible } = useLogger();
|
||||
@@ -19,11 +18,6 @@ const Footer: React.FC = () => {
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { showShortcuts, setShowShortcuts } = useShortcutStore();
|
||||
|
||||
OuterClick({
|
||||
contextClassName: ["shortcut-helper-overlay"],
|
||||
setMenuVisible: () => setShowShortcuts(false),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="footer-container">
|
||||
<div className="footer-wrapper">
|
||||
@@ -82,7 +76,7 @@ const Footer: React.FC = () => {
|
||||
showShortcuts ? "visible" : ""
|
||||
}`}
|
||||
>
|
||||
<ShortcutHelper />
|
||||
<ShortcutHelper setShowShortcuts={setShowShortcuts}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -29,8 +29,8 @@ import {
|
||||
DublicateIcon,
|
||||
DuplicateInstanceIcon,
|
||||
PlayIcon,
|
||||
BrowserIcon,
|
||||
} from "../icons/ShortcutIcons";
|
||||
import { CloseIcon } from "../icons/ExportCommonIcons";
|
||||
|
||||
interface ShortcutItem {
|
||||
keys: string[];
|
||||
@@ -44,7 +44,13 @@ interface ShortcutGroup {
|
||||
items: ShortcutItem[];
|
||||
}
|
||||
|
||||
const ShortcutHelper = () => {
|
||||
interface ShortcutHelperProps {
|
||||
setShowShortcuts: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const ShortcutHelper: React.FC<ShortcutHelperProps> = ({
|
||||
setShowShortcuts,
|
||||
}) => {
|
||||
const shortcuts: ShortcutGroup[] = [
|
||||
{
|
||||
category: "Essential",
|
||||
@@ -256,27 +262,26 @@ const ShortcutHelper = () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Miscellaneous",
|
||||
items: [
|
||||
{
|
||||
keys: ["F5", "F11", "F12", "CTRL", "+", "R"],
|
||||
name: "Browser Defaults",
|
||||
description: "Reserved for browser defaults",
|
||||
icon: <BrowserIcon />,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const [activeCategory, setActiveCategory] =
|
||||
React.useState<string>("Essential");
|
||||
|
||||
const activeShortcuts =
|
||||
shortcuts.find((group) => group.category === activeCategory)?.items || [];
|
||||
shortcuts.find((group) => group.category === activeCategory)?.items ?? [];
|
||||
|
||||
return (
|
||||
<div className="shortcut-helper-container">
|
||||
<button
|
||||
id="close-shortcuts-helper"
|
||||
className="close-button"
|
||||
title="close-btn"
|
||||
onClick={() => {
|
||||
setShowShortcuts(false);
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
<div className="header">
|
||||
<div className="header-wrapper">
|
||||
{shortcuts.map((group) => (
|
||||
@@ -313,7 +318,7 @@ const ShortcutHelper = () => {
|
||||
{item.keys.map((key, i) => (
|
||||
<span
|
||||
key={`${key}-${i}`}
|
||||
className={`key ${key === "+" || key === "OR" ? "add" : ""}`}
|
||||
className={`key${key === "+" || key === "OR" ? " add" : ""}`}
|
||||
>
|
||||
{key}
|
||||
</span>
|
||||
|
||||
@@ -1,53 +1,85 @@
|
||||
import React, { useState } from "react";
|
||||
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import useCameraModeStore, {
|
||||
usePlayButtonStore,
|
||||
} from "../../../store/usePlayButtonStore";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
import { PlayIcon } from "../../icons/ShortcutIcons";
|
||||
import InputToggle from "../../ui/inputs/InputToggle";
|
||||
import { EyeCloseIcon, WalkIcon } from "../../icons/ExportCommonIcons";
|
||||
import { ExitIcon } from "../../icons/SimulationIcons";
|
||||
import { useCamMode } from "../../../store/builder/store";
|
||||
|
||||
const ControlsPlayer = () => {
|
||||
const ControlsPlayer: React.FC = () => {
|
||||
const { setIsPlaying } = usePlayButtonStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
const [walkMode, setWalkMode] = useState(false);
|
||||
const { walkMode, toggleWalkMode } = useCameraModeStore();
|
||||
const [hidePlayer, setHidePlayer] = useState(false);
|
||||
const { camMode } = useCamMode();
|
||||
|
||||
const changeCamMode = () => {
|
||||
setWalkMode(!walkMode);
|
||||
console.log("switch camera mode to first person");
|
||||
toggleWalkMode();
|
||||
echo.log("switch camera mode to first person");
|
||||
// Simulate "/" keypress
|
||||
const slashKeyEvent = new KeyboardEvent("keydown", {
|
||||
key: "/",
|
||||
code: "Slash",
|
||||
keyCode: 191, // for compatibility
|
||||
which: 191,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
document.dispatchEvent(slashKeyEvent);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (camMode === "ThirdPerson") {
|
||||
toggleWalkMode();
|
||||
} else return;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [camMode]);
|
||||
|
||||
return (
|
||||
<div className="controls-player-container">
|
||||
<div className="controls-left">
|
||||
<PlayIcon />
|
||||
<div className="label">Running {activeModule}...</div>
|
||||
</div>
|
||||
<div className={`controls-player-container${hidePlayer ? " hide" : ""}`}>
|
||||
{!hidePlayer && (
|
||||
<div className="controls-left">
|
||||
<PlayIcon />
|
||||
<div className="label">Running {activeModule}...</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="controls-right">
|
||||
<div className="walkMode-wrapper">
|
||||
<WalkIcon />
|
||||
<InputToggle
|
||||
value={walkMode}
|
||||
inputKey="1"
|
||||
label="Walk Mode"
|
||||
onClick={changeCamMode}
|
||||
/>
|
||||
</div>
|
||||
{!hidePlayer && activeModule === "builder" && (
|
||||
<div className="walkMode-wrapper">
|
||||
<WalkIcon />
|
||||
<InputToggle
|
||||
value={walkMode}
|
||||
inputKey="1"
|
||||
label="Walk Mode"
|
||||
onClick={changeCamMode}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
id="controls-player-play-button"
|
||||
className="btn-wrapper"
|
||||
className={`btn-wrapper${hidePlayer ? " hide" : ""}`}
|
||||
onClick={() => setIsPlaying(false)}
|
||||
>
|
||||
<div className="icon">
|
||||
<ExitIcon />
|
||||
</div>
|
||||
Exit
|
||||
{!hidePlayer && "Exit"}
|
||||
</button>
|
||||
<div className="btn-wrapper">
|
||||
<button
|
||||
className={`btn-wrapper${hidePlayer ? " hide" : ""}`}
|
||||
id="hide-btn"
|
||||
onClick={() => {
|
||||
setHidePlayer(!hidePlayer);
|
||||
}}
|
||||
>
|
||||
<div className="icon">
|
||||
<EyeCloseIcon />
|
||||
</div>
|
||||
Hide
|
||||
</div>
|
||||
{!hidePlayer && "Hide"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -89,7 +89,7 @@ const SideBarRight: React.FC = () => {
|
||||
{activeModule === "simulation" && (
|
||||
<>
|
||||
<button
|
||||
id="sidebar-action-list-properties"
|
||||
id="sidebar-action-list-simulation"
|
||||
className={`sidebar-action-list ${
|
||||
subModule === "simulations" ? "active" : ""
|
||||
}`}
|
||||
@@ -102,7 +102,7 @@ const SideBarRight: React.FC = () => {
|
||||
<SimulationIcon isActive={subModule === "simulations"} />
|
||||
</button>
|
||||
<button
|
||||
id="sidebar-action-list-properties"
|
||||
id="sidebar-action-list-mechanics"
|
||||
className={`sidebar-action-list ${
|
||||
subModule === "mechanics" ? "active" : ""
|
||||
}`}
|
||||
@@ -115,7 +115,7 @@ const SideBarRight: React.FC = () => {
|
||||
<MechanicsIcon isActive={subModule === "mechanics"} />
|
||||
</button>
|
||||
<button
|
||||
id="sidebar-action-list-properties"
|
||||
id="sidebar-action-list-analysis"
|
||||
className={`sidebar-action-list ${
|
||||
subModule === "analysis" ? "active" : ""
|
||||
}`}
|
||||
|
||||
@@ -84,8 +84,7 @@ const ActionsList: React.FC<ActionsListProps> = ({
|
||||
>
|
||||
<div className="list-container">
|
||||
{multipleAction &&
|
||||
selectedPointData &&
|
||||
selectedPointData.actions.map((action: any) => (
|
||||
selectedPointData?.actions?.map((action: any) => (
|
||||
<div
|
||||
key={action.actionUuid}
|
||||
className={`list-item ${
|
||||
@@ -106,7 +105,7 @@ const ActionsList: React.FC<ActionsListProps> = ({
|
||||
onRename={(value) => handleRenameAction(value)}
|
||||
/>
|
||||
</button>
|
||||
{selectedPointData.actions.length > 1 && (
|
||||
{selectedPointData?.actions?.length > 1 && (
|
||||
<button
|
||||
id="remove-action-button"
|
||||
className="remove-button"
|
||||
@@ -121,7 +120,7 @@ const ActionsList: React.FC<ActionsListProps> = ({
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{!multipleAction && selectedPointData && (
|
||||
{!multipleAction && selectedPointData?.action && (
|
||||
<div
|
||||
key={selectedPointData.action.actionUuid}
|
||||
className={`list-item active`}
|
||||
|
||||
@@ -102,6 +102,7 @@ const VersionHistory = () => {
|
||||
<button
|
||||
key={version.versionName}
|
||||
className="saved-version"
|
||||
id={`${version.versionName}-${index}`}
|
||||
onClick={() => handleSelectVersion(version)}
|
||||
>
|
||||
<div className="version-name">{version.versionName}</div>
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
import React from "react";
|
||||
import useLayoutStore from "../../store/builder/uselayoutStore";
|
||||
|
||||
const SelectFloorPlan: React.FC = () => {
|
||||
const { currentLayout, setLayout } = useLayoutStore();
|
||||
return (
|
||||
<div className="select-floorplane-wrapper">
|
||||
Don't have an idea? Use these presets!
|
||||
<div className="presets-container">
|
||||
<button
|
||||
id="preset-1"
|
||||
className={`preset ${currentLayout === "layout1" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setLayout("layout1");
|
||||
}}
|
||||
>
|
||||
Preset 1
|
||||
</button>
|
||||
<button
|
||||
id="preset-2"
|
||||
className={`preset ${currentLayout === "layout2" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setLayout("layout2");
|
||||
}}
|
||||
>
|
||||
Preset 2
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectFloorPlan;
|
||||
import React from "react";
|
||||
import useLayoutStore from "../../store/builder/uselayoutStore";
|
||||
|
||||
const SelectFloorPlan: React.FC = () => {
|
||||
const { currentLayout, setLayout } = useLayoutStore();
|
||||
return (
|
||||
<div className="select-floorplane-wrapper">
|
||||
Preset Layouts
|
||||
<div className="presets-container">
|
||||
<button
|
||||
className={`preset ${currentLayout === "layout1" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setLayout("layout1");
|
||||
}}
|
||||
>
|
||||
Preset 1
|
||||
</button>
|
||||
<button
|
||||
className={`preset ${currentLayout === "layout2" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setLayout("layout2");
|
||||
}}
|
||||
>
|
||||
Preset 2
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectFloorPlan;
|
||||
|
||||
@@ -335,6 +335,7 @@ const Tools: React.FC = () => {
|
||||
{activeModule !== "visualization" && (
|
||||
<button
|
||||
id="drop-down-button"
|
||||
title="drop-down"
|
||||
className="drop-down-option-button"
|
||||
ref={dropdownRef}
|
||||
onClick={() => setOpenDrop(!openDrop)}
|
||||
@@ -345,7 +346,7 @@ const Tools: React.FC = () => {
|
||||
{["cursor", "free-hand", "delete"].map((option) => (
|
||||
<button
|
||||
key={option}
|
||||
id={option}
|
||||
id={`${option}-tool`}
|
||||
className="option-list"
|
||||
onClick={() => {
|
||||
setActiveTool(option);
|
||||
|
||||
@@ -59,6 +59,7 @@ const ComparePopUp: React.FC<ComparePopUpProps> = ({ onClose }) => {
|
||||
</div>
|
||||
<button
|
||||
className="cancel btn"
|
||||
id="compare-cancel-btn"
|
||||
onClick={() => setComparePopUp(false)}
|
||||
>
|
||||
Cancel
|
||||
|
||||
@@ -215,7 +215,7 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
|
||||
>
|
||||
<div className="list-item">
|
||||
<button
|
||||
id="asset-name"
|
||||
id={`${asset.name}-${asset.id}`}
|
||||
className="value"
|
||||
onClick={() => handleAssetClick(asset)}
|
||||
>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
DailyProductionIcon,
|
||||
EndIcon,
|
||||
ExpandIcon,
|
||||
EyeCloseIcon,
|
||||
HourlySimulationIcon,
|
||||
InfoIcon,
|
||||
MonthlyROI,
|
||||
@@ -30,6 +31,7 @@ const SimulationPlayer: React.FC = () => {
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
const [expand, setExpand] = useState(true);
|
||||
const [playSimulation, setPlaySimulation] = useState(false);
|
||||
const [hidePlayer, setHidePlayer] = useState(false);
|
||||
|
||||
const { speed, setSpeed } = useAnimationPlaySpeed();
|
||||
const { setIsPlaying } = usePlayButtonStore();
|
||||
@@ -161,10 +163,10 @@ const SimulationPlayer: React.FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="simulation-player-wrapper">
|
||||
<div className={`simulation-player-wrapper${hidePlayer ? " hide" : ""}`}>
|
||||
<div className={`simulation-player-container ${expand ? "open" : ""}`}>
|
||||
<div className="controls-container">
|
||||
{subModule === "analysis" && (
|
||||
{!hidePlayer && subModule === "analysis" && (
|
||||
<div className="production-details">
|
||||
{/* hourlySimulation */}
|
||||
<div className="hourly-wrapper production-wrapper">
|
||||
@@ -213,7 +215,7 @@ const SimulationPlayer: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{subModule !== "analysis" && (
|
||||
{!hidePlayer && subModule !== "analysis" && (
|
||||
<div className="header">
|
||||
<InfoIcon />
|
||||
{playSimulation
|
||||
@@ -222,26 +224,30 @@ const SimulationPlayer: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
<div className="controls-wrapper">
|
||||
<button
|
||||
id="simulation-reset-button"
|
||||
className="simulation-button-container"
|
||||
onClick={() => {
|
||||
handleReset();
|
||||
}}
|
||||
>
|
||||
<ResetIcon />
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
id="simulation-play-button"
|
||||
className="simulation-button-container"
|
||||
onClick={() => {
|
||||
handlePlayStop();
|
||||
}}
|
||||
>
|
||||
<PlayStopIcon />
|
||||
{playSimulation ? "Play" : "Stop"}
|
||||
</button>
|
||||
{!hidePlayer && (
|
||||
<button
|
||||
id="simulation-reset-button"
|
||||
className="simulation-button-container"
|
||||
onClick={() => {
|
||||
handleReset();
|
||||
}}
|
||||
>
|
||||
<ResetIcon />
|
||||
Reset
|
||||
</button>
|
||||
)}
|
||||
{!hidePlayer && (
|
||||
<button
|
||||
id="simulation-play-button"
|
||||
className="simulation-button-container"
|
||||
onClick={() => {
|
||||
handlePlayStop();
|
||||
}}
|
||||
>
|
||||
<PlayStopIcon />
|
||||
{playSimulation ? "Play" : "Stop"}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
id="simulation-reset-button"
|
||||
className="simulation-button-container"
|
||||
@@ -250,7 +256,17 @@ const SimulationPlayer: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<ExitIcon />
|
||||
Exit
|
||||
{!hidePlayer && "Exit"}
|
||||
</button>
|
||||
<button
|
||||
id="simulation-reset-button"
|
||||
className="simulation-button-container"
|
||||
onClick={() => {
|
||||
setHidePlayer(!hidePlayer);
|
||||
}}
|
||||
>
|
||||
<EyeCloseIcon />
|
||||
{!hidePlayer && "Hide"}
|
||||
</button>
|
||||
{subModule === "analysis" && (
|
||||
<button
|
||||
@@ -263,100 +279,102 @@ const SimulationPlayer: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="progresser-wrapper">
|
||||
<div className="time-displayer">
|
||||
<div className="start-time-wrappper">
|
||||
<div className="icon">
|
||||
<StartIcon />
|
||||
</div>
|
||||
<div className="time-wrapper">
|
||||
<div className="date">23 April ,25</div>
|
||||
<div className="time">04:41 PM</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="time-progresser">
|
||||
<div className="timeline">
|
||||
{intervals.map((label, index) => {
|
||||
const segmentProgress = (index / totalSegments) * 100;
|
||||
const isFilled = progress >= segmentProgress;
|
||||
return (
|
||||
<React.Fragment key={`${index}-${label}`}>
|
||||
<div className="label-dot-wrapper">
|
||||
<div className="label">{label} mins</div>
|
||||
<div
|
||||
className={`dot ${isFilled ? "filled" : ""}`}
|
||||
></div>
|
||||
</div>
|
||||
{index < intervals.length - 1 && (
|
||||
<div
|
||||
className={`line ${
|
||||
progress >= ((index + 1) / totalSegments) * 100
|
||||
? "filled"
|
||||
: ""
|
||||
}`}
|
||||
></div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="end-time-wrappper">
|
||||
<div className="time-wrapper">
|
||||
<div className="time">00:10:20</div>
|
||||
</div>
|
||||
<div className="icon">
|
||||
<EndIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="speed-control-container">
|
||||
<div className="min-value">
|
||||
<div className="icon">
|
||||
<SpeedIcon />
|
||||
</div>
|
||||
Speed
|
||||
</div>
|
||||
<div className="slider-container" ref={sliderRef}>
|
||||
<div className="speed-label mix-value">0.5X</div>
|
||||
<div className="marker marker-10"></div>
|
||||
<div className="marker marker-20"></div>
|
||||
<div className="marker marker-30"></div>
|
||||
<div className="marker marker-40"></div>
|
||||
<div className="marker marker-50"></div>
|
||||
<div className="marker marker-60"></div>
|
||||
<div className="marker marker-70"></div>
|
||||
<div className="marker marker-80"></div>
|
||||
<div className="marker marker-90"></div>
|
||||
<div className="custom-slider-wrapper">
|
||||
<div className="custom-slider">
|
||||
<button
|
||||
id="slider-handle"
|
||||
className={`slider-handle ${
|
||||
isDragging ? "dragging" : ""
|
||||
}`}
|
||||
style={{ left: `${calculateHandlePosition()}%` }}
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
{speed.toFixed(1)}x
|
||||
</button>
|
||||
<input
|
||||
type="range"
|
||||
min="0.5"
|
||||
max={MAX_SPEED}
|
||||
step="0.1"
|
||||
value={speed}
|
||||
onChange={handleSpeedChange}
|
||||
className="slider-input"
|
||||
/>
|
||||
{!hidePlayer && (
|
||||
<div className="progresser-wrapper">
|
||||
<div className="time-displayer">
|
||||
<div className="start-time-wrappper">
|
||||
<div className="icon">
|
||||
<StartIcon />
|
||||
</div>
|
||||
<div className="time-wrapper">
|
||||
<div className="date">23 April ,25</div>
|
||||
<div className="time">04:41 PM</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="time-progresser">
|
||||
<div className="timeline">
|
||||
{intervals.map((label, index) => {
|
||||
const segmentProgress = (index / totalSegments) * 100;
|
||||
const isFilled = progress >= segmentProgress;
|
||||
return (
|
||||
<React.Fragment key={`${index}-${label}`}>
|
||||
<div className="label-dot-wrapper">
|
||||
<div className="label">{label} mins</div>
|
||||
<div
|
||||
className={`dot ${isFilled ? "filled" : ""}`}
|
||||
></div>
|
||||
</div>
|
||||
{index < intervals.length - 1 && (
|
||||
<div
|
||||
className={`line ${
|
||||
progress >= ((index + 1) / totalSegments) * 100
|
||||
? "filled"
|
||||
: ""
|
||||
}`}
|
||||
></div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="end-time-wrappper">
|
||||
<div className="time-wrapper">
|
||||
<div className="time">00:10:20</div>
|
||||
</div>
|
||||
<div className="icon">
|
||||
<EndIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="speed-control-container">
|
||||
<div className="min-value">
|
||||
<div className="icon">
|
||||
<SpeedIcon />
|
||||
</div>
|
||||
Speed
|
||||
</div>
|
||||
<div className="slider-container" ref={sliderRef}>
|
||||
<div className="speed-label mix-value">0.5X</div>
|
||||
<div className="marker marker-10"></div>
|
||||
<div className="marker marker-20"></div>
|
||||
<div className="marker marker-30"></div>
|
||||
<div className="marker marker-40"></div>
|
||||
<div className="marker marker-50"></div>
|
||||
<div className="marker marker-60"></div>
|
||||
<div className="marker marker-70"></div>
|
||||
<div className="marker marker-80"></div>
|
||||
<div className="marker marker-90"></div>
|
||||
<div className="custom-slider-wrapper">
|
||||
<div className="custom-slider">
|
||||
<button
|
||||
id="slider-handle"
|
||||
className={`slider-handle ${
|
||||
isDragging ? "dragging" : ""
|
||||
}`}
|
||||
style={{ left: `${calculateHandlePosition()}%` }}
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
{speed.toFixed(1)}x
|
||||
</button>
|
||||
<input
|
||||
type="range"
|
||||
min="0.5"
|
||||
max={MAX_SPEED}
|
||||
step="0.1"
|
||||
value={speed}
|
||||
onChange={handleSpeedChange}
|
||||
className="slider-input"
|
||||
/>
|
||||
</div>
|
||||
<div className="speed-label max-value">{MAX_SPEED}x</div>
|
||||
</div>
|
||||
<div className="speed-label max-value">{MAX_SPEED}x</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{subModule === "analysis" && (
|
||||
)}
|
||||
{!hidePlayer && subModule === "analysis" && (
|
||||
<div className="processDisplayer">
|
||||
<div className="start-displayer timmer">00:00</div>
|
||||
<div className="end-displayer timmer">24:00</div>
|
||||
|
||||
@@ -3,7 +3,6 @@ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import gsap from 'gsap';
|
||||
import * as THREE from 'three';
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
import { toast } from 'react-toastify';
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
|
||||
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
|
||||
@@ -91,7 +90,7 @@ async function loadInitialFloorItems(
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
toast.error(`[IndexedDB] Error loading ${item.modelName}:`);
|
||||
echo.error(`[IndexedDB] Error loading ${item.modelName}:`);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
resolve();
|
||||
}
|
||||
@@ -111,7 +110,7 @@ async function loadInitialFloorItems(
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
toast.error(`[Backend] Error loading ${item.modelName}:`);
|
||||
echo.error(`[Backend] Error loading ${item.modelName}:`);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -33,7 +33,7 @@ async function loadInitialWallItems(
|
||||
|
||||
const loadedWallItems = await Promise.all(items.map(async (item: Types.WallItem) => {
|
||||
// Check THREE.js cache first
|
||||
const cachedModel = THREE.Cache.get(item.modelName!);
|
||||
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||
if (cachedModel) {
|
||||
return processModel(cachedModel, item);
|
||||
}
|
||||
@@ -45,7 +45,7 @@ async function loadInitialWallItems(
|
||||
return new Promise<Types.WallItem>((resolve) => {
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.add(item.modelName!, gltf);
|
||||
THREE.Cache.add(item.modelfileID!, gltf);
|
||||
resolve(processModel(gltf, item));
|
||||
});
|
||||
});
|
||||
@@ -58,8 +58,8 @@ async function loadInitialWallItems(
|
||||
try {
|
||||
// Cache the model
|
||||
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||
await storeGLTF(item.modelName!, modelBlob);
|
||||
THREE.Cache.add(item.modelName!, gltf);
|
||||
await storeGLTF(item.modelfileID!, modelBlob);
|
||||
THREE.Cache.add(item.modelfileID!, gltf);
|
||||
resolve(processModel(gltf, item));
|
||||
} catch (error) {
|
||||
console.error('Failed to cache model:', error);
|
||||
|
||||
125
app/src/modules/builder/assetGroup/assetsGroup.tsx
Normal file
125
app/src/modules/builder/assetGroup/assetsGroup.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import * as THREE from "three"
|
||||
import { useEffect } from 'react'
|
||||
import { getFloorAssets } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||
import { useLoadingProgress } from '../../../store/builder/store';
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||
import { FloorItems } from "../../../types/world/worldTypes";
|
||||
import { useAssetsStore } from "../../../store/builder/useAssetStore";
|
||||
import Models from "./models/models";
|
||||
import { useGLTF } from "@react-three/drei";
|
||||
|
||||
const gltfLoaderWorker = new Worker(
|
||||
new URL(
|
||||
"../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js",
|
||||
import.meta.url
|
||||
)
|
||||
);
|
||||
|
||||
function AssetsGroup() {
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const { setAssets } = useAssetsStore();
|
||||
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
|
||||
dracoLoader.setDecoderPath(
|
||||
"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/"
|
||||
);
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
useEffect(() => {
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email!.split("@")[1].split(".")[0];
|
||||
|
||||
let totalAssets = 0;
|
||||
let loadedAssets = 0;
|
||||
|
||||
const updateLoadingProgress = (progress: number) => {
|
||||
if (progress < 100) {
|
||||
setLoadingProgress(progress);
|
||||
} else if (progress === 100) {
|
||||
setTimeout(() => {
|
||||
setLoadingProgress(100);
|
||||
setTimeout(() => {
|
||||
setLoadingProgress(0);
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
getFloorAssets(organization).then((data) => {
|
||||
if (data.length > 0) {
|
||||
const uniqueItems = (data as FloorItems).filter((item, index, self) => index === self.findIndex((t) => t.modelfileID === item.modelfileID));
|
||||
totalAssets = uniqueItems.length;
|
||||
if (totalAssets === 0) {
|
||||
updateLoadingProgress(100);
|
||||
return;
|
||||
}
|
||||
gltfLoaderWorker.postMessage({ floorItems: uniqueItems });
|
||||
} else {
|
||||
gltfLoaderWorker.postMessage({ floorItems: [] });
|
||||
updateLoadingProgress(100);
|
||||
}
|
||||
});
|
||||
|
||||
gltfLoaderWorker.onmessage = async (event) => {
|
||||
if (event.data.message === "gltfLoaded" && event.data.modelBlob) {
|
||||
const blobUrl = URL.createObjectURL(event.data.modelBlob);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(event.data.modelID, gltf);
|
||||
|
||||
loadedAssets++;
|
||||
const progress = Math.round((loadedAssets / totalAssets) * 100);
|
||||
updateLoadingProgress(progress);
|
||||
|
||||
if (loadedAssets === totalAssets) {
|
||||
const assets: Asset[] = [];
|
||||
getFloorAssets(organization).then((data: FloorItems) => {
|
||||
data.forEach((item) => {
|
||||
if (item.eventData) {
|
||||
assets.push({
|
||||
modelUuid: item.modelUuid,
|
||||
modelName: item.modelName,
|
||||
assetId: item.modelfileID,
|
||||
position: item.position,
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
isLocked: item.isLocked,
|
||||
isCollidable: false,
|
||||
isVisible: item.isVisible,
|
||||
opacity: 1,
|
||||
eventData: item.eventData
|
||||
})
|
||||
} else {
|
||||
assets.push({
|
||||
modelUuid: item.modelUuid,
|
||||
modelName: item.modelName,
|
||||
assetId: item.modelfileID,
|
||||
position: item.position,
|
||||
rotation: [item.rotation.x, item.rotation.y, item.rotation.z],
|
||||
isLocked: item.isLocked,
|
||||
isCollidable: false,
|
||||
isVisible: item.isVisible,
|
||||
opacity: 1,
|
||||
})
|
||||
}
|
||||
})
|
||||
setAssets(assets);
|
||||
})
|
||||
updateLoadingProgress(100);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Models />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AssetsGroup;
|
||||
82
app/src/modules/builder/assetGroup/models/model/model.tsx
Normal file
82
app/src/modules/builder/assetGroup/models/model/model.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Outlines } from '@react-three/drei';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
|
||||
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import * as THREE from 'three';
|
||||
|
||||
function Model({ asset }: { asset: Asset }) {
|
||||
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
const [gltfScene, setGltfScene] = useState<GLTF | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
|
||||
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
const loadModel = async () => {
|
||||
try {
|
||||
const cachedModel = THREE.Cache.get(asset.assetId!);
|
||||
if (cachedModel) {
|
||||
setGltfScene(cachedModel);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check IndexedDB
|
||||
const indexedDBModel = await retrieveGLTF(asset.assetId!);
|
||||
if (indexedDBModel) {
|
||||
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(asset.assetId!, gltf);
|
||||
setGltfScene(gltf);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
echo.error(`[IndexedDB] Error loading ${asset.modelName}:`);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch from Backend
|
||||
const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${asset.assetId!}`;
|
||||
loader.load(modelUrl, async (gltf) => {
|
||||
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||
await storeGLTF(asset.assetId!, modelBlob);
|
||||
THREE.Cache.add(asset.assetId!, gltf);
|
||||
setGltfScene(gltf);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
echo.error(`[Backend] Error loading ${asset.modelName}:`);
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("Failed to load model:", asset.assetId, err);
|
||||
}
|
||||
};
|
||||
|
||||
loadModel();
|
||||
|
||||
}, [asset.assetId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{gltfScene &&
|
||||
<group
|
||||
position={asset.position}
|
||||
rotation={asset.rotation}
|
||||
visible={asset.isVisible}
|
||||
>
|
||||
<primitive object={gltfScene.scene.clone()} />
|
||||
</group>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Model;
|
||||
17
app/src/modules/builder/assetGroup/models/models.tsx
Normal file
17
app/src/modules/builder/assetGroup/models/models.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react'
|
||||
import { useAssetsStore } from '../../../../store/builder/useAssetStore';
|
||||
import Model from './model/model';
|
||||
|
||||
function Models() {
|
||||
const { assets } = useAssetsStore();
|
||||
|
||||
return (
|
||||
<>
|
||||
{assets.map((asset) =>
|
||||
<Model key={asset.modelUuid} asset={asset} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Models
|
||||
@@ -47,6 +47,7 @@ import MeasurementTool from "../scene/tools/measurementTool";
|
||||
import NavMesh from "../simulation/vehicle/navMesh/navMesh";
|
||||
import CalculateAreaGroup from "./groups/calculateAreaGroup";
|
||||
import LayoutImage from "./layout/layoutImage";
|
||||
import AssetsGroup from "./assetGroup/assetsGroup";
|
||||
|
||||
export default function Builder() {
|
||||
const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
|
||||
@@ -299,6 +300,8 @@ export default function Builder() {
|
||||
anglesnappedPoint={anglesnappedPoint}
|
||||
/>
|
||||
|
||||
{/* <AssetsGroup /> */}
|
||||
|
||||
<MeasurementTool />
|
||||
|
||||
<CalculateAreaGroup />
|
||||
|
||||
@@ -78,7 +78,7 @@ export default async function assetManager(
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
toast.error(`[IndexedDB] Error loading ${item.modelName}:`);
|
||||
echo.error(`[IndexedDB] Error loading ${item.modelName}:`);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
@@ -97,7 +97,7 @@ export default async function assetManager(
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
toast.error(`[Backend] Error loading ${item.modelName}:`);
|
||||
echo.error(`[Backend] Error loading ${item.modelName}:`);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ function updateDistanceText(
|
||||
const textMesh = text as THREE.Mesh;
|
||||
if (textMesh.userData[0][1] === linePoints[0][1] && textMesh.userData[1][1] === linePoints[1][1]) {
|
||||
textMesh.position.set(position.x, 1, position.z);
|
||||
const className = `Distance line-${textMesh.userData[0][1]}_${textMesh.userData[1][1]}_${linePoints[0][2]}`;
|
||||
const className = `distance line-${textMesh.userData[0][1]}_${textMesh.userData[1][1]}_${linePoints[0][2]}`;
|
||||
const element = document.getElementsByClassName(className)[0] as HTMLElement;
|
||||
if (element) {
|
||||
element.innerHTML = `${distance} m`;
|
||||
|
||||
@@ -104,7 +104,7 @@ const FloorItemsGroup = ({
|
||||
updateLoadingProgress(100);
|
||||
return;
|
||||
}
|
||||
gltfLoaderWorker.postMessage({ floorItems: data });
|
||||
gltfLoaderWorker.postMessage({ floorItems: uniqueItems });
|
||||
} else {
|
||||
gltfLoaderWorker.postMessage({ floorItems: [] });
|
||||
loadInitialFloorItems(
|
||||
|
||||
@@ -103,7 +103,7 @@ const Card: React.FC<CardProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
id="asset-buy"
|
||||
id={`${AssetID}-asset-buy`}
|
||||
className="buy-now-button"
|
||||
onClick={handleCardSelect}
|
||||
>
|
||||
|
||||
@@ -79,7 +79,7 @@ const FilterSearch: React.FC<ModelsProps> = ({
|
||||
<div className="stars">
|
||||
{[0, 1, 2, 3, 4].map((i) => (
|
||||
<button
|
||||
id="star-button"
|
||||
id={`${i + 1}-star-button`}
|
||||
key={i}
|
||||
onClick={() => handleStarClick(i)}
|
||||
className={`star-wrapper ${i < rating ? "filled" : "empty"}`}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useKeyboardControls } from "@react-three/drei";
|
||||
import switchToThirdPerson from "./switchToThirdPerson";
|
||||
import switchToFirstPerson from "./switchToFirstPerson";
|
||||
import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys";
|
||||
import { firstPersonCamera } from "./firstPersonCamera";
|
||||
|
||||
const CamMode: React.FC = () => {
|
||||
const { camMode, setCamMode } = useCamMode();
|
||||
@@ -65,26 +66,14 @@ const CamMode: React.FC = () => {
|
||||
const keyCombination = detectModifierKeys(event);
|
||||
|
||||
if (keyCombination === "/" && !isTransitioning && !toggleView) {
|
||||
setIsTransitioning(true);
|
||||
|
||||
state.controls.mouseButtons.left =
|
||||
CONSTANTS.controlsTransition.leftMouse;
|
||||
state.controls.mouseButtons.right =
|
||||
CONSTANTS.controlsTransition.rightMouse;
|
||||
state.controls.mouseButtons.wheel =
|
||||
CONSTANTS.controlsTransition.wheelMouse;
|
||||
state.controls.mouseButtons.middle =
|
||||
CONSTANTS.controlsTransition.middleMouse;
|
||||
|
||||
if (camMode === "ThirdPerson") {
|
||||
setCamMode("FirstPerson");
|
||||
await switchToFirstPerson(state.controls, state.camera);
|
||||
} else if (camMode === "FirstPerson") {
|
||||
setCamMode("ThirdPerson");
|
||||
await switchToThirdPerson(state.controls, state.camera);
|
||||
}
|
||||
|
||||
setIsTransitioning(false);
|
||||
firstPersonCamera({
|
||||
setIsTransitioning,
|
||||
state,
|
||||
camMode,
|
||||
setCamMode,
|
||||
switchToFirstPerson,
|
||||
switchToThirdPerson,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,6 +81,7 @@ const CamMode: React.FC = () => {
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleKeyPress);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
camMode,
|
||||
isTransitioning,
|
||||
@@ -140,6 +130,3 @@ const CamMode: React.FC = () => {
|
||||
};
|
||||
|
||||
export default CamMode;
|
||||
|
||||
|
||||
|
||||
|
||||
39
app/src/modules/scene/camera/firstPersonCamera.ts
Normal file
39
app/src/modules/scene/camera/firstPersonCamera.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||
|
||||
interface FirstPersonCameraProps {
|
||||
setIsTransitioning?: (value: boolean) => void;
|
||||
state: any;
|
||||
}
|
||||
|
||||
interface FirstPersonCameraParams extends FirstPersonCameraProps {
|
||||
camMode: string;
|
||||
setCamMode: (mode: string) => void;
|
||||
switchToFirstPerson: (controls: any, camera: any) => Promise<void>;
|
||||
switchToThirdPerson: (controls: any, camera: any) => Promise<void>;
|
||||
}
|
||||
|
||||
export async function firstPersonCamera({
|
||||
setIsTransitioning,
|
||||
state,
|
||||
camMode,
|
||||
setCamMode,
|
||||
switchToFirstPerson,
|
||||
switchToThirdPerson
|
||||
}: FirstPersonCameraParams): Promise<void> {
|
||||
setIsTransitioning && setIsTransitioning(true);
|
||||
|
||||
state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
|
||||
state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
|
||||
state.controls.mouseButtons.wheel = CONSTANTS.controlsTransition.wheelMouse;
|
||||
state.controls.mouseButtons.middle = CONSTANTS.controlsTransition.middleMouse;
|
||||
|
||||
if (camMode === "ThirdPerson") {
|
||||
setCamMode("FirstPerson");
|
||||
await switchToFirstPerson(state.controls, state.camera);
|
||||
} else if (camMode === "FirstPerson") {
|
||||
setCamMode("ThirdPerson");
|
||||
await switchToThirdPerson(state.controls, state.camera);
|
||||
}
|
||||
|
||||
setIsTransitioning && setIsTransitioning(false);
|
||||
}
|
||||
@@ -14,7 +14,7 @@ const Templates = () => {
|
||||
useEffect(() => {
|
||||
async function templateData() {
|
||||
try {
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const email = localStorage.getItem("email") ?? "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
let response = await getTemplateData(organization);
|
||||
setTemplates(response);
|
||||
@@ -33,8 +33,7 @@ const Templates = () => {
|
||||
) => {
|
||||
try {
|
||||
e.stopPropagation();
|
||||
const email = localStorage.getItem("email") || "";
|
||||
|
||||
const email = localStorage.getItem("email") ?? "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
let deleteTemplate = {
|
||||
organization: organization,
|
||||
@@ -56,7 +55,7 @@ const Templates = () => {
|
||||
const handleLoadTemplate = async (template: any) => {
|
||||
try {
|
||||
if (selectedZone.zoneName === "") return;
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const email = localStorage.getItem("email") ?? "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
|
||||
let loadingTemplate = {
|
||||
@@ -110,7 +109,6 @@ const Templates = () => {
|
||||
)}
|
||||
<div className="template-details">
|
||||
<div className="template-name">
|
||||
{/* {`Template ${index + 1}`} */}
|
||||
<RenameInput value={`Template ${index + 1}`} />
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import {
|
||||
CleanPannel,
|
||||
EyeIcon,
|
||||
@@ -95,8 +95,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
|
||||
// Function to toggle lock/unlock a panel
|
||||
const toggleLockPanel = async (side: Side) => {
|
||||
// console.log('side: ', side);
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const email = localStorage.getItem("email") ?? "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0]; // Fallback value
|
||||
//add api
|
||||
const newLockedPanels = selectedZone.lockedPanels.includes(side)
|
||||
@@ -118,12 +117,6 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
}
|
||||
|
||||
setSelectedZone(updatedZone);
|
||||
// let response = await lockPanel(selectedZone.zoneId, organization, newLockedPanels)
|
||||
// console.log('response: ', response);
|
||||
// if (response.message === 'locked panel updated successfully') {
|
||||
// // Update the selectedZone state
|
||||
// setSelectedZone(updatedZone);
|
||||
// }
|
||||
};
|
||||
|
||||
// Function to clean all widgets from a panel
|
||||
@@ -136,7 +129,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
)
|
||||
return;
|
||||
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const email = localStorage.getItem("email") ?? "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0]; // Fallback value
|
||||
|
||||
let clearPanel = {
|
||||
@@ -155,23 +148,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
widgets: cleanedWidgets,
|
||||
};
|
||||
// Update the selectedZone state
|
||||
// console.log('updatedZone: ', updatedZone);
|
||||
setSelectedZone(updatedZone);
|
||||
|
||||
// let response = await clearPanel(selectedZone.zoneId, organization, side)
|
||||
// console.log('response: ', response);
|
||||
// if (response.message === 'PanelWidgets cleared successfully') {
|
||||
|
||||
// const cleanedWidgets = selectedZone.widgets.filter(
|
||||
// (widget) => widget.panel !== side
|
||||
// );
|
||||
// const updatedZone = {
|
||||
// ...selectedZone,
|
||||
// widgets: cleanedWidgets,
|
||||
// };
|
||||
// // Update the selectedZone state
|
||||
// setSelectedZone(updatedZone);
|
||||
// }
|
||||
};
|
||||
|
||||
// Function to handle "+" button click
|
||||
@@ -186,8 +163,8 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
setTimeout(() => {
|
||||
console.log("Removing after wait...");
|
||||
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0] || "";
|
||||
const email = localStorage.getItem("email") ?? "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0] ?? "";
|
||||
|
||||
// Remove widgets for that side
|
||||
const cleanedWidgets = selectedZone.widgets.filter(
|
||||
@@ -229,8 +206,8 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
} else {
|
||||
// Panel does not exist: Add it
|
||||
try {
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0] || "";
|
||||
const email = localStorage.getItem("email") ?? "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0] ?? "";
|
||||
|
||||
const newActiveSides = selectedZone.activeSides.includes(side)
|
||||
? [...selectedZone.activeSides]
|
||||
@@ -261,33 +238,32 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{(["top", "right", "bottom", "left"] as Side[]).map((side) => (
|
||||
<div key={side} className={`side-button-container ${side}`}>
|
||||
{/* "+" Button */}
|
||||
<div>
|
||||
{(["top", "right", "bottom", "left"] as Side[]).map((side) => (
|
||||
<div key={side} className={`side-button-container ${side}`}>
|
||||
{/* "+" Button */}
|
||||
|
||||
<button
|
||||
id="panel-add-button"
|
||||
className={`side-button ${side}${
|
||||
selectedZone.activeSides.includes(side) ? " active" : ""
|
||||
}`}
|
||||
onClick={() => handlePlusButtonClick(side)}
|
||||
title={
|
||||
selectedZone.activeSides.includes(side)
|
||||
? `Remove all items and close ${side} panel`
|
||||
: `Activate ${side} panel`
|
||||
}
|
||||
>
|
||||
<div className="add-icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
id="panel-add-button"
|
||||
className={`side-button ${side}${
|
||||
selectedZone.activeSides.includes(side) ? " active" : ""
|
||||
}`}
|
||||
onClick={() => handlePlusButtonClick(side)}
|
||||
title={
|
||||
selectedZone.activeSides.includes(side)
|
||||
? `Remove all items and close ${side} panel`
|
||||
: `Activate ${side} panel`
|
||||
}
|
||||
>
|
||||
<div className="add-icon">
|
||||
<AddIcon />
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Extra Buttons */}
|
||||
{selectedZone.activeSides.includes(side) && (
|
||||
<div
|
||||
className={`extra-Bs
|
||||
{/* Extra Buttons */}
|
||||
{selectedZone.activeSides.includes(side) && (
|
||||
<div
|
||||
className={`extra-Bs
|
||||
${waitingPanels === side ? "extra-Bs-addclosing" : ""}
|
||||
${
|
||||
!hiddenPanels[selectedZone.zoneId]?.includes(side) &&
|
||||
@@ -296,72 +272,74 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
: ""
|
||||
}
|
||||
`}
|
||||
>
|
||||
{/* Hide Panel */}
|
||||
<button
|
||||
className={`icon ${
|
||||
hiddenPanels[selectedZone.zoneId]?.includes(side)
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
id="hide-panel-visulization"
|
||||
title={
|
||||
hiddenPanels[selectedZone.zoneId]?.includes(side)
|
||||
? "Show Panel"
|
||||
: "Hide Panel"
|
||||
}
|
||||
onClick={() => toggleVisibility(side)}
|
||||
>
|
||||
{/* Hide Panel */}
|
||||
<div
|
||||
className={`icon ${
|
||||
<EyeIcon
|
||||
fill={
|
||||
hiddenPanels[selectedZone.zoneId]?.includes(side)
|
||||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
title={
|
||||
hiddenPanels[selectedZone.zoneId]?.includes(side)
|
||||
? "Show Panel"
|
||||
: "Hide Panel"
|
||||
? "var(--icon-default-color-active)"
|
||||
: "var(--text-color)"
|
||||
}
|
||||
onClick={() => toggleVisibility(side)}
|
||||
>
|
||||
<EyeIcon
|
||||
fill={
|
||||
hiddenPanels[selectedZone.zoneId]?.includes(side)
|
||||
? "var(--icon-default-color-active)"
|
||||
: "var(--text-color)"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
</button>
|
||||
|
||||
{/* Clean Panel */}
|
||||
<div
|
||||
className="icon"
|
||||
title="Clean Panel"
|
||||
onClick={() => cleanPanel(side)}
|
||||
style={{
|
||||
cursor:
|
||||
hiddenPanels[selectedZone.zoneId]?.includes(side) ||
|
||||
selectedZone.lockedPanels.includes(side)
|
||||
? "not-allowed"
|
||||
: "pointer",
|
||||
}}
|
||||
>
|
||||
<CleanPannel />
|
||||
</div>
|
||||
|
||||
{/* Lock/Unlock Panel */}
|
||||
<div
|
||||
className={`icon ${
|
||||
selectedZone.lockedPanels.includes(side) ? "active" : ""
|
||||
}`}
|
||||
title={
|
||||
{/* Clean Panel */}
|
||||
<button
|
||||
className="icon"
|
||||
title="Clean Panel"
|
||||
id="clean-panel-visulization"
|
||||
onClick={() => cleanPanel(side)}
|
||||
style={{
|
||||
cursor:
|
||||
hiddenPanels[selectedZone.zoneId]?.includes(side) ||
|
||||
selectedZone.lockedPanels.includes(side)
|
||||
? "Unlock Panel"
|
||||
: "Lock Panel"
|
||||
? "not-allowed"
|
||||
: "pointer",
|
||||
}}
|
||||
>
|
||||
<CleanPannel />
|
||||
</button>
|
||||
|
||||
{/* Lock/Unlock Panel */}
|
||||
<button
|
||||
className={`icon ${
|
||||
selectedZone.lockedPanels.includes(side) ? "active" : ""
|
||||
}`}
|
||||
id="lock-panel-visulization"
|
||||
title={
|
||||
selectedZone.lockedPanels.includes(side)
|
||||
? "Unlock Panel"
|
||||
: "Lock Panel"
|
||||
}
|
||||
onClick={() => toggleLockPanel(side)}
|
||||
>
|
||||
<LockIcon
|
||||
fill={
|
||||
selectedZone.lockedPanels.includes(side)
|
||||
? "var(--icon-default-color-active)"
|
||||
: "var(--text-color)"
|
||||
}
|
||||
onClick={() => toggleLockPanel(side)}
|
||||
>
|
||||
<LockIcon
|
||||
fill={
|
||||
selectedZone.lockedPanels.includes(side)
|
||||
? "var(--icon-default-color-active)"
|
||||
: "var(--text-color)"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { arrayMove } from "@dnd-kit/sortable";
|
||||
import { useAsset3dWidget, useSocketStore } from "../../../../store/builder/store";
|
||||
import { useSocketStore } from "../../../../store/builder/store";
|
||||
import { usePlayButtonStore } from "../../../../store/usePlayButtonStore";
|
||||
import { useWidgetStore } from "../../../../store/useWidgetStore";
|
||||
import { DraggableWidget } from "../2d/DraggableWidget";
|
||||
@@ -47,7 +47,7 @@ interface PanelProps {
|
||||
}
|
||||
|
||||
const generateUniqueId = () =>
|
||||
`${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
`${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
||||
|
||||
const Panel: React.FC<PanelProps> = ({
|
||||
selectedZone,
|
||||
@@ -56,7 +56,6 @@ const Panel: React.FC<PanelProps> = ({
|
||||
setZonesData,
|
||||
waitingPanels,
|
||||
}) => {
|
||||
const { widgetSelect, setWidgetSelect } = useAsset3dWidget();
|
||||
const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({});
|
||||
const [panelDimensions, setPanelDimensions] = useState<{
|
||||
[side in Side]?: { width: number; height: number };
|
||||
@@ -183,7 +182,7 @@ const Panel: React.FC<PanelProps> = ({
|
||||
|
||||
// Add widget to panel
|
||||
const addWidgetToPanel = async (asset: any, panel: Side) => {
|
||||
const email = localStorage.getItem("email") || "";
|
||||
const email = localStorage.getItem("email") ?? "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
|
||||
const newWidget = {
|
||||
@@ -285,7 +284,7 @@ const Panel: React.FC<PanelProps> = ({
|
||||
{selectedZone.activeSides.map((side) => (
|
||||
<div
|
||||
key={side}
|
||||
id="panel-wrapper"
|
||||
id={`panel-wrapper-${side}`}
|
||||
className={`panel ${side}-panel absolute ${
|
||||
hiddenPanels[selectedZone.zoneId]?.includes(side) ? "hidePanel" : ""
|
||||
}`}
|
||||
@@ -301,14 +300,14 @@ const Panel: React.FC<PanelProps> = ({
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`panel-content
|
||||
${waitingPanels === side ? `${side}-closing` : ""}
|
||||
${
|
||||
!hiddenPanels[selectedZone.zoneId]?.includes(side) && waitingPanels !== side
|
||||
? `${side}-opening`
|
||||
: ""
|
||||
}
|
||||
${isPlaying ? "fullScreen" : ""}`}
|
||||
className={`panel-content ${
|
||||
waitingPanels === side ? `${side}-closing` : ""
|
||||
}${
|
||||
!hiddenPanels[selectedZone.zoneId]?.includes(side) &&
|
||||
waitingPanels !== side
|
||||
? `${side}-opening`
|
||||
: ""
|
||||
} ${isPlaying ? "fullScreen" : ""}`}
|
||||
style={{
|
||||
pointerEvents:
|
||||
selectedZone.lockedPanels.includes(side) ||
|
||||
|
||||
@@ -82,7 +82,7 @@ const Project: React.FC = () => {
|
||||
const { toggleThreeD } = useThreeDStore();
|
||||
|
||||
// simulation store
|
||||
const { isPlaying, setIsPlaying } = usePlayButtonStore();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
|
||||
// collaboration store
|
||||
const { selectedUser } = useSelectedUserStore();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||
// let url_Backend_dwinzo = `http://192.168.0.102:5000`;
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
export const panelData = async (
|
||||
|
||||
221
app/src/store/builder/useAssetStore.ts
Normal file
221
app/src/store/builder/useAssetStore.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
import { create } from 'zustand';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
|
||||
interface AssetsStore {
|
||||
assets: Assets;
|
||||
|
||||
// Asset CRUD operations
|
||||
addAsset: (asset: Asset) => void;
|
||||
removeAsset: (modelUuid: string) => void;
|
||||
updateAsset: (modelUuid: string, updates: Partial<Asset>) => void;
|
||||
setAssets: (assets: Assets) => void;
|
||||
|
||||
// Asset properties
|
||||
setPosition: (modelUuid: string, position: [number, number, number]) => void;
|
||||
setRotation: (modelUuid: string, rotation: [number, number, number]) => void;
|
||||
setLock: (modelUuid: string, isLocked: boolean) => void;
|
||||
setCollision: (modelUuid: string, isCollidable: boolean) => void;
|
||||
setVisibility: (modelUuid: string, isVisible: boolean) => void;
|
||||
setOpacity: (modelUuid: string, opacity: number) => void;
|
||||
|
||||
// Animation controls
|
||||
setAnimation: (modelUuid: string, animation: string) => void;
|
||||
setCurrentAnimation: (modelUuid: string, current: string, isPlaying: boolean) => void;
|
||||
addAnimation: (modelUuid: string, animation: string) => void;
|
||||
removeAnimation: (modelUuid: string, animation: string) => void;
|
||||
|
||||
// Event data operations
|
||||
addEventData: (modelUuid: string, eventData: Asset['eventData']) => void;
|
||||
updateEventData: (modelUuid: string, updates: Partial<Asset['eventData']>) => void;
|
||||
removeEventData: (modelUuid: string) => void;
|
||||
|
||||
// Helper functions
|
||||
getAssetById: (modelUuid: string) => Asset | undefined;
|
||||
getAssetByPointUuid: (pointUuid: string) => Asset | undefined;
|
||||
hasAsset: (modelUuid: string) => boolean;
|
||||
}
|
||||
|
||||
export const useAssetsStore = create<AssetsStore>()(
|
||||
immer((set, get) => ({
|
||||
assets: [],
|
||||
|
||||
// Asset CRUD operations
|
||||
addAsset: (asset) => {
|
||||
set((state) => {
|
||||
if (!state.assets.some(a => a.modelUuid === asset.modelUuid)) {
|
||||
state.assets.push(asset);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
removeAsset: (modelUuid) => {
|
||||
set((state) => {
|
||||
state.assets = state.assets.filter(a => a.modelUuid !== modelUuid);
|
||||
});
|
||||
},
|
||||
|
||||
updateAsset: (modelUuid, updates) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset) {
|
||||
Object.assign(asset, updates);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setAssets: (assets) => {
|
||||
set((state) => {
|
||||
state.assets = assets;
|
||||
});
|
||||
},
|
||||
|
||||
// Asset properties
|
||||
setPosition: (modelUuid, position) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset) {
|
||||
asset.position = position;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setRotation: (modelUuid, rotation) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset) {
|
||||
asset.rotation = rotation;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setLock: (modelUuid, isLocked) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset) {
|
||||
asset.isLocked = isLocked;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setCollision: (modelUuid, isCollidable) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset) {
|
||||
asset.isCollidable = isCollidable;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setVisibility: (modelUuid, isVisible) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset) {
|
||||
asset.isVisible = isVisible;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setOpacity: (modelUuid, opacity) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset) {
|
||||
asset.opacity = opacity;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Animation controls
|
||||
setAnimation: (modelUuid, animation) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset) {
|
||||
if (!asset.animationState) {
|
||||
asset.animationState = { current: animation, playing: false };
|
||||
} else {
|
||||
asset.animationState.current = animation;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setCurrentAnimation: (modelUuid, current, isPlaying) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset?.animationState) {
|
||||
asset.animationState.current = current;
|
||||
asset.animationState.playing = isPlaying;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
addAnimation: (modelUuid, animation) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset) {
|
||||
if (!asset.animations) {
|
||||
asset.animations = [animation];
|
||||
} else if (!asset.animations.includes(animation)) {
|
||||
asset.animations.push(animation);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
removeAnimation: (modelUuid, animation) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset?.animations) {
|
||||
asset.animations = asset.animations.filter(a => a !== animation);
|
||||
if (asset.animationState?.current === animation) {
|
||||
asset.animationState.playing = false;
|
||||
asset.animationState.current = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Event data operations
|
||||
addEventData: (modelUuid, eventData) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset) {
|
||||
asset.eventData = eventData;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateEventData: (modelUuid, updates) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset?.eventData) {
|
||||
asset.eventData = { ...asset.eventData, ...updates };
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
removeEventData: (modelUuid) => {
|
||||
set((state) => {
|
||||
const asset = state.assets.find(a => a.modelUuid === modelUuid);
|
||||
if (asset) {
|
||||
delete asset.eventData;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Helper functions
|
||||
getAssetById: (modelUuid) => {
|
||||
return get().assets.find(a => a.modelUuid === modelUuid);
|
||||
},
|
||||
|
||||
getAssetByPointUuid: (pointUuid) => {
|
||||
return get().assets.find(asset =>
|
||||
asset.eventData?.point?.uuid === pointUuid ||
|
||||
asset.eventData?.points?.some(p => p.uuid === pointUuid)
|
||||
);
|
||||
},
|
||||
|
||||
hasAsset: (modelUuid) => {
|
||||
return get().assets.some(a => a.modelUuid === modelUuid);
|
||||
}
|
||||
}))
|
||||
);
|
||||
@@ -33,3 +33,17 @@ export const useAnimationPlaySpeed = create<AnimationSpeedState>((set) => ({
|
||||
speed: 1,
|
||||
setSpeed: (value) => set({ speed: value }),
|
||||
}));
|
||||
|
||||
interface CameraModeState {
|
||||
walkMode: boolean;
|
||||
setWalkMode: (enabled: boolean) => void;
|
||||
toggleWalkMode: () => void;
|
||||
}
|
||||
|
||||
const useCameraModeStore = create<CameraModeState>((set) => ({
|
||||
walkMode: false,
|
||||
setWalkMode: (enabled) => set({ walkMode: enabled }),
|
||||
toggleWalkMode: () => set((state) => ({ walkMode: !state.walkMode })),
|
||||
}));
|
||||
|
||||
export default useCameraModeStore;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
@use "../../abstracts/variables" as *;
|
||||
@use "../../abstracts/mixins" as *;
|
||||
|
||||
|
||||
.footer-container {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
@@ -34,7 +33,7 @@
|
||||
|
||||
.selector {
|
||||
color: var(--text-color);
|
||||
font-size: var(--font-size-small)
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.icon {
|
||||
@@ -48,7 +47,7 @@
|
||||
gap: 6px;
|
||||
position: relative;
|
||||
pointer-events: all;
|
||||
// dummy
|
||||
// dummy
|
||||
.bg-dummy {
|
||||
background: var(--background-color-solid);
|
||||
position: absolute;
|
||||
@@ -166,11 +165,23 @@
|
||||
min-height: 320px;
|
||||
height: 320px;
|
||||
border-radius: 18px;
|
||||
|
||||
|
||||
pointer-events: all;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
|
||||
.close-button {
|
||||
position: absolute;
|
||||
@include flex-center;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
right: 12px;
|
||||
top: 10px;
|
||||
background: var(--background-color);
|
||||
border-radius: #{$border-radius-medium};
|
||||
outline: 1px solid var(--border-color);
|
||||
&:hover{
|
||||
background: var(--background-color-solid);
|
||||
}
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -192,7 +203,7 @@
|
||||
padding-left: 10px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -244,7 +255,6 @@
|
||||
|
||||
.shortcut-intro {
|
||||
display: flex;
|
||||
// align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
.value-wrapper {
|
||||
@@ -254,7 +264,6 @@
|
||||
|
||||
.description {
|
||||
font-size: var(--font-size-tiny);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,14 +274,22 @@
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
|
||||
.key {
|
||||
background: linear-gradient(135.11deg, #656DC2 3.48%, #9526E5 91.33%);
|
||||
background: linear-gradient(
|
||||
135.11deg,
|
||||
#656dc2 3.48%,
|
||||
#9526e5 91.33%
|
||||
);
|
||||
@include flex-center;
|
||||
padding: 4px 10px;
|
||||
height: 25px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: var(--font-size-tiny);
|
||||
color: var(--icon-default-color-active);
|
||||
&:last-child{
|
||||
background: var(--background-color-button);
|
||||
}
|
||||
}
|
||||
|
||||
.key.add {
|
||||
@@ -295,19 +312,12 @@
|
||||
align-items: flex-start;
|
||||
/* or center if vertical centering is desired */
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
.shortcut-helper-overlay {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
// opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: all 0.3s ease-in-out;
|
||||
|
||||
@@ -316,4 +326,4 @@
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,16 @@
|
||||
z-index: 2;
|
||||
transform: translate(-50%, 0);
|
||||
width: 70vw;
|
||||
|
||||
transition: all 0.3s;
|
||||
&.hide {
|
||||
width: fit-content;
|
||||
.simulation-player-container
|
||||
.controls-container
|
||||
.simulation-button-container {
|
||||
width: 32px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
.simulation-player-container {
|
||||
background: var(--background-color);
|
||||
padding: 7px;
|
||||
@@ -90,11 +99,12 @@
|
||||
@include flex-center;
|
||||
gap: 2px;
|
||||
padding: 4px 8px;
|
||||
min-width: 64px;
|
||||
width: 64px;
|
||||
background: var(--background-color);
|
||||
border-radius: #{$border-radius-extra-large};
|
||||
height: fit-content;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
outline: 1px solid var(--border-color);
|
||||
@@ -347,6 +357,86 @@
|
||||
}
|
||||
}
|
||||
|
||||
.controls-player-container {
|
||||
min-width: 26vw;
|
||||
max-width: 80vw;
|
||||
border-radius: 15px;
|
||||
gap: 40px;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
cursor: pointer;
|
||||
@include flex-center;
|
||||
justify-content: space-between;
|
||||
position: fixed;
|
||||
bottom: 32px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
color: var(--accent-color);
|
||||
z-index: 100;
|
||||
isolation: isolate;
|
||||
font-weight: 700;
|
||||
padding: 8px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.hide {
|
||||
min-width: auto;
|
||||
width: 92px;
|
||||
}
|
||||
|
||||
.controls-left,
|
||||
.controls-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: var(--font-size-small);
|
||||
|
||||
.label {
|
||||
text-transform: capitalize;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.walkMode-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
.input-toggle-container {
|
||||
padding: 0;
|
||||
gap: 4px;
|
||||
|
||||
.label {
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-wrapper {
|
||||
@include flex-center;
|
||||
gap: 2px;
|
||||
padding: 4px 8px;
|
||||
width: 64px;
|
||||
background: var(--background-color);
|
||||
border-radius: 20px;
|
||||
height: fit-content;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
outline: 1px solid transparent;
|
||||
&:hover {
|
||||
outline: 1px solid var(--border-color);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
&.hide {
|
||||
width: 32px;
|
||||
}
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@include flex-center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.processDisplayer {
|
||||
border-radius: #{$border-radius-large};
|
||||
outline: 1px solid var(--border-color);
|
||||
@@ -454,7 +544,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.storage-container{
|
||||
.storage-container {
|
||||
font-size: var(--font-size-tiny);
|
||||
color: var(--highlight-text-color);
|
||||
}
|
||||
|
||||
@@ -12,12 +12,16 @@
|
||||
box-shadow: #{$box-shadow-medium};
|
||||
border-radius: #{$border-radius-large};
|
||||
width: fit-content;
|
||||
transition: width 0.2s;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
z-index: 2;
|
||||
outline: 1px solid var(--border-color);
|
||||
outline-offset: -1px;
|
||||
transition: transform 0.4s ease-in-out 0.01s;
|
||||
|
||||
&.visible {
|
||||
transform: translate(-50%, -310px);
|
||||
}
|
||||
|
||||
.split {
|
||||
height: 20px;
|
||||
@@ -47,9 +51,11 @@
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb,
|
||||
var(--highlight-accent-color) 60%,
|
||||
transparent);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--highlight-accent-color) 60%,
|
||||
transparent
|
||||
);
|
||||
|
||||
.tooltip {
|
||||
opacity: 1;
|
||||
@@ -78,9 +84,11 @@
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb,
|
||||
var(--highlight-accent-color) 60%,
|
||||
transparent);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--highlight-accent-color) 60%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.drop-down-container {
|
||||
@@ -178,88 +186,6 @@
|
||||
}
|
||||
|
||||
|
||||
.tools-container {
|
||||
transition: transform 0.4s ease-in-out 0.01s;
|
||||
|
||||
&.visible {
|
||||
transform: translate(-50%, -310px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.controls-player-container {
|
||||
width: 663px;
|
||||
height: 30px;
|
||||
border-radius: 15px;
|
||||
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
cursor: pointer;
|
||||
@include flex-center;
|
||||
justify-content: space-between;
|
||||
position: fixed;
|
||||
bottom: 40px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
color: var(--accent-color);
|
||||
z-index: 100;
|
||||
isolation: isolate;
|
||||
font-weight: 700;
|
||||
padding: 8px;
|
||||
|
||||
.controls-left,
|
||||
.controls-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: var(--font-size-small);
|
||||
|
||||
.label {
|
||||
text-transform: capitalize;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
||||
.walkMode-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
|
||||
.input-toggle-container {
|
||||
padding: 0;
|
||||
gap: 4px;
|
||||
|
||||
.label {
|
||||
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-wrapper {
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(8px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 5px;
|
||||
border-radius: 15px;
|
||||
padding: 2px 8px;
|
||||
color: var(--highlight-text-color);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-small);
|
||||
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
@include flex-center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0;
|
||||
@@ -286,4 +212,4 @@
|
||||
width: fit-content;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
.floating {
|
||||
// width: calc(var(--realTimeViz-container-width) * 0.2px);
|
||||
|
||||
// transform: scale(min(1, calc(var(--realTimeViz-container-width) / 1000)));
|
||||
|
||||
min-width: 230px;
|
||||
@@ -63,7 +62,6 @@
|
||||
left: 50%;
|
||||
gap: 6px;
|
||||
border-radius: #{$border-radius-medium};
|
||||
|
||||
overflow: auto;
|
||||
max-width: calc(100% - 500px);
|
||||
z-index: 3;
|
||||
@@ -71,7 +69,7 @@
|
||||
pointer-events: all;
|
||||
transition: all 0.3s linear;
|
||||
|
||||
&.bottom {
|
||||
&.bottom{
|
||||
bottom: var(--bottomWidth);
|
||||
}
|
||||
|
||||
@@ -122,7 +120,10 @@
|
||||
}
|
||||
|
||||
.zone-container.visualization-playing {
|
||||
bottom: 70px;
|
||||
bottom: 74px;
|
||||
&.bottom{
|
||||
bottom: var(--bottomWidth);
|
||||
}
|
||||
}
|
||||
|
||||
.zone-wrapper.bottom {
|
||||
|
||||
31
app/src/types/builderTypes.d.ts
vendored
Normal file
31
app/src/types/builderTypes.d.ts
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
interface Asset {
|
||||
modelUuid: string;
|
||||
modelName: string;
|
||||
assetId: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
isLocked: boolean;
|
||||
isCollidable: boolean;
|
||||
isVisible: boolean;
|
||||
opacity: number;
|
||||
animations?: string[];
|
||||
animationState?: {
|
||||
current: string;
|
||||
playing: boolean;
|
||||
};
|
||||
eventData?: {
|
||||
type: string;
|
||||
point?: {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
}
|
||||
points?: {
|
||||
uuid: string;
|
||||
position: [number, number, number];
|
||||
rotation: [number, number, number];
|
||||
}[];
|
||||
}
|
||||
};
|
||||
|
||||
type Assets = Asset[];
|
||||
@@ -12,25 +12,25 @@ import {
|
||||
useToggleView,
|
||||
useToolMode,
|
||||
} from "../../store/builder/store";
|
||||
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
||||
import useCameraModeStore, { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
||||
import { detectModifierKeys } from "./detectModifierKeys";
|
||||
import { useSelectedZoneStore } from "../../store/visualization/useZoneStore";
|
||||
|
||||
const KeyPressListener: React.FC = () => {
|
||||
const { activeModule, setActiveModule } = useModuleStore();
|
||||
const { setActiveSubTool } = useActiveSubTool();
|
||||
const { toggleUILeft, toggleUIRight, setToggleUI } = useToggleStore();
|
||||
const { setToggleThreeD } = useThreeDStore();
|
||||
const { setToolMode } = useToolMode();
|
||||
const { setIsPlaying } = usePlayButtonStore();
|
||||
const { toggleView, setToggleView } = useToggleView();
|
||||
const { setDeleteTool } = useDeleteTool();
|
||||
const { setAddAction } = useAddAction();
|
||||
const { setSelectedWallItem } = useSelectedWallItem();
|
||||
const { setActiveTool } = useActiveTool();
|
||||
const { clearSelectedZone } = useSelectedZoneStore();
|
||||
const { showShortcuts, setShowShortcuts } = useShortcutStore();
|
||||
const { setIsVersionSaved } = useSaveVersion();
|
||||
const { activeModule, setActiveModule } = useModuleStore();
|
||||
const { setActiveSubTool } = useActiveSubTool();
|
||||
const { toggleUILeft, toggleUIRight, setToggleUI } = useToggleStore();
|
||||
const { setToggleThreeD } = useThreeDStore();
|
||||
const { setToolMode } = useToolMode();
|
||||
const { isPlaying, setIsPlaying } = usePlayButtonStore();
|
||||
const { toggleView, setToggleView } = useToggleView();
|
||||
const { setDeleteTool } = useDeleteTool();
|
||||
const { setAddAction } = useAddAction();
|
||||
const { setSelectedWallItem } = useSelectedWallItem();
|
||||
const { setActiveTool } = useActiveTool();
|
||||
const { clearSelectedZone } = useSelectedZoneStore();
|
||||
const { showShortcuts, setShowShortcuts } = useShortcutStore();
|
||||
const { setWalkMode } = useCameraModeStore();
|
||||
|
||||
const isTextInput = (element: Element | null): boolean =>
|
||||
element instanceof HTMLInputElement ||
|
||||
@@ -67,8 +67,8 @@ const KeyPressListener: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBuilderShortcuts = (key: string) => {
|
||||
if (activeModule !== "builder") return;
|
||||
const handleBuilderShortcuts = (key: string) => {
|
||||
if (activeModule !== "builder" || isPlaying) return;
|
||||
|
||||
if (key === "TAB") {
|
||||
const toggleTo2D = toggleView;
|
||||
@@ -172,14 +172,14 @@ const KeyPressListener: React.FC = () => {
|
||||
setIsPlaying(true);
|
||||
}
|
||||
|
||||
if (keyCombination === "ESCAPE") {
|
||||
setActiveTool("cursor");
|
||||
setActiveSubTool("cursor");
|
||||
setIsPlaying(false);
|
||||
clearSelectedZone();
|
||||
setShowShortcuts(false);
|
||||
setIsVersionSaved(false);
|
||||
}
|
||||
if (keyCombination === "ESCAPE") {
|
||||
setWalkMode(false);
|
||||
setActiveTool("cursor");
|
||||
setActiveSubTool("cursor");
|
||||
setIsPlaying(false);
|
||||
clearSelectedZone();
|
||||
setShowShortcuts(false);
|
||||
}
|
||||
|
||||
if (keyCombination === "Ctrl+Shift+?") {
|
||||
setShowShortcuts(!showShortcuts);
|
||||
@@ -195,11 +195,11 @@ const KeyPressListener: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", handleKeyPress);
|
||||
return () => window.removeEventListener("keydown", handleKeyPress);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeModule, toggleUIRight, toggleUILeft, toggleView, showShortcuts]);
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", handleKeyPress);
|
||||
return () => window.removeEventListener("keydown", handleKeyPress);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeModule, toggleUIRight, toggleUILeft, toggleView, showShortcuts, isPlaying]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user