13 Commits

Author SHA1 Message Date
s
4ce8b41a4c chore: release v1.0.9
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 18:04:41 +02:00
s
0562b326c8 fix: volume slider par alerte, défaut 10%, suppression du pre-cache au démarrage
- Chaque alerte a son propre slider volume (0–100%, step 1, défaut 10%)
- Volume très bas possible (0%) — pas de minimum forcé
- Slider volume aussi dans le formulaire d'ajout pour setter dès la création
- Supprime le pre-cache IndexedDB au démarrage qui ralentissait le lancement
- Supprime le slider volume global (remplacé par le per-alerte)
- Migration automatique des anciennes alertes vers volume=10

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 17:40:35 +02:00
s
857a8477d7 feat: système de sons Minecraft configurable par alerte + volume global
- Volume global réglable (slider, défaut 25%) persisté dans localStorage
- Catalogue de 10 sons Minecraft officiels (Level Up, XP Orb, Item Pop, Pling, Bell, Toast, Challenge, Coffre, Hit, Click)
- Sons téléchargés à la demande depuis le CDN Mojang et cachés en IndexedDB
- Chaque alerte de drop a son propre son sélectionnable via dropdown
- Bouton ▶ preview par alerte et dans la bibliothèque (auto-download si pas en cache)
- Bibliothèque de sons repliable : cards avec preview + statut téléchargé
- Lecture via Web Audio API (AudioContext + GainNode) pour contrôle précis du volume
- Migration automatique des anciennes alertConfigs sans soundId (défaut: levelup)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 17:31:20 +02:00
s
fdbf57a6d6 chore: release v1.0.8
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 17:14:16 +02:00
s
4f7aab646b feat: son Minecraft lvl-up réel pour alertes de drop + diverses améliorations UI
- Remplace le son synthétique Web Audio par le vrai levelup.ogg Minecraft (extrait du CDN Mojang)
- Ajout d'un écran de détail par session dans l'historique (mobs + items avec bars + rates)
- Tooltip interactif sur les graphes en direct et historique (valeur au survol)
- Alertes de drop : suggestions d'items depuis la session et les stats
- Timers : suggestions de mobs depuis session et historique
- Hover highlight sur les lignes du tableau sessions
- Drop board et modal mob : affiche xp/kill et cols/kill quand disponible
- lib/dropstats.js : merge preserve totalExp et totalCols

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 17:14:01 +02:00
s
35481fe7d7 fix: update Linux via spawn comme Windows (plus app.relaunch)
app.relaunch() ne fonctionne pas avec les AppImages — il tente de relancer
le binaire Electron interne au lieu du fichier .AppImage. On utilise maintenant
spawn + detach + app.quit() comme sur Windows. selfInstallLinux() gère la copie
vers ~/.local/bin/ au premier démarrage du nouvel AppImage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 12:45:37 +02:00
s
26b70d29c4 feat: écran Timers — compte à rebours automatique au kill de mob
- Nouvel onglet "Timers" dans le hub
- Ajout de timers mob + durée (ex: 5:00 ou 90s)
- Détection automatique : chaque kill du mob configuré démarre/reset le timer
- Affichage des comptes à rebours avec barre de progression colorée (vert → orange → rouge)
- Configs persistées en localStorage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 12:36:26 +02:00
s
c94cdf679a fix: auto-install AppImage dans ~/.local/bin au premier lancement Linux
Au démarrage, si l'AppImage ne tourne pas depuis ~/.local/bin/farm-tracker.AppImage,
elle s'y copie automatiquement — plus besoin d'installation manuelle.
Les mises à jour suivantes y atterrissent toujours (fix update Linux).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 12:27:04 +02:00
s
a5d1725ee7 feat: stats historique enrichies en kills/min avec exp, kamas et top item
- Kills/h remplacé par kills/min partout (graphique + tableau)
- Hero : 3 nouvelles cartes (kills/min moyen + meilleur, exp totale, kamas totaux)
- Tableau : ajout colonnes exp/min, kamas, cols/min, top item droppé

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 12:14:02 +02:00
s
23662003da chore: release v1.0.3 2026-06-30 11:56:05 +02:00
s
34ed92a647 fix: chemin stable pour AppImage, desktop entry permanent après install initiale 2026-06-30 11:55:04 +02:00
s
2760ab4897 chore: release v1.0.2 2026-06-30 11:51:49 +02:00
s
6e306a9bed feat: auto-install silencieux des mises à jour (téléchargement + relance auto) 2026-06-30 11:50:15 +02:00
10 changed files with 1017 additions and 45 deletions

3
.gitignore vendored
View File

@@ -22,5 +22,8 @@ Thumbs.db
.vscode/ .vscode/
.idea/ .idea/
# Token local (jamais commité)
.gitea-token
# Claude Code # Claude Code
.claude/ .claude/

View File

@@ -1,37 +1,46 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Installe (ou met à jour) l'entrée de menu "Farm Tracker" pointant vers l'AppImage déjà construit. # Installation initiale de Farm Tracker (AppImage + entrée de menu).
set -e # À lancer une seule fois. Les mises à jour suivantes sont automatiques.
set -euo pipefail
APPDIR="/home/shamiiow/GitHub/farm-tracker-app/dist" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
APPIMAGE=$(ls "$APPDIR"/*.AppImage 2>/dev/null | head -n1) DIST_DIR="$SCRIPT_DIR/dist"
if [ -z "$APPIMAGE" ]; then # Trouve l'AppImage dans dist/
echo "Aucun fichier .AppImage trouvé dans : $APPDIR" APPIMAGE_SRC=$(ls "$DIST_DIR"/*.AppImage 2>/dev/null | head -n1)
echo "Lance d'abord (depuis le dossier du projet) : npm run dist" if [ -z "$APPIMAGE_SRC" ]; then
echo "Aucun .AppImage trouvé dans dist/. Lance d'abord : npm run dist"
exit 1 exit 1
fi fi
chmod +x "$APPIMAGE" # Chemin stable — indépendant de la version
INSTALL_DIR="$HOME/.local/bin"
STABLE_PATH="$INSTALL_DIR/farm-tracker.AppImage"
mkdir -p "$INSTALL_DIR"
cp "$APPIMAGE_SRC" "$STABLE_PATH"
chmod +x "$STABLE_PATH"
# Entrée de menu
DESKTOP_DIR="$HOME/.local/share/applications" DESKTOP_DIR="$HOME/.local/share/applications"
mkdir -p "$DESKTOP_DIR" mkdir -p "$DESKTOP_DIR"
DESKTOP_FILE="$DESKTOP_DIR/farm-tracker.desktop"
cat > "$DESKTOP_FILE" << INNEREOF cat > "$DESKTOP_DIR/farm-tracker.desktop" << EOF
[Desktop Entry] [Desktop Entry]
Type=Application Type=Application
Name=Farm Tracker Name=Farm Tracker
Comment=Suivi de loot Minecraft en direct Comment=Suivi de loot Minecraft en direct
Exec="$APPIMAGE" %U Exec="$STABLE_PATH" %U
Icon=applications-games Icon=applications-games
Terminal=false Terminal=false
Categories=Game;Utility; Categories=Game;Utility;
StartupWMClass=farm-tracker StartupWMClass=farm-tracker
INNEREOF EOF
chmod +x "$DESKTOP_FILE" chmod +x "$DESKTOP_DIR/farm-tracker.desktop"
update-desktop-database "$DESKTOP_DIR" >/dev/null 2>&1 || true update-desktop-database "$DESKTOP_DIR" >/dev/null 2>&1 || true
echo "Entrée installée : $DESKTOP_FILE" echo "Installé dans : $STABLE_PATH"
echo "AppImage détecté : $APPIMAGE" echo "Entrée de menu : $DESKTOP_DIR/farm-tracker.desktop"
echo "Cherche \"Farm Tracker\" dans ton menu d'applications (déconnexion/reconnexion parfois nécessaire selon le bureau)." echo ""
echo "Les prochaines mises à jour s'installeront automatiquement sans rien retoucher."

View File

@@ -21,12 +21,14 @@ function mergeDropStatsInto(target, source) {
for (const [mobName, mobData] of Object.entries(sourceMobs)) { for (const [mobName, mobData] of Object.entries(sourceMobs)) {
if (!mobName) continue; if (!mobName) continue;
if (!target.mobs[mobName]) target.mobs[mobName] = { kills: 0, items: {} }; if (!target.mobs[mobName]) target.mobs[mobName] = { kills: 0, totalExp: 0, totalCols: 0, items: {} };
const t = target.mobs[mobName]; const t = target.mobs[mobName];
const kills = Number(mobData.kills) || 0; const kills = Number(mobData.kills) || 0;
t.kills += kills; t.kills += kills;
addedKills += kills; addedKills += kills;
t.totalExp = (t.totalExp || 0) + (Number(mobData.totalExp) || 0);
t.totalCols = (t.totalCols || 0) + (Number(mobData.totalCols) || 0);
const items = mobData.items || {}; const items = mobData.items || {};
for (const [key, item] of Object.entries(items)) { for (const [key, item] of Object.entries(items)) {

83
main.js
View File

@@ -103,6 +103,69 @@ function resolveAssetUrl(release) {
return asset ? asset.browser_download_url : null; return asset ? asset.browser_download_url : null;
} }
function sendUpdateProgress(percent, status, error) {
if (mainWindow && !mainWindow.isDestroyed())
mainWindow.webContents.send('update-progress', { percent, status, error });
}
function downloadFileWithProgress(url, destPath, onProgress) {
return new Promise((resolve, reject) => {
const follow = (currentUrl, redirects) => {
if (redirects > 5) return reject(new Error('Trop de redirections'));
const mod = currentUrl.startsWith('https') ? require('https') : require('http');
const req = mod.get(currentUrl, { timeout: 120000, headers: { 'User-Agent': 'farm-tracker-updater' } }, (res) => {
if ([301, 302, 307, 308].includes(res.statusCode))
return follow(res.headers.location, redirects + 1);
if (res.statusCode !== 200)
return reject(new Error(`HTTP ${res.statusCode}`));
const total = parseInt(res.headers['content-length'] || '0', 10);
let downloaded = 0;
const file = fs.createWriteStream(destPath);
res.on('data', chunk => {
downloaded += chunk.length;
file.write(chunk);
if (total > 0 && onProgress) onProgress(Math.round(downloaded / total * 100));
});
res.on('end', () => file.close(() => resolve(destPath)));
res.on('error', err => { file.close(); try { fs.unlinkSync(destPath); } catch (_) {} reject(err); });
});
req.on('error', reject);
req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
};
follow(url, 0);
});
}
async function downloadAndInstall(downloadUrl, version) {
if (!app.isPackaged) {
dialog.showMessageBox(mainWindow, {
type: 'info', title: 'Mode développement',
message: 'Auto-install non disponible en mode dev.',
buttons: ['OK']
});
return;
}
const ext = process.platform === 'win32' ? '.exe' : '.AppImage';
const tmpPath = path.join(app.getPath('temp'), `farm-tracker-${version}${ext}`);
try {
sendUpdateProgress(0, 'downloading');
await downloadFileWithProgress(downloadUrl, tmpPath, pct => sendUpdateProgress(pct, 'downloading'));
sendUpdateProgress(100, 'installing');
if (process.platform === 'linux') {
fs.chmodSync(tmpPath, 0o755);
const { spawn } = require('child_process');
spawn(tmpPath, [], { detached: true, stdio: 'ignore' }).unref();
app.quit();
} else if (process.platform === 'win32') {
const { spawn } = require('child_process');
spawn(tmpPath, ['/S'], { detached: true, stdio: 'ignore' }).unref();
app.quit();
}
} catch (e) {
sendUpdateProgress(0, 'error', e.message);
}
}
async function showUpdateDialog(version, downloadUrl, ignoreSnooze = false) { async function showUpdateDialog(version, downloadUrl, ignoreSnooze = false) {
const current = app.getVersion(); const current = app.getVersion();
if (!isNewerVersion(version, current)) return false; if (!isNewerVersion(version, current)) return false;
@@ -117,14 +180,14 @@ async function showUpdateDialog(version, downloadUrl, ignoreSnooze = false) {
type: 'info', type: 'info',
title: 'Mise à jour disponible', title: 'Mise à jour disponible',
message: `Version ${version} disponible`, message: `Version ${version} disponible`,
detail: `Version actuelle : ${current}`, detail: `Version actuelle : ${current}\nL'application se relancera automatiquement après la mise à jour.`,
buttons: ['Télécharger', 'Me rappeler dans...'], buttons: ['Installer maintenant', 'Me rappeler dans...'],
defaultId: 0, defaultId: 0,
cancelId: 1 cancelId: 1
}); });
if (response === 0) { if (response === 0) {
shell.openExternal(downloadUrl); downloadAndInstall(downloadUrl, version);
return true; return true;
} }
@@ -181,7 +244,21 @@ function createWindow() {
mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html')); mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html'));
} }
function selfInstallLinux() {
if (process.platform !== 'linux' || !app.isPackaged) return;
const appImagePath = process.env.APPIMAGE;
if (!appImagePath) return;
const stablePath = path.join(require('os').homedir(), '.local', 'bin', 'farm-tracker.AppImage');
if (appImagePath === stablePath) return;
try {
fs.mkdirSync(path.dirname(stablePath), { recursive: true });
fs.copyFileSync(appImagePath, stablePath);
fs.chmodSync(stablePath, 0o755);
} catch (_) {}
}
app.whenReady().then(() => { app.whenReady().then(() => {
selfInstallLinux();
createWindow(); createWindow();
setTimeout(() => checkForUpdates(false), 5000); setTimeout(() => checkForUpdates(false), 5000);
}); });

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "farm-tracker", "name": "farm-tracker",
"version": "1.0.1", "version": "1.0.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "farm-tracker", "name": "farm-tracker",
"version": "1.0.1", "version": "1.0.3",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"dist": "^0.1.2", "dist": "^0.1.2",

View File

@@ -1,6 +1,6 @@
{ {
"name": "farm-tracker", "name": "farm-tracker",
"version": "1.0.1", "version": "1.0.9",
"description": "Suivi de loot Minecraft en direct, avec sauvegarde automatique des stats et des chances de drop.", "description": "Suivi de loot Minecraft en direct, avec sauvegarde automatique des stats et des chances de drop.",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

View File

@@ -24,5 +24,6 @@ contextBridge.exposeInMainWorld('api', {
loadSessionHistory: () => ipcRenderer.invoke('load-session-history'), loadSessionHistory: () => ipcRenderer.invoke('load-session-history'),
saveSessionHistory: (obj) => ipcRenderer.invoke('save-session-history', obj), saveSessionHistory: (obj) => ipcRenderer.invoke('save-session-history', obj),
checkUpdateManual: () => ipcRenderer.invoke('check-update-manual') checkUpdateManual: () => ipcRenderer.invoke('check-update-manual'),
onUpdateProgress: (cb) => ipcRenderer.on('update-progress', (_, data) => cb(data))
}); });

File diff suppressed because it is too large Load Diff

BIN
renderer/levelup.ogg Normal file

Binary file not shown.

56
tools/release.sh Executable file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
# ---------- Token ----------
TOKEN="${GITEA_TOKEN:-}"
if [ -z "$TOKEN" ] && [ -f ".gitea-token" ]; then
TOKEN=$(tr -d '[:space:]' < .gitea-token)
fi
if [ -z "$TOKEN" ]; then
read -rsp "Token Gitea : " TOKEN
echo
fi
# ---------- Version ----------
CURRENT=$(node -p "require('./package.json').version")
if [ -n "${1:-}" ]; then
VERSION="$1"
else
echo "Version actuelle : v$CURRENT"
read -rp "Nouvelle version [patch / minor / major / X.Y.Z] : " VERSION
fi
npm version "$VERSION" --no-git-tag-version --silent
NEW=$(node -p "require('./package.json').version")
echo "→ v$NEW"
# ---------- Build ----------
echo ""
echo "Nettoyage des anciens binaires..."
find dist -maxdepth 1 \( -name "*.AppImage" -o -name "*.exe" -o -name "*.blockmap" \) -delete 2>/dev/null || true
echo "Build Linux (AppImage)..."
npm run dist --silent
if command -v wine &>/dev/null; then
echo "Build Windows (exe)..."
npm run dist:win --silent
else
echo "(Wine non trouvé — build Windows ignoré)"
fi
# ---------- Release Gitea ----------
echo ""
GITEA_TOKEN="$TOKEN" node tools/release.js
# ---------- Git ----------
git add package.json package-lock.json
git commit -m "chore: release v$NEW" --quiet
git remote set-url origin "https://shamiiow:${TOKEN}@git.shamiiow.com/shamiiow/farm-tracker-sao.git"
git push --quiet
git remote set-url origin "https://git.shamiiow.com/shamiiow/farm-tracker-sao.git"
echo ""
echo "Release v$NEW publiée."