Skip to content

Commit

Permalink
Feat/download des avatars (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
femi-Zedev authored Mar 16, 2024
1 parent b836463 commit d3d1231
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 43 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEXT_PUBLIC_BASE_URL=https://avatarlustr.netlify.app
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEXT_PUBLIC_BASE_URL=http://127.0.0.1:3000
7 changes: 5 additions & 2 deletions src/components/ApiSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ export default function ApiSection() {
return (
<section id='api' className="px-[4%] lg:px-[6%] 2xl:px-[10%] w-full py-14 text-center bg-primary-bold text-primary-script">
<h1 className='text-H1 text-white'>Comment utiliser l’API ?</h1>
<hgroup className='flex-y gap-8 mt-10'>

<h2 className="text-H2 text-teal-500 font-extrabold mt-4">L&lsquo;API est bientôt là ...</h2>

{/* <hgroup className='flex-y gap-8 mt-10'>
<p className="text-paragraph">
Grâce à cette API gratuite, les développeurs et designers
peuvent utiliser <br /> des avatars aléatoires en fonction de la race
Expand Down Expand Up @@ -79,7 +82,7 @@ export default function ApiSection() {
</div>
</div>
</div> */}



Expand Down
48 changes: 48 additions & 0 deletions src/components/SvgToPngConverter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { useState } from 'react';

interface SVGtoPNGConverterProps {
svgUrl: string;
}

const SVGtoPNGConverter: React.FC<SVGtoPNGConverterProps> = ({ svgUrl }) => {
const [pngDataUrl, setPngDataUrl] = useState<string | null>(null);

const convertSVGtoPNG = async () => {
try {
const response = await fetch(svgUrl);
const blob = await response.blob();
const img = new Image();

img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.drawImage(img, 0, 0);
const dataURL = canvas.toDataURL('image/png',1);
setPngDataUrl(dataURL);
}
};

img.src = URL.createObjectURL(blob);
} catch (error) {
console.error('Error converting SVG to PNG:', error);
}
};

return (
<div>
<img src={svgUrl} alt="SVG" />
<button onClick={convertSVGtoPNG}>Convert to PNG</button>
{pngDataUrl && (
<div>
<h2>Converted PNG:</h2>
<img src={pngDataUrl} alt="Converted PNG" />
</div>
)}
</div>
);
};

export default SVGtoPNGConverter;
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { AvatarProps } from '@/interfaces/avatar';
import { Avatar, CopyButton, Menu, Tooltip } from '@mantine/core'
import { Avatar, Menu } from '@mantine/core'
import React, { useState } from 'react'
import { LuCopy, LuDownload } from "react-icons/lu";
import { FaInstagram } from "react-icons/fa6";
import { IoLogoLinkedin } from "react-icons/io5";
import { notifications } from '@mantine/notifications';
import CopyMenu from './avatar-menu/CopyMenu';
import DownloadMenu from './avatar-menu/DownloadMenu';

function AuthorMenu({ }) {
return (
Expand All @@ -26,37 +27,20 @@ function AuthorMenu({ }) {
</hgroup>
)
}
export default function AvatarCard({ imgUrl, author, link }: AvatarProps) {
export default function AvatarCard({ imgUrl, name, author, link }: AvatarProps) {

const [show, setShow] = useState(false)

function handleCopy(copy: Function) {
copy();
notifications.show({
id: 'ds',
title: 'Lien copié',
color: 'teal',
message: "Url copié vous pouvez le coller maintenant",
})
}

return (
<div
onMouseEnter={() => setShow(true)}
onMouseLeave={() => setShow(false)}
className='relative cursor-pointer rounded-2xl lg:rounded-3xl px-4 py-2 bg-primary-medium flex-y_center w-full md:w-fit lg:!w-60 '>
<span className="hidden lg:flex w-full relative h-6">
<CopyButton value={link} key={2000} >
{({ copied, copy }) => (
<button className={`${show ? 'opacity-100' : 'opacity-0'} absolute btn-icon right-0 top-4`} onClick={() => handleCopy(copy)}>
<LuCopy size="1.2rem" />
</button>
)}
</CopyButton>



<CopyMenu imgUrl={imgUrl} show={show} />
</span>

<img className='w-28 md:w-36 mt-4 mb-8 lg:mt-0' src={imgUrl} />
<span className="hidden absolute bottom-3 px-4 lg:flex justify-between items-center w-full mt-2">
<button className={`${show ? 'opacity-100' : 'opacity-0'} lg:flex items-center gap-2 transition-opacity cursor-pointer`} >
Expand All @@ -70,9 +54,9 @@ export default function AvatarCard({ imgUrl, author, link }: AvatarProps) {
</Menu>
<p className='font-medium text-sm'>{author}</p>
</button>
<button className={`${show ? 'opacity-100' : 'opacity-0'} btn-icon`} >
<LuDownload size="1.2rem" />
</button>
<span>
<DownloadMenu name={name} imgUrl={imgUrl} show={show} />
</span>
</span>
<hgroup className='w-full flex justify-between lg:hidden' >
<button className='btn-icon'>
Expand Down
101 changes: 101 additions & 0 deletions src/components/UI/avatar-card/avatar-menu/CopyMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react'
import { Menu } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
import { LuCopy } from "react-icons/lu";
import { BsCardImage, BsFiletypeSvg } from "react-icons/bs";
import { RxLink1 } from "react-icons/rx";
import { convertSVGtoPNG } from '@/helpers/imageConverter';
import { notifications } from '@mantine/notifications';



export default function CopyMenu({show, imgUrl}:{show:boolean, imgUrl: string}) {
const [ opened, handlers] = useDisclosure(false);

function handleCopy(imgUrl: string) {
navigator.clipboard.writeText(process.env.NEXT_PUBLIC_BASE_URL+'/'+imgUrl);
notifications.show({
id: 'link-copy',
title: 'Lien copié',
color: 'teal',
message: "Url copié vous pouvez le coller maintenant",
})
}

function handleCopyAsImage(type: 'svg' | 'png', link: string) {
switch (type) {
case 'svg':
copyAsSvg(link).then(() => {
notifications.show({
id: 'svg-copy',
title: 'Illustration copiée',
color: 'teal',
message: "L'illustration à été copiée vous pouvez la coller maintenant",
})
})

break;

case 'png':
copyAsPng(link).then(()=>{
notifications.show({
id: 'svg-copy',
title: 'Illustration copiée',
color: 'teal',
message: "L'illustration à été copiée vous pouvez la coller maintenant",
})
})

default:
break;
}
}

async function copyAsSvg(imgUrl: string) {
try {
const response = await fetch(imgUrl);
const svgContent = await response.text();
navigator.clipboard.writeText(svgContent);
console.log(svgContent)
} catch (error) {
console.error('Error:', error);
}
}

async function copyAsPng(imgUrl: string){
try {
const blob = await convertSVGtoPNG(imgUrl);
console.log('Converted PNG:', blob);
await navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob
})
]);
} catch (error) {
console.error('Error:', error);
}
}

return (
<Menu opened={opened} onChange={handlers.toggle} shadow="md" radius="md" width={200}>
<Menu.Target>
<button onClick={handlers.open} className={`absolute btn-icon right-0 top-4 ${opened && 'bg-primary-lighter '} ${(show || opened) ? 'opacity-100' : 'opacity-0'} `}>
<LuCopy size="1.2rem" />
</button>
</Menu.Target>

<Menu.Dropdown className='bg-primary-base' >
<Menu.Label>Copier</Menu.Label>
<Menu.Item leftSection={<BsCardImage />} className='flex items-center hover:bg-primary-light rounded' onClick={() => handleCopyAsImage('png', `./${imgUrl}`)} >
en tant que PNG
</Menu.Item>
<Menu.Item leftSection={<BsFiletypeSvg />} className='flex items-center hover:bg-primary-light rounded' onClick={() => handleCopyAsImage('svg', `./${imgUrl}`)} >
en tant que SVG
</Menu.Item>
<Menu.Item leftSection={<RxLink1 />} className='flex items-center hover:bg-primary-light rounded' onClick={() => handleCopy(imgUrl)}>
l&lsquo;url de l&lsquo;illustration
</Menu.Item>
</Menu.Dropdown>
</Menu>
)
}
59 changes: 59 additions & 0 deletions src/components/UI/avatar-card/avatar-menu/DownloadMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react'
import { Menu } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks';
import { LuDownload } from 'react-icons/lu';
import { BsCardImage, BsFiletypeSvg } from 'react-icons/bs';
import { downloadPNG } from '@/helpers/downloadPng';

export default function DownloadMenu({show, name, imgUrl}:{show:boolean, name: string, imgUrl: string}) {
const [opened, handlers] = useDisclosure(false);

function handleDownloadPng(link: string, name: string) {
downloadPNG(link, name+'.png')
}

async function handleDownloadSvg(svgUrl: string, fileName: string){
try {
const response = await fetch(svgUrl, {
headers: {
'Content-Type': 'image/svg+xml'
}
});
const blob = await response.blob();

const url = URL.createObjectURL(blob);

const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

URL.revokeObjectURL(url);

} catch (error) {
console.error('Error downloading SVG:', error);
}
}

return (
<Menu opened={opened} onChange={handlers.toggle} shadow="md" radius="md" width={200}>
<Menu.Target>
<button onClick={handlers.open} className={`btn-icon ${opened && 'bg-primary-lighter'} ${(show || opened) ? 'opacity-100' : 'opacity-0'} `}>
<LuDownload size="1.2rem" />
</button>
</Menu.Target>

<Menu.Dropdown className='bg-primary-base' >
<Menu.Label>Télécharger</Menu.Label>
<Menu.Item leftSection={<BsCardImage />} className='flex items-center hover:bg-primary-light rounded' onClick={() => handleDownloadPng(imgUrl, name)} >
en PNG
</Menu.Item>
<Menu.Item leftSection={<BsFiletypeSvg />} className='flex items-center hover:bg-primary-light rounded' onClick={() => handleDownloadSvg(imgUrl, name)} >
en SVG
</Menu.Item>
</Menu.Dropdown>
</Menu>
)
}
26 changes: 15 additions & 11 deletions src/data/avatars.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { AvatarProps } from "@/interfaces/avatar";

// fetch baseUrl

const baseUrl = process.env.NEXT_PUBLIC_BASE_URL

export const avatarsArray: AvatarProps[] = [
{ imgUrl: 'avatars/2.svg', name: 'afro-F', link: '/2', sexe: 'femme', race: 'afro', author: 'Gilka' },
{ imgUrl: 'avatars/3.svg', name: 'amerindien-F', link: '/3', sexe: 'femme', race: 'amerindien', author: 'Gilka' },
{ imgUrl: 'avatars/9.svg', name: 'oceanien-F', link: '/9', sexe: 'femme', race: 'oceanien', author: 'Gilka' },
{ imgUrl: 'avatars/7.svg', name: 'caucasien-F', link: '/7', sexe: 'femme', race: 'caucasien', author: 'Gilka' },
{ imgUrl: 'avatars/5.svg', name: 'asiat-F', link: '/5', sexe: 'femme', race: 'asiat', author: 'Gilka' },

{ imgUrl: 'avatars/1.svg', name: 'afro-H', link: '', sexe: 'homme', race: 'afro', author: 'Gilka' },
{ imgUrl: 'avatars/4.svg', name: 'amerindien-H', link: '', sexe: 'homme', race: 'amerindien', author: 'Gilka' },
{ imgUrl: 'avatars/10.svg', name: 'oceanien-H', link: '', sexe: 'homme', race: 'oceanien', author: 'Gilka' },
{ imgUrl: 'avatars/8.svg', name: 'caucasien-H', link: '', sexe: 'homme', race: 'caucasien', author: 'Gilka' },
{ imgUrl: 'avatars/6.svg', name: 'asiat-H', link: '', sexe: 'homme', race: 'asiat', author: 'Gilka' },
{ imgUrl: 'avatars/2.svg', name: 'afro-F', link: `/avatars/2.svg`, sexe: 'femme', race: 'afro', author: 'Gilka' },
{ imgUrl: 'avatars/3.svg', name: 'amerindien-F', link: `/avatars/3.svg`, sexe: 'femme', race: 'amerindien', author: 'Gilka' },
{ imgUrl: 'avatars/9.svg', name: 'oceanien-F', link: `/avatars/9.svg`, sexe: 'femme', race: 'oceanien', author: 'Gilka' },
{ imgUrl: 'avatars/7.svg', name: 'caucasien-F', link: `/avatars/7.svg`, sexe: 'femme', race: 'caucasien', author: 'Gilka' },
{ imgUrl: 'avatars/5.svg', name: 'asiat-F', link: `/avatars/5.svg`, sexe: 'femme', race: 'asiat', author: 'Gilka' },

{ imgUrl: 'avatars/1.svg', name: 'afro-H', link: `/avatars/1.svg`, sexe: 'homme', race: 'afro', author: 'Gilka' },
{ imgUrl: 'avatars/4.svg', name: 'amerindien-H', link: `/avatars/4.svg`, sexe: 'homme', race: 'amerindien', author: 'Gilka' },
{ imgUrl: 'avatars/10.svg', name: 'oceanien-H', link: `/avatars/10.svg`, sexe: 'homme', race: 'oceanien', author: 'Gilka' },
{ imgUrl: 'avatars/8.svg', name: 'caucasien-H', link: `/avatars/8.svg`, sexe: 'homme', race: 'caucasien', author: 'Gilka' },
{ imgUrl: 'avatars/6.svg', name: 'asiat-H', link: `/avatars/6.svg`, sexe: 'homme', race: 'asiat', author: 'Gilka' },
]
17 changes: 17 additions & 0 deletions src/helpers/downloadPng.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { convertSVGtoPNG } from "./imageConverter";

export const downloadPNG = async (svgUrl: string, fileName: string) => {
try {
const pngBlob = await convertSVGtoPNG(svgUrl);
const url = URL.createObjectURL(pngBlob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
} catch (error) {
console.error('Error downloading PNG:', error);
}
};
Loading

0 comments on commit d3d1231

Please sign in to comment.