This commit is contained in:
Vishnu 2025-05-02 18:17:03 +05:30
commit 0f41f3f845
13 changed files with 616 additions and 11 deletions

View File

@ -3,23 +3,20 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Dashboard from "./pages/Dashboard";
import Project from "./pages/Project";
import UserAuth from "./pages/UserAuth";
import ToastProvider from "./components/templates/ToastProvider";
import "./styles/main.scss"
import "./styles/main.scss";
import { LoggerProvider } from "./components/ui/log/LoggerContext";
const App: React.FC = () => {
return (
<ToastProvider>
<LoggerProvider>
<Router>
<Routes>
<Route
path="/"
element={<UserAuth />}
/>
<Route path="/" element={<UserAuth />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/project" element={<Project />} />
</Routes>
</Router>
</ToastProvider>
</LoggerProvider>
);
};

View File

@ -814,3 +814,136 @@ export const SpeedIcon = () => {
</svg>
);
};
export const LogListIcon = () => {
return (
<svg
width="24"
height="25"
viewBox="0 0 24 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.5 18.25H5.5M18.5 21.7272V19.3568H17.3829C16.8245 19.3568 16.375 18.9072 16.375 18.3489V18.08C16.375 17.5216 16.8246 17.0721 17.3829 17.0721H18.5V13.7318H17.2579C16.6995 13.7318 16.25 13.2822 16.25 12.7239V12.4551C16.25 11.8967 16.6996 11.4472 17.2579 11.4472H18.5V7.98185H17.2579C16.6995 7.98185 16.25 7.5323 16.25 6.97395V6.70515C16.25 6.14675 16.6996 5.69725 17.2579 5.69725H18.5V3.27065M14.5 12.75H5.5M12 7.24995H5.5M20.25 3.25H3.75C3.1977 3.25 2.75 3.6977 2.75 4.25V20.75C2.75 21.3023 3.1977 21.75 3.75 21.75H20.25C20.8023 21.75 21.25 21.3023 21.25 20.75V4.25C21.25 3.6977 20.8023 3.25 20.25 3.25Z"
stroke="#2B3344"
strokeWidth="1.2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export const LogTickIcon = () => {
return (
<svg
width="10"
height="10"
viewBox="0 0 10 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_3962_4223)">
<path
d="M5.00065 0.835938C2.70482 0.835938 0.833984 2.70677 0.833984 5.0026C0.833984 7.29844 2.70482 9.16927 5.00065 9.16927C7.29648 9.16927 9.16732 7.29844 9.16732 5.0026C9.16732 2.70677 7.29648 0.835938 5.00065 0.835938ZM6.99232 4.04427L4.62982 6.40677C4.57148 6.4651 4.49232 6.49844 4.40898 6.49844C4.32565 6.49844 4.24648 6.4651 4.18815 6.40677L3.00898 5.2276C2.88815 5.10677 2.88815 4.90677 3.00898 4.78594C3.12982 4.6651 3.32982 4.6651 3.45065 4.78594L4.40898 5.74427L6.55065 3.6026C6.67148 3.48177 6.87148 3.48177 6.99232 3.6026C7.11315 3.72344 7.11315 3.91927 6.99232 4.04427Z"
fill="#49B841"
/>
</g>
<defs>
<clipPath id="clip0_3962_4223">
<rect width="10" height="10" fill="white" />
</clipPath>
</defs>
</svg>
);
};
export const LogInfoIcon = () => {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.75 2.64438V7.41438C0.75 7.92094 0.951232 8.40675 1.30943 8.76495C1.66762 9.12314 2.15344 9.32438 2.66 9.32438H3.615V10.7544L6.5 9.32438H9.365C9.87156 9.32438 10.3574 9.12314 10.7156 8.76495C11.0738 8.40675 11.275 7.92094 11.275 7.41438V2.64438C11.275 2.13781 11.0738 1.652 10.7156 1.2938C10.3574 0.935607 9.87156 0.734375 9.365 0.734375H2.66C2.15344 0.734375 1.66762 0.935607 1.30943 1.2938C0.951232 1.652 0.75 2.13781 0.75 2.64438Z"
fill="#8E8E93"
/>
<path
d="M5.04492 6.94531H6.95492"
stroke="white"
strokeWidth="0.955"
strokeMiterlimit="10"
/>
<path
d="M5.04492 4.07812H5.99992V6.94313"
stroke="white"
strokeWidth="0.955"
strokeMiterlimit="10"
/>
<path
d="M5.52539 2.65625H6.47539"
stroke="white"
strokeWidth="0.955"
strokeMiterlimit="10"
/>
</svg>
);
};
export const WarningIcon = () => {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0.75 2.64438V7.41438C0.75 7.92094 0.951232 8.40675 1.30943 8.76495C1.66762 9.12314 2.15344 9.32438 2.66 9.32438H3.615V10.7544L6.5 9.32438H9.365C9.87156 9.32438 10.3574 9.12314 10.7156 8.76495C11.0738 8.40675 11.275 7.92094 11.275 7.41438V2.64438C11.275 2.13781 11.0738 1.652 10.7156 1.2938C10.3574 0.935607 9.87156 0.734375 9.365 0.734375H2.66C2.15344 0.734375 1.66762 0.935607 1.30943 1.2938C0.951232 1.652 0.75 2.13781 0.75 2.64438Z"
fill="#8E8E93"
/>
<path
d="M5.04492 6.94531H6.95492"
stroke="white"
strokeWidth="0.955"
strokeMiterlimit="10"
/>
<path
d="M5.04492 4.07812H5.99992V6.94313"
stroke="white"
strokeWidth="0.955"
strokeMiterlimit="10"
/>
<path
d="M5.52539 2.65625H6.47539"
stroke="white"
strokeWidth="0.955"
strokeMiterlimit="10"
/>
</svg>
);
};
export const ErrorIcon = () => {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M11 6C11 8.7614 8.7614 11 6 11C3.23857 11 1 8.7614 1 6C1 3.23857 3.23857 1 6 1C8.7614 1 11 3.23857 11 6ZM4.48482 4.48483C4.63126 4.33838 4.8687 4.33838 5.01515 4.48483L6 5.46965L6.9848 4.48484C7.13125 4.33839 7.3687 4.33839 7.51515 4.48484C7.6616 4.63128 7.6616 4.86872 7.51515 5.01515L6.5303 6L7.51515 6.9848C7.6616 7.13125 7.6616 7.3687 7.51515 7.51515C7.3687 7.6616 7.13125 7.6616 6.9848 7.51515L6 6.53035L5.01515 7.51515C4.86871 7.6616 4.63127 7.6616 4.48483 7.51515C4.33838 7.3687 4.33838 7.13125 4.48483 6.98485L5.46965 6L4.48482 5.01515C4.33837 4.86871 4.33837 4.63127 4.48482 4.48483Z"
fill="#ED5342"
/>
</svg>
);
};

View File

@ -0,0 +1,67 @@
import React from "react";
import { HelpIcon } from "../../icons/DashboardIcon";
import { useLogger } from "../log/LoggerContext";
import {
LogInfoIcon,
ErrorIcon,
WarningIcon,
} from "../../icons/ExportCommonIcons"; // Adjust path as needed
const getLogIcon = (type: string) => {
switch (type) {
case "info":
return <LogInfoIcon />;
case "error":
return <ErrorIcon />;
case "warning":
return <WarningIcon />;
case "log":
default:
return <LogInfoIcon />;
}
};
const Footer = () => {
const { logs, setIsLogListVisible } = useLogger();
const lastLog = logs.length > 0 ? logs[logs.length - 1] : null;
return (
<div className="footer-wrapper">
<div className="selection-wrapper">
<div className="selector-wrapper">
<div className="icon"></div>
<div className="selector">Selection</div>
</div>
<div className="selector-wrapper">
<div className="icon"></div>
<div className="selector">Rotate/Zoom</div>
</div>
<div className="selector-wrapper">
<div className="icon"></div>
<div className="selector">Pan/Context Menu</div>
</div>
</div>
<div className="logs-wrapper">
<div className="logs-detail" onClick={() => setIsLogListVisible(true)}>
{lastLog ? (
<>
<span className="log-icon">{getLogIcon(lastLog.type)}</span>
<span className="log-message">{lastLog.message}</span>
</>
) : (
"No logs yet."
)}
</div>
<div className="version">
V 0.01
<div className="icon">
<HelpIcon />
</div>
</div>
</div>
</div>
);
};
export default Footer;

View File

@ -0,0 +1,88 @@
// LogList.tsx
import React, { useState } from "react";
import {
LogListIcon,
TickIcon,
LogInfoIcon,
WarningIcon,
ErrorIcon,
CloseIcon,
} from "../../icons/ExportCommonIcons"; // Adjust path as needed
import { useLogger } from "./LoggerContext";
const LogList: React.FC = () => {
const { logs, clear, setIsLogListVisible } = useLogger();
const [selectedTab, setSelectedTab] = useState<
"all" | "info" | "warning" | "error" | "log"
>("all");
const getLogIcon = (type: string) => {
switch (type) {
case "info":
return <LogInfoIcon />;
case "error":
return <ErrorIcon />;
case "warning":
return <WarningIcon />;
case "log":
default:
return <LogInfoIcon />;
}
};
const formatTimestamp = (date: Date) => new Date(date).toLocaleTimeString();
const filteredLogs =
selectedTab === "all"
? [...logs].reverse()
: [...logs].filter((log) => log.type === selectedTab).reverse();
return (
<div className="log-list-container">
<div className="log-list-wrapper">
<div className="log-header">
<div className="log-header-wrapper">
<div className="icon">
<LogListIcon />
</div>
<div className="head">Log List</div>
</div>
<div className="close" onClick={() => setIsLogListVisible(false)}>
{/* <CloseIcon /> */}X
</div>
</div>
{/* Tabs */}
<div className="log-nav-wrapper">
{["all", "info", "warning", "error"].map((type) => (
<div
key={type}
className={`log-nav ${selectedTab === type ? "active" : ""}`}
onClick={() => setSelectedTab(type as any)}
>
{`${type.charAt(0).toUpperCase() + type.slice(1)} Logs`}
</div>
))}
</div>
{/* Log Entries */}
<div className="log-entry-wrapper">
{filteredLogs.map((log) => (
<div key={log.id} className={`log-entry ${log.type}`}>
<div className="tick">
<TickIcon />
</div>
<div className="log-icon">{getLogIcon(log.type)}</div>
<div className="log-entry-message">
[{formatTimestamp(log.timestamp)}] [{log.type.toUpperCase()}]{" "}
{log.message}
</div>
</div>
))}
</div>
</div>
</div>
);
};
export default LogList;

View File

@ -0,0 +1,77 @@
import React, { createContext, useContext, useState, useCallback } from "react";
export type LogType = "log" | "info" | "warning" | "error";
export interface LogEntry {
id: string;
type: LogType;
message: string;
timestamp: Date;
}
interface LoggerContextValue {
logs: LogEntry[];
setLogs: React.Dispatch<React.SetStateAction<LogEntry[]>>;
isLogListVisible: boolean;
setIsLogListVisible: React.Dispatch<React.SetStateAction<boolean>>;
log: (message: string) => void;
info: (message: string) => void;
warn: (message: string) => void;
error: (message: string) => void;
clear: () => void;
}
const LoggerContext = createContext<LoggerContextValue | undefined>(undefined);
export const LoggerProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [logs, setLogs] = useState<LogEntry[]>([]);
const [isLogListVisible, setIsLogListVisible] = useState<boolean>(false);
const generateId = useCallback(
() => Math.random().toString(36).substring(2, 9),
[]
);
const addLog = useCallback(
(type: LogType, message: string) => {
const newLog: LogEntry = {
id: generateId(),
type,
message,
timestamp: new Date(),
};
setLogs((prevLogs) => [...prevLogs, newLog]);
},
[generateId]
);
const loggerMethods: LoggerContextValue = {
logs,
setLogs,
isLogListVisible,
setIsLogListVisible,
log: (message: string) => addLog("log", message),
info: (message: string) => addLog("info", message),
warn: (message: string) => addLog("warning", message),
error: (message: string) => addLog("error", message),
clear: () => {
setLogs([]);
},
};
return (
<LoggerContext.Provider value={loggerMethods}>
{children}
</LoggerContext.Provider>
);
};
export const useLogger = () => {
const context = useContext(LoggerContext);
if (!context) {
throw new Error("useLogger must be used within a LoggerProvider");
}
return context;
};

View File

@ -0,0 +1,71 @@
type LogType = 'log' | 'info' | 'warning' | 'error';
interface LogEntry {
type: LogType;
message: string;
timestamp: Date;
context?: string;
}
class Logger {
private static instance: Logger;
private logs: LogEntry[] = [];
private subscribers: Array<(log: LogEntry) => void> = [];
private constructor() {}
public static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
private notifySubscribers(log: LogEntry) {
this.subscribers.forEach(callback => callback(log));
}
private addLog(type: LogType, message: string, context?: string) {
const log: LogEntry = { type, message, timestamp: new Date(), context };
this.logs.push(log);
this.notifySubscribers(log);
if (process.env.NODE_ENV === 'development') {
const logMessage = context ? `[${context}] ${message}` : message;
console[type === 'warning' ? 'warn' : type](logMessage);
}
}
public log(message: string, context?: string) {
this.addLog('log', message, context);
}
public info(message: string, context?: string) {
this.addLog('info', message, context);
}
public warning(message: string, context?: string) {
this.addLog('warning', message, context);
}
public error(message: string, context?: string) {
this.addLog('error', message, context);
}
public getLogs(): LogEntry[] {
return [...this.logs];
}
public clear() {
this.logs = [];
}
public subscribe(callback: (log: LogEntry) => void) {
this.subscribers.push(callback);
return () => {
this.subscribers = this.subscribers.filter(sub => sub !== callback);
};
}
}
export const logger = Logger.getInstance();

View File

@ -27,9 +27,15 @@ import Scene from "../modules/scene/scene";
import { createHandleDrop } from "../modules/visualization/functions/handleUiDrop";
import { useSelectedZoneStore } from "../store/visualization/useZoneStore";
import { useFloatingWidget } from "../store/visualization/useDroppedObjectsStore";
import { useLogger } from "../components/ui/log/LoggerContext";
import Footer from "../components/ui/footer/Footer";
import RenderOverlay from "../components/templates/Overlay";
import LogList from "../components/ui/log/LogList";
const Project: React.FC = () => {
let navigate = useNavigate();
const echo = useLogger();
const { activeModule, setActiveModule } = useModuleStore();
const { loadingProgress } = useLoadingProgress();
const { setUserName } = useUserName();
@ -52,6 +58,7 @@ const Project: React.FC = () => {
setOrganization(Organization);
setUserName(name);
}
echo.info("Log in success full");
} else {
navigate("/");
}
@ -66,6 +73,7 @@ const Project: React.FC = () => {
// collaboration store
const { selectedUser } = useSelectedUserStore();
const { isLogListVisible } = useLogger();
// real-time visualization store
const { widgetSubOption } = useWidgetSubOption();
@ -117,6 +125,12 @@ const Project: React.FC = () => {
<Scene />
</div>
{selectedUser && <FollowPerson />}
{isLogListVisible && (
<RenderOverlay>
<LogList />
</RenderOverlay>
)}
<Footer />
</div>
);
};

View File

@ -0,0 +1,57 @@
.footer-wrapper {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
z-index: 1;
display: flex;
justify-content: space-between;
padding: 12px 24px;
.selection-wrapper {
display: flex;
gap: 6px;
.selector-wrapper {
display: flex;
gap: 6px;
align-items: center;
background: var(--background-color);
padding: 3px 6px;
border-radius: 12px;
color: var(--text-color);
.selector {
color: var(--text-color);
}
}
}
.logs-wrapper {
display: flex;
gap: 6px;
.logs-detail,
.version {
border-radius: 12px;
background: var(--background-color);
padding: 3px 6px;
color: var(--text-color);
display: flex;
align-items: center;
gap: 6px;
}
.logs-detail {
// background-color: #fff;
cursor: pointer;
}
.version {
display: flex;
gap: 6px;
}
}
}

View File

@ -0,0 +1,77 @@
.log-list-container {
width: 100vw;
height: 100vh;
// background: var(--background-color-secondary);
// backdrop-filter: blur(2px);
.log-list-wrapper {
height: 50%;
min-width: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 5;
background: var(--background-color);
padding: 14px 12px;
border-radius: 15px;
display: flex;
flex-direction: column;
gap: 12px;
backdrop-filter: blur(50px);
.log-header {
display: flex;
justify-content: space-between;
.log-header-wrapper {
display: flex;
align-items: center;
gap: 6px;
}
.close {
// transform: scale(1.5);
cursor: pointer;
}
}
.log-nav-wrapper {
display: flex;
gap: 6px;
.log-nav {
padding: 8px 16px;
border-radius: 19px;
}
.log-nav.active {
background-color: var(--accent-color);
color: var(--text-button-color);
}
}
.log-entry-wrapper {
height: 100%;
display: flex;
flex-direction: column;
gap: 4px;
background: var(--background-color);
padding: 18px 10px;
border-radius: 16px;
.log-entry {
padding: 4px;
border-radius: 4px;
font-size: var(--font-size-small);
display: flex;
align-items: center;
gap: 6px;
&:nth-child(odd) {
background: var(--background-color);
}
}
}
}
}

View File

@ -314,6 +314,22 @@
font-size: var(--font-size-tiny);
}
.timmer {
width: auto;
position: absolute;
bottom: 0;
font-size: var(--font-size-tiny);
}
.start-displayer {
left: 8px;
}
.end-displayer {
width: auto;
right: 8px;
}
.start-displayer {
bottom: 4px;
left: 16px;
@ -354,6 +370,12 @@
}
.simulation-player-container.open {
.start-displayer,
.end-displayer {
display: none;
}
.timmer {
display: none;
}

View File

@ -416,7 +416,7 @@
outline: none;
path {
stroke: var(--text-button-color);
stroke-width: 1.3;
strokeWidth: 1.3;
}
}
}
@ -682,7 +682,7 @@
path {
stroke: var(--accent-color);
stroke-width: 1.5px;
strokeWidth: 1.5px;
}
&:hover {

View File

@ -27,6 +27,8 @@
@use 'components/confirmationPopUp';
@use 'components/analysis/analysis';
@use 'components/analysis/ROISummary.scss';
@use 'components/logs/logs';
@use 'components/footer/footer.scss';
// layout
@use 'layout/loading';

View File

@ -423,7 +423,7 @@
path {
stroke: #f65648;
stroke-width: 1.3;
strokeWidth: 1.3;
}
}
}