Skip to content

Commit e3e25e0

Browse files
committed
update signal state through normal dispatch
1 parent 242102b commit e3e25e0

File tree

3 files changed

+64
-33
lines changed

3 files changed

+64
-33
lines changed

special-pages/pages/history/app/components/Sidebar.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useQueryContext, useQueryDispatch } from '../global-state/QueryProvider
99
import { BTN_ACTION_DELETE_RANGE } from '../constants.js';
1010
import { useGlobalState } from '../global-state/GlobalStateProvider.js';
1111
import { toRange } from '../history.service.js';
12+
import { memo } from 'preact/compat';
1213

1314
/**
1415
* @import json from "../strings.json"
@@ -77,7 +78,6 @@ export function Sidebar({ ranges }) {
7778
console.warn('could not determine valid range');
7879
}
7980
}
80-
8181
return (
8282
<div class={styles.stack}>
8383
<h1 class={styles.pageTitle}>{t('page_title')}</h1>
@@ -89,7 +89,7 @@ export function Sidebar({ ranges }) {
8989
</div>
9090
);
9191
}
92-
92+
const SidebarItem = memo(SidebarItem_);
9393
/**
9494
* Renders an item component with additional properties and functionality.
9595
*
@@ -99,7 +99,7 @@ export function Sidebar({ ranges }) {
9999
* @param {import("@preact/signals").Signal<Range|null>} props.current The current state object used to determine active item styling.
100100
* @param {import("@preact/signals").Signal<number>} props.count
101101
*/
102-
function SidebarItem({ range, title, current, count }) {
102+
function SidebarItem_({ range, title, current, count }) {
103103
const { t } = useTypedTranslationWith(/** @type {json} */ ({}));
104104
const ariaDisabled = useComputed(() => {
105105
return range === 'all' && count.value === 0 ? 'true' : 'false';
@@ -125,15 +125,14 @@ function SidebarItem({ range, title, current, count }) {
125125
return [t('show_history_older'), t('delete_history_older')];
126126
}
127127
})();
128+
129+
const classNames = useComputed(() => {
130+
return cn(styles.link, current.value === range && styles.active);
131+
});
132+
128133
return (
129134
<div class={styles.item}>
130-
<a
131-
href="#"
132-
aria-label={linkLabel}
133-
data-filter={range}
134-
class={cn(styles.link, current.value === range && styles.active)}
135-
tabindex={0}
136-
>
135+
<a href="#" aria-label={linkLabel} data-filter={range} class={classNames} tabindex={0}>
137136
<span class={styles.icon}>
138137
<img src={iconMap[range]} />
139138
</span>

special-pages/pages/history/app/global-state/QueryProvider.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export function QueryProvider({ children, query = { term: '' } }) {
6464
* @param {Action} action
6565
*/
6666
function dispatch(action) {
67+
// console.log('queryState.dispatch', action);
6768
queryState.value = (() => {
6869
switch (action.kind) {
6970
case 'reset': {

special-pages/pages/history/app/global-state/SelectionProvider.js

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createContext, h } from 'preact';
2-
import { useContext } from 'preact/hooks';
2+
import { useCallback, useContext } from 'preact/hooks';
33
import { signal, useComputed, useSignal, useSignalEffect } from '@preact/signals';
44
import { useQueryContext } from './QueryProvider.js';
55
import { usePlatformName } from '../types.js';
@@ -11,6 +11,10 @@ import { eventToIntention } from '../utils.js';
1111
* @typedef {import("../utils.js").Intention} Intention
1212
*/
1313

14+
/**
15+
* @typedef {{kind: 'set-selections', value: Set<number>, reason: string}} Action
16+
*/
17+
const SelectionDispatchContext = createContext(/** @type {(a: Action) => void} */ ((a) => {}));
1418
const SelectionContext = createContext({
1519
selected: signal(/** @type {Set<number>} */ (new Set([]))),
1620
});
@@ -23,7 +27,29 @@ const SelectionContext = createContext({
2327
*/
2428
export function SelectionProvider({ children }) {
2529
const selected = useSignal(new Set(/** @type {number[]} */ ([])));
26-
return <SelectionContext.Provider value={{ selected }}>{children}</SelectionContext.Provider>;
30+
/** @type {(a: Action) => void} */
31+
const dispatch = useCallback(
32+
/**
33+
* @param {Action} action
34+
*/
35+
function dispatch(action) {
36+
selected.value = (() => {
37+
switch (action.kind) {
38+
case 'set-selections': {
39+
return action.value;
40+
}
41+
default:
42+
return selected.value;
43+
}
44+
})();
45+
},
46+
[selected],
47+
);
48+
return (
49+
<SelectionContext.Provider value={{ selected }}>
50+
<SelectionDispatchContext.Provider value={dispatch}>{children}</SelectionDispatchContext.Provider>
51+
</SelectionContext.Provider>
52+
);
2753
}
2854

2955
// Hook for consuming the context
@@ -37,20 +63,18 @@ export function useSelected() {
3763

3864
export function useSelectionEvents(containerRef) {
3965
const { selected } = useContext(SelectionContext);
40-
/** @type {UpdateSelected} */
41-
const update = (fn, reason) => {
42-
// console.log('[❌] clearing selections because', reason);
43-
selected.value = fn(selected.value);
44-
};
66+
useResetOnQueryChange();
67+
useRowInteractions(selected, containerRef);
68+
}
4569

46-
useResetOnQueryChange(update);
47-
useRowInteractions(update, selected, containerRef);
70+
function useSelectionDispatch() {
71+
return useContext(SelectionDispatchContext);
4872
}
4973

5074
/**
51-
* @param {UpdateSelected} update
5275
*/
53-
function useResetOnQueryChange(update) {
76+
function useResetOnQueryChange() {
77+
const dispatch = useSelectionDispatch();
5478
const query = useQueryContext();
5579
const { results } = useGlobalState();
5680
const length = useComputed(() => results.value.items.length);
@@ -61,12 +85,16 @@ function useResetOnQueryChange(update) {
6185
// when anything about the query changes, reset selections
6286
// todo: this should not fire on the first value too
6387
query.subscribe((old) => {
64-
update((prev) => new Set([]), 'query changed');
88+
dispatch({ kind: 'set-selections', value: new Set([]), reason: 'query changed' });
6589
}),
6690
// todo: this should not fire on the first value too
6791
length.subscribe((newLength) => {
6892
if (newLength < prevLength) {
69-
update((prev) => new Set([]), `items length shrank from ${prevLength} to ${newLength}`);
93+
dispatch({
94+
kind: 'set-selections',
95+
value: new Set([]),
96+
reason: `items length shrank from ${prevLength} to ${newLength}`,
97+
});
7098
}
7199
prevLength = newLength;
72100
}),
@@ -81,12 +109,11 @@ function useResetOnQueryChange(update) {
81109
}
82110

83111
/**
84-
* @param {UpdateSelected} update
85-
* @param {import("@preact/signals").Signal<Set<number>>} selected
112+
* @param {import("@preact/signals").ReadonlySignal<Set<number>>} selected
86113
*/
87-
function useRowInteractions(update, selected, containerRef) {
114+
function useRowInteractions(selected, containerRef) {
88115
const platformName = usePlatformName();
89-
116+
const dispatch = useSelectionDispatch();
90117
const anchorIndex = useSignal(/** @type {null|number} */ (null));
91118
const lastShiftRange = useSignal({ start: /** @type {null|number} */ (null), end: /** @type {null|number} */ (null) });
92119
const focusedIndex = useSignal(/** @type {null|number} */ (null));
@@ -101,7 +128,7 @@ function useRowInteractions(update, selected, containerRef) {
101128
const { index } = selection;
102129
switch (intention) {
103130
case 'click': {
104-
selected.value = new Set([index]);
131+
dispatch({ kind: 'set-selections', value: new Set([index]), reason: 'row clicked' });
105132
anchorIndex.value = index;
106133
lastShiftRange.value = { start: null, end: null };
107134
focusedIndex.value = index;
@@ -114,7 +141,7 @@ function useRowInteractions(update, selected, containerRef) {
114141
} else {
115142
newSelected.add(index);
116143
}
117-
selected.value = newSelected;
144+
dispatch({ kind: 'set-selections', value: newSelected, reason: 'row ctrl+clicked' });
118145
anchorIndex.value = index;
119146
lastShiftRange.value = { start: null, end: null };
120147
focusedIndex.value = index;
@@ -140,7 +167,7 @@ function useRowInteractions(update, selected, containerRef) {
140167
}
141168

142169
lastShiftRange.value = { start, end };
143-
selected.value = newSelected;
170+
dispatch({ kind: 'set-selections', value: newSelected, reason: 'row shift+clicked' });
144171
focusedIndex.value = index;
145172
break;
146173
}
@@ -182,7 +209,7 @@ function useRowInteractions(update, selected, containerRef) {
182209
}
183210

184211
lastShiftRange.value = { start, end };
185-
selected.value = newSelected;
212+
dispatch({ kind: 'set-selections', value: newSelected, reason: 'shift+up or shift+down pressed' });
186213

187214
if (anchorIndex.value === null) {
188215
anchorIndex.value = newIndex;
@@ -191,7 +218,7 @@ function useRowInteractions(update, selected, containerRef) {
191218
}
192219
case 'up':
193220
case 'down': {
194-
selected.value = new Set([newIndex]);
221+
dispatch({ kind: 'set-selections', value: new Set([newIndex]), reason: 'up or down pressed without modifier' });
195222
anchorIndex.value = newIndex;
196223
lastShiftRange.value = { start: null, end: null };
197224
break;
@@ -218,7 +245,11 @@ function useRowInteractions(update, selected, containerRef) {
218245
if (event.target !== document.body) return;
219246
switch (intention) {
220247
case 'escape': {
221-
update((prev) => (prev.size > 0 ? new Set([]) : prev), 'escape key pressed');
248+
dispatch({
249+
kind: 'set-selections',
250+
value: selected.value.size > 0 ? new Set([]) : selected.value,
251+
reason: 'escape key pressed',
252+
});
222253
return true;
223254
}
224255
}

0 commit comments

Comments
 (0)