diff --git a/package-lock.json b/package-lock.json index 31612a2f..6df71b3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "source-map": "^0.7.3", "thenby": "^1.3.4", "undent": "^0.1.0", + "uuid": "^9.0.1", "vscode-languageclient": "^7.0.0", "vscode-uri": "^1.0.6" }, @@ -5647,6 +5648,15 @@ "node": ">=8" } }, + "node_modules/http-auth/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -6295,6 +6305,15 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -7333,6 +7352,15 @@ "which": "^2.0.2" } }, + "node_modules/node-notifier/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -8163,6 +8191,14 @@ "verror": "1.10.0" } }, + "node_modules/postman-request/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -10458,9 +10494,13 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } @@ -15312,6 +15352,14 @@ "apache-md5": "^1.0.6", "bcryptjs": "^2.4.3", "uuid": "^8.3.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } } }, "http-auth-connect": { @@ -15775,6 +15823,14 @@ "p-map": "^3.0.0", "rimraf": "^3.0.0", "uuid": "^8.3.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } } }, "istanbul-lib-report": { @@ -16607,6 +16663,14 @@ "shellwords": "^0.1.1", "uuid": "^8.3.2", "which": "^2.0.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } } }, "node-preload": { @@ -17245,6 +17309,11 @@ "json-schema": "0.4.0", "verror": "1.10.0" } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" } } }, @@ -19016,9 +19085,9 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" }, "v8-compile-cache-lib": { "version": "3.0.1", diff --git a/package.json b/package.json index 8f1da668..7d30fc1f 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "source-map": "^0.7.3", "thenby": "^1.3.4", "undent": "^0.1.0", + "uuid": "^9.0.1", "vscode-languageclient": "^7.0.0", "vscode-uri": "^1.0.6" }, @@ -206,8 +207,20 @@ }, { "id": "rokuDeviceView", - "contextualTitle": "Roku", - "name": "Device View", + "contextualTitle": "Roku Device View", + "name": "Roku Device View", + "type": "webview" + }, + { + "id": "rokuFileSystemView", + "contextualTitle": "Roku File System", + "name": "Roku File System", + "type": "webview" + }, + { + "id": "rokuAppOverlaysView", + "contextualTitle": "Roku App Overlays", + "name": "Roku App Overlays", "type": "webview" }, { @@ -258,22 +271,22 @@ }, { "command": "extension.brightscript.rokuRegistry.refreshRegistry", - "when": "view == rokuRegistryView", + "when": "view == rokuRegistryView && brightscript.isOnDeviceComponentAvailable", "group": "navigation" }, { "command": "extension.brightscript.rokuRegistry.importRegistry", - "when": "view == rokuRegistryView", + "when": "view == rokuRegistryView && brightscript.isOnDeviceComponentAvailable", "group": "navigation" }, { "command": "extension.brightscript.rokuRegistry.exportRegistry", - "when": "view == rokuRegistryView", + "when": "view == rokuRegistryView && brightscript.isOnDeviceComponentAvailable", "group": "navigation" }, { "command": "extension.brightscript.rokuRegistry.clearRegistry", - "when": "view == rokuRegistryView", + "when": "view == rokuRegistryView && brightscript.isOnDeviceComponentAvailable", "group": "navigation" }, { @@ -325,6 +338,21 @@ "command": "extension.brightscript.rokuAutomationView.disableAutorunOnDeploy", "when": "view == rokuAutomationView && brightscript.rokuAutomationView.autorunOnDeploy", "group": "navigation@2" + }, + { + "command": "extension.brightscript.rokuFileSystemView.refresh", + "when": "view == rokuFileSystemView && brightscript.isOnDeviceComponentAvailable", + "group": "navigation@1" + }, + { + "command": "extension.brightscript.rokuAppOverlaysView.addNewOverlay", + "when": "view == rokuAppOverlaysView && brightscript.isOnDeviceComponentAvailable", + "group": "navigation@1" + }, + { + "command": "extension.brightscript.rokuAppOverlaysView.removeAllOverlays", + "when": "view == rokuAppOverlaysView && brightscript.isOnDeviceComponentAvailable", + "group": "navigation@2" } ], "webview/context": [ @@ -2798,6 +2826,24 @@ "category": "BrighterScript", "icon": "$(pass-filled)" }, + { + "command": "extension.brightscript.rokuFileSystemView.refresh", + "title": "Refresh", + "category": "BrightScript", + "icon": "$(refresh)" + }, + { + "command": "extension.brightscript.rokuAppOverlaysView.addNewOverlay", + "title": "Add New Overlay", + "category": "BrighterScript", + "icon": "$(new-file)" + }, + { + "command": "extension.brightscript.rokuAppOverlaysView.removeAllOverlays", + "title": "Remove All Overlays", + "category": "BrighterScript", + "icon": "$(trash)" + }, { "command": "extension.brightscript.languageServer.restart", "title": "Restart Language Server", diff --git a/src/commands/VscodeCommand.ts b/src/commands/VscodeCommand.ts index a0d943fd..131c9322 100644 --- a/src/commands/VscodeCommand.ts +++ b/src/commands/VscodeCommand.ts @@ -14,5 +14,8 @@ export enum VscodeCommand { rokuAutomationViewStartRecording = 'extension.brightscript.rokuAutomationView.startRecording', rokuAutomationViewStopRecording = 'extension.brightscript.rokuAutomationView.stopRecording', enableRemoteControlMode = 'extension.brightscript.enableRemoteControlMode', - disableRemoteControlMode = 'extension.brightscript.disableRemoteControlMode' + disableRemoteControlMode = 'extension.brightscript.disableRemoteControlMode', + rokuAppOverlaysViewAddNewOverlay = 'extension.brightscript.rokuAppOverlaysView.addNewOverlay', + rokuAppOverlaysViewRemoveAllOverlays = 'extension.brightscript.rokuAppOverlaysView.removeAllOverlays', + rokuFileSystemViewRefresh = 'extension.brightscript.rokuFileSystemView.refresh' } diff --git a/src/managers/RtaManager.ts b/src/managers/RtaManager.ts index 140d46c9..514275ef 100644 --- a/src/managers/RtaManager.ts +++ b/src/managers/RtaManager.ts @@ -1,3 +1,5 @@ +import * as fs from 'fs'; +import * as path from 'path'; import * as rta from 'roku-test-automation'; import { ViewProviderEvent } from '../viewProviders/ViewProviderEvent'; import { ViewProviderId } from '../viewProviders/ViewProviderId'; @@ -56,25 +58,7 @@ export class RtaManager { public async sendOdcRequest(requestorId: string, command: string, context: { args: any; options: any }) { const { args, options } = context; - if (command === rta.RequestType.findNodesAtLocation) { - if (!this.lastStoreNodesResponse) { - args.includeBoundingRectInfo = true; - await this.sendOdcRequest(requestorId, rta.RequestType.storeNodeReferences, args); - } - context.args.nodeTreeResponse = this.lastStoreNodesResponse; - let { matches } = await rta.odc.findNodesAtLocation(args, options); - if (requestorId === ViewProviderId.rokuDeviceView) { - if (matches.length) { - const match = { ...matches[0] }; - // Remove children as this is where most of the payload is and we don't need this info - match.children = []; - matches = [match]; - } - } - return { - matches: matches - }; - } else if (command === rta.RequestType.storeNodeReferences) { + if (command === rta.RequestType.storeNodeReferences) { this.lastStoreNodesResponse = await rta.odc.storeNodeReferences(args, options); const viewIds = []; @@ -87,6 +71,18 @@ export class RtaManager { event: ViewProviderEvent.onStoredNodeReferencesUpdated }); return this.lastStoreNodesResponse; + } else if (command === rta.RequestType.writeFile) { + // We can't access files from the webview so we just store the path and access it in node instead + const directoryPath = path.dirname(args.destinationPath); + // We always try to make the directory. Doesn't fail if it already exists + await rta.odc.createDirectory({ + path: directoryPath + }); + + return rta.odc.writeFile({ + binaryPayload: fs.readFileSync(args.sourcePath), + path: args.destinationPath + }, options); } else { return this.onDeviceComponent[command](args, options); } diff --git a/src/managers/WebviewViewProviderManager.ts b/src/managers/WebviewViewProviderManager.ts index 061d9b83..e7f8b7f1 100644 --- a/src/managers/WebviewViewProviderManager.ts +++ b/src/managers/WebviewViewProviderManager.ts @@ -5,6 +5,8 @@ import type { BrightScriptCommands } from '../BrightScriptCommands'; import * as vscode from 'vscode'; import { RokuCommandsViewProvider } from '../viewProviders/RokuCommandsViewProvider'; import { RokuDeviceViewViewProvider } from '../viewProviders/RokuDeviceViewViewProvider'; +import { RokuFileSystemViewViewProvider } from '../viewProviders/RokuFileSystemViewViewProvider'; +import { RokuAppOverlaysViewViewProvider } from '../viewProviders/RokuAppOverlaysViewViewProvider'; import { RokuRegistryViewProvider } from '../viewProviders/RokuRegistryViewProvider'; import { SceneGraphInspectorViewProvider } from '../viewProviders/SceneGraphInspectorViewProvider'; import { RokuAutomationViewViewProvider } from '../viewProviders/RokuAutomationViewViewProvider'; @@ -15,7 +17,6 @@ export class WebviewViewProviderManager { private rtaManager: RtaManager, brightScriptCommands: BrightScriptCommands ) { - for (const webview of this.webviewViews) { if (!webview.provider) { webview.provider = new webview.constructor(context, { @@ -30,11 +31,8 @@ export class WebviewViewProviderManager { } private webviewViews = [{ - constructor: SceneGraphInspectorViewProvider, - provider: undefined as SceneGraphInspectorViewProvider - }, { - constructor: RokuRegistryViewProvider, - provider: undefined as RokuRegistryViewProvider + constructor: RokuAutomationViewViewProvider, + provider: undefined as RokuAutomationViewViewProvider }, { constructor: RokuCommandsViewProvider, provider: undefined as RokuCommandsViewProvider @@ -42,8 +40,17 @@ export class WebviewViewProviderManager { constructor: RokuDeviceViewViewProvider, provider: undefined as RokuDeviceViewViewProvider }, { - constructor: RokuAutomationViewViewProvider, - provider: undefined as RokuAutomationViewViewProvider + constructor: RokuFileSystemViewViewProvider, + provider: undefined as RokuFileSystemViewViewProvider + }, { + constructor: RokuRegistryViewProvider, + provider: undefined as RokuRegistryViewProvider + }, { + constructor: RokuAppOverlaysViewViewProvider, + provider: undefined as RokuAppOverlaysViewViewProvider + }, { + constructor: SceneGraphInspectorViewProvider, + provider: undefined as SceneGraphInspectorViewProvider }]; public getWebviewViewProviders() { diff --git a/src/viewProviders/BaseWebviewViewProvider.ts b/src/viewProviders/BaseWebviewViewProvider.ts index 2797b11c..01c928d1 100644 --- a/src/viewProviders/BaseWebviewViewProvider.ts +++ b/src/viewProviders/BaseWebviewViewProvider.ts @@ -106,6 +106,19 @@ export abstract class BaseWebviewViewProvider implements vscode.WebviewViewProvi } else if (command === ViewProviderCommand.sendMessageToWebviews) { const context = message.context; this.webviewViewProviderManager.sendMessageToWebviews(context.viewIds, context.message); + } else if (command === ViewProviderCommand.updateWorkspaceState) { + const context = message.context; + await this.extensionContext.workspaceState.update(context.key, context.value); + this.postOrQueueMessage({ + ...message + }); + } else if (command === ViewProviderCommand.getWorkspaceState) { + const context = message.context; + const response = await this.extensionContext.workspaceState.get(context.key, context.defaultValue); + this.postOrQueueMessage({ + ...message, + response: response + }); } else { const callback = this.messageCommandCallbacks[command]; if (!callback || !await callback(message)) { diff --git a/src/viewProviders/RokuAppOverlaysViewViewProvider.ts b/src/viewProviders/RokuAppOverlaysViewViewProvider.ts new file mode 100644 index 00000000..fd177949 --- /dev/null +++ b/src/viewProviders/RokuAppOverlaysViewViewProvider.ts @@ -0,0 +1,45 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import { v4 as uuid } from 'uuid'; +import { VscodeCommand } from '../commands/VscodeCommand'; +import { BaseRdbViewProvider } from './BaseRdbViewProvider'; +import { ViewProviderId } from './ViewProviderId'; +import { ViewProviderEvent } from './ViewProviderEvent'; + +export class RokuAppOverlaysViewViewProvider extends BaseRdbViewProvider { + public readonly id = ViewProviderId.rokuAppOverlaysView; + + constructor(context: vscode.ExtensionContext, dependencies) { + super(context, dependencies); + + const subscriptions = context.subscriptions; + + this.registerCommandWithWebViewNotifier(context, VscodeCommand.rokuAppOverlaysViewRemoveAllOverlays); + + subscriptions.push(vscode.commands.registerCommand(VscodeCommand.rokuAppOverlaysViewAddNewOverlay, async () => { + const options: vscode.OpenDialogOptions = { + canSelectMany: false, + openLabel: 'Add Overlay', + canSelectFiles: true, + canSelectFolders: false, + filters: { + Images: ['png', 'jpg', 'jpeg', 'webp', 'bmp', 'gif'] + } + }; + const filePath = (await vscode.window.showOpenDialog(options))[0]?.fsPath; + + const name = path.basename(filePath); + const extension = path.extname(filePath); + const destinationFileName = path.basename(filePath, extension) + '_' + Date.now() + extension; + + const message = this.createEventMessage(ViewProviderEvent.onRokuAppOverlayAdded, { + id: uuid(), + name: name, + sourcePath: filePath, + destinationFileName: destinationFileName + }); + + this.postOrQueueMessage(message); + })); + } +} diff --git a/src/viewProviders/RokuAutomationViewViewProvider.ts b/src/viewProviders/RokuAutomationViewViewProvider.ts index b0feebb9..2c6f31bd 100644 --- a/src/viewProviders/RokuAutomationViewViewProvider.ts +++ b/src/viewProviders/RokuAutomationViewViewProvider.ts @@ -8,6 +8,7 @@ import { BaseRdbViewProvider } from './BaseRdbViewProvider'; import { ViewProviderId } from './ViewProviderId'; import { ViewProviderCommand } from './ViewProviderCommand'; import { ViewProviderEvent } from './ViewProviderEvent'; +import { WorkspaceStateKey } from './WorkspaceStateKey'; export class RokuAutomationViewViewProvider extends BaseRdbViewProvider { public readonly id = ViewProviderId.rokuAutomationView; @@ -20,7 +21,7 @@ export class RokuAutomationViewViewProvider extends BaseRdbViewProvider { this.addMessageCommandCallback(ViewProviderCommand.storeRokuAutomationConfigs, async (message) => { this.rokuAutomationConfigs = message.context.configs; // Make sure to use JSON.stringify or weird stuff happens - await context.workspaceState.update(this.configStorageKey, JSON.stringify(message.context)); + await context.workspaceState.update(WorkspaceStateKey.rokuAutomationConfigs, JSON.stringify(message.context)); return true; }); @@ -70,7 +71,7 @@ export class RokuAutomationViewViewProvider extends BaseRdbViewProvider { await this.setAutorunOnDeploy(false); })); - let autorunOnDeploy: boolean = this.extensionContext.workspaceState.get(this.autorunOnDeployStorageKey); + let autorunOnDeploy: boolean = this.extensionContext.workspaceState.get(WorkspaceStateKey.rokuAutomationAutorunOnDeploy); // Default to true if not set if (autorunOnDeploy !== false) { autorunOnDeploy = true; @@ -86,13 +87,12 @@ export class RokuAutomationViewViewProvider extends BaseRdbViewProvider { private async setAutorunOnDeploy(autorunOnDeploy: boolean) { this.rokuAutomationAutorunOnDeploy = autorunOnDeploy; await vscodeContextManager.set('brightscript.rokuAutomationView.autorunOnDeploy', autorunOnDeploy); - await this.context.workspaceState.update(this.autorunOnDeployStorageKey, autorunOnDeploy); + await this.context.workspaceState.update(WorkspaceStateKey.rokuAutomationAutorunOnDeploy, autorunOnDeploy); } private context: vscode.ExtensionContext; private isRecording = false; - private configStorageKey = 'rokuAutomationConfigs'; private rokuAutomationConfigs: { name: string; steps: { @@ -101,7 +101,6 @@ export class RokuAutomationViewViewProvider extends BaseRdbViewProvider { }[]; }[]; - private autorunOnDeployStorageKey = 'rokuAutomationAutorunOnDeploy'; private rokuAutomationAutorunOnDeploy = false; private currentRunningStep = -1; @@ -157,7 +156,7 @@ export class RokuAutomationViewViewProvider extends BaseRdbViewProvider { // 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); + const json = this.extensionContext.workspaceState.get(WorkspaceStateKey.rokuAutomationConfigs); if (typeof json === 'string') { const result = JSON.parse(json); this.rokuAutomationConfigs = result.configs; diff --git a/src/viewProviders/RokuFileSystemViewViewProvider.ts b/src/viewProviders/RokuFileSystemViewViewProvider.ts new file mode 100644 index 00000000..fb6c2505 --- /dev/null +++ b/src/viewProviders/RokuFileSystemViewViewProvider.ts @@ -0,0 +1,33 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { VscodeCommand } from '../commands/VscodeCommand'; +import { BaseRdbViewProvider } from './BaseRdbViewProvider'; +import { ViewProviderId } from './ViewProviderId'; +import { ViewProviderCommand } from './ViewProviderCommand'; + +export class RokuFileSystemViewViewProvider extends BaseRdbViewProvider { + public readonly id = ViewProviderId.rokuFileSystemView; + + constructor(context: vscode.ExtensionContext, dependencies) { + super(context, dependencies); + + this.registerCommandWithWebViewNotifier(context, VscodeCommand.rokuFileSystemViewRefresh); + + this.addMessageCommandCallback(ViewProviderCommand.openRokuFile, async (message) => { + const pathContentsInfo = message.context; + const result = await this.dependencies.rtaManager.onDeviceComponent.readFile({ + path: pathContentsInfo.path + }); + + const filePath = path.join(os.tmpdir(), path.basename(pathContentsInfo.path)); + + // Write some content to the new file + fs.writeFileSync(filePath, result.binaryPayload); + await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(filePath)); + await vscode.commands.executeCommand('workbench.action.files.setActiveEditorReadonlyInSession'); + return true; + }); + } +} diff --git a/src/viewProviders/ViewProviderCommand.ts b/src/viewProviders/ViewProviderCommand.ts index 65621e3b..8d9b38a5 100644 --- a/src/viewProviders/ViewProviderCommand.ts +++ b/src/viewProviders/ViewProviderCommand.ts @@ -1,11 +1,16 @@ export enum ViewProviderCommand { - viewReady = 'viewReady', - getStoredNodeReferences = 'getStoredNodeReferences', getScreenshot = 'getScreenshot', - setManualIpAddress = 'setManualIpAddress', + getStoredNodeReferences = 'getStoredNodeReferences', + getStoredRokuAppOverlays = 'getStoredRokuAppOverlays', + getWorkspaceState = 'getWorkspaceState', + openRokuFile = 'openRokuFile', + runRokuAutomationConfig = 'runRokuAutomationConfig', sendMessageToWebviews = 'sendMessageToWebviews', + setManualIpAddress = 'setManualIpAddress', setVscodeContext = 'setVscodeContext', + updateWorkspaceState = 'updateWorkspaceState', + stopRokuAutomationConfig = 'stopRokuAutomationConfig', + storeRokuAppOverlays = 'storeRokuAppOverlays', storeRokuAutomationConfigs = 'storeRokuAutomationConfigs', - runRokuAutomationConfig = 'runRokuAutomationConfig', - stopRokuAutomationConfig = 'stopRokuAutomationConfig' + viewReady = 'viewReady' } diff --git a/src/viewProviders/ViewProviderEvent.ts b/src/viewProviders/ViewProviderEvent.ts index 1a475902..bc6ae5d8 100644 --- a/src/viewProviders/ViewProviderEvent.ts +++ b/src/viewProviders/ViewProviderEvent.ts @@ -6,5 +6,6 @@ export enum ViewProviderEvent { onStoredNodeReferencesUpdated = 'onStoredNodeReferencesUpdated', onRokuAutomationConfigsLoaded = 'onRokuAutomationConfigsLoaded', onRokuAutomationConfigStepChange = 'onRokuAutomationConfigStepChange', - onRokuAutomationKeyPressed = 'onRokuAutomationKeyPressed' + onRokuAutomationKeyPressed = 'onRokuAutomationKeyPressed', + onRokuAppOverlayAdded = 'onRokuAppOverlayAdded' } diff --git a/src/viewProviders/ViewProviderId.ts b/src/viewProviders/ViewProviderId.ts index 4a4ab12f..dc61317b 100644 --- a/src/viewProviders/ViewProviderId.ts +++ b/src/viewProviders/ViewProviderId.ts @@ -3,7 +3,9 @@ export enum ViewProviderId { rendezvousView = 'rendezvousView', sceneGraphInspectorView = 'sceneGraphInspectorView', rokuDeviceView = 'rokuDeviceView', + rokuFileSystemView = 'rokuFileSystemView', rokuRegistryView = 'rokuRegistryView', + rokuAppOverlaysView = 'rokuAppOverlaysView', rokuCommandsView = 'rokuCommandsView', rokuAutomationView = 'rokuAutomationView' } diff --git a/src/viewProviders/WorkspaceStateKey.ts b/src/viewProviders/WorkspaceStateKey.ts new file mode 100644 index 00000000..d571ae20 --- /dev/null +++ b/src/viewProviders/WorkspaceStateKey.ts @@ -0,0 +1,6 @@ +export enum WorkspaceStateKey { + rokuAppOverlays = 'rokuAppOverlays', + rokuAutomationConfigs = 'rokuAutomationConfigs', + rokuAutomationAutorunOnDeploy = 'rokuAutomationAutorunOnDeploy', + rokuFileSystemCurrentPath = 'rokuFileSystemCurrentPath' +} diff --git a/webviews/src/ExtensionIntermediary.ts b/webviews/src/ExtensionIntermediary.ts index a97caab0..f1b396f6 100644 --- a/webviews/src/ExtensionIntermediary.ts +++ b/webviews/src/ExtensionIntermediary.ts @@ -4,7 +4,7 @@ import type { VscodeCommand } from '../../src/commands/VscodeCommand'; import type { ViewProviderEvent } from '../../src/viewProviders/ViewProviderEvent'; import { ViewProviderCommand } from '../../src/viewProviders/ViewProviderCommand'; import { RequestType } from 'roku-test-automation/client/dist/types/OnDeviceComponent'; -import type { DeleteEntireRegistrySectionsArgs, DeleteNodeReferencesArgs, DeleteRegistrySectionsArgs, FindNodesAtLocationArgs, GetFocusedNodeArgs, GetNodesInfoArgs, GetNodesWithPropertiesArgs, GetValueArgs, GetValuesArgs, HasFocusArgs, IsInFocusChainArgs, OnFieldChangeOnceArgs, ReadRegistryArgs, RequestOptions, SetValueArgs, StoreNodeReferencesArgs, WriteRegistryArgs } from 'roku-test-automation'; +import type { DeleteEntireRegistrySectionsArgs, DeleteNodeReferencesArgs, DeleteRegistrySectionsArgs, FindNodesAtLocationArgs, GetFocusedNodeArgs, GetNodesInfoArgs, GetNodesWithPropertiesArgs, GetValueArgs, GetValuesArgs, HasFocusArgs, IsInFocusChainArgs, OnFieldChangeOnceArgs, ReadRegistryArgs, RequestOptions, SetValueArgs, StoreNodeReferencesArgs, WriteRegistryArgs, GetVolumeListArgs, GetDirectoryListingArgs, StatPathArgs, RenameFileArgs, DeleteFileArgs, CreateDirectoryArgs, RemoveNodeChildrenArgs } from 'roku-test-automation'; class ExtensionIntermediary { private inflightRequests = {}; @@ -97,6 +97,20 @@ class ExtensionIntermediary { })); } + public updateWorkspaceState(key: string, value: any) { + return this.sendCommand(ViewProviderCommand.updateWorkspaceState, { + key: key, + value: value + }); + } + + public getWorkspaceState(key: string, defaultValue: string | undefined = undefined) { + return this.sendCommand(ViewProviderCommand.getWorkspaceState, { + key: key, + defaultValue: defaultValue + }); + } + public async getStoredNodeReferences() { return this.sendCommand>(ViewProviderCommand.getStoredNodeReferences); } @@ -202,6 +216,38 @@ class ODCIntermediary { public async findNodesAtLocation(args: FindNodesAtLocationArgs, options?: RequestOptions) { return this.sendOdcMessage>(RequestType.findNodesAtLocation, args, options); } + + public async getVolumeList(args: GetVolumeListArgs, options?: RequestOptions) { + return this.sendOdcMessage>(RequestType.getVolumeList, args, options); + } + + public async getDirectoryListing(args: GetDirectoryListingArgs, options?: RequestOptions) { + return this.sendOdcMessage>(RequestType.getDirectoryListing, args, options); + } + + public async statPath(args: StatPathArgs, options?: RequestOptions) { + return this.sendOdcMessage>(RequestType.statPath, args, options); + } + + public async renameFile(args: RenameFileArgs, options?: RequestOptions) { + return this.sendOdcMessage>(RequestType.renameFile, args, options); + } + + public async deleteFile(args: DeleteFileArgs, options?: RequestOptions) { + return this.sendOdcMessage>(RequestType.deleteFile, args, options); + } + + public async createDirectory(args: CreateDirectoryArgs, options?: RequestOptions) { + return this.sendOdcMessage>(RequestType.createDirectory, args, options); + } + + public async writeFile(args: { sourcePath: string; destinationPath: string }, options?: RequestOptions) { + return this.sendOdcMessage>(RequestType.writeFile, args, options); + } + + public async removeNodeChildren(args: RemoveNodeChildrenArgs, options?: RequestOptions) { + return this.sendOdcMessage>(RequestType.removeNodeChildren, args, options); + } } type ObserverCallback = (message) => void; diff --git a/webviews/src/main.ts b/webviews/src/main.ts index ce404989..3e30dfa8 100644 --- a/webviews/src/main.ts +++ b/webviews/src/main.ts @@ -1,10 +1,13 @@ /* eslint-disable object-shorthand */ -import rokuRegistryView from './views/RokuRegistryView/RokuRegistryView.svelte'; +import { provideVSCodeDesignSystem, allComponents } from '@vscode/webview-ui-toolkit'; +import rokuAutomationView from './views/RokuAutomationView/RokuAutomationView.svelte'; import rokuCommandsView from './views/RokuCommandsView/RokuCommandsView.svelte'; import rokuDeviceView from './views/RokuDeviceView/RokuDeviceView.svelte'; +import rokuFileSystemView from './views/RokuFileSystemView/RokuFileSystemView.svelte'; +import rokuRegistryView from './views/RokuRegistryView/RokuRegistryView.svelte'; +import rokuAppOverlaysView from './views/RokuAppOverlaysView/RokuAppOverlaysView.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'; @@ -22,11 +25,13 @@ declare const viewName; //these need to exactly match the names from the "views" contributions in package.json const views = { - rokuRegistryView, + rokuAutomationView, rokuCommandsView, rokuDeviceView, - sceneGraphInspectorView, - rokuAutomationView + rokuFileSystemView, + rokuRegistryView, + rokuAppOverlaysView, + sceneGraphInspectorView }; const app = new views[viewName]({ diff --git a/webviews/src/shared/NumberField.svelte b/webviews/src/shared/NumberField.svelte index 988113bd..57d9cc4e 100644 --- a/webviews/src/shared/NumberField.svelte +++ b/webviews/src/shared/NumberField.svelte @@ -2,8 +2,8 @@ + + + +{title} + {#if sortDirection !== 'none'} + + {#if sortDirection === 'asc'} + + {:else} + + {/if} + + {/if} + diff --git a/webviews/src/shared/types.ts b/webviews/src/shared/types.ts new file mode 100644 index 00000000..a384a413 --- /dev/null +++ b/webviews/src/shared/types.ts @@ -0,0 +1,6 @@ +import type { odc } from '../ExtensionIntermediary'; +export type PathContentsInfo = Omit>>, 'type'> & { + name: string; + path: string; + type?: 'file' | 'directory' | 'fileSystem'; +}; diff --git a/webviews/src/views/RokuAppOverlaysView/RokuAppOverlaysView.svelte b/webviews/src/views/RokuAppOverlaysView/RokuAppOverlaysView.svelte new file mode 100644 index 00000000..1a988e27 --- /dev/null +++ b/webviews/src/views/RokuAppOverlaysView/RokuAppOverlaysView.svelte @@ -0,0 +1,261 @@ + + + + + + +
+ {#if odcAvailable} + {#if overlays.length } + + {#each overlays as overlay, index} + + + + + + + + + + {/each} +
+ + + + + + + + + +
+ +
+ {:else} + + You haven't added any overlays. Add one by clicking + + {/if} + {:else} + + {/if} +
diff --git a/webviews/src/views/RokuFileSystemView/FileSystemEntry.svelte b/webviews/src/views/RokuFileSystemView/FileSystemEntry.svelte new file mode 100644 index 00000000..63551d53 --- /dev/null +++ b/webviews/src/views/RokuFileSystemView/FileSystemEntry.svelte @@ -0,0 +1,77 @@ + + + + + + + {#if entry.type === 'file'} + + {:else if entry.type === 'directory'} + + {:else if entry.type === 'fileSystem'} + + {:else} + + {/if} + + {#if columnsToShow.name} + {entry.name} + {/if} +{#if entry.type === 'file'} + {#if columnsToShow.size} + {formatSize(entry.size)} + {/if} + {#if columnsToShow.dateModified} + {formatDate(entry.mtime)} + {/if} + {#if columnsToShow.dateCreated} + {formatDate(entry.ctime)} + {/if} +{/if} + diff --git a/webviews/src/views/RokuFileSystemView/RokuFileSystemView.svelte b/webviews/src/views/RokuFileSystemView/RokuFileSystemView.svelte new file mode 100644 index 00000000..b923f4be --- /dev/null +++ b/webviews/src/views/RokuFileSystemView/RokuFileSystemView.svelte @@ -0,0 +1,310 @@ + + + + +
+ {#if odcAvailable} + {#if loading} + + {:else} + +
+
0}> + This folder is empty. +
+ + + {#if columnsToShow.name} + + {/if} + {#if columnsToShow.size} + + {/if} + {#if columnsToShow.dateModified} + + {/if} + {#if columnsToShow.dateCreated} + + {/if} + + + {#each currentPathContentsInfo as entry} + + {/each} + +
+ {/if} + {:else} + + {/if} +
diff --git a/webviews/src/views/SceneGraphInspectorView/Branch.svelte b/webviews/src/views/SceneGraphInspectorView/Branch.svelte index 03483d2b..4e97375e 100644 --- a/webviews/src/views/SceneGraphInspectorView/Branch.svelte +++ b/webviews/src/views/SceneGraphInspectorView/Branch.svelte @@ -1,11 +1,12 @@ +