Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 0 additions & 91 deletions Makefile

This file was deleted.

79 changes: 79 additions & 0 deletions assets/controllers/elements/assembly_select_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {Controller} from "@hotwired/stimulus";

import "tom-select/dist/css/tom-select.bootstrap5.css";
import '../../css/components/tom-select_extensions.css';
import TomSelect from "tom-select";
import {marked} from "marked";

export default class extends Controller {
_tomSelect;

connect() {

//Check if tomselect is inside an modal and do not attach the dropdown to body in that case (as it breaks the modal)
let dropdownParent = "body";
if (this.element.closest('.modal')) {
dropdownParent = null
}

let settings = {
allowEmptyOption: true,
plugins: ['dropdown_input', this.element.required ? null : 'clear_button'],
searchField: ["name", "description", "category", "footprint", "ipn"],
valueField: "id",
labelField: "name",
dropdownParent: dropdownParent,
preload: "focus",
render: {
item: (data, escape) => {
return '<span>' + (data.image ? "<img style='height: 1.5rem; margin-right: 5px;' ' src='" + data.image + "'/>" : "") + escape(data.name) + '</span>';
},
option: (data, escape) => {
if(data.text) {
return '<span>' + escape(data.text) + '</span>';
}

let tmp = '<div class="row m-0">' +
"<div class='col-2 p-0 d-flex align-items-center' style='max-width: 80px;'>" +
(data.image ? "<img class='typeahead-image' src='" + data.image + "'/>" : "") +
"</div>" +
"<div class='col-10'>" +
'<h6 class="m-0">' + escape(data.name) + '</h6>' +
(data.description ? '<p class="m-0">' + marked.parseInline(data.description) + '</p>' : "") +
(data.category ? '<p class="m-0"><span class="fa-solid fa-tags fa-fw"></span> ' + escape(data.category) : "");

return tmp + '</p>' +
'</div></div>';
}
}
};


if (this.element.dataset.autocomplete || this.element.querySelector('[data-autocomplete]')) {
const autocompleteElement = this.element.dataset.autocomplete ? this.element : this.element.querySelector('[data-autocomplete]');
const base_url = autocompleteElement.dataset.autocomplete;
settings.valueField = "id";
settings.load = (query, callback) => {
const url = base_url.replace('__QUERY__', encodeURIComponent(query));

fetch(url)
.then(response => response.json())
.then(json => {callback(json);})
.catch(() => {
callback()
});
};


const targetElement = this.element instanceof HTMLInputElement || this.element instanceof HTMLSelectElement ? this.element : this.element.querySelector('select, input');
this._tomSelect = new TomSelect(targetElement, settings);
//this._tomSelect.clearOptions();
}
}

disconnect() {
super.disconnect();
//Destroy the TomSelect instance
this._tomSelect.destroy();
}
}
94 changes: 94 additions & 0 deletions assets/controllers/elements/bom_name_sync_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {Controller} from "@hotwired/stimulus";

export default class extends Controller {
static targets = ["part", "assembly", "name"];

connect() {
this.updatePlaceholder();
// Give TomSelect some time to initialize and set values
setTimeout(() => this.updatePlaceholder(), 100);
setTimeout(() => this.updatePlaceholder(), 500);
}

updatePlaceholder() {
const partSelect = this.hasPartTarget ? this.partTarget.querySelector('select, input') : null;
const assemblySelect = this.hasAssemblyTarget ? this.assemblyTarget.querySelector('select, input') : null;
const nameInput = this.hasNameTarget ? this.nameTarget : null;

if (!nameInput) return;

let selectedName = "";

// Helper to get name from tomselect
const getNameFromTS = (el) => {
if (el && el.tomselect) {
const val = el.tomselect.getValue();
if (val) {
const data = el.tomselect.options[val];
if (data && data.name) return data.name;
}
}
// Fallback for raw select
if (el && el.value && el.options && el.selectedIndex >= 0) {
return el.options[el.selectedIndex].text;
}
return "";
};

selectedName = getNameFromTS(partSelect);

if (!selectedName) {
selectedName = getNameFromTS(assemblySelect);
}

if (selectedName) {
nameInput.placeholder = selectedName;
if (nameInput.value === "") {
nameInput.style.opacity = "0.6";
} else {
nameInput.style.opacity = "1";
}
} else {
nameInput.placeholder = nameInput.dataset.originalPlaceholder || "";
nameInput.style.opacity = "1";
}
}

// This method will be called via action when a change occurs
sync(event) {
// Handle mutual exclusion: if a part is selected, clear the assembly (and vice-versa)
// We identify which field was changed by looking at the event target
const changedElement = event.target;
const partSelect = this.hasPartTarget ? this.partTarget.querySelector('select, input') : null;
const assemblySelect = this.hasAssemblyTarget ? this.assemblyTarget.querySelector('select, input') : null;

// If part was changed and has a value, clear assembly
if (partSelect && (changedElement === partSelect || partSelect.contains(changedElement))) {
const val = partSelect.tomselect ? partSelect.tomselect.getValue() : partSelect.value;
if (val && assemblySelect) {
if (assemblySelect.tomselect) {
assemblySelect.tomselect.clear(true); // true to silent event to avoid loops
} else {
assemblySelect.value = "";
}
}
}

// If assembly was changed and has a value, clear part
if (assemblySelect && (changedElement === assemblySelect || assemblySelect.contains(changedElement))) {
const val = assemblySelect.tomselect ? assemblySelect.tomselect.getValue() : assemblySelect.value;
if (val && partSelect) {
if (partSelect.tomselect) {
partSelect.tomselect.clear(true); // true to silent event to avoid loops
} else {
partSelect.value = "";
}
}
}

// Delay slightly to allow TomSelect to update its internal state if needed
setTimeout(() => {
this.updatePlaceholder();
}, 100);
}
}
Loading