commit
b9c5eea1e2
@ -0,0 +1,66 @@ |
||||
'use strict' |
||||
import { NightwatchBrowser } from 'nightwatch' |
||||
import init from '../helpers/init' |
||||
|
||||
const testData = { |
||||
validURL: 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol', |
||||
invalidURL: 'https://github.com/Oppelin/Roles.sol' |
||||
} |
||||
|
||||
module.exports = { |
||||
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||
init(browser, done) |
||||
}, |
||||
|
||||
'Import from GitHub Modal': function (browser: NightwatchBrowser) { |
||||
browser.clickLaunchIcon('home') |
||||
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) |
||||
.clickLaunchIcon('filePanel') |
||||
.click('div[title="home"]') |
||||
.waitForElementVisible('button[data-id="landingPageImportFromGitHubButton"]') |
||||
.pause(1000) |
||||
.scrollAndClick('button[data-id="landingPageImportFromGitHubButton"]') |
||||
.waitForElementVisible('*[data-id="homeTabModalDialogModalTitle-react"]') |
||||
.assert.containsText('*[data-id="homeTabModalDialogModalTitle-react"]', 'Import from Github') |
||||
.waitForElementVisible('*[data-id="homeTabModalDialogModalBody-react"]') |
||||
.assert.containsText('*[data-id="homeTabModalDialogModalBody-react"]', 'Enter the github URL you would like to load.') |
||||
.waitForElementVisible('*[data-id="homeTabModalDialogCustomPromptText"]') |
||||
.refresh() |
||||
}, |
||||
|
||||
'Display Error Message For Invalid GitHub URL Modal': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) |
||||
.clickLaunchIcon('settings') |
||||
.clickLaunchIcon('filePanel') |
||||
.scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]') |
||||
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]') |
||||
.execute(() => { |
||||
(document.querySelector('input[data-id="homeTabModalDialogCustomPromptText"]') as any).focus() |
||||
}, [], () => {}) |
||||
.setValue('input[data-id="homeTabModalDialogCustomPromptText"]', testData.invalidURL) |
||||
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]') |
||||
.scrollAndClick('[data-id="homeTab-modal-footer-ok-react"]') // submitted
|
||||
.waitForElementVisible('*[data-shared="tooltipPopup"]') |
||||
.assert.containsText('*[data-shared="tooltipPopup"] span', 'not found ' + testData.invalidURL) |
||||
}, |
||||
|
||||
'Import From Github For Valid URL': function (browser: NightwatchBrowser) { |
||||
browser |
||||
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000) |
||||
.clickLaunchIcon('settings') |
||||
.clickLaunchIcon('filePanel') |
||||
.scrollAndClick('*[data-id="landingPageImportFromGitHubButton"]') |
||||
.waitForElementVisible('*[data-id="homeTabModalDialogCustomPromptText"]') |
||||
.clearValue('*[data-id="homeTabModalDialogCustomPromptText"]') |
||||
.execute(() => { |
||||
(document.querySelector('input[data-id="homeTabModalDialogCustomPromptText"]') as any).focus() |
||||
}, [], () => {}) |
||||
.setValue('input[data-id="homeTabModalDialogCustomPromptText"]', testData.validURL) |
||||
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]') |
||||
.scrollAndClick('[data-id="homeTab-modal-footer-ok-react"]') |
||||
.openFile('github/OpenZeppelin/openzeppelin-solidity/contracts/access/Roles.sol') |
||||
.waitForElementVisible("div[title='default_workspace/github/OpenZeppelin/openzeppelin-solidity/contracts/access/Roles.sol'") |
||||
.end() |
||||
} |
||||
} |
@ -1,194 +0,0 @@ |
||||
'use strict' |
||||
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
||||
const yo = require('yo-yo') |
||||
const globalRegistry = require('../../global/registry') |
||||
|
||||
const css = require('./styles/contextView-styles') |
||||
|
||||
/* |
||||
Display information about the current focused code: |
||||
- if it's a reference, display information about the declaration |
||||
- jump to the declaration |
||||
- number of references |
||||
- rename declaration/references |
||||
*/ |
||||
class ContextView { |
||||
constructor (opts, localRegistry) { |
||||
this._components = {} |
||||
this._components.registry = localRegistry || globalRegistry |
||||
this.contextualListener = opts.contextualListener |
||||
this.editor = opts.editor |
||||
this._deps = { |
||||
compilersArtefacts: this._components.registry.get('compilersartefacts').api, |
||||
offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api, |
||||
config: this._components.registry.get('config').api, |
||||
fileManager: this._components.registry.get('filemanager').api |
||||
} |
||||
this._view = null |
||||
this._nodes = null |
||||
this._current = null |
||||
this.sourceMappingDecoder = sourceMappingDecoder |
||||
this.previousElement = null |
||||
this.contextualListener.event.register('contextChanged', nodes => { |
||||
this.show() |
||||
this._nodes = nodes |
||||
this.update() |
||||
}) |
||||
this.contextualListener.event.register('stopHighlighting', () => { |
||||
}) |
||||
} |
||||
|
||||
render () { |
||||
const view = yo` |
||||
<div class="${css.contextview} ${css.contextviewcontainer} bg-light text-dark border-0"> |
||||
<div class=${css.container}> |
||||
${this._renderTarget()} |
||||
</div> |
||||
</div>` |
||||
if (!this._view) { |
||||
this._view = view |
||||
} |
||||
return view |
||||
} |
||||
|
||||
hide () { |
||||
if (this._view) { |
||||
this._view.style.display = 'none' |
||||
} |
||||
} |
||||
|
||||
show () { |
||||
if (this._view) { |
||||
this._view.style.display = 'block' |
||||
} |
||||
} |
||||
|
||||
update () { |
||||
if (this._view) { |
||||
yo.update(this._view, this.render()) |
||||
} |
||||
} |
||||
|
||||
_renderTarget () { |
||||
let last |
||||
const previous = this._current |
||||
if (this._nodes && this._nodes.length) { |
||||
last = this._nodes[this._nodes.length - 1] |
||||
if (isDefinition(last)) { |
||||
this._current = last |
||||
} else { |
||||
const target = this.contextualListener.declarationOf(last) |
||||
if (target) { |
||||
this._current = target |
||||
} else { |
||||
this._current = null |
||||
} |
||||
} |
||||
} |
||||
if (!this._current || !previous || previous.id !== this._current.id || (this.previousElement && !this.previousElement.children.length)) { |
||||
this.previousElement = this._render(this._current, last) |
||||
} |
||||
return this.previousElement |
||||
} |
||||
|
||||
_jumpToInternal (position) { |
||||
const jumpToLine = (lineColumn) => { |
||||
if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) { |
||||
this.editor.gotoLine(lineColumn.start.line, lineColumn.end.column + 1) |
||||
} |
||||
} |
||||
const lastCompilationResult = this._deps.compilersArtefacts.__last |
||||
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) { |
||||
const lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn( |
||||
position, |
||||
position.file, |
||||
lastCompilationResult.getSourceCode().sources, |
||||
lastCompilationResult.getAsts()) |
||||
const filename = lastCompilationResult.getSourceName(position.file) |
||||
// TODO: refactor with rendererAPI.errorClick
|
||||
if (filename !== this._deps.config.get('currentFile')) { |
||||
const provider = this._deps.fileManager.fileProviderOf(filename) |
||||
if (provider) { |
||||
provider.exists(filename).then(exist => { |
||||
this._deps.fileManager.open(filename) |
||||
jumpToLine(lineColumn) |
||||
}).catch(error => { |
||||
if (error) return console.log(error) |
||||
}) |
||||
} |
||||
} else { |
||||
jumpToLine(lineColumn) |
||||
} |
||||
} |
||||
} |
||||
|
||||
_render (node, nodeAtCursorPosition) { |
||||
if (!node) return yo`<div></div>` |
||||
let references = this.contextualListener.referencesOf(node) |
||||
const type = node.typeDescriptions && node.typeDescriptions.typeString ? node.typeDescriptions.typeString : node.nodeType |
||||
references = `${references ? references.length : '0'} reference(s)` |
||||
|
||||
let ref = 0 |
||||
const nodes = this.contextualListener.getActiveHighlights() |
||||
for (const k in nodes) { |
||||
if (nodeAtCursorPosition.id === nodes[k].nodeId) { |
||||
ref = k |
||||
break |
||||
} |
||||
} |
||||
|
||||
// JUMP BETWEEN REFERENCES
|
||||
const jump = (e) => { |
||||
e.target.dataset.action === 'next' ? ref++ : ref-- |
||||
if (ref < 0) ref = nodes.length - 1 |
||||
if (ref >= nodes.length) ref = 0 |
||||
this._jumpToInternal(nodes[ref].position) |
||||
} |
||||
|
||||
const jumpTo = () => { |
||||
if (node && node.src) { |
||||
const position = this.sourceMappingDecoder.decode(node.src) |
||||
if (position) { |
||||
this._jumpToInternal(position) |
||||
} |
||||
} |
||||
} |
||||
|
||||
const showGasEstimation = () => { |
||||
if (node.nodeType === 'FunctionDefinition') { |
||||
const result = this.contextualListener.gasEstimation(node) |
||||
const executionCost = ' Execution cost: ' + result.executionCost + ' gas' |
||||
const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas' |
||||
const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}` |
||||
return yo` |
||||
<div class=${css.gasEstimation}> |
||||
<i class="fas fa-gas-pump ${css.gasStationIcon}" title='Gas estimation'></i> |
||||
<span>${estimatedGas}</span> |
||||
</div> |
||||
` |
||||
} |
||||
} |
||||
|
||||
return yo` |
||||
<div class=${css.line}>${showGasEstimation()} |
||||
<div title=${type} class=${css.type}>${type}</div> |
||||
<div title=${node.name} class=${css.name}>${node.name}</div> |
||||
<i class="fas fa-share ${css.jump}" aria-hidden="true" onclick=${jumpTo}></i> |
||||
<span class=${css.referencesnb}>${references}</span> |
||||
<i data-action='previous' class="fas fa-chevron-up ${css.jump}" aria-hidden="true" onclick=${jump}></i> |
||||
<i data-action='next' class="fas fa-chevron-down ${css.jump}" aria-hidden="true" onclick=${jump}></i> |
||||
</div> |
||||
` |
||||
} |
||||
} |
||||
|
||||
function isDefinition (node) { |
||||
return node.nodeType === 'ContractDefinition' || |
||||
node.nodeType === 'FunctionDefinition' || |
||||
node.nodeType === 'ModifierDefinition' || |
||||
node.nodeType === 'VariableDeclaration' || |
||||
node.nodeType === 'StructDefinition' || |
||||
node.nodeType === 'EventDefinition' |
||||
} |
||||
|
||||
module.exports = ContextView |
Before Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,4 @@ |
||||
{ |
||||
"presets": ["@nrwl/react/babel"], |
||||
"plugins": [] |
||||
} |
@ -0,0 +1,19 @@ |
||||
{ |
||||
"env": { |
||||
"browser": true, |
||||
"es6": true |
||||
}, |
||||
"extends": "../../../.eslintrc", |
||||
"globals": { |
||||
"Atomics": "readonly", |
||||
"SharedArrayBuffer": "readonly" |
||||
}, |
||||
"parserOptions": { |
||||
"ecmaVersion": 11, |
||||
"sourceType": "module" |
||||
}, |
||||
"rules": { |
||||
"no-unused-vars": "off", |
||||
"@typescript-eslint/no-unused-vars": "error" |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
# remix-ui-editor-context-view |
||||
|
||||
This library was generated with [Nx](https://nx.dev). |
||||
|
||||
## Running unit tests |
||||
|
||||
Run `nx test remix-ui-editor-context-view` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1 @@ |
||||
export * from './lib/remix-ui-editor-context-view'; |
@ -0,0 +1,191 @@ |
||||
import React, { useEffect, useState, useRef } from 'react' // eslint-disable-line
|
||||
import { sourceMappingDecoder } from '@remix-project/remix-debug' |
||||
|
||||
import './remix-ui-editor-context-view.css' |
||||
|
||||
/* eslint-disable-next-line */ |
||||
|
||||
export type astNode = { |
||||
name: string, |
||||
id: number, |
||||
children: Array<any>, |
||||
typeDescriptions: any, |
||||
nodeType: String, |
||||
src: any, |
||||
nodeId: any, |
||||
position: any |
||||
} |
||||
|
||||
export type onContextListenerChangedListener = (nodes: Array<astNode>) => void |
||||
|
||||
export type gasEstimationType = { |
||||
executionCost: string, |
||||
codeDepositCost: string |
||||
} |
||||
export interface RemixUiEditorContextViewProps { |
||||
hide: boolean, |
||||
gotoLine: (line: number, column: number) => void, |
||||
openFile: (fileName: string) => void, |
||||
getLastCompilationResult: () => any, |
||||
offsetToLineColumn: (position: any, file: any, sources: any, asts: any) => any, |
||||
getCurrentFileName: () => String |
||||
onContextListenerChanged: (listener: onContextListenerChangedListener) => void |
||||
referencesOf: (nodes: astNode) => Array<astNode> |
||||
getActiveHighlights: () => Array<astNode> |
||||
gasEstimation: (node: astNode) => gasEstimationType |
||||
declarationOf: (node: astNode) => astNode |
||||
} |
||||
|
||||
function isDefinition (node: any) { |
||||
return node.nodeType === 'ContractDefinition' || |
||||
node.nodeType === 'FunctionDefinition' || |
||||
node.nodeType === 'ModifierDefinition' || |
||||
node.nodeType === 'VariableDeclaration' || |
||||
node.nodeType === 'StructDefinition' || |
||||
node.nodeType === 'EventDefinition' |
||||
} |
||||
|
||||
|
||||
|
||||
type nullableAstNode = astNode | null |
||||
|
||||
export function RemixUiEditorContextView (props: RemixUiEditorContextViewProps) { |
||||
/* |
||||
gotoLineDisableRef is used to temporarily disable the update of the view. |
||||
e.g when the user ask the component to "gotoLine" we don't want to rerender the component (but just to put the mouse on the desired line) |
||||
*/ |
||||
const gotoLineDisableRef = useRef(false) |
||||
const [state, setState] = useState<{ |
||||
nodes: Array<astNode>, |
||||
references: Array<astNode>, |
||||
activeHighlights: Array<any> |
||||
currentNode: nullableAstNode, |
||||
gasEstimation: gasEstimationType |
||||
}>({ |
||||
nodes: [], |
||||
references: [], |
||||
activeHighlights: [], |
||||
currentNode: null, |
||||
gasEstimation: { executionCost: '', codeDepositCost: '' } |
||||
}) |
||||
|
||||
useEffect(() => { |
||||
props.onContextListenerChanged(async (nodes: Array<astNode>) => { |
||||
if (gotoLineDisableRef.current) { |
||||
gotoLineDisableRef.current = false |
||||
return |
||||
} |
||||
let currentNode |
||||
if (!props.hide && nodes && nodes.length) { |
||||
currentNode = nodes[nodes.length - 1] |
||||
if (!isDefinition(currentNode)) { |
||||
currentNode = await props.declarationOf(currentNode) |
||||
} |
||||
} |
||||
let references |
||||
let gasEstimation |
||||
if (currentNode) { |
||||
references = await props.referencesOf(currentNode) |
||||
if (currentNode.nodeType === 'FunctionDefinition') { |
||||
gasEstimation = await props.gasEstimation(currentNode) |
||||
}
|
||||
} |
||||
let activeHighlights = await props.getActiveHighlights() |
||||
setState(prevState => { |
||||
return { ...prevState, nodes, references, activeHighlights, currentNode, gasEstimation } |
||||
}) |
||||
}) |
||||
}, []) |
||||
|
||||
/* |
||||
* show gas estimation |
||||
*/ |
||||
const gasEstimation = (node) => { |
||||
if (node.nodeType === 'FunctionDefinition') { |
||||
const result: gasEstimationType = state.gasEstimation |
||||
const executionCost = ' Execution cost: ' + result.executionCost + ' gas' |
||||
const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas' |
||||
const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}` |
||||
return ( |
||||
<div className="gasEstimation"> |
||||
<i className="fas fa-gas-pump gasStationIcon" title='Gas estimation'></i> |
||||
<span>{estimatedGas}</span> |
||||
</div> |
||||
) |
||||
} else { |
||||
return (<div></div>) |
||||
} |
||||
} |
||||
|
||||
/* |
||||
* onClick jump to ast node in the editor |
||||
*/ |
||||
const _jumpToInternal = async (position: any) => { |
||||
const jumpToLine = async (fileName: string, lineColumn: any) => { |
||||
if (fileName !== await props.getCurrentFileName()) { |
||||
await props.openFile(fileName) |
||||
} |
||||
if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) { |
||||
gotoLineDisableRef.current = true |
||||
props.gotoLine(lineColumn.start.line, lineColumn.end.column + 1) |
||||
} |
||||
} |
||||
const lastCompilationResult = await props.getLastCompilationResult() |
||||
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) { |
||||
const lineColumn = await props.offsetToLineColumn( |
||||
position, |
||||
position.file, |
||||
lastCompilationResult.getSourceCode().sources, |
||||
lastCompilationResult.getAsts()) |
||||
const filename = lastCompilationResult.getSourceName(position.file) |
||||
// TODO: refactor with rendererAPI.errorClick
|
||||
jumpToLine(filename, lineColumn) |
||||
} |
||||
} |
||||
|
||||
const _render = (node: nullableAstNode) => { |
||||
if (!node) return (<div></div>) |
||||
const references = state.references |
||||
const type = node.typeDescriptions && node.typeDescriptions.typeString ? node.typeDescriptions.typeString : node.nodeType |
||||
const referencesCount = `${references ? references.length : '0'} reference(s)` |
||||
|
||||
let ref = 0 |
||||
const nodes: Array<astNode> = state.activeHighlights |
||||
|
||||
const jumpTo = () => { |
||||
if (node && node.src) { |
||||
const position = sourceMappingDecoder.decode(node.src) |
||||
if (position) { |
||||
_jumpToInternal(position) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// JUMP BETWEEN REFERENCES
|
||||
const jump = (e: any) => { |
||||
e.target.dataset.action === 'next' ? ref++ : ref-- |
||||
if (ref < 0) ref = nodes.length - 1 |
||||
if (ref >= nodes.length) ref = 0 |
||||
_jumpToInternal(nodes[ref].position) |
||||
} |
||||
|
||||
return ( |
||||
<div className="line">{gasEstimation(node)} |
||||
<div title={type} className="type">{type}</div> |
||||
<div title={node.name} className="name mr-2">{node.name}</div> |
||||
<i className="fas fa-share jump" data-action='gotoref' aria-hidden="true" onClick={jumpTo}></i> |
||||
<span className="referencesnb">{referencesCount}</span> |
||||
<i data-action='previous' className="fas fa-chevron-up jump" aria-hidden="true" onClick={jump}></i> |
||||
<i data-action='next' className="fas fa-chevron-down jump" aria-hidden="true" onClick={jump}></i> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
return ( |
||||
!props.hide && <div className="container-context-view contextviewcontainer bg-light text-dark border-0 py-1"> |
||||
{_render(state.currentNode)} |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
export default RemixUiEditorContextView |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"extends": "../../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"jsx": "react", |
||||
"allowJs": true, |
||||
"esModuleInterop": true, |
||||
"allowSyntheticDefaultImports": true |
||||
}, |
||||
"files": [], |
||||
"include": [], |
||||
"references": [ |
||||
{ |
||||
"path": "./tsconfig.lib.json" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,13 @@ |
||||
{ |
||||
"extends": "./tsconfig.json", |
||||
"compilerOptions": { |
||||
"outDir": "../../../dist/out-tsc", |
||||
"types": ["node"] |
||||
}, |
||||
"files": [ |
||||
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||
"../../../node_modules/@nrwl/react/typings/image.d.ts" |
||||
], |
||||
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||
} |
@ -0,0 +1,133 @@ |
||||
/* eslint-disable */ |
||||
export const cairoConf = { |
||||
comments: { |
||||
lineComment: '#' |
||||
}, |
||||
brackets: [ |
||||
['{', '}'], |
||||
['[', ']'], |
||||
['(', ')'], |
||||
['%{', '%}'] |
||||
], |
||||
autoClosingPairs: [ |
||||
{ open: '{', close: '}' }, |
||||
{ open: '[', close: ']' }, |
||||
{ open: '(', close: ')' }, |
||||
{ open: '%{', close: '%}' }, |
||||
{ open: "'", close: "'", notIn: ['string', 'comment'] } |
||||
], |
||||
surroundingPairs: [ |
||||
{ open: '{', close: '}' }, |
||||
{ open: '[', close: ']' }, |
||||
{ open: '(', close: ')' }, |
||||
{ open: '%{', close: '%}' }, |
||||
{ open: "'", close: "'" } |
||||
] |
||||
} |
||||
|
||||
|
||||
export const cairoLang = { |
||||
defaultToken: '', |
||||
tokenPostfix: '.cairo', |
||||
|
||||
brackets: [ |
||||
{ token: 'delimiter.curly', open: '{', close: '}' }, |
||||
{ token: 'delimiter.parenthesis', open: '(', close: ')' }, |
||||
{ token: 'delimiter.square', open: '[', close: ']' }, |
||||
{ token: 'delimiter.curly', open: '%{', close: '%}' } |
||||
], |
||||
|
||||
keywords: [ |
||||
// control
|
||||
'if', |
||||
'else', |
||||
'end', |
||||
|
||||
// meta
|
||||
'alloc_locals', |
||||
'as', |
||||
'assert', |
||||
'cast', |
||||
'const', |
||||
'dw', |
||||
'felt', |
||||
'from', |
||||
'func', |
||||
'import', |
||||
'let', |
||||
'local', |
||||
'member', |
||||
'nondet', |
||||
'return', |
||||
'static_assert', |
||||
'struct', |
||||
'tempvar', |
||||
'with_attr', |
||||
'with', |
||||
|
||||
// register
|
||||
'ap', |
||||
'fp', |
||||
|
||||
// opcode
|
||||
'call', |
||||
'jmp', |
||||
'ret', |
||||
'abs', |
||||
'rel' |
||||
], |
||||
|
||||
operators: ['=', ':', '==', '++', '+', '-', '*', '**', '/', '&', '%', '_'], |
||||
|
||||
// we include these common regular expressions
|
||||
symbols: /[=><!~?:&|+\-*\/\^%]+/, |
||||
numberDecimal: /[+-]?[0-9]+/, |
||||
numberHex: /[+-]?0x[0-9a-fA-F]+/, |
||||
|
||||
// The main tokenizer for our languages
|
||||
tokenizer: { |
||||
root: [ |
||||
// identifiers and keywords
|
||||
[ |
||||
/[a-zA-Z_]\w*/, |
||||
{ |
||||
cases: { |
||||
'@keywords': { token: 'keyword.$0' }, |
||||
'@default': 'identifier' |
||||
} |
||||
} |
||||
], |
||||
|
||||
// whitespace
|
||||
{ include: '@whitespace' }, |
||||
|
||||
// directives
|
||||
[/^%[a-zA-Z]\w*/, 'tag'], |
||||
|
||||
// delimiters and operators
|
||||
[/[{}()\[\]]/, '@brackets'], |
||||
[/[<>](?!@symbols)/, '@brackets'], |
||||
[ |
||||
/@symbols/, |
||||
{ |
||||
cases: { |
||||
'@operators': 'delimiter', |
||||
'@default': '' |
||||
} |
||||
} |
||||
], |
||||
|
||||
// numbers
|
||||
[/(@numberHex)/, 'number.hex'], |
||||
[/(@numberDecimal)/, 'number'], |
||||
|
||||
// strings
|
||||
[/'[^']*'/, 'string'] |
||||
], |
||||
|
||||
whitespace: [ |
||||
[/\s+/, 'white'], |
||||
[/(^#.*$)/, 'comment'] |
||||
] |
||||
} |
||||
} |
@ -0,0 +1,4 @@ |
||||
{ |
||||
"presets": ["@nrwl/react/babel"], |
||||
"plugins": [] |
||||
} |
@ -0,0 +1,248 @@ |
||||
{ |
||||
"rules": { |
||||
"array-callback-return": "warn", |
||||
"dot-location": ["warn", "property"], |
||||
"eqeqeq": ["warn", "smart"], |
||||
"new-parens": "warn", |
||||
"no-caller": "warn", |
||||
"no-cond-assign": ["warn", "except-parens"], |
||||
"no-const-assign": "warn", |
||||
"no-control-regex": "warn", |
||||
"no-delete-var": "warn", |
||||
"no-dupe-args": "warn", |
||||
"no-dupe-keys": "warn", |
||||
"no-duplicate-case": "warn", |
||||
"no-empty-character-class": "warn", |
||||
"no-empty-pattern": "warn", |
||||
"no-eval": "warn", |
||||
"no-ex-assign": "warn", |
||||
"no-extend-native": "warn", |
||||
"no-extra-bind": "warn", |
||||
"no-extra-label": "warn", |
||||
"no-fallthrough": "warn", |
||||
"no-func-assign": "warn", |
||||
"no-implied-eval": "warn", |
||||
"no-invalid-regexp": "warn", |
||||
"no-iterator": "warn", |
||||
"no-label-var": "warn", |
||||
"no-labels": ["warn", { "allowLoop": true, "allowSwitch": false }], |
||||
"no-lone-blocks": "warn", |
||||
"no-loop-func": "warn", |
||||
"no-mixed-operators": [ |
||||
"warn", |
||||
{ |
||||
"groups": [ |
||||
["&", "|", "^", "~", "<<", ">>", ">>>"], |
||||
["==", "!=", "===", "!==", ">", ">=", "<", "<="], |
||||
["&&", "||"], |
||||
["in", "instanceof"] |
||||
], |
||||
"allowSamePrecedence": false |
||||
} |
||||
], |
||||
"no-multi-str": "warn", |
||||
"no-native-reassign": "warn", |
||||
"no-negated-in-lhs": "warn", |
||||
"no-new-func": "warn", |
||||
"no-new-object": "warn", |
||||
"no-new-symbol": "warn", |
||||
"no-new-wrappers": "warn", |
||||
"no-obj-calls": "warn", |
||||
"no-octal": "warn", |
||||
"no-octal-escape": "warn", |
||||
"no-redeclare": "warn", |
||||
"no-regex-spaces": "warn", |
||||
"no-restricted-syntax": ["warn", "WithStatement"], |
||||
"no-script-url": "warn", |
||||
"no-self-assign": "warn", |
||||
"no-self-compare": "warn", |
||||
"no-sequences": "warn", |
||||
"no-shadow-restricted-names": "warn", |
||||
"no-sparse-arrays": "warn", |
||||
"no-template-curly-in-string": "warn", |
||||
"no-this-before-super": "warn", |
||||
"no-throw-literal": "warn", |
||||
"no-restricted-globals": [ |
||||
"error", |
||||
"addEventListener", |
||||
"blur", |
||||
"close", |
||||
"closed", |
||||
"confirm", |
||||
"defaultStatus", |
||||
"defaultstatus", |
||||
"event", |
||||
"external", |
||||
"find", |
||||
"focus", |
||||
"frameElement", |
||||
"frames", |
||||
"history", |
||||
"innerHeight", |
||||
"innerWidth", |
||||
"length", |
||||
"location", |
||||
"locationbar", |
||||
"menubar", |
||||
"moveBy", |
||||
"moveTo", |
||||
"name", |
||||
"onblur", |
||||
"onerror", |
||||
"onfocus", |
||||
"onload", |
||||
"onresize", |
||||
"onunload", |
||||
"open", |
||||
"opener", |
||||
"opera", |
||||
"outerHeight", |
||||
"outerWidth", |
||||
"pageXOffset", |
||||
"pageYOffset", |
||||
"parent", |
||||
"print", |
||||
"removeEventListener", |
||||
"resizeBy", |
||||
"resizeTo", |
||||
"screen", |
||||
"screenLeft", |
||||
"screenTop", |
||||
"screenX", |
||||
"screenY", |
||||
"scroll", |
||||
"scrollbars", |
||||
"scrollBy", |
||||
"scrollTo", |
||||
"scrollX", |
||||
"scrollY", |
||||
"self", |
||||
"status", |
||||
"statusbar", |
||||
"stop", |
||||
"toolbar", |
||||
"top" |
||||
], |
||||
"no-unexpected-multiline": "warn", |
||||
"no-unreachable": "warn", |
||||
"no-unused-expressions": [ |
||||
"error", |
||||
{ |
||||
"allowShortCircuit": true, |
||||
"allowTernary": true, |
||||
"allowTaggedTemplates": true |
||||
} |
||||
], |
||||
"no-unused-labels": "warn", |
||||
"no-useless-computed-key": "warn", |
||||
"no-useless-concat": "warn", |
||||
"no-useless-escape": "warn", |
||||
"no-useless-rename": [ |
||||
"warn", |
||||
{ |
||||
"ignoreDestructuring": false, |
||||
"ignoreImport": false, |
||||
"ignoreExport": false |
||||
} |
||||
], |
||||
"no-with": "warn", |
||||
"no-whitespace-before-property": "warn", |
||||
"react-hooks/exhaustive-deps": "warn", |
||||
"require-yield": "warn", |
||||
"rest-spread-spacing": ["warn", "never"], |
||||
"strict": ["warn", "never"], |
||||
"unicode-bom": ["warn", "never"], |
||||
"use-isnan": "warn", |
||||
"valid-typeof": "warn", |
||||
"no-restricted-properties": [ |
||||
"error", |
||||
{ |
||||
"object": "require", |
||||
"property": "ensure", |
||||
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting" |
||||
}, |
||||
{ |
||||
"object": "System", |
||||
"property": "import", |
||||
"message": "Please use import() instead. More info: https://facebook.github.io/create-react-app/docs/code-splitting" |
||||
} |
||||
], |
||||
"getter-return": "warn", |
||||
"import/first": "error", |
||||
"import/no-amd": "error", |
||||
"import/no-webpack-loader-syntax": "error", |
||||
"react/forbid-foreign-prop-types": ["warn", { "allowInPropTypes": true }], |
||||
"react/jsx-no-comment-textnodes": "warn", |
||||
"react/jsx-no-duplicate-props": "warn", |
||||
"react/jsx-no-target-blank": "warn", |
||||
"react/jsx-no-undef": "error", |
||||
"react/jsx-pascal-case": ["warn", { "allowAllCaps": true, "ignore": [] }], |
||||
"react/jsx-uses-react": "warn", |
||||
"react/jsx-uses-vars": "warn", |
||||
"react/no-danger-with-children": "warn", |
||||
"react/no-direct-mutation-state": "warn", |
||||
"react/no-is-mounted": "warn", |
||||
"react/no-typos": "error", |
||||
"react/react-in-jsx-scope": "error", |
||||
"react/require-render-return": "error", |
||||
"react/style-prop-object": "warn", |
||||
"react/jsx-no-useless-fragment": "warn", |
||||
"jsx-a11y/accessible-emoji": "warn", |
||||
"jsx-a11y/alt-text": "warn", |
||||
"jsx-a11y/anchor-has-content": "warn", |
||||
"jsx-a11y/anchor-is-valid": [ |
||||
"warn", |
||||
{ "aspects": ["noHref", "invalidHref"] } |
||||
], |
||||
"jsx-a11y/aria-activedescendant-has-tabindex": "warn", |
||||
"jsx-a11y/aria-props": "warn", |
||||
"jsx-a11y/aria-proptypes": "warn", |
||||
"jsx-a11y/aria-role": "warn", |
||||
"jsx-a11y/aria-unsupported-elements": "warn", |
||||
"jsx-a11y/heading-has-content": "warn", |
||||
"jsx-a11y/iframe-has-title": "warn", |
||||
"jsx-a11y/img-redundant-alt": "warn", |
||||
"jsx-a11y/no-access-key": "warn", |
||||
"jsx-a11y/no-distracting-elements": "warn", |
||||
"jsx-a11y/no-redundant-roles": "warn", |
||||
"jsx-a11y/role-has-required-aria-props": "warn", |
||||
"jsx-a11y/role-supports-aria-props": "warn", |
||||
"jsx-a11y/scope": "warn", |
||||
"react-hooks/rules-of-hooks": "error", |
||||
"default-case": "off", |
||||
"no-dupe-class-members": "off", |
||||
"no-undef": "off", |
||||
"@typescript-eslint/consistent-type-assertions": "warn", |
||||
"no-array-constructor": "off", |
||||
"@typescript-eslint/no-array-constructor": "warn", |
||||
"@typescript-eslint/no-namespace": "error", |
||||
"no-use-before-define": "off", |
||||
"@typescript-eslint/no-use-before-define": [ |
||||
"warn", |
||||
{ |
||||
"functions": false, |
||||
"classes": false, |
||||
"variables": false, |
||||
"typedefs": false |
||||
} |
||||
], |
||||
"no-unused-vars": "off", |
||||
"@typescript-eslint/no-unused-vars": [ |
||||
"warn", |
||||
{ "args": "none", "ignoreRestSiblings": true } |
||||
], |
||||
"no-useless-constructor": "off", |
||||
"@typescript-eslint/no-useless-constructor": "warn" |
||||
}, |
||||
"env": { |
||||
"browser": true, |
||||
"commonjs": true, |
||||
"es6": true, |
||||
"jest": true, |
||||
"node": true |
||||
}, |
||||
"settings": { "react": { "version": "detect" } }, |
||||
"plugins": ["import", "jsx-a11y", "react", "react-hooks"], |
||||
"extends": ["../../../.eslintrc"], |
||||
"ignorePatterns": ["!**/*"] |
||||
} |
@ -0,0 +1,7 @@ |
||||
# remix-ui-home-tab |
||||
|
||||
This library was generated with [Nx](https://nx.dev). |
||||
|
||||
## Running unit tests |
||||
|
||||
Run `nx test remix-ui-home-tab` to execute the unit tests via [Jest](https://jestjs.io). |
@ -0,0 +1 @@ |
||||
export * from './lib/remix-ui-home-tab' |
@ -0,0 +1,27 @@ |
||||
/* eslint-disable @typescript-eslint/no-unused-vars */ |
||||
import React, { useContext } from 'react' |
||||
import { ThemeContext } from '../themeContext' |
||||
|
||||
interface PluginButtonProps { |
||||
imgPath: string, |
||||
envID: string, |
||||
envText: string, |
||||
callback: any |
||||
} |
||||
|
||||
function PluginButton ({ imgPath, envID, envText, callback }: PluginButtonProps) { |
||||
const themeFilter = useContext(ThemeContext) |
||||
|
||||
return ( |
||||
<button |
||||
className="btn border-secondary d-flex mr-3 text-nowrap justify-content-center flex-column align-items-center remixui_envButton" |
||||
data-id={'landingPageStart' + envText} |
||||
onClick={() => callback()} |
||||
> |
||||
<img className="m-2 align-self-center remixui_envLogo" id={envID} src={imgPath} alt="" style={ { filter: themeFilter.filter } } /> |
||||
<label className="text-uppercase text-dark remixui_cursorStyle">{envText}</label> |
||||
</button> |
||||
) |
||||
} |
||||
|
||||
export default PluginButton |
@ -0,0 +1,82 @@ |
||||
.remixui_text { |
||||
cursor: pointer; |
||||
font-weight: normal; |
||||
max-width: 300px; |
||||
} |
||||
.remixui_text:hover { |
||||
cursor: pointer; |
||||
text-decoration: underline; |
||||
} |
||||
.remixui_homeContainer { |
||||
overflow-y: hidden; |
||||
overflow-y: auto; |
||||
flex-grow: 3; |
||||
} |
||||
.remixui_hpLogoContainer { |
||||
margin: 30px; |
||||
padding-right: 90px; |
||||
} |
||||
.remixui_mediaBadge { |
||||
font-size: 2em; |
||||
height: 2em; |
||||
width: 2em; |
||||
} |
||||
.remixui_mediaBadge:focus { |
||||
outline: none; |
||||
} |
||||
.remixui_image { |
||||
height: 1em; |
||||
width: 1em; |
||||
text-align: center; |
||||
} |
||||
.remixui_logoImg { |
||||
height: 10em; |
||||
} |
||||
.remixui_rightPanel { |
||||
right: 0; |
||||
position: absolute; |
||||
z-index: 3; |
||||
} |
||||
.remixui_remixHomeMedia { |
||||
overflow-y: auto; |
||||
overflow-x: hidden; |
||||
} |
||||
.remixui_panels { |
||||
box-shadow: 0px 0px 13px -7px; |
||||
} |
||||
.remixui_labelIt { |
||||
margin-bottom: 0; |
||||
} |
||||
.remixui_bigLabelSize { |
||||
font-size: 13px; |
||||
} |
||||
.remixui_seeAll { |
||||
margin-top: 7px; |
||||
white-space: nowrap; |
||||
} |
||||
.remixui_importFrom p { |
||||
margin-right: 10px; |
||||
} |
||||
.remixui_logoContainer img{ |
||||
height: 150px; |
||||
opacity: 0.7; |
||||
} |
||||
.remixui_envLogo { |
||||
height: 16px; |
||||
} |
||||
.remixui_cursorStyle { |
||||
cursor: pointer; |
||||
} |
||||
.remixui_envButton { |
||||
width: 120px; |
||||
height: 70px; |
||||
} |
||||
.remixui_media { |
||||
overflow: hidden; |
||||
max-width: 400px; |
||||
transition: .5s ease-out; |
||||
z-index: 1000; |
||||
} |
||||
.remixui_migrationBtn { |
||||
width: 100px; |
||||
} |
@ -0,0 +1,370 @@ |
||||
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
|
||||
|
||||
import './remix-ui-home-tab.css' |
||||
import { ModalDialog } from '@remix-ui/modal-dialog' // eslint-disable-line
|
||||
import { Toaster } from '@remix-ui/toaster' // eslint-disable-line
|
||||
import PluginButton from './components/pluginButton' // eslint-disable-line
|
||||
import QueryParams from '../../../../../apps/remix-ide/src/lib/query-params' |
||||
import { ThemeContext, themes } from './themeContext' |
||||
declare global { |
||||
interface Window { |
||||
_paq: any |
||||
} |
||||
} |
||||
const _paq = window._paq = window._paq || [] //eslint-disable-line
|
||||
|
||||
/* eslint-disable-next-line */ |
||||
export interface RemixUiHomeTabProps { |
||||
plugin: any |
||||
} |
||||
|
||||
const loadingInitialState = { |
||||
tooltip: '', |
||||
showModalDialog: false, |
||||
importSource: '' |
||||
} |
||||
|
||||
const loadingReducer = (state = loadingInitialState, action) => { |
||||
return { ...state, tooltip: action.tooltip, showModalDialog: false, importSource: '' } |
||||
} |
||||
|
||||
export const RemixUiHomeTab = (props: RemixUiHomeTabProps) => { |
||||
const { plugin } = props |
||||
const fileManager = plugin.fileManager |
||||
|
||||
const [state, setState] = useState<{ |
||||
themeQuality: { filter: string, name: string }, |
||||
showMediaPanel: 'none' | 'twitter' | 'medium', |
||||
showModalDialog: boolean, |
||||
modalInfo: { title: string, loadItem: string, examples: Array<string> }, |
||||
importSource: string, |
||||
toasterMsg: string |
||||
}>({ |
||||
themeQuality: themes.light, |
||||
showMediaPanel: 'none', |
||||
showModalDialog: false, |
||||
modalInfo: { title: '', loadItem: '', examples: [] }, |
||||
importSource: '', |
||||
toasterMsg: '' |
||||
}) |
||||
|
||||
const processLoading = () => { |
||||
const contentImport = plugin.contentImport |
||||
const workspace = fileManager.getProvider('workspace') |
||||
contentImport.import( |
||||
state.importSource, |
||||
(loadingMsg) => dispatch({ tooltip: loadingMsg }), |
||||
(error, content, cleanUrl, type, url) => { |
||||
if (error) { |
||||
toast(error.message || error) |
||||
} else { |
||||
try { |
||||
workspace.addExternal(type + '/' + cleanUrl, content, url) |
||||
plugin.call('menuicons', 'select', 'filePanel') |
||||
} catch (e) { |
||||
toast(e.message) |
||||
} |
||||
} |
||||
} |
||||
) |
||||
setState(prevState => { |
||||
return { ...prevState, showModalDialog: false, importSource: '' } |
||||
}) |
||||
} |
||||
|
||||
const [, dispatch] = useReducer(loadingReducer, loadingInitialState) |
||||
|
||||
const playRemi = async () => { |
||||
remiAudioEl.current.play() |
||||
} |
||||
|
||||
const remiAudioEl = useRef(null) |
||||
const inputValue = useRef(null) |
||||
|
||||
useEffect(() => { |
||||
plugin.call('theme', 'currentTheme').then((theme) => { |
||||
// update theme quality. To be used for for images
|
||||
setState(prevState => { |
||||
return { ...prevState, themeQuality: theme.quality === 'dark' ? themes.dark : themes.light } |
||||
}) |
||||
}) |
||||
plugin.on('theme', 'themeChanged', (theme) => { |
||||
// update theme quality. To be used for for images
|
||||
setState(prevState => { |
||||
return { ...prevState, themeQuality: theme.quality === 'dark' ? themes.dark : themes.light } |
||||
}) |
||||
}) |
||||
window.addEventListener('click', (event) => { |
||||
const target = event.target as Element |
||||
const id = target.id |
||||
if (id !== 'remixIDEHomeTwitterbtn' && id !== 'remixIDEHomeMediumbtn') { |
||||
// todo check event.target
|
||||
setState(prevState => { return { ...prevState, showMediaPanel: 'none' } }) |
||||
} |
||||
}) |
||||
// to retrieve twitter feed
|
||||
const scriptTwitter = document.createElement('script') |
||||
scriptTwitter.src = 'https://platform.twitter.com/widgets.js' |
||||
scriptTwitter.async = true |
||||
document.body.appendChild(scriptTwitter) |
||||
// to retrieve medium publications
|
||||
const scriptMedium = document.createElement('script') |
||||
scriptMedium.src = 'https://www.twilik.com/assets/retainable/rss-embed/retainable-rss-embed.js' |
||||
scriptMedium.async = true |
||||
document.body.appendChild(scriptMedium) |
||||
return () => { |
||||
document.body.removeChild(scriptTwitter) |
||||
document.body.removeChild(scriptMedium) |
||||
} |
||||
}, []) |
||||
|
||||
const toast = (message: string) => { |
||||
setState(prevState => { |
||||
return { ...prevState, toasterMsg: message } |
||||
}) |
||||
} |
||||
|
||||
const createNewFile = async () => { |
||||
await plugin.call('filePanel', 'createNewFile') |
||||
} |
||||
|
||||
const uploadFile = async (target) => { |
||||
await plugin.call('filePanel', 'uploadFile', target) |
||||
} |
||||
|
||||
const connectToLocalhost = () => { |
||||
plugin.appManager.activatePlugin('remixd') |
||||
} |
||||
const importFromGist = () => { |
||||
plugin.gistHandler.loadFromGist({ gist: '' }, fileManager) |
||||
plugin.verticalIcons.select('filePanel') |
||||
} |
||||
const switchToPreviousVersion = () => { |
||||
const query = new QueryParams() |
||||
query.update({ appVersion: '0.7.7' }) |
||||
_paq.push(['trackEvent', 'LoadingType', 'oldExperience_0.7.7']) |
||||
document.location.reload() |
||||
} |
||||
const startSolidity = async () => { |
||||
await plugin.appManager.activatePlugin(['solidity', 'udapp', 'solidityStaticAnalysis', 'solidityUnitTesting']) |
||||
plugin.verticalIcons.select('solidity') |
||||
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'solidity']) |
||||
} |
||||
const startOptimism = async () => { |
||||
await plugin.appManager.activatePlugin('optimism-compiler') |
||||
plugin.verticalIcons.select('optimism-compiler') |
||||
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'optimism-compiler']) |
||||
} |
||||
const startSolhint = async () => { |
||||
await plugin.appManager.activatePlugin(['solidity', 'solhint']) |
||||
plugin.verticalIcons.select('solhint') |
||||
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'solhint']) |
||||
} |
||||
const startLearnEth = async () => { |
||||
await plugin.appManager.activatePlugin(['solidity', 'LearnEth', 'solidityUnitTesting']) |
||||
plugin.verticalIcons.select('LearnEth') |
||||
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'learnEth']) |
||||
} |
||||
const startSourceVerify = async () => { |
||||
await plugin.appManager.activatePlugin(['solidity', 'source-verification']) |
||||
plugin.verticalIcons.select('source-verification') |
||||
_paq.push(['trackEvent', 'pluginManager', 'userActivate', 'source-verification']) |
||||
} |
||||
const startPluginManager = async () => { |
||||
await plugin.appManager.activatePlugin('pluginManager') |
||||
plugin.verticalIcons.select('pluginManager') |
||||
} |
||||
|
||||
const showFullMessage = (title: string, loadItem: string, examples: Array<string>) => { |
||||
setState(prevState => { |
||||
return { ...prevState, showModalDialog: true, modalInfo: { title: title, loadItem: loadItem, examples: examples } } |
||||
}) |
||||
} |
||||
|
||||
const hideFullMessage = () => { //eslint-disable-line
|
||||
setState(prevState => { |
||||
return { ...prevState, showModalDialog: false, importSource: '' } |
||||
}) |
||||
} |
||||
|
||||
const maxHeight = Math.max(window.innerHeight - 150, 250) + 'px' |
||||
const examples = state.modalInfo.examples.map((urlEl, key) => (<div key={key} className="p-1 user-select-auto"><a>{urlEl}</a></div>)) |
||||
const elHeight = '4000px' |
||||
return ( |
||||
<> |
||||
<ModalDialog |
||||
id='homeTab' |
||||
title={ 'Import from ' + state.modalInfo.title } |
||||
okLabel='Import' |
||||
hide={ !state.showModalDialog } |
||||
handleHide={ () => hideFullMessage() } |
||||
okFn={ () => processLoading() } |
||||
> |
||||
<div className="p-2 user-select-auto"> |
||||
{ state.modalInfo.loadItem !== '' && <span>Enter the { state.modalInfo.loadItem } you would like to load.</span> } |
||||
{ state.modalInfo.examples.length !== 0 && |
||||
<> |
||||
<div>e.g</div> |
||||
<div> |
||||
{ examples } |
||||
</div> |
||||
</> } |
||||
<input |
||||
ref={inputValue} |
||||
type='text' |
||||
name='prompt_text' |
||||
id='inputPrompt_text' |
||||
className="w-100 mt-1 form-control" |
||||
data-id="homeTabModalDialogCustomPromptText" |
||||
value={state.importSource} |
||||
onInput={(e) => { |
||||
setState(prevState => { |
||||
return { ...prevState, importSource: inputValue.current.value } |
||||
}) |
||||
}} |
||||
/> |
||||
</div> |
||||
</ModalDialog> |
||||
<Toaster message={state.toasterMsg} /> |
||||
<div className="d-flex flex-column ml-4" id="remixUiRightPanel"> |
||||
<div className="border-bottom d-flex justify-content-between mr-4 pb-3 mb-3"> |
||||
<div className="mx-4 my-4 d-flex"> |
||||
<label style={ { fontSize: 'xxx-large', height: 'auto', alignSelf: 'flex-end' } }>Remix IDE</label> |
||||
</div> |
||||
<div className="mr-4 d-flex"> |
||||
<img className="mt-4 mb-2 remixui_logoImg" src="assets/img/guitarRemiCroped.webp" onClick={ () => playRemi() } alt=""></img> |
||||
<audio |
||||
id="remiAudio" |
||||
muted={false} |
||||
src="assets/audio/remiGuitar-single-power-chord-A-minor.wav" |
||||
ref={remiAudioEl} |
||||
></audio> |
||||
</div> |
||||
</div> |
||||
<div className="row remixui_hpSections mx-2 mr-4" data-id="landingPageHpSections"> |
||||
<div className="ml-3"> |
||||
<div className="mb-5"> |
||||
<h4>Featured Plugins</h4> |
||||
<div className="d-flex flex-row pt-2"> |
||||
<ThemeContext.Provider value={ state.themeQuality }> |
||||
<PluginButton imgPath="assets/img/solidityLogo.webp" envID="solidityLogo" envText="Solidity" callback={() => startSolidity()} /> |
||||
<PluginButton imgPath="assets/img/optimismLogo.webp" envID="optimismLogo" envText="Optimism" callback={() => startOptimism()} /> |
||||
<PluginButton imgPath="assets/img/solhintLogo.webp" envID="solhintLogo" envText="Solhint linter" callback={() => startSolhint()} /> |
||||
<PluginButton imgPath="assets/img/learnEthLogo.webp" envID="learnEthLogo" envText="LearnEth" callback={() => startLearnEth()} /> |
||||
<PluginButton imgPath="assets/img/sourcifyLogo.webp" envID="sourcifyLogo" envText="Sourcify" callback={() => startSourceVerify()} /> |
||||
<PluginButton imgPath="assets/img/moreLogo.webp" envID="moreLogo" envText="More" callback={startPluginManager} /> |
||||
</ThemeContext.Provider> |
||||
</div> |
||||
</div> |
||||
<div className="d-flex"> |
||||
<div className="file"> |
||||
<h4>File</h4> |
||||
<p className="mb-1"> |
||||
<i className="mr-2 far fa-file"></i> |
||||
<span className="ml-1 mb-1 remixui_text" onClick={() => createNewFile()}>New File</span> |
||||
</p> |
||||
<p className="mb-1"> |
||||
<i className="mr-2 far fa-file-alt"></i> |
||||
<span className="ml-1 remixui_labelIt remixui_bigLabelSize} remixui_text"> |
||||
Open Files |
||||
<input title="open file" type="file" onChange={(event) => { |
||||
event.stopPropagation() |
||||
uploadFile(event.target) |
||||
}} multiple /> |
||||
</span> |
||||
</p> |
||||
<p className="mb-1"> |
||||
<i className="mr-1 far fa-hdd"></i> |
||||
<span className="ml-1 remixui_text" onClick={() => connectToLocalhost()}>Connect to Localhost</span> |
||||
</p> |
||||
<p className="mt-3 mb-0"><label>LOAD FROM:</label></p> |
||||
<div className="btn-group"> |
||||
<button className="btn mr-1 btn-secondary" data-id="landingPageImportFromGistButton" onClick={() => importFromGist()}>Gist</button> |
||||
<button className="btn mx-1 btn-secondary" data-id="landingPageImportFromGitHubButton" onClick={() => showFullMessage('Github', 'github URL', ['https://github.com/0xcert/ethereum-erc721/src/contracts/tokens/nf-token-metadata.sol', 'https://github.com/OpenZeppelin/openzeppelin-solidity/blob/67bca857eedf99bf44a4b6a0fc5b5ed553135316/contracts/access/Roles.sol'])}>GitHub</button> |
||||
<button className="btn mx-1 btn-secondary" onClick={() => showFullMessage('Ipfs', 'ipfs URL', ['ipfs://<ipfs-hash>'])}>Ipfs</button> |
||||
<button className="btn mx-1 btn-secondary" onClick={() => showFullMessage('Https', 'http/https raw content', ['https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/token/ERC20/ERC20.sol'])}>https</button> |
||||
</div> |
||||
</div> |
||||
<div className="ml-4 pl-4"> |
||||
<h4>Resources</h4> |
||||
<p className="mb-1"> |
||||
<i className="mr-2 fas fa-book"></i> |
||||
<a className="remixui_text" target="__blank" href="https://remix-ide.readthedocs.io/en/latest/#">Documentation</a> |
||||
</p> |
||||
<p className="mb-1"> |
||||
<i className="mr-2 fab fa-gitter"></i> |
||||
<a className="remixui_text" target="__blank" href="https://gitter.im/ethereum/remix">Gitter channel</a> |
||||
</p> |
||||
<p className="mb-1"> |
||||
<img id='remixHhomeWebsite' className="mr-2 remixui_image" src={ plugin.profile.icon } style={ { filter: state.themeQuality.filter } } alt=''></img> |
||||
<a className="remixui_text" target="__blank" href="https://remix-project.org">Featuring website</a> |
||||
</p> |
||||
<p className="mb-1"> |
||||
<i className="mr-2 fab fa-ethereum remixui_image"></i> |
||||
<span className="remixui_text" onClick={() => switchToPreviousVersion()}>Old experience</span> |
||||
</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div className="d-flex flex-column remixui_rightPanel"> |
||||
<div className="d-flex pr-3 py-2 align-self-end" id="remixIDEMediaPanelsTitle"> |
||||
<button |
||||
className="btn-info p-2 m-1 border rounded-circle remixui_mediaBadge fab fa-twitter" |
||||
id="remixIDEHomeTwitterbtn" |
||||
title="Twitter" |
||||
onClick={(e) => { |
||||
setState(prevState => { |
||||
return { ...prevState, showMediaPanel: state.showMediaPanel === 'twitter' ? 'none' : 'twitter' } |
||||
}) |
||||
_paq.push(['trackEvent', 'pluginManager', 'media', 'twitter']) |
||||
}} |
||||
></button> |
||||
<button |
||||
className="btn-danger p-2 m-1 border rounded-circle remixui_mediaBadge fab fa-medium" |
||||
id="remixIDEHomeMediumbtn" |
||||
title="Medium blogs" |
||||
onClick={(e) => { |
||||
setState(prevState => { |
||||
return { ...prevState, showMediaPanel: state.showMediaPanel === 'medium' ? 'none' : 'medium' } |
||||
}) |
||||
_paq.push(['trackEvent', 'pluginManager', 'media', 'medium']) |
||||
}} |
||||
></button> |
||||
</div> |
||||
<div className="mr-3 d-flex bg-light remixui_panels" style={ { visibility: state.showMediaPanel === 'none' ? 'hidden' : 'visible' } } id="remixIDEMediaPanels"> |
||||
<div id="remixIDE_MediumBlock" className="p-2 mx-1 mt-3 mb-0 remixui_remixHomeMedia" style={ { maxHeight: maxHeight } }> |
||||
<div id="medium-widget" className="px-3 remixui_media" hidden={state.showMediaPanel !== 'medium'} style={ { maxHeight: elHeight } }> |
||||
<div |
||||
id="retainable-rss-embed" |
||||
data-rss="https://medium.com/feed/remix-ide" |
||||
data-maxcols="1" |
||||
data-layout="grid" |
||||
data-poststyle="external" |
||||
data-readmore="More..." |
||||
data-buttonclass="btn mb-3" |
||||
data-offset="-100" |
||||
> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div id="remixIDE_TwitterBlock" className="p-2 mx-1 mt-3 mb-0 remixui_remixHomeMedia" hidden={state.showMediaPanel !== 'twitter'} style={ { maxHeight: maxHeight, marginRight: '28px' } } > |
||||
<div className="remixui_media" style={ { minHeight: elHeight } } > |
||||
<a className="twitter-timeline" |
||||
data-width="330" |
||||
data-theme={ state.themeQuality.name } |
||||
data-chrome="nofooter noheader transparent" |
||||
data-tweet-limit="18" |
||||
href="https://twitter.com/EthereumRemix" |
||||
> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</> |
||||
) |
||||
} |
||||
|
||||
export default RemixUiHomeTab |
@ -0,0 +1,16 @@ |
||||
import React from 'react' // eslint-disable-line
|
||||
|
||||
export const themes = { |
||||
light: { |
||||
filter: 'invert(0)', |
||||
name: 'light' |
||||
}, |
||||
dark: { |
||||
filter: 'invert(1)', |
||||
name: 'dark' |
||||
} |
||||
} |
||||
|
||||
export const ThemeContext = React.createContext( |
||||
themes.dark // default value
|
||||
) |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"extends": "../../../tsconfig.base.json", |
||||
"compilerOptions": { |
||||
"jsx": "react", |
||||
"allowJs": true, |
||||
"esModuleInterop": true, |
||||
"allowSyntheticDefaultImports": true |
||||
}, |
||||
"files": [], |
||||
"include": [], |
||||
"references": [ |
||||
{ |
||||
"path": "./tsconfig.lib.json" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,13 @@ |
||||
{ |
||||
"extends": "./tsconfig.json", |
||||
"compilerOptions": { |
||||
"outDir": "../../../dist/out-tsc", |
||||
"types": ["node"] |
||||
}, |
||||
"files": [ |
||||
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts", |
||||
"../../../node_modules/@nrwl/react/typings/image.d.ts" |
||||
], |
||||
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"], |
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] |
||||
} |
@ -0,0 +1,77 @@ |
||||
import React, { useEffect, useState } from 'react' |
||||
|
||||
export const useDragTerminal = (minHeight: number, defaultPosition: number) => { |
||||
const [isOpen, setIsOpen] = useState(defaultPosition > minHeight) |
||||
const [lastYPosition, setLastYPosition] = useState(0) |
||||
const [terminalPosition, setTerminalPosition] = useState(defaultPosition) |
||||
// Used to save position of the terminal when it is closed
|
||||
const [lastTerminalPosition, setLastTerminalPosition] = useState(defaultPosition) |
||||
const [isDragging, setIsDragging] = useState(false) |
||||
|
||||
const handleDraggingStart = (event: React.MouseEvent) => { |
||||
setLastYPosition(event.clientY) |
||||
setIsDragging(true) |
||||
} |
||||
|
||||
const handleDragging = (event: MouseEvent) => { |
||||
event.preventDefault() |
||||
|
||||
if (isDragging) { |
||||
const mouseYPosition = event.clientY |
||||
const difference = lastYPosition - mouseYPosition |
||||
const newTerminalPosition = terminalPosition + difference |
||||
setTerminalPosition(newTerminalPosition) |
||||
setLastYPosition(mouseYPosition) |
||||
} |
||||
} |
||||
|
||||
const handleDraggingEnd = () => { |
||||
if (!isDragging) return |
||||
|
||||
setIsDragging(false) |
||||
|
||||
// Check terminal position to determine if it should be open or closed
|
||||
setIsOpen(terminalPosition > minHeight) |
||||
} |
||||
|
||||
const handleToggleTerminal = (event: React.MouseEvent<HTMLElement>) => { |
||||
event.preventDefault() |
||||
event.stopPropagation() |
||||
|
||||
if (isOpen) { |
||||
setLastTerminalPosition(terminalPosition) |
||||
setLastYPosition(0) |
||||
setTerminalPosition(minHeight) |
||||
} else { |
||||
setTerminalPosition(lastTerminalPosition <= minHeight ? 323 : lastTerminalPosition) |
||||
} |
||||
|
||||
setIsOpen(!isOpen) |
||||
} |
||||
|
||||
// Add event listeners for dragging
|
||||
useEffect(() => { |
||||
document.addEventListener('mousemove', handleDragging) |
||||
document.addEventListener('mouseup', handleDraggingEnd) |
||||
|
||||
return () => { |
||||
document.removeEventListener('mousemove', handleDragging) |
||||
document.removeEventListener('mouseup', handleDraggingEnd) |
||||
} |
||||
}, [handleDragging, handleDraggingEnd]) |
||||
|
||||
// Reset terminal position
|
||||
useEffect(() => { |
||||
if (!terminalPosition) { |
||||
setTerminalPosition(defaultPosition) |
||||
} |
||||
}, [terminalPosition, setTerminalPosition]) |
||||
|
||||
return { |
||||
isOpen, |
||||
terminalPosition, |
||||
isDragging, |
||||
handleDraggingStart, |
||||
handleToggleTerminal |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue