Compare commits
No commits in common. 'master' and 'v1.1.1' have entirely different histories.
@ -1,39 +1,22 @@
|
||||
language: android
|
||||
language: node_js
|
||||
node_js:
|
||||
- "8"
|
||||
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- platform-tools
|
||||
- build-tools-27.0.3
|
||||
- android-27
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
before_install:
|
||||
- yes | sdkmanager "platforms;android-27" # accept android licenses
|
||||
|
||||
install:
|
||||
- nvm install 12
|
||||
- npm install -g ionic cordova
|
||||
before_script:
|
||||
- npm install
|
||||
|
||||
script:
|
||||
- npm run android:build # implies web build in www/ dir
|
||||
|
||||
before_deploy: "cp platforms/android/app/build/outputs/apk/release/app-release-unsigned.apk sensebox_blockly_${TRAVIS_TAG}.apk"
|
||||
- npm run build
|
||||
|
||||
deploy:
|
||||
# deploy web build to gh-pages branch
|
||||
- provider: pages
|
||||
skip-cleanup: true
|
||||
github-token: $GITHUB_TOKEN
|
||||
keep-history: true
|
||||
on:
|
||||
tags: true
|
||||
local_dir: www
|
||||
|
||||
- provider: releases
|
||||
api_key: $GITHUB_TOKEN
|
||||
file:
|
||||
- "sensebox_blockly_${TRAVIS_TAG}.apk"
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
provider: pages
|
||||
skip-cleanup: true
|
||||
github-token: $GITHUB_TOKEN
|
||||
keep-history: true
|
||||
on:
|
||||
tags: true
|
||||
local_dir: www
|
||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 14 KiB |
@ -0,0 +1 @@
|
||||
41e1c536c5dfb47db1311f7335b357b3
|
After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 818 B |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 158 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 97 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 153 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 92 KiB |
@ -0,0 +1 @@
|
||||
287a089b0cd0ff44dfda2fd7bd7ad812
|
After Width: | Height: | Size: 77 KiB |
@ -1 +1 @@
|
||||
Subproject commit cc464a6466c2e6b2648e93e386d574d526cd3f40
|
||||
Subproject commit c43a8244e72996457ce832317bb463787645caae
|
@ -1,70 +1,73 @@
|
||||
import { Component, ViewChild, ElementRef, OnInit } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { IonicPage, NavController, NavParams, Platform } from 'ionic-angular';
|
||||
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { OtaWizardPage } from '../ota-wizard/ota-wizard';
|
||||
import { LoggingProvider } from '../../providers/logging/logging';
|
||||
import { StorageProvider, LASTSKETCH } from '../../providers/storage/storage';
|
||||
import { BlocklyMessageProtocol } from './blockly_protocol';
|
||||
|
||||
@IonicPage()
|
||||
@Component({
|
||||
selector: 'page-blockly',
|
||||
templateUrl: 'blockly.html',
|
||||
})
|
||||
export class BlocklyPage implements OnInit {
|
||||
export class BlocklyPage {
|
||||
@ViewChild('blocklyFrame') blocklyFrame: ElementRef
|
||||
blocklyUrl: SafeResourceUrl
|
||||
blockly: BlocklyMessageProtocol
|
||||
|
||||
private messageHandler: (ev: IframePostMessageEvent) => void
|
||||
private log: LoggingProvider
|
||||
|
||||
constructor(
|
||||
public navCtrl: NavController,
|
||||
public navParams: NavParams,
|
||||
private sanitizer: DomSanitizer,
|
||||
private platform: Platform,
|
||||
private storage: StorageProvider,
|
||||
logger: LoggingProvider,
|
||||
translate: TranslateService,
|
||||
) {
|
||||
this.log = logger.createChild('BlocklyPage')
|
||||
this.blocklyUrl = this.buildBlocklyUrl(translate.currentLang)
|
||||
}
|
||||
|
||||
async ngOnInit () {
|
||||
// blocklyFrame is available from here on
|
||||
this.blockly = new BlocklyMessageProtocol(this.blocklyFrame, this.log)
|
||||
await this.blockly.ready
|
||||
// load the last sketch
|
||||
const xml = this.storage.get(LASTSKETCH)
|
||||
if (xml) this.blockly.setXml(xml)
|
||||
// need to assign it here to keep the function reference for unsubscribing again
|
||||
// and to maintain the this scope properly
|
||||
this.messageHandler = (ev: IframePostMessageEvent) => {
|
||||
const { type, data } = ev.data;
|
||||
switch (type) {
|
||||
case 'sketch':
|
||||
this.log.debug('sketch received, launching ota wizard page', { sketch: data })
|
||||
this.navCtrl.push(OtaWizardPage, { sketch: data })
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// save state when exiting (on mobile & browsers separately)
|
||||
this.platform.pause.subscribe(() => this.saveBlocklyState())
|
||||
window.addEventListener('beforeunload', () => this.saveBlocklyState())
|
||||
window.addEventListener('message', this.messageHandler)
|
||||
}
|
||||
|
||||
async ionViewCanLeave () {
|
||||
// hold closing the page until we saved the current blockly state
|
||||
await this.saveBlocklyState()
|
||||
return true
|
||||
ionViewWillUnload () {
|
||||
window.removeEventListener('message', this.messageHandler)
|
||||
}
|
||||
|
||||
async saveBlocklyState () {
|
||||
const xml = await this.blockly.getXml()
|
||||
this.storage.set(LASTSKETCH, xml)
|
||||
launchOtaWizard () {
|
||||
this.log.debug('clicked launch ota')
|
||||
this.blocklyFrame.nativeElement.contentWindow.postMessage('getSketch', '*')
|
||||
}
|
||||
|
||||
async launchOtaWizard () {
|
||||
const sketch = await this.blockly.getSketch()
|
||||
this.navCtrl.push(OtaWizardPage, { sketch })
|
||||
toggleView () {
|
||||
this.log.debug('clicked toggle view')
|
||||
this.blocklyFrame.nativeElement.contentWindow.postMessage('toggleView', '*')
|
||||
}
|
||||
|
||||
private buildBlocklyUrl (lang: string): SafeResourceUrl {
|
||||
buildBlocklyUrl (lang: string): SafeResourceUrl {
|
||||
if (!lang) this.log.error('building url with empty language!')
|
||||
const url = `./assets/blockly.html?lang=${lang}`
|
||||
return this.sanitizer.bypassSecurityTrustResourceUrl(url)
|
||||
}
|
||||
}
|
||||
|
||||
interface IframePostMessageEvent extends MessageEvent {
|
||||
data: {
|
||||
type: 'sketch',
|
||||
data: any,
|
||||
}
|
||||
}
|
||||
|
@ -1,96 +0,0 @@
|
||||
import { ElementRef } from '@angular/core';
|
||||
import { LoggingProvider } from '../../providers/logging/logging';
|
||||
|
||||
/**
|
||||
* this file defines & implements the message passing protocol to communicate
|
||||
* with an iframe that has src/assets/blockly.html loaded.
|
||||
* The underlying postMessage protocol is wrapped into a promise API.
|
||||
*/
|
||||
|
||||
export class BlocklyMessageProtocol {
|
||||
// resolve ready promise once the blocklyFrame is ready
|
||||
ready = new Promise(resolve => {
|
||||
window.addEventListener('message', (ev: IframePostMessageEvent) => {
|
||||
// @HACK @FIXME: timeout is required, as blockly resolves some async functions
|
||||
// after firing the `ready` event..
|
||||
if (ev.data.type === 'ready') setTimeout(resolve, 300)
|
||||
})
|
||||
})
|
||||
|
||||
static reqResPatterns: BlocklyReqPatterns = {
|
||||
'getSketch': 'sketch',
|
||||
'getXml': 'xml',
|
||||
'setXml': null,
|
||||
'toggleView': null,
|
||||
}
|
||||
|
||||
constructor (private blocklyFrame: ElementRef, private log: LoggingProvider) {
|
||||
// set up event listeners for non-request log messages
|
||||
window.addEventListener('message', (ev: IframePostMessageEvent) => {
|
||||
if (ev.data.type === 'log')
|
||||
this.log.warn('log entry from blockly:', ev.data)
|
||||
else
|
||||
this.log.debug(`received ${ev.data.type} message from blockly`, { message: ev.data })
|
||||
})
|
||||
}
|
||||
|
||||
toggleView() { this.sendRequest({ type: 'toggleView' }) }
|
||||
setXml(data: string) { this.sendRequest({ type: 'setXml', data }) }
|
||||
getXml() { return this.sendRequest({ type: 'getXml' }) }
|
||||
getSketch() { return this.sendRequest({ type: 'getSketch' }) }
|
||||
|
||||
private async sendRequest(req: BlocklyRequest): Promise<any> {
|
||||
await this.ready
|
||||
|
||||
if (
|
||||
!this.blocklyFrame ||
|
||||
!this.blocklyFrame.nativeElement ||
|
||||
!this.blocklyFrame.nativeElement.contentWindow
|
||||
) {
|
||||
throw new Error('cannot access blockly frame')
|
||||
}
|
||||
|
||||
const expectResponse = BlocklyMessageProtocol.reqResPatterns[req.type]
|
||||
this.log.debug(`sending ${req.type} message to blockly, expecting response: ${expectResponse}`, { message: req })
|
||||
|
||||
if (!expectResponse)
|
||||
return this.blocklyFrame.nativeElement.contentWindow.postMessage(req, '*')
|
||||
|
||||
// create promise waiting for the response event
|
||||
const resPromise = new Promise<any>((resolve, reject) => {
|
||||
const resHandler = ({ data: res }: IframePostMessageEvent) => {
|
||||
if (expectResponse !== res.type) return
|
||||
window.removeEventListener('message', resHandler)
|
||||
if (res.type === 'error') reject(res.data)
|
||||
else resolve(res.data)
|
||||
}
|
||||
// TODO: promise reject after timeout?
|
||||
window.addEventListener('message', resHandler)
|
||||
})
|
||||
|
||||
// send message *after* registering the response handler!
|
||||
this.blocklyFrame.nativeElement.contentWindow.postMessage(req, '*')
|
||||
return resPromise
|
||||
}
|
||||
}
|
||||
|
||||
interface IframePostMessageEvent extends MessageEvent {
|
||||
data: BlocklyResponse
|
||||
}
|
||||
|
||||
type BlocklyReqPatterns = {
|
||||
[k in BlocklyRequest['type']]: BlocklyResponse['type']
|
||||
}
|
||||
|
||||
type BlocklyRequest =
|
||||
{ type: 'getSketch' } |
|
||||
{ type: 'getXml' } |
|
||||
{ type: 'setXml', data: string } |
|
||||
{ type: 'toggleView' }
|
||||
|
||||
type BlocklyResponse =
|
||||
{ type: 'log', data: any } |
|
||||
{ type: 'error', data: any } |
|
||||
{ type: 'ready', data: undefined } |
|
||||
{ type: 'sketch', data: string } |
|
||||
{ type: 'xml', data: string }
|
@ -1,3 +1,3 @@
|
||||
.error {
|
||||
color: red !important;
|
||||
page-settings {
|
||||
|
||||
}
|
||||
|
@ -1,41 +1,33 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
export const SETTINGS = 'appsettings'
|
||||
export const LASTSKETCH = 'lastsketch'
|
||||
|
||||
@Injectable()
|
||||
export class StorageProvider {
|
||||
private cache: Map<string, any> = new Map()
|
||||
|
||||
constructor () {
|
||||
this.init()
|
||||
}
|
||||
|
||||
init () {
|
||||
// set up default values
|
||||
this.registerKey(SETTINGS, { logOptin: false })
|
||||
this.registerKey(LASTSKETCH, '')
|
||||
this.registerKey(SETTINGS, {
|
||||
logOptin: false,
|
||||
})
|
||||
}
|
||||
|
||||
registerKey (key, defaultValue) {
|
||||
const stored = localStorage.getItem(key)
|
||||
if (stored === null) {
|
||||
this.set(key, defaultValue)
|
||||
if (!stored) {
|
||||
localStorage.setItem(key, JSON.stringify(defaultValue))
|
||||
this.cache[key] = defaultValue
|
||||
} else {
|
||||
this.cache[key] = JSON.parse(stored)
|
||||
}
|
||||
}
|
||||
|
||||
get (key) { return this.cache[key] }
|
||||
get (key) {
|
||||
return this.cache[key]
|
||||
}
|
||||
|
||||
set (key, value) {
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
this.cache[key] = value
|
||||
}
|
||||
|
||||
reset () {
|
||||
localStorage.clear()
|
||||
this.cache = new Map()
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
|