fix: vídeo no app Linux travava a janela; abrir no player do sistema (0.3.2)
Release / Cria a release (push) Canceled after 0s
Release / CLI (linux-aarch64) (push) Canceled after 0s
Release / CLI (linux-x86_64) (push) Canceled after 0s
Release / CLI (macos-aarch64) (push) Canceled after 0s
Release / CLI (macos-x86_64) (push) Canceled after 0s
Release / CLI (windows-x86_64) (push) Canceled after 0s
Release / App desktop (linux-x86_64) (push) Canceled after 0s
Release / App desktop (macos-aarch64) (push) Canceled after 0s
Release / App desktop (macos-x86_64) (push) Canceled after 0s
Release / App desktop (windows-x86_64) (push) Canceled after 0s
Release / Publica a release (push) Canceled after 0s
Release / Cria a release (push) Canceled after 0s
Release / CLI (linux-aarch64) (push) Canceled after 0s
Release / CLI (linux-x86_64) (push) Canceled after 0s
Release / CLI (macos-aarch64) (push) Canceled after 0s
Release / CLI (macos-x86_64) (push) Canceled after 0s
Release / CLI (windows-x86_64) (push) Canceled after 0s
Release / App desktop (linux-x86_64) (push) Canceled after 0s
Release / App desktop (macos-aarch64) (push) Canceled after 0s
Release / App desktop (macos-x86_64) (push) Canceled after 0s
Release / App desktop (windows-x86_64) (push) Canceled after 0s
Release / Publica a release (push) Canceled after 0s
O AppImage traz o WebKitGTK sem os plugins de mídia do GStreamer; criar um <video> derrubava o processo da página. O app Linux não cria mais <video>/<audio>: oferece "Abrir no player do sistema". Clicar no preview abre a visualização grande. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
282161d297
commit
97f940f261
17 files changed
+345
-87
No files matched your search
@@ -3,7 +3,7 @@
|
||||
[package]
|
||||
name = "guara-gui"
|
||||
description = "Guará: interface gráfica de recuperação de arquivos"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/lucasrguerra/guara"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Guara",
|
||||
"version": "0.3.1",
|
||||
"version": "0.3.2",
|
||||
"identifier": "dev.guara.app",
|
||||
"build": {
|
||||
"frontendDist": "../ui"
|
||||
|
||||
+7
-1
@@ -323,7 +323,7 @@ code, .mono { overflow-wrap: anywhere; font-family: var(--mono); font-size: .92e
|
||||
.pv-pdfbar .pg { font-size: 13px; color: var(--text-2); min-width: 110px; text-align: center; }
|
||||
.pv-pdfwrap { padding: 8px; } .pv-canvas { display: block; background: #fff; box-shadow: 0 2px 14px rgba(0, 0, 0, .25); }
|
||||
|
||||
dialog.lightbox { width: min(1180px, 96vw); height: min(90vh, 920px); padding: 0; display: none; grid-template-rows: auto minmax(0, 1fr) auto; overflow: hidden; }
|
||||
dialog.lightbox { width: min(1760px, 98vw); height: min(95vh, 1100px); padding: 0; display: none; grid-template-rows: auto minmax(0, 1fr) auto; overflow: hidden; }
|
||||
dialog.lightbox[open] { display: grid; }
|
||||
.lb-head, .lb-foot { display: flex; gap: 12px; align-items: center; padding: 12px 16px; }
|
||||
.lb-head { border-bottom: 1px solid var(--line); } .lb-foot { border-top: 1px solid var(--line); flex-wrap: wrap; justify-content: space-between; }
|
||||
@@ -454,3 +454,9 @@ dialog.lightbox[open] { display: grid; }
|
||||
.hit .ctx { font-family: var(--mono); font-size: 12px; overflow-wrap: anywhere; color: var(--text-2); }
|
||||
.hit mark { background: var(--accent-soft); color: var(--accent); padding: 0 2px; border-radius: 3px; font-weight: 600; }
|
||||
@media (max-width: 860px) { .hit { grid-template-columns: 100px 1fr; } .hit span:nth-child(2), .hit span:nth-child(3) { display: none; } }
|
||||
|
||||
.xp-insp #xpPv { cursor: zoom-in; }
|
||||
.pv-card { width: 100%; min-height: 220px; display: grid; place-content: center; justify-items: center; gap: 10px; padding: 24px; text-align: center; }
|
||||
.pv-card b { overflow-wrap: anywhere; font-size: 15px; }
|
||||
.pv-bigicon { font-size: 52px; line-height: 1; }
|
||||
.pv-why { max-width: 38ch; font-size: 12.5px; color: var(--text-2); }
|
||||
+1
-1
@@ -431,7 +431,7 @@ function renderScan() {
|
||||
</div>`;
|
||||
$('#backBtn').onclick = () => (S.device ? openDevice(S.device) : renderHome());
|
||||
$('#progressArea').onclick = onProgressClick;
|
||||
EX = createExplorer({ host: $('#xpHost'), job: S.job, rpc, esc, human, fileUrl, thumbUrl, onRecover: recover, toast });
|
||||
EX = createExplorer({ host: $('#xpHost'), job: S.job, rpc, esc, human, fileUrl, thumbUrl, onRecover: recover, toast, noInlineMedia: !!tauri && /Linux/i.test(navigator.userAgent) });
|
||||
}
|
||||
|
||||
function paintStatus() {
|
||||
|
||||
+12
-2
@@ -31,6 +31,11 @@ const I = {
|
||||
|
||||
export function createExplorer(deps) {
|
||||
const { host, job, rpc, esc, human, fileUrl, thumbUrl, onRecover, toast } = deps;
|
||||
const inlineMedia = !deps.noInlineMedia;
|
||||
const openExternal = (it) => {
|
||||
toast('Preparando para abrir no player do sistema…');
|
||||
rpc('open_external', { job, id: it.id }).then(() => toast('Aberto no player do sistema')).catch((err) => toast(err.message));
|
||||
};
|
||||
const E = {
|
||||
filters: { category: null, year: null, month: null, undated: false, folder: null, camera: null, origin: null, minConf: null, gps: false, dedupe: false, bigOnly: false, mp1: false, query: '' },
|
||||
sort: 'date',
|
||||
@@ -570,7 +575,12 @@ export function createExplorer(deps) {
|
||||
<dl class="kv"><dt>Pasta</dt><dd>${esc(it.dir || (it.origin === 'carved' ? 'Encontrado por assinatura' : '/'))}</dd>${timesHtml(it)}${it.camera ? `<dt>Câmera</dt><dd>${esc(it.camera)}</dd>` : ''}${it.px ? `<dt>Resolução</dt><dd>${it.px[0]} × ${it.px[1]} px</dd>` : ''}</dl>
|
||||
<div class="xp-meta" id="xpMeta"></div>
|
||||
<div class="row"><button class="btn sm" data-i="full">Tela cheia</button><button class="btn primary sm" data-i="rec1">Recuperar este arquivo</button></div>`;
|
||||
stopPv = renderPreview($('#xpPv'), it, { url, thumb: kindOf(it) === 'image' });
|
||||
stopPv = renderPreview($('#xpPv'), it, { url, thumb: kindOf(it) === 'image', inlineMedia, onOpen: () => openLightbox(idxOf(it.id)), onExternal: () => openExternal(it) });
|
||||
// clicar na miniatura abre o modal grande
|
||||
$('#xpPv').addEventListener('click', (ev) => {
|
||||
if (ev.target.closest('button, a, .pv-pdfbar')) return;
|
||||
openLightbox(idxOf(it.id));
|
||||
});
|
||||
const my = ++metaToken;
|
||||
rpc('metadata', { job, id: it.id }).then((m) => {
|
||||
if (my !== metaToken || !$('#xpMeta')) return;
|
||||
@@ -602,7 +612,7 @@ export function createExplorer(deps) {
|
||||
<div class="lb-body"><div class="viewer large" id="lbHost"></div></div>
|
||||
<div class="lb-foot"><span class="sub">${it.note ? esc(it.note) : '← → para navegar · Esc para fechar'}</span><button class="btn primary sm" id="lbRec">Recuperar este arquivo</button></div>`;
|
||||
stopLb?.();
|
||||
stopLb = renderPreview(d.querySelector('#lbHost'), it, { url: fileUrl(job, it.id), large: true });
|
||||
stopLb = renderPreview(d.querySelector('#lbHost'), it, { url: fileUrl(job, it.id), large: true, inlineMedia, onExternal: () => openExternal(it) });
|
||||
const go = (n) => { const t = i + n; if (t >= 0 && t < E.items.length) { setCursor(t, { reveal: true }); openLightbox(t); } };
|
||||
d.querySelector('#lbPrev').onclick = () => go(-1);
|
||||
d.querySelector('#lbNext').onclick = () => go(1);
|
||||
|
||||
+28
-3
@@ -131,8 +131,10 @@ async function image(host, item, url, state, thumb) {
|
||||
if (state.dead) return resolve();
|
||||
host.innerHTML = '';
|
||||
host.appendChild(img);
|
||||
host.classList.add('zoomable');
|
||||
img.onclick = () => host.classList.toggle('actual');
|
||||
if (!thumb) {
|
||||
host.classList.add('zoomable');
|
||||
img.onclick = () => host.classList.toggle('actual');
|
||||
}
|
||||
if (!thumb) note(host, `${img.naturalWidth} × ${img.naturalHeight} px. Clique para alternar entre ajustar e tamanho real.`);
|
||||
resolve();
|
||||
};
|
||||
@@ -272,12 +274,35 @@ async function zip(host, item, url, state) {
|
||||
|
||||
// ------------------------------------------------------------------ API
|
||||
/** Desenha a pré-visualização de `item` dentro de `host`. Devolve uma função de limpeza. */
|
||||
export function renderPreview(host, item, { url, large = false, thumb = false }) {
|
||||
function mediaCard(host, item, kind, { large, inlineMedia, onOpen, onExternal }) {
|
||||
const ico = kind === 'video' ? '🎞' : '🎵';
|
||||
const canInline = inlineMedia && large;
|
||||
host.innerHTML = `<div class="pv-card"><div class="pv-bigicon" aria-hidden="true">${ico}</div>
|
||||
<b>${esc(item.name)}</b><span class="sub">${esc(KIND_LABEL[kind])} · ${human(item.size)}</span>
|
||||
${inlineMedia
|
||||
? '<button class="btn primary" data-pv="open">Reproduzir em tela grande</button>'
|
||||
: '<button class="btn primary" data-pv="ext">Abrir no player do sistema</button><span class="pv-why">Neste app o ' + (kind === 'video' ? 'vídeo' : 'áudio') + ' abre no seu player: o navegador embutido do Linux não traz os codecs e travaria a janela.</span>'}
|
||||
</div>`;
|
||||
host.onclick = (e) => {
|
||||
const a = e.target.closest('[data-pv]')?.dataset.pv;
|
||||
if (a === 'open') onOpen?.();
|
||||
else if (a === 'ext') onExternal?.();
|
||||
};
|
||||
void canInline;
|
||||
}
|
||||
|
||||
export function renderPreview(host, item, { url, large = false, thumb = false, inlineMedia = true, onOpen = null, onExternal = null }) {
|
||||
const state = { dead: false, cleanup: [] };
|
||||
host.dataset.url = url;
|
||||
host.classList.remove('zoomable', 'actual');
|
||||
host.innerHTML = '<div class="pv-loading"><i></i></div>';
|
||||
const kind = kindOf(item);
|
||||
// Vídeo e áudio: nunca criamos <video>/<audio> no painel pequeno, e no Linux do app desktop
|
||||
// (WebKitGTK sem plugins de mídia) nem na tela grande, pois o processo da página morre.
|
||||
if ((kind === 'video' || kind === 'audio') && (!large || !inlineMedia)) {
|
||||
mediaCard(host, item, kind, { large, inlineMedia, onOpen, onExternal });
|
||||
return () => { state.dead = true; };
|
||||
}
|
||||
(async () => {
|
||||
try {
|
||||
if (!item.size) { host.innerHTML = ''; note(host, 'Arquivo vazio.'); return; }
|
||||
|
||||
Reference in new issue
Block a user