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} );