refactor: move activity log into settings dropdown, simplify permissions

Move the standalone activity log toggle button (~) into the settings
gear dropdown as a collapsible accordion section. Simplify the board
permission display by removing the verbose "Access Levels" grid and
replacing it with a compact current-permission badge + request button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-12 22:56:52 -07:00
parent 20094ea9a7
commit a37ab68588
3 changed files with 180 additions and 129 deletions

View File

@ -109,15 +109,5 @@ export function ActivityPanel({ isOpen, onClose }: ActivityPanelProps) {
);
}
// Toggle button component for the toolbar
export function ActivityToggleButton({ onClick, isActive }: { onClick: () => void; isActive: boolean }) {
return (
<button
className={`activity-toggle-btn ${isActive ? 'active' : ''}`}
onClick={onClick}
title="Activity Log"
>
<span className="activity-toggle-icon">~</span>
</button>
);
}
// Note: ActivityToggleButton has been removed - activity panel is now toggled
// from the settings dropdown via a custom event 'toggle-activity-panel'

View File

@ -141,7 +141,7 @@ import { updateLastVisited } from "../lib/starredBoards"
import { recordBoardVisit } from "../lib/visitedBoards"
import { captureBoardScreenshot } from "../lib/screenshotService"
import { logActivity } from "../lib/activityLogger"
import { ActivityPanel, ActivityToggleButton } from "../components/ActivityPanel"
import { ActivityPanel } from "../components/ActivityPanel"
import { WORKER_URL } from "../constants/workerUrl"
@ -528,6 +528,17 @@ export function Board() {
const [editor, setEditor] = useState<Editor | null>(null)
const [isActivityPanelOpen, setIsActivityPanelOpen] = useState(false)
// Listen for toggle-activity-panel event from settings dropdown
useEffect(() => {
const handleToggleActivityPanel = () => {
setIsActivityPanelOpen(prev => !prev)
}
window.addEventListener('toggle-activity-panel', handleToggleActivityPanel)
return () => {
window.removeEventListener('toggle-activity-panel', handleToggleActivityPanel)
}
}, [])
// Update read-only state when permission changes after editor is mounted
useEffect(() => {
if (!editor) return
@ -1474,14 +1485,7 @@ export function Board() {
/>
)}
*/}
{/* Activity Panel Toggle Button */}
<div style={{ position: 'fixed', top: 12, right: 12, zIndex: 999 }}>
<ActivityToggleButton
onClick={() => setIsActivityPanelOpen(!isActivityPanelOpen)}
isActive={isActivityPanelOpen}
/>
</div>
{/* Activity Panel */}
{/* Activity Panel - toggled from settings dropdown */}
<ActivityPanel
isOpen={isActivityPanelOpen}
onClose={() => setIsActivityPanelOpen(false)}

View File

@ -71,6 +71,7 @@ function CustomSharePanel() {
const [mobileMenuSection, setMobileMenuSection] = React.useState<'main' | 'signin' | 'share' | 'settings'>('main')
// const [showVersionHistory, setShowVersionHistory] = React.useState(false) // TODO: Re-enable when version reversion is ready
const [showAISection, setShowAISection] = React.useState(false)
const [showActivitySection, setShowActivitySection] = React.useState(false)
const [hasApiKey, setHasApiKey] = React.useState(false)
const [permissionRequestStatus, setPermissionRequestStatus] = React.useState<'idle' | 'sending' | 'sent' | 'error'>('idle')
const [requestMessage, setRequestMessage] = React.useState('')
@ -958,131 +959,72 @@ function CustomSharePanel() {
onWheel={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
>
{/* Board Permission Section */}
<div style={{ padding: '12px 16px 16px' }}>
{/* Section Header */}
{/* Your Permission - simplified display */}
<div style={{ padding: '12px 16px' }}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
marginBottom: '12px',
paddingBottom: '8px',
borderBottom: '1px solid var(--color-panel-contrast)',
justifyContent: 'space-between',
padding: '10px 12px',
background: 'var(--color-muted-2)',
borderRadius: '8px',
border: '1px solid var(--color-panel-contrast)',
}}>
<span style={{ fontSize: '14px' }}>🔐</span>
<span style={{ fontSize: '13px', fontWeight: 600, color: 'var(--color-text)' }}>Board Permission</span>
<span style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px', fontWeight: 500, color: 'var(--color-text)' }}>
<span style={{ fontSize: '14px' }}>{PERMISSION_CONFIG[currentPermission].icon}</span>
<span>Your Permission</span>
</span>
<span style={{
marginLeft: 'auto',
fontSize: '10px',
padding: '3px 8px',
fontSize: '11px',
padding: '4px 10px',
borderRadius: '12px',
background: `${PERMISSION_CONFIG[currentPermission].color}20`,
color: PERMISSION_CONFIG[currentPermission].color,
fontWeight: 600,
textTransform: 'uppercase',
letterSpacing: '0.3px',
}}>
{PERMISSION_CONFIG[currentPermission].label}
</span>
</div>
{/* Permission levels - indented to show hierarchy */}
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '6px',
marginLeft: '4px',
padding: '8px 12px',
background: 'var(--color-muted-2)',
borderRadius: '8px',
border: '1px solid var(--color-panel-contrast)',
}}>
<span style={{ fontSize: '10px', color: 'var(--color-text-3)', marginBottom: '4px', fontWeight: 500, textTransform: 'uppercase', letterSpacing: '0.5px' }}>
Access Levels
</span>
{(['view', 'edit', 'admin'] as PermissionLevel[]).map((level) => {
const config = PERMISSION_CONFIG[level]
const isCurrent = currentPermission === level
const canRequest = session.authed && !isCurrent && (
(level === 'edit' && currentPermission === 'view') ||
(level === 'admin' && currentPermission !== 'admin')
)
return (
<div
key={level}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '8px 10px',
borderRadius: '6px',
background: isCurrent ? `${config.color}15` : 'var(--color-panel)',
border: isCurrent ? `2px solid ${config.color}` : '1px solid var(--color-panel-contrast)',
transition: 'all 0.15s ease',
}}
>
<span style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
fontSize: '12px',
color: isCurrent ? config.color : 'var(--color-text)',
fontWeight: isCurrent ? 600 : 400,
}}>
<span style={{ fontSize: '14px' }}>{config.icon}</span>
<span>{config.label}</span>
{isCurrent && (
<span style={{
fontSize: '9px',
padding: '2px 6px',
borderRadius: '10px',
background: config.color,
color: 'white',
fontWeight: 500,
}}>
Current
</span>
)}
</span>
{canRequest && (
<button
onClick={() => handleRequestPermission(level)}
disabled={permissionRequestStatus === 'sending'}
style={{
padding: '4px 10px',
fontSize: '10px',
fontWeight: 600,
borderRadius: '4px',
border: `1px solid ${config.color}`,
background: 'transparent',
color: config.color,
cursor: permissionRequestStatus === 'sending' ? 'wait' : 'pointer',
opacity: permissionRequestStatus === 'sending' ? 0.6 : 1,
transition: 'all 0.15s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = config.color
e.currentTarget.style.color = 'white'
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent'
e.currentTarget.style.color = config.color
}}
>
{permissionRequestStatus === 'sending' ? '...' : 'Request'}
</button>
)}
</div>
)
})}
</div>
{/* Request higher permission button */}
{session.authed && currentPermission !== 'admin' && (
<button
onClick={() => handleRequestPermission(currentPermission === 'view' ? 'edit' : 'admin')}
disabled={permissionRequestStatus === 'sending'}
style={{
width: '100%',
marginTop: '8px',
padding: '8px 12px',
fontSize: '12px',
fontWeight: 500,
fontFamily: 'inherit',
borderRadius: '6px',
border: `1px solid ${PERMISSION_CONFIG[currentPermission === 'view' ? 'edit' : 'admin'].color}`,
background: 'transparent',
color: PERMISSION_CONFIG[currentPermission === 'view' ? 'edit' : 'admin'].color,
cursor: permissionRequestStatus === 'sending' ? 'wait' : 'pointer',
transition: 'all 0.15s ease',
}}
onMouseEnter={(e) => {
const color = PERMISSION_CONFIG[currentPermission === 'view' ? 'edit' : 'admin'].color
e.currentTarget.style.background = color
e.currentTarget.style.color = 'white'
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'transparent'
e.currentTarget.style.color = PERMISSION_CONFIG[currentPermission === 'view' ? 'edit' : 'admin'].color
}}
>
{permissionRequestStatus === 'sending' ? 'Sending...' :
permissionRequestStatus === 'sent' ? 'Request Sent!' :
`Request ${currentPermission === 'view' ? 'Edit' : 'Admin'} Access`}
</button>
)}
{/* Request status message */}
{requestMessage && (
<p style={{
margin: '10px 0 0',
margin: '8px 0 0',
fontSize: '11px',
padding: '8px 12px',
borderRadius: '6px',
@ -1098,7 +1040,7 @@ function CustomSharePanel() {
{!session.authed && (
<p style={{
margin: '10px 0 0',
margin: '8px 0 0',
fontSize: '10px',
color: 'var(--color-text-3)',
textAlign: 'center',
@ -1358,6 +1300,121 @@ function CustomSharePanel() {
<div style={{ height: '1px', background: 'var(--color-panel-contrast)', margin: '0' }} />
{/* Activity Log Accordion */}
<div>
<button
onClick={() => setShowActivitySection(!showActivitySection)}
style={{
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '12px 16px',
background: showActivitySection ? 'var(--color-muted-2)' : 'none',
border: 'none',
cursor: 'pointer',
color: 'var(--color-text)',
fontSize: '13px',
fontWeight: 600,
textAlign: 'left',
transition: 'background 0.15s ease',
fontFamily: 'inherit',
}}
onMouseEnter={(e) => {
if (!showActivitySection) e.currentTarget.style.background = 'var(--color-muted-2)'
}}
onMouseLeave={(e) => {
if (!showActivitySection) e.currentTarget.style.background = 'none'
}}
>
<span style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<span style={{ fontSize: '14px', fontFamily: 'monospace', fontWeight: 700 }}>~</span>
<span>Activity Log</span>
</span>
<span style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '20px',
height: '20px',
borderRadius: '4px',
background: showActivitySection ? 'var(--color-panel)' : 'var(--color-muted-2)',
transition: 'all 0.2s ease',
}}>
<svg
width="12"
height="12"
viewBox="0 0 16 16"
fill="currentColor"
style={{
transform: showActivitySection ? 'rotate(180deg)' : 'rotate(0deg)',
transition: 'transform 0.2s ease',
color: 'var(--color-text-3)',
}}
>
<path d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"/>
</svg>
</span>
</button>
{showActivitySection && (
<div style={{
padding: '12px 16px',
background: 'var(--color-muted-2)',
borderTop: '1px solid var(--color-panel-contrast)',
}}>
<p style={{
fontSize: '11px',
color: 'var(--color-text-3)',
marginBottom: '12px',
padding: '8px 10px',
background: 'var(--color-panel)',
borderRadius: '6px',
border: '1px solid var(--color-panel-contrast)',
}}>
Track shape creations, deletions, and updates on this board.
</p>
<button
onClick={() => {
setShowSettingsDropdown(false)
// Dispatch custom event to toggle activity panel
window.dispatchEvent(new CustomEvent('toggle-activity-panel'))
}}
style={{
width: '100%',
padding: '10px 12px',
fontSize: '12px',
fontWeight: 500,
fontFamily: 'inherit',
backgroundColor: isDarkMode ? '#3a3a3a' : '#ffffff',
color: 'var(--color-text)',
border: `1px solid ${isDarkMode ? '#505050' : '#d1d5db'}`,
borderRadius: '6px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '8px',
transition: 'all 0.15s ease',
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = isDarkMode ? '#4a4a4a' : '#f3f4f6'
e.currentTarget.style.borderColor = '#3b82f6'
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = isDarkMode ? '#3a3a3a' : '#ffffff'
e.currentTarget.style.borderColor = isDarkMode ? '#505050' : '#d1d5db'
}}
>
<span style={{ fontFamily: 'monospace', fontWeight: 700 }}>~</span>
<span>Open Activity Panel</span>
</button>
</div>
)}
</div>
<div style={{ height: '1px', background: 'var(--color-panel-contrast)', margin: '0' }} />
{/* Show Tutorial Button */}
<div style={{ padding: '12px 16px' }}>
<button