Skip to content

Commit

Permalink
Move from activity bar to secondary bar hint (#24)
Browse files Browse the repository at this point in the history
This PR adds a way to control the panel position.

As a part of this PR we add a new position option "secondary side
panel". We believe it may be useful to use secondary side panel for the
IDE, but it isn't a popular feature of vscode and users are not familiar
with it. On top of that, it cannot be fully controlled programatically.
Specifically, there is no API to move panel from primary to secondary
side panel. To workaround this issue, when user select "secondary side
panel" option we ask them to move to secondary panel by hand using drag
and drop.

This PR also updates the logic for command palette commands and makes it
so that "Open IDE" is always visible unless the panel is opened.

We also do some renamings: Tabpanel -> TabPanel and Sidepanel ->
SidePanel

---------

Co-authored-by: Filip Andrzej Kaminski <[email protected]>
Co-authored-by: Krzysztof Magiera <[email protected]>
  • Loading branch information
3 people authored Mar 27, 2024
1 parent 94e8c4e commit 11d8b39
Show file tree
Hide file tree
Showing 13 changed files with 330 additions and 91 deletions.
15 changes: 10 additions & 5 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ 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:
<img width="800" alt="sztudio_editor_button" src="https://github.com/software-mansion-labs/react-native-ide/assets/726445/18983660-0a06-4b56-ba3f-2eda2bf50f12">
<img width="800" alt="sztudio_editor_button" src="https://github.com/software-mansion-labs/react-native-ide/assets/726445/18983660-0a06-4b56-ba3f-2eda2bf50f12">
2. You can use "React Native IDE: Open IDE Panel" available in vscode's command palette:
<img width="800" alt="sztudio_command_palette" src="https://github.com/software-mansion-labs/react-native-ide/assets/726445/ea7579b1-fc40-47c2-9d1c-50907ec9d665">
<img width="800" alt="sztudio_command_palette" src="https://github.com/software-mansion-labs/react-native-ide/assets/726445/ea7579b1-fc40-47c2-9d1c-50907ec9d665">
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

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:

<img width="650" alt="sztudio-init-screen" src="https://github.com/software-mansion-labs/react-native-ide/assets/726445/d2c6a55a-2f22-46fe-917b-686766ad1f8e">

You will be able to add or remove simulators later using the device menu in the left bottom corner of the panel.
Expand All @@ -28,9 +29,13 @@ 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:

<img width="450" alt="sztudio-change-position" src="https://github.com/software-mansion-labs/react-native-ide/assets/726445/5540bce1-855a-4e77-8c22-a5429b6d90d9">

Here is how the IDE would look like when place in the side panel:

<img width="800" alt="sztudio-side-panel" src="https://github.com/software-mansion-labs/react-native-ide/assets/726445/fdb01232-c735-40e1-bf75-a6cbdef5d9a6">

## 4. Wait for the project to build and run
Expand Down
17 changes: 11 additions & 6 deletions packages/vscode-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,16 @@
"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":{
"type": "boolean",
"ReactNativeIDE.panelLocation": {
"type": "string",
"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.)"
"default": "tab",
"enum": [
"tab",
"side-panel",
"secondary-side-panel"
],
"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."
}
}
},
Expand All @@ -75,7 +80,7 @@
"type": "webview",
"id": "ReactNativeIDE.view",
"name": "IDE Panel",
"when": "config.ReactNativeIDE.showPanelInActivityBar"
"when": "config.ReactNativeIDE.panelLocation != 'tab'"
}
]
},
Expand All @@ -84,7 +89,7 @@
{
"command": "RNIDE.openPanel",
"group": "navigation",
"when": "RNIDE.extensionIsActive && !RNIDE.panelIsOpen && !config.ReactNativeIDE.showPanelInActivityBar"
"when": "RNIDE.extensionIsActive && !RNIDE.panelIsOpen"
}
]
},
Expand Down
1 change: 0 additions & 1 deletion packages/vscode-extension/src/common/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ export interface ProjectInterface {
focusBuildOutput(): Promise<void>;
focusExtensionLogsOutput(): Promise<void>;
focusDebugConsole(): Promise<void>;

openNavigation(navigationItemID: string): Promise<void>;

dispatchTouch(xRatio: number, yRatio: number, type: "Up" | "Move" | "Down"): Promise<void>;
Expand Down
31 changes: 31 additions & 0 deletions packages/vscode-extension/src/common/WorkspaceConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export type PanelLocation = "tab" | "side-panel" | "secondary-side-panel";

export type WorkspaceConfigProps = {
panelLocation: PanelLocation;
relativeAppLocation: string;
};

export interface WorkspaceConfigEventMap {
configChange: WorkspaceConfigProps;
}

export interface WorkspaceConfigEventListener<T> {
(event: T): void;
}

export interface WorkspaceConfig {
getConfig(): Promise<WorkspaceConfigProps>;
// update method can take any of the keys from WorkspaceConfigProps and appropriate value:
update<K extends keyof WorkspaceConfigProps>(
key: K,
value: WorkspaceConfigProps[K]
): Promise<void>;
addListener<K extends keyof WorkspaceConfigEventMap>(
eventType: K,
listener: WorkspaceConfigEventListener<WorkspaceConfigEventMap[K]>
): Promise<void>;
removeListener<K extends keyof WorkspaceConfigEventMap>(
eventType: K,
listener: WorkspaceConfigEventListener<WorkspaceConfigEventMap[K]>
): Promise<void>;
}
62 changes: 27 additions & 35 deletions packages/vscode-extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -54,38 +55,32 @@ export function deactivate(context: ExtensionContext): undefined {

export async function activate(context: ExtensionContext) {
handleUncaughtErrors();
IDEPanelLocationListener();

setExtensionContext(context);
if (context.extensionMode === ExtensionMode.Development) {
enableDevModeLogging();
}

await fixBinaries(context);

function showIDEPanel(fileName?: string, lineNumber?: number) {
if (
workspace.getConfiguration("ReactNativeIDE").get<PanelLocation>("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<boolean>("showPanelInActivityBar")) {
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<boolean>("showPanelInActivityBar")) {
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)
);
Expand Down Expand Up @@ -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("showPanelInActivityBar")) {
Tabpanel.currentPanel?.dispose();
}
});
await configureAppRootFolder();
}

async function findSingleFileInWorkspace(fileGlobPattern: string, excludePattern: string | null) {
Expand Down
28 changes: 20 additions & 8 deletions packages/vscode-extension/src/panels/SidepanelViewProvider.ts
Original file line number Diff line number Diff line change
@@ -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 { extensionContext } from "../utilities/extensionContext";

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 {
Expand All @@ -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}`
);
}
Expand Down
35 changes: 26 additions & 9 deletions packages/vscode-extension/src/panels/Tabpanel.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
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";
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;

Expand All @@ -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

Expand All @@ -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}`
);
}
Expand All @@ -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();
Expand Down
Loading

0 comments on commit 11d8b39

Please sign in to comment.