From f680d6bad20e67fd77f7ff664bd7c9ffe0186384 Mon Sep 17 00:00:00 2001 From: CODEX Date: Mon, 22 Jun 2026 23:05:15 +0300 Subject: [PATCH] Polish BIM shared viewer controls --- frontend/assets/favicon/apple-touch-icon.png | Bin 0 -> 4142 bytes frontend/assets/favicon/favicon.ico | Bin 0 -> 16958 bytes frontend/assets/favicon/icon-192.png | Bin 0 -> 4386 bytes frontend/assets/favicon/icon-adaptive.svg | 13 + .../assets/favicon/manifest.webmanifest.json | 29 ++ frontend/dcViewer.css | 77 +++++- frontend/dcViewer.js | 249 ++++++++++++++---- frontend/favicon.ico | Bin 0 -> 16958 bytes frontend/index.html | 48 ++-- frontend/src/ui/settingsMenu.js | 18 +- frontend/src/ui/toolbar.js | 4 +- frontend/theme.js | 6 +- 12 files changed, 367 insertions(+), 77 deletions(-) create mode 100644 frontend/assets/favicon/apple-touch-icon.png create mode 100644 frontend/assets/favicon/favicon.ico create mode 100644 frontend/assets/favicon/icon-192.png create mode 100644 frontend/assets/favicon/icon-adaptive.svg create mode 100644 frontend/assets/favicon/manifest.webmanifest.json create mode 100644 frontend/favicon.ico diff --git a/frontend/assets/favicon/apple-touch-icon.png b/frontend/assets/favicon/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5078c01653bd8fca4c6ff0d98769b86f54f17743 GIT binary patch literal 4142 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 literal 0 HcmV?d00001 diff --git a/frontend/assets/favicon/favicon.ico b/frontend/assets/favicon/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..973f3b2db97f00b686175edb7497021fe0f1421d GIT binary patch literal 16958 zcmeI4U5Hd=7{}jfidmD~6|IX*6f)Wnf+Q)(+Cdbxi;&QZK)Oj&H18Baw&oDs3EBsE z;e{-Scp-ux3t3&XQc8hzBS{xa4J68D#MTCGvCjDW&p9uw!#VG1XXmV^nfF=$=b7_9 z&v`!nXU==xGc#yRfxmLuuw8EY*BbMLF{U3dWCEb^hG1JXKSVg-x1IsdfM>un;2H1? zcm_NJo&nE*XTUQsM+|&Sp20cFmA5pjC};{yfE9TwF5`NN#Qz%9Bb?4SU*5v+oYuSL z;LW@hnNdAY5_c@7|53*JGRm2!u*YM1x9@>Nc`7u6S~d`~=IDPC+v*JR<{{*x6mbpM zbDg9w;iLJn)8qphO6s|Jd{2ipzmk6boBpM&OeoVI_W6Pl-oZ7uX} zx!L$X=iqVb|8*Aq|H!eA<&1+~>e&Hy0j=Yjv7504Kynn=a~Y?9!v1rLPk>#r7`z9X z^J!nTyaVQgtmQKj#PwpwhOz7ZwFcaE`0Cl`W$=c>M=>>TYW|!Dt^k`SPU{|%rMyOP z)g6~xV}F3Bz$4%)Nb=M85U_HA7cSo~Bgf~_cO1Ur>bc0b-KvLxX+7rbCkB-Qs1wwI!WXB>VyUhnlJNxQy}MF+l)PO3%YRj>%OLv&8R0m%^jz=LezuOr|$c%7~f(SSFOrP4s^vo z8wFm4)*RsSZKv%|a;=DRt4CLCany=z<{($xaqQ0k%@3DTd|h!`wb^;}MRMzNlcYF$ zmRtg6A`5X&yOuSNNwnTg%ANJNz4i{J)G&m7Ch`f+uG+N@DgmuqT->{2w8Qp0`85wN z0Iu_;`_5YMUt|qVQy{56*Pr z=WlBh2iv=-cX2QZ^f{J9YlBqZYX-?F&@DYr?^Dl5T03fOpx507J!|S7=EXDM z8So5v20R0v0ndPEz%$?(@CcG94$rVuPA(nwDs=p)jIDe|vg5Bf^g9wO-fMtE4d zA@!Om>l@8V%>?=tO+{b!pa&)!zo-cY^ko;aOlg!TEV9~L)h^$OjWm=g1)(lR+WsEL N>d?wIY~{gi?*Ti^k*5Fv literal 0 HcmV?d00001 diff --git a/frontend/assets/favicon/icon-192.png b/frontend/assets/favicon/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..d553a58e81dd92630500cf4bdc2f104993412841 GIT binary patch literal 4386 zcmbVQhg%cN*WQFkM=4ST!Eh;3F1>eYf*{3&Dn*JB1St}vN(A8s0$AwMK|z8yE!S zJuUdOp7o&vD_XZ3#saatGz9=yi6#bmh>)Dkxp4p6ztMDC)TZW_PwbRmzi|zH z30bms@UGmWILOMDU9-OXY9#BX!jPGm=h&CjS{9j^%>UwJLg|=4ix#FDUx%g`f8^Z} z8dvsO#K*#In)eAkgt2SB*Te~I<~~{-)oUmBk3cXv`H;2i8^LYPYV0TMmxg zEzG2^Mi;Q+dirZY>pmzV!HEvx>0WHCpDNX=l|Q_%0g#Sk-$Pe@x3Jik)hK`ett)`P z=wSGWXzr+xS4*m@sxCp8 z|IrpL@<1RY?>~D?%7jfdJtRBg8>Oeg5ezTyMn;iGpQ(Nqw6DGQT^|UJ6BK*35;x|B z8p}R@4O^v9{w=4qg^U=)8K%;=N`r3wCzzWGfUceHVjk28UbKSmF`qww>Wk3#fC9Se zxozh=H16udO?yW@hb!?Zq&>=CmI$EBifof80zjEpEWTc#WC za8sjm^ppHly5wviGBUCl|50f>IbQtgP9{0DIeDL>#~c8*BF}m-r{ZmTY$EXL4PIqW zsF@uNX=I>VKlSx$HEOEXCVF$Gf!gs>WK%4Gq^}^z^P)*BCMG7TD(dXC522$vCkb## z-D_g%kU=R*6~Xx5gM)0sR@c^etQKS?+2j~c1*fMzGF-&zp<($I@xLhhunUB2isFB5guovWmP@B!G-*q>ZQbI{w9aB zwYBvR`vZ!9^|X&Or+xA1e`9N3r^n0w@GHCxgTJvZGY9r2c4xi3D-h9xaVNJv-6ozS ze#Z%pONYlz)LogJZw&%S6WzS``-kIK7JUMoh&wg3BBYT*{RY6!ZK56jF82XwNPz(b9Qlp+ zhd%KA`4`i!ypf;SL7@J zj5du_Eh^7B^Lv$cV}oW4Ykg3?ly4(kat2}gh0jh^GQ z@-0i91zdaOwUMRkvtNnC{osRb0)bGwv})5iLZj>7Z>EtI00la#JyDvBuEzY4$QRI@ z-m`&ggj#TUiX#!G;q0)DzK4GahO?J-YnoNL` zO7(`itgOEdW-hqu>Zcd*-q#CM>>wE~`r*y)nTrXuOi&%u_Wjyod6_8$FAgR2A55L~v<#GfMgG8Fc1y|W2ZT)m< z=N8pKMx!b_5b`MHM00AW27RB3u~JY_(COs@+o{+*`)LKKNmws=_XOW4IixL?GTcnK zbod5z)dBFFIe_)fy|G@4Dt6#6tv-h~tIjETg$lnZG6@3!wmC*p`yypu+!xNCK2f6q>~jxJ~Z(A}EyqHlkX z1#idW{)62t{5vXzl2@z!XZGQ+FDC;4CgTw*aO-| zaMjxBDT;|&`6oCkcV3dW>i4H91e)cB3Mh`Wr`nfyN0dSK4hwUG4WcWS@blCjG4m;w zsU2D{EU~AFv`T6_qik$wvW#RiyHk02ctWO%+-qQ4np8A$7BzA9bda3;X#V)qh2Tez zG6amJxHks$kM4!|oe1}>kemVB0KCU;kGZA=>iRJ{eGqtjhg&bbzP8rj->-u0ES)-2 zU3nJE0Dd+KCC|$}($C`FFcu6FtNY_1q6E9ofNEWM!m1IdtCcAb^OrV7Ka*(J}|I ziT?*fJ#I#B7kDNLjZ~D|zR#=eR?8p0fONFb?GcM6N3?HQj8+WM0)mQ(zSeupH&z(| zer?S_x`J>P{dco9Z&dxa?z;Y(S>|%nksW!^g^)2hH?Hj%A4c z?zib~RR>f|1o7X!+i*X~=t2pqW*P@Xe3IP@UfAc*c=pMA(ixd7Y2a zbuIRPPd61o>q{Wpm^z4S;s?WP`#mi0$DVxu4{oudHknzapTK@}&`efB;!fIC@Yc zxaF5aQ1-46#-@A<|76-{xJ*$VLOO0MP4JsC9JvLM_ug_1??&k6SL0Z)u92cCS!-I& zW-Wkpl&xQ4aDD;KCG8e3Za#UvT^K9A^81uFD6 zZJW+v6pB5gK{Nva?k>f=>+r@qJxF(@4b3*4CC1Fr<`tg8lv1e@zJB zI$f=B8Oqx9Oq}mrT%~CwSm>BOWko2^+9O##GkHw{46txhE`u4-f=KCVRFkr zU0wa=xz9VtFCoa*iOMrCuLKy+dyVq7$pm;?^+l_0Sq}*220}YWJR54X%$e58Ql+W zr`ug}Ss@_HZOp@)b&+zp{bkI9>7nqWWPZFQa9`pTIFkpVpAz;jACV=f@ zTyk>q-rIVi>-JQM{Zk=&xQqXtX`lKVp94Ql9}L(J@4_#r2j#d*u50Q%Q0KD;yW{^# zf*%mra0eTkoO0xb&@W3pQ!9AJ3&KF?TM6trv1|(ay`G9+YdZqA);l_Mm!Tp=v@OkQ zHPhTwHOlUm2;jf0e^qpFIdOD_@H5y*skXlUF{I-?i+ibL+ERO?OXTkfNg!TorrQwq zvuOT_n6|KeUfswR*mqn+*TrQ)ynx6Prz&qR9cZhjE8MP%lWrsx6MjC{#QR3bJh`(G7GpQtPm2jTVv;l{p$$a-;1HX^d$P0r0ZorlUv>p<<=Lbey( zMKi!?B^kf=RN?I+72itJP@6bM<63xU!#kWw2{A6JTWA@=`TQ7UY-itUhcd)L)+Q%C z;Bo8UjPOTc+XmC)TbBN6LPZURbsq9rObBzanN978kk?m1LN?C^fRS&n+AfU{-yP}9 zdnh5Dm}Z<-xXO~-g`9{djr4Httr$*+yvra6GP z{F#u0lvqk}wG$Gq757+`jw1TxoQySRz*er;*6eDYjfY>-y_=gyRYf}#FEYk*Be#P< zyW+<}*qnU>>K&-rts6Ji!+7#Sxm1wTq|g_jsntM9%a9Tn+6Xv&sBPseQ4n#zwR2Lm z1}$!iw#D$(*4EB4kus7l_goBGyeHl-?ec?JRF@?zEX>HDvT$@sRr@fRdOWNWcCqFh zM}bk7+qMQAJF8vEkO>KYpI8HEBnrG>+3 z>FBgu!F0le?%U|EZAR9V(b3Lq;Gg_LDv= zs$gYPwyFq#U`J2v*9=}@eo}YK_-<%-}h+>|^N188bFBiS9sMy@jJkHtA1dPrL zsE2T4dWEs;SEB3Y=H}9kRR?dF0m5K9m{n)`YCb{pL2SofqS-UY_sj|#`C{~sE3IRuI~`*n~+~KXzf;=G}F0DKQXI3W*As% ze}zg>veSxzzrJ?9pga$|Imu@P_G`agZg##+k%iPb9+e)FXWPPed(AV!7W1^WzJyx- zy-_Szb+C$|!8yw@6JS{Tn{}Z}(9C5LZEK-R0*ffOL0vE4%#(>UGDu z3OssrgR8%QWn^LD>}L4!OL?J;%fGjn@@$u%1T!pg76i00lUCw&= + + + + \ No newline at end of file diff --git a/frontend/assets/favicon/manifest.webmanifest.json b/frontend/assets/favicon/manifest.webmanifest.json new file mode 100644 index 0000000..f7256e8 --- /dev/null +++ b/frontend/assets/favicon/manifest.webmanifest.json @@ -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" + } + ] +} diff --git a/frontend/dcViewer.css b/frontend/dcViewer.css index c7c3012..b5f8709 100644 --- a/frontend/dcViewer.css +++ b/frontend/dcViewer.css @@ -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; } diff --git a/frontend/dcViewer.js b/frontend/dcViewer.js index 3a3c2e3..f009d5a 100644 --- a/frontend/dcViewer.js +++ b/frontend/dcViewer.js @@ -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; diff --git a/frontend/favicon.ico b/frontend/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..973f3b2db97f00b686175edb7497021fe0f1421d GIT binary patch literal 16958 zcmeI4U5Hd=7{}jfidmD~6|IX*6f)Wnf+Q)(+Cdbxi;&QZK)Oj&H18Baw&oDs3EBsE z;e{-Scp-ux3t3&XQc8hzBS{xa4J68D#MTCGvCjDW&p9uw!#VG1XXmV^nfF=$=b7_9 z&v`!nXU==xGc#yRfxmLuuw8EY*BbMLF{U3dWCEb^hG1JXKSVg-x1IsdfM>un;2H1? zcm_NJo&nE*XTUQsM+|&Sp20cFmA5pjC};{yfE9TwF5`NN#Qz%9Bb?4SU*5v+oYuSL z;LW@hnNdAY5_c@7|53*JGRm2!u*YM1x9@>Nc`7u6S~d`~=IDPC+v*JR<{{*x6mbpM zbDg9w;iLJn)8qphO6s|Jd{2ipzmk6boBpM&OeoVI_W6Pl-oZ7uX} zx!L$X=iqVb|8*Aq|H!eA<&1+~>e&Hy0j=Yjv7504Kynn=a~Y?9!v1rLPk>#r7`z9X z^J!nTyaVQgtmQKj#PwpwhOz7ZwFcaE`0Cl`W$=c>M=>>TYW|!Dt^k`SPU{|%rMyOP z)g6~xV}F3Bz$4%)Nb=M85U_HA7cSo~Bgf~_cO1Ur>bc0b-KvLxX+7rbCkB-Qs1wwI!WXB>VyUhnlJNxQy}MF+l)PO3%YRj>%OLv&8R0m%^jz=LezuOr|$c%7~f(SSFOrP4s^vo z8wFm4)*RsSZKv%|a;=DRt4CLCany=z<{($xaqQ0k%@3DTd|h!`wb^;}MRMzNlcYF$ zmRtg6A`5X&yOuSNNwnTg%ANJNz4i{J)G&m7Ch`f+uG+N@DgmuqT->{2w8Qp0`85wN z0Iu_;`_5YMUt|qVQy{56*Pr z=WlBh2iv=-cX2QZ^f{J9YlBqZYX-?F&@DYr?^Dl5T03fOpx507J!|S7=EXDM z8So5v20R0v0ndPEz%$?(@CcG94$rVuPA(nwDs=p)jIDe|vg5Bf^g9wO-fMtE4d zA@!Om>l@8V%>?=tO+{b!pa&)!zo-cY^ko;aOlg!TEV9~L)h^$OjWm=g1)(lR+WsEL N>d?wIY~{gi?*Ti^k*5Fv literal 0 HcmV?d00001 diff --git a/frontend/index.html b/frontend/index.html index d4d290d..de4e331 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,35 +4,40 @@ BIM DC Viewer - + + + + - + + diff --git a/frontend/src/ui/settingsMenu.js b/frontend/src/ui/settingsMenu.js index 6b8849a..9d0471b 100644 --- a/frontend/src/ui/settingsMenu.js +++ b/frontend/src/ui/settingsMenu.js @@ -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)); diff --git a/frontend/src/ui/toolbar.js b/frontend/src/ui/toolbar.js index 6abe865..b59a17b 100644 --- a/frontend/src/ui/toolbar.js +++ b/frontend/src/ui/toolbar.js @@ -1,7 +1,7 @@ const toolbarConfig = { minSize: 25, - maxSize: 56, - lensCount: 7, + maxSize: 87, + lensCount: 5, autoHide: false, size: 25, gap: 10, diff --git a/frontend/theme.js b/frontend/theme.js index b67a9df..10a8bd4 100644 --- a/frontend/theme.js +++ b/frontend/theme.js @@ -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,