Pair device dialog (#1217)
* WebUSB E2E experience * Design feedback * adding docs * remove i got it * bring back download again * updated pair dialog wording * tweak style * make drive name strong * fix isAvailable -> isEnabled * upgrading pxt reference * fixed typo * bump pxt
This commit is contained in:
		
				
					committed by
					
						
						Peli de Halleux
					
				
			
			
				
	
			
			
			
						parent
						
							1b9d0f6d26
						
					
				
				
					commit
					7705fc1da5
				
			@@ -352,6 +352,7 @@
 | 
				
			|||||||
    * [Serial](/device/serial)
 | 
					    * [Serial](/device/serial)
 | 
				
			||||||
    * [Servo](/device/servo)
 | 
					    * [Servo](/device/servo)
 | 
				
			||||||
    * [Simulator](/device/simulator)
 | 
					    * [Simulator](/device/simulator)
 | 
				
			||||||
    * [Usb](/device/usb)
 | 
					    * [USB](/device/usb)
 | 
				
			||||||
 | 
					    * [WebUSB](/device/usb/webusb)
 | 
				
			||||||
    * [Flashing via HID (CMSIS-DAP)](/hidflash)
 | 
					    * [Flashing via HID (CMSIS-DAP)](/hidflash)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										31
									
								
								docs/device/usb/webusb.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								docs/device/usb/webusb.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					# WebUSB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WebUSB is an emerging web standard that allows to access USB devices from web pages.
 | 
				
			||||||
 | 
					It allows for a **one-click download** without installing any additional app or software! It also allows to receive data from the @boardname@.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Chrome 65+ for Android, Chrome OS, Linux, macOS and Windows 10.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Upgrade your Firmware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Make sure that your @boardname@ is running version **0248** or above of the firmware. Upgrading is as easy as dragging a file and it takes a few seconds to get it done.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Check out the instructions to check and upgrade your @boardname@.](https://support.microbit.org/support/solutions/articles/19000084059-beta-testing-web-usb)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Pair your device
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To get started with WebUSB,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* connect your @boardname@ to your computer with the microUSB cable
 | 
				
			||||||
 | 
					* open a script
 | 
				
			||||||
 | 
					* open the gearwheel menu and select **Pair device**
 | 
				
			||||||
 | 
					* click on the **Pair device** button and select your @boardname@ in the list
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## One-click Download
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Once your @boardname@ is paired, MakeCode will use WebUSB to transfer the code without having to drag and drop. Happy coding!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Console output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MakeCode will be able to "listen" to your @boardname@ and display the console output, generated by ``console.log`` for example.
 | 
				
			||||||
@@ -1,5 +1,13 @@
 | 
				
			|||||||
# Uploading from Chrome for Windows
 | 
					# Uploading from Chrome for Windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## ~ hint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Starting with Chrome 65 on Windows 10, 
 | 
				
			||||||
 | 
					you can use **WebUSB** to download with one-click.
 | 
				
			||||||
 | 
					[Learn more about WebUSB...](/device/usb/webusb).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## ~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
While you're writing and testing your programs, you'll mostly be [running them
 | 
					While you're writing and testing your programs, you'll mostly be [running them
 | 
				
			||||||
in the simulator](/device/simulator), but once you've finished your program you
 | 
					in the simulator](/device/simulator), but once you've finished your program you
 | 
				
			||||||
can **compile** it and run it on your micro:bit.
 | 
					can **compile** it and run it on your micro:bit.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								docs/static/download/firmware.png
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/static/download/firmware.png
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								docs/static/download/pair.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/static/download/pair.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 15 KiB  | 
@@ -432,39 +432,12 @@ namespace pxt.editor {
 | 
				
			|||||||
            });
 | 
					            });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function getFlashChecksumsAsync(wrap: DAPWrapper) {
 | 
					    function flashAsync(resp: pxtc.CompileResult, d: pxt.commands.DeployOptions = {}): Promise<void> {
 | 
				
			||||||
        log("getting existing flash checksums")
 | 
					 | 
				
			||||||
        let pages = numPages
 | 
					 | 
				
			||||||
        return wrap.cortexM.runCode(computeChecksums2, loadAddr, loadAddr + 1, 0xffffffff, stackAddr, true,
 | 
					 | 
				
			||||||
            dataAddr, 0, pageSize, pages)
 | 
					 | 
				
			||||||
            .then(() => wrap.cortexM.memory.readBlock(dataAddr, pages * 2, pageSize))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function onlyChanged(blocks: UF2.Block[], checksums: Uint8Array) {
 | 
					 | 
				
			||||||
        return blocks.filter(b => {
 | 
					 | 
				
			||||||
            let idx = b.targetAddr / pageSize
 | 
					 | 
				
			||||||
            U.assert((idx | 0) == idx)
 | 
					 | 
				
			||||||
            U.assert(b.data.length == pageSize)
 | 
					 | 
				
			||||||
            if (idx * 8 + 8 > checksums.length)
 | 
					 | 
				
			||||||
                return true // out of range?
 | 
					 | 
				
			||||||
            let c0 = HF2.read32(checksums, idx * 8)
 | 
					 | 
				
			||||||
            let c1 = HF2.read32(checksums, idx * 8 + 4)
 | 
					 | 
				
			||||||
            let ch = murmur3_core(b.data)
 | 
					 | 
				
			||||||
            if (c0 == ch[0] && c1 == ch[1])
 | 
					 | 
				
			||||||
                return false
 | 
					 | 
				
			||||||
            return true
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export function deployCoreAsync(resp: pxtc.CompileResult, d: pxt.commands.DeployOptions = {}): Promise<void> {
 | 
					 | 
				
			||||||
        let saveHexAsync = () => {
 | 
					 | 
				
			||||||
            return pxt.commands.saveOnlyAsync(resp)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        startTime = 0
 | 
					        startTime = 0
 | 
				
			||||||
        let wrap: DAPWrapper
 | 
					        let wrap: DAPWrapper
 | 
				
			||||||
        log("init")
 | 
					        log("init")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        d.showNotification(U.lf("Downloading..."));
 | 
				
			||||||
        pxt.tickEvent("hid.flash.start");
 | 
					        pxt.tickEvent("hid.flash.start");
 | 
				
			||||||
        return Promise.resolve()
 | 
					        return Promise.resolve()
 | 
				
			||||||
            .then(() => {
 | 
					            .then(() => {
 | 
				
			||||||
@@ -508,7 +481,7 @@ namespace pxt.editor {
 | 
				
			|||||||
                        .then(() => {
 | 
					                        .then(() => {
 | 
				
			||||||
                            return resp.confirmAsync({
 | 
					                            return resp.confirmAsync({
 | 
				
			||||||
                                header: lf("Something went wrong..."),
 | 
					                                header: lf("Something went wrong..."),
 | 
				
			||||||
                                body: lf("Flashing your {0} took too long. Please disconnect your {0} from your computer and reconnect it, then flash using drag and drop.", pxt.appTarget.appTheme.boardName || lf("device")),
 | 
					                                body: lf("One-click download took too long. Please disconnect your {0} from your computer and reconnect it, then manually download your program using drag and drop.", pxt.appTarget.appTheme.boardName || lf("device")),
 | 
				
			||||||
                                disagreeLbl: lf("Ok"),
 | 
					                                disagreeLbl: lf("Ok"),
 | 
				
			||||||
                                hideAgree: true
 | 
					                                hideAgree: true
 | 
				
			||||||
                            });
 | 
					                            });
 | 
				
			||||||
@@ -516,21 +489,75 @@ namespace pxt.editor {
 | 
				
			|||||||
                        .then(() => {
 | 
					                        .then(() => {
 | 
				
			||||||
                            return pxt.commands.saveOnlyAsync(resp);
 | 
					                            return pxt.commands.saveOnlyAsync(resp);
 | 
				
			||||||
                        });
 | 
					                        });
 | 
				
			||||||
 | 
					                } else if (e.isUserError) {
 | 
				
			||||||
 | 
					                    d.reportError(e.message);
 | 
				
			||||||
 | 
					                    return Promise.resolve();
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    pxt.tickEvent("hid.flash.unknownerror");
 | 
					                    pxt.tickEvent("hid.flash.unknownerror");
 | 
				
			||||||
                    return resp.confirmAsync({
 | 
					                    return resp.confirmAsync({
 | 
				
			||||||
                        header: U.lf("We cannot flash your program..."),
 | 
					                        header: U.lf("Something went wrong..."),
 | 
				
			||||||
                        body: U.lf("Please flash your device using drag and drop this time. Automatic flashing might work afterwards."),
 | 
					                        body: U.lf("Please manually download your program to your device using drag and drop. One-click download might work afterwards."),
 | 
				
			||||||
                        disagreeLbl: lf("Ok"),
 | 
					                        disagreeLbl: lf("Ok"),
 | 
				
			||||||
                        hideAgree: true
 | 
					                        hideAgree: true
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                        .then(() => {
 | 
					                        .then(() => {
 | 
				
			||||||
                            return saveHexAsync();
 | 
					                            return pxt.commands.saveOnlyAsync(resp);
 | 
				
			||||||
                        });
 | 
					                        });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function getFlashChecksumsAsync(wrap: DAPWrapper) {
 | 
				
			||||||
 | 
					        log("getting existing flash checksums")
 | 
				
			||||||
 | 
					        let pages = numPages
 | 
				
			||||||
 | 
					        return wrap.cortexM.runCode(computeChecksums2, loadAddr, loadAddr + 1, 0xffffffff, stackAddr, true,
 | 
				
			||||||
 | 
					            dataAddr, 0, pageSize, pages)
 | 
				
			||||||
 | 
					            .then(() => wrap.cortexM.memory.readBlock(dataAddr, pages * 2, pageSize))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function onlyChanged(blocks: UF2.Block[], checksums: Uint8Array) {
 | 
				
			||||||
 | 
					        return blocks.filter(b => {
 | 
				
			||||||
 | 
					            let idx = b.targetAddr / pageSize
 | 
				
			||||||
 | 
					            U.assert((idx | 0) == idx)
 | 
				
			||||||
 | 
					            U.assert(b.data.length == pageSize)
 | 
				
			||||||
 | 
					            if (idx * 8 + 8 > checksums.length)
 | 
				
			||||||
 | 
					                return true // out of range?
 | 
				
			||||||
 | 
					            let c0 = HF2.read32(checksums, idx * 8)
 | 
				
			||||||
 | 
					            let c1 = HF2.read32(checksums, idx * 8 + 4)
 | 
				
			||||||
 | 
					            let ch = murmur3_core(b.data)
 | 
				
			||||||
 | 
					            if (c0 == ch[0] && c1 == ch[1])
 | 
				
			||||||
 | 
					                return false
 | 
				
			||||||
 | 
					            return true
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export function deployCoreAsync(resp: pxtc.CompileResult, d: pxt.commands.DeployOptions = {}): Promise<void> {
 | 
				
			||||||
 | 
					        const saveHexAsync = () => {
 | 
				
			||||||
 | 
					            return pxt.commands.saveOnlyAsync(resp);
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        return Promise.resolve()
 | 
				
			||||||
 | 
					            .then(() => {
 | 
				
			||||||
 | 
					                const isUwp = !!(window as any).Windows;
 | 
				
			||||||
 | 
					                if (isUwp) {
 | 
				
			||||||
 | 
					                    // Go straight to flashing
 | 
				
			||||||
 | 
					                    return flashAsync(resp, d);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (!pxt.usb.isEnabled) {
 | 
				
			||||||
 | 
					                    return saveHexAsync();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return pxt.usb.isPairedAsync()
 | 
				
			||||||
 | 
					                    .then((isPaired) => {
 | 
				
			||||||
 | 
					                        if (isPaired) {
 | 
				
			||||||
 | 
					                            // Already paired from earlier in the session or from previous session
 | 
				
			||||||
 | 
					                            return flashAsync(resp, d);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // No device paired, prompt user
 | 
				
			||||||
 | 
					                        return saveHexAsync();
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     *       <block type="device_show_leds">
 | 
					     *       <block type="device_show_leds">
 | 
				
			||||||
        <field name="LED00">FALSE</field>
 | 
					        <field name="LED00">FALSE</field>
 | 
				
			||||||
@@ -798,6 +825,7 @@ namespace pxt.editor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        res.blocklyPatch = patchBlocks;
 | 
					        res.blocklyPatch = patchBlocks;
 | 
				
			||||||
        res.showUploadInstructionsAsync = showUploadInstructionsAsync;
 | 
					        res.showUploadInstructionsAsync = showUploadInstructionsAsync;
 | 
				
			||||||
 | 
					        res.webUsbPairDialogAsync = webUsbPairDialogAsync;
 | 
				
			||||||
        return Promise.resolve<pxt.editor.ExtensionResult>(res);
 | 
					        return Promise.resolve<pxt.editor.ExtensionResult>(res);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -832,29 +860,96 @@ namespace pxt.editor {
 | 
				
			|||||||
        valueNode.appendChild(s);
 | 
					        valueNode.appendChild(s);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function showUploadInstructionsAsync(fn: string, url: string, confirmAsync: (options: any) => Promise<number>) {
 | 
					    function webUsbPairDialogAsync(confirmAsync: (options: any) => Promise<number>): Promise<number> {
 | 
				
			||||||
        const boardName = pxt.appTarget.appTheme.boardName || "???";
 | 
					        const boardName = pxt.appTarget.appTheme.boardName || "???";
 | 
				
			||||||
        const boardDriveName = pxt.appTarget.appTheme.driveDisplayName || pxt.appTarget.compile.driveName || "???";
 | 
					 | 
				
			||||||
        const canWebusb = pxt.usb.isEnabled;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // https://msdn.microsoft.com/en-us/library/cc848897.aspx
 | 
					 | 
				
			||||||
        // "For security reasons, data URIs are restricted to downloaded resources. 
 | 
					 | 
				
			||||||
        // Data URIs cannot be used for navigation, for scripting, or to populate frame or iframe elements"
 | 
					 | 
				
			||||||
        const downloadAgain = false // !pxt.BrowserUtils.isIE() && !pxt.BrowserUtils.isEdge();
 | 
					 | 
				
			||||||
        const docUrl = pxt.appTarget.appTheme.usbDocs;
 | 
					 | 
				
			||||||
        const columns = canWebusb ? "eleven" : "sixteen";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const htmlBody = `
 | 
					        const htmlBody = `
 | 
				
			||||||
        <div class="ui grid stackable">
 | 
					        <div class="ui grid stackable">
 | 
				
			||||||
            ${canWebusb ? `<div class="column five wide" style="background-color: #E2E2E2;">
 | 
					            <div class="column five wide" style="background-color: #FFFFCE;">
 | 
				
			||||||
                <div class="ui header">${lf("One click download?")}</div>
 | 
					                <div class="ui header">${lf("First time here?")}</div>
 | 
				
			||||||
                <strong style="font-size:small">${lf("Pair your device to download instantly.")}</strong>
 | 
					                <strong style="font-size:small">${lf("You must have version 0248 or above of the firmware")}</strong>
 | 
				
			||||||
                <div style="justify-content: center;display: flex;padding: 1rem;">
 | 
					                <div style="justify-content: center;display: flex;padding: 1rem;">
 | 
				
			||||||
                    <img class="ui image" src="./static/download/firmware.png" style="height:100px;" />
 | 
					                    <img class="ui image" src="./static/download/firmware.png" style="height:100px;" />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <a href="https://support.microbit.org/support/solutions/articles/19000084059-beta-testing-web-usb" target="_blank">${lf("Check your firmware version here and update if needed")}</a>
 | 
					                <a href="https://support.microbit.org/support/solutions/articles/19000084059-beta-testing-web-usb" target="_blank">${lf("Check your firmware version here and update if needed")}</a>
 | 
				
			||||||
            </div>` : ''}
 | 
					            </div>
 | 
				
			||||||
            <div class="column ${columns} wide">
 | 
					            <div class="column eleven wide">
 | 
				
			||||||
 | 
					                <div class="ui grid">
 | 
				
			||||||
 | 
					                    <div class="row">
 | 
				
			||||||
 | 
					                        <div class="column">
 | 
				
			||||||
 | 
					                            <div class="ui two column grid padded">
 | 
				
			||||||
 | 
					                                <div class="column">
 | 
				
			||||||
 | 
					                                    <div class="ui">
 | 
				
			||||||
 | 
					                                        <div class="image">
 | 
				
			||||||
 | 
					                                            <img class="ui medium rounded image" src="./static/download/connect.png" style="margin-bottom:1rem;" />
 | 
				
			||||||
 | 
					                                        </div>
 | 
				
			||||||
 | 
					                                        <div class="content">
 | 
				
			||||||
 | 
					                                            <div class="description">
 | 
				
			||||||
 | 
					                                                <span class="ui purple circular label">1</span>
 | 
				
			||||||
 | 
					                                                <strong>${lf("Connect the {0} to your computer with a USB cable", boardName)}</strong>
 | 
				
			||||||
 | 
					                                                <br />
 | 
				
			||||||
 | 
					                                                <span style="font-size:small">${lf("Use the microUSB port on the top of the {0}", boardName)}</span>
 | 
				
			||||||
 | 
					                                            </div>
 | 
				
			||||||
 | 
					                                        </div>
 | 
				
			||||||
 | 
					                                    </div>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                                <div class="column">
 | 
				
			||||||
 | 
					                                    <div class="ui">
 | 
				
			||||||
 | 
					                                        <div class="image">
 | 
				
			||||||
 | 
					                                            <img class="ui medium rounded image" src="./static/download/pair.png" style="margin-bottom:1rem;" />
 | 
				
			||||||
 | 
					                                        </div>
 | 
				
			||||||
 | 
					                                        <div class="content">
 | 
				
			||||||
 | 
					                                            <div class="description">
 | 
				
			||||||
 | 
					                                                <span class="ui purple circular label">2</span>
 | 
				
			||||||
 | 
					                                                <strong>${lf("Pair your {0}", boardName)}</strong>
 | 
				
			||||||
 | 
					                                                <br />
 | 
				
			||||||
 | 
					                                                <span style="font-size:small">${lf("Click 'Pair device' below and select <strong>BBC micro:bit CMSIS-DAP</strong> or <strong>DAPLink CMSIS-DAP</strong> from the list")}</span>
 | 
				
			||||||
 | 
					                                            </div>
 | 
				
			||||||
 | 
					                                        </div>
 | 
				
			||||||
 | 
					                                    </div>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const buttons: any[] = [];
 | 
				
			||||||
 | 
					        const docUrl = pxt.appTarget.appTheme.usbDocs;
 | 
				
			||||||
 | 
					        if (docUrl) {
 | 
				
			||||||
 | 
					            buttons.push({
 | 
				
			||||||
 | 
					                label: lf("Help"),
 | 
				
			||||||
 | 
					                icon: "help",
 | 
				
			||||||
 | 
					                className: "lightgrey",
 | 
				
			||||||
 | 
					                url: `${docUrl}/webusb`
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return confirmAsync({
 | 
				
			||||||
 | 
					            header: lf("Pair device for one-click downloads"),
 | 
				
			||||||
 | 
					            htmlBody,
 | 
				
			||||||
 | 
					            hasCloseIcon: true,
 | 
				
			||||||
 | 
					            agreeLbl: lf("Pair device"),
 | 
				
			||||||
 | 
					            agreeIcon: "usb",
 | 
				
			||||||
 | 
					            hideCancel: true,
 | 
				
			||||||
 | 
					            className: 'downloaddialog',
 | 
				
			||||||
 | 
					            buttons
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function showUploadInstructionsAsync(fn: string, url: string, confirmAsync: (options: any) => Promise<number>) {
 | 
				
			||||||
 | 
					        const boardName = pxt.appTarget.appTheme.boardName || "???";
 | 
				
			||||||
 | 
					        const boardDriveName = pxt.appTarget.appTheme.driveDisplayName || pxt.appTarget.compile.driveName || "???";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // https://msdn.microsoft.com/en-us/library/cc848897.aspx
 | 
				
			||||||
 | 
					        // "For security reasons, data URIs are restricted to downloaded resources.
 | 
				
			||||||
 | 
					        // Data URIs cannot be used for navigation, for scripting, or to populate frame or iframe elements"
 | 
				
			||||||
 | 
					        const downloadAgain = !pxt.BrowserUtils.isIE() && !pxt.BrowserUtils.isEdge();
 | 
				
			||||||
 | 
					        const docUrl = pxt.appTarget.appTheme.usbDocs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const htmlBody = `
 | 
				
			||||||
 | 
					        <div class="ui grid stackable">
 | 
				
			||||||
 | 
					            <div class="column sixteen wide">
 | 
				
			||||||
                <div class="ui grid">
 | 
					                <div class="ui grid">
 | 
				
			||||||
                    <div class="row">
 | 
					                    <div class="row">
 | 
				
			||||||
                        <div class="column">
 | 
					                        <div class="column">
 | 
				
			||||||
@@ -884,7 +979,7 @@ namespace pxt.editor {
 | 
				
			|||||||
                                                <span class="ui purple circular label">2</span>
 | 
					                                                <span class="ui purple circular label">2</span>
 | 
				
			||||||
                                                <strong>${lf("Move the .hex file to the {0}", boardName)}</strong>
 | 
					                                                <strong>${lf("Move the .hex file to the {0}", boardName)}</strong>
 | 
				
			||||||
                                                <br />
 | 
					                                                <br />
 | 
				
			||||||
                                                <span style="font-size:small">${lf("Locate the downloaded .hex file and drag it to the {0} drive", boardDriveName)}</span>
 | 
					                                                <span style="font-size:small">${lf("Locate the downloaded .hex file and drag it to the <strong>{0}</strong> drive", boardDriveName)}</span>
 | 
				
			||||||
                                            </div>
 | 
					                                            </div>
 | 
				
			||||||
                                        </div>
 | 
					                                        </div>
 | 
				
			||||||
                                    </div>
 | 
					                                    </div>
 | 
				
			||||||
@@ -896,33 +991,35 @@ namespace pxt.editor {
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>`;
 | 
					        </div>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return confirmAsync({
 | 
					        const buttons: any[] = [];
 | 
				
			||||||
            header: lf("Download to your micro:bit"),
 | 
					
 | 
				
			||||||
            htmlBody,
 | 
					        if (downloadAgain) {
 | 
				
			||||||
            hasCloseIcon: true,
 | 
					            buttons.push({
 | 
				
			||||||
            hideCancel: true,
 | 
					 | 
				
			||||||
            hideAgree: false,
 | 
					 | 
				
			||||||
            agreeLbl: lf("I got it"),
 | 
					 | 
				
			||||||
            className: 'downloaddialog',
 | 
					 | 
				
			||||||
            buttons: [downloadAgain ? {
 | 
					 | 
				
			||||||
                label: fn,
 | 
					                label: fn,
 | 
				
			||||||
                icon: "download",
 | 
					                icon: "download",
 | 
				
			||||||
                className: "lightgrey focused",
 | 
					                className: "lightgrey focused",
 | 
				
			||||||
                url,
 | 
					                url,
 | 
				
			||||||
                fileName: fn
 | 
					                fileName: fn
 | 
				
			||||||
            } : undefined, canWebusb ? {
 | 
					            });
 | 
				
			||||||
                label: lf("Pair device"),
 | 
					        }
 | 
				
			||||||
                icon: "usb",
 | 
					
 | 
				
			||||||
                className: "lightgrey focused",
 | 
					        if (docUrl) {
 | 
				
			||||||
                onclick: () => {
 | 
					            buttons.push({
 | 
				
			||||||
                    pxt.usb.pairAsync().done();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } : undefined, docUrl ? {
 | 
					 | 
				
			||||||
                label: lf("Help"),
 | 
					                label: lf("Help"),
 | 
				
			||||||
                icon: "help",
 | 
					                icon: "help",
 | 
				
			||||||
                className: "lightgrey",
 | 
					                className: "lightgrey",
 | 
				
			||||||
                url: docUrl
 | 
					                url: docUrl
 | 
				
			||||||
            } : undefined]
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return confirmAsync({
 | 
				
			||||||
 | 
					            header: lf("Download to your {0}", pxt.appTarget.appTheme.boardName),
 | 
				
			||||||
 | 
					            htmlBody,
 | 
				
			||||||
 | 
					            hasCloseIcon: true,
 | 
				
			||||||
 | 
					            hideCancel: true,
 | 
				
			||||||
 | 
					            hideAgree: true,
 | 
				
			||||||
 | 
					            className: 'downloaddialog',
 | 
				
			||||||
 | 
					            buttons
 | 
				
			||||||
            //timeout: 20000
 | 
					            //timeout: 20000
 | 
				
			||||||
        }).then(() => { });
 | 
					        }).then(() => { });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,6 +42,6 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "pxt-common-packages": "0.23.53",
 | 
					    "pxt-common-packages": "0.23.53",
 | 
				
			||||||
    "pxt-core": "3.22.18"
 | 
					    "pxt-core": "3.22.20"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -90,7 +90,8 @@
 | 
				
			|||||||
                "vid": "0x0d28",
 | 
					                "vid": "0x0d28",
 | 
				
			||||||
                "pid": "0x0204"
 | 
					                "pid": "0x0204"
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        ]
 | 
					        ],
 | 
				
			||||||
 | 
					        "webUSB": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "runtime": {
 | 
					    "runtime": {
 | 
				
			||||||
        "mathBlocks": true,
 | 
					        "mathBlocks": true,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -79,3 +79,9 @@
 | 
				
			|||||||
/* Large Monitor */
 | 
					/* Large Monitor */
 | 
				
			||||||
@media only screen and (min-width: @largeMonitorBreakpoint) {
 | 
					@media only screen and (min-width: @largeMonitorBreakpoint) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Download dialog */
 | 
				
			||||||
 | 
					.ui.downloaddialog.modal>.content {
 | 
				
			||||||
 | 
					    padding: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user