feat(console): card shape selector (square/circle) + circle-mode layout/fonts + done-glow re-light fix
This commit is contained in:
@@ -69,6 +69,8 @@ type ViewState = {
|
||||
consoleTheme?: 'dark' | 'light' | 'system'
|
||||
/** 控制室每行显示项目数(1-5,默认 3)*/
|
||||
consoleCols?: number
|
||||
/** 控制室卡片形状:square/circle(默认 square)*/
|
||||
consoleShape?: 'square' | 'circle'
|
||||
}
|
||||
|
||||
/** 卡片上报的项目元信息(协议 v2)。 */
|
||||
@@ -295,6 +297,7 @@ function loadView(): ViewState {
|
||||
floatTop: typeof p.floatTop === 'number' ? p.floatTop : null,
|
||||
consoleTheme: p.consoleTheme === 'dark' || p.consoleTheme === 'light' || p.consoleTheme === 'system' ? p.consoleTheme : 'system',
|
||||
consoleCols: typeof p.consoleCols === 'number' && p.consoleCols >= 1 && p.consoleCols <= 5 ? Math.round(p.consoleCols) : 3,
|
||||
consoleShape: p.consoleShape === 'circle' ? 'circle' : 'square',
|
||||
}
|
||||
} catch {
|
||||
return { ...DEFAULT_VIEW }
|
||||
@@ -921,6 +924,8 @@ function buildMountContent(folder: string, d: any): any {
|
||||
|
||||
/** 每会话最近一次观察到的「需要判断」布尔值:状态转移时清除旧 ack(新一轮待决重新点亮) */
|
||||
const notifyStateSeenRef: { current: Record<string, boolean> } = { current: {} }
|
||||
/** 每会话最近观察到的「已完成」布尔值:新一次完成时清除旧 done ack(控制室绿光重新点亮) */
|
||||
const doneSeenRef: { current: Record<string, boolean> } = { current: {} }
|
||||
/** 清除某会话的 ack 记录(状态转移时调用,保证新问题不会被旧确认压住) */
|
||||
function clearNotifyAck(sid: string) {
|
||||
try {
|
||||
@@ -1196,6 +1201,11 @@ function WorktableSection(props: any) {
|
||||
if (Array.isArray(face?.pending) && face.pending.length > 0) return 'need'
|
||||
} catch {}
|
||||
if (e.completed === true) return 'done'
|
||||
// 会话面兜底:宿主快照可能滞后或缺少完成字段
|
||||
try {
|
||||
const face = sessionBridge?.sessions?.binding?.(sid)?.session?.getSnapshot?.()
|
||||
if (face && face.completed === true) return 'done'
|
||||
} catch {}
|
||||
if (e.running === true) return 'busy'
|
||||
return 'idle'
|
||||
}
|
||||
@@ -1328,6 +1338,8 @@ function WorktableSection(props: any) {
|
||||
setTheme: (th: 'dark' | 'light' | 'system') => persistView({ consoleTheme: th }),
|
||||
getCols: () => viewRef.current.consoleCols ?? 3,
|
||||
setCols: (n: number) => persistView({ consoleCols: n }),
|
||||
getShape: () => viewRef.current.consoleShape ?? 'square',
|
||||
setShape: (s: 'square' | 'circle') => persistView({ consoleShape: s }),
|
||||
onAck: (id) => { ackRef.current?.(id); setNotifyTick((t) => t + 1); notifyConsole() },
|
||||
refreshPreviews: () => {
|
||||
if (previewTimer != null) { window.clearTimeout(previewTimer); previewTimer = null }
|
||||
@@ -1837,11 +1849,15 @@ function buildCustomLayoutPrompt(req: string): string {
|
||||
const e = byId[sid]
|
||||
if (!e) continue
|
||||
if (e.completed === true) {
|
||||
// 新一次完成 → 清除旧 done ack,控制室绿光重新点亮(即使旧完成已被确认过)
|
||||
if (doneSeenRef.current[sid] !== true) clearNotifyAck(sid)
|
||||
doneSeenRef.current[sid] = true
|
||||
if (!mountConsumedRef.current.has(sid)) {
|
||||
mountConsumedRef.current.add(sid)
|
||||
tryAutoMount(pid, sid)
|
||||
}
|
||||
} else {
|
||||
doneSeenRef.current[sid] = false
|
||||
mountConsumedRef.current.delete(sid)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,9 @@ export const zh = {
|
||||
'console.themeLabel': '面板主题',
|
||||
'console.cols': '每行',
|
||||
'console.colsLabel': '每行显示项目数',
|
||||
'console.shapeLabel': '卡片形状',
|
||||
'console.shapeSquare': '方形',
|
||||
'console.shapeCircle': '圆形',
|
||||
'console.themeDark': '深色',
|
||||
'console.themeLight': '白色',
|
||||
'console.themeSystem': '跟随系统',
|
||||
@@ -298,6 +301,9 @@ export const en = {
|
||||
'console.themeLabel': 'Panel theme',
|
||||
'console.cols': 'Per row',
|
||||
'console.colsLabel': 'Projects per row',
|
||||
'console.shapeLabel': 'Card shape',
|
||||
'console.shapeSquare': 'Square',
|
||||
'console.shapeCircle': 'Circle',
|
||||
'console.themeDark': 'Dark',
|
||||
'console.themeLight': 'Light',
|
||||
'console.themeSystem': 'System',
|
||||
|
||||
@@ -1017,6 +1017,7 @@ function ConsolePane() {
|
||||
const [, setTick] = useState(0)
|
||||
const [now, setNow] = useState(() => Date.now())
|
||||
const [cols, setColsState] = useState<number>(() => splitEnv?.console?.getCols?.() ?? 3)
|
||||
const [shape, setShapeState] = useState<'square' | 'circle'>(() => splitEnv?.console?.getShape?.() ?? 'square')
|
||||
const gridRef = useRef<HTMLDivElement | null>(null)
|
||||
const firstRectsRef = useRef<Map<string, { x: number; y: number }> | null>(null)
|
||||
const onCols = (n: number) => {
|
||||
@@ -1033,6 +1034,10 @@ function ConsolePane() {
|
||||
setColsState(n)
|
||||
splitEnv?.console?.setCols?.(n)
|
||||
}
|
||||
const onShape = (s: 'square' | 'circle') => {
|
||||
setShapeState(s)
|
||||
splitEnv?.console?.setShape?.(s)
|
||||
}
|
||||
useLayoutEffect(() => {
|
||||
const first = firstRectsRef.current
|
||||
if (!first) return
|
||||
@@ -1108,8 +1113,16 @@ function ConsolePane() {
|
||||
{ mode: 'system', icon: '🖥️', key: 'console.themeSystem' },
|
||||
]
|
||||
return (
|
||||
<div className="dsh-wt_console" data-wt-theme={resolvedTheme}>
|
||||
<div className="dsh-wt_console" data-wt-theme={resolvedTheme} data-wt-shape={shape}>
|
||||
<div className="dsh-wt_consoleHead">
|
||||
<div className="dsh-wt_consoleShape" role="group" aria-label={T('console.shapeLabel')}>
|
||||
<button type="button" className={'dsh-wt_consoleShapeBtn' + (shape === 'square' ? ' dsh-wt_consoleShapeBtnOn' : '')} title={T('console.shapeSquare')} aria-label={T('console.shapeSquare')} onClick={() => onShape('square')}>
|
||||
<svg width="12" height="12" viewBox="0 0 16 16" aria-hidden><rect x="4" y="4" width="8" height="8" rx="1.5" fill="none" stroke="currentColor" strokeWidth="1.5" /></svg>
|
||||
</button>
|
||||
<button type="button" className={'dsh-wt_consoleShapeBtn' + (shape === 'circle' ? ' dsh-wt_consoleShapeBtnOn' : '')} title={T('console.shapeCircle')} aria-label={T('console.shapeCircle')} onClick={() => onShape('circle')}>
|
||||
<svg width="12" height="12" viewBox="0 0 16 16" aria-hidden><circle cx="8" cy="8" r="4.5" fill="none" stroke="currentColor" strokeWidth="1.5" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="dsh-wt_consoleTheme" role="group" aria-label={T('console.themeLabel')}>
|
||||
{themeOpts.map((o) => (
|
||||
<button
|
||||
|
||||
@@ -408,6 +408,21 @@ export const css = xtermCss + '\n' + [
|
||||
'.dsh-wt_consoleThemeBtn{flex:none;padding:3px 9px;border:none;border-radius:999px;background:transparent;color:var(--wt-text3);font:inherit;font-size:12px;line-height:18px;cursor:pointer;opacity:.85}',
|
||||
'.dsh-wt_consoleThemeBtn:hover{opacity:1;background:var(--wt-chip)}',
|
||||
'.dsh-wt_consoleThemeBtnOn{background:var(--wt-chip);color:var(--wt-text);opacity:1}',
|
||||
'.dsh-wt_consoleShape{display:inline-flex;align-items:center;padding:2px;border:1px solid var(--wt-border);border-radius:999px;background:var(--wt-card)}',
|
||||
'.dsh-wt_consoleShapeBtn{flex:none;width:26px;height:26px;padding:0;border:none;border-radius:999px;background:transparent;color:var(--wt-text3);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;opacity:.85}',
|
||||
'.dsh-wt_consoleShapeBtn:hover{opacity:1;background:var(--wt-chip)}',
|
||||
'.dsh-wt_consoleShapeBtnOn{background:var(--wt-chip);color:var(--wt-text);opacity:1}',
|
||||
'.dsh-wt_console[data-wt-shape=circle] .dsh-wt_consoleGrid{max-width:calc(var(--wt-cols,3)*268px + (var(--wt-cols,3) - 1)*64px)}',
|
||||
'.dsh-wt_console[data-wt-shape=circle] .dsh-wt_consoleCard{border-radius:50%;justify-content:center;align-items:center;padding:24px 24px 38px;gap:9px}',
|
||||
'.dsh-wt_console[data-wt-shape=circle] .dsh-wt_consoleCardHead{width:100%;flex-direction:column;justify-content:center;gap:5px;text-align:center}',
|
||||
'.dsh-wt_console[data-wt-shape=circle] .dsh-wt_consoleIcon{font-size:27px;line-height:1}',
|
||||
'.dsh-wt_console[data-wt-shape=circle] .dsh-wt_consoleName{flex:none;max-width:88%;font-size:19px;font-weight:700;line-height:26px;text-align:center}',
|
||||
'.dsh-wt_console[data-wt-shape=circle] .dsh-wt_consoleDivider{width:56%;margin:2px auto}',
|
||||
'.dsh-wt_console[data-wt-shape=circle] .dsh-wt_consoleStatusRow{justify-content:center;gap:8px}',
|
||||
'.dsh-wt_console[data-wt-shape=circle] .dsh-wt_consoleStatus{font-size:17px;line-height:24px}',
|
||||
'.dsh-wt_console[data-wt-shape=circle] .dsh-wt_consoleRuntime{font-size:15px;line-height:21px}',
|
||||
'.dsh-wt_console[data-wt-shape=circle] .dsh-wt_consolePreview{text-align:center;max-width:82%;margin:0 auto;-webkit-line-clamp:2;font-size:9px;line-height:13px}',
|
||||
'.dsh-wt_console[data-wt-shape=circle] .dsh-wt_consoleCard-busy::before{border-radius:50%}',
|
||||
'.dsh-wt_consoleGrid{display:flex;flex-wrap:wrap;justify-content:center;gap:64px;align-content:start;margin-top:clamp(32px,9vh,110px);width:100%;max-width:calc(var(--wt-cols,3)*242px + (var(--wt-cols,3) - 1)*64px);margin-left:auto;margin-right:auto}',
|
||||
'.dsh-wt_consoleCols{display:inline-flex;align-items:center;gap:8px;padding:2px 8px;border:1px solid var(--wt-border);border-radius:999px;background:var(--wt-card);color:var(--wt-text3);font-size:11px}',
|
||||
'.dsh-wt_consoleBar{position:relative;width:128px;height:20px;border-radius:999px;background:rgba(255,255,255,.08);cursor:pointer;flex:none}',
|
||||
|
||||
Reference in New Issue
Block a user