|
1 | 1 | <script lang="ts">
|
| 2 | + import { createEventDispatcher } from 'svelte'; |
2 | 3 | import { getModalStore, Modal, TreeView, TreeViewItem } from '@skeletonlabs/skeleton';
|
3 | 4 |
|
4 | 5 | import ShowMore from './ShowMore.svelte';
|
5 |
| - import type { FacetOption, FacetGroup } from '$models/Models'; |
| 6 | + import type { FacetGroup, SelectedFacetGroup } from '$models/Models'; |
6 | 7 |
|
7 | 8 | export let groupSelection = false;
|
8 |
| - export let groups: FacetGroup; |
9 |
| - export let selected: FacetGroup; |
10 |
| - export let selectedGroups: { [key: string]: boolean } = {}; |
| 9 | + export let groups: FacetGroup[]; |
11 | 10 | export let showAll = false;
|
12 | 11 | export let open = false;
|
13 | 12 |
|
| 13 | + let displayedGroups = structuredClone(groups); |
| 14 | +
|
| 15 | + let selected: { [key: string]: SelectedFacetGroup } = groups.reduce((acc, g) => { |
| 16 | + const children = g.children.reduce((acc, c) => { |
| 17 | + acc[c.name] = { |
| 18 | + ...c, |
| 19 | + selected: false |
| 20 | + }; |
| 21 | + return acc; |
| 22 | + }, {}); |
| 23 | +
|
| 24 | + acc[g.name] = { |
| 25 | + ...g, |
| 26 | + children, |
| 27 | + selected: false |
| 28 | + }; |
| 29 | +
|
| 30 | + return acc; |
| 31 | + }, {}); |
| 32 | + let selectedItems: { |
| 33 | + [key: string]: { |
| 34 | + [key: string]: boolean; |
| 35 | + }; |
| 36 | + } = {}; |
| 37 | + let selectedGroups: { [key: string]: boolean } = {}; |
| 38 | +
|
| 39 | + Object.keys(selected).forEach((groupName) => { |
| 40 | + selectedItems[groupName] = {}; |
| 41 | + Object.keys(selected[groupName].children).forEach((itemName) => { |
| 42 | + selectedItems[groupName][itemName] = false; |
| 43 | + }); |
| 44 | + selectedGroups[groupName] = false; |
| 45 | + }); |
| 46 | +
|
| 47 | + const dispatch = createEventDispatcher(); |
| 48 | + |
14 | 49 | const modalStore = getModalStore();
|
15 |
| - const showMore = (group: string) => { |
| 50 | + const showMore = (group: SelectedFacetGroup) => { |
16 | 51 | modalStore.trigger({
|
17 | 52 | type: 'component',
|
18 |
| - title: `${group}`, |
| 53 | + title: `${group.displayName}`, |
19 | 54 | component: {
|
20 | 55 | ref: ShowMore,
|
21 | 56 | props: {
|
22 | 57 | group,
|
23 | 58 | handleSave,
|
24 |
| - handleCancel, |
25 |
| - selected: selected[group], |
26 |
| - items: groups[group].sort((a, b) => a.value.localeCompare(b.value)) |
| 59 | + handleCancel |
27 | 60 | }
|
28 | 61 | }
|
29 | 62 | });
|
30 | 63 | };
|
31 | 64 |
|
32 |
| - const handleSave = (group: string, selectedItems: FacetOption[]) => { |
33 |
| - selected[group] = selectedItems; |
| 65 | + const handleSave = (group: SelectedFacetGroup) => { |
| 66 | + Object.keys(group.children).forEach((key) => { |
| 67 | + selectedItems[group.name][key] = group.children[key].selected; |
| 68 | + }); |
34 | 69 | modalStore.close();
|
35 | 70 | };
|
36 | 71 |
|
37 | 72 | const handleCancel = () => {
|
38 | 73 | modalStore.close();
|
39 | 74 | };
|
40 | 75 |
|
| 76 | + const mapSelected = (type: 'items' | 'groups') => { |
| 77 | + const changed: any = []; |
| 78 | +
|
| 79 | + if (type === 'items') { |
| 80 | + Object.keys(selectedItems).forEach((group) => { |
| 81 | + Object.keys(selectedItems[group]).forEach((item) => { |
| 82 | + if (selectedItems[group][item] !== selected[group].children[item].selected) { |
| 83 | + changed.push({ |
| 84 | + parent: group, |
| 85 | + selectedItem: item |
| 86 | + }); |
| 87 | + selected[group].children[item].selected = selectedItems[group][item]; |
| 88 | + } |
| 89 | + }); |
| 90 | + }); |
| 91 | + } else { |
| 92 | + Object.keys(selectedGroups).forEach((group) => { |
| 93 | + if (selectedGroups[group] !== selected[group].selected) { |
| 94 | + changed.push({ |
| 95 | + parent: null, |
| 96 | + selectedItem: group |
| 97 | + }); |
| 98 | + selected[group].selected = selectedGroups[group]; |
| 99 | + } |
| 100 | + }); |
| 101 | + } |
| 102 | +
|
| 103 | + dispatch('change', changed); |
| 104 | + }; |
| 105 | +
|
41 | 106 | const sortOptions = () => {
|
42 | 107 | // Sort facets in a descending order if count exits, or sort alphabetically
|
43 |
| - Object.keys(groups).forEach((group) => { |
44 |
| - groups[group] = [ |
45 |
| - ...selected[group].sort((a, b) => { |
| 108 | + Object.keys(selected).forEach((group) => { |
| 109 | + const checked = Object.keys(selected[group].children) |
| 110 | + .filter((item) => selected[group].children[item].selected) |
| 111 | + .map((item) => selected[group].children[item]) |
| 112 | + .sort((a, b) => { |
46 | 113 | if (a.count != undefined && b.count != undefined) {
|
47 | 114 | return b.count - a.count;
|
48 | 115 | }
|
49 |
| - return a.value.localeCompare(b.value); |
50 |
| - }), |
51 |
| - ...groups[group] |
52 |
| - .filter((item) => !selected[group].includes(item)) |
53 |
| - .sort((a, b) => { |
54 |
| - if (a.count != undefined && b.count != undefined) { |
55 |
| - return b.count - a.count; |
56 |
| - } |
57 |
| - return a.value.localeCompare(b.value); |
58 |
| - }) |
| 116 | + return a.displayName.localeCompare(b.displayName); |
| 117 | + }) |
| 118 | + .map((item) => item.name); |
| 119 | +
|
| 120 | + const unchecked = Object.keys(selected[group].children).filter( |
| 121 | + (item) => !checked.includes(item) |
| 122 | + ); |
| 123 | +
|
| 124 | + const groupIndex = displayedGroups.findIndex((g) => g.name === group); |
| 125 | +
|
| 126 | + displayedGroups[groupIndex].children = [ |
| 127 | + ...checked.map( |
| 128 | + (item) => displayedGroups[groupIndex].children.find((i) => i.name === item)! |
| 129 | + ), |
| 130 | + ...unchecked.map( |
| 131 | + (item) => displayedGroups[groupIndex].children.find((i) => i.name === item)! |
| 132 | + ) |
59 | 133 | ];
|
60 | 134 | });
|
61 | 135 | };
|
62 | 136 |
|
63 |
| - $: selected, sortOptions(); |
| 137 | + $: selectedItems, mapSelected('items'), sortOptions(); |
| 138 | + $: selectedGroups, mapSelected('groups'); |
64 | 139 | </script>
|
65 | 140 |
|
66 | 141 | <TreeView selection={groupSelection} multiple={groupSelection} padding="p-1" hover="">
|
67 |
| - {#each Object.keys(groups) as group} |
| 142 | + {#each displayedGroups as group} |
68 | 143 | <TreeViewItem
|
69 |
| - name="groups" |
70 |
| - value={group} |
| 144 | + name={group.name} |
| 145 | + value={group.name} |
71 | 146 | {open}
|
72 | 147 | hyphenOpacity="opacity-0"
|
| 148 | + bind:checked={selectedGroups[group.name]} |
73 | 149 | bind:group={selectedGroups}
|
74 |
| - bind:checked={selectedGroups[group]} |
75 | 150 | >
|
76 |
| - <p class="font-semibold">{group}</p> |
| 151 | + <p class="font-semibold">{group.displayName}</p> |
77 | 152 |
|
78 | 153 | <svelte:fragment slot="children">
|
79 | 154 | <!-- If more than 5 choices, show the remaining in the Modal -->
|
80 | 155 | {#if !showAll}
|
81 |
| - {#each groups[group].slice(0, 5) as item} |
| 156 | + {#each group.children.slice(0, 5) as item} |
82 | 157 | <TreeViewItem
|
83 |
| - bind:group={selected[group]} |
84 |
| - name={group} |
85 |
| - value={item} |
| 158 | + bind:group={selectedItems[group.name]} |
| 159 | + name={item.name} |
| 160 | + value={item.displayName} |
86 | 161 | hyphenOpacity="opacity-0"
|
| 162 | + bind:checked={selectedItems[group.name][item.name]} |
87 | 163 | spacing="space-x-3"
|
88 | 164 | selection
|
89 | 165 | multiple
|
90 | 166 | >
|
91 |
| - <p>{item.value} ({item.count})</p> |
| 167 | + <p>{item.displayName} ({item.count})</p> |
92 | 168 | </TreeViewItem>
|
93 | 169 | {/each}
|
94 | 170 | <!-- Trigger for the Modal to view all options -->
|
95 |
| - {#if groups[group].length > 5} |
| 171 | + {#if group.children.length > 5} |
96 | 172 | <TreeViewItem hyphenOpacity="opacity-0">
|
97 |
| - <button class="anchor" on:click={() => showMore(group)}>more</button></TreeViewItem |
| 173 | + <button class="anchor" on:click={() => showMore(selected[group.name])}>more</button |
| 174 | + ></TreeViewItem |
98 | 175 | >
|
99 | 176 | {/if}
|
100 | 177 | {:else}
|
101 |
| - {#each groups[group] as item} |
| 178 | + {#each group.children as item} |
102 | 179 | <TreeViewItem
|
103 |
| - bind:group={selected[group]} |
104 |
| - name={group} |
105 |
| - value={item} |
| 180 | + bind:group={selectedItems[group.name]} |
| 181 | + bind:checked={selectedItems[group.name][item.name]} |
| 182 | + name={item.name} |
| 183 | + value={item.displayName} |
106 | 184 | hyphenOpacity="opacity-0"
|
107 | 185 | spacing="space-x-3"
|
108 | 186 | selection
|
109 | 187 | multiple
|
110 | 188 | >
|
111 |
| - <p>{item.value} ({item.count})</p> |
| 189 | + <p>{item.displayName} ({item.count})</p> |
112 | 190 | </TreeViewItem>
|
113 | 191 | {/each}
|
114 | 192 | {/if}
|
|
0 commit comments