diff --git a/cli/buildengine.ts b/cli/buildengine.ts index 42ba3d8aab93..cee175edb349 100644 --- a/cli/buildengine.ts +++ b/cli/buildengine.ts @@ -86,7 +86,7 @@ export const buildEngines: Map = { codal: { id: "codal", updateEngineAsync: updateCodalBuildAsync, - buildAsync: () => runBuildCmdAsync("python", "build.py"), + buildAsync: () => runBuildCmdAsync("python3", "build.py"), setPlatformAsync: noopAsync, patchHexInfo: patchCodalHexInfo, prepBuildDirAsync: prepCodalBuildDirAsync, @@ -99,7 +99,7 @@ export const buildEngines: Map = { dockercodal: { id: "dockercodal", updateEngineAsync: updateCodalBuildAsync, - buildAsync: () => runDockerAsync(["python", "build.py"]), + buildAsync: () => runDockerAsync(["python3", "build.py"]), setPlatformAsync: noopAsync, patchHexInfo: patchCodalHexInfo, prepBuildDirAsync: prepCodalBuildDirAsync, diff --git a/localtypings/pxtarget.d.ts b/localtypings/pxtarget.d.ts index a7eb379bb604..659329a2ba6f 100644 --- a/localtypings/pxtarget.d.ts +++ b/localtypings/pxtarget.d.ts @@ -544,6 +544,7 @@ declare namespace pxt { forceEnableAiErrorHelp?: boolean; // Enables the AI Error Help feature, regardless of geo setting. shareHomepageContent?: boolean; // Show buttons to share links to homepage content more easily showProjectDescription?: boolean; // Show project description in pxtjson editor and share dialog + showJacdac?: boolean; // show jacdac button in the sidebar } interface DownloadDialogTheme { diff --git a/localtypings/pxteditor.d.ts b/localtypings/pxteditor.d.ts index 7e8ee576d315..0f3664d73c56 100644 --- a/localtypings/pxteditor.d.ts +++ b/localtypings/pxteditor.d.ts @@ -831,6 +831,7 @@ declare namespace pxt.editor { feedback?: FeedbackState; themePickerOpen?: boolean; errorListNote?: string; + showJacdac?: boolean; } export interface EditorState { @@ -998,6 +999,7 @@ declare namespace pxt.editor { stopSimulator(unload?: boolean, opts?: SimulatorStartOptions): void; restartSimulator(): void; startSimulator(opts?: SimulatorStartOptions): void; + reinitializeSimulatorAsync(): Promise; runSimulator(): void; isSimulatorRunning(): boolean; expandSimulator(): void; diff --git a/pxteditor/experiments.ts b/pxteditor/experiments.ts index 8f3496acaa78..8d1c0d449eb9 100644 --- a/pxteditor/experiments.ts +++ b/pxteditor/experiments.ts @@ -178,6 +178,13 @@ export function all(): Experiment[] { feedbackUrl: "https://github.com/microsoft/pxt/issues/10694", enableOnline: true }, + { + id: "jacdacUI", + name: lf("New Jacdac UI"), + description: lf("Simplified Jacdac UI"), + // feedbackUrl: "https://github.com/microsoft/pxt/issues/10694", + enableOnline: true + }, ]; return exps.filter(experiment => ids.indexOf(experiment.id) > -1) diff --git a/pxtlib/main.ts b/pxtlib/main.ts index e1566fa2d63a..493e806e518a 100644 --- a/pxtlib/main.ts +++ b/pxtlib/main.ts @@ -522,6 +522,7 @@ namespace pxt { export const CONFIG_NAME = "pxt.json" export const SIMSTATE_JSON = ".simstate.json" export const SERIAL_EDITOR_FILE = "serial.txt" + export const JACDAC_EDITOR_FILE = "jacdac.txt" export const README_FILE = "README.md" export const GITIGNORE_FILE = ".gitignore" export const ASSETS_FILE = "assets.json" diff --git a/pxtsim/simdriver.ts b/pxtsim/simdriver.ts index ffc6a78cd5ec..29ef76ffa784 100644 --- a/pxtsim/simdriver.ts +++ b/pxtsim/simdriver.ts @@ -350,11 +350,60 @@ namespace pxsim { this.singleSimulator = true } + private mode: "simulator" | "devices" = "simulator" + private removedElements: HTMLElement[] = []; + private removedIndices: number[] = []; + private jacdacIndex: number = -1; + public setMode(mode: "simulator" | "devices") { + this.mode = mode + if (this.jacdacIndex != -1 && mode === "simulator") { + // add frames back to the DOM + const jacdacFrame = this.simFrames()[0]; + this.removedElements.forEach((elem, index) => { + if (index < this.jacdacIndex) { + this.container.insertBefore(elem, jacdacFrame.parentElement); + } else { + this.container.appendChild(elem); + } + }); + this.removedElements = []; + this.removedIndices = []; + this.jacdacIndex = -1; + this.postMessageCore(jacdacFrame, { + type: "simulatorMode", + source: MESSAGE_SOURCE, + }); + this.start(); + } else if (this.jacdacIndex === -1 && mode === "devices") { + this.suspend(); + const frames = this.simFrames(); + frames.forEach((frame,index) => { + if (frame.dataset[FRAME_DATA_MESSAGE_CHANNEL] !== "jacdac/pxt-jacdac") { + this.removedElements.push(frame.parentElement); + this.removedIndices.push(index); + } else { + this.jacdacIndex = index; + this.postMessageCore(frame, { + type: "devicesMode", + source: MESSAGE_SOURCE, + }); + } + }); + this.removedElements.forEach(elem => this.container.removeChild(elem)); + } + } + public postMessage(msg: pxsim.SimulatorMessage, source?: Window, frameID?: string) { if (this.hwdbg) { this.hwdbg.postMessage(msg) return } + if (this.mode === "simulator" && (msg as any)?.sender === "packetio") { + const messageChannel = msg.type === "messagepacket" && (msg as SimulatorControlMessage).channel; + if (messageChannel === "jacdac") { + return; // don't send packetio jacdac messages to sims when in simulator mode + } + } const depEditors = this.dependentEditors(); let frames = this.simFrames(); @@ -370,7 +419,8 @@ namespace pxsim { broadcastmsg.srcFrameIndex = this.simFrames().findIndex((item) => item.contentWindow === source); const sourceFrame = broadcastmsg.srcFrameIndex >= 0 ? this.simFrames()[broadcastmsg.srcFrameIndex] : undefined; // jacdac messages from a board sim other than first should be dropped - if (broadcastmsg.srcFrameIndex > 0 && mkcdFrames.find(f => f === sourceFrame) && messageChannel === "jacdac") + if (broadcastmsg.srcFrameIndex > 0 && mkcdFrames.find(f => f === sourceFrame) + && messageChannel === "jacdac") return; // if the editor is hosted in a multi-editor setting // don't start extra frames @@ -477,7 +527,8 @@ namespace pxsim { if (source && frame.contentWindow == source) continue; // if jacdac message, don't send to other (board) simulator frames if (i > 0 && !frame.dataset[FRAME_DATA_MESSAGE_CHANNEL] && - msg.type === "messagepacket" && (msg as pxsim.SimulatorControlMessage).channel === "jacdac") continue; + msg.type === "messagepacket" && + (msg as pxsim.SimulatorControlMessage).channel === "jacdac") continue; // frame not in DOM if (!frame.contentWindow) continue; @@ -667,6 +718,60 @@ namespace pxsim { && this.loanedSimulator.querySelector("iframe"); } + // the jacdac simulator frame, visually relocated on top of another element + private jacdacOverlayWrapper: HTMLElement; + private jacdacOverlayTarget: HTMLElement; + private jacdacOverlayResizeObserver: ResizeObserver; + private jacdacOverlayReposition = () => { + if (!this.jacdacOverlayWrapper || !this.jacdacOverlayTarget) return; + const rect = this.jacdacOverlayTarget.getBoundingClientRect(); + const style = this.jacdacOverlayWrapper.style; + style.position = "fixed"; + style.top = `${rect.top}px`; + style.left = `${rect.left}px`; + style.width = `${rect.width}px`; + style.height = `${rect.height}px`; + style.zIndex = "1000"; + } + + // visually relocates the jacdac simulator frame on top of `target`, without + // reparenting its iframe: reparenting an iframe forces the browser to tear down + // and reload its nested browsing context, which wipes out all the running + // jacdac module simulators inside it + public showJacdacSimulator(target: HTMLElement): boolean { + this.hideJacdacSimulator(); + + const wrapper = pxsim.util.toArray(this.container.children) + .find(el => (el.querySelector("iframe") as HTMLIFrameElement)?.dataset[FRAME_DATA_MESSAGE_CHANNEL] === "jacdac/pxt-jacdac") as HTMLElement; + if (!wrapper) return false; + + this.jacdacOverlayWrapper = wrapper; + this.jacdacOverlayTarget = target; + this.jacdacOverlayReposition(); + + if (typeof ResizeObserver !== "undefined") { + this.jacdacOverlayResizeObserver = new ResizeObserver(this.jacdacOverlayReposition); + this.jacdacOverlayResizeObserver.observe(target); + } + window.addEventListener("resize", this.jacdacOverlayReposition); + window.addEventListener("scroll", this.jacdacOverlayReposition, true); + return true; + } + + public hideJacdacSimulator() { + if (!this.jacdacOverlayWrapper) return; + + const style = this.jacdacOverlayWrapper.style; + style.position = style.top = style.left = style.width = style.height = style.zIndex = ""; + + window.removeEventListener("resize", this.jacdacOverlayReposition); + window.removeEventListener("scroll", this.jacdacOverlayReposition, true); + this.jacdacOverlayResizeObserver?.disconnect(); + this.jacdacOverlayResizeObserver = undefined; + this.jacdacOverlayWrapper = undefined; + this.jacdacOverlayTarget = undefined; + } + private frameCleanupTimeout: any = undefined; private cancelFrameCleanup() { if (this.frameCleanupTimeout) { diff --git a/theme/common.less b/theme/common.less index 113a97f7a0e6..1950a5dc0318 100644 --- a/theme/common.less +++ b/theme/common.less @@ -1445,7 +1445,8 @@ Field editors } } -#serialPreview .label:focus { +#serialPreview .label:focus, +#jacdacPreview .label:focus { outline: 3px solid var(--pxt-focus-border) !important; outline-offset: -15px; } @@ -1642,6 +1643,7 @@ p.ui.font.small { #maineditor, #editortools, #serialPreview, + #jacdacPreview, .settings-menuitem, .help-dropdown-menuitem { display: none !important; diff --git a/theme/highcontrast.less b/theme/highcontrast.less index a35dfe90a264..be1934e3fa93 100644 --- a/theme/highcontrast.less +++ b/theme/highcontrast.less @@ -617,7 +617,8 @@ /* Serial editor */ - #serialPreview div { + #serialPreview div, + #jacdacPreview div { color: @HCtextColor; } @@ -640,7 +641,8 @@ border-color: @HCtextColor; } - #serialPreview .label { + #serialPreview .label, + #jacdacPreview .label { border: 10px solid @HCtextColor !important; &:hover { border-color: darken(@HCtextColor, 10.0) !important; diff --git a/theme/jacdac.less b/theme/jacdac.less new file mode 100644 index 000000000000..41c338241ed0 --- /dev/null +++ b/theme/jacdac.less @@ -0,0 +1,29 @@ +/* Import all components */ +@import 'themes/default/globals/site.variables'; +@import 'themes/pxt/globals/site.variables'; + +/* Reference import */ +@import (reference) "semantic.less"; + +/*------------------- + Jacdac editor +--------------------*/ + +#jacdacEditor { + background-color: var(--pxt-target-background2); + color: var(--pxt-target-foreground2); +} + +#jacdacArea { + height: 90%; + padding: 1rem; + display: flex; + flex-direction: column; +} + +#jacdacSimulator { + // placeholder box: the actual jacdac sim iframe is positioned on top of + // this element (see SimulatorDriver.showJacdacSimulator), it is not a child of it + flex: 1; + min-height: 20rem; +} diff --git a/theme/pxt.less b/theme/pxt.less index 93bc940ae5ec..c0c92df5ba59 100644 --- a/theme/pxt.less +++ b/theme/pxt.less @@ -14,6 +14,7 @@ @import 'sidedoc-keyboard-nav-help'; @import 'home'; @import 'serial'; +@import 'jacdac'; @import 'docs'; @import 'debugger'; @import 'toolbox'; diff --git a/theme/serial.less b/theme/serial.less index e8cde459f935..63806f3c080f 100644 --- a/theme/serial.less +++ b/theme/serial.less @@ -222,11 +222,13 @@ margin: 0 !important; } -#serialPreview { +#serialPreview, +#jacdacPreview { cursor: pointer; } -#serialPreview .label { +#serialPreview .label, +#jacdacPreview .label { width: 100%; background-color: var(--pxt-target-background2); font-size: 0.85em; @@ -245,16 +247,20 @@ } } -#serialPreview .label:hover { +#serialPreview .label:hover, +#jacdacPreview .label:hover { opacity: 0.8; } -#serialPreview .label:focus { +#serialPreview .label:focus, +#jacdacPreview .label:focus { outline: none; } .fullscreensim #serialPreview, -.simView #serialPreview { +.fullscreensim #jacdacPreview, +.simView #serialPreview, +.simView #jacdacPreview { display: none !important; z-index: -10 !important; } diff --git a/theme/tutorial-sidebar.less b/theme/tutorial-sidebar.less index 1431a4b6bfca..f2943fd52a48 100644 --- a/theme/tutorial-sidebar.less +++ b/theme/tutorial-sidebar.less @@ -454,6 +454,51 @@ &.ui.items { margin-top: 0; } } +.jacdac-view-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + margin: 0.75rem 0.75rem 0.25rem 0.75rem; +} + +.jacdac-view-toggle { + display: inline-flex; + align-self: flex-start; + gap: 0.35rem; + margin: 0; + padding: 0.35rem; + border: 1px solid var(--pxt-target-foreground0); + border-radius: 0.75rem; + background: var(--pxt-target-background1); + + > .ui.button { + border: 1px solid var(--pxt-target-foreground0) !important; + border-radius: 0.5rem !important; + box-shadow: none !important; + margin: 0 !important; + color: var(--pxt-button-secondary-foreground) !important; + + &:not(.active) { + background: transparent !important; + } + + &.active { + background: var(--pxt-colors-blue-background) !important; + border-color: var(--pxt-colors-blue-background) !important; + color: var(--pxt-colors-blue-foreground) !important; + box-shadow: 0 0 0 1px var(--pxt-colors-blue-background), 0 0.15rem 0.35rem rgba(0, 0, 0, 0.18) !important; + } + } +} + +.jacdac-view-expand { + border: 1px solid var(--pxt-target-foreground0) !important; + border-radius: 0.5rem !important; + box-shadow: none !important; + background: var(--pxt-target-background1) !important; + color: var(--pxt-button-secondary-foreground) !important; +} + // Mini sim is visible when tab is hidden #root.tabTutorial { &:not(.fullscreensim) .simulator-container.hidden { diff --git a/webapp/src/app.tsx b/webapp/src/app.tsx index 435d3ff22c14..f63c5fdc1dc2 100644 --- a/webapp/src/app.tsx +++ b/webapp/src/app.tsx @@ -52,6 +52,7 @@ import * as monaco from "./monaco" import * as toolboxHelpers from "./toolboxHelpers" import * as pxtjson from "./pxtjson" import * as serial from "./serial" +import * as jacdac from "./jacdac" import * as blocks from "./blocks" import * as gitjson from "./gitjson" import * as serialindicator from "./serialindicator" @@ -137,6 +138,7 @@ export class ProjectView textEditor: monaco.Editor; pxtJsonEditor: pxtjson.Editor; serialEditor: serial.Editor; + jacdacEditor: jacdac.Editor; blocksEditor: blocks.Editor; gitjsonEditor: gitjson.Editor; assetEditor: assetEditor.AssetEditor; @@ -208,7 +210,8 @@ export class ProjectView activeTourConfig: undefined, mute: pxt.editor.MuteState.Unmuted, feedback: {showing: false, kind: "generic"}, // state that tracks if the feedback modal is showing and what kind - errorListCollapsed: true // error pane is collapsed by default + errorListCollapsed: true, // error pane is collapsed by default + showJacdac: pxt.appTarget.appTheme.showJacdac || false, // show jacdac button in the sidebar }; if (!this.settings.editorFontSize) this.settings.editorFontSize = /mobile/i.test(navigator.userAgent) ? 15 : 19; if (!this.settings.fileHistory) this.settings.fileHistory = []; @@ -222,6 +225,7 @@ export class ProjectView this.openSimSerial = this.openSimSerial.bind(this); this.openDeviceSerial = this.openDeviceSerial.bind(this); this.openSerial = this.openSerial.bind(this); + this.openJacdac = this.openJacdac.bind(this); this.toggleGreenScreen = this.toggleGreenScreen.bind(this); this.toggleScreenReaderModeAsync = this.toggleScreenReaderModeAsync.bind(this); this.toggleSimulatorFullscreen = this.toggleSimulatorFullscreen.bind(this); @@ -843,6 +847,20 @@ export class ProjectView this.setFile(mainEditorPkg.lookupFile("this/" + pxt.SERIAL_EDITOR_FILE)) } + openJacdac() { + if (this.editor == this.jacdacEditor) + return; // already showing + + const mainEditorPkg = pkg.mainEditorPkg() + if (!mainEditorPkg) return; // no project loaded + + if (!mainEditorPkg.lookupFile("this/" + pxt.JACDAC_EDITOR_FILE)) { + mainEditorPkg.setFile(pxt.JACDAC_EDITOR_FILE, "jacdac\n", true) + } + pxt.tickEvent("jacdac.editorOpened") + this.setFile(mainEditorPkg.lookupFile("this/" + pxt.JACDAC_EDITOR_FILE)) + } + openPreviousEditor() { this.preserveUndoStack = true; const id = this.state.header.id; @@ -1097,6 +1115,7 @@ export class ProjectView this.textEditor = new monaco.Editor(this); this.pxtJsonEditor = new pxtjson.Editor(this); this.serialEditor = new serial.Editor(this); + this.jacdacEditor = new jacdac.Editor(this); this.blocksEditor = new blocks.Editor(this); this.gitjsonEditor = new gitjson.Editor(this); this.assetEditor = new assetEditor.AssetEditor(this); @@ -1118,7 +1137,7 @@ export class ProjectView this.editorChangeHandler(); } } - this.allEditors = [this.pxtJsonEditor, this.gitjsonEditor, this.blocksEditor, this.serialEditor, this.assetEditor, this.textEditor] + this.allEditors = [this.pxtJsonEditor, this.gitjsonEditor, this.blocksEditor, this.serialEditor, this.jacdacEditor, this.assetEditor, this.textEditor] this.allEditors.forEach(e => e.changeCallback = changeHandler) this.editor = this.allEditors[this.allEditors.length - 1] } @@ -1130,7 +1149,25 @@ export class ProjectView public async componentDidMount() { this.allEditors.forEach(e => e.prepare()) - await simulator.initAsync({ + await simulator.initAsync(this.createSimulatorInitOptions()); + + // we now have editors prepared + this.forceUpdate(); + // start blockly load + this.loadBlocklyAsync(); + + // subscribe to user preference changes (for simulator or non-render subscriptions) + data.subscribe(this.cloudStatusSubscriber, `${cloud.HEADER_CLOUDSTATE}:*`); + data.subscribe(this.headerChangeSubscriber, "header:*"); + this.editorMountComplete.resolve(undefined); + } + + public async reinitializeSimulatorAsync() { + await simulator.initAsync(this.createSimulatorInitOptions()); + } + + private createSimulatorInitOptions(): Parameters[0] { + return { orphanException: brk => { // TODO: start debugging session // TODO: user friendly error message @@ -1194,17 +1231,7 @@ export class ProjectView pkg.mainEditorPkg().setSimState(k, v) }, editor: this.state.header ? this.state.header.editor : '' - }); - - // we now have editors prepared - this.forceUpdate(); - // start blockly load - this.loadBlocklyAsync(); - - // subscribe to user preference changes (for simulator or non-render subscriptions) - data.subscribe(this.cloudStatusSubscriber, `${cloud.HEADER_CLOUDSTATE}:*`); - data.subscribe(this.headerChangeSubscriber, "header:*"); - this.editorMountComplete.resolve(undefined); + }; } public componentWillUnmount() { @@ -5560,7 +5587,7 @@ export class ProjectView const inDebugMode = this.state.debugging; const inHome = this.state.home && !sandbox; const inEditor = !!this.state.header && !inHome; - const { lightbox, greenScreen } = this.state; + const { lightbox, greenScreen, showJacdac } = this.state; const hideTutorialIteration = inTutorial && tutorialOptions.metadata?.hideIteration; const hideToolbox = inTutorial && tutorialOptions.metadata?.hideToolbox; // flyoutOnly has become a de facto css class for styling tutorials (especially minecraft HOC), so keep it if hideToolbox is true, even if flyoutOnly is false. @@ -5613,6 +5640,7 @@ export class ProjectView sandbox && this.isEmbedSimActive() ? 'simView' : '', isApp ? "app" : "", greenScreen ? "greenscreen" : "", + showJacdac ? "showJacdac" : "", logoWide ? "logo-wide" : "", isHeadless ? "headless" : "", flyoutOnly ? "flyoutOnly" : "", @@ -5669,6 +5697,7 @@ export class ProjectView { + // give the jacdac simulator a larger view; the board simulator(s) stay + // in the sidebar and keep running + if (this.simulatorContainerRef) + simulator.driver?.showJacdacSimulator(this.simulatorContainerRef); + return super.loadFileAsync(file, hc); + } + + unloadFileAsync(unloadToHome?: boolean): Promise { + simulator.driver?.hideJacdacSimulator(); + return super.unloadFileAsync(unloadToHome); + } + + private handleSimulatorRef = (el: HTMLDivElement) => { + this.simulatorContainerRef = el; + if (el) + simulator.driver?.showJacdacSimulator(el); + } + + display() { + return ( +
+
+
+
+ + + {lf("Go back")} + +
+
+
+
+
+ ) + } +} + diff --git a/webapp/src/serialindicator.tsx b/webapp/src/serialindicator.tsx index 71f15e649f44..e03f46a75580 100644 --- a/webapp/src/serialindicator.tsx +++ b/webapp/src/serialindicator.tsx @@ -28,7 +28,7 @@ export class SerialIndicator extends data.Component void; showMiniSim: (visible?: boolean) => void; openSerial: (isSim: boolean) => void; + openJacdac: () => void; handleHardwareDebugClick: () => void; handleFullscreenButtonClick: () => void; } @@ -79,6 +84,7 @@ export class Sidepanel extends data.Component { } componentDidMount(): void { + window.addEventListener("message", this.setActive.bind(this)) this.updateShouldResize(); } @@ -86,11 +92,11 @@ export class Sidepanel extends data.Component { if ((this.state.height || state.height) && this.state.height != state.height) { this.props.setEditorOffset(); } - this.updateShouldResize(); } componentWillUnmount(): void { + window.removeEventListener("message", this.setActive.bind(this)) if (this.simResizeObserver && this.simRef) { this.simResizeObserver.unobserve(this.simRef); } @@ -100,6 +106,14 @@ export class Sidepanel extends data.Component { } } + private setActive(ev: MessageEvent) { + // check for packetio jacdac messages and show the jacdacUI toggle if so + const msg = ev.data; + if (msg?.type === "messagepacket" && msg?.sender === "packetio" && msg?.channel === "jacdac") { + this.setState({ showSimulatorDevicesToggle: true }); + } + } + private updateShouldResize() { const shouldResize = pxt.BrowserUtils.isTabletSize() || this.props.tutorialSimSidebar; if (shouldResize != this.state.shouldResize) { @@ -134,6 +148,16 @@ export class Sidepanel extends data.Component { this.props.openSerial(false); } + protected handleJacdacClick = () => { + this.props.openJacdac(); + } + + protected handleJacdacExpandClick = () => { + // loans the jacdac sim frame to the jacdac editor for a larger view; + // the microbit simulator stays put and keeps running + this.props.openJacdac(); + } + protected handleSimOverlayClick = () => { const { tutorialOptions, handleFullscreenButtonClick } = this.props; if (!tutorialOptions || pxt.BrowserUtils.useOldTutorialLayout()) { @@ -143,6 +167,14 @@ export class Sidepanel extends data.Component { } } + protected setJacdacView = (jacdacView: "simulator" | "devices") => { + if (this.state.jacdacView !== jacdacView) { + this.setState({ jacdacView }); + // let the simdriver switch between the virtual and physical jacdac bus + simulator?.driver?.setMode(jacdacView) + } + } + protected handleSimPanelRef = (c: HTMLDivElement) => { this.simPanelRef = c; if (c && typeof ResizeObserver !== "undefined") { @@ -197,7 +229,7 @@ export class Sidepanel extends data.Component { } renderCore() { - const { parent, inHome, showKeymap, showSerialButtons, showFileList, showFullscreenButton, isMultiplayerGame, + const { parent, inHome, showKeymap, showSerialButtons, showJacdacButton, showFileList, showFullscreenButton, isMultiplayerGame, collapseEditorTools, simSerialActive, deviceSerialActive, tutorialOptions, handleHardwareDebugClick, onTutorialStepChange, onTutorialComplete } = this.props; @@ -209,6 +241,13 @@ export class Sidepanel extends data.Component { const showHostMultiplayerGameButton = isMultiplayerGame && !pxt.shell.isTimeMachineEmbed(); + const jacdacUI = hasSimulator && + pxt.appTarget?.appTheme?.experiments?.some(e => e === "jacdacUI") + && !pxt.shell.isTimeMachineEmbed() && this.state.showSimulatorDevicesToggle || + true; // TODO, for now always show the toggle - not clear we want to show it only when jacdac messages are received, + + const jacdacView = this.state.jacdacView || "simulator"; + const simContainerClassName = classList( "simulator-container", !this.props.tutorialSimSidebar && "hidden" @@ -242,6 +281,8 @@ export class Sidepanel extends data.Component { onTutorialComplete={onTutorialComplete} setParentHeight={newSize => this.setComponentHeight(newSize, false)} /> : undefined; + // TODO: this is where we check if jacdacUI experiment is enabled and add switch + // TODO: to toggle between the simulator view and the twin view return
{!hasSimulator && <>
@@ -252,12 +293,43 @@ export class Sidepanel extends data.Component {
} } {hasSimulator &&
+ {jacdacUI &&
+
+
+
}
{showHostMultiplayerGameButton &&
} + {jacdacUI && jacdacView === "devices" ? <> : ( + <> {showKeymap && }
@@ -269,6 +341,14 @@ export class Sidepanel extends data.Component {
{showHostMultiplayerGameButton &&
+ {showJacdacButton &&
+
+
+ +
+ {lf("Show Jacdac Simulators/Devices")} +
+
} {showSerialButtons &&
@@ -276,6 +356,7 @@ export class Sidepanel extends data.Component { {showFileList && } {showFullscreenButton &&
} + )}
}