mirror of
https://github.com/pheralb/svgl.git
synced 2025-12-29 08:01:36 +08:00
Compare commits
9 Commits
a488bd4c7a
...
55199765be
| Author | SHA1 | Date | |
|---|---|---|---|
| 55199765be | |||
| 4cd2c84273 | |||
| 733e136b3a | |||
| 2c3fdf79fe | |||
| 374fb8f2d5 | |||
| 2927e42659 | |||
| d947f7f907 | |||
| d2e418363d | |||
| 5df7a336ba |
@@ -0,0 +1,19 @@
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
README.md
|
||||
.npmrc
|
||||
.prettierrc
|
||||
prettier.config.mjs
|
||||
.eslintrc.cjs
|
||||
eslint.config.mjs
|
||||
.graphqlrc
|
||||
.editorconfig
|
||||
.svelte-kit
|
||||
.vscode
|
||||
node_modules
|
||||
build
|
||||
package
|
||||
**/.env
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
FROM node:22.17.0-alpine AS base
|
||||
|
||||
# Install pnpm
|
||||
RUN npm install -g pnpm@10.13.1
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies with cache
|
||||
FROM base AS deps
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Build the application
|
||||
FROM base AS builder
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
ENV PUBLIC_SVGL_VERSION=beta
|
||||
RUN pnpm run build:prod
|
||||
|
||||
# Production image
|
||||
FROM node:22.17.0-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
# Copy necessary files from builder
|
||||
COPY --from=builder /app/build ./build
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY package.json ./
|
||||
|
||||
# Set production environment
|
||||
ENV NODE_ENV=production
|
||||
ENV PUBLIC_SVGL_VERSION=beta
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Start the server
|
||||
CMD ["node", "build"]
|
||||
+3
-8
@@ -41,14 +41,9 @@ export default ts.config(
|
||||
},
|
||||
rules: {
|
||||
"svelte/no-unused-svelte-ignore": "warn",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^(_|\\$\\$)",
|
||||
destructuredArrayIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
"svelte/no-useless-mustaches": "warn",
|
||||
"svelte/require-each-key": "warn",
|
||||
"svelte/no-at-html-tags": "off",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
+33
-37
@@ -13,7 +13,6 @@ import { parseReactSvgContent } from "./src/utils/parseReactSvgContent";
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
// ⚙️ Settings:
|
||||
const MINIFY_TSX = false;
|
||||
const REGENERATE_ALL = true;
|
||||
const SVGS_DATA = svgs;
|
||||
const PUBLIC_FOLDER = "static";
|
||||
@@ -31,7 +30,6 @@ interface RegistryItem {
|
||||
name: string;
|
||||
type: string;
|
||||
title: string;
|
||||
registryDependencies: string[];
|
||||
files: RegistryFile[];
|
||||
}
|
||||
|
||||
@@ -59,7 +57,7 @@ function prepareRegistryJson(): ShadcnSchema {
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/[^a-z0-9-]/g, "");
|
||||
|
||||
|
||||
const files: RegistryFile[] = [];
|
||||
|
||||
const svgPaths = extractSvgPaths(svg);
|
||||
@@ -81,7 +79,6 @@ function prepareRegistryJson(): ShadcnSchema {
|
||||
name: componentName,
|
||||
type: "registry:component",
|
||||
title: componentName,
|
||||
registryDependencies: [""],
|
||||
files: files,
|
||||
});
|
||||
}
|
||||
@@ -188,41 +185,18 @@ function convertToFilesystemPath(svgPath: string): string {
|
||||
return `./${PUBLIC_FOLDER}/${cleanPath}`;
|
||||
}
|
||||
|
||||
function createSpinner() {
|
||||
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
||||
let current = 0;
|
||||
let interval;
|
||||
|
||||
return {
|
||||
start(getMessage) {
|
||||
interval = setInterval(() => {
|
||||
const message =
|
||||
typeof getMessage === "function" ? getMessage() : getMessage;
|
||||
process.stdout.write(`\r${frames[current]} ${message}`);
|
||||
current = (current + 1) % frames.length;
|
||||
}, 100);
|
||||
},
|
||||
stop() {
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
process.stdout.write("\r");
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function convertSvgToReact(svgPath: string): Promise<string> {
|
||||
const rawSvg = await fs.promises.readFile(svgPath, "utf-8");
|
||||
const optimizedSvg = optimizeSvg({ svgCode: rawSvg });
|
||||
const componentName = parseSvgFilename({
|
||||
file: path.basename(svgPath, ".svg"),
|
||||
log: true,
|
||||
firstUpperCase: true,
|
||||
});
|
||||
const code = await parseReactSvgContent({
|
||||
componentName,
|
||||
svgCode: optimizedSvg,
|
||||
typescript: true,
|
||||
minify: MINIFY_TSX,
|
||||
});
|
||||
return code;
|
||||
}
|
||||
@@ -263,8 +237,35 @@ async function runShadcnBuild() {
|
||||
}
|
||||
}
|
||||
|
||||
const checkFinallyDirs = async () => {
|
||||
// Check if static/r directory exists
|
||||
const rDirExists = await fs.promises
|
||||
.access(`./${PUBLIC_FOLDER}/r`)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (!rDirExists) {
|
||||
console.error("[🔎] Error - Directory ./static/r does not exist");
|
||||
return;
|
||||
} else {
|
||||
console.log("[🔎] Directory ./static/r exists");
|
||||
}
|
||||
|
||||
// Check if registry.json exists
|
||||
const registryExists = await fs.promises
|
||||
.access("./registry.json")
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (!registryExists) {
|
||||
console.error("[🔎] Error - File registry.json does not exist");
|
||||
return;
|
||||
} else {
|
||||
console.log("[🔎] File registry.json exists");
|
||||
}
|
||||
};
|
||||
|
||||
async function run() {
|
||||
const spinner = createSpinner();
|
||||
let convertedCount = 0;
|
||||
let totalCount = 0;
|
||||
|
||||
@@ -287,9 +288,7 @@ async function run() {
|
||||
return;
|
||||
}
|
||||
|
||||
spinner.start(
|
||||
() => `[📦] ${convertedCount}/${totalCount} SVGs converted to TSX`,
|
||||
);
|
||||
console.log(`[📦] Converting ${totalCount} SVGs converted to TSX...`);
|
||||
|
||||
// Process files
|
||||
for (const svgFile of svgFiles) {
|
||||
@@ -319,11 +318,8 @@ async function run() {
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
spinner.stop();
|
||||
|
||||
console.log(
|
||||
`\n[✅] Conversion completed: ${convertedCount}/${totalCount} SVGs processed`,
|
||||
`\n[📦] ✨ Conversion completed: ${convertedCount}/${totalCount} SVGs processed`,
|
||||
);
|
||||
|
||||
if (convertedCount < totalCount) {
|
||||
@@ -335,10 +331,10 @@ async function run() {
|
||||
await runShadcnBuild();
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.stop();
|
||||
console.error("[❌] Error:", error);
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
await checkFinallyDirs();
|
||||
await cleanupDirectory(OUTPUT_DIR);
|
||||
console.log("[🎉] Process completed");
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
[variables]
|
||||
NODE_VERSION = "22.17.0"
|
||||
PNPM_VERSION = "10.13.1"
|
||||
|
||||
[providers.node]
|
||||
version = "22.17.0"
|
||||
|
||||
[phases.setup]
|
||||
cmds = ["npm install -g pnpm@10.13.1"]
|
||||
|
||||
[phases.install]
|
||||
cmds = ["pnpm install --frozen-lockfile"]
|
||||
|
||||
[phases.build]
|
||||
# Build command using PNPM
|
||||
cmds = ["pnpm run build"]
|
||||
|
||||
[start]
|
||||
# Start command using PNPM
|
||||
cmd = "node build"
|
||||
+1
-1
@@ -37,7 +37,7 @@
|
||||
"lint": "eslint ./src",
|
||||
"lint:fix": "eslint ./src --fix",
|
||||
"build:shadcn": "shadcn build --output ./static/r",
|
||||
"build:prod": "vite build && pnpm build:registry",
|
||||
"build:prod": "pnpm build:registry && vite build",
|
||||
"build:registry": "tsx ./generate-registry.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Component, Snippet, SvelteComponent } from "svelte";
|
||||
import type { Component } from "svelte";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
import CopyIcon from "@lucide/svelte/icons/copy";
|
||||
@@ -18,21 +18,17 @@
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await clipboard(code);
|
||||
await clipboard(code);
|
||||
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
copied = true;
|
||||
timeoutId = setTimeout(() => {
|
||||
copied = false;
|
||||
timeoutId = null;
|
||||
}, copyDuration);
|
||||
} catch (error) {
|
||||
copied = false;
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
copied = true;
|
||||
timeoutId = setTimeout(() => {
|
||||
copied = false;
|
||||
timeoutId = null;
|
||||
}, copyDuration);
|
||||
};
|
||||
|
||||
$effect(() => {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{#each categories.sort() as category}
|
||||
{#each categories.sort() as category (category)}
|
||||
<a
|
||||
href={`/directory/${category.toLowerCase()}`}
|
||||
data-sveltekit-preload-data
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
{/if}
|
||||
</Select.Trigger>
|
||||
<Select.Content sideOffset={1.5}>
|
||||
{#each Object.entries(managers) as [value, { Icon, label }]}
|
||||
{#each Object.entries(managers) as [value, { Icon, label }] (value)}
|
||||
<Select.Item
|
||||
{value}
|
||||
onclick={() => pkgManager.set(value as PackageManager)}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger>
|
||||
{@render children?.()}
|
||||
{@render children()}
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Content class="text-sm">
|
||||
<Dialog.Header>
|
||||
@@ -33,6 +33,6 @@
|
||||
</p>
|
||||
<CodeBlock code={registryCode} />
|
||||
<p class="mt-2">2. Then use the following command to add SVGs:</p>
|
||||
<CodeBlock code={`npx shadcn@latest add @svgl/[svg-name]`} />
|
||||
<CodeBlock code="npx shadcn@latest add @svgl/[svg-name]" />
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
</p>
|
||||
<div class="flex items-center justify-center space-x-1">
|
||||
{#if Array.isArray(svgInfo.category)}
|
||||
{#each svgInfo.category.slice(0, maxVisibleCategories) as c, index}
|
||||
{#each svgInfo.category.slice(0, maxVisibleCategories) as c (c)}
|
||||
<a
|
||||
href={`/directory/${c.toLowerCase()}`}
|
||||
class={badgeVariants({
|
||||
@@ -176,7 +176,7 @@
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="flex w-auto flex-col space-y-2">
|
||||
<p class="font-medium">More tags:</p>
|
||||
{#each svgInfo.category.slice(maxVisibleCategories) as c}
|
||||
{#each svgInfo.category.slice(maxVisibleCategories) as c (c)}
|
||||
<a
|
||||
href={`/directory/${c.toLowerCase()}`}
|
||||
class={cn(buttonVariants({ variant: "outline" }), "w-full")}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
HTMLAnchorAttributes,
|
||||
HTMLButtonAttributes,
|
||||
} from "svelte/elements";
|
||||
import { type VariantProps, tv } from "tailwind-variants";
|
||||
import type { VariantProps } from "tailwind-variants";
|
||||
import type { WithElementRef } from "@/types/components";
|
||||
|
||||
import { cn } from "@/utils/cn";
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
</PageHeader>
|
||||
<Container className="my-6">
|
||||
<Grid>
|
||||
{#each displaySvgs as svg}
|
||||
{#each displaySvgs as svg (svg.id)}
|
||||
<SvgCard svgInfo={svg} />
|
||||
{/each}
|
||||
</Grid>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
// States:
|
||||
let searchTerm = $state<string>(data.searchTerm || "");
|
||||
let filteredSvgs = $state<iSVG[]>(data.filteredSvgs);
|
||||
let filteredSvgs = $derived<iSVG[]>(data.filteredSvgs);
|
||||
|
||||
const searchSvgs = () => {
|
||||
if (!searchTerm) {
|
||||
@@ -120,7 +120,7 @@
|
||||
</PageHeader>
|
||||
<Container className="my-6">
|
||||
<Grid>
|
||||
{#each filteredSvgs as svg}
|
||||
{#each filteredSvgs as svg (svg.id)}
|
||||
<SvgCard svgInfo={svg} />
|
||||
{/each}
|
||||
</Grid>
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
interface ParseSvgFilename {
|
||||
file: string;
|
||||
log?: boolean;
|
||||
firstUpperCase?: boolean;
|
||||
}
|
||||
|
||||
export const parseSvgFilename = (params: ParseSvgFilename): string => {
|
||||
const { file, log } = params;
|
||||
const { file, log, firstUpperCase = false } = params;
|
||||
const name = file.replace(/\.svg$/i, "");
|
||||
|
||||
let component = name.replace(/(^\w|[-_]\w)/g, (m) =>
|
||||
m.replace(/[-_]/, "").toUpperCase(),
|
||||
);
|
||||
let component = name
|
||||
.replace(/[-_\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ""))
|
||||
.replace(/^(.)/, (char) =>
|
||||
firstUpperCase ? char.toUpperCase() : char.toLowerCase(),
|
||||
);
|
||||
|
||||
if (/^\d/.test(component)) {
|
||||
if (log) {
|
||||
|
||||
Reference in New Issue
Block a user