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/contexts' |
||||
export { FileType } from './lib/types/index' |
Loading…
Reference in new issue