diff --git a/USAGE.md b/USAGE.md index 473b84ad9..ead408b73 100644 --- a/USAGE.md +++ b/USAGE.md @@ -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: -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 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. @@ -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: + +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 diff --git a/packages/vscode-extension/package.json b/packages/vscode-extension/package.json index c0bffa094..f81a74f01 100644 --- a/packages/vscode-extension/package.json +++ b/packages/vscode-extension/package.json @@ -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." } } }, @@ -75,7 +80,7 @@ "type": "webview", "id": "ReactNativeIDE.view", "name": "IDE Panel", - "when": "config.ReactNativeIDE.showPanelInActivityBar" + "when": "config.ReactNativeIDE.panelLocation != 'tab'" } ] }, @@ -84,7 +89,7 @@ { "command": "RNIDE.openPanel", "group": "navigation", - "when": "RNIDE.extensionIsActive && !RNIDE.panelIsOpen && !config.ReactNativeIDE.showPanelInActivityBar" + "when": "RNIDE.extensionIsActive && !RNIDE.panelIsOpen" } ] }, diff --git a/packages/vscode-extension/src/common/Project.ts b/packages/vscode-extension/src/common/Project.ts index 66d18a859..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; - 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..a6075608b --- /dev/null +++ b/packages/vscode-extension/src/common/WorkspaceConfig.ts @@ -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 { + (event: T): void; +} + +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 + ): Promise; + removeListener( + eventType: K, + listener: WorkspaceConfigEventListener + ): Promise; +} diff --git a/packages/vscode-extension/src/extension.ts b/packages/vscode-extension/src/extension.ts index 35d0171c0..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("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("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) ); @@ -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) { diff --git a/packages/vscode-extension/src/panels/SidepanelViewProvider.ts b/packages/vscode-extension/src/panels/SidepanelViewProvider.ts index 1a11f301b..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 { 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 { @@ -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 3892382f2..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,12 +6,14 @@ import { Project } from "../project/project"; import { openExternalUrl } from "../utilities/vsc"; import { Logger } from "../Logger"; import { extensionContext } from "../utilities/extensionContext"; +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: WorkspaceConfigController; private disposables: Disposable[] = []; private followEnabled = false; @@ -20,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); @@ -29,25 +31,33 @@ 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 WorkspaceConfigController(); + 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], ]); + + 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(); @@ -57,7 +67,7 @@ export class WebviewController implements Disposable { } } - private _setWebviewMessageListener(webview: Webview) { + private setWebviewMessageListener(webview: Webview) { webview.onDidReceiveMessage( (message: any) => { const command = message.command; @@ -132,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 7c136b459..a31627082 100644 --- a/packages/vscode-extension/src/project/project.ts +++ b/packages/vscode-extension/src/project/project.ts @@ -261,6 +261,7 @@ export class Project implements Disposable, MetroDelegate, ProjectInterface { public async stepOverDebugger() { this.deviceSession?.stepOverDebugger(); } + public async focusBuildOutput() { if (!this.projectState.selectedDevice) { return; diff --git a/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx b/packages/vscode-extension/src/webview/components/SettingsDropdown.tsx index e8199f8dd..3b6a25e7c 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 { panelLocation, update } = useWorkspaceConfig(); const { openModal } = useModal(); return ( @@ -43,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... - */} Clean rebuild + 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..fa85b5e5c --- /dev/null +++ b/packages/vscode-extension/src/webview/providers/WorkspaceConfigProvider.tsx @@ -0,0 +1,62 @@ +import { + PropsWithChildren, + useContext, + createContext, + useState, + useEffect, + useCallback, +} from "react"; +import { makeProxy } from "../utilities/rpc"; +import { WorkspaceConfig, WorkspaceConfigProps } from "../../common/WorkspaceConfig"; + +const workspaceConfig = makeProxy("WorkspaceConfig"); + +type WorkspaceConfigContextType = WorkspaceConfigProps & { + update: (key: K, value: WorkspaceConfigProps[K]) => void; +}; + +const WorkspaceConfigContext = createContext({ + panelLocation: "tab", + relativeAppLocation: "", + update: () => {}, +}); + +export default function WorkspaceConfigProvider({ children }: PropsWithChildren) { + const [config, setConfig] = useState({ + panelLocation: "tab", + relativeAppLocation: "", + }); + + useEffect(() => { + workspaceConfig.getConfig().then(setConfig); + workspaceConfig.addListener("configChange", setConfig); + + return () => { + 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} + + ); +} + +export function useWorkspaceConfig() { + const context = useContext(WorkspaceConfigContext); + + if (context === undefined) { + throw new Error("useWorkspaceConfig must be used within a WorkspaceConfigProvider"); + } + return context; +}