v1.1.4 - correctif de bug mineur + features.
This commit is contained in:
parent
2f4fe578ab
commit
4dae272706
12
README.md
12
README.md
@ -70,6 +70,18 @@ dd-timer/
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### v1.1.4
|
||||||
|
- ✨ **Reinitialisation des statistiques** — bouton dans l'onglet Statistiques pour remettre a zero tous les bebes, historiques et stats archivees (avec confirmation)
|
||||||
|
- 🔧 Correction "vider l'enclos" / "nouvelle fournee" / "supprimer enclos" — remplacement de confirm() par un dialogue natif Electron pour corriger le bug qui rendait tous les inputs inutilisables
|
||||||
|
- 🔧 Correction du bouton "Quitter" — utilisation de process.exit() pour forcer l'arret complet du processus
|
||||||
|
|
||||||
|
### v1.1.3
|
||||||
|
- ✨ **Inputs intelligents** — les champs numeriques se vident au clic pour saisir facilement, et restaurent la valeur precedente si rien n'est change
|
||||||
|
- 🔧 Correction du bug "enclos vide" — apres avoir vide un enclos ou lance une nouvelle fournee, une dragodinde est automatiquement ajoutee et les inputs restent editables
|
||||||
|
- 🔧 Correction "nouvelle fournee" — ne vide plus l'historique de session des bebes, seul "vider l'enclos" le fait
|
||||||
|
- 🔧 Correction des statistiques globales — supprimer un enclos conserve desormais ses stats dans les statistiques globales (archivage automatique)
|
||||||
|
- 🔧 Correction du bouton "Quitter" — la fermeture complete de l'app fonctionne maintenant correctement
|
||||||
|
|
||||||
### v1.1.2
|
### v1.1.2
|
||||||
- 💾 **Sauvegarde persistante** — les donnees sont maintenant sauvegardees dans un fichier JSON, plus de perte de statistiques apres une mise a jour
|
- 💾 **Sauvegarde persistante** — les donnees sont maintenant sauvegardees dans un fichier JSON, plus de perte de statistiques apres une mise a jour
|
||||||
- ❌ **Dialogue de fermeture** — cliquer sur la croix propose de minimiser en arriere-plan ou de quitter completement
|
- ❌ **Dialogue de fermeture** — cliquer sur la croix propose de minimiser en arriere-plan ou de quitter completement
|
||||||
|
|||||||
18
main.js
18
main.js
@ -65,8 +65,8 @@ function createWindow() {
|
|||||||
detail: 'Minimiser garde l\'app en arriere-plan.\nLes alarmes continueront de sonner.',
|
detail: 'Minimiser garde l\'app en arriere-plan.\nLes alarmes continueront de sonner.',
|
||||||
});
|
});
|
||||||
if (choice === 1) {
|
if (choice === 1) {
|
||||||
isQuitting = true;
|
if (tray) { tray.destroy(); tray = null; }
|
||||||
app.quit();
|
process.exit(0);
|
||||||
} else {
|
} else {
|
||||||
mainWindow.hide();
|
mainWindow.hide();
|
||||||
}
|
}
|
||||||
@ -142,6 +142,20 @@ ipcMain.on('show-notification', (event, { title, body }) => {
|
|||||||
fireNotification(title, body);
|
fireNotification(title, body);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Dialogue de confirmation natif (remplace confirm() du renderer qui casse les inputs)
|
||||||
|
ipcMain.handle('show-confirm', (event, { title, message, detail }) => {
|
||||||
|
const choice = dialog.showMessageBoxSync(mainWindow, {
|
||||||
|
type: 'question',
|
||||||
|
buttons: ['Annuler', 'Confirmer'],
|
||||||
|
defaultId: 0,
|
||||||
|
cancelId: 0,
|
||||||
|
title: title || 'Confirmation',
|
||||||
|
message: message || '',
|
||||||
|
detail: detail || '',
|
||||||
|
});
|
||||||
|
return choice === 1;
|
||||||
|
});
|
||||||
|
|
||||||
// ─── SAUVEGARDE FICHIER (persistante entre mises à jour) ─────────────────
|
// ─── SAUVEGARDE FICHIER (persistante entre mises à jour) ─────────────────
|
||||||
const dataFile = path.join(app.getPath('userData'), 'dd-timer-data.json');
|
const dataFile = path.join(app.getPath('userData'), 'dd-timer-data.json');
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "minuteur-dragodinde",
|
"name": "minuteur-dragodinde",
|
||||||
"version": "1.1.2",
|
"version": "1.1.4",
|
||||||
"description": "Minuteur elevage Dragodinde Dofus 3",
|
"description": "Minuteur elevage Dragodinde Dofus 3",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"author": "Mickael",
|
"author": "Mickael",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
showNotification: (title, body) => ipcRenderer.send('show-notification', { title, body }),
|
showNotification: (title, body) => ipcRenderer.send('show-notification', { title, body }),
|
||||||
sendNtfy: (url, title, message) => ipcRenderer.send('send-ntfy', { url, title, message }),
|
sendNtfy: (url, title, message) => ipcRenderer.send('send-ntfy', { url, title, message }),
|
||||||
focusWindow: () => ipcRenderer.send('focus-window'),
|
focusWindow: () => ipcRenderer.send('focus-window'),
|
||||||
|
showConfirm: (title, message, detail) => ipcRenderer.invoke('show-confirm', { title, message, detail }),
|
||||||
onPlayAlarmSound: (cb) => ipcRenderer.on('play-alarm-sound', () => cb()),
|
onPlayAlarmSound: (cb) => ipcRenderer.on('play-alarm-sound', () => cb()),
|
||||||
|
|
||||||
// Version
|
// Version
|
||||||
|
|||||||
@ -569,7 +569,7 @@ const RACES_DATA={
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let S={enclos:[],activeId:null,nextEnclosId:1,alarmSound:'arpege',notifsEnabled:true,ntfyTopic:''};
|
let S={enclos:[],activeId:null,nextEnclosId:1,alarmSound:'arpege',notifsEnabled:true,ntfyTopic:'',archivedStats:[]};
|
||||||
const NTFY_BASE='https://ntfy.mickael-pol.fr';
|
const NTFY_BASE='https://ntfy.mickael-pol.fr';
|
||||||
const NTFY_REDIRECT='https://ntfy-redirect.mickael-pol.fr';
|
const NTFY_REDIRECT='https://ntfy-redirect.mickael-pol.fr';
|
||||||
const lastTickIdx={};
|
const lastTickIdx={};
|
||||||
@ -856,11 +856,22 @@ function addEnclos(){
|
|||||||
S.enclos.push(e);S.activeId=e.id;
|
S.enclos.push(e);S.activeId=e.id;
|
||||||
save();render();addDD(e.id);
|
save();render();addDD(e.id);
|
||||||
}
|
}
|
||||||
function removeEnclos(id){
|
async function removeEnclos(id){
|
||||||
if(S.enclos.length<=1)return;
|
if(S.enclos.length<=1)return;
|
||||||
|
const enc=S.enclos.find(e=>e.id===id);
|
||||||
|
if(!enc)return;
|
||||||
|
const ok=await window.electronAPI.showConfirm('Supprimer l\'enclos',`Supprimer "${enc.name}" ?`,'Les statistiques globales seront conservees.');
|
||||||
|
if(!ok)return;
|
||||||
|
// Archiver les stats (babyHistory) pour les conserver dans les statistiques globales
|
||||||
|
const hist=enc.babyHistory||enc.babies||[];
|
||||||
|
if(hist.length){
|
||||||
|
if(!S.archivedStats)S.archivedStats=[];
|
||||||
|
S.archivedStats.push(...hist.map(b=>({...b,fromEnclos:enc.name})));
|
||||||
|
}
|
||||||
S.enclos=S.enclos.filter(e=>e.id!==id);
|
S.enclos=S.enclos.filter(e=>e.id!==id);
|
||||||
if(S.activeId===id||S.activeId==='dashboard')S.activeId=S.enclos[0].id;
|
if(S.activeId===id||S.activeId==='dashboard')S.activeId=S.enclos[0].id;
|
||||||
save();render();
|
save();
|
||||||
|
render();
|
||||||
}
|
}
|
||||||
function selectEnclos(id){S.activeId=id;save();renderTabs();renderContent();}
|
function selectEnclos(id){S.activeId=id;save();renderTabs();renderContent();}
|
||||||
|
|
||||||
@ -915,20 +926,23 @@ function removeDD(encId,ddId){
|
|||||||
enc.dragodindes=enc.dragodindes.filter(d=>d.id!==ddId);
|
enc.dragodindes=enc.dragodindes.filter(d=>d.id!==ddId);
|
||||||
save();renderDDs(enc);
|
save();renderDDs(enc);
|
||||||
}
|
}
|
||||||
function viderEnclos(encId){
|
async function viderEnclos(encId){
|
||||||
const enc=S.enclos.find(e=>e.id===encId);
|
const enc=S.enclos.find(e=>e.id===encId);
|
||||||
if(!enc)return;
|
if(!enc)return;
|
||||||
if(!confirm(`Vider "${enc.name}" ?\n\nToutes les dragodindes seront supprimées.\nLes bébés et statistiques sont conservés.`))return;
|
const ok=await window.electronAPI.showConfirm('Vider l\'enclos',`Vider "${enc.name}" ?`,'Toutes les dragodindes seront supprimees.\nLes bebes et statistiques sont conserves.');
|
||||||
|
if(!ok)return;
|
||||||
enc.dragodindes=[];
|
enc.dragodindes=[];
|
||||||
enc.nextDdId=1;
|
enc.nextDdId=1;
|
||||||
enc.timer={running:false,startTime:null,pausedAt:null,pausedMs:0,snapGauges:{},snapStats:{}};
|
enc.timer={running:false,startTime:null,pausedAt:null,pausedMs:0,snapGauges:{},snapStats:{}};
|
||||||
enc.alerted={};
|
enc.alerted={};
|
||||||
enc.activeGauges=[];
|
enc.activeGauges=[];
|
||||||
Object.keys(enc.gaugeLevels).forEach(k=>{enc.gaugeLevels[k]=0;});
|
Object.keys(enc.gaugeLevels).forEach(k=>{enc.gaugeLevels[k]=0;});
|
||||||
// babies session vide mais babyHistory intact
|
|
||||||
enc.babies=[];
|
enc.babies=[];
|
||||||
lockInputs(encId,false);
|
// Ajouter une DD par défaut
|
||||||
save();renderContent();
|
const newId=enc.nextDdId++;
|
||||||
|
enc.dragodindes.push({id:newId,name:`Dragodinde ${newId}`,stats:{serenite:0,endurance:0,maturite:0,amour:0,xp:1},targets:{...DEFAULT_TARGETS}});
|
||||||
|
save();
|
||||||
|
render();
|
||||||
}
|
}
|
||||||
function updateName(encId,ddId,v){
|
function updateName(encId,ddId,v){
|
||||||
const enc=S.enclos.find(e=>e.id===encId);
|
const enc=S.enclos.find(e=>e.id===encId);
|
||||||
@ -956,18 +970,22 @@ function updateStatDirect(encId,ddId,stat,v){
|
|||||||
save();updateLive();
|
save();updateLive();
|
||||||
}
|
}
|
||||||
|
|
||||||
function nouvelleFournee(encId){
|
async function nouvelleFournee(encId){
|
||||||
const enc=S.enclos.find(e=>e.id===encId);
|
const enc=S.enclos.find(e=>e.id===encId);
|
||||||
if(!enc)return;
|
if(!enc)return;
|
||||||
if(!confirm(`Nouvelle fournée pour ${enc.name} ?\n\nLes dragodindes actuelles seront supprimées.\nLes bébés et l'historique sont conservés.`))return;
|
const ok=await window.electronAPI.showConfirm('Nouvelle fournee',`Nouvelle fournee pour ${enc.name} ?`,'Les dragodindes actuelles seront supprimees.\nLes bebes et l\'historique sont conserves.');
|
||||||
|
if(!ok)return;
|
||||||
enc.dragodindes=[];
|
enc.dragodindes=[];
|
||||||
enc.nextDdId=1;
|
enc.nextDdId=1;
|
||||||
enc.timer={running:false,startTime:null,pausedAt:null,pausedMs:0,snapGauges:{},snapStats:{}};
|
enc.timer={running:false,startTime:null,pausedAt:null,pausedMs:0,snapGauges:{},snapStats:{}};
|
||||||
enc.alerted={};
|
enc.alerted={};
|
||||||
enc.activeGauges=[];
|
enc.activeGauges=[];
|
||||||
Object.keys(enc.gaugeLevels).forEach(k=>{enc.gaugeLevels[k]=0;});
|
Object.keys(enc.gaugeLevels).forEach(k=>{enc.gaugeLevels[k]=0;});
|
||||||
enc.babies=[];
|
// Ajouter une DD par défaut
|
||||||
save();renderContent();
|
const newId=enc.nextDdId++;
|
||||||
|
enc.dragodindes.push({id:newId,name:`Dragodinde ${newId}`,stats:{serenite:0,endurance:0,maturite:0,amour:0,xp:1},targets:{...DEFAULT_TARGETS}});
|
||||||
|
save();
|
||||||
|
render();
|
||||||
}
|
}
|
||||||
function updateStat(encId,ddId,gid,v){
|
function updateStat(encId,ddId,gid,v){
|
||||||
const enc=S.enclos.find(e=>e.id===encId);
|
const enc=S.enclos.find(e=>e.id===encId);
|
||||||
@ -1070,6 +1088,7 @@ async function load(){
|
|||||||
S.alarmSound=d.alarmSound||'arpege';
|
S.alarmSound=d.alarmSound||'arpege';
|
||||||
S.notifsEnabled=d.notifsEnabled!==undefined?d.notifsEnabled:true;
|
S.notifsEnabled=d.notifsEnabled!==undefined?d.notifsEnabled:true;
|
||||||
S.ntfyTopic=d.ntfyTopic||'';
|
S.ntfyTopic=d.ntfyTopic||'';
|
||||||
|
S.archivedStats=d.archivedStats||[];
|
||||||
// Migration: ancien format ntfyUrl → ntfyTopic
|
// Migration: ancien format ntfyUrl → ntfyTopic
|
||||||
if(!S.ntfyTopic&&d.ntfyUrl){const m=d.ntfyUrl.match(/\/([^\/]+)$/);if(m)S.ntfyTopic=m[1];}
|
if(!S.ntfyTopic&&d.ntfyUrl){const m=d.ntfyUrl.match(/\/([^\/]+)$/);if(m)S.ntfyTopic=m[1];}
|
||||||
S.enclos.forEach(enc=>{
|
S.enclos.forEach(enc=>{
|
||||||
@ -1315,6 +1334,8 @@ function renderGaugesCfg(enc){
|
|||||||
<input type="number" class="gauge-inp" min="0" max="100000" step="1000"
|
<input type="number" class="gauge-inp" min="0" max="100000" step="1000"
|
||||||
value="${lvl}" style="border-color:${color}44"
|
value="${lvl}" style="border-color:${color}44"
|
||||||
oninput="updateGaugeLevel(${enc.id},'${gid}',this.value)"
|
oninput="updateGaugeLevel(${enc.id},'${gid}',this.value)"
|
||||||
|
onfocus="this.dataset.prev=this.value;this.value=''"
|
||||||
|
onblur="if(this.value==='')this.value=this.dataset.prev"
|
||||||
${enc.timer.running?'disabled':''}>
|
${enc.timer.running?'disabled':''}>
|
||||||
<span style="color:var(--muted);font-size:11px">/ 100 000</span>
|
<span style="color:var(--muted);font-size:11px">/ 100 000</span>
|
||||||
</div>
|
</div>
|
||||||
@ -1390,6 +1411,8 @@ function renderDDs(enc){
|
|||||||
<input type="number" id="pstat-${enc.id}-${dd.id}-${p.stat}"
|
<input type="number" id="pstat-${enc.id}-${dd.id}-${p.stat}"
|
||||||
value="${val}" min="${p.min}" max="${p.max}" step="${p.stat==='xp'?1:10}"
|
value="${val}" min="${p.min}" max="${p.max}" step="${p.stat==='xp'?1:10}"
|
||||||
oninput="updateStatDirect(${enc.id},${dd.id},'${p.stat}',this.value)"
|
oninput="updateStatDirect(${enc.id},${dd.id},'${p.stat}',this.value)"
|
||||||
|
onfocus="this.dataset.prev=this.value;this.value=''"
|
||||||
|
onblur="if(this.value==='')this.value=this.dataset.prev"
|
||||||
title="${p.stat}">
|
title="${p.stat}">
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
@ -1987,17 +2010,41 @@ function changeCount(delta){
|
|||||||
// ══════════════════════════════════════════
|
// ══════════════════════════════════════════
|
||||||
// STATS TAB
|
// STATS TAB
|
||||||
// ══════════════════════════════════════════
|
// ══════════════════════════════════════════
|
||||||
|
async function resetStats(){
|
||||||
|
const ok=await window.electronAPI.showConfirm(
|
||||||
|
'Reinitialiser les statistiques',
|
||||||
|
'Reinitialiser toutes les statistiques ?',
|
||||||
|
'Cette action est irreversible.\nTous les bebes enregistres, l\'historique de chaque enclos et les statistiques archivees seront definitivement supprimes.'
|
||||||
|
);
|
||||||
|
if(!ok)return;
|
||||||
|
S.archivedStats=[];
|
||||||
|
S.enclos.forEach(enc=>{
|
||||||
|
enc.babies=[];
|
||||||
|
enc.babyHistory=[];
|
||||||
|
});
|
||||||
|
save();
|
||||||
|
render();
|
||||||
|
}
|
||||||
function renderStats(){
|
function renderStats(){
|
||||||
const c=document.getElementById('enclos-content');
|
const c=document.getElementById('enclos-content');
|
||||||
c.removeAttribute('data-enc');
|
c.removeAttribute('data-enc');
|
||||||
|
|
||||||
// Agréger toutes les données
|
// Agréger toutes les données (enclos actifs + stats archivées des enclos supprimés)
|
||||||
let totalDD=0,totalBabies=0,totalSessions=0;
|
let totalDD=0,totalBabies=0,totalSessions=0;
|
||||||
const raceMap={};
|
const raceMap={};
|
||||||
const enclosStats=[];
|
const enclosStats=[];
|
||||||
|
|
||||||
|
// Stats archivées (enclos supprimés)
|
||||||
|
const archived=S.archivedStats||[];
|
||||||
|
archived.forEach(b=>{
|
||||||
|
if(!raceMap[b.race])raceMap[b.race]={count:0,gen:b.gen};
|
||||||
|
raceMap[b.race].count+=b.count;
|
||||||
|
totalBabies+=b.count;
|
||||||
|
});
|
||||||
|
const archivedMax=archived.reduce((s,b)=>s+Math.min(5,Math.floor((b.ddCount||1)/2)),0);
|
||||||
|
|
||||||
S.enclos.forEach(enc=>{
|
S.enclos.forEach(enc=>{
|
||||||
const babies=(enc.babyHistory||enc.babies||[]); // utilise l'historique permanent
|
const babies=(enc.babyHistory||enc.babies||[]);
|
||||||
const nbBabies=babies.reduce((s,b)=>s+b.count,0);
|
const nbBabies=babies.reduce((s,b)=>s+b.count,0);
|
||||||
const nbDD=enc.dragodindes.length;
|
const nbDD=enc.dragodindes.length;
|
||||||
totalDD+=nbDD;
|
totalDD+=nbDD;
|
||||||
@ -2011,8 +2058,6 @@ function renderStats(){
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(nbBabies>0){
|
if(nbBabies>0){
|
||||||
// Calcul taux de réussite basé sur le ddCount stocké lors de l'ajout
|
|
||||||
// → résiste à la suppression/nouvelle fournée de DD
|
|
||||||
const maxPossible=babies.reduce((s,b)=>{
|
const maxPossible=babies.reduce((s,b)=>{
|
||||||
const m=Math.min(5,Math.floor((b.ddCount||1)/2));
|
const m=Math.min(5,Math.floor((b.ddCount||1)/2));
|
||||||
return s+m;
|
return s+m;
|
||||||
@ -2022,8 +2067,8 @@ function renderStats(){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Taux global basé sur les ddCount stockés
|
// Taux global (enclos actifs + archivés)
|
||||||
const totalMax=S.enclos.reduce((s,e)=>{
|
const totalMax=archivedMax+S.enclos.reduce((s,e)=>{
|
||||||
const hist=(e.babyHistory||e.babies||[]);
|
const hist=(e.babyHistory||e.babies||[]);
|
||||||
return s+hist.reduce((ss,b)=>ss+Math.min(5,Math.floor((b.ddCount||1)/2)),0);
|
return s+hist.reduce((ss,b)=>ss+Math.min(5,Math.floor((b.ddCount||1)/2)),0);
|
||||||
},0);
|
},0);
|
||||||
@ -2034,9 +2079,17 @@ function renderStats(){
|
|||||||
const maxRaceCount=racesSorted[0]?.[1].count||1;
|
const maxRaceCount=racesSorted[0]?.[1].count||1;
|
||||||
|
|
||||||
c.innerHTML=`
|
c.innerHTML=`
|
||||||
<div style="font-family:'Cinzel',serif;font-size:0.8rem;letter-spacing:2.5px;text-transform:uppercase;color:var(--muted);margin-bottom:6px">
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
|
||||||
|
<div style="font-family:'Cinzel',serif;font-size:0.8rem;letter-spacing:2.5px;text-transform:uppercase;color:var(--muted)">
|
||||||
Statistiques globales
|
Statistiques globales
|
||||||
</div>
|
</div>
|
||||||
|
<button onclick="resetStats()" style="padding:6px 14px;border-radius:8px;border:1px solid var(--border);background:transparent;color:var(--muted);cursor:pointer;font:700 0.82rem 'Nunito',sans-serif;transition:.15s"
|
||||||
|
onmouseover="this.style.borderColor='var(--amour)';this.style.color='var(--amour)'"
|
||||||
|
onmouseout="this.style.borderColor='var(--border)';this.style.color='var(--muted)'"
|
||||||
|
title="Remettre toutes les statistiques a zero">
|
||||||
|
🗑 Reinitialiser les statistiques
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="stats-wrap">
|
<div class="stats-wrap">
|
||||||
<div class="stats-section">
|
<div class="stats-section">
|
||||||
<div class="stats-section-title">Vue d'ensemble</div>
|
<div class="stats-section-title">Vue d'ensemble</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user