Grotti 3.6.0: Lauf-Identität nach Filament-Pause, robustere Queue, Projekte/Aufträge list-first und API-Polish.
This commit is contained in:
1 parent
d1eb87ba25
commit
2b1e36634a
53 files changed
+4254
-636
No files matched your search
+345
-56
@@ -6,11 +6,16 @@
|
||||
<p class="tiny">Arbeitsmappen mit Ordner unter dem Slice-Pfad — G-code und Konstruktionsdaten.</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="seg" id="proj-filter" role="group" aria-label="Filter">
|
||||
<button type="button" class="on" data-filter="aktiv">Aktiv</button>
|
||||
<button type="button" data-filter="fertig">Fertig</button>
|
||||
<button type="button" data-filter="">Alle</button>
|
||||
</div>
|
||||
<button type="button" class="btn-primary" id="proj-new"><svg class="ico"><use href="#i-plus"/></svg>Neues Projekt</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="split">
|
||||
<div class="split edit-split is-idle" id="proj-layout">
|
||||
<section class="card">
|
||||
<div class="card-head lib-head">
|
||||
<svg class="ico head-ico"><use href="#i-folder"/></svg>
|
||||
@@ -19,16 +24,21 @@
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>Name</th><th>Status</th><th>Ordner</th><th class="num">Dateien</th></tr></thead>
|
||||
<thead><tr><th>Name</th><th>Status</th><th>Ordner</th><th class="num">Dateien</th><th class="num">Kosten</th></tr></thead>
|
||||
<tbody id="proj-rows"></tbody>
|
||||
</table>
|
||||
<div class="pager" id="proj-pager" hidden></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card" id="proj-panel">
|
||||
<h2 id="proj-title">Neues Projekt</h2>
|
||||
<form id="proj-form" novalidate>
|
||||
<section class="card edit-panel" id="proj-panel" hidden>
|
||||
<div class="mat-form-head">
|
||||
<h2 id="proj-title">Neues Projekt</h2>
|
||||
<button type="button" class="ghost tiny-btn" id="proj-edit-btn" hidden>Bearbeiten</button>
|
||||
<button type="button" class="icon-btn" id="proj-close" title="Schließen" aria-label="Schließen"></button>
|
||||
</div>
|
||||
|
||||
<form id="proj-form" novalidate hidden>
|
||||
<input id="proj-id" type="hidden">
|
||||
<div class="field"><label for="proj-name">Name</label><input id="proj-name" required autocomplete="off"></div>
|
||||
<div class="grid-2">
|
||||
@@ -36,9 +46,9 @@
|
||||
<label for="proj-status">Status</label>
|
||||
<select id="proj-status">
|
||||
<option value="idee">Idee</option>
|
||||
<option value="bereit">bereit</option>
|
||||
<option value="druckt">druckt</option>
|
||||
<option value="fertig">fertig</option>
|
||||
<option value="bereit">Bereit</option>
|
||||
<option value="druckt">Druckt</option>
|
||||
<option value="fertig">Fertig</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
@@ -50,23 +60,46 @@
|
||||
<div class="field"><label for="proj-notes">Notiz</label><textarea id="proj-notes" rows="3"></textarea></div>
|
||||
<div class="actions">
|
||||
<button type="submit" class="btn-primary" id="proj-save">Speichern</button>
|
||||
<a class="ghost" id="proj-files-link" href="/dateien" hidden>Dateien</a>
|
||||
<button type="button" class="ghost" id="proj-cancel-edit" hidden>Abbrechen</button>
|
||||
<button type="button" class="warn" id="proj-delete" hidden><svg class="ico"><use href="#i-trash"/></svg>Löschen</button>
|
||||
</div>
|
||||
</form>
|
||||
<p id="proj-note" class="tiny"></p>
|
||||
|
||||
<div id="proj-detail" hidden>
|
||||
<h3 class="section-gap">Druckdateien</h3>
|
||||
<div id="proj-gcodes" class="proj-file-list"></div>
|
||||
|
||||
<h3 class="section-gap">Weitere Dateien</h3>
|
||||
<p class="tiny">STEP, STL, PDF, SolidWorks usw. im Projektordner — nicht in der Druck-Bibliothek.</p>
|
||||
<div class="actions" style="margin-bottom:0.5rem">
|
||||
<label class="button ghost" for="proj-asset-file"><svg class="ico"><use href="#i-upload"/></svg>Hochladen</label>
|
||||
<input id="proj-asset-file" type="file" multiple hidden>
|
||||
<a class="ghost" id="proj-files-link" href="/dateien" hidden>Dateien</a>
|
||||
</div>
|
||||
<div id="proj-stats" class="proj-stats"></div>
|
||||
<p id="proj-suggest" class="tiny" hidden></p>
|
||||
<p id="proj-meta" class="tiny"></p>
|
||||
|
||||
<div class="seg" id="proj-tabs" role="tablist" style="margin:0.75rem 0">
|
||||
<button type="button" class="on" data-tab="files">Druckdateien</button>
|
||||
<button type="button" data-tab="runs">Läufe</button>
|
||||
<button type="button" data-tab="assets">Weitere Dateien</button>
|
||||
</div>
|
||||
|
||||
<div id="proj-tab-files">
|
||||
<div class="actions" style="margin-bottom:0.5rem">
|
||||
<button type="button" class="btn-primary" id="proj-enqueue-all" hidden>Alle einreihen</button>
|
||||
</div>
|
||||
<div id="proj-gcodes" class="proj-file-list"></div>
|
||||
</div>
|
||||
|
||||
<div id="proj-tab-runs" hidden>
|
||||
<div id="proj-runs" class="proj-file-list"></div>
|
||||
<div class="pager" id="proj-runs-pager" hidden></div>
|
||||
</div>
|
||||
|
||||
<div id="proj-tab-assets" hidden>
|
||||
<p class="tiny">STEP, STL, PDF, SolidWorks usw. im Projektordner — nicht in der Druck-Bibliothek.</p>
|
||||
<div class="actions" style="margin-bottom:0.5rem">
|
||||
<label class="button ghost" for="proj-asset-file"><svg class="ico"><use href="#i-upload"/></svg>Hochladen</label>
|
||||
<input id="proj-asset-file" type="file" multiple hidden>
|
||||
</div>
|
||||
<div id="proj-assets" class="proj-file-list"></div>
|
||||
</div>
|
||||
<div id="proj-assets" class="proj-file-list"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -74,51 +107,117 @@
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
const STATUS_LABEL = { idee: "Idee", bereit: "bereit", druckt: "druckt", fertig: "fertig" };
|
||||
const STATUS_LABEL = { idee: "Idee", bereit: "Bereit", druckt: "Druckt", fertig: "Fertig" };
|
||||
let projects = [];
|
||||
let selectedId = null;
|
||||
let listFilter = "aktiv";
|
||||
let detailTab = "files";
|
||||
let runsPage = 0;
|
||||
let runsTotal = 0;
|
||||
const RUNS_PAGE = 20;
|
||||
/** idle | create | view | edit */
|
||||
let panelMode = "idle";
|
||||
let folderTouched = false;
|
||||
let projPage = 1;
|
||||
|
||||
function fillBlank() {
|
||||
document.getElementById("proj-close").innerHTML = icon("x");
|
||||
|
||||
function openPanel() {
|
||||
document.getElementById("proj-panel").hidden = false;
|
||||
document.getElementById("proj-layout").classList.remove("is-idle");
|
||||
document.getElementById("proj-panel").scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||
}
|
||||
|
||||
function closePanel() {
|
||||
panelMode = "idle";
|
||||
selectedId = null;
|
||||
document.getElementById("proj-id").value = "";
|
||||
document.getElementById("proj-name").value = "";
|
||||
document.getElementById("proj-status").value = "idee";
|
||||
document.getElementById("proj-folder").value = "";
|
||||
document.getElementById("proj-notes").value = "";
|
||||
document.getElementById("proj-title").textContent = "Neues Projekt";
|
||||
document.getElementById("proj-panel").hidden = true;
|
||||
document.getElementById("proj-layout").classList.add("is-idle");
|
||||
document.getElementById("proj-form").hidden = true;
|
||||
document.getElementById("proj-detail").hidden = true;
|
||||
document.getElementById("proj-edit-btn").hidden = true;
|
||||
document.getElementById("proj-cancel-edit").hidden = true;
|
||||
document.getElementById("proj-delete").hidden = true;
|
||||
document.getElementById("proj-files-link").hidden = true;
|
||||
document.getElementById("proj-detail").hidden = true;
|
||||
document.getElementById("proj-note").textContent = "";
|
||||
document.getElementById("proj-enqueue-all").hidden = true;
|
||||
history.replaceState(null, "", location.pathname);
|
||||
renderList();
|
||||
}
|
||||
|
||||
function fill(project) {
|
||||
function setFormValues(project) {
|
||||
document.getElementById("proj-id").value = project ? project.id : "";
|
||||
document.getElementById("proj-name").value = project ? (project.name || "") : "";
|
||||
document.getElementById("proj-status").value = project ? (project.status || "idee") : "idee";
|
||||
document.getElementById("proj-folder").value = project ? (project.folder || "") : "";
|
||||
document.getElementById("proj-notes").value = project ? (project.notes || "") : "";
|
||||
}
|
||||
|
||||
function showCreate() {
|
||||
panelMode = "create";
|
||||
selectedId = null;
|
||||
folderTouched = false;
|
||||
setFormValues(null);
|
||||
document.getElementById("proj-title").textContent = "Neues Projekt";
|
||||
document.getElementById("proj-form").hidden = false;
|
||||
document.getElementById("proj-detail").hidden = true;
|
||||
document.getElementById("proj-edit-btn").hidden = true;
|
||||
document.getElementById("proj-cancel-edit").hidden = true;
|
||||
document.getElementById("proj-delete").hidden = true;
|
||||
document.getElementById("proj-files-link").hidden = true;
|
||||
document.getElementById("proj-note").textContent = "";
|
||||
document.getElementById("proj-enqueue-all").hidden = true;
|
||||
openPanel();
|
||||
renderList();
|
||||
document.getElementById("proj-name").focus();
|
||||
}
|
||||
|
||||
function showView(project) {
|
||||
panelMode = "view";
|
||||
selectedId = project.id;
|
||||
document.getElementById("proj-id").value = project.id;
|
||||
document.getElementById("proj-name").value = project.name || "";
|
||||
document.getElementById("proj-status").value = project.status || "idee";
|
||||
document.getElementById("proj-folder").value = project.folder || "";
|
||||
document.getElementById("proj-notes").value = project.notes || "";
|
||||
setFormValues(project);
|
||||
document.getElementById("proj-title").textContent = project.name || "Projekt";
|
||||
document.getElementById("proj-delete").hidden = false;
|
||||
document.getElementById("proj-form").hidden = true;
|
||||
document.getElementById("proj-detail").hidden = false;
|
||||
document.getElementById("proj-edit-btn").hidden = false;
|
||||
document.getElementById("proj-cancel-edit").hidden = true;
|
||||
document.getElementById("proj-delete").hidden = true;
|
||||
const link = document.getElementById("proj-files-link");
|
||||
link.hidden = false;
|
||||
link.href = "/dateien";
|
||||
document.getElementById("proj-detail").hidden = false;
|
||||
link.href = "/dateien?project=" + encodeURIComponent(project.id);
|
||||
document.getElementById("proj-note").textContent = "";
|
||||
openPanel();
|
||||
renderList();
|
||||
renderDetail(project);
|
||||
}
|
||||
|
||||
let projPage = 1;
|
||||
function showEdit(project) {
|
||||
panelMode = "edit";
|
||||
selectedId = project.id;
|
||||
folderTouched = true;
|
||||
setFormValues(project);
|
||||
document.getElementById("proj-title").textContent = "Bearbeiten";
|
||||
document.getElementById("proj-form").hidden = false;
|
||||
document.getElementById("proj-detail").hidden = true;
|
||||
document.getElementById("proj-edit-btn").hidden = true;
|
||||
document.getElementById("proj-cancel-edit").hidden = false;
|
||||
document.getElementById("proj-delete").hidden = false;
|
||||
document.getElementById("proj-files-link").hidden = true;
|
||||
openPanel();
|
||||
renderList();
|
||||
document.getElementById("proj-name").focus();
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
const body = document.getElementById("proj-rows");
|
||||
const pager = document.getElementById("proj-pager");
|
||||
document.getElementById("proj-count").textContent = String(projects.length);
|
||||
if (!projects.length) {
|
||||
body.innerHTML = '<tr><td class="empty" colspan="4"><div class="empty-state">' + icon("folder") + "<p>Noch kein Projekt.</p></div></td></tr>";
|
||||
body.innerHTML = '<tr><td class="empty" colspan="5"><div class="empty-state">' + icon("folder")
|
||||
+ "<p>Noch kein Projekt.</p>"
|
||||
+ '<p><button type="button" class="btn-primary" id="proj-empty-new">Erstes Projekt anlegen</button></p>'
|
||||
+ "</div></td></tr>";
|
||||
document.getElementById("proj-empty-new")?.addEventListener("click", showCreate);
|
||||
renderPager(pager, null, () => {});
|
||||
return;
|
||||
}
|
||||
@@ -128,12 +227,16 @@ function renderList() {
|
||||
paged.items.forEach((project) => {
|
||||
const tr = document.createElement("tr");
|
||||
tr.className = "pick" + (String(project.id) === String(selectedId) ? " on" : "");
|
||||
tr.innerHTML = "<td></td><td></td><td></td><td class=\"num\"></td>";
|
||||
tr.innerHTML = "<td></td><td></td><td class=\"cell-path\"></td><td class=\"num\"></td><td class=\"num\"></td>";
|
||||
tr.children[0].textContent = project.name;
|
||||
tr.children[1].textContent = STATUS_LABEL[project.status] || project.status || "";
|
||||
let status = STATUS_LABEL[project.status] || project.status || "";
|
||||
if ((project.stats || {}).active_count > 0) status = STATUS_LABEL.druckt;
|
||||
tr.children[1].textContent = status;
|
||||
tr.children[2].textContent = project.folder || "—";
|
||||
tr.children[2].title = project.folder || "";
|
||||
tr.children[3].textContent = String(project.file_count ?? (project.files || []).length);
|
||||
const cost = (project.stats || {}).cost;
|
||||
tr.children[4].textContent = cost ? euro(cost) : "—";
|
||||
tr.addEventListener("click", () => openProject(project.id));
|
||||
body.appendChild(tr);
|
||||
});
|
||||
@@ -143,20 +246,122 @@ function renderList() {
|
||||
});
|
||||
}
|
||||
|
||||
function setTab(tab) {
|
||||
detailTab = tab;
|
||||
document.querySelectorAll("#proj-tabs [data-tab]").forEach((btn) => {
|
||||
btn.classList.toggle("on", btn.dataset.tab === tab);
|
||||
});
|
||||
document.getElementById("proj-tab-files").hidden = tab !== "files";
|
||||
document.getElementById("proj-tab-runs").hidden = tab !== "runs";
|
||||
document.getElementById("proj-tab-assets").hidden = tab !== "assets";
|
||||
if (tab === "runs" && selectedId) loadRuns(selectedId, 0);
|
||||
if (tab === "assets" && selectedId) loadAssets(selectedId);
|
||||
}
|
||||
|
||||
function renderDetail(project) {
|
||||
const gcodes = document.getElementById("proj-gcodes");
|
||||
const stats = project.stats || {};
|
||||
const statsEl = document.getElementById("proj-stats");
|
||||
const chips = [
|
||||
[stats.run_count || 0, "Läufe"],
|
||||
[hours(stats.duration_s) || "0 min", "Zeit"],
|
||||
[grams(stats.weight_g || 0), "Filament"],
|
||||
[euro(stats.cost || 0), "Kosten"],
|
||||
];
|
||||
if (stats.files_total) {
|
||||
chips.push([stats.files_ok + "/" + stats.files_total, "Dateien ok"]);
|
||||
}
|
||||
statsEl.innerHTML = chips.map(([v, l]) =>
|
||||
`<div class="proj-stat"><strong>${escapeHtml(String(v))}</strong><span class="tiny">${escapeHtml(l)}</span></div>`
|
||||
).join("");
|
||||
|
||||
const meta = [];
|
||||
if (project.folder) meta.push(project.folder);
|
||||
if (project.created_at) meta.push("Angelegt " + formatDateTime(project.created_at));
|
||||
if (stats.last_run) {
|
||||
meta.push("Zuletzt " + formatDateTime(stats.last_run.started_at) + " · " + (stats.last_run.filename || ""));
|
||||
}
|
||||
document.getElementById("proj-meta").textContent = meta.join(" · ");
|
||||
|
||||
const suggest = document.getElementById("proj-suggest");
|
||||
if (stats.suggest_fertig) {
|
||||
suggest.hidden = false;
|
||||
suggest.innerHTML = 'Alle Dateien mindestens einmal ok gedruckt. <button type="button" class="ghost" id="proj-mark-fertig">Als fertig markieren?</button>';
|
||||
document.getElementById("proj-mark-fertig")?.addEventListener("click", async () => {
|
||||
try {
|
||||
await api("/projekte/" + project.id, { method: "PATCH", json: { status: "fertig" } });
|
||||
toast("Projekt als fertig markiert.", "good");
|
||||
await load();
|
||||
await openProject(project.id);
|
||||
} catch (error) {
|
||||
showNote("proj-note", error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
suggest.hidden = true;
|
||||
suggest.textContent = "";
|
||||
}
|
||||
|
||||
const files = project.files || [];
|
||||
document.getElementById("proj-enqueue-all").hidden = !files.length;
|
||||
const gcodes = document.getElementById("proj-gcodes");
|
||||
if (!files.length) {
|
||||
gcodes.innerHTML = '<p class="tiny">Keine G-code/3MF-Dateien verknüpft. Dateien in den Projektordner legen oder unter Dateien verschieben.</p>';
|
||||
} else {
|
||||
gcodes.innerHTML = files.map((file) => {
|
||||
const mats = (file.filaments || []).map((f) => f.type || f.material_type || f.name || "").filter(Boolean);
|
||||
const mat = [...new Set(mats)].join(" / ");
|
||||
const meta = [mat, file.weight_g ? Math.round(file.weight_g) + " g" : ""].filter(Boolean).join(" · ");
|
||||
return `<a class="proj-file" href="/dateien">${escapeHtml(file.name)}<span class="tiny">${escapeHtml(meta)}</span></a>`;
|
||||
const metaBits = [
|
||||
mat,
|
||||
file.weight_g ? Math.round(file.weight_g) + " g" : "",
|
||||
file.printed ? file.printed + "× gedruckt" : "noch nie",
|
||||
file.queued ? file.queued + " in Queue" : "",
|
||||
file.ok_once ? "ok" : "",
|
||||
].filter(Boolean).join(" · ");
|
||||
const href = "/dateien?file=" + encodeURIComponent(file.id) + "&project=" + encodeURIComponent(project.id);
|
||||
return `<div class="proj-file-row">
|
||||
<a class="proj-file" href="${escapeHtml(href)}">${escapeHtml(file.name)}<span class="tiny">${escapeHtml(metaBits)}</span></a>
|
||||
<button type="button" class="ghost" data-enqueue="${file.id}">Einreihen</button>
|
||||
</div>`;
|
||||
}).join("");
|
||||
}
|
||||
loadAssets(project.id);
|
||||
setTab(detailTab);
|
||||
if (detailTab === "assets") loadAssets(project.id);
|
||||
if (detailTab === "runs") loadRuns(project.id, runsPage);
|
||||
}
|
||||
|
||||
async function loadRuns(projectId, offset) {
|
||||
const box = document.getElementById("proj-runs");
|
||||
const pager = document.getElementById("proj-runs-pager");
|
||||
runsPage = Math.max(0, offset || 0);
|
||||
try {
|
||||
const resp = await fetch(withToken("/api/v1/projekte/" + projectId + "/laeufe?limit=" + RUNS_PAGE + "&offset=" + runsPage), {
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
if (!resp.ok) throw new Error("Läufe laden fehlgeschlagen.");
|
||||
const rows = await resp.json();
|
||||
runsTotal = Number(resp.headers.get("X-Total-Count") || rows.length);
|
||||
if (!rows.length) {
|
||||
box.innerHTML = '<p class="tiny">Noch keine Läufe.</p>';
|
||||
pager.hidden = true;
|
||||
return;
|
||||
}
|
||||
box.innerHTML = rows.map((run) => {
|
||||
const state = (window.RUN_STATES && RUN_STATES[run.state]) || run.state || "";
|
||||
const cost = Number(run.filament_cost || 0) + Number(run.energy_cost || 0);
|
||||
const meta = [state, hours(run.duration_s), cost ? euro(cost) : "", formatDateTime(run.started_at)].filter(Boolean).join(" · ");
|
||||
return `<a class="proj-file" href="/protokoll/${run.id}">${escapeHtml(run.filename || "Lauf #" + run.id)}<span class="tiny">${escapeHtml(meta)}</span></a>`;
|
||||
}).join("");
|
||||
const pages = Math.max(1, Math.ceil(runsTotal / RUNS_PAGE));
|
||||
const page = Math.floor(runsPage / RUNS_PAGE) + 1;
|
||||
renderPager(pager, {
|
||||
items: rows, page, pages, total: runsTotal, pageSize: RUNS_PAGE,
|
||||
from: runsTotal ? runsPage + 1 : 0,
|
||||
to: Math.min(runsPage + rows.length, runsTotal),
|
||||
}, (p) => loadRuns(projectId, (p - 1) * RUNS_PAGE));
|
||||
} catch (error) {
|
||||
box.innerHTML = '<p class="tiny">' + escapeHtml(error.message || String(error)) + "</p>";
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAssets(projectId) {
|
||||
@@ -187,32 +392,80 @@ function sizeText(bytes) {
|
||||
return (n / (1024 * 1024)).toFixed(1).replace(".", ",") + " MB";
|
||||
}
|
||||
|
||||
async function enqueueFiles(projectId, fileIds) {
|
||||
const payload = { confirm: true };
|
||||
if (fileIds && fileIds.length) payload.file_ids = fileIds;
|
||||
const out = await api("/projekte/" + projectId + "/einreihen", { method: "POST", json: payload });
|
||||
toast((out.count || 0) + " in die Warteschlange.", "good");
|
||||
await openProject(projectId);
|
||||
}
|
||||
|
||||
async function openProject(id) {
|
||||
try {
|
||||
const row = await api("/projekte/" + id);
|
||||
fill(row);
|
||||
showView(row);
|
||||
history.replaceState(null, "", "#" + id);
|
||||
} catch (error) {
|
||||
showNote("proj-note", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
projects = await api("/projekte");
|
||||
const q = listFilter ? ("?status=" + encodeURIComponent(listFilter)) : "";
|
||||
projects = await api("/projekte" + q);
|
||||
renderList();
|
||||
if (selectedId) {
|
||||
if (selectedId && (panelMode === "view" || panelMode === "edit")) {
|
||||
const still = projects.find((p) => String(p.id) === String(selectedId));
|
||||
if (still) await openProject(selectedId);
|
||||
else fillBlank();
|
||||
if (still) {
|
||||
if (panelMode === "edit") {
|
||||
const row = await api("/projekte/" + selectedId);
|
||||
showEdit(row);
|
||||
} else {
|
||||
await openProject(selectedId);
|
||||
}
|
||||
} else {
|
||||
closePanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let folderTouched = false;
|
||||
document.getElementById("proj-filter").addEventListener("click", (event) => {
|
||||
const btn = event.target.closest("[data-filter]");
|
||||
if (!btn) return;
|
||||
listFilter = btn.dataset.filter || "";
|
||||
document.querySelectorAll("#proj-filter [data-filter]").forEach((b) => b.classList.toggle("on", b === btn));
|
||||
projPage = 1;
|
||||
load().catch((error) => showNote("proj-note", error));
|
||||
});
|
||||
|
||||
document.getElementById("proj-tabs").addEventListener("click", (event) => {
|
||||
const btn = event.target.closest("[data-tab]");
|
||||
if (!btn) return;
|
||||
setTab(btn.dataset.tab);
|
||||
});
|
||||
|
||||
document.getElementById("proj-new").addEventListener("click", () => {
|
||||
folderTouched = false;
|
||||
fillBlank();
|
||||
document.getElementById("proj-name").focus();
|
||||
history.replaceState(null, "", location.pathname);
|
||||
showCreate();
|
||||
});
|
||||
document.getElementById("proj-close").addEventListener("click", closePanel);
|
||||
document.getElementById("proj-edit-btn").addEventListener("click", async () => {
|
||||
if (!selectedId) return;
|
||||
try {
|
||||
const row = await api("/projekte/" + selectedId);
|
||||
showEdit(row);
|
||||
} catch (error) {
|
||||
showNote("proj-note", error);
|
||||
}
|
||||
});
|
||||
document.getElementById("proj-cancel-edit").addEventListener("click", async () => {
|
||||
if (!selectedId) {
|
||||
closePanel();
|
||||
return;
|
||||
}
|
||||
await openProject(selectedId);
|
||||
});
|
||||
|
||||
document.getElementById("proj-folder").addEventListener("input", () => {
|
||||
folderTouched = true;
|
||||
});
|
||||
@@ -278,7 +531,7 @@ document.getElementById("proj-delete").addEventListener("click", () => {
|
||||
try {
|
||||
await api("/projekte/" + id + "?force=1", { method: "DELETE" });
|
||||
toast("Projekt gelöscht.");
|
||||
fillBlank();
|
||||
closePanel();
|
||||
await load();
|
||||
} catch (error) {
|
||||
showNote("proj-note", error);
|
||||
@@ -286,8 +539,36 @@ document.getElementById("proj-delete").addEventListener("click", () => {
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("proj-enqueue-all").addEventListener("click", () => {
|
||||
const id = selectedId;
|
||||
if (!id) return;
|
||||
armButton(document.getElementById("proj-enqueue-all"), "Alle einreihen?", async () => {
|
||||
try {
|
||||
await enqueueFiles(id, null);
|
||||
} catch (error) {
|
||||
showNote("proj-note", error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("proj-gcodes").addEventListener("click", (event) => {
|
||||
const btn = event.target.closest("[data-enqueue]");
|
||||
if (!btn) return;
|
||||
event.preventDefault();
|
||||
const id = selectedId;
|
||||
const fileId = Number(btn.dataset.enqueue);
|
||||
if (!id || !fileId) return;
|
||||
armButton(btn, "einreihen?", async () => {
|
||||
try {
|
||||
await enqueueFiles(id, [fileId]);
|
||||
} catch (error) {
|
||||
showNote("proj-note", error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("proj-asset-file").addEventListener("change", async (event) => {
|
||||
const id = document.getElementById("proj-id").value;
|
||||
const id = selectedId;
|
||||
const files = [...(event.target.files || [])];
|
||||
event.target.value = "";
|
||||
if (!id || !files.length) return;
|
||||
@@ -307,7 +588,7 @@ document.getElementById("proj-asset-file").addEventListener("change", async (eve
|
||||
document.getElementById("proj-assets").addEventListener("click", (event) => {
|
||||
const btn = event.target.closest("[data-asset-del]");
|
||||
if (!btn) return;
|
||||
const id = document.getElementById("proj-id").value;
|
||||
const id = selectedId;
|
||||
const rel = btn.dataset.assetDel;
|
||||
if (!id || !rel) return;
|
||||
armButton(btn, "löschen?", async () => {
|
||||
@@ -317,6 +598,14 @@ document.getElementById("proj-assets").addEventListener("click", (event) => {
|
||||
});
|
||||
});
|
||||
|
||||
load().catch((error) => showNote("proj-note", error));
|
||||
(async () => {
|
||||
try {
|
||||
await load();
|
||||
const hash = (location.hash || "").replace(/^#/, "");
|
||||
if (hash && /^\d+$/.test(hash)) await openProject(Number(hash));
|
||||
} catch (error) {
|
||||
showNote("proj-note", error);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user