Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
68815645a4 | ||
|
|
725ab74d1f | ||
|
|
20a016bd3a | ||
|
|
a65700175b | ||
|
|
def63c9b06 | ||
|
|
447801870e | ||
|
|
25fc46851c | ||
|
|
d5cb63a27b |
@@ -14,7 +14,14 @@
|
|||||||
"Bash(powershell.exe -NoProfile -Command \"Get-Process | Where-Object { $_.ProcessName -match ''electron|Blender|Multi'' } | Select-Object Id, ProcessName | Format-Table\")",
|
"Bash(powershell.exe -NoProfile -Command \"Get-Process | Where-Object { $_.ProcessName -match ''electron|Blender|Multi'' } | Select-Object Id, ProcessName | Format-Table\")",
|
||||||
"Bash(tasklist:*)",
|
"Bash(tasklist:*)",
|
||||||
"Bash(\"/c/Users/pc-valentin/Documents/GitHub/multi_render_blender/dist2/win-unpacked/Multi Render Blender.exe\")",
|
"Bash(\"/c/Users/pc-valentin/Documents/GitHub/multi_render_blender/dist2/win-unpacked/Multi Render Blender.exe\")",
|
||||||
"Bash(npm run build:*)"
|
"Bash(npm run build:*)",
|
||||||
|
"WebFetch(domain:git.sorlinv.fr)",
|
||||||
|
"Bash(zip:*)",
|
||||||
|
"Bash(git add:*)",
|
||||||
|
"Bash(git commit:*)",
|
||||||
|
"Bash(git tag:*)",
|
||||||
|
"Bash(node:*)",
|
||||||
|
"Bash(chmod +x /home/valentin/Documents/GitHub/multi_render_blender/release.sh)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ dist/
|
|||||||
config/saves/
|
config/saves/
|
||||||
blender/
|
blender/
|
||||||
*.log
|
*.log
|
||||||
|
.env
|
||||||
|
|||||||
78
main.js
78
main.js
@@ -5,9 +5,34 @@ const CameraParser = require("./src/main/CameraParser.js");
|
|||||||
const QueueManager = require("./src/main/QueueManager.js");
|
const QueueManager = require("./src/main/QueueManager.js");
|
||||||
const ConfigManager = require("./src/main/ConfigManager.js");
|
const ConfigManager = require("./src/main/ConfigManager.js");
|
||||||
const UpdateManager = require("./src/main/UpdateManager.js");
|
const UpdateManager = require("./src/main/UpdateManager.js");
|
||||||
|
const PathResolver = require("./src/main/PathResolver.js");
|
||||||
|
|
||||||
let obj_main_window = null;
|
let obj_main_window = null;
|
||||||
let obj_queue_manager = null;
|
let obj_queue_manager = null;
|
||||||
|
let obj_notification_config = { is_notify_each_image: false, is_notify_all_done: true };
|
||||||
|
|
||||||
|
const STR_NOTIFICATION_CONFIG_FILE = "notification_config.json";
|
||||||
|
|
||||||
|
const _load_notification_config = () => {
|
||||||
|
let str_config_path = path.join(app.getPath("userData"), STR_NOTIFICATION_CONFIG_FILE);
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(str_config_path)) {
|
||||||
|
let str_content = fs.readFileSync(str_config_path, "utf8");
|
||||||
|
obj_notification_config = JSON.parse(str_content);
|
||||||
|
}
|
||||||
|
} catch (obj_err) {
|
||||||
|
console.error("Erreur lecture config notifications :", obj_err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const _save_notification_config = () => {
|
||||||
|
let str_config_path = path.join(app.getPath("userData"), STR_NOTIFICATION_CONFIG_FILE);
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(str_config_path, JSON.stringify(obj_notification_config, null, 4), "utf8");
|
||||||
|
} catch (obj_err) {
|
||||||
|
console.error("Erreur sauvegarde config notifications :", obj_err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const create_window = () => {
|
const create_window = () => {
|
||||||
obj_main_window = new BrowserWindow({
|
obj_main_window = new BrowserWindow({
|
||||||
@@ -27,9 +52,14 @@ const create_window = () => {
|
|||||||
|
|
||||||
obj_queue_manager = new QueueManager(obj_main_window);
|
obj_queue_manager = new QueueManager(obj_main_window);
|
||||||
|
|
||||||
|
PathResolver.load_saved_path();
|
||||||
|
_load_notification_config();
|
||||||
|
obj_queue_manager.set_notification_config(obj_notification_config);
|
||||||
|
|
||||||
UpdateManager.init(obj_main_window);
|
UpdateManager.init(obj_main_window);
|
||||||
obj_main_window.webContents.on("did-finish-load", () => {
|
obj_main_window.webContents.on("did-finish-load", () => {
|
||||||
UpdateManager.check_for_updates();
|
UpdateManager.check_for_updates();
|
||||||
|
obj_main_window.webContents.send("blender-path-status", PathResolver.get_status());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,6 +107,10 @@ ipcMain.handle("start-render", (event, obj_config) => {
|
|||||||
return obj_queue_manager.start(obj_config);
|
return obj_queue_manager.start(obj_config);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("check-queue", (event, obj_config) => {
|
||||||
|
return obj_queue_manager.check_queue(obj_config);
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle("pause-render", () => {
|
ipcMain.handle("pause-render", () => {
|
||||||
return obj_queue_manager.pause();
|
return obj_queue_manager.pause();
|
||||||
});
|
});
|
||||||
@@ -159,6 +193,37 @@ ipcMain.handle("read-image", (event, str_image_path) => {
|
|||||||
return fn_try_read(0);
|
return fn_try_read(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("get-blender-path", () => {
|
||||||
|
return PathResolver.get_status();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("set-blender-path", (event, str_path) => {
|
||||||
|
let obj_result = PathResolver.set_blender_path(str_path);
|
||||||
|
if (obj_result.is_success) {
|
||||||
|
obj_main_window.webContents.send("blender-path-status", PathResolver.get_status());
|
||||||
|
}
|
||||||
|
return obj_result;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("select-blender-exe", () => {
|
||||||
|
let str_exe_name = process.platform === "win32" ? "blender.exe" : "blender";
|
||||||
|
let list_filters = process.platform === "win32"
|
||||||
|
? [{ name: "Blender", extensions: ["exe"] }]
|
||||||
|
: [{ name: "Blender", extensions: ["*"] }];
|
||||||
|
|
||||||
|
return dialog.showOpenDialog(obj_main_window, {
|
||||||
|
title: "Selectionner l'executable Blender",
|
||||||
|
filters: list_filters,
|
||||||
|
properties: ["openFile"],
|
||||||
|
})
|
||||||
|
.then((obj_result) => {
|
||||||
|
if (obj_result.canceled || obj_result.filePaths.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return obj_result.filePaths[0];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle("check-for-updates", () => {
|
ipcMain.handle("check-for-updates", () => {
|
||||||
return UpdateManager.check_for_updates();
|
return UpdateManager.check_for_updates();
|
||||||
});
|
});
|
||||||
@@ -179,3 +244,16 @@ ipcMain.handle("select-output-folder", () => {
|
|||||||
return obj_result.filePaths[0];
|
return obj_result.filePaths[0];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Notification Config ───────────────────────────────────────
|
||||||
|
|
||||||
|
ipcMain.handle("get-notification-config", () => {
|
||||||
|
return obj_notification_config;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("set-notification-config", (event, obj_config) => {
|
||||||
|
obj_notification_config = obj_config;
|
||||||
|
_save_notification_config();
|
||||||
|
obj_queue_manager.set_notification_config(obj_notification_config);
|
||||||
|
return { is_success: true };
|
||||||
|
});
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "multi-render-blender",
|
"name": "multi-render-blender",
|
||||||
"version": "1.0.0",
|
"version": "1.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "multi-render-blender",
|
"name": "multi-render-blender",
|
||||||
"version": "1.0.0",
|
"version": "1.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^34.0.0",
|
"electron": "^34.0.0",
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "multi-render-blender",
|
"name": "multi-render-blender",
|
||||||
"version": "1.0.0",
|
"version": "1.2.0",
|
||||||
"description": "Application Electron pour piloter des rendus Blender multi-cameras",
|
"description": "Application Electron pour piloter des rendus Blender multi-cameras",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"build": "electron-builder"
|
"build": "electron-builder --linux dir --win dir --x64",
|
||||||
|
"release": "bash release.sh"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -23,13 +24,6 @@
|
|||||||
"src/**/*",
|
"src/**/*",
|
||||||
"version.json"
|
"version.json"
|
||||||
],
|
],
|
||||||
"extraResources": [
|
|
||||||
{
|
|
||||||
"from": "blender",
|
|
||||||
"to": "blender",
|
|
||||||
"filter": ["**/*"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"linux": {
|
"linux": {
|
||||||
"target": "dir"
|
"target": "dir"
|
||||||
},
|
},
|
||||||
|
|||||||
30
preload.js
30
preload.js
@@ -13,6 +13,10 @@ contextBridge.exposeInMainWorld("api", {
|
|||||||
return ipcRenderer.invoke("start-render", obj_config);
|
return ipcRenderer.invoke("start-render", obj_config);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
check_queue: (obj_config) => {
|
||||||
|
return ipcRenderer.invoke("check-queue", obj_config);
|
||||||
|
},
|
||||||
|
|
||||||
pause_render: () => {
|
pause_render: () => {
|
||||||
return ipcRenderer.invoke("pause-render");
|
return ipcRenderer.invoke("pause-render");
|
||||||
},
|
},
|
||||||
@@ -67,6 +71,32 @@ contextBridge.exposeInMainWorld("api", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get_blender_path: () => {
|
||||||
|
return ipcRenderer.invoke("get-blender-path");
|
||||||
|
},
|
||||||
|
|
||||||
|
set_blender_path: (str_path) => {
|
||||||
|
return ipcRenderer.invoke("set-blender-path", str_path);
|
||||||
|
},
|
||||||
|
|
||||||
|
select_blender_exe: () => {
|
||||||
|
return ipcRenderer.invoke("select-blender-exe");
|
||||||
|
},
|
||||||
|
|
||||||
|
on_blender_path_status: (fn_callback) => {
|
||||||
|
ipcRenderer.on("blender-path-status", (event, obj_data) => {
|
||||||
|
fn_callback(obj_data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get_notification_config: () => {
|
||||||
|
return ipcRenderer.invoke("get-notification-config");
|
||||||
|
},
|
||||||
|
|
||||||
|
set_notification_config: (obj_config) => {
|
||||||
|
return ipcRenderer.invoke("set-notification-config", obj_config);
|
||||||
|
},
|
||||||
|
|
||||||
check_for_updates: () => {
|
check_for_updates: () => {
|
||||||
return ipcRenderer.invoke("check-for-updates");
|
return ipcRenderer.invoke("check-for-updates");
|
||||||
},
|
},
|
||||||
|
|||||||
156
release.sh
Executable file
156
release.sh
Executable file
@@ -0,0 +1,156 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Chargement du token
|
||||||
|
############################################
|
||||||
|
if [ -f .env ]; then
|
||||||
|
export $(grep -v '^#' .env | xargs)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||||
|
echo "Erreur: GITEA_TOKEN non defini. Creez un fichier .env avec GITEA_TOKEN=votre_token"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Config
|
||||||
|
############################################
|
||||||
|
GITEA_URL="https://git.sorlinv.fr"
|
||||||
|
OWNER="sorlinv"
|
||||||
|
REPO="multi_render_blender"
|
||||||
|
PRODUCT_NAME="multi-render-blender"
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Version actuelle
|
||||||
|
############################################
|
||||||
|
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
||||||
|
echo "Version actuelle: v$CURRENT_VERSION"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Comment incrementer la version ?"
|
||||||
|
echo " 1) patch"
|
||||||
|
echo " 2) minor"
|
||||||
|
echo " 3) major"
|
||||||
|
echo " 4) garder ($CURRENT_VERSION)"
|
||||||
|
echo ""
|
||||||
|
read -p "Choix [1/2/3/4]: " BUMP_CHOICE
|
||||||
|
|
||||||
|
case "$BUMP_CHOICE" in
|
||||||
|
1) npm version patch --no-git-tag-version ;;
|
||||||
|
2) npm version minor --no-git-tag-version ;;
|
||||||
|
3) npm version major --no-git-tag-version ;;
|
||||||
|
4) echo "Version inchangee." ;;
|
||||||
|
*) echo "Choix invalide"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
VERSION=$(node -p "require('./package.json').version")
|
||||||
|
TAG="v$VERSION"
|
||||||
|
|
||||||
|
if [ "$CURRENT_VERSION" = "$VERSION" ]; then
|
||||||
|
echo "Aucune modification de version."
|
||||||
|
else
|
||||||
|
echo "Nouvelle version: $VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Mise a jour version.json (robuste)
|
||||||
|
############################################
|
||||||
|
echo "Synchronisation de version.json..."
|
||||||
|
|
||||||
|
if command -v jq >/dev/null 2>&1; then
|
||||||
|
tmp=$(mktemp)
|
||||||
|
jq --arg v "$VERSION" '.str_version=$v' version.json > "$tmp"
|
||||||
|
mv "$tmp" version.json
|
||||||
|
else
|
||||||
|
node <<EOF
|
||||||
|
const fs = require('fs');
|
||||||
|
const file = 'version.json';
|
||||||
|
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||||
|
data.str_version = "$VERSION";
|
||||||
|
fs.writeFileSync(file, JSON.stringify(data, null, 4) + '\n');
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Commit + tag local + push
|
||||||
|
############################################
|
||||||
|
git add package.json version.json
|
||||||
|
git commit -m "chore: release ${TAG}" || true
|
||||||
|
git tag -a "${TAG}" -m "Release ${TAG}"
|
||||||
|
git push
|
||||||
|
git push origin "${TAG}"
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Build
|
||||||
|
############################################
|
||||||
|
echo ""
|
||||||
|
echo "==> Build de la version $TAG"
|
||||||
|
rm -rf dist
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# ZIP
|
||||||
|
############################################
|
||||||
|
cd dist
|
||||||
|
|
||||||
|
if [ -d "linux-unpacked" ]; then
|
||||||
|
echo "Zip linux-unpacked..."
|
||||||
|
zip -r -q "${PRODUCT_NAME}-${VERSION}-linux-x64.zip" "linux-unpacked/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "win-unpacked" ]; then
|
||||||
|
echo "Zip win-unpacked..."
|
||||||
|
zip -r -q "${PRODUCT_NAME}-${VERSION}-win-x64.zip" "win-unpacked/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Release Gitea
|
||||||
|
############################################
|
||||||
|
echo ""
|
||||||
|
echo "==> Upload de la release $TAG sur Gitea..."
|
||||||
|
|
||||||
|
RELEASE_RESPONSE=$(curl -s -X POST \
|
||||||
|
"${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}/releases" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"tag_name\": \"${TAG}\",
|
||||||
|
\"name\": \"${TAG}\",
|
||||||
|
\"body\": \"Release ${TAG}\",
|
||||||
|
\"draft\": false,
|
||||||
|
\"prerelease\": false
|
||||||
|
}")
|
||||||
|
|
||||||
|
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id // empty')
|
||||||
|
|
||||||
|
if [ -z "$RELEASE_ID" ]; then
|
||||||
|
echo "Erreur lors de la creation de la release:"
|
||||||
|
echo "$RELEASE_RESPONSE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Release creee (id: $RELEASE_ID)"
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Upload assets
|
||||||
|
############################################
|
||||||
|
for FILE in dist/${PRODUCT_NAME}-${VERSION}-*.zip; do
|
||||||
|
[ -f "$FILE" ] || continue
|
||||||
|
|
||||||
|
FILENAME=$(basename "$FILE")
|
||||||
|
echo "Upload de $FILENAME..."
|
||||||
|
|
||||||
|
curl -s -X POST \
|
||||||
|
"${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}/releases/${RELEASE_ID}/assets?name=${FILENAME}" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-F "attachment=@${FILE}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> Release publiee : ${GITEA_URL}/${OWNER}/${REPO}/releases"
|
||||||
|
echo "Done!"
|
||||||
@@ -1,47 +1,154 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const { app } = require("electron");
|
||||||
|
const { execFileSync } = require("child_process");
|
||||||
|
|
||||||
const STR_EXE_NAME = process.platform === "win32" ? "blender.exe" : "blender";
|
const STR_EXE_NAME = process.platform === "win32" ? "blender.exe" : "blender";
|
||||||
|
const STR_CONFIG_FILE = "blender_path.json";
|
||||||
|
|
||||||
const PathResolver = {
|
const PathResolver = {
|
||||||
_str_blender_path: null,
|
_str_blender_path: null,
|
||||||
|
_is_found: false,
|
||||||
|
|
||||||
get_blender_path: () => {
|
load_saved_path: () => {
|
||||||
if (PathResolver._str_blender_path) {
|
let str_config_path = PathResolver._get_config_path();
|
||||||
return PathResolver._str_blender_path;
|
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(str_config_path)) {
|
||||||
|
let str_content = fs.readFileSync(str_config_path, "utf8");
|
||||||
|
let obj_data = JSON.parse(str_content);
|
||||||
|
|
||||||
|
if (obj_data.str_path && fs.existsSync(obj_data.str_path)) {
|
||||||
|
PathResolver._str_blender_path = obj_data.str_path;
|
||||||
|
PathResolver._is_found = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (obj_err) {
|
||||||
|
console.error("PathResolver: impossible de lire la config :", obj_err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mode package : resources/blender/
|
let str_detected = PathResolver.auto_detect();
|
||||||
let str_resources_dir = path.join(process.resourcesPath, "blender");
|
if (str_detected) {
|
||||||
let str_found = PathResolver._find_in_dir(str_resources_dir);
|
PathResolver._str_blender_path = str_detected;
|
||||||
if (str_found) {
|
PathResolver._is_found = true;
|
||||||
PathResolver._str_blender_path = str_found;
|
} else {
|
||||||
return str_found;
|
PathResolver._str_blender_path = "blender";
|
||||||
|
PathResolver._is_found = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mode dev : racine projet/blender/
|
|
||||||
let str_dev_dir = path.join(__dirname, "..", "..", "blender");
|
|
||||||
str_found = PathResolver._find_in_dir(str_dev_dir);
|
|
||||||
if (str_found) {
|
|
||||||
PathResolver._str_blender_path = str_found;
|
|
||||||
return str_found;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback : PATH systeme
|
|
||||||
PathResolver._str_blender_path = "blender";
|
|
||||||
return "blender";
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_find_in_dir: (str_dir) => {
|
get_blender_path: () => {
|
||||||
if (!fs.existsSync(str_dir)) {
|
if (!PathResolver._str_blender_path) {
|
||||||
return null;
|
PathResolver.load_saved_path();
|
||||||
|
}
|
||||||
|
return PathResolver._str_blender_path;
|
||||||
|
},
|
||||||
|
|
||||||
|
is_found: () => {
|
||||||
|
return PathResolver._is_found;
|
||||||
|
},
|
||||||
|
|
||||||
|
set_blender_path: (str_path) => {
|
||||||
|
if (!str_path || !fs.existsSync(str_path)) {
|
||||||
|
return { is_success: false, str_error: "Fichier introuvable : " + str_path };
|
||||||
}
|
}
|
||||||
|
|
||||||
let list_entries = fs.readdirSync(str_dir);
|
PathResolver._str_blender_path = str_path;
|
||||||
for (let str_entry of list_entries) {
|
PathResolver._is_found = true;
|
||||||
let str_exe = path.join(str_dir, str_entry, STR_EXE_NAME);
|
|
||||||
if (fs.existsSync(str_exe)) {
|
let str_config_path = PathResolver._get_config_path();
|
||||||
return str_exe;
|
try {
|
||||||
|
let str_dir = path.dirname(str_config_path);
|
||||||
|
if (!fs.existsSync(str_dir)) {
|
||||||
|
fs.mkdirSync(str_dir, { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(str_config_path, JSON.stringify({ str_path: str_path }, null, 4), "utf8");
|
||||||
|
} catch (obj_err) {
|
||||||
|
console.error("PathResolver: impossible de sauvegarder :", obj_err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { is_success: true, str_path: str_path };
|
||||||
|
},
|
||||||
|
|
||||||
|
auto_detect: () => {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
return PathResolver._auto_detect_windows();
|
||||||
|
}
|
||||||
|
return PathResolver._auto_detect_linux();
|
||||||
|
},
|
||||||
|
|
||||||
|
get_status: () => {
|
||||||
|
return {
|
||||||
|
str_path: PathResolver._str_blender_path || "blender",
|
||||||
|
is_found: PathResolver._is_found,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Private ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
_get_config_path: () => {
|
||||||
|
return path.join(app.getPath("userData"), STR_CONFIG_FILE);
|
||||||
|
},
|
||||||
|
|
||||||
|
_auto_detect_linux: () => {
|
||||||
|
let LIST_PATHS = [
|
||||||
|
"/usr/bin/blender",
|
||||||
|
"/snap/bin/blender",
|
||||||
|
"/usr/local/bin/blender",
|
||||||
|
"/opt/blender/blender",
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
let str_result = execFileSync("which", ["blender"], { encoding: "utf8", timeout: 5000 }).trim();
|
||||||
|
if (str_result && fs.existsSync(str_result)) {
|
||||||
|
return str_result;
|
||||||
|
}
|
||||||
|
} catch (obj_err) {
|
||||||
|
// which not found or blender not in PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let str_path of LIST_PATHS) {
|
||||||
|
if (fs.existsSync(str_path)) {
|
||||||
|
return str_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
_auto_detect_windows: () => {
|
||||||
|
try {
|
||||||
|
let str_result = execFileSync("where", ["blender.exe"], { encoding: "utf8", timeout: 5000 }).trim();
|
||||||
|
let str_first = str_result.split("\n")[0].trim();
|
||||||
|
if (str_first && fs.existsSync(str_first)) {
|
||||||
|
return str_first;
|
||||||
|
}
|
||||||
|
} catch (obj_err) {
|
||||||
|
// where not found or blender not in PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
let LIST_BASES = [
|
||||||
|
process.env.PROGRAMFILES || "C:\\Program Files",
|
||||||
|
process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let str_base of LIST_BASES) {
|
||||||
|
let str_foundation = path.join(str_base, "Blender Foundation");
|
||||||
|
if (!fs.existsSync(str_foundation)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let list_dirs = fs.readdirSync(str_foundation);
|
||||||
|
for (let str_dir of list_dirs) {
|
||||||
|
let str_exe = path.join(str_foundation, str_dir, STR_EXE_NAME);
|
||||||
|
if (fs.existsSync(str_exe)) {
|
||||||
|
return str_exe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (obj_err) {
|
||||||
|
// permission denied or similar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const BlenderProcess = require("./BlenderProcess.js");
|
const BlenderProcess = require("./BlenderProcess.js");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const { Notification } = require("electron");
|
||||||
|
|
||||||
const STR_STATUS_IDLE = "idle";
|
const STR_STATUS_IDLE = "idle";
|
||||||
const STR_STATUS_RUNNING = "running";
|
const STR_STATUS_RUNNING = "running";
|
||||||
@@ -15,6 +16,11 @@ class QueueManager {
|
|||||||
this.obj_current_process = null;
|
this.obj_current_process = null;
|
||||||
this.nb_last_render_ms = 0;
|
this.nb_last_render_ms = 0;
|
||||||
this.str_last_image_path = null;
|
this.str_last_image_path = null;
|
||||||
|
this.obj_notification_config = { is_notify_each_image: false, is_notify_all_done: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
set_notification_config(obj_config) {
|
||||||
|
this.obj_notification_config = obj_config || { is_notify_each_image: false, is_notify_all_done: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
start(obj_config) {
|
start(obj_config) {
|
||||||
@@ -56,10 +62,39 @@ class QueueManager {
|
|||||||
this.obj_current_process = null;
|
this.obj_current_process = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.nb_current_index < this.list_queue.length) {
|
||||||
|
let obj_item = this.list_queue[this.nb_current_index];
|
||||||
|
if (obj_item.str_status === "rendering") {
|
||||||
|
obj_item.str_status = "stopped";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._send_log("Rendu arrete.");
|
this._send_log("Rendu arrete.");
|
||||||
|
this._send_progress();
|
||||||
return Promise.resolve({ is_success: true });
|
return Promise.resolve({ is_success: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_queue(obj_config) {
|
||||||
|
let list_queue = this._build_queue(obj_config);
|
||||||
|
let list_existing = [];
|
||||||
|
|
||||||
|
for (let nb_i = 0; nb_i < list_queue.length; nb_i++) {
|
||||||
|
let obj_item = list_queue[nb_i];
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(obj_item.str_expected_file)) {
|
||||||
|
let nb_size = fs.statSync(obj_item.str_expected_file).size;
|
||||||
|
if (nb_size > 0) {
|
||||||
|
list_existing.push({ nb_index: nb_i, str_path: obj_item.str_expected_file });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (obj_err) {
|
||||||
|
// Fichier inaccessible, on ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve({ list_existing: list_existing, nb_total: list_queue.length });
|
||||||
|
}
|
||||||
|
|
||||||
_build_queue(obj_config) {
|
_build_queue(obj_config) {
|
||||||
let list_queue = [];
|
let list_queue = [];
|
||||||
let str_blend_path = obj_config.str_blend_file;
|
let str_blend_path = obj_config.str_blend_file;
|
||||||
@@ -159,11 +194,21 @@ class QueueManager {
|
|||||||
let nb_skip_count = 0;
|
let nb_skip_count = 0;
|
||||||
while (this.nb_current_index < this.list_queue.length && this.str_overwrite_mode === "skip") {
|
while (this.nb_current_index < this.list_queue.length && this.str_overwrite_mode === "skip") {
|
||||||
let obj_check = this.list_queue[this.nb_current_index];
|
let obj_check = this.list_queue[this.nb_current_index];
|
||||||
if (!fs.existsSync(obj_check.str_expected_file)) {
|
let is_exists = false;
|
||||||
|
let nb_size = 0;
|
||||||
|
try {
|
||||||
|
is_exists = fs.existsSync(obj_check.str_expected_file);
|
||||||
|
if (is_exists) {
|
||||||
|
nb_size = fs.statSync(obj_check.str_expected_file).size;
|
||||||
|
}
|
||||||
|
} catch (obj_fs_err) {
|
||||||
|
is_exists = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_exists) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let obj_stats = fs.statSync(obj_check.str_expected_file);
|
if (nb_size === 0) {
|
||||||
if (obj_stats.size === 0) {
|
|
||||||
this._send_log("Placeholder vide detecte, re-rendu : " + obj_check.str_camera_name + " F" + obj_check.nb_frame);
|
this._send_log("Placeholder vide detecte, re-rendu : " + obj_check.str_camera_name + " F" + obj_check.nb_frame);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -181,10 +226,33 @@ class QueueManager {
|
|||||||
this.str_status = STR_STATUS_IDLE;
|
this.str_status = STR_STATUS_IDLE;
|
||||||
this._send_log("Tous les rendus sont termines !");
|
this._send_log("Tous les rendus sont termines !");
|
||||||
this._send_event("render-complete", { is_all_done: true });
|
this._send_event("render-complete", { is_all_done: true });
|
||||||
|
if (this.obj_notification_config.is_notify_all_done) {
|
||||||
|
this._send_notification("Rendus termines", "Tous les rendus de la file sont termines (" + this.list_queue.length + " elements).");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let obj_item = this.list_queue[this.nb_current_index];
|
let obj_item = this.list_queue[this.nb_current_index];
|
||||||
|
|
||||||
|
// Re-verification avant rendu : rattrape les fichiers non detectes par le batch skip
|
||||||
|
if (this.str_overwrite_mode === "skip") {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(obj_item.str_expected_file)) {
|
||||||
|
let nb_recheck_size = fs.statSync(obj_item.str_expected_file).size;
|
||||||
|
if (nb_recheck_size > 0) {
|
||||||
|
obj_item.str_status = "skipped";
|
||||||
|
this._send_log("Skip : " + obj_item.str_camera_name + " F" + obj_item.nb_frame + " (existant)");
|
||||||
|
this.nb_current_index++;
|
||||||
|
this._send_progress();
|
||||||
|
this._process_next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (obj_recheck_err) {
|
||||||
|
// Erreur fs, on continue le rendu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
obj_item.str_status = "rendering";
|
obj_item.str_status = "rendering";
|
||||||
|
|
||||||
if (this.str_overwrite_mode === "skip") {
|
if (this.str_overwrite_mode === "skip") {
|
||||||
@@ -234,6 +302,10 @@ class QueueManager {
|
|||||||
this._send_event("preview-update", str_image);
|
this._send_event("preview-update", str_image);
|
||||||
this._send_log("Termine : " + str_image);
|
this._send_log("Termine : " + str_image);
|
||||||
|
|
||||||
|
if (this.obj_notification_config.is_notify_each_image) {
|
||||||
|
this._send_notification("Rendu termine", obj_item.str_camera_name + " — Frame " + obj_item.nb_frame);
|
||||||
|
}
|
||||||
|
|
||||||
this.nb_current_index++;
|
this.nb_current_index++;
|
||||||
this._send_progress();
|
this._send_progress();
|
||||||
this._process_next();
|
this._process_next();
|
||||||
@@ -260,11 +332,12 @@ class QueueManager {
|
|||||||
this.nb_last_render_ms = Date.now() - nb_start;
|
this.nb_last_render_ms = Date.now() - nb_start;
|
||||||
this.str_last_image_path = null;
|
this.str_last_image_path = null;
|
||||||
|
|
||||||
this._send_log("ERREUR : " + obj_err.str_message);
|
let str_err_msg = obj_err.str_message || obj_err.message || String(obj_err);
|
||||||
|
this._send_log("ERREUR : " + str_err_msg);
|
||||||
this._send_event("render-error", {
|
this._send_event("render-error", {
|
||||||
str_camera: obj_item.str_camera_name,
|
str_camera: obj_item.str_camera_name,
|
||||||
nb_frame: obj_item.nb_frame,
|
nb_frame: obj_item.nb_frame,
|
||||||
str_error: obj_err.str_message,
|
str_error: str_err_msg,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.nb_current_index++;
|
this.nb_current_index++;
|
||||||
@@ -276,9 +349,14 @@ class QueueManager {
|
|||||||
_send_progress() {
|
_send_progress() {
|
||||||
let obj_item = this.list_queue[this.nb_current_index] || {};
|
let obj_item = this.list_queue[this.nb_current_index] || {};
|
||||||
let list_skipped = [];
|
let list_skipped = [];
|
||||||
|
let list_stopped = [];
|
||||||
|
let list_skipped_paths = [];
|
||||||
for (let nb_i = 0; nb_i < this.list_queue.length; nb_i++) {
|
for (let nb_i = 0; nb_i < this.list_queue.length; nb_i++) {
|
||||||
if (this.list_queue[nb_i].str_status === "skipped") {
|
if (this.list_queue[nb_i].str_status === "skipped") {
|
||||||
list_skipped.push(nb_i);
|
list_skipped.push(nb_i);
|
||||||
|
list_skipped_paths.push({ nb_index: nb_i, str_path: this.list_queue[nb_i].str_expected_file });
|
||||||
|
} else if (this.list_queue[nb_i].str_status === "stopped") {
|
||||||
|
list_stopped.push(nb_i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._send_event("render-progress", {
|
this._send_event("render-progress", {
|
||||||
@@ -290,6 +368,8 @@ class QueueManager {
|
|||||||
nb_last_render_ms: this.nb_last_render_ms,
|
nb_last_render_ms: this.nb_last_render_ms,
|
||||||
str_last_image_path: this.str_last_image_path,
|
str_last_image_path: this.str_last_image_path,
|
||||||
list_skipped: list_skipped,
|
list_skipped: list_skipped,
|
||||||
|
list_stopped: list_stopped,
|
||||||
|
list_skipped_paths: list_skipped_paths,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +382,13 @@ class QueueManager {
|
|||||||
this.obj_window.webContents.send(str_channel, obj_data);
|
this.obj_window.webContents.send(str_channel, obj_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_send_notification(str_title, str_body) {
|
||||||
|
if (Notification.isSupported()) {
|
||||||
|
let obj_notif = new Notification({ title: str_title, body: str_body });
|
||||||
|
obj_notif.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = QueueManager;
|
module.exports = QueueManager;
|
||||||
|
|||||||
@@ -8,11 +8,15 @@ const { execFile } = require("child_process");
|
|||||||
const GITEA_HOST = "git.sorlinv.fr";
|
const GITEA_HOST = "git.sorlinv.fr";
|
||||||
const REPO_PATH = "/api/v1/repos/sorlinv/multi_render_blender/tags";
|
const REPO_PATH = "/api/v1/repos/sorlinv/multi_render_blender/tags";
|
||||||
const ARCHIVE_URL_BASE = "https://git.sorlinv.fr/sorlinv/multi_render_blender/archive/";
|
const ARCHIVE_URL_BASE = "https://git.sorlinv.fr/sorlinv/multi_render_blender/archive/";
|
||||||
const NB_TIMEOUT = 10000;
|
const NB_TIMEOUT_API = 10000;
|
||||||
|
const NB_TIMEOUT_DOWNLOAD = 60000;
|
||||||
|
const LIST_REQUIRED_FILES = ["main.js", "version.json"];
|
||||||
|
const LIST_TARGETS = ["main.js", "preload.js", "src", "version.json", "package.json"];
|
||||||
|
|
||||||
const UpdateManager = {
|
const UpdateManager = {
|
||||||
obj_window: null,
|
obj_window: null,
|
||||||
str_local_version: null,
|
str_local_version: null,
|
||||||
|
is_updating: false,
|
||||||
|
|
||||||
init: (obj_window) => {
|
init: (obj_window) => {
|
||||||
UpdateManager.obj_window = obj_window;
|
UpdateManager.obj_window = obj_window;
|
||||||
@@ -61,12 +65,18 @@ const UpdateManager = {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((obj_err) => {
|
||||||
|
console.error("UpdateManager: check_for_updates echoue :", obj_err.message);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
download_and_apply: (str_tag_name) => {
|
download_and_apply: (str_tag_name) => {
|
||||||
|
if (UpdateManager.is_updating) {
|
||||||
|
return Promise.reject(new Error("Mise a jour deja en cours"));
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateManager.is_updating = true;
|
||||||
let str_temp_dir = path.join(app.getPath("temp"), "mrb_update_" + Date.now());
|
let str_temp_dir = path.join(app.getPath("temp"), "mrb_update_" + Date.now());
|
||||||
let str_zip_path = path.join(str_temp_dir, "update.zip");
|
let str_zip_path = path.join(str_temp_dir, "update.zip");
|
||||||
|
|
||||||
@@ -102,11 +112,14 @@ const UpdateManager = {
|
|||||||
nb_percent: 100,
|
nb_percent: 100,
|
||||||
});
|
});
|
||||||
UpdateManager._cleanup(str_temp_dir);
|
UpdateManager._cleanup(str_temp_dir);
|
||||||
|
UpdateManager.is_updating = false;
|
||||||
app.relaunch();
|
app.relaunch();
|
||||||
app.quit();
|
app.quit();
|
||||||
})
|
})
|
||||||
.catch((obj_err) => {
|
.catch((obj_err) => {
|
||||||
|
UpdateManager.is_updating = false;
|
||||||
UpdateManager._cleanup(str_temp_dir);
|
UpdateManager._cleanup(str_temp_dir);
|
||||||
|
console.error("UpdateManager: download_and_apply echoue :", obj_err.message);
|
||||||
UpdateManager._send_event("update-error", {
|
UpdateManager._send_event("update-error", {
|
||||||
str_message: obj_err.message || "Erreur inconnue",
|
str_message: obj_err.message || "Erreur inconnue",
|
||||||
str_tag_name: str_tag_name,
|
str_tag_name: str_tag_name,
|
||||||
@@ -154,7 +167,7 @@ const UpdateManager = {
|
|||||||
hostname: GITEA_HOST,
|
hostname: GITEA_HOST,
|
||||||
path: REPO_PATH,
|
path: REPO_PATH,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
timeout: NB_TIMEOUT,
|
timeout: NB_TIMEOUT_API,
|
||||||
headers: {
|
headers: {
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
"User-Agent": "MultiRenderBlender",
|
"User-Agent": "MultiRenderBlender",
|
||||||
@@ -208,10 +221,11 @@ const UpdateManager = {
|
|||||||
let obj_module = obj_parsed.protocol === "https:" ? https : http;
|
let obj_module = obj_parsed.protocol === "https:" ? https : http;
|
||||||
|
|
||||||
let obj_req = obj_module.get(str_download_url, {
|
let obj_req = obj_module.get(str_download_url, {
|
||||||
timeout: NB_TIMEOUT,
|
timeout: NB_TIMEOUT_DOWNLOAD,
|
||||||
headers: { "User-Agent": "MultiRenderBlender" },
|
headers: { "User-Agent": "MultiRenderBlender" },
|
||||||
}, (obj_res) => {
|
}, (obj_res) => {
|
||||||
if (obj_res.statusCode === 301 || obj_res.statusCode === 302) {
|
if (obj_res.statusCode >= 300 && obj_res.statusCode < 400) {
|
||||||
|
obj_res.resume();
|
||||||
let str_redirect = obj_res.headers.location;
|
let str_redirect = obj_res.headers.location;
|
||||||
if (!str_redirect) {
|
if (!str_redirect) {
|
||||||
reject(new Error("Redirection sans header Location"));
|
reject(new Error("Redirection sans header Location"));
|
||||||
@@ -224,6 +238,7 @@ const UpdateManager = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (obj_res.statusCode !== 200) {
|
if (obj_res.statusCode !== 200) {
|
||||||
|
obj_res.resume();
|
||||||
reject(new Error("Telechargement echoue : HTTP " + obj_res.statusCode));
|
reject(new Error("Telechargement echoue : HTTP " + obj_res.statusCode));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -274,13 +289,8 @@ const UpdateManager = {
|
|||||||
_extract_zip: (str_zip_path, str_dest_dir) => {
|
_extract_zip: (str_zip_path, str_dest_dir) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
let str_cmd = "Expand-Archive";
|
let str_script = "Expand-Archive -Path '" + str_zip_path + "' -DestinationPath '" + str_dest_dir + "' -Force";
|
||||||
let list_args = [
|
execFile("powershell.exe", ["-NoProfile", "-Command", str_script], (obj_err) => {
|
||||||
"-Path", str_zip_path,
|
|
||||||
"-DestinationPath", str_dest_dir,
|
|
||||||
"-Force",
|
|
||||||
];
|
|
||||||
execFile("powershell.exe", ["-Command", str_cmd + " " + list_args.join(" ")], (obj_err) => {
|
|
||||||
if (obj_err) {
|
if (obj_err) {
|
||||||
reject(new Error("Extraction echouee : " + obj_err.message));
|
reject(new Error("Extraction echouee : " + obj_err.message));
|
||||||
return;
|
return;
|
||||||
@@ -309,14 +319,34 @@ const UpdateManager = {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let str_full = path.join(str_temp_dir, str_entry);
|
let str_full = path.join(str_temp_dir, str_entry);
|
||||||
if (fs.statSync(str_full).isDirectory()) {
|
if (!fs.statSync(str_full).isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let str_version_check = path.join(str_full, "version.json");
|
||||||
|
if (fs.existsSync(str_version_check)) {
|
||||||
str_root = str_full;
|
str_root = str_full;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let list_sub = fs.readdirSync(str_full);
|
||||||
|
for (let str_sub of list_sub) {
|
||||||
|
let str_sub_full = path.join(str_full, str_sub);
|
||||||
|
if (fs.statSync(str_sub_full).isDirectory()) {
|
||||||
|
let str_nested_check = path.join(str_sub_full, "version.json");
|
||||||
|
if (fs.existsSync(str_nested_check)) {
|
||||||
|
str_root = str_sub_full;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (str_root) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!str_root) {
|
if (!str_root) {
|
||||||
reject(new Error("Dossier extrait introuvable"));
|
reject(new Error("Dossier extrait introuvable (version.json absent)"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,15 +356,23 @@ const UpdateManager = {
|
|||||||
|
|
||||||
_replace_app_files: (str_source_dir) => {
|
_replace_app_files: (str_source_dir) => {
|
||||||
let str_app_dir = path.join(__dirname, "..", "..");
|
let str_app_dir = path.join(__dirname, "..", "..");
|
||||||
let LIST_TARGETS = ["main.js", "preload.js", "src", "version.json", "package.json"];
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
|
for (let str_required of LIST_REQUIRED_FILES) {
|
||||||
|
let str_check = path.join(str_source_dir, str_required);
|
||||||
|
if (!fs.existsSync(str_check)) {
|
||||||
|
reject(new Error("Fichier requis manquant dans la mise a jour : " + str_required));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let str_target of LIST_TARGETS) {
|
for (let str_target of LIST_TARGETS) {
|
||||||
let str_src = path.join(str_source_dir, str_target);
|
let str_src = path.join(str_source_dir, str_target);
|
||||||
let str_dest = path.join(str_app_dir, str_target);
|
let str_dest = path.join(str_app_dir, str_target);
|
||||||
|
|
||||||
if (!fs.existsSync(str_src)) {
|
if (!fs.existsSync(str_src)) {
|
||||||
|
console.log("UpdateManager: cible absente dans la source, ignoree : " + str_target);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; font-src https://cdn.jsdelivr.net; img-src 'self' file: data:;">
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; font-src https://cdn.jsdelivr.net; img-src 'self' file: data:;">
|
||||||
<title>Multi Render Blender</title>
|
<title>Multi Render Blender</title>
|
||||||
|
|
||||||
<!-- Bootstrap 5 -->
|
<!-- Bootstrap 5 -->
|
||||||
@@ -172,6 +172,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex gap-2 mb-3">
|
<div class="d-flex gap-2 mb-3">
|
||||||
|
<button id="btn_check_queue" class="btn btn-outline-info" disabled title="Verifier les fichiers existants">
|
||||||
|
<i class="mdi mdi-check-all"></i>
|
||||||
|
</button>
|
||||||
<button id="btn_start" class="btn btn-success flex-fill" disabled>
|
<button id="btn_start" class="btn btn-success flex-fill" disabled>
|
||||||
<i class="mdi mdi-play"></i> Start
|
<i class="mdi mdi-play"></i> Start
|
||||||
</button>
|
</button>
|
||||||
@@ -253,6 +256,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script src="scripts/ConsoleLog.js"></script>
|
<script src="scripts/ConsoleLog.js"></script>
|
||||||
<script src="scripts/CameraList.js"></script>
|
<script src="scripts/CameraList.js"></script>
|
||||||
@@ -261,6 +267,8 @@
|
|||||||
<script src="scripts/PreviewPanel.js"></script>
|
<script src="scripts/PreviewPanel.js"></script>
|
||||||
<script src="scripts/ProgressBar.js"></script>
|
<script src="scripts/ProgressBar.js"></script>
|
||||||
<script src="scripts/UpdateBanner.js"></script>
|
<script src="scripts/UpdateBanner.js"></script>
|
||||||
|
<script src="scripts/BlenderPath.js"></script>
|
||||||
|
<script src="scripts/NotificationConfig.js"></script>
|
||||||
<script src="scripts/App.js"></script>
|
<script src="scripts/App.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ const App = {
|
|||||||
PreviewPanel.init();
|
PreviewPanel.init();
|
||||||
ProgressBar.init();
|
ProgressBar.init();
|
||||||
UpdateBanner.init();
|
UpdateBanner.init();
|
||||||
|
BlenderPath.init();
|
||||||
|
NotificationConfig.init();
|
||||||
|
|
||||||
App._bind_events();
|
App._bind_events();
|
||||||
App._bind_render_events();
|
App._bind_render_events();
|
||||||
@@ -43,6 +45,11 @@ const App = {
|
|||||||
App._stop_render();
|
App._stop_render();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let obj_btn_check = document.getElementById("btn_check_queue");
|
||||||
|
obj_btn_check.addEventListener("click", () => {
|
||||||
|
App._check_queue();
|
||||||
|
});
|
||||||
|
|
||||||
let obj_btn_save = document.getElementById("btn_save_config");
|
let obj_btn_save = document.getElementById("btn_save_config");
|
||||||
obj_btn_save.addEventListener("click", () => {
|
obj_btn_save.addEventListener("click", () => {
|
||||||
App._save_config();
|
App._save_config();
|
||||||
@@ -217,6 +224,51 @@ const App = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_check_queue: () => {
|
||||||
|
let obj_btn_check = document.getElementById("btn_check_queue");
|
||||||
|
if (obj_btn_check.disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!App.str_output_path) {
|
||||||
|
ConsoleLog.add("Veuillez selectionner un dossier de sortie.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let str_mode = document.querySelector('input[name="render_mode"]:checked').value;
|
||||||
|
let str_output_mode = document.querySelector('input[name="output_mode"]:checked').value;
|
||||||
|
let list_cameras = CameraList.list_cameras;
|
||||||
|
let str_frame_prefix = document.getElementById("input_frame_prefix").value || "";
|
||||||
|
let nb_frame_padding = parseInt(document.getElementById("input_frame_padding").value, 10) || 5;
|
||||||
|
|
||||||
|
let obj_config = {
|
||||||
|
str_blend_file: App.str_blend_path,
|
||||||
|
str_render_mode: str_mode,
|
||||||
|
str_output_mode: str_output_mode,
|
||||||
|
str_frame_prefix: str_frame_prefix,
|
||||||
|
nb_frame_padding: nb_frame_padding,
|
||||||
|
str_output_path: App.str_output_path,
|
||||||
|
list_cameras: list_cameras,
|
||||||
|
};
|
||||||
|
|
||||||
|
RenderQueue.build_display(str_mode, list_cameras);
|
||||||
|
obj_btn_check.disabled = true;
|
||||||
|
ConsoleLog.add("Verification des fichiers existants...");
|
||||||
|
|
||||||
|
window.api.check_queue(obj_config)
|
||||||
|
.then((obj_result) => {
|
||||||
|
obj_btn_check.disabled = false;
|
||||||
|
RenderQueue.mark_existing(obj_result.list_existing);
|
||||||
|
let nb_existing = obj_result.list_existing.length;
|
||||||
|
let nb_to_render = obj_result.nb_total - nb_existing;
|
||||||
|
ConsoleLog.add("Verification terminee : " + nb_existing + " fichier(s) existant(s), " + nb_to_render + " a rendre.");
|
||||||
|
})
|
||||||
|
.catch((obj_err) => {
|
||||||
|
obj_btn_check.disabled = false;
|
||||||
|
ConsoleLog.add("Erreur verification : " + obj_err.message);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
_save_config: () => {
|
_save_config: () => {
|
||||||
let str_mode = document.querySelector('input[name="render_mode"]:checked').value;
|
let str_mode = document.querySelector('input[name="render_mode"]:checked').value;
|
||||||
let str_output_mode = document.querySelector('input[name="output_mode"]:checked').value;
|
let str_output_mode = document.querySelector('input[name="output_mode"]:checked').value;
|
||||||
@@ -310,15 +362,18 @@ const App = {
|
|||||||
let obj_btn_start = document.getElementById("btn_start");
|
let obj_btn_start = document.getElementById("btn_start");
|
||||||
let obj_btn_pause = document.getElementById("btn_pause");
|
let obj_btn_pause = document.getElementById("btn_pause");
|
||||||
let obj_btn_stop = document.getElementById("btn_stop");
|
let obj_btn_stop = document.getElementById("btn_stop");
|
||||||
|
let obj_btn_check = document.getElementById("btn_check_queue");
|
||||||
|
|
||||||
if (str_state === "running") {
|
if (str_state === "running") {
|
||||||
obj_btn_start.disabled = true;
|
obj_btn_start.disabled = true;
|
||||||
obj_btn_pause.disabled = false;
|
obj_btn_pause.disabled = false;
|
||||||
obj_btn_stop.disabled = false;
|
obj_btn_stop.disabled = false;
|
||||||
|
obj_btn_check.disabled = true;
|
||||||
} else if (str_state === "paused") {
|
} else if (str_state === "paused") {
|
||||||
obj_btn_start.disabled = false;
|
obj_btn_start.disabled = false;
|
||||||
obj_btn_pause.disabled = true;
|
obj_btn_pause.disabled = true;
|
||||||
obj_btn_stop.disabled = false;
|
obj_btn_stop.disabled = false;
|
||||||
|
obj_btn_check.disabled = true;
|
||||||
} else {
|
} else {
|
||||||
App._update_start_button();
|
App._update_start_button();
|
||||||
obj_btn_pause.disabled = true;
|
obj_btn_pause.disabled = true;
|
||||||
@@ -328,10 +383,12 @@ const App = {
|
|||||||
|
|
||||||
_update_start_button: () => {
|
_update_start_button: () => {
|
||||||
let obj_btn_start = document.getElementById("btn_start");
|
let obj_btn_start = document.getElementById("btn_start");
|
||||||
|
let obj_btn_check = document.getElementById("btn_check_queue");
|
||||||
let is_ready = App.str_blend_path
|
let is_ready = App.str_blend_path
|
||||||
&& App.str_output_path
|
&& App.str_output_path
|
||||||
&& CameraList.get_enabled_cameras().length > 0;
|
&& CameraList.get_enabled_cameras().length > 0;
|
||||||
obj_btn_start.disabled = !is_ready;
|
obj_btn_start.disabled = !is_ready;
|
||||||
|
obj_btn_check.disabled = !is_ready;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
197
src/renderer/scripts/BlenderPath.js
Normal file
197
src/renderer/scripts/BlenderPath.js
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
const BlenderPath = {
|
||||||
|
str_current_path: null,
|
||||||
|
is_found: false,
|
||||||
|
obj_modal: null,
|
||||||
|
|
||||||
|
init: () => {
|
||||||
|
BlenderPath._create_badge();
|
||||||
|
BlenderPath._create_modal();
|
||||||
|
BlenderPath._bind_events();
|
||||||
|
},
|
||||||
|
|
||||||
|
_create_badge: () => {
|
||||||
|
let obj_nav_right = document.querySelector("nav .d-flex.gap-2");
|
||||||
|
let obj_badge = document.createElement("button");
|
||||||
|
obj_badge.id = "btn_blender_status";
|
||||||
|
obj_badge.className = "btn btn-sm btn-outline-secondary";
|
||||||
|
obj_badge.title = "Chemin Blender";
|
||||||
|
obj_badge.innerHTML = '<i class="mdi mdi-blender-software"></i>';
|
||||||
|
obj_nav_right.insertBefore(obj_badge, obj_nav_right.firstChild);
|
||||||
|
|
||||||
|
obj_badge.addEventListener("click", () => {
|
||||||
|
BlenderPath._open_modal();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_create_modal: () => {
|
||||||
|
let obj_modal_el = document.createElement("div");
|
||||||
|
obj_modal_el.id = "modal_blender_path";
|
||||||
|
obj_modal_el.className = "modal fade";
|
||||||
|
obj_modal_el.tabIndex = -1;
|
||||||
|
obj_modal_el.innerHTML =
|
||||||
|
'<div class="modal-dialog modal-dialog-centered">' +
|
||||||
|
'<div class="modal-content bg-dark text-light border-secondary">' +
|
||||||
|
'<div class="modal-header border-secondary">' +
|
||||||
|
'<h6 class="modal-title"><i class="mdi mdi-blender-software me-2"></i>Chemin Blender</h6>' +
|
||||||
|
'<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="modal-body">' +
|
||||||
|
'<p class="text-light-emphasis mb-3" style="font-size: 0.85rem;">' +
|
||||||
|
'Selectionnez l\'executable Blender sur votre machine.' +
|
||||||
|
'</p>' +
|
||||||
|
'<div class="input-group input-group-sm mb-3">' +
|
||||||
|
'<input type="text" id="input_blender_path" class="form-control bg-dark text-light border-secondary" placeholder="Aucun chemin configure" readonly>' +
|
||||||
|
'<button id="btn_browse_blender" class="btn btn-outline-primary" type="button">' +
|
||||||
|
'<i class="mdi mdi-folder-search-outline"></i> Parcourir' +
|
||||||
|
'</button>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="d-flex justify-content-between align-items-center">' +
|
||||||
|
'<button id="btn_detect_blender" class="btn btn-sm btn-outline-secondary">' +
|
||||||
|
'<i class="mdi mdi-magnify me-1"></i>Detecter automatiquement' +
|
||||||
|
'</button>' +
|
||||||
|
'<span id="label_blender_status" class="badge bg-danger">' +
|
||||||
|
'<i class="mdi mdi-close-circle me-1"></i>Non trouve' +
|
||||||
|
'</span>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="modal-footer border-secondary">' +
|
||||||
|
'<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Annuler</button>' +
|
||||||
|
'<button id="btn_validate_blender" type="button" class="btn btn-sm btn-primary" disabled>' +
|
||||||
|
'<i class="mdi mdi-check me-1"></i>Valider' +
|
||||||
|
'</button>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
document.body.appendChild(obj_modal_el);
|
||||||
|
BlenderPath.obj_modal = new bootstrap.Modal(obj_modal_el);
|
||||||
|
},
|
||||||
|
|
||||||
|
_bind_events: () => {
|
||||||
|
let obj_btn_browse = document.getElementById("btn_browse_blender");
|
||||||
|
obj_btn_browse.addEventListener("click", () => {
|
||||||
|
BlenderPath._browse();
|
||||||
|
});
|
||||||
|
|
||||||
|
let obj_btn_detect = document.getElementById("btn_detect_blender");
|
||||||
|
obj_btn_detect.addEventListener("click", () => {
|
||||||
|
BlenderPath._detect();
|
||||||
|
});
|
||||||
|
|
||||||
|
let obj_btn_validate = document.getElementById("btn_validate_blender");
|
||||||
|
obj_btn_validate.addEventListener("click", () => {
|
||||||
|
BlenderPath._validate();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.api.on_blender_path_status((obj_data) => {
|
||||||
|
BlenderPath.str_current_path = obj_data.str_path;
|
||||||
|
BlenderPath.is_found = obj_data.is_found;
|
||||||
|
BlenderPath._update_badge();
|
||||||
|
|
||||||
|
if (!obj_data.is_found) {
|
||||||
|
BlenderPath._open_modal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_open_modal: () => {
|
||||||
|
let obj_input = document.getElementById("input_blender_path");
|
||||||
|
obj_input.value = BlenderPath.is_found ? BlenderPath.str_current_path : "";
|
||||||
|
BlenderPath._update_modal_status(BlenderPath.is_found, BlenderPath.str_current_path);
|
||||||
|
BlenderPath.obj_modal.show();
|
||||||
|
|
||||||
|
if (!BlenderPath.is_found) {
|
||||||
|
BlenderPath._detect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_browse: () => {
|
||||||
|
window.api.select_blender_exe()
|
||||||
|
.then((str_path) => {
|
||||||
|
if (!str_path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let obj_input = document.getElementById("input_blender_path");
|
||||||
|
obj_input.value = str_path;
|
||||||
|
BlenderPath._update_modal_status(true, str_path);
|
||||||
|
})
|
||||||
|
.catch((obj_err) => {
|
||||||
|
ConsoleLog.add("Erreur selection Blender : " + obj_err.message);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_detect: () => {
|
||||||
|
let obj_btn = document.getElementById("btn_detect_blender");
|
||||||
|
obj_btn.disabled = true;
|
||||||
|
obj_btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Detection...';
|
||||||
|
|
||||||
|
window.api.get_blender_path()
|
||||||
|
.then((obj_data) => {
|
||||||
|
obj_btn.disabled = false;
|
||||||
|
obj_btn.innerHTML = '<i class="mdi mdi-magnify me-1"></i>Detecter automatiquement';
|
||||||
|
|
||||||
|
if (obj_data.is_found) {
|
||||||
|
let obj_input = document.getElementById("input_blender_path");
|
||||||
|
obj_input.value = obj_data.str_path;
|
||||||
|
BlenderPath._update_modal_status(true, obj_data.str_path);
|
||||||
|
} else {
|
||||||
|
BlenderPath._update_modal_status(false, null);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
obj_btn.disabled = false;
|
||||||
|
obj_btn.innerHTML = '<i class="mdi mdi-magnify me-1"></i>Detecter automatiquement';
|
||||||
|
BlenderPath._update_modal_status(false, null);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_validate: () => {
|
||||||
|
let str_path = document.getElementById("input_blender_path").value;
|
||||||
|
if (!str_path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.api.set_blender_path(str_path)
|
||||||
|
.then((obj_result) => {
|
||||||
|
if (obj_result.is_success) {
|
||||||
|
BlenderPath.str_current_path = obj_result.str_path;
|
||||||
|
BlenderPath.is_found = true;
|
||||||
|
BlenderPath._update_badge();
|
||||||
|
BlenderPath.obj_modal.hide();
|
||||||
|
ConsoleLog.add("Chemin Blender configure : " + obj_result.str_path);
|
||||||
|
} else {
|
||||||
|
BlenderPath._update_modal_status(false, null);
|
||||||
|
ConsoleLog.add("Chemin Blender invalide : " + (obj_result.str_error || ""));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((obj_err) => {
|
||||||
|
ConsoleLog.add("Erreur configuration Blender : " + obj_err.message);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_update_badge: () => {
|
||||||
|
let obj_badge = document.getElementById("btn_blender_status");
|
||||||
|
if (BlenderPath.is_found) {
|
||||||
|
obj_badge.className = "btn btn-sm btn-outline-success";
|
||||||
|
obj_badge.title = "Blender : " + BlenderPath.str_current_path;
|
||||||
|
} else {
|
||||||
|
obj_badge.className = "btn btn-sm btn-outline-danger";
|
||||||
|
obj_badge.title = "Blender non trouve";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_update_modal_status: (is_valid, str_path) => {
|
||||||
|
let obj_label = document.getElementById("label_blender_status");
|
||||||
|
let obj_btn_validate = document.getElementById("btn_validate_blender");
|
||||||
|
|
||||||
|
if (is_valid && str_path) {
|
||||||
|
obj_label.className = "badge bg-success";
|
||||||
|
obj_label.innerHTML = '<i class="mdi mdi-check-circle me-1"></i>Trouve';
|
||||||
|
obj_btn_validate.disabled = false;
|
||||||
|
} else {
|
||||||
|
obj_label.className = "badge bg-danger";
|
||||||
|
obj_label.innerHTML = '<i class="mdi mdi-close-circle me-1"></i>Non trouve';
|
||||||
|
obj_btn_validate.disabled = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
132
src/renderer/scripts/NotificationConfig.js
Normal file
132
src/renderer/scripts/NotificationConfig.js
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
const NotificationConfig = {
|
||||||
|
is_notify_each_image: false,
|
||||||
|
is_notify_all_done: true,
|
||||||
|
obj_modal: null,
|
||||||
|
|
||||||
|
init: () => {
|
||||||
|
NotificationConfig._create_badge();
|
||||||
|
NotificationConfig._create_modal();
|
||||||
|
NotificationConfig._bind_events();
|
||||||
|
NotificationConfig._load_config();
|
||||||
|
},
|
||||||
|
|
||||||
|
_create_badge: () => {
|
||||||
|
let obj_nav_right = document.querySelector("nav .d-flex.gap-2");
|
||||||
|
let obj_badge = document.createElement("button");
|
||||||
|
obj_badge.id = "btn_notification_config";
|
||||||
|
obj_badge.className = "btn btn-sm btn-outline-secondary";
|
||||||
|
obj_badge.title = "Notifications";
|
||||||
|
obj_badge.innerHTML = '<i class="mdi mdi-bell-outline"></i>';
|
||||||
|
let obj_blender_btn = document.getElementById("btn_blender_status");
|
||||||
|
if (obj_blender_btn) {
|
||||||
|
obj_nav_right.insertBefore(obj_badge, obj_blender_btn.nextSibling);
|
||||||
|
} else {
|
||||||
|
obj_nav_right.insertBefore(obj_badge, obj_nav_right.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
obj_badge.addEventListener("click", () => {
|
||||||
|
NotificationConfig._open_modal();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_create_modal: () => {
|
||||||
|
let obj_modal_el = document.createElement("div");
|
||||||
|
obj_modal_el.id = "modal_notification_config";
|
||||||
|
obj_modal_el.className = "modal fade";
|
||||||
|
obj_modal_el.tabIndex = -1;
|
||||||
|
obj_modal_el.innerHTML =
|
||||||
|
'<div class="modal-dialog modal-dialog-centered modal-sm">' +
|
||||||
|
'<div class="modal-content bg-dark text-light border-secondary">' +
|
||||||
|
'<div class="modal-header border-secondary">' +
|
||||||
|
'<h6 class="modal-title"><i class="mdi mdi-bell-outline me-2"></i>Notifications</h6>' +
|
||||||
|
'<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="modal-body">' +
|
||||||
|
'<p class="text-light-emphasis mb-3" style="font-size: 0.85rem;">' +
|
||||||
|
'Configurez les notifications systeme.' +
|
||||||
|
'</p>' +
|
||||||
|
'<div class="form-check mb-2">' +
|
||||||
|
'<input class="form-check-input" type="checkbox" id="check_notify_each_image">' +
|
||||||
|
'<label class="form-check-label" for="check_notify_each_image">' +
|
||||||
|
'A chaque image rendue' +
|
||||||
|
'</label>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="form-check">' +
|
||||||
|
'<input class="form-check-input" type="checkbox" id="check_notify_all_done" checked>' +
|
||||||
|
'<label class="form-check-label" for="check_notify_all_done">' +
|
||||||
|
'Quand tous les rendus sont termines' +
|
||||||
|
'</label>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="modal-footer border-secondary">' +
|
||||||
|
'<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Annuler</button>' +
|
||||||
|
'<button id="btn_save_notification" type="button" class="btn btn-sm btn-primary">' +
|
||||||
|
'<i class="mdi mdi-check me-1"></i>Valider' +
|
||||||
|
'</button>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
document.body.appendChild(obj_modal_el);
|
||||||
|
NotificationConfig.obj_modal = new bootstrap.Modal(obj_modal_el);
|
||||||
|
},
|
||||||
|
|
||||||
|
_bind_events: () => {
|
||||||
|
let obj_btn_save = document.getElementById("btn_save_notification");
|
||||||
|
obj_btn_save.addEventListener("click", () => {
|
||||||
|
NotificationConfig._save_config();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_open_modal: () => {
|
||||||
|
document.getElementById("check_notify_each_image").checked = NotificationConfig.is_notify_each_image;
|
||||||
|
document.getElementById("check_notify_all_done").checked = NotificationConfig.is_notify_all_done;
|
||||||
|
NotificationConfig.obj_modal.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
_load_config: () => {
|
||||||
|
window.api.get_notification_config()
|
||||||
|
.then((obj_config) => {
|
||||||
|
if (obj_config) {
|
||||||
|
NotificationConfig.is_notify_each_image = obj_config.is_notify_each_image || false;
|
||||||
|
NotificationConfig.is_notify_all_done = obj_config.is_notify_all_done !== undefined ? obj_config.is_notify_all_done : true;
|
||||||
|
NotificationConfig._update_badge();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Config non trouvee, valeurs par defaut
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_save_config: () => {
|
||||||
|
NotificationConfig.is_notify_each_image = document.getElementById("check_notify_each_image").checked;
|
||||||
|
NotificationConfig.is_notify_all_done = document.getElementById("check_notify_all_done").checked;
|
||||||
|
|
||||||
|
let obj_config = {
|
||||||
|
is_notify_each_image: NotificationConfig.is_notify_each_image,
|
||||||
|
is_notify_all_done: NotificationConfig.is_notify_all_done,
|
||||||
|
};
|
||||||
|
|
||||||
|
window.api.set_notification_config(obj_config)
|
||||||
|
.then(() => {
|
||||||
|
NotificationConfig._update_badge();
|
||||||
|
NotificationConfig.obj_modal.hide();
|
||||||
|
ConsoleLog.add("Configuration notifications sauvegardee.");
|
||||||
|
})
|
||||||
|
.catch((obj_err) => {
|
||||||
|
ConsoleLog.add("Erreur sauvegarde notifications : " + obj_err.message);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_update_badge: () => {
|
||||||
|
let obj_badge = document.getElementById("btn_notification_config");
|
||||||
|
let is_any_active = NotificationConfig.is_notify_each_image || NotificationConfig.is_notify_all_done;
|
||||||
|
if (is_any_active) {
|
||||||
|
obj_badge.className = "btn btn-sm btn-outline-success";
|
||||||
|
obj_badge.title = "Notifications activees";
|
||||||
|
} else {
|
||||||
|
obj_badge.className = "btn btn-sm btn-outline-secondary";
|
||||||
|
obj_badge.title = "Notifications desactivees";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -40,7 +40,7 @@ const ProgressBar = {
|
|||||||
let obj_frame_label = document.getElementById("label_current_frame");
|
let obj_frame_label = document.getElementById("label_current_frame");
|
||||||
obj_frame_label.textContent = nb_frame !== null && nb_frame !== undefined ? String(nb_frame) : "-";
|
obj_frame_label.textContent = nb_frame !== null && nb_frame !== undefined ? String(nb_frame) : "-";
|
||||||
|
|
||||||
RenderQueue.update_progress(nb_current, obj_data.nb_last_render_ms || 0, obj_data.str_last_image_path || null, obj_data.list_skipped || []);
|
RenderQueue.update_progress(nb_current, obj_data.nb_last_render_ms || 0, obj_data.str_last_image_path || null, obj_data.list_skipped || [], obj_data.list_stopped || [], obj_data.list_skipped_paths || []);
|
||||||
},
|
},
|
||||||
|
|
||||||
reset: () => {
|
reset: () => {
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ const RenderQueue = {
|
|||||||
obj_container.appendChild(obj_fragment);
|
obj_container.appendChild(obj_fragment);
|
||||||
},
|
},
|
||||||
|
|
||||||
update_progress: (nb_current, nb_last_render_ms, str_last_image_path, list_skipped) => {
|
update_progress: (nb_current, nb_last_render_ms, str_last_image_path, list_skipped, list_stopped, list_skipped_paths) => {
|
||||||
if (nb_current > RenderQueue.nb_last_current && nb_last_render_ms > 0) {
|
if (nb_current > RenderQueue.nb_last_current && nb_last_render_ms > 0) {
|
||||||
RenderQueue.nb_total_render_ms += nb_last_render_ms;
|
RenderQueue.nb_total_render_ms += nb_last_render_ms;
|
||||||
RenderQueue.nb_completed_renders++;
|
RenderQueue.nb_completed_renders++;
|
||||||
@@ -126,8 +126,18 @@ const RenderQueue = {
|
|||||||
RenderQueue.list_items[nb_current - 1].str_image_path = str_last_image_path;
|
RenderQueue.list_items[nb_current - 1].str_image_path = str_last_image_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (list_skipped_paths) {
|
||||||
|
for (let obj_sp of list_skipped_paths) {
|
||||||
|
if (obj_sp.nb_index < RenderQueue.list_items.length) {
|
||||||
|
RenderQueue.list_items[obj_sp.nb_index].str_image_path = obj_sp.str_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let nb_i = 0; nb_i < RenderQueue.list_items.length; nb_i++) {
|
for (let nb_i = 0; nb_i < RenderQueue.list_items.length; nb_i++) {
|
||||||
if (list_skipped && list_skipped.indexOf(nb_i) !== -1) {
|
if (list_stopped && list_stopped.indexOf(nb_i) !== -1) {
|
||||||
|
RenderQueue.list_items[nb_i].str_status = "stopped";
|
||||||
|
} else if (list_skipped && list_skipped.indexOf(nb_i) !== -1) {
|
||||||
RenderQueue.list_items[nb_i].str_status = "skipped";
|
RenderQueue.list_items[nb_i].str_status = "skipped";
|
||||||
} else if (nb_i < nb_current) {
|
} else if (nb_i < nb_current) {
|
||||||
RenderQueue.list_items[nb_i].str_status = "done";
|
RenderQueue.list_items[nb_i].str_status = "done";
|
||||||
@@ -143,14 +153,20 @@ const RenderQueue = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_update_statuses: () => {
|
_update_statuses: () => {
|
||||||
|
let obj_rendering_el = null;
|
||||||
|
|
||||||
for (let obj_item of RenderQueue.list_items) {
|
for (let obj_item of RenderQueue.list_items) {
|
||||||
if (!obj_item.obj_dom_el) {
|
if (!obj_item.obj_dom_el) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_needs_click = obj_item.str_status === "done" && obj_item.str_image_path && !obj_item.is_click_bound;
|
let is_needs_click = (obj_item.str_status === "done" || obj_item.str_status === "skipped")
|
||||||
|
&& obj_item.str_image_path && !obj_item.is_click_bound;
|
||||||
|
|
||||||
if (obj_item.str_status === obj_item.str_dom_status && !is_needs_click) {
|
if (obj_item.str_status === obj_item.str_dom_status && !is_needs_click) {
|
||||||
|
if (obj_item.str_status === "rendering") {
|
||||||
|
obj_rendering_el = obj_item.obj_dom_el;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +176,7 @@ const RenderQueue = {
|
|||||||
if (obj_item.str_status === "rendering") {
|
if (obj_item.str_status === "rendering") {
|
||||||
str_icon = "mdi-loading mdi-spin";
|
str_icon = "mdi-loading mdi-spin";
|
||||||
str_color = "text-primary";
|
str_color = "text-primary";
|
||||||
|
obj_rendering_el = obj_item.obj_dom_el;
|
||||||
} else if (obj_item.str_status === "done") {
|
} else if (obj_item.str_status === "done") {
|
||||||
str_icon = "mdi-check-circle";
|
str_icon = "mdi-check-circle";
|
||||||
str_color = "text-success";
|
str_color = "text-success";
|
||||||
@@ -169,6 +186,9 @@ const RenderQueue = {
|
|||||||
} else if (obj_item.str_status === "skipped") {
|
} else if (obj_item.str_status === "skipped") {
|
||||||
str_icon = "mdi-skip-next-circle";
|
str_icon = "mdi-skip-next-circle";
|
||||||
str_color = "text-info";
|
str_color = "text-info";
|
||||||
|
} else if (obj_item.str_status === "stopped") {
|
||||||
|
str_icon = "mdi-stop-circle";
|
||||||
|
str_color = "text-warning";
|
||||||
}
|
}
|
||||||
|
|
||||||
obj_item.obj_dom_icon.className = "mdi " + str_icon + " " + str_color;
|
obj_item.obj_dom_icon.className = "mdi " + str_icon + " " + str_color;
|
||||||
@@ -184,6 +204,20 @@ const RenderQueue = {
|
|||||||
|
|
||||||
obj_item.str_dom_status = obj_item.str_status;
|
obj_item.str_dom_status = obj_item.str_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (obj_rendering_el) {
|
||||||
|
obj_rendering_el.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mark_existing: (list_existing) => {
|
||||||
|
for (let obj_existing of list_existing) {
|
||||||
|
if (obj_existing.nb_index < RenderQueue.list_items.length) {
|
||||||
|
RenderQueue.list_items[obj_existing.nb_index].str_status = "skipped";
|
||||||
|
RenderQueue.list_items[obj_existing.nb_index].str_image_path = obj_existing.str_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RenderQueue._update_statuses();
|
||||||
},
|
},
|
||||||
|
|
||||||
_update_time_display: () => {
|
_update_time_display: () => {
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ const UpdateBanner = {
|
|||||||
obj_btn_close.classList.add("d-none");
|
obj_btn_close.classList.add("d-none");
|
||||||
|
|
||||||
window.api.apply_update(UpdateBanner.str_pending_tag)
|
window.api.apply_update(UpdateBanner.str_pending_tag)
|
||||||
.catch(() => {});
|
.catch((obj_err) => {
|
||||||
|
console.error("UpdateBanner: apply_update echoue :", obj_err);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_show_progress: (str_step, nb_percent) => {
|
_show_progress: (str_step, nb_percent) => {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"str_version": "1.0.0"
|
"str_version": "1.2.0"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user