Skip to content

Commit

Permalink
feat: wip filters using dropdowns
Browse files Browse the repository at this point in the history
  • Loading branch information
sgoudham committed Jun 18, 2024
1 parent 9e2d1ca commit 1ecdd5e
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 20 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"astro": "^4.5.16",
"fuse.js": "^7.0.0",
"svelte": "^4.2.12",
"svelte-select": "^5.8.3",
"typescript": "^5.4.4",
"yaml": "^2.4.0"
},
Expand Down
111 changes: 99 additions & 12 deletions src/components/PortExplorer.svelte
Original file line number Diff line number Diff line change
@@ -1,40 +1,127 @@
<script lang="ts">
import type { Port, Userstyle } from "../lib/ports";
import type { CategoryWithPortCount, HandleCategory, Platform, Port, Userstyle } from "../lib/ports";
import SearchBar from "./SearchBar.svelte";
import PortGrid from "./PortGrid.svelte";
import Fuse from "fuse.js";
import CategorySelect from "./dropdowns/CategorySelect.svelte";
import PlatformSelect from "./dropdowns/PlatformSelect.svelte";
export let platforms: Array<Platform>;
export let ports: Array<Port | Userstyle>;
export let categories: Array<CategoryWithPortCount>;
let portGrid: Array<Port | Userstyle> | undefined = undefined;
let debounceTimeout: NodeJS.Timeout;
const fuse = new Fuse(ports, {
keys: [
{ name: "key", weight: 1 },
{ name: "categories.name", weight: 0.8 },
{ name: "name", weight: 0.4 },
{ name: "name", weight: 1 },
{ name: "key", weight: 0.5 },
],
includeScore: false,
threshold: 0.3,
});
const url = new URL(window.location.href);
let searchTerm = url.searchParams.get("q") ?? "";
handleInput();
function handleInput() {
// Keep the URL in sync with the search bar
/* --- Category Initial State --- */
// Default to category "everything"
const foundCategory = categories.find((c) => c.key === "everything")!;
let category: HandleCategory = { value: foundCategory.key, label: foundCategory.nameWithCount };
// If the URL has a category query parameter, use that instead
if (url.searchParams.has("category")) {
category.value = url.searchParams.get("category")!;
} else {
// Make sure the URL is in sync with the category
url.searchParams.set("category", category.value);
}
// Category is guaranteed to be set to atleast "everything" or another category by now
if (category.value === "everything") {
// No need to change the default view if the category is "everything"
portGrid = ports;
} else {
// If the category is not "everything", filter the ports to only show the selected category
portGrid = ports.filter((port) => port.categories.some((c) => c.key === category.value));
}
// Make sure to update the fuse collection to the only the filtered ports
fuse.setCollection(portGrid);
/* --- Category Initial State --- */
/* --- Search Initial State --- */
// Default to an empty search term
let searchTerm = "";
if (url.searchParams.has("q")) {
searchTerm = url.searchParams.get("q")!;
// If a search term is present in the URL, search the ports for that term.
// This is guaranteed to be only the selected category since we did that logic above
if (searchTerm) {
portGrid = fuse.search(searchTerm).map((key) => key.item);
}
}
/* --- Search Initial State --- */
/* --- Category Event Handler --- */
const handleCategory = (e: { detail: HandleCategory }) => {
console.log(e);
// Keep the URL in sync with the category dropdown
url.searchParams.set("category", e.detail.value);
window.history.pushState(null, "", url.toString());
category.value = e.detail.value;
// If the user selects "everything", show all the ports
if (category.value === "everything") {
portGrid = ports;
} else {
// Otherwise, filter the ports to only show the selected category
portGrid = ports.filter((port) => port.categories.some((c) => c.key === category.value));
}
// Make sure to update the fuse collection to only the filtered ports
fuse.setCollection(portGrid);
};
/* --- Category Event Handler --- */
/* --- Search Event Handler --- */
const handleSearch = () => {
// Keep the URL in sync with the search input
url.searchParams.set("q", searchTerm);
window.history.pushState(null, "", url.toString());
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => {
searchTerm ? (portGrid = fuse.search(searchTerm).map((key) => key.item)) : (portGrid = ports);
// if the search term is empty, filter the ports to only show the selected category
if (searchTerm === "") {
if (category.value === "everything") {
portGrid = ports;
} else {
portGrid = ports.filter((port) => port.categories.some((c) => c.key === category.value));
}
} else {
// Otherwise, search within the selected category
portGrid = fuse.search(searchTerm).map((key) => key.item);
}
}, 25);
}
};
/* --- Search Event Handler --- */
</script>

<SearchBar bind:searchTerm {handleInput} />
<!-- LAYOUT/CSS TODO -->
<div style="display: flex; gap: 2rem; margin-block-end: var(--space-md); flex-wrap: wrap">
<div style="flex: 1; min-width: min(100%, 40em);">
<PlatformSelect {platforms} />
</div>
<div style="flex: 2; min-width: min(100%, 20em); height: 100%">
<CategorySelect {categories} bind:category {handleCategory} />
</div>
</div>
<SearchBar bind:searchTerm handleInput={handleSearch} />
<PortGrid bind:portGrid bind:searchTerm>
<svelte:fragment slot="no-results">
<slot name="no-results" />
Expand Down
4 changes: 2 additions & 2 deletions src/components/SearchBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
type="text"
id="search-field"
aria-label="Search"
placeholder="Search port or category..."
placeholder="Search port..."
autocomplete="off"
bind:value={searchTerm}
on:input={handleInput} />
Expand All @@ -38,7 +38,7 @@
border: none;
outline: none;
width: 100%;
font-size: 2rem;
font-size: 2.2rem;
}
}
Expand Down
33 changes: 33 additions & 0 deletions src/components/dropdowns/CategorySelect.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script lang="ts">
import Select from "svelte-select";
import type { CategoryWithPortCount } from "../../lib/ports.ts";
import type { HandleCategory } from "../../lib/ports.ts";
export let category: HandleCategory;
export let handleCategory;
export let categories: CategoryWithPortCount[];
let items = categories.map((c) => ({
value: c.key,
label: c.nameWithCount,
}));
</script>

<Select
--background="var(--mantle)"
--font-size="2.2rem"
--placeholder-color="var(--overlay2)"
--border-radius="var(--border-radius-normal)"
--border="none"
--border-hover="none"
--border-focused="2px solid var(--blue)"
--item-hover-bg="var(--surface1)"
--list-background="var(--mantle)"
--list-border-radius="var(--border-radius-normal)"
--clear-icon-color="var(--red)"
showChevron={true}
clearable={false}
placeholder="Select category"
{items}
bind:value={category}
on:change={handleCategory}>
</Select>
67 changes: 67 additions & 0 deletions src/components/dropdowns/PlatformSelect.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script lang="ts">
import Select from "svelte-select";
import type { Platform } from "../../lib/ports.ts";
export let platforms: Platform[];
let items = platforms.map((p) => ({ value: p, label: p.charAt(0).toUpperCase() + p.slice(1) }));
let value = [];
let checked: Platform = [];
let isChecked = {};
$: computeValue(checked);
$: computeIsChecked(checked);
function computeIsChecked() {
isChecked = {};
checked.forEach((c) => (isChecked[c] = true));
}
function computeValue() {
value = checked.map((c) => items.find((i) => i.value === c));
}
function handleChange(e) {
if (e.type === "clear" && Array.isArray(e.detail)) checked = [];
else
checked.includes(e.detail.value)
? (checked = checked.filter((i) => i != e.detail.value))
: (checked = [...checked, e.detail.value]);
}
</script>

<Select
--background="var(--mantle)"
--font-size="2.1rem"
--placeholder-color="var(--overlay2)"
--border-radius="var(--border-radius-normal)"
--border="none"
--border-hover="none"
--border-focused="2px solid var(--blue)"
--item-hover-bg="var(--surface1)"
--multi-item-bg="color-mix(in srgb, var(--base), var(--subtext0) 10%)"
--multi-item-clear-icon-color="var(--maroon)"
--multi-item-border-radius="var(--border-radius-normal)"
--multi-item-active-outline="none"
--multi-item-outline="none"
--multi-item-gap="var(--space-xs)"
--multi-item-margin="var(--space-xxs)"
--list-background="var(--mantle)"
--list-border-radius="var(--border-radius-normal)"
--clear-icon-color="var(--red)"
{items}
{value}
placeholder="Select platform (TODO)"
multiple={true}
filterSelectedItems={false}
closeListOnChange={false}
showChevron={true}
on:select={handleChange}
on:clear={handleChange}>
<div class="item" slot="item" let:item>
<label for={item.value}>
<input type="checkbox" id={item.value} bind:checked={isChecked[item.value]} />
{item.label}
</label>
</div>
</Select>
2 changes: 1 addition & 1 deletion src/components/lists/Pills.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
@include utils.containerPadding(xxs-y);
border-radius: 9999px;
background-color: color-mix(in srgb, var(--base), var(--subtext0) 20%);
background-color: color-mix(in srgb, var(--base), var(--subtext0) 10%);
font-size: 80%;
}
Expand Down
33 changes: 31 additions & 2 deletions src/lib/ports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { PropertyBasedSet } from "./propertyBasedSet";
import { getIcon } from "./getIcon";
import type { ColorName } from "@catppuccin/palette";

export type HandleCategory = {
value: string;
label: string;
};

// Mostly auto-generated but have made manual edits to the types
// to stop TypeScript from complaining about the types. Future
// Hammy can deal with the consequences of this decision.
Expand All @@ -21,7 +26,7 @@ export interface ArchivedPort {
name: string;
reason: string;
categories: Category[];
platform: PlatformElement[] | "agnostic";
platform: Platform;
color: ColorName;
icon?: string;
key: string;
Expand All @@ -43,6 +48,8 @@ export enum PlatformElement {
Windows = "windows",
}

export type Platform = PlatformElement | "agnostic";

export interface Repository {
name: string;
url: string;
Expand All @@ -59,7 +66,7 @@ export interface Collaborator {
export interface Port {
name: string;
categories: Category[];
platform: PlatformElement[] | "agnostic";
platform: Platform;
color: ColorName;
key: string;
repository: Repository;
Expand Down Expand Up @@ -108,6 +115,8 @@ export interface FAQ {
answer: string;
}

export type CategoryWithPortCount = Category & { portCount: number; nameWithCount: string };

export const repositoriesYml = (await fetch(
"https://raw.githubusercontent.com/catppuccin/catppuccin/portscelain/pigeon/ports.porcelain.yml",
)
Expand All @@ -134,3 +143,23 @@ export const currentMaintainers: Collaborator[] = new PropertyBasedSet<Collabora
(p) => p["current-maintainers"],
),
).sorted();

export const categories = repositoriesYml.categories.map((category) => {
const portCount = ports.filter((port) => port.categories.some((c) => c.key === category.key)).length;
return {
...category,
portCount,
nameWithCount: `${category.name} (${portCount})`,
};
});
categories.push({
key: "everything",
name: "Ports",
description: "All ports and userstyles",
emoji: "🌐",
portCount: ports.length,
nameWithCount: `Ports (${ports.length})`,
});
categories.sort((a, b) => b.portCount - a.portCount);

export const platforms: Platform[] = [...Object.values(PlatformElement), "agnostic"];
5 changes: 2 additions & 3 deletions src/pages/ports.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import { ports } from "../lib/ports";
import { platforms, categories, ports } from "../lib/ports";
import Layout from "../layouts/Layout.astro";
Expand All @@ -17,7 +17,6 @@ import Link from "../components/Link.astro";
Catppuccin offers ports for all sorts of different applications, tools, websites and just about anything that
comes to your mind!
</p>
<p>Total Ports: <strong>{ports.length}</strong></p>
</PageIntro>

<!-- Display ports without search bar for browsers without JavaScript enabled -->
Expand All @@ -27,7 +26,7 @@ import Link from "../components/Link.astro";
</div>
</noscript>

<PortExplorer {ports} client:only="svelte">
<PortExplorer {ports} {categories} {platforms} client:only="svelte">
<svelte:fragment slot="no-results">
<p>Sorry, we couldn't find any ports matching your search :(</p>
<p>
Expand Down

0 comments on commit 1ecdd5e

Please sign in to comment.