inventorySearch.ts 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. import type { InventorySpool } from '../api/client';
  2. import { resolveSpoolColorName } from './colors';
  3. /**
  4. * The spool ID a "#<number>" query asks for, or null for any other query.
  5. *
  6. * A hash-prefixed number is an explicit spool ID lookup. Plain numeric
  7. * searches stay backwards-compatible ("3" can still match #3, #13, #30, ...),
  8. * while "#3" selects only the physical spool whose label says #3.
  9. */
  10. export function exactSpoolIdQuery(query: string): number | null {
  11. const match = query.trim().match(/^#(\d+)$/);
  12. return match ? Number(match[1]) : null;
  13. }
  14. /**
  15. * Return true when spool matches the search query across all searchable text fields.
  16. * Case-insensitive. Empty query always returns true.
  17. */
  18. export function spoolMatchesQuery(spool: InventorySpool, query: string): boolean {
  19. const q = query.trim().toLowerCase();
  20. if (!q) return true;
  21. const exactId = exactSpoolIdQuery(q);
  22. if (exactId !== null) return spool.id === exactId;
  23. return (
  24. String(spool.id).includes(q) ||
  25. spool.material.toLowerCase().includes(q) ||
  26. (spool.brand?.toLowerCase().includes(q) ?? false) ||
  27. // Both the stored name and the displayed one. They differ often: a Bambu
  28. // tag may carry no colour name or an internal code, and Spoolman has no
  29. // such field, so what the list shows is usually resolved from the swatch's
  30. // hex. Searching only the stored value means typing what you can plainly
  31. // read finds nothing (#3090).
  32. (spool.color_name?.toLowerCase().includes(q) ?? false) ||
  33. (resolveSpoolColorName(spool.color_name, spool.rgba, spool.color_name_is_synthesized)
  34. ?.toLowerCase()
  35. .includes(q) ??
  36. false) ||
  37. (spool.subtype?.toLowerCase().includes(q) ?? false) ||
  38. (spool.note?.toLowerCase().includes(q) ?? false) ||
  39. (spool.slicer_filament_name?.toLowerCase().includes(q) ?? false) ||
  40. (spool.storage_location?.toLowerCase().includes(q) ?? false) ||
  41. (spool.material_number?.toLowerCase().includes(q) ?? false) ||
  42. (spool.suppliers?.some(
  43. (link) =>
  44. link.supplier_name.toLowerCase().includes(q) ||
  45. (link.supplier_article_number?.toLowerCase().includes(q) ?? false)
  46. ) ?? false)
  47. );
  48. }
  49. /** Filter a spool list by a free-text search query. */
  50. export function filterSpoolsByQuery(spools: InventorySpool[], query: string): InventorySpool[] {
  51. if (!query) return spools;
  52. return spools.filter((spool) => spoolMatchesQuery(spool, query));
  53. }