mirror of
https://github.com/pheralb/svgl.git
synced 2025-12-29 08:01:36 +08:00
✨ Add settings management with package manager and SVG optimization options
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
import SvglVersion from "@/components/svglVersion.svelte";
|
import SvglVersion from "@/components/svglVersion.svelte";
|
||||||
import SendIcon from "@/components/ui/moving-icons/send-icon.svelte";
|
import SendIcon from "@/components/ui/moving-icons/send-icon.svelte";
|
||||||
import SidebarMobileMenu from "@/components/layout/sidebarMobileMenu.svelte";
|
import SidebarMobileMenu from "@/components/layout/sidebarMobileMenu.svelte";
|
||||||
|
import SettingsMenu from "../settings/settingsMenu.svelte";
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
githubStars?: number;
|
githubStars?: number;
|
||||||
@@ -55,6 +56,7 @@
|
|||||||
"hover:bg-neutral-200 dark:hover:bg-neutral-800",
|
"hover:bg-neutral-200 dark:hover:bg-neutral-800",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<SettingsMenu />
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden h-5 items-center space-x-2 md:flex">
|
<div class="hidden h-5 items-center space-x-2 md:flex">
|
||||||
<Separator orientation="vertical" />
|
<Separator orientation="vertical" />
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { settingsStore } from "@/stores/settings.store";
|
||||||
|
|
||||||
|
let optimize = $derived($settingsStore.optimizeSvgs);
|
||||||
|
|
||||||
|
const handleOptimizeChange = (checked: boolean) => {
|
||||||
|
settingsStore.setOptimizeSvgs(checked);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<Switch
|
||||||
|
id="optimize"
|
||||||
|
checked={optimize}
|
||||||
|
onCheckedChange={handleOptimizeChange}
|
||||||
|
/>
|
||||||
|
<label for="optimize">Optimize SVGs</label>
|
||||||
|
</div>
|
||||||
+14
-8
@@ -1,16 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { Component } from "svelte";
|
||||||
import * as Select from "@/components/ui/select";
|
import * as Select from "@/components/ui/select";
|
||||||
import { pkgManager, type PackageManager } from "@/stores/pkgManager.store";
|
import { buttonVariants } from "@/components/ui/button";
|
||||||
|
|
||||||
|
import { settingsStore, type PackageManager } from "@/stores/settings.store";
|
||||||
|
|
||||||
import Npm from "@/components/logos/npm.svelte";
|
import Npm from "@/components/logos/npm.svelte";
|
||||||
import Pnpm from "@/components/logos/pnpm.svelte";
|
import Pnpm from "@/components/logos/pnpm.svelte";
|
||||||
import Yarn from "@/components/logos/yarn.svelte";
|
import Yarn from "@/components/logos/yarn.svelte";
|
||||||
import Bun from "@/components/logos/bun.svelte";
|
import Bun from "@/components/logos/bun.svelte";
|
||||||
import { buttonVariants } from "./ui/button";
|
|
||||||
|
|
||||||
let pkg = $derived($pkgManager);
|
let pkg = $derived($settingsStore.packageManager);
|
||||||
|
|
||||||
const managers = {
|
const managers: Record<PackageManager, { label: string; Icon: Component }> = {
|
||||||
npm: { label: "npm", Icon: Npm },
|
npm: { label: "npm", Icon: Npm },
|
||||||
pnpm: { label: "pnpm", Icon: Pnpm },
|
pnpm: { label: "pnpm", Icon: Pnpm },
|
||||||
yarn: { label: "yarn", Icon: Yarn },
|
yarn: { label: "yarn", Icon: Yarn },
|
||||||
@@ -19,18 +21,22 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Select.Root type="single" bind:value={pkg}>
|
<Select.Root type="single" bind:value={pkg}>
|
||||||
<Select.Trigger class={buttonVariants({ variant: "outline", size: "sm" })}>
|
<Select.Trigger
|
||||||
|
class={buttonVariants({ variant: "outline", class: "justify-between" })}
|
||||||
|
>
|
||||||
{#if managers[pkg]}
|
{#if managers[pkg]}
|
||||||
{@const { Icon, label } = managers[pkg]}
|
{@const { Icon, label } = managers[pkg]}
|
||||||
<Icon size={14} />
|
<div class="flex items-center space-x-2.5">
|
||||||
<span>{label}</span>
|
<Icon size={16} />
|
||||||
|
<span>{label}</span>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</Select.Trigger>
|
</Select.Trigger>
|
||||||
<Select.Content sideOffset={1.5}>
|
<Select.Content sideOffset={1.5}>
|
||||||
{#each Object.entries(managers) as [value, { Icon, label }] (value)}
|
{#each Object.entries(managers) as [value, { Icon, label }] (value)}
|
||||||
<Select.Item
|
<Select.Item
|
||||||
{value}
|
{value}
|
||||||
onclick={() => pkgManager.set(value as PackageManager)}
|
onclick={() => settingsStore.setPackageManager(value as PackageManager)}
|
||||||
>
|
>
|
||||||
<Icon size={16} />
|
<Icon size={16} />
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
|
interface SettingsCardProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { title, description, children }: SettingsCardProps = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h3 class="mb-0.5 font-medium">{title}</h3>
|
||||||
|
<p class="mb-3 text-sm text-neutral-600 dark:text-neutral-400">
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { cn } from "@/utils/cn";
|
||||||
|
import { toast } from "svelte-sonner";
|
||||||
|
|
||||||
|
import { settingsStore } from "@/stores/settings.store";
|
||||||
|
import SettingsCard from "@/components/settings/settingsCard.svelte";
|
||||||
|
import SettingsIcon from "@lucide/svelte/icons/settings";
|
||||||
|
|
||||||
|
import * as Dialog from "@/components/ui/dialog";
|
||||||
|
import { Button, buttonVariants } from "@/components/ui/button";
|
||||||
|
import Separator from "@/components/ui/separator/separator.svelte";
|
||||||
|
|
||||||
|
import OptimizeSvgs from "@/components/settings/options/optimizeSvgs.svelte";
|
||||||
|
import SelectPkgManager from "@/components/settings/options/selectPkgManager.svelte";
|
||||||
|
|
||||||
|
const handleResetSettings = () => {
|
||||||
|
settingsStore.reset();
|
||||||
|
toast.success("Settings have been reset to default");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog.Root>
|
||||||
|
<Dialog.Trigger
|
||||||
|
class={cn(
|
||||||
|
buttonVariants({ variant: "ghost", size: "icon" }),
|
||||||
|
"hover:bg-neutral-200 dark:hover:bg-neutral-800",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SettingsIcon size={20} strokeWidth={1.5} />
|
||||||
|
</Dialog.Trigger>
|
||||||
|
<Dialog.Content>
|
||||||
|
<Dialog.Header>
|
||||||
|
<Dialog.Title>Settings</Dialog.Title>
|
||||||
|
<Dialog.Description>Customize your preferences.</Dialog.Description>
|
||||||
|
</Dialog.Header>
|
||||||
|
<Separator />
|
||||||
|
<div class="my-3 flex flex-col space-y-8">
|
||||||
|
<SettingsCard
|
||||||
|
title="Package Manager"
|
||||||
|
description="Select your preferred package manager for all installations commands"
|
||||||
|
>
|
||||||
|
<SelectPkgManager />
|
||||||
|
</SettingsCard>
|
||||||
|
<SettingsCard
|
||||||
|
title="Copy SVGs"
|
||||||
|
description="Use SVGO to optimize your SVGs when you copy source code (including all frameworks)"
|
||||||
|
>
|
||||||
|
<OptimizeSvgs />
|
||||||
|
</SettingsCard>
|
||||||
|
</div>
|
||||||
|
<Dialog.Footer>
|
||||||
|
<Button variant="outline" onclick={handleResetSettings}>
|
||||||
|
<span>Reset</span>
|
||||||
|
</Button>
|
||||||
|
<Dialog.Close class={buttonVariants({ variant: "default" })}>
|
||||||
|
<span>Save</span>
|
||||||
|
</Dialog.Close>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { buttonVariants } from "@/components/ui/button";
|
import { buttonVariants } from "@/components/ui/button";
|
||||||
import Shadcn from "@/components/logos/shadcn.svelte";
|
import Shadcn from "@/components/logos/shadcn.svelte";
|
||||||
import SelectPkgManager from "@/components/selectPkgManager.svelte";
|
|
||||||
|
|
||||||
import { pkgManager, type PackageManager } from "@/stores/pkgManager.store";
|
import { settingsStore, type PackageManager } from "@/stores/settings.store";
|
||||||
import CodeBlock from "@/components/codeBlock.svelte";
|
import CodeBlock from "@/components/codeBlock.svelte";
|
||||||
import ArrowUpRightIcon from "@lucide/svelte/icons/arrow-up-right";
|
import ArrowUpRightIcon from "@lucide/svelte/icons/arrow-up-right";
|
||||||
|
|
||||||
@@ -20,7 +19,7 @@
|
|||||||
bun: "bunx shadcn@latest add",
|
bun: "bunx shadcn@latest add",
|
||||||
};
|
};
|
||||||
|
|
||||||
let pkg = $derived($pkgManager);
|
let pkg = $derived($settingsStore.packageManager);
|
||||||
let shadcnCommand = $derived(shadcnCommands[pkg]);
|
let shadcnCommand = $derived(shadcnCommands[pkg]);
|
||||||
const svgFormatTitle = svgTitle
|
const svgFormatTitle = svgTitle
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@@ -40,6 +39,5 @@
|
|||||||
class="text-neutral-500 dark:text-neutral-400"
|
class="text-neutral-500 dark:text-neutral-400"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<SelectPkgManager />
|
|
||||||
</div>
|
</div>
|
||||||
<CodeBlock code={`${shadcnCommand} @svgl/${svgFormatTitle}`} Icon={Shadcn} />
|
<CodeBlock code={`${shadcnCommand} @svgl/${svgFormatTitle}`} Icon={Shadcn} />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { clipboard } from "@/utils/clipboard";
|
import { clipboard } from "@/utils/clipboard";
|
||||||
import { getPrefixFromSvgUrl, prefixSvgIds } from "@/utils/prefixSvgIds";
|
import { getPrefixFromSvgUrl, prefixSvgIds } from "@/utils/prefixSvgIds";
|
||||||
import { copyToClipboard as figmaCopyToClipboard } from "@/figma/copy-to-clipboard";
|
import { copyToClipboard as figmaCopyToClipboard } from "@/figma/copy-to-clipboard";
|
||||||
|
import { settingsStore } from "@/stores/settings.store";
|
||||||
|
|
||||||
// Icons:
|
// Icons:
|
||||||
import XIcon from "@lucide/svelte/icons/x";
|
import XIcon from "@lucide/svelte/icons/x";
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
// States:
|
// States:
|
||||||
let optionsOpen = $state<boolean>(false);
|
let optionsOpen = $state<boolean>(false);
|
||||||
let isLoading = $state<boolean>(false);
|
let isLoading = $state<boolean>(false);
|
||||||
|
let optimize = $derived($settingsStore.optimizeSvgs);
|
||||||
|
|
||||||
const getSvgUrl = () => {
|
const getSvgUrl = () => {
|
||||||
let svgUrlToCopy;
|
let svgUrlToCopy;
|
||||||
@@ -105,6 +107,7 @@
|
|||||||
|
|
||||||
let content = await getSource({
|
let content = await getSource({
|
||||||
url: svgUrlToCopy,
|
url: svgUrlToCopy,
|
||||||
|
optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (svgUrlToCopy) {
|
if (svgUrlToCopy) {
|
||||||
@@ -150,6 +153,7 @@
|
|||||||
const title = svgInfo.title.split(" ").join("");
|
const title = svgInfo.title.split(" ").join("");
|
||||||
let content = await getSource({
|
let content = await getSource({
|
||||||
url: svgUrlToCopy,
|
url: svgUrlToCopy,
|
||||||
|
optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (svgUrlToCopy) {
|
if (svgUrlToCopy) {
|
||||||
@@ -186,6 +190,7 @@
|
|||||||
|
|
||||||
let content = await getSource({
|
let content = await getSource({
|
||||||
url: svgUrlToCopy,
|
url: svgUrlToCopy,
|
||||||
|
optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (svgUrlToCopy) {
|
if (svgUrlToCopy) {
|
||||||
@@ -223,6 +228,7 @@
|
|||||||
|
|
||||||
let content = await getSource({
|
let content = await getSource({
|
||||||
url: svgUrlToCopy,
|
url: svgUrlToCopy,
|
||||||
|
optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (svgUrlToCopy) {
|
if (svgUrlToCopy) {
|
||||||
@@ -260,6 +266,7 @@
|
|||||||
const svgUrlToCopy = getSvgUrl();
|
const svgUrlToCopy = getSvgUrl();
|
||||||
let content = await getSource({
|
let content = await getSource({
|
||||||
url: svgUrlToCopy,
|
url: svgUrlToCopy,
|
||||||
|
optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (svgUrlToCopy) {
|
if (svgUrlToCopy) {
|
||||||
@@ -297,6 +304,7 @@
|
|||||||
const svgUrlToCopy = getSvgUrl();
|
const svgUrlToCopy = getSvgUrl();
|
||||||
let content = await getSource({
|
let content = await getSource({
|
||||||
url: svgUrlToCopy,
|
url: svgUrlToCopy,
|
||||||
|
optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (svgUrlToCopy) {
|
if (svgUrlToCopy) {
|
||||||
@@ -333,6 +341,7 @@
|
|||||||
const svgUrlToCopy = getSvgUrl();
|
const svgUrlToCopy = getSvgUrl();
|
||||||
let content = await getSource({
|
let content = await getSource({
|
||||||
url: svgUrlToCopy,
|
url: svgUrlToCopy,
|
||||||
|
optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (svgUrlToCopy) {
|
if (svgUrlToCopy) {
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { writable } from "svelte/store";
|
|
||||||
import { browser } from "$app/environment";
|
|
||||||
|
|
||||||
type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
|
|
||||||
|
|
||||||
const localStorageKey = "svgl_package_manager";
|
|
||||||
const defaultValue: PackageManager = "npm";
|
|
||||||
|
|
||||||
function getInitialValue(): PackageManager {
|
|
||||||
if (browser) {
|
|
||||||
const stored = localStorage.getItem(localStorageKey);
|
|
||||||
if (
|
|
||||||
stored === "npm" ||
|
|
||||||
stored === "pnpm" ||
|
|
||||||
stored === "yarn" ||
|
|
||||||
stored === "bun"
|
|
||||||
) {
|
|
||||||
return stored;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pkgManager = writable<PackageManager>(getInitialValue());
|
|
||||||
|
|
||||||
pkgManager.subscribe((value) => {
|
|
||||||
if (browser) {
|
|
||||||
localStorage.setItem(localStorageKey, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export { pkgManager, type PackageManager };
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
import { browser } from "$app/environment";
|
||||||
|
|
||||||
|
type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
|
||||||
|
|
||||||
|
interface Settings {
|
||||||
|
packageManager: PackageManager;
|
||||||
|
optimizeSvgs: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localStorageKey = "svgl_settings";
|
||||||
|
const defaultSettings: Settings = {
|
||||||
|
packageManager: "pnpm",
|
||||||
|
optimizeSvgs: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
function getInitialSettings(): Settings {
|
||||||
|
if (browser) {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(localStorageKey);
|
||||||
|
if (stored) {
|
||||||
|
const parsedSettings = JSON.parse(stored) as Partial<Settings>;
|
||||||
|
return {
|
||||||
|
packageManager: isValidPackageManager(parsedSettings.packageManager)
|
||||||
|
? parsedSettings.packageManager
|
||||||
|
: defaultSettings.packageManager,
|
||||||
|
optimizeSvgs:
|
||||||
|
typeof parsedSettings.optimizeSvgs === "boolean"
|
||||||
|
? parsedSettings.optimizeSvgs
|
||||||
|
: defaultSettings.optimizeSvgs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error parsing settings from localStorage:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidPackageManager(value: unknown): value is PackageManager {
|
||||||
|
return (
|
||||||
|
value === "npm" || value === "pnpm" || value === "yarn" || value === "bun"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSettingsStore() {
|
||||||
|
const { subscribe, set, update } = writable<Settings>(getInitialSettings());
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
|
||||||
|
// Update package manager
|
||||||
|
setPackageManager: (packageManager: PackageManager) => {
|
||||||
|
update((settings) => {
|
||||||
|
const newSettings = { ...settings, packageManager };
|
||||||
|
if (browser) {
|
||||||
|
localStorage.setItem(localStorageKey, JSON.stringify(newSettings));
|
||||||
|
}
|
||||||
|
return newSettings;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update optimize SVGs setting
|
||||||
|
setOptimizeSvgs: (optimizeSvgs: boolean) => {
|
||||||
|
update((settings) => {
|
||||||
|
const newSettings = { ...settings, optimizeSvgs };
|
||||||
|
if (browser) {
|
||||||
|
localStorage.setItem(localStorageKey, JSON.stringify(newSettings));
|
||||||
|
}
|
||||||
|
return newSettings;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update multiple settings at once
|
||||||
|
updateSettings: (newSettings: Partial<Settings>) => {
|
||||||
|
update((settings) => {
|
||||||
|
const updatedSettings = { ...settings, ...newSettings };
|
||||||
|
if (browser) {
|
||||||
|
localStorage.setItem(
|
||||||
|
localStorageKey,
|
||||||
|
JSON.stringify(updatedSettings),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return updatedSettings;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Reset to default settings
|
||||||
|
reset: () => {
|
||||||
|
set(defaultSettings);
|
||||||
|
if (browser) {
|
||||||
|
localStorage.setItem(localStorageKey, JSON.stringify(defaultSettings));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingsStore = createSettingsStore();
|
||||||
|
|
||||||
|
export { settingsStore, type PackageManager, type Settings };
|
||||||
@@ -2,11 +2,13 @@ import { optimizeSvg } from "@/utils/optimizeSvg";
|
|||||||
|
|
||||||
interface SourceParams {
|
interface SourceParams {
|
||||||
url: string | undefined;
|
url: string | undefined;
|
||||||
|
optimize?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSource = async (params: SourceParams) => {
|
export const getSource = async (params: SourceParams) => {
|
||||||
const response = await fetch(params.url || "");
|
const response = await fetch(params.url || "");
|
||||||
const content = await response.text();
|
const content = await response.text();
|
||||||
|
if (!params.optimize) return content;
|
||||||
const optimizedContent = optimizeSvg({ svgCode: content });
|
const optimizedContent = optimizeSvg({ svgCode: content });
|
||||||
return optimizedContent;
|
return optimizedContent;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user