mirror of
https://github.com/xmanrui/OpenClaw-bot-review.git
synced 2026-07-27 22:25:52 +00:00
feat(pixel-office): float working code snippets above top bar
This commit is contained in:
@@ -107,6 +107,7 @@ function getGhostBorderDirection(col: number, row: number, cols: number, rows: n
|
||||
}
|
||||
|
||||
const FIXED_CANVAS_ZOOM = 2.5
|
||||
const CODE_SNIPPET_LIFETIME_SEC = 5.5
|
||||
|
||||
let cachedOfficeState: OfficeState | null = null
|
||||
let cachedEditorState: EditorState | null = null
|
||||
@@ -157,6 +158,7 @@ export default function PixelOfficePage() {
|
||||
const [showIdleRank, setShowIdleRank] = useState(false)
|
||||
const idleRankRef = useRef<Array<{ agentId: string; onlineMinutes: number; activeMinutes: number; idleMinutes: number; idlePercent: number }> | null>(null)
|
||||
const floatingCommentsRef = useRef<Array<{ key: string; text: string; x: number; y: number; opacity: number }>>([])
|
||||
const floatingCodeRef = useRef<Array<{ key: string; text: string; x: number; y: number; opacity: number }>>([])
|
||||
const [floatingTick, setFloatingTick] = useState(0)
|
||||
const forceEditorUpdate = useCallback(() => setEditorTick(t => t + 1), [])
|
||||
|
||||
@@ -318,6 +320,13 @@ export default function PixelOfficePage() {
|
||||
const containerTop = container.offsetTop
|
||||
const lifetime = 4.0
|
||||
const items: Array<{ key: string; text: string; x: number; y: number; opacity: number }> = []
|
||||
const codeItems: Array<{ key: string; text: string; x: number; y: number; opacity: number }> = []
|
||||
const workingCharIds = new Set<number>()
|
||||
for (const a of agents) {
|
||||
if (a.state !== 'working') continue
|
||||
const cid = agentIdMapRef.current.get(a.agentId)
|
||||
if (typeof cid === 'number') workingCharIds.add(cid)
|
||||
}
|
||||
for (const ch of office.getCharacters()) {
|
||||
if (ch.photoComments.length === 0) continue
|
||||
const anchorX = ox + ch.x * zoom
|
||||
@@ -339,7 +348,28 @@ export default function PixelOfficePage() {
|
||||
})
|
||||
}
|
||||
}
|
||||
for (const ch of office.getCharacters()) {
|
||||
if (!workingCharIds.has(ch.id)) continue
|
||||
if (ch.codeSnippets.length === 0) continue
|
||||
const anchorX = ox + ch.x * zoom
|
||||
const anchorY = containerTop + oy + (ch.y - 10) * zoom
|
||||
const totalDist = anchorY + 24
|
||||
for (let i = 0; i < ch.codeSnippets.length; i++) {
|
||||
const s = ch.codeSnippets[i]
|
||||
const progress = s.age / CODE_SNIPPET_LIFETIME_SEC
|
||||
if (progress <= 0 || progress >= 1) continue
|
||||
const alpha = progress < 0.15 ? progress / 0.15 : progress > 0.88 ? (1 - progress) / 0.12 : 1
|
||||
codeItems.push({
|
||||
key: `${ch.id}-code-${i}-${s.text}`,
|
||||
text: s.text,
|
||||
x: anchorX + s.x * zoom,
|
||||
y: anchorY - progress * totalDist,
|
||||
opacity: Math.max(0, alpha * 0.9),
|
||||
})
|
||||
}
|
||||
}
|
||||
floatingCommentsRef.current = items
|
||||
floatingCodeRef.current = codeItems
|
||||
setFloatingTick(t => t + 1)
|
||||
}
|
||||
animationFrameIdRef.current = requestAnimationFrame(render)
|
||||
@@ -348,7 +378,7 @@ export default function PixelOfficePage() {
|
||||
return () => {
|
||||
if (animationFrameIdRef.current !== null) cancelAnimationFrame(animationFrameIdRef.current)
|
||||
}
|
||||
}, [hoveredAgentId, editorTick, officeReady])
|
||||
}, [hoveredAgentId, editorTick, officeReady, agents])
|
||||
|
||||
// Load GitHub contribution heatmap data (real → fallback mock)
|
||||
useEffect(() => {
|
||||
@@ -1055,6 +1085,18 @@ export default function PixelOfficePage() {
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{/* Floating code snippets (working agents): rise to top, overlay top bar */}
|
||||
{floatingCodeRef.current.map(fc => (
|
||||
<div key={fc.key} className="absolute pointer-events-none z-40 whitespace-nowrap"
|
||||
style={{ left: fc.x, top: fc.y, opacity: fc.opacity, transform: 'translateX(-50%)' }}>
|
||||
<span
|
||||
className="inline-block px-2 py-0.5 rounded-md text-[11px] font-mono font-semibold"
|
||||
style={{ backgroundColor: 'rgba(0,0,0,0.72)', color: '#4ade80' }}
|
||||
>
|
||||
{fc.text}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{/* Top bar: agent tags + controls */}
|
||||
<div className="flex flex-wrap items-center gap-2 p-4 border-b border-[var(--border)]">
|
||||
<span className="text-sm font-bold text-[var(--text)] mr-2">{t('pixelOffice.title')}</span>
|
||||
|
||||
@@ -32,7 +32,7 @@ import { BugSystem } from '../bugs/bugSystem'
|
||||
import { BUG_DEFAULT_COUNT } from '../bugs/config'
|
||||
import type { BugEntity } from '../bugs/types'
|
||||
|
||||
const CODE_SNIPPET_LIFETIME = 2.5 // seconds
|
||||
const CODE_SNIPPET_LIFETIME = 5.5 // seconds
|
||||
const CODE_SNIPPET_SPAWN_RATE = 0.6 // per second
|
||||
const PHOTO_COMMENT_LIFETIME = 4.0 // seconds
|
||||
const PHOTO_COMMENT_SPAWN_RATE = 2.5 // per second
|
||||
|
||||
@@ -426,55 +426,8 @@ export function renderScene(
|
||||
})
|
||||
}
|
||||
|
||||
// Code snippet particles floating above working characters
|
||||
if (ch.codeSnippets.length > 0) {
|
||||
const snippets = ch.codeSnippets
|
||||
const baseX = Math.round(offsetX + ch.x * zoom)
|
||||
const baseY = drawY
|
||||
const snipFontSize = Math.max(12, Math.round(7 * zoom))
|
||||
drawables.push({
|
||||
zY: charZY + 0.2,
|
||||
draw: (c) => {
|
||||
c.save()
|
||||
c.font = `${snipFontSize}px monospace`
|
||||
c.textAlign = 'center'
|
||||
c.textBaseline = 'bottom'
|
||||
for (const s of snippets) {
|
||||
const progress = s.age / 2.5 // 0→1 over lifetime
|
||||
const floatY = baseY - (10 + progress * 28) * zoom
|
||||
const floatX = baseX + s.x * zoom
|
||||
const alpha = progress < 0.2 ? progress / 0.2 : progress > 0.7 ? (1 - progress) / 0.3 : 1
|
||||
c.globalAlpha = alpha * 0.85
|
||||
// Background pill
|
||||
const tw = c.measureText(s.text).width
|
||||
const px = 3 * (zoom / 3)
|
||||
const py = 1.5 * (zoom / 3)
|
||||
c.fillStyle = 'rgba(0,0,0,0.7)'
|
||||
const rx = floatX - tw / 2 - px
|
||||
const ry = floatY - snipFontSize - py
|
||||
const rw = tw + px * 2
|
||||
const rh = snipFontSize + py * 2
|
||||
const cr = 3
|
||||
c.beginPath()
|
||||
c.moveTo(rx + cr, ry)
|
||||
c.lineTo(rx + rw - cr, ry)
|
||||
c.quadraticCurveTo(rx + rw, ry, rx + rw, ry + cr)
|
||||
c.lineTo(rx + rw, ry + rh - cr)
|
||||
c.quadraticCurveTo(rx + rw, ry + rh, rx + rw - cr, ry + rh)
|
||||
c.lineTo(rx + cr, ry + rh)
|
||||
c.quadraticCurveTo(rx, ry + rh, rx, ry + rh - cr)
|
||||
c.lineTo(rx, ry + cr)
|
||||
c.quadraticCurveTo(rx, ry, rx + cr, ry)
|
||||
c.closePath()
|
||||
c.fill()
|
||||
// Text
|
||||
c.fillStyle = '#4ade80'
|
||||
c.fillText(s.text, floatX, floatY)
|
||||
}
|
||||
c.restore()
|
||||
},
|
||||
})
|
||||
}
|
||||
// Code snippet particles are rendered as DOM overlays in app/pixel-office/page.tsx
|
||||
// so they can float beyond the canvas area and pass over the top agent list.
|
||||
}
|
||||
|
||||
// Sort by Y (lower = in front = drawn later)
|
||||
|
||||
Reference in New Issue
Block a user