commit
1bf5ccd8ef
@ -0,0 +1,125 @@ |
|||||||
|
|
||||||
|
'use strict' |
||||||
|
import { NightwatchBrowser } from 'nightwatch' |
||||||
|
import init from '../helpers/init' |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
|
||||||
|
before: function (browser: NightwatchBrowser, done: VoidFunction) { |
||||||
|
init(browser, done) |
||||||
|
}, |
||||||
|
|
||||||
|
'Test decorators with script': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.openFile('contracts') |
||||||
|
.openFile('contracts/2_Owner.sol') |
||||||
|
.openFile('contracts/1_Storage.sol') |
||||||
|
.openFile('contracts/3_Ballot.sol') |
||||||
|
.addFile('scripts/decorators.ts', { content: testScriptSet }) |
||||||
|
.pause(2000) |
||||||
|
.executeScriptInTerminal('remix.exeCurrent()') |
||||||
|
.pause(4000) |
||||||
|
.useXpath() |
||||||
|
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2') |
||||||
|
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2') |
||||||
|
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U') |
||||||
|
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/2_Owner.sol"]', 'U') |
||||||
|
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2') |
||||||
|
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-warning-contracts/1_Storage.sol"]', '2') |
||||||
|
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 'customtext') |
||||||
|
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 'customtext') |
||||||
|
.moveToElement('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', 0, 0) |
||||||
|
.waitForElementVisible('//*[@id="error-tooltip-contracts/2_Owner.sol"]') |
||||||
|
.waitForElementContainsText('//*[@id="error-tooltip-contracts/2_Owner.sol"]', 'error on owner') |
||||||
|
}, |
||||||
|
|
||||||
|
'clear ballot decorator': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.useCss() |
||||||
|
.addFile('scripts/clearballot.ts', { content: testScriptClearBallot }) |
||||||
|
.pause(2000) |
||||||
|
.executeScriptInTerminal('remix.exeCurrent()') |
||||||
|
.pause(4000) |
||||||
|
.waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000) |
||||||
|
}, |
||||||
|
'clear all decorators': function (browser: NightwatchBrowser) { |
||||||
|
browser |
||||||
|
.addFile('scripts/clearall.ts', { content: testScriptClear }) |
||||||
|
.pause(2000) |
||||||
|
.executeScriptInTerminal('remix.exeCurrent()') |
||||||
|
.pause(4000) |
||||||
|
.waitForElementNotPresent('[data-id="file-decoration-error-contracts/2_Owner.sol"]', 10000) |
||||||
|
.waitForElementNotPresent('[data-id="file-decoration-warning-contracts/1_Storage.sol"]', 10000) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
const testScriptSet = ` |
||||||
|
(async () => { |
||||||
|
remix.call('fileDecorator' as any, 'clearFileDecorators') |
||||||
|
let decorator: any = { |
||||||
|
path: 'contracts/2_Owner.sol', |
||||||
|
isDirectory: false, |
||||||
|
fileStateType: 'ERROR', |
||||||
|
fileStateLabelClass: 'text-danger', |
||||||
|
fileStateIconClass: '', |
||||||
|
fileStateIcon: '', |
||||||
|
text: '2', |
||||||
|
bubble: true, |
||||||
|
comment: 'error on owner', |
||||||
|
} |
||||||
|
let decorator2: any = { |
||||||
|
path: 'contracts/2_Owner.sol', |
||||||
|
isDirectory: false, |
||||||
|
fileStateType: 'CUSTOM', |
||||||
|
fileStateLabelClass: 'text-success', |
||||||
|
fileStateIconClass: 'text-success', |
||||||
|
fileStateIcon: 'U', |
||||||
|
text: '', |
||||||
|
bubble: true, |
||||||
|
comment: 'modified', |
||||||
|
} |
||||||
|
await remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2]) |
||||||
|
|
||||||
|
decorator = { |
||||||
|
path: 'contracts/1_Storage.sol', |
||||||
|
isDirectory: false, |
||||||
|
fileStateType: 'WARNING', |
||||||
|
fileStateLabelClass: 'text-warning', |
||||||
|
fileStateIconClass: '', |
||||||
|
fileStateIcon: '', |
||||||
|
text: '2', |
||||||
|
bubble: true, |
||||||
|
comment: 'warning on storage', |
||||||
|
} |
||||||
|
await remix.call('fileDecorator' as any, 'setFileDecorators', decorator) |
||||||
|
|
||||||
|
decorator = { |
||||||
|
path: 'contracts/3_Ballot.sol', |
||||||
|
isDirectory: false, |
||||||
|
fileStateType: 'CUSTOM', |
||||||
|
fileStateLabelClass: '', |
||||||
|
fileStateIconClass: '', |
||||||
|
fileStateIcon: 'customtext', |
||||||
|
text: 'with text', |
||||||
|
bubble: true, |
||||||
|
comment: 'custom comment', |
||||||
|
} |
||||||
|
await remix.call('fileDecorator' as any, 'setFileDecorators', decorator) |
||||||
|
|
||||||
|
})()` |
||||||
|
|
||||||
|
|
||||||
|
const testScriptClearBallot = ` |
||||||
|
(async () => { |
||||||
|
|
||||||
|
await remix.call('fileDecorator' as any, 'clearFileDecorators', 'contracts/3_Ballot.sol') |
||||||
|
|
||||||
|
})()` |
||||||
|
|
||||||
|
const testScriptClear = ` |
||||||
|
(async () => { |
||||||
|
await remix.call('fileDecorator' as any, 'clearAllFileDecorators') |
||||||
|
|
||||||
|
|
||||||
|
})()` |
@ -0,0 +1,86 @@ |
|||||||
|
'use strict' |
||||||
|
|
||||||
|
import { default as deepequal } from 'deep-equal' // eslint-disable-line
|
||||||
|
|
||||||
|
import { Plugin } from '@remixproject/engine' |
||||||
|
import { fileDecoration } from '@remix-ui/file-decorators' |
||||||
|
|
||||||
|
const profile = { |
||||||
|
name: 'fileDecorator', |
||||||
|
desciption: 'Keeps decorators of the files', |
||||||
|
methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'], |
||||||
|
events: ['fileDecoratorsChanged'], |
||||||
|
version: '0.0.1' |
||||||
|
} |
||||||
|
|
||||||
|
export class FileDecorator extends Plugin { |
||||||
|
private _fileStates: fileDecoration[] = [] |
||||||
|
constructor() { |
||||||
|
super(profile) |
||||||
|
} |
||||||
|
|
||||||
|
onActivation(): void { |
||||||
|
this.on('filePanel', 'setWorkspace', async () => { |
||||||
|
await this.clearAllFileDecorators() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*
|
||||||
|
* @param fileStates Array of file states |
||||||
|
*/ |
||||||
|
async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) { |
||||||
|
const { from } = this.currentRequest |
||||||
|
const workspace = await this.call('filePanel', 'getCurrentWorkspace') |
||||||
|
const fileStatesPayload = Array.isArray(fileStates) ? fileStates : [fileStates] |
||||||
|
// clear all file states in the previous state of this owner on the files called
|
||||||
|
fileStatesPayload.forEach((state) => { |
||||||
|
state.workspace = workspace |
||||||
|
state.owner = from |
||||||
|
}) |
||||||
|
const filteredState = this._fileStates.filter((state) => { |
||||||
|
const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => { |
||||||
|
return from == state.owner && payloadFileState.path == state.path |
||||||
|
}) |
||||||
|
return index == -1 |
||||||
|
}) |
||||||
|
const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath) |
||||||
|
|
||||||
|
if (!deepequal(newState, this._fileStates)) { |
||||||
|
this._fileStates = newState |
||||||
|
this.emit('fileDecoratorsChanged', this._fileStates) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async clearFileDecorators(path?: string) { |
||||||
|
const { from } = this.currentRequest |
||||||
|
if (!from) return |
||||||
|
|
||||||
|
const filteredState = this._fileStates.filter((state) => { |
||||||
|
if(state.owner != from) return true |
||||||
|
if(path && state.path != path) return true |
||||||
|
}) |
||||||
|
const newState = [...filteredState].sort(sortByPath) |
||||||
|
|
||||||
|
if (!deepequal(newState, this._fileStates)) { |
||||||
|
this._fileStates = newState |
||||||
|
this.emit('fileDecoratorsChanged', this._fileStates) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
async clearAllFileDecorators() { |
||||||
|
this._fileStates = [] |
||||||
|
this.emit('fileDecoratorsChanged', []) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const sortByPath = (a: fileDecoration, b: fileDecoration) => { |
||||||
|
if (a.path < b.path) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
if (a.path > b.path) { |
||||||
|
return 1; |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
{ |
||||||
|
"presets": ["@nrwl/react/babel"], |
||||||
|
"plugins": [] |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
{ |
||||||
|
"env": { |
||||||
|
"browser": true, |
||||||
|
"es6": true |
||||||
|
}, |
||||||
|
"extends": "../../../.eslintrc.json", |
||||||
|
"globals": { |
||||||
|
"Atomics": "readonly", |
||||||
|
"SharedArrayBuffer": "readonly" |
||||||
|
}, |
||||||
|
"parserOptions": { |
||||||
|
"ecmaVersion": 11, |
||||||
|
"sourceType": "module" |
||||||
|
}, |
||||||
|
"rules": { |
||||||
|
"no-unused-vars": "off", |
||||||
|
"@typescript-eslint/no-unused-vars": "error" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,3 @@ |
|||||||
|
export { fileDecoration, fileDecorationType, FileType } from './lib/types/index' |
||||||
|
export { FileDecorationIcons } from './lib/components/file-decoration-icon' |
||||||
|
|
@ -0,0 +1,51 @@ |
|||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
import React, { useEffect, useState } from 'react' |
||||||
|
|
||||||
|
import { fileDecoration, fileDecorationType, FileType } from '../types' |
||||||
|
import FileDecorationCustomIcon from './filedecorationicons/file-decoration-custom-icon' |
||||||
|
import FileDecorationErrorIcon from './filedecorationicons/file-decoration-error-icon' |
||||||
|
import FileDecorationTooltip from './filedecorationicons/file-decoration-tooltip' |
||||||
|
import FileDecorationWarningIcon from './filedecorationicons/file-decoration-warning-icon' |
||||||
|
|
||||||
|
export type fileDecorationProps = { |
||||||
|
file: FileType, |
||||||
|
fileDecorations: fileDecoration[] |
||||||
|
} |
||||||
|
|
||||||
|
export const FileDecorationIcons = (props: fileDecorationProps) => { |
||||||
|
const [states, setStates] = useState<fileDecoration[]>([]) |
||||||
|
useEffect(() => { |
||||||
|
//console.log(props.file)
|
||||||
|
//console.log(props.fileState)
|
||||||
|
setStates(props.fileDecorations.filter((fileDecoration) => fileDecoration.path === props.file.path || `${fileDecoration.workspace.name}/${fileDecoration.path}` === props.file.path)) |
||||||
|
}, [props.fileDecorations]) |
||||||
|
|
||||||
|
|
||||||
|
const getTags = function () { |
||||||
|
if (states && states.length) { |
||||||
|
const elements: JSX.Element[] = [] |
||||||
|
|
||||||
|
for (const [index, state] of states.entries()) { |
||||||
|
switch (state.fileStateType) { |
||||||
|
case fileDecorationType.Error: |
||||||
|
elements.push(<FileDecorationTooltip key={index} index={index} fileDecoration={state} icon={<FileDecorationErrorIcon fileDecoration={state} key={index}/>}/>) |
||||||
|
break |
||||||
|
case fileDecorationType.Warning: |
||||||
|
elements.push(<FileDecorationTooltip key={index} index={index} fileDecoration={state} icon={<FileDecorationWarningIcon fileDecoration={state} key={index}/>}/>) |
||||||
|
break |
||||||
|
case fileDecorationType.Custom: |
||||||
|
elements.push(<FileDecorationTooltip key={index} index={index} fileDecoration={state} icon={<FileDecorationCustomIcon fileDecoration={state} key={index}/>}/>) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return elements |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return <> |
||||||
|
{getTags()} |
||||||
|
</> |
||||||
|
} |
||||||
|
|
||||||
|
export default FileDecorationIcons |
@ -0,0 +1,13 @@ |
|||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
import React from 'react' |
||||||
|
import { fileDecoration } from '../../types' |
||||||
|
|
||||||
|
const FileDecorationCustomIcon = (props: { |
||||||
|
fileDecoration: fileDecoration |
||||||
|
}) => { |
||||||
|
return <><span data-id={`file-decoration-custom-${props.fileDecoration.path}`} className={`${props.fileDecoration.fileStateIconClass} pr-2`}> |
||||||
|
{props.fileDecoration.fileStateIcon} |
||||||
|
</span></> |
||||||
|
} |
||||||
|
|
||||||
|
export default FileDecorationCustomIcon |
@ -0,0 +1,14 @@ |
|||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
import React from 'react' |
||||||
|
|
||||||
|
import { fileDecoration } from '../../types' |
||||||
|
|
||||||
|
const FileDecorationErrorIcon = (props: { |
||||||
|
fileDecoration: fileDecoration |
||||||
|
}) => { |
||||||
|
return <> |
||||||
|
<span data-id={`file-decoration-error-${props.fileDecoration.path}`} className={`${props.fileDecoration.fileStateIconClass} text-danger pr-2`}>{props.fileDecoration.text}</span> |
||||||
|
</> |
||||||
|
} |
||||||
|
|
||||||
|
export default FileDecorationErrorIcon |
@ -0,0 +1,33 @@ |
|||||||
|
import React from "react"; |
||||||
|
import { OverlayTrigger, Tooltip } from "react-bootstrap"; |
||||||
|
import { fileDecoration } from "../../types"; |
||||||
|
|
||||||
|
const FileDecorationTooltip = (props: { |
||||||
|
fileDecoration: fileDecoration, |
||||||
|
icon: JSX.Element |
||||||
|
index: number |
||||||
|
}, |
||||||
|
) => { |
||||||
|
const getComments = function (fileDecoration: fileDecoration) { |
||||||
|
if (fileDecoration.comment) { |
||||||
|
const comments = Array.isArray(fileDecoration.comment) ? fileDecoration.comment : [fileDecoration.comment] |
||||||
|
return comments.map((comment, index) => { |
||||||
|
return <div key={index}>{comment}<br></br></div> |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return <OverlayTrigger |
||||||
|
key={`overlaytrigger-${props.fileDecoration.path}-${props.index}`} |
||||||
|
placement='auto' |
||||||
|
overlay={ |
||||||
|
<Tooltip id={`error-tooltip-${props.fileDecoration.path}`}> |
||||||
|
<>{getComments(props.fileDecoration)}</> |
||||||
|
</Tooltip> |
||||||
|
} |
||||||
|
><div>{props.icon}</div></OverlayTrigger> |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
export default FileDecorationTooltip; |
@ -0,0 +1,11 @@ |
|||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
import React from 'react' |
||||||
|
import { fileDecoration } from '../../types' |
||||||
|
|
||||||
|
const FileDecorationWarningIcon = (props: { |
||||||
|
fileDecoration: fileDecoration |
||||||
|
}) => { |
||||||
|
return <><span data-id={`file-decoration-warning-${props.fileDecoration.path}`} className={`${props.fileDecoration.fileStateIconClass} text-warning pr-2`}>{props.fileDecoration.text}</span></> |
||||||
|
} |
||||||
|
|
||||||
|
export default FileDecorationWarningIcon |
@ -0,0 +1,11 @@ |
|||||||
|
import React from "react" |
||||||
|
import { fileDecoration } from "../types" |
||||||
|
|
||||||
|
export const getComments = function (fileDecoration: fileDecoration) { |
||||||
|
if(fileDecoration.comment){ |
||||||
|
const comments = Array.isArray(fileDecoration.comment) ? fileDecoration.comment : [fileDecoration.comment] |
||||||
|
return comments.map((comment, index) => { |
||||||
|
return <div key={index}>{comment}<br></br></div> |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
export enum fileDecorationType { |
||||||
|
Error = 'ERROR', |
||||||
|
Warning = 'WARNING', |
||||||
|
Custom = 'CUSTOM', |
||||||
|
None = 'NONE' |
||||||
|
} |
||||||
|
|
||||||
|
export type fileDecoration = { |
||||||
|
path: string, |
||||||
|
isDirectory: boolean, |
||||||
|
fileStateType: fileDecorationType, |
||||||
|
fileStateLabelClass: string, |
||||||
|
fileStateIconClass: string, |
||||||
|
fileStateIcon: string | HTMLDivElement | JSX.Element, |
||||||
|
bubble: boolean, |
||||||
|
text?: string, |
||||||
|
owner?: string, |
||||||
|
workspace?: any |
||||||
|
tooltip?: string |
||||||
|
comment?: string[] | string |
||||||
|
} |
||||||
|
|
||||||
|
export interface FileType { |
||||||
|
path: string, |
||||||
|
name?: string, |
||||||
|
isDirectory?: boolean, |
||||||
|
type?: 'folder' | 'file' | 'gist', |
||||||
|
child?: File[] |
||||||
|
} |
@ -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"] |
||||||
|
} |
@ -1,2 +1,3 @@ |
|||||||
export * from './lib/providers/FileSystemProvider' |
export * from './lib/providers/FileSystemProvider' |
||||||
export * from './lib/contexts' |
export * from './lib/contexts' |
||||||
|
export { FileType } from './lib/types/index' |
Loading…
Reference in new issue