mirror of
https://github.com/pheralb/svgl.git
synced 2025-12-29 08:01:36 +08:00
Merge branch 'main' into main
This commit is contained in:
+23
-5
@@ -6,13 +6,13 @@
|
||||
:root {
|
||||
--sb-track-color: rgb(229 229 229 / 0.5);
|
||||
--sb-thumb-color: #d4d4d4;
|
||||
--sb-size: 8px;
|
||||
--sb-size: 10px;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--sb-track-color: #171717;
|
||||
--sb-thumb-color: #404040;
|
||||
--sb-size: 8px;
|
||||
--sb-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,18 @@
|
||||
background: var(--sb-thumb-color);
|
||||
}
|
||||
|
||||
aside::-webkit-scrollbar {
|
||||
width: var(--sb-size);
|
||||
}
|
||||
|
||||
aside::-webkit-scrollbar-track {
|
||||
background: var(--sb-track-color);
|
||||
}
|
||||
|
||||
aside::-webkit-scrollbar-thumb {
|
||||
background: var(--sb-thumb-color);
|
||||
}
|
||||
|
||||
nav::-webkit-scrollbar {
|
||||
width: var(--sb-size);
|
||||
}
|
||||
@@ -48,9 +60,15 @@
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'General-Sans';
|
||||
src: url('/fonts/GeneralSans-Variable.woff2') format('woff2');
|
||||
font-weight: 200 700;
|
||||
font-family: 'InterVariable';
|
||||
src: url('/fonts/InterVariable.woff2') format('woff2');
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'GeistMono';
|
||||
src: url('/fonts/GeistMonoVariableVF.woff2') format('woff2');
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
+9
-1
@@ -5,6 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="author" content="@pheralb_" />
|
||||
<meta name="description" content="A beautiful library with SVG logos" />
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/images/logo.svg" />
|
||||
@@ -23,13 +24,20 @@
|
||||
<meta name="twitter:creator" content="@pheralb_" />
|
||||
<meta name="twitter:image" content="https://svgl.vercel.app/images/screenshot.png" />
|
||||
|
||||
<!-- Analytics -->
|
||||
<script
|
||||
async
|
||||
src="https://umami.pheralb.dev/script.js"
|
||||
data-website-id="50de464e-cf2c-4b76-b5e8-21c9259bc7be"
|
||||
></script>
|
||||
|
||||
<!-- Title -->
|
||||
<title>A beautiful library with SVG logos - Svgl</title>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body
|
||||
data-sveltekit-preload-data="hover"
|
||||
class="min-h-screen bg-light font-sans text-mini dark:bg-dark dark:text-white antialiased"
|
||||
class="min-h-screen bg-white font-sans text-mini dark:bg-dark dark:text-white antialiased selection:bg-neutral-200 dark:selection:bg-neutral-700"
|
||||
>
|
||||
<div>%sveltekit.body%</div>
|
||||
</body>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
on:blur={handleBlur}
|
||||
on:mouseenter={handleMouseEnter}
|
||||
on:mouseleave={handleMouseLeave}
|
||||
class="relative flex items-center justify-center overflow-hidden rounded-md border border-neutral-200 dark:border-neutral-800 bg-neutral-100 dark:bg-neutral-900"
|
||||
class="relative flex items-center justify-center overflow-hidden rounded-md border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900"
|
||||
>
|
||||
<div
|
||||
class="pointer-events-none absolute -inset-px opacity-0 transition duration-300"
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
type methodType = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
||||
|
||||
export let method: methodType;
|
||||
export let title: string;
|
||||
export let description: string;
|
||||
</script>
|
||||
|
||||
<div class={cn('border-2 border-neutral-100 dark:border-neutral-800 rounded-lg', 'p-4 mb-2')}>
|
||||
<div class="flex items-center space-x-4 mb-4">
|
||||
<p
|
||||
class={cn(
|
||||
'm-0 rounded-md font-medium px-1.5 py-0.5 text-sm leading-5',
|
||||
method === 'GET' &&
|
||||
' text-green-600 dark:text-green-500 bg-green-400/20 dark:bg-green-400/20',
|
||||
method === 'POST' && ' text-blue-600 dark:text-blue-500 bg-blue-400/20 dark:bg-blue-400/20',
|
||||
method === 'PUT' &&
|
||||
' text-yellow-600 dark:text-yellow-500 bg-yellow-400/20 dark:bg-yellow-400/20',
|
||||
method === 'PATCH' &&
|
||||
' text-yellow-600 dark:text-yellow-500 bg-yellow-400/20 dark:bg-yellow-400/20',
|
||||
method === 'DELETE' && ' text-red-600 dark:text-red-500 bg-red-400/20 dark:bg-red-400/20'
|
||||
)}
|
||||
>
|
||||
{method}
|
||||
</p>
|
||||
<div class="flex flex-col space-y-0 m-0">
|
||||
<h3 class="m-0 font-medium">{title}</h3>
|
||||
<p class="mb-0 font-mono text-sm">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
export let iconSize: number;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width={iconSize || 16}
|
||||
height={iconSize || 16}
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid"
|
||||
><path
|
||||
d="M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46 6.397 1.185 8.746-2.777 8.746-6.158 0-3.052-.12-13.135-.174-23.83-35.61 7.742-43.124-15.103-43.124-15.103-5.823-14.795-14.213-18.73-14.213-18.73-11.613-7.944.876-7.78.876-7.78 12.853.902 19.621 13.19 19.621 13.19 11.417 19.568 29.945 13.911 37.249 10.64 1.149-8.272 4.466-13.92 8.127-17.116-28.431-3.236-58.318-14.212-58.318-63.258 0-13.975 5-25.394 13.188-34.358-1.329-3.224-5.71-16.242 1.24-33.874 0 0 10.749-3.44 35.21 13.121 10.21-2.836 21.16-4.258 32.038-4.307 10.878.049 21.837 1.47 32.066 4.307 24.431-16.56 35.165-13.12 35.165-13.12 6.967 17.63 2.584 30.65 1.255 33.873 8.207 8.964 13.173 20.383 13.173 34.358 0 49.163-29.944 59.988-58.447 63.157 4.591 3.972 8.682 11.762 8.682 23.704 0 17.126-.148 30.91-.148 35.126 0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002 256 57.307 198.691 0 128.001 0Zm-80.06 182.34c-.282.636-1.283.827-2.194.39-.929-.417-1.45-1.284-1.15-1.922.276-.655 1.279-.838 2.205-.399.93.418 1.46 1.293 1.139 1.931Zm6.296 5.618c-.61.566-1.804.303-2.614-.591-.837-.892-.994-2.086-.375-2.66.63-.566 1.787-.301 2.626.591.838.903 1 2.088.363 2.66Zm4.32 7.188c-.785.545-2.067.034-2.86-1.104-.784-1.138-.784-2.503.017-3.05.795-.547 2.058-.055 2.861 1.075.782 1.157.782 2.522-.019 3.08Zm7.304 8.325c-.701.774-2.196.566-3.29-.49-1.119-1.032-1.43-2.496-.726-3.27.71-.776 2.213-.558 3.315.49 1.11 1.03 1.45 2.505.701 3.27Zm9.442 2.81c-.31 1.003-1.75 1.459-3.199 1.033-1.448-.439-2.395-1.613-2.103-2.626.301-1.01 1.747-1.484 3.207-1.028 1.446.436 2.396 1.602 2.095 2.622Zm10.744 1.193c.036 1.055-1.193 1.93-2.715 1.95-1.53.034-2.769-.82-2.786-1.86 0-1.065 1.202-1.932 2.733-1.958 1.522-.03 2.768.818 2.768 1.868Zm10.555-.405c.182 1.03-.875 2.088-2.387 2.37-1.485.271-2.861-.365-3.05-1.386-.184-1.056.893-2.114 2.376-2.387 1.514-.263 2.868.356 3.061 1.403Z"
|
||||
fill="#121212"
|
||||
/>
|
||||
</svg>
|
||||
@@ -0,0 +1,60 @@
|
||||
<svg
|
||||
width="30"
|
||||
name="SVGL Logo"
|
||||
viewBox="0 0 512 512"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
class=""
|
||||
><rect
|
||||
id="r4"
|
||||
width="512"
|
||||
height="512"
|
||||
x="0"
|
||||
y="0"
|
||||
rx="128"
|
||||
fill="#222"
|
||||
stroke="#FFFFFF"
|
||||
stroke-width="0"
|
||||
stroke-opacity="100%"
|
||||
paint-order="stroke"
|
||||
></rect><rect
|
||||
width="512"
|
||||
height="512"
|
||||
x="0"
|
||||
y="0"
|
||||
fill="url(#r6)"
|
||||
rx="128"
|
||||
style="mix-blend-mode: overlay;"
|
||||
></rect><clipPath id="clip"><use xlink:href="#r4"></use></clipPath><defs
|
||||
><linearGradient
|
||||
id="r5"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="rotate(135)"
|
||||
style="transform-origin: center center;"
|
||||
><stop stop-color="#222"></stop><stop offset="1" stop-color="#222222"></stop></linearGradient
|
||||
><radialGradient
|
||||
id="r6"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(256) rotate(90) scale(512)"
|
||||
><stop stop-color="white"></stop><stop offset="1" stop-color="white" stop-opacity="0"
|
||||
></stop></radialGradient
|
||||
></defs
|
||||
><svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="310"
|
||||
height="310"
|
||||
fill="#e8e8e8"
|
||||
viewBox="0 0 256 256"
|
||||
x="101"
|
||||
y="101"
|
||||
alignment-baseline="middle"
|
||||
style="color: rgb(255, 255, 255);"
|
||||
><path
|
||||
d="M168,32H88A56.06,56.06,0,0,0,32,88v80a56.06,56.06,0,0,0,56,56h48a8.07,8.07,0,0,0,2.53-.41c26.23-8.75,76.31-58.83,85.06-85.06A8.07,8.07,0,0,0,224,136V88A56.06,56.06,0,0,0,168,32ZM48,168V88A40,40,0,0,1,88,48h80a40,40,0,0,1,40,40v40H184a56.06,56.06,0,0,0-56,56v24H88A40,40,0,0,1,48,168Zm96,35.14V184a40,40,0,0,1,40-40h19.14C191,163.5,163.5,191,144,203.14Z"
|
||||
></path></svg
|
||||
></svg
|
||||
>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,118 @@
|
||||
<script lang="ts">
|
||||
export let currentPath: string;
|
||||
|
||||
import { cn } from '@/utils/cn';
|
||||
import Logo from './logo.svelte';
|
||||
import Theme from './theme.svelte';
|
||||
|
||||
import { ArrowUpRight, CloudyIcon, GithubIcon, TwitterIcon } from 'lucide-svelte';
|
||||
import XIcon from './xIcon.svelte';
|
||||
|
||||
const socials = [
|
||||
{
|
||||
name: 'GitHub',
|
||||
url: 'https://github.com/pheralb/svgl',
|
||||
icon: GithubIcon
|
||||
}
|
||||
];
|
||||
|
||||
const externalLinks = [
|
||||
{
|
||||
name: 'API',
|
||||
url: '/api',
|
||||
icon: CloudyIcon,
|
||||
external: false
|
||||
},
|
||||
{
|
||||
name: 'Terminal',
|
||||
url: 'https://github.com/pheralb/svgl?tab=readme-ov-file#-terminal',
|
||||
icon: ArrowUpRight,
|
||||
external: true
|
||||
},
|
||||
{
|
||||
name: 'Submit logo',
|
||||
url: 'https://github.com/pheralb/svgl#-getting-started',
|
||||
icon: ArrowUpRight,
|
||||
external: true
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<nav
|
||||
class={cn(
|
||||
'dark:bg-neutral-900 bg-white',
|
||||
'w-full px-5 py-4 border-b border-neutral-200 dark:border-neutral-800',
|
||||
'sticky top-0 z-50',
|
||||
'backdrop-blur-md opacity-95'
|
||||
)}
|
||||
>
|
||||
<div class="flex items-center justify-between mx-auto">
|
||||
<div class="flex items-center space-x-2">
|
||||
<a href="/">
|
||||
<div class="flex items-center space-x-2 hover:opacity-80 transition-opacity">
|
||||
<svelte:component this={Logo} />
|
||||
<span class="text-[19px] font-medium tracking-wide hidden md:block">svgl</span>
|
||||
<p class="text-neutral-500 hidden md:block font-mono">v4.0</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center space-x-0 md:space-x-7">
|
||||
<div
|
||||
class="flex items-center md:space-x-4 divide-x divide-neutral-300 dark:divide-neutral-700"
|
||||
>
|
||||
{#each externalLinks as link}
|
||||
<a
|
||||
href={link.url}
|
||||
target={link.external ? '_blank' : ''}
|
||||
class={cn(
|
||||
'flex items-center hover:opacity-80 transition-opacity text-[15px] pl-2 md:pl-3 group',
|
||||
currentPath === link.url &&
|
||||
'underline underline-offset-8 decoration-dotted decoration-neutral-500'
|
||||
)}
|
||||
>
|
||||
{#if !link.external}
|
||||
<svelte:component
|
||||
this={link.icon}
|
||||
size={16}
|
||||
strokeWidth={1.5}
|
||||
class="mr-2"
|
||||
name={link.name}
|
||||
/>
|
||||
{/if}
|
||||
<span class={cn('hidden md:block', !link.external && 'block')}>{link.name}</span>
|
||||
{#if link.external}
|
||||
<svelte:component
|
||||
this={link.icon}
|
||||
size={16}
|
||||
name="External link"
|
||||
strokeWidth={1.5}
|
||||
class="ml-1 transition-transform duration-300 group-hover:translate-x-[1px] hidden md:block"
|
||||
/>
|
||||
{/if}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<a
|
||||
href="https://twitter.com/pheralb_"
|
||||
target="_blank"
|
||||
class="flex items-center space-x-1 hover:opacity-80 transition-opacity"
|
||||
title="Twitter"
|
||||
>
|
||||
<XIcon iconSize={16} />
|
||||
</a>
|
||||
{#each socials as social}
|
||||
<a
|
||||
href={social.url}
|
||||
target="_blank"
|
||||
class="flex items-center space-x-1 hover:opacity-80 transition-opacity"
|
||||
title={social.name}
|
||||
>
|
||||
<svelte:component this={social.icon} size={20} strokeWidth={1.5} name={social.name} />
|
||||
</a>
|
||||
{/each}
|
||||
<Theme />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1,22 +1,28 @@
|
||||
<script lang="ts">
|
||||
export let notFoundTerm: string;
|
||||
|
||||
import FileSearch from 'phosphor-svelte/lib/FileMagnifyingGlass';
|
||||
import ArrowUpRight from 'phosphor-svelte/lib/ArrowUpRight';
|
||||
import { PackageOpen, ArrowUpRight } from 'lucide-svelte';
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="mt-6 flex w-full flex-col items-center justify-center space-y-2 text-gray-600 dark:text-gray-400"
|
||||
>
|
||||
<FileSearch size={40} />
|
||||
<p class="mt-1 text-xl font-medium">SVG not found</p>
|
||||
<p class="text-lg">"{notFoundTerm}"</p>
|
||||
<a
|
||||
href="https://github.com/pheralb/svgl/issues/new"
|
||||
target="_blank"
|
||||
class="flex items-center space-x-2 rounded-md border border-neutral-300 p-2 duration-100 hover:bg-neutral-200 dark:border-neutral-700 dark:hover:bg-neutral-700/40"
|
||||
>
|
||||
<span>Request SVG</span>
|
||||
<ArrowUpRight size={16} />
|
||||
</a>
|
||||
<div class="mt-6 flex w-full flex-col items-center justify-center text-gray-600 dark:text-gray-400">
|
||||
<PackageOpen size={40} class="mb-4" />
|
||||
<p class="text-xl mb-1 font-medium">SVG not found</p>
|
||||
<p class="text-md mb-4 font-mono">"{notFoundTerm}"</p>
|
||||
<div class="flex items-center space-x-1">
|
||||
<a
|
||||
href="https://github.com/pheralb/svgl?tab=readme-ov-file#-getting-started"
|
||||
target="_blank"
|
||||
class="flex items-center space-x-2 rounded-md border border-neutral-300 p-2 duration-100 hover:bg-neutral-200 dark:border-neutral-700 dark:hover:bg-neutral-700/40"
|
||||
>
|
||||
<span>Submit logo</span>
|
||||
<ArrowUpRight size={16} />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/pheralb/svgl/issues/new"
|
||||
target="_blank"
|
||||
class="flex items-center space-x-2 rounded-md border border-neutral-300 p-2 duration-100 hover:bg-neutral-200 dark:border-neutral-700 dark:hover:bg-neutral-700/40"
|
||||
>
|
||||
<span>Request SVG</span>
|
||||
<ArrowUpRight size={16} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { SearchIcon } from 'lucide-svelte';
|
||||
export let searchTerm: string;
|
||||
export let placeholder: string = 'Search...';
|
||||
export let clearSearch: () => void;
|
||||
import MagnifyingGlass from 'phosphor-svelte/lib/MagnifyingGlass';
|
||||
import X from 'phosphor-svelte/lib/X';
|
||||
</script>
|
||||
|
||||
<div class="relative w-full">
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pl-3 text-neutral-500">
|
||||
<div class="pointer-events-none">
|
||||
<MagnifyingGlass size={18} weight={searchTerm ? 'duotone' : 'regular'} />
|
||||
<div class="sticky top-[63px] z-50">
|
||||
<div class="relative w-full text-[16px]">
|
||||
<div class="absolute inset-y-0 left-0 flex items-center pl-3 text-neutral-500">
|
||||
<div class="pointer-events-none">
|
||||
<SearchIcon size={20} strokeWidth={searchTerm ? 2.5 : 1.5} />
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
{placeholder}
|
||||
autocomplete="off"
|
||||
class="w-full border-b border-neutral-300 bg-white p-3 pl-11 placeholder-neutral-500 focus:outline-none focus:ring-1 focus:ring-neutral-300 dark:border-neutral-800 dark:bg-neutral-900 dark:focus:ring-neutral-700"
|
||||
bind:value={searchTerm}
|
||||
on:input
|
||||
/>
|
||||
{#if searchTerm.length > 0}
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
|
||||
<button
|
||||
type="button"
|
||||
class="focus:outline-none focus:ring-1 focus:ring-neutral-300"
|
||||
on:click={clearSearch}
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
{placeholder}
|
||||
autocomplete="off"
|
||||
class="w-full rounded-md border border-neutral-300 bg-neutral-200/50 p-3 pl-10 placeholder-neutral-500 focus:outline-none focus:ring-1 focus:ring-neutral-300 dark:border-neutral-800 dark:bg-neutral-700/10 dark:focus:ring-neutral-700"
|
||||
bind:value={searchTerm}
|
||||
on:input
|
||||
/>
|
||||
{#if searchTerm.length > 0}
|
||||
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
|
||||
<button
|
||||
type="button"
|
||||
class="focus:outline-none focus:ring-1 focus:ring-neutral-300"
|
||||
on:click={clearSearch}
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
+115
-37
@@ -1,15 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { iSVG } from '../types/svg';
|
||||
|
||||
import download from 'downloadjs';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import type { iSVG } from '../types/svg';
|
||||
import { MIMETYPE, getSvgContent } from '../utils/getSvgContent';
|
||||
import jszip from 'jszip';
|
||||
|
||||
// Utils:
|
||||
import { MIMETYPE, getSvgContent } from '@/utils/getSvgContent';
|
||||
import { flyAndScale } from '@/utils/flyAndScale';
|
||||
|
||||
// Icons:
|
||||
import DownloadSimple from 'phosphor-svelte/lib/DownloadSimple';
|
||||
import Copy from 'phosphor-svelte/lib/Copy';
|
||||
import Link from 'phosphor-svelte/lib/Link';
|
||||
import Star from 'phosphor-svelte/lib/Star';
|
||||
import { CopyIcon, DownloadIcon, LinkIcon, PackageIcon, PaintBucket } from 'lucide-svelte';
|
||||
|
||||
// Main Card:
|
||||
import CardSpotlight from './cardSpotlight.svelte';
|
||||
import { DropdownMenu } from 'bits-ui';
|
||||
|
||||
// Props:
|
||||
export let svgInfo: iSVG;
|
||||
@@ -17,7 +22,33 @@
|
||||
// Download SVG:
|
||||
const downloadSvg = (url?: string) => {
|
||||
download(url || '');
|
||||
toast.success('Downloading...');
|
||||
toast.success(`Downloading...`, {
|
||||
description: `${svgInfo.title} - ${svgInfo.category}`
|
||||
});
|
||||
};
|
||||
|
||||
// Download all variants:
|
||||
const downloadAllVariants = async ({ route }: iSVG) => {
|
||||
const zip = new jszip();
|
||||
|
||||
if (typeof route === 'string') {
|
||||
downloadSvg(route);
|
||||
return;
|
||||
}
|
||||
|
||||
const lightSvg = await getSvgContent(route.light, false);
|
||||
const darkSvg = await getSvgContent(route.dark, false);
|
||||
|
||||
zip.file(`${svgInfo.title}.svg`, lightSvg);
|
||||
zip.file(`${svgInfo.title}.dark.svg`, darkSvg);
|
||||
|
||||
zip.generateAsync({ type: 'blob' }).then((content) => {
|
||||
download(content, `${svgInfo.title}.zip`, 'application/zip');
|
||||
});
|
||||
|
||||
toast.success('Downloading light & dark variants...', {
|
||||
description: `${svgInfo.title} - ${svgInfo.category}`
|
||||
});
|
||||
};
|
||||
|
||||
// Copy SVG to clipboard:
|
||||
@@ -33,31 +64,43 @@
|
||||
await navigator.clipboard.writeText(content);
|
||||
}
|
||||
toast.success('Copied to clipboard!', {
|
||||
icon: Star,
|
||||
description: `${svgInfo.title} - ${svgInfo.category}`
|
||||
});
|
||||
};
|
||||
|
||||
// Icon Stroke & Size:
|
||||
let iconStroke = 1.8;
|
||||
let iconSize = 16;
|
||||
</script>
|
||||
|
||||
<CardSpotlight>
|
||||
<div class="flex flex-col items-center justify-center rounded-md p-4">
|
||||
<!-- Image -->
|
||||
<img
|
||||
class="hidden dark:block mb-4 mt-2 h-10"
|
||||
src={typeof svgInfo.route !== 'string' ? svgInfo.route.dark : svgInfo.route}
|
||||
alt={svgInfo.title}
|
||||
title={svgInfo.title}
|
||||
loading="lazy"
|
||||
/>
|
||||
<img
|
||||
class="block dark:hidden mb-4 mt-2 h-10"
|
||||
src={typeof svgInfo.route !== 'string' ? svgInfo.route.light : svgInfo.route}
|
||||
alt={svgInfo.title}
|
||||
title={svgInfo.title}
|
||||
loading="lazy"
|
||||
/>
|
||||
<!-- Title -->
|
||||
<div class="mb-3 flex flex-col items-center justify-center">
|
||||
<p class="truncate text-[15px] font-medium text-balance text-center">{svgInfo.title}</p>
|
||||
<p class="truncate text-[15px] font-medium text-balance text-center select-all">
|
||||
{svgInfo.title}
|
||||
</p>
|
||||
<a
|
||||
href={`/directory/${svgInfo.category.toLowerCase()}`}
|
||||
class="text-sm lowercase text-neutral-500 hover:underline">{svgInfo.category}</a
|
||||
class="text-sm lowercase text-neutral-500 hover:underline font-mono">{svgInfo.category}</a
|
||||
>
|
||||
</div>
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center space-x-1">
|
||||
<button
|
||||
title="Copy to clipboard"
|
||||
@@ -85,43 +128,78 @@
|
||||
}}
|
||||
class="flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
|
||||
>
|
||||
<Copy size={17} />
|
||||
<CopyIcon size={iconSize} strokeWidth={iconStroke} />
|
||||
</button>
|
||||
<button
|
||||
title="Download"
|
||||
on:click={() => {
|
||||
const svgHasTheme = typeof svgInfo.route !== 'string';
|
||||
|
||||
if (!svgHasTheme) {
|
||||
downloadSvg(
|
||||
typeof svgInfo.route === 'string'
|
||||
? svgInfo.route
|
||||
: "Something went wrong. Couldn't copy the SVG."
|
||||
);
|
||||
return;
|
||||
}
|
||||
{#if typeof svgInfo.route !== 'string'}
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger
|
||||
class="flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
|
||||
>
|
||||
<DownloadIcon size={iconSize} strokeWidth={iconStroke} />
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
class="w-full shadow-md max-w-[229px] rounded-md border border-neutral-100 dark:border-neutral-800 bg-white dark:bg-neutral-900 px-1 py-1.5 shadow-popover"
|
||||
transition={flyAndScale}
|
||||
sideOffset={3}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
class="flex h-10 select-none items-center rounded-md py-3 pl-3 pr-1.5 text-sm font-medium hover:bg-neutral-100 dark:hover:bg-neutral-700/40"
|
||||
on:click={() => {
|
||||
downloadAllVariants(svgInfo);
|
||||
}}
|
||||
>
|
||||
<PackageIcon class="mr-2" size={18} />
|
||||
<p>Light & Dark variants</p>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
class="flex h-10 select-none items-center rounded-md py-3 pl-3 pr-1.5 text-sm font-medium hover:bg-neutral-100 dark:hover:bg-neutral-700/40"
|
||||
on:click={() => {
|
||||
const svgHasTheme = typeof svgInfo.route !== 'string';
|
||||
|
||||
const dark = document.documentElement.classList.contains('dark');
|
||||
if (!svgHasTheme) {
|
||||
downloadSvg(
|
||||
typeof svgInfo.route === 'string'
|
||||
? svgInfo.route
|
||||
: "Something went wrong. Couldn't copy the SVG."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
downloadSvg(
|
||||
typeof svgInfo.route !== 'string'
|
||||
? dark
|
||||
? svgInfo.route.dark
|
||||
: svgInfo.route.light
|
||||
: svgInfo.route
|
||||
);
|
||||
}}
|
||||
class="flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
|
||||
>
|
||||
<DownloadSimple size={17} />
|
||||
</button>
|
||||
const dark = document.documentElement.classList.contains('dark');
|
||||
|
||||
downloadSvg(
|
||||
typeof svgInfo.route !== 'string'
|
||||
? dark
|
||||
? svgInfo.route.dark
|
||||
: svgInfo.route.light
|
||||
: svgInfo.route
|
||||
);
|
||||
}}
|
||||
>
|
||||
<PaintBucket class="mr-2" size={18} />
|
||||
Only {document.documentElement.classList.contains('dark') ? 'dark' : 'light'} variant
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
{:else}
|
||||
<button
|
||||
title="Download SVG"
|
||||
on:click={() => {
|
||||
if (typeof svgInfo.route === 'string') downloadSvg(svgInfo.route);
|
||||
}}
|
||||
class="flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
|
||||
>
|
||||
<DownloadIcon size={iconSize} strokeWidth={iconStroke} />
|
||||
</button>
|
||||
{/if}
|
||||
<a
|
||||
href={svgInfo.url}
|
||||
title="Website"
|
||||
target="_blank"
|
||||
class="flex items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
|
||||
>
|
||||
<Link size={17} />
|
||||
<LinkIcon size={iconSize} strokeWidth={iconStroke} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,12 +35,10 @@
|
||||
}
|
||||
|
||||
// Icons:
|
||||
import Moon from 'phosphor-svelte/lib/Moon';
|
||||
import Sun from 'phosphor-svelte/lib/Sun';
|
||||
import { MoonIcon, SunIcon } from 'lucide-svelte';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<!-- set dark mode class based on user preference / device settings (in head to avoid FOUC) -->
|
||||
<script>
|
||||
if (
|
||||
localStorage.theme === 'dark' ||
|
||||
@@ -53,15 +51,11 @@
|
||||
</script>
|
||||
</svelte:head>
|
||||
|
||||
<button
|
||||
on:click={toggle}
|
||||
class="duration-100 hover:-translate-y-0.5 focus:outline-none"
|
||||
aria-label="Toggle dark mode"
|
||||
>
|
||||
<button on:click={toggle} aria-label="Toggle dark mode" class="hover:opacity-80">
|
||||
<!-- moon icon -->
|
||||
{#if dark}
|
||||
<Sun size={18} />
|
||||
<SunIcon size={20} strokeWidth={1.5} />
|
||||
{:else}
|
||||
<Moon size={18} />
|
||||
<MoonIcon size={20} strokeWidth={1.5} />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { AlertTriangleIcon, Check } from 'lucide-svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let warning = false;
|
||||
const initialValue = browser ? window.localStorage.getItem('warning') : true;
|
||||
</script>
|
||||
|
||||
{#if !warning && !initialValue}
|
||||
<div
|
||||
class="flex items-center md:flex-row flex-col md:space-x-2 space-x-0 space-y-2 md:space-y-0 py-2 px-3 bg-neutral-100 dark:bg-neutral-800/40 text-neutral-700 dark:text-neutral-300"
|
||||
>
|
||||
<AlertTriangleIcon size={18} strokeWidth={2} class="mr-1" />
|
||||
<p>
|
||||
Each svg provides the link to the product or company that owns it, <strong
|
||||
>please contact them</strong
|
||||
>
|
||||
if you are going to use their logo. If you are the owner of an svg and do not want it to appear
|
||||
here, please
|
||||
<a
|
||||
target="_blank"
|
||||
class="underline underline-offset-4 decoration-dotted decoration-neutral-500"
|
||||
href="https://github.com/pheralb/svgl/issues/new">create an issue</a
|
||||
> on Github.
|
||||
</p>
|
||||
<button
|
||||
class="ml-auto transition-colors flex items-center space-x-1 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40 bg-neutral-200/50 dark:bg-neutral-800/40 text-neutral-700 dark:text-neutral-300"
|
||||
on:click={() => {
|
||||
localStorage.setItem('warning', 'true');
|
||||
warning = true;
|
||||
}}
|
||||
>
|
||||
<Check size={14} strokeWidth={2} />
|
||||
<span>Accept</span>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
export let iconSize: number;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={iconSize || 16}
|
||||
height={iconSize || 16}
|
||||
name="Twitter"
|
||||
fill="none"
|
||||
viewBox="0 0 1200 1227"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M714.163 519.284 1160.89 0h-105.86L667.137 450.887 357.328 0H0l468.492 681.821L0 1226.37h105.866l409.625-476.152 327.181 476.152H1200L714.137 519.284h.026ZM569.165 687.828l-47.468-67.894-377.686-540.24h162.604l304.797 435.991 47.468 67.894 396.2 566.721H892.476L569.165 687.854v-.026Z"
|
||||
/>
|
||||
</svg>
|
||||
+1
-2
@@ -2,6 +2,5 @@ import type { iSVG } from '@/types/svg';
|
||||
import { svgs } from './svgs';
|
||||
|
||||
export const svgsData = svgs.map((svg: iSVG, index: number) => {
|
||||
svg.id = index;
|
||||
return svg;
|
||||
return { id: index, ...svg };
|
||||
});
|
||||
+122
-50
@@ -46,6 +46,12 @@ export const svgs: iSVG[] = [
|
||||
route: '/library/vue.svg',
|
||||
url: 'https://vuejs.org/'
|
||||
},
|
||||
{
|
||||
title: "Vuetify",
|
||||
category: "Library",
|
||||
route: "/library/vuetify.svg",
|
||||
url: "https://vuetifyjs.com/"
|
||||
},
|
||||
{
|
||||
title: 'Nuxt',
|
||||
category: 'Framework',
|
||||
@@ -325,12 +331,6 @@ export const svgs: iSVG[] = [
|
||||
route: '/library/mongodb.svg',
|
||||
url: 'https://www.mongodb.com/'
|
||||
},
|
||||
{
|
||||
title: 'Babel',
|
||||
category: 'Compiler',
|
||||
route: '/library/babel.svg',
|
||||
url: 'https://babeljs.io'
|
||||
},
|
||||
{
|
||||
title: 'Moon',
|
||||
category: 'Framework',
|
||||
@@ -436,6 +436,15 @@ export const svgs: iSVG[] = [
|
||||
},
|
||||
url: 'https://arc.dev'
|
||||
},
|
||||
{
|
||||
title: 'Arc',
|
||||
category: 'Fintech',
|
||||
route: {
|
||||
light: '/library/arc_fintech_light.svg',
|
||||
dark: '/library/arc_fintech_dark.svg'
|
||||
},
|
||||
url: 'https://arc.tech'
|
||||
},
|
||||
{
|
||||
title: 'Qwik',
|
||||
category: 'Framework',
|
||||
@@ -799,12 +808,6 @@ export const svgs: iSVG[] = [
|
||||
route: '/library/swift.svg',
|
||||
url: 'https://swift.org/'
|
||||
},
|
||||
{
|
||||
title: 'Testing Library',
|
||||
category: 'Framework',
|
||||
route: '/library/testinglibrary.svg',
|
||||
url: 'https://testing-library.com/'
|
||||
},
|
||||
{
|
||||
title: 'TypeORM',
|
||||
category: 'Database',
|
||||
@@ -858,7 +861,7 @@ export const svgs: iSVG[] = [
|
||||
},
|
||||
{
|
||||
title: 'Developer Student Club',
|
||||
category: 'Education',
|
||||
category: 'Community',
|
||||
route: '/library/gdsc.svg',
|
||||
url: 'https://gdsc.community.dev/'
|
||||
},
|
||||
@@ -888,7 +891,7 @@ export const svgs: iSVG[] = [
|
||||
},
|
||||
{
|
||||
title: 'midudev',
|
||||
category: 'Education',
|
||||
category: 'Community',
|
||||
route: '/library/midudev.svg',
|
||||
url: 'https://midu.dev'
|
||||
},
|
||||
@@ -943,12 +946,6 @@ export const svgs: iSVG[] = [
|
||||
route: '/library/jetbrains-space.svg',
|
||||
url: 'https://www.jetbrains.com/space/'
|
||||
},
|
||||
{
|
||||
title: 'Gin',
|
||||
category: 'Framework',
|
||||
route: '/library/gin.svg',
|
||||
url: 'https://gin-gonic.com/'
|
||||
},
|
||||
{
|
||||
title: 'Stimulus',
|
||||
category: 'Framework',
|
||||
@@ -1327,12 +1324,6 @@ export const svgs: iSVG[] = [
|
||||
route: '/library/bnb.svg',
|
||||
url: 'https://www.bnbchain.org/'
|
||||
},
|
||||
{
|
||||
title: 'Cardano',
|
||||
category: 'Crypto',
|
||||
route: '/library/ada.svg',
|
||||
url: 'https://cardano.org/'
|
||||
},
|
||||
{
|
||||
title: 'TRON',
|
||||
category: 'Crypto',
|
||||
@@ -1351,6 +1342,18 @@ export const svgs: iSVG[] = [
|
||||
route: '/library/matic.svg',
|
||||
url: 'https://polygon.technology/'
|
||||
},
|
||||
{
|
||||
title: 'Algorand',
|
||||
category: 'Crypto',
|
||||
route: '/library/algorand.svg',
|
||||
url: 'https://algorand.org/'
|
||||
},
|
||||
{
|
||||
title: 'Tether',
|
||||
category: 'Crypto',
|
||||
route: '/library/tether.svg',
|
||||
url: 'https://tether.to/'
|
||||
},
|
||||
{
|
||||
title: 'X',
|
||||
category: 'Social',
|
||||
@@ -1552,16 +1555,13 @@ export const svgs: iSVG[] = [
|
||||
route: '/library/rowy.svg',
|
||||
url: 'https://www.rowy.io/'
|
||||
},
|
||||
{
|
||||
title: 'BuildShip',
|
||||
category: 'AI',
|
||||
route: '/library/buildship.svg',
|
||||
url: 'https://buildship.com/'
|
||||
},
|
||||
{
|
||||
title: 'Cal.com',
|
||||
category: 'Software',
|
||||
route: '/library/cal.svg',
|
||||
route: {
|
||||
light: '/library/cal.svg',
|
||||
dark: '/library/cal_dark.svg'
|
||||
},
|
||||
url: 'https://cal.com'
|
||||
},
|
||||
{
|
||||
@@ -1590,7 +1590,7 @@ export const svgs: iSVG[] = [
|
||||
},
|
||||
{
|
||||
title: 'Patreon',
|
||||
category: 'Software',
|
||||
category: 'Social',
|
||||
route: {
|
||||
light: '/library/patreon.svg',
|
||||
dark: '/library/patreon_dark.svg'
|
||||
@@ -1735,12 +1735,6 @@ export const svgs: iSVG[] = [
|
||||
},
|
||||
url: 'https://axiom.co/'
|
||||
},
|
||||
{
|
||||
title: 'ArtisanLabs',
|
||||
category: 'Software',
|
||||
route: '/library/ArtisanLabs.svg',
|
||||
url: 'https://artisanlabs.io'
|
||||
},
|
||||
{
|
||||
title: 'Django',
|
||||
category: 'Framework',
|
||||
@@ -1779,7 +1773,7 @@ export const svgs: iSVG[] = [
|
||||
},
|
||||
{
|
||||
title: 'Pinterest',
|
||||
category: 'Software',
|
||||
category: 'Social',
|
||||
route: '/library/pinterest.svg',
|
||||
url: 'https://pinterest.com/'
|
||||
},
|
||||
@@ -1819,13 +1813,19 @@ export const svgs: iSVG[] = [
|
||||
{
|
||||
title: 'Penpot',
|
||||
category: 'Design',
|
||||
route: '/library/penpot.svg',
|
||||
route: {
|
||||
light: '/library/penpot.svg',
|
||||
dark: '/library/penpot_dark.svg'
|
||||
},
|
||||
url: 'https://penpot.app/'
|
||||
},
|
||||
{
|
||||
title: 'Sketch',
|
||||
category: 'Design',
|
||||
route: '/library/sketch.svg',
|
||||
route: {
|
||||
light: '/library/sketch_light.svg',
|
||||
dark: '/library/sketch.svg'
|
||||
},
|
||||
url: 'https://www.sketch.com/'
|
||||
},
|
||||
{
|
||||
@@ -1840,12 +1840,6 @@ export const svgs: iSVG[] = [
|
||||
route: '/library/ubuntu.svg',
|
||||
url: 'https://ubuntu.com/'
|
||||
},
|
||||
{
|
||||
title: 'Meta',
|
||||
category: 'Social',
|
||||
route: '/library/meta.svg',
|
||||
url: 'https://meta.com/'
|
||||
},
|
||||
{
|
||||
title: 'Cypress',
|
||||
category: 'Framework',
|
||||
@@ -1889,7 +1883,13 @@ export const svgs: iSVG[] = [
|
||||
url: 'https://www.jetbrains.com/phpstorm/'
|
||||
},
|
||||
{
|
||||
title: 'JetBrains PyCharm',
|
||||
title: 'MonkeyType',
|
||||
category: 'Typing',
|
||||
route: '/library/monkeytype.svg',
|
||||
url: 'https://monkeytype.com/'
|
||||
},
|
||||
{
|
||||
title: 'Raycast',
|
||||
category: 'Software',
|
||||
route: '/library/pycharm.svg',
|
||||
url: 'https://www.jetbrains.com/pycharm/'
|
||||
@@ -1978,4 +1978,76 @@ export const svgs: iSVG[] = [
|
||||
route: '/library/trustpilot.svg',
|
||||
url: 'https://www.trustpilot.com'
|
||||
},
|
||||
{
|
||||
title: 'Raycast',
|
||||
category: 'Software',
|
||||
route: '/library/raycast.svg',
|
||||
url: 'https://raycast.com/'
|
||||
},
|
||||
{
|
||||
title: 'Procure',
|
||||
category: 'Marketplace',
|
||||
route: '/library/procure.svg',
|
||||
url: 'https://procure.biz/'
|
||||
},
|
||||
{
|
||||
title: 'Julia',
|
||||
category: 'Language',
|
||||
route: '/library/julia.svg',
|
||||
url: 'https://julialang.org/'
|
||||
},
|
||||
{
|
||||
title: 'SWC',
|
||||
category: 'Compiler',
|
||||
route: '/library/swc.svg',
|
||||
url: 'https://swc.rs/'
|
||||
},
|
||||
{
|
||||
title: 'PlayStation',
|
||||
category: 'Software',
|
||||
route: '/library/playstation.svg',
|
||||
url: 'https://www.playstation.com/'
|
||||
},
|
||||
{
|
||||
title: 'Xbox',
|
||||
category: 'Software',
|
||||
route: '/library/xbox.svg',
|
||||
url: 'https://www.xbox.com/'
|
||||
},
|
||||
{
|
||||
title: 'Cody',
|
||||
category: 'AI',
|
||||
route: '/library/cody.svg',
|
||||
url: 'https://about.sourcegraph.com/'
|
||||
},
|
||||
{
|
||||
title: 'Sourcegraph',
|
||||
category: 'AI',
|
||||
route: '/library/sourcegraph.svg',
|
||||
url: 'https://about.sourcegraph.com/'
|
||||
},
|
||||
{
|
||||
title: 'Claude AI',
|
||||
category: 'AI',
|
||||
route: '/library/claude-ai.svg',
|
||||
url: 'https://claude.ai/'
|
||||
},
|
||||
{
|
||||
title: 'Perplexity AI',
|
||||
category: 'AI',
|
||||
route: '/library/perplexity.svg',
|
||||
url: 'https://perplexity.ai/'
|
||||
},
|
||||
{
|
||||
title: 'Spring',
|
||||
category: 'Framework',
|
||||
route: '/library/spring.svg',
|
||||
url: 'https://spring.io/'
|
||||
},
|
||||
{
|
||||
"title": "Directus",
|
||||
"category": "CMS",
|
||||
"route": "/library/directus.svg",
|
||||
"url": "https://directus.io/"
|
||||
},
|
||||
];
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
---
|
||||
title: API Reference
|
||||
description: The API reference is a detailed documentation of all the endpoints available in the SVGL API.
|
||||
---
|
||||
|
||||
<script>
|
||||
import Endpoint from '../components/endpoints.svelte';
|
||||
</script>
|
||||
|
||||
## Introduction
|
||||
|
||||
SVGL API is a RESTFul API that allows you to get all the information of the SVGs that are in the repository.
|
||||
|
||||
## Limitations
|
||||
|
||||
The API is currently open to everyone and does not require any authentication. However, to prevent abusive use of the API, there is a limit to the number of requests: **5 requests** with **60s** cooldown.
|
||||
|
||||
## Base URL
|
||||
|
||||
The base URL for the API is:
|
||||
|
||||
```bash
|
||||
https://svgl.app/api/svgs
|
||||
# or
|
||||
https://svgl.app/api/categories
|
||||
```
|
||||
|
||||
## Typescript usage
|
||||
|
||||
- For SVGs:
|
||||
|
||||
```ts
|
||||
export interface svg {
|
||||
id: number;
|
||||
title: string;
|
||||
category: string;
|
||||
route:
|
||||
| string
|
||||
| {
|
||||
dark: string;
|
||||
light: string;
|
||||
};
|
||||
url: string;
|
||||
}
|
||||
```
|
||||
|
||||
- For categories:
|
||||
|
||||
```ts
|
||||
export interface category {
|
||||
category: string;
|
||||
total: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
<Endpoint title="Get all SVGs" method="GET" description="Returns all the SVGs in the repository.">
|
||||
|
||||
```bash
|
||||
/api/svgs
|
||||
```
|
||||
|
||||
<p></p>
|
||||
|
||||
```json
|
||||
// Returns:
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"title": "Discord",
|
||||
"category": "Software",
|
||||
"route": "https://svgl.app/library/discord.svg",
|
||||
"url": "https://discord.com/"
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
</Endpoint>
|
||||
|
||||
<Endpoint title="Get a limited number of SVGs" method="GET" description="Returns a limited number of SVGs in the repository. Start from the first SVG.">
|
||||
|
||||
```bash
|
||||
/api/svgs?limit=10
|
||||
```
|
||||
|
||||
<p></p>
|
||||
|
||||
```json
|
||||
// Returns:
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"title": "Discord",
|
||||
"category": "Software",
|
||||
"route": "https://svgl.app/library/discord.svg",
|
||||
"url": "https://discord.com/"
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
</Endpoint>
|
||||
|
||||
<Endpoint title="Filter SVGs by category" method="GET" description="Returns all the SVGs in the repository that match the category.">
|
||||
|
||||
```bash
|
||||
/api/svgs?category=software
|
||||
```
|
||||
|
||||
<p></p>
|
||||
|
||||
```json
|
||||
// Returns:
|
||||
[
|
||||
{
|
||||
"id": 0,
|
||||
"title": "Discord",
|
||||
"category": "Software",
|
||||
"route": "https://svgl.app/library/discord.svg",
|
||||
"url": "https://discord.com/"
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
The list of categories is available [here](https://github.com/pheralb/svgl/blob/main/src/types/categories.ts) (except for the _all_ category).
|
||||
|
||||
</Endpoint>
|
||||
|
||||
<Endpoint title="Get only categories" method="GET" description="Returns only categories with the number of SVGs in each category.">
|
||||
|
||||
```bash
|
||||
/api/categories
|
||||
```
|
||||
|
||||
<p></p>
|
||||
|
||||
```json
|
||||
// Returns:
|
||||
[
|
||||
{
|
||||
"category": "Software",
|
||||
"total": 97
|
||||
},
|
||||
{
|
||||
"category": "Library",
|
||||
"total": 25
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
</Endpoint>
|
||||
|
||||
<Endpoint title="Search SVGs by name" method="GET" description="Returns all the SVGs in the repository that match the name.">
|
||||
|
||||
```bash
|
||||
/api/svgs?search=axiom
|
||||
```
|
||||
|
||||
<p></p>
|
||||
|
||||
```json
|
||||
// Returns:
|
||||
[
|
||||
{
|
||||
"id": 267,
|
||||
"title": "Axiom",
|
||||
"category": "Software",
|
||||
"route": {
|
||||
"light": "https://svgl.app/library/axiom-light.svg",
|
||||
"dark": "https://svgl.app/library/axiom-dark.svg"
|
||||
},
|
||||
"url": "https://axiom.co/"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</Endpoint>
|
||||
+39
-84
@@ -13,42 +13,34 @@
|
||||
|
||||
// Icons:
|
||||
import Heart from 'phosphor-svelte/lib/Heart';
|
||||
import ArrowUpRight from 'phosphor-svelte/lib/ArrowUpRight';
|
||||
import ArrowLeft from 'phosphor-svelte/lib/ArrowLeft';
|
||||
import Star from 'phosphor-svelte/lib/Star';
|
||||
import GithubLogo from 'phosphor-svelte/lib/GithubLogo';
|
||||
import Box from 'phosphor-svelte/lib/Cube';
|
||||
import { ArrowUpRight } from 'lucide-svelte';
|
||||
|
||||
// Toaster:
|
||||
import { Toaster } from 'svelte-sonner';
|
||||
|
||||
// Components for all pages:
|
||||
import Transition from '@/components/transition.svelte';
|
||||
import Container from '@/components/container.svelte';
|
||||
import Theme from '@/components/theme.svelte';
|
||||
import Warning from '@/components/warning.svelte';
|
||||
|
||||
// Layout:
|
||||
import Navbar from '@/components/navbar.svelte';
|
||||
import { cn } from '@/utils/cn';
|
||||
</script>
|
||||
|
||||
<Navbar currentPath={data.pathname} />
|
||||
<main>
|
||||
<nav
|
||||
class="z-50 w-full overflow-y-auto overflow-x-hidden border-b border-neutral-300 dark:border-neutral-800 md:fixed md:left-0 md:top-0 md:h-full md:w-60 md:border-none md:pb-10"
|
||||
<aside
|
||||
class={cn(
|
||||
'z-50 w-full overflow-y-auto overflow-x-hidden',
|
||||
'dark:border-neutral-800 md:fixed md:left-0 md:h-full md:w-56 md:pb-10',
|
||||
'bg-white dark:bg-neutral-900',
|
||||
'backdrop-blur-md opacity-95',
|
||||
'border-r border-neutral-200 dark:border-neutral-800'
|
||||
)}
|
||||
>
|
||||
<div class="px-6 py-6">
|
||||
<div class="mb-3 border-b border-neutral-300 pb-3 dark:border-neutral-700/40">
|
||||
<div class="flex items-center justify-between">
|
||||
<a href="/">
|
||||
<div
|
||||
class="flex items-center space-x-2 duration-150 hover:text-neutral-500 dark:hover:text-neutral-300"
|
||||
>
|
||||
<h3 class="text-xl font-medium">svgl</h3>
|
||||
<p class="text-neutral-500">v3.2.2</p>
|
||||
</div>
|
||||
</a>
|
||||
<Theme />
|
||||
</div>
|
||||
<p class="mt-2 font-medium text-neutral-400">✨ Optimized SVGs for web</p>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center space-x-1 overflow-y-auto border-b border-neutral-300 pb-3 dark:border-neutral-700/40 md:mb-3 md:flex-col md:space-x-0 md:space-y-1 md:overflow-y-visible"
|
||||
<div class="md:px-6 md:py-6">
|
||||
<nav
|
||||
class="flex items-center space-x-1 overflow-y-auto border-b border-neutral-300 dark:border-neutral-700/40 md:mb-3 md:flex-col md:space-x-0 md:space-y-1 md:overflow-y-visible px-5 md:px-0 pb-2 pt-3 md:pt-0"
|
||||
>
|
||||
<a
|
||||
href="/"
|
||||
@@ -63,83 +55,46 @@
|
||||
{#each categories.sort() as category}
|
||||
<a
|
||||
href={`/directory/${category.toLowerCase()}`}
|
||||
class={`flex w-full items-center rounded-md p-2 transition-none duration-100 text-neutral-600 hover:text-dark dark:hover:text-white dark:text-neutral-400 hover:bg-neutral-200 dark:hover:bg-neutral-700/40
|
||||
${
|
||||
data.pathname === `/directory/${category.toLowerCase()}`
|
||||
? 'bg-neutral-200 dark:bg-neutral-700/30 font-medium dark:text-white text-dark'
|
||||
: ''
|
||||
}`}
|
||||
data-sveltekit-preload-data>{category}</a
|
||||
class={cn(
|
||||
'flex w-full items-center justify-between rounded-md p-2 transition-none duration-100 text-neutral-600 hover:text-dark dark:hover:text-white dark:text-neutral-400 hover:bg-neutral-200 dark:hover:bg-neutral-700/40',
|
||||
data.pathname === `/directory/${category.toLowerCase()}`
|
||||
? 'bg-neutral-200 dark:bg-neutral-700/30 font-medium dark:text-white text-dark'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
<span>{category}</span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
<div
|
||||
class="mt-3 flex flex-row items-center space-x-2 border-b border-neutral-300 pb-3 dark:border-neutral-700/40 md:mt-0 md:flex-col md:space-x-0 md:space-y-1"
|
||||
>
|
||||
<a
|
||||
href="https://github.com/pheralb/svgl#-getting-started"
|
||||
target="_blank"
|
||||
class="flex w-full items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
|
||||
>
|
||||
<div><Box size={18} /></div>
|
||||
<span>Submit logo</span>
|
||||
<div><ArrowUpRight size={12} /></div>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/pheralb/svgl"
|
||||
target="_blank"
|
||||
class="flex w-full items-center space-x-2 rounded-md p-2 duration-100 hover:bg-neutral-200 dark:hover:bg-neutral-700/40"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div><GithubLogo size={18} /></div>
|
||||
<span class="flex items-center justify-center">Repository</span>
|
||||
{#if data.stars}
|
||||
<div class="flex items-center space-x-1 text-neutral-600 dark:text-neutral-400">
|
||||
<div>
|
||||
<Star size={14} weight="duotone" class="text-yellow-700 dark:text-yellow-500" />
|
||||
</div>
|
||||
<span>{data.stars}</span>
|
||||
</div>
|
||||
{/if}
|
||||
<div><ArrowUpRight size={12} /></div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<a
|
||||
href="https://twitter.com/pheralb_"
|
||||
target="_blank"
|
||||
class="mt-5 flex items-center space-x-2 duration-100 hover:text-dark dark:text-neutral-400 dark:hover:text-white"
|
||||
class="group mt-5 md:flex hidden items-center space-x-2 duration-100 hover:text-dark dark:text-neutral-400 dark:hover:text-white"
|
||||
>
|
||||
<Heart color="#991b1b" size={18} weight={'duotone'} />
|
||||
<div class="flex items-center space-x-1">
|
||||
<p class="text-muted text-sm">Created by pheralb</p>
|
||||
<ArrowUpRight size={12} />
|
||||
<ArrowUpRight
|
||||
size={14}
|
||||
class="transition-transform duration-300 group-hover:-translate-y-[1px]"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="py-2 md:ml-60 md:py-6">
|
||||
{#if data.pathname !== '/'}
|
||||
<Container>
|
||||
<a href="/">
|
||||
<div
|
||||
class="flex items-center space-x-2 duration-100 hover:text-neutral-500 dark:text-neutral-400 dark:hover:text-white"
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
<span>View all</span>
|
||||
</div>
|
||||
</a>
|
||||
</Container>
|
||||
{/if}
|
||||
</aside>
|
||||
<div class="ml-0 md:ml-56 pb-6">
|
||||
<Warning />
|
||||
<Transition pathname={data.pathname}>
|
||||
<slot />
|
||||
</Transition>
|
||||
<Toaster
|
||||
position="bottom-right"
|
||||
toastOptions={{
|
||||
style: `background-color: #171717;
|
||||
class: 'font-sans',
|
||||
descriptionClass: 'font-mono',
|
||||
style: `background-color: #262626;
|
||||
color: #ffff;
|
||||
border-radius: 0.4rem; border: 1px solid #262626;`
|
||||
border-radius: 0.4rem; border: 1px solid #121212;`
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+25
-22
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { iSVG } from '@/types/svg';
|
||||
import { cn } from '@/utils/cn';
|
||||
|
||||
// Get all svgs:
|
||||
import { svgsData } from '@/data';
|
||||
@@ -13,19 +14,22 @@
|
||||
import NotFound from '@/components/notFound.svelte';
|
||||
|
||||
// Icons:
|
||||
import ArrowsClockWise from 'phosphor-svelte/lib/ArrowsClockwise';
|
||||
import ArrowSquareOut from 'phosphor-svelte/lib/ArrowSquareOut';
|
||||
import { ArrowDownUpIcon, ArrowUpDownIcon } from 'lucide-svelte';
|
||||
|
||||
let sorted: boolean = false;
|
||||
|
||||
// Search:
|
||||
let searchTerm = '';
|
||||
let filteredSvgs: iSVG[] = [];
|
||||
|
||||
// Order by last added:
|
||||
if (searchTerm.length === 0) {
|
||||
filteredSvgs = allSvgs.sort((a: iSVG, b: iSVG) => {
|
||||
return b.id! - a.id!;
|
||||
});
|
||||
}
|
||||
|
||||
// Search svgs:
|
||||
const searchSvgs = () => {
|
||||
return (filteredSvgs = allSvgs.filter((svg: iSVG) => {
|
||||
let svgTitle = svg.title.toLowerCase();
|
||||
@@ -33,14 +37,13 @@
|
||||
}));
|
||||
};
|
||||
|
||||
// Clear search:
|
||||
const clearSearch = () => {
|
||||
searchTerm = '';
|
||||
searchSvgs();
|
||||
};
|
||||
|
||||
// Sort:
|
||||
let sorted: boolean = false;
|
||||
|
||||
const sort = () => {
|
||||
if (sorted) {
|
||||
sortByLatest();
|
||||
@@ -52,7 +55,7 @@
|
||||
|
||||
// Sort alphabetically:
|
||||
const sortAlphabetically = () => {
|
||||
filteredSvgs = filteredSvgs.sort((a: iSVG, b: iSVG) => {
|
||||
filteredSvgs = allSvgs.sort((a: iSVG, b: iSVG) => {
|
||||
return a.title.localeCompare(b.title);
|
||||
});
|
||||
};
|
||||
@@ -69,29 +72,29 @@
|
||||
<title>A beautiful library with SVG logos - Svgl</title>
|
||||
</svelte:head>
|
||||
|
||||
<Search
|
||||
bind:searchTerm
|
||||
on:input={searchSvgs}
|
||||
clearSearch={() => clearSearch()}
|
||||
placeholder={`Search ${filteredSvgs.length} logos...`}
|
||||
/>
|
||||
|
||||
<Container>
|
||||
<Search
|
||||
bind:searchTerm
|
||||
on:input={searchSvgs}
|
||||
clearSearch={() => clearSearch()}
|
||||
placeholder={`Search ${filteredSvgs.length} logos...`}
|
||||
/>
|
||||
<div class={`flex items-center justify-between my-4 ${filteredSvgs.length === 0 && 'hidden'}`}>
|
||||
<div class="flex items-center justify-end mb-4">
|
||||
<button
|
||||
class="flex items-center justify-center space-x-2 rounded-md py-1.5 text-sm font-medium text-neutral-500 dark:text-neutral-400 hover:text-dark dark:hover:text-white duration-100 transition-colors"
|
||||
class={cn(
|
||||
'flex items-center justify-center space-x-1 rounded-md px-3 py-1.5 text-sm font-medium hover:opacity-80 transition-opacity',
|
||||
filteredSvgs.length === 0 && 'hidden'
|
||||
)}
|
||||
on:click={() => sort()}
|
||||
>
|
||||
<ArrowsClockWise size={14} />
|
||||
{#if sorted}
|
||||
<ArrowDownUpIcon size={16} strokeWidth={2} class="mr-1" />
|
||||
{:else}
|
||||
<ArrowUpDownIcon size={16} strokeWidth={2} class="mr-1" />
|
||||
{/if}
|
||||
<span>{sorted ? 'Sort by latest' : 'Sort alphabetically'}</span>
|
||||
</button>
|
||||
<a
|
||||
class="flex items-center justify-center space-x-2 rounded-md py-1.5 text-sm font-medium text-neutral-500 dark:text-neutral-400 hover:text-dark dark:hover:text-white duration-100 transition-colors"
|
||||
href="https://github.com/pheralb/svgl?tab=readme-ov-file#-getting-started"
|
||||
target="_blank"
|
||||
>
|
||||
<span>Submit SVG</span>
|
||||
<ArrowSquareOut size={14} />
|
||||
</a>
|
||||
</div>
|
||||
<Grid>
|
||||
{#each filteredSvgs as svg}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<script>
|
||||
import { cn } from '@/utils/cn';
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{data.meta.title} - SVGL</title>
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:title" content={data.meta.title} />
|
||||
<meta property="og:description" content={data.meta.description} />
|
||||
</svelte:head>
|
||||
|
||||
<section
|
||||
class="bg-white dark:bg-neutral-900 bg-[url('/images/hero-pattern_light.svg')] dark:bg-[url('/images/hero-pattern_dark.svg')]"
|
||||
>
|
||||
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-20 z-10 relative">
|
||||
<div class="flex items-center space-x-4 text-center justify-center">
|
||||
<h1
|
||||
class="mb-4 text-4xl font-bold tracking-tight leading-none text-neutral-900 md:text-5xl lg:text-6xl dark:text-white"
|
||||
>
|
||||
API Reference
|
||||
</h1>
|
||||
<span class="relative inline-block overflow-hidden rounded-full p-[1px] shadow-sm">
|
||||
<span
|
||||
class="absolute inset-[-1000%] animate-[spin_4s_linear_infinite] bg-[conic-gradient(from_90deg_at_50%_50%,#f4f4f5_0%,#f4f4f5_50%,#737373_100%)] dark:bg-[conic-gradient(from_90deg_at_50%_50%,#121212_0%,#121212_50%,#737373_100%)]"
|
||||
/>
|
||||
<div
|
||||
class="inline-flex h-full w-full cursor-default items-center justify-center rounded-full bg-neutral-100 dark:bg-neutral-900 px-3 py-1 text-xs font-medium dark:text-white backdrop-blur-3xl border border-neutral-100 dark:border-neutral-800 font-mono"
|
||||
>
|
||||
beta
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-lg font-normal text-gray-500 lg:text-xl sm:px-16 lg:px-48 dark:text-gray-200">
|
||||
The API reference is a detailed documentation of all the endpoints available in the API.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<article
|
||||
class={cn(
|
||||
'prose dark:prose-invert',
|
||||
'mx-auto py-10 px-4 max-w-3xl',
|
||||
'prose-h2:font-medium',
|
||||
'prose-pre:m-0'
|
||||
)}
|
||||
>
|
||||
<svelte:component this={data.content} />
|
||||
</article>
|
||||
@@ -0,0 +1,14 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export async function load() {
|
||||
try {
|
||||
const documentTitle = 'api';
|
||||
const post = await import(`../../docs/${documentTitle}.md`);
|
||||
return {
|
||||
content: post.default,
|
||||
meta: post.metadata
|
||||
};
|
||||
} catch (e) {
|
||||
throw error(404, `Could not find this page`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { RequestEvent } from './$types';
|
||||
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { ratelimit } from '@/server/redis';
|
||||
|
||||
// Data:
|
||||
import { svgs } from '@/data/svgs';
|
||||
|
||||
export const GET = async ({ request }: RequestEvent) => {
|
||||
const categories = svgs
|
||||
.map((svg) => svg.category)
|
||||
.filter((category, index, array) => array.indexOf(category) === index);
|
||||
const ip = request.headers.get('x-forwarded-for') ?? '';
|
||||
const { success, reset } = await ratelimit.limit(ip);
|
||||
|
||||
// Error 429 | If rate limit is exceeded:
|
||||
if (!success) {
|
||||
const now = Date.now();
|
||||
const retryAfter = Math.floor((reset - now) / 1000);
|
||||
return new Response('Too Many Requests', {
|
||||
status: 429,
|
||||
headers: {
|
||||
'Retry-After': retryAfter.toString()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Status 200 | If limit is a number:
|
||||
return json(
|
||||
categories.map((category) => {
|
||||
return {
|
||||
category,
|
||||
total: svgs.filter((svg) => svg.category === category).length
|
||||
};
|
||||
}),
|
||||
{ status: 200 }
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
import type { RequestEvent } from './$types';
|
||||
import type { iSVG } from '@/types/svg';
|
||||
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
import { ratelimit } from '@/server/redis';
|
||||
|
||||
// Data:
|
||||
import { svgsData } from '@/data';
|
||||
|
||||
export const GET = async ({ url, request }: RequestEvent) => {
|
||||
const fullUrl = url.origin ?? 'svgl.vercel.app';
|
||||
const ip = request.headers.get('x-forwarded-for') ?? '';
|
||||
const { success, reset } = await ratelimit.limit(ip);
|
||||
|
||||
// Error 429 | If rate limit is exceeded:
|
||||
if (!success) {
|
||||
const now = Date.now();
|
||||
const retryAfter = Math.floor((reset - now) / 1000);
|
||||
return new Response('Too Many Requests', {
|
||||
status: 429,
|
||||
headers: {
|
||||
'Retry-After': retryAfter.toString()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Params:
|
||||
const getLimitParams = url.searchParams.get('limit');
|
||||
const getCategoryParams = url.searchParams.get('category');
|
||||
const getSearchParams = url.searchParams.get('search');
|
||||
|
||||
// Add full route to svgs:
|
||||
const fullRouteSvgsData: iSVG[] = svgsData.map((svg) => {
|
||||
if (typeof svg.route === 'object' && svg.route !== null) {
|
||||
return {
|
||||
...svg,
|
||||
route: {
|
||||
light: `${fullUrl}${svg.route.light}`,
|
||||
dark: `${fullUrl}${svg.route.dark}`
|
||||
}
|
||||
};
|
||||
} else if (typeof svg.route === 'string') {
|
||||
return {
|
||||
...svg,
|
||||
route: `${fullUrl}${svg.route}`
|
||||
};
|
||||
}
|
||||
return svg;
|
||||
});
|
||||
|
||||
// Status 200 | If no limit is provided, return all svgs:
|
||||
if (!getLimitParams && !getCategoryParams && !getSearchParams) {
|
||||
return json(fullRouteSvgsData, { status: 200 });
|
||||
}
|
||||
|
||||
const limit = Number(getLimitParams);
|
||||
const category = getCategoryParams;
|
||||
|
||||
if (category) {
|
||||
const categorySvgs = fullRouteSvgsData.filter((svg) => {
|
||||
return svg.category === category.charAt(0).toUpperCase() + category.slice(1);
|
||||
});
|
||||
|
||||
// Error 400 | If category does not exist:
|
||||
if (categorySvgs.length === 0) {
|
||||
error(400, {
|
||||
message: 'Category does not exist.'
|
||||
});
|
||||
}
|
||||
|
||||
if (!getLimitParams) {
|
||||
return json(categorySvgs, { status: 200 });
|
||||
}
|
||||
|
||||
return json(categorySvgs.slice(0, limit), { status: 200 });
|
||||
}
|
||||
|
||||
if (getSearchParams) {
|
||||
const searchSvgs = fullRouteSvgsData.filter((svg) => {
|
||||
return svg.title.toLowerCase().includes(getSearchParams.toLowerCase());
|
||||
});
|
||||
|
||||
// Error 400 | If search does not exist:
|
||||
if (searchSvgs.length === 0) {
|
||||
error(400, {
|
||||
message: 'Search does not exist.'
|
||||
});
|
||||
}
|
||||
|
||||
if (!getLimitParams) {
|
||||
return json(searchSvgs, { status: 200 });
|
||||
}
|
||||
|
||||
return json(searchSvgs.slice(0, limit), { status: 200 });
|
||||
}
|
||||
|
||||
// Error 400 | if limit is not a number:
|
||||
if (isNaN(limit)) {
|
||||
error(400, {
|
||||
message: 'Limit must be a number.'
|
||||
});
|
||||
}
|
||||
|
||||
// Error 400 | If limit is not positive:
|
||||
if (limit < 1) {
|
||||
error(400, {
|
||||
message: 'Limit must be a positive number.'
|
||||
});
|
||||
}
|
||||
|
||||
// Error 400 | If limit is greater than the number of svgs:
|
||||
if (limit > fullRouteSvgsData.length) {
|
||||
error(400, {
|
||||
message: 'Limit is greater than the number of svgs.'
|
||||
});
|
||||
}
|
||||
|
||||
// Status 200 | If limit is a number:
|
||||
return json(fullRouteSvgsData.slice(0, limit), { status: 200 });
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
<script>
|
||||
import Container from '@/components/container.svelte';
|
||||
import { ArrowLeft } from 'lucide-svelte';
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<a href="/">
|
||||
<div
|
||||
class="flex items-center space-x-2 duration-100 hover:text-neutral-500 dark:text-neutral-400 dark:hover:text-white group md:mt-2"
|
||||
>
|
||||
<ArrowLeft size={20} class="group-hover:-translate-x-[2px] group-hover:duration-200" />
|
||||
<span>View all</span>
|
||||
</div>
|
||||
</a>
|
||||
</Container>
|
||||
<slot />
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Redis } from '@upstash/redis';
|
||||
import { Ratelimit } from '@upstash/ratelimit';
|
||||
import { UPSTASH_REDIS_TOKEN, UPSTASH_REDIS_URL, SVGL_API_REQUESTS } from '$env/static/private';
|
||||
|
||||
const redis = new Redis({
|
||||
url: UPSTASH_REDIS_URL,
|
||||
token: UPSTASH_REDIS_TOKEN
|
||||
});
|
||||
|
||||
export const ratelimit = new Ratelimit({
|
||||
redis: redis,
|
||||
limiter: Ratelimit.slidingWindow(Number(SVGL_API_REQUESTS), '60s'),
|
||||
analytics: true
|
||||
});
|
||||
@@ -14,4 +14,8 @@ export type tCategory =
|
||||
| 'Browser'
|
||||
| 'Language'
|
||||
| 'Education'
|
||||
| 'Design';
|
||||
| 'Design'
|
||||
| 'Typing'
|
||||
| 'Community'
|
||||
| 'Marketplace'
|
||||
| 'Fintech';
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import type { TransitionConfig } from 'svelte/transition';
|
||||
|
||||
type FlyAndScaleParams = {
|
||||
y?: number;
|
||||
x?: number;
|
||||
start?: number;
|
||||
duration?: number;
|
||||
};
|
||||
export const flyAndScale = (
|
||||
node: Element,
|
||||
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
|
||||
): TransitionConfig => {
|
||||
const style = getComputedStyle(node);
|
||||
const transform = style.transform === 'none' ? '' : style.transform;
|
||||
const scaleConversion = (valueA: number, scaleA: [number, number], scaleB: [number, number]) => {
|
||||
const [minA, maxA] = scaleA;
|
||||
const [minB, maxB] = scaleB;
|
||||
const percentage = (valueA - minA) / (maxA - minA);
|
||||
const valueB = percentage * (maxB - minB) + minB;
|
||||
return valueB;
|
||||
};
|
||||
const styleToString = (style: Record<string, number | string | undefined>): string => {
|
||||
return Object.keys(style).reduce((str, key) => {
|
||||
if (style[key] === undefined) return str;
|
||||
return str + key + ':' + style[key] + ';';
|
||||
}, '');
|
||||
};
|
||||
return {
|
||||
duration: params.duration ?? 200,
|
||||
delay: 0,
|
||||
css: (t) => {
|
||||
const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
|
||||
const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
|
||||
const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
|
||||
return styleToString({
|
||||
transform: transform + 'translate3d(' + x + 'px, ' + y + 'px, 0) scale(' + scale + ')',
|
||||
opacity: t
|
||||
});
|
||||
},
|
||||
easing: cubicOut
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user