mirror of
https://github.com/hansjone/dsh-ops-cron.git
synced 2026-10-09 00:43:22 +08:00
Keep the job list mounted across sidebar collapse so it does not flash.
Expanding used to replay a fade-in and sometimes remount CronApp onto an empty state before /jobs returned. Reattach the existing root and skip setState when the snapshot has not changed.
This commit is contained in:
parent
30e37b5e89
commit
4447404361
2 changed files with 43 additions and 22 deletions
|
|
@ -162,6 +162,15 @@ window.__ModuleLoader__.load({
|
|||
const API = '/dsh-cron-tasks'
|
||||
const LOCALE_NS = 'settings.dshCronTasks'
|
||||
const TITLE_PREFIX = '定时任务 · '
|
||||
const listSnapshot = { jobs: [], runs: [], workspaces: [], catalog: { groups: [], current: null } }
|
||||
|
||||
function jobStamp(rows) {
|
||||
return (rows || []).map((row) => `${row.id}:${row.updatedAt}:${row.nextRunAt}:${row.enabled}:${row.lastStatus}:${row.model}:${row.provider}`).join('|')
|
||||
}
|
||||
|
||||
function runStamp(rows) {
|
||||
return (rows || []).map((row) => `${row.id}:${row.status}:${row.actualAt}:${row.summary || ''}`).join('|')
|
||||
}
|
||||
const DEFAULTS = {
|
||||
enabled: true,
|
||||
timezone: 'Asia/Shanghai',
|
||||
|
|
@ -201,7 +210,7 @@ window.__ModuleLoader__.load({
|
|||
[class*="_collapsed"] .dsh-ct-entry .dsh-ct-entryLabel,.dsh-ct-entry[data-collapsed=true] .dsh-ct-entryLabel{display:none!important;max-width:0;min-width:0;width:0;height:0;overflow:hidden;opacity:0;pointer-events:none}
|
||||
[class*="_collapsed"] .dsh-ct-entry svg,.dsh-ct-entry[data-collapsed=true] svg{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0}
|
||||
.dsh-ct-region{display:none !important;flex-direction:column;flex:1;min-height:0;overflow:hidden;padding-right:var(--dsh-sidebar-inline-padding,12px);box-sizing:border-box}
|
||||
[data-dsh-ct-mode=on] .dsh-ct-region{display:flex !important;animation:dsh-ct-fade-in .18s var(--ds-ease-in-out,ease)}
|
||||
[data-dsh-ct-mode=on] .dsh-ct-region{display:flex !important}
|
||||
[class*="_collapsed"][data-dsh-ct-mode=on] .dsh-ct-region,[class*="_collapsed"] .dsh-ct-region{display:none !important}
|
||||
[data-dsh-ct-mode=on] [class*="regionArea"] > :not(.dsh-ct-region){display:none !important}
|
||||
@keyframes dsh-ct-fade-in{0%{opacity:0;transform:translateY(4px)}100%{opacity:1;transform:none}}
|
||||
|
|
@ -870,15 +879,15 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
|
|||
}
|
||||
|
||||
function CronApp({ t, faces }) {
|
||||
const [jobs, setJobs] = useState([])
|
||||
const [runs, setRuns] = useState([])
|
||||
const [jobs, setJobs] = useState(() => listSnapshot.jobs)
|
||||
const [runs, setRuns] = useState(() => listSnapshot.runs)
|
||||
const [selection, setSelection] = useState({ type: 'new' })
|
||||
const [expanded, setExpanded] = useState({})
|
||||
const [form, setForm] = useState(emptyForm())
|
||||
const [error, setError] = useState('')
|
||||
const [cronMode, setCronMode] = useState(false)
|
||||
const [workspaces, setWorkspaces] = useState([])
|
||||
const [catalog, setCatalog] = useState({ groups: [], current: null })
|
||||
const [workspaces, setWorkspaces] = useState(() => listSnapshot.workspaces)
|
||||
const [catalog, setCatalog] = useState(() => listSnapshot.catalog)
|
||||
const [paneHost, setPaneHost] = useState(() => (typeof document === 'undefined' ? null : document.body))
|
||||
const skipAutoSelect = useRef(false)
|
||||
|
||||
|
|
@ -909,11 +918,23 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
|
|||
api('/workspaces').catch(() => ({ workspaces: [] })),
|
||||
api('/models').catch(() => ({ groups: [], current: null })),
|
||||
])
|
||||
setJobs(jobBody.jobs || [])
|
||||
setRuns(histBody.runs || [])
|
||||
setWorkspaces(wsBody.workspaces || [])
|
||||
setCatalog({ groups: modelBody.groups || [], current: modelBody.current || null })
|
||||
faces.cronSessionIds = new Set((histBody.runs || []).map((run) => run.sessionId).filter(Boolean))
|
||||
const nextJobs = jobBody.jobs || []
|
||||
const nextRuns = histBody.runs || []
|
||||
const nextWorkspaces = wsBody.workspaces || []
|
||||
const nextCatalog = { groups: modelBody.groups || [], current: modelBody.current || null }
|
||||
if (jobStamp(listSnapshot.jobs) !== jobStamp(nextJobs)) {
|
||||
listSnapshot.jobs = nextJobs
|
||||
setJobs(nextJobs)
|
||||
}
|
||||
if (runStamp(listSnapshot.runs) !== runStamp(nextRuns)) {
|
||||
listSnapshot.runs = nextRuns
|
||||
setRuns(nextRuns)
|
||||
}
|
||||
listSnapshot.workspaces = nextWorkspaces
|
||||
listSnapshot.catalog = nextCatalog
|
||||
setWorkspaces(nextWorkspaces)
|
||||
setCatalog(nextCatalog)
|
||||
faces.cronSessionIds = new Set(nextRuns.map((run) => run.sessionId).filter(Boolean))
|
||||
setError('')
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err))
|
||||
|
|
@ -1329,20 +1350,17 @@ body>.dsh-ct-main{position:fixed;top:0;right:0;bottom:0;left:var(--dsh-ct-sideba
|
|||
})
|
||||
}
|
||||
|
||||
if (region && (!listRoot || listRoot.parentElement !== region)) {
|
||||
if (appRoot) {
|
||||
try { appRoot.unmount() } catch { /* ignore */ }
|
||||
appRoot = null
|
||||
}
|
||||
if (listRoot) listRoot.remove()
|
||||
if (region) {
|
||||
if (!listRoot) {
|
||||
listRoot = document.createElement('div')
|
||||
listRoot.className = 'dsh-ct-region'
|
||||
listRoot.setAttribute('data-plugin', name)
|
||||
region.appendChild(listRoot)
|
||||
const hostApi = require('react-dom/client')
|
||||
appRoot = hostApi.createRoot(listRoot)
|
||||
appRoot.render(h(CronApp, { t, faces }))
|
||||
}
|
||||
if (listRoot.parentElement !== region) region.appendChild(listRoot)
|
||||
}
|
||||
|
||||
hideNativeCronRows(sidebar)
|
||||
if (sidebar) {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ test('installable bundle declares host apply, client half, unique id, and no @de
|
|||
assert.match(client, /centerCol/)
|
||||
assert.match(client, /data-dsh-ct-host/)
|
||||
assert.match(client, /_collapsed"\]\[data-dsh-ct-mode=on\] \.dsh-ct-region/)
|
||||
assert.match(client, /listRoot\.parentElement !== region\) region\.appendChild\(listRoot\)/)
|
||||
assert.match(client, /jobStamp/)
|
||||
assert.doesNotMatch(client, /\[data-dsh-ct-mode=on\] \.dsh-ct-region\{display:flex !important;animation:/)
|
||||
assert.match(client, /visibility:hidden/)
|
||||
assert.match(client, /createPortal\(editor, paneHost\)/)
|
||||
assert.match(client, /sidebarCollapsed/)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue