Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

7 changed files with 114 additions and 1259 deletions

View File

@ -1,55 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Minuteur Dragodinde is a Windows desktop app (Electron) for managing Dragodinde breeding timers in Dofus 3. French-language UI.
## Commands
- **Dev**: `npm start` (runs `electron .`)
- **Build**: `npm run build` (produces NSIS installer in `dist/`)
- **Build scripts**: `build.bat` (auto-elevates to admin) or `build.ps1` for Windows
No test framework or linter is configured.
## Architecture
**Single-page Electron app with no bundler.** All source code lives in 4 files:
- `main.js` — Electron main process: window management, system tray, native notifications, ntfy push notifications (mobile), auto-update via Gitea Releases API, IPC handlers
- `preload.js` — Context bridge exposing `window.electronAPI` (alarm, notifications, ntfy, version, update channels)
- `src/index.html`**Monolithic ~2200-line file** containing all HTML, CSS, and JS in one file. This is the entire renderer process.
- `icon.png` — App icon (256x256)
### Renderer architecture (src/index.html)
All application logic is in `<script>` tags inside index.html. Key concepts:
- **State object `S`**: Central state with `enclos` array, `activeId`, settings. Persisted to `localStorage`.
- **Enclos**: Up to 6 independent pens, each holding up to 10 Dragodindes (DDs). Enclos and DDs are drag-and-drop reorderable.
- **Gauges**: 6 gauge types (baffeur, caresseur, foudroyeur, abreuvoir, dragofesse, mangeoire/XP) with tier-based progression rates (tiers 1-4 based on gauge level thresholds at 40k/70k/90k).
- **Timer system**: Per-enclos timers with snapshot-based calculation. `elapsed()` computes time since start, `gaugeAfter()`/`gainedIn()`/`timeToGain()` compute gauge progression.
- **Rendering**: Imperative DOM manipulation via `render()` function — no framework.
- **Dashboard/Stats views**: Special `activeId` values (`'dashboard'`, `'stats'`) for overview and statistics pages.
### IPC channels
Main→Renderer: `app-version`, `play-alarm-sound`, `update-available`, `update-downloading`, `update-progress`, `update-ready`, `update-error`
Renderer→Main: `trigger-alarm`, `show-notification`, `send-ntfy`, `focus-window`, `install-update`, `get-version`
### Auto-update flow
Checks Gitea Releases API on startup (after 3s) and hourly. Downloads NSIS Setup .exe to temp, launches with `/S` (silent), then quits app.
### Dev vs packaged mode
When `!app.isPackaged`, userData is stored in `MinuteurDragodinde-DEV` (isolated from installed app) and a DEV badge is injected into the UI.
## Key conventions
- All UI text is in French
- No external JS dependencies in renderer — everything is vanilla JS
- CSS uses custom properties defined in `:root` (color theme: dark purple/gaming aesthetic)
- Electron config: `contextIsolation: true`, `nodeIntegration: false`, `backgroundThrottling: false`

View File

@ -11,11 +11,10 @@ Application desktop Windows construite avec Electron.
- ⏱ **Timer en temps réel** avec calcul automatique par tier de jauge (1→4)
- 📊 **Dashboard** vue d'ensemble multi-enclos
- 🔔 **Notifications natives Windows** même application en arrière-plan
- 🔊 **4 sons d'alarme** au choix (Arpège, Pulsation, Fanfare, Cloche)
- 🔊 **5 sons d'alarme** au choix (Arpège, Pulsation, Fanfare, Sirène, Cloche)
- 🐉 **Jauges** : Baffeur, Caresseur, Foudroyeur, Abreuvoir, Dragofesse, Mangeoire (XP)
- 🖱 **Drag & drop** des enclos et des Dragodindes pour les réordonner
- ⬆ **Mise à jour automatique** via Gitea Releases
- 📱 **Notifications mobiles** via ntfy (serveur self-hosted)
- 💾 Sauvegarde automatique locale
## Installation (utilisateurs)
@ -70,23 +69,63 @@ dd-timer/
## Changelog
### v1.1.0
- 📱 **Notifications mobiles (ntfy)** — Alerte sur telephone quand un enclos est pret, via serveur ntfy self-hosted
- Modale de configuration avec QR code pour installer l'app ntfy (Play Store / App Store)
- QR code d'abonnement automatique via page de redirection (ntfy-redirect)
- Bouton de test des notifications
- Envoi via le processus principal Electron (pas de CORS)
- 🐣 **Systeme de bebes** — Ajout de bebes dragodindes issus de la reproduction dans chaque enclos
- Modale de selection par generation et race avec images
- Historique des bebes par enclos
- 📊 **Onglet Statistiques** — Vue globale de l'elevage avec KPIs, repartition par race, et progression par enclos
- 🖱 **Drag & drop** des onglets d'enclos pour les reordonner
- 🐉 **Images des dragodindes** par race avec couleurs par generation
- 📝 **Sous-onglets par enclos** (Elevage / Historique bebes)
- 🔧 **Mode DEV** — Donnees isolees et badge DEV visible quand lance avec `npm start`
- ⬆ **Mise a jour automatique** via Gitea Releases avec banniere de progression dans l'interface
- 🔧 Correction de l'identifiant applicatif (`fr.mickael-pol.minuteur-dragodinde`)
- 🔧 Masquage des spinners natifs sur les champs numeriques
### v1.0.0
- Version initiale
Outil de gestion d'élevage de Dragodindes pour Dofus 3.
Application desktop Windows construite avec Electron.
## Fonctionnalités
- 🐦 Gestion de **6 enclos indépendants** avec jusqu'à 10 Dragodindes chacun
- ⏱ **Timer en temps réel** avec calcul automatique par tier de jauge (1→4)
- 📊 **Dashboard** vue d'ensemble multi-enclos
- 🔔 **Notifications natives Windows** même application en arrière-plan
- 🔊 **5 sons d'alarme** au choix (Arpège, Pulsation, Fanfare, Sirène, Cloche)
- 🐉 **Jauges** : Baffeur, Caresseur, Foudroyeur, Abreuvoir, Dragofesse, Mangeoire (XP)
- 🖱 **Drag & drop** des enclos et des Dragodindes pour les réordonner
- 💾 Sauvegarde automatique locale
## Installation (utilisateurs)
1. Télécharger `Minuteur Dragodinde Setup x.x.x.exe` depuis la section [Releases](../../releases)
2. **Clic droit → Propriétés → cocher "Débloquer" → OK** (important, une seule fois)
3. Double-cliquer pour lancer l'installation → suivre l'assistant
4. L'app apparaît dans le menu Démarrer et sur le Bureau
> **Si Windows affiche "Application inconnue"** : cliquer **"Informations complémentaires" → "Exécuter quand même"**
> **Si Smart App Control bloque** : Sécurité Windows → Contrôle des applications → Smart App Control → Évaluation, puis retélécharger
## Build (développeurs)
### Prérequis
- [Node.js LTS](https://nodejs.org)
### Compiler l'application
```bash
# Double-cliquer sur build.bat (se relance automatiquement en admin)
# ou manuellement :
npm install
npm run build
```
L'exécutable est généré dans `dist/`.
## Structure du projet
```
dd-timer/
├── src/
│ └── index.html # Interface complète (HTML/CSS/JS)
├── main.js # Processus principal Electron
├── preload.js # Pont sécurisé Electron ↔ renderer
├── icon.png # Icône application (256x256)
├── package.json # Config et dépendances
└── build.bat # Script de build Windows
```
## Changelog
### v1.0.0
- Version initiale

58
main.js
View File

@ -4,19 +4,6 @@ const https = require('https');
const fs = require('fs');
const os = require('os');
// ─── NOM DE L'APPLICATION ─────────────────────────────────────────────────────
app.setName('Minuteur Dragodinde');
// Windows utilise l'AppUserModelId pour le nom affiché dans les notifications
if (process.platform === 'win32') {
app.setAppUserModelId('Minuteur Dragodinde');
}
// ─── MODE DEV ────────────────────────────────────────────────────────────────
// En dev (npm start), les données sont isolées de l'app installée
if (!app.isPackaged) {
app.setPath('userData', path.join(app.getPath('appData'), 'MinuteurDragodinde-DEV'));
}
// ─── CONFIG GITEA ─────────────────────────────────────────────────────────────
const GITEA_HOST = 'gitea.mickael-pol.fr'; // ton instance Gitea
const GITEA_USER = 'mickael'; // ton user Gitea
@ -70,19 +57,6 @@ function createWindow() {
if (updateInfo) {
mainWindow.webContents.send('update-available', updateInfo);
}
// Badge DEV visible dans l'interface
if (!app.isPackaged) {
mainWindow.webContents.executeJavaScript(`
const p = document.querySelector('header p');
if (p && !document.getElementById('dev-badge')) {
const b = document.createElement('span');
b.id = 'dev-badge';
b.textContent = 'DEV';
b.style.cssText = 'background:#ff9820;color:#000;padding:2px 10px;border-radius:8px;font-size:0.72rem;font-weight:800;margin-left:8px;vertical-align:middle';
p.appendChild(b);
}
`).catch(()=>{});
}
});
}
@ -133,38 +107,6 @@ ipcMain.on('show-notification', (event, { title, body }) => {
fireNotification(title, body);
});
// ─── NTFY (notifications mobiles) ─────────────────────────────────────────
ipcMain.on('send-ntfy', (event, { url, title, message }) => {
if (!url) return;
try {
const parsed = new URL(url.trim());
const mod = parsed.protocol === 'https:' ? https : require('http');
const postData = message;
const options = {
hostname: parsed.hostname,
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
path: parsed.pathname + parsed.search,
method: 'POST',
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Content-Length': Buffer.byteLength(postData, 'utf-8'),
'Title': title,
'Priority': 'high',
'Tags': 'hatching_chick',
},
};
const req = mod.request(options, (res) => {
res.on('data', () => {}); // drain
res.on('end', () => {});
});
req.on('error', (e) => console.warn('ntfy send error:', e.message));
req.write(postData, 'utf-8');
req.end();
} catch (e) {
console.warn('ntfy error:', e.message);
}
});
ipcMain.on('focus-window', () => { mainWindow.show(); mainWindow.focus(); });
// Renderer demande à installer la mise à jour

View File

@ -1,74 +0,0 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Minuteur Dragodinde - Notifications</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0b0b14;color:#dddaf8;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}
.card{background:#181828;border:1px solid #2a2a45;border-radius:16px;padding:32px;max-width:400px;width:100%;text-align:center}
h1{font-size:1.3rem;margin-bottom:8px}
.sub{color:#6868a0;font-size:0.85rem;margin-bottom:24px}
.spinner{width:32px;height:32px;border:3px solid #2a2a45;border-top-color:#c060ff;border-radius:50%;animation:spin .8s linear infinite;margin:0 auto 16px}
@keyframes spin{to{transform:rotate(360deg)}}
.msg{font-size:0.9rem;margin-bottom:24px;line-height:1.6}
.btn{display:block;width:100%;padding:14px;border-radius:10px;border:none;font-size:1rem;font-weight:700;cursor:pointer;text-decoration:none;margin-bottom:10px;text-align:center}
.btn-main{background:linear-gradient(135deg,#5020b0,#c060ff);color:#fff}
.btn-store{background:#20203a;color:#dddaf8;border:1px solid #2a2a45}
.btn-store:hover{background:#2a2a45}
.hidden{display:none}
.sep{color:#6868a0;font-size:0.8rem;margin:12px 0}
</style>
</head>
<body>
<div class="card">
<h1>Minuteur Dragodinde</h1>
<p class="sub">Notifications mobiles</p>
<div id="loading">
<div class="spinner"></div>
<p class="msg">Ouverture de l'app ntfy...</p>
</div>
<div id="fallback" class="hidden">
<p class="msg">Si l'app ntfy ne s'est pas ouverte, installe-la d'abord :</p>
<a id="btn-play" class="btn btn-store" href="#" target="_blank">Android — Play Store</a>
<a id="btn-apple" class="btn btn-store" href="#" target="_blank">iOS — App Store</a>
<div class="sep">puis reviens sur cette page</div>
<button id="btn-retry" class="btn btn-main" onclick="doRedirect()">Ouvrir dans ntfy</button>
</div>
</div>
<script>
const params = new URLSearchParams(window.location.search);
const topic = params.get('t');
const server = params.get('s') || 'ntfy.mickael-pol.fr';
const name = params.get('n') || 'dd-timer';
const ntfyLink = 'ntfy://' + server + '/' + topic + '?display=' + encodeURIComponent(name);
const playStore = 'https://play.google.com/store/apps/details?id=io.heckel.ntfy';
const appStore = 'https://apps.apple.com/app/ntfy/id1625396347';
document.getElementById('btn-play').href = playStore;
document.getElementById('btn-apple').href = appStore;
function doRedirect() {
if (!topic) return;
window.location = ntfyLink;
}
// Tenter la redirection automatique
if (topic) {
doRedirect();
// Afficher le fallback après 2s si on est toujours sur la page
setTimeout(function() {
document.getElementById('loading').classList.add('hidden');
document.getElementById('fallback').classList.remove('hidden');
}, 2000);
} else {
document.getElementById('loading').innerHTML = '<p class="msg" style="color:#ff5070">Lien invalide — aucun topic fourni.</p>';
}
</script>
</body>
</html>

View File

@ -1,6 +1,6 @@
{
"name": "minuteur-dragodinde",
"version": "1.1.0",
"version": "1.0.0",
"description": "Minuteur elevage Dragodinde Dofus 3",
"main": "main.js",
"author": "Mickael",
@ -9,7 +9,7 @@
"build": "electron-builder --win --x64"
},
"build": {
"appId": "fr.mickael-pol.minuteur-dragodinde",
"appId": "com.dofus3.minuteur-dragodinde",
"productName": "Minuteur Dragodinde",
"directories": {
"output": "dist"
@ -51,6 +51,5 @@
"repository": {
"type": "git",
"url": "https://gitea.mickael-pol.fr/mickael/dd-timer.git"
},
"productName": "Minuteur Dragodinde"
}
}

View File

@ -6,7 +6,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
// Alarme
triggerAlarm: (enclosName) => ipcRenderer.send('trigger-alarm', { enclosName }),
showNotification: (title, body) => ipcRenderer.send('show-notification', { title, body }),
sendNtfy: (url, title, message) => ipcRenderer.send('send-ntfy', { url, title, message }),
focusWindow: () => ipcRenderer.send('focus-window'),
onPlayAlarmSound: (cb) => ipcRenderer.on('play-alarm-sound', () => cb()),

File diff suppressed because one or more lines are too long