diff --git a/web/src/lib/executor/executor.test.ts b/web/src/lib/executor/executor.test.ts index 91bc986..8b7f4db 100644 --- a/web/src/lib/executor/executor.test.ts +++ b/web/src/lib/executor/executor.test.ts @@ -66,6 +66,7 @@ function makeTool( ): ToolEntry { return { id: "test-tool", + slug: "test-tool", title: "Test tool", description: "Test tool", category: "analyze", diff --git a/web/src/lib/registry/alpha.ts b/web/src/lib/registry/alpha.ts index dc142d9..9c1ed4c 100644 --- a/web/src/lib/registry/alpha.ts +++ b/web/src/lib/registry/alpha.ts @@ -46,6 +46,7 @@ export const addStrokeSchema = toolSchema({ const addStroke: ToolEntry = { id: "add-stroke-png", + slug: "add-stroke-png", title: "Outline PNG", description: "Adds a colored ring outline around the opaque content with the chosen thickness.", @@ -73,6 +74,7 @@ export const findContourSchema = toolSchema({ const findContour: ToolEntry = { id: "find-contour-png", + slug: "find-contour-png", title: "Find contour PNG", description: "Leaves only a line along the boundary of opaque regions in the chosen color and thickness.", @@ -100,6 +102,7 @@ export const removeColorSchema = toolSchema({ const removeColor: ToolEntry = { id: "remove-color-from-png", + slug: "remove-color-from-png", title: "Remove color from PNG (make transparent)", description: "Makes all pixels close to the chosen color transparent. The tolerance sets the allowed deviation as a percentage of the maximum color distance.", @@ -143,6 +146,7 @@ const circleMaskSchema = toolSchema( const circleMask: ToolEntry = { id: "circle-mask-png", + slug: "circle-mask-png", title: "Circle Mask PNG", description: "Cuts the image into a circle. Diameter is set as a share of the smaller side.", @@ -205,6 +209,7 @@ const squareMaskSchema = toolSchema( const squareMask: ToolEntry = { id: "square-mask-png", + slug: "square-mask-png", title: "Square Mask PNG", description: "Cuts the image into a rectangle with sides as a share of the smaller side.", @@ -283,6 +288,7 @@ const starMaskSchema = toolSchema( const starMask: ToolEntry = { id: "star-mask-png", + slug: "star-mask-png", title: "Star Mask PNG", description: "Cuts the image into an n-pointed star with adjustable inner radius and rotation.", @@ -361,6 +367,7 @@ const wavyMaskSchema = toolSchema( const wavyMask: ToolEntry = { id: "wavy-mask-png", + slug: "wavy-mask-png", title: "Wavy Mask PNG", description: "Cuts the image into a wavy-edged circle: radius is modulated by a sine with chosen amplitude and frequency.", @@ -383,6 +390,7 @@ export const removeAlphaChannelSchema = toolSchema({}); const removeAlphaChannel: ToolEntry = { id: "remove-alpha-channel-png", + slug: "remove-alpha-channel-png", title: "Remove alpha channel PNG", description: "Composites the image over a white background and saves without transparency.", @@ -408,6 +416,7 @@ export const setAlphaChannelSchema = toolSchema({ const setAlphaChannelTool: ToolEntry = { id: "set-alpha-channel-png", + slug: "set-alpha-channel-png", title: "Set alpha channel PNG", description: "Assigns the same opacity to all pixels; colors stay unchanged.", category: "alpha", @@ -420,6 +429,7 @@ export const extractAlphaMaskSchema = toolSchema({}); const extractAlphaMaskTool: ToolEntry = { id: "extract-alpha-mask-png", + slug: "extract-alpha-mask-png", title: "Extract alpha mask PNG", description: "Turns transparency into a black-and-white opaque mask.", category: "alpha", @@ -444,6 +454,7 @@ export const roundCornersSchema = toolSchema({ const roundCornersTool: ToolEntry = { id: "round-corners-png", + slug: "round-corners-png", title: "Round corners PNG", description: "Clips corners by a radius set as a percentage of half the smaller side.", @@ -457,6 +468,7 @@ export const invertAlphaSchema = toolSchema({}); const invertAlphaTool: ToolEntry = { id: "invert-alpha-png", + slug: "invert-alpha-png", title: "Invert alpha PNG", description: "Opaque areas become transparent and vice versa.", category: "alpha", @@ -507,6 +519,7 @@ export const removeBackgroundSchema = toolSchema( const removeBackgroundTool: ToolEntry = { id: "remove-background-png", + slug: "remove-background-png", title: "Remove background PNG (smart)", description: "Removes a solid background: by color with tolerance, outer regions from the edges only, or every matching pixel. Can smooth the boundary.", @@ -539,6 +552,7 @@ export const makeThickerSchema = toolSchema({ const makeThickerTool: ToolEntry = { id: "make-thicker-png", + slug: "make-thicker-png", title: "Thicken PNG", description: "Expands opaque areas by the given number of pixels.", category: "alpha", @@ -563,6 +577,7 @@ export const makeThinnerSchema = toolSchema({ const makeThinnerTool: ToolEntry = { id: "make-thinner-png", + slug: "make-thinner-png", title: "Thin PNG", description: "Shrinks opaque areas — thins the strokes of text and details.", category: "alpha", @@ -587,6 +602,7 @@ export const featherEdgesSchema = toolSchema({ const featherEdgesTool: ToolEntry = { id: "feather-edges-png", + slug: "feather-edges-png", title: "Feather Edges PNG", description: "Blurs only the alpha channel: hard cutout edges become soft and gradual, colors stay untouched.", @@ -612,6 +628,7 @@ export const cleanEdgesSchema = toolSchema({ const cleanEdgesTool: ToolEntry = { id: "clean-edges-png", + slug: "clean-edges-png", title: "Clean Edges PNG (defringe)", description: "Replaces edge-halo colors of semi-transparent pixels with the nearest fully opaque color. Alpha stays as is.", @@ -637,6 +654,7 @@ export const hardenAlphaSchema = toolSchema({ const hardenAlphaTool: ToolEntry = { id: "harden-alpha-png", + slug: "harden-alpha-png", title: "Harden edges PNG", description: "Binarizes the alpha channel by threshold: semi-transparent pixels become either fully transparent or fully opaque.", @@ -662,6 +680,7 @@ export const despeckleAlphaSchema = toolSchema({ const despeckleAlphaTool: ToolEntry = { id: "despeckle-alpha-png", + slug: "despeckle-alpha-png", title: "Despeckle PNG", description: "Opening: removes lone semi-transparent pixels and small specks.", @@ -687,6 +706,7 @@ export const closeHolesSchema = toolSchema({ const closeHolesTool: ToolEntry = { id: "close-holes-png", + slug: "close-holes-png", title: "Close holes PNG", description: "Closing: fills lone transparent dots inside the object.", category: "alpha", diff --git a/web/src/lib/registry/analyze.ts b/web/src/lib/registry/analyze.ts index 2344cec..1b991b3 100644 --- a/web/src/lib/registry/analyze.ts +++ b/web/src/lib/registry/analyze.ts @@ -29,6 +29,7 @@ export const extractColorSchema = toolSchema({ const extractColor: ToolEntry = { id: "extract-color-from-png", + slug: "extract-color-from-png", title: "Extract Color from PNG", description: "Keeps only pixels close to the chosen color and makes everything else transparent — the inverse of Remove Color.", @@ -89,6 +90,7 @@ export const showTransparentSchema = toolSchema({ const showTransparent: ToolEntry = { id: "show-transparent-png", + slug: "show-transparent-png", title: "Show Transparent Areas PNG", description: "Highlights every transparent or semi-transparent pixel with the chosen color so gaps become obvious.", @@ -115,6 +117,7 @@ export const showGrayscalePixelsSchema = toolSchema({ const showGrayscalePixels: ToolEntry = { id: "show-grayscale-pixels-png", + slug: "show-grayscale-pixels-png", title: "Show Grayscale Pixels PNG", description: "Finds pixels whose channels are nearly equal and renders them as a mask. Tolerance is in channel units.", @@ -143,6 +146,7 @@ export const showColorPixelsSchema = toolSchema({ const showColorPixels: ToolEntry = { id: "show-color-pixels-png", + slug: "show-color-pixels-png", title: "Show Color Pixels PNG", description: "Finds colored (non-gray) pixels beyond the channel tolerance and renders them as a mask.", @@ -171,6 +175,7 @@ export const lightPixelMaskSchema = toolSchema({ const lightPixelMask: ToolEntry = { id: "light-pixel-mask-png", + slug: "light-pixel-mask-png", title: "Light Pixel Mask PNG", description: "Selects pixels brighter than the luminance threshold.", category: "analyze", @@ -198,6 +203,7 @@ export const darkPixelMaskSchema = toolSchema({ const darkPixelMask: ToolEntry = { id: "dark-pixel-mask-png", + slug: "dark-pixel-mask-png", title: "Dark Pixel Mask PNG", description: "Selects pixels darker than the luminance threshold.", category: "analyze", @@ -225,6 +231,7 @@ export const uniqueColorMaskSchema = toolSchema({ const uniqueColorMask: ToolEntry = { id: "unique-color-mask-png", + slug: "unique-color-mask-png", title: "Unique Color Mask PNG", description: "Selects colors that occur no more than the given number of times — rare and one-off pixels.", @@ -240,6 +247,7 @@ const emptySchema = toolSchema({}); const verifyIsPng: ToolEntry = { id: "verify-is-png", + slug: "verify-is-png", title: "Verify If Image Is a PNG", description: "Checks the signature of pasted base64 / data-uri content and reports whether it is a real PNG.", @@ -254,6 +262,7 @@ const verifyIsPng: ToolEntry = { const pngIsGrayscale: ToolEntry = { id: "png-is-grayscale", + slug: "png-is-grayscale", title: "Check: is PNG grayscale?", description: "Reports whether the image consists only of shades of gray.", category: "analyze", @@ -265,6 +274,7 @@ const pngIsGrayscale: ToolEntry = { const pngFileSize: ToolEntry = { id: "png-file-size", + slug: "png-file-size", title: "PNG File Size", description: "Encodes the image as PNG and reports the resulting file size.", category: "analyze", @@ -282,6 +292,7 @@ const pngFileSize: ToolEntry = { const pngIsTransparent: ToolEntry = { id: "png-is-transparent", + slug: "png-is-transparent", title: "Check: is PNG transparent?", description: "Reports whether the image contains transparent or semi-transparent pixels.", @@ -296,6 +307,7 @@ const pngIsTransparent: ToolEntry = { const pngOrientation: ToolEntry = { id: "png-orientation", + slug: "png-orientation", title: "PNG orientation", description: "Reports whether it is portrait, landscape or square.", category: "analyze", diff --git a/web/src/lib/registry/color.ts b/web/src/lib/registry/color.ts index f8feca4..29ce974 100644 --- a/web/src/lib/registry/color.ts +++ b/web/src/lib/registry/color.ts @@ -45,6 +45,7 @@ export const twoColorsSchema = toolSchema({ const twoColorsTool: ToolEntry = { id: "two-colors-png", + slug: "two-colors-png", title: "Two colors PNG", description: "Recolors the image into two chosen colors by luminance threshold.", @@ -70,6 +71,7 @@ export const gammaSchema = toolSchema({ const gammaTool: ToolEntry = { id: "gamma-png", + slug: "gamma-png", title: "Gamma correction PNG", description: "Corrects midtone brightness. <1 darker, >1 lighter, 1 — unchanged.", @@ -95,6 +97,7 @@ export const temperatureSchema = toolSchema({ const temperatureTool: ToolEntry = { id: "temperature-png", + slug: "temperature-png", title: "Temperature PNG", description: "Positive values make the image warmer (more orange), negative ones cooler (more blue).", @@ -122,6 +125,7 @@ export const tintSchema = toolSchema({ const tintTool: ToolEntry = { id: "tint-png", + slug: "tint-png", title: "Tint PNG", description: "Multiplies color channels by the chosen tint with the given strength.", @@ -147,6 +151,7 @@ export const quantizeSchema = toolSchema({ const quantizeTool: ToolEntry = { id: "quantize-png", + slug: "quantize-png", title: "Quantize PNG", description: "Reduces the image to k colors via median-cut palette. Transparent pixels are preserved.", @@ -166,6 +171,7 @@ export const customPaletteSchema = toolSchema({ const customPalette: ToolEntry = { id: "custom-palette-png", + slug: "custom-palette-png", title: "Custom Palette PNG", description: "Maps every pixel to the nearest color from your comma-separated hex list.", @@ -200,6 +206,7 @@ export const ditheringSchema = toolSchema({ const ditheringTool: ToolEntry = { id: "dithering-png", + slug: "dithering-png", title: "Dithering PNG", description: "Applies Floyd–Steinberg error diffusion or ordered Bayer dithering while reducing to k colors.", @@ -215,6 +222,7 @@ export const grayscaleSchema = toolSchema({}); const grayscaleTool: ToolEntry = { id: "grayscale-png", + slug: "grayscale-png", title: "Grayscale PNG", description: "Converts the image to shades of gray using the BT.601 luminance formula. Alpha is preserved.", @@ -228,6 +236,7 @@ export const invertColorsSchema = toolSchema({}); const invertColorsTool: ToolEntry = { id: "invert-colors-png", + slug: "invert-colors-png", title: "Invert colors PNG", description: "Inverts each color channel (255 − value). Alpha is unchanged.", category: "color", @@ -269,6 +278,7 @@ export const brightnessContrastSchema = toolSchema( const brightnessContrastTool: ToolEntry = { id: "adjust-brightness-contrast-png", + slug: "adjust-brightness-contrast-png", title: "Brightness & contrast PNG", description: "Adjusts brightness and contrast in the range from −100 to +100. Zero means no change.", @@ -294,6 +304,7 @@ export const opacitySchema = toolSchema({ const opacityTool: ToolEntry = { id: "change-png-opacity", + slug: "change-png-opacity", title: "Change PNG opacity", description: "Multiplies the alpha channel by a percentage: 0% — fully transparent, 100% — unchanged.", @@ -307,6 +318,7 @@ export const sepiaSchema = toolSchema({}); const sepiaTool: ToolEntry = { id: "sepia-png", + slug: "sepia-png", title: "Sepia effect", description: "Tints the image into the warm brown tones of classic sepia.", category: "color", @@ -331,6 +343,7 @@ export const hueShiftSchema = toolSchema({ const hueShiftTool: ToolEntry = { id: "change-png-hue", + slug: "change-png-hue", title: "Change hue PNG", description: "Shifts the hue around the circle. Saturation and lightness are preserved.", @@ -358,6 +371,7 @@ export const extractChannelSchema = toolSchema({ const extractChannelTool: ToolEntry = { id: "extract-channel-png", + slug: "extract-channel-png", title: "Extract channel PNG", description: "Keeps only the chosen channel — red, green or blue — as shades of gray.", @@ -385,6 +399,7 @@ export const swapChannelsSchema = toolSchema({ const swapChannelsTool: ToolEntry = { id: "swap-channels-png", + slug: "swap-channels-png", title: "Swap channels PNG", description: "Swaps two color channels — a quick way to get unusual coloring.", @@ -410,6 +425,7 @@ export const blackAndWhiteSchema = toolSchema({ const blackAndWhiteTool: ToolEntry = { id: "black-and-white-png", + slug: "black-and-white-png", title: "Black & white threshold PNG", description: "Hard binarization by luminance: every pixel becomes black or white.", @@ -435,6 +451,7 @@ export const posterizeSchema = toolSchema({ const posterizeTool: ToolEntry = { id: "posterize-png", + slug: "posterize-png", title: "Posterize PNG", description: "Reduces the number of levels per channel — a poster effect.", category: "color", @@ -447,6 +464,7 @@ export const autoContrastSchema = toolSchema({}); const autoContrastTool: ToolEntry = { id: "auto-contrast-png", + slug: "auto-contrast-png", title: "Auto contrast PNG", description: "Stretches each channel's range across the full available brightness range.", @@ -483,6 +501,7 @@ export const decreaseColorCountSchema = toolSchema({ const decreaseColorCountTool: ToolEntry = { id: "decrease-color-count-png", + slug: "decrease-color-count-png", title: "Decrease Color Count PNG", description: "Median-cut engine as a quick way to drop to 2–256 colors. Presets marked (extreme/strong/balanced/light) match the classic compression levels.", @@ -499,7 +518,6 @@ interface ChannelParams { type SpaceEntry = { id: SpaceId; - suffix: string; title: string; description: string; }; @@ -507,42 +525,36 @@ type SpaceEntry = { const CHANNEL_SPACES: SpaceEntry[] = [ { id: "hsl", - suffix: "hsl", title: "Split PNG into HSL", description: "Decomposes the image into Hue, Saturation and Lightness components.", }, { id: "hsv", - suffix: "hsv", title: "Split PNG into HSV", description: "Decomposes the image into Hue, Saturation and Value (brightness) components.", }, { id: "hsi", - suffix: "hsi", title: "Split PNG into HSI", description: "Decomposes the image into Hue, Saturation and Intensity components.", }, { id: "cmyk", - suffix: "cmyk", title: "Convert PNG to CMYK Colors", description: "Decomposes the image into print-style Cyan, Magenta, Yellow and Key (black) components.", }, { id: "ycbcr", - suffix: "ycbcr", title: "Convert PNG to YCbCr Colors", description: "Decomposes the image into Luma (Y) and Blue-difference / Red-difference chroma components.", }, { id: "lab", - suffix: "lab", title: "Convert PNG to LAB Colors", description: "Decomposes the image into perceptual Lightness and green–magenta / blue–yellow opponents.", @@ -553,7 +565,8 @@ function channelEntries(): ToolEntry[] { return CHANNEL_SPACES.map((space) => { const components = SPACES[space.id].components; return { - id: `png-to-${space.suffix}`, + id: `png-to-${space.id}`, + slug: `png-to-${space.id}`, title: space.title, description: space.description, category: "color", diff --git a/web/src/lib/registry/convert.ts b/web/src/lib/registry/convert.ts index b826063..5c32c1d 100644 --- a/web/src/lib/registry/convert.ts +++ b/web/src/lib/registry/convert.ts @@ -40,6 +40,7 @@ export const convertToJpgSchema = toolSchema({ const convertToJpg: ToolEntry = { id: "convert-png-to-jpg", + slug: "convert-png-to-jpg", title: "Convert PNG to JPG", description: "Transparency is composited over the chosen backdrop color (white by default) and saved as JPEG.", @@ -66,6 +67,7 @@ export const convertToWebpSchema = toolSchema({ const convertToWebp: ToolEntry = { id: "convert-png-to-webp", + slug: "convert-png-to-webp", title: "Convert PNG to WebP", description: "Re-encodes the image into WebP with adjustable quality. Transparency is preserved.", @@ -82,6 +84,7 @@ export const convertToBmpSchema = toolSchema({}); const convertToBmp: ToolEntry = { id: "png-to-bmp", + slug: "png-to-bmp", title: "Convert PNG to BMP", description: "Saves the image as 24-bit BMP without an alpha channel: transparency is replaced with a black background.", @@ -98,6 +101,7 @@ const emptySchema = toolSchema({}); const pngToBase64: ToolEntry = { id: "png-to-base64", + slug: "png-to-base64", title: "PNG to Base64", description: "Encodes the image into a base64 string for embedding in code or styles.", @@ -111,6 +115,7 @@ const pngToBase64: ToolEntry = { const pngToDataUri: ToolEntry = { id: "png-to-data-uri", + slug: "png-to-data-uri", title: "PNG to Data URI", description: "Builds a full data-uri (data:image/png;base64,…) for embedding in HTML/CSS.", @@ -124,6 +129,7 @@ const pngToDataUri: ToolEntry = { const pngToHex: ToolEntry = { id: "png-to-hex", + slug: "png-to-hex", title: "PNG to HEX pixels", description: "Shows all pixels as rrggbbaa hex values — row by row, space separated.", @@ -136,6 +142,7 @@ const pngToHex: ToolEntry = { const pngToBytes: ToolEntry = { id: "png-to-bytes", + slug: "png-to-bytes", title: "PNG to Bytes", description: "Lists every pixel as four decimal bytes (R G B A), one image row per line.", @@ -148,6 +155,7 @@ const pngToBytes: ToolEntry = { const pngToRgbValues: ToolEntry = { id: "png-to-rgb-values", + slug: "png-to-rgb-values", title: "PNG to RGB Values", description: "Lists every pixel as rgba(r, g, b, a), one image row per line.", category: "convert", @@ -159,6 +167,7 @@ const pngToRgbValues: ToolEntry = { const base64ToPng: ToolEntry = { id: "base64-to-png", + slug: "base64-to-png", title: "Base64 to PNG", description: "Decodes a base64 string or data-uri back into an image. Paste the string on the left.", @@ -171,6 +180,7 @@ const base64ToPng: ToolEntry = { const dataUriToPng: ToolEntry = { id: "data-uri-to-png", + slug: "data-uri-to-png", title: "Data URI to PNG", description: "Decodes data:image/…;base64,… back into an image file.", category: "convert", @@ -190,6 +200,7 @@ export const hexToPngSchema = toolSchema({ const hexToPng: ToolEntry = { id: "hex-to-png", + slug: "hex-to-png", title: "HEX pixels to PNG", description: "Assembles an image from rrggbbaa hex values (space separated). Set the width — the height is computed automatically.", @@ -209,6 +220,7 @@ export const bytesToPngSchema = toolSchema({ const bytesToPng: ToolEntry = { id: "bytes-to-png", + slug: "bytes-to-png", title: "Bytes to PNG", description: "Assembles an image from decimal RGBA byte numbers (any separators). Set the width — height is computed automatically.", @@ -228,6 +240,7 @@ export const rgbValuesToPngSchema = toolSchema({ const rgbValuesToPng: ToolEntry = { id: "rgb-values-to-png", + slug: "rgb-values-to-png", title: "RGB Values to PNG", description: "Assembles an image from rgba(r, g, b, a) numbers. Set the width — height is computed automatically.", @@ -247,6 +260,7 @@ export const svgToPngSchema = toolSchema({ const svgToPng: ToolEntry = { id: "svg-to-png", + slug: "svg-to-png", title: "SVG to PNG", description: "Decodes SVG markup into a raster image. Paste the SVG code on the left.", diff --git a/web/src/lib/registry/filters.ts b/web/src/lib/registry/filters.ts index e4c57c9..7fef2d4 100644 --- a/web/src/lib/registry/filters.ts +++ b/web/src/lib/registry/filters.ts @@ -26,6 +26,7 @@ export const blurSchema = toolSchema({ const blurTool: ToolEntry = { id: "blur-png", + slug: "blur-png", title: "Blur PNG", description: "Gaussian blur: three passes of separable box blur — fast at any radius. Transparent edges do not darken.", @@ -51,6 +52,7 @@ export const sharpenSchema = toolSchema({ const sharpenTool: ToolEntry = { id: "sharpen-png", + slug: "sharpen-png", title: "Sharpen PNG", description: "Emphasizes edges with a sharpening kernel; strength sets the blend with the original. 0% means no change.", @@ -78,6 +80,7 @@ export const silhouetteSchema = toolSchema({ const silhouetteTool: ToolEntry = { id: "silhouette-png", + slug: "silhouette-png", title: "Silhouette PNG", description: "Turns all visible pixels into a single solid color while keeping their transparency — instant silhouette.", @@ -103,6 +106,7 @@ export const vignetteSchema = toolSchema({ const vignetteTool: ToolEntry = { id: "vignette-png", + slug: "vignette-png", title: "Vignette PNG", description: "Smoothly darkens the edges of the image, leaving the center untouched.", @@ -128,6 +132,7 @@ export const pixelateSchema = toolSchema({ const pixelateTool: ToolEntry = { id: "pixelate-png", + slug: "pixelate-png", title: "Pixelate PNG", description: "Averages every blockSize×blockSize area into one color — classic mosaic.", @@ -170,6 +175,7 @@ export const randomizePixelsSchema = toolSchema( const randomizePixels: ToolEntry = { id: "randomize-pixels-png", + slug: "randomize-pixels-png", title: "Randomize Pixels PNG", description: "Shuffles blocks of the image between positions. Same seed gives the same arrangement.", @@ -222,6 +228,7 @@ export const addNoiseSchema = toolSchema( const addNoiseTool: ToolEntry = { id: "add-noise-png", + slug: "add-noise-png", title: "Add Noise to PNG", description: "Adds film-grain style noise. Deterministic by seed; monochrome keeps original hue balance.", @@ -247,6 +254,7 @@ export const jpegArtifactsSchema = toolSchema({ const jpegArtifacts: ToolEntry = { id: "jpeg-artifacts-png", + slug: "jpeg-artifacts-png", title: "JPEG artifacts", description: "Simulates low-quality JPEG re-compression — visible blocks and smeared colors.", diff --git a/web/src/lib/registry/generate.ts b/web/src/lib/registry/generate.ts index b38ff75..f04e41a 100644 --- a/web/src/lib/registry/generate.ts +++ b/web/src/lib/registry/generate.ts @@ -102,6 +102,7 @@ export const createEmptySchema = toolSchema( const createEmpty: ToolEntry = { id: "create-empty-png", + slug: "create-empty-png", title: "Create empty PNG", description: "Creates a blank canvas of the chosen dimensions, either transparent or filled with a solid color.", @@ -136,6 +137,7 @@ export const singleColorSchema = toolSchema({ const singleColor: ToolEntry = { id: "single-color-png", + slug: "single-color-png", title: "Create solid color PNG", description: "Generates a rectangle of the given size and color.", category: "generate", @@ -172,6 +174,7 @@ export const randomNoiseSchema = toolSchema({ const randomNoise: ToolEntry = { id: "random-noise-png", + slug: "random-noise-png", title: "Create random noise PNG", description: "Generates an image with random pixels. The seed fixes the result: one seed — one image.", @@ -218,6 +221,7 @@ export const linearGradientSchema = toolSchema( const linearGradient: ToolEntry = { id: "linear-gradient-png", + slug: "linear-gradient-png", title: "Create gradient PNG", description: "Generates a smooth transition between two colors along a chosen angle.", @@ -291,6 +295,7 @@ export const colorSpectrumSchema = toolSchema( const colorSpectrumTool: ToolEntry = { id: "color-spectrum-png", + slug: "color-spectrum-png", title: "Color Spectrum PNG", description: "Full hue rainbow 0–360° along the chosen axis with adjustable saturation and lightness.", @@ -346,6 +351,7 @@ export const randomColorsSchema = toolSchema( const randomColors: ToolEntry = { id: "random-colors-png", + slug: "random-colors-png", title: "Random Color Blocks PNG", description: "Fills the canvas with random vivid color blocks. Deterministic by seed.", @@ -420,6 +426,7 @@ export const drawGridSchema = toolSchema( const drawGridTool: ToolEntry = { id: "draw-grid-png", + slug: "draw-grid-png", title: "Draw Grid PNG", description: "Draws a grid with custom columns, rows and line width on a transparent or white background.", @@ -481,6 +488,7 @@ export const placeholderSchema = toolSchema( const placeholder: ToolEntry = { id: "placeholder-png", + slug: "placeholder-png", title: "Create Placeholder PNG", description: "Generates a placeholder rectangle with its dimensions printed in the center.", @@ -531,6 +539,7 @@ export const blendTwoSchema = toolSchema({ const blendTwo: ToolEntry = { id: "blend-two-png", + slug: "blend-two-png", title: "Blend Two Colors PNG", description: "A continuous horizontal gradient between two colors.", category: "generate", @@ -592,6 +601,7 @@ export const stepColorsSchema = toolSchema( const stepColorsTool: ToolEntry = { id: "step-colors-png", + slug: "step-colors-png", title: "Color Steps PNG", description: "A discrete set of evenly spaced steps between two colors.", category: "generate", @@ -624,6 +634,7 @@ export const emojiToPngSchema = toolSchema({ const emojiToPng: ToolEntry = { id: "emoji-to-png", + slug: "emoji-to-png", title: "Emoji to PNG", description: "Renders an emoji or any Unicode symbol as a transparent PNG of the chosen size.", @@ -658,6 +669,7 @@ export const colorWheelSchema = toolSchema({ const colorWheelTool: ToolEntry = { id: "color-wheel-png", + slug: "color-wheel-png", title: "Color Wheel PNG", description: "Generates an HSL color wheel: hue around the circle, saturation from center to edge, chosen lightness.", @@ -707,6 +719,7 @@ const paletteLayoutGroup = { const complementaryTool: ToolEntry = { id: "complementary-png", + slug: "complementary-png", title: "Complementary Palette PNG", description: "Two opposite colors on the color wheel — the base and its complement.", @@ -723,6 +736,7 @@ const complementaryTool: ToolEntry = { const triadicTool: ToolEntry = { id: "triadic-png", + slug: "triadic-png", title: "Triadic Palette PNG", description: "Three colors evenly spaced 120° apart on the color wheel.", category: "generate", @@ -741,6 +755,7 @@ const triadicTool: ToolEntry = { const tetradicTool: ToolEntry = { id: "tetradic-png", + slug: "tetradic-png", title: "Tetradic Palette PNG", description: "Four colors in two complementary pairs, 90° apart on the wheel.", @@ -765,6 +780,7 @@ interface AnalogousParams extends PaletteBaseParams { const analogousTool: ToolEntry = { id: "analogous-png", + slug: "analogous-png", title: "Analogous Palette PNG", description: "Neighboring hues around the base color — calm, related color scheme.", @@ -807,6 +823,7 @@ interface MonochromaticParams extends PaletteBaseParams { const monochromaticTool: ToolEntry = { id: "monochromatic-png", + slug: "monochromatic-png", title: "Monochromatic Palette PNG", description: "Tones of a single hue: lightness varies within the chosen range, hue and saturation stay fixed.", @@ -849,6 +866,7 @@ interface ShadesParams extends PaletteBaseParams { const shadesTool: ToolEntry = { id: "shades-png", + slug: "shades-png", title: "Shade Ramp PNG", description: "A ramp of the base color getting darker step by step.", category: "generate", @@ -910,6 +928,7 @@ export const mixColorsSchema = toolSchema( const mixColorsTool: ToolEntry = { id: "mix-colors-png", + slug: "mix-colors-png", title: "Mix Colors PNG", description: "Averages the selected colors into one swatch. Colors become one uniform fill.", @@ -980,6 +999,7 @@ export const sortColorsSchema = toolSchema( const sortColorsTool: ToolEntry = { id: "sort-colors-png", + slug: "sort-colors-png", title: "Sort Colors PNG", description: "Renders the chosen colors as swatches sorted by hue, brightness or saturation.", @@ -1047,6 +1067,7 @@ export const textToPngSchema = toolSchema( const textToPng: ToolEntry = { id: "text-to-png", + slug: "text-to-png", title: "Text to PNG", description: "Creates a PNG image from text: the canvas is sized to fit the label plus padding.", diff --git a/web/src/lib/registry/geometry.ts b/web/src/lib/registry/geometry.ts index e5b7bd0..2da83ca 100644 --- a/web/src/lib/registry/geometry.ts +++ b/web/src/lib/registry/geometry.ts @@ -49,6 +49,7 @@ export const addBorderSchema = toolSchema({ const addBorder: ToolEntry = { id: "add-border-png", + slug: "add-border-png", title: "Add border to PNG", description: "Draws a colored frame of the chosen thickness around the image.", @@ -100,6 +101,7 @@ export const fitOnBackgroundSchema = toolSchema( const fitOnBackground: ToolEntry = { id: "fit-on-background-png", + slug: "fit-on-background-png", title: "Fit PNG onto background", description: "Places the image centered on a canvas of the given size with a transparent or colored background.", @@ -167,6 +169,7 @@ export const changeCanvasSizeSchema = toolSchema( const changeCanvasSizeTool: ToolEntry = { id: "change-canvas-size-png", + slug: "change-canvas-size-png", title: "Change Canvas Size PNG", description: "Sets the exact canvas size: overflow is cropped, missing space is filled with transparency. Anchor picks which part of the image stays.", @@ -212,6 +215,7 @@ export const resizeSchema = toolSchema( const resizeTool: ToolEntry = { id: "resize-png", + slug: "resize-png", title: "Resize PNG", description: "Scales the image with bilinear interpolation. With aspect kept, one side defines the scale; if both are set, the image fits inside them.", @@ -283,6 +287,7 @@ export const cropSchema = toolSchema( const cropTool: ToolEntry = { id: "crop-png", + slug: "crop-png", title: "Crop PNG", description: "Cuts out a rectangular area. Coordinates and sizes may go beyond the image — the area is clipped to the intersection.", @@ -317,6 +322,7 @@ export const rotateSchema = toolSchema({ const rotateTool: ToolEntry = { id: "rotate-png", + slug: "rotate-png", title: "Rotate PNG", description: "Rotates by 90°, 180° or 270° clockwise without quality loss.", category: "geometry", @@ -342,6 +348,7 @@ export const flipSchema = toolSchema({ const flipTool: ToolEntry = { id: "flip-png", + slug: "flip-png", title: "Flip PNG", description: "Mirrors horizontally or vertically without quality loss.", category: "geometry", @@ -380,6 +387,7 @@ export const addPaddingSchema = toolSchema( const addPaddingTool: ToolEntry = { id: "add-padding-png", + slug: "add-padding-png", title: "Add padding to PNG", description: "Expands the canvas on all sides by the chosen number of pixels.", @@ -422,6 +430,7 @@ export const tileSchema = toolSchema({ const tileTool: ToolEntry = { id: "tile-png", + slug: "tile-png", title: "Tile PNG", description: "Repeats the image in a grid of the chosen columns and rows.", category: "geometry", @@ -456,6 +465,7 @@ export const splitPartsSchema = toolSchema({ const splitPartsTool: ToolEntry = { id: "split-into-parts-png", + slug: "split-into-parts-png", title: "Split PNG into parts", description: "Divides the image into a grid of equal-sized parts. The canvas is padded with transparency to keep every part the same size.", @@ -493,6 +503,7 @@ export const centerByAlphaSchema = toolSchema({}); const centerByAlphaTool: ToolEntry = { id: "center-by-alpha-png", + slug: "center-by-alpha-png", title: "Center PNG by content", description: "Finds the opaque part of the image and centers it on the original canvas.", @@ -526,6 +537,7 @@ export const skewSchema = toolSchema({ const skewTool: ToolEntry = { id: "skew-png", + slug: "skew-png", title: "Skew PNG", description: "Shifts content horizontally and vertically — a perspective effect.", @@ -551,6 +563,7 @@ export const rotateFreeSchema = toolSchema({ const rotateFreeTool: ToolEntry = { id: "rotate-free-png", + slug: "rotate-free-png", title: "Rotate by custom angle", description: "Rotation by any angle. The canvas grows to fit the new bounds; corners stay transparent.", @@ -576,6 +589,7 @@ export const zoomSchema = toolSchema({ const zoomTool: ToolEntry = { id: "zoom-png", + slug: "zoom-png", title: "Zoom PNG", description: "Magnifies content toward the center. The canvas keeps its size — edges are cropped.", @@ -601,6 +615,7 @@ export const trimEmptySpaceSchema = toolSchema({ const trimEmptySpaceTool: ToolEntry = { id: "trim-empty-space-png", + slug: "trim-empty-space-png", title: "Trim Empty Space PNG", description: "Crops transparent borders around the content. Pixels with alpha above the threshold count as content.", @@ -643,6 +658,7 @@ export const changeAspectRatioSchema = toolSchema({ const changeAspectRatioTool: ToolEntry = { id: "change-aspect-ratio-png", + slug: "change-aspect-ratio-png", title: "Change Aspect Ratio PNG", description: "Fits the image into a target aspect ratio: crop the center to fill, or pad with transparency.", @@ -673,6 +689,7 @@ export const swapOrientationSchema = toolSchema({ const swapOrientationTool: ToolEntry = { id: "swap-orientation-png", + slug: "swap-orientation-png", title: "Swap Orientation PNG", description: "Rotates the image by 90° when its orientation differs from the target — landscape becomes portrait and back. Square images are untouched.", @@ -713,6 +730,7 @@ export const symmetricCopySchema = toolSchema({ const symmetricCopyTool: ToolEntry = { id: "symmetric-copy-png", + slug: "symmetric-copy-png", title: "Symmetric Copy PNG", description: "Doubles the canvas by mirroring the kept side onto the empty half — instant symmetric pattern.", @@ -748,6 +766,7 @@ export const shiftSchema = toolSchema({ const shiftTool: ToolEntry = { id: "shift-png", + slug: "shift-png", title: "Shift PNG", description: "Moves content by the given X and Y offset.", category: "geometry", diff --git a/web/src/lib/registry/index.ts b/web/src/lib/registry/index.ts index ff89d7a..fbb5265 100644 --- a/web/src/lib/registry/index.ts +++ b/web/src/lib/registry/index.ts @@ -42,3 +42,8 @@ export const TOOLS: ToolEntry[] = [ export function getTool(id: string): ToolEntry | undefined { return TOOLS.find((tool) => tool.id === id); } + +/** Поиск по адресу страницы; `getTool` остаётся поиском по внутреннему имени. */ +export function getToolBySlug(slug: string): ToolEntry | undefined { + return TOOLS.find((tool) => tool.slug === slug); +} diff --git a/web/src/lib/registry/registry.test.ts b/web/src/lib/registry/registry.test.ts index 9eea41b..34d7466 100644 --- a/web/src/lib/registry/registry.test.ts +++ b/web/src/lib/registry/registry.test.ts @@ -19,6 +19,21 @@ describe("registry-new (переведённые инструменты)", () => expect(new Set(ids).size).toBe(ids.length); }); + it("slug уникальны", () => { + const slugs = TOOLS.map((t) => t.slug); + expect(new Set(slugs).size).toBe(slugs.length); + }); + + it("id и slug пригодны для адреса и для кода", () => { + // Тот же формат, что разбирает `tools-map-doc.test.ts`: латиница в + // нижнем регистре, слова через дефис. + const shape = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/; + for (const tool of TOOLS) { + expect(shape.test(tool.id), `id: ${tool.id}`).toBe(true); + expect(shape.test(tool.slug), `slug: ${tool.slug}`).toBe(true); + } + }); + it("PREVIEW_GROUPS строится без ошибок", () => { expect(PREVIEW_GROUPS.length).toBeGreaterThan(0); expect(PREVIEW_GROUPS.reduce((n, g) => n + g.tools.length, 0)).toBe( diff --git a/web/src/lib/registry/text.ts b/web/src/lib/registry/text.ts index f051c47..1b36f05 100644 --- a/web/src/lib/registry/text.ts +++ b/web/src/lib/registry/text.ts @@ -60,6 +60,7 @@ const addTextSchema = toolSchema( const addText: ToolEntry = { id: "add-text-png", + slug: "add-text-png", title: "Add text to PNG", description: "Draws a text label on the image: font, size, color, bold, position on a 3×3 grid and an optional backing plate.", @@ -138,6 +139,7 @@ const dateStampSchema = toolSchema( const dateStamp: ToolEntry = { id: "date-stamp-png", + slug: "date-stamp-png", title: "Date stamp PNG", description: "Stamps the current date and time using a format string (YYYY MM DD hh mm ss tokens). Same styling options as Add text.", @@ -231,6 +233,7 @@ const watermarkTileSchema = toolSchema( const watermarkTile: ToolEntry = { id: "watermark-tile-png", + slug: "watermark-tile-png", title: "Watermark Tile PNG", description: "Covers the image with a repeating diagonal semi-transparent text tile — a protection watermark.", diff --git a/web/src/lib/registry/types.ts b/web/src/lib/registry/types.ts index 2400bb4..254d472 100644 --- a/web/src/lib/registry/types.ts +++ b/web/src/lib/registry/types.ts @@ -70,7 +70,13 @@ export type ToolResult = PixelImage | string | FileResult | VerdictResult; * `generate`/`runFromText`/`toText`/`textToText` нет. */ export type ToolEntry

> = { + /** Внутреннее имя: ключ i18n `tools..*`, `toolId` worker-протокола, запись пайплайна. */ id: string; + /** + * Адрес страницы `/tools/` и база имени скачиваемого файла. Инструмент + * работает с любым форматом, поэтому png-интент держит slug, а не id. + */ + slug: string; title: string; description: string; category: CategoryId;