feat: anim pane type + bind button redesign (aligned double-circle icon, status popup) + auto-bind rule + workspaces route BOM tolerance

This commit is contained in:
Aisland
2026-08-19 01:55:36 +08:00
parent 2ec499ceed
commit 696f8070b9
9 changed files with 428 additions and 22 deletions
+39 -2
View File
@@ -23,7 +23,7 @@ hljs.registerLanguage('json', hljsJson)
* 内容与 chatSide 的变更经 onSpecMutated 回调交给工作台持久化(布局条目)。
*/
export type BuiltinType = 'browser' | 'explorer' | 'scm' | 'tasks' | 'terminal' | 'custom'
export type BuiltinType = 'browser' | 'anim' | 'explorer' | 'scm' | 'tasks' | 'terminal' | 'custom'
export type SplitContent =
| { kind: 'iframe'; url: string; title?: string }
@@ -123,6 +123,7 @@ const PERSIST_KEY = 'dsh.worktable.split.v2'
/** 内置内容窗图标 */
const BUILTIN_ICONS: Record<BuiltinType, string> = {
browser: '🌐',
anim: '🎬',
explorer: '📁',
scm: '🔀',
tasks: '✅',
@@ -132,6 +133,7 @@ const BUILTIN_ICONS: Record<BuiltinType, string> = {
const BUILTIN_LABEL_KEYS: Record<BuiltinType, string> = {
browser: 'pane.browser',
anim: 'pane.anim',
explorer: 'pane.explorer',
scm: 'pane.scm',
tasks: 'pane.tasks',
@@ -861,6 +863,31 @@ function BrowserPane() {
)
}
/** 动画播放窗:iframe 壳 + 地址栏(站内自带项目/场景列表、播放、画幅切换、导出等全部控件) */
function AnimPane() {
const [url, setUrl] = useState('')
const [src, setSrc] = useState('about:blank')
const go = () => {
const u = url.trim()
setSrc(/^(\/|https?:\/\/)/i.test(u) ? u : 'about:blank')
}
return (
<>
<div className="dsh-wt_browserBar">
<input
className="dsh-wt_browserInput"
value={url}
placeholder={T('pane.animUrlPh')}
onChange={(e) => setUrl(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') go() }}
/>
<button type="button" className="dsh-wt_browserGo" onClick={go}>↗</button>
</div>
<iframe className="dsh-wt_paneFrame" src={src} title="anim" />
</>
)
}
/** 文件夹图标(重绘 SVG,与 better-sidebar 同款风格) */
function FolderIcon() {
return (
@@ -1313,6 +1340,7 @@ function CustomPane() {
const [busy, setBusy] = useState(false)
const [done, setDone] = useState(false)
const [fail, setFail] = useState('')
const [bindNote, setBindNote] = useState<'auto' | 'kept' | 'none'>('none')
// 默认项目 = 当前工作区所属项目;默认会话 = 当前会话;默认分组 = 当前会话所在工作区
useEffect(() => {
const list = custom?.getProjects?.() ?? []
@@ -1344,10 +1372,12 @@ function CustomPane() {
if (!newGroupParent.trim() || !newGroupName.trim()) { setFail(T('custom.groupNeedPath')); return }
group = { kind: 'new', parent: newGroupParent.trim(), name: newGroupName.trim() }
}
await custom.submit(projectId, pname, text, group)
const sid = await custom.submit(projectId, pname, text, group)
setBindNote((custom.autoBind?.(sid) ?? 'none') as any)
} else {
if (!sessionId) return
await custom.sendToSession(sessionId, pname, text)
setBindNote((custom.autoBind?.(sessionId) ?? 'none') as any)
}
setDone(true)
} catch (e) {
@@ -1365,6 +1395,9 @@ function CustomPane() {
<span className="dsh-wt_customDone" aria-hidden>✅</span>
<p className="dsh-wt_customDoneText">{mode === 'new' ? T('custom.done') : T('custom.sent')}</p>
<p className="dsh-wt_customDoneHint">{T('custom.doneHint')}</p>
{bindNote !== 'none' && (
<p className="dsh-wt_customDoneBind">{bindNote === 'auto' ? T('custom.autoBound') : T('custom.keptBinding')}</p>
)}
</div>
)
}
@@ -1473,6 +1506,7 @@ function PaneTabBody(props: { tab: PaneTab; row: PaneRow; index: number }) {
return <FileViewer path={content.path} />
}
if (content.type === 'browser') return <BrowserPane />
if (content.type === 'anim') return <AnimPane />
if (content.type === 'explorer') return <ExplorerPane row={props.row} index={props.index} />
if (content.type === 'scm') return <GitPane />
if (content.type === 'tasks') return <JobsPane />
@@ -1547,6 +1581,9 @@ function PanePicker(props: { row: PaneRow; index: number }) {
<button type="button" className="dsh-wt_panePick" onClick={() => pick({ kind: 'builtin', type: 'browser' })}>
<span aria-hidden>🌐</span>{T('pane.browser')}
</button>
<button type="button" className="dsh-wt_panePick" onClick={() => pick({ kind: 'builtin', type: 'anim' })}>
<span aria-hidden>🎬</span>{T('pane.anim')}
</button>
<button type="button" className="dsh-wt_panePick" onClick={() => pick({ kind: 'builtin', type: 'explorer' })}>
<span aria-hidden>📁</span>{T('pane.explorer')}
</button>