Skip to content

Commit 74513b7

Browse files
author
Marcel Diegelmann
committed
Unterstützung für Projekt- und Baugruppensuche zum QuickSearch-Suggest hinzufügen
1 parent 4911b5b commit 74513b7

23 files changed

+519
-50
lines changed

assets/controllers/elements/part_search_controller.js

Lines changed: 166 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,19 @@ export default class extends Controller {
5454
}
5555

5656
initialize() {
57-
// The endpoint for searching parts
57+
// The endpoint for searching parts or assemblies
5858
const base_url = this.element.dataset.autocomplete;
5959
// The URL template for the part detail pages
6060
const part_detail_uri_template = this.element.dataset.detailUrl;
61+
// The URL template for the assembly detail pages
62+
const assembly_detail_uri_template = this.element.dataset.assemblyDetailUrl;
63+
// The URL template for the project detail pages
64+
const project_detail_uri_template = this.element.dataset.projectDetailUrl;
65+
66+
const hasAssemblyDetailUrl =
67+
typeof assembly_detail_uri_template === "string" && assembly_detail_uri_template.length > 0;
68+
const hasProjectDetailUrl =
69+
typeof project_detail_uri_template === "string" && project_detail_uri_template.length > 0;
6170

6271
//The URL of the placeholder picture
6372
const placeholder_image = this.element.dataset.placeholderImage;
@@ -72,6 +81,43 @@ export default class extends Controller {
7281
limit: 5,
7382
});
7483

84+
// Cache the last query to avoid fetching the same endpoint twice (parts source + assemblies source)
85+
let lastQuery = null;
86+
let lastFetchPromise = null;
87+
88+
const fetchMixedItems = (query) => {
89+
if (query === lastQuery && lastFetchPromise) {
90+
return lastFetchPromise;
91+
}
92+
93+
lastQuery = query;
94+
95+
const urlString = base_url.replace('__QUERY__', encodeURIComponent(query));
96+
const url = new URL(urlString, window.location.href);
97+
if (hasAssemblyDetailUrl || hasProjectDetailUrl) {
98+
url.searchParams.set('multidatasources', '1');
99+
}
100+
101+
lastFetchPromise = fetch(url.toString())
102+
.then((response) => response.json())
103+
.then((items) => {
104+
//Iterate over all fields besides the id and highlight them (if present)
105+
const fields = ["name", "description", "category", "footprint"];
106+
107+
items.forEach((item) => {
108+
for (const field of fields) {
109+
if (item[field] !== undefined && item[field] !== null) {
110+
item[field] = that._highlight(item[field], query);
111+
}
112+
}
113+
});
114+
115+
return items;
116+
});
117+
118+
return lastFetchPromise;
119+
};
120+
75121
this._autocomplete = autocomplete({
76122
container: this.element,
77123
//Place the panel in the navbar, if the element is in navbar mode
@@ -102,7 +148,7 @@ export default class extends Controller {
102148
},
103149

104150
// If the form is submitted, forward the term to the form
105-
onSubmit({state, event, ...setters}) {
151+
onSubmit({ state, event, ...setters }) {
106152
//Put the current text into each target input field
107153
const input = that.inputTarget;
108154

@@ -119,68 +165,146 @@ export default class extends Controller {
119165
input.form.requestSubmit();
120166
},
121167

122-
123168
getSources({ query }) {
124-
return [
125-
// The parts source
169+
const sources = [
170+
// Parts source (filtered from mixed endpoint results)
126171
{
127172
sourceId: 'parts',
128173
getItems() {
129-
const url = base_url.replace('__QUERY__', encodeURIComponent(query));
130-
131-
const data = fetch(url)
132-
.then((response) => response.json())
133-
;
134-
135-
//Iterate over all fields besides the id and highlight them
136-
const fields = ["name", "description", "category", "footprint"];
137-
138-
data.then((items) => {
139-
items.forEach((item) => {
140-
for (const field of fields) {
141-
item[field] = that._highlight(item[field], query);
142-
}
143-
});
144-
});
145-
146-
return data;
174+
return fetchMixedItems(query).then((items) =>
175+
items.filter((item) => item.type !== "assembly")
176+
);
147177
},
148178
getItemUrl({ item }) {
149179
return part_detail_uri_template.replace('__ID__', item.id);
150180
},
151181
templates: {
152182
header({ html }) {
153183
return html`<span class="aa-SourceHeaderTitle">${trans("part.labelp")}</span>
154-
<div class="aa-SourceHeaderLine" />`;
184+
<div class="aa-SourceHeaderLine" />`;
155185
},
156-
item({item, components, html}) {
186+
item({ item, components, html }) {
157187
const details_url = part_detail_uri_template.replace('__ID__', item.id);
158188

159189
return html`
160-
<a class="aa-ItemLink" href="${details_url}">
161-
<div class="aa-ItemContent">
162-
<div class="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop">
163-
<img src="${item.image !== "" ? item.image : placeholder_image}" alt="${item.name}" width="30" height="30"/>
164-
</div>
165-
<div class="aa-ItemContentBody">
166-
<div class="aa-ItemContentTitle">
167-
<b>
168-
${components.Highlight({hit: item, attribute: 'name'})}
169-
</b>
190+
<a class="aa-ItemLink" href="${details_url}">
191+
<div class="aa-ItemContent">
192+
<div class="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop">
193+
<img src="${item.image !== "" ? item.image : placeholder_image}" alt="${item.name}" width="30" height="30"/>
170194
</div>
171-
<div class="aa-ItemContentDescription">
172-
${components.Highlight({hit: item, attribute: 'description'})}
173-
${item.category ? html`<p class="m-0"><span class="fa-solid fa-tags fa-fw"></span>${components.Highlight({hit: item, attribute: 'category'})}</p>` : ""}
174-
${item.footprint ? html`<p class="m-0"><span class="fa-solid fa-microchip fa-fw"></span>${components.Highlight({hit: item, attribute: 'footprint'})}</p>` : ""}
195+
<div class="aa-ItemContentBody">
196+
<div class="aa-ItemContentTitle">
197+
<b>
198+
${components.Highlight({hit: item, attribute: 'name'})}
199+
</b>
200+
</div>
201+
<div class="aa-ItemContentDescription">
202+
${components.Highlight({hit: item, attribute: 'description'})}
203+
${item.category ? html`<p class="m-0"><span class="fa-solid fa-tags fa-fw"></span>${components.Highlight({hit: item, attribute: 'category'})}</p>` : ""}
204+
${item.footprint ? html`<p class="m-0"><span class="fa-solid fa-microchip fa-fw"></span>${components.Highlight({hit: item, attribute: 'footprint'})}</p>` : ""}
205+
</div>
175206
</div>
176207
</div>
177-
</div>
178-
</a>
179-
`;
208+
</a>
209+
`;
180210
},
181211
},
182212
},
183213
];
214+
215+
if (hasAssemblyDetailUrl) {
216+
sources.push(
217+
// Assemblies source (filtered from the same mixed endpoint results)
218+
{
219+
sourceId: 'assemblies',
220+
getItems() {
221+
return fetchMixedItems(query).then((items) =>
222+
items.filter((item) => item.type === "assembly")
223+
);
224+
},
225+
getItemUrl({ item }) {
226+
return assembly_detail_uri_template.replace('__ID__', item.id);
227+
},
228+
templates: {
229+
header({ html }) {
230+
return html`<span class="aa-SourceHeaderTitle">${trans(STATISTICS_ASSEMBLIES)}</span>
231+
<div class="aa-SourceHeaderLine" />`;
232+
},
233+
item({ item, components, html }) {
234+
const details_url = assembly_detail_uri_template.replace('__ID__', item.id);
235+
236+
return html`
237+
<a class="aa-ItemLink" href="${details_url}">
238+
<div class="aa-ItemContent">
239+
<div class="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop">
240+
<img src="${item.image !== "" ? item.image : placeholder_image}" alt="${item.name}" width="30" height="30"/>
241+
</div>
242+
<div class="aa-ItemContentBody">
243+
<div class="aa-ItemContentTitle">
244+
<b>
245+
${components.Highlight({hit: item, attribute: 'name'})}
246+
</b>
247+
</div>
248+
<div class="aa-ItemContentDescription">
249+
${components.Highlight({hit: item, attribute: 'description'})}
250+
</div>
251+
</div>
252+
</div>
253+
</a>
254+
`;
255+
},
256+
},
257+
}
258+
);
259+
}
260+
261+
if (hasProjectDetailUrl) {
262+
sources.push(
263+
// Projects source (filtered from the same mixed endpoint results)
264+
{
265+
sourceId: 'projects',
266+
getItems() {
267+
return fetchMixedItems(query).then((items) =>
268+
items.filter((item) => item.type === "project")
269+
);
270+
},
271+
getItemUrl({ item }) {
272+
return project_detail_uri_template.replace('__ID__', item.id);
273+
},
274+
templates: {
275+
header({ html }) {
276+
return html`<span class="aa-SourceHeaderTitle">${trans(STATISTICS_PROJECTS)}</span>
277+
<div class="aa-SourceHeaderLine" />`;
278+
},
279+
item({ item, components, html }) {
280+
const details_url = project_detail_uri_template.replace('__ID__', item.id);
281+
282+
return html`
283+
<a class="aa-ItemLink" href="${details_url}">
284+
<div class="aa-ItemContent">
285+
<div class="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop">
286+
<img src="${item.image !== "" ? item.image : placeholder_image}" alt="${item.name}" width="30" height="30"/>
287+
</div>
288+
<div class="aa-ItemContentBody">
289+
<div class="aa-ItemContentTitle">
290+
<b>
291+
${components.Highlight({hit: item, attribute: 'name'})}
292+
</b>
293+
</div>
294+
<div class="aa-ItemContentDescription">
295+
${components.Highlight({hit: item, attribute: 'description'})}
296+
</div>
297+
</div>
298+
</div>
299+
</a>
300+
`;
301+
},
302+
},
303+
}
304+
);
305+
}
306+
307+
return sources;
184308
},
185309
});
186310

@@ -192,6 +316,5 @@ export default class extends Controller {
192316
this._autocomplete.setIsOpen(false);
193317
});
194318
}
195-
196319
}
197320
}

src/Controller/AssemblyController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ protected function showListWithFilter(Request $request, string $template, ?calla
128128
], $additonal_template_vars));
129129
}
130130

131-
#[Route(path: '/{id}/info', name: 'assembly_info', requirements: ['id' => '\d+'])]
131+
#[Route(path: '/{id}/info', name: 'assembly_info')]
132+
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
132133
public function info(Assembly $assembly, Request $request): Response
133134
{
134135
$this->denyAccessUnlessGranted('read', $assembly);

src/Controller/ProjectController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ public function __construct(
5858
) {
5959
}
6060

61-
#[Route(path: '/{id}/info', name: 'project_info', requirements: ['id' => '\d+'])]
61+
#[Route(path: '/{id}/info', name: 'project_info')]
62+
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
6263
public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper, TableSettings $tableSettings): Response
6364
{
6465
$this->denyAccessUnlessGranted('read', $project);

0 commit comments

Comments
 (0)