Merge branch 'prettier' of https://github.com/ethereum/remix-project into prettier

pull/3486/head
filip mertens 2 years ago
commit 328e8a6000
  1. 11
      apps/remix-ide/src/app/plugins/parser/services/code-parser-compiler.ts
  2. 9
      apps/remix-ide/src/app/tabs/locales/en/filePanel.json
  3. 3
      apps/remix-ide/webpack.config.js
  4. 1
      libs/remix-ui/editor/src/index.ts
  5. 4
      libs/remix-ui/editor/src/lib/actions/editor.ts
  6. 45
      libs/remix-ui/editor/src/lib/providers/completion/completionGlobals.ts
  7. 12
      libs/remix-ui/editor/src/lib/providers/completionProvider.ts
  8. 6
      libs/remix-ui/editor/src/lib/providers/hoverProvider.ts
  9. 30
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  10. 90
      libs/remix-ui/workspace/src/lib/actions/workspace.ts
  11. 51
      libs/remix-ui/workspace/src/lib/components/file-explorer-menu.tsx
  12. 9
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  13. 1
      libs/remix-ui/workspace/src/lib/contexts/index.ts
  14. 7
      libs/remix-ui/workspace/src/lib/providers/FileSystemProvider.tsx
  15. 4
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  16. 2
      libs/remix-ui/workspace/src/lib/types/index.ts
  17. 6
      package.json
  18. 2558
      yarn.lock

@ -7,13 +7,20 @@ import { CodeParser } from "../code-parser";
import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators' import { fileDecoration, fileDecorationType } from '@remix-ui/file-decorators'
import { sourceMappingDecoder } from '@remix-project/remix-debug' import { sourceMappingDecoder } from '@remix-project/remix-debug'
import { CompilerRetriggerMode, CompilationSourceCode } from '@remix-project/remix-solidity'; import { CompilerRetriggerMode, CompilationSourceCode } from '@remix-project/remix-solidity';
import { MarkerSeverity } from 'monaco-editor';
import { findLinesInStringWithMatch, SearchResultLine } from '@remix-ui/search' import { findLinesInStringWithMatch, SearchResultLine } from '@remix-ui/search'
import { lastCompilationResult } from '@remixproject/plugin-api'; import { lastCompilationResult } from '@remixproject/plugin-api';
import { monacoTypes } from '@remix-ui/editor';
enum MarkerSeverity {
Hint = 1,
Info = 2,
Warning = 4,
Error = 8
}
type errorMarker = { type errorMarker = {
message: string message: string
severity: MarkerSeverity severity: monacoTypes.MarkerSeverity
position: { position: {
start: { start: {
line: number line: number

@ -39,10 +39,11 @@
"filePanel.paste": "Paste", "filePanel.paste": "Paste",
"filePanel.compile": "Compile", "filePanel.compile": "Compile",
"filePanel.compileForNahmii": "Compile for Nahmii", "filePanel.compileForNahmii": "Compile for Nahmii",
"filePanel.createNewFile": "Create New File", "filePanel.createNewFile": "Create new file",
"filePanel.createNewFolder": "Create New Folder", "filePanel.createNewFolder": "Create new folder",
"filePanel.publishToGist": "Publish all the current workspace files to a github gist", "filePanel.publishToGist": "Publish all files to GitHub gist",
"filePanel.uploadFile": "Load a local file into current workspace", "filePanel.uploadFile": "Upload files",
"filePanel.uploadFolder": "Upload folder",
"filePanel.updateGist": "Update the current [gist] explorer", "filePanel.updateGist": "Update the current [gist] explorer",
"filePanel.viewAllBranches": "View all branches", "filePanel.viewAllBranches": "View all branches",
"filePanel.createBranch": "Create branch", "filePanel.createBranch": "Create branch",

@ -60,7 +60,7 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
config.plugins.push( config.plugins.push(
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: [
{ from: '../../node_modules/monaco-editor/dev/vs', to: 'assets/js/monaco-editor/dev/vs' } { from: '../../node_modules/monaco-editor/min/vs', to: 'assets/js/monaco-editor/min/vs' }
].filter(Boolean) ].filter(Boolean)
}), }),
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
@ -79,7 +79,6 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer // set minimizer
config.optimization.minimizer = [ config.optimization.minimizer = [
new TerserPlugin({ new TerserPlugin({

@ -1 +1,2 @@
export * from './lib/remix-ui-editor' export * from './lib/remix-ui-editor'
export { default as monacoTypes } from './types/monaco'

@ -1,5 +1,5 @@
import { IRange } from "monaco-editor";
import { monacoTypes } from '@remix-ui/editor';
export interface Action { export interface Action {
type: string; type: string;
payload: Record<string, any> payload: Record<string, any>
@ -58,7 +58,7 @@ export const reducerActions = (models = initialState, action: Action) => {
} }
case 'REVEAL_RANGE': { case 'REVEAL_RANGE': {
if (!editor) return models if (!editor) return models
const range: IRange = { const range: monacoTypes.IRange = {
startLineNumber: action.payload.startLineNumber + 1, startLineNumber: action.payload.startLineNumber + 1,
startColumn: action.payload.startColumn, startColumn: action.payload.startColumn,
endLineNumber: action.payload.endLineNumber + 1, endLineNumber: action.payload.endLineNumber + 1,

@ -1,6 +1,5 @@
import { IRange } from "monaco-editor";
import monaco from "../../../types/monaco";
import path from "path"; import path from "path";
import { monacoTypes } from '@remix-ui/editor';
type CodeParserImportsData = { type CodeParserImportsData = {
files?: string[], files?: string[],
@ -8,7 +7,7 @@ type CodeParserImportsData = {
packages?: string[], packages?: string[],
} }
export function getStringCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { export function getStringCompletionItems(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
return [ return [
{ {
detail: 'concatenate an arbitrary number of string values', detail: 'concatenate an arbitrary number of string values',
@ -21,7 +20,7 @@ export function getStringCompletionItems(range: IRange, monaco): monaco.language
] ]
} }
export function getBytesCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { export function getBytesCompletionItems(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
return [ return [
{ {
detail: 'concatenate an arbitrary number of values', detail: 'concatenate an arbitrary number of values',
@ -35,7 +34,7 @@ export function getBytesCompletionItems(range: IRange, monaco): monaco.languages
} }
export function getBlockCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { export function getBlockCompletionItems(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
return [ return [
{ {
detail: '(address): Current block miner’s address', detail: '(address): Current block miner’s address',
@ -97,7 +96,7 @@ export function getBlockCompletionItems(range: IRange, monaco): monaco.languages
]; ];
} }
export function getCompletionSnippets(range: IRange, monaco): monaco.languages.CompletionItem[] { export function getCompletionSnippets(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
return [ return [
{ {
label: 'contract', label: 'contract',
@ -193,7 +192,7 @@ export function getCompletionSnippets(range: IRange, monaco): monaco.languages.C
] ]
} }
export function getTxCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { export function getTxCompletionItems(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
return [ return [
{ {
detail: '(uint): gas price of the transaction', detail: '(uint): gas price of the transaction',
@ -212,7 +211,7 @@ export function getTxCompletionItems(range: IRange, monaco): monaco.languages.Co
]; ];
} }
export function getMsgCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { export function getMsgCompletionItems(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
return [ return [
{ {
detail: '(bytes): complete calldata', detail: '(bytes): complete calldata',
@ -252,7 +251,7 @@ export function getMsgCompletionItems(range: IRange, monaco): monaco.languages.C
]; ];
} }
export function getAbiCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { export function getAbiCompletionItems(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
return [ return [
{ {
detail: 'encode(..) returs (bytes): ABI-encodes the given arguments', detail: 'encode(..) returs (bytes): ABI-encodes the given arguments',
@ -298,7 +297,7 @@ export function getAbiCompletionItems(range: IRange, monaco): monaco.languages.C
} }
export function GetCompletionTypes(range: IRange, monaco): monaco.languages.CompletionItem[] { export function GetCompletionTypes(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
const completionItems = []; const completionItems = [];
const types = ['address', 'string', 'bytes', 'byte', 'int', 'uint', 'bool', 'hash']; const types = ['address', 'string', 'bytes', 'byte', 'int', 'uint', 'bool', 'hash'];
for (let index = 8; index <= 256; index += 8) { for (let index = 8; index <= 256; index += 8) {
@ -314,8 +313,8 @@ export function GetCompletionTypes(range: IRange, monaco): monaco.languages.Comp
return completionItems; return completionItems;
} }
function CreateCompletionItem(label: string, kind: monaco.languages.CompletionItemKind, detail: string, range: IRange) { function CreateCompletionItem(label: string, kind: monacoTypes.languages.CompletionItemKind, detail: string, range: monacoTypes.IRange) {
const completionItem: monaco.languages.CompletionItem = { const completionItem: monacoTypes.languages.CompletionItem = {
label, label,
kind, kind,
detail, detail,
@ -327,7 +326,7 @@ function CreateCompletionItem(label: string, kind: monaco.languages.CompletionIt
return completionItem; return completionItem;
} }
export function GetCompletionKeywords(range: IRange, monaco): monaco.languages.CompletionItem[] { export function GetCompletionKeywords(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
const completionItems = []; const completionItems = [];
const keywords = ['modifier', 'mapping', 'break', 'continue', 'delete', 'else', 'for', const keywords = ['modifier', 'mapping', 'break', 'continue', 'delete', 'else', 'for',
'after', 'promise', 'alias', 'apply', 'auto', 'copyof', 'default', 'define', 'final', 'implements', 'after', 'promise', 'alias', 'apply', 'auto', 'copyof', 'default', 'define', 'final', 'implements',
@ -337,7 +336,7 @@ export function GetCompletionKeywords(range: IRange, monaco): monaco.languages.C
'private', 'public', 'external', 'internal', 'payable', 'nonpayable', 'view', 'pure', 'case', 'do', 'else', 'finally', 'private', 'public', 'external', 'internal', 'payable', 'nonpayable', 'view', 'pure', 'case', 'do', 'else', 'finally',
'in', 'instanceof', 'return', 'throw', 'try', 'catch', 'typeof', 'yield', 'void', 'virtual', 'override']; 'in', 'instanceof', 'return', 'throw', 'try', 'catch', 'typeof', 'yield', 'void', 'virtual', 'override'];
keywords.forEach(unit => { keywords.forEach(unit => {
const completionItem: monaco.languages.CompletionItem = { const completionItem: monacoTypes.languages.CompletionItem = {
label: unit, label: unit,
kind: monaco.languages.CompletionItemKind.Keyword, kind: monaco.languages.CompletionItemKind.Keyword,
detail: unit + ' keyword', detail: unit + ' keyword',
@ -366,7 +365,7 @@ export function GetCompletionKeywords(range: IRange, monaco): monaco.languages.C
} }
export function GeCompletionUnits(range: IRange, monaco): monaco.languages.CompletionItem[] { export function GeCompletionUnits(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
const completionItems = []; const completionItems = [];
const etherUnits = ['wei', 'gwei', 'finney', 'szabo', 'ether']; const etherUnits = ['wei', 'gwei', 'finney', 'szabo', 'ether'];
etherUnits.forEach(unit => { etherUnits.forEach(unit => {
@ -390,10 +389,10 @@ export function GeCompletionUnits(range: IRange, monaco): monaco.languages.Compl
return completionItems; return completionItems;
} }
export function GetImports(range: IRange export function GetImports(range: monacoTypes.IRange
, monaco, data: CodeParserImportsData , monaco, data: CodeParserImportsData
, word: string , word: string
): monaco.languages.CompletionItem[] { ): monacoTypes.languages.CompletionItem[] {
let list = [] let list = []
if (!word.startsWith('@')) { if (!word.startsWith('@')) {
word = word.replace('"', ''); word = word.replace('"', '');
@ -473,7 +472,7 @@ export function GetImports(range: IRange
return list; return list;
}; };
export function GetGlobalVariable(range: IRange, monaco): monaco.languages.CompletionItem[] { export function GetGlobalVariable(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
return [ return [
{ {
detail: 'Current block', detail: 'Current block',
@ -520,7 +519,7 @@ export function GetGlobalVariable(range: IRange, monaco): monaco.languages.Compl
]; ];
} }
export function GetGlobalFunctions(range: IRange, monaco): monaco.languages.CompletionItem[] { export function GetGlobalFunctions(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
return [ return [
{ {
detail: 'assert(bool condition): throws if the condition is not met - to be used for internal errors.', detail: 'assert(bool condition): throws if the condition is not met - to be used for internal errors.',
@ -644,7 +643,7 @@ export function GetGlobalFunctions(range: IRange, monaco): monaco.languages.Comp
]; ];
} }
export function getContextualAutoCompleteByGlobalVariable(word: string, range: IRange, monaco): monaco.languages.CompletionItem[] { export function getContextualAutoCompleteByGlobalVariable(word: string, range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
if (word === 'block') { if (word === 'block') {
return getBlockCompletionItems(range, monaco); return getBlockCompletionItems(range, monaco);
} }
@ -669,7 +668,7 @@ export function getContextualAutoCompleteByGlobalVariable(word: string, range: I
return null; return null;
} }
export function getArrayCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { export function getArrayCompletionItems(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
return [ return [
{ {
detail: '', detail: '',
@ -706,7 +705,7 @@ export function getArrayCompletionItems(range: IRange, monaco): monaco.languages
] ]
} }
export function getAddressCompletionItems(range: IRange, monaco): monaco.languages.CompletionItem[] { export function getAddressCompletionItems(range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
return [ return [
{ {
detail: '(uint256): balance of the Address in Wei', detail: '(uint256): balance of the Address in Wei',
@ -752,7 +751,7 @@ export function getAddressCompletionItems(range: IRange, monaco): monaco.languag
} }
export function getContextualAutoCompleteBTypeName(word: string, range: IRange, monaco): monaco.languages.CompletionItem[] { export function getContextualAutoCompleteBTypeName(word: string, range: monacoTypes.IRange, monaco): monacoTypes.languages.CompletionItem[] {
if (word === 'ArrayTypeName') { if (word === 'ArrayTypeName') {
return getArrayCompletionItems(range, monaco); return getArrayCompletionItems(range, monaco);
} }

@ -1,11 +1,9 @@
import { AstNode } from "@remix-project/remix-solidity" import { AstNode } from "@remix-project/remix-solidity"
import { isArray } from "lodash" import { isArray } from "lodash"
import { editor, languages, Position } from "monaco-editor"
import monaco from "../../types/monaco"
import { EditorUIProps } from "../remix-ui-editor" import { EditorUIProps } from "../remix-ui-editor"
import { GeCompletionUnits, GetCompletionKeywords, getCompletionSnippets, GetCompletionTypes, getContextualAutoCompleteBTypeName, getContextualAutoCompleteByGlobalVariable, GetGlobalFunctions, GetGlobalVariable, GetImports } from "./completion/completionGlobals" import { GeCompletionUnits, GetCompletionKeywords, getCompletionSnippets, GetCompletionTypes, getContextualAutoCompleteBTypeName, getContextualAutoCompleteByGlobalVariable, GetGlobalFunctions, GetGlobalVariable, GetImports } from "./completion/completionGlobals"
import { monacoTypes } from '@remix-ui/editor';
export class RemixCompletionProvider implements languages.CompletionItemProvider { export class RemixCompletionProvider implements monacoTypes.languages.CompletionItemProvider {
props: EditorUIProps props: EditorUIProps
monaco: any monaco: any
@ -17,7 +15,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
} }
triggerCharacters = ['.', '', '"', '@', '/'] triggerCharacters = ['.', '', '"', '@', '/']
async provideCompletionItems(model: editor.ITextModel, position: Position, context: monaco.languages.CompletionContext): Promise<monaco.languages.CompletionList | undefined> { async provideCompletionItems(model: monacoTypes.editor.ITextModel, position: monacoTypes.Position, context: monacoTypes.languages.CompletionContext): Promise<monacoTypes.languages.CompletionList | undefined> {
const completionSettings = await this.props.plugin.call('config', 'getAppParameter', 'settings/auto-completion') const completionSettings = await this.props.plugin.call('config', 'getAppParameter', 'settings/auto-completion')
if (!completionSettings) return if (!completionSettings) return
@ -32,7 +30,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
const line = model.getLineContent(position.lineNumber) const line = model.getLineContent(position.lineNumber)
let nodes: AstNode[] = [] let nodes: AstNode[] = []
let suggestions: monaco.languages.CompletionItem[] = [] let suggestions: monacoTypes.languages.CompletionItem[] = []
if (context.triggerCharacter === '"' || context.triggerCharacter === '@' || context.triggerCharacter === '/') { if (context.triggerCharacter === '"' || context.triggerCharacter === '@' || context.triggerCharacter === '/') {
const lastpart = line.substring(0, position.column - 1).split(';').pop() const lastpart = line.substring(0, position.column - 1).split(';').pop()
@ -342,7 +340,7 @@ export class RemixCompletionProvider implements languages.CompletionItemProvider
private getDotCompletions = async (nameOfLastTypedExpression: string, range) => { private getDotCompletions = async (nameOfLastTypedExpression: string, range) => {
const contractCompletions = await this.getContractCompletions() const contractCompletions = await this.getContractCompletions()
let nodes: any[] = [] let nodes: any[] = []
let suggestions: monaco.languages.CompletionItem[] = [] let suggestions: monacoTypes.languages.CompletionItem[] = []
const filterNodes = (nodes: any[], parentNode: any, declarationOf: any = null) => { const filterNodes = (nodes: any[], parentNode: any, declarationOf: any = null) => {
return nodes && nodes.filter(node => { return nodes && nodes.filter(node => {

@ -1,8 +1,8 @@
import { Monaco } from '@monaco-editor/react' import { Monaco } from '@monaco-editor/react'
import { editor, languages, Position } from 'monaco-editor'
import { EditorUIProps } from '../remix-ui-editor' import { EditorUIProps } from '../remix-ui-editor'
export class RemixHoverProvider implements languages.HoverProvider { import { monacoTypes } from '@remix-ui/editor';
export class RemixHoverProvider implements monacoTypes.languages.HoverProvider {
props: EditorUIProps props: EditorUIProps
monaco: Monaco monaco: Monaco
@ -11,7 +11,7 @@ export class RemixHoverProvider implements languages.HoverProvider {
this.monaco = monaco this.monaco = monaco
} }
provideHover = async function (model: editor.ITextModel, position: Position): Promise<languages.Hover> { provideHover = async function (model: monacoTypes.editor.ITextModel, position: monacoTypes.Position): Promise<monacoTypes.languages.Hover> {
const cursorPosition = this.props.editorAPI.getHoverPosition(position) const cursorPosition = this.props.editorAPI.getHoverPosition(position)
const nodeAtPosition = await this.props.plugin.call('codeParser', 'definitionAtPosition', cursorPosition) const nodeAtPosition = await this.props.plugin.call('codeParser', 'definitionAtPosition', cursorPosition)
const contents = [] const contents = []

@ -7,11 +7,12 @@ import { solidityTokensProvider, solidityLanguageConfig } from './syntaxes/solid
import { cairoTokensProvider, cairoLanguageConfig } from './syntaxes/cairo' import { cairoTokensProvider, cairoLanguageConfig } from './syntaxes/cairo'
import { zokratesTokensProvider, zokratesLanguageConfig } from './syntaxes/zokrates' import { zokratesTokensProvider, zokratesLanguageConfig } from './syntaxes/zokrates'
import { moveTokenProvider, moveLanguageConfig } from './syntaxes/move' import { moveTokenProvider, moveLanguageConfig } from './syntaxes/move'
import { monacoTypes } from '@remix-ui/editor';
import './remix-ui-editor.css' import './remix-ui-editor.css'
import { loadTypes } from './web-types' import { loadTypes } from './web-types'
import monaco from '../types/monaco'
import { IMarkdownString, IPosition, MarkerSeverity } from 'monaco-editor'
import { RemixHoverProvider } from './providers/hoverProvider' import { RemixHoverProvider } from './providers/hoverProvider'
import { RemixReferenceProvider } from './providers/referenceProvider' import { RemixReferenceProvider } from './providers/referenceProvider'
@ -19,6 +20,14 @@ import { RemixCompletionProvider } from './providers/completionProvider'
import { RemixHighLightProvider } from './providers/highlightProvider' import { RemixHighLightProvider } from './providers/highlightProvider'
import { RemixDefinitionProvider } from './providers/definitionProvider' import { RemixDefinitionProvider } from './providers/definitionProvider'
enum MarkerSeverity {
Hint = 1,
Info = 2,
Warning = 4,
Error = 8
}
type sourceAnnotation = { type sourceAnnotation = {
row: number, row: number,
column: number, column: number,
@ -59,12 +68,12 @@ export type lineText = {
className: string className: string
afterContentClassName: string afterContentClassName: string
hide: boolean, hide: boolean,
hoverMessage: IMarkdownString | IMarkdownString[] hoverMessage: monacoTypes.IMarkdownString | monacoTypes.IMarkdownString[]
} }
type errorMarker = { type errorMarker = {
message: string message: string
severity: MarkerSeverity | 'warning' | 'info' | 'error' | 'hint' severity: monacoTypes.MarkerSeverity | 'warning' | 'info' | 'error' | 'hint'
position: { position: {
start: { start: {
line: number line: number
@ -78,7 +87,8 @@ type errorMarker = {
file: string file: string
} }
loader.config({ paths: { vs: 'assets/js/monaco-editor/dev/vs' } })
loader.config({ paths: { vs: 'assets/js/monaco-editor/min/vs' } })
export type DecorationsReturn = { export type DecorationsReturn = {
currentDecorations: Array<string> currentDecorations: Array<string>
@ -105,8 +115,8 @@ export interface EditorUIProps {
findMatches: (uri: string, value: string) => any findMatches: (uri: string, value: string) => any
getFontSize: () => number, getFontSize: () => number,
getValue: (uri: string) => string getValue: (uri: string) => string
getCursorPosition: (offset?: boolean) => number | IPosition getCursorPosition: (offset?: boolean) => number | monacoTypes.IPosition
getHoverPosition: (position: IPosition) => number getHoverPosition: (position: monacoTypes.IPosition) => number
addDecoration: (marker: sourceMarker, filePath: string, typeOfDecoration: string) => DecorationsReturn addDecoration: (marker: sourceMarker, filePath: string, typeOfDecoration: string) => DecorationsReturn
clearDecorationsByPlugin: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn clearDecorationsByPlugin: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
keepDecorationsFor: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn keepDecorationsFor: (filePath: string, plugin: string, typeOfDecoration: string, registeredDecorations: any, currentDecorations: any) => DecorationsReturn
@ -425,7 +435,7 @@ export const EditorUI = (props: EditorUIProps) => {
props.editorAPI.addErrorMarker = async (errors: errorMarker[], from: string) => { props.editorAPI.addErrorMarker = async (errors: errorMarker[], from: string) => {
const allMarkersPerfile: Record<string, Array<monaco.editor.IMarkerData>> = {} const allMarkersPerfile: Record<string, Array<monacoTypes.editor.IMarkerData>> = {}
for (const error of errors) { for (const error of errors) {
let filePath = error.file let filePath = error.file
@ -440,7 +450,7 @@ export const EditorUI = (props: EditorUIProps) => {
'info': MarkerSeverity.Info 'info': MarkerSeverity.Info
} }
if (model) { if (model) {
const markerData: monaco.editor.IMarkerData = { const markerData: monacoTypes.editor.IMarkerData = {
severity: (typeof error.severity === 'string') ? errorServerityMap[error.severity] : error.severity, severity: (typeof error.severity === 'string') ? errorServerityMap[error.severity] : error.severity,
startLineNumber: ((error.position.start && error.position.start.line) || 0), startLineNumber: ((error.position.start && error.position.start.line) || 0),
startColumn: ((error.position.start && error.position.start.column) || 0), startColumn: ((error.position.start && error.position.start.column) || 0),
@ -496,7 +506,7 @@ export const EditorUI = (props: EditorUIProps) => {
} }
} }
props.editorAPI.getHoverPosition = (position: monaco.Position) => { props.editorAPI.getHoverPosition = (position: monacoTypes.Position) => {
if (!monacoRef.current) return if (!monacoRef.current) return
const model = editorModelsState[currentFileRef.current]?.model const model = editorModelsState[currentFileRef.current]?.model
if (model) { if (model) {

@ -12,6 +12,7 @@ import { ROOT_PATH, slitherYml, solTestYml, tsSolTestYml } from '../utils/consta
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries // eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { IndexedDBStorage } from '../../../../../../apps/remix-ide/src/app/files/filesystems/indexedDB' import { IndexedDBStorage } from '../../../../../../apps/remix-ide/src/app/files/filesystems/indexedDB'
import { getUncommittedFiles } from '../utils/gitStatusFilter' import { getUncommittedFiles } from '../utils/gitStatusFilter'
import { AppModal, ModalTypes } from '@remix-ui/app'
declare global { declare global {
interface Window { remixFileSystemCallback: IndexedDBStorage; } interface Window { remixFileSystemCallback: IndexedDBStorage; }
@ -358,6 +359,30 @@ export const switchToWorkspace = async (name: string) => {
} }
} }
const loadFile = (name, file, provider, cb?): void => {
const fileReader = new FileReader()
fileReader.onload = async function (event) {
if (checkSpecialChars(file.name)) {
return dispatch(displayNotification('File Upload Failed', 'Special characters are not allowed', 'Close', null, async () => { }))
}
try {
await provider.set(name, event.target.result)
} catch (error) {
return dispatch(displayNotification('File Upload Failed', 'Failed to create file ' + name, 'Close', null, async () => { }))
}
const config = plugin.registry.get('config').api
const editor = plugin.registry.get('editor').api
if ((config.get('currentFile') === name) && (editor.currentContent() !== event.target.result)) {
editor.setText(name, event.target.result)
}
}
fileReader.readAsText(file)
cb && cb(null, true)
}
export const uploadFile = async (target, targetFolder: string, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => { export const uploadFile = async (target, targetFolder: string, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => {
// TODO The file explorer is merely a view on the current state of // TODO The file explorer is merely a view on the current state of
// the files module. Please ask the user here if they want to overwrite // the files module. Please ask the user here if they want to overwrite
@ -365,39 +390,52 @@ export const uploadFile = async (target, targetFolder: string, cb?: (err: Error,
// pick that up via the 'fileAdded' event from the files module. // pick that up via the 'fileAdded' event from the files module.
[...target.files].forEach(async (file) => { [...target.files].forEach(async (file) => {
const workspaceProvider = plugin.fileProviders.workspace const workspaceProvider = plugin.fileProviders.workspace
const loadFile = (name: string): void => { const name = targetFolder === '/' ? file.name : `${targetFolder}/${file.name}`
const fileReader = new FileReader()
fileReader.onload = async function (event) {
if (checkSpecialChars(file.name)) {
return dispatch(displayNotification('File Upload Failed', 'Special characters are not allowed', 'Close', null, async () => { }))
}
try {
await workspaceProvider.set(name, event.target.result)
} catch (error) {
return dispatch(displayNotification('File Upload Failed', 'Failed to create file ' + name, 'Close', null, async () => { }))
}
const config = plugin.registry.get('config').api
const editor = plugin.registry.get('editor').api
if ((config.get('currentFile') === name) && (editor.currentContent() !== event.target.result)) { if (!await workspaceProvider.exists(name)) {
editor.setText(name, event.target.result) loadFile(name, file, workspaceProvider, cb)
} } else {
const modalContent: AppModal = {
id: 'overwriteUploadFile',
title: 'Confirm overwrite',
message: `The file "${name}" already exists! Would you like to overwrite it?`,
modalType: ModalTypes.confirm,
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: () => {
loadFile(name, file, workspaceProvider, cb)
},
cancelFn: () => {},
hideFn: () => {}
} }
fileReader.readAsText(file) plugin.call('notification', 'modal', modalContent)
cb && cb(null, true)
} }
const name = targetFolder === '/' ? file.name : `${targetFolder}/${file.name}` })
}
export const uploadFolder = async (target, targetFolder: string, cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => {
for(const file of [...target.files]) {
const workspaceProvider = plugin.fileProviders.workspace
const name = targetFolder === '/' ? file.webkitRelativePath : `${targetFolder}/${file.webkitRelativePath}`
if (!await workspaceProvider.exists(name)) { if (!await workspaceProvider.exists(name)) {
loadFile(name) loadFile(name, file, workspaceProvider, cb)
} else { } else {
dispatch(displayNotification('Confirm overwrite', `The file ${name} already exists! Would you like to overwrite it?`, 'OK', null, () => { const modalContent: AppModal = {
loadFile(name) id: 'overwriteUploadFolderFile',
}, () => { })) title: 'Confirm overwrite',
message: `The file "${name}" already exists! Would you like to overwrite it?`,
modalType: ModalTypes.confirm,
okLabel: 'OK',
cancelLabel: 'Cancel',
okFn: () => {
loadFile(name, file, workspaceProvider, cb)
},
cancelFn: () => {},
hideFn: () => {}
}
plugin.call('notification', 'modal', modalContent)
} }
}) }
} }
export const getWorkspaces = async (): Promise<{ name: string, isGitRepo: boolean, branches?: { remote: any; name: string; }[], currentBranch?: string }[]> | undefined => { export const getWorkspaces = async (): Promise<{ name: string, isGitRepo: boolean, branches?: { remote: any; name: string; }[], currentBranch?: string }[]> | undefined => {

@ -10,27 +10,33 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
menuItems: [ menuItems: [
{ {
action: 'createNewFile', action: 'createNewFile',
title: 'Create New File', title: 'Create new file',
icon: 'far fa-file', icon: 'far fa-file',
placement: 'top-start' placement: 'top'
}, },
{ {
action: 'createNewFolder', action: 'createNewFolder',
title: 'Create New Folder', title: 'Create new folder',
icon: 'far fa-folder', icon: 'far fa-folder',
placement: 'top-end' placement: 'top'
}, },
{ {
action: 'publishToGist', action: 'publishToGist',
title: 'Publish all the current workspace files to a github gist', title: 'Publish current workspace to GitHub gist',
icon: 'fab fa-github', icon: 'fab fa-github',
placement: 'bottom-start' placement: 'top'
}, },
{ {
action: 'uploadFile', action: 'uploadFile',
title: 'Load a local file into current workspace', title: 'Upload files into current workspace',
icon: 'fa fa-upload', icon: 'fa fa-upload',
placement: 'right' placement: 'top'
},
{
action: 'uploadFolder',
title: 'Upload folder into current workspace',
icon: 'fas fa-folder-upload',
placement: 'top'
}, },
{ {
action: 'updateGist', action: 'updateGist',
@ -41,6 +47,7 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
].filter(item => props.menuItems && props.menuItems.find((name) => { return name === item.action })), ].filter(item => props.menuItems && props.menuItems.find((name) => { return name === item.action })),
actions: {} actions: {}
}) })
const enableDirUpload = { directory: "", webkitdirectory: "" }
useEffect(() => { useEffect(() => {
const actions = { const actions = {
@ -67,7 +74,7 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
if (action === 'uploadFile') { if (action === 'uploadFile') {
return ( return (
<CustomTooltip <CustomTooltip
placement="right" placement={placement as Placement}
tooltipId="uploadFileTooltip" tooltipId="uploadFileTooltip"
tooltipClasses="text-nowrap" tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id={`filePanel.${action}`} defaultMessage={title} />} tooltipText={<FormattedMessage id={`filePanel.${action}`} defaultMessage={title} />}
@ -81,6 +88,7 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
> >
<input id="fileUpload" data-id="fileExplorerFileUpload" type="file" onChange={(e) => { <input id="fileUpload" data-id="fileExplorerFileUpload" type="file" onChange={(e) => {
e.stopPropagation() e.stopPropagation()
_paq.push(['trackEvent', 'fileExplorer', 'fileAction', action])
props.uploadFile(e.target) props.uploadFile(e.target)
e.target.value = null e.target.value = null
}} }}
@ -88,6 +96,31 @@ export const FileExplorerMenu = (props: FileExplorerMenuProps) => {
</label> </label>
</CustomTooltip> </CustomTooltip>
) )
} else if (action === 'uploadFolder') {
return (
<CustomTooltip
placement={placement as Placement}
tooltipId="uploadFolderTooltip"
tooltipClasses="text-nowrap"
tooltipText={<FormattedMessage id={`filePanel.${action}`} defaultMessage={title} />}
key={`index-${action}-${placement}-${icon}`}
>
<label
id={action}
data-id={'fileExplorerUploadFolder' + action }
className={icon + ' mb-0 remixui_newFile'}
key={`index-${action}-${placement}-${icon}`}
>
<input id="folderUpload" data-id="fileExplorerFolderUpload" type="file" onChange={(e) => {
e.stopPropagation()
_paq.push(['trackEvent', 'fileExplorer', 'fileAction', action])
props.uploadFolder(e.target)
e.target.value = null
}}
{...enableDirUpload} multiple />
</label>
</CustomTooltip>
)
} else { } else {
return ( return (
<CustomTooltip <CustomTooltip

@ -190,6 +190,14 @@ export const FileExplorer = (props: FileExplorerProps) => {
props.dispatchUploadFile(target, parentFolder) props.dispatchUploadFile(target, parentFolder)
} }
const uploadFolder = (target) => {
const parentFolder = getFocusedFolder()
const expandPath = [...new Set([...props.expandPath, parentFolder])]
props.dispatchHandleExpandPath(expandPath)
props.dispatchUploadFolder(target, parentFolder)
}
const copyFile = (src: string, dest: string) => { const copyFile = (src: string, dest: string) => {
try { try {
props.dispatchCopyFile(src, dest) props.dispatchCopyFile(src, dest)
@ -459,6 +467,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
createNewFolder={handleNewFolderInput} createNewFolder={handleNewFolderInput}
publishToGist={publishToGist} publishToGist={publishToGist}
uploadFile={uploadFile} uploadFile={uploadFile}
uploadFolder={uploadFolder}
/> />
</div> </div>
} }

@ -18,6 +18,7 @@ export const FileSystemContext = createContext<{
dispatchDeleteAllWorkspaces: () => Promise<void>, dispatchDeleteAllWorkspaces: () => Promise<void>,
dispatchPublishToGist: (path?: string, type?: string) => Promise<void>, dispatchPublishToGist: (path?: string, type?: string) => Promise<void>,
dispatchUploadFile: (target?: SyntheticEvent, targetFolder?: string) => Promise<void>, dispatchUploadFile: (target?: SyntheticEvent, targetFolder?: string) => Promise<void>,
dispatchUploadFolder: (target?: SyntheticEvent, targetFolder?: string) => Promise<void>,
dispatchCreateNewFile: (path: string, rootDir: string) => Promise<void>, dispatchCreateNewFile: (path: string, rootDir: string) => Promise<void>,
dispatchSetFocusElement: (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => Promise<void>, dispatchSetFocusElement: (elements: { key: string, type: 'file' | 'folder' | 'gist' }[]) => Promise<void>,
dispatchCreateNewFolder: (path: string, rootDir: string) => Promise<void>, dispatchCreateNewFolder: (path: string, rootDir: string) => Promise<void>,

@ -7,7 +7,7 @@ import { FileSystemContext } from '../contexts'
import { browserReducer, browserInitialState } from '../reducers/workspace' import { browserReducer, browserInitialState } from '../reducers/workspace'
import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, deleteAllWorkspaces, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder, import { initWorkspace, fetchDirectory, removeInputField, deleteWorkspace, deleteAllWorkspaces, clearPopUp, publishToGist, createNewFile, setFocusElement, createNewFolder,
deletePath, renamePath, downloadPath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace, deletePath, renamePath, downloadPath, copyFile, copyFolder, runScript, emitContextMenuEvent, handleClickFile, handleExpandPath, addInputField, createWorkspace,
fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile, uploadFolder, handleDownloadFiles, restoreBackupZip, cloneRepository, moveFile, moveFolder,
showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction showAllBranches, switchBranch, createNewBranch, checkoutRemoteBranch, createSolidityGithubAction, createTsSolGithubAction, createSlitherGithubAction
} from '../actions' } from '../actions'
import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types' import { Modal, WorkspaceProps, WorkspaceTemplate } from '../types'
@ -79,6 +79,10 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
await uploadFile(target, targetFolder) await uploadFile(target, targetFolder)
} }
const dispatchUploadFolder = async (target?: SyntheticEvent, targetFolder?: string) => {
await uploadFolder(target, targetFolder)
}
const dispatchCreateNewFile = async (path: string, rootDir: string) => { const dispatchCreateNewFile = async (path: string, rootDir: string) => {
await createNewFile(path, rootDir) await createNewFile(path, rootDir)
} }
@ -265,6 +269,7 @@ export const FileSystemProvider = (props: WorkspaceProps) => {
dispatchDeleteAllWorkspaces, dispatchDeleteAllWorkspaces,
dispatchPublishToGist, dispatchPublishToGist,
dispatchUploadFile, dispatchUploadFile,
dispatchUploadFolder,
dispatchCreateNewFile, dispatchCreateNewFile,
dispatchSetFocusElement, dispatchSetFocusElement,
dispatchCreateNewFolder, dispatchCreateNewFolder,

@ -528,7 +528,7 @@ export function Workspace () {
<div className='h-100 remixui_treeview' data-id='filePanelFileExplorerTree'> <div className='h-100 remixui_treeview' data-id='filePanelFileExplorerTree'>
<FileExplorer <FileExplorer
name={currentWorkspace} name={currentWorkspace}
menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '']} menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '', canUpload ? 'uploadFolder' : '']}
contextMenuItems={global.fs.browser.contextMenu.registeredMenuItems} contextMenuItems={global.fs.browser.contextMenu.registeredMenuItems}
removedContextMenuItems={global.fs.browser.contextMenu.removedMenuItems} removedContextMenuItems={global.fs.browser.contextMenu.removedMenuItems}
files={global.fs.browser.files} files={global.fs.browser.files}
@ -547,6 +547,7 @@ export function Workspace () {
dispatchRenamePath={global.dispatchRenamePath} dispatchRenamePath={global.dispatchRenamePath}
dispatchDownloadPath={global.dispatchDownloadPath} dispatchDownloadPath={global.dispatchDownloadPath}
dispatchUploadFile={global.dispatchUploadFile} dispatchUploadFile={global.dispatchUploadFile}
dispatchUploadFolder={global.dispatchUploadFolder}
dispatchCopyFile={global.dispatchCopyFile} dispatchCopyFile={global.dispatchCopyFile}
dispatchCopyFolder={global.dispatchCopyFolder} dispatchCopyFolder={global.dispatchCopyFolder}
dispatchPublishToGist={global.dispatchPublishToGist} dispatchPublishToGist={global.dispatchPublishToGist}
@ -587,6 +588,7 @@ export function Workspace () {
dispatchRenamePath={global.dispatchRenamePath} dispatchRenamePath={global.dispatchRenamePath}
dispatchDownloadPath={global.dispatchDownloadPath} dispatchDownloadPath={global.dispatchDownloadPath}
dispatchUploadFile={global.dispatchUploadFile} dispatchUploadFile={global.dispatchUploadFile}
dispatchUploadFolder={global.dispatchUploadFolder}
dispatchCopyFile={global.dispatchCopyFile} dispatchCopyFile={global.dispatchCopyFile}
dispatchCopyFolder={global.dispatchCopyFolder} dispatchCopyFolder={global.dispatchCopyFolder}
dispatchPublishToGist={global.dispatchPublishToGist} dispatchPublishToGist={global.dispatchPublishToGist}

@ -98,6 +98,7 @@ export interface FileExplorerProps {
dispatchRenamePath: (oldPath: string, newPath: string) => Promise<void>, dispatchRenamePath: (oldPath: string, newPath: string) => Promise<void>,
dispatchDownloadPath: (path: string) => Promise<void>, dispatchDownloadPath: (path: string) => Promise<void>,
dispatchUploadFile: (target?: React.SyntheticEvent, targetFolder?: string) => Promise<void>, dispatchUploadFile: (target?: React.SyntheticEvent, targetFolder?: string) => Promise<void>,
dispatchUploadFolder: (target?: React.SyntheticEvent, targetFolder?: string) => Promise<void>,
dispatchCopyFile: (src: string, dest: string) => Promise<void>, dispatchCopyFile: (src: string, dest: string) => Promise<void>,
dispatchCopyFolder: (src: string, dest: string) => Promise<void>, dispatchCopyFolder: (src: string, dest: string) => Promise<void>,
dispatchRunScript: (path: string) => Promise<void>, dispatchRunScript: (path: string) => Promise<void>,
@ -120,6 +121,7 @@ export interface FileExplorerMenuProps {
createNewFolder: (parentFolder?: string) => void, createNewFolder: (parentFolder?: string) => void,
publishToGist: (path?: string) => void, publishToGist: (path?: string) => void,
uploadFile: (target: EventTarget & HTMLInputElement) => void uploadFile: (target: EventTarget & HTMLInputElement) => void
uploadFolder: (target: EventTarget & HTMLInputElement) => void
tooltipPlacement?: Placement tooltipPlacement?: Placement
} }
export interface FileExplorerContextMenuProps { export interface FileExplorerContextMenuProps {

@ -129,7 +129,6 @@
"@ethereumjs/vm": "^6.3.0", "@ethereumjs/vm": "^6.3.0",
"@ethersphere/bee-js": "^3.2.0", "@ethersphere/bee-js": "^3.2.0",
"@isomorphic-git/lightning-fs": "^4.4.1", "@isomorphic-git/lightning-fs": "^4.4.1",
"@monaco-editor/react": "4.4.5",
"@openzeppelin/contracts": "^4.7.3", "@openzeppelin/contracts": "^4.7.3",
"@openzeppelin/upgrades-core": "^1.22.0", "@openzeppelin/upgrades-core": "^1.22.0",
"@openzeppelin/wizard": "^0.1.1", "@openzeppelin/wizard": "^0.1.1",
@ -175,7 +174,6 @@
"jszip": "^3.6.0", "jszip": "^3.6.0",
"latest-version": "^5.1.0", "latest-version": "^5.1.0",
"merge": "^2.1.1", "merge": "^2.1.1",
"monaco-editor": "^0.30.1",
"npm-install-version": "^6.0.2", "npm-install-version": "^6.0.2",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"prettier": "^2.8.4", "prettier": "^2.8.4",
@ -224,6 +222,7 @@
"@babel/preset-typescript": "^7.18.6", "@babel/preset-typescript": "^7.18.6",
"@babel/register": "^7.4.4", "@babel/register": "^7.4.4",
"@fortawesome/fontawesome-free": "^5.8.1", "@fortawesome/fontawesome-free": "^5.8.1",
"@monaco-editor/react": "4.4.5",
"@nrwl/cli": "^15.7.1", "@nrwl/cli": "^15.7.1",
"@nrwl/eslint-plugin-nx": "^15.7.1", "@nrwl/eslint-plugin-nx": "^15.7.1",
"@nrwl/jest": "15.7.1", "@nrwl/jest": "15.7.1",
@ -235,8 +234,8 @@
"@nrwl/web": "15.7.1", "@nrwl/web": "15.7.1",
"@nrwl/webpack": "15.7.1", "@nrwl/webpack": "15.7.1",
"@nrwl/workspace": "^15.7.1", "@nrwl/workspace": "^15.7.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@openzeppelin/contracts-upgradeable": "^4.8.1", "@openzeppelin/contracts-upgradeable": "^4.8.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
"@testing-library/react": "13.4.0", "@testing-library/react": "13.4.0",
"@types/axios": "^0.14.0", "@types/axios": "^0.14.0",
@ -324,6 +323,7 @@
"minixhr": "^4.0.0", "minixhr": "^4.0.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"mocha": "^8.0.1", "mocha": "^8.0.1",
"monaco-editor": "^0.30.1",
"nanohtml": "^1.6.3", "nanohtml": "^1.6.3",
"nightwatch": "^2.3", "nightwatch": "^2.3",
"nodemon": "^2.0.4", "nodemon": "^2.0.4",

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