Skip to content

Commit 8a043e3

Browse files
authored
history: adding item selection (#1486)
* history: adding item selection * refactor handlers * single row selection * global handlers
1 parent 9b7a4a5 commit 8a043e3

19 files changed

+833
-480
lines changed

special-pages/pages/history/app/HistoryProvider.js

-166
This file was deleted.

special-pages/pages/history/app/components/App.jsx

+7-47
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,20 @@ import { h } from 'preact';
22
import styles from './App.module.css';
33
import { useEnv } from '../../../../shared/components/EnvironmentProvider.js';
44
import { Header } from './Header.js';
5-
import { batch, useSignal, useSignalEffect } from '@preact/signals';
65
import { Results } from './Results.js';
76
import { useRef } from 'preact/hooks';
8-
import { useHistory } from '../HistoryProvider.js';
9-
import { generateHeights } from '../utils.js';
107
import { Sidebar } from './Sidebar.js';
11-
12-
/**
13-
* @typedef {object} Results
14-
* @property {import('../../types/history').HistoryItem[]} items
15-
* @property {number[]} heights
16-
*/
8+
import { useGlobalState } from '../global-state/GlobalStateProvider.js';
9+
import { useSelected } from '../global-state/SelectionProvider.js';
10+
import { useGlobalHandlers } from '../global-state/HistoryServiceProvider.js';
1711

1812
export function App() {
1913
const { isDarkMode } = useEnv();
2014
const containerRef = useRef(/** @type {HTMLElement|null} */ (null));
21-
const { initial, service } = useHistory();
22-
23-
// NOTE: These states will get extracted out later, once I know all the use-cases
24-
const ranges = useSignal(initial.ranges.ranges);
25-
const term = useSignal('term' in initial.query.info.query ? initial.query.info.query.term : '');
26-
const results = useSignal({
27-
items: initial.query.results,
28-
heights: generateHeights(initial.query.results),
29-
});
30-
31-
useSignalEffect(() => {
32-
const unsub = service.onResults((data) => {
33-
batch(() => {
34-
if ('term' in data.info.query && data.info.query.term !== null) {
35-
term.value = data.info.query.term;
36-
}
37-
results.value = {
38-
items: data.results,
39-
heights: generateHeights(data.results),
40-
};
41-
});
42-
});
43-
44-
// Subscribe to changes in the 'ranges' data and reflect the updates into the UI
45-
const unsubRanges = service.onRanges((data) => {
46-
ranges.value = data.ranges;
47-
});
48-
return () => {
49-
unsub();
50-
unsubRanges();
51-
};
52-
});
15+
const { ranges, term, results } = useGlobalState();
16+
const selected = useSelected();
5317

54-
useSignalEffect(() => {
55-
term.subscribe((t) => {
56-
containerRef.current?.scrollTo(0, 0);
57-
});
58-
});
18+
useGlobalHandlers();
5919

6020
return (
6121
<div class={styles.layout} data-theme={isDarkMode ? 'dark' : 'light'}>
@@ -66,7 +26,7 @@ export function App() {
6626
<Sidebar ranges={ranges} />
6727
</aside>
6828
<main class={styles.main} ref={containerRef} data-main-scroller data-term={term}>
69-
<Results results={results} />
29+
<Results results={results} selected={selected} />
7030
</main>
7131
</div>
7232
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { h } from 'preact';
2+
import { useTypedTranslation } from '../types.js';
3+
import cn from 'classnames';
4+
import styles from './VirtualizedList.module.css';
5+
6+
/**
7+
* Empty state component displayed when no results are available
8+
*/
9+
export function Empty() {
10+
const { t } = useTypedTranslation();
11+
return (
12+
<div class={cn(styles.emptyState, styles.emptyStateOffset)}>
13+
<img src="icons/clock.svg" width={128} height={96} alt="" class={styles.emptyStateImage} />
14+
<h2 class={styles.emptyTitle}>{t('empty_title')}</h2>
15+
</div>
16+
);
17+
}

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import styles from './Header.module.css';
22
import { h } from 'preact';
33
import { useComputed } from '@preact/signals';
4-
import { SearchForm, useSearchContext } from './SearchForm.js';
4+
import { SearchForm } from './SearchForm.js';
55
import { Trash } from '../icons/Trash.js';
66
import { useTypedTranslation } from '../types.js';
7+
import { useQueryContext } from '../global-state/QueryProvider.js';
8+
import { BTN_ACTION_DELETE_ALL } from '../constants.js';
79

810
/**
911
*/
1012
export function Header() {
1113
const { t } = useTypedTranslation();
12-
const search = useSearchContext();
14+
const search = useQueryContext();
1315
const term = useComputed(() => search.value.term);
1416
return (
1517
<div class={styles.root}>
1618
<div class={styles.controls}>
17-
<button class={styles.largeButton} data-delete-all>
19+
<button class={styles.largeButton} data-action={BTN_ACTION_DELETE_ALL}>
1820
<span>{t('delete_all')}</span>
1921
<Trash />
2022
</button>

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

+13-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Fragment, h } from 'preact';
55
import styles from './Item.module.css';
66
import { Dots } from '../icons/dots.js';
77
import { useTypedTranslation } from '../types.js';
8+
import { BTN_ACTION_ENTRIES_MENU, BTN_ACTION_TITLE_MENU } from '../constants.js';
89

910
export const Item = memo(
1011
/**
@@ -19,31 +20,38 @@ export const Item = memo(
1920
* @param {string} props.dateRelativeDay - The relative day information to display (shown when kind is equal to TITLE_KIND).
2021
* @param {string} props.dateTimeOfDay - the time of day, like 11.00am.
2122
* @param {number} props.index - original index
23+
* @param {boolean} props.selected - whether this item is selected
2224
*/
23-
function Item({ id, url, domain, title, kind, dateRelativeDay, dateTimeOfDay, index }) {
25+
function Item({ id, url, domain, title, kind, dateRelativeDay, dateTimeOfDay, index, selected }) {
2426
const { t } = useTypedTranslation();
2527
return (
2628
<Fragment>
2729
{kind === TITLE_KIND && (
28-
<div class={styles.title} tabindex={0} data-section-title>
30+
<div class={cn(styles.title, styles.hover)} data-section-title>
2931
{dateRelativeDay}
3032
<button
3133
class={cn(styles.dots, styles.titleDots)}
32-
data-title-menu
34+
data-action={BTN_ACTION_TITLE_MENU}
3335
value={dateRelativeDay}
3436
aria-label={t('menu_sectionTitle', { relativeTime: dateRelativeDay })}
37+
tabindex={0}
3538
>
3639
<Dots />
3740
</button>
3841
</div>
3942
)}
40-
<div class={cn(styles.row, kind === END_KIND && styles.last)} tabindex={0} data-history-entry={id}>
43+
<div
44+
class={cn(styles.row, styles.hover, kind === END_KIND && styles.last)}
45+
data-history-entry={id}
46+
data-index={index}
47+
aria-selected={selected}
48+
>
4149
<a href={url} data-url={url} class={styles.entryLink}>
4250
{title}
4351
</a>
4452
<span class={styles.domain}>{domain}</span>
4553
<span class={styles.time}>{dateTimeOfDay}</span>
46-
<button class={styles.dots} data-row-menu data-index={index} value={id}>
54+
<button class={styles.dots} data-action={BTN_ACTION_ENTRIES_MENU} data-index={index} value={id} tabindex={0}>
4755
<Dots />
4856
</button>
4957
</div>

0 commit comments

Comments
 (0)