Compare commits
No commits in common. "main" and "v1.0.0" have entirely different histories.
55
CLAUDE.md
55
CLAUDE.md
@ -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`
|
||||
77
README.md
77
README.md
@ -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
58
main.js
@ -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
|
||||
|
||||
@ -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>
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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()),
|
||||
|
||||
|
||||
1101
src/index.html
1101
src/index.html
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user