diff options
| author | haoyuren <13851610112@163.com> | 2026-03-15 18:47:29 -0500 |
|---|---|---|
| committer | haoyuren <13851610112@163.com> | 2026-03-15 18:47:29 -0500 |
| commit | a36b73505754acab6872d23c9add58dfca3cecdd (patch) | |
| tree | 2c750e7710fa828a277ee7db15e4e5e081747758 /src/renderer | |
| parent | 4969ea10ffd14e52c83f32e708563bacf910e238 (diff) | |
Add online users popover, MCP get_online_users, claude-workspace, stronger CLAUDE.mdv0.3.4
- Online users: click "N online" badge to see user names with cursor colors
- MCP get_online_users tool: main process tracks users in .lattex-online-users.json
- CLAUDE.md: show current user's Overleaf name (fetched from /user/settings)
- CLAUDE.md: mandatory first steps with MUST/NEVER language at top of file
- claude-workspace/ directory: untracked scratch space for Claude Code experiments
- chokidar + bridge skip claude-workspace/ to prevent syncing to Overleaf
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src/renderer')
| -rw-r--r-- | src/renderer/src/App.css | 64 | ||||
| -rw-r--r-- | src/renderer/src/components/Toolbar.tsx | 40 |
2 files changed, 97 insertions, 7 deletions
diff --git a/src/renderer/src/App.css b/src/renderer/src/App.css index 7f7b24c..906bf36 100644 --- a/src/renderer/src/App.css +++ b/src/renderer/src/App.css @@ -2229,6 +2229,10 @@ html, body, #root { /* ── Toolbar Users Count ─────────────────────────────────────── */ +.toolbar-users-wrap { + position: relative; +} + .toolbar-users { font-size: 11px; color: var(--text-muted); @@ -2236,6 +2240,66 @@ html, body, #root { padding: 2px 8px; border-radius: 10px; font-weight: 500; + border: none; + cursor: pointer; + font-family: var(--font-sans); +} + +.toolbar-users:hover { + background: var(--bg-active); +} + +.users-popover { + position: absolute; + top: calc(100% + 6px); + right: 0; + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: 6px; + padding: 6px 0; + min-width: 180px; + z-index: 100; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); +} + +.users-popover-title { + font-size: 10px; + font-weight: 600; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 4px 12px 6px; + border-bottom: 1px solid var(--border); + margin-bottom: 2px; +} + +.users-popover-item { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 12px; + font-size: 12px; + color: var(--text-primary); +} + +.users-popover-dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; +} + +.users-popover-name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.users-popover-empty { + padding: 5px 12px; + font-size: 11px; + color: var(--text-muted); + font-style: italic; } /* ── Chat Panel ──────────────────────────────────────────────── */ diff --git a/src/renderer/src/components/Toolbar.tsx b/src/renderer/src/components/Toolbar.tsx index 0cee7e1..b89385a 100644 --- a/src/renderer/src/components/Toolbar.tsx +++ b/src/renderer/src/components/Toolbar.tsx @@ -3,6 +3,7 @@ import { useState, useRef, useEffect } from 'react' import { useAppStore } from '../stores/appStore' +import { remoteCursors } from '../App' interface ToolbarProps { onCompile: () => void @@ -19,19 +20,24 @@ export default function Toolbar({ onCompile, onLocalCompile, onBack }: ToolbarPr } = useAppStore() const [showCompileMenu, setShowCompileMenu] = useState(false) + const [showUsersPopover, setShowUsersPopover] = useState(false) const menuRef = useRef<HTMLDivElement>(null) + const usersRef = useRef<HTMLDivElement>(null) - // Close menu on outside click + // Close menus on outside click useEffect(() => { - if (!showCompileMenu) return + if (!showCompileMenu && !showUsersPopover) return const handler = (e: MouseEvent) => { - if (menuRef.current && !menuRef.current.contains(e.target as Node)) { + if (showCompileMenu && menuRef.current && !menuRef.current.contains(e.target as Node)) { setShowCompileMenu(false) } + if (showUsersPopover && usersRef.current && !usersRef.current.contains(e.target as Node)) { + setShowUsersPopover(false) + } } document.addEventListener('mousedown', handler) return () => document.removeEventListener('mousedown', handler) - }, [showCompileMenu]) + }, [showCompileMenu, showUsersPopover]) const projectName = overleafProject?.name || 'Project' @@ -91,9 +97,29 @@ export default function Toolbar({ onCompile, onLocalCompile, onBack }: ToolbarPr </div> <div className="toolbar-right"> {onlineUsersCount > 0 && ( - <span className="toolbar-users" title={`${onlineUsersCount} user${onlineUsersCount > 1 ? 's' : ''} online`}> - {onlineUsersCount} - </span> + <div className="toolbar-users-wrap" ref={usersRef}> + <button + className="toolbar-users" + onClick={() => setShowUsersPopover(!showUsersPopover)} + title={`${onlineUsersCount} user${onlineUsersCount > 1 ? 's' : ''} online`} + > + {onlineUsersCount} online + </button> + {showUsersPopover && ( + <div className="users-popover"> + <div className="users-popover-title">Online Users</div> + {Array.from(remoteCursors.values()).map((u) => ( + <div key={u.userId} className="users-popover-item"> + <span className="users-popover-dot" style={{ background: u.color }} /> + <span className="users-popover-name">{u.name}</span> + </div> + ))} + {remoteCursors.size === 0 && ( + <div className="users-popover-empty">No cursor data yet</div> + )} + </div> + )} + </div> )} <button className={`toolbar-btn ${showChat ? 'active' : ''}`} onClick={toggleChat} title="Toggle chat"> Chat |
