Merge branch 'main' into pnpm-svgs

This commit is contained in:
Pablo Hdez
2024-01-01 13:06:51 +00:00
committed by GitHub
153 changed files with 1117 additions and 233 deletions
+2 -2
View File
@@ -24,8 +24,8 @@
external: false
},
{
name: 'Terminal',
url: 'https://github.com/pheralb/svgl?tab=readme-ov-file#-terminal',
name: 'Extensions',
url: 'https://github.com/pheralb/svgl?tab=readme-ov-file#-extensions',
icon: ArrowUpRight,
external: true
},
+67 -8
View File
@@ -10,14 +10,26 @@
import { flyAndScale } from '@/utils/flyAndScale';
// Icons:
import { CopyIcon, DownloadIcon, LinkIcon, PackageIcon, PaintBucket } from 'lucide-svelte';
import { CopyIcon, DownloadIcon, LinkIcon, PackageIcon, PaintBucket, ChevronsRight } from 'lucide-svelte';
// Main Card:
import CardSpotlight from './cardSpotlight.svelte';
import { DropdownMenu } from 'bits-ui';
// Figma
import { onMount } from "svelte";
import { copyToClipboard as figmaCopyToClipboard } from '@/figma/copy-to-clipboard';
import { insertSVG as figmaInsertSVG } from '@/figma/insert-svg';
// Props:
export let svgInfo: iSVG;
let isInFigma = false
onMount(() => {
const searchParams = new URLSearchParams(window.location.search);
isInFigma = searchParams.get('figma') === '1';
});
// Download SVG:
const downloadSvg = (url?: string) => {
@@ -56,18 +68,30 @@
const data = {
[MIMETYPE]: getSvgContent(url, true)
};
try {
const clipboardItem = new ClipboardItem(data);
await navigator.clipboard.write([clipboardItem]);
} catch (error) {
if(isInFigma) {
const content = (await getSvgContent(url, false)) as string;
await navigator.clipboard.writeText(content);
figmaCopyToClipboard(content);
} else {
try {
const clipboardItem = new ClipboardItem(data);
await navigator.clipboard.write([clipboardItem]);
} catch (error) {
const content = (await getSvgContent(url, false)) as string;
await navigator.clipboard.writeText(content);
}
}
toast.success('Copied to clipboard!', {
description: `${svgInfo.title} - ${svgInfo.category}`
});
};
const insertSVG = async (url?: string) => {
const content = (await getSvgContent(url, false)) as string;
figmaInsertSVG(content);
}
// Icon Stroke & Size:
let iconStroke = 1.8;
let iconSize = 16;
@@ -102,6 +126,38 @@
</div>
<!-- Actions -->
<div class="flex items-center space-x-1">
{#if isInFigma}
<button
title="Insert to figma"
on:click={() => {
const svgHasTheme = typeof svgInfo.route !== 'string';
if (!svgHasTheme) {
insertSVG(
typeof svgInfo.route === 'string'
? svgInfo.route
: "Something went wrong. Couldn't copy the SVG."
);
return;
}
const dark = document.documentElement.classList.contains('dark');
insertSVG(
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"
>
<ChevronsRight size={iconSize} strokeWidth={iconStroke} />
</button>
{/if}
<button
title="Copy to clipboard"
on:click={() => {
@@ -134,6 +190,7 @@
{#if typeof svgInfo.route !== 'string'}
<DropdownMenu.Root>
<DropdownMenu.Trigger
title="Download SVG"
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} />
@@ -144,7 +201,8 @@
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"
title="Download Light & Dark variants"
class="flex h-10 select-none items-center rounded-md py-3 pl-3 pr-1.5 text-sm font-medium cursor-pointer hover:bg-neutral-100 dark:hover:bg-neutral-700/40"
on:click={() => {
downloadAllVariants(svgInfo);
}}
@@ -153,7 +211,8 @@
<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"
title="Download only {document.documentElement.classList.contains('dark') ? 'dark' : 'light'} variant"
class="flex h-10 select-none items-center rounded-md py-3 pl-3 pr-1.5 text-sm font-medium cursor-pointer hover:bg-neutral-100 dark:hover:bg-neutral-700/40"
on:click={() => {
const svgHasTheme = typeof svgInfo.route !== 'string';
+121 -10
View File
@@ -47,10 +47,10 @@ export const svgs: iSVG[] = [
url: 'https://vuejs.org/'
},
{
title: "Vuetify",
category: "Library",
route: "/library/vuetify.svg",
url: "https://vuetifyjs.com/"
title: 'Vuetify',
category: 'Library',
route: '/library/vuetify.svg',
url: 'https://vuetifyjs.com/'
},
{
title: 'Nuxt',
@@ -70,6 +70,24 @@ export const svgs: iSVG[] = [
route: '/library/vscode.svg',
url: 'https://code.visualstudio.com/'
},
{
title: 'Ton',
category: 'Crypto',
route: '/library/ton.svg',
url: 'https://ton.org/'
},
{
title: 'Runway',
category: 'AI',
route: '/library/runway.svg',
url: 'https://runwayml.com/'
},
{
title: 'Sentry',
category: 'Software',
route: '/library/sentry.svg',
url: 'https://sentry.io/'
},
{
title: 'JWT',
category: 'Library',
@@ -94,12 +112,27 @@ export const svgs: iSVG[] = [
route: '/library/spotify.svg',
url: 'https://www.spotify.com/'
},
{
title: 'WorkOS',
category: 'Software',
route: {
light: '/library/workos.svg',
dark: '/library/workos-light.svg'
},
url: 'https://workos.com/'
},
{
title: 'Postman',
category: 'Software',
route: '/library/postman.svg',
url: 'https://www.getpostman.com/'
},
{
title: 'OpenSea',
category: 'Crypto',
route: '/library/opensea.svg',
url: 'https://opensea.io/'
},
{
title: 'Algolia',
category: 'Library',
@@ -1090,6 +1123,12 @@ export const svgs: iSVG[] = [
route: '/library/redux.svg',
url: 'https://redux.js.org/'
},
{
title: 'Trust Wallet',
category: 'Crypto',
route: '/library/trust.svg',
url: 'https://trustwallet.com/'
},
{
title: 'Php',
category: 'Language',
@@ -1889,7 +1928,7 @@ export const svgs: iSVG[] = [
url: 'https://monkeytype.com/'
},
{
title: 'Raycast',
title: 'PyCharm',
category: 'Software',
route: '/library/pycharm.svg',
url: 'https://www.jetbrains.com/pycharm/'
@@ -2045,10 +2084,10 @@ export const svgs: iSVG[] = [
url: 'https://spring.io/'
},
{
"title": "Directus",
"category": "CMS",
"route": "/library/directus.svg",
"url": "https://directus.io/"
title: 'Directus',
category: 'CMS',
route: '/library/directus.svg',
url: 'https://directus.io/'
},
{
title: 'Pnpm',
@@ -2067,5 +2106,77 @@ export const svgs: iSVG[] = [
dark: '/library/pnpm_no_text_dark.svg'
},
url: 'https://pnpm.io/'
},
{
title: 'Emacs',
category: 'Software',
route: '/library/emacs.svg',
url: 'https://www.gnu.org/software/emacs/'
},
{
title: 'Svgl',
category: 'Library',
route: '/library/svgl.svg',
url: 'https://svgl.app'
},
{
title: 'Google Idx',
category: 'Software',
route: '/library/google-idx.svg',
url: 'https://idx.dev/'
},
{
title: 'Bluesky',
category: 'Social',
route: '/library/bluesky.svg',
url: 'https://blueskyweb.xyz/'
},
{
title: 'Remix',
category: 'Framework',
route: {
light: '/library/remix_light.svg',
dark: '/library/remix_dark.svg'
},
url: 'https://remix.run/'
},
{
title: 'Steam',
category: 'Software',
route: '/library/steam.svg',
url: 'https://store.steampowered.com/'
},
{
title: 'Tabby',
category: 'Software',
route: '/library/tabby.svg',
url: 'https://tabby.sh/'
},
{
title: '1Password',
category: 'Software',
route: {
light: '/library/1password-light.svg',
dark: '/library/1password-dark.svg'
},
url: 'https://1password.com'
},
{
title: 'Alacritty',
category: 'Software',
route: '/library/alacritty.svg',
url: 'https://alacritty.org'
},
{
title: 'Qt',
category: 'Software',
route: '/library/qt.svg',
url: 'https://www.qt.io/'
},
{
title: 'PNPM',
category: 'Software',
route: '/library/pnpm.svg',
url: 'https://pnpm.io/'
}
]
];
+37
View File
@@ -0,0 +1,37 @@
declare const SITE_URL: string
figma.showUI(`<script>window.location.href = '${SITE_URL}'</script>`, {
width: 400,
height: 700,
})
figma.ui.onmessage = async (message, props) => {
if (!SITE_URL.includes(props.origin)) {
return
}
switch (message.type) {
case 'EVAL': {
const fn = eval.call(null, message.code)
try {
const result = await fn(figma, message.params)
figma.ui.postMessage({
type: 'EVAL_RESULT',
result,
id: message.id,
})
} catch (e) {
figma.ui.postMessage({
type: 'EVAL_REJECT',
error: typeof e === 'string' ? e : e && typeof e === 'object' && 'message' in e ? e.message : null,
id: message.id,
})
}
break
}
}
}
+26
View File
@@ -0,0 +1,26 @@
export function copyToClipboard(value: string) {
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (window.copy) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.copy(value);
} else {
const area = document.createElement('textarea');
document.body.appendChild(area);
area.value = value;
// area.focus();
area.select();
const result = document.execCommand('copy');
document.body.removeChild(area);
if (!result) {
throw new Error();
}
}
} catch (e) {
console.error(`Unable to copy the value: ${value}`);
return false;
}
return true;
}
+80
View File
@@ -0,0 +1,80 @@
/**
* This is a magic file that allows us to run code in the Figma plugin context
* from the iframe. It does this by getting the code as a string, and sending it
* to the plugin via postMessage. The plugin then evals the code and sends the
* result back to the iframe. There are a few caveats:
* 1. The code cannot reference any variables outside of the function. This is
* because the code is stringified and sent to the plugin, and the plugin
* evals it. The plugin has no access to the variables in the iframe.
* 2. The return value of the function must be JSON serializable. This is
* because the result is sent back to the iframe via postMessage, which only
* supports JSON.
*
* You can get around these limitations by passing in the variables you need
* as parameters to the function.
*
* @example
* ```ts
* const result = await figmaAPI.run((figma, {nodeId}) => {
* return figma.getNodeById(nodeId)?.name;
* }, {nodeId: "0:2"});
*
* console.log(result); // "Page 1"
* ```
*/
class FigmaAPI {
private id = 0
/**
* Run a function in the Figma plugin context. The function cannot reference
* any variables outside of itself, and the return value must be JSON
* serializable. If you need to pass in variables, you can do so by passing
* them as the second parameter.
*/
run<T, U>(fn: (figma: PluginAPI, params: U) => Promise<T> | T, params?: U): Promise<T> {
return new Promise((resolve, reject) => {
const id = this.id++
const cb = (event: MessageEvent) => {
if (event.origin !== 'https://www.figma.com' && event.origin !== 'https://staging.figma.com') {
return
}
if (event.data.pluginMessage?.type === 'EVAL_RESULT') {
if (event.data.pluginMessage.id === id) {
window.removeEventListener('message', cb)
resolve(event.data.pluginMessage.result)
}
}
if (event.data.pluginMessage?.type === 'EVAL_REJECT') {
if (event.data.pluginMessage.id === id) {
window.removeEventListener('message', cb)
const message = event.data.pluginMessage.error
reject(new Error(typeof message === 'string' ? message : 'An error occurred in FigmaAPI.run()'))
}
}
}
window.addEventListener('message', cb)
const msg = {
pluginMessage: {
type: 'EVAL',
code: fn.toString(),
id,
params,
},
pluginId: '*',
}
;['https://www.figma.com', 'https://staging.figma.com'].forEach((origin) => {
try {
parent.postMessage(msg, origin)
} catch (e) {
console.error(e)
}
})
})
}
}
export const figmaAPI = new FigmaAPI()
+22
View File
@@ -0,0 +1,22 @@
import { figmaAPI } from './figma-api'
export async function insertSVG(svgString: string) {
if (!svgString) return
figmaAPI.run(
async (figma, { svgString }: { svgString: string }) => {
const node = figma.createNodeFromSvg(svgString)
const selectedNode = figma.currentPage.selection[0]
if (selectedNode) {
node.x = selectedNode.x + selectedNode.width + 20
node.y = selectedNode.y
}
figma.currentPage.appendChild(node)
figma.currentPage.selection = [node]
figma.viewport.scrollAndZoomIntoView([node])
},
{ svgString },
)
}
+13
View File
@@ -0,0 +1,13 @@
{
"name": "SVGL",
"id": "1320306989350693206",
"api": "1.0.0",
"main": "dist/code.js",
"enableProposedApi": false,
"editorType": ["figma", "figjam"],
"permissions": ["currentuser"],
"networkAccess": {
"allowedDomains": ["*"],
"reasoning": "Internet access for local development."
}
}