-
Notifications
You must be signed in to change notification settings - Fork 0
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
9e76f5c
commit 8b355c1
Showing
21 changed files
with
661 additions
and
238 deletions.
There are no files selected for viewing
File renamed without changes.
Large diffs are not rendered by default.
Oops, something went wrong.
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,17 @@ | ||
/** @type {import("prettier").Config} */ | ||
export default { | ||
plugins: ['prettier-plugin-astro'], | ||
semi: false, | ||
tabWidth: 3, | ||
printWidth: 120, | ||
arrowParens: 'avoid', | ||
singleQuote: true, | ||
overrides: [ | ||
{ | ||
files: '*.astro', | ||
options: { | ||
parser: 'astro', | ||
}, | ||
}, | ||
], | ||
} |
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,4 +1,4 @@ | ||
{ | ||
"recommendations": ["astro-build.astro-vscode"], | ||
"unwantedRecommendations": [] | ||
"recommendations": ["astro-build.astro-vscode"], | ||
"unwantedRecommendations": [] | ||
} |
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,11 +1,11 @@ | ||
{ | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"command": "./node_modules/.bin/astro dev", | ||
"name": "Development server", | ||
"request": "launch", | ||
"type": "node-terminal" | ||
} | ||
] | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"command": "./node_modules/.bin/astro dev", | ||
"name": "Development server", | ||
"request": "launch", | ||
"type": "node-terminal" | ||
} | ||
] | ||
} |
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,8 +1,13 @@ | ||
import preact from '@astrojs/preact' | ||
import { defineConfig } from 'astro/config' | ||
|
||
import preact from '@astrojs/preact' | ||
import node from '@astrojs/node' | ||
|
||
// https://astro.build/config | ||
export default defineConfig({ | ||
integrations: [preact()], | ||
output: 'hybrid', | ||
adapter: node({ | ||
mode: 'standalone', | ||
}), | ||
}) |
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,111 +1,70 @@ | ||
import Fuse, { type Expression, type FuseResult } from 'fuse.js' | ||
import { useEffect, useMemo, useReducer } from 'preact/hooks' | ||
import { type FuseResult } from 'fuse.js' | ||
import { useQuery } from 'preact-fetching' | ||
import { useCallback, useEffect, useMemo, useReducer, useState } from 'preact/hooks' | ||
import type { Icon } from '../types/Icon.ts' | ||
import IconPanel from './IconPanel.tsx' | ||
|
||
import debounce from 'debounce' | ||
import './IconList.module.css' | ||
|
||
type Props = { | ||
icons: Icon[] | ||
items: FuseResult<Icon>[] | ||
query: string | ||
limit: number | ||
} | ||
|
||
const MAX_ENTRIES = 250 | ||
const MIN_QUERY_LENGTH = 1 | ||
|
||
type Encodeable = { toString(): string } | ||
|
||
function useQueryState<T extends Encodeable>(key: string, decode: (from: string) => T | null, defaultValue: T) { | ||
function useQueryState<T extends Encodeable>(key: string, defaultValue: T) { | ||
const state = useReducer((_: T, value: T) => { | ||
const url = new URL(window.location.href) | ||
url.searchParams.set(key, value.toString()) | ||
window.history.replaceState(value, value.toString(), url) | ||
return value | ||
}, defaultValue) | ||
|
||
// Load query params on client | ||
useEffect(() => { | ||
const query = new URLSearchParams(window.location.search) | ||
if (query.has(key)) { | ||
const decoded = decode(query.get(key)!) | ||
if (decoded !== null) state[1](decoded) | ||
} | ||
}, []) | ||
|
||
return state | ||
} | ||
|
||
function toExpression(query: string): string | Expression { | ||
const namespaceQueries = | ||
query | ||
.match(/@(["\^\$!\w]*)/g) | ||
?.map(it => it.slice(1)) | ||
?.filter(it => it.length) ?? [] | ||
|
||
const idQueries = query | ||
.replace(/@["\^\$!\w]*/g, '') | ||
.split(/\s+/) | ||
.filter(it => it.trim().length) | ||
|
||
return { | ||
$and: [...idQueries.map(search => ({ id: search })), ...namespaceQueries.map(search => ({ namespace: search }))], | ||
} | ||
type Page<T> = { | ||
items: T[] | ||
count: number | ||
total: number | ||
} | ||
|
||
export default function IconList({ icons }: Props) { | ||
const [query, setQuery] = useQueryState('q', it => it, '') | ||
const [size] = useQueryState('s', parseInt, MAX_ENTRIES) | ||
|
||
const unfiltered = useMemo( | ||
() => | ||
icons.map<FuseResult<Icon>>((item, refIndex) => ({ | ||
item, | ||
refIndex, | ||
})), | ||
[icons] | ||
) | ||
async function fetchItems(query: string, limit: number): Promise<Page<FuseResult<Icon>>> { | ||
const response = await fetch(`/browse.json?includeMatches=true&query=${query}&limit=${limit}`) | ||
if (!response.ok) throw new Error(response.statusText) | ||
return await response.json() | ||
} | ||
|
||
const fuse = useMemo( | ||
() => | ||
new Fuse(icons, { | ||
keys: [ | ||
{ | ||
name: 'id', | ||
weight: 10, | ||
}, | ||
{ | ||
name: 'namespace', | ||
weight: 1, | ||
}, | ||
], | ||
includeMatches: true, | ||
minMatchCharLength: MIN_QUERY_LENGTH, | ||
threshold: 0.25, | ||
useExtendedSearch: true, | ||
}), | ||
[icons] | ||
) | ||
export function useLazyQuery<T>(data: T | undefined | null, initialData?: T) { | ||
const [value, setValue] = useState(initialData) | ||
useEffect(() => { | ||
if (data) setValue(data) | ||
}, [data]) | ||
return value | ||
} | ||
|
||
const filtered = useMemo(() => { | ||
if (query.trim().length < MIN_QUERY_LENGTH) return unfiltered | ||
return fuse.search(toExpression(query)) | ||
}, [query, icons]) | ||
export default function IconList(initial: Props) { | ||
const [query, setQuery] = useQueryState('q', initial.query) | ||
const [size] = useQueryState('s', initial.limit) | ||
const setQueryDebounced = useMemo(() => debounce(setQuery, 250), [setQuery]) | ||
|
||
const sliced = useMemo(() => filtered.slice(0, size), [filtered, size]) | ||
const fetch = useCallback(() => fetchItems(query, size), [query, size]) | ||
const { data } = useQuery(`browse/${query}/${size}´`, fetch) | ||
const items = useLazyQuery(data?.items, initial.items) | ||
|
||
return ( | ||
<div> | ||
<input | ||
type='text' | ||
name='search' | ||
placeholder='Search...' | ||
type="text" | ||
name="search" | ||
placeholder="Search..." | ||
value={query} | ||
onInput={e => setQuery(e.currentTarget.value)} | ||
onInput={e => setQueryDebounced(e.currentTarget.value)} | ||
/> | ||
<ul> | ||
{sliced.map(icon => ( | ||
<IconPanel {...icon} key={icon.item.url} /> | ||
))} | ||
</ul> | ||
<ul>{items?.map(icon => <IconPanel {...icon} key={icon.item.url} />)}</ul> | ||
</div> | ||
) | ||
} |
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,52 @@ | ||
--- | ||
import hole from '../assets/hole.png' | ||
import Layout from '../layouts/Layout.astro' | ||
interface Props { | ||
title: string | ||
} | ||
const { title } = Astro.props | ||
--- | ||
|
||
<Layout title={title}> | ||
<main class="centered"> | ||
<section class="hole centered" style={{ backgroundImage: `url(${hole.src})` }}> | ||
<slot /> | ||
</section> | ||
</main> | ||
</Layout> | ||
|
||
<style scoped> | ||
main, | ||
.hole { | ||
height: calc(100dvh - 2em); | ||
} | ||
|
||
.centered { | ||
display: grid; | ||
place-content: center; | ||
} | ||
|
||
.hole :global(h1, h2) { | ||
margin: 0; | ||
text-align: center; | ||
} | ||
|
||
.hole :global(h1) { | ||
font-size: 7rem; | ||
} | ||
|
||
.hole :global(h2) { | ||
font-size: 1.5rem; | ||
} | ||
|
||
.hole { | ||
aspect-ratio: 4 / 3; | ||
max-width: 90dvw; | ||
|
||
background-position: center; | ||
background-size: contain; | ||
background-repeat: no-repeat; | ||
} | ||
</style> |
Oops, something went wrong.