From 567e5622f8d156b5bd2bf0171bfb4e551f9eba02 Mon Sep 17 00:00:00 2001 From: Mateusz7410 Date: Fri, 6 Nov 2026 16:57:16 +0000 Subject: [PATCH] chore: loop the landing demo without a reset and drop its screenshots The hero card faded to black to restart, so a finished chain now hands over to the next one. It links to the Job Timeline page, which keeps the screenshots. --- .../theme/components/JobTimelineDemo.vue | 384 +++++++++++------- website/index.md | 18 - 2 files changed, 226 insertions(+), 176 deletions(-) diff --git a/website/.vitepress/theme/components/JobTimelineDemo.vue b/website/.vitepress/theme/components/JobTimelineDemo.vue index b5ed297..3258467 100644 --- a/website/.vitepress/theme/components/JobTimelineDemo.vue +++ b/website/.vitepress/theme/components/JobTimelineDemo.vue @@ -3,78 +3,130 @@ import { computed, onBeforeUnmount, onMounted, ref } from 'vue'; import { withBase } from 'vitepress'; const PAGES = 8; -const PAGE_MS = 450; -const CHUNK_START_MS = 1800; +const PAGE_MS = 600; +const CHUNK_START_MS = 2400; const CHUNK_END_MS = CHUNK_START_MS + PAGE_MS * PAGES; -const ADDED_JOB_END_MS = CHUNK_END_MS + 1600; -const CHAIN_END_MS = ADDED_JOB_END_MS + 1600; -const LOOP_MS = CHAIN_END_MS + 2400; +const ADDED_JOB_END_MS = CHUNK_END_MS + 2200; +const CHAIN_END_MS = ADDED_JOB_END_MS + 2200; +const HAND_OVER_MS = CHAIN_END_MS + 4000; const STILL_FRAME_MS = CHUNK_START_MS + PAGE_MS * 5; const STATUS_LABELS = { ok: 'Completed', run: 'Running', wait: 'Enqueued' }; -const startingState = () => ({ +const RUNS = [ + { + name: 'Order approval pipeline', + first: 'Validate order', + second: 'Credit check', + third: 'Reserve stock', + chunk: 'Tax 16 lines', + added: 'Notify sales team', + last: 'Sync to ERP' + }, + { + name: 'Nightly stock sync', + first: 'Read warehouse feed', + second: 'Match SKUs', + third: 'Adjust reservations', + chunk: 'Update 16 locations', + added: 'Alert buyers', + last: 'Publish to storefront' + }, + { + name: 'Invoice export', + first: 'Collect invoices', + second: 'Apply tax rules', + third: 'Render PDFs', + chunk: 'Send 16 invoices', + added: 'Flag late payers', + last: 'Post to ledger' + } +]; + +const runningChain = run => ({ + name: run.name, + addedJobName: run.added, + open: false, + isNew: false, + finished: false, + age: 'just now', seconds: 12, pagesDone: 0, - finished: false, - newChain: false, steps: [ - { id: 'validate', name: 'Validate order', status: 'ok', meta: '4 s' }, - { id: 'credit', name: 'Credit check', status: 'ok', meta: '6 s' }, - { id: 'reserve', name: 'Reserve stock', status: 'run', meta: 'on the platform' }, - { id: 'tax', name: 'Tax 16 lines', status: 'wait', chunk: true }, - { id: 'sync', name: 'Sync to ERP', status: 'wait' } + { id: 'first', name: run.first, status: 'ok', meta: '4 s' }, + { id: 'second', name: run.second, status: 'ok', meta: '6 s' }, + { id: 'third', name: run.third, status: 'run', meta: 'on the platform' }, + { id: 'chunk', name: run.chunk, status: 'wait', chunk: true }, + { id: 'last', name: run.last, status: 'wait' } ] }); -const state = ref(startingState()); -const fading = ref(false); +const finishedChain = (run, age) => { + const chain = runningChain(run); + chain.steps.forEach(step => (step.status = 'ok')); + return { ...chain, age, finished: true, seconds: 21, pagesDone: PAGES }; +}; + +const openChain = chain => ({ ...chain, open: true }); + +const chains = ref([openChain(runningChain(RUNS[0])), finishedChain(RUNS[2], '1 min ago')]); +const runIndex = ref(0); +const completedCount = ref(1); const timers = []; -const stepById = id => state.value.steps.find(step => step.id === id); +const currentChain = () => chains.value.find(chain => chain.open); +const stepOf = (chain, id) => chain.steps.find(step => step.id === id); function setStep(id, status, meta) { - Object.assign(stepById(id), { status, meta }); + Object.assign(stepOf(currentChain(), id), { status, meta }); } function finishPage() { - state.value.pagesDone += 1; + currentChain().pagesDone += 1; } function startChunk() { - setStep('reserve', 'ok', '5 s'); - setStep('tax', 'run'); - state.value.seconds += 2; + setStep('third', 'ok', '5 s'); + setStep('chunk', 'run'); + currentChain().seconds += 2; } function addJobAfterChunk() { - setStep('tax', 'ok', '4 s'); - state.value.steps.splice(4, 0, { - id: 'notify', - name: 'Notify sales team', + const chain = currentChain(); + setStep('chunk', 'ok', '4 s'); + chain.steps.splice(4, 0, { + id: 'added', + name: chain.addedJobName, status: 'run', isNew: true, - tag: '↳ added by Tax 16 lines', + tag: `↳ added by ${stepOf(chain, 'chunk').name}`, meta: 'on the platform' }); - state.value.seconds += 4; + chain.seconds += 4; } function runLastStep() { - setStep('notify', 'ok', '1 s'); - setStep('sync', 'run', 'on the platform'); - state.value.newChain = true; - state.value.seconds += 2; + setStep('added', 'ok', '1 s'); + setStep('last', 'run', 'on the platform'); + currentChain().seconds += 2; + const nextRun = RUNS[(runIndex.value + 1) % RUNS.length]; + chains.value.unshift({ ...runningChain(nextRun), isNew: true }); } function finishChain() { - setStep('sync', 'ok', '2 s'); - state.value.finished = true; -} - -function restart() { - state.value = startingState(); - fading.value = false; + setStep('last', 'ok', '2 s'); + currentChain().finished = true; + completedCount.value += 1; +} + +function handOverToNextChain() { + const finished = currentChain(); + const next = chains.value[0]; + finished.open = false; + next.open = true; + next.isNew = false; + chains.value = [next, finished]; + runIndex.value = (runIndex.value + 1) % RUNS.length; schedule(); } @@ -90,9 +142,9 @@ const beats = () => [ ]; function schedule() { + timers.length = 0; beats().forEach(([at, beat]) => timers.push(setTimeout(beat, at))); - timers.push(setTimeout(() => (fading.value = true), LOOP_MS - 300)); - timers.push(setTimeout(restart, LOOP_MS)); + timers.push(setTimeout(handOverToNextChain, HAND_OVER_MS)); } onMounted(() => { @@ -107,57 +159,53 @@ onMounted(() => { onBeforeUnmount(() => timers.forEach(clearTimeout)); -const inFlight = computed(() => (state.value.finished ? 0 : 1) + (state.value.newChain ? 1 : 0)); +const inFlight = computed(() => chains.value.filter(chain => !chain.finished).length); const chips = computed(() => [ - { label: 'All', count: 3 + (state.value.newChain ? 1 : 0), active: true }, + { label: 'All', count: inFlight.value + completedCount.value + 1, active: true }, { label: 'In flight', count: inFlight.value, live: true }, { label: 'Failed', count: 1 }, - { label: 'Completed', count: 1 + (state.value.finished ? 1 : 0) } + { label: 'Completed', count: completedCount.value } ]); -const stepsDone = computed(() => state.value.steps.filter(step => step.status === 'ok').length); +const stepsDoneOf = chain => chain.steps.filter(step => step.status === 'ok').length; -const percent = computed(() => { - const jobs = state.value.steps.filter(step => !step.chunk); +function percentOf(chain) { + const jobs = chain.steps.filter(step => !step.chunk); const jobsDone = jobs.filter(step => step.status === 'ok').length; - return Math.round(((jobsDone + state.value.pagesDone) / (jobs.length + PAGES)) * 100); -}); + return Math.round(((jobsDone + chain.pagesDone) / (jobs.length + PAGES)) * 100); +} -const progressText = computed( - () => - `${stepsDone.value} of ${state.value.steps.length} steps completed · ` + - `${state.value.pagesDone} of ${PAGES} pages` -); - -const chainMeta = computed(() => { - const steps = `${state.value.steps.length} steps · ${PAGES} pages`; - return state.value.finished - ? `just now · ${state.value.seconds} s · ${steps}` - : `just now · running ${state.value.seconds} s · ${steps}`; -}); +const progressTextOf = chain => + `${stepsDoneOf(chain)} of ${chain.steps.length} steps completed · ` + + `${chain.pagesDone} of ${PAGES} pages`; -const squares = computed(() => +function metaOf(chain) { + const steps = `${chain.steps.length} steps · ${PAGES} pages`; + return chain.finished + ? `${chain.age} · ${chain.seconds} s · ${steps}` + : `${chain.age} · running ${chain.seconds} s · ${steps}`; +} + +const squaresOf = chain => Array.from({ length: PAGES }, (_, page) => { - if (page < state.value.pagesDone) return 'ok'; - return page === state.value.pagesDone && stepById('tax').status === 'run' ? 'run' : 'todo'; - }) -); - -const pageSummary = computed(() => - stepById('tax').status === 'run' - ? `page ${Math.min(state.value.pagesDone + 1, PAGES)} of ${PAGES}` - : `${state.value.pagesDone} of ${PAGES} pages` -); + if (page < chain.pagesDone) return 'ok'; + return page === chain.pagesDone && stepOf(chain, 'chunk').status === 'run' ? 'run' : 'todo'; + }); + +const pageSummaryOf = chain => + stepOf(chain, 'chunk').status === 'run' + ? `page ${Math.min(chain.pagesDone + 1, PAGES)} of ${PAGES}` + : `${chain.pagesDone} of ${PAGES} pages`;