-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1f07543
commit d140264
Showing
17 changed files
with
474 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { h, createContext } from 'preact'; | ||
import { useContext } from 'preact/hooks'; | ||
import { useSearchContext } from './app/components/SearchForm.js'; | ||
import { useSignalEffect } from '@preact/signals'; | ||
import { paramsToQuery } from './history.service.js'; | ||
import { OVERSCAN_AMOUNT } from './app/constants.js'; | ||
|
||
// Create the context | ||
const HistoryServiceContext = createContext({ | ||
service: /** @type {import("./history.service").HistoryService} */ ({}), | ||
initial: /** @type {import("./history.service").ServiceData} */ ({}), | ||
}); | ||
|
||
// Provider component | ||
/** | ||
* Provides a context for the history service, allowing dependent components to access it. | ||
* | ||
* @param {Object} props - The properties object for the HistoryServiceProvider component. | ||
* @param {import("./history.service").HistoryService} props.service - The history service instance to be provided through the context. | ||
* @param {import("./history.service").ServiceData} props.initial - The history service instance to be provided through the context. | ||
* @param {import("preact").ComponentChild} props.children - The child components that will consume the history service context. | ||
*/ | ||
export function HistoryServiceProvider({ service, initial, children }) { | ||
useSignalEffect(() => { | ||
let seen = 0; | ||
// Add a listener for the 'search-commit' event | ||
window.addEventListener('search-commit', (/** @type {CustomEvent<{params: URLSearchParams}>} */ event) => { | ||
const detail = event.detail; | ||
if (seen > 0) { | ||
if (detail && detail.params instanceof URLSearchParams) { | ||
const asQuery = paramsToQuery(detail.params); | ||
service.trigger(asQuery); | ||
} else { | ||
console.error('missing detail.params from search-commit event'); | ||
} | ||
} | ||
seen += 1; | ||
}); | ||
|
||
// Cleanup the event listener on unmount | ||
return () => { | ||
window.removeEventListener('search-commit', this); | ||
}; | ||
}); | ||
|
||
useSignalEffect(() => { | ||
function handler(/** @type {CustomEvent<{start: number, end: number}>} */ event) { | ||
if (!service.query.data) throw new Error('unreachable'); | ||
const { end } = event.detail; | ||
const memory = service.query.data.results; | ||
if (memory.length - end < OVERSCAN_AMOUNT) { | ||
service.requestMore(); | ||
} | ||
} | ||
window.addEventListener('range-change', handler); | ||
return () => { | ||
window.removeEventListener('range-change', handler); | ||
}; | ||
}); | ||
return <HistoryServiceContext.Provider value={{ service, initial }}>{children}</HistoryServiceContext.Provider>; | ||
} | ||
|
||
// Hook for consuming the context | ||
export function useHistory() { | ||
const context = useContext(HistoryServiceContext); | ||
if (!context) { | ||
throw new Error('useHistoryService must be used within a HistoryServiceProvider'); | ||
} | ||
return context; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,22 @@ | ||
import { h } from 'preact'; | ||
import { VirtualizedHistoryList } from './VirtualizedHistoryList.js'; | ||
import { OVERSCAN_AMOUNT } from '../constants.js'; | ||
|
||
/** | ||
* @param {object} props | ||
* @param {import("@preact/signals").Signal<import("./App.jsx").Results>} props.results | ||
* @param {import("@preact/signals").Signal<string>} props.term | ||
* @param {any} props.containerRef | ||
*/ | ||
export function Results({ results, containerRef }) { | ||
export function Results({ results, term, containerRef }) { | ||
return ( | ||
<div> | ||
<VirtualizedHistoryList items={results.value.items} heights={results.value.heights} containerRef={containerRef} /> | ||
<VirtualizedHistoryList | ||
items={results.value.items} | ||
heights={results.value.heights} | ||
containerRef={containerRef} | ||
overscan={OVERSCAN_AMOUNT} | ||
/> | ||
</div> | ||
); | ||
} |
102 changes: 102 additions & 0 deletions
102
special-pages/pages/history/app/components/SearchForm.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import styles from './Header.module.css'; | ||
import { createContext, h } from 'preact'; | ||
import { useTypedTranslation } from '../types.js'; | ||
import { signal, useSignal, useSignalEffect } from '@preact/signals'; | ||
import { useContext } from 'preact/hooks'; | ||
|
||
/** | ||
* @param {object} props | ||
* @param {import("@preact/signals").Signal<string>} props.term | ||
*/ | ||
export function SearchForm({ term }) { | ||
const { t } = useTypedTranslation(); | ||
// const onInput = (event) => (term.value = event.currentTarget.value); | ||
return ( | ||
<form role="search"> | ||
<label> | ||
<span class="sr-only">{t('search_your_history')}</span> | ||
<input type="search" placeholder={t('search')} className={styles.searchInput} name="term" value={term} /> | ||
</label> | ||
</form> | ||
); | ||
} | ||
|
||
const SearchContext = createContext(signal({ term: '' })); | ||
|
||
/** | ||
* A custom hook to access the SearchContext. | ||
* | ||
* @returns {import('@preact/signals').Signal<{ term: string }>} The current search context value. | ||
*/ | ||
export function useSearchContext() { | ||
return useContext(SearchContext); | ||
} | ||
|
||
/** | ||
* A provider component that sets up the search context for its children. Allows access to and updates of the search term within the context. | ||
* | ||
* @param {Object} props - The props object for the component. | ||
* @param {import("preact").ComponentChild} props.children - The child components wrapped within the provider. | ||
* @param {string} [props.term=''] - The initial search term for the context. | ||
*/ | ||
export function SearchProvider({ children, term = '' }) { | ||
const searchState = useSignal({ term }); | ||
useSignalEffect(() => { | ||
const controller = new AbortController(); | ||
|
||
// @ts-expect-error - later | ||
window._accept = (v) => { | ||
searchState.value = { ...searchState.value, term: v }; | ||
}; | ||
|
||
document.addEventListener( | ||
'submit', | ||
(e) => { | ||
e.preventDefault(); | ||
console.log('re-issue search plz', [searchState.value.term]); | ||
}, | ||
{ signal: controller.signal }, | ||
); | ||
|
||
document.addEventListener( | ||
'input', | ||
(e) => { | ||
if (e.target instanceof HTMLInputElement) { | ||
searchState.value = { ...searchState.value, term: e.target.value }; | ||
} | ||
}, | ||
{ signal: controller.signal }, | ||
); | ||
|
||
return () => { | ||
controller.abort(); | ||
}; | ||
}); | ||
|
||
useSignalEffect(() => { | ||
let timer; | ||
const sub = searchState.subscribe((v) => { | ||
clearTimeout(timer); | ||
timer = setTimeout(() => { | ||
const url = new URL(window.location.href); | ||
url.searchParams.delete('q'); | ||
if (searchState.value.term) { | ||
console.log('update URL with term:', searchState.value.term); | ||
url.searchParams.set('q', searchState.value.term); | ||
window.history.replaceState(null, '', url.toString()); | ||
} else { | ||
const url = new URL(window.location.href); | ||
window.history.replaceState(null, '', url.toString()); | ||
} | ||
window.dispatchEvent(new CustomEvent('search-commit', { detail: { params: new URLSearchParams(url.searchParams) } })); | ||
}, 500); | ||
}); | ||
|
||
return () => { | ||
sub(); | ||
clearTimeout(timer); | ||
}; | ||
}); | ||
|
||
return <SearchContext.Provider value={searchState}>{children}</SearchContext.Provider>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const OVERSCAN_AMOUNT = 5; |
Oops, something went wrong.