Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions special-pages/pages/history/app/HistoryProvider.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { h, createContext } from 'preact';
import { useContext } from 'preact/hooks';
import { useSignalEffect } from '@preact/signals';
import { paramsToQuery } from './history.service.js';
import { paramsToQuery, toRange } from './history.service.js';
import { OVERSCAN_AMOUNT } from './constants.js';
import { usePlatformName } from './types.js';
import { eventToTarget } from '../../../shared/handlers.js';
Expand Down Expand Up @@ -75,12 +75,17 @@ export function HistoryServiceProvider({ service, initial, children }) {
if (btn?.dataset.deleteRange) {
event.stopImmediatePropagation();
event.preventDefault();
return confirm(`todo: delete range for ${btn.dataset.deleteRange}`);
const range = toRange(btn.value);
if (range) {
// eslint-disable-next-line promise/prefer-await-to-then
service.deleteRange(range).catch(console.error);
}
}
if (btn?.dataset.deleteAll) {
event.stopImmediatePropagation();
event.preventDefault();
return confirm(`todo: delete all`);
// eslint-disable-next-line promise/prefer-await-to-then
service.deleteRange('all').catch(console.error);
}
} else if (anchor) {
const url = anchor.dataset.url;
Expand Down
13 changes: 10 additions & 3 deletions special-pages/pages/history/app/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ export function App() {
const containerRef = useRef(/** @type {HTMLElement|null} */ (null));
const { initial, service } = useHistory();

// NOTE: These states will get extracted out later, once I know all the use-cases
const ranges = useSignal(initial.ranges.ranges);
const term = useSignal('term' in initial.query.info.query ? initial.query.info.query.term : '');
const results = useSignal({
items: initial.query.results,
heights: generateHeights(initial.query.results),
});

const term = useSignal('term' in initial.query.info.query ? initial.query.info.query.term : '');

useSignalEffect(() => {
const unsub = service.onResults((data) => {
batch(() => {
Expand All @@ -39,8 +40,14 @@ export function App() {
};
});
});

// Subscribe to changes in the 'ranges' data and reflect the updates into the UI
const unsubRanges = service.onRanges((data) => {
ranges.value = data.ranges;
});
return () => {
unsub();
unsubRanges();
};
});

Expand All @@ -56,7 +63,7 @@ export function App() {
<Header />
</header>
<aside class={styles.aside}>
<Sidebar ranges={initial.ranges.ranges} />
<Sidebar ranges={ranges} />
</aside>
<main class={styles.main} ref={containerRef} data-main-scroller data-term={term}>
<Results results={results} />
Expand Down
4 changes: 3 additions & 1 deletion special-pages/pages/history/app/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@ import { h } from 'preact';
import { useComputed } from '@preact/signals';
import { SearchForm, useSearchContext } from './SearchForm.js';
import { Trash } from '../icons/Trash.js';
import { useTypedTranslation } from '../types.js';

/**
*/
export function Header() {
const { t } = useTypedTranslation();
const search = useSearchContext();
const term = useComputed(() => search.value.term);
return (
<div class={styles.root}>
<div class={styles.controls}>
<button class={styles.largeButton} data-delete-all>
<span>Delete All</span>
<span>{t('delete_all')}</span>
<Trash />
</button>
</div>
Expand Down
18 changes: 18 additions & 0 deletions special-pages/pages/history/app/components/Results.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { h } from 'preact';
import cn from 'classnames';
import { OVERSCAN_AMOUNT } from '../constants.js';
import { Item } from './Item.js';
import styles from './VirtualizedList.module.css';
import { VisibleItems } from './VirtualizedList.js';
import { useTypedTranslation } from '../types.js';

/**
* @param {object} props
* @param {import("@preact/signals").Signal<import("./App.jsx").Results>} props.results
*/
export function Results({ results }) {
if (results.value.items.length === 0) {
return <Empty />;
}
const totalHeight = results.value.heights.reduce((acc, item) => acc + item, 0);
return (
<ul class={styles.container} style={{ height: totalHeight + 'px' }}>
Expand Down Expand Up @@ -36,3 +41,16 @@ export function Results({ results }) {
</ul>
);
}

/**
* Empty state component displayed when no results are available
*/
function Empty() {
const { t } = useTypedTranslation();
return (
<div class={cn(styles.emptyState, styles.emptyStateOffset)}>
<img src="icons/clock.svg" width={128} height={96} alt="" class={styles.emptyStateImage} />
<h2 class={styles.emptyTitle}>{t('empty_title')}</h2>
</div>
);
}
52 changes: 26 additions & 26 deletions special-pages/pages/history/app/components/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import { useTypedTranslationWith } from '../../../new-tab/app/types.js';

/**
* @import json from "../strings.json"
* @typedef {import('../../types/history.js').Range} Range
*/

/** @type {Record<import('../../types/history.js').Range, string>} */
/** @type {Record<Range, string>} */
const iconMap = {
all: 'icons/all.svg',
today: 'icons/today.svg',
Expand All @@ -23,11 +24,10 @@ const iconMap = {
friday: 'icons/day.svg',
saturday: 'icons/day.svg',
sunday: 'icons/day.svg',
recentlyOpened: 'icons/closed.svg',
older: 'icons/older.svg',
};

/** @type {Record<import('../../types/history.js').Range, (t: (s: keyof json) => string) => string>} */
/** @type {Record<Range, (t: (s: keyof json) => string) => string>} */
const titleMap = {
all: (t) => t('range_all'),
today: (t) => t('range_today'),
Expand All @@ -39,33 +39,27 @@ const titleMap = {
friday: (t) => t('range_friday'),
saturday: (t) => t('range_saturday'),
sunday: (t) => t('range_sunday'),
recentlyOpened: (t) => t('range_recentlyOpened'),
older: (t) => t('range_older'),
};

/**
* Renders a sidebar navigation component with links based on the provided ranges.
*
* @param {Object} props - The properties object.
* @param {import('../../types/history.js').Range[]} props.ranges - An array of range values used to generate navigation links.
* @param {import("@preact/signals").Signal<Range[]>} props.ranges - An array of range values used to generate navigation links.
*/
export function Sidebar({ ranges }) {
const { t } = useTypedTranslation();
const search = useSearchContext();
const current = useComputed(() => search.value.range);
const others = ranges.filter((x) => x === 'recentlyOpened');
const main = ranges.filter((x) => x !== 'recentlyOpened');
return (
<div class={styles.stack}>
<h1 class={styles.pageTitle}>{t('page_title')}</h1>
<nav class={styles.nav}>
{main.map((range) => {
{ranges.value.map((range) => {
return <Item range={range} key={range} current={current} title={titleMap[range](t)} />;
})}
</nav>
{others.map((range) => {
return <Item range={range} key={range} current={current} title={titleMap[range](t)} />;
})}
</div>
);
}
Expand All @@ -74,16 +68,16 @@ export function Sidebar({ ranges }) {
* Renders an item component with additional properties and functionality.
*
* @param {Object} props
* @param {import('../../types/history.js').Range} props.range The range value used for filtering and identification.
* @param {Range} props.range The range value used for filtering and identification.
* @param {string} props.title The title or label of the item.
* @param {import("@preact/signals").Signal<import('../../types/history.js').Range|null>} props.current The current state object used to determine active item styling.
* @param {import("@preact/signals").Signal<Range|null>} props.current The current state object used to determine active item styling.
*/
function Item({ range, title, current }) {
const { t } = useTypedTranslationWith(/** @type {json} */ ({}));
const label = (() => {
const [linkLabel, deleteLabel] = (() => {
switch (range) {
case 'all':
return t('show_history_all');
return [t('show_history_all'), t('delete_history_all')];
case 'today':
case 'yesterday':
case 'monday':
Expand All @@ -93,22 +87,28 @@ function Item({ range, title, current }) {
case 'friday':
case 'saturday':
case 'sunday':
return t('show_history_for', { range });
return [t('show_history_for', { range }), t('delete_history_for', { range })];
case 'older':
return t('show_history_older');
case 'recentlyOpened':
return t('show_history_closed');
return [t('show_history_older'), t('delete_history_older')];
}
})();
return (
<a href="#" aria-label={label} data-filter={range} class={cn(styles.item, current.value === range && styles.active)}>
<span class={styles.icon}>
<img src={iconMap[range]} />
</span>
{title}
<button class={styles.delete} data-delete-range={range}>
<div class={styles.item}>
<a
href="#"
aria-label={linkLabel}
data-filter={range}
class={cn(styles.link, current.value === range && styles.active)}
tabindex={0}
>
<span class={styles.icon}>
<img src={iconMap[range]} />
</span>
{title}
</a>
<button class={styles.delete} data-delete-range={range} aria-label={deleteLabel} tabindex={0} value={range}>
<Trash />
</button>
</a>
</div>
);
}
30 changes: 19 additions & 11 deletions special-pages/pages/history/app/components/Sidebar.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,8 @@
}
.nav {}
.item {
height: 40px;
position: relative;
display: flex;
align-items: center;
border-radius: 8px;
padding-left: 16px;
text-decoration: none;
font-weight: 510;
color: var(--history-text-normal);
gap: 6px;

&:hover, &:focus-visible {
background: var(--color-black-at-6);
Expand All @@ -34,6 +26,17 @@
}
}
}
.link {
height: 40px;
display: flex;
align-items: center;
border-radius: 8px;
padding-left: 16px;
text-decoration: none;
font-weight: 510;
color: var(--history-text-normal);
gap: 6px;
}

.delete {
height: 40px;
Expand All @@ -49,16 +52,18 @@
border-bottom-left-radius: 0;
background: transparent;
border: none;
cursor: pointer;
opacity: 0;
visibility: hidden;
color: inherit;

&:hover {
&:hover, &:focus-visible {
background: var(--color-black-at-9);
opacity: 1;
}
&:active {
background: var(--color-black-at-12);
}

[data-theme="dark"] & {
&:hover {
background: var(--color-white-at-9);
Expand All @@ -70,7 +75,10 @@

.item:hover & {
opacity: 1;
visibility: visible;
}

.link:focus-visible + & {
opacity: 1;
}

svg path {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,27 @@
position: absolute;
padding: 0;
margin: 0;
}

.emptyState {
width: 100%;
height: 100%;
text-align: center;
color: var(--history-text-normal)
}

.emptyStateOffset {
padding-top: var(--sp-32);
}

.emptyStateImage {
width: 128px;
height: 96px;
display: inline-block;
}

.emptyTitle {
font-size: var(--title-3-em-font-size);
font-weight: var(--title-3-em-font-weight);
line-height: var(--title-3-em-line-height);
}
21 changes: 21 additions & 0 deletions special-pages/pages/history/app/history.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,27 @@ Response, note: always return the same query I sent:
}
```

### `deleteRange`
- Sent to delete a range as displayed in the sidebar.
- Parameters: {@link "History Messages".DeleteRangeParams}
- If the user confirms, respond with `{ action: 'delete' }`
- otherwise `{ action: 'none' }`
- Response: {@link "History Messages".DeleteRangeResponse}

**params**
```json
{
"range": "today"
}
```

**response**
```json
{
"action": "delete"
}
```

## Notifications

### `open`
Expand Down
Loading
Loading