Merge branch 'master' of https://github.com/ethereum/remix-project into resolve_with_package
commit
c81c559310
@ -0,0 +1,49 @@ |
||||
import React from 'react' |
||||
import { Plugin } from '@remixproject/engine' |
||||
import { customAction } from '@remixproject/plugin-api' |
||||
import { concatSourceFiles, getDependencyGraph } from '@remix-ui/solidity-compiler' |
||||
|
||||
const profile = { |
||||
name: 'contractflattener', |
||||
displayName: 'Contract Flattener', |
||||
description: 'Flatten solidity contracts', |
||||
methods: ['flattenAContract'], |
||||
events: [], |
||||
} |
||||
|
||||
export class ContractFlattener extends Plugin { |
||||
fileName: string |
||||
constructor() { |
||||
super(profile) |
||||
this.fileName = '' |
||||
} |
||||
|
||||
onActivation(): void { |
||||
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => { |
||||
await this.flattenContract(source, this.fileName, data) |
||||
})
|
||||
} |
||||
|
||||
async flattenAContract(action: customAction) { |
||||
this.fileName = action.path[0] |
||||
this.call('solidity', 'compile', this.fileName) |
||||
} |
||||
|
||||
/** |
||||
* Takes currently compiled contract that has a bunch of imports at the top |
||||
* and flattens them ready for UML creation. Takes the flattened result |
||||
* and assigns to a local property |
||||
* @returns {Promise<string>} |
||||
*/ |
||||
async flattenContract (source: any, filePath: string, data: any) { |
||||
const ast = data.sources |
||||
const dependencyGraph = getDependencyGraph(ast, filePath) |
||||
const sorted = dependencyGraph.isEmpty() |
||||
? [filePath] |
||||
: dependencyGraph.sort().reverse() |
||||
const sources = source.sources |
||||
const result = concatSourceFiles(sorted, sources) |
||||
await this.call('fileManager', 'writeFile', `${filePath}_flattened.sol`, result) |
||||
return result |
||||
} |
||||
} |
@ -0,0 +1,159 @@ |
||||
/* eslint-disable @nrwl/nx/enforce-module-boundaries */ |
||||
import { ViewPlugin } from '@remixproject/engine-web' |
||||
import React from 'react' |
||||
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
|
||||
import { RemixUiSolidityUmlGen } from '@remix-ui/solidity-uml-gen'
|
||||
import { ISolidityUmlGen } from 'libs/remix-ui/solidity-uml-gen/src/types' |
||||
import { RemixAppManager } from 'libs/remix-ui/plugin-manager/src/types' |
||||
import { concatSourceFiles, getDependencyGraph } from 'libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities' |
||||
import { convertUmlClasses2Dot } from 'sol2uml/lib/converterClasses2Dot' |
||||
import { convertAST2UmlClasses } from 'sol2uml/lib/converterAST2Classes' |
||||
import vizRenderStringSync from '@aduh95/viz.js/sync' |
||||
import { PluginViewWrapper } from '@remix-ui/helper' |
||||
import { customAction } from '@remixproject/plugin-api' |
||||
const parser = (window as any).SolidityParser |
||||
|
||||
const profile = { |
||||
name: 'solidityumlgen', |
||||
displayName: 'Solidity UML Generator', |
||||
description: 'Generate UML diagram in svg format from last compiled contract', |
||||
location: 'mainPanel', |
||||
methods: ['showUmlDiagram', 'generateUml', 'generateCustomAction'], |
||||
events: [], |
||||
} |
||||
|
||||
export class SolidityUmlGen extends ViewPlugin implements ISolidityUmlGen { |
||||
element: HTMLDivElement |
||||
currentFile: string |
||||
svgPayload: string |
||||
updatedSvg: string |
||||
currentlySelectedTheme: string |
||||
loading: boolean |
||||
|
||||
appManager: RemixAppManager |
||||
dispatch: React.Dispatch<any> = () => {} |
||||
constructor(appManager: RemixAppManager) { |
||||
super(profile) |
||||
this.currentFile = '' |
||||
this.svgPayload = '' |
||||
this.updatedSvg = '' |
||||
this.loading = false |
||||
this.currentlySelectedTheme = '' |
||||
this.appManager = appManager |
||||
this.element = document.createElement('div') |
||||
this.element.setAttribute('id', 'sol-uml-gen') |
||||
} |
||||
|
||||
onActivation(): void { |
||||
if (this.currentFile.length < 1)
|
||||
this.on('solidity', 'compilationFinished', async (file, source, languageVersion, data, input, version) => { |
||||
let result = '' |
||||
try { |
||||
if (data.sources && Object.keys(data.sources).length > 1) { // we should flatten first as there are multiple asts
|
||||
result = await this.flattenContract(source, this.currentFile, data) |
||||
} |
||||
const ast = result.length > 1 ? parser.parse(result) : parser.parse(source.sources[this.currentFile].content) |
||||
const umlClasses = convertAST2UmlClasses(ast, this.currentFile) |
||||
const umlDot = convertUmlClasses2Dot(umlClasses) |
||||
const payload = vizRenderStringSync(umlDot) |
||||
const currentTheme = await this.call('theme', 'currentTheme') |
||||
this.currentlySelectedTheme = currentTheme.quality |
||||
this.updatedSvg = payload |
||||
this.renderComponent() |
||||
} catch (error) { |
||||
console.log({ error }) |
||||
} |
||||
}) |
||||
this.on('theme', 'themeChanged', (theme) => { |
||||
this.currentlySelectedTheme = theme.quality |
||||
this.renderComponent() |
||||
}) |
||||
} |
||||
|
||||
async mangleSvgPayload(svgPayload: string) : Promise<string> { |
||||
const parser = new DOMParser() |
||||
const themeQuality = await this.call('theme', 'currentTheme') |
||||
const parsedDocument = parser.parseFromString(svgPayload, 'image/svg+xml') |
||||
const res = parsedDocument.documentElement |
||||
parsedDocument.bgColor = '#cccabc' |
||||
res.style.filter = themeQuality.quality === 'dark' ? 'invert(1)' : 'invert(0)' |
||||
const stringifiedSvg = new XMLSerializer().serializeToString(parsedDocument) |
||||
console.log({ parsedDocument, themeQuality, stringifiedSvg }) |
||||
return stringifiedSvg |
||||
} |
||||
|
||||
onDeactivation(): void { |
||||
this.off('solidity', 'compilationFinished') |
||||
} |
||||
|
||||
generateCustomAction = async (action: customAction) => { |
||||
this.currentFile = action.path[0] |
||||
await this.generateUml(action.path[0]) |
||||
} |
||||
|
||||
async generateUml(currentFile: string) { |
||||
await this.call('solidity', 'compile', currentFile) |
||||
await this.call('tabs', 'focus', 'solidityumlgen') |
||||
this.loading = true |
||||
this.renderComponent() |
||||
} |
||||
|
||||
/** |
||||
* Takes currently compiled contract that has a bunch of imports at the top |
||||
* and flattens them ready for UML creation. Takes the flattened result |
||||
* and assigns to a local property |
||||
* @returns {Promise<string>} |
||||
*/ |
||||
async flattenContract (source: any, filePath: string, data: any) { |
||||
const hold = { data, source, filePath } |
||||
const ast = data.sources |
||||
const dependencyGraph = getDependencyGraph(ast, filePath) |
||||
const sorted = dependencyGraph.isEmpty() |
||||
? [filePath] |
||||
: dependencyGraph.sort().reverse() |
||||
const sources = source.sources |
||||
const result = concatSourceFiles(sorted, sources) |
||||
await this.call('fileManager', 'writeFile', `${filePath}_flattened.sol`, result) |
||||
return result |
||||
} |
||||
|
||||
async showUmlDiagram(svgPayload: string) { |
||||
this.updatedSvg = svgPayload |
||||
this.renderComponent() |
||||
} |
||||
|
||||
hideSpinner() { |
||||
this.loading = false |
||||
this.renderComponent() |
||||
} |
||||
|
||||
setDispatch (dispatch: React.Dispatch<any>) { |
||||
this.dispatch = dispatch |
||||
this.renderComponent() |
||||
} |
||||
|
||||
render() { |
||||
return <div id='sol-uml-gen'> |
||||
<PluginViewWrapper plugin={this} /> |
||||
</div> |
||||
} |
||||
|
||||
renderComponent () { |
||||
this.dispatch({ |
||||
...this, |
||||
updatedSvg: this.updatedSvg, |
||||
loading: this.loading, |
||||
themeSelected: this.currentlySelectedTheme |
||||
}) |
||||
} |
||||
|
||||
updateComponent(state: any) { |
||||
return <RemixUiSolidityUmlGen |
||||
plugin={state} |
||||
updatedSvg={state.updatedSvg} |
||||
loading={state.loading} |
||||
themeSelected={state.currentlySelectedTheme} |
||||
/> |
||||
} |
||||
} |
||||
|
After Width: | Height: | Size: 6.6 KiB |
@ -1,2 +1,3 @@ |
||||
export * from './lib/solidity-compiler' |
||||
export * from './lib/logic' |
||||
export * from './lib/logic/flattenerUtilities' |
||||
|
@ -0,0 +1,169 @@ |
||||
const IMPORT_SOLIDITY_REGEX = /^\s*import(\s+).*$/gm; |
||||
const SPDX_SOLIDITY_REGEX = /^\s*\/\/ SPDX-License-Identifier:.*$/gm; |
||||
|
||||
export function getDependencyGraph(ast, target) { |
||||
const graph = tsort(); |
||||
const visited = {}; |
||||
visited[target] = 1; |
||||
_traverse(graph, visited, ast, target); |
||||
return graph; |
||||
} |
||||
|
||||
export function concatSourceFiles(files, sources) { |
||||
|
||||
let concat = ''; |
||||
for (const file of files) { |
||||
const source = sources[file].content; |
||||
const sourceWithoutImport = source.replace(IMPORT_SOLIDITY_REGEX, ''); |
||||
const sourceWithoutSPDX = sourceWithoutImport.replace(SPDX_SOLIDITY_REGEX, ''); |
||||
concat += `\n// File: ${file}\n\n`; |
||||
concat += sourceWithoutSPDX; |
||||
} |
||||
return concat; |
||||
} |
||||
|
||||
function _traverse(graph, visited, ast, name) { |
||||
const currentAst = ast[name].ast; |
||||
const dependencies = _getDependencies(currentAst); |
||||
for (const dependency of dependencies) { |
||||
const path = resolve(name, dependency); |
||||
if (path in visited) { |
||||
continue; |
||||
} |
||||
visited[path] = 1; |
||||
graph.add(name, path); |
||||
_traverse(graph, visited, ast, path); |
||||
} |
||||
} |
||||
|
||||
function _getDependencies(ast) { |
||||
const dependencies = ast.nodes |
||||
.filter(node => node.nodeType === 'ImportDirective') |
||||
.map(node => node.file); |
||||
return dependencies; |
||||
} |
||||
|
||||
|
||||
// TSORT
|
||||
|
||||
function tsort(initial?: any) { |
||||
const graph = new Graph(); |
||||
|
||||
if (initial) { |
||||
initial.forEach(function (entry) { |
||||
Graph.prototype.add.apply(graph, entry); |
||||
}); |
||||
} |
||||
|
||||
return graph; |
||||
} |
||||
|
||||
|
||||
function Graph() { |
||||
this.nodes = {}; |
||||
} |
||||
|
||||
// Add sorted items to the graph
|
||||
Graph.prototype.add = function () { |
||||
const self = this; |
||||
// eslint-disable-next-line prefer-rest-params
|
||||
let items = [].slice.call(arguments); |
||||
|
||||
if (items.length === 1 && Array.isArray(items[0])) |
||||
items = items[0]; |
||||
|
||||
items.forEach(function (item) { |
||||
if (!self.nodes[item]) { |
||||
self.nodes[item] = []; |
||||
} |
||||
}); |
||||
|
||||
for (let i = 1; i < items.length; i++) { |
||||
const from = items[i]; |
||||
const to = items[i - 1]; |
||||
|
||||
self.nodes[from].push(to); |
||||
} |
||||
|
||||
return self; |
||||
}; |
||||
|
||||
// Depth first search
|
||||
// As given in http://en.wikipedia.org/wiki/Topological_sorting
|
||||
Graph.prototype.sort = function () { |
||||
const self = this; |
||||
const nodes = Object.keys(this.nodes); |
||||
|
||||
const sorted = []; |
||||
const marks = {}; |
||||
|
||||
for (let i = 0; i < nodes.length; i++) { |
||||
const node = nodes[i]; |
||||
|
||||
if (!marks[node]) { |
||||
visit(node); |
||||
} |
||||
} |
||||
|
||||
return sorted; |
||||
|
||||
function visit(node) { |
||||
if (marks[node] === 'temp') |
||||
throw new Error("There is a cycle in the graph. It is not possible to derive a topological sort."); |
||||
else if (marks[node]) |
||||
return; |
||||
|
||||
marks[node] = 'temp'; |
||||
self.nodes[node].forEach(visit); |
||||
marks[node] = 'perm'; |
||||
|
||||
sorted.push(node); |
||||
} |
||||
}; |
||||
|
||||
Graph.prototype.isEmpty = function () { |
||||
const nodes = Object.keys(this.nodes); |
||||
return nodes.length === 0; |
||||
} |
||||
|
||||
|
||||
// PATH
|
||||
|
||||
function resolve(parentPath, childPath) { |
||||
if (_isAbsolute(childPath)) { |
||||
return childPath; |
||||
} |
||||
const path = parentPath + '/../' + childPath; |
||||
const pathParts = path.split('/'); |
||||
const resolvedParts = _resolvePathArray(pathParts); |
||||
const resolvedPath = resolvedParts |
||||
.join('/') |
||||
.replace('http:/', 'http://') |
||||
.replace('https:/', 'https://'); |
||||
return resolvedPath; |
||||
} |
||||
|
||||
function _isAbsolute(path) { |
||||
return path[0] !== '.'; |
||||
} |
||||
|
||||
function _resolvePathArray(parts) { |
||||
const res = []; |
||||
for (let i = 0; i < parts.length; i++) { |
||||
const p = parts[i]; |
||||
|
||||
// ignore empty parts
|
||||
if (!p || p === '.') |
||||
continue; |
||||
|
||||
if (p === '..') { |
||||
if (res.length && res[res.length - 1] !== '..') { |
||||
res.pop(); |
||||
} |
||||
} else { |
||||
res.push(p); |
||||
} |
||||
} |
||||
|
||||
return res; |
||||
} |
@ -0,0 +1,294 @@ |
||||
/* eslint-disable prefer-const */ |
||||
import domToImage from 'dom-to-image'; |
||||
import { jsPDF } from 'jspdf'; |
||||
|
||||
const _cloneNode = (node, javascriptEnabled) => { |
||||
let child = node.firstChild |
||||
const clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false) |
||||
while (child) { |
||||
if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') { |
||||
clone.appendChild(_cloneNode(child, javascriptEnabled)) |
||||
} |
||||
child = child.nextSibling |
||||
} |
||||
if (node.nodeType === 1) { |
||||
if (node.nodeName === 'CANVAS') { |
||||
clone.width = node.width |
||||
clone.height = node.height |
||||
clone.getContext('2d').drawImage(node, 0, 0) |
||||
} else if (node.nodeName === 'TEXTAREA' || node.nodeName === 'SELECT') { |
||||
clone.value = node.value |
||||
} |
||||
clone.addEventListener('load', (() => { |
||||
clone.scrollTop = node.scrollTop |
||||
clone.scrollLeft = node.scrollLeft |
||||
}), true) |
||||
} |
||||
return clone |
||||
} |
||||
|
||||
const _createElement = (tagName, {className, innerHTML, style}) => { |
||||
let i |
||||
let scripts |
||||
const el = document.createElement(tagName) |
||||
if (className) { |
||||
el.className = className |
||||
} |
||||
if (innerHTML) { |
||||
el.innerHTML = innerHTML |
||||
scripts = el.getElementsByTagName('script') |
||||
i = scripts.length |
||||
while (i-- > 0) { |
||||
scripts[i].parentNode.removeChild(scripts[i]) |
||||
} |
||||
} |
||||
for (const key in style) { |
||||
el.style[key] = style[key]; |
||||
} |
||||
return el; |
||||
}; |
||||
|
||||
const _isCanvasBlank = canvas => { |
||||
const blank = document.createElement('canvas'); |
||||
blank.width = canvas.width; |
||||
blank.height = canvas.height; |
||||
const ctx = blank.getContext('2d'); |
||||
ctx.fillStyle = '#FFFFFF'; |
||||
ctx.fillRect(0, 0, blank.width, blank.height); |
||||
return canvas.toDataURL() === blank.toDataURL(); |
||||
}; |
||||
|
||||
const downloadPdf = (dom, options, cb) => { |
||||
const a4Height = 841.89; |
||||
const a4Width = 595.28; |
||||
let overrideWidth; |
||||
let container; |
||||
let containerCSS; |
||||
let containerWidth; |
||||
let elements; |
||||
let excludeClassNames; |
||||
let excludeTagNames; |
||||
let filename; |
||||
let filterFn; |
||||
let innerRatio; |
||||
let overlay; |
||||
let overlayCSS; |
||||
let pageHeightPx; |
||||
let proxyUrl; |
||||
let compression = 'NONE'; |
||||
let scale; |
||||
let opts; |
||||
let offsetHeight; |
||||
let offsetWidth; |
||||
let scaleObj; |
||||
let style; |
||||
const transformOrigin = 'top left'; |
||||
const pdfOptions: any = { |
||||
orientation: 'l', |
||||
unit: 'pt', |
||||
format: 'a4' |
||||
}; |
||||
|
||||
({filename, excludeClassNames = [], excludeTagNames = ['button', 'input', 'select'], overrideWidth, proxyUrl, compression, scale} = options); |
||||
|
||||
overlayCSS = { |
||||
position: 'fixed', |
||||
zIndex: 1000, |
||||
opacity: 0, |
||||
left: 0, |
||||
right: 0, |
||||
bottom: 0, |
||||
top: 0, |
||||
backgroundColor: 'rgba(0,0,0,0.8)' |
||||
}; |
||||
if (overrideWidth) { |
||||
overlayCSS.width = `${overrideWidth}px`; |
||||
} |
||||
containerCSS = { |
||||
position: 'absolute', |
||||
left: 0, |
||||
right: 0, |
||||
top: 0, |
||||
height: 'auto', |
||||
margin: 'auto', |
||||
overflow: 'auto', |
||||
backgroundColor: 'white' |
||||
}; |
||||
overlay = _createElement('div', { |
||||
style: overlayCSS, |
||||
className: '', |
||||
innerHTML: '' |
||||
}); |
||||
container = _createElement('div', { |
||||
style: containerCSS, |
||||
className: '', |
||||
innerHTML: '' |
||||
}); |
||||
//@ts-ignore
|
||||
container.appendChild(_cloneNode(dom)); |
||||
overlay.appendChild(container); |
||||
document.body.appendChild(overlay); |
||||
innerRatio = a4Height / a4Width; |
||||
containerWidth = overrideWidth || container.getBoundingClientRect().width; |
||||
pageHeightPx = Math.floor(containerWidth * innerRatio); |
||||
elements = container.querySelectorAll('*'); |
||||
|
||||
for (let i = 0, len = excludeClassNames.length; i < len; i++) { |
||||
const clName = excludeClassNames[i]; |
||||
container.querySelectorAll(`.${clName}`).forEach(function(a) { |
||||
return a.remove(); |
||||
}); |
||||
} |
||||
|
||||
for (let j = 0, len1 = excludeTagNames.length; j < len1; j++) { |
||||
const tName = excludeTagNames[j]; |
||||
let els = container.getElementsByTagName(tName); |
||||
|
||||
for (let k = els.length - 1; k >= 0; k--) { |
||||
if (!els[k]) { |
||||
continue; |
||||
} |
||||
els[k].parentNode.removeChild(els[k]); |
||||
} |
||||
} |
||||
|
||||
Array.prototype.forEach.call(elements, el => { |
||||
let clientRect; |
||||
let endPage; |
||||
let nPages; |
||||
let pad; |
||||
let rules; |
||||
let startPage; |
||||
rules = { |
||||
before: false, |
||||
after: false, |
||||
avoid: true |
||||
}; |
||||
clientRect = el.getBoundingClientRect(); |
||||
if (rules.avoid && !rules.before) { |
||||
startPage = Math.floor(clientRect.top / pageHeightPx); |
||||
endPage = Math.floor(clientRect.bottom / pageHeightPx); |
||||
nPages = Math.abs(clientRect.bottom - clientRect.top) / pageHeightPx; |
||||
// Turn on rules.before if the el is broken and is at most one page long.
|
||||
if (endPage !== startPage && nPages <= 1) { |
||||
rules.before = true; |
||||
} |
||||
// Before: Create a padding div to push the element to the next page.
|
||||
if (rules.before) { |
||||
pad = _createElement('div', { |
||||
className: '', |
||||
innerHTML: '', |
||||
style: { |
||||
display: 'block', |
||||
height: `${pageHeightPx - clientRect.top % pageHeightPx}px` |
||||
} |
||||
}); |
||||
return el.parentNode.insertBefore(pad, el); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
// Remove unnecessary elements from result pdf
|
||||
filterFn = ({classList, tagName}) => { |
||||
let cName; |
||||
let j; |
||||
let len; |
||||
let ref; |
||||
if (classList) { |
||||
for (j = 0, len = excludeClassNames.length; j < len; j++) { |
||||
cName = excludeClassNames[j]; |
||||
if (Array.prototype.indexOf.call(classList, cName) >= 0) { |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
ref = tagName != null ? tagName.toLowerCase() : undefined; |
||||
return excludeTagNames.indexOf(ref) < 0; |
||||
}; |
||||
|
||||
opts = { |
||||
filter: filterFn, |
||||
proxy: proxyUrl |
||||
}; |
||||
|
||||
if (scale) { |
||||
offsetWidth = container.offsetWidth; |
||||
offsetHeight = container.offsetHeight; |
||||
style = { |
||||
transform: 'scale(' + scale + ')', |
||||
transformOrigin: transformOrigin, |
||||
width: offsetWidth + 'px', |
||||
height: offsetHeight + 'px' |
||||
}; |
||||
scaleObj = { |
||||
width: offsetWidth * scale, |
||||
height: offsetHeight * scale, |
||||
quality: 1, |
||||
style: style |
||||
}; |
||||
opts = Object.assign(opts, scaleObj); |
||||
} |
||||
|
||||
return domToImage.toCanvas(container, opts).then(canvas => { |
||||
let h; |
||||
let imgData; |
||||
let nPages; |
||||
let page; |
||||
let pageCanvas; |
||||
let pageCtx; |
||||
let pageHeight; |
||||
let pdf; |
||||
let pxFullHeight; |
||||
let w; |
||||
// Remove overlay
|
||||
document.body.removeChild(overlay); |
||||
// Initialize the PDF.
|
||||
pdf = new jsPDF(pdfOptions); |
||||
// Calculate the number of pages.
|
||||
pxFullHeight = canvas.height; |
||||
nPages = Math.ceil(pxFullHeight / pageHeightPx); |
||||
// Define pageHeight separately so it can be trimmed on the final page.
|
||||
pageHeight = a4Height; |
||||
pageCanvas = document.createElement('canvas'); |
||||
pageCtx = pageCanvas.getContext('2d'); |
||||
pageCanvas.width = canvas.width; |
||||
pageCanvas.height = pageHeightPx; |
||||
page = 0; |
||||
while (page < nPages) { |
||||
if (page === nPages - 1 && pxFullHeight % pageHeightPx !== 0) { |
||||
pageCanvas.height = pxFullHeight % pageHeightPx; |
||||
pageHeight = pageCanvas.height * a4Width / pageCanvas.width; |
||||
} |
||||
w = pageCanvas.width; |
||||
h = pageCanvas.height; |
||||
pageCtx.fillStyle = 'white'; |
||||
pageCtx.fillRect(0, 0, w, h); |
||||
pageCtx.drawImage(canvas, 0, page * pageHeightPx, w, h, 0, 0, w, h); |
||||
// Don't create blank pages
|
||||
if (_isCanvasBlank(pageCanvas)) { |
||||
++page; |
||||
continue; |
||||
} |
||||
// Add the page to the PDF.
|
||||
if (page) { |
||||
pdf.addPage(); |
||||
} |
||||
imgData = pageCanvas.toDataURL('image/PNG'); |
||||
pdf.addImage(imgData, 'PNG', 0, 0, a4Width, pageHeight, undefined, compression); |
||||
++page; |
||||
} |
||||
if (typeof cb === "function") { |
||||
cb(pdf); |
||||
} |
||||
return pdf.save(filename); |
||||
}).catch(error => { |
||||
// Remove overlay
|
||||
document.body.removeChild(overlay); |
||||
if (typeof cb === "function") { |
||||
cb(null); |
||||
} |
||||
return console.error(error); |
||||
}); |
||||
}; |
||||
|
||||
module.exports = downloadPdf; |
@ -0,0 +1 @@ |
||||
export * from './lib/solidity-uml-gen' |
@ -0,0 +1,3 @@ |
||||
.remixui_default-message { |
||||
margin-top: 100px; |
||||
} |
@ -0,0 +1,118 @@ |
||||
import { CustomTooltip } from '@remix-ui/helper' |
||||
import React, { Fragment, useEffect, useState } from 'react' |
||||
import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch' |
||||
import { ISolidityUmlGen } from '../types' |
||||
import './css/solidity-uml-gen.css' |
||||
export interface RemixUiSolidityUmlGenProps { |
||||
plugin?: ISolidityUmlGen |
||||
updatedSvg?: string |
||||
loading?: boolean |
||||
themeSelected?: string |
||||
} |
||||
|
||||
type ButtonAction = { |
||||
svgValid: () => boolean |
||||
action: () => void |
||||
buttonText: string |
||||
icon?: string |
||||
customcss?: string |
||||
} |
||||
|
||||
interface ActionButtonsProps { |
||||
buttons: ButtonAction[] |
||||
} |
||||
|
||||
const ActionButtons = ({ buttons }: ActionButtonsProps) => ( |
||||
<> |
||||
{buttons.map(btn => ( |
||||
<CustomTooltip |
||||
key={btn.buttonText} |
||||
placement="top" |
||||
tooltipText={btn.buttonText} |
||||
tooltipId={btn.buttonText} |
||||
> |
||||
<button |
||||
key={btn.buttonText} |
||||
className={`btn btn-primary btn-sm rounded-circle ${btn.customcss}`} |
||||
disabled={!btn.svgValid} |
||||
onClick={btn.action} |
||||
> |
||||
<i className={btn.icon}></i> |
||||
</button> |
||||
</CustomTooltip> |
||||
))} |
||||
</> |
||||
) |
||||
|
||||
export function RemixUiSolidityUmlGen ({ plugin, updatedSvg, loading, themeSelected }: RemixUiSolidityUmlGenProps) { |
||||
const [showViewer, setShowViewer] = useState(false) |
||||
const [svgPayload, setSVGPayload] = useState<string>('') |
||||
const [validSvg, setValidSvg] = useState(false) |
||||
|
||||
|
||||
|
||||
useEffect(() => { |
||||
setValidSvg (updatedSvg.startsWith('<?xml') && updatedSvg.includes('<svg'))
|
||||
setShowViewer(updatedSvg.startsWith('<?xml') && updatedSvg.includes('<svg')) |
||||
} |
||||
, [updatedSvg]) |
||||
|
||||
const buttons: ButtonAction[] = [ |
||||
{
|
||||
buttonText: 'Download as PDF', |
||||
svgValid: () => validSvg, |
||||
action: () => console.log('generated!!'), |
||||
icon: 'fa mr-1 pt-1 pb-1 fa-file' |
||||
}, |
||||
{
|
||||
buttonText: 'Download as PNG', |
||||
svgValid: () => validSvg, |
||||
action: () => console.log('generated!!'), |
||||
icon: 'fa fa-picture-o' |
||||
} |
||||
] |
||||
|
||||
const DefaultInfo = () => ( |
||||
<div className="d-flex flex-column justify-content-center align-items-center mt-5"> |
||||
<h2 className="h2 align-self-start"><p>To view your contract as a Uml Diragram</p></h2> |
||||
<h3 className="h4 align-self-start"><p>Right Click on your contract file (Usually ends with .sol)</p></h3> |
||||
<h3 className="h4 align-self-start"><p>Click on Generate UML</p></h3> |
||||
</div> |
||||
) |
||||
const Display = () => { |
||||
const invert = themeSelected === 'dark' ? 'invert(0.8)' : 'invert(0)' |
||||
return ( |
||||
<div className="d-flex flex-column justify-content-center align-items-center"> |
||||
<div id="umlImageHolder" className="w-100 px-2 py-2"> |
||||
{ validSvg && showViewer ? ( |
||||
<TransformWrapper |
||||
initialScale={1} |
||||
> |
||||
{ |
||||
({ zoomIn, zoomOut, resetTransform }) => ( |
||||
<Fragment> |
||||
<TransformComponent> |
||||
<img
|
||||
src={`data:image/svg+xml;base64,${btoa(plugin.updatedSvg ?? svgPayload)}`} |
||||
width={'100%'} |
||||
height={'auto'} |
||||
style={{ filter: invert }} |
||||
/> |
||||
</TransformComponent> |
||||
</Fragment> |
||||
) |
||||
} |
||||
</TransformWrapper> |
||||
) : loading ? <div className="justify-content-center align-items-center d-flex mx-auto my-auto"> |
||||
<i className="fas fa-spinner fa-spin fa-4x"></i> |
||||
</div> : <DefaultInfo />} |
||||
</div> |
||||
</div> |
||||
)} |
||||
return (<> |
||||
{ <Display /> } |
||||
</> |
||||
) |
||||
} |
||||
|
||||
export default RemixUiSolidityUmlGen |
@ -0,0 +1,14 @@ |
||||
import { ViewPlugin } from '@remixproject/engine-web' |
||||
import React from 'react' |
||||
|
||||
export interface ISolidityUmlGen extends ViewPlugin { |
||||
element: HTMLDivElement |
||||
currentFile: string |
||||
svgPayload: string |
||||
updatedSvg: string |
||||
showUmlDiagram(path: string, svgPayload: string): void |
||||
updateComponent(state: any): JSX.Element |
||||
setDispatch(dispatch: React.Dispatch<any>): void |
||||
|
||||
render(): JSX.Element |
||||
} |
Loading…
Reference in new issue