Skip to content

Commit 6e6fa79

Browse files
committed
Stop concerts from being deselected all the time
Closes #1
1 parent 417ebbf commit 6e6fa79

7 files changed

+69
-45
lines changed

src/components/ConcertList.svelte

+19-16
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,44 @@
22
import Tags from "src/components/Tags.svelte";
33
import { type Concert } from "src/lib/bindings/Concert";
44
import { formatDate, getPriceString } from "src/lib/utils";
5-
import { selectedConcertIndices } from "src/lib/stores";
5+
import { concertViews, selectedConcertIds } from "src/lib/stores";
6+
import { notUndefined } from "src/lib/utils";
67
78
export let allConcerts: Concert[];
8-
export let shownIndices: number[];
9+
export let shownIds: string[];
910
1011
// Event handler when a concert is clicked. The behaviour is chosen to
1112
// provide as intuitive a UI as possible
12-
function selectOrDeselect(event: MouseEvent, idx: number) {
13-
if ($selectedConcertIndices.includes(idx)) {
13+
function selectOrDeselect(event: MouseEvent, id: string) {
14+
if ($selectedConcertIds.includes(id)) {
1415
if (event.shiftKey) {
15-
$selectedConcertIndices = $selectedConcertIndices.filter(
16-
(i) => i !== idx,
16+
$selectedConcertIds = $selectedConcertIds.filter(
17+
(i) => i !== id,
1718
);
1819
} else {
19-
if ($selectedConcertIndices.length === 1) {
20-
$selectedConcertIndices = [];
20+
if ($selectedConcertIds.length === 1) {
21+
$selectedConcertIds = [];
2122
} else {
22-
$selectedConcertIndices = [idx];
23+
$selectedConcertIds = [id];
2324
}
2425
}
2526
} else {
2627
if (event.shiftKey) {
27-
$selectedConcertIndices = [...$selectedConcertIndices, idx];
28+
$selectedConcertIds = [...$selectedConcertIds, id];
2829
} else {
29-
$selectedConcertIndices = [idx];
30+
$selectedConcertIds = [id];
3031
}
3132
}
3233
}
3334
</script>
3435

3536
<div class="concert-list">
36-
{#each shownIndices as idx}
37-
{@const concert = allConcerts[idx]}
37+
{#each shownIds as id}
38+
{@const concert = notUndefined(allConcerts.find((c) => c.id === id))}
3839
<button
3940
class="concert"
40-
class:active={$selectedConcertIndices.includes(idx)}
41-
on:click={(event) => selectOrDeselect(event, idx)}
41+
class:active={$selectedConcertIds.includes(id)}
42+
on:click={(event) => selectOrDeselect(event, id)}
4243
>
4344
<Tags {concert} />
4445
<h3>{concert.title}</h3>
@@ -68,7 +69,9 @@
6869
border: 2px solid #666;
6970
padding: 10px;
7071
border-radius: 5px;
71-
width: calc(100% - 5px); /* Leave some space for scrollbar on some systems */
72+
width: calc(
73+
100% - 5px
74+
); /* Leave some space for scrollbar on some systems */
7275
7376
font-family: inherit;
7477
text-align: left;

src/components/Details.svelte

+5-5
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
import {
55
concertViews,
66
currentViewName,
7-
selectedConcertIndices,
7+
selectedConcertIds,
88
defaultViewName,
99
} from "src/lib/stores";
1010
import Dropdown from "src/components/Dropdown.svelte";
1111
1212
let allConcerts: Concert[] = $concertViews.get(
1313
$currentViewName,
1414
) as Concert[];
15-
let selectedConcerts: Concert[] = allConcerts.filter((_, idx) =>
16-
$selectedConcertIndices.includes(idx),
15+
let selectedConcerts: Concert[] = allConcerts.filter((c) =>
16+
$selectedConcertIds.includes(c.id),
1717
);
1818
1919
function compareConcertTime(a: Concert, b: Concert): number {
@@ -61,8 +61,8 @@
6161
6262
$: {
6363
allConcerts = $concertViews.get($currentViewName) as Concert[];
64-
selectedConcerts = allConcerts.filter((_, idx) =>
65-
$selectedConcertIndices.includes(idx),
64+
selectedConcerts = allConcerts.filter((c) =>
65+
$selectedConcertIds.includes(c.id),
6666
);
6767
}
6868
</script>

src/components/Overview.svelte

+23-11
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,37 @@
66
concertViews,
77
filters,
88
currentViewName,
9-
selectedConcertIndices,
9+
selectedConcertIds,
1010
} from "src/lib/stores";
11-
import { getPassingIndices } from "src/lib/filters";
11+
import { getPassingIds, type FiltersType } from "src/lib/filters";
1212
13-
let allConcerts: Concert[] = $concertViews.get($currentViewName) as Concert[];
14-
let shownIndices: number[] = getPassingIndices(allConcerts, $filters);
13+
let allConcerts: Concert[] = $concertViews.get(
14+
$currentViewName,
15+
) as Concert[];
16+
let shownIds: string[] = getPassingIds(allConcerts, $filters);
17+
18+
// Whenever either the view name changes or the filters are updated, update the UI,
19+
// and reset the selected concert IDs.
20+
// The reason why we explicitly take currentViewName and filters as arguments is that
21+
// we can use them in the $: block to trigger the updateUI function
22+
// whenever these variables change.
23+
// In contrast, we don't need to do this when $concertViews itself gets updated, so we
24+
// just use its value directly inside the function.
25+
// Hacky!
26+
function updateUI(currentViewName1: string, filters1: FiltersType) {
27+
allConcerts = $concertViews.get(currentViewName1) as Concert[];
28+
shownIds = getPassingIds(allConcerts, filters1);
29+
$selectedConcertIds = [];
30+
}
1531
1632
$: {
17-
// Calculate the indices of the concerts that should be shown
18-
allConcerts = $concertViews.get($currentViewName) as Concert[];
19-
shownIndices = getPassingIndices(allConcerts, $filters);
20-
// Clear selected concerts when filters are changed (or allConcerts for that matter)
21-
$selectedConcertIndices = [];
33+
updateUI($currentViewName, $filters);
2234
}
2335
</script>
2436

2537
<div class="overview">
26-
<ViewList bind:allConcerts bind:shownIndices />
27-
<ConcertList bind:allConcerts bind:shownIndices />
38+
<ViewList bind:allConcerts bind:shownIds />
39+
<ConcertList bind:allConcerts bind:shownIds />
2840
</div>
2941

3042
<style>

src/components/ViewList.svelte

+9-7
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
concertViews,
55
filters,
66
currentViewName,
7-
selectedConcertIndices,
7+
selectedConcertIds,
88
defaultViewName,
99
} from "src/lib/stores";
1010
import { initialFilters } from "src/lib/filters";
1111
import FileSelector from "src/components/FileSelector.svelte";
1212
import Dropdown from "src/components/Dropdown.svelte";
1313
1414
export let allConcerts: Concert[];
15-
export let shownIndices: number[];
15+
export let shownIds: string[];
1616
1717
function setViewName(newViewName: string) {
1818
$currentViewName = newViewName;
@@ -33,7 +33,9 @@
3333
if (newViewName === null) {
3434
return;
3535
}
36-
const shownConcerts = shownIndices.map((i) => allConcerts[i]);
36+
const shownConcerts = shownIds.map((i) =>
37+
allConcerts.find((c) => c.id === i),
38+
) as Concert[];
3739
$filters = initialFilters;
3840
$concertViews.set(newViewName, shownConcerts);
3941
$concertViews = new Map($concertViews); // Required to trigger store update
@@ -45,9 +47,9 @@
4547
if (newViewName === null) {
4648
return;
4749
}
48-
const selectedConcerts = $selectedConcertIndices.map(
49-
(i) => allConcerts[i],
50-
);
50+
const selectedConcerts = $selectedConcertIds
51+
.map((i) => allConcerts.find((c) => c.id === i))
52+
.filter((c) => c !== undefined) as Concert[];
5153
$filters = initialFilters;
5254
$concertViews.set(newViewName, selectedConcerts);
5355
$concertViews = new Map($concertViews); // Required to trigger store update
@@ -133,7 +135,7 @@
133135
{@const allConcertsLength = notUndefined(
134136
$concertViews.get(viewName),
135137
).length}
136-
{@const shownConcertsLength = shownIndices.length}
138+
{@const shownConcertsLength = shownIds.length}
137139
<Dropdown
138140
hasOptions={viewName !== defaultViewName}
139141
selected={$currentViewName === viewName}

src/lib/filters.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,14 @@ function satisfies(concert: Concert, filters: FiltersType): boolean {
6464
return searchPass && booleanPass;
6565
}
6666

67-
export function getPassingIndices(concerts: Concert[], filters: FiltersType): number[] {
68-
let passingIndices: number[] = [];
69-
concerts.forEach((concert, i) => {
67+
export function getPassingIds(concerts: Concert[], filters: FiltersType): string[] {
68+
let passingIds: string[] = [];
69+
concerts.forEach((concert) => {
7070
if (satisfies(concert, filters)) {
71-
passingIndices.push(i);
71+
passingIds.push(concert.id);
7272
}
7373
});
74-
return passingIndices;
74+
return passingIds;
7575
}
7676

7777
export const initialFilters: FiltersType = {

src/lib/stores.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export const concertViews = {
6666
export const currentViewName: Writable<string> = writable(defaultViewName);
6767

6868
// Indices of selected concerts
69-
export const selectedConcertIndices: Writable<number[]> = writable([]);
69+
export const selectedConcertIds: Writable<string[]> = writable([]);
7070

7171
// Store for state of filters. We have to make a copy of initialFilters to avoid
7272
// mutating it

src/lib/utils.ts

+7
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,10 @@ export function getPriceString(concert: Concert): string {
2222
}
2323
return "Price not available";
2424
}
25+
26+
export function notUndefined<T>(x: T | undefined): T {
27+
if (x === undefined) {
28+
throw new Error("Unexpected undefined value");
29+
}
30+
return x;
31+
}

0 commit comments

Comments
 (0)