diff --git a/apps/remix-ide-e2e/src/tests/pinned_plugin.test.ts b/apps/remix-ide-e2e/src/tests/pinned_plugin.test.ts new file mode 100644 index 00000000000..802afbb1882 --- /dev/null +++ b/apps/remix-ide-e2e/src/tests/pinned_plugin.test.ts @@ -0,0 +1,51 @@ +'use strict' +import { NightwatchBrowser } from 'nightwatch' +import init from '../helpers/init' + +module.exports = { + '@disabled': true, + before: function (browser: NightwatchBrowser, done: VoidFunction) { + init(browser, done) + }, + + 'Check if RemixAI plugin is pinned #group1': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="movePluginToLeft"]') + .waitForElementVisible('*[data-id="remix-ai-assistant-starter-0"]') + .click('*[data-id="movePluginToLeft"]') + .waitForElementVisible('*[data-pinnedPlugin="movePluginToRight-remixaiassistant"]') + }, + 'Pin Solidity Compiler plugin #group1': function (browser: NightwatchBrowser) { + browser + .clickLaunchIcon('solidity') + .pause(2000) + .waitForElementVisible('*[data-id="movePluginToRight"]') + .click('*[data-id="movePluginToRight"]') + .waitForElementVisible('*[data-pinnedPlugin="movePluginToLeft-solidity"]') + .clickLaunchIcon('filePanel') + }, + 'Close Solidity Compiler Plugin and restore it #group1': function (browser: NightwatchBrowser) { + browser + .waitForElementVisible('*[data-id="closePinnedPlugin"]') + .click('*[data-id="closePinnedPlugin"]') + .waitForElementNotVisible('*[data-pinnedplugin="movePluginToLeft-solidity"]') + .waitForElementVisible('*[data-id="restoreClosedPlugin"') + .click('*[data-id="restoreClosedPlugin"]') + .waitForElementVisible('*[data-pinnedplugin="movePluginToLeft-solidity"]') + }, + 'Swap pinned Solidity Compiler Plugin with RemixAI Assistant when pinned plugin is closed #group1': function (browser: NightwatchBrowser) { + browser + .refreshPage() + .waitForElementVisible('*[data-pinnedplugin="movePluginToLeft-solidity"]') + .waitForElementVisible('*[data-id="closePinnedPlugin"]') + .click('*[data-id="closePinnedPlugin"]') + .waitForElementVisible('*[data-id="restoreClosedPlugin"]') + .clickLaunchIcon('udapp') + .waitForElementVisible('*[data-pinnedplugin="movePluginToRight-udapp"]') + .click('*[data-id="movePluginToRight"]') + .waitForElementVisible('*[data-pinnedplugin="movePluginToLeft-udapp"]') + .waitForElementVisible('*[data-id="movePluginToRight"]') + .click('*[data-pinnedplugin="movePluginToLeft-udapp"]') + .end() + } +} diff --git a/apps/remix-ide/src/app/components/pinned-panel.tsx b/apps/remix-ide/src/app/components/pinned-panel.tsx index ea29093f6af..47c9255dbda 100644 --- a/apps/remix-ide/src/app/components/pinned-panel.tsx +++ b/apps/remix-ide/src/app/components/pinned-panel.tsx @@ -11,13 +11,17 @@ const pinnedPanel = { displayName: 'Pinned Panel', description: 'Remix IDE pinned panel', version: packageJson.version, - methods: ['addView', 'removeView', 'currentFocus', 'pinView', 'unPinView', 'highlight'] + methods: ['addView', 'removeView', 'currentFocus', 'pinView', 'unPinView', 'highlight', 'closePlugin', 'maximizePlugin', + 'getClosedPlugin' + ], + events: ['pluginClosed', 'pluginMaximized'] } export class PinnedPanel extends AbstractPanel { dispatch: React.Dispatch = () => {} loggedState: Record highlightStamp: number + closedPlugin: any constructor() { super(pinnedPanel) @@ -35,6 +39,9 @@ export class PinnedPanel extends AbstractPanel { } async pinView (profile, view) { + if (this.closedPlugin) { + this.maximizePlugin(this.closedPlugin) + } const activePlugin = this.currentFocus() if (activePlugin === profile.name) throw new Error(`Plugin ${profile.name} already pinned`) @@ -62,6 +69,26 @@ export class PinnedPanel extends AbstractPanel { this.emit('unPinnedPlugin', profile) } + getClosedPlugin() { + return this.closedPlugin + } + + async closePlugin (profile) { + const pinnedPanel = document.querySelector('#pinned-panel') + pinnedPanel.classList.add('d-none') + this.closedPlugin = profile + this.events.emit('pluginClosed', profile) + this.emit('pluginClosed', profile) + } + + async maximizePlugin (profile) { + const pinnedPanel = document.querySelector('#pinned-panel') + pinnedPanel.classList.remove('d-none') + this.closedPlugin = null + this.events.emit('pluginMaximized', profile) + this.emit('pluginMaximized', profile) + } + highlight () { this.highlightStamp = Date.now() this.renderComponent() @@ -78,7 +105,7 @@ export class PinnedPanel extends AbstractPanel { } updateComponent(state: any) { - return } { ...state } /> + return } { ...state } /> } renderComponent() { diff --git a/apps/remix-ide/src/app/components/side-panel.tsx b/apps/remix-ide/src/app/components/side-panel.tsx index d89a97cd51e..a741217276f 100644 --- a/apps/remix-ide/src/app/components/side-panel.tsx +++ b/apps/remix-ide/src/app/components/side-panel.tsx @@ -100,10 +100,6 @@ export class SidePanel extends AbstractPanel { */ async showContent(name) { super.showContent(name) - if (name === 'remixaiassistant') { - await this.call('sidePanel', 'pinView', this.plugins[name].profile) - return - } this.emit('focusChanged', name) this.renderComponent() } diff --git a/apps/remix-ide/src/app/components/top-bar.tsx b/apps/remix-ide/src/app/components/top-bar.tsx index 7a70479f7cc..a778fa8a32c 100644 --- a/apps/remix-ide/src/app/components/top-bar.tsx +++ b/apps/remix-ide/src/app/components/top-bar.tsx @@ -30,7 +30,7 @@ export class Topbar extends Plugin { dispatch: React.Dispatch = () => { } appStateDispatch: React.Dispatch = () => { } htmlElement: HTMLDivElement - events: EventEmitter + event: EventEmitter topbarExpandPath: string filePanel: FilePanel git: GitPlugin @@ -44,6 +44,7 @@ export class Topbar extends Plugin { super(TopBarProfile) this.filePanel = filePanel this.registry = Registry.getInstance() + this.event = new EventEmitter() this.fileProviders = this.registry.get('fileproviders').api this.fileManager = this.registry.get('filemanager').api this.git = git @@ -52,6 +53,12 @@ export class Topbar extends Plugin { } onActivation(): void { + this.on('pinnedPanel', 'pluginClosed', (profile) => { + this.event.emit('pluginIsClosed', profile) + }) + this.on('pinnedPanel', 'pluginMaximized', (profile) => { + this.event.emit('pluginIsMaximized', profile) + }) this.renderComponent() } @@ -69,12 +76,6 @@ export class Topbar extends Plugin { } async createWorkspace(workspaceName, workspaceTemplateName, isEmpty) { - // return new Promise((resolve, reject) => { - // this.emit('createWorkspaceReducerEvent', workspaceName, workspaceTemplateName, isEmpty, (err, data) => { - // if (err) reject(err) - // else resolve(data || true) - // }) - // }) try { await createWorkspace(workspaceName, workspaceTemplateName, isEmpty) this.emit('workspaceCreated', workspaceName, workspaceTemplateName, isEmpty) @@ -84,12 +85,6 @@ export class Topbar extends Plugin { } async renameWorkspace(oldName, workspaceName) { - // return new Promise((resolve, reject) => { - // this.emit('renameWorkspaceReducerEvent', oldName, workspaceName, (err, data) => { - // if (err) reject(err) - // else resolve(data || true) - // }) - // }) try { await renameWorkspace(oldName, workspaceName) this.emit('workspaceRenamed', oldName, workspaceName) @@ -99,12 +94,6 @@ export class Topbar extends Plugin { } async deleteWorkspace(workspaceName) { - // return new Promise((resolve, reject) => { - // this.emit('deleteWorkspaceReducerEvent', workspaceName, (err, data) => { - // if (err) reject(err) - // else resolve(data || true) - // }) - // }) try { await deleteWorkspace(workspaceName) this.emit('workspaceDeleted', workspaceName) diff --git a/apps/remix-ide/src/app/panels/tab-proxy.js b/apps/remix-ide/src/app/panels/tab-proxy.js index fbc8295c26a..5a5356fdced 100644 --- a/apps/remix-ide/src/app/panels/tab-proxy.js +++ b/apps/remix-ide/src/app/panels/tab-proxy.js @@ -22,6 +22,7 @@ export default class TabProxy extends Plugin { this.loadedTabs = [] this.dispatch = null this.themeQuality = 'dark' + this.maximize = false } async onActivation () { @@ -31,6 +32,13 @@ export default class TabProxy extends Plugin { this.renderComponent() }) + this.on('pinnedPanel', 'pluginClosed', (profile) => { + this.event.emit('pluginIsClosed', profile) + }) + this.on('pinnedPanel', 'pluginMaximized', (profile) => { + this.event.emit('pluginIsMaximized', profile) + }) + this.on('fileManager', 'filesAllClosed', () => { this.call('manager', 'activatePlugin', 'home') this.focus('home') @@ -357,6 +365,7 @@ export default class TabProxy extends Plugin { onZoomOut={state.onZoomOut} onReady={state.onReady} themeQuality={state.themeQuality} + maximize={this.maximize} /> } diff --git a/apps/remix-ide/src/remixAppManager.ts b/apps/remix-ide/src/remixAppManager.ts index 1ce7ffeafb7..743209653f2 100644 --- a/apps/remix-ide/src/remixAppManager.ts +++ b/apps/remix-ide/src/remixAppManager.ts @@ -147,6 +147,7 @@ export function isNative(name) { 'foundry-provider', 'basic-http-provider', 'tabs', + 'pinnedPanel', 'doc-gen', 'doc-viewer', 'circuit-compiler', diff --git a/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx b/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx index 6030ace56a6..d605f100118 100644 --- a/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx +++ b/libs/remix-ui/panel/src/lib/plugins/panel-header.tsx @@ -8,7 +8,9 @@ const _paq = (window._paq = window._paq || []) export interface RemixPanelProps { plugins: Record, pinView?: (profile: PluginRecord['profile'], view: PluginRecord['view']) => void, - unPinView?: (profile: PluginRecord['profile']) => void + unPinView?: (profile: PluginRecord['profile']) => void, + closePlugin?: (profile: PluginRecord['profile']) => void, + maximizePlugin?: (profile: PluginRecord['profile']) => void } const RemixUIPanelHeader = (props: RemixPanelProps) => { const [plugin, setPlugin] = useState() @@ -38,6 +40,10 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => { _paq.push(['trackEvent', 'PluginPanel', 'pinToLeft', plugin.profile.name]) } + const closePlugin = async () => { + props.closePlugin && props.closePlugin(plugin.profile) + } + const tooltipChild = return ( @@ -71,14 +77,23 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => { <> -
- }> - + <> +
+ }> + + +
+ + -
+
-
+
}> @@ -121,13 +136,6 @@ const RemixUIPanelHeader = (props: RemixPanelProps) => { )} - - {plugin?.profile?.documentation && ( - - - - - )}
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 77a1f490b37..57d520c7348 100644 --- a/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx +++ b/libs/remix-ui/tabs/src/lib/remix-ui-tabs.tsx @@ -9,19 +9,22 @@ import { values } from 'lodash' import { AppContext } from '@remix-ui/app' import { desktopConnectionType } from '@remix-api' import { CompileDropdown, RunScriptDropdown } from '@remix-ui/tabs' +// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries +import TabProxy from 'apps/remix-ide/src/app/panels/tab-proxy' const _paq = (window._paq = window._paq || []) /* eslint-disable-next-line */ export interface TabsUIProps { tabs: Array - plugin: Plugin + plugin: TabProxy onSelect: (index: number) => void onClose: (index: number) => void onZoomOut: () => void onZoomIn: () => void onReady: (api: any) => void themeQuality: string + maximize: boolean } export interface Tab { @@ -86,6 +89,8 @@ export const TabsUI = (props: TabsUIProps) => { const compileSeq = useRef(0) const compileWatchdog = useRef(null) const settledSeqRef = useRef(0) + const [maximized, setMaximized] = useState(false) + const [closedPlugin, setClosedPlugin] = useState(null) const [compileState, setCompileState] = useState<'idle' | 'compiling' | 'compiled'>('idle') @@ -98,6 +103,19 @@ export const TabsUI = (props: TabsUIProps) => { } }, [tabsState.selectedIndex]) + useEffect(() => { + props.plugin.event.on('pluginIsClosed', (profile) => { + setClosedPlugin(profile) + if (maximized) { + setMaximized(false) + } + }) + props.plugin.event.on('pluginIsMaximized', () => { + setClosedPlugin(null) + setMaximized(true) + }) + }, []) + // Toggle the copilot in editor when clicked to update in status bar useEffect(() => { const run = async () => { @@ -490,7 +508,7 @@ export const TabsUI = (props: TabsUIProps) => { return (
{ ))} -
+ ) } diff --git a/libs/remix-ui/top-bar/src/lib/remix-ui-topbar.tsx b/libs/remix-ui/top-bar/src/lib/remix-ui-topbar.tsx index e0f717b6faf..1554d20374b 100644 --- a/libs/remix-ui/top-bar/src/lib/remix-ui-topbar.tsx +++ b/libs/remix-ui/top-bar/src/lib/remix-ui-topbar.tsx @@ -17,6 +17,8 @@ import { GitHubUser } from 'libs/remix-api/src/lib/types/git' import { GitHubCallback } from '../topbarUtils/gitOauthHandler' import { GitHubLogin } from '../components/gitLogin' import GithubLoginSuccess from '../components/githubLoginSuccess' +import { CustomTooltip } from 'libs/remix-ui/helper/src/lib/components/custom-tooltip' +import { Topbar } from 'apps/remix-ide/src/app/components/top-bar' const _paq = window._paq || [] @@ -25,7 +27,7 @@ export function RemixUiTopbar () { const [showDropdown, setShowDropdown] = useState(false) const platform = useContext(platformContext) const global = useContext(TopbarContext) - const plugin = global.plugin as any + const plugin = global.plugin const LOCALHOST = ' - connect to localhost - ' const NO_WORKSPACE = ' - none - ' const ROOT_PATH = '/' @@ -44,6 +46,8 @@ export function RemixUiTopbar () { useOnClickOutside([themeIconRef], () => setShowTheme(false)) const workspaceRenameInput = useRef() const cloneUrlRef = useRef() + const [closedPlugin, setClosedPlugin] = useState(null) + const [maximized, setMaximized] = useState(false) const [user, setUser] = useState(null); const [error, setError] = useState(null); @@ -88,6 +92,19 @@ export function RemixUiTopbar () { run() }, []) + useEffect(() => { + plugin.event.on('pluginIsClosed', (profile) => { + setClosedPlugin(profile) + if (maximized) { + setMaximized(false) + } + }) + plugin.event.on('pluginIsMaximized', () => { + setClosedPlugin(null) + setMaximized(true) + }) + }, []) + useEffect(() => { if (global.fs.mode === 'browser') { if (global.fs.browser.currentWorkspace) { @@ -142,8 +159,8 @@ export function RemixUiTopbar () { ] }, []) - const updateMenuItems = (workspaces?: WorkspaceMetadata[]) => { - const menuItems = (workspaces || plugin.getWorkspaces()).map((workspace) => ({ + const updateMenuItems = async (workspaces?: WorkspaceMetadata[]) => { + const menuItems = (workspaces || await plugin.getWorkspaces()).map((workspace) => ({ name: workspace.name, isGitRepo: workspace.isGitRepo, isGist: workspace.isGist, @@ -513,6 +530,15 @@ export function RemixUiTopbar () { className="d-flex flex-row align-items-center justify-content-end flex-nowrap" style={{ minWidth: '33%' }} > + {/* {closedPlugin &&
+ + plugin.call('pinnedPanel', 'maximizePlugin')} + > + +
} */} <> {user ? ( + + {closedPlugin &&
+ + plugin.call('pinnedPanel', 'maximizePlugin')} + > + +
}