ATL-1206: Reuse selected board for new sketches.

Closes #95.

Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
Akos Kitta 2021-04-07 18:11:02 +02:00 committed by Akos Kitta
parent fa9334eb7a
commit c86d82d273
6 changed files with 113 additions and 11 deletions

View File

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

View File

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

View File

@ -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<Board>(key);
return this.getData<Board>(key);
}
protected async saveState(): Promise<void> {
@ -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<void> {
const storedLatestValidBoardsConfig = await this.storageService.getData<RecursiveRequired<BoardsConfig.Config>>('latest-valid-boards-config');
const storedLatestValidBoardsConfig = await this.getData<RecursiveRequired<BoardsConfig.Config>>('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<BoardsConfig.Config | undefined>('latest-boards-config');
let storedLatestBoardsConfig = await this.getData<BoardsConfig.Config | undefined>('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<T>(key: string, value: T): Promise<void> {
return this.commandService.executeCommand(StorageWrapper.Commands.SET_DATA.id, key, value);
}
private getData<T>(key: string): Promise<T | undefined> {
return this.commandService.executeCommand<T>(StorageWrapper.Commands.GET_DATA.id, key);
}
}
/**

View File

@ -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'
};
}
}

View File

@ -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<string | undefined>;
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<boolean> {
const exists = await this.fileService.exists(new URI(uri));
if (!exists) {

View File

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