├── cordova ├── .npmignore ├── www │ ├── libs │ │ ├── jquery │ │ │ └── VERSION │ │ ├── evothings │ │ │ ├── VERSION │ │ │ ├── ui │ │ │ │ ├── ui.js │ │ │ │ └── fastclick.js │ │ │ ├── util │ │ │ │ └── util.js │ │ │ └── evothings.js │ │ ├── nouislider │ │ │ └── nouislider.css │ │ └── spectrum │ │ │ └── spectrum.css │ ├── evothings.json │ ├── img │ │ ├── loader.gif │ │ ├── paint │ │ │ ├── fire.jpg │ │ │ ├── mario.png │ │ │ ├── pacman.png │ │ │ ├── water.jpg │ │ │ ├── Nyan-Cat.png │ │ │ ├── monalisa.jpg │ │ │ └── space_invaders.png │ │ └── loader_small.gif │ ├── fonts │ │ ├── Rajdhani-Regular.woff2 │ │ ├── rajdhani.css │ │ ├── Rajdhani-Bold.woff2 │ │ ├── Rajdhani-Medium.woff2 │ │ └── Rajdhani-SemiBold.woff2 │ ├── ui │ │ ├── fonts │ │ │ ├── ProximaNova-Reg-webfont.eot │ │ │ ├── ProximaNova-Reg-webfont.ttf │ │ │ ├── ProximaNova-Black-webfont.eot │ │ │ ├── ProximaNova-Black-webfont.ttf │ │ │ ├── ProximaNova-Bold-webfont.eot │ │ │ ├── ProximaNova-Bold-webfont.ttf │ │ │ ├── ProximaNova-Bold-webfont.woff │ │ │ ├── ProximaNova-Reg-webfont.woff │ │ │ ├── ProximaNova-Black-webfont.woff │ │ │ └── ProximaNova.css │ │ ├── images │ │ │ ├── arrow-left.svg │ │ │ ├── arrow-right.svg │ │ │ ├── menu.svg │ │ │ └── logo.svg │ │ └── css │ │ │ └── evothings-app.css │ ├── css │ │ └── spinner.css │ ├── index.html │ └── app.js ├── res │ ├── logo │ │ ├── icon.png │ │ ├── splash.png │ │ ├── appstore.png │ │ ├── original.jpg │ │ ├── googleplay.png │ │ └── icon-feature-graphic-playstore.jpeg │ ├── icons │ │ ├── ios │ │ │ ├── icon.png │ │ │ ├── icon-2x.png │ │ │ ├── icon-40.png │ │ │ ├── icon-50.png │ │ │ ├── icon-60.png │ │ │ ├── icon-72.png │ │ │ ├── icon-76.png │ │ │ ├── icon-40-2x.png │ │ │ ├── icon-50-2x.png │ │ │ ├── icon-60-2x.png │ │ │ ├── icon-60-3x.png │ │ │ ├── icon-72-2x.png │ │ │ ├── icon-76-2x.png │ │ │ ├── icon-small.png │ │ │ └── icon-small-2x.png │ │ └── android │ │ │ ├── icon-36-ldpi.png │ │ │ ├── icon-48-mdpi.png │ │ │ ├── icon-72-hdpi.png │ │ │ ├── icon-96-xhdpi.png │ │ │ ├── icon-144-xxhdpi.png │ │ │ └── icon-192-xxxhdpi.png │ ├── icon │ │ ├── ios │ │ │ ├── icon-57.png │ │ │ ├── icon-72.png │ │ │ ├── icon-57-2x.png │ │ │ └── icon-72-2x.png │ │ ├── bada │ │ │ └── icon-128.png │ │ ├── tizen │ │ │ └── icon-128.png │ │ ├── webos │ │ │ └── icon-64.png │ │ ├── blackberry │ │ │ └── icon-80.png │ │ ├── android │ │ │ ├── icon-36-ldpi.png │ │ │ ├── icon-48-mdpi.png │ │ │ ├── icon-72-hdpi.png │ │ │ └── icon-96-xhdpi.png │ │ ├── bada-wac │ │ │ ├── icon-48-type5.png │ │ │ ├── icon-50-type3.png │ │ │ └── icon-80-type4.png │ │ ├── blackberry10 │ │ │ └── icon-80.png │ │ └── windows-phone │ │ │ ├── icon-48.png │ │ │ ├── icon-173-tile.png │ │ │ └── icon-62-tile.png │ ├── screen │ │ ├── webos │ │ │ └── screen-64.png │ │ ├── bada │ │ │ └── screen-portrait.png │ │ ├── bada-wac │ │ │ ├── screen-type3.png │ │ │ ├── screen-type4.png │ │ │ └── screen-type5.png │ │ ├── blackberry │ │ │ └── screen-225.png │ │ ├── ios │ │ │ ├── screen-ipad-landscape.png │ │ │ ├── screen-ipad-portrait.png │ │ │ ├── screen-ipad-portrait-2x.png │ │ │ ├── screen-iphone-landscape.png │ │ │ ├── screen-iphone-portrait.png │ │ │ ├── screen-ipad-landscape-2x.png │ │ │ ├── screen-iphone-landscape-2x.png │ │ │ ├── screen-iphone-portrait-2x.png │ │ │ └── screen-iphone-portrait-568h-2x.png │ │ ├── blackberry10 │ │ │ ├── splash-720x720.png │ │ │ ├── splash-1280x768.png │ │ │ └── splash-768x1280.png │ │ ├── android │ │ │ ├── screen-hdpi-landscape.png │ │ │ ├── screen-hdpi-portrait.png │ │ │ ├── screen-ldpi-landscape.png │ │ │ ├── screen-ldpi-portrait.png │ │ │ ├── screen-mdpi-landscape.png │ │ │ ├── screen-mdpi-portrait.png │ │ │ ├── screen-xhdpi-landscape.png │ │ │ └── screen-xhdpi-portrait.png │ │ ├── windows-phone │ │ │ └── screen-portrait.jpg │ │ └── tizen │ │ │ └── README.md │ ├── screens │ │ ├── ios │ │ │ ├── screen-ipad-portrait.png │ │ │ ├── screen-iphone-portrait.png │ │ │ ├── screen-ipad-portrait-2x.png │ │ │ ├── screen-iphone-portrait-2x.png │ │ │ ├── screen-iphone-portrait-667h.png │ │ │ ├── screen-iphone-portrait-736h.png │ │ │ └── screen-iphone-portrait-568h-2x.png │ │ └── android │ │ │ ├── screen-hdpi-portrait.png │ │ │ ├── screen-ldpi-portrait.png │ │ │ ├── screen-mdpi-portrait.png │ │ │ ├── screen-xhdpi-portrait.png │ │ │ ├── screen-xxhdpi-portrait.png │ │ │ └── screen-xxxhdpi-portrait.png │ └── README.md ├── RELEASE_ALL.sh ├── package.json └── config.xml ├── .gitignore ├── Programmable LED Pattern Language Architecture (ESP32 + Mobile App).pdf ├── index.html ├── gfxfont.h ├── platformio.ini ├── BLEDeviceID.h ├── BLESerial.h ├── README.md ├── BLESerial.cpp ├── BLEDeviceID.cpp ├── Picopixel.h ├── Adafruit_GFX.h └── glcdfont.c /cordova/.npmignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /cordova/www/libs/jquery/VERSION: -------------------------------------------------------------------------------- 1 | jquery-2.1.1.min.js 2 | -------------------------------------------------------------------------------- /cordova/www/libs/evothings/VERSION: -------------------------------------------------------------------------------- 1 | Evothings Libraries version 2.1.0 2 | -------------------------------------------------------------------------------- /cordova/www/evothings.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "31f6be13-7f18-49ad-9f33-9d885791fa72" 3 | } -------------------------------------------------------------------------------- /cordova/res/logo/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/logo/icon.png -------------------------------------------------------------------------------- /cordova/res/logo/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/logo/splash.png -------------------------------------------------------------------------------- /cordova/www/img/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/img/loader.gif -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon.png -------------------------------------------------------------------------------- /cordova/res/logo/appstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/logo/appstore.png -------------------------------------------------------------------------------- /cordova/res/logo/original.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/logo/original.jpg -------------------------------------------------------------------------------- /cordova/www/img/paint/fire.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/img/paint/fire.jpg -------------------------------------------------------------------------------- /cordova/res/icon/ios/icon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/ios/icon-57.png -------------------------------------------------------------------------------- /cordova/res/icon/ios/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/ios/icon-72.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-2x.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-40.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-50.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-60.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-72.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-76.png -------------------------------------------------------------------------------- /cordova/res/logo/googleplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/logo/googleplay.png -------------------------------------------------------------------------------- /cordova/www/img/loader_small.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/img/loader_small.gif -------------------------------------------------------------------------------- /cordova/www/img/paint/mario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/img/paint/mario.png -------------------------------------------------------------------------------- /cordova/www/img/paint/pacman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/img/paint/pacman.png -------------------------------------------------------------------------------- /cordova/www/img/paint/water.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/img/paint/water.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.sw? 3 | node_modules 4 | cordova/platforms/ 5 | cordova/plugins/ 6 | cordova/hooks/ 7 | 8 | -------------------------------------------------------------------------------- /cordova/res/icon/bada/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/bada/icon-128.png -------------------------------------------------------------------------------- /cordova/res/icon/ios/icon-57-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/ios/icon-57-2x.png -------------------------------------------------------------------------------- /cordova/res/icon/ios/icon-72-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/ios/icon-72-2x.png -------------------------------------------------------------------------------- /cordova/res/icon/tizen/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/tizen/icon-128.png -------------------------------------------------------------------------------- /cordova/res/icon/webos/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/webos/icon-64.png -------------------------------------------------------------------------------- /cordova/www/img/paint/Nyan-Cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/img/paint/Nyan-Cat.png -------------------------------------------------------------------------------- /cordova/www/img/paint/monalisa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/img/paint/monalisa.jpg -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-40-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-40-2x.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-50-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-50-2x.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-60-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-60-2x.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-60-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-60-3x.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-72-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-72-2x.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-76-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-76-2x.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-small.png -------------------------------------------------------------------------------- /cordova/res/screen/webos/screen-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/webos/screen-64.png -------------------------------------------------------------------------------- /cordova/res/icon/blackberry/icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/blackberry/icon-80.png -------------------------------------------------------------------------------- /cordova/res/icons/ios/icon-small-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/ios/icon-small-2x.png -------------------------------------------------------------------------------- /cordova/www/fonts/Rajdhani-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/fonts/Rajdhani-Regular.woff2 -------------------------------------------------------------------------------- /cordova/www/img/paint/space_invaders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/img/paint/space_invaders.png -------------------------------------------------------------------------------- /cordova/res/icon/android/icon-36-ldpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/android/icon-36-ldpi.png -------------------------------------------------------------------------------- /cordova/res/icon/android/icon-48-mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/android/icon-48-mdpi.png -------------------------------------------------------------------------------- /cordova/res/icon/android/icon-72-hdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/android/icon-72-hdpi.png -------------------------------------------------------------------------------- /cordova/res/icon/android/icon-96-xhdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/android/icon-96-xhdpi.png -------------------------------------------------------------------------------- /cordova/res/icon/bada-wac/icon-48-type5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/bada-wac/icon-48-type5.png -------------------------------------------------------------------------------- /cordova/res/icon/bada-wac/icon-50-type3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/bada-wac/icon-50-type3.png -------------------------------------------------------------------------------- /cordova/res/icon/bada-wac/icon-80-type4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/bada-wac/icon-80-type4.png -------------------------------------------------------------------------------- /cordova/res/icon/blackberry10/icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/blackberry10/icon-80.png -------------------------------------------------------------------------------- /cordova/res/icon/windows-phone/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/windows-phone/icon-48.png -------------------------------------------------------------------------------- /cordova/res/icons/android/icon-36-ldpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/android/icon-36-ldpi.png -------------------------------------------------------------------------------- /cordova/res/icons/android/icon-48-mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/android/icon-48-mdpi.png -------------------------------------------------------------------------------- /cordova/res/icons/android/icon-72-hdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/android/icon-72-hdpi.png -------------------------------------------------------------------------------- /cordova/res/icons/android/icon-96-xhdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/android/icon-96-xhdpi.png -------------------------------------------------------------------------------- /cordova/res/screen/bada/screen-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/bada/screen-portrait.png -------------------------------------------------------------------------------- /cordova/res/icons/android/icon-144-xxhdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/android/icon-144-xxhdpi.png -------------------------------------------------------------------------------- /cordova/res/screen/bada-wac/screen-type3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/bada-wac/screen-type3.png -------------------------------------------------------------------------------- /cordova/res/screen/bada-wac/screen-type4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/bada-wac/screen-type4.png -------------------------------------------------------------------------------- /cordova/res/screen/bada-wac/screen-type5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/bada-wac/screen-type5.png -------------------------------------------------------------------------------- /cordova/res/screen/blackberry/screen-225.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/blackberry/screen-225.png -------------------------------------------------------------------------------- /cordova/res/icon/windows-phone/icon-173-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/windows-phone/icon-173-tile.png -------------------------------------------------------------------------------- /cordova/res/icon/windows-phone/icon-62-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icon/windows-phone/icon-62-tile.png -------------------------------------------------------------------------------- /cordova/res/icons/android/icon-192-xxxhdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/icons/android/icon-192-xxxhdpi.png -------------------------------------------------------------------------------- /cordova/res/screen/ios/screen-ipad-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/ios/screen-ipad-landscape.png -------------------------------------------------------------------------------- /cordova/res/screen/ios/screen-ipad-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/ios/screen-ipad-portrait.png -------------------------------------------------------------------------------- /cordova/res/screens/ios/screen-ipad-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/ios/screen-ipad-portrait.png -------------------------------------------------------------------------------- /cordova/www/ui/fonts/ProximaNova-Reg-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/ui/fonts/ProximaNova-Reg-webfont.eot -------------------------------------------------------------------------------- /cordova/www/ui/fonts/ProximaNova-Reg-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/ui/fonts/ProximaNova-Reg-webfont.ttf -------------------------------------------------------------------------------- /cordova/res/screen/blackberry10/splash-720x720.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/blackberry10/splash-720x720.png -------------------------------------------------------------------------------- /cordova/res/screen/ios/screen-ipad-portrait-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/ios/screen-ipad-portrait-2x.png -------------------------------------------------------------------------------- /cordova/res/screen/ios/screen-iphone-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/ios/screen-iphone-landscape.png -------------------------------------------------------------------------------- /cordova/res/screen/ios/screen-iphone-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/ios/screen-iphone-portrait.png -------------------------------------------------------------------------------- /cordova/res/screens/ios/screen-iphone-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/ios/screen-iphone-portrait.png -------------------------------------------------------------------------------- /cordova/www/ui/fonts/ProximaNova-Black-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/ui/fonts/ProximaNova-Black-webfont.eot -------------------------------------------------------------------------------- /cordova/www/ui/fonts/ProximaNova-Black-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/ui/fonts/ProximaNova-Black-webfont.ttf -------------------------------------------------------------------------------- /cordova/www/ui/fonts/ProximaNova-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/ui/fonts/ProximaNova-Bold-webfont.eot -------------------------------------------------------------------------------- /cordova/www/ui/fonts/ProximaNova-Bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/ui/fonts/ProximaNova-Bold-webfont.ttf -------------------------------------------------------------------------------- /cordova/www/ui/fonts/ProximaNova-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/ui/fonts/ProximaNova-Bold-webfont.woff -------------------------------------------------------------------------------- /cordova/www/ui/fonts/ProximaNova-Reg-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/ui/fonts/ProximaNova-Reg-webfont.woff -------------------------------------------------------------------------------- /cordova/res/logo/icon-feature-graphic-playstore.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/logo/icon-feature-graphic-playstore.jpeg -------------------------------------------------------------------------------- /cordova/res/screen/android/screen-hdpi-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/android/screen-hdpi-landscape.png -------------------------------------------------------------------------------- /cordova/res/screen/android/screen-hdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/android/screen-hdpi-portrait.png -------------------------------------------------------------------------------- /cordova/res/screen/android/screen-ldpi-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/android/screen-ldpi-landscape.png -------------------------------------------------------------------------------- /cordova/res/screen/android/screen-ldpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/android/screen-ldpi-portrait.png -------------------------------------------------------------------------------- /cordova/res/screen/android/screen-mdpi-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/android/screen-mdpi-landscape.png -------------------------------------------------------------------------------- /cordova/res/screen/android/screen-mdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/android/screen-mdpi-portrait.png -------------------------------------------------------------------------------- /cordova/res/screen/android/screen-xhdpi-landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/android/screen-xhdpi-landscape.png -------------------------------------------------------------------------------- /cordova/res/screen/android/screen-xhdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/android/screen-xhdpi-portrait.png -------------------------------------------------------------------------------- /cordova/res/screen/blackberry10/splash-1280x768.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/blackberry10/splash-1280x768.png -------------------------------------------------------------------------------- /cordova/res/screen/blackberry10/splash-768x1280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/blackberry10/splash-768x1280.png -------------------------------------------------------------------------------- /cordova/res/screen/ios/screen-ipad-landscape-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/ios/screen-ipad-landscape-2x.png -------------------------------------------------------------------------------- /cordova/res/screen/ios/screen-iphone-landscape-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/ios/screen-iphone-landscape-2x.png -------------------------------------------------------------------------------- /cordova/res/screen/ios/screen-iphone-portrait-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/ios/screen-iphone-portrait-2x.png -------------------------------------------------------------------------------- /cordova/res/screen/windows-phone/screen-portrait.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/windows-phone/screen-portrait.jpg -------------------------------------------------------------------------------- /cordova/res/screens/android/screen-hdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/android/screen-hdpi-portrait.png -------------------------------------------------------------------------------- /cordova/res/screens/android/screen-ldpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/android/screen-ldpi-portrait.png -------------------------------------------------------------------------------- /cordova/res/screens/android/screen-mdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/android/screen-mdpi-portrait.png -------------------------------------------------------------------------------- /cordova/res/screens/android/screen-xhdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/android/screen-xhdpi-portrait.png -------------------------------------------------------------------------------- /cordova/res/screens/ios/screen-ipad-portrait-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/ios/screen-ipad-portrait-2x.png -------------------------------------------------------------------------------- /cordova/res/screens/ios/screen-iphone-portrait-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/ios/screen-iphone-portrait-2x.png -------------------------------------------------------------------------------- /cordova/www/ui/fonts/ProximaNova-Black-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/www/ui/fonts/ProximaNova-Black-webfont.woff -------------------------------------------------------------------------------- /cordova/res/screens/android/screen-xxhdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/android/screen-xxhdpi-portrait.png -------------------------------------------------------------------------------- /cordova/res/screens/android/screen-xxxhdpi-portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/android/screen-xxxhdpi-portrait.png -------------------------------------------------------------------------------- /cordova/res/screens/ios/screen-iphone-portrait-667h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/ios/screen-iphone-portrait-667h.png -------------------------------------------------------------------------------- /cordova/res/screens/ios/screen-iphone-portrait-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/ios/screen-iphone-portrait-736h.png -------------------------------------------------------------------------------- /cordova/res/screen/ios/screen-iphone-portrait-568h-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screen/ios/screen-iphone-portrait-568h-2x.png -------------------------------------------------------------------------------- /cordova/res/screens/ios/screen-iphone-portrait-568h-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/cordova/res/screens/ios/screen-iphone-portrait-568h-2x.png -------------------------------------------------------------------------------- /Programmable LED Pattern Language Architecture (ESP32 + Mobile App).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hobzcalvin/blume/HEAD/Programmable LED Pattern Language Architecture (ESP32 + Mobile App).pdf -------------------------------------------------------------------------------- /cordova/RELEASE_ALL.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | code-push release-cordova blume-android android -m -t 1.0.x -d Production 4 | code-push release-cordova blume-ios ios -m -t 1.0.x -d Production 5 | echo Android: 6 | code-push deployment ls blume-android 7 | echo iOS: 8 | code-push deployment ls blume-ios 9 | -------------------------------------------------------------------------------- /cordova/www/css/spinner.css: -------------------------------------------------------------------------------- 1 | .spinner { 2 | width: 40px; 3 | height: 40px; 4 | margin: 20px auto; 5 | border: 3px solid var(--border-color); 6 | border-top: 3px solid var(--accent-color); 7 | border-radius: 50%; 8 | animation: spin 1s linear infinite; 9 | } 10 | 11 | @keyframes spin { 12 | 0% { transform: rotate(0deg); } 13 | 100% { transform: rotate(360deg); } 14 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | Blume 10 | 11 | 12 |

Click here to try Blume.

13 | 14 | 15 | -------------------------------------------------------------------------------- /cordova/www/fonts/rajdhani.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Rajdhani'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url('Rajdhani-Regular.woff2') format('woff2'); 6 | } 7 | 8 | @font-face { 9 | font-family: 'Rajdhani'; 10 | font-style: normal; 11 | font-weight: 500; 12 | src: url('Rajdhani-Medium.woff2') format('woff2'); 13 | } 14 | 15 | @font-face { 16 | font-family: 'Rajdhani'; 17 | font-style: normal; 18 | font-weight: 600; 19 | src: url('Rajdhani-SemiBold.woff2') format('woff2'); 20 | } 21 | 22 | @font-face { 23 | font-family: 'Rajdhani'; 24 | font-style: normal; 25 | font-weight: 700; 26 | src: url('Rajdhani-Bold.woff2') format('woff2'); 27 | } -------------------------------------------------------------------------------- /cordova/www/ui/images/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /cordova/www/ui/images/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /cordova/res/screen/tizen/README.md: -------------------------------------------------------------------------------- 1 | 21 | 22 | # Tizen Splash Screen 23 | 24 | Splash screens are unsupported on the Tizen platform. 25 | -------------------------------------------------------------------------------- /gfxfont.h: -------------------------------------------------------------------------------- 1 | // Font structures for newer Adafruit_GFX (1.1 and later). 2 | // Example fonts are included in 'Fonts' directory. 3 | // To use a font in your Arduino sketch, #include the corresponding .h 4 | // file and pass address of GFXfont struct to setFont(). Pass NULL to 5 | // revert to 'classic' fixed-space bitmap font. 6 | 7 | #ifndef _GFXFONT_H_ 8 | #define _GFXFONT_H_ 9 | 10 | typedef struct { // Data stored PER GLYPH 11 | uint16_t bitmapOffset; // Pointer into GFXfont->bitmap 12 | uint8_t width, height; // Bitmap dimensions in pixels 13 | uint8_t xAdvance; // Distance to advance cursor (x axis) 14 | int8_t xOffset, yOffset; // Dist from cursor pos to UL corner 15 | } GFXglyph; 16 | 17 | typedef struct { // Data stored for FONT AS A WHOLE: 18 | uint8_t *bitmap; // Glyph bitmaps, concatenated 19 | GFXglyph *glyph; // Glyph array 20 | uint8_t first, last; // ASCII extents 21 | uint8_t yAdvance; // Newline distance (y axis) 22 | } GFXfont; 23 | 24 | #endif // _GFXFONT_H_ 25 | -------------------------------------------------------------------------------- /cordova/www/libs/evothings/ui/ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | Evothings UI functionality. 3 | 4 | FastClick is used to make UI responsive. 5 | */ 6 | 7 | ;(function() 8 | { 9 | // Special layout hack for iOS 7. 10 | function applyiOS7LayoutHack() 11 | { 12 | // Set an absolute base font size in iOS 7 due to that viewport-relative 13 | // font sizes doesn't work properly caused by the WebKit bug described at 14 | // https://bugs.webkit.org/show_bug.cgi?id=131863. 15 | if (evothings.os.isIOS7()) 16 | { 17 | document.body.style.fontSize = '20pt'; 18 | } 19 | } 20 | 21 | function applyUIUpdatesWhenPageHasLoaded() 22 | { 23 | var applyUIUpdates = function() { 24 | applyiOS7LayoutHack(); 25 | FastClick.attach(document.body); 26 | } 27 | 28 | /* If the DOMContentLoaded event was already fired, apply the UI updates 29 | * now, otherwise wait for the event. 30 | */ 31 | if (evothings.gotDOMContentLoaded) 32 | { 33 | applyUIUpdates() 34 | } 35 | else 36 | { 37 | window.addEventListener('DOMContentLoaded', applyUIUpdates) 38 | } 39 | } 40 | 41 | // Load FastClick, when loaded apply UI modifications. 42 | evothings.loadScript( 43 | 'libs/evothings/ui/fastclick.js', 44 | applyUIUpdatesWhenPageHasLoaded); 45 | })(); 46 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | [env:esp32dev] 2 | platform = espressif32 3 | board = esp32dev 4 | framework = arduino 5 | monitor_speed = 115200 6 | upload_speed = 115200 7 | 8 | ; ESP32 Arduino Core - Using 2.0.14 for smaller binary size 9 | ; Note: Current version is 2.3.2, but this version was chosen for size optimization 10 | platform_packages = 11 | framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.14 12 | 13 | ; Required libraries - versions chosen for stability and compatibility 14 | lib_deps = 15 | fastled/FastLED @ ^3.6.0 16 | adafruit/Adafruit GFX Library @ ^1.11.5 17 | adafruit/Adafruit BusIO @ ^1.14.1 18 | 19 | ; Build flags 20 | build_flags = 21 | -D ESP32 22 | -D DEBUG 23 | -D MAX_MILLIAMPS=5000 24 | -D BLUETOOTH_NAME="Blume" 25 | -D WIDTH=2 26 | -D HEIGHT=66 27 | -D NUM_LEDS=131 28 | -D SKIP_FRONT=0 29 | -D MAX_FPS=120 30 | -D DISABLE_DITHER 31 | -D COLOR_ORDER=GRB 32 | -D CHIPSET=WS2812 33 | -D DATA_PIN_0=32 34 | -D DATA_PIN_1=33 35 | -D CLOCK_PIN_0=25 36 | -D CLOCK_PIN_1=26 37 | -D ROTATE 38 | -D STAGGERED 39 | -D BASE_WID=2 40 | -D PHYSICAL_WIDTH=2 41 | -D TEXTMODE 42 | -D FONT=Adafruit5x7 43 | -D TEXT_OFFSET=0 -------------------------------------------------------------------------------- /cordova/res/README.md: -------------------------------------------------------------------------------- 1 | 21 | 22 | Note that these image resources are not copied into a project when a project 23 | is created with the CLI. Although there are default image resources in a 24 | newly-created project, those come from the platform-specific project template, 25 | which can generally be found in the platform's `template` directory. Until 26 | icon and splashscreen support is added to the CLI, these image resources 27 | aren't used directly. 28 | 29 | See https://issues.apache.org/jira/browse/CB-5145 30 | -------------------------------------------------------------------------------- /cordova/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "org.selfobserved.blume", 3 | "displayName": "Blume", 4 | "version": "1.0.0", 5 | "description": "A sample Apache Cordova application that responds to the deviceready event.", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Apache Cordova Team", 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "code-push": "^2.0.1-beta", 14 | "cordova-android": "^6.2.3", 15 | "cordova-plugin-ble": "git+https://github.com/hobzcalvin/cordova-ble.git", 16 | "cordova-plugin-code-push": "^1.9.5-beta", 17 | "cordova-plugin-compat": "^1.1.0", 18 | "cordova-plugin-device": "^1.1.6", 19 | "cordova-plugin-dialogs": "^1.3.3", 20 | "cordova-plugin-file": "^4.3.3", 21 | "cordova-plugin-file-transfer": "^1.6.3", 22 | "cordova-plugin-whitelist": "^1.3.2", 23 | "cordova-plugin-zip": "^3.1.0" 24 | }, 25 | "cordova": { 26 | "plugins": { 27 | "cordova-plugin-code-push": {}, 28 | "cordova-plugin-whitelist": {}, 29 | "cordova-plugin-ble": { 30 | "BLUETOOTH_USAGE_DESCRIPTION": "This app would like to use Bluetooth to connect to nearby devices." 31 | } 32 | }, 33 | "platforms": [ 34 | "android", 35 | "ios" 36 | ] 37 | }, 38 | "devDependencies": { 39 | "cordova-ios": "^7.1.1" 40 | } 41 | } -------------------------------------------------------------------------------- /cordova/www/ui/images/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 10 | 11 | 13 | 15 | 17 | 19 | 20 | -------------------------------------------------------------------------------- /cordova/www/fonts/Rajdhani-Bold.woff2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Error 404 (Not Found)!!1 6 | 9 | 10 |

404. That’s an error. 11 |

The requested URL /s/rajdhani/v15/LDI2apCSOBg7S-QT7q4tOeegPdo.woff2 was not found on this server. That’s all we know. 12 | -------------------------------------------------------------------------------- /cordova/www/fonts/Rajdhani-Medium.woff2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Error 404 (Not Found)!!1 6 | 9 | 10 |

404. That’s an error. 11 |

The requested URL /s/rajdhani/v15/LDI2apCSOBg7S-QT7q4AOuegPdo.woff2 was not found on this server. That’s all we know. 12 | -------------------------------------------------------------------------------- /cordova/www/fonts/Rajdhani-SemiBold.woff2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Error 404 (Not Found)!!1 6 | 9 | 10 |

404. That’s an error. 11 |

The requested URL /s/rajdhani/v15/LDI2apCSOBg7S-QT7q4YOOegPdo.woff2 was not found on this server. That’s all we know. 12 | -------------------------------------------------------------------------------- /cordova/www/ui/fonts/ProximaNova.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Web Fonts from fontspring.com 3 | * 4 | * All OpenType features and all extended glyphs have been removed. 5 | * Fully installable fonts can be purchased at http://www.fontspring.com 6 | * 7 | * The fonts included in this stylesheet are subject to the End User License you purchased 8 | * from Fontspring. The fonts are protected under domestic and international trademark and 9 | * copyright law. You are prohibited from modifying, reverse engineering, duplicating, or 10 | * distributing this font software. 11 | * 12 | * (c) 2010-2014 Fontspring 13 | * 14 | * 15 | * 16 | * 17 | * The fonts included are copyrighted by the vendor listed below. 18 | * 19 | * Vendor: Mark Simonson Studio 20 | * License URL: http://www.fontspring.com/fflicense/mark-simonson-studio 21 | * 22 | * 23 | */ 24 | 25 | @font-face { 26 | font-family: 'Proxima Nova Regular'; 27 | src: url('ProximaNova-Reg-webfont.eot'); 28 | src: url('ProximaNova-Reg-webfont.eot?#iefix') format('embedded-opentype'), 29 | url('ProximaNova-Reg-webfont.woff') format('woff'), 30 | url('ProximaNova-Reg-webfont.ttf') format('truetype'), 31 | url('ProximaNova-Reg-webfont.svg#proxima_nova_rgregular') format('svg'); 32 | font-weight: normal; 33 | font-style: normal; 34 | } 35 | 36 | @font-face { 37 | font-family: 'Proxima Nova Bold'; 38 | src: url('ProximaNova-Bold-webfont.eot'); 39 | src: url('ProximaNova-Bold-webfont.eot?#iefix') format('embedded-opentype'), 40 | url('ProximaNova-Bold-webfont.woff') format('woff'), 41 | url('ProximaNova-Bold-webfont.ttf') format('truetype'), 42 | url('ProximaNova-Bold-webfont.svg#proxima_nova_rgbold') format('svg'); 43 | font-weight: bold; 44 | font-style: normal; 45 | } 46 | 47 | @font-face { 48 | font-family: 'Proxima Nova Black'; 49 | src: url('ProximaNova-Black-webfont.eot'); 50 | src: url('ProximaNova-Black-webfont.eot?#iefix') format('embedded-opentype'), 51 | url('ProximaNova-Black-webfont.woff') format('woff'), 52 | url('ProximaNova-Black-webfont.ttf') format('truetype'), 53 | url('ProximaNova-Black-webfont.svg#proxima_nova_blblack') format('svg'); 54 | font-weight: bolder; 55 | font-style: normal; 56 | } 57 | -------------------------------------------------------------------------------- /BLEDeviceID.h: -------------------------------------------------------------------------------- 1 | /* 2 | BLEDeviceID.h - Bluetooth Low Energy Device ID Profile implementation. 3 | Copyright (c) 2017 by Grant Patterson. All rights reserved. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | 20 | #ifndef MAIN_BLEDeviceID_H_ 21 | #define MAIN_BLEDeviceID_H_ 22 | #include "sdkconfig.h" 23 | #if defined(CONFIG_BT_ENABLED) 24 | #include 25 | #include 26 | 27 | #include "BLEService.h" 28 | 29 | class BLEDeviceID { 30 | public: 31 | // Specify server and all values needed to implement the Device ID spec: 32 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.device_information.xml 33 | BLEDeviceID( 34 | BLEServer* server, 35 | std::string systemId, // 0x2A23, 64 bits, use strtoull format "0x..." 36 | std::string modelNumber, // 0x2A24 37 | std::string serialNumber, // 0x2A25 38 | std::string firmwareRevision, // 0x2A26 39 | std::string hardwareRevision, // 0x2A27 40 | std::string softwareRevision, // 0x2A28 41 | std::string manufacturerName, // 0x2A29 42 | std::string ieee11073_20601, // 0x2A2A, standard is behind paywall. 43 | // 0x2A50 PnP ID values: 44 | uint8_t vendorIdSource, // 1=Bluetooth SIG, 2=USB Impl. Forum 45 | uint16_t vendorId, 46 | uint16_t productId, 47 | uint16_t productVersion); 48 | void start(); 49 | 50 | protected: 51 | BLEService* service; 52 | }; 53 | 54 | #endif // CONFIG_BT_ENABLED 55 | #endif /* MAIN_BLEDeviceID_H_ */ 56 | -------------------------------------------------------------------------------- /BLESerial.h: -------------------------------------------------------------------------------- 1 | /* 2 | BLESerial.h - Bluetooth Low Energy Serial library. 3 | Copyright (c) 2017 by Grant Patterson. All rights reserved. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | 20 | #ifndef BLESerial_h 21 | #define BLESerial_h 22 | 23 | #include 24 | #include 25 | 26 | #include "BLECharacteristic.h" 27 | #include "BLEService.h" 28 | #include "Stream.h" 29 | 30 | class BLESerial: public Stream, public BLECharacteristicCallbacks { 31 | public: 32 | BLESerial(); 33 | 34 | void begin(BLEServer* server, BLEUUID service_uuid, BLEUUID characteristic_uuid); 35 | void end(); 36 | int available(void); 37 | int peek(void); 38 | int read(void); 39 | void flush(void); 40 | size_t write(uint8_t); 41 | size_t write(const uint8_t *buffer, size_t size); 42 | 43 | inline size_t write(const char * s) { 44 | return write((uint8_t*) s, strlen(s)); 45 | } 46 | inline size_t write(unsigned long n) { 47 | return write((uint8_t) n); 48 | } 49 | inline size_t write(long n) { 50 | return write((uint8_t) n); 51 | } 52 | inline size_t write(unsigned int n) { 53 | return write((uint8_t) n); 54 | } 55 | inline size_t write(int n) { 56 | return write((uint8_t) n); 57 | } 58 | uint32_t baudRate(); 59 | operator bool() const; 60 | 61 | void setDebugOutput(bool); 62 | 63 | void onWrite(BLECharacteristic* characteristic); 64 | 65 | protected: 66 | BLEService* service; 67 | BLECharacteristic* characteristic; 68 | std::string buffer; 69 | }; 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blume LED Controller 2 | 3 | A flexible LED controller for ESP32 that supports Bluetooth Low Energy (BLE) communication and various LED patterns. 4 | 5 | ## Features 6 | 7 | - BLE control interface 8 | - Support for WS2812/APA102 LED strips 9 | - Multiple display modes and patterns 10 | - Text display mode 11 | - Demo mode with saved presets 12 | - Configurable dimensions and LED layout 13 | 14 | ## Hardware Requirements 15 | 16 | - ESP32 development board 17 | - WS2812 or APA102 LED strips 18 | - Power supply (5V, sufficient for your LED count) 19 | 20 | ## Software Setup 21 | 22 | ### Option 1: Using PlatformIO (Recommended) 23 | 24 | 1. Install [PlatformIO](https://platformio.org/install) 25 | 2. Clone this repository 26 | 3. Open the project in PlatformIO 27 | 4. Build and upload to your ESP32 28 | 29 | ### Option 2: Using Arduino IDE 30 | 31 | 1. Install [Arduino IDE](https://www.arduino.cc/en/software) 32 | 2. Install ESP32 board support: 33 | - Open Arduino IDE 34 | - Go to File > Preferences 35 | - Add `https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json` to Additional Boards Manager URLs 36 | - Go to Tools > Board > Boards Manager 37 | - Search for "esp32" and install "ESP32 by Espressif Systems" 38 | 3. Install required libraries: 39 | - FastLED (3.6.0 or later) 40 | - Adafruit GFX Library (1.11.5 or later) 41 | - Adafruit BusIO (1.14.1 or later) 42 | 4. Open `blume.ino` in Arduino IDE 43 | 5. Select your ESP32 board from Tools > Board 44 | 6. Build and upload 45 | 46 | ## Configuration 47 | 48 | The project can be configured through build flags in `platformio.ini` or by modifying the defines in the code: 49 | 50 | - `WIDTH` and `HEIGHT`: Physical dimensions of your LED matrix 51 | - `NUM_LEDS`: Total number of LEDs 52 | - `CHIPSET`: LED type (WS2812 or APA102) 53 | - `DATA_PIN_0`, `DATA_PIN_1`, `CLOCK_PIN_0`, `CLOCK_PIN_1`: Pin assignments 54 | - `MAX_MILLIAMPS`: Maximum current draw 55 | - `BLUETOOTH_NAME`: Name of the BLE device 56 | 57 | ## Usage 58 | 59 | 1. Power on the ESP32 60 | 2. Connect to the BLE device named "Blume" (or your configured name) 61 | 3. Send commands through the BLE interface: 62 | - `!D`: Request dimensions 63 | - `!d`: Demo mode commands 64 | - Other commands as documented in the code 65 | 66 | ## License 67 | 68 | [Add your license information here] 69 | -------------------------------------------------------------------------------- /cordova/www/ui/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 14 | 18 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | -------------------------------------------------------------------------------- /BLESerial.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | BLESerial.cpp - Bluetooth Low Energy Serial library. 3 | Copyright (c) 2017 by Grant Patterson. All rights reserved. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | 20 | #ifdef ESP32 21 | 22 | #include "BLESerial.h" 23 | 24 | BLESerial::BLESerial() { 25 | } 26 | 27 | // From BLECharacteristicCallbacks 28 | void BLESerial::onWrite(BLECharacteristic* characteristic) { 29 | buffer += characteristic->getValue(); 30 | } 31 | 32 | void BLESerial::begin(BLEServer* server, BLEUUID service_uuid, BLEUUID characteristic_uuid) 33 | { 34 | service = server->createService(service_uuid); 35 | buffer = ""; 36 | characteristic = service->createCharacteristic(characteristic_uuid, 37 | BLECharacteristic::PROPERTY_READ | 38 | BLECharacteristic::PROPERTY_WRITE | 39 | BLECharacteristic::PROPERTY_NOTIFY); 40 | BLEDescriptor* desc = new BLEDescriptor(BLEUUID((uint16_t)0x2901)); 41 | desc->setValue("Serial"); 42 | characteristic->addDescriptor(desc); 43 | characteristic->setCallbacks(this); 44 | service->start(); 45 | } 46 | 47 | void BLESerial::end() { 48 | } 49 | 50 | void BLESerial::setDebugOutput(bool en) { 51 | } 52 | 53 | int BLESerial::available(void) { 54 | return buffer.size(); 55 | } 56 | 57 | int BLESerial::peek(void) { 58 | if (available()) { 59 | return (int)buffer[0]; 60 | } 61 | return -1; 62 | } 63 | 64 | int BLESerial::read(void) { 65 | if (available()) { 66 | int result = (int)buffer[0]; 67 | buffer.erase(0, 1); 68 | return result; 69 | } 70 | return -1; 71 | } 72 | 73 | void BLESerial::flush() { 74 | // Pretty sure this is supposed to go both ways in Arduino. 75 | buffer.clear(); 76 | } 77 | 78 | size_t BLESerial::write(uint8_t c) { 79 | characteristic->setValue(&c, 1); 80 | characteristic->notify(); 81 | return 1; 82 | } 83 | 84 | size_t BLESerial::write(const uint8_t *buffer, size_t size) { 85 | characteristic->setValue((uint8_t*)buffer, size); 86 | characteristic->notify(); 87 | return size; 88 | } 89 | 90 | uint32_t BLESerial::baudRate() { 91 | return 0; 92 | } 93 | 94 | BLESerial::operator bool() const { 95 | return true; 96 | } 97 | 98 | #endif // ESP32 99 | -------------------------------------------------------------------------------- /cordova/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Blume 4 | 5 | An app to control LEDs based on the Blume Bluetooth platform. 6 | 7 | 8 | Grant Patterson 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /BLEDeviceID.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | BLEDeviceID.cpp - Bluetooth Low Energy Device ID Profile implementation. 3 | Copyright (c) 2017 by Grant Patterson. All rights reserved. 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | */ 19 | #ifdef ESP32 20 | #include "sdkconfig.h" 21 | #if defined(CONFIG_BT_ENABLED) 22 | #include 23 | 24 | #include "BLEDeviceID.h" 25 | 26 | BLEDeviceID::BLEDeviceID( 27 | BLEServer* server, 28 | std::string systemId, // 0x2A23, 64 bits, use strtoull format "0x..." 29 | std::string modelNumber, // 0x2A24 30 | std::string serialNumber, // 0x2A25 31 | std::string firmwareRevision, // 0x2A26 32 | std::string hardwareRevision, // 0x2A27 33 | std::string softwareRevision, // 0x2A28 34 | std::string manufacturerName, // 0x2A29 35 | std::string ieee11073_20601, // 0x2A2A, standard is behind paywall. 36 | // 0x2A50 PnP ID values: 37 | uint8_t vendorIdSource, // 1=Bluetooth SIG, 2=USB Impl. Forum 38 | uint16_t vendorId, 39 | uint16_t productId, 40 | uint16_t productVersion) { 41 | // Need space for 20 handles to hold all the characteristics we have. 42 | service = server->createService(BLEUUID((uint16_t)0x180A), 20); 43 | 44 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.system_id.xml 45 | // Use strtoull to convert string to 64-bit value. 46 | // systemId should probably be of format "0x0123456789ABCDEF" 47 | unsigned long long systemIdLong = strtoull(systemId.c_str(), NULL, 0); 48 | // Store 64 bits as byte stream; string is convenient 49 | systemId = std::string((char*)&systemIdLong, sizeof(systemIdLong)); 50 | // Need to reverse string to correct for bigendian 51 | systemId = std::string(systemId.rbegin(), systemId.rend()); 52 | service->createCharacteristic( 53 | BLEUUID((uint16_t)0x2A23), 54 | BLECharacteristic::PROPERTY_READ) 55 | ->setValue(systemId); 56 | 57 | // Straightforward string values. 58 | service->createCharacteristic( 59 | BLEUUID((uint16_t)0x2A24), 60 | BLECharacteristic::PROPERTY_READ) 61 | ->setValue(modelNumber); 62 | service->createCharacteristic( 63 | BLEUUID((uint16_t)0x2A25), 64 | BLECharacteristic::PROPERTY_READ) 65 | ->setValue(serialNumber); 66 | service->createCharacteristic( 67 | BLEUUID((uint16_t)0x2A26), 68 | BLECharacteristic::PROPERTY_READ) 69 | ->setValue(firmwareRevision); 70 | service->createCharacteristic( 71 | BLEUUID((uint16_t)0x2A27), 72 | BLECharacteristic::PROPERTY_READ) 73 | ->setValue(hardwareRevision); 74 | service->createCharacteristic( 75 | BLEUUID((uint16_t)0x2A28), 76 | BLECharacteristic::PROPERTY_READ) 77 | ->setValue(softwareRevision); 78 | service->createCharacteristic( 79 | BLEUUID((uint16_t)0x2A29), 80 | BLECharacteristic::PROPERTY_READ) 81 | ->setValue(manufacturerName); 82 | // Format is mysterious. 83 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.ieee_11073-20601_regulatory_certification_data_list.xml 84 | service->createCharacteristic( 85 | BLEUUID((uint16_t)0x2A2A), 86 | BLECharacteristic::PROPERTY_READ) 87 | ->setValue(ieee11073_20601); 88 | 89 | // Per spec, order these from least-significant octet to most. 90 | // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.pnp_id.xml 91 | uint8_t val2a50[7]; 92 | val2a50[6] = vendorIdSource; 93 | val2a50[5] = vendorId; 94 | val2a50[4] = vendorId >> 8; 95 | val2a50[3] = productId; 96 | val2a50[2] = productId >> 8; 97 | val2a50[1] = productVersion; 98 | val2a50[0] = productVersion >> 8; 99 | service->createCharacteristic( 100 | BLEUUID((uint16_t)0x2A50), 101 | BLECharacteristic::PROPERTY_READ) 102 | ->setValue(val2a50, sizeof(val2a50)); 103 | } 104 | 105 | void BLEDeviceID::start() { 106 | service->start(); 107 | } 108 | 109 | #endif // CONFIG_BT_ENABLED 110 | #endif // ESP32 111 | -------------------------------------------------------------------------------- /cordova/www/libs/nouislider/nouislider.css: -------------------------------------------------------------------------------- 1 | /*! nouislider - 10.0.0 - 2017-05-28 14:52:48 */ 2 | /* Functional styling; 3 | * These styles are required for noUiSlider to function. 4 | * You don't need to change these rules to apply your design. 5 | */ 6 | .noUi-target, 7 | .noUi-target * { 8 | -webkit-touch-callout: none; 9 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 10 | -webkit-user-select: none; 11 | -ms-touch-action: none; 12 | touch-action: none; 13 | -ms-user-select: none; 14 | -moz-user-select: none; 15 | user-select: none; 16 | -moz-box-sizing: border-box; 17 | box-sizing: border-box; 18 | } 19 | .noUi-target { 20 | position: relative; 21 | direction: ltr; 22 | } 23 | .noUi-base { 24 | width: 100%; 25 | height: 100%; 26 | position: relative; 27 | z-index: 1; 28 | /* Fix 401 */ 29 | } 30 | .noUi-connect { 31 | position: absolute; 32 | right: 0; 33 | top: 0; 34 | left: 0; 35 | bottom: 0; 36 | } 37 | .noUi-origin { 38 | position: absolute; 39 | height: 0; 40 | width: 0; 41 | } 42 | .noUi-handle { 43 | position: relative; 44 | z-index: 1; 45 | } 46 | .noUi-state-tap .noUi-connect, 47 | .noUi-state-tap .noUi-origin { 48 | -webkit-transition: top 0.3s, right 0.3s, bottom 0.3s, left 0.3s; 49 | transition: top 0.3s, right 0.3s, bottom 0.3s, left 0.3s; 50 | } 51 | .noUi-state-drag * { 52 | cursor: inherit !important; 53 | } 54 | /* Painting and performance; 55 | * Browsers can paint handles in their own layer. 56 | */ 57 | .noUi-base, 58 | .noUi-handle { 59 | -webkit-transform: translate3d(0, 0, 0); 60 | transform: translate3d(0, 0, 0); 61 | } 62 | /* Slider size and handle placement; 63 | */ 64 | .noUi-horizontal { 65 | height: 18px; 66 | } 67 | .noUi-horizontal .noUi-handle { 68 | width: 34px; 69 | height: 28px; 70 | left: -17px; 71 | top: -6px; 72 | } 73 | .noUi-vertical { 74 | width: 18px; 75 | } 76 | .noUi-vertical .noUi-handle { 77 | width: 28px; 78 | height: 34px; 79 | left: -6px; 80 | top: -17px; 81 | } 82 | /* Styling; 83 | */ 84 | .noUi-target { 85 | background: #FAFAFA; 86 | border-radius: 4px; 87 | border: 1px solid #D3D3D3; 88 | box-shadow: inset 0 1px 1px #F0F0F0, 0 3px 6px -5px #BBB; 89 | } 90 | .noUi-connect { 91 | background: #3FB8AF; 92 | border-radius: 4px; 93 | box-shadow: inset 0 0 3px rgba(51, 51, 51, 0.45); 94 | -webkit-transition: background 450ms; 95 | transition: background 450ms; 96 | } 97 | /* Handles and cursors; 98 | */ 99 | .noUi-draggable { 100 | cursor: ew-resize; 101 | } 102 | .noUi-vertical .noUi-draggable { 103 | cursor: ns-resize; 104 | } 105 | .noUi-handle { 106 | border: 1px solid #D9D9D9; 107 | border-radius: 3px; 108 | background: #FFF; 109 | cursor: default; 110 | box-shadow: inset 0 0 1px #FFF, inset 0 1px 7px #EBEBEB, 0 3px 6px -3px #BBB; 111 | } 112 | .noUi-active { 113 | box-shadow: inset 0 0 1px #FFF, inset 0 1px 7px #DDD, 0 3px 6px -3px #BBB; 114 | } 115 | /* Handle stripes; 116 | */ 117 | .noUi-handle:before, 118 | .noUi-handle:after { 119 | content: ""; 120 | display: block; 121 | position: absolute; 122 | height: 14px; 123 | width: 1px; 124 | background: #E8E7E6; 125 | left: 14px; 126 | top: 6px; 127 | } 128 | .noUi-handle:after { 129 | left: 17px; 130 | } 131 | .noUi-vertical .noUi-handle:before, 132 | .noUi-vertical .noUi-handle:after { 133 | width: 14px; 134 | height: 1px; 135 | left: 6px; 136 | top: 14px; 137 | } 138 | .noUi-vertical .noUi-handle:after { 139 | top: 17px; 140 | } 141 | /* Disabled state; 142 | */ 143 | [disabled] .noUi-connect { 144 | background: #B8B8B8; 145 | } 146 | [disabled].noUi-target, 147 | [disabled].noUi-handle, 148 | [disabled] .noUi-handle { 149 | cursor: not-allowed; 150 | } 151 | /* Base; 152 | * 153 | */ 154 | .noUi-pips, 155 | .noUi-pips * { 156 | -moz-box-sizing: border-box; 157 | box-sizing: border-box; 158 | } 159 | .noUi-pips { 160 | position: absolute; 161 | color: #999; 162 | } 163 | /* Values; 164 | * 165 | */ 166 | .noUi-value { 167 | position: absolute; 168 | white-space: nowrap; 169 | text-align: center; 170 | } 171 | .noUi-value-sub { 172 | color: #ccc; 173 | font-size: 10px; 174 | } 175 | /* Markings; 176 | * 177 | */ 178 | .noUi-marker { 179 | position: absolute; 180 | background: #CCC; 181 | } 182 | .noUi-marker-sub { 183 | background: #AAA; 184 | } 185 | .noUi-marker-large { 186 | background: #AAA; 187 | } 188 | /* Horizontal layout; 189 | * 190 | */ 191 | .noUi-pips-horizontal { 192 | padding: 10px 0; 193 | height: 80px; 194 | top: 100%; 195 | left: 0; 196 | width: 100%; 197 | } 198 | .noUi-value-horizontal { 199 | -webkit-transform: translate3d(-50%, 50%, 0); 200 | transform: translate3d(-50%, 50%, 0); 201 | } 202 | .noUi-marker-horizontal.noUi-marker { 203 | margin-left: -1px; 204 | width: 2px; 205 | height: 5px; 206 | } 207 | .noUi-marker-horizontal.noUi-marker-sub { 208 | height: 10px; 209 | } 210 | .noUi-marker-horizontal.noUi-marker-large { 211 | height: 15px; 212 | } 213 | /* Vertical layout; 214 | * 215 | */ 216 | .noUi-pips-vertical { 217 | padding: 0 10px; 218 | height: 100%; 219 | top: 0; 220 | left: 100%; 221 | } 222 | .noUi-value-vertical { 223 | -webkit-transform: translate3d(0, 50%, 0); 224 | transform: translate3d(0, 50%, 0); 225 | padding-left: 25px; 226 | } 227 | .noUi-marker-vertical.noUi-marker { 228 | width: 5px; 229 | height: 2px; 230 | margin-top: -1px; 231 | } 232 | .noUi-marker-vertical.noUi-marker-sub { 233 | width: 10px; 234 | } 235 | .noUi-marker-vertical.noUi-marker-large { 236 | width: 15px; 237 | } 238 | .noUi-tooltip { 239 | display: block; 240 | position: absolute; 241 | border: 1px solid #D9D9D9; 242 | border-radius: 3px; 243 | background: #fff; 244 | color: #000; 245 | padding: 5px; 246 | text-align: center; 247 | white-space: nowrap; 248 | } 249 | .noUi-horizontal .noUi-tooltip { 250 | -webkit-transform: translate(-50%, 0); 251 | transform: translate(-50%, 0); 252 | left: 50%; 253 | bottom: 120%; 254 | } 255 | .noUi-vertical .noUi-tooltip { 256 | -webkit-transform: translate(0, -50%); 257 | transform: translate(0, -50%); 258 | top: 50%; 259 | right: 120%; 260 | } 261 | -------------------------------------------------------------------------------- /Picopixel.h: -------------------------------------------------------------------------------- 1 | // Picopixel by Sebastian Weber. A tiny font 2 | // with all characters within a 6 pixel height. 3 | 4 | const uint8_t PicopixelBitmaps[] PROGMEM = { 5 | 0xE8, 0xB4, 0x57, 0xD5, 0xF5, 0x00, 0x4E, 0x3E, 0x80, 0xA5, 0x4A, 0x4A, 6 | 0x5A, 0x50, 0xC0, 0x6A, 0x40, 0x95, 0x80, 0xAA, 0x80, 0x5D, 0x00, 0x60, 7 | 0xE0, 0x80, 0x25, 0x48, 0x56, 0xD4, 0x75, 0x40, 0xC5, 0x4E, 0xC5, 0x1C, 8 | 0x97, 0x92, 0xF3, 0x1C, 0x53, 0x54, 0xE5, 0x48, 0x55, 0x54, 0x55, 0x94, 9 | 0xA0, 0x46, 0x64, 0xE3, 0x80, 0x98, 0xC5, 0x04, 0x56, 0xC6, 0x57, 0xDA, 10 | 0xD7, 0x5C, 0x72, 0x46, 0xD6, 0xDC, 0xF3, 0xCE, 0xF3, 0x48, 0x72, 0xD4, 11 | 0xB7, 0xDA, 0xF8, 0x24, 0xD4, 0xBB, 0x5A, 0x92, 0x4E, 0x8E, 0xEB, 0x58, 12 | 0x80, 0x9D, 0xB9, 0x90, 0x56, 0xD4, 0xD7, 0x48, 0x56, 0xD4, 0x40, 0xD7, 13 | 0x5A, 0x71, 0x1C, 0xE9, 0x24, 0xB6, 0xD4, 0xB6, 0xA4, 0x8C, 0x6B, 0x55, 14 | 0x00, 0xB5, 0x5A, 0xB5, 0x24, 0xE5, 0x4E, 0xEA, 0xC0, 0x91, 0x12, 0xD5, 15 | 0xC0, 0x54, 0xF0, 0x90, 0xC7, 0xF0, 0x93, 0x5E, 0x71, 0x80, 0x25, 0xDE, 16 | 0x5E, 0x30, 0x6E, 0x80, 0x77, 0x9C, 0x93, 0x5A, 0xB8, 0x45, 0x60, 0x92, 17 | 0xEA, 0xAA, 0x40, 0xD5, 0x6A, 0xD6, 0x80, 0x55, 0x00, 0xD7, 0x40, 0x75, 18 | 0x90, 0xE8, 0x71, 0xE0, 0xBA, 0x40, 0xB5, 0x80, 0xB5, 0x00, 0x8D, 0x54, 19 | 0xAA, 0x80, 0xAC, 0xE0, 0xE5, 0x70, 0x6A, 0x26, 0xFC, 0xC8, 0xAC, 0x5A }; 20 | 21 | const GFXglyph PicopixelGlyphs[] PROGMEM = { 22 | { 0, 0, 0, 2, 0, 1 }, // 0x20 ' ' 23 | { 0, 1, 5, 2, 0, -4 }, // 0x21 '!' 24 | { 1, 3, 2, 4, 0, -4 }, // 0x22 '"' 25 | { 2, 5, 5, 6, 0, -4 }, // 0x23 '#' 26 | { 6, 3, 6, 4, 0, -4 }, // 0x24 '$' 27 | { 9, 3, 5, 4, 0, -4 }, // 0x25 '%' 28 | { 11, 4, 5, 5, 0, -4 }, // 0x26 '&' 29 | { 14, 1, 2, 2, 0, -4 }, // 0x27 ''' 30 | { 15, 2, 5, 3, 0, -4 }, // 0x28 '(' 31 | { 17, 2, 5, 3, 0, -4 }, // 0x29 ')' 32 | { 19, 3, 3, 4, 0, -3 }, // 0x2A '*' 33 | { 21, 3, 3, 4, 0, -3 }, // 0x2B '+' 34 | { 23, 2, 2, 3, 0, 0 }, // 0x2C ',' 35 | { 24, 3, 1, 4, 0, -2 }, // 0x2D '-' 36 | { 25, 1, 1, 2, 0, 0 }, // 0x2E '.' 37 | { 26, 3, 5, 4, 0, -4 }, // 0x2F '/' 38 | { 28, 3, 5, 4, 0, -4 }, // 0x30 '0' 39 | { 30, 2, 5, 3, 0, -4 }, // 0x31 '1' 40 | { 32, 3, 5, 4, 0, -4 }, // 0x32 '2' 41 | { 34, 3, 5, 4, 0, -4 }, // 0x33 '3' 42 | { 36, 3, 5, 4, 0, -4 }, // 0x34 '4' 43 | { 38, 3, 5, 4, 0, -4 }, // 0x35 '5' 44 | { 40, 3, 5, 4, 0, -4 }, // 0x36 '6' 45 | { 42, 3, 5, 4, 0, -4 }, // 0x37 '7' 46 | { 44, 3, 5, 4, 0, -4 }, // 0x38 '8' 47 | { 46, 3, 5, 4, 0, -4 }, // 0x39 '9' 48 | { 48, 1, 3, 2, 0, -3 }, // 0x3A ':' 49 | { 49, 2, 4, 3, 0, -3 }, // 0x3B ';' 50 | { 50, 2, 3, 3, 0, -3 }, // 0x3C '<' 51 | { 51, 3, 3, 4, 0, -3 }, // 0x3D '=' 52 | { 53, 2, 3, 3, 0, -3 }, // 0x3E '>' 53 | { 54, 3, 5, 4, 0, -4 }, // 0x3F '?' 54 | { 56, 3, 5, 4, 0, -4 }, // 0x40 '@' 55 | { 58, 3, 5, 4, 0, -4 }, // 0x41 'A' 56 | { 60, 3, 5, 4, 0, -4 }, // 0x42 'B' 57 | { 62, 3, 5, 4, 0, -4 }, // 0x43 'C' 58 | { 64, 3, 5, 4, 0, -4 }, // 0x44 'D' 59 | { 66, 3, 5, 4, 0, -4 }, // 0x45 'E' 60 | { 68, 3, 5, 4, 0, -4 }, // 0x46 'F' 61 | { 70, 3, 5, 4, 0, -4 }, // 0x47 'G' 62 | { 72, 3, 5, 4, 0, -4 }, // 0x48 'H' 63 | { 74, 1, 5, 2, 0, -4 }, // 0x49 'I' 64 | { 75, 3, 5, 4, 0, -4 }, // 0x4A 'J' 65 | { 77, 3, 5, 4, 0, -4 }, // 0x4B 'K' 66 | { 79, 3, 5, 4, 0, -4 }, // 0x4C 'L' 67 | { 81, 5, 5, 6, 0, -4 }, // 0x4D 'M' 68 | { 85, 4, 5, 5, 0, -4 }, // 0x4E 'N' 69 | { 88, 3, 5, 4, 0, -4 }, // 0x4F 'O' 70 | { 90, 3, 5, 4, 0, -4 }, // 0x50 'P' 71 | { 92, 3, 6, 4, 0, -4 }, // 0x51 'Q' 72 | { 95, 3, 5, 4, 0, -4 }, // 0x52 'R' 73 | { 97, 3, 5, 4, 0, -4 }, // 0x53 'S' 74 | { 99, 3, 5, 4, 0, -4 }, // 0x54 'T' 75 | { 101, 3, 5, 4, 0, -4 }, // 0x55 'U' 76 | { 103, 3, 5, 4, 0, -4 }, // 0x56 'V' 77 | { 105, 5, 5, 6, 0, -4 }, // 0x57 'W' 78 | { 109, 3, 5, 4, 0, -4 }, // 0x58 'X' 79 | { 111, 3, 5, 4, 0, -4 }, // 0x59 'Y' 80 | { 113, 3, 5, 4, 0, -4 }, // 0x5A 'Z' 81 | { 115, 2, 5, 3, 0, -4 }, // 0x5B '[' 82 | { 117, 3, 5, 4, 0, -4 }, // 0x5C '\' 83 | { 119, 2, 5, 3, 0, -4 }, // 0x5D ']' 84 | { 121, 3, 2, 4, 0, -4 }, // 0x5E '^' 85 | { 122, 4, 1, 4, 0, 1 }, // 0x5F '_' 86 | { 123, 2, 2, 3, 0, -4 }, // 0x60 '`' 87 | { 124, 3, 4, 4, 0, -3 }, // 0x61 'a' 88 | { 126, 3, 5, 4, 0, -4 }, // 0x62 'b' 89 | { 128, 3, 3, 4, 0, -2 }, // 0x63 'c' 90 | { 130, 3, 5, 4, 0, -4 }, // 0x64 'd' 91 | { 132, 3, 4, 4, 0, -3 }, // 0x65 'e' 92 | { 134, 2, 5, 3, 0, -4 }, // 0x66 'f' 93 | { 136, 3, 5, 4, 0, -3 }, // 0x67 'g' 94 | { 138, 3, 5, 4, 0, -4 }, // 0x68 'h' 95 | { 140, 1, 5, 2, 0, -4 }, // 0x69 'i' 96 | { 141, 2, 6, 3, 0, -4 }, // 0x6A 'j' 97 | { 143, 3, 5, 4, 0, -4 }, // 0x6B 'k' 98 | { 145, 2, 5, 3, 0, -4 }, // 0x6C 'l' 99 | { 147, 5, 3, 6, 0, -2 }, // 0x6D 'm' 100 | { 149, 3, 3, 4, 0, -2 }, // 0x6E 'n' 101 | { 151, 3, 3, 4, 0, -2 }, // 0x6F 'o' 102 | { 153, 3, 4, 4, 0, -2 }, // 0x70 'p' 103 | { 155, 3, 4, 4, 0, -2 }, // 0x71 'q' 104 | { 157, 2, 3, 3, 0, -2 }, // 0x72 'r' 105 | { 158, 3, 4, 4, 0, -3 }, // 0x73 's' 106 | { 160, 2, 5, 3, 0, -4 }, // 0x74 't' 107 | { 162, 3, 3, 4, 0, -2 }, // 0x75 'u' 108 | { 164, 3, 3, 4, 0, -2 }, // 0x76 'v' 109 | { 166, 5, 3, 6, 0, -2 }, // 0x77 'w' 110 | { 168, 3, 3, 4, 0, -2 }, // 0x78 'x' 111 | { 170, 3, 4, 4, 0, -2 }, // 0x79 'y' 112 | { 172, 3, 4, 4, 0, -3 }, // 0x7A 'z' 113 | { 174, 3, 5, 4, 0, -4 }, // 0x7B '{' 114 | { 176, 1, 6, 2, 0, -4 }, // 0x7C '|' 115 | { 177, 3, 5, 4, 0, -4 }, // 0x7D '}' 116 | { 179, 4, 2, 5, 0, -3 } }; // 0x7E '~' 117 | 118 | const GFXfont Picopixel PROGMEM = { 119 | (uint8_t *)PicopixelBitmaps, 120 | (GFXglyph *)PicopixelGlyphs, 121 | 0x20, 0x7E, 7 }; 122 | 123 | // Approx. 852 bytes 124 | -------------------------------------------------------------------------------- /cordova/www/libs/evothings/util/util.js: -------------------------------------------------------------------------------- 1 | // File: util.js 2 | 3 | evothings = window.evothings || {}; 4 | 5 | /** 6 | * @namespace 7 | * @author Aaron Ardiri 8 | * @author Fredrik Eldh 9 | * @description Utilities for byte arrays. 10 | */ 11 | evothings.util = {}; 12 | 13 | ;(function() 14 | { 15 | /** 16 | * Interpret byte buffer as little endian 8 bit integer. 17 | * Returns converted number. 18 | * @param {ArrayBuffer} data - Input buffer. 19 | * @param {number} offset - Start of data. 20 | * @return Converted number. 21 | * @public 22 | */ 23 | evothings.util.littleEndianToInt8 = function(data, offset) 24 | { 25 | var x = evothings.util.littleEndianToUint8(data, offset) 26 | if (x & 0x80) x = x - 256 27 | return x 28 | } 29 | 30 | /** 31 | * Interpret byte buffer as unsigned little endian 8 bit integer. 32 | * Returns converted number. 33 | * @param {ArrayBuffer} data - Input buffer. 34 | * @param {number} offset - Start of data. 35 | * @return Converted number. 36 | * @public 37 | */ 38 | evothings.util.littleEndianToUint8 = function(data, offset) 39 | { 40 | return data[offset] 41 | } 42 | 43 | /** 44 | * Interpret byte buffer as little endian 16 bit integer. 45 | * Returns converted number. 46 | * @param {ArrayBuffer} data - Input buffer. 47 | * @param {number} offset - Start of data. 48 | * @return Converted number. 49 | * @public 50 | */ 51 | evothings.util.littleEndianToInt16 = function(data, offset) 52 | { 53 | return (evothings.util.littleEndianToInt8(data, offset + 1) << 8) + 54 | evothings.util.littleEndianToUint8(data, offset) 55 | } 56 | 57 | /** 58 | * Interpret byte buffer as unsigned little endian 16 bit integer. 59 | * Returns converted number. 60 | * @param {ArrayBuffer} data - Input buffer. 61 | * @param {number} offset - Start of data. 62 | * @return Converted number. 63 | * @public 64 | */ 65 | evothings.util.littleEndianToUint16 = function(data, offset) 66 | { 67 | return (evothings.util.littleEndianToUint8(data, offset + 1) << 8) + 68 | evothings.util.littleEndianToUint8(data, offset) 69 | } 70 | 71 | /** 72 | * Interpret byte buffer as unsigned little endian 32 bit integer. 73 | * Returns converted number. 74 | * @param {ArrayBuffer} data - Input buffer. 75 | * @param {number} offset - Start of data. 76 | * @return Converted number. 77 | * @public 78 | */ 79 | evothings.util.littleEndianToUint32 = function(data, offset) 80 | { 81 | return (evothings.util.littleEndianToUint8(data, offset + 3) << 24) + 82 | (evothings.util.littleEndianToUint8(data, offset + 2) << 16) + 83 | (evothings.util.littleEndianToUint8(data, offset + 1) << 8) + 84 | evothings.util.littleEndianToUint8(data, offset) 85 | } 86 | 87 | 88 | /** 89 | * Interpret byte buffer as signed big endian 16 bit integer. 90 | * Returns converted number. 91 | * @param {ArrayBuffer} data - Input buffer. 92 | * @param {number} offset - Start of data. 93 | * @return Converted number. 94 | * @public 95 | */ 96 | evothings.util.bigEndianToInt16 = function(data, offset) 97 | { 98 | return (evothings.util.littleEndianToInt8(data, offset) << 8) + 99 | evothings.util.littleEndianToUint8(data, offset + 1) 100 | } 101 | 102 | /** 103 | * Interpret byte buffer as unsigned big endian 16 bit integer. 104 | * Returns converted number. 105 | * @param {ArrayBuffer} data - Input buffer. 106 | * @param {number} offset - Start of data. 107 | * @return Converted number. 108 | * @public 109 | */ 110 | evothings.util.bigEndianToUint16 = function(data, offset) 111 | { 112 | return (evothings.util.littleEndianToUint8(data, offset) << 8) + 113 | evothings.util.littleEndianToUint8(data, offset + 1) 114 | } 115 | 116 | /** 117 | * Interpret byte buffer as unsigned big endian 32 bit integer. 118 | * Returns converted number. 119 | * @param {ArrayBuffer} data - Input buffer. 120 | * @param {number} offset - Start of data. 121 | * @return Converted number. 122 | * @public 123 | */ 124 | evothings.util.bigEndianToUint32 = function(data, offset) 125 | { 126 | return (evothings.util.littleEndianToUint8(data, offset) << 24) + 127 | (evothings.util.littleEndianToUint8(data, offset + 1) << 16) + 128 | (evothings.util.littleEndianToUint8(data, offset + 2) << 8) + 129 | evothings.util.littleEndianToUint8(data, offset + 3) 130 | } 131 | 132 | /** 133 | * Converts a single Base64 character to a 6-bit integer. 134 | * @private 135 | */ 136 | function b64ToUint6(nChr) { 137 | return nChr > 64 && nChr < 91 ? 138 | nChr - 65 139 | : nChr > 96 && nChr < 123 ? 140 | nChr - 71 141 | : nChr > 47 && nChr < 58 ? 142 | nChr + 4 143 | : nChr === 43 ? 144 | 62 145 | : nChr === 47 ? 146 | 63 147 | : 148 | 0; 149 | } 150 | 151 | /** 152 | * Decodes a Base64 string. Returns a Uint8Array. 153 | * nBlocksSize is optional. 154 | * @param {String} sBase64 155 | * @param {int} nBlocksSize 156 | * @return {Uint8Array} 157 | * @public 158 | */ 159 | evothings.util.base64DecToArr = function(sBase64, nBlocksSize) { 160 | var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""); 161 | var nInLen = sB64Enc.length; 162 | var nOutLen = nBlocksSize ? 163 | Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize 164 | : nInLen * 3 + 1 >> 2; 165 | var taBytes = new Uint8Array(nOutLen); 166 | 167 | for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) { 168 | nMod4 = nInIdx & 3; 169 | nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4; 170 | if (nMod4 === 3 || nInLen - nInIdx === 1) { 171 | for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { 172 | taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255; 173 | } 174 | nUint24 = 0; 175 | } 176 | } 177 | 178 | return taBytes; 179 | } 180 | 181 | /** 182 | * Returns the integer i in hexadecimal string form, 183 | * with leading zeroes, such that 184 | * the resulting string is at least byteCount*2 characters long. 185 | * @param {int} i 186 | * @param {int} byteCount 187 | * @public 188 | */ 189 | evothings.util.toHexString = function(i, byteCount) { 190 | var string = (new Number(i)).toString(16); 191 | while(string.length < byteCount*2) { 192 | string = '0'+string; 193 | } 194 | return string; 195 | } 196 | 197 | /** 198 | * Takes a ArrayBuffer or TypedArray and returns its hexadecimal representation. 199 | * No spaces or linebreaks. 200 | * @param data 201 | * @public 202 | */ 203 | evothings.util.typedArrayToHexString = function(data) { 204 | // view data as a Uint8Array, unless it already is one. 205 | if(data.buffer) { 206 | if(!(data instanceof Uint8Array)) 207 | data = new Uint8Array(data.buffer); 208 | } else if(data instanceof ArrayBuffer) { 209 | data = new Uint8Array(data); 210 | } else { 211 | throw "not an ArrayBuffer or TypedArray."; 212 | } 213 | var str = ''; 214 | for(var i=0; iFunctions for loading scripts asynchronously, 12 | * detecting platform, and other common application functionality.

13 | * @alias evothings 14 | * @public 15 | */ 16 | var evothings = window.evothings; 17 | 18 | /* ------------------ Script loading ------------------ */ 19 | 20 | var mScriptLoadingCounter = 0; 21 | var mLoadedScripts = {}; 22 | var mScriptsLoadedCallbacks = []; 23 | 24 | /** 25 | * Make sure to catch any DOMContentLoaded events occurring before 26 | * asynchronous loading of scripts. Those scripts, like ui.js, should check 27 | * this variable before listening for the event. 28 | */ 29 | evothings.gotDOMContentLoaded = false; 30 | 31 | window.addEventListener('DOMContentLoaded', function(e) 32 | { 33 | evothings.gotDOMContentLoaded = true; 34 | }) 35 | 36 | /** 37 | * Load a script. 38 | * @param {string} url - URL or path to the script. Relative paths are 39 | * relative to the HTML file that initiated script loading. 40 | * @param {function} successCallback - Optional parameterless function that 41 | * will be called when the script has loaded. 42 | * @param {function} errorCallback - Optional function that will be called 43 | * if loading the script fails, takes an error object as parameter. 44 | * @public 45 | */ 46 | evothings.loadScript = function(url, successCallback, errorCallback) 47 | { 48 | // If script is already loaded call callback directly and return. 49 | if (mLoadedScripts[url] == 'loadingcomplete') 50 | { 51 | successCallback && successCallback(); 52 | return; 53 | } 54 | 55 | // Add script to dictionary of loaded scripts. 56 | mLoadedScripts[url] = 'loadingstarted'; 57 | ++mScriptLoadingCounter; 58 | 59 | // Create script tag. 60 | var script = document.createElement('script'); 61 | script.type = 'text/javascript'; 62 | script.src = url; 63 | 64 | // Bind the onload event. 65 | script.onload = function() 66 | { 67 | // Mark as loaded. 68 | mLoadedScripts[url] = 'loadingcomplete'; 69 | --mScriptLoadingCounter; 70 | 71 | // Call success callback if given. 72 | successCallback && successCallback(); 73 | 74 | // Call scripts loaded callbacks if this was the last script loaded. 75 | if (0 == mScriptLoadingCounter) 76 | { 77 | for (var i = 0; i < mScriptsLoadedCallbacks.length; ++i) 78 | { 79 | var loadedCallback = mScriptsLoadedCallbacks[i]; 80 | loadedCallback && loadedCallback(); 81 | } 82 | 83 | // Clear callbacks - should we do this??? 84 | mScriptsLoadedCallbacks = []; 85 | } 86 | }; 87 | 88 | // onerror fires for things like malformed URLs and 404's. 89 | // If this function is called, the matching onload will not be called and 90 | // scriptsLoaded will not fire. 91 | script.onerror = function(error) 92 | { 93 | errorCallback && errorCallback(error); 94 | }; 95 | 96 | // Attaching the script tag to the document starts loading the script. 97 | document.head.appendChild(script); 98 | }; 99 | 100 | /** 101 | * Load array of scripts. 102 | * @param {array} array - Array of URL or path name stringa. 103 | * Relative paths are relative to the HTML file that initiated 104 | * script loading. 105 | * @param {function} loadedCallback - Optional parameterless 106 | * function called when all scripts in the array has loaded. 107 | * @public 108 | */ 109 | evothings.loadScripts = function(array, loadedCallback) 110 | { 111 | var lib = array.shift(); 112 | if (!lib) 113 | { 114 | // Array is empty and all scripts are loaded. 115 | loadedCallback && loadedCallback(); 116 | } 117 | else 118 | { 119 | // Load next script. 120 | evothings.loadScript(lib, function() { 121 | evothings.loadScripts(array, loadedCallback); 122 | }); 123 | } 124 | }; 125 | 126 | /** 127 | * Experimental. 128 | * Mark a script as loaded. This is useful if a script is designed 129 | * to be included both in HTML and in JavaScript. 130 | * @param {string} pathOrURL - URL or path to the script. Relative paths are 131 | * relative to the HTML file that initiated script loading. 132 | * @public 133 | */ 134 | evothings.markScriptAsLoaded = function(pathOrURL) 135 | { 136 | mLoadedScripts[url] = 'loadingcomplete'; 137 | }; 138 | 139 | /** 140 | *

Add a callback that will be called when all scripts are loaded.

141 | *

It is good practise to always use this function when 142 | * loading script asynchronously or using a library that does so.

143 | * @param {function} callback - Parameterless function that will 144 | * be called when all scripts have finished loading. 145 | * @public 146 | */ 147 | evothings.scriptsLoaded = function(callback) 148 | { 149 | // If scripts are already loaded call the callback directly, 150 | // else add the callback to the callbacks array. 151 | if (0 != Object.keys(mLoadedScripts).length && 152 | 0 == mScriptLoadingCounter) 153 | { 154 | callback && callback(); 155 | } 156 | else 157 | { 158 | mScriptsLoadedCallbacks.push(callback); 159 | } 160 | }; 161 | 162 | /* ------------------ Debugging ------------------ */ 163 | 164 | /** 165 | * Print a JavaScript object (dictionary). For debugging. 166 | * 167 | * @param {Object} obj - Object to print. 168 | * @param {function} printFun - print function (optional - defaults to 169 | * console.log if not given). 170 | * 171 | * @example 172 | * var obj = { company: 'Evothings', field: 'IoT' }; 173 | * evothings.printObject(obj); 174 | * evothings.printObject(obj, console.log); 175 | * 176 | * @public 177 | */ 178 | evothings.printObject = function(obj, printFun) 179 | { 180 | printFun = printFun || console.log; 181 | function print(obj, level) 182 | { 183 | var indent = new Array(level + 1).join(' '); 184 | for (var prop in obj) 185 | { 186 | if (obj.hasOwnProperty(prop)) 187 | { 188 | var value = obj[prop]; 189 | if (typeof value == 'object') 190 | { 191 | printFun(indent + prop + ':'); 192 | print(value, level + 1); 193 | } 194 | else 195 | { 196 | printFun(indent + prop + ': ' + value); 197 | } 198 | } 199 | } 200 | } 201 | print(obj, 0); 202 | }; 203 | 204 | /* ------------------ Platform check ------------------ */ 205 | 206 | /** 207 | * @namespace 208 | * @description Namespace for platform check functions. 209 | */ 210 | evothings.os = {}; 211 | 212 | /** 213 | * Returns true if current platform is iOS, false if not. 214 | * @return {boolean} true if platform is iOS, false if not. 215 | * @public 216 | */ 217 | evothings.os.isIOS = function() 218 | { 219 | return /iP(hone|ad|od)/.test(navigator.userAgent); 220 | }; 221 | 222 | /** 223 | * Returns true if current platform is iOS 7, false if not. 224 | * @return {boolean} true if platform is iOS 7, false if not. 225 | * @public 226 | */ 227 | evothings.os.isIOS7 = function() 228 | { 229 | return /iP(hone|ad|od).*OS 7/.test(navigator.userAgent); 230 | }; 231 | 232 | /** 233 | * Returns true if current platform is Android, false if not. 234 | * @return {boolean} true if platform is Android, false if not. 235 | * @public 236 | */ 237 | evothings.os.isAndroid = function() 238 | { 239 | return /Android|android/.test(navigator.userAgent); 240 | }; 241 | 242 | /** 243 | * Returns true if current platform is Windows Phone, false if not. 244 | * @return {boolean} true if platform is Windows Phone, false if not. 245 | * @public 246 | */ 247 | evothings.os.isWP = function() 248 | { 249 | return /Windows Phone/.test(navigator.userAgent); 250 | }; 251 | })(); 252 | -------------------------------------------------------------------------------- /Adafruit_GFX.h: -------------------------------------------------------------------------------- 1 | #ifndef _ADAFRUIT_GFX_H 2 | #define _ADAFRUIT_GFX_H 3 | 4 | #if ARDUINO >= 100 5 | #include "Arduino.h" 6 | #include "Print.h" 7 | #else 8 | #include "WProgram.h" 9 | #endif 10 | #include "gfxfont.h" 11 | 12 | class Adafruit_GFX : public Print { 13 | 14 | public: 15 | 16 | Adafruit_GFX(int16_t w, int16_t h); // Constructor 17 | 18 | // This MUST be defined by the subclass: 19 | virtual void drawPixel(int16_t x, int16_t y, uint16_t color) = 0; 20 | 21 | // TRANSACTION API / CORE DRAW API 22 | // These MAY be overridden by the subclass to provide device-specific 23 | // optimized code. Otherwise 'generic' versions are used. 24 | virtual void startWrite(void); 25 | virtual void writePixel(int16_t x, int16_t y, uint16_t color); 26 | virtual void writeFillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); 27 | virtual void writeFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color); 28 | virtual void writeFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color); 29 | virtual void writeLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color); 30 | virtual void endWrite(void); 31 | 32 | // CONTROL API 33 | // These MAY be overridden by the subclass to provide device-specific 34 | // optimized code. Otherwise 'generic' versions are used. 35 | virtual void setRotation(uint8_t r); 36 | virtual void invertDisplay(boolean i); 37 | 38 | // BASIC DRAW API 39 | // These MAY be overridden by the subclass to provide device-specific 40 | // optimized code. Otherwise 'generic' versions are used. 41 | virtual void 42 | // It's good to implement those, even if using transaction API 43 | drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color), 44 | drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color), 45 | fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color), 46 | fillScreen(uint16_t color), 47 | // Optional and probably not necessary to change 48 | drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color), 49 | drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); 50 | 51 | // These exist only with Adafruit_GFX (no subclass overrides) 52 | void 53 | drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color), 54 | drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, 55 | uint16_t color), 56 | fillCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color), 57 | fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, 58 | int16_t delta, uint16_t color), 59 | drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, 60 | int16_t x2, int16_t y2, uint16_t color), 61 | fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, 62 | int16_t x2, int16_t y2, uint16_t color), 63 | drawRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h, 64 | int16_t radius, uint16_t color), 65 | fillRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h, 66 | int16_t radius, uint16_t color), 67 | drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[], 68 | int16_t w, int16_t h, uint16_t color), 69 | drawBitmap(int16_t x, int16_t y, const uint8_t bitmap[], 70 | int16_t w, int16_t h, uint16_t color, uint16_t bg), 71 | drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, 72 | int16_t w, int16_t h, uint16_t color), 73 | drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, 74 | int16_t w, int16_t h, uint16_t color, uint16_t bg), 75 | drawXBitmap(int16_t x, int16_t y, const uint8_t bitmap[], 76 | int16_t w, int16_t h, uint16_t color), 77 | drawGrayscaleBitmap(int16_t x, int16_t y, const uint8_t bitmap[], 78 | int16_t w, int16_t h), 79 | drawGrayscaleBitmap(int16_t x, int16_t y, uint8_t *bitmap, 80 | int16_t w, int16_t h), 81 | drawGrayscaleBitmap(int16_t x, int16_t y, 82 | const uint8_t bitmap[], const uint8_t mask[], 83 | int16_t w, int16_t h), 84 | drawGrayscaleBitmap(int16_t x, int16_t y, 85 | uint8_t *bitmap, uint8_t *mask, int16_t w, int16_t h), 86 | drawRGBBitmap(int16_t x, int16_t y, const uint16_t bitmap[], 87 | int16_t w, int16_t h), 88 | drawRGBBitmap(int16_t x, int16_t y, uint16_t *bitmap, 89 | int16_t w, int16_t h), 90 | drawRGBBitmap(int16_t x, int16_t y, 91 | const uint16_t bitmap[], const uint8_t mask[], 92 | int16_t w, int16_t h), 93 | drawRGBBitmap(int16_t x, int16_t y, 94 | uint16_t *bitmap, uint8_t *mask, int16_t w, int16_t h), 95 | drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, 96 | uint16_t bg, uint8_t size), 97 | setCursor(int16_t x, int16_t y), 98 | setTextColor(uint16_t c), 99 | setTextColor(uint16_t c, uint16_t bg), 100 | setTextSize(uint8_t s), 101 | setTextWrap(boolean w), 102 | cp437(boolean x=true), 103 | setFont(const GFXfont *f = NULL), 104 | getTextBounds(char *string, int16_t x, int16_t y, 105 | int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h), 106 | getTextBounds(const __FlashStringHelper *s, int16_t x, int16_t y, 107 | int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h); 108 | 109 | #if ARDUINO >= 100 110 | virtual size_t write(uint8_t); 111 | #else 112 | virtual void write(uint8_t); 113 | #endif 114 | 115 | int16_t height(void) const; 116 | int16_t width(void) const; 117 | 118 | uint8_t getRotation(void) const; 119 | 120 | // get current cursor position (get rotation safe maximum values, using: width() for x, height() for y) 121 | int16_t getCursorX(void) const; 122 | int16_t getCursorY(void) const; 123 | 124 | protected: 125 | void 126 | charBounds(char c, int16_t *x, int16_t *y, 127 | int16_t *minx, int16_t *miny, int16_t *maxx, int16_t *maxy); 128 | const int16_t 129 | WIDTH, HEIGHT; // This is the 'raw' display w/h - never changes 130 | int16_t 131 | _width, _height, // Display w/h as modified by current rotation 132 | cursor_x, cursor_y; 133 | uint16_t 134 | textcolor, textbgcolor; 135 | uint8_t 136 | textsize, 137 | rotation; 138 | boolean 139 | wrap, // If set, 'wrap' text at right edge of display 140 | _cp437; // If set, use correct CP437 charset (default is off) 141 | GFXfont 142 | *gfxFont; 143 | }; 144 | 145 | class Adafruit_GFX_Button { 146 | 147 | public: 148 | Adafruit_GFX_Button(void); 149 | // "Classic" initButton() uses center & size 150 | void initButton(Adafruit_GFX *gfx, int16_t x, int16_t y, 151 | uint16_t w, uint16_t h, uint16_t outline, uint16_t fill, 152 | uint16_t textcolor, char *label, uint8_t textsize); 153 | // New/alt initButton() uses upper-left corner & size 154 | void initButtonUL(Adafruit_GFX *gfx, int16_t x1, int16_t y1, 155 | uint16_t w, uint16_t h, uint16_t outline, uint16_t fill, 156 | uint16_t textcolor, char *label, uint8_t textsize); 157 | void drawButton(boolean inverted = false); 158 | boolean contains(int16_t x, int16_t y); 159 | 160 | void press(boolean p); 161 | boolean isPressed(); 162 | boolean justPressed(); 163 | boolean justReleased(); 164 | 165 | private: 166 | Adafruit_GFX *_gfx; 167 | int16_t _x1, _y1; // Coordinates of top-left corner 168 | uint16_t _w, _h; 169 | uint8_t _textsize; 170 | uint16_t _outlinecolor, _fillcolor, _textcolor; 171 | char _label[10]; 172 | 173 | boolean currstate, laststate; 174 | }; 175 | 176 | class GFXcanvas1 : public Adafruit_GFX { 177 | public: 178 | GFXcanvas1(uint16_t w, uint16_t h); 179 | ~GFXcanvas1(void); 180 | void drawPixel(int16_t x, int16_t y, uint16_t color), 181 | fillScreen(uint16_t color); 182 | uint8_t *getBuffer(void); 183 | private: 184 | uint8_t *buffer; 185 | }; 186 | 187 | class GFXcanvas8 : public Adafruit_GFX { 188 | public: 189 | GFXcanvas8(uint16_t w, uint16_t h); 190 | ~GFXcanvas8(void); 191 | void drawPixel(int16_t x, int16_t y, uint16_t color), 192 | fillScreen(uint16_t color), 193 | writeFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color); 194 | 195 | uint8_t *getBuffer(void); 196 | private: 197 | uint8_t *buffer; 198 | }; 199 | 200 | class GFXcanvas16 : public Adafruit_GFX { 201 | public: 202 | GFXcanvas16(uint16_t w, uint16_t h); 203 | ~GFXcanvas16(void); 204 | void drawPixel(int16_t x, int16_t y, uint16_t color), 205 | fillScreen(uint16_t color); 206 | uint16_t *getBuffer(void); 207 | private: 208 | uint16_t *buffer; 209 | }; 210 | 211 | #endif // _ADAFRUIT_GFX_H 212 | -------------------------------------------------------------------------------- /glcdfont.c: -------------------------------------------------------------------------------- 1 | // This is the 'classic' fixed-space bitmap font for Adafruit_GFX since 1.0. 2 | // See gfxfont.h for newer custom bitmap font info. 3 | 4 | #ifndef FONT5X7_H 5 | #define FONT5X7_H 6 | 7 | #ifdef __AVR__ 8 | #include 9 | #include 10 | #elif defined(ESP8266) 11 | #include 12 | #else 13 | #define PROGMEM 14 | #endif 15 | 16 | // Standard ASCII 5x7 font 17 | 18 | static const unsigned char font[] PROGMEM = { 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 20 | 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, 21 | 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, 22 | 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, 23 | 0x18, 0x3C, 0x7E, 0x3C, 0x18, 24 | 0x1C, 0x57, 0x7D, 0x57, 0x1C, 25 | 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, 26 | 0x00, 0x18, 0x3C, 0x18, 0x00, 27 | 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, 28 | 0x00, 0x18, 0x24, 0x18, 0x00, 29 | 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, 30 | 0x30, 0x48, 0x3A, 0x06, 0x0E, 31 | 0x26, 0x29, 0x79, 0x29, 0x26, 32 | 0x40, 0x7F, 0x05, 0x05, 0x07, 33 | 0x40, 0x7F, 0x05, 0x25, 0x3F, 34 | 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, 35 | 0x7F, 0x3E, 0x1C, 0x1C, 0x08, 36 | 0x08, 0x1C, 0x1C, 0x3E, 0x7F, 37 | 0x14, 0x22, 0x7F, 0x22, 0x14, 38 | 0x5F, 0x5F, 0x00, 0x5F, 0x5F, 39 | 0x06, 0x09, 0x7F, 0x01, 0x7F, 40 | 0x00, 0x66, 0x89, 0x95, 0x6A, 41 | 0x60, 0x60, 0x60, 0x60, 0x60, 42 | 0x94, 0xA2, 0xFF, 0xA2, 0x94, 43 | 0x08, 0x04, 0x7E, 0x04, 0x08, 44 | 0x10, 0x20, 0x7E, 0x20, 0x10, 45 | 0x08, 0x08, 0x2A, 0x1C, 0x08, 46 | 0x08, 0x1C, 0x2A, 0x08, 0x08, 47 | 0x1E, 0x10, 0x10, 0x10, 0x10, 48 | 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, 49 | 0x30, 0x38, 0x3E, 0x38, 0x30, 50 | 0x06, 0x0E, 0x3E, 0x0E, 0x06, 51 | 0x00, 0x00, 0x00, 0x00, 0x00, 52 | 0x00, 0x00, 0x5F, 0x00, 0x00, 53 | 0x00, 0x07, 0x00, 0x07, 0x00, 54 | 0x14, 0x7F, 0x14, 0x7F, 0x14, 55 | 0x24, 0x2A, 0x7F, 0x2A, 0x12, 56 | 0x23, 0x13, 0x08, 0x64, 0x62, 57 | 0x36, 0x49, 0x56, 0x20, 0x50, 58 | 0x00, 0x08, 0x07, 0x03, 0x00, 59 | 0x00, 0x1C, 0x22, 0x41, 0x00, 60 | 0x00, 0x41, 0x22, 0x1C, 0x00, 61 | 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 62 | 0x08, 0x08, 0x3E, 0x08, 0x08, 63 | 0x00, 0x80, 0x70, 0x30, 0x00, 64 | 0x08, 0x08, 0x08, 0x08, 0x08, 65 | 0x00, 0x00, 0x60, 0x60, 0x00, 66 | 0x20, 0x10, 0x08, 0x04, 0x02, 67 | 0x3E, 0x51, 0x49, 0x45, 0x3E, 68 | 0x00, 0x42, 0x7F, 0x40, 0x00, 69 | 0x72, 0x49, 0x49, 0x49, 0x46, 70 | 0x21, 0x41, 0x49, 0x4D, 0x33, 71 | 0x18, 0x14, 0x12, 0x7F, 0x10, 72 | 0x27, 0x45, 0x45, 0x45, 0x39, 73 | 0x3C, 0x4A, 0x49, 0x49, 0x31, 74 | 0x41, 0x21, 0x11, 0x09, 0x07, 75 | 0x36, 0x49, 0x49, 0x49, 0x36, 76 | 0x46, 0x49, 0x49, 0x29, 0x1E, 77 | 0x00, 0x00, 0x14, 0x00, 0x00, 78 | 0x00, 0x40, 0x34, 0x00, 0x00, 79 | 0x00, 0x08, 0x14, 0x22, 0x41, 80 | 0x14, 0x14, 0x14, 0x14, 0x14, 81 | 0x00, 0x41, 0x22, 0x14, 0x08, 82 | 0x02, 0x01, 0x59, 0x09, 0x06, 83 | 0x3E, 0x41, 0x5D, 0x59, 0x4E, 84 | 0x7C, 0x12, 0x11, 0x12, 0x7C, 85 | 0x7F, 0x49, 0x49, 0x49, 0x36, 86 | 0x3E, 0x41, 0x41, 0x41, 0x22, 87 | 0x7F, 0x41, 0x41, 0x41, 0x3E, 88 | 0x7F, 0x49, 0x49, 0x49, 0x41, 89 | 0x7F, 0x09, 0x09, 0x09, 0x01, 90 | 0x3E, 0x41, 0x41, 0x51, 0x73, 91 | 0x7F, 0x08, 0x08, 0x08, 0x7F, 92 | 0x00, 0x41, 0x7F, 0x41, 0x00, 93 | 0x20, 0x40, 0x41, 0x3F, 0x01, 94 | 0x7F, 0x08, 0x14, 0x22, 0x41, 95 | 0x7F, 0x40, 0x40, 0x40, 0x40, 96 | 0x7F, 0x02, 0x1C, 0x02, 0x7F, 97 | 0x7F, 0x04, 0x08, 0x10, 0x7F, 98 | 0x3E, 0x41, 0x41, 0x41, 0x3E, 99 | 0x7F, 0x09, 0x09, 0x09, 0x06, 100 | 0x3E, 0x41, 0x51, 0x21, 0x5E, 101 | 0x7F, 0x09, 0x19, 0x29, 0x46, 102 | 0x26, 0x49, 0x49, 0x49, 0x32, 103 | 0x03, 0x01, 0x7F, 0x01, 0x03, 104 | 0x3F, 0x40, 0x40, 0x40, 0x3F, 105 | 0x1F, 0x20, 0x40, 0x20, 0x1F, 106 | 0x3F, 0x40, 0x38, 0x40, 0x3F, 107 | 0x63, 0x14, 0x08, 0x14, 0x63, 108 | 0x03, 0x04, 0x78, 0x04, 0x03, 109 | 0x61, 0x59, 0x49, 0x4D, 0x43, 110 | 0x00, 0x7F, 0x41, 0x41, 0x41, 111 | 0x02, 0x04, 0x08, 0x10, 0x20, 112 | 0x00, 0x41, 0x41, 0x41, 0x7F, 113 | 0x04, 0x02, 0x01, 0x02, 0x04, 114 | 0x40, 0x40, 0x40, 0x40, 0x40, 115 | 0x00, 0x03, 0x07, 0x08, 0x00, 116 | 0x20, 0x54, 0x54, 0x78, 0x40, 117 | 0x7F, 0x28, 0x44, 0x44, 0x38, 118 | 0x38, 0x44, 0x44, 0x44, 0x28, 119 | 0x38, 0x44, 0x44, 0x28, 0x7F, 120 | 0x38, 0x54, 0x54, 0x54, 0x18, 121 | 0x00, 0x08, 0x7E, 0x09, 0x02, 122 | 0x18, 0xA4, 0xA4, 0x9C, 0x78, 123 | 0x7F, 0x08, 0x04, 0x04, 0x78, 124 | 0x00, 0x44, 0x7D, 0x40, 0x00, 125 | 0x20, 0x40, 0x40, 0x3D, 0x00, 126 | 0x7F, 0x10, 0x28, 0x44, 0x00, 127 | 0x00, 0x41, 0x7F, 0x40, 0x00, 128 | 0x7C, 0x04, 0x78, 0x04, 0x78, 129 | 0x7C, 0x08, 0x04, 0x04, 0x78, 130 | 0x38, 0x44, 0x44, 0x44, 0x38, 131 | 0xFC, 0x18, 0x24, 0x24, 0x18, 132 | 0x18, 0x24, 0x24, 0x18, 0xFC, 133 | 0x7C, 0x08, 0x04, 0x04, 0x08, 134 | 0x48, 0x54, 0x54, 0x54, 0x24, 135 | 0x04, 0x04, 0x3F, 0x44, 0x24, 136 | 0x3C, 0x40, 0x40, 0x20, 0x7C, 137 | 0x1C, 0x20, 0x40, 0x20, 0x1C, 138 | 0x3C, 0x40, 0x30, 0x40, 0x3C, 139 | 0x44, 0x28, 0x10, 0x28, 0x44, 140 | 0x4C, 0x90, 0x90, 0x90, 0x7C, 141 | 0x44, 0x64, 0x54, 0x4C, 0x44, 142 | 0x00, 0x08, 0x36, 0x41, 0x00, 143 | 0x00, 0x00, 0x77, 0x00, 0x00, 144 | 0x00, 0x41, 0x36, 0x08, 0x00, 145 | 0x02, 0x01, 0x02, 0x04, 0x02, 146 | 0x3C, 0x26, 0x23, 0x26, 0x3C, 147 | 0x1E, 0xA1, 0xA1, 0x61, 0x12, 148 | 0x3A, 0x40, 0x40, 0x20, 0x7A, 149 | 0x38, 0x54, 0x54, 0x55, 0x59, 150 | 0x21, 0x55, 0x55, 0x79, 0x41, 151 | 0x22, 0x54, 0x54, 0x78, 0x42, // a-umlaut 152 | 0x21, 0x55, 0x54, 0x78, 0x40, 153 | 0x20, 0x54, 0x55, 0x79, 0x40, 154 | 0x0C, 0x1E, 0x52, 0x72, 0x12, 155 | 0x39, 0x55, 0x55, 0x55, 0x59, 156 | 0x39, 0x54, 0x54, 0x54, 0x59, 157 | 0x39, 0x55, 0x54, 0x54, 0x58, 158 | 0x00, 0x00, 0x45, 0x7C, 0x41, 159 | 0x00, 0x02, 0x45, 0x7D, 0x42, 160 | 0x00, 0x01, 0x45, 0x7C, 0x40, 161 | 0x7D, 0x12, 0x11, 0x12, 0x7D, // A-umlaut 162 | 0xF0, 0x28, 0x25, 0x28, 0xF0, 163 | 0x7C, 0x54, 0x55, 0x45, 0x00, 164 | 0x20, 0x54, 0x54, 0x7C, 0x54, 165 | 0x7C, 0x0A, 0x09, 0x7F, 0x49, 166 | 0x32, 0x49, 0x49, 0x49, 0x32, 167 | 0x3A, 0x44, 0x44, 0x44, 0x3A, // o-umlaut 168 | 0x32, 0x4A, 0x48, 0x48, 0x30, 169 | 0x3A, 0x41, 0x41, 0x21, 0x7A, 170 | 0x3A, 0x42, 0x40, 0x20, 0x78, 171 | 0x00, 0x9D, 0xA0, 0xA0, 0x7D, 172 | 0x3D, 0x42, 0x42, 0x42, 0x3D, // O-umlaut 173 | 0x3D, 0x40, 0x40, 0x40, 0x3D, 174 | 0x3C, 0x24, 0xFF, 0x24, 0x24, 175 | 0x48, 0x7E, 0x49, 0x43, 0x66, 176 | 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, 177 | 0xFF, 0x09, 0x29, 0xF6, 0x20, 178 | 0xC0, 0x88, 0x7E, 0x09, 0x03, 179 | 0x20, 0x54, 0x54, 0x79, 0x41, 180 | 0x00, 0x00, 0x44, 0x7D, 0x41, 181 | 0x30, 0x48, 0x48, 0x4A, 0x32, 182 | 0x38, 0x40, 0x40, 0x22, 0x7A, 183 | 0x00, 0x7A, 0x0A, 0x0A, 0x72, 184 | 0x7D, 0x0D, 0x19, 0x31, 0x7D, 185 | 0x26, 0x29, 0x29, 0x2F, 0x28, 186 | 0x26, 0x29, 0x29, 0x29, 0x26, 187 | 0x30, 0x48, 0x4D, 0x40, 0x20, 188 | 0x38, 0x08, 0x08, 0x08, 0x08, 189 | 0x08, 0x08, 0x08, 0x08, 0x38, 190 | 0x2F, 0x10, 0xC8, 0xAC, 0xBA, 191 | 0x2F, 0x10, 0x28, 0x34, 0xFA, 192 | 0x00, 0x00, 0x7B, 0x00, 0x00, 193 | 0x08, 0x14, 0x2A, 0x14, 0x22, 194 | 0x22, 0x14, 0x2A, 0x14, 0x08, 195 | 0x55, 0x00, 0x55, 0x00, 0x55, // #176 (25% block) missing in old code 196 | 0xAA, 0x55, 0xAA, 0x55, 0xAA, // 50% block 197 | 0xFF, 0x55, 0xFF, 0x55, 0xFF, // 75% block 198 | 0x00, 0x00, 0x00, 0xFF, 0x00, 199 | 0x10, 0x10, 0x10, 0xFF, 0x00, 200 | 0x14, 0x14, 0x14, 0xFF, 0x00, 201 | 0x10, 0x10, 0xFF, 0x00, 0xFF, 202 | 0x10, 0x10, 0xF0, 0x10, 0xF0, 203 | 0x14, 0x14, 0x14, 0xFC, 0x00, 204 | 0x14, 0x14, 0xF7, 0x00, 0xFF, 205 | 0x00, 0x00, 0xFF, 0x00, 0xFF, 206 | 0x14, 0x14, 0xF4, 0x04, 0xFC, 207 | 0x14, 0x14, 0x17, 0x10, 0x1F, 208 | 0x10, 0x10, 0x1F, 0x10, 0x1F, 209 | 0x14, 0x14, 0x14, 0x1F, 0x00, 210 | 0x10, 0x10, 0x10, 0xF0, 0x00, 211 | 0x00, 0x00, 0x00, 0x1F, 0x10, 212 | 0x10, 0x10, 0x10, 0x1F, 0x10, 213 | 0x10, 0x10, 0x10, 0xF0, 0x10, 214 | 0x00, 0x00, 0x00, 0xFF, 0x10, 215 | 0x10, 0x10, 0x10, 0x10, 0x10, 216 | 0x10, 0x10, 0x10, 0xFF, 0x10, 217 | 0x00, 0x00, 0x00, 0xFF, 0x14, 218 | 0x00, 0x00, 0xFF, 0x00, 0xFF, 219 | 0x00, 0x00, 0x1F, 0x10, 0x17, 220 | 0x00, 0x00, 0xFC, 0x04, 0xF4, 221 | 0x14, 0x14, 0x17, 0x10, 0x17, 222 | 0x14, 0x14, 0xF4, 0x04, 0xF4, 223 | 0x00, 0x00, 0xFF, 0x00, 0xF7, 224 | 0x14, 0x14, 0x14, 0x14, 0x14, 225 | 0x14, 0x14, 0xF7, 0x00, 0xF7, 226 | 0x14, 0x14, 0x14, 0x17, 0x14, 227 | 0x10, 0x10, 0x1F, 0x10, 0x1F, 228 | 0x14, 0x14, 0x14, 0xF4, 0x14, 229 | 0x10, 0x10, 0xF0, 0x10, 0xF0, 230 | 0x00, 0x00, 0x1F, 0x10, 0x1F, 231 | 0x00, 0x00, 0x00, 0x1F, 0x14, 232 | 0x00, 0x00, 0x00, 0xFC, 0x14, 233 | 0x00, 0x00, 0xF0, 0x10, 0xF0, 234 | 0x10, 0x10, 0xFF, 0x10, 0xFF, 235 | 0x14, 0x14, 0x14, 0xFF, 0x14, 236 | 0x10, 0x10, 0x10, 0x1F, 0x00, 237 | 0x00, 0x00, 0x00, 0xF0, 0x10, 238 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 239 | 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 240 | 0xFF, 0xFF, 0xFF, 0x00, 0x00, 241 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 242 | 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 243 | 0x38, 0x44, 0x44, 0x38, 0x44, 244 | 0xFC, 0x4A, 0x4A, 0x4A, 0x34, // sharp-s or beta 245 | 0x7E, 0x02, 0x02, 0x06, 0x06, 246 | 0x02, 0x7E, 0x02, 0x7E, 0x02, 247 | 0x63, 0x55, 0x49, 0x41, 0x63, 248 | 0x38, 0x44, 0x44, 0x3C, 0x04, 249 | 0x40, 0x7E, 0x20, 0x1E, 0x20, 250 | 0x06, 0x02, 0x7E, 0x02, 0x02, 251 | 0x99, 0xA5, 0xE7, 0xA5, 0x99, 252 | 0x1C, 0x2A, 0x49, 0x2A, 0x1C, 253 | 0x4C, 0x72, 0x01, 0x72, 0x4C, 254 | 0x30, 0x4A, 0x4D, 0x4D, 0x30, 255 | 0x30, 0x48, 0x78, 0x48, 0x30, 256 | 0xBC, 0x62, 0x5A, 0x46, 0x3D, 257 | 0x3E, 0x49, 0x49, 0x49, 0x00, 258 | 0x7E, 0x01, 0x01, 0x01, 0x7E, 259 | 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 260 | 0x44, 0x44, 0x5F, 0x44, 0x44, 261 | 0x40, 0x51, 0x4A, 0x44, 0x40, 262 | 0x40, 0x44, 0x4A, 0x51, 0x40, 263 | 0x00, 0x00, 0xFF, 0x01, 0x03, 264 | 0xE0, 0x80, 0xFF, 0x00, 0x00, 265 | 0x08, 0x08, 0x6B, 0x6B, 0x08, 266 | 0x36, 0x12, 0x36, 0x24, 0x36, 267 | 0x06, 0x0F, 0x09, 0x0F, 0x06, 268 | 0x00, 0x00, 0x18, 0x18, 0x00, 269 | 0x00, 0x00, 0x10, 0x10, 0x00, 270 | 0x30, 0x40, 0xFF, 0x01, 0x01, 271 | 0x00, 0x1F, 0x01, 0x01, 0x1E, 272 | 0x00, 0x19, 0x1D, 0x17, 0x12, 273 | 0x00, 0x3C, 0x3C, 0x3C, 0x3C, 274 | 0x00, 0x00, 0x00, 0x00, 0x00 // #255 NBSP 275 | }; 276 | #endif // FONT5X7_H 277 | -------------------------------------------------------------------------------- /cordova/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | Blume 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 279 | 280 | 281 | 282 | 283 |
284 |
285 |

Blume

286 | 287 | 290 | 292 | 293 | 424 |
425 | 431 |
432 | 433 |
434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | -------------------------------------------------------------------------------- /cordova/www/libs/spectrum/spectrum.css: -------------------------------------------------------------------------------- 1 | /*** 2 | Spectrum Colorpicker v1.8.0 3 | https://github.com/bgrins/spectrum 4 | Author: Brian Grinstead 5 | License: MIT 6 | ***/ 7 | 8 | .sp-container { 9 | position:absolute; 10 | top:0; 11 | left:0; 12 | display:inline-block; 13 | *display: inline; 14 | *zoom: 1; 15 | /* https://github.com/bgrins/spectrum/issues/40 */ 16 | z-index: 9999994; 17 | overflow: hidden; 18 | } 19 | .sp-container.sp-flat { 20 | position: relative; 21 | } 22 | 23 | /* Fix for * { box-sizing: border-box; } */ 24 | .sp-container, 25 | .sp-container * { 26 | -webkit-box-sizing: content-box; 27 | -moz-box-sizing: content-box; 28 | box-sizing: content-box; 29 | } 30 | 31 | /* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */ 32 | .sp-top { 33 | position:relative; 34 | width: 100%; 35 | display:inline-block; 36 | } 37 | .sp-top-inner { 38 | position:absolute; 39 | top:0; 40 | left:0; 41 | bottom:0; 42 | right:0; 43 | } 44 | .sp-color { 45 | position: absolute; 46 | top:0; 47 | left:0; 48 | bottom:0; 49 | right:20%; 50 | } 51 | .sp-hue { 52 | position: absolute; 53 | top:0; 54 | right:0; 55 | bottom:0; 56 | left:84%; 57 | height: 100%; 58 | } 59 | 60 | .sp-clear-enabled .sp-hue { 61 | top:33px; 62 | height: 77.5%; 63 | } 64 | 65 | .sp-fill { 66 | padding-top: 80%; 67 | } 68 | .sp-sat, .sp-val { 69 | position: absolute; 70 | top:0; 71 | left:0; 72 | right:0; 73 | bottom:0; 74 | } 75 | 76 | .sp-alpha-enabled .sp-top { 77 | margin-bottom: 18px; 78 | } 79 | .sp-alpha-enabled .sp-alpha { 80 | display: block; 81 | } 82 | .sp-alpha-handle { 83 | position:absolute; 84 | top:-4px; 85 | bottom: -4px; 86 | width: 6px; 87 | left: 50%; 88 | cursor: pointer; 89 | border: 1px solid black; 90 | background: white; 91 | opacity: .8; 92 | } 93 | .sp-alpha { 94 | display: none; 95 | position: absolute; 96 | bottom: -14px; 97 | right: 0; 98 | left: 0; 99 | height: 8px; 100 | } 101 | .sp-alpha-inner { 102 | border: solid 1px #333; 103 | } 104 | 105 | .sp-clear { 106 | display: none; 107 | } 108 | 109 | .sp-clear.sp-clear-display { 110 | background-position: center; 111 | } 112 | 113 | .sp-clear-enabled .sp-clear { 114 | display: block; 115 | position:absolute; 116 | top:0px; 117 | right:0; 118 | bottom:0; 119 | left:84%; 120 | height: 28px; 121 | } 122 | 123 | /* Don't allow text selection */ 124 | .sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button { 125 | -webkit-user-select:none; 126 | -moz-user-select: -moz-none; 127 | -o-user-select:none; 128 | user-select: none; 129 | } 130 | 131 | .sp-container.sp-input-disabled .sp-input-container { 132 | display: none; 133 | } 134 | .sp-container.sp-buttons-disabled .sp-button-container { 135 | display: none; 136 | } 137 | .sp-container.sp-palette-buttons-disabled .sp-palette-button-container { 138 | display: none; 139 | } 140 | .sp-palette-only .sp-picker-container { 141 | display: none; 142 | } 143 | .sp-palette-disabled .sp-palette-container { 144 | display: none; 145 | } 146 | 147 | .sp-initial-disabled .sp-initial { 148 | display: none; 149 | } 150 | 151 | 152 | /* Gradients for hue, saturation and value instead of images. Not pretty... but it works */ 153 | .sp-sat { 154 | background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0))); 155 | background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0)); 156 | background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); 157 | background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); 158 | background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0)); 159 | background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0)); 160 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)"; 161 | filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81'); 162 | } 163 | .sp-val { 164 | background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0))); 165 | background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0)); 166 | background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); 167 | background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); 168 | background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0)); 169 | background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0)); 170 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)"; 171 | filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000'); 172 | } 173 | 174 | .sp-hue { 175 | background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 176 | background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 177 | background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 178 | background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000)); 179 | background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 180 | background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%); 181 | } 182 | 183 | /* IE filters do not support multiple color stops. 184 | Generate 6 divs, line them up, and do two color gradients for each. 185 | Yes, really. 186 | */ 187 | .sp-1 { 188 | height:17%; 189 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00'); 190 | } 191 | .sp-2 { 192 | height:16%; 193 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00'); 194 | } 195 | .sp-3 { 196 | height:17%; 197 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff'); 198 | } 199 | .sp-4 { 200 | height:17%; 201 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff'); 202 | } 203 | .sp-5 { 204 | height:16%; 205 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff'); 206 | } 207 | .sp-6 { 208 | height:17%; 209 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000'); 210 | } 211 | 212 | .sp-hidden { 213 | display: none !important; 214 | } 215 | 216 | /* Clearfix hack */ 217 | .sp-cf:before, .sp-cf:after { content: ""; display: table; } 218 | .sp-cf:after { clear: both; } 219 | .sp-cf { *zoom: 1; } 220 | 221 | .sp-dragger { 222 | border-radius: 5px; 223 | height: 5px; 224 | width: 5px; 225 | border: 1px solid #fff; 226 | background: #000; 227 | cursor: pointer; 228 | position:absolute; 229 | top:0; 230 | left: 0; 231 | } 232 | .sp-slider { 233 | position: absolute; 234 | top:0; 235 | cursor:pointer; 236 | height: 3px; 237 | left: -1px; 238 | right: -1px; 239 | border: 1px solid #000; 240 | background: white; 241 | opacity: .8; 242 | } 243 | 244 | /* 245 | Theme authors: 246 | Here are the basic themeable display options (colors, fonts, global widths). 247 | See http://bgrins.github.io/spectrum/themes/ for instructions. 248 | */ 249 | 250 | .sp-container { 251 | border-radius: 0; 252 | background-color: #ECECEC; 253 | border: solid 1px #f0c49B; 254 | padding: 0; 255 | } 256 | .sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear { 257 | font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; 258 | -webkit-box-sizing: border-box; 259 | -moz-box-sizing: border-box; 260 | -ms-box-sizing: border-box; 261 | box-sizing: border-box; 262 | } 263 | .sp-top { 264 | margin-bottom: 3px; 265 | } 266 | .sp-color, .sp-hue, .sp-clear { 267 | border: solid 1px #666; 268 | } 269 | 270 | /* Input */ 271 | .sp-input-container { 272 | float:right; 273 | width: 100px; 274 | margin-bottom: 4px; 275 | } 276 | .sp-initial-disabled .sp-input-container { 277 | width: 100%; 278 | } 279 | .sp-input { 280 | font-size: 12px !important; 281 | border: 1px inset; 282 | padding: 4px 5px; 283 | margin: 0; 284 | width: 100%; 285 | background:transparent; 286 | border-radius: 3px; 287 | color: #222; 288 | } 289 | .sp-input:focus { 290 | border: 1px solid orange; 291 | } 292 | .sp-input.sp-validation-error { 293 | border: 1px solid red; 294 | background: #fdd; 295 | } 296 | .sp-picker-container , .sp-palette-container { 297 | float:left; 298 | position: relative; 299 | padding: 10px; 300 | padding-bottom: 300px; 301 | margin-bottom: -290px; 302 | } 303 | .sp-picker-container { 304 | width: 172px; 305 | border-left: solid 1px #fff; 306 | } 307 | 308 | /* Palettes */ 309 | .sp-palette-container { 310 | border-right: solid 1px #ccc; 311 | } 312 | 313 | .sp-palette-only .sp-palette-container { 314 | border: 0; 315 | } 316 | 317 | .sp-palette .sp-thumb-el { 318 | display: block; 319 | position:relative; 320 | float:left; 321 | width: 24px; 322 | height: 15px; 323 | margin: 3px; 324 | cursor: pointer; 325 | border:solid 2px transparent; 326 | } 327 | .sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active { 328 | border-color: orange; 329 | } 330 | .sp-thumb-el { 331 | position:relative; 332 | } 333 | 334 | /* Initial */ 335 | .sp-initial { 336 | float: left; 337 | border: solid 1px #333; 338 | } 339 | .sp-initial span { 340 | width: 30px; 341 | height: 25px; 342 | border:none; 343 | display:block; 344 | float:left; 345 | margin:0; 346 | } 347 | 348 | .sp-initial .sp-clear-display { 349 | background-position: center; 350 | } 351 | 352 | /* Buttons */ 353 | .sp-palette-button-container, 354 | .sp-button-container { 355 | float: right; 356 | } 357 | 358 | /* Replacer (the little preview div that shows up instead of the ) */ 359 | .sp-replacer { 360 | margin:0; 361 | overflow:hidden; 362 | cursor:pointer; 363 | padding: 4px; 364 | display:inline-block; 365 | *zoom: 1; 366 | *display: inline; 367 | border: solid 1px #91765d; 368 | background: #eee; 369 | color: #333; 370 | vertical-align: middle; 371 | } 372 | .sp-replacer:hover, .sp-replacer.sp-active { 373 | border-color: #F0C49B; 374 | color: #111; 375 | } 376 | .sp-replacer.sp-disabled { 377 | cursor:default; 378 | border-color: silver; 379 | color: silver; 380 | } 381 | .sp-dd { 382 | padding: 2px 0; 383 | height: 16px; 384 | line-height: 16px; 385 | float:left; 386 | font-size:10px; 387 | } 388 | .sp-preview { 389 | position:relative; 390 | width:25px; 391 | height: 20px; 392 | border: solid 1px #222; 393 | margin-right: 5px; 394 | float:left; 395 | z-index: 0; 396 | } 397 | 398 | .sp-palette { 399 | *width: 220px; 400 | max-width: 220px; 401 | } 402 | .sp-palette .sp-thumb-el { 403 | width:16px; 404 | height: 16px; 405 | margin:2px 1px; 406 | border: solid 1px #d0d0d0; 407 | } 408 | 409 | .sp-container { 410 | padding-bottom:0; 411 | } 412 | 413 | 414 | /* Buttons: http://hellohappy.org/css3-buttons/ */ 415 | .sp-container button { 416 | background-color: #eeeeee; 417 | background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc); 418 | background-image: -moz-linear-gradient(top, #eeeeee, #cccccc); 419 | background-image: -ms-linear-gradient(top, #eeeeee, #cccccc); 420 | background-image: -o-linear-gradient(top, #eeeeee, #cccccc); 421 | background-image: linear-gradient(to bottom, #eeeeee, #cccccc); 422 | border: 1px solid #ccc; 423 | border-bottom: 1px solid #bbb; 424 | border-radius: 3px; 425 | color: #333; 426 | font-size: 14px; 427 | line-height: 1; 428 | padding: 5px 4px; 429 | text-align: center; 430 | text-shadow: 0 1px 0 #eee; 431 | vertical-align: middle; 432 | } 433 | .sp-container button:hover { 434 | background-color: #dddddd; 435 | background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb); 436 | background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb); 437 | background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb); 438 | background-image: -o-linear-gradient(top, #dddddd, #bbbbbb); 439 | background-image: linear-gradient(to bottom, #dddddd, #bbbbbb); 440 | border: 1px solid #bbb; 441 | border-bottom: 1px solid #999; 442 | cursor: pointer; 443 | text-shadow: 0 1px 0 #ddd; 444 | } 445 | .sp-container button:active { 446 | border: 1px solid #aaa; 447 | border-bottom: 1px solid #888; 448 | -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 449 | -moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 450 | -ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 451 | -o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 452 | box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; 453 | } 454 | .sp-cancel { 455 | font-size: 11px; 456 | color: #d93f3f !important; 457 | margin:0; 458 | padding:2px; 459 | margin-right: 5px; 460 | vertical-align: middle; 461 | text-decoration:none; 462 | 463 | } 464 | .sp-cancel:hover { 465 | color: #d93f3f !important; 466 | text-decoration: underline; 467 | } 468 | 469 | 470 | .sp-palette span:hover, .sp-palette span.sp-thumb-active { 471 | border-color: #000; 472 | } 473 | 474 | .sp-preview, .sp-alpha, .sp-thumb-el { 475 | position:relative; 476 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==); 477 | } 478 | .sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner { 479 | display:block; 480 | position:absolute; 481 | top:0;left:0;bottom:0;right:0; 482 | } 483 | 484 | .sp-palette .sp-thumb-inner { 485 | background-position: 50% 50%; 486 | background-repeat: no-repeat; 487 | } 488 | 489 | .sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner { 490 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=); 491 | } 492 | 493 | .sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner { 494 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=); 495 | } 496 | 497 | .sp-clear-display { 498 | background-repeat:no-repeat; 499 | background-position: center; 500 | background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==); 501 | } 502 | 503 | /* Mobile devices, make hue slider bigger so it is easier to slide */ 504 | @media (max-device-width: 480px) { 505 | .sp-color { right: 40%; } 506 | .sp-hue { left: 63%; width: 30%; } 507 | .sp-fill { padding-top: 60%; } 508 | .sp-container { width: 100%; } 509 | .sp-picker-container { width: 100%; } 510 | } 511 | -------------------------------------------------------------------------------- /cordova/www/ui/css/evothings-app.css: -------------------------------------------------------------------------------- 1 | /* Common styles for Evothings examples and client. */ 2 | 3 | @import '../fonts/ProximaNova.css'; 4 | 5 | html, body { 6 | height: 100%; 7 | margin: 0; 8 | padding: 0; 9 | 10 | font-family: 'Proxima Nova Regular', sans-serif; 11 | font-weight: normal; 12 | 13 | background: rgb(255,255,255); 14 | 15 | -webkit-touch-callout: none; 16 | -webkit-user-select: none; 17 | -khtml-user-select: none; 18 | -moz-user-select: none; 19 | -ms-user-select: none; 20 | user-select: none; 21 | -webkit-tap-highlight-color: rgba(0,0,0,0); 22 | } 23 | body { 24 | margin: 0 0.9em 0 0.9em; 25 | font-size: 150%; 26 | font-size: 7.5vw; 27 | } 28 | 29 | /* Add a top margin to the content corresponding to the height of the header. */ 30 | body::before, 31 | section::before { 32 | display: block; 33 | width: 100%; 34 | height: 1.1em; /* Should correspond to header's height. */ 35 | margin-bottom: 0.4em; 36 | 37 | content: '\0000a0'; /* Non-breaking space ( ) */ 38 | font-size: 180%; /* Same as the header's font size. */ 39 | line-height: 200%; /* Slighly more than the header's line-height. */ 40 | } 41 | 42 | /* Add a bottom margin. */ 43 | body::after, 44 | section::after { 45 | display:block; 46 | content: ''; 47 | height: 1em; 48 | } 49 | 50 | 51 | 52 | 53 | /* ---------------------------------------------------------- 54 | GENERAL 55 | ---------------------------------------------------------- */ 56 | 57 | .hidden { 58 | display: none; 59 | } 60 | 61 | 62 | 63 | 64 | /* ---------------------------------------------------------- 65 | HEADER 66 | ---------------------------------------------------------- */ 67 | 68 | /* The header's height is determined by the contained text's font size. */ 69 | header { 70 | box-sizing: border-box; /* Width & height includes padding & border. */ 71 | 72 | position: fixed; 73 | top: 0; 74 | left: 0; 75 | right: 0; 76 | 77 | width: 100%; 78 | height: 1.1em; 79 | 80 | /* Left and right margins should correspond to body margins. */ 81 | margin: 0 0 3.45% 0; 82 | /* Left padding should correspond to the back button's width. */ 83 | padding: 1% 2% 1% 2%; 84 | 85 | font-size: 180%; 86 | line-height: 100%; 87 | text-align: center; 88 | vertical-align: middle; 89 | 90 | background: #f3f3f3; 91 | 92 | z-index: 1000; 93 | } 94 | header button { 95 | display: block; 96 | position: absolute; 97 | top: 50%; 98 | max-height: 70%; 99 | 100 | margin: 0; 101 | 102 | font-size: 30%; 103 | 104 | -webkit-transform: translateY(-50%); 105 | -ms-transform: translateY(-50%); 106 | transform: translateY(-50%); 107 | } 108 | header button.back { 109 | position: absolute; 110 | left: 0; 111 | 112 | height: 100%; 113 | margin: 0; 114 | padding: 0 0.9em; 115 | 116 | /* Font size is percentage of the header's font size. */ 117 | font-size: 50%; 118 | color: #000; 119 | 120 | text-align: left; 121 | 122 | background: none; 123 | 124 | border-radius: 0; 125 | border: none; 126 | 127 | box-shadow: none; 128 | } 129 | header button.back img { 130 | height: 0.5em; 131 | } 132 | header img.logotype { 133 | position: absolute; 134 | top: 50%; 135 | left: 0; 136 | right: 0; 137 | 138 | height: 60%; 139 | 140 | margin: 0 auto; 141 | 142 | -webkit-transform: translateY(-50%); 143 | -ms-transform: translateY(-50%); 144 | transform: translateY(-50%); 145 | } 146 | 147 | 148 | 149 | 150 | /* ---------------------------------------------------------- 151 | MENU 152 | ---------------------------------------------------------- */ 153 | 154 | header button#menu-button { 155 | position: absolute; 156 | right: 0; 157 | 158 | width: 3em; 159 | height: 100%; 160 | max-height: 100%; 161 | margin: 0; 162 | padding: 0 0.9em; 163 | 164 | /* Font size is percentage of the header's font size. */ 165 | font-size: 50%; 166 | color: #000; 167 | 168 | text-align: right; 169 | 170 | background: none; 171 | 172 | border-radius: 0; 173 | border: none; 174 | 175 | box-shadow: none; 176 | } 177 | header button#menu-button img { 178 | height: 34%; 179 | } 180 | header button#menu-button:focus { 181 | pointer-events: none; 182 | outline: none; 183 | } 184 | header button#menu-button:focus::-moz-focus-inner { 185 | border: 0; 186 | } 187 | header button#menu-button:focus + menu { 188 | opacity: 1; 189 | visibility: visible; 190 | } 191 | header menu { 192 | display: block; 193 | position: absolute; 194 | top: 100%; 195 | right: 0; 196 | 197 | margin: 0; 198 | padding: 0; 199 | 200 | background: #f3f3f3; 201 | 202 | opacity: 0; 203 | visibility: hidden; 204 | transition: visibility 0.5s; 205 | } 206 | 207 | header menu > * { 208 | display: block; 209 | 210 | padding: 0 1em 0 1em; 211 | 212 | font-family: 'Proxima Nova Bold'; 213 | font-size: 35%; 214 | text-align: right; 215 | } 216 | header menu a { 217 | color: #000; 218 | text-decoration: none; 219 | } 220 | 221 | header h1 { 222 | width: 100%; 223 | margin: 0; 224 | padding: 0; 225 | 226 | font-size: inherit; 227 | line-height: inherit; 228 | font-weight: normal; 229 | text-align: center; 230 | vertical-align: middle; 231 | color: #eee; 232 | 233 | overflow: hidden; 234 | white-space: nowrap; 235 | text-overflow: ellipsis; 236 | } 237 | 238 | 239 | 240 | 241 | /* ---------------------------------------------------------- 242 | SECTIONS 243 | ---------------------------------------------------------- */ 244 | 245 | main { 246 | z-index: 0; 247 | } 248 | 249 | main footer { 250 | box-sizing: border-box; /* Width & height includes padding & border. */ 251 | 252 | position: fixed; 253 | bottom: 0; 254 | 255 | width: 100%; 256 | 257 | margin: 0 -5% 0 -5%; 258 | padding: 0 5% 0 5%; 259 | 260 | background-color: #fff; 261 | 262 | box-shadow: inset 0 5px 9px -9px #000; 263 | 264 | z-index: 2; 265 | } 266 | 267 | section { 268 | display: none; 269 | 270 | box-sizing: border-box; /* Width & height includes padding & border. */ 271 | 272 | position: absolute; 273 | 274 | top: 0; 275 | right: 0; 276 | bottom: 0; 277 | left: 0; 278 | 279 | width: 100%; 280 | 281 | padding: 0 0.9em 0 0.9em; 282 | 283 | background-color: #fff; 284 | 285 | z-index: 2; 286 | 287 | } 288 | 289 | section:target { 290 | display: block; 291 | } 292 | 293 | 294 | 295 | /* ---------------------------------------------------------- 296 | INPUT FIELDS 297 | ---------------------------------------------------------- */ 298 | 299 | input { 300 | margin: 0; 301 | padding: 0; 302 | 303 | border: none; 304 | 305 | font-family: inherit; 306 | font-size: 63%; 307 | } 308 | 309 | input[type="text"],input[type="url"],input[type="tel"],input[type="password"], 310 | input[type="email"],input[type="search"],input[type="number"], 311 | input[type="date"],input[type="month"],input[type="week"],input[type="time"] { 312 | width: 50%; 313 | 314 | padding: 7px 14px 7px 14px; 315 | 316 | font-family: inherit; 317 | font-size: 63%; 318 | color: #000; 319 | 320 | border-radius: 4px; 321 | border: none; 322 | 323 | background-color: #f3f3f3; /* Default color: "Arctic". */ 324 | } 325 | 326 | textarea { 327 | box-sizing: border-box; /* Width & height includes padding & border. */ 328 | width: 100%; 329 | 330 | padding: 7px 14px 7px 14px; 331 | 332 | border-radius: 4px; 333 | border: none; 334 | 335 | font-family: inherit; 336 | font-size: 63%; 337 | line-height: 1.5; 338 | 339 | background-color: #f3f3f3; /* Default color: "Arctic". */ 340 | } 341 | 342 | 343 | 344 | 345 | /* ---------------------------------------------------------- 346 | BUTTONS 347 | ---------------------------------------------------------- */ 348 | 349 | button.clear { 350 | margin-left: 1%; 351 | background-color: #e15a64; /* Default color: "Soft Red". */ 352 | } 353 | 354 | button, 355 | input.btn-group + label, 356 | .button { 357 | display: inline-block; 358 | box-sizing: border-box; /* Width & height includes padding & border. */ 359 | 360 | font-family: 'Proxima Nova Black'; 361 | font-size: inherit; 362 | line-height: 100%; 363 | text-align: center; 364 | text-transform: uppercase; 365 | 366 | color: #fff; 367 | 368 | margin: 0.5em 0 0.5em 0; 369 | padding: 9px 13px 8px 13px; 370 | 371 | border-radius: 4px; 372 | border: none; 373 | 374 | box-shadow: none; 375 | 376 | background-color: #b9b9b9; /* Default color: "Charcoal" */ 377 | } 378 | body > button, 379 | body > input.btn-group + label, 380 | main > button, 381 | main > input.btn-group + label { 382 | font-size: 63% !important; 383 | } 384 | button:active, button.pressed, 385 | input.btn-group:active + label, input.btn-group + label:active, 386 | input.btn-group:checked + label { 387 | box-shadow: inset 0 8px 9px -9px #000; 388 | background-color: #9d9d9d; /* Default color: "Charcoal" */ 389 | } 390 | button:focus, 391 | input.btn-group:focus { 392 | outline: none; 393 | } 394 | 395 | button.half, 396 | input.btn-group.half + label, 397 | input.btn-group + label.half { 398 | float: left; 399 | width: 50%; /* Fallback in case calc() is unsupported. */ 400 | width: calc(50% - 4px); 401 | margin-left: 2px; 402 | margin-right: 2px; 403 | } 404 | 405 | button.third, 406 | input.btn-group.third + label, 407 | input.btn-group + label.third { 408 | float: left; 409 | width: 33%; /* Fallback in case calc() is unsupported. */ 410 | width: calc(33% - 4px); 411 | margin-left: 2px; 412 | margin-right: 2px; 413 | } 414 | 415 | button.quarter, 416 | input.btn-group.quarter + label, 417 | input.btn-group + label.quarter { 418 | float: left; 419 | width: 25%; /* Fallback in case calc() is unsupported. */ 420 | width: calc(25% - 4px); 421 | margin-left: 2px; 422 | margin-right: 2px; 423 | } 424 | 425 | button.wide, 426 | input.btn-group.wide + label, 427 | input.btn-group + label.wide { 428 | width: 100%; 429 | } 430 | 431 | button.big, 432 | input.btn-group.big + label, 433 | input.btn-group + label.big { 434 | font-size: 150%; 435 | line-height: 3.0; 436 | } 437 | 438 | /* Input with class btn-group should be accompanied by a label acting as a 439 | button. */ 440 | input.btn-group { 441 | display: none; 442 | } 443 | 444 | 445 | 446 | 447 | /* ---------------------------------------------------------- 448 | VARIOUS ELEMENTS 449 | ---------------------------------------------------------- */ 450 | 451 | canvas { 452 | padding: 7px; 453 | 454 | border-radius: 4px; 455 | border: none; 456 | 457 | background-color: #f3f3f3; /* Default color: "Arctic" */ 458 | } 459 | 460 | figcaption { 461 | font-size: 63%; 462 | } 463 | 464 | h1 { 465 | padding: 0; 466 | margin: 1em 0 0 0; 467 | 468 | font-family: 'Proxima Nova Black'; 469 | font-size: 100%; 470 | line-height: 1.5; 471 | 472 | vertical-align: middle; 473 | } 474 | header + h1, body h1:first-child, main h1:first-child, article h1:first-child { 475 | margin-top: 0; 476 | } 477 | 478 | h2,h3,h4,h5,h6 { 479 | margin: 1em 0 0 0; 480 | padding: 0; 481 | 482 | font-family: 'Proxima Nova Bold'; 483 | font-size: 63%; 484 | 485 | line-height: 1.5; 486 | 487 | vertical-align: middle; 488 | } 489 | 490 | a { 491 | color: #52afb8; /* Default color: "Blue Hue" (dark variant) */ 492 | } 493 | 494 | p { 495 | margin: 0.4em 0 0.4em 0; 496 | padding: 0; 497 | 498 | font-size: 63%; 499 | line-height: 1.5; 500 | } 501 | 502 | table { 503 | table-layout: fixed; 504 | width: 100%; 505 | font-size: 63%; 506 | text-align: left; 507 | } 508 | thead { 509 | font-family: 'Proxima Nova Bold'; 510 | } 511 | 512 | strong { 513 | font-family: 'Proxima Nova Bold'; 514 | } 515 | 516 | code { 517 | font-size: inherit; 518 | } 519 | 520 | ol,ul { 521 | list-style-type: none; 522 | list-style-position: inside; 523 | counter-reset: item; 524 | 525 | margin: 0.5em 0 0 0; 526 | padding: 0; 527 | 528 | font-size: 63%; 529 | } 530 | ol li, ul li { 531 | padding: 0.5em 0 0.5em 0; 532 | 533 | font-size: inherit; 534 | line-height: 1.5; 535 | } 536 | ol li:before { 537 | content: counter(item) ". "; 538 | counter-increment: item; 539 | font-family: 'Proxima Nova Bold'; 540 | } 541 | ul.dynamic { 542 | list-style-type: none; 543 | padding: 0; 544 | } 545 | ul.dynamic li { 546 | padding: 4% 15% 4% 4%; 547 | 548 | border-radius: 4px; 549 | border: none; 550 | 551 | box-shadow: inset 0 -7px 12px -12px #000; 552 | 553 | background-color: #f3f3f3; 554 | } 555 | ul.dynamic li:last-child { 556 | box-shadow: none; 557 | } 558 | ul.dynamic li a { 559 | color: #000; 560 | } 561 | ul.dynamic.arrow li, 562 | ul.dynamic li.arrow { 563 | background-image: url('../images/arrow-right.svg'); 564 | background-position: 94% center; /* CSS 2 fallback */ 565 | background-position: right 5% center; /* requires CSS 3 */ 566 | background-size: 1em auto; 567 | background-repeat: no-repeat; 568 | } 569 | 570 | article { 571 | display: none; 572 | z-index: 2; 573 | 574 | font-size: 100%; 575 | } 576 | 577 | 578 | 579 | 580 | /* ---------------------------------------------------------- 581 | COLORS 582 | ---------------------------------------------------------- */ 583 | 584 | /* Color: "Soft Red" 585 | Class name: red 586 | ---------------- */ 587 | .color_softred { color: #e15a64; } 588 | .bg_red, input.red, button.red, ul.red li, label.red, 589 | input.red + label { 590 | background-color: #e15a64; 591 | } 592 | /* Downpressed button. */ 593 | button.red:active, button.red.pressed, 594 | /* Input element with adjacent label. */ 595 | input.btn-group:active + label.red, 596 | input.btn-group.red:active + label, 597 | input.btn-group:checked + label.red, 598 | input.btn-group.red:checked + label { 599 | background-color: #cb414b; 600 | } 601 | 602 | /* Color: "Bright Light" 603 | Class name: yellow 604 | -------------------- */ 605 | .color_brightlight { color: #ecd53b; } 606 | .bg_yellow, input.yellow, button.yellow, ul.yellow li, label.yellow, 607 | input.yellow + label { 608 | background-color: #ecd53b; 609 | } 610 | /* Downpressed button. */ 611 | button.yellow:active, button.yellow.pressed, 612 | /* Input element with adjacent label. */ 613 | input.btn-group:active + label.yellow, 614 | input.btn-group.yellow:active + label, 615 | input.btn-group:checked + label.yellow, 616 | input.btn-group.yellow:checked + label { 617 | background-color: #d9c022; 618 | } 619 | 620 | /* Color: "Wave Green" 621 | Class name: green 622 | ------------------ */ 623 | .color_wavegreen { color: #54dfb3; } 624 | .bg_green, input.green, button.green, ul.green li, label.green, 625 | input.green + label { 626 | background-color: #54dfb3; 627 | } 628 | /* Downpressed button. */ 629 | button.green:active, button.green.pressed, 630 | /* Input element with adjacent label. */ 631 | input.btn-group:active + label.green, 632 | input.btn-group.green:active + label, 633 | input.btn-group:checked + label.green, 634 | input.btn-group.green:checked + label { 635 | background-color: #3aca9c; 636 | } 637 | 638 | /* Color: "Blue Hue" 639 | Clas name: blue 640 | ------------------ */ 641 | .color_bluehue { color: #6bc6ce; } 642 | .bg_blue, input.blue, button.blue, ul.blue li, label.blue, 643 | input.blue + label { 644 | background-color: #6bc6ce; 645 | } 646 | /* Downpressed button. */ 647 | button.blue:active, button.blue.pressed, 648 | /* Input element with adjacent label. */ 649 | input.btn-group:active + label.blue, 650 | input.btn-group.blue:active + label, 651 | input.btn-group:checked + label.blue, 652 | input.btn-group.blue:checked + label { 653 | background-color: #52afb8; 654 | } 655 | 656 | /* Color: "Indigo" 657 | Class name: indigo 658 | ------------------ */ 659 | .color_indigo { color: #b48b9b; } 660 | .bg_indigo, input.indigo, button.indigo, ul.indigo li, label.indigo, 661 | input.indigo + label { 662 | background-color: #b48b9b; 663 | } 664 | /* Downpressed button. */ 665 | button.indigo:active, button.indigo.pressed, 666 | /* Input element with adjacent label. */ 667 | input.btn-group:active + label.indigo, 668 | input.btn-group.indigo:active + label, 669 | input.btn-group:checked + label.indigo, 670 | input.btn-group.indigo:checked + label { 671 | background-color: #9c7283; 672 | } 673 | 674 | /* Color: "Arctic" 675 | Class name: arctic 676 | ------------------ */ 677 | .color_arctic { color: #f3f3f3; } 678 | .bg_arctic, input.arctic, button.arctic, ul.arctic li, 679 | input.arctic + label { 680 | background-color: #f3f3f3; 681 | } 682 | /* Downpressed button. */ 683 | button.arctic:active, button.arctic.pressed, 684 | /* Input element with adjacent label. */ 685 | input.btn-group:active + label.arctic, 686 | input.btn-group.arctic:active + label, 687 | input.btn-group:checked + label.arctic, 688 | input.btn-group.arctic:checked + label { 689 | background-color: #656565; 690 | } 691 | 692 | /* Color: "Aluminium" 693 | Class name: aluminium 694 | ------------------ */ 695 | .color_aluminium { color: #e2e2e2 } 696 | .bg_aluminium, input.aluminium, button.aluminium, ul.aluminium li, 697 | input.aluminium + label { 698 | background-color: #e2e2e2; 699 | } 700 | /* Downpressed button. */ 701 | button.aluminium:active, button.aluminium.pressed, 702 | /* Input element with adjacent label. */ 703 | input.btn-group:active + label.aluminium, 704 | input.btn-group.aluminium:active + label, 705 | input.btn-group:checked + label.aluminium, 706 | input.btn-group.aluminium:checked + label { 707 | background-color: #c0c0c0; 708 | } 709 | 710 | /* Color: "Charcoal" 711 | Class name: charcoal 712 | ------------------ */ 713 | .color_charcoal { color: #b9b9b9; } 714 | .bg_charcoal, input.charcoal, button.charcoal, ul.charcoal li, 715 | input.charcoal + label { 716 | background-color: #b9b9b9; 717 | } 718 | /* Downpressed button. */ 719 | button.charcoal:active, button.charcoal.pressed, 720 | /* Input element with adjacent label. */ 721 | input.btn-group:active + label.charcoal, 722 | input.btn-group.charcoal:active + label, 723 | input.btn-group:checked + label.charcoal, 724 | input.btn-group.charcoal:checked + label { 725 | background-color: #9d9d9d; 726 | } 727 | 728 | /* Color: "Stone" 729 | Class name: stone 730 | ------------------ */ 731 | .color_stone { color: #777777 } 732 | .bg_stone, input.stone, button.stone, ul.stone li, 733 | input.stone + label { 734 | background-color: #777777; 735 | } 736 | /* Downpressed button. */ 737 | button.stone:active, button.stone.pressed, 738 | /* Input element with adjacent label. */ 739 | input.btn-group:active + label.stone, 740 | input.btn-group.stone:active + label, 741 | input.btn-group:checked + label.stone, 742 | input.btn-group.stone:checked + label { 743 | background-color: #656565; 744 | } 745 | 746 | /* Color: "Jet Black" 747 | Class name: jetblack 748 | ------------------ */ 749 | .color_black { color: #070707 } 750 | .bg_black, input.black, button.black, ul.black li, 751 | input.black + label { 752 | background-color: #070707; 753 | } 754 | /* Downpressed button. */ 755 | button.black:active, button.black.pressed, 756 | /* Input element with adjacent label. */ 757 | input.btn-group:active + label.black, 758 | input.btn-group.black:active + label, 759 | input.btn-group:checked + label.black, 760 | input.btn-group.black:checked + label { 761 | background-color: #000000; 762 | } 763 | 764 | @media screen and (orientation: landscape) and (-webkit-min-device-pixel-ratio : 2), 765 | screen and (orientation: landscape) and (min--moz-device-pixel-ratio: 2), 766 | screen and (orientation: landscape) and (min-device-pixel-ratio : 2) { 767 | body { font-size: 4.5vw; } 768 | } 769 | -------------------------------------------------------------------------------- /cordova/www/app.js: -------------------------------------------------------------------------------- 1 | // Route all console logs to Evothings studio log 2 | if (window.hyper && window.hyper.log) { console.log = hyper.log; }; 3 | 4 | if (window.cordova) { 5 | // We're in the Cordova app; initialize when the device is ready. 6 | // Do CodePush sync when the device is ready and every time we're 7 | // foregrounded. 8 | document.addEventListener('deviceready', function() { 9 | window.codePush.sync(); 10 | window.codePush.getCurrentPackage(function(currentPackage) { 11 | if (currentPackage) { 12 | $('#codePushVersion').html(currentPackage.label); 13 | } 14 | }); 15 | evothings.scriptsLoaded(app.initialize); 16 | }); 17 | document.addEventListener("resume", function() { 18 | window.codePush.sync(); 19 | }); 20 | } else { 21 | // No Cordova. Wait until jQuery's available (actually, we just need to wait 22 | // until app.initialize() is defined below, but this gives us a reasonable 23 | // delay), then initialize. 24 | $(function() { 25 | app.initialize(); 26 | }); 27 | } 28 | 29 | // Define globally for easy console debugging 30 | app = {}; 31 | app.devices = {}; 32 | app.DFRBLU_SERVICE_UUID = '0000dfb0-0000-1000-8000-00805f9b34fb'; 33 | app.DFRBLU_CHAR_RXTX_UUID = '0000dfb1-0000-1000-8000-00805f9b34fb'; 34 | app.PAINT_DIR = 'img/paint/' 35 | 36 | // Number of devices that still need to be sent the current command. 37 | var pendingSends = 0; 38 | // If truthy, this will be executed and unset when pendingSends returns to 0. 39 | var doPostSend = null; 40 | 41 | var speedSlider = document.getElementById('movement_speed'); 42 | var sizeSlider = document.getElementById('movement_size'); 43 | var imgBrightSlider = document.getElementById('img_bright_slider'); 44 | var imgWidthSlider = document.getElementById('img_width_slider'); 45 | var blobsBrightSlider = document.getElementById('blobs_bright_slider'); 46 | var blobsSpeedSlider = document.getElementById('blobs_speed_slider'); 47 | var blobsRedSizeSlider = document.getElementById('blobs_red_size_slider'); 48 | var blobsGreenSizeSlider = document.getElementById('blobs_green_size_slider'); 49 | var blobsBlueSizeSlider = document.getElementById('blobs_blue_size_slider'); 50 | var demoBrightSlider = document.getElementById('demo_bright_slider'); 51 | var demoTimeSlider = document.getElementById('demo_time_slider'); 52 | 53 | function sendColor() { 54 | if (!app.initialized) { 55 | return; 56 | } 57 | if (pendingSends) { 58 | doPostSend = function() { 59 | sendColor(); 60 | }; 61 | return; 62 | } 63 | var color = $("#picker").spectrum("get"); 64 | var hsv = color.toHsv(); 65 | hsv.h = Math.round(hsv.h / 360.0 * 255.0); 66 | hsv.s = Math.round(hsv.s * 255.0); 67 | hsv.v = Math.round(hsv.v * 255.0); 68 | var rainbows = $('#rainbows').is(':checked'); 69 | var speed = parseInt(speedSlider.noUiSlider.get()); 70 | var size = parseInt(sizeSlider.noUiSlider.get()); 71 | if ($('#movement').is(':checked')) { 72 | app.sendCommand(null, hsv.v, 73 | $('#movement_random').is(':checked') ? 'Z' : 'M', 74 | hsv.h, hsv.s, 75 | speed + ( 76 | rainbows ? 101 : 0), 77 | size + ( 78 | $('#movement_vertical').is(':checked') ? 0 : 101)); 79 | } else { 80 | app.sendCommand(null, hsv.v, rainbows ? 'R' : 'C', hsv.h, hsv.s); 81 | } 82 | }; 83 | 84 | function imageDataPixelToByte(imageData, x, y) { 85 | // Takes an ImageData object and x,y and returns 86 | // the pixel at that coordiante in RRRGGGBB format. 87 | var idx = (x + y * imageData.width) * 4; 88 | return ( 89 | (Math.floor(imageData.data[idx + 0] / 32) << 5) + 90 | (Math.floor(imageData.data[idx + 1] / 32) << 2) + 91 | (Math.floor(imageData.data[idx + 2] / 64) << 0)); 92 | } 93 | 94 | handleGif = function(file) { 95 | var brightness = parseInt(imgBrightSlider.noUiSlider.get()); 96 | var frameTime = parseInt(imgWidthSlider.noUiSlider.get()); 97 | var canvas = document.getElementById('canvas'); 98 | gifler(file).get().then(function(animator) { 99 | window.animator = animator; // XXX TEMP 100 | animator.onDrawFrame = function(ctx, frame) { 101 | for (var i in app.devices) { 102 | var dev = app.devices[i]; 103 | if (!dev.isConnected()) { 104 | // Don't do anything with disconnected devices. 105 | continue; 106 | } 107 | if (!dev.maxFrames) { 108 | console.log("Skipping device with maxFrames=0", dev); 109 | continue; 110 | } 111 | if (!dev.imgArr) { 112 | dev.imgArr = new Uint8Array( 113 | dev.height * dev.width * 3 * animator._frames.length); 114 | } else if (animator._loops > 0) { 115 | app.sendCommand( 116 | dev, brightness, 'P', 117 | animator._frames.length, frameTime, 118 | 24); 119 | app.sendData(dev, dev.imgArr); 120 | dev.imgArr = null; 121 | continue; 122 | } 123 | 124 | // Draw the image on the canvas to scale it. 125 | ctx.fillStyle = 'black'; 126 | ctx.fillRect(0, 0, canvas.width, canvas.height); 127 | ctx.drawImage(frame.buffer, 0, 0, dev.width, dev.height); 128 | // Grab scaled pixel data and put it in our data array. 129 | var data = ctx.getImageData(0, 0, dev.width, dev.height); 130 | for (var x = 0; x < dev.width; x++) { 131 | for (var y = 0; y < dev.height; y++) { 132 | // Loop through R, G, B 133 | for (var i = 0; i < 3; i++) { 134 | dev.imgArr[(x + dev.width * y + 135 | animator._frameIndex * dev.width * dev.height) * 3 + i] = 136 | data.data[(x + y * dev.width) * 4 + i]; 137 | } 138 | } 139 | } 140 | } 141 | if (animator._loops > 0) { 142 | animator.stop(); 143 | } 144 | }; 145 | //var canvas = gifler.Gif.getCanvasElement('#canvas'); 146 | animator.animateInCanvas(canvas, false); 147 | }, function() { 148 | console.log("animation error??", arguments); 149 | }); 150 | } 151 | 152 | sendImage = function(file) { 153 | if (!app.initialized) { 154 | return; 155 | } 156 | if (pendingSends) { 157 | // Don't send a new image, but do sent the latest brightness/width 158 | doPostSend = function() { 159 | sendImage(); 160 | }; 161 | return; 162 | } 163 | var brightness = parseInt(imgBrightSlider.noUiSlider.get()); 164 | var frameTime = parseInt(imgWidthSlider.noUiSlider.get()); 165 | if (!file) { 166 | // No file specified: just tell poi new brightness/frame-time 167 | app.sendCommand(null, brightness, 'P', 0, frameTime); 168 | return; 169 | } 170 | 171 | /* 172 | * EXPERIMENTAL: Animated GIF support. 173 | * Kinda works, but doesn't check for maxFrames, 174 | * only supports 24-bit color, 175 | * is very slow (needs a full loop of the gif before sending), 176 | * and some frames don't appear as expected. 177 | if (file.endsWith('.gif')) { 178 | handleGif(file); 179 | return; 180 | } 181 | */ 182 | 183 | var img = new Image(); 184 | img.src = file; 185 | var canvas = document.getElementById('canvas'); 186 | var ctx = canvas.getContext('2d'); 187 | img.onload = function() { 188 | // TODO: Avoid recomputing all this stuff for 2 (poi) devices 189 | // of the same dimensions. 190 | for (var i in app.devices) { 191 | var dev = app.devices[i]; 192 | if (!dev.isConnected()) { 193 | // Don't do anything with disconnected devices. 194 | continue; 195 | } 196 | if (!dev.maxFrames) { 197 | console.log("Skipping device with maxFrames=0", dev); 198 | continue; 199 | } 200 | 201 | // This is a persistence-of-vision display if its width is 1. 202 | // Otherwise it's two-dimensional. 203 | var pov = dev.width == 1; 204 | // Frames of image data we will send. 205 | var numFrames = pov ? 206 | // POV display: scale the image proportinally if we can; 207 | // otherwise make it maxFrames wide. 208 | Math.min( 209 | Math.round(img.naturalWidth * dev.height / img.naturalHeight), 210 | dev.maxFrames) : 211 | // 2D display: only sending one frame. 212 | 1; 213 | // Image will be scaled to numFrames for POV imagery 214 | // and scaled to the device's width for 2D imagery 215 | var width = pov ? numFrames : dev.width; 216 | // If maxFrames is at least 3x numFrames, we can send in 24-bit color 217 | // instead of 8-bit color. 218 | var trueColor = dev.maxFrames >= numFrames * 3; 219 | 220 | // Draw the image on the canvas to scale it. 221 | ctx.fillStyle = 'black'; 222 | ctx.fillRect(0, 0, canvas.width, canvas.height); 223 | ctx.drawImage(img, 0, 0, width, dev.height); 224 | // TODO: Needed? 225 | img.style.display = 'none'; 226 | // Multiply array size by 3 for 24-bit color. 227 | var arr = new Uint8Array(dev.height * width * (trueColor ? 3 : 1)); 228 | // Grab scaled pixel data and put it in our data array. 229 | var data = ctx.getImageData(0, 0, width, dev.height); 230 | for (var x = 0; x < width; x++) { 231 | for (var y = 0; y < dev.height; y++) { 232 | var outputIdx = pov ? 233 | // POV images are sent by column, 234 | x * dev.height + y : 235 | // while 2D images are standardized to be sent as progressive 236 | // rows of data. 237 | x + width * y; 238 | if (trueColor) { 239 | // Loop through R, G, B 240 | for (var i = 0; i < 3; i++) { 241 | // Image data is in RGBA format 242 | arr[outputIdx * 3 + i] = data.data[(x + y * width) * 4 + i]; 243 | } 244 | } else { 245 | arr[outputIdx] = imageDataPixelToByte(data, x, y); 246 | } 247 | } 248 | } 249 | app.sendCommand( 250 | dev, brightness, 'P', 251 | numFrames, frameTime, 252 | trueColor ? 24 : 8); 253 | app.sendData(dev, arr); 254 | } 255 | }; 256 | }; 257 | 258 | sendBlobs = function() { 259 | if (!app.initialized) { 260 | return; 261 | } 262 | if (pendingSends) { 263 | doPostSend = function() { 264 | sendBlobs(); 265 | }; 266 | return; 267 | } 268 | var bright = parseInt(blobsBrightSlider.noUiSlider.get()); 269 | var speed = parseInt(blobsSpeedSlider.noUiSlider.get()); 270 | var rs = parseInt(blobsRedSizeSlider.noUiSlider.get()); 271 | var gs = parseInt(blobsGreenSizeSlider.noUiSlider.get()); 272 | var bs = parseInt(blobsBlueSizeSlider.noUiSlider.get()); 273 | app.sendCommand(null, bright, 'B', speed, rs, gs, bs); 274 | }; 275 | 276 | sendText = function() { 277 | if (!app.initialized) { 278 | return; 279 | } 280 | if (pendingSends) { 281 | doPostSend = function() { 282 | sendText(); 283 | }; 284 | return; 285 | } 286 | var color = $("#textColorPicker").spectrum("get"); 287 | var hsv = color.toHsv(); 288 | hsv.h = Math.round(hsv.h / 360.0 * 255.0); 289 | hsv.s = Math.round(hsv.s * 255.0); 290 | hsv.v = Math.round(hsv.v * 255.0); 291 | app.sendCommand(null, hsv.v, 'T', hsv.h, hsv.s); 292 | var txt = new TextEncoder("ascii").encode($('#textInput').val() + '\0'); 293 | app.sendData(null, txt); 294 | } 295 | 296 | sendDemo = function() { 297 | if (!app.initialized) { 298 | return; 299 | } 300 | if (pendingSends) { 301 | doPostSend = function() { 302 | sendDemo(); 303 | }; 304 | return; 305 | } 306 | var bright = parseInt(demoBrightSlider.noUiSlider.get()); 307 | var time = parseInt(demoTimeSlider.noUiSlider.get()); 308 | $('#demoTimeSeconds').html(time == 256 ? 'infinite': time); 309 | var mode = $('input[type=radio][name=demo-mode]:checked').val(); 310 | app.sendCommand(null, bright, 'd', time == 256 ? 0 : time, mode == 'added' ? 0 : 1); 311 | } 312 | saveDemoCurrent = function() { 313 | app.sendAsk(null, 'ds'); 314 | } 315 | moveDemoNext = function() { 316 | app.sendAsk(null, 'dn'); 317 | } 318 | clearDemoSaved = function() { 319 | app.sendAsk(null, 'dc'); 320 | } 321 | 322 | app.initialize = function() { 323 | 324 | $('#accordion .collapse').on('show.bs.collapse', function() { 325 | if (this.id === 'collapseColor') { 326 | // Switch to color mode: send the latest 327 | sendColor(); 328 | } else if (this.id === 'collapseImage') { 329 | // Switch to image mode: we won't send any image until the user selects 330 | // one, so revert the selection to None. 331 | $('#img_select_none').click(); 332 | } else if (this.id === 'collapseBlobs') { 333 | sendBlobs(); 334 | } else if (this.id === 'collapseText') { 335 | sendText(); 336 | } else if (this.id === 'collapseDemo') { 337 | sendDemo(); 338 | } 339 | }); 340 | 341 | $('#picker').spectrum({ 342 | color: "#FF0000", 343 | flat: true, 344 | showInput: false, 345 | showButtons: false, 346 | preferredFormat: "hex", 347 | move: sendColor 348 | }); 349 | 350 | noUiSlider.create(speedSlider, { 351 | start: 45, 352 | range: { min: 0, max: 100}, 353 | }); 354 | speedSlider.noUiSlider.on('update', function() { 355 | sendColor(); 356 | }); 357 | noUiSlider.create(sizeSlider, { 358 | start: 45, 359 | range: { min: 0, max: 100}, 360 | }); 361 | sizeSlider.noUiSlider.on('update', function() { 362 | sendColor(); 363 | }); 364 | 365 | $('#rainbows').change(function() { 366 | sendColor(); 367 | }); 368 | $('#movement').change(function() { 369 | $('.ctl-movement').toggle(this.checked); 370 | sendColor(); 371 | }).change(); 372 | $('.ctl-movement').change(function() { 373 | sendColor(); 374 | }); 375 | 376 | if (!!window.cordova) { 377 | window.resolveLocalFileSystemURL( 378 | cordova.file.applicationDirectory + 'www/' + app.PAINT_DIR, 379 | function (fileSystem) { 380 | var reader = fileSystem.createReader(); 381 | reader.readEntries( 382 | function (entries) { 383 | var images = []; 384 | for (var i in entries) { 385 | if (entries[i].isFile && !entries[i].isDirectory) { 386 | images.push(entries[i].name); 387 | } 388 | } 389 | initImages(images); 390 | }, 391 | function (err) { 392 | console.log(err); 393 | } 394 | ); 395 | }, function (err) { 396 | console.log(err); 397 | } 398 | ); 399 | } else { 400 | // Not cordova? Rely on a static list, maybe up to date. 401 | initImages(['mario.png', 'fire.jpg', 'pacman.png', 'space_invaders.png']); 402 | } 403 | noUiSlider.create(imgBrightSlider, { 404 | start: 255, 405 | range: { min: 0, max: 255}, 406 | }); 407 | noUiSlider.create(imgWidthSlider, { 408 | start: 0, 409 | range: { min: 0, max: 255}, 410 | }); 411 | imgBrightSlider.noUiSlider.on('update', function() { 412 | sendImage(); 413 | }); 414 | imgWidthSlider.noUiSlider.on('update', function() { 415 | sendImage(); 416 | }); 417 | 418 | noUiSlider.create(blobsBrightSlider, { 419 | start: 255, 420 | range: { min: 0, max: 255 }, 421 | }); 422 | blobsBrightSlider.noUiSlider.on('update', function() { 423 | sendBlobs(); 424 | }); 425 | [blobsSpeedSlider, blobsRedSizeSlider, blobsGreenSizeSlider, 426 | blobsBlueSizeSlider].forEach(function(slider) { 427 | noUiSlider.create(slider, { 428 | start: 64, 429 | range: { min: 0, max: 255 }, 430 | }); 431 | slider.noUiSlider.on('update', function() { 432 | sendBlobs(); 433 | }); 434 | }); 435 | 436 | $('#textColorPicker').spectrum({ 437 | color: "#FF0000", 438 | flat: true, 439 | showInput: false, 440 | showButtons: false, 441 | preferredFormat: "hex", 442 | move: sendText 443 | }); 444 | $('#textInput').on('input', sendText); 445 | 446 | $('#demoAddCurrent').click(function(e) { 447 | saveDemoCurrent(); 448 | e.stopPropagation(); 449 | }); 450 | noUiSlider.create(demoBrightSlider, { 451 | start: 255, 452 | range: { min: 0, max: 255 }, 453 | }); 454 | demoBrightSlider.noUiSlider.on('update', function() { 455 | sendDemo(); 456 | }); 457 | noUiSlider.create(demoTimeSlider, { 458 | start: 30, 459 | range: { min: 1, max: 256 }, 460 | }); 461 | demoTimeSlider.noUiSlider.on('update', function() { 462 | sendDemo(); 463 | }); 464 | $('input[type=radio][name=demo-mode]').change(function() { 465 | sendDemo(); 466 | }); 467 | $('#demoNext').click(function() { 468 | moveDemoNext(); 469 | }); 470 | $('#demoClear').click(function() { 471 | if (window.confirm( 472 | "WARNING! This will clear ALL saved demo settings on ALL connected devices." 473 | )) { 474 | clearDemoSaved(); 475 | } 476 | }); 477 | 478 | app.initialized = true; 479 | app.startScan(); 480 | listDevices(); 481 | 482 | setTimeout(function() { 483 | if (Object.keys(app.devices).length === 0) { 484 | // We have no devices after a while. Offer the fake device. 485 | var fake = { 486 | fake: true, 487 | connected: false, 488 | isConnected: function() { return this.connected; }, 489 | close: function() { this.connected = false; }, 490 | width: 1, 491 | height: 18, 492 | numLeds: 18, 493 | maxFrames: 64, 494 | maxText: 32, 495 | name: 'Fake Blume', 496 | address: 'abcdefghi' 497 | }; 498 | app.devices[fake.address] = fake; 499 | listDevices(); 500 | } 501 | }, 3000); 502 | }; 503 | 504 | function initImages(files) { 505 | var none = $('#img_select_none'); 506 | for (var i in files) { 507 | var file = files[i]; 508 | var clone = none.clone().attr('id', 'img_select_' + file) 509 | .attr('imageid', file).removeClass('img-select-selected'); 510 | clone.html(''); 511 | none.after(clone); 512 | } 513 | $('.img-select').click(function() { 514 | if ($(this).hasClass('img-select-selected')) { 515 | return; 516 | } 517 | if (pendingSends) { 518 | return; 519 | } 520 | $('.img-select').removeClass('img-select-selected'); 521 | $(this).addClass('img-select-selected'); 522 | var img = $(this).attr('imageid'); 523 | if (img) { 524 | if (img === 'url') { 525 | $('#img_url').focus(); 526 | var val = $('#img_url').val(); 527 | if (val) { 528 | sendImage(val); 529 | } 530 | } else { 531 | sendImage(app.PAINT_DIR + img); 532 | } 533 | } 534 | }); 535 | $('#img_url').change(function() { 536 | if ($(this).val()) { 537 | $('#img_select_url').removeClass('img-select-selected').click(); 538 | } 539 | }); 540 | } 541 | 542 | function listDevices() { 543 | $('#scanResultView').empty(); 544 | if (Object.keys(app.devices).length === 0) { 545 | $('#loadingIndicator').show(); 546 | $('#controlView').hide(); 547 | return; 548 | } else { 549 | $('#loadingIndicator').hide(); 550 | $('#scanResultView').show(); 551 | } 552 | var anyConnected = false; 553 | var anyImage = false; 554 | var anyText = false; 555 | // Set the max text length to the lowest supported maxText of any device. 556 | var minText = 999; 557 | for (var i in app.devices) { 558 | var dev = app.devices[i]; 559 | var htmlString = '
' + 573 | '

' + dev.name; 574 | if (dev.connectPending) { 575 | htmlString += ' Connecting...'; 576 | } 577 | htmlString += '

' + 578 | '

' + dev.address + '

' + 579 | '
'; 580 | $('#scanResultView').append($(htmlString)); 581 | } 582 | $('#controlView').toggle(anyConnected); 583 | $('#imageCard').toggle(anyImage); 584 | $('#textCard').toggle(anyText); 585 | $('#textInput').attr('maxlength', minText); 586 | }; 587 | 588 | app.startScan = function() { 589 | if (!window.cordova) { 590 | return; 591 | } 592 | console.log('Scanning started...'); 593 | 594 | var htmlString = 595 | '' + 597 | '

Scanning...

'; 598 | 599 | $('#scanResultView').append($(htmlString)); 600 | 601 | $('#scanResultView').show(); 602 | 603 | function onScanSuccess(dev) { 604 | if (dev.name && 605 | (dev.name.indexOf('Blume') !== -1 || 606 | dev.name.indexOf('Bluno') !== -1)) { 607 | app.devices[dev.address] = dev; 608 | 609 | console.log( 610 | 'Found: ' + dev.name + ', ' + 611 | dev.address + ', ' + dev.rssi); 612 | 613 | listDevices(); 614 | } 615 | } 616 | 617 | function onScanFailure(errorCode) { 618 | // Write debug information to console. 619 | console.log('Error ' + errorCode); 620 | } 621 | 622 | evothings.easyble.reportDeviceOnce(true); 623 | evothings.easyble.startScan(onScanSuccess, onScanFailure); 624 | 625 | }; 626 | 627 | app.toggleConnect = function(address) { 628 | var dev = app.devices[address]; 629 | if (dev && dev.isConnected()) { 630 | app.disconnectFrom(address); 631 | } else { 632 | app.connectTo(address); 633 | } 634 | } 635 | 636 | app.connectTo = function(address) { 637 | var dev = app.devices[address]; 638 | if (dev.fake) { 639 | dev.connectPending = true; 640 | listDevices(); 641 | setTimeout(function() { 642 | dev.connectPending = false; 643 | dev.connected = true; 644 | listDevices(); 645 | }, 1000); 646 | return; 647 | } 648 | 649 | function onConnectSuccess(dev) { 650 | function onServiceSuccess(dev) { 651 | 652 | console.log('Connected to ' + dev.name); 653 | 654 | dev.enableNotification( 655 | app.DFRBLU_SERVICE_UUID, 656 | app.DFRBLU_CHAR_RXTX_UUID, 657 | app.receivedData.bind(dev), 658 | function(errorCode) { 659 | console.log('BLE enableNotification error: ' + errorCode); 660 | }, 661 | { writeConfigDescriptor: false }); 662 | 663 | dev.connectPending = false; 664 | listDevices(); 665 | 666 | app.sendAsk(dev, 'D'); 667 | } 668 | 669 | function onServiceFailure(errorCode) { 670 | // Write debug information to console. 671 | console.log('Error reading services: ' + errorCode); 672 | dev.connectPending = false; 673 | listDevices(); 674 | } 675 | 676 | // Connect to the appropriate BLE service 677 | dev.readServices([app.DFRBLU_SERVICE_UUID], onServiceSuccess, onServiceFailure); 678 | } 679 | 680 | function onConnectFailure(errorCode) { 681 | // Write debug information to console 682 | console.log('Error ' + errorCode); 683 | if (errorCode === 133) { 684 | // On Android this just means we should retry! 685 | console.log("Attempting reconnect to " + dev.name); 686 | app.connectTo(address); 687 | } else { 688 | dev.connectPending = false; 689 | listDevices(); 690 | } 691 | } 692 | 693 | // Connect to our device 694 | console.log('Identifying service for communication'); 695 | dev.connect(onConnectSuccess, onConnectFailure); 696 | dev.connectPending = true; 697 | listDevices(); 698 | }; 699 | 700 | app.disconnectFrom = function(address) { 701 | var dev = app.devices[address]; 702 | dev.connectPending = false; 703 | dev.close(); 704 | listDevices(); 705 | }; 706 | 707 | app.sendCommand = function(devices, brightness, mode) { 708 | var data = Array.prototype.slice.call(arguments, 3); 709 | // Commands always have brightness 710 | var cmd = [brightness]; 711 | // Add mode if it was passed in 712 | if (mode) { 713 | // Convert one-character string to character code 714 | cmd.push(mode.charCodeAt(0)); 715 | } 716 | // !, length of the rest, the rest! 717 | app.sendData(devices, [0x21, cmd.length + data.length].concat(cmd, data)); 718 | } 719 | 720 | app.sendAsk = function(devices, question) { 721 | var data = [0x21, 0]; 722 | for (var i = 0; i < question.length; i++) { 723 | data.push(question.charCodeAt(i)); 724 | } 725 | app.sendData(devices, data); 726 | } 727 | 728 | function toHexString(byteArray) { 729 | var s = '0x'; 730 | for (var i in byteArray) { 731 | s += ('0' + (byteArray[i] & 0xFF).toString(16)).slice(-2); 732 | }; 733 | return s; 734 | } 735 | 736 | function sendEnd() { 737 | pendingSends--; 738 | if (!pendingSends && doPostSend) { 739 | doPostSend(); 740 | doPostSend = null; 741 | } 742 | } 743 | 744 | app.sendData = function(devices, data) { 745 | var tosend; 746 | if ((typeof data) == 'string') { 747 | tosend = new TextEncoder("ascii").encode(data); 748 | } else if (Array.isArray(data)) { 749 | tosend = new Uint8Array(data); 750 | } else { 751 | tosend = data; 752 | } 753 | if (tosend.length > 16) { 754 | //console.log("chunking data of length", tosend.length); 755 | for (var i = 0; i < tosend.length; i += 16) { 756 | app.sendData(devices, tosend.slice(i, i+16)); 757 | } 758 | return; 759 | } 760 | //console.log("sending data of length", tosend.length); 761 | if (!devices) { 762 | devices = app.devices; 763 | } else if (!Array.isArray(devices)) { 764 | devices = [devices]; 765 | } 766 | 767 | for (var i in devices) { 768 | var dev = devices[i]; 769 | if (!dev.isConnected() || dev.fake) { 770 | continue; 771 | } 772 | pendingSends++; 773 | dev.writeCharacteristic( 774 | app.DFRBLU_CHAR_RXTX_UUID, 775 | tosend, 776 | function() { 777 | //console.log("SENT: " + toHexString(tosend)); 778 | sendEnd(); 779 | }, 780 | function(err) { 781 | console.log("ERR:", err, pendingSends); 782 | sendEnd(); 783 | } 784 | ); 785 | } 786 | }; 787 | 788 | function buf2hex(buffer) { // buffer is an ArrayBuffer 789 | return Array.prototype.map.call(new Uint8Array(buffer), function(x) { return ('00' + x.toString(16)).slice(-2); }).join(''); 790 | } 791 | 792 | app.receivedData = function(data) { 793 | var data = new Uint8Array(data); 794 | console.log('RECV:' + toHexString(data)); 795 | 796 | if (data[0] === 0x58) { 797 | console.log("EEP!"); 798 | } else if (data[0] === 0x21) { 799 | console.log("question?!", this); 800 | if (data[1] === 0x44) { 801 | // Got dimensions result. "this" is the sending device. 802 | this.width = data[2]; 803 | this.height = data[3]; 804 | this.numLeds = data[4]; 805 | this.maxFrames = data[5]; 806 | this.maxText = data[6]; 807 | listDevices(); 808 | } 809 | } 810 | }; 811 | 812 | -------------------------------------------------------------------------------- /cordova/www/libs/evothings/ui/fastclick.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | 'use strict'; 3 | 4 | /** 5 | * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs. 6 | * 7 | * @codingstandard ftlabs-jsv2 8 | * @copyright The Financial Times Limited [All Rights Reserved] 9 | * @license MIT License (see LICENSE.txt) 10 | */ 11 | 12 | /*jslint browser:true, node:true*/ 13 | /*global define, Event, Node*/ 14 | 15 | 16 | /** 17 | * Instantiate fast-clicking listeners on the specified layer. 18 | * 19 | * @constructor 20 | * @param {Element} layer The layer to listen on 21 | * @param {Object} [options={}] The options to override the defaults 22 | */ 23 | function FastClick(layer, options) { 24 | var oldOnClick; 25 | 26 | options = options || {}; 27 | 28 | /** 29 | * Whether a click is currently being tracked. 30 | * 31 | * @type boolean 32 | */ 33 | this.trackingClick = false; 34 | 35 | 36 | /** 37 | * Timestamp for when click tracking started. 38 | * 39 | * @type number 40 | */ 41 | this.trackingClickStart = 0; 42 | 43 | 44 | /** 45 | * The element being tracked for a click. 46 | * 47 | * @type EventTarget 48 | */ 49 | this.targetElement = null; 50 | 51 | 52 | /** 53 | * X-coordinate of touch start event. 54 | * 55 | * @type number 56 | */ 57 | this.touchStartX = 0; 58 | 59 | 60 | /** 61 | * Y-coordinate of touch start event. 62 | * 63 | * @type number 64 | */ 65 | this.touchStartY = 0; 66 | 67 | 68 | /** 69 | * ID of the last touch, retrieved from Touch.identifier. 70 | * 71 | * @type number 72 | */ 73 | this.lastTouchIdentifier = 0; 74 | 75 | 76 | /** 77 | * Touchmove boundary, beyond which a click will be cancelled. 78 | * 79 | * @type number 80 | */ 81 | this.touchBoundary = options.touchBoundary || 10; 82 | 83 | 84 | /** 85 | * The FastClick layer. 86 | * 87 | * @type Element 88 | */ 89 | this.layer = layer; 90 | 91 | /** 92 | * The minimum time between tap(touchstart and touchend) events 93 | * 94 | * @type number 95 | */ 96 | this.tapDelay = options.tapDelay || 200; 97 | 98 | /** 99 | * The maximum time for a tap 100 | * 101 | * @type number 102 | */ 103 | this.tapTimeout = options.tapTimeout || 700; 104 | 105 | if (FastClick.notNeeded(layer)) { 106 | return; 107 | } 108 | 109 | // Some old versions of Android don't have Function.prototype.bind 110 | function bind(method, context) { 111 | return function() { return method.apply(context, arguments); }; 112 | } 113 | 114 | 115 | var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel']; 116 | var context = this; 117 | for (var i = 0, l = methods.length; i < l; i++) { 118 | context[methods[i]] = bind(context[methods[i]], context); 119 | } 120 | 121 | // Set up event handlers as required 122 | if (deviceIsAndroid) { 123 | layer.addEventListener('mouseover', this.onMouse, true); 124 | layer.addEventListener('mousedown', this.onMouse, true); 125 | layer.addEventListener('mouseup', this.onMouse, true); 126 | } 127 | 128 | layer.addEventListener('click', this.onClick, true); 129 | layer.addEventListener('touchstart', this.onTouchStart, false); 130 | layer.addEventListener('touchmove', this.onTouchMove, false); 131 | layer.addEventListener('touchend', this.onTouchEnd, false); 132 | layer.addEventListener('touchcancel', this.onTouchCancel, false); 133 | 134 | // Hack is required for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2) 135 | // which is how FastClick normally stops click events bubbling to callbacks registered on the FastClick 136 | // layer when they are cancelled. 137 | if (!Event.prototype.stopImmediatePropagation) { 138 | layer.removeEventListener = function(type, callback, capture) { 139 | var rmv = Node.prototype.removeEventListener; 140 | if (type === 'click') { 141 | rmv.call(layer, type, callback.hijacked || callback, capture); 142 | } else { 143 | rmv.call(layer, type, callback, capture); 144 | } 145 | }; 146 | 147 | layer.addEventListener = function(type, callback, capture) { 148 | var adv = Node.prototype.addEventListener; 149 | if (type === 'click') { 150 | adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) { 151 | if (!event.propagationStopped) { 152 | callback(event); 153 | } 154 | }), capture); 155 | } else { 156 | adv.call(layer, type, callback, capture); 157 | } 158 | }; 159 | } 160 | 161 | // If a handler is already declared in the element's onclick attribute, it will be fired before 162 | // FastClick's onClick handler. Fix this by pulling out the user-defined handler function and 163 | // adding it as listener. 164 | if (typeof layer.onclick === 'function') { 165 | 166 | // Android browser on at least 3.2 requires a new reference to the function in layer.onclick 167 | // - the old one won't work if passed to addEventListener directly. 168 | oldOnClick = layer.onclick; 169 | layer.addEventListener('click', function(event) { 170 | oldOnClick(event); 171 | }, false); 172 | layer.onclick = null; 173 | } 174 | } 175 | 176 | /** 177 | * Windows Phone 8.1 fakes user agent string to look like Android and iPhone. 178 | * 179 | * @type boolean 180 | */ 181 | var deviceIsWindowsPhone = navigator.userAgent.indexOf("Windows Phone") >= 0; 182 | 183 | /** 184 | * Android requires exceptions. 185 | * 186 | * @type boolean 187 | */ 188 | var deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0 && !deviceIsWindowsPhone; 189 | 190 | 191 | /** 192 | * iOS requires exceptions. 193 | * 194 | * @type boolean 195 | */ 196 | var deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent) && !deviceIsWindowsPhone; 197 | 198 | 199 | /** 200 | * iOS 4 requires an exception for select elements. 201 | * 202 | * @type boolean 203 | */ 204 | var deviceIsIOS4 = deviceIsIOS && (/OS 4_\d(_\d)?/).test(navigator.userAgent); 205 | 206 | 207 | /** 208 | * iOS 6.0-7.* requires the target element to be manually derived 209 | * 210 | * @type boolean 211 | */ 212 | var deviceIsIOSWithBadTarget = deviceIsIOS && (/OS [6-7]_\d/).test(navigator.userAgent); 213 | 214 | /** 215 | * BlackBerry requires exceptions. 216 | * 217 | * @type boolean 218 | */ 219 | var deviceIsBlackBerry10 = navigator.userAgent.indexOf('BB10') > 0; 220 | 221 | /** 222 | * Determine whether a given element requires a native click. 223 | * 224 | * @param {EventTarget|Element} target Target DOM element 225 | * @returns {boolean} Returns true if the element needs a native click 226 | */ 227 | FastClick.prototype.needsClick = function(target) { 228 | switch (target.nodeName.toLowerCase()) { 229 | 230 | // Don't send a synthetic click to disabled inputs (issue #62) 231 | case 'button': 232 | case 'select': 233 | case 'textarea': 234 | if (target.disabled) { 235 | return true; 236 | } 237 | 238 | break; 239 | case 'input': 240 | 241 | // File inputs need real clicks on iOS 6 due to a browser bug (issue #68) 242 | if ((deviceIsIOS && target.type === 'file') || target.disabled) { 243 | return true; 244 | } 245 | 246 | break; 247 | case 'label': 248 | case 'iframe': // iOS8 homescreen apps can prevent events bubbling into frames 249 | case 'video': 250 | return true; 251 | } 252 | 253 | return (/\bneedsclick\b/).test(target.className); 254 | }; 255 | 256 | 257 | /** 258 | * Determine whether a given element requires a call to focus to simulate click into element. 259 | * 260 | * @param {EventTarget|Element} target Target DOM element 261 | * @returns {boolean} Returns true if the element requires a call to focus to simulate native click. 262 | */ 263 | FastClick.prototype.needsFocus = function(target) { 264 | switch (target.nodeName.toLowerCase()) { 265 | case 'textarea': 266 | return true; 267 | case 'select': 268 | return !deviceIsAndroid; 269 | case 'input': 270 | switch (target.type) { 271 | case 'button': 272 | case 'checkbox': 273 | case 'file': 274 | case 'image': 275 | case 'radio': 276 | case 'submit': 277 | return false; 278 | } 279 | 280 | // No point in attempting to focus disabled inputs 281 | return !target.disabled && !target.readOnly; 282 | default: 283 | return (/\bneedsfocus\b/).test(target.className); 284 | } 285 | }; 286 | 287 | 288 | /** 289 | * Send a click event to the specified element. 290 | * 291 | * @param {EventTarget|Element} targetElement 292 | * @param {Event} event 293 | */ 294 | FastClick.prototype.sendClick = function(targetElement, event) { 295 | var clickEvent, touch; 296 | 297 | // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24) 298 | if (document.activeElement && document.activeElement !== targetElement) { 299 | document.activeElement.blur(); 300 | } 301 | 302 | touch = event.changedTouches[0]; 303 | 304 | // Synthesise a click event, with an extra attribute so it can be tracked 305 | clickEvent = document.createEvent('MouseEvents'); 306 | clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null); 307 | clickEvent.forwardedTouchEvent = true; 308 | targetElement.dispatchEvent(clickEvent); 309 | }; 310 | 311 | FastClick.prototype.determineEventType = function(targetElement) { 312 | 313 | //Issue #159: Android Chrome Select Box does not open with a synthetic click event 314 | if (deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') { 315 | return 'mousedown'; 316 | } 317 | 318 | return 'click'; 319 | }; 320 | 321 | 322 | /** 323 | * @param {EventTarget|Element} targetElement 324 | */ 325 | FastClick.prototype.focus = function(targetElement) { 326 | var length; 327 | 328 | // Issue #160: on iOS 7, some input elements (e.g. date datetime month) throw a vague TypeError on setSelectionRange. These elements don't have an integer value for the selectionStart and selectionEnd properties, but unfortunately that can't be used for detection because accessing the properties also throws a TypeError. Just check the type instead. Filed as Apple bug #15122724. 329 | if (deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf('date') !== 0 && targetElement.type !== 'time' && targetElement.type !== 'month') { 330 | length = targetElement.value.length; 331 | targetElement.setSelectionRange(length, length); 332 | } else { 333 | targetElement.focus(); 334 | } 335 | }; 336 | 337 | 338 | /** 339 | * Check whether the given target element is a child of a scrollable layer and if so, set a flag on it. 340 | * 341 | * @param {EventTarget|Element} targetElement 342 | */ 343 | FastClick.prototype.updateScrollParent = function(targetElement) { 344 | var scrollParent, parentElement; 345 | 346 | scrollParent = targetElement.fastClickScrollParent; 347 | 348 | // Attempt to discover whether the target element is contained within a scrollable layer. Re-check if the 349 | // target element was moved to another parent. 350 | if (!scrollParent || !scrollParent.contains(targetElement)) { 351 | parentElement = targetElement; 352 | do { 353 | if (parentElement.scrollHeight > parentElement.offsetHeight) { 354 | scrollParent = parentElement; 355 | targetElement.fastClickScrollParent = parentElement; 356 | break; 357 | } 358 | 359 | parentElement = parentElement.parentElement; 360 | } while (parentElement); 361 | } 362 | 363 | // Always update the scroll top tracker if possible. 364 | if (scrollParent) { 365 | scrollParent.fastClickLastScrollTop = scrollParent.scrollTop; 366 | } 367 | }; 368 | 369 | 370 | /** 371 | * @param {EventTarget} targetElement 372 | * @returns {Element|EventTarget} 373 | */ 374 | FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) { 375 | 376 | // On some older browsers (notably Safari on iOS 4.1 - see issue #56) the event target may be a text node. 377 | if (eventTarget.nodeType === Node.TEXT_NODE) { 378 | return eventTarget.parentNode; 379 | } 380 | 381 | return eventTarget; 382 | }; 383 | 384 | 385 | /** 386 | * On touch start, record the position and scroll offset. 387 | * 388 | * @param {Event} event 389 | * @returns {boolean} 390 | */ 391 | FastClick.prototype.onTouchStart = function(event) { 392 | var targetElement, touch, selection; 393 | 394 | // Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111). 395 | if (event.targetTouches.length > 1) { 396 | return true; 397 | } 398 | 399 | targetElement = this.getTargetElementFromEventTarget(event.target); 400 | touch = event.targetTouches[0]; 401 | 402 | if (deviceIsIOS) { 403 | 404 | // Only trusted events will deselect text on iOS (issue #49) 405 | selection = window.getSelection(); 406 | if (selection.rangeCount && !selection.isCollapsed) { 407 | return true; 408 | } 409 | 410 | if (!deviceIsIOS4) { 411 | 412 | // Weird things happen on iOS when an alert or confirm dialog is opened from a click event callback (issue #23): 413 | // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched 414 | // with the same identifier as the touch event that previously triggered the click that triggered the alert. 415 | // Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an 416 | // immediately preceeding touch event (issue #52), so this fix is unavailable on that platform. 417 | // Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string, 418 | // which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long, 419 | // random integers, it's safe to to continue if the identifier is 0 here. 420 | if (touch.identifier && touch.identifier === this.lastTouchIdentifier) { 421 | event.preventDefault(); 422 | return false; 423 | } 424 | 425 | this.lastTouchIdentifier = touch.identifier; 426 | 427 | // If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and: 428 | // 1) the user does a fling scroll on the scrollable layer 429 | // 2) the user stops the fling scroll with another tap 430 | // then the event.target of the last 'touchend' event will be the element that was under the user's finger 431 | // when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check 432 | // is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42). 433 | this.updateScrollParent(targetElement); 434 | } 435 | } 436 | 437 | this.trackingClick = true; 438 | this.trackingClickStart = event.timeStamp; 439 | this.targetElement = targetElement; 440 | 441 | this.touchStartX = touch.pageX; 442 | this.touchStartY = touch.pageY; 443 | 444 | // Prevent phantom clicks on fast double-tap (issue #36) 445 | if ((event.timeStamp - this.lastClickTime) < this.tapDelay) { 446 | event.preventDefault(); 447 | } 448 | 449 | return true; 450 | }; 451 | 452 | 453 | /** 454 | * Based on a touchmove event object, check whether the touch has moved past a boundary since it started. 455 | * 456 | * @param {Event} event 457 | * @returns {boolean} 458 | */ 459 | FastClick.prototype.touchHasMoved = function(event) { 460 | var touch = event.changedTouches[0], boundary = this.touchBoundary; 461 | 462 | if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) { 463 | return true; 464 | } 465 | 466 | return false; 467 | }; 468 | 469 | 470 | /** 471 | * Update the last position. 472 | * 473 | * @param {Event} event 474 | * @returns {boolean} 475 | */ 476 | FastClick.prototype.onTouchMove = function(event) { 477 | if (!this.trackingClick) { 478 | return true; 479 | } 480 | 481 | // If the touch has moved, cancel the click tracking 482 | if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) { 483 | this.trackingClick = false; 484 | this.targetElement = null; 485 | } 486 | 487 | return true; 488 | }; 489 | 490 | 491 | /** 492 | * Attempt to find the labelled control for the given label element. 493 | * 494 | * @param {EventTarget|HTMLLabelElement} labelElement 495 | * @returns {Element|null} 496 | */ 497 | FastClick.prototype.findControl = function(labelElement) { 498 | 499 | // Fast path for newer browsers supporting the HTML5 control attribute 500 | if (labelElement.control !== undefined) { 501 | return labelElement.control; 502 | } 503 | 504 | // All browsers under test that support touch events also support the HTML5 htmlFor attribute 505 | if (labelElement.htmlFor) { 506 | return document.getElementById(labelElement.htmlFor); 507 | } 508 | 509 | // If no for attribute exists, attempt to retrieve the first labellable descendant element 510 | // the list of which is defined here: http://www.w3.org/TR/html5/forms.html#category-label 511 | return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea'); 512 | }; 513 | 514 | 515 | /** 516 | * On touch end, determine whether to send a click event at once. 517 | * 518 | * @param {Event} event 519 | * @returns {boolean} 520 | */ 521 | FastClick.prototype.onTouchEnd = function(event) { 522 | var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement; 523 | 524 | if (!this.trackingClick) { 525 | return true; 526 | } 527 | 528 | // Prevent phantom clicks on fast double-tap (issue #36) 529 | if ((event.timeStamp - this.lastClickTime) < this.tapDelay) { 530 | this.cancelNextClick = true; 531 | return true; 532 | } 533 | 534 | if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) { 535 | return true; 536 | } 537 | 538 | // Reset to prevent wrong click cancel on input (issue #156). 539 | this.cancelNextClick = false; 540 | 541 | this.lastClickTime = event.timeStamp; 542 | 543 | trackingClickStart = this.trackingClickStart; 544 | this.trackingClick = false; 545 | this.trackingClickStart = 0; 546 | 547 | // On some iOS devices, the targetElement supplied with the event is invalid if the layer 548 | // is performing a transition or scroll, and has to be re-detected manually. Note that 549 | // for this to function correctly, it must be called *after* the event target is checked! 550 | // See issue #57; also filed as rdar://13048589 . 551 | if (deviceIsIOSWithBadTarget) { 552 | touch = event.changedTouches[0]; 553 | 554 | // In certain cases arguments of elementFromPoint can be negative, so prevent setting targetElement to null 555 | targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement; 556 | targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent; 557 | } 558 | 559 | targetTagName = targetElement.tagName.toLowerCase(); 560 | if (targetTagName === 'label') { 561 | forElement = this.findControl(targetElement); 562 | if (forElement) { 563 | this.focus(targetElement); 564 | if (deviceIsAndroid) { 565 | return false; 566 | } 567 | 568 | targetElement = forElement; 569 | } 570 | } else if (this.needsFocus(targetElement)) { 571 | 572 | // Case 1: If the touch started a while ago (best guess is 100ms based on tests for issue #36) then focus will be triggered anyway. Return early and unset the target element reference so that the subsequent click will be allowed through. 573 | // Case 2: Without this exception for input elements tapped when the document is contained in an iframe, then any inputted text won't be visible even though the value attribute is updated as the user types (issue #37). 574 | if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) { 575 | this.targetElement = null; 576 | return false; 577 | } 578 | 579 | this.focus(targetElement); 580 | this.sendClick(targetElement, event); 581 | 582 | // Select elements need the event to go through on iOS 4, otherwise the selector menu won't open. 583 | // Also this breaks opening selects when VoiceOver is active on iOS6, iOS7 (and possibly others) 584 | if (!deviceIsIOS || targetTagName !== 'select') { 585 | this.targetElement = null; 586 | event.preventDefault(); 587 | } 588 | 589 | return false; 590 | } 591 | 592 | if (deviceIsIOS && !deviceIsIOS4) { 593 | 594 | // Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled 595 | // and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42). 596 | scrollParent = targetElement.fastClickScrollParent; 597 | if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) { 598 | return true; 599 | } 600 | } 601 | 602 | // Prevent the actual click from going though - unless the target node is marked as requiring 603 | // real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted. 604 | if (!this.needsClick(targetElement)) { 605 | event.preventDefault(); 606 | this.sendClick(targetElement, event); 607 | } 608 | 609 | return false; 610 | }; 611 | 612 | 613 | /** 614 | * On touch cancel, stop tracking the click. 615 | * 616 | * @returns {void} 617 | */ 618 | FastClick.prototype.onTouchCancel = function() { 619 | this.trackingClick = false; 620 | this.targetElement = null; 621 | }; 622 | 623 | 624 | /** 625 | * Determine mouse events which should be permitted. 626 | * 627 | * @param {Event} event 628 | * @returns {boolean} 629 | */ 630 | FastClick.prototype.onMouse = function(event) { 631 | 632 | // If a target element was never set (because a touch event was never fired) allow the event 633 | if (!this.targetElement) { 634 | return true; 635 | } 636 | 637 | if (event.forwardedTouchEvent) { 638 | return true; 639 | } 640 | 641 | // Programmatically generated events targeting a specific element should be permitted 642 | if (!event.cancelable) { 643 | return true; 644 | } 645 | 646 | // Derive and check the target element to see whether the mouse event needs to be permitted; 647 | // unless explicitly enabled, prevent non-touch click events from triggering actions, 648 | // to prevent ghost/doubleclicks. 649 | if (!this.needsClick(this.targetElement) || this.cancelNextClick) { 650 | 651 | // Prevent any user-added listeners declared on FastClick element from being fired. 652 | if (event.stopImmediatePropagation) { 653 | event.stopImmediatePropagation(); 654 | } else { 655 | 656 | // Part of the hack for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2) 657 | event.propagationStopped = true; 658 | } 659 | 660 | // Cancel the event 661 | event.stopPropagation(); 662 | event.preventDefault(); 663 | 664 | return false; 665 | } 666 | 667 | // If the mouse event is permitted, return true for the action to go through. 668 | return true; 669 | }; 670 | 671 | 672 | /** 673 | * On actual clicks, determine whether this is a touch-generated click, a click action occurring 674 | * naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or 675 | * an actual click which should be permitted. 676 | * 677 | * @param {Event} event 678 | * @returns {boolean} 679 | */ 680 | FastClick.prototype.onClick = function(event) { 681 | var permitted; 682 | 683 | // It's possible for another FastClick-like library delivered with third-party code to fire a click event before FastClick does (issue #44). In that case, set the click-tracking flag back to false and return early. This will cause onTouchEnd to return early. 684 | if (this.trackingClick) { 685 | this.targetElement = null; 686 | this.trackingClick = false; 687 | return true; 688 | } 689 | 690 | // Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target. 691 | if (event.target.type === 'submit' && event.detail === 0) { 692 | return true; 693 | } 694 | 695 | permitted = this.onMouse(event); 696 | 697 | // Only unset targetElement if the click is not permitted. This will ensure that the check for !targetElement in onMouse fails and the browser's click doesn't go through. 698 | if (!permitted) { 699 | this.targetElement = null; 700 | } 701 | 702 | // If clicks are permitted, return true for the action to go through. 703 | return permitted; 704 | }; 705 | 706 | 707 | /** 708 | * Remove all FastClick's event listeners. 709 | * 710 | * @returns {void} 711 | */ 712 | FastClick.prototype.destroy = function() { 713 | var layer = this.layer; 714 | 715 | if (deviceIsAndroid) { 716 | layer.removeEventListener('mouseover', this.onMouse, true); 717 | layer.removeEventListener('mousedown', this.onMouse, true); 718 | layer.removeEventListener('mouseup', this.onMouse, true); 719 | } 720 | 721 | layer.removeEventListener('click', this.onClick, true); 722 | layer.removeEventListener('touchstart', this.onTouchStart, false); 723 | layer.removeEventListener('touchmove', this.onTouchMove, false); 724 | layer.removeEventListener('touchend', this.onTouchEnd, false); 725 | layer.removeEventListener('touchcancel', this.onTouchCancel, false); 726 | }; 727 | 728 | 729 | /** 730 | * Check whether FastClick is needed. 731 | * 732 | * @param {Element} layer The layer to listen on 733 | */ 734 | FastClick.notNeeded = function(layer) { 735 | var metaViewport; 736 | var chromeVersion; 737 | var blackberryVersion; 738 | var firefoxVersion; 739 | 740 | // Devices that don't support touch don't need FastClick 741 | if (typeof window.ontouchstart === 'undefined') { 742 | return true; 743 | } 744 | 745 | // Chrome version - zero for other browsers 746 | chromeVersion = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1]; 747 | 748 | if (chromeVersion) { 749 | 750 | if (deviceIsAndroid) { 751 | metaViewport = document.querySelector('meta[name=viewport]'); 752 | 753 | if (metaViewport) { 754 | // Chrome on Android with user-scalable="no" doesn't need FastClick (issue #89) 755 | if (metaViewport.content.indexOf('user-scalable=no') !== -1) { 756 | return true; 757 | } 758 | // Chrome 32 and above with width=device-width or less don't need FastClick 759 | if (chromeVersion > 31 && document.documentElement.scrollWidth <= window.outerWidth) { 760 | return true; 761 | } 762 | } 763 | 764 | // Chrome desktop doesn't need FastClick (issue #15) 765 | } else { 766 | return true; 767 | } 768 | } 769 | 770 | if (deviceIsBlackBerry10) { 771 | blackberryVersion = navigator.userAgent.match(/Version\/([0-9]*)\.([0-9]*)/); 772 | 773 | // BlackBerry 10.3+ does not require Fastclick library. 774 | // https://github.com/ftlabs/fastclick/issues/251 775 | if (blackberryVersion[1] >= 10 && blackberryVersion[2] >= 3) { 776 | metaViewport = document.querySelector('meta[name=viewport]'); 777 | 778 | if (metaViewport) { 779 | // user-scalable=no eliminates click delay. 780 | if (metaViewport.content.indexOf('user-scalable=no') !== -1) { 781 | return true; 782 | } 783 | // width=device-width (or less than device-width) eliminates click delay. 784 | if (document.documentElement.scrollWidth <= window.outerWidth) { 785 | return true; 786 | } 787 | } 788 | } 789 | } 790 | 791 | // IE10 with -ms-touch-action: none or manipulation, which disables double-tap-to-zoom (issue #97) 792 | if (layer.style.msTouchAction === 'none' || layer.style.touchAction === 'manipulation') { 793 | return true; 794 | } 795 | 796 | // Firefox version - zero for other browsers 797 | firefoxVersion = +(/Firefox\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1]; 798 | 799 | if (firefoxVersion >= 27) { 800 | // Firefox 27+ does not have tap delay if the content is not zoomable - https://bugzilla.mozilla.org/show_bug.cgi?id=922896 801 | 802 | metaViewport = document.querySelector('meta[name=viewport]'); 803 | if (metaViewport && (metaViewport.content.indexOf('user-scalable=no') !== -1 || document.documentElement.scrollWidth <= window.outerWidth)) { 804 | return true; 805 | } 806 | } 807 | 808 | // IE11: prefixed -ms-touch-action is no longer supported and it's recomended to use non-prefixed version 809 | // http://msdn.microsoft.com/en-us/library/windows/apps/Hh767313.aspx 810 | if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') { 811 | return true; 812 | } 813 | 814 | return false; 815 | }; 816 | 817 | 818 | /** 819 | * Factory method for creating a FastClick object 820 | * 821 | * @param {Element} layer The layer to listen on 822 | * @param {Object} [options={}] The options to override the defaults 823 | */ 824 | FastClick.attach = function(layer, options) { 825 | return new FastClick(layer, options); 826 | }; 827 | 828 | 829 | if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) { 830 | 831 | // AMD. Register as an anonymous module. 832 | define(function() { 833 | return FastClick; 834 | }); 835 | } else if (typeof module !== 'undefined' && module.exports) { 836 | module.exports = FastClick.attach; 837 | module.exports.FastClick = FastClick; 838 | } else { 839 | window.FastClick = FastClick; 840 | } 841 | }()); 842 | --------------------------------------------------------------------------------