Skip to content

Commit

Permalink
add basic support for simx
Browse files Browse the repository at this point in the history
  • Loading branch information
riknoll committed Nov 25, 2024
1 parent a479713 commit f2c0d40
Show file tree
Hide file tree
Showing 12 changed files with 1,712 additions and 1,228 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,7 @@ dist
# TernJS port file
.tern-port

.vscode-test-web
.vscode-test-web

resources/sim.js
resources/sim.js.map
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -332,13 +332,14 @@
"test": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/index.js",
"pretest": "yarn run compile-web",
"vscode:prepublish": "yarn run package-web",
"compile-web": "webpack",
"watch-web": "webpack --watch",
"package-web": "webpack --mode production --devtool hidden-source-map",
"compile-web": "yarn run simulator && webpack",
"watch-web": "yarn run simulator && webpack --watch",
"package-web": "yarn run simulator && webpack --mode production --devtool hidden-source-map",
"lint": "eslint src --ext ts",
"run-in-browser": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=.",
"run-in-browser": "yarn run simulator && vscode-test-web --browserType=chromium --extensionDevelopmentPath=.",
"generate-l10n": "vscode-l10n-dev export --outDir ./l10n ./src",
"generate-l10n-ploc": "vscode-l10n-dev generate-pseudo -o ./l10n ./l10n/bundle.l10n.json ./package.nls.json"
"generate-l10n-ploc": "vscode-l10n-dev generate-pseudo -o ./l10n ./l10n/bundle.l10n.json ./package.nls.json",
"simulator": "cd src/sim && webpack"
},
"devDependencies": {
"@types/mocha": "^10.0.0",
Expand Down
27 changes: 27 additions & 0 deletions src/localtypings/simulatorExtensionMessages.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
interface VSCodeAPI {
postMessage(data: any): void;
}

interface BaseMessage {
type: "simulator-extension"
src: "simulator" | "extension";
action: string;
id?: string;
}

interface VSCodeResponse extends BaseMessage {
success: boolean;
id: string;
}

interface TargetConfigMessage extends BaseMessage {
action: "targetConfig";
}

interface TargetConfigResponse extends VSCodeResponse {
action: "targetConfig";
config: any;
}

type SimulatorExtensionMessage = TargetConfigMessage;
type SimulatorExtensionResponse = TargetConfigResponse | VSCodeResponse;
144 changes: 144 additions & 0 deletions src/sim/frames.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { getTargetConfigAsync } from "./service";

const FRAME_DATA_MESSAGE_CHANNEL = "messagechannel"

Check warning on line 3 in src/sim/frames.ts

View workflow job for this annotation

GitHub Actions / buildpr

Missing semicolon
const FRAME_ASPECT_RATIO = "aspectratio"

Check warning on line 4 in src/sim/frames.ts

View workflow job for this annotation

GitHub Actions / buildpr

Missing semicolon
const MESSAGE_SOURCE = "pxtdriver"

Check warning on line 5 in src/sim/frames.ts

View workflow job for this annotation

GitHub Actions / buildpr

Missing semicolon
const PERMANENT = "permanent"

Check warning on line 6 in src/sim/frames.ts

View workflow job for this annotation

GitHub Actions / buildpr

Missing semicolon


let simFrames: {[index: string]: HTMLIFrameElement} = {};
let _targetConfig: Promise<any>;


function nextId() {
return crypto.randomUUID();
}

let pendingFrames: {[index: string]: Promise<void>} = {};

export function handleMessagePacket(message: any, source?: Window) {
if (message.type === "messagepacket") {
const channel = message.channel as string;

if (!pendingFrames[channel]) {
pendingFrames[channel] = (async () => {
const targetConfig = await targetConfigAsync();
const simInfo = targetConfig.packages.approvedRepoLib[channel];

if (simInfo.simx) {
startSimulatorExtension(
getSimxUrl(channel, simInfo.simx.index),
true,
undefined,
channel
);
}
})();
}

pendingFrames[channel].then(() => {
for (const frame of Object.keys(simFrames)) {
const contentWindow = simFrames[frame].contentWindow;

if (contentWindow && contentWindow !== source) {
simFrames[frame].contentWindow?.postMessage(message, "*");
}
}
})

Check warning on line 47 in src/sim/frames.ts

View workflow job for this annotation

GitHub Actions / buildpr

Missing semicolon

const simFrame = getSimFrame();

if (simFrame.contentWindow && simFrame.contentWindow !== source) {
simFrame.contentWindow.postMessage(message, "*")

Check warning on line 52 in src/sim/frames.ts

View workflow job for this annotation

GitHub Actions / buildpr

Missing semicolon
}
}
}

function getSimFrame() {
return document.getElementById("simframe") as HTMLIFrameElement;
}

function getSimUrl(): URL {
return new URL(getSimFrame().src);
}


function getSimxUrl(repo: string, index = "index.html") {
const simUrl = getSimUrl();

// Ensure we preserve upload target path (/app/<sha>---simulator)
const simPath = simUrl.pathname.replace(/---?.*/, "");
// Construct the path. The "-" element delineates the extension key from the resource name.
const simxPath = [simPath, "simx", repo, "-", index].join("/");
// Create the fully-qualified URL, preserving the origin by removing all leading slashes
return new URL(simxPath.replace(/^\/+/, ""), simUrl.origin).toString();
}


function createFrame(url: string): HTMLDivElement {
const wrapper = document.createElement("div") as HTMLDivElement;
wrapper.className = `simframe ui embed`;

const frame = document.createElement('iframe') as HTMLIFrameElement;
frame.id = 'sim-frame-' + nextId()

Check warning on line 83 in src/sim/frames.ts

View workflow job for this annotation

GitHub Actions / buildpr

Missing semicolon
frame.title = "Simulator";
frame.allowFullscreen = true;
frame.setAttribute('allow', 'autoplay;microphone');
frame.setAttribute('sandbox', 'allow-same-origin allow-scripts');
frame.className = 'no-select';

let furl = url;
furl += '#' + frame.id;

frame.src = furl;
frame.frameBorder = "0";
// frame.dataset['runid'] = this.runId;
frame.dataset['origin'] = new URL(furl).origin || "*";
frame.dataset['loading'] = "true";

wrapper.appendChild(frame);

const i = document.createElement("i");
i.className = "videoplay xicon icon";
i.style.display = "none";
wrapper.appendChild(i);

const l = document.createElement("div");
l.className = "ui active loader";
i.style.display = "none";
wrapper.appendChild(l);

return wrapper;
}

function startSimulatorExtension(url: string, permanent: boolean, aspectRatio?: number, messageChannel?: string) {
const root = document.getElementById("root");
if (root) {
root.classList.add("simx");
}

aspectRatio = aspectRatio || 1.22;
let wrapper = createFrame(url);
getContainer().appendChild(wrapper);
const messageFrame = wrapper.firstElementChild as HTMLIFrameElement;
messageFrame.dataset[FRAME_DATA_MESSAGE_CHANNEL] = messageChannel;
messageFrame.dataset[FRAME_ASPECT_RATIO] = aspectRatio + "";
wrapper.classList.add("simmsg");
if (permanent) {
messageFrame.dataset[PERMANENT] = "true";
}

simFrames[messageChannel!] = messageFrame;
}

function getContainer() {
return document.getElementById("simulator-extension-frames") as HTMLDivElement;
}

async function targetConfigAsync() {
if (!_targetConfig) {
_targetConfig = await getTargetConfigAsync();
}

return _targetConfig;
}
26 changes: 26 additions & 0 deletions src/sim/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { handleMessagePacket } from "./frames";
import { initService } from "./service";

const acquireApi = (window as any).acquireVsCodeApi;
const vscode = acquireApi();

(window as any).acquireVsCodeApi = () => vscode;

window.addEventListener("message", function (m) {
handleMessagePacket(m.data, m.source as Window);
});

document.addEventListener("DOMContentLoaded", function (event) {
const fs = document.getElementById("fullscreen");
if (fs) {
fs.remove();
}

const simFrame = document.getElementById("simframe") as HTMLIFrameElement;

const framesContainer = document.createElement("div");
framesContainer.id = "simulator-extension-frames";
simFrame.insertAdjacentElement("afterend", framesContainer);
});

initService(vscode);
50 changes: 50 additions & 0 deletions src/sim/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/// <reference path="../localtypings/simulatorExtensionMessages.d.ts" />

let vscode: VSCodeAPI;
const pendingMessages: {[index: string]: (resp: SimulatorExtensionResponse) => void} = {};

export async function getTargetConfigAsync() {
const resp = await sendMessageAsync({
type: "simulator-extension",
src: "simulator",
action: "targetConfig"
}) as TargetConfigResponse;

return resp.config;
}

function sendMessageAsync(message: SimulatorExtensionMessage): Promise<VSCodeResponse> {
return new Promise((resolve, reject) => {
const toSend: BaseMessage = {
...message,
id: crypto.randomUUID()
};

pendingMessages[toSend.id!] = resp => {
if (resp.success) {
resolve(resp);
}
else {
reject(resp);
}
};

vscode.postMessage(toSend);
});
}

export function initService(api: VSCodeAPI) {
vscode = api;

window.addEventListener("message", function (m) {
if (m.data?.type === "simulator-extension") {
const response = m.data as SimulatorExtensionResponse;

const handler = pendingMessages[response.id];
if (handler) {
delete pendingMessages[response.id];
handler(response);
}
}
});
}
17 changes: 17 additions & 0 deletions src/sim/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"outDir": "../../dist/sim",
"lib": [
"ES2020", "DOM"
],
"sourceMap": true,
"rootDir": ".",
"strict": true /* enable all strict type-checking options */
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
}
}
76 changes: 76 additions & 0 deletions src/sim/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

//@ts-check
"use strict";

//@ts-check
/** @typedef {import("webpack").Configuration} WebpackConfig **/

const path = require("path");
const webpack = require("webpack");

/** @type WebpackConfig */
const webExtensionConfig = {
mode: "none", // this leaves the source code as close as possible to the original (when packaging we set this to "production")
target: "web", // extensions run in a webworker context
entry: {
"extension": "./index.ts",
},
output: {
filename: "sim.js",
path: path.join(__dirname, "../../resources"),
libraryTarget: "commonjs",
devtoolModuleFilenameTemplate: "../../[resource-path]"
},
resolve: {
mainFields: ["browser", "module", "main"], // look for `browser` entry point in imported node modules
extensions: [".ts", ".js"], // support ts-files and js-files
alias: {
// provides alternate implementation for node module and source files
},
fallback: {
// Webpack 5 no longer polyfills Node.js core modules automatically.
// see https://webpack.js.org/configuration/resolve/#resolvefallback
// for the list of Node.js core module polyfills.
"assert": require.resolve("assert"),
"path": require.resolve("path-browserify")
}
},
module: {
rules: [{
test: /\.ts$/,
exclude: /node_modules/,
use: [{
loader: "ts-loader"
}]
},
{
// Make sure our marketplace icon is copied to final output
test: /\.(jpe?g|gif|png|svg)$/,
loader: "file"
}]
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1 // disable chunks by default since web extensions must be a single bundle
}),
new webpack.ProvidePlugin({
process: "process/browser", // provide a shim for the global `process` variable
}),
],
externals: {
"vscode": "commonjs vscode", // ignored because it doesn"t exist
},
performance: {
hints: false
},
devtool: "nosources-source-map", // create a source map that points to the original source file
infrastructureLogging: {
level: "log", // enables logging required for problem matchers
},
};

module.exports = [ webExtensionConfig ];
Loading

0 comments on commit f2c0d40

Please sign in to comment.