diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 95021fde..1346acc9 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -158,6 +158,7 @@ import { MonacoEditorProvider } from './theia/monaco/monaco-editor-provider'; import { MonacoEditorProvider as TheiaMonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider'; import { DebugEditorModel } from './theia/debug/debug-editor-model'; import { DebugEditorModelFactory } from '@theia/debug/lib/browser/editor/debug-editor-model'; +import { StorageWrapper } from './storage-wrapper'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -435,4 +436,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(SettingsDialogProps).toConstantValue({ title: 'Preferences' }); + + bind(StorageWrapper).toSelf().inSingletonScope(); + bind(CommandContribution).toService(StorageWrapper); }); diff --git a/arduino-ide-extension/src/browser/boards/boards-config.tsx b/arduino-ide-extension/src/browser/boards/boards-config.tsx index f48fd311..33e07b42 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-config.tsx @@ -281,6 +281,39 @@ export namespace BoardsConfig { return `${name}${port ? ' at ' + Port.toString(port) : ''}`; } + export function setConfig(config: Config | undefined, urlToAttachTo: URL): URL { + const copy = new URL(urlToAttachTo.toString()); + if (!config) { + copy.searchParams.delete('boards-config'); + return copy; + } + + const selectedBoard = config.selectedBoard ? { name: config.selectedBoard.name, fqbn: config.selectedBoard.fqbn } : undefined; + const selectedPort = config.selectedPort ? { protocol: config.selectedPort.protocol, address: config.selectedPort.address } : undefined; + const jsonConfig = JSON.stringify({ selectedBoard, selectedPort }); + copy.searchParams.set('boards-config', encodeURIComponent(jsonConfig)); + return copy; + } + + export function getConfig(url: URL): Config | undefined { + const encoded = url.searchParams.get('boards-config'); + if (!encoded) { + return undefined; + } + try { + const raw = decodeURIComponent(encoded); + const candidate = JSON.parse(raw); + if (typeof candidate === 'object') { + return candidate; + } + console.warn(`Expected candidate to be an object. It was ${typeof candidate}. URL was: ${url}`); + return undefined; + } catch (e) { + console.log(`Could not get board config from URL: ${url}.`, e); + return undefined; + } + } + } } diff --git a/arduino-ide-extension/src/browser/boards/boards-service-provider.ts b/arduino-ide-extension/src/browser/boards/boards-service-provider.ts index 7070526c..b02e918d 100644 --- a/arduino-ide-extension/src/browser/boards/boards-service-provider.ts +++ b/arduino-ide-extension/src/browser/boards/boards-service-provider.ts @@ -1,8 +1,8 @@ import { injectable, inject } from 'inversify'; import { Emitter } from '@theia/core/lib/common/event'; import { ILogger } from '@theia/core/lib/common/logger'; +import { CommandService } from '@theia/core/lib/common/command'; import { MessageService } from '@theia/core/lib/common/message-service'; -import { StorageService } from '@theia/core/lib/browser/storage-service'; import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'; import { RecursiveRequired } from '../../common/types'; import { @@ -16,8 +16,8 @@ import { import { BoardsConfig } from './boards-config'; import { naturalCompare } from '../../common/utils'; import { NotificationCenter } from '../notification-center'; -import { CommandService } from '@theia/core'; import { ArduinoCommands } from '../arduino-commands'; +import { StorageWrapper } from '../storage-wrapper'; @injectable() export class BoardsServiceProvider implements FrontendApplicationContribution { @@ -28,8 +28,6 @@ export class BoardsServiceProvider implements FrontendApplicationContribution { @inject(MessageService) protected messageService: MessageService; - @inject(StorageService) - protected storageService: StorageService; @inject(BoardsService) protected boardsService: BoardsService; @@ -349,7 +347,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution { return undefined; } const key = this.getLastSelectedBoardOnPortKey(port); - return this.storageService.getData(key); + return this.getData(key); } protected async saveState(): Promise { @@ -360,11 +358,11 @@ export class BoardsServiceProvider implements FrontendApplicationContribution { const { selectedBoard, selectedPort } = this.boardsConfig; if (selectedBoard && selectedPort) { const key = this.getLastSelectedBoardOnPortKey(selectedPort); - await this.storageService.setData(key, selectedBoard); + await this.setData(key, selectedBoard); } await Promise.all([ - this.storageService.setData('latest-valid-boards-config', this.latestValidBoardsConfig), - this.storageService.setData('latest-boards-config', this.latestBoardsConfig) + this.setData('latest-valid-boards-config', this.latestValidBoardsConfig), + this.setData('latest-boards-config', this.latestBoardsConfig) ]); } @@ -374,7 +372,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution { } protected async loadState(): Promise { - const storedLatestValidBoardsConfig = await this.storageService.getData>('latest-valid-boards-config'); + const storedLatestValidBoardsConfig = await this.getData>('latest-valid-boards-config'); if (storedLatestValidBoardsConfig) { this.latestValidBoardsConfig = storedLatestValidBoardsConfig; if (this.canUploadTo(this.latestValidBoardsConfig)) { @@ -382,13 +380,25 @@ export class BoardsServiceProvider implements FrontendApplicationContribution { } } else { // If we could not restore the latest valid config, try to restore something, the board at least. - const storedLatestBoardsConfig = await this.storageService.getData('latest-boards-config'); + let storedLatestBoardsConfig = await this.getData('latest-boards-config'); + // Try to get from the URL if it was not persisted. + if (!storedLatestBoardsConfig) { + storedLatestBoardsConfig = BoardsConfig.Config.getConfig(new URL(window.location.href)); + } if (storedLatestBoardsConfig) { this.latestBoardsConfig = storedLatestBoardsConfig; this.boardsConfig = this.latestBoardsConfig; } } } + + private setData(key: string, value: T): Promise { + return this.commandService.executeCommand(StorageWrapper.Commands.SET_DATA.id, key, value); + } + + private getData(key: string): Promise { + return this.commandService.executeCommand(StorageWrapper.Commands.GET_DATA.id, key); + } } /** diff --git a/arduino-ide-extension/src/browser/storage-wrapper.ts b/arduino-ide-extension/src/browser/storage-wrapper.ts new file mode 100644 index 00000000..3aa7dad8 --- /dev/null +++ b/arduino-ide-extension/src/browser/storage-wrapper.ts @@ -0,0 +1,33 @@ +import { injectable, inject } from 'inversify'; +import { StorageService } from '@theia/core/lib/browser/storage-service'; +import { Command, CommandContribution, CommandRegistry } from '@theia/core/lib/common/command'; + +/** + * This is a workaround to break cycles in the dependency injection. Provides commands for `setData` and `getData`. + */ +@injectable() +export class StorageWrapper implements CommandContribution { + + @inject(StorageService) + protected storageService: StorageService; + + registerCommands(commands: CommandRegistry): void { + commands.registerCommand(StorageWrapper.Commands.GET_DATA, { + execute: (key: string, defaultValue?: any) => this.storageService.getData(key, defaultValue) + }); + commands.registerCommand(StorageWrapper.Commands.SET_DATA, { + execute: (key: string, value: any) => this.storageService.setData(key, value) + }); + } + +} +export namespace StorageWrapper { + export namespace Commands { + export const SET_DATA: Command = { + id: 'arduino-store-wrapper-set' + }; + export const GET_DATA: Command = { + id: 'arduino-store-wrapper-get' + }; + } +} diff --git a/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts b/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts index 784f0a7f..592f8d8a 100644 --- a/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts +++ b/arduino-ide-extension/src/browser/theia/workspace/workspace-service.ts @@ -11,6 +11,8 @@ import { WorkspaceService as TheiaWorkspaceService } from '@theia/workspace/lib/ import { ConfigService } from '../../../common/protocol/config-service'; import { SketchesService, Sketch, SketchContainer } from '../../../common/protocol/sketches-service'; import { ArduinoWorkspaceRootResolver } from '../../arduino-workspace-resolver'; +import { BoardsServiceProvider } from '../../boards/boards-service-provider'; +import { BoardsConfig } from '../../boards/boards-config'; @injectable() export class WorkspaceService extends TheiaWorkspaceService { @@ -33,6 +35,9 @@ export class WorkspaceService extends TheiaWorkspaceService { @inject(FrontendApplicationStateService) protected readonly appStateService: FrontendApplicationStateService; + @inject(BoardsServiceProvider) + protected readonly boardsServiceProvider: BoardsServiceProvider; + private application: FrontendApplication; private workspaceUri?: Promise; private version?: string; @@ -80,6 +85,13 @@ export class WorkspaceService extends TheiaWorkspaceService { return this.workspaceUri; } + protected openNewWindow(workspacePath: string): void { + const { boardsConfig } = this.boardsServiceProvider; + const url = BoardsConfig.Config.setConfig(boardsConfig, new URL(window.location.href)); // Set the current boards config for the new browser window. + url.hash = workspacePath; + this.windowService.openNewWindow(url.toString()); + } + private async isValid(uri: string): Promise { const exists = await this.fileService.exists(new URI(uri)); if (!exists) { diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts index 7a1428ab..1b113fa5 100644 --- a/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts +++ b/arduino-ide-extension/src/electron-main/theia/electron-main-window-service.ts @@ -11,7 +11,8 @@ export class ElectronMainWindowServiceImpl extends TheiaElectronMainWindowServic openNewWindow(url: string, { external }: NewWindowOptions): undefined { if (!external) { - const existing = this.app.windows.find(window => window.webContents.getURL() === url); + const sanitizedUrl = this.sanitize(url); + const existing = this.app.windows.find(window => this.sanitize(window.webContents.getURL()) === sanitizedUrl); if (existing) { existing.focus(); return; @@ -20,5 +21,14 @@ export class ElectronMainWindowServiceImpl extends TheiaElectronMainWindowServic return super.openNewWindow(url, { external }); } + private sanitize(url: string): string { + const copy = new URL(url); + const searchParams: string[] = []; + copy.searchParams.forEach((_, key) => searchParams.push(key)); + for (const param of searchParams) { + copy.searchParams.delete(param); + } + return copy.toString(); + } }