Skip to content

Commit

Permalink
history: adding item selection (#1486)
Browse files Browse the repository at this point in the history
* history: adding item selection

* refactor handlers

* single row selection

* global handlers
  • Loading branch information
shakyShane authored Feb 13, 2025
1 parent 9b7a4a5 commit 8a043e3
Show file tree
Hide file tree
Showing 19 changed files with 833 additions and 480 deletions.
166 changes: 0 additions & 166 deletions special-pages/pages/history/app/HistoryProvider.js

This file was deleted.

54 changes: 7 additions & 47 deletions special-pages/pages/history/app/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,20 @@ import { h } from 'preact';
import styles from './App.module.css';
import { useEnv } from '../../../../shared/components/EnvironmentProvider.js';
import { Header } from './Header.js';
import { batch, useSignal, useSignalEffect } from '@preact/signals';
import { Results } from './Results.js';
import { useRef } from 'preact/hooks';
import { useHistory } from '../HistoryProvider.js';
import { generateHeights } from '../utils.js';
import { Sidebar } from './Sidebar.js';

/**
* @typedef {object} Results
* @property {import('../../types/history').HistoryItem[]} items
* @property {number[]} heights
*/
import { useGlobalState } from '../global-state/GlobalStateProvider.js';
import { useSelected } from '../global-state/SelectionProvider.js';
import { useGlobalHandlers } from '../global-state/HistoryServiceProvider.js';

export function App() {
const { isDarkMode } = useEnv();
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),
});

useSignalEffect(() => {
const unsub = service.onResults((data) => {
batch(() => {
if ('term' in data.info.query && data.info.query.term !== null) {
term.value = data.info.query.term;
}
results.value = {
items: data.results,
heights: generateHeights(data.results),
};
});
});

// 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();
};
});
const { ranges, term, results } = useGlobalState();
const selected = useSelected();

useSignalEffect(() => {
term.subscribe((t) => {
containerRef.current?.scrollTo(0, 0);
});
});
useGlobalHandlers();

return (
<div class={styles.layout} data-theme={isDarkMode ? 'dark' : 'light'}>
Expand All @@ -66,7 +26,7 @@ export function App() {
<Sidebar ranges={ranges} />
</aside>
<main class={styles.main} ref={containerRef} data-main-scroller data-term={term}>
<Results results={results} />
<Results results={results} selected={selected} />
</main>
</div>
);
Expand Down
17 changes: 17 additions & 0 deletions special-pages/pages/history/app/components/Empty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { h } from 'preact';
import { useTypedTranslation } from '../types.js';
import cn from 'classnames';
import styles from './VirtualizedList.module.css';

/**
* Empty state component displayed when no results are available
*/
export 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>
);
}
8 changes: 5 additions & 3 deletions special-pages/pages/history/app/components/Header.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import styles from './Header.module.css';
import { h } from 'preact';
import { useComputed } from '@preact/signals';
import { SearchForm, useSearchContext } from './SearchForm.js';
import { SearchForm } from './SearchForm.js';
import { Trash } from '../icons/Trash.js';
import { useTypedTranslation } from '../types.js';
import { useQueryContext } from '../global-state/QueryProvider.js';
import { BTN_ACTION_DELETE_ALL } from '../constants.js';

/**
*/
export function Header() {
const { t } = useTypedTranslation();
const search = useSearchContext();
const search = useQueryContext();
const term = useComputed(() => search.value.term);
return (
<div class={styles.root}>
<div class={styles.controls}>
<button class={styles.largeButton} data-delete-all>
<button class={styles.largeButton} data-action={BTN_ACTION_DELETE_ALL}>
<span>{t('delete_all')}</span>
<Trash />
</button>
Expand Down
18 changes: 13 additions & 5 deletions special-pages/pages/history/app/components/Item.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Fragment, h } from 'preact';
import styles from './Item.module.css';
import { Dots } from '../icons/dots.js';
import { useTypedTranslation } from '../types.js';
import { BTN_ACTION_ENTRIES_MENU, BTN_ACTION_TITLE_MENU } from '../constants.js';

export const Item = memo(
/**
Expand All @@ -19,31 +20,38 @@ export const Item = memo(
* @param {string} props.dateRelativeDay - The relative day information to display (shown when kind is equal to TITLE_KIND).
* @param {string} props.dateTimeOfDay - the time of day, like 11.00am.
* @param {number} props.index - original index
* @param {boolean} props.selected - whether this item is selected
*/
function Item({ id, url, domain, title, kind, dateRelativeDay, dateTimeOfDay, index }) {
function Item({ id, url, domain, title, kind, dateRelativeDay, dateTimeOfDay, index, selected }) {
const { t } = useTypedTranslation();
return (
<Fragment>
{kind === TITLE_KIND && (
<div class={styles.title} tabindex={0} data-section-title>
<div class={cn(styles.title, styles.hover)} data-section-title>
{dateRelativeDay}
<button
class={cn(styles.dots, styles.titleDots)}
data-title-menu
data-action={BTN_ACTION_TITLE_MENU}
value={dateRelativeDay}
aria-label={t('menu_sectionTitle', { relativeTime: dateRelativeDay })}
tabindex={0}
>
<Dots />
</button>
</div>
)}
<div class={cn(styles.row, kind === END_KIND && styles.last)} tabindex={0} data-history-entry={id}>
<div
class={cn(styles.row, styles.hover, kind === END_KIND && styles.last)}
data-history-entry={id}
data-index={index}
aria-selected={selected}
>
<a href={url} data-url={url} class={styles.entryLink}>
{title}
</a>
<span class={styles.domain}>{domain}</span>
<span class={styles.time}>{dateTimeOfDay}</span>
<button class={styles.dots} data-row-menu data-index={index} value={id}>
<button class={styles.dots} data-action={BTN_ACTION_ENTRIES_MENU} data-index={index} value={id} tabindex={0}>
<Dots />
</button>
</div>
Expand Down
Loading

0 comments on commit 8a043e3

Please sign in to comment.