From fba71240ab5afbe9ec1680607ddafbd0b317a1b4 Mon Sep 17 00:00:00 2001 From: Brian Leighty Date: Wed, 14 Jun 2023 17:07:57 -0400 Subject: [PATCH 01/21] roku automation panel progress commit, standardize event messages to contain fields inside of `context`, add support for webview-ui-toolkit in webviews --- src/BrightScriptCommands.ts | 18 +- src/commands/VscodeCommand.ts | 4 +- src/extension.ts | 2 +- src/managers/RtaManager.ts | 8 +- .../WebviewViewProviderManager.spec.ts | 11 +- src/managers/WebviewViewProviderManager.ts | 28 +- src/viewProviders/BaseRdbViewProvider.ts | 80 ++--- src/viewProviders/BaseWebviewViewProvider.ts | 55 ++- .../RokuAutomationViewViewProvider.ts | 142 ++++++++ .../RokuCommandsViewProvider.spec.ts | 7 +- .../RokuDeviceViewViewProvider.ts | 26 +- .../RokuRegistryViewProvider.spec.ts | 9 +- src/viewProviders/RokuRegistryViewProvider.ts | 23 +- .../SceneGraphInspectorViewProvider.ts | 14 +- src/viewProviders/ViewProviderCommand.ts | 5 +- src/viewProviders/ViewProviderEvent.ts | 5 +- src/viewProviders/ViewProviderId.ts | 3 +- webviews/package-lock.json | 2 +- webviews/package.json | 2 +- webviews/src/main.ts | 11 +- .../RokuAutomationView.svelte | 324 ++++++++++++++++++ .../RokuCommandsView/RokuCommandsView.svelte | 2 +- .../RokuDeviceView/RokuDeviceView.svelte | 14 +- .../RokuRegistryView/RokuRegistryView.svelte | 9 +- .../SceneGraphInspectorView.svelte | 6 +- 25 files changed, 682 insertions(+), 128 deletions(-) create mode 100644 src/viewProviders/RokuAutomationViewViewProvider.ts create mode 100644 webviews/src/views/RokuAutomationView/RokuAutomationView.svelte diff --git a/src/BrightScriptCommands.ts b/src/BrightScriptCommands.ts index 5a5fd6f8..b80a8374 100644 --- a/src/BrightScriptCommands.ts +++ b/src/BrightScriptCommands.ts @@ -23,6 +23,7 @@ export class BrightScriptCommands { private fileUtils: BrightScriptFileUtils; private host: string; + private keypressNotifiers = [] as ((key: string, literalCharacter: boolean) => void)[]; public registerCommands() { @@ -306,7 +307,15 @@ export class BrightScriptCommands { } } - public async sendRemoteCommand(key: string, host?: string) { + public async sendRemoteCommand(key: string, host?: string, literalCharacter = false) { + for (const notifier of this.keypressNotifiers) { + notifier(key, literalCharacter); + } + + if (literalCharacter) { + key = 'Lit_' + encodeURIComponent(key); + } + // do we have a temporary override? if (!host) { // Get the long lived host ip @@ -356,6 +365,10 @@ export class BrightScriptCommands { } } + public registerKeypressNotifier(notifier: (key: string, literalCharacter: boolean) => void) { + this.keypressNotifiers.push(notifier); + } + private registerCommand(name: string, callback: (...args: any[]) => any, thisArg?: any) { const prefix = 'extension.brightscript.'; const commandName = name.startsWith(prefix) ? name : prefix + name; @@ -363,7 +376,6 @@ export class BrightScriptCommands { } private async sendAsciiToDevice(character: string) { - let commandToSend: string = 'Lit_' + encodeURIComponent(character); - await this.sendRemoteCommand(commandToSend); + await this.sendRemoteCommand(character, undefined, true); } } diff --git a/src/commands/VscodeCommand.ts b/src/commands/VscodeCommand.ts index a9371392..9f1c5967 100644 --- a/src/commands/VscodeCommand.ts +++ b/src/commands/VscodeCommand.ts @@ -7,5 +7,7 @@ export enum VscodeCommand { rokuRegistryExportRegistry = 'extension.brightscript.rokuRegistry.exportRegistry', rokuRegistryImportRegistry = 'extension.brightscript.rokuRegistry.importRegistry', rokuRegistryClearRegistry = 'extension.brightscript.rokuRegistry.clearRegistry', - rokuRegistryRefreshRegistry = 'extension.brightscript.rokuRegistry.refreshRegistry' + rokuRegistryRefreshRegistry = 'extension.brightscript.rokuRegistry.refreshRegistry', + rokuAutomationStartRecording = 'extension.brightscript.rokuAutomation.startRecording', + rokuAutomationStopRecording = 'extension.brightscript.rokuAutomation.stopRecording' } diff --git a/src/extension.ts b/src/extension.ts index a9becd9d..7a9eb016 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -72,7 +72,7 @@ export class Extension { ); this.rtaManager = new RtaManager(); - this.webviewViewProviderManager = new WebviewViewProviderManager(context, this.rtaManager); + this.webviewViewProviderManager = new WebviewViewProviderManager(context, this.rtaManager, this.brightScriptCommands); this.rtaManager.setWebviewViewProviderManager(this.webviewViewProviderManager); //update the tracked version of the extension diff --git a/src/managers/RtaManager.ts b/src/managers/RtaManager.ts index 49c37fe9..140d46c9 100644 --- a/src/managers/RtaManager.ts +++ b/src/managers/RtaManager.ts @@ -13,7 +13,7 @@ export class RtaManager { public setupRtaWithConfig(config: { host: string; password: string; logLevel?: string; disableScreenSaver?: boolean; injectRdbOnDeviceComponent?: boolean }) { const enableDebugging = ['info', 'debug', 'trace'].includes(config.logLevel); - rta.odc.setConfig({ + const rtaConfig: rta.ConfigOptions = { RokuDevice: { devices: [{ host: config.host, @@ -26,7 +26,11 @@ export class RtaManager { disableTelnet: true, disableCallOriginationLine: true } - }); + }; + + rta.odc.setConfig(rtaConfig); + + rta.ecp.setConfig(rtaConfig); this.device = rta.device; diff --git a/src/managers/WebviewViewProviderManager.spec.ts b/src/managers/WebviewViewProviderManager.spec.ts index 907bc143..6e60da0a 100644 --- a/src/managers/WebviewViewProviderManager.spec.ts +++ b/src/managers/WebviewViewProviderManager.spec.ts @@ -4,6 +4,7 @@ import { vscode } from '../mockVscode.spec'; import type { BrightScriptLaunchConfiguration } from '../DebugConfigurationProvider'; import { WebviewViewProviderManager } from './WebviewViewProviderManager'; import { RtaManager } from './RtaManager'; +import { BrightScriptCommands } from '../BrightScriptCommands'; const sinon = createSandbox(); @@ -14,6 +15,7 @@ describe('WebviewViewProviderManager', () => { const config = {} as BrightScriptLaunchConfiguration; let webviewViewProviderManager: WebviewViewProviderManager; let rtaManager: RtaManager; + const brightScriptCommands = new BrightScriptCommands({} as any, {} as any, {} as any, {} as any); before(() => { context = { @@ -46,16 +48,17 @@ describe('WebviewViewProviderManager', () => { before(() => { spy = sinon.spy(vscode.window, 'registerWebviewViewProvider'); rtaManager = new RtaManager(); - webviewViewProviderManager = new WebviewViewProviderManager(context, rtaManager); + webviewViewProviderManager = new WebviewViewProviderManager(context, rtaManager, brightScriptCommands); }); it('initializes webview providers and calls registerWebviewViewProvider for each', () => { expect(spy.callCount).to.equal(webviewViewProviderManager.getWebviewViewProviders().length); }); - it('assigns RtaManager to each webviewViewProvider', () => { + it('assigns dependencies to each webviewViewProvider', () => { for (const webviewViewProvider of webviewViewProviderManager.getWebviewViewProviders()) { - expect(webviewViewProvider['rtaManager']).to.equal(rtaManager); + expect(webviewViewProvider['dependencies']['rtaManager']).to.equal(rtaManager); + expect(webviewViewProvider['dependencies']['brightScriptCommands']).to.equal(brightScriptCommands); } expect(spy.callCount).to.equal(webviewViewProviderManager.getWebviewViewProviders().length); }); @@ -74,7 +77,7 @@ describe('WebviewViewProviderManager', () => { }; rtaManager = new RtaManager(); - webviewViewProviderManager = new WebviewViewProviderManager(context, rtaManager); + webviewViewProviderManager = new WebviewViewProviderManager(context, rtaManager, brightScriptCommands); rtaManager.setWebviewViewProviderManager(webviewViewProviderManager); }); diff --git a/src/managers/WebviewViewProviderManager.ts b/src/managers/WebviewViewProviderManager.ts index 5bec92b4..26c9ff5f 100644 --- a/src/managers/WebviewViewProviderManager.ts +++ b/src/managers/WebviewViewProviderManager.ts @@ -1,33 +1,34 @@ import type { ChannelPublishedEvent } from 'roku-debug'; import type { BrightScriptLaunchConfiguration } from '../DebugConfigurationProvider'; import type { RtaManager } from './RtaManager'; +import type { BrightScriptCommands } from '../BrightScriptCommands'; import * as vscode from 'vscode'; import { RokuCommandsViewProvider } from '../viewProviders/RokuCommandsViewProvider'; import { RokuDeviceViewViewProvider } from '../viewProviders/RokuDeviceViewViewProvider'; import { RokuRegistryViewProvider } from '../viewProviders/RokuRegistryViewProvider'; import { SceneGraphInspectorViewProvider } from '../viewProviders/SceneGraphInspectorViewProvider'; - +import { RokuAutomationViewViewProvider } from '../viewProviders/RokuAutomationViewViewProvider'; export class WebviewViewProviderManager { - constructor(context: vscode.ExtensionContext, rtaManager: RtaManager) { - this.rtaManager = rtaManager; + constructor( + private context: vscode.ExtensionContext, + private rtaManager: RtaManager, + private brightScriptCommands: BrightScriptCommands + ) { for (const webview of this.webviewViews) { if (!webview.provider) { - webview.provider = new webview.constructor(context); + webview.provider = new webview.constructor(context, { + rtaManager: rtaManager, + brightScriptCommands: brightScriptCommands + }); vscode.window.registerWebviewViewProvider(webview.provider.id, webview.provider); webview.provider.setWebviewViewProviderManager(this); - - if (typeof webview.provider.setRtaManager === 'function') { - webview.provider.setRtaManager(this.rtaManager); - } } } } - private rtaManager?: RtaManager; - private webviewViews = [{ constructor: SceneGraphInspectorViewProvider, provider: undefined as SceneGraphInspectorViewProvider @@ -40,6 +41,9 @@ export class WebviewViewProviderManager { }, { constructor: RokuDeviceViewViewProvider, provider: undefined as RokuDeviceViewViewProvider + }, { + constructor: RokuAutomationViewViewProvider, + provider: undefined as RokuAutomationViewViewProvider }]; public getWebviewViewProviders() { @@ -54,6 +58,10 @@ export class WebviewViewProviderManager { public onChannelPublishedEvent(e: ChannelPublishedEvent) { const config = e.body.launchConfiguration as BrightScriptLaunchConfiguration; this.rtaManager.setupRtaWithConfig(config); + + for (const webview of this.webviewViews) { + void webview.provider.onChannelPublishedEvent(e); + } } // Mainly for communicating between webviews diff --git a/src/viewProviders/BaseRdbViewProvider.ts b/src/viewProviders/BaseRdbViewProvider.ts index c2146c2b..0b8a804b 100644 --- a/src/viewProviders/BaseRdbViewProvider.ts +++ b/src/viewProviders/BaseRdbViewProvider.ts @@ -11,78 +11,50 @@ import { ViewProviderCommand } from './ViewProviderCommand'; export abstract class BaseRdbViewProvider extends BaseWebviewViewProvider { - protected rtaManager?: RtaManager; - protected odcCommands: Array; - constructor(context: vscode.ExtensionContext) { - super(context); + constructor(context: vscode.ExtensionContext, dependencies) { + super(context, dependencies); const requestTypesPath = path.join(rta.utils.getClientFilesPath(), 'requestTypes.schema.json'); const json = JSON.parse(fsExtra.readFileSync(requestTypesPath, 'utf8')); this.odcCommands = Object.values(json.enum); - } - public setRtaManager(rtaManager?: RtaManager) { - this.rtaManager = rtaManager; + this.setupCommandObservers(); } public updateDeviceAvailability() { - this.postOrQueueMessage({ - event: ViewProviderEvent.onDeviceAvailabilityChange, - odcAvailable: !!this.rtaManager.onDeviceComponent, - deviceAvailable: !!this.rtaManager.device + const message = this.createEventMessage(ViewProviderEvent.onDeviceAvailabilityChange, { + odcAvailable: !!this.dependencies.rtaManager.onDeviceComponent, + deviceAvailable: !!this.dependencies.rtaManager.device }); - } - protected onViewReady() { - // Always post back the device status so we make sure the client doesn't miss it if it got refreshed - this.updateDeviceAvailability(); + this.postOrQueueMessage(message); } - protected async handleViewMessage(message) { - const { command, context } = message; - if (this.odcCommands.includes(command)) { - const response = await this.rtaManager.sendOdcRequest(this.id, command, context); - this.postOrQueueMessage({ - ...message, - response: response - }); - return true; - } else if (command === ViewProviderCommand.getStoredNodeReferences) { - const response = this.rtaManager.getStoredNodeReferences(); - this.postOrQueueMessage({ - ...message, - response: response + protected setupCommandObservers() { + for (const command of this.odcCommands) { + this.addMessageCommandCallback(command, async (message) => { + const { command, context } = message; + const response = await this.dependencies.rtaManager.sendOdcRequest(this.id, command, context); + this.postOrQueueMessage({ + ...message, + response: response + }); + return true; }); + } - return true; - } else if (command === ViewProviderCommand.setManualIpAddress) { - this.rtaManager.setupRtaWithConfig({ + this.addMessageCommandCallback(ViewProviderCommand.setManualIpAddress, (message) => { + this.dependencies.rtaManager.setupRtaWithConfig({ ...message.context, injectRdbOnDeviceComponent: true }); - return true; - } else if (command === ViewProviderCommand.getScreenshot) { - try { - const result = await this.rtaManager.device.getScreenshot(); - this.postOrQueueMessage({ - ...message, - response: { - success: true, - arrayBuffer: result.buffer.buffer - } - }); - } catch (e) { - this.postOrQueueMessage({ - ...message, - response: { - success: false - } - }); - } - return true; - } + return Promise.resolve(true); + }); + } - return false; + protected onViewReady() { + // Always post back the device status so we make sure the client doesn't miss it if it got refreshed + this.updateDeviceAvailability(); } } diff --git a/src/viewProviders/BaseWebviewViewProvider.ts b/src/viewProviders/BaseWebviewViewProvider.ts index e7d47dd6..2af88ef3 100644 --- a/src/viewProviders/BaseWebviewViewProvider.ts +++ b/src/viewProviders/BaseWebviewViewProvider.ts @@ -2,16 +2,26 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as fsExtra from 'fs-extra'; import type { AsyncSubscription, Event } from '@parcel/watcher'; +import type { ChannelPublishedEvent } from 'roku-debug'; import { vscodeContextManager } from '../managers/VscodeContextManager'; -import { util } from '../util'; import type { WebviewViewProviderManager } from '../managers/WebviewViewProviderManager'; import { ViewProviderEvent } from './ViewProviderEvent'; import { ViewProviderCommand } from './ViewProviderCommand'; +import type { VscodeCommand } from '../commands/VscodeCommand'; +import type { RtaManager } from '../managers/RtaManager'; +import type { BrightScriptCommands } from '../BrightScriptCommands'; export abstract class BaseWebviewViewProvider implements vscode.WebviewViewProvider, vscode.Disposable { - constructor(context: vscode.ExtensionContext) { - this.webviewBasePath = path.join(context.extensionPath, 'dist', 'webviews'); - context.subscriptions.push(this); + constructor( + protected extensionContext: vscode.ExtensionContext, + protected dependencies: { + rtaManager: RtaManager; + brightscriptCommands: BrightScriptCommands; + } + ) { + this.webviewBasePath = path.join(extensionContext.extensionPath, 'dist', 'webviews'); + extensionContext.subscriptions.push(this); + this.extensionContext = extensionContext; } /** @@ -26,6 +36,7 @@ export abstract class BaseWebviewViewProvider implements vscode.WebviewViewProvi private viewReady = false; private queuedMessages = []; private webviewViewProviderManager: WebviewViewProviderManager; + private messageCommandCallbacks = {} as Record Promise>; public dispose() { void this.outDirWatcher?.unsubscribe(); @@ -35,6 +46,26 @@ export abstract class BaseWebviewViewProvider implements vscode.WebviewViewProvi this.webviewViewProviderManager = manager; } + public onChannelPublishedEvent(e: ChannelPublishedEvent) { + // Can be overwritten in a child to notify on channel publish + } + + public createCommandMessage(command: VscodeCommand | ViewProviderCommand, context = {}) { + const message = { + command: command, + context: context + }; + return message; + } + + public createEventMessage(event: ViewProviderEvent, context = {}) { + const message = { + event: event, + context: context + }; + return message; + } + public postOrQueueMessage(message) { if (this.viewReady) { this.postMessage(message); @@ -55,6 +86,10 @@ export abstract class BaseWebviewViewProvider implements vscode.WebviewViewProvi } } + protected addMessageCommandCallback(command: string, callback: (message) => Promise) { + this.messageCommandCallbacks[command] = callback; + } + private setupViewMessageObserver(webview: vscode.Webview) { webview.onDidReceiveMessage(async (message) => { try { @@ -71,7 +106,8 @@ export abstract class BaseWebviewViewProvider implements vscode.WebviewViewProvi const context = message.context; this.webviewViewProviderManager.sendMessageToWebviews(context.viewIds, context.message); } else { - if (!await this.handleViewMessage(message)) { + const callback = this.messageCommandCallbacks[command]; + if (!callback || !await callback(message)) { console.warn('Did not handle message', message); } } @@ -87,16 +123,13 @@ export abstract class BaseWebviewViewProvider implements vscode.WebviewViewProvi }); } - protected handleViewMessage(message): Promise | boolean { - return false; - } - protected registerCommandWithWebViewNotifier(context: vscode.ExtensionContext, command: string) { context.subscriptions.push(vscode.commands.registerCommand(command, () => { - this.postOrQueueMessage({ - event: ViewProviderEvent.onVscodeCommandReceived, + const message = this.createEventMessage(ViewProviderEvent.onVscodeCommandReceived, { commandName: command }); + + this.postOrQueueMessage(message); })); } diff --git a/src/viewProviders/RokuAutomationViewViewProvider.ts b/src/viewProviders/RokuAutomationViewViewProvider.ts new file mode 100644 index 00000000..72afd8b0 --- /dev/null +++ b/src/viewProviders/RokuAutomationViewViewProvider.ts @@ -0,0 +1,142 @@ +import * as vscode from 'vscode'; +import type { ChannelPublishedEvent } from 'roku-debug'; +import { utils, ecp } from 'roku-test-automation'; +import { vscodeContextManager } from '../managers/VscodeContextManager'; +import type { BrightScriptCommands } from '../BrightScriptCommands'; +import { VscodeCommand } from '../commands/VscodeCommand'; +import { BaseRdbViewProvider } from './BaseRdbViewProvider'; +import { ViewProviderId } from './ViewProviderId'; +import { ViewProviderCommand } from './ViewProviderCommand'; +import { ViewProviderEvent } from './ViewProviderEvent'; + +export class RokuAutomationViewViewProvider extends BaseRdbViewProvider { + public readonly id = ViewProviderId.rokuAutomationView; + + constructor(context: vscode.ExtensionContext, dependencies) { + super(context, dependencies); + + this.addMessageCommandCallback(ViewProviderCommand.storeRokuAutomationConfigs, async (message) => { + this.rokuAutomationConfigs = message.context.configs; + this.rokuAutomationAutorunOnDeploy = message.context.autorunOnDeploy; + // Make sure to use JSON.stringify or weird stuff happens + await context.workspaceState.update(this.configStorageKey, JSON.stringify(message.context)); + return true; + }); + + this.addMessageCommandCallback(ViewProviderCommand.runRokuAutomationConfig, async (message) => { + const index = message.context.configIndex; + await this.runRokuAutomationConfig(index); + return true; + }); + + const brightScriptCommands = dependencies.brightScriptCommands as BrightScriptCommands; + brightScriptCommands.registerKeypressNotifier((key, literalCharacter) => { + if (this.isRecording) { + const message = this.createEventMessage(ViewProviderEvent.onRokuAutomationKeyPressed, { + key: key, + literalCharacter: literalCharacter + }); + + this.postOrQueueMessage(message); + } + }); + + const subscriptions = context.subscriptions; + + subscriptions.push(vscode.commands.registerCommand(VscodeCommand.rokuAutomationStartRecording, async () => { + // TODO decide how to handle initiator + await vscode.commands.executeCommand('extension.brightscript.enableRemoteControlMode'); + await this.setIsRecording(true); + })); + + subscriptions.push(vscode.commands.registerCommand(VscodeCommand.rokuAutomationStopRecording, async () => { + await vscode.commands.executeCommand('extension.brightscript.disableRemoteControlMode'); + await this.setIsRecording(false); + })); + } + + private async setIsRecording(isRecording) { + this.isRecording = isRecording; + // TODO decide how to handle initiator + await vscodeContextManager.set('brightscript.rokuAutomationView.isRecording', isRecording); + } + + private isRecording = false; + private configStorageKey = 'rokuAutomationConfigs'; + private rokuAutomationConfigs: { + name: string; + steps: { + type: string; + value: string; + }[]; + }[]; + private rokuAutomationAutorunOnDeploy = true; + + private currentRunningStep = -1; + + public async runRokuAutomationConfig(index) { + let stopRunning = false; + this.addMessageCommandCallback(ViewProviderCommand.stopRokuAutomationConfig, (message) => { + stopRunning = true; + return Promise.resolve(true); + }); + + const config = this.rokuAutomationConfigs[index]; + for (const [index, step] of config.steps.entries()) { + if (stopRunning) { + break; + } + + this.updateCurrentRunningStep(index); + switch (step.type) { + case 'sleep': + await utils.sleep(+step.value * 1000); + break; + case 'sendText': + await ecp.sendText(step.value); + break; + case 'sendKeyPress': + await ecp.sendKeyPress(step.value as any); + break; + } + } + + // Let the view know we're done running + this.updateCurrentRunningStep(-1); + } + + public onChannelPublishedEvent(e: ChannelPublishedEvent) { + if (this.rokuAutomationAutorunOnDeploy) { + return this.runRokuAutomationConfig(0); + } + } + + protected updateCurrentRunningStep(step = this.currentRunningStep) { + const message = this.createEventMessage(ViewProviderEvent.onRokuAutomationConfigStepChange, { + step: step + }); + + this.postOrQueueMessage(message); + } + + protected onViewReady() { + // Always post back the device status so we make sure the client doesn't miss it if it got refreshed + this.updateDeviceAvailability(); + + const json = this.extensionContext.workspaceState.get(this.configStorageKey); + if (typeof json === 'string') { + const result = JSON.parse(json); + this.rokuAutomationConfigs = result.configs; + this.rokuAutomationAutorunOnDeploy = result.autorunOnDeploy; + } + + const message = this.createEventMessage(ViewProviderEvent.onRokuAutomationConfigsLoaded, { + configs: this.rokuAutomationConfigs, + autorunOnDeploy: this.rokuAutomationAutorunOnDeploy + }); + + this.postOrQueueMessage(message); + + this.updateCurrentRunningStep(); + } +} diff --git a/src/viewProviders/RokuCommandsViewProvider.spec.ts b/src/viewProviders/RokuCommandsViewProvider.spec.ts index c8c2f79b..57667a06 100644 --- a/src/viewProviders/RokuCommandsViewProvider.spec.ts +++ b/src/viewProviders/RokuCommandsViewProvider.spec.ts @@ -29,7 +29,7 @@ beforeEach(() => { show: () => { } }; - provider = new RokuCommandsViewProvider(vscode.context) as any; + provider = new RokuCommandsViewProvider(vscode.context, {}) as any; }); afterEach(() => { provider.dispose(); @@ -51,12 +51,13 @@ describe('RokuCommandsViewProvider', () => { await provider['resolveWebviewView'](view, {} as any, {} as any); expect(typeof callback).to.equal('function'); - const spy = sinon.spy(provider as any, 'handleViewMessage'); + const fake = sinonImport.fake.returns(Promise.resolve(true)); + provider['addMessageCommandCallback']('importRegistry', fake); callback({ command: 'importRegistry', context: {} }); - expect(spy.calledOnce).to.be.true; + expect(fake.calledOnce).to.be.true; }); }); }); diff --git a/src/viewProviders/RokuDeviceViewViewProvider.ts b/src/viewProviders/RokuDeviceViewViewProvider.ts index 9cab6128..4b6e6332 100644 --- a/src/viewProviders/RokuDeviceViewViewProvider.ts +++ b/src/viewProviders/RokuDeviceViewViewProvider.ts @@ -2,17 +2,39 @@ import type * as vscode from 'vscode'; import { VscodeCommand } from '../commands/VscodeCommand'; import { BaseRdbViewProvider } from './BaseRdbViewProvider'; import { ViewProviderId } from './ViewProviderId'; +import { ViewProviderCommand } from './ViewProviderCommand'; export class RokuDeviceViewViewProvider extends BaseRdbViewProvider { public readonly id = ViewProviderId.rokuDeviceView; - constructor(context: vscode.ExtensionContext) { - super(context); + constructor(context: vscode.ExtensionContext, dependencies) { + super(context, dependencies); this.registerCommandWithWebViewNotifier(context, VscodeCommand.rokuDeviceViewEnableNodeInspector); this.registerCommandWithWebViewNotifier(context, VscodeCommand.rokuDeviceViewDisableNodeInspector); this.registerCommandWithWebViewNotifier(context, VscodeCommand.rokuDeviceViewRefreshScreenshot); this.registerCommandWithWebViewNotifier(context, VscodeCommand.rokuDeviceViewPauseScreenshotCapture); this.registerCommandWithWebViewNotifier(context, VscodeCommand.rokuDeviceViewResumeScreenshotCapture); + + this.addMessageCommandCallback(ViewProviderCommand.getScreenshot, async (message) => { + try { + const result = await this.dependencies.rtaManager.device.getScreenshot(); + this.postOrQueueMessage({ + ...message, + response: { + success: true, + arrayBuffer: result.buffer.buffer + } + }); + } catch (e) { + this.postOrQueueMessage({ + ...message, + response: { + success: false + } + }); + } + return true; + }); } } diff --git a/src/viewProviders/RokuRegistryViewProvider.spec.ts b/src/viewProviders/RokuRegistryViewProvider.spec.ts index 7f093de8..48db4ad3 100644 --- a/src/viewProviders/RokuRegistryViewProvider.spec.ts +++ b/src/viewProviders/RokuRegistryViewProvider.spec.ts @@ -35,14 +35,15 @@ afterEach(() => { }); describe('RokuRegistryViewProvider', () => { - const provider = new RokuRegistryViewProvider(vscode.context); const rtaManager = new RtaManager(); - provider.setRtaManager(rtaManager); + const provider = new RokuRegistryViewProvider(vscode.context, { + rtaManager: rtaManager + }); describe('sendRegistryUpdated', () => { - it('Triggers postOrQueueMessage to send message to web view that the registry was updated', async () => { + it('Triggers postOrQueueMessage to send message to web view that the registry was updated', () => { const spy = sinon.stub(provider as any, 'postOrQueueMessage'); - await provider['sendRegistryUpdated'](); + provider['sendRegistryUpdated'](); expect(spy.calledOnce).to.be.true; }); }); diff --git a/src/viewProviders/RokuRegistryViewProvider.ts b/src/viewProviders/RokuRegistryViewProvider.ts index 187b31e3..5568c3d4 100644 --- a/src/viewProviders/RokuRegistryViewProvider.ts +++ b/src/viewProviders/RokuRegistryViewProvider.ts @@ -7,14 +7,14 @@ import { ViewProviderId } from './ViewProviderId'; export class RokuRegistryViewProvider extends BaseRdbViewProvider { public readonly id = ViewProviderId.rokuRegistryView; - constructor(context: vscode.ExtensionContext) { - super(context); + constructor(context: vscode.ExtensionContext, dependencies) { + super(context, dependencies); const subscriptions = context.subscriptions; subscriptions.push(vscode.commands.registerCommand(VscodeCommand.rokuRegistryExportRegistry, async () => { await vscode.window.showSaveDialog({ saveLabel: 'Save' }).then(async (uri) => { - const result = await this.rtaManager.onDeviceComponent?.readRegistry(); + const result = await this.dependencies.rtaManager.onDeviceComponent?.readRegistry(); await vscode.workspace.fs.writeFile(uri, Buffer.from(JSON.stringify(result?.values), 'utf8')); }); })); @@ -30,12 +30,12 @@ export class RokuRegistryViewProvider extends BaseRdbViewProvider { })); subscriptions.push(vscode.commands.registerCommand(VscodeCommand.rokuRegistryClearRegistry, async () => { - await this.rtaManager.onDeviceComponent.deleteEntireRegistry(); - await this.sendRegistryUpdated(); + await this.dependencies.rtaManager.onDeviceComponent.deleteEntireRegistry(); + this.sendRegistryUpdated(); })); - subscriptions.push(vscode.commands.registerCommand(VscodeCommand.rokuRegistryRefreshRegistry, async () => { - await this.sendRegistryUpdated(); + subscriptions.push(vscode.commands.registerCommand(VscodeCommand.rokuRegistryRefreshRegistry, () => { + this.sendRegistryUpdated(); })); } @@ -44,15 +44,14 @@ export class RokuRegistryViewProvider extends BaseRdbViewProvider { const input = await vscode.workspace.fs.readFile(uri[0]); const data = JSON.parse(Buffer.from(input).toString('utf8')); - await this.rtaManager.onDeviceComponent?.writeRegistry({ + await this.dependencies.rtaManager.onDeviceComponent?.writeRegistry({ values: data }); - await this.sendRegistryUpdated(); + this.sendRegistryUpdated(); } } - protected async sendRegistryUpdated() { - const result = await this.rtaManager.onDeviceComponent?.readRegistry(); - this.postOrQueueMessage({ name: ViewProviderEvent.onRegistryUpdated, values: result?.values }); + protected sendRegistryUpdated() { + this.postOrQueueMessage(this.createEventMessage(ViewProviderEvent.onRegistryUpdated)); } } diff --git a/src/viewProviders/SceneGraphInspectorViewProvider.ts b/src/viewProviders/SceneGraphInspectorViewProvider.ts index 53ef47de..73485d8c 100644 --- a/src/viewProviders/SceneGraphInspectorViewProvider.ts +++ b/src/viewProviders/SceneGraphInspectorViewProvider.ts @@ -1,13 +1,23 @@ import type * as vscode from 'vscode'; import { BaseRdbViewProvider } from './BaseRdbViewProvider'; import { ViewProviderId } from './ViewProviderId'; +import { ViewProviderCommand } from './ViewProviderCommand'; export class SceneGraphInspectorViewProvider extends BaseRdbViewProvider { public readonly id = ViewProviderId.sceneGraphInspectorView; - constructor(context: vscode.ExtensionContext) { - super(context); + constructor(context: vscode.ExtensionContext, dependencies) { + super(context, dependencies); this.registerCommandWithWebViewNotifier(context, 'extension.brightscript.sceneGraphInspectorView.refreshNodeTree'); + + this.addMessageCommandCallback(ViewProviderCommand.getStoredNodeReferences, (message) => { + const response = this.dependencies.rtaManager.getStoredNodeReferences(); + this.postOrQueueMessage({ + ...message, + response: response + }); + return Promise.resolve(true); + }); } } diff --git a/src/viewProviders/ViewProviderCommand.ts b/src/viewProviders/ViewProviderCommand.ts index 329d3e08..65621e3b 100644 --- a/src/viewProviders/ViewProviderCommand.ts +++ b/src/viewProviders/ViewProviderCommand.ts @@ -4,5 +4,8 @@ export enum ViewProviderCommand { getScreenshot = 'getScreenshot', setManualIpAddress = 'setManualIpAddress', sendMessageToWebviews = 'sendMessageToWebviews', - setVscodeContext = 'setVscodeContext' + setVscodeContext = 'setVscodeContext', + storeRokuAutomationConfigs = 'storeRokuAutomationConfigs', + runRokuAutomationConfig = 'runRokuAutomationConfig', + stopRokuAutomationConfig = 'stopRokuAutomationConfig' } diff --git a/src/viewProviders/ViewProviderEvent.ts b/src/viewProviders/ViewProviderEvent.ts index b761cced..1a475902 100644 --- a/src/viewProviders/ViewProviderEvent.ts +++ b/src/viewProviders/ViewProviderEvent.ts @@ -3,5 +3,8 @@ export enum ViewProviderEvent { onDeviceAvailabilityChange = 'onDeviceAvailabilityChange', onVscodeCommandReceived = 'onVscodeCommandReceived', onRegistryUpdated = 'onRegistryUpdated', - onStoredNodeReferencesUpdated = 'onStoredNodeReferencesUpdated' + onStoredNodeReferencesUpdated = 'onStoredNodeReferencesUpdated', + onRokuAutomationConfigsLoaded = 'onRokuAutomationConfigsLoaded', + onRokuAutomationConfigStepChange = 'onRokuAutomationConfigStepChange', + onRokuAutomationKeyPressed = 'onRokuAutomationKeyPressed' } diff --git a/src/viewProviders/ViewProviderId.ts b/src/viewProviders/ViewProviderId.ts index a5d76c39..4a4ab12f 100644 --- a/src/viewProviders/ViewProviderId.ts +++ b/src/viewProviders/ViewProviderId.ts @@ -4,5 +4,6 @@ export enum ViewProviderId { sceneGraphInspectorView = 'sceneGraphInspectorView', rokuDeviceView = 'rokuDeviceView', rokuRegistryView = 'rokuRegistryView', - rokuCommandsView = 'rokuCommandsView' + rokuCommandsView = 'rokuCommandsView', + rokuAutomationView = 'rokuAutomationView' } diff --git a/webviews/package-lock.json b/webviews/package-lock.json index 749527c0..bb537a99 100644 --- a/webviews/package-lock.json +++ b/webviews/package-lock.json @@ -10,7 +10,7 @@ "devDependencies": { "@sveltejs/vite-plugin-svelte": "^2.0.0", "@tsconfig/svelte": "^3.0.0", - "@vscode/webview-ui-toolkit": "^1.0.0", + "@vscode/webview-ui-toolkit": "^1.2.2", "svelte": "^3.54.0", "svelte-check": "^2.10.0", "svelte-codicons": "^0.10.1", diff --git a/webviews/package.json b/webviews/package.json index 1ddf5522..d24ae45e 100644 --- a/webviews/package.json +++ b/webviews/package.json @@ -11,7 +11,7 @@ "devDependencies": { "@sveltejs/vite-plugin-svelte": "^2.0.0", "@tsconfig/svelte": "^3.0.0", - "@vscode/webview-ui-toolkit": "^1.0.0", + "@vscode/webview-ui-toolkit": "^1.2.2", "svelte": "^3.54.0", "svelte-check": "^2.10.0", "svelte-codicons": "^0.10.1", diff --git a/webviews/src/main.ts b/webviews/src/main.ts index 9e35a81f..ce404989 100644 --- a/webviews/src/main.ts +++ b/webviews/src/main.ts @@ -3,12 +3,20 @@ import rokuRegistryView from './views/RokuRegistryView/RokuRegistryView.svelte'; import rokuCommandsView from './views/RokuCommandsView/RokuCommandsView.svelte'; import rokuDeviceView from './views/RokuDeviceView/RokuDeviceView.svelte'; import sceneGraphInspectorView from './views/SceneGraphInspectorView/SceneGraphInspectorView.svelte'; +import rokuAutomationView from './views/RokuAutomationView/RokuAutomationView.svelte'; +import { provideVSCodeDesignSystem, allComponents } from '@vscode/webview-ui-toolkit'; + import './style.css'; //write toolkit to window this to prevent svelte from tree-shaking it import * as toolkit from '@vscode/webview-ui-toolkit/dist/toolkit'; (window as any).___toolkit = toolkit; +// In order to use the Webview UI Toolkit web components they +// must be registered with the browser (i.e. webview) using the +// syntax below. +provideVSCodeDesignSystem().register(allComponents); + // Provided by ViewProviders declare const viewName; @@ -17,7 +25,8 @@ const views = { rokuRegistryView, rokuCommandsView, rokuDeviceView, - sceneGraphInspectorView + sceneGraphInspectorView, + rokuAutomationView }; const app = new views[viewName]({ diff --git a/webviews/src/views/RokuAutomationView/RokuAutomationView.svelte b/webviews/src/views/RokuAutomationView/RokuAutomationView.svelte new file mode 100644 index 00000000..fe41003d --- /dev/null +++ b/webviews/src/views/RokuAutomationView/RokuAutomationView.svelte @@ -0,0 +1,324 @@ + + + + + + + +
+ + {#each steps as step, index} + dragstart(event, index)} + on:drop={drop} + on:dragover="{false}" + on:dragenter={() => hoveringIndex = index} + class:highlighted={hoveringIndex === index}> + + + + + + + {/each} + + + + + + + +
+ + + + {#each Object.entries(stepTypes) as [stepType, stepTypeParams]} + {stepTypeParams.name} + {/each} + + + {#if step.type === stepTypes.sleep.type} + + {:else if step.type === stepTypes.sendKeyPress.type} + + {#each Object.entries(availableKeys) as [key, text]} + {text} + {/each} + + {:else if step.type === stepTypes.sendText.type} + + {/if} + + {#if currentRunningStep === -1} + + {:else if currentRunningStep === index} + + {/if} +
+ + + + Autorun on deploy +
+ {#if currentRunningStep >= 0} + Stop + {:else} + Run + {/if} +
+ +
+ {#each list as n, index (n.name)} +
dragstart(event, index)} + on:drop|preventDefault={event => drop(event, index)} + on:dragenter={() => hoveringIndex = index} + class:is-active={hoveringIndex === index}> + {n.name} +
+ {/each} +
+ +
diff --git a/webviews/src/views/RokuCommandsView/RokuCommandsView.svelte b/webviews/src/views/RokuCommandsView/RokuCommandsView.svelte index 2bcdbd37..2c387a4e 100644 --- a/webviews/src/views/RokuCommandsView/RokuCommandsView.svelte +++ b/webviews/src/views/RokuCommandsView/RokuCommandsView.svelte @@ -92,7 +92,7 @@ let odcAvailable = true; intermediary.observeEvent(ViewProviderEvent.onDeviceAvailabilityChange, (message) => { - odcAvailable = message.odcAvailable; + odcAvailable = message.context.odcAvailable; }); // Required by any view so we can know that the view is ready to receive messages diff --git a/webviews/src/views/RokuDeviceView/RokuDeviceView.svelte b/webviews/src/views/RokuDeviceView/RokuDeviceView.svelte index ca7ceecd..9ee329ac 100644 --- a/webviews/src/views/RokuDeviceView/RokuDeviceView.svelte +++ b/webviews/src/views/RokuDeviceView/RokuDeviceView.svelte @@ -15,7 +15,7 @@ let deviceAvailable = false; intermediary.observeEvent(ViewProviderEvent.onDeviceAvailabilityChange, (message) => { - deviceAvailable = message.deviceAvailable; + deviceAvailable = message.context.deviceAvailable; requestScreenshot(); }); @@ -79,16 +79,18 @@ ref: focusedTreeNode.ref } - intermediary.sendMessageToWebviews(ViewProviderId.sceneGraphInspectorView, { - event: ViewProviderEvent.onTreeNodeFocused, + const message = intermediary.createEventMessage(ViewProviderEvent.onTreeNodeFocused, { treeNode: treeNode }); + + intermediary.sendMessageToWebviews(ViewProviderId.sceneGraphInspectorView, message); } } else { - intermediary.sendMessageToWebviews(ViewProviderId.sceneGraphInspectorView, { - event: ViewProviderEvent.onTreeNodeFocused, + const message = intermediary.createEventMessage(ViewProviderEvent.onTreeNodeFocused, { treeNode: null }); + + intermediary.sendMessageToWebviews(ViewProviderId.sceneGraphInspectorView, message); } } } @@ -183,7 +185,7 @@ } intermediary.observeEvent(ViewProviderEvent.onVscodeCommandReceived, async (message) => { - const name = message.commandName; + const name = message.context.commandName; if (name === VscodeCommand.rokuDeviceViewEnableNodeInspector) { wasRunningScreenshotCaptureBeforeInspect = enableScreenshotCapture; isInspectingNodes = true; diff --git a/webviews/src/views/RokuRegistryView/RokuRegistryView.svelte b/webviews/src/views/RokuRegistryView/RokuRegistryView.svelte index 2853c811..d00fc0ae 100644 --- a/webviews/src/views/RokuRegistryView/RokuRegistryView.svelte +++ b/webviews/src/views/RokuRegistryView/RokuRegistryView.svelte @@ -14,7 +14,7 @@ let odcAvailable = false; intermediary.observeEvent(ViewProviderEvent.onDeviceAvailabilityChange, async (message) => { - odcAvailable = message.odcAvailable; + odcAvailable = message.context.odcAvailable; if (odcAvailable) { loading = true; const { values } = await odc.readRegistry(); @@ -25,8 +25,11 @@ } }); - intermediary.observeEvent(ViewProviderEvent.onRegistryUpdated, (message) => { - registryValues = registryView.formatValues(message.values); + intermediary.observeEvent(ViewProviderEvent.onRegistryUpdated, async (message) => { + loading = true; + const { values } = await odc.readRegistry(); + registryValues = registryView.formatValues(values); + loading = false; }); // Required by any view so we can know that the view is ready to receive messages diff --git a/webviews/src/views/SceneGraphInspectorView/SceneGraphInspectorView.svelte b/webviews/src/views/SceneGraphInspectorView/SceneGraphInspectorView.svelte index d96a81ee..47e618b1 100644 --- a/webviews/src/views/SceneGraphInspectorView/SceneGraphInspectorView.svelte +++ b/webviews/src/views/SceneGraphInspectorView/SceneGraphInspectorView.svelte @@ -104,7 +104,7 @@ let odcAvailable = false; intermediary.observeEvent(ViewProviderEvent.onDeviceAvailabilityChange, (message) => { - odcAvailable = message.odcAvailable; + odcAvailable = message.context.odcAvailable; if (odcAvailable) { refresh(); } else { @@ -132,8 +132,8 @@ intermediary.observeEvent(ViewProviderEvent.onTreeNodeFocused, (message) => { focusedNode = -1; - if (message.treeNode) { - focusedNode = message.treeNode.ref; + if (message.context.treeNode) { + focusedNode = message.context.treeNode.ref; } }); From 670537141ce680fc1eaa15a7533068e7ffdd4888 Mon Sep 17 00:00:00 2001 From: Brian Leighty Date: Mon, 7 Aug 2023 15:36:57 -0400 Subject: [PATCH 02/21] finish up roku automation panel --- package-lock.json | 14 +-- package.json | 29 ++++- src/commands/VscodeCommand.ts | 4 +- src/managers/RemoteControlManager.ts | 6 + .../RokuAutomationViewViewProvider.ts | 21 +++- .../RokuAutomationView.svelte | 113 +++++++----------- 6 files changed, 100 insertions(+), 87 deletions(-) diff --git a/package-lock.json b/package-lock.json index 035d2af0..520b22aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "pretty-bytes": "^5.6.0", "roku-debug": "^0.19.1", "roku-deploy": "^3.10.2", - "roku-test-automation": "^2.0.0-beta.19", + "roku-test-automation": "^2.0.0-beta.20", "semver": "^7.1.3", "source-map": "^0.7.3", "thenby": "^1.3.4", @@ -9124,9 +9124,9 @@ } }, "node_modules/roku-test-automation": { - "version": "2.0.0-beta.19", - "resolved": "https://registry.npmjs.org/roku-test-automation/-/roku-test-automation-2.0.0-beta.19.tgz", - "integrity": "sha512-+3h2rv2dzSbicbTReD/ptSgdzYV8m9F5gZWYnyanPneEmzwvPMEnISpmQjirytON6jP5+8V8qfaFGGlbg/Mkgw==", + "version": "2.0.0-beta.20", + "resolved": "https://registry.npmjs.org/roku-test-automation/-/roku-test-automation-2.0.0-beta.20.tgz", + "integrity": "sha512-k0LFT4D010aunJvXmNEr09TM2QcRt9fpAQrUxIICWuK5NKX1cVizy++Z3HzWVMoVyMvJv2MOLy7OE/umf4ZAHg==", "dependencies": { "@suitest/types": "^4.6.0", "ajv": "^6.12.6", @@ -18522,9 +18522,9 @@ } }, "roku-test-automation": { - "version": "2.0.0-beta.19", - "resolved": "https://registry.npmjs.org/roku-test-automation/-/roku-test-automation-2.0.0-beta.19.tgz", - "integrity": "sha512-+3h2rv2dzSbicbTReD/ptSgdzYV8m9F5gZWYnyanPneEmzwvPMEnISpmQjirytON6jP5+8V8qfaFGGlbg/Mkgw==", + "version": "2.0.0-beta.20", + "resolved": "https://registry.npmjs.org/roku-test-automation/-/roku-test-automation-2.0.0-beta.20.tgz", + "integrity": "sha512-k0LFT4D010aunJvXmNEr09TM2QcRt9fpAQrUxIICWuK5NKX1cVizy++Z3HzWVMoVyMvJv2MOLy7OE/umf4ZAHg==", "requires": { "@suitest/types": "^4.6.0", "ajv": "^6.12.6", diff --git a/package.json b/package.json index 3760fc72..3ed62b2f 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "pretty-bytes": "^5.6.0", "roku-debug": "^0.19.1", "roku-deploy": "^3.10.2", - "roku-test-automation": "^2.0.0-beta.19", + "roku-test-automation": "^2.0.0-beta.20", "semver": "^7.1.3", "source-map": "^0.7.3", "thenby": "^1.3.4", @@ -213,6 +213,11 @@ "id": "rokuCommandsView", "name": "Roku Commands", "type": "webview" + }, + { + "id": "rokuAutomationView", + "name": "Roku Automation", + "type": "webview" } ] }, @@ -289,6 +294,16 @@ "command": "extension.brightscript.rokuDeviceView.disableNodeInspector", "when": "view == rokuDeviceView && brightscript.isOnDeviceComponentAvailable && brightscript.rokuDeviceView.isInspectingNodes", "group": "navigation@3" + }, + { + "command": "extension.brightscript.rokuAutomation.startRecording", + "when": "view == rokuAutomationView && !brightscript.rokuAutomationView.isRecording", + "group": "navigation" + }, + { + "command": "extension.brightscript.rokuAutomation.stopRecording", + "when": "view == rokuAutomationView && brightscript.rokuAutomationView.isRecording", + "group": "navigation" } ], "commandPalette": [] @@ -2529,6 +2544,18 @@ "category": "BrighterScript", "icon": "$(arrow-up)" }, + { + "command": "extension.brightscript.rokuAutomation.startRecording", + "title": "Start Recording", + "category": "BrighterScript", + "icon": "$(record)" + }, + { + "command": "extension.brightscript.rokuAutomation.stopRecording", + "title": "Stop Recording", + "category": "BrighterScript", + "icon": "$(debug-stop)" + }, { "command": "extension.brightscript.languageServer.restart", "title": "Restart Language Server", diff --git a/src/commands/VscodeCommand.ts b/src/commands/VscodeCommand.ts index 9f1c5967..27b702f6 100644 --- a/src/commands/VscodeCommand.ts +++ b/src/commands/VscodeCommand.ts @@ -9,5 +9,7 @@ export enum VscodeCommand { rokuRegistryClearRegistry = 'extension.brightscript.rokuRegistry.clearRegistry', rokuRegistryRefreshRegistry = 'extension.brightscript.rokuRegistry.refreshRegistry', rokuAutomationStartRecording = 'extension.brightscript.rokuAutomation.startRecording', - rokuAutomationStopRecording = 'extension.brightscript.rokuAutomation.stopRecording' + rokuAutomationStopRecording = 'extension.brightscript.rokuAutomation.stopRecording', + enableRemoteControlMode = 'extension.brightscript.enableRemoteControlMode', + disableRemoteControlMode = 'extension.brightscript.disableRemoteControlMode' } diff --git a/src/managers/RemoteControlManager.ts b/src/managers/RemoteControlManager.ts index 04c764e3..756d37f4 100644 --- a/src/managers/RemoteControlManager.ts +++ b/src/managers/RemoteControlManager.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import { vscodeContextManager } from './VscodeContextManager'; import type { TelemetryManager } from './TelemetryManager'; +import { VscodeCommand } from '../commands/VscodeCommand'; export class RemoteControlManager { constructor( @@ -70,6 +71,11 @@ export class RemoteControlManager { } public async setRemoteControlMode(isEnabled: boolean, initiator: RemoteControlModeInitiator) { + if (this.isEnabled && !isEnabled) { + // Want to also stop Roku automation recording if it was running + await vscode.commands.executeCommand(VscodeCommand.rokuAutomationStopRecording); + } + //only send a telemetry event if we know who initiated the mode. `undefined` usually means our internal system set the value...so don't track that if (initiator) { this.telemetryManager.sendSetRemoteControlModeEvent(isEnabled, initiator); diff --git a/src/viewProviders/RokuAutomationViewViewProvider.ts b/src/viewProviders/RokuAutomationViewViewProvider.ts index 72afd8b0..8316c641 100644 --- a/src/viewProviders/RokuAutomationViewViewProvider.ts +++ b/src/viewProviders/RokuAutomationViewViewProvider.ts @@ -44,20 +44,29 @@ export class RokuAutomationViewViewProvider extends BaseRdbViewProvider { const subscriptions = context.subscriptions; subscriptions.push(vscode.commands.registerCommand(VscodeCommand.rokuAutomationStartRecording, async () => { - // TODO decide how to handle initiator - await vscode.commands.executeCommand('extension.brightscript.enableRemoteControlMode'); - await this.setIsRecording(true); + if (this.currentRunningStep === -1) { + // Only allow recording when we aren't currently running + await this.setIsRecording(true); + + // TODO decide how to handle initiator + await vscode.commands.executeCommand(VscodeCommand.enableRemoteControlMode); + + // We reset the current step to update the timestamp of the first sleep + this.updateCurrentRunningStep(-1); + } })); subscriptions.push(vscode.commands.registerCommand(VscodeCommand.rokuAutomationStopRecording, async () => { - await vscode.commands.executeCommand('extension.brightscript.disableRemoteControlMode'); - await this.setIsRecording(false); + if (this.isRecording) { + await this.setIsRecording(false); + // TODO decide how to handle initiator + await vscode.commands.executeCommand(VscodeCommand.disableRemoteControlMode); + } })); } private async setIsRecording(isRecording) { this.isRecording = isRecording; - // TODO decide how to handle initiator await vscodeContextManager.set('brightscript.rokuAutomationView.isRecording', isRecording); } diff --git a/webviews/src/views/RokuAutomationView/RokuAutomationView.svelte b/webviews/src/views/RokuAutomationView/RokuAutomationView.svelte index fe41003d..126d43fc 100644 --- a/webviews/src/views/RokuAutomationView/RokuAutomationView.svelte +++ b/webviews/src/views/RokuAutomationView/RokuAutomationView.svelte @@ -1,7 +1,7 @@ @@ -247,15 +223,13 @@ console.log('elapsedTime', elapsedTime);
{#each steps as step, index} - dragstart(event, index)} - on:drop={drop} - on:dragover="{false}" - on:dragenter={() => hoveringIndex = index} - class:highlighted={hoveringIndex === index}> - + + + + + - + + - {/each}
- +
+ {#if index > 0} + + + + {/if} @@ -264,6 +238,20 @@ console.log('elapsedTime', elapsedTime); {/each} + {#if currentRunningStep === -1} + + {:else if currentRunningStep === index} + + {/if} +
+ {#if index < steps.length - 1} + + {/if} + {#if step.type === stepTypes.sleep.type} @@ -277,19 +265,15 @@ console.log('elapsedTime', elapsedTime); {/if} - {#if currentRunningStep === -1} - - {:else if currentRunningStep === index} - - {/if} +
+
- @@ -299,26 +283,11 @@ console.log('elapsedTime', elapsedTime);
{#if currentRunningStep >= 0} - Stop + Stop {:else} - Run + Run {/if}
- -
- {#each list as n, index (n.name)} -
dragstart(event, index)} - on:drop|preventDefault={event => drop(event, index)} - on:dragenter={() => hoveringIndex = index} - class:is-active={hoveringIndex === index}> - {n.name} -
- {/each} -
-
From e2461288c995a0910fe32656e42d2830f446a7cf Mon Sep 17 00:00:00 2001 From: Brian Leighty Date: Tue, 22 Aug 2023 16:26:39 -0400 Subject: [PATCH 03/21] remove unneeded code --- .../RokuAutomationView/RokuAutomationView.svelte | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/webviews/src/views/RokuAutomationView/RokuAutomationView.svelte b/webviews/src/views/RokuAutomationView/RokuAutomationView.svelte index 126d43fc..53f09ac3 100644 --- a/webviews/src/views/RokuAutomationView/RokuAutomationView.svelte +++ b/webviews/src/views/RokuAutomationView/RokuAutomationView.svelte @@ -7,9 +7,6 @@ window.vscode = acquireVsCodeApi(); - let list = [{name: "foo", id: 0}, {name: "bar", id: 1}, - {name: "bob", id: 2}, {name: "jean", id: 3}]; - let loading = true; let currentRunningStep = -1; @@ -145,11 +142,8 @@ const configs = message.context.configs; if (configs) { const config = configs[0]; - console.log('config.steps', config.steps); steps = config.steps; autorunOnDeploy = message.context.autorunOnDeploy; - console.log('autorunOnDeploy', autorunOnDeploy); - } else { steps = [{ type: 'sleep', @@ -164,7 +158,7 @@ currentRunningStep = message.context.step; console.log('currentRunningStep', currentRunningStep); if (currentRunningStep === -1) { - // Once we finish running all current steps update our last step date in case we want to add any more steps + // Once we finish running all current steps, update our last step date in case we want to add any more steps lastStepDate = Date.now(); } }); @@ -175,7 +169,6 @@ // Round to the nearest tenth elapsedTime = (Math.round(elapsedTime * 10) / 10); -console.log('elapsedTime', elapsedTime); steps.push({ type: stepTypes.sleep.type, @@ -185,8 +178,6 @@ console.log('elapsedTime', elapsedTime); intermediary.observeEvent(ViewProviderEvent.onRokuAutomationKeyPressed, (message) => { let {key, literalCharacter} = message.context; - console.log('key', key); - console.log('literalCharacter', literalCharacter); if (literalCharacter) { // Check if we were typing somethign before and if so just add on to it const lastStep = steps.at(-1); From e2b8d64e42a12c04a437011a575fc48e7f072aea Mon Sep 17 00:00:00 2001 From: Brian Leighty Date: Tue, 29 Aug 2023 16:45:14 -0400 Subject: [PATCH 04/21] progress commit on new node detail page --- .../NodeDetailPage.svelte | 470 +++++++++--------- .../NumberField.svelte | 51 ++ .../SceneGraphInspectorView.svelte | 1 - 3 files changed, 296 insertions(+), 226 deletions(-) create mode 100644 webviews/src/views/SceneGraphInspectorView/NumberField.svelte diff --git a/webviews/src/views/SceneGraphInspectorView/NodeDetailPage.svelte b/webviews/src/views/SceneGraphInspectorView/NodeDetailPage.svelte index 9630d824..5ec90cf9 100644 --- a/webviews/src/views/SceneGraphInspectorView/NodeDetailPage.svelte +++ b/webviews/src/views/SceneGraphInspectorView/NodeDetailPage.svelte @@ -4,6 +4,7 @@ import { odc } from '../../ExtensionIntermediary'; import { utils } from '../../utils'; import ColorField from './ColorField.svelte'; + import NumberField from './NumberField.svelte'; import Chevron from '../../shared/Chevron.svelte'; import { Refresh, Discard, ArrowLeft, Move, Key } from 'svelte-codicons'; @@ -18,6 +19,8 @@ utils.setStorageValue('showKeyPathInfo', showKeyPathInfo); } + let expandedCollectionFields = {}; + function close() { if (autoRefreshInterval) { @@ -78,15 +81,19 @@ function onNumberFieldChange() { const value = Number(this.value); + debugger; handleResetValueButtonDisplay(this, value); setValue(this.id, value); } - function onVector2dFieldChange() { + function onVector2dFieldChange(tes) { + // TODO fix parent access + console.log('tes', tes); const id = this.id; const values = []; for (const element of this.parentElement.children) { if (element.id === id) { + debugger; values.push(Number(element.value)); } } @@ -136,7 +143,8 @@ } } } - for (const child of element.parentElement.children) { + + for (const child of element.parentElement.previousElementSibling.children) { if (child.classList.contains('resetValueButton')) { if (valueChanged) { child.classList.remove('hide'); @@ -151,7 +159,7 @@ function onResetValueButtonClicked() { const elements = []; // Find the elements we want to reset based on their relationship to the reset button - for (const child of this.parentElement.children) { + for (const child of this.parentElement.nextElementSibling.children) { if (child.classList.contains('fieldValue')) { elements.push(child); }; @@ -161,15 +169,17 @@ // If we got one then it's a standard value const element = elements[0] const id = element.id; - let eventType = 'input'; - if (element.type === 'checkbox') { - eventType = 'click'; + if (element.checked === true || element.checked === false) { element.checked = fields[id].value; + // If we dispatch the event like before the new value gets overwritten + onBooleanFieldClick.call(element); } else { element.value = fields[id].value; + // Have to manually trigger the observer + element.dispatchEvent(new Event('input')); } - // Have to manually trigger the observer - element.dispatchEvent(new Event(eventType)); + + } else if (elements.length === 2) { // If we got two then it's for vector2d field and so we have to update both inputs const xElement = elements[0]; @@ -247,7 +257,7 @@ } function toggleShowingBraceOrBracketContent() { - this.nextElementSibling.classList.toggle('hide'); + expandedCollectionFields[this.id] = !expandedCollectionFields[this.id]; } @@ -276,16 +286,6 @@ border-bottom: 2px solid rgb(190, 190, 190); } - button { - padding: 2px 5px; - font-size: 12px; - border: none; - margin: 0; - cursor: pointer; - outline: none; - display: inline-block; - } - #baseKeyPathContainer { padding: 3px 10px; border-bottom: 2px solid rgb(190, 190, 190); @@ -309,52 +309,45 @@ #nodeSubtype { user-select: text; + padding: 0 10px 0 5px; } - ul { + table { margin: 5px; - list-style: none; - padding: 0; } - li { - padding: 0 5px 10px; + td { + --input-min-width: 30px; + padding: 0 3px 5px; } - label { - font-weight: bold; - padding-right: 5px; + td:first-of-type { + text-align: right; } - input[type='number'] { - width: 50px; + label { + font-weight: bold; } - input[type='checkbox'] { - position: relative; - top: 2px; + .collectionItem { + background-color: var(--vscode-editor-background); } - .inline { + /* .inline { display: inline; width: auto; - } + } */ - .collectionItems { + /* .collectionItems { padding: 3px 0 3px 15px; display: block; } - .collectionItems .collectionItemId { + .collectionItem .collectionItemId { font-weight: bold; - } - - button { - cursor: pointer; - } + } */ - .openingBrace, - .openingBracket { + .braceOrBracket { cursor: pointer; user-select: none; } @@ -370,34 +363,34 @@
{#if showKeyPathInfo && inspectNodeTreeNode}
@@ -405,207 +398,234 @@ "keyPath": "{inspectNodeTreeNode.keyPath}"
{/if} - - - {#if children.length > 0} -
- - - -
children ({children.length})
-
-
- {#each children as child, i} -
{i}: -
- {/each} -
+{#if children.length > 0} +
+ + + +
children ({children.length})
+
+
+ {#each children as child, i} +
+ {i}: + {child.subtype} +
+ {/each}
- {/if} +
+{/if} -
    + {#each Object.entries(fields) as [id, field]} -
  • - - +
  • + + +
    + + + + + + + {#if field.value === null} Invalid {:else if field.fieldType === 'vector2d'} - - - {#if id !== 'scale'} - - + {/if} - - - - {:else if field.fieldType === 'color'} {:else if field.type === 'roBoolean'} - {:else if field.type === 'roFloat' || field.type === 'roInt'} - {:else if field.type === 'roAssociativeArray'} { -
    - {#each Object.entries(field.value) as [collectionItemId, item]} -
    - {collectionItemId}: - {#if utils.isObjectWithProperty(item, 'subtype')} - {:else if typeof item === 'boolean'} - {:else if typeof item === 'object'} - {JSON.stringify( - item - )}{:else if typeof item === 'number'} - {:else} - {/if}{#if Object.entries(field.value).pop()[0] !== collectionItemId},{/if} - - - -
    - {/each} -
    - } + {id} + class="braceOrBracket" + on:click={toggleShowingBraceOrBracketContent}> + { + {#if !expandedCollectionFields[id]}}{/if} + {:else if field.type === 'roArray'} [ - {#each field.value as item, collectionItemId} -
    - {collectionItemId}: - {#if utils.isObjectWithProperty(item, 'subtype')} - {:else if typeof item === 'object'} - {JSON.stringify( - item - )}{:else if typeof item === 'number'} - {:else if typeof item === 'boolean'} - {:else} - {/if}{#if collectionItemId + 1 < field.value.length},{/if} - - - -
    - {/each} - ] + {id} + class="braceOrBracket" + on:click={toggleShowingBraceOrBracketContent}> + [ + {#if !expandedCollectionFields[id]}]{/if} + {:else if field.type === 'roSGNode' || field.fieldType === 'node'} - + {field.value.subtype} {:else if field.type === 'roString' || field.fieldType == 'string'} - {:else} -