perf: optimisations RAM et rendu + fix logo sidebar en production
- Fix logo sidebar : chemin relatif au lieu d'absolu (cassé en prod) - Compression icone_sidebar.png : 448KB → 24KB (resize 128x128) - Cache DOM EnclosView : élimine ~360 querySelector/sec dans le RAF - Throttle RAF à 4fps quand aucun timer actif (idle) - Cache version Sidebar : un seul appel IPC au lieu d'un par update() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6e0af993ca
commit
61bbac0adc
@ -324,8 +324,18 @@ export class App {
|
||||
}
|
||||
|
||||
// ── Live update loop ──────────────────────────────────────────
|
||||
private lastRafTime = 0;
|
||||
|
||||
private startAnimationLoop(): void {
|
||||
const loop = () => {
|
||||
const loop = (now: number) => {
|
||||
// Throttle à ~4fps quand aucun timer ne tourne (idle)
|
||||
const data = this.getDashboardData();
|
||||
const anyRunning = data.enclosSummaries.some(e => e.running);
|
||||
if (!anyRunning && now - this.lastRafTime < 250) {
|
||||
this.rafId = requestAnimationFrame(loop);
|
||||
return;
|
||||
}
|
||||
this.lastRafTime = now;
|
||||
this.updateTabDots();
|
||||
if (this.activeChild) this.activeChild.update();
|
||||
this.rafId = requestAnimationFrame(loop);
|
||||
|
||||
@ -15,10 +15,28 @@ import { UndoManager } from '@presentation/services/UndoManager';
|
||||
|
||||
const ALL_GAUGES: GaugeType[] = ['baffeur', 'caresseur', 'foudroyeur', 'abreuvoir', 'dragofesse', 'mangeoire'];
|
||||
|
||||
interface CachedGaugeEls {
|
||||
tier: Element | null;
|
||||
bar: HTMLElement | null;
|
||||
empty: Element | null;
|
||||
inp: HTMLInputElement | null;
|
||||
}
|
||||
|
||||
export class EnclosView {
|
||||
private el: HTMLElement | null = null;
|
||||
private enclosId = 0;
|
||||
private ddCards: Map<number, DragodindeCard> = new Map();
|
||||
private cachedEls: {
|
||||
elapsed: Element | null;
|
||||
gcd: Element | null;
|
||||
tbtn: HTMLButtonElement | null;
|
||||
resetBtn: HTMLElement | null;
|
||||
doneBanner: HTMLElement | null;
|
||||
doneResetBtn: HTMLElement | null;
|
||||
ddCount: Element | null;
|
||||
addBtn: HTMLButtonElement | null;
|
||||
gauges: Map<string, CachedGaugeEls>;
|
||||
} | null = null;
|
||||
|
||||
constructor(
|
||||
private commandBus: CommandBus,
|
||||
@ -179,9 +197,35 @@ export class EnclosView {
|
||||
`;
|
||||
|
||||
this.bindEvents(enc);
|
||||
this.cacheElements(enc);
|
||||
this.renderDdCards(enc);
|
||||
}
|
||||
|
||||
private cacheElements(enc: Enclos): void {
|
||||
if (!this.el) return;
|
||||
const eId = this.enclosId;
|
||||
const gauges = new Map<string, CachedGaugeEls>();
|
||||
enc.activeGauges.forEach(gid => {
|
||||
gauges.set(gid, {
|
||||
tier: this.el!.querySelector(`#gtier-${eId}-${gid}`),
|
||||
bar: this.el!.querySelector<HTMLElement>(`#gbar-${eId}-${gid}`),
|
||||
empty: this.el!.querySelector(`#gempty-${eId}-${gid}`),
|
||||
inp: this.el!.querySelector<HTMLInputElement>(`#glvl-${eId}-${gid}`),
|
||||
});
|
||||
});
|
||||
this.cachedEls = {
|
||||
elapsed: this.el.querySelector(`#elapsed-${eId}`),
|
||||
gcd: this.el.querySelector(`#gcd-${eId}`),
|
||||
tbtn: this.el.querySelector<HTMLButtonElement>(`#tbtn-${eId}`),
|
||||
resetBtn: this.el.querySelector<HTMLElement>(`#treset-${eId}`),
|
||||
doneBanner: this.el.querySelector<HTMLElement>(`#done-banner-${eId}`),
|
||||
doneResetBtn: this.el.querySelector<HTMLElement>(`#done-reset-${eId}`),
|
||||
ddCount: this.el.querySelector(`#ddcount-${eId}`),
|
||||
addBtn: this.el.querySelector<HTMLButtonElement>(`#adddd-${eId}`),
|
||||
gauges,
|
||||
};
|
||||
}
|
||||
|
||||
private bindEvents(enc: Enclos): void {
|
||||
if (!this.el) return;
|
||||
const eId = this.enclosId;
|
||||
@ -307,54 +351,52 @@ export class EnclosView {
|
||||
}
|
||||
|
||||
update(): void {
|
||||
if (!this.el) return;
|
||||
if (!this.el || !this.cachedEls) return;
|
||||
const enc = this.getEnc();
|
||||
const eId = this.enclosId;
|
||||
const { globalMax, allDone, started, el: elSec, ddDone } = enclosGlobalState(enc);
|
||||
const c = this.cachedEls;
|
||||
const { globalMax, allDone, started, el: elSec } = enclosGlobalState(enc);
|
||||
const running = enc.timer.running;
|
||||
|
||||
/* Complétion automatique : toutes les cibles atteintes → une seule alarme */
|
||||
if (allDone && running && enc.dragodindes.length > 0 && enc.activeGauges.length > 0) {
|
||||
this.commandBus.execute({ type: 'complete-timer', enclosId: eId });
|
||||
this.commandBus.execute({ type: 'complete-timer', enclosId: this.enclosId });
|
||||
this.renderInner();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Elapsed clock */
|
||||
const elapsedEl = this.el.querySelector(`#elapsed-${eId}`);
|
||||
if (elapsedEl) elapsedEl.textContent = fmtClock(elSec);
|
||||
if (c.elapsed) c.elapsed.textContent = fmtClock(elSec);
|
||||
|
||||
/* Global countdown */
|
||||
const gcdEl = this.el.querySelector(`#gcd-${eId}`);
|
||||
if (gcdEl) {
|
||||
if (c.gcd) {
|
||||
if (enc.activeGauges.length > 0 && enc.dragodindes.length > 0) {
|
||||
if (allDone) {
|
||||
gcdEl.textContent = '✅';
|
||||
c.gcd.textContent = '✅';
|
||||
} else if (!isFinite(globalMax)) {
|
||||
gcdEl.textContent = '∞';
|
||||
c.gcd.textContent = '∞';
|
||||
} else {
|
||||
gcdEl.textContent = fmtClock(globalMax);
|
||||
c.gcd.textContent = fmtClock(globalMax);
|
||||
}
|
||||
} else {
|
||||
gcdEl.textContent = '--:--:--';
|
||||
c.gcd.textContent = '--:--:--';
|
||||
}
|
||||
}
|
||||
|
||||
/* Timer button state */
|
||||
const tbtn = this.el.querySelector<HTMLButtonElement>(`#tbtn-${eId}`);
|
||||
if (tbtn) {
|
||||
if (c.tbtn) {
|
||||
const timerIcon = running ? 'pause' : 'play_arrow';
|
||||
const timerText = running ? 'PAUSE' : (enc.timer.pausedAt && !enc.alerted['__done__'] ? 'REPRENDRE' : 'DÉMARRER');
|
||||
tbtn.className = running ? 'enc-start-btn enc-btn-pause' : 'enc-start-btn';
|
||||
tbtn.innerHTML = `<span class="material-symbols-outlined">${timerIcon}</span>${timerText}`;
|
||||
c.tbtn.className = running ? 'enc-start-btn enc-btn-pause' : 'enc-start-btn';
|
||||
c.tbtn.innerHTML = `<span class="material-symbols-outlined">${timerIcon}</span>${timerText}`;
|
||||
}
|
||||
|
||||
/* Reset button visibility */
|
||||
const resetBtn = this.el.querySelector<HTMLElement>(`#treset-${eId}`);
|
||||
if (resetBtn) resetBtn.style.display = started ? '' : 'none';
|
||||
if (c.resetBtn) c.resetBtn.style.display = started ? '' : 'none';
|
||||
|
||||
/* Gauge config live updates (tier badges and bars decay over time) */
|
||||
/* Gauge config live updates (cached — no querySelector per frame) */
|
||||
enc.activeGauges.forEach(gid => {
|
||||
const g = c.gauges.get(gid);
|
||||
if (!g) return;
|
||||
const startGl = started ? (enc.timer.snapGauges[gid] ?? enc.gaugeLevels[gid]) : enc.gaugeLevels[gid];
|
||||
const curGl = started ? enclosGaugeCurGl(enc, gid) : startGl;
|
||||
const tn = tierNum(curGl);
|
||||
@ -363,62 +405,48 @@ export class EnclosView {
|
||||
const emptyTime = curGl > 0 ? timeToGain(curGl, curGl) : 0;
|
||||
const emptyStr = emptyTime === Infinity ? '∞' : fmt(emptyTime);
|
||||
|
||||
const tierEl = this.el!.querySelector(`#gtier-${eId}-${gid}`);
|
||||
if (tierEl) {
|
||||
tierEl.textContent = curGl > 0 ? `Tier ${tn} · ±${tr}/tick` : 'Jauge vide';
|
||||
if (g.tier) {
|
||||
g.tier.textContent = curGl > 0 ? `Tier ${tn} · ±${tr}/tick` : 'Jauge vide';
|
||||
}
|
||||
|
||||
const barEl = this.el!.querySelector<HTMLElement>(`#gbar-${eId}-${gid}`);
|
||||
if (barEl) barEl.style.width = `${pct.toFixed(1)}%`;
|
||||
|
||||
const emptyEl = this.el!.querySelector(`#gempty-${eId}-${gid}`);
|
||||
if (emptyEl) {
|
||||
if (g.bar) g.bar.style.width = `${pct.toFixed(1)}%`;
|
||||
if (g.empty) {
|
||||
if (curGl > 0) {
|
||||
emptyEl.textContent = `Vide en ${emptyStr}`;
|
||||
emptyEl.classList.remove('enc-gauge-alert');
|
||||
g.empty.textContent = `Vide en ${emptyStr}`;
|
||||
g.empty.classList.remove('enc-gauge-alert');
|
||||
} else if (started && running) {
|
||||
emptyEl.textContent = '⚠ Rechargez la jauge';
|
||||
emptyEl.classList.add('enc-gauge-alert');
|
||||
g.empty.textContent = '⚠ Rechargez la jauge';
|
||||
g.empty.classList.add('enc-gauge-alert');
|
||||
} else {
|
||||
emptyEl.textContent = 'Vide';
|
||||
emptyEl.classList.remove('enc-gauge-alert');
|
||||
g.empty.textContent = 'Vide';
|
||||
g.empty.classList.remove('enc-gauge-alert');
|
||||
}
|
||||
}
|
||||
|
||||
/* Mettre à jour la valeur affichée de l'input et son état en temps réel */
|
||||
const inp = this.el!.querySelector<HTMLInputElement>(`#glvl-${eId}-${gid}`);
|
||||
if (inp) {
|
||||
// Synchroniser data-running avec l'état réel du timer
|
||||
inp.dataset.running = started ? '1' : '0';
|
||||
// Mettre à jour la valeur affichée (sauf si l'utilisateur est en train de taper)
|
||||
if (document.activeElement !== inp) {
|
||||
inp.value = String(Math.round(started ? curGl : (enc.gaugeLevels[gid] ?? 0)));
|
||||
if (g.inp) {
|
||||
g.inp.dataset.running = started ? '1' : '0';
|
||||
if (document.activeElement !== g.inp) {
|
||||
g.inp.value = String(Math.round(started ? curGl : (enc.gaugeLevels[gid] ?? 0)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Done banner */
|
||||
const doneBanner = this.el.querySelector<HTMLElement>(`#done-banner-${eId}`);
|
||||
if (doneBanner) {
|
||||
doneBanner.style.display = (allDone && started && enc.dragodindes.length > 0 && enc.activeGauges.length > 0) ? '' : 'none';
|
||||
if (c.doneBanner) {
|
||||
c.doneBanner.style.display = (allDone && started && enc.dragodindes.length > 0 && enc.activeGauges.length > 0) ? '' : 'none';
|
||||
}
|
||||
|
||||
/* Bouton "Nouvelle fournée" — visible uniquement si toutes les DD ont 20000 en maturité, endurance et amour */
|
||||
const doneResetBtn = this.el.querySelector<HTMLElement>(`#done-reset-${eId}`);
|
||||
if (doneResetBtn) {
|
||||
/* Bouton "Nouvelle fournée" */
|
||||
if (c.doneResetBtn) {
|
||||
const allMaxed = enc.dragodindes.length > 0 && enc.dragodindes.every(dd =>
|
||||
dd.stats.maturite >= 20000 && dd.stats.endurance >= 20000 && dd.stats.amour >= 20000
|
||||
);
|
||||
doneResetBtn.style.display = allMaxed ? '' : 'none';
|
||||
c.doneResetBtn.style.display = allMaxed ? '' : 'none';
|
||||
}
|
||||
|
||||
/* DD count */
|
||||
const ddCountEl = this.el.querySelector(`#ddcount-${eId}`);
|
||||
if (ddCountEl) ddCountEl.textContent = `${enc.dragodindes.length}/10`;
|
||||
if (c.ddCount) c.ddCount.textContent = `${enc.dragodindes.length}/10`;
|
||||
|
||||
/* Add button state */
|
||||
const addBtn = this.el.querySelector<HTMLButtonElement>(`#adddd-${eId}`);
|
||||
if (addBtn) addBtn.disabled = enc.dragodindes.length >= 10;
|
||||
if (c.addBtn) c.addBtn.disabled = enc.dragodindes.length >= 10;
|
||||
|
||||
/* Update each DD card */
|
||||
enc.dragodindes.forEach(dd => {
|
||||
@ -439,6 +467,7 @@ export class EnclosView {
|
||||
destroy(): void {
|
||||
this.ddCards.forEach(card => card.destroy());
|
||||
this.ddCards.clear();
|
||||
this.cachedEls = null;
|
||||
this.el?.remove();
|
||||
this.el = null;
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import { esc } from '@presentation/helpers/format';
|
||||
|
||||
export class Sidebar {
|
||||
private el: HTMLElement | null = null;
|
||||
private cachedVersion: string | null = null;
|
||||
|
||||
constructor(
|
||||
private uiState: UIState,
|
||||
@ -32,7 +33,7 @@ export class Sidebar {
|
||||
html += `
|
||||
<div class="sb-header">
|
||||
<div class="sb-logo-wrap">
|
||||
<img src="/icone_sidebar.png" alt="logo" class="sb-logo-img" />
|
||||
<img src="icone_sidebar.png" alt="logo" class="sb-logo-img" />
|
||||
</div>
|
||||
<div class="sb-brand">
|
||||
<span class="sb-brand-name">Obsidienne</span>
|
||||
@ -111,9 +112,15 @@ export class Sidebar {
|
||||
}
|
||||
|
||||
private fetchAndInjectVersion(): void {
|
||||
if (this.cachedVersion) {
|
||||
const verEl = this.el?.querySelector('#sb-ver');
|
||||
if (verEl) verEl.textContent = `v${this.cachedVersion}`;
|
||||
return;
|
||||
}
|
||||
const api = (window as any).electronAPI;
|
||||
if (!api?.getVersion) return;
|
||||
api.getVersion().then((v: string) => {
|
||||
this.cachedVersion = v;
|
||||
const verEl = this.el?.querySelector('#sb-ver');
|
||||
if (verEl) verEl.textContent = `v${v}`;
|
||||
}).catch(() => {});
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 438 KiB After Width: | Height: | Size: 24 KiB |
Loading…
Reference in New Issue
Block a user