feat: create portal component added to view child in new window
This commit is contained in:
@@ -710,6 +710,27 @@ export const ExpandIcon = ({ isActive }: { isActive: boolean }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ExpandIcon2 = () => {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M9.78365 2H6.6001C6.46676 2 6.33342 2.08334 6.33342 2.21668V2.71672C6.33342 2.85006 6.45009 3.00007 6.6001 3.00007H7.91686C8.06687 3.00007 8.15021 3.16675 8.03353 3.26675L5.20001 6.10028C5.1 6.20028 5.1 6.3503 5.20001 6.4503L5.55003 6.80033C5.65004 6.90033 5.80005 6.90033 5.90005 6.80033L8.73358 3.9668C8.83359 3.86679 9.00026 3.93346 9.00026 4.08347V5.40023C9.00026 5.53357 9.1336 5.68358 9.26695 5.68358H9.75031C9.88366 5.68358 10.0003 5.53357 10.0003 5.40023V2.23335C10.0003 2.08334 9.91699 2 9.78365 2Z"
|
||||||
|
fill="var(--text-color)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M7.71705 5.91777L7.15035 6.50115C7.05034 6.60115 7.00034 6.71783 7.00034 6.85117V8.7513C7.00034 8.88464 6.88366 9.00132 6.75032 9.00132H3.25008C3.11674 9.00132 3.00007 8.88464 3.00007 8.7513V5.25106C3.00007 5.11772 3.11674 5.00104 3.25008 5.00104H5.16688C5.30022 5.00104 5.43357 4.95104 5.5169 4.85103L6.08361 4.28433C6.18362 4.18432 6.11695 4.00098 5.96694 4.00098H2.66671C2.30002 4.00098 2 4.301 2 4.66769V9.33467C2 9.70136 2.30002 10.0014 2.66671 10.0014H7.33369C7.70039 10.0014 8.00041 9.70136 8.00041 9.33467V6.03445C8.00041 5.88444 7.81706 5.81777 7.71705 5.91777Z"
|
||||||
|
fill="var(--text-color)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const StartIcon = () => {
|
export const StartIcon = () => {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
182
app/src/components/templates/CreateNewWindow.tsx
Normal file
182
app/src/components/templates/CreateNewWindow.tsx
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
import React, { ReactNode, useEffect, useRef, useState } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
|
||||||
|
type NewWindowProps = {
|
||||||
|
children: ReactNode;
|
||||||
|
title?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
left?: number;
|
||||||
|
top?: number;
|
||||||
|
center?: boolean;
|
||||||
|
features?: Partial<{
|
||||||
|
toolbar: boolean;
|
||||||
|
menubar: boolean;
|
||||||
|
scrollbars: boolean;
|
||||||
|
resizable: boolean;
|
||||||
|
location: boolean;
|
||||||
|
status: boolean;
|
||||||
|
}>;
|
||||||
|
onClose?: () => void;
|
||||||
|
copyStyles?: boolean;
|
||||||
|
noopener?: boolean;
|
||||||
|
className?: string;
|
||||||
|
theme?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RenderInNewWindow: React.FC<NewWindowProps> = ({
|
||||||
|
children,
|
||||||
|
title = "New Window",
|
||||||
|
width = 900,
|
||||||
|
height = 700,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
center = true,
|
||||||
|
features,
|
||||||
|
onClose,
|
||||||
|
copyStyles = true,
|
||||||
|
noopener = true,
|
||||||
|
className,
|
||||||
|
theme = "light",
|
||||||
|
}) => {
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const childWindowRef = useRef<Window | null>(null);
|
||||||
|
const containerElRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
|
||||||
|
const screenLeft = window.screenLeft ?? window.screenX ?? 0;
|
||||||
|
const screenTop = window.screenTop ?? window.screenY ?? 0;
|
||||||
|
const availWidth = window.outerWidth ?? window.innerWidth;
|
||||||
|
const availHeight = window.outerHeight ?? window.innerHeight;
|
||||||
|
|
||||||
|
const finalLeft =
|
||||||
|
center && availWidth
|
||||||
|
? Math.max(0, screenLeft + (availWidth - width) / 2)
|
||||||
|
: left ?? 100;
|
||||||
|
|
||||||
|
const finalTop =
|
||||||
|
center && availHeight
|
||||||
|
? Math.max(0, screenTop + (availHeight - height) / 2)
|
||||||
|
: top ?? 100;
|
||||||
|
|
||||||
|
const baseFeatures = [
|
||||||
|
`width=${Math.floor(width)}`,
|
||||||
|
`height=${Math.floor(height)}`,
|
||||||
|
`left=${Math.floor(finalLeft)}`,
|
||||||
|
`top=${Math.floor(finalTop)}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
const featureFlags = features ?? {
|
||||||
|
toolbar: false,
|
||||||
|
menubar: false,
|
||||||
|
scrollbars: true,
|
||||||
|
resizable: true,
|
||||||
|
location: false,
|
||||||
|
status: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.entries(featureFlags).forEach(([k, v]) =>
|
||||||
|
baseFeatures.push(`${k}=${v ? "yes" : "no"}`)
|
||||||
|
);
|
||||||
|
|
||||||
|
const newWin = window.open("", "_blank", baseFeatures.join(","));
|
||||||
|
if (!newWin) {
|
||||||
|
console.warn("Popup blocked or failed to open window.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noopener) {
|
||||||
|
try {
|
||||||
|
newWin.opener = null;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
newWin.document.open();
|
||||||
|
newWin.document.write(`<!doctype html>
|
||||||
|
<html data-theme=${theme}>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>${title}</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
</head>
|
||||||
|
<body style="margin:0;"></body>
|
||||||
|
</html>`);
|
||||||
|
newWin.document.close();
|
||||||
|
|
||||||
|
if (copyStyles) {
|
||||||
|
const head = newWin.document.head;
|
||||||
|
Array.from(document.styleSheets).forEach((styleSheet) => {
|
||||||
|
try {
|
||||||
|
if ((styleSheet as CSSStyleSheet).cssRules) {
|
||||||
|
const newStyleEl = newWin.document.createElement("style");
|
||||||
|
const rules = Array.from(
|
||||||
|
(styleSheet as CSSStyleSheet).cssRules
|
||||||
|
).map((r) => r.cssText);
|
||||||
|
newStyleEl.appendChild(
|
||||||
|
newWin.document.createTextNode(rules.join("\n"))
|
||||||
|
);
|
||||||
|
head.appendChild(newStyleEl);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
const ownerNode = styleSheet.ownerNode as HTMLElement | null;
|
||||||
|
if (ownerNode && ownerNode.tagName === "LINK") {
|
||||||
|
const link = ownerNode as HTMLLinkElement;
|
||||||
|
const newLink = newWin.document.createElement("link");
|
||||||
|
newLink.rel = link.rel;
|
||||||
|
newLink.href = link.href;
|
||||||
|
newLink.media = link.media;
|
||||||
|
head.appendChild(newLink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = newWin.document.createElement("div");
|
||||||
|
if (className) container.className = className;
|
||||||
|
newWin.document.body.appendChild(container);
|
||||||
|
|
||||||
|
newWin.document.title = title;
|
||||||
|
|
||||||
|
// Handle child window close
|
||||||
|
const handleChildUnload = () => {
|
||||||
|
onClose?.();
|
||||||
|
};
|
||||||
|
newWin.addEventListener("beforeunload", handleChildUnload);
|
||||||
|
|
||||||
|
// 👇 Handle parent refresh/close → auto close child
|
||||||
|
const handleParentUnload = () => {
|
||||||
|
try {
|
||||||
|
newWin.close();
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
window.addEventListener("beforeunload", handleParentUnload);
|
||||||
|
|
||||||
|
childWindowRef.current = newWin;
|
||||||
|
containerElRef.current = container;
|
||||||
|
setMounted(true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
newWin.removeEventListener("beforeunload", handleChildUnload);
|
||||||
|
window.removeEventListener("beforeunload", handleParentUnload);
|
||||||
|
try {
|
||||||
|
newWin.close();
|
||||||
|
} catch {}
|
||||||
|
childWindowRef.current = null;
|
||||||
|
containerElRef.current = null;
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const w = childWindowRef.current;
|
||||||
|
if (w && !w.closed) {
|
||||||
|
w.document.title = title;
|
||||||
|
}
|
||||||
|
}, [title]);
|
||||||
|
|
||||||
|
if (!mounted || !containerElRef.current) return null;
|
||||||
|
|
||||||
|
return createPortal(children, containerElRef.current);
|
||||||
|
};
|
||||||
@@ -1,9 +1,80 @@
|
|||||||
// LogList.tsx
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { LogListIcon, CloseIcon } from "../../icons/ExportCommonIcons"; // Adjust path as needed
|
import {
|
||||||
import { useLogger } from "./LoggerContext";
|
LogListIcon,
|
||||||
|
CloseIcon,
|
||||||
|
ExpandIcon2,
|
||||||
|
} from "../../icons/ExportCommonIcons"; // Adjust path as needed
|
||||||
|
import { LogEntry, useLogger } from "./LoggerContext";
|
||||||
import { GetLogIcon } from "../../footer/getLogIcons";
|
import { GetLogIcon } from "../../footer/getLogIcons";
|
||||||
|
import { RenderInNewWindow } from "../../templates/CreateNewWindow";
|
||||||
|
|
||||||
|
// --- Logs Component ---
|
||||||
|
type LogsProps = {
|
||||||
|
selectedTab: "all" | "info" | "warning" | "error";
|
||||||
|
setSelectedTab: (tab: "all" | "info" | "warning" | "error") => void;
|
||||||
|
clear: () => void;
|
||||||
|
filteredLogs: LogEntry[];
|
||||||
|
formatTimestamp: (date: Date) => string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Logs: React.FC<LogsProps> = ({
|
||||||
|
selectedTab,
|
||||||
|
setSelectedTab,
|
||||||
|
clear,
|
||||||
|
filteredLogs,
|
||||||
|
formatTimestamp,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="log-nav-container">
|
||||||
|
<div className="log-nav-wrapper">
|
||||||
|
{["all", "info", "warning", "error"].map((type) => (
|
||||||
|
<button
|
||||||
|
id="log-type"
|
||||||
|
title="log-type"
|
||||||
|
key={type}
|
||||||
|
className={`log-nav ${selectedTab === type ? "active" : ""}`}
|
||||||
|
onClick={() => setSelectedTab(type as any)}
|
||||||
|
>
|
||||||
|
{`${type.charAt(0).toUpperCase() + type.slice(1)} Logs`}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="clean-btn"
|
||||||
|
title="clear-btn"
|
||||||
|
className="clear-button"
|
||||||
|
onClick={clear}
|
||||||
|
>
|
||||||
|
clear
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Log Entries */}
|
||||||
|
<div className="log-entry-wrapper">
|
||||||
|
{filteredLogs.length > 0 ? (
|
||||||
|
filteredLogs.map((log) => (
|
||||||
|
<div key={log.id} className={`log-entry ${log.type}`}>
|
||||||
|
<div className="log-icon">{GetLogIcon(log.type)}</div>
|
||||||
|
<div className="log-entry-message-container">
|
||||||
|
<div className="log-entry-message">{log.message}</div>
|
||||||
|
<div className="message-time">
|
||||||
|
{formatTimestamp(log.timestamp)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="no-log">
|
||||||
|
There are no logs to display at the moment.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- LogList Component ---
|
||||||
const LogList: React.FC = () => {
|
const LogList: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
logs,
|
logs,
|
||||||
@@ -15,6 +86,7 @@ const LogList: React.FC = () => {
|
|||||||
} = useLogger();
|
} = useLogger();
|
||||||
|
|
||||||
const formatTimestamp = (date: Date) => new Date(date).toLocaleTimeString();
|
const formatTimestamp = (date: Date) => new Date(date).toLocaleTimeString();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const filteredLogs =
|
const filteredLogs =
|
||||||
selectedTab === "all"
|
selectedTab === "all"
|
||||||
@@ -25,92 +97,86 @@ const LogList: React.FC = () => {
|
|||||||
if (isLogListVisible && logs.length > 0) {
|
if (isLogListVisible && logs.length > 0) {
|
||||||
const lastLog = logs[logs.length - 1];
|
const lastLog = logs[logs.length - 1];
|
||||||
const validTypes = ["all", "info", "warning", "error"];
|
const validTypes = ["all", "info", "warning", "error"];
|
||||||
|
|
||||||
if (validTypes.includes(lastLog.type)) {
|
if (validTypes.includes(lastLog.type)) {
|
||||||
setSelectedTab(lastLog.type);
|
setSelectedTab(lastLog.type as any);
|
||||||
} else {
|
} else {
|
||||||
setSelectedTab("all");
|
setSelectedTab("all");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line
|
||||||
}, [isLogListVisible]);
|
}, [isLogListVisible]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line
|
<>
|
||||||
<div
|
{!open ? (
|
||||||
className="log-list-container"
|
<div
|
||||||
onClick={() => setIsLogListVisible(false)}
|
className="log-list-container"
|
||||||
>
|
onClick={() => setIsLogListVisible(false)}
|
||||||
{/* eslint-disable-next-line */}
|
>
|
||||||
<div
|
{/* eslint-disable-next-line */}
|
||||||
className="log-list-wrapper"
|
<div
|
||||||
onClick={(e) => {
|
className="log-list-wrapper"
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
}}
|
e.stopPropagation();
|
||||||
>
|
}}
|
||||||
<div className="log-header">
|
|
||||||
<div className="log-header-wrapper">
|
|
||||||
<div className="icon">
|
|
||||||
<LogListIcon />
|
|
||||||
</div>
|
|
||||||
<div className="head">Log List</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
id="close-btn"
|
|
||||||
title="close-btn"
|
|
||||||
className="close"
|
|
||||||
onClick={() => setIsLogListVisible(false)}
|
|
||||||
>
|
>
|
||||||
<CloseIcon />
|
<div className="log-header">
|
||||||
</button>
|
<div className="log-header-wrapper">
|
||||||
</div>
|
<div className="icon">
|
||||||
|
<LogListIcon />
|
||||||
{/* Tabs */}
|
|
||||||
<div className="log-nav-container">
|
|
||||||
<div className="log-nav-wrapper">
|
|
||||||
{["all", "info", "warning", "error"].map((type) => (
|
|
||||||
<button
|
|
||||||
id="log-type"
|
|
||||||
title="log-type"
|
|
||||||
key={type}
|
|
||||||
className={`log-nav ${selectedTab === type ? "active" : ""}`}
|
|
||||||
onClick={() => setSelectedTab(type as any)}
|
|
||||||
>
|
|
||||||
{`${type.charAt(0).toUpperCase() + type.slice(1)} Logs`}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
id="clean-btn"
|
|
||||||
title="clear-btn"
|
|
||||||
className="clear-button"
|
|
||||||
onClick={clear}
|
|
||||||
>
|
|
||||||
clear
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Log Entries */}
|
|
||||||
<div className="log-entry-wrapper">
|
|
||||||
{filteredLogs.length > 0 ? (
|
|
||||||
filteredLogs.map((log) => (
|
|
||||||
<div key={log.id} className={`log-entry ${log.type}`}>
|
|
||||||
<div className="log-icon">{GetLogIcon(log.type)}</div>
|
|
||||||
<div className="log-entry-message-container">
|
|
||||||
<div className="log-entry-message">{log.message}</div>
|
|
||||||
<div className="message-time">
|
|
||||||
{formatTimestamp(log.timestamp)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="head">Log List</div>
|
||||||
|
</div>
|
||||||
|
<div className="action-buttons-container">
|
||||||
|
<button
|
||||||
|
id="expand-log-btn"
|
||||||
|
title="open in new tab"
|
||||||
|
className="expand-btn"
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ExpandIcon2 />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="close-btn"
|
||||||
|
title="close"
|
||||||
|
className="close"
|
||||||
|
onClick={() => setIsLogListVisible(false)}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="no-log">
|
|
||||||
There are no logs to display at the moment.
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
{/* Logs Section */}
|
||||||
|
<Logs
|
||||||
|
selectedTab={selectedTab as any}
|
||||||
|
setSelectedTab={setSelectedTab as any}
|
||||||
|
clear={clear}
|
||||||
|
filteredLogs={filteredLogs}
|
||||||
|
formatTimestamp={formatTimestamp}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
</div>
|
<RenderInNewWindow
|
||||||
|
title="Log list"
|
||||||
|
theme={localStorage.getItem("theme")}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
<div className="log-list-new-window-wrapper">
|
||||||
|
<Logs
|
||||||
|
selectedTab={selectedTab as any}
|
||||||
|
setSelectedTab={setSelectedTab as any}
|
||||||
|
clear={clear}
|
||||||
|
filteredLogs={filteredLogs}
|
||||||
|
formatTimestamp={formatTimestamp}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</RenderInNewWindow>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,86 @@
|
|||||||
@use "../../abstracts/variables.scss" as *;
|
@use "../../abstracts/variables.scss" as *;
|
||||||
@use "../../abstracts/mixins.scss" as *;
|
@use "../../abstracts/mixins.scss" as *;
|
||||||
|
|
||||||
|
.log-nav-container {
|
||||||
|
@include flex-space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
.log-nav-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.log-nav {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: #{$border-radius-extra-large};
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-nav.active {
|
||||||
|
background-color: var(--background-color-accent);
|
||||||
|
color: var(--text-button-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.clear-button {
|
||||||
|
margin: 0 6px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
color: var(--text-disabled);
|
||||||
|
border-radius: #{$border-radius-large};
|
||||||
|
&:hover {
|
||||||
|
font-weight: 300;
|
||||||
|
color: var(--text-color);
|
||||||
|
background: var(--background-color-solid-gradient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-entry-wrapper {
|
||||||
|
height: calc(100% - 80px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
background: var(--background-color);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: #{$border-radius-xlarge};
|
||||||
|
outline: 1px solid var(--border-color);
|
||||||
|
outline-offset: -1px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.log-entry {
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: #{$border-radius-small};
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.log-icon {
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
@include flex-center;
|
||||||
|
}
|
||||||
|
.log-entry-message-container {
|
||||||
|
@include flex-space-between;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
.message-time {
|
||||||
|
font-size: var(--font-size-tiny);
|
||||||
|
font-weight: 300;
|
||||||
|
opacity: 0.8;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
// height: 100%;
|
||||||
|
}
|
||||||
|
.log-entry-message {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
background: var(--background-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.no-log {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.log-list-container {
|
.log-list-container {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
@@ -33,99 +113,35 @@
|
|||||||
scale: 0.8;
|
scale: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.action-buttons-container {
|
||||||
.close {
|
|
||||||
@include flex-center;
|
|
||||||
height: 28px;
|
|
||||||
width: 28px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: #{$border-radius-medium};
|
|
||||||
svg {
|
|
||||||
scale: 1.6;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
background: var(--background-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.log-nav-container {
|
|
||||||
@include flex-space-between;
|
|
||||||
align-items: flex-end;
|
|
||||||
.log-nav-wrapper {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
.log-nav {
|
.close,
|
||||||
padding: 8px 16px;
|
.expand-btn {
|
||||||
border-radius: #{$border-radius-extra-large};
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-nav.active {
|
|
||||||
background-color: var(--background-color-accent);
|
|
||||||
color: var(--text-button-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.clear-button{
|
|
||||||
margin: 0 6px;
|
|
||||||
padding: 4px 12px;
|
|
||||||
color: var(--text-disabled);
|
|
||||||
border-radius: #{$border-radius-large};
|
|
||||||
&:hover{
|
|
||||||
font-weight: 300;
|
|
||||||
color: var(--text-color);
|
|
||||||
background: var(--background-color-solid-gradient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-entry-wrapper {
|
|
||||||
height: calc(100% - 80px);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
background: var(--background-color);
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: #{$border-radius-xlarge};
|
|
||||||
outline: 1px solid var(--border-color);
|
|
||||||
outline-offset: -1px;
|
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
.log-entry {
|
|
||||||
padding: 4px;
|
|
||||||
border-radius: #{$border-radius-small};
|
|
||||||
font-size: var(--font-size-small);
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.log-icon {
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
}
|
height: 28px;
|
||||||
.log-entry-message-container {
|
width: 28px;
|
||||||
@include flex-space-between;
|
cursor: pointer;
|
||||||
gap: 12px;
|
border-radius: #{$border-radius-medium};
|
||||||
width: 100%;
|
svg {
|
||||||
.message-time {
|
scale: 1.6;
|
||||||
font-size: var(--font-size-tiny);
|
|
||||||
font-weight: 300;
|
|
||||||
opacity: 0.8;
|
|
||||||
text-wrap: nowrap;
|
|
||||||
// height: 100%;
|
|
||||||
}
|
}
|
||||||
.log-entry-message {
|
&:hover {
|
||||||
width: 100%;
|
background: var(--background-color);
|
||||||
|
border: 1px solid #ffffff29;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(odd) {
|
|
||||||
background: var(--background-color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.no-log{
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log-list-new-window-wrapper{
|
||||||
|
padding: 10px;
|
||||||
|
background: var(--background-color-solid);
|
||||||
|
height: 100vh;
|
||||||
|
.log-nav-container{
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user