+page.svelte 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. <script lang="ts">
  2. import { Toolbox } from '@lucide/svelte';
  3. import { SvelteSet } from 'svelte/reactivity';
  4. import { resolve } from '$app/paths';
  5. import { FilterList } from '$lib/components/common/filter-list';
  6. import * as Filters from '$lib/components/common/filters';
  7. import { ToolCard } from '$lib/components/tools/card';
  8. import { getBreadcrumbContext } from '$lib/stores/breadcrumb.svelte';
  9. import { toolsStore } from '$lib/stores/tools.svelte';
  10. import type { PageData } from './$types';
  11. const { data }: { data: PageData } = $props();
  12. let query = $state('');
  13. const allTags = $derived([...new Set(toolsStore.list.flatMap((m) => m.tags ?? []))].sort());
  14. const selectedTags = new SvelteSet<string>();
  15. let filters: Filters.FilterRow[] = $derived([
  16. {
  17. id: 'mode',
  18. type: 'single',
  19. label: 'Mode',
  20. options: [
  21. { value: 'all', label: 'All' },
  22. { value: 'external', label: 'External' },
  23. { value: 'managed', label: 'Managed' },
  24. ],
  25. selected: 'all',
  26. default: 'all',
  27. },
  28. ...(allTags.length > 0
  29. ? [
  30. {
  31. id: 'tags',
  32. type: 'multi' as const,
  33. label: 'Tags',
  34. options: allTags.map((t) => ({ value: t, label: t })),
  35. selected: selectedTags,
  36. },
  37. ]
  38. : []),
  39. ]);
  40. const filtered = $derived.by(() => {
  41. let list = toolsStore.list;
  42. const q = query.trim().toLowerCase();
  43. if (q) list = list.filter((t) => t.name.toLowerCase().includes(q));
  44. for (const filter of filters) {
  45. if (filter.id === 'mode') {
  46. list = list.filter((t) => (filter.selected === 'all' ? true : t.mode === filter.selected));
  47. } else if (filter.id === 'tags' && filter.type === 'multi') {
  48. if (filter.selected.size > 0)
  49. list = list.filter((t) => t.tags?.some((tag) => filter.selected.has(tag)));
  50. }
  51. }
  52. return list;
  53. });
  54. $effect.root(() => {
  55. toolsStore.hydrate(data.tools);
  56. });
  57. const breadcrumb = getBreadcrumbContext();
  58. $effect(() => {
  59. breadcrumb.set({ module: 'Tools', pages: [] });
  60. });
  61. </script>
  62. <svelte:head>
  63. <title>Tools - LocoStack</title>
  64. </svelte:head>
  65. <div class="mx-auto max-w-400">
  66. <FilterList
  67. creationLink={resolve('/workloads/tools/new')}
  68. itemCount={toolsStore.list.length}
  69. itemIcon={Toolbox}
  70. itemKey={(m) => m.id}
  71. itemName="tool"
  72. itemNamePlural="tools"
  73. items={filtered}
  74. bind:query
  75. bind:filters
  76. >
  77. {#snippet itemRenderer(tool)}
  78. <ToolCard {selectedTags} {tool} />
  79. {/snippet}
  80. </FilterList>
  81. </div>