diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 98733fb..18441b4 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -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(tasklist:*)", "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)" ] } } diff --git a/.env b/.env new file mode 100644 index 0000000..a5da3a0 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +GITEA_TOKEN=a2e914e25f14b1de8ee5eddea2d18f753db7bbc1 \ No newline at end of file diff --git a/main.js b/main.js index 2f55529..0aa438e 100644 --- a/main.js +++ b/main.js @@ -9,6 +9,30 @@ const PathResolver = require("./src/main/PathResolver.js"); let obj_main_window = 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 = () => { obj_main_window = new BrowserWindow({ @@ -29,6 +53,8 @@ const create_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); obj_main_window.webContents.on("did-finish-load", () => { @@ -81,6 +107,10 @@ ipcMain.handle("start-render", (event, 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", () => { return obj_queue_manager.pause(); }); @@ -214,3 +244,16 @@ ipcMain.handle("select-output-folder", () => { 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 }; +}); diff --git a/package-lock.json b/package-lock.json index 6c24f43..3b3cc1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "multi-render-blender", - "version": "1.0.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "multi-render-blender", - "version": "1.0.0", + "version": "1.2.0", "license": "MIT", "devDependencies": { "electron": "^34.0.0", diff --git a/preload.js b/preload.js index bfc7284..24808fa 100644 --- a/preload.js +++ b/preload.js @@ -13,6 +13,10 @@ contextBridge.exposeInMainWorld("api", { return ipcRenderer.invoke("start-render", obj_config); }, + check_queue: (obj_config) => { + return ipcRenderer.invoke("check-queue", obj_config); + }, + pause_render: () => { return ipcRenderer.invoke("pause-render"); }, @@ -85,6 +89,14 @@ contextBridge.exposeInMainWorld("api", { }); }, + 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: () => { return ipcRenderer.invoke("check-for-updates"); }, diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..e6218e0 --- /dev/null +++ b/release.sh @@ -0,0 +1,153 @@ +#!/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 < 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!" \ No newline at end of file diff --git a/src/main/QueueManager.js b/src/main/QueueManager.js index 25c7b03..10e7849 100644 --- a/src/main/QueueManager.js +++ b/src/main/QueueManager.js @@ -1,6 +1,7 @@ const BlenderProcess = require("./BlenderProcess.js"); const path = require("path"); const fs = require("fs"); +const { Notification } = require("electron"); const STR_STATUS_IDLE = "idle"; const STR_STATUS_RUNNING = "running"; @@ -15,6 +16,11 @@ class QueueManager { this.obj_current_process = null; this.nb_last_render_ms = 0; 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) { @@ -56,10 +62,39 @@ class QueueManager { 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_progress(); 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) { let list_queue = []; let str_blend_path = obj_config.str_blend_file; @@ -159,11 +194,21 @@ class QueueManager { let nb_skip_count = 0; while (this.nb_current_index < this.list_queue.length && this.str_overwrite_mode === "skip") { 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; } - let obj_stats = fs.statSync(obj_check.str_expected_file); - if (obj_stats.size === 0) { + if (nb_size === 0) { this._send_log("Placeholder vide detecte, re-rendu : " + obj_check.str_camera_name + " F" + obj_check.nb_frame); break; } @@ -181,10 +226,33 @@ class QueueManager { this.str_status = STR_STATUS_IDLE; this._send_log("Tous les rendus sont termines !"); 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; } 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"; if (this.str_overwrite_mode === "skip") { @@ -234,6 +302,10 @@ class QueueManager { this._send_event("preview-update", 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._send_progress(); this._process_next(); @@ -260,11 +332,12 @@ class QueueManager { this.nb_last_render_ms = Date.now() - nb_start; 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", { str_camera: obj_item.str_camera_name, nb_frame: obj_item.nb_frame, - str_error: obj_err.str_message, + str_error: str_err_msg, }); this.nb_current_index++; @@ -276,9 +349,14 @@ class QueueManager { _send_progress() { let obj_item = this.list_queue[this.nb_current_index] || {}; let list_skipped = []; + let list_stopped = []; + let list_skipped_paths = []; for (let nb_i = 0; nb_i < this.list_queue.length; nb_i++) { if (this.list_queue[nb_i].str_status === "skipped") { 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", { @@ -290,6 +368,8 @@ class QueueManager { nb_last_render_ms: this.nb_last_render_ms, str_last_image_path: this.str_last_image_path, 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); } } + + _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; diff --git a/src/renderer/index.html b/src/renderer/index.html index aa317d8..783e322 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -172,6 +172,9 @@
+ @@ -265,6 +268,7 @@ + diff --git a/src/renderer/scripts/App.js b/src/renderer/scripts/App.js index 701a1b5..f5ff147 100644 --- a/src/renderer/scripts/App.js +++ b/src/renderer/scripts/App.js @@ -11,6 +11,7 @@ const App = { ProgressBar.init(); UpdateBanner.init(); BlenderPath.init(); + NotificationConfig.init(); App._bind_events(); App._bind_render_events(); @@ -44,6 +45,11 @@ const App = { 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"); obj_btn_save.addEventListener("click", () => { App._save_config(); @@ -218,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: () => { let str_mode = document.querySelector('input[name="render_mode"]:checked').value; let str_output_mode = document.querySelector('input[name="output_mode"]:checked').value; @@ -311,15 +362,18 @@ const App = { let obj_btn_start = document.getElementById("btn_start"); let obj_btn_pause = document.getElementById("btn_pause"); let obj_btn_stop = document.getElementById("btn_stop"); + let obj_btn_check = document.getElementById("btn_check_queue"); if (str_state === "running") { obj_btn_start.disabled = true; obj_btn_pause.disabled = false; obj_btn_stop.disabled = false; + obj_btn_check.disabled = true; } else if (str_state === "paused") { obj_btn_start.disabled = false; obj_btn_pause.disabled = true; obj_btn_stop.disabled = false; + obj_btn_check.disabled = true; } else { App._update_start_button(); obj_btn_pause.disabled = true; @@ -329,10 +383,12 @@ const App = { _update_start_button: () => { let obj_btn_start = document.getElementById("btn_start"); + let obj_btn_check = document.getElementById("btn_check_queue"); let is_ready = App.str_blend_path && App.str_output_path && CameraList.get_enabled_cameras().length > 0; obj_btn_start.disabled = !is_ready; + obj_btn_check.disabled = !is_ready; }, }; diff --git a/src/renderer/scripts/NotificationConfig.js b/src/renderer/scripts/NotificationConfig.js new file mode 100644 index 0000000..60b8ea2 --- /dev/null +++ b/src/renderer/scripts/NotificationConfig.js @@ -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 = ''; + 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 = + ''; + + 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"; + } + }, +}; diff --git a/src/renderer/scripts/ProgressBar.js b/src/renderer/scripts/ProgressBar.js index e857484..f0b31f6 100644 --- a/src/renderer/scripts/ProgressBar.js +++ b/src/renderer/scripts/ProgressBar.js @@ -40,7 +40,7 @@ const ProgressBar = { let obj_frame_label = document.getElementById("label_current_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: () => { diff --git a/src/renderer/scripts/RenderQueue.js b/src/renderer/scripts/RenderQueue.js index 9667598..b3307b0 100644 --- a/src/renderer/scripts/RenderQueue.js +++ b/src/renderer/scripts/RenderQueue.js @@ -115,7 +115,7 @@ const RenderQueue = { 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) { RenderQueue.nb_total_render_ms += nb_last_render_ms; RenderQueue.nb_completed_renders++; @@ -126,8 +126,18 @@ const RenderQueue = { 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++) { - 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"; } else if (nb_i < nb_current) { RenderQueue.list_items[nb_i].str_status = "done"; @@ -143,14 +153,20 @@ const RenderQueue = { }, _update_statuses: () => { + let obj_rendering_el = null; + for (let obj_item of RenderQueue.list_items) { if (!obj_item.obj_dom_el) { 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 === "rendering") { + obj_rendering_el = obj_item.obj_dom_el; + } continue; } @@ -160,6 +176,7 @@ const RenderQueue = { if (obj_item.str_status === "rendering") { str_icon = "mdi-loading mdi-spin"; str_color = "text-primary"; + obj_rendering_el = obj_item.obj_dom_el; } else if (obj_item.str_status === "done") { str_icon = "mdi-check-circle"; str_color = "text-success"; @@ -169,6 +186,9 @@ const RenderQueue = { } else if (obj_item.str_status === "skipped") { str_icon = "mdi-skip-next-circle"; 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; @@ -184,6 +204,20 @@ const RenderQueue = { 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: () => {