v1.0.0 — release avec auto-update Gitea
Ajout du systeme de mise a jour automatique : - UpdateManager (main) : verifie les tags Gitea, telecharge et applique les MAJ - UpdateBanner (renderer) : banniere UI avec progression et retry - IPC channels : check-for-updates, apply-update, update-available, update-progress, update-error - Desactivation asar pour permettre le remplacement des sources - version.json comme source de verite pour la version locale Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -67,17 +67,33 @@
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="output_mode" id="radio_output_subfolder" value="subfolder" checked>
|
||||
<label class="form-check-label" for="radio_output_subfolder">
|
||||
Sous-dossier par camera
|
||||
Sous-dossier
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="output_mode" id="radio_output_prefix" value="prefix">
|
||||
<label class="form-check-label" for="radio_output_prefix">
|
||||
Nom camera dans le fichier
|
||||
Prefixe
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" name="output_mode" id="radio_output_both" value="both">
|
||||
<label class="form-check-label" for="radio_output_both">
|
||||
Les deux
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-2 d-flex gap-2 align-items-end">
|
||||
<div>
|
||||
<label class="form-label form-label-sm mb-1">Prefixe frame</label>
|
||||
<input type="text" class="form-control form-control-sm bg-dark text-light border-secondary" id="input_frame_prefix" value="f_" style="max-width: 160px;">
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label form-label-sm mb-1">Padding</label>
|
||||
<input type="number" class="form-control form-control-sm bg-dark text-light border-secondary" id="input_frame_padding" value="5" min="1" max="10" style="max-width: 80px;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
<small id="label_output_example" class="text-light-emphasis">Ex: /sortie/<strong>Camera.001</strong>/frame_00001.png</small>
|
||||
<small id="label_output_example" class="text-light-emphasis">Ex: /sortie/<strong>Camera.001</strong>/f_00001.png</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -244,6 +260,7 @@
|
||||
<script src="scripts/RenderQueue.js"></script>
|
||||
<script src="scripts/PreviewPanel.js"></script>
|
||||
<script src="scripts/ProgressBar.js"></script>
|
||||
<script src="scripts/UpdateBanner.js"></script>
|
||||
<script src="scripts/App.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,6 +9,7 @@ const App = {
|
||||
RenderQueue.init();
|
||||
PreviewPanel.init();
|
||||
ProgressBar.init();
|
||||
UpdateBanner.init();
|
||||
|
||||
App._bind_events();
|
||||
App._bind_render_events();
|
||||
@@ -54,8 +55,14 @@ const App = {
|
||||
|
||||
let obj_radio_subfolder = document.getElementById("radio_output_subfolder");
|
||||
let obj_radio_prefix = document.getElementById("radio_output_prefix");
|
||||
let obj_radio_both = document.getElementById("radio_output_both");
|
||||
let obj_input_frame_prefix = document.getElementById("input_frame_prefix");
|
||||
let obj_input_frame_padding = document.getElementById("input_frame_padding");
|
||||
obj_radio_subfolder.addEventListener("change", () => { App._update_output_example(); });
|
||||
obj_radio_prefix.addEventListener("change", () => { App._update_output_example(); });
|
||||
obj_radio_both.addEventListener("change", () => { App._update_output_example(); });
|
||||
obj_input_frame_prefix.addEventListener("input", () => { App._update_output_example(); });
|
||||
obj_input_frame_padding.addEventListener("input", () => { App._update_output_example(); });
|
||||
},
|
||||
|
||||
_bind_render_events: () => {
|
||||
@@ -73,11 +80,16 @@ const App = {
|
||||
|
||||
_update_output_example: () => {
|
||||
let str_mode = document.querySelector('input[name="output_mode"]:checked').value;
|
||||
let str_prefix = document.getElementById("input_frame_prefix").value || "";
|
||||
let nb_padding = parseInt(document.getElementById("input_frame_padding").value, 10) || 5;
|
||||
let str_padded = String(1).padStart(nb_padding, "0");
|
||||
let obj_label = document.getElementById("label_output_example");
|
||||
if (str_mode === "subfolder") {
|
||||
obj_label.innerHTML = 'Ex: /sortie/<strong>Camera.001</strong>/frame_00001.png';
|
||||
obj_label.innerHTML = 'Ex: /sortie/<strong>Camera.001</strong>/' + str_prefix + str_padded + '.png';
|
||||
} else if (str_mode === "prefix") {
|
||||
obj_label.innerHTML = 'Ex: /sortie/<strong>Camera.001</strong>_' + str_prefix + str_padded + '.png';
|
||||
} else {
|
||||
obj_label.innerHTML = 'Ex: /sortie/<strong>Camera.001</strong>_frame_00001.png';
|
||||
obj_label.innerHTML = 'Ex: /sortie/<strong>Camera.001</strong>/<strong>Camera.001</strong>_' + str_prefix + str_padded + '.png';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -101,16 +113,16 @@ const App = {
|
||||
|
||||
return window.api.get_cameras(str_path);
|
||||
})
|
||||
.then((list_cameras) => {
|
||||
.then((obj_result) => {
|
||||
obj_btn_blend.disabled = false;
|
||||
|
||||
if (!list_cameras) {
|
||||
if (!obj_result) {
|
||||
return;
|
||||
}
|
||||
|
||||
CameraList.set_cameras(list_cameras);
|
||||
CameraList.set_cameras(obj_result.list_cameras, obj_result.obj_scene);
|
||||
CameraConfig.clear();
|
||||
ConsoleLog.add(list_cameras.length + " camera(s) trouvee(s).");
|
||||
ConsoleLog.add(obj_result.list_cameras.length + " camera(s) trouvee(s).");
|
||||
App._update_start_button();
|
||||
})
|
||||
.catch((obj_err) => {
|
||||
@@ -158,11 +170,15 @@ const App = {
|
||||
let str_overwrite_mode = document.querySelector('input[name="overwrite_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_overwrite_mode: str_overwrite_mode,
|
||||
str_frame_prefix: str_frame_prefix,
|
||||
nb_frame_padding: nb_frame_padding,
|
||||
str_output_path: App.str_output_path,
|
||||
list_cameras: list_cameras,
|
||||
};
|
||||
@@ -205,11 +221,15 @@ const App = {
|
||||
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_overwrite_mode = document.querySelector('input[name="overwrite_mode"]:checked').value;
|
||||
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_overwrite_mode: str_overwrite_mode,
|
||||
str_frame_prefix: str_frame_prefix,
|
||||
nb_frame_padding: nb_frame_padding,
|
||||
str_output_path: App.str_output_path,
|
||||
list_cameras: CameraList.list_cameras,
|
||||
};
|
||||
@@ -245,6 +265,8 @@ const App = {
|
||||
|
||||
if (obj_config.str_output_mode === "prefix") {
|
||||
document.getElementById("radio_output_prefix").checked = true;
|
||||
} else if (obj_config.str_output_mode === "both") {
|
||||
document.getElementById("radio_output_both").checked = true;
|
||||
} else {
|
||||
document.getElementById("radio_output_subfolder").checked = true;
|
||||
}
|
||||
@@ -255,6 +277,13 @@ const App = {
|
||||
document.getElementById("radio_overwrite").checked = true;
|
||||
}
|
||||
|
||||
if (obj_config.str_frame_prefix !== undefined) {
|
||||
document.getElementById("input_frame_prefix").value = obj_config.str_frame_prefix;
|
||||
}
|
||||
if (obj_config.nb_frame_padding !== undefined) {
|
||||
document.getElementById("input_frame_padding").value = obj_config.nb_frame_padding;
|
||||
}
|
||||
|
||||
App._update_output_example();
|
||||
|
||||
if (obj_config.list_cameras && obj_config.list_cameras.length > 0) {
|
||||
|
||||
@@ -32,6 +32,10 @@ const CameraConfig = {
|
||||
+ ' <label class="form-label form-label-sm">Frame end</label>'
|
||||
+ ' <input type="number" class="form-control form-control-sm bg-dark text-light border-secondary" id="input_frame_end" value="' + obj_camera.nb_frame_end + '" min="0">'
|
||||
+ " </div>"
|
||||
+ ' <div class="col-6">'
|
||||
+ ' <label class="form-label form-label-sm">Frame step</label>'
|
||||
+ ' <input type="number" class="form-control form-control-sm bg-dark text-light border-secondary" id="input_frame_step" value="' + obj_camera.nb_frame_step + '" min="1">'
|
||||
+ " </div>"
|
||||
+ ' <div class="col-12">'
|
||||
+ ' <label class="form-label form-label-sm">Format</label>'
|
||||
+ ' <select class="form-select form-select-sm bg-dark text-light border-secondary" id="select_format">'
|
||||
@@ -69,8 +73,12 @@ const CameraConfig = {
|
||||
|
||||
obj_cam.nb_resolution_x = parseInt(document.getElementById("input_res_x").value, 10) || 1920;
|
||||
obj_cam.nb_resolution_y = parseInt(document.getElementById("input_res_y").value, 10) || 1080;
|
||||
obj_cam.nb_frame_start = parseInt(document.getElementById("input_frame_start").value, 10) || 1;
|
||||
obj_cam.nb_frame_end = parseInt(document.getElementById("input_frame_end").value, 10) || 250;
|
||||
let nb_parsed_start = parseInt(document.getElementById("input_frame_start").value, 10);
|
||||
obj_cam.nb_frame_start = isNaN(nb_parsed_start) ? 1 : nb_parsed_start;
|
||||
let nb_parsed_end = parseInt(document.getElementById("input_frame_end").value, 10);
|
||||
obj_cam.nb_frame_end = isNaN(nb_parsed_end) ? 250 : nb_parsed_end;
|
||||
let nb_parsed_step = parseInt(document.getElementById("input_frame_step").value, 10);
|
||||
obj_cam.nb_frame_step = isNaN(nb_parsed_step) || nb_parsed_step < 1 ? 1 : nb_parsed_step;
|
||||
obj_cam.str_format = document.getElementById("select_format").value;
|
||||
|
||||
ConsoleLog.add("Config appliquee pour " + obj_cam.str_name);
|
||||
|
||||
@@ -7,17 +7,24 @@ const CameraList = {
|
||||
CameraList.fn_on_select = fn_on_select;
|
||||
},
|
||||
|
||||
set_cameras: (list_names) => {
|
||||
set_cameras: (list_names, obj_scene) => {
|
||||
CameraList.list_cameras = [];
|
||||
|
||||
let nb_res_x = (obj_scene && obj_scene.nb_resolution_x) || 1920;
|
||||
let nb_res_y = (obj_scene && obj_scene.nb_resolution_y) || 1080;
|
||||
let nb_start = obj_scene && obj_scene.nb_frame_start !== undefined ? obj_scene.nb_frame_start : 1;
|
||||
let nb_end = (obj_scene && obj_scene.nb_frame_end) || 250;
|
||||
let nb_step = (obj_scene && obj_scene.nb_frame_step) || 1;
|
||||
|
||||
for (let str_name of list_names) {
|
||||
CameraList.list_cameras.push({
|
||||
str_name: str_name,
|
||||
is_enabled: true,
|
||||
nb_resolution_x: 1920,
|
||||
nb_resolution_y: 1080,
|
||||
nb_frame_start: 1,
|
||||
nb_frame_end: 250,
|
||||
nb_resolution_x: nb_res_x,
|
||||
nb_resolution_y: nb_res_y,
|
||||
nb_frame_start: nb_start,
|
||||
nb_frame_end: nb_end,
|
||||
nb_frame_step: nb_step,
|
||||
str_format: "PNG",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ const ProgressBar = {
|
||||
obj_camera_label.textContent = str_camera;
|
||||
|
||||
let obj_frame_label = document.getElementById("label_current_frame");
|
||||
obj_frame_label.textContent = nb_frame > 0 ? 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 || []);
|
||||
},
|
||||
|
||||
@@ -23,7 +23,8 @@ const RenderQueue = {
|
||||
|
||||
if (str_mode === "camera_by_camera") {
|
||||
for (let obj_cam of list_enabled) {
|
||||
for (let nb_frame = obj_cam.nb_frame_start; nb_frame <= obj_cam.nb_frame_end; nb_frame++) {
|
||||
let nb_step = obj_cam.nb_frame_step || 1;
|
||||
for (let nb_frame = obj_cam.nb_frame_start; nb_frame <= obj_cam.nb_frame_end; nb_frame += nb_step) {
|
||||
RenderQueue.list_items.push({
|
||||
str_camera: obj_cam.str_name,
|
||||
nb_frame: nb_frame,
|
||||
@@ -50,7 +51,8 @@ const RenderQueue = {
|
||||
|
||||
for (let nb_frame = nb_min; nb_frame <= nb_max; nb_frame++) {
|
||||
for (let obj_cam of list_enabled) {
|
||||
if (nb_frame >= obj_cam.nb_frame_start && nb_frame <= obj_cam.nb_frame_end) {
|
||||
let nb_cam_step = obj_cam.nb_frame_step || 1;
|
||||
if (nb_frame >= obj_cam.nb_frame_start && nb_frame <= obj_cam.nb_frame_end && (nb_frame - obj_cam.nb_frame_start) % nb_cam_step === 0) {
|
||||
RenderQueue.list_items.push({
|
||||
str_camera: obj_cam.str_name,
|
||||
nb_frame: nb_frame,
|
||||
|
||||
132
src/renderer/scripts/UpdateBanner.js
Normal file
132
src/renderer/scripts/UpdateBanner.js
Normal file
@@ -0,0 +1,132 @@
|
||||
const UpdateBanner = {
|
||||
obj_banner: null,
|
||||
str_pending_tag: null,
|
||||
|
||||
init: () => {
|
||||
UpdateBanner._create_dom();
|
||||
UpdateBanner._bind_events();
|
||||
},
|
||||
|
||||
_create_dom: () => {
|
||||
let obj_banner = document.createElement("div");
|
||||
obj_banner.id = "update_banner";
|
||||
obj_banner.className = "update-banner d-none";
|
||||
obj_banner.innerHTML =
|
||||
'<div class="d-flex align-items-center justify-content-between px-3 py-2">' +
|
||||
'<span class="update-banner-text">' +
|
||||
'<i class="mdi mdi-download-circle-outline me-1"></i>' +
|
||||
'<span id="update_banner_message">Mise a jour disponible</span>' +
|
||||
'</span>' +
|
||||
'<div class="d-flex align-items-center gap-2">' +
|
||||
'<button id="btn_update_install" class="btn btn-sm btn-light">' +
|
||||
'<i class="mdi mdi-download me-1"></i>Installer' +
|
||||
'</button>' +
|
||||
'<button id="btn_update_close" class="btn btn-sm btn-outline-light border-0">' +
|
||||
'<i class="mdi mdi-close"></i>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
let obj_nav = document.querySelector("nav.navbar");
|
||||
obj_nav.parentNode.insertBefore(obj_banner, obj_nav.nextSibling);
|
||||
UpdateBanner.obj_banner = obj_banner;
|
||||
},
|
||||
|
||||
_bind_events: () => {
|
||||
let obj_btn_install = document.getElementById("btn_update_install");
|
||||
obj_btn_install.addEventListener("click", () => {
|
||||
UpdateBanner._on_install_click();
|
||||
});
|
||||
|
||||
let obj_btn_close = document.getElementById("btn_update_close");
|
||||
obj_btn_close.addEventListener("click", () => {
|
||||
UpdateBanner._hide();
|
||||
});
|
||||
|
||||
window.api.on_update_available((obj_data) => {
|
||||
UpdateBanner.str_pending_tag = obj_data.str_tag_name;
|
||||
UpdateBanner._show("Version " + obj_data.str_version + " disponible");
|
||||
});
|
||||
|
||||
window.api.on_update_progress((obj_data) => {
|
||||
UpdateBanner._show_progress(obj_data.str_step, obj_data.nb_percent);
|
||||
});
|
||||
|
||||
window.api.on_update_error((obj_data) => {
|
||||
UpdateBanner.str_pending_tag = obj_data.str_tag_name || UpdateBanner.str_pending_tag;
|
||||
UpdateBanner._show_error(obj_data.str_message);
|
||||
});
|
||||
},
|
||||
|
||||
_show: (str_message) => {
|
||||
let obj_message = document.getElementById("update_banner_message");
|
||||
obj_message.textContent = str_message;
|
||||
|
||||
let obj_btn_install = document.getElementById("btn_update_install");
|
||||
obj_btn_install.innerHTML = '<i class="mdi mdi-download me-1"></i>Installer';
|
||||
obj_btn_install.disabled = false;
|
||||
obj_btn_install.className = "btn btn-sm btn-light";
|
||||
|
||||
let obj_btn_close = document.getElementById("btn_update_close");
|
||||
obj_btn_close.classList.remove("d-none");
|
||||
|
||||
UpdateBanner.obj_banner.classList.remove("d-none", "update-banner-error");
|
||||
document.body.classList.add("has-update-banner");
|
||||
},
|
||||
|
||||
_hide: () => {
|
||||
UpdateBanner.obj_banner.classList.add("d-none");
|
||||
document.body.classList.remove("has-update-banner");
|
||||
},
|
||||
|
||||
_on_install_click: () => {
|
||||
if (!UpdateBanner.str_pending_tag) {
|
||||
return;
|
||||
}
|
||||
|
||||
let obj_btn_install = document.getElementById("btn_update_install");
|
||||
obj_btn_install.disabled = true;
|
||||
obj_btn_install.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>0%';
|
||||
|
||||
let obj_btn_close = document.getElementById("btn_update_close");
|
||||
obj_btn_close.classList.add("d-none");
|
||||
|
||||
window.api.apply_update(UpdateBanner.str_pending_tag)
|
||||
.catch(() => {});
|
||||
},
|
||||
|
||||
_show_progress: (str_step, nb_percent) => {
|
||||
let obj_btn_install = document.getElementById("btn_update_install");
|
||||
let obj_message = document.getElementById("update_banner_message");
|
||||
|
||||
let str_label = "Mise a jour";
|
||||
if (str_step === "downloading") {
|
||||
str_label = "Telechargement";
|
||||
} else if (str_step === "extracting") {
|
||||
str_label = "Extraction";
|
||||
} else if (str_step === "installing") {
|
||||
str_label = "Installation";
|
||||
} else if (str_step === "restarting") {
|
||||
str_label = "Redemarrage";
|
||||
}
|
||||
|
||||
obj_message.textContent = str_label + "...";
|
||||
obj_btn_install.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>' + nb_percent + "%";
|
||||
obj_btn_install.disabled = true;
|
||||
},
|
||||
|
||||
_show_error: (str_message) => {
|
||||
let obj_message = document.getElementById("update_banner_message");
|
||||
obj_message.textContent = "Erreur : " + str_message;
|
||||
|
||||
UpdateBanner.obj_banner.classList.add("update-banner-error");
|
||||
|
||||
let obj_btn_install = document.getElementById("btn_update_install");
|
||||
obj_btn_install.innerHTML = '<i class="mdi mdi-refresh me-1"></i>Reessayer';
|
||||
obj_btn_install.disabled = false;
|
||||
obj_btn_install.className = "btn btn-sm btn-outline-light";
|
||||
|
||||
let obj_btn_close = document.getElementById("btn_update_close");
|
||||
obj_btn_close.classList.remove("d-none");
|
||||
},
|
||||
};
|
||||
@@ -12,6 +12,29 @@ body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.has-update-banner .container-fluid {
|
||||
height: calc(100vh - 56px - 42px);
|
||||
}
|
||||
|
||||
/* ── Update Banner ─────────────────────────────────────────── */
|
||||
|
||||
.update-banner {
|
||||
background-color: #1a4d2e;
|
||||
border-bottom: 1px solid #2d6b42;
|
||||
color: #d4edda;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.update-banner.update-banner-error {
|
||||
background-color: #4d1a1a;
|
||||
border-bottom-color: #6b2d2d;
|
||||
color: #f5c6cb;
|
||||
}
|
||||
|
||||
.update-banner-text {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.row {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user