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:
parent
20094ea9a7
commit
a37ab68588
|
|
@ -109,15 +109,5 @@ export function ActivityPanel({ isOpen, onClose }: ActivityPanelProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle button component for the toolbar
|
// Note: ActivityToggleButton has been removed - activity panel is now toggled
|
||||||
export function ActivityToggleButton({ onClick, isActive }: { onClick: () => void; isActive: boolean }) {
|
// from the settings dropdown via a custom event 'toggle-activity-panel'
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={`activity-toggle-btn ${isActive ? 'active' : ''}`}
|
|
||||||
onClick={onClick}
|
|
||||||
title="Activity Log"
|
|
||||||
>
|
|
||||||
<span className="activity-toggle-icon">~</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ import { updateLastVisited } from "../lib/starredBoards"
|
||||||
import { recordBoardVisit } from "../lib/visitedBoards"
|
import { recordBoardVisit } from "../lib/visitedBoards"
|
||||||
import { captureBoardScreenshot } from "../lib/screenshotService"
|
import { captureBoardScreenshot } from "../lib/screenshotService"
|
||||||
import { logActivity } from "../lib/activityLogger"
|
import { logActivity } from "../lib/activityLogger"
|
||||||
import { ActivityPanel, ActivityToggleButton } from "../components/ActivityPanel"
|
import { ActivityPanel } from "../components/ActivityPanel"
|
||||||
|
|
||||||
import { WORKER_URL } from "../constants/workerUrl"
|
import { WORKER_URL } from "../constants/workerUrl"
|
||||||
|
|
||||||
|
|
@ -528,6 +528,17 @@ export function Board() {
|
||||||
const [editor, setEditor] = useState<Editor | null>(null)
|
const [editor, setEditor] = useState<Editor | null>(null)
|
||||||
const [isActivityPanelOpen, setIsActivityPanelOpen] = useState(false)
|
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
|
// Update read-only state when permission changes after editor is mounted
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor) return
|
if (!editor) return
|
||||||
|
|
@ -1474,14 +1485,7 @@ export function Board() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
*/}
|
*/}
|
||||||
{/* Activity Panel Toggle Button */}
|
{/* Activity Panel - toggled from settings dropdown */}
|
||||||
<div style={{ position: 'fixed', top: 12, right: 12, zIndex: 999 }}>
|
|
||||||
<ActivityToggleButton
|
|
||||||
onClick={() => setIsActivityPanelOpen(!isActivityPanelOpen)}
|
|
||||||
isActive={isActivityPanelOpen}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/* Activity Panel */}
|
|
||||||
<ActivityPanel
|
<ActivityPanel
|
||||||
isOpen={isActivityPanelOpen}
|
isOpen={isActivityPanelOpen}
|
||||||
onClose={() => setIsActivityPanelOpen(false)}
|
onClose={() => setIsActivityPanelOpen(false)}
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ function CustomSharePanel() {
|
||||||
const [mobileMenuSection, setMobileMenuSection] = React.useState<'main' | 'signin' | 'share' | 'settings'>('main')
|
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 [showVersionHistory, setShowVersionHistory] = React.useState(false) // TODO: Re-enable when version reversion is ready
|
||||||
const [showAISection, setShowAISection] = React.useState(false)
|
const [showAISection, setShowAISection] = React.useState(false)
|
||||||
|
const [showActivitySection, setShowActivitySection] = React.useState(false)
|
||||||
const [hasApiKey, setHasApiKey] = React.useState(false)
|
const [hasApiKey, setHasApiKey] = React.useState(false)
|
||||||
const [permissionRequestStatus, setPermissionRequestStatus] = React.useState<'idle' | 'sending' | 'sent' | 'error'>('idle')
|
const [permissionRequestStatus, setPermissionRequestStatus] = React.useState<'idle' | 'sending' | 'sent' | 'error'>('idle')
|
||||||
const [requestMessage, setRequestMessage] = React.useState('')
|
const [requestMessage, setRequestMessage] = React.useState('')
|
||||||
|
|
@ -958,131 +959,72 @@ function CustomSharePanel() {
|
||||||
onWheel={(e) => e.stopPropagation()}
|
onWheel={(e) => e.stopPropagation()}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{/* Board Permission Section */}
|
{/* Your Permission - simplified display */}
|
||||||
<div style={{ padding: '12px 16px 16px' }}>
|
<div style={{ padding: '12px 16px' }}>
|
||||||
{/* Section Header */}
|
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: '8px',
|
justifyContent: 'space-between',
|
||||||
marginBottom: '12px',
|
padding: '10px 12px',
|
||||||
paddingBottom: '8px',
|
background: 'var(--color-muted-2)',
|
||||||
borderBottom: '1px solid var(--color-panel-contrast)',
|
borderRadius: '8px',
|
||||||
|
border: '1px solid var(--color-panel-contrast)',
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: '14px' }}>🔐</span>
|
<span style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px', fontWeight: 500, color: 'var(--color-text)' }}>
|
||||||
<span style={{ fontSize: '13px', fontWeight: 600, color: 'var(--color-text)' }}>Board Permission</span>
|
<span style={{ fontSize: '14px' }}>{PERMISSION_CONFIG[currentPermission].icon}</span>
|
||||||
|
<span>Your Permission</span>
|
||||||
|
</span>
|
||||||
<span style={{
|
<span style={{
|
||||||
marginLeft: 'auto',
|
fontSize: '11px',
|
||||||
fontSize: '10px',
|
padding: '4px 10px',
|
||||||
padding: '3px 8px',
|
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
background: `${PERMISSION_CONFIG[currentPermission].color}20`,
|
background: `${PERMISSION_CONFIG[currentPermission].color}20`,
|
||||||
color: PERMISSION_CONFIG[currentPermission].color,
|
color: PERMISSION_CONFIG[currentPermission].color,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
textTransform: 'uppercase',
|
|
||||||
letterSpacing: '0.3px',
|
|
||||||
}}>
|
}}>
|
||||||
{PERMISSION_CONFIG[currentPermission].label}
|
{PERMISSION_CONFIG[currentPermission].label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Permission levels - indented to show hierarchy */}
|
{/* Request higher permission button */}
|
||||||
<div style={{
|
{session.authed && currentPermission !== 'admin' && (
|
||||||
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
|
<button
|
||||||
onClick={() => handleRequestPermission(level)}
|
onClick={() => handleRequestPermission(currentPermission === 'view' ? 'edit' : 'admin')}
|
||||||
disabled={permissionRequestStatus === 'sending'}
|
disabled={permissionRequestStatus === 'sending'}
|
||||||
style={{
|
style={{
|
||||||
padding: '4px 10px',
|
width: '100%',
|
||||||
fontSize: '10px',
|
marginTop: '8px',
|
||||||
fontWeight: 600,
|
padding: '8px 12px',
|
||||||
borderRadius: '4px',
|
fontSize: '12px',
|
||||||
border: `1px solid ${config.color}`,
|
fontWeight: 500,
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
borderRadius: '6px',
|
||||||
|
border: `1px solid ${PERMISSION_CONFIG[currentPermission === 'view' ? 'edit' : 'admin'].color}`,
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
color: config.color,
|
color: PERMISSION_CONFIG[currentPermission === 'view' ? 'edit' : 'admin'].color,
|
||||||
cursor: permissionRequestStatus === 'sending' ? 'wait' : 'pointer',
|
cursor: permissionRequestStatus === 'sending' ? 'wait' : 'pointer',
|
||||||
opacity: permissionRequestStatus === 'sending' ? 0.6 : 1,
|
|
||||||
transition: 'all 0.15s ease',
|
transition: 'all 0.15s ease',
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.currentTarget.style.background = config.color
|
const color = PERMISSION_CONFIG[currentPermission === 'view' ? 'edit' : 'admin'].color
|
||||||
|
e.currentTarget.style.background = color
|
||||||
e.currentTarget.style.color = 'white'
|
e.currentTarget.style.color = 'white'
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.currentTarget.style.background = 'transparent'
|
e.currentTarget.style.background = 'transparent'
|
||||||
e.currentTarget.style.color = config.color
|
e.currentTarget.style.color = PERMISSION_CONFIG[currentPermission === 'view' ? 'edit' : 'admin'].color
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{permissionRequestStatus === 'sending' ? '...' : 'Request'}
|
{permissionRequestStatus === 'sending' ? 'Sending...' :
|
||||||
|
permissionRequestStatus === 'sent' ? 'Request Sent!' :
|
||||||
|
`Request ${currentPermission === 'view' ? 'Edit' : 'Admin'} Access`}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Request status message */}
|
{/* Request status message */}
|
||||||
{requestMessage && (
|
{requestMessage && (
|
||||||
<p style={{
|
<p style={{
|
||||||
margin: '10px 0 0',
|
margin: '8px 0 0',
|
||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
padding: '8px 12px',
|
padding: '8px 12px',
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
|
|
@ -1098,7 +1040,7 @@ function CustomSharePanel() {
|
||||||
|
|
||||||
{!session.authed && (
|
{!session.authed && (
|
||||||
<p style={{
|
<p style={{
|
||||||
margin: '10px 0 0',
|
margin: '8px 0 0',
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
color: 'var(--color-text-3)',
|
color: 'var(--color-text-3)',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
|
@ -1358,6 +1300,121 @@ function CustomSharePanel() {
|
||||||
|
|
||||||
<div style={{ height: '1px', background: 'var(--color-panel-contrast)', margin: '0' }} />
|
<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 */}
|
{/* Show Tutorial Button */}
|
||||||
<div style={{ padding: '12px 16px' }}>
|
<div style={{ padding: '12px 16px' }}>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue