mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-12 23:31:02 +00:00
815a523d1c
- The design prompt keeps the print rules and adds energy: calm keeps today's rules, lively and loud layer photos, tilt, set oversize display type and block colour. Type sizes come as numbers relative to the page, and the model answers eight silent questions and previews the cover twice before it proposes. - The type scale reaches 2.5 and a display tier sets a cover title up to 200 pt; existing books keep their sizes. - An overlap is severe only where more than half of a photo or a text is covered by accident; a title over a photo, a framed inset or a turned photo is layering. - The assistant may use every icon of the icon tab, checked by name, and every enum of the book tools says what its values mean.
114 lines
4.7 KiB
TypeScript
114 lines
4.7 KiB
TypeScript
import * as LucideIcons from 'lucide-react'
|
|
import { resolvePluginIcon } from '../shared/PluginIcon'
|
|
import type { LucideIcon } from 'lucide-react'
|
|
import { BOOK_DESIGN_ICONS } from '@trek/shared'
|
|
|
|
/**
|
|
* Every drawing in lucide, as a list a picker can show.
|
|
*
|
|
* ── Why the whole set, and why it costs nothing ──────────────────────────
|
|
*
|
|
* The obvious worry with a fourteen-hundred-icon library is the bundle, and it
|
|
* is worth being precise about why it does not apply. TREK already pulls lucide
|
|
* in whole: `SystemNoticeModal` and `PluginIcon` both index the namespace with a
|
|
* computed key, which no bundler can tree-shake, and both sit in the eager entry
|
|
* graph. The library ships either way, so enumerating it here adds names, not
|
|
* kilobytes — and it avoids the alternative, which would be fourteen hundred
|
|
* lazily-imported chunks that the export path could not wait for. Studio prints
|
|
* by serialising the live DOM in one synchronous pass; an icon that had not
|
|
* finished loading would print as a hole in the book.
|
|
*
|
|
* ── The alias problem ────────────────────────────────────────────────────
|
|
*
|
|
* lucide exports every icon three times — `Plane`, `PlaneIcon`, `LucidePlane` —
|
|
* so the namespace holds about forty-five hundred names for about fourteen
|
|
* hundred drawings. A picker built from the raw keys shows every glyph three
|
|
* times, and the search box then looks broken. Both alias shapes are dropped.
|
|
*/
|
|
|
|
const PASCAL_CASE = /^[A-Z][A-Za-z0-9]*$/
|
|
|
|
function buildNames(): string[] {
|
|
const all = LucideIcons as unknown as Record<string, unknown>
|
|
const names = Object.keys(all).filter(k => PASCAL_CASE.test(k))
|
|
const present = new Set(names)
|
|
|
|
return names
|
|
.filter(name => {
|
|
// `LucidePlane` is always an alias of `Plane`.
|
|
if (name.startsWith('Lucide') && present.has(name.slice(6))) return false
|
|
// `PlaneIcon` likewise — but only when the bare name really exists, so a
|
|
// drawing genuinely called something-Icon is not thrown away with them.
|
|
if (name.endsWith('Icon') && present.has(name.slice(0, -4))) return false
|
|
return true
|
|
})
|
|
.sort((a, b) => a.localeCompare(b))
|
|
}
|
|
|
|
/** Built once: the namespace cannot change while the tab is open. */
|
|
export const ICON_NAMES: string[] = buildNames()
|
|
|
|
/**
|
|
* What the tab opens on.
|
|
*
|
|
* Alphabetical order puts `AArrowDown`, `AArrowUp` and `ALargeSmall` in the
|
|
* first row, which is a poor answer to "what can I put on this page" in a book
|
|
* about a journey. These are the drawings a travel page actually reaches for,
|
|
* in the order somebody would look for them; the whole library is underneath.
|
|
*
|
|
* Filtered against the real export list, so a lucide version that renames one
|
|
* of them quietly drops it instead of drawing a fallback in the shelf.
|
|
*/
|
|
/*
|
|
* The shelf itself lives in @trek/shared (book-icons.ts), beside the whole
|
|
* list the book engine takes (BOOK_ICON_NAMES): the assistant may name any
|
|
* drawing a person could pick here, and iconLibrary.test.ts holds that list
|
|
* to ICON_NAMES.
|
|
*/
|
|
const FEATURED: readonly string[] = BOOK_DESIGN_ICONS
|
|
|
|
/** The starter shelf, minus anything this version of lucide does not have. */
|
|
export const FEATURED_ICONS: string[] = FEATURED.filter(name => ICON_NAMES.includes(name))
|
|
|
|
/**
|
|
* The words to match a search against.
|
|
*
|
|
* "mountainsnow" for MountainSnow as well as its parts, so both "mountain" and
|
|
* "snow" find it — searching an icon set is searching for a thing, and the
|
|
* thing's name is rarely the first word someone types.
|
|
*/
|
|
function searchable(name: string): string {
|
|
return `${name} ${name.replace(/([a-z0-9])([A-Z])/g, '$1 $2')}`.toLowerCase()
|
|
}
|
|
|
|
const SEARCH_INDEX: Record<string, string> = Object.fromEntries(
|
|
ICON_NAMES.map(name => [name, searchable(name)]),
|
|
)
|
|
|
|
export function searchIcons(query: string): string[] {
|
|
const q = query.trim().toLowerCase()
|
|
if (!q) return ICON_NAMES
|
|
// Every word has to appear, so "snow mountain" finds MountainSnow too.
|
|
const words = q.split(/\s+/)
|
|
return ICON_NAMES.filter(name => {
|
|
const haystack = SEARCH_INDEX[name]
|
|
return words.every(w => haystack.includes(w))
|
|
})
|
|
}
|
|
|
|
/** Spaced out for a tooltip: `MountainSnow` reads as "Mountain Snow". */
|
|
export function iconLabel(name: string): string {
|
|
return name.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
}
|
|
|
|
/**
|
|
* The component for a stored name.
|
|
*
|
|
* The same resolver the plugin system uses, so a book that outlives an icon
|
|
* behaves the way the rest of TREK does with a name it no longer knows: it
|
|
* draws the fallback rather than leaving a hole where a drawing was.
|
|
*/
|
|
export function iconComponent(name: string): LucideIcon {
|
|
return resolvePluginIcon(name)
|
|
}
|