progress with pdf generation

pull/3395/head
Joseph Izang 2 years ago
parent d7e0f6354f
commit ef7107cc43
  1. 2
      apps/remix-ide/src/app/plugins/solidity-umlgen.tsx
  2. 16
      libs/remix-ui/solidity-uml-gen/src/lib/components/UmlDownload.tsx
  3. 32
      libs/remix-ui/solidity-uml-gen/src/lib/solidity-uml-gen.tsx
  4. 72
      libs/remix-ui/solidity-uml-gen/src/lib/utilities/UmlDownloadStrategy.ts
  5. 2
      package.json
  6. 25
      yarn.lock

@ -171,6 +171,7 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
loading: this.loading, loading: this.loading,
themeSelected: this.currentlySelectedTheme, themeSelected: this.currentlySelectedTheme,
themeName: this.themeName, themeName: this.themeName,
fileName: this.currentFile,
themeCollection: this.themeCollection themeCollection: this.themeCollection
}) })
} }
@ -181,6 +182,7 @@ export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen {
loading={state.loading} loading={state.loading}
themeSelected={state.currentlySelectedTheme} themeSelected={state.currentlySelectedTheme}
themeName={state.themeName} themeName={state.themeName}
fileName={state.fileName}
themeCollection={state.themeCollection} themeCollection={state.themeCollection}
/> />
} }

@ -1,6 +1,7 @@
import { CustomTooltip } from '@remix-ui/helper' import { CustomTooltip } from '@remix-ui/helper'
import React, { Fragment, Ref } from 'react' import React, { Fragment, Ref } from 'react'
import { Dropdown } from 'react-bootstrap' import { Dropdown } from 'react-bootstrap'
import { UmlFileType } from '../utilities/UmlDownloadStrategy'
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
@ -39,8 +40,7 @@ export const UmlCustomMenu = React.forwardRef(
) )
interface UmlDownloadProps { interface UmlDownloadProps {
downloadAsPng: () => void download: (fileType: UmlFileType) => void
downloadAsPdf: () => void
} }
export default function UmlDownload(props: UmlDownloadProps) { export default function UmlDownload(props: UmlDownloadProps) {
@ -55,7 +55,7 @@ export default function UmlDownload(props: UmlDownloadProps) {
> >
</Dropdown.Toggle> </Dropdown.Toggle>
<Dropdown.Menu as={UmlCustomMenu} className="custom-dropdown-items"> <Dropdown.Menu as={UmlCustomMenu} className="custom-dropdown-items">
<Dropdown.Item onClick={props.downloadAsPng}> <Dropdown.Item onClick={() => props.download('png')}>
<CustomTooltip <CustomTooltip
placement="left-start" placement="left-start"
tooltipId="solUmlgenDownloadAsPngTooltip" tooltipId="solUmlgenDownloadAsPngTooltip"
@ -66,7 +66,7 @@ export default function UmlDownload(props: UmlDownloadProps) {
data-id='umlPngDownload' data-id='umlPngDownload'
onClick={() => { onClick={() => {
_paq.push(['trackEvent', 'solUmlgen', 'download', 'downloadAsPng']) _paq.push(['trackEvent', 'solUmlgen', 'download', 'downloadAsPng'])
props.downloadAsPng() props.download('png')
}} }}
> >
<span <span
@ -74,7 +74,7 @@ export default function UmlDownload(props: UmlDownloadProps) {
data-id='umlPngDownload' data-id='umlPngDownload'
onClick={() => { onClick={() => {
_paq.push(['trackEvent', 'solUmlgen', 'download', 'downloadAsPng']) _paq.push(['trackEvent', 'solUmlgen', 'download', 'downloadAsPng'])
props.downloadAsPng() props.download('png')
}} }}
className='far fa-image pl-2' className='far fa-image pl-2'
> >
@ -86,7 +86,7 @@ export default function UmlDownload(props: UmlDownloadProps) {
</CustomTooltip> </CustomTooltip>
</Dropdown.Item> </Dropdown.Item>
<Dropdown.Divider /> <Dropdown.Divider />
<Dropdown.Item onClick={props.downloadAsPdf}> <Dropdown.Item onClick={() => props.download('pdf')}>
<CustomTooltip <CustomTooltip
placement="left-start" placement="left-start"
tooltipId="solUmlgenDownloadAsPdfTooltip" tooltipId="solUmlgenDownloadAsPdfTooltip"
@ -97,7 +97,7 @@ export default function UmlDownload(props: UmlDownloadProps) {
data-id='umlPdfDownload' data-id='umlPdfDownload'
onClick={() => { onClick={() => {
_paq.push(['trackEvent', 'solUmlgen', 'download', 'downloadAsPdf']) _paq.push(['trackEvent', 'solUmlgen', 'download', 'downloadAsPdf'])
props.downloadAsPdf() props.download('pdf')
}} }}
> >
<span <span
@ -105,7 +105,7 @@ export default function UmlDownload(props: UmlDownloadProps) {
data-id='umlPdfDownload' data-id='umlPdfDownload'
onClick={() => { onClick={() => {
_paq.push(['trackEvent', 'solUmlgen', 'download', 'downloadAsPdf']) _paq.push(['trackEvent', 'solUmlgen', 'download', 'downloadAsPdf'])
props.downloadAsPdf() props.download('pdf')
}} }}
className='far fa-file-pdf pl-2' className='far fa-file-pdf pl-2'
> >

@ -1,13 +1,15 @@
import React, { Fragment, useEffect, useState } from 'react' import React, { Fragment, useCallback, useEffect, useState } from 'react'
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch' import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch'
import { ThemeSummary } from '../types' import { ThemeSummary } from '../types'
import UmlDownload from './components/UmlDownload' import UmlDownload from './components/UmlDownload'
import './css/solidity-uml-gen.css' import './css/solidity-uml-gen.css'
import { UmlDownloadContext, UmlFileType } from './utilities/UmlDownloadStrategy'
export interface RemixUiSolidityUmlGenProps { export interface RemixUiSolidityUmlGenProps {
updatedSvg?: string updatedSvg?: string
loading?: boolean loading?: boolean
themeSelected?: string themeSelected?: string
themeName: string themeName: string
fileName: string
themeCollection: ThemeSummary[] themeCollection: ThemeSummary[]
} }
@ -23,29 +25,33 @@ const _paq = window._paq = window._paq || []
export function RemixUiSolidityUmlGen ({ updatedSvg, loading }: RemixUiSolidityUmlGenProps) { let umlCopy = ''
export function RemixUiSolidityUmlGen ({ updatedSvg, loading, fileName }: RemixUiSolidityUmlGenProps) {
const [showViewer, setShowViewer] = useState(false) const [showViewer, setShowViewer] = useState(false)
const [validSvg, setValidSvg] = useState(false) const [validSvg, setValidSvg] = useState(false)
const umlDownloader = new UmlDownloadContext()
useEffect(() => { useEffect(() => {
if (updatedSvg.startsWith('<?xml') && updatedSvg.includes('<svg')) {
umlCopy = updatedSvg
}
setValidSvg (updatedSvg.startsWith('<?xml') && updatedSvg.includes('<svg')) setValidSvg (updatedSvg.startsWith('<?xml') && updatedSvg.includes('<svg'))
setShowViewer(updatedSvg.startsWith('<?xml') && updatedSvg.includes('<svg')) setShowViewer(updatedSvg.startsWith('<?xml') && updatedSvg.includes('<svg'))
} }, [updatedSvg])
, [updatedSvg])
const encoder = new TextEncoder() const encoder = new TextEncoder()
const data = encoder.encode(updatedSvg) const data = encoder.encode(updatedSvg)
const final = btoa(String.fromCharCode.apply(null, data)) const final = btoa(String.fromCharCode.apply(null, data))
const downloadAsPng = () => { const download = useCallback((fileType: UmlFileType) => {
// convert serialized svg to png and download console.log({ umlCopy, validSvg })
if (umlCopy.length === 0) {
} console.log('svg not valid yet!')
return
const downloadAsPdf = () => { }
// convert serialized svg to pdf and download umlDownloader.download(umlCopy, fileName, fileType)
} }, [updatedSvg, fileName])
function ActionButtons({ actions: { zoomIn, zoomOut, resetTransform }}: ActionButtonsProps) { function ActionButtons({ actions: { zoomIn, zoomOut, resetTransform }}: ActionButtonsProps) {
@ -57,7 +63,7 @@ export function RemixUiSolidityUmlGen ({ updatedSvg, loading }: RemixUiSolidityU
style={{ zIndex: 3, top: "10", right: "2em" }} style={{ zIndex: 3, top: "10", right: "2em" }}
> >
<div className="py-2 px-2 d-flex justify-content-center align-items-center"> <div className="py-2 px-2 d-flex justify-content-center align-items-center">
<UmlDownload downloadAsPdf={downloadAsPdf} downloadAsPng={downloadAsPng} /> <UmlDownload download={download} />
<button <button
className="badge badge-info remixui_no-shadow p-2 rounded-circle mr-2" className="badge badge-info remixui_no-shadow p-2 rounded-circle mr-2"
onClick={() => zoomIn()} onClick={() => zoomIn()}

@ -0,0 +1,72 @@
import jsPDF from 'jspdf'
import 'svg2pdf.js'
const jsPdf = new jsPDF('landscape', 'px', 'a4')
interface IUmlDownloadStrategy {
download (uml: string, fileName: string): void
}
export type UmlFileType = 'pdf' | 'png'
class PdfUmlDownloadStrategy implements IUmlDownloadStrategy {
doc: jsPDF
constructor(pdf: jsPDF) {
this.doc = pdf
}
public download (uml: string, fileName: string): void {
const parser = new DOMParser()
const parsedDocument = parser.parseFromString(uml, 'image/svg+xml')
const element = parsedDocument.getElementsByTagName('svg')[0]
this.doc.svg(element, {
x: 20,
y: 20,
width: this.doc.internal.pageSize.getWidth() - 20,
height: this.doc.internal.pageSize.getHeight() - 40
}).then(() => {
this.doc.save(fileName.split('/')[1].split('.')[0].concat('.pdf'))
}).catch((err) => {
console.log(err)
})
}
}
class ImageUmlDownloadStrategy implements IUmlDownloadStrategy {
public download (uml: string, fileName: string): void {
const svg = new Blob([uml], { type: 'image/svg+xml;charset=utf-8' })
const url = URL.createObjectURL(svg)
const img = new Image()
img.onload = () => {
const canvas = document.createElement('canvas')
canvas.width = img.width
canvas.height = img.height
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0)
const png = canvas.toDataURL('image/png')
const a = document.createElement('a')
a.download = fileName.split('/')[1].split('.')[0].concat('.png')
a.href = png
a.click()
}
img.src = url
}
}
export class UmlDownloadContext {
private strategy: IUmlDownloadStrategy
private setStrategy (strategy: IUmlDownloadStrategy): void {
this.strategy = strategy
}
public download (uml: string, fileName: string, fileType: UmlFileType ): void {
if (fileType === 'pdf') {
this.setStrategy(new PdfUmlDownloadStrategy(jsPdf))
} else if (fileType === 'png') {
this.setStrategy(new ImageUmlDownloadStrategy())
} else {
throw new Error('Invalid file type')
}
this.strategy.download(uml, fileName)
}
}

@ -171,6 +171,7 @@
"isomorphic-git": "^1.8.2", "isomorphic-git": "^1.8.2",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jspdf": "^2.5.1",
"jszip": "^3.6.0", "jszip": "^3.6.0",
"latest-version": "^5.1.0", "latest-version": "^5.1.0",
"merge": "^2.1.1", "merge": "^2.1.1",
@ -195,6 +196,7 @@
"signale": "^1.4.0", "signale": "^1.4.0",
"sol2uml": "^2.4.3", "sol2uml": "^2.4.3",
"string-similarity": "^4.0.4", "string-similarity": "^4.0.4",
"svg2pdf.js": "^2.2.1",
"swarmgw": "^0.3.1", "swarmgw": "^0.3.1",
"time-stamp": "^2.2.0", "time-stamp": "^2.2.0",
"toml": "^3.0.0", "toml": "^3.0.0",

@ -13344,6 +13344,11 @@ follow-redirects@^1.15.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
font-family-papandreou@^0.2.0-patch1:
version "0.2.0-patch2"
resolved "https://registry.yarnpkg.com/font-family-papandreou/-/font-family-papandreou-0.2.0-patch2.tgz#c75b659e96ffbc7ab2af651cf7b4910b334e8dd2"
integrity sha512-l/YiRdBSH/eWv6OF3sLGkwErL+n0MqCICi9mppTZBOCL5vixWGDqCYvRcuxB2h7RGCTzaTKOHT2caHvCXQPRlw==
for-each@~0.3.3: for-each@~0.3.3:
version "0.3.3" version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
@ -23804,6 +23809,11 @@ spdy@^4.0.2:
select-hose "^2.0.0" select-hose "^2.0.0"
spdy-transport "^3.0.0" spdy-transport "^3.0.0"
specificity@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019"
integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==
split-on-first@^1.0.0: split-on-first@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
@ -24408,6 +24418,16 @@ svg-pathdata@^6.0.3:
resolved "https://registry.yarnpkg.com/svg-pathdata/-/svg-pathdata-6.0.3.tgz#80b0e0283b652ccbafb69ad4f8f73e8d3fbf2cac" resolved "https://registry.yarnpkg.com/svg-pathdata/-/svg-pathdata-6.0.3.tgz#80b0e0283b652ccbafb69ad4f8f73e8d3fbf2cac"
integrity sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw== integrity sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==
svg2pdf.js@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/svg2pdf.js/-/svg2pdf.js-2.2.1.tgz#fa81849be57c5a405c8394d35e4a4ea8aaaebeb5"
integrity sha512-gJsFT42tb+pYTuFudkKgpMws54DvsJW7wmzGRUY1b9CUJpRMoBU5B4HrCMUTlK2lpcdPL5cOyr84hy2BEj1/Ag==
dependencies:
cssesc "^3.0.0"
font-family-papandreou "^0.2.0-patch1"
specificity "^0.4.1"
svgpath "^2.3.0"
svgo@^2.7.0, svgo@^2.8.0: svgo@^2.7.0, svgo@^2.8.0:
version "2.8.0" version "2.8.0"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24"
@ -24421,6 +24441,11 @@ svgo@^2.7.0, svgo@^2.8.0:
picocolors "^1.0.0" picocolors "^1.0.0"
stable "^0.1.8" stable "^0.1.8"
svgpath@^2.3.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/svgpath/-/svgpath-2.6.0.tgz#5b160ef3d742b7dfd2d721bf90588d3450d7a90d"
integrity sha512-OIWR6bKzXvdXYyO4DK/UWa1VA1JeKq8E+0ug2DG98Y/vOmMpfZNj+TIG988HjfYSqtcy/hFOtZq/n/j5GSESNg==
swarm-js@^0.1.40: swarm-js@^0.1.40:
version "0.1.40" version "0.1.40"
resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.40.tgz#b1bc7b6dcc76061f6c772203e004c11997e06b99" resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.40.tgz#b1bc7b6dcc76061f6c772203e004c11997e06b99"

Loading…
Cancel
Save