Skip to content

Commit

Permalink
Merge pull request urfit-tech#221 from jhihruei/feature/certificate-c…
Browse files Browse the repository at this point in the history
…apture

[FEATURE] Download certificate image
  • Loading branch information
sunnylochun authored Aug 15, 2022
2 parents 3153566 + f8460d7 commit 5424a82
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 15 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"env-cmd": "^10.1.0",
"framer-motion": "2.9.4",
"graphql-tag": "^2.12.5",
"html2canvas": "^1.4.1",
"husky": "4.3.8",
"js-cookie": "^3.0.1",
"jsonwebtoken": "8.5.1",
Expand All @@ -99,7 +100,7 @@
"libphonenumber-js": "1.9.34",
"lint-staged": "10.5.4",
"lodash": "^4.17.15",
"lodestar-app-element": "urfit-tech/lodestar-app-element#master",
"lodestar-app-element": "urfit-tech/lodestar-app-element#v1.14.0",
"moment-timezone": "^0.5.31",
"mustache": "^4.2.0",
"node-sass": "4.14.1",
Expand Down
63 changes: 50 additions & 13 deletions src/pages/MemberCertificatePage/CertificateContentBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Button } from '@chakra-ui/react'
import html2canvas from 'html2canvas'
import { BraftContent } from 'lodestar-app-element/src/components/common/StyledBraftEditor'
import { useAuth } from 'lodestar-app-element/src/contexts/AuthContext'
import moment from 'moment'
import { render } from 'mustache'
import { useCallback, useEffect, useRef, useState } from 'react'
import { forwardRef, RefObject, useCallback, useEffect, useRef, useState } from 'react'
import { useIntl } from 'react-intl'
import styled from 'styled-components'
import { StyledCode, StyledDate } from '../../components/common/CertificateCard'
Expand Down Expand Up @@ -93,6 +94,27 @@ const CertificateContentBlock: React.VFC<{ memberCertificate: MemberCertificate
...memberCertificate.values,
deliveredAt,
}
const certificateRef = useRef<HTMLDivElement | null>(null)

const CERTIFICATE_IMAGE_SIZE = 2400
const onDownLoad = () => {
if (!certificateRef.current) {
return null
}
const scale = CERTIFICATE_IMAGE_SIZE / (certificateRef as RefObject<HTMLDivElement>).current!.offsetWidth
html2canvas(certificateRef.current, {
// NOTE: Cannot get background image without allowTaint and useCORS
allowTaint: true,
useCORS: true,
scale,
}).then(canvas => {
const img = canvas.toDataURL('image/png', 1)
const link = document.createElement('a')
link.download = `${certificate.title}.png`
link.href = img
link.click()
})
}

return (
<StyledContainer>
Expand Down Expand Up @@ -127,17 +149,22 @@ const CertificateContentBlock: React.VFC<{ memberCertificate: MemberCertificate
</StyledAbstract>
</StyledContentBlock>
{/* TEMPLATE */}
<Certificate template={certificate.template || ''} templateVars={templateVars} />
<Certificate template={certificate.template || ''} templateVars={templateVars} ref={certificateRef} />
{/* TEMPLATE */}
<StyledContentBlockFooter>
<StyledAbstract className="mr-3">
{currentMember?.name}
{formatMessage(pageMessages.MemberCertificatePage.congratulations)}
</StyledAbstract>
<SocialSharePopover
url={window.location.href}
children={<StyledButton>{formatMessage(pageMessages.MemberCertificatePage.share)}</StyledButton>}
/>
<div>
<Button variant="outline" className="mr-2" onClick={onDownLoad}>
{formatMessage(pageMessages.MemberCertificatePage.download)}
</Button>
<SocialSharePopover
url={window.location.href}
children={<StyledButton>{formatMessage(pageMessages.MemberCertificatePage.share)}</StyledButton>}
/>
</div>
</StyledContentBlockFooter>
</StyledContainer>
)
Expand All @@ -157,19 +184,19 @@ const StyledCertificateCard = styled.div<{ scale: number }>`
transform: scale(${props => props.scale});
transform-origin: top left;
`
const Certificate: React.VFC<{
const _Certificate: React.VFC<{
template: string
templateVars?: any
}> = ({ template, templateVars }) => {
certificateRef?: React.Ref<HTMLDivElement>
}> = ({ template, templateVars, certificateRef }) => {
const [scale, setScale] = useState(0)
const containerRef = useRef<HTMLDivElement | null>(null)
const cardRef = useRef<HTMLDivElement | null>(null)

const handleResize = useCallback(() => {
if (containerRef.current && cardRef.current) {
setScale(containerRef.current.offsetWidth / cardRef.current.offsetWidth)
if (certificateRef && (certificateRef as RefObject<HTMLDivElement>).current && cardRef.current) {
setScale((certificateRef as RefObject<HTMLDivElement>).current!.offsetWidth / cardRef.current.offsetWidth)
}
}, [containerRef, cardRef])
}, [certificateRef, cardRef])

useEffect(() => {
handleResize()
Expand All @@ -178,7 +205,7 @@ const Certificate: React.VFC<{
}, [handleResize])

return (
<StyledCertificateContainer ref={containerRef}>
<StyledCertificateContainer ref={certificateRef}>
<StyledCertificateCard
ref={cardRef}
scale={scale}
Expand All @@ -188,4 +215,14 @@ const Certificate: React.VFC<{
)
}

const Certificate = forwardRef(
(
props: {
template: string
templateVars?: any
},
ref?: React.Ref<HTMLDivElement>,
) => <_Certificate {...props} certificateRef={ref} />,
)

export default CertificateContentBlock
4 changes: 4 additions & 0 deletions src/pages/translation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ const pageMessages = {
id: 'page.MemberCertificatePage.qualification',
defaultMessage: '學習時數',
},
download: {
id: 'page.MemberCertificatePage.download',
defaultMessage: '下載證書',
},
}),
// MerchandisePage
MerchandisePage: defineMessages({}),
Expand Down
36 changes: 35 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4967,6 +4967,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==

base64-arraybuffer@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==

base64-js@^1.0.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
Expand Down Expand Up @@ -6324,6 +6329,13 @@ css-has-pseudo@^0.10.0:
postcss "^7.0.6"
postcss-selector-parser "^5.0.0-rc.4"

css-line-break@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==
dependencies:
utrie "^1.0.2"

css-loader@4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-4.3.0.tgz#c888af64b2a5b2e85462c72c0f4a85c7e2e0821e"
Expand Down Expand Up @@ -8864,6 +8876,14 @@ html-webpack-plugin@4.5.0:
tapable "^1.1.3"
util.promisify "1.0.0"

html2canvas@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543"
integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==
dependencies:
css-line-break "^2.1.0"
text-segmentation "^1.0.3"

htmlparser2@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
Expand Down Expand Up @@ -10978,7 +10998,7 @@ lodash.xorby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==

lodestar-app-element@urfit-tech/lodestar-app-element#master:
lodestar-app-element@urfit-tech/lodestar-app-element#v1.14.0:
version "0.1.0"
resolved "https://codeload.github.com/urfit-tech/lodestar-app-element/tar.gz/8af4df5dabe7ffc77f818d8989a37b247fe1fb05"
dependencies:
Expand Down Expand Up @@ -16408,6 +16428,13 @@ test-exclude@^6.0.0:
glob "^7.1.4"
minimatch "^3.0.4"

text-segmentation@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943"
integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==
dependencies:
utrie "^1.0.2"

text-table@0.2.0, text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
Expand Down Expand Up @@ -17061,6 +17088,13 @@ utils-merge@1.0.1:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=

utrie@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645"
integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==
dependencies:
base64-arraybuffer "^1.0.2"

uuid@3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
Expand Down

0 comments on commit 5424a82

Please sign in to comment.