Skip to content

Commit a2b54b3

Browse files
committed
Formalise filtering by tags
1 parent 85bc4ec commit a2b54b3

File tree

5 files changed

+151
-45
lines changed

5 files changed

+151
-45
lines changed

src/App.svelte

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@
1414
// Filter concerts
1515
let filters: FiltersType = {
1616
searchTerm: "",
17-
wigmoreU35: false,
17+
booleanTagNames: [],
1818
};
1919
let concertsToShow: HashedConcert[];
2020
let selectedConcertHashes: string[] = [];
2121
let selectedConcerts: HashedConcert[];
2222
$: {
2323
concertsToShow = concerts.filter((c) => satisfies(c, filters));
24-
selectedConcerts = concertsToShow.filter((c) => selectedConcertHashes.includes(c.hash));
24+
selectedConcerts = concertsToShow.filter((c) =>
25+
selectedConcertHashes.includes(c.hash),
26+
);
2527
}
2628
</script>
2729

src/lib/Filters.svelte

+56-17
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,55 @@
11
<script lang="ts">
2-
import { type FiltersType } from "src/lib/filters";
2+
import { type FiltersType, allBooleanFilters } from "src/lib/filters";
3+
import Tag from "src/lib/Tag.svelte";
4+
35
export let filters: FiltersType;
4-
$: {
5-
console.log(filters);
6+
7+
function toggleBooleanTag(
8+
event: CustomEvent<{ boolFilter: BooleanFilter }>,
9+
) {
10+
const { boolFilter } = event.detail;
11+
12+
if (filters.booleanTagNames.includes(boolFilter.tagName)) {
13+
filters.booleanTagNames = filters.booleanTagNames.filter(
14+
(tagName) => tagName !== boolFilter.tagName,
15+
);
16+
} else {
17+
filters.booleanTagNames = [
18+
...filters.booleanTagNames,
19+
boolFilter.tagName,
20+
];
21+
}
622
}
723
</script>
824

925
<div class="filters">
10-
<input
11-
id="search"
12-
type="text"
13-
placeholder="Search for a composer, performer, etc."
14-
bind:value={filters.searchTerm}
15-
/>
16-
17-
<div id="u35">
26+
<span class="bold">Currently filtering by</span>
27+
<div class="horizontal-flex">
28+
1829
<input
19-
type="checkbox"
20-
id="wigmoreU35"
21-
name="wigmoreU35"
22-
bind:checked={filters.wigmoreU35}
30+
id="search"
31+
type="text"
32+
placeholder="Search for a composer, performer, etc."
33+
bind:value={filters.searchTerm}
2334
/>
24-
<label for="wigmoreU35">£5 Wigmore Hall under-35 tickets only?</label>
35+
{#each allBooleanFilters as boolFilter}
36+
{#if filters.booleanTagNames.includes(boolFilter.tagName)}
37+
<Tag
38+
{boolFilter}
39+
mode="canRemove"
40+
on:clicked={toggleBooleanTag}
41+
/>
42+
{/if}
43+
{/each}
44+
</div>
45+
46+
<span class="bold">Add a filter</span>
47+
<div class="horizontal-flex">
48+
{#each allBooleanFilters as boolFilter}
49+
{#if !filters.booleanTagNames.includes(boolFilter.tagName)}
50+
<Tag {boolFilter} mode="canAdd" on:clicked={toggleBooleanTag} />
51+
{/if}
52+
{/each}
2553
</div>
2654
</div>
2755

@@ -35,7 +63,7 @@
3563
3664
display: flex;
3765
flex-direction: column;
38-
gap: 5px;
66+
gap: 10px;
3967
}
4068
4169
input {
@@ -45,4 +73,15 @@
4573
input#search {
4674
width: 300px;
4775
}
76+
77+
.horizontal-flex {
78+
display: flex;
79+
flex-direction: row;
80+
gap: 5px;
81+
align-items: baseline;
82+
}
83+
84+
.bold {
85+
font-weight: bold;
86+
}
4887
</style>

src/lib/Tag.svelte

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script lang="ts">
2+
import { type BooleanFilter } from "src/lib/filters";
3+
4+
import { createEventDispatcher } from "svelte";
5+
const dispatch = createEventDispatcher();
6+
7+
function handleClick() {
8+
dispatch("clicked", {
9+
boolFilter: boolFilter,
10+
});
11+
}
12+
13+
export let boolFilter: BooleanFilter;
14+
export let mode: "normal" | "canAdd" | "canRemove" = "normal";
15+
</script>
16+
17+
<button
18+
class="tag"
19+
class:can-add={mode === "canAdd"}
20+
class:can-remove={mode === "canRemove"}
21+
style="background-color: {boolFilter.tagColor}"
22+
on:click={handleClick}
23+
>
24+
<span>{boolFilter.tagName}</span>
25+
</button>
26+
27+
<style>
28+
button.tag {
29+
font-family: inherit;
30+
padding: 2px 5px;
31+
border-radius: 5px;
32+
margin: 0;
33+
color: white;
34+
border: none;
35+
width: max-content;
36+
}
37+
38+
button.tag.can-remove,
39+
button.tag.can-add {
40+
cursor: pointer;
41+
}
42+
43+
button.tag.can-remove > span::before {
44+
content: "";
45+
}
46+
47+
button.tag.can-add > span::before {
48+
content: "+ ";
49+
}
50+
</style>

src/lib/Tags.svelte

+8-22
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
<script lang="ts">
22
import { type Concert } from "src/lib/bindings/Concert";
3+
import { type BooleanFilter, allBooleanFilters } from "src/lib/filters";
4+
import Tag from "src/lib/Tag.svelte";
5+
36
export let concert: Concert;
47
</script>
58

69
<div id="tags">
7-
{#if concert.venue === "Wigmore Hall"}
8-
<span class="tag wigmore">Wigmore Hall</span>
9-
{/if}
10-
{#if concert.is_wigmore_u35}
11-
<span class="tag wigmore-u35">£5 for U35</span>
12-
{/if}
10+
{#each allBooleanFilters as boolFilter}
11+
{#if boolFilter.filterFunc(concert)}
12+
<Tag {boolFilter} />
13+
{/if}
14+
{/each}
1315
</div>
1416

1517
<style>
@@ -19,20 +21,4 @@
1921
gap: 5px;
2022
margin-bottom: 5px;
2123
}
22-
23-
span.tag {
24-
padding: 2px 5px;
25-
border-radius: 5px;
26-
margin: 0;
27-
}
28-
29-
span.wigmore {
30-
background-color: #17a8ad;
31-
color: white;
32-
}
33-
34-
span.wigmore-u35 {
35-
background-color: #3694cf;
36-
color: white;
37-
}
3824
</style>

src/lib/filters.ts

+33-4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,30 @@ import { type Concert } from "src/lib/bindings/Concert";
22

33
export type FiltersType = {
44
searchTerm: string;
5-
wigmoreU35: boolean;
5+
booleanTagNames: string[];
66
};
77

8+
// Type of a filter which is either on/off
9+
export type BooleanFilter = {
10+
tagName: string;
11+
tagColor: string;
12+
filterFunc: (concert: Concert) => boolean;
13+
}
14+
15+
// List of all boolean filters that we know of
16+
export const allBooleanFilters: BooleanFilter[] = [
17+
{
18+
tagName: "Wigmore U35 £5",
19+
tagColor: "#3694cf",
20+
filterFunc: (concert: Concert) => concert.is_wigmore_u35,
21+
},
22+
{
23+
tagName: "Wigmore Hall",
24+
tagColor: "#17a8ad",
25+
filterFunc: (concert: Concert) => concert.venue === "Wigmore Hall",
26+
}
27+
];
28+
829
// Check if a concert satisfies the filters
930
export function satisfies(concert: Concert, filters: FiltersType): boolean {
1031
// Check search filter
@@ -19,9 +40,17 @@ export function satisfies(concert: Concert, filters: FiltersType): boolean {
1940
p.name.toLowerCase().includes(ciSearchTerm),
2041
);
2142

22-
// Check U35 filter
23-
let u35Pass = filters.wigmoreU35 ? concert.is_wigmore_u35 : true;
43+
// Check boolean tags
44+
let booleanPass = filters.booleanTagNames.every((tag) => {
45+
let filter = allBooleanFilters.find((f) => f.tagName === tag);
46+
if (filter === undefined) {
47+
console.error(`Unknown boolean tag ${tag}`);
48+
return false;
49+
}
50+
return filter.filterFunc(concert);
51+
});
2452

25-
return searchPass && u35Pass;
53+
// Return conjunction of both
54+
return searchPass && booleanPass;
2655
}
2756

0 commit comments

Comments
 (0)