Skip to content

Commit

Permalink
Add thumbnail to Roku AppOverlays grid #549 (#614)
Browse files Browse the repository at this point in the history
  • Loading branch information
TwitchBronBron authored Feb 14, 2025
2 parents 7d137f9 + f9dee84 commit 0fdf813
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 43 deletions.
41 changes: 39 additions & 2 deletions src/viewProviders/RokuAppOverlaysViewViewProvider.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as vscode from 'vscode';
import * as path from 'path';
import { promises as fs } from 'fs';
import { v4 as uuid } from 'uuid';
import { VscodeCommand } from '../commands/VscodeCommand';
import { BaseRdbViewProvider } from './BaseRdbViewProvider';
import { ViewProviderId } from './ViewProviderId';
import { ViewProviderEvent } from './ViewProviderEvent';
import { ViewProviderCommand } from './ViewProviderCommand';

export class RokuAppOverlaysViewViewProvider extends BaseRdbViewProvider {
public readonly id = ViewProviderId.rokuAppOverlaysView;
Expand All @@ -16,6 +18,31 @@ export class RokuAppOverlaysViewViewProvider extends BaseRdbViewProvider {

this.registerCommandWithWebViewNotifier(VscodeCommand.rokuAppOverlaysViewRemoveAllOverlays);

this.addMessageCommandCallback(ViewProviderCommand.openRokuFile, async (message) => {
const filePath = message.context.filePath;
await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(filePath));
await vscode.commands.executeCommand('workbench.action.files.setActiveEditorReadonlyInSession');
return true;
});

this.addMessageCommandCallback(ViewProviderCommand.loadRokuAppOverlaysThumbnails, async (message) => {
return new Promise((resolve, reject) => {
const overlays = message.context.overlays;
const index = message.context.index;
this.getDataUriFromFile(overlays[index].sourcePath).then((imageData) => {
overlays[index].imageData = imageData;
const response = this.createEventMessage(ViewProviderEvent.onRokuAppOverlayThumbnailsLoaded, {
overlays: overlays
});
this.postOrQueueMessage(response);
resolve(true);
}).catch((e) => {
console.error(`Error loading overlay thumbnails: ${e.message}`);
reject(e);
});
});
});

subscriptions.push(vscode.commands.registerCommand(VscodeCommand.rokuAppOverlaysViewAddNewOverlay, async () => {
const options: vscode.OpenDialogOptions = {
canSelectMany: false,
Expand All @@ -27,9 +54,8 @@ export class RokuAppOverlaysViewViewProvider extends BaseRdbViewProvider {
}
};
const filePath = (await vscode.window.showOpenDialog(options))[0]?.fsPath;

const name = path.basename(filePath);
const extension = path.extname(filePath);
const name = path.basename(filePath, extension);
const destinationFileName = path.basename(filePath, extension) + '_' + Date.now() + extension;

const message = this.createEventMessage(ViewProviderEvent.onRokuAppOverlayAdded, {
Expand All @@ -42,4 +68,15 @@ export class RokuAppOverlaysViewViewProvider extends BaseRdbViewProvider {
this.postOrQueueMessage(message);
}));
}

private async getDataUriFromFile(filePath) {
try {
const contents = await fs.readFile(filePath, { encoding: 'base64' });
const base64String = `data:image/png;base64, ${contents}`;
return base64String;
} catch (error) {
console.error(`Error reading or encoding file: ${error.message}`);
return null;
}
}
}
1 change: 1 addition & 0 deletions src/viewProviders/ViewProviderCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum ViewProviderCommand {
getStoredRokuAppOverlays = 'getStoredRokuAppOverlays',
getWorkspaceState = 'getWorkspaceState',
openRokuFile = 'openRokuFile',
loadRokuAppOverlaysThumbnails = 'loadRokuAppOverlaysThumbnails',
runRokuAutomationConfig = 'runRokuAutomationConfig',
sendMessageToWebviews = 'sendMessageToWebviews',
sendReplRequest = 'sendReplRequest',
Expand Down
3 changes: 2 additions & 1 deletion src/viewProviders/ViewProviderEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export enum ViewProviderEvent {
onRokuAutomationConfigsLoaded = 'onRokuAutomationConfigsLoaded',
onRokuAutomationConfigStepChange = 'onRokuAutomationConfigStepChange',
onRokuAutomationKeyPressed = 'onRokuAutomationKeyPressed',
onRokuAppOverlayAdded = 'onRokuAppOverlayAdded'
onRokuAppOverlayAdded = 'onRokuAppOverlayAdded',
onRokuAppOverlayThumbnailsLoaded = 'onRokuAppOverlayThumbnailsLoaded'
}
2 changes: 1 addition & 1 deletion webviews/src/ExtensionIntermediary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class ExtensionIntermediary {
}

public getWorkspaceState(key: string, defaultValue: string | undefined = undefined) {
return this.sendCommand<any>(ViewProviderCommand.getWorkspaceState, {
return this.sendCommand(ViewProviderCommand.getWorkspaceState, {
key: key,
defaultValue: defaultValue
});
Expand Down
164 changes: 125 additions & 39 deletions webviews/src/views/RokuAppOverlaysView/RokuAppOverlaysView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { odc, intermediary } from '../../ExtensionIntermediary';
import OdcSetupSteps from '../../shared/OdcSetupSteps.svelte';
import { NewFile, Trash } from 'svelte-codicons';
import { ViewProviderCommand } from '../../../../src/viewProviders/ViewProviderCommand';
import { ViewProviderEvent } from '../../../../src/viewProviders/ViewProviderEvent';
import { VscodeCommand } from '../../../../src/commands/VscodeCommand';
import { WorkspaceStateKey } from '../../../../src/viewProviders/WorkspaceStateKey';
Expand All @@ -24,6 +25,7 @@
destinationFileName: string;
visible: boolean;
opacity: number;
imageData: any;
}
let overlays = [] as OverlayInfo[]
Expand Down Expand Up @@ -97,6 +99,11 @@
overlays = [overlayInfo, ...overlays];
}
// load thumbnail data from disk
intermediary.sendCommand(ViewProviderCommand.loadRokuAppOverlaysThumbnails, {
overlays: overlays,
index: index !== undefined ? index : 0
});
} catch (e) {
debugger;
}
Expand Down Expand Up @@ -206,56 +213,135 @@
}
});
intermediary.observeEvent(ViewProviderEvent.onRokuAppOverlayThumbnailsLoaded, (message) => {
overlays = message.context.overlays;
});
function onOpenFile(event) {
const srcFilePath = event.target.getAttribute('data-file');
const pathContentsInfo = { filePath: srcFilePath, type: 'file' };
intermediary.sendCommand(ViewProviderCommand.openRokuFile, pathContentsInfo);
}
// Required by any view so we can know that the view is ready to receive messages
intermediary.sendViewReady();
</script>

<style>
#container {
padding: 8px;
.container {
width:100%;
box-sizing: border-box;
overflow: hidden;
display: grid;
grid-template-columns: 0.4fr 0.4fr 3.2fr 0.1fr 0.5fr;
grid-template-rows: 1fr 1fr;
gap: 0px 0px;
min-width: 0;
min-height: 0;
grid-auto-flow: row;
grid-template-areas:
'checkbox image filepath filepath delete'
'checkbox image label slider delete';
}
table {
border-spacing: 0;
.filepath {
grid-area: filepath;
justify-self: left;
align-self: center;
overflow: hidden;
min-width: 0;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: 100%;
direction: rtl;
text-align: left;
opacity: .5;
}
.checkbox {
grid-area: checkbox;
justify-self: center;
align-self: center;
}
.image {
grid-area: image;
display: flex;
justify-self: center;
align-self: center;
align-content: center;
width: 50px;
height: 50px;
margin: 0px 5px;
border: 1px solid var(--vscode-scrollbarSlider-background);
}
.image img {
max-width: 50px;
height: auto;
max-height: 50px;
margin: auto;
}
.label {
grid-area: label;
justify-self: left;
align-self: center;
width:100%;
}
.label vscode-text-field {
width:100%;
}
.slider {
grid-area: slider;
justify-self: center;
align-self: center;
}
.slider-input{
width: 75px;
}
.delete {
grid-area: delete;
justify-self: center;
align-self: center;
}
</style>


<div id="container">
{#if odcAvailable}
{#if overlays.length }
<table>
{#each overlays as overlay, index}
<tr>
<td>
<vscode-checkbox id="{index}" on:change={onOverlayVisibleChange} checked={overlay.visible} />
</td>
<td>
<vscode-text-field id="{index}" on:input={onOverlayNameChange} value="{overlay.name}" />
</td>
<td>
<input id="{index.toString()}" type="range" min="0" max="100" value="{overlay.opacity * 100}" class="slider" on:input={onOverlayOpacityChange}>
</td>
<td>
<vscode-button id="{index}" appearance="icon" title="Delete Overlay" aria-label="Delete Overlay" on:click={deleteOverlay}>
<Trash />
</vscode-button>
</td>
</tr>
<tr>
<td colspan="4">
<vscode-divider />
</td>
</tr>
{/each}
</table>
{:else}
<span style="padding:10px">
You haven't added any overlays. Add one by clicking <NewFile />
</span>
{/if}
{#if odcAvailable}
{#if overlays.length }
{#each overlays as overlay, index}
<div class="container">
<div class="filepath" title="{overlay.sourcePath}">
{overlay.sourcePath}
</div>
<div class="checkbox">
<vscode-checkbox id="{index}" on:change={onOverlayVisibleChange} checked={overlay.visible} />
</div>
<div class="image">
<img src="{overlay.imageData}" data-file="{overlay.sourcePath}"/>
</div>
<div class="label">
<vscode-text-field id="{index}" on:input={onOverlayNameChange} value="{overlay.name}" />
</div>
<div class="slider">
<input id="{index.toString()}" class="slider-input" type="range" min="0" max="100" value="{overlay.opacity * 100}" on:input={onOverlayOpacityChange}>
</div>
<div class="delete">
<vscode-button id="{index}" appearance="icon" title="Delete Overlay" aria-label="Delete Overlay" on:click={deleteOverlay}>
<Trash />
</vscode-button>
</div>
</div>
<vscode-divider />
{/each}
{:else}
<OdcSetupSteps />
<span style="padding:10px">
You haven't added any overlays. Add one by clicking <NewFile />
</span>
{/if}
</div>
{:else}
<OdcSetupSteps />
{/if}

0 comments on commit 0fdf813

Please sign in to comment.