Merge branch 'master' into youtH

pull/3323/head
Liana Husikyan 2 years ago committed by GitHub
commit 836ba45a7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      apps/remix-ide-e2e/src/tests/ballot.test.ts
  2. 12
      apps/remix-ide/src/app.js
  3. 7
      apps/remix-ide/src/app/files/fileManager.ts
  4. 2
      apps/remix-ide/src/app/panels/tab-proxy.js
  5. 49
      apps/remix-ide/src/app/plugins/contractFlattener.tsx
  6. 159
      apps/remix-ide/src/app/plugins/solidity-umlgen.tsx
  7. 4
      apps/remix-ide/src/app/tabs/locales/en/solidity.json
  8. 4
      apps/remix-ide/src/app/tabs/locales/zh/solidity.json
  9. 4
      apps/remix-ide/src/app/tabs/theme-module.js
  10. 16
      apps/remix-ide/src/remixAppManager.js
  11. 2
      libs/remix-lib/src/types/ICompilerApi.ts
  12. 1
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  13. 1
      libs/remix-ui/solidity-compiler/src/index.ts
  14. 3
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  15. 3
      libs/remix-ui/solidity-compiler/src/lib/contract-selection.tsx
  16. 169
      libs/remix-ui/solidity-compiler/src/lib/logic/flattenerUtilities.ts
  17. 294
      libs/remix-ui/solidity-compiler/src/lib/logic/pdfSaveLogic.ts
  18. 1
      libs/remix-ui/solidity-uml-gen/src/index.ts
  19. 3
      libs/remix-ui/solidity-uml-gen/src/lib/css/solidity-uml-gen.css
  20. 118
      libs/remix-ui/solidity-uml-gen/src/lib/solidity-uml-gen.tsx
  21. 14
      libs/remix-ui/solidity-uml-gen/src/types/index.ts
  22. 2
      libs/remix-ui/theme-module/types/theme-module.ts
  23. 2
      libs/remix-ui/vertical-icons-panel/src/lib/components/Icon.tsx
  24. 3
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  25. 63
      libs/remix-ui/workspace/src/lib/types/index.ts
  26. 2
      libs/remix-ui/workspace/src/lib/utils/index.ts
  27. 7
      package.json
  28. 3
      tsconfig.paths.json
  29. 917
      yarn.lock

@ -236,7 +236,7 @@ module.exports = {
.waitForElementVisible('select[id="compilierLanguageSelector"]', 10000)
.click('select[id="compilierLanguageSelector"]')
.click('select[id="compilierLanguageSelector"] option[value=Yul]')
.waitForElementContainsText('[data-id="compiledContracts"]', 'Contract', 60000)
.waitForElementContainsText('[data-id="compiledContracts"]', 'Contract', 65000)
.clickLaunchIcon('udapp')
.click('*[data-id="Deploy - transact (not payable)"]')
.waitForElementPresent('*[data-id="universalDappUiContractActionWrapper"]', 60000)

@ -35,6 +35,8 @@ import { Injected0ptimismProvider } from './app/tabs/injected-optimism-provider'
import { InjectedArbitrumOneProvider } from './app/tabs/injected-arbitrum-one-provider'
import { FileDecorator } from './app/plugins/file-decorator'
import { CodeFormat } from './app/plugins/code-format'
import { SolidityUmlGen } from './app/plugins/solidity-umlgen'
import { ContractFlattener } from './app/plugins/contractFlattener'
const isElectron = require('is-electron')
@ -173,6 +175,12 @@ class AppComponent {
//----- search
const search = new SearchPlugin()
//---------------- Solidity UML Generator -------------------------
const solidityumlgen = new SolidityUmlGen(appManager)
// ----------------- ContractFlattener ----------------------------
const contractFlattener = new ContractFlattener()
// ----------------- import content service ------------------------
const contentImport = new CompilerImports()
@ -265,7 +273,9 @@ class AppComponent {
injected0ptimismProvider,
injectedArbitrumOneProvider,
this.walkthroughService,
search
search,
solidityumlgen,
contractFlattener
])
// LAYOUT & SYSTEM VIEWS

@ -5,6 +5,7 @@ import Registry from '../state/registry'
import { EventEmitter } from 'events'
import { fileChangedToastMsg, recursivePasteToastMsg, storageFullMessage } from '@remix-ui/helper'
import helper from '../../lib/helper.js'
import { RemixAppManager } from '../../remixAppManager'
/*
attach to files event (removed renamed)
@ -40,7 +41,7 @@ class FileManager extends Plugin {
events: EventEmitter
editor: any
_components: any
appManager: any
appManager: RemixAppManager
_deps: any
getCurrentFile: () => any
getFile: (path: any) => Promise<unknown>
@ -622,13 +623,15 @@ class FileManager extends Plugin {
file = resolved.file
await this.saveCurrentFile()
if (this.currentFile() === file) return
const provider = resolved.provider
this._deps.config.set('currentFile', file)
this.openedFiles[file] = file
let content = ''
try {
content = await provider.get(file)
content = await provider.get(file)
} catch (error) {
console.log(error)
throw error

@ -33,7 +33,7 @@ export class TabProxy extends Plugin {
this.on('fileManager', 'filesAllClosed', () => {
this.call('manager', 'activatePlugin', 'home')
this.focus("home")
this.focus('home')
})
this.on('fileManager', 'fileRemoved', (name) => {

@ -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}
/>
}
}

@ -22,6 +22,10 @@
"solidity.noFileSelected": "no file selected",
"solidity.compileAndRunScript": "Compile and Run script",
"solidity.publishOn": "Publish on",
"solidity.flatten": "Flatten contracts before UML generation.",
"solidity.generateUML": "Generate a UML diagram of your contract.",
"solidity.flattenLabel": "Flatten",
"solidity.generateUMLLabel": "Generate UML Diagram",
"solidity.Assembly": "Assembly opcodes describing the contract including corresponding solidity source code",
"solidity.Opcodes": "Assembly opcodes describing the contract",
"solidity.name": "Name of the compiled contract",

@ -22,6 +22,10 @@
"solidity.noFileSelected": "未选中文件",
"solidity.compileAndRunScript": "编译且执行脚本",
"solidity.publishOn": "发布到",
"solidity.flatten": "",
"solidity.generateUML": "",
"solidity.flattenLabel": "",
"solidity.generateUMLLabel": "",
"solidity.Assembly": "合约的汇编操作码,包含对应的solidity源程序",
"solidity.Opcodes": "合约的汇编操作码",
"solidity.name": "已编译合约的名称",

@ -53,7 +53,9 @@ export class ThemeModule extends Plugin {
this.forced = !!queryTheme
}
/** Return the active theme */
/** Return the active theme
* @return {{ name: string, quality: string, url: string }} - The active theme
*/
currentTheme () {
return this.themes[this.active]
}

@ -11,7 +11,7 @@ const requiredModules = [ // services + layout views + system views
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout',
'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy',
'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider', 'injected-arbitrum-one-provider',
'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser', 'codeFormatter']
'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser', 'codeFormatter', 'solidityumlgen', 'contractflattener']
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither']
@ -157,8 +157,8 @@ export class RemixAppManager extends PluginManager {
async registerContextMenuItems() {
await this.call('filePanel', 'registerContextMenuItem', {
id: 'flattener',
name: 'flattenFileCustomAction',
id: 'contractflattener',
name: 'flattenAContract',
label: 'Flatten',
type: [],
extension: ['.sol'],
@ -176,6 +176,16 @@ export class RemixAppManager extends PluginManager {
pattern: [],
sticky: true
})
await this.call('filePanel', 'registerContextMenuItem', {
id: 'solidityumlgen',
name: 'generateCustomAction',
label: 'Generate UML',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true
})
}
}

@ -34,7 +34,7 @@ export interface ICompilerApi {
resolveContentAndSave: (url: string) => Promise<string>
fileExists: (file: string) => Promise<boolean>
writeFile: (file: string, content: string) => Promise<void>
writeFile: (file: string, content: any) => Promise<void>
readFile: (file: string) => Promise<string>
open: (file: string) => void
saveCurrentFile: () => void

@ -78,6 +78,7 @@ const RemixApp = (props: IRemixAppUi) => {
}
return (
//@ts-ignore
<IntlProvider locale={locale.code} messages={locale.messages}>
<AppProvider value={value}>
<OriginWarning></OriginWarning>

@ -1,2 +1,3 @@
export * from './lib/solidity-compiler'
export * from './lib/logic'
export * from './lib/logic/flattenerUtilities'

@ -5,7 +5,6 @@ import { CompilerContainerProps } from './types'
import { ConfigurationSettings } from '@remix-project/remix-lib'
import { checkSpecialChars, CustomTooltip, extractNameFromKey } from '@remix-ui/helper'
import { canUseWorker, baseURLBin, baseURLWasm, urlFromVersion, pathToURL } from '@remix-project/remix-solidity'
import { compilerReducer, compilerInitialState } from './reducers/compiler'
import { resetEditorMode, listenToEvents } from './actions/compiler'
import { getValidLanguage } from '@remix-project/remix-solidity'
@ -22,7 +21,6 @@ declare global {
_paq: any
}
}
const _paq = window._paq = window._paq || [] //eslint-disable-line
export const CompilerContainer = (props: CompilerContainerProps) => {
@ -733,7 +731,6 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
setToggleExpander(!toggleExpander)
}
return (
<section>
<article>

@ -1,4 +1,4 @@
import React, { useState, useEffect, Fragment } from 'react' // eslint-disable-line
import React, { useState, useEffect } from 'react' // eslint-disable-line
import { FormattedMessage, useIntl } from 'react-intl'
import { ContractSelectionProps } from './types'
import { PublishToStorage } from '@remix-ui/publish-to-storage' // eslint-disable-line
@ -25,6 +25,7 @@ export const ContractSelection = (props: ContractSelectionProps) => {
}
}, [contractList])
const resetStorage = () => {
setStorage('')
}

@ -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
}

@ -43,4 +43,4 @@ export interface ThemeModule extends Plugin<any, any> {
fixInvert(image?: any): void;
}
interface Theme { name: string, quality: string, url: string }
export interface Theme { name: string, quality: string, url: string }

@ -86,7 +86,7 @@ const Icon = ({
return (
<>
<CustomTooltip
placement={name === 'settings' ? 'right' : name === 'search' ? 'bottom' :
placement={name === 'settings' ? 'right' : name === 'search' ? 'top' :
name === 'udapp' ? 'bottom' : "top"}
tooltipText={title}
delay={{ show: 1000, hide: 0 }}

@ -38,7 +38,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
})
const [canPaste, setCanPaste] = useState(false)
const treeRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (contextMenuItems) {
addMenuItems(contextMenuItems)
@ -435,6 +435,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
props.modal('Moving Folder Failed', 'Unexpected error while moving folder: ' + src, 'Close', async () => {})
}
}
return (
<Drag onFileMoved={handleFileMove} onFolderMoved={handleFolderMove}>
<div ref={treeRef} tabIndex={0} style={{ outline: "none" }}>

@ -1,6 +1,9 @@
/* eslint-disable @nrwl/nx/enforce-module-boundaries */
import React from 'react'
import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel'
import { fileDecoration } from '@remix-ui/file-decorators';
import { fileDecoration } from '@remix-ui/file-decorators'
import { RemixAppManager } from 'libs/remix-ui/plugin-manager/src/types'
import { ViewPlugin } from '@remixproject/engine-web'
export type action = { name: string, type?: Array<'folder' | 'gist' | 'file'>, path?: string[], extension?: string[], pattern?: string[], id: string, multiselect: boolean, label: string, sticky?: boolean }
export interface JSONStandardInput {
@ -16,32 +19,7 @@ export interface JSONStandardInput {
export type MenuItems = action[]
export type WorkspaceTemplate = 'gist-template' | 'code-template' | 'remixDefault' | 'blank' | 'ozerc20' | 'zeroxErc20' | 'ozerc721'
export interface WorkspaceProps {
plugin: {
setWorkspace: ({ name, isLocalhost }, setEvent: boolean) => void,
createWorkspace: (name: string, workspaceTemplateName: string) => void,
renameWorkspace: (oldName: string, newName: string) => void
workspaceRenamed: ({ name }) => void,
workspaceCreated: ({ name }) => void,
workspaceDeleted: ({ name }) => void,
workspace: any // workspace provider,
browser: any // browser provider
localhost: any // localhost provider
fileManager : any
registry: any // registry
request: {
createWorkspace: () => void,
setWorkspace: (workspaceName: string) => void,
createNewFile: () => void,
uploadFile: (target: EventTarget & HTMLInputElement) => void,
getCurrentWorkspace: () => void
} // api request,
workspaces: any,
registeredMenuItems: MenuItems // menu items
removedMenuItems: MenuItems
initialWorkspace: string,
resetNewFile: () => void,
getWorkspaces: () => string[]
}
plugin: FilePanelType
}
export interface WorkspaceState {
hideRemixdExplorer: boolean
@ -67,6 +45,36 @@ export interface FileType {
child?: File[]
}
export interface FilePanelType extends ViewPlugin {
setWorkspace: ({ name, isLocalhost }, setEvent: boolean) => void,
createWorkspace: (name: string, workspaceTemplateName: string) => void,
renameWorkspace: (oldName: string, newName: string) => void
compileContractForUml: (path: string) => void
workspaceRenamed: ({ name }) => void,
workspaceCreated: ({ name }) => void,
workspaceDeleted: ({ name }) => void,
workspace?: any // workspace provider,
browser?: any // browser provider
localhost?: any // localhost provider
fileManager? : any
appManager: RemixAppManager
registry?: any // registry
pluginApi?: any
request: {
createWorkspace: () => void,
setWorkspace: (workspaceName: string) => void,
createNewFile: () => void,
uploadFile: (target: EventTarget & HTMLInputElement) => void,
getCurrentWorkspace: () => void
} // api request,
workspaces: any,
registeredMenuItems: MenuItems // menu items
removedMenuItems: MenuItems
initialWorkspace: string,
resetNewFile: () => void,
getWorkspaces: () => string[]
}
/* eslint-disable-next-line */
export interface FileExplorerProps {
name: string,
@ -136,6 +144,7 @@ export interface FileExplorerContextMenuProps {
paste?: (destination: string, type: string) => void
copyFileName?: (path: string, type: string) => void
copyPath?: (path: string, type: string) => void
generateUml?: (path: string) => Promise<void>
}
export interface FileExplorerState {

@ -30,7 +30,7 @@ export const contextMenuActions: MenuItems = [{
extension: ['.js', '.ts'],
multiselect: false,
label: ''
}, {
},{
id: 'pushChangesToGist',
name: 'Push changes to gist',
type: ['gist'],

@ -151,6 +151,7 @@
"core-js": "^3.6.5",
"deep-equal": "^1.0.1",
"document-register-element": "1.13.1",
"dom-to-pdf": "^0.3.1",
"eslint-config-prettier": "^8.5.0",
"ethers": "^5.4.2",
"ethjs-util": "^0.1.6",
@ -187,9 +188,11 @@
"react-multi-carousel": "^2.8.2",
"react-router-dom": "^6.3.0",
"react-tabs": "^3.2.2",
"react-zoom-pan-pinch": "^2.2.0",
"regenerator-runtime": "0.13.7",
"rss-parser": "^3.12.0",
"signale": "^1.4.0",
"sol2uml": "^2.4.3",
"string-similarity": "^4.0.4",
"swarmgw": "^0.3.1",
"time-stamp": "^2.2.0",
@ -269,10 +272,10 @@
"browserify-reload": "^1.0.3",
"browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"chai": "^4.3.7",
"child_process": "^1.0.2",
"colors": "^1.4.0",
"colors-browserify": "^0.1.1",
"chai": "^4.3.7",
"component-type": "^1.2.1",
"constants-browserify": "^1.0.0",
"copy-to-clipboard": "^3.3.1",
@ -352,4 +355,4 @@
"resolutions": {
"@types/react": "^17.0.24"
}
}
}

@ -145,6 +145,9 @@
"@remix-ui/locale-module": [
"libs/remix-ui/locale-module/src/index.ts"
],
"@remix-ui/solidity-uml-gen": [
"libs/remix-ui/solidity-uml-gen/src/index.ts"
],
"@remix-project/ghaction-helper": [
"libs/ghaction-helper/src/index.ts"
]

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save