Datasource-spezifische Suche für Projects/Assemblies sowie Parts umsetzen

This commit is contained in:
Marcel Diegelmann 2026-04-01 16:08:54 +02:00
parent dde91ff1c5
commit ca6254cc53
39 changed files with 4695 additions and 494 deletions

View file

@ -120,6 +120,9 @@ export default class extends Controller {
this._autocomplete = autocomplete({
container: this.element,
initialState: {
query: this.element.dataset.initialQuery || that.inputTarget.value || ""
},
//Place the panel in the navbar, if the element is in navbar mode
panelContainer: navbar_mode ? document.getElementById("navbar-search-form") : document.body,
panelPlacement: this.element.dataset.panelPlacement,
@ -162,7 +165,10 @@ export default class extends Controller {
}
input.value = state.query;
input.form.requestSubmit();
if (input.form) {
input.form.requestSubmit();
}
},
getSources({ query }) {

View file

@ -0,0 +1,76 @@
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["datasource", "partOptions", "assemblyOptions", "projectOptions", "divider"];
static values = {
isSearchList: Boolean
};
connect() {
// Delay update slightly to ensure all child controllers are connected and DOM is ready
setTimeout(() => {
this.updateVisibility();
}, 1000);
}
onDatasourceChange() {
this.updateVisibility();
}
updateVisibility() {
if (!this.hasDatasourceTarget) return;
const datasource = this.datasourceTarget.value;
const isSearchList = this.isSearchListValue;
const isPart = (datasource === "parts");
const isAssembly = (datasource === "assemblies");
const isProject = (datasource === "projects");
if (this.hasPartOptionsTarget) {
this.toggleOptions(this.partOptionsTarget, isPart, isSearchList);
}
if (this.hasAssemblyOptionsTarget) {
this.toggleOptions(this.assemblyOptionsTarget, isAssembly, isSearchList);
}
if (this.hasProjectOptionsTarget) {
this.toggleOptions(this.projectOptionsTarget, isProject, isSearchList);
}
if (this.hasDividerTarget) {
this.dividerTarget.classList.toggle("d-none", !isPart && !isAssembly && !isProject);
}
}
toggleOptions(container, show, isSearchList) {
const wasHidden = container.classList.contains("d-none");
container.classList.toggle("d-none", !show);
const checkboxes = container.querySelectorAll('input[type="checkbox"]');
if (!show) {
// Deselect checkboxes if not in correct mode
checkboxes.forEach(checkbox => {
// Store current state to restore it later if the user switches back
if (checkbox.checked) {
checkbox.dataset.previousState = "true";
checkbox.checked = false;
// Trigger a change event to update sessionStorage via the sessionStorage_checkbox controller
// We use a CustomEvent to pass the skipStorage flag
checkbox.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { skipStorage: true } }));
}
});
} else if (wasHidden) {
// Restore state when switching back
checkboxes.forEach(checkbox => {
// Restore state if NOT on search list
// On search list, we don't restore to avoid overwriting Twig's checked state
if (!isSearchList && checkbox.dataset.previousState === "true") {
checkbox.checked = true;
checkbox.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { skipStorage: true } }));
}
delete checkbox.dataset.previousState;
});
}
}
}

View file

@ -0,0 +1,81 @@
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2023 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 <https://www.gnu.org/licenses/>.
*/
import {Controller} from "@hotwired/stimulus";
export default class extends Controller
{
static values = {
id: String,
isSearchList: Boolean
}
connect() {
if (this.isSearchListValue) {
// If we are on the search list, we want to update the localStorage with the current (server-side) state
// to ensure consistency.
this.saveState();
} else {
// Otherwise, we load the state from localStorage.
this.loadState();
}
this.element.addEventListener('change', (event) => {
// Don't save state if we are currently being toggled by the search_options controller
// to avoid saving "unchecked" states when options are hidden.
// CustomEvent's detail property contains the data we passed.
if (event instanceof CustomEvent && event.detail && event.detail.skipStorage) {
return;
}
this.saveState()
});
}
loadState() {
let storageKey = this.getStorageKey();
let value = localStorage.getItem(storageKey);
if (value === null) {
return;
}
if (value === 'true') {
this.element.checked = true
}
if (value === 'false') {
this.element.checked = false
}
}
saveState() {
let storageKey = this.getStorageKey();
if (this.element.checked) {
localStorage.setItem(storageKey, 'true');
} else {
localStorage.setItem(storageKey, 'false');
}
}
getStorageKey() {
if (this.hasIdValue) {
return 'persistent_checkbox_' + this.idValue
}
return 'persistent_checkbox_' + this.element.id;
}
}