Polish BIM shared viewer controls
This commit is contained in:
parent
fcfa59ec98
commit
f680d6bad2
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
|
|
@ -0,0 +1,13 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.icon-fav-01 { fill: #333334; } /* Тёмный цвет для светлого таб-бара */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.icon-fav-01 { fill: #FEFEFE; } /* Светлый цвет для тёмного таб-бара */
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
.icon-fav-01 { fill: #000000; } /* Тёмный цвет для светлого таб-бара (для явности) */
|
||||
}
|
||||
</style>
|
||||
<path class="icon-fav-01" d="M17.8738 15.9159L16.0002 18.9718L14.1267 15.9159H17.8738ZM23.6307 12.7864H8.36914L15.9999 25.2308L23.6307 12.7864Z"/>
|
||||
<path class="icon-fav-01" d="M10.9793 19.4872L6.67269 11.5918H25.5344L21.2281 19.4872H25.0581L31.2614 8H0.738281L7.16244 19.4872H10.9793Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 852 B |
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"theme_color": "#eeeff4",
|
||||
"background_color": "#eeeff4",
|
||||
"display": "browser",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon-adaptive.svg",
|
||||
"type": "image/svg+xml",
|
||||
"sizes": "any"
|
||||
},
|
||||
{
|
||||
"src": "icon-192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "icon-512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
},
|
||||
{
|
||||
"src": "apple-touch-icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "180x180"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1183,6 +1183,74 @@ header {
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
.inspector-color-host {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.inspector-color-host .env-color-field {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 40px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
box-sizing: border-box;
|
||||
padding: 5px 10px 5px 7px;
|
||||
border-radius: 14px;
|
||||
background: var(--nodedc-glass-control-bg);
|
||||
box-shadow: var(--nodedc-glass-control-shadow);
|
||||
backdrop-filter: blur(24px) saturate(128%) brightness(1.05);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(128%) brightness(1.05);
|
||||
}
|
||||
|
||||
.inspector-color-host .env-color-field__swatch {
|
||||
position: relative;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
min-height: 26px;
|
||||
flex: 0 0 26px;
|
||||
overflow: hidden;
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.inspector-color-host .env-color-field__preview {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.inspector-color-host .env-color-field__swatch input[type="color"] {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.inspector-color-host .env-color-field__value {
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.inspector-color-host .env-color-field__value input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
padding: 0 2px;
|
||||
background: transparent;
|
||||
color: rgba(255, 255, 255, 0.66);
|
||||
font-size: 12px;
|
||||
font-weight: 760;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nodedc-select {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
|
|
@ -3102,10 +3170,11 @@ header {
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
body[data-viewer-mode="guest"] .bottom-toolbar,
|
||||
body[data-viewer-mode="guest"] .section-toolbar,
|
||||
body[data-viewer-mode="guest"] .section-overview,
|
||||
body[data-viewer-mode="guest"] .inspector-restore-tray {
|
||||
body[data-viewer-mode="guest"] .tb-btn:not([data-action="measure"]):not([data-action="section"]):not([data-action="projection"]),
|
||||
body[data-viewer-mode="guest"] #settingsPanel,
|
||||
body[data-viewer-mode="guest"] #templatesPanel,
|
||||
body[data-viewer-mode="guest"] #projectNameSection,
|
||||
body[data-viewer-mode="guest"] #saveProjectSection {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ const SDK_VERSION = "2.6.78"; // соответствует package.json
|
|||
const SDK_URL = "./lib/dc-camera-context.es.js?v=3";
|
||||
|
||||
import { createLogo } from "./src/ui/logo.js";
|
||||
import { applyToolbarVars } from "./src/ui/toolbar.js?v=2";
|
||||
import { applyToolbarVars } from "./src/ui/toolbar.js?v=3";
|
||||
import { createTemplatesMenu } from "./src/ui/templatesMenu.js?v=3";
|
||||
import { createSettingsMenu } from "./src/ui/settingsMenu.js?v=12";
|
||||
import { createSettingsMenu } from "./src/ui/settingsMenu.js?v=14";
|
||||
import { createMeasurementModal } from "./src/ui/measurementModal.js";
|
||||
import { createGlassSelect } from "./src/ui/glassSelect.js";
|
||||
import { createColorControl } from "./src/ui/controls/color.js";
|
||||
import { createSliderControl } from "./src/ui/controls/slider.js";
|
||||
import "./theme.js?v=2";
|
||||
import "./theme.js?v=3";
|
||||
import { TransformGizmo } from "./transformGizmo.js";
|
||||
import { createObjectModal } from "./src/ui/objectModal.js";
|
||||
|
||||
|
|
@ -73,6 +74,20 @@ const commentThreadList = document.getElementById("commentThreadList");
|
|||
const commentReplyInput = document.getElementById("commentReplyInput");
|
||||
const commentReplyAttachButton = document.getElementById("commentReplyAttachButton");
|
||||
const commentReplyAttachmentInput = document.getElementById("commentReplyAttachmentInput");
|
||||
|
||||
const DEFAULT_LAUNCHER_URL = "https://hub.nodedc.ru/";
|
||||
|
||||
const getLauncherUrl = () => {
|
||||
const configured = typeof window !== "undefined" ? window.__BIMDC_LAUNCHER_URL__ : "";
|
||||
const raw = typeof configured === "string" && configured.trim()
|
||||
? configured.trim()
|
||||
: DEFAULT_LAUNCHER_URL;
|
||||
try {
|
||||
return new URL(raw, window.location.href).toString();
|
||||
} catch {
|
||||
return DEFAULT_LAUNCHER_URL;
|
||||
}
|
||||
};
|
||||
const commentReplyAttachmentGrid = document.getElementById("commentReplyAttachmentGrid");
|
||||
const commentModalError = document.getElementById("commentModalError");
|
||||
const commentCancelButton = document.getElementById("commentCancelButton");
|
||||
|
|
@ -82,6 +97,9 @@ const commentApplyInlineButton = document.getElementById("commentApplyInlineButt
|
|||
const treeContainer = document.getElementById("treeContainer");
|
||||
const displayModeControl = document.getElementById("displayModeControl");
|
||||
const displayModeSelectHost = document.getElementById("displayModeSelect");
|
||||
const customDisplayColorRow = document.getElementById("customDisplayColorRow");
|
||||
const customDisplayColorControlHost = document.getElementById("customDisplayColorControl");
|
||||
const customDisplaySaturationControl = document.getElementById("customDisplaySaturationControl");
|
||||
const lightingModeControl = document.getElementById("lightingModeControl");
|
||||
const lightingModeSelectHost = document.getElementById("lightingModeSelect");
|
||||
const navigationAxisControl = document.getElementById("navigationAxisControl");
|
||||
|
|
@ -214,11 +232,14 @@ const DISPLAY_MODE_OPTIONS = [
|
|||
{ value: "source", label: "Исходный" },
|
||||
{ value: "clay", label: "Clay" },
|
||||
{ value: "white", label: "Белый" },
|
||||
{ value: "contrast", label: "Контраст" },
|
||||
{ value: "custom", label: "Кастом" },
|
||||
{ value: "edges", label: "Рёбра" },
|
||||
{ value: "xray", label: "Рентген" },
|
||||
];
|
||||
|
||||
const DEFAULT_CUSTOM_DISPLAY_COLOR = "#ffffff";
|
||||
const DEFAULT_CUSTOM_DISPLAY_SATURATION = 100;
|
||||
|
||||
const NAVIGATION_AXIS_OPTIONS = [
|
||||
{ value: "auto", label: "Авто" },
|
||||
{ value: "x-up", label: "X" },
|
||||
|
|
@ -229,7 +250,6 @@ const NAVIGATION_AXIS_OPTIONS = [
|
|||
const LIGHTING_MODE_OPTIONS = [
|
||||
{ value: "standard", label: "Стандарт" },
|
||||
{ value: "soft", label: "Мягкий" },
|
||||
{ value: "ambient", label: "Без солнца" },
|
||||
{ value: "technical", label: "Тех." },
|
||||
];
|
||||
|
||||
|
|
@ -253,6 +273,7 @@ const NAVIGATION_AXIS_PRESETS = {
|
|||
|
||||
let templatesMenu = null;
|
||||
let settingsMenu = null;
|
||||
let pendingDesignState = null;
|
||||
let measurementModal = null;
|
||||
|
||||
applyPanelOffsets();
|
||||
|
|
@ -265,6 +286,7 @@ const activeModels = [];
|
|||
let viewerMode = "standard";
|
||||
let runtimeSessionInfo = { authRequired: false, authenticated: false, canShare: true, user: null };
|
||||
let startupSharePayload = null;
|
||||
const GUEST_TOOLBAR_ACTIONS = new Set(["measure", "section", "projection"]);
|
||||
const memoryStore = new Map(); // url -> ArrayBuffer
|
||||
const blobUrls = [];
|
||||
let gizmo = null;
|
||||
|
|
@ -273,11 +295,15 @@ const modelTypes = {}; // modelId -> normalized type
|
|||
const sceneModels = {}; // modelId -> SceneModel
|
||||
const pointCloudColorSnapshots = new Map(); // modelId -> [{ layer, colors, workingColors }]
|
||||
let activeDisplayMode = "source";
|
||||
let activeLightingMode = "ambient";
|
||||
let activeLightingMode = "technical";
|
||||
let activeNavigationAxis = "auto";
|
||||
let activeZoomSpeed = 20;
|
||||
let activeCustomDisplayColor = DEFAULT_CUSTOM_DISPLAY_COLOR;
|
||||
let activeCustomDisplaySaturation = DEFAULT_CUSTOM_DISPLAY_SATURATION;
|
||||
let inspectorCollapsed = false;
|
||||
let displayModeSelect = null;
|
||||
let customDisplayColorControl = null;
|
||||
let customDisplaySaturationSlider = null;
|
||||
let lightingModeSelect = null;
|
||||
let navigationAxisSelect = null;
|
||||
let zoomSpeedSlider = null;
|
||||
|
|
@ -344,6 +370,43 @@ const normalizeZoomSpeed = (value) => {
|
|||
return Math.min(100, Math.max(5, Math.round(numeric)));
|
||||
};
|
||||
|
||||
const isHexColor = (value) => /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(String(value || "").trim());
|
||||
|
||||
const normalizeCustomDisplayColor = (value) => {
|
||||
const raw = String(value || "").trim();
|
||||
if (!isHexColor(raw)) return DEFAULT_CUSTOM_DISPLAY_COLOR;
|
||||
if (raw.length === 4) {
|
||||
const [, r, g, b] = raw;
|
||||
return `#${r}${r}${g}${g}${b}${b}`.toLowerCase();
|
||||
}
|
||||
return raw.toLowerCase();
|
||||
};
|
||||
|
||||
const normalizeCustomDisplaySaturation = (value) => {
|
||||
const numeric = Number(value);
|
||||
if (!Number.isFinite(numeric)) return DEFAULT_CUSTOM_DISPLAY_SATURATION;
|
||||
return Math.min(200, Math.max(0, Math.round(numeric)));
|
||||
};
|
||||
|
||||
const customDisplayHexToRgb01 = (value) => {
|
||||
const hex = normalizeCustomDisplayColor(value).slice(1);
|
||||
return [
|
||||
Number.parseInt(hex.slice(0, 2), 16) / 255,
|
||||
Number.parseInt(hex.slice(2, 4), 16) / 255,
|
||||
Number.parseInt(hex.slice(4, 6), 16) / 255,
|
||||
];
|
||||
};
|
||||
|
||||
const applyRgbSaturation = (rgb, saturation) => {
|
||||
const factor = normalizeCustomDisplaySaturation(saturation) / 100;
|
||||
const gray = (rgb[0] * 0.2126) + (rgb[1] * 0.7152) + (rgb[2] * 0.0722);
|
||||
return rgb.map((channel) => Math.min(1, Math.max(0, gray + ((channel - gray) * factor))));
|
||||
};
|
||||
|
||||
const getCustomDisplayColorize = () => (
|
||||
applyRgbSaturation(customDisplayHexToRgb01(activeCustomDisplayColor), activeCustomDisplaySaturation)
|
||||
);
|
||||
|
||||
const configureCameraPanMap = (control) => {
|
||||
const input = viewer?.scene?.input;
|
||||
if (!control || !input) return;
|
||||
|
|
@ -439,16 +502,36 @@ const normalizeDisplayMode = (mode) => (
|
|||
);
|
||||
|
||||
const normalizeLightingMode = (mode) => (
|
||||
LIGHTING_MODE_OPTIONS.some((option) => option.value === mode) ? mode : "ambient"
|
||||
LIGHTING_MODE_OPTIONS.some((option) => option.value === mode) ? mode : "technical"
|
||||
);
|
||||
|
||||
const normalizeNavigationAxis = (axis) => (
|
||||
NAVIGATION_AXIS_OPTIONS.some((option) => option.value === axis) ? axis : "auto"
|
||||
);
|
||||
|
||||
const setCustomDisplaySettings = ({ color, saturation } = {}) => {
|
||||
if (color !== undefined) {
|
||||
activeCustomDisplayColor = normalizeCustomDisplayColor(color);
|
||||
customDisplayColorControl?.setValue?.(activeCustomDisplayColor);
|
||||
}
|
||||
if (saturation !== undefined) {
|
||||
activeCustomDisplaySaturation = normalizeCustomDisplaySaturation(saturation);
|
||||
customDisplaySaturationSlider?.setValue?.(activeCustomDisplaySaturation);
|
||||
}
|
||||
};
|
||||
|
||||
const setCustomDisplayControlsState = () => {
|
||||
const isCustom = normalizeDisplayMode(activeDisplayMode) === "custom";
|
||||
customDisplayColorRow?.classList.toggle("hidden", !isCustom);
|
||||
customDisplaySaturationControl?.classList.toggle("hidden", !isCustom);
|
||||
customDisplayColorControl?.setValue?.(activeCustomDisplayColor);
|
||||
customDisplaySaturationSlider?.setValue?.(activeCustomDisplaySaturation);
|
||||
};
|
||||
|
||||
const setDisplayModeControlState = () => {
|
||||
activeDisplayMode = normalizeDisplayMode(activeDisplayMode);
|
||||
displayModeSelect?.setValue(activeDisplayMode);
|
||||
setCustomDisplayControlsState();
|
||||
};
|
||||
|
||||
const setLightingModeControlState = () => {
|
||||
|
|
@ -519,6 +602,7 @@ const getPrimaryModelEntry = () => (
|
|||
const hasModelSettingsTarget = () => !!getModelSettingsSrc(getPrimaryModelEntry());
|
||||
|
||||
const isGuestMode = () => viewerMode === "guest";
|
||||
const isGuestToolbarAction = (action) => GUEST_TOOLBAR_ACTIONS.has(action);
|
||||
|
||||
const getShareTokenFromPath = () => {
|
||||
const match = window.location.pathname.match(/^\/share\/([^/?#]+)/);
|
||||
|
|
@ -1609,15 +1693,34 @@ const bindFooterButtonAction = (button, handler) => {
|
|||
};
|
||||
|
||||
const applyRuntimeMode = () => {
|
||||
const guest = isGuestMode();
|
||||
document.body.dataset.viewerMode = viewerMode;
|
||||
if (bottomToolbar) {
|
||||
bottomToolbar.classList.toggle("hidden", isGuestMode());
|
||||
bottomToolbar.classList.remove("hidden");
|
||||
bottomToolbar.querySelectorAll(".tb-btn").forEach((btn) => {
|
||||
const action = btn.dataset.action || "";
|
||||
const guestHidden = guest && !isGuestToolbarAction(action);
|
||||
btn.classList.toggle("hidden", guestHidden);
|
||||
if (guestHidden) {
|
||||
btn.disabled = true;
|
||||
} else if (!guest && action !== "section" && action !== "comments" && action !== "share") {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (settingsButton) settingsButton.disabled = guest;
|
||||
if (templatesButton) templatesButton.disabled = guest;
|
||||
if (measureButton && guest) measureButton.disabled = !measurePlugin || !measureControl || activeModels.length === 0;
|
||||
if (projectionToggle && guest) projectionToggle.disabled = activeModels.length === 0;
|
||||
if (commentsButton) {
|
||||
commentsButton.classList.toggle("hidden", isGuestMode());
|
||||
commentsButton.disabled = isGuestMode() || activeModels.length === 0;
|
||||
commentsButton.classList.toggle("hidden", guest);
|
||||
commentsButton.disabled = guest || activeModels.length === 0;
|
||||
}
|
||||
if (isGuestMode()) {
|
||||
if (guest) {
|
||||
closeSettingsPanel();
|
||||
closeTemplatesPanel();
|
||||
settingsPanel?.classList.add("hidden");
|
||||
templatesPanel?.classList.add("hidden");
|
||||
commentsModeActive = false;
|
||||
commentsCollapsed = false;
|
||||
commentContextTarget = null;
|
||||
|
|
@ -1630,15 +1733,12 @@ const applyRuntimeMode = () => {
|
|||
stopCommentTargetLoop();
|
||||
}
|
||||
if (shareButton) {
|
||||
const canShowShare = !isGuestMode() && runtimeSessionInfo.canShare && activeModels.length > 0;
|
||||
const canShowShare = !guest && runtimeSessionInfo.canShare && activeModels.length > 0;
|
||||
shareButton.classList.toggle("hidden", !canShowShare);
|
||||
shareButton.disabled = !canShowShare;
|
||||
}
|
||||
if (dropZone) {
|
||||
dropZone.style.pointerEvents = isGuestMode() ? "none" : "";
|
||||
}
|
||||
if (sectionToolbar && isGuestMode()) {
|
||||
sectionToolbar.classList.add("hidden");
|
||||
dropZone.style.pointerEvents = guest ? "none" : "";
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1646,6 +1746,7 @@ const initializeRuntimeMode = async () => {
|
|||
const shareToken = getShareTokenFromPath();
|
||||
if (shareToken) {
|
||||
viewerMode = "guest";
|
||||
inspectorCollapsed = true;
|
||||
try {
|
||||
const response = await fetch(`${SHARES_API}/${encodeURIComponent(shareToken)}`, {
|
||||
credentials: "include",
|
||||
|
|
@ -1755,11 +1856,11 @@ const copyShareLink = async () => {
|
|||
const getDisplayModePreset = (mode) => {
|
||||
switch (mode) {
|
||||
case "clay":
|
||||
return { colorize: [0.86, 0.86, 0.82], opacity: 1, edges: false, xrayed: false };
|
||||
return { colorize: [0.76, 0.35, 0.25], opacity: 1, edges: false, xrayed: false };
|
||||
case "white":
|
||||
return { colorize: [1, 1, 1], opacity: 1, edges: false, xrayed: false };
|
||||
case "contrast":
|
||||
return { colorize: [0.78, 0.82, 1], opacity: 1, edges: false, xrayed: false };
|
||||
case "custom":
|
||||
return { colorize: getCustomDisplayColorize(), opacity: 1, edges: false, xrayed: false };
|
||||
case "edges":
|
||||
return { colorize: null, opacity: 1, edges: true, xrayed: false };
|
||||
case "xray":
|
||||
|
|
@ -1960,6 +2061,7 @@ const applyLightingMode = ({ announce = false } = {}) => {
|
|||
});
|
||||
break;
|
||||
case "technical":
|
||||
default:
|
||||
createSceneLight("ambient", { color: [1.0, 1.0, 1.0], intensity: 0.62 });
|
||||
createSceneLight("dir", {
|
||||
dir: [0.9, -0.45, -0.35],
|
||||
|
|
@ -1981,10 +2083,6 @@ const applyLightingMode = ({ announce = false } = {}) => {
|
|||
scene.sao.numSamples = 20;
|
||||
}
|
||||
break;
|
||||
case "ambient":
|
||||
default:
|
||||
createSceneLight("ambient", { color: [1.0, 1.0, 1.0], intensity: 0.92 });
|
||||
break;
|
||||
}
|
||||
|
||||
scene.glRedraw?.();
|
||||
|
|
@ -2706,7 +2804,7 @@ const positionSectionToolbar = () => {
|
|||
};
|
||||
|
||||
const setSectionModeActive = (active) => {
|
||||
sectionModeActive = !!active && !!sectionPlanesPlugin && getSectionPlaneIds().length > 0 && !isGuestMode();
|
||||
sectionModeActive = !!active && !!sectionPlanesPlugin && getSectionPlaneIds().length > 0;
|
||||
if (sectionModeActive) {
|
||||
positionSectionToolbar();
|
||||
}
|
||||
|
|
@ -2721,7 +2819,7 @@ const setSectionModeActive = (active) => {
|
|||
};
|
||||
|
||||
const updateSectionControls = () => {
|
||||
const available = !!sectionPlanesPlugin && !isGuestMode() && activeModels.length > 0;
|
||||
const available = !!sectionPlanesPlugin && activeModels.length > 0;
|
||||
if (sectionButton) {
|
||||
sectionButton.disabled = !available;
|
||||
sectionButton.classList.toggle("disabled", !available);
|
||||
|
|
@ -3386,6 +3484,7 @@ const parseInputNumber = (val) => {
|
|||
};
|
||||
|
||||
const persistTransform = (modelId, objectId, transform) => {
|
||||
if (isGuestMode()) return;
|
||||
if (!modelId || !transform) return;
|
||||
if (!transformStore[modelId]) transformStore[modelId] = {};
|
||||
const key = objectId || "__model__";
|
||||
|
|
@ -4299,6 +4398,8 @@ const buildProjectSnapshot = async (name) => {
|
|||
edges: !!toggleEdges?.checked,
|
||||
addMode: !!addMode?.checked,
|
||||
displayMode: activeDisplayMode,
|
||||
customDisplayColor: activeCustomDisplayColor,
|
||||
customDisplaySaturation: activeCustomDisplaySaturation,
|
||||
lightingMode: activeLightingMode,
|
||||
navigationAxis: activeNavigationAxis,
|
||||
zoomSpeed: activeZoomSpeed,
|
||||
|
|
@ -4329,7 +4430,11 @@ const buildProjectSnapshot = async (name) => {
|
|||
};
|
||||
|
||||
const applyDesignState = (design) => {
|
||||
if (!design || !settingsMenu?.applyState) return;
|
||||
if (!design || typeof design !== "object") return;
|
||||
if (!settingsMenu?.applyState) {
|
||||
pendingDesignState = { ...design };
|
||||
return;
|
||||
}
|
||||
settingsMenu.applyState(design);
|
||||
applyToolbarVars(settingsMenu?.getState?.());
|
||||
updateToolbarDockMode();
|
||||
|
|
@ -4364,6 +4469,8 @@ const buildModelViewerSettings = () => ({
|
|||
transparent: !!toggleTransparent?.checked,
|
||||
edges: !!toggleEdges?.checked,
|
||||
displayMode: activeDisplayMode,
|
||||
customDisplayColor: activeCustomDisplayColor,
|
||||
customDisplaySaturation: activeCustomDisplaySaturation,
|
||||
lightingMode: activeLightingMode,
|
||||
navigationAxis: activeNavigationAxis,
|
||||
zoomSpeed: activeZoomSpeed
|
||||
|
|
@ -4394,9 +4501,18 @@ const applyModelViewerSettings = (settings, { applyCamera = true, applyDesign =
|
|||
toggleEdges.checked = viewerState.edges;
|
||||
suppressEdgeReload = false;
|
||||
}
|
||||
setCustomDisplaySettings({
|
||||
color: viewerState.customDisplayColor,
|
||||
saturation: viewerState.customDisplaySaturation,
|
||||
});
|
||||
if (typeof viewerState.displayMode === "string") {
|
||||
activeDisplayMode = viewerState.displayMode;
|
||||
applyDisplayMode();
|
||||
} else if (viewerState.customDisplayColor !== undefined || viewerState.customDisplaySaturation !== undefined) {
|
||||
setCustomDisplayControlsState();
|
||||
if (normalizeDisplayMode(activeDisplayMode) === "custom") {
|
||||
applyDisplayMode();
|
||||
}
|
||||
}
|
||||
if (typeof viewerState.lightingMode === "string") {
|
||||
activeLightingMode = viewerState.lightingMode;
|
||||
|
|
@ -4491,9 +4607,18 @@ const applyViewerState = (state) => {
|
|||
if (typeof state.addMode === "boolean" && addMode) {
|
||||
addMode.checked = state.addMode;
|
||||
}
|
||||
setCustomDisplaySettings({
|
||||
color: state.customDisplayColor,
|
||||
saturation: state.customDisplaySaturation,
|
||||
});
|
||||
if (typeof state.displayMode === "string") {
|
||||
activeDisplayMode = state.displayMode;
|
||||
applyDisplayMode();
|
||||
} else if (state.customDisplayColor !== undefined || state.customDisplaySaturation !== undefined) {
|
||||
setCustomDisplayControlsState();
|
||||
if (normalizeDisplayMode(activeDisplayMode) === "custom") {
|
||||
applyDisplayMode();
|
||||
}
|
||||
}
|
||||
if (typeof state.lightingMode === "string") {
|
||||
activeLightingMode = state.lightingMode;
|
||||
|
|
@ -4558,6 +4683,8 @@ const initViewer = async () => {
|
|||
if (headerEl && homeButton && !homeButton.querySelector(".ue-logo")) {
|
||||
const logoEl = createLogo();
|
||||
homeButton.appendChild(logoEl);
|
||||
homeButton.title = "Открыть NODE.DC Hub";
|
||||
homeButton.setAttribute("aria-label", "Открыть NODE.DC Hub");
|
||||
if (logoEl.style.width) homeButton.style.width = logoEl.style.width;
|
||||
if (logoEl.style.height) homeButton.style.height = logoEl.style.height;
|
||||
homeButton.style.border = "none";
|
||||
|
|
@ -5192,8 +5319,15 @@ const initViewer = async () => {
|
|||
activeDisplayMode = params.get("displayMode") || "source";
|
||||
setDisplayModeControlState();
|
||||
}
|
||||
setCustomDisplaySettings({
|
||||
color: params.has("customDisplayColor") ? params.get("customDisplayColor") : undefined,
|
||||
saturation: params.has("customDisplaySaturation") ? params.get("customDisplaySaturation") : undefined,
|
||||
});
|
||||
if ((params.has("customDisplayColor") || params.has("customDisplaySaturation")) && normalizeDisplayMode(activeDisplayMode) === "custom") {
|
||||
applyDisplayMode();
|
||||
}
|
||||
if (params.has("lightingMode") || params.has("light")) {
|
||||
activeLightingMode = params.get("lightingMode") || params.get("light") || "ambient";
|
||||
activeLightingMode = params.get("lightingMode") || params.get("light") || "technical";
|
||||
applyLightingMode();
|
||||
}
|
||||
if (params.has("navigationAxis") || params.has("axis")) {
|
||||
|
|
@ -5582,6 +5716,11 @@ const loadProjectSnapshot = async (project) => {
|
|||
setToolbarButtonActive(settingsButton, false);
|
||||
}
|
||||
});
|
||||
if (pendingDesignState) {
|
||||
const design = pendingDesignState;
|
||||
pendingDesignState = null;
|
||||
applyDesignState(design);
|
||||
}
|
||||
// ensure persisted settings are applied to cube/highlight on first load
|
||||
applyToolbarVars(settingsMenu?.getState?.());
|
||||
updateToolbarDockMode();
|
||||
|
|
@ -5603,6 +5742,40 @@ const loadProjectSnapshot = async (project) => {
|
|||
});
|
||||
setDisplayModeControlState();
|
||||
}
|
||||
if (customDisplayColorControlHost) {
|
||||
customDisplayColorControl = createColorControl({
|
||||
value: activeCustomDisplayColor,
|
||||
ariaLabel: "Цвет кастомного режима",
|
||||
valueLabel: "HEX цвет кастомного режима",
|
||||
onChange: (value) => {
|
||||
setCustomDisplaySettings({ color: value });
|
||||
if (normalizeDisplayMode(activeDisplayMode) === "custom") {
|
||||
applyDisplayMode();
|
||||
}
|
||||
},
|
||||
});
|
||||
customDisplayColorControlHost.replaceChildren(customDisplayColorControl);
|
||||
}
|
||||
if (customDisplaySaturationControl) {
|
||||
customDisplaySaturationSlider = createSliderControl({
|
||||
label: "Насыщенность",
|
||||
value: activeCustomDisplaySaturation,
|
||||
min: 0,
|
||||
max: 200,
|
||||
step: 1,
|
||||
className: "inspector-slider",
|
||||
showValue: true,
|
||||
formatValue: (value) => `${Math.round(value)}%`,
|
||||
onChange: (value) => {
|
||||
setCustomDisplaySettings({ saturation: value });
|
||||
if (normalizeDisplayMode(activeDisplayMode) === "custom") {
|
||||
applyDisplayMode();
|
||||
}
|
||||
},
|
||||
});
|
||||
customDisplaySaturationControl.replaceChildren(customDisplaySaturationSlider);
|
||||
}
|
||||
setCustomDisplayControlsState();
|
||||
if (lightingModeControl && lightingModeSelectHost) {
|
||||
lightingModeSelect = createGlassSelect({
|
||||
host: lightingModeSelectHost,
|
||||
|
|
@ -5846,23 +6019,7 @@ const loadProjectSnapshot = async (project) => {
|
|||
});
|
||||
|
||||
homeButton.addEventListener("click", () => {
|
||||
destroyAllModels();
|
||||
dropZone.classList.remove("hidden");
|
||||
setStatus("Готов к загрузке");
|
||||
modelCounter = 0;
|
||||
setLoading(false);
|
||||
memoryStore.clear();
|
||||
currentSelection = null;
|
||||
updateTransformInputs(null);
|
||||
templatesMenu?.hide();
|
||||
setMeasureActive(false);
|
||||
clearMeasurements();
|
||||
closeSaveModal();
|
||||
closeDeleteModal();
|
||||
// Примеры доступны, но сцена чистая
|
||||
gizmo?.clearTarget();
|
||||
updateNavCubeVisibility();
|
||||
updatePanelsVisibility();
|
||||
window.location.assign(getLauncherUrl());
|
||||
});
|
||||
|
||||
if (saveProjectButton) {
|
||||
|
|
@ -6190,8 +6347,9 @@ const loadProjectSnapshot = async (project) => {
|
|||
bottomToolbar.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest(".tb-btn");
|
||||
if (!btn) return;
|
||||
if (isGuestMode()) return;
|
||||
if (btn.disabled) return;
|
||||
const action = btn.dataset.action;
|
||||
if (isGuestMode() && !isGuestToolbarAction(action)) return;
|
||||
if (action === "settings") {
|
||||
if (btn.classList.contains("active")) {
|
||||
closeSettingsPanel();
|
||||
|
|
@ -6222,7 +6380,6 @@ const loadProjectSnapshot = async (project) => {
|
|||
}
|
||||
|
||||
sectionToolbar?.addEventListener("click", (e) => {
|
||||
if (isGuestMode()) return;
|
||||
const btn = e.target.closest("[data-section-action]");
|
||||
if (!btn) return;
|
||||
const action = btn.dataset.sectionAction;
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
|
|
@ -4,35 +4,40 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>BIM DC Viewer</title>
|
||||
<link rel="icon" href="/assets/logo.svg">
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/favicon/icon-adaptive.svg">
|
||||
<link rel="alternate icon" href="/assets/favicon/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/assets/favicon/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/assets/favicon/manifest.webmanifest.json">
|
||||
<style id="preload-hidden">body{opacity:0;}</style>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700&display=swap">
|
||||
<script src="/theme.js?v=2"></script>
|
||||
<script src="/theme.js?v=3"></script>
|
||||
<script>
|
||||
// Apply persisted theme ASAP to avoid initial flash of defaults
|
||||
(function () {
|
||||
const STORAGE_KEY = "bimdc-settings-v3";
|
||||
const LEGACY_KEYS = ["bimdc-settings", "bimdc-settings-v2"];
|
||||
try {
|
||||
LEGACY_KEYS.forEach((k) => localStorage.removeItem(k));
|
||||
|
||||
const project = window.__BIMDC_THEME__ || {};
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
const sharedViewer = /^\/share\/[^/]+\/?$/.test(window.location.pathname || "");
|
||||
let stored = null;
|
||||
try {
|
||||
stored = raw ? JSON.parse(raw) : null;
|
||||
} catch (e) {
|
||||
stored = null;
|
||||
if (!sharedViewer) {
|
||||
LEGACY_KEYS.forEach((k) => localStorage.removeItem(k));
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
try {
|
||||
stored = raw ? JSON.parse(raw) : null;
|
||||
} catch (e) {
|
||||
stored = null;
|
||||
}
|
||||
const storedVersion = stored && stored.themeVersion;
|
||||
const projectVersion = project && project.themeVersion;
|
||||
if (!stored || storedVersion !== projectVersion) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(project));
|
||||
stored = { ...project };
|
||||
}
|
||||
}
|
||||
const storedVersion = stored && stored.themeVersion;
|
||||
const projectVersion = project && project.themeVersion;
|
||||
if (!stored || storedVersion !== projectVersion) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(project));
|
||||
stored = { ...project };
|
||||
}
|
||||
const s = { ...(project || {}), ...(stored || {}) };
|
||||
const s = { ...(project || {}), ...(!sharedViewer && stored ? stored : {}) };
|
||||
if (!Object.keys(s).length) return;
|
||||
const root = document.documentElement;
|
||||
const set = (k, v) => v !== undefined && root.style.setProperty(k, v);
|
||||
|
|
@ -203,11 +208,11 @@
|
|||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<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 rel="stylesheet" href="/dcViewer.css?v=36">
|
||||
<link rel="stylesheet" href="/dcViewer.css?v=37">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<button class="logo-btn" id="homeButton" title="Вернуться к старту"></button>
|
||||
<button class="logo-btn" id="homeButton" type="button" title="Открыть NODE.DC Hub"></button>
|
||||
<div class="toolbar"></div>
|
||||
<div class="hidden-controls" style="display:none">
|
||||
<input type="checkbox" id="toggleEdges" checked>
|
||||
|
|
@ -263,10 +268,15 @@
|
|||
<div class="inspector-control-label">Режим отображения</div>
|
||||
<div class="inspector-select-host" id="displayModeSelect"></div>
|
||||
</div>
|
||||
<div class="inspector-control-row inspector-control-row--half hidden" id="customDisplayColorRow">
|
||||
<div class="inspector-control-label">Цвет кастома</div>
|
||||
<div class="inspector-color-host" id="customDisplayColorControl"></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 inspector-control-row--slider hidden" id="customDisplaySaturationControl"></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>
|
||||
|
|
@ -641,6 +651,6 @@
|
|||
<input id="fileInput" class="hidden" type="file"
|
||||
accept=".xkt,.glb,.gltf,.bim,.las,.laz,.obj,.stl">
|
||||
|
||||
<script type="module" src="/dcViewer.js?v=50"></script>
|
||||
<script type="module" src="/dcViewer.js?v=55"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { createSliderControl } from "./controls/slider.js";
|
|||
import { createColorControl } from "./controls/color.js";
|
||||
|
||||
const defaultSettings = {
|
||||
themeVersion: "v6-comments-targets",
|
||||
themeVersion: "v7-toolbar-hub-icon",
|
||||
bgOuter: "#0f0f0f",
|
||||
canvasTop: "#1c1c1c",
|
||||
canvasBottom: "#1c1c1c",
|
||||
|
|
@ -25,8 +25,8 @@ const defaultSettings = {
|
|||
toolbarBorder: "#292929",
|
||||
toolbarOutline: "#292929",
|
||||
toolbarMinSize: 25,
|
||||
toolbarMaxSize: 56,
|
||||
toolbarLensCount: 7,
|
||||
toolbarMaxSize: 87,
|
||||
toolbarLensCount: 5,
|
||||
toolbarAutoHide: false,
|
||||
modalBg: "#1c1c1c",
|
||||
modalBgAlpha: 1,
|
||||
|
|
@ -72,6 +72,14 @@ const defaultSettings = {
|
|||
|
||||
const STORAGE_KEY = "bimdc-settings-v3";
|
||||
const LEGACY_KEYS = ["bimdc-settings", "bimdc-settings-v2"];
|
||||
const isSharedViewerPath = () => (
|
||||
typeof window !== "undefined" &&
|
||||
/^\/share\/[^/]+\/?$/.test(window.location.pathname || "")
|
||||
);
|
||||
const isReadonlySettingsContext = () => (
|
||||
isSharedViewerPath() ||
|
||||
(typeof document !== "undefined" && document.body?.dataset?.viewerMode === "guest")
|
||||
);
|
||||
const projectSettings = typeof window !== "undefined" && window.__BIMDC_THEME__
|
||||
? { ...defaultSettings, ...window.__BIMDC_THEME__ }
|
||||
: { ...defaultSettings };
|
||||
|
|
@ -278,6 +286,7 @@ const applySettings = (s) => {
|
|||
};
|
||||
|
||||
const saveSettings = (s) => {
|
||||
if (isReadonlySettingsContext()) return;
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(s));
|
||||
} catch (e) {
|
||||
|
|
@ -286,6 +295,9 @@ const saveSettings = (s) => {
|
|||
};
|
||||
|
||||
const loadSettings = () => {
|
||||
if (isReadonlySettingsContext()) {
|
||||
return { ...projectSettings };
|
||||
}
|
||||
try {
|
||||
// purge legacy keys so старые палитры не мешали
|
||||
LEGACY_KEYS.forEach((k) => localStorage.removeItem(k));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const toolbarConfig = {
|
||||
minSize: 25,
|
||||
maxSize: 56,
|
||||
lensCount: 7,
|
||||
maxSize: 87,
|
||||
lensCount: 5,
|
||||
autoHide: false,
|
||||
size: 25,
|
||||
gap: 10,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Базовые цвета/настройки проекта. При очистке localStorage эти значения остаются.
|
||||
// Чтобы изменить палитру по умолчанию — правь этот объект и закоммить изменения.
|
||||
const PROJECT_THEME = {
|
||||
themeVersion: "v6-comments-targets",
|
||||
themeVersion: "v7-toolbar-hub-icon",
|
||||
bgOuter: "#0f0f0f",
|
||||
canvasTop: "#1c1c1c",
|
||||
canvasBottom: "#1c1c1c",
|
||||
|
|
@ -18,8 +18,8 @@ const PROJECT_THEME = {
|
|||
toolbarBorder: "#292929",
|
||||
toolbarOutline: "#292929",
|
||||
toolbarMinSize: 25,
|
||||
toolbarMaxSize: 56,
|
||||
toolbarLensCount: 7,
|
||||
toolbarMaxSize: 87,
|
||||
toolbarLensCount: 5,
|
||||
toolbarAutoHide: false,
|
||||
modalBg: "#1c1c1c",
|
||||
modalBgAlpha: 1,
|
||||
|
|
|
|||
Loading…
Reference in New Issue