/** * GYXX 工作台客户端插件入口。 * 由 esbuild 打包为 CJS 并通过 window.__ModuleLoader__ 注册(见 scripts/build-client.mjs)。 * 运行时只依赖 dsh 平台共享模块表中的 react。 */ import { createElement, useSyncExternalStore } from 'react' import { WorkflowPanel, refreshWorkflows } from './Panel.jsx' import { Toasts } from './Toasts.jsx' import { getState, subscribe, update } from './store.js' import stylesText from './styles.css' export const inject = ['slots'] function useWorkbench() { return useSyncExternalStore(subscribe, getState) } function injectStyles() { if (typeof document === 'undefined') return if (document.getElementById('gyxxwb-styles')) return const style = document.createElement('style') style.id = 'gyxxwb-styles' style.textContent = stylesText document.head.appendChild(style) } /** 侧边栏底部动作:工作流面板开关 + 聚合状态点。 */ function WorkbenchFooterAction() { const s = useWorkbench() const failed = s.workflows.filter((w) => !w.active_run && w.last_run?.status === 'failed').length const running = s.workflows.filter((w) => w.active_run).length const tone = failed > 0 ? 'failed' : running > 0 ? 'running' : 'success' return createElement( 'button', { type: 'button', className: `gyxxwb-footer-btn${s.open ? ' gyxxwb-footer-btn-active' : ''}`, title: 'GYXX 工作流工作台', onClick: () => { const next = !getState().open update({ open: next }) if (next) refreshWorkflows() }, }, createElement('span', { className: `gyxxwb-dot gyxxwb-dot-${tone}` }), createElement('span', null, '工作流'), ) } /** 覆盖层根:左侧抽屉面板 + 失败 toast。 */ function WorkbenchOverlay() { return createElement( 'div', { className: 'gyxxwb-overlay' }, createElement(WorkflowPanel, null), createElement(Toasts, null), ) } export function apply(ctx) { injectStyles() // 插槽须等宿主声明后才能注册:slots.inject 会等待座位声明(顺序安全), // 直接 register 在我们先于 ui-sidebar/ui-layout 加载时会报“slot is not declared”。 ctx.effect( () => ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register( { name: 'sidebar.footer.action', id: 'gyxx-workbench.footer' }, WorkbenchFooterAction, ), ), 'gyxx-workbench: footer action', ) ctx.effect( () => ctx.slots.inject('shell.overlay', () => ctx.slots.register( { name: 'shell.overlay', id: 'gyxx-workbench.overlay' }, WorkbenchOverlay, ), ), 'gyxx-workbench: overlay', ) }