diff --git a/css/base.css b/css/base.css index 600250e7..3c188cad 100644 --- a/css/base.css +++ b/css/base.css @@ -1590,11 +1590,18 @@ font-size: 1.3rem; } -.organization-department.expanded { +/* Renamed from .expanded -- this rule drives the blue "this is my department" highlight from + docs/images/部门组织框架.png (only the department the current user directly belongs to, e.g. + "技术4人" in the reference, not every expanded node -- the reference's always-expanded root + "总裁办50人" stays plain black). Was keyed off the same class as the expand/collapse chevron + state, which colored ANY expanded department blue including the root, on every page load, with + nothing to do with where the current user actually is -- see workplace-collaboration-view.jsx's + DeptRow/DeptNode comments for the live repro (via stub data, this tenant's real org tree is empty). */ +.organization-department.me { color: #2094f3; } -.organization-department.expanded > .material-icons:first-child { +.organization-department.me > .material-icons:first-child { color: #2094f3; } diff --git a/src/widgets/call-panel.jsx b/src/widgets/call-panel.jsx index 55fef792..83717ba2 100644 --- a/src/widgets/call-panel.jsx +++ b/src/widgets/call-panel.jsx @@ -69,6 +69,7 @@ class CallPanel extends React.PureComponent { this.isOutgoingCall = props.callState == CALL_STATE_OUTGOING_INITATED; this.containerRef = React.createRef(); + this.selfBoxRef = React.createRef(); this.localRef = React.createRef(); this.remoteRef = React.createRef(); @@ -125,6 +126,10 @@ class CallPanel extends React.PureComponent { this.handleDataChannelClose = this.handleDataChannelClose.bind(this); this.bounds = {}; + // Which element the in-progress drag is moving: 'container' for the whole minimized bubble, + // 'self' for just the local self-view preview box inside a fullscreen connected video call. + // Null when no drag is in progress (also doubles as the pointermove/pointerup no-op guard). + this.dragTarget = null; this.dragStart = (event) => { if (!event.target.closest('.draggable')) { @@ -152,28 +157,73 @@ class CallPanel extends React.PureComponent { const parent = el.offsetParent; this.bounds.maxX = parent.offsetWidth; this.bounds.maxY = parent.offsetHeight; + this.dragTarget = 'container'; + return; + } + // Fullscreen connected video call: let the user drag their own small self-view preview + // out of the way instead of it being stuck wherever it defaults to (spec 8.9 "双方视频 + // 通话中": "小窗应支持拖动且不能遮挡关键按钮"). Only relevant when the box actually exists + // (base.css hides it entirely for audio-only calls / while minimized via + // .call-party.self[disabled]) and only when the drag actually starts on that box, not on + // the peer's video/avatar card behind it. + if (!this.props.minimized && !this.state.audioOnly && this.selfBoxRef.current && + event.target.closest('.call-party.self')) { + event.stopPropagation(); + const el = this.selfBoxRef.current; + // Pointer events are captured on the outer container (same element the listeners below + // are attached to) rather than the box itself, purely so drag()/dragEnd() below have one + // consistent place to check hasPointerCapture() regardless of which element is moving. + this.containerRef.current.setPointerCapture(event.pointerId); + el.style.cursor = 'grabbing'; + this.bounds.left = el.offsetLeft; + this.bounds.top = el.offsetTop; + this.bounds.width = el.offsetWidth; + this.bounds.height = el.offsetHeight; + // Switch from right/bottom CSS positioning to explicit left/top so drag() can move it freely. + el.style.right = 'auto'; + el.style.bottom = 'auto'; + el.style.left = `${this.bounds.left}px`; + el.style.top = `${this.bounds.top}px`; + + const parent = el.offsetParent; + this.bounds.maxX = parent.offsetWidth; + this.bounds.maxY = parent.offsetHeight; + this.dragTarget = 'self'; } }; this.drag = (event) => { event.stopPropagation(); - const el = this.containerRef.current; - if (this.props.minimized && this.containerRef && el.hasPointerCapture(event.pointerId)) { - // Calculate new position. Restrict to the visible area of the parent element. - this.bounds.left = Math.min(Math.max(this.bounds.left + event.movementX, 0), - this.bounds.maxX - this.bounds.width); - this.bounds.top = Math.min(Math.max(this.bounds.top + event.movementY, 0), - this.bounds.maxY - this.bounds.height); - - el.style.left = `${this.bounds.left}px`; - el.style.top = `${this.bounds.top}px`; + const container = this.containerRef.current; + if (!this.dragTarget || !container.hasPointerCapture(event.pointerId)) { + return; } + const el = this.dragTarget == 'self' ? this.selfBoxRef.current : container; + if (!el) { + return; + } + // Calculate new position. Restrict to the visible area of the parent element. + this.bounds.left = Math.min(Math.max(this.bounds.left + event.movementX, 0), + this.bounds.maxX - this.bounds.width); + this.bounds.top = Math.min(Math.max(this.bounds.top + event.movementY, 0), + this.bounds.maxY - this.bounds.height); + + el.style.left = `${this.bounds.left}px`; + el.style.top = `${this.bounds.top}px`; }; this.dragEnd = (event) => { event.stopPropagation(); - if (this.props.minimized && this.containerRef) { - this.containerRef.current.releasePointerCapture(event.pointerId); - this.containerRef.current.style.cursor = 'grab'; + const container = this.containerRef.current; + if (!this.dragTarget) { + return; } + const el = this.dragTarget == 'self' ? this.selfBoxRef.current : container; + if (container && container.hasPointerCapture(event.pointerId)) { + container.releasePointerCapture(event.pointerId); + } + if (el) { + el.style.cursor = 'grab'; + } + this.dragTarget = null; }; // dragStart's inline left/top/right (set the first time the minimized bubble is dragged, see // above) always win over base.css's #video-container.minimized:not(.audio-minimized) mobile @@ -183,14 +233,26 @@ class CallPanel extends React.PureComponent { // bubble pinned at its old desktop-width position/size instead of docking as the intended // mobile full-width strip. Clearing the manual offsets once the mobile breakpoint applies lets // the stylesheet rule take back over; harmless (a no-op) whenever nothing was ever dragged. + // Same idea for the self-view box's own drag above, keyed off its own (768px) breakpoint + // (base.css ".call-party.self" mobile rule only ever touches `bottom`, but a stale inline + // left/top from a desktop-width drag would still leave it sitting at the wrong height once + // that rule's extra bottom clearance was meant to apply). this.handleWindowResize = () => { - if (this.props.minimized && this.containerRef && this.containerRef.current && + if (this.props.minimized && this.containerRef.current && window.matchMedia('(width<=640px)').matches) { const el = this.containerRef.current; el.style.removeProperty('left'); el.style.removeProperty('right'); el.style.removeProperty('top'); } + if (!this.props.minimized && this.selfBoxRef.current && + window.matchMedia('(width<=768px)').matches) { + const el = this.selfBoxRef.current; + el.style.removeProperty('left'); + el.style.removeProperty('right'); + el.style.removeProperty('top'); + el.style.removeProperty('bottom'); + } }; } @@ -893,7 +955,8 @@ class CallPanel extends React.PureComponent { {connected &&