mirror of
https://github.com/pheralb/svgl.git
synced 2025-12-29 08:01:36 +08:00
🧡 Initial commit with Sveltekit.
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
import { ErrorProps } from "@/interfaces/components";
|
||||
import {
|
||||
Button,
|
||||
Center,
|
||||
Heading,
|
||||
HStack,
|
||||
Text,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import { ArrowClockwise, ArrowSquareOut, Warning } from "phosphor-react";
|
||||
import { useRouter } from "next/router";
|
||||
import CustomLink from "@/common/link";
|
||||
|
||||
const Error = (props: ErrorProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const handleRefresh = () => {
|
||||
router.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<Center>
|
||||
<VStack>
|
||||
<Warning size={90} />
|
||||
<Heading fontSize="3xl">{props.title}</Heading>
|
||||
<Text>{props.description}</Text>
|
||||
<HStack>
|
||||
<Button
|
||||
variant="ghost"
|
||||
borderWidth="1px"
|
||||
leftIcon={<ArrowClockwise />}
|
||||
onClick={handleRefresh}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
<CustomLink href="https://github.com/pheralb/svgl/issues/new" external={true}>
|
||||
<Button variant="ghost" rightIcon={<ArrowSquareOut />}>
|
||||
Create issue
|
||||
</Button>
|
||||
</CustomLink>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</Center>
|
||||
);
|
||||
};
|
||||
|
||||
export default Error;
|
||||
@@ -1,16 +0,0 @@
|
||||
import { LoadingProps } from "@/interfaces/components";
|
||||
import { Center, Spinner, Text, VStack } from "@chakra-ui/react";
|
||||
import { LeapFrog } from "@uiball/loaders";
|
||||
|
||||
const Loading = (props: LoadingProps) => {
|
||||
return (
|
||||
<Center>
|
||||
<VStack spacing={3} mt="3">
|
||||
<LeapFrog size={32} speed={2.5} color="#4343E5" />
|
||||
<Text>{props.text}</Text>
|
||||
</VStack>
|
||||
</Center>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loading;
|
||||
@@ -1,125 +0,0 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Input,
|
||||
Text,
|
||||
Image,
|
||||
HStack,
|
||||
Box,
|
||||
Center,
|
||||
Spinner,
|
||||
} from "@chakra-ui/react";
|
||||
import useDebounce from "@/hooks/useDebounce";
|
||||
import { SearchProps, SVGCardProps } from "@/interfaces/components";
|
||||
import CustomLink from "@/common/link";
|
||||
import { getSvgByQuery } from "@/services";
|
||||
import CustomIconBtn from "@/common/iconBtn";
|
||||
import { Trash } from "phosphor-react";
|
||||
import Tap from "@/animations/tap";
|
||||
|
||||
const Search = ({ availableFocus = false }: SearchProps) => {
|
||||
const [search, setSearch] = useState("");
|
||||
const [empty, setEmpty] = useState(false);
|
||||
const [results, setResults] = useState<SVGCardProps[]>([]);
|
||||
const debouncedSearch = useDebounce(search, 500);
|
||||
const searchRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedSearch) {
|
||||
fetch(getSvgByQuery + debouncedSearch).then((res) => {
|
||||
if (res.ok) {
|
||||
res.json().then((data) => {
|
||||
setEmpty(data.length === 0);
|
||||
setResults(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [debouncedSearch]);
|
||||
|
||||
useEffect(() => {
|
||||
const isFocusAvailable = availableFocus && searchRef.current;
|
||||
|
||||
if (!isFocusAvailable) return;
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
searchRef.current?.focus();
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [availableFocus]);
|
||||
|
||||
const handleFilter = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setEmpty(false);
|
||||
setSearch(e.target.value);
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setSearch("");
|
||||
setResults([]);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
width="full"
|
||||
variant="flushed"
|
||||
size="lg"
|
||||
placeholder="Search svgs..."
|
||||
value={search}
|
||||
onChange={handleFilter}
|
||||
ref={searchRef}
|
||||
/>
|
||||
{search && !empty && results.length === 0 && (
|
||||
<Box pt="4">
|
||||
<Spinner />
|
||||
</Box>
|
||||
)}
|
||||
{search && empty && <Box pt="3">No results found!</Box>}
|
||||
{results && results.length > 0 && (
|
||||
<>
|
||||
<HStack
|
||||
spacing={4}
|
||||
mt={4}
|
||||
overflowX="auto"
|
||||
overflowY="hidden"
|
||||
alignItems="start"
|
||||
>
|
||||
{results.map((item: SVGCardProps) => (
|
||||
<Tap key={item.title}>
|
||||
<CustomLink href={`/svg/${item.id}`}>
|
||||
<Box
|
||||
mb="2"
|
||||
p="3"
|
||||
shadow="sm"
|
||||
borderWidth="1px"
|
||||
borderRadius="5px"
|
||||
width="100%"
|
||||
>
|
||||
<Center>
|
||||
<Image
|
||||
width="25px"
|
||||
mb="2"
|
||||
src={item.slug}
|
||||
alt={item.title}
|
||||
/>
|
||||
</Center>
|
||||
<Text>{item.title}</Text>
|
||||
</Box>
|
||||
</CustomLink>
|
||||
</Tap>
|
||||
))}
|
||||
</HStack>
|
||||
<Box p="3">
|
||||
<CustomIconBtn
|
||||
title="clear"
|
||||
icon={<Trash size={16} />}
|
||||
onClick={handleClear}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Search;
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from "react";
|
||||
import { SVGCardProps } from "@/interfaces/components";
|
||||
import {
|
||||
Box,
|
||||
Center,
|
||||
Image,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import Tap from "@/animations/tap";
|
||||
import CustomLink from "@/common/link";
|
||||
import { Smiley } from "phosphor-react";
|
||||
|
||||
const SVGCard = (props: SVGCardProps) => {
|
||||
const bg = useColorModeValue("bg.light", "bg.dark");
|
||||
const color = useColorModeValue("rgb(0,0,0, .1)", "rgb(255,255,255, .1)");
|
||||
return (
|
||||
<>
|
||||
<Tap>
|
||||
<CustomLink href={`/svg/${props.id}`}>
|
||||
<Box
|
||||
bg={bg}
|
||||
p={4}
|
||||
cursor="pointer"
|
||||
borderRadius="10px"
|
||||
borderWidth="1px"
|
||||
mb="2"
|
||||
_hover={{
|
||||
border:`1px solid ${color}`,
|
||||
transform: "scale(1.03)",
|
||||
}}
|
||||
transition="all 0.2s" >
|
||||
<Center>
|
||||
<Image height="40px" src={props.svg} alt={props.title} />
|
||||
</Center>
|
||||
<Text mt="3" fontWeight="light" textAlign="center">
|
||||
{props.title}
|
||||
</Text>
|
||||
</Box>
|
||||
</CustomLink>
|
||||
</Tap>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SVGCard;
|
||||
@@ -1,108 +0,0 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Heading,
|
||||
HStack,
|
||||
Icon,
|
||||
Image,
|
||||
Link,
|
||||
} from "@chakra-ui/react";
|
||||
import { ArrowSquareOut, Copy, DownloadSimple } from "phosphor-react";
|
||||
import confetti from "canvas-confetti";
|
||||
import download from "downloadjs";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { ToastTheme } from "@/theme/toast";
|
||||
import { SVGCardProps } from "@/interfaces/components";
|
||||
|
||||
// Download SVG =>
|
||||
const downloadSvg = (url?: string) => {
|
||||
confetti({
|
||||
particleCount: 200,
|
||||
startVelocity: 30,
|
||||
spread: 300,
|
||||
gravity: 1.2,
|
||||
origin: { y: 0 },
|
||||
});
|
||||
download(url || "");
|
||||
};
|
||||
|
||||
|
||||
const MIMETYPE = 'text/plain';
|
||||
|
||||
// Return content of svg as blob =>
|
||||
const getSvgContent = async (url: string | undefined, isSupported: boolean) => {
|
||||
const response = await fetch(url || "");
|
||||
const content = await response.text();
|
||||
|
||||
// It was necessary to use blob because in chrome there were issues with the copy to clipboard
|
||||
const blob = new Blob([content], { type: MIMETYPE });
|
||||
return isSupported ? blob : content;
|
||||
}
|
||||
|
||||
// Copy to clipboard =>
|
||||
const copyToClipboard = async (url?: string) => {
|
||||
const data = {
|
||||
[MIMETYPE]: getSvgContent(url, true)
|
||||
};
|
||||
try {
|
||||
const clipboardItem = new ClipboardItem(data);
|
||||
await navigator.clipboard.write([clipboardItem]);
|
||||
} catch (error) {
|
||||
// This section works as a fallback on Firefox
|
||||
const content = await getSvgContent(url, false) as string;
|
||||
await navigator.clipboard.writeText(content);
|
||||
}
|
||||
toast("Copied to clipboard", ToastTheme);
|
||||
};
|
||||
|
||||
const SVGInfo = (props: SVGCardProps) => {
|
||||
return (
|
||||
<Flex
|
||||
pt="7"
|
||||
pb="7"
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
borderWidth="1px"
|
||||
borderRadius="10px"
|
||||
>
|
||||
<Image
|
||||
src={props.slug}
|
||||
alt={props.title}
|
||||
fit="cover"
|
||||
loading="lazy"
|
||||
width="85px"
|
||||
/>
|
||||
<Heading mt={6} mb={6} fontSize="4xl">
|
||||
{props.title}
|
||||
</Heading>
|
||||
<Flex direction={{ base: "column", md: "row" }}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
borderWidth="1px"
|
||||
leftIcon={<Copy />}
|
||||
onClick={() => copyToClipboard(props.slug)}
|
||||
mb={{ base: "2", md: "0" }}
|
||||
mr={{ base: "0", md: "3" }}
|
||||
>
|
||||
Copy to clipboard
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<DownloadSimple />}
|
||||
variant="primary"
|
||||
onClick={() => downloadSvg(props.slug)}
|
||||
mb={{ base: "2", md: "0" }}
|
||||
mr={{ base: "0", md: "3" }}
|
||||
>
|
||||
Download .svg
|
||||
</Button>
|
||||
<Link href={props.url} isExternal={true}>
|
||||
{props.title} website <Icon as={ArrowSquareOut} mt="2" />
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default SVGInfo;
|
||||
Reference in New Issue
Block a user