Skip to content

Commit 6a8c868

Browse files
committed
history: support deleting sidebar time ranges
1 parent 2ad8273 commit 6a8c868

21 files changed

+383
-76
lines changed

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { h, createContext } from 'preact';
22
import { useContext } from 'preact/hooks';
33
import { useSignalEffect } from '@preact/signals';
4-
import { paramsToQuery } from './history.service.js';
4+
import { paramsToQuery, toRange } from './history.service.js';
55
import { OVERSCAN_AMOUNT } from './constants.js';
66
import { usePlatformName } from './types.js';
77
import { eventToTarget } from '../../../shared/handlers.js';
@@ -75,12 +75,17 @@ export function HistoryServiceProvider({ service, initial, children }) {
7575
if (btn?.dataset.deleteRange) {
7676
event.stopImmediatePropagation();
7777
event.preventDefault();
78-
return confirm(`todo: delete range for ${btn.dataset.deleteRange}`);
78+
const range = toRange(btn.value);
79+
if (range) {
80+
// eslint-disable-next-line promise/prefer-await-to-then
81+
service.deleteRange(range).catch(console.error);
82+
}
7983
}
8084
if (btn?.dataset.deleteAll) {
8185
event.stopImmediatePropagation();
8286
event.preventDefault();
83-
return confirm(`todo: delete all`);
87+
// eslint-disable-next-line promise/prefer-await-to-then
88+
service.deleteRange('all').catch(console.error);
8489
}
8590
} else if (anchor) {
8691
const url = anchor.dataset.url;

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

+10-3
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ export function App() {
2020
const containerRef = useRef(/** @type {HTMLElement|null} */ (null));
2121
const { initial, service } = useHistory();
2222

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 : '');
2326
const results = useSignal({
2427
items: initial.query.results,
2528
heights: generateHeights(initial.query.results),
2629
});
2730

28-
const term = useSignal('term' in initial.query.info.query ? initial.query.info.query.term : '');
29-
3031
useSignalEffect(() => {
3132
const unsub = service.onResults((data) => {
3233
batch(() => {
@@ -39,8 +40,14 @@ export function App() {
3940
};
4041
});
4142
});
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+
});
4248
return () => {
4349
unsub();
50+
unsubRanges();
4451
};
4552
});
4653

@@ -56,7 +63,7 @@ export function App() {
5663
<Header />
5764
</header>
5865
<aside class={styles.aside}>
59-
<Sidebar ranges={initial.ranges.ranges} />
66+
<Sidebar ranges={ranges} />
6067
</aside>
6168
<main class={styles.main} ref={containerRef} data-main-scroller data-term={term}>
6269
<Results results={results} />

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@ import { h } from 'preact';
33
import { useComputed } from '@preact/signals';
44
import { SearchForm, useSearchContext } from './SearchForm.js';
55
import { Trash } from '../icons/Trash.js';
6+
import { useTypedTranslation } from '../types.js';
67

78
/**
89
*/
910
export function Header() {
11+
const { t } = useTypedTranslation();
1012
const search = useSearchContext();
1113
const term = useComputed(() => search.value.term);
1214
return (
1315
<div class={styles.root}>
1416
<div class={styles.controls}>
1517
<button class={styles.largeButton} data-delete-all>
16-
<span>Delete All</span>
18+
<span>{t('delete_all')}</span>
1719
<Trash />
1820
</button>
1921
</div>

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

+18
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import { h } from 'preact';
2+
import cn from 'classnames';
23
import { OVERSCAN_AMOUNT } from '../constants.js';
34
import { Item } from './Item.js';
45
import styles from './VirtualizedList.module.css';
56
import { VisibleItems } from './VirtualizedList.js';
7+
import { useTypedTranslation } from '../types.js';
68

79
/**
810
* @param {object} props
911
* @param {import("@preact/signals").Signal<import("./App.jsx").Results>} props.results
1012
*/
1113
export function Results({ results }) {
14+
if (results.value.items.length === 0) {
15+
return <Empty />;
16+
}
1217
const totalHeight = results.value.heights.reduce((acc, item) => acc + item, 0);
1318
return (
1419
<ul class={styles.container} style={{ height: totalHeight + 'px' }}>
@@ -36,3 +41,16 @@ export function Results({ results }) {
3641
</ul>
3742
);
3843
}
44+
45+
/**
46+
* Empty state component displayed when no results are available
47+
*/
48+
function Empty() {
49+
const { t } = useTypedTranslation();
50+
return (
51+
<div class={cn(styles.emptyState, styles.emptyStateOffset)}>
52+
<img src="icons/clock.svg" width={128} height={96} alt="" class={styles.emptyStateImage} />
53+
<h2 class={styles.emptyTitle}>{t('empty_title')}</h2>
54+
</div>
55+
);
56+
}

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

+26-26
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import { useTypedTranslationWith } from '../../../new-tab/app/types.js';
99

1010
/**
1111
* @import json from "../strings.json"
12+
* @typedef {import('../../types/history.js').Range} Range
1213
*/
1314

14-
/** @type {Record<import('../../types/history.js').Range, string>} */
15+
/** @type {Record<Range, string>} */
1516
const iconMap = {
1617
all: 'icons/all.svg',
1718
today: 'icons/today.svg',
@@ -23,11 +24,10 @@ const iconMap = {
2324
friday: 'icons/day.svg',
2425
saturday: 'icons/day.svg',
2526
sunday: 'icons/day.svg',
26-
recentlyOpened: 'icons/closed.svg',
2727
older: 'icons/older.svg',
2828
};
2929

30-
/** @type {Record<import('../../types/history.js').Range, (t: (s: keyof json) => string) => string>} */
30+
/** @type {Record<Range, (t: (s: keyof json) => string) => string>} */
3131
const titleMap = {
3232
all: (t) => t('range_all'),
3333
today: (t) => t('range_today'),
@@ -39,33 +39,27 @@ const titleMap = {
3939
friday: (t) => t('range_friday'),
4040
saturday: (t) => t('range_saturday'),
4141
sunday: (t) => t('range_sunday'),
42-
recentlyOpened: (t) => t('range_recentlyOpened'),
4342
older: (t) => t('range_older'),
4443
};
4544

4645
/**
4746
* Renders a sidebar navigation component with links based on the provided ranges.
4847
*
4948
* @param {Object} props - The properties object.
50-
* @param {import('../../types/history.js').Range[]} props.ranges - An array of range values used to generate navigation links.
49+
* @param {import("@preact/signals").Signal<Range[]>} props.ranges - An array of range values used to generate navigation links.
5150
*/
5251
export function Sidebar({ ranges }) {
5352
const { t } = useTypedTranslation();
5453
const search = useSearchContext();
5554
const current = useComputed(() => search.value.range);
56-
const others = ranges.filter((x) => x === 'recentlyOpened');
57-
const main = ranges.filter((x) => x !== 'recentlyOpened');
5855
return (
5956
<div class={styles.stack}>
6057
<h1 class={styles.pageTitle}>{t('page_title')}</h1>
6158
<nav class={styles.nav}>
62-
{main.map((range) => {
59+
{ranges.value.map((range) => {
6360
return <Item range={range} key={range} current={current} title={titleMap[range](t)} />;
6461
})}
6562
</nav>
66-
{others.map((range) => {
67-
return <Item range={range} key={range} current={current} title={titleMap[range](t)} />;
68-
})}
6963
</div>
7064
);
7165
}
@@ -74,16 +68,16 @@ export function Sidebar({ ranges }) {
7468
* Renders an item component with additional properties and functionality.
7569
*
7670
* @param {Object} props
77-
* @param {import('../../types/history.js').Range} props.range The range value used for filtering and identification.
71+
* @param {Range} props.range The range value used for filtering and identification.
7872
* @param {string} props.title The title or label of the item.
79-
* @param {import("@preact/signals").Signal<import('../../types/history.js').Range|null>} props.current The current state object used to determine active item styling.
73+
* @param {import("@preact/signals").Signal<Range|null>} props.current The current state object used to determine active item styling.
8074
*/
8175
function Item({ range, title, current }) {
8276
const { t } = useTypedTranslationWith(/** @type {json} */ ({}));
83-
const label = (() => {
77+
const [linkLabel, deleteLabel] = (() => {
8478
switch (range) {
8579
case 'all':
86-
return t('show_history_all');
80+
return [t('show_history_all'), t('delete_history_all')];
8781
case 'today':
8882
case 'yesterday':
8983
case 'monday':
@@ -93,22 +87,28 @@ function Item({ range, title, current }) {
9387
case 'friday':
9488
case 'saturday':
9589
case 'sunday':
96-
return t('show_history_for', { range });
90+
return [t('show_history_for', { range }), t('delete_history_for', { range })];
9791
case 'older':
98-
return t('show_history_older');
99-
case 'recentlyOpened':
100-
return t('show_history_closed');
92+
return [t('show_history_older'), t('delete_history_older')];
10193
}
10294
})();
10395
return (
104-
<a href="#" aria-label={label} data-filter={range} class={cn(styles.item, current.value === range && styles.active)}>
105-
<span class={styles.icon}>
106-
<img src={iconMap[range]} />
107-
</span>
108-
{title}
109-
<button class={styles.delete} data-delete-range={range}>
96+
<div class={styles.item}>
97+
<a
98+
href="#"
99+
aria-label={linkLabel}
100+
data-filter={range}
101+
class={cn(styles.link, current.value === range && styles.active)}
102+
tabindex={0}
103+
>
104+
<span class={styles.icon}>
105+
<img src={iconMap[range]} />
106+
</span>
107+
{title}
108+
</a>
109+
<button class={styles.delete} data-delete-range={range} aria-label={deleteLabel} tabindex={0} value={range}>
110110
<Trash />
111111
</button>
112-
</a>
112+
</div>
113113
);
114114
}

special-pages/pages/history/app/components/Sidebar.module.css

+19-11
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,8 @@
1313
}
1414
.nav {}
1515
.item {
16-
height: 40px;
1716
position: relative;
18-
display: flex;
19-
align-items: center;
2017
border-radius: 8px;
21-
padding-left: 16px;
22-
text-decoration: none;
23-
font-weight: 510;
24-
color: var(--history-text-normal);
25-
gap: 6px;
2618

2719
&:hover, &:focus-visible {
2820
background: var(--color-black-at-6);
@@ -34,6 +26,17 @@
3426
}
3527
}
3628
}
29+
.link {
30+
height: 40px;
31+
display: flex;
32+
align-items: center;
33+
border-radius: 8px;
34+
padding-left: 16px;
35+
text-decoration: none;
36+
font-weight: 510;
37+
color: var(--history-text-normal);
38+
gap: 6px;
39+
}
3740

3841
.delete {
3942
height: 40px;
@@ -49,16 +52,18 @@
4952
border-bottom-left-radius: 0;
5053
background: transparent;
5154
border: none;
55+
cursor: pointer;
5256
opacity: 0;
53-
visibility: hidden;
5457
color: inherit;
5558

56-
&:hover {
59+
&:hover, &:focus-visible {
5760
background: var(--color-black-at-9);
61+
opacity: 1;
5862
}
5963
&:active {
6064
background: var(--color-black-at-12);
6165
}
66+
6267
[data-theme="dark"] & {
6368
&:hover {
6469
background: var(--color-white-at-9);
@@ -70,7 +75,10 @@
7075

7176
.item:hover & {
7277
opacity: 1;
73-
visibility: visible;
78+
}
79+
80+
.link:focus-visible + & {
81+
opacity: 1;
7482
}
7583

7684
svg path {

special-pages/pages/history/app/components/VirtualizedList.module.css

+23
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,27 @@
88
position: absolute;
99
padding: 0;
1010
margin: 0;
11+
}
12+
13+
.emptyState {
14+
width: 100%;
15+
height: 100%;
16+
text-align: center;
17+
color: var(--history-text-normal)
18+
}
19+
20+
.emptyStateOffset {
21+
padding-top: var(--sp-32);
22+
}
23+
24+
.emptyStateImage {
25+
width: 128px;
26+
height: 96px;
27+
display: inline-block;
28+
}
29+
30+
.emptyTitle {
31+
font-size: var(--title-3-em-font-size);
32+
font-weight: var(--title-3-em-font-weight);
33+
line-height: var(--title-3-em-line-height);
1134
}

special-pages/pages/history/app/history.md

+21
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,27 @@ Response, note: always return the same query I sent:
9797
}
9898
```
9999

100+
### `deleteRange`
101+
- Sent to delete a range as displayed in the sidebar.
102+
- Parameters: {@link "History Messages".DeleteRangeParams}
103+
- If the user confirms, respond with `{ action: 'delete' }`
104+
- otherwise `{ action: 'none' }`
105+
- Response: {@link "History Messages".DeleteRangeResponse}
106+
107+
**params**
108+
```json
109+
{
110+
"range": "today"
111+
}
112+
```
113+
114+
**response**
115+
```json
116+
{
117+
"action": "delete"
118+
}
119+
```
120+
100121
## Notifications
101122

102123
### `open`

0 commit comments

Comments
 (0)