Skip to content

Commit 01d27df

Browse files
authored
feat(site): Search (#34)
1 parent 2aa348b commit 01d27df

File tree

9 files changed

+168
-12
lines changed

9 files changed

+168
-12
lines changed

addons/plenticons/plugin.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
name="plenticons"
44
description="A library of icons for your custom nodes!"
55
author="Tamás Gálffy"
6-
version="1.6.0"
6+
version="1.7.0"
77
script="plenticons.gd"

site/bun.lockb

1.13 KB
Binary file not shown.

site/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13+
"minimatch": "^10.1.1",
1314
"react": "^19.1.0",
1415
"react-dom": "^19.1.0"
1516
},

site/src/App.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { IconCardContainer } from "./IconCardContainer"
33
import { DefaultVariant, type Category } from "./plenticons"
44
import { Toolbar } from "./Toolbar"
55
import { IconModal } from "./IconModal"
6+
import { SearchBar } from "./SearchBar"
67

78
function Header() {
89
return (
@@ -32,12 +33,19 @@ interface ModalState {
3233
function App() {
3334
const [variant, setVariant] = useState(DefaultVariant);
3435
const [modal, setModal] = useState<ModalState | undefined>(undefined);
36+
const [search, setSearch] = useState<string | undefined>(undefined);
3537

3638
return (
3739
<>
3840
<Header/>
39-
<Toolbar onVariant={setVariant}/>
40-
<IconCardContainer variant={variant} onIcon={(category: Category, name: string) => setModal({ category, name })} />
41+
<Toolbar onVariant={setVariant}>
42+
<SearchBar onChange={setSearch} />
43+
</Toolbar>
44+
<IconCardContainer
45+
variant={variant}
46+
search={search}
47+
onIcon={(category: Category, name: string) => setModal({ category, name })}
48+
/>
4149
{ modal && <IconModal category={modal.category} name={modal.name} variant={variant} onClose={() => setModal(undefined)}/>}
4250
</>
4351
)

site/src/IconCardContainer.tsx

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
11
import { IconCard } from "./IconCard";
22
import { manifest, unslug, type Category, type Variant } from "./plenticons";
3+
import { synonyms } from "./synonyms";
34

4-
export function IconCardContainer(props: {variant: Variant, onIcon?: (category: Category, name: string) => void}) {
5+
export interface IconCardContainerProps {
6+
variant: Variant,
7+
search?: string,
8+
onIcon?: (category: Category, name: string) => void,
9+
}
10+
11+
export function IconCardContainer(props: IconCardContainerProps) {
512
return (
613
<div className="icon-card-container">
714
{Object.entries(manifest.icons).map(([category, icons]) => (
815
<div key={category}>
916
<h3>{unslug(category)}</h3>
10-
{icons.map(icon =>
11-
<IconCard
12-
key={`${category}/${icon}`}
13-
category={category} name={icon} variant={props.variant}
14-
onClick={ () => props.onIcon && props.onIcon(category, icon)}
15-
/>
16-
)}
17+
{icons
18+
.filter(icon => !props.search || searchString(category, icon).includes(props.search))
19+
.map(icon =>
20+
<IconCard
21+
key={`${category}/${icon}`}
22+
category={category} name={icon} variant={props.variant}
23+
onClick={ () => props.onIcon && props.onIcon(category, icon)}
24+
/>
25+
)}
1726
</div>
1827
)) }
1928
</div>
2029
)
2130
}
31+
32+
function searchString(category: string, name: string): string {
33+
return [
34+
unslug(category),
35+
name,
36+
...(synonyms[`${category}/${name}`] ?? [])
37+
].join(" ").toLowerCase();
38+
}

site/src/SearchBar.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export interface SearchBarProps {
2+
onChange?: (value: string) => void
3+
}
4+
5+
export function SearchBar(props: SearchBarProps) {
6+
return <div className="searchbar">
7+
<img src="./icons/objects/magnifying-glass-gray.svg" alt="search"></img>
8+
<input
9+
type="text"
10+
placeholder="Search icons"
11+
onChange={e => props.onChange?.call(undefined, e.target.value)}
12+
/>
13+
</div>;
14+
}

site/src/Toolbar.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
import type { ReactNode } from "react";
12
import { icons, type Variant } from "./plenticons";
23
import { VariantPicker } from "./VariantPicker";
34

4-
export function Toolbar(props: { onVariant: (variant: Variant) => void}) {
5+
export interface ToolbarProps {
6+
onVariant: (variant: Variant) => void,
7+
children: ReactNode[]
8+
}
9+
10+
export function Toolbar(props: ToolbarProps) {
511
return (
612
<div className="toolbar">
13+
{props.children}
14+
715
<VariantPicker onVariant={props.onVariant}/>
816
<a href="https://discord.gg/xWGh4GskG5" target="_blank">
917
<button><img src={icons.discord} />Discord</button>

site/src/index.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,3 +230,16 @@ img.icon-inline {
230230
text-align: right;
231231
position: relative;
232232
}
233+
234+
.searchbar {
235+
display: flex;
236+
align-items: center;
237+
padding: 2pt;
238+
margin: 2pt;
239+
width: 100%;
240+
background-color: var(--godot-dark);
241+
}
242+
243+
.searchbar > input {
244+
flex-grow: 1;
245+
}

site/src/synonyms.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { Minimatch } from "minimatch";
2+
import { manifest, type Manifest } from "./plenticons";
3+
4+
export type Synonyms = Record<string, string[]>;
5+
6+
function makeSynonyms(manifest: Manifest, spec: [string | string[], string | string[]][]): Synonyms {
7+
const result: Synonyms = {}
8+
const iconNames = Object.entries(manifest.icons)
9+
.flatMap(([category, icons]) => icons.map(icon => `${category}/${icon}`))
10+
11+
for (const entry of spec) {
12+
const patterns = typeof entry[0] == "string" ? [entry[0]] : entry[0];
13+
const synonyms = typeof entry[1] == "string" ? [entry[1]] : entry[1];
14+
15+
for (const pattern of patterns) {
16+
// Find matching icons
17+
const matcher = new Minimatch(pattern);
18+
const matches = iconNames.filter(it => matcher.match(it))
19+
20+
// Warn if pattern doesn't match - probably spec error
21+
if (matches.length == 0)
22+
console.warn(`Pattern "${pattern}" didn't match any icons!`)
23+
24+
// Add synonyms to each icon
25+
for (const icon of matches) {
26+
result[icon] = [...(result[icon] ?? []), ...synonyms]
27+
}
28+
}
29+
}
30+
31+
return result
32+
}
33+
34+
export const synonyms = makeSynonyms(manifest, [
35+
// 2D
36+
["2d/checkmark", [ "tick", "tickmark", "ok", "done", "yes" ]],
37+
["2d/circle", [ "ok", "round" ]],
38+
["2d/cross", [ "no", "deny", "ban", "cancel" ]],
39+
["2d/square", [ "cube", "box" ]],
40+
41+
["2d/triangle*", ["cone"]],
42+
["*/die*", [ "dice", "random", "chance", "throwing" ]],
43+
["2d/*-chevron-*", [ "arrow", "direction" ]],
44+
45+
["2d/plus", [ "add" ]],
46+
["2d/spark*", [ "star" ]],
47+
48+
// 3D
49+
["3d/cone", [ "triangle" ]],
50+
["3d/cube", [ "square", "box" ]],
51+
["3d/sphere", [ "circle", "round" ]],
52+
53+
// Creatures
54+
["creatures/bug", [ "insect", "roach", "tick" ]],
55+
["creatures/demon", [ "monster", "enemy", "foe", "evil" ]],
56+
["creatures/eye-closed", [ "invisible", "blind", "shut" ]],
57+
[["creatures/eye-hollow", "creatures/eye"], [ "see", "watch", "visible", "open" ]],
58+
59+
["creatures/heart*", [ "life", "lives", "health", "love" ]],
60+
61+
["creatures/person", [ "human", "man", "woman", "guy", "gal", "boy", "girl" ]],
62+
63+
// Objects
64+
["objects/ammo", [ "shells" ]],
65+
["objects/axe", [ "hatchet", "timber", "lumber", "wood" ]],
66+
["objects/bell", [ "notification", "alert" ]],
67+
[["objects/bill", "objects/coins"], [ "money", "currency", "dollars", "euros" ]],
68+
["objects/cd", [ "dvd" ]],
69+
["objects/chain", [ "link", "anchor", "reference" ]],
70+
["objects/chest", [ "box", "treasure", "booty" ]],
71+
["objects/clock", [ "watch", "time" ]],
72+
["objects/floppy", [ "save" ]],
73+
["objects/globe", [ "planet", "earth", "global", "internet", "web", "www" ]],
74+
["objects/gun", [ "weapon", "arm" ]],
75+
["objects/hammer", [ "build" ]],
76+
["objects/hourglass", [ "time" ]],
77+
["objects/key", [ "password", "open", "access" ]],
78+
["objects/lightbulb", [ "idea", "new", "create" ]],
79+
["objects/lightning", [ "fast", "speed", "measure", "benchmark" ]],
80+
["objects/magnifying-glass", [ "search", "find" ]],
81+
["objects/pickaxe", [ "mine", "mining" ]],
82+
["objects/shield", [ "protect", "armor", "safe", "security" ]],
83+
["objects/stopwatch", [ "time", "measure", "benchmark", "speed", "fast" ]],
84+
["objects/sword*", [ "fight", "army", "war", "battle", "force" ]],
85+
["objects/trashcan", [ "delete", "clear", "remove", "erase", "recycle", "bin" ]],
86+
87+
[["objects/vial", "objects/*-flask"], ["vial", "flask", "bottle"]],
88+
["objects/conical-flask", ["experiment", "chemistry", "chemical"]],
89+
90+
// Symbols
91+
["symbols/crosshair", [ "aim" ]],
92+
["symbols/jump-to", [ "go to", "goto" ]],
93+
["symbols/refresh", [ "reload" ]],
94+
["symbols/todo", [ "list" ]]
95+
])

0 commit comments

Comments
 (0)