Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 120 additions & 42 deletions src/lib/components/Facets/Facets.svelte
Original file line number Diff line number Diff line change
@@ -1,114 +1,192 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { getModalStore, Modal, TreeView, TreeViewItem } from '@skeletonlabs/skeleton';
import ShowMore from './ShowMore.svelte';
import type { FacetOption, FacetGroup } from '$models/Models';
import type { FacetGroup, SelectedFacetGroup } from '$models/Models';
export let groupSelection = false;
export let groups: FacetGroup;
export let selected: FacetGroup;
export let selectedGroups: { [key: string]: boolean } = {};
export let groups: FacetGroup[];
export let showAll = false;
export let open = false;
let displayedGroups = structuredClone(groups);
let selected: { [key: string]: SelectedFacetGroup } = groups.reduce((acc, g) => {
const children = g.children.reduce((acc, c) => {
acc[c.name] = {
...c,
selected: false
};
return acc;
}, {});
acc[g.name] = {
...g,
children,
selected: false
};
return acc;
}, {});
let selectedItems: {
[key: string]: {
[key: string]: boolean;
};
} = {};
let selectedGroups: { [key: string]: boolean } = {};
Object.keys(selected).forEach((groupName) => {
selectedItems[groupName] = {};
Object.keys(selected[groupName].children).forEach((itemName) => {
selectedItems[groupName][itemName] = false;
});
selectedGroups[groupName] = false;
});
const dispatch = createEventDispatcher();
const modalStore = getModalStore();
const showMore = (group: string) => {
const showMore = (group: SelectedFacetGroup) => {
modalStore.trigger({
type: 'component',
title: `${group}`,
title: `${group.displayName}`,
component: {
ref: ShowMore,
props: {
group,
handleSave,
handleCancel,
selected: selected[group],
items: groups[group].sort((a, b) => a.value.localeCompare(b.value))
handleCancel
}
}
});
};
const handleSave = (group: string, selectedItems: FacetOption[]) => {
selected[group] = selectedItems;
const handleSave = (group: SelectedFacetGroup) => {
Object.keys(group.children).forEach((key) => {
selectedItems[group.name][key] = group.children[key].selected;
});
modalStore.close();
};
const handleCancel = () => {
modalStore.close();
};
const mapSelected = (type: 'items' | 'groups') => {
const changed: any = [];
if (type === 'items') {
Object.keys(selectedItems).forEach((group) => {
Object.keys(selectedItems[group]).forEach((item) => {
if (selectedItems[group][item] !== selected[group].children[item].selected) {
changed.push({
parent: group,
selectedItem: item
});
selected[group].children[item].selected = selectedItems[group][item];
}
});
});
} else {
Object.keys(selectedGroups).forEach((group) => {
if (selectedGroups[group] !== selected[group].selected) {
changed.push({
parent: null,
selectedItem: group
});
selected[group].selected = selectedGroups[group];
}
});
}
dispatch('change', changed);
};
const sortOptions = () => {
// Sort facets in a descending order if count exits, or sort alphabetically
Object.keys(groups).forEach((group) => {
groups[group] = [
...selected[group].sort((a, b) => {
Object.keys(selected).forEach((group) => {
const checked = Object.keys(selected[group].children)
.filter((item) => selected[group].children[item].selected)
.map((item) => selected[group].children[item])
.sort((a, b) => {
if (a.count != undefined && b.count != undefined) {
return b.count - a.count;
}
return a.value.localeCompare(b.value);
}),
...groups[group]
.filter((item) => !selected[group].includes(item))
.sort((a, b) => {
if (a.count != undefined && b.count != undefined) {
return b.count - a.count;
}
return a.value.localeCompare(b.value);
})
return a.displayName.localeCompare(b.displayName);
})
.map((item) => item.name);
const unchecked = Object.keys(selected[group].children).filter(
(item) => !checked.includes(item)
);
const groupIndex = displayedGroups.findIndex((g) => g.name === group);
displayedGroups[groupIndex].children = [
...checked.map(
(item) => displayedGroups[groupIndex].children.find((i) => i.name === item)!
),
...unchecked.map(
(item) => displayedGroups[groupIndex].children.find((i) => i.name === item)!
)
];
});
};
$: selected, sortOptions();
$: selectedItems, mapSelected('items'), sortOptions();
$: selectedGroups, mapSelected('groups');
</script>

<TreeView selection={groupSelection} multiple={groupSelection} padding="p-1" hover="">
{#each Object.keys(groups) as group}
{#each displayedGroups as group}
<TreeViewItem
name="groups"
value={group}
name={group.name}
value={group.name}
{open}
hyphenOpacity="opacity-0"
bind:checked={selectedGroups[group.name]}
bind:group={selectedGroups}
bind:checked={selectedGroups[group]}
>
<p class="font-semibold">{group}</p>
<p class="font-semibold">{group.displayName}</p>

<svelte:fragment slot="children">
<!-- If more than 5 choices, show the remaining in the Modal -->
{#if !showAll}
{#each groups[group].slice(0, 5) as item}
{#each group.children.slice(0, 5) as item}
<TreeViewItem
bind:group={selected[group]}
name={group}
value={item}
bind:group={selectedItems[group.name]}
name={item.name}
value={item.displayName}
hyphenOpacity="opacity-0"
bind:checked={selectedItems[group.name][item.name]}
spacing="space-x-3"
selection
multiple
>
<p>{item.value} ({item.count})</p>
<p>{item.displayName} ({item.count})</p>
</TreeViewItem>
{/each}
<!-- Trigger for the Modal to view all options -->
{#if groups[group].length > 5}
{#if group.children.length > 5}
<TreeViewItem hyphenOpacity="opacity-0">
<button class="anchor" on:click={() => showMore(group)}>more</button></TreeViewItem
<button class="anchor" on:click={() => showMore(selected[group.name])}>more</button
></TreeViewItem
>
{/if}
{:else}
{#each groups[group] as item}
{#each group.children as item}
<TreeViewItem
bind:group={selected[group]}
name={group}
value={item}
bind:group={selectedItems[group.name]}
bind:checked={selectedItems[group.name][item.name]}
name={item.name}
value={item.displayName}
hyphenOpacity="opacity-0"
spacing="space-x-3"
selection
multiple
>
<p>{item.value} ({item.count})</p>
<p>{item.displayName} ({item.count})</p>
</TreeViewItem>
{/each}
{/if}
Expand Down
52 changes: 20 additions & 32 deletions src/lib/components/Facets/ShowMore.svelte
Original file line number Diff line number Diff line change
@@ -1,42 +1,34 @@
<script lang="ts">
import type { FacetOption } from '$models/Models';
import type { SelectedFacetGroup } from '$models/Models';
export let group: string; // Group name
export let items: FacetOption[]; // All possible choices
export let selected: FacetOption[]; // Initially selected items
export let handleSave: (group: string, selectedItems: FacetOption[]) => {};
export let group: SelectedFacetGroup;
export let handleSave: (group: SelectedFacetGroup) => {};
export let handleCancel: () => {};
// This local variable is needed for resetting the Modal when the user cancels selection.
let selectedItems = selected; // Selected items in the Modal.
const handleCheck = (e, index: number) => {
const target = e.target as HTMLInputElement;
if (target.checked) {
selectedItems = [...selectedItems, items[index]];
} else {
selectedItems = selectedItems.filter((item) => item !== items[index]);
}
};
let selected = structuredClone(group.children);
const selectAll = () => {
selectedItems = items;
Object.keys(selected).forEach((key) => (selected[key].selected = true));
};
const selectNone = () => {
selectedItems = [];
Object.keys(selected).forEach((key) => (selected[key].selected = false));
};
const onSave = () => {
handleSave(group, selectedItems);
handleSave({
...group,
children: selected
});
};
const onCancel = () => {
selectedItems = selected;
console.log(selected, group.children);
selected = structuredClone(group.children);
handleCancel();
};
const gridClass = (items: FacetOption[]) => {
const gridClass = (items: any[]) => {
if (items.length >= 50) {
return 'grid-cols-5';
} else if (items.length >= 30) {
Expand All @@ -51,24 +43,20 @@

<div class="p-5 rounded-md bg-surface-50 dark:bg-surface-800 border-primary-500 border-2">
<!-- Header -->
<h2 class="text-xl font-semibold">{group}</h2>
<h2 class="text-xl font-semibold">{group.displayName}</h2>

<!-- Items -->
<div
class="grid {gridClass(
items
Object.keys(selected)
)} !gap-x-20 gap-y-2 py-10 px-2 max-h-[1000px] overflow-x-auto max-w-6xl"
>
{#each items as item, index}
{#each Object.keys(selected) as key}
<label class="flex gap-3 items-center">
<input
type="checkbox"
class="checkbox"
value={item.value}
on:click={(e) => handleCheck(e, index)}
checked={selectedItems.includes(item)}
/>
<span class="whitespace-nowrap break-before-avoid break-after-avoid">{item.value}</span>
<input type="checkbox" class="checkbox" bind:checked={selected[key].selected} />
<span class="whitespace-nowrap break-before-avoid break-after-avoid"
>{selected[key].displayName}</span
>
</label>
{/each}
</div>
Expand Down
42 changes: 28 additions & 14 deletions src/lib/models/Models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export interface Columns {
[key: string]: Column;
}

// Server config type for the table
// Server config type for the table
export type ServerConfig = {
baseUrl?: string; // Base URL for server-side table
sendModel?: Send; // Send model for server-side table
Expand Down Expand Up @@ -191,14 +191,29 @@ export type Filter = {
value: string | number | Date | boolean;
};

export type FacetOption = {
value: string;
export interface FacetOption {
name: string;
displayName: string;
count?: number;
};
}

export type FacetGroup = {
[key: string]: FacetOption[];
};
export interface FacetGroup {
name: string;
displayName: string;
children: FacetOption[];
count?: number;
}

export interface SelectedFacetOption extends FacetOption {
selected: boolean;
}

export interface SelectedFacetGroup extends Omit<FacetGroup, 'children'> {
selected: boolean;
children: {
[key: string]: SelectedFacetOption;
};
}

export class Send {
id: number;
Expand Down Expand Up @@ -237,14 +252,13 @@ export class Receive {
export class errorType {
statusText: string;
status: number;
error:string;
stackTrace:string
error: string;
stackTrace: string;

constructor() {
this.statusText = "";
this.statusText = '';
this.status = 0;
this.error = "";
this.stackTrace = "";
this.error = '';
this.stackTrace = '';
}

};
}
Loading