mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-11-13 17:08:20 +00:00
refactor: better typing for tool icons
This commit is contained in:
@@ -180,12 +180,19 @@ round-семантика `clampByte` и trunc-семантика `clampInt` со
|
|||||||
- `executor.ts` `as unknown as WorkerLike` оставлен намеренно: несовместимость
|
- `executor.ts` `as unknown as WorkerLike` оставлен намеренно: несовместимость
|
||||||
`onerror` (ErrorEvent vs Event) в типах lib.dom; добавлен комментарий-причина.
|
`onerror` (ErrorEvent vs Event) в типах lib.dom; добавлен комментарий-причина.
|
||||||
|
|
||||||
### L5. Хардкод slug-ов вне registry
|
### L5. Хардкод slug-ов вне registry — СДЕЛАНО (2026-10)
|
||||||
|
|
||||||
`tool-icons.ts:54-142` — карта slug→icon без типизации по slug-union
|
`Page` стал readonly, группы `registry/pages/*` —
|
||||||
(переименование slug ломает иконки молча); `kit/+page.svelte:40` — захардкожены
|
`as const satisfies readonly Page[]`; в `registry/index.ts` добавлен union
|
||||||
`["linear-gradient-png","resize-png","quantize-png"]` (kit — витрина, не
|
`PageSlug`. `TOOL_ICONS` типизирован `Record<PageSlug, IconComponent>` —
|
||||||
production).
|
переименование или удаление страницы теперь ломает компиляцию, а не падает
|
||||||
|
молча. Чтение с внешней строкой — через аксессор `toolIcon(slug)`. Попутно
|
||||||
|
найдены и удалены 9 мёртвых ключей иконок (страницы уже не существуют:
|
||||||
|
`png-info`, `jpg-to-png`, `webp-to-png`, `gif-to-png`, `bmp-to-png`,
|
||||||
|
`ico-to-png`, `compress-png`, `reduce-to-size-png`, `watermark-image-png`).
|
||||||
|
Добавлен runtime-тест `web/src/lib/tool-icons.test.ts` (оба направления: сироты
|
||||||
|
и страницы без иконки). Хардкод в `kit/+page.svelte` оставлен — витрина, не
|
||||||
|
production.
|
||||||
|
|
||||||
### L6. Дублирование CSS-значения превью-тайла — СДЕЛАНО (2026-10)
|
### L6. Дублирование CSS-значения превью-тайла — СДЕЛАНО (2026-10)
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -222,18 +222,18 @@ i18n-подписи → тест в `registry-schema.test.ts`.
|
|||||||
|
|
||||||
## 7. Точки расширения
|
## 7. Точки расширения
|
||||||
|
|
||||||
| Задача | Где править |
|
| Задача | Где править |
|
||||||
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| Новый инструмент | `registry/tools/<group>.ts` + страница в `registry/pages/<group>.ts` + i18n + тесты (рецепт ниже) |
|
| Новый инструмент | `registry/tools/<group>.ts` + страница в `registry/pages/<group>.ts` + i18n + тесты (рецепт ниже) |
|
||||||
| Новая страница на готовом инструменте | запись `Page` со своим `slug` и текстами; новый код не нужен |
|
| Новая страница на готовом инструменте | запись `Page` со своим `slug` и текстами; новый код не нужен |
|
||||||
| Смена `id` | `registry/tools/*` + ключи `tools.*` в словарях + хардкод id в тестах; адреса, имена файлов, иконки и карта не меняются |
|
| Смена `id` | `registry/tools/*` + ключи `tools.*` в словарях + хардкод id в тестах; адреса, имена файлов, иконки и карта не меняются |
|
||||||
| Смена `slug` | `registry/pages/*` + `pages.*` в словарях + `tool-icons.ts` + `docs/tools-map.md` + хардкод slug в тестах и e2e; меняется адрес страницы — операция с SEO-последствиями, не часть обычной задачи |
|
| Смена `slug` | `registry/pages/*` + `pages.*` в словарях + `docs/tools-map.md` + хардкод slug в тестах и e2e; иконки править не нужно — `TOOL_ICONS` типизирован `Record<PageSlug, _>` и ломает компиляцию; меняется адрес страницы — операция с SEO-последствиями, не часть обычной задачи |
|
||||||
| Новый вид поля | `registry-schema.ts` + контрол + `FIELDS` (раздел 4) |
|
| Новый вид поля | `registry-schema.ts` + контрол + `FIELDS` (раздел 4) |
|
||||||
| Новый маршрут/страница | `web/src/routes/**` + ссылка в каталоге |
|
| Новый маршрут/страница | `web/src/routes/**` + ссылка в каталоге |
|
||||||
| Новый визуальный элемент | компонент в `components/**` по образцу `ui/` или `layout/` |
|
| Новый визуальный элемент | компонент в `components/**` по образцу `ui/` или `layout/` |
|
||||||
| Новый дизайн-токен | `web/src/app.css` (только там) + потребитель сразу |
|
| Новый дизайн-токен | `web/src/app.css` (только там) + потребитель сразу |
|
||||||
| Тяжёлая операция → worker/wasm | `executor/**`, без правок компонентов |
|
| Тяжёлая операция → worker/wasm | `executor/**`, без правок компонентов |
|
||||||
| Мультифайловый результат | `result: "files"` + `zip.ts` |
|
| Мультифайловый результат | `result: "files"` + `zip.ts` |
|
||||||
|
|
||||||
## 8. Рецепт: добавить инструмент
|
## 8. Рецепт: добавить инструмент
|
||||||
|
|
||||||
|
|||||||
@@ -194,6 +194,7 @@
|
|||||||
|
|
||||||
## Тесты и lint
|
## Тесты и lint
|
||||||
|
|
||||||
|
- [ ] линтер на deprecated иконки возможен? 'FilePlus2' is deprecated.
|
||||||
- [ ] `tseslint.config(...)` deprecated: сигнатура
|
- [ ] `tseslint.config(...)` deprecated: сигнатура
|
||||||
`(...configs: InfiniteDepthConfigWithExtends[]): ConfigArray` помечена
|
`(...configs: InfiniteDepthConfigWithExtends[]): ConfigArray` помечена
|
||||||
устаревшей, нужен переход на `defineConfig` из `eslint/config` (ESLint
|
устаревшей, нужен переход на `defineConfig` из `eslint/config` (ESLint
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ describe("dictionary completeness for the new registry", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("the pages section has no extra pages", () => {
|
it("the pages section has no extra pages", () => {
|
||||||
const slugs = new Set(PAGES.map((page) => page.slug));
|
const slugs = new Set<string>(PAGES.map((page) => page.slug));
|
||||||
for (const [locale, dict] of [
|
for (const [locale, dict] of [
|
||||||
["ru", ru],
|
["ru", ru],
|
||||||
["en", en],
|
["en", en],
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ export const TOOLS: Tool[] = [
|
|||||||
...textTools,
|
...textTools,
|
||||||
] as unknown as Tool[];
|
] as unknown as Tool[];
|
||||||
|
|
||||||
export const PAGES: Page[] = [
|
// Not annotated as Page[] on purpose: the literal slug types must survive
|
||||||
|
// for PageSlug below; every consumer treats PAGES as a readonly Page list.
|
||||||
|
export const PAGES = [
|
||||||
...geometryPages,
|
...geometryPages,
|
||||||
...alphaPages,
|
...alphaPages,
|
||||||
...convertPages,
|
...convertPages,
|
||||||
@@ -59,7 +61,10 @@ export const PAGES: Page[] = [
|
|||||||
...colorPages,
|
...colorPages,
|
||||||
...generatePages,
|
...generatePages,
|
||||||
...textPages,
|
...textPages,
|
||||||
] as unknown as Page[];
|
];
|
||||||
|
|
||||||
|
/** Union of existing page slugs (groups use `satisfies Page[]`, so literals survive). */
|
||||||
|
export type PageSlug = (typeof PAGES)[number]["slug"];
|
||||||
|
|
||||||
export function getTool(id: string): Tool | undefined {
|
export function getTool(id: string): Tool | undefined {
|
||||||
return TOOLS.find((tool) => tool.id === id);
|
return TOOLS.find((tool) => tool.id === id);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Page } from "../types";
|
import type { Page } from "../types";
|
||||||
|
|
||||||
export const alphaPages: Page[] = [
|
export const alphaPages = [
|
||||||
{
|
{
|
||||||
slug: "add-stroke-png",
|
slug: "add-stroke-png",
|
||||||
title: "Outline PNG",
|
title: "Outline PNG",
|
||||||
@@ -157,4 +157,4 @@ export const alphaPages: Page[] = [
|
|||||||
category: "alpha",
|
category: "alpha",
|
||||||
steps: [{ id: "close-holes" }],
|
steps: [{ id: "close-holes" }],
|
||||||
},
|
},
|
||||||
];
|
] as const satisfies readonly Page[];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Page } from "../types";
|
import type { Page } from "../types";
|
||||||
|
|
||||||
export const analyzePages: Page[] = [
|
export const analyzePages = [
|
||||||
{
|
{
|
||||||
slug: "extract-color-from-png",
|
slug: "extract-color-from-png",
|
||||||
title: "Extract Color from PNG",
|
title: "Extract Color from PNG",
|
||||||
@@ -93,4 +93,4 @@ export const analyzePages: Page[] = [
|
|||||||
category: "analyze",
|
category: "analyze",
|
||||||
steps: [{ id: "orientation" }],
|
steps: [{ id: "orientation" }],
|
||||||
},
|
},
|
||||||
];
|
] as const satisfies readonly Page[];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Page } from "../types";
|
import type { Page } from "../types";
|
||||||
|
|
||||||
export const colorPages: Page[] = [
|
export const colorPages = [
|
||||||
{
|
{
|
||||||
slug: "two-colors-png",
|
slug: "two-colors-png",
|
||||||
title: "Two colors PNG",
|
title: "Two colors PNG",
|
||||||
@@ -199,4 +199,4 @@ export const colorPages: Page[] = [
|
|||||||
category: "color",
|
category: "color",
|
||||||
steps: [{ id: "to-lab" }],
|
steps: [{ id: "to-lab" }],
|
||||||
},
|
},
|
||||||
];
|
] as const satisfies readonly Page[];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Page } from "../types";
|
import type { Page } from "../types";
|
||||||
|
|
||||||
export const convertPages: Page[] = [
|
export const convertPages = [
|
||||||
{
|
{
|
||||||
slug: "convert-png-to-jpg",
|
slug: "convert-png-to-jpg",
|
||||||
title: "Convert PNG to JPG",
|
title: "Convert PNG to JPG",
|
||||||
@@ -112,4 +112,4 @@ export const convertPages: Page[] = [
|
|||||||
category: "convert",
|
category: "convert",
|
||||||
steps: [{ id: "from-svg" }],
|
steps: [{ id: "from-svg" }],
|
||||||
},
|
},
|
||||||
];
|
] as const satisfies readonly Page[];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Page } from "../types";
|
import type { Page } from "../types";
|
||||||
|
|
||||||
export const filtersPages: Page[] = [
|
export const filtersPages = [
|
||||||
{
|
{
|
||||||
slug: "blur-png",
|
slug: "blur-png",
|
||||||
title: "Blur PNG",
|
title: "Blur PNG",
|
||||||
@@ -65,4 +65,4 @@ export const filtersPages: Page[] = [
|
|||||||
category: "filters",
|
category: "filters",
|
||||||
steps: [{ id: "jpeg-artifacts" }],
|
steps: [{ id: "jpeg-artifacts" }],
|
||||||
},
|
},
|
||||||
];
|
] as const satisfies readonly Page[];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Page } from "../types";
|
import type { Page } from "../types";
|
||||||
|
|
||||||
export const generatePages: Page[] = [
|
export const generatePages = [
|
||||||
{
|
{
|
||||||
slug: "create-empty-png",
|
slug: "create-empty-png",
|
||||||
title: "Create empty PNG",
|
title: "Create empty PNG",
|
||||||
@@ -164,4 +164,4 @@ export const generatePages: Page[] = [
|
|||||||
category: "generate",
|
category: "generate",
|
||||||
steps: [{ id: "from-text" }],
|
steps: [{ id: "from-text" }],
|
||||||
},
|
},
|
||||||
];
|
] as const satisfies readonly Page[];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Page } from "../types";
|
import type { Page } from "../types";
|
||||||
|
|
||||||
export const geometryPages: Page[] = [
|
export const geometryPages = [
|
||||||
{
|
{
|
||||||
slug: "add-border-png",
|
slug: "add-border-png",
|
||||||
title: "Add border to PNG",
|
title: "Add border to PNG",
|
||||||
@@ -149,4 +149,4 @@ export const geometryPages: Page[] = [
|
|||||||
category: "geometry",
|
category: "geometry",
|
||||||
steps: [{ id: "shift" }],
|
steps: [{ id: "shift" }],
|
||||||
},
|
},
|
||||||
];
|
] as const satisfies readonly Page[];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Page } from "../types";
|
import type { Page } from "../types";
|
||||||
|
|
||||||
export const textPages: Page[] = [
|
export const textPages = [
|
||||||
{
|
{
|
||||||
slug: "add-text-png",
|
slug: "add-text-png",
|
||||||
title: "Add text to PNG",
|
title: "Add text to PNG",
|
||||||
@@ -25,4 +25,4 @@ export const textPages: Page[] = [
|
|||||||
category: "text",
|
category: "text",
|
||||||
steps: [{ id: "watermark-tile" }],
|
steps: [{ id: "watermark-tile" }],
|
||||||
},
|
},
|
||||||
];
|
] as const satisfies readonly Page[];
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ describe("registry-new (migrated tools)", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("every tool is used by at least one page", () => {
|
it("every tool is used by at least one page", () => {
|
||||||
const used = new Set(PAGES.flatMap((page) => page.steps.map((s) => s.id)));
|
const used = new Set<string>(
|
||||||
|
PAGES.flatMap((page) => page.steps.map((s) => s.id)),
|
||||||
|
);
|
||||||
const orphans = TOOLS.map((tool) => tool.id).filter((id) => !used.has(id));
|
const orphans = TOOLS.map((tool) => tool.id).filter((id) => !used.has(id));
|
||||||
expect(orphans, `tools without pages:\n${orphans.join("\n")}`).toEqual([]);
|
expect(orphans, `tools without pages:\n${orphans.join("\n")}`).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ describe("docs/tools-map.md vs registry pages", () => {
|
|||||||
const markdown = readFileSync(MAP_PATH, "utf8");
|
const markdown = readFileSync(MAP_PATH, "utf8");
|
||||||
const mentioned = mentionedIds(markdown);
|
const mentioned = mentionedIds(markdown);
|
||||||
const planned = plannedIds(markdown);
|
const planned = plannedIds(markdown);
|
||||||
const registrySlugs = new Set(PAGES.map((page) => page.slug));
|
const registrySlugs = new Set<string>(PAGES.map((page) => page.slug));
|
||||||
|
|
||||||
it("parses a non-empty slug list (parser did not silently break)", () => {
|
it("parses a non-empty slug list (parser did not silently break)", () => {
|
||||||
expect(mentioned.length).toBeGreaterThan(0);
|
expect(mentioned.length).toBeGreaterThan(0);
|
||||||
|
|||||||
@@ -80,12 +80,12 @@ export type PageStep = {
|
|||||||
/** Page: unique address, strings, and the ordered tool list in `steps` (a page may be a ready-made chain run by `SchemaToolView`). */
|
/** Page: unique address, strings, and the ordered tool list in `steps` (a page may be a ready-made chain run by `SchemaToolView`). */
|
||||||
export type Page = {
|
export type Page = {
|
||||||
/** Address `/tools/<slug>`, downloaded file name, and icon key. */
|
/** Address `/tools/<slug>`, downloaded file name, and icon key. */
|
||||||
slug: string;
|
readonly slug: string;
|
||||||
/** EN string; the translation lives in the dictionary by slug. */
|
/** EN string; the translation lives in the dictionary by slug. */
|
||||||
title: string;
|
readonly title: string;
|
||||||
description: string;
|
readonly description: string;
|
||||||
category: CategoryId;
|
readonly category: CategoryId;
|
||||||
steps: PageStep[];
|
readonly steps: readonly PageStep[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export function requireSource<P>(ctx: ToolContext<P>): PixelImage {
|
export function requireSource<P>(ctx: ToolContext<P>): PixelImage {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { PAGES } from "./registry";
|
||||||
|
import { TOOL_ICONS } from "./tool-icons";
|
||||||
|
|
||||||
|
describe("tool-icons", () => {
|
||||||
|
it("every icon key is an existing page slug", () => {
|
||||||
|
const slugs = new Set<string>(PAGES.map((page) => page.slug));
|
||||||
|
const orphans = Object.keys(TOOL_ICONS).filter((key) => !slugs.has(key));
|
||||||
|
expect(orphans).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("every page has an icon", () => {
|
||||||
|
const missing = PAGES.map((page) => page.slug).filter(
|
||||||
|
(slug) => !(slug in TOOL_ICONS),
|
||||||
|
);
|
||||||
|
expect(missing).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
+12
-13
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
AppWindow,
|
|
||||||
Binary,
|
Binary,
|
||||||
Blend,
|
Blend,
|
||||||
CalendarDays,
|
CalendarDays,
|
||||||
@@ -15,7 +14,6 @@ import {
|
|||||||
FileImage,
|
FileImage,
|
||||||
FileOutput,
|
FileOutput,
|
||||||
FilePlus2,
|
FilePlus2,
|
||||||
Film,
|
|
||||||
FlipHorizontal2,
|
FlipHorizontal2,
|
||||||
Focus,
|
Focus,
|
||||||
Frame,
|
Frame,
|
||||||
@@ -27,7 +25,6 @@ import {
|
|||||||
Layers,
|
Layers,
|
||||||
Link2,
|
Link2,
|
||||||
Maximize2,
|
Maximize2,
|
||||||
Minimize2,
|
|
||||||
Moon,
|
Moon,
|
||||||
PaintBucket,
|
PaintBucket,
|
||||||
Palette,
|
Palette,
|
||||||
@@ -49,8 +46,13 @@ import {
|
|||||||
ZoomIn,
|
ZoomIn,
|
||||||
ZoomOut,
|
ZoomOut,
|
||||||
} from "@lucide/svelte";
|
} from "@lucide/svelte";
|
||||||
|
import type { Component } from "svelte";
|
||||||
|
import type { PageSlug } from "./registry";
|
||||||
|
|
||||||
export const TOOL_ICONS: Record<string, typeof AppWindow> = {
|
type IconComponent = Component<{ size?: number; class?: string }>;
|
||||||
|
|
||||||
|
// Keyed by page slug: a renamed/missing page is a compile error here.
|
||||||
|
export const TOOL_ICONS: Record<PageSlug, IconComponent> = {
|
||||||
"resize-png": Scaling,
|
"resize-png": Scaling,
|
||||||
"crop-png": Crop,
|
"crop-png": Crop,
|
||||||
"rotate-png": RotateCw,
|
"rotate-png": RotateCw,
|
||||||
@@ -61,12 +63,6 @@ export const TOOL_ICONS: Record<string, typeof AppWindow> = {
|
|||||||
"convert-png-to-jpg": FileImage,
|
"convert-png-to-jpg": FileImage,
|
||||||
"convert-png-to-webp": FileImage,
|
"convert-png-to-webp": FileImage,
|
||||||
"remove-color-from-png": Scissors,
|
"remove-color-from-png": Scissors,
|
||||||
"png-info": Info,
|
|
||||||
"jpg-to-png": FileImage,
|
|
||||||
"webp-to-png": FileImage,
|
|
||||||
"gif-to-png": Film,
|
|
||||||
"bmp-to-png": FileImage,
|
|
||||||
"ico-to-png": AppWindow,
|
|
||||||
"png-to-bmp": FileOutput,
|
"png-to-bmp": FileOutput,
|
||||||
"png-to-base64": Binary,
|
"png-to-base64": Binary,
|
||||||
"base64-to-png": ClipboardPaste,
|
"base64-to-png": ClipboardPaste,
|
||||||
@@ -134,8 +130,6 @@ export const TOOL_ICONS: Record<string, typeof AppWindow> = {
|
|||||||
"change-aspect-ratio-png": Scaling,
|
"change-aspect-ratio-png": Scaling,
|
||||||
"swap-orientation-png": RotateCw,
|
"swap-orientation-png": RotateCw,
|
||||||
"symmetric-copy-png": FlipHorizontal2,
|
"symmetric-copy-png": FlipHorizontal2,
|
||||||
"compress-png": Minimize2,
|
|
||||||
"reduce-to-size-png": Scaling,
|
|
||||||
"png-file-size": Info,
|
"png-file-size": Info,
|
||||||
"png-to-bytes": Binary,
|
"png-to-bytes": Binary,
|
||||||
"bytes-to-png": Binary,
|
"bytes-to-png": Binary,
|
||||||
@@ -153,7 +147,6 @@ export const TOOL_ICONS: Record<string, typeof AppWindow> = {
|
|||||||
"star-mask-png": Star,
|
"star-mask-png": Star,
|
||||||
"wavy-mask-png": WavesHorizontal,
|
"wavy-mask-png": WavesHorizontal,
|
||||||
"watermark-tile-png": Stamp,
|
"watermark-tile-png": Stamp,
|
||||||
"watermark-image-png": FileImage,
|
|
||||||
"color-wheel-png": Rainbow,
|
"color-wheel-png": Rainbow,
|
||||||
"complementary-png": Contrast,
|
"complementary-png": Contrast,
|
||||||
"triadic-png": Hash,
|
"triadic-png": Hash,
|
||||||
@@ -183,3 +176,9 @@ export const TOOL_ICONS: Record<string, typeof AppWindow> = {
|
|||||||
"tint-png": Droplet,
|
"tint-png": Droplet,
|
||||||
"svg-to-png": FileImage,
|
"svg-to-png": FileImage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Slugs come from the registry at runtime (Page.slug is string there), so the
|
||||||
|
// lookup widens; completeness is guaranteed by the Record<PageSlug, _> above.
|
||||||
|
export function toolIcon(slug: string): IconComponent {
|
||||||
|
return TOOL_ICONS[slug as PageSlug];
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
} from "$lib/i18n/schema-tool-strings";
|
} from "$lib/i18n/schema-tool-strings";
|
||||||
import { t } from "$lib/i18n/t";
|
import { t } from "$lib/i18n/t";
|
||||||
import { PAGES } from "$lib/registry";
|
import { PAGES } from "$lib/registry";
|
||||||
import { TOOL_ICONS } from "$lib/tool-icons";
|
import { toolIcon } from "$lib/tool-icons";
|
||||||
|
|
||||||
let query = $state("");
|
let query = $state("");
|
||||||
let category = $state<string>("all");
|
let category = $state<string>("all");
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
slug={page.slug}
|
slug={page.slug}
|
||||||
description={pageDescription(page)}
|
description={pageDescription(page)}
|
||||||
index={i + 1}
|
index={i + 1}
|
||||||
icon={TOOL_ICONS[page.slug]}
|
icon={toolIcon(page.slug)}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</CatalogGroup>
|
</CatalogGroup>
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
slug={page.slug}
|
slug={page.slug}
|
||||||
description={pageDescription(page)}
|
description={pageDescription(page)}
|
||||||
index={i + 1}
|
index={i + 1}
|
||||||
icon={TOOL_ICONS[page.slug]}
|
icon={toolIcon(page.slug)}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</CatalogGroup>
|
</CatalogGroup>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
} from "$lib/i18n/schema-tool-strings";
|
} from "$lib/i18n/schema-tool-strings";
|
||||||
import { t } from "$lib/i18n/t";
|
import { t } from "$lib/i18n/t";
|
||||||
import { PAGES } from "$lib/registry";
|
import { PAGES } from "$lib/registry";
|
||||||
import { TOOL_ICONS } from "$lib/tool-icons";
|
import { toolIcon } from "$lib/tool-icons";
|
||||||
|
|
||||||
let query = $state("");
|
let query = $state("");
|
||||||
let category = $state<string>("all");
|
let category = $state<string>("all");
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
slug={page.slug}
|
slug={page.slug}
|
||||||
description={pageDescription(page)}
|
description={pageDescription(page)}
|
||||||
index={i + 1}
|
index={i + 1}
|
||||||
icon={TOOL_ICONS[page.slug]}
|
icon={toolIcon(page.slug)}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</CatalogGroup>
|
</CatalogGroup>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
slug={page.slug}
|
slug={page.slug}
|
||||||
description={pageDescription(page)}
|
description={pageDescription(page)}
|
||||||
index={i + 1}
|
index={i + 1}
|
||||||
icon={TOOL_ICONS[page.slug]}
|
icon={toolIcon(page.slug)}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
</CatalogGroup>
|
</CatalogGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user