/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-server).
*
* Copyright (C) 2019 - 2024 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
import {Controller} from "@hotwired/stimulus";
import {AlertSwal} from "../../helpers/swal";
import {trans} from "../../translator.js";
/**
* Drives the hidden value-calculator to render a preview for every candidate row, then attaches the
* checked ones to their parts via the per-part generate-image endpoint (with a progress bar).
*/
export default class extends Controller {
static targets = ["row", "progress", "progressBar", "attachBtn", "edaBtn",
"batchPitch", "batchDiameter", "batchColor", "batchVoltage", "batchShape", "batchLead", "batchTolerance",
"batchPower", "batchPpm", "batchPackage"];
connect() {
this.tryRenderPreviews(0);
}
/** The value-calculator controller instance (retries briefly, since it may connect after us). */
calcController() {
const el = this.element.querySelector('[data-controller*="alueCalculator"], [data-controller*="alue-calculator"]');
if (!el) {
return null;
}
for (const id of ["pages--valueCalculator", "pages--value-calculator"]) {
const c = this.application.getControllerForElementAndIdentifier(el, id);
if (c && typeof c.generateSvg === "function") {
return c;
}
}
return null;
}
tryRenderPreviews(attempt) {
const calc = this.calcController();
if (!calc) {
if (attempt < 20) {
setTimeout(() => this.tryRenderPreviews(attempt + 1), 50);
}
return;
}
this.rowTargets.forEach((row) => this.renderPreview(row, calc));
}
renderPreview(row, calc) {
//Each row carries its own appearance inputs; shape/lead length are batch-wide.
const colorEl = row.querySelector("[data-row-color]");
const diamEl = row.querySelector("[data-row-diameter]");
const pitchEl = row.querySelector("[data-row-pitch]");
const voltEl = row.querySelector("[data-row-voltage]");
const tolEl = row.querySelector("[data-row-tolerance]");
const powerEl = row.querySelector("[data-row-power]");
const ppmEl = row.querySelector("[data-row-ppm]");
const pkgEl = row.querySelector("[data-row-package]");
const svg = calc.generateSvg(row.dataset.type, parseFloat(row.dataset.value), {
voltage: voltEl && voltEl.value ? parseFloat(voltEl.value) : (row.dataset.voltage ? parseFloat(row.dataset.voltage) : 0),
package: pkgEl && pkgEl.value ? pkgEl.value : (row.dataset.package || null),
power: powerEl && powerEl.value ? parseFloat(powerEl.value) : (row.dataset.power ? parseFloat(row.dataset.power) : 0),
ppm: ppmEl && ppmEl.value ? parseFloat(ppmEl.value) : (row.dataset.ppm ? parseFloat(row.dataset.ppm) : 0),
tolerance: tolEl
? (tolEl.value ? parseFloat(tolEl.value) : null)
: (row.dataset.tolerance ? parseFloat(row.dataset.tolerance) : null),
pitch: pitchEl ? pitchEl.value : (row.dataset.pitch || null),
diameter: diamEl && diamEl.value ? parseFloat(diamEl.value) : (row.dataset.diameter ? parseFloat(row.dataset.diameter) : 0),
subtype: row.dataset.subtype || null,
marking: row.dataset.marking || null,
bodyColor: colorEl ? colorEl.value : null,
shape: this.hasBatchShapeTarget ? this.batchShapeTarget.value : "disc",
leadLength: this.hasBatchLeadTarget ? this.batchLeadTarget.value : "medium",
});
row.dataset.svg = svg;
const cell = row.querySelector("[data-bulk-preview]");
if (cell) {
cell.innerHTML = svg || "";
}
//Clear the calculator's scratch SVG so its leftover copy can't collide (duplicate ids)
//with the copy we just placed in this row's cell — that made the last preview render off.
if (typeof calc.clearScratchSvg === "function") {
calc.clearScratchSvg();
}
}
/** Re-render every preview (used by batch controls and the shape/lead selects). */
regenerate() {
const calc = this.calcController();
if (calc) {
this.rowTargets.forEach((row) => this.renderPreview(row, calc));
}
}
/** Re-render only the row whose per-row appearance input changed. */
regenerateRow(event) {
const calc = this.calcController();
const row = event.target.closest("tr");
if (calc && row) {
this.renderPreview(row, calc);
}
}
/** Copy a batch-bar value into every row's matching input, then re-render. */
applyToAll(selector, value) {
if (value === "" || value === null || value === undefined) {
return;
}
this.rowTargets.forEach((row) => {
const el = row.querySelector(selector);
if (el) {
el.value = value;
}
});
this.regenerate();
}
applyColor() {
this.applyToAll("[data-row-color]", this.hasBatchColorTarget ? this.batchColorTarget.value : "");
}
applyDiameter() {
this.applyToAll("[data-row-diameter]", this.hasBatchDiameterTarget ? this.batchDiameterTarget.value : "");
}
applyPitch() {
this.applyToAll("[data-row-pitch]", this.hasBatchPitchTarget ? this.batchPitchTarget.value : "");
}
applyVoltage() {
this.applyToAll("[data-row-voltage]", this.hasBatchVoltageTarget ? this.batchVoltageTarget.value : "");
}
applyPower() {
this.applyToAll("[data-row-power]", this.hasBatchPowerTarget ? this.batchPowerTarget.value : "");
}
applyPpm() {
this.applyToAll("[data-row-ppm]", this.hasBatchPpmTarget ? this.batchPpmTarget.value : "");
}
applyPackage() {
this.applyToAll("[data-row-package]", this.hasBatchPackageTarget ? this.batchPackageTarget.value : "");
}
/**
* Applies the batch tolerance to every row that offers that option (tolerance choices differ
* between capacitors and resistors). "—" always applies, so it can also clear every row.
*/
applyTolerance() {
if (!this.hasBatchToleranceTarget) {
return;
}
const value = this.batchToleranceTarget.value;
this.rowTargets.forEach((row) => {
const el = row.querySelector("[data-row-tolerance]");
if (el && (value === "" || Array.from(el.options).some((o) => o.value === value))) {
el.value = value;
}
});
this.regenerate();
}
/** A body-colour quick-pick button: set the batch colour input, then apply it to all rows. */
pickColor(event) {
if (this.hasBatchColorTarget) {
this.batchColorTarget.value = event.currentTarget.dataset.color;
}
this.applyColor();
}
/** Returns to the previous page (the parts list the action came from); the link's href is the no-JS fallback. */
goBack(event) {
if (window.history.length > 1) {
event.preventDefault();
window.history.back();
}
}
/** Header checkbox: check/uncheck every row. */
toggleAll(event) {
const checked = event.currentTarget.checked;
this.rowTargets.forEach((row) => {
const cb = row.querySelector("input[type=checkbox]");
if (cb) {
cb.checked = checked;
}
});
}
/** Attaches the generated picture of each checked row to its part. */
attachSelected() {
return this.runBatch(
(row) => {
if (!(row.dataset.svg || "").includes("