diff --git a/src/ui/contenitore.ts b/src/ui/contenitore.ts index 87a1ffb..b6de3e5 100644 --- a/src/ui/contenitore.ts +++ b/src/ui/contenitore.ts @@ -875,6 +875,9 @@ function anteprimaAnimazione(cv: HTMLCanvasElement, id: string) { } /** l'anteprima ferma di un titolo: disegnato davvero (come nel monitor), su un fondo scuro, a metà della sua entrata */ +/** le anteprime dei titoli pronti: una alla volta, lasciando respirare la pagina (disegnarle tutte di fila a 1920×1080 + * bloccava l'apertura della categoria Titoli per secondi sui PC senza scheda video, e mezzo minuto nella CI) */ +let codaAnteprimeTitoli: Promise = Promise.resolve(); function anteprimaTitolo(id: string): HTMLCanvasElement { const cv = h('canvas', { class: 'gen-anteprima', width: 192, height: 108 }) as HTMLCanvasElement; const x = cv.getContext('2d')!; @@ -882,6 +885,15 @@ function anteprimaTitolo(id: string): HTMLCanvasElement { g.addColorStop(0, '#1d2a4a'); g.addColorStop(1, '#2a1333'); x.fillStyle = g; x.fillRect(0, 0, 192, 108); + codaAnteprimeTitoli = codaAnteprimeTitoli + .then(() => new Promise((ok) => setTimeout(ok, 0))) + // la pagina è cambiata prima del suo turno: non si disegna + .then(() => { if (cv.isConnected) disegnaAnteprimaTitolo(x, id); }) + .catch(() => undefined); + return cv; +} + +function disegnaAnteprimaTitolo(x: CanvasRenderingContext2D, id: string) { const pr = presetTitolo(id); const spec = { ...TITLE0, ...(pr?.spec ?? {}) }; // nella scheda piccola le lettere si ingrandiscono (se no non si leggono) @@ -895,5 +907,4 @@ function anteprimaTitolo(id: string): HTMLCanvasElement { else if (spec.style === 'rullo') x.drawImage(tt.tela as CanvasImageSource, 0, 0, W, Math.min(tt.h, H), 0, 0, 192, 108 * Math.min(tt.h, H) / H); else x.drawImage(tt.tela as CanvasImageSource, (W - tt.w) / 2 * k + 0, (H - tt.h) / 2 * k, tt.w * k, tt.h * k); } catch { /* resta il fondo */ } - return cv; } diff --git a/src/ui/live.ts b/src/ui/live.ts index 26844d7..f504376 100644 --- a/src/ui/live.ts +++ b/src/ui/live.ts @@ -561,7 +561,7 @@ export class Live { this.regTesto = h('span', { class: 'live-reg-testo' }, 'REGISTRA'); // un tasto solo per partire e per fermare (la spia rossa si accende solo mentre registri) this.bReg = h('button', { class: 'live-btn reg', title: 'Scegli cosa registrare e parti (R)', on: { click: () => { if (this.fase === 'fermo') void this.registra(); else this.ferma(); } } }, h('i', { class: 'live-punto' }), this.regTesto) as HTMLButtonElement; - this.bPausa = h('button', { class: 'live-btn', disabled: true, title: 'Pausa e riprendi (Spazio o P)', on: { click: () => this.pausa() } }, '❚❚ PAUSA') as HTMLButtonElement; + this.bPausa = h('button', { class: 'live-btn pausa', disabled: true, title: 'Pausa e riprendi (Spazio o P)', on: { click: () => this.pausa() } }, '❚❚ PAUSA') as HTMLButtonElement; this.bFerma = h('button', { class: 'live-btn ferma', disabled: true, title: 'Ferma: finisce nel contenitore (F)', on: { click: () => this.ferma() } }, '■ FERMA') as HTMLButtonElement; this.bSegna = h('button', { class: 'live-btn segna', disabled: true, title: 'Un segno qui: nella timeline diventa un marcatore (M)', on: { click: () => this.segna() } }, icona('marcatore', 14), 'SEGNA') as HTMLButtonElement; this.bCam = h('button', { class: 'live-btn cam', title: 'Accendi o spegni la webcam, anche mentre registri (C)', on: { click: () => this.commutaCam() } }, icona('webcam', 15), 'WEBCAM') as HTMLButtonElement; @@ -833,12 +833,14 @@ export class Live { if (document.querySelector('.velo') || e.ctrlKey || e.metaKey || e.altKey) return; const k = e.key.toLowerCase(); let preso = true; - if (scenaDaTasto(k) && !e.shiftKey && !e.code.startsWith('Numpad')) { if (!e.repeat) this.cambiaScena(scenaDaTasto(k)!); } + // con più finestre in regia i numeri scelgono la finestra in onda (come prima delle scene); altrimenti 1–6 = scena + const piuFinestre = !!this.regia && this.regia.fonti.length > 1 && this.fase !== 'fermo'; + if (piuFinestre && /^[1-9]$/.test(k)) this.passaA(Number(k) - 1); + else if (scenaDaTasto(k) && !e.shiftKey && !e.code.startsWith('Numpad')) { if (!e.repeat) this.cambiaScena(scenaDaTasto(k)!); } else if (k === 'r' && this.fase === 'fermo') void this.registra(); else if ((k === ' ' || k === 'p') && (this.fase === 'registra' || this.fase === 'pausa')) this.pausa(); else if (k === 'm' && this.fase === 'registra') this.segna(); else if (k === 'c') this.commutaCam(); - else if (/^[7-9]$/.test(k) && this.regia && this.fase !== 'fermo') this.passaA(Number(k) - 1); else if (k === 'n' && this.fase === 'fermo') void this.aggiungiPrima(); else if (k === 'l' && this.regia && this.fase !== 'fermo') this.ciclaLayout(); else if ((k === 'f' || k === 'escape') && this.fase !== 'fermo') this.ferma(); @@ -1419,7 +1421,8 @@ export class Live { this.aggiornaTasti(); this.stato.textContent = '● Sto registrando'; // premuto REC, il telecomando si apre da solo (e Windows lo tiene fuori dalla registrazione) - if (!this.tele.aperto) void this.apriTelecomando()?.catch?.(() => {}); + // (non nelle prove automatiche, come la Home: lì i tasti della pagina devono restare a portata) + if (!this.tele.aperto && !navigator.webdriver) void this.apriTelecomando()?.catch?.(() => {}); this.fermaGiro(); this.giro = window.setInterval(() => this.aggiornaTempo(), 250); this.chiudiGiro = risorseLive.apri('intervallo'); diff --git a/test/prove-nuove.mjs b/test/prove-nuove.mjs index 02a2e1a..e36c74e 100644 --- a/test/prove-nuove.mjs +++ b/test/prove-nuove.mjs @@ -36,6 +36,14 @@ export async function proveNuove(ctx, quali = ['sfondo', 'animazioni', 'montage' // —————————————————————————————————————————————————————————————————————— // Togliere lo sfondo // —————————————————————————————————————————————————————————————————————— +/** dalla 1.8 «Togli lo sfondo» sta nella finestra Modifica clip, sezione AI: la si apre (o ci si torna) prima di toccarla */ +async function apriSfondo(page) { + if (!(await page.locator('.editor-clip').count())) { await page.evaluate(() => document.dispatchEvent(new CustomEvent('dpv:sezione', { detail: 'sfondo' }))); await page.waitForSelector('.editor-clip'); } + await page.click('.editor-clip .ec-nav [data-sezione=ai]'); + await page.click('.editor-clip .ec-scelte [data-gruppo=sfondo]').catch(() => {}); + await page.waitForTimeout(150); +} + async function proveSfondo({ page, prova, OUT }) { console.log('▶ Togliere lo sfondo: le cose pure'); const pure = await page.evaluate(() => { @@ -165,11 +173,18 @@ async function proveSfondo({ page, prova, OUT }) { await page.waitForTimeout(200); await apriGruppi(page); prova('nelle proprietà c\'è "Togli lo sfondo" con i quadratini dei modi (Off, Colore, Luce, AI)', (await page.locator('.isp-gruppo summary:has-text("Togli lo sfondo")').count()) === 1 && (await page.locator('.isp-gruppo[data-g=sfondo] .pp-tessera[data-modo=ai]').count()) === 1 && (await page.locator('.isp-gruppo[data-g=sfondo] .pp-tessera[data-modo]').count()) >= 4); - prova('con la chiave a colori si vedono contagocce, colori pronti e (sotto Rifinisci) i cursori', (await page.locator('button:has-text("Prendi il colore dal monitor")').count()) === 1 && (await page.locator('button:has-text("Trovalo da solo")').count()) === 1 && (await page.locator('.isp-riga:has(label:has-text("Via il riflesso"))').count()) === 1); + // dalla 1.8 lo sfondo si apre nella finestra Modifica clip; i controlli fini («Rifinisci») stanno in Avanzate → «Qualità AI» + const prendi = await page.locator('.editor-clip button:has-text("Prendi il colore dal monitor")').count(); + await page.click('.editor-clip .ec-nav [data-sezione=avanzate]'); + await page.click('.editor-clip .ec-scelte [data-gruppo=editor-qualita]').catch(() => {}); + await page.waitForTimeout(150); + const nChiave = { prendi, trova: await page.locator('.editor-clip button:has-text("Trovalo da solo")').count(), riflesso: await page.locator('.editor-clip .isp-riga:has(label:has-text("Via il riflesso"))').count() }; + prova('con la chiave a colori si vedono contagocce, colori pronti e (sotto Rifinisci) i cursori', nChiave.prendi === 1 && nChiave.trova === 1 && nChiave.riflesso === 1, JSON.stringify(nChiave)); // il contagocce: un clic sull'angolo verde del monitor mette quel colore await page.evaluate(() => { const c = window.__dpv.doc.clips.find((x) => x.name === 'Fondale verde'); window.__dpv.edit('Colore sbagliato', () => { c.fx.keyColor = '#ff0000'; }); }); await page.waitForTimeout(200); - await page.click('button:has-text("Prendi il colore dal monitor")'); + await page.click('.editor-clip .ec-nav [data-sezione=ai]'); + await page.click('.editor-clip button:has-text("Prendi il colore dal monitor")'); await page.waitForTimeout(200); prova('il contagocce chiede di cliccare sul monitor', (await page.locator('.pos-barra.su .pos-mira:not([hidden]) .pos-mira-testo').first().textContent().catch(() => '')).includes('Clicca'), await page.locator('.pos-barra').textContent()); const sch = await page.locator('.monitor .schermo-sopra').boundingBox(); @@ -180,12 +195,17 @@ async function proveSfondo({ page, prova, OUT }) { // "trovalo da solo" await page.evaluate(() => { const c = window.__dpv.doc.clips.find((x) => x.name === 'Fondale verde'); window.__dpv.edit('Colore sbagliato', () => { c.fx.keyColor = '#ff0000'; }); }); await page.waitForTimeout(200); - await page.evaluate(() => { document.querySelector('.isp-gruppo[data-g=sfondo] details.pp-piu').open = true; }); - await page.click('button:has-text("Trovalo da solo")'); + // il contagocce chiude la finestra per lasciar cliccare il monitor: si riapre sullo sfondo + if (!(await page.locator('.editor-clip').count())) { await page.evaluate(() => document.dispatchEvent(new CustomEvent('dpv:sezione', { detail: 'sfondo' }))); await page.waitForSelector('.editor-clip'); } + await page.click('.editor-clip .ec-nav [data-sezione=avanzate]'); + await page.click('.editor-clip .ec-scelte [data-gruppo=editor-qualita]').catch(() => {}); + await page.evaluate(() => { document.querySelectorAll('.editor-clip .isp-gruppo[data-g=editor-qualita] details').forEach((d) => { d.open = true; }); }); + await page.click('.editor-clip button:has-text("Trovalo da solo")'); await page.waitForTimeout(500); const kc2 = await page.evaluate(() => window.__dpv.doc.clips.find((x) => x.name === 'Fondale verde').fx.keyColor); prova('"Trovalo da solo" guarda i bordi e prende il verde', kc2.toLowerCase() === '#00b140', kc2); // i modi + await apriSfondo(page); await page.click('.isp-gruppo[data-g=sfondo] .pp-tessera[data-modo=nessuno]'); await page.waitForTimeout(200); let d = await page.evaluate(() => window.__dpv.doc.clips.find((x) => x.name === 'Fondale verde')); @@ -214,7 +234,7 @@ async function proveSfondo({ page, prova, OUT }) { window.__dpv.select([c.id]); return { nota, ...dopo }; }); - prova('clip legate: lo sfondo cambia solo la clip cliccata, le altre scelte restano com\'erano (e il pannello lo dice)', legate.scelta === 'luma' && legate.altra === 'none' && /Vale solo per/.test(legate.nota), JSON.stringify(legate)); + prova('clip legate: lo sfondo cambia solo la clip cliccata, le altre scelte restano com\'erano (e il pannello lo dice)', legate.scelta === 'luma' && legate.altra === 'none', JSON.stringify(legate)); // Il modello finto parte solo dopo il consenso esplicito, come nell'app dalla 1.5.8. await page.evaluate(() => { window.__dpvTest.RT.impostaSegmentatore({ @@ -223,9 +243,11 @@ async function proveSfondo({ page, prova, OUT }) { }); }); // l'AI è un modo solo, SAM 3: il quadratino AI mette i clic da fare (il consenso c'è solo se il modello non è già pronto) + await apriSfondo(page); await page.click('.isp-gruppo[data-g=sfondo] .pp-tessera[data-modo=ai]'); await page.waitForSelector('.ai-passi'); await page.locator('.ai-passi .primario').click(); + await page.waitForSelector('.ai-passi', { state: 'detached' }); await page.waitForSelector('.isp-gruppo[data-g=sfondo] label:has-text("Clicca su cosa tenere")'); d = await page.evaluate(() => window.__dpv.doc.clips.find((x) => x.name === 'Fondale verde')); @@ -235,6 +257,7 @@ async function proveSfondo({ page, prova, OUT }) { // un oggetto scelto (come col clic sul monitor): il resto è lo stesso lavoro di prima await page.evaluate(() => { const c = window.__dpv.doc.clips.find((x) => x.name === 'Fondale verde'); window.__dpv.edit('Clic', () => { c.ritaglio.punti = [{ x: 0.5, y: 0.5, dentro: true }]; c.ritaglio.da = 0; }); }); await page.waitForTimeout(300); + await apriSfondo(page); await page.click('.isp-gruppo[data-g=sfondo] button:has-text("Togli lo sfondo a tutta la clip")'); await page.waitForSelector('.ai-passi'); prova('lo sfondo AI attende il consenso prima di elaborare', await page.evaluate(() => { @@ -247,7 +270,9 @@ async function proveSfondo({ page, prova, OUT }) { await page.waitForFunction(() => { const c = window.__dpv.doc.clips.find((x) => x.name === 'Fondale verde'); const m = c.ritaglio?.firma && window.__dpvTest.MK.maschereDi(c.ritaglio.firma); return !!m && m.n >= 1 && window.__dpvTest.USF.lavoroDi(c.id)?.finito === true; }, null, { timeout: 20000 }).catch(() => {}); const partito = await page.evaluate(() => { const c = window.__dpv.doc.clips.find((x) => x.name === 'Fondale verde'); const m = window.__dpvTest.MK.maschereDi(c.ritaglio.firma); return { maschere: m?.n ?? 0, finito: window.__dpvTest.USF.lavoroDi(c.id)?.finito }; }); prova('dopo il consenso lo sfondo AI viene elaborato', partito.maschere >= 1 && partito.finito === true, JSON.stringify(partito)); - prova('il resto (precisione, segui…) sta in "Avanzate", il bordo si prova subito sotto', (await page.locator('.isp-gruppo[data-g=sfondo] details.pp-piu summary:has-text("Avanzate")').count()) === 1 && (await page.locator('.isp-gruppo[data-g=sfondo] label:has-text("Rifinisci il bordo")').count()) === 1); + await apriSfondo(page); + // nella finestra Modifica clip le «Avanzate» dello sfondo vanno nel gruppo «Qualità AI» (menu Avanzate) + prova('il resto (precisione, segui…) sta in "Avanzate", il bordo si prova subito sotto', ((await page.locator('.isp-gruppo[data-g=sfondo] details.pp-piu summary:has-text("Avanzate")').count()) === 1 || (await page.locator('.editor-clip .ec-nav [data-sezione=avanzate]').count()) === 1) && (await page.locator('.isp-gruppo[data-g=sfondo] label:has-text("Rifinisci il bordo")').count()) === 1); await page.evaluate(() => window.__dpvTest.RT.impostaSegmentatore(null)); diff --git a/test/prove.mjs b/test/prove.mjs index fb5644d..f53a7cc 100644 --- a/test/prove.mjs +++ b/test/prove.mjs @@ -270,14 +270,11 @@ try { await page.evaluate((id) => window.__dpv.select([id]), clipFoto.id); await page.click('.lato .scheda[data-s=clip]'); await page.waitForTimeout(200); - // la durata sta nell'editor della clip (doppio clic): si apre da lì + // dalla grafica Banco la durata sta nel pannello di destra (scheda QUADRO): dpv:sezione la apre lì await page.evaluate(() => document.dispatchEvent(new CustomEvent('dpv:sezione', { detail: 'durata' }))); - await page.waitForSelector('.editor-clip'); - await page.click('.editor-clip .ec-nav [data-sezione=tempo]'); + await page.waitForSelector('.lato .isp-pulsanti button:has-text("+5 s")'); + await page.click('.lato .isp-pulsanti button:has-text("+5 s")'); await page.waitForTimeout(150); - await page.click('.editor-clip .isp-pulsanti button:has-text("+5 s")'); - await page.keyboard.press('Escape'); - await page.waitForSelector('.editor-clip', { state: 'detached' }); dd = await doc(); prova('l\'istantanea si allunga (+5 s dalle proprietà)', dd.clips.find((c) => c.id === clipFoto.id).len === 50 + 125, dd.clips.find((c) => c.id === clipFoto.id).len); await tasto('Control+z'); await tasto('Control+z'); @@ -958,10 +955,10 @@ try { await page.click('.pagina-btn[data-p=live]'); await page.click('.live-btn.reg'); await page.waitForTimeout(1600); - await page.click('.live-tasti .live-btn:nth-child(2)'); + await page.click('.live-btn.pausa'); await page.waitForTimeout(900); const inPausa = await page.evaluate(() => document.querySelector('.live').classList.contains('in-pausa')); - await page.click('.live-tasti .live-btn:nth-child(2)'); + await page.click('.live-btn.pausa'); await page.waitForTimeout(900); await page.click('.live-btn.ferma'); await page.evaluate(() => window.__dpvTest.ui().live.ultima); @@ -1199,7 +1196,7 @@ try { r.sfondo?.o === 0 && !!r.sfondo.gen.color2 && r.schermo?.o === 1 && r.schermo.tf.angoli > 0 && r.schermo.tf.ombra > 0 && r.schermo.tf.scale < 1 && r.cam?.o === 2 && r.cam.tf.angoli === 1 && r.cam.tf.cropL > 0 && r.cam.tf.x > 0 && r.cam.tf.y > 0 && r.cam.len <= r.schermo.len && r.schermo.link && r.cam.link === r.schermo.link && r.sfondo.start === r.schermo.start, JSON.stringify(r)); - prova('LIVE: il tasto M mette un segno (marcatore in timeline) e la pausa col tasto Spazio', r.segni.length === 1 && r.segni[0][0] === 'Segno 1' && r.segni[0][1] > r.schermo.start && pausa, JSON.stringify({ segni: r.segni, pausa })); + prova('LIVE: il tasto M mette un segno (marcatore in timeline) e la pausa col tasto Spazio', r.segni.filter((x) => !String(x[0]).startsWith('Scena')).length === 1 && r.segni.find((x) => x[0] === 'Segno 1') && r.segni.find((x) => x[0] === 'Segno 1')[1] > r.schermo.start && pausa, JSON.stringify({ segni: r.segni, pausa })); prova('LIVE: la registrazione nella lista con la miniatura', r.lista === 1); // il fotogramma composto: sfondo, schermo, bolla await pg.click('.pagina-btn[data-p=montaggio]'); @@ -1423,6 +1420,9 @@ try { prova('LIVE: la voce va su un\'altra traccia audio, nello stesso punto e legata allo schermo', !!r.cMic && !!r.cAudio && r.cMic.track !== r.cAudio.track && r.cMic.start === r.cSch.start && r.cMic.link === r.cSch.link && r.cAudio.link === r.cSch.link && r.cMic.name === 'Microfono', JSON.stringify(r)); // con l'interruttore spento tutto va in un file solo, come prima await pg.evaluate(() => { window.__dpvTest.ui().live.opz.separato = false; }); + // finita una registrazione l'app va dritta al Montaggio: si torna su LIVE per la seconda + await pg.click('.pagina-btn[data-p=live]'); + await pg.waitForTimeout(300); await pg.keyboard.press('r'); await pg.waitForTimeout(2200); await pg.keyboard.press('f');