From 82edba9b84770449eefb3ed6283009cecd34b3e3 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Thu, 21 Jul 2022 15:39:04 +0200 Subject: [PATCH 01/29] add test --- apps/remix-ide/src/app/panels/tab-proxy.js | 6 ++ .../src/app/plugins/file-decorator.ts | 60 +++++++++++ libs/remix-ui/file-decorators/.babelrc | 4 + libs/remix-ui/file-decorators/.eslintrc | 19 ++++ libs/remix-ui/file-decorators/src/index.ts | 3 + .../lib/components/file-decoration-icon.tsx | 51 ++++++++++ .../file-decoration-custom-icon.tsx | 13 +++ .../file-decoration-error-icon.tsx | 14 +++ .../file-decoration-tooltip.tsx | 33 +++++++ .../file-decoration-warning-icon.tsx | 11 +++ .../file-decorators/src/lib/helper/index.tsx | 11 +++ .../file-decorators/src/lib/types/index.ts | 29 ++++++ libs/remix-ui/file-decorators/tsconfig.json | 16 +++ .../file-decorators/tsconfig.lib.json | 13 +++ libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx | 99 +++++++++++++++---- libs/remix-ui/workspace/src/index.ts | 1 + .../workspace/src/lib/actions/events.ts | 12 ++- .../workspace/src/lib/actions/payload.ts | 7 ++ .../src/lib/components/file-explorer.tsx | 3 +- .../src/lib/components/file-label.tsx | 21 +++- .../src/lib/components/file-render.tsx | 18 +++- .../workspace/src/lib/reducers/workspace.ts | 31 ++++-- .../workspace/src/lib/remix-ui-workspace.tsx | 2 + .../remix-ui/workspace/src/lib/types/index.ts | 2 + 24 files changed, 444 insertions(+), 35 deletions(-) create mode 100644 apps/remix-ide/src/app/plugins/file-decorator.ts create mode 100644 libs/remix-ui/file-decorators/.babelrc create mode 100644 libs/remix-ui/file-decorators/.eslintrc create mode 100644 libs/remix-ui/file-decorators/src/index.ts create mode 100644 libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/helper/index.tsx create mode 100644 libs/remix-ui/file-decorators/src/lib/types/index.ts create mode 100644 libs/remix-ui/file-decorators/tsconfig.json create mode 100644 libs/remix-ui/file-decorators/tsconfig.lib.json diff --git a/apps/remix-ide/src/app/panels/tab-proxy.js b/apps/remix-ide/src/app/panels/tab-proxy.js index 82758c169f..3821788e18 100644 --- a/apps/remix-ide/src/app/panels/tab-proxy.js +++ b/apps/remix-ide/src/app/panels/tab-proxy.js @@ -169,6 +169,10 @@ export class TabProxy extends Plugin { this.on('manager', 'pluginDeactivated', (profile) => { this.removeTab(profile.name) }) + + this.on('fileDecorator', 'fileDecoratorsChanged', async (items) => { + this.tabsApi.setFileDecorations(items) + }) try { this.themeQuality = (await this.call('theme', 'currentTheme') ).quality @@ -306,6 +310,7 @@ export class TabProxy extends Plugin { updateComponent(state) { return { this.tabsApi = api } this.dispatch({ + plugin: this, loadedTabs: this.loadedTabs, onSelect, onClose, diff --git a/apps/remix-ide/src/app/plugins/file-decorator.ts b/apps/remix-ide/src/app/plugins/file-decorator.ts new file mode 100644 index 0000000000..60a14ef47f --- /dev/null +++ b/apps/remix-ide/src/app/plugins/file-decorator.ts @@ -0,0 +1,60 @@ +'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'], + events: ['fileDecoratorsChanged'], + version: '0.0.1' +} + +export class FileDecorator extends Plugin { + private _fileStates: fileDecoration[] = [] + constructor() { + super(profile) + } + /** + * + * @param fileStates Array of file states + */ + async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) { + const workspace = await this.call('filePanel', 'getCurrentWorkspace') + function sortByPath( a: fileDecoration, b: fileDecoration ) { + if ( a.path < b.path ){ + return -1; + } + if ( a.path > b.path ){ + return 1; + } + return 0; + } + + 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 + }) + const filteredState = this._fileStates.filter((state) => { + const index = fileStatesPayload.findIndex((payloadFileState: fileDecoration) => { + return payloadFileState.owner == 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() { + this._fileStates = [] + this.emit('fileDecoratorsChanged', []) + } +} \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/.babelrc b/libs/remix-ui/file-decorators/.babelrc new file mode 100644 index 0000000000..09d67939cc --- /dev/null +++ b/libs/remix-ui/file-decorators/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@nrwl/react/babel"], + "plugins": [] +} diff --git a/libs/remix-ui/file-decorators/.eslintrc b/libs/remix-ui/file-decorators/.eslintrc new file mode 100644 index 0000000000..0d43d424e3 --- /dev/null +++ b/libs/remix-ui/file-decorators/.eslintrc @@ -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" + } +} diff --git a/libs/remix-ui/file-decorators/src/index.ts b/libs/remix-ui/file-decorators/src/index.ts new file mode 100644 index 0000000000..a116c66cd3 --- /dev/null +++ b/libs/remix-ui/file-decorators/src/index.ts @@ -0,0 +1,3 @@ +export { fileDecoration, fileDecorationType, FileType } from './lib/types/index' +export { FileDecorationIcons } from './lib/components/file-decoration-icon' + diff --git a/libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx new file mode 100644 index 0000000000..f05cea9f65 --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/components/file-decoration-icon.tsx @@ -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([]) + 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(}/>) + break + case fileDecorationType.Warning: + elements.push(}/>) + break + case fileDecorationType.Custom: + elements.push(}/>) + break + } + } + + return elements + } + } + + return <> + {getTags()} + +} + +export default FileDecorationIcons \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx new file mode 100644 index 0000000000..c157bc66c0 --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx @@ -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 <> + {props.fileDecoration.fileStateIcon} + +} + +export default FileDecorationCustomIcon \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx new file mode 100644 index 0000000000..2bb61fd97f --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx @@ -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 <> + {props.fileDecoration.text} + +} + +export default FileDecorationErrorIcon \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx new file mode 100644 index 0000000000..7c9149c14d --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx @@ -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.commment) { + const commments = Array.isArray(fileDecoration.commment) ? fileDecoration.commment : [fileDecoration.commment] + return commments.map((comment, index) => { + return
{comment}

+ }) + } + } + + return + <>{getComments(props.fileDecoration)} + + } + >
{props.icon}
+ +} + + +export default FileDecorationTooltip; \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx new file mode 100644 index 0000000000..1c027c9854 --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx @@ -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 <>{props.fileDecoration.text} +} + +export default FileDecorationWarningIcon \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/helper/index.tsx b/libs/remix-ui/file-decorators/src/lib/helper/index.tsx new file mode 100644 index 0000000000..1afe91b0cf --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/helper/index.tsx @@ -0,0 +1,11 @@ +import React from "react" +import { fileDecoration } from "../types" + +export const getComments = function (fileDecoration: fileDecoration) { + if(fileDecoration.commment){ + const commments = Array.isArray(fileDecoration.commment) ? fileDecoration.commment : [fileDecoration.commment] + return commments.map((comment, index) => { + return
{comment}

+ }) + } +} \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/types/index.ts b/libs/remix-ui/file-decorators/src/lib/types/index.ts new file mode 100644 index 0000000000..35178721e0 --- /dev/null +++ b/libs/remix-ui/file-decorators/src/lib/types/index.ts @@ -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 + commment?: string[] | string + } + + export interface FileType { + path: string, + name?: string, + isDirectory?: boolean, + type?: 'folder' | 'file' | 'gist', + child?: File[] + } \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/tsconfig.json b/libs/remix-ui/file-decorators/tsconfig.json new file mode 100644 index 0000000000..d52e31ad74 --- /dev/null +++ b/libs/remix-ui/file-decorators/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/libs/remix-ui/file-decorators/tsconfig.lib.json b/libs/remix-ui/file-decorators/tsconfig.lib.json new file mode 100644 index 0000000000..b560bc4dec --- /dev/null +++ b/libs/remix-ui/file-decorators/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"] +} diff --git a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx index 499d0d1eb1..67c7224c96 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -1,25 +1,63 @@ + +import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators' +import { Plugin } from '@remixproject/engine' + import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line import { Tab, Tabs, TabList, TabPanel } from 'react-tabs' import './remix-ui-tabs.css' /* eslint-disable-next-line */ export interface TabsUIProps { - tabs: Array - onSelect: (index: number) => void - onClose: (index: number) => void - onZoomOut: () => void - onZoomIn: () => void - onReady: (api: any) => void - themeQuality: string + tabs: Array + plugin: Plugin, + onSelect: (index: number) => void + onClose: (index: number) => void + onZoomOut: () => void + onZoomIn: () => void + onReady: (api: any) => void + themeQuality: string } export interface TabsUIApi { - activateTab: (namee: string) => void - active: () => string + activateTab: (namee: string) => void + active: () => string +} + +interface ITabsState { + selectedIndex: number, + fileDecorations: fileDecoration[], +} + +interface ITabsAction { + type: string, + payload: any, +} + + +const initialTabsState: ITabsState = { + selectedIndex: -1, + fileDecorations: [], +} + +const tabsReducer = (state: ITabsState, action: ITabsAction) => { + switch (action.type) { + case 'SELECT_INDEX': + return { + ...state, + selectedIndex: action.payload, + } + case 'SET_FILE_DECORATIONS': + return { + ...state, + fileDecorations: action.payload as fileDecoration[], + } + default: + return state + } } export const TabsUI = (props: TabsUIProps) => { - const [selectedIndex, setSelectedIndex] = useState(-1) + const [tabsState, dispatch] = useReducer(tabsReducer, initialTabsState); const currentIndexRef = useRef(-1) const tabsRef = useRef({}) const tabsElement = useRef(null) @@ -28,10 +66,24 @@ export const TabsUI = (props: TabsUIProps) => { tabs.current = props.tabs // we do this to pass the tabs list to the onReady callbacks useEffect(() => { - if (props.tabs[selectedIndex]) { - tabsRef.current[selectedIndex].scrollIntoView({ behavior: 'smooth', block: 'center' }) + if (props.tabs[tabsState.selectedIndex]) { + tabsRef.current[tabsState.selectedIndex].scrollIntoView({ behavior: 'smooth', block: 'center' }) } - }, [selectedIndex]) + }, [tabsState.selectedIndex]) + + + + const getFileDecorationClasses = (tab: any) => { + const fileDecoration = tabsState.fileDecorations.find((fileDecoration: fileDecoration) => { + if(`${fileDecoration.workspace.name}/${fileDecoration.path}` === tab.name) return true + }) + return fileDecoration && fileDecoration.fileStateLabelClass + } + + const getFileDecorationIcons = (tab: any) => { + return + } + const renderTab = (tab, index) => { const classNameImg = 'my-1 mr-1 text-dark ' + tab.iconClass @@ -40,8 +92,9 @@ export const TabsUI = (props: TabsUIProps) => { return (
{ tabsRef.current[index] = el }} className={classNameTab} data-id={index === currentIndexRef.current ? 'tab-active' : ''} title={tab.tooltip}> - {tab.icon ? () : ()} - {tab.title} + {tab.icon ? () : ()} + {tab.title} + {getFileDecorationIcons(tab)} { props.onClose(index); event.stopPropagation() }}> @@ -56,7 +109,11 @@ export const TabsUI = (props: TabsUIProps) => { const activateTab = (name: string) => { const index = tabs.current.findIndex((tab) => tab.name === name) currentIndexRef.current = index - setSelectedIndex(index) + dispatch({ type: 'SELECT_INDEX', payload: index }) + } + + const setFileDecorations = (fileStates: fileDecoration[]) => { + dispatch({ type: 'SET_FILE_DECORATIONS', payload: fileStates }) } const transformScroll = (event) => { @@ -71,21 +128,23 @@ export const TabsUI = (props: TabsUIProps) => { useEffect(() => { props.onReady({ activateTab, - active + active, + setFileDecorations }) + return () => { tabsElement.current.removeEventListener('wheel', transformScroll) } }, []) return (
-
+
props.onZoomOut()}> props.onZoomIn()}>
{ if (tabsElement.current) return tabsElement.current = domEl @@ -94,7 +153,7 @@ export const TabsUI = (props: TabsUIProps) => { onSelect={(index) => { props.onSelect(index) currentIndexRef.current = index - setSelectedIndex(index) + dispatch({ type: 'SELECT_INDEX', payload: index }) }} > diff --git a/libs/remix-ui/workspace/src/index.ts b/libs/remix-ui/workspace/src/index.ts index 166467d115..c9e5b5355b 100644 --- a/libs/remix-ui/workspace/src/index.ts +++ b/libs/remix-ui/workspace/src/index.ts @@ -1,2 +1,3 @@ export * from './lib/providers/FileSystemProvider' export * from './lib/contexts' +export { FileType } from './lib/types/index' \ No newline at end of file diff --git a/libs/remix-ui/workspace/src/lib/actions/events.ts b/libs/remix-ui/workspace/src/lib/actions/events.ts index a8bde65fd5..62092df30f 100644 --- a/libs/remix-ui/workspace/src/lib/actions/events.ts +++ b/libs/remix-ui/workspace/src/lib/actions/events.ts @@ -1,7 +1,8 @@ +import { fileDecoration } from '@remix-ui/file-decorators' import { extractParentFromKey } from '@remix-ui/helper' import React from 'react' import { action, WorkspaceTemplate } from '../types' -import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, loadLocalhostError, loadLocalhostRequest, loadLocalhostSuccess, removeContextMenuItem, removeFocus, rootFolderChangedSuccess, setContextMenuItem, setMode, setReadOnlyMode } from './payload' +import { displayNotification, displayPopUp, fileAddedSuccess, fileRemovedSuccess, fileRenamedSuccess, folderAddedSuccess, loadLocalhostError, loadLocalhostRequest, loadLocalhostSuccess, removeContextMenuItem, removeFocus, rootFolderChangedSuccess, setContextMenuItem, setMode, setReadOnlyMode, setFileDecorationSuccess } from './payload' import { addInputField, createWorkspace, deleteWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace' const LOCALHOST = ' - connect to localhost - ' @@ -38,6 +39,10 @@ export const listenOnPluginEvents = (filePanelPlugin) => { uploadFile(target, dir, cb) }) + plugin.on('fileDecorator', 'fileDecoratorsChanged', async (items: fileDecoration[]) => { + setFileDecorators(items) + }) + plugin.on('remixd', 'rootFolderChanged', async (path: string) => { rootFolderChanged(path) }) @@ -202,3 +207,8 @@ const fileRenamed = async (oldPath: string) => { const rootFolderChanged = async (path) => { await dispatch(rootFolderChangedSuccess(path)) } + +const setFileDecorators = async (items: fileDecoration[], cb?: (err: Error, result?: string | number | boolean | Record) => void) => { + await dispatch(setFileDecorationSuccess(items)) + cb && cb(null, true) +} diff --git a/libs/remix-ui/workspace/src/lib/actions/payload.ts b/libs/remix-ui/workspace/src/lib/actions/payload.ts index b663f60cfe..8c3fb8fc18 100644 --- a/libs/remix-ui/workspace/src/lib/actions/payload.ts +++ b/libs/remix-ui/workspace/src/lib/actions/payload.ts @@ -1,3 +1,4 @@ +import { fileDecoration } from '@remix-ui/file-decorators' import { action } from '../types' export const setCurrentWorkspace = (workspace: { name: string; isGitRepo: boolean; }) => { @@ -240,6 +241,12 @@ export const fsInitializationCompleted = () => { } } +export const setFileDecorationSuccess = (items: fileDecoration[]) => { + return { + type: 'SET_FILE_DECORATION_SUCCESS', + payload: items + } +} export const cloneRepositoryRequest = () => { return { type: 'CLONE_REPOSITORY_REQUEST' diff --git a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx index a0351ea3a1..3ccefdb2db 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-explorer.tsx @@ -12,7 +12,7 @@ import { checkSpecialChars, extractNameFromKey, extractParentFromKey, joinPath } import { FileRender } from './file-render' export const FileExplorer = (props: FileExplorerProps) => { - const { name, contextMenuItems, removedContextMenuItems, files } = props + const { name, contextMenuItems, removedContextMenuItems, files, fileState } = props const [state, setState] = useState({ ctrlKey: false, newFileName: '', @@ -432,6 +432,7 @@ export const FileExplorer = (props: FileExplorerProps) => { { files[props.name] && Object.keys(files[props.name]).map((key, index) => void } export const FileLabel = (props: FileLabelProps) => { - const { file, focusEdit, editModeOff } = props + const { file, focusEdit, editModeOff, fileDecorations } = props const [isEditable, setIsEditable] = useState(false) + const [fileStateClasses, setFileStateClasses] = useState('') const labelRef = useRef(null) useEffect(() => { @@ -24,6 +27,18 @@ export const FileLabel = (props: FileLabelProps) => { } }, [file.path, focusEdit]) + useEffect(() => { + const state = props.fileDecorations.find((state: fileDecoration) => { + if(state.path === props.file.path) return true + if(state.bubble && props.file.isDirectory && state.path.startsWith(props.file.path)) return true + }) + if (state && state.fileStateLabelClass) { + setFileStateClasses(state.fileStateLabelClass) + } else{ + setFileStateClasses('') + } + }, [fileDecorations]) + useEffect(() => { if (labelRef.current) { setTimeout(() => { @@ -57,10 +72,10 @@ export const FileLabel = (props: FileLabelProps) => { > - { file.name } + {file.name}
) diff --git a/libs/remix-ui/workspace/src/lib/components/file-render.tsx b/libs/remix-ui/workspace/src/lib/components/file-render.tsx index ac0054e96b..164689899b 100644 --- a/libs/remix-ui/workspace/src/lib/components/file-render.tsx +++ b/libs/remix-ui/workspace/src/lib/components/file-render.tsx @@ -6,6 +6,11 @@ import { TreeView, TreeViewItem } from '@remix-ui/tree-view' import { getPathIcon } from '@remix-ui/helper' // eslint-disable-next-line @typescript-eslint/no-unused-vars import { FileLabel } from './file-label' +import { fileDecoration, FileDecorationIcons } from '@remix-ui/file-decorators' + + + + export interface RenderFileProps { file: FileType, @@ -19,6 +24,7 @@ export interface RenderFileProps { handleClickFolder: (path: string, type: string) => void, handleClickFile: (path: string, type: string) => void, handleContextMenu: (pageX: number, pageY: number, path: string, content: string, type: string) => void + fileDecorations: fileDecoration[] } export const FileRender = (props: RenderFileProps) => { @@ -76,7 +82,7 @@ export const FileRender = (props: RenderFileProps) => { iconX='pr-3 fa fa-folder' iconY='pr-3 fa fa-folder-open' key={`${file.path + props.index}`} - label={} + label={} onClick={handleFolderClick} onContextMenu={handleContextMenu} labelClass={labelClass} @@ -89,6 +95,7 @@ export const FileRender = (props: RenderFileProps) => { file.child ? { Object.keys(file.child).map((key, index) => { } + label={ + <> +
+ + +
+ + } onClick={handleFileClick} onContextMenu={handleContextMenu} icon={icon} diff --git a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts index 39f56a202f..0a774e6b03 100644 --- a/libs/remix-ui/workspace/src/lib/reducers/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/reducers/workspace.ts @@ -1,9 +1,10 @@ import { extractNameFromKey } from '@remix-ui/helper' import { action, FileType } from '../types' import * as _ from 'lodash' +import { fileDecoration } from '@remix-ui/file-decorators' interface Action { - type: string - payload: any + type: string + payload: any } export interface BrowserState { browser: { @@ -25,7 +26,8 @@ export interface BrowserState { registeredMenuItems: action[], removedMenuItems: action[], error: string - } + }, + fileState: fileDecoration[] }, localhost: { sharedFolder: string, @@ -40,7 +42,8 @@ export interface BrowserState { registeredMenuItems: action[], removedMenuItems: action[], error: string - } + }, + fileState: [] }, mode: 'browser' | 'localhost', notification: { @@ -75,7 +78,8 @@ export const browserInitialState: BrowserState = { registeredMenuItems: [], removedMenuItems: [], error: null - } + }, + fileState: [] }, localhost: { sharedFolder: '', @@ -90,14 +94,15 @@ export const browserInitialState: BrowserState = { registeredMenuItems: [], removedMenuItems: [], error: null - } + }, + fileState: [] }, mode: 'browser', notification: { title: '', message: '', - actionOk: () => {}, - actionCancel: () => {}, + actionOk: () => { }, + actionCancel: () => { }, labelOk: '', labelCancel: '' }, @@ -650,6 +655,16 @@ export const browserReducer = (state = browserInitialState, action: Action) => { } } + case 'SET_FILE_DECORATION_SUCCESS': { + return { + ...state, + browser: { + ...state.browser, + fileState: action.payload + } + } + } + default: throw new Error() } diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index e25811df68..353e4ccf91 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -307,6 +307,7 @@ export function Workspace () { contextMenuItems={global.fs.browser.contextMenu.registeredMenuItems} removedContextMenuItems={global.fs.browser.contextMenu.removedMenuItems} files={global.fs.browser.files} + fileState={global.fs.browser.fileState} expandPath={global.fs.browser.expandPath} focusEdit={global.fs.focusEdit} focusElement={global.fs.focusElement} @@ -343,6 +344,7 @@ export function Workspace () { contextMenuItems={global.fs.localhost.contextMenu.registeredMenuItems} removedContextMenuItems={global.fs.localhost.contextMenu.removedMenuItems} files={global.fs.localhost.files} + fileState={[]} expandPath={global.fs.localhost.expandPath} focusEdit={global.fs.focusEdit} focusElement={global.fs.focusElement} diff --git a/libs/remix-ui/workspace/src/lib/types/index.ts b/libs/remix-ui/workspace/src/lib/types/index.ts index fb169db5e8..93b6d9f8c9 100644 --- a/libs/remix-ui/workspace/src/lib/types/index.ts +++ b/libs/remix-ui/workspace/src/lib/types/index.ts @@ -1,5 +1,6 @@ import React from 'react' import { customAction } from '@remixproject/plugin-api/lib/file-system/file-panel' +import { fileDecoration } from '@remix-ui/file-decorators'; 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 { @@ -73,6 +74,7 @@ export interface FileExplorerProps { contextMenuItems: MenuItems, removedContextMenuItems: MenuItems, files: { [x: string]: Record }, + fileState: fileDecoration[], expandPath: string[], focusEdit: string, focusElement: { key: string, type: 'file' | 'folder' | 'gist' }[], From 53e9650cca64e43481a397f4d01b92d11a07c6e6 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Thu, 21 Jul 2022 15:39:15 +0200 Subject: [PATCH 02/29] add test --- .../src/tests/editorAutoComplete.test.ts | 518 ++++++++++++++++++ 1 file changed, 518 insertions(+) create mode 100644 apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts diff --git a/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts b/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts new file mode 100644 index 0000000000..8dc7bd4440 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts @@ -0,0 +1,518 @@ +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' +import examples from '../examples/editor-test-contracts' + +const autoCompleteLineElement = (name: string) => { + return `//*[@class='editor-widget suggest-widget visible']//*[@class='contents' and contains(.,'${name}')]` +} + +module.exports = { + '@disabled': true, + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done, 'http://127.0.0.1:8080', false) + }, + 'Should add test and base files #group2': function (browser: NightwatchBrowser) { + browser.addFile(examples.testContract.name, examples.testContract) + .addFile(examples.baseContract.name, examples.baseContract) + .addFile(examples.import1Contract.name, examples.import1Contract) + .addFile(examples.baseOfBaseContract.name, examples.baseOfBaseContract) + .addFile(examples.secondimport.name, examples.secondimport) + .addFile(examples.importbase.name, examples.importbase) + .openFile(examples.testContract.name) + }, + 'Should put cursor in the () of the function #group2': function (browser: NightwatchBrowser) { + browser.scrollToLine(36) + const path = "//*[@class='view-line' and contains(.,'myprivatefunction') and contains(.,'private')]//span//span[contains(.,'(')]" + browser.waitForElementVisible('#editorView') + .useXpath() + .click(path).pause(1000) + }, + 'Should complete variable declaration types in a function definition #group2': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('uint25') + }) + .waitForElementPresent(autoCompleteLineElement('uint256')) + .click(autoCompleteLineElement('uint256')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' abc') + .sendKeys(this.Keys.ENTER) // we need to split lines for FF texts to pass because the screen is too narrow + .sendKeys(', testb') + }) + .waitForElementPresent(autoCompleteLineElement('"TestBookDefinition"')) + .click(autoCompleteLineElement('"TestBookDefinition"')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' memo') + }) + .waitForElementPresent(autoCompleteLineElement('memory')) + .click(autoCompleteLineElement('memory')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' btextbook') + .sendKeys(this.Keys.ENTER) + .sendKeys(', BaseB') + }) + .waitForElementPresent(autoCompleteLineElement('"BaseBook"')) + .click(autoCompleteLineElement('"BaseBook"')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' stor') + }) + .waitForElementPresent(autoCompleteLineElement('storage')) + .click(autoCompleteLineElement('storage')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' localbbook') + }).pause(3000) + }, + 'Should put cursor at the end of function #group2': function (browser: NightwatchBrowser) { + + const path = "//*[@class='view-line' and contains(.,'localbbook') and contains(.,'private')]//span//span[contains(.,'{')]" + browser + .useXpath() + .click(path).pause(1000) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + // right arrow key + sendKeys(this.Keys.ARROW_RIGHT). + sendKeys(this.Keys.ARROW_RIGHT) + }) + }, + 'Should autcomplete address types': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('addre') + }) + .waitForElementPresent(autoCompleteLineElement('address')) + .click(autoCompleteLineElement('address')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' someaddress;') + .sendKeys(this.Keys.ENTER) + }).pause(2000) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('someaddress.') + }) + .waitForElementVisible(autoCompleteLineElement('balance')) + .waitForElementVisible(autoCompleteLineElement('send')) + .waitForElementVisible(autoCompleteLineElement('transfer')) + .waitForElementVisible(autoCompleteLineElement('code')) + .click(autoCompleteLineElement('balance')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should autcomplete array types': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('uin') + }) + .waitForElementPresent(autoCompleteLineElement('uint')) + .click(autoCompleteLineElement('uint')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('[] mem') + }) + .waitForElementVisible(autoCompleteLineElement('memory')) + .click(autoCompleteLineElement('memory')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' somearray;') + } + ).pause(2000) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + .sendKeys('somearray.') + }) + .waitForElementVisible(autoCompleteLineElement('push')) + .waitForElementVisible(autoCompleteLineElement('pop')) + .waitForElementVisible(autoCompleteLineElement('length')) + .click(autoCompleteLineElement('length')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should see and autocomplete second import because it was imported by the first import #group2': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('secondi') + }) + .waitForElementPresent(autoCompleteLineElement('secondimport')) + .click(autoCompleteLineElement('secondimport')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(' sec;') + .sendKeys(this.Keys.ENTER) + }).pause(3000) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('sec.') + }) + .waitForElementVisible(autoCompleteLineElement('secondimportstring')) + .click(autoCompleteLineElement('secondimportstring')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + + }, + 'Should see and autocomplete imported local class #group2': function (browser: NightwatchBrowser) { + browser + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('import') + }) + .waitForElementPresent(autoCompleteLineElement('importedcontract')) + .click(autoCompleteLineElement('importedcontract')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('externalimport')) + .waitForElementVisible(autoCompleteLineElement('importbasestring')) + .waitForElementVisible(autoCompleteLineElement('importedbook')) + .waitForElementVisible(autoCompleteLineElement('importpublicstring')) + .waitForElementVisible(autoCompleteLineElement('publicimport')) + // no private + .waitForElementNotPresent(autoCompleteLineElement('importprivatestring')) + .waitForElementNotPresent(autoCompleteLineElement('privateimport')) + // no internal + .waitForElementNotPresent(autoCompleteLineElement('importinternalstring')) + .waitForElementNotPresent(autoCompleteLineElement('internalimport')) + .click(autoCompleteLineElement('importbasestring')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + + }, + 'Should autocomplete derived and local event when not using this. #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('emit base') + }) + .waitForElementVisible(autoCompleteLineElement('BaseEvent')) + .click(autoCompleteLineElement('BaseEvent')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys('msg.sender') + .sendKeys(this.Keys.TAB) + .sendKeys(this.Keys.TAB) // somehow this is needed to get the cursor to the next parameter, only for selenium + .sendKeys('3232') + .sendKeys(this.Keys.TAB) + .sendKeys(this.Keys.ENTER) + }) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('emit MyEv') + }) + .waitForElementVisible(autoCompleteLineElement('MyEvent')) + .click(autoCompleteLineElement('MyEvent')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys('3232') + .sendKeys(this.Keys.TAB) + .sendKeys(this.Keys.ENTER) + }) + }, + + 'Should type and get msg options #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('msg.') + }) + .waitForElementVisible(autoCompleteLineElement('sender')) + .waitForElementVisible(autoCompleteLineElement('data')) + .waitForElementVisible(autoCompleteLineElement('value')) + .waitForElementVisible(autoCompleteLineElement('gas')) + .waitForElementVisible(autoCompleteLineElement('sig')) + .click(autoCompleteLineElement('sender')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('balance')) + .waitForElementVisible(autoCompleteLineElement('code')) + .waitForElementVisible(autoCompleteLineElement('codehash')) + .waitForElementVisible(autoCompleteLineElement('send')) + .waitForElementVisible(autoCompleteLineElement('transfer')) + .click(autoCompleteLineElement('balance')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER) + }) + }, + 'Should bo and get book #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('bo') + }) + .waitForElementVisible(autoCompleteLineElement('book')) + .click(autoCompleteLineElement('book')) + }, + 'Should autcomplete derived struct #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('author')) + .waitForElementVisible(autoCompleteLineElement('book_id')) + .waitForElementVisible(autoCompleteLineElement('title')) + .click(autoCompleteLineElement('title')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should bo and get basebook #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('base') + }) + .waitForElementVisible(autoCompleteLineElement('basebook')) + .click(autoCompleteLineElement('basebook')) + }, + 'Should autcomplete derived struct from base class #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('author')) + .waitForElementVisible(autoCompleteLineElement('book_id')) + .waitForElementVisible(autoCompleteLineElement('title')) + .click(autoCompleteLineElement('title')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should block scoped localbbook #group2': function (browser: NightwatchBrowser) { + browser.pause(4000). + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('localb') + }) + .waitForElementVisible(autoCompleteLineElement('localbbook')) + .click(autoCompleteLineElement('localbbook')) + }, + 'Should autcomplete derived struct from block localbbook #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('author')) + .waitForElementVisible(autoCompleteLineElement('book_id')) + .waitForElementVisible(autoCompleteLineElement('title')) + .click(autoCompleteLineElement('title')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should block scoped btextbook #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('btext') + }) + .waitForElementVisible(autoCompleteLineElement('btextbook')) + .click(autoCompleteLineElement('btextbook')) + }, + 'Should autcomplete derived struct from block btextbook #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('.') + }) + .waitForElementVisible(autoCompleteLineElement('author')) + .waitForElementVisible(autoCompleteLineElement('book_id')) + .waitForElementVisible(autoCompleteLineElement('title')) + .click(autoCompleteLineElement('title')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should find private and internal local functions #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('my') + }) + .waitForElementVisible(autoCompleteLineElement('myprivatefunction')) + .waitForElementVisible(autoCompleteLineElement('myinternalfunction')) + .waitForElementVisible(autoCompleteLineElement('memory')) + .click(autoCompleteLineElement('myinternalfunction')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER) + }) + }, + 'Should find internal functions and var from base and owner #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('intern') + }) + .waitForElementVisible(autoCompleteLineElement('internalbasefunction')) + .waitForElementVisible(autoCompleteLineElement('internalstring')) + .waitForElementVisible(autoCompleteLineElement('internalbasestring')) + // keyword internal + .waitForElementVisible(autoCompleteLineElement('internal keyword')) + .click(autoCompleteLineElement('internalbasefunction')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + }) + }, + + 'Should not find external functions without this. #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('extern') + }) + .waitForElementNotPresent(autoCompleteLineElement('externalbasefunction')) + .waitForElementNotPresent(autoCompleteLineElement('myexternalfunction')) + // keyword internal + .waitForElementVisible(autoCompleteLineElement('external keyword')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + .sendKeys(this.Keys.BACK_SPACE) + }) + }, + 'Should find external functions using this. #group2': function (browser: NightwatchBrowser) { + browser. + perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(this.Keys.ENTER). + sendKeys('this.') + }) + .waitForElementVisible(autoCompleteLineElement('externalbasefunction')) + .waitForElementVisible(autoCompleteLineElement('myexternalfunction')) + }, + 'Should find public functions and vars using this. but not private & other types of nodes #group2': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible(autoCompleteLineElement('"publicbasefunction"')) + .waitForElementVisible(autoCompleteLineElement('"publicstring"')) + .waitForElementVisible(autoCompleteLineElement('"basebook"')) + .waitForElementVisible(autoCompleteLineElement('"mybook"')) + .waitForElementVisible(autoCompleteLineElement('"testing"')) + // but no private functions or vars or other types of nodes + .waitForElementNotPresent(autoCompleteLineElement('"private"')) + .waitForElementNotPresent(autoCompleteLineElement('"BaseEvent"')) + .waitForElementNotPresent(autoCompleteLineElement('"BaseEnum"')) + .waitForElementNotPresent(autoCompleteLineElement('"TestBookDefinition"')) + .click(autoCompleteLineElement('"publicbasefunction"')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions + .sendKeys(this.Keys.ENTER) + }) + }, + 'Should autocomplete local and derived ENUMS #group2': function (browser: NightwatchBrowser) { + browser.perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys('BaseEnum.') + }) + .waitForElementVisible(autoCompleteLineElement('SMALL')) + .waitForElementVisible(autoCompleteLineElement('MEDIUM')) + .waitForElementVisible(autoCompleteLineElement('LARGE')) + .click(autoCompleteLineElement('SMALL')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + .sendKeys('MyEnum.') + }) + .waitForElementVisible(autoCompleteLineElement('SMALL')) + .waitForElementVisible(autoCompleteLineElement('MEDIUM')) + .waitForElementVisible(autoCompleteLineElement('LARGE')) + .click(autoCompleteLineElement('SMALL')) + .perform(function () { + const actions = this.actions({ async: true }); + return actions. + sendKeys(';') + .sendKeys(this.Keys.ENTER) + }) + } +} + From 53cedb0dd89f7712ae26515dd08587d8f4d24022 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Thu, 21 Jul 2022 15:39:23 +0200 Subject: [PATCH 03/29] add tests --- .../src/tests/file_decorator.test.ts | 97 +++++++++++++++++++ apps/remix-ide/src/app.js | 7 +- .../src/app/plugins/file-decorator.ts | 42 +++++--- .../file-decoration-custom-icon.tsx | 2 +- .../file-decoration-error-icon.tsx | 2 +- .../file-decoration-warning-icon.tsx | 2 +- nx.json | 3 + tsconfig.base.json | 1 + workspace.json | 15 +++ 9 files changed, 153 insertions(+), 18 deletions(-) create mode 100644 apps/remix-ide-e2e/src/tests/file_decorator.test.ts diff --git a/apps/remix-ide-e2e/src/tests/file_decorator.test.ts b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts new file mode 100644 index 0000000000..40e78991b3 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts @@ -0,0 +1,97 @@ + +'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: testScript }) + .pause(2000) + .executeScript('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') + .useCss() + .waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000) + .useXpath() + .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') + } + +} +const testScript = ` +(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', + owner: 'code-parser', + bubble: true, + commment: 'error on owner', + } + let decorator2: any = { + path: 'contracts/2_Owner.sol', + isDirectory: false, + fileStateType: 'CUSTOM', + fileStateLabelClass: 'text-success', + fileStateIconClass: 'text-success', + fileStateIcon: 'U', + text: '', + owner: 'code-parser', + bubble: true, + commment: 'modified', + } + 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', + owner: 'code-parser', + bubble: true, + commment: 'warning on storage', + } + remix.call('fileDecorator' as any, 'setFileDecorators', decorator) + + decorator = { + path: 'contracts/3_Ballot.sol', + isDirectory: false, + fileStateType: 'CUSTOM', + fileStateLabelClass: '', + fileStateIconClass: '', + fileStateIcon: 'customtext', + text: 'w', + owner: 'dgit', + bubble: true, + commment: '', + } + remix.call('fileDecorator' as any, 'setFileDecorators', decorator) + + remix.call('fileDecorator' as any, 'clearFileDecorators', 'dgit') + + })()` \ No newline at end of file diff --git a/apps/remix-ide/src/app.js b/apps/remix-ide/src/app.js index d9c93fd4f6..4270ecce92 100644 --- a/apps/remix-ide/src/app.js +++ b/apps/remix-ide/src/app.js @@ -31,6 +31,7 @@ import { FoundryProvider } from './app/tabs/foundry-provider' import { ExternalHttpProvider } from './app/tabs/external-http-provider' import { Injected0ptimismProvider } from './app/tabs/injected-optimism-provider' import { InjectedArbitrumOneProvider } from './app/tabs/injected-arbitrum-one-provider' +import { FileDecorator } from './app/plugins/file-decorator' const isElectron = require('is-electron') @@ -156,6 +157,9 @@ class AppComponent { // ----------------- Storage plugin --------------------------------- const storagePlugin = new StoragePlugin() + // ------- FILE DECORATOR PLUGIN ------------------ + const fileDecorator = new FileDecorator() + //----- search const search = new SearchPlugin() @@ -239,6 +243,7 @@ class AppComponent { fetchAndCompile, dGitProvider, storagePlugin, + fileDecorator, hardhatProvider, ganacheProvider, foundryProvider, @@ -363,7 +368,7 @@ class AppComponent { await this.appManager.activatePlugin(['sidePanel']) // activating host plugin separately await this.appManager.activatePlugin(['home']) await this.appManager.activatePlugin(['settings', 'config']) - await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler']) + await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'fileDecorator', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler']) await this.appManager.activatePlugin(['settings']) await this.appManager.activatePlugin(['walkthrough','storage', 'search','compileAndRun', 'recorder']) diff --git a/apps/remix-ide/src/app/plugins/file-decorator.ts b/apps/remix-ide/src/app/plugins/file-decorator.ts index 60a14ef47f..5332db1508 100644 --- a/apps/remix-ide/src/app/plugins/file-decorator.ts +++ b/apps/remix-ide/src/app/plugins/file-decorator.ts @@ -18,22 +18,14 @@ export class FileDecorator extends Plugin { constructor() { super(profile) } + + /** * * @param fileStates Array of file states */ async setFileDecorators(fileStates: fileDecoration[] | fileDecoration) { const workspace = await this.call('filePanel', 'getCurrentWorkspace') - function sortByPath( a: fileDecoration, b: fileDecoration ) { - if ( a.path < b.path ){ - return -1; - } - if ( a.path > b.path ){ - return 1; - } - return 0; - } - 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) => { @@ -46,15 +38,37 @@ export class FileDecorator extends Plugin { return index == -1 }) const newState = [...filteredState, ...fileStatesPayload].sort(sortByPath) - + if (!deepequal(newState, this._fileStates)) { this._fileStates = newState this.emit('fileDecoratorsChanged', this._fileStates) } } - async clearFileDecorators() { - this._fileStates = [] - this.emit('fileDecoratorsChanged', []) + async clearFileDecorators(owner? : string) { + if(!owner) { + this._fileStates = [] + this.emit('fileDecoratorsChanged', []) + } else { + const filteredState = this._fileStates.filter((state) => { + return state.owner != owner + }) + const newState = [...filteredState].sort(sortByPath) + + if (!deepequal(newState, this._fileStates)) { + this._fileStates = newState + this.emit('fileDecoratorsChanged', this._fileStates) + } + } + } +} + +const sortByPath = (a: fileDecoration, b: fileDecoration) => { + if (a.path < b.path) { + return -1; + } + if (a.path > b.path) { + return 1; } + return 0; } \ No newline at end of file diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx index c157bc66c0..2cfec2ac97 100644 --- a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-custom-icon.tsx @@ -5,7 +5,7 @@ import { fileDecoration } from '../../types' const FileDecorationCustomIcon = (props: { fileDecoration: fileDecoration }) => { - return <> + return <> {props.fileDecoration.fileStateIcon} } diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx index 2bb61fd97f..5a9c48b555 100644 --- a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-error-icon.tsx @@ -7,7 +7,7 @@ const FileDecorationErrorIcon = (props: { fileDecoration: fileDecoration }) => { return <> - {props.fileDecoration.text} + {props.fileDecoration.text} } diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx index 1c027c9854..9bfd368506 100644 --- a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-warning-icon.tsx @@ -5,7 +5,7 @@ import { fileDecoration } from '../../types' const FileDecorationWarningIcon = (props: { fileDecoration: fileDecoration }) => { - return <>{props.fileDecoration.text} + return <>{props.fileDecoration.text} } export default FileDecorationWarningIcon \ No newline at end of file diff --git a/nx.json b/nx.json index 13cd26c2b2..734bd55a4c 100644 --- a/nx.json +++ b/nx.json @@ -170,6 +170,9 @@ "remix-ui-permission-handler": { "tags": [] }, + "remix-ui-file-decorators": { + "tags": [] + }, "remix-ui-tooltip-popup": { "tags": [] } diff --git a/tsconfig.base.json b/tsconfig.base.json index e97aaf9aff..9174b23427 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -88,6 +88,7 @@ "@remix-ui/permission-handler": [ "libs/remix-ui/permission-handler/src/index.ts" ], + "@remix-ui/file-decorators": ["libs/remix-ui/file-decorators/src/index.ts"], "@remix-ui/tooltip-popup": ["libs/remix-ui/tooltip-popup/src/index.ts"] } }, diff --git a/workspace.json b/workspace.json index b1ec5f63d5..fd0935ee57 100644 --- a/workspace.json +++ b/workspace.json @@ -1270,6 +1270,21 @@ } } } + }, + "remix-ui-file-decorators": { + "root": "libs/remix-ui/file-decorators", + "sourceRoot": "libs/remix-ui/file-decorators/src", + "projectType": "library", + "architect": { + "lint": { + "builder": "@nrwl/linter:lint", + "options": { + "linter": "eslint", + "tsConfig": ["libs/remix-ui/file-decorators/tsconfig.lib.json"], + "exclude": ["**/node_modules/**", "!libs/remix-ui/file-decorators/**/*"] + } + } + } } }, "cli": { From d9a09cdc2999dd50f222c9e2432821b5aed9bac6 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Thu, 21 Jul 2022 15:40:27 +0200 Subject: [PATCH 04/29] rm test --- .../src/tests/editorAutoComplete.test.ts | 518 ------------------ 1 file changed, 518 deletions(-) delete mode 100644 apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts diff --git a/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts b/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts deleted file mode 100644 index 8dc7bd4440..0000000000 --- a/apps/remix-ide-e2e/src/tests/editorAutoComplete.test.ts +++ /dev/null @@ -1,518 +0,0 @@ -'use strict' -import { NightwatchBrowser } from 'nightwatch' -import init from '../helpers/init' -import examples from '../examples/editor-test-contracts' - -const autoCompleteLineElement = (name: string) => { - return `//*[@class='editor-widget suggest-widget visible']//*[@class='contents' and contains(.,'${name}')]` -} - -module.exports = { - '@disabled': true, - before: function (browser: NightwatchBrowser, done: VoidFunction) { - init(browser, done, 'http://127.0.0.1:8080', false) - }, - 'Should add test and base files #group2': function (browser: NightwatchBrowser) { - browser.addFile(examples.testContract.name, examples.testContract) - .addFile(examples.baseContract.name, examples.baseContract) - .addFile(examples.import1Contract.name, examples.import1Contract) - .addFile(examples.baseOfBaseContract.name, examples.baseOfBaseContract) - .addFile(examples.secondimport.name, examples.secondimport) - .addFile(examples.importbase.name, examples.importbase) - .openFile(examples.testContract.name) - }, - 'Should put cursor in the () of the function #group2': function (browser: NightwatchBrowser) { - browser.scrollToLine(36) - const path = "//*[@class='view-line' and contains(.,'myprivatefunction') and contains(.,'private')]//span//span[contains(.,'(')]" - browser.waitForElementVisible('#editorView') - .useXpath() - .click(path).pause(1000) - }, - 'Should complete variable declaration types in a function definition #group2': function (browser: NightwatchBrowser) { - browser - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('uint25') - }) - .waitForElementPresent(autoCompleteLineElement('uint256')) - .click(autoCompleteLineElement('uint256')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' abc') - .sendKeys(this.Keys.ENTER) // we need to split lines for FF texts to pass because the screen is too narrow - .sendKeys(', testb') - }) - .waitForElementPresent(autoCompleteLineElement('"TestBookDefinition"')) - .click(autoCompleteLineElement('"TestBookDefinition"')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' memo') - }) - .waitForElementPresent(autoCompleteLineElement('memory')) - .click(autoCompleteLineElement('memory')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' btextbook') - .sendKeys(this.Keys.ENTER) - .sendKeys(', BaseB') - }) - .waitForElementPresent(autoCompleteLineElement('"BaseBook"')) - .click(autoCompleteLineElement('"BaseBook"')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' stor') - }) - .waitForElementPresent(autoCompleteLineElement('storage')) - .click(autoCompleteLineElement('storage')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' localbbook') - }).pause(3000) - }, - 'Should put cursor at the end of function #group2': function (browser: NightwatchBrowser) { - - const path = "//*[@class='view-line' and contains(.,'localbbook') and contains(.,'private')]//span//span[contains(.,'{')]" - browser - .useXpath() - .click(path).pause(1000) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - // right arrow key - sendKeys(this.Keys.ARROW_RIGHT). - sendKeys(this.Keys.ARROW_RIGHT) - }) - }, - 'Should autcomplete address types': function (browser: NightwatchBrowser) { - browser - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('addre') - }) - .waitForElementPresent(autoCompleteLineElement('address')) - .click(autoCompleteLineElement('address')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' someaddress;') - .sendKeys(this.Keys.ENTER) - }).pause(2000) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('someaddress.') - }) - .waitForElementVisible(autoCompleteLineElement('balance')) - .waitForElementVisible(autoCompleteLineElement('send')) - .waitForElementVisible(autoCompleteLineElement('transfer')) - .waitForElementVisible(autoCompleteLineElement('code')) - .click(autoCompleteLineElement('balance')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should autcomplete array types': function (browser: NightwatchBrowser) { - browser - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('uin') - }) - .waitForElementPresent(autoCompleteLineElement('uint')) - .click(autoCompleteLineElement('uint')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('[] mem') - }) - .waitForElementVisible(autoCompleteLineElement('memory')) - .click(autoCompleteLineElement('memory')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' somearray;') - } - ).pause(2000) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys(this.Keys.ENTER) - .sendKeys('somearray.') - }) - .waitForElementVisible(autoCompleteLineElement('push')) - .waitForElementVisible(autoCompleteLineElement('pop')) - .waitForElementVisible(autoCompleteLineElement('length')) - .click(autoCompleteLineElement('length')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should see and autocomplete second import because it was imported by the first import #group2': function (browser: NightwatchBrowser) { - browser - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('secondi') - }) - .waitForElementPresent(autoCompleteLineElement('secondimport')) - .click(autoCompleteLineElement('secondimport')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(' sec;') - .sendKeys(this.Keys.ENTER) - }).pause(3000) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('sec.') - }) - .waitForElementVisible(autoCompleteLineElement('secondimportstring')) - .click(autoCompleteLineElement('secondimportstring')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - - }, - 'Should see and autocomplete imported local class #group2': function (browser: NightwatchBrowser) { - browser - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('import') - }) - .waitForElementPresent(autoCompleteLineElement('importedcontract')) - .click(autoCompleteLineElement('importedcontract')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('.') - }) - .waitForElementVisible(autoCompleteLineElement('externalimport')) - .waitForElementVisible(autoCompleteLineElement('importbasestring')) - .waitForElementVisible(autoCompleteLineElement('importedbook')) - .waitForElementVisible(autoCompleteLineElement('importpublicstring')) - .waitForElementVisible(autoCompleteLineElement('publicimport')) - // no private - .waitForElementNotPresent(autoCompleteLineElement('importprivatestring')) - .waitForElementNotPresent(autoCompleteLineElement('privateimport')) - // no internal - .waitForElementNotPresent(autoCompleteLineElement('importinternalstring')) - .waitForElementNotPresent(autoCompleteLineElement('internalimport')) - .click(autoCompleteLineElement('importbasestring')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - - }, - 'Should autocomplete derived and local event when not using this. #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('emit base') - }) - .waitForElementVisible(autoCompleteLineElement('BaseEvent')) - .click(autoCompleteLineElement('BaseEvent')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys('msg.sender') - .sendKeys(this.Keys.TAB) - .sendKeys(this.Keys.TAB) // somehow this is needed to get the cursor to the next parameter, only for selenium - .sendKeys('3232') - .sendKeys(this.Keys.TAB) - .sendKeys(this.Keys.ENTER) - }) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('emit MyEv') - }) - .waitForElementVisible(autoCompleteLineElement('MyEvent')) - .click(autoCompleteLineElement('MyEvent')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys('3232') - .sendKeys(this.Keys.TAB) - .sendKeys(this.Keys.ENTER) - }) - }, - - 'Should type and get msg options #group2': function (browser: NightwatchBrowser) { - browser. - perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER). - sendKeys('msg.') - }) - .waitForElementVisible(autoCompleteLineElement('sender')) - .waitForElementVisible(autoCompleteLineElement('data')) - .waitForElementVisible(autoCompleteLineElement('value')) - .waitForElementVisible(autoCompleteLineElement('gas')) - .waitForElementVisible(autoCompleteLineElement('sig')) - .click(autoCompleteLineElement('sender')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('.') - }) - .waitForElementVisible(autoCompleteLineElement('balance')) - .waitForElementVisible(autoCompleteLineElement('code')) - .waitForElementVisible(autoCompleteLineElement('codehash')) - .waitForElementVisible(autoCompleteLineElement('send')) - .waitForElementVisible(autoCompleteLineElement('transfer')) - .click(autoCompleteLineElement('balance')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER) - }) - }, - 'Should bo and get book #group2': function (browser: NightwatchBrowser) { - browser. - perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER). - sendKeys('bo') - }) - .waitForElementVisible(autoCompleteLineElement('book')) - .click(autoCompleteLineElement('book')) - }, - 'Should autcomplete derived struct #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('.') - }) - .waitForElementVisible(autoCompleteLineElement('author')) - .waitForElementVisible(autoCompleteLineElement('book_id')) - .waitForElementVisible(autoCompleteLineElement('title')) - .click(autoCompleteLineElement('title')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should bo and get basebook #group2': function (browser: NightwatchBrowser) { - browser. - perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER). - sendKeys('base') - }) - .waitForElementVisible(autoCompleteLineElement('basebook')) - .click(autoCompleteLineElement('basebook')) - }, - 'Should autcomplete derived struct from base class #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('.') - }) - .waitForElementVisible(autoCompleteLineElement('author')) - .waitForElementVisible(autoCompleteLineElement('book_id')) - .waitForElementVisible(autoCompleteLineElement('title')) - .click(autoCompleteLineElement('title')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should block scoped localbbook #group2': function (browser: NightwatchBrowser) { - browser.pause(4000). - perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER). - sendKeys('localb') - }) - .waitForElementVisible(autoCompleteLineElement('localbbook')) - .click(autoCompleteLineElement('localbbook')) - }, - 'Should autcomplete derived struct from block localbbook #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('.') - }) - .waitForElementVisible(autoCompleteLineElement('author')) - .waitForElementVisible(autoCompleteLineElement('book_id')) - .waitForElementVisible(autoCompleteLineElement('title')) - .click(autoCompleteLineElement('title')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should block scoped btextbook #group2': function (browser: NightwatchBrowser) { - browser. - perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER). - sendKeys('btext') - }) - .waitForElementVisible(autoCompleteLineElement('btextbook')) - .click(autoCompleteLineElement('btextbook')) - }, - 'Should autcomplete derived struct from block btextbook #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('.') - }) - .waitForElementVisible(autoCompleteLineElement('author')) - .waitForElementVisible(autoCompleteLineElement('book_id')) - .waitForElementVisible(autoCompleteLineElement('title')) - .click(autoCompleteLineElement('title')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should find private and internal local functions #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('my') - }) - .waitForElementVisible(autoCompleteLineElement('myprivatefunction')) - .waitForElementVisible(autoCompleteLineElement('myinternalfunction')) - .waitForElementVisible(autoCompleteLineElement('memory')) - .click(autoCompleteLineElement('myinternalfunction')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER) - }) - }, - 'Should find internal functions and var from base and owner #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('intern') - }) - .waitForElementVisible(autoCompleteLineElement('internalbasefunction')) - .waitForElementVisible(autoCompleteLineElement('internalstring')) - .waitForElementVisible(autoCompleteLineElement('internalbasestring')) - // keyword internal - .waitForElementVisible(autoCompleteLineElement('internal keyword')) - .click(autoCompleteLineElement('internalbasefunction')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys(this.Keys.ENTER) - }) - }, - - 'Should not find external functions without this. #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('extern') - }) - .waitForElementNotPresent(autoCompleteLineElement('externalbasefunction')) - .waitForElementNotPresent(autoCompleteLineElement('myexternalfunction')) - // keyword internal - .waitForElementVisible(autoCompleteLineElement('external keyword')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys(this.Keys.BACK_SPACE) - .sendKeys(this.Keys.BACK_SPACE) - .sendKeys(this.Keys.BACK_SPACE) - .sendKeys(this.Keys.BACK_SPACE) - .sendKeys(this.Keys.BACK_SPACE) - .sendKeys(this.Keys.BACK_SPACE) - .sendKeys(this.Keys.BACK_SPACE) - }) - }, - 'Should find external functions using this. #group2': function (browser: NightwatchBrowser) { - browser. - perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(this.Keys.ENTER). - sendKeys('this.') - }) - .waitForElementVisible(autoCompleteLineElement('externalbasefunction')) - .waitForElementVisible(autoCompleteLineElement('myexternalfunction')) - }, - 'Should find public functions and vars using this. but not private & other types of nodes #group2': function (browser: NightwatchBrowser) { - browser - .waitForElementVisible(autoCompleteLineElement('"publicbasefunction"')) - .waitForElementVisible(autoCompleteLineElement('"publicstring"')) - .waitForElementVisible(autoCompleteLineElement('"basebook"')) - .waitForElementVisible(autoCompleteLineElement('"mybook"')) - .waitForElementVisible(autoCompleteLineElement('"testing"')) - // but no private functions or vars or other types of nodes - .waitForElementNotPresent(autoCompleteLineElement('"private"')) - .waitForElementNotPresent(autoCompleteLineElement('"BaseEvent"')) - .waitForElementNotPresent(autoCompleteLineElement('"BaseEnum"')) - .waitForElementNotPresent(autoCompleteLineElement('"TestBookDefinition"')) - .click(autoCompleteLineElement('"publicbasefunction"')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions - .sendKeys(this.Keys.ENTER) - }) - }, - 'Should autocomplete local and derived ENUMS #group2': function (browser: NightwatchBrowser) { - browser.perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys('BaseEnum.') - }) - .waitForElementVisible(autoCompleteLineElement('SMALL')) - .waitForElementVisible(autoCompleteLineElement('MEDIUM')) - .waitForElementVisible(autoCompleteLineElement('LARGE')) - .click(autoCompleteLineElement('SMALL')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - .sendKeys('MyEnum.') - }) - .waitForElementVisible(autoCompleteLineElement('SMALL')) - .waitForElementVisible(autoCompleteLineElement('MEDIUM')) - .waitForElementVisible(autoCompleteLineElement('LARGE')) - .click(autoCompleteLineElement('SMALL')) - .perform(function () { - const actions = this.actions({ async: true }); - return actions. - sendKeys(';') - .sendKeys(this.Keys.ENTER) - }) - } -} - From 1e4c047652472960f0f41f52757c9979472d1e68 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Thu, 21 Jul 2022 15:49:43 +0200 Subject: [PATCH 05/29] typo fix --- apps/remix-ide-e2e/src/tests/file_decorator.test.ts | 8 ++++---- .../filedecorationicons/file-decoration-tooltip.tsx | 6 +++--- libs/remix-ui/file-decorators/src/lib/helper/index.tsx | 6 +++--- libs/remix-ui/file-decorators/src/lib/types/index.ts | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/file_decorator.test.ts b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts index 40e78991b3..2aa226397c 100644 --- a/apps/remix-ide-e2e/src/tests/file_decorator.test.ts +++ b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts @@ -48,7 +48,7 @@ const testScript = ` text: '2', owner: 'code-parser', bubble: true, - commment: 'error on owner', + comment: 'error on owner', } let decorator2: any = { path: 'contracts/2_Owner.sol', @@ -60,7 +60,7 @@ const testScript = ` text: '', owner: 'code-parser', bubble: true, - commment: 'modified', + comment: 'modified', } remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2]) @@ -74,7 +74,7 @@ const testScript = ` text: '2', owner: 'code-parser', bubble: true, - commment: 'warning on storage', + comment: 'warning on storage', } remix.call('fileDecorator' as any, 'setFileDecorators', decorator) @@ -88,7 +88,7 @@ const testScript = ` text: 'w', owner: 'dgit', bubble: true, - commment: '', + comment: '', } remix.call('fileDecorator' as any, 'setFileDecorators', decorator) diff --git a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx index 7c9149c14d..1d87846192 100644 --- a/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx +++ b/libs/remix-ui/file-decorators/src/lib/components/filedecorationicons/file-decoration-tooltip.tsx @@ -9,9 +9,9 @@ const FileDecorationTooltip = (props: { }, ) => { const getComments = function (fileDecoration: fileDecoration) { - if (fileDecoration.commment) { - const commments = Array.isArray(fileDecoration.commment) ? fileDecoration.commment : [fileDecoration.commment] - return commments.map((comment, index) => { + if (fileDecoration.comment) { + const comments = Array.isArray(fileDecoration.comment) ? fileDecoration.comment : [fileDecoration.comment] + return comments.map((comment, index) => { return
{comment}

}) } diff --git a/libs/remix-ui/file-decorators/src/lib/helper/index.tsx b/libs/remix-ui/file-decorators/src/lib/helper/index.tsx index 1afe91b0cf..10bad7d3fc 100644 --- a/libs/remix-ui/file-decorators/src/lib/helper/index.tsx +++ b/libs/remix-ui/file-decorators/src/lib/helper/index.tsx @@ -2,9 +2,9 @@ import React from "react" import { fileDecoration } from "../types" export const getComments = function (fileDecoration: fileDecoration) { - if(fileDecoration.commment){ - const commments = Array.isArray(fileDecoration.commment) ? fileDecoration.commment : [fileDecoration.commment] - return commments.map((comment, index) => { + if(fileDecoration.comment){ + const comments = Array.isArray(fileDecoration.comment) ? fileDecoration.comment : [fileDecoration.comment] + return comments.map((comment, index) => { return
{comment}

}) } diff --git a/libs/remix-ui/file-decorators/src/lib/types/index.ts b/libs/remix-ui/file-decorators/src/lib/types/index.ts index 35178721e0..ef6620434a 100644 --- a/libs/remix-ui/file-decorators/src/lib/types/index.ts +++ b/libs/remix-ui/file-decorators/src/lib/types/index.ts @@ -17,7 +17,7 @@ export enum fileDecorationType { owner: string, workspace?: any tooltip?: string - commment?: string[] | string + comment?: string[] | string } export interface FileType { From 528f53692c064c87cbd7d2e9b812232b9d068374 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 22 Jul 2022 11:53:33 +0200 Subject: [PATCH 06/29] updates --- .../src/tests/file_decorator.test.ts | 96 ++++++++++++------- .../src/app/plugins/file-decorator.ts | 39 ++++---- .../file-decorators/src/lib/types/index.ts | 2 +- 3 files changed, 86 insertions(+), 51 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/file_decorator.test.ts b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts index 2aa226397c..9b47bbc61f 100644 --- a/apps/remix-ide-e2e/src/tests/file_decorator.test.ts +++ b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts @@ -11,31 +11,50 @@ module.exports = { '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: testScript }) - .pause(2000) - .executeScript('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') - .useCss() - .waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000) - .useXpath() - .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') + .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) + .executeScript('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) + .executeScript('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) + .executeScript('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 testScript = ` +const testScriptSet = ` (async () => { remix.call('fileDecorator' as any, 'clearFileDecorators') let decorator: any = { @@ -46,7 +65,6 @@ const testScript = ` fileStateIconClass: '', fileStateIcon: '', text: '2', - owner: 'code-parser', bubble: true, comment: 'error on owner', } @@ -58,11 +76,10 @@ const testScript = ` fileStateIconClass: 'text-success', fileStateIcon: 'U', text: '', - owner: 'code-parser', bubble: true, comment: 'modified', } - remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2]) + await remix.call('fileDecorator' as any, 'setFileDecorators', [decorator, decorator2]) decorator = { path: 'contracts/1_Storage.sol', @@ -72,11 +89,10 @@ const testScript = ` fileStateIconClass: '', fileStateIcon: '', text: '2', - owner: 'code-parser', bubble: true, comment: 'warning on storage', } - remix.call('fileDecorator' as any, 'setFileDecorators', decorator) + await remix.call('fileDecorator' as any, 'setFileDecorators', decorator) decorator = { path: 'contracts/3_Ballot.sol', @@ -85,13 +101,25 @@ const testScript = ` fileStateLabelClass: '', fileStateIconClass: '', fileStateIcon: 'customtext', - text: 'w', - owner: 'dgit', + text: 'with text', bubble: true, - comment: '', + comment: 'custom comment', } - remix.call('fileDecorator' as any, 'setFileDecorators', decorator) - - remix.call('fileDecorator' as any, 'clearFileDecorators', 'dgit') + await remix.call('fileDecorator' as any, 'setFileDecorators', decorator) - })()` \ No newline at end of file + })()` + + +const testScriptClearBallot = ` + (async () => { + + await remix.call('fileDecorator' as any, 'clearFileDecorators', 'contracts/3_Ballot.sol') + + })()` + +const testScriptClear = ` + (async () => { + await remix.call('fileDecorator' as any, 'clearAllFileDecorators') + + + })()` \ No newline at end of file diff --git a/apps/remix-ide/src/app/plugins/file-decorator.ts b/apps/remix-ide/src/app/plugins/file-decorator.ts index 5332db1508..5fc495bcf1 100644 --- a/apps/remix-ide/src/app/plugins/file-decorator.ts +++ b/apps/remix-ide/src/app/plugins/file-decorator.ts @@ -8,7 +8,7 @@ import { fileDecoration } from '@remix-ui/file-decorators' const profile = { name: 'fileDecorator', desciption: 'Keeps decorators of the files', - methods: ['setFileDecorators', 'clearFileDecorators'], + methods: ['setFileDecorators', 'clearFileDecorators', 'clearAllFileDecorators'], events: ['fileDecoratorsChanged'], version: '0.0.1' } @@ -25,15 +25,17 @@ export class FileDecorator extends Plugin { * @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 payloadFileState.owner == state.owner && payloadFileState.path == state.path + return from == state.owner && payloadFileState.path == state.path }) return index == -1 }) @@ -45,21 +47,26 @@ export class FileDecorator extends Plugin { } } - async clearFileDecorators(owner? : string) { - if(!owner) { - this._fileStates = [] - this.emit('fileDecoratorsChanged', []) - } else { - const filteredState = this._fileStates.filter((state) => { - return state.owner != owner - }) - const newState = [...filteredState].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', []) } } diff --git a/libs/remix-ui/file-decorators/src/lib/types/index.ts b/libs/remix-ui/file-decorators/src/lib/types/index.ts index ef6620434a..fa783ec6e5 100644 --- a/libs/remix-ui/file-decorators/src/lib/types/index.ts +++ b/libs/remix-ui/file-decorators/src/lib/types/index.ts @@ -14,7 +14,7 @@ export enum fileDecorationType { fileStateIcon: string | HTMLDivElement | JSX.Element, bubble: boolean, text?: string, - owner: string, + owner?: string, workspace?: any tooltip?: string comment?: string[] | string From ead34726c7042af1be35cea7fd4a59f953983921 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 22 Jul 2022 11:57:25 +0200 Subject: [PATCH 07/29] clear on workspace change --- apps/remix-ide/src/app/plugins/file-decorator.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/remix-ide/src/app/plugins/file-decorator.ts b/apps/remix-ide/src/app/plugins/file-decorator.ts index 5fc495bcf1..c81c1894ff 100644 --- a/apps/remix-ide/src/app/plugins/file-decorator.ts +++ b/apps/remix-ide/src/app/plugins/file-decorator.ts @@ -19,6 +19,11 @@ export class FileDecorator extends Plugin { super(profile) } + onActivation(): void { + this.on('filePanel', 'setWorkspace', async () => { + await this.clearAllFileDecorators() + }) + } /** * From 1d8f36dcb3e0f4903d9378dc81e953d1a3dbee03 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 22 Jul 2022 12:06:42 +0200 Subject: [PATCH 08/29] cherry pick editor --- apps/remix-ide/src/app/editor/editor.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/remix-ide/src/app/editor/editor.js b/apps/remix-ide/src/app/editor/editor.js index 97dc63f988..7725387688 100644 --- a/apps/remix-ide/src/app/editor/editor.js +++ b/apps/remix-ide/src/app/editor/editor.js @@ -13,7 +13,7 @@ const profile = { name: 'editor', description: 'service - editor', version: packageJson.version, - methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition'] + methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open'] } class Editor extends Plugin { @@ -26,8 +26,8 @@ class Editor extends Plugin { remixDark: 'remix-dark' } - this.registeredDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {} } - this.currentDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {} } + this.registeredDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} } + this.currentDecorations = { sourceAnnotationsPerFile: {}, markerPerFile: {}, lineTextPerFile: {} } // Init this.event = new EventManager() @@ -567,6 +567,18 @@ class Editor extends Plugin { this.clearDecorationsByPlugin(session, from, 'markerPerFile', this.registeredDecorations, this.currentDecorations) } } + + async addLineText (lineText, filePath) { + filePath = filePath || this.currentFile + await this.addDecoration(lineText, filePath, 'lineTextPerFile') + } + + discardLineTexts() { + const { from } = this.currentRequest + for (const session in this.sessions) { + this.clearDecorationsByPlugin(session, from, 'lineTextPerFile', this.registeredDecorations, this.currentDecorations) + } + } } module.exports = Editor From 5523be0f89e90957c43fc78a8a667c7fdf60a805 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 22 Jul 2022 12:33:37 +0200 Subject: [PATCH 09/29] types --- .../editor/src/lib/remix-ui-editor.tsx | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx index 46a5e76d9b..59637a2438 100644 --- a/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx +++ b/libs/remix-ui/editor/src/lib/remix-ui-editor.tsx @@ -4,6 +4,7 @@ import Editor, { loader } from '@monaco-editor/react' import { reducerActions, reducerListener, initialState } from './actions/editor' import { language, conf } from './syntax' import { cairoLang, cairoConf } from './cairoSyntax' +import { IMarkdownString } from 'monaco-editor' import './remix-ui-editor.css' import { loadTypes } from './web-types' @@ -39,6 +40,25 @@ type sourceMarker = { hide: boolean } +export type lineText = { + position: { + start: { + line: number + column: number + }, + end: { + line: number + column: number + } + }, + from?: string // plugin name + content: string + className: string + afterContentClassName: string + hide: boolean, + hoverMessage: IMarkdownString | IMarkdownString[] +} + loader.config({ paths: { vs: 'assets/js/monaco-editor/dev/vs' } }) export type DecorationsReturn = { @@ -259,7 +279,7 @@ export const EditorUI = (props: EditorUIProps) => { } }, [props.currentFile]) - const convertToMonacoDecoration = (decoration: sourceAnnotation | sourceMarker, typeOfDecoration: string) => { + const convertToMonacoDecoration = (decoration: lineText | sourceAnnotation | sourceMarker, typeOfDecoration: string) => { if (typeOfDecoration === 'sourceAnnotationsPerFile') { decoration = decoration as sourceAnnotation return { @@ -289,6 +309,19 @@ export const EditorUI = (props: EditorUIProps) => { } } } + if (typeOfDecoration === 'lineTextPerFile') { + const lineTextDecoration = decoration as lineText + return { + type: typeOfDecoration, + range: new monacoRef.current.Range(lineTextDecoration.position.start.line + 1, lineTextDecoration.position.start.column + 1, lineTextDecoration.position.start.line + 1, 1024), + options: { + after: { content: ` ${lineTextDecoration.content}`, inlineClassName: `${lineTextDecoration.className}` }, + afterContentClassName: `${lineTextDecoration.afterContentClassName}`, + hoverMessage : lineTextDecoration.hoverMessage + }, + + } + } } props.editorAPI.clearDecorationsByPlugin = (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => { From 17b7615ceac15c187906761b1a381c57168c0102 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 22 Jul 2022 12:34:23 +0200 Subject: [PATCH 10/29] rm open call --- apps/remix-ide/src/app/editor/editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/remix-ide/src/app/editor/editor.js b/apps/remix-ide/src/app/editor/editor.js index 7725387688..34b9c172e5 100644 --- a/apps/remix-ide/src/app/editor/editor.js +++ b/apps/remix-ide/src/app/editor/editor.js @@ -13,7 +13,7 @@ const profile = { name: 'editor', description: 'service - editor', version: packageJson.version, - methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition', 'open'] + methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addLineText', 'discardLineTexts', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition'] } class Editor extends Plugin { From 315652f12939f68ee309a9c6a64ec9b78842eaa4 Mon Sep 17 00:00:00 2001 From: filip mertens Date: Fri, 22 Jul 2022 13:28:22 +0200 Subject: [PATCH 11/29] add tests --- .../src/tests/editor_line_text.test.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 apps/remix-ide-e2e/src/tests/editor_line_text.test.ts diff --git a/apps/remix-ide-e2e/src/tests/editor_line_text.test.ts b/apps/remix-ide-e2e/src/tests/editor_line_text.test.ts new file mode 100644 index 0000000000..d0d079cf68 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/editor_line_text.test.ts @@ -0,0 +1,69 @@ +'use strict' + +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' + +module.exports = { + + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done, 'http://127.0.0.1:8080', true) + }, + 'Should add line texts': function (browser: NightwatchBrowser) { + browser + .openFile('contracts') + .openFile('contracts/1_Storage.sol') + .addFile('scripts/addlinetext.ts', {content: addLineText}) + .pause(2000) + .executeScript('remix.exeCurrent()') + .pause(4000) + .openFile('contracts/1_Storage.sol') + .useXpath() + .waitForElementVisible("//*[@class='view-line' and contains(.,'contract')]//span//span[contains(.,'mylinetext1')]") + .waitForElementVisible("//*[@class='view-line' and contains(.,'function')]//span//span[contains(.,'mylinetext2')]") + } +} + +const addLineText = ` +(async () => { + + await remix.call('editor', 'discardLineTexts' as any) + let linetext = { + content: 'mylinetext1', + position: { + start: { + line: 9, + column: 1, + } + }, + hide: false, + className: 'text-muted small', + afterContentClassName: 'text-muted small fas fa-gas-pump pl-4', + hoverMessage: [{ + value: 'hovering1', + }, + ], + } + + await remix.call('editor', 'addLineText' as any, linetext, 'contracts/1_Storage.sol') + + + linetext = { + content: 'mylinetext2', + position: { + start: { + line: 17, + column: 1, + } + }, + hide: false, + className: 'text-muted small', + afterContentClassName: 'text-muted small fas fa-gas-pump pl-4', + hoverMessage: [{ + value: 'hovering2', + }, + ], + } + + await remix.call('editor', 'addLineText' as any, linetext, 'contracts/1_Storage.sol') + +})()` \ No newline at end of file From 043069f1120f832e60832269fe0a6e0071a58167 Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Wed, 3 Aug 2022 12:13:10 +0200 Subject: [PATCH 12/29] fix workspace --- workspace.json | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/workspace.json b/workspace.json index 95d40e5000..900de9173e 100644 --- a/workspace.json +++ b/workspace.json @@ -1505,35 +1505,16 @@ "builder": "@nrwl/linter:lint", "options": { "linter": "eslint", - "tsConfig": ["libs/remix-ui/file-decorators/tsconfig.lib.json"], - "exclude": ["**/node_modules/**", "!libs/remix-ui/file-decorators/**/*"] + "tsConfig": [ + "libs/remix-ui/file-decorators/tsconfig.lib.json" + ], + "exclude": [ + "**/node_modules/**", + "!libs/remix-ui/file-decorators/**/*" + ] } } } - } - }, - "cli": { - "defaultCollection": "@nrwl/react" - }, - "schematics": { - "@nrwl/workspace": { - "library": { - "linter": "eslint" - } - }, - "@nrwl/react": { - "application": { - "style": "css", - "linter": "eslint", - "babel": true - }, - "component": { - "style": "css" - }, - "library": { - "style": "css", - "linter": "eslint" - } }, "vyper": { "root": "apps/vyper", @@ -1605,7 +1586,8 @@ "linter": "eslint", "config": "apps/vyper/.eslintrc", "files": [ - "apps/vyper/src/**/*.js", "apps/vyper/src/**/*.ts" + "apps/vyper/src/**/*.js", + "apps/vyper/src/**/*.ts" ], "exclude": [ "**/node_modules/**" From 917dd66012cca04c5bd4f7a84097758fe26877d1 Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Thu, 4 Aug 2022 10:04:06 +0200 Subject: [PATCH 13/29] fix execute --- apps/remix-ide-e2e/src/tests/file_decorator.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/file_decorator.test.ts b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts index 9b47bbc61f..3483853cad 100644 --- a/apps/remix-ide-e2e/src/tests/file_decorator.test.ts +++ b/apps/remix-ide-e2e/src/tests/file_decorator.test.ts @@ -17,7 +17,7 @@ module.exports = { .openFile('contracts/3_Ballot.sol') .addFile('scripts/decorators.ts', { content: testScriptSet }) .pause(2000) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .pause(4000) .useXpath() .waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', '2') @@ -38,7 +38,7 @@ module.exports = { .useCss() .addFile('scripts/clearballot.ts', { content: testScriptClearBallot }) .pause(2000) - .executeScript('remix.exeCurrent()') + .executeScriptInTerminal('remix.exeCurrent()') .pause(4000) .waitForElementNotPresent('[data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 10000) }, @@ -46,7 +46,7 @@ module.exports = { browser .addFile('scripts/clearall.ts', { content: testScriptClear }) .pause(2000) - .executeScript('remix.exeCurrent()') + .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) From f915706b2ecd78c7c577939cb6cd05ecae63ac29 Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Thu, 4 Aug 2022 10:12:08 +0200 Subject: [PATCH 14/29] add filedecorator to required --- apps/remix-ide/src/remixAppManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/remix-ide/src/remixAppManager.js b/apps/remix-ide/src/remixAppManager.js index 1bebbc7083..fcf96659ec 100644 --- a/apps/remix-ide/src/remixAppManager.js +++ b/apps/remix-ide/src/remixAppManager.js @@ -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'] + 'compileAndRun', 'search', 'recorder', 'fileDecorator'] // dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd) const dependentModules = ['hardhat', 'truffle', 'slither'] From 65ef95b7882183ab1776af48e7d8b34785770118 Mon Sep 17 00:00:00 2001 From: bunsenstraat Date: Tue, 9 Aug 2022 12:24:45 +0200 Subject: [PATCH 15/29] update test --- apps/remix-ide-e2e/src/tests/editor_line_text.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/editor_line_text.test.ts b/apps/remix-ide-e2e/src/tests/editor_line_text.test.ts index d0d079cf68..afff1c8f28 100644 --- a/apps/remix-ide-e2e/src/tests/editor_line_text.test.ts +++ b/apps/remix-ide-e2e/src/tests/editor_line_text.test.ts @@ -13,8 +13,8 @@ module.exports = { .openFile('contracts') .openFile('contracts/1_Storage.sol') .addFile('scripts/addlinetext.ts', {content: addLineText}) - .pause(2000) - .executeScript('remix.exeCurrent()') + .pause(4000) + .executeScriptInTerminal('remix.exeCurrent()') .pause(4000) .openFile('contracts/1_Storage.sol') .useXpath() From 3a10b3385221aab6fd1043b3a681e050c719b054 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Wed, 3 Aug 2022 17:42:56 +0530 Subject: [PATCH 16/29] show contract balance --- .../run-tab/src/lib/components/instanceContainerUI.tsx | 1 + .../remix-ui/run-tab/src/lib/components/universalDappUI.tsx | 6 ++++++ libs/remix-ui/run-tab/src/lib/run-tab.tsx | 1 + libs/remix-ui/run-tab/src/lib/types/index.ts | 2 ++ 4 files changed, 10 insertions(+) diff --git a/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx b/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx index 6890c53f6d..1d51e10b04 100644 --- a/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx @@ -35,6 +35,7 @@ export function InstanceContainerUI (props: InstanceContainerProps) { runTransactions={props.runTransactions} sendValue={props.sendValue} getFuncABIInputs={props.getFuncABIInputs} + blockchain={props.blockchain} /> }) }
diff --git a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx index 679ab61b15..690c03d5eb 100644 --- a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx @@ -20,6 +20,7 @@ export function UniversalDappUI (props: UdappProps) { const [llIError, setLlIError] = useState('') const [calldataValue, setCalldataValue] = useState('') const [evmBC, setEvmBC] = useState(null) + const [contractBal, setContractBal] = useState(0) useEffect(() => { if (!props.instance.abi) { @@ -33,6 +34,10 @@ export function UniversalDappUI (props: UdappProps) { useEffect(() => { if (props.instance.address) { + props.blockchain.getBalanceInEther(props.instance.address, (err, balInEth) => { + if (!err) setContractBal(balInEth) + }) + // @ts-ignore let address = (props.instance.address.slice(0, 2) === '0x' ? '' : '0x') + props.instance.address.toString('hex') @@ -228,6 +233,7 @@ export function UniversalDappUI (props: UdappProps) {
+
{ contractABI && contractABI.map((funcABI, index) => { diff --git a/libs/remix-ui/run-tab/src/lib/run-tab.tsx b/libs/remix-ui/run-tab/src/lib/run-tab.tsx index 128d315201..5c7548536b 100644 --- a/libs/remix-ui/run-tab/src/lib/run-tab.tsx +++ b/libs/remix-ui/run-tab/src/lib/run-tab.tsx @@ -263,6 +263,7 @@ export function RunTabUI (props: RunTabProps) { runTransactions={executeTransactions} sendValue={runTab.sendValue} getFuncABIInputs={getFuncABIValues} + blockchain={plugin.blockchain} />
diff --git a/libs/remix-ui/run-tab/src/lib/types/index.ts b/libs/remix-ui/run-tab/src/lib/types/index.ts index d27eb1f580..3744759b5b 100644 --- a/libs/remix-ui/run-tab/src/lib/types/index.ts +++ b/libs/remix-ui/run-tab/src/lib/types/index.ts @@ -208,6 +208,7 @@ export interface InstanceContainerProps { mainnetPrompt: (tx: Tx, network: Network, amount: string, gasEstimation: string, gasFees: (maxFee: string, cb: (txFeeText: string, priceStatus: boolean) => void) => void, determineGasPrice: (cb: (txFeeText: string, gasPriceValue: string, gasPriceStatus: boolean) => void) => void) => JSX.Element, sendValue: string, getFuncABIInputs: (funcABI: FuncABI) => string + blockchain: any } export interface Modal { @@ -306,6 +307,7 @@ export interface UdappProps { funcIndex?: number) => void, sendValue: string, getFuncABIInputs: (funcABI: FuncABI) => string + blockchain: any } export interface DeployButtonProps { From 70a1c7e3726f1cecb3c976c588ef6fecc09eed85 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Thu, 4 Aug 2022 12:50:35 +0530 Subject: [PATCH 17/29] left align --- libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx index 690c03d5eb..7d1ada661b 100644 --- a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx @@ -233,8 +233,10 @@ export function UniversalDappUI (props: UdappProps) {
-
+
+ Balance: {contractBal} ETH +
{ contractABI && contractABI.map((funcABI, index) => { if (funcABI.type !== 'function') return null From 0c6223a21c718cec74ff42ea6684d7c9f435c395 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Thu, 4 Aug 2022 14:14:30 +0530 Subject: [PATCH 18/29] update balance --- .../src/lib/components/universalDappUI.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx index 7d1ada661b..6156f82b5f 100644 --- a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx @@ -34,10 +34,7 @@ export function UniversalDappUI (props: UdappProps) { useEffect(() => { if (props.instance.address) { - props.blockchain.getBalanceInEther(props.instance.address, (err, balInEth) => { - if (!err) setContractBal(balInEth) - }) - + updateBalance() // @ts-ignore let address = (props.instance.address.slice(0, 2) === '0x' ? '' : '0x') + props.instance.address.toString('hex') @@ -52,6 +49,16 @@ export function UniversalDappUI (props: UdappProps) { } }, [props.instance.contractData]) + props.blockchain.event.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp, _payload) => { + if (!error) updateBalance() + }) + + const updateBalance = () => { + props.blockchain.getBalanceInEther(props.instance.address, (err, balInEth) => { + if (!err) setContractBal(balInEth) + }) + } + const sendData = () => { setLlIError('') const fallback = txHelper.getFallbackInterface(contractABI) From 7fcdcb260739e89883278b7e02d2722d28753c51 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Thu, 4 Aug 2022 18:29:20 +0530 Subject: [PATCH 19/29] dark label --- libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx index 6156f82b5f..5b7926ffa3 100644 --- a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx @@ -242,7 +242,7 @@ export function UniversalDappUI (props: UdappProps) {
- Balance: {contractBal} ETH +
{ contractABI && contractABI.map((funcABI, index) => { From 0355ce5c2bc65d47655a5e659af143eef489fe29 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Fri, 5 Aug 2022 15:33:16 +0530 Subject: [PATCH 20/29] periodically update balance --- libs/remix-ui/run-tab/src/lib/actions/index.ts | 11 ++++++++++- .../src/lib/components/universalDappUI.tsx | 15 +++++---------- libs/remix-ui/run-tab/src/lib/reducers/runTab.ts | 1 + libs/remix-ui/run-tab/src/lib/types/index.ts | 2 ++ 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/libs/remix-ui/run-tab/src/lib/actions/index.ts b/libs/remix-ui/run-tab/src/lib/actions/index.ts index 5dc0eb2de7..bbf375eaed 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/index.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/index.ts @@ -3,7 +3,9 @@ import React from 'react' import { RunTab } from '../types/run-tab' import { resetAndInit, setupEvents } from './events' import { createNewBlockchainAccount, fillAccountsList, setExecutionContext, signMessageWithAddress } from './account' -import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt, setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit, updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath, updateTxFeeContent } from './actions' +import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt, + setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit, + updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath, updateTxFeeContent } from './actions' import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions } from './deploy' import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity-ts' import { ContractData, FuncABI } from "@remix-project/core-plugin" @@ -26,6 +28,13 @@ export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispa setupEvents(plugin, dispatch) setInterval(() => { fillAccountsList(plugin, dispatch) + if (plugin.REACT_API?.instances?.instanceList?.length){ + for (const instance of plugin.REACT_API.instances.instanceList) { + plugin.blockchain.getBalanceInEther(instance.address, (err, balInEth) => { + if (!err) instance.balance = balInEth + }) + } + } }, 1000) } diff --git a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx index 5b7926ffa3..2beb6bec1a 100644 --- a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx @@ -34,7 +34,6 @@ export function UniversalDappUI (props: UdappProps) { useEffect(() => { if (props.instance.address) { - updateBalance() // @ts-ignore let address = (props.instance.address.slice(0, 2) === '0x' ? '' : '0x') + props.instance.address.toString('hex') @@ -49,15 +48,11 @@ export function UniversalDappUI (props: UdappProps) { } }, [props.instance.contractData]) - props.blockchain.event.register('transactionExecuted', (error, from, to, data, call, txResult, timestamp, _payload) => { - if (!error) updateBalance() - }) - - const updateBalance = () => { - props.blockchain.getBalanceInEther(props.instance.address, (err, balInEth) => { - if (!err) setContractBal(balInEth) - }) - } + useEffect(() => { + if (props.instance.balance) { + setContractBal(props.instance.balance) + } + }, [props.instance.balance]) const sendData = () => { setLlIError('') diff --git a/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts b/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts index 5299b748ab..5bae13a09e 100644 --- a/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts +++ b/libs/remix-ui/run-tab/src/lib/reducers/runTab.ts @@ -88,6 +88,7 @@ export interface RunTabState { instanceList: { contractData?: ContractData, address: string, + balance?: number, name: string, decodedResponse?: Record, abi?: any diff --git a/libs/remix-ui/run-tab/src/lib/types/index.ts b/libs/remix-ui/run-tab/src/lib/types/index.ts index 3744759b5b..5d6c819555 100644 --- a/libs/remix-ui/run-tab/src/lib/types/index.ts +++ b/libs/remix-ui/run-tab/src/lib/types/index.ts @@ -181,6 +181,7 @@ export interface InstanceContainerProps { instanceList: { contractData?: ContractData, address: string, + balance?: number, name: string, decodedResponse?: Record, abi?: any @@ -282,6 +283,7 @@ export interface UdappProps { instance: { contractData?: ContractData, address: string, + balance?: number, name: string, decodedResponse?: Record, abi?: any From 024f819084bbb415916a2bae9610f71d6d410bef Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Fri, 5 Aug 2022 19:36:31 +0530 Subject: [PATCH 21/29] naming --- .../run-tab/src/lib/components/instanceContainerUI.tsx | 1 - .../run-tab/src/lib/components/universalDappUI.tsx | 8 ++++---- libs/remix-ui/run-tab/src/lib/run-tab.tsx | 1 - libs/remix-ui/run-tab/src/lib/types/index.ts | 2 -- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx b/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx index 1d51e10b04..6890c53f6d 100644 --- a/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/instanceContainerUI.tsx @@ -35,7 +35,6 @@ export function InstanceContainerUI (props: InstanceContainerProps) { runTransactions={props.runTransactions} sendValue={props.sendValue} getFuncABIInputs={props.getFuncABIInputs} - blockchain={props.blockchain} /> }) }
diff --git a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx index 2beb6bec1a..3ac993bd23 100644 --- a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx @@ -20,7 +20,7 @@ export function UniversalDappUI (props: UdappProps) { const [llIError, setLlIError] = useState('') const [calldataValue, setCalldataValue] = useState('') const [evmBC, setEvmBC] = useState(null) - const [contractBal, setContractBal] = useState(0) + const [instanceBalance, setInstanceBalance] = useState(0) useEffect(() => { if (!props.instance.abi) { @@ -50,7 +50,7 @@ export function UniversalDappUI (props: UdappProps) { useEffect(() => { if (props.instance.balance) { - setContractBal(props.instance.balance) + setInstanceBalance(props.instance.balance) } }, [props.instance.balance]) @@ -236,8 +236,8 @@ export function UniversalDappUI (props: UdappProps) {
-
- +
+
{ contractABI && contractABI.map((funcABI, index) => { diff --git a/libs/remix-ui/run-tab/src/lib/run-tab.tsx b/libs/remix-ui/run-tab/src/lib/run-tab.tsx index 5c7548536b..128d315201 100644 --- a/libs/remix-ui/run-tab/src/lib/run-tab.tsx +++ b/libs/remix-ui/run-tab/src/lib/run-tab.tsx @@ -263,7 +263,6 @@ export function RunTabUI (props: RunTabProps) { runTransactions={executeTransactions} sendValue={runTab.sendValue} getFuncABIInputs={getFuncABIValues} - blockchain={plugin.blockchain} />
diff --git a/libs/remix-ui/run-tab/src/lib/types/index.ts b/libs/remix-ui/run-tab/src/lib/types/index.ts index 5d6c819555..8ef73156df 100644 --- a/libs/remix-ui/run-tab/src/lib/types/index.ts +++ b/libs/remix-ui/run-tab/src/lib/types/index.ts @@ -209,7 +209,6 @@ export interface InstanceContainerProps { mainnetPrompt: (tx: Tx, network: Network, amount: string, gasEstimation: string, gasFees: (maxFee: string, cb: (txFeeText: string, priceStatus: boolean) => void) => void, determineGasPrice: (cb: (txFeeText: string, gasPriceValue: string, gasPriceStatus: boolean) => void) => void) => JSX.Element, sendValue: string, getFuncABIInputs: (funcABI: FuncABI) => string - blockchain: any } export interface Modal { @@ -309,7 +308,6 @@ export interface UdappProps { funcIndex?: number) => void, sendValue: string, getFuncABIInputs: (funcABI: FuncABI) => string - blockchain: any } export interface DeployButtonProps { From bf1da2baf9f570071d0b8ee7613bc14ccf299467 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Mon, 8 Aug 2022 17:55:32 +0530 Subject: [PATCH 22/29] e2e tests --- .../src/tests/runAndDeploy.test.ts | 26 +++++++++++++++++++ .../src/lib/components/universalDappUI.tsx | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts index c8f2277ba2..075bd0e05a 100644 --- a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts +++ b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts @@ -65,6 +65,21 @@ module.exports = { }) }, + 'Should show and update balance for deployed contract on JavascriptVM #group3': function (browser: NightwatchBrowser) { + browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]') + .clickLaunchIcon('filePanel') + .addFile('checkBalance.sol', sources[0]['checkBalance.sol']) + .clickLaunchIcon('udapp') + .setValue('*[data-id="dandrValue"]', '111') + .waitForElementVisible('*[data-id="Deploy - transact (payable)"]', 45000) + .click('*[data-id="Deploy - transact (payable)"]') + .pause(1000) + .clickInstance(1) + .pause(1000) + .waitForElementVisible('*[data-id="instanceBal"]', 45000) + .assert.containsText('*[data-id="instanceBal"]', 'Balance') + }, + 'Should run low level interaction (fallback function) #group3': function (browser: NightwatchBrowser) { browser.waitForElementPresent('*[data-id="remixIdeSidePanel"]') .clickInstance(0) @@ -224,6 +239,17 @@ const sources = [ message = _message; } }` + }, + 'checkBalance.sol': { + content: `pragma solidity ^0.8.0; + contract checkBalance { + constructor () payable {} + + function sendSomeEther(uint256 num) public { + payable(msg.sender).transfer(num); + } + + }` } } ] diff --git a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx index 3ac993bd23..d7cd9c09c3 100644 --- a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx @@ -236,7 +236,7 @@ export function UniversalDappUI (props: UdappProps) {
-
+
{ From d37bfcea62255f9c97bc4de744c290f38b0747a9 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Tue, 9 Aug 2022 10:35:36 +0530 Subject: [PATCH 23/29] add method --- libs/remix-ui/run-tab/src/lib/actions/deploy.ts | 10 ++++++++++ libs/remix-ui/run-tab/src/lib/actions/index.ts | 10 ++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts index 440d805664..caf8e6de8a 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/deploy.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/deploy.ts @@ -300,4 +300,14 @@ export const runTransactions = ( export const getFuncABIInputs = (plugin: RunTab, funcABI: FuncABI) => { return plugin.blockchain.getInputs(funcABI) +} + +export const updateInstanceBalance = (plugin: RunTab) => { + if (plugin.REACT_API?.instances?.instanceList?.length) { + for (const instance of plugin.REACT_API.instances.instanceList) { + plugin.blockchain.getBalanceInEther(instance.address, (err, balInEth) => { + if (!err) instance.balance = balInEth + }) + } + } } \ No newline at end of file diff --git a/libs/remix-ui/run-tab/src/lib/actions/index.ts b/libs/remix-ui/run-tab/src/lib/actions/index.ts index bbf375eaed..6a20e9470d 100644 --- a/libs/remix-ui/run-tab/src/lib/actions/index.ts +++ b/libs/remix-ui/run-tab/src/lib/actions/index.ts @@ -6,7 +6,7 @@ import { createNewBlockchainAccount, fillAccountsList, setExecutionContext, sign import { clearInstances, clearPopUp, removeInstance, setAccount, setGasFee, setMatchPassphrasePrompt, setNetworkNameFromProvider, setPassphrasePrompt, setSelectedContract, setSendTransactionValue, setUnit, updateBaseFeePerGas, updateConfirmSettings, updateGasPrice, updateGasPriceStatus, updateMaxFee, updateMaxPriorityFee, updateScenarioPath, updateTxFeeContent } from './actions' -import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions } from './deploy' +import { createInstance, getContext, getFuncABIInputs, getSelectedContract, loadAddress, runTransactions, updateInstanceBalance } from './deploy' import { CompilerAbstract as CompilerAbstractType } from '@remix-project/remix-solidity-ts' import { ContractData, FuncABI } from "@remix-project/core-plugin" import { DeployMode, MainnetPrompt } from '../types' @@ -28,13 +28,7 @@ export const initRunTab = (udapp: RunTab) => async (reducerDispatch: React.Dispa setupEvents(plugin, dispatch) setInterval(() => { fillAccountsList(plugin, dispatch) - if (plugin.REACT_API?.instances?.instanceList?.length){ - for (const instance of plugin.REACT_API.instances.instanceList) { - plugin.blockchain.getBalanceInEther(instance.address, (err, balInEth) => { - if (!err) instance.balance = balInEth - }) - } - } + updateInstanceBalance(plugin) }, 1000) } From 9eb90b1a6d58793ab6d0ef835abe6f93f0d53a82 Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Tue, 9 Aug 2022 16:23:17 +0530 Subject: [PATCH 24/29] fix e2e --- apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts | 9 +++++++-- .../run-tab/src/lib/components/universalDappUI.tsx | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts index 075bd0e05a..e1edef7dad 100644 --- a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts +++ b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts @@ -66,6 +66,7 @@ module.exports = { }, 'Should show and update balance for deployed contract on JavascriptVM #group3': function (browser: NightwatchBrowser) { + let instanceAddress browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]') .clickLaunchIcon('filePanel') .addFile('checkBalance.sol', sources[0]['checkBalance.sol']) @@ -76,8 +77,12 @@ module.exports = { .pause(1000) .clickInstance(1) .pause(1000) - .waitForElementVisible('*[data-id="instanceBal"]', 45000) - .assert.containsText('*[data-id="instanceBal"]', 'Balance') + .getAddressAtPosition(1, (address) => { + instanceAddress = address + browser + .waitForElementVisible(`#instance${instanceAddress} [data-id="instanceContractBal"]`) + .assert.containsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance:') + }) }, 'Should run low level interaction (fallback function) #group3': function (browser: NightwatchBrowser) { diff --git a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx index d7cd9c09c3..46d2f2dfb7 100644 --- a/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx +++ b/libs/remix-ui/run-tab/src/lib/components/universalDappUI.tsx @@ -236,7 +236,7 @@ export function UniversalDappUI (props: UdappProps) {
-
+
{ From 6f7f6414be68921d073588719ae676fef1fb472c Mon Sep 17 00:00:00 2001 From: Aniket-Engg Date: Tue, 9 Aug 2022 16:47:24 +0530 Subject: [PATCH 25/29] more e2e --- apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts index e1edef7dad..0d8df82b96 100644 --- a/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts +++ b/apps/remix-ide-e2e/src/tests/runAndDeploy.test.ts @@ -81,7 +81,10 @@ module.exports = { instanceAddress = address browser .waitForElementVisible(`#instance${instanceAddress} [data-id="instanceContractBal"]`) - .assert.containsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance:') + .assert.containsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000111 ETH') + .clickFunction('sendSomeEther - transact (not payable)', { types: 'uint256 num', values: '2' }) + .pause(1000) + .assert.containsText(`#instance${instanceAddress} [data-id="instanceContractBal"]`, 'Balance: 0.000000000000000109 ETH') }) }, From e5f6f7184644deaf30902d6800fbffad1ac99ebb Mon Sep 17 00:00:00 2001 From: lianahus Date: Wed, 10 Aug 2022 08:24:57 +0200 Subject: [PATCH 26/29] regular icon for delete workshop --- libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx index 353e4ccf91..b56c83b5de 100644 --- a/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx +++ b/libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx @@ -227,7 +227,7 @@ export function Workspace () { e.stopPropagation() deleteCurrentWorkspace() }} - className='fas fa-trash remixui_menuicon' + className='far fa-trash remixui_menuicon' title='Delete'> Date: Wed, 10 Aug 2022 15:07:21 +0200 Subject: [PATCH 27/29] Update publish-to-storage.tsx --- .../publish-to-storage/src/lib/publish-to-storage.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx b/libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx index 8ff00ca026..51b5d46b01 100644 --- a/libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx +++ b/libs/remix-ui/publish-to-storage/src/lib/publish-to-storage.tsx @@ -31,12 +31,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => { modal(`Published ${contract.name}'s Metadata and Sources`, publishMessage(result.uploaded)) } catch (err) { - let parseError = err - try { - parseError = JSON.stringify(err) - } catch (e) { - } - modal('Swarm Publish Failed', publishMessageFailed(storage, parseError)) + modal('Swarm Publish Failed', publishMessageFailed(storage, err.message)) } } else { if (!api.config.get('settings/ipfs-url') && !modalShown) { @@ -81,7 +76,7 @@ export const PublishToStorage = (props: RemixUiPublishToStorageProps) => { const result = await publishToIPFS(contract, api) modal(`Published ${contract.name}'s Metadata and Sources`, publishMessage(result.uploaded)) } catch (err) { - modal('IPFS Publish Failed', publishMessageFailed(storage, err)) + modal('IPFS Publish Failed', publishMessageFailed(storage, err.message)) } setModalShown(true) } From a5018b37666a36474038f346be266c6625803a0a Mon Sep 17 00:00:00 2001 From: yann300 Date: Wed, 10 Aug 2022 15:17:54 +0200 Subject: [PATCH 28/29] disable ipfs e2e tests --- apps/remix-ide-e2e/src/tests/publishContract.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/remix-ide-e2e/src/tests/publishContract.test.ts b/apps/remix-ide-e2e/src/tests/publishContract.test.ts index 38ee1c3e54..2545a53808 100644 --- a/apps/remix-ide-e2e/src/tests/publishContract.test.ts +++ b/apps/remix-ide-e2e/src/tests/publishContract.test.ts @@ -3,6 +3,7 @@ import { NightwatchBrowser } from 'nightwatch' import init from '../helpers/init' module.exports = { + '@disabled': true, before: function (browser: NightwatchBrowser, done: VoidFunction) { init(browser, done) }, From b82729c109ef2d3605d27c55a64b12e383a26b8a Mon Sep 17 00:00:00 2001 From: yann300 Date: Tue, 9 Aug 2022 16:28:13 +0200 Subject: [PATCH 29/29] fix display json rpc error --- apps/remix-ide/src/app/panels/terminal.js | 4 ++-- apps/remix-ide/src/app/tabs/web3-provider.js | 5 ++++- libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/remix-ide/src/app/panels/terminal.js b/apps/remix-ide/src/app/panels/terminal.js index bd89f2d7ce..6d830c2054 100644 --- a/apps/remix-ide/src/app/panels/terminal.js +++ b/apps/remix-ide/src/app/panels/terminal.js @@ -100,8 +100,8 @@ class Terminal extends Plugin { this.terminalApi.logHtml(html) } - log (message) { - this.terminalApi.log(message) + log (message, type) { + this.terminalApi.log(message, type) } setDispatch(dispatch) { diff --git a/apps/remix-ide/src/app/tabs/web3-provider.js b/apps/remix-ide/src/app/tabs/web3-provider.js index 4bf96f062d..a6690f545e 100644 --- a/apps/remix-ide/src/app/tabs/web3-provider.js +++ b/apps/remix-ide/src/app/tabs/web3-provider.js @@ -29,7 +29,10 @@ export class Web3ProviderModule extends Plugin { const provider = this.blockchain.web3().currentProvider // see https://github.com/ethereum/web3.js/pull/1018/files#diff-d25786686c1053b786cc2626dc6e048675050593c0ebaafbf0814e1996f22022R129 provider[provider.sendAsync ? 'sendAsync' : 'send'](payload, async (error, message) => { - if (error) return reject(error) + if (error) { + this.call('terminal', 'log', error.data ? error.data : error, 'error') + return reject(error.data ? error.data : error) + } if (payload.method === 'eth_sendTransaction') { if (payload.params.length && !payload.params[0].to && message.result) { setTimeout(async () => { diff --git a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx index d8cbe6d610..4c30fee8f9 100644 --- a/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx +++ b/libs/remix-ui/terminal/src/lib/remix-ui-terminal.tsx @@ -86,8 +86,8 @@ export const RemixUiTerminal = (props: RemixUiTerminalProps) => { scriptRunnerDispatch({ type: 'html', payload: { message: [html ? html.innerText ? html.innerText : html : null] } }) }, - log: (message) => { - scriptRunnerDispatch({ type: 'log', payload: { message: [message] } }) + log: (message, type) => { + scriptRunnerDispatch({ type: type ? type : 'log', payload: { message: [message] } }) } }) }, [])