diff --git a/apps/remix-ide/src/app/panels/file-panel.js b/apps/remix-ide/src/app/panels/file-panel.js index 91f33673dd..ca5e94417e 100644 --- a/apps/remix-ide/src/app/panels/file-panel.js +++ b/apps/remix-ide/src/app/panels/file-panel.js @@ -66,6 +66,7 @@ module.exports = class Filepanel extends ViewPlugin { } } this.reset = false + this.registeredMenuItems = [] this.el = yo`
@@ -102,6 +103,20 @@ module.exports = class Filepanel extends ViewPlugin { return this.el } + /** + * + * @param item { id: string, name: string, type?: string[], path?: string[], extension?: string[], pattern?: string[] } + * @param callback (...args) => void + */ + registerContextMenuItem (item) { + if (!item) throw new Error('Invalid register context menu argument') + if (!item.name || !item.id) throw new Error('Item name and id is mandatory') + if (!item.type && !item.path && !item.extension && !item.pattern) throw new Error('Invalid file matching criteria provided') + + this.registeredMenuItems = [...this.registeredMenuItems, item] + this.renderComponent() + } + renderComponent () { ReactDOM.render(
@@ -116,6 +131,7 @@ module.exports = class Filepanel extends ViewPlugin { menuItems={['createNewFile', 'createNewFolder', 'publishToGist', canUpload ? 'uploadFile' : '']} plugin={this} focusRoot={this.reset} + contextMenuItems={this.registeredMenuItems} />
@@ -127,6 +143,7 @@ module.exports = class Filepanel extends ViewPlugin { menuItems={['createNewFile', 'createNewFolder']} plugin={this} focusRoot={this.reset} + contextMenuItems={this.registeredMenuItems} /> }
diff --git a/libs/remix-ui/file-explorer/src/lib/file-explorer-context-menu.tsx b/libs/remix-ui/file-explorer/src/lib/file-explorer-context-menu.tsx index aebd8eb053..3a842d91b3 100644 --- a/libs/remix-ui/file-explorer/src/lib/file-explorer-context-menu.tsx +++ b/libs/remix-ui/file-explorer/src/lib/file-explorer-context-menu.tsx @@ -4,7 +4,7 @@ import { FileExplorerContextMenuProps } from './types' import './css/file-explorer-context-menu.css' export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => { - const { actions, createNewFile, createNewFolder, deletePath, renamePath, hideContextMenu, publishToGist, runScript, pageX, pageY, path, type, ...otherProps } = props + const { actions, createNewFile, createNewFolder, deletePath, renamePath, hideContextMenu, publishToGist, runScript, emit, pageX, pageY, path, type, ...otherProps } = props const contextMenuRef = useRef(null) useEffect(() => { @@ -24,10 +24,10 @@ export const FileExplorerContextMenu = (props: FileExplorerContextMenuProps) => const menu = () => { return actions.filter(item => { - if (item.type.findIndex(name => name === type) !== -1) return true - else if (item.path.findIndex(key => key === path) !== -1) return true - else if (item.extension.findIndex(ext => path.endsWith(ext)) !== -1) return true - else if (item.pattern.filter(value => path.match(new RegExp(value))).length > 0) return true + if (item.type && Array.isArray(item.type) && (item.type.findIndex(name => name === type) !== -1)) return true + else if (item.path && Array.isArray(item.path) && (item.path.findIndex(key => key === path) !== -1)) return true + else if (item.extension && Array.isArray(item.extension) && (item.extension.findIndex(ext => path.endsWith(ext)) !== -1)) return true + else if (item.pattern && Array.isArray(item.pattern) && (item.pattern.filter(value => path.match(new RegExp(value))).length > 0)) return true else return false }).map((item, index) => { return
  • runScript(path) break default: + emit && emit(item.id, path) break } hideContextMenu() diff --git a/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx b/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx index 16ab028599..e7e2f6e31f 100644 --- a/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx +++ b/libs/remix-ui/file-explorer/src/lib/file-explorer.tsx @@ -16,7 +16,7 @@ import './css/file-explorer.css' const queryParams = new QueryParams() export const FileExplorer = (props: FileExplorerProps) => { - const { filesProvider, name, registry, plugin, focusRoot } = props + const { filesProvider, name, registry, plugin, focusRoot, contextMenuItems } = props const [state, setState] = useState({ focusElement: [{ key: name, @@ -76,36 +76,42 @@ export const FileExplorer = (props: FileExplorerProps) => { const accessToken = config.get('settings/gist-access-token') const files = await fetchDirectoryContent(name) const actions = [{ + id: 'newFile', name: 'New File', type: ['folder'], path: [], extension: [], pattern: [] }, { + id: 'newFolder', name: 'New Folder', type: ['folder'], path: [], extension: [], pattern: [] }, { + id: 'rename', name: 'Rename', type: ['file', 'folder'], path: [], extension: [], pattern: [] }, { + id: 'delete', name: 'Delete', type: ['file', 'folder'], path: [], extension: [], pattern: [] }, { + id: 'pushChangesToGist', name: 'Push changes to gist', type: [], path: [], extension: [], pattern: ['^browser/gists/([0-9]|[a-z])*$'] }, { + id: 'run', name: 'Run', type: [], path: [], @@ -165,6 +171,17 @@ export const FileExplorer = (props: FileExplorerProps) => { } }, [focusRoot]) + useEffect(() => { + if (contextMenuItems) { + setState(prevState => { + // filter duplicate items + const items = contextMenuItems.filter(({ name }) => prevState.actions.findIndex(action => action.name === name) === -1) + + return { ...prevState, actions: [...prevState.actions, ...items] } + }) + } + }, [contextMenuItems]) + const resolveDirectory = async (folderPath, dir: File[], isChild = false): Promise => { if (!isChild && (state.focusEdit.element === 'browser/blank') && state.focusEdit.isNew && (dir.findIndex(({ path }) => path === 'browser/blank') === -1)) { dir = state.focusEdit.type === 'file' ? [...dir, { @@ -603,6 +620,10 @@ export const FileExplorer = (props: FileExplorerProps) => { }) } + const emitContextMenuEvent = (id: string, path: string) => { + plugin.emit(id, path) + } + const handleHideModal = () => { setState(prevState => { return { ...prevState, modalOptions: { ...state.modalOptions, hide: true } } @@ -839,6 +860,7 @@ export const FileExplorer = (props: FileExplorerProps) => { deletePath={deletePath} renamePath={editModeOn} publishToGist={publishToGist} + emit={emitContextMenuEvent} pageX={state.focusContext.x} pageY={state.focusContext.y} path={file.path} @@ -875,6 +897,7 @@ export const FileExplorer = (props: FileExplorerProps) => { deletePath={deletePath} renamePath={editModeOn} runScript={runScript} + emit={emitContextMenuEvent} pageX={state.focusContext.x} pageY={state.focusContext.y} path={file.path} diff --git a/libs/remix-ui/file-explorer/src/lib/types/index.ts b/libs/remix-ui/file-explorer/src/lib/types/index.ts index e2225dfc24..caa203ab7b 100644 --- a/libs/remix-ui/file-explorer/src/lib/types/index.ts +++ b/libs/remix-ui/file-explorer/src/lib/types/index.ts @@ -5,7 +5,8 @@ export interface FileExplorerProps { filesProvider: any, menuItems?: string[], plugin: any, - focusRoot: boolean + focusRoot: boolean, + contextMenuItems: { name: string, type: string[], path: string[], extension: string[], pattern: string[] }[] } export interface File { @@ -26,15 +27,15 @@ export interface FileExplorerMenuProps { } export interface FileExplorerContextMenuProps { - actions: { name: string, type: string[], path: string[], extension: string[], pattern: string[] }[], + actions: { name: string, type: string[], path: string[], extension: string[], pattern: string[], id: string }[], createNewFile: (folder?: string) => void, createNewFolder: (parentFolder?: string) => void, deletePath: (path: string) => void, renamePath: (path: string, type: string) => void, hideContextMenu: () => void, - extractParentFromKey?: (key: string) => string, publishToGist?: () => void, runScript?: (path: string) => void, + emit?: (id: string, path: string) => void, pageX: number, pageY: number, path: string,