296 lines
22 KiB
JavaScript
296 lines
22 KiB
JavaScript
'use strict';
|
|
/**
|
|
* tools/demo-video/record.js: nimmt das Demo-Video als echte Bewegung auf (30 Bilder pro Sekunde): Mauszeiger, Tippen, Klicken, Scrollen,
|
|
* Hervorhebungen, weiche Übergänge und animierte Bildunterschriften, erst im Browser (mit der Demo-Installation), dann im Windows-Programm.
|
|
* Nebenbei schreibt es <raw.mp4>.events.json (Zeitpunkte von Kapiteln, Klicks, Tastenanschlägen, Übergängen). make_music.js erzeugt daraus
|
|
* Musik und Geräusche, die genau zum Bild passen.
|
|
* 1. php tools/demo-video/seed_demo.php <Ordner> 2. php -S 127.0.0.1:8097 -t <Ordner> &
|
|
* 3. electron tools/demo-video/record.js <raw.mp4> 4. bash tools/demo-video/compose.sh <raw.mp4> <Zielordner>
|
|
*/
|
|
const { app, BrowserWindow } = require('electron');
|
|
const { spawn } = require('child_process');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const OUT = path.resolve(process.argv[2] || 'raw.mp4');
|
|
const BASE = 'http://127.0.0.1:8097';
|
|
const MOCK = path.resolve(__dirname, '..', '..', '..', 'desktop', 'test', 'mock.html');
|
|
const W = 1440, H = 900, FPS = 30;
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
app.disableHardwareAcceleration();
|
|
|
|
// ── Gerüst, das in jede Seite eingesetzt wird: Zeiger, Klick-Welle, Hervorhebung, Bildunterschrift, Überblendung ──
|
|
const OVERLAY = `(() => { if (window.__ov) return; window.__ov = true;
|
|
const st = document.createElement('style'); st.textContent = \`
|
|
#__cur{position:fixed;left:0;top:0;width:30px;height:30px;z-index:2147483647;pointer-events:none;filter:drop-shadow(0 2px 3px rgba(0,0,0,.45))}
|
|
#__rip{position:fixed;left:0;top:0;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;background:rgba(14,138,201,.35);border:3px solid #0E8AC9;z-index:2147483646;pointer-events:none;opacity:0}
|
|
#__spot{position:fixed;z-index:2147483640;border-radius:16px;box-shadow:0 0 0 9999px rgba(6,18,28,.58),0 0 0 3px rgba(255,255,255,.92),0 0 28px 6px rgba(18,165,148,.55);transition:all .6s cubic-bezier(.2,.8,.2,1);opacity:0;pointer-events:none}
|
|
#__cap{position:fixed;left:50%;bottom:34px;transform:translate(-50%,40px);opacity:0;transition:opacity .5s ease,transform .5s cubic-bezier(.2,.8,.2,1);z-index:2147483645;display:flex;align-items:center;gap:14px;background:rgba(8,24,36,.94);color:#fff;border-radius:50px;padding:12px 30px 12px 12px;box-shadow:0 14px 36px rgba(0,0,0,.45);font:600 22px 'Segoe UI',Arial,sans-serif;max-width:1260px}
|
|
#__cap.on{opacity:1;transform:translate(-50%,0)}
|
|
#__cap .n{flex:none;background:linear-gradient(135deg,#0E7FBF,#12A594);width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:19px}
|
|
#__cap small{display:block;font-weight:400;font-size:15px;opacity:.78;margin-top:2px}
|
|
#__cover{position:fixed;left:0;top:0;right:0;bottom:0;background:#0b1f2e;z-index:2147483647;opacity:1;transition:opacity .45s ease;pointer-events:none}
|
|
html{scroll-behavior:auto!important}\`;
|
|
document.documentElement.appendChild(st);
|
|
const cur = document.createElement('div'); cur.id = '__cur';
|
|
cur.innerHTML = '<svg width="30" height="30" viewBox="0 0 24 24"><path d="M4 2l15 9.5-6.6 1.4L9.5 20z" fill="#fff" stroke="#10212B" stroke-width="1.4" stroke-linejoin="round"/></svg>';
|
|
const rip = document.createElement('div'); rip.id = '__rip';
|
|
const spot = document.createElement('div'); spot.id = '__spot';
|
|
const cap = document.createElement('div'); cap.id = '__cap';
|
|
const cover = document.createElement('div'); cover.id = '__cover';
|
|
document.documentElement.append(spot, cur, rip, cap, cover);
|
|
window.__pos = window.__pos || { x: 700, y: 520 };
|
|
const place = (x, y) => { cur.style.transform = 'translate(' + x + 'px,' + y + 'px)'; };
|
|
place(window.__pos.x, window.__pos.y);
|
|
window.__move = (x, y, ms) => new Promise((res) => { const from = { ...window.__pos }, t0 = performance.now();
|
|
const f = (t) => { const k = Math.min(1, (t - t0) / ms), e = k < .5 ? 4 * k * k * k : 1 - Math.pow(-2 * k + 2, 3) / 2;
|
|
const lift = Math.sin(k * Math.PI) * 18;
|
|
place(from.x + (x - from.x) * e, from.y + (y - from.y) * e - lift); if (k < 1) requestAnimationFrame(f); else { window.__pos = { x, y }; place(x, y); res(); } };
|
|
requestAnimationFrame(f); });
|
|
window.__ripple = (x, y) => { rip.style.left = x + 'px'; rip.style.top = y + 'px'; rip.animate([{ opacity: .9, transform: 'scale(.3)' }, { opacity: 0, transform: 'scale(1.6)' }], { duration: 520, easing: 'ease-out' }); };
|
|
window.__scrollTo = (el, y, ms) => new Promise((res) => { const from = el.scrollTop, t0 = performance.now();
|
|
const f = (t) => { const k = Math.min(1, (t - t0) / ms), e = k < .5 ? 2 * k * k : 1 - Math.pow(-2 * k + 2, 2) / 2; el.scrollTop = from + (y - from) * e; if (k < 1) requestAnimationFrame(f); else res(); };
|
|
requestAnimationFrame(f); });
|
|
window.__caption = (n, text, sub) => { cap.innerHTML = '<div class="n">' + n + '</div><div>' + text + (sub ? '<small>' + sub + '</small>' : '') + '</div>'; requestAnimationFrame(() => cap.classList.add('on')); };
|
|
window.__hideCaption = () => cap.classList.remove('on');
|
|
window.__spot = (x, y, w, h) => { spot.style.left = x + 'px'; spot.style.top = y + 'px'; spot.style.width = w + 'px'; spot.style.height = h + 'px'; spot.style.opacity = 1; };
|
|
window.__spotOff = () => { spot.style.opacity = 0; };
|
|
window.__cover = (on) => { cover.style.opacity = on ? 1 : 0; };
|
|
})()`;
|
|
|
|
let win, ff, latest = null, lastBuf = null, size = null, written = 0, t0 = 0, pump = null;
|
|
let step = 0;
|
|
const events = [];
|
|
const evt = (type, extra) => { if (t0) events.push({ t: +((Date.now() - t0) / 1000).toFixed(3), type, ...(extra || {}) }); };
|
|
const ev = async (js) => { try { return await win.webContents.executeJavaScript(js, true); } catch (e) { console.log('Fehler im Skript:', String(e).slice(0, 160), '\n ', js.slice(0, 120)); return null; } };
|
|
|
|
function startRecording() {
|
|
ff = spawn('ffmpeg', ['-loglevel', 'error', '-y', '-f', 'rawvideo', '-pix_fmt', 'bgra', '-s', `${size.width}x${size.height}`, '-r', String(FPS), '-i', '-',
|
|
'-vf', `scale=${W}:${H}:flags=lanczos`, '-c:v', 'libx264', '-crf', '15', '-preset', 'fast', '-pix_fmt', 'yuv420p', OUT], { stdio: ['pipe', 'inherit', 'inherit'] });
|
|
t0 = Date.now(); written = 0;
|
|
pump = setInterval(() => {
|
|
if (!latest) return;
|
|
if (latest !== lastBuf) { lastBuf = latest; lastBuf.buf = latest.toBitmap(); }
|
|
const target = Math.floor(((Date.now() - t0) * FPS) / 1000);
|
|
while (written < target && ff.stdin.writableLength < 150e6) { ff.stdin.write(lastBuf.buf); written += 1; }
|
|
}, 8);
|
|
}
|
|
async function stopRecording() {
|
|
clearInterval(pump);
|
|
await new Promise((r) => ff.stdin.end(r));
|
|
await new Promise((r) => ff.on('close', r));
|
|
}
|
|
|
|
// ── Bausteine für Szenen ──
|
|
const find = (spec) => `(() => { const s = ${JSON.stringify(spec)}; if (typeof s === 'string') return document.querySelector(s);
|
|
return [...document.querySelectorAll(s.sel)].find((e) => (e.textContent || '').toLowerCase().includes(s.text.toLowerCase()) && e.getBoundingClientRect().width > 0 && (e.innerText || '').trim().length < 400); })()`;
|
|
const center = async (spec) => ev(`(() => { const el = ${find(spec)}; if (!el) return null; el.scrollIntoView({ block: 'nearest' }); const r = el.getBoundingClientRect(); return { x: r.left + Math.min(r.width / 2, 120), y: r.top + r.height / 2 }; })()`);
|
|
async function move(spec, ms = 800) { const c = await center(spec); if (!c) { console.log('Nicht gefunden:', JSON.stringify(spec)); return null; } await ev(`window.__move(${c.x}, ${c.y}, ${ms})`); return c; }
|
|
async function click(spec, { wait = 400, ms = 800 } = {}) {
|
|
const c = await move(spec, ms); if (!c) return;
|
|
await ev(`window.__ripple(${c.x}, ${c.y})`); evt('click'); await sleep(150);
|
|
await ev(`(() => { const el = ${find(spec)}; if (el) el.click(); })()`); await sleep(wait);
|
|
}
|
|
async function type(spec, text, per = 55) {
|
|
await click(spec, { wait: 120, ms: 650 });
|
|
for (const ch of text) {
|
|
await ev(`(() => { const el = ${find(spec)}; el.value += ${JSON.stringify(ch)}; el.dispatchEvent(new Event('input', { bubbles: true })); })()`);
|
|
evt('key');
|
|
await sleep(per + Math.random() * 35);
|
|
}
|
|
await sleep(200);
|
|
}
|
|
async function scroll(selector, y, ms = 1300) { await ev(`window.__scrollTo(${selector === 'page' ? 'document.scrollingElement' : `document.querySelector(${JSON.stringify(selector)})`}, ${y}, ${ms})`); await sleep(150); }
|
|
async function caption(text, sub) { step += 1; evt('caption'); await ev(`window.__caption(${step}, ${JSON.stringify(text)}, ${JSON.stringify(sub || '')})`); }
|
|
const hide = async () => { await ev(`window.__hideCaption()`); await sleep(420); };
|
|
/** Element (oder der übergeordnete Kasten) hervorheben; der Rest der Seite wird abgedunkelt. */
|
|
async function spot(spec, { closest = '', pad = 12 } = {}) {
|
|
const r = await ev(`(() => { let el = ${find(spec)}; if (!el) return null; ${closest ? `el = el.closest(${JSON.stringify(closest)}) || el;` : ''} el.scrollIntoView({ block: 'center' }); const b = el.getBoundingClientRect(); return { x: b.left, y: b.top, w: b.width, h: b.height }; })()`);
|
|
if (!r) { console.log('Hervorhebung: nicht gefunden', JSON.stringify(spec)); return; }
|
|
evt('pop');
|
|
await ev(`window.__spot(${r.x - pad}, ${r.y - pad}, ${r.w + 2 * pad}, ${r.h + 2 * pad})`);
|
|
}
|
|
const unspot = async () => { await ev(`window.__spotOff()`); await sleep(500); };
|
|
const chapter = (name) => evt('chapter', { name });
|
|
|
|
const CLEAN = `(() => { document.querySelectorAll('div,p,section').forEach((e) => { if (e.children.length < 6 && /SQLite-Modus aktiv/.test(e.textContent) && e.textContent.length < 260) e.remove(); }); document.getElementById('session-timer-bar')?.remove(); })()`;
|
|
const inject = async () => { await ev(OVERLAY); await ev(CLEAN); };
|
|
/** Seite laden: erst überblenden, dann laden, Gerüst einsetzen, wieder aufblenden. */
|
|
async function go(url, opts = {}) {
|
|
if (win.webContents.getURL() && !opts.first) { evt('whoosh'); await ev(`window.__cover && window.__cover(true)`); await sleep(480); }
|
|
const loaded = new Promise((r) => win.webContents.once('did-finish-load', r));
|
|
if (opts.file) win.loadFile(opts.file); else win.loadURL(opts.raw ? url : BASE + url);
|
|
await loaded; await sleep(120);
|
|
await inject(); await sleep(250);
|
|
await ev(`window.__cover(false)`); await sleep(520);
|
|
}
|
|
const waitLoad = () => new Promise((r) => win.webContents.once('did-finish-load', r));
|
|
async function nav(clickFn) { const l = waitLoad(); await clickFn(); evt('whoosh'); await l; await sleep(120); await inject(); await ev(`window.__cover(false)`); await sleep(450); }
|
|
|
|
const card = (html, css = '') => 'data:text/html;charset=utf-8,' + encodeURIComponent(`<!doctype html><meta charset="utf-8"><style>
|
|
html,body{margin:0;width:${W}px;height:${H}px;overflow:hidden;font-family:'Segoe UI',Arial,sans-serif;color:#fff;background:linear-gradient(135deg,#0A5F95,#0E8C7C)}
|
|
.c{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;text-align:center;padding:0 110px;box-sizing:border-box}
|
|
.logo{font-size:110px;font-weight:800;letter-spacing:-3px;animation:pop .9s cubic-bezier(.2,.9,.2,1) both}.logo span{color:#FFD79A}
|
|
.sub{font-size:38px;opacity:0;animation:up .8s .5s ease forwards}.small{font-size:24px;opacity:0;animation:up .8s 1s ease forwards;margin-top:34px}
|
|
.row{display:flex;gap:26px;margin-top:46px}.chip{background:rgba(255,255,255,.16);border-radius:40px;padding:12px 26px;font-size:26px;opacity:0;animation:up .7s ease forwards}
|
|
.chip:nth-child(1){animation-delay:1.0s}.chip:nth-child(2){animation-delay:1.25s}.chip:nth-child(3){animation-delay:1.5s}.chip:nth-child(4){animation-delay:1.75s}
|
|
h2{font-size:68px;margin:0 0 20px;animation:up .8s ease both}ul{list-style:none;padding:0;margin:20px 0 0;text-align:left;font-size:35px;line-height:1.75}
|
|
li{opacity:0;animation:up .7s ease forwards}li:nth-child(1){animation-delay:.35s}li:nth-child(2){animation-delay:.7s}li:nth-child(3){animation-delay:1.05s}li:nth-child(4){animation-delay:1.4s}li:nth-child(5){animation-delay:1.75s}
|
|
li:before{content:"✔";margin-right:20px;color:#FFD79A}
|
|
@keyframes pop{from{opacity:0;transform:scale(.7)}to{opacity:1;transform:none}}@keyframes up{from{opacity:0;transform:translateY(26px)}to{opacity:1;transform:none}}${css}</style><div class="c">${html}</div>`);
|
|
|
|
app.whenReady().then(async () => {
|
|
win = new BrowserWindow({ width: W, height: H, show: false, useContentSize: true, webPreferences: { offscreen: true, contextIsolation: false, nodeIntegration: false, backgroundThrottling: false } });
|
|
win.webContents.setFrameRate(FPS);
|
|
win.webContents.on('paint', (e, dirty, image) => { latest = image; if (!size) size = image.getSize(); });
|
|
win.webContents.on('console-message', () => {});
|
|
await win.loadURL(card('<div class="logo">Desk<span>Hub</span></div>'));
|
|
await sleep(500);
|
|
startRecording();
|
|
try {
|
|
// ── Titel ──
|
|
chapter('intro');
|
|
await win.loadURL(card('<div class="logo">Desk<span>Hub</span></div><div class="sub">Das Helpdesk-System zum Selbsthosten</div><div class="row"><div class="chip">Kundenportal</div><div class="chip">Digitaler Assistent</div><div class="chip">Windows</div><div class="chip">Android</div></div>'));
|
|
await sleep(3300);
|
|
|
|
// ── 1. Kundenportal und digitaler Assistent ──
|
|
chapter('portal');
|
|
await go('/home.php', { first: true });
|
|
await caption('Kundenportal', 'Hilfe finden und Tickets verfolgen');
|
|
await sleep(700);
|
|
await scroll('page', 360, 1400); await scroll('page', 0, 1000);
|
|
await hide();
|
|
await click('#as-bubble', { wait: 900 });
|
|
await caption('Digitaler Assistent', 'Antwortet aus Wissensdatenbank und eigenen Antworten');
|
|
await type('#as-input', 'Wie lange dauert der Versand?');
|
|
await click('#as-send'); await sleep(1100); evt('ding'); await sleep(1400);
|
|
await click({ sel: '.as-btns button', text: 'Das hat geholfen' }, { wait: 800 });
|
|
await type('#as-input', 'Wie kann ich meinen Vertrag beenden?');
|
|
await click('#as-send'); await sleep(1500);
|
|
await hide();
|
|
await caption('Keine Antwort gefunden?', 'Der Assistent bietet an, ein Ticket zu erstellen');
|
|
await sleep(900);
|
|
await click({ sel: '.as-btns button', text: 'Ticket erstellen' }, { wait: 1500 });
|
|
await hide();
|
|
|
|
// ── 2. Mitarbeiterbereich ──
|
|
chapter('staff');
|
|
await go('/staff/login.php');
|
|
await caption('Mitarbeiterbereich', 'Anmelden mit dem eigenen Konto');
|
|
await type('#email', 'lena@demo.example', 45);
|
|
await type('#password', 'Demo-Passwort-1234', 45);
|
|
await nav(() => click('form button[type=submit]', { wait: 100 }));
|
|
await hide();
|
|
await go('/staff/dashboard.php');
|
|
await caption('Übersicht', 'Kennzahlen, Status des Teams und Berichte');
|
|
await sleep(1200);
|
|
await scroll('page', 420, 1600); await sleep(500); await scroll('page', 0, 1200);
|
|
await hide();
|
|
await nav(() => click({ sel: '.sidebar a, nav a, aside a', text: 'Tickets' }, { wait: 100 }));
|
|
await caption('Tickets', 'Filter, Tabs und gespeicherte Ansichten');
|
|
await sleep(1400);
|
|
await nav(() => click({ sel: 'a', text: 'Wiedervorlage' }, { wait: 100 }));
|
|
await sleep(1100);
|
|
await nav(() => click({ sel: 'a', text: 'Offen' }, { wait: 100 }));
|
|
await sleep(700);
|
|
await hide();
|
|
await go('/staff/ticket_view.php?n=260102');
|
|
await caption('Ticket bearbeiten', 'Verlauf, Antworten und interne Notizen');
|
|
await sleep(1500);
|
|
await scroll('page', 700, 1500);
|
|
await hide();
|
|
await caption('Wiedervorlage und Beobachter', 'Später wieder vorlegen, passende Artikel dazu');
|
|
await sleep(500);
|
|
await spot({ sel: 'span', text: 'Wiedervorlage' }, { closest: '.card', pad: 8 });
|
|
await sleep(2200);
|
|
await unspot();
|
|
await ev(`(() => { const s = document.querySelector('select[name=snooze_choice]'); if (s) s.value = '3h'; })()`);
|
|
await nav(() => click({ sel: 'button', text: 'Auf später legen' }, { wait: 100 }));
|
|
await sleep(1500);
|
|
await hide();
|
|
|
|
// ── 3. Digitaler Assistent: Verwaltung ──
|
|
chapter('assistant');
|
|
await go('/staff/assistant.php');
|
|
await caption('Assistenten verwalten', 'Einstellungen, Antworten, Fragen-Log');
|
|
await sleep(1300);
|
|
await nav(() => click({ sel: 'a', text: 'Zusätzliche Antworten' }, { wait: 100 }));
|
|
await sleep(1100);
|
|
await nav(() => click({ sel: 'a', text: 'Fragen-Log' }, { wait: 100 }));
|
|
await sleep(500);
|
|
await hide();
|
|
await caption('Was blieb ohne Antwort?', 'Mit einem Klick eine neue Antwort anlegen');
|
|
await spot({ sel: 'b, strong', text: 'Häufige Fragen ohne Antwort' }, { closest: 'div', pad: 10 });
|
|
await sleep(2300);
|
|
await unspot();
|
|
await nav(() => click({ sel: 'a', text: 'Keine Antwort' }, { wait: 100 }));
|
|
await sleep(1400);
|
|
await hide();
|
|
|
|
// ── 3b. Weitere Funktionen der Weboberfläche ──
|
|
chapter('features');
|
|
const tour = async (url, title, sub, { y = 0, dwell = 2600 } = {}) => {
|
|
await go(url); await caption(title, sub); await sleep(1100);
|
|
if (y) { await scroll('page', y, 1700); await sleep(900); await scroll('page', 0, 1000); } else await sleep(dwell);
|
|
await hide();
|
|
};
|
|
await tour('/staff/tickets.php', 'Alle Tickets', 'Suchen, filtern, sortieren und Sammelaktionen', { dwell: 3200 });
|
|
await tour('/staff/reports.php', 'Berichte', 'Auslastung, Antwortzeiten und Zufriedenheit', { y: 520 });
|
|
await tour('/staff/sla_plans.php', 'SLA-Pläne', 'Reaktions- und Lösungszeiten je Priorität', { dwell: 3000 });
|
|
await tour('/staff/escalation_rules.php', 'Eskalation und Regeln', 'Überfällige Tickets automatisch weitergeben', { dwell: 3000 });
|
|
await tour('/staff/canned_responses.php', 'Textbausteine', 'Fertige Antworten für wiederkehrende Fragen', { dwell: 3000 });
|
|
await tour('/staff/kb_manage.php', 'Wissensdatenbank', 'Artikel für Kunden und den Assistenten', { dwell: 3000 });
|
|
await tour('/staff/customers.php', 'Kunden und Organisationen', 'Alle Tickets eines Kunden auf einen Blick', { dwell: 3000 });
|
|
await tour('/staff/tasks.php', 'Aufgaben', 'Eigene To-dos neben den Tickets', { dwell: 2800 });
|
|
await tour('/staff/time_entries.php', 'Zeiterfassung', 'Aufwand pro Ticket und Kunde', { dwell: 2800 });
|
|
await tour('/staff/status_page.php', 'Status-Seite', 'Störungen öffentlich melden', { dwell: 3000 });
|
|
await tour('/staff/departments.php', 'Abteilungen, Teams und Rollen', 'Rechte genau festlegen', { dwell: 2800 });
|
|
await tour('/staff/api_keys.php', 'Schnittstelle und Webhooks', 'DeskHub mit anderen Systemen verbinden', { dwell: 2800 });
|
|
await tour('/staff/audit_log.php', 'Protokoll', 'Wer hat wann was geändert', { dwell: 2800 });
|
|
await tour('/staff/security_check.php', 'Sicherheitsprüfung', 'Zeigt Schwachstellen der Installation', { dwell: 3000 });
|
|
await tour('/staff/backup.php', 'Datensicherung', 'Backups per Klick, auch auf ein NAS', { dwell: 3200 });
|
|
await tour('/staff/plugins.php', 'Plugins', 'Erweiterungen ohne Eingriff am Server', { dwell: 3000 });
|
|
await tour('/staff/apps.php', 'Apps und Minecraft-Plugin', 'Windows, Android und Minecraft zum Herunterladen', { dwell: 3200 });
|
|
await tour('/staff/settings.php', 'Einstellungen', 'Name, Farben, E-Mail, Sicherheit und mehr', { y: 500 });
|
|
|
|
// ── 4. Windows-Programm ──
|
|
chapter('windows');
|
|
await go('', { file: MOCK });
|
|
await ev(`(() => { const o = window.deskhub.api; window.deskhub.api = async (r) => { const res = await o(r); return res.error && /^Mock:/.test(res.error.message) ? { data: { items: [], rules: [], holidays: [], hours: [], staffOptions: [], departments: [], permissionKeys: [], fieldOptions: [], operatorOptions: [], actionOptions: [] } } : res; }; go('dashboard'); })()`);
|
|
await sleep(900);
|
|
await caption('Windows-Programm', 'Dieselben Funktionen auf dem Desktop');
|
|
await sleep(1800);
|
|
await click({ sel: '.side .nav, .side button, .side a', text: 'Tickets' }, { wait: 1200 });
|
|
await click({ sel: 'tr.click, tr', text: 'Drucker druckt nicht' }, { wait: 1500 });
|
|
await hide();
|
|
await caption('Auf später legen und beobachten', 'Auch im Windows-Programm');
|
|
await click({ sel: 'button', text: 'Auf später' }, { wait: 1500 });
|
|
await ev(`document.querySelector('.modal-bg')?.remove()`);
|
|
await ev(`state.staff.role = 'admin'; go('admin', 'assistant')`); evt('whoosh'); await sleep(1000);
|
|
await hide();
|
|
await caption('Assistent im Windows-Programm', 'Antworten, Fragen-Log, Wörter, Ausprobieren');
|
|
await click({ sel: '.tabs button', text: 'Fragen-Log' }, { wait: 2200 });
|
|
await hide();
|
|
|
|
// ── 5. Android und Abspann ──
|
|
chapter('android');
|
|
evt('whoosh');
|
|
await win.loadURL(card('<h2>Android-App</h2><ul><li>Neues, helles Design mit schwebender Navigation</li><li>Wiedervorlage, Beobachten, „Dazu passend“</li><li>Digitalen Assistenten komplett verwalten</li><li>Widget in drei Größen, hell und dunkel</li></ul>'));
|
|
await sleep(3700);
|
|
chapter('extras');
|
|
evt('whoosh');
|
|
await win.loadURL(card('<h2>Und noch mehr</h2><ul><li>Chrome-Erweiterung: Zähler, Suche, Ticket anlegen</li><li>Minecraft-Plugin für Paper, Spigot und BungeeCord</li><li>Windows-Programm auch für Linux und Mac</li><li>Plugins und Sprachpakete, zum Beispiel Englisch</li><li>Updates der Apps direkt aus DeskHub</li></ul>'));
|
|
await sleep(3700);
|
|
chapter('outro');
|
|
evt('whoosh');
|
|
await win.loadURL(card('<div class="logo" style="font-size:84px">Desk<span>Hub</span></div><div class="sub">Kundenportal · Weboberfläche · Windows · Android</div><div class="small">Mehr in der README</div>'));
|
|
await sleep(4200);
|
|
} catch (e) { console.error('Fehler:', e); process.exitCode = 1; }
|
|
const total = (Date.now() - t0) / 1000;
|
|
await stopRecording();
|
|
fs.writeFileSync(OUT + '.events.json', JSON.stringify({ duration: +total.toFixed(3), events }, null, 1));
|
|
console.log(`Aufnahme fertig: ${OUT} (${(written / FPS).toFixed(1)} Sekunden, ${size.width}x${size.height}), ${events.length} Ereignisse`);
|
|
app.quit();
|
|
});
|