From 8b0d4e0361c3af3179036ed414d2da1e3bc7d68c Mon Sep 17 00:00:00 2001 From: Filip Andrzej Kaminski Date: Tue, 26 Mar 2024 19:33:14 +0100 Subject: [PATCH 1/6] init --- .../vscode-extension/src/common/Project.ts | 2 +- .../src/common/WorkspaceConfig.ts | 94 +++++++++++++++++++ .../src/panels/SidepanelViewProvider.ts | 2 +- .../src/panels/WebviewController.ts | 8 +- .../vscode-extension/src/project/project.ts | 6 ++ .../webview/components/SettingsDropdown.tsx | 26 ++++- .../vscode-extension/src/webview/index.jsx | 21 +++-- .../providers/WorkspaceConfigProvider.tsx | 41 ++++++++ 8 files changed, 185 insertions(+), 15 deletions(-) create mode 100644 packages/vscode-extension/src/common/WorkspaceConfig.ts create mode 100644 packages/vscode-extension/src/webview/providers/WorkspaceConfigProvider.tsx diff --git a/packages/vscode-extension/src/common/Project.ts b/packages/vscode-extension/src/common/Project.ts index 856c300f8..37bc12aa9 100644 --- a/packages/vscode-extension/src/common/Project.ts +++ b/packages/vscode-extension/src/common/Project.ts @@ -78,7 +78,7 @@ export interface ProjectInterface { stepOverDebugger(): Promise; focusBuildOutput(): Promise; focusDebugConsole(): Promise; - + focusIntoSecondarySidebar(): Promise; openNavigation(navigationItemID: string): Promise; dispatchTouch(xRatio: number, yRatio: number, type: "Up" | "Move" | "Down"): Promise; diff --git a/packages/vscode-extension/src/common/WorkspaceConfig.ts b/packages/vscode-extension/src/common/WorkspaceConfig.ts new file mode 100644 index 000000000..6266182e6 --- /dev/null +++ b/packages/vscode-extension/src/common/WorkspaceConfig.ts @@ -0,0 +1,94 @@ +import { ConfigurationChangeEvent, workspace, Disposable } from "vscode"; +import { Tabpanel } from "../panels/Tabpanel"; +import { EventEmitter } from "stream"; +import { Logger } from "../Logger"; + +export type WorkspaceConfigProps = { + showPanelInActivityBar: boolean; + relativeAppLocation: string; +}; + +export interface WorkspaceConfigEventMap { + workspaceConfigChange: WorkspaceConfigProps; +} + +export interface WorkspaceConfigEventListener { + (event: T): void; +} + +export interface WorkspaceConfigInterface { + getWorkspaceConfigProps(): Promise; + addListener( + eventType: K, + listener: WorkspaceConfigEventListener + ): Promise; + removeListener( + eventType: K, + listener: WorkspaceConfigEventListener + ): Promise; +} + +export class WorkspaceConfig implements Disposable, WorkspaceConfigInterface { + public static currentWorkspaceConfig: WorkspaceConfig | undefined; + private workspaceConfigProps: WorkspaceConfigProps; + private eventEmitter = new EventEmitter(); + private workspaceConfigListener: Disposable | undefined; + + constructor() { + WorkspaceConfig.currentWorkspaceConfig = this; + this.workspaceConfigProps = { + showPanelInActivityBar: workspace + .getConfiguration("ReactNativeIDE") + .get("showPanelInActivityBar")!, + relativeAppLocation: workspace + .getConfiguration("ReactNativeIDE") + .get("relativeAppLocation")!, + }; + this.IDEPanelLocationListener(); + } + + private IDEPanelLocationListener() { + this.workspaceConfigListener = workspace.onDidChangeConfiguration( + (event: ConfigurationChangeEvent) => { + if (!event.affectsConfiguration("ReactNativeIDE")) { + return; + } + if (event.affectsConfiguration("ReactNativeIDE.showPanelInActivityBar")) { + this.workspaceConfigProps.showPanelInActivityBar = workspace + .getConfiguration("ReactNativeIDE") + .get("showPanelInActivityBar")!; + if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInActivityBar")) { + Tabpanel.currentPanel?.dispose(); + } + } else if (event.affectsConfiguration("ReactNativeIDE.relativeAppLocation")) { + this.workspaceConfigProps.relativeAppLocation = workspace + .getConfiguration("ReactNativeIDE") + .get("relativeAppLocation")!; + } + this.eventEmitter.emit("workspaceConfigChange", this.workspaceConfigProps); + } + ); + } + + async getWorkspaceConfigProps(): Promise { + return this.workspaceConfigProps; + } + + async addListener( + eventType: K, + listener: WorkspaceConfigEventListener + ) { + this.eventEmitter.addListener(eventType, listener); + } + + async removeListener( + eventType: K, + listener: WorkspaceConfigEventListener + ) { + this.eventEmitter.removeListener(eventType, listener); + } + + dispose() { + this.workspaceConfigListener?.dispose(); + } +} diff --git a/packages/vscode-extension/src/panels/SidepanelViewProvider.ts b/packages/vscode-extension/src/panels/SidepanelViewProvider.ts index 1a11f301b..871e76e7f 100644 --- a/packages/vscode-extension/src/panels/SidepanelViewProvider.ts +++ b/packages/vscode-extension/src/panels/SidepanelViewProvider.ts @@ -1,6 +1,6 @@ import { ExtensionContext, Uri, WebviewView, WebviewViewProvider, commands } from "vscode"; import { generateWebviewContent } from "./webviewContentGenerator"; -import { extensionContext } from "../utilities/extensionContext"; + import { WebviewController } from "./WebviewController"; import { Logger } from "../Logger"; diff --git a/packages/vscode-extension/src/panels/WebviewController.ts b/packages/vscode-extension/src/panels/WebviewController.ts index 3892382f2..94c68e833 100644 --- a/packages/vscode-extension/src/panels/WebviewController.ts +++ b/packages/vscode-extension/src/panels/WebviewController.ts @@ -6,12 +6,14 @@ import { Project } from "../project/project"; import { openExternalUrl } from "../utilities/vsc"; import { Logger } from "../Logger"; import { extensionContext } from "../utilities/extensionContext"; +import { WorkspaceConfig } from "../common/WorkspaceConfig"; export class WebviewController implements Disposable { private readonly dependencyChecker: DependencyChecker; private readonly dependencyInstaller: DependencyInstaller; private readonly deviceManager: DeviceManager; public readonly project: Project; + public readonly workspaceConfig: WorkspaceConfig; private disposables: Disposable[] = []; private followEnabled = false; @@ -34,16 +36,20 @@ export class WebviewController implements Disposable { this.deviceManager = new DeviceManager(); this.project = new Project(this.deviceManager); + this.workspaceConfig = new WorkspaceConfig(); + this.disposables.push( this.dependencyChecker, this.dependencyInstaller, this.deviceManager, - this.project + this.project, + this.workspaceConfig ); this.callableObjects = new Map([ ["DeviceManager", this.deviceManager as object], ["Project", this.project as object], + ["WorkspaceConfig", this.workspaceConfig as object], ]); } diff --git a/packages/vscode-extension/src/project/project.ts b/packages/vscode-extension/src/project/project.ts index 9ca11f135..e5ab2a79e 100644 --- a/packages/vscode-extension/src/project/project.ts +++ b/packages/vscode-extension/src/project/project.ts @@ -261,16 +261,22 @@ export class Project implements Disposable, MetroDelegate, ProjectInterface { public async stepOverDebugger() { this.deviceSession?.stepOverDebugger(); } + public async focusBuildOutput() { if (!this.projectState.selectedDevice) { return; } this.buildManager.focusBuildOutput(); } + public async focusDebugConsole() { commands.executeCommand("workbench.panel.repl.view.focus"); } + public async focusIntoSecondarySidebar(): Promise { + commands.executeCommand("workbench.action.focusAuxiliaryBar"); + } + public async openNavigation(navigationItemID: string) { this.deviceSession?.openNavigation(navigationItemID); } diff --git a/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx b/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx index e8199f8dd..ee4ea817f 100644 --- a/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx +++ b/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx @@ -1,14 +1,12 @@ import React from "react"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; - import "./shared/Dropdown.css"; import { useModal } from "../providers/ModalProvider"; - import DiagnosticView from "../views/DiagnosticView"; -import AndroidImagesView from "../views/AndroidImagesView"; import ManageDevicesView from "../views/ManageDevicesView"; import { ProjectInterface } from "../../common/Project"; import DoctorIcon from "./icons/DoctorIcon"; +import { useWorkspaceConfig } from "../providers/WorkspaceConfigProvider"; interface SettingsDropdownProps { children: React.ReactNode; @@ -17,6 +15,7 @@ interface SettingsDropdownProps { } function SettingsDropdown({ project, children, disabled }: SettingsDropdownProps) { + const { showPanelInActivityBar } = useWorkspaceConfig(); const { openModal } = useModal(); return ( @@ -62,6 +61,27 @@ function SettingsDropdown({ project, children, disabled }: SettingsDropdownProps Clean rebuild + + {showPanelInActivityBar && ( + <> + + { + project.focusIntoSecondarySidebar(); + openModal( + "Move to secondary sidebar", +
+ You can move extensions from Primary to secondary sidebar, by grab and droping + them.{" "} +
+ ); + }}> + + Move to secondary sidebar +
+ + )} diff --git a/packages/vscode-extension/src/webview/index.jsx b/packages/vscode-extension/src/webview/index.jsx index fa79bfb29..123b3b9a4 100644 --- a/packages/vscode-extension/src/webview/index.jsx +++ b/packages/vscode-extension/src/webview/index.jsx @@ -7,6 +7,7 @@ import DependenciesProvider from "./providers/DependenciesProvider"; import ModalProvider from "./providers/ModalProvider"; import ProjectProvider from "./providers/ProjectProvider"; import AlertProvider from "./providers/AlertProvider"; +import WorkspaceConfigProvider from "./providers/WorkspaceConfigProvider"; import "./styles/colors.css"; @@ -16,15 +17,17 @@ const root = createRoot(container); root.render( - - - - - - - - - + + + + + + + + + + + ); diff --git a/packages/vscode-extension/src/webview/providers/WorkspaceConfigProvider.tsx b/packages/vscode-extension/src/webview/providers/WorkspaceConfigProvider.tsx new file mode 100644 index 000000000..dc4346104 --- /dev/null +++ b/packages/vscode-extension/src/webview/providers/WorkspaceConfigProvider.tsx @@ -0,0 +1,41 @@ +import { PropsWithChildren, useContext, createContext, useState, useEffect } from "react"; +import { makeProxy } from "../utilities/rpc"; +import { WorkspaceConfigInterface, WorkspaceConfigProps } from "../../common/WorkspaceConfig"; + +const config = makeProxy("WorkspaceConfig"); + +const WorkspaceConfigContext = createContext({ + showPanelInActivityBar: false, + relativeAppLocation: "", +}); + +export default function WorkspaceConfigProvider({ children }: PropsWithChildren) { + const [workspaceConfig, setWorkspaceConfig] = useState({ + showPanelInActivityBar: false, + relativeAppLocation: "", + }); + + useEffect(() => { + config.getWorkspaceConfigProps().then(setWorkspaceConfig); + config.addListener("workspaceConfigChange", setWorkspaceConfig); + + return () => { + config.removeListener("workspaceConfigChange", setWorkspaceConfig); + }; + }, []); + + return ( + + {children} + + ); +} + +export function useWorkspaceConfig() { + const context = useContext(WorkspaceConfigContext); + + if (context === undefined) { + throw new Error("useWorkspaceConfig must be used within a WorkspaceConfigProvider"); + } + return context; +} From 8f50d6787029afc0ad78ef35c71db9ac73f70c33 Mon Sep 17 00:00:00 2001 From: Filip Andrzej Kaminski Date: Tue, 26 Mar 2024 20:16:22 +0100 Subject: [PATCH 2/6] change description --- packages/vscode-extension/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vscode-extension/package.json b/packages/vscode-extension/package.json index c0bffa094..4b18fa11e 100644 --- a/packages/vscode-extension/package.json +++ b/packages/vscode-extension/package.json @@ -56,7 +56,7 @@ "type": "boolean", "scope": "window", "default": false, - "description": "This option alows you to move IDE Panel to Activity Bar. (warning: if you currently have it open it is going to be close after ajusting that setting.)" + "description": "Show IDE In Side Panel\n\n Moves IDE from tabs to panel next to the editor. Toggling this option closes and reopens IDE." } } }, From 04e23f2e8b4ee39f357069c90a8dd7a70365c5fa Mon Sep 17 00:00:00 2001 From: Filip Andrzej Kaminski Date: Tue, 26 Mar 2024 20:22:58 +0100 Subject: [PATCH 3/6] change description --- packages/vscode-extension/package.json | 8 ++++---- .../vscode-extension/src/common/WorkspaceConfig.ts | 14 +++++++------- packages/vscode-extension/src/extension.ts | 6 +++--- .../src/webview/components/SettingsDropdown.tsx | 4 ++-- .../webview/providers/WorkspaceConfigProvider.tsx | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/vscode-extension/package.json b/packages/vscode-extension/package.json index 4b18fa11e..19c35c326 100644 --- a/packages/vscode-extension/package.json +++ b/packages/vscode-extension/package.json @@ -52,11 +52,11 @@ "default": null, "description": "Location of the React Native application root folder relative to the workspace workspace. This is used for monorepo type setups when the workspace root is not the root of the React Native project. The IDE extension tries to locate the React Native application root automatically, but in case it failes to do so (i.e. there are multiple applications defined in the workspace), you can use this setting to override the location." }, - "ReactNativeIDE.showPanelInActivityBar":{ + "ReactNativeIDE.showPanelInSideBar":{ "type": "boolean", "scope": "window", "default": false, - "description": "Show IDE In Side Panel\n\n Moves IDE from tabs to panel next to the editor. Toggling this option closes and reopens IDE." + "description": "Moves IDE from tabs to window next to the editor. Toggling this option closes and reopens IDE." } } }, @@ -75,7 +75,7 @@ "type": "webview", "id": "ReactNativeIDE.view", "name": "IDE Panel", - "when": "config.ReactNativeIDE.showPanelInActivityBar" + "when": "config.ReactNativeIDE.showPanelInSideBar" } ] }, @@ -84,7 +84,7 @@ { "command": "RNIDE.openPanel", "group": "navigation", - "when": "RNIDE.extensionIsActive && !RNIDE.panelIsOpen && !config.ReactNativeIDE.showPanelInActivityBar" + "when": "RNIDE.extensionIsActive && !RNIDE.panelIsOpen && !config.ReactNativeIDE.showPanelInSideBar" } ] }, diff --git a/packages/vscode-extension/src/common/WorkspaceConfig.ts b/packages/vscode-extension/src/common/WorkspaceConfig.ts index 6266182e6..c2ec2a401 100644 --- a/packages/vscode-extension/src/common/WorkspaceConfig.ts +++ b/packages/vscode-extension/src/common/WorkspaceConfig.ts @@ -4,7 +4,7 @@ import { EventEmitter } from "stream"; import { Logger } from "../Logger"; export type WorkspaceConfigProps = { - showPanelInActivityBar: boolean; + showPanelInSideBar: boolean; relativeAppLocation: string; }; @@ -37,9 +37,9 @@ export class WorkspaceConfig implements Disposable, WorkspaceConfigInterface { constructor() { WorkspaceConfig.currentWorkspaceConfig = this; this.workspaceConfigProps = { - showPanelInActivityBar: workspace + showPanelInSideBar: workspace .getConfiguration("ReactNativeIDE") - .get("showPanelInActivityBar")!, + .get("showPanelInSideBar")!, relativeAppLocation: workspace .getConfiguration("ReactNativeIDE") .get("relativeAppLocation")!, @@ -53,11 +53,11 @@ export class WorkspaceConfig implements Disposable, WorkspaceConfigInterface { if (!event.affectsConfiguration("ReactNativeIDE")) { return; } - if (event.affectsConfiguration("ReactNativeIDE.showPanelInActivityBar")) { - this.workspaceConfigProps.showPanelInActivityBar = workspace + if (event.affectsConfiguration("ReactNativeIDE.showPanelInSideBar")) { + this.workspaceConfigProps.showPanelInSideBar = workspace .getConfiguration("ReactNativeIDE") - .get("showPanelInActivityBar")!; - if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInActivityBar")) { + .get("showPanelInSideBar")!; + if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInSideBar")) { Tabpanel.currentPanel?.dispose(); } } else if (event.affectsConfiguration("ReactNativeIDE.relativeAppLocation")) { diff --git a/packages/vscode-extension/src/extension.ts b/packages/vscode-extension/src/extension.ts index 35d0171c0..1edf250ee 100644 --- a/packages/vscode-extension/src/extension.ts +++ b/packages/vscode-extension/src/extension.ts @@ -70,7 +70,7 @@ export async function activate(context: ExtensionContext) { ); context.subscriptions.push( commands.registerCommand("RNIDE.openPanel", (fileName?: string, lineNumber?: number) => { - if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInActivityBar")) { + if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInSideBar")) { SidepanelViewProvider.showView(context, fileName, lineNumber); } else { Tabpanel.render(context, fileName, lineNumber); @@ -79,7 +79,7 @@ export async function activate(context: ExtensionContext) { ); context.subscriptions.push( commands.registerCommand("RNIDE.showPanel", (fileName?: string, lineNumber?: number) => { - if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInActivityBar")) { + if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInSideBar")) { SidepanelViewProvider.showView(context, fileName, lineNumber); } else { Tabpanel.render(context, fileName, lineNumber); @@ -123,7 +123,7 @@ function IDEPanelLocationListener() { if (!event.affectsConfiguration("ReactNativeIDE")) { return; } - if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInActivityBar")) { + if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInSideBar")) { Tabpanel.currentPanel?.dispose(); } }); diff --git a/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx b/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx index ee4ea817f..33179f722 100644 --- a/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx +++ b/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx @@ -15,7 +15,7 @@ interface SettingsDropdownProps { } function SettingsDropdown({ project, children, disabled }: SettingsDropdownProps) { - const { showPanelInActivityBar } = useWorkspaceConfig(); + const { showPanelInSideBar } = useWorkspaceConfig(); const { openModal } = useModal(); return ( @@ -62,7 +62,7 @@ function SettingsDropdown({ project, children, disabled }: SettingsDropdownProps Clean rebuild - {showPanelInActivityBar && ( + {showPanelInSideBar && ( <> ("WorkspaceConfig"); const WorkspaceConfigContext = createContext({ - showPanelInActivityBar: false, + showPanelInSideBar: false, relativeAppLocation: "", }); export default function WorkspaceConfigProvider({ children }: PropsWithChildren) { const [workspaceConfig, setWorkspaceConfig] = useState({ - showPanelInActivityBar: false, + showPanelInSideBar: false, relativeAppLocation: "", }); From fc72069ad2d28168ce7498a7303423a82661c638 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Wed, 27 Mar 2024 15:15:20 +0100 Subject: [PATCH 4/6] Some cleanup --- packages/vscode-extension/package.json | 22 +++-- .../vscode-extension/src/common/Project.ts | 1 - .../src/common/WorkspaceConfig.ts | 83 +++---------------- packages/vscode-extension/src/extension.ts | 62 ++++++-------- .../src/panels/SidepanelViewProvider.ts | 26 ++++-- .../vscode-extension/src/panels/Tabpanel.ts | 35 ++++++-- .../src/panels/WebviewController.ts | 20 +++-- .../src/panels/WorkspaceConfigController.ts | 62 ++++++++++++++ .../vscode-extension/src/project/project.ts | 4 - .../webview/components/SettingsDropdown.tsx | 80 +++++++++++------- .../providers/WorkspaceConfigProvider.tsx | 43 +++++++--- 11 files changed, 254 insertions(+), 184 deletions(-) create mode 100644 packages/vscode-extension/src/panels/WorkspaceConfigController.ts diff --git a/packages/vscode-extension/package.json b/packages/vscode-extension/package.json index 19c35c326..17742910a 100644 --- a/packages/vscode-extension/package.json +++ b/packages/vscode-extension/package.json @@ -52,11 +52,21 @@ "default": null, "description": "Location of the React Native application root folder relative to the workspace workspace. This is used for monorepo type setups when the workspace root is not the root of the React Native project. The IDE extension tries to locate the React Native application root automatically, but in case it failes to do so (i.e. there are multiple applications defined in the workspace), you can use this setting to override the location." }, - "ReactNativeIDE.showPanelInSideBar":{ - "type": "boolean", + "ReactNativeIDE.panelLocation": { + "type": "string", "scope": "window", - "default": false, - "description": "Moves IDE from tabs to window next to the editor. Toggling this option closes and reopens IDE." + "default": "tab", + "enum": [ + "tab", + "side-panel", + "secondary-side-panel" + ], + "enumDescriptions": [ + "Editor tab", + "Side panel", + "Secondary side panel" + ], + "description": "Controlls location of the IDE panel. Changing this option closes and reopens the IDE." } } }, @@ -75,7 +85,7 @@ "type": "webview", "id": "ReactNativeIDE.view", "name": "IDE Panel", - "when": "config.ReactNativeIDE.showPanelInSideBar" + "when": "config.ReactNativeIDE.panelLocation != 'tab'" } ] }, @@ -84,7 +94,7 @@ { "command": "RNIDE.openPanel", "group": "navigation", - "when": "RNIDE.extensionIsActive && !RNIDE.panelIsOpen && !config.ReactNativeIDE.showPanelInSideBar" + "when": "RNIDE.extensionIsActive && !RNIDE.panelIsOpen" } ] }, diff --git a/packages/vscode-extension/src/common/Project.ts b/packages/vscode-extension/src/common/Project.ts index 08382e8dc..971c71929 100644 --- a/packages/vscode-extension/src/common/Project.ts +++ b/packages/vscode-extension/src/common/Project.ts @@ -79,7 +79,6 @@ export interface ProjectInterface { focusBuildOutput(): Promise; focusExtensionLogsOutput(): Promise; focusDebugConsole(): Promise; - focusIntoSecondarySidebar(): Promise; openNavigation(navigationItemID: string): Promise; dispatchTouch(xRatio: number, yRatio: number, type: "Up" | "Move" | "Down"): Promise; diff --git a/packages/vscode-extension/src/common/WorkspaceConfig.ts b/packages/vscode-extension/src/common/WorkspaceConfig.ts index c2ec2a401..a6075608b 100644 --- a/packages/vscode-extension/src/common/WorkspaceConfig.ts +++ b/packages/vscode-extension/src/common/WorkspaceConfig.ts @@ -1,23 +1,25 @@ -import { ConfigurationChangeEvent, workspace, Disposable } from "vscode"; -import { Tabpanel } from "../panels/Tabpanel"; -import { EventEmitter } from "stream"; -import { Logger } from "../Logger"; +export type PanelLocation = "tab" | "side-panel" | "secondary-side-panel"; export type WorkspaceConfigProps = { - showPanelInSideBar: boolean; + panelLocation: PanelLocation; relativeAppLocation: string; }; export interface WorkspaceConfigEventMap { - workspaceConfigChange: WorkspaceConfigProps; + configChange: WorkspaceConfigProps; } export interface WorkspaceConfigEventListener { (event: T): void; } -export interface WorkspaceConfigInterface { - getWorkspaceConfigProps(): Promise; +export interface WorkspaceConfig { + getConfig(): Promise; + // update method can take any of the keys from WorkspaceConfigProps and appropriate value: + update( + key: K, + value: WorkspaceConfigProps[K] + ): Promise; addListener( eventType: K, listener: WorkspaceConfigEventListener @@ -27,68 +29,3 @@ export interface WorkspaceConfigInterface { listener: WorkspaceConfigEventListener ): Promise; } - -export class WorkspaceConfig implements Disposable, WorkspaceConfigInterface { - public static currentWorkspaceConfig: WorkspaceConfig | undefined; - private workspaceConfigProps: WorkspaceConfigProps; - private eventEmitter = new EventEmitter(); - private workspaceConfigListener: Disposable | undefined; - - constructor() { - WorkspaceConfig.currentWorkspaceConfig = this; - this.workspaceConfigProps = { - showPanelInSideBar: workspace - .getConfiguration("ReactNativeIDE") - .get("showPanelInSideBar")!, - relativeAppLocation: workspace - .getConfiguration("ReactNativeIDE") - .get("relativeAppLocation")!, - }; - this.IDEPanelLocationListener(); - } - - private IDEPanelLocationListener() { - this.workspaceConfigListener = workspace.onDidChangeConfiguration( - (event: ConfigurationChangeEvent) => { - if (!event.affectsConfiguration("ReactNativeIDE")) { - return; - } - if (event.affectsConfiguration("ReactNativeIDE.showPanelInSideBar")) { - this.workspaceConfigProps.showPanelInSideBar = workspace - .getConfiguration("ReactNativeIDE") - .get("showPanelInSideBar")!; - if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInSideBar")) { - Tabpanel.currentPanel?.dispose(); - } - } else if (event.affectsConfiguration("ReactNativeIDE.relativeAppLocation")) { - this.workspaceConfigProps.relativeAppLocation = workspace - .getConfiguration("ReactNativeIDE") - .get("relativeAppLocation")!; - } - this.eventEmitter.emit("workspaceConfigChange", this.workspaceConfigProps); - } - ); - } - - async getWorkspaceConfigProps(): Promise { - return this.workspaceConfigProps; - } - - async addListener( - eventType: K, - listener: WorkspaceConfigEventListener - ) { - this.eventEmitter.addListener(eventType, listener); - } - - async removeListener( - eventType: K, - listener: WorkspaceConfigEventListener - ) { - this.eventEmitter.removeListener(eventType, listener); - } - - dispose() { - this.workspaceConfigListener?.dispose(); - } -} diff --git a/packages/vscode-extension/src/extension.ts b/packages/vscode-extension/src/extension.ts index 1edf250ee..d47f767fd 100644 --- a/packages/vscode-extension/src/extension.ts +++ b/packages/vscode-extension/src/extension.ts @@ -7,10 +7,10 @@ import { Uri, ExtensionContext, ExtensionMode, - DebugConfigurationProviderTriggerKind, ConfigurationChangeEvent, + DebugConfigurationProviderTriggerKind, } from "vscode"; -import { Tabpanel } from "./panels/Tabpanel"; +import { TabPanel } from "./panels/Tabpanel"; import { PreviewCodeLensProvider } from "./providers/PreviewCodeLensProvider"; import { DebugConfigProvider } from "./providers/DebugConfigProvider"; import { DebugAdapterDescriptorFactory } from "./debugging/DebugAdapterDescriptorFactory"; @@ -24,7 +24,8 @@ import { command } from "./utilities/subprocess"; import path from "path"; import os from "os"; import fs from "fs"; -import { SidepanelViewProvider } from "./panels/SidepanelViewProvider"; +import { SidePanelViewProvider } from "./panels/SidepanelViewProvider"; +import { PanelLocation } from "./common/WorkspaceConfig"; const BIN_MODIFICATION_DATE_KEY = "bin_modification_date"; const OPEN_PANEL_ON_ACTIVATION = "open_panel_on_activation"; @@ -54,7 +55,7 @@ export function deactivate(context: ExtensionContext): undefined { export async function activate(context: ExtensionContext) { handleUncaughtErrors(); - IDEPanelLocationListener(); + setExtensionContext(context); if (context.extensionMode === ExtensionMode.Development) { enableDevModeLogging(); @@ -62,30 +63,24 @@ export async function activate(context: ExtensionContext) { await fixBinaries(context); + function showIDEPanel(fileName?: string, lineNumber?: number) { + if ( + workspace.getConfiguration("ReactNativeIDE").get("panelLocation") !== "tab" + ) { + SidePanelViewProvider.showView(context, fileName, lineNumber); + } else { + TabPanel.render(context, fileName, lineNumber); + } + } + context.subscriptions.push( window.registerWebviewViewProvider( - SidepanelViewProvider.viewType, - new SidepanelViewProvider(context) + SidePanelViewProvider.viewType, + new SidePanelViewProvider(context) ) ); - context.subscriptions.push( - commands.registerCommand("RNIDE.openPanel", (fileName?: string, lineNumber?: number) => { - if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInSideBar")) { - SidepanelViewProvider.showView(context, fileName, lineNumber); - } else { - Tabpanel.render(context, fileName, lineNumber); - } - }) - ); - context.subscriptions.push( - commands.registerCommand("RNIDE.showPanel", (fileName?: string, lineNumber?: number) => { - if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInSideBar")) { - SidepanelViewProvider.showView(context, fileName, lineNumber); - } else { - Tabpanel.render(context, fileName, lineNumber); - } - }) - ); + context.subscriptions.push(commands.registerCommand("RNIDE.openPanel", showIDEPanel)); + context.subscriptions.push(commands.registerCommand("RNIDE.showPanel", showIDEPanel)); context.subscriptions.push( commands.registerCommand("RNIDE.diagnose", diagnoseWorkspaceStructure) ); @@ -115,18 +110,15 @@ export async function activate(context: ExtensionContext) { ) ); - await configureAppRootFolder(); -} + context.subscriptions.push( + workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => { + if (event.affectsConfiguration("ReactNativeIDE.panelLocation")) { + showIDEPanel(); + } + }) + ); -function IDEPanelLocationListener() { - workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => { - if (!event.affectsConfiguration("ReactNativeIDE")) { - return; - } - if (workspace.getConfiguration("ReactNativeIDE").get("showPanelInSideBar")) { - Tabpanel.currentPanel?.dispose(); - } - }); + await configureAppRootFolder(); } async function findSingleFileInWorkspace(fileGlobPattern: string, excludePattern: string | null) { diff --git a/packages/vscode-extension/src/panels/SidepanelViewProvider.ts b/packages/vscode-extension/src/panels/SidepanelViewProvider.ts index 871e76e7f..e75335b79 100644 --- a/packages/vscode-extension/src/panels/SidepanelViewProvider.ts +++ b/packages/vscode-extension/src/panels/SidepanelViewProvider.ts @@ -1,17 +1,24 @@ -import { ExtensionContext, Uri, WebviewView, WebviewViewProvider, commands } from "vscode"; +import { + ExtensionContext, + Uri, + WebviewView, + WebviewViewProvider, + commands, + workspace, +} from "vscode"; import { generateWebviewContent } from "./webviewContentGenerator"; import { WebviewController } from "./WebviewController"; import { Logger } from "../Logger"; -export class SidepanelViewProvider implements WebviewViewProvider { +export class SidePanelViewProvider implements WebviewViewProvider { public static readonly viewType = "ReactNativeIDE.view"; - public static currentProvider: SidepanelViewProvider | undefined; + public static currentProvider: SidePanelViewProvider | undefined; private _view: any = null; private webviewController: any = null; constructor(private readonly context: ExtensionContext) { - SidepanelViewProvider.currentProvider = this; + SidePanelViewProvider.currentProvider = this; } refresh(): void { @@ -23,15 +30,20 @@ export class SidepanelViewProvider implements WebviewViewProvider { } public static showView(context: ExtensionContext, fileName?: string, lineNumber?: number) { - if (SidepanelViewProvider.currentProvider) { - commands.executeCommand(`${SidepanelViewProvider.viewType}.focus`); + if (SidePanelViewProvider.currentProvider) { + commands.executeCommand(`${SidePanelViewProvider.viewType}.focus`); + if ( + workspace.getConfiguration("ReactNativeIDE").get("panelLocation") === "secondary-side-panel" + ) { + commands.executeCommand("workbench.action.focusAuxiliaryBar"); + } } else { Logger.error("SidepanelViewProvider does not exist."); return; } if (fileName !== undefined && lineNumber !== undefined) { - SidepanelViewProvider.currentProvider.webviewController.project.startPreview( + SidePanelViewProvider.currentProvider.webviewController.project.startPreview( `preview:/${fileName}:${lineNumber}` ); } diff --git a/packages/vscode-extension/src/panels/Tabpanel.ts b/packages/vscode-extension/src/panels/Tabpanel.ts index 45cb43456..1b765f21a 100644 --- a/packages/vscode-extension/src/panels/Tabpanel.ts +++ b/packages/vscode-extension/src/panels/Tabpanel.ts @@ -1,4 +1,13 @@ -import { WebviewPanel, window, Uri, ViewColumn, ExtensionContext, commands } from "vscode"; +import { + WebviewPanel, + window, + Uri, + ViewColumn, + ExtensionContext, + commands, + workspace, + ConfigurationChangeEvent, +} from "vscode"; import { extensionContext } from "../utilities/extensionContext"; import { generateWebviewContent } from "./webviewContentGenerator"; @@ -6,8 +15,8 @@ import { WebviewController } from "./WebviewController"; const OPEN_PANEL_ON_ACTIVATION = "open_panel_on_activation"; -export class Tabpanel { - public static currentPanel: Tabpanel | undefined; +export class TabPanel { + public static currentPanel: TabPanel | undefined; private readonly _panel: WebviewPanel; private webviewController: WebviewController; @@ -26,12 +35,21 @@ export class Tabpanel { ); this.webviewController = new WebviewController(this._panel.webview); + + workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => { + if (!event.affectsConfiguration("ReactNativeIDE")) { + return; + } + if (workspace.getConfiguration("ReactNativeIDE").get("panelLocation") !== "tab") { + this.dispose(); + } + }); } public static render(context: ExtensionContext, fileName?: string, lineNumber?: number) { - if (Tabpanel.currentPanel) { + if (TabPanel.currentPanel) { // If the webview panel already exists reveal it - Tabpanel.currentPanel._panel.reveal(ViewColumn.Beside); + TabPanel.currentPanel._panel.reveal(ViewColumn.Beside); } else { // If a webview panel does not already exist create and show a new one @@ -51,15 +69,14 @@ export class Tabpanel { retainContextWhenHidden: true, } ); - Tabpanel.currentPanel = new Tabpanel(panel); + TabPanel.currentPanel = new TabPanel(panel); context.workspaceState.update(OPEN_PANEL_ON_ACTIVATION, true); commands.executeCommand("workbench.action.lockEditorGroup"); - commands.executeCommand("setContext", "RNIDE.panelIsOpen", true); } if (fileName !== undefined && lineNumber !== undefined) { - Tabpanel.currentPanel.webviewController.project.startPreview( + TabPanel.currentPanel.webviewController.project.startPreview( `preview:/${fileName}:${lineNumber}` ); } @@ -71,7 +88,7 @@ export class Tabpanel { // key in this case to prevent extension from automatically opening the panel next time they open the editor extensionContext.workspaceState.update(OPEN_PANEL_ON_ACTIVATION, undefined); - Tabpanel.currentPanel = undefined; + TabPanel.currentPanel = undefined; // Dispose of the current webview panel this._panel.dispose(); diff --git a/packages/vscode-extension/src/panels/WebviewController.ts b/packages/vscode-extension/src/panels/WebviewController.ts index 94c68e833..7efd4b170 100644 --- a/packages/vscode-extension/src/panels/WebviewController.ts +++ b/packages/vscode-extension/src/panels/WebviewController.ts @@ -1,4 +1,4 @@ -import { Webview, Disposable, window } from "vscode"; +import { Webview, Disposable, window, commands } from "vscode"; import { DependencyChecker } from "../dependency/DependencyChecker"; import { DependencyInstaller } from "../dependency/DependencyInstaller"; import { DeviceManager } from "../devices/DeviceManager"; @@ -6,14 +6,14 @@ import { Project } from "../project/project"; import { openExternalUrl } from "../utilities/vsc"; import { Logger } from "../Logger"; import { extensionContext } from "../utilities/extensionContext"; -import { WorkspaceConfig } from "../common/WorkspaceConfig"; +import { WorkspaceConfigController } from "./WorkspaceConfigController"; export class WebviewController implements Disposable { private readonly dependencyChecker: DependencyChecker; private readonly dependencyInstaller: DependencyInstaller; private readonly deviceManager: DeviceManager; public readonly project: Project; - public readonly workspaceConfig: WorkspaceConfig; + public readonly workspaceConfig: WorkspaceConfigController; private disposables: Disposable[] = []; private followEnabled = false; @@ -22,7 +22,7 @@ export class WebviewController implements Disposable { constructor(private webview: Webview) { // Set an event listener to listen for messages passed from the webview context - this._setWebviewMessageListener(webview); + this.setWebviewMessageListener(webview); // Set the manager to listen and change the persisting storage for the extension. this.dependencyChecker = new DependencyChecker(webview); @@ -31,12 +31,12 @@ export class WebviewController implements Disposable { this.dependencyInstaller = new DependencyInstaller(webview); this.dependencyInstaller.setWebviewMessageListener(); - this._setupEditorListeners(); + this.setupEditorListeners(); this.deviceManager = new DeviceManager(); this.project = new Project(this.deviceManager); - this.workspaceConfig = new WorkspaceConfig(); + this.workspaceConfig = new WorkspaceConfigController(); this.disposables.push( this.dependencyChecker, @@ -51,9 +51,13 @@ export class WebviewController implements Disposable { ["Project", this.project as object], ["WorkspaceConfig", this.workspaceConfig as object], ]); + + commands.executeCommand("setContext", "RNIDE.panelIsOpen", true); } public dispose() { + commands.executeCommand("setContext", "RNIDE.panelIsOpen", false); + // Dispose of all disposables (i.e. commands) for the current webview while (this.disposables.length) { const disposable = this.disposables.pop(); @@ -63,7 +67,7 @@ export class WebviewController implements Disposable { } } - private _setWebviewMessageListener(webview: Webview) { + private setWebviewMessageListener(webview: Webview) { webview.onDidReceiveMessage( (message: any) => { const command = message.command; @@ -138,7 +142,7 @@ export class WebviewController implements Disposable { } } - private _setupEditorListeners() { + private setupEditorListeners() { extensionContext.subscriptions.push( window.onDidChangeActiveTextEditor((editor) => { if (editor) { diff --git a/packages/vscode-extension/src/panels/WorkspaceConfigController.ts b/packages/vscode-extension/src/panels/WorkspaceConfigController.ts new file mode 100644 index 000000000..2a2e20ab4 --- /dev/null +++ b/packages/vscode-extension/src/panels/WorkspaceConfigController.ts @@ -0,0 +1,62 @@ +import { ConfigurationChangeEvent, workspace, Disposable } from "vscode"; +import { + PanelLocation, + WorkspaceConfig, + WorkspaceConfigProps, + WorkspaceConfigEventMap, + WorkspaceConfigEventListener, +} from "../common/WorkspaceConfig"; +import { EventEmitter } from "stream"; + +export class WorkspaceConfigController implements Disposable, WorkspaceConfig { + private config: WorkspaceConfigProps; + private eventEmitter = new EventEmitter(); + private configListener: Disposable | undefined; + + constructor() { + const configuration = workspace.getConfiguration("ReactNativeIDE"); + this.config = { + panelLocation: configuration.get("panelLocation")!, + relativeAppLocation: configuration.get("relativeAppLocation")!, + }; + + this.configListener = workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => { + if (!event.affectsConfiguration("ReactNativeIDE")) { + return; + } + const configuration = workspace.getConfiguration("ReactNativeIDE"); + this.config = { + panelLocation: configuration.get("panelLocation")!, + relativeAppLocation: configuration.get("relativeAppLocation")!, + }; + this.eventEmitter.emit("configChange", this.config); + }); + } + + async getConfig() { + return this.config; + } + + async update(key: K, value: WorkspaceConfigProps[K]) { + const configuration = workspace.getConfiguration("ReactNativeIDE"); + await configuration.update(key as string, value); + } + + async addListener( + eventType: K, + listener: WorkspaceConfigEventListener + ) { + this.eventEmitter.addListener(eventType, listener); + } + + async removeListener( + eventType: K, + listener: WorkspaceConfigEventListener + ) { + this.eventEmitter.removeListener(eventType, listener); + } + + dispose() { + this.configListener?.dispose(); + } +} diff --git a/packages/vscode-extension/src/project/project.ts b/packages/vscode-extension/src/project/project.ts index 4bbd4bc54..a31627082 100644 --- a/packages/vscode-extension/src/project/project.ts +++ b/packages/vscode-extension/src/project/project.ts @@ -277,10 +277,6 @@ export class Project implements Disposable, MetroDelegate, ProjectInterface { commands.executeCommand("workbench.panel.repl.view.focus"); } - public async focusIntoSecondarySidebar(): Promise { - commands.executeCommand("workbench.action.focusAuxiliaryBar"); - } - public async openNavigation(navigationItemID: string) { this.deviceSession?.openNavigation(navigationItemID); } diff --git a/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx b/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx index 33179f722..3b6a25e7c 100644 --- a/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx +++ b/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx @@ -15,7 +15,7 @@ interface SettingsDropdownProps { } function SettingsDropdown({ project, children, disabled }: SettingsDropdownProps) { - const { showPanelInSideBar } = useWorkspaceConfig(); + const { panelLocation, update } = useWorkspaceConfig(); const { openModal } = useModal(); return ( @@ -42,16 +42,56 @@ function SettingsDropdown({ project, children, disabled }: SettingsDropdownProps Manage devices... + + + + Change IDE panel location + + + + + update("panelLocation", "tab")}> + + Editor tab + {panelLocation === "tab" && } + + update("panelLocation", "side-panel")}> + + Side panel + {panelLocation === "side-panel" && ( + + )} + + { + update("panelLocation", "secondary-side-panel"); + openModal( + "Drag and drop to secondary side panel", +
+ Drag and drop the IDE Panel by its icon from the side bar to move it to the + secondary panel. +
+ ); + }}> + + Secondary side panel + {panelLocation === "secondary-side-panel" && ( + + )} +
+
+
+
+ - {/* TODO: add this option back when its fully working - { - // @ts-ignore TODO fix this - openModal("Manage Android SDKs", ); - }}> - Manage Android SDKs... - */} - {showPanelInSideBar && ( - <> - - { - project.focusIntoSecondarySidebar(); - openModal( - "Move to secondary sidebar", -
- You can move extensions from Primary to secondary sidebar, by grab and droping - them.{" "} -
- ); - }}> - - Move to secondary sidebar -
- - )} diff --git a/packages/vscode-extension/src/webview/providers/WorkspaceConfigProvider.tsx b/packages/vscode-extension/src/webview/providers/WorkspaceConfigProvider.tsx index ff14bb99a..fa85b5e5c 100644 --- a/packages/vscode-extension/src/webview/providers/WorkspaceConfigProvider.tsx +++ b/packages/vscode-extension/src/webview/providers/WorkspaceConfigProvider.tsx @@ -1,31 +1,52 @@ -import { PropsWithChildren, useContext, createContext, useState, useEffect } from "react"; +import { + PropsWithChildren, + useContext, + createContext, + useState, + useEffect, + useCallback, +} from "react"; import { makeProxy } from "../utilities/rpc"; -import { WorkspaceConfigInterface, WorkspaceConfigProps } from "../../common/WorkspaceConfig"; +import { WorkspaceConfig, WorkspaceConfigProps } from "../../common/WorkspaceConfig"; -const config = makeProxy("WorkspaceConfig"); +const workspaceConfig = makeProxy("WorkspaceConfig"); -const WorkspaceConfigContext = createContext({ - showPanelInSideBar: false, +type WorkspaceConfigContextType = WorkspaceConfigProps & { + update: (key: K, value: WorkspaceConfigProps[K]) => void; +}; + +const WorkspaceConfigContext = createContext({ + panelLocation: "tab", relativeAppLocation: "", + update: () => {}, }); export default function WorkspaceConfigProvider({ children }: PropsWithChildren) { - const [workspaceConfig, setWorkspaceConfig] = useState({ - showPanelInSideBar: false, + const [config, setConfig] = useState({ + panelLocation: "tab", relativeAppLocation: "", }); useEffect(() => { - config.getWorkspaceConfigProps().then(setWorkspaceConfig); - config.addListener("workspaceConfigChange", setWorkspaceConfig); + workspaceConfig.getConfig().then(setConfig); + workspaceConfig.addListener("configChange", setConfig); return () => { - config.removeListener("workspaceConfigChange", setWorkspaceConfig); + workspaceConfig.removeListener("configChange", setConfig); }; }, []); + const update = useCallback( + (key: K, value: WorkspaceConfigProps[K]) => { + const newState = { ...config, [key]: value }; + setConfig(newState); + workspaceConfig.update(key, value); + }, + [config, setConfig] + ); + return ( - + {children} ); From 97f31e57f560d3a9d11179f4b22a0dd8f2653424 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Wed, 27 Mar 2024 15:28:24 +0100 Subject: [PATCH 5/6] Update docs and description --- USAGE.md | 11 ++++++----- packages/vscode-extension/package.json | 7 +------ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/USAGE.md b/USAGE.md index 473b84ad9..ad663afbe 100644 --- a/USAGE.md +++ b/USAGE.md @@ -10,9 +10,9 @@ The only thing you need to do is open your React Native of Expo project as works Once you have it open, you can start the extension panel in one of a few ways: 1. When you open any file of your project to edit it, you can launch the extension from "Open IDE Panel" button in the editor toolbar: -sztudio_editor_button + sztudio_editor_button 2. You can use "React Native IDE: Open IDE Panel" available in vscode's command palette: -sztudio_command_palette + sztudio_command_palette 3. If you already had the panel open in this project before restarting the editor, it will automatically reopen in the same place. ## 2. Create simulator and emulator instances on the first run @@ -28,9 +28,10 @@ Please follow the [SIMULATORS](SIMULATORS.md) section to learn how to manage sys ## 3. Decide on the location of the IDE panel -The main extension window can be either presented as one of the editor tabs, which is the default behavior, or as a side panel. -To change between these two modes, open VSCode settings and search for "React Native IDE: Show Panel in Activity Bar" option. -Note that when the extension is used as side panel it can be dragged to the secondary side panel that's on the opposite side to where your file explorer is placed: +The main extension window can be either presented as one of the editor tabs, which is the default behavior, or as a side panel (in primary or secondary side panel location). +To change between these modes, you can either use React Native IDE section in the VSCode settings, or use the dropdown menu from the right top corner in the IDE panel: + +Here is how the IDE would look like when place in the side panel: sztudio-side-panel ## 4. Wait for the project to build and run diff --git a/packages/vscode-extension/package.json b/packages/vscode-extension/package.json index 17742910a..f81a74f01 100644 --- a/packages/vscode-extension/package.json +++ b/packages/vscode-extension/package.json @@ -61,12 +61,7 @@ "side-panel", "secondary-side-panel" ], - "enumDescriptions": [ - "Editor tab", - "Side panel", - "Secondary side panel" - ], - "description": "Controlls location of the IDE panel. Changing this option closes and reopens the IDE." + "description": "Controlls location of the IDE panel. Due to vscode API limitations, when secondary side panel is selected, you need to manually move the IDE panel to the secondary side panel. Changing this option closes and reopens the IDE." } } }, From f76562a4e7d9692d0d61545ecc42577323cc26c7 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Wed, 27 Mar 2024 15:30:20 +0100 Subject: [PATCH 6/6] Upload screenshot with location settings --- USAGE.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/USAGE.md b/USAGE.md index ad663afbe..ead408b73 100644 --- a/USAGE.md +++ b/USAGE.md @@ -19,6 +19,7 @@ Once you have it open, you can start the extension panel in one of a few ways: When you open the IDE panel for the first time, it'll ask you to configure Android emulator of iOS simulator. Depending on which platform you want to run your app on first, click one of the options available at the initial screen: + sztudio-init-screen You will be able to add or remove simulators later using the device menu in the left bottom corner of the panel. @@ -31,7 +32,10 @@ Please follow the [SIMULATORS](SIMULATORS.md) section to learn how to manage sys The main extension window can be either presented as one of the editor tabs, which is the default behavior, or as a side panel (in primary or secondary side panel location). To change between these modes, you can either use React Native IDE section in the VSCode settings, or use the dropdown menu from the right top corner in the IDE panel: +sztudio-change-position + Here is how the IDE would look like when place in the side panel: + sztudio-side-panel ## 4. Wait for the project to build and run