Refine BIM glass theming and panel scroll layout

This commit is contained in:
CODEX 2026-06-20 21:55:23 +03:00
parent 3584359833
commit ad3a4f3877
5 changed files with 307 additions and 113 deletions

View File

@ -68,11 +68,11 @@
--comment-target-passive: #f5f5fa; --comment-target-passive: #f5f5fa;
--comment-target-passive-alpha: 0.58; --comment-target-passive-alpha: 0.58;
--comment-target-passive-size: 22px; --comment-target-passive-size: 22px;
--nodedc-header-dropdown-bg: rgba(11, 11, 14, 0.94); --nodedc-header-dropdown-bg: var(--nodedc-glass-panel-bg-strong);
--nodedc-header-dropdown-item-bg: rgba(42, 42, 46, 0.74); --nodedc-header-dropdown-item-bg: var(--nodedc-glass-control-bg);
--nodedc-header-dropdown-item-hover-bg: rgba(42, 42, 46, 0.86); --nodedc-header-dropdown-item-hover-bg: var(--nodedc-glass-control-hover);
--nodedc-header-dropdown-item-active-bg: rgba(47, 47, 52, 0.96); --nodedc-header-dropdown-item-active-bg: var(--toolbar-bg-active);
--nodedc-header-dropdown-item-active-color: #ffffff; --nodedc-header-dropdown-item-active-color: var(--toolbar-bg-active-contrast);
--nodedc-glass-panel-bg: rgba(28, 28, 30, 0.72); --nodedc-glass-panel-bg: rgba(28, 28, 30, 0.72);
--nodedc-glass-panel-bg-strong: rgba(28, 28, 30, 0.82); --nodedc-glass-panel-bg-strong: rgba(28, 28, 30, 0.82);
--nodedc-glass-panel-bg-soft: rgba(28, 28, 30, 0.56); --nodedc-glass-panel-bg-soft: rgba(28, 28, 30, 0.56);
@ -398,17 +398,17 @@ header {
bottom: 12px; bottom: 12px;
width: 320px; width: 320px;
max-height: calc(100vh - 90px); max-height: calc(100vh - 90px);
overflow: auto; overflow: hidden;
background: var(--nodedc-glass-panel-surface); background: var(--nodedc-glass-panel-surface);
border: 1px solid var(--nodedc-glass-outline); border: 1px solid var(--nodedc-glass-outline);
border-radius: var(--modal-radius, 16px); border-radius: var(--modal-radius, 16px);
padding: 12px; padding: 0;
box-shadow: var(--nodedc-glass-panel-shadow); box-shadow: var(--nodedc-glass-panel-shadow);
backdrop-filter: var(--nodedc-glass-filter); backdrop-filter: var(--nodedc-glass-filter);
-webkit-backdrop-filter: var(--nodedc-glass-filter); -webkit-backdrop-filter: var(--nodedc-glass-filter);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 0;
z-index: 11; z-index: 11;
display: none; display: none;
} }
@ -418,8 +418,7 @@ header {
} }
.inspector-panel-header { .inspector-panel-header {
position: sticky; position: relative;
top: -12px;
z-index: 5; z-index: 5;
flex: 0 0 auto; flex: 0 0 auto;
display: grid; display: grid;
@ -428,9 +427,9 @@ header {
gap: 10px; gap: 10px;
height: calc(var(--env-control-h) + 20px); height: calc(var(--env-control-h) + 20px);
min-height: var(--env-control-h); min-height: var(--env-control-h);
margin: -12px -12px 0; margin: 0;
padding: 12px 12px 8px 16px; padding: 12px 12px 8px 16px;
background: var(--nodedc-glass-panel-surface-strong); background: transparent;
border-radius: var(--modal-radius, 16px) var(--modal-radius, 16px) 0 0; border-radius: var(--modal-radius, 16px) var(--modal-radius, 16px) 0 0;
color: var(--text); color: var(--text);
font-weight: 700; font-weight: 700;
@ -443,6 +442,16 @@ header {
line-height: 1.2; line-height: 1.2;
} }
.inspector-panel-body {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
padding: 0 12px 12px;
display: flex;
flex-direction: column;
gap: 10px;
}
.inspector-collapse-btn { .inspector-collapse-btn {
display: grid; display: grid;
place-items: center; place-items: center;
@ -561,16 +570,16 @@ header {
} }
.comments-panel-header { .comments-panel-header {
position: sticky; position: relative;
top: 0;
z-index: 5; z-index: 5;
flex: 0 0 auto;
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) var(--env-control-h); grid-template-columns: minmax(0, 1fr) var(--env-control-h);
align-items: center; align-items: center;
gap: 10px; gap: 10px;
min-height: calc(var(--env-control-h) + 20px); min-height: calc(var(--env-control-h) + 20px);
padding: 12px 12px 8px 16px; padding: 12px 12px 8px 16px;
background: var(--nodedc-glass-panel-surface-strong); background: transparent;
border-radius: var(--modal-radius, 16px) var(--modal-radius, 16px) 0 0; border-radius: var(--modal-radius, 16px) var(--modal-radius, 16px) 0 0;
color: var(--text); color: var(--text);
font-weight: 700; font-weight: 700;
@ -590,6 +599,7 @@ header {
} }
.comments-panel-body { .comments-panel-body {
flex: 1 1 auto;
min-height: 0; min-height: 0;
overflow: auto; overflow: auto;
padding: 0 12px 12px; padding: 0 12px 12px;
@ -1323,13 +1333,14 @@ header {
background: var(--nodedc-glass-panel-surface); background: var(--nodedc-glass-panel-surface);
border: 1px solid var(--nodedc-glass-outline); border: 1px solid var(--nodedc-glass-outline);
border-radius: var(--modal-radius, 14px); border-radius: var(--modal-radius, 14px);
padding: 12px; padding: 0;
display: grid; display: flex;
gap: 10px; flex-direction: column;
gap: 0;
width: var(--templates-width, 280px); width: var(--templates-width, 280px);
min-height: 200px; min-height: 200px;
max-height: 420px; max-height: 420px;
overflow: auto; overflow: hidden;
box-shadow: var(--nodedc-glass-panel-shadow); box-shadow: var(--nodedc-glass-panel-shadow);
z-index: 20; z-index: 20;
backdrop-filter: var(--nodedc-glass-filter); backdrop-filter: var(--nodedc-glass-filter);
@ -1341,23 +1352,32 @@ header {
} }
.templates-panel .templates-header { .templates-panel .templates-header {
position: sticky; position: relative;
top: -12px;
z-index: 5; z-index: 5;
flex: 0 0 auto;
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) auto; grid-template-columns: minmax(0, 1fr) auto;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
height: calc(var(--env-control-h) + 20px); height: calc(var(--env-control-h) + 20px);
min-height: var(--env-control-h); min-height: var(--env-control-h);
margin: -12px -12px 0; margin: 0;
padding: 12px 12px 8px 16px; padding: 12px 12px 8px 16px;
border-radius: var(--modal-radius, 14px) var(--modal-radius, 14px) 0 0; border-radius: var(--modal-radius, 14px) var(--modal-radius, 14px) 0 0;
background: var(--nodedc-glass-panel-surface-strong); background: transparent;
color: var(--text); color: var(--text);
font-weight: 700; font-weight: 700;
} }
.templates-panel .templates-panel-body {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
padding: 0 12px 12px;
display: grid;
gap: 10px;
}
.templates-panel .templates-title { .templates-panel .templates-title {
font-size: 13px; font-size: 13px;
line-height: 1.2; line-height: 1.2;
@ -1530,6 +1550,8 @@ header {
width: var(--templates-width, 280px); width: var(--templates-width, 280px);
max-width: min(var(--templates-width, 350px), calc(100vw - 32px)); max-width: min(var(--templates-width, 350px), calc(100vw - 32px));
padding: 12px; padding: 12px;
gap: 10px;
overflow: auto;
z-index: 40; z-index: 40;
} }
@ -2376,13 +2398,14 @@ header {
background: var(--nodedc-glass-panel-surface); background: var(--nodedc-glass-panel-surface);
border: 1px solid var(--nodedc-glass-outline); border: 1px solid var(--nodedc-glass-outline);
border-radius: var(--modal-radius, 14px); border-radius: var(--modal-radius, 14px);
padding: 12px; padding: 0;
display: grid; display: flex;
gap: 10px; flex-direction: column;
gap: 0;
width: var(--settings-width, 350px); width: var(--settings-width, 350px);
min-height: 420px; min-height: 420px;
max-height: min(720px, calc(100vh - 72px)); max-height: min(720px, calc(100vh - 72px));
overflow: auto; overflow: hidden;
box-shadow: var(--nodedc-glass-panel-shadow); box-shadow: var(--nodedc-glass-panel-shadow);
z-index: 22; z-index: 22;
font-size: 12px; font-size: 12px;
@ -2391,9 +2414,9 @@ header {
} }
.settings-panel .settings-header { .settings-panel .settings-header {
position: sticky; position: relative;
top: -12px;
z-index: 5; z-index: 5;
flex: 0 0 auto;
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) var(--env-control-h); grid-template-columns: minmax(0, 1fr) var(--env-control-h);
align-items: center; align-items: center;
@ -2402,12 +2425,21 @@ header {
font-weight: 700; font-weight: 700;
height: calc(var(--env-control-h) + 20px); height: calc(var(--env-control-h) + 20px);
min-height: var(--env-control-h); min-height: var(--env-control-h);
margin: -12px -12px 0; margin: 0;
padding: 12px 12px 8px 16px; padding: 12px 12px 8px 16px;
background: var(--nodedc-glass-panel-surface-strong); background: transparent;
border-radius: var(--modal-radius, 14px) var(--modal-radius, 14px) 0 0; border-radius: var(--modal-radius, 14px) var(--modal-radius, 14px) 0 0;
} }
.settings-panel .settings-panel-body {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
padding: 0 12px 12px;
display: grid;
gap: 10px;
}
.settings-panel .settings-title { .settings-panel .settings-title {
font-size: 13px; font-size: 13px;
line-height: 1.2; line-height: 1.2;
@ -3120,13 +3152,14 @@ body[data-viewer-mode="guest"] .inspector-restore-tray {
background: var(--nodedc-glass-panel-surface); background: var(--nodedc-glass-panel-surface);
border: 1px solid var(--nodedc-glass-outline); border: 1px solid var(--nodedc-glass-outline);
border-radius: var(--modal-radius, 14px); border-radius: var(--modal-radius, 14px);
padding: 10px; padding: 0;
display: grid; display: flex;
gap: 10px; flex-direction: column;
gap: 0;
width: var(--templates-width, 280px); width: var(--templates-width, 280px);
min-height: 200px; min-height: 200px;
max-height: 420px; max-height: 420px;
overflow: auto; overflow: hidden;
box-shadow: var(--nodedc-glass-panel-shadow); box-shadow: var(--nodedc-glass-panel-shadow);
z-index: 20; z-index: 20;
backdrop-filter: var(--nodedc-glass-filter); backdrop-filter: var(--nodedc-glass-filter);

View File

@ -3,8 +3,8 @@ const SDK_URL = "./lib/dc-camera-context.es.js?v=3";
import { createLogo } from "./src/ui/logo.js"; import { createLogo } from "./src/ui/logo.js";
import { toolbarConfig, applyToolbarVars } from "./src/ui/toolbar.js"; import { toolbarConfig, applyToolbarVars } from "./src/ui/toolbar.js";
import { createTemplatesMenu } from "./src/ui/templatesMenu.js?v=2"; import { createTemplatesMenu } from "./src/ui/templatesMenu.js?v=3";
import { createSettingsMenu } from "./src/ui/settingsMenu.js?v=9"; import { createSettingsMenu } from "./src/ui/settingsMenu.js?v=11";
import { createMeasurementModal } from "./src/ui/measurementModal.js"; import { createMeasurementModal } from "./src/ui/measurementModal.js";
import { createGlassSelect } from "./src/ui/glassSelect.js"; import { createGlassSelect } from "./src/ui/glassSelect.js";
import { createSliderControl } from "./src/ui/controls/slider.js"; import { createSliderControl } from "./src/ui/controls/slider.js";

View File

@ -64,6 +64,69 @@
const lum = 0.2126 * toLinear(rgb[0]) + 0.7152 * toLinear(rgb[1]) + 0.0722 * toLinear(rgb[2]); const lum = 0.2126 * toLinear(rgb[0]) + 0.7152 * toLinear(rgb[1]) + 0.0722 * toLinear(rgb[2]);
return (lum + 0.05) / 0.05 >= 1.05 / (lum + 0.05) ? "#15170f" : "#f5f5fa"; return (lum + 0.05) / 0.05 >= 1.05 / (lum + 0.05) ? "#15170f" : "#f5f5fa";
}; };
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
const colorParts = (value, fallback) => {
const parsed = parseColor(value);
if (parsed && parsed.every((channel) => Number.isFinite(channel))) return parsed;
const parsedFallback = parseColor(fallback);
if (parsedFallback && parsedFallback.every((channel) => Number.isFinite(channel))) return parsedFallback;
return [28, 28, 28];
};
const alphaValue = (value, fallback) => {
const numeric = Number(value);
return Number.isFinite(numeric) ? clamp(numeric, 0, 1) : fallback;
};
const rgba = (parts, alpha) => {
const [r, g, b] = parts.map((channel) => Math.round(clamp(channel, 0, 255)));
return `rgba(${r}, ${g}, ${b}, ${clamp(alpha, 0, 1).toFixed(3)})`;
};
const applyGlassSettings = () => {
const panelColor = colorParts(s.modalBg, "#1c1c1c");
const focusColor = colorParts(s.modalFocus, "#292929");
const controlColor = colorParts(s.modalElement, s.modalFocus || "#262626");
const outlineColor = colorParts(s.modalBorder, s.modalElementOutline || "#1c1c1c");
const controlOutlineColor = colorParts(s.modalElementOutline, s.modalBorder || "#262626");
const modalBgAlpha = alphaValue(s.modalBgAlpha, 1);
const modalFocusAlpha = alphaValue(s.modalFocusAlpha, 0.35);
const panelAlpha = clamp(0.24 + modalBgAlpha * 0.48, 0.32, 0.88);
const panelStrongAlpha = clamp(panelAlpha + 0.1, 0.42, 0.92);
const panelSoftAlpha = clamp(panelAlpha - 0.16, 0.2, 0.72);
const sectionAlpha = clamp(modalFocusAlpha * 0.6, 0.04, 0.42);
const controlAlpha = clamp(0.12 + modalFocusAlpha * 0.9, 0.16, 0.66);
const controlHoverAlpha = clamp(controlAlpha + 0.08, 0.22, 0.74);
const outlineAlpha = clamp(0.055 + modalFocusAlpha * 0.18, 0.065, 0.2);
const controlOutlineAlpha = clamp(0.045 + modalFocusAlpha * 0.12, 0.055, 0.16);
const panelBg = rgba(panelColor, panelAlpha);
const panelBgStrong = rgba(panelColor, panelStrongAlpha);
const panelBgSoft = rgba(panelColor, panelSoftAlpha);
const sectionBg = rgba(focusColor, sectionAlpha);
const outline = rgba(outlineColor, outlineAlpha);
const outlineBright = rgba(outlineColor, clamp(outlineAlpha + 0.05, 0.08, 0.26));
const outlineMid = rgba(outlineColor, clamp(outlineAlpha + 0.015, 0.07, 0.22));
const outlineDim = rgba(outlineColor, clamp(outlineAlpha * 0.62, 0.04, 0.16));
const controlBg = rgba(controlColor, controlAlpha);
const controlHover = rgba(focusColor, controlHoverAlpha);
const controlShadow = rgba(controlOutlineColor, controlOutlineAlpha);
set("--nodedc-glass-panel-bg", panelBg);
set("--nodedc-glass-panel-bg-strong", panelBgStrong);
set("--nodedc-glass-panel-bg-soft", panelBgSoft);
set("--nodedc-glass-panel-surface", `linear-gradient(180deg, rgba(255, 255, 255, 0.052) 0%, rgba(255, 255, 255, 0.014) 100%), ${panelBg}`);
set("--nodedc-glass-panel-surface-strong", `linear-gradient(180deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.016) 100%), ${panelBgStrong}`);
set("--nodedc-glass-section-bg", `linear-gradient(180deg, rgba(255, 255, 255, 0.038) 0%, rgba(255, 255, 255, 0.01) 100%), ${sectionBg}`);
set("--nodedc-glass-outline", outline);
set("--nodedc-glass-outline-gradient", `linear-gradient(180deg, ${outlineBright} 0%, ${outlineMid} 56%, ${outlineDim} 100%)`);
set("--nodedc-glass-panel-shadow", `inset 0 1px 0 ${outlineDim}, 0 22px 72px rgba(0, 0, 0, 0.42)`);
set("--nodedc-glass-control-bg", `linear-gradient(180deg, rgba(255, 255, 255, 0.052) 0%, rgba(255, 255, 255, 0.018) 100%), ${controlBg}`);
set("--nodedc-glass-control-hover", `linear-gradient(180deg, rgba(255, 255, 255, 0.075) 0%, rgba(255, 255, 255, 0.026) 100%), ${controlHover}`);
set("--nodedc-glass-control-shadow", `inset 0 1px 0 ${controlShadow}`);
set("--nodedc-header-dropdown-bg", panelBgStrong);
set("--nodedc-header-dropdown-item-bg", controlBg);
set("--nodedc-header-dropdown-item-hover-bg", controlHover);
set("--nodedc-header-dropdown-item-active-bg", s.toolbarBgActive);
set("--nodedc-header-dropdown-item-active-color", s.toolbarBgActive ? readableTextColor(s.toolbarBgActive) : undefined);
};
set("--bg-outer", s.bgOuter); set("--bg-outer", s.bgOuter);
set("--canvas-top", s.canvasTop); set("--canvas-top", s.canvasTop);
set("--canvas-bottom", s.canvasBottom); set("--canvas-bottom", s.canvasBottom);
@ -88,6 +151,7 @@
set("--modal-element", s.modalElement); set("--modal-element", s.modalElement);
set("--modal-element-outline", s.modalElementOutline); set("--modal-element-outline", s.modalElementOutline);
set("--modal-radius", s.modalRadius); set("--modal-radius", s.modalRadius);
applyGlassSettings();
set("--project-btn-fill", s.projectBtnFill); set("--project-btn-fill", s.projectBtnFill);
set("--project-btn-border", s.projectBtnBorder); set("--project-btn-border", s.projectBtnBorder);
set("--project-btn-hover", s.projectBtnHover); set("--project-btn-hover", s.projectBtnHover);
@ -134,7 +198,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./dcViewer.css?v=30"> <link rel="stylesheet" href="./dcViewer.css?v=32">
</head> </head>
<body> <body>
<header> <header>
@ -186,68 +250,70 @@
</svg> </svg>
</button> </button>
</div> </div>
<div class="panel-section inspector-main-section"> <div class="inspector-panel-body">
<div id="treeContainer" class="tree"></div> <div class="panel-section inspector-main-section">
<div class="inspector-controls"> <div id="treeContainer" class="tree"></div>
<div class="inspector-control-row inspector-control-row--half" id="displayModeControl"> <div class="inspector-controls">
<div class="inspector-control-label">Режим отображения</div> <div class="inspector-control-row inspector-control-row--half" id="displayModeControl">
<div class="inspector-select-host" id="displayModeSelect"></div> <div class="inspector-control-label">Режим отображения</div>
<div class="inspector-select-host" id="displayModeSelect"></div>
</div>
<div class="inspector-control-row inspector-control-row--half" id="lightingModeControl">
<div class="inspector-control-label">Свет</div>
<div class="inspector-select-host" id="lightingModeSelect"></div>
</div>
<div class="inspector-control-row inspector-control-row--full" id="navigationAxisControl">
<div class="inspector-control-label">Ось навигации</div>
<div class="inspector-select-host" id="navigationAxisSelect"></div>
</div>
<div class="inspector-control-row inspector-control-row--full inspector-control-row--slider" id="zoomSpeedControl"></div>
</div> </div>
<div class="inspector-control-row inspector-control-row--half" id="lightingModeControl"> </div>
<div class="inspector-control-label">Свет</div> <div class="panel-section" id="projectNameSection">
<div class="inspector-select-host" id="lightingModeSelect"></div> <h3>Название проекта</h3>
<input type="text" id="projectNameInput" class="project-name-input" placeholder="Проект" autocomplete="off">
<div class="error-text inline-error" id="projectNameError"></div>
</div>
<div class="panel-section">
<h3>Глобальные координаты</h3>
<div class="muted" style="margin-bottom:6px;">Центр выделения (мировые)</div>
<div class="transform-grid">
<label>X <input type="text" id="globalX"></label>
<label>Y <input type="text" id="globalY"></label>
<label>Z <input type="text" id="globalZ"></label>
</div> </div>
<div class="inspector-control-row inspector-control-row--full" id="navigationAxisControl"> </div>
<div class="inspector-control-label">Ось навигации</div> <div class="panel-section">
<div class="inspector-select-host" id="navigationAxisSelect"></div> <h3>Трансформации</h3>
<div class="muted" style="margin-bottom:6px;">Перемещение</div>
<div class="transform-grid">
<label>X <input type="text" id="posX"></label>
<label>Y <input type="text" id="posY"></label>
<label>Z <input type="text" id="posZ"></label>
</div> </div>
<div class="inspector-control-row inspector-control-row--full inspector-control-row--slider" id="zoomSpeedControl"></div> <div class="muted" style="margin:8px 0 6px;">Вращение (deg)</div>
<div class="transform-grid">
<label>X <input type="text" id="rotX"></label>
<label>Y <input type="text" id="rotY"></label>
<label>Z <input type="text" id="rotZ"></label>
</div>
<div class="muted" style="margin:8px 0 6px;">Масштаб</div>
<div class="transform-grid">
<label>X <input type="text" id="scaleX"></label>
<label>Y <input type="text" id="scaleY"></label>
<label>Z <input type="text" id="scaleZ"></label>
</div>
<small id="transformHint" class="muted">Выберите объект в инспекторе или сцене, чтобы редактировать.</small>
</div> </div>
</div> <div class="panel-section">
<div class="panel-section" id="projectNameSection"> <h3>Журнал</h3>
<h3>Название проекта</h3> <div id="logList" class="log"></div>
<input type="text" id="projectNameInput" class="project-name-input" placeholder="Проект" autocomplete="off">
<div class="error-text inline-error" id="projectNameError"></div>
</div>
<div class="panel-section">
<h3>Глобальные координаты</h3>
<div class="muted" style="margin-bottom:6px;">Центр выделения (мировые)</div>
<div class="transform-grid">
<label>X <input type="text" id="globalX"></label>
<label>Y <input type="text" id="globalY"></label>
<label>Z <input type="text" id="globalZ"></label>
</div> </div>
</div> <div class="panel-section" id="saveProjectSection">
<div class="panel-section"> <button class="secondary-btn model-settings-btn" id="saveModelSettingsButton" type="button">Сохранить настройки</button>
<h3>Трансформации</h3> <button class="primary-btn" id="saveProjectButton" type="button">Сохранить проект</button>
<div class="muted" style="margin-bottom:6px;">Перемещение</div> <button class="secondary-btn danger-btn" id="deleteProjectButton" type="button">Удалить проект</button>
<div class="transform-grid">
<label>X <input type="text" id="posX"></label>
<label>Y <input type="text" id="posY"></label>
<label>Z <input type="text" id="posZ"></label>
</div> </div>
<div class="muted" style="margin:8px 0 6px;">Вращение (deg)</div>
<div class="transform-grid">
<label>X <input type="text" id="rotX"></label>
<label>Y <input type="text" id="rotY"></label>
<label>Z <input type="text" id="rotZ"></label>
</div>
<div class="muted" style="margin:8px 0 6px;">Масштаб</div>
<div class="transform-grid">
<label>X <input type="text" id="scaleX"></label>
<label>Y <input type="text" id="scaleY"></label>
<label>Z <input type="text" id="scaleZ"></label>
</div>
<small id="transformHint" class="muted">Выберите объект в инспекторе или сцене, чтобы редактировать.</small>
</div>
<div class="panel-section">
<h3>Журнал</h3>
<div id="logList" class="log"></div>
</div>
<div class="panel-section" id="saveProjectSection">
<button class="secondary-btn model-settings-btn" id="saveModelSettingsButton" type="button">Сохранить настройки</button>
<button class="primary-btn" id="saveProjectButton" type="button">Сохранить проект</button>
<button class="secondary-btn danger-btn" id="deleteProjectButton" type="button">Удалить проект</button>
</div> </div>
</aside> </aside>
<div class="inspector-restore-tray" id="inspectorRestoreTray" hidden> <div class="inspector-restore-tray" id="inspectorRestoreTray" hidden>
@ -570,6 +636,6 @@
<input id="fileInput" class="hidden" type="file" <input id="fileInput" class="hidden" type="file"
accept=".xkt,.glb,.gltf,.bim,.las,.laz,.obj,.stl"> accept=".xkt,.glb,.gltf,.bim,.las,.laz,.obj,.stl">
<script type="module" src="./dcViewer.js?v=43"></script> <script type="module" src="./dcViewer.js?v=45"></script>
</body> </body>
</html> </html>

View File

@ -102,6 +102,92 @@ const readableTextColor = (value) => {
return (lum + 0.05) / 0.05 >= 1.05 / (lum + 0.05) ? "#15170f" : "#f5f5fa"; return (lum + 0.05) / 0.05 >= 1.05 / (lum + 0.05) ? "#15170f" : "#f5f5fa";
}; };
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
const colorParts = (value, fallback) => {
const parsed = parseColor(value);
if (parsed && parsed.every((channel) => Number.isFinite(channel))) return parsed;
const parsedFallback = parseColor(fallback);
if (parsedFallback && parsedFallback.every((channel) => Number.isFinite(channel))) return parsedFallback;
return [28, 28, 28];
};
const alphaValue = (value, fallback) => {
const numeric = Number(value);
return Number.isFinite(numeric) ? clamp(numeric, 0, 1) : fallback;
};
const rgba = (parts, alpha) => {
const [r, g, b] = parts.map((channel) => Math.round(clamp(channel, 0, 255)));
return `rgba(${r}, ${g}, ${b}, ${clamp(alpha, 0, 1).toFixed(3)})`;
};
const applyGlassSettings = (root, s) => {
const panelColor = colorParts(s.modalBg, defaultSettings.modalBg);
const focusColor = colorParts(s.modalFocus, defaultSettings.modalFocus);
const controlColor = colorParts(s.modalElement, s.modalFocus || defaultSettings.modalElement);
const outlineColor = colorParts(s.modalBorder, s.modalElementOutline || defaultSettings.modalBorder);
const controlOutlineColor = colorParts(s.modalElementOutline, s.modalBorder || defaultSettings.modalElementOutline);
const modalBgAlpha = alphaValue(s.modalBgAlpha, defaultSettings.modalBgAlpha);
const modalFocusAlpha = alphaValue(s.modalFocusAlpha, defaultSettings.modalFocusAlpha);
const panelAlpha = clamp(0.24 + modalBgAlpha * 0.48, 0.32, 0.88);
const panelStrongAlpha = clamp(panelAlpha + 0.1, 0.42, 0.92);
const panelSoftAlpha = clamp(panelAlpha - 0.16, 0.2, 0.72);
const sectionAlpha = clamp(modalFocusAlpha * 0.6, 0.04, 0.42);
const controlAlpha = clamp(0.12 + modalFocusAlpha * 0.9, 0.16, 0.66);
const controlHoverAlpha = clamp(controlAlpha + 0.08, 0.22, 0.74);
const outlineAlpha = clamp(0.055 + modalFocusAlpha * 0.18, 0.065, 0.2);
const controlOutlineAlpha = clamp(0.045 + modalFocusAlpha * 0.12, 0.055, 0.16);
const panelBg = rgba(panelColor, panelAlpha);
const panelBgStrong = rgba(panelColor, panelStrongAlpha);
const panelBgSoft = rgba(panelColor, panelSoftAlpha);
const sectionBg = rgba(focusColor, sectionAlpha);
const outline = rgba(outlineColor, outlineAlpha);
const outlineBright = rgba(outlineColor, clamp(outlineAlpha + 0.05, 0.08, 0.26));
const outlineMid = rgba(outlineColor, clamp(outlineAlpha + 0.015, 0.07, 0.22));
const outlineDim = rgba(outlineColor, clamp(outlineAlpha * 0.62, 0.04, 0.16));
const controlBg = rgba(controlColor, controlAlpha);
const controlHover = rgba(focusColor, controlHoverAlpha);
const controlShadow = rgba(controlOutlineColor, controlOutlineAlpha);
root.style.setProperty("--nodedc-glass-panel-bg", panelBg);
root.style.setProperty("--nodedc-glass-panel-bg-strong", panelBgStrong);
root.style.setProperty("--nodedc-glass-panel-bg-soft", panelBgSoft);
root.style.setProperty(
"--nodedc-glass-panel-surface",
`linear-gradient(180deg, rgba(255, 255, 255, 0.052) 0%, rgba(255, 255, 255, 0.014) 100%), ${panelBg}`
);
root.style.setProperty(
"--nodedc-glass-panel-surface-strong",
`linear-gradient(180deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.016) 100%), ${panelBgStrong}`
);
root.style.setProperty(
"--nodedc-glass-section-bg",
`linear-gradient(180deg, rgba(255, 255, 255, 0.038) 0%, rgba(255, 255, 255, 0.01) 100%), ${sectionBg}`
);
root.style.setProperty("--nodedc-glass-outline", outline);
root.style.setProperty(
"--nodedc-glass-outline-gradient",
`linear-gradient(180deg, ${outlineBright} 0%, ${outlineMid} 56%, ${outlineDim} 100%)`
);
root.style.setProperty("--nodedc-glass-panel-shadow", `inset 0 1px 0 ${outlineDim}, 0 22px 72px rgba(0, 0, 0, 0.42)`);
root.style.setProperty(
"--nodedc-glass-control-bg",
`linear-gradient(180deg, rgba(255, 255, 255, 0.052) 0%, rgba(255, 255, 255, 0.018) 100%), ${controlBg}`
);
root.style.setProperty(
"--nodedc-glass-control-hover",
`linear-gradient(180deg, rgba(255, 255, 255, 0.075) 0%, rgba(255, 255, 255, 0.026) 100%), ${controlHover}`
);
root.style.setProperty("--nodedc-glass-control-shadow", `inset 0 1px 0 ${controlShadow}`);
root.style.setProperty("--nodedc-header-dropdown-bg", panelBgStrong);
root.style.setProperty("--nodedc-header-dropdown-item-bg", controlBg);
root.style.setProperty("--nodedc-header-dropdown-item-hover-bg", controlHover);
root.style.setProperty("--nodedc-header-dropdown-item-active-bg", s.toolbarBgActive);
root.style.setProperty("--nodedc-header-dropdown-item-active-color", readableTextColor(s.toolbarBgActive));
};
const applySettings = (s) => { const applySettings = (s) => {
const root = document.documentElement; const root = document.documentElement;
root.style.setProperty("--bg-outer", s.bgOuter); root.style.setProperty("--bg-outer", s.bgOuter);
@ -137,6 +223,7 @@ const applySettings = (s) => {
root.style.setProperty("--modal-element", s.modalElement); root.style.setProperty("--modal-element", s.modalElement);
root.style.setProperty("--modal-element-outline", s.modalElementOutline); root.style.setProperty("--modal-element-outline", s.modalElementOutline);
root.style.setProperty("--modal-radius", s.modalRadius); root.style.setProperty("--modal-radius", s.modalRadius);
applyGlassSettings(root, s);
root.style.setProperty("--border", s.border); root.style.setProperty("--border", s.border);
root.style.setProperty("--text", s.text); root.style.setProperty("--text", s.text);
root.style.setProperty("--panel", s.panel); root.style.setProperty("--panel", s.panel);
@ -259,6 +346,10 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
header.appendChild(closeBtn); header.appendChild(closeBtn);
panel.appendChild(header); panel.appendChild(header);
const body = document.createElement("div");
body.className = "settings-panel-body";
panel.appendChild(body);
const section = (title) => { const section = (title) => {
const box = document.createElement("div"); const box = document.createElement("div");
box.className = "settings-section"; box.className = "settings-section";
@ -357,7 +448,7 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
addColor(font, "Фон холста низ", "canvasBottom"); addColor(font, "Фон холста низ", "canvasBottom");
addColor(font, "Фон загрузки верх", "loadingTop"); addColor(font, "Фон загрузки верх", "loadingTop");
addColor(font, "Фон загрузки низ", "loadingBottom"); addColor(font, "Фон загрузки низ", "loadingBottom");
panel.appendChild(font); body.appendChild(font);
const buttons = section("Кнопки"); const buttons = section("Кнопки");
addColor(buttons, "Плюс", "plus"); addColor(buttons, "Плюс", "plus");
@ -374,26 +465,26 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
step: 1, step: 1,
formatValue: (val) => `${Math.round(val)}%`, formatValue: (val) => `${Math.round(val)}%`,
}); });
panel.appendChild(buttons); body.appendChild(buttons);
const projectButtons = section("Кнопки проектов"); const projectButtons = section("Кнопки проектов");
addColor(projectButtons, "Проекты (заливка)", "projectBtnFill"); addColor(projectButtons, "Проекты (заливка)", "projectBtnFill");
addColor(projectButtons, "Проекты (бордер)", "projectBtnBorder"); addColor(projectButtons, "Проекты (бордер)", "projectBtnBorder");
addColor(projectButtons, "Проекты (hover)", "projectBtnHover"); addColor(projectButtons, "Проекты (hover)", "projectBtnHover");
panel.appendChild(projectButtons); body.appendChild(projectButtons);
const modalButtons = section("Кнопки модалок"); const modalButtons = section("Кнопки модалок");
addColor(modalButtons, "Модальная primary", "modalBtnPrimary"); addColor(modalButtons, "Модальная primary", "modalBtnPrimary");
addColor(modalButtons, "Модальная secondary", "modalBtnSecondary"); addColor(modalButtons, "Модальная secondary", "modalBtnSecondary");
addColor(modalButtons, "Лоудер", "loaderColor"); addColor(modalButtons, "Лоудер", "loaderColor");
panel.appendChild(modalButtons); body.appendChild(modalButtons);
const toolbar = section("Toolbar"); const toolbar = section("Toolbar");
addColor(toolbar, "Фон", "toolbarBg"); addColor(toolbar, "Фон", "toolbarBg");
addColor(toolbar, "Актив", "toolbarBgActive"); addColor(toolbar, "Актив", "toolbarBgActive");
addColor(toolbar, "Бордер", "toolbarBorder"); addColor(toolbar, "Бордер", "toolbarBorder");
addColor(toolbar, "Outline", "toolbarOutline"); addColor(toolbar, "Outline", "toolbarOutline");
panel.appendChild(toolbar); body.appendChild(toolbar);
const navCube = section("NavCube"); const navCube = section("NavCube");
addColor(navCube, "Грани", "navCubeBase"); addColor(navCube, "Грани", "navCubeBase");
@ -412,7 +503,7 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
setVal("navCubeEdgeAlpha", Math.max(0, Math.min(1, Number(val) / 100))); setVal("navCubeEdgeAlpha", Math.max(0, Math.min(1, Number(val) / 100)));
}, },
}); });
panel.appendChild(navCube); body.appendChild(navCube);
const sectionCut = section("Разрез / сечение"); const sectionCut = section("Разрез / сечение");
addColor(sectionCut, "Ось X", "sectionAxisX"); addColor(sectionCut, "Ось X", "sectionAxisX");
@ -468,7 +559,7 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
setVal("sectionPlaneEdgeAlpha", Math.max(0, Math.min(1, Number(val) / 100))); setVal("sectionPlaneEdgeAlpha", Math.max(0, Math.min(1, Number(val) / 100)));
}, },
}); });
panel.appendChild(sectionCut); body.appendChild(sectionCut);
const commentTargets = section("Таргеты комментариев"); const commentTargets = section("Таргеты комментариев");
addColor(commentTargets, "Активный: цвет", "commentTargetActiveColor"); addColor(commentTargets, "Активный: цвет", "commentTargetActiveColor");
@ -521,14 +612,14 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
setVal("commentTargetPassiveSize", Math.max(12, Math.min(48, Math.round(Number(val))))); setVal("commentTargetPassiveSize", Math.max(12, Math.min(48, Math.round(Number(val)))));
}, },
}); });
panel.appendChild(commentTargets); body.appendChild(commentTargets);
const measure = section("Линейка"); const measure = section("Линейка");
addColor(measure, "Линия/бордер", "measureLine"); addColor(measure, "Линия/бордер", "measureLine");
addColor(measure, "Плашка", "measureLabel"); addColor(measure, "Плашка", "measureLabel");
addColor(measure, "Текст плашки", "measureLabelText"); addColor(measure, "Текст плашки", "measureLabelText");
addColor(measure, "Кружок", "measureDot"); addColor(measure, "Кружок", "measureDot");
panel.appendChild(measure); body.appendChild(measure);
const viewport = section("Viewport"); const viewport = section("Viewport");
const addCheck = (label, checked, handler) => { const addCheck = (label, checked, handler) => {
@ -548,7 +639,7 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
if (controls.toggleEdges) addCheck("Рёбра", controls.toggleEdges.checked, (v) => { controls.toggleEdges.checked = v; controls.toggleEdges.dispatchEvent(new Event("change")); }); if (controls.toggleEdges) addCheck("Рёбра", controls.toggleEdges.checked, (v) => { controls.toggleEdges.checked = v; controls.toggleEdges.dispatchEvent(new Event("change")); });
if (controls.toggleTransparent) addCheck("Прозрачный фон", controls.toggleTransparent.checked, (v) => { controls.toggleTransparent.checked = v; controls.toggleTransparent.dispatchEvent(new Event("change")); }); if (controls.toggleTransparent) addCheck("Прозрачный фон", controls.toggleTransparent.checked, (v) => { controls.toggleTransparent.checked = v; controls.toggleTransparent.dispatchEvent(new Event("change")); });
if (controls.addMode) addCheck("Добавлять в сцену", controls.addMode.checked, (v) => { controls.addMode.checked = v; }); if (controls.addMode) addCheck("Добавлять в сцену", controls.addMode.checked, (v) => { controls.addMode.checked = v; });
panel.appendChild(viewport); body.appendChild(viewport);
const modals = section("Модальные окна"); const modals = section("Модальные окна");
addColor(modals, "Основной фон", "modalBg"); addColor(modals, "Основной фон", "modalBg");
@ -598,18 +689,18 @@ export function createSettingsMenu({ panel, controls = {}, onChange = null, onSh
setVal("modalRadius", val); setVal("modalRadius", val);
}, },
}); });
panel.appendChild(modals); body.appendChild(modals);
const cube = section("Выделение объектов"); const cube = section("Выделение объектов");
addColor(cube, "Подсветка объектов", "highlight"); addColor(cube, "Подсветка объектов", "highlight");
panel.appendChild(cube); body.appendChild(cube);
}; };
const show = () => { const show = () => {
render(); render();
if (panel) { if (panel) {
panel.classList.remove("hidden"); panel.classList.remove("hidden");
panel.style.display = "grid"; panel.style.display = "flex";
} }
onShow?.(); onShow?.();
}; };

View File

@ -89,11 +89,15 @@ export function createTemplatesMenu({ panel, fetchProjects, openProject, onShow,
header.appendChild(actions); header.appendChild(actions);
panel.appendChild(header); panel.appendChild(header);
const body = document.createElement("div");
body.className = "templates-panel-body";
panel.appendChild(body);
if (state.loading) { if (state.loading) {
const loadingEl = document.createElement("div"); const loadingEl = document.createElement("div");
loadingEl.className = "templates-empty"; loadingEl.className = "templates-empty";
loadingEl.textContent = "Загрузка списка проектов..."; loadingEl.textContent = "Загрузка списка проектов...";
panel.appendChild(loadingEl); body.appendChild(loadingEl);
return; return;
} }
@ -102,11 +106,11 @@ export function createTemplatesMenu({ panel, fetchProjects, openProject, onShow,
errorEl.className = "templates-empty"; errorEl.className = "templates-empty";
errorEl.style.color = "var(--error)"; errorEl.style.color = "var(--error)";
errorEl.textContent = state.error; errorEl.textContent = state.error;
panel.appendChild(errorEl); body.appendChild(errorEl);
} }
panel.appendChild(renderSection("Шаблоны", state.templates, "Нет шаблонов")); body.appendChild(renderSection("Шаблоны", state.templates, "Нет шаблонов"));
panel.appendChild(renderSection("Мои проекты", state.projects, "Нет сохранённых проектов")); body.appendChild(renderSection("Мои проекты", state.projects, "Нет сохранённых проектов"));
}; };
const load = async () => { const load = async () => {
@ -137,7 +141,7 @@ export function createTemplatesMenu({ panel, fetchProjects, openProject, onShow,
render(); render();
if (panel) { if (panel) {
panel.classList.remove("hidden"); panel.classList.remove("hidden");
panel.style.display = "grid"; panel.style.display = "flex";
} }
load(); load();
onShow?.(); onShow?.();