diff options
Diffstat (limited to 'src/components/preview')
| -rw-r--r-- | src/components/preview/__snapshots__/badge.test.tsx.snap | 21 | ||||
| -rw-r--r-- | src/components/preview/__snapshots__/card.test.tsx.snap | 165 | ||||
| -rw-r--r-- | src/components/preview/badge.test.tsx | 22 | ||||
| -rw-r--r-- | src/components/preview/badge.tsx | 57 | ||||
| -rw-r--r-- | src/components/preview/card.test.tsx | 163 | ||||
| -rw-r--r-- | src/components/preview/card.tsx | 198 | ||||
| -rw-r--r-- | src/components/preview/preview.tsx | 218 |
7 files changed, 844 insertions, 0 deletions
diff --git a/src/components/preview/__snapshots__/badge.test.tsx.snap b/src/components/preview/__snapshots__/badge.test.tsx.snap new file mode 100644 index 0000000..9000a91 --- /dev/null +++ b/src/components/preview/__snapshots__/badge.test.tsx.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Badge renders 1`] = ` +<div + class="badge-wrapper" + style="height: 25px; background-color: rgb(85, 85, 85); display: flex; margin: 0px 7px;" +> + <p + class="badge-label" + style="color: rgb(255, 255, 255); font-family: Jost; font-size: 11px; height: 100%; letter-spacing: 1px; margin: 0px; text-transform: uppercase; padding: 0px 12px 0px 10px; display: flex; align-items: center;" + > + name1 + </p> + <p + class="badge-value" + style="background-color: black; color: rgb(255, 255, 255); font-family: Jost; font-size: 11px; height: 100%; letter-spacing: 1px; margin: 0px -4px 0px -4px; padding: 0px 8px; display: flex; align-items: center;" + > + value1 + </p> +</div> +`; diff --git a/src/components/preview/__snapshots__/card.test.tsx.snap b/src/components/preview/__snapshots__/card.test.tsx.snap new file mode 100644 index 0000000..e5ec993 --- /dev/null +++ b/src/components/preview/__snapshots__/card.test.tsx.snap @@ -0,0 +1,165 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Card #1 renders 1`] = ` +<div + class="card-wrapper theme-light" + style="width: 640px; height: 320px; padding: 10px 30px; font-family: Inter; font-weight: 400; background-color: rgb(255, 255, 255); background-image: url(data:image/svg+xml,%3Csvg%20width%3D%2242%22%20height%3D%2244%22%20viewBox%3D%220%200%2042%2044%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M0%200h42v44H0V0zm1%201h40v20H1V1zM0%2023h20v20H0V23zm22%200h20v20H22V23z%22%20fill%3D%22%23eaeaea%22%20fill-opacity%3D%220.6%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); background-size: 42px 44px; background-repeat: repeat; color: rgb(0, 0, 0); text-align: center; overflow: hidden; display: flex; flex-direction: column; justify-content: center; align-items: center; transform: scale(2); transform-origin: top left;" +> + <div + class="card-logo-wrapper" + style="display: flex; justify-content: center; align-items: center; margin-top: 10px;" + > + <img + alt="Logo" + height="100" + src="data:image/svg+xml,%3Csvg%20fill%3D%22%23181717%22%20role%3D%22img%22%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ctitle%3EGitHub%3C%2Ftitle%3E%3Cpath%20d%3D%22M12%20.297c-6.63%200-12%205.373-12%2012%200%205.303%203.438%209.8%208.205%2011.385.6.113.82-.258.82-.577%200-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422%2018.07%203.633%2017.7%203.633%2017.7c-1.087-.744.084-.729.084-.729%201.205.084%201.838%201.236%201.838%201.236%201.07%201.835%202.809%201.305%203.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93%200-1.31.465-2.38%201.235-3.22-.135-.303-.54-1.523.105-3.176%200%200%201.005-.322%203.3%201.23.96-.267%201.98-.399%203-.405%201.02.006%202.04.138%203%20.405%202.28-1.552%203.285-1.23%203.285-1.23.645%201.653.24%202.873.12%203.176.765.84%201.23%201.91%201.23%203.22%200%204.61-2.805%205.625-5.475%205.92.42.36.81%201.096.81%202.22%200%201.606-.015%202.896-.015%203.286%200%20.315.21.69.825.57C20.565%2022.092%2024%2017.592%2024%2012.297c0-6.627-5.373-12-12-12%22%2F%3E%3C%2Fsvg%3E" + style="object-fit: contain;" + width="100" + /> + </div> + <p + class="card-name-wrapper" + style="display: flex; align-items: center; margin-top: 15px; margin-bottom: 0px; font-weight: 500; font-size: 40px; line-height: 1.4;" + > + <span + class="card-name-owner" + style="display: flex; white-space: nowrap; font-weight: 200;" + /> + <span + class="card-name-name" + style="display: flex; white-space: nowrap;" + > + project_name + </span> + </p> +</div> +`; + +exports[`Card #2 renders 1`] = ` +<div + class="card-wrapper theme-dark" + style="width: 640px; height: 320px; padding: 10px 30px; font-family: KoHo; font-weight: 400; background-color: rgb(0, 0, 0); background-image: url(data:image/svg+xml,%3Csvg%20width%3D%2242%22%20height%3D%2244%22%20viewBox%3D%220%200%2042%2044%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M0%200h42v44H0V0zm1%201h40v20H1V1zM0%2023h20v20H0V23zm22%200h20v20H22V23z%22%20fill%3D%22%23eaeaea%22%20fill-opacity%3D%220.2%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); background-size: 42px 44px; background-repeat: repeat; color: rgb(255, 255, 255); text-align: center; overflow: hidden; display: flex; flex-direction: column; justify-content: center; align-items: center; transform: scale(2); transform-origin: top left;" +> + <div + class="card-logo-wrapper" + style="display: flex; justify-content: center; align-items: center; margin-top: 10px;" + > + <img + alt="Logo" + height="100" + src="data:image/gif;base64,R0lGODlhAQABAAAAACw=" + style="object-fit: contain;" + width="100" + /> + <p + class="card-logo-divider" + style="color: rgb(187, 187, 187); font-size: 30px; margin: 0px 20px; font-family: Jost;" + > + + + </p> + <img + alt="JavaScript" + height="85" + src="data:image/svg+xml,%3Csvg%20fill%3D%22%23fff%22%20role%3D%22img%22%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ctitle%3EJavaScript%3C%2Ftitle%3E%3Cpath%20d%3D%22M0%200h24v24H0V0zm22.034%2018.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84%201.515-.66.39.12.75.42.976.9%201.034-.676%201.034-.676%201.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71%201.005-1.14%201.291-.811%203.541.569%204.471%201.365%201.02%203.361%201.244%203.616%202.205.24%201.17-.87%201.545-1.966%201.41-.811-.18-1.26-.586-1.755-1.336l-1.83%201.051c.21.48.45.689.81%201.109%201.74%201.756%206.09%201.666%206.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0%201.938-.009%203.864-.009%205.805%200%201.232.063%202.363-.138%202.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825%201.125c.305.63.75%201.172%201.324%201.517.855.51%202.004.675%203.207.405.783-.226%201.458-.691%201.811-1.411.51-.93.402-2.07.397-3.346.012-2.054%200-4.109%200-6.179l.004-.056z%22%2F%3E%3C%2Fsvg%3E" + style="object-fit: contain;" + width="85" + /> + </div> + <p + class="card-name-wrapper" + style="display: flex; align-items: center; margin-top: 15px; margin-bottom: 0px; font-weight: 500; font-size: 40px; line-height: 1.4;" + > + <span + class="card-name-owner" + style="display: flex; white-space: nowrap; font-weight: 200;" + > + owner/ + </span> + <span + class="card-name-name" + style="display: flex; white-space: nowrap;" + > + project_name + </span> + </p> + <p + class="card-description-wrapper" + style="margin-top: 10px; margin-bottom: 0px; font-size: 17px; line-height: 1.4; max-height: 3em; overflow: hidden; word-break: break-all;" + > + TEST DESCRIPTION + </p> + <div + class="card-badges-wrapper" + style="margin-top: 25px; margin-bottom: 0px; display: flex; flex-direction: row;" + > + <div + class="badge-wrapper" + style="height: 25px; background-color: rgb(85, 85, 85); display: flex; margin: 0px 7px;" + > + <p + class="badge-label" + style="color: rgb(255, 255, 255); font-family: Jost; font-size: 11px; height: 100%; letter-spacing: 1px; margin: 0px; text-transform: uppercase; padding: 0px 12px 0px 10px; display: flex; align-items: center;" + > + stars + </p> + <p + class="badge-value" + style="background-color: rgb(223, 179, 23); color: rgb(255, 255, 255); font-family: Jost; font-size: 11px; height: 100%; letter-spacing: 1px; margin: 0px -4px 0px -4px; padding: 0px 8px; display: flex; align-items: center;" + > + 1 + </p> + </div> + <div + class="badge-wrapper" + style="height: 25px; background-color: rgb(85, 85, 85); display: flex; margin: 0px 7px;" + > + <p + class="badge-label" + style="color: rgb(255, 255, 255); font-family: Jost; font-size: 11px; height: 100%; letter-spacing: 1px; margin: 0px; text-transform: uppercase; padding: 0px 12px 0px 10px; display: flex; align-items: center;" + > + forks + </p> + <p + class="badge-value" + style="background-color: rgb(151, 202, 0); color: rgb(255, 255, 255); font-family: Jost; font-size: 11px; height: 100%; letter-spacing: 1px; margin: 0px -4px 0px -4px; padding: 0px 8px; display: flex; align-items: center;" + > + 2 + </p> + </div> + <div + class="badge-wrapper" + style="height: 25px; background-color: rgb(85, 85, 85); display: flex; margin: 0px 7px;" + > + <p + class="badge-label" + style="color: rgb(255, 255, 255); font-family: Jost; font-size: 11px; height: 100%; letter-spacing: 1px; margin: 0px; text-transform: uppercase; padding: 0px 12px 0px 10px; display: flex; align-items: center;" + > + issues + </p> + <p + class="badge-value" + style="background-color: rgb(0, 126, 198); color: rgb(255, 255, 255); font-family: Jost; font-size: 11px; height: 100%; letter-spacing: 1px; margin: 0px -4px 0px -4px; padding: 0px 8px; display: flex; align-items: center;" + > + 3 + </p> + </div> + <div + class="badge-wrapper" + style="height: 25px; background-color: rgb(85, 85, 85); display: flex; margin: 0px 7px;" + > + <p + class="badge-label" + style="color: rgb(255, 255, 255); font-family: Jost; font-size: 11px; height: 100%; letter-spacing: 1px; margin: 0px; text-transform: uppercase; padding: 0px 12px 0px 10px; display: flex; align-items: center;" + > + pulls + </p> + <p + class="badge-value" + style="background-color: rgb(254, 125, 55); color: rgb(255, 255, 255); font-family: Jost; font-size: 11px; height: 100%; letter-spacing: 1px; margin: 0px -4px 0px -4px; padding: 0px 8px; display: flex; align-items: center;" + > + 4 + </p> + </div> + </div> +</div> +`; diff --git a/src/components/preview/badge.test.tsx b/src/components/preview/badge.test.tsx new file mode 100644 index 0000000..fea753d --- /dev/null +++ b/src/components/preview/badge.test.tsx @@ -0,0 +1,22 @@ +import { render } from '@testing-library/react' + +import Badge from './badge' + +test('Badge renders', () => { + const { container } = render( + <Badge color="black" name="name1" value="value1" /> + ) + const badge = container.firstElementChild! + + expect(badge).toMatchSnapshot() + expect(badge.classList.contains('badge-wrapper')).toBe(true) + expect( + badge.querySelector<HTMLElement>('.badge-label')?.textContent + ).toStrictEqual('name1') + expect( + badge.querySelector<HTMLElement>('.badge-value')?.textContent + ).toStrictEqual('value1') + expect( + badge.querySelector<HTMLElement>('.badge-value')?.style.backgroundColor + ).toStrictEqual('black') +}) diff --git a/src/components/preview/badge.tsx b/src/components/preview/badge.tsx new file mode 100644 index 0000000..4ccb876 --- /dev/null +++ b/src/components/preview/badge.tsx @@ -0,0 +1,57 @@ +import React from 'react' + +type BadgeConfig = { + name: string + value: string + color: string +} + +const Badge: React.FC<BadgeConfig> = (config) => { + return ( + <div + className="badge-wrapper" + style={{ + height: 25, + backgroundColor: '#555', + display: 'flex', + margin: '0 7px' + }}> + <p + className="badge-label" + style={{ + color: '#fff', + fontFamily: 'Jost', + fontSize: 11, + height: '100%', + letterSpacing: 1, + margin: 0, + textTransform: 'uppercase', + padding: '0 12px 0 10px', + display: 'flex', + alignItems: 'center' + }}> + {config.name} + </p> + <p + className="badge-value" + style={{ + backgroundColor: config.color, + color: '#fff', + fontFamily: 'Jost', + fontSize: 11, + height: '100%', + letterSpacing: 1, + margin: 0, + padding: '0 8px', + display: 'flex', + alignItems: 'center', + marginLeft: -4, + marginRight: -4 + }}> + {config.value} + </p> + </div> + ) +} + +export default Badge diff --git a/src/components/preview/card.test.tsx b/src/components/preview/card.test.tsx new file mode 100644 index 0000000..8b4b945 --- /dev/null +++ b/src/components/preview/card.test.tsx @@ -0,0 +1,163 @@ +/* eslint-disable jest/no-conditional-expect */ +import { render } from '@testing-library/react' + +import Card from './card' + +import Configuration, { + Font, + Pattern, + Theme +} from '../../../common/types/configType' + +test('Card #1 renders', () => { + const config: Configuration = { + font: Font.inter, + logo: '', + name: { + value: 'project_name', + state: true + }, + pattern: Pattern.brickWall, + theme: Theme.light + } + + const { container } = render(<Card {...config} />) + + const cardWrapper = container.firstElementChild! as HTMLDivElement + expect(cardWrapper).toMatchSnapshot() + + expect(cardWrapper).toBeTruthy() + expect(cardWrapper.classList.contains('card-wrapper')).toBe(true) + expect(cardWrapper.style.fontFamily).toStrictEqual(config.font) + expect( + cardWrapper.classList.contains(`theme-${config.theme.toLowerCase()}`) + ).toBe(true) + expect(cardWrapper.querySelectorAll('.card-logo-wrapper img').length).toBe(1) + expect( + cardWrapper.querySelectorAll<HTMLImageElement>( + '.card-logo-wrapper img' + )?.[0]?.alt + ).toBe('Logo') + expect(cardWrapper.querySelectorAll('.card-logo-divider').length).toBe(0) + expect( + cardWrapper.querySelector('.card-name-name')?.textContent + ).toStrictEqual(config.name?.value) + expect(cardWrapper.querySelector('.card-description-wrapper')).toBeFalsy() + expect(cardWrapper.querySelectorAll('.card-badges-wrapper').length).toBe(0) +}) + +test('Card #2 renders', () => { + const config: Configuration = { + font: Font.koHo, + logo: 'data:image/gif;base64,R0lGODlhAQABAAAAACw=', + name: { + value: 'project_name', + state: true + }, + pattern: Pattern.brickWall, + theme: Theme.dark, + description: { + value: 'TEST DESCRIPTION', + state: true + }, + owner: { + value: 'owner', + state: true + }, + language: { + value: 'JavaScript', + state: true + }, + language2: { + value: 'TypeScript', + state: true + }, + stargazers: { + value: 1, + state: true + }, + forks: { + value: 2, + state: true + }, + issues: { + value: 3, + state: true + }, + pulls: { + value: 4, + state: true + } + } + + const { container } = render(<Card {...config} />) + + const cardWrapper = container.firstElementChild! as HTMLDivElement + expect(cardWrapper).toMatchSnapshot() + + expect(cardWrapper).toBeTruthy() + expect(cardWrapper.classList.contains('card-wrapper')).toBe(true) + expect(cardWrapper.style.fontFamily).toStrictEqual(config.font) + expect( + cardWrapper.classList.contains(`theme-${config.theme.toLowerCase()}`) + ).toBe(true) + expect( + cardWrapper.querySelector('.card-name-name')?.textContent + ).toStrictEqual(config.name?.value) + expect(cardWrapper.querySelectorAll('.card-logo-wrapper img').length).toBe(2) + expect( + cardWrapper.querySelectorAll<HTMLImageElement>( + '.card-logo-wrapper img' + )?.[0].src + ).toBe(config.logo) + expect( + cardWrapper.querySelectorAll<HTMLImageElement>( + '.card-logo-wrapper img' + )?.[0]?.alt + ).toBe('Logo') + expect(cardWrapper.querySelectorAll('.card-logo-divider').length).toBe(1) + expect( + cardWrapper.querySelectorAll<HTMLImageElement>( + '.card-logo-wrapper img' + )?.[1]?.alt + ).toBe('JavaScript') + expect( + cardWrapper.querySelector('.card-description-wrapper')?.textContent + ).toStrictEqual(config.description?.value) + expect(cardWrapper.querySelectorAll('.card-badges-wrapper').length).toBe(1) + expect(cardWrapper.querySelectorAll('.card-badges-wrapper > *').length).toBe( + 4 + ) + expect( + cardWrapper.querySelectorAll('.card-badges-wrapper > *')[0] + ?.firstElementChild?.textContent + ).toStrictEqual('stars') + expect( + cardWrapper.querySelectorAll('.card-badges-wrapper > *')[0] + ?.lastElementChild?.textContent + ).toStrictEqual(`${config.stargazers?.value}`) + expect( + cardWrapper.querySelectorAll('.card-badges-wrapper > *')[1] + ?.firstElementChild?.textContent + ).toStrictEqual('forks') + expect( + cardWrapper.querySelectorAll('.card-badges-wrapper > *')[1] + ?.lastElementChild?.textContent + ).toStrictEqual(`${config.forks?.value}`) + expect( + cardWrapper.querySelectorAll('.card-badges-wrapper > *')[2] + ?.firstElementChild?.textContent + ).toStrictEqual('issues') + expect( + cardWrapper.querySelectorAll('.card-badges-wrapper > *')[2] + ?.lastElementChild?.textContent + ).toStrictEqual(`${config.issues?.value}`) + expect( + cardWrapper.querySelectorAll('.card-badges-wrapper > *')[3] + ?.firstElementChild?.textContent + ).toStrictEqual('pulls') + expect( + cardWrapper.querySelectorAll('.card-badges-wrapper > *')[3] + ?.lastElementChild?.textContent + ).toStrictEqual(`${config.pulls?.value}`) +}) diff --git a/src/components/preview/card.tsx b/src/components/preview/card.tsx new file mode 100644 index 0000000..a5ddf5c --- /dev/null +++ b/src/components/preview/card.tsx @@ -0,0 +1,198 @@ +import Badge from './badge' + +import Configuration from '../../../common/types/configType' + +import { getHeroPattern, getSimpleIconsImageURI } from '../../../common/helpers' + +const Card = (config: Configuration) => { + const backgroundPatternStyles = getHeroPattern(config.pattern, config.theme) + + const languageIconImageURI = + config.language?.state && + getSimpleIconsImageURI(config.language.value, config.theme) + + const language2IconImageURI = + config.language2?.state && + getSimpleIconsImageURI(config.language2.value, config.theme) + + const displayName = [ + config.owner?.state && config.owner?.value, + config.name?.state && config.name?.value + ] + .filter((value) => typeof value === 'string') + .join('/') + const nameLength = displayName.length + const nameFontSize = + nameLength > 55 + ? '17px' + : nameLength > 45 + ? '20px' + : nameLength > 35 + ? '24px' + : nameLength > 25 + ? '30px' + : '40px' + + return ( + <div + className={`card-wrapper theme-${config.theme.toLowerCase()}`} + style={{ + width: 640, + height: 320, + padding: '10px 30px', + fontFamily: config.font, + fontWeight: 400, + ...backgroundPatternStyles, + color: config.theme.match(/dark/i) ? '#fff' : '#000', + textAlign: 'center', + overflow: 'hidden', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + transform: 'scale(2)', + transformOrigin: 'top left' + }}> + {/* Logo */} + <div + className="card-logo-wrapper" + style={{ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + marginTop: 10 + }}> + {languageIconImageURI && ( + <img + src={languageIconImageURI} + alt={config?.language?.value} + width={85} + height={85} + style={{ + objectFit: 'contain' + }} + /> + )} + {language2IconImageURI && ( + <p + className="card-logo-divider" + style={{ + color: '#bbb', + fontSize: 30, + margin: '0 20px', + fontFamily: 'Jost' + }}> + + + </p> + )} + {language2IconImageURI && ( + <img + src={language2IconImageURI} + alt={config?.language2?.value} + width={85} + height={85} + style={{ + objectFit: 'contain' + }} + /> + )} + </div> + + {/* Name */} + <p + className="card-name-wrapper" + style={{ + display: 'flex', + alignItems: 'center', + marginTop: 15, + marginBottom: 0, + fontWeight: 500, + fontSize: nameFontSize, + lineHeight: 1.4 + }}> + <span + className="card-name-owner" + style={{ + display: 'flex', + whiteSpace: 'nowrap', + fontWeight: 200 + }}> + {config.owner?.state + ? `${config.owner.value}${config.name?.state ? '/' : ''}` + : ''} + </span> + <span + className="card-name-name" + style={{ + display: 'flex', + whiteSpace: 'nowrap' + }}> + {config.name?.state ? `${config.name.value}` : ''} + </span> + </p> + + {/* Description */} + {config.description?.state && ( + <p + className="card-description-wrapper" + style={{ + marginTop: 10, + marginBottom: 0, + fontSize: 17, + lineHeight: 1.4, + maxHeight: '3em', + overflow: 'hidden', + wordBreak: 'break-all' + }}> + {config.description.value} + </p> + )} + + {/* Badges */} + {(config.stargazers?.state || + config.forks?.state || + config.issues?.state || + config.pulls?.state) && ( + <div + className="card-badges-wrapper" + style={{ + marginTop: 25, + marginBottom: 0, + display: 'flex', + flexDirection: 'row' + }}> + {config.stargazers?.state && ( + <Badge + name="stars" + value={`${config.stargazers.value}`} + color="#dfb317" + /> + )} + {config.forks?.state && ( + <Badge + name="forks" + value={`${config.forks.value}`} + color="#97ca00" + /> + )} + {config.issues?.state && ( + <Badge + name="issues" + value={`${config.issues.value}`} + color="#007ec6" + /> + )} + {config.pulls?.state && ( + <Badge + name="pulls" + value={`${config.pulls.value}`} + color="#fe7d37" + /> + )} + </div> + )} + </div> + ) +} + +export default Card diff --git a/src/components/preview/preview.tsx b/src/components/preview/preview.tsx new file mode 100644 index 0000000..6517696 --- /dev/null +++ b/src/components/preview/preview.tsx @@ -0,0 +1,218 @@ +import React, { useContext } from 'react' +import classnames from 'clsx' +import Head from 'next/head' +import Router from 'next/router' +import { toClipboard } from 'copee' +import { MdContentCopy, MdDownload } from 'react-icons/md' + +import toaster from '../toaster' + +import ConfigContext from '../../contexts/ConfigContext' +import { checkWebpSupport } from '../../../common/helpers' +import Card from './card' + +const getRelativeImageUrl = (format = 'image') => { + const [path, query] = Router.asPath.split('?') + return `${path}/${format}${query ? `?${query}` : ''}` +} + +const getImageUrl = (format = 'image') => { + return `${window.location.protocol}//${ + window.location.host + }${getRelativeImageUrl(format)}` +} + +const copyImageUrl = () => { + const screenshotImageUrl = getImageUrl() + const success = toClipboard(screenshotImageUrl) + if (success) { + toaster.success('Copied image url to clipboard') + } else { + window.open(screenshotImageUrl, '_blank') + } +} + +const copyMarkdown = () => { + const screenshotImageUrl = getImageUrl() + const ogTag = `` + const success = toClipboard(ogTag) + if (success) { + toaster.success('Copied markdown to clipboard') + } +} + +const copyImageTag = () => { + const screenshotImageUrl = getImageUrl() + const ogTag = `<img src="${screenshotImageUrl}" alt="${Router.query._name}" width="640" height="320" />` + const success = toClipboard(ogTag) + if (success) { + toaster.success('Copied image tag to clipboard') + } +} + +const copyOpenGraphTags = () => { + const ogTag = ` +<meta property="og:image" content="${getImageUrl('png')}" /> +<meta property="og:image:width" content="1280" /> +<meta property="og:image:height" content="640" /> + `.trim() + const success = toClipboard(ogTag) + if (success) { + toaster.success('Copied open graph tags to clipboard') + } +} + +const handleDownload = (fileType: string) => async () => { + toaster.info('Downloading...') + + try { + const img = new Image() + img.onload = () => { + const canvas = document.createElement('canvas') + canvas.width = 1280 + canvas.height = 640 + const context = canvas.getContext('2d') + if (context && img) { + context.drawImage(img, 0, 0, canvas.width, canvas.height) + const dataUrl = canvas.toDataURL(`image/${fileType}`) + const link = document.createElement('a') + link.download = `${Router.query._name}.${fileType}` + link.href = dataUrl + link.click() + } + } + img.src = getRelativeImageUrl() + } catch (error) { + toaster.error('Download failed: Please use a modern browser.') + console.error(error) + } +} + +const openRelativeURLInNewTab = (fileType: string) => async () => { + window.open(getRelativeImageUrl(fileType), '_blank') +} + +const Preview: React.FC = () => { + const { config } = useContext(ConfigContext) + + return ( + <section className="mb-3"> + <div + className={classnames( + 'relative cursor-pointer rounded-lg shadow-2xl overflow-hidden', + 'w-[320px] h-[160px]', + 'min-[384px]:w-[384px] min-[384px]:h-[192px]', + 'min-[400px]:w-[400px] min-[400px]:h-[200px]', + 'min-[480px]:w-[480px] min-[480px]:h-[240px]', + 'min-[640px]:w-[640px] min-[640px]:h-[320px]' + )} + onClick={copyImageUrl}> + <div + className={classnames( + 'origin-top-left', + 'scale-[0.25]', + 'min-[384px]:scale-[0.3]', + 'min-[400px]:scale-[0.3125]', + 'min-[480px]:scale-[0.375]', + 'min-[640px]:scale-[0.5]' + )}> + <Head> + <link + href={`https://fonts.googleapis.com/css2?family=Jost:wght@400&display=swap`} + rel="stylesheet" + key="preview-card-fonts-1" + /> + <link + href={`https://fonts.googleapis.com/css2?family=${config.font}:wght@200;400;500&display=swap`} + rel="stylesheet" + key="preview-card-fonts-2" + /> + </Head> + <Card {...config} /> + </div> + <img + className="absolute top-0 left-0 w-full h-full opacity-0" + alt="Card" + src={getRelativeImageUrl()} + /> + </div> + <div className="card mt-3 mx-auto w-fit bg-base-100 shadow-xl"> + <div className="card-body px-3 py-2"> + <div + className={classnames('flex justify-center content-center gap-2')}> + <div className="dropdown"> + <label tabIndex={0} className="btn btn-primary btn-sm gap-2"> + <MdDownload className="w-5 h-5" /> + Download + </label> + <ul + style={{ width: 'auto' }} + tabIndex={0} + className="dropdown-content menu menu-compact p-2 shadow bg-base-100 rounded-box w-52"> + {(checkWebpSupport() + ? ['png', 'jpeg', 'webp'] + : ['png', 'jpeg'] + ).map((fileType) => ( + <li key={fileType}> + {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} + <a + className="font-bold gap-2" + onClick={handleDownload(fileType)}> + <MdDownload className="w-5 h-5" /> + {`${config.name?.value ?? ''}.${fileType}`} + </a> + </li> + ))} + </ul> + </div> + <div className="dropdown"> + <label tabIndex={0} className="btn btn-primary btn-sm gap-2"> + API URL + </label> + <ul + style={{ width: 'auto' }} + tabIndex={0} + className="dropdown-content menu menu-compact p-2 shadow bg-base-100 rounded-box w-52"> + {['svg', 'png'].map((fileType) => ( + <li key={fileType}> + {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} + <a + className="font-bold gap-2" + onClick={openRelativeURLInNewTab(fileType)} + target="_blank" + rel="noopener noreferrer"> + {`${config.name?.value ?? ''}.${fileType}`} + </a> + </li> + ))} + </ul> + </div> + <div className="btn-group"> + <button className="btn btn-sm gap-2" onClick={copyImageUrl}> + <MdContentCopy className="w-4 h-4" /> + Url + </button> + <button + className="btn btn-sm hidden sm:inline-flex" + onClick={copyMarkdown}> + Markdown + </button> + <button + className="btn btn-sm hidden sm:inline-flex" + onClick={copyImageTag}> + {'<img />'} + </button> + <button + className="btn btn-sm gap-2 hidden sm:inline-flex" + onClick={copyOpenGraphTags}> + Open Graph + </button> + </div> + </div> + </div> + </div> + </section> + ) +} + +export default Preview |
