FE decorators

pull/5370/head
filip mertens 2 years ago
parent e976d37ab1
commit e7dd6f07e3
  1. 7
      apps/remix-ide/src/app/panels/file-panel.js
  2. 45
      libs/remix-core-plugin/src/lib/code-parser.tsx
  3. 3
      libs/remix-core-plugin/tsconfig.json
  4. 2
      libs/remix-ui/editor/src/lib/providers/completionProvider.ts
  5. 1
      libs/remix-ui/workspace/src/index.ts
  6. 13
      libs/remix-ui/workspace/src/lib/actions/events.ts
  7. 9
      libs/remix-ui/workspace/src/lib/actions/payload.ts
  8. 3
      libs/remix-ui/workspace/src/lib/components/file-explorer.tsx
  9. 21
      libs/remix-ui/workspace/src/lib/components/file-label.tsx
  10. 16
      libs/remix-ui/workspace/src/lib/components/file-render.tsx
  11. 56
      libs/remix-ui/workspace/src/lib/components/file-state.tsx
  12. 13
      libs/remix-ui/workspace/src/lib/components/filestates/file-state-custom.tsx
  13. 11
      libs/remix-ui/workspace/src/lib/components/filestates/file-state-error.tsx
  14. 0
      libs/remix-ui/workspace/src/lib/components/filestates/file-state-icon-properties.tsx
  15. 11
      libs/remix-ui/workspace/src/lib/components/filestates/file-state-warning.tsx
  16. 42
      libs/remix-ui/workspace/src/lib/reducers/workspace.ts
  17. 2
      libs/remix-ui/workspace/src/lib/remix-ui-workspace.tsx
  18. 30
      libs/remix-ui/workspace/src/lib/types/index.ts

@ -30,7 +30,7 @@ const { SlitherHandle } = require('../files/slither-handle.js')
const profile = { const profile = {
name: 'filePanel', name: 'filePanel',
displayName: 'File explorers', displayName: 'File explorers',
methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace', 'registerContextMenuItem', 'renameWorkspace', 'deleteWorkspace'], methods: ['createNewFile', 'uploadFile', 'getCurrentWorkspace', 'getWorkspaces', 'createWorkspace', 'setWorkspace', 'registerContextMenuItem', 'renameWorkspace', 'deleteWorkspace', 'setFileState'],
events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'], events: ['setWorkspace', 'workspaceRenamed', 'workspaceDeleted', 'workspaceCreated'],
icon: 'assets/img/fileManager.webp', icon: 'assets/img/fileManager.webp',
description: ' - ', description: ' - ',
@ -85,6 +85,11 @@ module.exports = class Filepanel extends ViewPlugin {
}) })
} }
async setFileState (items) {
console.log(items)
this.emit('setFileState', items)
}
getCurrentWorkspace () { getCurrentWorkspace () {
return this.currentWorkspaceMetadata return this.currentWorkspaceMetadata
} }

@ -6,6 +6,10 @@ import { Compiler } from '@remix-project/remix-solidity'
import { AstNode, CompilationError, CompilationResult, CompilationSource } from '@remix-project/remix-solidity' import { AstNode, CompilationError, CompilationResult, CompilationSource } from '@remix-project/remix-solidity'
import { helper } from '@remix-project/remix-solidity' import { helper } from '@remix-project/remix-solidity'
import React from 'react'
// eslint-disable-next-line
import { fileState, fileStateType } from '@remix-ui/workspace'
const SolidityParser = (window as any).SolidityParser = (window as any).SolidityParser || [] const SolidityParser = (window as any).SolidityParser = (window as any).SolidityParser || []
const profile = { const profile = {
@ -107,7 +111,48 @@ export class CodeParser extends Plugin {
} }
console.log('allErrors', allErrors) console.log('allErrors', allErrors)
await this.call('editor', 'addErrorMarker', allErrors) await this.call('editor', 'addErrorMarker', allErrors)
try {
let fileState:fileState = {
path: this.currentFile,
isDirectory: false,
fileStateType: [fileStateType.Custom],
fileStateLabelClass: 'text-success',
fileStateIconClass: '',
fileStateIcon: <i className="text-success fas fa-smile"></i>,
comment: '',
owner: 'code-parser',
bubble: true
}
await this.call('filePanel', 'setFileState', [fileState])
fileState = {
...fileState,
path: 'contracts/1_Storage.sol',
fileStateLabelClass: 'text-danger',
fileStateIcon: <i className="text-danger fas fa-spinner fa-spin"></i>,
}
await this.call('filePanel', 'setFileState', [fileState])
fileState = {
...fileState,
path: 'scripts/ethers-lib.ts',
fileStateLabelClass: 'text-danger',
fileStateIcon: <div className='btn btn-danger btn-sm'><li className='fa fa-phone'></li>call rob now!</div>,
}
await this.call('filePanel', 'setFileState', [fileState])
} catch (e) {
console.log('error calling filePanel', e)
}
} else { } else {
await this.call('filePanel', 'setFileState', [{
path: this.currentFile,
isDirectory: false,
fileStateType: [],
fileStateClass: '',
comment: '',
owner: 'code-parser',
bubble: true
}])
await this.call('editor', 'clearErrorMarkers', result.getSourceCode().sources) await this.call('editor', 'clearErrorMarkers', result.getSourceCode().sources)
} }

@ -1,8 +1,9 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"jsx": "react",
"types": ["node"], "types": ["node"],
"esModuleInterop": true "esModuleInterop": true
}, },
"include": ["**/*.ts"] "include": ["**/*.ts", "src/lib/code-parser.tsx"]
} }

@ -2,7 +2,7 @@ import { isArray } from "lodash"
import { editor, languages, Position } from "monaco-editor" import { editor, languages, Position } from "monaco-editor"
import monaco from "../../types/monaco" import monaco from "../../types/monaco"
import { EditorUIProps } from "../remix-ui-editor" import { EditorUIProps } from "../remix-ui-editor"
import { GeCompletionUnits, getBlockCompletionItems, GetCompletionKeywords, getCompletionSnippets, GetCompletionTypes, getContextualAutoCompleteByGlobalVariable, GetGlobalFunctions, GetGlobalVariable, getMsgCompletionItems, getTxCompletionItems } from "./completion/completionGlobals" import { GeCompletionUnits, GetCompletionKeywords, getCompletionSnippets, GetCompletionTypes, getContextualAutoCompleteByGlobalVariable, GetGlobalFunctions, GetGlobalVariable } from "./completion/completionGlobals"
export class RemixCompletionProvider implements languages.CompletionItemProvider { export class RemixCompletionProvider implements languages.CompletionItemProvider {

@ -1,2 +1,3 @@
export * from './lib/providers/FileSystemProvider' export * from './lib/providers/FileSystemProvider'
export * from './lib/contexts' export * from './lib/contexts'
export { fileState, fileStateType } from './lib/types/index'

@ -1,7 +1,7 @@
import { extractParentFromKey } from '@remix-ui/helper' import { extractParentFromKey } from '@remix-ui/helper'
import React from 'react' import React from 'react'
import { action, WorkspaceTemplate } from '../types' import { action, WorkspaceTemplate, fileState } 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, setFileStateSuccess } from './payload'
import { addInputField, createWorkspace, deleteWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace' import { addInputField, createWorkspace, deleteWorkspace, fetchWorkspaceDirectory, renameWorkspace, switchToWorkspace, uploadFile } from './workspace'
const LOCALHOST = ' - connect to localhost - ' const LOCALHOST = ' - connect to localhost - '
@ -38,6 +38,10 @@ export const listenOnPluginEvents = (filePanelPlugin) => {
uploadFile(target, dir, cb) uploadFile(target, dir, cb)
}) })
plugin.on('filePanel', 'setFileState', async (items: fileState[]) => {
setFileState(items)
})
plugin.on('remixd', 'rootFolderChanged', async (path: string) => { plugin.on('remixd', 'rootFolderChanged', async (path: string) => {
rootFolderChanged(path) rootFolderChanged(path)
}) })
@ -202,3 +206,8 @@ const fileRenamed = async (oldPath: string) => {
const rootFolderChanged = async (path) => { const rootFolderChanged = async (path) => {
await dispatch(rootFolderChangedSuccess(path)) await dispatch(rootFolderChangedSuccess(path))
} }
const setFileState = async (items: fileState[], cb?: (err: Error, result?: string | number | boolean | Record<string, any>) => void) => {
await dispatch(setFileStateSuccess(items))
cb && cb(null, true)
}

@ -1,4 +1,4 @@
import { action } from '../types' import { action, fileState } from '../types'
export const setCurrentWorkspace = (workspace: string) => { export const setCurrentWorkspace = (workspace: string) => {
return { return {
@ -239,3 +239,10 @@ export const fsInitializationCompleted = () => {
type: 'FS_INITIALIZATION_COMPLETED' type: 'FS_INITIALIZATION_COMPLETED'
} }
} }
export const setFileStateSuccess = (items: fileState[]) => {
return {
type: 'SET_FILE_STATE_SUCCESS',
payload: items
}
}

@ -12,7 +12,7 @@ import { checkSpecialChars, extractNameFromKey, extractParentFromKey, joinPath }
import { FileRender } from './file-render' import { FileRender } from './file-render'
export const FileExplorer = (props: FileExplorerProps) => { export const FileExplorer = (props: FileExplorerProps) => {
const { name, contextMenuItems, removedContextMenuItems, files } = props const { name, contextMenuItems, removedContextMenuItems, files, fileState } = props
const [state, setState] = useState<FileExplorerState>({ const [state, setState] = useState<FileExplorerState>({
ctrlKey: false, ctrlKey: false,
newFileName: '', newFileName: '',
@ -432,6 +432,7 @@ export const FileExplorer = (props: FileExplorerProps) => {
{ {
files[props.name] && Object.keys(files[props.name]).map((key, index) => <FileRender files[props.name] && Object.keys(files[props.name]).map((key, index) => <FileRender
file={files[props.name][key]} file={files[props.name][key]}
fileState={fileState}
index={index} index={index}
focusContext={state.focusContext} focusContext={state.focusContext}
focusEdit={state.focusEdit} focusEdit={state.focusEdit}

@ -1,6 +1,6 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { FileType } from '../types' import { fileState, FileType } from '../types'
export interface FileLabelProps { export interface FileLabelProps {
file: FileType, file: FileType,
@ -10,12 +10,14 @@ export interface FileLabelProps {
isNew: boolean isNew: boolean
lastEdit: string lastEdit: string
} }
fileState: fileState[],
editModeOff: (content: string) => void editModeOff: (content: string) => void
} }
export const FileLabel = (props: FileLabelProps) => { export const FileLabel = (props: FileLabelProps) => {
const { file, focusEdit, editModeOff } = props const { file, focusEdit, editModeOff, fileState } = props
const [isEditable, setIsEditable] = useState<boolean>(false) const [isEditable, setIsEditable] = useState<boolean>(false)
const [fileStateClasses, setFileStateClasses] = useState<string>('')
const labelRef = useRef(null) const labelRef = useRef(null)
useEffect(() => { useEffect(() => {
@ -24,6 +26,17 @@ export const FileLabel = (props: FileLabelProps) => {
} }
}, [file.path, focusEdit]) }, [file.path, focusEdit])
useEffect(() => {
const state = props.fileState.find((state: fileState) => {
if(state.path === props.file.path) return true
if(state.bubble && props.file.isDirectory && state.path.startsWith(props.file.path)) return true
})
console.log(props)
if (state && state.fileStateLabelClass) {
setFileStateClasses(state.fileStateLabelClass)
}
}, [fileState])
useEffect(() => { useEffect(() => {
if (labelRef.current) { if (labelRef.current) {
setTimeout(() => { setTimeout(() => {
@ -57,10 +70,10 @@ export const FileLabel = (props: FileLabelProps) => {
> >
<span <span
title={file.path} title={file.path}
className={'text-nowrap remixui_label ' + (file.isDirectory ? 'folder' : 'remixui_leaf')} className={`text-nowrap remixui_label ${fileStateClasses} ` + (file.isDirectory ? 'folder' : 'remixui_leaf')}
data-path={file.path} data-path={file.path}
> >
{ file.name } {file.name}
</span> </span>
</div> </div>
) )

@ -1,11 +1,12 @@
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
import React, { SyntheticEvent, useEffect, useState } from 'react' import React, { SyntheticEvent, useEffect, useState } from 'react'
import { FileType } from '../types' import { fileState, FileType } from '../types'
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
import { TreeView, TreeViewItem } from '@remix-ui/tree-view' import { TreeView, TreeViewItem } from '@remix-ui/tree-view'
import { getPathIcon } from '@remix-ui/helper' import { getPathIcon } from '@remix-ui/helper'
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
import { FileLabel } from './file-label' import { FileLabel } from './file-label'
import FileState from './file-state'
export interface RenderFileProps { export interface RenderFileProps {
file: FileType, file: FileType,
@ -19,6 +20,7 @@ export interface RenderFileProps {
handleClickFolder: (path: string, type: string) => void, handleClickFolder: (path: string, type: string) => void,
handleClickFile: (path: string, type: string) => void, handleClickFile: (path: string, type: string) => void,
handleContextMenu: (pageX: number, pageY: number, path: string, content: string, type: string) => void handleContextMenu: (pageX: number, pageY: number, path: string, content: string, type: string) => void
fileState: fileState[]
} }
export const FileRender = (props: RenderFileProps) => { export const FileRender = (props: RenderFileProps) => {
@ -76,7 +78,7 @@ export const FileRender = (props: RenderFileProps) => {
iconX='pr-3 fa fa-folder' iconX='pr-3 fa fa-folder'
iconY='pr-3 fa fa-folder-open' iconY='pr-3 fa fa-folder-open'
key={`${file.path + props.index}`} key={`${file.path + props.index}`}
label={<FileLabel file={file} focusEdit={props.focusEdit} editModeOff={props.editModeOff} />} label={<FileLabel fileState={props.fileState} file={file} focusEdit={props.focusEdit} editModeOff={props.editModeOff} />}
onClick={handleFolderClick} onClick={handleFolderClick}
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
labelClass={labelClass} labelClass={labelClass}
@ -89,6 +91,7 @@ export const FileRender = (props: RenderFileProps) => {
file.child ? <TreeView id={`treeView${file.path}`} key={`treeView${file.path}`} {...spreadProps }>{ file.child ? <TreeView id={`treeView${file.path}`} key={`treeView${file.path}`} {...spreadProps }>{
Object.keys(file.child).map((key, index) => <FileRender Object.keys(file.child).map((key, index) => <FileRender
file={file.child[key]} file={file.child[key]}
fileState={props.fileState}
index={index} index={index}
focusContext={props.focusContext} focusContext={props.focusContext}
focusEdit={props.focusEdit} focusEdit={props.focusEdit}
@ -111,7 +114,14 @@ export const FileRender = (props: RenderFileProps) => {
<TreeViewItem <TreeViewItem
id={`treeViewItem${file.path}`} id={`treeViewItem${file.path}`}
key={`treeView${file.path}`} key={`treeView${file.path}`}
label={<FileLabel file={file} focusEdit={props.focusEdit} editModeOff={props.editModeOff} />} label={
<>
<div className="d-flex flex-row">
<FileLabel file={file} fileState={props.fileState} focusEdit={props.focusEdit} editModeOff={props.editModeOff} />
<FileState file={file} fileState={props.fileState}/>
</div>
</>
}
onClick={handleFileClick} onClick={handleFileClick}
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
icon={icon} icon={icon}

@ -0,0 +1,56 @@
// eslint-disable-next-line no-use-before-define
import React, { useEffect, useState } from 'react'
import { FileType, fileState, fileStateType } from '../types'
import FileStateCustom from './filestates/file-state-custom'
import FileStateError from './filestates/file-state-error'
import FileStateWarning from './filestates/file-state-warning'
// import FileStateModified from './filestates/file-state-modified'
// import FileStateUntracked from './filestates/file-state-untracked'
export type fileStateProps = {
file: FileType,
fileState: fileState[]
}
export const FileState = (props: fileStateProps) => {
const [state, setState] = useState<fileState>(undefined)
useEffect(() => {
console.log(props.file)
console.log(props.fileState)
setState(props.fileState.find((st) => st.path === props.file.path))
}, [props.fileState])
const getTags = function () {
if (state) {
const types = state.fileStateType
const elements: any[] = []
for (const type of types) {
switch (type) {
case fileStateType.Modified:
//elements.push(<FileStateModified key={type}/>)
break
case fileStateType.Untracked:
//elements.push(<FileStateUntracked key={type}/>)
break
case fileStateType.Error:
elements.push(<FileStateError fileState={state} key={type} />)
break
case fileStateType.Warning:
elements.push(<FileStateWarning fileState={state} key={type} />)
break
case fileStateType.Custom:
elements.push(<FileStateCustom fileState={state} key={type} />)
break
}
}
return elements
}
}
return <>
{getTags()}
</>
}
export default FileState

@ -0,0 +1,13 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import { fileState } from '../../types'
const FileStateError = (props: {
fileState: fileState
}) => {
return <><span className={`${props.fileState.fileStateIconClass}pr-2`}>
{props.fileState.fileStateIcon}
</span></>
}
export default FileStateError

@ -0,0 +1,11 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import { fileState } from '../../types'
const FileStateError = (props: {
fileState: fileState
}) => {
return <><span className={`${props.fileState.fileStateIconClass} text-danger pr-2`}>&#11044;</span></>
}
export default FileStateError

@ -0,0 +1,11 @@
// eslint-disable-next-line no-use-before-define
import React from 'react'
import { fileState } from '../../types'
const FileStateWarning = (props: {
fileState: fileState
}) => {
return <><span className={`${props.fileState.fileStateIconClass} text-warning pr-2`}>&#11044;</span></>
}
export default FileStateWarning

@ -1,5 +1,5 @@
import { extractNameFromKey } from '@remix-ui/helper' import { extractNameFromKey } from '@remix-ui/helper'
import { action, FileType } from '../types' import { action, fileState, FileType } from '../types'
import * as _ from 'lodash' import * as _ from 'lodash'
interface Action { interface Action {
type: string type: string
@ -20,7 +20,8 @@ export interface BrowserState {
registeredMenuItems: action[], registeredMenuItems: action[],
removedMenuItems: action[], removedMenuItems: action[],
error: string error: string
} },
fileState: fileState[]
}, },
localhost: { localhost: {
sharedFolder: string, sharedFolder: string,
@ -35,7 +36,8 @@ export interface BrowserState {
registeredMenuItems: action[], registeredMenuItems: action[],
removedMenuItems: action[], removedMenuItems: action[],
error: string error: string
} },
fileState: []
}, },
mode: 'browser' | 'localhost', mode: 'browser' | 'localhost',
notification: { notification: {
@ -68,7 +70,8 @@ export const browserInitialState: BrowserState = {
registeredMenuItems: [], registeredMenuItems: [],
removedMenuItems: [], removedMenuItems: [],
error: null error: null
} },
fileState: []
}, },
localhost: { localhost: {
sharedFolder: '', sharedFolder: '',
@ -83,7 +86,8 @@ export const browserInitialState: BrowserState = {
registeredMenuItems: [], registeredMenuItems: [],
removedMenuItems: [], removedMenuItems: [],
error: null error: null
} },
fileState: []
}, },
mode: 'browser', mode: 'browser',
notification: { notification: {
@ -599,6 +603,34 @@ export const browserReducer = (state = browserInitialState, action: Action) => {
} }
} }
case 'SET_FILE_STATE_SUCCESS': {
const payload = action.payload as fileState[]
console.log('SET_FILE_STATE_SUCCESS', payload)
const a = state.browser.fileState
const b = payload
const merge = _.merge({}, _.keyBy(a, 'path'), _.keyBy(b, 'path'))
const vals = _.values(merge)
console.log(a)
console.log(vals)
vals.map(function (x) {
const c = a.find(function (el) {
return el.path === x.path
})
if (c && c.fileStateType) {
x.fileStateType = _.uniq([...c.fileStateType, ...x.fileStateType])
}
return x
})
console.log(vals)
return {
...state,
browser: {
...state.browser,
fileState: vals
}
}
}
default: default:
throw new Error() throw new Error()
} }

@ -238,6 +238,7 @@ export function Workspace () {
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}
fileState={global.fs.browser.fileState}
expandPath={global.fs.browser.expandPath} expandPath={global.fs.browser.expandPath}
focusEdit={global.fs.focusEdit} focusEdit={global.fs.focusEdit}
focusElement={global.fs.focusElement} focusElement={global.fs.focusElement}
@ -273,6 +274,7 @@ export function Workspace () {
contextMenuItems={global.fs.localhost.contextMenu.registeredMenuItems} contextMenuItems={global.fs.localhost.contextMenu.registeredMenuItems}
removedContextMenuItems={global.fs.localhost.contextMenu.removedMenuItems} removedContextMenuItems={global.fs.localhost.contextMenu.removedMenuItems}
files={global.fs.localhost.files} files={global.fs.localhost.files}
fileState={[]}
expandPath={global.fs.localhost.expandPath} expandPath={global.fs.localhost.expandPath}
focusEdit={global.fs.focusEdit} focusEdit={global.fs.focusEdit}
focusElement={global.fs.focusElement} focusElement={global.fs.focusElement}

@ -66,6 +66,35 @@ export interface FileType {
child?: File[] child?: File[]
} }
export enum fileStateType {
Error = 'ERROR',
Warning = 'WARNING',
Success = 'SUCCESS',
Loading = 'LOADING',
Unsaved = 'UNSAVED',
Untracked = 'UNTRACKED',
Modified = 'MODIFIED',
Staged = 'STAGED',
Committed = 'COMMITTED',
Deleted = 'DELETED',
Added = 'ADDED',
New = 'NEW',
Compiled = 'COMPILED',
Custom = 'CUSTOM',
}
export type fileState = {
path: string,
isDirectory: boolean,
fileStateType: fileStateType[],
fileStateLabelClass: string,
fileStateIconClass: string,
fileStateIcon: string | HTMLDivElement | JSX.Element,
bubble: boolean,
comment: string,
owner: string,
}
/* eslint-disable-next-line */ /* eslint-disable-next-line */
export interface FileExplorerProps { export interface FileExplorerProps {
name: string, name: string,
@ -73,6 +102,7 @@ export interface FileExplorerProps {
contextMenuItems: MenuItems, contextMenuItems: MenuItems,
removedContextMenuItems: MenuItems, removedContextMenuItems: MenuItems,
files: { [x: string]: Record<string, FileType> }, files: { [x: string]: Record<string, FileType> },
fileState: fileState[],
expandPath: string[], expandPath: string[],
focusEdit: string, focusEdit: string,
focusElement: { key: string, type: 'file' | 'folder' | 'gist' }[], focusElement: { key: string, type: 'file' | 'folder' | 'gist' }[],

Loading…
Cancel
Save