1
0
Fork 0
mirror of https://github.com/sensebox/blockly-app synced 2025-10-21 08:03:54 +02:00

UX improvements

This commit is contained in:
Norwin 2019-02-17 17:37:35 +01:00
parent 8af35cd30c
commit 4e14d01112
5 changed files with 149 additions and 51 deletions

View file

@ -13,12 +13,14 @@
}, },
"OPENSOURCE": { "OPENSOURCE": {
"TITLE": "Open Source", "TITLE": "Open Source",
"TEXT": "This app is free & libre open source software. To may find the source code & issue tracker on GitHub:" "TEXT": "This app is free & libre open source software. You may find the source code & issue tracker on GitHub:"
}, },
"HINT": "<em>Hint:</em> A <a href=\"https://blockly.sensebox.de\">web version of Blockly for senseBox</a> is available as well!" "HINT": "<em>Hint:</em> A <a href=\"https://blockly.sensebox.de\">web version of Blockly for senseBox</a> is available as well!"
}, },
"BLOCKLY": { "BLOCKLY": {
"TITLE": "Blockly for senseBox" "TITLE": "Blockly for senseBox",
"BTN_CODE": "Code View",
"BTN_OTA": "OTA Programmer"
}, },
"OTAWIZ": { "OTAWIZ": {
"TITLE": "Over The Air Programmer", "TITLE": "Over The Air Programmer",
@ -27,19 +29,27 @@
"BTN_BACK": "Back", "BTN_BACK": "Back",
"INTRO": { "INTRO": {
"TITLE": "Welcome", "TITLE": "Welcome",
"TEXT": "To <b>program your senseBox</b> wirelessly, please follow these steps. We will help you connect to your senseBox via <b>WiFi</b>.", "TEXT": "To transfer your code to your senseBox over the air (OTA), please follow these steps.",
"STEPS": "First, please make sure that your senseBox...", "STEPS": "First, please make sure that your senseBox...",
"STEP1": "has the <b>WiFi shield</b> plugged in", "STEP1": "has the <b>WiFi Bee</b> plugged in XBee Slot 1",
"STEP2": "has the initial <b>OTA sketch</b> installed", "STEP2": "has the initial <b>OTA sketch</b> installed",
"STEP3": "is running" "STEP3": "is powered"
},
"OTAMODE": {
"TITLE": "Enable OTA Mode",
"TEXT": "To transfer your code, the senseBox must be in OTA mode.<br/>This is indicated by the green blinking LED next to the red reset button.",
"STEPS": "To enable OTA mode...",
"STEP1": "press &amp; hold the switch button (gray or blue)",
"STEP2": "press the red reset button shortly",
"STEP3": "release the switch button after one second"
}, },
"COMPILATION": { "COMPILATION": {
"COMPILING": { "COMPILING": {
"TITLE": "Compiling your sketch..." "TITLE": "Compiling your sketch..."
}, },
"GO_ONLINE": { "GO_ONLINE": {
"TITLE": "Compiling your sketch...", "TITLE": "You are offline.",
"TEXT": "For compilation, you need to connect to the internet. Please enable a connection." "TEXT": "For compilation you need to connect to the internet. Please enable a connection."
}, },
"DONE": { "DONE": {
"TITLE": "Sketch successfully compiled." "TITLE": "Sketch successfully compiled."
@ -51,11 +61,13 @@
"WIFI": { "WIFI": {
"MANUAL": { "MANUAL": {
"TITLE": "Connect to your senseBox", "TITLE": "Connect to your senseBox",
"TEXT": "Your senseBox should have opened the a WiFi network. Because we can not do this automatically on your platform, please connect to it manually." "TEXT": "Your senseBox should have created a WiFi network. Because we can not do this automatically on your platform, please connect to it manually."
}, },
"AUTO": { "AUTO": {
"TITLE": "Select your senseBox", "TITLE": "Select your senseBox",
"TEXT": "In the list, all running senseBoxes with OTA available are shown. If you don't see yours, please make sure that GPS is enabled.", "TEXT1": "All powered senseBoxes around you with OTA mode enabled are listed here.",
"TEXT2": "Please check the last digits of MAC-Adress on your WiFi Bee to identify your senseBox. You can find it printed on the bottom of the WiFi Bee.",
"TEXT3": "If you can't find your senseBox, please make sure that your WiFi Bee is mounted correctly, OTA mode is enabled and GPS is enabled on this device.",
"AVAILABLE": "Available senseBox WiFis", "AVAILABLE": "Available senseBox WiFis",
"SCANNING": "searching...", "SCANNING": "searching...",
"CONNECTING": "connecting...", "CONNECTING": "connecting...",

View file

@ -14,12 +14,28 @@
</ion-header> </ion-header>
<ion-content> <ion-content>
<ion-fab top right edge style="right: 4vh"> <ion-fab top right edge style="right: 2vh">
<button ion-fab large (click)="launchOtaWizard()" color="light"><ion-icon name="wifi"></ion-icon></button> <button
ion-fab
large
color ="light"
[title]="'BLOCKLY.BTN_OTA' | translate"
(click)="launchOtaWizard();"
>
<ion-icon name="wifi"></ion-icon>
</button>
</ion-fab> </ion-fab>
<ion-fab top right edge style="right: calc(4vh + 60px)"> <ion-fab top right edge style="right: calc(2vh + 60px)">
<button ion-fab mini (click)="toggleView();" color ="light"><ion-icon name="code"></ion-icon></button> <button
ion-fab
mini
color ="light"
[title]="'BLOCKLY.BTN_CODE' | translate"
(click)="toggleView();"
>
<ion-icon name="code"></ion-icon>
</button>
</ion-fab> </ion-fab>
<iframe #blocklyFrame scrolling="no" src='assets/blockly.html'></iframe> <iframe #blocklyFrame scrolling="no" src='assets/blockly.html'></iframe>

View file

@ -7,13 +7,13 @@
</ion-navbar> </ion-navbar>
</ion-header> </ion-header>
<ion-content> <ion-content padding>
<ion-slides #slides pager (ionSlideDidChange)="onSlideChange()"> <ion-slides #slides pager (ionSlideDidChange)="onSlideChange()">
<!-- intro --> <!-- intro -->
<ion-slide> <ion-slide>
<ion-grid> <ion-grid>
<ion-row> <ion-row align-items-center>
<ion-col col-12 col-md-6> <ion-col col-12 col-md-6>
<ion-icon name="wifi" style="font-size: 160px"></ion-icon> <ion-icon name="wifi" style="font-size: 160px"></ion-icon>
<h2 translate>OTAWIZ.INTRO.TITLE</h2> <h2 translate>OTAWIZ.INTRO.TITLE</h2>
@ -36,11 +36,38 @@
</ion-grid> </ion-grid>
</ion-slide> </ion-slide>
<!-- compilation waiting screen --> <!-- OTA mode guide -->
<ion-slide> <ion-slide>
<ion-grid>
<ion-row align-items-center>
<ion-col col-12 col-md-6>
<h2 translate>OTAWIZ.OTAMODE.TITLE</h2>
<p [innerHTML]="'OTAWIZ.OTAMODE.TEXT' | translate"></p>
<p translate>OTAWIZ.OTAMODE.STEPS</p>
<ol style="text-align: left">
<li [innerHTML]="'OTAWIZ.OTAMODE.STEP1' | translate"></li>
<li [innerHTML]="'OTAWIZ.OTAMODE.STEP2' | translate"></li>
<li [innerHTML]="'OTAWIZ.OTAMODE.STEP3' | translate"></li>
</ol>
</ion-col>
<ion-col col-12 col-md-6>
<!-- @TODO: diagram! -->
<button ion-button large clear icon-end color="primary" (click)="slides.slideNext()">
{{ 'OTAWIZ.BTN_NEXT' | translate }}
<ion-icon name="arrow-forward"></ion-icon>
</button>
</ion-col>
</ion-row>
</ion-grid>
</ion-slide>
<!-- compilation waiting screen -->
<ion-slide *ngIf="!slideIsHidden(slideCompilation)">
<ng-container *ngIf="state.compilation == 'compiling'"> <ng-container *ngIf="state.compilation == 'compiling'">
<h2 translate>OTAWIZ.COMPILATION.COMPILING.TITLE</h2> <h2 translate>OTAWIZ.COMPILATION.COMPILING.TITLE</h2>
<ion-spinner *ngIf="slides.getActiveIndex() == 1" item-start name="dots"></ion-spinner> <!-- getActiveIndex() check because animated icons use loads of CPU, even when not visible! -->
<ion-spinner *ngIf="currentSlide == slideCompilation" item-start name="dots"></ion-spinner>
</ng-container> </ng-container>
<ng-container *ngIf="state.compilation == 'go-online'"> <ng-container *ngIf="state.compilation == 'go-online'">
@ -78,7 +105,11 @@
<ion-col col-12 col-md-6> <ion-col col-12 col-md-6>
<ion-icon name="wifi" style="font-size: 160px"></ion-icon> <ion-icon name="wifi" style="font-size: 160px"></ion-icon>
<h2 translate>OTAWIZ.WIFI.AUTO.TITLE</h2> <h2 translate>OTAWIZ.WIFI.AUTO.TITLE</h2>
<p translate>OTAWIZ.WIFI.AUTO.TEXT</p> <ul style="text-align: left">
<li translate>OTAWIZ.WIFI.AUTO.TEXT1</li>
<li translate>OTAWIZ.WIFI.AUTO.TEXT2</li>
<li translate>OTAWIZ.WIFI.AUTO.TEXT3</li>
</ul>
</ion-col> </ion-col>
<ion-col col-12 col-md-6> <ion-col col-12 col-md-6>
@ -89,12 +120,12 @@
<ion-list id="wifi-list"> <ion-list id="wifi-list">
<ion-item *ngIf="state.wifiSelection == 'scanning'"> <ion-item *ngIf="state.wifiSelection == 'scanning'">
<ion-spinner *ngIf="slides.getActiveIndex() == 2" item-start name="dots"></ion-spinner> <ion-spinner *ngIf="currentSlide == slideWifi" item-start name="dots"></ion-spinner>
{{ 'OTAWIZ.WIFI.AUTO.SCANNING' | translate }} {{ 'OTAWIZ.WIFI.AUTO.SCANNING' | translate }}
</ion-item> </ion-item>
<ion-item *ngIf="state.wifiSelection == 'connecting'"> <ion-item *ngIf="state.wifiSelection == 'connecting'">
<ion-spinner *ngIf="slides.getActiveIndex() == 2" item-start name="dots"></ion-spinner> <ion-spinner *ngIf="currentSlide == slideWifi" item-start name="dots"></ion-spinner>
{{ 'OTAWIZ.WIFI.AUTO.CONNECTING' | translate }} {{ 'OTAWIZ.WIFI.AUTO.CONNECTING' | translate }}
</ion-item> </ion-item>
@ -122,7 +153,7 @@
<ion-slide> <ion-slide>
<ng-container *ngIf="state.upload == 'uploading'"> <ng-container *ngIf="state.upload == 'uploading'">
<h2 translate>OTAWIZ.UPLOAD.UPLOADING</h2> <h2 translate>OTAWIZ.UPLOAD.UPLOADING</h2>
<ion-spinner *ngIf="slides.getActiveIndex() == 3" item-start name="dots"></ion-spinner> <ion-spinner *ngIf="currentSlide == slideUpload" item-start name="dots"></ion-spinner>
</ng-container> </ng-container>
<ng-container *ngIf="state.upload == 'done'"> <ng-container *ngIf="state.upload == 'done'">

View file

@ -20,10 +20,10 @@ page-ota-wizard {
} }
} }
p, ul { p, ul, ol {
padding: 0 20px; padding: 0 20px;
font-size: 16px; font-size: 16px;
line-height: 1.4; line-height: 1.5;
color: #60646B; color: #60646B;
b { b {
@ -32,8 +32,13 @@ page-ota-wizard {
} }
} }
ul, ol {
margin-left: 4%;
li {
padding-bottom: 8px;
}
}
ul { ul {
margin-left: 3%;
list-style-type: disc; list-style-type: disc;
} }
} }
@ -54,7 +59,5 @@ page-ota-wizard {
overflow-y: scroll; overflow-y: scroll;
} }
} }
} }
} }

View file

@ -3,6 +3,7 @@ import {
OnDestroy, OnDestroy,
OnInit, OnInit,
ViewChild, ViewChild,
ChangeDetectorRef,
} from '@angular/core' } from '@angular/core'
import { import {
IonicPage, IonicPage,
@ -23,14 +24,8 @@ import { CompilerProvider } from '../../providers/compiler/compiler';
}) })
export class OtaWizardPage implements OnInit, OnDestroy { export class OtaWizardPage implements OnInit, OnDestroy {
@ViewChild(Slides) slides: Slides @ViewChild(Slides) slides: Slides
onlineSub: Subscription
offlineSub: Subscription
sketch = ''
availableSenseboxes: string[] = [] // list of SSIDs availableSenseboxes: string[] = [] // list of SSIDs
compiledSketch: ArrayBuffer = undefined
errorMsg = '' errorMsg = ''
state: OtaState = { state: OtaState = {
isOnline: false, isOnline: false,
compilation: 'compiling', compilation: 'compiling',
@ -38,12 +33,24 @@ export class OtaWizardPage implements OnInit, OnDestroy {
upload: 'uploading', upload: 'uploading',
} }
// for unified slide index access in the template
slideCompilation = OtaSlides.Compilation
slideWifi = OtaSlides.WifiSelection
slideUpload = OtaSlides.Upload
private onlineSub: Subscription
private offlineSub: Subscription
private sketch = ''
private compiledSketch: ArrayBuffer = undefined
private hiddenSlides: OtaSlides[] = []
constructor( constructor(
private network: Network, private network: Network,
private otaWifi: OtaWifiProvider, private otaWifi: OtaWifiProvider,
private navCtrl: NavController, private navCtrl: NavController,
private webcompiler: CompilerProvider,
private changedetect: ChangeDetectorRef,
navParams : NavParams, navParams : NavParams,
private compilerprovider:CompilerProvider
) { ) {
// for OTA to work, the new sketch has to include the OTA logic as well. // for OTA to work, the new sketch has to include the OTA logic as well.
// to ensure that, we're prepending it here to the sketch. // to ensure that, we're prepending it here to the sketch.
@ -61,6 +68,14 @@ export class OtaWizardPage implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
// try to start compilation already in the background // try to start compilation already in the background
this.compileSketch() this.compileSketch()
.then(() => this.hideSlide(OtaSlides.Compilation))
if (this.otaWifi.strategy === WifiStrategy.Automatic) {
this.otaWifi.findSenseboxes(true)
.then(ssids => this.availableSenseboxes = ssids)
} else {
this.state.wifiSelection = 'manual'
}
this.state.isOnline = this.network.type !== 'none' this.state.isOnline = this.network.type !== 'none'
@ -81,7 +96,7 @@ export class OtaWizardPage implements OnInit, OnDestroy {
} }
onWifiRefresh () { onWifiRefresh () {
this.handleWifiSelection() this.handleWifiSelection(true)
} }
onClose () { onClose () {
@ -90,8 +105,9 @@ export class OtaWizardPage implements OnInit, OnDestroy {
// call logic for each slide // call logic for each slide
onSlideChange () { onSlideChange () {
switch (this.slides.getActiveIndex()) { switch (this.currentSlide) {
case OtaSlides.Intro: case OtaSlides.Intro:
case OtaSlides.Intro2:
this.slides.lockSwipeToNext(false) this.slides.lockSwipeToNext(false)
break break
@ -112,6 +128,23 @@ export class OtaWizardPage implements OnInit, OnDestroy {
} }
} }
get currentSlide (): OtaSlides {
const current = this.slides.getActiveIndex()
const hiddenOffset = this.hiddenSlides.filter(slide => slide <= current).length
return current + hiddenOffset
}
slideIsHidden (slide: OtaSlides): boolean {
return this.hiddenSlides.indexOf(slide) !== -1
}
private hideSlide (slide: OtaSlides) {
if (this.currentSlide === slide) return
if (this.slideIsHidden(slide)) return
this.hiddenSlides.push(slide)
this.slides.update()
}
async connectToSensebox (ssid: string) { async connectToSensebox (ssid: string) {
this.state.wifiSelection = 'connecting' this.state.wifiSelection = 'connecting'
try { try {
@ -126,10 +159,7 @@ export class OtaWizardPage implements OnInit, OnDestroy {
} }
private handleCompilation () { private handleCompilation () {
// skip compilation slide if already compiled
this.slides.lockSwipeToNext(!this.compiledSketch) this.slides.lockSwipeToNext(!this.compiledSketch)
if (this.compiledSketch)
return this.slides.slideNext(0)
// need to go online for compilation. compilation is retriggered via this.onlineSub // need to go online for compilation. compilation is retriggered via this.onlineSub
if (!this.state.isOnline) { if (!this.state.isOnline) {
@ -145,20 +175,25 @@ export class OtaWizardPage implements OnInit, OnDestroy {
} }
} }
private async handleWifiSelection () { private async handleWifiSelection (force = false) {
if (this.otaWifi.strategy === WifiStrategy.Automatic) {
this.slides.lockSwipeToNext(true) this.slides.lockSwipeToNext(true)
if (this.otaWifi.strategy == WifiStrategy.Manual) { // skip scan when boxes where already found from the scan on startup
this.state.wifiSelection = 'manual' if (!force && this.availableSenseboxes.length)
this.slides.lockSwipeToNext(false) return this.state.wifiSelection = 'select'
} else {
this.state.wifiSelection = 'scanning'
try { try {
this.state.wifiSelection = 'scanning'
// force update of view, as setting subproperties of this.state is not detected automatically :/
this.changedetect.detectChanges()
this.availableSenseboxes = await this.otaWifi.findSenseboxes(true) this.availableSenseboxes = await this.otaWifi.findSenseboxes(true)
this.state.wifiSelection = 'select' this.state.wifiSelection = 'select'
this.changedetect.detectChanges()
} catch (err) { } catch (err) {
this.state.wifiSelection = 'error'
this.errorMsg = err.message this.errorMsg = err.message
this.state.wifiSelection = 'error'
this.changedetect.detectChanges()
} }
} }
} }
@ -181,7 +216,7 @@ export class OtaWizardPage implements OnInit, OnDestroy {
private async compileSketch () { private async compileSketch () {
this.state.compilation = 'compiling' this.state.compilation = 'compiling'
try { try {
this.compiledSketch = await this.compilerprovider.callcompiler(this.sketch) this.compiledSketch = await this.webcompiler.compile(this.sketch)
this.state.compilation = 'done' this.state.compilation = 'done'
this.slides.lockSwipeToNext(false) this.slides.lockSwipeToNext(false)
} catch (err) { } catch (err) {
@ -206,7 +241,8 @@ type OtaState = {
// names for the slide indices for easier access // names for the slide indices for easier access
enum OtaSlides { enum OtaSlides {
Intro = 0, Intro = 0,
Compilation = 1, Intro2 = 1,
WifiSelection = 2, Compilation = 2,
Upload = 3, WifiSelection = 3,
Upload = 4,
} }