From 4d933ed959cd9cf2965879991cd07f2636eacb43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20ICH=C3=89?= <4037271+peeweek@users.noreply.github.com> Date: Fri, 20 Aug 2021 15:49:13 +0200 Subject: [PATCH 0001/1062] Added Settings for Color scheme --- apps.json | 3 +- apps/hcclock/ChangeLog | 2 +- apps/hcclock/hcclock.app.js | 47 +++++++++++++++++++++++++++----- apps/hcclock/hcclock.settings.js | 32 ++++++++++++++++++++++ core | 2 +- 5 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 apps/hcclock/hcclock.settings.js diff --git a/apps.json b/apps.json index 2b28509cf..a9a6d736d 100644 --- a/apps.json +++ b/apps.json @@ -3390,13 +3390,14 @@ { "id": "hcclock", "name": "Hi-Contrast Clock", "icon": "hcclock-icon.png", - "version":"0.01", + "version":"0.02", "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", "tags": "clock", "type":"clock", "allow_emulator":true, "storage": [ {"name":"hcclock.app.js","url":"hcclock.app.js"}, + {"name":"hcclock.settings.js","url":"hcclock.settings.js"}, {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true} ] }, diff --git a/apps/hcclock/ChangeLog b/apps/hcclock/ChangeLog index 0ca30d066..343be7f07 100644 --- a/apps/hcclock/ChangeLog +++ b/apps/hcclock/ChangeLog @@ -1,2 +1,2 @@ 0.01: base code - +0.02: added settings for color schemes \ No newline at end of file diff --git a/apps/hcclock/hcclock.app.js b/apps/hcclock/hcclock.app.js index 98abbc6f3..4664dd763 100644 --- a/apps/hcclock/hcclock.app.js +++ b/apps/hcclock/hcclock.app.js @@ -174,19 +174,52 @@ function fmtDate(day,month,year,hour) let ap = "(AM)"; if(hour == 0 || hour > 12) ap = "(PM)"; - return months[month] + " " + day + " " + year + " "+ ap; + return months[month] + " " + day + " " + year + " "+ ap; } else return months[month] + ". " + day + " " + year; } -// Handles Flipping colors, then refreshes the UI + +////////////////////////////////////////// +// +// HANDLE COLORS + SETTINGS +// + +function getColorScheme() +{ + let settings = require('Storage').readJSON("hcclock.json", true) || {}; + if (!("scheme" in settings)) { + settings.scheme = 0; + } + return settings.scheme; +} + +function setColorScheme(value) +{ + let settings = require('Storage').readJSON("hcclock.json", true) || {}; + settings.scheme = value; + require('Storage').writeJSON('hcclock.json', settings); + + if(value == 0) // White + { + bg = 255; + fg = 0; + } + else // Black + { + bg = 0; + fg = 255; + } + redraw(); +} + function flipColors() { - let t = bg; - bg = fg; - fg = t; - redraw(); + if(getColorScheme() == 0) + setColorScheme(1); + else + setColorScheme(0); } ////////////////////////////////////////// @@ -197,7 +230,7 @@ function flipColors() // Initialize g.clear(); Bangle.loadWidgets(); -redraw(); +setColorScheme(getColorScheme()); // Define Refresh Interval setInterval(updateTime, interval); diff --git a/apps/hcclock/hcclock.settings.js b/apps/hcclock/hcclock.settings.js new file mode 100644 index 000000000..34d6d191a --- /dev/null +++ b/apps/hcclock/hcclock.settings.js @@ -0,0 +1,32 @@ +(function(back) { + + function getColorScheme() + { + let settings = require('Storage').readJSON("hcclock.json", true) || {}; + if (!("scheme" in settings)) { + settings.scheme = 0; + } + return settings.scheme; + } + function setColorScheme(value) + { + let settings = require('Storage').readJSON("hcclock.json", true) || {}; + settings.scheme = value? 1 : 0; + require('Storage').writeJSON('hcclock.json', settings); + } + function setIcon(visible) { + updateSetting('showIcon', visible); + + } + var mainmenu = { + "" : { "title" : "Hi-Contrast Clock" }, + "Color Scheme" : { + value: getColorScheme, + format: v => v == 0?"White":"Black", + onchange: setColorScheme + }, + "< Back" : back, + }; + E.showMenu(mainmenu); + }) + \ No newline at end of file diff --git a/core b/core index 27f9a7125..8d9a012d6 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 27f9a7125146a38c4357d679ec783f6e98a983c6 +Subproject commit 8d9a012d62d40aae1b2304d0149440fb3c022393 From 183a36f5acac51c184d002e838a4642179414680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20ICH=C3=89?= <4037271+peeweek@users.noreply.github.com> Date: Fri, 20 Aug 2021 15:54:32 +0200 Subject: [PATCH 0002/1062] Added Cycling for Values in Color Schemes --- apps/hcclock/hcclock.settings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/hcclock/hcclock.settings.js b/apps/hcclock/hcclock.settings.js index 34d6d191a..92d5f47e5 100644 --- a/apps/hcclock/hcclock.settings.js +++ b/apps/hcclock/hcclock.settings.js @@ -10,6 +10,7 @@ } function setColorScheme(value) { + value = value + 1 % 2; let settings = require('Storage').readJSON("hcclock.json", true) || {}; settings.scheme = value? 1 : 0; require('Storage').writeJSON('hcclock.json', settings); From 5972b991f95e8aec690bd005758147c7adb164d1 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Sun, 5 Sep 2021 20:42:36 +0200 Subject: [PATCH 0003/1062] Layout: add support for "img" with object or ArrayBuffer Graphics object Rendering these already worked, because we simply pass src to drawImage, so we just need to determine the correct size. --- modules/Layout.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/Layout.js b/modules/Layout.js index a45b17107..893403c59 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -167,9 +167,15 @@ function updateMin(l) { break; } case "img": { - var im = E.toString(l.src()); - l._h = im.charCodeAt(0); - l._w = im.charCodeAt(1); + var src = l.src(); + if (typeof(src) === 'object') { + l._h = ("width" in src) ? src.width : src.getWidth(); + l._w = ("height" in src) ? src.height : src.getHeight(); + } else { + var im = E.toString(src); + l._h = im.charCodeAt(0); + l._w = im.charCodeAt(1); + } break; } case undefined: From 79f84b25ea2d58bbb50c5a8f73b5f3264b98ccb7 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 13 Sep 2021 20:26:53 +0100 Subject: [PATCH 0004/1062] Added pastel clock for bangle 2 --- apps/pastel/ChangeLog | 1 + apps/pastel/README.md | 4 ++ apps/pastel/pastel.app.js | 111 +++++++++++++++++++++++++++++++++++++ apps/pastel/pastel.icon.js | 1 + apps/pastel/pastel.info.js | 7 +++ apps/pastel/pastel.png | Bin 0 -> 1123 bytes 6 files changed, 124 insertions(+) create mode 100644 apps/pastel/ChangeLog create mode 100644 apps/pastel/README.md create mode 100644 apps/pastel/pastel.app.js create mode 100644 apps/pastel/pastel.icon.js create mode 100644 apps/pastel/pastel.info.js create mode 100644 apps/pastel/pastel.png diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog new file mode 100644 index 000000000..7b83706bf --- /dev/null +++ b/apps/pastel/ChangeLog @@ -0,0 +1 @@ +0.01: First release diff --git a/apps/pastel/README.md b/apps/pastel/README.md new file mode 100644 index 000000000..26486b990 --- /dev/null +++ b/apps/pastel/README.md @@ -0,0 +1,4 @@ +# Pastel Clock + +A Configurable clock with custom fonts and background +Designed specifically for bangle 2 diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js new file mode 100644 index 000000000..985c5f045 --- /dev/null +++ b/apps/pastel/pastel.app.js @@ -0,0 +1,111 @@ + + +Graphics.prototype.setFontCabinSketch = function() { +// Actual height 48 (51 - 4) +var widths = atob("ECMtGCEiJSIkHyYlDw=="); +var font = atob("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"); +var scale = 1; // size multiplier for this font +g.setFontCustom(font, 46, widths, 65+(scale<<8)+(1<<16)); +}; + + +Graphics.prototype.setFontGochiHand = function() { + // Actual height 49 (53 - 5) +var widths = atob("DRQeExgXGxgbGRobDg=="); +var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAA/gAAAAAAA/gAAAAAAA/gAAAAAAA/gAAAAAAA/gAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAD/+AAAAAAP/8AAAAAA//gAAAAAH/8AAAAAAf/wAAAAAB/+AAAAAAP/4AAAAAA//AAAAAAH/8AAAAAAf/gAAAAAD/+AAAAAAP/4AAAAAB//AAAAAAf/8AAAAAA//wAAAAAB//AAAAAAB/4AAAAAAB/gAAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAf/wAAAAAB//8AAAAAD//+AAAAAH//+AAAAAP4D/AAAAAfwB/AAAAAfgA/gAAAAfAAfgAAAA/AAfgAAAA/AAfgAAAA/AAfgAAAA/AAfgAAAA/AAfgAAAA/AAfgAAAA/gA/AAAAA/gA/AAAAAfwB+AAAAAf4D+AAAAAP//8AAAAAH//4AAAAAD//gAAAAAB/+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAAD8AAAAAAAD8AAAAAAAD8AAAAAAAH8AAAAAAAH4AAAAAAAH4H+AAAAAPz//AAAAAP///gAAAAf///gAAAA////gAAAA////gAAAA/wAfgAAAA4AAPAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAADgA/AAAAAHwA/gAAAAPwB/gAAAAfgB/gAAAAfgD/gAAAA/gH/gAAAA/AH/gAAAA/AP/gAAAA/AffgAAAA/A/fgAAAA/B+fgAAAA/j8fgAAAA//4fgAAAA//4fgAAAAf/wfgAAAAf/A/gAAAAP+A/AAAAAD4B/AAAAAAAB+AAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAH8AAAAAAAP+AAAHwAAP+AAAPwAAH/AAAPwAAD/AAAfgAAB/AAAfgAAB/AAA/ADAA/AAA/AHwA/AAA/AH4A/AAA/AP4A/AAA/AP4A/AAA/Af4A+AAA/g/8B+AAA///8B8AAA///+D8AAAf/7//4AAAP/x//wAAAH/A//gAAAD8Af+AAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAA/AAAAAAAD/gAAAAAAP/gAAAAAAf/gAAAAAA//gAAAAAD+fgAAAAAH4fgAAAAAPwfgAAAAAfgfgAAAAA/AfgAAAAB+AfgAAAAD8AfgAAAAH4Afj8AAAPwAf/+AAAPwP///AAAf/////AAA//////AAA//////AAA////h/AAA//wfgeAAAPAAfgIAAAAAAfgAAAAAAAfAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAPwAAAA/8Af4AAB//8Af8AAB//8AP+AAB//8AH+AAB//8AD+AAB+D8AD+AAB+D+AB+AAB+D+AB+AAB+B+AB+AAB+B/AB+AAB+B/AB+AAB+A/gD8AAB+A/wD8AAB+A/4H4AAB+Af//wAAD+AP//gAAD+AH//AAAH8AD/+AAAD4AA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//AAAAAAP//4AAAAA///8AAAAD///+AAAAH////AAAAf/4H/AAAA/nwB/gAAB/PwA/gAAB+PwAfgAAD8fgAfgAAH4fgAfgAAH4fgAfgAAPwfwAfgAAPwfwAfAAAfwP4A/AAAfwP8B+AAAfwP/H+AAAfwH//8AAAfgD//wAAAfAB//gAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAB/AAAAAAAB/AAAAAAAB/AAAB8AAB/AAAf+AAA/AAB//AAA/AAH//AAA/AAf//AAA/AB//+AAA/AH//+AAA/Af/z8AAA/A/8A4AAA/D/wAAAAA/H/AAAAAA/f8AAAAAA//wAAAAAA//AAAAAAA/+AAAAAAA/4AAAAAAA/gAAAAAAA/AAAAAAAA8AAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAgAB/8AAAD7wD/+AAAH/+H//AAAH//P//AAAP/////gAAP//+B/gAAfx/8A/gAAfgf4AfgAAfgP4AfgAAfgH8AfgAAfgH8AfgAAfgD8AfgAAfgD+AfgAAfgH/AfAAAfwP/AfAAAP+//g+AAAP//f/+AAAH/+f/8AAAD/8P/4AAAB/wD/wAAAAeAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAMAAAB//gB+AAAH//wB+AAAP//4D+AAAf4P8D+AAAfgH8D+AAA/AD+D+AAA/AD+D8AAB+AB+D8AAB+AB+H4AAB+AB+H4AAB+AB8PwAAB/AD8fgAAB/gD8/AAAA/wD7+AAAA/8H/8AAAAf///4AAAAP///gAAAAH//+AAAAAB//4AAAAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAD4A/gAAAAH8A/gAAAAH8A/gAAAAH8A/gAAAAH8A/gAAAAH8AfAAAAAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); +var scale = 1; // size multiplier for this font +g.setFontCustom(font, 46, widths, 54+(scale<<8)+(1<<16)); +}; + +Graphics.prototype.setFontLatoSmall = function() { +// Actual height 21 (20 - 0) +var widths = atob("BAgJDQ0RDwUHBwkNBQgFCA0NDQ0NDQ0NDQ0GBg0NDQkSDw4PEQ0MEBEHCg8LFBESDRIODA0QDxYODg4HCAcNCQcLDAoMDAcLDAYGDAYSDAwMDAkKCAwLEQsLCgcHBw0A"); +var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAEA/84D/zgAAEAAAAAAAAAAAA+AAD8AAAAAAAAAD4AAPgAAAAAAAAAABAADGIAM/gB/8A/+AD8YAAx+AD/4B/4APxgAjGAAIAAAAAAAAAAAAADwMAfg4DnBgMMHg///P/5gMGGAwc4Bg/AEB4AAAAA4AAHwAA5gYDCDgMIcAxjgB84ADnAAA4AAHOABz8AOMYBwwgMDCAgP4AAfAAAAAAAAAAeAAH8APY4B/BgMcGAw4YDBxgMDmA4HwBwPAAB8AAf4ABhgAACAAAAD4AAPgAAAAAAAAAAAAAH/gB//wfAHzgAHAAAAAAAAAAAOAAcfAPwf/8Af+AAAAAAAAAAAANgAAUAABwAAfwAAcAADQAAJAAAAAAAAAAAGAAAYAABgAAGAAP/gA/+AAGAAAYAABgAAGAAAQAAAAAAAEAAA7AAD4AAAAAAAAAAAAGAAAYAABgAAGAAAYAAAgAAAAAAAAAADgAAOAAAYAAAAAAPAAD4AB8AAfAAHwAD4AAeAABAAAADgAB/wAf/wBwHAMAGAwAYDABgMAGA4A4B4PAD/4AH/AAAAAAAAAAAAAYAgDgGAcAYDgBgP/+A//4AABgAAGAAAYAAAAAAAAAAAAQBgHgOAcB4DgPgMA2AwGYDAxgOOGAfwYB+BgBgGAAAAAAAADA4AcDwDgDgMAGAwgYDDBgMcGA5w4B9/ADj4AAAAAAAAABgAAOAAB4AAfgADmAAcYADhgA4GAD//gP/+AAGAAAYAAAgAAAAAADAH4OA/gYDGBgMYGAxgYDGDgMccAw/wCB8AAAAAAAAAAYAAH4AB/wAPjgB8GAOwYDzBgOMGAg44AD/AAH4AACAAAAAAAAAwAADAAAMAGAwB4DAfAMHwAw8ADPAAPwAA+AADgAAAAAAAAAA4+AH38A/44DHBgMMGAwwYDHBgOeOAffwA4/AABwAAAAAAAAAAAAPgAB/AAOMGAww4DBngMF4Aw/ADj4AH+AAPwAAAAAAAAADg4AODgAwGAAAAAAAAAAAABAAAODsA4PgBAYAAAAAAAAAQAABgAAPAAA8AAG4AAZgADHAAMMABgwAAAAAAAAAAAAAAAAEQAAZgABmAAGYAAZgABmAAGYAAZgABmAAGYAAAAAAAAAAAAAAAAGDAAMMAAxwABmAAG4AAPAAA8AABgAAEAAAAAAAAAEAAA4AADABgMHOAw84DGAAP4AAfAAAAAAACAAD/gAePADgGAYAMBh8YMPxgxxGDGEIIYwgxOCDH8IMYRgYBGAwMwD/hAD8AAAAAAAGAAB4AAfgAP4AD+AA/YAPhgA4GAD4YAD9gAD+AAD+AAB+AAB4AABgAAAAAAAD//gP/+AwYYDBhgMGGAwYYDDhgOOGA/84B+/ABh4AAAAAAAAA/gAH/gA+/AHAcA4A4DgBgMAGAwAYDABgMAGA4A4BgDAGAMAAAAAAAAAAAA//4D//gMAGAwAYDABgMAGAwAYDABgOAOAYAwB4PAD/4AH/AAHwAAAAAAAAAAAAP/+A//4DDBgMMGAwwYDDBgMMGAwwYDABgMAGAAAAAAAAAAAA//4D//gMGAAwYADBgAMGAAwYADBgAMGAAwAAAAAAA/gAH/AA++AHAcA4A4DgBgMAGAwAYDABgMGGAwYYDhjgGH8AAfwAAAAAAAAAAAD//gP/+A//4ADAAAMAAAwAADAAAMAAAwAADAAAMAA//4D//gAAAAAAAAAAAAAAA//4D//gAAAAAAAAAAAAAAAAAYAABgAAGAAA4AAHgP/8A//gAAAAAAAAAAAAAAAP/+A//4ADAAAMAAB4AAPwABzgAOHABwPAOAeAwA4CAAgAAAAAAAAAAAP/+A//4AABgAAGAAAYAABgAAGAAAYAABgAAAA//4D//gP/+AeAAAeAAAeAAA+AAA8AAA4AAHgAB4AAeAAHwAA8AAPAAA//4D//gAAAAAAAAAAAAAAA//4D//gHAAAOAAAeAAA8AAA4AABwAADwAADgAAHAP/+A//4AAAAAAAAAAAAP4AD/4AeDwBwHAOAOAwAYDABgMAGAwAYDABgOAOAcBwB4PAD/4AD+AABAAAAAAAAAAAAAP/+A//4DBgAMGAAwYADBgAMGAA44AB/AAH4AAHAAAAAAA/gAP/gB4PAHAcA4A4DABgMAGAwAYDABgMAGA4A4BwHwHg/gP/nAP4MAEAQAAAAAAAAAAA//4D//gMGAAwYADBgAMHAAw/ADneAH4eAPA4AABgAAAAAAwA8DAH4OA5wYDDBgMMGAw4YDBjgOH8AYPgAAIAAAAAwAADAAAMAAAwAADAAAP/+A//4DAAAMAAAwAADAAAMAAAAAAAAAAAAAA//AD//AAAcAAA4AABgAAGAAAYAABgAAOAABwD//AP/4A/8AAAAAOAAA+AAB+AAB/AAA/AAA/AAAeAAD4AA/AAPwAH8AB+AAPgAA4AAAAAAOAAA/AAB/gAA/wAAf4AAPgAB+AA/gAfwAH4AA8AAD8AAD+AAB/AAB/gAA+AAH4AD/AD/gA/wAD4AAMAAAgAYDgDgPAeAeHgAe8AA/AAA4AAHwAB/wAPHgDwPgOAOAgAYAAAAIAAA4AADwAAHwAAHgAAHgAAP+AA/4APgAB4AAeAADwAAMAAAgAAAAAAMAGAwA4DAPgMB+AwPYDDxgMeGAzwYD8BgPgGA8AYDABgAAAAAAAH//8f//xAABEAAEAAAAAAAHAAAPAAAPgAAPgAAHwAAHwAAHgAADAAAAEAAEQAAR///H//8AAAAAAAAAAAAAAABgAAeAADwAA8AADgAAHgAAPAAAOAAAIAAAAAAAAAAAAQAABAAAEAAAQAABAAAEAAAQAABAAAAAAAAgAADAAAOAAAIAAAAAAAAAAAAAAAHAAY+ADnYAMYgAxiADGYAORAAf+AA/4AAAAAAAB//4H//gAYMADAYAMBgAwGADAYAPHgAf8AA/gAAAAAAAAA/gAH/AA4OADAYAMBgAwGADAYAMDgAQEAAAAAB+AAf8ADx4AMBgAwGADAYAMBgAYMB//4H//gAAAAAAAAB8AAf8ADpwAMhgAyGADIYAMhgA6GAB4wADhAAAAACAAAMAAH/+A//4DMAAMwAAzAAABAcAff4D/5gMbmAwmYDCZgMZmA/mYD8fAMA4AgAAAAAAAAAf/+B//4AGAAAwAADAAAMAAA4AAD/4AH/gAAAAAAACAAAc/+Bz/4CAAAAAAAAABiAAGc//5z//CAAAAAAAAAAAAAAf/+B//4AAYAADgAAfAAHuAA4cADAYAIAgAAAAAAAAAAAf/+B//4AAAAAAAAAAAAAAAA/+AD/4AEAAAwAADAAAMAAA/+AB/4AH/gAwAADAAAMAAA4AAD/4AD/gAAAAAAAAAAAA/+AD/4AGAAAwAADAAAMAAAwAAD/4AH/gAAAAAAAAD+AAf8ADg4AMBgAwGADAYAMBgA4OAB/wAD+AADgAAAAAP/+A//4BgwAMBgAwGADAYAMBgA8eAB/wAD8AAAAAAAAAB+AAf8ADx4AMBgAwGADAYAMBgAYMAD//gP/+AAAAAAAAP/gA/+ABwAAOAAAwAADAAAMAAAAAAAAQAHhgA/GADMYAMxgAxmADH4AEPAAAQAAAAAMAAAwAAf/wD//gAwGADAYAMBgAAAAAAAAP/AA/+AAAYAABgAAGAAAYAADAA/+AD/4AAAAAAAADgAAPgAAfgAAPwAAPgAAeAAHwAD8AA/AADgAAIAAA4AAD8AAD+AAB+AAB4AA/AAfgADwAAPgAAfwAAP4AAHgAB+AA/gAPwAA4AAAAAAAAgAwGADh4AHvAAPwAAOAAB8AAe8ADh4AMBgAgCACAAAOAAA+AAA/BgA/eAA/wAD8AA/AAPgAD4AAOAAAgGADA4AMHgAx+ADOYANxgA+GADgYAMBgAAAAAMAD//4f9/xgADEAAEAAAAAAAAAAAAAAB///n//+AAAAAAAAAAAAAABAABGAAMf9/w//+AAwAAAAAAAAAA4AADgAAYAABgAAHAAAMAAAwAADAAAcAADgAAAAAAAA"); +var scale = 1; // size multiplier for this font +g.setFontCustom(font, 32, widths, 22+(scale<<8)+(1<<16)); + +}; + +Graphics.prototype.setFontLato = function() { +// Actual height 50 (53 - 4) +var widths = atob("DhglJSUlJSUlJSUlEA=="); +var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAAAHwAAAAAAAAA/gAAAAAAAAH/AAAAAAAAAf8AAAAAAAAB/wAAAAAAAAD+AAAAAAAAAHwAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAAAB/AAAAAAAAAf8AAAAAAAAP/wAAAAAAAD/8AAAAAAAB//AAAAAAAAf/wAAAAAAAP/4AAAAAAAD/+AAAAAAAA//AAAAAAAAf/wAAAAAAAH/4AAAAAAAD/+AAAAAAAA//AAAAAAAAf/wAAAAAAAH/4AAAAAAAD/+AAAAAAAA//AAAAAAAAf/wAAAAAAAD/4AAAAAAAAP+AAAAAAAAA/AAAAAAAAADwAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//+AAAAAAA////AAAAAAP////gAAAAD/////AAAAA//////AAAAH/////+AAAA//gAH/8AAAH/gAAB/4AAA/4AAAB/wAAD+AAAAB/AAAfwAAAAD+AAB+AAAAAH4AAH4AAAAAfgAAfAAAAAA+AAD8AAAAAD8AAPwAAAAAPwAA/AAAAAA/AAD8AAAAAD8AAPwAAAAAPwAAfAAAAAA+AAB+AAAAAD4AAH4AAAAAfgAAfwAAAAD+AAA/gAAAAfwAAD/gAAAH/AAAH/gAAB/4AAAP/4AB//AAAAf/////4AAAA//////AAAAA/////4AAAAB////+AAAAAA////AAAAAAAf//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAB8AAAAAAAAAPwAAAD4AAAB/AAAAPgAAAP4AAAA+AAAB/AAAAD4AAAP8AAAAPgAAA/gAAAA+AAAH8AAAAD4AAA/gAAAAPgAAH8AAAAA+AAA/gAAAAD4AAH///////gAAf//////+AAB///////4AAH///////gAAf//////+AAB///////4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAAADwAAAA+AAAA/AAAAH4AAAP8AAAA/gAAB/wAAAH+AAAP/AAAA/4AAB/4AAAH/gAAH+AAAA/+AAA/gAAAH/4AAD8AAAA/vgAAfgAAAH8+AAB+AAAA/n4AAHwAAAH8fgAA/AAAA/h+AAD8AAAH8H4AAPwAAA/gfgAA/AAAH8B+AAD8AAA/gH4AAPwAAH8AfgAAfAAB/gB+AAB+AAP8AH4AAH8AB/gAfgAAP4Af8AB+AAA/8f/gAH4AAB///8AAfgAAH///gAB+AAAP//4AAH4AAAP//AAAfgAAAf/wAAB+AAAAP4AAAD4AAAAAAAAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAHgAAAAA8AAA/gAAAAPwAAD/AAAAD/AAAP+AAAAf8AAA/8AAAD/wAAB/4AAAf+AAAB/wAAD/gAAAB/AAAP4AAAAD+AAB/AAAAAH4AAH4AAAAAfgAAfgAAAAA+AAB8AAAAAD8AAPwAB4AAPwAA/AAHgAA/AAD8AAfAAD8AAPwAD8AAPwAA/AAPwAA/AAD8AA/AAD4AAHwAH8AAfgAAfgAf4AB+AAB/AD/gAP4AAD+AffAB/AAAP//9/Af8AAAf//n///gAAB//+P//8AAAD//wf//gAAAD/+A//8AAAAH/gB//gAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAB+AAAAAAAAAf4AAAAAAAAD/gAAAAAAAAf+AAAAAAAAH/4AAAAAAAA//gAAAAAAAH++AAAAAAAB/z4AAAAAAAP+PgAAAAAAB/g+AAAAAAAf8D4AAAAAAD/gPgAAAAAAf4A+AAAAAAH/AD4AAAAAA/wAPgAAAAAH+AA+AAAAAB/wAD4AAAAAP8AAPgAAAAB/gAA+AAAAAf8AAD4AAAAD/AAAPgAAAAf4AAA+AAAAB///////4AAH///////gAAf//////+AAB///////4AAH///////gAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAADwAAAAAAQAAfgAAAAA/gAB+AAAAD/+AAH8AAAP//4AAPwAAH///gAAfgAAf//+AAB+AAB///4AAH4AAH/wPgAAPgAAfgB8AAA/AAB+AHwAAD8AAH4AfAAAPwAAfgB8AAA/AAB+AHwAAD8AAH4AfAAAPwAAfgB+AAA+AAB+AH4AAD4AAH4AfgAAfgAAfgA/AAD+AAB+AD8AAPwAAH4AP4AD/AAAfgAf4Af4AAB+AB////AAAH4AD///4AAAfAAH///AAAB8AAP//4AAAHwAAf//AAAAAAAAf/wAAAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAAAAAAAB//gAAAAAAAf//gAAAAAAH///gAAAAAA////AAAAAAP///8AAAAAB//Af4AAAAAf/wAfwAAAAD/8AA/AAAAAf/wAB+AAAAH/+AAH4AAAA/7wAAPgAAAH/PAAA/AAAB/58AAD8AAAP+HwAAPwAAB/wfAAA/AAAf+B8AAD8AAD/wHwAAPwAAf8AfAAA+AAB/gB8AAD4AAH8AH4AAfgAAfgAfgAB+AAB4AA/AAPwAAHAAD+AB/AAAYAAP+Af4AAAAAAf///AAAAAAA///8AAAAAAB///gAAAAAAD//4AAAAAAAH//AAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAAAAH4AAAAAAAAAfgAAAAAAAAB+AAAAAAAAAH4AAAAAAgAAfgAAAAAOAAB+AAAAAD4AAH4AAAAA/gAAfgAAAAP+AAB+AAAAD/4AAH4AAAA//AAAfgAAAP/4AAB+AAAD/+AAAH4AAA//gAAAfgAAP/4AAAB+AAD/+AAAAH4AA//gAAAAfgAP/4AAAAB+AB/+AAAAAH4Af/gAAAAAfgH/4AAAAAB+B/+AAAAAAH4f/gAAAAAAfn/4AAAAAAB//+AAAAAAAH//gAAAAAAAf/4AAAAAAAB/+AAAAAAAAH/gAAAAAAAAf4AAAAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAwAD/+AAAAA/8Af/8AAAAP/4D//4AAAB//4f//wAAAP//j///gAAB///P8H/AAAP////AH8AAA/gH/wAP4AAH4AH+AAfgAAfgAf4AA+AAB8AA/gAD4AAHwAD8AAPwAA+AAHwAAfAAD4AAfAAB8AAPgAB8AAHwAA+AAHwAAfAAD4AAfAAB8AAHwAD8AAPwAAfAAPwAA+AAB+AB/gAD4AAH4AH+AAfgAAP4B/8AD+AAA/8//4AfwAAB///P8H/AAAD//8///4AAAH//h///AAAAP/4D//4AAAAP/AH//AAAAAHAAP/4AAAAAAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAAP/8AAAAAAAD//4AAAAAAAf//4AAAAAAD///gAAAAAAf///AAAIAAB/gP+AABgAAP4AP4AAeAAA/AAfwAD4AAH4AA/AAfgAAfgAB8AH+AAB8AAHwA/4AAPwAAfAH/gAA/AAB8B/8AAD8AAHwP/AAAPwAAfB/4AAA/AAB8P+AAAD8AAHj/wAAAHwAAef8AAAAfgAD7/gAAAB+AAP/8AAAAD8AB//AAAAAP4AP/4AAAAAf4D/+AAAAAB////wAAAAAD///8AAAAAAH///gAAAAAAH//4AAAAAAAP/+AAAAAAAAH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAfAAAAAD8AAD+AAAAAf4AAP4AAAAB/gAB/wAAAAH+AAH/AAAAAf4AAP4AAAAA/AAA/gAAAAB4AAB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); +var scale = 1; // size multiplier for this font +g.setFontCustom(font, 46, widths, 64+(scale<<8)+(1<<16)); + +}; + + +Graphics.prototype.setFontArchitect = function() { +// Actual height 50 (52 - 3) +var widths = atob("CiEuCSolMjQuLS8qDA=="); +var font = atob("AAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAPAAAAAAAAAAAPgAAAAAAAAAAHwAAAAAAAAAAD4AAAAAAAAAAB8AAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAB8AAAAAAAAAAD+AAAAAAAAAAH+AAAAAAAAAAH+AAAAAAAAAAP8AAAAAAAAAAf8AAAAAAAAAAf8AAAAAAAAAA/8AAAAAAAAAA/4AAAAAAAAAB/4AAAAAAAAAB/4AAAAAAAAAD/wAAAAAAAAAH/wAAAAAAAAAH/gAAAAAAAAAP/AAAAAAAAAAP/AAAAAAAAAAf+AAAAAAAAAAf8AAAAAAAAAA/8AAAAAAAAAA/4AAAAAAAAAB/wAAAAAAAAAB/gAAAAAAAAAD/gAAAAAAAAAD/AAAAAAAAAAH+AAAAAAAAAAH8AAAAAAAAAAP4AAAAAAAAAAPgAAAAAAAAAAfAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAA//AAAAAAAAAH//wAAAAAAAAf//+AAAAAAAA////gAAAAAAB//wfwAAAAAAD/8AB8AAAAAAH/wAAfAAAAAAH/gAAHgAAAAAP+AAAD4AAAAAP8AAAA8AAAAAP4AAAAeAAAAAP4AAAAHgAAAAHwAAAADwAAAAHwAAAAB4AAAAHwAAAAA8AAAADwAAAAAeAAAADwAAAAAPAAAAB4AAAAAHgAAAA4AAAAADwAAAAcAAAAAB4AAAAeAAAAAA8AAAAPAAAAAAeAAAAHgAAAAAPAAAADwAAAAAHgAAAB4AAAAAHgAAAA+AAAAADwAAAAPAAAAAB4AAAAHgAAAAB4AAAADwAAAAA8AAAAA8AAAAA8AAAAAeAAAAAeAAAAAHgAAAAeAAAAAD4AAAAfAAAAAA+AAAAfAAAAAAPgAAA/AAAAAAD8AAB/AAAAAAA/wAH/AAAAAAAH////AAAAAAAB///+AAAAAAAAH//8AAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///8AAAAAf//////AAAAAP//////AAAAAH//////gAAAAD//////gAAAAB///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAADwAAABAAAAAAD4AAABwAAAAAD8AAAB8AAAAAD8AAAB+AAAAAD+AAAB/AAAAAB+AAAB/gAAAAB+AAAB/wAAAAB+AAAD/4AAAAA+AAAD/4AAAAAfAAAD/8AAAAAfAAAD/+AAAAAPAAAD/fAAAAAHgAAH/PgAAAAHwAAH/HgAAAADwAAP+DwAAAAB4AAP+B4AAAAA8AAf+A8AAAAAfAAf8A8AAAAAPgA/8AeAAAAAD4D/4APAAAAAB///4APgAAAAAf//wAHgAAAAAP//AADwAAAAAD/+AAD4AAAAAAPwAAB8AAAAAAAAAAA8AAAAAAAAAAA+AAAAAAAAAAAfAAAAAAAAAAAPgAAAAAAAAAAPwAAAAAAAAAAHwAAAAAAAAAAD4AAAAAAAAAAD8AAAAAAAAAAB+AAAAAAAAAAA/AAAAAAAAAAAfgAAAAAAAAAAPgAAAAAAAAAAHwAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAPAAAAAAAAAAAHgAAAAAAAAAAHwAAAAAAAAAAD4AAAAAAAAAAB8AAAAAAAAAAA+ABgAAAAAAAA+AA4AAAAAAAAfAA+AAAAAAAAPgAfAAIAAAAAHwAfAAGAAAAAHwAPgAHAAAAAD4APgADgAAAAB8AHwABwAAAAA+AH4AB4AAAAAeAD4AA8AAAAAfAD8AA+AAAAAPgB+AAeAAAAAHwB+AAfAAAAAD4A/AAPgAAAAB4A/gAPgAAAAA8A/wAPwAAAAAeAf4APwAAAAAPAf8AHwAAAAAHgf+AH4AAAAAD4fvAP4AAAAAB8fngP4AAAAAA//j4f4AAAAAAf/h//8AAAAAAP/g//8AAAAAAD/gP/4AAAAAAB/gH/4AAAAAAAfAB/4AAAAAAAEAAfwAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAeAAAAAAAAAAAfAAAAAAAAAAA/gAAAAAAAAAA/wAAAAAAAAAA/4AAAAAAAAAB/8AAAAAAAAAB/+AAAAAAAAAB/PAAAAAAAAAD/HgAAAAAAAAD+DwAAAAAAAAD+B4AAAAAAAAH+A8AAAAAAAAH+AeAAAAAAAAH+APAAAAAAAAH+APgAAAAAAAH+AHwAAAAAAAH+AD4AAAAAAAH+AB8AAAAAAAH+AA+AAAAAAAH+AAfAAAAAAAD+AAPgf4AAAAD//////+AAAAB///////AAAAAf//////gAAAAH//////wAAAAA//////4AAAAAB/////4AAAAAAAAP4AAAAAAAAAAHwAAAAAAAAAAD4AAAAAAAAAAB8AAAAAAAAAAA+AAAAAAAAAAAfAAAAAAAAAAAPAAAAAAAAAAAHgAAAAAAAAAADwAAAAAAAAAAB4AAAAAAAAAAA8AAAAAAAAAAAeAAAAAAAAAAAPAAAAAAAAAAAHgAAAAAAAAAADwAAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAAB//+AAAAAAAB////AAAAAAAA////gAAAAAAAf///wAAAAAAAP///4AAAAAAAH///8AAAAAAAAH4B8AAfAAAAAD8A+AAfgAAAAB8AfAAPwAAAAA+APAAH4AAAAAfAHgAB8AAAAAPgDwAA+AAAAAHwBwAAfAAAAAHwA4AAPgAAAAD4A8AAHwAAAAB8AcAAD4AAAAA+AOAAD8AAAAAfAHAAB8AAAAAfADgAA+AAAAAPgBwAAfAAAAAHwA4AAPgAAAAD4AcAAHgAAAAD4APAAHwAAAAB8AHgAD4AAAAA+ADwAD4AAAAAfAB4AB8AAAAAfgA+AB8AAAAAPgAPgB+AAAAAHwAH4B+AAAAAD4AD/n+AAAAAD8AA//+AAAAAB+AAP/+AAAAAA+AAD/+AAAAAAfAAA/+AAAAAAPgAADwAAAAAAPwAAAAAAAAAAH4AAAAAAAAAAD4AAAAAAAAAAB8AAAAAAAAAAA+AAAAAAAAAAAfAAAAAAAAAAAfAAAAAAAAAAAPgAAAAAAAAAAHwAAAAAAAAAAD4AAAAAAAAAAB4AAAAAAAAAAA8AAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAAAB//wAAAAAAAAH//+AAAAAAAAP///wAAAAAAAP///8AAAAAAAf////AAAAAAAf////wAAAAAA//AB/8AAAAAA/8AAP/AAAAAA/4AAB/wAAAAA/wAAAP4AAAAA/wAAAD+AAAAA/gAAAB/AAAAAfgAAAA/wAAAAfgAAAA/4AAAAfgAAAA/8AAAAPgAAAA//AAAAPwAAAA//gAAAHwAAAA//wAAADwAAAA/34AAAD4AAAA/z8AAAB4AAAA/x+AAAA8AAAA/w/AAAAeAAAAfwfgAAAOAAAAfwPwAAAHAAAAfwH4AAABgAAAfwH8AAAAAAAAPwD+AAAAAAAAP4B+AAAAAAAAH4A/AAAAAAAAH4A/gAAAAAAAD8AfgAAAAAAAB8AfwAAAAAAAB+AfwAAAAAAAA+AP4AAAAAAAAfAP4AAAAAAAAPgP4AAAAAAAAHwf4AAAAAAAAD//4AAAAAAAAB//4AAAAAAAAAf/4AAAAAAAAAP/wAAAAAAAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAA4AAAAAAAAAAAcAAAAAAAAAAAOAAAAAAAAAAAHAAAAAAAAAAADgAAAAAAAAAABwAAAAAAAAAAA4AAAAAAAAAAAcAAAAAAAAAAAeAAAAAAAAAAAPAAAAAAAAAAAHgAAAAAAAAAADwAAAAAAAAAAB4AAAAAAAAAAA8AAAAAAAAAAAeAAAAAAAAAAAPAAAAAAAAAAAPgAAAAAAAAAAHwAAAAAAAAAAD4AAAAAAAAAAB8AAAAAAAAAAA+AAAD/4AAAAAfAAAP/8AAAAAfgAA//+AAAAAPwAB///AAAAAH4AD///AAAAAD8AH///gAAAAB+AP/8AAAAAAA/Af/gAAAAAAAfg/+AAAAAAAAfz/8AAAAAAAAP//4AAAAAAAAH//wAAAAAAAAD//gAAAAAAAAB//AAAAAAAAAA//AAAAAAAAAAf+AAAAAAAAAAP+AAAAAAAAAAH+AAAAAAAAAAD8AAAAAAAAAAB8AAAAAAAAAAA8AAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAP8AAAAAAAAAAf/AAAAAAAAAAf/wAAAAAAAAAf/8AAAAAAB8A//+AAAAAAD/g///gAAAAAD///gPwAAAAAD///gD8AAAAAD///gA+AAAAAD///gAPAAAAAB/P/gAHwAAAAB+B/gAD4AAAAA+AfgAA8AAAAA+APwAAeAAAAAeAHwAAPAAAAAfAD4AAHgAAAAPAB4AADwAAAAHgA8AAB4AAAAHgAeAAA8AAAADwAOAAAeAAAAB4AHAAAPAAAAA8AHgAAHgAAAAcADwAADwAAAAOABwAAB4AAAAPAB4AAA4AAAAHgA8AAA8AAAADwAeAAAeAAAAB4AfAAAPAAAAA8APgAAHAAAAAeAHwAAHgAAAAPgH4AADwAAAADwD8AADwAAAAB4D+AAD4AAAAA8D/gAB4AAAAAfB/wAB8AAAAAPh/8AB8AAAAAD//+AD8AAAAAB/+/gH8AAAAAAf+P8/8AAAAAAD8H//8AAAAAAAAB//8AAAAAAAAAf/8AAAAAAAAAH/4AAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAAAAf4AAAAAAAAAA/+AAAAAAAAAA//AAAAAAAAAA//wAAAAAAAAA//4AAAAAAAAA//+AAAAAAAAAf4/AAAAAAAAAfgfgAAAAAAAAfgHwAAAAAAAAPgD4AAAAAAAAPgB8AAAAAAAAPgA+AAAAAAAAHwAfAAAwAAAADwAPgAAeAAAAD4AHwAAfAAAAB8AD4AAPgAAAB+AB8AAPwAAAA+AA+AAP4AAAAfAAeAAP4AAAAPgAPAAH8AAAAHwAHgAH8AAAAD4AHwAH+AAAAB8AD4AH+AAAAA/AB8AH+AAAAAPgA8AP+AAAAAH4A+AP+AAAAAB+AfAf+AAAAAA/gPh/+AAAAAAP////8AAAAAAD////8AAAAAAA////4AAAAAAAP///wAAAAAAAD///gAAAAAAAAf/+AAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAPAAAAAAAAAAAHgAAAAAAAAAADwAMAAAAAAAAB4APAAAAAAAAA8APgAAAAAAAAEAHwAAAAAAAAAAD4AAAAAAAAAAA4AAAAAAAAAAAAAAAA=="); +var scale = 1; // size multiplier for this font +g.setFontCustom(font, 46, widths, 73+(scale<<8)+(1<<16)); + +}; + + +function draw() { + var d = new Date(); + var da = d.toString().split(" "); + var time = da[4].substr(0,5); + + var hh = da[4].substr(0,2); + var mm = da[4].substr(3,2); + var day = da[0]; + var month_day = da[1] + " " + da[2]; + + // fix hh for 12hr clock + var h2 = "0" + parseInt(hh) % 12 || 12; + hh = h2.substr(h2.length -2); + + var w = g.getWidth(); + var h = g.getHeight(); + var x = (g.getWidth()/2); + var y = (g.getHeight()/3); + + g.reset(); + g.clearRect(0, 30, w, h); + + // draw a grid like graph paper + g.setColor("#0f0"); + for (var gx=20; gx <= w; gx += 20) + g.drawLine(gx, 30, gx, h); + for (var gy=30; gy <= h; gy += 20) + g.drawLine(0, gy, w, gy); + + g.setColor("#000"); + + g.setFontLato(); + //g.setFontArchitect(); + //g.setFontGochiHand(); + //g.setFontCabinSketch(); + g.setFontAlign(1,-1); // right aligned + g.drawString(hh, x - 6, y); + g.setFontAlign(-1,-1); // left aligned + g.drawString(mm ,x + 6, y); + + // for the colon + g.setFontAlign(0,-1); // centre aligned + if (d.getSeconds()&1) g.drawString(":", x,y); + + g.setFontLatoSmall(); + g.setFontAlign(1, -1); + g.drawString(day + " ", w, h - 24 - 24); + g.drawString(month_day + " ", w, h - 24); +} + +// handle switch display on by pressing BTN1 +Bangle.on('lcdPower', function(on) { + if (on) draw(); +}); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +setInterval(draw, 1000); // refresh every second +draw(); +// Show launcher when button pressed +Bangle.setUI("clock"); + diff --git a/apps/pastel/pastel.icon.js b/apps/pastel/pastel.icon.js new file mode 100644 index 000000000..dfa6b7361 --- /dev/null +++ b/apps/pastel/pastel.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("ADMH8AFDj/4Aoc//gFDv/+Aof8n4FD+EPAgUBAoOAAoXgg4FCj4FBFQX/AoP/CAP/8fH/+Agf/+fP//Ag/jAoPHCgPA/ffEwIsBv//GQRHDAoMBKYOAAoIkBEQIFHw4FB8Hwg+BAoOBF4PgAoIDBCwQYCJoIACMAIFDQQQEBQgV+HYN+AoN8I4M+AoP4AoMfRwV77yQC+ET5AFDgfAApH4IoIdCvkP+ApCvzLBGoU/IILMCj/+v6tCh7LB+AFBgZ6B4D/SAAoA==")) diff --git a/apps/pastel/pastel.info.js b/apps/pastel/pastel.info.js new file mode 100644 index 000000000..70eec47dd --- /dev/null +++ b/apps/pastel/pastel.info.js @@ -0,0 +1,7 @@ +require("Storage").write("pastel.info",{ + "id":"pastel", + "name":"Configurable clock with custom fonts and background", + "src":"pastel.app.js", + "icon":"pastel.img", + "type":"clock" +}); diff --git a/apps/pastel/pastel.png b/apps/pastel/pastel.png new file mode 100644 index 0000000000000000000000000000000000000000..8f616dc6c7fabec4a0c47aa8fc09761cc43b85ea GIT binary patch literal 1123 zcmV-p1f2VcP)(&BalbASoX<^o?Byq{egHNz4^ z)SChRI)lG(lr;wNIqA%2VUP0JHEQZ2@Rt(vLWIwY%IA+F0!op*M}b9%z!qd&p9eaD zF&?iGsrw-`B9lJB2(Z_+dkr`Td=GpLT!_?H5tD$Afg$25%jAeQr$n|j1bhTE-KqE| zfRmA(CAP%#*WVIY6F+1pot9jfij2*?wf!ea^zw0Br!c0?s zJuv}XRvk^P!0QCw16r|%Qpl2_)9Tvj4CwI}%gZqV+)$m3ufRL%i8&1H&k?|%&L_$4 z(SRPml;4O6;H>KGS%WhxhuBz~W%ScTc|!X%!P?3mQ_rs(oYls=qOjUrOL+m&+DxpZ zJToJ}tBP|UQn$YWhPm^s4n>A&)l&mDS z^e`%f@uchVGR2tyZ1=vO0wx+_C%SLR^LFR!W2y4_t!MzPhLB1VYZX$lo^sFH5BII# zaV<%1_N9m`(E-4=`HN+0RNi%+h%-gQydG=tLMV^ zLI7VK|u${RWVKSydG?kVzdY zqTl(<$Qi@{a+nzvJdC~MYx@d#%=2qOZkx0GjxY+Dx6lTBf~=H-NY;9QP1q$g!E?Z; pjyr%{E1p8CP1PoB9d+DG_#Z<{6w%j?;|c%(002ovPDHLkV1nLw4+sDN literal 0 HcmV?d00001 From 440662f068f30eb72068998bec139dede8851228 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 13 Sep 2021 21:38:19 +0100 Subject: [PATCH 0005/1062] Added pastel clock for bangle 2 to apps.json --- apps.json | 14 ++++++++++++++ apps/pastel/README.md | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 560d6505b..59df27bc6 100644 --- a/apps.json +++ b/apps.json @@ -3442,5 +3442,19 @@ "data": [ {"name":"app.json"} ] +}, +{ "id": "pastel", + "name": "Pastel Clock", + "shortName":"Pastel", + "icon": "pastel.png", + "version":"0.01", + "description": "A nixie tube clock for both Bangle 1 and 2.", + "tags": "clock b2", + "type":"clock", + "readme": "README.md", + "storage": [ + {"name":"pastel.app.js","url":"pastel.app.js"}, + {"name":"pastel.img","url":"pastel.icon.js","evaluate":true} + ] } ] diff --git a/apps/pastel/README.md b/apps/pastel/README.md index 26486b990..9a227f51d 100644 --- a/apps/pastel/README.md +++ b/apps/pastel/README.md @@ -1,4 +1,4 @@ # Pastel Clock -A Configurable clock with custom fonts and background -Designed specifically for bangle 2 +A Configurable clock with custom fonts and background. Designed +specifically for bangle 2 From 442950b83d278c9a1c94ad7465034fb4e5b26a4a Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 13 Sep 2021 21:43:03 +0100 Subject: [PATCH 0006/1062] updated apps.json description --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 59df27bc6..ebbd68b2e 100644 --- a/apps.json +++ b/apps.json @@ -3445,10 +3445,10 @@ }, { "id": "pastel", "name": "Pastel Clock", - "shortName":"Pastel", + "shortName": "Pastel", "icon": "pastel.png", "version":"0.01", - "description": "A nixie tube clock for both Bangle 1 and 2.", + "description": "A Configurable clock with custom fonts and background", "tags": "clock b2", "type":"clock", "readme": "README.md", From fc8718a38ea652c1ebfbb3b102e8dd783f46833a Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 13 Sep 2021 21:49:45 +0100 Subject: [PATCH 0007/1062] updated pastel.icon.js --- apps/pastel/pastel.icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/pastel/pastel.icon.js b/apps/pastel/pastel.icon.js index dfa6b7361..63fd1707b 100644 --- a/apps/pastel/pastel.icon.js +++ b/apps/pastel/pastel.icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("ADMH8AFDj/4Aoc//gFDv/+Aof8n4FD+EPAgUBAoOAAoXgg4FCj4FBFQX/AoP/CAP/8fH/+Agf/+fP//Ag/jAoPHCgPA/ffEwIsBv//GQRHDAoMBKYOAAoIkBEQIFHw4FB8Hwg+BAoOBF4PgAoIDBCwQYCJoIACMAIFDQQQEBQgV+HYN+AoN8I4M+AoP4AoMfRwV77yQC+ET5AFDgfAApH4IoIdCvkP+ApCvzLBGoU/IILMCj/+v6tCh7LB+AFBgZ6B4D/SAAoA==")) +require("heatshrink").decompress(atob("/4AY/EfAofgg4FD4EDAoeAgIFDgfAAocP+AEC/wFBv4FCj/4AoXgAoIqCgAFBKQP+gEcnEAv/8gEMmEAn/4jgFBnAUBn8EkAmBFgOAgAyCI4YFB/xTBv4FBEgIiBAo94AoMfh/4vwFBvwvBj4FBAYIWCDARNBAARgBVgpABgCECwI7BwIFBwZHB4IFBg4FB8COCwkhSAUP9k3Aof8n4FIg5FBDoWD+EPFIWBZYI1C4BBBZgXggOAVoXwZYMPAoP8PQM/f6QAFA==")) From a626759114dfb11ea2f1bdbfd2401994701a1cb5 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 13 Sep 2021 22:14:39 +0100 Subject: [PATCH 0008/1062] updated pastel.icon.js again --- apps/pastel/pastel.icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/pastel/pastel.icon.js b/apps/pastel/pastel.icon.js index 63fd1707b..e9558d44e 100644 --- a/apps/pastel/pastel.icon.js +++ b/apps/pastel/pastel.icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("/4AY/EfAofgg4FD4EDAoeAgIFDgfAAocP+AEC/wFBv4FCj/4AoXgAoIqCgAFBKQP+gEcnEAv/8gEMmEAn/4jgFBnAUBn8EkAmBFgOAgAyCI4YFB/xTBv4FBEgIiBAo94AoMfh/4vwFBvwvBj4FBAYIWCDARNBAARgBVgpABgCECwI7BwIFBwZHB4IFBg4FB8COCwkhSAUP9k3Aof8n4FIg5FBDoWD+EPFIWBZYI1C4BBBZgXggOAVoXwZYMPAoP8PQM/f6QAFA==")) +require("heatshrink").decompress(atob("mEw4MA///ELcH8AFDj/4Aoc//gFDv/+Aof8n4FD+EPAgUBAoOAAoXgg4FCj4FBFQX/AoJUBgP/8fH/+Agf/+fP//Ag/jAoPHCgPA/ffEwIsBv//GQRHDAoMBKYOAAoIkBEQIFHw4FB8Hwg+BAoOBF4PgAoIDBCwQYCJoIACMAIFDQQQEBQgV+HYN+AoN8I4M+AoP4AoMfRwV77yQC+ET5AFDgfAApH4IoIdCvkP+ApCvzLBGoU/IILMCj/+v6tCh7LB+AFBgZ6B4D/SAAoA=")) From 6f0d0303e063fac07d73a661fcb77a6bc3b958a6 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 13 Sep 2021 22:32:30 +0100 Subject: [PATCH 0009/1062] updated pastel.icon.js again 2 --- apps/pastel/pastel.icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/pastel/pastel.icon.js b/apps/pastel/pastel.icon.js index e9558d44e..c7360b401 100644 --- a/apps/pastel/pastel.icon.js +++ b/apps/pastel/pastel.icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEw4MA///ELcH8AFDj/4Aoc//gFDv/+Aof8n4FD+EPAgUBAoOAAoXgg4FCj4FBFQX/AoJUBgP/8fH/+Agf/+fP//Ag/jAoPHCgPA/ffEwIsBv//GQRHDAoMBKYOAAoIkBEQIFHw4FB8Hwg+BAoOBF4PgAoIDBCwQYCJoIACMAIFDQQQEBQgV+HYN+AoN8I4M+AoP4AoMfRwV77yQC+ET5AFDgfAApH4IoIdCvkP+ApCvzLBGoU/IILMCj/+v6tCh7LB+AFBgZ6B4D/SAAoA=")) +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AUbwottGNAuJGEguLGEQmJGEojLF8RTMMEQhNF7rjRerQaHF6gyRDJQaDAxQwUDBYaDApIwUK5gvPD5YvLBRYEIEg4vMHpiENF5YiMZTAjSI5YwNCAgvaRhzXOF/YMEFyYmGZixejEo4wUD563ZNCowoFxgwhFxwwfFyDTJExwvcM6AYKFyovRGCoTIBAgEJDZgvTBQQNHCBAvdBrwv5ChDrJd7AVLFwoNMFyBTKFw4NLF6ImKACIuSGBoNPGDwNRGLYNVAH4A/AH4AFA=")) From b06ce95c7bbbed07f4e2cf0846badea2f7864625 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 13 Sep 2021 22:38:58 +0100 Subject: [PATCH 0010/1062] updated pastel.icon.js again 3 --- apps/pastel/pastel.icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/pastel/pastel.icon.js b/apps/pastel/pastel.icon.js index c7360b401..380fc9c27 100644 --- a/apps/pastel/pastel.icon.js +++ b/apps/pastel/pastel.icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AUbwottGNAuJGEguLGEQmJGEojLF8RTMMEQhNF7rjRerQaHF6gyRDJQaDAxQwUDBYaDApIwUK5gvPD5YvLBRYEIEg4vMHpiENF5YiMZTAjSI5YwNCAgvaRhzXOF/YMEFyYmGZixejEo4wUD563ZNCowoFxgwhFxwwfFyDTJExwvcM6AYKFyovRGCoTIBAgEJDZgvTBQQNHCBAvdBrwv5ChDrJd7AVLFwoNMFyBTKFw4NLF6ImKACIuSGBoNPGDwNRGLYNVAH4A/AH4AFA=")) +require("heatshrink").decompress(atob("mEwhH+AH4A/AH4AT64AEFtoxoFxIwkFxYwiExIwlEZYviKZhgiEJovdcaL1aDQ4vUGSIZKDQYGKGCgYLDQYFJGChXMF54fLF5YKLAhAkHF5g9MQhovLERjKYEaRHLGBoQEF7SMOa5wv7BgguTEwzMWL0YlHGCgfPW7JoVGFAuMGEIuOGD4uQaZImOF7hnQDBQuVF6IwVCZAIEAhIbMF6YKCBo4QIF7oNeF/IUIdZLvYCpYuFBpguQKZQuHBpYvRExQARFyQwNBp4weBqIxbBqoA/AH4A/AAo")) From 5167f62d17b5a62e28375a324251bf25c74603eb Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 13 Sep 2021 23:16:30 +0100 Subject: [PATCH 0011/1062] updated pastel.icon.js again 4 --- apps/pastel/pastel.icon.js | 2 +- apps/pastel/pastel.png | Bin 1123 -> 3101 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/pastel/pastel.icon.js b/apps/pastel/pastel.icon.js index 380fc9c27..1140680b7 100644 --- a/apps/pastel/pastel.icon.js +++ b/apps/pastel/pastel.icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwhH+AH4A/AH4AT64AEFtoxoFxIwkFxYwiExIwlEZYviKZhgiEJovdcaL1aDQ4vUGSIZKDQYGKGCgYLDQYFJGChXMF54fLF5YKLAhAkHF5g9MQhovLERjKYEaRHLGBoQEF7SMOa5wv7BgguTEwzMWL0YlHGCgfPW7JoVGFAuMGEIuOGD4uQaZImOF7hnQDBQuVF6IwVCZAIEAhIbMF6YKCBo4QIF7oNeF/IUIdZLvYCpYuFBpguQKZQuHBpYvRExQARFyQwNBp4weBqIxbBqoA/AH4A/AAo")) +require("heatshrink").decompress(atob("mEw4cA///kf0BIPi1Uzu2D6ej7dgjA/riQPOhMk3foCBkC9u2pMtCJkTkmSpMl0ARL3IRCkiCCABOxCIeUCBUB5IRDy2ACJMECImcCKGSCL+fxMkzWAgMkyARGgoRBzwCBy2Cr+679ACAicBBwOXCIWTHYXgCAeXtu2GQOcAQOeSodoCAUkDoOT7ILBzMk6oUBCIssMoOTqYCB/Mk/xuCCIkuBAVasmS/Mn+QRHRgdLpgLBntiCIcoag+pAQPriVJ5vSCIgjD705vVPrGa/VL2VJngRCkxZC/Vk+mSkul7o1GkQ+CpvE//fl4gBLIwjCcwWmvRuBBAVLYob4EGQP7NwQ+B3TXEVoILB3s21OrCIW+3/pCIcXF4NfrjpBneu7f9l4lB0ARCgVL/27kmfHYO//9JvPSyUkEgcJ21Jk2U5/EGoNqumkyVECIccBYPf8lP8gFBn/F6VKp2ACIv831LohxCos6+VECI3k+1JCIUn+sirw1HkmqpNYCImL4gRGk2iXIcn4siyN+GocKIIXyCIclp8iyUoCIcBlwRGn/Jk+SvARDbQIRBGohMBAgXACInNvsmBgPn/9LegYREgGSoEFGAOkwEBvMir/JCIoACy71DksigMkI4gADgVAFggOHAH4AmA=")) diff --git a/apps/pastel/pastel.png b/apps/pastel/pastel.png index 8f616dc6c7fabec4a0c47aa8fc09761cc43b85ea..06961f5d2db4c8a522fcf6d367adff6f80ebf95a 100644 GIT binary patch delta 3078 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 U>tzg5Y5)KL07*qoM6N<$f+a!a#{d8T delta 1085 zcmV-D1j75B7~=?#HGc#cNkl+ZewW1x;Y>Zmq4fUUr3;D4&K(?B<{poY3KF&+33 zxRqf)08iFPV@0$BSI8Eh*cITVnkY5!3@}K%ct41zYoNCb4Zv~I;&uabfCC9(gkMh|yYU(2JmlE?rgwKo0=Z_)+N|C%rfklYG z7GzwX2ReZ<9)GV9srw-`B9lJB2(Z_+dkr`Td=GpLT!_?H5tD$Afg$25%jAeQr$n|j z1bhTE-KqE|fRmA(CAP%#*WVIY6F+1pot9jfij2*?wf!eIrq0{Qx=nUxb7t6~r0o+iXjjzBv>WMiF z?9UOvpMTCL$?nmB9>0{|hza1V>g-vAGb@MKSes?^(?oef`!vDY${thCuN$1z#=D}h z+FVO{0nyq_tfV|MBfzVQb01Q-zX681^Q;de`=G_HO&{}t>x#c3CV;V|SK(pcec&OF zHwBmnd|-4GV?)4)$Z^y!6wSa}z<-MWyVDkh1%KF?9Y$^#^gOACopWth>bE^se1bQy z>-pWb5$t&wg{9d0ox5chk@-;w4+2{DS^e`%f@uchVGR2tyZ1=vO0wx+_C%SLR^LFR!W2y4_t!MzPhLB1V zYZX$lo^sFH5BII#aV<%1_N9m`(E-4=`HN+0RNi% z+h%-gQydG=tLMV^LI7VN9n+)>I8G$X#gO#p)kt zdHn{Ee_2%(GmuFgE27`|%*YwU0CJca6+Dc+ Date: Mon, 13 Sep 2021 23:50:04 +0100 Subject: [PATCH 0012/1062] updated pastel.icon.js again 5 --- apps.json | 2 +- apps/pastel/pastel.icon.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index ebbd68b2e..a75ff0e5b 100644 --- a/apps.json +++ b/apps.json @@ -3449,7 +3449,7 @@ "icon": "pastel.png", "version":"0.01", "description": "A Configurable clock with custom fonts and background", - "tags": "clock b2", + "tags": "clock,b2", "type":"clock", "readme": "README.md", "storage": [ diff --git a/apps/pastel/pastel.icon.js b/apps/pastel/pastel.icon.js index 1140680b7..44bdcef9e 100644 --- a/apps/pastel/pastel.icon.js +++ b/apps/pastel/pastel.icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEw4cA///kf0BIPi1Uzu2D6ej7dgjA/riQPOhMk3foCBkC9u2pMtCJkTkmSpMl0ARL3IRCkiCCABOxCIeUCBUB5IRDy2ACJMECImcCKGSCL+fxMkzWAgMkyARGgoRBzwCBy2Cr+679ACAicBBwOXCIWTHYXgCAeXtu2GQOcAQOeSodoCAUkDoOT7ILBzMk6oUBCIssMoOTqYCB/Mk/xuCCIkuBAVasmS/Mn+QRHRgdLpgLBntiCIcoag+pAQPriVJ5vSCIgjD705vVPrGa/VL2VJngRCkxZC/Vk+mSkul7o1GkQ+CpvE//fl4gBLIwjCcwWmvRuBBAVLYob4EGQP7NwQ+B3TXEVoILB3s21OrCIW+3/pCIcXF4NfrjpBneu7f9l4lB0ARCgVL/27kmfHYO//9JvPSyUkEgcJ21Jk2U5/EGoNqumkyVECIccBYPf8lP8gFBn/F6VKp2ACIv831LohxCos6+VECI3k+1JCIUn+sirw1HkmqpNYCImL4gRGk2iXIcn4siyN+GocKIIXyCIclp8iyUoCIcBlwRGn/Jk+SvARDbQIRBGohMBAgXACInNvsmBgPn/9LegYREgGSoEFGAOkwEBvMir/JCIoACy71DksigMkI4gADgVAFggOHAH4AmA=")) +require("heatshrink").decompress(atob("mEwxH+AH4A1PYIqqAAUqp0kGMopCuVOvHHAAV4GEcAFQgAFuQwhLoIuJAAMqGEEAzovL44vgkguM49OGD0ApwvNzovfdpSQjF+FyEwskewyPFgAADX7kkA414EwIqCp14zmcuUkGJ5FEkgnFpwHHFgXBTQ0kFhpSBvAABD4KHHEY0ATAw7EFpTnHRA8Azg2FFxIvMCxGcHI0qG4kAlQuJF5gXJcIIwEvARDlSVGfZAwJuQWKSQwuCRpQAB4IvK/0kC5K0BGAouHzucvAIFL5bvHvAbCGAMkEAQuEzi0BAAZ9FX5QuH48kDgILB4IFCAAPB4IsCklySZIvKUxWdLYYABpyhBuUkIxAADHoJfSJI140SOBzgTNFxQwPQ4QuBAgK1FOoyiCF5QwBpwaIzgZBzlydgT2BCZGcHwMrmMHGJjnDAAd4JAImCvDRDGAIKBvF4uTJBA4MrrtjrljrowOAAIZCEQPBFQTuBHgpXBCYkGnBcCmVjsaTOJYOcFgZ+BeAXHkiRBAAprBrkxFAM4MAMyMgIvMkjaGKQICBMQUqfxInBlZ+DMIIGBF5QgGgAHBWQLCGAAnBgC5BFIUrR4KQNF4wGCki+DgCQHBwMySQNcFQMHSQKPTFQ4vJlS/BrhaBMgIwBd5ofHXwYALXALuCGQcxHAIvLznBD4q6JX41jmIvCdoKOBMB0ASQtyTJIADp0GdIVjgwvBMYQvMGISzEL5ucRIQAIg4vOzmdzpdBM4NOZA1OlUqBoUrFIZeCAAUrF5qSCAAIGDlSIEBgoNBGAVdBQMGmJoBgwvOG5JZBFgoNFLoIMCJgwxXO5wpYAH4A/AH4A/ABwA=")) From 63b19409f44d331793eaf3c08290271fd6c28262 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 14 Sep 2021 00:50:52 +0100 Subject: [PATCH 0013/1062] modified to work with bangle 1 as well as 2 --- apps/pastel/pastel.app.js | 42 +++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 985c5f045..42c8d0f96 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -45,6 +45,7 @@ g.setFontCustom(font, 46, widths, 73+(scale<<8)+(1<<16)); }; +var mm_prev = "xx"; function draw() { var d = new Date(); @@ -66,16 +67,31 @@ function draw() { var y = (g.getHeight()/3); g.reset(); - g.clearRect(0, 30, w, h); + if (process.env.HWVERSION == 1) { + // avoid flicker on a bangle 1 by comparing with previous minute + if (mm_prev != mm) { + mm_prev = mm; + g.clearRect(0, 30, w, h); + } + } else { + // on a b2 safe to just clear anyway as there is no flicker + g.clearRect(0, 30, w, h); + } + // draw a grid like graph paper - g.setColor("#0f0"); - for (var gx=20; gx <= w; gx += 20) - g.drawLine(gx, 30, gx, h); - for (var gy=30; gy <= h; gy += 20) - g.drawLine(0, gy, w, gy); + if (process.env.HWVERSION !=1) { + g.setColor("#0f0"); + for (var gx=20; gx <= w; gx += 20) + g.drawLine(gx, 30, gx, h); + for (var gy=30; gy <= h; gy += 20) + g.drawLine(0, gy, w, gy); + } - g.setColor("#000"); + if (process.env.HWVERSION ==1) + g.setColor("#fff"); + else + g.setColor("#000"); g.setFontLato(); //g.setFontArchitect(); @@ -88,7 +104,17 @@ function draw() { // for the colon g.setFontAlign(0,-1); // centre aligned - if (d.getSeconds()&1) g.drawString(":", x,y); + + if (d.getSeconds()&1) { + g.drawString(":", x,y); + } else { + // on bangle 1, we are not using clearRect(), hide : by printing over it in reverse color + if (process.env.HWVERSION ==1) { + g.setColor("#000") + g.drawString(":", x,y); + g.setColor("#fff"); + } + } g.setFontLatoSmall(); g.setFontAlign(1, -1); From e455c3616ccd83dac53ee5c570cea428d925a95b Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 14 Sep 2021 22:01:20 +0100 Subject: [PATCH 0014/1062] Added settings menu for Pastel --- apps.json | 6 +++- apps/pastel/pastel.app.js | 40 ++++++++++++++++++------ apps/pastel/pastel.settings.js | 57 ++++++++++++++++++++++++++++++++++ apps/pastel/test-settings.js | 6 ++++ 4 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 apps/pastel/pastel.settings.js create mode 100644 apps/pastel/test-settings.js diff --git a/apps.json b/apps.json index a75ff0e5b..a09d18e12 100644 --- a/apps.json +++ b/apps.json @@ -3454,7 +3454,11 @@ "readme": "README.md", "storage": [ {"name":"pastel.app.js","url":"pastel.app.js"}, - {"name":"pastel.img","url":"pastel.icon.js","evaluate":true} + {"name":"pastel.img","url":"pastel.icon.js","evaluate":true}, + {"name":"pastel.settings.js","url":"pastel.settings.js"} + ], + "data": [ + {"name":"pastel.json"} ] } ] diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 42c8d0f96..96ac67766 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -45,6 +45,18 @@ g.setFontCustom(font, 46, widths, 73+(scale<<8)+(1<<16)); }; +const SETTINGS_FILE = "pastel.json"; +let settings = undefined; + +function loadSettings() { + //console.log("loadSettings()"); + settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; + settings.grid = settings.grid||false; + settings.date = settings.date||false; + settings.font = settings.font||"Lato"; + //console.log(settings); +} + var mm_prev = "xx"; function draw() { @@ -80,7 +92,7 @@ function draw() { } // draw a grid like graph paper - if (process.env.HWVERSION !=1) { + if (settings.grid && process.env.HWVERSION !=1) { g.setColor("#0f0"); for (var gx=20; gx <= w; gx += 20) g.drawLine(gx, 30, gx, h); @@ -93,10 +105,15 @@ function draw() { else g.setColor("#000"); - g.setFontLato(); - //g.setFontArchitect(); - //g.setFontGochiHand(); - //g.setFontCabinSketch(); + if (settings.font == "Architect") + g.setFontArchitect(); + else if (settings.font == "GochiHand") + g.setFontGochiHand(); + else if (settings.font == "CabinSketch") + g.setFontCabinSketch(); + else + g.setFontLato(); + g.setFontAlign(1,-1); // right aligned g.drawString(hh, x - 6, y); g.setFontAlign(-1,-1); // left aligned @@ -115,11 +132,13 @@ function draw() { g.setColor("#fff"); } } - - g.setFontLatoSmall(); - g.setFontAlign(1, -1); - g.drawString(day + " ", w, h - 24 - 24); - g.drawString(month_day + " ", w, h - 24); + + if (settings.date) { + g.setFontLatoSmall(); + g.setFontAlign(1, -1); + g.drawString(day + " ", w, h - 24 - 24); + g.drawString(month_day + " ", w, h - 24); + } } // handle switch display on by pressing BTN1 @@ -130,6 +149,7 @@ Bangle.on('lcdPower', function(on) { g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); +loadSettings(); setInterval(draw, 1000); // refresh every second draw(); // Show launcher when button pressed diff --git a/apps/pastel/pastel.settings.js b/apps/pastel/pastel.settings.js new file mode 100644 index 000000000..c6870cd71 --- /dev/null +++ b/apps/pastel/pastel.settings.js @@ -0,0 +1,57 @@ +(function(back) { + const SETTINGS_FILE = "pastel.json"; + + // initialize with default settings... + let s = { + 'grid': false, + 'date': false, + 'font': "Lato" + } + + // ...and overwrite them with any saved values + // This way saved values are preserved if a new version adds more settings + const storage = require('Storage') + const settings = storage.readJSON(SETTINGS_FILE, 1) || {} + const saved = settings || {} + for (const key in saved) { + s[key] = saved[key] + } + + function save() { + settings = s + storage.write(SETTINGS_FILE, settings) + } + + var font_options = ["Lato","Architect","GochiHand","CabinSketch"]; + + E.showMenu({ + '': { 'title': 'Pastel Clock' }, + '< Back': back, + 'Font': { + value: 0 | font_options.indexOf(s.font), + min: 0, max: 3, + format: v => font_options[v], + onchange: v => { + s.font = font_options[v]; + save(); + }, + }, + 'Show Grid': { + value: s.grid, + format: () => (s.grid ? 'Yes' : 'No'), + onchange: () => { + s.grid = !s.grid + save() + }, + }, + 'Show Date': { + value: s.date, + format: () => (s.date ? 'Yes' : 'No'), + onchange: () => { + s.date = !s.date + save() + }, + }, + '< Back': back, + }) +}) diff --git a/apps/pastel/test-settings.js b/apps/pastel/test-settings.js new file mode 100644 index 000000000..45e655818 --- /dev/null +++ b/apps/pastel/test-settings.js @@ -0,0 +1,6 @@ +const SETTINGS_FILE = "pastel.json"; +let settings = {"grid":false, "date":false, "font":"Lato"} + +require("Storage").write(SETTINGS_FILE, settings); +settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; +console.log(settings); From f87e89ff25c9d72e27e495a99907a2999dc83e5d Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 14 Sep 2021 23:04:48 +0100 Subject: [PATCH 0015/1062] updated Pastel fonts --- apps/pastel/pastel.app.js | 24 ++++++++++++++---------- apps/pastel/pastel.settings.js | 4 ++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 96ac67766..0f4be60d9 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -1,4 +1,11 @@ +Graphics.prototype.setFontOrbitron = function() { +// Actual height 32 (35 - 4) +var widths = atob("ChcmEiUlISUlHiYlCg=="); +var font = atob("AAAAAAAAAAAAAAAAAAAAPAAAAAAB4AAAAAAPAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAPwAAAAAD8AAAAAA+AAAAAAPgAAAAAD4AAAAAB+AAAAAAfgAAAAAHwAAAAAB8AAAAAAfAAAAAAPwAAAAAD4AAAAAA+AAAAAAPgAAAAAH4AAAAAB+AAAAAAfAAAAAAHwAAAAAB8AAAAAA/AAAAAAPwAAAAAD4AAAAAAAAAAAAAAAAAAAAAB///+AAA////8AAP////wAD/////AAfgAA/4ADwAAH/AAeAAB94ADwAAfPAAeAAH54ADwAB+PAAeAAPh4ADwAD4PAAeAA+B4ADwAPgPAAeAD8B4ADwAfAPAAeAHwB4ADwB8APAAeAfAB4ADwH4APAAeB+AB4ADwPgAPAAeD4AB4ADw+AAPAAePwAB4ADz8AAPAAefAAB4AD3wAAPAAf8AAB4AD/////AAf////4AB////+AAH////gAAH///gAAAAAAAAAAAAAAAAAACAAAAAAAwAAAAAAOAAAAAADwAAAAAB+AAAAAAfwAAAAAH4AAAAAB+AAAAAAfgAAAAAD4AAAAAAf////4AD/////AAf////4AD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAf/4AA+AP//AAPwD//4AD+Af//AAfgH4H4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4AD//8APAAf//gB4AB//4APAAH/+AB4AAH+AAHAAAAAAAAAAAAAAAAAAAAAAAAAOAAA4AAHwAAHgAB+AAA+AAfwAAH4AD4AAAfAAeAAAB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAfAPgB4AD//8APAAP////4AA////+AAD////wAAAAP/8AAAAAAAAAAAAAAAAAAAAPgAAAAAD8AAAAAA/gAAAAAP8AAAAAB/gAAAAAf8AAAAAH3gAAAAB88AAAAAfHgAAAAHw8AAAAB+HgAAAAfg8AAAAH4HgAAAA+A8AAAAPgHgAAAD4A8AAAA/AHgAAAPwA8AAAD8AHgAAA/AA8AAAPwAHgAAB8AA8AAAfgAHgAAD/////AAf////4AD/////AAf////4AAAAA8AAAAAAHgAAAAAA8AAAAAAHgAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAD//4BwAAf//APgAD//4B+AAf//AP4AD8H4A/AAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAP//4ADwA///AAeAD//wADwAP/8AAcAAP8AAAAAAAAAAAAAAAAAAAAAAAAAB///+AAA////8AAP////wAD/////AAfg/AH4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAAAPAB4AAAB///AAAAH//4AAAAf/+AAAAB//gAAAAB/gAAAAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAf////4AB/////AAP////4AA/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+D/wAAH////gAB////+AAf////4AD8D+A/AAeAHgB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA8APAAf////4AB/////AAP////wAAf/v/8AAA/gP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAH/+ABgAB//4AOAAf//gB4AD4B8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA+APAAf////4AB////+AAP////wAAf///4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAB4AADwAAPAAAeAAB4AADwAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); +var scale = 1; // size multiplier for this font +g.setFontCustom(font, 46, widths, 45+(scale<<8)+(1<<16)); +}; Graphics.prototype.setFontCabinSketch = function() { // Actual height 48 (51 - 4) @@ -8,9 +15,8 @@ var scale = 1; // size multiplier for this font g.setFontCustom(font, 46, widths, 65+(scale<<8)+(1<<16)); }; - Graphics.prototype.setFontGochiHand = function() { - // Actual height 49 (53 - 5) +// Actual height 49 (53 - 5) var widths = atob("DRQeExgXGxgbGRobDg=="); var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAA/gAAAAAAA/gAAAAAAA/gAAAAAAA/gAAAAAAA/gAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAD/+AAAAAAP/8AAAAAA//gAAAAAH/8AAAAAAf/wAAAAAB/+AAAAAAP/4AAAAAA//AAAAAAH/8AAAAAAf/gAAAAAD/+AAAAAAP/4AAAAAB//AAAAAAf/8AAAAAA//wAAAAAB//AAAAAAB/4AAAAAAB/gAAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAf/wAAAAAB//8AAAAAD//+AAAAAH//+AAAAAP4D/AAAAAfwB/AAAAAfgA/gAAAAfAAfgAAAA/AAfgAAAA/AAfgAAAA/AAfgAAAA/AAfgAAAA/AAfgAAAA/AAfgAAAA/gA/AAAAA/gA/AAAAAfwB+AAAAAf4D+AAAAAP//8AAAAAH//4AAAAAD//gAAAAAB/+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAAD8AAAAAAAD8AAAAAAAD8AAAAAAAH8AAAAAAAH4AAAAAAAH4H+AAAAAPz//AAAAAP///gAAAAf///gAAAA////gAAAA////gAAAA/wAfgAAAA4AAPAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAADgA/AAAAAHwA/gAAAAPwB/gAAAAfgB/gAAAAfgD/gAAAA/gH/gAAAA/AH/gAAAA/AP/gAAAA/AffgAAAA/A/fgAAAA/B+fgAAAA/j8fgAAAA//4fgAAAA//4fgAAAAf/wfgAAAAf/A/gAAAAP+A/AAAAAD4B/AAAAAAAB+AAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAH8AAAAAAAP+AAAHwAAP+AAAPwAAH/AAAPwAAD/AAAfgAAB/AAAfgAAB/AAA/ADAA/AAA/AHwA/AAA/AH4A/AAA/AP4A/AAA/AP4A/AAA/Af4A+AAA/g/8B+AAA///8B8AAA///+D8AAAf/7//4AAAP/x//wAAAH/A//gAAAD8Af+AAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAA/AAAAAAAD/gAAAAAAP/gAAAAAAf/gAAAAAA//gAAAAAD+fgAAAAAH4fgAAAAAPwfgAAAAAfgfgAAAAA/AfgAAAAB+AfgAAAAD8AfgAAAAH4Afj8AAAPwAf/+AAAPwP///AAAf/////AAA//////AAA//////AAA////h/AAA//wfgeAAAPAAfgIAAAAAAfgAAAAAAAfAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAPwAAAA/8Af4AAB//8Af8AAB//8AP+AAB//8AH+AAB//8AD+AAB+D8AD+AAB+D+AB+AAB+D+AB+AAB+B+AB+AAB+B/AB+AAB+B/AB+AAB+A/gD8AAB+A/wD8AAB+A/4H4AAB+Af//wAAD+AP//gAAD+AH//AAAH8AD/+AAAD4AA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//AAAAAAP//4AAAAA///8AAAAD///+AAAAH////AAAAf/4H/AAAA/nwB/gAAB/PwA/gAAB+PwAfgAAD8fgAfgAAH4fgAfgAAH4fgAfgAAPwfwAfgAAPwfwAfAAAfwP4A/AAAfwP8B+AAAfwP/H+AAAfwH//8AAAfgD//wAAAfAB//gAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAB/AAAAAAAB/AAAAAAAB/AAAB8AAB/AAAf+AAA/AAB//AAA/AAH//AAA/AAf//AAA/AB//+AAA/AH//+AAA/Af/z8AAA/A/8A4AAA/D/wAAAAA/H/AAAAAA/f8AAAAAA//wAAAAAA//AAAAAAA/+AAAAAAA/4AAAAAAA/gAAAAAAA/AAAAAAAA8AAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAgAB/8AAAD7wD/+AAAH/+H//AAAH//P//AAAP/////gAAP//+B/gAAfx/8A/gAAfgf4AfgAAfgP4AfgAAfgH8AfgAAfgH8AfgAAfgD8AfgAAfgD+AfgAAfgH/AfAAAfwP/AfAAAP+//g+AAAP//f/+AAAH/+f/8AAAD/8P/4AAAB/wD/wAAAAeAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAMAAAB//gB+AAAH//wB+AAAP//4D+AAAf4P8D+AAAfgH8D+AAA/AD+D+AAA/AD+D8AAB+AB+D8AAB+AB+H4AAB+AB+H4AAB+AB8PwAAB/AD8fgAAB/gD8/AAAA/wD7+AAAA/8H/8AAAAf///4AAAAP///gAAAAH//+AAAAAB//4AAAAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAD4A/gAAAAH8A/gAAAAH8A/gAAAAH8A/gAAAAH8A/gAAAAH8AfAAAAAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); var scale = 1; // size multiplier for this font @@ -23,7 +29,6 @@ var widths = atob("BAgJDQ0RDwUHBwkNBQgFCA0NDQ0NDQ0NDQ0GBg0NDQkSDw4PEQ0MEBEHCg8LF var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAEA/84D/zgAAEAAAAAAAAAAAA+AAD8AAAAAAAAAD4AAPgAAAAAAAAAABAADGIAM/gB/8A/+AD8YAAx+AD/4B/4APxgAjGAAIAAAAAAAAAAAAADwMAfg4DnBgMMHg///P/5gMGGAwc4Bg/AEB4AAAAA4AAHwAA5gYDCDgMIcAxjgB84ADnAAA4AAHOABz8AOMYBwwgMDCAgP4AAfAAAAAAAAAAeAAH8APY4B/BgMcGAw4YDBxgMDmA4HwBwPAAB8AAf4ABhgAACAAAAD4AAPgAAAAAAAAAAAAAH/gB//wfAHzgAHAAAAAAAAAAAOAAcfAPwf/8Af+AAAAAAAAAAAANgAAUAABwAAfwAAcAADQAAJAAAAAAAAAAAGAAAYAABgAAGAAP/gA/+AAGAAAYAABgAAGAAAQAAAAAAAEAAA7AAD4AAAAAAAAAAAAGAAAYAABgAAGAAAYAAAgAAAAAAAAAADgAAOAAAYAAAAAAPAAD4AB8AAfAAHwAD4AAeAABAAAADgAB/wAf/wBwHAMAGAwAYDABgMAGA4A4B4PAD/4AH/AAAAAAAAAAAAAYAgDgGAcAYDgBgP/+A//4AABgAAGAAAYAAAAAAAAAAAAQBgHgOAcB4DgPgMA2AwGYDAxgOOGAfwYB+BgBgGAAAAAAAADA4AcDwDgDgMAGAwgYDDBgMcGA5w4B9/ADj4AAAAAAAAABgAAOAAB4AAfgADmAAcYADhgA4GAD//gP/+AAGAAAYAAAgAAAAAADAH4OA/gYDGBgMYGAxgYDGDgMccAw/wCB8AAAAAAAAAAYAAH4AB/wAPjgB8GAOwYDzBgOMGAg44AD/AAH4AACAAAAAAAAAwAADAAAMAGAwB4DAfAMHwAw8ADPAAPwAA+AADgAAAAAAAAAA4+AH38A/44DHBgMMGAwwYDHBgOeOAffwA4/AABwAAAAAAAAAAAAPgAB/AAOMGAww4DBngMF4Aw/ADj4AH+AAPwAAAAAAAAADg4AODgAwGAAAAAAAAAAAABAAAODsA4PgBAYAAAAAAAAAQAABgAAPAAA8AAG4AAZgADHAAMMABgwAAAAAAAAAAAAAAAAEQAAZgABmAAGYAAZgABmAAGYAAZgABmAAGYAAAAAAAAAAAAAAAAGDAAMMAAxwABmAAG4AAPAAA8AABgAAEAAAAAAAAAEAAA4AADABgMHOAw84DGAAP4AAfAAAAAAACAAD/gAePADgGAYAMBh8YMPxgxxGDGEIIYwgxOCDH8IMYRgYBGAwMwD/hAD8AAAAAAAGAAB4AAfgAP4AD+AA/YAPhgA4GAD4YAD9gAD+AAD+AAB+AAB4AABgAAAAAAAD//gP/+AwYYDBhgMGGAwYYDDhgOOGA/84B+/ABh4AAAAAAAAA/gAH/gA+/AHAcA4A4DgBgMAGAwAYDABgMAGA4A4BgDAGAMAAAAAAAAAAAA//4D//gMAGAwAYDABgMAGAwAYDABgOAOAYAwB4PAD/4AH/AAHwAAAAAAAAAAAAP/+A//4DDBgMMGAwwYDDBgMMGAwwYDABgMAGAAAAAAAAAAAA//4D//gMGAAwYADBgAMGAAwYADBgAMGAAwAAAAAAA/gAH/AA++AHAcA4A4DgBgMAGAwAYDABgMGGAwYYDhjgGH8AAfwAAAAAAAAAAAD//gP/+A//4ADAAAMAAAwAADAAAMAAAwAADAAAMAA//4D//gAAAAAAAAAAAAAAA//4D//gAAAAAAAAAAAAAAAAAYAABgAAGAAA4AAHgP/8A//gAAAAAAAAAAAAAAAP/+A//4ADAAAMAAB4AAPwABzgAOHABwPAOAeAwA4CAAgAAAAAAAAAAAP/+A//4AABgAAGAAAYAABgAAGAAAYAABgAAAA//4D//gP/+AeAAAeAAAeAAA+AAA8AAA4AAHgAB4AAeAAHwAA8AAPAAA//4D//gAAAAAAAAAAAAAAA//4D//gHAAAOAAAeAAA8AAA4AABwAADwAADgAAHAP/+A//4AAAAAAAAAAAAP4AD/4AeDwBwHAOAOAwAYDABgMAGAwAYDABgOAOAcBwB4PAD/4AD+AABAAAAAAAAAAAAAP/+A//4DBgAMGAAwYADBgAMGAA44AB/AAH4AAHAAAAAAA/gAP/gB4PAHAcA4A4DABgMAGAwAYDABgMAGA4A4BwHwHg/gP/nAP4MAEAQAAAAAAAAAAA//4D//gMGAAwYADBgAMHAAw/ADneAH4eAPA4AABgAAAAAAwA8DAH4OA5wYDDBgMMGAw4YDBjgOH8AYPgAAIAAAAAwAADAAAMAAAwAADAAAP/+A//4DAAAMAAAwAADAAAMAAAAAAAAAAAAAA//AD//AAAcAAA4AABgAAGAAAYAABgAAOAABwD//AP/4A/8AAAAAOAAA+AAB+AAB/AAA/AAA/AAAeAAD4AA/AAPwAH8AB+AAPgAA4AAAAAAOAAA/AAB/gAA/wAAf4AAPgAB+AA/gAfwAH4AA8AAD8AAD+AAB/AAB/gAA+AAH4AD/AD/gA/wAD4AAMAAAgAYDgDgPAeAeHgAe8AA/AAA4AAHwAB/wAPHgDwPgOAOAgAYAAAAIAAA4AADwAAHwAAHgAAHgAAP+AA/4APgAB4AAeAADwAAMAAAgAAAAAAMAGAwA4DAPgMB+AwPYDDxgMeGAzwYD8BgPgGA8AYDABgAAAAAAAH//8f//xAABEAAEAAAAAAAHAAAPAAAPgAAPgAAHwAAHwAAHgAADAAAAEAAEQAAR///H//8AAAAAAAAAAAAAAABgAAeAADwAA8AADgAAHgAAPAAAOAAAIAAAAAAAAAAAAQAABAAAEAAAQAABAAAEAAAQAABAAAAAAAAgAADAAAOAAAIAAAAAAAAAAAAAAAHAAY+ADnYAMYgAxiADGYAORAAf+AA/4AAAAAAAB//4H//gAYMADAYAMBgAwGADAYAPHgAf8AA/gAAAAAAAAA/gAH/AA4OADAYAMBgAwGADAYAMDgAQEAAAAAB+AAf8ADx4AMBgAwGADAYAMBgAYMB//4H//gAAAAAAAAB8AAf8ADpwAMhgAyGADIYAMhgA6GAB4wADhAAAAACAAAMAAH/+A//4DMAAMwAAzAAABAcAff4D/5gMbmAwmYDCZgMZmA/mYD8fAMA4AgAAAAAAAAAf/+B//4AGAAAwAADAAAMAAA4AAD/4AH/gAAAAAAACAAAc/+Bz/4CAAAAAAAAABiAAGc//5z//CAAAAAAAAAAAAAAf/+B//4AAYAADgAAfAAHuAA4cADAYAIAgAAAAAAAAAAAf/+B//4AAAAAAAAAAAAAAAA/+AD/4AEAAAwAADAAAMAAA/+AB/4AH/gAwAADAAAMAAA4AAD/4AD/gAAAAAAAAAAAA/+AD/4AGAAAwAADAAAMAAAwAAD/4AH/gAAAAAAAAD+AAf8ADg4AMBgAwGADAYAMBgA4OAB/wAD+AADgAAAAAP/+A//4BgwAMBgAwGADAYAMBgA8eAB/wAD8AAAAAAAAAB+AAf8ADx4AMBgAwGADAYAMBgAYMAD//gP/+AAAAAAAAP/gA/+ABwAAOAAAwAADAAAMAAAAAAAAQAHhgA/GADMYAMxgAxmADH4AEPAAAQAAAAAMAAAwAAf/wD//gAwGADAYAMBgAAAAAAAAP/AA/+AAAYAABgAAGAAAYAADAA/+AD/4AAAAAAAADgAAPgAAfgAAPwAAPgAAeAAHwAD8AA/AADgAAIAAA4AAD8AAD+AAB+AAB4AA/AAfgADwAAPgAAfwAAP4AAHgAB+AA/gAPwAA4AAAAAAAAgAwGADh4AHvAAPwAAOAAB8AAe8ADh4AMBgAgCACAAAOAAA+AAA/BgA/eAA/wAD8AA/AAPgAD4AAOAAAgGADA4AMHgAx+ADOYANxgA+GADgYAMBgAAAAAMAD//4f9/xgADEAAEAAAAAAAAAAAAAAB///n//+AAAAAAAAAAAAAABAABGAAMf9/w//+AAwAAAAAAAAAA4AADgAAYAABgAAHAAAMAAAwAADAAAcAADgAAAAAAAA"); var scale = 1; // size multiplier for this font g.setFontCustom(font, 32, widths, 22+(scale<<8)+(1<<16)); - }; Graphics.prototype.setFontLato = function() { @@ -32,17 +37,14 @@ var widths = atob("DhglJSUlJSUlJSUlEA=="); var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAAAHwAAAAAAAAA/gAAAAAAAAH/AAAAAAAAAf8AAAAAAAAB/wAAAAAAAAD+AAAAAAAAAHwAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAAAB/AAAAAAAAAf8AAAAAAAAP/wAAAAAAAD/8AAAAAAAB//AAAAAAAAf/wAAAAAAAP/4AAAAAAAD/+AAAAAAAA//AAAAAAAAf/wAAAAAAAH/4AAAAAAAD/+AAAAAAAA//AAAAAAAAf/wAAAAAAAH/4AAAAAAAD/+AAAAAAAA//AAAAAAAAf/wAAAAAAAD/4AAAAAAAAP+AAAAAAAAA/AAAAAAAAADwAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//+AAAAAAA////AAAAAAP////gAAAAD/////AAAAA//////AAAAH/////+AAAA//gAH/8AAAH/gAAB/4AAA/4AAAB/wAAD+AAAAB/AAAfwAAAAD+AAB+AAAAAH4AAH4AAAAAfgAAfAAAAAA+AAD8AAAAAD8AAPwAAAAAPwAA/AAAAAA/AAD8AAAAAD8AAPwAAAAAPwAAfAAAAAA+AAB+AAAAAD4AAH4AAAAAfgAAfwAAAAD+AAA/gAAAAfwAAD/gAAAH/AAAH/gAAB/4AAAP/4AB//AAAAf/////4AAAA//////AAAAA/////4AAAAB////+AAAAAA////AAAAAAAf//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAB8AAAAAAAAAPwAAAD4AAAB/AAAAPgAAAP4AAAA+AAAB/AAAAD4AAAP8AAAAPgAAA/gAAAA+AAAH8AAAAD4AAA/gAAAAPgAAH8AAAAA+AAA/gAAAAD4AAH///////gAAf//////+AAB///////4AAH///////gAAf//////+AAB///////4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAAADwAAAA+AAAA/AAAAH4AAAP8AAAA/gAAB/wAAAH+AAAP/AAAA/4AAB/4AAAH/gAAH+AAAA/+AAA/gAAAH/4AAD8AAAA/vgAAfgAAAH8+AAB+AAAA/n4AAHwAAAH8fgAA/AAAA/h+AAD8AAAH8H4AAPwAAA/gfgAA/AAAH8B+AAD8AAA/gH4AAPwAAH8AfgAAfAAB/gB+AAB+AAP8AH4AAH8AB/gAfgAAP4Af8AB+AAA/8f/gAH4AAB///8AAfgAAH///gAB+AAAP//4AAH4AAAP//AAAfgAAAf/wAAB+AAAAP4AAAD4AAAAAAAAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAHgAAAAA8AAA/gAAAAPwAAD/AAAAD/AAAP+AAAAf8AAA/8AAAD/wAAB/4AAAf+AAAB/wAAD/gAAAB/AAAP4AAAAD+AAB/AAAAAH4AAH4AAAAAfgAAfgAAAAA+AAB8AAAAAD8AAPwAB4AAPwAA/AAHgAA/AAD8AAfAAD8AAPwAD8AAPwAA/AAPwAA/AAD8AA/AAD4AAHwAH8AAfgAAfgAf4AB+AAB/AD/gAP4AAD+AffAB/AAAP//9/Af8AAAf//n///gAAB//+P//8AAAD//wf//gAAAD/+A//8AAAAH/gB//gAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAB+AAAAAAAAAf4AAAAAAAAD/gAAAAAAAAf+AAAAAAAAH/4AAAAAAAA//gAAAAAAAH++AAAAAAAB/z4AAAAAAAP+PgAAAAAAB/g+AAAAAAAf8D4AAAAAAD/gPgAAAAAAf4A+AAAAAAH/AD4AAAAAA/wAPgAAAAAH+AA+AAAAAB/wAD4AAAAAP8AAPgAAAAB/gAA+AAAAAf8AAD4AAAAD/AAAPgAAAAf4AAA+AAAAB///////4AAH///////gAAf//////+AAB///////4AAH///////gAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAADwAAAAAAQAAfgAAAAA/gAB+AAAAD/+AAH8AAAP//4AAPwAAH///gAAfgAAf//+AAB+AAB///4AAH4AAH/wPgAAPgAAfgB8AAA/AAB+AHwAAD8AAH4AfAAAPwAAfgB8AAA/AAB+AHwAAD8AAH4AfAAAPwAAfgB+AAA+AAB+AH4AAD4AAH4AfgAAfgAAfgA/AAD+AAB+AD8AAPwAAH4AP4AD/AAAfgAf4Af4AAB+AB////AAAH4AD///4AAAfAAH///AAAB8AAP//4AAAHwAAf//AAAAAAAAf/wAAAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAAAAAAAB//gAAAAAAAf//gAAAAAAH///gAAAAAA////AAAAAAP///8AAAAAB//Af4AAAAAf/wAfwAAAAD/8AA/AAAAAf/wAB+AAAAH/+AAH4AAAA/7wAAPgAAAH/PAAA/AAAB/58AAD8AAAP+HwAAPwAAB/wfAAA/AAAf+B8AAD8AAD/wHwAAPwAAf8AfAAA+AAB/gB8AAD4AAH8AH4AAfgAAfgAfgAB+AAB4AA/AAPwAAHAAD+AB/AAAYAAP+Af4AAAAAAf///AAAAAAA///8AAAAAAB///gAAAAAAD//4AAAAAAAH//AAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAAAAH4AAAAAAAAAfgAAAAAAAAB+AAAAAAAAAH4AAAAAAgAAfgAAAAAOAAB+AAAAAD4AAH4AAAAA/gAAfgAAAAP+AAB+AAAAD/4AAH4AAAA//AAAfgAAAP/4AAB+AAAD/+AAAH4AAA//gAAAfgAAP/4AAAB+AAD/+AAAAH4AA//gAAAAfgAP/4AAAAB+AB/+AAAAAH4Af/gAAAAAfgH/4AAAAAB+B/+AAAAAAH4f/gAAAAAAfn/4AAAAAAB//+AAAAAAAH//gAAAAAAAf/4AAAAAAAB/+AAAAAAAAH/gAAAAAAAAf4AAAAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAwAD/+AAAAA/8Af/8AAAAP/4D//4AAAB//4f//wAAAP//j///gAAB///P8H/AAAP////AH8AAA/gH/wAP4AAH4AH+AAfgAAfgAf4AA+AAB8AA/gAD4AAHwAD8AAPwAA+AAHwAAfAAD4AAfAAB8AAPgAB8AAHwAA+AAHwAAfAAD4AAfAAB8AAHwAD8AAPwAAfAAPwAA+AAB+AB/gAD4AAH4AH+AAfgAAP4B/8AD+AAA/8//4AfwAAB///P8H/AAAD//8///4AAAH//h///AAAAP/4D//4AAAAP/AH//AAAAAHAAP/4AAAAAAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAAP/8AAAAAAAD//4AAAAAAAf//4AAAAAAD///gAAAAAAf///AAAIAAB/gP+AABgAAP4AP4AAeAAA/AAfwAD4AAH4AA/AAfgAAfgAB8AH+AAB8AAHwA/4AAPwAAfAH/gAA/AAB8B/8AAD8AAHwP/AAAPwAAfB/4AAA/AAB8P+AAAD8AAHj/wAAAHwAAef8AAAAfgAD7/gAAAB+AAP/8AAAAD8AB//AAAAAP4AP/4AAAAAf4D/+AAAAAB////wAAAAAD///8AAAAAAH///gAAAAAAH//4AAAAAAAP/+AAAAAAAAH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAfAAAAAD8AAD+AAAAAf4AAP4AAAAB/gAB/wAAAAH+AAH/AAAAAf4AAP4AAAAA/AAA/gAAAAB4AAB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); var scale = 1; // size multiplier for this font g.setFontCustom(font, 46, widths, 64+(scale<<8)+(1<<16)); - }; - Graphics.prototype.setFontArchitect = function() { -// Actual height 50 (52 - 3) -var widths = atob("CiEuCSolMjQuLS8qDA=="); -var font = atob("AAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAPAAAAAAAAAAAPgAAAAAAAAAAHwAAAAAAAAAAD4AAAAAAAAAAB8AAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAB8AAAAAAAAAAD+AAAAAAAAAAH+AAAAAAAAAAH+AAAAAAAAAAP8AAAAAAAAAAf8AAAAAAAAAAf8AAAAAAAAAA/8AAAAAAAAAA/4AAAAAAAAAB/4AAAAAAAAAB/4AAAAAAAAAD/wAAAAAAAAAH/wAAAAAAAAAH/gAAAAAAAAAP/AAAAAAAAAAP/AAAAAAAAAAf+AAAAAAAAAAf8AAAAAAAAAA/8AAAAAAAAAA/4AAAAAAAAAB/wAAAAAAAAAB/gAAAAAAAAAD/gAAAAAAAAAD/AAAAAAAAAAH+AAAAAAAAAAH8AAAAAAAAAAP4AAAAAAAAAAPgAAAAAAAAAAfAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAA//AAAAAAAAAH//wAAAAAAAAf//+AAAAAAAA////gAAAAAAB//wfwAAAAAAD/8AB8AAAAAAH/wAAfAAAAAAH/gAAHgAAAAAP+AAAD4AAAAAP8AAAA8AAAAAP4AAAAeAAAAAP4AAAAHgAAAAHwAAAADwAAAAHwAAAAB4AAAAHwAAAAA8AAAADwAAAAAeAAAADwAAAAAPAAAAB4AAAAAHgAAAA4AAAAADwAAAAcAAAAAB4AAAAeAAAAAA8AAAAPAAAAAAeAAAAHgAAAAAPAAAADwAAAAAHgAAAB4AAAAAHgAAAA+AAAAADwAAAAPAAAAAB4AAAAHgAAAAB4AAAADwAAAAA8AAAAA8AAAAA8AAAAAeAAAAAeAAAAAHgAAAAeAAAAAD4AAAAfAAAAAA+AAAAfAAAAAAPgAAA/AAAAAAD8AAB/AAAAAAA/wAH/AAAAAAAH////AAAAAAAB///+AAAAAAAAH//8AAAAAAAAAf/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///8AAAAAf//////AAAAAP//////AAAAAH//////gAAAAD//////gAAAAB///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAADwAAABAAAAAAD4AAABwAAAAAD8AAAB8AAAAAD8AAAB+AAAAAD+AAAB/AAAAAB+AAAB/gAAAAB+AAAB/wAAAAB+AAAD/4AAAAA+AAAD/4AAAAAfAAAD/8AAAAAfAAAD/+AAAAAPAAAD/fAAAAAHgAAH/PgAAAAHwAAH/HgAAAADwAAP+DwAAAAB4AAP+B4AAAAA8AAf+A8AAAAAfAAf8A8AAAAAPgA/8AeAAAAAD4D/4APAAAAAB///4APgAAAAAf//wAHgAAAAAP//AADwAAAAAD/+AAD4AAAAAAPwAAB8AAAAAAAAAAA8AAAAAAAAAAA+AAAAAAAAAAAfAAAAAAAAAAAPgAAAAAAAAAAPwAAAAAAAAAAHwAAAAAAAAAAD4AAAAAAAAAAD8AAAAAAAAAAB+AAAAAAAAAAA/AAAAAAAAAAAfgAAAAAAAAAAPgAAAAAAAAAAHwAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAPAAAAAAAAAAAHgAAAAAAAAAAHwAAAAAAAAAAD4AAAAAAAAAAB8AAAAAAAAAAA+ABgAAAAAAAA+AA4AAAAAAAAfAA+AAAAAAAAPgAfAAIAAAAAHwAfAAGAAAAAHwAPgAHAAAAAD4APgADgAAAAB8AHwABwAAAAA+AH4AB4AAAAAeAD4AA8AAAAAfAD8AA+AAAAAPgB+AAeAAAAAHwB+AAfAAAAAD4A/AAPgAAAAB4A/gAPgAAAAA8A/wAPwAAAAAeAf4APwAAAAAPAf8AHwAAAAAHgf+AH4AAAAAD4fvAP4AAAAAB8fngP4AAAAAA//j4f4AAAAAAf/h//8AAAAAAP/g//8AAAAAAD/gP/4AAAAAAB/gH/4AAAAAAAfAB/4AAAAAAAEAAfwAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAAAAAAAAeAAAAAAAAAAAfAAAAAAAAAAA/gAAAAAAAAAA/wAAAAAAAAAA/4AAAAAAAAAB/8AAAAAAAAAB/+AAAAAAAAAB/PAAAAAAAAAD/HgAAAAAAAAD+DwAAAAAAAAD+B4AAAAAAAAH+A8AAAAAAAAH+AeAAAAAAAAH+APAAAAAAAAH+APgAAAAAAAH+AHwAAAAAAAH+AD4AAAAAAAH+AB8AAAAAAAH+AA+AAAAAAAH+AAfAAAAAAAD+AAPgf4AAAAD//////+AAAAB///////AAAAAf//////gAAAAH//////wAAAAA//////4AAAAAB/////4AAAAAAAAP4AAAAAAAAAAHwAAAAAAAAAAD4AAAAAAAAAAB8AAAAAAAAAAA+AAAAAAAAAAAfAAAAAAAAAAAPAAAAAAAAAAAHgAAAAAAAAAADwAAAAAAAAAAB4AAAAAAAAAAA8AAAAAAAAAAAeAAAAAAAAAAAPAAAAAAAAAAAHgAAAAAAAAAADwAAAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAAB//+AAAAAAAB////AAAAAAAA////gAAAAAAAf///wAAAAAAAP///4AAAAAAAH///8AAAAAAAAH4B8AAfAAAAAD8A+AAfgAAAAB8AfAAPwAAAAA+APAAH4AAAAAfAHgAB8AAAAAPgDwAA+AAAAAHwBwAAfAAAAAHwA4AAPgAAAAD4A8AAHwAAAAB8AcAAD4AAAAA+AOAAD8AAAAAfAHAAB8AAAAAfADgAA+AAAAAPgBwAAfAAAAAHwA4AAPgAAAAD4AcAAHgAAAAD4APAAHwAAAAB8AHgAD4AAAAA+ADwAD4AAAAAfAB4AB8AAAAAfgA+AB8AAAAAPgAPgB+AAAAAHwAH4B+AAAAAD4AD/n+AAAAAD8AA//+AAAAAB+AAP/+AAAAAA+AAD/+AAAAAAfAAA/+AAAAAAPgAADwAAAAAAPwAAAAAAAAAAH4AAAAAAAAAAD4AAAAAAAAAAB8AAAAAAAAAAA+AAAAAAAAAAAfAAAAAAAAAAAfAAAAAAAAAAAPgAAAAAAAAAAHwAAAAAAAAAAD4AAAAAAAAAAB4AAAAAAAAAAA8AAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAAAB//wAAAAAAAAH//+AAAAAAAAP///wAAAAAAAP///8AAAAAAAf////AAAAAAAf////wAAAAAA//AB/8AAAAAA/8AAP/AAAAAA/4AAB/wAAAAA/wAAAP4AAAAA/wAAAD+AAAAA/gAAAB/AAAAAfgAAAA/wAAAAfgAAAA/4AAAAfgAAAA/8AAAAPgAAAA//AAAAPwAAAA//gAAAHwAAAA//wAAADwAAAA/34AAAD4AAAA/z8AAAB4AAAA/x+AAAA8AAAA/w/AAAAeAAAAfwfgAAAOAAAAfwPwAAAHAAAAfwH4AAABgAAAfwH8AAAAAAAAPwD+AAAAAAAAP4B+AAAAAAAAH4A/AAAAAAAAH4A/gAAAAAAAD8AfgAAAAAAAB8AfwAAAAAAAB+AfwAAAAAAAA+AP4AAAAAAAAfAP4AAAAAAAAPgP4AAAAAAAAHwf4AAAAAAAAD//4AAAAAAAAB//4AAAAAAAAAf/4AAAAAAAAAP/wAAAAAAAAAB/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAA4AAAAAAAAAAAcAAAAAAAAAAAOAAAAAAAAAAAHAAAAAAAAAAADgAAAAAAAAAABwAAAAAAAAAAA4AAAAAAAAAAAcAAAAAAAAAAAeAAAAAAAAAAAPAAAAAAAAAAAHgAAAAAAAAAADwAAAAAAAAAAB4AAAAAAAAAAA8AAAAAAAAAAAeAAAAAAAAAAAPAAAAAAAAAAAPgAAAAAAAAAAHwAAAAAAAAAAD4AAAAAAAAAAB8AAAAAAAAAAA+AAAD/4AAAAAfAAAP/8AAAAAfgAA//+AAAAAPwAB///AAAAAH4AD///AAAAAD8AH///gAAAAB+AP/8AAAAAAA/Af/gAAAAAAAfg/+AAAAAAAAfz/8AAAAAAAAP//4AAAAAAAAH//wAAAAAAAAD//gAAAAAAAAB//AAAAAAAAAA//AAAAAAAAAAf+AAAAAAAAAAP+AAAAAAAAAAH+AAAAAAAAAAD8AAAAAAAAAAB8AAAAAAAAAAA8AAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAP8AAAAAAAAAAf/AAAAAAAAAAf/wAAAAAAAAAf/8AAAAAAB8A//+AAAAAAD/g///gAAAAAD///gPwAAAAAD///gD8AAAAAD///gA+AAAAAD///gAPAAAAAB/P/gAHwAAAAB+B/gAD4AAAAA+AfgAA8AAAAA+APwAAeAAAAAeAHwAAPAAAAAfAD4AAHgAAAAPAB4AADwAAAAHgA8AAB4AAAAHgAeAAA8AAAADwAOAAAeAAAAB4AHAAAPAAAAA8AHgAAHgAAAAcADwAADwAAAAOABwAAB4AAAAPAB4AAA4AAAAHgA8AAA8AAAADwAeAAAeAAAAB4AfAAAPAAAAA8APgAAHAAAAAeAHwAAHgAAAAPgH4AADwAAAADwD8AADwAAAAB4D+AAD4AAAAA8D/gAB4AAAAAfB/wAB8AAAAAPh/8AB8AAAAAD//+AD8AAAAAB/+/gH8AAAAAAf+P8/8AAAAAAD8H//8AAAAAAAAB//8AAAAAAAAAf/8AAAAAAAAAH/4AAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAAAAf4AAAAAAAAAA/+AAAAAAAAAA//AAAAAAAAAA//wAAAAAAAAA//4AAAAAAAAA//+AAAAAAAAAf4/AAAAAAAAAfgfgAAAAAAAAfgHwAAAAAAAAPgD4AAAAAAAAPgB8AAAAAAAAPgA+AAAAAAAAHwAfAAAwAAAADwAPgAAeAAAAD4AHwAAfAAAAB8AD4AAPgAAAB+AB8AAPwAAAA+AA+AAP4AAAAfAAeAAP4AAAAPgAPAAH8AAAAHwAHgAH8AAAAD4AHwAH+AAAAB8AD4AH+AAAAA/AB8AH+AAAAAPgA8AP+AAAAAH4A+AP+AAAAAB+AfAf+AAAAAA/gPh/+AAAAAAP////8AAAAAAD////8AAAAAAA////4AAAAAAAP///wAAAAAAAD///gAAAAAAAAf/+AAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAPAAAAAAAAAAAHgAAAAAAAAAADwAMAAAAAAAAB4APAAAAAAAAA8APgAAAAAAAAEAHwAAAAAAAAAAD4AAAAAAAAAAA4AAAAAAAAAAAAAAAA=="); +// Actual height 40 (41 - 2) +var widths = atob("CBolByEeJykkJCYhCg=="); +var font = atob("AAAAAAAAAAAAAAAAYAAAAAAAADgAAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAD4AAAAAAAA/AAAAAAAAH4AAAAAAAB/AAAAAAAAf4AAAAAAAD+AAAAAAAA/wAAAAAAAH+AAAAAAAB/gAAAAAAAP8AAAAAAAD/AAAAAAAAf4AAAAAAAH+AAAAAAAA/gAAAAAAAP8AAAAAAAB/AAAAAAAAfwAAAAAAAH8AAAAAAAA/AAAAAAAAPwAAAAAAAB8AAAAAAAAfAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAP/8AAAAAAH//4AAAAAB///wAAAAAf/APgAAAAD/gAeAAAAA/wAA8AAAAH8AABwAAAA/AAAHgAAAHwAAAeAAAA+AAAA4AAADgAAADgAAAcAAAAOAAABwAAAA4AAAOAAAADgAAA4AAAAOAAADgAAAA4AAAOAAAADgAAA4AAAAOAAADgAAAB4AAAOAAAAHAAAA4AAAAcAAADwAAADwAAAHAAAAOAAAAeAAAB4AAAA4AAAPAAAADwAAB4AAAAHwAAPgAAAAPgAD8AAAAAf4D/gAAAAAf//4AAAAAAf/+AAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAP////4AAAB/////gAAAH////+AAAAf////gAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAADwAADAAAAAeAAAeAAAAD4AAD4AAAAfAAAfgAAAD4AAD+AAAAPAAAf4AAAB8AAH/AAAAHgAA/8AAAAcAAH/wAAADwAA/vAAAAOAAP48AAAA4AB/DgAAADgAf4OAAAAPAD+A4AAAA8A/wHgAAAD8/8AcAAAAH//gBwAAAAP/wAPAAAAAf8AA8AAAAAAAADgAAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAA+AAAAAAAAD4AAAAAAAAPAAAAAAAAA8AAAAAAAAHwAAAAAAAAfAAAAAAAAA4AAAAAAAABAAAAAAIAAAAAAAADwAAAAAAAAPAAAAAAAAA8AAAAAAAADgAAAAAAAAeAAAAAAAAB4AYAAAAAAHgBwAAAAAAeAPABAAAADwA8AGAAAAPAHgAYAAAA8AeADgAAADwDwAOAAAAOAPAB4AAAB4B8AHgAAAHgPwA8AAAAeA+ADwAAAB4H4AeAAAAHgfgD4AAAAeD+AfAAAAB4e4D8AAAAHj7gfgAAAAf/PH8AAAAB/4//gAAAAH/D/8AAAAAP4H/gAAAAA+Af8AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAADwAAAAAAAAfAAAAAAAAD8AAAAAAAA/wAAAAAAAH/AAAAAAAA/8AAAAAAAPxwAAAAAAB+HAAAAAAAPwcAAAAAAB+BwAAAAAAfwPAAAAAAD+A8AAAAAAfwDwAAAAAD+APAAAAAAPwA8AAAAAB+ADwAAAAAP/////AAAA/////8AAAB/////wAAAD/////AAAAD////8AAAAAAH8AAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAGAAAAAAAAAAAAAAAAAAOAAAAAAAH/8AAAAAAf//wAAAAAD///AAAAAAP//8AAAAAA///wAAAAAAPgPAB4AAAA+A4APgAAAD4DgA+AAAAPAeAB4AAAA8BwAHgAAADwHAAeAAAAPAcAB4AAAB4BgAHgAAAHgGAAeAAAAeAYAD4AAAB4BgAPAAAAPgGAA8AAAA8AYADwAAADwBwAOAAAAPAHAB4AAAA8AcAHgAAAHwB4A8AAAAeAHgHgAAAB4APh+AAAAHgA//wAAAA+AB/+AAAADwAD/wAAAAPAAD8AAAAA8AAAAAAAAHwAAAAAAAAfAAAAAAAAB4AAAAAAAAHgAAAAAAAAeAAAAAAAAB4AAAAAAAAHAAAAAAAAAcAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAAH//AAAAAAB///AAAAAAP//+AAAAAD///8AAAAAf+B/4AAAAD/AA/wAAAA/wAA/gAAAD8AAB+AAAAfAAAD8AAAD4AAAPwAAAfAAAB/AAAB4AAAP+AAAPAAAB/4AAA8AAAP/gAAHgAAB++AAAeAAAPz4AABwAAB+PgAAHAAAPw+AAAcAAA+D4AABgAAHwPgAAAAAA/A+AAAAAAD4H4AAAAAAfAfAAAAAAB4D8AAAAAAPgPgAAAAAA8B+AAAAAADwPwAAAAAAPA+AAAAAAA8P4AAAAAAD//AAAAAAAP/4AAAAAAAf+AAAAAAAA/gAAAAAAAAAAAAAAAIAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAADwAAAAAAAAPAAAAAAAAA8AAAAAAAADwAAAAAAAAPAAAP4AAAA8AAP/gAAADwAH/+AAAAfAB//wAAAB8Af//AAAAHwH/4AAAAAfB/4AAAAAB8f8AAAAAAH//AAAAAAAf/wAAAAAAB/8AAAAAAAP/gAAAAAAA/4AAAAAAAD/AAAAAAAAPwAAAAAAAA+AAAAAAAADwAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAH+AAAAAAAA/8AAAAAAAP/4AAAAAfB//gAAAAH/Pw/AAAAA//8A8AAAAH//gDwAAAA//8AHgAAAD4fwAeAAAAeA+AB4AAAB4DwADgAAAPAPAAOAAAA4A4AA4AAADgDgADgAAAOAOAAOAAABwAwAA4AAAHAHAADgAAAcAcAAOAAABwBwAA4AAAHAPAAHgAAAcA8AAcAAABwDgABwAAAHAeAAHAAAAcB8AA4AAABwPwAHgAAAHg/AAcAAAAeH8ADwAAAB4/4AeAAAAD//gD4AAAAP+fA/AAAAAfx//4AAAAAAD//AAAAAAAP/wAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAA/wAAAAAAAH/gAAAAAAA/+AAAAAAAH/8AAAAAAA/nwAAAAAAD4PAAAAAAAeA8AAAAAADwDwAAAAAAPAPAAAAAAB4A8AAwAAAHgDwAHgAAAeAPAAeAAADwA8AD4AAAPADwAfgAAA8AOAB8AAADwA4APwAAAPADgB+AAAA8AeAPwAAAD4B4B/AAAAHgHgf4AAAAfA+D+AAAAA/D5/wAAAAB///+AAAAAH///gAAAAAH//4AAAAAAP/+AAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAA4AAAAAAAADwDAAAAAAAOAeAAAAAAAYB4AAAAAAAAHgAAAAAAAAMAAAAAAAAAAAAA="); var scale = 1; // size multiplier for this font -g.setFontCustom(font, 46, widths, 73+(scale<<8)+(1<<16)); - +g.setFontCustom(font, 46, widths, 58+(scale<<8)+(1<<16)); }; const SETTINGS_FILE = "pastel.json"; @@ -111,6 +113,8 @@ function draw() { g.setFontGochiHand(); else if (settings.font == "CabinSketch") g.setFontCabinSketch(); + else if (settings.font == "Orbitron") + g.setFontOrbitron(); else g.setFontLato(); diff --git a/apps/pastel/pastel.settings.js b/apps/pastel/pastel.settings.js index c6870cd71..b9d557aaf 100644 --- a/apps/pastel/pastel.settings.js +++ b/apps/pastel/pastel.settings.js @@ -22,14 +22,14 @@ storage.write(SETTINGS_FILE, settings) } - var font_options = ["Lato","Architect","GochiHand","CabinSketch"]; + var font_options = ["Lato","Architect","GochiHand","CabinSketch","Orbitron"]; E.showMenu({ '': { 'title': 'Pastel Clock' }, '< Back': back, 'Font': { value: 0 | font_options.indexOf(s.font), - min: 0, max: 3, + min: 0, max: 4, format: v => font_options[v], onchange: v => { s.font = font_options[v]; From 95d353a7496e638180b07bc17b6ad7443913b365 Mon Sep 17 00:00:00 2001 From: Ben Whittaker Date: Tue, 14 Sep 2021 20:02:09 -0400 Subject: [PATCH 0016/1062] Add lazy rendering support to Layout --- modules/Layout.js | 43 +++++++++++++++++++++++++++++++++++++++---- modules/Layout.min.js | 43 +++++++++++++++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/modules/Layout.js b/modules/Layout.js index a45b17107..ad033bb4d 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -1,11 +1,10 @@ - /* Usage: ``` var Layout = require("Layout"); -var layout = new Layout( layoutObject, btns ) +var layout = new Layout( layoutObject, btns, options ) layout.render(optionalObject); ``` @@ -52,6 +51,13 @@ btns is an array of objects containing: * `cb` - a callback function * `cbl` - a callback function for long presses +options is an object containing: + +* `lazy` - a boolean specifying whether to enable automatic lazy rendering + +If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically +determine what objects have changed or moved, clear their previous locations, and re-render just those objects. + Once `layout.update()` is called, the following fields are added to each object: @@ -69,13 +75,16 @@ Other functions: */ -function Layout(layout, buttons) { +function Layout(layout, buttons, options) { this._l = this.l = layout; this.b = buttons; // Do we have >1 physical buttons? this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0; + options = options || {}; + this.lazy = options.lazy || false; + if (buttons) { if (this.physBtns >= buttons.length) { // enough physical buttons @@ -234,9 +243,35 @@ function render(l) { if (l.c) l.c.forEach(render); } +function prepareLazyRender(l, rectsToClear, drawList) { + if (l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { + let hash = E.CRC32(E.toJS(l)); + if (!delete rectsToClear[hash]) drawList.push({hash: hash, l: l}); + } + else if (l.c) l.c.forEach(l => prepareLazyRender(l, rectsToClear, drawList)); +} + Layout.prototype.render = function (l) { if (!l) l = this._l; - render(l); + + if (this.lazy) { + // TODO: Handle bg colors + + if (!this.rects) this.rects = {}; + let rectsToClear = Object.assign({}, this.rects); + let drawList = []; + prepareLazyRender(l, rectsToClear, drawList); + for (let hash in rectsToClear) delete this.rects[hash]; + for (let rect of rectsToClear) if (rect) g.clearRect(rect.x1, rect.y1, rect.x2, rect.y2); + g.getModified(true); + for (let d of drawList) { + render(d.l); + this.rects[d.hash] = g.getModified(true); + } + } + else { + render(l); + } }; Layout.prototype.layout = function (l) { diff --git a/modules/Layout.min.js b/modules/Layout.min.js index a45b17107..ad033bb4d 100644 --- a/modules/Layout.min.js +++ b/modules/Layout.min.js @@ -1,11 +1,10 @@ - /* Usage: ``` var Layout = require("Layout"); -var layout = new Layout( layoutObject, btns ) +var layout = new Layout( layoutObject, btns, options ) layout.render(optionalObject); ``` @@ -52,6 +51,13 @@ btns is an array of objects containing: * `cb` - a callback function * `cbl` - a callback function for long presses +options is an object containing: + +* `lazy` - a boolean specifying whether to enable automatic lazy rendering + +If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically +determine what objects have changed or moved, clear their previous locations, and re-render just those objects. + Once `layout.update()` is called, the following fields are added to each object: @@ -69,13 +75,16 @@ Other functions: */ -function Layout(layout, buttons) { +function Layout(layout, buttons, options) { this._l = this.l = layout; this.b = buttons; // Do we have >1 physical buttons? this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0; + options = options || {}; + this.lazy = options.lazy || false; + if (buttons) { if (this.physBtns >= buttons.length) { // enough physical buttons @@ -234,9 +243,35 @@ function render(l) { if (l.c) l.c.forEach(render); } +function prepareLazyRender(l, rectsToClear, drawList) { + if (l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { + let hash = E.CRC32(E.toJS(l)); + if (!delete rectsToClear[hash]) drawList.push({hash: hash, l: l}); + } + else if (l.c) l.c.forEach(l => prepareLazyRender(l, rectsToClear, drawList)); +} + Layout.prototype.render = function (l) { if (!l) l = this._l; - render(l); + + if (this.lazy) { + // TODO: Handle bg colors + + if (!this.rects) this.rects = {}; + let rectsToClear = Object.assign({}, this.rects); + let drawList = []; + prepareLazyRender(l, rectsToClear, drawList); + for (let hash in rectsToClear) delete this.rects[hash]; + for (let rect of rectsToClear) if (rect) g.clearRect(rect.x1, rect.y1, rect.x2, rect.y2); + g.getModified(true); + for (let d of drawList) { + render(d.l); + this.rects[d.hash] = g.getModified(true); + } + } + else { + render(l); + } }; Layout.prototype.layout = function (l) { From 6bd606b64524b0ddb258b4df2d8f54b449311799 Mon Sep 17 00:00:00 2001 From: Ben Whittaker Date: Wed, 15 Sep 2021 05:58:04 -0400 Subject: [PATCH 0017/1062] Don't use getModified in lazy layout rendering --- modules/Layout.js | 21 ++++++++++----------- modules/Layout.min.js | 21 ++++++++++----------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/modules/Layout.js b/modules/Layout.js index ad033bb4d..0f1d69f8f 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -214,7 +214,7 @@ function render(l) { if (!l) l = this.l; g.reset(); if (l.col) g.setColor(l.col); - if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w,l.y+l.h); + if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); switch (l.type) { case "txt": g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1), true/*solid bg*/); @@ -243,12 +243,15 @@ function render(l) { if (l.c) l.c.forEach(render); } -function prepareLazyRender(l, rectsToClear, drawList) { +function prepareLazyRender(l, rectsToClear, drawList, rects) { if (l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { let hash = E.CRC32(E.toJS(l)); - if (!delete rectsToClear[hash]) drawList.push({hash: hash, l: l}); + if (!delete rectsToClear[hash]) { + drawList.push(l); + rects[hash] = [l.x,l.y,l.x+l.w-1,l.y+l.h-1]; + } } - else if (l.c) l.c.forEach(l => prepareLazyRender(l, rectsToClear, drawList)); + else if (l.c) l.c.forEach(l => prepareLazyRender(l, rectsToClear, drawList, rects)); } Layout.prototype.render = function (l) { @@ -260,14 +263,10 @@ Layout.prototype.render = function (l) { if (!this.rects) this.rects = {}; let rectsToClear = Object.assign({}, this.rects); let drawList = []; - prepareLazyRender(l, rectsToClear, drawList); + prepareLazyRender(l, rectsToClear, drawList, this.rects); for (let hash in rectsToClear) delete this.rects[hash]; - for (let rect of rectsToClear) if (rect) g.clearRect(rect.x1, rect.y1, rect.x2, rect.y2); - g.getModified(true); - for (let d of drawList) { - render(d.l); - this.rects[d.hash] = g.getModified(true); - } + for (let rect of rectsToClear) g.clearRect.apply(g, rect); + drawList.forEach(render); } else { render(l); diff --git a/modules/Layout.min.js b/modules/Layout.min.js index ad033bb4d..0f1d69f8f 100644 --- a/modules/Layout.min.js +++ b/modules/Layout.min.js @@ -214,7 +214,7 @@ function render(l) { if (!l) l = this.l; g.reset(); if (l.col) g.setColor(l.col); - if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w,l.y+l.h); + if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); switch (l.type) { case "txt": g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1), true/*solid bg*/); @@ -243,12 +243,15 @@ function render(l) { if (l.c) l.c.forEach(render); } -function prepareLazyRender(l, rectsToClear, drawList) { +function prepareLazyRender(l, rectsToClear, drawList, rects) { if (l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { let hash = E.CRC32(E.toJS(l)); - if (!delete rectsToClear[hash]) drawList.push({hash: hash, l: l}); + if (!delete rectsToClear[hash]) { + drawList.push(l); + rects[hash] = [l.x,l.y,l.x+l.w-1,l.y+l.h-1]; + } } - else if (l.c) l.c.forEach(l => prepareLazyRender(l, rectsToClear, drawList)); + else if (l.c) l.c.forEach(l => prepareLazyRender(l, rectsToClear, drawList, rects)); } Layout.prototype.render = function (l) { @@ -260,14 +263,10 @@ Layout.prototype.render = function (l) { if (!this.rects) this.rects = {}; let rectsToClear = Object.assign({}, this.rects); let drawList = []; - prepareLazyRender(l, rectsToClear, drawList); + prepareLazyRender(l, rectsToClear, drawList, this.rects); for (let hash in rectsToClear) delete this.rects[hash]; - for (let rect of rectsToClear) if (rect) g.clearRect(rect.x1, rect.y1, rect.x2, rect.y2); - g.getModified(true); - for (let d of drawList) { - render(d.l); - this.rects[d.hash] = g.getModified(true); - } + for (let rect of rectsToClear) g.clearRect.apply(g, rect); + drawList.forEach(render); } else { render(l); From 74e739d019e23623ebaa2d1c4bb59a7fc8bf9b2b Mon Sep 17 00:00:00 2001 From: Ben Whittaker Date: Wed, 15 Sep 2021 09:36:17 -0400 Subject: [PATCH 0018/1062] Handle bg colors correctly in lazy layout render --- modules/Layout.js | 36 +++++++++++++++++++++++------------- modules/Layout.min.js | 36 +++++++++++++++++++++++------------- 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/modules/Layout.js b/modules/Layout.js index 0f1d69f8f..e0d15f8e5 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -243,29 +243,39 @@ function render(l) { if (l.c) l.c.forEach(render); } -function prepareLazyRender(l, rectsToClear, drawList, rects) { - if (l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { +function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) { + if ((l.bgCol != null && l.bgCol != bgCol) || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { + // Hash the layoutObject without including its children + let c = l.c; + delete l.c; let hash = E.CRC32(E.toJS(l)); - if (!delete rectsToClear[hash]) { - drawList.push(l); - rects[hash] = [l.x,l.y,l.x+l.w-1,l.y+l.h-1]; + if (c) l.c = c; + + let i = rectsToClear.findIndex(r => r.h == hash); + if (i != -1) rectsToClear.splice(i, 1); + else { + rects.push({h: hash, bg: bgCol, x1: l.x, y1: l.y, x2: l.x+l.w-1, y2: l.y+l.h-1}); + if (drawList) { + drawList.push(l); + drawList = null; // Prevent children from being redundantly added to the drawList + } } } - else if (l.c) l.c.forEach(l => prepareLazyRender(l, rectsToClear, drawList, rects)); + + if (l.c) for (let ch of l.c) prepareLazyRender(ch, rectsToClear, drawList, rects, l.bgCol == null ? bgCol : l.bgCol); } Layout.prototype.render = function (l) { if (!l) l = this._l; if (this.lazy) { - // TODO: Handle bg colors - - if (!this.rects) this.rects = {}; - let rectsToClear = Object.assign({}, this.rects); + if (!this.rects) this.rects = []; + let rectsToClear = this.rects.slice(); let drawList = []; - prepareLazyRender(l, rectsToClear, drawList, this.rects); - for (let hash in rectsToClear) delete this.rects[hash]; - for (let rect of rectsToClear) g.clearRect.apply(g, rect); + prepareLazyRender(l, rectsToClear, drawList, this.rects, g.getBgColor()); + this.rects = this.rects.filter(r1 => !rectsToClear.some(r2 => r1.h == r2.h)); + rectsToClear.reverse(); // Rects are cleared in reverse order so that the original bg color is restored + for (let r of rectsToClear) g.setBgColor(r.bg).clearRect(r.x1, r.y1, r.x2, r.y2); drawList.forEach(render); } else { diff --git a/modules/Layout.min.js b/modules/Layout.min.js index 0f1d69f8f..e0d15f8e5 100644 --- a/modules/Layout.min.js +++ b/modules/Layout.min.js @@ -243,29 +243,39 @@ function render(l) { if (l.c) l.c.forEach(render); } -function prepareLazyRender(l, rectsToClear, drawList, rects) { - if (l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { +function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) { + if ((l.bgCol != null && l.bgCol != bgCol) || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { + // Hash the layoutObject without including its children + let c = l.c; + delete l.c; let hash = E.CRC32(E.toJS(l)); - if (!delete rectsToClear[hash]) { - drawList.push(l); - rects[hash] = [l.x,l.y,l.x+l.w-1,l.y+l.h-1]; + if (c) l.c = c; + + let i = rectsToClear.findIndex(r => r.h == hash); + if (i != -1) rectsToClear.splice(i, 1); + else { + rects.push({h: hash, bg: bgCol, x1: l.x, y1: l.y, x2: l.x+l.w-1, y2: l.y+l.h-1}); + if (drawList) { + drawList.push(l); + drawList = null; // Prevent children from being redundantly added to the drawList + } } } - else if (l.c) l.c.forEach(l => prepareLazyRender(l, rectsToClear, drawList, rects)); + + if (l.c) for (let ch of l.c) prepareLazyRender(ch, rectsToClear, drawList, rects, l.bgCol == null ? bgCol : l.bgCol); } Layout.prototype.render = function (l) { if (!l) l = this._l; if (this.lazy) { - // TODO: Handle bg colors - - if (!this.rects) this.rects = {}; - let rectsToClear = Object.assign({}, this.rects); + if (!this.rects) this.rects = []; + let rectsToClear = this.rects.slice(); let drawList = []; - prepareLazyRender(l, rectsToClear, drawList, this.rects); - for (let hash in rectsToClear) delete this.rects[hash]; - for (let rect of rectsToClear) g.clearRect.apply(g, rect); + prepareLazyRender(l, rectsToClear, drawList, this.rects, g.getBgColor()); + this.rects = this.rects.filter(r1 => !rectsToClear.some(r2 => r1.h == r2.h)); + rectsToClear.reverse(); // Rects are cleared in reverse order so that the original bg color is restored + for (let r of rectsToClear) g.setBgColor(r.bg).clearRect(r.x1, r.y1, r.x2, r.y2); drawList.forEach(render); } else { From aeaf1a13f1c02a958721ce2fa97afdabeff1156d Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 15 Sep 2021 21:38:21 +0100 Subject: [PATCH 0019/1062] screenshots and README file for Pastel app --- apps/pastel/README.md | 19 ++++++++++++++++--- apps/pastel/pastel.app.js | 19 ++++++++----------- apps/pastel/screenshot_architech.jpg | Bin 0 -> 38320 bytes apps/pastel/screenshot_b1_light.jpg | Bin 0 -> 46044 bytes apps/pastel/screenshot_b2_dark.jpg | Bin 0 -> 52318 bytes apps/pastel/screenshot_gochi.jpg | Bin 0 -> 40511 bytes apps/pastel/screenshot_lato.jpg | Bin 0 -> 46058 bytes 7 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 apps/pastel/screenshot_architech.jpg create mode 100644 apps/pastel/screenshot_b1_light.jpg create mode 100644 apps/pastel/screenshot_b2_dark.jpg create mode 100644 apps/pastel/screenshot_gochi.jpg create mode 100644 apps/pastel/screenshot_lato.jpg diff --git a/apps/pastel/README.md b/apps/pastel/README.md index 9a227f51d..9e8c133ec 100644 --- a/apps/pastel/README.md +++ b/apps/pastel/README.md @@ -1,4 +1,17 @@ -# Pastel Clock +# Pastel Clock - a configurable clock with custom fonts and background + +* Designed specifically for Bangle 1 and Bangle 2 +* A choice of 5 different custom fonts +* Supports the Light and Dark themes +* Has a settings menu, change font, enable/disable the grid and the date display + + +I came up with the name Pastel due to the shade of the grid background. + +![](screenshot_lato.jpg) +![](screenshot_architech.jpg) +![](screenshot_gochi.jpg) + +![](screenshot_b1_light.jpg) +![](screenshot_b2_dark.jpg) -A Configurable clock with custom fonts and background. Designed -specifically for bangle 2 diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 0f4be60d9..0c1d56118 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -16,11 +16,11 @@ g.setFontCustom(font, 46, widths, 65+(scale<<8)+(1<<16)); }; Graphics.prototype.setFontGochiHand = function() { -// Actual height 49 (53 - 5) -var widths = atob("DRQeExgXGxgbGRobDg=="); -var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAAA/gAAAAAAA/gAAAAAAA/gAAAAAAA/gAAAAAAA/gAAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+AAAAAAD/+AAAAAAP/8AAAAAA//gAAAAAH/8AAAAAAf/wAAAAAB/+AAAAAAP/4AAAAAA//AAAAAAH/8AAAAAAf/gAAAAAD/+AAAAAAP/4AAAAAB//AAAAAAf/8AAAAAA//wAAAAAB//AAAAAAB/4AAAAAAB/gAAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAf/wAAAAAB//8AAAAAD//+AAAAAH//+AAAAAP4D/AAAAAfwB/AAAAAfgA/gAAAAfAAfgAAAA/AAfgAAAA/AAfgAAAA/AAfgAAAA/AAfgAAAA/AAfgAAAA/AAfgAAAA/gA/AAAAA/gA/AAAAAfwB+AAAAAf4D+AAAAAP//8AAAAAH//4AAAAAD//gAAAAAB/+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AAAAAAAD8AAAAAAAD8AAAAAAAD8AAAAAAAH8AAAAAAAH4AAAAAAAH4H+AAAAAPz//AAAAAP///gAAAAf///gAAAA////gAAAA////gAAAA/wAfgAAAA4AAPAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAADgA/AAAAAHwA/gAAAAPwB/gAAAAfgB/gAAAAfgD/gAAAA/gH/gAAAA/AH/gAAAA/AP/gAAAA/AffgAAAA/A/fgAAAA/B+fgAAAA/j8fgAAAA//4fgAAAA//4fgAAAAf/wfgAAAAf/A/gAAAAP+A/AAAAAD4B/AAAAAAAB+AAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAH8AAAAAAAP+AAAHwAAP+AAAPwAAH/AAAPwAAD/AAAfgAAB/AAAfgAAB/AAA/ADAA/AAA/AHwA/AAA/AH4A/AAA/AP4A/AAA/AP4A/AAA/Af4A+AAA/g/8B+AAA///8B8AAA///+D8AAAf/7//4AAAP/x//wAAAH/A//gAAAD8Af+AAAAAAAH4AAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAA/AAAAAAAD/gAAAAAAP/gAAAAAAf/gAAAAAA//gAAAAAD+fgAAAAAH4fgAAAAAPwfgAAAAAfgfgAAAAA/AfgAAAAB+AfgAAAAD8AfgAAAAH4Afj8AAAPwAf/+AAAPwP///AAAf/////AAA//////AAA//////AAA////h/AAA//wfgeAAAPAAfgIAAAAAAfgAAAAAAAfAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAPwAAAA/8Af4AAB//8Af8AAB//8AP+AAB//8AH+AAB//8AD+AAB+D8AD+AAB+D+AB+AAB+D+AB+AAB+B+AB+AAB+B/AB+AAB+B/AB+AAB+A/gD8AAB+A/wD8AAB+A/4H4AAB+Af//wAAD+AP//gAAD+AH//AAAH8AD/+AAAD4AA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//AAAAAAP//4AAAAA///8AAAAD///+AAAAH////AAAAf/4H/AAAA/nwB/gAAB/PwA/gAAB+PwAfgAAD8fgAfgAAH4fgAfgAAH4fgAfgAAPwfwAfgAAPwfwAfAAAfwP4A/AAAfwP8B+AAAfwP/H+AAAfwH//8AAAfgD//wAAAfAB//gAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAB/AAAAAAAB/AAAAAAAB/AAAB8AAB/AAAf+AAA/AAB//AAA/AAH//AAA/AAf//AAA/AB//+AAA/AH//+AAA/Af/z8AAA/A/8A4AAA/D/wAAAAA/H/AAAAAA/f8AAAAAA//wAAAAAA//AAAAAAA/+AAAAAAA/4AAAAAAA/gAAAAAAA/AAAAAAAA8AAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfgAAAAgAB/8AAAD7wD/+AAAH/+H//AAAH//P//AAAP/////gAAP//+B/gAAfx/8A/gAAfgf4AfgAAfgP4AfgAAfgH8AfgAAfgH8AfgAAfgD8AfgAAfgD+AfgAAfgH/AfAAAfwP/AfAAAP+//g+AAAP//f/+AAAH/+f/8AAAD/8P/4AAAB/wD/wAAAAeAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+AAMAAAB//gB+AAAH//wB+AAAP//4D+AAAf4P8D+AAAfgH8D+AAA/AD+D+AAA/AD+D8AAB+AB+D8AAB+AB+H4AAB+AB+H4AAB+AB8PwAAB/AD8fgAAB/gD8/AAAA/wD7+AAAA/8H/8AAAAf///4AAAAP///gAAAAH//+AAAAAB//4AAAAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAD4A/gAAAAH8A/gAAAAH8A/gAAAAH8A/gAAAAH8A/gAAAAH8AfAAAAAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); +// Actual height 54 (59 - 6) +var widths = atob("GRMtICcqJiopKiwoGQ=="); +var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAA+AAAAAAAAAAAAfwAAAAAAAAAAAH+AAAAAAAAAAAB/gAAAAAAAAAAAf4AAAAAAAAAAAH+AAAAAAAAAAAB/gAAAAAAAAAAAP4AAAAAAAAAAAD8AAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAP+AAAAAAAAAAA//gAAAAAAAAAH//4AAAAAAAAA///+AAAAAAAAP////gAAAAAAB/////4AAAAAAP/////+AAAAAD//////+AAAAA///////wAAAAAf//////AAAAAAP/////4AAAAAAD/////AAAAAAAA////4AAAAAAAAP//+AAAAAAAAAD//gAAAAAAAAAA/8AAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAAAAAAAAAAH//AAAAAAAAAAH//8AAAAAAAAAH///wAAAAAAAAD///+AAAAAAAAB////wAAAAAAAA////+AAAAAAAAf////gAAAAAAAP/8//8AAAAAAAD/wAf/AAAAAAAB/wAD/4AAAAAAA/4AAP+AAAAAAAP8AAB/wAAAAAAD/AAAf8AAAAAAB/gAAD/AAAAAAAf4AAA/wAAAAAAH8AAAP8AAAAAAB/AAAD/AAAAAAAfwAAA/wAAAAAAP8AAAH8AAAAAAD/AAAD/AAAAAAAf4AAA/wAAAAAAH+AAAP8AAAAAAB/gAAD/AAAAAAAf4AAA/wAAAAAAH/AAAP4AAAAAAB/wAAH+AAAAAAAP+AAB/gAAAAAAD/wAA/wAAAAAAA/+AAf8AAAAAAAH/wAH+AAAAAAAB/+AH/gAAAAAAAP/4D/wAAAAAAAB////4AAAAAAAAf///+AAAAAAAAD////AAAAAAAAAf///gAAAAAAAAD///wAAAAAAAAAP//wAAAAAAAAAA//4AAAAAAAAAAD/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAAAAAAA/gAAAAAAAAAAAf4AAAAAAAAAAAP+AAAAAAAAAAAH/gAAAAAAAAAAB/wAAAAAAAAAAA/4AAAAAAAAAAAf8AAAAAAAAAAAH/AAAAAAAAAAAD/gAAAAAAAAAAA/wAAAAAAAAAAAf4AAAAAAAAAAAP+AAAAAAAAAAAD/AAAAAAAAAAAB/wAAAAAAAAAAAf+AAAAAAAAAAAH/4AAAAAAAAAAD//8AAAAAAAAAA////4AAAAAAAAP/////gAAAAAAB/////8AAAAAAAf/////AAAAAAAB/////wAAAAAAAP////8AAAAAAAAP////AAAAAAAAAH///wAAAAAAAAAAf/4AAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAHwAAAAAAD8AAAD+AAAAAAB/AAAB/gAAAAAA/4AAA/8AAAAAAf+AAAf/AAAAAAH/gAAH/wAAAAAD/wAAD/8AAAAAB/4AAB//AAAAAAf8AAA//wAAAAAH+AAAf/8AAAAAD/AAAP//AAAAAA/wAAD//wAAAAAP4AAB//8AAAAAD+AAA///AAAAAA/gAAf8/wAAAAAP4AAP+P8AAAAAD/AAP/j/AAAAAA/wAH/w/wAAAAAP+AH/4P8AAAAAD/wD/8D/AAAAAA//P/+A/wAAAAAH////AP+AAAAAB////AB/gAAAAAP///gAf4AAAAAD///wAH+AAAAAAf//wAB/gAAAAAD//4AAf4AAAAAAP/4AAH+AAAAAAA/wAAB/gAAAAAAAAAAAf4AAAAAAAAAAAH+AAAAAAAAAAAA/gAAAAAAAAAAAPwAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAHgAAAAAAAAAAAD8AAAAAAAAAAAB/gAAAAAAAAAAAf8AAAAAAAAAAAP+AAAAAAAAAAAD/gAAAAAAAAAAB/wAAAcAAAAAAAf8AAAPwAAAAAAH+AAAD/AAAAAAB/gAAA/4AAAAAA/wAAAP/AAAAAAP8AAAD/4AAAAAD/AB+A//AAAAAA/wA/wH/wAAAAAP4AP8A/+AAAAAD+AD/AD/gAAAAA/gB/wAf8AAAAAP8Af8AH/AAAAAD/AH/AA/wAAAAA/wB/gAP8AAAAAP+Af4AD/AAAAAD/gP+AA/wAAAAAf+H/gAP8AAAAAH///4AD/AAAAAB////AA/wAAAAAP///wAP8AAAAAB///+AD/AAAAAAf///gA/gAAAAAD///+Af4AAAAAAP/n/4f+AAAAAAB/g////AAAAAAAAAP///wAAAAAAAAB///4AAAAAAAAAP//8AAAAAAAAAB///AAAAAAAAAAP//AAAAAAAAAAA//gAAAAAAAAAAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAAAAAAAH+AAAAAAAAAAAH/wAAAAAAAAAAD/+AAAAAAAAAAD//gAAAAAAAAAB//8AAAAAAAAAB///AAAAAAAAAA///wAAAAAAAAAf//8AAAAAAAAAf/7/AAAAAAAAAP/4/4AAAAAAAAP/4H+AAAAAAAAH/8B/gAAAAAAB//8Af4AAAAAAA//+AH+AAAAAAAP//AB/gAAAAAAH//wAf4AAAAAAB///AH+AAAAAAAf//+B/gAAAAAAH///+f4AAAAAAA/////+AAAAAAAH/////wAAAAAAA/////8AAAAAAAAf////8AAAAAAAAf////+AAAAAAAA/////4AAAAAAAA/////AAAAAAAAB////wAAAAAAAAB///8AAAAAAAAAD///AAAAAAAAAA///wAAAAAAAAAP//4AAAAAAAAAD/D8AAAAAAAAAA/wAAAAAAAAAAAH4AAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAf4AP/wAAAAAAAf/AH/+AAAAAAAP/4D//wAAAAAAD//A//+AAAAAAB//wP//wAAAAAAf/8D//8AAAAAAH//g///gAAAAAB//4H//4AAAAAAf//AAf/AAAAAAP9/wAD/wAAAAAD/P+AAf8AAAAAA/j/gAD/AAAAAAP4f4AA/4AAAAAD+H/AAH+AAAAAA/h/wAB/gAAAAAP4P+AAf4AAAAAD+D/gAH+AAAAAA/g/4AA/gAAAAAP4H/AAP4AAAAAD+B/wAD+AAAAAB/gP+AA/gAAAAAf4D/gAP4AAAAAH+Af8AH+AAAAAB/gH/gB/gAAAAAf4B/4Af4AAAAAH+AP/AH8AAAAAB/gB/4D/AAAAAAf4Af/h/wAAAAAD+AD///4AAAAAA/gA///+AAAAAAP4AH///AAAAAAD+AA///gAAAAAA/gAH//wAAAAAAH4AA//4AAAAAAAMAAD/8AAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAH//wAAAAAAAAAH///AAAAAAAAAD///4AAAAAAAAD////gAAAAAAAB////8AAAAAAAA/////gAAAAAAAf////4AAAAAAAH/4B//AAAAAAAD/wAP/4AAAAAAA/4AD/+AAAAAAAf8AB//wAAAAAAH+AAf/8AAAAAAD/AAP//gAAAAAA/wAD//4AAAAAAP8AA/n+AAAAAAD/AAf5/gAAAAAA/wAH8P8AAAAAAP8AB/D/AAAAAAD/AA/w/wAAAAAA/4AP8P8AAAAAAP+AD+D/AAAAAAD/wA/g/wAAAAAAf8AP4P8AAAAAAH+AD+D/AAAAAAA/gA/g/wAAAAAAHwAP8P8AAAAAAAwAD/D/AAAAAAAAAA/x/wAAAAAAAAAP//4AAAAAAAAAD//+AAAAAAAAAAf//AAAAAAAAAAH//wAAAAAAAAAA//4AAAAAAAAAAH/8AAAAAAAAAAB/+AAAAAAAAAAAH/AAAAAAAAAAAAeAAAAAAAD+AAAAAAAAAAAA/gAAAAAAAAAAAP4AAAAAAAAAAAD+AAAAAAAAAAAA/gAeAAAAAAAAAP4AfwAAAAAAAAD+AH8AAAAAAAAA/gB/AAAAAAAAAP4AfwAAAAAAAAD+AH8AAAAAAAAA/gB/AAAAAAAAAP4AfwAAAAAAAAD+AH8AAAAAAAAA/wD/AAAAAAAAAP8A/wAAAAAAAAD/AP8AAAAAAAAA/wD/AAAAAAAAAP8A/wAAAAAAAAD/gP8AAAAAAAAA/4D/AAAAAAAAAH/A/wAAAAAAAAB/4P+AHwAAAAAAf//////AAAAAAD//////wAAAAAA//////8AAAAAAH//////AAAAAAB//////wAAAAAAP/////8AAAAAAB/////+AAAAAAAH/////AAAAAAAAAP+AAAAAAAAAAAB/gAAAAAAAAAAAf4AAAAAAAAAAAH+AAAAAAAAAAAB/gAAAAAAAAAAAf4AAAAAAAAAAAH+AAAAAAAAAAAB/gAAAAAAAAAAAP4AAAAAAAAAAAD+AAAAAAAAAAAA/gAAAAAAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAAAAD//AAAAAAAAD+D//8AAAAAAAD/9///gAAAAAAB/////8AAAAAAA//////AAAAAAAf/////4AAAAAAH//////AAAAAAD///4H/wAAAAAA///4Af+AAAAAAP4f+AD/gAAAAAH+D/gAf4AAAAAB/A/4AH/AAAAAAfwP+AA/wAAAAAH8D/wAP8AAAAAB/A/8AD/AAAAAAfwP/AAfwAAAAAH+D/wAH8AAAAAB/g/8AB/AAAAAAf4P/AAfwAAAAAH+D/wAH8AAAAAB/w/8AB/AAAAAAP+P+AA/wAAAAAD/x/gAP8AAAAAA///8AD+AAAAAAH///gB/gAAAAAB///4Af4AAAAAAP///gP8AAAAAAB///+H/AAAAAAAP/////gAAAAAAB/////4AAAAAAAH////8AAAAAAAAAH//+AAAAAAAAAA///AAAAAAAAAAD//gAAAAAAAAAAP/wAAAAAAAAAAA/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+AAAAAAAAAAB//wAAAAAAAAAA//+AAAAAAAAAAf//wAAAAAAAAAH//8AAAAAAAAAD///gAAAAAAAAA///4AAAAAAAAAf4H/AAAAAAAAAH+B/wAAAAAAAAB/AP8AAAAAAAAA/wD/AAAAAAAAAP4A/wAAAAAAAAD+AP8AAAAAAAAB/gD/AAAAAAAAAf4A/wAAAAAAAAH+AP8AAAAAAAAB/AD/AAAAAAAAAfwB/gAAAAAAAAH8Af4AAAAAAAAB/AP8AAAAAAAAAfwD/AAAAAAAAAH8B/wAAAAAAAAB/Af4AAAAAAAAAf4P8AAAAAAAAAH+H/AAAAAAAAAB/j/gAAAAAAAAAf//////wAAAAAH///////gAAAAA///////8AAAAAP///////AAAAAD///////wAAAAAf//////4AAAAAH//////+AAAAAB///////gAAAAAP//////wAAAAAB/gAAAAAAAAAAAfgAAAAAAAAAAADwAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAACAAAAAAAAAPgAD4AAAAAAAAH8AB/AAAAAAAAB/gAf4AAAAAAAAf4AH+AAAAAAAAH+AB/gAAAAAAAB/gAf4AAAAAAAAf4AH+AAAAAAAAD+AA/gAAAAAAAA/AAPwAAAAAAAADgAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); var scale = 1; // size multiplier for this font -g.setFontCustom(font, 46, widths, 54+(scale<<8)+(1<<16)); +g.setFontCustom(font, 46, widths, 80+(scale<<8)+(1<<16)); }; Graphics.prototype.setFontLatoSmall = function() { @@ -102,10 +102,7 @@ function draw() { g.drawLine(0, gy, w, gy); } - if (process.env.HWVERSION ==1) - g.setColor("#fff"); - else - g.setColor("#000"); + g.setColor(g.theme.fg); if (settings.font == "Architect") g.setFontArchitect(); @@ -130,10 +127,10 @@ function draw() { g.drawString(":", x,y); } else { // on bangle 1, we are not using clearRect(), hide : by printing over it in reverse color - if (process.env.HWVERSION ==1) { - g.setColor("#000") + if (process.env.HWVERSION == 1) { + g.setColor(g.theme.bg); g.drawString(":", x,y); - g.setColor("#fff"); + g.setColor(g.theme.fg); } } diff --git a/apps/pastel/screenshot_architech.jpg b/apps/pastel/screenshot_architech.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b13ecc54a6fd52af488849869490b4924b5c9a9e GIT binary patch literal 38320 zcmb@tXH*nH*Dl&HLl{7E&S6MHkStj;GsFRgBp^BGC{bYulEVNaAWDW|2m+E3BxgZ# zRwSq(C?Fz&;qrcGed{|v&RzG%xw}_aKV5t8r*~C%bysy)|6BdH4N&XpAano%0stVm zS%7~#ghB{SO?#xNu@1sO`@a-~%)pHhQUidmUr3Ott_HW2^*!$Y6Z)TH{YU;2F@>w2x!KLu>SkpJ zf&o)N7tpxD|G&!r)Te(r0LcFU08sY-;vN<0vdpjh5$rE@NXF4x+w%P z=tlkD6##(1M8qVdgb*?Ufc(Zlo*Ez|026>fp#LObLJ&Yecw<6COH4w?1Mo_c(n~=Y z_|!ERrA?sxj!ev^PO^cTab-82>^DsX4Ei79o6~jU`(NxgT4kF5?0|@gz=Q;Z#5c46 z0Sym8D5-AZ7zm<`%PBhn6EzRa5%YdHrIVuPyWy8HbqZpDnxO~(tpgM{yo59$8sIKa z$CQsRUw!4#0>z{F%S-@smF&MBN zP192kidaa>bdzh-Tz7KwhZlX=B>`L)u{@3G>*j$O;AF$bb@5kie; z8;9U?lTAZct%U_m88c%;-mv||mxj`{{{TNzw;HyihbAA~R^bn-v^5w~c~KrTrV~~* zA0G(zqzrp%+^us+|7*t+J5!;XmqX)!pcI{^jFP-Hzod9(7}eRhBT(H0r4mN zb#!yAX7I7-yxiQ}a!o7Y>!5e45T0bo4AK)>em06bt8|i*KpM5KvQoH6YsOpgrK9u>kEGfsz0yu^_z!ya{VTp=pCR@k6TcqZf;E378=O<(4FDNBR z&y4O+O>Q|8%gkOIW$IH)0AWBlxMQ}58>ou?2=gl{mM|qDwfngYCP&v^6yDs|9T^FX(X~#_NB$q zbeqgq6w#udI;VQ5j}J4$3~_W4=t^)tkr3h8m0oY|39G|YRlAIn;|3W-;&YDWhaC3e zvbQUkh%4x8tO6jy?_T3B*I)SZgpWGe0z2Agmi?xWILxhfU4Zm_g|l0)n0IyV(ufUq zIbVLWlI9@#3l`-S+Gga|Ye*DBOIhX5#Nwc(zx)-*6eh~}81Gp>l&aSca0;FwNk5Yx z%FA@4PBMHcsOjgeK){iXBJ=O@pG$*P_&m$y~yOHeBBXKB! zcYwMpqd!JqnJhE${Li~`dBG&~F{A=MRutY9^Oe~>0$T0=b61^jY%9cYdX9NHWJb`J z4?{?8b#Q>$T5cwiFL;^f9`HrFEaxLyXnIG1)6%1aP`GMw5!3%XfrZZ;G3oF_)%V3s-7cdfu!0P%Ft4{&`z;ym>_gUG}tx&!=uJCvi~EIibJZl!I+!n*L|E^3L1tBfwqmp}e>KJ) zF7wsN_fR6cH5lvQ!umLIgGM4xB%AJD3F>gJ>+&B!)CbXZ(l>vb6OW<^q<>T!J-N{T z`NdB^6MIANHWS03jOUSsJumgPawzCqn^t79_|?MSJy#5MH(yH~W{+I|%{b}X_w}$s z#f~t;t85o}3i$l2;sztlp0IDg7iUoqD}FsO@srJyL*S31je@koiXxu&n$c=r9In7V zzs72LL{)u`k9nytH!rKNP0{SjMnxh7OhW_~l_Qtem`7*ZxVeRO9*V5b^FW+TlWa27 zkbNYfp=qqxw-o5(J)p)XTh3I<=Jw0ANg}pdv9V^$R5ru}w7k63hGI6xfaW2>x01_S zUsueat}mSr?wIQwmtLQ>Yl6=s85a#GjZ_9jci;c`il*Cba$?ESJKx6#W3?;ULkOxu z@}E%@EDm)!uu-THsGW?FxeFUCWuw@*Ep0BE?lNW6z$`13dlDD8-9%tE5m2W4!rIBPq^+mSPCZM{IzR8RLm3u6YK|7(%41|U zCAf8bIwUSAN5*74?F^@a&)6!z}0&N~7 zbq!SRhGvn?68V;$?z;3MALk^63P_9FdaY^%W7i125-0NuVH<43y_$@7BTJ%3?piU+ zztYdHWiHPwLp@3y&C7m_?9{1ct7BTE*S+4`~%Klnp_|s#&gIBrAjL+agw-+Pg-lfz8!p=q1Anf?u=J9 zztD__L7L5VMWk6{eCgvFPAHtKI9vltHagjd?W?&8_T07guVJ5&+vy-@GrYyW+~@HR zaC_nPv@LY4y0#8qljG~g;$30|tCWe!6Cmq+XNBuUo^rBJLkLC+;bSt@+5Kn}&xxfG*?}C-{Ar+K^fN$7ct--b2$iFbiWSU!)XyttHzzE^e-6j7iI?@%u~fQ6YezVbQi%?rL;^YkFRTKqr;$ zwEsHvyxc1NMmMCnHP3pTU}$5_(!FS=O*oUHym%hRSOnqF%9pispNb|uxn{W&D-AGs z;c%(tl`uTxolN)L-v+tI?MbGiPDJsvRgCy&^OSgeb+g$cW@|6&C=beV1c20M05`)| z<*fBuW2G7seog5MA%T^?_g2HRQcbe8_h)AUp7>Vb`!N386oO^4C1l2Tn7=6XIB8Q*s_A{+(ZM3nV?Q+OROcMn_pE7FL%C{NP(ZDccFw# z9@fQfUl<|^cFbxkz*q)U^ba2Q4XxL9bZolP#*_PXFwQ!q>AY=h#@@{V)7q2x_8v;e zEWyDXi;88x9$u-5xwRq$-un_z?Fp%;V_72>#RGyqsRr-SoRpRJoTfqNV1Mcj^giz; zE>O}B+tM_WmkZ#utt@!-vN3_+^lPn z36eVj*ThwBEPgC$ij&?NKb!Z z#VcFMP07dDM6`D_9ZI@1o^(fxR^(?zj!T#sxN$@yk>$2g=hG>fAmiu^+UHdV`+h1o zT!WPW77Njq-D!#RiuectFY!Y5Y9h=>WDPuj#}zqQW#@aD71JYx0sMC+mFCCv9GnMr zF^3jEc%N_G4U=#1<)Zu~i#<%pr^paHvtJb!@j0Jc$WNd0>Ik|w+G#dncnhz`;`FK# zB1b+#3yq^;BmDD9>G+XRFKf{^W1>%o_BDBorcD!eVHMktkZt)i!r81vUNDBw$0~+) zgYpHkigd=l&kYlv+|$KDyD{5a_OjfY)Z zTEqY8<6(51aM&<613j6{r#xQ!zNdNYY{&d(_Y zXD#$3o`0{#&Xk+|369|m)o%R4XL9m4Ka?(iQ=zTZv{&tYexRyv|0ToO=*tEXR-q9N z_su^eJOZ*g90`c9clW9^(?>znQt|%)`c$10fS!))r>t5~TILrtz}x-vMTgxHW`c;} zD~j~0GSAZb;TBeb1?2?cWe(L|L~vdPa2QcIU?!=B9v=U8#RLG9l!wK(2)B6rmt%3tp*$3?3Hbz7)h)B2Wwz~wKj`OfzYJ1=JSysj^PIx zzOQ3LSsSAslNOPQ7-CPQQ3$h`Rc0G=L?7^r;y-`> zP=KKQdC!_*`L5Mk(t+}5Hi9f(?^N;kZ*`oPfMj9(`0;U=HF!5@QQN9A!HD_xKCE?~ zv_L@XtdXCkfJo!_U|RI=fj!IG(gyk4VXwS?{$Tr3WvEgy5 zTU@ea^)|=oMO?tz%ugQvl%Y80Mq|gslo~h;<`$h*^Uu1>em7Lc8K1ucuo|>8skO_KeE+**b}079hHjXh83G2Qx!WGkH|ULsU+QoO=L2I2z<~qWS8H&`W3*35N5@z z_pt;3g+vpwudI(xqu?8D)Wvf4qE^v?&3haLvTqff!|r)mo)HA1aJkM(S{dz2<3|8M zy($9QA-K%<{*$^{9s*06#~r?lvRbWFBa#0Zx9c!)8$St%&;*wk# zdnj27_QDrsN_@*ynM}Hy8|?u52jolvtM()mHh_qn8VR%|fnX)gl|WXamfpCB&z+lb z$booeK$KV&!O5KOAib?HG?+f0W^LnYsmbOeLt!2(?~v65r3(7wZg!a-YpyX*>}}tw zPm#$?fPd(nejVTZ{dWQm96@r^cF{>tGAzH6@)kxq^SMT8J|B@BP+txBAb z&izr-(~lix{57a7ZS(+Xoef%-X90)vXG-Zv?}FhZAL70SzZCMv@-qH1FuI#O;g)^t z4<5Bwo2g9OrEqaXRZM_^gn~??BU$pOt@erzI`i%a3P%4yic|qAya=ek;fC4H zbw7)#eck2Mf*U@2L(DC{X#1Gzn)p;t>ENs#tun1RLa;Jl6UWCLSd3Bw(WE(fz(uW% zvt}3}4RmpNIr-{zAP0kJFvCJ6WZJ|ZySi*pcjNl`pFntNhmSjv1kT-XHNXD*aX4+g zFg{83XXP?7q1efUx^~fXOPB&jwR4@8M#}-YSre~E+^JA*CkCP>nW$mip`W8>=$*n6 zXGA?301Q`%O&qy7sa1BpJ$nkr`(H&RLNyc*wW$jDCzf)oUNTpSK%1noFh+Qldw5#v zHG-a&?!%vJ>hx_|jtaLD&EBZaEwg`sPFHC3g2lSW?h`a>QCa=tOXLH$7rQzUBD}Tv zz9&sD+U7;g|D@S6-3#-I@@+&XJ1ScY!zua!h*%uMPt79sPogVCx}g~uvy#q?a_!`c zG0)Ti^Npi7pwZl+dcSgOet>9e4_>5VC3&2VsO>3e<8kfMSy}Ft&h+bkGv#)8*D9h$ z!tWn&{L}l-WR!a}>9kz9K)7hDgH-Jsjz$=qyd}j2qaau-W_s4+9{timj+4!un^da> zcW+wl$9)g(cdk3tr>eXW6XI}!b!4ywbm$h+PyS#i!E2YDp?o)avXPXL0oUPEK1_h}Y5VMCI;>?GlX5JC7 zXaoc>13_v$f!PfDRGt&OwA-A295u25Zh4~S=GE|_{8cQJ==RsUSO{{O;Jm}Fe~7~a z!uLulK9F>k$ZheK<)_&h0(lmYpsAD`-`UKF3-pe^ee%QWPa7JQKCx6pgiQdo8jKk7 zi&Lc^gKD~n4O*j>0=w~Ee)&$J#&i9lytnj9tv+Ed9|U9m(pMG69BTK3gyq+Os>Fc7 z1A$4fD`76^U6Tk>Fa&Pmg3*pf#MUESpU!|Bc9jT$fIT4iV7TBcc>w1b3bJdmu`nrR z3->uB?~8`7t?()rvnNq?iAP>+@eF8CsqpQWOFc1HWrG>SGfxm8xPhAf!GoM*^RwWC z*ICNGlRwdrq#s^@RyX!b=^Lx2nz!}t6QY0ljPg6n@UQaQ}JJ zb|Q}-GHkq+;bZ)SBP9Y>!9y?XKc3#MzF6x-;3PLdW*uw)2$!v`rFZat8qDi4%3xO& zoG+wL$i$2yF_q}FI3p}(IO!CFXjF}qZU$lneS8Ryi2Hv{iNA@Jb8II}KV&Bi=ro~| zDgRwk9PK$B!LN>t@(TV61@RKfkekEmKyHHuvBl8?Lz#hrVk7Q&x=dPbgsGO|vd zrxj_~C+Fr*J8}+e0)!+#EMh=@>38}KngTJ_TDH@z1Z_kf5n*q?8`NhwhdXY4Hf@dz zIiBJ)cSR!H`oVA=b)5k)tre~M$I`pSkssZQ%=LxA%dqf!!$|s){y3 zy64|}9~{6Kg%0^@dys3F$6{f*^||wKEMAP~TE3D_5n;|-z^>VI!}?e@j&PgZjbT-gx-YU zt4WhufP^`G%3Z;9SyS-aPY_*xWY|xMFmf**R}#1MVyOsLv`*)cmyyInqYY;ejM^Mx z?(6umfI2JrT=Vj`<_w1Jh_tM!jEVISL7)!EA;y7bRpVA-mLLA^!Ubv36=$4pA{g&ibS#2yg5 zA8NK&MX)e)0~OXRso=KldqJ|fWmr8uGAv``9s&rASM#7;@)J-Ahqj@hf{G4Iz5 zFkQ?bmvm`DTP_7C&B3FrR&m#&L$hw%NR4+nq;w)n{thFOjlSqS)R8qyMGQI{B?Mc8 zpmk6Vu{SS77UfEX_+O8$Kiaq>Zuw_Ow4NX;L;9i~My8M^BBMrc4<-Vrcv^_@_%0r+ zJ-Gl&q?-NiY=qnXuY?H6Z_Q*m8^wSN$7`ZBUe9mmb11@gd&g=SsoaiCbfceT^HszcW)STp*Le$WE z=$DNPhupV-?l^30+ogJVuhzHC@ud_2I&8Jsm2Z)v@}A|A89hIdwp6P;)CA-`Froy% zeI40Dx@23cS;iA98rS2ROcYa>{T^f+%{0_M>ESAfzR*2{3O>DmhCX{M4>&$PXF~vl zhOc}8ZJ1*}*Y4Yb6LiHT5KJe{7VTAF{sQK^GNB2n)cr&=y)U6zMCbdQlHuKDDnxeIEtgnGLS zZ1Z~a)-zzy1&}+8*#L#yMY{A5@#%-?doh-$m}^T(v)xKl8ZKV@!G%9 z0-;Mfat2bDYse#FDUChhA)bjJELHDxwGz3k+!$V2h)uL)HX!#WpQ#vG+rg_; zBkdUy8gd>@tNQB5EETH1h2aErM57h@(;`w}I*hUr?Cs`=6@?Pm$=LAaGwgICosn!< z&9tV6sfIb3KsN<}*u%g61BYpLfJQ9rST~>Mk-NtU(a+=yzT$Jr=x1 z;rUQx?0A2}P_!x^1a`vImtiFJYYjDL@O-Fm=;i1u%Mhdub>{g8Kz>D#g*lUfx*=k^Hc#4i zUi<_2b_oAWC7lovz2&{P`J6G*@?7Pde7Q&4@ZId3*E=EDs{zDa__xZ`*8H-^B^zP( zQl@8}t`dGYhV11H5!q0sJ)yZ%YL_{(PWP)j{X7VMaKc~QtF^-H;i~WNshO!48G@$u zkyt%^Jq6s|JALcsCCZZLBN=vR5H{)ha{YR)H>2}!CP%OJ|bf77Y&OX~awlpWqd3bVP%V^#0ik$4E-O3Yl* znw4=Ebj!D-;w@0ucD8s_{#%L`5T~Xm0crn;*Z&lyKS{6?k3{$>y3BU|P1|HdyguG? z?HK||q4SGXF)iju=&X-=Ol4Z94W&J^B#4c#>${6+PUAB3cVm2t3_@3yCSBnZgKGqlM2tn!T zy4_odA!&YxboCim9w04ZPb_`^`v{L4x5__EOcP`H%+nL{hv5Y&=I_LE6!bGOFRpb}bw&jH$ zI$Cp6`no!BPuoY@jnyz6-)E)=zYnX*L-8GFFpU8d4fwO7$bsr%7fC772jLZKrYxfm zWAi7`bO0s61KTv-Au3g@*Esr1zyNGN4@*SC`Yrm8{8x+i26V~IrEL7YmqTkYrhCR_ z!oyuwCbW(I4v!FS$Jf|sF}MGurKp^_ave6pZZ>7+eJ;LJNWy>o*Ob*IwtP>vm{x4sSn8Is~|B?W}2dZVtNts7Pp=<5XGe?4lu~$_lBHHScSJENRbndf0 zY&r?e({fSg*YY>FM^Gm{4Y$ThxfR-q+Kn0SrCjlOButWn;uPeLE%_3tBN_)?fvktR zCKf;MavhZXK-kgXTTY(d<*ufi(#vlbFAcdm(4(v*zL%^ZU~QFT61$+bdR^d|UlG>O zdL=S4Nw248?)t~Zwm;=jNlPA1B$-G<(ShKLmj=V^`Sw*u`-n{NIre~i*%G23z0)>7 zw&8^DD({`m{VwBZg+Sv{y_i}H{=As$ld?7#w0_Cf9+a9ATM_Rf;i)|2Qu|tD(*%Ua zj^wTjzrK4g3q2Tvb_G(}cwv{ueMUSRmzTrskKH_+YcuY9S$lE2dkCZynmSEdiDdV` zXwV-GSNJXZ-RD!@ltdvrH`Hp6+I{f$QTzO{#-utU@8jBIPwM^kYy|n8F4v#2Oq-}} zsY2o^G-0LODrhX5iL<%MBfc?TVqpyBMW2|zemhF)SiBPGcZfSf07t1qW=#Yd3HMng zJv-?qxVo0=^##;;%Hk%F{pgz2jat=REIFpZ-s5hE>gAmsL4KoWS2`=NjfJcH_+}lY z4s*VfgWswtIaU^WOq)(ai`>)*CyX-4G?@Y?{l!cm44f}N2J7=;Jk=I$gIX3*t*);G zhL33onrmpU)@G_@1rQFl!pJxY$m$*gkq$YbPVv??HKF zKPBPwUR6~O`rS+^tLTZHhO>i=i#yJVgH%|Nl^};duOB4)$Kce<=(m6ETHV`S>4~?+ z?OUhHV+1pd6| zSY9>1VIn$v?{En@TzBwI^l9**&t^h4n0p%@;>snOGaGHR6t*BQq!(*Ty(#vL{*~rW z|EhQX9=pqte}!#yBYP0xpW32dCI&s%d{p;1dsVT!J<46Od2CVG0{?p@iUEz$k9mx> zJ6>UNKh_r0E~nfz9mBBcRj%d#KHJZ;RI($6Ju>mWx-(FBnYY=KV_SQ0{D3mhX4I>C z_heD?UUB4;w16HGit={N2bru^UjtO`a^1V!xACPjBO66qRbzx<_vs8Eu|Zaj*nP5}ImYJTObWeow?$0p*;U zwaBWMLSN_iW>>*9pbug}Q4fG=r?#=Iu>|5}wAunt_UE-&#y+wp*#P9d66T+l@lJh6 z>6Z`*0+Yul5?1@av%-K4vLGY+%O9jd1svfCth-@i%aj8TM!|_esMk}8(Cp|vmu+C= zsR+w}=<0YJ*8jJW2mMtRT&Mu-SB1rikrY*SCplR^q!%eJ2?GROVFQRQ!>6KxAY^5Z`BLd03k&(xxSY~sYf6GMP!At+q%cpI3j$40Ec@sv z5;x-c)^WYS+0>l%{9@*q^<&d_BW;I+Z$_R+O!talB9A%idf@kjybS&UeP?w)-Wu#Q z3c7kF!-S`+TCPF0bpHU!mFQVtgx<6GReu*RD4}O^!S*`C z7Y3J&W355Eokf$nASao$Cu8zsT)lxd+S=dJPp`VyuU)}E3CvyFl-jiNJgjX~wbKt< z6!D+=w4KXghqW&+1ADoPJT;z}n^z@PD1GRddZW;+nR>-$Et9%+zP30+{rgMZ7FYW` zYCcHc*fcUf5|^5}z;K&W&fA}_a(A&o+r*eV^$rbNh&TMa^$>VRJ=vnofRS^#8K7@v ze_8*;Vs>|(E3`|Lv)c59JH0{<*rp7E4@UD*)4GXhl^Ht zjDnJY0S+xY$w~0T7V{5y+AbAK%8$OYxmQg(yosUk?X#Bo2MmdmjtG4hDS9i>Op#ad zvvV4?p!mx*OWRIUIlUgb!iTh3-Ph0|$eEG+YF2GTO;LFHJf|QJXF0pn5?-f#h@x*; zm!~!7%{$FoaUz}R91zY2nof-Fa6YDueOzw`7qkd8^~V%(Vh{P=XW>9%}uv=qX&0bLgNcrYvV9 zE#v#*y{?!t(tJg6_-t;WA>=!%l7YbuTMhFinSG`H#K$$S{j+Lv`qxUv(P5q4y?3nE zRqwea3Dk%a^*}+HuUORQa-}Mzh@#t_#P*w$xIww;FJ^mSYMdylj4y745~0RfR`Y^? z$L_AQ(zexlDOAst@|)b=6@@rdjpDlG!G|Em4MntZof@~gN5V)3&|lO`To#=42Q~e5 zA_qrGxkmwd_PCgfLj5i$u)*JC$gL)+`gr~C6oLFb9G+xa`I5Q%AAqzjtDnx64W$o0 zW{ObVa^v_={z`qcs<+xIKT~3&S+)Irbrs3z(NrCgyQUc9>@2))r`j-me~7xE&Dx8B z*pKg=q-o1nHUx!X%Mf7^?kDsrvcT%tR!A4vY7DnX2$awtKk-=P(|Om+w!PsfG^|HM zLnkoU*y{4ox4rmT>&RhdDu{mRPf_+oO{WRaT(lnaxm<$cRGOZwN;>7yw3qQe;8j*} z8sOSCJx&D0tffl}t)*tOf9_W>!vs_rcdXlnlRpbW0cFDU!py8SZ<_}Q zY)tLcX1iKQPqU?CsdCe)C0ll_3XAianZ^?D2Jp*9saeP=@nt8_?n2qKfMeVl{a-Bs z(wN?Xf?P>~j&Jf2?iRv1T~)PHvZUD`D~k)}ChUa;I>({MwV=^8a1OWsckT>g36%rw zE3V{bo|pTlc_Lb~CBc9sx{^>wN<;YVi_T}dzZpe|CHe|xME}Sye9xl==j^%iDv*r_ zjZel5pr4U-B9vU%+ehy#Uav7n83;gE+LCxQw58fYvT*HRrAI`W9)cETjBhjSw)fp- zm{hF_`of-1f3Y5xDHtg)uT++;fORvolL%IX=5~+T&ToWUhmE)a*?Mf{IDN2L;{)QA zAw-*q1eurF+{g;JBKged>DhQs!RJ|yY%HJL%nMr=lFixfw8unc6k0A?IWKiMdZv%M zv?GW5ed0{_c!*fWR$Ol zkOKE2!?DULKJ>KM(2LuFfUAgTf;0mQM{!R}Ir?MXw`v+U>j$1sjJ_`ufBt4%8-C0& zmG>?@^hs7I2ot0l6V$srWay21)%zL~JoTQZqQl~S!|lFY7xvNw-We&Dh1_ zj+6*>e(&`!N#|(pN%Xh-ms(>MzcTtw^?V40mDs~ApmCqL9V8XZ&A;zOzSyC}ayTpQaxLJ=&_}#PmAhNwgf9|IB&B{E1I`@5ldk^s99|x~?6p59O!Ei1fy?1$$ zbb8F^o3i4oNzu%5c{fFFkAwDncR<=Qj=tq%=Wnl5xe(e^{V_{LXYWk*C5zy{d}^ul zQjop6*EY4w{21AFsbkn!chnPM9%dG{$Ma|Y;cNGkOfzAc^mtUX#fvoZ=3+6|0=EQ( zb9Qfz%MD>+0~!QBI}82kM`>1h+tD}+83zsSp~NM1A!%OFuH4x- zhQS-Y#6CjfVy8qAVq##6A>R3guig!kNpOZDN!j^S0T?6qK!Ow^f5=68SlybLZbUd0 zqjj528>_}{<8jgt`UfB&toc553@MrskIsggRwdn(A_JIocfT@{_0b>iFO_ z&^Qxh`MoL?o{3vUn@kY4+M(_G1raa8CnEL3`_UMLgMJD6or@bKfrik4%m4}a6De*qatF-LgatoFsI- zTt7@seodX_Q-d$+J7s_3-I;`KDT2Y@RTn0xdw9auQDIf%boxrQ*3Td9kV@~TQFJ;L z4x@4K+=lbeNB;C_5e;tO*hC@vW;?)C^-T(^usg7y_S%=hM7Ol)iS6kp#tWxcwcn0G zYj2jzvwHbI2Jpw^tVhs>{PaNIg*fGeO4Xli*w|P=)A!6Zorl#y^l0{AID8Q{e2L4sqgZ-n#mU zIyp%Jt4<04T-@rFWStBMNSW{S=Yp-^bnl+D#dN4qT{*VQ@l2LAVHd&AH$EOmUfOPnuCJQ

bE9_bAj8HI)6S8ut5Ow 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%0AiW#S9X&)cM+DGIPfb>_G&02I zo<)+HCS;B$E&(*}IIpxWnv}L#V-}hly|Q)KT&UTwqX|_H%M}_qjrA_xHN=gT0aVBV zDqs;)P%J-qa;y#mw$#$S-sd>0f<ccf&^;RP>=ONv_3Sw2s zDeH90^=Lq$)3iJyA^NbpCA;avtW^)X^kAG<^dPc*=QX>Ea|LFWqun(zX8patgI$k{jd3_3rVx-Xa-` zJ36zz!JJ47B%#}83hlpzSJF^VuRVCOyLrI0(>q>bX=lM>dy(Xplnl2a`qm&Gqci=y z>;a6ILKLF+4hYMu<(Edo2HCQ?)L}spR*NEmaEe{Xr2hcD-0~!Crhx&-%Q!HSLWX0m z_|*vnL*$#}JQP(l^1>rpDx!v#z#oWIPrLuKM7Vw=kD5^Px#t~d)0 zk>&eAKEhRC0ZO$%QU)PYT?a0e#abu)k8P$o+dFv!`@z%&d@6te0Y(A!)?~lN8S$iY z!sMJ`M(Ut($yPwwGSh3RU_S>X9SeEzOOcQXQI#j~AgD}XjShw*s6w+9AO$A_Bm9BM zBmV$786k2rpB_i6Qm*hzm(z_P1XM9}s^1Y@kECrI^p>JE{{UH>0&yIM8%?L0TxM{g z!lB|Azk)w2YrXARdX(~wJ!>cO>Nfp7^HrQdaBYa>3gWIK;0VvJQol1j}g^dWzcc$gQ z{)pkERK9EUgY@alxs(@+r%!09E3T84DLhW=On!uofI?hu%gg_SD63zBf8 zRnDM&6m(CR@&n8*KkA;Da~ZC!cc|oDG`~y7YpUxzjarWIO$B|XIhN{SxQg0p2(Ar1 zGze-%JV^k3^##Hvt3`WXw1W+$6s=KMw{EKlmd~YDxr*3s{e1d^z1v%o)!eGB_bz)( z)b*8c$58^*8mcQrHO`m2B&$<7p}AAVI}Y2$7-d)oG~i-Qu-8p6f(gOZo7CtZ@fJ6N zbf>3XD7HN(2;&y3JffMBs=Oz5@pEY7jXvh>b*S##YbTUJA9AXyX&H=Q7DZ8knWiL# z_~%2_IFrN{xW?y8;6S1%~|e`L(lxmiv7xTyIL$aGh!? zqXfeULy9VSRzy-YN7g{Cl~&4mC-Nbf6?^(q+87c^1ScBzcWAADTlALOf70((XId!M zLu$0a363b0jJ158$3`_R1c1n6k(a@YvD7SpDu%%M^LQ<)vD_&_2&pCQK^ZSB8{2t8 z7CY2ll@xxckIDtX{{Ze2rgkpv;Ni62XFD?=C4s~Idz=PR0MDO1XZ=L%i8RO_hGNd; z@wjN;qzWYi03RUsh#RS_(Z*Q`C7q+m1g-#J%n4N<Bxi91A2vmpHAZXs@ZHi|q1w=dhnL;B= zmv$<0aEx7_1g;74UsFeE{U+1C`xf?p+EYhd`9}LcO=#$&=`T6`ws>cyhpU(BcxoYe zq^Fvi4kOb!+IUgpS8n7Cp+l-vvi-WV96CeJyzge4a#6_4uq6S&vOf#P{{ZCk{{W*Z zA_^^LPzIx|w2pTx0_qf!rz0fbf((%+KsdoHFhC%N%C0mFI$WeO;?+P=15w@U>m1e; zZOgNW4QD~|w@9I)GFFbY7;>r6^o6h{>JXGa9>k+57r#z)4p=6$- z%WK*;0b69IxYZdI18j#^1_LTTSo#PL=3oPhmn@D#2PJR`8tqYwNIs{Wy#$->+!rq=07_`rY{G zUnp!;QdWPcEy?@MaaFb4rAZ&`gs9(fZAEOgFx*NQhet9hXquE8Gfh*e`SqpZ5Vk4n z(C)X&^62W#-G+iie}GpjMi?s(EvZ=$k(W{8M^pHqV?C+zz0zRRS_K1S7(?3)#*k+mF(A(DbCZ)Vb{&MQB6{ z!0kvwV2}?+1X)PVsYxF!k>U zFKf9_3d_Ol+*q}0wo=(#>@h`5Jd}Z-I02Zr*hi_uN84{vGJ|W6%AGo-FeF_VqWXJ` z^@@r5gk9~@eJXVplX`_UReVkq6H?Vt%^SnY?30oI08p?7RS^b~EIUgWl%h0+NY_54 z-Iu`aRfOB28OB1W%Ap>rrZZkplX#g)TLK0SW-CiFUO8c@RQsvIp!YFSUEVTS(9O{nYQzi9bDs|9FRu;0J8|02g#FK^e#N0-Tk|V!o?)-*jj_ZXpSidw3wdp*94O0&iV2ulwhv^>gSw1%N56VU|)3%_Css5R)VWDLC+OYx@x0_uE4H)uN8ntWv;yi^eOc)mUm(f9SDuQHj-tP=57NR@HNI;X8CSXr-Bv0WmykfFhREWlJ4o z!l~6E#-A))TT)%8asEx7J zb&j9V=*wCu=tQWD7A-T-V;5E*yDx-PKW;i^* zx!YxnATFvhs<>h0p@3D$6BKLpGpjItE?*;6P%3#Z9H-c^Rom{Rs#Wd);IbNJBtAw% zF$Gs&TA$UEk>Do;u?1Dpt(m72jAs0mM~Ii58<@l;Y0^1%s6g^L46z3R-IQRV2g|8( z?=6L74h|Rb#>`iM?T+@=W1Pf-Tsb$oR9ekWg(_~!$bcZ>`}K<7)fowJU-z-c$are6$71v$z2 z01@NX>nfoZK%}~7ugqe}sbSDT&P>KT4KjubvQ$lla-atx06rWx2w*-&%15}c$Ey+` zAsYt9z-G7or3k*iluMr0BZvx?$p<;|N4bl9gMtn|4}w6!@vaVT?8zyS16n7mO08?l4o;Juge@pr#YXC5{%WtY86=?Atz#vNPW#5 zhAAdscOHGz$A?>t8 z72LDa#>H873zlt2)Ziki!j|@xCk@qto!j6t9W0zQjRIg)obbha;_nF1+bIVnkU>t7 zsI$R5k<;cOy;Z`o3%ODRa+3$fSJ;_gKn@KM3w*faq%VHYL>i&hDg=(Yvqx82pf2Ez zRMZDRthoV+kgl5&>U~6lp%pHN+uS&pj+hyYFg`R4eq^RS_*+G5;Ck<`B31Y)r|s-6w|kd zJuSZG#*Gblk>|QVv4*A?jbf10dDXGxsR4kDDuUqqSo*27%&A(6QT5e7Cl*ab7m2@E zmniA(tCRQCny8Njs2yYua=C4)%4DHc1mvj$4#(Ud zCOop`q*#U4u$JnKzQHoWs2T3P9B&~bH&uGwExG?9M$Wi?f{%B!4o(t$N9QC%&y z)YGi7D#9L-q;_h8WO$U)DzHo`@CQ8L_fNXhMaV2m&3OWG{ugb&RQ-*Q{YQRlV_YdI z=BpO!wEfcCZV~?HPLfysnzcZVk>Qyt=Zwf!Qj|WrcKEaa$%iPNKh~7gc6w{XwcKti zO=zcxknz7(odA{g8LX)*Li8Dxx9_5g+pk`HK9-@bpSFcD%>90^G zRRsHjPdOHz|b6vV{? zFy2I5^UKiR-KCHcQw(%oC-j0TkLrhgtp5Ppm7TQW7s5Mkg6o#9Pa~jy` z+=ew0qnJw=;X-?`#U!!@jxgGH)$@_yCn4&^ZUf-M-x$n!M8ExsYJu5RhzYAV0ae45!O0mJyI2WDD?DQjDpBTfrO~XjXHPBauGwzsvn)R8(|C2LAvBFO*iU zr;xQ^yLQzXj-CpzRN6wh8Nl$)as)tt0-du8Lz)`!bI8}?j}=DKtkJC&8$0TRswCF_Escw+{;S>J=M53pt>z@KjnT%1zbE~P0jZ}%Ktv4F# zKnt=&GgnIj9a=U4j*Ykn)-%LSx(+qRwR+HSg2-7gZu<7Ix=TeuNvkqdvVF3mM$ASM zT3C`LlASANmVK2QSJi^LI64M9;Wo#4Y=-^kTj>@gEJhyj&ZL?wwAn+Sx`kNdB!IFI z2pIu#18e{!jDeAc+T;~H5;5}?QM>?aDKyfqiE6A3%b60{K^n50%Ir%r7IFx~WQ>e} z2gkUK5I|s0AX2vX7{hM@497`A6uX-(xEPP23%cy>g2ZGG3JD5+7~{#uwBVfkLiqJ^ zM%^afgscX7dUx|1&ixvV6~C5sn(b3Ok@oo48n;ImYFY97h1TIz(vu8w6-fU8Xh0O5 zNuOsZ5!w46(2{+oX9~{4`(l^Ds$Jtz`i5_|Pzgc{6h;Mt(37H@hB3Z#$?XiTR=PFS z^)g7ZsuB4jtd9sofUTcC(UbWz6V#vV7)t0!s(a7}TE>z{Lt3zQ9c$|FlS{~~g85xC z-DuUGiiVlxd1F}QXymHV8J)H+iz9_X?!qvI1D5nij62Ql(JV2lU1->lx}#Lag_IBh zXv<-lwvB@@Ewg7I$k|ZJmc>mFv3yn&>=ict?b21Pf=ML1)cILsU&1&e5g{_=z!_&G zBd+Y98wE%doO$We8+=nV!n(Qyd?C#v!qHBTBzXu|eb}&IH8~-+ATasHR4CXIJVt-( z>z184n2h9`MH=(~XHOBt{?;sZs2Ecw6&j4w^c+ z*%mbi&PZ7qyKlF+v0^vTnVK_w)IuY951Z!J?LsY)qha*2Zt zSISdVlOp*!@JMBddwvOHxq6#W{7+Z*i(4R|6K|IH?y`14EV%>_7zHHw3;_U)>!`sj z#E?G_{{S9?7Nk{Pc}0Eerx)k9n(h~0(}~`nl~^n5Vug&5ybxNs94TtKSIm+~is^4u zr>?-KtDm(@@zT>s(#n#kG_xc6+%t#$S43{6^rM#0r%vg5TSTzJ{BEnIoSvje;%k2u zx{Ga1Mc_ByQq!5}jk>eA;+{&og;mZ;SPw|mTQ2+7 zBCb-$mYQ)zdWbVy$1Qwv<n{?NDt1l_LMGaMwzORc|sOsEN zLr(?v-A_cck=rdxdJAr>vX6oI_g4b}f+bQ0LG0;hvC~skQrqfarItEIsfMnVB9@*OhDs-jNMV&)DAyR79N%;Z&^ENM`CaJxZ)>q6b(w-ToI99mAG*jcAf3jpA|{vfg~@pI6L3 z!J9B$Y?|F4E}2g#lz%t@Y8*LL2*RjKqoi||j!|6aV3}2wnj*}9+XYk@*`M8q)pW>X zg)ANTRP!#pa5 zKweQAymLd{Q;G3`0XYJA6kOWX^n%G#-+VaQHz_~^K~7{Uso9RD;*VKnki9JGiqfQo z1bG8?RPC^g4VE?xaI;NQ-|8&MFaZs(mQ>_dEVV9vmokwWiiJiuF*s&8@LAK^gXK#QK=4i%Pk?b^ zLMe0TA&k@xGMeDyQbk)fq%dj8W&{<<$Wp+O`2&!ued-G3h7ji}JROC}Kik9F13fC4 zW8dyK?fNgkQ~v;8D#TE-if)@k(lW-iC}*FXNU_Gu!Q+NOO7AMTc-`WFvj&1Sm$iKh zDE<;yD5_&IbU{?tF>Oog2}bOtEI>GvRs!m(gB57qyp?e@sVfy>BK)a7OAvDyCpcrP zAQ@D3$AUBKrJw95(y-6IQKS%x&CMNwI%{lUGS@vkyo&Y+k5z`yisCc{$|YQbR)ur! zN{E_N@aW7tim8$6aTtC_OGxFTlA1PG17h)bDDDXis*H>V6di#8Dgxf%#Uk){P?=!1 z+oz?tIHjd`mi2XMT)`Xe?O!U8pabJojX`G^P#twZPp3v+#=6=CIxcGZdSUKiYO>R4 zijJmvC@Bhv=5G3dv})HKN$5M*|h1 zrDt;z{{SFp11m@~mH^-f@IVTV%8I~@55FN)qNmNE(zq4k<^tpv+hlgxHH9J{bwL`1 zkSB@jso{?rOAE%yBgj%h&nI!gMJyEhQwovA*H%L6hO9Y5!s6R#b}B^RWU4H6RZ1ia zhGDF7gc^*tSwfwHf>l5eBfbEPG2&JWCGo}I*Fy-mutONB#JB`kDCYCu(DXPQaUon8?sW~;Ro)p^^mq+Zy{!xc>H`iZZN z+$XBER@`|76~l;4M^#f#JXP0(H>01vtg_U>MJmiDN2W2NNgyKD z=4CKm*uD_tp=ui2vRBgb1gVWES6Se#p{Yh`XOR(OnY)5Q8lAnK@}M{>H4G37NQ}VB zRno@1I;1@5=x3A}mi1X8Fe)8Vjzrmf`%2R4Fc^E3xXTTSuBDVg-4!m2))crtdo%5E)5h^RCG|)M@u4DjnExI0D#a|FxZYD5N}_VB zIl2X@3BfYLf*gE{s5Y)0UrT7ZT`S(@T}e@2Pe*CuSMEz(XlSN_8EYh_<8)Qi($l2V zM;V%Edj?Vz2_9JDkwl5BxKLe486W_guMDBz!>jHWn-#j_MNEW3H9uUXwQ6PG8B%m)~Y;&q4YftrLs;h^loiwDZsHJtPWMmK&0#CnSay4xH4kK7+ip<1yLBWrIz@LSz56o z{rAQze;5w8I8SUG(pEVE03b-ZoSbZmt>?%EnB)PEbS5lHE>wjUpXqjx)^*mx$YUmw z-+ihPyT+T#Ew-kHIOFfj09kw0(S;?)YbHhtk+ZHZ#)5Waiz0+oa@FIDCCnN?gIA5W zTl{^kJewdCu=fcfS<3yTL}EyqMtqPUk=q12Al+CY%$oG|I5vu%^@&G3D9ihmB7ztR z9i#r61{nwB$0dpqFuvjeI0Wa`-U$kq_KH&yq!Hb0Qw(4pa^WGj#8Vl7%k9npc^D=} zqw`=_#M)h70=Z`<^Pu6u77>upL~r>K^uyQlPvQ4eiPc46hxwK~ZqN=33gOmaG< zJG*zoOC&*KS-W4*)5<(K-kAwN`5%MZrmyGHlOQ2WO+cU#DMDLUD^^2IzON*fNk(j! zIM0N}v`Qpm&+SlZ-8L!%V3MH#A&Zg+NfSxDf@EUK6>_-=g66=+kYw5+22x!Vf(wm0Vesun=P%t%}x$VsU^WB zN)eVm-%%@ya8tp=Ff#uD+tmIcBHw?srK*xRtDvV?DXEZ|Rq6FHqDCVjsnfQgufq@u zkP`^#6e?DzZhv0TK`>wfRg+(EbA(PqezM6+1ZL|>%lnn3<+)=!;RO}*=X;)dyrmeqLOOpZ(MV`Q~Q0!7%Cz}?pSV7@_H(W zBV>K)D9s$Zq*K)w5>u^hrt%zbN;<|vWCK**muj)*5NQSEiKc3@Jxo^gg=s}O9eIlqm5s>gVO}PP|0Aaw!u>cva;(Hd9~RVlFMCCBuFXN z;wh*eTBztDjtFN+mD)f|6@g?`WKy^a{1%G$a@ji5}j>6qU<`*slMK!a8FCVf~ z*V0m1+R@Klv-(d zf$#7HC36m6!R?(*;G(yXam#ge2Rpr2@*?$g6xER`aBKalhFi^3$xdk?6&DC;>E*AK z#_cpzutzOau_`c7;2lv%r&~njlmSpy>M4=-Rx1xaq0Q6d$fmvBdF59Pbs_#GN+IdDnZ>cE|M-gWUg4K z!Ov!^XKFa=(Bz<0Uu^S^ao|-qbY}N5cS457jqE}-87;O5EXpyGeChx@SfKs&yi=TC zA6J4r=A;ItES{Iu$WD+kUw|r)gr6IC{6p`*O^ZKis9VsO+TVBi2w=Ng6XY#Zkw&k(^~t zb7l3Hm`Fm-4mHD_n1r&dRz6kXi=C8>kb2Q6TAWNgtskgA)g7rSW2Wje$t6V5)k`g3 z9^$gd)e4bB9c4B(W&RjaHHjavzVcuONef<+>yH))MJURpID$z^(4LWBY2^MCpZP#2 zDgy%l0Q)7G19FKH)B2%(S5QI}b4yI}MIULTtKwX4O@J$-u!7x8j>r`1(4x2aXwe?GAhp`>ohfE%7nakAi zT0<<2S5q}x7L{a)ks^{&9XeB_QN>`EtKt1En28HQ64S~N zA&xYXB#BmDT~^LZGPI8)Z0)|sGLj)L*I*Pf;dCoPs!(05Ebt@r=FCu>l^2K~h*V$I zlc}{!PLL}SSOirs zF+oy_h^PxySiCX%b7lZL(<=l)#-RzYaPMe+TmJx7+u4LIe@HmpOvoBJB!>H4OBj4) znBi+}S;p$1{{T5+rAcKfBV&)QHUtoNb0%Vus`c~+&FBG2^e z^+mH^B8IYFjjbQrM^O%ua}=xlmzo-J7ij+grC|FtNAI+;NR?SZ2uW4Zl-HZky%ME)}9R1%f?{si7mlCCVYZ&8g{WVkXGvaig1OO{XlvXd$`b6 z-@0qcRV>#VRpQo^lG^5Da<<2Hh_sP3kWaSX{-9Do&m5A?9kY&tl1_{s zK#}suT0seIC1TM^BWWax8G%LhZeOc>xYud}I!wyygv^hcKCxbJA@L%JN@l7*{Xzqs z5(K3B{`y#&I+<(fd7>Hab<-DXH9G`}YD84^Q5C%zWXxe|*vgSl9BGZ7Qc%c!Q|0AT zXb_61PP0m^)IjL13_P2}%73Y*Asn*0-$9DJE?0eR{VCoSnxbpRB5BP!JQWqTTb!*- z+8Km#)JAJUyiU2=6DboJC8rW22}=I}iM$m*l2ef)xun%&*9J$*d{PvYn-ogatJZ&XxYL!VriwuJzD+!dG!e%OhlVR3U=2cylvjpA?gVR>^%LvYO!~7^(@6LiDm=Cu9SeL)(5lNlM&nMG;;0IA{XB2voe zL2gT#?R{=1(Uf(AtM`hibXsWmomx&M?MS1$e&f?Z&kT^%JhLy_$YT+R*;zmmqc}&y zc*8Ok8iM+8wqqv;5|Wr3#?)xz6H=`5)8bT`84{XmH;zFRg5egVJv}r1vW=aQkriG@ z$k;qV@cT*+PktWojHErW5zfUq?pAcdB6aHm0TUY8d@eD_NZVG!ikq8<(nX|Brw>P zIbZ=_pFZy&0Q_VU%Q7-IU2{lKYAJML7`BWKNn*ZA;OmT%bxe`B=Eax81qGCaRy7sJ zF$_Q{mL04rM8q!H1Q5)2ARlgbBHRUAxX$2Yv2r{63hk|P6D!VC4N2c5Sl_tgH=`NkTg>O%w5Z56$%D+1Ip?E_-_Xw zFT;VH46X1*Os1-fjT+i~z{sdMj{DXQD=1(~WUG|`oDu#xBn47+0gg#sa&v{k%ue6i zijZ93sx#*f0-#{Yewp$RZ7K>8kL`6+k%C;0Bp+eVldud`f{-u+32zhr;&|cl6Ee++ zHlSf^Qzy$KX*SET3XPv0;0p|AC(n`nu=Dfm_JYO){{ZBKR)qYlG{R*2oN&?g{iG3j zC54t#vl1OxYRW-4_YfB(00u++`S$_m(i_T8@)M#CX0XDPAx$H*hx zbr?SZh5!qllpFvD$#;nbW+96U-H?PTGS($5r9y_`-nnNMkE7RUY3)-a^$^Bhe4uP2 zYreiiu`;%y8Fe5kk`+p^WvjF^DN6F<^{*QmMX3P-fzdI@B@!ne^*yRLj7e~%I!EQZ zsB)x66H&J4ilIR&GpQu5_yjxipZ64HR7b`vN+wkxYJqw|!OU$YB@$g}gGM$4PBIIY zK*BLD#D`$O4|8CUuHZ0`J48@xY9DV{ngdX<6#U-OuE>`1Mp^3lN%voN~;wr%G-Prx2KtRoZzN|5cpQ<#s~zWlIKb;zSoG~jPi-Doazed z=E1M~xMdNR2+GvXAb}8)aHcpTDi4+L2>}B^W*8Dn3@?xApBLT=0=Zt6I2<7-sna&s zS0K3*jqN4Bj57=>94N>PsHKKmDjA3bi77x!lnYOk;;DS1GfV+QTz?tgwu?A`+FPur z$BvDr$1~$An%FqN))mH|DjE z7^rGHU6v@Na$YzktGo9Dh_sO!Kxov(nAAqfc9k;w;rwL42fwv*Y{jC~fD}^53)J_1 zk+WK5o-+bZwBk%dD2_N}V8F#vKyez2LV!23aU;Q%==I zBrF>q)+p?QR`3G?M&+^qDyqW=B#?lk63bruWr58pC`NvU>%)nNz(|RbNybPFsxV7x za99E}jFJ5k8yEu~iPj;zGq<|(furzJM9xV3RA4TrBLI`H%9UT}25)MCsb!gv*b_U@v@1b}>yq-P{+kV)`BBOn&RGWh`>6XERk>_UT~uooKy1pFUy@@TzoW5J;pe2{QSz#kq6+~}ePMY~loINdo$VzdB8#Yw>= zXJ88fsC?|rkh=qo800YnK9o+8=&xi2wo`-L1Oc&cjAL&EC;>|QMhBb%71W|DcG^OW zmgOWwE4V%z=SJHDe);4vWi10=V}CmNPe zG7zZm))WOCs*DCnKk&qW8*BrN1wS9<@;t6ssRo#)pyP-&g$|p2=BgNpNf$?iakDfvf~PWyw)-+y-b_n95jqVl>q=ySQTQe z;eiB}ED7>JV8UR5a^`H;#tlP!s8(Rovk4LQ%M69tlqqHSRL`_yAh{^pKeft$A07{l z*B%y!GXYZrwb9u_30W0@C5sV~k$?$e-s}u6Mp&*!R{$5@JaMT^9bz*OK|&OK+vHL* z7m4t!wKIE8RArC6`Ap!e90QOB54htPIb|lKonT>I0+57a9NUHmf;sqa_dtX#RYA!N zOUlIHD99>E8ZpBKB$Xg%KOUMKk^r$BIgfkYUI2Q@=l7XRg$`MtB(92 zImj$MF2pV4r73{0z-a}fst7;~S&xilw*acjN>K2A>OKUM}@pu>DRx^ zeNSF!D=4av6p{*@R^B99O2T=QM8@y%M3mIxF%p~>-#?}YHvPavEk1O$SWi5bl<4|%53RsYQ5&<9(6b5xTo@OBetxEwSocRMeZvgzRHXvb`42-WtckX17pMipR3L6A5KaY@qwy!(6V!wlk5C%ixdr1J8l; zl_S##=wjWlYlUCLi>m~^r6K^x%b^5%C@3~cRz|Naf1z6FaV+PmIq)6*a8E5vtwoXKOR5is2n&f#gACW z?j;NuWtq^I8yteghqyKu0f57QWp&9aaO9Q--suVstxGauSq@79sXC8#S5Q|Qcsl|} z0D?&e!DxQ@_wxtgs9XgyorqOXjh4t}!2{dpKOTNCK>q-SBnL3^q^?))Wf%_Dr*?cJnF5e_L z8Oa2LsKFV*Djbyv)m;|DC|u^Z1eTf_NZ979wsuwk_NmoKRv93NaG^(%M$Es6^f}E( z%K9M>Muv=d%x0R&B$5VKsF{nlt%9T~LJ8D>+J_*GNLD0}3ud;`aVR=1c@1?$4+PoD zNd}>9RB%r=jpF{Cd@=0(60YR6Z4^@Tu4Q?xq>`Paj#;d>DXms$=_x}JW@W=dcW|p832zLOwQM%x& zb^~|gmfN*8OtmWYzT_ewjEhSh#oSm<~3$8u5p_$76FtJs<(ez z;un)e&lBJ!pJq(5{m5Mcs@$PDQp>L;H(PCOp5a|@tM?=_`w7>|5)4v<9#{uLt(PK3 z8QE2`7pCmy2M0nP?xP0hTqBr5Jv85CRV0#<@Xe^7^8Wyh3x(S(t}x6SB}?PZ0)MhF82Kk2azcT}&meyxh*bej znfz7o3mlz5$?!fuATy~B0jO$`YE?ACu-6Y_qnD-62=$0!xER!7z#i<92sv!$K28Yo zf9Dty-(W#vHFa{OR_Ros4G$Teb{f1 z3lD|^{xOy#BoZi=#1$Y_@OP0^Zy*s01hkk^uDLU|H1I zj>lfGyVeB~xhGHt=T!uGT;vnrfWve6XCM=tftGT@{3{L-T(Zm6Br7|2RH;VCB50h9IuiIAhr*?TzM)& zhv^2!5KLql5k1Rqa}&hMrAo*y}t-hb~#ZZ#>l6} zK>5fy2N_+M9Fjv22mySP=#r3(SBur~^o1FvSyP+(mKd|1^<3r%rJ6cBbu@2Z zTP$%-$wdWBnma}UVSLNQ4-c}MWg@^b?=E5Ji}E8k<-QPY_WLrqP}*ar&C)}^wHE+N@F6nrItzB zdFo8j^KTQjJ4vRPpSY56bA#IWB5Tq7KEz40mO#J>Mt}qY*dh0maG;zu0jv7K4Nx?>E!ZMO1>=JDV11uPfcvd)sR!ZpvRZ>vSuT3mc zylk_Atfy@p6dH!|4#E5o7@U4Bc3HQA>DKU}yH`UZ$i^ucnKmsR#6;)-If2&+xOD`; zJ~G$ea`;?lH~aw@9_j25;ei|oaIDFP>PtwD6XKBgl6{m@$yKytNv{z+j*#@4VneiM1-Z$UYQj@%HCa<0R+%Ki1e{8wDzC;F1eaS=2h= zQIouScagMEaxsEI3Pv;YfP50U^XK5?oV0%B1P8hQ0BbP;T^ll`P@=dl)001XG z;sI;`*i)SP6c$1f)T5}+GE1_65)7K8Kad{KaKUn?&&RNWrydS<#sS;lga=5J!~k8; z{u7F)_RyTpw63vxdFBM$ftv18o9QbMutN!QL-0Qf8y-GV?r9;77#mASe7-XNt_ zOVqOIl2Ac5mPe3~!cC6PjD`vhGQJCbJ`TA6;{*T*c#v}{hC}WZ*>TV{A!>%AzI29$ ziFC_|%!q_8illK^>MU5hWEB^&4+6 z1Yw*KFd1^h2V$rQ3a3&5B}FZ?+oEEqB^;=}`L>Z`W*$&Q>08r}NZgOm*VE*@Cev5d zE@N*oa*j`8s;9P7R#W%t?0jyn8&&3(;E+v0Q58M*?M#KtZ1dJzZO!&o5KvquzgnQt zeCNeYIsQHGv=c@|B%1@oT~fw3pC<64Ne0nA>5$j}RSQ_6hG$bp+5M`CWTKX%u!U*O z#6uz#Ldh*Q1Tx4dl0md!NgQfjV-NhVIgAK(S;Y;h$Fb>&O|NFb7Y07RDGsEi#Qy-w zf;CXEyKhPSSZYaGREMT#$zlX$N4T7Z4GOrC)pQ9=Ua&~!^8Wx&0V5w?SKdW0NE$bdd!m+F~kS}^YtUydS?(XG2)iH>ixr^q^*jONgZ4u83^pGk;gb1 zEXff@3HUe?Hm<&NdtynLOPT|Mx4(9fm1QR5*mroVrnYEisHHl|98x@IU~c6h^B631h`}N7JY*0+ z^1~p)dhpBR)nXIOlo*A3fN{^Dfg-iTB8)q!lTb<3ykpvMKpO&f*kdd*H6-Q`qzt(J zpya%AA?-@3SJ@kaQCNc54e-i5>`#CKkTnCw9619LB5{Pgcef;Qn&)ELKK+CQdp5?q zmCy8807>$Edwm!Ly6fA+5`+Y)PfwUFC6vM!6=k+U-|T=x50jB5PIev-{{Ru3;9-oa z1D3t&c!O%AQXlun7|u&Ws|5S3kX(R0sjCQ z&ITh517(5|TQ~?jWE?lY1IfqEJbyzU1dmD_09PPSC=|%uqWR1C_a7%D{{ZLuA&4X6 z=NK3PLLnC-p1%<8wAdl5q>|-PJDA{f2AU*T+D&9taA#(hP~d@tgcZkzKhqi;v4Thd zQZ9?hc?Ch#lMgv(OKl?ktO**TI|Wdn<#4+*1li=k@gtUH_yi_Yf@z8uplll|K_x0> zIHXzs0Hwa4*ttb}^NvQ1beh|BdP{Ak)-<=%)WZx_i*lSg%++-e$vk3~7ZK7#BX+2Q zu1AUK@g6s6FqDjmU2DyIb+aK!fy_qo`f*v_ve3@)h7wXs43L$j5VBKRsG(31DH8xC zQ>f03cFv%`Pk|3mmG_kQ6&(g9$W?*EYf{}uJc#MIgN zlj-vFbaDDD{8L$zPmE^%zp(p%u+jg*k^jL?DoVniJUO43()@qJM*kajb8&F_L;ukj!PHJw`7_k`Jn;Z$KnV~Bgg){Auk_#TX`crG9CrW! z5&Pe9Mri=h6bt}ZEB_rwngakxzW|`|uY;kJ;r~yOaVeb60ie|0s2qG1pELP0k)4-K=`wJ{J+tE zg#5or_{{y$4Isk;Gr$l87%~8k3i^lMkWkPtuwdZ87dZG& z2L5jVI5-$200jvK0R{~T2?KzELm+?hpb!BtsKku0XiS2NU(iW};4qX79pf;W19C~p zYkIM;S&W>gXFdhsf&riM|MTkn7k%DBLIKb)pNT@qpQ&IF5D?%{;1E!-U=UDX;K)QM zjDi4!A|$b)V*nI1DpOo;jgV3g8i~>LHaZ3=vs2*ZRV@o^ys&a_-^|Affbgk3I5Gq> zzz^I&?!(gN+=5-~Mlt9-W8y*_9qPkZb#8XHcHN5CI92HA#T{R4p4s6vc!JPX#Vau5$6jzcAB>vw+wZQNd+ zM`|3mTg8&r1k>OWE@F3LwI=@-XCDNj)sF&1z6n4Eexca9MxYX!hP=k%jeee{m4u6{ zQT15;lBq4n;751-1`n-tc6mI5u`Tc@QdZD<8rjpaQ*SzWO;=L0x?7;ZW40^Molxc2?9x{q4q)}_=3YlTs$ zF*B75{?4JGAN_8l$!6?&@8|(D_S!PA2oW9e000r^1&vpFw4-=|JFRP^Yx0l& ziUv)rv|vYJ4^#$Ucwbpdp+~L|@^n{L2yQt$&QEL>xDJn##0{#G%nwdmsbJn!7vP42 znp064Cx$A?12?Z5J*$@44T5%m@$l@9RcQOjsN!n7%wK8;B5`9N zRnH?|as-Hjs)EKqJ78Z`DHNlc3ZUqi$36g>W0KXz6mBmmn()o2aaHyO{{SU$f?7ey z?C?qv7p~(M;txPqF(HBmI^6Z387YH-fRAf0lgzDT?&ft#u_hZzUx>M-o^K0@9HXDI z^06V|(vu+k^_EY)^;hQEh@|(^?vVJoPuVV0tW%mOUz7GaqUU0SQ?9|QE9`d&HP5m< zTa7pqmjVJP;z(#+j$GOMO6L?CpSCfTusUHm1f<}-ZRXiJ%M*s2lT?p#9g+Co*T~vM zgNcrb4yW8X_KFaQ#476FanRdp=Daoft2Na*8R;v^0^u$+6Y^x9I}KrLcZ}hKVrgF)NTTg7yKsGC z)Q>&@#O#u@q2?c6N-*k4BgOda#pxTZNX%t-e(Xyp9B5OsvV;1bu&VA= zRu-smtJ%pJ}+^ zmtdCV%&0;!0`VBff!P+2Q!sggl!2Dk{M@#g2NMG{v2x)9hS3rM{_M7on)I>7du) zXppTjj7pcv&%r)jRY2ct{wag40?m43zHdX23??3;NKCxv=SDtp4BubnO=D#{S63l1 zNTz5|WJ%-sUTBH$KfE(NMJWkMl?VPJl_jMCLrSUF9A75L9|na@W?7A=>8k4`&d-Su z`d9TItmSt`+qcok%Ex7?E!?(a-t`~G23uxU@mRZ9Q<60PLUpf6k*mQ6lS(Y4_meqs zyTUVWWK?~PWg8frz3XkZF$7pAlkXHqFFvW#Z2ym6)ttxk%pqr<@R=087jtjI64_7l(Q11 zX`z5R{3KP4b<@9+ZjX zFs&H{w+w|7SVyEAkc5as=oxCh1g|Qji7QrU7m?BBSF4Hy?X>ceC?-f5&=58{VXD`J zx`Hb51HO=wF_n15m7}piP!&d2p$^CAvZ4F>5@4=T9m4ilaRF-#hNuorjjXNx@ z$Q<3gbqRX#;GL< zVijdNxKiWQ^DCH0+V8Atc-`H=H#oy`(_pm zGZu!JJkCv-d5Lu2Ps)J5lKTKkLY-%YYt}OHPYW?*s&H~}D(~I5EOx8ZA1MlN*DvIc z+d9ri*1Sl~l`sO*3$=*U&Hh{%;%i?V-5BoMGf;c?U*BM}%2T}dZQ`0kIBXe7b8Q{Q z9;a`RvuSwmjoF-$z<+k^%Nv6U8@AQf@~_2`Nnr_me1CqWdcI#AJ+%4EhnAiRw9%>^XZSoo)Wc z%h9qGKPoiu@FX;sExXvDdwNG0*CO@X1QWCnr)(0!wKS1shvu7Px>(#A-9_)q+vEpO zaQ46*Z_ctFCo8Aw@fC!dq{6-+zS|K!RCKpK>`HvUWjO=*XAoRYZNs(I*@3Kwu&Fr` zN#2AOW15qWY)XhbMelL9y{4L2x3rnfeqvNyE4`ZY8qceJKQ&R29^M8C-FOvpx7Lf6 z3||fZL773+5038{!n}_iZIrFvf!X1au|Y#k;#jBjZJB7>EtSEZ(*>nVk9hRhC16c7 zP2ne+rhzeb&^WTQcw1U!Iy%6GLqvQNq^0TKE6pd)qH14YE9cyOJpM}EZ{^gY#xgY> zZM_qcoxT`EiC_H9^P#wv7%HL~_DNYVyy5EmG9RJ#e4LIhw791?iy9C8x(JG}M{7ar zK(g`BS9R{6ac4v^L+HG|ZPgMrD%_Ckx&bDrD^}ERjU&}h?5*!|o^gUoyY@q4WUe%5 z1W4*IM`I3|nP1KArH_IKl2|FRuaQ7~B$3R;d1@W7T3D&W4R3Xi`$6fG11r*(H^j9+jVn(nlbfD1q8-&Y(nbA)T0}4 zG$6(ZUz0yh^D%i*4`WQK?uf~G)(^gy73rqjxSY2H$mzU5r-XC}-Hy)$47W z0=Z2RGPI~BPp$C-KqchyGYabjT=X@LS5Va1$| z12)IzcPJ(&L#1HVC-|RJ+86P`c#<%kXg%&L4^t?jZJH?<9$FQRDg|sq3h8_KCA5ML zU}$xCjb=?()7X?bO66V{_1woJ^n&ES!MsSiHpTmPzmpcxYlCNC^6NBiJ{ULVtcV(wJ$jY`4%|HPO#!jMLZQX*A#w-%`J7Gwo2@u94@p-A3ZZBfZ8nnHDgz zD|wNnd%aWX>&Z$+%*0g1!KH5LZiAYCqdi6%=vXC8QPpgr(b>2iZfEuC$G5Uu815OG z$)PVM`?XBS-!d%?Ys45R${iZl12(@M(fK#xJ!bZMLpF_C5$)tE#bbHLQTflb$Cw=k zPe|&#a1oo03Ncpm1Ri&qixtNfr%d7b0udyY+b=LlmlV|%rb{m0Lh$Yh9r2p;9Xl?f zy+b^EV)GE#@~XG5Ajh$F-@3c zLr4G6gI6i5?Ub)k&7y`@$PX@jo0oeCe)bD_FX0iMwHR|%-C#o+=FZ1eXGHlTYP<

Ng8#yR)@aLtS+9jr;eAclj&hXtyK5k|6P=FC;mV)nD%(S6*vg?blAu zfa-K7L{pN#A(hRbtA?M0*!X;OJa8O!SnGR5*Dc86t7D70A%%oxO&Da7wS5Wbz&C8| z-#l6x>zakugAOJm+RfI_w@bqjCED@xn0m9G_4P_8Bzwn09@zgD)~=gGZwSQJaMaAI zVmcgH3Zi+w)C6$i$l&ZbZtr792|^oL+Yzv_jCr#UDY=|Z?THG{Ju}=#UlgD7P9UF- zB<5!xun48kpV3)=P7L%fKY+p9!!~?S{rx`wee^1vsTQfOG=`{Hby}aW52x=R-%`f* z{P%x3c06v2jdCE$x#&NTluwU13tV8(dhmOxM($3(6vY!_cybNWX>Cr~LM)17n5wE? z>G?n+FHUcKd$L>|bN#5sZj|<|90g5}=DYv$@i#pgoNGa_+eSsEwq%6{gJf<#;+qOt z_Gg>yO*iLJF!I?F-MTbLavFD3H5m-#66RxBGDG1QF8(oX9HlW2niWU8^nxRTarey& zycC5#3`V@Ui_)~PI>`(42u*HDQgWs5BqzBi9U^T9(8ytUdO5T0*3jNx+O!2W3Y=7t zS2F&6+IZK#@BkL@g|~}8t1_xOJ|YmrC+F;;<+{ty8F`+^2@*HdchEq;ei|AE^^-Ac zr~~XGaNnmed_qQEyNF?Ag#^Q_UqT{^*{#EWC|@?jw;m|g7G0>8jY9W4fSZ#gmNXyz z0ADMBnv>YZymA3ibJ794jTItUBSb0$FzK93Ib*7hFC|@ZJvY&@!-;9===Q4X1l!%_ zXBmwfq0~A4yy2z8XoUR(FTO&lRoHO(itzn@kWSDn;7lv?Mf7vH>VIl&wV6;&sin&7 z4L4%T2`1HME6|mfaHg|<6EX$|#^;U3Ty5fyI?L&zlmCrwgrnBVbm`-1wU;Nn|+R3(st@RUP;wD3=;?VAU5(zM*ux6w*Wy2(RWZ_GB6L_Lg zKr#QqJ->bf$Gn0>VZ+zb);-^*My~Z&l7+Du&k}i+-eVDmVHvAUXyA*o_bzC+%KC>m zf@2>s^8^+Zsq0ZsON6k?RwO}TG62b}Pyv7RbHC#)wKOw~+qcL{U z?|bSjtTyRi_xQLR&e}>7)u?QD3e{e2JbIE)5#qqip$o6^{@SE(%hQN)ir`(JLR?T( z)Mt%t-zjO+=3mSic6Rs4v!V`}vhE#R;hPEJ4l_L9{v3b{(0L}p&npCp0wAgIr>JAH z&cD#n3auRt%)E&|4VZA<`xKnJl4y@nsdsoNf(>Kuek%FPDQq4_1lc7O4y`gimfr-_=8Q^{W_?`VoIu%2~GUd#7^Xo ziV${%2Q<5rfz^P4@YITWnvtSyaMAne@Lfn^W64M zevQPM6tTo23%d`5YNDZt!KoXI&6i!mXCb1J2}8rU-FIrz_=g^8(EG0(|C9n+`d3bv zU*mPlxZN6i2HbI%ovaexA2FL|mA@o)c@pA|L+E*_#yoFmXzhiDlVVIjV4>*rnd=^U z3ESKXW(K}0-aPImlIDAU3yR+Cy2K?%QzFf1;;QVn6i zNCYmtl}~|gSX~TGYl8W#xUz`;$g9n?s-7Kh*Q55O@cLCHw zfh3j$phSCRfCcoZkS22k$DbL?giEdkHfpsij_Kgr#T|a}J#BJbfS%UVG4bm!Rj-x3 zQMC~zAg3D0d{NAZ)9Z^L_X1c$>mmWPXs4g9~MeKi0IN@=^lsRkII6S~m!Z)OzQH7Rst`q{WNB&jBm5jCz#9;wL~_@fZDGMs%b+IN+!vbcI6S2_=ly%tdFUQk^Q8v_{^-9 zXSdI`a|aVm-?(WPRrz;r1@(u<3K?t*IOaono=qwS4-Bkh15N{BcUjis5dGVOO-`9wvfq) zd@yg~FK06>qurCa4Hn>i89jE8lwVunc*aw0v1-Ji`!Jw~q?b+`YTzVUELio!Vkvj# z6r`*Nt_GZIZgZcI)Sn^ep6j^wC@i&il1Q=n}Z3eqR zgh{mBniiUZ(Io~=Hnc9rjA+PGlAwm^6p?FEC zIDc9gva={f{m*i5qy{-`E{RN?l;{sUGk|ypLy!!m0tH)=O6408YA_vkZ9td{S^sZg zV36Hm@Q|Syy)7Th0G?W*H;?Yjr7GPW`n+9VL`rRxX}T>Kxks~i-u{4SK5r=_f`b*sMuQ4>2=5aw%#@l|Y0nNnPc(|+(mlwyg5v=nmJqA8}Of0v@!E#>l zuLk5D;qw&diGcX zdG|fxM=g2kb_zMYn5ur-f0I`g2DI_<;Nd$1%I@zB%Vq+gCy*lWVSle2lO6+h6$9Lt zOl6bct*L)e6bFa`CEKDZ8*Dq-L5G||C}4!jKlGyynZ01?mO)^GJA&1e?z-$^IHgwQ zQ(u~9C%L{W=C2LY(Wf(qTyU&eV{6%R2}vag3LMhWZ8G!+osvSMPaSI1Z3fc*gK5Zh zvVXHCx-|AcCr$B8$EilE0O8H>*%!W{s@(Po?bU%Pmgc$NisQ@JECGmbzrbOuoqf~| zO+m-Tk%pa^zOrX0#+RGw3SLf@1)Q;H*yZb(QBAtfE~MoHU@&zul;PGaO(v)!{`@KEf@_k~nm5(rL7|CH?dVX)^K zUTls_JpwxY+D-Y$A!s>}wTN7)mqjh=n66md%=mmP`|` zsutD!snD7{`t#UieL`+yA{ieaE#38VfdI^O-Tg!XU6MyIT6Jq;U{q*eG#!W^@Bp_= zrH-YYQgWoMo7ivS?-8EBez{FnB25*E;*_!P)gOD~f|h(E2WM@P6P|-9(%KOu4D@6j zkRX6`Ddb@yio~%08~zUqUp^?;Yz28j>Zo;N`tQu+ljjTqUi*Y^B-T}rXT!1ad#kIVZKnHUr#e;k4Kf+2 zcXLCj5qu3BoFj6)CokX);UzL@s_Hs(Gw5Kef#~V63ZH=2qDWvAV}tNfHj*qlFKIiy+VBMTn8F2Q+Fa%xUXu`>rPGF^|kCut|W8YZubWn6@?4 zb~=#{Lt;?(2aSm04-u{f;Qcln`rYYY#v=4c!t|5woJBqA0)2R&jp;j{d9YorC1=@0 zC(~wfqg*|e$qz_?rtOJd!?ggh#5*Qn2I$?3PNJJ!TWZTzj%u3Nr{vD5-zc-*d!#{R zh&|jIN%c}yp+IR@^)j%pZ4^f}>Q+!mFnD#(_+6;voB>)RD5gspxb-Xxfcl|p<&4bU z^AF<#5Hg#R7;e{0eG$YHC*7X5Zrz(OxbQ)b#_>L+};HY{QVaU&gZlBnUc}e(vb9p1W-b zhLY_<(=hPp3;5=eysEn_Po8t)Y_k4&RtzQ5lsLd#Jy)|6VY&i8qU(X~k9dK|;UY|o zN<#=)9Mmp%4(gdz>svoZ&9WW6rdY-9j4D-A5OfM3^I7}d9jwGB85xhp-n3U+c1;Z} zG|x`mSW@Y{H&Cw?Ha1KABC0-OYahdEQHAEsOECLWr-Oo?_LK?-uUV*CCAM~iK&Qfb z9WP=w90C~+I#TnpWe#nJkDyEbxkWpgh_n$@CPo&UsT!_nSvts~>C zl%hF`Ab&946l8d37zx%;5Em509E)w`MGO?l(POy`p0>qWv%7cG&&t6an6u9`>#};E zh2|{IbCgkyVnp~FUP#>tW1@&rz^eYOJNS61N_LGq10 zOI#|*zeE~Y>TBWeT}725e*mm6JgRa%I@6fQZD-QCP3HsfW2IPiA4`I;{j}gqUVr8s z^rw-Qq9GsG)j5G#5Sl7+`u-+$ff4Q0+Ak=mq_MX3etq2h4xQ9)-+b+TdH}NHIBbtk z0W%iJ^Cyzktn$as*mL^c(gufwDpFQXC7ZNe4*a7|eb>2V^_Wmqol@(0@9O2C=WL0zMDd54x7B!>j}bc@(xgO zxgQ+RfQ^RjEh{U{6-b7NeV2A5@90Yz!#Rv%NJ7tbw$-%l@+#YSCL9$bL$_@r*)OyA zm8Y7?6J}O`^KfESA8X4gNQeHNCx zD2tfjSoZwxK*nyV_J#D?jNyV8s*GAG%27!}8?f#lYCPT&XCUXxBG_JyP9_SKao9^{ zS6y{8YZ_^riU_3!_u=Z=(;#2^<}0ZmezG5sD-c(T(C`)e*@;GeL!ZYctL%%Swz=PIDxfaE?9NoXz9xO;2ndj{zroR6Hr(1)U=lQ*7oRsUc1;9d0kA98U>+O zV+deo%IaS4Kt@uu=te3|a5O6&>3KFa24B9%Jo;M`_G%6>AT7^_axz4_9ct#SAO9{~ zw+*N9g28*+sK+W-8;#eB5sJYN0G)pGRFXK2yV(pQVrgWtMNM3**V$Q956wz-lO-GF zijS?x=+Q}3Y%sP9@D?{Ue0CCqEnZu{TjD!Lzp;W> z!69}WQl_F@cN2v5G$a~Hk>NxQ57-W?@~HKelB~G7CEj_$6%_0Mr5`FG2u?Cov@n_cL&Ep`c&ISCaOjM;s0VDxsJGQKZ`2;S3``C8EU&MAQY$w&3K_LidnZM{AsA0;#GRv1l1F4w>&jCI+I%_L;3kAN;JCqu zvF?U%Np44ZkAB@PwyOE9oF5r}P~~SQW0qWYl5aDr;@~vloi>DxfFZ~uuNJZ#Cyf-i zhf)qZN-dUv#vaiG;nmLZ_h!151^Pr4#G;*9Ymx6ux|o$zZ)?vk@K@#)8lD&eZ@Tt6 zT7?<{CK5wjnop2 zqj!$p1fY|T#J=$-q%97()Ho}5dqzvj3@tkBpnKg0zsasVtZ2a`GVFKNe82=)}C zssZi!jm?t8_#Z)3bn3C-Cu{_6=?HgR2ZuQCNufpiC!W4y)K&^Csmn;HMSL2;;WCD9 zU$HF;Djh?OGUFE$e|wvCyw9#>k}x^`sxNns%dGl3?bu^ti%>zWTD6g6&?Id^m9K+7)j=z^?}9@z&hM-JFl#1H~v|A z^?W!=OvyAs(#l>|ZOwheo9a}kh7k9Vv1;l(y{tJqOAWRw1g3YHWu8G>1-@2dSwvNUXe|J`s$%o2ux3Ch*ld zCE|OD&bT@yM&3M0r&e{IYMV>eg{#7k?LK-ssKKYfzY}@9>Y3%m>Bbc0lbYG-5^79i z>u4VUp*e$jY!nRC2Y`c58c+zUM%5(2^6zz~OaOVH%$V*jjdSi{t|B-C-h94qiy$ka zVijG9lF_|4K>CaY`^m`&3StpO{HJhFe;0YWv@etU2cY%=Bvt$}RiTTX#-|Bcnf%KO z!HwcWYKg&wk#s(D_}&{hp7(bIK(9M*oLa!;WAOW`kL#ygDMpQx(YpkWxjW#eb1}1! zP+SmAmgM8FwN%J=&8iy1aWsjF|vubNsBZJT#l!1dCXhpi$jzKlH3b%x>!P z-rpZ@iLS_p1##`ZbO_f#sG5UdQF7Qo0PVV01Pgkxis|C#W>BWGv>KxSUcuD%hUl0j z*QWW5{GNpG^JYbFcRK5+Jyu2TQcfqQD?Fh)P+X};4$2NZ1E$#MH?q~HnAj_8XN!an zhSM9P8g9LP*cFEs-1L&l$MTN27Rz5Lt~Z_M-m~4>-(!qaR%na=aJ=1`+7%S_wH$O| zzVOxD^uC(8+{rUi=y$&DD_ibexy7Z6p<>vHNH?)<# zpAsV7=oyl@l^ss$ypZgVpDWwS_udHq! zSyD?D%I3qG{1V2==6CH^K9%?E^M3f`KfQB5IX+iCZ$bPMeqrLx1Kf7;H!JZoHYuIY zAgeJ+1M&C2{L=}-$hbGh^h$Bv%Si7J9=_FKq^P+f6r)(e5ac{@0R-hW%r~f#Sdr30 z`&B~Al;lDo>a9E=RW%a?2)FgW6J23kwEkt&Xw`9PvPLwpHvuCQr1dql!)^#hz3opNDkA{hTOo=LE`PV_- z$17Xe(%R)y41C- zTz>XLSR^3pNf>%-==-^Hzr(^?*AHN&2@!PAl)1u|Qu-vx{zu71?e8=b zYX$e{qZ?vAqTJiq4Kxzvb>r(Ym)KIMe1`hnRe3KTM$2a%Y^}kp-^|DAZ+|LakJ6K?ZnF=PPZyo59cmbF*IN#hPCLA$);xEf{xsi5pAYaEoqBg* z(%4#MVA@(zC@(Y)>L;}hiWQb`nOJ(VbtQqUoDXr8C!RlmQQf-6v6<5KDjU*T(!y>= zoXq^9qw-m&`@184#Mfm`H^bGt*vK!<**5WU-xB|!WuL`I5l0Lb~u}yuM^FgTdW*VthT4tF89y5yp}|!at(%+_GxPIb>*9xTX>#lka{M^WM>zf z8t+SPci89N;m2!nPfdG7VJDgl#yKQk^a(FEoq2ZtEsZo?%~Y_lomdV589tv|A?6~S zijFEJfSG`eL?Qb@#;pWRMS`gmti(&^=gY!&irU&vu9>Iryf(`&VlnL+)BIGgext#h z^GFG!N88qv$LK@^BMO{4tuibR6xHZt6?+xM)qat(-2dmt8pDrPp4hNXYFQqG?q0sJ zWY5|ggO|IQw$}|uSvt;7q+7iuG%^gGWAUm1z}mAkg(O~1m#(NJDAD$P}whIEJ&z zHvC1Nffd{o%k6iSrjBNfT=1uI|%HoFuqli5P8wu7rPA`zKumW)| zv9IiRP+ar6PuEsd58n3AzI0Wa<862bvHLbV*A+yOTP&a+4H^NL7NY?d2yjkLLao=P z)GCac0FF=eT6He@LN*;(rcv?-P{pfq_kDO4tI)=*OzeV~e!f|Cw@7-K@1y}pi+dC5 zf0@_pyX2iBXC4OgNn&>hSFvqR-m_Xm_jR;eGcR-62``FsZb*v;`m>8?D@>AupuyH6 zm`Iqh!7w00Q@~MIy*)_@N&Ri2G@mieR@pYRV9=iUgj8PQr_{;N7NO2`{-IsN2YpLQ zN%1`ff{kFjQ^AaSVYbbWUX zdSl`Xz0jDLK;k04|^(<|g@8 zW>yd~&x9p&O7_pSMWeGp^^OE2%Hi_DAWyODiwRf@4$lvZ#W+ zLxVGogTV!YC5GEumg%l+T0O;EM>rvuhGgExhC+aN zJxg~a44=;+-ZY4aC|$EyJ)l`)jJuNsgfb2gY01>k+y}4rh-%UEXxj^}X^vWqEQo1k zX^Kf=Ylkz9$VOa(y)A%6#Km%8V`B0K7UxA8hpH^ZDTf7nDn^?mmMzD#;TN{@2N0F^ zD`G7!C&i3K9IRGM249TAk>^I{Wwy3C7zYs%I9%pcN-vVI(Q2B6zUpA|YYc~=Be!DZ zY_5cv>nI|Wr&h(K#AKP@$(K2;n1i*&%AXiwP@BY~M+%mm9e8L*XLDbLDNpU<_G{1U7)g!iL>g^m znq{m}bsHup_&nV2-YRAZAgypuG)3udcKC4`Tip1 zc|nX%qWY=iNDlJWc#ZymcB=g2n9ET;DPF!(jQjBupx~?W| zDWmM`17swz!nga0b9l%?VVUxANmJpR%z81$u(QOr(IFJczS}*@&tW?lGgDbAdml5U z8DHMkR#{-C?&^l6VBT$`B`PYcP`1#>ShU&AtgD@oWVqi}Tzx!Sd9Z)}itXL5DXvVG zzOeX^OE4Z57oA4=iod`3L~m|?HGqE8inp!JR3U0bJ~4#_mvz%q;6sgKR?tTU@CbtCj6Mrn7GGPdciN^_2<=f^lN+8k zlHh_|Xhum-LQ+XzQ&YhT=hTBnD7-?yMqQ1Y(xK;hTf?nkRK#P|c1M-|QSq1i9XZCt z{2%o6<+vJ(N?&^)5F0-Ls8{{AL$6a)D&EqXHwoHUmX#E;HkqjW7qydrvYn054Yd#W zZH-MBDJ2%^=bQ7)w?4+=^A5$c4ZIX;Gj(L$bGIGR5cF8Jw{ODoV}af}<1)=gBYmU6 z+sUMHaPChqWoOcL>)G}#Xql$D%ou}(CV>*c6}F8uRTZ8dy$&%>h_!;0OEt!#TE>`@ zzIypQr>yBPay(z)1Zp`P^9P`ujA+-QW@FX2RUQkHttKG8Kka&=)maa|e%H*Gj&T-> zIy%Uk$8<65qH>wonc}8_aKxt@U2LqLcacgkegdLD00PwXQ4Qn0$BoABe{?sCIF@ZL zVA7^H*;^J7eStwzb_?8Kfz@t5Jq-j%!M~`H{xZrHFonz_^N)1=^YCo5x7399#%1K2 z!L>AC&s3I6_O;_J`*lbfPsLBr;nh^myPs>?0W|QQabOZLEncX!5UD zp+D|!L9XAMvfZSTZ4Jx$XV~>;8RU1LtL>X z2M1@}P+USxbQo<72wSX0uJU%wIBWpv=TMm zp^6SS8fa7&=juMP&o}6*m;SRf^YEZT@Nlq8x!TGPpG~;^z77_3&pgw4_MXCtAQ*wSI)varT~Vr6X6xC zq(b6OFgz76n^`E}ik^?_av;%Ir=~Ac+zYLywXv+Gkt={X63#x-(Xq!=QK$Vo=a0N- z49Ijmt3FD?=x0_@>%79O76rk6_Fuii430?+obUQi1mezdiZoEk*fT+|;YZSNyS!XK zkf_jYEt(78{6Ll6ih8TH;s*kF{tUz~F08U9HS~S!gVx@vK4MCFRZ7WwbmmJ70v^Spdp?ky@zBsKn9<9e-5PsiLFU6Juu zWo~cOmASL9xoa?V#-@`y+g6^WyZRV%*oZT;KVcB%!0COPa!6ItB8siFhpu{&KuPt_iKqF`c0zOxNMj=9 z0VPlP$6W|o&o(@jL#1FTrfLD>&kkG@8m!Iy`P6t~Ik5-VK_}|6w;CB;7IL|SJ0BaL z=n9Gk(xD(1v38~{6}R3wc0vJ@mn}p09pwBGw987C@Kb-TBvjg(5om=j$ze0(*E(NFIlZIMw&uHI_QVngI_b0bkB^}}vNiX-yp%VixKN@nxfI8+CF zRWTSTAkW{VQCh0OuJ7u_)XC)T$Cxh`b}Ylml!5)TxsG#&QlNHI<7`({7`jEehN)v? z5;@UmI+o~?rtm^#Snk(w=3}J4D!&J!AsIj!`gn z6MH@OxXA7q-JHsK!ivU?ONYBrIVN=2&TVZqXsISNGUrg|d{7!my+0>>3|n=1&<06V zEnAj0OUymFNIEld%OD+2xhdA*4D2wL|Ego$i=(j=(r|=oYvS1~k=m+5Yk9QB`l=TtayC;$u!b4{q-Tt_x3inZpu5JEmlZ6#e;^ZO?yz{XuLk}uRO|Pu}zS3M^b+!7Z zq-wVzx!iq+^gMBGb=qqV~r`iForzJlHRh`Xp`!WykdRc}!f=s7a^};OqA593s#tJ1h zA_2b?2Xj;`O(FpA&>K$FD|vNMb*E^EZ-{IIh%`h#J{i{^zz5La$>{w2<7<3lyKO|J z1vc?PkW6&B5ewS4a{Dksf`q=cZv^2K831KCAI}cla(Hw0N=%k~N|k{0D>Z}wEE2mm zMEXS4BAu)?!k|c5bPlB?Jd~0zzmzmSsS|OZ17QP7asQrcTRmqL1J8v$S@h_+vbBu9 zeFT#2llRT-5e|l04l-`o9y|`36lfOA)IFjRas>`e;%ahZC&6g(S&w+DFLX$G^;}%W zjx_Zz6o@0DA z+O18V)bO#uY{slP)U@|&UejFAevuj<;!fgTcc4tWWZqzG7C9zDU281W7t^prMg?KY zEihLK?nOZ_;g`|}J^IUVwRu^Op1<1qL6iI-VnYly(Ij-eJ=(WH`AUJ>!lbZcdnHGTs!!Wxhw~U2L?0IqdIxs@EhLby>5HuGRIbO=k zZ&W85>aZ?e_xEFda|EhpiBiPKtqJfpiK+yD2lEzua4S6$osYSKG!+|)DMZ@*?$P;z zj*-MJVgeUs>t^>dy?4xD?0yDQjQo`Wbn8OJm1e8#Jbm>7{%R>xKd%!Z^MlarOJfdw5SDPv_j*IX6o z1K_#1JJed+|9iE)FzN+r-5X)1XniF+spev5NAX=P)3 zqs#Gq0GKmmyzt+Wa=SK7Dy{LN?#{HI;Y|7%rs|K=2SOX#Xjz{v`A$z9xBV(9-0B_wGMh`>LEnZ2?CB377)FA5MD%0ro7C$%E zESrLiB#987IEP}5b+j3*AgPgMf4RtnZ>f+&PN%topc{7z%Bu`KRwC3uMn zU5apzQM{Rp78;0z%1O`<<_vfc~4+sI-If2{Ljt2 z=R6(rJgzYTTY}h1t3BDguI+3BKVhX_3Ax)(TVXxANsEFsEn8uQ;Ut`bVATzo5K5_M z%W@TQdXRFl1PKg|~>sK1>Q7-HGikM(*kJF6e8Dh0N?VB1gN|HnqYQw{k1KFT% zBgFVG{{yW+QokV^V`JvUl9>LalDo=7M;Vb-SffS7pV9-bXHA$&-1kTfaqyf8i#lGZ z5hH6T&St9IV;UWXQrafgykn(i&oPPkJxMESP=eAzr9S6J*J4>AiQUJk0RiK(&XUHN zGERmS@K9=mBNDE%-Cu@`XC857^=6WGtVOkd^-)jJUR}q>N;+{u*oL4HB`nUnaZtdstC<0fs0(ZT-!TRQts&llh>b$pfASwP4Dl&pXt{791OBz zUIA{1-`63FUYofvu1s!*?60&c`oOhn8lz z_HS0(jHdYdZ~@Vr49PIFv2vB0)Po`^i*h`O4j3x`0K>?1+-xxFfi2>{sO_D|P8Q^8 zYe<(>cbgBNo_c>{xW;fvqaE$u(_^{{Y_luQ;*N7gkmRQB+Zb@nAstV@CQM zK<>e?)jcO~NYYVav;tTB@&3JP`p*5rA_`XZO4D`bjjPRbNW02^xz=(C0a`vWsV1pW zXpTS;#=)>hw!svn(|s`hoNpE=-zo?CiNXCS`hd8VN&xU(+h+Osyl(K(4Auy1uFabj zSAX{(vwT1uc6?cYE~Tof1;ey+=FcV%q16<#65V9@GxRHZtjg)4`YS6In~L;70)ZeG z=rCTvHmmYSWd4Z+Hj^3iVB>sU8S)+r>J=p*tK?TsY{F4^Ro{;b#&C|x7@D;PgRO=r z2VT-h0=w#~`SHMV@q{_UT>b6wwmMhp8L#+(P-SJ*n4W*I3J46$ND*R@X0a)0A*zWFs2>*l&A~X zx2q%z3_;~Z{h*2}Imv>=_)Fs(-%IXdJ9~t4V~PXg`-V6d+28aAozKztn2=<$rYiaO z=ILfgl3a|KCHidHSs!UD$sW7aa8KsE*OY+kH0J0_Dy{v3KsHu2s zR8&_bITHS3`M*?sXJI99eIzi*p*>G|3|?JAX7HGki&BYRagJ^+R~x(H+j{Ym8A>{7SelsF10LW$ zM7&uTpZdB;%k`k2)s{9Oec?W8~DanqbpEJDj@{7xD$3BhbKbpQ?Yz<>o{{VQkJvR${Zw5=z3d%&?dz~2O0ykU z?e;s}-7jmm-0ntD%;OnUZ?`RiDAPepSIlwJ=7YX89qpz*cWuVctQ>T0(~ zeBu89iu#EX?fWGe6E9R{6t8SQZQ_%b&ug~bEaYcMgDNzi(wcl-%JByvK+7({*49Y! zIpWIg>wTD$m;ec3LaMH48ia06ac}!>BV5JYboQKp`?r4tX|e4wvI7((>q-A z&&rLpm##&+ywdXbLz6?)^6|Ch*Eep7rnZ-nEShW&kE*tzsbh>uQf&AYn^OUoB4{zD zX!4{~VVvV5RnV4G%AZz}sjY?c)xOWC?t~;lQ@C60TXaa(RG7Oi&8u)8+edwEo&Nx& zw!OABtt)WpS(w<*2C<>)U)``Xm;V3{w&eIt1i13FGh@SzkBsQ>hJz@i^14kVlgRur zY}7S}+J;^huCYnK80i!3$(;Lg?Xe9A%1D}H5vb6WEuSXONG<1Il(TuYr1>B>&efaE zdE|X-4`Qaf7x(zF(?-`OnHmT(xIwGUrslz^#kb}$YgxN;d3PQ*pC*Nim7fO_j=05@ zGBr}hZFKHR#e0&M{ zG^>$~qEE%-LBVr$9UCoU$O)F~&M_*auBJ+qCA&tgF(%n@V1dJNX&W})uF-ED1Eu#4 zmBV%ZXrr5PwlPg^^syFHv3h8}q7FMmJYy-F)<)I$P8**|b3Hbd9F-C}NYX+QB0@oq zBV6vJHB~>11a)|;&3(4pr^GvQjCUlOsqrdle%>!Xf1cmgyZXOXA5n5~B!A<7nZZ<+ z2A6v409j{Z!>T-;!&ca317Apt>{k zZKrN}?$}7J+uQcMZ2cOu3H!2i(bS_@=LAPg!GpSz6q-_~-L!V0T#5j*5`Dxe@zD^u zNUYYXYW(ioi{$8gi50Oho8jAl;z^(2!_gpls@)DDkk=~*@>AF#(XngDaZ!D+UlXLU z9OsRy>fEL+KaP5Vuv)=x_I>YlyMFJEf*^4O-^f47qJJ6`F2dacKp%+=4~4F)D8sV& z-$}W2-B5AU3eq^Mh*tSFmz~skH!i79S<5*%T7M$U(Lk`NumJRr9{z6Cet7)ZzWP^Y z0XYEWQ2Ms5?awatvOQVu_YUMcWUUB>D9lgX*8UOaK6){%Nb_nLlAn2b&4n9y8pjz4hyutxk^-%Q z2YuNENun(E@&Tqd;$bUEJe%iy<^B;ht>%GGImY1Ga&_8I)t}Us-1=JbS9f{G zxiAK}`bKP8c7wK~)i5-Cte+%Lqz$rZ9naMUyV{yW`88O~a-qb*#-#Z*Se}rtOWnwT|M|H)d=JqUgC@m(=>G zz4meJYSa30j3YS2)gdHH-zyazLaor_t?yIc{5SCa?EO3GpHlW#9nR#XDLu)ACd4+< zu%*Oep4}tVtMqO4gXdP{^E=G%q(7@YBL;6cc5d3;+le&OdKNZJI$pE08*ah0v^ccvowtjh9zI$U9+QTTX#*~8^R1v?Aq4C$klK+R)?)fu5Yd1 zMcVHF0NK8Sx7qa_ze6wfZBmSZl-aCM32JEPB0P(VNbF3H-qGYVc`Tn)d3@_THPG*at(| za6zG;;oa?Cr@IzC&df`cdws!W7_1nc=F15&OF#=pS5*H19`uXodtL2t@ArFG4(E5Z z9VCR4lH`LZ>0@eM6=T)h^FwicQ~h53JN-rZXMyA{<@#~+7e~s_bPWb=!@plY7tClO z#Pbs`f9U&fcxl@!Z|P5>YrA`9W$jEoUkg7sXYTnoLDI9gW**YX)^hT+`_k@89;3ER zgD6T6?^wAmGLD5!QL!;PO2%9c-)GYLe($00%e;5>cXfAralJs%0b4ASsVm*0w3x1` z{IlCXPrgDfi?F`BzK%Ecld40JxfUNcG)?Q~2lmbHm8MCybr`g)Z66DAX`53#Vd3lE zmZ!P)mgb)i8MU3MgO#_m*T~G=J~7VI*@b$p+eDP^$!FW0)m+ISwf-_R;@2;&d%fRT z8PD+l09V=wzxf&srJb-m5;*&D-*6`b1mzTgpu{c5WSiM@^mmw=X|Vmm_nonYMG~$DRyWA=T{9u@21U zN{3P1NxmEuD0$ zrEjl0cF)zTt&sEwELrtUU zGnkSyE|ItAW#F+0+C3U5&_@(dnBi1p$?5 z@5eoO1|`YA+o&;<-5lz7OpR>bpO+!~k4!dJJh>3!oJ8XUjwXq5_x4#6O05tL7uaU8 zm=%&J;tuIBY=)X4?gzQLQNc-7v*U;3;<`AKoS`Kc!bRO;yNYd7&j# zu4?>s_K(ZU{{UqEA9XMM7hs{s{!}r7{)xEnbQIelGZ*%fLo(R{)Yu#V2avzyufQLT zxbf$cD#fgqVB(8YJG^t&=@L$FPD27V&?ez-{9%@xxbVfbL+UcoVhW0LiC(sU*hAr)7=L)>@TvrtIqltvtimWm_EKwnJ^oJ>L zoe~MPRXnP|Dr|v7_=!4P5&6}()ZVkO@%8G=J8YsBkwLZ!NmI#Dl9=6<|N-emb(wzTbuKJ<@B)b>8L z7DO0twsW;S9Zw4*BQGhKHWpCHlOkjoUaa92aHK@Tn~n*F7Rj2wqxF6M?{)3Ur5Kg^ zhi*_FYMfgpgtI)?RzDE*ovznw^+H{#%CEHe)B|gZR`G7m-Bh>;Us$+4nTghbC<8D7 zI=eh%RFjV@ETlKIpBnjFqX;=KCxU>stmkhXdFL+m9MxG_pwgE4Ogk?{**&(T*IYxd z&6=ZN4`}S3sDH?h3@*h-&2nUAw$I16KOGNn)rioXDQ%$M>qWfpy&=(vX{Z7Kuv_`F zRtYR@6J&MR6=V`iV;Jh7yzUMvtoWg5`n7jUqrz2c4U^jGG-RMh7-|#b*51o_&~qK+!b6N#0-y z0wfUqLhEp$g*7LeH0J$2=0&Pw;O+IueFp|T)0?1V zYTJuS+p%i-ZN1*@9;4IuyHP{+VWtyrPPj|6+>Wbp9Z(53RNH~;&3X>}{RiSbpH(AQ zZWALiHp@)o+=QM*q+(&KBHbNz@AUrw{ZjCJt@6)hc^CB&Q6T}KaCneg{Miz36h4&KsfcKeg; zcFx~>wcU*2MQCIo>izMRI0}?d_{L%n3uWb+eV^Am3Qi?A`*a%uaUGjeUOFR@WJ^jh{t(bF|O z7cXh)iK$7fL8?W#G`!rHw$A3-`cAKbsefXwcKg1LeZpP2_d9*g<0yA~agxse0B)+e z6Sq?oyA-&#-t`^0{xRG1-Oo?o?e@E!j>+5ZcDs{%asL3!oUmaOnri|wpt|Zt@+bZ4 z`l9;y*xyxKTgg4c<r@c#hAyWWm{{@GIRey`2_T*8c#0a`oLu&Fuq8(EQmu%SqXB;Ak6f3wU5bs7Ih@==12( z>$`ukM{m2^4(IB^QPOs%>5MfCn8s^VDQu}T(__j%>P_^$mGtMK`Dy3RmwT>#Tg&~0 z<@V9m_6D)Fa6G}p^D8%L;piJ#rqU+WjvkD48ROeH+pAK?-x2MMT`wakF!YU2a%wt! znR92u*57YOdL7Ty2T?B9acCIQ5TEKYZop%I8)FH^hqyhTQR=-vrOtq-1EiCf3ohw)WS-eEq+tZM>w~+J2RVovY#HXxfgWtmR9E4sI@f zqm!lPK1|wK%9m5{>31jTp)T!3sKhG3kGC02w$p*%kjMOYwb<8fuAs*Z`y6qiu zluqQNZbWrQHaJi-cdH@?jb?%7b};_{cqsEaNfXD1x%AwGrirAC!ynv6_@HrFV}%+q zAZbCfBx1SRuT56M7tZatqQaZ&C$Q#{qAMAy4xGBjC`oa%MF}3f1ATQNhwB6CiU)Bc z#q|T{41Tc4e8ipO^9yjOnB>dvm`lL1mOvPm=0LK(K27<6CHSYR6aN6};c;{syKbBc zn{09RkIUo#0B3qQu^=9svPIa$1MDE!m~NZTRKj%vzzU!nB7p_(q5-k|@vy-Vc4t{$vN&f)3V5+Nb&$pY(D6lA^CT;uC;_#dV0KE7U`y&ZkHo!F&>og&6o{ExNc zmFbJ+KJ3-De(229G~K?Sh8f}NgiU)kByh;G#RkspptB9V__pi zqA8||WG_|Qk8`$a?nku6XBka2N?0;7kF;Asv&UH9hxfakuH#VsMpj%(oB&G_g|*9? zr+c^Wnx0M>xK2tn%K@)r#`H zr%`yxwxT$Yc@PSd_^|@|{#w}BP^QcKdz#yl)vo)~Z#D*a&iEHL@m7`06@<}{{X^H{8O2Qk1U^3sgELO&zFxE46i7q5J!x|Pt38(uG_^XXU5SC^NggGW^O<#g6Gp{rFUOd+rc2Pm0=65F>ddi z)lg#S>&N%h?nWfQX;H&CcO~t<69}ip(9=!~d|b^7Z)RiQM~wuVb7N)Y@y3oTX?&%d z26G&7;l~7vH{0_@##qrJc6?%CfJxvb5RkvNEpVT{l80c+A}GEp{e^3tbN%$1on z4A|w02c3khN*OZoC1A;q;$s*^_Ts}2G@BW_w;89PNs!hAm`u0NvS2c4vi@%NyXi|B zTy@WiY}fw);Lc9hk=Wv7%oTC9eZ>Q`#xk)Kg)?6wH4h>pHdOO{u~v{u>b+06#XK62 zwy7)czZKL5wn=u3h0Lxoji!=-JMs{wC21GeCE=R3W`ocK>0L-l^3?5I%kz)c``*j8?!d#?;QM@+r^v=|$ z3SyA%Mb!xiF-WXZuB4^%Rot*y-!AqZ*YewB=vxQPT7+2S^JZ9v&t( zy|@dzNei5eto-b$WzN=h47n%N^-W77J3luoKQ|(5{H8)DZZRSeq@cz%Sw?Y}0mOVS zUFti%-&Hrc-Il2=g_7K>wnb|NF^o%G2ElJqCT>3rOst7kGK&V{X%L8&T3lFk4MY`3 z&9D@_f?~+Nw9g5~kxFDl>h39v+@osqVR*K&q}%Frabp-M91QV)<%usI;|?!BBVBj~ z`uKi-S$|dMwhi?i=X+Z)fBerfcQIzd(yr ze07Z;7PEuJ-fk^_J6h1XWvVwUnGQy+lA{)&9nlhNHN0hDjL+ZZaZ7__H(iwQXbszzG{_qrTCg_8>{a-xaj$QUOJaHPz4s=Qkg(=qZ!P8` zc8MgY^RsmCh zFQnaTbnZ>WiypbJYWliHW|A1Q^YU{cf#y7&poV!`CL5u#8WEl>-w4$p70+&giO z0^(H}vjKZ^%`Z#t`i}2#PUPn*_LoRgwOVHJ?K4SteKZ!Sz>P?Cd*|PD&Fu!g!-y-< zB%Ai1mWIn3w#NSeb=nt;`=#>j)o80}r%lvrV652x03_Pjj#bxYnMwT`U=Jbl zee(#YJz$1}g2|>o&?!5;Q={YRyQnX1A`o&Jq#L2sDZSxNv#dtQMQJuPp3*a5aJzU0b`wHpD$4I`_#uP z6mb~F_1~Iw1zQ{6Ez6eYBiB;(Z4O%FOB#6LE!~lL99XvRoj19hCsQ_Z85$`ST)K2d z!ip}r!tK3u($ishakjCMQQrm+F5Y{W3>TYB8$;D9>hfcOE=D`C`#BkznqwK5v-9C@ zwr3cv-4z5t*g1F_=N&~$yQf?2gy?9zcLq%hm9J{pT{U! z2Rn!pA$)Sqv`Mflld=#CJ=$W#F?E8&Hj49Ao_2bA$4+k1n7Fc{A{aP$_Pc%a~fHiH~#_I99Lj?#nfR-8{;Kk8M=cp_A(H8I?{~dZAq}&KTpALSjZ_soYs$GSNo$e_j{( z&2=!0qDoRywaPoxiAF6I!cw`_kmIkPXV;vu%k_)(Q6kV1eMNcEDj-@aujVf0V5@5x zp?s8ajFli8AhUd{@#!D-udCE;-|kdg}jclsMsqS7*psD5?kK%hI*cL$SDE$lU^mBX0%gMg$k014`9=b$YsX4Vd zt($i7i|6aoW}T{MX!*F>zMGRa9-*dXL7k`NL{v+SnU6GbW6XiNEgPtHB}%XqRE8j` za3ryzj!JD2c~>TP4D+1z9iHU7YbdE9)F%O7YOEk*Y8pCHlYValt2VfspInDjZEOAF zI*vA}mxq%czZcAniaYjC9p$(jgJBEZE@M@21Pt z_vJhNSsJBlU`j%_M$P+42}i@ze=9d5FD_i{*=5hm%$8~Lax&+aBPM*AUF4f0Y2;}q z$d+ejl38SCXLc^`&PgQp@nfXTHNCqPwN}@+-=$2}xwUTBXE#mc^1SW7UAvzcKF4i|hR%sQawnAQ*g~lVwdG9T$5Y%CgQ}{{R7(eD99AFIH$8WM!>_oDJAj z>?w-qlUkCawn6{`&n73nsB#8@2IvFV$)ip9+iag1 zi2MW5vU?X*_}w)YKP5mPHGejK+bqCU{{U;P)5BQC)zYVILYxZWtXSWQDL5aVoSI>& z6XlOJQACl`0NjAD%j2`hmGT^D8y&R6fKZkWHqvdgw%#+Il#Xu1*;!=A6k&Yvi_6!v zzvu%y#q`O#gaUDrzfikm`lBh~$H$8+4?w{X%Z#gx*^8GZY$&T1V{?*26wpZ!vJjkb2KmhCGW$T*tMoj9C7 z!qKR>BS@7b$Aw}D&_M%Z0!bS!IkC+@wj%wiyx&78NRt~0CvOz7k$uNBTp0knRV6u1 zC&dRpM{ixyDK{R&Ot~YQBNkZk=kuNnJnvc8IApw{EoN9G5y%=Sq(hCB!R2)wG|q>5 zqWSJL?%I^uioNdoJyDE1e(E&b>-P)VjA^Kih+YeBi#lpUgCN4eb(y03Of3T@LhT}% zlMyhnM0||KXkIw+@xw<6fPKQm5?@G2W#wlRSMI`n7{BG*2I|~xNzvS88IcVBq=btG zH6d{!30M}`(|5=ofJ|LA6hNT{Ml(ed%)BJR^FM#%4r3(52P|oE@}Mw;iPtUivH2!@ zO_9|k%Y?!vLKI^^H{)%(70DHpMcELxZDxmR%3nOvoU!T=`XV6-SmMB$+a*Yl$&0>r z9!ST6XlFBGOt{UPmXpFnZrJbYwU8$u+!{T?f5an2;JsUA_fU;J9Ihltd)TpcRRu*N zsQaBIj?ENe`fW)VN-M}ojjqwuU6`QDv+;j5 zlsYI=sfDX6jTWp?X)7Woa8h~c)rpabk(rjzSib!QK%PVO)sMh%qbb8%4{7b!8~ z=0;=#NfXAh;j=7xTYpx;o#)()VWm(;(X`hrN($EbPD=4N=5Nd%)Ia<`_p zlk0kz$}wf<%t+E>D#0Nk;#7=*+#R3ZS;iyt}p|=74FX zK(>bCvsiAv&i?=#^(>0^-sg>i#Kk4?SWx~Af_?!Y148VB-()ee@)@tK z{{YDI*GlP>>rm60UIZ4c&E7GMx|X-D6|6aGror@FgG4A*kv-pyTL1??lEhe3$1l_O zBI>W)rdkBZ`?I$H0NK8@JJDro7D+2)B32$pa7W_XsNk?)YchZvzEo<*!h>XA>{nol z@u3mSY%Ho%>w}IT{_#Tp0C1`lTIRST;2bu1=Y30~yOi#aW~dWi3@SdkS>h-X#>dF; zEyasLy>8;&{{WY79NTh-4$zf4LB~whPb-VNt9$a5Ymd+Zo6Z9-rO(T=s3^qY_DW774z}fv(YzJK@I%$ z8<#C0jvMk@yKUn0dHRJp1Mcj+M63ySTVLC!7MNM5K*yruxX>r`-v9uFbzeL{DnCTw zJ7}*aS9>m(7%&yN^yxj#Emj4l>eBgZW9eHxduINDn9;-Zo2cZ?8a6IppIRg)c`)?p z3>hO**q9kpL6I&7MpitZXPpuQGTEKeTnHR_K-_zTJ3^eI1%}v#ya_z z=IHlN#5+1Pr|48x%DrM?mIr<_ioUzF(&Cm#bmn;79xN@jlFy8oak91!e%G20wOKSa z#gIiN0!=HJxyDEN;Ao{l-j)*>h~=fbs;OCw<9g!i?RxSZ&dJ@B?N8ma?p;vY;&5sL z=q;mVEL|>j5#*uG^s*p_4dJzpE_fUB&CLmUJ~j$TF=iRr5(scW35ACuYDBB#;^9%2>>TK^z@R6fY`?w^ZHC7kEA8u>$bj@!{F&B>}E7SkwEL zUXIj)^?Pih!L`K~eK+zk-C&hiNI=ANE0D6r!h9U;zDp;lrWWAQAW<6pE6f~JWWtJ` z;S+!|$~$;4^XEC~sU~9>$4st-SKPxGcW810RweWXp`=X|*${e=%!63ee%WzxxCvr^ zGqSQ%(uE*nkchg8noP+jX2@9M$&O&tlu2hAt)*MppC9w;T2K6w724NGDkNi zg*6>q#Jq~92^JY6Da5R0$euMtDBdGnYRz1igK0IL+}ri~H7piPV#nK;d!i-Z2>kEj zoVX;9B7a|uahSbrUR2~sWOu>ad6A=&8#L&RB)9Df++zO#8Y)K40!vNFWnqOEE^Qpv z2js%q9Ib?yk&c^H7mKXtC6t!NvOQAoH@=+rp6uTH?>lQY-o@Lx=HA5fCu`&7?hWCj z>ewGx+Xq6{v>z+{!_+^vjGYHk^5z|@rR{ikmd@93^R%s7acn;?wM5(cqf5La)LZ(H zNJ2^ke@&I8j$;8pNR+yT` z;`2vw`Lnt8nQ>#ypK@nw`f9 z;>DD;b=mz@Rggi#YgYMD0+xGB+@;u!+I$5gcAi`-926Y` zE0yAgfkmTq2O?RB0253s3oU-y01!M5lyyfEYh<)j>%)J}{YGuzJd~V)oMidOK`7+P z2p^KrA1YRZF{S?iQ27V>HbsUba*fy~FIFkw<4zG;FP;vGwgQD%7qLlJb5-W^^VTLR z%%lK&Ziju6Y>NO7j>!4wbVs?7adcI;}f+glZ%RyU1Y1vwbQ**#SX^IqIYf7X+dhd zP(=fSIXpMi1(gVjX(FZ`o6{L9kt%rp@7c=3SOtYx2FHy^CL7^U8r%8K=La>`Wcy(x zHW?Sn@B@_`CE(3vu16qNP+7RJVaEER03fvx2_O^S8^4R~v#lZnS;>@kcJcC4Y~yeFIrHZC@qKbQLlsqR zP~bP)I9$12dLXO-BIw;Q;uIb~hh36L0MH>v5ll-H;G)$zIJ3?i+qcr69~_q%MN2G4 z&eeP>80X~aTLNwpD^<0F^J0q7AFuk|8yzSVNOWpiKs27z@~OT&8{NNms`7>Bn~_&x zhV8wI;-3X)uU+5JC+dgFyk9Q$kEd;4Sc^jQIXuSO`_pY{xrPYF^TL;NY?*j%%#59G z5tFOvSvoW1?oApUI|m{?u&J%-nHr>c`<89L^k2X`y_t3h>or_S14zY$O^sgSHaNVP z->;m0XY{@Iy$<`W?dj8C&XC!_g40b!rN>^AG(3oBhBjBmnPJlO@agHdsT5G^Iy{Lk zJ}jAX`|c_IS%Fm;?f!D}1XuLYokbFcL6++aYp^3&#nA12c-OTvx!qaFG&`BqI%g>E zSrn=Z!CA6F#Kthk8kT`1gEMpCp_3jfU$XO$%sdBUH2G#coOF^|A;l<>!;Oxy#mVD| zWrUvMp41}19NOxPQ;Wb}Q-8lp?Zlyvwj}n_;X8@kp6%0K(5R|gcq^#Eh@;Ylr4qiE zs?Rhj7COd*7khbSks~`DK+TYSOrKIH*<=`gs|QG>wl`x@ zEKZAIyeLL8W8A4zY;xPex=?nH^@%Ys@U@J``&kzjEzLASMUU*>7&G@8#JqzE3{pDL zNO)u_NI%BS97n+&?1Z9#v1z8SO@&ok%aYCW(9Q04;xt@;#8#Uj3@vV}sI=M6Z*5_; zyiHFkHVPUi+#lnp5=fP|PS?~65`f7x?=qIi^<$UOS9F}H;xZx3Oa_|Cfucp{FF!wG zx@U4NlXq@NVJ(ENs}>fadD^kkJlUFdW}lsn_VCXF;%nZjm>`}Ojr1HTon zZ&f)WO&LZ##>2-mTBaroEGP=urwN`5ZwFBdo+_BPT5Xj|MbdyskO%Ghy*TDEWaeeb z8*&nAT8y5fIxMm*`E4YUu!x`>=9HG;%Ey~At|l<4%C~5wjb7?r_^dac8+x$u{%VZ3#$J)I7r=6255`Zjhimb#z& zxtEu?{{Y@?E9vWZ>YImmXu3w%O^=tfb+0-!qr32T_D1hMUF{UmwtHgs8QI z5-b_YB;_eePM5g^ibcx?AQ9DFA8I*vkUY@J+!{vG$k;n!w&5%Eiz&zOVlRQ{C`oJb_O=#^P&tb%k=x^7MH2(7<(_w&3DRO zpSgb5%ZYXdejDw*x&Gk3=EB9>{%Gphxp^6xd+Sxr)T7!{YF}xaur}0j4)3?Hk?pe9 zj7j#z0zbc2s)~$r7C73OJsBTob)ZT1(UfJY+{^VehG>?WBZlRhEO&i8>$2=WCwH#l z^Dk7?wB4PxwOz}t>3(7F^XC@U&eV23g@?4YJ$rTTExGj#<{y|oX!(7Iq3$%@+6dBj zX5z(*QNY_c+sjSS;oDoA15(uM_xsR-_h%w7ffiD@Q6RYlqZqr>L8uK1ppnKvLhEvlB6FxPdGPD9_8PA%W+`5rw?7++V-!Cvxru2 zx6jn_^5FH>8i-BlO&_e#xrn$fj?2V&`B&pz_N~xgQ+g@Ap)mi>Gw zCa%0gATI%D!gGMIXy8m7@7Ex8XWN@L-OcS;Fz)?m%3FiLMUtSgF-la*fLp4lAR6TC z7@kIj5(gFoV=E;n+O8ih?~2M@mEzoVZ5K?8a7j{L>*;L}MtSoUaDuzf|2<6N* zTLOKwZj^Ec`fBVRf#*=zzqGdMv$|))cWQ6*3lfC_=vB)s3J1*wQx6>{N-5NID7ExO z0muQ>t#&`sYX1ONWPl>Y&zo-Z#t`E7`18;!6##Dm^)L$0Gzyt-IoAQscbnhA}nmK7L;`jRXOVkW*K}s#slp7m_ zn{?Tl-=;xgJ9Z#|q$<}UO&jl~G(q?x#i>1bx&wmtpsOyO^Fes}0M}JDZpe-k2swGQ z=bId0dFfIq7DNDb#2Z3A!A$^u4-!>^u~I<&uP%V<3QfF}Ry=L?tmNLhj3qjw8f$%@ z2h(!1T)60r=*rC@1%W>66bJ+`B}D_!_X9w^f-8;5DBa&T$ZlQZ8}a;7Julq|-G7S9 zx0@6D^NU*JSY#lE6=S3DO5GZ;wNwpsL8Df~iEV&LJ~;<}vYTC3-yJEYvc;9ITrTD~ z@s!Dfm;TS*+lOZBnl9$r`qrtpcOKr=H2rTw*ED?WjXPJ-b*(Eh9Q`X-(zP5mhci#q zw9Nc?GqAI9CW|WIUtp1F&P9+EQ=T+tL33sc+VnkB^OwhxH@okX&3$d^9;5moGX`x5bU&ge$JhQrl;Q!iYaLS@q0+b31jGc$7BH!R6A zWfPZK4;eo&`rmVKtJr|FiCO;u40ya1gQU;$@8ReNVk1_u)f>*ONJy%DZmF8CjzRri zKECmm0Uy+k^mN68CmJD$#569+D zTv#wNzxiN;Luf6vALA{z*F*B};qfBN)5cPh8(B${ZJArg)B|8`H9ytU>(q>uf2e)O zFWtpFSpy?M*%PBKT=`Sv9x`o~5lNFSep^o>AeSOJRVR5Ph7lWo?>Db9OSO&tgP)PR zwzs<2$h}Pe04{zRAc^Ut8oaWdWi<<^H1Jr^AnSmVVWFaH3gQ;s?O&Fk%%`d%>uVD^+0P?X;G;m=%t zT>LXsNqT6s>P3{YhxoNJ1A^$mfUdYdtC!b;%=r;{_o_t2@>`T!N6XNva)U*XvBox^ z2;nhE*zrQDutSJW95AjRSiAxGkJn!C+f#kGqS9nbOz~OH`RXP4d+@Saq3L2dFu%8l zDp-|f=f*l#$NsSY0AHAd2>zfo01@YqhuEH2OnqF0^yfD&%VsExW+=fB<2Y8wiZdK# zNB;5p*HHeNIcv8X+vA50YO*~W_y^(18tg)%)m*OfjH*K6#(%?-o|IGi%YRUeZ@2q@ zKjjS(jg_VH=gN6W^zkLZMSovnxRNBro_PH^1IA0VvLu-c9HM3Uht^mhrxPjc2CQB7 zRJPsx2z5XAd(eOVsOn`|l;|sBptNu+YezHaVp*ggLMcztGdFkTPS59z&ZVz%90i~jHa9UV-4JY^`9qaZ*) z4MpLtTRa@}C;tGQU-c9vWnZXiSBlI2HS*KR@dy$iMh%YJ1)*|)GUEqEIK1ThVHc?> zmJiH+vN!(#m1S4^vbi6j5{ z85&^3%Fom#$*Jmp9QA&#EF~y~X-Gha{{YE1H^wh!_v!tQ!o4SAdi#Bt9m_67xyA-d zhwEF>!$PxjWXq9=EJG4{zDJiU_K{^k0CFrz`76+@bX1+!)9tqA&F9Asde!a4B8Do8 zb9vm{qP}eNeJ0Zu43ZQ*5LD9BDFL|w)vxG|N<#r;noR0TacCNM$;*2JxZuO%r^XE^ zhFzV%;`nO#F~-%TfD!<{0*VrEzi%=L`38>?yhWaeeRkkowi{4zTe|-M@V<-m#c;NJ zL1;62t0m_5=C5wDfJDk<4Hr7&HxN-x(<_>_{7VDVStQ!IzG78f-afRSAy@wZKQvlR zMi7-}4w1uot1^P8(XNN!4;G#vc?2twkI=KpRI=MGbQrPj)|;|NfSEgE8ajb;)Fg_e0;*j@0tjk6MKC>85=bPQ1NkRn54O;Av-`83 z6#McExL6Ub9-lzth@A)tZC8eSTo~EV6X-@3K^D4_8-~ykOf#2gp-;4V6Id03Vew6raXbgE1#iKz6>&{Zl}l`Sy@vx{Z84%A=v+ipjq28Gf~;WE7KhN-hPj~ynm`YqM*CAf>G z2K!QM4o0h;(OP9;RHsIlCTl(%wTs)}=CRVHOxVaQakX7+V`uvL$<_n1^ca9mA9H+I z2G1LzVF5G<<=WVuA8R!_z9AR^QYwc&i)nlUxl*`v3MRshOwj_7+ zcYf2v5q3v@44jk$WvZ;VR`Ij++hAUXFa^AuTSwU2ddc&DQ$KY=xlw8gRlUGn8a)x! zk;s4n7F5|WDP51dp zo3ICF?}_a8D!daUGnL!s=?{&s$CA3}z+ZCOfr5zap|&d?cB|`k$Z&dMN9kCaU=Q*x z%>p>zT~X{2DA}abknwe!adeygGtiy9j5B!NxM_aZvwj?4Mahw{~}+LBh~3c}b|6X?f##qKzUe=7QFTrS66}y6{>yMeovTTsVr(l)@Gjs-Wcp>(tOmlU59(7{ z0Yr-oc&&VKC7-~uR@WT9a&3PUdBN0?4MTce+Oj1o)olNLoREKAgAII|IL z$dy$Ty`G1O*QPY@#RYFo-q>p1JajsxhA$&jw@PL6jxUnOJsi^K#E&~2rG{BCW0hph z!)Ik#k!0m2I=TY!8I@T*WdY(=GN)*ugvE=e63Kh-SB`6cmWK{q+wMX#i75L@Wfv8j zX}&PGl)hK1ue!YQ+F9OFYFJx-88ofcuVglxou)#CF!cR7!HhNx$f7fgkE7#aXr?q= zCXz{J%JjV#e190-ze6Z`pVXF_LN)q$_pPtGqq3~8g~sn*pVfXT*_Wa_J(VTiG|ab2 zyIeeGvbG&i+_vpVQsKSS0U+2XqQ-$2$RFXDe!*?7*<##GyVLv*-_x%p+(@jHxjWUn zKhx}Q)t(^P_OS#owPHSctFd-06(G{_0e!wPwFbcFBWC&9t+uQVZ>@1jk)a!G;_dM7 zZtwKZMT&cg27x!klHJh)tknzb^yDtWae0$iZTsz-qW=Je z&rDs}pthp}XJR2~u@nhoP*155Z_I&Uvat-`wF_)QsRG|)zZ2PLQCMi=w^M#Ugx4}-hbDQE6P@6Xsx3Gm*#+5LsX!H@@O6>wfQ}jr7_W+RlIxYZU2p<5E-#`Ef?3(#K-FDZ11Hn0VqNvylT6Rx2^Lthi zqDgvGwQW3GRs1(sWeRn}GZLU#Do85V@!0@S{R*H34*ohI(^WfSjrXN5;5M(nh6i2L zXg#vB_WuA5FUA71e_n(VRf0$E{yIOkvQJ)b(;ddE1Rz(A3r=3S+iv? z96KLN;k3!t1f`0%=mW@IRSO@>0ogo>Jg8Zq!@%1o>3H8YnkyV(`*am_sBd|y1KUG) zxaV}hd!0al1E+NP4i~db!RyRRbKR)W~{7V#n^cZ>#*H^>u95ca~3V}&oyUT z36JEx*#Lpch_Qe6vwq^LMT#|j8A)w!&|3#j(_#L;cD|rp_m;s<1q$2`{$}2V84DQf z^uUGg{h%-)fGWWy0Qm(^Yw$+>!x)HZ0dMc{RvS~!*exxhkB0a_aFjOHUd`B)7i_yN zL~%WV@K4AD*Ij}7B8B_|-%DF~#b-BEZJmqGK9;%bkc({#lYO=>o5s{}e_;7L1`TAD z41?TBG%A|@2qC?o5D!DY5I)+g<+^sTOL*G9>C{*#m1P#yRsPj0CDz>hb(ndUq>k7C z827{r;vD{7)?P};-7p0EhvODlt_`RP+X>+R0PCK(iq#rkZ#Yf!jrYO5HPMw{O>2=X zOAciJ0HE070q%nV?gWcJvO@^9N-FIRF<)z~(K2Jm_~zGkgI3kOwe(Ev<;F}CL7BbW zRB|6QEqia|Taut(VWMW2#->9a zNEFJ&xch;#MO2UiD}elppMY_YM7ydy)9uN*u~{v=ZJvX9RpL8UTX~IEFKZ2t>b0b3 zxf;%5z*bn+FkVd56snBWyqK0=a(notS)xrKW8r=2#t^*IRBvwl7g)S!7I^6UU9wY^ zMGaIZ?oIMjhUl`bTj|4@vf3$(xLHydk0UEg36*FmBo#$RW}+;F0Q+oq!7=Yk;iDAO zkgRi^lJWD7tR0IFI1q zs_^dHh6Zmh(62rprS)E2xk&fqFj%`T(c$`0am1n1Bfc2en=Le z-HKDwd;=^Iuy0U?Zk_PghW@A6WNkIlS#?F)6mmrila&^_H>48Ys<^X7MVe3z3c4hk z0Jp#$5oV}le&krv(^v|tH&G1uG3Dylrb$Cgg-|lxXNKC`N@@D<9B*BsVtI8vCL4)` z!g009v`=2ZMo$3Z~1>AguHjOA=VjIA|R0mgpsHe$xNtn{C{aV4i_C5&-# zqll>1YV6rMih%iEL=r)fjS_F71rkXY_|<{(IS(2UlIL%W@wPrOzx2mhBy_0O;Z4QnRb#_%HaO<`TX}aC z#Z_*+eMYz|Yod&;7#6fW)<_@{SkYhfkiR60qHm3n;KXxS!DjVeee$Q3+irolxT67j zbB&PZs;$pK)ssvVU83F4Eyk*t5(joUQ8i%M2dDrKn!SzhH@$r>&MTp%7PPloahr0Z zx_;d57rphKFVMRmKIZ6Oeex!)KqTL^50IK53ORJ zf1R9ia%l3l{g6N>+A6v|k7y_7y6h9;Hv^wP8wNAGdtG_sw7OGkZNilER#R19KTPBY zKGIFG7Az1?DkPuZCLC_A*O$eDYhSrq+ibOyX1A>Q=xLB8;kBw7pl!i?l+nn&a3$5b zB>wC6%g9mr{D&Jf2jobgJO%Gq2JY|&6wzj@&2RJ9Xf;zHrb_LQ8@8(zd;b7Ot$>U4OZE#{{U02TViMzV7>nU%U>G-n{izj ziU$BkA>yEd0{e*sUm6FBf&!lTeq96Iip9Ddhs9Q0w%b&^y2=0X`C$JcfOn+p2)dSfb5Dzz5v0dC_=o@+aZ?RyBUeN~?OuV>qJmj;(rMOWp6vT2E*)82fP0e^4816xh93eKTj_X&7;0V=zEMt2+Q%NjMVj z&&tadR%I0YK~d>|o-ya@r`>f~S3#1iV{(H(cYeJDKM(E0wpN<}Pyr0KirX#>YTnMd zm!Sp?QWBhKl5BuM9b;%2ci$K5(qZn6Wxoe`M!Vvf(nbhso84$X6W&=b+db4@k)K9 zj+pz9q2&a~qnx9*DvGYG3l=4b>xu8#)dSHMLop)ko~-so_{$|y;)x@}O<88!576q< zt7?nxEX;I+!m6zpb}_Kg9EKOxbIlzBaOq@Lx_DVn+V}_Y$E>^ukU}@J&z^@<8m1B>Wn~Ai|j& zE~Te-p8<@CKqg+a16)Nl5)w_6G2X70f!{Wj$51x)(H9p{l33AWgC~|mU?5d;+)S~x zBC0yf82d;boQl#>B(~3_?=y>Xw)1<5v&l%A$ZcEogX?=NR&g#8%ze2$h!{_v6X&Kg zD=o7rAE6rtS~SaSw@UU3sK3`EV22m1X zqnwsP#aK|TK}Tt3>48#5D=WpoAoM`9OO?fH&m5X)t@)t;0P4DzIV(xJZe_DOD_9CX ziu4=f#z^9#!m}0&#N7}B8~FqR3a@9r=zwIcsaRt1bg5ufm%lm1w|z`^u4zY0pR30E zV(F*IClP~EmR>=D00Y+^DhFhrZG-c~SnILkZTA)k4d&dfm~vpoGkjM=rsRVVOSHBr zdshC?w;*Eh^@YQ5^e8g`4<%t_kf44Xd-jkh-%LD4u~8S~;cIjHIK>7s_0bVycZIg| zd24&U%r47@y2D~f#1dEHl!0V)K02am$rM8$f!~7NOEI13zmFTWweQy^9LCtRD7i2< zgX{jCZn%=QkjyL|Mu`HMUYC$dZbz~YUeVVsri`cCJ_}k$A6JLSbTJfGj#150Rc9`E zu)Jpa6k#Iq4Qv96CB4i!g4V0pR=ciCOW0sTH;JJ=8b|iIN((bw z;l<|_vFDA}6#xWEDov>53lm0*0$I3#qPZXWS8Br@>^26MWv``X>&d>@Q5RlH(qbW< zIol{v^IYsNTfC8|EPdQVHyGJlh$6p0qWhS1_G}AQ1P>B7NAoS4tfgQm2Vfp)>DsRD z%Kf@&*r#y~Ja5B`0HaD@nkxkgIoM?=>sG*)Rc52@005$ms6IJNm34mY_Ez(-mcD#f zT^psk4aLmX>7v^+b^JK!4oXOo=*5sYxT<7pRx~Wv8=zX--Pt9B`0Vg|kPA0p~1* zV)dt#+L>}B5uuVsVlAqIk4`j5!uZVZUv*_E1(?sAW84GJ9J^Oq-Ja_Oj3ZSYOChBL zdcPG&#o)zsttPRROUNRvkXV6iNCfl?0IEY(aR7iq{BlXzpaSh`rDU(gCX^k~D0Vt* z{am2k#EVHBT$M2UYJlC?*v~+=sXJ2I)ioCByRvz43f~Q0(g;7Z_=x==P?F~JO_tg9 z3&F-dI$!%-=S*4_=->g=rk{1V8t}E#XHmi`ww_?4z^MNc50Qqu~nLB*fj z?df*1f|jcOo?P?0y`5I>cc(a(`Wi#qxW?CudBvsF7~~8&A-3e<$N(R0 zvjf-c(fyxjAXkY^L;hOaHuLpfb1ZGYQ5Hm!O-kxgw__%2+Fsk6oMq9B$t)_$#?^hm z6YXmR5PSI+2<%;s$nKQ1#b4c;7kl?_Ls&Kr3~ko8W@1oLLwdR8UlOd28orymuSy!8 zmoejVV}{6u$4p2Ak^l+rRiz+TBB}ZS_-Cf>Fzz`Bx{fP6XN~+DrbHF$pm#fK7TCI| zGTvo&(T1ou7jvrllDu4o9Jrn%lGyD$^*%TL%TCeocEKLGjY?QC_S$toU9ZVTgw}9H8 z?}2HctPON(JwZR$6JHZtTotmWscWIgk~pv+P(9fI_LG|`R5omGSI_c&x*|XpBc@21 z8E`rIzp;0ooVp&HsO|}-$I-N%M^ctBhCz*ttdT2Ur|L*t#SxGPAz6`~qar>9CA@gK%73#v+we@@$KB&00|Jm&>i`3{psT)~I1yJiRvGvO3~=82XOcEi9vJdCjuhU_AZKHtDkT(qhyTNp=EDNsP(*+5Z5-ILXcQ zC6^v(Cyscek~v|TKS`lvYGhK5Py4Dlk(h zisZe^z+(UvxmzDnt(P=}7IQ?BRDXfXRa{5F?~w|#Kgs_9E`P60h*fDa%GWoR zmC94*zI?brY{4&#v>jNl$OWufJ=hd&K>jF-89GNf;+x-tkF9+4wT`AB>9B2ht&E%a z`T)4FdutCDOVBZ4iLQM3*E1!W!D$*WQZg)%B0wQf2NOnKDmEt&f|fsVyFrV zdCjd?@z%UxL$XBGsR_K~ZF#yYG*(lkv}NcJK%qyGBoC4auWKnJpY)!{rY44rR?M8a zNo-^3jO8DWq?*QtrBvQ=&ZBcVK*&5>(4_}`DdGX5Yo_?2V0Hl@fknA@K=Ezp0WGR( z+-*|HNyT``bha*}lS5{}Dw55a$AJpBE%nYJiTDP)_$TrP&5%e0wOJr{YtbG8X(g3X z@>EW0@MiW(`Q5$>jD?~uoC~Noc)AvwN{L@7nK;1NRZ;WU0gCY|6d&1RbO+=OmEu6; z_^rj1wIx3V;IR7a3OntUNTrEF+PkDjX6b06zJ_;OIoN7PA{fy&Mc5^Rj$BwCO;`A^ zq4GQNW*BWP&F-8~+msxAI<4G{wOn;jRmHJaC4(TvlvB44Pnn%~02!5OTz)J9S0Wqa z0L@%T2KbT8bVgKIu{XTh6-!t3;NNG@S}JM%$hizLM7AYjyd#F&anfUa7P}mckB|Ti zMc8W!v*8dx$VhE8^sY;oet%K0|G7BhyRwpnUbRv-n@ zr+V{sjCBd_-MlopgxJDEi8v&Bmc}^`B4g})3b@(Iw;rpmwf#SLTu=CP)>Nvv@rQfI zjqj|#TiJVa?le>WCq_y6(q!V2;g#1^z1r8grfM>*p4A zn1o!EP*zHdz1A-n&F7%KMn1!4jnOBMLFkb~uwBTbef;tV#Net+^4$;jzpg$jXfKBp z*>rdo^NVnK9v-w{NcQ{>$OB&;5KVsiDnCug1~<#W)t1qazAlurWN2}XhJ&Fgy24g( z=r`kgD~ITyoPw5z^d z_~Wgpj+lZERr|6{3O?_ig&X)EH{u>D?7lMFl9K9)>-c(m0U3nY>ZSzEW^Hf-u!(Dt5mdCt=8&C9fL zS{#ST*JtxKOssrpkvS^Qh~IC`CY2I3cAg;$#wH=e0X!k@K?+Xeb`NFCTp@qDx1n*) zF2*9wSwn3)r3N}}_M>*>HcI+iN_T3<7U$dv26y);W7jnli#+5y zeZmEh6DI;7ZImSLKH;ym)=wv44gsl4`#wo|#Pd#i=yCQKCaJNL4pjFa37Tas2f)sErk5q8lw;IOjhW@Nv5GW}*uoiT&6E;=namRegbV z{PX6hx;gXBQ~v|M1R1lIGGppB2&!0M;hbfsw|3>a{T zu;J|l%!Poc_?+)m-!DWhk^6wkJYFjX*=yp?wLLdtFp1sn5483Y^M5%kXY}fJ`1B5=C`Hh`r@%|pSB_muFWOIvp08- zr$UB|g5838EIE)mptZycBmwPEEc0PiyC?MAP6WBz>=X&f(42TWo)-!iRo0^PzEB%n5}-&HSW*PC3AN9*y(-2ja^kVzv2s% zrmdqk;c?O&=oMB#EMIDdh~;1k1dfQi?10=&c6?Zj+cwSD(7U^6FDK70Nt!W?fd$Gr zIWIrk+yaHtX_9I6m42;XI9}Z z&z136x;{ZZ;JpryR>sA5$b)C(j?H&g!{foWrH?pw!r)H_ocZg5dR#_WNa?#h)y)>u zTuTVO0#F+EkUm%EjaPS9$iEV+)s^Bsynrw$;@eiQ8dC{&U3g4x)_R6F3q-rEM~bma zyV^E?YUm*>u#y#ug&~OL%7?fR)n5byOaWRHYrhdF63@b=jZbLOg_3(N)IUx}mV!iTnySs+ zdH6PNx_@`Rcc8&74k)h{HN{I$$|*=+OTFQjc{IY}e22YxwQkZ)0Hi z$AcL9^kHr&LinyMLevpVweeI22ghIw0?#kxilRFG*s*p27sL`#&x&hr@|AdR{{V+U z?#C90(fSlAFI)f;e-lQp_0<4-BJYv!&P#fUL!U z9noS%07q62XXBApAdmfg5`ja9<2z@Xd}9}TCsU@xjGevN3=V4cOL1p5HPFi$B>Q-i z@qH0wpV2H&5J>B?KVXC47PFM$tFZC${W>8%yL0Xg^JbIldC%D`@wE25n#NMeGMM!E zTp98OC}mM%G`l6_X5rT^0gG`5h&26As9=OKDL8XMZR#*pRW-ggzOQ<3Px_MAB0r`Y zhMb2Rx|v-cJe6Hc6H63v!;OrQ$BzQ~QHYc(ghB#<0O(O_EAC>f08*pHAW2G67EW9_ zx-rj;rufe`d!5`=3L>#J1&9~Bil;7e^k4F4s{!eWJXVcb@)Qpi?1~_TUcfX%VkIPj z&K!$%fc{FE1O!<@N_gFs8i75PY_nSAcY)(<88zNU$O^gff;M8 z{?s$I*?!u~A*;Xz(!DAOeQCg2n`iBK`7w3Uk`llxD6+j@>{XG`B!4z}1S5%3O42Hs z5`E9x8}VFxmtO9j6y&qx%zwW({i*A3kyHU9gDE_e7GTDj*p+ak6K1T@pvQu^6mSma zYN+#fdsS^$(M>9)?~O+|j>1w;%rd9*Q;H055B@*-|b7oyeLmJ~q%+Y{Y|J#nx>`JzMO zNZ4;?>X5DW&7+JMzARYKRAr}sYZY5!-L7$bZJv~7{{YR5eu}Xp`5}-3f074Y6C8!- z+1%gY$MMHaKw3He&OeSiAt%j#jL@kcqO4SqN9+^X9|ZP%8-g{DrZ#Jj5^so>8K>j>;`fc&l_Oe3rdavfCI{qlHq2sM*0eSHO zeX*W}c52UGvu6JQoA72OD)avU`5iJ}zH$Em4ya>~@o~2YK#(}hXkYnzuKEjK_Mu(e)LqH#vxyntm5#8^?nmg@MA(D)uKc6WXfzATmXJ^065eX-lY{{R#H z^YzbB{GB{NbPFIO^g-ibkJvOUe&~N59R)_!&pGkdd_R0n8ncGX-<#*pMNdqX`!9d< z_W|Gjgpd7Ch9Ba2t^VQtvCtXAlH>mX=N`5AlZgCmb^7Qfb@BT@_dm~$c=~^<(2J^N zv`M1&%eRlWS`uwV_1|Bw=l=kyKleU4!qzhF(`B>&0H{9NOno}z6UI*dBkTTOBd_DG zzv_0Q*1pJ|N%dHT*C4&r6Qy{-^%{ z$>~Bq+~4wl_Xhs}@B8qnt?upT<>|lSm&Drm_DgTz{yMREh>>X+X%Z}GRqbO?ihbX; zPyzWrui5^5Wq`;~Tiq?+%hum>5Qt#XlwhldD911I^i+Yw8t;KO`?`!@{b-SYJ)h^n zzrMIw*~7E_`l~j$?oJkNxWZ0Po>4QeZS^2H&zz_s>%RZA)p(mkcXE7T4A3RDcu!00l`Rx+Dt( zkKGbTzk*GC_(ySdM=$g7E}v-j!NYcN`&aw)Sqb2x`s4+EtB4g}A02zXj4J%sLAO!L!}8}4<@=(-#r)B;WA|qK48u=z1|urLig8{mqoYXC obpHVPN1zA=^~YbN~PV literal 0 HcmV?d00001 diff --git a/apps/pastel/screenshot_b2_dark.jpg b/apps/pastel/screenshot_b2_dark.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3c2ffb7ae2a92efe2c0141f4af07137b9df6402a GIT binary patch literal 52318 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 literal 0 HcmV?d00001 diff --git a/apps/pastel/screenshot_gochi.jpg b/apps/pastel/screenshot_gochi.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a3c34e4d4ff232a3363bdb554043f45993979a7b GIT binary patch literal 40511 zcmb@sXH*oyw=dd59HQim4lrbpoP%VBJPb1g$vGogf*>GS!Y~9ukvOCw=O7|E3lb%W z5=F8gU;yRuKX;vV?uYx<`*7c`Rb9WXs=ceKYj;)k-hH!lvkp+{YUyYJAP@k6ZUb<$ ziN~j-u5JrQ7-{L~YyMXO9usg&cvJx3?Gxya&{l((TRepPC-1+>-Z8-Uzw!Sey_I{p z_#f#2Fem(fDF6S;B5`sKaJ;Sb{Wb>r-!^{RS*Ke}=lZ{J$bYcI|H3K%!Tv~u`fZ(} zTg>PBzhQ^}4L=U_4ZN*0`k(razW)EgpKh_Dw^z`A+WHUur^n>ZJ}8sh)ciKG0RaF4 z&<50Q@&B*%KhN_~2>{4m002JvfASpi0H8e%0GL+(Cl6W#0C!>mpl#aM-rxSeIzw=K z#dC21fU^n!ps)Y{+7AFgYWd$C{U6@{nT!9Iu|jTn*lx%9=r*_lUVt+I0dxQ#z!A8A zi^Ks@Kn#$+Sq0Q@)#Lw;{v+i7PQoqsO)o$V29AMId=NE&M-9TK2Hgw-cW(zlg#RDK z{%^u3Bq9cr;1Q65fZK9eDgX~efCs|IC-|>O2!QbL2?(iah&YI8ImG}l9SOa-Di>7E zn1NA(lpA3m&E(+cA9KsecKfCf-nRSSa&KqrmY0BtkQjVhs!08x8+;-{{9DO*c=)$! zM9l%@Tq|Z zfM%ixb6HBMRz{WbS3=wSB&Om35_p|z#t~BzZ|nN3aw~HewwC9rOQ$Q+j^#bzGViZ+ zXFk1W)GSIp?~Kl`;<~qmTH+tp?ObXE5yZS|V`lU_t@WmcB*$=wgjTy?+_%$>*(TCa zg|ljzO_Ez~{Z)Z|E>0Mz3ip33Yt?46-jrQqC>zoH? z_LAe>kx!$%8%Tk>%OLzulkDC)@h)75m6TuXDnAn^;Q@Cw9dOF~u72B=!aAG}J$#f5 z%Zimr}%F{`XNc?)Dn#Cv~fg ze6{pH%vABY9~5OIJxH#WNNE5@^&~JIOGNg^nkY;08-Qc&pm5X_@c-E`2|X%11Iqb^P@L+0#@@UdmtdZKRme-*$u_gYZ*W zqdqZ*)tAUuvunJiQ6Xy%!*a46yf%;WCu3mcgGP^yzy4|d-PiIB)1q9}ciqYxK>Ts^ zv;1&+9sQUfre9DtLV_sEXj_zjEkTMUByUX1>M7^aH+!=h25A0Oki&X0>Zdg&n!ij_ zV7#gd$2yy1U?(Rl!^2-0BTBFJ^gU~wA6jdCD44f7X7H-ZCn;j9+XzDQ5z*t0_Y$=Q zdO*1Y5l{A4wPLt1BP9Xyqm~ZwB>sw?2*!L30>#Z!b#s(qVk?6*Cz=-<*z%3g!780r{*+UD1)nxll~5gOS7k7%QE2A*7)@^8AKJIGJ>)$q^Mnt_A*QzX}p zp%h_8vDW0G;3RoWwoMJa9E&)(7YYn@L2m%b`HX)HOP5H#8EMvKg_kVoT?N9Q?Ab70 zGF;(FiLY85eEFU()sqZk9p^)p{?*85T6FXlPvq1(>;Mc8y53`k48mKAIXXVdm`n-TXZ>Ahy016e zvKyFmx)rHTNT2y!s~y0YEc3eycz;-A@hH3Sz5&v04b{f>;{tQsB1RJrc()oO)>u

that wraps the table */ + position: relative; + z-index: 1; /* container inner z-index's because s can't do it */ + } +.fc .fc-daygrid-day.fc-day-today { + background-color: rgba(255, 220, 40, 0.15); + background-color: var(--fc-today-bg-color, rgba(255, 220, 40, 0.15)); + } +.fc .fc-daygrid-day-frame { + position: relative; + min-height: 100%; /* seems to work better than `height` because sets height after rows/cells naturally do it */ + } +.fc { + + /* cell top */ + +} +.fc .fc-daygrid-day-top { + display: flex; + flex-direction: row-reverse; + } +.fc .fc-day-other .fc-daygrid-day-top { + opacity: 0.3; + } +.fc { + + /* day number (within cell top) */ + +} +.fc .fc-daygrid-day-number { + position: relative; + z-index: 4; + padding: 4px; + } +.fc { + + /* event container */ + +} +.fc .fc-daygrid-day-events { + margin-top: 1px; /* needs to be margin, not padding, so that available cell height can be computed */ + } +.fc { + + /* positioning for balanced vs natural */ + +} +.fc .fc-daygrid-body-balanced .fc-daygrid-day-events { + position: absolute; + left: 0; + right: 0; + } +.fc .fc-daygrid-body-unbalanced .fc-daygrid-day-events { + position: relative; /* for containing abs positioned event harnesses */ + min-height: 2em; /* in addition to being a min-height during natural height, equalizes the heights a little bit */ + } +.fc .fc-daygrid-body-natural { /* can coexist with -unbalanced */ + } +.fc .fc-daygrid-body-natural .fc-daygrid-day-events { + margin-bottom: 1em; + } +.fc { + + /* event harness */ + +} +.fc .fc-daygrid-event-harness { + position: relative; + } +.fc .fc-daygrid-event-harness-abs { + position: absolute; + top: 0; /* fallback coords for when cannot yet be computed */ + left: 0; /* */ + right: 0; /* */ + } +.fc .fc-daygrid-bg-harness { + position: absolute; + top: 0; + bottom: 0; + } +.fc { + + /* bg content */ + +} +.fc .fc-daygrid-day-bg .fc-non-business { z-index: 1 } +.fc .fc-daygrid-day-bg .fc-bg-event { z-index: 2 } +.fc .fc-daygrid-day-bg .fc-highlight { z-index: 3 } +.fc { + + /* events */ + +} +.fc .fc-daygrid-event { + z-index: 6; + margin-top: 1px; + } +.fc .fc-daygrid-event.fc-event-mirror { + z-index: 7; + } +.fc { + + /* cell bottom (within day-events) */ + +} +.fc .fc-daygrid-day-bottom { + font-size: .85em; + padding: 2px 3px 0 + } +.fc .fc-daygrid-day-bottom:before { + content: ""; + clear: both; + display: table; } +.fc .fc-daygrid-more-link { + position: relative; + z-index: 4; + cursor: pointer; + } +.fc { + + /* week number (within frame) */ + +} +.fc .fc-daygrid-week-number { + position: absolute; + z-index: 5; + top: 0; + padding: 2px; + min-width: 1.5em; + text-align: center; + background-color: rgba(208, 208, 208, 0.3); + background-color: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + color: #808080; + color: var(--fc-neutral-text-color, #808080); + } +.fc { + + /* popover */ + +} +.fc .fc-more-popover .fc-popover-body { + min-width: 220px; + padding: 10px; + } +.fc-direction-ltr .fc-daygrid-event.fc-event-start, +.fc-direction-rtl .fc-daygrid-event.fc-event-end { + margin-left: 2px; +} +.fc-direction-ltr .fc-daygrid-event.fc-event-end, +.fc-direction-rtl .fc-daygrid-event.fc-event-start { + margin-right: 2px; +} +.fc-direction-ltr .fc-daygrid-week-number { + left: 0; + border-radius: 0 0 3px 0; + } +.fc-direction-rtl .fc-daygrid-week-number { + right: 0; + border-radius: 0 0 0 3px; + } +.fc-liquid-hack .fc-daygrid-day-frame { + position: static; /* will cause inner absolute stuff to expand to */ + } +.fc-daygrid-event { /* make root-level, because will be dragged-and-dropped outside of a component root */ + position: relative; /* for z-indexes assigned later */ + white-space: nowrap; + border-radius: 3px; /* dot event needs this to when selected */ + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); +} +/* --- the rectangle ("block") style of event --- */ +.fc-daygrid-block-event .fc-event-time { + font-weight: bold; + } +.fc-daygrid-block-event .fc-event-time, + .fc-daygrid-block-event .fc-event-title { + padding: 1px; + } +/* --- the dot style of event --- */ +.fc-daygrid-dot-event { + display: flex; + align-items: center; + padding: 2px 0 + +} +.fc-daygrid-dot-event .fc-event-title { + flex-grow: 1; + flex-shrink: 1; + min-width: 0; /* important for allowing to shrink all the way */ + overflow: hidden; + font-weight: bold; + } +.fc-daygrid-dot-event:hover, + .fc-daygrid-dot-event.fc-event-mirror { + background: rgba(0, 0, 0, 0.1); + } +.fc-daygrid-dot-event.fc-event-selected:before { + /* expand hit area */ + top: -10px; + bottom: -10px; + } +.fc-daygrid-event-dot { /* the actual dot */ + margin: 0 4px; + box-sizing: content-box; + width: 0; + height: 0; + border: 4px solid #3788d8; + border: calc(var(--fc-daygrid-event-dot-width, 8px) / 2) solid var(--fc-event-border-color, #3788d8); + border-radius: 4px; + border-radius: calc(var(--fc-daygrid-event-dot-width, 8px) / 2); +} +/* --- spacing between time and title --- */ +.fc-direction-ltr .fc-daygrid-event .fc-event-time { + margin-right: 3px; + } +.fc-direction-rtl .fc-daygrid-event .fc-event-time { + margin-left: 3px; + } + + +/* +A VERTICAL event +*/ + +.fc-v-event { /* allowed to be top-level */ + display: block; + border: 1px solid #3788d8; + border: 1px solid var(--fc-event-border-color, #3788d8); + background-color: #3788d8; + background-color: var(--fc-event-bg-color, #3788d8) + +} + +.fc-v-event .fc-event-main { + color: #fff; + color: var(--fc-event-text-color, #fff); + height: 100%; + } + +.fc-v-event .fc-event-main-frame { + height: 100%; + display: flex; + flex-direction: column; + } + +.fc-v-event .fc-event-time { + flex-grow: 0; + flex-shrink: 0; + max-height: 100%; + overflow: hidden; + } + +.fc-v-event .fc-event-title-container { /* a container for the sticky cushion */ + flex-grow: 1; + flex-shrink: 1; + min-height: 0; /* important for allowing to shrink all the way */ + } + +.fc-v-event .fc-event-title { /* will have fc-sticky on it */ + top: 0; + bottom: 0; + max-height: 100%; /* clip overflow */ + overflow: hidden; + } + +.fc-v-event:not(.fc-event-start) { + border-top-width: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + } + +.fc-v-event:not(.fc-event-end) { + border-bottom-width: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + +.fc-v-event.fc-event-selected:before { + /* expand hit area */ + left: -10px; + right: -10px; + } + +.fc-v-event { + + /* resizer (mouse AND touch) */ + +} + +.fc-v-event .fc-event-resizer-start { + cursor: n-resize; + } + +.fc-v-event .fc-event-resizer-end { + cursor: s-resize; + } + +.fc-v-event { + + /* resizer for MOUSE */ + +} + +.fc-v-event:not(.fc-event-selected) .fc-event-resizer { + height: 8px; + height: var(--fc-event-resizer-thickness, 8px); + left: 0; + right: 0; + } + +.fc-v-event:not(.fc-event-selected) .fc-event-resizer-start { + top: -4px; + top: calc(var(--fc-event-resizer-thickness, 8px) / -2); + } + +.fc-v-event:not(.fc-event-selected) .fc-event-resizer-end { + bottom: -4px; + bottom: calc(var(--fc-event-resizer-thickness, 8px) / -2); + } + +.fc-v-event { + + /* resizer for TOUCH (when event is "selected") */ + +} + +.fc-v-event.fc-event-selected .fc-event-resizer { + left: 50%; + margin-left: -4px; + margin-left: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); + } + +.fc-v-event.fc-event-selected .fc-event-resizer-start { + top: -4px; + top: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); + } + +.fc-v-event.fc-event-selected .fc-event-resizer-end { + bottom: -4px; + bottom: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); + } +.fc .fc-timegrid .fc-daygrid-body { /* the all-day daygrid within the timegrid view */ + z-index: 2; /* put above the timegrid-body so that more-popover is above everything. TODO: better solution */ + } +.fc .fc-timegrid-divider { + padding: 0 0 2px; /* browsers get confused when you set height. use padding instead */ + } +.fc .fc-timegrid-body { + position: relative; + z-index: 1; /* scope the z-indexes of slots and cols */ + min-height: 100%; /* fill height always, even when slat table doesn't grow */ + } +.fc .fc-timegrid-axis-chunk { /* for advanced ScrollGrid */ + position: relative /* offset parent for now-indicator-container */ + + } +.fc .fc-timegrid-axis-chunk > table { + position: relative; + z-index: 1; /* above the now-indicator-container */ + } +.fc .fc-timegrid-slots { + position: relative; + z-index: 1; + } +.fc .fc-timegrid-slot { /* a */ + height: 1.5em; + border-bottom: 0 /* each cell owns its top border */ + } +.fc .fc-timegrid-slot:empty:before { + content: '\00a0'; /* make sure there's at least an empty space to create height for height syncing */ + } +.fc .fc-timegrid-slot-minor { + border-top-style: dotted; + } +.fc .fc-timegrid-slot-label-cushion { + display: inline-block; + white-space: nowrap; + } +.fc .fc-timegrid-slot-label { + vertical-align: middle; /* vertical align the slots */ + } +.fc { + + + /* slots AND axis cells (top-left corner of view including the "all-day" text) */ + +} +.fc .fc-timegrid-axis-cushion, + .fc .fc-timegrid-slot-label-cushion { + padding: 0 4px; + } +.fc { + + + /* axis cells (top-left corner of view including the "all-day" text) */ + /* vertical align is more complicated, uses flexbox */ + +} +.fc .fc-timegrid-axis-frame-liquid { + height: 100%; /* will need liquid-hack in FF */ + } +.fc .fc-timegrid-axis-frame { + overflow: hidden; + display: flex; + align-items: center; /* vertical align */ + justify-content: flex-end; /* horizontal align. matches text-align below */ + } +.fc .fc-timegrid-axis-cushion { + max-width: 60px; /* limits the width of the "all-day" text */ + flex-shrink: 0; /* allows text to expand how it normally would, regardless of constrained width */ + } +.fc-direction-ltr .fc-timegrid-slot-label-frame { + text-align: right; + } +.fc-direction-rtl .fc-timegrid-slot-label-frame { + text-align: left; + } +.fc-liquid-hack .fc-timegrid-axis-frame-liquid { + height: auto; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +.fc .fc-timegrid-col.fc-day-today { + background-color: rgba(255, 220, 40, 0.15); + background-color: var(--fc-today-bg-color, rgba(255, 220, 40, 0.15)); + } +.fc .fc-timegrid-col-frame { + min-height: 100%; /* liquid-hack is below */ + position: relative; + } +.fc-media-screen.fc-liquid-hack .fc-timegrid-col-frame { + height: auto; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +.fc-media-screen .fc-timegrid-cols { + position: absolute; /* no z-index. children will decide and go above slots */ + top: 0; + left: 0; + right: 0; + bottom: 0 + } +.fc-media-screen .fc-timegrid-cols > table { + height: 100%; + } +.fc-media-screen .fc-timegrid-col-bg, + .fc-media-screen .fc-timegrid-col-events, + .fc-media-screen .fc-timegrid-now-indicator-container { + position: absolute; + top: 0; + left: 0; + right: 0; + } +.fc { + + /* bg */ + +} +.fc .fc-timegrid-col-bg { + z-index: 2; /* TODO: kill */ + } +.fc .fc-timegrid-col-bg .fc-non-business { z-index: 1 } +.fc .fc-timegrid-col-bg .fc-bg-event { z-index: 2 } +.fc .fc-timegrid-col-bg .fc-highlight { z-index: 3 } +.fc .fc-timegrid-bg-harness { + position: absolute; /* top/bottom will be set by JS */ + left: 0; + right: 0; + } +.fc { + + /* fg events */ + /* (the mirror segs are put into a separate container with same classname, */ + /* and they must be after the normal seg container to appear at a higher z-index) */ + +} +.fc .fc-timegrid-col-events { + z-index: 3; + /* child event segs have z-indexes that are scoped within this div */ + } +.fc { + + /* now indicator */ + +} +.fc .fc-timegrid-now-indicator-container { + bottom: 0; + overflow: hidden; /* don't let overflow of lines/arrows cause unnecessary scrolling */ + /* z-index is set on the individual elements */ + } +.fc-direction-ltr .fc-timegrid-col-events { + margin: 0 2.5% 0 2px; + } +.fc-direction-rtl .fc-timegrid-col-events { + margin: 0 2px 0 2.5%; + } +.fc-timegrid-event-harness { + position: absolute /* top/left/right/bottom will all be set by JS */ +} +.fc-timegrid-event-harness > .fc-timegrid-event { + position: absolute; /* absolute WITHIN the harness */ + top: 0; /* for when not yet positioned */ + bottom: 0; /* " */ + left: 0; + right: 0; + } +.fc-timegrid-event-harness-inset .fc-timegrid-event, +.fc-timegrid-event.fc-event-mirror, +.fc-timegrid-more-link { + box-shadow: 0px 0px 0px 1px #fff; + box-shadow: 0px 0px 0px 1px var(--fc-page-bg-color, #fff); +} +.fc-timegrid-event, +.fc-timegrid-more-link { /* events need to be root */ + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); + border-radius: 3px; +} +.fc-timegrid-event { /* events need to be root */ + margin-bottom: 1px /* give some space from bottom */ +} +.fc-timegrid-event .fc-event-main { + padding: 1px 1px 0; + } +.fc-timegrid-event .fc-event-time { + white-space: nowrap; + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); + margin-bottom: 1px; + } +.fc-timegrid-event-short .fc-event-main-frame { + flex-direction: row; + overflow: hidden; + } +.fc-timegrid-event-short .fc-event-time:after { + content: '\00a0-\00a0'; /* dash surrounded by non-breaking spaces */ + } +.fc-timegrid-event-short .fc-event-title { + font-size: .85em; + font-size: var(--fc-small-font-size, .85em) + } +.fc-timegrid-more-link { /* does NOT inherit from fc-timegrid-event */ + position: absolute; + z-index: 9999; /* hack */ + color: inherit; + color: var(--fc-more-link-text-color, inherit); + background: #d0d0d0; + background: var(--fc-more-link-bg-color, #d0d0d0); + cursor: pointer; + margin-bottom: 1px; /* match space below fc-timegrid-event */ +} +.fc-timegrid-more-link-inner { /* has fc-sticky */ + padding: 3px 2px; + top: 0; +} +.fc-direction-ltr .fc-timegrid-more-link { + right: 0; + } +.fc-direction-rtl .fc-timegrid-more-link { + left: 0; + } +.fc { + + /* line */ + +} +.fc .fc-timegrid-now-indicator-line { + position: absolute; + z-index: 4; + left: 0; + right: 0; + border-style: solid; + border-color: red; + border-color: var(--fc-now-indicator-color, red); + border-width: 1px 0 0; + } +.fc { + + /* arrow */ + +} +.fc .fc-timegrid-now-indicator-arrow { + position: absolute; + z-index: 4; + margin-top: -5px; /* vertically center on top coordinate */ + border-style: solid; + border-color: red; + border-color: var(--fc-now-indicator-color, red); + } +.fc-direction-ltr .fc-timegrid-now-indicator-arrow { + left: 0; + + /* triangle pointing right. TODO: mixin */ + border-width: 5px 0 5px 6px; + border-top-color: transparent; + border-bottom-color: transparent; + } +.fc-direction-rtl .fc-timegrid-now-indicator-arrow { + right: 0; + + /* triangle pointing left. TODO: mixin */ + border-width: 5px 6px 5px 0; + border-top-color: transparent; + border-bottom-color: transparent; + } + + +:root { + --fc-list-event-dot-width: 10px; + --fc-list-event-hover-bg-color: #f5f5f5; +} +.fc-theme-standard .fc-list { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); + } +.fc { + + /* message when no events */ + +} +.fc .fc-list-empty { + background-color: rgba(208, 208, 208, 0.3); + background-color: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + height: 100%; + display: flex; + justify-content: center; + align-items: center; /* vertically aligns fc-list-empty-inner */ + } +.fc .fc-list-empty-cushion { + margin: 5em 0; + } +.fc { + + /* table within the scroller */ + /* ---------------------------------------------------------------------------------------------------- */ + +} +.fc .fc-list-table { + width: 100%; + border-style: hidden; /* kill outer border on theme */ + } +.fc .fc-list-table tr > * { + border-left: 0; + border-right: 0; + } +.fc .fc-list-sticky .fc-list-day > * { /* the cells */ + position: sticky; + top: 0; + background: #fff; + background: var(--fc-page-bg-color, #fff); /* for when headers are styled to be transparent and sticky */ + } +.fc .fc-list-table th { + padding: 0; /* uses an inner-wrapper instead... */ + } +.fc .fc-list-table td, + .fc .fc-list-day-cushion { + padding: 8px 14px; + } +.fc { + + + /* date heading rows */ + /* ---------------------------------------------------------------------------------------------------- */ + +} +.fc .fc-list-day-cushion:after { + content: ""; + clear: both; + display: table; /* clear floating */ + } +.fc-theme-standard .fc-list-day-cushion { + background-color: rgba(208, 208, 208, 0.3); + background-color: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + } +.fc-direction-ltr .fc-list-day-text, +.fc-direction-rtl .fc-list-day-side-text { + float: left; +} +.fc-direction-ltr .fc-list-day-side-text, +.fc-direction-rtl .fc-list-day-text { + float: right; +} +/* make the dot closer to the event title */ +.fc-direction-ltr .fc-list-table .fc-list-event-graphic { padding-right: 0 } +.fc-direction-rtl .fc-list-table .fc-list-event-graphic { padding-left: 0 } +.fc .fc-list-event.fc-event-forced-url { + cursor: pointer; /* whole row will seem clickable */ + } +.fc .fc-list-event:hover td { + background-color: #f5f5f5; + background-color: var(--fc-list-event-hover-bg-color, #f5f5f5); + } +.fc { + + /* shrink certain cols */ + +} +.fc .fc-list-event-graphic, + .fc .fc-list-event-time { + white-space: nowrap; + width: 1px; + } +.fc .fc-list-event-dot { + display: inline-block; + box-sizing: content-box; + width: 0; + height: 0; + border: 5px solid #3788d8; + border: calc(var(--fc-list-event-dot-width, 10px) / 2) solid var(--fc-event-border-color, #3788d8); + border-radius: 5px; + border-radius: calc(var(--fc-list-event-dot-width, 10px) / 2); + } +.fc { + + /* reset styling */ + +} +.fc .fc-list-event-title a { + color: inherit; + text-decoration: none; + } +.fc { + + /* underline link when hovering over any part of row */ + +} +.fc .fc-list-event.fc-event-forced-url:hover a { + text-decoration: underline; + } + + + + .fc-theme-bootstrap a:not([href]) { + color: inherit; /* natural color for navlinks */ + } + diff --git a/apps/schoolCalender/fullcalendar/main.js b/apps/schoolCalender/fullcalendar/main.js new file mode 100644 index 000000000..54bf45d3f --- /dev/null +++ b/apps/schoolCalender/fullcalendar/main.js @@ -0,0 +1,14738 @@ +/*! +FullCalendar v5.9.0 +Docs & License: https://fullcalendar.io/ +(c) 2021 Adam Shaw +*/ +var FullCalendar = (function (exports) { + 'use strict'; + + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + /* global Reflect, Promise */ + + var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + + function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + + function __spreadArray(to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || from); + } + + var n,u,i$1,t,o,r$1={},f$1=[],e$1=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function c$1(n,l){for(var u in l)n[u]=l[u];return n}function s(n){var l=n.parentNode;l&&l.removeChild(n);}function a$1(n,l,u){var i,t,o,r=arguments,f={};for(o in l)"key"==o?i=l[o]:"ref"==o?t=l[o]:f[o]=l[o];if(arguments.length>3)for(u=[u],o=3;o0?v$1(k.type,k.props,k.key,null,k.__v):k)){if(k.__=u,k.__b=u.__b+1,null===(_=A[h])||_&&k.key==_.key&&k.type===_.type)A[h]=void 0;else for(p=0;p3;)e.pop()();if(e[1]>>1,1),t.i.removeChild(n);}}),N(a$1(T,{context:t.context},n.__v),t.l)):t.l&&t.componentWillUnmount();}function I(n,t){return a$1(j,{__v:n,i:t})}(F.prototype=new p).__e=function(n){var t=this,e=U(t.__v),r=t.o.get(n);return r[0]++,function(u){var o=function(){t.props.revealOrder?(r.push(u),M(t,n,r)):u();};e?e(o):o();}},F.prototype.render=function(n){this.u=null,this.o=new Map;var t=w$1(n.children);n.revealOrder&&"b"===n.revealOrder[0]&&t.reverse();for(var e=t.length;e--;)this.o.set(t[e],this.u=[1,0,this.u]);return n.children},F.prototype.componentDidUpdate=F.prototype.componentDidMount=function(){var n=this;this.o.forEach(function(t,e){M(n,e,t);});};var W="undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103,P=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|fill|flood|font|glyph(?!R)|horiz|marker(?!H|W|U)|overline|paint|stop|strikethrough|stroke|text(?!L)|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,V=function(n){return ("undefined"!=typeof Symbol&&"symbol"==typeof Symbol()?/fil|che|rad/i:/fil|che|ra/i).test(n)};p.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(n){Object.defineProperty(p.prototype,n,{configurable:!0,get:function(){return this["UNSAFE_"+n]},set:function(t){Object.defineProperty(this,n,{configurable:!0,writable:!0,value:t});}});});var H=n.event;function Z(){}function Y(){return this.cancelBubble}function $(){return this.defaultPrevented}n.event=function(n){return H&&(n=H(n)),n.persist=Z,n.isPropagationStopped=Y,n.isDefaultPrevented=$,n.nativeEvent=n};var G={configurable:!0,get:function(){return this.class}},J=n.vnode;n.vnode=function(n){var t=n.type,e=n.props,r=e;if("string"==typeof t){for(var u in r={},e){var o=e[u];"value"===u&&"defaultValue"in e&&null==o||("defaultValue"===u&&"value"in e&&null==e.value?u="value":"download"===u&&!0===o?o="":/ondoubleclick/i.test(u)?u="ondblclick":/^onchange(textarea|input)/i.test(u+t)&&!V(e.type)?u="oninput":/^on(Ani|Tra|Tou|BeforeInp)/.test(u)?u=u.toLowerCase():P.test(u)?u=u.replace(/[A-Z0-9]/,"-$&").toLowerCase():null===o&&(o=void 0),r[u]=o);}"select"==t&&r.multiple&&Array.isArray(r.value)&&(r.value=w$1(e.children).forEach(function(n){n.props.selected=-1!=r.value.indexOf(n.props.value);})),"select"==t&&null!=r.defaultValue&&(r.value=w$1(e.children).forEach(function(n){n.props.selected=r.multiple?-1!=r.defaultValue.indexOf(n.props.value):r.defaultValue==n.props.value;})),n.props=r;}t&&e.class!=e.className&&(G.enumerable="className"in e,null!=e.className&&(r.class=e.className),Object.defineProperty(r,"className",G)),n.$$typeof=W,J&&J(n);};var K=n.__r;n.__r=function(n){K&&K(n);};"object"==typeof performance&&"function"==typeof performance.now?performance.now.bind(performance):function(){return Date.now()}; + + var globalObj = typeof globalThis !== 'undefined' ? globalThis : window; // // TODO: streamline when killing IE11 support + if (globalObj.FullCalendarVDom) { + console.warn('FullCalendar VDOM already loaded'); + } + else { + globalObj.FullCalendarVDom = { + Component: p, + createElement: a$1, + render: N, + createRef: h, + Fragment: y, + createContext: createContext$1, + createPortal: I, + flushToDom: flushToDom$1, + unmountComponentAtNode: unmountComponentAtNode$1, + }; + } + // HACKS... + // TODO: lock version + // TODO: link gh issues + function flushToDom$1() { + var oldDebounceRendering = n.debounceRendering; // orig + var callbackQ = []; + function execCallbackSync(callback) { + callbackQ.push(callback); + } + n.debounceRendering = execCallbackSync; + N(a$1(FakeComponent, {}), document.createElement('div')); + while (callbackQ.length) { + callbackQ.shift()(); + } + n.debounceRendering = oldDebounceRendering; + } + var FakeComponent = /** @class */ (function (_super) { + __extends(FakeComponent, _super); + function FakeComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + FakeComponent.prototype.render = function () { return a$1('div', {}); }; + FakeComponent.prototype.componentDidMount = function () { this.setState({}); }; + return FakeComponent; + }(p)); + function createContext$1(defaultValue) { + var ContextType = q(defaultValue); + var origProvider = ContextType.Provider; + ContextType.Provider = function () { + var _this = this; + var isNew = !this.getChildContext; + var children = origProvider.apply(this, arguments); // eslint-disable-line prefer-rest-params + if (isNew) { + var subs_1 = []; + this.shouldComponentUpdate = function (_props) { + if (_this.props.value !== _props.value) { + subs_1.forEach(function (c) { + c.context = _props.value; + c.forceUpdate(); + }); + } + }; + this.sub = function (c) { + subs_1.push(c); + var old = c.componentWillUnmount; + c.componentWillUnmount = function () { + subs_1.splice(subs_1.indexOf(c), 1); + old && old.call(c); + }; + }; + } + return children; + }; + return ContextType; + } + function unmountComponentAtNode$1(node) { + N(null, node); + } + + // no public types yet. when there are, export from: + // import {} from './api-type-deps' + var EventSourceApi = /** @class */ (function () { + function EventSourceApi(context, internalEventSource) { + this.context = context; + this.internalEventSource = internalEventSource; + } + EventSourceApi.prototype.remove = function () { + this.context.dispatch({ + type: 'REMOVE_EVENT_SOURCE', + sourceId: this.internalEventSource.sourceId, + }); + }; + EventSourceApi.prototype.refetch = function () { + this.context.dispatch({ + type: 'FETCH_EVENT_SOURCES', + sourceIds: [this.internalEventSource.sourceId], + isRefetch: true, + }); + }; + Object.defineProperty(EventSourceApi.prototype, "id", { + get: function () { + return this.internalEventSource.publicId; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventSourceApi.prototype, "url", { + get: function () { + return this.internalEventSource.meta.url; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventSourceApi.prototype, "format", { + get: function () { + return this.internalEventSource.meta.format; // TODO: bad. not guaranteed + }, + enumerable: false, + configurable: true + }); + return EventSourceApi; + }()); + + function removeElement(el) { + if (el.parentNode) { + el.parentNode.removeChild(el); + } + } + // Querying + // ---------------------------------------------------------------------------------------------------------------- + function elementClosest(el, selector) { + if (el.closest) { + return el.closest(selector); + // really bad fallback for IE + // from https://developer.mozilla.org/en-US/docs/Web/API/Element/closest + } + if (!document.documentElement.contains(el)) { + return null; + } + do { + if (elementMatches(el, selector)) { + return el; + } + el = (el.parentElement || el.parentNode); + } while (el !== null && el.nodeType === 1); + return null; + } + function elementMatches(el, selector) { + var method = el.matches || el.matchesSelector || el.msMatchesSelector; + return method.call(el, selector); + } + // accepts multiple subject els + // returns a real array. good for methods like forEach + // TODO: accept the document + function findElements(container, selector) { + var containers = container instanceof HTMLElement ? [container] : container; + var allMatches = []; + for (var i = 0; i < containers.length; i += 1) { + var matches = containers[i].querySelectorAll(selector); + for (var j = 0; j < matches.length; j += 1) { + allMatches.push(matches[j]); + } + } + return allMatches; + } + // accepts multiple subject els + // only queries direct child elements // TODO: rename to findDirectChildren! + function findDirectChildren(parent, selector) { + var parents = parent instanceof HTMLElement ? [parent] : parent; + var allMatches = []; + for (var i = 0; i < parents.length; i += 1) { + var childNodes = parents[i].children; // only ever elements + for (var j = 0; j < childNodes.length; j += 1) { + var childNode = childNodes[j]; + if (!selector || elementMatches(childNode, selector)) { + allMatches.push(childNode); + } + } + } + return allMatches; + } + // Style + // ---------------------------------------------------------------------------------------------------------------- + var PIXEL_PROP_RE = /(top|left|right|bottom|width|height)$/i; + function applyStyle(el, props) { + for (var propName in props) { + applyStyleProp(el, propName, props[propName]); + } + } + function applyStyleProp(el, name, val) { + if (val == null) { + el.style[name] = ''; + } + else if (typeof val === 'number' && PIXEL_PROP_RE.test(name)) { + el.style[name] = val + "px"; + } + else { + el.style[name] = val; + } + } + // Event Handling + // ---------------------------------------------------------------------------------------------------------------- + // if intercepting bubbled events at the document/window/body level, + // and want to see originating element (the 'target'), use this util instead + // of `ev.target` because it goes within web-component boundaries. + function getEventTargetViaRoot(ev) { + var _a, _b; + return (_b = (_a = ev.composedPath) === null || _a === void 0 ? void 0 : _a.call(ev)[0]) !== null && _b !== void 0 ? _b : ev.target; + } + // Shadow DOM consuderations + // ---------------------------------------------------------------------------------------------------------------- + function getElRoot(el) { + return el.getRootNode ? el.getRootNode() : document; + } + + // Stops a mouse/touch event from doing it's native browser action + function preventDefault(ev) { + ev.preventDefault(); + } + // Event Delegation + // ---------------------------------------------------------------------------------------------------------------- + function buildDelegationHandler(selector, handler) { + return function (ev) { + var matchedChild = elementClosest(ev.target, selector); + if (matchedChild) { + handler.call(matchedChild, ev, matchedChild); + } + }; + } + function listenBySelector(container, eventType, selector, handler) { + var attachedHandler = buildDelegationHandler(selector, handler); + container.addEventListener(eventType, attachedHandler); + return function () { + container.removeEventListener(eventType, attachedHandler); + }; + } + function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) { + var currentMatchedChild; + return listenBySelector(container, 'mouseover', selector, function (mouseOverEv, matchedChild) { + if (matchedChild !== currentMatchedChild) { + currentMatchedChild = matchedChild; + onMouseEnter(mouseOverEv, matchedChild); + var realOnMouseLeave_1 = function (mouseLeaveEv) { + currentMatchedChild = null; + onMouseLeave(mouseLeaveEv, matchedChild); + matchedChild.removeEventListener('mouseleave', realOnMouseLeave_1); + }; + // listen to the next mouseleave, and then unattach + matchedChild.addEventListener('mouseleave', realOnMouseLeave_1); + } + }); + } + // Animation + // ---------------------------------------------------------------------------------------------------------------- + var transitionEventNames = [ + 'webkitTransitionEnd', + 'otransitionend', + 'oTransitionEnd', + 'msTransitionEnd', + 'transitionend', + ]; + // triggered only when the next single subsequent transition finishes + function whenTransitionDone(el, callback) { + var realCallback = function (ev) { + callback(ev); + transitionEventNames.forEach(function (eventName) { + el.removeEventListener(eventName, realCallback); + }); + }; + transitionEventNames.forEach(function (eventName) { + el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes + }); + } + + var guidNumber = 0; + function guid() { + guidNumber += 1; + return String(guidNumber); + } + /* FullCalendar-specific DOM Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + // Make the mouse cursor express that an event is not allowed in the current area + function disableCursor() { + document.body.classList.add('fc-not-allowed'); + } + // Returns the mouse cursor to its original look + function enableCursor() { + document.body.classList.remove('fc-not-allowed'); + } + /* Selection + ----------------------------------------------------------------------------------------------------------------------*/ + function preventSelection(el) { + el.classList.add('fc-unselectable'); + el.addEventListener('selectstart', preventDefault); + } + function allowSelection(el) { + el.classList.remove('fc-unselectable'); + el.removeEventListener('selectstart', preventDefault); + } + /* Context Menu + ----------------------------------------------------------------------------------------------------------------------*/ + function preventContextMenu(el) { + el.addEventListener('contextmenu', preventDefault); + } + function allowContextMenu(el) { + el.removeEventListener('contextmenu', preventDefault); + } + function parseFieldSpecs(input) { + var specs = []; + var tokens = []; + var i; + var token; + if (typeof input === 'string') { + tokens = input.split(/\s*,\s*/); + } + else if (typeof input === 'function') { + tokens = [input]; + } + else if (Array.isArray(input)) { + tokens = input; + } + for (i = 0; i < tokens.length; i += 1) { + token = tokens[i]; + if (typeof token === 'string') { + specs.push(token.charAt(0) === '-' ? + { field: token.substring(1), order: -1 } : + { field: token, order: 1 }); + } + else if (typeof token === 'function') { + specs.push({ func: token }); + } + } + return specs; + } + function compareByFieldSpecs(obj0, obj1, fieldSpecs) { + var i; + var cmp; + for (i = 0; i < fieldSpecs.length; i += 1) { + cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]); + if (cmp) { + return cmp; + } + } + return 0; + } + function compareByFieldSpec(obj0, obj1, fieldSpec) { + if (fieldSpec.func) { + return fieldSpec.func(obj0, obj1); + } + return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field]) + * (fieldSpec.order || 1); + } + function flexibleCompare(a, b) { + if (!a && !b) { + return 0; + } + if (b == null) { + return -1; + } + if (a == null) { + return 1; + } + if (typeof a === 'string' || typeof b === 'string') { + return String(a).localeCompare(String(b)); + } + return a - b; + } + /* String Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + function padStart(val, len) { + var s = String(val); + return '000'.substr(0, len - s.length) + s; + } + /* Number Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + function compareNumbers(a, b) { + return a - b; + } + function isInt(n) { + return n % 1 === 0; + } + /* FC-specific DOM dimension stuff + ----------------------------------------------------------------------------------------------------------------------*/ + function computeSmallestCellWidth(cellEl) { + var allWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-frame'); + var contentWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-cushion'); + if (!allWidthEl) { + throw new Error('needs fc-scrollgrid-shrink-frame className'); // TODO: use const + } + if (!contentWidthEl) { + throw new Error('needs fc-scrollgrid-shrink-cushion className'); + } + return cellEl.getBoundingClientRect().width - allWidthEl.getBoundingClientRect().width + // the cell padding+border + contentWidthEl.getBoundingClientRect().width; + } + + var DAY_IDS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; + // Adding + function addWeeks(m, n) { + var a = dateToUtcArray(m); + a[2] += n * 7; + return arrayToUtcDate(a); + } + function addDays(m, n) { + var a = dateToUtcArray(m); + a[2] += n; + return arrayToUtcDate(a); + } + function addMs(m, n) { + var a = dateToUtcArray(m); + a[6] += n; + return arrayToUtcDate(a); + } + // Diffing (all return floats) + // TODO: why not use ranges? + function diffWeeks(m0, m1) { + return diffDays(m0, m1) / 7; + } + function diffDays(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60 * 24); + } + function diffHours(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60); + } + function diffMinutes(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60); + } + function diffSeconds(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / 1000; + } + function diffDayAndTime(m0, m1) { + var m0day = startOfDay(m0); + var m1day = startOfDay(m1); + return { + years: 0, + months: 0, + days: Math.round(diffDays(m0day, m1day)), + milliseconds: (m1.valueOf() - m1day.valueOf()) - (m0.valueOf() - m0day.valueOf()), + }; + } + // Diffing Whole Units + function diffWholeWeeks(m0, m1) { + var d = diffWholeDays(m0, m1); + if (d !== null && d % 7 === 0) { + return d / 7; + } + return null; + } + function diffWholeDays(m0, m1) { + if (timeAsMs(m0) === timeAsMs(m1)) { + return Math.round(diffDays(m0, m1)); + } + return null; + } + // Start-Of + function startOfDay(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + ]); + } + function startOfHour(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + ]); + } + function startOfMinute(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + m.getUTCMinutes(), + ]); + } + function startOfSecond(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + m.getUTCMinutes(), + m.getUTCSeconds(), + ]); + } + // Week Computation + function weekOfYear(marker, dow, doy) { + var y = marker.getUTCFullYear(); + var w = weekOfGivenYear(marker, y, dow, doy); + if (w < 1) { + return weekOfGivenYear(marker, y - 1, dow, doy); + } + var nextW = weekOfGivenYear(marker, y + 1, dow, doy); + if (nextW >= 1) { + return Math.min(w, nextW); + } + return w; + } + function weekOfGivenYear(marker, year, dow, doy) { + var firstWeekStart = arrayToUtcDate([year, 0, 1 + firstWeekOffset(year, dow, doy)]); + var dayStart = startOfDay(marker); + var days = Math.round(diffDays(firstWeekStart, dayStart)); + return Math.floor(days / 7) + 1; // zero-indexed + } + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + // first-week day -- which january is always in the first week (4 for iso, 1 for other) + var fwd = 7 + dow - doy; + // first-week day local weekday -- which local weekday is fwd + var fwdlw = (7 + arrayToUtcDate([year, 0, fwd]).getUTCDay() - dow) % 7; + return -fwdlw + fwd - 1; + } + // Array Conversion + function dateToLocalArray(date) { + return [ + date.getFullYear(), + date.getMonth(), + date.getDate(), + date.getHours(), + date.getMinutes(), + date.getSeconds(), + date.getMilliseconds(), + ]; + } + function arrayToLocalDate(a) { + return new Date(a[0], a[1] || 0, a[2] == null ? 1 : a[2], // day of month + a[3] || 0, a[4] || 0, a[5] || 0); + } + function dateToUtcArray(date) { + return [ + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds(), + ]; + } + function arrayToUtcDate(a) { + // according to web standards (and Safari), a month index is required. + // massage if only given a year. + if (a.length === 1) { + a = a.concat([0]); + } + return new Date(Date.UTC.apply(Date, a)); + } + // Other Utils + function isValidDate(m) { + return !isNaN(m.valueOf()); + } + function timeAsMs(m) { + return m.getUTCHours() * 1000 * 60 * 60 + + m.getUTCMinutes() * 1000 * 60 + + m.getUTCSeconds() * 1000 + + m.getUTCMilliseconds(); + } + + function createEventInstance(defId, range, forcedStartTzo, forcedEndTzo) { + return { + instanceId: guid(), + defId: defId, + range: range, + forcedStartTzo: forcedStartTzo == null ? null : forcedStartTzo, + forcedEndTzo: forcedEndTzo == null ? null : forcedEndTzo, + }; + } + + var hasOwnProperty = Object.prototype.hasOwnProperty; + // Merges an array of objects into a single object. + // The second argument allows for an array of property names who's object values will be merged together. + function mergeProps(propObjs, complexPropsMap) { + var dest = {}; + if (complexPropsMap) { + for (var name_1 in complexPropsMap) { + var complexObjs = []; + // collect the trailing object values, stopping when a non-object is discovered + for (var i = propObjs.length - 1; i >= 0; i -= 1) { + var val = propObjs[i][name_1]; + if (typeof val === 'object' && val) { // non-null object + complexObjs.unshift(val); + } + else if (val !== undefined) { + dest[name_1] = val; // if there were no objects, this value will be used + break; + } + } + // if the trailing values were objects, use the merged value + if (complexObjs.length) { + dest[name_1] = mergeProps(complexObjs); + } + } + } + // copy values into the destination, going from last to first + for (var i = propObjs.length - 1; i >= 0; i -= 1) { + var props = propObjs[i]; + for (var name_2 in props) { + if (!(name_2 in dest)) { // if already assigned by previous props or complex props, don't reassign + dest[name_2] = props[name_2]; + } + } + } + return dest; + } + function filterHash(hash, func) { + var filtered = {}; + for (var key in hash) { + if (func(hash[key], key)) { + filtered[key] = hash[key]; + } + } + return filtered; + } + function mapHash(hash, func) { + var newHash = {}; + for (var key in hash) { + newHash[key] = func(hash[key], key); + } + return newHash; + } + function arrayToHash(a) { + var hash = {}; + for (var _i = 0, a_1 = a; _i < a_1.length; _i++) { + var item = a_1[_i]; + hash[item] = true; + } + return hash; + } + function buildHashFromArray(a, func) { + var hash = {}; + for (var i = 0; i < a.length; i += 1) { + var tuple = func(a[i], i); + hash[tuple[0]] = tuple[1]; + } + return hash; + } + function hashValuesToArray(obj) { + var a = []; + for (var key in obj) { + a.push(obj[key]); + } + return a; + } + function isPropsEqual(obj0, obj1) { + if (obj0 === obj1) { + return true; + } + for (var key in obj0) { + if (hasOwnProperty.call(obj0, key)) { + if (!(key in obj1)) { + return false; + } + } + } + for (var key in obj1) { + if (hasOwnProperty.call(obj1, key)) { + if (obj0[key] !== obj1[key]) { + return false; + } + } + } + return true; + } + function getUnequalProps(obj0, obj1) { + var keys = []; + for (var key in obj0) { + if (hasOwnProperty.call(obj0, key)) { + if (!(key in obj1)) { + keys.push(key); + } + } + } + for (var key in obj1) { + if (hasOwnProperty.call(obj1, key)) { + if (obj0[key] !== obj1[key]) { + keys.push(key); + } + } + } + return keys; + } + function compareObjs(oldProps, newProps, equalityFuncs) { + if (equalityFuncs === void 0) { equalityFuncs = {}; } + if (oldProps === newProps) { + return true; + } + for (var key in newProps) { + if (key in oldProps && isObjValsEqual(oldProps[key], newProps[key], equalityFuncs[key])) ; + else { + return false; + } + } + // check for props that were omitted in the new + for (var key in oldProps) { + if (!(key in newProps)) { + return false; + } + } + return true; + } + /* + assumed "true" equality for handler names like "onReceiveSomething" + */ + function isObjValsEqual(val0, val1, comparator) { + if (val0 === val1 || comparator === true) { + return true; + } + if (comparator) { + return comparator(val0, val1); + } + return false; + } + function collectFromHash(hash, startIndex, endIndex, step) { + if (startIndex === void 0) { startIndex = 0; } + if (step === void 0) { step = 1; } + var res = []; + if (endIndex == null) { + endIndex = Object.keys(hash).length; + } + for (var i = startIndex; i < endIndex; i += step) { + var val = hash[i]; + if (val !== undefined) { // will disregard undefined for sparse arrays + res.push(val); + } + } + return res; + } + + function parseRecurring(refined, defaultAllDay, dateEnv, recurringTypes) { + for (var i = 0; i < recurringTypes.length; i += 1) { + var parsed = recurringTypes[i].parse(refined, dateEnv); + if (parsed) { + var allDay = refined.allDay; + if (allDay == null) { + allDay = defaultAllDay; + if (allDay == null) { + allDay = parsed.allDayGuess; + if (allDay == null) { + allDay = false; + } + } + } + return { + allDay: allDay, + duration: parsed.duration, + typeData: parsed.typeData, + typeId: i, + }; + } + } + return null; + } + function expandRecurring(eventStore, framingRange, context) { + var dateEnv = context.dateEnv, pluginHooks = context.pluginHooks, options = context.options; + var defs = eventStore.defs, instances = eventStore.instances; + // remove existing recurring instances + // TODO: bad. always expand events as a second step + instances = filterHash(instances, function (instance) { return !defs[instance.defId].recurringDef; }); + for (var defId in defs) { + var def = defs[defId]; + if (def.recurringDef) { + var duration = def.recurringDef.duration; + if (!duration) { + duration = def.allDay ? + options.defaultAllDayEventDuration : + options.defaultTimedEventDuration; + } + var starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes); + for (var _i = 0, starts_1 = starts; _i < starts_1.length; _i++) { + var start = starts_1[_i]; + var instance = createEventInstance(defId, { + start: start, + end: dateEnv.add(start, duration), + }); + instances[instance.instanceId] = instance; + } + } + } + return { defs: defs, instances: instances }; + } + /* + Event MUST have a recurringDef + */ + function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) { + var typeDef = recurringTypes[eventDef.recurringDef.typeId]; + var markers = typeDef.expand(eventDef.recurringDef.typeData, { + start: dateEnv.subtract(framingRange.start, duration), + end: framingRange.end, + }, dateEnv); + // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to + if (eventDef.allDay) { + markers = markers.map(startOfDay); + } + return markers; + } + + var INTERNAL_UNITS = ['years', 'months', 'days', 'milliseconds']; + var PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/; + // Parsing and Creation + function createDuration(input, unit) { + var _a; + if (typeof input === 'string') { + return parseString(input); + } + if (typeof input === 'object' && input) { // non-null object + return parseObject(input); + } + if (typeof input === 'number') { + return parseObject((_a = {}, _a[unit || 'milliseconds'] = input, _a)); + } + return null; + } + function parseString(s) { + var m = PARSE_RE.exec(s); + if (m) { + var sign = m[1] ? -1 : 1; + return { + years: 0, + months: 0, + days: sign * (m[2] ? parseInt(m[2], 10) : 0), + milliseconds: sign * ((m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours + (m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes + (m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds + (m[6] ? parseInt(m[6], 10) : 0) // ms + ), + }; + } + return null; + } + function parseObject(obj) { + var duration = { + years: obj.years || obj.year || 0, + months: obj.months || obj.month || 0, + days: obj.days || obj.day || 0, + milliseconds: (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours + (obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes + (obj.seconds || obj.second || 0) * 1000 + // seconds + (obj.milliseconds || obj.millisecond || obj.ms || 0), // ms + }; + var weeks = obj.weeks || obj.week; + if (weeks) { + duration.days += weeks * 7; + duration.specifiedWeeks = true; + } + return duration; + } + // Equality + function durationsEqual(d0, d1) { + return d0.years === d1.years && + d0.months === d1.months && + d0.days === d1.days && + d0.milliseconds === d1.milliseconds; + } + function asCleanDays(dur) { + if (!dur.years && !dur.months && !dur.milliseconds) { + return dur.days; + } + return 0; + } + // Simple Math + function addDurations(d0, d1) { + return { + years: d0.years + d1.years, + months: d0.months + d1.months, + days: d0.days + d1.days, + milliseconds: d0.milliseconds + d1.milliseconds, + }; + } + function subtractDurations(d1, d0) { + return { + years: d1.years - d0.years, + months: d1.months - d0.months, + days: d1.days - d0.days, + milliseconds: d1.milliseconds - d0.milliseconds, + }; + } + function multiplyDuration(d, n) { + return { + years: d.years * n, + months: d.months * n, + days: d.days * n, + milliseconds: d.milliseconds * n, + }; + } + // Conversions + // "Rough" because they are based on average-case Gregorian months/years + function asRoughYears(dur) { + return asRoughDays(dur) / 365; + } + function asRoughMonths(dur) { + return asRoughDays(dur) / 30; + } + function asRoughDays(dur) { + return asRoughMs(dur) / 864e5; + } + function asRoughMinutes(dur) { + return asRoughMs(dur) / (1000 * 60); + } + function asRoughSeconds(dur) { + return asRoughMs(dur) / 1000; + } + function asRoughMs(dur) { + return dur.years * (365 * 864e5) + + dur.months * (30 * 864e5) + + dur.days * 864e5 + + dur.milliseconds; + } + // Advanced Math + function wholeDivideDurations(numerator, denominator) { + var res = null; + for (var i = 0; i < INTERNAL_UNITS.length; i += 1) { + var unit = INTERNAL_UNITS[i]; + if (denominator[unit]) { + var localRes = numerator[unit] / denominator[unit]; + if (!isInt(localRes) || (res !== null && res !== localRes)) { + return null; + } + res = localRes; + } + else if (numerator[unit]) { + // needs to divide by something but can't! + return null; + } + } + return res; + } + function greatestDurationDenominator(dur) { + var ms = dur.milliseconds; + if (ms) { + if (ms % 1000 !== 0) { + return { unit: 'millisecond', value: ms }; + } + if (ms % (1000 * 60) !== 0) { + return { unit: 'second', value: ms / 1000 }; + } + if (ms % (1000 * 60 * 60) !== 0) { + return { unit: 'minute', value: ms / (1000 * 60) }; + } + if (ms) { + return { unit: 'hour', value: ms / (1000 * 60 * 60) }; + } + } + if (dur.days) { + if (dur.specifiedWeeks && dur.days % 7 === 0) { + return { unit: 'week', value: dur.days / 7 }; + } + return { unit: 'day', value: dur.days }; + } + if (dur.months) { + return { unit: 'month', value: dur.months }; + } + if (dur.years) { + return { unit: 'year', value: dur.years }; + } + return { unit: 'millisecond', value: 0 }; + } + + // timeZoneOffset is in minutes + function buildIsoString(marker, timeZoneOffset, stripZeroTime) { + if (stripZeroTime === void 0) { stripZeroTime = false; } + var s = marker.toISOString(); + s = s.replace('.000', ''); + if (stripZeroTime) { + s = s.replace('T00:00:00Z', ''); + } + if (s.length > 10) { // time part wasn't stripped, can add timezone info + if (timeZoneOffset == null) { + s = s.replace('Z', ''); + } + else if (timeZoneOffset !== 0) { + s = s.replace('Z', formatTimeZoneOffset(timeZoneOffset, true)); + } + // otherwise, its UTC-0 and we want to keep the Z + } + return s; + } + // formats the date, but with no time part + // TODO: somehow merge with buildIsoString and stripZeroTime + // TODO: rename. omit "string" + function formatDayString(marker) { + return marker.toISOString().replace(/T.*$/, ''); + } + // TODO: use Date::toISOString and use everything after the T? + function formatIsoTimeString(marker) { + return padStart(marker.getUTCHours(), 2) + ':' + + padStart(marker.getUTCMinutes(), 2) + ':' + + padStart(marker.getUTCSeconds(), 2); + } + function formatTimeZoneOffset(minutes, doIso) { + if (doIso === void 0) { doIso = false; } + var sign = minutes < 0 ? '-' : '+'; + var abs = Math.abs(minutes); + var hours = Math.floor(abs / 60); + var mins = Math.round(abs % 60); + if (doIso) { + return sign + padStart(hours, 2) + ":" + padStart(mins, 2); + } + return "GMT" + sign + hours + (mins ? ":" + padStart(mins, 2) : ''); + } + + // TODO: new util arrayify? + function removeExact(array, exactVal) { + var removeCnt = 0; + var i = 0; + while (i < array.length) { + if (array[i] === exactVal) { + array.splice(i, 1); + removeCnt += 1; + } + else { + i += 1; + } + } + return removeCnt; + } + function isArraysEqual(a0, a1, equalityFunc) { + if (a0 === a1) { + return true; + } + var len = a0.length; + var i; + if (len !== a1.length) { // not array? or not same length? + return false; + } + for (i = 0; i < len; i += 1) { + if (!(equalityFunc ? equalityFunc(a0[i], a1[i]) : a0[i] === a1[i])) { + return false; + } + } + return true; + } + + function memoize(workerFunc, resEquality, teardownFunc) { + var currentArgs; + var currentRes; + return function () { + var newArgs = []; + for (var _i = 0; _i < arguments.length; _i++) { + newArgs[_i] = arguments[_i]; + } + if (!currentArgs) { + currentRes = workerFunc.apply(this, newArgs); + } + else if (!isArraysEqual(currentArgs, newArgs)) { + if (teardownFunc) { + teardownFunc(currentRes); + } + var res = workerFunc.apply(this, newArgs); + if (!resEquality || !resEquality(res, currentRes)) { + currentRes = res; + } + } + currentArgs = newArgs; + return currentRes; + }; + } + function memoizeObjArg(workerFunc, resEquality, teardownFunc) { + var _this = this; + var currentArg; + var currentRes; + return function (newArg) { + if (!currentArg) { + currentRes = workerFunc.call(_this, newArg); + } + else if (!isPropsEqual(currentArg, newArg)) { + if (teardownFunc) { + teardownFunc(currentRes); + } + var res = workerFunc.call(_this, newArg); + if (!resEquality || !resEquality(res, currentRes)) { + currentRes = res; + } + } + currentArg = newArg; + return currentRes; + }; + } + function memoizeArraylike(// used at all? + workerFunc, resEquality, teardownFunc) { + var _this = this; + var currentArgSets = []; + var currentResults = []; + return function (newArgSets) { + var currentLen = currentArgSets.length; + var newLen = newArgSets.length; + var i = 0; + for (; i < currentLen; i += 1) { + if (!newArgSets[i]) { // one of the old sets no longer exists + if (teardownFunc) { + teardownFunc(currentResults[i]); + } + } + else if (!isArraysEqual(currentArgSets[i], newArgSets[i])) { + if (teardownFunc) { + teardownFunc(currentResults[i]); + } + var res = workerFunc.apply(_this, newArgSets[i]); + if (!resEquality || !resEquality(res, currentResults[i])) { + currentResults[i] = res; + } + } + } + for (; i < newLen; i += 1) { + currentResults[i] = workerFunc.apply(_this, newArgSets[i]); + } + currentArgSets = newArgSets; + currentResults.splice(newLen); // remove excess + return currentResults; + }; + } + function memoizeHashlike(// used? + workerFunc, resEquality, teardownFunc) { + var _this = this; + var currentArgHash = {}; + var currentResHash = {}; + return function (newArgHash) { + var newResHash = {}; + for (var key in newArgHash) { + if (!currentResHash[key]) { + newResHash[key] = workerFunc.apply(_this, newArgHash[key]); + } + else if (!isArraysEqual(currentArgHash[key], newArgHash[key])) { + if (teardownFunc) { + teardownFunc(currentResHash[key]); + } + var res = workerFunc.apply(_this, newArgHash[key]); + newResHash[key] = (resEquality && resEquality(res, currentResHash[key])) + ? currentResHash[key] + : res; + } + else { + newResHash[key] = currentResHash[key]; + } + } + currentArgHash = newArgHash; + currentResHash = newResHash; + return newResHash; + }; + } + + var EXTENDED_SETTINGS_AND_SEVERITIES = { + week: 3, + separator: 0, + omitZeroMinute: 0, + meridiem: 0, + omitCommas: 0, + }; + var STANDARD_DATE_PROP_SEVERITIES = { + timeZoneName: 7, + era: 6, + year: 5, + month: 4, + day: 2, + weekday: 2, + hour: 1, + minute: 1, + second: 1, + }; + var MERIDIEM_RE = /\s*([ap])\.?m\.?/i; // eats up leading spaces too + var COMMA_RE = /,/g; // we need re for globalness + var MULTI_SPACE_RE = /\s+/g; + var LTR_RE = /\u200e/g; // control character + var UTC_RE = /UTC|GMT/; + var NativeFormatter = /** @class */ (function () { + function NativeFormatter(formatSettings) { + var standardDateProps = {}; + var extendedSettings = {}; + var severity = 0; + for (var name_1 in formatSettings) { + if (name_1 in EXTENDED_SETTINGS_AND_SEVERITIES) { + extendedSettings[name_1] = formatSettings[name_1]; + severity = Math.max(EXTENDED_SETTINGS_AND_SEVERITIES[name_1], severity); + } + else { + standardDateProps[name_1] = formatSettings[name_1]; + if (name_1 in STANDARD_DATE_PROP_SEVERITIES) { // TODO: what about hour12? no severity + severity = Math.max(STANDARD_DATE_PROP_SEVERITIES[name_1], severity); + } + } + } + this.standardDateProps = standardDateProps; + this.extendedSettings = extendedSettings; + this.severity = severity; + this.buildFormattingFunc = memoize(buildFormattingFunc); + } + NativeFormatter.prototype.format = function (date, context) { + return this.buildFormattingFunc(this.standardDateProps, this.extendedSettings, context)(date); + }; + NativeFormatter.prototype.formatRange = function (start, end, context, betterDefaultSeparator) { + var _a = this, standardDateProps = _a.standardDateProps, extendedSettings = _a.extendedSettings; + var diffSeverity = computeMarkerDiffSeverity(start.marker, end.marker, context.calendarSystem); + if (!diffSeverity) { + return this.format(start, context); + } + var biggestUnitForPartial = diffSeverity; + if (biggestUnitForPartial > 1 && // the two dates are different in a way that's larger scale than time + (standardDateProps.year === 'numeric' || standardDateProps.year === '2-digit') && + (standardDateProps.month === 'numeric' || standardDateProps.month === '2-digit') && + (standardDateProps.day === 'numeric' || standardDateProps.day === '2-digit')) { + biggestUnitForPartial = 1; // make it look like the dates are only different in terms of time + } + var full0 = this.format(start, context); + var full1 = this.format(end, context); + if (full0 === full1) { + return full0; + } + var partialDateProps = computePartialFormattingOptions(standardDateProps, biggestUnitForPartial); + var partialFormattingFunc = buildFormattingFunc(partialDateProps, extendedSettings, context); + var partial0 = partialFormattingFunc(start); + var partial1 = partialFormattingFunc(end); + var insertion = findCommonInsertion(full0, partial0, full1, partial1); + var separator = extendedSettings.separator || betterDefaultSeparator || context.defaultSeparator || ''; + if (insertion) { + return insertion.before + partial0 + separator + partial1 + insertion.after; + } + return full0 + separator + full1; + }; + NativeFormatter.prototype.getLargestUnit = function () { + switch (this.severity) { + case 7: + case 6: + case 5: + return 'year'; + case 4: + return 'month'; + case 3: + return 'week'; + case 2: + return 'day'; + default: + return 'time'; // really? + } + }; + return NativeFormatter; + }()); + function buildFormattingFunc(standardDateProps, extendedSettings, context) { + var standardDatePropCnt = Object.keys(standardDateProps).length; + if (standardDatePropCnt === 1 && standardDateProps.timeZoneName === 'short') { + return function (date) { return (formatTimeZoneOffset(date.timeZoneOffset)); }; + } + if (standardDatePropCnt === 0 && extendedSettings.week) { + return function (date) { return (formatWeekNumber(context.computeWeekNumber(date.marker), context.weekText, context.locale, extendedSettings.week)); }; + } + return buildNativeFormattingFunc(standardDateProps, extendedSettings, context); + } + function buildNativeFormattingFunc(standardDateProps, extendedSettings, context) { + standardDateProps = __assign({}, standardDateProps); // copy + extendedSettings = __assign({}, extendedSettings); // copy + sanitizeSettings(standardDateProps, extendedSettings); + standardDateProps.timeZone = 'UTC'; // we leverage the only guaranteed timeZone for our UTC markers + var normalFormat = new Intl.DateTimeFormat(context.locale.codes, standardDateProps); + var zeroFormat; // needed? + if (extendedSettings.omitZeroMinute) { + var zeroProps = __assign({}, standardDateProps); + delete zeroProps.minute; // seconds and ms were already considered in sanitizeSettings + zeroFormat = new Intl.DateTimeFormat(context.locale.codes, zeroProps); + } + return function (date) { + var marker = date.marker; + var format; + if (zeroFormat && !marker.getUTCMinutes()) { + format = zeroFormat; + } + else { + format = normalFormat; + } + var s = format.format(marker); + return postProcess(s, date, standardDateProps, extendedSettings, context); + }; + } + function sanitizeSettings(standardDateProps, extendedSettings) { + // deal with a browser inconsistency where formatting the timezone + // requires that the hour/minute be present. + if (standardDateProps.timeZoneName) { + if (!standardDateProps.hour) { + standardDateProps.hour = '2-digit'; + } + if (!standardDateProps.minute) { + standardDateProps.minute = '2-digit'; + } + } + // only support short timezone names + if (standardDateProps.timeZoneName === 'long') { + standardDateProps.timeZoneName = 'short'; + } + // if requesting to display seconds, MUST display minutes + if (extendedSettings.omitZeroMinute && (standardDateProps.second || standardDateProps.millisecond)) { + delete extendedSettings.omitZeroMinute; + } + } + function postProcess(s, date, standardDateProps, extendedSettings, context) { + s = s.replace(LTR_RE, ''); // remove left-to-right control chars. do first. good for other regexes + if (standardDateProps.timeZoneName === 'short') { + s = injectTzoStr(s, (context.timeZone === 'UTC' || date.timeZoneOffset == null) ? + 'UTC' : // important to normalize for IE, which does "GMT" + formatTimeZoneOffset(date.timeZoneOffset)); + } + if (extendedSettings.omitCommas) { + s = s.replace(COMMA_RE, '').trim(); + } + if (extendedSettings.omitZeroMinute) { + s = s.replace(':00', ''); // zeroFormat doesn't always achieve this + } + // ^ do anything that might create adjacent spaces before this point, + // because MERIDIEM_RE likes to eat up loading spaces + if (extendedSettings.meridiem === false) { + s = s.replace(MERIDIEM_RE, '').trim(); + } + else if (extendedSettings.meridiem === 'narrow') { // a/p + s = s.replace(MERIDIEM_RE, function (m0, m1) { return m1.toLocaleLowerCase(); }); + } + else if (extendedSettings.meridiem === 'short') { // am/pm + s = s.replace(MERIDIEM_RE, function (m0, m1) { return m1.toLocaleLowerCase() + "m"; }); + } + else if (extendedSettings.meridiem === 'lowercase') { // other meridiem transformers already converted to lowercase + s = s.replace(MERIDIEM_RE, function (m0) { return m0.toLocaleLowerCase(); }); + } + s = s.replace(MULTI_SPACE_RE, ' '); + s = s.trim(); + return s; + } + function injectTzoStr(s, tzoStr) { + var replaced = false; + s = s.replace(UTC_RE, function () { + replaced = true; + return tzoStr; + }); + // IE11 doesn't include UTC/GMT in the original string, so append to end + if (!replaced) { + s += " " + tzoStr; + } + return s; + } + function formatWeekNumber(num, weekText, locale, display) { + var parts = []; + if (display === 'narrow') { + parts.push(weekText); + } + else if (display === 'short') { + parts.push(weekText, ' '); + } + // otherwise, considered 'numeric' + parts.push(locale.simpleNumberFormat.format(num)); + if (locale.options.direction === 'rtl') { // TODO: use control characters instead? + parts.reverse(); + } + return parts.join(''); + } + // Range Formatting Utils + // 0 = exactly the same + // 1 = different by time + // and bigger + function computeMarkerDiffSeverity(d0, d1, ca) { + if (ca.getMarkerYear(d0) !== ca.getMarkerYear(d1)) { + return 5; + } + if (ca.getMarkerMonth(d0) !== ca.getMarkerMonth(d1)) { + return 4; + } + if (ca.getMarkerDay(d0) !== ca.getMarkerDay(d1)) { + return 2; + } + if (timeAsMs(d0) !== timeAsMs(d1)) { + return 1; + } + return 0; + } + function computePartialFormattingOptions(options, biggestUnit) { + var partialOptions = {}; + for (var name_2 in options) { + if (!(name_2 in STANDARD_DATE_PROP_SEVERITIES) || // not a date part prop (like timeZone) + STANDARD_DATE_PROP_SEVERITIES[name_2] <= biggestUnit) { + partialOptions[name_2] = options[name_2]; + } + } + return partialOptions; + } + function findCommonInsertion(full0, partial0, full1, partial1) { + var i0 = 0; + while (i0 < full0.length) { + var found0 = full0.indexOf(partial0, i0); + if (found0 === -1) { + break; + } + var before0 = full0.substr(0, found0); + i0 = found0 + partial0.length; + var after0 = full0.substr(i0); + var i1 = 0; + while (i1 < full1.length) { + var found1 = full1.indexOf(partial1, i1); + if (found1 === -1) { + break; + } + var before1 = full1.substr(0, found1); + i1 = found1 + partial1.length; + var after1 = full1.substr(i1); + if (before0 === before1 && after0 === after1) { + return { + before: before0, + after: after0, + }; + } + } + } + return null; + } + + function expandZonedMarker(dateInfo, calendarSystem) { + var a = calendarSystem.markerToArray(dateInfo.marker); + return { + marker: dateInfo.marker, + timeZoneOffset: dateInfo.timeZoneOffset, + array: a, + year: a[0], + month: a[1], + day: a[2], + hour: a[3], + minute: a[4], + second: a[5], + millisecond: a[6], + }; + } + + function createVerboseFormattingArg(start, end, context, betterDefaultSeparator) { + var startInfo = expandZonedMarker(start, context.calendarSystem); + var endInfo = end ? expandZonedMarker(end, context.calendarSystem) : null; + return { + date: startInfo, + start: startInfo, + end: endInfo, + timeZone: context.timeZone, + localeCodes: context.locale.codes, + defaultSeparator: betterDefaultSeparator || context.defaultSeparator, + }; + } + + /* + TODO: fix the terminology of "formatter" vs "formatting func" + */ + /* + At the time of instantiation, this object does not know which cmd-formatting system it will use. + It receives this at the time of formatting, as a setting. + */ + var CmdFormatter = /** @class */ (function () { + function CmdFormatter(cmdStr) { + this.cmdStr = cmdStr; + } + CmdFormatter.prototype.format = function (date, context, betterDefaultSeparator) { + return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(date, null, context, betterDefaultSeparator)); + }; + CmdFormatter.prototype.formatRange = function (start, end, context, betterDefaultSeparator) { + return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(start, end, context, betterDefaultSeparator)); + }; + return CmdFormatter; + }()); + + var FuncFormatter = /** @class */ (function () { + function FuncFormatter(func) { + this.func = func; + } + FuncFormatter.prototype.format = function (date, context, betterDefaultSeparator) { + return this.func(createVerboseFormattingArg(date, null, context, betterDefaultSeparator)); + }; + FuncFormatter.prototype.formatRange = function (start, end, context, betterDefaultSeparator) { + return this.func(createVerboseFormattingArg(start, end, context, betterDefaultSeparator)); + }; + return FuncFormatter; + }()); + + function createFormatter(input) { + if (typeof input === 'object' && input) { // non-null object + return new NativeFormatter(input); + } + if (typeof input === 'string') { + return new CmdFormatter(input); + } + if (typeof input === 'function') { + return new FuncFormatter(input); + } + return null; + } + + // base options + // ------------ + var BASE_OPTION_REFINERS = { + navLinkDayClick: identity, + navLinkWeekClick: identity, + duration: createDuration, + bootstrapFontAwesome: identity, + buttonIcons: identity, + customButtons: identity, + defaultAllDayEventDuration: createDuration, + defaultTimedEventDuration: createDuration, + nextDayThreshold: createDuration, + scrollTime: createDuration, + scrollTimeReset: Boolean, + slotMinTime: createDuration, + slotMaxTime: createDuration, + dayPopoverFormat: createFormatter, + slotDuration: createDuration, + snapDuration: createDuration, + headerToolbar: identity, + footerToolbar: identity, + defaultRangeSeparator: String, + titleRangeSeparator: String, + forceEventDuration: Boolean, + dayHeaders: Boolean, + dayHeaderFormat: createFormatter, + dayHeaderClassNames: identity, + dayHeaderContent: identity, + dayHeaderDidMount: identity, + dayHeaderWillUnmount: identity, + dayCellClassNames: identity, + dayCellContent: identity, + dayCellDidMount: identity, + dayCellWillUnmount: identity, + initialView: String, + aspectRatio: Number, + weekends: Boolean, + weekNumberCalculation: identity, + weekNumbers: Boolean, + weekNumberClassNames: identity, + weekNumberContent: identity, + weekNumberDidMount: identity, + weekNumberWillUnmount: identity, + editable: Boolean, + viewClassNames: identity, + viewDidMount: identity, + viewWillUnmount: identity, + nowIndicator: Boolean, + nowIndicatorClassNames: identity, + nowIndicatorContent: identity, + nowIndicatorDidMount: identity, + nowIndicatorWillUnmount: identity, + showNonCurrentDates: Boolean, + lazyFetching: Boolean, + startParam: String, + endParam: String, + timeZoneParam: String, + timeZone: String, + locales: identity, + locale: identity, + themeSystem: String, + dragRevertDuration: Number, + dragScroll: Boolean, + allDayMaintainDuration: Boolean, + unselectAuto: Boolean, + dropAccept: identity, + eventOrder: parseFieldSpecs, + eventOrderStrict: Boolean, + handleWindowResize: Boolean, + windowResizeDelay: Number, + longPressDelay: Number, + eventDragMinDistance: Number, + expandRows: Boolean, + height: identity, + contentHeight: identity, + direction: String, + weekNumberFormat: createFormatter, + eventResizableFromStart: Boolean, + displayEventTime: Boolean, + displayEventEnd: Boolean, + weekText: String, + progressiveEventRendering: Boolean, + businessHours: identity, + initialDate: identity, + now: identity, + eventDataTransform: identity, + stickyHeaderDates: identity, + stickyFooterScrollbar: identity, + viewHeight: identity, + defaultAllDay: Boolean, + eventSourceFailure: identity, + eventSourceSuccess: identity, + eventDisplay: String, + eventStartEditable: Boolean, + eventDurationEditable: Boolean, + eventOverlap: identity, + eventConstraint: identity, + eventAllow: identity, + eventBackgroundColor: String, + eventBorderColor: String, + eventTextColor: String, + eventColor: String, + eventClassNames: identity, + eventContent: identity, + eventDidMount: identity, + eventWillUnmount: identity, + selectConstraint: identity, + selectOverlap: identity, + selectAllow: identity, + droppable: Boolean, + unselectCancel: String, + slotLabelFormat: identity, + slotLaneClassNames: identity, + slotLaneContent: identity, + slotLaneDidMount: identity, + slotLaneWillUnmount: identity, + slotLabelClassNames: identity, + slotLabelContent: identity, + slotLabelDidMount: identity, + slotLabelWillUnmount: identity, + dayMaxEvents: identity, + dayMaxEventRows: identity, + dayMinWidth: Number, + slotLabelInterval: createDuration, + allDayText: String, + allDayClassNames: identity, + allDayContent: identity, + allDayDidMount: identity, + allDayWillUnmount: identity, + slotMinWidth: Number, + navLinks: Boolean, + eventTimeFormat: createFormatter, + rerenderDelay: Number, + moreLinkText: identity, + selectMinDistance: Number, + selectable: Boolean, + selectLongPressDelay: Number, + eventLongPressDelay: Number, + selectMirror: Boolean, + eventMaxStack: Number, + eventMinHeight: Number, + eventMinWidth: Number, + eventShortHeight: Number, + slotEventOverlap: Boolean, + plugins: identity, + firstDay: Number, + dayCount: Number, + dateAlignment: String, + dateIncrement: createDuration, + hiddenDays: identity, + monthMode: Boolean, + fixedWeekCount: Boolean, + validRange: identity, + visibleRange: identity, + titleFormat: identity, + // only used by list-view, but languages define the value, so we need it in base options + noEventsText: String, + moreLinkClick: identity, + moreLinkClassNames: identity, + moreLinkContent: identity, + moreLinkDidMount: identity, + moreLinkWillUnmount: identity, + }; + // do NOT give a type here. need `typeof BASE_OPTION_DEFAULTS` to give real results. + // raw values. + var BASE_OPTION_DEFAULTS = { + eventDisplay: 'auto', + defaultRangeSeparator: ' - ', + titleRangeSeparator: ' \u2013 ', + defaultTimedEventDuration: '01:00:00', + defaultAllDayEventDuration: { day: 1 }, + forceEventDuration: false, + nextDayThreshold: '00:00:00', + dayHeaders: true, + initialView: '', + aspectRatio: 1.35, + headerToolbar: { + start: 'title', + center: '', + end: 'today prev,next', + }, + weekends: true, + weekNumbers: false, + weekNumberCalculation: 'local', + editable: false, + nowIndicator: false, + scrollTime: '06:00:00', + scrollTimeReset: true, + slotMinTime: '00:00:00', + slotMaxTime: '24:00:00', + showNonCurrentDates: true, + lazyFetching: true, + startParam: 'start', + endParam: 'end', + timeZoneParam: 'timeZone', + timeZone: 'local', + locales: [], + locale: '', + themeSystem: 'standard', + dragRevertDuration: 500, + dragScroll: true, + allDayMaintainDuration: false, + unselectAuto: true, + dropAccept: '*', + eventOrder: 'start,-duration,allDay,title', + dayPopoverFormat: { month: 'long', day: 'numeric', year: 'numeric' }, + handleWindowResize: true, + windowResizeDelay: 100, + longPressDelay: 1000, + eventDragMinDistance: 5, + expandRows: false, + navLinks: false, + selectable: false, + eventMinHeight: 15, + eventMinWidth: 30, + eventShortHeight: 30, + }; + // calendar listeners + // ------------------ + var CALENDAR_LISTENER_REFINERS = { + datesSet: identity, + eventsSet: identity, + eventAdd: identity, + eventChange: identity, + eventRemove: identity, + windowResize: identity, + eventClick: identity, + eventMouseEnter: identity, + eventMouseLeave: identity, + select: identity, + unselect: identity, + loading: identity, + // internal + _unmount: identity, + _beforeprint: identity, + _afterprint: identity, + _noEventDrop: identity, + _noEventResize: identity, + _resize: identity, + _scrollRequest: identity, + }; + // calendar-specific options + // ------------------------- + var CALENDAR_OPTION_REFINERS = { + buttonText: identity, + views: identity, + plugins: identity, + initialEvents: identity, + events: identity, + eventSources: identity, + }; + var COMPLEX_OPTION_COMPARATORS = { + headerToolbar: isBoolComplexEqual, + footerToolbar: isBoolComplexEqual, + buttonText: isBoolComplexEqual, + buttonIcons: isBoolComplexEqual, + }; + function isBoolComplexEqual(a, b) { + if (typeof a === 'object' && typeof b === 'object' && a && b) { // both non-null objects + return isPropsEqual(a, b); + } + return a === b; + } + // view-specific options + // --------------------- + var VIEW_OPTION_REFINERS = { + type: String, + component: identity, + buttonText: String, + buttonTextKey: String, + dateProfileGeneratorClass: identity, + usesMinMaxTime: Boolean, + classNames: identity, + content: identity, + didMount: identity, + willUnmount: identity, + }; + // util funcs + // ---------------------------------------------------------------------------------------------------- + function mergeRawOptions(optionSets) { + return mergeProps(optionSets, COMPLEX_OPTION_COMPARATORS); + } + function refineProps(input, refiners) { + var refined = {}; + var extra = {}; + for (var propName in refiners) { + if (propName in input) { + refined[propName] = refiners[propName](input[propName]); + } + } + for (var propName in input) { + if (!(propName in refiners)) { + extra[propName] = input[propName]; + } + } + return { refined: refined, extra: extra }; + } + function identity(raw) { + return raw; + } + + function parseEvents(rawEvents, eventSource, context, allowOpenRange) { + var eventStore = createEmptyEventStore(); + var eventRefiners = buildEventRefiners(context); + for (var _i = 0, rawEvents_1 = rawEvents; _i < rawEvents_1.length; _i++) { + var rawEvent = rawEvents_1[_i]; + var tuple = parseEvent(rawEvent, eventSource, context, allowOpenRange, eventRefiners); + if (tuple) { + eventTupleToStore(tuple, eventStore); + } + } + return eventStore; + } + function eventTupleToStore(tuple, eventStore) { + if (eventStore === void 0) { eventStore = createEmptyEventStore(); } + eventStore.defs[tuple.def.defId] = tuple.def; + if (tuple.instance) { + eventStore.instances[tuple.instance.instanceId] = tuple.instance; + } + return eventStore; + } + // retrieves events that have the same groupId as the instance specified by `instanceId` + // or they are the same as the instance. + // why might instanceId not be in the store? an event from another calendar? + function getRelevantEvents(eventStore, instanceId) { + var instance = eventStore.instances[instanceId]; + if (instance) { + var def_1 = eventStore.defs[instance.defId]; + // get events/instances with same group + var newStore = filterEventStoreDefs(eventStore, function (lookDef) { return isEventDefsGrouped(def_1, lookDef); }); + // add the original + // TODO: wish we could use eventTupleToStore or something like it + newStore.defs[def_1.defId] = def_1; + newStore.instances[instance.instanceId] = instance; + return newStore; + } + return createEmptyEventStore(); + } + function isEventDefsGrouped(def0, def1) { + return Boolean(def0.groupId && def0.groupId === def1.groupId); + } + function createEmptyEventStore() { + return { defs: {}, instances: {} }; + } + function mergeEventStores(store0, store1) { + return { + defs: __assign(__assign({}, store0.defs), store1.defs), + instances: __assign(__assign({}, store0.instances), store1.instances), + }; + } + function filterEventStoreDefs(eventStore, filterFunc) { + var defs = filterHash(eventStore.defs, filterFunc); + var instances = filterHash(eventStore.instances, function (instance) { return (defs[instance.defId] // still exists? + ); }); + return { defs: defs, instances: instances }; + } + function excludeSubEventStore(master, sub) { + var defs = master.defs, instances = master.instances; + var filteredDefs = {}; + var filteredInstances = {}; + for (var defId in defs) { + if (!sub.defs[defId]) { // not explicitly excluded + filteredDefs[defId] = defs[defId]; + } + } + for (var instanceId in instances) { + if (!sub.instances[instanceId] && // not explicitly excluded + filteredDefs[instances[instanceId].defId] // def wasn't filtered away + ) { + filteredInstances[instanceId] = instances[instanceId]; + } + } + return { + defs: filteredDefs, + instances: filteredInstances, + }; + } + + function normalizeConstraint(input, context) { + if (Array.isArray(input)) { + return parseEvents(input, null, context, true); // allowOpenRange=true + } + if (typeof input === 'object' && input) { // non-null object + return parseEvents([input], null, context, true); // allowOpenRange=true + } + if (input != null) { + return String(input); + } + return null; + } + + function parseClassNames(raw) { + if (Array.isArray(raw)) { + return raw; + } + if (typeof raw === 'string') { + return raw.split(/\s+/); + } + return []; + } + + // TODO: better called "EventSettings" or "EventConfig" + // TODO: move this file into structs + // TODO: separate constraint/overlap/allow, because selection uses only that, not other props + var EVENT_UI_REFINERS = { + display: String, + editable: Boolean, + startEditable: Boolean, + durationEditable: Boolean, + constraint: identity, + overlap: identity, + allow: identity, + className: parseClassNames, + classNames: parseClassNames, + color: String, + backgroundColor: String, + borderColor: String, + textColor: String, + }; + var EMPTY_EVENT_UI = { + display: null, + startEditable: null, + durationEditable: null, + constraints: [], + overlap: null, + allows: [], + backgroundColor: '', + borderColor: '', + textColor: '', + classNames: [], + }; + function createEventUi(refined, context) { + var constraint = normalizeConstraint(refined.constraint, context); + return { + display: refined.display || null, + startEditable: refined.startEditable != null ? refined.startEditable : refined.editable, + durationEditable: refined.durationEditable != null ? refined.durationEditable : refined.editable, + constraints: constraint != null ? [constraint] : [], + overlap: refined.overlap != null ? refined.overlap : null, + allows: refined.allow != null ? [refined.allow] : [], + backgroundColor: refined.backgroundColor || refined.color || '', + borderColor: refined.borderColor || refined.color || '', + textColor: refined.textColor || '', + classNames: (refined.className || []).concat(refined.classNames || []), // join singular and plural + }; + } + // TODO: prevent against problems with <2 args! + function combineEventUis(uis) { + return uis.reduce(combineTwoEventUis, EMPTY_EVENT_UI); + } + function combineTwoEventUis(item0, item1) { + return { + display: item1.display != null ? item1.display : item0.display, + startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable, + durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable, + constraints: item0.constraints.concat(item1.constraints), + overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap, + allows: item0.allows.concat(item1.allows), + backgroundColor: item1.backgroundColor || item0.backgroundColor, + borderColor: item1.borderColor || item0.borderColor, + textColor: item1.textColor || item0.textColor, + classNames: item0.classNames.concat(item1.classNames), + }; + } + + var EVENT_NON_DATE_REFINERS = { + id: String, + groupId: String, + title: String, + url: String, + }; + var EVENT_DATE_REFINERS = { + start: identity, + end: identity, + date: identity, + allDay: Boolean, + }; + var EVENT_REFINERS = __assign(__assign(__assign({}, EVENT_NON_DATE_REFINERS), EVENT_DATE_REFINERS), { extendedProps: identity }); + function parseEvent(raw, eventSource, context, allowOpenRange, refiners) { + if (refiners === void 0) { refiners = buildEventRefiners(context); } + var _a = refineEventDef(raw, context, refiners), refined = _a.refined, extra = _a.extra; + var defaultAllDay = computeIsDefaultAllDay(eventSource, context); + var recurringRes = parseRecurring(refined, defaultAllDay, context.dateEnv, context.pluginHooks.recurringTypes); + if (recurringRes) { + var def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', recurringRes.allDay, Boolean(recurringRes.duration), context); + def.recurringDef = { + typeId: recurringRes.typeId, + typeData: recurringRes.typeData, + duration: recurringRes.duration, + }; + return { def: def, instance: null }; + } + var singleRes = parseSingle(refined, defaultAllDay, context, allowOpenRange); + if (singleRes) { + var def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', singleRes.allDay, singleRes.hasEnd, context); + var instance = createEventInstance(def.defId, singleRes.range, singleRes.forcedStartTzo, singleRes.forcedEndTzo); + return { def: def, instance: instance }; + } + return null; + } + function refineEventDef(raw, context, refiners) { + if (refiners === void 0) { refiners = buildEventRefiners(context); } + return refineProps(raw, refiners); + } + function buildEventRefiners(context) { + return __assign(__assign(__assign({}, EVENT_UI_REFINERS), EVENT_REFINERS), context.pluginHooks.eventRefiners); + } + /* + Will NOT populate extendedProps with the leftover properties. + Will NOT populate date-related props. + */ + function parseEventDef(refined, extra, sourceId, allDay, hasEnd, context) { + var def = { + title: refined.title || '', + groupId: refined.groupId || '', + publicId: refined.id || '', + url: refined.url || '', + recurringDef: null, + defId: guid(), + sourceId: sourceId, + allDay: allDay, + hasEnd: hasEnd, + ui: createEventUi(refined, context), + extendedProps: __assign(__assign({}, (refined.extendedProps || {})), extra), + }; + for (var _i = 0, _a = context.pluginHooks.eventDefMemberAdders; _i < _a.length; _i++) { + var memberAdder = _a[_i]; + __assign(def, memberAdder(refined)); + } + // help out EventApi from having user modify props + Object.freeze(def.ui.classNames); + Object.freeze(def.extendedProps); + return def; + } + function parseSingle(refined, defaultAllDay, context, allowOpenRange) { + var allDay = refined.allDay; + var startMeta; + var startMarker = null; + var hasEnd = false; + var endMeta; + var endMarker = null; + var startInput = refined.start != null ? refined.start : refined.date; + startMeta = context.dateEnv.createMarkerMeta(startInput); + if (startMeta) { + startMarker = startMeta.marker; + } + else if (!allowOpenRange) { + return null; + } + if (refined.end != null) { + endMeta = context.dateEnv.createMarkerMeta(refined.end); + } + if (allDay == null) { + if (defaultAllDay != null) { + allDay = defaultAllDay; + } + else { + // fall back to the date props LAST + allDay = (!startMeta || startMeta.isTimeUnspecified) && + (!endMeta || endMeta.isTimeUnspecified); + } + } + if (allDay && startMarker) { + startMarker = startOfDay(startMarker); + } + if (endMeta) { + endMarker = endMeta.marker; + if (allDay) { + endMarker = startOfDay(endMarker); + } + if (startMarker && endMarker <= startMarker) { + endMarker = null; + } + } + if (endMarker) { + hasEnd = true; + } + else if (!allowOpenRange) { + hasEnd = context.options.forceEventDuration || false; + endMarker = context.dateEnv.add(startMarker, allDay ? + context.options.defaultAllDayEventDuration : + context.options.defaultTimedEventDuration); + } + return { + allDay: allDay, + hasEnd: hasEnd, + range: { start: startMarker, end: endMarker }, + forcedStartTzo: startMeta ? startMeta.forcedTzo : null, + forcedEndTzo: endMeta ? endMeta.forcedTzo : null, + }; + } + function computeIsDefaultAllDay(eventSource, context) { + var res = null; + if (eventSource) { + res = eventSource.defaultAllDay; + } + if (res == null) { + res = context.options.defaultAllDay; + } + return res; + } + + /* Date stuff that doesn't belong in datelib core + ----------------------------------------------------------------------------------------------------------------------*/ + // given a timed range, computes an all-day range that has the same exact duration, + // but whose start time is aligned with the start of the day. + function computeAlignedDayRange(timedRange) { + var dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1; + var start = startOfDay(timedRange.start); + var end = addDays(start, dayCnt); + return { start: start, end: end }; + } + // given a timed range, computes an all-day range based on how for the end date bleeds into the next day + // TODO: give nextDayThreshold a default arg + function computeVisibleDayRange(timedRange, nextDayThreshold) { + if (nextDayThreshold === void 0) { nextDayThreshold = createDuration(0); } + var startDay = null; + var endDay = null; + if (timedRange.end) { + endDay = startOfDay(timedRange.end); + var endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay` + // If the end time is actually inclusively part of the next day and is equal to or + // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`. + // Otherwise, leaving it as inclusive will cause it to exclude `endDay`. + if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) { + endDay = addDays(endDay, 1); + } + } + if (timedRange.start) { + startDay = startOfDay(timedRange.start); // the beginning of the day the range starts + // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day. + if (endDay && endDay <= startDay) { + endDay = addDays(startDay, 1); + } + } + return { start: startDay, end: endDay }; + } + // spans from one day into another? + function isMultiDayRange(range) { + var visibleRange = computeVisibleDayRange(range); + return diffDays(visibleRange.start, visibleRange.end) > 1; + } + function diffDates(date0, date1, dateEnv, largeUnit) { + if (largeUnit === 'year') { + return createDuration(dateEnv.diffWholeYears(date0, date1), 'year'); + } + if (largeUnit === 'month') { + return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month'); + } + return diffDayAndTime(date0, date1); // returns a duration + } + + function parseRange(input, dateEnv) { + var start = null; + var end = null; + if (input.start) { + start = dateEnv.createMarker(input.start); + } + if (input.end) { + end = dateEnv.createMarker(input.end); + } + if (!start && !end) { + return null; + } + if (start && end && end < start) { + return null; + } + return { start: start, end: end }; + } + // SIDE-EFFECT: will mutate ranges. + // Will return a new array result. + function invertRanges(ranges, constraintRange) { + var invertedRanges = []; + var start = constraintRange.start; // the end of the previous range. the start of the new range + var i; + var dateRange; + // ranges need to be in order. required for our date-walking algorithm + ranges.sort(compareRanges); + for (i = 0; i < ranges.length; i += 1) { + dateRange = ranges[i]; + // add the span of time before the event (if there is any) + if (dateRange.start > start) { // compare millisecond time (skip any ambig logic) + invertedRanges.push({ start: start, end: dateRange.start }); + } + if (dateRange.end > start) { + start = dateRange.end; + } + } + // add the span of time after the last event (if there is any) + if (start < constraintRange.end) { // compare millisecond time (skip any ambig logic) + invertedRanges.push({ start: start, end: constraintRange.end }); + } + return invertedRanges; + } + function compareRanges(range0, range1) { + return range0.start.valueOf() - range1.start.valueOf(); // earlier ranges go first + } + function intersectRanges(range0, range1) { + var start = range0.start, end = range0.end; + var newRange = null; + if (range1.start !== null) { + if (start === null) { + start = range1.start; + } + else { + start = new Date(Math.max(start.valueOf(), range1.start.valueOf())); + } + } + if (range1.end != null) { + if (end === null) { + end = range1.end; + } + else { + end = new Date(Math.min(end.valueOf(), range1.end.valueOf())); + } + } + if (start === null || end === null || start < end) { + newRange = { start: start, end: end }; + } + return newRange; + } + function rangesEqual(range0, range1) { + return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) && + (range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf()); + } + function rangesIntersect(range0, range1) { + return (range0.end === null || range1.start === null || range0.end > range1.start) && + (range0.start === null || range1.end === null || range0.start < range1.end); + } + function rangeContainsRange(outerRange, innerRange) { + return (outerRange.start === null || (innerRange.start !== null && innerRange.start >= outerRange.start)) && + (outerRange.end === null || (innerRange.end !== null && innerRange.end <= outerRange.end)); + } + function rangeContainsMarker(range, date) { + return (range.start === null || date >= range.start) && + (range.end === null || date < range.end); + } + // If the given date is not within the given range, move it inside. + // (If it's past the end, make it one millisecond before the end). + function constrainMarkerToRange(date, range) { + if (range.start != null && date < range.start) { + return range.start; + } + if (range.end != null && date >= range.end) { + return new Date(range.end.valueOf() - 1); + } + return date; + } + + /* + Specifying nextDayThreshold signals that all-day ranges should be sliced. + */ + function sliceEventStore(eventStore, eventUiBases, framingRange, nextDayThreshold) { + var inverseBgByGroupId = {}; + var inverseBgByDefId = {}; + var defByGroupId = {}; + var bgRanges = []; + var fgRanges = []; + var eventUis = compileEventUis(eventStore.defs, eventUiBases); + for (var defId in eventStore.defs) { + var def = eventStore.defs[defId]; + var ui = eventUis[def.defId]; + if (ui.display === 'inverse-background') { + if (def.groupId) { + inverseBgByGroupId[def.groupId] = []; + if (!defByGroupId[def.groupId]) { + defByGroupId[def.groupId] = def; + } + } + else { + inverseBgByDefId[defId] = []; + } + } + } + for (var instanceId in eventStore.instances) { + var instance = eventStore.instances[instanceId]; + var def = eventStore.defs[instance.defId]; + var ui = eventUis[def.defId]; + var origRange = instance.range; + var normalRange = (!def.allDay && nextDayThreshold) ? + computeVisibleDayRange(origRange, nextDayThreshold) : + origRange; + var slicedRange = intersectRanges(normalRange, framingRange); + if (slicedRange) { + if (ui.display === 'inverse-background') { + if (def.groupId) { + inverseBgByGroupId[def.groupId].push(slicedRange); + } + else { + inverseBgByDefId[instance.defId].push(slicedRange); + } + } + else if (ui.display !== 'none') { + (ui.display === 'background' ? bgRanges : fgRanges).push({ + def: def, + ui: ui, + instance: instance, + range: slicedRange, + isStart: normalRange.start && normalRange.start.valueOf() === slicedRange.start.valueOf(), + isEnd: normalRange.end && normalRange.end.valueOf() === slicedRange.end.valueOf(), + }); + } + } + } + for (var groupId in inverseBgByGroupId) { // BY GROUP + var ranges = inverseBgByGroupId[groupId]; + var invertedRanges = invertRanges(ranges, framingRange); + for (var _i = 0, invertedRanges_1 = invertedRanges; _i < invertedRanges_1.length; _i++) { + var invertedRange = invertedRanges_1[_i]; + var def = defByGroupId[groupId]; + var ui = eventUis[def.defId]; + bgRanges.push({ + def: def, + ui: ui, + instance: null, + range: invertedRange, + isStart: false, + isEnd: false, + }); + } + } + for (var defId in inverseBgByDefId) { + var ranges = inverseBgByDefId[defId]; + var invertedRanges = invertRanges(ranges, framingRange); + for (var _a = 0, invertedRanges_2 = invertedRanges; _a < invertedRanges_2.length; _a++) { + var invertedRange = invertedRanges_2[_a]; + bgRanges.push({ + def: eventStore.defs[defId], + ui: eventUis[defId], + instance: null, + range: invertedRange, + isStart: false, + isEnd: false, + }); + } + } + return { bg: bgRanges, fg: fgRanges }; + } + function hasBgRendering(def) { + return def.ui.display === 'background' || def.ui.display === 'inverse-background'; + } + function setElSeg(el, seg) { + el.fcSeg = seg; + } + function getElSeg(el) { + return el.fcSeg || + el.parentNode.fcSeg || // for the harness + null; + } + // event ui computation + function compileEventUis(eventDefs, eventUiBases) { + return mapHash(eventDefs, function (eventDef) { return compileEventUi(eventDef, eventUiBases); }); + } + function compileEventUi(eventDef, eventUiBases) { + var uis = []; + if (eventUiBases['']) { + uis.push(eventUiBases['']); + } + if (eventUiBases[eventDef.defId]) { + uis.push(eventUiBases[eventDef.defId]); + } + uis.push(eventDef.ui); + return combineEventUis(uis); + } + function sortEventSegs(segs, eventOrderSpecs) { + var objs = segs.map(buildSegCompareObj); + objs.sort(function (obj0, obj1) { return compareByFieldSpecs(obj0, obj1, eventOrderSpecs); }); + return objs.map(function (c) { return c._seg; }); + } + // returns a object with all primitive props that can be compared + function buildSegCompareObj(seg) { + var eventRange = seg.eventRange; + var eventDef = eventRange.def; + var range = eventRange.instance ? eventRange.instance.range : eventRange.range; + var start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events + var end = range.end ? range.end.valueOf() : 0; // " + return __assign(__assign(__assign({}, eventDef.extendedProps), eventDef), { id: eventDef.publicId, start: start, + end: end, duration: end - start, allDay: Number(eventDef.allDay), _seg: seg }); + } + function computeSegDraggable(seg, context) { + var pluginHooks = context.pluginHooks; + var transformers = pluginHooks.isDraggableTransformers; + var _a = seg.eventRange, def = _a.def, ui = _a.ui; + var val = ui.startEditable; + for (var _i = 0, transformers_1 = transformers; _i < transformers_1.length; _i++) { + var transformer = transformers_1[_i]; + val = transformer(val, def, ui, context); + } + return val; + } + function computeSegStartResizable(seg, context) { + return seg.isStart && seg.eventRange.ui.durationEditable && context.options.eventResizableFromStart; + } + function computeSegEndResizable(seg, context) { + return seg.isEnd && seg.eventRange.ui.durationEditable; + } + function buildSegTimeText(seg, timeFormat, context, defaultDisplayEventTime, // defaults to true + defaultDisplayEventEnd, // defaults to true + startOverride, endOverride) { + var dateEnv = context.dateEnv, options = context.options; + var displayEventTime = options.displayEventTime, displayEventEnd = options.displayEventEnd; + var eventDef = seg.eventRange.def; + var eventInstance = seg.eventRange.instance; + if (displayEventTime == null) { + displayEventTime = defaultDisplayEventTime !== false; + } + if (displayEventEnd == null) { + displayEventEnd = defaultDisplayEventEnd !== false; + } + var wholeEventStart = eventInstance.range.start; + var wholeEventEnd = eventInstance.range.end; + var segStart = startOverride || seg.start || seg.eventRange.range.start; + var segEnd = endOverride || seg.end || seg.eventRange.range.end; + var isStartDay = startOfDay(wholeEventStart).valueOf() === startOfDay(segStart).valueOf(); + var isEndDay = startOfDay(addMs(wholeEventEnd, -1)).valueOf() === startOfDay(addMs(segEnd, -1)).valueOf(); + if (displayEventTime && !eventDef.allDay && (isStartDay || isEndDay)) { + segStart = isStartDay ? wholeEventStart : segStart; + segEnd = isEndDay ? wholeEventEnd : segEnd; + if (displayEventEnd && eventDef.hasEnd) { + return dateEnv.formatRange(segStart, segEnd, timeFormat, { + forcedStartTzo: startOverride ? null : eventInstance.forcedStartTzo, + forcedEndTzo: endOverride ? null : eventInstance.forcedEndTzo, + }); + } + return dateEnv.format(segStart, timeFormat, { + forcedTzo: startOverride ? null : eventInstance.forcedStartTzo, // nooooo, same + }); + } + return ''; + } + function getSegMeta(seg, todayRange, nowDate) { + var segRange = seg.eventRange.range; + return { + isPast: segRange.end < (nowDate || todayRange.start), + isFuture: segRange.start >= (nowDate || todayRange.end), + isToday: todayRange && rangeContainsMarker(todayRange, segRange.start), + }; + } + function getEventClassNames(props) { + var classNames = ['fc-event']; + if (props.isMirror) { + classNames.push('fc-event-mirror'); + } + if (props.isDraggable) { + classNames.push('fc-event-draggable'); + } + if (props.isStartResizable || props.isEndResizable) { + classNames.push('fc-event-resizable'); + } + if (props.isDragging) { + classNames.push('fc-event-dragging'); + } + if (props.isResizing) { + classNames.push('fc-event-resizing'); + } + if (props.isSelected) { + classNames.push('fc-event-selected'); + } + if (props.isStart) { + classNames.push('fc-event-start'); + } + if (props.isEnd) { + classNames.push('fc-event-end'); + } + if (props.isPast) { + classNames.push('fc-event-past'); + } + if (props.isToday) { + classNames.push('fc-event-today'); + } + if (props.isFuture) { + classNames.push('fc-event-future'); + } + return classNames; + } + function buildEventRangeKey(eventRange) { + return eventRange.instance + ? eventRange.instance.instanceId + : eventRange.def.defId + ":" + eventRange.range.start.toISOString(); + // inverse-background events don't have specific instances. TODO: better solution + } + + var STANDARD_PROPS = { + start: identity, + end: identity, + allDay: Boolean, + }; + function parseDateSpan(raw, dateEnv, defaultDuration) { + var span = parseOpenDateSpan(raw, dateEnv); + var range = span.range; + if (!range.start) { + return null; + } + if (!range.end) { + if (defaultDuration == null) { + return null; + } + range.end = dateEnv.add(range.start, defaultDuration); + } + return span; + } + /* + TODO: somehow combine with parseRange? + Will return null if the start/end props were present but parsed invalidly. + */ + function parseOpenDateSpan(raw, dateEnv) { + var _a = refineProps(raw, STANDARD_PROPS), standardProps = _a.refined, extra = _a.extra; + var startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null; + var endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null; + var allDay = standardProps.allDay; + if (allDay == null) { + allDay = (startMeta && startMeta.isTimeUnspecified) && + (!endMeta || endMeta.isTimeUnspecified); + } + return __assign({ range: { + start: startMeta ? startMeta.marker : null, + end: endMeta ? endMeta.marker : null, + }, allDay: allDay }, extra); + } + function isDateSpansEqual(span0, span1) { + return rangesEqual(span0.range, span1.range) && + span0.allDay === span1.allDay && + isSpanPropsEqual(span0, span1); + } + // the NON-DATE-RELATED props + function isSpanPropsEqual(span0, span1) { + for (var propName in span1) { + if (propName !== 'range' && propName !== 'allDay') { + if (span0[propName] !== span1[propName]) { + return false; + } + } + } + // are there any props that span0 has that span1 DOESN'T have? + // both have range/allDay, so no need to special-case. + for (var propName in span0) { + if (!(propName in span1)) { + return false; + } + } + return true; + } + function buildDateSpanApi(span, dateEnv) { + return __assign(__assign({}, buildRangeApi(span.range, dateEnv, span.allDay)), { allDay: span.allDay }); + } + function buildRangeApiWithTimeZone(range, dateEnv, omitTime) { + return __assign(__assign({}, buildRangeApi(range, dateEnv, omitTime)), { timeZone: dateEnv.timeZone }); + } + function buildRangeApi(range, dateEnv, omitTime) { + return { + start: dateEnv.toDate(range.start), + end: dateEnv.toDate(range.end), + startStr: dateEnv.formatIso(range.start, { omitTime: omitTime }), + endStr: dateEnv.formatIso(range.end, { omitTime: omitTime }), + }; + } + function fabricateEventRange(dateSpan, eventUiBases, context) { + var res = refineEventDef({ editable: false }, context); + var def = parseEventDef(res.refined, res.extra, '', // sourceId + dateSpan.allDay, true, // hasEnd + context); + return { + def: def, + ui: compileEventUi(def, eventUiBases), + instance: createEventInstance(def.defId, dateSpan.range), + range: dateSpan.range, + isStart: true, + isEnd: true, + }; + } + + function triggerDateSelect(selection, pev, context) { + context.emitter.trigger('select', __assign(__assign({}, buildDateSpanApiWithContext(selection, context)), { jsEvent: pev ? pev.origEvent : null, view: context.viewApi || context.calendarApi.view })); + } + function triggerDateUnselect(pev, context) { + context.emitter.trigger('unselect', { + jsEvent: pev ? pev.origEvent : null, + view: context.viewApi || context.calendarApi.view, + }); + } + function buildDateSpanApiWithContext(dateSpan, context) { + var props = {}; + for (var _i = 0, _a = context.pluginHooks.dateSpanTransforms; _i < _a.length; _i++) { + var transform = _a[_i]; + __assign(props, transform(dateSpan, context)); + } + __assign(props, buildDateSpanApi(dateSpan, context.dateEnv)); + return props; + } + // Given an event's allDay status and start date, return what its fallback end date should be. + // TODO: rename to computeDefaultEventEnd + function getDefaultEventEnd(allDay, marker, context) { + var dateEnv = context.dateEnv, options = context.options; + var end = marker; + if (allDay) { + end = startOfDay(end); + end = dateEnv.add(end, options.defaultAllDayEventDuration); + } + else { + end = dateEnv.add(end, options.defaultTimedEventDuration); + } + return end; + } + + // applies the mutation to ALL defs/instances within the event store + function applyMutationToEventStore(eventStore, eventConfigBase, mutation, context) { + var eventConfigs = compileEventUis(eventStore.defs, eventConfigBase); + var dest = createEmptyEventStore(); + for (var defId in eventStore.defs) { + var def = eventStore.defs[defId]; + dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context); + } + for (var instanceId in eventStore.instances) { + var instance = eventStore.instances[instanceId]; + var def = dest.defs[instance.defId]; // important to grab the newly modified def + dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context); + } + return dest; + } + function applyMutationToEventDef(eventDef, eventConfig, mutation, context) { + var standardProps = mutation.standardProps || {}; + // if hasEnd has not been specified, guess a good value based on deltas. + // if duration will change, there's no way the default duration will persist, + // and thus, we need to mark the event as having a real end + if (standardProps.hasEnd == null && + eventConfig.durationEditable && + (mutation.startDelta || mutation.endDelta)) { + standardProps.hasEnd = true; // TODO: is this mutation okay? + } + var copy = __assign(__assign(__assign({}, eventDef), standardProps), { ui: __assign(__assign({}, eventDef.ui), standardProps.ui) }); + if (mutation.extendedProps) { + copy.extendedProps = __assign(__assign({}, copy.extendedProps), mutation.extendedProps); + } + for (var _i = 0, _a = context.pluginHooks.eventDefMutationAppliers; _i < _a.length; _i++) { + var applier = _a[_i]; + applier(copy, mutation, context); + } + if (!copy.hasEnd && context.options.forceEventDuration) { + copy.hasEnd = true; + } + return copy; + } + function applyMutationToEventInstance(eventInstance, eventDef, // must first be modified by applyMutationToEventDef + eventConfig, mutation, context) { + var dateEnv = context.dateEnv; + var forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true; + var clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false; + var copy = __assign({}, eventInstance); + if (forceAllDay) { + copy.range = computeAlignedDayRange(copy.range); + } + if (mutation.datesDelta && eventConfig.startEditable) { + copy.range = { + start: dateEnv.add(copy.range.start, mutation.datesDelta), + end: dateEnv.add(copy.range.end, mutation.datesDelta), + }; + } + if (mutation.startDelta && eventConfig.durationEditable) { + copy.range = { + start: dateEnv.add(copy.range.start, mutation.startDelta), + end: copy.range.end, + }; + } + if (mutation.endDelta && eventConfig.durationEditable) { + copy.range = { + start: copy.range.start, + end: dateEnv.add(copy.range.end, mutation.endDelta), + }; + } + if (clearEnd) { + copy.range = { + start: copy.range.start, + end: getDefaultEventEnd(eventDef.allDay, copy.range.start, context), + }; + } + // in case event was all-day but the supplied deltas were not + // better util for this? + if (eventDef.allDay) { + copy.range = { + start: startOfDay(copy.range.start), + end: startOfDay(copy.range.end), + }; + } + // handle invalid durations + if (copy.range.end < copy.range.start) { + copy.range.end = getDefaultEventEnd(eventDef.allDay, copy.range.start, context); + } + return copy; + } + + // no public types yet. when there are, export from: + // import {} from './api-type-deps' + var ViewApi = /** @class */ (function () { + function ViewApi(type, getCurrentData, dateEnv) { + this.type = type; + this.getCurrentData = getCurrentData; + this.dateEnv = dateEnv; + } + Object.defineProperty(ViewApi.prototype, "calendar", { + get: function () { + return this.getCurrentData().calendarApi; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "title", { + get: function () { + return this.getCurrentData().viewTitle; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "activeStart", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "activeEnd", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "currentStart", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "currentEnd", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end); + }, + enumerable: false, + configurable: true + }); + ViewApi.prototype.getOption = function (name) { + return this.getCurrentData().options[name]; // are the view-specific options + }; + return ViewApi; + }()); + + var EVENT_SOURCE_REFINERS$1 = { + id: String, + defaultAllDay: Boolean, + url: String, + format: String, + events: identity, + eventDataTransform: identity, + // for any network-related sources + success: identity, + failure: identity, + }; + function parseEventSource(raw, context, refiners) { + if (refiners === void 0) { refiners = buildEventSourceRefiners(context); } + var rawObj; + if (typeof raw === 'string') { + rawObj = { url: raw }; + } + else if (typeof raw === 'function' || Array.isArray(raw)) { + rawObj = { events: raw }; + } + else if (typeof raw === 'object' && raw) { // not null + rawObj = raw; + } + if (rawObj) { + var _a = refineProps(rawObj, refiners), refined = _a.refined, extra = _a.extra; + var metaRes = buildEventSourceMeta(refined, context); + if (metaRes) { + return { + _raw: raw, + isFetching: false, + latestFetchId: '', + fetchRange: null, + defaultAllDay: refined.defaultAllDay, + eventDataTransform: refined.eventDataTransform, + success: refined.success, + failure: refined.failure, + publicId: refined.id || '', + sourceId: guid(), + sourceDefId: metaRes.sourceDefId, + meta: metaRes.meta, + ui: createEventUi(refined, context), + extendedProps: extra, + }; + } + } + return null; + } + function buildEventSourceRefiners(context) { + return __assign(__assign(__assign({}, EVENT_UI_REFINERS), EVENT_SOURCE_REFINERS$1), context.pluginHooks.eventSourceRefiners); + } + function buildEventSourceMeta(raw, context) { + var defs = context.pluginHooks.eventSourceDefs; + for (var i = defs.length - 1; i >= 0; i -= 1) { // later-added plugins take precedence + var def = defs[i]; + var meta = def.parseMeta(raw); + if (meta) { + return { sourceDefId: i, meta: meta }; + } + } + return null; + } + + function reduceCurrentDate(currentDate, action) { + switch (action.type) { + case 'CHANGE_DATE': + return action.dateMarker; + default: + return currentDate; + } + } + function getInitialDate(options, dateEnv) { + var initialDateInput = options.initialDate; + // compute the initial ambig-timezone date + if (initialDateInput != null) { + return dateEnv.createMarker(initialDateInput); + } + return getNow(options.now, dateEnv); // getNow already returns unzoned + } + function getNow(nowInput, dateEnv) { + if (typeof nowInput === 'function') { + nowInput = nowInput(); + } + if (nowInput == null) { + return dateEnv.createNowMarker(); + } + return dateEnv.createMarker(nowInput); + } + + var CalendarApi = /** @class */ (function () { + function CalendarApi() { + } + CalendarApi.prototype.getCurrentData = function () { + return this.currentDataManager.getCurrentData(); + }; + CalendarApi.prototype.dispatch = function (action) { + return this.currentDataManager.dispatch(action); + }; + Object.defineProperty(CalendarApi.prototype, "view", { + get: function () { return this.getCurrentData().viewApi; } // for public API + , + enumerable: false, + configurable: true + }); + CalendarApi.prototype.batchRendering = function (callback) { + callback(); + }; + CalendarApi.prototype.updateSize = function () { + this.trigger('_resize', true); + }; + // Options + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.setOption = function (name, val) { + this.dispatch({ + type: 'SET_OPTION', + optionName: name, + rawOptionValue: val, + }); + }; + CalendarApi.prototype.getOption = function (name) { + return this.currentDataManager.currentCalendarOptionsInput[name]; + }; + CalendarApi.prototype.getAvailableLocaleCodes = function () { + return Object.keys(this.getCurrentData().availableRawLocales); + }; + // Trigger + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.on = function (handlerName, handler) { + var currentDataManager = this.currentDataManager; + if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) { + currentDataManager.emitter.on(handlerName, handler); + } + else { + console.warn("Unknown listener name '" + handlerName + "'"); + } + }; + CalendarApi.prototype.off = function (handlerName, handler) { + this.currentDataManager.emitter.off(handlerName, handler); + }; + // not meant for public use + CalendarApi.prototype.trigger = function (handlerName) { + var _a; + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + (_a = this.currentDataManager.emitter).trigger.apply(_a, __spreadArray([handlerName], args)); + }; + // View + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.changeView = function (viewType, dateOrRange) { + var _this = this; + this.batchRendering(function () { + _this.unselect(); + if (dateOrRange) { + if (dateOrRange.start && dateOrRange.end) { // a range + _this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: viewType, + }); + _this.dispatch({ + type: 'SET_OPTION', + optionName: 'visibleRange', + rawOptionValue: dateOrRange, + }); + } + else { + var dateEnv = _this.getCurrentData().dateEnv; + _this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: viewType, + dateMarker: dateEnv.createMarker(dateOrRange), + }); + } + } + else { + _this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: viewType, + }); + } + }); + }; + // Forces navigation to a view for the given date. + // `viewType` can be a specific view name or a generic one like "week" or "day". + // needs to change + CalendarApi.prototype.zoomTo = function (dateMarker, viewType) { + var state = this.getCurrentData(); + var spec; + viewType = viewType || 'day'; // day is default zoom + spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType); + this.unselect(); + if (spec) { + this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: spec.type, + dateMarker: dateMarker, + }); + } + else { + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: dateMarker, + }); + } + }; + // Given a duration singular unit, like "week" or "day", finds a matching view spec. + // Preference is given to views that have corresponding buttons. + CalendarApi.prototype.getUnitViewSpec = function (unit) { + var _a = this.getCurrentData(), viewSpecs = _a.viewSpecs, toolbarConfig = _a.toolbarConfig; + var viewTypes = [].concat(toolbarConfig.viewsWithButtons); + var i; + var spec; + for (var viewType in viewSpecs) { + viewTypes.push(viewType); + } + for (i = 0; i < viewTypes.length; i += 1) { + spec = viewSpecs[viewTypes[i]]; + if (spec) { + if (spec.singleUnit === unit) { + return spec; + } + } + } + return null; + }; + // Current Date + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.prev = function () { + this.unselect(); + this.dispatch({ type: 'PREV' }); + }; + CalendarApi.prototype.next = function () { + this.unselect(); + this.dispatch({ type: 'NEXT' }); + }; + CalendarApi.prototype.prevYear = function () { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.addYears(state.currentDate, -1), + }); + }; + CalendarApi.prototype.nextYear = function () { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.addYears(state.currentDate, 1), + }); + }; + CalendarApi.prototype.today = function () { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: getNow(state.calendarOptions.now, state.dateEnv), + }); + }; + CalendarApi.prototype.gotoDate = function (zonedDateInput) { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.createMarker(zonedDateInput), + }); + }; + CalendarApi.prototype.incrementDate = function (deltaInput) { + var state = this.getCurrentData(); + var delta = createDuration(deltaInput); + if (delta) { // else, warn about invalid input? + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.add(state.currentDate, delta), + }); + } + }; + // for external API + CalendarApi.prototype.getDate = function () { + var state = this.getCurrentData(); + return state.dateEnv.toDate(state.currentDate); + }; + // Date Formatting Utils + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.formatDate = function (d, formatter) { + var dateEnv = this.getCurrentData().dateEnv; + return dateEnv.format(dateEnv.createMarker(d), createFormatter(formatter)); + }; + // `settings` is for formatter AND isEndExclusive + CalendarApi.prototype.formatRange = function (d0, d1, settings) { + var dateEnv = this.getCurrentData().dateEnv; + return dateEnv.formatRange(dateEnv.createMarker(d0), dateEnv.createMarker(d1), createFormatter(settings), settings); + }; + CalendarApi.prototype.formatIso = function (d, omitTime) { + var dateEnv = this.getCurrentData().dateEnv; + return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime: omitTime }); + }; + // Date Selection / Event Selection / DayClick + // ----------------------------------------------------------------------------------------------------------------- + // this public method receives start/end dates in any format, with any timezone + // NOTE: args were changed from v3 + CalendarApi.prototype.select = function (dateOrObj, endDate) { + var selectionInput; + if (endDate == null) { + if (dateOrObj.start != null) { + selectionInput = dateOrObj; + } + else { + selectionInput = { + start: dateOrObj, + end: null, + }; + } + } + else { + selectionInput = { + start: dateOrObj, + end: endDate, + }; + } + var state = this.getCurrentData(); + var selection = parseDateSpan(selectionInput, state.dateEnv, createDuration({ days: 1 })); + if (selection) { // throw parse error otherwise? + this.dispatch({ type: 'SELECT_DATES', selection: selection }); + triggerDateSelect(selection, null, state); + } + }; + // public method + CalendarApi.prototype.unselect = function (pev) { + var state = this.getCurrentData(); + if (state.dateSelection) { + this.dispatch({ type: 'UNSELECT_DATES' }); + triggerDateUnselect(pev, state); + } + }; + // Public Events API + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.addEvent = function (eventInput, sourceInput) { + if (eventInput instanceof EventApi) { + var def = eventInput._def; + var instance = eventInput._instance; + var currentData = this.getCurrentData(); + // not already present? don't want to add an old snapshot + if (!currentData.eventStore.defs[def.defId]) { + this.dispatch({ + type: 'ADD_EVENTS', + eventStore: eventTupleToStore({ def: def, instance: instance }), // TODO: better util for two args? + }); + this.triggerEventAdd(eventInput); + } + return eventInput; + } + var state = this.getCurrentData(); + var eventSource; + if (sourceInput instanceof EventSourceApi) { + eventSource = sourceInput.internalEventSource; + } + else if (typeof sourceInput === 'boolean') { + if (sourceInput) { // true. part of the first event source + eventSource = hashValuesToArray(state.eventSources)[0]; + } + } + else if (sourceInput != null) { // an ID. accepts a number too + var sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function + if (!sourceApi) { + console.warn("Could not find an event source with ID \"" + sourceInput + "\""); // TODO: test + return null; + } + eventSource = sourceApi.internalEventSource; + } + var tuple = parseEvent(eventInput, eventSource, state, false); + if (tuple) { + var newEventApi = new EventApi(state, tuple.def, tuple.def.recurringDef ? null : tuple.instance); + this.dispatch({ + type: 'ADD_EVENTS', + eventStore: eventTupleToStore(tuple), + }); + this.triggerEventAdd(newEventApi); + return newEventApi; + } + return null; + }; + CalendarApi.prototype.triggerEventAdd = function (eventApi) { + var _this = this; + var emitter = this.getCurrentData().emitter; + emitter.trigger('eventAdd', { + event: eventApi, + relatedEvents: [], + revert: function () { + _this.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: eventApiToStore(eventApi), + }); + }, + }); + }; + // TODO: optimize + CalendarApi.prototype.getEventById = function (id) { + var state = this.getCurrentData(); + var _a = state.eventStore, defs = _a.defs, instances = _a.instances; + id = String(id); + for (var defId in defs) { + var def = defs[defId]; + if (def.publicId === id) { + if (def.recurringDef) { + return new EventApi(state, def, null); + } + for (var instanceId in instances) { + var instance = instances[instanceId]; + if (instance.defId === def.defId) { + return new EventApi(state, def, instance); + } + } + } + } + return null; + }; + CalendarApi.prototype.getEvents = function () { + var currentData = this.getCurrentData(); + return buildEventApis(currentData.eventStore, currentData); + }; + CalendarApi.prototype.removeAllEvents = function () { + this.dispatch({ type: 'REMOVE_ALL_EVENTS' }); + }; + // Public Event Sources API + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.getEventSources = function () { + var state = this.getCurrentData(); + var sourceHash = state.eventSources; + var sourceApis = []; + for (var internalId in sourceHash) { + sourceApis.push(new EventSourceApi(state, sourceHash[internalId])); + } + return sourceApis; + }; + CalendarApi.prototype.getEventSourceById = function (id) { + var state = this.getCurrentData(); + var sourceHash = state.eventSources; + id = String(id); + for (var sourceId in sourceHash) { + if (sourceHash[sourceId].publicId === id) { + return new EventSourceApi(state, sourceHash[sourceId]); + } + } + return null; + }; + CalendarApi.prototype.addEventSource = function (sourceInput) { + var state = this.getCurrentData(); + if (sourceInput instanceof EventSourceApi) { + // not already present? don't want to add an old snapshot + if (!state.eventSources[sourceInput.internalEventSource.sourceId]) { + this.dispatch({ + type: 'ADD_EVENT_SOURCES', + sources: [sourceInput.internalEventSource], + }); + } + return sourceInput; + } + var eventSource = parseEventSource(sourceInput, state); + if (eventSource) { // TODO: error otherwise? + this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] }); + return new EventSourceApi(state, eventSource); + } + return null; + }; + CalendarApi.prototype.removeAllEventSources = function () { + this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' }); + }; + CalendarApi.prototype.refetchEvents = function () { + this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true }); + }; + // Scroll + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.scrollToTime = function (timeInput) { + var time = createDuration(timeInput); + if (time) { + this.trigger('_scrollRequest', { time: time }); + } + }; + return CalendarApi; + }()); + + var EventApi = /** @class */ (function () { + // instance will be null if expressing a recurring event that has no current instances, + // OR if trying to validate an incoming external event that has no dates assigned + function EventApi(context, def, instance) { + this._context = context; + this._def = def; + this._instance = instance || null; + } + /* + TODO: make event struct more responsible for this + */ + EventApi.prototype.setProp = function (name, val) { + var _a, _b; + if (name in EVENT_DATE_REFINERS) { + console.warn('Could not set date-related prop \'name\'. Use one of the date-related methods instead.'); + // TODO: make proper aliasing system? + } + else if (name === 'id') { + val = EVENT_NON_DATE_REFINERS[name](val); + this.mutate({ + standardProps: { publicId: val }, // hardcoded internal name + }); + } + else if (name in EVENT_NON_DATE_REFINERS) { + val = EVENT_NON_DATE_REFINERS[name](val); + this.mutate({ + standardProps: (_a = {}, _a[name] = val, _a), + }); + } + else if (name in EVENT_UI_REFINERS) { + var ui = EVENT_UI_REFINERS[name](val); + if (name === 'color') { + ui = { backgroundColor: val, borderColor: val }; + } + else if (name === 'editable') { + ui = { startEditable: val, durationEditable: val }; + } + else { + ui = (_b = {}, _b[name] = val, _b); + } + this.mutate({ + standardProps: { ui: ui }, + }); + } + else { + console.warn("Could not set prop '" + name + "'. Use setExtendedProp instead."); + } + }; + EventApi.prototype.setExtendedProp = function (name, val) { + var _a; + this.mutate({ + extendedProps: (_a = {}, _a[name] = val, _a), + }); + }; + EventApi.prototype.setStart = function (startInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = this._context.dateEnv; + var start = dateEnv.createMarker(startInput); + if (start && this._instance) { // TODO: warning if parsed bad + var instanceRange = this._instance.range; + var startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); // what if parsed bad!? + if (options.maintainDuration) { + this.mutate({ datesDelta: startDelta }); + } + else { + this.mutate({ startDelta: startDelta }); + } + } + }; + EventApi.prototype.setEnd = function (endInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = this._context.dateEnv; + var end; + if (endInput != null) { + end = dateEnv.createMarker(endInput); + if (!end) { + return; // TODO: warning if parsed bad + } + } + if (this._instance) { + if (end) { + var endDelta = diffDates(this._instance.range.end, end, dateEnv, options.granularity); + this.mutate({ endDelta: endDelta }); + } + else { + this.mutate({ standardProps: { hasEnd: false } }); + } + } + }; + EventApi.prototype.setDates = function (startInput, endInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = this._context.dateEnv; + var standardProps = { allDay: options.allDay }; + var start = dateEnv.createMarker(startInput); + var end; + if (!start) { + return; // TODO: warning if parsed bad + } + if (endInput != null) { + end = dateEnv.createMarker(endInput); + if (!end) { // TODO: warning if parsed bad + return; + } + } + if (this._instance) { + var instanceRange = this._instance.range; + // when computing the diff for an event being converted to all-day, + // compute diff off of the all-day values the way event-mutation does. + if (options.allDay === true) { + instanceRange = computeAlignedDayRange(instanceRange); + } + var startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); + if (end) { + var endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity); + if (durationsEqual(startDelta, endDelta)) { + this.mutate({ datesDelta: startDelta, standardProps: standardProps }); + } + else { + this.mutate({ startDelta: startDelta, endDelta: endDelta, standardProps: standardProps }); + } + } + else { // means "clear the end" + standardProps.hasEnd = false; + this.mutate({ datesDelta: startDelta, standardProps: standardProps }); + } + } + }; + EventApi.prototype.moveStart = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ startDelta: delta }); + } + }; + EventApi.prototype.moveEnd = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ endDelta: delta }); + } + }; + EventApi.prototype.moveDates = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ datesDelta: delta }); + } + }; + EventApi.prototype.setAllDay = function (allDay, options) { + if (options === void 0) { options = {}; } + var standardProps = { allDay: allDay }; + var maintainDuration = options.maintainDuration; + if (maintainDuration == null) { + maintainDuration = this._context.options.allDayMaintainDuration; + } + if (this._def.allDay !== allDay) { + standardProps.hasEnd = maintainDuration; + } + this.mutate({ standardProps: standardProps }); + }; + EventApi.prototype.formatRange = function (formatInput) { + var dateEnv = this._context.dateEnv; + var instance = this._instance; + var formatter = createFormatter(formatInput); + if (this._def.hasEnd) { + return dateEnv.formatRange(instance.range.start, instance.range.end, formatter, { + forcedStartTzo: instance.forcedStartTzo, + forcedEndTzo: instance.forcedEndTzo, + }); + } + return dateEnv.format(instance.range.start, formatter, { + forcedTzo: instance.forcedStartTzo, + }); + }; + EventApi.prototype.mutate = function (mutation) { + var instance = this._instance; + if (instance) { + var def = this._def; + var context_1 = this._context; + var eventStore_1 = context_1.getCurrentData().eventStore; + var relevantEvents = getRelevantEvents(eventStore_1, instance.instanceId); + var eventConfigBase = { + '': { + display: '', + startEditable: true, + durationEditable: true, + constraints: [], + overlap: null, + allows: [], + backgroundColor: '', + borderColor: '', + textColor: '', + classNames: [], + }, + }; + relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context_1); + var oldEvent = new EventApi(context_1, def, instance); // snapshot + this._def = relevantEvents.defs[def.defId]; + this._instance = relevantEvents.instances[instance.instanceId]; + context_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, + }); + context_1.emitter.trigger('eventChange', { + oldEvent: oldEvent, + event: this, + relatedEvents: buildEventApis(relevantEvents, context_1, instance), + revert: function () { + context_1.dispatch({ + type: 'RESET_EVENTS', + eventStore: eventStore_1, + }); + }, + }); + } + }; + EventApi.prototype.remove = function () { + var context = this._context; + var asStore = eventApiToStore(this); + context.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: asStore, + }); + context.emitter.trigger('eventRemove', { + event: this, + relatedEvents: [], + revert: function () { + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: asStore, + }); + }, + }); + }; + Object.defineProperty(EventApi.prototype, "source", { + get: function () { + var sourceId = this._def.sourceId; + if (sourceId) { + return new EventSourceApi(this._context, this._context.getCurrentData().eventSources[sourceId]); + } + return null; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "start", { + get: function () { + return this._instance ? + this._context.dateEnv.toDate(this._instance.range.start) : + null; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "end", { + get: function () { + return (this._instance && this._def.hasEnd) ? + this._context.dateEnv.toDate(this._instance.range.end) : + null; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "startStr", { + get: function () { + var instance = this._instance; + if (instance) { + return this._context.dateEnv.formatIso(instance.range.start, { + omitTime: this._def.allDay, + forcedTzo: instance.forcedStartTzo, + }); + } + return ''; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "endStr", { + get: function () { + var instance = this._instance; + if (instance && this._def.hasEnd) { + return this._context.dateEnv.formatIso(instance.range.end, { + omitTime: this._def.allDay, + forcedTzo: instance.forcedEndTzo, + }); + } + return ''; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "id", { + // computable props that all access the def + // TODO: find a TypeScript-compatible way to do this at scale + get: function () { return this._def.publicId; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "groupId", { + get: function () { return this._def.groupId; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "allDay", { + get: function () { return this._def.allDay; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "title", { + get: function () { return this._def.title; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "url", { + get: function () { return this._def.url; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "display", { + get: function () { return this._def.ui.display || 'auto'; } // bad. just normalize the type earlier + , + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "startEditable", { + get: function () { return this._def.ui.startEditable; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "durationEditable", { + get: function () { return this._def.ui.durationEditable; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "constraint", { + get: function () { return this._def.ui.constraints[0] || null; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "overlap", { + get: function () { return this._def.ui.overlap; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "allow", { + get: function () { return this._def.ui.allows[0] || null; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "backgroundColor", { + get: function () { return this._def.ui.backgroundColor; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "borderColor", { + get: function () { return this._def.ui.borderColor; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "textColor", { + get: function () { return this._def.ui.textColor; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "classNames", { + // NOTE: user can't modify these because Object.freeze was called in event-def parsing + get: function () { return this._def.ui.classNames; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "extendedProps", { + get: function () { return this._def.extendedProps; }, + enumerable: false, + configurable: true + }); + EventApi.prototype.toPlainObject = function (settings) { + if (settings === void 0) { settings = {}; } + var def = this._def; + var ui = def.ui; + var _a = this, startStr = _a.startStr, endStr = _a.endStr; + var res = {}; + if (def.title) { + res.title = def.title; + } + if (startStr) { + res.start = startStr; + } + if (endStr) { + res.end = endStr; + } + if (def.publicId) { + res.id = def.publicId; + } + if (def.groupId) { + res.groupId = def.groupId; + } + if (def.url) { + res.url = def.url; + } + if (ui.display && ui.display !== 'auto') { + res.display = ui.display; + } + // TODO: what about recurring-event properties??? + // TODO: include startEditable/durationEditable/constraint/overlap/allow + if (settings.collapseColor && ui.backgroundColor && ui.backgroundColor === ui.borderColor) { + res.color = ui.backgroundColor; + } + else { + if (ui.backgroundColor) { + res.backgroundColor = ui.backgroundColor; + } + if (ui.borderColor) { + res.borderColor = ui.borderColor; + } + } + if (ui.textColor) { + res.textColor = ui.textColor; + } + if (ui.classNames.length) { + res.classNames = ui.classNames; + } + if (Object.keys(def.extendedProps).length) { + if (settings.collapseExtendedProps) { + __assign(res, def.extendedProps); + } + else { + res.extendedProps = def.extendedProps; + } + } + return res; + }; + EventApi.prototype.toJSON = function () { + return this.toPlainObject(); + }; + return EventApi; + }()); + function eventApiToStore(eventApi) { + var _a, _b; + var def = eventApi._def; + var instance = eventApi._instance; + return { + defs: (_a = {}, _a[def.defId] = def, _a), + instances: instance + ? (_b = {}, _b[instance.instanceId] = instance, _b) : {}, + }; + } + function buildEventApis(eventStore, context, excludeInstance) { + var defs = eventStore.defs, instances = eventStore.instances; + var eventApis = []; + var excludeInstanceId = excludeInstance ? excludeInstance.instanceId : ''; + for (var id in instances) { + var instance = instances[id]; + var def = defs[instance.defId]; + if (instance.instanceId !== excludeInstanceId) { + eventApis.push(new EventApi(context, def, instance)); + } + } + return eventApis; + } + + var calendarSystemClassMap = {}; + function registerCalendarSystem(name, theClass) { + calendarSystemClassMap[name] = theClass; + } + function createCalendarSystem(name) { + return new calendarSystemClassMap[name](); + } + var GregorianCalendarSystem = /** @class */ (function () { + function GregorianCalendarSystem() { + } + GregorianCalendarSystem.prototype.getMarkerYear = function (d) { + return d.getUTCFullYear(); + }; + GregorianCalendarSystem.prototype.getMarkerMonth = function (d) { + return d.getUTCMonth(); + }; + GregorianCalendarSystem.prototype.getMarkerDay = function (d) { + return d.getUTCDate(); + }; + GregorianCalendarSystem.prototype.arrayToMarker = function (arr) { + return arrayToUtcDate(arr); + }; + GregorianCalendarSystem.prototype.markerToArray = function (marker) { + return dateToUtcArray(marker); + }; + return GregorianCalendarSystem; + }()); + registerCalendarSystem('gregory', GregorianCalendarSystem); + + var ISO_RE = /^\s*(\d{4})(-?(\d{2})(-?(\d{2})([T ](\d{2}):?(\d{2})(:?(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/; + function parse(str) { + var m = ISO_RE.exec(str); + if (m) { + var marker = new Date(Date.UTC(Number(m[1]), m[3] ? Number(m[3]) - 1 : 0, Number(m[5] || 1), Number(m[7] || 0), Number(m[8] || 0), Number(m[10] || 0), m[12] ? Number("0." + m[12]) * 1000 : 0)); + if (isValidDate(marker)) { + var timeZoneOffset = null; + if (m[13]) { + timeZoneOffset = (m[15] === '-' ? -1 : 1) * (Number(m[16] || 0) * 60 + + Number(m[18] || 0)); + } + return { + marker: marker, + isTimeUnspecified: !m[6], + timeZoneOffset: timeZoneOffset, + }; + } + } + return null; + } + + var DateEnv = /** @class */ (function () { + function DateEnv(settings) { + var timeZone = this.timeZone = settings.timeZone; + var isNamedTimeZone = timeZone !== 'local' && timeZone !== 'UTC'; + if (settings.namedTimeZoneImpl && isNamedTimeZone) { + this.namedTimeZoneImpl = new settings.namedTimeZoneImpl(timeZone); + } + this.canComputeOffset = Boolean(!isNamedTimeZone || this.namedTimeZoneImpl); + this.calendarSystem = createCalendarSystem(settings.calendarSystem); + this.locale = settings.locale; + this.weekDow = settings.locale.week.dow; + this.weekDoy = settings.locale.week.doy; + if (settings.weekNumberCalculation === 'ISO') { + this.weekDow = 1; + this.weekDoy = 4; + } + if (typeof settings.firstDay === 'number') { + this.weekDow = settings.firstDay; + } + if (typeof settings.weekNumberCalculation === 'function') { + this.weekNumberFunc = settings.weekNumberCalculation; + } + this.weekText = settings.weekText != null ? settings.weekText : settings.locale.options.weekText; + this.cmdFormatter = settings.cmdFormatter; + this.defaultSeparator = settings.defaultSeparator; + } + // Creating / Parsing + DateEnv.prototype.createMarker = function (input) { + var meta = this.createMarkerMeta(input); + if (meta === null) { + return null; + } + return meta.marker; + }; + DateEnv.prototype.createNowMarker = function () { + if (this.canComputeOffset) { + return this.timestampToMarker(new Date().valueOf()); + } + // if we can't compute the current date val for a timezone, + // better to give the current local date vals than UTC + return arrayToUtcDate(dateToLocalArray(new Date())); + }; + DateEnv.prototype.createMarkerMeta = function (input) { + if (typeof input === 'string') { + return this.parse(input); + } + var marker = null; + if (typeof input === 'number') { + marker = this.timestampToMarker(input); + } + else if (input instanceof Date) { + input = input.valueOf(); + if (!isNaN(input)) { + marker = this.timestampToMarker(input); + } + } + else if (Array.isArray(input)) { + marker = arrayToUtcDate(input); + } + if (marker === null || !isValidDate(marker)) { + return null; + } + return { marker: marker, isTimeUnspecified: false, forcedTzo: null }; + }; + DateEnv.prototype.parse = function (s) { + var parts = parse(s); + if (parts === null) { + return null; + } + var marker = parts.marker; + var forcedTzo = null; + if (parts.timeZoneOffset !== null) { + if (this.canComputeOffset) { + marker = this.timestampToMarker(marker.valueOf() - parts.timeZoneOffset * 60 * 1000); + } + else { + forcedTzo = parts.timeZoneOffset; + } + } + return { marker: marker, isTimeUnspecified: parts.isTimeUnspecified, forcedTzo: forcedTzo }; + }; + // Accessors + DateEnv.prototype.getYear = function (marker) { + return this.calendarSystem.getMarkerYear(marker); + }; + DateEnv.prototype.getMonth = function (marker) { + return this.calendarSystem.getMarkerMonth(marker); + }; + // Adding / Subtracting + DateEnv.prototype.add = function (marker, dur) { + var a = this.calendarSystem.markerToArray(marker); + a[0] += dur.years; + a[1] += dur.months; + a[2] += dur.days; + a[6] += dur.milliseconds; + return this.calendarSystem.arrayToMarker(a); + }; + DateEnv.prototype.subtract = function (marker, dur) { + var a = this.calendarSystem.markerToArray(marker); + a[0] -= dur.years; + a[1] -= dur.months; + a[2] -= dur.days; + a[6] -= dur.milliseconds; + return this.calendarSystem.arrayToMarker(a); + }; + DateEnv.prototype.addYears = function (marker, n) { + var a = this.calendarSystem.markerToArray(marker); + a[0] += n; + return this.calendarSystem.arrayToMarker(a); + }; + DateEnv.prototype.addMonths = function (marker, n) { + var a = this.calendarSystem.markerToArray(marker); + a[1] += n; + return this.calendarSystem.arrayToMarker(a); + }; + // Diffing Whole Units + DateEnv.prototype.diffWholeYears = function (m0, m1) { + var calendarSystem = this.calendarSystem; + if (timeAsMs(m0) === timeAsMs(m1) && + calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1) && + calendarSystem.getMarkerMonth(m0) === calendarSystem.getMarkerMonth(m1)) { + return calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0); + } + return null; + }; + DateEnv.prototype.diffWholeMonths = function (m0, m1) { + var calendarSystem = this.calendarSystem; + if (timeAsMs(m0) === timeAsMs(m1) && + calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1)) { + return (calendarSystem.getMarkerMonth(m1) - calendarSystem.getMarkerMonth(m0)) + + (calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0)) * 12; + } + return null; + }; + // Range / Duration + DateEnv.prototype.greatestWholeUnit = function (m0, m1) { + var n = this.diffWholeYears(m0, m1); + if (n !== null) { + return { unit: 'year', value: n }; + } + n = this.diffWholeMonths(m0, m1); + if (n !== null) { + return { unit: 'month', value: n }; + } + n = diffWholeWeeks(m0, m1); + if (n !== null) { + return { unit: 'week', value: n }; + } + n = diffWholeDays(m0, m1); + if (n !== null) { + return { unit: 'day', value: n }; + } + n = diffHours(m0, m1); + if (isInt(n)) { + return { unit: 'hour', value: n }; + } + n = diffMinutes(m0, m1); + if (isInt(n)) { + return { unit: 'minute', value: n }; + } + n = diffSeconds(m0, m1); + if (isInt(n)) { + return { unit: 'second', value: n }; + } + return { unit: 'millisecond', value: m1.valueOf() - m0.valueOf() }; + }; + DateEnv.prototype.countDurationsBetween = function (m0, m1, d) { + // TODO: can use greatestWholeUnit + var diff; + if (d.years) { + diff = this.diffWholeYears(m0, m1); + if (diff !== null) { + return diff / asRoughYears(d); + } + } + if (d.months) { + diff = this.diffWholeMonths(m0, m1); + if (diff !== null) { + return diff / asRoughMonths(d); + } + } + if (d.days) { + diff = diffWholeDays(m0, m1); + if (diff !== null) { + return diff / asRoughDays(d); + } + } + return (m1.valueOf() - m0.valueOf()) / asRoughMs(d); + }; + // Start-Of + // these DON'T return zoned-dates. only UTC start-of dates + DateEnv.prototype.startOf = function (m, unit) { + if (unit === 'year') { + return this.startOfYear(m); + } + if (unit === 'month') { + return this.startOfMonth(m); + } + if (unit === 'week') { + return this.startOfWeek(m); + } + if (unit === 'day') { + return startOfDay(m); + } + if (unit === 'hour') { + return startOfHour(m); + } + if (unit === 'minute') { + return startOfMinute(m); + } + if (unit === 'second') { + return startOfSecond(m); + } + return null; + }; + DateEnv.prototype.startOfYear = function (m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + ]); + }; + DateEnv.prototype.startOfMonth = function (m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + this.calendarSystem.getMarkerMonth(m), + ]); + }; + DateEnv.prototype.startOfWeek = function (m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + this.calendarSystem.getMarkerMonth(m), + m.getUTCDate() - ((m.getUTCDay() - this.weekDow + 7) % 7), + ]); + }; + // Week Number + DateEnv.prototype.computeWeekNumber = function (marker) { + if (this.weekNumberFunc) { + return this.weekNumberFunc(this.toDate(marker)); + } + return weekOfYear(marker, this.weekDow, this.weekDoy); + }; + // TODO: choke on timeZoneName: long + DateEnv.prototype.format = function (marker, formatter, dateOptions) { + if (dateOptions === void 0) { dateOptions = {}; } + return formatter.format({ + marker: marker, + timeZoneOffset: dateOptions.forcedTzo != null ? + dateOptions.forcedTzo : + this.offsetForMarker(marker), + }, this); + }; + DateEnv.prototype.formatRange = function (start, end, formatter, dateOptions) { + if (dateOptions === void 0) { dateOptions = {}; } + if (dateOptions.isEndExclusive) { + end = addMs(end, -1); + } + return formatter.formatRange({ + marker: start, + timeZoneOffset: dateOptions.forcedStartTzo != null ? + dateOptions.forcedStartTzo : + this.offsetForMarker(start), + }, { + marker: end, + timeZoneOffset: dateOptions.forcedEndTzo != null ? + dateOptions.forcedEndTzo : + this.offsetForMarker(end), + }, this, dateOptions.defaultSeparator); + }; + /* + DUMB: the omitTime arg is dumb. if we omit the time, we want to omit the timezone offset. and if we do that, + might as well use buildIsoString or some other util directly + */ + DateEnv.prototype.formatIso = function (marker, extraOptions) { + if (extraOptions === void 0) { extraOptions = {}; } + var timeZoneOffset = null; + if (!extraOptions.omitTimeZoneOffset) { + if (extraOptions.forcedTzo != null) { + timeZoneOffset = extraOptions.forcedTzo; + } + else { + timeZoneOffset = this.offsetForMarker(marker); + } + } + return buildIsoString(marker, timeZoneOffset, extraOptions.omitTime); + }; + // TimeZone + DateEnv.prototype.timestampToMarker = function (ms) { + if (this.timeZone === 'local') { + return arrayToUtcDate(dateToLocalArray(new Date(ms))); + } + if (this.timeZone === 'UTC' || !this.namedTimeZoneImpl) { + return new Date(ms); + } + return arrayToUtcDate(this.namedTimeZoneImpl.timestampToArray(ms)); + }; + DateEnv.prototype.offsetForMarker = function (m) { + if (this.timeZone === 'local') { + return -arrayToLocalDate(dateToUtcArray(m)).getTimezoneOffset(); // convert "inverse" offset to "normal" offset + } + if (this.timeZone === 'UTC') { + return 0; + } + if (this.namedTimeZoneImpl) { + return this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)); + } + return null; + }; + // Conversion + DateEnv.prototype.toDate = function (m, forcedTzo) { + if (this.timeZone === 'local') { + return arrayToLocalDate(dateToUtcArray(m)); + } + if (this.timeZone === 'UTC') { + return new Date(m.valueOf()); // make sure it's a copy + } + if (!this.namedTimeZoneImpl) { + return new Date(m.valueOf() - (forcedTzo || 0)); + } + return new Date(m.valueOf() - + this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)) * 1000 * 60); + }; + return DateEnv; + }()); + + var globalLocales = []; + + var RAW_EN_LOCALE = { + code: 'en', + week: { + dow: 0, + doy: 4, // 4 days need to be within the year to be considered the first week + }, + direction: 'ltr', + buttonText: { + prev: 'prev', + next: 'next', + prevYear: 'prev year', + nextYear: 'next year', + year: 'year', + today: 'today', + month: 'month', + week: 'week', + day: 'day', + list: 'list', + }, + weekText: 'W', + allDayText: 'all-day', + moreLinkText: 'more', + noEventsText: 'No events to display', + }; + function organizeRawLocales(explicitRawLocales) { + var defaultCode = explicitRawLocales.length > 0 ? explicitRawLocales[0].code : 'en'; + var allRawLocales = globalLocales.concat(explicitRawLocales); + var rawLocaleMap = { + en: RAW_EN_LOCALE, // necessary? + }; + for (var _i = 0, allRawLocales_1 = allRawLocales; _i < allRawLocales_1.length; _i++) { + var rawLocale = allRawLocales_1[_i]; + rawLocaleMap[rawLocale.code] = rawLocale; + } + return { + map: rawLocaleMap, + defaultCode: defaultCode, + }; + } + function buildLocale(inputSingular, available) { + if (typeof inputSingular === 'object' && !Array.isArray(inputSingular)) { + return parseLocale(inputSingular.code, [inputSingular.code], inputSingular); + } + return queryLocale(inputSingular, available); + } + function queryLocale(codeArg, available) { + var codes = [].concat(codeArg || []); // will convert to array + var raw = queryRawLocale(codes, available) || RAW_EN_LOCALE; + return parseLocale(codeArg, codes, raw); + } + function queryRawLocale(codes, available) { + for (var i = 0; i < codes.length; i += 1) { + var parts = codes[i].toLocaleLowerCase().split('-'); + for (var j = parts.length; j > 0; j -= 1) { + var simpleId = parts.slice(0, j).join('-'); + if (available[simpleId]) { + return available[simpleId]; + } + } + } + return null; + } + function parseLocale(codeArg, codes, raw) { + var merged = mergeProps([RAW_EN_LOCALE, raw], ['buttonText']); + delete merged.code; // don't want this part of the options + var week = merged.week; + delete merged.week; + return { + codeArg: codeArg, + codes: codes, + week: week, + simpleNumberFormat: new Intl.NumberFormat(codeArg), + options: merged, + }; + } + + function formatDate(dateInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = buildDateEnv$1(options); + var formatter = createFormatter(options); + var dateMeta = dateEnv.createMarkerMeta(dateInput); + if (!dateMeta) { // TODO: warning? + return ''; + } + return dateEnv.format(dateMeta.marker, formatter, { + forcedTzo: dateMeta.forcedTzo, + }); + } + function formatRange(startInput, endInput, options) { + var dateEnv = buildDateEnv$1(typeof options === 'object' && options ? options : {}); // pass in if non-null object + var formatter = createFormatter(options); + var startMeta = dateEnv.createMarkerMeta(startInput); + var endMeta = dateEnv.createMarkerMeta(endInput); + if (!startMeta || !endMeta) { // TODO: warning? + return ''; + } + return dateEnv.formatRange(startMeta.marker, endMeta.marker, formatter, { + forcedStartTzo: startMeta.forcedTzo, + forcedEndTzo: endMeta.forcedTzo, + isEndExclusive: options.isEndExclusive, + defaultSeparator: BASE_OPTION_DEFAULTS.defaultRangeSeparator, + }); + } + // TODO: more DRY and optimized + function buildDateEnv$1(settings) { + var locale = buildLocale(settings.locale || 'en', organizeRawLocales([]).map); // TODO: don't hardcode 'en' everywhere + return new DateEnv(__assign(__assign({ timeZone: BASE_OPTION_DEFAULTS.timeZone, calendarSystem: 'gregory' }, settings), { locale: locale })); + } + + var DEF_DEFAULTS = { + startTime: '09:00', + endTime: '17:00', + daysOfWeek: [1, 2, 3, 4, 5], + display: 'inverse-background', + classNames: 'fc-non-business', + groupId: '_businessHours', // so multiple defs get grouped + }; + /* + TODO: pass around as EventDefHash!!! + */ + function parseBusinessHours(input, context) { + return parseEvents(refineInputs(input), null, context); + } + function refineInputs(input) { + var rawDefs; + if (input === true) { + rawDefs = [{}]; // will get DEF_DEFAULTS verbatim + } + else if (Array.isArray(input)) { + // if specifying an array, every sub-definition NEEDS a day-of-week + rawDefs = input.filter(function (rawDef) { return rawDef.daysOfWeek; }); + } + else if (typeof input === 'object' && input) { // non-null object + rawDefs = [input]; + } + else { // is probably false + rawDefs = []; + } + rawDefs = rawDefs.map(function (rawDef) { return (__assign(__assign({}, DEF_DEFAULTS), rawDef)); }); + return rawDefs; + } + + function pointInsideRect(point, rect) { + return point.left >= rect.left && + point.left < rect.right && + point.top >= rect.top && + point.top < rect.bottom; + } + // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false + function intersectRects(rect1, rect2) { + var res = { + left: Math.max(rect1.left, rect2.left), + right: Math.min(rect1.right, rect2.right), + top: Math.max(rect1.top, rect2.top), + bottom: Math.min(rect1.bottom, rect2.bottom), + }; + if (res.left < res.right && res.top < res.bottom) { + return res; + } + return false; + } + function translateRect(rect, deltaX, deltaY) { + return { + left: rect.left + deltaX, + right: rect.right + deltaX, + top: rect.top + deltaY, + bottom: rect.bottom + deltaY, + }; + } + // Returns a new point that will have been moved to reside within the given rectangle + function constrainPoint(point, rect) { + return { + left: Math.min(Math.max(point.left, rect.left), rect.right), + top: Math.min(Math.max(point.top, rect.top), rect.bottom), + }; + } + // Returns a point that is the center of the given rectangle + function getRectCenter(rect) { + return { + left: (rect.left + rect.right) / 2, + top: (rect.top + rect.bottom) / 2, + }; + } + // Subtracts point2's coordinates from point1's coordinates, returning a delta + function diffPoints(point1, point2) { + return { + left: point1.left - point2.left, + top: point1.top - point2.top, + }; + } + + var canVGrowWithinCell; + function getCanVGrowWithinCell() { + if (canVGrowWithinCell == null) { + canVGrowWithinCell = computeCanVGrowWithinCell(); + } + return canVGrowWithinCell; + } + function computeCanVGrowWithinCell() { + // for SSR, because this function is call immediately at top-level + // TODO: just make this logic execute top-level, immediately, instead of doing lazily + if (typeof document === 'undefined') { + return true; + } + var el = document.createElement('div'); + el.style.position = 'absolute'; + el.style.top = '0px'; + el.style.left = '0px'; + el.innerHTML = '
'; + el.querySelector('table').style.height = '100px'; + el.querySelector('div').style.height = '100%'; + document.body.appendChild(el); + var div = el.querySelector('div'); + var possible = div.offsetHeight > 0; + document.body.removeChild(el); + return possible; + } + + var EMPTY_EVENT_STORE = createEmptyEventStore(); // for purecomponents. TODO: keep elsewhere + var Splitter = /** @class */ (function () { + function Splitter() { + this.getKeysForEventDefs = memoize(this._getKeysForEventDefs); + this.splitDateSelection = memoize(this._splitDateSpan); + this.splitEventStore = memoize(this._splitEventStore); + this.splitIndividualUi = memoize(this._splitIndividualUi); + this.splitEventDrag = memoize(this._splitInteraction); + this.splitEventResize = memoize(this._splitInteraction); + this.eventUiBuilders = {}; // TODO: typescript protection + } + Splitter.prototype.splitProps = function (props) { + var _this = this; + var keyInfos = this.getKeyInfo(props); + var defKeys = this.getKeysForEventDefs(props.eventStore); + var dateSelections = this.splitDateSelection(props.dateSelection); + var individualUi = this.splitIndividualUi(props.eventUiBases, defKeys); // the individual *bases* + var eventStores = this.splitEventStore(props.eventStore, defKeys); + var eventDrags = this.splitEventDrag(props.eventDrag); + var eventResizes = this.splitEventResize(props.eventResize); + var splitProps = {}; + this.eventUiBuilders = mapHash(keyInfos, function (info, key) { return _this.eventUiBuilders[key] || memoize(buildEventUiForKey); }); + for (var key in keyInfos) { + var keyInfo = keyInfos[key]; + var eventStore = eventStores[key] || EMPTY_EVENT_STORE; + var buildEventUi = this.eventUiBuilders[key]; + splitProps[key] = { + businessHours: keyInfo.businessHours || props.businessHours, + dateSelection: dateSelections[key] || null, + eventStore: eventStore, + eventUiBases: buildEventUi(props.eventUiBases[''], keyInfo.ui, individualUi[key]), + eventSelection: eventStore.instances[props.eventSelection] ? props.eventSelection : '', + eventDrag: eventDrags[key] || null, + eventResize: eventResizes[key] || null, + }; + } + return splitProps; + }; + Splitter.prototype._splitDateSpan = function (dateSpan) { + var dateSpans = {}; + if (dateSpan) { + var keys = this.getKeysForDateSpan(dateSpan); + for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { + var key = keys_1[_i]; + dateSpans[key] = dateSpan; + } + } + return dateSpans; + }; + Splitter.prototype._getKeysForEventDefs = function (eventStore) { + var _this = this; + return mapHash(eventStore.defs, function (eventDef) { return _this.getKeysForEventDef(eventDef); }); + }; + Splitter.prototype._splitEventStore = function (eventStore, defKeys) { + var defs = eventStore.defs, instances = eventStore.instances; + var splitStores = {}; + for (var defId in defs) { + for (var _i = 0, _a = defKeys[defId]; _i < _a.length; _i++) { + var key = _a[_i]; + if (!splitStores[key]) { + splitStores[key] = createEmptyEventStore(); + } + splitStores[key].defs[defId] = defs[defId]; + } + } + for (var instanceId in instances) { + var instance = instances[instanceId]; + for (var _b = 0, _c = defKeys[instance.defId]; _b < _c.length; _b++) { + var key = _c[_b]; + if (splitStores[key]) { // must have already been created + splitStores[key].instances[instanceId] = instance; + } + } + } + return splitStores; + }; + Splitter.prototype._splitIndividualUi = function (eventUiBases, defKeys) { + var splitHashes = {}; + for (var defId in eventUiBases) { + if (defId) { // not the '' key + for (var _i = 0, _a = defKeys[defId]; _i < _a.length; _i++) { + var key = _a[_i]; + if (!splitHashes[key]) { + splitHashes[key] = {}; + } + splitHashes[key][defId] = eventUiBases[defId]; + } + } + } + return splitHashes; + }; + Splitter.prototype._splitInteraction = function (interaction) { + var splitStates = {}; + if (interaction) { + var affectedStores_1 = this._splitEventStore(interaction.affectedEvents, this._getKeysForEventDefs(interaction.affectedEvents)); + // can't rely on defKeys because event data is mutated + var mutatedKeysByDefId = this._getKeysForEventDefs(interaction.mutatedEvents); + var mutatedStores_1 = this._splitEventStore(interaction.mutatedEvents, mutatedKeysByDefId); + var populate = function (key) { + if (!splitStates[key]) { + splitStates[key] = { + affectedEvents: affectedStores_1[key] || EMPTY_EVENT_STORE, + mutatedEvents: mutatedStores_1[key] || EMPTY_EVENT_STORE, + isEvent: interaction.isEvent, + }; + } + }; + for (var key in affectedStores_1) { + populate(key); + } + for (var key in mutatedStores_1) { + populate(key); + } + } + return splitStates; + }; + return Splitter; + }()); + function buildEventUiForKey(allUi, eventUiForKey, individualUi) { + var baseParts = []; + if (allUi) { + baseParts.push(allUi); + } + if (eventUiForKey) { + baseParts.push(eventUiForKey); + } + var stuff = { + '': combineEventUis(baseParts), + }; + if (individualUi) { + __assign(stuff, individualUi); + } + return stuff; + } + + function getDateMeta(date, todayRange, nowDate, dateProfile) { + return { + dow: date.getUTCDay(), + isDisabled: Boolean(dateProfile && !rangeContainsMarker(dateProfile.activeRange, date)), + isOther: Boolean(dateProfile && !rangeContainsMarker(dateProfile.currentRange, date)), + isToday: Boolean(todayRange && rangeContainsMarker(todayRange, date)), + isPast: Boolean(nowDate ? (date < nowDate) : todayRange ? (date < todayRange.start) : false), + isFuture: Boolean(nowDate ? (date > nowDate) : todayRange ? (date >= todayRange.end) : false), + }; + } + function getDayClassNames(meta, theme) { + var classNames = [ + 'fc-day', + "fc-day-" + DAY_IDS[meta.dow], + ]; + if (meta.isDisabled) { + classNames.push('fc-day-disabled'); + } + else { + if (meta.isToday) { + classNames.push('fc-day-today'); + classNames.push(theme.getClass('today')); + } + if (meta.isPast) { + classNames.push('fc-day-past'); + } + if (meta.isFuture) { + classNames.push('fc-day-future'); + } + if (meta.isOther) { + classNames.push('fc-day-other'); + } + } + return classNames; + } + function getSlotClassNames(meta, theme) { + var classNames = [ + 'fc-slot', + "fc-slot-" + DAY_IDS[meta.dow], + ]; + if (meta.isDisabled) { + classNames.push('fc-slot-disabled'); + } + else { + if (meta.isToday) { + classNames.push('fc-slot-today'); + classNames.push(theme.getClass('today')); + } + if (meta.isPast) { + classNames.push('fc-slot-past'); + } + if (meta.isFuture) { + classNames.push('fc-slot-future'); + } + } + return classNames; + } + + function buildNavLinkData(date, type) { + if (type === void 0) { type = 'day'; } + return JSON.stringify({ + date: formatDayString(date), + type: type, + }); + } + + var _isRtlScrollbarOnLeft = null; + function getIsRtlScrollbarOnLeft() { + if (_isRtlScrollbarOnLeft === null) { + _isRtlScrollbarOnLeft = computeIsRtlScrollbarOnLeft(); + } + return _isRtlScrollbarOnLeft; + } + function computeIsRtlScrollbarOnLeft() { + var outerEl = document.createElement('div'); + applyStyle(outerEl, { + position: 'absolute', + top: -1000, + left: 0, + border: 0, + padding: 0, + overflow: 'scroll', + direction: 'rtl', + }); + outerEl.innerHTML = '
'; + document.body.appendChild(outerEl); + var innerEl = outerEl.firstChild; + var res = innerEl.getBoundingClientRect().left > outerEl.getBoundingClientRect().left; + removeElement(outerEl); + return res; + } + + var _scrollbarWidths; + function getScrollbarWidths() { + if (!_scrollbarWidths) { + _scrollbarWidths = computeScrollbarWidths(); + } + return _scrollbarWidths; + } + function computeScrollbarWidths() { + var el = document.createElement('div'); + el.style.overflow = 'scroll'; + el.style.position = 'absolute'; + el.style.top = '-9999px'; + el.style.left = '-9999px'; + document.body.appendChild(el); + var res = computeScrollbarWidthsForEl(el); + document.body.removeChild(el); + return res; + } + // WARNING: will include border + function computeScrollbarWidthsForEl(el) { + return { + x: el.offsetHeight - el.clientHeight, + y: el.offsetWidth - el.clientWidth, + }; + } + + function computeEdges(el, getPadding) { + if (getPadding === void 0) { getPadding = false; } + var computedStyle = window.getComputedStyle(el); + var borderLeft = parseInt(computedStyle.borderLeftWidth, 10) || 0; + var borderRight = parseInt(computedStyle.borderRightWidth, 10) || 0; + var borderTop = parseInt(computedStyle.borderTopWidth, 10) || 0; + var borderBottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; + var badScrollbarWidths = computeScrollbarWidthsForEl(el); // includes border! + var scrollbarLeftRight = badScrollbarWidths.y - borderLeft - borderRight; + var scrollbarBottom = badScrollbarWidths.x - borderTop - borderBottom; + var res = { + borderLeft: borderLeft, + borderRight: borderRight, + borderTop: borderTop, + borderBottom: borderBottom, + scrollbarBottom: scrollbarBottom, + scrollbarLeft: 0, + scrollbarRight: 0, + }; + if (getIsRtlScrollbarOnLeft() && computedStyle.direction === 'rtl') { // is the scrollbar on the left side? + res.scrollbarLeft = scrollbarLeftRight; + } + else { + res.scrollbarRight = scrollbarLeftRight; + } + if (getPadding) { + res.paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0; + res.paddingRight = parseInt(computedStyle.paddingRight, 10) || 0; + res.paddingTop = parseInt(computedStyle.paddingTop, 10) || 0; + res.paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0; + } + return res; + } + function computeInnerRect(el, goWithinPadding, doFromWindowViewport) { + if (goWithinPadding === void 0) { goWithinPadding = false; } + var outerRect = doFromWindowViewport ? el.getBoundingClientRect() : computeRect(el); + var edges = computeEdges(el, goWithinPadding); + var res = { + left: outerRect.left + edges.borderLeft + edges.scrollbarLeft, + right: outerRect.right - edges.borderRight - edges.scrollbarRight, + top: outerRect.top + edges.borderTop, + bottom: outerRect.bottom - edges.borderBottom - edges.scrollbarBottom, + }; + if (goWithinPadding) { + res.left += edges.paddingLeft; + res.right -= edges.paddingRight; + res.top += edges.paddingTop; + res.bottom -= edges.paddingBottom; + } + return res; + } + function computeRect(el) { + var rect = el.getBoundingClientRect(); + return { + left: rect.left + window.pageXOffset, + top: rect.top + window.pageYOffset, + right: rect.right + window.pageXOffset, + bottom: rect.bottom + window.pageYOffset, + }; + } + function computeClippedClientRect(el) { + var clippingParents = getClippingParents(el); + var rect = el.getBoundingClientRect(); + for (var _i = 0, clippingParents_1 = clippingParents; _i < clippingParents_1.length; _i++) { + var clippingParent = clippingParents_1[_i]; + var intersection = intersectRects(rect, clippingParent.getBoundingClientRect()); + if (intersection) { + rect = intersection; + } + else { + return null; + } + } + return rect; + } + function computeHeightAndMargins(el) { + return el.getBoundingClientRect().height + computeVMargins(el); + } + function computeVMargins(el) { + var computed = window.getComputedStyle(el); + return parseInt(computed.marginTop, 10) + + parseInt(computed.marginBottom, 10); + } + // does not return window + function getClippingParents(el) { + var parents = []; + while (el instanceof HTMLElement) { // will stop when gets to document or null + var computedStyle = window.getComputedStyle(el); + if (computedStyle.position === 'fixed') { + break; + } + if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) { + parents.push(el); + } + el = el.parentNode; + } + return parents; + } + + // given a function that resolves a result asynchronously. + // the function can either call passed-in success and failure callbacks, + // or it can return a promise. + // if you need to pass additional params to func, bind them first. + function unpromisify(func, success, failure) { + // guard against success/failure callbacks being called more than once + // and guard against a promise AND callback being used together. + var isResolved = false; + var wrappedSuccess = function () { + if (!isResolved) { + isResolved = true; + success.apply(this, arguments); // eslint-disable-line prefer-rest-params + } + }; + var wrappedFailure = function () { + if (!isResolved) { + isResolved = true; + if (failure) { + failure.apply(this, arguments); // eslint-disable-line prefer-rest-params + } + } + }; + var res = func(wrappedSuccess, wrappedFailure); + if (res && typeof res.then === 'function') { + res.then(wrappedSuccess, wrappedFailure); + } + } + + var Emitter = /** @class */ (function () { + function Emitter() { + this.handlers = {}; + this.thisContext = null; + } + Emitter.prototype.setThisContext = function (thisContext) { + this.thisContext = thisContext; + }; + Emitter.prototype.setOptions = function (options) { + this.options = options; + }; + Emitter.prototype.on = function (type, handler) { + addToHash(this.handlers, type, handler); + }; + Emitter.prototype.off = function (type, handler) { + removeFromHash(this.handlers, type, handler); + }; + Emitter.prototype.trigger = function (type) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + var attachedHandlers = this.handlers[type] || []; + var optionHandler = this.options && this.options[type]; + var handlers = [].concat(optionHandler || [], attachedHandlers); + for (var _a = 0, handlers_1 = handlers; _a < handlers_1.length; _a++) { + var handler = handlers_1[_a]; + handler.apply(this.thisContext, args); + } + }; + Emitter.prototype.hasHandlers = function (type) { + return (this.handlers[type] && this.handlers[type].length) || + (this.options && this.options[type]); + }; + return Emitter; + }()); + function addToHash(hash, type, handler) { + (hash[type] || (hash[type] = [])) + .push(handler); + } + function removeFromHash(hash, type, handler) { + if (handler) { + if (hash[type]) { + hash[type] = hash[type].filter(function (func) { return func !== handler; }); + } + } + else { + delete hash[type]; // remove all handler funcs for this type + } + } + + /* + Records offset information for a set of elements, relative to an origin element. + Can record the left/right OR the top/bottom OR both. + Provides methods for querying the cache by position. + */ + var PositionCache = /** @class */ (function () { + function PositionCache(originEl, els, isHorizontal, isVertical) { + this.els = els; + var originClientRect = this.originClientRect = originEl.getBoundingClientRect(); // relative to viewport top-left + if (isHorizontal) { + this.buildElHorizontals(originClientRect.left); + } + if (isVertical) { + this.buildElVerticals(originClientRect.top); + } + } + // Populates the left/right internal coordinate arrays + PositionCache.prototype.buildElHorizontals = function (originClientLeft) { + var lefts = []; + var rights = []; + for (var _i = 0, _a = this.els; _i < _a.length; _i++) { + var el = _a[_i]; + var rect = el.getBoundingClientRect(); + lefts.push(rect.left - originClientLeft); + rights.push(rect.right - originClientLeft); + } + this.lefts = lefts; + this.rights = rights; + }; + // Populates the top/bottom internal coordinate arrays + PositionCache.prototype.buildElVerticals = function (originClientTop) { + var tops = []; + var bottoms = []; + for (var _i = 0, _a = this.els; _i < _a.length; _i++) { + var el = _a[_i]; + var rect = el.getBoundingClientRect(); + tops.push(rect.top - originClientTop); + bottoms.push(rect.bottom - originClientTop); + } + this.tops = tops; + this.bottoms = bottoms; + }; + // Given a left offset (from document left), returns the index of the el that it horizontally intersects. + // If no intersection is made, returns undefined. + PositionCache.prototype.leftToIndex = function (leftPosition) { + var _a = this, lefts = _a.lefts, rights = _a.rights; + var len = lefts.length; + var i; + for (i = 0; i < len; i += 1) { + if (leftPosition >= lefts[i] && leftPosition < rights[i]) { + return i; + } + } + return undefined; // TODO: better + }; + // Given a top offset (from document top), returns the index of the el that it vertically intersects. + // If no intersection is made, returns undefined. + PositionCache.prototype.topToIndex = function (topPosition) { + var _a = this, tops = _a.tops, bottoms = _a.bottoms; + var len = tops.length; + var i; + for (i = 0; i < len; i += 1) { + if (topPosition >= tops[i] && topPosition < bottoms[i]) { + return i; + } + } + return undefined; // TODO: better + }; + // Gets the width of the element at the given index + PositionCache.prototype.getWidth = function (leftIndex) { + return this.rights[leftIndex] - this.lefts[leftIndex]; + }; + // Gets the height of the element at the given index + PositionCache.prototype.getHeight = function (topIndex) { + return this.bottoms[topIndex] - this.tops[topIndex]; + }; + return PositionCache; + }()); + + /* eslint max-classes-per-file: "off" */ + /* + An object for getting/setting scroll-related information for an element. + Internally, this is done very differently for window versus DOM element, + so this object serves as a common interface. + */ + var ScrollController = /** @class */ (function () { + function ScrollController() { + } + ScrollController.prototype.getMaxScrollTop = function () { + return this.getScrollHeight() - this.getClientHeight(); + }; + ScrollController.prototype.getMaxScrollLeft = function () { + return this.getScrollWidth() - this.getClientWidth(); + }; + ScrollController.prototype.canScrollVertically = function () { + return this.getMaxScrollTop() > 0; + }; + ScrollController.prototype.canScrollHorizontally = function () { + return this.getMaxScrollLeft() > 0; + }; + ScrollController.prototype.canScrollUp = function () { + return this.getScrollTop() > 0; + }; + ScrollController.prototype.canScrollDown = function () { + return this.getScrollTop() < this.getMaxScrollTop(); + }; + ScrollController.prototype.canScrollLeft = function () { + return this.getScrollLeft() > 0; + }; + ScrollController.prototype.canScrollRight = function () { + return this.getScrollLeft() < this.getMaxScrollLeft(); + }; + return ScrollController; + }()); + var ElementScrollController = /** @class */ (function (_super) { + __extends(ElementScrollController, _super); + function ElementScrollController(el) { + var _this = _super.call(this) || this; + _this.el = el; + return _this; + } + ElementScrollController.prototype.getScrollTop = function () { + return this.el.scrollTop; + }; + ElementScrollController.prototype.getScrollLeft = function () { + return this.el.scrollLeft; + }; + ElementScrollController.prototype.setScrollTop = function (top) { + this.el.scrollTop = top; + }; + ElementScrollController.prototype.setScrollLeft = function (left) { + this.el.scrollLeft = left; + }; + ElementScrollController.prototype.getScrollWidth = function () { + return this.el.scrollWidth; + }; + ElementScrollController.prototype.getScrollHeight = function () { + return this.el.scrollHeight; + }; + ElementScrollController.prototype.getClientHeight = function () { + return this.el.clientHeight; + }; + ElementScrollController.prototype.getClientWidth = function () { + return this.el.clientWidth; + }; + return ElementScrollController; + }(ScrollController)); + var WindowScrollController = /** @class */ (function (_super) { + __extends(WindowScrollController, _super); + function WindowScrollController() { + return _super !== null && _super.apply(this, arguments) || this; + } + WindowScrollController.prototype.getScrollTop = function () { + return window.pageYOffset; + }; + WindowScrollController.prototype.getScrollLeft = function () { + return window.pageXOffset; + }; + WindowScrollController.prototype.setScrollTop = function (n) { + window.scroll(window.pageXOffset, n); + }; + WindowScrollController.prototype.setScrollLeft = function (n) { + window.scroll(n, window.pageYOffset); + }; + WindowScrollController.prototype.getScrollWidth = function () { + return document.documentElement.scrollWidth; + }; + WindowScrollController.prototype.getScrollHeight = function () { + return document.documentElement.scrollHeight; + }; + WindowScrollController.prototype.getClientHeight = function () { + return document.documentElement.clientHeight; + }; + WindowScrollController.prototype.getClientWidth = function () { + return document.documentElement.clientWidth; + }; + return WindowScrollController; + }(ScrollController)); + + var Theme = /** @class */ (function () { + function Theme(calendarOptions) { + if (this.iconOverrideOption) { + this.setIconOverride(calendarOptions[this.iconOverrideOption]); + } + } + Theme.prototype.setIconOverride = function (iconOverrideHash) { + var iconClassesCopy; + var buttonName; + if (typeof iconOverrideHash === 'object' && iconOverrideHash) { // non-null object + iconClassesCopy = __assign({}, this.iconClasses); + for (buttonName in iconOverrideHash) { + iconClassesCopy[buttonName] = this.applyIconOverridePrefix(iconOverrideHash[buttonName]); + } + this.iconClasses = iconClassesCopy; + } + else if (iconOverrideHash === false) { + this.iconClasses = {}; + } + }; + Theme.prototype.applyIconOverridePrefix = function (className) { + var prefix = this.iconOverridePrefix; + if (prefix && className.indexOf(prefix) !== 0) { // if not already present + className = prefix + className; + } + return className; + }; + Theme.prototype.getClass = function (key) { + return this.classes[key] || ''; + }; + Theme.prototype.getIconClass = function (buttonName, isRtl) { + var className; + if (isRtl && this.rtlIconClasses) { + className = this.rtlIconClasses[buttonName] || this.iconClasses[buttonName]; + } + else { + className = this.iconClasses[buttonName]; + } + if (className) { + return this.baseIconClass + " " + className; + } + return ''; + }; + Theme.prototype.getCustomButtonIconClass = function (customButtonProps) { + var className; + if (this.iconOverrideCustomButtonOption) { + className = customButtonProps[this.iconOverrideCustomButtonOption]; + if (className) { + return this.baseIconClass + " " + this.applyIconOverridePrefix(className); + } + } + return ''; + }; + return Theme; + }()); + Theme.prototype.classes = {}; + Theme.prototype.iconClasses = {}; + Theme.prototype.baseIconClass = ''; + Theme.prototype.iconOverridePrefix = ''; + + /// + if (typeof FullCalendarVDom === 'undefined') { + throw new Error('Please import the top-level fullcalendar lib before attempting to import a plugin.'); + } + var Component = FullCalendarVDom.Component; + var createElement = FullCalendarVDom.createElement; + var render = FullCalendarVDom.render; + var createRef = FullCalendarVDom.createRef; + var Fragment = FullCalendarVDom.Fragment; + var createContext = FullCalendarVDom.createContext; + var createPortal = FullCalendarVDom.createPortal; + var flushToDom = FullCalendarVDom.flushToDom; + var unmountComponentAtNode = FullCalendarVDom.unmountComponentAtNode; + /* eslint-enable */ + + var ScrollResponder = /** @class */ (function () { + function ScrollResponder(execFunc, emitter, scrollTime, scrollTimeReset) { + var _this = this; + this.execFunc = execFunc; + this.emitter = emitter; + this.scrollTime = scrollTime; + this.scrollTimeReset = scrollTimeReset; + this.handleScrollRequest = function (request) { + _this.queuedRequest = __assign({}, _this.queuedRequest || {}, request); + _this.drain(); + }; + emitter.on('_scrollRequest', this.handleScrollRequest); + this.fireInitialScroll(); + } + ScrollResponder.prototype.detach = function () { + this.emitter.off('_scrollRequest', this.handleScrollRequest); + }; + ScrollResponder.prototype.update = function (isDatesNew) { + if (isDatesNew && this.scrollTimeReset) { + this.fireInitialScroll(); // will drain + } + else { + this.drain(); + } + }; + ScrollResponder.prototype.fireInitialScroll = function () { + this.handleScrollRequest({ + time: this.scrollTime, + }); + }; + ScrollResponder.prototype.drain = function () { + if (this.queuedRequest && this.execFunc(this.queuedRequest)) { + this.queuedRequest = null; + } + }; + return ScrollResponder; + }()); + + var ViewContextType = createContext({}); // for Components + function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, theme, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, registerInteractiveComponent, unregisterInteractiveComponent) { + return { + dateEnv: dateEnv, + options: viewOptions, + pluginHooks: pluginHooks, + emitter: emitter, + dispatch: dispatch, + getCurrentData: getCurrentData, + calendarApi: calendarApi, + viewSpec: viewSpec, + viewApi: viewApi, + dateProfileGenerator: dateProfileGenerator, + theme: theme, + isRtl: viewOptions.direction === 'rtl', + addResizeHandler: function (handler) { + emitter.on('_resize', handler); + }, + removeResizeHandler: function (handler) { + emitter.off('_resize', handler); + }, + createScrollResponder: function (execFunc) { + return new ScrollResponder(execFunc, emitter, createDuration(viewOptions.scrollTime), viewOptions.scrollTimeReset); + }, + registerInteractiveComponent: registerInteractiveComponent, + unregisterInteractiveComponent: unregisterInteractiveComponent, + }; + } + + /* eslint max-classes-per-file: off */ + var PureComponent = /** @class */ (function (_super) { + __extends(PureComponent, _super); + function PureComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + PureComponent.prototype.shouldComponentUpdate = function (nextProps, nextState) { + if (this.debug) { + // eslint-disable-next-line no-console + console.log(getUnequalProps(nextProps, this.props), getUnequalProps(nextState, this.state)); + } + return !compareObjs(this.props, nextProps, this.propEquality) || + !compareObjs(this.state, nextState, this.stateEquality); + }; + PureComponent.addPropsEquality = addPropsEquality; + PureComponent.addStateEquality = addStateEquality; + PureComponent.contextType = ViewContextType; + return PureComponent; + }(Component)); + PureComponent.prototype.propEquality = {}; + PureComponent.prototype.stateEquality = {}; + var BaseComponent = /** @class */ (function (_super) { + __extends(BaseComponent, _super); + function BaseComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + BaseComponent.contextType = ViewContextType; + return BaseComponent; + }(PureComponent)); + function addPropsEquality(propEquality) { + var hash = Object.create(this.prototype.propEquality); + __assign(hash, propEquality); + this.prototype.propEquality = hash; + } + function addStateEquality(stateEquality) { + var hash = Object.create(this.prototype.stateEquality); + __assign(hash, stateEquality); + this.prototype.stateEquality = hash; + } + // use other one + function setRef(ref, current) { + if (typeof ref === 'function') { + ref(current); + } + else if (ref) { + // see https://github.com/facebook/react/issues/13029 + ref.current = current; + } + } + + /* + an INTERACTABLE date component + + PURPOSES: + - hook up to fg, fill, and mirror renderers + - interface for dragging and hits + */ + var DateComponent = /** @class */ (function (_super) { + __extends(DateComponent, _super); + function DateComponent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.uid = guid(); + return _this; + } + // Hit System + // ----------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.prepareHits = function () { + }; + DateComponent.prototype.queryHit = function (positionLeft, positionTop, elWidth, elHeight) { + return null; // this should be abstract + }; + // Pointer Interaction Utils + // ----------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.isValidSegDownEl = function (el) { + return !this.props.eventDrag && // HACK + !this.props.eventResize && // HACK + !elementClosest(el, '.fc-event-mirror'); + }; + DateComponent.prototype.isValidDateDownEl = function (el) { + return !elementClosest(el, '.fc-event:not(.fc-bg-event)') && + !elementClosest(el, '.fc-more-link') && // a "more.." link + !elementClosest(el, 'a[data-navlink]') && // a clickable nav link + !elementClosest(el, '.fc-popover'); // hack + }; + return DateComponent; + }(BaseComponent)); + + // TODO: easier way to add new hooks? need to update a million things + function createPlugin(input) { + return { + id: guid(), + deps: input.deps || [], + reducers: input.reducers || [], + isLoadingFuncs: input.isLoadingFuncs || [], + contextInit: [].concat(input.contextInit || []), + eventRefiners: input.eventRefiners || {}, + eventDefMemberAdders: input.eventDefMemberAdders || [], + eventSourceRefiners: input.eventSourceRefiners || {}, + isDraggableTransformers: input.isDraggableTransformers || [], + eventDragMutationMassagers: input.eventDragMutationMassagers || [], + eventDefMutationAppliers: input.eventDefMutationAppliers || [], + dateSelectionTransformers: input.dateSelectionTransformers || [], + datePointTransforms: input.datePointTransforms || [], + dateSpanTransforms: input.dateSpanTransforms || [], + views: input.views || {}, + viewPropsTransformers: input.viewPropsTransformers || [], + isPropsValid: input.isPropsValid || null, + externalDefTransforms: input.externalDefTransforms || [], + viewContainerAppends: input.viewContainerAppends || [], + eventDropTransformers: input.eventDropTransformers || [], + componentInteractions: input.componentInteractions || [], + calendarInteractions: input.calendarInteractions || [], + themeClasses: input.themeClasses || {}, + eventSourceDefs: input.eventSourceDefs || [], + cmdFormatter: input.cmdFormatter, + recurringTypes: input.recurringTypes || [], + namedTimeZonedImpl: input.namedTimeZonedImpl, + initialView: input.initialView || '', + elementDraggingImpl: input.elementDraggingImpl, + optionChangeHandlers: input.optionChangeHandlers || {}, + scrollGridImpl: input.scrollGridImpl || null, + contentTypeHandlers: input.contentTypeHandlers || {}, + listenerRefiners: input.listenerRefiners || {}, + optionRefiners: input.optionRefiners || {}, + propSetHandlers: input.propSetHandlers || {}, + }; + } + function buildPluginHooks(pluginDefs, globalDefs) { + var isAdded = {}; + var hooks = { + reducers: [], + isLoadingFuncs: [], + contextInit: [], + eventRefiners: {}, + eventDefMemberAdders: [], + eventSourceRefiners: {}, + isDraggableTransformers: [], + eventDragMutationMassagers: [], + eventDefMutationAppliers: [], + dateSelectionTransformers: [], + datePointTransforms: [], + dateSpanTransforms: [], + views: {}, + viewPropsTransformers: [], + isPropsValid: null, + externalDefTransforms: [], + viewContainerAppends: [], + eventDropTransformers: [], + componentInteractions: [], + calendarInteractions: [], + themeClasses: {}, + eventSourceDefs: [], + cmdFormatter: null, + recurringTypes: [], + namedTimeZonedImpl: null, + initialView: '', + elementDraggingImpl: null, + optionChangeHandlers: {}, + scrollGridImpl: null, + contentTypeHandlers: {}, + listenerRefiners: {}, + optionRefiners: {}, + propSetHandlers: {}, + }; + function addDefs(defs) { + for (var _i = 0, defs_1 = defs; _i < defs_1.length; _i++) { + var def = defs_1[_i]; + if (!isAdded[def.id]) { + isAdded[def.id] = true; + addDefs(def.deps); + hooks = combineHooks(hooks, def); + } + } + } + if (pluginDefs) { + addDefs(pluginDefs); + } + addDefs(globalDefs); + return hooks; + } + function buildBuildPluginHooks() { + var currentOverrideDefs = []; + var currentGlobalDefs = []; + var currentHooks; + return function (overrideDefs, globalDefs) { + if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) { + currentHooks = buildPluginHooks(overrideDefs, globalDefs); + } + currentOverrideDefs = overrideDefs; + currentGlobalDefs = globalDefs; + return currentHooks; + }; + } + function combineHooks(hooks0, hooks1) { + return { + reducers: hooks0.reducers.concat(hooks1.reducers), + isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs), + contextInit: hooks0.contextInit.concat(hooks1.contextInit), + eventRefiners: __assign(__assign({}, hooks0.eventRefiners), hooks1.eventRefiners), + eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders), + eventSourceRefiners: __assign(__assign({}, hooks0.eventSourceRefiners), hooks1.eventSourceRefiners), + isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers), + eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers), + eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers), + dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers), + datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms), + dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms), + views: __assign(__assign({}, hooks0.views), hooks1.views), + viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers), + isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid, + externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms), + viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends), + eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers), + calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions), + componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions), + themeClasses: __assign(__assign({}, hooks0.themeClasses), hooks1.themeClasses), + eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs), + cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter, + recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes), + namedTimeZonedImpl: hooks1.namedTimeZonedImpl || hooks0.namedTimeZonedImpl, + initialView: hooks0.initialView || hooks1.initialView, + elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl, + optionChangeHandlers: __assign(__assign({}, hooks0.optionChangeHandlers), hooks1.optionChangeHandlers), + scrollGridImpl: hooks1.scrollGridImpl || hooks0.scrollGridImpl, + contentTypeHandlers: __assign(__assign({}, hooks0.contentTypeHandlers), hooks1.contentTypeHandlers), + listenerRefiners: __assign(__assign({}, hooks0.listenerRefiners), hooks1.listenerRefiners), + optionRefiners: __assign(__assign({}, hooks0.optionRefiners), hooks1.optionRefiners), + propSetHandlers: __assign(__assign({}, hooks0.propSetHandlers), hooks1.propSetHandlers), + }; + } + + var StandardTheme = /** @class */ (function (_super) { + __extends(StandardTheme, _super); + function StandardTheme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return StandardTheme; + }(Theme)); + StandardTheme.prototype.classes = { + root: 'fc-theme-standard', + tableCellShaded: 'fc-cell-shaded', + buttonGroup: 'fc-button-group', + button: 'fc-button fc-button-primary', + buttonActive: 'fc-button-active', + }; + StandardTheme.prototype.baseIconClass = 'fc-icon'; + StandardTheme.prototype.iconClasses = { + close: 'fc-icon-x', + prev: 'fc-icon-chevron-left', + next: 'fc-icon-chevron-right', + prevYear: 'fc-icon-chevrons-left', + nextYear: 'fc-icon-chevrons-right', + }; + StandardTheme.prototype.rtlIconClasses = { + prev: 'fc-icon-chevron-right', + next: 'fc-icon-chevron-left', + prevYear: 'fc-icon-chevrons-right', + nextYear: 'fc-icon-chevrons-left', + }; + StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; // TODO: make TS-friendly + StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon'; + StandardTheme.prototype.iconOverridePrefix = 'fc-icon-'; + + function compileViewDefs(defaultConfigs, overrideConfigs) { + var hash = {}; + var viewType; + for (viewType in defaultConfigs) { + ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); + } + for (viewType in overrideConfigs) { + ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); + } + return hash; + } + function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) { + if (hash[viewType]) { + return hash[viewType]; + } + var viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs); + if (viewDef) { + hash[viewType] = viewDef; + } + return viewDef; + } + function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) { + var defaultConfig = defaultConfigs[viewType]; + var overrideConfig = overrideConfigs[viewType]; + var queryProp = function (name) { return ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] : + ((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null)); }; + var theComponent = queryProp('component'); + var superType = queryProp('superType'); + var superDef = null; + if (superType) { + if (superType === viewType) { + throw new Error('Can\'t have a custom view type that references itself'); + } + superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs); + } + if (!theComponent && superDef) { + theComponent = superDef.component; + } + if (!theComponent) { + return null; // don't throw a warning, might be settings for a single-unit view + } + return { + type: viewType, + component: theComponent, + defaults: __assign(__assign({}, (superDef ? superDef.defaults : {})), (defaultConfig ? defaultConfig.rawOptions : {})), + overrides: __assign(__assign({}, (superDef ? superDef.overrides : {})), (overrideConfig ? overrideConfig.rawOptions : {})), + }; + } + + /* eslint max-classes-per-file: off */ + // NOTE: in JSX, you should always use this class with arg. otherwise, will default to any??? + var RenderHook = /** @class */ (function (_super) { + __extends(RenderHook, _super); + function RenderHook() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + _this.handleRootEl = function (el) { + setRef(_this.rootElRef, el); + if (_this.props.elRef) { + setRef(_this.props.elRef, el); + } + }; + return _this; + } + RenderHook.prototype.render = function () { + var _this = this; + var props = this.props; + var hookProps = props.hookProps; + return (createElement(MountHook, { hookProps: hookProps, didMount: props.didMount, willUnmount: props.willUnmount, elRef: this.handleRootEl }, function (rootElRef) { return (createElement(ContentHook, { hookProps: hookProps, content: props.content, defaultContent: props.defaultContent, backupElRef: _this.rootElRef }, function (innerElRef, innerContent) { return props.children(rootElRef, normalizeClassNames(props.classNames, hookProps), innerElRef, innerContent); })); })); + }; + return RenderHook; + }(BaseComponent)); + // TODO: rename to be about function, not default. use in above type + // for forcing rerender of components that use the ContentHook + var CustomContentRenderContext = createContext(0); + function ContentHook(props) { + return (createElement(CustomContentRenderContext.Consumer, null, function (renderId) { return (createElement(ContentHookInner, __assign({ renderId: renderId }, props))); })); + } + var ContentHookInner = /** @class */ (function (_super) { + __extends(ContentHookInner, _super); + function ContentHookInner() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.innerElRef = createRef(); + return _this; + } + ContentHookInner.prototype.render = function () { + return this.props.children(this.innerElRef, this.renderInnerContent()); + }; + ContentHookInner.prototype.componentDidMount = function () { + this.updateCustomContent(); + }; + ContentHookInner.prototype.componentDidUpdate = function () { + this.updateCustomContent(); + }; + ContentHookInner.prototype.componentWillUnmount = function () { + if (this.customContentInfo && this.customContentInfo.destroy) { + this.customContentInfo.destroy(); + } + }; + ContentHookInner.prototype.renderInnerContent = function () { + var customContentInfo = this.customContentInfo; // only populated if using non-[p]react node(s) + var innerContent = this.getInnerContent(); + var meta = this.getContentMeta(innerContent); + // initial run, or content-type changing? (from vue -> react for example) + if (!customContentInfo || customContentInfo.contentKey !== meta.contentKey) { + // clearing old value + if (customContentInfo) { + if (customContentInfo.destroy) { + customContentInfo.destroy(); + } + customContentInfo = this.customContentInfo = null; + } + // assigning new value + if (meta.contentKey) { + customContentInfo = this.customContentInfo = __assign({ contentKey: meta.contentKey, contentVal: innerContent[meta.contentKey] }, meta.buildLifecycleFuncs()); + } + // updating + } + else if (customContentInfo) { + customContentInfo.contentVal = innerContent[meta.contentKey]; + } + return customContentInfo + ? [] // signal that something was specified + : innerContent; // assume a [p]react vdom node. use it + }; + ContentHookInner.prototype.getInnerContent = function () { + var props = this.props; + var innerContent = normalizeContent(props.content, props.hookProps); + if (innerContent === undefined) { // use the default + innerContent = normalizeContent(props.defaultContent, props.hookProps); + } + return innerContent == null ? null : innerContent; // convert undefined to null (better for React) + }; + ContentHookInner.prototype.getContentMeta = function (innerContent) { + var contentTypeHandlers = this.context.pluginHooks.contentTypeHandlers; + var contentKey = ''; + var buildLifecycleFuncs = null; + if (innerContent) { // allowed to be null, for convenience to caller + for (var searchKey in contentTypeHandlers) { + if (innerContent[searchKey] !== undefined) { + contentKey = searchKey; + buildLifecycleFuncs = contentTypeHandlers[searchKey]; + break; + } + } + } + return { contentKey: contentKey, buildLifecycleFuncs: buildLifecycleFuncs }; + }; + ContentHookInner.prototype.updateCustomContent = function () { + if (this.customContentInfo) { // for non-[p]react + this.customContentInfo.render(this.innerElRef.current || this.props.backupElRef.current, // the element to render into + this.customContentInfo.contentVal); + } + }; + return ContentHookInner; + }(BaseComponent)); + var MountHook = /** @class */ (function (_super) { + __extends(MountHook, _super); + function MountHook() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handleRootEl = function (rootEl) { + _this.rootEl = rootEl; + if (_this.props.elRef) { + setRef(_this.props.elRef, rootEl); + } + }; + return _this; + } + MountHook.prototype.render = function () { + return this.props.children(this.handleRootEl); + }; + MountHook.prototype.componentDidMount = function () { + var callback = this.props.didMount; + if (callback) { + callback(__assign(__assign({}, this.props.hookProps), { el: this.rootEl })); + } + }; + MountHook.prototype.componentWillUnmount = function () { + var callback = this.props.willUnmount; + if (callback) { + callback(__assign(__assign({}, this.props.hookProps), { el: this.rootEl })); + } + }; + return MountHook; + }(BaseComponent)); + function buildClassNameNormalizer() { + var currentGenerator; + var currentHookProps; + var currentClassNames = []; + return function (generator, hookProps) { + if (!currentHookProps || !isPropsEqual(currentHookProps, hookProps) || generator !== currentGenerator) { + currentGenerator = generator; + currentHookProps = hookProps; + currentClassNames = normalizeClassNames(generator, hookProps); + } + return currentClassNames; + }; + } + function normalizeClassNames(classNames, hookProps) { + if (typeof classNames === 'function') { + classNames = classNames(hookProps); + } + return parseClassNames(classNames); + } + function normalizeContent(input, hookProps) { + if (typeof input === 'function') { + return input(hookProps, createElement); // give the function the vdom-creation func + } + return input; + } + + var ViewRoot = /** @class */ (function (_super) { + __extends(ViewRoot, _super); + function ViewRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.normalizeClassNames = buildClassNameNormalizer(); + return _this; + } + ViewRoot.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var hookProps = { view: context.viewApi }; + var customClassNames = this.normalizeClassNames(options.viewClassNames, hookProps); + return (createElement(MountHook, { hookProps: hookProps, didMount: options.viewDidMount, willUnmount: options.viewWillUnmount, elRef: props.elRef }, function (rootElRef) { return props.children(rootElRef, ["fc-" + props.viewSpec.type + "-view", 'fc-view'].concat(customClassNames)); })); + }; + return ViewRoot; + }(BaseComponent)); + + function parseViewConfigs(inputs) { + return mapHash(inputs, parseViewConfig); + } + function parseViewConfig(input) { + var rawOptions = typeof input === 'function' ? + { component: input } : + input; + var component = rawOptions.component; + if (rawOptions.content) { + component = createViewHookComponent(rawOptions); + // TODO: remove content/classNames/didMount/etc from options? + } + return { + superType: rawOptions.type, + component: component, + rawOptions: rawOptions, + }; + } + function createViewHookComponent(options) { + return function (viewProps) { return (createElement(ViewContextType.Consumer, null, function (context) { return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (viewElRef, viewClassNames) { + var hookProps = __assign(__assign({}, viewProps), { nextDayThreshold: context.options.nextDayThreshold }); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.classNames, content: options.content, didMount: options.didMount, willUnmount: options.willUnmount, elRef: viewElRef }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("div", { className: viewClassNames.concat(customClassNames).join(' '), ref: rootElRef }, innerContent)); })); + })); })); }; + } + + function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides, localeDefaults) { + var defaultConfigs = parseViewConfigs(defaultInputs); + var overrideConfigs = parseViewConfigs(optionOverrides.views); + var viewDefs = compileViewDefs(defaultConfigs, overrideConfigs); + return mapHash(viewDefs, function (viewDef) { return buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults); }); + } + function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults) { + var durationInput = viewDef.overrides.duration || + viewDef.defaults.duration || + dynamicOptionOverrides.duration || + optionOverrides.duration; + var duration = null; + var durationUnit = ''; + var singleUnit = ''; + var singleUnitOverrides = {}; + if (durationInput) { + duration = createDurationCached(durationInput); + if (duration) { // valid? + var denom = greatestDurationDenominator(duration); + durationUnit = denom.unit; + if (denom.value === 1) { + singleUnit = durationUnit; + singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {}; + } + } + } + var queryButtonText = function (optionsSubset) { + var buttonTextMap = optionsSubset.buttonText || {}; + var buttonTextKey = viewDef.defaults.buttonTextKey; + if (buttonTextKey != null && buttonTextMap[buttonTextKey] != null) { + return buttonTextMap[buttonTextKey]; + } + if (buttonTextMap[viewDef.type] != null) { + return buttonTextMap[viewDef.type]; + } + if (buttonTextMap[singleUnit] != null) { + return buttonTextMap[singleUnit]; + } + return null; + }; + return { + type: viewDef.type, + component: viewDef.component, + duration: duration, + durationUnit: durationUnit, + singleUnit: singleUnit, + optionDefaults: viewDef.defaults, + optionOverrides: __assign(__assign({}, singleUnitOverrides), viewDef.overrides), + buttonTextOverride: queryButtonText(dynamicOptionOverrides) || + queryButtonText(optionOverrides) || // constructor-specified buttonText lookup hash takes precedence + viewDef.overrides.buttonText, + buttonTextDefault: queryButtonText(localeDefaults) || + viewDef.defaults.buttonText || + queryButtonText(BASE_OPTION_DEFAULTS) || + viewDef.type, // fall back to given view name + }; + } + // hack to get memoization working + var durationInputMap = {}; + function createDurationCached(durationInput) { + var json = JSON.stringify(durationInput); + var res = durationInputMap[json]; + if (res === undefined) { + res = createDuration(durationInput); + durationInputMap[json] = res; + } + return res; + } + + var DateProfileGenerator = /** @class */ (function () { + function DateProfileGenerator(props) { + this.props = props; + this.nowDate = getNow(props.nowInput, props.dateEnv); + this.initHiddenDays(); + } + /* Date Range Computation + ------------------------------------------------------------------------------------------------------------------*/ + // Builds a structure with info about what the dates/ranges will be for the "prev" view. + DateProfileGenerator.prototype.buildPrev = function (currentDateProfile, currentDate, forceToValid) { + var dateEnv = this.props.dateEnv; + var prevDate = dateEnv.subtract(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month + currentDateProfile.dateIncrement); + return this.build(prevDate, -1, forceToValid); + }; + // Builds a structure with info about what the dates/ranges will be for the "next" view. + DateProfileGenerator.prototype.buildNext = function (currentDateProfile, currentDate, forceToValid) { + var dateEnv = this.props.dateEnv; + var nextDate = dateEnv.add(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month + currentDateProfile.dateIncrement); + return this.build(nextDate, 1, forceToValid); + }; + // Builds a structure holding dates/ranges for rendering around the given date. + // Optional direction param indicates whether the date is being incremented/decremented + // from its previous value. decremented = -1, incremented = 1 (default). + DateProfileGenerator.prototype.build = function (currentDate, direction, forceToValid) { + if (forceToValid === void 0) { forceToValid = true; } + var props = this.props; + var validRange; + var currentInfo; + var isRangeAllDay; + var renderRange; + var activeRange; + var isValid; + validRange = this.buildValidRange(); + validRange = this.trimHiddenDays(validRange); + if (forceToValid) { + currentDate = constrainMarkerToRange(currentDate, validRange); + } + currentInfo = this.buildCurrentRangeInfo(currentDate, direction); + isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit); + renderRange = this.buildRenderRange(this.trimHiddenDays(currentInfo.range), currentInfo.unit, isRangeAllDay); + renderRange = this.trimHiddenDays(renderRange); + activeRange = renderRange; + if (!props.showNonCurrentDates) { + activeRange = intersectRanges(activeRange, currentInfo.range); + } + activeRange = this.adjustActiveRange(activeRange); + activeRange = intersectRanges(activeRange, validRange); // might return null + // it's invalid if the originally requested date is not contained, + // or if the range is completely outside of the valid range. + isValid = rangesIntersect(currentInfo.range, validRange); + return { + // constraint for where prev/next operations can go and where events can be dragged/resized to. + // an object with optional start and end properties. + validRange: validRange, + // range the view is formally responsible for. + // for example, a month view might have 1st-31st, excluding padded dates + currentRange: currentInfo.range, + // name of largest unit being displayed, like "month" or "week" + currentRangeUnit: currentInfo.unit, + isRangeAllDay: isRangeAllDay, + // dates that display events and accept drag-n-drop + // will be `null` if no dates accept events + activeRange: activeRange, + // date range with a rendered skeleton + // includes not-active days that need some sort of DOM + renderRange: renderRange, + // Duration object that denotes the first visible time of any given day + slotMinTime: props.slotMinTime, + // Duration object that denotes the exclusive visible end time of any given day + slotMaxTime: props.slotMaxTime, + isValid: isValid, + // how far the current date will move for a prev/next operation + dateIncrement: this.buildDateIncrement(currentInfo.duration), + // pass a fallback (might be null) ^ + }; + }; + // Builds an object with optional start/end properties. + // Indicates the minimum/maximum dates to display. + // not responsible for trimming hidden days. + DateProfileGenerator.prototype.buildValidRange = function () { + var input = this.props.validRangeInput; + var simpleInput = typeof input === 'function' + ? input.call(this.props.calendarApi, this.nowDate) + : input; + return this.refineRange(simpleInput) || + { start: null, end: null }; // completely open-ended + }; + // Builds a structure with info about the "current" range, the range that is + // highlighted as being the current month for example. + // See build() for a description of `direction`. + // Guaranteed to have `range` and `unit` properties. `duration` is optional. + DateProfileGenerator.prototype.buildCurrentRangeInfo = function (date, direction) { + var props = this.props; + var duration = null; + var unit = null; + var range = null; + var dayCount; + if (props.duration) { + duration = props.duration; + unit = props.durationUnit; + range = this.buildRangeFromDuration(date, direction, duration, unit); + } + else if ((dayCount = this.props.dayCount)) { + unit = 'day'; + range = this.buildRangeFromDayCount(date, direction, dayCount); + } + else if ((range = this.buildCustomVisibleRange(date))) { + unit = props.dateEnv.greatestWholeUnit(range.start, range.end).unit; + } + else { + duration = this.getFallbackDuration(); + unit = greatestDurationDenominator(duration).unit; + range = this.buildRangeFromDuration(date, direction, duration, unit); + } + return { duration: duration, unit: unit, range: range }; + }; + DateProfileGenerator.prototype.getFallbackDuration = function () { + return createDuration({ day: 1 }); + }; + // Returns a new activeRange to have time values (un-ambiguate) + // slotMinTime or slotMaxTime causes the range to expand. + DateProfileGenerator.prototype.adjustActiveRange = function (range) { + var _a = this.props, dateEnv = _a.dateEnv, usesMinMaxTime = _a.usesMinMaxTime, slotMinTime = _a.slotMinTime, slotMaxTime = _a.slotMaxTime; + var start = range.start, end = range.end; + if (usesMinMaxTime) { + // expand active range if slotMinTime is negative (why not when positive?) + if (asRoughDays(slotMinTime) < 0) { + start = startOfDay(start); // necessary? + start = dateEnv.add(start, slotMinTime); + } + // expand active range if slotMaxTime is beyond one day (why not when negative?) + if (asRoughDays(slotMaxTime) > 1) { + end = startOfDay(end); // necessary? + end = addDays(end, -1); + end = dateEnv.add(end, slotMaxTime); + } + } + return { start: start, end: end }; + }; + // Builds the "current" range when it is specified as an explicit duration. + // `unit` is the already-computed greatestDurationDenominator unit of duration. + DateProfileGenerator.prototype.buildRangeFromDuration = function (date, direction, duration, unit) { + var _a = this.props, dateEnv = _a.dateEnv, dateAlignment = _a.dateAlignment; + var start; + var end; + var res; + // compute what the alignment should be + if (!dateAlignment) { + var dateIncrement = this.props.dateIncrement; + if (dateIncrement) { + // use the smaller of the two units + if (asRoughMs(dateIncrement) < asRoughMs(duration)) { + dateAlignment = greatestDurationDenominator(dateIncrement).unit; + } + else { + dateAlignment = unit; + } + } + else { + dateAlignment = unit; + } + } + // if the view displays a single day or smaller + if (asRoughDays(duration) <= 1) { + if (this.isHiddenDay(start)) { + start = this.skipHiddenDays(start, direction); + start = startOfDay(start); + } + } + function computeRes() { + start = dateEnv.startOf(date, dateAlignment); + end = dateEnv.add(start, duration); + res = { start: start, end: end }; + } + computeRes(); + // if range is completely enveloped by hidden days, go past the hidden days + if (!this.trimHiddenDays(res)) { + date = this.skipHiddenDays(date, direction); + computeRes(); + } + return res; + }; + // Builds the "current" range when a dayCount is specified. + DateProfileGenerator.prototype.buildRangeFromDayCount = function (date, direction, dayCount) { + var _a = this.props, dateEnv = _a.dateEnv, dateAlignment = _a.dateAlignment; + var runningCount = 0; + var start = date; + var end; + if (dateAlignment) { + start = dateEnv.startOf(start, dateAlignment); + } + start = startOfDay(start); + start = this.skipHiddenDays(start, direction); + end = start; + do { + end = addDays(end, 1); + if (!this.isHiddenDay(end)) { + runningCount += 1; + } + } while (runningCount < dayCount); + return { start: start, end: end }; + }; + // Builds a normalized range object for the "visible" range, + // which is a way to define the currentRange and activeRange at the same time. + DateProfileGenerator.prototype.buildCustomVisibleRange = function (date) { + var props = this.props; + var input = props.visibleRangeInput; + var simpleInput = typeof input === 'function' + ? input.call(props.calendarApi, props.dateEnv.toDate(date)) + : input; + var range = this.refineRange(simpleInput); + if (range && (range.start == null || range.end == null)) { + return null; + } + return range; + }; + // Computes the range that will represent the element/cells for *rendering*, + // but which may have voided days/times. + // not responsible for trimming hidden days. + DateProfileGenerator.prototype.buildRenderRange = function (currentRange, currentRangeUnit, isRangeAllDay) { + return currentRange; + }; + // Compute the duration value that should be added/substracted to the current date + // when a prev/next operation happens. + DateProfileGenerator.prototype.buildDateIncrement = function (fallback) { + var dateIncrement = this.props.dateIncrement; + var customAlignment; + if (dateIncrement) { + return dateIncrement; + } + if ((customAlignment = this.props.dateAlignment)) { + return createDuration(1, customAlignment); + } + if (fallback) { + return fallback; + } + return createDuration({ days: 1 }); + }; + DateProfileGenerator.prototype.refineRange = function (rangeInput) { + if (rangeInput) { + var range = parseRange(rangeInput, this.props.dateEnv); + if (range) { + range = computeVisibleDayRange(range); + } + return range; + } + return null; + }; + /* Hidden Days + ------------------------------------------------------------------------------------------------------------------*/ + // Initializes internal variables related to calculating hidden days-of-week + DateProfileGenerator.prototype.initHiddenDays = function () { + var hiddenDays = this.props.hiddenDays || []; // array of day-of-week indices that are hidden + var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) + var dayCnt = 0; + var i; + if (this.props.weekends === false) { + hiddenDays.push(0, 6); // 0=sunday, 6=saturday + } + for (i = 0; i < 7; i += 1) { + if (!(isHiddenDayHash[i] = hiddenDays.indexOf(i) !== -1)) { + dayCnt += 1; + } + } + if (!dayCnt) { + throw new Error('invalid hiddenDays'); // all days were hidden? bad. + } + this.isHiddenDayHash = isHiddenDayHash; + }; + // Remove days from the beginning and end of the range that are computed as hidden. + // If the whole range is trimmed off, returns null + DateProfileGenerator.prototype.trimHiddenDays = function (range) { + var start = range.start, end = range.end; + if (start) { + start = this.skipHiddenDays(start); + } + if (end) { + end = this.skipHiddenDays(end, -1, true); + } + if (start == null || end == null || start < end) { + return { start: start, end: end }; + } + return null; + }; + // Is the current day hidden? + // `day` is a day-of-week index (0-6), or a Date (used for UTC) + DateProfileGenerator.prototype.isHiddenDay = function (day) { + if (day instanceof Date) { + day = day.getUTCDay(); + } + return this.isHiddenDayHash[day]; + }; + // Incrementing the current day until it is no longer a hidden day, returning a copy. + // DOES NOT CONSIDER validRange! + // If the initial value of `date` is not a hidden day, don't do anything. + // Pass `isExclusive` as `true` if you are dealing with an end date. + // `inc` defaults to `1` (increment one day forward each time) + DateProfileGenerator.prototype.skipHiddenDays = function (date, inc, isExclusive) { + if (inc === void 0) { inc = 1; } + if (isExclusive === void 0) { isExclusive = false; } + while (this.isHiddenDayHash[(date.getUTCDay() + (isExclusive ? inc : 0) + 7) % 7]) { + date = addDays(date, inc); + } + return date; + }; + return DateProfileGenerator; + }()); + + function reduceViewType(viewType, action) { + switch (action.type) { + case 'CHANGE_VIEW_TYPE': + viewType = action.viewType; + } + return viewType; + } + + function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) { + var _a; + switch (action.type) { + case 'SET_OPTION': + return __assign(__assign({}, dynamicOptionOverrides), (_a = {}, _a[action.optionName] = action.rawOptionValue, _a)); + default: + return dynamicOptionOverrides; + } + } + + function reduceDateProfile(currentDateProfile, action, currentDate, dateProfileGenerator) { + var dp; + switch (action.type) { + case 'CHANGE_VIEW_TYPE': + return dateProfileGenerator.build(action.dateMarker || currentDate); + case 'CHANGE_DATE': + return dateProfileGenerator.build(action.dateMarker); + case 'PREV': + dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate); + if (dp.isValid) { + return dp; + } + break; + case 'NEXT': + dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate); + if (dp.isValid) { + return dp; + } + break; + } + return currentDateProfile; + } + + function initEventSources(calendarOptions, dateProfile, context) { + var activeRange = dateProfile ? dateProfile.activeRange : null; + return addSources({}, parseInitialSources(calendarOptions, context), activeRange, context); + } + function reduceEventSources(eventSources, action, dateProfile, context) { + var activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? + switch (action.type) { + case 'ADD_EVENT_SOURCES': // already parsed + return addSources(eventSources, action.sources, activeRange, context); + case 'REMOVE_EVENT_SOURCE': + return removeSource(eventSources, action.sourceId); + case 'PREV': // TODO: how do we track all actions that affect dateProfile :( + case 'NEXT': + case 'CHANGE_DATE': + case 'CHANGE_VIEW_TYPE': + if (dateProfile) { + return fetchDirtySources(eventSources, activeRange, context); + } + return eventSources; + case 'FETCH_EVENT_SOURCES': + return fetchSourcesByIds(eventSources, action.sourceIds ? // why no type? + arrayToHash(action.sourceIds) : + excludeStaticSources(eventSources, context), activeRange, action.isRefetch || false, context); + case 'RECEIVE_EVENTS': + case 'RECEIVE_EVENT_ERROR': + return receiveResponse(eventSources, action.sourceId, action.fetchId, action.fetchRange); + case 'REMOVE_ALL_EVENT_SOURCES': + return {}; + default: + return eventSources; + } + } + function reduceEventSourcesNewTimeZone(eventSources, dateProfile, context) { + var activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? + return fetchSourcesByIds(eventSources, excludeStaticSources(eventSources, context), activeRange, true, context); + } + function computeEventSourcesLoading(eventSources) { + for (var sourceId in eventSources) { + if (eventSources[sourceId].isFetching) { + return true; + } + } + return false; + } + function addSources(eventSourceHash, sources, fetchRange, context) { + var hash = {}; + for (var _i = 0, sources_1 = sources; _i < sources_1.length; _i++) { + var source = sources_1[_i]; + hash[source.sourceId] = source; + } + if (fetchRange) { + hash = fetchDirtySources(hash, fetchRange, context); + } + return __assign(__assign({}, eventSourceHash), hash); + } + function removeSource(eventSourceHash, sourceId) { + return filterHash(eventSourceHash, function (eventSource) { return eventSource.sourceId !== sourceId; }); + } + function fetchDirtySources(sourceHash, fetchRange, context) { + return fetchSourcesByIds(sourceHash, filterHash(sourceHash, function (eventSource) { return isSourceDirty(eventSource, fetchRange, context); }), fetchRange, false, context); + } + function isSourceDirty(eventSource, fetchRange, context) { + if (!doesSourceNeedRange(eventSource, context)) { + return !eventSource.latestFetchId; + } + return !context.options.lazyFetching || + !eventSource.fetchRange || + eventSource.isFetching || // always cancel outdated in-progress fetches + fetchRange.start < eventSource.fetchRange.start || + fetchRange.end > eventSource.fetchRange.end; + } + function fetchSourcesByIds(prevSources, sourceIdHash, fetchRange, isRefetch, context) { + var nextSources = {}; + for (var sourceId in prevSources) { + var source = prevSources[sourceId]; + if (sourceIdHash[sourceId]) { + nextSources[sourceId] = fetchSource(source, fetchRange, isRefetch, context); + } + else { + nextSources[sourceId] = source; + } + } + return nextSources; + } + function fetchSource(eventSource, fetchRange, isRefetch, context) { + var options = context.options, calendarApi = context.calendarApi; + var sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId]; + var fetchId = guid(); + sourceDef.fetch({ + eventSource: eventSource, + range: fetchRange, + isRefetch: isRefetch, + context: context, + }, function (res) { + var rawEvents = res.rawEvents; + if (options.eventSourceSuccess) { + rawEvents = options.eventSourceSuccess.call(calendarApi, rawEvents, res.xhr) || rawEvents; + } + if (eventSource.success) { + rawEvents = eventSource.success.call(calendarApi, rawEvents, res.xhr) || rawEvents; + } + context.dispatch({ + type: 'RECEIVE_EVENTS', + sourceId: eventSource.sourceId, + fetchId: fetchId, + fetchRange: fetchRange, + rawEvents: rawEvents, + }); + }, function (error) { + console.warn(error.message, error); + if (options.eventSourceFailure) { + options.eventSourceFailure.call(calendarApi, error); + } + if (eventSource.failure) { + eventSource.failure(error); + } + context.dispatch({ + type: 'RECEIVE_EVENT_ERROR', + sourceId: eventSource.sourceId, + fetchId: fetchId, + fetchRange: fetchRange, + error: error, + }); + }); + return __assign(__assign({}, eventSource), { isFetching: true, latestFetchId: fetchId }); + } + function receiveResponse(sourceHash, sourceId, fetchId, fetchRange) { + var _a; + var eventSource = sourceHash[sourceId]; + if (eventSource && // not already removed + fetchId === eventSource.latestFetchId) { + return __assign(__assign({}, sourceHash), (_a = {}, _a[sourceId] = __assign(__assign({}, eventSource), { isFetching: false, fetchRange: fetchRange }), _a)); + } + return sourceHash; + } + function excludeStaticSources(eventSources, context) { + return filterHash(eventSources, function (eventSource) { return doesSourceNeedRange(eventSource, context); }); + } + function parseInitialSources(rawOptions, context) { + var refiners = buildEventSourceRefiners(context); + var rawSources = [].concat(rawOptions.eventSources || []); + var sources = []; // parsed + if (rawOptions.initialEvents) { + rawSources.unshift(rawOptions.initialEvents); + } + if (rawOptions.events) { + rawSources.unshift(rawOptions.events); + } + for (var _i = 0, rawSources_1 = rawSources; _i < rawSources_1.length; _i++) { + var rawSource = rawSources_1[_i]; + var source = parseEventSource(rawSource, context, refiners); + if (source) { + sources.push(source); + } + } + return sources; + } + function doesSourceNeedRange(eventSource, context) { + var defs = context.pluginHooks.eventSourceDefs; + return !defs[eventSource.sourceDefId].ignoreRange; + } + + function reduceEventStore(eventStore, action, eventSources, dateProfile, context) { + switch (action.type) { + case 'RECEIVE_EVENTS': // raw + return receiveRawEvents(eventStore, eventSources[action.sourceId], action.fetchId, action.fetchRange, action.rawEvents, context); + case 'ADD_EVENTS': // already parsed, but not expanded + return addEvent(eventStore, action.eventStore, // new ones + dateProfile ? dateProfile.activeRange : null, context); + case 'RESET_EVENTS': + return action.eventStore; + case 'MERGE_EVENTS': // already parsed and expanded + return mergeEventStores(eventStore, action.eventStore); + case 'PREV': // TODO: how do we track all actions that affect dateProfile :( + case 'NEXT': + case 'CHANGE_DATE': + case 'CHANGE_VIEW_TYPE': + if (dateProfile) { + return expandRecurring(eventStore, dateProfile.activeRange, context); + } + return eventStore; + case 'REMOVE_EVENTS': + return excludeSubEventStore(eventStore, action.eventStore); + case 'REMOVE_EVENT_SOURCE': + return excludeEventsBySourceId(eventStore, action.sourceId); + case 'REMOVE_ALL_EVENT_SOURCES': + return filterEventStoreDefs(eventStore, function (eventDef) { return (!eventDef.sourceId // only keep events with no source id + ); }); + case 'REMOVE_ALL_EVENTS': + return createEmptyEventStore(); + default: + return eventStore; + } + } + function receiveRawEvents(eventStore, eventSource, fetchId, fetchRange, rawEvents, context) { + if (eventSource && // not already removed + fetchId === eventSource.latestFetchId // TODO: wish this logic was always in event-sources + ) { + var subset = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context); + if (fetchRange) { + subset = expandRecurring(subset, fetchRange, context); + } + return mergeEventStores(excludeEventsBySourceId(eventStore, eventSource.sourceId), subset); + } + return eventStore; + } + function transformRawEvents(rawEvents, eventSource, context) { + var calEachTransform = context.options.eventDataTransform; + var sourceEachTransform = eventSource ? eventSource.eventDataTransform : null; + if (sourceEachTransform) { + rawEvents = transformEachRawEvent(rawEvents, sourceEachTransform); + } + if (calEachTransform) { + rawEvents = transformEachRawEvent(rawEvents, calEachTransform); + } + return rawEvents; + } + function transformEachRawEvent(rawEvents, func) { + var refinedEvents; + if (!func) { + refinedEvents = rawEvents; + } + else { + refinedEvents = []; + for (var _i = 0, rawEvents_1 = rawEvents; _i < rawEvents_1.length; _i++) { + var rawEvent = rawEvents_1[_i]; + var refinedEvent = func(rawEvent); + if (refinedEvent) { + refinedEvents.push(refinedEvent); + } + else if (refinedEvent == null) { + refinedEvents.push(rawEvent); + } // if a different falsy value, do nothing + } + } + return refinedEvents; + } + function addEvent(eventStore, subset, expandRange, context) { + if (expandRange) { + subset = expandRecurring(subset, expandRange, context); + } + return mergeEventStores(eventStore, subset); + } + function rezoneEventStoreDates(eventStore, oldDateEnv, newDateEnv) { + var defs = eventStore.defs; + var instances = mapHash(eventStore.instances, function (instance) { + var def = defs[instance.defId]; + if (def.allDay || def.recurringDef) { + return instance; // isn't dependent on timezone + } + return __assign(__assign({}, instance), { range: { + start: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.start, instance.forcedStartTzo)), + end: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.end, instance.forcedEndTzo)), + }, forcedStartTzo: newDateEnv.canComputeOffset ? null : instance.forcedStartTzo, forcedEndTzo: newDateEnv.canComputeOffset ? null : instance.forcedEndTzo }); + }); + return { defs: defs, instances: instances }; + } + function excludeEventsBySourceId(eventStore, sourceId) { + return filterEventStoreDefs(eventStore, function (eventDef) { return eventDef.sourceId !== sourceId; }); + } + // QUESTION: why not just return instances? do a general object-property-exclusion util + function excludeInstances(eventStore, removals) { + return { + defs: eventStore.defs, + instances: filterHash(eventStore.instances, function (instance) { return !removals[instance.instanceId]; }), + }; + } + + function reduceDateSelection(currentSelection, action) { + switch (action.type) { + case 'UNSELECT_DATES': + return null; + case 'SELECT_DATES': + return action.selection; + default: + return currentSelection; + } + } + + function reduceSelectedEvent(currentInstanceId, action) { + switch (action.type) { + case 'UNSELECT_EVENT': + return ''; + case 'SELECT_EVENT': + return action.eventInstanceId; + default: + return currentInstanceId; + } + } + + function reduceEventDrag(currentDrag, action) { + var newDrag; + switch (action.type) { + case 'UNSET_EVENT_DRAG': + return null; + case 'SET_EVENT_DRAG': + newDrag = action.state; + return { + affectedEvents: newDrag.affectedEvents, + mutatedEvents: newDrag.mutatedEvents, + isEvent: newDrag.isEvent, + }; + default: + return currentDrag; + } + } + + function reduceEventResize(currentResize, action) { + var newResize; + switch (action.type) { + case 'UNSET_EVENT_RESIZE': + return null; + case 'SET_EVENT_RESIZE': + newResize = action.state; + return { + affectedEvents: newResize.affectedEvents, + mutatedEvents: newResize.mutatedEvents, + isEvent: newResize.isEvent, + }; + default: + return currentResize; + } + } + + function parseToolbars(calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) { + var viewsWithButtons = []; + var headerToolbar = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) : null; + var footerToolbar = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) : null; + return { headerToolbar: headerToolbar, footerToolbar: footerToolbar, viewsWithButtons: viewsWithButtons }; + } + function parseToolbar(sectionStrHash, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) { + return mapHash(sectionStrHash, function (sectionStr) { return parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons); }); + } + /* + BAD: querying icons and text here. should be done at render time + */ + function parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) { + var isRtl = calendarOptions.direction === 'rtl'; + var calendarCustomButtons = calendarOptions.customButtons || {}; + var calendarButtonTextOverrides = calendarOptionOverrides.buttonText || {}; + var calendarButtonText = calendarOptions.buttonText || {}; + var sectionSubstrs = sectionStr ? sectionStr.split(' ') : []; + return sectionSubstrs.map(function (buttonGroupStr) { return (buttonGroupStr.split(',').map(function (buttonName) { + if (buttonName === 'title') { + return { buttonName: buttonName }; + } + var customButtonProps; + var viewSpec; + var buttonClick; + var buttonIcon; // only one of these will be set + var buttonText; // " + if ((customButtonProps = calendarCustomButtons[buttonName])) { + buttonClick = function (ev) { + if (customButtonProps.click) { + customButtonProps.click.call(ev.target, ev, ev.target); // TODO: use Calendar this context? + } + }; + (buttonIcon = theme.getCustomButtonIconClass(customButtonProps)) || + (buttonIcon = theme.getIconClass(buttonName, isRtl)) || + (buttonText = customButtonProps.text); + } + else if ((viewSpec = viewSpecs[buttonName])) { + viewsWithButtons.push(buttonName); + buttonClick = function () { + calendarApi.changeView(buttonName); + }; + (buttonText = viewSpec.buttonTextOverride) || + (buttonIcon = theme.getIconClass(buttonName, isRtl)) || + (buttonText = viewSpec.buttonTextDefault); + } + else if (calendarApi[buttonName]) { // a calendarApi method + buttonClick = function () { + calendarApi[buttonName](); + }; + (buttonText = calendarButtonTextOverrides[buttonName]) || + (buttonIcon = theme.getIconClass(buttonName, isRtl)) || + (buttonText = calendarButtonText[buttonName]); + // ^ everything else is considered default + } + return { buttonName: buttonName, buttonClick: buttonClick, buttonIcon: buttonIcon, buttonText: buttonText }; + })); }); + } + + var eventSourceDef$3 = { + ignoreRange: true, + parseMeta: function (refined) { + if (Array.isArray(refined.events)) { + return refined.events; + } + return null; + }, + fetch: function (arg, success) { + success({ + rawEvents: arg.eventSource.meta, + }); + }, + }; + var arrayEventSourcePlugin = createPlugin({ + eventSourceDefs: [eventSourceDef$3], + }); + + var eventSourceDef$2 = { + parseMeta: function (refined) { + if (typeof refined.events === 'function') { + return refined.events; + } + return null; + }, + fetch: function (arg, success, failure) { + var dateEnv = arg.context.dateEnv; + var func = arg.eventSource.meta; + unpromisify(func.bind(null, buildRangeApiWithTimeZone(arg.range, dateEnv)), function (rawEvents) { + success({ rawEvents: rawEvents }); // needs an object response + }, failure); + }, + }; + var funcEventSourcePlugin = createPlugin({ + eventSourceDefs: [eventSourceDef$2], + }); + + function requestJson(method, url, params, successCallback, failureCallback) { + method = method.toUpperCase(); + var body = null; + if (method === 'GET') { + url = injectQueryStringParams(url, params); + } + else { + body = encodeParams(params); + } + var xhr = new XMLHttpRequest(); + xhr.open(method, url, true); + if (method !== 'GET') { + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + xhr.onload = function () { + if (xhr.status >= 200 && xhr.status < 400) { + var parsed = false; + var res = void 0; + try { + res = JSON.parse(xhr.responseText); + parsed = true; + } + catch (err) { + // will handle parsed=false + } + if (parsed) { + successCallback(res, xhr); + } + else { + failureCallback('Failure parsing JSON', xhr); + } + } + else { + failureCallback('Request failed', xhr); + } + }; + xhr.onerror = function () { + failureCallback('Request failed', xhr); + }; + xhr.send(body); + } + function injectQueryStringParams(url, params) { + return url + + (url.indexOf('?') === -1 ? '?' : '&') + + encodeParams(params); + } + function encodeParams(params) { + var parts = []; + for (var key in params) { + parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(params[key])); + } + return parts.join('&'); + } + + var JSON_FEED_EVENT_SOURCE_REFINERS = { + method: String, + extraParams: identity, + startParam: String, + endParam: String, + timeZoneParam: String, + }; + + var eventSourceDef$1 = { + parseMeta: function (refined) { + if (refined.url && (refined.format === 'json' || !refined.format)) { + return { + url: refined.url, + format: 'json', + method: (refined.method || 'GET').toUpperCase(), + extraParams: refined.extraParams, + startParam: refined.startParam, + endParam: refined.endParam, + timeZoneParam: refined.timeZoneParam, + }; + } + return null; + }, + fetch: function (arg, success, failure) { + var meta = arg.eventSource.meta; + var requestParams = buildRequestParams$1(meta, arg.range, arg.context); + requestJson(meta.method, meta.url, requestParams, function (rawEvents, xhr) { + success({ rawEvents: rawEvents, xhr: xhr }); + }, function (errorMessage, xhr) { + failure({ message: errorMessage, xhr: xhr }); + }); + }, + }; + var jsonFeedEventSourcePlugin = createPlugin({ + eventSourceRefiners: JSON_FEED_EVENT_SOURCE_REFINERS, + eventSourceDefs: [eventSourceDef$1], + }); + function buildRequestParams$1(meta, range, context) { + var dateEnv = context.dateEnv, options = context.options; + var startParam; + var endParam; + var timeZoneParam; + var customRequestParams; + var params = {}; + startParam = meta.startParam; + if (startParam == null) { + startParam = options.startParam; + } + endParam = meta.endParam; + if (endParam == null) { + endParam = options.endParam; + } + timeZoneParam = meta.timeZoneParam; + if (timeZoneParam == null) { + timeZoneParam = options.timeZoneParam; + } + // retrieve any outbound GET/POST data from the options + if (typeof meta.extraParams === 'function') { + // supplied as a function that returns a key/value object + customRequestParams = meta.extraParams(); + } + else { + // probably supplied as a straight key/value object + customRequestParams = meta.extraParams || {}; + } + __assign(params, customRequestParams); + params[startParam] = dateEnv.formatIso(range.start); + params[endParam] = dateEnv.formatIso(range.end); + if (dateEnv.timeZone !== 'local') { + params[timeZoneParam] = dateEnv.timeZone; + } + return params; + } + + var SIMPLE_RECURRING_REFINERS = { + daysOfWeek: identity, + startTime: createDuration, + endTime: createDuration, + duration: createDuration, + startRecur: identity, + endRecur: identity, + }; + + var recurring = { + parse: function (refined, dateEnv) { + if (refined.daysOfWeek || refined.startTime || refined.endTime || refined.startRecur || refined.endRecur) { + var recurringData = { + daysOfWeek: refined.daysOfWeek || null, + startTime: refined.startTime || null, + endTime: refined.endTime || null, + startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null, + endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null, + }; + var duration = void 0; + if (refined.duration) { + duration = refined.duration; + } + if (!duration && refined.startTime && refined.endTime) { + duration = subtractDurations(refined.endTime, refined.startTime); + } + return { + allDayGuess: Boolean(!refined.startTime && !refined.endTime), + duration: duration, + typeData: recurringData, // doesn't need endTime anymore but oh well + }; + } + return null; + }, + expand: function (typeData, framingRange, dateEnv) { + var clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur }); + if (clippedFramingRange) { + return expandRanges(typeData.daysOfWeek, typeData.startTime, clippedFramingRange, dateEnv); + } + return []; + }, + }; + var simpleRecurringEventsPlugin = createPlugin({ + recurringTypes: [recurring], + eventRefiners: SIMPLE_RECURRING_REFINERS, + }); + function expandRanges(daysOfWeek, startTime, framingRange, dateEnv) { + var dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null; + var dayMarker = startOfDay(framingRange.start); + var endMarker = framingRange.end; + var instanceStarts = []; + while (dayMarker < endMarker) { + var instanceStart + // if everyday, or this particular day-of-week + = void 0; + // if everyday, or this particular day-of-week + if (!dowHash || dowHash[dayMarker.getUTCDay()]) { + if (startTime) { + instanceStart = dateEnv.add(dayMarker, startTime); + } + else { + instanceStart = dayMarker; + } + instanceStarts.push(instanceStart); + } + dayMarker = addDays(dayMarker, 1); + } + return instanceStarts; + } + + var changeHandlerPlugin = createPlugin({ + optionChangeHandlers: { + events: function (events, context) { + handleEventSources([events], context); + }, + eventSources: handleEventSources, + }, + }); + /* + BUG: if `event` was supplied, all previously-given `eventSources` will be wiped out + */ + function handleEventSources(inputs, context) { + var unfoundSources = hashValuesToArray(context.getCurrentData().eventSources); + var newInputs = []; + for (var _i = 0, inputs_1 = inputs; _i < inputs_1.length; _i++) { + var input = inputs_1[_i]; + var inputFound = false; + for (var i = 0; i < unfoundSources.length; i += 1) { + if (unfoundSources[i]._raw === input) { + unfoundSources.splice(i, 1); // delete + inputFound = true; + break; + } + } + if (!inputFound) { + newInputs.push(input); + } + } + for (var _a = 0, unfoundSources_1 = unfoundSources; _a < unfoundSources_1.length; _a++) { + var unfoundSource = unfoundSources_1[_a]; + context.dispatch({ + type: 'REMOVE_EVENT_SOURCE', + sourceId: unfoundSource.sourceId, + }); + } + for (var _b = 0, newInputs_1 = newInputs; _b < newInputs_1.length; _b++) { + var newInput = newInputs_1[_b]; + context.calendarApi.addEventSource(newInput); + } + } + + function handleDateProfile(dateProfile, context) { + context.emitter.trigger('datesSet', __assign(__assign({}, buildRangeApiWithTimeZone(dateProfile.activeRange, context.dateEnv)), { view: context.viewApi })); + } + + function handleEventStore(eventStore, context) { + var emitter = context.emitter; + if (emitter.hasHandlers('eventsSet')) { + emitter.trigger('eventsSet', buildEventApis(eventStore, context)); + } + } + + /* + this array is exposed on the root namespace so that UMD plugins can add to it. + see the rollup-bundles script. + */ + var globalPlugins = [ + arrayEventSourcePlugin, + funcEventSourcePlugin, + jsonFeedEventSourcePlugin, + simpleRecurringEventsPlugin, + changeHandlerPlugin, + createPlugin({ + isLoadingFuncs: [ + function (state) { return computeEventSourcesLoading(state.eventSources); }, + ], + contentTypeHandlers: { + html: function () { return ({ render: injectHtml }); }, + domNodes: function () { return ({ render: injectDomNodes }); }, + }, + propSetHandlers: { + dateProfile: handleDateProfile, + eventStore: handleEventStore, + }, + }), + ]; + function injectHtml(el, html) { + el.innerHTML = html; + } + function injectDomNodes(el, domNodes) { + var oldNodes = Array.prototype.slice.call(el.childNodes); // TODO: use array util + var newNodes = Array.prototype.slice.call(domNodes); // TODO: use array util + if (!isArraysEqual(oldNodes, newNodes)) { + for (var _i = 0, newNodes_1 = newNodes; _i < newNodes_1.length; _i++) { + var newNode = newNodes_1[_i]; + el.appendChild(newNode); + } + oldNodes.forEach(removeElement); + } + } + + var DelayedRunner = /** @class */ (function () { + function DelayedRunner(drainedOption) { + this.drainedOption = drainedOption; + this.isRunning = false; + this.isDirty = false; + this.pauseDepths = {}; + this.timeoutId = 0; + } + DelayedRunner.prototype.request = function (delay) { + this.isDirty = true; + if (!this.isPaused()) { + this.clearTimeout(); + if (delay == null) { + this.tryDrain(); + } + else { + this.timeoutId = setTimeout(// NOT OPTIMAL! TODO: look at debounce + this.tryDrain.bind(this), delay); + } + } + }; + DelayedRunner.prototype.pause = function (scope) { + if (scope === void 0) { scope = ''; } + var pauseDepths = this.pauseDepths; + pauseDepths[scope] = (pauseDepths[scope] || 0) + 1; + this.clearTimeout(); + }; + DelayedRunner.prototype.resume = function (scope, force) { + if (scope === void 0) { scope = ''; } + var pauseDepths = this.pauseDepths; + if (scope in pauseDepths) { + if (force) { + delete pauseDepths[scope]; + } + else { + pauseDepths[scope] -= 1; + var depth = pauseDepths[scope]; + if (depth <= 0) { + delete pauseDepths[scope]; + } + } + this.tryDrain(); + } + }; + DelayedRunner.prototype.isPaused = function () { + return Object.keys(this.pauseDepths).length; + }; + DelayedRunner.prototype.tryDrain = function () { + if (!this.isRunning && !this.isPaused()) { + this.isRunning = true; + while (this.isDirty) { + this.isDirty = false; + this.drained(); // might set isDirty to true again + } + this.isRunning = false; + } + }; + DelayedRunner.prototype.clear = function () { + this.clearTimeout(); + this.isDirty = false; + this.pauseDepths = {}; + }; + DelayedRunner.prototype.clearTimeout = function () { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + this.timeoutId = 0; + } + }; + DelayedRunner.prototype.drained = function () { + if (this.drainedOption) { + this.drainedOption(); + } + }; + return DelayedRunner; + }()); + + var TaskRunner = /** @class */ (function () { + function TaskRunner(runTaskOption, drainedOption) { + this.runTaskOption = runTaskOption; + this.drainedOption = drainedOption; + this.queue = []; + this.delayedRunner = new DelayedRunner(this.drain.bind(this)); + } + TaskRunner.prototype.request = function (task, delay) { + this.queue.push(task); + this.delayedRunner.request(delay); + }; + TaskRunner.prototype.pause = function (scope) { + this.delayedRunner.pause(scope); + }; + TaskRunner.prototype.resume = function (scope, force) { + this.delayedRunner.resume(scope, force); + }; + TaskRunner.prototype.drain = function () { + var queue = this.queue; + while (queue.length) { + var completedTasks = []; + var task = void 0; + while ((task = queue.shift())) { + this.runTask(task); + completedTasks.push(task); + } + this.drained(completedTasks); + } // keep going, in case new tasks were added in the drained handler + }; + TaskRunner.prototype.runTask = function (task) { + if (this.runTaskOption) { + this.runTaskOption(task); + } + }; + TaskRunner.prototype.drained = function (completedTasks) { + if (this.drainedOption) { + this.drainedOption(completedTasks); + } + }; + return TaskRunner; + }()); + + // Computes what the title at the top of the calendarApi should be for this view + function buildTitle(dateProfile, viewOptions, dateEnv) { + var range; + // for views that span a large unit of time, show the proper interval, ignoring stray days before and after + if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) { + range = dateProfile.currentRange; + } + else { // for day units or smaller, use the actual day range + range = dateProfile.activeRange; + } + return dateEnv.formatRange(range.start, range.end, createFormatter(viewOptions.titleFormat || buildTitleFormat(dateProfile)), { + isEndExclusive: dateProfile.isRangeAllDay, + defaultSeparator: viewOptions.titleRangeSeparator, + }); + } + // Generates the format string that should be used to generate the title for the current date range. + // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`. + function buildTitleFormat(dateProfile) { + var currentRangeUnit = dateProfile.currentRangeUnit; + if (currentRangeUnit === 'year') { + return { year: 'numeric' }; + } + if (currentRangeUnit === 'month') { + return { year: 'numeric', month: 'long' }; // like "September 2014" + } + var days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end); + if (days !== null && days > 1) { + // multi-day range. shorter, like "Sep 9 - 10 2014" + return { year: 'numeric', month: 'short', day: 'numeric' }; + } + // one day. longer, like "September 9 2014" + return { year: 'numeric', month: 'long', day: 'numeric' }; + } + + // in future refactor, do the redux-style function(state=initial) for initial-state + // also, whatever is happening in constructor, have it happen in action queue too + var CalendarDataManager = /** @class */ (function () { + function CalendarDataManager(props) { + var _this = this; + this.computeOptionsData = memoize(this._computeOptionsData); + this.computeCurrentViewData = memoize(this._computeCurrentViewData); + this.organizeRawLocales = memoize(organizeRawLocales); + this.buildLocale = memoize(buildLocale); + this.buildPluginHooks = buildBuildPluginHooks(); + this.buildDateEnv = memoize(buildDateEnv); + this.buildTheme = memoize(buildTheme); + this.parseToolbars = memoize(parseToolbars); + this.buildViewSpecs = memoize(buildViewSpecs); + this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator); + this.buildViewApi = memoize(buildViewApi); + this.buildViewUiProps = memoizeObjArg(buildViewUiProps); + this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqual); + this.buildEventUiBases = memoize(buildEventUiBases); + this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours); + this.buildTitle = memoize(buildTitle); + this.emitter = new Emitter(); + this.actionRunner = new TaskRunner(this._handleAction.bind(this), this.updateData.bind(this)); + this.currentCalendarOptionsInput = {}; + this.currentCalendarOptionsRefined = {}; + this.currentViewOptionsInput = {}; + this.currentViewOptionsRefined = {}; + this.currentCalendarOptionsRefiners = {}; + this.getCurrentData = function () { return _this.data; }; + this.dispatch = function (action) { + _this.actionRunner.request(action); // protects against recursive calls to _handleAction + }; + this.props = props; + this.actionRunner.pause(); + var dynamicOptionOverrides = {}; + var optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); + var currentViewType = optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView; + var currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides); + // wire things up + // TODO: not DRY + props.calendarApi.currentDataManager = this; + this.emitter.setThisContext(props.calendarApi); + this.emitter.setOptions(currentViewData.options); + var currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv); + var dateProfile = currentViewData.dateProfileGenerator.build(currentDate); + if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) { + currentDate = dateProfile.currentRange.start; + } + var calendarContext = { + dateEnv: optionsData.dateEnv, + options: optionsData.calendarOptions, + pluginHooks: optionsData.pluginHooks, + calendarApi: props.calendarApi, + dispatch: this.dispatch, + emitter: this.emitter, + getCurrentData: this.getCurrentData, + }; + // needs to be after setThisContext + for (var _i = 0, _a = optionsData.pluginHooks.contextInit; _i < _a.length; _i++) { + var callback = _a[_i]; + callback(calendarContext); + } + // NOT DRY + var eventSources = initEventSources(optionsData.calendarOptions, dateProfile, calendarContext); + var initialState = { + dynamicOptionOverrides: dynamicOptionOverrides, + currentViewType: currentViewType, + currentDate: currentDate, + dateProfile: dateProfile, + businessHours: this.parseContextBusinessHours(calendarContext), + eventSources: eventSources, + eventUiBases: {}, + eventStore: createEmptyEventStore(), + renderableEventStore: createEmptyEventStore(), + dateSelection: null, + eventSelection: '', + eventDrag: null, + eventResize: null, + selectionConfig: this.buildViewUiProps(calendarContext).selectionConfig, + }; + var contextAndState = __assign(__assign({}, calendarContext), initialState); + for (var _b = 0, _c = optionsData.pluginHooks.reducers; _b < _c.length; _b++) { + var reducer = _c[_b]; + __assign(initialState, reducer(null, null, contextAndState)); + } + if (computeIsLoading(initialState, calendarContext)) { + this.emitter.trigger('loading', true); // NOT DRY + } + this.state = initialState; + this.updateData(); + this.actionRunner.resume(); + } + CalendarDataManager.prototype.resetOptions = function (optionOverrides, append) { + var props = this.props; + props.optionOverrides = append + ? __assign(__assign({}, props.optionOverrides), optionOverrides) : optionOverrides; + this.actionRunner.request({ + type: 'NOTHING', + }); + }; + CalendarDataManager.prototype._handleAction = function (action) { + var _a = this, props = _a.props, state = _a.state, emitter = _a.emitter; + var dynamicOptionOverrides = reduceDynamicOptionOverrides(state.dynamicOptionOverrides, action); + var optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); + var currentViewType = reduceViewType(state.currentViewType, action); + var currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides); + // wire things up + // TODO: not DRY + props.calendarApi.currentDataManager = this; + emitter.setThisContext(props.calendarApi); + emitter.setOptions(currentViewData.options); + var calendarContext = { + dateEnv: optionsData.dateEnv, + options: optionsData.calendarOptions, + pluginHooks: optionsData.pluginHooks, + calendarApi: props.calendarApi, + dispatch: this.dispatch, + emitter: emitter, + getCurrentData: this.getCurrentData, + }; + var currentDate = state.currentDate, dateProfile = state.dateProfile; + if (this.data && this.data.dateProfileGenerator !== currentViewData.dateProfileGenerator) { // hack + dateProfile = currentViewData.dateProfileGenerator.build(currentDate); + } + currentDate = reduceCurrentDate(currentDate, action); + dateProfile = reduceDateProfile(dateProfile, action, currentDate, currentViewData.dateProfileGenerator); + if (action.type === 'PREV' || // TODO: move this logic into DateProfileGenerator + action.type === 'NEXT' || // " + !rangeContainsMarker(dateProfile.currentRange, currentDate)) { + currentDate = dateProfile.currentRange.start; + } + var eventSources = reduceEventSources(state.eventSources, action, dateProfile, calendarContext); + var eventStore = reduceEventStore(state.eventStore, action, eventSources, dateProfile, calendarContext); + var isEventsLoading = computeEventSourcesLoading(eventSources); // BAD. also called in this func in computeIsLoading + var renderableEventStore = (isEventsLoading && !currentViewData.options.progressiveEventRendering) ? + (state.renderableEventStore || eventStore) : // try from previous state + eventStore; + var _b = this.buildViewUiProps(calendarContext), eventUiSingleBase = _b.eventUiSingleBase, selectionConfig = _b.selectionConfig; // will memoize obj + var eventUiBySource = this.buildEventUiBySource(eventSources); + var eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource); + var newState = { + dynamicOptionOverrides: dynamicOptionOverrides, + currentViewType: currentViewType, + currentDate: currentDate, + dateProfile: dateProfile, + eventSources: eventSources, + eventStore: eventStore, + renderableEventStore: renderableEventStore, + selectionConfig: selectionConfig, + eventUiBases: eventUiBases, + businessHours: this.parseContextBusinessHours(calendarContext), + dateSelection: reduceDateSelection(state.dateSelection, action), + eventSelection: reduceSelectedEvent(state.eventSelection, action), + eventDrag: reduceEventDrag(state.eventDrag, action), + eventResize: reduceEventResize(state.eventResize, action), + }; + var contextAndState = __assign(__assign({}, calendarContext), newState); + for (var _i = 0, _c = optionsData.pluginHooks.reducers; _i < _c.length; _i++) { + var reducer = _c[_i]; + __assign(newState, reducer(state, action, contextAndState)); // give the OLD state, for old value + } + var wasLoading = computeIsLoading(state, calendarContext); + var isLoading = computeIsLoading(newState, calendarContext); + // TODO: use propSetHandlers in plugin system + if (!wasLoading && isLoading) { + emitter.trigger('loading', true); + } + else if (wasLoading && !isLoading) { + emitter.trigger('loading', false); + } + this.state = newState; + if (props.onAction) { + props.onAction(action); + } + }; + CalendarDataManager.prototype.updateData = function () { + var _a = this, props = _a.props, state = _a.state; + var oldData = this.data; + var optionsData = this.computeOptionsData(props.optionOverrides, state.dynamicOptionOverrides, props.calendarApi); + var currentViewData = this.computeCurrentViewData(state.currentViewType, optionsData, props.optionOverrides, state.dynamicOptionOverrides); + var data = this.data = __assign(__assign(__assign({ viewTitle: this.buildTitle(state.dateProfile, currentViewData.options, optionsData.dateEnv), calendarApi: props.calendarApi, dispatch: this.dispatch, emitter: this.emitter, getCurrentData: this.getCurrentData }, optionsData), currentViewData), state); + var changeHandlers = optionsData.pluginHooks.optionChangeHandlers; + var oldCalendarOptions = oldData && oldData.calendarOptions; + var newCalendarOptions = optionsData.calendarOptions; + if (oldCalendarOptions && oldCalendarOptions !== newCalendarOptions) { + if (oldCalendarOptions.timeZone !== newCalendarOptions.timeZone) { + // hack + state.eventSources = data.eventSources = reduceEventSourcesNewTimeZone(data.eventSources, state.dateProfile, data); + state.eventStore = data.eventStore = rezoneEventStoreDates(data.eventStore, oldData.dateEnv, data.dateEnv); + } + for (var optionName in changeHandlers) { + if (oldCalendarOptions[optionName] !== newCalendarOptions[optionName]) { + changeHandlers[optionName](newCalendarOptions[optionName], data); + } + } + } + if (props.onData) { + props.onData(data); + } + }; + CalendarDataManager.prototype._computeOptionsData = function (optionOverrides, dynamicOptionOverrides, calendarApi) { + // TODO: blacklist options that are handled by optionChangeHandlers + var _a = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides), refinedOptions = _a.refinedOptions, pluginHooks = _a.pluginHooks, localeDefaults = _a.localeDefaults, availableLocaleData = _a.availableLocaleData, extra = _a.extra; + warnUnknownOptions(extra); + var dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekText, pluginHooks, availableLocaleData, refinedOptions.defaultRangeSeparator); + var viewSpecs = this.buildViewSpecs(pluginHooks.views, optionOverrides, dynamicOptionOverrides, localeDefaults); + var theme = this.buildTheme(refinedOptions, pluginHooks); + var toolbarConfig = this.parseToolbars(refinedOptions, optionOverrides, theme, viewSpecs, calendarApi); + return { + calendarOptions: refinedOptions, + pluginHooks: pluginHooks, + dateEnv: dateEnv, + viewSpecs: viewSpecs, + theme: theme, + toolbarConfig: toolbarConfig, + localeDefaults: localeDefaults, + availableRawLocales: availableLocaleData.map, + }; + }; + // always called from behind a memoizer + CalendarDataManager.prototype.processRawCalendarOptions = function (optionOverrides, dynamicOptionOverrides) { + var _a = mergeRawOptions([ + BASE_OPTION_DEFAULTS, + optionOverrides, + dynamicOptionOverrides, + ]), locales = _a.locales, locale = _a.locale; + var availableLocaleData = this.organizeRawLocales(locales); + var availableRawLocales = availableLocaleData.map; + var localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options; + var pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins); + var refiners = this.currentCalendarOptionsRefiners = __assign(__assign(__assign(__assign(__assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners); + var extra = {}; + var raw = mergeRawOptions([ + BASE_OPTION_DEFAULTS, + localeDefaults, + optionOverrides, + dynamicOptionOverrides, + ]); + var refined = {}; + var currentRaw = this.currentCalendarOptionsInput; + var currentRefined = this.currentCalendarOptionsRefined; + var anyChanges = false; + for (var optionName in raw) { + if (optionName !== 'plugins') { // because plugins is special-cased + if (raw[optionName] === currentRaw[optionName] || + (COMPLEX_OPTION_COMPARATORS[optionName] && + (optionName in currentRaw) && + COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName]))) { + refined[optionName] = currentRefined[optionName]; + } + else if (refiners[optionName]) { + refined[optionName] = refiners[optionName](raw[optionName]); + anyChanges = true; + } + else { + extra[optionName] = currentRaw[optionName]; + } + } + } + if (anyChanges) { + this.currentCalendarOptionsInput = raw; + this.currentCalendarOptionsRefined = refined; + } + return { + rawOptions: this.currentCalendarOptionsInput, + refinedOptions: this.currentCalendarOptionsRefined, + pluginHooks: pluginHooks, + availableLocaleData: availableLocaleData, + localeDefaults: localeDefaults, + extra: extra, + }; + }; + CalendarDataManager.prototype._computeCurrentViewData = function (viewType, optionsData, optionOverrides, dynamicOptionOverrides) { + var viewSpec = optionsData.viewSpecs[viewType]; + if (!viewSpec) { + throw new Error("viewType \"" + viewType + "\" is not available. Please make sure you've loaded all neccessary plugins"); + } + var _a = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides), refinedOptions = _a.refinedOptions, extra = _a.extra; + warnUnknownOptions(extra); + var dateProfileGenerator = this.buildDateProfileGenerator({ + dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass, + duration: viewSpec.duration, + durationUnit: viewSpec.durationUnit, + usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime, + dateEnv: optionsData.dateEnv, + calendarApi: this.props.calendarApi, + slotMinTime: refinedOptions.slotMinTime, + slotMaxTime: refinedOptions.slotMaxTime, + showNonCurrentDates: refinedOptions.showNonCurrentDates, + dayCount: refinedOptions.dayCount, + dateAlignment: refinedOptions.dateAlignment, + dateIncrement: refinedOptions.dateIncrement, + hiddenDays: refinedOptions.hiddenDays, + weekends: refinedOptions.weekends, + nowInput: refinedOptions.now, + validRangeInput: refinedOptions.validRange, + visibleRangeInput: refinedOptions.visibleRange, + monthMode: refinedOptions.monthMode, + fixedWeekCount: refinedOptions.fixedWeekCount, + }); + var viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv); + return { viewSpec: viewSpec, options: refinedOptions, dateProfileGenerator: dateProfileGenerator, viewApi: viewApi }; + }; + CalendarDataManager.prototype.processRawViewOptions = function (viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) { + var raw = mergeRawOptions([ + BASE_OPTION_DEFAULTS, + viewSpec.optionDefaults, + localeDefaults, + optionOverrides, + viewSpec.optionOverrides, + dynamicOptionOverrides, + ]); + var refiners = __assign(__assign(__assign(__assign(__assign(__assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), VIEW_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners); + var refined = {}; + var currentRaw = this.currentViewOptionsInput; + var currentRefined = this.currentViewOptionsRefined; + var anyChanges = false; + var extra = {}; + for (var optionName in raw) { + if (raw[optionName] === currentRaw[optionName]) { + refined[optionName] = currentRefined[optionName]; + } + else { + if (raw[optionName] === this.currentCalendarOptionsInput[optionName]) { + if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop + refined[optionName] = this.currentCalendarOptionsRefined[optionName]; + } + } + else if (refiners[optionName]) { + refined[optionName] = refiners[optionName](raw[optionName]); + } + else { + extra[optionName] = raw[optionName]; + } + anyChanges = true; + } + } + if (anyChanges) { + this.currentViewOptionsInput = raw; + this.currentViewOptionsRefined = refined; + } + return { + rawOptions: this.currentViewOptionsInput, + refinedOptions: this.currentViewOptionsRefined, + extra: extra, + }; + }; + return CalendarDataManager; + }()); + function buildDateEnv(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekText, pluginHooks, availableLocaleData, defaultSeparator) { + var locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map); + return new DateEnv({ + calendarSystem: 'gregory', + timeZone: timeZone, + namedTimeZoneImpl: pluginHooks.namedTimeZonedImpl, + locale: locale, + weekNumberCalculation: weekNumberCalculation, + firstDay: firstDay, + weekText: weekText, + cmdFormatter: pluginHooks.cmdFormatter, + defaultSeparator: defaultSeparator, + }); + } + function buildTheme(options, pluginHooks) { + var ThemeClass = pluginHooks.themeClasses[options.themeSystem] || StandardTheme; + return new ThemeClass(options); + } + function buildDateProfileGenerator(props) { + var DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator; + return new DateProfileGeneratorClass(props); + } + function buildViewApi(type, getCurrentData, dateEnv) { + return new ViewApi(type, getCurrentData, dateEnv); + } + function buildEventUiBySource(eventSources) { + return mapHash(eventSources, function (eventSource) { return eventSource.ui; }); + } + function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) { + var eventUiBases = { '': eventUiSingleBase }; + for (var defId in eventDefs) { + var def = eventDefs[defId]; + if (def.sourceId && eventUiBySource[def.sourceId]) { + eventUiBases[defId] = eventUiBySource[def.sourceId]; + } + } + return eventUiBases; + } + function buildViewUiProps(calendarContext) { + var options = calendarContext.options; + return { + eventUiSingleBase: createEventUi({ + display: options.eventDisplay, + editable: options.editable, + startEditable: options.eventStartEditable, + durationEditable: options.eventDurationEditable, + constraint: options.eventConstraint, + overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined, + allow: options.eventAllow, + backgroundColor: options.eventBackgroundColor, + borderColor: options.eventBorderColor, + textColor: options.eventTextColor, + color: options.eventColor, + // classNames: options.eventClassNames // render hook will handle this + }, calendarContext), + selectionConfig: createEventUi({ + constraint: options.selectConstraint, + overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined, + allow: options.selectAllow, + }, calendarContext), + }; + } + function computeIsLoading(state, context) { + for (var _i = 0, _a = context.pluginHooks.isLoadingFuncs; _i < _a.length; _i++) { + var isLoadingFunc = _a[_i]; + if (isLoadingFunc(state)) { + return true; + } + } + return false; + } + function parseContextBusinessHours(calendarContext) { + return parseBusinessHours(calendarContext.options.businessHours, calendarContext); + } + function warnUnknownOptions(options, viewName) { + for (var optionName in options) { + console.warn("Unknown option '" + optionName + "'" + + (viewName ? " for view '" + viewName + "'" : '')); + } + } + + // TODO: move this to react plugin? + var CalendarDataProvider = /** @class */ (function (_super) { + __extends(CalendarDataProvider, _super); + function CalendarDataProvider(props) { + var _this = _super.call(this, props) || this; + _this.handleData = function (data) { + if (!_this.dataManager) { // still within initial run, before assignment in constructor + // eslint-disable-next-line react/no-direct-mutation-state + _this.state = data; // can't use setState yet + } + else { + _this.setState(data); + } + }; + _this.dataManager = new CalendarDataManager({ + optionOverrides: props.optionOverrides, + calendarApi: props.calendarApi, + onData: _this.handleData, + }); + return _this; + } + CalendarDataProvider.prototype.render = function () { + return this.props.children(this.state); + }; + CalendarDataProvider.prototype.componentDidUpdate = function (prevProps) { + var newOptionOverrides = this.props.optionOverrides; + if (newOptionOverrides !== prevProps.optionOverrides) { // prevent recursive handleData + this.dataManager.resetOptions(newOptionOverrides); + } + }; + return CalendarDataProvider; + }(Component)); + + // HELPERS + /* + if nextDayThreshold is specified, slicing is done in an all-day fashion. + you can get nextDayThreshold from context.nextDayThreshold + */ + function sliceEvents(props, allDay) { + return sliceEventStore(props.eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? props.nextDayThreshold : null).fg; + } + + var NamedTimeZoneImpl = /** @class */ (function () { + function NamedTimeZoneImpl(timeZoneName) { + this.timeZoneName = timeZoneName; + } + return NamedTimeZoneImpl; + }()); + + var SegHierarchy = /** @class */ (function () { + function SegHierarchy() { + // settings + this.strictOrder = false; + this.allowReslicing = false; + this.maxCoord = -1; // -1 means no max + this.maxStackCnt = -1; // -1 means no max + this.levelCoords = []; // ordered + this.entriesByLevel = []; // parallel with levelCoords + this.stackCnts = {}; // TODO: use better technique!? + } + SegHierarchy.prototype.addSegs = function (inputs) { + var hiddenEntries = []; + for (var _i = 0, inputs_1 = inputs; _i < inputs_1.length; _i++) { + var input = inputs_1[_i]; + this.insertEntry(input, hiddenEntries); + } + return hiddenEntries; + }; + SegHierarchy.prototype.insertEntry = function (entry, hiddenEntries) { + var insertion = this.findInsertion(entry); + if (this.isInsertionValid(insertion, entry)) { + this.insertEntryAt(entry, insertion); + return 1; + } + return this.handleInvalidInsertion(insertion, entry, hiddenEntries); + }; + SegHierarchy.prototype.isInsertionValid = function (insertion, entry) { + return (this.maxCoord === -1 || insertion.levelCoord + entry.thickness <= this.maxCoord) && + (this.maxStackCnt === -1 || insertion.stackCnt < this.maxStackCnt); + }; + // returns number of new entries inserted + SegHierarchy.prototype.handleInvalidInsertion = function (insertion, entry, hiddenEntries) { + if (this.allowReslicing && insertion.touchingEntry) { + return this.splitEntry(entry, insertion.touchingEntry, hiddenEntries); + } + hiddenEntries.push(entry); + return 0; + }; + SegHierarchy.prototype.splitEntry = function (entry, barrier, hiddenEntries) { + var partCnt = 0; + var splitHiddenEntries = []; + var entrySpan = entry.span; + var barrierSpan = barrier.span; + if (entrySpan.start < barrierSpan.start) { + partCnt += this.insertEntry({ + index: entry.index, + thickness: entry.thickness, + span: { start: entrySpan.start, end: barrierSpan.start }, + }, splitHiddenEntries); + } + if (entrySpan.end > barrierSpan.end) { + partCnt += this.insertEntry({ + index: entry.index, + thickness: entry.thickness, + span: { start: barrierSpan.end, end: entrySpan.end }, + }, splitHiddenEntries); + } + if (partCnt) { + hiddenEntries.push.apply(hiddenEntries, __spreadArray([{ + index: entry.index, + thickness: entry.thickness, + span: intersectSpans(barrierSpan, entrySpan), // guaranteed to intersect + }], splitHiddenEntries)); + return partCnt; + } + hiddenEntries.push(entry); + return 0; + }; + SegHierarchy.prototype.insertEntryAt = function (entry, insertion) { + var _a = this, entriesByLevel = _a.entriesByLevel, levelCoords = _a.levelCoords; + if (insertion.lateral === -1) { + // create a new level + insertAt(levelCoords, insertion.level, insertion.levelCoord); + insertAt(entriesByLevel, insertion.level, [entry]); + } + else { + // insert into existing level + insertAt(entriesByLevel[insertion.level], insertion.lateral, entry); + } + this.stackCnts[buildEntryKey(entry)] = insertion.stackCnt; + }; + SegHierarchy.prototype.findInsertion = function (newEntry) { + var _a = this, levelCoords = _a.levelCoords, entriesByLevel = _a.entriesByLevel, strictOrder = _a.strictOrder, stackCnts = _a.stackCnts; + var levelCnt = levelCoords.length; + var candidateCoord = 0; + var touchingLevel = -1; + var touchingLateral = -1; + var touchingEntry = null; + var stackCnt = 0; + for (var trackingLevel = 0; trackingLevel < levelCnt; trackingLevel += 1) { + var trackingCoord = levelCoords[trackingLevel]; + // if the current level is past the placed entry, we have found a good empty space and can stop. + // if strictOrder, keep finding more lateral intersections. + if (!strictOrder && trackingCoord >= candidateCoord + newEntry.thickness) { + break; + } + var trackingEntries = entriesByLevel[trackingLevel]; + var trackingEntry = void 0; + var searchRes = binarySearch(trackingEntries, newEntry.span.start, getEntrySpanEnd); // find first entry after newEntry's end + var lateralIndex = searchRes[0] + searchRes[1]; // if exact match (which doesn't collide), go to next one + while ( // loop through entries that horizontally intersect + (trackingEntry = trackingEntries[lateralIndex]) && // but not past the whole entry list + trackingEntry.span.start < newEntry.span.end // and not entirely past newEntry + ) { + var trackingEntryBottom = trackingCoord + trackingEntry.thickness; + // intersects into the top of the candidate? + if (trackingEntryBottom > candidateCoord) { + candidateCoord = trackingEntryBottom; + touchingEntry = trackingEntry; + touchingLevel = trackingLevel; + touchingLateral = lateralIndex; + } + // butts up against top of candidate? (will happen if just intersected as well) + if (trackingEntryBottom === candidateCoord) { + // accumulate the highest possible stackCnt of the trackingEntries that butt up + stackCnt = Math.max(stackCnt, stackCnts[buildEntryKey(trackingEntry)] + 1); + } + lateralIndex += 1; + } + } + // the destination level will be after touchingEntry's level. find it + var destLevel = 0; + if (touchingEntry) { + destLevel = touchingLevel + 1; + while (destLevel < levelCnt && levelCoords[destLevel] < candidateCoord) { + destLevel += 1; + } + } + // if adding to an existing level, find where to insert + var destLateral = -1; + if (destLevel < levelCnt && levelCoords[destLevel] === candidateCoord) { + destLateral = binarySearch(entriesByLevel[destLevel], newEntry.span.end, getEntrySpanEnd)[0]; + } + return { + touchingLevel: touchingLevel, + touchingLateral: touchingLateral, + touchingEntry: touchingEntry, + stackCnt: stackCnt, + levelCoord: candidateCoord, + level: destLevel, + lateral: destLateral, + }; + }; + // sorted by levelCoord (lowest to highest) + SegHierarchy.prototype.toRects = function () { + var _a = this, entriesByLevel = _a.entriesByLevel, levelCoords = _a.levelCoords; + var levelCnt = entriesByLevel.length; + var rects = []; + for (var level = 0; level < levelCnt; level += 1) { + var entries = entriesByLevel[level]; + var levelCoord = levelCoords[level]; + for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) { + var entry = entries_1[_i]; + rects.push(__assign(__assign({}, entry), { levelCoord: levelCoord })); + } + } + return rects; + }; + return SegHierarchy; + }()); + function getEntrySpanEnd(entry) { + return entry.span.end; + } + function buildEntryKey(entry) { + return entry.index + ':' + entry.span.start; + } + // returns groups with entries sorted by input order + function groupIntersectingEntries(entries) { + var merges = []; + for (var _i = 0, entries_2 = entries; _i < entries_2.length; _i++) { + var entry = entries_2[_i]; + var filteredMerges = []; + var hungryMerge = { + span: entry.span, + entries: [entry], + }; + for (var _a = 0, merges_1 = merges; _a < merges_1.length; _a++) { + var merge = merges_1[_a]; + if (intersectSpans(merge.span, hungryMerge.span)) { + hungryMerge = { + entries: merge.entries.concat(hungryMerge.entries), + span: joinSpans(merge.span, hungryMerge.span), + }; + } + else { + filteredMerges.push(merge); + } + } + filteredMerges.push(hungryMerge); + merges = filteredMerges; + } + return merges; + } + function joinSpans(span0, span1) { + return { + start: Math.min(span0.start, span1.start), + end: Math.max(span0.end, span1.end), + }; + } + function intersectSpans(span0, span1) { + var start = Math.max(span0.start, span1.start); + var end = Math.min(span0.end, span1.end); + if (start < end) { + return { start: start, end: end }; + } + return null; + } + // general util + // --------------------------------------------------------------------------------------------------------------------- + function insertAt(arr, index, item) { + arr.splice(index, 0, item); + } + function binarySearch(a, searchVal, getItemVal) { + var startIndex = 0; + var endIndex = a.length; // exclusive + if (!endIndex || searchVal < getItemVal(a[startIndex])) { // no items OR before first item + return [0, 0]; + } + if (searchVal > getItemVal(a[endIndex - 1])) { // after last item + return [endIndex, 0]; + } + while (startIndex < endIndex) { + var middleIndex = Math.floor(startIndex + (endIndex - startIndex) / 2); + var middleVal = getItemVal(a[middleIndex]); + if (searchVal < middleVal) { + endIndex = middleIndex; + } + else if (searchVal > middleVal) { + startIndex = middleIndex + 1; + } + else { // equal! + return [middleIndex, 1]; + } + } + return [startIndex, 0]; + } + + var Interaction = /** @class */ (function () { + function Interaction(settings) { + this.component = settings.component; + this.isHitComboAllowed = settings.isHitComboAllowed || null; + } + Interaction.prototype.destroy = function () { + }; + return Interaction; + }()); + function parseInteractionSettings(component, input) { + return { + component: component, + el: input.el, + useEventCenter: input.useEventCenter != null ? input.useEventCenter : true, + isHitComboAllowed: input.isHitComboAllowed || null, + }; + } + function interactionSettingsToStore(settings) { + var _a; + return _a = {}, + _a[settings.component.uid] = settings, + _a; + } + // global state + var interactionSettingsStore = {}; + + /* + An abstraction for a dragging interaction originating on an event. + Does higher-level things than PointerDragger, such as possibly: + - a "mirror" that moves with the pointer + - a minimum number of pixels or other criteria for a true drag to begin + + subclasses must emit: + - pointerdown + - dragstart + - dragmove + - pointerup + - dragend + */ + var ElementDragging = /** @class */ (function () { + function ElementDragging(el, selector) { + this.emitter = new Emitter(); + } + ElementDragging.prototype.destroy = function () { + }; + ElementDragging.prototype.setMirrorIsVisible = function (bool) { + // optional if subclass doesn't want to support a mirror + }; + ElementDragging.prototype.setMirrorNeedsRevert = function (bool) { + // optional if subclass doesn't want to support a mirror + }; + ElementDragging.prototype.setAutoScrollEnabled = function (bool) { + // optional + }; + return ElementDragging; + }()); + + // TODO: get rid of this in favor of options system, + // tho it's really easy to access this globally rather than pass thru options. + var config = {}; + + /* + Information about what will happen when an external element is dragged-and-dropped + onto a calendar. Contains information for creating an event. + */ + var DRAG_META_REFINERS = { + startTime: createDuration, + duration: createDuration, + create: Boolean, + sourceId: String, + }; + function parseDragMeta(raw) { + var _a = refineProps(raw, DRAG_META_REFINERS), refined = _a.refined, extra = _a.extra; + return { + startTime: refined.startTime || null, + duration: refined.duration || null, + create: refined.create != null ? refined.create : true, + sourceId: refined.sourceId, + leftoverProps: extra, + }; + } + + var ToolbarSection = /** @class */ (function (_super) { + __extends(ToolbarSection, _super); + function ToolbarSection() { + return _super !== null && _super.apply(this, arguments) || this; + } + ToolbarSection.prototype.render = function () { + var _this = this; + var children = this.props.widgetGroups.map(function (widgetGroup) { return _this.renderWidgetGroup(widgetGroup); }); + return createElement.apply(void 0, __spreadArray(['div', { className: 'fc-toolbar-chunk' }], children)); + }; + ToolbarSection.prototype.renderWidgetGroup = function (widgetGroup) { + var props = this.props; + var theme = this.context.theme; + var children = []; + var isOnlyButtons = true; + for (var _i = 0, widgetGroup_1 = widgetGroup; _i < widgetGroup_1.length; _i++) { + var widget = widgetGroup_1[_i]; + var buttonName = widget.buttonName, buttonClick = widget.buttonClick, buttonText = widget.buttonText, buttonIcon = widget.buttonIcon; + if (buttonName === 'title') { + isOnlyButtons = false; + children.push(createElement("h2", { className: "fc-toolbar-title" }, props.title)); + } + else { + var ariaAttrs = buttonIcon ? { 'aria-label': buttonName } : {}; + var buttonClasses = ["fc-" + buttonName + "-button", theme.getClass('button')]; + if (buttonName === props.activeButton) { + buttonClasses.push(theme.getClass('buttonActive')); + } + var isDisabled = (!props.isTodayEnabled && buttonName === 'today') || + (!props.isPrevEnabled && buttonName === 'prev') || + (!props.isNextEnabled && buttonName === 'next'); + children.push(createElement("button", __assign({ disabled: isDisabled, className: buttonClasses.join(' '), onClick: buttonClick, type: "button" }, ariaAttrs), buttonText || (buttonIcon ? createElement("span", { className: buttonIcon }) : ''))); + } + } + if (children.length > 1) { + var groupClassName = (isOnlyButtons && theme.getClass('buttonGroup')) || ''; + return createElement.apply(void 0, __spreadArray(['div', { className: groupClassName }], children)); + } + return children[0]; + }; + return ToolbarSection; + }(BaseComponent)); + + var Toolbar = /** @class */ (function (_super) { + __extends(Toolbar, _super); + function Toolbar() { + return _super !== null && _super.apply(this, arguments) || this; + } + Toolbar.prototype.render = function () { + var _a = this.props, model = _a.model, extraClassName = _a.extraClassName; + var forceLtr = false; + var startContent; + var endContent; + var centerContent = model.center; + if (model.left) { + forceLtr = true; + startContent = model.left; + } + else { + startContent = model.start; + } + if (model.right) { + forceLtr = true; + endContent = model.right; + } + else { + endContent = model.end; + } + var classNames = [ + extraClassName || '', + 'fc-toolbar', + forceLtr ? 'fc-toolbar-ltr' : '', + ]; + return (createElement("div", { className: classNames.join(' ') }, + this.renderSection('start', startContent || []), + this.renderSection('center', centerContent || []), + this.renderSection('end', endContent || []))); + }; + Toolbar.prototype.renderSection = function (key, widgetGroups) { + var props = this.props; + return (createElement(ToolbarSection, { key: key, widgetGroups: widgetGroups, title: props.title, activeButton: props.activeButton, isTodayEnabled: props.isTodayEnabled, isPrevEnabled: props.isPrevEnabled, isNextEnabled: props.isNextEnabled })); + }; + return Toolbar; + }(BaseComponent)); + + // TODO: do function component? + var ViewContainer = /** @class */ (function (_super) { + __extends(ViewContainer, _super); + function ViewContainer() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.state = { + availableWidth: null, + }; + _this.handleEl = function (el) { + _this.el = el; + setRef(_this.props.elRef, el); + _this.updateAvailableWidth(); + }; + _this.handleResize = function () { + _this.updateAvailableWidth(); + }; + return _this; + } + ViewContainer.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state; + var aspectRatio = props.aspectRatio; + var classNames = [ + 'fc-view-harness', + (aspectRatio || props.liquid || props.height) + ? 'fc-view-harness-active' // harness controls the height + : 'fc-view-harness-passive', // let the view do the height + ]; + var height = ''; + var paddingBottom = ''; + if (aspectRatio) { + if (state.availableWidth !== null) { + height = state.availableWidth / aspectRatio; + } + else { + // while waiting to know availableWidth, we can't set height to *zero* + // because will cause lots of unnecessary scrollbars within scrollgrid. + // BETTER: don't start rendering ANYTHING yet until we know container width + // NOTE: why not always use paddingBottom? Causes height oscillation (issue 5606) + paddingBottom = (1 / aspectRatio) * 100 + "%"; + } + } + else { + height = props.height || ''; + } + return (createElement("div", { ref: this.handleEl, onClick: props.onClick, className: classNames.join(' '), style: { height: height, paddingBottom: paddingBottom } }, props.children)); + }; + ViewContainer.prototype.componentDidMount = function () { + this.context.addResizeHandler(this.handleResize); + }; + ViewContainer.prototype.componentWillUnmount = function () { + this.context.removeResizeHandler(this.handleResize); + }; + ViewContainer.prototype.updateAvailableWidth = function () { + if (this.el && // needed. but why? + this.props.aspectRatio // aspectRatio is the only height setting that needs availableWidth + ) { + this.setState({ availableWidth: this.el.offsetWidth }); + } + }; + return ViewContainer; + }(BaseComponent)); + + /* + Detects when the user clicks on an event within a DateComponent + */ + var EventClicking = /** @class */ (function (_super) { + __extends(EventClicking, _super); + function EventClicking(settings) { + var _this = _super.call(this, settings) || this; + _this.handleSegClick = function (ev, segEl) { + var component = _this.component; + var context = component.context; + var seg = getElSeg(segEl); + if (seg && // might be the
surrounding the more link + component.isValidSegDownEl(ev.target)) { + // our way to simulate a link click for elements that can't be tags + // grab before trigger fired in case trigger trashes DOM thru rerendering + var hasUrlContainer = elementClosest(ev.target, '.fc-event-forced-url'); + var url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : ''; + context.emitter.trigger('eventClick', { + el: segEl, + event: new EventApi(component.context, seg.eventRange.def, seg.eventRange.instance), + jsEvent: ev, + view: context.viewApi, + }); + if (url && !ev.defaultPrevented) { + window.location.href = url; + } + } + }; + _this.destroy = listenBySelector(settings.el, 'click', '.fc-event', // on both fg and bg events + _this.handleSegClick); + return _this; + } + return EventClicking; + }(Interaction)); + + /* + Triggers events and adds/removes core classNames when the user's pointer + enters/leaves event-elements of a component. + */ + var EventHovering = /** @class */ (function (_super) { + __extends(EventHovering, _super); + function EventHovering(settings) { + var _this = _super.call(this, settings) || this; + // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it + _this.handleEventElRemove = function (el) { + if (el === _this.currentSegEl) { + _this.handleSegLeave(null, _this.currentSegEl); + } + }; + _this.handleSegEnter = function (ev, segEl) { + if (getElSeg(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper + _this.currentSegEl = segEl; + _this.triggerEvent('eventMouseEnter', ev, segEl); + } + }; + _this.handleSegLeave = function (ev, segEl) { + if (_this.currentSegEl) { + _this.currentSegEl = null; + _this.triggerEvent('eventMouseLeave', ev, segEl); + } + }; + _this.removeHoverListeners = listenToHoverBySelector(settings.el, '.fc-event', // on both fg and bg events + _this.handleSegEnter, _this.handleSegLeave); + return _this; + } + EventHovering.prototype.destroy = function () { + this.removeHoverListeners(); + }; + EventHovering.prototype.triggerEvent = function (publicEvName, ev, segEl) { + var component = this.component; + var context = component.context; + var seg = getElSeg(segEl); + if (!ev || component.isValidSegDownEl(ev.target)) { + context.emitter.trigger(publicEvName, { + el: segEl, + event: new EventApi(context, seg.eventRange.def, seg.eventRange.instance), + jsEvent: ev, + view: context.viewApi, + }); + } + }; + return EventHovering; + }(Interaction)); + + var CalendarContent = /** @class */ (function (_super) { + __extends(CalendarContent, _super); + function CalendarContent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildViewContext = memoize(buildViewContext); + _this.buildViewPropTransformers = memoize(buildViewPropTransformers); + _this.buildToolbarProps = memoize(buildToolbarProps); + _this.handleNavLinkClick = buildDelegationHandler('a[data-navlink]', _this._handleNavLinkClick.bind(_this)); + _this.headerRef = createRef(); + _this.footerRef = createRef(); + _this.interactionsStore = {}; + // Component Registration + // ----------------------------------------------------------------------------------------------------------------- + _this.registerInteractiveComponent = function (component, settingsInput) { + var settings = parseInteractionSettings(component, settingsInput); + var DEFAULT_INTERACTIONS = [ + EventClicking, + EventHovering, + ]; + var interactionClasses = DEFAULT_INTERACTIONS.concat(_this.props.pluginHooks.componentInteractions); + var interactions = interactionClasses.map(function (TheInteractionClass) { return new TheInteractionClass(settings); }); + _this.interactionsStore[component.uid] = interactions; + interactionSettingsStore[component.uid] = settings; + }; + _this.unregisterInteractiveComponent = function (component) { + for (var _i = 0, _a = _this.interactionsStore[component.uid]; _i < _a.length; _i++) { + var listener = _a[_i]; + listener.destroy(); + } + delete _this.interactionsStore[component.uid]; + delete interactionSettingsStore[component.uid]; + }; + // Resizing + // ----------------------------------------------------------------------------------------------------------------- + _this.resizeRunner = new DelayedRunner(function () { + _this.props.emitter.trigger('_resize', true); // should window resizes be considered "forced" ? + _this.props.emitter.trigger('windowResize', { view: _this.props.viewApi }); + }); + _this.handleWindowResize = function (ev) { + var options = _this.props.options; + if (options.handleWindowResize && + ev.target === window // avoid jqui events + ) { + _this.resizeRunner.request(options.windowResizeDelay); + } + }; + return _this; + } + /* + renders INSIDE of an outer div + */ + CalendarContent.prototype.render = function () { + var props = this.props; + var toolbarConfig = props.toolbarConfig, options = props.options; + var toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, getNow(props.options.now, props.dateEnv), // TODO: use NowTimer???? + props.viewTitle); + var viewVGrow = false; + var viewHeight = ''; + var viewAspectRatio; + if (props.isHeightAuto || props.forPrint) { + viewHeight = ''; + } + else if (options.height != null) { + viewVGrow = true; + } + else if (options.contentHeight != null) { + viewHeight = options.contentHeight; + } + else { + viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall + } + var viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.theme, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, this.registerInteractiveComponent, this.unregisterInteractiveComponent); + return (createElement(ViewContextType.Provider, { value: viewContext }, + toolbarConfig.headerToolbar && (createElement(Toolbar, __assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.headerToolbar }, toolbarProps))), + createElement(ViewContainer, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, onClick: this.handleNavLinkClick }, + this.renderView(props), + this.buildAppendContent()), + toolbarConfig.footerToolbar && (createElement(Toolbar, __assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footerToolbar }, toolbarProps))))); + }; + CalendarContent.prototype.componentDidMount = function () { + var props = this.props; + this.calendarInteractions = props.pluginHooks.calendarInteractions + .map(function (CalendarInteractionClass) { return new CalendarInteractionClass(props); }); + window.addEventListener('resize', this.handleWindowResize); + var propSetHandlers = props.pluginHooks.propSetHandlers; + for (var propName in propSetHandlers) { + propSetHandlers[propName](props[propName], props); + } + }; + CalendarContent.prototype.componentDidUpdate = function (prevProps) { + var props = this.props; + var propSetHandlers = props.pluginHooks.propSetHandlers; + for (var propName in propSetHandlers) { + if (props[propName] !== prevProps[propName]) { + propSetHandlers[propName](props[propName], props); + } + } + }; + CalendarContent.prototype.componentWillUnmount = function () { + window.removeEventListener('resize', this.handleWindowResize); + this.resizeRunner.clear(); + for (var _i = 0, _a = this.calendarInteractions; _i < _a.length; _i++) { + var interaction = _a[_i]; + interaction.destroy(); + } + this.props.emitter.trigger('_unmount'); + }; + CalendarContent.prototype._handleNavLinkClick = function (ev, anchorEl) { + var _a = this.props, dateEnv = _a.dateEnv, options = _a.options, calendarApi = _a.calendarApi; + var navLinkOptions = anchorEl.getAttribute('data-navlink'); + navLinkOptions = navLinkOptions ? JSON.parse(navLinkOptions) : {}; + var dateMarker = dateEnv.createMarker(navLinkOptions.date); + var viewType = navLinkOptions.type; + var customAction = viewType === 'day' ? options.navLinkDayClick : + viewType === 'week' ? options.navLinkWeekClick : null; + if (typeof customAction === 'function') { + customAction.call(calendarApi, dateEnv.toDate(dateMarker), ev); + } + else { + if (typeof customAction === 'string') { + viewType = customAction; + } + calendarApi.zoomTo(dateMarker, viewType); + } + }; + CalendarContent.prototype.buildAppendContent = function () { + var props = this.props; + var children = props.pluginHooks.viewContainerAppends.map(function (buildAppendContent) { return buildAppendContent(props); }); + return createElement.apply(void 0, __spreadArray([Fragment, {}], children)); + }; + CalendarContent.prototype.renderView = function (props) { + var pluginHooks = props.pluginHooks; + var viewSpec = props.viewSpec; + var viewProps = { + dateProfile: props.dateProfile, + businessHours: props.businessHours, + eventStore: props.renderableEventStore, + eventUiBases: props.eventUiBases, + dateSelection: props.dateSelection, + eventSelection: props.eventSelection, + eventDrag: props.eventDrag, + eventResize: props.eventResize, + isHeightAuto: props.isHeightAuto, + forPrint: props.forPrint, + }; + var transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers); + for (var _i = 0, transformers_1 = transformers; _i < transformers_1.length; _i++) { + var transformer = transformers_1[_i]; + __assign(viewProps, transformer.transform(viewProps, props)); + } + var ViewComponent = viewSpec.component; + return (createElement(ViewComponent, __assign({}, viewProps))); + }; + return CalendarContent; + }(PureComponent)); + function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, now, title) { + // don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid + var todayInfo = dateProfileGenerator.build(now, undefined, false); // TODO: need `undefined` or else INFINITE LOOP for some reason + var prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, false); + var nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, false); + return { + title: title, + activeButton: viewSpec.type, + isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now), + isPrevEnabled: prevInfo.isValid, + isNextEnabled: nextInfo.isValid, + }; + } + // Plugin + // ----------------------------------------------------------------------------------------------------------------- + function buildViewPropTransformers(theClasses) { + return theClasses.map(function (TheClass) { return new TheClass(); }); + } + + var CalendarRoot = /** @class */ (function (_super) { + __extends(CalendarRoot, _super); + function CalendarRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.state = { + forPrint: false, + }; + _this.handleBeforePrint = function () { + _this.setState({ forPrint: true }); + }; + _this.handleAfterPrint = function () { + _this.setState({ forPrint: false }); + }; + return _this; + } + CalendarRoot.prototype.render = function () { + var props = this.props; + var options = props.options; + var forPrint = this.state.forPrint; + var isHeightAuto = forPrint || options.height === 'auto' || options.contentHeight === 'auto'; + var height = (!isHeightAuto && options.height != null) ? options.height : ''; + var classNames = [ + 'fc', + forPrint ? 'fc-media-print' : 'fc-media-screen', + "fc-direction-" + options.direction, + props.theme.getClass('root'), + ]; + if (!getCanVGrowWithinCell()) { + classNames.push('fc-liquid-hack'); + } + return props.children(classNames, height, isHeightAuto, forPrint); + }; + CalendarRoot.prototype.componentDidMount = function () { + var emitter = this.props.emitter; + emitter.on('_beforeprint', this.handleBeforePrint); + emitter.on('_afterprint', this.handleAfterPrint); + }; + CalendarRoot.prototype.componentWillUnmount = function () { + var emitter = this.props.emitter; + emitter.off('_beforeprint', this.handleBeforePrint); + emitter.off('_afterprint', this.handleAfterPrint); + }; + return CalendarRoot; + }(BaseComponent)); + + // Computes a default column header formatting string if `colFormat` is not explicitly defined + function computeFallbackHeaderFormat(datesRepDistinctDays, dayCnt) { + // if more than one week row, or if there are a lot of columns with not much space, + // put just the day numbers will be in each cell + if (!datesRepDistinctDays || dayCnt > 10) { + return createFormatter({ weekday: 'short' }); // "Sat" + } + if (dayCnt > 1) { + return createFormatter({ weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }); // "Sat 11/12" + } + return createFormatter({ weekday: 'long' }); // "Saturday" + } + + var CLASS_NAME = 'fc-col-header-cell'; // do the cushion too? no + function renderInner$1(hookProps) { + return hookProps.text; + } + + var TableDateCell = /** @class */ (function (_super) { + __extends(TableDateCell, _super); + function TableDateCell() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableDateCell.prototype.render = function () { + var _a = this.context, dateEnv = _a.dateEnv, options = _a.options, theme = _a.theme, viewApi = _a.viewApi; + var props = this.props; + var date = props.date, dateProfile = props.dateProfile; + var dayMeta = getDateMeta(date, props.todayRange, null, dateProfile); + var classNames = [CLASS_NAME].concat(getDayClassNames(dayMeta, theme)); + var text = dateEnv.format(date, props.dayHeaderFormat); + // if colCnt is 1, we are already in a day-view and don't need a navlink + var navLinkAttrs = (options.navLinks && !dayMeta.isDisabled && props.colCnt > 1) + ? { 'data-navlink': buildNavLinkData(date), tabIndex: 0 } + : {}; + var hookProps = __assign(__assign(__assign({ date: dateEnv.toDate(date), view: viewApi }, props.extraHookProps), { text: text }), dayMeta); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.dayHeaderClassNames, content: options.dayHeaderContent, defaultContent: renderInner$1, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("th", __assign({ ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-date": !dayMeta.isDisabled ? formatDayString(date) : undefined, colSpan: props.colSpan }, props.extraDataAttrs), + createElement("div", { className: "fc-scrollgrid-sync-inner" }, !dayMeta.isDisabled && (createElement("a", __assign({ ref: innerElRef, className: [ + 'fc-col-header-cell-cushion', + props.isSticky ? 'fc-sticky' : '', + ].join(' ') }, navLinkAttrs), innerContent))))); })); + }; + return TableDateCell; + }(BaseComponent)); + + var TableDowCell = /** @class */ (function (_super) { + __extends(TableDowCell, _super); + function TableDowCell() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableDowCell.prototype.render = function () { + var props = this.props; + var _a = this.context, dateEnv = _a.dateEnv, theme = _a.theme, viewApi = _a.viewApi, options = _a.options; + var date = addDays(new Date(259200000), props.dow); // start with Sun, 04 Jan 1970 00:00:00 GMT + var dateMeta = { + dow: props.dow, + isDisabled: false, + isFuture: false, + isPast: false, + isToday: false, + isOther: false, + }; + var classNames = [CLASS_NAME].concat(getDayClassNames(dateMeta, theme), props.extraClassNames || []); + var text = dateEnv.format(date, props.dayHeaderFormat); + var hookProps = __assign(__assign(__assign(__assign({ // TODO: make this public? + date: date }, dateMeta), { view: viewApi }), props.extraHookProps), { text: text }); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.dayHeaderClassNames, content: options.dayHeaderContent, defaultContent: renderInner$1, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("th", __assign({ ref: rootElRef, className: classNames.concat(customClassNames).join(' '), colSpan: props.colSpan }, props.extraDataAttrs), + createElement("div", { className: "fc-scrollgrid-sync-inner" }, + createElement("a", { className: [ + 'fc-col-header-cell-cushion', + props.isSticky ? 'fc-sticky' : '', + ].join(' '), ref: innerElRef }, innerContent)))); })); + }; + return TableDowCell; + }(BaseComponent)); + + var NowTimer = /** @class */ (function (_super) { + __extends(NowTimer, _super); + function NowTimer(props, context) { + var _this = _super.call(this, props, context) || this; + _this.initialNowDate = getNow(context.options.now, context.dateEnv); + _this.initialNowQueriedMs = new Date().valueOf(); + _this.state = _this.computeTiming().currentState; + return _this; + } + NowTimer.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state; + return props.children(state.nowDate, state.todayRange); + }; + NowTimer.prototype.componentDidMount = function () { + this.setTimeout(); + }; + NowTimer.prototype.componentDidUpdate = function (prevProps) { + if (prevProps.unit !== this.props.unit) { + this.clearTimeout(); + this.setTimeout(); + } + }; + NowTimer.prototype.componentWillUnmount = function () { + this.clearTimeout(); + }; + NowTimer.prototype.computeTiming = function () { + var _a = this, props = _a.props, context = _a.context; + var unroundedNow = addMs(this.initialNowDate, new Date().valueOf() - this.initialNowQueriedMs); + var currentUnitStart = context.dateEnv.startOf(unroundedNow, props.unit); + var nextUnitStart = context.dateEnv.add(currentUnitStart, createDuration(1, props.unit)); + var waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf(); + // there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342) + // ensure no longer than a day + waitMs = Math.min(1000 * 60 * 60 * 24, waitMs); + return { + currentState: { nowDate: currentUnitStart, todayRange: buildDayRange(currentUnitStart) }, + nextState: { nowDate: nextUnitStart, todayRange: buildDayRange(nextUnitStart) }, + waitMs: waitMs, + }; + }; + NowTimer.prototype.setTimeout = function () { + var _this = this; + var _a = this.computeTiming(), nextState = _a.nextState, waitMs = _a.waitMs; + this.timeoutId = setTimeout(function () { + _this.setState(nextState, function () { + _this.setTimeout(); + }); + }, waitMs); + }; + NowTimer.prototype.clearTimeout = function () { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + } + }; + NowTimer.contextType = ViewContextType; + return NowTimer; + }(Component)); + function buildDayRange(date) { + var start = startOfDay(date); + var end = addDays(start, 1); + return { start: start, end: end }; + } + + var DayHeader = /** @class */ (function (_super) { + __extends(DayHeader, _super); + function DayHeader() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.createDayHeaderFormatter = memoize(createDayHeaderFormatter); + return _this; + } + DayHeader.prototype.render = function () { + var context = this.context; + var _a = this.props, dates = _a.dates, dateProfile = _a.dateProfile, datesRepDistinctDays = _a.datesRepDistinctDays, renderIntro = _a.renderIntro; + var dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, datesRepDistinctDays, dates.length); + return (createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { return (createElement("tr", null, + renderIntro && renderIntro('day'), + dates.map(function (date) { return (datesRepDistinctDays ? (createElement(TableDateCell, { key: date.toISOString(), date: date, dateProfile: dateProfile, todayRange: todayRange, colCnt: dates.length, dayHeaderFormat: dayHeaderFormat })) : (createElement(TableDowCell, { key: date.getUTCDay(), dow: date.getUTCDay(), dayHeaderFormat: dayHeaderFormat }))); }))); })); + }; + return DayHeader; + }(BaseComponent)); + function createDayHeaderFormatter(explicitFormat, datesRepDistinctDays, dateCnt) { + return explicitFormat || computeFallbackHeaderFormat(datesRepDistinctDays, dateCnt); + } + + var DaySeriesModel = /** @class */ (function () { + function DaySeriesModel(range, dateProfileGenerator) { + var date = range.start; + var end = range.end; + var indices = []; + var dates = []; + var dayIndex = -1; + while (date < end) { // loop each day from start to end + if (dateProfileGenerator.isHiddenDay(date)) { + indices.push(dayIndex + 0.5); // mark that it's between indices + } + else { + dayIndex += 1; + indices.push(dayIndex); + dates.push(date); + } + date = addDays(date, 1); + } + this.dates = dates; + this.indices = indices; + this.cnt = dates.length; + } + DaySeriesModel.prototype.sliceRange = function (range) { + var firstIndex = this.getDateDayIndex(range.start); // inclusive first index + var lastIndex = this.getDateDayIndex(addDays(range.end, -1)); // inclusive last index + var clippedFirstIndex = Math.max(0, firstIndex); + var clippedLastIndex = Math.min(this.cnt - 1, lastIndex); + // deal with in-between indices + clippedFirstIndex = Math.ceil(clippedFirstIndex); // in-between starts round to next cell + clippedLastIndex = Math.floor(clippedLastIndex); // in-between ends round to prev cell + if (clippedFirstIndex <= clippedLastIndex) { + return { + firstIndex: clippedFirstIndex, + lastIndex: clippedLastIndex, + isStart: firstIndex === clippedFirstIndex, + isEnd: lastIndex === clippedLastIndex, + }; + } + return null; + }; + // Given a date, returns its chronolocial cell-index from the first cell of the grid. + // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets. + // If before the first offset, returns a negative number. + // If after the last offset, returns an offset past the last cell offset. + // Only works for *start* dates of cells. Will not work for exclusive end dates for cells. + DaySeriesModel.prototype.getDateDayIndex = function (date) { + var indices = this.indices; + var dayOffset = Math.floor(diffDays(this.dates[0], date)); + if (dayOffset < 0) { + return indices[0] - 1; + } + if (dayOffset >= indices.length) { + return indices[indices.length - 1] + 1; + } + return indices[dayOffset]; + }; + return DaySeriesModel; + }()); + + var DayTableModel = /** @class */ (function () { + function DayTableModel(daySeries, breakOnWeeks) { + var dates = daySeries.dates; + var daysPerRow; + var firstDay; + var rowCnt; + if (breakOnWeeks) { + // count columns until the day-of-week repeats + firstDay = dates[0].getUTCDay(); + for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow += 1) { + if (dates[daysPerRow].getUTCDay() === firstDay) { + break; + } + } + rowCnt = Math.ceil(dates.length / daysPerRow); + } + else { + rowCnt = 1; + daysPerRow = dates.length; + } + this.rowCnt = rowCnt; + this.colCnt = daysPerRow; + this.daySeries = daySeries; + this.cells = this.buildCells(); + this.headerDates = this.buildHeaderDates(); + } + DayTableModel.prototype.buildCells = function () { + var rows = []; + for (var row = 0; row < this.rowCnt; row += 1) { + var cells = []; + for (var col = 0; col < this.colCnt; col += 1) { + cells.push(this.buildCell(row, col)); + } + rows.push(cells); + } + return rows; + }; + DayTableModel.prototype.buildCell = function (row, col) { + var date = this.daySeries.dates[row * this.colCnt + col]; + return { + key: date.toISOString(), + date: date, + }; + }; + DayTableModel.prototype.buildHeaderDates = function () { + var dates = []; + for (var col = 0; col < this.colCnt; col += 1) { + dates.push(this.cells[0][col].date); + } + return dates; + }; + DayTableModel.prototype.sliceRange = function (range) { + var colCnt = this.colCnt; + var seriesSeg = this.daySeries.sliceRange(range); + var segs = []; + if (seriesSeg) { + var firstIndex = seriesSeg.firstIndex, lastIndex = seriesSeg.lastIndex; + var index = firstIndex; + while (index <= lastIndex) { + var row = Math.floor(index / colCnt); + var nextIndex = Math.min((row + 1) * colCnt, lastIndex + 1); + segs.push({ + row: row, + firstCol: index % colCnt, + lastCol: (nextIndex - 1) % colCnt, + isStart: seriesSeg.isStart && index === firstIndex, + isEnd: seriesSeg.isEnd && (nextIndex - 1) === lastIndex, + }); + index = nextIndex; + } + } + return segs; + }; + return DayTableModel; + }()); + + var Slicer = /** @class */ (function () { + function Slicer() { + this.sliceBusinessHours = memoize(this._sliceBusinessHours); + this.sliceDateSelection = memoize(this._sliceDateSpan); + this.sliceEventStore = memoize(this._sliceEventStore); + this.sliceEventDrag = memoize(this._sliceInteraction); + this.sliceEventResize = memoize(this._sliceInteraction); + this.forceDayIfListItem = false; // hack + } + Slicer.prototype.sliceProps = function (props, dateProfile, nextDayThreshold, context) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + var eventUiBases = props.eventUiBases; + var eventSegs = this.sliceEventStore.apply(this, __spreadArray([props.eventStore, eventUiBases, dateProfile, nextDayThreshold], extraArgs)); + return { + dateSelectionSegs: this.sliceDateSelection.apply(this, __spreadArray([props.dateSelection, eventUiBases, context], extraArgs)), + businessHourSegs: this.sliceBusinessHours.apply(this, __spreadArray([props.businessHours, dateProfile, nextDayThreshold, context], extraArgs)), + fgEventSegs: eventSegs.fg, + bgEventSegs: eventSegs.bg, + eventDrag: this.sliceEventDrag.apply(this, __spreadArray([props.eventDrag, eventUiBases, dateProfile, nextDayThreshold], extraArgs)), + eventResize: this.sliceEventResize.apply(this, __spreadArray([props.eventResize, eventUiBases, dateProfile, nextDayThreshold], extraArgs)), + eventSelection: props.eventSelection, + }; // TODO: give interactionSegs? + }; + Slicer.prototype.sliceNowDate = function (// does not memoize + date, context) { + var extraArgs = []; + for (var _i = 2; _i < arguments.length; _i++) { + extraArgs[_i - 2] = arguments[_i]; + } + return this._sliceDateSpan.apply(this, __spreadArray([{ range: { start: date, end: addMs(date, 1) }, allDay: false }, + {}, + context], extraArgs)); + }; + Slicer.prototype._sliceBusinessHours = function (businessHours, dateProfile, nextDayThreshold, context) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (!businessHours) { + return []; + } + return this._sliceEventStore.apply(this, __spreadArray([expandRecurring(businessHours, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), context), + {}, + dateProfile, + nextDayThreshold], extraArgs)).bg; + }; + Slicer.prototype._sliceEventStore = function (eventStore, eventUiBases, dateProfile, nextDayThreshold) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (eventStore) { + var rangeRes = sliceEventStore(eventStore, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); + return { + bg: this.sliceEventRanges(rangeRes.bg, extraArgs), + fg: this.sliceEventRanges(rangeRes.fg, extraArgs), + }; + } + return { bg: [], fg: [] }; + }; + Slicer.prototype._sliceInteraction = function (interaction, eventUiBases, dateProfile, nextDayThreshold) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (!interaction) { + return null; + } + var rangeRes = sliceEventStore(interaction.mutatedEvents, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); + return { + segs: this.sliceEventRanges(rangeRes.fg, extraArgs), + affectedInstances: interaction.affectedEvents.instances, + isEvent: interaction.isEvent, + }; + }; + Slicer.prototype._sliceDateSpan = function (dateSpan, eventUiBases, context) { + var extraArgs = []; + for (var _i = 3; _i < arguments.length; _i++) { + extraArgs[_i - 3] = arguments[_i]; + } + if (!dateSpan) { + return []; + } + var eventRange = fabricateEventRange(dateSpan, eventUiBases, context); + var segs = this.sliceRange.apply(this, __spreadArray([dateSpan.range], extraArgs)); + for (var _a = 0, segs_1 = segs; _a < segs_1.length; _a++) { + var seg = segs_1[_a]; + seg.eventRange = eventRange; + } + return segs; + }; + /* + "complete" seg means it has component and eventRange + */ + Slicer.prototype.sliceEventRanges = function (eventRanges, extraArgs) { + var segs = []; + for (var _i = 0, eventRanges_1 = eventRanges; _i < eventRanges_1.length; _i++) { + var eventRange = eventRanges_1[_i]; + segs.push.apply(segs, this.sliceEventRange(eventRange, extraArgs)); + } + return segs; + }; + /* + "complete" seg means it has component and eventRange + */ + Slicer.prototype.sliceEventRange = function (eventRange, extraArgs) { + var dateRange = eventRange.range; + // hack to make multi-day events that are being force-displayed as list-items to take up only one day + if (this.forceDayIfListItem && eventRange.ui.display === 'list-item') { + dateRange = { + start: dateRange.start, + end: addDays(dateRange.start, 1), + }; + } + var segs = this.sliceRange.apply(this, __spreadArray([dateRange], extraArgs)); + for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) { + var seg = segs_2[_i]; + seg.eventRange = eventRange; + seg.isStart = eventRange.isStart && seg.isStart; + seg.isEnd = eventRange.isEnd && seg.isEnd; + } + return segs; + }; + return Slicer; + }()); + /* + for incorporating slotMinTime/slotMaxTime if appropriate + TODO: should be part of DateProfile! + TimelineDateProfile already does this btw + */ + function computeActiveRange(dateProfile, isComponentAllDay) { + var range = dateProfile.activeRange; + if (isComponentAllDay) { + return range; + } + return { + start: addMs(range.start, dateProfile.slotMinTime.milliseconds), + end: addMs(range.end, dateProfile.slotMaxTime.milliseconds - 864e5), // 864e5 = ms in a day + }; + } + + // high-level segmenting-aware tester functions + // ------------------------------------------------------------------------------------------------------------------------ + function isInteractionValid(interaction, dateProfile, context) { + var instances = interaction.mutatedEvents.instances; + for (var instanceId in instances) { + if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) { + return false; + } + } + return isNewPropsValid({ eventDrag: interaction }, context); // HACK: the eventDrag props is used for ALL interactions + } + function isDateSelectionValid(dateSelection, dateProfile, context) { + if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) { + return false; + } + return isNewPropsValid({ dateSelection: dateSelection }, context); + } + function isNewPropsValid(newProps, context) { + var calendarState = context.getCurrentData(); + var props = __assign({ businessHours: calendarState.businessHours, dateSelection: '', eventStore: calendarState.eventStore, eventUiBases: calendarState.eventUiBases, eventSelection: '', eventDrag: null, eventResize: null }, newProps); + return (context.pluginHooks.isPropsValid || isPropsValid)(props, context); + } + function isPropsValid(state, context, dateSpanMeta, filterConfig) { + if (dateSpanMeta === void 0) { dateSpanMeta = {}; } + if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) { + return false; + } + if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) { + return false; + } + return true; + } + // Moving Event Validation + // ------------------------------------------------------------------------------------------------------------------------ + function isInteractionPropsValid(state, context, dateSpanMeta, filterConfig) { + var currentState = context.getCurrentData(); + var interaction = state.eventDrag; // HACK: the eventDrag props is used for ALL interactions + var subjectEventStore = interaction.mutatedEvents; + var subjectDefs = subjectEventStore.defs; + var subjectInstances = subjectEventStore.instances; + var subjectConfigs = compileEventUis(subjectDefs, interaction.isEvent ? + state.eventUiBases : + { '': currentState.selectionConfig }); + if (filterConfig) { + subjectConfigs = mapHash(subjectConfigs, filterConfig); + } + // exclude the subject events. TODO: exclude defs too? + var otherEventStore = excludeInstances(state.eventStore, interaction.affectedEvents.instances); + var otherDefs = otherEventStore.defs; + var otherInstances = otherEventStore.instances; + var otherConfigs = compileEventUis(otherDefs, state.eventUiBases); + for (var subjectInstanceId in subjectInstances) { + var subjectInstance = subjectInstances[subjectInstanceId]; + var subjectRange = subjectInstance.range; + var subjectConfig = subjectConfigs[subjectInstance.defId]; + var subjectDef = subjectDefs[subjectInstance.defId]; + // constraint + if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) { + return false; + } + // overlap + var eventOverlap = context.options.eventOverlap; + var eventOverlapFunc = typeof eventOverlap === 'function' ? eventOverlap : null; + for (var otherInstanceId in otherInstances) { + var otherInstance = otherInstances[otherInstanceId]; + // intersect! evaluate + if (rangesIntersect(subjectRange, otherInstance.range)) { + var otherOverlap = otherConfigs[otherInstance.defId].overlap; + // consider the other event's overlap. only do this if the subject event is a "real" event + if (otherOverlap === false && interaction.isEvent) { + return false; + } + if (subjectConfig.overlap === false) { + return false; + } + if (eventOverlapFunc && !eventOverlapFunc(new EventApi(context, otherDefs[otherInstance.defId], otherInstance), // still event + new EventApi(context, subjectDef, subjectInstance))) { + return false; + } + } + } + // allow (a function) + var calendarEventStore = currentState.eventStore; // need global-to-calendar, not local to component (splittable)state + for (var _i = 0, _a = subjectConfig.allows; _i < _a.length; _i++) { + var subjectAllow = _a[_i]; + var subjectDateSpan = __assign(__assign({}, dateSpanMeta), { range: subjectInstance.range, allDay: subjectDef.allDay }); + var origDef = calendarEventStore.defs[subjectDef.defId]; + var origInstance = calendarEventStore.instances[subjectInstanceId]; + var eventApi = void 0; + if (origDef) { // was previously in the calendar + eventApi = new EventApi(context, origDef, origInstance); + } + else { // was an external event + eventApi = new EventApi(context, subjectDef); // no instance, because had no dates + } + if (!subjectAllow(buildDateSpanApiWithContext(subjectDateSpan, context), eventApi)) { + return false; + } + } + } + return true; + } + // Date Selection Validation + // ------------------------------------------------------------------------------------------------------------------------ + function isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig) { + var relevantEventStore = state.eventStore; + var relevantDefs = relevantEventStore.defs; + var relevantInstances = relevantEventStore.instances; + var selection = state.dateSelection; + var selectionRange = selection.range; + var selectionConfig = context.getCurrentData().selectionConfig; + if (filterConfig) { + selectionConfig = filterConfig(selectionConfig); + } + // constraint + if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) { + return false; + } + // overlap + var selectOverlap = context.options.selectOverlap; + var selectOverlapFunc = typeof selectOverlap === 'function' ? selectOverlap : null; + for (var relevantInstanceId in relevantInstances) { + var relevantInstance = relevantInstances[relevantInstanceId]; + // intersect! evaluate + if (rangesIntersect(selectionRange, relevantInstance.range)) { + if (selectionConfig.overlap === false) { + return false; + } + if (selectOverlapFunc && !selectOverlapFunc(new EventApi(context, relevantDefs[relevantInstance.defId], relevantInstance), null)) { + return false; + } + } + } + // allow (a function) + for (var _i = 0, _a = selectionConfig.allows; _i < _a.length; _i++) { + var selectionAllow = _a[_i]; + var fullDateSpan = __assign(__assign({}, dateSpanMeta), selection); + if (!selectionAllow(buildDateSpanApiWithContext(fullDateSpan, context), null)) { + return false; + } + } + return true; + } + // Constraint Utils + // ------------------------------------------------------------------------------------------------------------------------ + function allConstraintsPass(constraints, subjectRange, otherEventStore, businessHoursUnexpanded, context) { + for (var _i = 0, constraints_1 = constraints; _i < constraints_1.length; _i++) { + var constraint = constraints_1[_i]; + if (!anyRangesContainRange(constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context), subjectRange)) { + return false; + } + } + return true; + } + function constraintToRanges(constraint, subjectRange, // for expanding a recurring constraint, or expanding business hours + otherEventStore, // for if constraint is an even group ID + businessHoursUnexpanded, // for if constraint is 'businessHours' + context) { + if (constraint === 'businessHours') { + return eventStoreToRanges(expandRecurring(businessHoursUnexpanded, subjectRange, context)); + } + if (typeof constraint === 'string') { // an group ID + return eventStoreToRanges(filterEventStoreDefs(otherEventStore, function (eventDef) { return eventDef.groupId === constraint; })); + } + if (typeof constraint === 'object' && constraint) { // non-null object + return eventStoreToRanges(expandRecurring(constraint, subjectRange, context)); + } + return []; // if it's false + } + // TODO: move to event-store file? + function eventStoreToRanges(eventStore) { + var instances = eventStore.instances; + var ranges = []; + for (var instanceId in instances) { + ranges.push(instances[instanceId].range); + } + return ranges; + } + // TODO: move to geom file? + function anyRangesContainRange(outerRanges, innerRange) { + for (var _i = 0, outerRanges_1 = outerRanges; _i < outerRanges_1.length; _i++) { + var outerRange = outerRanges_1[_i]; + if (rangeContainsRange(outerRange, innerRange)) { + return true; + } + } + return false; + } + + var VISIBLE_HIDDEN_RE = /^(visible|hidden)$/; + var Scroller = /** @class */ (function (_super) { + __extends(Scroller, _super); + function Scroller() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handleEl = function (el) { + _this.el = el; + setRef(_this.props.elRef, el); + }; + return _this; + } + Scroller.prototype.render = function () { + var props = this.props; + var liquid = props.liquid, liquidIsAbsolute = props.liquidIsAbsolute; + var isAbsolute = liquid && liquidIsAbsolute; + var className = ['fc-scroller']; + if (liquid) { + if (liquidIsAbsolute) { + className.push('fc-scroller-liquid-absolute'); + } + else { + className.push('fc-scroller-liquid'); + } + } + return (createElement("div", { ref: this.handleEl, className: className.join(' '), style: { + overflowX: props.overflowX, + overflowY: props.overflowY, + left: (isAbsolute && -(props.overcomeLeft || 0)) || '', + right: (isAbsolute && -(props.overcomeRight || 0)) || '', + bottom: (isAbsolute && -(props.overcomeBottom || 0)) || '', + marginLeft: (!isAbsolute && -(props.overcomeLeft || 0)) || '', + marginRight: (!isAbsolute && -(props.overcomeRight || 0)) || '', + marginBottom: (!isAbsolute && -(props.overcomeBottom || 0)) || '', + maxHeight: props.maxHeight || '', + } }, props.children)); + }; + Scroller.prototype.needsXScrolling = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) { + return false; + } + // testing scrollWidth>clientWidth is unreliable cross-browser when pixel heights aren't integers. + // much more reliable to see if children are taller than the scroller, even tho doesn't account for + // inner-child margins and absolute positioning + var el = this.el; + var realClientWidth = this.el.getBoundingClientRect().width - this.getYScrollbarWidth(); + var children = el.children; + for (var i = 0; i < children.length; i += 1) { + var childEl = children[i]; + if (childEl.getBoundingClientRect().width > realClientWidth) { + return true; + } + } + return false; + }; + Scroller.prototype.needsYScrolling = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) { + return false; + } + // testing scrollHeight>clientHeight is unreliable cross-browser when pixel heights aren't integers. + // much more reliable to see if children are taller than the scroller, even tho doesn't account for + // inner-child margins and absolute positioning + var el = this.el; + var realClientHeight = this.el.getBoundingClientRect().height - this.getXScrollbarWidth(); + var children = el.children; + for (var i = 0; i < children.length; i += 1) { + var childEl = children[i]; + if (childEl.getBoundingClientRect().height > realClientHeight) { + return true; + } + } + return false; + }; + Scroller.prototype.getXScrollbarWidth = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) { + return 0; + } + return this.el.offsetHeight - this.el.clientHeight; // only works because we guarantee no borders. TODO: add to CSS with important? + }; + Scroller.prototype.getYScrollbarWidth = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) { + return 0; + } + return this.el.offsetWidth - this.el.clientWidth; // only works because we guarantee no borders. TODO: add to CSS with important? + }; + return Scroller; + }(BaseComponent)); + + /* + TODO: somehow infer OtherArgs from masterCallback? + TODO: infer RefType from masterCallback if provided + */ + var RefMap = /** @class */ (function () { + function RefMap(masterCallback) { + var _this = this; + this.masterCallback = masterCallback; + this.currentMap = {}; + this.depths = {}; + this.callbackMap = {}; + this.handleValue = function (val, key) { + var _a = _this, depths = _a.depths, currentMap = _a.currentMap; + var removed = false; + var added = false; + if (val !== null) { + // for bug... ACTUALLY: can probably do away with this now that callers don't share numeric indices anymore + removed = (key in currentMap); + currentMap[key] = val; + depths[key] = (depths[key] || 0) + 1; + added = true; + } + else { + depths[key] -= 1; + if (!depths[key]) { + delete currentMap[key]; + delete _this.callbackMap[key]; + removed = true; + } + } + if (_this.masterCallback) { + if (removed) { + _this.masterCallback(null, String(key)); + } + if (added) { + _this.masterCallback(val, String(key)); + } + } + }; + } + RefMap.prototype.createRef = function (key) { + var _this = this; + var refCallback = this.callbackMap[key]; + if (!refCallback) { + refCallback = this.callbackMap[key] = function (val) { + _this.handleValue(val, String(key)); + }; + } + return refCallback; + }; + // TODO: check callers that don't care about order. should use getAll instead + // NOTE: this method has become less valuable now that we are encouraged to map order by some other index + // TODO: provide ONE array-export function, buildArray, which fails on non-numeric indexes. caller can manipulate and "collect" + RefMap.prototype.collect = function (startIndex, endIndex, step) { + return collectFromHash(this.currentMap, startIndex, endIndex, step); + }; + RefMap.prototype.getAll = function () { + return hashValuesToArray(this.currentMap); + }; + return RefMap; + }()); + + function computeShrinkWidth(chunkEls) { + var shrinkCells = findElements(chunkEls, '.fc-scrollgrid-shrink'); + var largestWidth = 0; + for (var _i = 0, shrinkCells_1 = shrinkCells; _i < shrinkCells_1.length; _i++) { + var shrinkCell = shrinkCells_1[_i]; + largestWidth = Math.max(largestWidth, computeSmallestCellWidth(shrinkCell)); + } + return Math.ceil(largestWidth); // elements work best with integers. round up to ensure contents fits + } + function getSectionHasLiquidHeight(props, sectionConfig) { + return props.liquid && sectionConfig.liquid; // does the section do liquid-height? (need to have whole scrollgrid liquid-height as well) + } + function getAllowYScrolling(props, sectionConfig) { + return sectionConfig.maxHeight != null || // if its possible for the height to max out, we might need scrollbars + getSectionHasLiquidHeight(props, sectionConfig); // if the section is liquid height, it might condense enough to require scrollbars + } + // TODO: ONLY use `arg`. force out internal function to use same API + function renderChunkContent(sectionConfig, chunkConfig, arg) { + var expandRows = arg.expandRows; + var content = typeof chunkConfig.content === 'function' ? + chunkConfig.content(arg) : + createElement('table', { + className: [ + chunkConfig.tableClassName, + sectionConfig.syncRowHeights ? 'fc-scrollgrid-sync-table' : '', + ].join(' '), + style: { + minWidth: arg.tableMinWidth, + width: arg.clientWidth, + height: expandRows ? arg.clientHeight : '', // css `height` on a
serves as a min-height + }, + }, arg.tableColGroupNode, createElement('tbody', {}, typeof chunkConfig.rowContent === 'function' ? chunkConfig.rowContent(arg) : chunkConfig.rowContent)); + return content; + } + function isColPropsEqual(cols0, cols1) { + return isArraysEqual(cols0, cols1, isPropsEqual); + } + function renderMicroColGroup(cols, shrinkWidth) { + var colNodes = []; + /* + for ColProps with spans, it would have been great to make a single + HOWEVER, Chrome was getting messing up distributing the width to elements makes Chrome behave. + */ + for (var _i = 0, cols_1 = cols; _i < cols_1.length; _i++) { + var colProps = cols_1[_i]; + var span = colProps.span || 1; + for (var i = 0; i < span; i += 1) { + colNodes.push(createElement("col", { style: { + width: colProps.width === 'shrink' ? sanitizeShrinkWidth(shrinkWidth) : (colProps.width || ''), + minWidth: colProps.minWidth || '', + } })); + } + } + return createElement.apply(void 0, __spreadArray(['colgroup', {}], colNodes)); + } + function sanitizeShrinkWidth(shrinkWidth) { + /* why 4? if we do 0, it will kill any border, which are needed for computeSmallestCellWidth + 4 accounts for 2 2-pixel borders. TODO: better solution? */ + return shrinkWidth == null ? 4 : shrinkWidth; + } + function hasShrinkWidth(cols) { + for (var _i = 0, cols_2 = cols; _i < cols_2.length; _i++) { + var col = cols_2[_i]; + if (col.width === 'shrink') { + return true; + } + } + return false; + } + function getScrollGridClassNames(liquid, context) { + var classNames = [ + 'fc-scrollgrid', + context.theme.getClass('table'), + ]; + if (liquid) { + classNames.push('fc-scrollgrid-liquid'); + } + return classNames; + } + function getSectionClassNames(sectionConfig, wholeTableVGrow) { + var classNames = [ + 'fc-scrollgrid-section', + "fc-scrollgrid-section-" + sectionConfig.type, + sectionConfig.className, // used? + ]; + if (wholeTableVGrow && sectionConfig.liquid && sectionConfig.maxHeight == null) { + classNames.push('fc-scrollgrid-section-liquid'); + } + if (sectionConfig.isSticky) { + classNames.push('fc-scrollgrid-section-sticky'); + } + return classNames; + } + function renderScrollShim(arg) { + return (createElement("div", { className: "fc-scrollgrid-sticky-shim", style: { + width: arg.clientWidth, + minWidth: arg.tableMinWidth, + } })); + } + function getStickyHeaderDates(options) { + var stickyHeaderDates = options.stickyHeaderDates; + if (stickyHeaderDates == null || stickyHeaderDates === 'auto') { + stickyHeaderDates = options.height === 'auto' || options.viewHeight === 'auto'; + } + return stickyHeaderDates; + } + function getStickyFooterScrollbar(options) { + var stickyFooterScrollbar = options.stickyFooterScrollbar; + if (stickyFooterScrollbar == null || stickyFooterScrollbar === 'auto') { + stickyFooterScrollbar = options.height === 'auto' || options.viewHeight === 'auto'; + } + return stickyFooterScrollbar; + } + + var SimpleScrollGrid = /** @class */ (function (_super) { + __extends(SimpleScrollGrid, _super); + function SimpleScrollGrid() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.processCols = memoize(function (a) { return a; }, isColPropsEqual); // so we get same `cols` props every time + // yucky to memoize VNodes, but much more efficient for consumers + _this.renderMicroColGroup = memoize(renderMicroColGroup); + _this.scrollerRefs = new RefMap(); + _this.scrollerElRefs = new RefMap(_this._handleScrollerEl.bind(_this)); + _this.state = { + shrinkWidth: null, + forceYScrollbars: false, + scrollerClientWidths: {}, + scrollerClientHeights: {}, + }; + // TODO: can do a really simple print-view. dont need to join rows + _this.handleSizing = function () { + _this.setState(__assign({ shrinkWidth: _this.computeShrinkWidth() }, _this.computeScrollerDims())); + }; + return _this; + } + SimpleScrollGrid.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state, context = _a.context; + var sectionConfigs = props.sections || []; + var cols = this.processCols(props.cols); + var microColGroupNode = this.renderMicroColGroup(cols, state.shrinkWidth); + var classNames = getScrollGridClassNames(props.liquid, context); + if (props.collapsibleWidth) { + classNames.push('fc-scrollgrid-collapsible'); + } + // TODO: make DRY + var configCnt = sectionConfigs.length; + var configI = 0; + var currentConfig; + var headSectionNodes = []; + var bodySectionNodes = []; + var footSectionNodes = []; + while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') { + headSectionNodes.push(this.renderSection(currentConfig, microColGroupNode)); + configI += 1; + } + while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') { + bodySectionNodes.push(this.renderSection(currentConfig, microColGroupNode)); + configI += 1; + } + while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') { + footSectionNodes.push(this.renderSection(currentConfig, microColGroupNode)); + configI += 1; + } + // firefox bug: when setting height on table and there is a thead or tfoot, + // the necessary height:100% on the liquid-height body section forces the *whole* table to be taller. (bug #5524) + // use getCanVGrowWithinCell as a way to detect table-stupid firefox. + // if so, use a simpler dom structure, jam everything into a lone tbody. + var isBuggy = !getCanVGrowWithinCell(); + return createElement('table', { + className: classNames.join(' '), + style: { height: props.height }, + }, Boolean(!isBuggy && headSectionNodes.length) && createElement.apply(void 0, __spreadArray(['thead', {}], headSectionNodes)), Boolean(!isBuggy && bodySectionNodes.length) && createElement.apply(void 0, __spreadArray(['tbody', {}], bodySectionNodes)), Boolean(!isBuggy && footSectionNodes.length) && createElement.apply(void 0, __spreadArray(['tfoot', {}], footSectionNodes)), isBuggy && createElement.apply(void 0, __spreadArray(__spreadArray(__spreadArray(['tbody', {}], headSectionNodes), bodySectionNodes), footSectionNodes))); + }; + SimpleScrollGrid.prototype.renderSection = function (sectionConfig, microColGroupNode) { + if ('outerContent' in sectionConfig) { + return (createElement(Fragment, { key: sectionConfig.key }, sectionConfig.outerContent)); + } + return (createElement("tr", { key: sectionConfig.key, className: getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, this.renderChunkTd(sectionConfig, microColGroupNode, sectionConfig.chunk))); + }; + SimpleScrollGrid.prototype.renderChunkTd = function (sectionConfig, microColGroupNode, chunkConfig) { + if ('outerContent' in chunkConfig) { + return chunkConfig.outerContent; + } + var props = this.props; + var _a = this.state, forceYScrollbars = _a.forceYScrollbars, scrollerClientWidths = _a.scrollerClientWidths, scrollerClientHeights = _a.scrollerClientHeights; + var needsYScrolling = getAllowYScrolling(props, sectionConfig); // TODO: do lazily. do in section config? + var isLiquid = getSectionHasLiquidHeight(props, sectionConfig); + // for `!props.liquid` - is WHOLE scrollgrid natural height? + // TODO: do same thing in advanced scrollgrid? prolly not b/c always has horizontal scrollbars + var overflowY = !props.liquid ? 'visible' : + forceYScrollbars ? 'scroll' : + !needsYScrolling ? 'hidden' : + 'auto'; + var sectionKey = sectionConfig.key; + var content = renderChunkContent(sectionConfig, chunkConfig, { + tableColGroupNode: microColGroupNode, + tableMinWidth: '', + clientWidth: (!props.collapsibleWidth && scrollerClientWidths[sectionKey] !== undefined) ? scrollerClientWidths[sectionKey] : null, + clientHeight: scrollerClientHeights[sectionKey] !== undefined ? scrollerClientHeights[sectionKey] : null, + expandRows: sectionConfig.expandRows, + syncRowHeights: false, + rowSyncHeights: [], + reportRowHeightChange: function () { }, + }); + return (createElement("td", { ref: chunkConfig.elRef }, + createElement("div", { className: "fc-scroller-harness" + (isLiquid ? ' fc-scroller-harness-liquid' : '') }, + createElement(Scroller, { ref: this.scrollerRefs.createRef(sectionKey), elRef: this.scrollerElRefs.createRef(sectionKey), overflowY: overflowY, overflowX: !props.liquid ? 'visible' : 'hidden' /* natural height? */, maxHeight: sectionConfig.maxHeight, liquid: isLiquid, liquidIsAbsolute // because its within a harness + : true }, content)))); + }; + SimpleScrollGrid.prototype._handleScrollerEl = function (scrollerEl, key) { + var section = getSectionByKey(this.props.sections, key); + if (section) { + setRef(section.chunk.scrollerElRef, scrollerEl); + } + }; + SimpleScrollGrid.prototype.componentDidMount = function () { + this.handleSizing(); + this.context.addResizeHandler(this.handleSizing); + }; + SimpleScrollGrid.prototype.componentDidUpdate = function () { + // TODO: need better solution when state contains non-sizing things + this.handleSizing(); + }; + SimpleScrollGrid.prototype.componentWillUnmount = function () { + this.context.removeResizeHandler(this.handleSizing); + }; + SimpleScrollGrid.prototype.computeShrinkWidth = function () { + return hasShrinkWidth(this.props.cols) + ? computeShrinkWidth(this.scrollerElRefs.getAll()) + : 0; + }; + SimpleScrollGrid.prototype.computeScrollerDims = function () { + var scrollbarWidth = getScrollbarWidths(); + var _a = this, scrollerRefs = _a.scrollerRefs, scrollerElRefs = _a.scrollerElRefs; + var forceYScrollbars = false; + var scrollerClientWidths = {}; + var scrollerClientHeights = {}; + for (var sectionKey in scrollerRefs.currentMap) { + var scroller = scrollerRefs.currentMap[sectionKey]; + if (scroller && scroller.needsYScrolling()) { + forceYScrollbars = true; + break; + } + } + for (var _i = 0, _b = this.props.sections; _i < _b.length; _i++) { + var section = _b[_i]; + var sectionKey = section.key; + var scrollerEl = scrollerElRefs.currentMap[sectionKey]; + if (scrollerEl) { + var harnessEl = scrollerEl.parentNode; // TODO: weird way to get this. need harness b/c doesn't include table borders + scrollerClientWidths[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().width - (forceYScrollbars + ? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future + : 0)); + scrollerClientHeights[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().height); + } + } + return { forceYScrollbars: forceYScrollbars, scrollerClientWidths: scrollerClientWidths, scrollerClientHeights: scrollerClientHeights }; + }; + return SimpleScrollGrid; + }(BaseComponent)); + SimpleScrollGrid.addStateEquality({ + scrollerClientWidths: isPropsEqual, + scrollerClientHeights: isPropsEqual, + }); + function getSectionByKey(sections, key) { + for (var _i = 0, sections_1 = sections; _i < sections_1.length; _i++) { + var section = sections_1[_i]; + if (section.key === key) { + return section; + } + } + return null; + } + + var EventRoot = /** @class */ (function (_super) { + __extends(EventRoot, _super); + function EventRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.elRef = createRef(); + return _this; + } + EventRoot.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var seg = props.seg; + var eventRange = seg.eventRange; + var ui = eventRange.ui; + var hookProps = { + event: new EventApi(context, eventRange.def, eventRange.instance), + view: context.viewApi, + timeText: props.timeText, + textColor: ui.textColor, + backgroundColor: ui.backgroundColor, + borderColor: ui.borderColor, + isDraggable: !props.disableDragging && computeSegDraggable(seg, context), + isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context), + isEndResizable: !props.disableResizing && computeSegEndResizable(seg), + isMirror: Boolean(props.isDragging || props.isResizing || props.isDateSelecting), + isStart: Boolean(seg.isStart), + isEnd: Boolean(seg.isEnd), + isPast: Boolean(props.isPast), + isFuture: Boolean(props.isFuture), + isToday: Boolean(props.isToday), + isSelected: Boolean(props.isSelected), + isDragging: Boolean(props.isDragging), + isResizing: Boolean(props.isResizing), + }; + var standardClassNames = getEventClassNames(hookProps).concat(ui.classNames); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.eventClassNames, content: options.eventContent, defaultContent: props.defaultContent, didMount: options.eventDidMount, willUnmount: options.eventWillUnmount, elRef: this.elRef }, function (rootElRef, customClassNames, innerElRef, innerContent) { return props.children(rootElRef, standardClassNames.concat(customClassNames), innerElRef, innerContent, hookProps); })); + }; + EventRoot.prototype.componentDidMount = function () { + setElSeg(this.elRef.current, this.props.seg); + }; + /* + need to re-assign seg to the element if seg changes, even if the element is the same + */ + EventRoot.prototype.componentDidUpdate = function (prevProps) { + var seg = this.props.seg; + if (seg !== prevProps.seg) { + setElSeg(this.elRef.current, seg); + } + }; + return EventRoot; + }(BaseComponent)); + + // should not be a purecomponent + var StandardEvent = /** @class */ (function (_super) { + __extends(StandardEvent, _super); + function StandardEvent() { + return _super !== null && _super.apply(this, arguments) || this; + } + StandardEvent.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var seg = props.seg; + var timeFormat = context.options.eventTimeFormat || props.defaultTimeFormat; + var timeText = buildSegTimeText(seg, timeFormat, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd); + return (createElement(EventRoot, { seg: seg, timeText: timeText, disableDragging: props.disableDragging, disableResizing: props.disableResizing, defaultContent: props.defaultContent || renderInnerContent$4, isDragging: props.isDragging, isResizing: props.isResizing, isDateSelecting: props.isDateSelecting, isSelected: props.isSelected, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("a", __assign({ className: props.extraClassNames.concat(classNames).join(' '), style: { + borderColor: hookProps.borderColor, + backgroundColor: hookProps.backgroundColor, + }, ref: rootElRef }, getSegAnchorAttrs$1(seg)), + createElement("div", { className: "fc-event-main", ref: innerElRef, style: { color: hookProps.textColor } }, innerContent), + hookProps.isStartResizable && + createElement("div", { className: "fc-event-resizer fc-event-resizer-start" }), + hookProps.isEndResizable && + createElement("div", { className: "fc-event-resizer fc-event-resizer-end" }))); })); + }; + return StandardEvent; + }(BaseComponent)); + function renderInnerContent$4(innerProps) { + return (createElement("div", { className: "fc-event-main-frame" }, + innerProps.timeText && (createElement("div", { className: "fc-event-time" }, innerProps.timeText)), + createElement("div", { className: "fc-event-title-container" }, + createElement("div", { className: "fc-event-title fc-sticky" }, innerProps.event.title || createElement(Fragment, null, "\u00A0"))))); + } + function getSegAnchorAttrs$1(seg) { + var url = seg.eventRange.def.url; + return url ? { href: url } : {}; + } + + var NowIndicatorRoot = function (props) { return (createElement(ViewContextType.Consumer, null, function (context) { + var options = context.options; + var hookProps = { + isAxis: props.isAxis, + date: context.dateEnv.toDate(props.date), + view: context.viewApi, + }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.nowIndicatorClassNames, content: options.nowIndicatorContent, didMount: options.nowIndicatorDidMount, willUnmount: options.nowIndicatorWillUnmount }, props.children)); + })); }; + + var DAY_NUM_FORMAT = createFormatter({ day: 'numeric' }); + var DayCellContent = /** @class */ (function (_super) { + __extends(DayCellContent, _super); + function DayCellContent() { + return _super !== null && _super.apply(this, arguments) || this; + } + DayCellContent.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var hookProps = refineDayCellHookProps({ + date: props.date, + dateProfile: props.dateProfile, + todayRange: props.todayRange, + showDayNumber: props.showDayNumber, + extraProps: props.extraHookProps, + viewApi: context.viewApi, + dateEnv: context.dateEnv, + }); + return (createElement(ContentHook, { hookProps: hookProps, content: options.dayCellContent, defaultContent: props.defaultContent }, props.children)); + }; + return DayCellContent; + }(BaseComponent)); + function refineDayCellHookProps(raw) { + var date = raw.date, dateEnv = raw.dateEnv; + var dayMeta = getDateMeta(date, raw.todayRange, null, raw.dateProfile); + return __assign(__assign(__assign({ date: dateEnv.toDate(date), view: raw.viewApi }, dayMeta), { dayNumberText: raw.showDayNumber ? dateEnv.format(date, DAY_NUM_FORMAT) : '' }), raw.extraProps); + } + + var DayCellRoot = /** @class */ (function (_super) { + __extends(DayCellRoot, _super); + function DayCellRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.refineHookProps = memoizeObjArg(refineDayCellHookProps); + _this.normalizeClassNames = buildClassNameNormalizer(); + return _this; + } + DayCellRoot.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var hookProps = this.refineHookProps({ + date: props.date, + dateProfile: props.dateProfile, + todayRange: props.todayRange, + showDayNumber: props.showDayNumber, + extraProps: props.extraHookProps, + viewApi: context.viewApi, + dateEnv: context.dateEnv, + }); + var classNames = getDayClassNames(hookProps, context.theme).concat(hookProps.isDisabled + ? [] // don't use custom classNames if disabled + : this.normalizeClassNames(options.dayCellClassNames, hookProps)); + var dataAttrs = hookProps.isDisabled ? {} : { + 'data-date': formatDayString(props.date), + }; + return (createElement(MountHook, { hookProps: hookProps, didMount: options.dayCellDidMount, willUnmount: options.dayCellWillUnmount, elRef: props.elRef }, function (rootElRef) { return props.children(rootElRef, classNames, dataAttrs, hookProps.isDisabled); })); + }; + return DayCellRoot; + }(BaseComponent)); + + function renderFill(fillType) { + return (createElement("div", { className: "fc-" + fillType })); + } + var BgEvent = function (props) { return (createElement(EventRoot, { defaultContent: renderInnerContent$3, seg: props.seg /* uselesss i think */, timeText: "", disableDragging: true, disableResizing: true, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("div", { ref: rootElRef, className: ['fc-bg-event'].concat(classNames).join(' '), style: { + backgroundColor: hookProps.backgroundColor, + } }, innerContent)); })); }; + function renderInnerContent$3(props) { + var title = props.event.title; + return title && (createElement("div", { className: "fc-event-title" }, props.event.title)); + } + + var WeekNumberRoot = function (props) { return (createElement(ViewContextType.Consumer, null, function (context) { + var dateEnv = context.dateEnv, options = context.options; + var date = props.date; + var format = options.weekNumberFormat || props.defaultFormat; + var num = dateEnv.computeWeekNumber(date); // TODO: somehow use for formatting as well? + var text = dateEnv.format(date, format); + var hookProps = { num: num, text: text, date: date }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.weekNumberClassNames, content: options.weekNumberContent, defaultContent: renderInner, didMount: options.weekNumberDidMount, willUnmount: options.weekNumberWillUnmount }, props.children)); + })); }; + function renderInner(innerProps) { + return innerProps.text; + } + + var PADDING_FROM_VIEWPORT = 10; + var Popover = /** @class */ (function (_super) { + __extends(Popover, _super); + function Popover() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handleRootEl = function (el) { + _this.rootEl = el; + if (_this.props.elRef) { + setRef(_this.props.elRef, el); + } + }; + // Triggered when the user clicks *anywhere* in the document, for the autoHide feature + _this.handleDocumentMousedown = function (ev) { + // only hide the popover if the click happened outside the popover + var target = getEventTargetViaRoot(ev); + if (!_this.rootEl.contains(target)) { + _this.handleCloseClick(); + } + }; + _this.handleCloseClick = function () { + var onClose = _this.props.onClose; + if (onClose) { + onClose(); + } + }; + return _this; + } + Popover.prototype.render = function () { + var theme = this.context.theme; + var props = this.props; + var classNames = [ + 'fc-popover', + theme.getClass('popover'), + ].concat(props.extraClassNames || []); + return createPortal(createElement("div", __assign({ className: classNames.join(' ') }, props.extraAttrs, { ref: this.handleRootEl }), + createElement("div", { className: 'fc-popover-header ' + theme.getClass('popoverHeader') }, + createElement("span", { className: "fc-popover-title" }, props.title), + createElement("span", { className: 'fc-popover-close ' + theme.getIconClass('close'), onClick: this.handleCloseClick })), + createElement("div", { className: 'fc-popover-body ' + theme.getClass('popoverContent') }, props.children)), props.parentEl); + }; + Popover.prototype.componentDidMount = function () { + document.addEventListener('mousedown', this.handleDocumentMousedown); + this.updateSize(); + }; + Popover.prototype.componentWillUnmount = function () { + document.removeEventListener('mousedown', this.handleDocumentMousedown); + }; + Popover.prototype.updateSize = function () { + var isRtl = this.context.isRtl; + var _a = this.props, alignmentEl = _a.alignmentEl, alignGridTop = _a.alignGridTop; + var rootEl = this.rootEl; + var alignmentRect = computeClippedClientRect(alignmentEl); + if (alignmentRect) { + var popoverDims = rootEl.getBoundingClientRect(); + // position relative to viewport + var popoverTop = alignGridTop + ? elementClosest(alignmentEl, '.fc-scrollgrid').getBoundingClientRect().top + : alignmentRect.top; + var popoverLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left; + // constrain + popoverTop = Math.max(popoverTop, PADDING_FROM_VIEWPORT); + popoverLeft = Math.min(popoverLeft, document.documentElement.clientWidth - PADDING_FROM_VIEWPORT - popoverDims.width); + popoverLeft = Math.max(popoverLeft, PADDING_FROM_VIEWPORT); + var origin_1 = rootEl.offsetParent.getBoundingClientRect(); + applyStyle(rootEl, { + top: popoverTop - origin_1.top, + left: popoverLeft - origin_1.left, + }); + } + }; + return Popover; + }(BaseComponent)); + + var MorePopover = /** @class */ (function (_super) { + __extends(MorePopover, _super); + function MorePopover() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handleRootEl = function (rootEl) { + _this.rootEl = rootEl; + if (rootEl) { + _this.context.registerInteractiveComponent(_this, { + el: rootEl, + useEventCenter: false, + }); + } + else { + _this.context.unregisterInteractiveComponent(_this); + } + }; + return _this; + } + MorePopover.prototype.render = function () { + var _a = this.context, options = _a.options, dateEnv = _a.dateEnv; + var props = this.props; + var startDate = props.startDate, todayRange = props.todayRange, dateProfile = props.dateProfile; + var title = dateEnv.format(startDate, options.dayPopoverFormat); + return (createElement(DayCellRoot, { date: startDate, dateProfile: dateProfile, todayRange: todayRange, elRef: this.handleRootEl }, function (rootElRef, dayClassNames, dataAttrs) { return (createElement(Popover, { elRef: rootElRef, title: title, extraClassNames: ['fc-more-popover'].concat(dayClassNames), extraAttrs: dataAttrs /* TODO: make these time-based when not whole-day? */, parentEl: props.parentEl, alignmentEl: props.alignmentEl, alignGridTop: props.alignGridTop, onClose: props.onClose }, + createElement(DayCellContent, { date: startDate, dateProfile: dateProfile, todayRange: todayRange }, function (innerElRef, innerContent) { return (innerContent && + createElement("div", { className: "fc-more-popover-misc", ref: innerElRef }, innerContent)); }), + props.children)); })); + }; + MorePopover.prototype.queryHit = function (positionLeft, positionTop, elWidth, elHeight) { + var _a = this, rootEl = _a.rootEl, props = _a.props; + if (positionLeft >= 0 && positionLeft < elWidth && + positionTop >= 0 && positionTop < elHeight) { + return { + dateProfile: props.dateProfile, + dateSpan: __assign({ allDay: true, range: { + start: props.startDate, + end: props.endDate, + } }, props.extraDateSpan), + dayEl: rootEl, + rect: { + left: 0, + top: 0, + right: elWidth, + bottom: elHeight, + }, + layer: 1, // important when comparing with hits from other components + }; + } + return null; + }; + return MorePopover; + }(DateComponent)); + + var MoreLinkRoot = /** @class */ (function (_super) { + __extends(MoreLinkRoot, _super); + function MoreLinkRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.linkElRef = createRef(); + _this.state = { + isPopoverOpen: false, + }; + _this.handleClick = function (ev) { + var _a = _this, props = _a.props, context = _a.context; + var moreLinkClick = context.options.moreLinkClick; + var date = computeRange(props).start; + function buildPublicSeg(seg) { + var _a = seg.eventRange, def = _a.def, instance = _a.instance, range = _a.range; + return { + event: new EventApi(context, def, instance), + start: context.dateEnv.toDate(range.start), + end: context.dateEnv.toDate(range.end), + isStart: seg.isStart, + isEnd: seg.isEnd, + }; + } + if (typeof moreLinkClick === 'function') { + moreLinkClick = moreLinkClick({ + date: date, + allDay: Boolean(props.allDayDate), + allSegs: props.allSegs.map(buildPublicSeg), + hiddenSegs: props.hiddenSegs.map(buildPublicSeg), + jsEvent: ev, + view: context.viewApi, + }); + } + if (!moreLinkClick || moreLinkClick === 'popover') { + _this.setState({ isPopoverOpen: true }); + } + else if (typeof moreLinkClick === 'string') { // a view name + context.calendarApi.zoomTo(date, moreLinkClick); + } + }; + _this.handlePopoverClose = function () { + _this.setState({ isPopoverOpen: false }); + }; + return _this; + } + MoreLinkRoot.prototype.render = function () { + var _this = this; + var props = this.props; + return (createElement(ViewContextType.Consumer, null, function (context) { + var viewApi = context.viewApi, options = context.options, calendarApi = context.calendarApi; + var moreLinkText = options.moreLinkText; + var moreCnt = props.moreCnt; + var range = computeRange(props); + var hookProps = { + num: moreCnt, + shortText: "+" + moreCnt, + text: typeof moreLinkText === 'function' + ? moreLinkText.call(calendarApi, moreCnt) + : "+" + moreCnt + " " + moreLinkText, + view: viewApi, + }; + return (createElement(Fragment, null, + Boolean(props.moreCnt) && (createElement(RenderHook, { elRef: _this.linkElRef, hookProps: hookProps, classNames: options.moreLinkClassNames, content: options.moreLinkContent, defaultContent: props.defaultContent || renderMoreLinkInner$1, didMount: options.moreLinkDidMount, willUnmount: options.moreLinkWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return props.children(rootElRef, ['fc-more-link'].concat(customClassNames), innerElRef, innerContent, _this.handleClick); })), + _this.state.isPopoverOpen && (createElement(MorePopover, { startDate: range.start, endDate: range.end, dateProfile: props.dateProfile, todayRange: props.todayRange, extraDateSpan: props.extraDateSpan, parentEl: _this.parentEl, alignmentEl: props.alignmentElRef.current, alignGridTop: props.alignGridTop, onClose: _this.handlePopoverClose }, props.popoverContent())))); + })); + }; + MoreLinkRoot.prototype.componentDidMount = function () { + this.updateParentEl(); + }; + MoreLinkRoot.prototype.componentDidUpdate = function () { + this.updateParentEl(); + }; + MoreLinkRoot.prototype.updateParentEl = function () { + if (this.linkElRef.current) { + this.parentEl = elementClosest(this.linkElRef.current, '.fc-view-harness'); + } + }; + return MoreLinkRoot; + }(BaseComponent)); + function renderMoreLinkInner$1(props) { + return props.text; + } + function computeRange(props) { + if (props.allDayDate) { + return { + start: props.allDayDate, + end: addDays(props.allDayDate, 1), + }; + } + var hiddenSegs = props.hiddenSegs; + return { + start: computeEarliestSegStart(hiddenSegs), + end: computeLatestSegEnd(hiddenSegs), + }; + } + function computeEarliestSegStart(segs) { + return segs.reduce(pickEarliestStart).eventRange.range.start; + } + function pickEarliestStart(seg0, seg1) { + return seg0.eventRange.range.start < seg1.eventRange.range.start ? seg0 : seg1; + } + function computeLatestSegEnd(segs) { + return segs.reduce(pickLatestEnd).eventRange.range.end; + } + function pickLatestEnd(seg0, seg1) { + return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1; + } + + // exports + // -------------------------------------------------------------------------------------------------- + var version = '5.9.0'; // important to type it, so .d.ts has generic string + + var Calendar = /** @class */ (function (_super) { + __extends(Calendar, _super); + function Calendar(el, optionOverrides) { + if (optionOverrides === void 0) { optionOverrides = {}; } + var _this = _super.call(this) || this; + _this.isRendering = false; + _this.isRendered = false; + _this.currentClassNames = []; + _this.customContentRenderId = 0; // will affect custom generated classNames? + _this.handleAction = function (action) { + // actions we know we want to render immediately + switch (action.type) { + case 'SET_EVENT_DRAG': + case 'SET_EVENT_RESIZE': + _this.renderRunner.tryDrain(); + } + }; + _this.handleData = function (data) { + _this.currentData = data; + _this.renderRunner.request(data.calendarOptions.rerenderDelay); + }; + _this.handleRenderRequest = function () { + if (_this.isRendering) { + _this.isRendered = true; + var currentData_1 = _this.currentData; + render(createElement(CalendarRoot, { options: currentData_1.calendarOptions, theme: currentData_1.theme, emitter: currentData_1.emitter }, function (classNames, height, isHeightAuto, forPrint) { + _this.setClassNames(classNames); + _this.setHeight(height); + return (createElement(CustomContentRenderContext.Provider, { value: _this.customContentRenderId }, + createElement(CalendarContent, __assign({ isHeightAuto: isHeightAuto, forPrint: forPrint }, currentData_1)))); + }), _this.el); + } + else if (_this.isRendered) { + _this.isRendered = false; + unmountComponentAtNode(_this.el); + _this.setClassNames([]); + _this.setHeight(''); + } + flushToDom(); + }; + _this.el = el; + _this.renderRunner = new DelayedRunner(_this.handleRenderRequest); + new CalendarDataManager({ + optionOverrides: optionOverrides, + calendarApi: _this, + onAction: _this.handleAction, + onData: _this.handleData, + }); + return _this; + } + Object.defineProperty(Calendar.prototype, "view", { + get: function () { return this.currentData.viewApi; } // for public API + , + enumerable: false, + configurable: true + }); + Calendar.prototype.render = function () { + var wasRendering = this.isRendering; + if (!wasRendering) { + this.isRendering = true; + } + else { + this.customContentRenderId += 1; + } + this.renderRunner.request(); + if (wasRendering) { + this.updateSize(); + } + }; + Calendar.prototype.destroy = function () { + if (this.isRendering) { + this.isRendering = false; + this.renderRunner.request(); + } + }; + Calendar.prototype.updateSize = function () { + _super.prototype.updateSize.call(this); + flushToDom(); + }; + Calendar.prototype.batchRendering = function (func) { + this.renderRunner.pause('batchRendering'); + func(); + this.renderRunner.resume('batchRendering'); + }; + Calendar.prototype.pauseRendering = function () { + this.renderRunner.pause('pauseRendering'); + }; + Calendar.prototype.resumeRendering = function () { + this.renderRunner.resume('pauseRendering', true); + }; + Calendar.prototype.resetOptions = function (optionOverrides, append) { + this.currentDataManager.resetOptions(optionOverrides, append); + }; + Calendar.prototype.setClassNames = function (classNames) { + if (!isArraysEqual(classNames, this.currentClassNames)) { + var classList = this.el.classList; + for (var _i = 0, _a = this.currentClassNames; _i < _a.length; _i++) { + var className = _a[_i]; + classList.remove(className); + } + for (var _b = 0, classNames_1 = classNames; _b < classNames_1.length; _b++) { + var className = classNames_1[_b]; + classList.add(className); + } + this.currentClassNames = classNames; + } + }; + Calendar.prototype.setHeight = function (height) { + applyStyleProp(this.el, 'height', height); + }; + return Calendar; + }(CalendarApi)); + + config.touchMouseIgnoreWait = 500; + var ignoreMouseDepth = 0; + var listenerCnt = 0; + var isWindowTouchMoveCancelled = false; + /* + Uses a "pointer" abstraction, which monitors UI events for both mouse and touch. + Tracks when the pointer "drags" on a certain element, meaning down+move+up. + + Also, tracks if there was touch-scrolling. + Also, can prevent touch-scrolling from happening. + Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement. + + emits: + - pointerdown + - pointermove + - pointerup + */ + var PointerDragging = /** @class */ (function () { + function PointerDragging(containerEl) { + var _this = this; + this.subjectEl = null; + // options that can be directly assigned by caller + this.selector = ''; // will cause subjectEl in all emitted events to be this element + this.handleSelector = ''; + this.shouldIgnoreMove = false; + this.shouldWatchScroll = true; // for simulating pointermove on scroll + // internal states + this.isDragging = false; + this.isTouchDragging = false; + this.wasTouchScroll = false; + // Mouse + // ---------------------------------------------------------------------------------------------------- + this.handleMouseDown = function (ev) { + if (!_this.shouldIgnoreMouse() && + isPrimaryMouseButton(ev) && + _this.tryStart(ev)) { + var pev = _this.createEventFromMouse(ev, true); + _this.emitter.trigger('pointerdown', pev); + _this.initScrollWatch(pev); + if (!_this.shouldIgnoreMove) { + document.addEventListener('mousemove', _this.handleMouseMove); + } + document.addEventListener('mouseup', _this.handleMouseUp); + } + }; + this.handleMouseMove = function (ev) { + var pev = _this.createEventFromMouse(ev); + _this.recordCoords(pev); + _this.emitter.trigger('pointermove', pev); + }; + this.handleMouseUp = function (ev) { + document.removeEventListener('mousemove', _this.handleMouseMove); + document.removeEventListener('mouseup', _this.handleMouseUp); + _this.emitter.trigger('pointerup', _this.createEventFromMouse(ev)); + _this.cleanup(); // call last so that pointerup has access to props + }; + // Touch + // ---------------------------------------------------------------------------------------------------- + this.handleTouchStart = function (ev) { + if (_this.tryStart(ev)) { + _this.isTouchDragging = true; + var pev = _this.createEventFromTouch(ev, true); + _this.emitter.trigger('pointerdown', pev); + _this.initScrollWatch(pev); + // unlike mouse, need to attach to target, not document + // https://stackoverflow.com/a/45760014 + var targetEl = ev.target; + if (!_this.shouldIgnoreMove) { + targetEl.addEventListener('touchmove', _this.handleTouchMove); + } + targetEl.addEventListener('touchend', _this.handleTouchEnd); + targetEl.addEventListener('touchcancel', _this.handleTouchEnd); // treat it as a touch end + // attach a handler to get called when ANY scroll action happens on the page. + // this was impossible to do with normal on/off because 'scroll' doesn't bubble. + // http://stackoverflow.com/a/32954565/96342 + window.addEventListener('scroll', _this.handleTouchScroll, true); + } + }; + this.handleTouchMove = function (ev) { + var pev = _this.createEventFromTouch(ev); + _this.recordCoords(pev); + _this.emitter.trigger('pointermove', pev); + }; + this.handleTouchEnd = function (ev) { + if (_this.isDragging) { // done to guard against touchend followed by touchcancel + var targetEl = ev.target; + targetEl.removeEventListener('touchmove', _this.handleTouchMove); + targetEl.removeEventListener('touchend', _this.handleTouchEnd); + targetEl.removeEventListener('touchcancel', _this.handleTouchEnd); + window.removeEventListener('scroll', _this.handleTouchScroll, true); // useCaptured=true + _this.emitter.trigger('pointerup', _this.createEventFromTouch(ev)); + _this.cleanup(); // call last so that pointerup has access to props + _this.isTouchDragging = false; + startIgnoringMouse(); + } + }; + this.handleTouchScroll = function () { + _this.wasTouchScroll = true; + }; + this.handleScroll = function (ev) { + if (!_this.shouldIgnoreMove) { + var pageX = (window.pageXOffset - _this.prevScrollX) + _this.prevPageX; + var pageY = (window.pageYOffset - _this.prevScrollY) + _this.prevPageY; + _this.emitter.trigger('pointermove', { + origEvent: ev, + isTouch: _this.isTouchDragging, + subjectEl: _this.subjectEl, + pageX: pageX, + pageY: pageY, + deltaX: pageX - _this.origPageX, + deltaY: pageY - _this.origPageY, + }); + } + }; + this.containerEl = containerEl; + this.emitter = new Emitter(); + containerEl.addEventListener('mousedown', this.handleMouseDown); + containerEl.addEventListener('touchstart', this.handleTouchStart, { passive: true }); + listenerCreated(); + } + PointerDragging.prototype.destroy = function () { + this.containerEl.removeEventListener('mousedown', this.handleMouseDown); + this.containerEl.removeEventListener('touchstart', this.handleTouchStart, { passive: true }); + listenerDestroyed(); + }; + PointerDragging.prototype.tryStart = function (ev) { + var subjectEl = this.querySubjectEl(ev); + var downEl = ev.target; + if (subjectEl && + (!this.handleSelector || elementClosest(downEl, this.handleSelector))) { + this.subjectEl = subjectEl; + this.isDragging = true; // do this first so cancelTouchScroll will work + this.wasTouchScroll = false; + return true; + } + return false; + }; + PointerDragging.prototype.cleanup = function () { + isWindowTouchMoveCancelled = false; + this.isDragging = false; + this.subjectEl = null; + // keep wasTouchScroll around for later access + this.destroyScrollWatch(); + }; + PointerDragging.prototype.querySubjectEl = function (ev) { + if (this.selector) { + return elementClosest(ev.target, this.selector); + } + return this.containerEl; + }; + PointerDragging.prototype.shouldIgnoreMouse = function () { + return ignoreMouseDepth || this.isTouchDragging; + }; + // can be called by user of this class, to cancel touch-based scrolling for the current drag + PointerDragging.prototype.cancelTouchScroll = function () { + if (this.isDragging) { + isWindowTouchMoveCancelled = true; + } + }; + // Scrolling that simulates pointermoves + // ---------------------------------------------------------------------------------------------------- + PointerDragging.prototype.initScrollWatch = function (ev) { + if (this.shouldWatchScroll) { + this.recordCoords(ev); + window.addEventListener('scroll', this.handleScroll, true); // useCapture=true + } + }; + PointerDragging.prototype.recordCoords = function (ev) { + if (this.shouldWatchScroll) { + this.prevPageX = ev.pageX; + this.prevPageY = ev.pageY; + this.prevScrollX = window.pageXOffset; + this.prevScrollY = window.pageYOffset; + } + }; + PointerDragging.prototype.destroyScrollWatch = function () { + if (this.shouldWatchScroll) { + window.removeEventListener('scroll', this.handleScroll, true); // useCaptured=true + } + }; + // Event Normalization + // ---------------------------------------------------------------------------------------------------- + PointerDragging.prototype.createEventFromMouse = function (ev, isFirst) { + var deltaX = 0; + var deltaY = 0; + // TODO: repeat code + if (isFirst) { + this.origPageX = ev.pageX; + this.origPageY = ev.pageY; + } + else { + deltaX = ev.pageX - this.origPageX; + deltaY = ev.pageY - this.origPageY; + } + return { + origEvent: ev, + isTouch: false, + subjectEl: this.subjectEl, + pageX: ev.pageX, + pageY: ev.pageY, + deltaX: deltaX, + deltaY: deltaY, + }; + }; + PointerDragging.prototype.createEventFromTouch = function (ev, isFirst) { + var touches = ev.touches; + var pageX; + var pageY; + var deltaX = 0; + var deltaY = 0; + // if touch coords available, prefer, + // because FF would give bad ev.pageX ev.pageY + if (touches && touches.length) { + pageX = touches[0].pageX; + pageY = touches[0].pageY; + } + else { + pageX = ev.pageX; + pageY = ev.pageY; + } + // TODO: repeat code + if (isFirst) { + this.origPageX = pageX; + this.origPageY = pageY; + } + else { + deltaX = pageX - this.origPageX; + deltaY = pageY - this.origPageY; + } + return { + origEvent: ev, + isTouch: true, + subjectEl: this.subjectEl, + pageX: pageX, + pageY: pageY, + deltaX: deltaX, + deltaY: deltaY, + }; + }; + return PointerDragging; + }()); + // Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) + function isPrimaryMouseButton(ev) { + return ev.button === 0 && !ev.ctrlKey; + } + // Ignoring fake mouse events generated by touch + // ---------------------------------------------------------------------------------------------------- + function startIgnoringMouse() { + ignoreMouseDepth += 1; + setTimeout(function () { + ignoreMouseDepth -= 1; + }, config.touchMouseIgnoreWait); + } + // We want to attach touchmove as early as possible for Safari + // ---------------------------------------------------------------------------------------------------- + function listenerCreated() { + listenerCnt += 1; + if (listenerCnt === 1) { + window.addEventListener('touchmove', onWindowTouchMove, { passive: false }); + } + } + function listenerDestroyed() { + listenerCnt -= 1; + if (!listenerCnt) { + window.removeEventListener('touchmove', onWindowTouchMove, { passive: false }); + } + } + function onWindowTouchMove(ev) { + if (isWindowTouchMoveCancelled) { + ev.preventDefault(); + } + } + + /* + An effect in which an element follows the movement of a pointer across the screen. + The moving element is a clone of some other element. + Must call start + handleMove + stop. + */ + var ElementMirror = /** @class */ (function () { + function ElementMirror() { + this.isVisible = false; // must be explicitly enabled + this.sourceEl = null; + this.mirrorEl = null; + this.sourceElRect = null; // screen coords relative to viewport + // options that can be set directly by caller + this.parentNode = document.body; // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues + this.zIndex = 9999; + this.revertDuration = 0; + } + ElementMirror.prototype.start = function (sourceEl, pageX, pageY) { + this.sourceEl = sourceEl; + this.sourceElRect = this.sourceEl.getBoundingClientRect(); + this.origScreenX = pageX - window.pageXOffset; + this.origScreenY = pageY - window.pageYOffset; + this.deltaX = 0; + this.deltaY = 0; + this.updateElPosition(); + }; + ElementMirror.prototype.handleMove = function (pageX, pageY) { + this.deltaX = (pageX - window.pageXOffset) - this.origScreenX; + this.deltaY = (pageY - window.pageYOffset) - this.origScreenY; + this.updateElPosition(); + }; + // can be called before start + ElementMirror.prototype.setIsVisible = function (bool) { + if (bool) { + if (!this.isVisible) { + if (this.mirrorEl) { + this.mirrorEl.style.display = ''; + } + this.isVisible = bool; // needs to happen before updateElPosition + this.updateElPosition(); // because was not updating the position while invisible + } + } + else if (this.isVisible) { + if (this.mirrorEl) { + this.mirrorEl.style.display = 'none'; + } + this.isVisible = bool; + } + }; + // always async + ElementMirror.prototype.stop = function (needsRevertAnimation, callback) { + var _this = this; + var done = function () { + _this.cleanup(); + callback(); + }; + if (needsRevertAnimation && + this.mirrorEl && + this.isVisible && + this.revertDuration && // if 0, transition won't work + (this.deltaX || this.deltaY) // if same coords, transition won't work + ) { + this.doRevertAnimation(done, this.revertDuration); + } + else { + setTimeout(done, 0); + } + }; + ElementMirror.prototype.doRevertAnimation = function (callback, revertDuration) { + var mirrorEl = this.mirrorEl; + var finalSourceElRect = this.sourceEl.getBoundingClientRect(); // because autoscrolling might have happened + mirrorEl.style.transition = + 'top ' + revertDuration + 'ms,' + + 'left ' + revertDuration + 'ms'; + applyStyle(mirrorEl, { + left: finalSourceElRect.left, + top: finalSourceElRect.top, + }); + whenTransitionDone(mirrorEl, function () { + mirrorEl.style.transition = ''; + callback(); + }); + }; + ElementMirror.prototype.cleanup = function () { + if (this.mirrorEl) { + removeElement(this.mirrorEl); + this.mirrorEl = null; + } + this.sourceEl = null; + }; + ElementMirror.prototype.updateElPosition = function () { + if (this.sourceEl && this.isVisible) { + applyStyle(this.getMirrorEl(), { + left: this.sourceElRect.left + this.deltaX, + top: this.sourceElRect.top + this.deltaY, + }); + } + }; + ElementMirror.prototype.getMirrorEl = function () { + var sourceElRect = this.sourceElRect; + var mirrorEl = this.mirrorEl; + if (!mirrorEl) { + mirrorEl = this.mirrorEl = this.sourceEl.cloneNode(true); // cloneChildren=true + // we don't want long taps or any mouse interaction causing selection/menus. + // would use preventSelection(), but that prevents selectstart, causing problems. + mirrorEl.classList.add('fc-unselectable'); + mirrorEl.classList.add('fc-event-dragging'); + applyStyle(mirrorEl, { + position: 'fixed', + zIndex: this.zIndex, + visibility: '', + boxSizing: 'border-box', + width: sourceElRect.right - sourceElRect.left, + height: sourceElRect.bottom - sourceElRect.top, + right: 'auto', + bottom: 'auto', + margin: 0, + }); + this.parentNode.appendChild(mirrorEl); + } + return mirrorEl; + }; + return ElementMirror; + }()); + + /* + Is a cache for a given element's scroll information (all the info that ScrollController stores) + in addition the "client rectangle" of the element.. the area within the scrollbars. + + The cache can be in one of two modes: + - doesListening:false - ignores when the container is scrolled by someone else + - doesListening:true - watch for scrolling and update the cache + */ + var ScrollGeomCache = /** @class */ (function (_super) { + __extends(ScrollGeomCache, _super); + function ScrollGeomCache(scrollController, doesListening) { + var _this = _super.call(this) || this; + _this.handleScroll = function () { + _this.scrollTop = _this.scrollController.getScrollTop(); + _this.scrollLeft = _this.scrollController.getScrollLeft(); + _this.handleScrollChange(); + }; + _this.scrollController = scrollController; + _this.doesListening = doesListening; + _this.scrollTop = _this.origScrollTop = scrollController.getScrollTop(); + _this.scrollLeft = _this.origScrollLeft = scrollController.getScrollLeft(); + _this.scrollWidth = scrollController.getScrollWidth(); + _this.scrollHeight = scrollController.getScrollHeight(); + _this.clientWidth = scrollController.getClientWidth(); + _this.clientHeight = scrollController.getClientHeight(); + _this.clientRect = _this.computeClientRect(); // do last in case it needs cached values + if (_this.doesListening) { + _this.getEventTarget().addEventListener('scroll', _this.handleScroll); + } + return _this; + } + ScrollGeomCache.prototype.destroy = function () { + if (this.doesListening) { + this.getEventTarget().removeEventListener('scroll', this.handleScroll); + } + }; + ScrollGeomCache.prototype.getScrollTop = function () { + return this.scrollTop; + }; + ScrollGeomCache.prototype.getScrollLeft = function () { + return this.scrollLeft; + }; + ScrollGeomCache.prototype.setScrollTop = function (top) { + this.scrollController.setScrollTop(top); + if (!this.doesListening) { + // we are not relying on the element to normalize out-of-bounds scroll values + // so we need to sanitize ourselves + this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0); + this.handleScrollChange(); + } + }; + ScrollGeomCache.prototype.setScrollLeft = function (top) { + this.scrollController.setScrollLeft(top); + if (!this.doesListening) { + // we are not relying on the element to normalize out-of-bounds scroll values + // so we need to sanitize ourselves + this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0); + this.handleScrollChange(); + } + }; + ScrollGeomCache.prototype.getClientWidth = function () { + return this.clientWidth; + }; + ScrollGeomCache.prototype.getClientHeight = function () { + return this.clientHeight; + }; + ScrollGeomCache.prototype.getScrollWidth = function () { + return this.scrollWidth; + }; + ScrollGeomCache.prototype.getScrollHeight = function () { + return this.scrollHeight; + }; + ScrollGeomCache.prototype.handleScrollChange = function () { + }; + return ScrollGeomCache; + }(ScrollController)); + + var ElementScrollGeomCache = /** @class */ (function (_super) { + __extends(ElementScrollGeomCache, _super); + function ElementScrollGeomCache(el, doesListening) { + return _super.call(this, new ElementScrollController(el), doesListening) || this; + } + ElementScrollGeomCache.prototype.getEventTarget = function () { + return this.scrollController.el; + }; + ElementScrollGeomCache.prototype.computeClientRect = function () { + return computeInnerRect(this.scrollController.el); + }; + return ElementScrollGeomCache; + }(ScrollGeomCache)); + + var WindowScrollGeomCache = /** @class */ (function (_super) { + __extends(WindowScrollGeomCache, _super); + function WindowScrollGeomCache(doesListening) { + return _super.call(this, new WindowScrollController(), doesListening) || this; + } + WindowScrollGeomCache.prototype.getEventTarget = function () { + return window; + }; + WindowScrollGeomCache.prototype.computeClientRect = function () { + return { + left: this.scrollLeft, + right: this.scrollLeft + this.clientWidth, + top: this.scrollTop, + bottom: this.scrollTop + this.clientHeight, + }; + }; + // the window is the only scroll object that changes it's rectangle relative + // to the document's topleft as it scrolls + WindowScrollGeomCache.prototype.handleScrollChange = function () { + this.clientRect = this.computeClientRect(); + }; + return WindowScrollGeomCache; + }(ScrollGeomCache)); + + // If available we are using native "performance" API instead of "Date" + // Read more about it on MDN: + // https://developer.mozilla.org/en-US/docs/Web/API/Performance + var getTime = typeof performance === 'function' ? performance.now : Date.now; + /* + For a pointer interaction, automatically scrolls certain scroll containers when the pointer + approaches the edge. + + The caller must call start + handleMove + stop. + */ + var AutoScroller = /** @class */ (function () { + function AutoScroller() { + var _this = this; + // options that can be set by caller + this.isEnabled = true; + this.scrollQuery = [window, '.fc-scroller']; + this.edgeThreshold = 50; // pixels + this.maxVelocity = 300; // pixels per second + // internal state + this.pointerScreenX = null; + this.pointerScreenY = null; + this.isAnimating = false; + this.scrollCaches = null; + // protect against the initial pointerdown being too close to an edge and starting the scroll + this.everMovedUp = false; + this.everMovedDown = false; + this.everMovedLeft = false; + this.everMovedRight = false; + this.animate = function () { + if (_this.isAnimating) { // wasn't cancelled between animation calls + var edge = _this.computeBestEdge(_this.pointerScreenX + window.pageXOffset, _this.pointerScreenY + window.pageYOffset); + if (edge) { + var now = getTime(); + _this.handleSide(edge, (now - _this.msSinceRequest) / 1000); + _this.requestAnimation(now); + } + else { + _this.isAnimating = false; // will stop animation + } + } + }; + } + AutoScroller.prototype.start = function (pageX, pageY, scrollStartEl) { + if (this.isEnabled) { + this.scrollCaches = this.buildCaches(scrollStartEl); + this.pointerScreenX = null; + this.pointerScreenY = null; + this.everMovedUp = false; + this.everMovedDown = false; + this.everMovedLeft = false; + this.everMovedRight = false; + this.handleMove(pageX, pageY); + } + }; + AutoScroller.prototype.handleMove = function (pageX, pageY) { + if (this.isEnabled) { + var pointerScreenX = pageX - window.pageXOffset; + var pointerScreenY = pageY - window.pageYOffset; + var yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY; + var xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX; + if (yDelta < 0) { + this.everMovedUp = true; + } + else if (yDelta > 0) { + this.everMovedDown = true; + } + if (xDelta < 0) { + this.everMovedLeft = true; + } + else if (xDelta > 0) { + this.everMovedRight = true; + } + this.pointerScreenX = pointerScreenX; + this.pointerScreenY = pointerScreenY; + if (!this.isAnimating) { + this.isAnimating = true; + this.requestAnimation(getTime()); + } + } + }; + AutoScroller.prototype.stop = function () { + if (this.isEnabled) { + this.isAnimating = false; // will stop animation + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + scrollCache.destroy(); + } + this.scrollCaches = null; + } + }; + AutoScroller.prototype.requestAnimation = function (now) { + this.msSinceRequest = now; + requestAnimationFrame(this.animate); + }; + AutoScroller.prototype.handleSide = function (edge, seconds) { + var scrollCache = edge.scrollCache; + var edgeThreshold = this.edgeThreshold; + var invDistance = edgeThreshold - edge.distance; + var velocity = // the closer to the edge, the faster we scroll + ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic + this.maxVelocity * seconds; + var sign = 1; + switch (edge.name) { + case 'left': + sign = -1; + // falls through + case 'right': + scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign); + break; + case 'top': + sign = -1; + // falls through + case 'bottom': + scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign); + break; + } + }; + // left/top are relative to document topleft + AutoScroller.prototype.computeBestEdge = function (left, top) { + var edgeThreshold = this.edgeThreshold; + var bestSide = null; + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + var rect = scrollCache.clientRect; + var leftDist = left - rect.left; + var rightDist = rect.right - left; + var topDist = top - rect.top; + var bottomDist = rect.bottom - top; + // completely within the rect? + if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) { + if (topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() && + (!bestSide || bestSide.distance > topDist)) { + bestSide = { scrollCache: scrollCache, name: 'top', distance: topDist }; + } + if (bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() && + (!bestSide || bestSide.distance > bottomDist)) { + bestSide = { scrollCache: scrollCache, name: 'bottom', distance: bottomDist }; + } + if (leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() && + (!bestSide || bestSide.distance > leftDist)) { + bestSide = { scrollCache: scrollCache, name: 'left', distance: leftDist }; + } + if (rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() && + (!bestSide || bestSide.distance > rightDist)) { + bestSide = { scrollCache: scrollCache, name: 'right', distance: rightDist }; + } + } + } + return bestSide; + }; + AutoScroller.prototype.buildCaches = function (scrollStartEl) { + return this.queryScrollEls(scrollStartEl).map(function (el) { + if (el === window) { + return new WindowScrollGeomCache(false); // false = don't listen to user-generated scrolls + } + return new ElementScrollGeomCache(el, false); // false = don't listen to user-generated scrolls + }); + }; + AutoScroller.prototype.queryScrollEls = function (scrollStartEl) { + var els = []; + for (var _i = 0, _a = this.scrollQuery; _i < _a.length; _i++) { + var query = _a[_i]; + if (typeof query === 'object') { + els.push(query); + } + else { + els.push.apply(els, Array.prototype.slice.call(getElRoot(scrollStartEl).querySelectorAll(query))); + } + } + return els; + }; + return AutoScroller; + }()); + + /* + Monitors dragging on an element. Has a number of high-level features: + - minimum distance required before dragging + - minimum wait time ("delay") before dragging + - a mirror element that follows the pointer + */ + var FeaturefulElementDragging = /** @class */ (function (_super) { + __extends(FeaturefulElementDragging, _super); + function FeaturefulElementDragging(containerEl, selector) { + var _this = _super.call(this, containerEl) || this; + _this.containerEl = containerEl; + // options that can be directly set by caller + // the caller can also set the PointerDragging's options as well + _this.delay = null; + _this.minDistance = 0; + _this.touchScrollAllowed = true; // prevents drag from starting and blocks scrolling during drag + _this.mirrorNeedsRevert = false; + _this.isInteracting = false; // is the user validly moving the pointer? lasts until pointerup + _this.isDragging = false; // is it INTENTFULLY dragging? lasts until after revert animation + _this.isDelayEnded = false; + _this.isDistanceSurpassed = false; + _this.delayTimeoutId = null; + _this.onPointerDown = function (ev) { + if (!_this.isDragging) { // so new drag doesn't happen while revert animation is going + _this.isInteracting = true; + _this.isDelayEnded = false; + _this.isDistanceSurpassed = false; + preventSelection(document.body); + preventContextMenu(document.body); + // prevent links from being visited if there's an eventual drag. + // also prevents selection in older browsers (maybe?). + // not necessary for touch, besides, browser would complain about passiveness. + if (!ev.isTouch) { + ev.origEvent.preventDefault(); + } + _this.emitter.trigger('pointerdown', ev); + if (_this.isInteracting && // not destroyed via pointerdown handler + !_this.pointer.shouldIgnoreMove) { + // actions related to initiating dragstart+dragmove+dragend... + _this.mirror.setIsVisible(false); // reset. caller must set-visible + _this.mirror.start(ev.subjectEl, ev.pageX, ev.pageY); // must happen on first pointer down + _this.startDelay(ev); + if (!_this.minDistance) { + _this.handleDistanceSurpassed(ev); + } + } + } + }; + _this.onPointerMove = function (ev) { + if (_this.isInteracting) { + _this.emitter.trigger('pointermove', ev); + if (!_this.isDistanceSurpassed) { + var minDistance = _this.minDistance; + var distanceSq = void 0; // current distance from the origin, squared + var deltaX = ev.deltaX, deltaY = ev.deltaY; + distanceSq = deltaX * deltaX + deltaY * deltaY; + if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem + _this.handleDistanceSurpassed(ev); + } + } + if (_this.isDragging) { + // a real pointer move? (not one simulated by scrolling) + if (ev.origEvent.type !== 'scroll') { + _this.mirror.handleMove(ev.pageX, ev.pageY); + _this.autoScroller.handleMove(ev.pageX, ev.pageY); + } + _this.emitter.trigger('dragmove', ev); + } + } + }; + _this.onPointerUp = function (ev) { + if (_this.isInteracting) { + _this.isInteracting = false; + allowSelection(document.body); + allowContextMenu(document.body); + _this.emitter.trigger('pointerup', ev); // can potentially set mirrorNeedsRevert + if (_this.isDragging) { + _this.autoScroller.stop(); + _this.tryStopDrag(ev); // which will stop the mirror + } + if (_this.delayTimeoutId) { + clearTimeout(_this.delayTimeoutId); + _this.delayTimeoutId = null; + } + } + }; + var pointer = _this.pointer = new PointerDragging(containerEl); + pointer.emitter.on('pointerdown', _this.onPointerDown); + pointer.emitter.on('pointermove', _this.onPointerMove); + pointer.emitter.on('pointerup', _this.onPointerUp); + if (selector) { + pointer.selector = selector; + } + _this.mirror = new ElementMirror(); + _this.autoScroller = new AutoScroller(); + return _this; + } + FeaturefulElementDragging.prototype.destroy = function () { + this.pointer.destroy(); + // HACK: simulate a pointer-up to end the current drag + // TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire) + this.onPointerUp({}); + }; + FeaturefulElementDragging.prototype.startDelay = function (ev) { + var _this = this; + if (typeof this.delay === 'number') { + this.delayTimeoutId = setTimeout(function () { + _this.delayTimeoutId = null; + _this.handleDelayEnd(ev); + }, this.delay); // not assignable to number! + } + else { + this.handleDelayEnd(ev); + } + }; + FeaturefulElementDragging.prototype.handleDelayEnd = function (ev) { + this.isDelayEnded = true; + this.tryStartDrag(ev); + }; + FeaturefulElementDragging.prototype.handleDistanceSurpassed = function (ev) { + this.isDistanceSurpassed = true; + this.tryStartDrag(ev); + }; + FeaturefulElementDragging.prototype.tryStartDrag = function (ev) { + if (this.isDelayEnded && this.isDistanceSurpassed) { + if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) { + this.isDragging = true; + this.mirrorNeedsRevert = false; + this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl); + this.emitter.trigger('dragstart', ev); + if (this.touchScrollAllowed === false) { + this.pointer.cancelTouchScroll(); + } + } + } + }; + FeaturefulElementDragging.prototype.tryStopDrag = function (ev) { + // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events + // that come from the document to fire beforehand. much more convenient this way. + this.mirror.stop(this.mirrorNeedsRevert, this.stopDrag.bind(this, ev)); + }; + FeaturefulElementDragging.prototype.stopDrag = function (ev) { + this.isDragging = false; + this.emitter.trigger('dragend', ev); + }; + // fill in the implementations... + FeaturefulElementDragging.prototype.setIgnoreMove = function (bool) { + this.pointer.shouldIgnoreMove = bool; + }; + FeaturefulElementDragging.prototype.setMirrorIsVisible = function (bool) { + this.mirror.setIsVisible(bool); + }; + FeaturefulElementDragging.prototype.setMirrorNeedsRevert = function (bool) { + this.mirrorNeedsRevert = bool; + }; + FeaturefulElementDragging.prototype.setAutoScrollEnabled = function (bool) { + this.autoScroller.isEnabled = bool; + }; + return FeaturefulElementDragging; + }(ElementDragging)); + + /* + When this class is instantiated, it records the offset of an element (relative to the document topleft), + and continues to monitor scrolling, updating the cached coordinates if it needs to. + Does not access the DOM after instantiation, so highly performant. + + Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element + and an determine if a given point is inside the combined clipping rectangle. + */ + var OffsetTracker = /** @class */ (function () { + function OffsetTracker(el) { + this.origRect = computeRect(el); + // will work fine for divs that have overflow:hidden + this.scrollCaches = getClippingParents(el).map(function (scrollEl) { return new ElementScrollGeomCache(scrollEl, true); }); + } + OffsetTracker.prototype.destroy = function () { + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + scrollCache.destroy(); + } + }; + OffsetTracker.prototype.computeLeft = function () { + var left = this.origRect.left; + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + left += scrollCache.origScrollLeft - scrollCache.getScrollLeft(); + } + return left; + }; + OffsetTracker.prototype.computeTop = function () { + var top = this.origRect.top; + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + top += scrollCache.origScrollTop - scrollCache.getScrollTop(); + } + return top; + }; + OffsetTracker.prototype.isWithinClipping = function (pageX, pageY) { + var point = { left: pageX, top: pageY }; + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + if (!isIgnoredClipping(scrollCache.getEventTarget()) && + !pointInsideRect(point, scrollCache.clientRect)) { + return false; + } + } + return true; + }; + return OffsetTracker; + }()); + // certain clipping containers should never constrain interactions, like and + // https://github.com/fullcalendar/fullcalendar/issues/3615 + function isIgnoredClipping(node) { + var tagName = node.tagName; + return tagName === 'HTML' || tagName === 'BODY'; + } + + /* + Tracks movement over multiple droppable areas (aka "hits") + that exist in one or more DateComponents. + Relies on an existing draggable. + + emits: + - pointerdown + - dragstart + - hitchange - fires initially, even if not over a hit + - pointerup + - (hitchange - again, to null, if ended over a hit) + - dragend + */ + var HitDragging = /** @class */ (function () { + function HitDragging(dragging, droppableStore) { + var _this = this; + // options that can be set by caller + this.useSubjectCenter = false; + this.requireInitial = true; // if doesn't start out on a hit, won't emit any events + this.initialHit = null; + this.movingHit = null; + this.finalHit = null; // won't ever be populated if shouldIgnoreMove + this.handlePointerDown = function (ev) { + var dragging = _this.dragging; + _this.initialHit = null; + _this.movingHit = null; + _this.finalHit = null; + _this.prepareHits(); + _this.processFirstCoord(ev); + if (_this.initialHit || !_this.requireInitial) { + dragging.setIgnoreMove(false); + // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :( + _this.emitter.trigger('pointerdown', ev); + } + else { + dragging.setIgnoreMove(true); + } + }; + this.handleDragStart = function (ev) { + _this.emitter.trigger('dragstart', ev); + _this.handleMove(ev, true); // force = fire even if initially null + }; + this.handleDragMove = function (ev) { + _this.emitter.trigger('dragmove', ev); + _this.handleMove(ev); + }; + this.handlePointerUp = function (ev) { + _this.releaseHits(); + _this.emitter.trigger('pointerup', ev); + }; + this.handleDragEnd = function (ev) { + if (_this.movingHit) { + _this.emitter.trigger('hitupdate', null, true, ev); + } + _this.finalHit = _this.movingHit; + _this.movingHit = null; + _this.emitter.trigger('dragend', ev); + }; + this.droppableStore = droppableStore; + dragging.emitter.on('pointerdown', this.handlePointerDown); + dragging.emitter.on('dragstart', this.handleDragStart); + dragging.emitter.on('dragmove', this.handleDragMove); + dragging.emitter.on('pointerup', this.handlePointerUp); + dragging.emitter.on('dragend', this.handleDragEnd); + this.dragging = dragging; + this.emitter = new Emitter(); + } + // sets initialHit + // sets coordAdjust + HitDragging.prototype.processFirstCoord = function (ev) { + var origPoint = { left: ev.pageX, top: ev.pageY }; + var adjustedPoint = origPoint; + var subjectEl = ev.subjectEl; + var subjectRect; + if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot + subjectRect = computeRect(subjectEl); + adjustedPoint = constrainPoint(adjustedPoint, subjectRect); + } + var initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top); + if (initialHit) { + if (this.useSubjectCenter && subjectRect) { + var slicedSubjectRect = intersectRects(subjectRect, initialHit.rect); + if (slicedSubjectRect) { + adjustedPoint = getRectCenter(slicedSubjectRect); + } + } + this.coordAdjust = diffPoints(adjustedPoint, origPoint); + } + else { + this.coordAdjust = { left: 0, top: 0 }; + } + }; + HitDragging.prototype.handleMove = function (ev, forceHandle) { + var hit = this.queryHitForOffset(ev.pageX + this.coordAdjust.left, ev.pageY + this.coordAdjust.top); + if (forceHandle || !isHitsEqual(this.movingHit, hit)) { + this.movingHit = hit; + this.emitter.trigger('hitupdate', hit, false, ev); + } + }; + HitDragging.prototype.prepareHits = function () { + this.offsetTrackers = mapHash(this.droppableStore, function (interactionSettings) { + interactionSettings.component.prepareHits(); + return new OffsetTracker(interactionSettings.el); + }); + }; + HitDragging.prototype.releaseHits = function () { + var offsetTrackers = this.offsetTrackers; + for (var id in offsetTrackers) { + offsetTrackers[id].destroy(); + } + this.offsetTrackers = {}; + }; + HitDragging.prototype.queryHitForOffset = function (offsetLeft, offsetTop) { + var _a = this, droppableStore = _a.droppableStore, offsetTrackers = _a.offsetTrackers; + var bestHit = null; + for (var id in droppableStore) { + var component = droppableStore[id].component; + var offsetTracker = offsetTrackers[id]; + if (offsetTracker && // wasn't destroyed mid-drag + offsetTracker.isWithinClipping(offsetLeft, offsetTop)) { + var originLeft = offsetTracker.computeLeft(); + var originTop = offsetTracker.computeTop(); + var positionLeft = offsetLeft - originLeft; + var positionTop = offsetTop - originTop; + var origRect = offsetTracker.origRect; + var width = origRect.right - origRect.left; + var height = origRect.bottom - origRect.top; + if ( + // must be within the element's bounds + positionLeft >= 0 && positionLeft < width && + positionTop >= 0 && positionTop < height) { + var hit = component.queryHit(positionLeft, positionTop, width, height); + if (hit && ( + // make sure the hit is within activeRange, meaning it's not a dead cell + rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range)) && + (!bestHit || hit.layer > bestHit.layer)) { + hit.componentId = id; + hit.context = component.context; + // TODO: better way to re-orient rectangle + hit.rect.left += originLeft; + hit.rect.right += originLeft; + hit.rect.top += originTop; + hit.rect.bottom += originTop; + bestHit = hit; + } + } + } + } + return bestHit; + }; + return HitDragging; + }()); + function isHitsEqual(hit0, hit1) { + if (!hit0 && !hit1) { + return true; + } + if (Boolean(hit0) !== Boolean(hit1)) { + return false; + } + return isDateSpansEqual(hit0.dateSpan, hit1.dateSpan); + } + + function buildDatePointApiWithContext(dateSpan, context) { + var props = {}; + for (var _i = 0, _a = context.pluginHooks.datePointTransforms; _i < _a.length; _i++) { + var transform = _a[_i]; + __assign(props, transform(dateSpan, context)); + } + __assign(props, buildDatePointApi(dateSpan, context.dateEnv)); + return props; + } + function buildDatePointApi(span, dateEnv) { + return { + date: dateEnv.toDate(span.range.start), + dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }), + allDay: span.allDay, + }; + } + + /* + Monitors when the user clicks on a specific date/time of a component. + A pointerdown+pointerup on the same "hit" constitutes a click. + */ + var DateClicking = /** @class */ (function (_super) { + __extends(DateClicking, _super); + function DateClicking(settings) { + var _this = _super.call(this, settings) || this; + _this.handlePointerDown = function (pev) { + var dragging = _this.dragging; + var downEl = pev.origEvent.target; + // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired + dragging.setIgnoreMove(!_this.component.isValidDateDownEl(downEl)); + }; + // won't even fire if moving was ignored + _this.handleDragEnd = function (ev) { + var component = _this.component; + var pointer = _this.dragging.pointer; + if (!pointer.wasTouchScroll) { + var _a = _this.hitDragging, initialHit = _a.initialHit, finalHit = _a.finalHit; + if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) { + var context = component.context; + var arg = __assign(__assign({}, buildDatePointApiWithContext(initialHit.dateSpan, context)), { dayEl: initialHit.dayEl, jsEvent: ev.origEvent, view: context.viewApi || context.calendarApi.view }); + context.emitter.trigger('dateClick', arg); + } + } + }; + // we DO want to watch pointer moves because otherwise finalHit won't get populated + _this.dragging = new FeaturefulElementDragging(settings.el); + _this.dragging.autoScroller.isEnabled = false; + var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings)); + hitDragging.emitter.on('pointerdown', _this.handlePointerDown); + hitDragging.emitter.on('dragend', _this.handleDragEnd); + return _this; + } + DateClicking.prototype.destroy = function () { + this.dragging.destroy(); + }; + return DateClicking; + }(Interaction)); + + /* + Tracks when the user selects a portion of time of a component, + constituted by a drag over date cells, with a possible delay at the beginning of the drag. + */ + var DateSelecting = /** @class */ (function (_super) { + __extends(DateSelecting, _super); + function DateSelecting(settings) { + var _this = _super.call(this, settings) || this; + _this.dragSelection = null; + _this.handlePointerDown = function (ev) { + var _a = _this, component = _a.component, dragging = _a.dragging; + var options = component.context.options; + var canSelect = options.selectable && + component.isValidDateDownEl(ev.origEvent.target); + // don't bother to watch expensive moves if component won't do selection + dragging.setIgnoreMove(!canSelect); + // if touch, require user to hold down + dragging.delay = ev.isTouch ? getComponentTouchDelay$1(component) : null; + }; + _this.handleDragStart = function (ev) { + _this.component.context.calendarApi.unselect(ev); // unselect previous selections + }; + _this.handleHitUpdate = function (hit, isFinal) { + var context = _this.component.context; + var dragSelection = null; + var isInvalid = false; + if (hit) { + var initialHit = _this.hitDragging.initialHit; + var disallowed = hit.componentId === initialHit.componentId + && _this.isHitComboAllowed + && !_this.isHitComboAllowed(initialHit, hit); + if (!disallowed) { + dragSelection = joinHitsIntoSelection(initialHit, hit, context.pluginHooks.dateSelectionTransformers); + } + if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) { + isInvalid = true; + dragSelection = null; + } + } + if (dragSelection) { + context.dispatch({ type: 'SELECT_DATES', selection: dragSelection }); + } + else if (!isFinal) { // only unselect if moved away while dragging + context.dispatch({ type: 'UNSELECT_DATES' }); + } + if (!isInvalid) { + enableCursor(); + } + else { + disableCursor(); + } + if (!isFinal) { + _this.dragSelection = dragSelection; // only clear if moved away from all hits while dragging + } + }; + _this.handlePointerUp = function (pev) { + if (_this.dragSelection) { + // selection is already rendered, so just need to report selection + triggerDateSelect(_this.dragSelection, pev, _this.component.context); + _this.dragSelection = null; + } + }; + var component = settings.component; + var options = component.context.options; + var dragging = _this.dragging = new FeaturefulElementDragging(settings.el); + dragging.touchScrollAllowed = false; + dragging.minDistance = options.selectMinDistance || 0; + dragging.autoScroller.isEnabled = options.dragScroll; + var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings)); + hitDragging.emitter.on('pointerdown', _this.handlePointerDown); + hitDragging.emitter.on('dragstart', _this.handleDragStart); + hitDragging.emitter.on('hitupdate', _this.handleHitUpdate); + hitDragging.emitter.on('pointerup', _this.handlePointerUp); + return _this; + } + DateSelecting.prototype.destroy = function () { + this.dragging.destroy(); + }; + return DateSelecting; + }(Interaction)); + function getComponentTouchDelay$1(component) { + var options = component.context.options; + var delay = options.selectLongPressDelay; + if (delay == null) { + delay = options.longPressDelay; + } + return delay; + } + function joinHitsIntoSelection(hit0, hit1, dateSelectionTransformers) { + var dateSpan0 = hit0.dateSpan; + var dateSpan1 = hit1.dateSpan; + var ms = [ + dateSpan0.range.start, + dateSpan0.range.end, + dateSpan1.range.start, + dateSpan1.range.end, + ]; + ms.sort(compareNumbers); + var props = {}; + for (var _i = 0, dateSelectionTransformers_1 = dateSelectionTransformers; _i < dateSelectionTransformers_1.length; _i++) { + var transformer = dateSelectionTransformers_1[_i]; + var res = transformer(hit0, hit1); + if (res === false) { + return null; + } + if (res) { + __assign(props, res); + } + } + props.range = { start: ms[0], end: ms[3] }; + props.allDay = dateSpan0.allDay; + return props; + } + + var EventDragging = /** @class */ (function (_super) { + __extends(EventDragging, _super); + function EventDragging(settings) { + var _this = _super.call(this, settings) || this; + // internal state + _this.subjectEl = null; + _this.subjectSeg = null; // the seg being selected/dragged + _this.isDragging = false; + _this.eventRange = null; + _this.relevantEvents = null; // the events being dragged + _this.receivingContext = null; + _this.validMutation = null; + _this.mutatedRelevantEvents = null; + _this.handlePointerDown = function (ev) { + var origTarget = ev.origEvent.target; + var _a = _this, component = _a.component, dragging = _a.dragging; + var mirror = dragging.mirror; + var options = component.context.options; + var initialContext = component.context; + _this.subjectEl = ev.subjectEl; + var subjectSeg = _this.subjectSeg = getElSeg(ev.subjectEl); + var eventRange = _this.eventRange = subjectSeg.eventRange; + var eventInstanceId = eventRange.instance.instanceId; + _this.relevantEvents = getRelevantEvents(initialContext.getCurrentData().eventStore, eventInstanceId); + dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance; + dragging.delay = + // only do a touch delay if touch and this event hasn't been selected yet + (ev.isTouch && eventInstanceId !== component.props.eventSelection) ? + getComponentTouchDelay(component) : + null; + if (options.fixedMirrorParent) { + mirror.parentNode = options.fixedMirrorParent; + } + else { + mirror.parentNode = elementClosest(origTarget, '.fc'); + } + mirror.revertDuration = options.dragRevertDuration; + var isValid = component.isValidSegDownEl(origTarget) && + !elementClosest(origTarget, '.fc-event-resizer'); // NOT on a resizer + dragging.setIgnoreMove(!isValid); + // disable dragging for elements that are resizable (ie, selectable) + // but are not draggable + _this.isDragging = isValid && + ev.subjectEl.classList.contains('fc-event-draggable'); + }; + _this.handleDragStart = function (ev) { + var initialContext = _this.component.context; + var eventRange = _this.eventRange; + var eventInstanceId = eventRange.instance.instanceId; + if (ev.isTouch) { + // need to select a different event? + if (eventInstanceId !== _this.component.props.eventSelection) { + initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId: eventInstanceId }); + } + } + else { + // if now using mouse, but was previous touch interaction, clear selected event + initialContext.dispatch({ type: 'UNSELECT_EVENT' }); + } + if (_this.isDragging) { + initialContext.calendarApi.unselect(ev); // unselect *date* selection + initialContext.emitter.trigger('eventDragStart', { + el: _this.subjectEl, + event: new EventApi(initialContext, eventRange.def, eventRange.instance), + jsEvent: ev.origEvent, + view: initialContext.viewApi, + }); + } + }; + _this.handleHitUpdate = function (hit, isFinal) { + if (!_this.isDragging) { + return; + } + var relevantEvents = _this.relevantEvents; + var initialHit = _this.hitDragging.initialHit; + var initialContext = _this.component.context; + // states based on new hit + var receivingContext = null; + var mutation = null; + var mutatedRelevantEvents = null; + var isInvalid = false; + var interaction = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + }; + if (hit) { + receivingContext = hit.context; + var receivingOptions = receivingContext.options; + if (initialContext === receivingContext || + (receivingOptions.editable && receivingOptions.droppable)) { + mutation = computeEventMutation(initialHit, hit, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers); + if (mutation) { + mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, receivingContext.getCurrentData().eventUiBases, mutation, receivingContext); + interaction.mutatedEvents = mutatedRelevantEvents; + if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) { + isInvalid = true; + mutation = null; + mutatedRelevantEvents = null; + interaction.mutatedEvents = createEmptyEventStore(); + } + } + } + else { + receivingContext = null; + } + } + _this.displayDrag(receivingContext, interaction); + if (!isInvalid) { + enableCursor(); + } + else { + disableCursor(); + } + if (!isFinal) { + if (initialContext === receivingContext && // TODO: write test for this + isHitsEqual(initialHit, hit)) { + mutation = null; + } + _this.dragging.setMirrorNeedsRevert(!mutation); + // render the mirror if no already-rendered mirror + // TODO: wish we could somehow wait for dispatch to guarantee render + _this.dragging.setMirrorIsVisible(!hit || !getElRoot(_this.subjectEl).querySelector('.fc-event-mirror')); + // assign states based on new hit + _this.receivingContext = receivingContext; + _this.validMutation = mutation; + _this.mutatedRelevantEvents = mutatedRelevantEvents; + } + }; + _this.handlePointerUp = function () { + if (!_this.isDragging) { + _this.cleanup(); // because handleDragEnd won't fire + } + }; + _this.handleDragEnd = function (ev) { + if (_this.isDragging) { + var initialContext_1 = _this.component.context; + var initialView = initialContext_1.viewApi; + var _a = _this, receivingContext_1 = _a.receivingContext, validMutation = _a.validMutation; + var eventDef = _this.eventRange.def; + var eventInstance = _this.eventRange.instance; + var eventApi = new EventApi(initialContext_1, eventDef, eventInstance); + var relevantEvents_1 = _this.relevantEvents; + var mutatedRelevantEvents_1 = _this.mutatedRelevantEvents; + var finalHit = _this.hitDragging.finalHit; + _this.clearDrag(); // must happen after revert animation + initialContext_1.emitter.trigger('eventDragStop', { + el: _this.subjectEl, + event: eventApi, + jsEvent: ev.origEvent, + view: initialView, + }); + if (validMutation) { + // dropped within same calendar + if (receivingContext_1 === initialContext_1) { + var updatedEventApi = new EventApi(initialContext_1, mutatedRelevantEvents_1.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents_1.instances[eventInstance.instanceId] : null); + initialContext_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents_1, + }); + var eventChangeArg = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents_1, initialContext_1, eventInstance), + revert: function () { + initialContext_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents_1, // the pre-change data + }); + }, + }; + var transformed = {}; + for (var _i = 0, _b = initialContext_1.getCurrentData().pluginHooks.eventDropTransformers; _i < _b.length; _i++) { + var transformer = _b[_i]; + __assign(transformed, transformer(validMutation, initialContext_1)); + } + initialContext_1.emitter.trigger('eventDrop', __assign(__assign(__assign({}, eventChangeArg), transformed), { el: ev.subjectEl, delta: validMutation.datesDelta, jsEvent: ev.origEvent, view: initialView })); + initialContext_1.emitter.trigger('eventChange', eventChangeArg); + // dropped in different calendar + } + else if (receivingContext_1) { + var eventRemoveArg = { + event: eventApi, + relatedEvents: buildEventApis(relevantEvents_1, initialContext_1, eventInstance), + revert: function () { + initialContext_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents_1, + }); + }, + }; + initialContext_1.emitter.trigger('eventLeave', __assign(__assign({}, eventRemoveArg), { draggedEl: ev.subjectEl, view: initialView })); + initialContext_1.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: relevantEvents_1, + }); + initialContext_1.emitter.trigger('eventRemove', eventRemoveArg); + var addedEventDef = mutatedRelevantEvents_1.defs[eventDef.defId]; + var addedEventInstance = mutatedRelevantEvents_1.instances[eventInstance.instanceId]; + var addedEventApi = new EventApi(receivingContext_1, addedEventDef, addedEventInstance); + receivingContext_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents_1, + }); + var eventAddArg = { + event: addedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents_1, receivingContext_1, addedEventInstance), + revert: function () { + receivingContext_1.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: mutatedRelevantEvents_1, + }); + }, + }; + receivingContext_1.emitter.trigger('eventAdd', eventAddArg); + if (ev.isTouch) { + receivingContext_1.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: eventInstance.instanceId, + }); + } + receivingContext_1.emitter.trigger('drop', __assign(__assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext_1)), { draggedEl: ev.subjectEl, jsEvent: ev.origEvent, view: finalHit.context.viewApi })); + receivingContext_1.emitter.trigger('eventReceive', __assign(__assign({}, eventAddArg), { draggedEl: ev.subjectEl, view: finalHit.context.viewApi })); + } + } + else { + initialContext_1.emitter.trigger('_noEventDrop'); + } + } + _this.cleanup(); + }; + var component = _this.component; + var options = component.context.options; + var dragging = _this.dragging = new FeaturefulElementDragging(settings.el); + dragging.pointer.selector = EventDragging.SELECTOR; + dragging.touchScrollAllowed = false; + dragging.autoScroller.isEnabled = options.dragScroll; + var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsStore); + hitDragging.useSubjectCenter = settings.useEventCenter; + hitDragging.emitter.on('pointerdown', _this.handlePointerDown); + hitDragging.emitter.on('dragstart', _this.handleDragStart); + hitDragging.emitter.on('hitupdate', _this.handleHitUpdate); + hitDragging.emitter.on('pointerup', _this.handlePointerUp); + hitDragging.emitter.on('dragend', _this.handleDragEnd); + return _this; + } + EventDragging.prototype.destroy = function () { + this.dragging.destroy(); + }; + // render a drag state on the next receivingCalendar + EventDragging.prototype.displayDrag = function (nextContext, state) { + var initialContext = this.component.context; + var prevContext = this.receivingContext; + // does the previous calendar need to be cleared? + if (prevContext && prevContext !== nextContext) { + // does the initial calendar need to be cleared? + // if so, don't clear all the way. we still need to to hide the affectedEvents + if (prevContext === initialContext) { + prevContext.dispatch({ + type: 'SET_EVENT_DRAG', + state: { + affectedEvents: state.affectedEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + }, + }); + // completely clear the old calendar if it wasn't the initial + } + else { + prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + } + if (nextContext) { + nextContext.dispatch({ type: 'SET_EVENT_DRAG', state: state }); + } + }; + EventDragging.prototype.clearDrag = function () { + var initialCalendar = this.component.context; + var receivingContext = this.receivingContext; + if (receivingContext) { + receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + // the initial calendar might have an dummy drag state from displayDrag + if (initialCalendar !== receivingContext) { + initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + }; + EventDragging.prototype.cleanup = function () { + this.subjectSeg = null; + this.isDragging = false; + this.eventRange = null; + this.relevantEvents = null; + this.receivingContext = null; + this.validMutation = null; + this.mutatedRelevantEvents = null; + }; + // TODO: test this in IE11 + // QUESTION: why do we need it on the resizable??? + EventDragging.SELECTOR = '.fc-event-draggable, .fc-event-resizable'; + return EventDragging; + }(Interaction)); + function computeEventMutation(hit0, hit1, massagers) { + var dateSpan0 = hit0.dateSpan; + var dateSpan1 = hit1.dateSpan; + var date0 = dateSpan0.range.start; + var date1 = dateSpan1.range.start; + var standardProps = {}; + if (dateSpan0.allDay !== dateSpan1.allDay) { + standardProps.allDay = dateSpan1.allDay; + standardProps.hasEnd = hit1.context.options.allDayMaintainDuration; + if (dateSpan1.allDay) { + // means date1 is already start-of-day, + // but date0 needs to be converted + date0 = startOfDay(date0); + } + } + var delta = diffDates(date0, date1, hit0.context.dateEnv, hit0.componentId === hit1.componentId ? + hit0.largeUnit : + null); + if (delta.milliseconds) { // has hours/minutes/seconds + standardProps.allDay = false; + } + var mutation = { + datesDelta: delta, + standardProps: standardProps, + }; + for (var _i = 0, massagers_1 = massagers; _i < massagers_1.length; _i++) { + var massager = massagers_1[_i]; + massager(mutation, hit0, hit1); + } + return mutation; + } + function getComponentTouchDelay(component) { + var options = component.context.options; + var delay = options.eventLongPressDelay; + if (delay == null) { + delay = options.longPressDelay; + } + return delay; + } + + var EventResizing = /** @class */ (function (_super) { + __extends(EventResizing, _super); + function EventResizing(settings) { + var _this = _super.call(this, settings) || this; + // internal state + _this.draggingSegEl = null; + _this.draggingSeg = null; // TODO: rename to resizingSeg? subjectSeg? + _this.eventRange = null; + _this.relevantEvents = null; + _this.validMutation = null; + _this.mutatedRelevantEvents = null; + _this.handlePointerDown = function (ev) { + var component = _this.component; + var segEl = _this.querySegEl(ev); + var seg = getElSeg(segEl); + var eventRange = _this.eventRange = seg.eventRange; + _this.dragging.minDistance = component.context.options.eventDragMinDistance; + // if touch, need to be working with a selected event + _this.dragging.setIgnoreMove(!_this.component.isValidSegDownEl(ev.origEvent.target) || + (ev.isTouch && _this.component.props.eventSelection !== eventRange.instance.instanceId)); + }; + _this.handleDragStart = function (ev) { + var context = _this.component.context; + var eventRange = _this.eventRange; + _this.relevantEvents = getRelevantEvents(context.getCurrentData().eventStore, _this.eventRange.instance.instanceId); + var segEl = _this.querySegEl(ev); + _this.draggingSegEl = segEl; + _this.draggingSeg = getElSeg(segEl); + context.calendarApi.unselect(); + context.emitter.trigger('eventResizeStart', { + el: segEl, + event: new EventApi(context, eventRange.def, eventRange.instance), + jsEvent: ev.origEvent, + view: context.viewApi, + }); + }; + _this.handleHitUpdate = function (hit, isFinal, ev) { + var context = _this.component.context; + var relevantEvents = _this.relevantEvents; + var initialHit = _this.hitDragging.initialHit; + var eventInstance = _this.eventRange.instance; + var mutation = null; + var mutatedRelevantEvents = null; + var isInvalid = false; + var interaction = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + }; + if (hit) { + var disallowed = hit.componentId === initialHit.componentId + && _this.isHitComboAllowed + && !_this.isHitComboAllowed(initialHit, hit); + if (!disallowed) { + mutation = computeMutation(initialHit, hit, ev.subjectEl.classList.contains('fc-event-resizer-start'), eventInstance.range); + } + } + if (mutation) { + mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context); + interaction.mutatedEvents = mutatedRelevantEvents; + if (!isInteractionValid(interaction, hit.dateProfile, context)) { + isInvalid = true; + mutation = null; + mutatedRelevantEvents = null; + interaction.mutatedEvents = null; + } + } + if (mutatedRelevantEvents) { + context.dispatch({ + type: 'SET_EVENT_RESIZE', + state: interaction, + }); + } + else { + context.dispatch({ type: 'UNSET_EVENT_RESIZE' }); + } + if (!isInvalid) { + enableCursor(); + } + else { + disableCursor(); + } + if (!isFinal) { + if (mutation && isHitsEqual(initialHit, hit)) { + mutation = null; + } + _this.validMutation = mutation; + _this.mutatedRelevantEvents = mutatedRelevantEvents; + } + }; + _this.handleDragEnd = function (ev) { + var context = _this.component.context; + var eventDef = _this.eventRange.def; + var eventInstance = _this.eventRange.instance; + var eventApi = new EventApi(context, eventDef, eventInstance); + var relevantEvents = _this.relevantEvents; + var mutatedRelevantEvents = _this.mutatedRelevantEvents; + context.emitter.trigger('eventResizeStop', { + el: _this.draggingSegEl, + event: eventApi, + jsEvent: ev.origEvent, + view: context.viewApi, + }); + if (_this.validMutation) { + var updatedEventApi = new EventApi(context, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null); + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }); + var eventChangeArg = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance), + revert: function () { + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, // the pre-change events + }); + }, + }; + context.emitter.trigger('eventResize', __assign(__assign({}, eventChangeArg), { el: _this.draggingSegEl, startDelta: _this.validMutation.startDelta || createDuration(0), endDelta: _this.validMutation.endDelta || createDuration(0), jsEvent: ev.origEvent, view: context.viewApi })); + context.emitter.trigger('eventChange', eventChangeArg); + } + else { + context.emitter.trigger('_noEventResize'); + } + // reset all internal state + _this.draggingSeg = null; + _this.relevantEvents = null; + _this.validMutation = null; + // okay to keep eventInstance around. useful to set it in handlePointerDown + }; + var component = settings.component; + var dragging = _this.dragging = new FeaturefulElementDragging(settings.el); + dragging.pointer.selector = '.fc-event-resizer'; + dragging.touchScrollAllowed = false; + dragging.autoScroller.isEnabled = component.context.options.dragScroll; + var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings)); + hitDragging.emitter.on('pointerdown', _this.handlePointerDown); + hitDragging.emitter.on('dragstart', _this.handleDragStart); + hitDragging.emitter.on('hitupdate', _this.handleHitUpdate); + hitDragging.emitter.on('dragend', _this.handleDragEnd); + return _this; + } + EventResizing.prototype.destroy = function () { + this.dragging.destroy(); + }; + EventResizing.prototype.querySegEl = function (ev) { + return elementClosest(ev.subjectEl, '.fc-event'); + }; + return EventResizing; + }(Interaction)); + function computeMutation(hit0, hit1, isFromStart, instanceRange) { + var dateEnv = hit0.context.dateEnv; + var date0 = hit0.dateSpan.range.start; + var date1 = hit1.dateSpan.range.start; + var delta = diffDates(date0, date1, dateEnv, hit0.largeUnit); + if (isFromStart) { + if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) { + return { startDelta: delta }; + } + } + else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) { + return { endDelta: delta }; + } + return null; + } + + var UnselectAuto = /** @class */ (function () { + function UnselectAuto(context) { + var _this = this; + this.context = context; + this.isRecentPointerDateSelect = false; // wish we could use a selector to detect date selection, but uses hit system + this.matchesCancel = false; + this.matchesEvent = false; + this.onSelect = function (selectInfo) { + if (selectInfo.jsEvent) { + _this.isRecentPointerDateSelect = true; + } + }; + this.onDocumentPointerDown = function (pev) { + var unselectCancel = _this.context.options.unselectCancel; + var downEl = getEventTargetViaRoot(pev.origEvent); + _this.matchesCancel = !!elementClosest(downEl, unselectCancel); + _this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR); // interaction started on an event? + }; + this.onDocumentPointerUp = function (pev) { + var context = _this.context; + var documentPointer = _this.documentPointer; + var calendarState = context.getCurrentData(); + // touch-scrolling should never unfocus any type of selection + if (!documentPointer.wasTouchScroll) { + if (calendarState.dateSelection && // an existing date selection? + !_this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp? + ) { + var unselectAuto = context.options.unselectAuto; + if (unselectAuto && (!unselectAuto || !_this.matchesCancel)) { + context.calendarApi.unselect(pev); + } + } + if (calendarState.eventSelection && // an existing event selected? + !_this.matchesEvent // interaction DIDN'T start on an event + ) { + context.dispatch({ type: 'UNSELECT_EVENT' }); + } + } + _this.isRecentPointerDateSelect = false; + }; + var documentPointer = this.documentPointer = new PointerDragging(document); + documentPointer.shouldIgnoreMove = true; + documentPointer.shouldWatchScroll = false; + documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown); + documentPointer.emitter.on('pointerup', this.onDocumentPointerUp); + /* + TODO: better way to know about whether there was a selection with the pointer + */ + context.emitter.on('select', this.onSelect); + } + UnselectAuto.prototype.destroy = function () { + this.context.emitter.off('select', this.onSelect); + this.documentPointer.destroy(); + }; + return UnselectAuto; + }()); + + var OPTION_REFINERS$3 = { + fixedMirrorParent: identity, + }; + var LISTENER_REFINERS = { + dateClick: identity, + eventDragStart: identity, + eventDragStop: identity, + eventDrop: identity, + eventResizeStart: identity, + eventResizeStop: identity, + eventResize: identity, + drop: identity, + eventReceive: identity, + eventLeave: identity, + }; + + /* + Given an already instantiated draggable object for one-or-more elements, + Interprets any dragging as an attempt to drag an events that lives outside + of a calendar onto a calendar. + */ + var ExternalElementDragging = /** @class */ (function () { + function ExternalElementDragging(dragging, suppliedDragMeta) { + var _this = this; + this.receivingContext = null; + this.droppableEvent = null; // will exist for all drags, even if create:false + this.suppliedDragMeta = null; + this.dragMeta = null; + this.handleDragStart = function (ev) { + _this.dragMeta = _this.buildDragMeta(ev.subjectEl); + }; + this.handleHitUpdate = function (hit, isFinal, ev) { + var dragging = _this.hitDragging.dragging; + var receivingContext = null; + var droppableEvent = null; + var isInvalid = false; + var interaction = { + affectedEvents: createEmptyEventStore(), + mutatedEvents: createEmptyEventStore(), + isEvent: _this.dragMeta.create, + }; + if (hit) { + receivingContext = hit.context; + if (_this.canDropElOnCalendar(ev.subjectEl, receivingContext)) { + droppableEvent = computeEventForDateSpan(hit.dateSpan, _this.dragMeta, receivingContext); + interaction.mutatedEvents = eventTupleToStore(droppableEvent); + isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext); + if (isInvalid) { + interaction.mutatedEvents = createEmptyEventStore(); + droppableEvent = null; + } + } + } + _this.displayDrag(receivingContext, interaction); + // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?) + // TODO: wish we could somehow wait for dispatch to guarantee render + dragging.setMirrorIsVisible(isFinal || !droppableEvent || !document.querySelector('.fc-event-mirror')); + if (!isInvalid) { + enableCursor(); + } + else { + disableCursor(); + } + if (!isFinal) { + dragging.setMirrorNeedsRevert(!droppableEvent); + _this.receivingContext = receivingContext; + _this.droppableEvent = droppableEvent; + } + }; + this.handleDragEnd = function (pev) { + var _a = _this, receivingContext = _a.receivingContext, droppableEvent = _a.droppableEvent; + _this.clearDrag(); + if (receivingContext && droppableEvent) { + var finalHit = _this.hitDragging.finalHit; + var finalView = finalHit.context.viewApi; + var dragMeta = _this.dragMeta; + receivingContext.emitter.trigger('drop', __assign(__assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: pev.subjectEl, jsEvent: pev.origEvent, view: finalView })); + if (dragMeta.create) { + var addingEvents_1 = eventTupleToStore(droppableEvent); + receivingContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: addingEvents_1, + }); + if (pev.isTouch) { + receivingContext.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: droppableEvent.instance.instanceId, + }); + } + // signal that an external event landed + receivingContext.emitter.trigger('eventReceive', { + event: new EventApi(receivingContext, droppableEvent.def, droppableEvent.instance), + relatedEvents: [], + revert: function () { + receivingContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: addingEvents_1, + }); + }, + draggedEl: pev.subjectEl, + view: finalView, + }); + } + } + _this.receivingContext = null; + _this.droppableEvent = null; + }; + var hitDragging = this.hitDragging = new HitDragging(dragging, interactionSettingsStore); + hitDragging.requireInitial = false; // will start outside of a component + hitDragging.emitter.on('dragstart', this.handleDragStart); + hitDragging.emitter.on('hitupdate', this.handleHitUpdate); + hitDragging.emitter.on('dragend', this.handleDragEnd); + this.suppliedDragMeta = suppliedDragMeta; + } + ExternalElementDragging.prototype.buildDragMeta = function (subjectEl) { + if (typeof this.suppliedDragMeta === 'object') { + return parseDragMeta(this.suppliedDragMeta); + } + if (typeof this.suppliedDragMeta === 'function') { + return parseDragMeta(this.suppliedDragMeta(subjectEl)); + } + return getDragMetaFromEl(subjectEl); + }; + ExternalElementDragging.prototype.displayDrag = function (nextContext, state) { + var prevContext = this.receivingContext; + if (prevContext && prevContext !== nextContext) { + prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + if (nextContext) { + nextContext.dispatch({ type: 'SET_EVENT_DRAG', state: state }); + } + }; + ExternalElementDragging.prototype.clearDrag = function () { + if (this.receivingContext) { + this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + }; + ExternalElementDragging.prototype.canDropElOnCalendar = function (el, receivingContext) { + var dropAccept = receivingContext.options.dropAccept; + if (typeof dropAccept === 'function') { + return dropAccept.call(receivingContext.calendarApi, el); + } + if (typeof dropAccept === 'string' && dropAccept) { + return Boolean(elementMatches(el, dropAccept)); + } + return true; + }; + return ExternalElementDragging; + }()); + // Utils for computing event store from the DragMeta + // ---------------------------------------------------------------------------------------------------- + function computeEventForDateSpan(dateSpan, dragMeta, context) { + var defProps = __assign({}, dragMeta.leftoverProps); + for (var _i = 0, _a = context.pluginHooks.externalDefTransforms; _i < _a.length; _i++) { + var transform = _a[_i]; + __assign(defProps, transform(dateSpan, dragMeta)); + } + var _b = refineEventDef(defProps, context), refined = _b.refined, extra = _b.extra; + var def = parseEventDef(refined, extra, dragMeta.sourceId, dateSpan.allDay, context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd + context); + var start = dateSpan.range.start; + // only rely on time info if drop zone is all-day, + // otherwise, we already know the time + if (dateSpan.allDay && dragMeta.startTime) { + start = context.dateEnv.add(start, dragMeta.startTime); + } + var end = dragMeta.duration ? + context.dateEnv.add(start, dragMeta.duration) : + getDefaultEventEnd(dateSpan.allDay, start, context); + var instance = createEventInstance(def.defId, { start: start, end: end }); + return { def: def, instance: instance }; + } + // Utils for extracting data from element + // ---------------------------------------------------------------------------------------------------- + function getDragMetaFromEl(el) { + var str = getEmbeddedElData(el, 'event'); + var obj = str ? + JSON.parse(str) : + { create: false }; // if no embedded data, assume no event creation + return parseDragMeta(obj); + } + config.dataAttrPrefix = ''; + function getEmbeddedElData(el, name) { + var prefix = config.dataAttrPrefix; + var prefixedName = (prefix ? prefix + '-' : '') + name; + return el.getAttribute('data-' + prefixedName) || ''; + } + + /* + Makes an element (that is *external* to any calendar) draggable. + Can pass in data that determines how an event will be created when dropped onto a calendar. + Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system. + */ + var ExternalDraggable = /** @class */ (function () { + function ExternalDraggable(el, settings) { + var _this = this; + if (settings === void 0) { settings = {}; } + this.handlePointerDown = function (ev) { + var dragging = _this.dragging; + var _a = _this.settings, minDistance = _a.minDistance, longPressDelay = _a.longPressDelay; + dragging.minDistance = + minDistance != null ? + minDistance : + (ev.isTouch ? 0 : BASE_OPTION_DEFAULTS.eventDragMinDistance); + dragging.delay = + ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv + (longPressDelay != null ? longPressDelay : BASE_OPTION_DEFAULTS.longPressDelay) : + 0; + }; + this.handleDragStart = function (ev) { + if (ev.isTouch && + _this.dragging.delay && + ev.subjectEl.classList.contains('fc-event')) { + _this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected'); + } + }; + this.settings = settings; + var dragging = this.dragging = new FeaturefulElementDragging(el); + dragging.touchScrollAllowed = false; + if (settings.itemSelector != null) { + dragging.pointer.selector = settings.itemSelector; + } + if (settings.appendTo != null) { + dragging.mirror.parentNode = settings.appendTo; // TODO: write tests + } + dragging.emitter.on('pointerdown', this.handlePointerDown); + dragging.emitter.on('dragstart', this.handleDragStart); + new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new + } + ExternalDraggable.prototype.destroy = function () { + this.dragging.destroy(); + }; + return ExternalDraggable; + }()); + + /* + Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements. + The third-party system is responsible for drawing the visuals effects of the drag. + This class simply monitors for pointer movements and fires events. + It also has the ability to hide the moving element (the "mirror") during the drag. + */ + var InferredElementDragging = /** @class */ (function (_super) { + __extends(InferredElementDragging, _super); + function InferredElementDragging(containerEl) { + var _this = _super.call(this, containerEl) || this; + _this.shouldIgnoreMove = false; + _this.mirrorSelector = ''; + _this.currentMirrorEl = null; + _this.handlePointerDown = function (ev) { + _this.emitter.trigger('pointerdown', ev); + if (!_this.shouldIgnoreMove) { + // fire dragstart right away. does not support delay or min-distance + _this.emitter.trigger('dragstart', ev); + } + }; + _this.handlePointerMove = function (ev) { + if (!_this.shouldIgnoreMove) { + _this.emitter.trigger('dragmove', ev); + } + }; + _this.handlePointerUp = function (ev) { + _this.emitter.trigger('pointerup', ev); + if (!_this.shouldIgnoreMove) { + // fire dragend right away. does not support a revert animation + _this.emitter.trigger('dragend', ev); + } + }; + var pointer = _this.pointer = new PointerDragging(containerEl); + pointer.emitter.on('pointerdown', _this.handlePointerDown); + pointer.emitter.on('pointermove', _this.handlePointerMove); + pointer.emitter.on('pointerup', _this.handlePointerUp); + return _this; + } + InferredElementDragging.prototype.destroy = function () { + this.pointer.destroy(); + }; + InferredElementDragging.prototype.setIgnoreMove = function (bool) { + this.shouldIgnoreMove = bool; + }; + InferredElementDragging.prototype.setMirrorIsVisible = function (bool) { + if (bool) { + // restore a previously hidden element. + // use the reference in case the selector class has already been removed. + if (this.currentMirrorEl) { + this.currentMirrorEl.style.visibility = ''; + this.currentMirrorEl = null; + } + } + else { + var mirrorEl = this.mirrorSelector + // TODO: somehow query FullCalendars WITHIN shadow-roots + ? document.querySelector(this.mirrorSelector) + : null; + if (mirrorEl) { + this.currentMirrorEl = mirrorEl; + mirrorEl.style.visibility = 'hidden'; + } + } + }; + return InferredElementDragging; + }(ElementDragging)); + + /* + Bridges third-party drag-n-drop systems with FullCalendar. + Must be instantiated and destroyed by caller. + */ + var ThirdPartyDraggable = /** @class */ (function () { + function ThirdPartyDraggable(containerOrSettings, settings) { + var containerEl = document; + if ( + // wish we could just test instanceof EventTarget, but doesn't work in IE11 + containerOrSettings === document || + containerOrSettings instanceof Element) { + containerEl = containerOrSettings; + settings = settings || {}; + } + else { + settings = (containerOrSettings || {}); + } + var dragging = this.dragging = new InferredElementDragging(containerEl); + if (typeof settings.itemSelector === 'string') { + dragging.pointer.selector = settings.itemSelector; + } + else if (containerEl === document) { + dragging.pointer.selector = '[data-event]'; + } + if (typeof settings.mirrorSelector === 'string') { + dragging.mirrorSelector = settings.mirrorSelector; + } + new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new + } + ThirdPartyDraggable.prototype.destroy = function () { + this.dragging.destroy(); + }; + return ThirdPartyDraggable; + }()); + + var interactionPlugin = createPlugin({ + componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing], + calendarInteractions: [UnselectAuto], + elementDraggingImpl: FeaturefulElementDragging, + optionRefiners: OPTION_REFINERS$3, + listenerRefiners: LISTENER_REFINERS, + }); + + /* An abstract class for the daygrid views, as well as month view. Renders one or more rows of day cells. + ----------------------------------------------------------------------------------------------------------------------*/ + // It is a manager for a Table subcomponent, which does most of the heavy lifting. + // It is responsible for managing width/height. + var TableView = /** @class */ (function (_super) { + __extends(TableView, _super); + function TableView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.headerElRef = createRef(); + return _this; + } + TableView.prototype.renderSimpleLayout = function (headerRowContent, bodyContent) { + var _a = this, props = _a.props, context = _a.context; + var sections = []; + var stickyHeaderDates = getStickyHeaderDates(context.options); + if (headerRowContent) { + sections.push({ + type: 'header', + key: 'header', + isSticky: stickyHeaderDates, + chunk: { + elRef: this.headerElRef, + tableClassName: 'fc-col-header', + rowContent: headerRowContent, + }, + }); + } + sections.push({ + type: 'body', + key: 'body', + liquid: true, + chunk: { content: bodyContent }, + }); + return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: ['fc-daygrid'].concat(classNames).join(' ') }, + createElement(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [] /* TODO: make optional? */, sections: sections }))); })); + }; + TableView.prototype.renderHScrollLayout = function (headerRowContent, bodyContent, colCnt, dayMinWidth) { + var ScrollGrid = this.context.pluginHooks.scrollGridImpl; + if (!ScrollGrid) { + throw new Error('No ScrollGrid implementation'); + } + var _a = this, props = _a.props, context = _a.context; + var stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options); + var stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options); + var sections = []; + if (headerRowContent) { + sections.push({ + type: 'header', + key: 'header', + isSticky: stickyHeaderDates, + chunks: [{ + key: 'main', + elRef: this.headerElRef, + tableClassName: 'fc-col-header', + rowContent: headerRowContent, + }], + }); + } + sections.push({ + type: 'body', + key: 'body', + liquid: true, + chunks: [{ + key: 'main', + content: bodyContent, + }], + }); + if (stickyFooterScrollbar) { + sections.push({ + type: 'footer', + key: 'footer', + isSticky: true, + chunks: [{ + key: 'main', + content: renderScrollShim, + }], + }); + } + return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: ['fc-daygrid'].concat(classNames).join(' ') }, + createElement(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, colGroups: [{ cols: [{ span: colCnt, minWidth: dayMinWidth }] }], sections: sections }))); })); + }; + return TableView; + }(DateComponent)); + + function splitSegsByRow(segs, rowCnt) { + var byRow = []; + for (var i = 0; i < rowCnt; i += 1) { + byRow[i] = []; + } + for (var _i = 0, segs_1 = segs; _i < segs_1.length; _i++) { + var seg = segs_1[_i]; + byRow[seg.row].push(seg); + } + return byRow; + } + function splitSegsByFirstCol(segs, colCnt) { + var byCol = []; + for (var i = 0; i < colCnt; i += 1) { + byCol[i] = []; + } + for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) { + var seg = segs_2[_i]; + byCol[seg.firstCol].push(seg); + } + return byCol; + } + function splitInteractionByRow(ui, rowCnt) { + var byRow = []; + if (!ui) { + for (var i = 0; i < rowCnt; i += 1) { + byRow[i] = null; + } + } + else { + for (var i = 0; i < rowCnt; i += 1) { + byRow[i] = { + affectedInstances: ui.affectedInstances, + isEvent: ui.isEvent, + segs: [], + }; + } + for (var _i = 0, _a = ui.segs; _i < _a.length; _i++) { + var seg = _a[_i]; + byRow[seg.row].segs.push(seg); + } + } + return byRow; + } + + var TableCellTop = /** @class */ (function (_super) { + __extends(TableCellTop, _super); + function TableCellTop() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableCellTop.prototype.render = function () { + var props = this.props; + var navLinkAttrs = this.context.options.navLinks + ? { 'data-navlink': buildNavLinkData(props.date), tabIndex: 0 } + : {}; + return (createElement(DayCellContent, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, showDayNumber: props.showDayNumber, extraHookProps: props.extraHookProps, defaultContent: renderTopInner }, function (innerElRef, innerContent) { return ((innerContent || props.forceDayTop) && (createElement("div", { className: "fc-daygrid-day-top", ref: innerElRef }, + createElement("a", __assign({ className: "fc-daygrid-day-number" }, navLinkAttrs), innerContent || createElement(Fragment, null, "\u00A0"))))); })); + }; + return TableCellTop; + }(BaseComponent)); + function renderTopInner(props) { + return props.dayNumberText; + } + + var DEFAULT_TABLE_EVENT_TIME_FORMAT = createFormatter({ + hour: 'numeric', + minute: '2-digit', + omitZeroMinute: true, + meridiem: 'narrow', + }); + function hasListItemDisplay(seg) { + var display = seg.eventRange.ui.display; + return display === 'list-item' || (display === 'auto' && + !seg.eventRange.def.allDay && + seg.firstCol === seg.lastCol && // can't be multi-day + seg.isStart && // " + seg.isEnd // " + ); + } + + var TableBlockEvent = /** @class */ (function (_super) { + __extends(TableBlockEvent, _super); + function TableBlockEvent() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableBlockEvent.prototype.render = function () { + var props = this.props; + return (createElement(StandardEvent, __assign({}, props, { extraClassNames: ['fc-daygrid-event', 'fc-daygrid-block-event', 'fc-h-event'], defaultTimeFormat: DEFAULT_TABLE_EVENT_TIME_FORMAT, defaultDisplayEventEnd: props.defaultDisplayEventEnd, disableResizing: !props.seg.eventRange.def.allDay }))); + }; + return TableBlockEvent; + }(BaseComponent)); + + var TableListItemEvent = /** @class */ (function (_super) { + __extends(TableListItemEvent, _super); + function TableListItemEvent() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableListItemEvent.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var timeFormat = context.options.eventTimeFormat || DEFAULT_TABLE_EVENT_TIME_FORMAT; + var timeText = buildSegTimeText(props.seg, timeFormat, context, true, props.defaultDisplayEventEnd); + return (createElement(EventRoot, { seg: props.seg, timeText: timeText, defaultContent: renderInnerContent$2, isDragging: props.isDragging, isResizing: false, isDateSelecting: false, isSelected: props.isSelected, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent) { return ( // we don't use styles! + createElement("a", __assign({ className: ['fc-daygrid-event', 'fc-daygrid-dot-event'].concat(classNames).join(' '), ref: rootElRef }, getSegAnchorAttrs(props.seg)), innerContent)); })); + }; + return TableListItemEvent; + }(BaseComponent)); + function renderInnerContent$2(innerProps) { + return (createElement(Fragment, null, + createElement("div", { className: "fc-daygrid-event-dot", style: { borderColor: innerProps.borderColor || innerProps.backgroundColor } }), + innerProps.timeText && (createElement("div", { className: "fc-event-time" }, innerProps.timeText)), + createElement("div", { className: "fc-event-title" }, innerProps.event.title || createElement(Fragment, null, "\u00A0")))); + } + function getSegAnchorAttrs(seg) { + var url = seg.eventRange.def.url; + return url ? { href: url } : {}; + } + + var TableCellMoreLink = /** @class */ (function (_super) { + __extends(TableCellMoreLink, _super); + function TableCellMoreLink() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.compileSegs = memoize(compileSegs); + return _this; + } + TableCellMoreLink.prototype.render = function () { + var props = this.props; + var _a = this.compileSegs(props.singlePlacements), allSegs = _a.allSegs, invisibleSegs = _a.invisibleSegs; + return (createElement(MoreLinkRoot, { dateProfile: props.dateProfile, todayRange: props.todayRange, allDayDate: props.allDayDate, moreCnt: props.moreCnt, allSegs: allSegs, hiddenSegs: invisibleSegs, alignmentElRef: props.alignmentElRef, alignGridTop: props.alignGridTop, extraDateSpan: props.extraDateSpan, popoverContent: function () { + var isForcedInvisible = (props.eventDrag ? props.eventDrag.affectedInstances : null) || + (props.eventResize ? props.eventResize.affectedInstances : null) || + {}; + return (createElement(Fragment, null, allSegs.map(function (seg) { + var instanceId = seg.eventRange.instance.instanceId; + return (createElement("div", { className: "fc-daygrid-event-harness", key: instanceId, style: { + visibility: isForcedInvisible[instanceId] ? 'hidden' : '', + } }, hasListItemDisplay(seg) ? (createElement(TableListItemEvent, __assign({ seg: seg, isDragging: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange)))) : (createElement(TableBlockEvent, __assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange)))))); + }))); + } }, function (rootElRef, classNames, innerElRef, innerContent, handleClick) { return (createElement("a", { ref: rootElRef, className: ['fc-daygrid-more-link'].concat(classNames).join(' '), onClick: handleClick }, innerContent)); })); + }; + return TableCellMoreLink; + }(BaseComponent)); + function compileSegs(singlePlacements) { + var allSegs = []; + var invisibleSegs = []; + for (var _i = 0, singlePlacements_1 = singlePlacements; _i < singlePlacements_1.length; _i++) { + var placement = singlePlacements_1[_i]; + allSegs.push(placement.seg); + if (!placement.isVisible) { + invisibleSegs.push(placement.seg); + } + } + return { allSegs: allSegs, invisibleSegs: invisibleSegs }; + } + + var DEFAULT_WEEK_NUM_FORMAT$1 = createFormatter({ week: 'narrow' }); + var TableCell = /** @class */ (function (_super) { + __extends(TableCell, _super); + function TableCell() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + _this.handleRootEl = function (el) { + setRef(_this.rootElRef, el); + setRef(_this.props.elRef, el); + }; + return _this; + } + TableCell.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context, rootElRef = _a.rootElRef; + var options = context.options; + var date = props.date, dateProfile = props.dateProfile; + var navLinkAttrs = options.navLinks + ? { 'data-navlink': buildNavLinkData(date, 'week'), tabIndex: 0 } + : {}; + return (createElement(DayCellRoot, { date: date, dateProfile: dateProfile, todayRange: props.todayRange, showDayNumber: props.showDayNumber, extraHookProps: props.extraHookProps, elRef: this.handleRootEl }, function (dayElRef, dayClassNames, rootDataAttrs, isDisabled) { return (createElement("td", __assign({ ref: dayElRef, className: ['fc-daygrid-day'].concat(dayClassNames, props.extraClassNames || []).join(' ') }, rootDataAttrs, props.extraDataAttrs), + createElement("div", { className: "fc-daygrid-day-frame fc-scrollgrid-sync-inner", ref: props.innerElRef /* different from hook system! RENAME */ }, + props.showWeekNumber && (createElement(WeekNumberRoot, { date: date, defaultFormat: DEFAULT_WEEK_NUM_FORMAT$1 }, function (weekElRef, weekClassNames, innerElRef, innerContent) { return (createElement("a", __assign({ ref: weekElRef, className: ['fc-daygrid-week-number'].concat(weekClassNames).join(' ') }, navLinkAttrs), innerContent)); })), + !isDisabled && (createElement(TableCellTop, { date: date, dateProfile: dateProfile, showDayNumber: props.showDayNumber, forceDayTop: props.forceDayTop, todayRange: props.todayRange, extraHookProps: props.extraHookProps })), + createElement("div", { className: "fc-daygrid-day-events", ref: props.fgContentElRef }, + props.fgContent, + createElement("div", { className: "fc-daygrid-day-bottom", style: { marginTop: props.moreMarginTop } }, + createElement(TableCellMoreLink, { allDayDate: date, singlePlacements: props.singlePlacements, moreCnt: props.moreCnt, alignmentElRef: rootElRef, alignGridTop: !props.showDayNumber, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange }))), + createElement("div", { className: "fc-daygrid-day-bg" }, props.bgContent)))); })); + }; + return TableCell; + }(DateComponent)); + + function computeFgSegPlacement(segs, // assumed already sorted + dayMaxEvents, dayMaxEventRows, strictOrder, eventInstanceHeights, maxContentHeight, cells) { + var hierarchy = new DayGridSegHierarchy(); + hierarchy.allowReslicing = true; + hierarchy.strictOrder = strictOrder; + if (dayMaxEvents === true || dayMaxEventRows === true) { + hierarchy.maxCoord = maxContentHeight; + hierarchy.hiddenConsumes = true; + } + else if (typeof dayMaxEvents === 'number') { + hierarchy.maxStackCnt = dayMaxEvents; + } + else if (typeof dayMaxEventRows === 'number') { + hierarchy.maxStackCnt = dayMaxEventRows; + hierarchy.hiddenConsumes = true; + } + // create segInputs only for segs with known heights + var segInputs = []; + var unknownHeightSegs = []; + for (var i = 0; i < segs.length; i += 1) { + var seg = segs[i]; + var instanceId = seg.eventRange.instance.instanceId; + var eventHeight = eventInstanceHeights[instanceId]; + if (eventHeight != null) { + segInputs.push({ + index: i, + thickness: eventHeight, + span: { + start: seg.firstCol, + end: seg.lastCol + 1, + }, + }); + } + else { + unknownHeightSegs.push(seg); + } + } + var hiddenEntries = hierarchy.addSegs(segInputs); + var segRects = hierarchy.toRects(); + var _a = placeRects(segRects, segs, cells), singleColPlacements = _a.singleColPlacements, multiColPlacements = _a.multiColPlacements, leftoverMargins = _a.leftoverMargins; + var moreCnts = []; + var moreMarginTops = []; + // add segs with unknown heights + for (var _i = 0, unknownHeightSegs_1 = unknownHeightSegs; _i < unknownHeightSegs_1.length; _i++) { + var seg = unknownHeightSegs_1[_i]; + multiColPlacements[seg.firstCol].push({ + seg: seg, + isVisible: false, + isAbsolute: true, + absoluteTop: 0, + marginTop: 0, + }); + for (var col = seg.firstCol; col <= seg.lastCol; col += 1) { + singleColPlacements[col].push({ + seg: resliceSeg(seg, col, col + 1, cells), + isVisible: false, + isAbsolute: false, + absoluteTop: 0, + marginTop: 0, + }); + } + } + // add the hidden entries + for (var col = 0; col < cells.length; col += 1) { + moreCnts.push(0); + } + for (var _b = 0, hiddenEntries_1 = hiddenEntries; _b < hiddenEntries_1.length; _b++) { + var hiddenEntry = hiddenEntries_1[_b]; + var seg = segs[hiddenEntry.index]; + var hiddenSpan = hiddenEntry.span; + multiColPlacements[hiddenSpan.start].push({ + seg: resliceSeg(seg, hiddenSpan.start, hiddenSpan.end, cells), + isVisible: false, + isAbsolute: true, + absoluteTop: 0, + marginTop: 0, + }); + for (var col = hiddenSpan.start; col < hiddenSpan.end; col += 1) { + moreCnts[col] += 1; + singleColPlacements[col].push({ + seg: resliceSeg(seg, col, col + 1, cells), + isVisible: false, + isAbsolute: false, + absoluteTop: 0, + marginTop: 0, + }); + } + } + // deal with leftover margins + for (var col = 0; col < cells.length; col += 1) { + moreMarginTops.push(leftoverMargins[col]); + } + return { singleColPlacements: singleColPlacements, multiColPlacements: multiColPlacements, moreCnts: moreCnts, moreMarginTops: moreMarginTops }; + } + // rects ordered by top coord, then left + function placeRects(allRects, segs, cells) { + var rectsByEachCol = groupRectsByEachCol(allRects, cells.length); + var singleColPlacements = []; + var multiColPlacements = []; + var leftoverMargins = []; + for (var col = 0; col < cells.length; col += 1) { + var rects = rectsByEachCol[col]; + // compute all static segs in singlePlacements + var singlePlacements = []; + var currentHeight = 0; + var currentMarginTop = 0; + for (var _i = 0, rects_1 = rects; _i < rects_1.length; _i++) { + var rect = rects_1[_i]; + var seg = segs[rect.index]; + singlePlacements.push({ + seg: resliceSeg(seg, col, col + 1, cells), + isVisible: true, + isAbsolute: false, + absoluteTop: rect.levelCoord, + marginTop: rect.levelCoord - currentHeight, + }); + currentHeight = rect.levelCoord + rect.thickness; + } + // compute mixed static/absolute segs in multiPlacements + var multiPlacements = []; + currentHeight = 0; + currentMarginTop = 0; + for (var _a = 0, rects_2 = rects; _a < rects_2.length; _a++) { + var rect = rects_2[_a]; + var seg = segs[rect.index]; + var isAbsolute = rect.span.end - rect.span.start > 1; // multi-column? + var isFirstCol = rect.span.start === col; + currentMarginTop += rect.levelCoord - currentHeight; // amount of space since bottom of previous seg + currentHeight = rect.levelCoord + rect.thickness; // height will now be bottom of current seg + if (isAbsolute) { + currentMarginTop += rect.thickness; + if (isFirstCol) { + multiPlacements.push({ + seg: resliceSeg(seg, rect.span.start, rect.span.end, cells), + isVisible: true, + isAbsolute: true, + absoluteTop: rect.levelCoord, + marginTop: 0, + }); + } + } + else if (isFirstCol) { + multiPlacements.push({ + seg: resliceSeg(seg, rect.span.start, rect.span.end, cells), + isVisible: true, + isAbsolute: false, + absoluteTop: rect.levelCoord, + marginTop: currentMarginTop, // claim the margin + }); + currentMarginTop = 0; + } + } + singleColPlacements.push(singlePlacements); + multiColPlacements.push(multiPlacements); + leftoverMargins.push(currentMarginTop); + } + return { singleColPlacements: singleColPlacements, multiColPlacements: multiColPlacements, leftoverMargins: leftoverMargins }; + } + function groupRectsByEachCol(rects, colCnt) { + var rectsByEachCol = []; + for (var col = 0; col < colCnt; col += 1) { + rectsByEachCol.push([]); + } + for (var _i = 0, rects_3 = rects; _i < rects_3.length; _i++) { + var rect = rects_3[_i]; + for (var col = rect.span.start; col < rect.span.end; col += 1) { + rectsByEachCol[col].push(rect); + } + } + return rectsByEachCol; + } + function resliceSeg(seg, spanStart, spanEnd, cells) { + if (seg.firstCol === spanStart && seg.lastCol === spanEnd - 1) { + return seg; + } + var eventRange = seg.eventRange; + var origRange = eventRange.range; + var slicedRange = intersectRanges(origRange, { + start: cells[spanStart].date, + end: addDays(cells[spanEnd - 1].date, 1), + }); + return __assign(__assign({}, seg), { firstCol: spanStart, lastCol: spanEnd - 1, eventRange: { + def: eventRange.def, + ui: __assign(__assign({}, eventRange.ui), { durationEditable: false }), + instance: eventRange.instance, + range: slicedRange, + }, isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(), isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf() }); + } + var DayGridSegHierarchy = /** @class */ (function (_super) { + __extends(DayGridSegHierarchy, _super); + function DayGridSegHierarchy() { + var _this = _super !== null && _super.apply(this, arguments) || this; + // config + _this.hiddenConsumes = false; + // allows us to keep hidden entries in the hierarchy so they take up space + _this.forceHidden = {}; + return _this; + } + DayGridSegHierarchy.prototype.addSegs = function (segInputs) { + var _this = this; + var hiddenSegs = _super.prototype.addSegs.call(this, segInputs); + var entriesByLevel = this.entriesByLevel; + var excludeHidden = function (entry) { return !_this.forceHidden[buildEntryKey(entry)]; }; + // remove the forced-hidden segs + for (var level = 0; level < entriesByLevel.length; level += 1) { + entriesByLevel[level] = entriesByLevel[level].filter(excludeHidden); + } + return hiddenSegs; + }; + DayGridSegHierarchy.prototype.handleInvalidInsertion = function (insertion, entry, hiddenEntries) { + var _a = this, entriesByLevel = _a.entriesByLevel, forceHidden = _a.forceHidden; + var touchingEntry = insertion.touchingEntry, touchingLevel = insertion.touchingLevel, touchingLateral = insertion.touchingLateral; + if (this.hiddenConsumes && touchingEntry) { + var touchingEntryId = buildEntryKey(touchingEntry); + // if not already hidden + if (!forceHidden[touchingEntryId]) { + if (this.allowReslicing) { + var placeholderEntry = __assign(__assign({}, touchingEntry), { span: intersectSpans(touchingEntry.span, entry.span) }); + var placeholderEntryId = buildEntryKey(placeholderEntry); + forceHidden[placeholderEntryId] = true; + entriesByLevel[touchingLevel][touchingLateral] = placeholderEntry; // replace touchingEntry with our placeholder + this.splitEntry(touchingEntry, entry, hiddenEntries); // split up the touchingEntry, reinsert it + } + else { + forceHidden[touchingEntryId] = true; + hiddenEntries.push(touchingEntry); + } + } + } + return _super.prototype.handleInvalidInsertion.call(this, insertion, entry, hiddenEntries); + }; + return DayGridSegHierarchy; + }(SegHierarchy)); + + var TableRow = /** @class */ (function (_super) { + __extends(TableRow, _super); + function TableRow() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.cellElRefs = new RefMap(); // the ? + createElement("tr", { className: "fc-scrollgrid-section" }, + createElement("td", { className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))), + }); + } + sections.push({ + type: 'body', + key: 'body', + liquid: true, + expandRows: Boolean(context.options.expandRows), + chunk: { + scrollerElRef: this.scrollerElRef, + content: timeContent, + }, + }); + return (createElement(ViewRoot, { viewSpec: context.viewSpec, elRef: this.rootElRef }, function (rootElRef, classNames) { return (createElement("div", { className: ['fc-timegrid'].concat(classNames).join(' '), ref: rootElRef }, + createElement(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [{ width: 'shrink' }], sections: sections }))); })); + }; + TimeColsView.prototype.renderHScrollLayout = function (headerRowContent, allDayContent, timeContent, colCnt, dayMinWidth, slatMetas, slatCoords) { + var _this = this; + var ScrollGrid = this.context.pluginHooks.scrollGridImpl; + if (!ScrollGrid) { + throw new Error('No ScrollGrid implementation'); + } + var _a = this, context = _a.context, props = _a.props; + var stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options); + var stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options); + var sections = []; + if (headerRowContent) { + sections.push({ + type: 'header', + key: 'header', + isSticky: stickyHeaderDates, + syncRowHeights: true, + chunks: [ + { + key: 'axis', + rowContent: function (arg) { return (createElement("tr", null, _this.renderHeadAxis('day', arg.rowSyncHeights[0]))); }, + }, + { + key: 'cols', + elRef: this.headerElRef, + tableClassName: 'fc-col-header', + rowContent: headerRowContent, + }, + ], + }); + } + if (allDayContent) { + sections.push({ + type: 'body', + key: 'all-day', + syncRowHeights: true, + chunks: [ + { + key: 'axis', + rowContent: function (contentArg) { return (createElement("tr", null, _this.renderTableRowAxis(contentArg.rowSyncHeights[0]))); }, + }, + { + key: 'cols', + content: allDayContent, + }, + ], + }); + sections.push({ + key: 'all-day-divider', + type: 'body', + outerContent: ( // TODO: rename to cellContent so don't need to define ? + createElement("tr", { className: "fc-scrollgrid-section" }, + createElement("td", { colSpan: 2, className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))), + }); + } + var isNowIndicator = context.options.nowIndicator; + sections.push({ + type: 'body', + key: 'body', + liquid: true, + expandRows: Boolean(context.options.expandRows), + chunks: [ + { + key: 'axis', + content: function (arg) { return ( + // TODO: make this now-indicator arrow more DRY with TimeColsContent + createElement("div", { className: "fc-timegrid-axis-chunk" }, + createElement("table", { style: { height: arg.expandRows ? arg.clientHeight : '' } }, + arg.tableColGroupNode, + createElement("tbody", null, + createElement(TimeBodyAxis, { slatMetas: slatMetas }))), + createElement("div", { className: "fc-timegrid-now-indicator-container" }, + createElement(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' /* hacky */ }, function (nowDate) { + var nowIndicatorTop = isNowIndicator && + slatCoords && + slatCoords.safeComputeTop(nowDate); // might return void + if (typeof nowIndicatorTop === 'number') { + return (createElement(NowIndicatorRoot, { isAxis: true, date: nowDate }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-arrow'].concat(classNames).join(' '), style: { top: nowIndicatorTop } }, innerContent)); })); + } + return null; + })))); }, + }, + { + key: 'cols', + scrollerElRef: this.scrollerElRef, + content: timeContent, + }, + ], + }); + if (stickyFooterScrollbar) { + sections.push({ + key: 'footer', + type: 'footer', + isSticky: true, + chunks: [ + { + key: 'axis', + content: renderScrollShim, + }, + { + key: 'cols', + content: renderScrollShim, + }, + ], + }); + } + return (createElement(ViewRoot, { viewSpec: context.viewSpec, elRef: this.rootElRef }, function (rootElRef, classNames) { return (createElement("div", { className: ['fc-timegrid'].concat(classNames).join(' '), ref: rootElRef }, + createElement(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: false, colGroups: [ + { width: 'shrink', cols: [{ width: 'shrink' }] }, + { cols: [{ span: colCnt, minWidth: dayMinWidth }] }, + ], sections: sections }))); })); + }; + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + TimeColsView.prototype.getAllDayMaxEventProps = function () { + var _a = this.context.options, dayMaxEvents = _a.dayMaxEvents, dayMaxEventRows = _a.dayMaxEventRows; + if (dayMaxEvents === true || dayMaxEventRows === true) { // is auto? + dayMaxEvents = undefined; + dayMaxEventRows = AUTO_ALL_DAY_MAX_EVENT_ROWS; // make sure "auto" goes to a real number + } + return { dayMaxEvents: dayMaxEvents, dayMaxEventRows: dayMaxEventRows }; + }; + return TimeColsView; + }(DateComponent)); + function renderAllDayInner$1(hookProps) { + return hookProps.text; + } + + var TimeColsSlatsCoords = /** @class */ (function () { + function TimeColsSlatsCoords(positions, dateProfile, slotDuration) { + this.positions = positions; + this.dateProfile = dateProfile; + this.slotDuration = slotDuration; + } + TimeColsSlatsCoords.prototype.safeComputeTop = function (date) { + var dateProfile = this.dateProfile; + if (rangeContainsMarker(dateProfile.currentRange, date)) { + var startOfDayDate = startOfDay(date); + var timeMs = date.valueOf() - startOfDayDate.valueOf(); + if (timeMs >= asRoughMs(dateProfile.slotMinTime) && + timeMs < asRoughMs(dateProfile.slotMaxTime)) { + return this.computeTimeTop(createDuration(timeMs)); + } + } + return null; + }; + // Computes the top coordinate, relative to the bounds of the grid, of the given date. + // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. + TimeColsSlatsCoords.prototype.computeDateTop = function (when, startOfDayDate) { + if (!startOfDayDate) { + startOfDayDate = startOfDay(when); + } + return this.computeTimeTop(createDuration(when.valueOf() - startOfDayDate.valueOf())); + }; + // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration). + // This is a makeshify way to compute the time-top. Assumes all slatMetas dates are uniform. + // Eventually allow computation with arbirary slat dates. + TimeColsSlatsCoords.prototype.computeTimeTop = function (duration) { + var _a = this, positions = _a.positions, dateProfile = _a.dateProfile; + var len = positions.els.length; + // floating-point value of # of slots covered + var slatCoverage = (duration.milliseconds - asRoughMs(dateProfile.slotMinTime)) / asRoughMs(this.slotDuration); + var slatIndex; + var slatRemainder; + // compute a floating-point number for how many slats should be progressed through. + // from 0 to number of slats (inclusive) + // constrained because slotMinTime/slotMaxTime might be customized. + slatCoverage = Math.max(0, slatCoverage); + slatCoverage = Math.min(len, slatCoverage); + // an integer index of the furthest whole slat + // from 0 to number slats (*exclusive*, so len-1) + slatIndex = Math.floor(slatCoverage); + slatIndex = Math.min(slatIndex, len - 1); + // how much further through the slatIndex slat (from 0.0-1.0) must be covered in addition. + // could be 1.0 if slatCoverage is covering *all* the slots + slatRemainder = slatCoverage - slatIndex; + return positions.tops[slatIndex] + + positions.getHeight(slatIndex) * slatRemainder; + }; + return TimeColsSlatsCoords; + }()); + + var TimeColsSlatsBody = /** @class */ (function (_super) { + __extends(TimeColsSlatsBody, _super); + function TimeColsSlatsBody() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeColsSlatsBody.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var slatElRefs = props.slatElRefs; + return (createElement("tbody", null, props.slatMetas.map(function (slatMeta, i) { + var hookProps = { + time: slatMeta.time, + date: context.dateEnv.toDate(slatMeta.date), + view: context.viewApi, + }; + var classNames = [ + 'fc-timegrid-slot', + 'fc-timegrid-slot-lane', + slatMeta.isLabeled ? '' : 'fc-timegrid-slot-minor', + ]; + return (createElement("tr", { key: slatMeta.key, ref: slatElRefs.createRef(slatMeta.key) }, + props.axis && (createElement(TimeColsAxisCell, __assign({}, slatMeta))), + createElement(RenderHook, { hookProps: hookProps, classNames: options.slotLaneClassNames, content: options.slotLaneContent, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-time": slatMeta.isoTimeStr }, innerContent)); }))); + }))); + }; + return TimeColsSlatsBody; + }(BaseComponent)); + + /* + for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. + */ + var TimeColsSlats = /** @class */ (function (_super) { + __extends(TimeColsSlats, _super); + function TimeColsSlats() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + _this.slatElRefs = new RefMap(); + return _this; + } + TimeColsSlats.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + return (createElement("div", { className: "fc-timegrid-slots", ref: this.rootElRef }, + createElement("table", { className: context.theme.getClass('table'), style: { + minWidth: props.tableMinWidth, + width: props.clientWidth, + height: props.minHeight, + } }, + props.tableColGroupNode /* relies on there only being a single for the axis */, + createElement(TimeColsSlatsBody, { slatElRefs: this.slatElRefs, axis: props.axis, slatMetas: props.slatMetas })))); + }; + TimeColsSlats.prototype.componentDidMount = function () { + this.updateSizing(); + }; + TimeColsSlats.prototype.componentDidUpdate = function () { + this.updateSizing(); + }; + TimeColsSlats.prototype.componentWillUnmount = function () { + if (this.props.onCoords) { + this.props.onCoords(null); + } + }; + TimeColsSlats.prototype.updateSizing = function () { + var _a = this, context = _a.context, props = _a.props; + if (props.onCoords && + props.clientWidth !== null // means sizing has stabilized + ) { + var rootEl = this.rootElRef.current; + if (rootEl.offsetHeight) { // not hidden by css + props.onCoords(new TimeColsSlatsCoords(new PositionCache(this.rootElRef.current, collectSlatEls(this.slatElRefs.currentMap, props.slatMetas), false, true), this.props.dateProfile, context.options.slotDuration)); + } + } + }; + return TimeColsSlats; + }(BaseComponent)); + function collectSlatEls(elMap, slatMetas) { + return slatMetas.map(function (slatMeta) { return elMap[slatMeta.key]; }); + } + + function splitSegsByCol(segs, colCnt) { + var segsByCol = []; + var i; + for (i = 0; i < colCnt; i += 1) { + segsByCol.push([]); + } + if (segs) { + for (i = 0; i < segs.length; i += 1) { + segsByCol[segs[i].col].push(segs[i]); + } + } + return segsByCol; + } + function splitInteractionByCol(ui, colCnt) { + var byRow = []; + if (!ui) { + for (var i = 0; i < colCnt; i += 1) { + byRow[i] = null; + } + } + else { + for (var i = 0; i < colCnt; i += 1) { + byRow[i] = { + affectedInstances: ui.affectedInstances, + isEvent: ui.isEvent, + segs: [], + }; + } + for (var _i = 0, _a = ui.segs; _i < _a.length; _i++) { + var seg = _a[_i]; + byRow[seg.col].segs.push(seg); + } + } + return byRow; + } + + var TimeColMoreLink = /** @class */ (function (_super) { + __extends(TimeColMoreLink, _super); + function TimeColMoreLink() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + return _this; + } + TimeColMoreLink.prototype.render = function () { + var _this = this; + var props = this.props; + return (createElement(MoreLinkRoot, { allDayDate: null, moreCnt: props.hiddenSegs.length, allSegs: props.hiddenSegs, hiddenSegs: props.hiddenSegs, alignmentElRef: this.rootElRef, defaultContent: renderMoreLinkInner, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, todayRange: props.todayRange, popoverContent: function () { return renderPlainFgSegs(props.hiddenSegs, props); } }, function (rootElRef, classNames, innerElRef, innerContent, handleClick) { return (createElement("a", { ref: function (el) { + setRef(rootElRef, el); + setRef(_this.rootElRef, el); + }, className: ['fc-timegrid-more-link'].concat(classNames).join(' '), style: { top: props.top, bottom: props.bottom }, onClick: handleClick }, + createElement("div", { ref: innerElRef, className: "fc-timegrid-more-link-inner fc-sticky" }, innerContent))); })); + }; + return TimeColMoreLink; + }(BaseComponent)); + function renderMoreLinkInner(props) { + return props.shortText; + } + + // segInputs assumed sorted + function buildPositioning(segInputs, strictOrder, maxStackCnt) { + var hierarchy = new SegHierarchy(); + if (strictOrder != null) { + hierarchy.strictOrder = strictOrder; + } + if (maxStackCnt != null) { + hierarchy.maxStackCnt = maxStackCnt; + } + var hiddenEntries = hierarchy.addSegs(segInputs); + var hiddenGroups = groupIntersectingEntries(hiddenEntries); + var web = buildWeb(hierarchy); + web = stretchWeb(web, 1); // all levelCoords/thickness will have 0.0-1.0 + var segRects = webToRects(web); + return { segRects: segRects, hiddenGroups: hiddenGroups }; + } + function buildWeb(hierarchy) { + var entriesByLevel = hierarchy.entriesByLevel; + var buildNode = cacheable(function (level, lateral) { return level + ':' + lateral; }, function (level, lateral) { + var siblingRange = findNextLevelSegs(hierarchy, level, lateral); + var nextLevelRes = buildNodes(siblingRange, buildNode); + var entry = entriesByLevel[level][lateral]; + return [ + __assign(__assign({}, entry), { nextLevelNodes: nextLevelRes[0] }), + entry.thickness + nextLevelRes[1], // the pressure builds + ]; + }); + return buildNodes(entriesByLevel.length + ? { level: 0, lateralStart: 0, lateralEnd: entriesByLevel[0].length } + : null, buildNode)[0]; + } + function buildNodes(siblingRange, buildNode) { + if (!siblingRange) { + return [[], 0]; + } + var level = siblingRange.level, lateralStart = siblingRange.lateralStart, lateralEnd = siblingRange.lateralEnd; + var lateral = lateralStart; + var pairs = []; + while (lateral < lateralEnd) { + pairs.push(buildNode(level, lateral)); + lateral += 1; + } + pairs.sort(cmpDescPressures); + return [ + pairs.map(extractNode), + pairs[0][1], // first item's pressure + ]; + } + function cmpDescPressures(a, b) { + return b[1] - a[1]; + } + function extractNode(a) { + return a[0]; + } + function findNextLevelSegs(hierarchy, subjectLevel, subjectLateral) { + var levelCoords = hierarchy.levelCoords, entriesByLevel = hierarchy.entriesByLevel; + var subjectEntry = entriesByLevel[subjectLevel][subjectLateral]; + var afterSubject = levelCoords[subjectLevel] + subjectEntry.thickness; + var levelCnt = levelCoords.length; + var level = subjectLevel; + // skip past levels that are too high up + for (; level < levelCnt && levelCoords[level] < afterSubject; level += 1) + ; // do nothing + for (; level < levelCnt; level += 1) { + var entries = entriesByLevel[level]; + var entry = void 0; + var searchIndex = binarySearch(entries, subjectEntry.span.start, getEntrySpanEnd); + var lateralStart = searchIndex[0] + searchIndex[1]; // if exact match (which doesn't collide), go to next one + var lateralEnd = lateralStart; + while ( // loop through entries that horizontally intersect + (entry = entries[lateralEnd]) && // but not past the whole seg list + entry.span.start < subjectEntry.span.end) { + lateralEnd += 1; + } + if (lateralStart < lateralEnd) { + return { level: level, lateralStart: lateralStart, lateralEnd: lateralEnd }; + } + } + return null; + } + function stretchWeb(topLevelNodes, totalThickness) { + var stretchNode = cacheable(function (node, startCoord, prevThickness) { return buildEntryKey(node); }, function (node, startCoord, prevThickness) { + var nextLevelNodes = node.nextLevelNodes, thickness = node.thickness; + var allThickness = thickness + prevThickness; + var thicknessFraction = thickness / allThickness; + var endCoord; + var newChildren = []; + if (!nextLevelNodes.length) { + endCoord = totalThickness; + } + else { + for (var _i = 0, nextLevelNodes_1 = nextLevelNodes; _i < nextLevelNodes_1.length; _i++) { + var childNode = nextLevelNodes_1[_i]; + if (endCoord === undefined) { + var res = stretchNode(childNode, startCoord, allThickness); + endCoord = res[0]; + newChildren.push(res[1]); + } + else { + var res = stretchNode(childNode, endCoord, 0); + newChildren.push(res[1]); + } + } + } + var newThickness = (endCoord - startCoord) * thicknessFraction; + return [endCoord - newThickness, __assign(__assign({}, node), { thickness: newThickness, nextLevelNodes: newChildren })]; + }); + return topLevelNodes.map(function (node) { return stretchNode(node, 0, 0)[1]; }); + } + // not sorted in any particular order + function webToRects(topLevelNodes) { + var rects = []; + var processNode = cacheable(function (node, levelCoord, stackDepth) { return buildEntryKey(node); }, function (node, levelCoord, stackDepth) { + var rect = __assign(__assign({}, node), { levelCoord: levelCoord, + stackDepth: stackDepth, stackForward: 0 }); + rects.push(rect); + return (rect.stackForward = processNodes(node.nextLevelNodes, levelCoord + node.thickness, stackDepth + 1) + 1); + }); + function processNodes(nodes, levelCoord, stackDepth) { + var stackForward = 0; + for (var _i = 0, nodes_1 = nodes; _i < nodes_1.length; _i++) { + var node = nodes_1[_i]; + stackForward = Math.max(processNode(node, levelCoord, stackDepth), stackForward); + } + return stackForward; + } + processNodes(topLevelNodes, 0, 0); + return rects; // TODO: sort rects by levelCoord to be consistent with toRects? + } + // TODO: move to general util + function cacheable(keyFunc, workFunc) { + var cache = {}; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var key = keyFunc.apply(void 0, args); + return (key in cache) + ? cache[key] + : (cache[key] = workFunc.apply(void 0, args)); + }; + } + + function computeSegVCoords(segs, colDate, slatCoords, eventMinHeight) { + if (slatCoords === void 0) { slatCoords = null; } + if (eventMinHeight === void 0) { eventMinHeight = 0; } + var vcoords = []; + if (slatCoords) { + for (var i = 0; i < segs.length; i += 1) { + var seg = segs[i]; + var spanStart = slatCoords.computeDateTop(seg.start, colDate); + var spanEnd = Math.max(spanStart + (eventMinHeight || 0), // :( + slatCoords.computeDateTop(seg.end, colDate)); + vcoords.push({ + start: Math.round(spanStart), + end: Math.round(spanEnd), // + }); + } + } + return vcoords; + } + function computeFgSegPlacements(segs, segVCoords, // might not have for every seg + eventOrderStrict, eventMaxStack) { + var segInputs = []; + var dumbSegs = []; // segs without coords + for (var i = 0; i < segs.length; i += 1) { + var vcoords = segVCoords[i]; + if (vcoords) { + segInputs.push({ + index: i, + thickness: 1, + span: vcoords, + }); + } + else { + dumbSegs.push(segs[i]); + } + } + var _a = buildPositioning(segInputs, eventOrderStrict, eventMaxStack), segRects = _a.segRects, hiddenGroups = _a.hiddenGroups; + var segPlacements = []; + for (var _i = 0, segRects_1 = segRects; _i < segRects_1.length; _i++) { + var segRect = segRects_1[_i]; + segPlacements.push({ + seg: segs[segRect.index], + rect: segRect, + }); + } + for (var _b = 0, dumbSegs_1 = dumbSegs; _b < dumbSegs_1.length; _b++) { + var dumbSeg = dumbSegs_1[_b]; + segPlacements.push({ seg: dumbSeg, rect: null }); + } + return { segPlacements: segPlacements, hiddenGroups: hiddenGroups }; + } + + var DEFAULT_TIME_FORMAT$1 = createFormatter({ + hour: 'numeric', + minute: '2-digit', + meridiem: false, + }); + var TimeColEvent = /** @class */ (function (_super) { + __extends(TimeColEvent, _super); + function TimeColEvent() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeColEvent.prototype.render = function () { + var classNames = [ + 'fc-timegrid-event', + 'fc-v-event', + ]; + if (this.props.isShort) { + classNames.push('fc-timegrid-event-short'); + } + return (createElement(StandardEvent, __assign({}, this.props, { defaultTimeFormat: DEFAULT_TIME_FORMAT$1, extraClassNames: classNames }))); + }; + return TimeColEvent; + }(BaseComponent)); + + var TimeColMisc = /** @class */ (function (_super) { + __extends(TimeColMisc, _super); + function TimeColMisc() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeColMisc.prototype.render = function () { + var props = this.props; + return (createElement(DayCellContent, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraHookProps: props.extraHookProps }, function (innerElRef, innerContent) { return (innerContent && + createElement("div", { className: "fc-timegrid-col-misc", ref: innerElRef }, innerContent)); })); + }; + return TimeColMisc; + }(BaseComponent)); + + var TimeCol = /** @class */ (function (_super) { + __extends(TimeCol, _super); + function TimeCol() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.sortEventSegs = memoize(sortEventSegs); + return _this; + } + // TODO: memoize event-placement? + TimeCol.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var isSelectMirror = context.options.selectMirror; + var mirrorSegs = (props.eventDrag && props.eventDrag.segs) || + (props.eventResize && props.eventResize.segs) || + (isSelectMirror && props.dateSelectionSegs) || + []; + var interactionAffectedInstances = // TODO: messy way to compute this + (props.eventDrag && props.eventDrag.affectedInstances) || + (props.eventResize && props.eventResize.affectedInstances) || + {}; + var sortedFgSegs = this.sortEventSegs(props.fgEventSegs, context.options.eventOrder); + return (createElement(DayCellRoot, { elRef: props.elRef, date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraHookProps: props.extraHookProps }, function (rootElRef, classNames, dataAttrs) { return (createElement("td", __assign({ ref: rootElRef, className: ['fc-timegrid-col'].concat(classNames, props.extraClassNames || []).join(' ') }, dataAttrs, props.extraDataAttrs), + createElement("div", { className: "fc-timegrid-col-frame" }, + createElement("div", { className: "fc-timegrid-col-bg" }, + _this.renderFillSegs(props.businessHourSegs, 'non-business'), + _this.renderFillSegs(props.bgEventSegs, 'bg-event'), + _this.renderFillSegs(props.dateSelectionSegs, 'highlight')), + createElement("div", { className: "fc-timegrid-col-events" }, _this.renderFgSegs(sortedFgSegs, interactionAffectedInstances, false, false, false)), + createElement("div", { className: "fc-timegrid-col-events" }, _this.renderFgSegs(mirrorSegs, {}, Boolean(props.eventDrag), Boolean(props.eventResize), Boolean(isSelectMirror))), + createElement("div", { className: "fc-timegrid-now-indicator-container" }, _this.renderNowIndicator(props.nowIndicatorSegs)), + createElement(TimeColMisc, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraHookProps: props.extraHookProps })))); })); + }; + TimeCol.prototype.renderFgSegs = function (sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting) { + var props = this.props; + if (props.forPrint) { + return renderPlainFgSegs(sortedFgSegs, props); + } + return this.renderPositionedFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting); + }; + TimeCol.prototype.renderPositionedFgSegs = function (segs, // if not mirror, needs to be sorted + segIsInvisible, isDragging, isResizing, isDateSelecting) { + var _this = this; + var _a = this.context.options, eventMaxStack = _a.eventMaxStack, eventShortHeight = _a.eventShortHeight, eventOrderStrict = _a.eventOrderStrict, eventMinHeight = _a.eventMinHeight; + var _b = this.props, date = _b.date, slatCoords = _b.slatCoords, eventSelection = _b.eventSelection, todayRange = _b.todayRange, nowDate = _b.nowDate; + var isMirror = isDragging || isResizing || isDateSelecting; + var segVCoords = computeSegVCoords(segs, date, slatCoords, eventMinHeight); + var _c = computeFgSegPlacements(segs, segVCoords, eventOrderStrict, eventMaxStack), segPlacements = _c.segPlacements, hiddenGroups = _c.hiddenGroups; + return (createElement(Fragment, null, + this.renderHiddenGroups(hiddenGroups, segs), + segPlacements.map(function (segPlacement) { + var seg = segPlacement.seg, rect = segPlacement.rect; + var instanceId = seg.eventRange.instance.instanceId; + var isVisible = isMirror || Boolean(!segIsInvisible[instanceId] && rect); + var vStyle = computeSegVStyle(rect && rect.span); + var hStyle = (!isMirror && rect) ? _this.computeSegHStyle(rect) : { left: 0, right: 0 }; + var isInset = Boolean(rect) && rect.stackForward > 0; + var isShort = Boolean(rect) && (rect.span.end - rect.span.start) < eventShortHeight; // look at other places for this problem + return (createElement("div", { className: 'fc-timegrid-event-harness' + + (isInset ? ' fc-timegrid-event-harness-inset' : ''), key: instanceId, style: __assign(__assign({ visibility: isVisible ? '' : 'hidden' }, vStyle), hStyle) }, + createElement(TimeColEvent, __assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, isShort: isShort }, getSegMeta(seg, todayRange, nowDate))))); + }))); + }; + // will already have eventMinHeight applied because segInputs already had it + TimeCol.prototype.renderHiddenGroups = function (hiddenGroups, segs) { + var _a = this.props, extraDateSpan = _a.extraDateSpan, dateProfile = _a.dateProfile, todayRange = _a.todayRange, nowDate = _a.nowDate, eventSelection = _a.eventSelection, eventDrag = _a.eventDrag, eventResize = _a.eventResize; + return (createElement(Fragment, null, hiddenGroups.map(function (hiddenGroup) { + var positionCss = computeSegVStyle(hiddenGroup.span); + var hiddenSegs = compileSegsFromEntries(hiddenGroup.entries, segs); + return (createElement(TimeColMoreLink, { key: buildIsoString(computeEarliestSegStart(hiddenSegs)), hiddenSegs: hiddenSegs, top: positionCss.top, bottom: positionCss.bottom, extraDateSpan: extraDateSpan, dateProfile: dateProfile, todayRange: todayRange, nowDate: nowDate, eventSelection: eventSelection, eventDrag: eventDrag, eventResize: eventResize })); + }))); + }; + TimeCol.prototype.renderFillSegs = function (segs, fillType) { + var _a = this, props = _a.props, context = _a.context; + var segVCoords = computeSegVCoords(segs, props.date, props.slatCoords, context.options.eventMinHeight); // don't assume all populated + var children = segVCoords.map(function (vcoords, i) { + var seg = segs[i]; + return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-timegrid-bg-harness", style: computeSegVStyle(vcoords) }, fillType === 'bg-event' ? + createElement(BgEvent, __assign({ seg: seg }, getSegMeta(seg, props.todayRange, props.nowDate))) : + renderFill(fillType))); + }); + return createElement(Fragment, null, children); + }; + TimeCol.prototype.renderNowIndicator = function (segs) { + var _a = this.props, slatCoords = _a.slatCoords, date = _a.date; + if (!slatCoords) { + return null; + } + return segs.map(function (seg, i) { return (createElement(NowIndicatorRoot, { isAxis: false, date: date, + // key doesn't matter. will only ever be one + key: i }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-line'].concat(classNames).join(' '), style: { top: slatCoords.computeDateTop(seg.start, date) } }, innerContent)); })); }); + }; + TimeCol.prototype.computeSegHStyle = function (segHCoords) { + var _a = this.context, isRtl = _a.isRtl, options = _a.options; + var shouldOverlap = options.slotEventOverlap; + var nearCoord = segHCoords.levelCoord; // the left side if LTR. the right side if RTL. floating-point + var farCoord = segHCoords.levelCoord + segHCoords.thickness; // the right side if LTR. the left side if RTL. floating-point + var left; // amount of space from left edge, a fraction of the total width + var right; // amount of space from right edge, a fraction of the total width + if (shouldOverlap) { + // double the width, but don't go beyond the maximum forward coordinate (1.0) + farCoord = Math.min(1, nearCoord + (farCoord - nearCoord) * 2); + } + if (isRtl) { + left = 1 - farCoord; + right = nearCoord; + } + else { + left = nearCoord; + right = 1 - farCoord; + } + var props = { + zIndex: segHCoords.stackDepth + 1, + left: left * 100 + '%', + right: right * 100 + '%', + }; + if (shouldOverlap && !segHCoords.stackForward) { + // add padding to the edge so that forward stacked events don't cover the resizer's icon + props[isRtl ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width + } + return props; + }; + return TimeCol; + }(BaseComponent)); + function renderPlainFgSegs(sortedFgSegs, _a) { + var todayRange = _a.todayRange, nowDate = _a.nowDate, eventSelection = _a.eventSelection, eventDrag = _a.eventDrag, eventResize = _a.eventResize; + var hiddenInstances = (eventDrag ? eventDrag.affectedInstances : null) || + (eventResize ? eventResize.affectedInstances : null) || + {}; + return (createElement(Fragment, null, sortedFgSegs.map(function (seg) { + var instanceId = seg.eventRange.instance.instanceId; + return (createElement("div", { key: instanceId, style: { visibility: hiddenInstances[instanceId] ? 'hidden' : '' } }, + createElement(TimeColEvent, __assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === eventSelection, isShort: false }, getSegMeta(seg, todayRange, nowDate))))); + }))); + } + function computeSegVStyle(segVCoords) { + if (!segVCoords) { + return { top: '', bottom: '' }; + } + return { + top: segVCoords.start, + bottom: -segVCoords.end, + }; + } + function compileSegsFromEntries(segEntries, allSegs) { + return segEntries.map(function (segEntry) { return allSegs[segEntry.index]; }); + } + + var TimeColsContent = /** @class */ (function (_super) { + __extends(TimeColsContent, _super); + function TimeColsContent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.splitFgEventSegs = memoize(splitSegsByCol); + _this.splitBgEventSegs = memoize(splitSegsByCol); + _this.splitBusinessHourSegs = memoize(splitSegsByCol); + _this.splitNowIndicatorSegs = memoize(splitSegsByCol); + _this.splitDateSelectionSegs = memoize(splitSegsByCol); + _this.splitEventDrag = memoize(splitInteractionByCol); + _this.splitEventResize = memoize(splitInteractionByCol); + _this.rootElRef = createRef(); + _this.cellElRefs = new RefMap(); + return _this; + } + TimeColsContent.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var nowIndicatorTop = context.options.nowIndicator && + props.slatCoords && + props.slatCoords.safeComputeTop(props.nowDate); // might return void + var colCnt = props.cells.length; + var fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, colCnt); + var bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, colCnt); + var businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, colCnt); + var nowIndicatorSegsByRow = this.splitNowIndicatorSegs(props.nowIndicatorSegs, colCnt); + var dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, colCnt); + var eventDragByRow = this.splitEventDrag(props.eventDrag, colCnt); + var eventResizeByRow = this.splitEventResize(props.eventResize, colCnt); + return (createElement("div", { className: "fc-timegrid-cols", ref: this.rootElRef }, + createElement("table", { style: { + minWidth: props.tableMinWidth, + width: props.clientWidth, + } }, + props.tableColGroupNode, + createElement("tbody", null, + createElement("tr", null, + props.axis && (createElement("td", { className: "fc-timegrid-col fc-timegrid-axis" }, + createElement("div", { className: "fc-timegrid-col-frame" }, + createElement("div", { className: "fc-timegrid-now-indicator-container" }, typeof nowIndicatorTop === 'number' && (createElement(NowIndicatorRoot, { isAxis: true, date: props.nowDate }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-arrow'].concat(classNames).join(' '), style: { top: nowIndicatorTop } }, innerContent)); })))))), + props.cells.map(function (cell, i) { return (createElement(TimeCol, { key: cell.key, elRef: _this.cellElRefs.createRef(cell.key), dateProfile: props.dateProfile, date: cell.date, nowDate: props.nowDate, todayRange: props.todayRange, extraHookProps: cell.extraHookProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, fgEventSegs: fgEventSegsByRow[i], bgEventSegs: bgEventSegsByRow[i], businessHourSegs: businessHourSegsByRow[i], nowIndicatorSegs: nowIndicatorSegsByRow[i], dateSelectionSegs: dateSelectionSegsByRow[i], eventDrag: eventDragByRow[i], eventResize: eventResizeByRow[i], slatCoords: props.slatCoords, eventSelection: props.eventSelection, forPrint: props.forPrint })); })))))); + }; + TimeColsContent.prototype.componentDidMount = function () { + this.updateCoords(); + }; + TimeColsContent.prototype.componentDidUpdate = function () { + this.updateCoords(); + }; + TimeColsContent.prototype.updateCoords = function () { + var props = this.props; + if (props.onColCoords && + props.clientWidth !== null // means sizing has stabilized + ) { + props.onColCoords(new PositionCache(this.rootElRef.current, collectCellEls(this.cellElRefs.currentMap, props.cells), true, // horizontal + false)); + } + }; + return TimeColsContent; + }(BaseComponent)); + function collectCellEls(elMap, cells) { + return cells.map(function (cell) { return elMap[cell.key]; }); + } + + /* A component that renders one or more columns of vertical time slots + ----------------------------------------------------------------------------------------------------------------------*/ + var TimeCols = /** @class */ (function (_super) { + __extends(TimeCols, _super); + function TimeCols() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.processSlotOptions = memoize(processSlotOptions); + _this.state = { + slatCoords: null, + }; + _this.handleRootEl = function (el) { + if (el) { + _this.context.registerInteractiveComponent(_this, { + el: el, + isHitComboAllowed: _this.props.isHitComboAllowed, + }); + } + else { + _this.context.unregisterInteractiveComponent(_this); + } + }; + _this.handleScrollRequest = function (request) { + var onScrollTopRequest = _this.props.onScrollTopRequest; + var slatCoords = _this.state.slatCoords; + if (onScrollTopRequest && slatCoords) { + if (request.time) { + var top_1 = slatCoords.computeTimeTop(request.time); + top_1 = Math.ceil(top_1); // zoom can give weird floating-point values. rather scroll a little bit further + if (top_1) { + top_1 += 1; // to overcome top border that slots beyond the first have. looks better + } + onScrollTopRequest(top_1); + } + return true; + } + return false; + }; + _this.handleColCoords = function (colCoords) { + _this.colCoords = colCoords; + }; + _this.handleSlatCoords = function (slatCoords) { + _this.setState({ slatCoords: slatCoords }); + if (_this.props.onSlatCoords) { + _this.props.onSlatCoords(slatCoords); + } + }; + return _this; + } + TimeCols.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state; + return (createElement("div", { className: "fc-timegrid-body", ref: this.handleRootEl, style: { + // these props are important to give this wrapper correct dimensions for interactions + // TODO: if we set it here, can we avoid giving to inner tables? + width: props.clientWidth, + minWidth: props.tableMinWidth, + } }, + createElement(TimeColsSlats, { axis: props.axis, dateProfile: props.dateProfile, slatMetas: props.slatMetas, clientWidth: props.clientWidth, minHeight: props.expandRows ? props.clientHeight : '', tableMinWidth: props.tableMinWidth, tableColGroupNode: props.axis ? props.tableColGroupNode : null /* axis depends on the colgroup's shrinking */, onCoords: this.handleSlatCoords }), + createElement(TimeColsContent, { cells: props.cells, axis: props.axis, dateProfile: props.dateProfile, businessHourSegs: props.businessHourSegs, bgEventSegs: props.bgEventSegs, fgEventSegs: props.fgEventSegs, dateSelectionSegs: props.dateSelectionSegs, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange, nowDate: props.nowDate, nowIndicatorSegs: props.nowIndicatorSegs, clientWidth: props.clientWidth, tableMinWidth: props.tableMinWidth, tableColGroupNode: props.tableColGroupNode, slatCoords: state.slatCoords, onColCoords: this.handleColCoords, forPrint: props.forPrint }))); + }; + TimeCols.prototype.componentDidMount = function () { + this.scrollResponder = this.context.createScrollResponder(this.handleScrollRequest); + }; + TimeCols.prototype.componentDidUpdate = function (prevProps) { + this.scrollResponder.update(prevProps.dateProfile !== this.props.dateProfile); + }; + TimeCols.prototype.componentWillUnmount = function () { + this.scrollResponder.detach(); + }; + TimeCols.prototype.queryHit = function (positionLeft, positionTop) { + var _a = this.context, dateEnv = _a.dateEnv, options = _a.options; + var colCoords = this.colCoords; + var dateProfile = this.props.dateProfile; + var slatCoords = this.state.slatCoords; + var _b = this.processSlotOptions(this.props.slotDuration, options.snapDuration), snapDuration = _b.snapDuration, snapsPerSlot = _b.snapsPerSlot; + var colIndex = colCoords.leftToIndex(positionLeft); + var slatIndex = slatCoords.positions.topToIndex(positionTop); + if (colIndex != null && slatIndex != null) { + var cell = this.props.cells[colIndex]; + var slatTop = slatCoords.positions.tops[slatIndex]; + var slatHeight = slatCoords.positions.getHeight(slatIndex); + var partial = (positionTop - slatTop) / slatHeight; // floating point number between 0 and 1 + var localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat + var snapIndex = slatIndex * snapsPerSlot + localSnapIndex; + var dayDate = this.props.cells[colIndex].date; + var time = addDurations(dateProfile.slotMinTime, multiplyDuration(snapDuration, snapIndex)); + var start = dateEnv.add(dayDate, time); + var end = dateEnv.add(start, snapDuration); + return { + dateProfile: dateProfile, + dateSpan: __assign({ range: { start: start, end: end }, allDay: false }, cell.extraDateSpan), + dayEl: colCoords.els[colIndex], + rect: { + left: colCoords.lefts[colIndex], + right: colCoords.rights[colIndex], + top: slatTop, + bottom: slatTop + slatHeight, + }, + layer: 0, + }; + } + return null; + }; + return TimeCols; + }(DateComponent)); + function processSlotOptions(slotDuration, snapDurationOverride) { + var snapDuration = snapDurationOverride || slotDuration; + var snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration); + if (snapsPerSlot === null) { + snapDuration = slotDuration; + snapsPerSlot = 1; + // TODO: say warning? + } + return { snapDuration: snapDuration, snapsPerSlot: snapsPerSlot }; + } + + var DayTimeColsSlicer = /** @class */ (function (_super) { + __extends(DayTimeColsSlicer, _super); + function DayTimeColsSlicer() { + return _super !== null && _super.apply(this, arguments) || this; + } + DayTimeColsSlicer.prototype.sliceRange = function (range, dayRanges) { + var segs = []; + for (var col = 0; col < dayRanges.length; col += 1) { + var segRange = intersectRanges(range, dayRanges[col]); + if (segRange) { + segs.push({ + start: segRange.start, + end: segRange.end, + isStart: segRange.start.valueOf() === range.start.valueOf(), + isEnd: segRange.end.valueOf() === range.end.valueOf(), + col: col, + }); + } + } + return segs; + }; + return DayTimeColsSlicer; + }(Slicer)); + + var DayTimeCols = /** @class */ (function (_super) { + __extends(DayTimeCols, _super); + function DayTimeCols() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildDayRanges = memoize(buildDayRanges); + _this.slicer = new DayTimeColsSlicer(); + _this.timeColsRef = createRef(); + return _this; + } + DayTimeCols.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var dateProfile = props.dateProfile, dayTableModel = props.dayTableModel; + var isNowIndicator = context.options.nowIndicator; + var dayRanges = this.buildDayRanges(dayTableModel, dateProfile, context.dateEnv); + // give it the first row of cells + // TODO: would move this further down hierarchy, but sliceNowDate needs it + return (createElement(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' }, function (nowDate, todayRange) { return (createElement(TimeCols, __assign({ ref: _this.timeColsRef }, _this.slicer.sliceProps(props, dateProfile, null, context, dayRanges), { forPrint: props.forPrint, axis: props.axis, dateProfile: dateProfile, slatMetas: props.slatMetas, slotDuration: props.slotDuration, cells: dayTableModel.cells[0], tableColGroupNode: props.tableColGroupNode, tableMinWidth: props.tableMinWidth, clientWidth: props.clientWidth, clientHeight: props.clientHeight, expandRows: props.expandRows, nowDate: nowDate, nowIndicatorSegs: isNowIndicator && _this.slicer.sliceNowDate(nowDate, context, dayRanges), todayRange: todayRange, onScrollTopRequest: props.onScrollTopRequest, onSlatCoords: props.onSlatCoords }))); })); + }; + return DayTimeCols; + }(DateComponent)); + function buildDayRanges(dayTableModel, dateProfile, dateEnv) { + var ranges = []; + for (var _i = 0, _a = dayTableModel.headerDates; _i < _a.length; _i++) { + var date = _a[_i]; + ranges.push({ + start: dateEnv.add(date, dateProfile.slotMinTime), + end: dateEnv.add(date, dateProfile.slotMaxTime), + }); + } + return ranges; + } + + // potential nice values for the slot-duration and interval-duration + // from largest to smallest + var STOCK_SUB_DURATIONS = [ + { hours: 1 }, + { minutes: 30 }, + { minutes: 15 }, + { seconds: 30 }, + { seconds: 15 }, + ]; + function buildSlatMetas(slotMinTime, slotMaxTime, explicitLabelInterval, slotDuration, dateEnv) { + var dayStart = new Date(0); + var slatTime = slotMinTime; + var slatIterator = createDuration(0); + var labelInterval = explicitLabelInterval || computeLabelInterval(slotDuration); + var metas = []; + while (asRoughMs(slatTime) < asRoughMs(slotMaxTime)) { + var date = dateEnv.add(dayStart, slatTime); + var isLabeled = wholeDivideDurations(slatIterator, labelInterval) !== null; + metas.push({ + date: date, + time: slatTime, + key: date.toISOString(), + isoTimeStr: formatIsoTimeString(date), + isLabeled: isLabeled, + }); + slatTime = addDurations(slatTime, slotDuration); + slatIterator = addDurations(slatIterator, slotDuration); + } + return metas; + } + // Computes an automatic value for slotLabelInterval + function computeLabelInterval(slotDuration) { + var i; + var labelInterval; + var slotsPerLabel; + // find the smallest stock label interval that results in more than one slots-per-label + for (i = STOCK_SUB_DURATIONS.length - 1; i >= 0; i -= 1) { + labelInterval = createDuration(STOCK_SUB_DURATIONS[i]); + slotsPerLabel = wholeDivideDurations(labelInterval, slotDuration); + if (slotsPerLabel !== null && slotsPerLabel > 1) { + return labelInterval; + } + } + return slotDuration; // fall back + } + + var DayTimeColsView = /** @class */ (function (_super) { + __extends(DayTimeColsView, _super); + function DayTimeColsView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildTimeColsModel = memoize(buildTimeColsModel); + _this.buildSlatMetas = memoize(buildSlatMetas); + return _this; + } + DayTimeColsView.prototype.render = function () { + var _this = this; + var _a = this.context, options = _a.options, dateEnv = _a.dateEnv, dateProfileGenerator = _a.dateProfileGenerator; + var props = this.props; + var dateProfile = props.dateProfile; + var dayTableModel = this.buildTimeColsModel(dateProfile, dateProfileGenerator); + var splitProps = this.allDaySplitter.splitProps(props); + var slatMetas = this.buildSlatMetas(dateProfile.slotMinTime, dateProfile.slotMaxTime, options.slotLabelInterval, options.slotDuration, dateEnv); + var dayMinWidth = options.dayMinWidth; + var hasAttachedAxis = !dayMinWidth; + var hasDetachedAxis = dayMinWidth; + var headerContent = options.dayHeaders && (createElement(DayHeader, { dates: dayTableModel.headerDates, dateProfile: dateProfile, datesRepDistinctDays: true, renderIntro: hasAttachedAxis ? this.renderHeadAxis : null })); + var allDayContent = (options.allDaySlot !== false) && (function (contentArg) { return (createElement(DayTable, __assign({}, splitProps.allDay, { dateProfile: dateProfile, dayTableModel: dayTableModel, nextDayThreshold: options.nextDayThreshold, tableMinWidth: contentArg.tableMinWidth, colGroupNode: contentArg.tableColGroupNode, renderRowIntro: hasAttachedAxis ? _this.renderTableRowAxis : null, showWeekNumbers: false, expandRows: false, headerAlignElRef: _this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint }, _this.getAllDayMaxEventProps()))); }); + var timeGridContent = function (contentArg) { return (createElement(DayTimeCols, __assign({}, splitProps.timed, { dayTableModel: dayTableModel, dateProfile: dateProfile, axis: hasAttachedAxis, slotDuration: options.slotDuration, slatMetas: slatMetas, forPrint: props.forPrint, tableColGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, onSlatCoords: _this.handleSlatCoords, expandRows: contentArg.expandRows, onScrollTopRequest: _this.handleScrollTopRequest }))); }; + return hasDetachedAxis + ? this.renderHScrollLayout(headerContent, allDayContent, timeGridContent, dayTableModel.colCnt, dayMinWidth, slatMetas, this.state.slatCoords) + : this.renderSimpleLayout(headerContent, allDayContent, timeGridContent); + }; + return DayTimeColsView; + }(TimeColsView)); + function buildTimeColsModel(dateProfile, dateProfileGenerator) { + var daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator); + return new DayTableModel(daySeries, false); + } + + var OPTION_REFINERS$2 = { + allDaySlot: Boolean, + }; + + var timeGridPlugin = createPlugin({ + initialView: 'timeGridWeek', + optionRefiners: OPTION_REFINERS$2, + views: { + timeGrid: { + component: DayTimeColsView, + usesMinMaxTime: true, + allDaySlot: true, + slotDuration: '00:30:00', + slotEventOverlap: true, // a bad name. confused with overlap/constraint system + }, + timeGridDay: { + type: 'timeGrid', + duration: { days: 1 }, + }, + timeGridWeek: { + type: 'timeGrid', + duration: { weeks: 1 }, + }, + }, + }); + + var ListViewHeaderRow = /** @class */ (function (_super) { + __extends(ListViewHeaderRow, _super); + function ListViewHeaderRow() { + return _super !== null && _super.apply(this, arguments) || this; + } + ListViewHeaderRow.prototype.render = function () { + var _a = this.props, dayDate = _a.dayDate, todayRange = _a.todayRange; + var _b = this.context, theme = _b.theme, dateEnv = _b.dateEnv, options = _b.options, viewApi = _b.viewApi; + var dayMeta = getDateMeta(dayDate, todayRange); + // will ever be falsy? + var text = options.listDayFormat ? dateEnv.format(dayDate, options.listDayFormat) : ''; + // will ever be falsy? also, BAD NAME "alt" + var sideText = options.listDaySideFormat ? dateEnv.format(dayDate, options.listDaySideFormat) : ''; + var navLinkData = options.navLinks + ? buildNavLinkData(dayDate) + : null; + var hookProps = __assign({ date: dateEnv.toDate(dayDate), view: viewApi, text: text, + sideText: sideText, + navLinkData: navLinkData }, dayMeta); + var classNames = ['fc-list-day'].concat(getDayClassNames(dayMeta, theme)); + // TODO: make a reusable HOC for dayHeader (used in daygrid/timegrid too) + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.dayHeaderClassNames, content: options.dayHeaderContent, defaultContent: renderInnerContent, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("tr", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-date": formatDayString(dayDate) }, + createElement("th", { colSpan: 3 }, + createElement("div", { className: 'fc-list-day-cushion ' + theme.getClass('tableCellShaded'), ref: innerElRef }, innerContent)))); })); + }; + return ListViewHeaderRow; + }(BaseComponent)); + function renderInnerContent(props) { + var navLinkAttrs = props.navLinkData // is there a type for this? + ? { 'data-navlink': props.navLinkData, tabIndex: 0 } + : {}; + return (createElement(Fragment, null, + props.text && (createElement("a", __assign({ className: "fc-list-day-text" }, navLinkAttrs), props.text)), + props.sideText && (createElement("a", __assign({ className: "fc-list-day-side-text" }, navLinkAttrs), props.sideText)))); + } + + var DEFAULT_TIME_FORMAT = createFormatter({ + hour: 'numeric', + minute: '2-digit', + meridiem: 'short', + }); + var ListViewEventRow = /** @class */ (function (_super) { + __extends(ListViewEventRow, _super); + function ListViewEventRow() { + return _super !== null && _super.apply(this, arguments) || this; + } + ListViewEventRow.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var seg = props.seg; + var timeFormat = context.options.eventTimeFormat || DEFAULT_TIME_FORMAT; + return (createElement(EventRoot, { seg: seg, timeText: "" // BAD. because of all-day content + , disableDragging: true, disableResizing: true, defaultContent: renderEventInnerContent, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday, isSelected: props.isSelected, isDragging: props.isDragging, isResizing: props.isResizing, isDateSelecting: props.isDateSelecting }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("tr", { className: ['fc-list-event', hookProps.event.url ? 'fc-event-forced-url' : ''].concat(classNames).join(' '), ref: rootElRef }, + buildTimeContent(seg, timeFormat, context), + createElement("td", { className: "fc-list-event-graphic" }, + createElement("span", { className: "fc-list-event-dot", style: { borderColor: hookProps.borderColor || hookProps.backgroundColor } })), + createElement("td", { className: "fc-list-event-title", ref: innerElRef }, innerContent))); })); + }; + return ListViewEventRow; + }(BaseComponent)); + function renderEventInnerContent(props) { + var event = props.event; + var url = event.url; + var anchorAttrs = url ? { href: url } : {}; + return (createElement("a", __assign({}, anchorAttrs), event.title)); + } + function buildTimeContent(seg, timeFormat, context) { + var options = context.options; + if (options.displayEventTime !== false) { + var eventDef = seg.eventRange.def; + var eventInstance = seg.eventRange.instance; + var doAllDay = false; + var timeText = void 0; + if (eventDef.allDay) { + doAllDay = true; + } + else if (isMultiDayRange(seg.eventRange.range)) { // TODO: use (!isStart || !isEnd) instead? + if (seg.isStart) { + timeText = buildSegTimeText(seg, timeFormat, context, null, null, eventInstance.range.start, seg.end); + } + else if (seg.isEnd) { + timeText = buildSegTimeText(seg, timeFormat, context, null, null, seg.start, eventInstance.range.end); + } + else { + doAllDay = true; + } + } + else { + timeText = buildSegTimeText(seg, timeFormat, context); + } + if (doAllDay) { + var hookProps = { + text: context.options.allDayText, + view: context.viewApi, + }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.allDayClassNames, content: options.allDayContent, defaultContent: renderAllDayInner, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("td", { className: ['fc-list-event-time'].concat(classNames).join(' '), ref: rootElRef }, innerContent)); })); + } + return (createElement("td", { className: "fc-list-event-time" }, timeText)); + } + return null; + } + function renderAllDayInner(hookProps) { + return hookProps.text; + } + + /* + Responsible for the scroller, and forwarding event-related actions into the "grid". + */ + var ListView = /** @class */ (function (_super) { + __extends(ListView, _super); + function ListView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.computeDateVars = memoize(computeDateVars); + _this.eventStoreToSegs = memoize(_this._eventStoreToSegs); + _this.setRootEl = function (rootEl) { + if (rootEl) { + _this.context.registerInteractiveComponent(_this, { + el: rootEl, + }); + } + else { + _this.context.unregisterInteractiveComponent(_this); + } + }; + return _this; + } + ListView.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var extraClassNames = [ + 'fc-list', + context.theme.getClass('table'), + context.options.stickyHeaderDates !== false ? 'fc-list-sticky' : '', + ]; + var _b = this.computeDateVars(props.dateProfile), dayDates = _b.dayDates, dayRanges = _b.dayRanges; + var eventSegs = this.eventStoreToSegs(props.eventStore, props.eventUiBases, dayRanges); + return (createElement(ViewRoot, { viewSpec: context.viewSpec, elRef: this.setRootEl }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: extraClassNames.concat(classNames).join(' ') }, + createElement(Scroller, { liquid: !props.isHeightAuto, overflowX: props.isHeightAuto ? 'visible' : 'hidden', overflowY: props.isHeightAuto ? 'visible' : 'auto' }, eventSegs.length > 0 ? + _this.renderSegList(eventSegs, dayDates) : + _this.renderEmptyMessage()))); })); + }; + ListView.prototype.renderEmptyMessage = function () { + var _a = this.context, options = _a.options, viewApi = _a.viewApi; + var hookProps = { + text: options.noEventsText, + view: viewApi, + }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.noEventsClassNames, content: options.noEventsContent, defaultContent: renderNoEventsInner, didMount: options.noEventsDidMount, willUnmount: options.noEventsWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { className: ['fc-list-empty'].concat(classNames).join(' '), ref: rootElRef }, + createElement("div", { className: "fc-list-empty-cushion", ref: innerElRef }, innerContent))); })); + }; + ListView.prototype.renderSegList = function (allSegs, dayDates) { + var _a = this.context, theme = _a.theme, options = _a.options; + var segsByDay = groupSegsByDay(allSegs); // sparse array + return (createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { + var innerNodes = []; + for (var dayIndex = 0; dayIndex < segsByDay.length; dayIndex += 1) { + var daySegs = segsByDay[dayIndex]; + if (daySegs) { // sparse array, so might be undefined + var dayStr = dayDates[dayIndex].toISOString(); + // append a day header + innerNodes.push(createElement(ListViewHeaderRow, { key: dayStr, dayDate: dayDates[dayIndex], todayRange: todayRange })); + daySegs = sortEventSegs(daySegs, options.eventOrder); + for (var _i = 0, daySegs_1 = daySegs; _i < daySegs_1.length; _i++) { + var seg = daySegs_1[_i]; + innerNodes.push(createElement(ListViewEventRow, __assign({ key: dayStr + ':' + seg.eventRange.instance.instanceId /* are multiple segs for an instanceId */, seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false }, getSegMeta(seg, todayRange, nowDate)))); + } + } + } + return (createElement("table", { className: 'fc-list-table ' + theme.getClass('table') }, + createElement("tbody", null, innerNodes))); + })); + }; + ListView.prototype._eventStoreToSegs = function (eventStore, eventUiBases, dayRanges) { + return this.eventRangesToSegs(sliceEventStore(eventStore, eventUiBases, this.props.dateProfile.activeRange, this.context.options.nextDayThreshold).fg, dayRanges); + }; + ListView.prototype.eventRangesToSegs = function (eventRanges, dayRanges) { + var segs = []; + for (var _i = 0, eventRanges_1 = eventRanges; _i < eventRanges_1.length; _i++) { + var eventRange = eventRanges_1[_i]; + segs.push.apply(segs, this.eventRangeToSegs(eventRange, dayRanges)); + } + return segs; + }; + ListView.prototype.eventRangeToSegs = function (eventRange, dayRanges) { + var dateEnv = this.context.dateEnv; + var nextDayThreshold = this.context.options.nextDayThreshold; + var range = eventRange.range; + var allDay = eventRange.def.allDay; + var dayIndex; + var segRange; + var seg; + var segs = []; + for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex += 1) { + segRange = intersectRanges(range, dayRanges[dayIndex]); + if (segRange) { + seg = { + component: this, + eventRange: eventRange, + start: segRange.start, + end: segRange.end, + isStart: eventRange.isStart && segRange.start.valueOf() === range.start.valueOf(), + isEnd: eventRange.isEnd && segRange.end.valueOf() === range.end.valueOf(), + dayIndex: dayIndex, + }; + segs.push(seg); + // detect when range won't go fully into the next day, + // and mutate the latest seg to the be the end. + if (!seg.isEnd && !allDay && + dayIndex + 1 < dayRanges.length && + range.end < + dateEnv.add(dayRanges[dayIndex + 1].start, nextDayThreshold)) { + seg.end = range.end; + seg.isEnd = true; + break; + } + } + } + return segs; + }; + return ListView; + }(DateComponent)); + function renderNoEventsInner(hookProps) { + return hookProps.text; + } + function computeDateVars(dateProfile) { + var dayStart = startOfDay(dateProfile.renderRange.start); + var viewEnd = dateProfile.renderRange.end; + var dayDates = []; + var dayRanges = []; + while (dayStart < viewEnd) { + dayDates.push(dayStart); + dayRanges.push({ + start: dayStart, + end: addDays(dayStart, 1), + }); + dayStart = addDays(dayStart, 1); + } + return { dayDates: dayDates, dayRanges: dayRanges }; + } + // Returns a sparse array of arrays, segs grouped by their dayIndex + function groupSegsByDay(segs) { + var segsByDay = []; // sparse array + var i; + var seg; + for (i = 0; i < segs.length; i += 1) { + seg = segs[i]; + (segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = [])) + .push(seg); + } + return segsByDay; + } + + var OPTION_REFINERS$1 = { + listDayFormat: createFalsableFormatter, + listDaySideFormat: createFalsableFormatter, + noEventsClassNames: identity, + noEventsContent: identity, + noEventsDidMount: identity, + noEventsWillUnmount: identity, + // noEventsText is defined in base options + }; + function createFalsableFormatter(input) { + return input === false ? null : createFormatter(input); + } + + var listPlugin = createPlugin({ + optionRefiners: OPTION_REFINERS$1, + views: { + list: { + component: ListView, + buttonTextKey: 'list', + listDayFormat: { month: 'long', day: 'numeric', year: 'numeric' }, // like "January 1, 2016" + }, + listDay: { + type: 'list', + duration: { days: 1 }, + listDayFormat: { weekday: 'long' }, // day-of-week is all we need. full date is probably in headerToolbar + }, + listWeek: { + type: 'list', + duration: { weeks: 1 }, + listDayFormat: { weekday: 'long' }, + listDaySideFormat: { month: 'long', day: 'numeric', year: 'numeric' }, + }, + listMonth: { + type: 'list', + duration: { month: 1 }, + listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have + }, + listYear: { + type: 'list', + duration: { year: 1 }, + listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have + }, + }, + }); + + var BootstrapTheme = /** @class */ (function (_super) { + __extends(BootstrapTheme, _super); + function BootstrapTheme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return BootstrapTheme; + }(Theme)); + BootstrapTheme.prototype.classes = { + root: 'fc-theme-bootstrap', + table: 'table-bordered', + tableCellShaded: 'table-active', + buttonGroup: 'btn-group', + button: 'btn btn-primary', + buttonActive: 'active', + popover: 'popover', + popoverHeader: 'popover-header', + popoverContent: 'popover-body', + }; + BootstrapTheme.prototype.baseIconClass = 'fa'; + BootstrapTheme.prototype.iconClasses = { + close: 'fa-times', + prev: 'fa-chevron-left', + next: 'fa-chevron-right', + prevYear: 'fa-angle-double-left', + nextYear: 'fa-angle-double-right', + }; + BootstrapTheme.prototype.rtlIconClasses = { + prev: 'fa-chevron-right', + next: 'fa-chevron-left', + prevYear: 'fa-angle-double-right', + nextYear: 'fa-angle-double-left', + }; + BootstrapTheme.prototype.iconOverrideOption = 'bootstrapFontAwesome'; // TODO: make TS-friendly. move the option-processing into this plugin + BootstrapTheme.prototype.iconOverrideCustomButtonOption = 'bootstrapFontAwesome'; + BootstrapTheme.prototype.iconOverridePrefix = 'fa-'; + var plugin = createPlugin({ + themeClasses: { + bootstrap: BootstrapTheme, + }, + }); + + // rename this file to options.ts like other packages? + var OPTION_REFINERS = { + googleCalendarApiKey: String, + }; + + var EVENT_SOURCE_REFINERS = { + googleCalendarApiKey: String, + googleCalendarId: String, + googleCalendarApiBase: String, + extraParams: identity, + }; + + // TODO: expose somehow + var API_BASE = 'https://www.googleapis.com/calendar/v3/calendars'; + var eventSourceDef = { + parseMeta: function (refined) { + var googleCalendarId = refined.googleCalendarId; + if (!googleCalendarId && refined.url) { + googleCalendarId = parseGoogleCalendarId(refined.url); + } + if (googleCalendarId) { + return { + googleCalendarId: googleCalendarId, + googleCalendarApiKey: refined.googleCalendarApiKey, + googleCalendarApiBase: refined.googleCalendarApiBase, + extraParams: refined.extraParams, + }; + } + return null; + }, + fetch: function (arg, onSuccess, onFailure) { + var _a = arg.context, dateEnv = _a.dateEnv, options = _a.options; + var meta = arg.eventSource.meta; + var apiKey = meta.googleCalendarApiKey || options.googleCalendarApiKey; + if (!apiKey) { + onFailure({ + message: 'Specify a googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/', + }); + } + else { + var url = buildUrl(meta); + // TODO: make DRY with json-feed-event-source + var extraParams = meta.extraParams; + var extraParamsObj = typeof extraParams === 'function' ? extraParams() : extraParams; + var requestParams_1 = buildRequestParams(arg.range, apiKey, extraParamsObj, dateEnv); + requestJson('GET', url, requestParams_1, function (body, xhr) { + if (body.error) { + onFailure({ + message: 'Google Calendar API: ' + body.error.message, + errors: body.error.errors, + xhr: xhr, + }); + } + else { + onSuccess({ + rawEvents: gcalItemsToRawEventDefs(body.items, requestParams_1.timeZone), + xhr: xhr, + }); + } + }, function (message, xhr) { + onFailure({ message: message, xhr: xhr }); + }); + } + }, + }; + function parseGoogleCalendarId(url) { + var match; + // detect if the ID was specified as a single string. + // will match calendars like "asdf1234@calendar.google.com" in addition to person email calendars. + if (/^[^/]+@([^/.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) { + return url; + } + if ((match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^/]*)/.exec(url)) || + (match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^/]*)/.exec(url))) { + return decodeURIComponent(match[1]); + } + return null; + } + function buildUrl(meta) { + var apiBase = meta.googleCalendarApiBase; + if (!apiBase) { + apiBase = API_BASE; + } + return apiBase + '/' + encodeURIComponent(meta.googleCalendarId) + '/events'; + } + function buildRequestParams(range, apiKey, extraParams, dateEnv) { + var params; + var startStr; + var endStr; + if (dateEnv.canComputeOffset) { + // strings will naturally have offsets, which GCal needs + startStr = dateEnv.formatIso(range.start); + endStr = dateEnv.formatIso(range.end); + } + else { + // when timezone isn't known, we don't know what the UTC offset should be, so ask for +/- 1 day + // from the UTC day-start to guarantee we're getting all the events + // (start/end will be UTC-coerced dates, so toISOString is okay) + startStr = addDays(range.start, -1).toISOString(); + endStr = addDays(range.end, 1).toISOString(); + } + params = __assign(__assign({}, (extraParams || {})), { key: apiKey, timeMin: startStr, timeMax: endStr, singleEvents: true, maxResults: 9999 }); + if (dateEnv.timeZone !== 'local') { + params.timeZone = dateEnv.timeZone; + } + return params; + } + function gcalItemsToRawEventDefs(items, gcalTimezone) { + return items.map(function (item) { return gcalItemToRawEventDef(item, gcalTimezone); }); + } + function gcalItemToRawEventDef(item, gcalTimezone) { + var url = item.htmlLink || null; + // make the URLs for each event show times in the correct timezone + if (url && gcalTimezone) { + url = injectQsComponent(url, 'ctz=' + gcalTimezone); + } + return { + id: item.id, + title: item.summary, + start: item.start.dateTime || item.start.date, + end: item.end.dateTime || item.end.date, + url: url, + location: item.location, + description: item.description, + attachments: item.attachments || [], + extendedProps: (item.extendedProperties || {}).shared || {}, + }; + } + // Injects a string like "arg=value" into the querystring of a URL + // TODO: move to a general util file? + function injectQsComponent(url, component) { + // inject it after the querystring but before the fragment + return url.replace(/(\?.*?)?(#|$)/, function (whole, qs, hash) { return (qs ? qs + '&' : '?') + component + hash; }); + } + var googleCalendarPlugin = createPlugin({ + eventSourceDefs: [eventSourceDef], + optionRefiners: OPTION_REFINERS, + eventSourceRefiners: EVENT_SOURCE_REFINERS, + }); + + globalPlugins.push(interactionPlugin, dayGridPlugin, timeGridPlugin, listPlugin, plugin, googleCalendarPlugin); + + exports.BASE_OPTION_DEFAULTS = BASE_OPTION_DEFAULTS; + exports.BASE_OPTION_REFINERS = BASE_OPTION_REFINERS; + exports.BaseComponent = BaseComponent; + exports.BgEvent = BgEvent; + exports.BootstrapTheme = BootstrapTheme; + exports.Calendar = Calendar; + exports.CalendarApi = CalendarApi; + exports.CalendarContent = CalendarContent; + exports.CalendarDataManager = CalendarDataManager; + exports.CalendarDataProvider = CalendarDataProvider; + exports.CalendarRoot = CalendarRoot; + exports.Component = Component; + exports.ContentHook = ContentHook; + exports.CustomContentRenderContext = CustomContentRenderContext; + exports.DateComponent = DateComponent; + exports.DateEnv = DateEnv; + exports.DateProfileGenerator = DateProfileGenerator; + exports.DayCellContent = DayCellContent; + exports.DayCellRoot = DayCellRoot; + exports.DayGridView = DayTableView; + exports.DayHeader = DayHeader; + exports.DaySeriesModel = DaySeriesModel; + exports.DayTable = DayTable; + exports.DayTableModel = DayTableModel; + exports.DayTableSlicer = DayTableSlicer; + exports.DayTimeCols = DayTimeCols; + exports.DayTimeColsSlicer = DayTimeColsSlicer; + exports.DayTimeColsView = DayTimeColsView; + exports.DelayedRunner = DelayedRunner; + exports.Draggable = ExternalDraggable; + exports.ElementDragging = ElementDragging; + exports.ElementScrollController = ElementScrollController; + exports.Emitter = Emitter; + exports.EventApi = EventApi; + exports.EventRoot = EventRoot; + exports.EventSourceApi = EventSourceApi; + exports.FeaturefulElementDragging = FeaturefulElementDragging; + exports.Fragment = Fragment; + exports.Interaction = Interaction; + exports.ListView = ListView; + exports.MoreLinkRoot = MoreLinkRoot; + exports.MountHook = MountHook; + exports.NamedTimeZoneImpl = NamedTimeZoneImpl; + exports.NowIndicatorRoot = NowIndicatorRoot; + exports.NowTimer = NowTimer; + exports.PointerDragging = PointerDragging; + exports.PositionCache = PositionCache; + exports.RefMap = RefMap; + exports.RenderHook = RenderHook; + exports.ScrollController = ScrollController; + exports.ScrollResponder = ScrollResponder; + exports.Scroller = Scroller; + exports.SegHierarchy = SegHierarchy; + exports.SimpleScrollGrid = SimpleScrollGrid; + exports.Slicer = Slicer; + exports.Splitter = Splitter; + exports.StandardEvent = StandardEvent; + exports.Table = Table; + exports.TableDateCell = TableDateCell; + exports.TableDowCell = TableDowCell; + exports.TableView = TableView; + exports.Theme = Theme; + exports.ThirdPartyDraggable = ThirdPartyDraggable; + exports.TimeCols = TimeCols; + exports.TimeColsSlatsCoords = TimeColsSlatsCoords; + exports.TimeColsView = TimeColsView; + exports.ViewApi = ViewApi; + exports.ViewContextType = ViewContextType; + exports.ViewRoot = ViewRoot; + exports.WeekNumberRoot = WeekNumberRoot; + exports.WindowScrollController = WindowScrollController; + exports.addDays = addDays; + exports.addDurations = addDurations; + exports.addMs = addMs; + exports.addWeeks = addWeeks; + exports.allowContextMenu = allowContextMenu; + exports.allowSelection = allowSelection; + exports.applyMutationToEventStore = applyMutationToEventStore; + exports.applyStyle = applyStyle; + exports.applyStyleProp = applyStyleProp; + exports.asCleanDays = asCleanDays; + exports.asRoughMinutes = asRoughMinutes; + exports.asRoughMs = asRoughMs; + exports.asRoughSeconds = asRoughSeconds; + exports.binarySearch = binarySearch; + exports.buildClassNameNormalizer = buildClassNameNormalizer; + exports.buildDayRanges = buildDayRanges; + exports.buildDayTableModel = buildDayTableModel; + exports.buildEntryKey = buildEntryKey; + exports.buildEventApis = buildEventApis; + exports.buildEventRangeKey = buildEventRangeKey; + exports.buildHashFromArray = buildHashFromArray; + exports.buildIsoString = buildIsoString; + exports.buildNavLinkData = buildNavLinkData; + exports.buildSegCompareObj = buildSegCompareObj; + exports.buildSegTimeText = buildSegTimeText; + exports.buildSlatMetas = buildSlatMetas; + exports.buildTimeColsModel = buildTimeColsModel; + exports.collectFromHash = collectFromHash; + exports.combineEventUis = combineEventUis; + exports.compareByFieldSpec = compareByFieldSpec; + exports.compareByFieldSpecs = compareByFieldSpecs; + exports.compareNumbers = compareNumbers; + exports.compareObjs = compareObjs; + exports.computeEarliestSegStart = computeEarliestSegStart; + exports.computeEdges = computeEdges; + exports.computeFallbackHeaderFormat = computeFallbackHeaderFormat; + exports.computeHeightAndMargins = computeHeightAndMargins; + exports.computeInnerRect = computeInnerRect; + exports.computeRect = computeRect; + exports.computeSegDraggable = computeSegDraggable; + exports.computeSegEndResizable = computeSegEndResizable; + exports.computeSegStartResizable = computeSegStartResizable; + exports.computeShrinkWidth = computeShrinkWidth; + exports.computeSmallestCellWidth = computeSmallestCellWidth; + exports.computeVisibleDayRange = computeVisibleDayRange; + exports.config = config; + exports.constrainPoint = constrainPoint; + exports.createContext = createContext; + exports.createDuration = createDuration; + exports.createElement = createElement; + exports.createEmptyEventStore = createEmptyEventStore; + exports.createEventInstance = createEventInstance; + exports.createEventUi = createEventUi; + exports.createFormatter = createFormatter; + exports.createPlugin = createPlugin; + exports.createPortal = createPortal; + exports.createRef = createRef; + exports.diffDates = diffDates; + exports.diffDayAndTime = diffDayAndTime; + exports.diffDays = diffDays; + exports.diffPoints = diffPoints; + exports.diffWeeks = diffWeeks; + exports.diffWholeDays = diffWholeDays; + exports.diffWholeWeeks = diffWholeWeeks; + exports.disableCursor = disableCursor; + exports.elementClosest = elementClosest; + exports.elementMatches = elementMatches; + exports.enableCursor = enableCursor; + exports.eventTupleToStore = eventTupleToStore; + exports.filterEventStoreDefs = filterEventStoreDefs; + exports.filterHash = filterHash; + exports.findDirectChildren = findDirectChildren; + exports.findElements = findElements; + exports.flexibleCompare = flexibleCompare; + exports.flushToDom = flushToDom; + exports.formatDate = formatDate; + exports.formatDayString = formatDayString; + exports.formatIsoTimeString = formatIsoTimeString; + exports.formatRange = formatRange; + exports.getAllowYScrolling = getAllowYScrolling; + exports.getCanVGrowWithinCell = getCanVGrowWithinCell; + exports.getClippingParents = getClippingParents; + exports.getDateMeta = getDateMeta; + exports.getDayClassNames = getDayClassNames; + exports.getDefaultEventEnd = getDefaultEventEnd; + exports.getElRoot = getElRoot; + exports.getElSeg = getElSeg; + exports.getEntrySpanEnd = getEntrySpanEnd; + exports.getEventClassNames = getEventClassNames; + exports.getEventTargetViaRoot = getEventTargetViaRoot; + exports.getIsRtlScrollbarOnLeft = getIsRtlScrollbarOnLeft; + exports.getRectCenter = getRectCenter; + exports.getRelevantEvents = getRelevantEvents; + exports.getScrollGridClassNames = getScrollGridClassNames; + exports.getScrollbarWidths = getScrollbarWidths; + exports.getSectionClassNames = getSectionClassNames; + exports.getSectionHasLiquidHeight = getSectionHasLiquidHeight; + exports.getSegMeta = getSegMeta; + exports.getSlotClassNames = getSlotClassNames; + exports.getStickyFooterScrollbar = getStickyFooterScrollbar; + exports.getStickyHeaderDates = getStickyHeaderDates; + exports.getUnequalProps = getUnequalProps; + exports.globalLocales = globalLocales; + exports.globalPlugins = globalPlugins; + exports.greatestDurationDenominator = greatestDurationDenominator; + exports.groupIntersectingEntries = groupIntersectingEntries; + exports.guid = guid; + exports.hasBgRendering = hasBgRendering; + exports.hasShrinkWidth = hasShrinkWidth; + exports.identity = identity; + exports.interactionSettingsStore = interactionSettingsStore; + exports.interactionSettingsToStore = interactionSettingsToStore; + exports.intersectRanges = intersectRanges; + exports.intersectRects = intersectRects; + exports.intersectSpans = intersectSpans; + exports.isArraysEqual = isArraysEqual; + exports.isColPropsEqual = isColPropsEqual; + exports.isDateSelectionValid = isDateSelectionValid; + exports.isDateSpansEqual = isDateSpansEqual; + exports.isInt = isInt; + exports.isInteractionValid = isInteractionValid; + exports.isMultiDayRange = isMultiDayRange; + exports.isPropsEqual = isPropsEqual; + exports.isPropsValid = isPropsValid; + exports.isValidDate = isValidDate; + exports.joinSpans = joinSpans; + exports.listenBySelector = listenBySelector; + exports.mapHash = mapHash; + exports.memoize = memoize; + exports.memoizeArraylike = memoizeArraylike; + exports.memoizeHashlike = memoizeHashlike; + exports.memoizeObjArg = memoizeObjArg; + exports.mergeEventStores = mergeEventStores; + exports.multiplyDuration = multiplyDuration; + exports.padStart = padStart; + exports.parseBusinessHours = parseBusinessHours; + exports.parseClassNames = parseClassNames; + exports.parseDragMeta = parseDragMeta; + exports.parseEventDef = parseEventDef; + exports.parseFieldSpecs = parseFieldSpecs; + exports.parseMarker = parse; + exports.pointInsideRect = pointInsideRect; + exports.preventContextMenu = preventContextMenu; + exports.preventDefault = preventDefault; + exports.preventSelection = preventSelection; + exports.rangeContainsMarker = rangeContainsMarker; + exports.rangeContainsRange = rangeContainsRange; + exports.rangesEqual = rangesEqual; + exports.rangesIntersect = rangesIntersect; + exports.refineEventDef = refineEventDef; + exports.refineProps = refineProps; + exports.removeElement = removeElement; + exports.removeExact = removeExact; + exports.render = render; + exports.renderChunkContent = renderChunkContent; + exports.renderFill = renderFill; + exports.renderMicroColGroup = renderMicroColGroup; + exports.renderScrollShim = renderScrollShim; + exports.requestJson = requestJson; + exports.sanitizeShrinkWidth = sanitizeShrinkWidth; + exports.setElSeg = setElSeg; + exports.setRef = setRef; + exports.sliceEventStore = sliceEventStore; + exports.sliceEvents = sliceEvents; + exports.sortEventSegs = sortEventSegs; + exports.startOfDay = startOfDay; + exports.translateRect = translateRect; + exports.triggerDateSelect = triggerDateSelect; + exports.unmountComponentAtNode = unmountComponentAtNode; + exports.unpromisify = unpromisify; + exports.version = version; + exports.whenTransitionDone = whenTransitionDone; + exports.wholeDivideDurations = wholeDivideDurations; + + Object.defineProperty(exports, '__esModule', { value: true }); + + return exports; + +}({})); diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html new file mode 100644 index 000000000..3b626c5e9 --- /dev/null +++ b/apps/schoolCalender/interface.html @@ -0,0 +1,60 @@ + + + + + + + + + + + +
+ + diff --git a/apps/schoolCalender/schoolCalender.js b/apps/schoolCalender/schoolCalender.js new file mode 100644 index 000000000..be4c45d45 --- /dev/null +++ b/apps/schoolCalender/schoolCalender.js @@ -0,0 +1,229 @@ +require("FontTeletext5x9Mode7").add(Graphics); +Bangle.setLCDMode(); + +function getBackgroundImage() { + return require("heatshrink").decompress(atob("gMwyEgBAsAgQBCgcAggBCgsAgwBCg8AhABChMAhQBChcAhgBChsAhwBCh8AiEAiIBCiUAiYBCikAioBCi0Ai4BCjEAjIBCjUAjYBCjkAjoBCj0Aj4BBA")); +} + +Graphics.prototype.setFontAudiowide = function() { + // Actual height 33 (36 - 4) + var widths = atob("BxYfDBkYGhkZFRkZCA=="); + var font = atob("AAAAAAAAA8AAAAHgAAAB8AAAAHgAAAA4AAAAAAAAAAEAAAABgAAAA8AAAAPgAAAH8AAAB/gAAA/4AAAf+AAAH/AAAD/wAAA/4AAAf8AAAP/AAAD/gAAB/4AAAf8AAAD+AAAAfgAAADwAAAAcAAAAAAAAAAAAAAAAAAAAAAP/AAAH//AAB//8AAf//wAH///AA///4APwD/gB8A/8APgP/gB8D98AfAfvgD4H58AfB/PgD4Px8AfD8PgD4/h8AfH4PgD5+B8AP/wPgB/8B8AP/APgB/4D8AH8B/AA///4AD//+AAP//gAA//4AAB/8AAAAAAAAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAAAAD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAA/8AAAf/gD4H/8AfA//gD4P/8AfB+PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//B8AP/4PgA/+B8AD/gPgABgA8AAAAAAAAAAAAPA4HgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP///gB///8AH///AA///wAB//8AAAAAAAAAAAAB/8AAAf/4AAD//gAAf/8AAD//gAAf/8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAP///gD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8B8Af/wPgD//B8Af/4PgD//h8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AfA//AAAD/4AAAP8AAAAAAAAAAAAAH//AAB//8AAf//wAH///AB///8AP58/gB8Ph8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AAA//AAAD/4AAAP8AAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAABgD4AA8AfAAPgD4AH8AfAD/gD4A/8AfAf/AD4P/gAfH/wAD5/8AAf/+AAD//AAAf/gAAD/4AAAf8AAAB+AAAAPAAAAAAAAAAAAAAAAAB/gAAAf+AAP//4AH///gA///8AP/+PgB//h8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//h8AP///gA///8AD///AADz/4AAAP8AAAAMAAAAAAAAAAAAAB/gAAA/+AAAH/4AAB//B8AP/8PgB8Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8APh8PgB8Ph8APx8fgB///8AH///AAf//wAD//8AAH//AAAAAAAAAAAAAAAAAAAAAAAAAOAHgAD4A8AAfAPgAD4A8AAOAHAAAAAAA=="); + var scale = 1; // size multiplier for this font + g.setFontCustom(font, 46, widths, 33+(scale<<8)+(1<<16)); +}; + +function logDebug(message){ + //console.log(message); +} + +var NEXTCLASS = 4; +var CURRRENTCLASS = 3; +var NEXTNEXTCLASS = 5; +var BEHINDCLASS = 2; +var BEHINDBEHINDCLASS = 1; +var NEXTNEXTNEXTCLASS = 6; +var stage = 3; + +function drawInfo(){ + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var currentMinuteUpdated; + var currentHourUpdated; + if (currentMinute<10){ + currentMinuteUpdated = "0"+currentMinute; + }else{ + currentMinuteUpdated = currentMinute; + }if(currentHour >= 13){ + currentHourUpdated = currentHour-12; + }else{ + currentHourUpdated = currentHour; + } + for(var i = 0;i<=240;i++){ + g.drawImage(getBackgroundImage(),i,120,{scale:5,rotate:0}); + } + g.setColor(255,255,255); + g.setFont("Audiowide"); + g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 145, 16); + g.setFont("Teletext5x9Mode7", 2); + foundClass = processDay(); + if (foundClass.startingTimeMinute<10){ + classMinuteUpdated = "0"+foundClass.startingTimeMinute; + }else{ + classMinuteUpdated = foundClass.startingTimeMinute; + } + if (foundClass.endingTimeMinute<10){ + classEndingMinuteUpdated = "0"+foundClass.endingTimeMinute; + }else{ + classEndingMinuteUpdated = foundClass.endingTimeMinute; + }if(foundClass.startingTimeHour >= 13){ + classHourUpdated = foundClass.startingTimeHour-12; + }else{ + classHourUpdated = foundClass.startingTimeHour; + }if(foundClass.endingTimeHour >= 13){ + classEndingHourUpdated = foundClass.endingTimeHour-12; + }else{ + classEndingHourUpdated = foundClass.endingTimeHour; + } + switch (foundClass.dayOfWeek) { + case 0: + updatedDay = "Sun"; + break; + case 1: + updatedDay = "Mon"; + break; + case 2: + updatedDay = "Tue"; + break; + case 3: + updatedDay = "Wed"; + break; + case 4: + updatedDay = "Thur"; + break; + case 5: + updatedDay = "Fri"; + break; + case 6: + updatedDay = "Sat"; +} + if (foundClass != null) { + g.drawString(classHourUpdated+":"+classMinuteUpdated+" - "+classEndingHourUpdated+":"+classEndingMinuteUpdated+" "+updatedDay, 25, 50); + g.drawString(foundClass.className, 25, 80); + g.drawString(foundClass.teacher, 25, 110); + g.drawString(foundClass.roomNumber, 25, 140); + } +} +setInterval(drawInfo, 60000); + +function processDay(){ + let schedule = [ + //Sunday + + //Monday: + {className: "Biblical Theology", dayOfWeek:1, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "English", dayOfWeek:1, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"English 7B 4B Dr. Wong Block 4B M206", teacher:"Dr. Wong"}, + {className: "Break", dayOfWeek:1, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", teacher:""}, + {className: "MS Robotics", dayOfWeek:1, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "MS Physical Education Boys", dayOfWeek:1, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Physical Education Boys S1B Mr. Mendezona MS MF Elective Block B Gym", roomNumber:"GYM", teacher:"Mr. Mendezona"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:1, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:"Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:1, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:1, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Latin", dayOfWeek:1, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Latin 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + {className: "Algebra 1", dayOfWeek:1, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + + //Tuesday: + {className: "Logic", dayOfWeek:2, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 0, description:"Logic 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + {className: "Algebra 1", dayOfWeek:2, startingTimeHour: 9, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {className: "Chapel", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 25, description:"Chapel MF MS", roomNumber:"Advisory", teacher:""}, + {className: "Break", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 25, endingTimeHour:10, endingTimeMinute: 35, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "Advisory Besaw", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 35, endingTimeHour:11, endingTimeMinute: 0, description:"Advisory Besaw Mr. Besaw Advisory MF MS M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "MS Robotics", dayOfWeek:2, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:2, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 5, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Medieval Western Civilization", dayOfWeek:2, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1BM205", roomNumber:"205", teacher:"Mr. Khule"}, + {className: "Introductory Biology and Epidemiology", dayOfWeek:2, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + + //Wensday: + {className: "English", dayOfWeek:3, startingTimeHour: 9, startingTimeMinute: 0, endingTimeHour:9, endingTimeMinute: 55, description:"English 7B 4B Dr. Wong Block 4B M206", roomNumber:"206", teacher:"Dr. Wong"}, + {className: "Biblical Theology", dayOfWeek:3, startingTimeHour: 9, startingTimeMinute: 55, endingTimeHour:10, endingTimeMinute: 50, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "Break", dayOfWeek:3, startingTimeHour: 10, startingTimeMinute: 50, endingTimeHour:11, endingTimeMinute: 0, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "MS Physical Education Boys", dayOfWeek:3, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Physical Education Boys S1B Mr. Mendezona MS MF Elective Block B Gym", roomNumber:"GYM", teacher:"Mr. Mendezona"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:3, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:3, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Introductory Biology and Epidemiology", dayOfWeek:3, startingTimeHour: 13, startingTimeMinute: 0, endingTimeHour:14, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + {className: "Medieval Western Civilization", dayOfWeek:3, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1B M205", roomNumber:"205", teacher:"Mr. Khule"}, + + //Thursday: + {className: "Algebra 1", dayOfWeek:4, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {className: "Latin", dayOfWeek:4, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"Latin 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + {className: "Break", dayOfWeek:4, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "MS Robotics", dayOfWeek:4, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "Advisory Besaw", dayOfWeek:4, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Advisory Besaw Mr. Besaw Advisory MF MS M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "Lunch", dayOfWeek:4, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:4, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Biblical Theology", dayOfWeek:4, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "English", dayOfWeek:4, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"English 7B 4B Dr. Wong Block 4B M206", roomNumber:"206", teacher:"Dr. Wong"}, + + //Friday: + {className: "Medieval Western Civilization", dayOfWeek:5, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1B M205", roomNumber:"205", teacher:"Mr. Khule"}, + {className: "Introductory Biology and Epidemiology", dayOfWeek:5, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + {className: "Break", dayOfWeek:5, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "MS Robotics", dayOfWeek:5, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:5, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:5, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:5, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Algebra 1", dayOfWeek:5, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {className: "Logic", dayOfWeek:5, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Logic 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + + //Sataturday: + ]; + + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var minofDay = (currentHour*60)+currentMinute; + var i; + var currentPositon; + for(i = 0;i= (schedule[i].startingTimeHour*60+schedule[i].startingTimeMinute) && minofDay < (schedule[i].endingTimeHour*60+schedule[i].endingTimeMinute) ){ + console.log("Match:" + schedule[i].className); + console.log("stage:" + stage); + if(stage == 3){ + return schedule[i]; + }else if(stage == 4 && ++currentPositon <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 5 && (currentPositon+=2) <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 6 && (currentPositon+=3) <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 2 && (currentPositon-=1) <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 1 && (currentPositon-=2) <= schedule.length){ + return schedule[currentPositon]; + } + } + } + } + return null; +} + + +setWatch(() => { + if(stage<=1){ + }else{ + stage -= 1; + drawInfo(); + } +}, BTN1, {repeat:true}); + +setWatch(() => { +}, BTN2, {repeat:true}); + +setWatch(() => { + if(stage>=6){ + }else{ + stage += 1; + drawInfo(); + } +}, BTN3, {repeat:true}); + +setWatch(() => { + +}, BTN4, {repeat:true}); + +setWatch(() => { + +}, BTN5, {repeat:true}); + +drawInfo(); From cfde9e14a87466a8dc7cb92934c3af373f6a43d0 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 16 Sep 2021 18:41:40 -0700 Subject: [PATCH 0029/1062] Update interface.html --- apps/schoolCalender/interface.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html index 3b626c5e9..1761da640 100644 --- a/apps/schoolCalender/interface.html +++ b/apps/schoolCalender/interface.html @@ -11,7 +11,7 @@ headerToolbar: { left: 'prev,next today', center: 'title', - right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek' + right: 'timeGridWeek,listWeek' }, navLinks: true, // can click day/week names to navigate views editable: true, From bc71f317311509e8f3fd5054e6c3efaa05bc0416 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 16 Sep 2021 18:47:13 -0700 Subject: [PATCH 0030/1062] Update interface.html --- apps/schoolCalender/interface.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html index 1761da640..26f97c2f7 100644 --- a/apps/schoolCalender/interface.html +++ b/apps/schoolCalender/interface.html @@ -9,7 +9,7 @@ var calendar = new FullCalendar.Calendar(calendarEl, { initialView: 'timeGridWeek', headerToolbar: { - left: 'prev,next today', + left: '', center: 'title', right: 'timeGridWeek,listWeek' }, From 751fbdda43ea542db4887bed7b4d4e2ad7628a23 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 16 Sep 2021 19:00:47 -0700 Subject: [PATCH 0031/1062] Update interface.html --- apps/schoolCalender/interface.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html index 26f97c2f7..201aa9a91 100644 --- a/apps/schoolCalender/interface.html +++ b/apps/schoolCalender/interface.html @@ -52,6 +52,10 @@ width: 100%; } + From 34baf995e7f5288266b5da08bcac6d76202a92fc Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 16 Sep 2021 19:04:33 -0700 Subject: [PATCH 0032/1062] Update interface.html --- apps/schoolCalender/interface.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html index 201aa9a91..bbcb3ffb2 100644 --- a/apps/schoolCalender/interface.html +++ b/apps/schoolCalender/interface.html @@ -52,9 +52,9 @@ width: 100%; } - +

+

+

This is currently Christmas-themed, but more themes will be added in the future.

+ + + - - -
From 5925628cf6098cb5447d608b90375dc66d08a8f5 Mon Sep 17 00:00:00 2001 From: Ben Whittaker Date: Sun, 19 Sep 2021 14:28:56 -0400 Subject: [PATCH 0057/1062] Weather: refactoring and reduced ram usage These changes reduce the memory usage of the ram widget by up to 70 JSVars, depending on the weather. --- apps.json | 2 +- apps/weather/ChangeLog | 3 +- apps/weather/app.js | 23 +++++++------- apps/weather/lib.js | 72 +++++++++++++++++------------------------- apps/weather/widget.js | 70 +++++++++++++++++++--------------------- 5 files changed, 76 insertions(+), 94 deletions(-) diff --git a/apps.json b/apps.json index 1dafc8f40..0d0492ce7 100644 --- a/apps.json +++ b/apps.json @@ -571,7 +571,7 @@ { "id": "weather", "name": "Weather", "icon": "icon.png", - "version":"0.07", + "version":"0.08", "description": "Show Gadgetbridge weather report", "readme": "readme.md", "tags": "widget,outdoors", diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog index 8e99f8faf..fd5d4d146 100644 --- a/apps/weather/ChangeLog +++ b/apps/weather/ChangeLog @@ -3,4 +3,5 @@ 0.04: Adjust "weather unknown" message according to Bluetooth connection. 0.05: Add wind direction. 0.06: Use setUI for launcher. -0.07: Add theme support and unknown icon. \ No newline at end of file +0.07: Add theme support and unknown icon. +0.08: Refactor and reduce widget ram usage. \ No newline at end of file diff --git a/apps/weather/app.js b/apps/weather/app.js index f5200c8ae..9d64583e9 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -1,5 +1,6 @@ (() => { const weather = require('weather'); + let current = weather.get(); function formatDuration(millis) { let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); @@ -10,16 +11,15 @@ } function draw() { - let w = weather.current; g.reset(); g.clearRect(0, 24, 239, 239); - weather.drawIcon(w.txt, 65, 90, 55); + weather.drawIcon(current.txt, 65, 90, 55); const locale = require("locale"); g.reset(); - const temp = locale.temp(w.temp-273.15).match(/^(\D*\d*)(.*)$/); + const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/); let width = g.setFont("Vector", 40).stringWidth(temp[1]); width += g.setFont("Vector", 20).stringWidth(temp[2]); g.setFont("Vector", 40).setFontAlign(-1, -1, 0); @@ -31,19 +31,19 @@ g.setFontAlign(-1, 0, 0); g.drawString("Humidity", 135, 130); g.setFontAlign(1, 0, 0); - g.drawString(w.hum+"%", 225, 130); - if ('wind' in w) { + g.drawString(current.hum+"%", 225, 130); + if ('wind' in current) { g.setFontAlign(-1, 0, 0); g.drawString("Wind", 135, 142); g.setFontAlign(1, 0, 0); - g.drawString(locale.speed(w.wind)+' '+w.wrose.toUpperCase(), 225, 142); + g.drawString(locale.speed(current.wind)+' '+current.wrose.toUpperCase(), 225, 142); } g.setFont("6x8", 2).setFontAlign(0, 0, 0); - g.drawString(w.loc, 120, 170); + g.drawString(current.loc, 120, 170); g.setFont("6x8", 1).setFontAlign(0, 0, 0); - g.drawString(w.txt.charAt(0).toUpperCase()+w.txt.slice(1), 120, 190); + g.drawString(current.txt.charAt(0).toUpperCase()+current.txt.slice(1), 120, 190); drawUpdateTime(); @@ -51,8 +51,8 @@ } function drawUpdateTime() { - if (!weather.current || !weather.current.time) return; - let text = `Last update received ${formatDuration(Date.now() - weather.current.time)} ago`; + if (!current || !current.time) return; + let text = `Last update received ${formatDuration(Date.now() - current.time)} ago`; g.reset(); g.clearRect(0, 202, 239, 210); g.setFont("6x8", 1).setFontAlign(0, 0, 0); @@ -60,8 +60,9 @@ } function update() { + current = weather.get(); NRF.removeListener("connect", update); - if (weather.current) { + if (current) { draw(); } else if (NRF.getSecurityStatus().connected) { E.showMessage("Weather unknown\n\nIs Gadgetbridge\nweather reporting\nset up on your\nphone?"); diff --git a/apps/weather/lib.js b/apps/weather/lib.js index 6a57e1f00..f08df4a4a 100644 --- a/apps/weather/lib.js +++ b/apps/weather/lib.js @@ -1,6 +1,6 @@ const storage = require('Storage'); -let expiryTimeout = undefined; +let expiryTimeout; function scheduleExpiry(json) { if (expiryTimeout) { clearTimeout(expiryTimeout); @@ -9,53 +9,35 @@ function scheduleExpiry(json) { let expiry = "expiry" in json ? json.expiry : 2*3600000; if (json.weather && json.weather.time && expiry) { let t = json.weather.time + expiry - Date.now(); - expiryTimeout = setTimeout(() => { - expiryTimeout = undefined; - - let json = storage.readJSON('weather.json')||{}; - delete json.weather; - storage.write('weather.json', json); - - exports.current = undefined; - exports.emit("update"); - }, t); + expiryTimeout = setTimeout(update, t); } } -/** - * Convert numeric direction into human-readable label - * - * @param {number} deg - Direction in degrees - * @return {string|null} - Nearest compass point - */ -function compassRose(deg) { - if (typeof deg === 'undefined') return null; - while (deg<0 || deg>360) { - deg = (deg+360)%360; - } - return ['n','ne','e','se','s','sw','w','nw','n'][Math.floor((deg+22.5)/45)]; -} - -function setCurrentWeather(json) { - scheduleExpiry(json); - exports.current = json.weather; -} - function update(weatherEvent) { - let weather = Object.assign({}, weatherEvent); - weather.time = Date.now(); - if ('wdir' in weather) { - weather.wrose = compassRose(weather.wdir); - } - delete weather.t; - let json = storage.readJSON('weather.json')||{}; - json.weather = weather; + + if (weatherEvent) { + let weather = weatherEvent.clone(); + delete weather.t; + weather.time = Date.now(); + if (weather.wdir != null) { + // Convert numeric direction into human-readable label + let deg = weather.wdir; + while (deg<0 || deg>360) { + deg = (deg+360)%360; + } + weather.wrose = ['n','ne','e','se','s','sw','w','nw','n'][Math.floor((deg+22.5)/45)]; + } + + json.weather = weather; + } + else { + delete json.weather; + } + storage.write('weather.json', json); - - setCurrentWeather(json); - - exports.emit("update"); + scheduleExpiry(json); + exports.emit("update", json.weather); } const _GB = global.GB; @@ -64,7 +46,11 @@ global.GB = (event) => { if (_GB) setTimeout(_GB, 0, event); }; -setCurrentWeather(storage.readJSON('weather.json')||{}); +exports.get = function() { + return storage.readJSON('weather.json').weather; +} + +scheduleExpiry(storage.readJSON('weather.json')||{}); exports.drawIcon = function(cond, x, y, r) { function drawSun(x, y, r) { diff --git a/apps/weather/widget.js b/apps/weather/widget.js index ba0c8604d..4871ceda4 100644 --- a/apps/weather/widget.js +++ b/apps/weather/widget.js @@ -1,45 +1,23 @@ (() => { const weather = require('weather'); - function draw() { - const w = weather.current; - if (!w) return; - g.reset(); - g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23); - if (w.txt) { - weather.drawIcon(w.txt, this.x+10, this.y+8, 7.5); - } - if (w.temp) { - let t = require('locale').temp(w.temp-273.15); // applies conversion - t = t.match(/[\d\-]*/)[0]; // but we have no room for units - g.reset(); - g.setFontAlign(0, 1); // center horizontally at bottom of widget - g.setFont('6x8', 1); - g.drawString(t, this.x+10, this.y+24); - } - } - var dirty = false; - function update() { - if (!WIDGETS["weather"].width) { - WIDGETS["weather"].width = 20; - Bangle.drawWidgets(); - } else if (Bangle.isLCDOn()) { - WIDGETS["weather"].draw(); - } else { - dirty = true; + weather.on("update", w => { + if (w) { + if (!WIDGETS["weather"].width) { + WIDGETS["weather"].width = 20; + Bangle.drawWidgets(); + } else if (Bangle.isLCDOn()) { + WIDGETS["weather"].draw(); + } else { + dirty = true; + } + } + else { + WIDGETS["weather"].width = 0; + Bangle.drawWidgets(); } - } - - function hide() { - WIDGETS["weather"].width = 0; - Bangle.drawWidgets(); - } - - weather.on("update", () => { - if (weather.current) update(); - else hide(); }); Bangle.on('lcdPower', on => { @@ -51,7 +29,23 @@ WIDGETS["weather"] = { area: "tl", - width: weather.current ? 20 : 0, - draw: draw, + width: weather.get() ? 20 : 0, + draw: function() { + const w = weather.get(); + if (!w) return; + g.reset(); + g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23); + if (w.txt) { + weather.drawIcon(w.txt, this.x+10, this.y+8, 7.5); + } + if (w.temp) { + let t = require('locale').temp(w.temp-273.15); // applies conversion + t = t.match(/[\d\-]*/)[0]; // but we have no room for units + g.reset(); + g.setFontAlign(0, 1); // center horizontally at bottom of widget + g.setFont('6x8', 1); + g.drawString(t, this.x+10, this.y+24); + } + }, }; })(); From ec453c0e100f7fd140305fe8ed4a62d3987e68f1 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 20 Sep 2021 19:45:50 -0700 Subject: [PATCH 0058/1062] Update README.md --- apps/schoolCalender/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalender/README.md b/apps/schoolCalender/README.md index e053cfdb5..c91f59759 100644 --- a/apps/schoolCalender/README.md +++ b/apps/schoolCalender/README.md @@ -1,6 +1,6 @@ # Bangle.js Calendar -School Calender is a calender that you can see your upcoming classes or schedule. +School Calender is a calender that you can see your upcoming classes or schedule. Keep in note that the events repeat weekly. ## Versions: From ca35cd9ee749917606a79b967eba4060547a1656 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 21 Sep 2021 09:03:41 +0100 Subject: [PATCH 0059/1062] new tools that solves pretokenise + unminified library include --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 2aac601e3..91318ea7c 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 2aac601e38d659876eb7db5aebc7a12dd3c39da7 +Subproject commit 91318ea7cb262ed0b15c43bd67546594d956f61f From 2fd4d4ded1783e4dcc21a9ff0111c23334a22882 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 21 Sep 2021 10:17:38 +0100 Subject: [PATCH 0060/1062] widpedom 0.19: Allow goal in large font mode Stop goal drawing outside widget area Fix issue with widget overwrite in large font mode Memory usage enhancements --- apps.json | 2 +- apps/widpedom/ChangeLog | 4 + apps/widpedom/settings.js | 8 +- apps/widpedom/widget.js | 154 +++++++++++++++++++------------------- 4 files changed, 85 insertions(+), 83 deletions(-) diff --git a/apps.json b/apps.json index d368cf33b..0c197fa2b 100644 --- a/apps.json +++ b/apps.json @@ -1184,7 +1184,7 @@ { "id": "widpedom", "name": "Pedometer widget", "icon": "widget.png", - "version":"0.18", + "version":"0.19", "description": "Daily pedometer widget", "tags": "widget,b2", "type":"widget", diff --git a/apps/widpedom/ChangeLog b/apps/widpedom/ChangeLog index 8844a7d44..2f36c7647 100644 --- a/apps/widpedom/ChangeLog +++ b/apps/widpedom/ChangeLog @@ -15,3 +15,7 @@ 0.16: Settings option to show large digits in widget area 0.17: Cope with 2v10+ firmware sometimes reporting >1 step 0.18: Adjust widget width when displaying large text +0.19: Allow goal in large font mode + Stop goal drawing outside widget area + Fix issue with widget overwrite in large font mode + Memory usage enhancements diff --git a/apps/widpedom/settings.js b/apps/widpedom/settings.js index 754b636c9..4455ce7d7 100644 --- a/apps/widpedom/settings.js +++ b/apps/widpedom/settings.js @@ -32,7 +32,7 @@ onchange: (g) => { s.goal = g s.progress = !!g - save() + save(); }, }, 'Show Progress': { @@ -40,7 +40,7 @@ format: () => (s.progress ? 'Yes' : 'No'), onchange: () => { s.progress = !s.progress - save() + save(); }, }, 'Large Digits': { @@ -48,7 +48,7 @@ format: () => (s.large ? 'Yes' : 'No'), onchange: () => { s.large = !s.large - save() + save(); }, }, 'Hide Widget': { @@ -56,7 +56,7 @@ format: () => (s.hide ? 'Yes' : 'No'), onchange: () => { s.hide = !s.hide - save() + save(); }, }, '< Back': back, diff --git a/apps/widpedom/widget.js b/apps/widpedom/widget.js index fbc2a87bb..3c861cf54 100644 --- a/apps/widpedom/widget.js +++ b/apps/widpedom/widget.js @@ -23,78 +23,6 @@ return (key in settings) ? settings[key] : DEFAULTS[key]; } - function drawProgress(stps) { - if (setting('hide')) return; - const width = 24, half = width/2; - const goal = setting('goal'), left = Math.max(goal-stps,0); - const c = left ? "#00f" : "#090"; // blue or dark green - g.setColor(c).fillCircle(this.x + half, this.y + half, half); - const TAU = Math.PI*2; - if (left) { - const f = left/goal; // fraction to blank out - let p = []; - p.push(half,half); - p.push(half,0); - if(f>1/8) p.push(0,0); - if(f>2/8) p.push(0,half); - if(f>3/8) p.push(0,width); - if(f>4/8) p.push(half,width); - if(f>5/8) p.push(width,width); - if(f>6/8) p.push(width,half); - if(f>7/8) p.push(width,0); - p.push(half - Math.sin(f * TAU) * half); - p.push(half - Math.cos(f * TAU) * half); - for (let i = p.length; i; i -= 2) { - p[i - 2] += this.x; - p[i - 1] += this.y; - } - g.setColor(g.theme.bg).fillPoly(p); - } - } - - // show the step count in the widget area in a readable sized font - function draw_large(st) { - this.width = 12 * st.length + 3; - g.reset(); - g.clearRect(this.x, this.y, this.x + this.width, this.y + 16); // erase background - g.setColor(g.theme.fg); - g.setFont("6x8",2); - g.setFontAlign(-1, -1); - g.drawString(st, this.x + 4, this.y + 2); - } - - // draw your widget - function draw() { - if (setting('hide')) return; - var width = 24; - if (stp_today > 99999){ - stp_today = stp_today % 100000; // cap to five digits + comma = 6 characters - } - let stps = stp_today.toString(); - if (setting('large')) { - draw_large.call(this, stps); - return; - } - g.reset().clearRect(this.x, this.y, this.x + width, this.y + 23); // erase background - if (setting('progress')){ drawProgress.call(this, stps); } - g.setColor(g.theme.fg); - if (stps.length > 3){ - stps = stps.slice(0,-3) + "," + stps.slice(-3); - g.setFont("4x6", 1); // if big, shrink text to fix - } else { - g.setFont("6x8", 1); - } - g.setFontAlign(0, 0); // align to x: center, y: center - g.drawString(stps, this.x+width/2, this.y+19); - // on low bpp screens, draw 1 bit. Currently there is no getBPP so we just do it based on resolution - g.drawImage(atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="),this.x+(width-10)/2,this.y+2); - } - - function reload() { - loadSettings() - draw() - } - Bangle.on('step', stepCount => { var steps = stepCount-lastStepCount; if (lastStepCount===undefined || steps<0) steps=1; @@ -115,11 +43,11 @@ } lastUpdate = date //console.log("up: " + up + " stp: " + stp_today + " " + date.toString()); - if (Bangle.isLCDOn()) WIDGETS["wpedom"].draw(); + WIDGETS["wpedom"].redraw(); }); // redraw when the LCD turns on Bangle.on('lcdPower', function(on) { - if (on) WIDGETS["wpedom"].draw(); + if (on) WIDGETS["wpedom"].redraw(); }); // When unloading, save state E.on('kill', () => { @@ -134,10 +62,80 @@ // add your widget WIDGETS["wpedom"]={area:"tl",width:26, - draw:draw, - reload:reload, - getSteps:()=>stp_today - }; + redraw:function() { // work out the width, and queue a full redraw if needed + let stps = stp_today.toString(); + let newWidth = 24; + if (setting('hide')) + newWidth = 0; + else { + if (setting('large')) { + newWidth = 12 * stps.length + 3; + if (setting('progress')) + newWidth += 24; + } + } + if (newWidth!=this.width) { + // width has changed, re-layout all widgets + this.width = newWidth; + Bangle.drawWidgets(); + } else { + // width not changed - just redraw + WIDGETS["wpedom"].draw(); + } + }, + draw:function() { + if (setting('hide')) return; + if (stp_today > 99999) + stp_today = stp_today % 100000; // cap to five digits + comma = 6 characters + let stps = stp_today.toString(); + g.reset().clearRect(this.x, this.y, this.x + this.width, this.y + 23); // erase background + if (setting('progress')) { + const width = 23, half = 11; + const goal = setting('goal'), left = Math.max(goal-stps,0); + // blue or dark green + g.setColor(left ? "#08f" : "#080").fillCircle(this.x + half, this.y + half, half); + if (left) { + const TAU = Math.PI*2; + const f = left/goal; // fraction to blank out + let p = []; + p.push(half,half); + p.push(half,0); + if(f>1/8) p.push(0,0); + if(f>2/8) p.push(0,half); + if(f>3/8) p.push(0,width); + if(f>4/8) p.push(half,width); + if(f>5/8) p.push(width,width); + if(f>6/8) p.push(width,half); + if(f>7/8) p.push(width,0); + p.push(half - Math.sin(f * TAU) * half); + p.push(half - Math.cos(f * TAU) * half); + g.setColor(g.theme.bg).fillPoly(g.transformVertices(p,{x:this.x,y:this.y})); + } + g.reset(); + } + if (setting('large')) { + g.setFont("6x8",2); + g.setFontAlign(-1, 0); + g.drawString(stps, this.x + (setting('progress')?28:4), this.y + 12); + } else { + let w = 24; + if (stps.length > 3){ + stps = stps.slice(0,-3) + "," + stps.slice(-3); + g.setFont("4x6", 1); // if big, shrink text to fix + } else { + g.setFont("6x8", 1); + } + g.setFontAlign(0, 0); // align to x: center, y: center + g.drawString(stps, this.x+w/2, this.y+19); + g.drawImage(atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="),this.x+(w-10)/2,this.y+2); + } + }, + reload:function() { + loadSettings(); + WIDGETS["wpedom"].redraw(); + }, + getSteps:()=>stp_today + }; // Load data at startup let pedomData = require("Storage").readJSON(PEDOMFILE,1); if (pedomData) { From 47bf4edd8ef910f0fed5b5fead7fc514239e4b3e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 21 Sep 2021 10:34:32 +0100 Subject: [PATCH 0061/1062] Fix 'removing undefined' message Automatically install dependencies correctly https://github.com/espruino/BangleApps/issues/792 - previously it'd fail to then install the original app the first time --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 91318ea7c..0fd608f08 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 91318ea7cb262ed0b15c43bd67546594d956f61f +Subproject commit 0fd608f085deff9b39f2db3559ecc88edb232aba From aaea30f303b05f66fb1a5982ba90c8ddf2e88d66 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 21 Sep 2021 10:34:42 +0100 Subject: [PATCH 0062/1062] update for new layout --- apps/accellog/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/accellog/app.js b/apps/accellog/app.js index 5408264f1..0da56b6ea 100644 --- a/apps/accellog/app.js +++ b/apps/accellog/app.js @@ -97,7 +97,7 @@ function startRecord(force) { {type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5}, {type:"txt", font:"6x8", label:"Time", pad:2}, {type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5}, - {type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:true}, + {type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1}, ] },[ // Buttons... {label:"STOP", cb:()=>{ From 3b15a7f92258bfdad4bc8f65845acf7fcff761cb Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 21 Sep 2021 10:50:04 +0100 Subject: [PATCH 0063/1062] Notifications - fullscreen now works with Bangle.js 2, and better supports themes --- apps.json | 8 ++++---- apps/notify/ChangeLog | 1 + apps/notify/README.md | 4 ++++ apps/notify/notify.js | 10 ++++++---- apps/notifyfs/ChangeLog | 1 + apps/notifyfs/notify.js | 33 ++++++++++++++++----------------- 6 files changed, 32 insertions(+), 25 deletions(-) diff --git a/apps.json b/apps.json index 0c197fa2b..cd5bf3489 100644 --- a/apps.json +++ b/apps.json @@ -94,7 +94,7 @@ "name": "Notifications (default)", "shortName":"Notifications", "icon": "notify.png", - "version":"0.09", + "version":"0.10", "description": "A handler for displaying notifications that displays them in a bar at the top of the screen", "tags": "widget", "type": "notify", @@ -107,9 +107,9 @@ "name": "Fullscreen Notifications", "shortName":"Notifications", "icon": "notify.png", - "version":"0.10", + "version":"0.11", "description": "A handler for displaying notifications that displays them fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notifications library.", - "tags": "widget", + "tags": "widget,b2", "type": "notify", "storage": [ {"name":"notify","url":"notify.js"} @@ -155,7 +155,7 @@ "icon": "app.png", "version":"0.24", "description": "The default notification handler for Gadgetbridge notifications from Android", - "tags": "tool,system,android,widget", + "tags": "tool,system,android,widget,b2", "readme": "README.md", "type":"widget", "dependencies": { "notify":"type" }, diff --git a/apps/notify/ChangeLog b/apps/notify/ChangeLog index 291f32a5a..d1826f57e 100644 --- a/apps/notify/ChangeLog +++ b/apps/notify/ChangeLog @@ -6,3 +6,4 @@ 0.07: Auto-calculate height, and pad text down even when there's no title (so it stays on-screen) 0.08: Don't turn on screen during Quiet Mode 0.09: Add onHide callback +0.10: Improvements to help notifications work with themes diff --git a/apps/notify/README.md b/apps/notify/README.md index 11c493102..f186aaab2 100644 --- a/apps/notify/README.md +++ b/apps/notify/README.md @@ -5,6 +5,10 @@ A handler for displaying notifications that displays them in a bar at the top of This is not an app, but instead it is a library that can be used by other applications or widgets to display messages. +**Note:** There are other implementations of this library available such +as `notifyfs` (Fullscreen Notifications). These can be used in the exact +same way from code, but they look different to the user. + ## Usage ```JS diff --git a/apps/notify/notify.js b/apps/notify/notify.js index 68bc1a954..4448de73b 100644 --- a/apps/notify/notify.js +++ b/apps/notify/notify.js @@ -96,15 +96,17 @@ exports.show = function(options) { b = y+h-1, r = x+w-1; // bottom,right g.setClipRect(x,y, r,b); // clear area - g.setColor(options.bgColor||0).fillRect(x,y, r,b); + g.reset(); + if (options.bgColor!==undefined) g.setColor(options.bgColor); + g.clearRect(x,y, r,b); // bottom border - g.setColor(0x39C7).fillRect(0,b-1, r,b); + g.setColor("#333").fillRect(0,b-1, r,b); b -= 2;h -= 2; // title bar if (options.title || options.src) { g.setColor(options.titleBgColor||0x39C7).fillRect(x,y, r,y+20); const title = options.title||options.src; - g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 2); + g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 2); g.drawString(title.trim().substring(0, 13), x+25,y+3); if (options.title && options.src) { g.setFont("6x8", 1).setFontAlign(1, 1, 0); @@ -122,7 +124,7 @@ exports.show = function(options) { } // body text if (options.body) { - g.setColor(-1).setFont("6x8", 1).setFontAlign(-1, -1, 0).drawString(text, x+6,y+4); + g.setColor(g.theme.fg).setFont("6x8", 1).setFontAlign(-1, -1, 0).drawString(text, x+6,y+4); } if (options.render) { diff --git a/apps/notifyfs/ChangeLog b/apps/notifyfs/ChangeLog index ace651a0d..1c39bcbd5 100644 --- a/apps/notifyfs/ChangeLog +++ b/apps/notifyfs/ChangeLog @@ -8,3 +8,4 @@ 0.08: Don't turn on screen during Quiet Mode 0.09: Add onHide callback 0.10: Ensure dismissing a notification dismissal doesn't enter launcher if in clock mode +0.11: Improvements to help notifications work with themes, Bangle.js 2 support diff --git a/apps/notifyfs/notify.js b/apps/notifyfs/notify.js index cd8d108d5..a45d889f0 100644 --- a/apps/notifyfs/notify.js +++ b/apps/notifyfs/notify.js @@ -50,22 +50,24 @@ exports.show = function(options) { if (options.on===undefined) options.on=true; id = ("id" in options)?options.id:null; let size = options.size||120; - if (size>120) {size=120} - Bangle.setLCDMode("direct"); + if (size>120) size=120; + try { Bangle.setLCDMode("direct"); } catch(e) {} // not supported/needed on Bangle.js 2 let x = 0, y = 40, - w = 240, + w = g.getWidth(), h = size; // clear screen - g.setColor(options.bgColor||0).fillRect(0,0,g.getWidth(),g.getHeight()); + g.reset(); + if (options.bgColor!==undefined) g.setColor(options.bgColor); + g.clearRect(0,0,g.getWidth(),g.getHeight()); // top bar if (options.title||options.src) { - const title = options.title || options.src - g.setColor(options.titleBgColor||0x39C7).fillRect(x, y, x+w-1, y+30); - g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 3); + const title = options.title || options.src; + g.setColor(options.titleBgColor||"#333").fillRect(x, y, x+w-1, y+30); + g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 3); g.drawString(title.trim().substring(0, 13), x+5, y+3); if (options.title && options.src) { - g.setColor(-1).setFontAlign(1, 1, 0).setFont("6x8", 2); + g.setColor(g.theme.fg).setFontAlign(1, 1, 0).setFont("6x8", 2); // above drawing area, but we are fullscreen g.drawString(options.src.substring(0, 10), w-16, y-4); } @@ -73,8 +75,8 @@ exports.show = function(options) { } if (options.icon) { let i = options.icon, iw,ih; - if ("string"==typeof i) {iw=i.charCodeAt(0); ih=i.charCodeAt(1)} - else {iw=i[0]; ih=i[1]} + if ("string"==typeof i) {iw=i.charCodeAt(0); ih=i.charCodeAt(1);} + else {iw=i[0]; ih=i[1];} const iy=y ? (y+4) : (h-ih)/2; // show below title bar if present, otherwise center vertically g.drawImage(i, x+4,iy); x += iw+4;w -= iw+4; @@ -84,16 +86,13 @@ exports.show = function(options) { const maxRows = Math.floor((h-4)/16), // font=2*(6x8) maxChars = Math.floor((w-4)/12), text=fitWords(options.body, maxRows, maxChars); - g.setColor(-1).setFont("6x8", 2).setFontAlign(-1, -1, 0).drawString(text, x+4, y+4); + g.setColor(g.theme.fg).setFont("6x8", 2).setFontAlign(-1, -1, 0).drawString(text, x+4, y+4); } - if (options.render) { - const area={x:x, y:y, w:w, h:h} - options.render(area); - } - if (options.on && !(require('Storage').readJSON('setting.json',1)||{}).quiet) { + if (options.render) + options.render({x:x, y:y, w:w, h:h}); + if (options.on && !(require('Storage').readJSON('setting.json',1)||{}).quiet) Bangle.setLCDPower(1); // light up - } Bangle.on("touch", exports.hide); if (options.onHide) hideCallback = options.onHide; From ceab5d46a23f913faf96be90e644458ac5546157 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 21 Sep 2021 20:58:01 -0700 Subject: [PATCH 0064/1062] Rename schoolCalender.js to app.js --- apps/schoolCalender/{schoolCalender.js => app.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/schoolCalender/{schoolCalender.js => app.js} (100%) diff --git a/apps/schoolCalender/schoolCalender.js b/apps/schoolCalender/app.js similarity index 100% rename from apps/schoolCalender/schoolCalender.js rename to apps/schoolCalender/app.js From d8c149073fb6bca8f5f78ba4a031004adeab1a6b Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 21 Sep 2021 21:01:31 -0700 Subject: [PATCH 0065/1062] Update apps.json --- apps.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps.json b/apps.json index 052feff77..c8b8a1ad0 100644 --- a/apps.json +++ b/apps.json @@ -3512,6 +3512,10 @@ "tags": "tool", "readme": "README.md", "custom":"interface.html", + "storage": [ + {"name":"schoolCalender.app.js","url":"app.js"}, + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} + ], "data": [ {"name":"app.json"} ] From 2ca58d2cc436b72beb87d347b8d8c217b461e805 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 21 Sep 2021 21:02:14 -0700 Subject: [PATCH 0066/1062] Update custom.html --- apps/mywelcome/custom.html | 203 +++++++++++++++---------------------- 1 file changed, 79 insertions(+), 124 deletions(-) diff --git a/apps/mywelcome/custom.html b/apps/mywelcome/custom.html index b021b7b1a..6b73a4ac4 100644 --- a/apps/mywelcome/custom.html +++ b/apps/mywelcome/custom.html @@ -1,137 +1,92 @@ - - - -
-

Style: -

-

Line 1:

-

Line 2:

-

Line 3 (smaller):

-

Line 4 (smaller):

- -

-

-

This is currently Christmas-themed, but more themes will be added in the future.

- - - - + + + +
From 3d8ebe82c289156581799134b3c56fd02ade1fad Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 21 Sep 2021 21:07:20 -0700 Subject: [PATCH 0067/1062] Update interface.html --- apps/schoolCalender/interface.html | 196 ++++++++++++----------------- 1 file changed, 79 insertions(+), 117 deletions(-) diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html index 10e7b2717..9f49c5886 100644 --- a/apps/schoolCalender/interface.html +++ b/apps/schoolCalender/interface.html @@ -1,130 +1,92 @@ - - - -
-

Style: -

-

Line 1:

-

Line 2:

-

Line 3 (smaller):

-

Line 4 (smaller):

- -

-

-

This is currently Christmas-themed, but more themes will be added in the future.

- - - - + + + +
From 1f130c53b46546328260a6b94ad182be5d55ff18 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Wed, 22 Sep 2021 07:25:41 -0700 Subject: [PATCH 0068/1062] Update interface.html --- apps/schoolCalender/interface.html | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/schoolCalender/interface.html b/apps/schoolCalender/interface.html index 9f49c5886..a03aa1660 100644 --- a/apps/schoolCalender/interface.html +++ b/apps/schoolCalender/interface.html @@ -27,6 +27,7 @@ + - - - - -
- - - From 941bc852c1ab4472dc74a7f84e12e92ab891432e Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 4 Oct 2021 15:57:53 -0700 Subject: [PATCH 0126/1062] Add files via upload --- apps/schoolCalendar/CalenderLogo.png | Bin 0 -> 1162 bytes apps/schoolCalendar/ChangeLog.md | 2 + apps/schoolCalendar/README.md | 9 + apps/schoolCalendar/app-icon.js | 1 + .../fullcalendar/interaction/LICENSE.txt | 22 + .../fullcalendar/interaction/README.md | 8 + .../fullcalendar/interaction/package.json | 32 + .../interaction/src/ElementScrollGeomCache.ts | 16 + .../interaction/src/OffsetTracker.ts | 76 + .../interaction/src/ScrollGeomCache.ts | 107 + .../interaction/src/WindowScrollGeomCache.ts | 27 + .../interaction/src/api-type-deps.ts | 6 + .../interaction/src/dnd/AutoScroller.ts | 217 + .../interaction/src/dnd/ElementMirror.ts | 146 + .../src/dnd/FeaturefulElementDragging.ts | 213 + .../interaction/src/dnd/PointerDragging.ts | 344 + .../ExternalDraggable.ts | 70 + .../ExternalElementDragging.ts | 268 + .../InferredElementDragging.ts | 77 + .../ThirdPartyDraggable.ts | 52 + .../src/interactions/DateClicking.ts | 71 + .../src/interactions/DateSelecting.ts | 152 + .../src/interactions/EventDragging.ts | 482 + .../src/interactions/EventResizing.ts | 263 + .../src/interactions/HitDragging.ts | 220 + .../src/interactions/UnselectAuto.ts | 77 + .../interaction/src/main.global.ts | 7 + .../fullcalendar/interaction/src/main.ts | 23 + .../interaction/src/options-declare.ts | 9 + .../fullcalendar/interaction/src/options.ts | 26 + .../fullcalendar/interaction/src/utils.ts | 38 + .../fullcalendar/interaction/tsconfig.json | 13 + .../fullcalendar/locales-all.js | 1622 ++ apps/schoolCalendar/fullcalendar/main.css | 1446 ++ apps/schoolCalendar/fullcalendar/main.js | 14738 ++++++++++++++++ apps/schoolCalendar/interface.html | 82 + apps/schoolCalendar/schoolCalendar.js | 229 + 37 files changed, 21191 insertions(+) create mode 100644 apps/schoolCalendar/CalenderLogo.png create mode 100644 apps/schoolCalendar/ChangeLog.md create mode 100644 apps/schoolCalendar/README.md create mode 100644 apps/schoolCalendar/app-icon.js create mode 100644 apps/schoolCalendar/fullcalendar/interaction/LICENSE.txt create mode 100644 apps/schoolCalendar/fullcalendar/interaction/README.md create mode 100644 apps/schoolCalendar/fullcalendar/interaction/package.json create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/ElementScrollGeomCache.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/OffsetTracker.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/main.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/options.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/utils.ts create mode 100644 apps/schoolCalendar/fullcalendar/interaction/tsconfig.json create mode 100644 apps/schoolCalendar/fullcalendar/locales-all.js create mode 100644 apps/schoolCalendar/fullcalendar/main.css create mode 100644 apps/schoolCalendar/fullcalendar/main.js create mode 100644 apps/schoolCalendar/interface.html create mode 100644 apps/schoolCalendar/schoolCalendar.js diff --git a/apps/schoolCalendar/CalenderLogo.png b/apps/schoolCalendar/CalenderLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..49d198a15b0feb1c4d175282c6e1c1112df8b06c GIT binary patch literal 1162 zcmV;51anZw{&TSgFQ0s2e#fgMU}6Rl(1Cxt0iNJP?%_Xw&WO?Wwfph4b@stgo+Qa&i(UCnxF= z=^f0^&*Sj$5W~a6ihNb)l-ma_oI-sNn#AfHKVDn)1KFU|8k1(FzFl78?CeY#U}0f_ zt*$IhHpGmKH2oliK)jN`FZi~|&(6*wpU>k`D$U`+_o|PNE}>0tfFZyFtu-8(t`Mk4 zRVadHfw%A8(ZHa13UYil?-IKa`-5p2n{%CWPgF;s%vHCvW7)h_h`u0X2E*gnkAhjL7?#g!$$ zm&@qu>r+YlRVJ;lbwApL8doz}bu2NPJ9T+Lj;K2XsklI%Bg*<07Z*^WVtNPHd5(J{ zi)?T27ixenz{XvLjL)@vy)h_U=P@O?KINEKJM=B{v^Vc zQg7yIFotKK#7GlDQ8PnhosU!%@z7{B``hhynA5}plQ?{oFs0Ou4@N-|w new ElementScrollGeomCache(scrollEl, true), // listen=true + ) + } + + destroy() { + for (let scrollCache of this.scrollCaches) { + scrollCache.destroy() + } + } + + computeLeft() { + let left = this.origRect.left + + for (let scrollCache of this.scrollCaches) { + left += scrollCache.origScrollLeft - scrollCache.getScrollLeft() + } + + return left + } + + computeTop() { + let top = this.origRect.top + + for (let scrollCache of this.scrollCaches) { + top += scrollCache.origScrollTop - scrollCache.getScrollTop() + } + + return top + } + + isWithinClipping(pageX: number, pageY: number): boolean { + let point = { left: pageX, top: pageY } + + for (let scrollCache of this.scrollCaches) { + if ( + !isIgnoredClipping(scrollCache.getEventTarget()) && + !pointInsideRect(point, scrollCache.clientRect) + ) { + return false + } + } + + return true + } +} + +// certain clipping containers should never constrain interactions, like and +// https://github.com/fullcalendar/fullcalendar/issues/3615 +function isIgnoredClipping(node: EventTarget) { + let tagName = (node as HTMLElement).tagName + + return tagName === 'HTML' || tagName === 'BODY' +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts b/apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts new file mode 100644 index 000000000..63ec6a5e1 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts @@ -0,0 +1,107 @@ +import { Rect, ScrollController } from '@fullcalendar/common' + +/* +Is a cache for a given element's scroll information (all the info that ScrollController stores) +in addition the "client rectangle" of the element.. the area within the scrollbars. + +The cache can be in one of two modes: +- doesListening:false - ignores when the container is scrolled by someone else +- doesListening:true - watch for scrolling and update the cache +*/ +export abstract class ScrollGeomCache extends ScrollController { + clientRect: Rect + origScrollTop: number + origScrollLeft: number + + protected scrollController: ScrollController + protected doesListening: boolean + protected scrollTop: number + protected scrollLeft: number + protected scrollWidth: number + protected scrollHeight: number + protected clientWidth: number + protected clientHeight: number + + constructor(scrollController: ScrollController, doesListening: boolean) { + super() + this.scrollController = scrollController + this.doesListening = doesListening + this.scrollTop = this.origScrollTop = scrollController.getScrollTop() + this.scrollLeft = this.origScrollLeft = scrollController.getScrollLeft() + this.scrollWidth = scrollController.getScrollWidth() + this.scrollHeight = scrollController.getScrollHeight() + this.clientWidth = scrollController.getClientWidth() + this.clientHeight = scrollController.getClientHeight() + this.clientRect = this.computeClientRect() // do last in case it needs cached values + + if (this.doesListening) { + this.getEventTarget().addEventListener('scroll', this.handleScroll) + } + } + + abstract getEventTarget(): EventTarget + abstract computeClientRect(): Rect + + destroy() { + if (this.doesListening) { + this.getEventTarget().removeEventListener('scroll', this.handleScroll) + } + } + + handleScroll = () => { + this.scrollTop = this.scrollController.getScrollTop() + this.scrollLeft = this.scrollController.getScrollLeft() + this.handleScrollChange() + } + + getScrollTop() { + return this.scrollTop + } + + getScrollLeft() { + return this.scrollLeft + } + + setScrollTop(top: number) { + this.scrollController.setScrollTop(top) + + if (!this.doesListening) { + // we are not relying on the element to normalize out-of-bounds scroll values + // so we need to sanitize ourselves + this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0) + + this.handleScrollChange() + } + } + + setScrollLeft(top: number) { + this.scrollController.setScrollLeft(top) + + if (!this.doesListening) { + // we are not relying on the element to normalize out-of-bounds scroll values + // so we need to sanitize ourselves + this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0) + + this.handleScrollChange() + } + } + + getClientWidth() { + return this.clientWidth + } + + getClientHeight() { + return this.clientHeight + } + + getScrollWidth() { + return this.scrollWidth + } + + getScrollHeight() { + return this.scrollHeight + } + + handleScrollChange() { + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts b/apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts new file mode 100644 index 000000000..ca65dee6e --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts @@ -0,0 +1,27 @@ +import { Rect, WindowScrollController } from '@fullcalendar/common' +import { ScrollGeomCache } from './ScrollGeomCache' + +export class WindowScrollGeomCache extends ScrollGeomCache { + constructor(doesListening: boolean) { + super(new WindowScrollController(), doesListening) + } + + getEventTarget(): EventTarget { + return window + } + + computeClientRect(): Rect { + return { + left: this.scrollLeft, + right: this.scrollLeft + this.clientWidth, + top: this.scrollTop, + bottom: this.scrollTop + this.clientHeight, + } + } + + // the window is the only scroll object that changes it's rectangle relative + // to the document's topleft as it scrolls + handleScrollChange() { + this.clientRect = this.computeClientRect() + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts b/apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts new file mode 100644 index 000000000..2b1b51b06 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts @@ -0,0 +1,6 @@ +// TODO: rename file to public-types.ts + +export { DateClickArg } from './interactions/DateClicking' +export { EventDragStartArg, EventDragStopArg } from './interactions/EventDragging' +export { EventResizeStartArg, EventResizeStopArg, EventResizeDoneArg } from './interactions/EventResizing' +export { DropArg, EventReceiveArg, EventLeaveArg } from './utils' diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts new file mode 100644 index 000000000..8d4e8f015 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts @@ -0,0 +1,217 @@ +import { getElRoot } from '@fullcalendar/common' +import { ScrollGeomCache } from '../ScrollGeomCache' +import { ElementScrollGeomCache } from '../ElementScrollGeomCache' +import { WindowScrollGeomCache } from '../WindowScrollGeomCache' + +interface Edge { + scrollCache: ScrollGeomCache + name: 'top' | 'left' | 'right' | 'bottom' + distance: number // how many pixels the current pointer is from the edge +} + +// If available we are using native "performance" API instead of "Date" +// Read more about it on MDN: +// https://developer.mozilla.org/en-US/docs/Web/API/Performance +const getTime = typeof performance === 'function' ? (performance as any).now : Date.now + +/* +For a pointer interaction, automatically scrolls certain scroll containers when the pointer +approaches the edge. + +The caller must call start + handleMove + stop. +*/ +export class AutoScroller { + // options that can be set by caller + isEnabled: boolean = true + scrollQuery: (Window | string)[] = [window, '.fc-scroller'] + edgeThreshold: number = 50 // pixels + maxVelocity: number = 300 // pixels per second + + // internal state + pointerScreenX: number | null = null + pointerScreenY: number | null = null + isAnimating: boolean = false + scrollCaches: ScrollGeomCache[] | null = null + msSinceRequest?: number + + // protect against the initial pointerdown being too close to an edge and starting the scroll + everMovedUp: boolean = false + everMovedDown: boolean = false + everMovedLeft: boolean = false + everMovedRight: boolean = false + + start(pageX: number, pageY: number, scrollStartEl: HTMLElement) { + if (this.isEnabled) { + this.scrollCaches = this.buildCaches(scrollStartEl) + this.pointerScreenX = null + this.pointerScreenY = null + this.everMovedUp = false + this.everMovedDown = false + this.everMovedLeft = false + this.everMovedRight = false + this.handleMove(pageX, pageY) + } + } + + handleMove(pageX: number, pageY: number) { + if (this.isEnabled) { + let pointerScreenX = pageX - window.pageXOffset + let pointerScreenY = pageY - window.pageYOffset + + let yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY + let xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX + + if (yDelta < 0) { + this.everMovedUp = true + } else if (yDelta > 0) { + this.everMovedDown = true + } + + if (xDelta < 0) { + this.everMovedLeft = true + } else if (xDelta > 0) { + this.everMovedRight = true + } + + this.pointerScreenX = pointerScreenX + this.pointerScreenY = pointerScreenY + + if (!this.isAnimating) { + this.isAnimating = true + this.requestAnimation(getTime()) + } + } + } + + stop() { + if (this.isEnabled) { + this.isAnimating = false // will stop animation + + for (let scrollCache of this.scrollCaches!) { + scrollCache.destroy() + } + + this.scrollCaches = null + } + } + + requestAnimation(now: number) { + this.msSinceRequest = now + requestAnimationFrame(this.animate) + } + + private animate = () => { + if (this.isAnimating) { // wasn't cancelled between animation calls + let edge = this.computeBestEdge( + this.pointerScreenX! + window.pageXOffset, + this.pointerScreenY! + window.pageYOffset, + ) + + if (edge) { + let now = getTime() + this.handleSide(edge, (now - this.msSinceRequest!) / 1000) + this.requestAnimation(now) + } else { + this.isAnimating = false // will stop animation + } + } + } + + private handleSide(edge: Edge, seconds: number) { + let { scrollCache } = edge + let { edgeThreshold } = this + let invDistance = edgeThreshold - edge.distance + let velocity = // the closer to the edge, the faster we scroll + ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic + this.maxVelocity * seconds + let sign = 1 + + switch (edge.name) { + case 'left': + sign = -1 + // falls through + case 'right': + scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign) + break + + case 'top': + sign = -1 + // falls through + case 'bottom': + scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign) + break + } + } + + // left/top are relative to document topleft + private computeBestEdge(left: number, top: number): Edge | null { + let { edgeThreshold } = this + let bestSide: Edge | null = null + + for (let scrollCache of this.scrollCaches!) { + let rect = scrollCache.clientRect + let leftDist = left - rect.left + let rightDist = rect.right - left + let topDist = top - rect.top + let bottomDist = rect.bottom - top + + // completely within the rect? + if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) { + if ( + topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() && + (!bestSide || bestSide.distance > topDist) + ) { + bestSide = { scrollCache, name: 'top', distance: topDist } + } + + if ( + bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() && + (!bestSide || bestSide.distance > bottomDist) + ) { + bestSide = { scrollCache, name: 'bottom', distance: bottomDist } + } + + if ( + leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() && + (!bestSide || bestSide.distance > leftDist) + ) { + bestSide = { scrollCache, name: 'left', distance: leftDist } + } + + if ( + rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() && + (!bestSide || bestSide.distance > rightDist) + ) { + bestSide = { scrollCache, name: 'right', distance: rightDist } + } + } + } + + return bestSide + } + + private buildCaches(scrollStartEl: HTMLElement) { + return this.queryScrollEls(scrollStartEl).map((el) => { + if (el === window) { + return new WindowScrollGeomCache(false) // false = don't listen to user-generated scrolls + } + return new ElementScrollGeomCache(el, false) // false = don't listen to user-generated scrolls + }) + } + + private queryScrollEls(scrollStartEl: HTMLElement) { + let els = [] + + for (let query of this.scrollQuery) { + if (typeof query === 'object') { + els.push(query) + } else { + els.push(...Array.prototype.slice.call( + getElRoot(scrollStartEl).querySelectorAll(query), + )) + } + } + + return els + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts new file mode 100644 index 000000000..6c1e9f4a1 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts @@ -0,0 +1,146 @@ +import { removeElement, applyStyle, whenTransitionDone, Rect } from '@fullcalendar/common' + +/* +An effect in which an element follows the movement of a pointer across the screen. +The moving element is a clone of some other element. +Must call start + handleMove + stop. +*/ +export class ElementMirror { + isVisible: boolean = false // must be explicitly enabled + origScreenX?: number + origScreenY?: number + deltaX?: number + deltaY?: number + sourceEl: HTMLElement | null = null + mirrorEl: HTMLElement | null = null + sourceElRect: Rect | null = null // screen coords relative to viewport + + // options that can be set directly by caller + parentNode: HTMLElement = document.body // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues + zIndex: number = 9999 + revertDuration: number = 0 + + start(sourceEl: HTMLElement, pageX: number, pageY: number) { + this.sourceEl = sourceEl + this.sourceElRect = this.sourceEl.getBoundingClientRect() + this.origScreenX = pageX - window.pageXOffset + this.origScreenY = pageY - window.pageYOffset + this.deltaX = 0 + this.deltaY = 0 + this.updateElPosition() + } + + handleMove(pageX: number, pageY: number) { + this.deltaX = (pageX - window.pageXOffset) - this.origScreenX! + this.deltaY = (pageY - window.pageYOffset) - this.origScreenY! + this.updateElPosition() + } + + // can be called before start + setIsVisible(bool: boolean) { + if (bool) { + if (!this.isVisible) { + if (this.mirrorEl) { + this.mirrorEl.style.display = '' + } + + this.isVisible = bool // needs to happen before updateElPosition + this.updateElPosition() // because was not updating the position while invisible + } + } else if (this.isVisible) { + if (this.mirrorEl) { + this.mirrorEl.style.display = 'none' + } + + this.isVisible = bool + } + } + + // always async + stop(needsRevertAnimation: boolean, callback: () => void) { + let done = () => { + this.cleanup() + callback() + } + + if ( + needsRevertAnimation && + this.mirrorEl && + this.isVisible && + this.revertDuration && // if 0, transition won't work + (this.deltaX || this.deltaY) // if same coords, transition won't work + ) { + this.doRevertAnimation(done, this.revertDuration) + } else { + setTimeout(done, 0) + } + } + + doRevertAnimation(callback: () => void, revertDuration: number) { + let mirrorEl = this.mirrorEl! + let finalSourceElRect = this.sourceEl!.getBoundingClientRect() // because autoscrolling might have happened + + mirrorEl.style.transition = + 'top ' + revertDuration + 'ms,' + + 'left ' + revertDuration + 'ms' + + applyStyle(mirrorEl, { + left: finalSourceElRect.left, + top: finalSourceElRect.top, + }) + + whenTransitionDone(mirrorEl, () => { + mirrorEl.style.transition = '' + callback() + }) + } + + cleanup() { + if (this.mirrorEl) { + removeElement(this.mirrorEl) + this.mirrorEl = null + } + + this.sourceEl = null + } + + updateElPosition() { + if (this.sourceEl && this.isVisible) { + applyStyle(this.getMirrorEl(), { + left: this.sourceElRect!.left + this.deltaX!, + top: this.sourceElRect!.top + this.deltaY!, + }) + } + } + + getMirrorEl(): HTMLElement { + let sourceElRect = this.sourceElRect! + let mirrorEl = this.mirrorEl + + if (!mirrorEl) { + mirrorEl = this.mirrorEl = this.sourceEl!.cloneNode(true) as HTMLElement // cloneChildren=true + + // we don't want long taps or any mouse interaction causing selection/menus. + // would use preventSelection(), but that prevents selectstart, causing problems. + mirrorEl.classList.add('fc-unselectable') + + mirrorEl.classList.add('fc-event-dragging') + + applyStyle(mirrorEl, { + position: 'fixed', + zIndex: this.zIndex, + visibility: '', // in case original element was hidden by the drag effect + boxSizing: 'border-box', // for easy width/height + width: sourceElRect.right - sourceElRect.left, // explicit height in case there was a 'right' value + height: sourceElRect.bottom - sourceElRect.top, // explicit width in case there was a 'bottom' value + right: 'auto', // erase and set width instead + bottom: 'auto', // erase and set height instead + margin: 0, + }) + + this.parentNode.appendChild(mirrorEl) + } + + return mirrorEl + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts new file mode 100644 index 000000000..3f1c7826b --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts @@ -0,0 +1,213 @@ +import { + PointerDragEvent, + preventSelection, + allowSelection, + preventContextMenu, + allowContextMenu, + ElementDragging, +} from '@fullcalendar/common' +import { PointerDragging } from './PointerDragging' +import { ElementMirror } from './ElementMirror' +import { AutoScroller } from './AutoScroller' + +/* +Monitors dragging on an element. Has a number of high-level features: +- minimum distance required before dragging +- minimum wait time ("delay") before dragging +- a mirror element that follows the pointer +*/ +export class FeaturefulElementDragging extends ElementDragging { + pointer: PointerDragging + mirror: ElementMirror + autoScroller: AutoScroller + + // options that can be directly set by caller + // the caller can also set the PointerDragging's options as well + delay: number | null = null + minDistance: number = 0 + touchScrollAllowed: boolean = true // prevents drag from starting and blocks scrolling during drag + + mirrorNeedsRevert: boolean = false + isInteracting: boolean = false // is the user validly moving the pointer? lasts until pointerup + isDragging: boolean = false // is it INTENTFULLY dragging? lasts until after revert animation + isDelayEnded: boolean = false + isDistanceSurpassed: boolean = false + delayTimeoutId: number | null = null + + constructor(private containerEl: HTMLElement, selector?: string) { + super(containerEl) + + let pointer = this.pointer = new PointerDragging(containerEl) + pointer.emitter.on('pointerdown', this.onPointerDown) + pointer.emitter.on('pointermove', this.onPointerMove) + pointer.emitter.on('pointerup', this.onPointerUp) + + if (selector) { + pointer.selector = selector + } + + this.mirror = new ElementMirror() + this.autoScroller = new AutoScroller() + } + + destroy() { + this.pointer.destroy() + + // HACK: simulate a pointer-up to end the current drag + // TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire) + this.onPointerUp({} as any) + } + + onPointerDown = (ev: PointerDragEvent) => { + if (!this.isDragging) { // so new drag doesn't happen while revert animation is going + this.isInteracting = true + this.isDelayEnded = false + this.isDistanceSurpassed = false + + preventSelection(document.body) + preventContextMenu(document.body) + + // prevent links from being visited if there's an eventual drag. + // also prevents selection in older browsers (maybe?). + // not necessary for touch, besides, browser would complain about passiveness. + if (!ev.isTouch) { + ev.origEvent.preventDefault() + } + + this.emitter.trigger('pointerdown', ev) + + if ( + this.isInteracting && // not destroyed via pointerdown handler + !this.pointer.shouldIgnoreMove + ) { + // actions related to initiating dragstart+dragmove+dragend... + + this.mirror.setIsVisible(false) // reset. caller must set-visible + this.mirror.start(ev.subjectEl as HTMLElement, ev.pageX, ev.pageY) // must happen on first pointer down + + this.startDelay(ev) + + if (!this.minDistance) { + this.handleDistanceSurpassed(ev) + } + } + } + } + + onPointerMove = (ev: PointerDragEvent) => { + if (this.isInteracting) { + this.emitter.trigger('pointermove', ev) + + if (!this.isDistanceSurpassed) { + let minDistance = this.minDistance + let distanceSq // current distance from the origin, squared + let { deltaX, deltaY } = ev + + distanceSq = deltaX * deltaX + deltaY * deltaY + if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem + this.handleDistanceSurpassed(ev) + } + } + + if (this.isDragging) { + // a real pointer move? (not one simulated by scrolling) + if (ev.origEvent.type !== 'scroll') { + this.mirror.handleMove(ev.pageX, ev.pageY) + this.autoScroller.handleMove(ev.pageX, ev.pageY) + } + + this.emitter.trigger('dragmove', ev) + } + } + } + + onPointerUp = (ev: PointerDragEvent) => { + if (this.isInteracting) { + this.isInteracting = false + + allowSelection(document.body) + allowContextMenu(document.body) + + this.emitter.trigger('pointerup', ev) // can potentially set mirrorNeedsRevert + + if (this.isDragging) { + this.autoScroller.stop() + this.tryStopDrag(ev) // which will stop the mirror + } + + if (this.delayTimeoutId) { + clearTimeout(this.delayTimeoutId) + this.delayTimeoutId = null + } + } + } + + startDelay(ev: PointerDragEvent) { + if (typeof this.delay === 'number') { + this.delayTimeoutId = setTimeout(() => { + this.delayTimeoutId = null + this.handleDelayEnd(ev) + }, this.delay) as any // not assignable to number! + } else { + this.handleDelayEnd(ev) + } + } + + handleDelayEnd(ev: PointerDragEvent) { + this.isDelayEnded = true + this.tryStartDrag(ev) + } + + handleDistanceSurpassed(ev: PointerDragEvent) { + this.isDistanceSurpassed = true + this.tryStartDrag(ev) + } + + tryStartDrag(ev: PointerDragEvent) { + if (this.isDelayEnded && this.isDistanceSurpassed) { + if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) { + this.isDragging = true + this.mirrorNeedsRevert = false + + this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl) + this.emitter.trigger('dragstart', ev) + + if (this.touchScrollAllowed === false) { + this.pointer.cancelTouchScroll() + } + } + } + } + + tryStopDrag(ev: PointerDragEvent) { + // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events + // that come from the document to fire beforehand. much more convenient this way. + this.mirror.stop( + this.mirrorNeedsRevert, + this.stopDrag.bind(this, ev), // bound with args + ) + } + + stopDrag(ev: PointerDragEvent) { + this.isDragging = false + this.emitter.trigger('dragend', ev) + } + + // fill in the implementations... + + setIgnoreMove(bool: boolean) { + this.pointer.shouldIgnoreMove = bool + } + + setMirrorIsVisible(bool: boolean) { + this.mirror.setIsVisible(bool) + } + + setMirrorNeedsRevert(bool: boolean) { + this.mirrorNeedsRevert = bool + } + + setAutoScrollEnabled(bool: boolean) { + this.autoScroller.isEnabled = bool + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts new file mode 100644 index 000000000..dbe7d8abb --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts @@ -0,0 +1,344 @@ +import { config, elementClosest, Emitter, PointerDragEvent } from '@fullcalendar/common' + +config.touchMouseIgnoreWait = 500 + +let ignoreMouseDepth = 0 +let listenerCnt = 0 +let isWindowTouchMoveCancelled = false + +/* +Uses a "pointer" abstraction, which monitors UI events for both mouse and touch. +Tracks when the pointer "drags" on a certain element, meaning down+move+up. + +Also, tracks if there was touch-scrolling. +Also, can prevent touch-scrolling from happening. +Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement. + +emits: +- pointerdown +- pointermove +- pointerup +*/ +export class PointerDragging { + containerEl: EventTarget + subjectEl: HTMLElement | null = null + emitter: Emitter + + // options that can be directly assigned by caller + selector: string = '' // will cause subjectEl in all emitted events to be this element + handleSelector: string = '' + shouldIgnoreMove: boolean = false + shouldWatchScroll: boolean = true // for simulating pointermove on scroll + + // internal states + isDragging: boolean = false + isTouchDragging: boolean = false + wasTouchScroll: boolean = false + origPageX: number + origPageY: number + prevPageX: number + prevPageY: number + prevScrollX: number // at time of last pointer pageX/pageY capture + prevScrollY: number // " + + constructor(containerEl: EventTarget) { + this.containerEl = containerEl + this.emitter = new Emitter() + containerEl.addEventListener('mousedown', this.handleMouseDown as EventListener) + containerEl.addEventListener('touchstart', this.handleTouchStart as EventListener, { passive: true }) + listenerCreated() + } + + destroy() { + this.containerEl.removeEventListener('mousedown', this.handleMouseDown as EventListener) + this.containerEl.removeEventListener('touchstart', this.handleTouchStart as EventListener, { passive: true } as AddEventListenerOptions) + listenerDestroyed() + } + + tryStart(ev: UIEvent): boolean { + let subjectEl = this.querySubjectEl(ev) + let downEl = ev.target as HTMLElement + + if ( + subjectEl && + (!this.handleSelector || elementClosest(downEl, this.handleSelector)) + ) { + this.subjectEl = subjectEl + this.isDragging = true // do this first so cancelTouchScroll will work + this.wasTouchScroll = false + + return true + } + + return false + } + + cleanup() { + isWindowTouchMoveCancelled = false + this.isDragging = false + this.subjectEl = null + // keep wasTouchScroll around for later access + this.destroyScrollWatch() + } + + querySubjectEl(ev: UIEvent): HTMLElement { + if (this.selector) { + return elementClosest(ev.target as HTMLElement, this.selector) + } + return this.containerEl as HTMLElement + } + + // Mouse + // ---------------------------------------------------------------------------------------------------- + + handleMouseDown = (ev: MouseEvent) => { + if ( + !this.shouldIgnoreMouse() && + isPrimaryMouseButton(ev) && + this.tryStart(ev) + ) { + let pev = this.createEventFromMouse(ev, true) + this.emitter.trigger('pointerdown', pev) + this.initScrollWatch(pev) + + if (!this.shouldIgnoreMove) { + document.addEventListener('mousemove', this.handleMouseMove) + } + + document.addEventListener('mouseup', this.handleMouseUp) + } + } + + handleMouseMove = (ev: MouseEvent) => { + let pev = this.createEventFromMouse(ev) + this.recordCoords(pev) + this.emitter.trigger('pointermove', pev) + } + + handleMouseUp = (ev: MouseEvent) => { + document.removeEventListener('mousemove', this.handleMouseMove) + document.removeEventListener('mouseup', this.handleMouseUp) + + this.emitter.trigger('pointerup', this.createEventFromMouse(ev)) + + this.cleanup() // call last so that pointerup has access to props + } + + shouldIgnoreMouse() { + return ignoreMouseDepth || this.isTouchDragging + } + + // Touch + // ---------------------------------------------------------------------------------------------------- + + handleTouchStart = (ev: TouchEvent) => { + if (this.tryStart(ev)) { + this.isTouchDragging = true + + let pev = this.createEventFromTouch(ev, true) + this.emitter.trigger('pointerdown', pev) + this.initScrollWatch(pev) + + // unlike mouse, need to attach to target, not document + // https://stackoverflow.com/a/45760014 + let targetEl = ev.target as HTMLElement + + if (!this.shouldIgnoreMove) { + targetEl.addEventListener('touchmove', this.handleTouchMove) + } + + targetEl.addEventListener('touchend', this.handleTouchEnd) + targetEl.addEventListener('touchcancel', this.handleTouchEnd) // treat it as a touch end + + // attach a handler to get called when ANY scroll action happens on the page. + // this was impossible to do with normal on/off because 'scroll' doesn't bubble. + // http://stackoverflow.com/a/32954565/96342 + window.addEventListener( + 'scroll', + this.handleTouchScroll, + true, // useCapture + ) + } + } + + handleTouchMove = (ev: TouchEvent) => { + let pev = this.createEventFromTouch(ev) + this.recordCoords(pev) + this.emitter.trigger('pointermove', pev) + } + + handleTouchEnd = (ev: TouchEvent) => { + if (this.isDragging) { // done to guard against touchend followed by touchcancel + let targetEl = ev.target as HTMLElement + + targetEl.removeEventListener('touchmove', this.handleTouchMove) + targetEl.removeEventListener('touchend', this.handleTouchEnd) + targetEl.removeEventListener('touchcancel', this.handleTouchEnd) + window.removeEventListener('scroll', this.handleTouchScroll, true) // useCaptured=true + + this.emitter.trigger('pointerup', this.createEventFromTouch(ev)) + + this.cleanup() // call last so that pointerup has access to props + this.isTouchDragging = false + startIgnoringMouse() + } + } + + handleTouchScroll = () => { + this.wasTouchScroll = true + } + + // can be called by user of this class, to cancel touch-based scrolling for the current drag + cancelTouchScroll() { + if (this.isDragging) { + isWindowTouchMoveCancelled = true + } + } + + // Scrolling that simulates pointermoves + // ---------------------------------------------------------------------------------------------------- + + initScrollWatch(ev: PointerDragEvent) { + if (this.shouldWatchScroll) { + this.recordCoords(ev) + window.addEventListener('scroll', this.handleScroll, true) // useCapture=true + } + } + + recordCoords(ev: PointerDragEvent) { + if (this.shouldWatchScroll) { + this.prevPageX = (ev as any).pageX + this.prevPageY = (ev as any).pageY + this.prevScrollX = window.pageXOffset + this.prevScrollY = window.pageYOffset + } + } + + handleScroll = (ev: UIEvent) => { + if (!this.shouldIgnoreMove) { + let pageX = (window.pageXOffset - this.prevScrollX) + this.prevPageX + let pageY = (window.pageYOffset - this.prevScrollY) + this.prevPageY + + this.emitter.trigger('pointermove', { + origEvent: ev, + isTouch: this.isTouchDragging, + subjectEl: this.subjectEl, + pageX, + pageY, + deltaX: pageX - this.origPageX, + deltaY: pageY - this.origPageY, + } as PointerDragEvent) + } + } + + destroyScrollWatch() { + if (this.shouldWatchScroll) { + window.removeEventListener('scroll', this.handleScroll, true) // useCaptured=true + } + } + + // Event Normalization + // ---------------------------------------------------------------------------------------------------- + + createEventFromMouse(ev: MouseEvent, isFirst?: boolean): PointerDragEvent { + let deltaX = 0 + let deltaY = 0 + + // TODO: repeat code + if (isFirst) { + this.origPageX = ev.pageX + this.origPageY = ev.pageY + } else { + deltaX = ev.pageX - this.origPageX + deltaY = ev.pageY - this.origPageY + } + + return { + origEvent: ev, + isTouch: false, + subjectEl: this.subjectEl, + pageX: ev.pageX, + pageY: ev.pageY, + deltaX, + deltaY, + } + } + + createEventFromTouch(ev: TouchEvent, isFirst?: boolean): PointerDragEvent { + let touches = ev.touches + let pageX + let pageY + let deltaX = 0 + let deltaY = 0 + + // if touch coords available, prefer, + // because FF would give bad ev.pageX ev.pageY + if (touches && touches.length) { + pageX = touches[0].pageX + pageY = touches[0].pageY + } else { + pageX = (ev as any).pageX + pageY = (ev as any).pageY + } + + // TODO: repeat code + if (isFirst) { + this.origPageX = pageX + this.origPageY = pageY + } else { + deltaX = pageX - this.origPageX + deltaY = pageY - this.origPageY + } + + return { + origEvent: ev, + isTouch: true, + subjectEl: this.subjectEl, + pageX, + pageY, + deltaX, + deltaY, + } + } +} + +// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) +function isPrimaryMouseButton(ev: MouseEvent) { + return ev.button === 0 && !ev.ctrlKey +} + +// Ignoring fake mouse events generated by touch +// ---------------------------------------------------------------------------------------------------- + +function startIgnoringMouse() { // can be made non-class function + ignoreMouseDepth += 1 + + setTimeout(() => { + ignoreMouseDepth -= 1 + }, config.touchMouseIgnoreWait) +} + +// We want to attach touchmove as early as possible for Safari +// ---------------------------------------------------------------------------------------------------- + +function listenerCreated() { + listenerCnt += 1 + + if (listenerCnt === 1) { + window.addEventListener('touchmove', onWindowTouchMove, { passive: false }) + } +} + +function listenerDestroyed() { + listenerCnt -= 1 + + if (!listenerCnt) { + window.removeEventListener('touchmove', onWindowTouchMove, { passive: false } as AddEventListenerOptions) + } +} + +function onWindowTouchMove(ev: UIEvent) { + if (isWindowTouchMoveCancelled) { + ev.preventDefault() + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts new file mode 100644 index 000000000..22aba108d --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts @@ -0,0 +1,70 @@ +import { BASE_OPTION_DEFAULTS, PointerDragEvent } from '@fullcalendar/common' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' +import { ExternalElementDragging, DragMetaGenerator } from './ExternalElementDragging' + +export interface ExternalDraggableSettings { + eventData?: DragMetaGenerator + itemSelector?: string + minDistance?: number + longPressDelay?: number + appendTo?: HTMLElement +} + +/* +Makes an element (that is *external* to any calendar) draggable. +Can pass in data that determines how an event will be created when dropped onto a calendar. +Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system. +*/ +export class ExternalDraggable { + dragging: FeaturefulElementDragging + settings: ExternalDraggableSettings + + constructor(el: HTMLElement, settings: ExternalDraggableSettings = {}) { + this.settings = settings + + let dragging = this.dragging = new FeaturefulElementDragging(el) + dragging.touchScrollAllowed = false + + if (settings.itemSelector != null) { + dragging.pointer.selector = settings.itemSelector + } + + if (settings.appendTo != null) { + dragging.mirror.parentNode = settings.appendTo // TODO: write tests + } + + dragging.emitter.on('pointerdown', this.handlePointerDown) + dragging.emitter.on('dragstart', this.handleDragStart) + + new ExternalElementDragging(dragging, settings.eventData) // eslint-disable-line no-new + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { dragging } = this + let { minDistance, longPressDelay } = this.settings + + dragging.minDistance = + minDistance != null ? + minDistance : + (ev.isTouch ? 0 : BASE_OPTION_DEFAULTS.eventDragMinDistance) + + dragging.delay = + ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv + (longPressDelay != null ? longPressDelay : BASE_OPTION_DEFAULTS.longPressDelay) : + 0 + } + + handleDragStart = (ev: PointerDragEvent) => { + if ( + ev.isTouch && + this.dragging.delay && + (ev.subjectEl as HTMLElement).classList.contains('fc-event') + ) { + this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected') + } + } + + destroy() { + this.dragging.destroy() + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts new file mode 100644 index 000000000..95ac7e0c2 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts @@ -0,0 +1,268 @@ +import { + Hit, + interactionSettingsStore, + PointerDragEvent, + parseEventDef, createEventInstance, EventTuple, + createEmptyEventStore, eventTupleToStore, + config, + DateSpan, DatePointApi, + EventInteractionState, + DragMetaInput, DragMeta, parseDragMeta, + EventApi, + elementMatches, + enableCursor, disableCursor, + isInteractionValid, + ElementDragging, + ViewApi, + CalendarContext, + getDefaultEventEnd, + refineEventDef, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging } from '../interactions/HitDragging' +import { buildDatePointApiWithContext } from '../utils' + +export type DragMetaGenerator = DragMetaInput | ((el: HTMLElement) => DragMetaInput) + +export interface ExternalDropApi extends DatePointApi { + draggedEl: HTMLElement + jsEvent: UIEvent + view: ViewApi +} + +/* +Given an already instantiated draggable object for one-or-more elements, +Interprets any dragging as an attempt to drag an events that lives outside +of a calendar onto a calendar. +*/ +export class ExternalElementDragging { + hitDragging: HitDragging + receivingContext: CalendarContext | null = null + droppableEvent: EventTuple | null = null // will exist for all drags, even if create:false + suppliedDragMeta: DragMetaGenerator | null = null + dragMeta: DragMeta | null = null + + constructor(dragging: ElementDragging, suppliedDragMeta?: DragMetaGenerator) { + let hitDragging = this.hitDragging = new HitDragging(dragging, interactionSettingsStore) + hitDragging.requireInitial = false // will start outside of a component + hitDragging.emitter.on('dragstart', this.handleDragStart) + hitDragging.emitter.on('hitupdate', this.handleHitUpdate) + hitDragging.emitter.on('dragend', this.handleDragEnd) + + this.suppliedDragMeta = suppliedDragMeta + } + + handleDragStart = (ev: PointerDragEvent) => { + this.dragMeta = this.buildDragMeta(ev.subjectEl as HTMLElement) + } + + buildDragMeta(subjectEl: HTMLElement) { + if (typeof this.suppliedDragMeta === 'object') { + return parseDragMeta(this.suppliedDragMeta) + } + if (typeof this.suppliedDragMeta === 'function') { + return parseDragMeta(this.suppliedDragMeta(subjectEl)) + } + return getDragMetaFromEl(subjectEl) + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => { + let { dragging } = this.hitDragging + let receivingContext: CalendarContext | null = null + let droppableEvent: EventTuple | null = null + let isInvalid = false + let interaction: EventInteractionState = { + affectedEvents: createEmptyEventStore(), + mutatedEvents: createEmptyEventStore(), + isEvent: this.dragMeta!.create, + } + + if (hit) { + receivingContext = hit.context + + if (this.canDropElOnCalendar(ev.subjectEl as HTMLElement, receivingContext)) { + droppableEvent = computeEventForDateSpan( + hit.dateSpan, + this.dragMeta!, + receivingContext, + ) + + interaction.mutatedEvents = eventTupleToStore(droppableEvent) + isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext) + + if (isInvalid) { + interaction.mutatedEvents = createEmptyEventStore() + droppableEvent = null + } + } + } + + this.displayDrag(receivingContext, interaction) + + // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?) + // TODO: wish we could somehow wait for dispatch to guarantee render + dragging.setMirrorIsVisible( + isFinal || !droppableEvent || !document.querySelector('.fc-event-mirror'), // TODO: turn className into constant + // TODO: somehow query FullCalendars WITHIN shadow-roots for existing event-mirror els + ) + + if (!isInvalid) { + enableCursor() + } else { + disableCursor() + } + + if (!isFinal) { + dragging.setMirrorNeedsRevert(!droppableEvent) + + this.receivingContext = receivingContext + this.droppableEvent = droppableEvent + } + } + + handleDragEnd = (pev: PointerDragEvent) => { + let { receivingContext, droppableEvent } = this + + this.clearDrag() + + if (receivingContext && droppableEvent) { + let finalHit = this.hitDragging.finalHit! + let finalView = finalHit.context.viewApi + let dragMeta = this.dragMeta! + + receivingContext.emitter.trigger('drop', { + ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), + draggedEl: pev.subjectEl as HTMLElement, + jsEvent: pev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: finalView, + }) + + if (dragMeta.create) { + let addingEvents = eventTupleToStore(droppableEvent) + + receivingContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: addingEvents, + }) + + if (pev.isTouch) { + receivingContext.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: droppableEvent.instance.instanceId, + }) + } + + // signal that an external event landed + receivingContext.emitter.trigger('eventReceive', { + event: new EventApi( + receivingContext, + droppableEvent.def, + droppableEvent.instance, + ), + relatedEvents: [], + revert() { + receivingContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: addingEvents, + }) + }, + draggedEl: pev.subjectEl as HTMLElement, + view: finalView, + }) + } + } + + this.receivingContext = null + this.droppableEvent = null + } + + displayDrag(nextContext: CalendarContext | null, state: EventInteractionState) { + let prevContext = this.receivingContext + + if (prevContext && prevContext !== nextContext) { + prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + + if (nextContext) { + nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }) + } + } + + clearDrag() { + if (this.receivingContext) { + this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + } + + canDropElOnCalendar(el: HTMLElement, receivingContext: CalendarContext): boolean { + let dropAccept = receivingContext.options.dropAccept + + if (typeof dropAccept === 'function') { + return dropAccept.call(receivingContext.calendarApi, el) + } + + if (typeof dropAccept === 'string' && dropAccept) { + return Boolean(elementMatches(el, dropAccept)) + } + + return true + } +} + +// Utils for computing event store from the DragMeta +// ---------------------------------------------------------------------------------------------------- + +function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, context: CalendarContext): EventTuple { + let defProps = { ...dragMeta.leftoverProps } + + for (let transform of context.pluginHooks.externalDefTransforms) { + __assign(defProps, transform(dateSpan, dragMeta)) + } + + let { refined, extra } = refineEventDef(defProps, context) + let def = parseEventDef( + refined, + extra, + dragMeta.sourceId, + dateSpan.allDay, + context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd + context, + ) + + let start = dateSpan.range.start + + // only rely on time info if drop zone is all-day, + // otherwise, we already know the time + if (dateSpan.allDay && dragMeta.startTime) { + start = context.dateEnv.add(start, dragMeta.startTime) + } + + let end = dragMeta.duration ? + context.dateEnv.add(start, dragMeta.duration) : + getDefaultEventEnd(dateSpan.allDay, start, context) + + let instance = createEventInstance(def.defId, { start, end }) + + return { def, instance } +} + +// Utils for extracting data from element +// ---------------------------------------------------------------------------------------------------- + +function getDragMetaFromEl(el: HTMLElement): DragMeta { + let str = getEmbeddedElData(el, 'event') + let obj = str ? + JSON.parse(str) : + { create: false } // if no embedded data, assume no event creation + + return parseDragMeta(obj) +} + +config.dataAttrPrefix = '' + +function getEmbeddedElData(el: HTMLElement, name: string): string { + let prefix = config.dataAttrPrefix + let prefixedName = (prefix ? prefix + '-' : '') + name + + return el.getAttribute('data-' + prefixedName) || '' +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts new file mode 100644 index 000000000..ab03cec00 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts @@ -0,0 +1,77 @@ +import { PointerDragEvent, ElementDragging } from '@fullcalendar/common' +import { PointerDragging } from '../dnd/PointerDragging' + +/* +Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements. +The third-party system is responsible for drawing the visuals effects of the drag. +This class simply monitors for pointer movements and fires events. +It also has the ability to hide the moving element (the "mirror") during the drag. +*/ +export class InferredElementDragging extends ElementDragging { + pointer: PointerDragging + shouldIgnoreMove: boolean = false + mirrorSelector: string = '' + currentMirrorEl: HTMLElement | null = null + + constructor(containerEl: HTMLElement) { + super(containerEl) + + let pointer = this.pointer = new PointerDragging(containerEl) + pointer.emitter.on('pointerdown', this.handlePointerDown) + pointer.emitter.on('pointermove', this.handlePointerMove) + pointer.emitter.on('pointerup', this.handlePointerUp) + } + + destroy() { + this.pointer.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + this.emitter.trigger('pointerdown', ev) + + if (!this.shouldIgnoreMove) { + // fire dragstart right away. does not support delay or min-distance + this.emitter.trigger('dragstart', ev) + } + } + + handlePointerMove = (ev: PointerDragEvent) => { + if (!this.shouldIgnoreMove) { + this.emitter.trigger('dragmove', ev) + } + } + + handlePointerUp = (ev: PointerDragEvent) => { + this.emitter.trigger('pointerup', ev) + + if (!this.shouldIgnoreMove) { + // fire dragend right away. does not support a revert animation + this.emitter.trigger('dragend', ev) + } + } + + setIgnoreMove(bool: boolean) { + this.shouldIgnoreMove = bool + } + + setMirrorIsVisible(bool: boolean) { + if (bool) { + // restore a previously hidden element. + // use the reference in case the selector class has already been removed. + if (this.currentMirrorEl) { + this.currentMirrorEl.style.visibility = '' + this.currentMirrorEl = null + } + } else { + let mirrorEl = this.mirrorSelector + // TODO: somehow query FullCalendars WITHIN shadow-roots + ? document.querySelector(this.mirrorSelector) as HTMLElement + : null + + if (mirrorEl) { + this.currentMirrorEl = mirrorEl + mirrorEl.style.visibility = 'hidden' + } + } + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts new file mode 100644 index 000000000..324b22255 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts @@ -0,0 +1,52 @@ +import { ExternalElementDragging, DragMetaGenerator } from './ExternalElementDragging' +import { InferredElementDragging } from './InferredElementDragging' + +export interface ThirdPartyDraggableSettings { + eventData?: DragMetaGenerator + itemSelector?: string + mirrorSelector?: string +} + +/* +Bridges third-party drag-n-drop systems with FullCalendar. +Must be instantiated and destroyed by caller. +*/ +export class ThirdPartyDraggable { + dragging: InferredElementDragging + + constructor( + containerOrSettings?: EventTarget | ThirdPartyDraggableSettings, + settings?: ThirdPartyDraggableSettings, + ) { + let containerEl: EventTarget = document + + if ( + // wish we could just test instanceof EventTarget, but doesn't work in IE11 + containerOrSettings === document || + containerOrSettings instanceof Element + ) { + containerEl = containerOrSettings as EventTarget + settings = settings || {} + } else { + settings = (containerOrSettings || {}) as ThirdPartyDraggableSettings + } + + let dragging = this.dragging = new InferredElementDragging(containerEl as HTMLElement) + + if (typeof settings.itemSelector === 'string') { + dragging.pointer.selector = settings.itemSelector + } else if (containerEl === document) { + dragging.pointer.selector = '[data-event]' + } + + if (typeof settings.mirrorSelector === 'string') { + dragging.mirrorSelector = settings.mirrorSelector + } + + new ExternalElementDragging(dragging, settings.eventData) // eslint-disable-line no-new + } + + destroy() { + this.dragging.destroy() + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts new file mode 100644 index 000000000..06b352de9 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts @@ -0,0 +1,71 @@ +import { + PointerDragEvent, Interaction, InteractionSettings, interactionSettingsToStore, + DatePointApi, + ViewApi, +} from '@fullcalendar/common' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' +import { HitDragging, isHitsEqual } from './HitDragging' +import { buildDatePointApiWithContext } from '../utils' + +export interface DateClickArg extends DatePointApi { + dayEl: HTMLElement + jsEvent: MouseEvent + view: ViewApi +} + +/* +Monitors when the user clicks on a specific date/time of a component. +A pointerdown+pointerup on the same "hit" constitutes a click. +*/ +export class DateClicking extends Interaction { + dragging: FeaturefulElementDragging + hitDragging: HitDragging + + constructor(settings: InteractionSettings) { + super(settings) + + // we DO want to watch pointer moves because otherwise finalHit won't get populated + this.dragging = new FeaturefulElementDragging(settings.el) + this.dragging.autoScroller.isEnabled = false + + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)) + hitDragging.emitter.on('pointerdown', this.handlePointerDown) + hitDragging.emitter.on('dragend', this.handleDragEnd) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (pev: PointerDragEvent) => { + let { dragging } = this + let downEl = pev.origEvent.target as HTMLElement + + // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired + dragging.setIgnoreMove( + !this.component.isValidDateDownEl(downEl), + ) + } + + // won't even fire if moving was ignored + handleDragEnd = (ev: PointerDragEvent) => { + let { component } = this + let { pointer } = this.dragging + + if (!pointer.wasTouchScroll) { + let { initialHit, finalHit } = this.hitDragging + + if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) { + let { context } = component + let arg: DateClickArg = { + ...buildDatePointApiWithContext(initialHit.dateSpan, context), + dayEl: initialHit.dayEl, + jsEvent: ev.origEvent as MouseEvent, + view: context.viewApi || context.calendarApi.view, + } + + context.emitter.trigger('dateClick', arg) + } + } + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts new file mode 100644 index 000000000..fa6b3ff09 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts @@ -0,0 +1,152 @@ +import { + compareNumbers, enableCursor, disableCursor, DateComponent, Hit, + DateSpan, PointerDragEvent, dateSelectionJoinTransformer, + Interaction, InteractionSettings, interactionSettingsToStore, + triggerDateSelect, isDateSelectionValid, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging } from './HitDragging' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' + +/* +Tracks when the user selects a portion of time of a component, +constituted by a drag over date cells, with a possible delay at the beginning of the drag. +*/ +export class DateSelecting extends Interaction { + dragging: FeaturefulElementDragging + hitDragging: HitDragging + dragSelection: DateSpan | null = null + + constructor(settings: InteractionSettings) { + super(settings) + let { component } = settings + let { options } = component.context + + let dragging = this.dragging = new FeaturefulElementDragging(settings.el) + dragging.touchScrollAllowed = false + dragging.minDistance = options.selectMinDistance || 0 + dragging.autoScroller.isEnabled = options.dragScroll + + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)) + hitDragging.emitter.on('pointerdown', this.handlePointerDown) + hitDragging.emitter.on('dragstart', this.handleDragStart) + hitDragging.emitter.on('hitupdate', this.handleHitUpdate) + hitDragging.emitter.on('pointerup', this.handlePointerUp) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { component, dragging } = this + let { options } = component.context + + let canSelect = options.selectable && + component.isValidDateDownEl(ev.origEvent.target as HTMLElement) + + // don't bother to watch expensive moves if component won't do selection + dragging.setIgnoreMove(!canSelect) + + // if touch, require user to hold down + dragging.delay = ev.isTouch ? getComponentTouchDelay(component) : null + } + + handleDragStart = (ev: PointerDragEvent) => { + this.component.context.calendarApi.unselect(ev) // unselect previous selections + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean) => { + let { context } = this.component + let dragSelection: DateSpan | null = null + let isInvalid = false + + if (hit) { + let initialHit = this.hitDragging.initialHit! + let disallowed = hit.componentId === initialHit.componentId + && this.isHitComboAllowed + && !this.isHitComboAllowed(initialHit, hit) + + if (!disallowed) { + dragSelection = joinHitsIntoSelection( + initialHit, + hit, + context.pluginHooks.dateSelectionTransformers, + ) + } + + if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) { + isInvalid = true + dragSelection = null + } + } + + if (dragSelection) { + context.dispatch({ type: 'SELECT_DATES', selection: dragSelection }) + } else if (!isFinal) { // only unselect if moved away while dragging + context.dispatch({ type: 'UNSELECT_DATES' }) + } + + if (!isInvalid) { + enableCursor() + } else { + disableCursor() + } + + if (!isFinal) { + this.dragSelection = dragSelection // only clear if moved away from all hits while dragging + } + } + + handlePointerUp = (pev: PointerDragEvent) => { + if (this.dragSelection) { + // selection is already rendered, so just need to report selection + triggerDateSelect(this.dragSelection, pev, this.component.context) + + this.dragSelection = null + } + } +} + +function getComponentTouchDelay(component: DateComponent): number { + let { options } = component.context + let delay = options.selectLongPressDelay + + if (delay == null) { + delay = options.longPressDelay + } + + return delay +} + +function joinHitsIntoSelection(hit0: Hit, hit1: Hit, dateSelectionTransformers: dateSelectionJoinTransformer[]): DateSpan { + let dateSpan0 = hit0.dateSpan + let dateSpan1 = hit1.dateSpan + let ms = [ + dateSpan0.range.start, + dateSpan0.range.end, + dateSpan1.range.start, + dateSpan1.range.end, + ] + + ms.sort(compareNumbers) + + let props = {} as DateSpan + + for (let transformer of dateSelectionTransformers) { + let res = transformer(hit0, hit1) + + if (res === false) { + return null + } + + if (res) { + __assign(props, res) + } + } + + props.range = { start: ms[0], end: ms[3] } + props.allDay = dateSpan0.allDay + + return props +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts new file mode 100644 index 000000000..9a9ecfc49 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts @@ -0,0 +1,482 @@ +import { + DateComponent, Seg, + PointerDragEvent, Hit, + EventMutation, applyMutationToEventStore, + startOfDay, + elementClosest, + EventStore, getRelevantEvents, createEmptyEventStore, + EventInteractionState, + diffDates, enableCursor, disableCursor, + EventRenderRange, getElSeg, + EventApi, + eventDragMutationMassager, + Interaction, InteractionSettings, interactionSettingsStore, + EventDropTransformers, + CalendarContext, + ViewApi, + EventChangeArg, + buildEventApis, + EventAddArg, + EventRemoveArg, + isInteractionValid, + getElRoot, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging, isHitsEqual } from './HitDragging' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' +import { buildDatePointApiWithContext } from '../utils' + +export type EventDragStopArg = EventDragArg +export type EventDragStartArg = EventDragArg + +export interface EventDragArg { + el: HTMLElement + event: EventApi + jsEvent: MouseEvent + view: ViewApi +} + +export class EventDragging extends Interaction { // TODO: rename to EventSelectingAndDragging + // TODO: test this in IE11 + // QUESTION: why do we need it on the resizable??? + static SELECTOR = '.fc-event-draggable, .fc-event-resizable' + + dragging: FeaturefulElementDragging + hitDragging: HitDragging + + // internal state + subjectEl: HTMLElement | null = null + subjectSeg: Seg | null = null // the seg being selected/dragged + isDragging: boolean = false + eventRange: EventRenderRange | null = null + relevantEvents: EventStore | null = null // the events being dragged + receivingContext: CalendarContext | null = null + validMutation: EventMutation | null = null + mutatedRelevantEvents: EventStore | null = null + + constructor(settings: InteractionSettings) { + super(settings) + let { component } = this + let { options } = component.context + + let dragging = this.dragging = new FeaturefulElementDragging(settings.el) + dragging.pointer.selector = EventDragging.SELECTOR + dragging.touchScrollAllowed = false + dragging.autoScroller.isEnabled = options.dragScroll + + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsStore) + hitDragging.useSubjectCenter = settings.useEventCenter + hitDragging.emitter.on('pointerdown', this.handlePointerDown) + hitDragging.emitter.on('dragstart', this.handleDragStart) + hitDragging.emitter.on('hitupdate', this.handleHitUpdate) + hitDragging.emitter.on('pointerup', this.handlePointerUp) + hitDragging.emitter.on('dragend', this.handleDragEnd) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let origTarget = ev.origEvent.target as HTMLElement + let { component, dragging } = this + let { mirror } = dragging + let { options } = component.context + let initialContext = component.context + this.subjectEl = ev.subjectEl as HTMLElement + let subjectSeg = this.subjectSeg = getElSeg(ev.subjectEl as HTMLElement)! + let eventRange = this.eventRange = subjectSeg.eventRange! + let eventInstanceId = eventRange.instance!.instanceId + + this.relevantEvents = getRelevantEvents( + initialContext.getCurrentData().eventStore, + eventInstanceId, + ) + + dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance + dragging.delay = + // only do a touch delay if touch and this event hasn't been selected yet + (ev.isTouch && eventInstanceId !== component.props.eventSelection) ? + getComponentTouchDelay(component) : + null + + if (options.fixedMirrorParent) { + mirror.parentNode = options.fixedMirrorParent + } else { + mirror.parentNode = elementClosest(origTarget, '.fc') + } + + mirror.revertDuration = options.dragRevertDuration + + let isValid = + component.isValidSegDownEl(origTarget) && + !elementClosest(origTarget, '.fc-event-resizer') // NOT on a resizer + + dragging.setIgnoreMove(!isValid) + + // disable dragging for elements that are resizable (ie, selectable) + // but are not draggable + this.isDragging = isValid && + (ev.subjectEl as HTMLElement).classList.contains('fc-event-draggable') + } + + handleDragStart = (ev: PointerDragEvent) => { + let initialContext = this.component.context + let eventRange = this.eventRange! + let eventInstanceId = eventRange.instance.instanceId + + if (ev.isTouch) { + // need to select a different event? + if (eventInstanceId !== this.component.props.eventSelection) { + initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId }) + } + } else { + // if now using mouse, but was previous touch interaction, clear selected event + initialContext.dispatch({ type: 'UNSELECT_EVENT' }) + } + + if (this.isDragging) { + initialContext.calendarApi.unselect(ev) // unselect *date* selection + initialContext.emitter.trigger('eventDragStart', { + el: this.subjectEl, + event: new EventApi(initialContext, eventRange.def, eventRange.instance), + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: initialContext.viewApi, + } as EventDragStartArg) + } + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean) => { + if (!this.isDragging) { + return + } + + let relevantEvents = this.relevantEvents! + let initialHit = this.hitDragging.initialHit! + let initialContext = this.component.context + + // states based on new hit + let receivingContext: CalendarContext | null = null + let mutation: EventMutation | null = null + let mutatedRelevantEvents: EventStore | null = null + let isInvalid = false + let interaction: EventInteractionState = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + } + + if (hit) { + receivingContext = hit.context + let receivingOptions = receivingContext.options + + if ( + initialContext === receivingContext || + (receivingOptions.editable && receivingOptions.droppable) + ) { + mutation = computeEventMutation(initialHit, hit, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers) + + if (mutation) { + mutatedRelevantEvents = applyMutationToEventStore( + relevantEvents, + receivingContext.getCurrentData().eventUiBases, + mutation, + receivingContext, + ) + interaction.mutatedEvents = mutatedRelevantEvents + + if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) { + isInvalid = true + mutation = null + mutatedRelevantEvents = null + interaction.mutatedEvents = createEmptyEventStore() + } + } + } else { + receivingContext = null + } + } + + this.displayDrag(receivingContext, interaction) + + if (!isInvalid) { + enableCursor() + } else { + disableCursor() + } + + if (!isFinal) { + if ( + initialContext === receivingContext && // TODO: write test for this + isHitsEqual(initialHit, hit) + ) { + mutation = null + } + + this.dragging.setMirrorNeedsRevert(!mutation) + + // render the mirror if no already-rendered mirror + // TODO: wish we could somehow wait for dispatch to guarantee render + this.dragging.setMirrorIsVisible( + !hit || !getElRoot(this.subjectEl).querySelector('.fc-event-mirror'), // TODO: turn className into constant + ) + + // assign states based on new hit + this.receivingContext = receivingContext + this.validMutation = mutation + this.mutatedRelevantEvents = mutatedRelevantEvents + } + } + + handlePointerUp = () => { + if (!this.isDragging) { + this.cleanup() // because handleDragEnd won't fire + } + } + + handleDragEnd = (ev: PointerDragEvent) => { + if (this.isDragging) { + let initialContext = this.component.context + let initialView = initialContext.viewApi + let { receivingContext, validMutation } = this + let eventDef = this.eventRange!.def + let eventInstance = this.eventRange!.instance + let eventApi = new EventApi(initialContext, eventDef, eventInstance) + let relevantEvents = this.relevantEvents! + let mutatedRelevantEvents = this.mutatedRelevantEvents! + let { finalHit } = this.hitDragging + + this.clearDrag() // must happen after revert animation + + initialContext.emitter.trigger('eventDragStop', { + el: this.subjectEl, + event: eventApi, + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: initialView, + } as EventDragStopArg) + + if (validMutation) { + // dropped within same calendar + if (receivingContext === initialContext) { + let updatedEventApi = new EventApi( + initialContext, + mutatedRelevantEvents.defs[eventDef.defId], + eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null, + ) + + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + + let eventChangeArg: EventChangeArg = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, initialContext, eventInstance), + revert() { + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, // the pre-change data + }) + }, + } + + let transformed: ReturnType = {} + for (let transformer of initialContext.getCurrentData().pluginHooks.eventDropTransformers) { + __assign(transformed, transformer(validMutation, initialContext)) + } + + initialContext.emitter.trigger('eventDrop', { + ...eventChangeArg, + ...transformed, + el: ev.subjectEl as HTMLElement, + delta: validMutation.datesDelta!, + jsEvent: ev.origEvent as MouseEvent, // bad + view: initialView, + }) + + initialContext.emitter.trigger('eventChange', eventChangeArg) + + // dropped in different calendar + } else if (receivingContext) { + let eventRemoveArg: EventRemoveArg = { + event: eventApi, + relatedEvents: buildEventApis(relevantEvents, initialContext, eventInstance), + revert() { + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, + }) + }, + } + + initialContext.emitter.trigger('eventLeave', { + ...eventRemoveArg, + draggedEl: ev.subjectEl as HTMLElement, + view: initialView, + }) + + initialContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: relevantEvents, + }) + + initialContext.emitter.trigger('eventRemove', eventRemoveArg) + + let addedEventDef = mutatedRelevantEvents.defs[eventDef.defId] + let addedEventInstance = mutatedRelevantEvents.instances[eventInstance.instanceId] + let addedEventApi = new EventApi(receivingContext, addedEventDef, addedEventInstance) + + receivingContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + + let eventAddArg: EventAddArg = { + event: addedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, receivingContext, addedEventInstance), + revert() { + receivingContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + }, + } + + receivingContext.emitter.trigger('eventAdd', eventAddArg) + + if (ev.isTouch) { + receivingContext.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: eventInstance.instanceId, + }) + } + + receivingContext.emitter.trigger('drop', { + ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), + draggedEl: ev.subjectEl as HTMLElement, + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: finalHit.context.viewApi, + }) + + receivingContext.emitter.trigger('eventReceive', { + ...eventAddArg, + draggedEl: ev.subjectEl as HTMLElement, + view: finalHit.context.viewApi, + }) + } + } else { + initialContext.emitter.trigger('_noEventDrop') + } + } + + this.cleanup() + } + + // render a drag state on the next receivingCalendar + displayDrag(nextContext: CalendarContext | null, state: EventInteractionState) { + let initialContext = this.component.context + let prevContext = this.receivingContext + + // does the previous calendar need to be cleared? + if (prevContext && prevContext !== nextContext) { + // does the initial calendar need to be cleared? + // if so, don't clear all the way. we still need to to hide the affectedEvents + if (prevContext === initialContext) { + prevContext.dispatch({ + type: 'SET_EVENT_DRAG', + state: { + affectedEvents: state.affectedEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + }, + }) + + // completely clear the old calendar if it wasn't the initial + } else { + prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + } + + if (nextContext) { + nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }) + } + } + + clearDrag() { + let initialCalendar = this.component.context + let { receivingContext } = this + + if (receivingContext) { + receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + + // the initial calendar might have an dummy drag state from displayDrag + if (initialCalendar !== receivingContext) { + initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + } + + cleanup() { // reset all internal state + this.subjectSeg = null + this.isDragging = false + this.eventRange = null + this.relevantEvents = null + this.receivingContext = null + this.validMutation = null + this.mutatedRelevantEvents = null + } +} + +function computeEventMutation(hit0: Hit, hit1: Hit, massagers: eventDragMutationMassager[]): EventMutation { + let dateSpan0 = hit0.dateSpan + let dateSpan1 = hit1.dateSpan + let date0 = dateSpan0.range.start + let date1 = dateSpan1.range.start + let standardProps = {} as any + + if (dateSpan0.allDay !== dateSpan1.allDay) { + standardProps.allDay = dateSpan1.allDay + standardProps.hasEnd = hit1.context.options.allDayMaintainDuration + + if (dateSpan1.allDay) { + // means date1 is already start-of-day, + // but date0 needs to be converted + date0 = startOfDay(date0) + } + } + + let delta = diffDates( + date0, date1, + hit0.context.dateEnv, + hit0.componentId === hit1.componentId ? + hit0.largeUnit : + null, + ) + + if (delta.milliseconds) { // has hours/minutes/seconds + standardProps.allDay = false + } + + let mutation: EventMutation = { + datesDelta: delta, + standardProps, + } + + for (let massager of massagers) { + massager(mutation, hit0, hit1) + } + + return mutation +} + +function getComponentTouchDelay(component: DateComponent): number | null { + let { options } = component.context + let delay = options.eventLongPressDelay + + if (delay == null) { + delay = options.longPressDelay + } + + return delay +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts new file mode 100644 index 000000000..013b399ae --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts @@ -0,0 +1,263 @@ +import { + Seg, Hit, + EventMutation, applyMutationToEventStore, + elementClosest, + PointerDragEvent, + EventStore, getRelevantEvents, createEmptyEventStore, + diffDates, enableCursor, disableCursor, + DateRange, + EventApi, + EventRenderRange, getElSeg, + createDuration, + EventInteractionState, + Interaction, InteractionSettings, interactionSettingsToStore, ViewApi, Duration, EventChangeArg, buildEventApis, isInteractionValid, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging, isHitsEqual } from './HitDragging' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' + +export type EventResizeStartArg = EventResizeStartStopArg +export type EventResizeStopArg = EventResizeStartStopArg + +export interface EventResizeStartStopArg { + el: HTMLElement + event: EventApi + jsEvent: MouseEvent + view: ViewApi +} + +export interface EventResizeDoneArg extends EventChangeArg { + el: HTMLElement + startDelta: Duration + endDelta: Duration + jsEvent: MouseEvent + view: ViewApi +} + +export class EventResizing extends Interaction { + dragging: FeaturefulElementDragging + hitDragging: HitDragging + + // internal state + draggingSegEl: HTMLElement | null = null + draggingSeg: Seg | null = null // TODO: rename to resizingSeg? subjectSeg? + eventRange: EventRenderRange | null = null + relevantEvents: EventStore | null = null + validMutation: EventMutation | null = null + mutatedRelevantEvents: EventStore | null = null + + constructor(settings: InteractionSettings) { + super(settings) + let { component } = settings + + let dragging = this.dragging = new FeaturefulElementDragging(settings.el) + dragging.pointer.selector = '.fc-event-resizer' + dragging.touchScrollAllowed = false + dragging.autoScroller.isEnabled = component.context.options.dragScroll + + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)) + hitDragging.emitter.on('pointerdown', this.handlePointerDown) + hitDragging.emitter.on('dragstart', this.handleDragStart) + hitDragging.emitter.on('hitupdate', this.handleHitUpdate) + hitDragging.emitter.on('dragend', this.handleDragEnd) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { component } = this + let segEl = this.querySegEl(ev) + let seg = getElSeg(segEl) + let eventRange = this.eventRange = seg.eventRange! + + this.dragging.minDistance = component.context.options.eventDragMinDistance + + // if touch, need to be working with a selected event + this.dragging.setIgnoreMove( + !this.component.isValidSegDownEl(ev.origEvent.target as HTMLElement) || + (ev.isTouch && this.component.props.eventSelection !== eventRange.instance!.instanceId), + ) + } + + handleDragStart = (ev: PointerDragEvent) => { + let { context } = this.component + let eventRange = this.eventRange! + + this.relevantEvents = getRelevantEvents( + context.getCurrentData().eventStore, + this.eventRange.instance!.instanceId, + ) + + let segEl = this.querySegEl(ev) + this.draggingSegEl = segEl + this.draggingSeg = getElSeg(segEl) + + context.calendarApi.unselect() + context.emitter.trigger('eventResizeStart', { + el: segEl, + event: new EventApi(context, eventRange.def, eventRange.instance), + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: context.viewApi, + } as EventResizeStartArg) + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => { + let { context } = this.component + let relevantEvents = this.relevantEvents! + let initialHit = this.hitDragging.initialHit! + let eventInstance = this.eventRange.instance! + let mutation: EventMutation | null = null + let mutatedRelevantEvents: EventStore | null = null + let isInvalid = false + let interaction: EventInteractionState = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + } + + if (hit) { + let disallowed = hit.componentId === initialHit.componentId + && this.isHitComboAllowed + && !this.isHitComboAllowed(initialHit, hit) + + if (!disallowed) { + mutation = computeMutation( + initialHit, + hit, + (ev.subjectEl as HTMLElement).classList.contains('fc-event-resizer-start'), + eventInstance.range, + ) + } + } + + if (mutation) { + mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context) + interaction.mutatedEvents = mutatedRelevantEvents + + if (!isInteractionValid(interaction, hit.dateProfile, context)) { + isInvalid = true + mutation = null + mutatedRelevantEvents = null + interaction.mutatedEvents = null + } + } + + if (mutatedRelevantEvents) { + context.dispatch({ + type: 'SET_EVENT_RESIZE', + state: interaction, + }) + } else { + context.dispatch({ type: 'UNSET_EVENT_RESIZE' }) + } + + if (!isInvalid) { + enableCursor() + } else { + disableCursor() + } + + if (!isFinal) { + if (mutation && isHitsEqual(initialHit, hit)) { + mutation = null + } + + this.validMutation = mutation + this.mutatedRelevantEvents = mutatedRelevantEvents + } + } + + handleDragEnd = (ev: PointerDragEvent) => { + let { context } = this.component + let eventDef = this.eventRange!.def + let eventInstance = this.eventRange!.instance + let eventApi = new EventApi(context, eventDef, eventInstance) + let relevantEvents = this.relevantEvents! + let mutatedRelevantEvents = this.mutatedRelevantEvents! + + context.emitter.trigger('eventResizeStop', { + el: this.draggingSegEl, + event: eventApi, + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: context.viewApi, + } as EventResizeStopArg) + + if (this.validMutation) { + let updatedEventApi = new EventApi( + context, + mutatedRelevantEvents.defs[eventDef.defId], + eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null, + ) + + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + + let eventChangeArg: EventChangeArg = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance), + revert() { + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, // the pre-change events + }) + }, + } + + context.emitter.trigger('eventResize', { + ...eventChangeArg, + el: this.draggingSegEl, + startDelta: this.validMutation.startDelta || createDuration(0), + endDelta: this.validMutation.endDelta || createDuration(0), + jsEvent: ev.origEvent as MouseEvent, + view: context.viewApi, + }) + + context.emitter.trigger('eventChange', eventChangeArg) + } else { + context.emitter.trigger('_noEventResize') + } + + // reset all internal state + this.draggingSeg = null + this.relevantEvents = null + this.validMutation = null + + // okay to keep eventInstance around. useful to set it in handlePointerDown + } + + querySegEl(ev: PointerDragEvent) { + return elementClosest(ev.subjectEl as HTMLElement, '.fc-event') + } +} + +function computeMutation( + hit0: Hit, + hit1: Hit, + isFromStart: boolean, + instanceRange: DateRange, +): EventMutation | null { + let dateEnv = hit0.context.dateEnv + let date0 = hit0.dateSpan.range.start + let date1 = hit1.dateSpan.range.start + + let delta = diffDates( + date0, date1, + dateEnv, + hit0.largeUnit, + ) + + if (isFromStart) { + if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) { + return { startDelta: delta } + } + } else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) { + return { endDelta: delta } + } + + return null +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts new file mode 100644 index 000000000..d0a329e9c --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts @@ -0,0 +1,220 @@ +import { + Emitter, PointerDragEvent, + isDateSpansEqual, + computeRect, + constrainPoint, intersectRects, getRectCenter, diffPoints, Point, + rangeContainsRange, + Hit, + InteractionSettingsStore, + mapHash, + ElementDragging, +} from '@fullcalendar/common' +import { OffsetTracker } from '../OffsetTracker' + +/* +Tracks movement over multiple droppable areas (aka "hits") +that exist in one or more DateComponents. +Relies on an existing draggable. + +emits: +- pointerdown +- dragstart +- hitchange - fires initially, even if not over a hit +- pointerup +- (hitchange - again, to null, if ended over a hit) +- dragend +*/ +export class HitDragging { + droppableStore: InteractionSettingsStore + dragging: ElementDragging + emitter: Emitter + + // options that can be set by caller + useSubjectCenter: boolean = false + requireInitial: boolean = true // if doesn't start out on a hit, won't emit any events + + // internal state + offsetTrackers: { [componentUid: string]: OffsetTracker } + initialHit: Hit | null = null + movingHit: Hit | null = null + finalHit: Hit | null = null // won't ever be populated if shouldIgnoreMove + coordAdjust?: Point + + constructor(dragging: ElementDragging, droppableStore: InteractionSettingsStore) { + this.droppableStore = droppableStore + + dragging.emitter.on('pointerdown', this.handlePointerDown) + dragging.emitter.on('dragstart', this.handleDragStart) + dragging.emitter.on('dragmove', this.handleDragMove) + dragging.emitter.on('pointerup', this.handlePointerUp) + dragging.emitter.on('dragend', this.handleDragEnd) + + this.dragging = dragging + this.emitter = new Emitter() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { dragging } = this + + this.initialHit = null + this.movingHit = null + this.finalHit = null + + this.prepareHits() + this.processFirstCoord(ev) + + if (this.initialHit || !this.requireInitial) { + dragging.setIgnoreMove(false) + + // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :( + this.emitter.trigger('pointerdown', ev) + } else { + dragging.setIgnoreMove(true) + } + } + + // sets initialHit + // sets coordAdjust + processFirstCoord(ev: PointerDragEvent) { + let origPoint = { left: ev.pageX, top: ev.pageY } + let adjustedPoint = origPoint + let subjectEl = ev.subjectEl + let subjectRect + + if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot + subjectRect = computeRect(subjectEl) + adjustedPoint = constrainPoint(adjustedPoint, subjectRect) + } + + let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top) + if (initialHit) { + if (this.useSubjectCenter && subjectRect) { + let slicedSubjectRect = intersectRects(subjectRect, initialHit.rect) + if (slicedSubjectRect) { + adjustedPoint = getRectCenter(slicedSubjectRect) + } + } + + this.coordAdjust = diffPoints(adjustedPoint, origPoint) + } else { + this.coordAdjust = { left: 0, top: 0 } + } + } + + handleDragStart = (ev: PointerDragEvent) => { + this.emitter.trigger('dragstart', ev) + this.handleMove(ev, true) // force = fire even if initially null + } + + handleDragMove = (ev: PointerDragEvent) => { + this.emitter.trigger('dragmove', ev) + this.handleMove(ev) + } + + handlePointerUp = (ev: PointerDragEvent) => { + this.releaseHits() + this.emitter.trigger('pointerup', ev) + } + + handleDragEnd = (ev: PointerDragEvent) => { + if (this.movingHit) { + this.emitter.trigger('hitupdate', null, true, ev) + } + + this.finalHit = this.movingHit + this.movingHit = null + this.emitter.trigger('dragend', ev) + } + + handleMove(ev: PointerDragEvent, forceHandle?: boolean) { + let hit = this.queryHitForOffset( + ev.pageX + this.coordAdjust!.left, + ev.pageY + this.coordAdjust!.top, + ) + + if (forceHandle || !isHitsEqual(this.movingHit, hit)) { + this.movingHit = hit + this.emitter.trigger('hitupdate', hit, false, ev) + } + } + + prepareHits() { + this.offsetTrackers = mapHash(this.droppableStore, (interactionSettings) => { + interactionSettings.component.prepareHits() + return new OffsetTracker(interactionSettings.el) + }) + } + + releaseHits() { + let { offsetTrackers } = this + + for (let id in offsetTrackers) { + offsetTrackers[id].destroy() + } + + this.offsetTrackers = {} + } + + queryHitForOffset(offsetLeft: number, offsetTop: number): Hit | null { + let { droppableStore, offsetTrackers } = this + let bestHit: Hit | null = null + + for (let id in droppableStore) { + let component = droppableStore[id].component + let offsetTracker = offsetTrackers[id] + + if ( + offsetTracker && // wasn't destroyed mid-drag + offsetTracker.isWithinClipping(offsetLeft, offsetTop) + ) { + let originLeft = offsetTracker.computeLeft() + let originTop = offsetTracker.computeTop() + let positionLeft = offsetLeft - originLeft + let positionTop = offsetTop - originTop + let { origRect } = offsetTracker + let width = origRect.right - origRect.left + let height = origRect.bottom - origRect.top + + if ( + // must be within the element's bounds + positionLeft >= 0 && positionLeft < width && + positionTop >= 0 && positionTop < height + ) { + let hit = component.queryHit(positionLeft, positionTop, width, height) + if ( + hit && ( + // make sure the hit is within activeRange, meaning it's not a dead cell + rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range) + ) && + (!bestHit || hit.layer > bestHit.layer) + ) { + hit.componentId = id + hit.context = component.context + + // TODO: better way to re-orient rectangle + hit.rect.left += originLeft + hit.rect.right += originLeft + hit.rect.top += originTop + hit.rect.bottom += originTop + + bestHit = hit + } + } + } + } + + return bestHit + } +} + +export function isHitsEqual(hit0: Hit | null, hit1: Hit | null): boolean { + if (!hit0 && !hit1) { + return true + } + + if (Boolean(hit0) !== Boolean(hit1)) { + return false + } + + return isDateSpansEqual(hit0!.dateSpan, hit1!.dateSpan) +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts new file mode 100644 index 000000000..23e5b47cb --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts @@ -0,0 +1,77 @@ +import { + DateSelectionApi, + PointerDragEvent, + elementClosest, + CalendarContext, + getEventTargetViaRoot, +} from '@fullcalendar/common' +import { PointerDragging } from '../dnd/PointerDragging' +import { EventDragging } from './EventDragging' + +export class UnselectAuto { + documentPointer: PointerDragging // for unfocusing + isRecentPointerDateSelect = false // wish we could use a selector to detect date selection, but uses hit system + matchesCancel = false + matchesEvent = false + + constructor(private context: CalendarContext) { + let documentPointer = this.documentPointer = new PointerDragging(document) + documentPointer.shouldIgnoreMove = true + documentPointer.shouldWatchScroll = false + documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown) + documentPointer.emitter.on('pointerup', this.onDocumentPointerUp) + + /* + TODO: better way to know about whether there was a selection with the pointer + */ + context.emitter.on('select', this.onSelect) + } + + destroy() { + this.context.emitter.off('select', this.onSelect) + this.documentPointer.destroy() + } + + onSelect = (selectInfo: DateSelectionApi) => { + if (selectInfo.jsEvent) { + this.isRecentPointerDateSelect = true + } + } + + onDocumentPointerDown = (pev: PointerDragEvent) => { + let unselectCancel = this.context.options.unselectCancel + let downEl = getEventTargetViaRoot(pev.origEvent) as HTMLElement + + this.matchesCancel = !!elementClosest(downEl, unselectCancel) + this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR) // interaction started on an event? + } + + onDocumentPointerUp = (pev: PointerDragEvent) => { + let { context } = this + let { documentPointer } = this + let calendarState = context.getCurrentData() + + // touch-scrolling should never unfocus any type of selection + if (!documentPointer.wasTouchScroll) { + if ( + calendarState.dateSelection && // an existing date selection? + !this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp? + ) { + let unselectAuto = context.options.unselectAuto + + if (unselectAuto && (!unselectAuto || !this.matchesCancel)) { + context.calendarApi.unselect(pev) + } + } + + if ( + calendarState.eventSelection && // an existing event selected? + !this.matchesEvent // interaction DIDN'T start on an event + ) { + context.dispatch({ type: 'UNSELECT_EVENT' }) + } + } + + this.isRecentPointerDateSelect = false + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts b/apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts new file mode 100644 index 000000000..0af579774 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts @@ -0,0 +1,7 @@ +import { globalPlugins } from '@fullcalendar/common' +import plugin from './main' + +globalPlugins.push(plugin) + +export default plugin +export * from './main' diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/main.ts b/apps/schoolCalendar/fullcalendar/interaction/src/main.ts new file mode 100644 index 000000000..f57049ca3 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/main.ts @@ -0,0 +1,23 @@ +import { createPlugin } from '@fullcalendar/common' +import { DateClicking } from './interactions/DateClicking' +import { DateSelecting } from './interactions/DateSelecting' +import { EventDragging } from './interactions/EventDragging' +import { EventResizing } from './interactions/EventResizing' +import { UnselectAuto } from './interactions/UnselectAuto' +import { FeaturefulElementDragging } from './dnd/FeaturefulElementDragging' +import { OPTION_REFINERS, LISTENER_REFINERS } from './options' +import './options-declare' + +export default createPlugin({ + componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing], + calendarInteractions: [UnselectAuto], + elementDraggingImpl: FeaturefulElementDragging, + optionRefiners: OPTION_REFINERS, + listenerRefiners: LISTENER_REFINERS, +}) + +export * from './api-type-deps' +export { FeaturefulElementDragging } +export { PointerDragging } from './dnd/PointerDragging' +export { ExternalDraggable as Draggable } from './interactions-external/ExternalDraggable' +export { ThirdPartyDraggable } from './interactions-external/ThirdPartyDraggable' diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts b/apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts new file mode 100644 index 000000000..9cbc5a4a8 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts @@ -0,0 +1,9 @@ +import { OPTION_REFINERS, LISTENER_REFINERS } from './options' + +type ExtraOptionRefiners = typeof OPTION_REFINERS +type ExtraListenerRefiners = typeof LISTENER_REFINERS + +declare module '@fullcalendar/common' { + interface BaseOptionRefiners extends ExtraOptionRefiners {} + interface CalendarListenerRefiners extends ExtraListenerRefiners {} +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/options.ts b/apps/schoolCalendar/fullcalendar/interaction/src/options.ts new file mode 100644 index 000000000..a11ce5399 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/options.ts @@ -0,0 +1,26 @@ +import { identity, Identity, EventDropArg } from '@fullcalendar/common' + +// public +import { + DateClickArg, + EventDragStartArg, EventDragStopArg, + EventResizeStartArg, EventResizeStopArg, EventResizeDoneArg, + DropArg, EventReceiveArg, EventLeaveArg, +} from './api-type-deps' + +export const OPTION_REFINERS = { + fixedMirrorParent: identity as Identity, +} + +export const LISTENER_REFINERS = { + dateClick: identity as Identity<(arg: DateClickArg) => void>, + eventDragStart: identity as Identity<(arg: EventDragStartArg) => void>, + eventDragStop: identity as Identity<(arg: EventDragStopArg) => void>, + eventDrop: identity as Identity<(arg: EventDropArg) => void>, + eventResizeStart: identity as Identity<(arg: EventResizeStartArg) => void>, + eventResizeStop: identity as Identity<(arg: EventResizeStopArg) => void>, + eventResize: identity as Identity<(arg: EventResizeDoneArg) => void>, + drop: identity as Identity<(arg: DropArg) => void>, + eventReceive: identity as Identity<(arg: EventReceiveArg) => void>, + eventLeave: identity as Identity<(arg: EventLeaveArg) => void>, +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/utils.ts b/apps/schoolCalendar/fullcalendar/interaction/src/utils.ts new file mode 100644 index 000000000..056a0040d --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/src/utils.ts @@ -0,0 +1,38 @@ +import { DateSpan, CalendarContext, DatePointApi, DateEnv, ViewApi, EventApi } from '@fullcalendar/common' +import { __assign } from 'tslib' + +export interface DropArg extends DatePointApi { + draggedEl: HTMLElement + jsEvent: MouseEvent + view: ViewApi +} + +export type EventReceiveArg = EventReceiveLeaveArg +export type EventLeaveArg = EventReceiveLeaveArg +export interface EventReceiveLeaveArg { // will this become public? + draggedEl: HTMLElement + event: EventApi + relatedEvents: EventApi[] + revert: () => void + view: ViewApi +} + +export function buildDatePointApiWithContext(dateSpan: DateSpan, context: CalendarContext) { + let props = {} as DatePointApi + + for (let transform of context.pluginHooks.datePointTransforms) { + __assign(props, transform(dateSpan, context)) + } + + __assign(props, buildDatePointApi(dateSpan, context.dateEnv)) + + return props +} + +export function buildDatePointApi(span: DateSpan, dateEnv: DateEnv): DatePointApi { + return { + date: dateEnv.toDate(span.range.start), + dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }), + allDay: span.allDay, + } +} diff --git a/apps/schoolCalendar/fullcalendar/interaction/tsconfig.json b/apps/schoolCalendar/fullcalendar/interaction/tsconfig.json new file mode 100644 index 000000000..6172b1a45 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/interaction/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "rootDir": "src", + "outDir": "tsc" + }, + "include": [ + "src/**/*" + ], + "references": [ + { "path": "../common" } + ] +} diff --git a/apps/schoolCalendar/fullcalendar/locales-all.js b/apps/schoolCalendar/fullcalendar/locales-all.js new file mode 100644 index 000000000..f8be47ef2 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/locales-all.js @@ -0,0 +1,1622 @@ +[].push.apply(FullCalendar.globalLocales, function () { + 'use strict'; + + var l0 = { + code: 'af', + week: { + dow: 1, // Maandag is die eerste dag van die week. + doy: 4, // Die week wat die 4de Januarie bevat is die eerste week van die jaar. + }, + buttonText: { + prev: 'Vorige', + next: 'Volgende', + today: 'Vandag', + year: 'Jaar', + month: 'Maand', + week: 'Week', + day: 'Dag', + list: 'Agenda', + }, + allDayText: 'Heeldag', + moreLinkText: 'Addisionele', + noEventsText: 'Daar is geen gebeurtenisse nie', + }; + + var l1 = { + code: 'ar-dz', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 4, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l2 = { + code: 'ar-kw', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l3 = { + code: 'ar-ly', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l4 = { + code: 'ar-ma', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l5 = { + code: 'ar-sa', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l6 = { + code: 'ar-tn', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l7 = { + code: 'ar', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l8 = { + code: 'az', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Əvvəl', + next: 'Sonra', + today: 'Bu Gün', + month: 'Ay', + week: 'Həftə', + day: 'Gün', + list: 'Gündəm', + }, + weekText: 'Həftə', + allDayText: 'Bütün Gün', + moreLinkText: function(n) { + return '+ daha çox ' + n + }, + noEventsText: 'Göstərmək üçün hadisə yoxdur', + }; + + var l9 = { + code: 'bg', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'назад', + next: 'напред', + today: 'днес', + month: 'Месец', + week: 'Седмица', + day: 'Ден', + list: 'График', + }, + allDayText: 'Цял ден', + moreLinkText: function(n) { + return '+още ' + n + }, + noEventsText: 'Няма събития за показване', + }; + + var l10 = { + code: 'bn', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'পেছনে', + next: 'সামনে', + today: 'আজ', + month: 'মাস', + week: 'সপ্তাহ', + day: 'দিন', + list: 'তালিকা', + }, + weekText: 'সপ্তাহ', + allDayText: 'সারাদিন', + moreLinkText: function(n) { + return '+অন্যান্য ' + n + }, + noEventsText: 'কোনো ইভেন্ট নেই', + }; + + var l11 = { + code: 'bs', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prošli', + next: 'Sljedeći', + today: 'Danas', + month: 'Mjesec', + week: 'Sedmica', + day: 'Dan', + list: 'Raspored', + }, + weekText: 'Sed', + allDayText: 'Cijeli dan', + moreLinkText: function(n) { + return '+ još ' + n + }, + noEventsText: 'Nema događaja za prikazivanje', + }; + + var l12 = { + code: 'ca', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Anterior', + next: 'Següent', + today: 'Avui', + month: 'Mes', + week: 'Setmana', + day: 'Dia', + list: 'Agenda', + }, + weekText: 'Set', + allDayText: 'Tot el dia', + moreLinkText: 'més', + noEventsText: 'No hi ha esdeveniments per mostrar', + }; + + var l13 = { + code: 'cs', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Dříve', + next: 'Později', + today: 'Nyní', + month: 'Měsíc', + week: 'Týden', + day: 'Den', + list: 'Agenda', + }, + weekText: 'Týd', + allDayText: 'Celý den', + moreLinkText: function(n) { + return '+další: ' + n + }, + noEventsText: 'Žádné akce k zobrazení', + }; + + var l14 = { + code: 'cy', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Blaenorol', + next: 'Nesaf', + today: 'Heddiw', + year: 'Blwyddyn', + month: 'Mis', + week: 'Wythnos', + day: 'Dydd', + list: 'Rhestr', + }, + weekText: 'Wythnos', + allDayText: 'Trwy\'r dydd', + moreLinkText: 'Mwy', + noEventsText: 'Dim digwyddiadau', + }; + + var l15 = { + code: 'da', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Forrige', + next: 'Næste', + today: 'I dag', + month: 'Måned', + week: 'Uge', + day: 'Dag', + list: 'Agenda', + }, + weekText: 'Uge', + allDayText: 'Hele dagen', + moreLinkText: 'flere', + noEventsText: 'Ingen arrangementer at vise', + }; + + var l16 = { + code: 'de-at', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Zurück', + next: 'Vor', + today: 'Heute', + year: 'Jahr', + month: 'Monat', + week: 'Woche', + day: 'Tag', + list: 'Terminübersicht', + }, + weekText: 'KW', + allDayText: 'Ganztägig', + moreLinkText: function(n) { + return '+ weitere ' + n + }, + noEventsText: 'Keine Ereignisse anzuzeigen', + }; + + var l17 = { + code: 'de', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Zurück', + next: 'Vor', + today: 'Heute', + year: 'Jahr', + month: 'Monat', + week: 'Woche', + day: 'Tag', + list: 'Terminübersicht', + }, + weekText: 'KW', + allDayText: 'Ganztägig', + moreLinkText: function(n) { + return '+ weitere ' + n + }, + noEventsText: 'Keine Ereignisse anzuzeigen', + }; + + var l18 = { + code: 'el', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4st is the first week of the year. + }, + buttonText: { + prev: 'Προηγούμενος', + next: 'Επόμενος', + today: 'Σήμερα', + month: 'Μήνας', + week: 'Εβδομάδα', + day: 'Ημέρα', + list: 'Ατζέντα', + }, + weekText: 'Εβδ', + allDayText: 'Ολοήμερο', + moreLinkText: 'περισσότερα', + noEventsText: 'Δεν υπάρχουν γεγονότα προς εμφάνιση', + }; + + var l19 = { + code: 'en-au', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + }; + + var l20 = { + code: 'en-gb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + }; + + var l21 = { + code: 'en-nz', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + }; + + var l22 = { + code: 'eo', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Antaŭa', + next: 'Sekva', + today: 'Hodiaŭ', + month: 'Monato', + week: 'Semajno', + day: 'Tago', + list: 'Tagordo', + }, + weekText: 'Sm', + allDayText: 'Tuta tago', + moreLinkText: 'pli', + noEventsText: 'Neniuj eventoj por montri', + }; + + var l23 = { + code: 'es', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Ant', + next: 'Sig', + today: 'Hoy', + month: 'Mes', + week: 'Semana', + day: 'Día', + list: 'Agenda', + }, + weekText: 'Sm', + allDayText: 'Todo el día', + moreLinkText: 'más', + noEventsText: 'No hay eventos para mostrar', + }; + + var l24 = { + code: 'es', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Ant', + next: 'Sig', + today: 'Hoy', + month: 'Mes', + week: 'Semana', + day: 'Día', + list: 'Agenda', + }, + weekText: 'Sm', + allDayText: 'Todo el día', + moreLinkText: 'más', + noEventsText: 'No hay eventos para mostrar', + }; + + var l25 = { + code: 'et', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Eelnev', + next: 'Järgnev', + today: 'Täna', + month: 'Kuu', + week: 'Nädal', + day: 'Päev', + list: 'Päevakord', + }, + weekText: 'näd', + allDayText: 'Kogu päev', + moreLinkText: function(n) { + return '+ veel ' + n + }, + noEventsText: 'Kuvamiseks puuduvad sündmused', + }; + + var l26 = { + code: 'eu', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Aur', + next: 'Hur', + today: 'Gaur', + month: 'Hilabetea', + week: 'Astea', + day: 'Eguna', + list: 'Agenda', + }, + weekText: 'As', + allDayText: 'Egun osoa', + moreLinkText: 'gehiago', + noEventsText: 'Ez dago ekitaldirik erakusteko', + }; + + var l27 = { + code: 'fa', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'قبلی', + next: 'بعدی', + today: 'امروز', + month: 'ماه', + week: 'هفته', + day: 'روز', + list: 'برنامه', + }, + weekText: 'هف', + allDayText: 'تمام روز', + moreLinkText: function(n) { + return 'بیش از ' + n + }, + noEventsText: 'هیچ رویدادی به نمایش', + }; + + var l28 = { + code: 'fi', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Edellinen', + next: 'Seuraava', + today: 'Tänään', + month: 'Kuukausi', + week: 'Viikko', + day: 'Päivä', + list: 'Tapahtumat', + }, + weekText: 'Vk', + allDayText: 'Koko päivä', + moreLinkText: 'lisää', + noEventsText: 'Ei näytettäviä tapahtumia', + }; + + var l29 = { + code: 'fr', + buttonText: { + prev: 'Précédent', + next: 'Suivant', + today: "Aujourd'hui", + year: 'Année', + month: 'Mois', + week: 'Semaine', + day: 'Jour', + list: 'Mon planning', + }, + weekText: 'Sem.', + allDayText: 'Toute la journée', + moreLinkText: 'en plus', + noEventsText: 'Aucun événement à afficher', + }; + + var l30 = { + code: 'fr-ch', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Précédent', + next: 'Suivant', + today: 'Courant', + year: 'Année', + month: 'Mois', + week: 'Semaine', + day: 'Jour', + list: 'Mon planning', + }, + weekText: 'Sm', + allDayText: 'Toute la journée', + moreLinkText: 'en plus', + noEventsText: 'Aucun événement à afficher', + }; + + var l31 = { + code: 'fr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Précédent', + next: 'Suivant', + today: "Aujourd'hui", + year: 'Année', + month: 'Mois', + week: 'Semaine', + day: 'Jour', + list: 'Planning', + }, + weekText: 'Sem.', + allDayText: 'Toute la journée', + moreLinkText: 'en plus', + noEventsText: 'Aucun événement à afficher', + }; + + var l32 = { + code: 'gl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Ant', + next: 'Seg', + today: 'Hoxe', + month: 'Mes', + week: 'Semana', + day: 'Día', + list: 'Axenda', + }, + weekText: 'Sm', + allDayText: 'Todo o día', + moreLinkText: 'máis', + noEventsText: 'Non hai eventos para amosar', + }; + + var l33 = { + code: 'he', + direction: 'rtl', + buttonText: { + prev: 'הקודם', + next: 'הבא', + today: 'היום', + month: 'חודש', + week: 'שבוע', + day: 'יום', + list: 'סדר יום', + }, + allDayText: 'כל היום', + moreLinkText: 'אחר', + noEventsText: 'אין אירועים להצגה', + weekText: 'שבוע', + }; + + var l34 = { + code: 'hi', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'पिछला', + next: 'अगला', + today: 'आज', + month: 'महीना', + week: 'सप्ताह', + day: 'दिन', + list: 'कार्यसूची', + }, + weekText: 'हफ्ता', + allDayText: 'सभी दिन', + moreLinkText: function(n) { + return '+अधिक ' + n + }, + noEventsText: 'कोई घटनाओं को प्रदर्शित करने के लिए', + }; + + var l35 = { + code: 'hr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prijašnji', + next: 'Sljedeći', + today: 'Danas', + month: 'Mjesec', + week: 'Tjedan', + day: 'Dan', + list: 'Raspored', + }, + weekText: 'Tje', + allDayText: 'Cijeli dan', + moreLinkText: function(n) { + return '+ još ' + n + }, + noEventsText: 'Nema događaja za prikaz', + }; + + var l36 = { + code: 'hu', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'vissza', + next: 'előre', + today: 'ma', + month: 'Hónap', + week: 'Hét', + day: 'Nap', + list: 'Lista', + }, + weekText: 'Hét', + allDayText: 'Egész nap', + moreLinkText: 'további', + noEventsText: 'Nincs megjeleníthető esemény', + }; + + var l37 = { + code: 'hy-am', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Նախորդ', + next: 'Հաջորդ', + today: 'Այսօր', + month: 'Ամիս', + week: 'Շաբաթ', + day: 'Օր', + list: 'Օրվա ցուցակ', + }, + weekText: 'Շաբ', + allDayText: 'Ամբողջ օր', + moreLinkText: function(n) { + return '+ ևս ' + n + }, + noEventsText: 'Բացակայում է իրադարձությունը ցուցադրելու', + }; + + var l38 = { + code: 'id', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'mundur', + next: 'maju', + today: 'hari ini', + month: 'Bulan', + week: 'Minggu', + day: 'Hari', + list: 'Agenda', + }, + weekText: 'Mg', + allDayText: 'Sehari penuh', + moreLinkText: 'lebih', + noEventsText: 'Tidak ada acara untuk ditampilkan', + }; + + var l39 = { + code: 'is', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Fyrri', + next: 'Næsti', + today: 'Í dag', + month: 'Mánuður', + week: 'Vika', + day: 'Dagur', + list: 'Dagskrá', + }, + weekText: 'Vika', + allDayText: 'Allan daginn', + moreLinkText: 'meira', + noEventsText: 'Engir viðburðir til að sýna', + }; + + var l40 = { + code: 'it', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Prec', + next: 'Succ', + today: 'Oggi', + month: 'Mese', + week: 'Settimana', + day: 'Giorno', + list: 'Agenda', + }, + weekText: 'Sm', + allDayText: 'Tutto il giorno', + moreLinkText: function(n) { + return '+altri ' + n + }, + noEventsText: 'Non ci sono eventi da visualizzare', + }; + + var l41 = { + code: 'ja', + buttonText: { + prev: '前', + next: '次', + today: '今日', + month: '月', + week: '週', + day: '日', + list: '予定リスト', + }, + weekText: '週', + allDayText: '終日', + moreLinkText: function(n) { + return '他 ' + n + ' 件' + }, + noEventsText: '表示する予定はありません', + }; + + var l42 = { + code: 'ka', + week: { + dow: 1, + doy: 7, + }, + buttonText: { + prev: 'წინა', + next: 'შემდეგი', + today: 'დღეს', + month: 'თვე', + week: 'კვირა', + day: 'დღე', + list: 'დღის წესრიგი', + }, + weekText: 'კვ', + allDayText: 'მთელი დღე', + moreLinkText: function(n) { + return '+ კიდევ ' + n + }, + noEventsText: 'ღონისძიებები არ არის', + }; + + var l43 = { + code: 'kk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Алдыңғы', + next: 'Келесі', + today: 'Бүгін', + month: 'Ай', + week: 'Апта', + day: 'Күн', + list: 'Күн тәртібі', + }, + weekText: 'Не', + allDayText: 'Күні бойы', + moreLinkText: function(n) { + return '+ тағы ' + n + }, + noEventsText: 'Көрсету үшін оқиғалар жоқ', + }; + + var l44 = { + code: 'km', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'មុន', + next: 'បន្ទាប់', + today: 'ថ្ងៃនេះ', + year: 'ឆ្នាំ', + month: 'ខែ', + week: 'សប្តាហ៍', + day: 'ថ្ងៃ', + list: 'បញ្ជី', + }, + weekText: 'សប្តាហ៍', + allDayText: 'ពេញមួយថ្ងៃ', + moreLinkText: 'ច្រើនទៀត', + noEventsText: 'គ្មានព្រឹត្តិការណ៍ត្រូវបង្ហាញ', + }; + + var l45 = { + code: 'ko', + buttonText: { + prev: '이전달', + next: '다음달', + today: '오늘', + month: '월', + week: '주', + day: '일', + list: '일정목록', + }, + weekText: '주', + allDayText: '종일', + moreLinkText: '개', + noEventsText: '일정이 없습니다', + }; + + var l46 = { + code: 'ku', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'پێشتر', + next: 'دواتر', + today: 'ئەمڕو', + month: 'مانگ', + week: 'هەفتە', + day: 'ڕۆژ', + list: 'بەرنامە', + }, + weekText: 'هەفتە', + allDayText: 'هەموو ڕۆژەکە', + moreLinkText: 'زیاتر', + noEventsText: 'هیچ ڕووداوێك نیە', + }; + + var l47 = { + code: 'lb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Zréck', + next: 'Weider', + today: 'Haut', + month: 'Mount', + week: 'Woch', + day: 'Dag', + list: 'Terminiwwersiicht', + }, + weekText: 'W', + allDayText: 'Ganzen Dag', + moreLinkText: 'méi', + noEventsText: 'Nee Evenementer ze affichéieren', + }; + + var l48 = { + code: 'lt', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Atgal', + next: 'Pirmyn', + today: 'Šiandien', + month: 'Mėnuo', + week: 'Savaitė', + day: 'Diena', + list: 'Darbotvarkė', + }, + weekText: 'SAV', + allDayText: 'Visą dieną', + moreLinkText: 'daugiau', + noEventsText: 'Nėra įvykių rodyti', + }; + + var l49 = { + code: 'lv', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Iepr.', + next: 'Nāk.', + today: 'Šodien', + month: 'Mēnesis', + week: 'Nedēļa', + day: 'Diena', + list: 'Dienas kārtība', + }, + weekText: 'Ned.', + allDayText: 'Visu dienu', + moreLinkText: function(n) { + return '+vēl ' + n + }, + noEventsText: 'Nav notikumu', + }; + + var l50 = { + code: 'mk', + buttonText: { + prev: 'претходно', + next: 'следно', + today: 'Денес', + month: 'Месец', + week: 'Недела', + day: 'Ден', + list: 'График', + }, + weekText: 'Сед', + allDayText: 'Цел ден', + moreLinkText: function(n) { + return '+повеќе ' + n + }, + noEventsText: 'Нема настани за прикажување', + }; + + var l51 = { + code: 'ms', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Sebelum', + next: 'Selepas', + today: 'hari ini', + month: 'Bulan', + week: 'Minggu', + day: 'Hari', + list: 'Agenda', + }, + weekText: 'Mg', + allDayText: 'Sepanjang hari', + moreLinkText: function(n) { + return 'masih ada ' + n + ' acara' + }, + noEventsText: 'Tiada peristiwa untuk dipaparkan', + }; + + var l52 = { + code: 'nb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Forrige', + next: 'Neste', + today: 'I dag', + month: 'Måned', + week: 'Uke', + day: 'Dag', + list: 'Agenda', + }, + weekText: 'Uke', + allDayText: 'Hele dagen', + moreLinkText: 'til', + noEventsText: 'Ingen hendelser å vise', + }; + + var l53 = { + code: 'ne', // code for nepal + week: { + dow: 7, // Sunday is the first day of the week. + doy: 1, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'अघिल्लो', + next: 'अर्को', + today: 'आज', + month: 'महिना', + week: 'हप्ता', + day: 'दिन', + list: 'सूची', + }, + weekText: 'हप्ता', + allDayText: 'दिनभरि', + moreLinkText: 'थप लिंक', + noEventsText: 'देखाउनको लागि कुनै घटनाहरू छैनन्', + }; + + var l54 = { + code: 'nl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Vorige', + next: 'Volgende', + today: 'Vandaag', + year: 'Jaar', + month: 'Maand', + week: 'Week', + day: 'Dag', + list: 'Agenda', + }, + allDayText: 'Hele dag', + moreLinkText: 'extra', + noEventsText: 'Geen evenementen om te laten zien', + }; + + var l55 = { + code: 'nn', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Førre', + next: 'Neste', + today: 'I dag', + month: 'Månad', + week: 'Veke', + day: 'Dag', + list: 'Agenda', + }, + weekText: 'Veke', + allDayText: 'Heile dagen', + moreLinkText: 'til', + noEventsText: 'Ingen hendelser å vise', + }; + + var l56 = { + code: 'pl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Poprzedni', + next: 'Następny', + today: 'Dziś', + month: 'Miesiąc', + week: 'Tydzień', + day: 'Dzień', + list: 'Plan dnia', + }, + weekText: 'Tydz', + allDayText: 'Cały dzień', + moreLinkText: 'więcej', + noEventsText: 'Brak wydarzeń do wyświetlenia', + }; + + var l57 = { + code: 'pt-br', + buttonText: { + prev: 'Anterior', + next: 'Próximo', + today: 'Hoje', + month: 'Mês', + week: 'Semana', + day: 'Dia', + list: 'Lista', + }, + weekText: 'Sm', + allDayText: 'dia inteiro', + moreLinkText: function(n) { + return 'mais +' + n + }, + noEventsText: 'Não há eventos para mostrar', + }; + + var l58 = { + code: 'pt', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Anterior', + next: 'Seguinte', + today: 'Hoje', + month: 'Mês', + week: 'Semana', + day: 'Dia', + list: 'Agenda', + }, + weekText: 'Sem', + allDayText: 'Todo o dia', + moreLinkText: 'mais', + noEventsText: 'Não há eventos para mostrar', + }; + + var l59 = { + code: 'ro', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'precedentă', + next: 'următoare', + today: 'Azi', + month: 'Lună', + week: 'Săptămână', + day: 'Zi', + list: 'Agendă', + }, + weekText: 'Săpt', + allDayText: 'Toată ziua', + moreLinkText: function(n) { + return '+alte ' + n + }, + noEventsText: 'Nu există evenimente de afișat', + }; + + var l60 = { + code: 'ru', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Пред', + next: 'След', + today: 'Сегодня', + month: 'Месяц', + week: 'Неделя', + day: 'День', + list: 'Повестка дня', + }, + weekText: 'Нед', + allDayText: 'Весь день', + moreLinkText: function(n) { + return '+ ещё ' + n + }, + noEventsText: 'Нет событий для отображения', + }; + + var l61 = { + code: 'sk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Predchádzajúci', + next: 'Nasledujúci', + today: 'Dnes', + month: 'Mesiac', + week: 'Týždeň', + day: 'Deň', + list: 'Rozvrh', + }, + weekText: 'Ty', + allDayText: 'Celý deň', + moreLinkText: function(n) { + return '+ďalšie: ' + n + }, + noEventsText: 'Žiadne akcie na zobrazenie', + }; + + var l62 = { + code: 'sl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prejšnji', + next: 'Naslednji', + today: 'Trenutni', + month: 'Mesec', + week: 'Teden', + day: 'Dan', + list: 'Dnevni red', + }, + weekText: 'Teden', + allDayText: 'Ves dan', + moreLinkText: 'več', + noEventsText: 'Ni dogodkov za prikaz', + }; + + var l63 = { + code: 'sm', + buttonText: { + prev: 'Talu ai', + next: 'Mulimuli atu', + today: 'Aso nei', + month: 'Masina', + week: 'Vaiaso', + day: 'Aso', + list: 'Faasologa', + }, + weekText: 'Vaiaso', + allDayText: 'Aso atoa', + moreLinkText: 'sili atu', + noEventsText: 'Leai ni mea na tutupu', + }; + + var l64 = { + code: 'sq', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'mbrapa', + next: 'Përpara', + today: 'sot', + month: 'Muaj', + week: 'Javë', + day: 'Ditë', + list: 'Listë', + }, + weekText: 'Ja', + allDayText: 'Gjithë ditën', + moreLinkText: function(n) { + return '+më tepër ' + n + }, + noEventsText: 'Nuk ka evente për të shfaqur', + }; + + var l65 = { + code: 'sr-cyrl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Претходна', + next: 'следећи', + today: 'Данас', + month: 'Месец', + week: 'Недеља', + day: 'Дан', + list: 'Планер', + }, + weekText: 'Сед', + allDayText: 'Цео дан', + moreLinkText: function(n) { + return '+ још ' + n + }, + noEventsText: 'Нема догађаја за приказ', + }; + + var l66 = { + code: 'sr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prethodna', + next: 'Sledeći', + today: 'Danas', + month: 'Mеsеc', + week: 'Nеdеlja', + day: 'Dan', + list: 'Planеr', + }, + weekText: 'Sed', + allDayText: 'Cеo dan', + moreLinkText: function(n) { + return '+ još ' + n + }, + noEventsText: 'Nеma događaja za prikaz', + }; + + var l67 = { + code: 'sv', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Förra', + next: 'Nästa', + today: 'Idag', + month: 'Månad', + week: 'Vecka', + day: 'Dag', + list: 'Program', + }, + weekText: 'v.', + allDayText: 'Heldag', + moreLinkText: 'till', + noEventsText: 'Inga händelser att visa', + }; + + var l68 = { + code: 'ta-in', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'முந்தைய', + next: 'அடுத்தது', + today: 'இன்று', + month: 'மாதம்', + week: 'வாரம்', + day: 'நாள்', + list: 'தினசரி அட்டவணை', + }, + weekText: 'வாரம்', + allDayText: 'நாள் முழுவதும்', + moreLinkText: function(n) { + return '+ மேலும் ' + n + }, + noEventsText: 'காண்பிக்க நிகழ்வுகள் இல்லை', + }; + + var l69 = { + code: 'th', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'ก่อนหน้า', + next: 'ถัดไป', + prevYear: 'ปีก่อนหน้า', + nextYear: 'ปีถัดไป', + year: 'ปี', + today: 'วันนี้', + month: 'เดือน', + week: 'สัปดาห์', + day: 'วัน', + list: 'กำหนดการ', + }, + weekText: 'สัปดาห์', + allDayText: 'ตลอดวัน', + moreLinkText: 'เพิ่มเติม', + noEventsText: 'ไม่มีกิจกรรมที่จะแสดง', + }; + + var l70 = { + code: 'tr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'geri', + next: 'ileri', + today: 'bugün', + month: 'Ay', + week: 'Hafta', + day: 'Gün', + list: 'Ajanda', + }, + weekText: 'Hf', + allDayText: 'Tüm gün', + moreLinkText: 'daha fazla', + noEventsText: 'Gösterilecek etkinlik yok', + }; + + var l71 = { + code: 'ug', + buttonText: { + month: 'ئاي', + week: 'ھەپتە', + day: 'كۈن', + list: 'كۈنتەرتىپ', + }, + allDayText: 'پۈتۈن كۈن', + }; + + var l72 = { + code: 'uk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Попередній', + next: 'далі', + today: 'Сьогодні', + month: 'Місяць', + week: 'Тиждень', + day: 'День', + list: 'Порядок денний', + }, + weekText: 'Тиж', + allDayText: 'Увесь день', + moreLinkText: function(n) { + return '+ще ' + n + '...' + }, + noEventsText: 'Немає подій для відображення', + }; + + var l73 = { + code: 'uz', + buttonText: { + month: 'Oy', + week: 'Xafta', + day: 'Kun', + list: 'Kun tartibi', + }, + allDayText: "Kun bo'yi", + moreLinkText: function(n) { + return '+ yana ' + n + }, + noEventsText: "Ko'rsatish uchun voqealar yo'q", + }; + + var l74 = { + code: 'vi', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Trước', + next: 'Tiếp', + today: 'Hôm nay', + month: 'Tháng', + week: 'Tuần', + day: 'Ngày', + list: 'Lịch biểu', + }, + weekText: 'Tu', + allDayText: 'Cả ngày', + moreLinkText: function(n) { + return '+ thêm ' + n + }, + noEventsText: 'Không có sự kiện để hiển thị', + }; + + var l75 = { + code: 'zh-cn', + week: { + // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: '上月', + next: '下月', + today: '今天', + month: '月', + week: '周', + day: '日', + list: '日程', + }, + weekText: '周', + allDayText: '全天', + moreLinkText: function(n) { + return '另外 ' + n + ' 个' + }, + noEventsText: '没有事件显示', + }; + + var l76 = { + code: 'zh-tw', + buttonText: { + prev: '上月', + next: '下月', + today: '今天', + month: '月', + week: '週', + day: '天', + list: '活動列表', + }, + weekText: '周', + allDayText: '整天', + moreLinkText: '顯示更多', + noEventsText: '没有任何活動', + }; + + /* eslint max-len: off */ + + var localesAll = [ + l0, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71, l72, l73, l74, l75, l76, + ]; + + return localesAll; + +}()); diff --git a/apps/schoolCalendar/fullcalendar/main.css b/apps/schoolCalendar/fullcalendar/main.css new file mode 100644 index 000000000..957887b56 --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/main.css @@ -0,0 +1,1446 @@ + +/* classes attached to */ +/* TODO: make fc-event selector work when calender in shadow DOM */ +.fc-not-allowed, +.fc-not-allowed .fc-event { /* override events' custom cursors */ + cursor: not-allowed; +} + +/* TODO: not attached to body. attached to specific els. move */ +.fc-unselectable { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +.fc { + /* layout of immediate children */ + display: flex; + flex-direction: column; + + font-size: 1em +} +.fc, + .fc *, + .fc *:before, + .fc *:after { + box-sizing: border-box; + } +.fc table { + border-collapse: collapse; + border-spacing: 0; + font-size: 1em; /* normalize cross-browser */ + } +.fc th { + text-align: center; + } +.fc th, + .fc td { + vertical-align: top; + padding: 0; + } +.fc a[data-navlink] { + cursor: pointer; + } +.fc a[data-navlink]:hover { + text-decoration: underline; + } +.fc-direction-ltr { + direction: ltr; + text-align: left; +} +.fc-direction-rtl { + direction: rtl; + text-align: right; +} +.fc-theme-standard td, + .fc-theme-standard th { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); + } +/* for FF, which doesn't expand a 100% div within a table cell. use absolute positioning */ +/* inner-wrappers are responsible for being absolute */ +/* TODO: best place for this? */ +.fc-liquid-hack td, + .fc-liquid-hack th { + position: relative; + } + +@font-face { + font-family: 'fcicons'; + src: url("data:application/x-font-ttf;charset=utf-8;base64,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") format('truetype'); + font-weight: normal; + font-style: normal; +} + +.fc-icon { + /* added for fc */ + display: inline-block; + width: 1em; + height: 1em; + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'fcicons' !important; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.fc-icon-chevron-left:before { + content: "\e900"; +} + +.fc-icon-chevron-right:before { + content: "\e901"; +} + +.fc-icon-chevrons-left:before { + content: "\e902"; +} + +.fc-icon-chevrons-right:before { + content: "\e903"; +} + +.fc-icon-minus-square:before { + content: "\e904"; +} + +.fc-icon-plus-square:before { + content: "\e905"; +} + +.fc-icon-x:before { + content: "\e906"; +} +/* +Lots taken from Flatly (MIT): https://bootswatch.com/4/flatly/bootstrap.css + +These styles only apply when the standard-theme is activated. +When it's NOT activated, the fc-button classes won't even be in the DOM. +*/ +.fc { + + /* reset */ + +} +.fc .fc-button { + border-radius: 0; + overflow: visible; + text-transform: none; + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; + } +.fc .fc-button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; + } +.fc .fc-button { + -webkit-appearance: button; + } +.fc .fc-button:not(:disabled) { + cursor: pointer; + } +.fc .fc-button::-moz-focus-inner { + padding: 0; + border-style: none; + } +.fc { + + /* theme */ + +} +.fc .fc-button { + display: inline-block; + font-weight: 400; + text-align: center; + vertical-align: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 0.4em 0.65em; + font-size: 1em; + line-height: 1.5; + border-radius: 0.25em; + } +.fc .fc-button:hover { + text-decoration: none; + } +.fc .fc-button:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(44, 62, 80, 0.25); + } +.fc .fc-button:disabled { + opacity: 0.65; + } +.fc { + + /* "primary" coloring */ + +} +.fc .fc-button-primary { + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #2C3E50; + background-color: var(--fc-button-bg-color, #2C3E50); + border-color: #2C3E50; + border-color: var(--fc-button-border-color, #2C3E50); + } +.fc .fc-button-primary:hover { + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #1e2b37; + background-color: var(--fc-button-hover-bg-color, #1e2b37); + border-color: #1a252f; + border-color: var(--fc-button-hover-border-color, #1a252f); + } +.fc .fc-button-primary:disabled { /* not DRY */ + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #2C3E50; + background-color: var(--fc-button-bg-color, #2C3E50); + border-color: #2C3E50; + border-color: var(--fc-button-border-color, #2C3E50); /* overrides :hover */ + } +.fc .fc-button-primary:focus { + box-shadow: 0 0 0 0.2rem rgba(76, 91, 106, 0.5); + } +.fc .fc-button-primary:not(:disabled):active, + .fc .fc-button-primary:not(:disabled).fc-button-active { + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #1a252f; + background-color: var(--fc-button-active-bg-color, #1a252f); + border-color: #151e27; + border-color: var(--fc-button-active-border-color, #151e27); + } +.fc .fc-button-primary:not(:disabled):active:focus, + .fc .fc-button-primary:not(:disabled).fc-button-active:focus { + box-shadow: 0 0 0 0.2rem rgba(76, 91, 106, 0.5); + } +.fc { + + /* icons within buttons */ + +} +.fc .fc-button .fc-icon { + vertical-align: middle; + font-size: 1.5em; /* bump up the size (but don't make it bigger than line-height of button, which is 1.5em also) */ + } +.fc .fc-button-group { + position: relative; + display: inline-flex; + vertical-align: middle; + } +.fc .fc-button-group > .fc-button { + position: relative; + flex: 1 1 auto; + } +.fc .fc-button-group > .fc-button:hover { + z-index: 1; + } +.fc .fc-button-group > .fc-button:focus, + .fc .fc-button-group > .fc-button:active, + .fc .fc-button-group > .fc-button.fc-button-active { + z-index: 1; + } +.fc-direction-ltr .fc-button-group > .fc-button:not(:first-child) { + margin-left: -1px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } +.fc-direction-ltr .fc-button-group > .fc-button:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +.fc-direction-rtl .fc-button-group > .fc-button:not(:first-child) { + margin-right: -1px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +.fc-direction-rtl .fc-button-group > .fc-button:not(:last-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } +.fc .fc-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + } +.fc .fc-toolbar.fc-header-toolbar { + margin-bottom: 1.5em; + } +.fc .fc-toolbar.fc-footer-toolbar { + margin-top: 1.5em; + } +.fc .fc-toolbar-title { + font-size: 1.75em; + margin: 0; + } +.fc-direction-ltr .fc-toolbar > * > :not(:first-child) { + margin-left: .75em; /* space between */ + } +.fc-direction-rtl .fc-toolbar > * > :not(:first-child) { + margin-right: .75em; /* space between */ + } +.fc-direction-rtl .fc-toolbar-ltr { /* when the toolbar-chunk positioning system is explicitly left-to-right */ + flex-direction: row-reverse; + } +.fc .fc-scroller { + -webkit-overflow-scrolling: touch; + position: relative; /* for abs-positioned elements within */ + } +.fc .fc-scroller-liquid { + height: 100%; + } +.fc .fc-scroller-liquid-absolute { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + } +.fc .fc-scroller-harness { + position: relative; + overflow: hidden; + direction: ltr; + /* hack for chrome computing the scroller's right/left wrong for rtl. undone below... */ + /* TODO: demonstrate in codepen */ + } +.fc .fc-scroller-harness-liquid { + height: 100%; + } +.fc-direction-rtl .fc-scroller-harness > .fc-scroller { /* undo above hack */ + direction: rtl; + } +.fc-theme-standard .fc-scrollgrid { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); /* bootstrap does this. match */ + } +.fc .fc-scrollgrid, + .fc .fc-scrollgrid table { /* all tables (self included) */ + width: 100%; /* because tables don't normally do this */ + table-layout: fixed; + } +.fc .fc-scrollgrid table { /* inner tables */ + border-top-style: hidden; + border-left-style: hidden; + border-right-style: hidden; + } +.fc .fc-scrollgrid { + + border-collapse: separate; + border-right-width: 0; + border-bottom-width: 0; + + } +.fc .fc-scrollgrid-liquid { + height: 100%; + } +.fc .fc-scrollgrid-section { /* a
*/ + height: 1px /* better than 0, for firefox */ + + } +.fc .fc-scrollgrid-section > td { + height: 1px; /* needs a height so inner div within grow. better than 0, for firefox */ + } +.fc .fc-scrollgrid-section table { + height: 1px; + /* for most browsers, if a height isn't set on the table, can't do liquid-height within cells */ + /* serves as a min-height. harmless */ + } +.fc .fc-scrollgrid-section-liquid > td { + height: 100%; /* better than `auto`, for firefox */ + } +.fc .fc-scrollgrid-section > * { + border-top-width: 0; + border-left-width: 0; + } +.fc .fc-scrollgrid-section-header > *, + .fc .fc-scrollgrid-section-footer > * { + border-bottom-width: 0; + } +.fc .fc-scrollgrid-section-body table, + .fc .fc-scrollgrid-section-footer table { + border-bottom-style: hidden; /* head keeps its bottom border tho */ + } +.fc { + + /* stickiness */ + +} +.fc .fc-scrollgrid-section-sticky > * { + background: #fff; + background: var(--fc-page-bg-color, #fff); + position: sticky; + z-index: 3; /* TODO: var */ + /* TODO: box-shadow when sticking */ + } +.fc .fc-scrollgrid-section-header.fc-scrollgrid-section-sticky > * { + top: 0; /* because border-sharing causes a gap at the top */ + /* TODO: give safari -1. has bug */ + } +.fc .fc-scrollgrid-section-footer.fc-scrollgrid-section-sticky > * { + bottom: 0; /* known bug: bottom-stickiness doesn't work in safari */ + } +.fc .fc-scrollgrid-sticky-shim { /* for horizontal scrollbar */ + height: 1px; /* needs height to create scrollbars */ + margin-bottom: -1px; + } +.fc-sticky { /* no .fc wrap because used as child of body */ + position: sticky; +} +.fc .fc-view-harness { + flex-grow: 1; /* because this harness is WITHIN the .fc's flexbox */ + position: relative; + } +.fc { + + /* when the harness controls the height, make the view liquid */ + +} +.fc .fc-view-harness-active > .fc-view { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +.fc .fc-col-header-cell-cushion { + display: inline-block; /* x-browser for when sticky (when multi-tier header) */ + padding: 2px 4px; + } +.fc .fc-bg-event, + .fc .fc-non-business, + .fc .fc-highlight { + /* will always have a harness with position:relative/absolute, so absolutely expand */ + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +.fc .fc-non-business { + background: rgba(215, 215, 215, 0.3); + background: var(--fc-non-business-color, rgba(215, 215, 215, 0.3)); + } +.fc .fc-bg-event { + background: rgb(143, 223, 130); + background: var(--fc-bg-event-color, rgb(143, 223, 130)); + opacity: 0.3; + opacity: var(--fc-bg-event-opacity, 0.3) + } +.fc .fc-bg-event .fc-event-title { + margin: .5em; + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); + font-style: italic; + } +.fc .fc-highlight { + background: rgba(188, 232, 241, 0.3); + background: var(--fc-highlight-color, rgba(188, 232, 241, 0.3)); + } +.fc .fc-cell-shaded, + .fc .fc-day-disabled { + background: rgba(208, 208, 208, 0.3); + background: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + } +/* link resets */ +/* ---------------------------------------------------------------------------------------------------- */ +a.fc-event, +a.fc-event:hover { + text-decoration: none; +} +/* cursor */ +.fc-event[href], +.fc-event.fc-event-draggable { + cursor: pointer; +} +/* event text content */ +/* ---------------------------------------------------------------------------------------------------- */ +.fc-event .fc-event-main { + position: relative; + z-index: 2; + } +/* dragging */ +/* ---------------------------------------------------------------------------------------------------- */ +.fc-event-dragging:not(.fc-event-selected) { /* MOUSE */ + opacity: 0.75; + } +.fc-event-dragging.fc-event-selected { /* TOUCH */ + box-shadow: 0 2px 7px rgba(0, 0, 0, 0.3); + } +/* resizing */ +/* ---------------------------------------------------------------------------------------------------- */ +/* (subclasses should hone positioning for touch and non-touch) */ +.fc-event .fc-event-resizer { + display: none; + position: absolute; + z-index: 4; + } +.fc-event:hover, /* MOUSE */ +.fc-event-selected { /* TOUCH */ + +} +.fc-event:hover .fc-event-resizer, .fc-event-selected .fc-event-resizer { + display: block; + } +.fc-event-selected .fc-event-resizer { + border-radius: 4px; + border-radius: calc(var(--fc-event-resizer-dot-total-width, 8px) / 2); + border-width: 1px; + border-width: var(--fc-event-resizer-dot-border-width, 1px); + width: 8px; + width: var(--fc-event-resizer-dot-total-width, 8px); + height: 8px; + height: var(--fc-event-resizer-dot-total-width, 8px); + border-style: solid; + border-color: inherit; + background: #fff; + background: var(--fc-page-bg-color, #fff) + + /* expand hit area */ + + } +.fc-event-selected .fc-event-resizer:before { + content: ''; + position: absolute; + top: -20px; + left: -20px; + right: -20px; + bottom: -20px; + } +/* selecting (always TOUCH) */ +/* ---------------------------------------------------------------------------------------------------- */ +.fc-event-selected { + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2) + + /* expand hit area (subclasses should expand) */ + +} +.fc-event-selected:before { + content: ""; + position: absolute; + z-index: 3; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +.fc-event-selected { + + /* dimmer effect */ + +} +.fc-event-selected:after { + content: ""; + background: rgba(0, 0, 0, 0.25); + background: var(--fc-event-selected-overlay-color, rgba(0, 0, 0, 0.25)); + position: absolute; + z-index: 1; + + /* assume there's a border on all sides. overcome it. */ + /* sometimes there's NOT a border, in which case the dimmer will go over */ + /* an adjacent border, which looks fine. */ + top: -1px; + left: -1px; + right: -1px; + bottom: -1px; + } +/* +A HORIZONTAL event +*/ +.fc-h-event { /* allowed to be top-level */ + display: block; + border: 1px solid #3788d8; + border: 1px solid var(--fc-event-border-color, #3788d8); + background-color: #3788d8; + background-color: var(--fc-event-bg-color, #3788d8) + +} +.fc-h-event .fc-event-main { + color: #fff; + color: var(--fc-event-text-color, #fff); + } +.fc-h-event .fc-event-main-frame { + display: flex; /* for make fc-event-title-container expand */ + } +.fc-h-event .fc-event-time { + max-width: 100%; /* clip overflow on this element */ + overflow: hidden; + } +.fc-h-event .fc-event-title-container { /* serves as a container for the sticky cushion */ + flex-grow: 1; + flex-shrink: 1; + min-width: 0; /* important for allowing to shrink all the way */ + } +.fc-h-event .fc-event-title { + display: inline-block; /* need this to be sticky cross-browser */ + vertical-align: top; /* for not messing up line-height */ + left: 0; /* for sticky */ + right: 0; /* for sticky */ + max-width: 100%; /* clip overflow on this element */ + overflow: hidden; + } +.fc-h-event.fc-event-selected:before { + /* expand hit area */ + top: -10px; + bottom: -10px; + } +/* adjust border and border-radius (if there is any) for non-start/end */ +.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-start), +.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-end) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left-width: 0; +} +.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-end), +.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-start) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right-width: 0; +} +/* resizers */ +.fc-h-event:not(.fc-event-selected) .fc-event-resizer { + top: 0; + bottom: 0; + width: 8px; + width: var(--fc-event-resizer-thickness, 8px); +} +.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start, +.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end { + cursor: w-resize; + left: -4px; + left: calc(var(--fc-event-resizer-thickness, 8px) / -2); +} +.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end, +.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start { + cursor: e-resize; + right: -4px; + right: calc(var(--fc-event-resizer-thickness, 8px) / -2); +} +/* resizers for TOUCH */ +.fc-h-event.fc-event-selected .fc-event-resizer { + top: 50%; + margin-top: -4px; + margin-top: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); +} +.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-start, +.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-end { + left: -4px; + left: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); +} +.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-end, +.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-start { + right: -4px; + right: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); +} +.fc .fc-popover { + position: absolute; + z-index: 9999; + box-shadow: 0 2px 6px rgba(0,0,0,.15); + } +.fc .fc-popover-header { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 3px 4px; + } +.fc .fc-popover-title { + margin: 0 2px; + } +.fc .fc-popover-close { + cursor: pointer; + opacity: 0.65; + font-size: 1.1em; + } +.fc-theme-standard .fc-popover { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); + background: #fff; + background: var(--fc-page-bg-color, #fff); + } +.fc-theme-standard .fc-popover-header { + background: rgba(208, 208, 208, 0.3); + background: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + } + + +:root { + --fc-daygrid-event-dot-width: 8px; +} +/* help things clear margins of inner content */ +.fc-daygrid-day-frame, +.fc-daygrid-day-events, +.fc-daygrid-event-harness { /* for event top/bottom margins */ +} +.fc-daygrid-day-frame:before, .fc-daygrid-day-events:before, .fc-daygrid-event-harness:before { + content: ""; + clear: both; + display: table; } +.fc-daygrid-day-frame:after, .fc-daygrid-day-events:after, .fc-daygrid-event-harness:after { + content: ""; + clear: both; + display: table; } +.fc .fc-daygrid-body { /* a
that wraps the table */ + position: relative; + z-index: 1; /* container inner z-index's because
s can't do it */ + } +.fc .fc-daygrid-day.fc-day-today { + background-color: rgba(255, 220, 40, 0.15); + background-color: var(--fc-today-bg-color, rgba(255, 220, 40, 0.15)); + } +.fc .fc-daygrid-day-frame { + position: relative; + min-height: 100%; /* seems to work better than `height` because sets height after rows/cells naturally do it */ + } +.fc { + + /* cell top */ + +} +.fc .fc-daygrid-day-top { + display: flex; + flex-direction: row-reverse; + } +.fc .fc-day-other .fc-daygrid-day-top { + opacity: 0.3; + } +.fc { + + /* day number (within cell top) */ + +} +.fc .fc-daygrid-day-number { + position: relative; + z-index: 4; + padding: 4px; + } +.fc { + + /* event container */ + +} +.fc .fc-daygrid-day-events { + margin-top: 1px; /* needs to be margin, not padding, so that available cell height can be computed */ + } +.fc { + + /* positioning for balanced vs natural */ + +} +.fc .fc-daygrid-body-balanced .fc-daygrid-day-events { + position: absolute; + left: 0; + right: 0; + } +.fc .fc-daygrid-body-unbalanced .fc-daygrid-day-events { + position: relative; /* for containing abs positioned event harnesses */ + min-height: 2em; /* in addition to being a min-height during natural height, equalizes the heights a little bit */ + } +.fc .fc-daygrid-body-natural { /* can coexist with -unbalanced */ + } +.fc .fc-daygrid-body-natural .fc-daygrid-day-events { + margin-bottom: 1em; + } +.fc { + + /* event harness */ + +} +.fc .fc-daygrid-event-harness { + position: relative; + } +.fc .fc-daygrid-event-harness-abs { + position: absolute; + top: 0; /* fallback coords for when cannot yet be computed */ + left: 0; /* */ + right: 0; /* */ + } +.fc .fc-daygrid-bg-harness { + position: absolute; + top: 0; + bottom: 0; + } +.fc { + + /* bg content */ + +} +.fc .fc-daygrid-day-bg .fc-non-business { z-index: 1 } +.fc .fc-daygrid-day-bg .fc-bg-event { z-index: 2 } +.fc .fc-daygrid-day-bg .fc-highlight { z-index: 3 } +.fc { + + /* events */ + +} +.fc .fc-daygrid-event { + z-index: 6; + margin-top: 1px; + } +.fc .fc-daygrid-event.fc-event-mirror { + z-index: 7; + } +.fc { + + /* cell bottom (within day-events) */ + +} +.fc .fc-daygrid-day-bottom { + font-size: .85em; + padding: 2px 3px 0 + } +.fc .fc-daygrid-day-bottom:before { + content: ""; + clear: both; + display: table; } +.fc .fc-daygrid-more-link { + position: relative; + z-index: 4; + cursor: pointer; + } +.fc { + + /* week number (within frame) */ + +} +.fc .fc-daygrid-week-number { + position: absolute; + z-index: 5; + top: 0; + padding: 2px; + min-width: 1.5em; + text-align: center; + background-color: rgba(208, 208, 208, 0.3); + background-color: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + color: #808080; + color: var(--fc-neutral-text-color, #808080); + } +.fc { + + /* popover */ + +} +.fc .fc-more-popover .fc-popover-body { + min-width: 220px; + padding: 10px; + } +.fc-direction-ltr .fc-daygrid-event.fc-event-start, +.fc-direction-rtl .fc-daygrid-event.fc-event-end { + margin-left: 2px; +} +.fc-direction-ltr .fc-daygrid-event.fc-event-end, +.fc-direction-rtl .fc-daygrid-event.fc-event-start { + margin-right: 2px; +} +.fc-direction-ltr .fc-daygrid-week-number { + left: 0; + border-radius: 0 0 3px 0; + } +.fc-direction-rtl .fc-daygrid-week-number { + right: 0; + border-radius: 0 0 0 3px; + } +.fc-liquid-hack .fc-daygrid-day-frame { + position: static; /* will cause inner absolute stuff to expand to
/ elements with colspans. + SOLUTION: making individual
+ _this.frameElRefs = new RefMap(); // the fc-daygrid-day-frame + _this.fgElRefs = new RefMap(); // the fc-daygrid-day-events + _this.segHarnessRefs = new RefMap(); // indexed by "instanceId:firstCol" + _this.rootElRef = createRef(); + _this.state = { + framePositions: null, + maxContentHeight: null, + eventInstanceHeights: {}, + }; + return _this; + } + TableRow.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, state = _a.state, context = _a.context; + var options = context.options; + var colCnt = props.cells.length; + var businessHoursByCol = splitSegsByFirstCol(props.businessHourSegs, colCnt); + var bgEventSegsByCol = splitSegsByFirstCol(props.bgEventSegs, colCnt); + var highlightSegsByCol = splitSegsByFirstCol(this.getHighlightSegs(), colCnt); + var mirrorSegsByCol = splitSegsByFirstCol(this.getMirrorSegs(), colCnt); + var _b = computeFgSegPlacement(sortEventSegs(props.fgEventSegs, options.eventOrder), props.dayMaxEvents, props.dayMaxEventRows, options.eventOrderStrict, state.eventInstanceHeights, state.maxContentHeight, props.cells), singleColPlacements = _b.singleColPlacements, multiColPlacements = _b.multiColPlacements, moreCnts = _b.moreCnts, moreMarginTops = _b.moreMarginTops; + var isForcedInvisible = // TODO: messy way to compute this + (props.eventDrag && props.eventDrag.affectedInstances) || + (props.eventResize && props.eventResize.affectedInstances) || + {}; + return (createElement("tr", { ref: this.rootElRef }, + props.renderIntro && props.renderIntro(), + props.cells.map(function (cell, col) { + var normalFgNodes = _this.renderFgSegs(col, props.forPrint ? singleColPlacements[col] : multiColPlacements[col], props.todayRange, isForcedInvisible); + var mirrorFgNodes = _this.renderFgSegs(col, buildMirrorPlacements(mirrorSegsByCol[col], multiColPlacements), props.todayRange, {}, Boolean(props.eventDrag), Boolean(props.eventResize), false); + return (createElement(TableCell, { key: cell.key, elRef: _this.cellElRefs.createRef(cell.key), innerElRef: _this.frameElRefs.createRef(cell.key) /* FF problem, but okay to use for left/right. TODO: rename prop */, dateProfile: props.dateProfile, date: cell.date, showDayNumber: props.showDayNumbers, showWeekNumber: props.showWeekNumbers && col === 0, forceDayTop: props.showWeekNumbers /* even displaying weeknum for row, not necessarily day */, todayRange: props.todayRange, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, extraHookProps: cell.extraHookProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, moreCnt: moreCnts[col], moreMarginTop: moreMarginTops[col], singlePlacements: singleColPlacements[col], fgContentElRef: _this.fgElRefs.createRef(cell.key), fgContent: ( // Fragment scopes the keys + createElement(Fragment, null, + createElement(Fragment, null, normalFgNodes), + createElement(Fragment, null, mirrorFgNodes))), bgContent: ( // Fragment scopes the keys + createElement(Fragment, null, + _this.renderFillSegs(highlightSegsByCol[col], 'highlight'), + _this.renderFillSegs(businessHoursByCol[col], 'non-business'), + _this.renderFillSegs(bgEventSegsByCol[col], 'bg-event'))) })); + }))); + }; + TableRow.prototype.componentDidMount = function () { + this.updateSizing(true); + }; + TableRow.prototype.componentDidUpdate = function (prevProps, prevState) { + var currentProps = this.props; + this.updateSizing(!isPropsEqual(prevProps, currentProps)); + }; + TableRow.prototype.getHighlightSegs = function () { + var props = this.props; + if (props.eventDrag && props.eventDrag.segs.length) { // messy check + return props.eventDrag.segs; + } + if (props.eventResize && props.eventResize.segs.length) { // messy check + return props.eventResize.segs; + } + return props.dateSelectionSegs; + }; + TableRow.prototype.getMirrorSegs = function () { + var props = this.props; + if (props.eventResize && props.eventResize.segs.length) { // messy check + return props.eventResize.segs; + } + return []; + }; + TableRow.prototype.renderFgSegs = function (col, segPlacements, todayRange, isForcedInvisible, isDragging, isResizing, isDateSelecting) { + var context = this.context; + var eventSelection = this.props.eventSelection; + var framePositions = this.state.framePositions; + var defaultDisplayEventEnd = this.props.cells.length === 1; // colCnt === 1 + var isMirror = isDragging || isResizing || isDateSelecting; + var nodes = []; + if (framePositions) { + for (var _i = 0, segPlacements_1 = segPlacements; _i < segPlacements_1.length; _i++) { + var placement = segPlacements_1[_i]; + var seg = placement.seg; + var instanceId = seg.eventRange.instance.instanceId; + var key = instanceId + ':' + col; + var isVisible = placement.isVisible && !isForcedInvisible[instanceId]; + var isAbsolute = placement.isAbsolute; + var left = ''; + var right = ''; + if (isAbsolute) { + if (context.isRtl) { + right = 0; + left = framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol]; + } + else { + left = 0; + right = framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol]; + } + } + /* + known bug: events that are force to be list-item but span multiple days still take up space in later columns + todo: in print view, for multi-day events, don't display title within non-start/end segs + */ + nodes.push(createElement("div", { className: 'fc-daygrid-event-harness' + (isAbsolute ? ' fc-daygrid-event-harness-abs' : ''), key: key, ref: isMirror ? null : this.segHarnessRefs.createRef(key), style: { + visibility: isVisible ? '' : 'hidden', + marginTop: isAbsolute ? '' : placement.marginTop, + top: isAbsolute ? placement.absoluteTop : '', + left: left, + right: right, + } }, hasListItemDisplay(seg) ? (createElement(TableListItemEvent, __assign({ seg: seg, isDragging: isDragging, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange)))) : (createElement(TableBlockEvent, __assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange)))))); + } + } + return nodes; + }; + TableRow.prototype.renderFillSegs = function (segs, fillType) { + var isRtl = this.context.isRtl; + var todayRange = this.props.todayRange; + var framePositions = this.state.framePositions; + var nodes = []; + if (framePositions) { + for (var _i = 0, segs_1 = segs; _i < segs_1.length; _i++) { + var seg = segs_1[_i]; + var leftRightCss = isRtl ? { + right: 0, + left: framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol], + } : { + left: 0, + right: framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol], + }; + nodes.push(createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-daygrid-bg-harness", style: leftRightCss }, fillType === 'bg-event' ? + createElement(BgEvent, __assign({ seg: seg }, getSegMeta(seg, todayRange))) : + renderFill(fillType))); + } + } + return createElement.apply(void 0, __spreadArray([Fragment, {}], nodes)); + }; + TableRow.prototype.updateSizing = function (isExternalSizingChange) { + var _a = this, props = _a.props, frameElRefs = _a.frameElRefs; + if (!props.forPrint && + props.clientWidth !== null // positioning ready? + ) { + if (isExternalSizingChange) { + var frameEls = props.cells.map(function (cell) { return frameElRefs.currentMap[cell.key]; }); + if (frameEls.length) { + var originEl = this.rootElRef.current; + this.setState({ + framePositions: new PositionCache(originEl, frameEls, true, // isHorizontal + false), + }); + } + } + var limitByContentHeight = props.dayMaxEvents === true || props.dayMaxEventRows === true; + this.setState({ + eventInstanceHeights: this.queryEventInstanceHeights(), + maxContentHeight: limitByContentHeight ? this.computeMaxContentHeight() : null, + }); + } + }; + TableRow.prototype.queryEventInstanceHeights = function () { + var segElMap = this.segHarnessRefs.currentMap; + var eventInstanceHeights = {}; + // get the max height amongst instance segs + for (var key in segElMap) { + var height = Math.round(segElMap[key].getBoundingClientRect().height); + var instanceId = key.split(':')[0]; // deconstruct how renderFgSegs makes the key + eventInstanceHeights[instanceId] = Math.max(eventInstanceHeights[instanceId] || 0, height); + } + return eventInstanceHeights; + }; + TableRow.prototype.computeMaxContentHeight = function () { + var firstKey = this.props.cells[0].key; + var cellEl = this.cellElRefs.currentMap[firstKey]; + var fcContainerEl = this.fgElRefs.currentMap[firstKey]; + return cellEl.getBoundingClientRect().bottom - fcContainerEl.getBoundingClientRect().top; + }; + TableRow.prototype.getCellEls = function () { + var elMap = this.cellElRefs.currentMap; + return this.props.cells.map(function (cell) { return elMap[cell.key]; }); + }; + return TableRow; + }(DateComponent)); + TableRow.addStateEquality({ + eventInstanceHeights: isPropsEqual, + }); + function buildMirrorPlacements(mirrorSegs, colPlacements) { + if (!mirrorSegs.length) { + return []; + } + var topsByInstanceId = buildAbsoluteTopHash(colPlacements); // TODO: cache this at first render? + return mirrorSegs.map(function (seg) { return ({ + seg: seg, + isVisible: true, + isAbsolute: true, + absoluteTop: topsByInstanceId[seg.eventRange.instance.instanceId], + marginTop: 0, + }); }); + } + function buildAbsoluteTopHash(colPlacements) { + var topsByInstanceId = {}; + for (var _i = 0, colPlacements_1 = colPlacements; _i < colPlacements_1.length; _i++) { + var placements = colPlacements_1[_i]; + for (var _a = 0, placements_1 = placements; _a < placements_1.length; _a++) { + var placement = placements_1[_a]; + topsByInstanceId[placement.seg.eventRange.instance.instanceId] = placement.absoluteTop; + } + } + return topsByInstanceId; + } + + var Table = /** @class */ (function (_super) { + __extends(Table, _super); + function Table() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.splitBusinessHourSegs = memoize(splitSegsByRow); + _this.splitBgEventSegs = memoize(splitSegsByRow); + _this.splitFgEventSegs = memoize(splitSegsByRow); + _this.splitDateSelectionSegs = memoize(splitSegsByRow); + _this.splitEventDrag = memoize(splitInteractionByRow); + _this.splitEventResize = memoize(splitInteractionByRow); + _this.rowRefs = new RefMap(); + _this.handleRootEl = function (rootEl) { + _this.rootEl = rootEl; + if (rootEl) { + _this.context.registerInteractiveComponent(_this, { + el: rootEl, + isHitComboAllowed: _this.props.isHitComboAllowed, + }); + } + else { + _this.context.unregisterInteractiveComponent(_this); + } + }; + return _this; + } + Table.prototype.render = function () { + var _this = this; + var props = this.props; + var dateProfile = props.dateProfile, dayMaxEventRows = props.dayMaxEventRows, dayMaxEvents = props.dayMaxEvents, expandRows = props.expandRows; + var rowCnt = props.cells.length; + var businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, rowCnt); + var bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, rowCnt); + var fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, rowCnt); + var dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, rowCnt); + var eventDragByRow = this.splitEventDrag(props.eventDrag, rowCnt); + var eventResizeByRow = this.splitEventResize(props.eventResize, rowCnt); + var limitViaBalanced = dayMaxEvents === true || dayMaxEventRows === true; + // if rows can't expand to fill fixed height, can't do balanced-height event limit + // TODO: best place to normalize these options? + if (limitViaBalanced && !expandRows) { + limitViaBalanced = false; + dayMaxEventRows = null; + dayMaxEvents = null; + } + var classNames = [ + 'fc-daygrid-body', + limitViaBalanced ? 'fc-daygrid-body-balanced' : 'fc-daygrid-body-unbalanced', + expandRows ? '' : 'fc-daygrid-body-natural', // will height of one row depend on the others? + ]; + return (createElement("div", { className: classNames.join(' '), ref: this.handleRootEl, style: { + // these props are important to give this wrapper correct dimensions for interactions + // TODO: if we set it here, can we avoid giving to inner tables? + width: props.clientWidth, + minWidth: props.tableMinWidth, + } }, + createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { return (createElement(Fragment, null, + createElement("table", { className: "fc-scrollgrid-sync-table", style: { + width: props.clientWidth, + minWidth: props.tableMinWidth, + height: expandRows ? props.clientHeight : '', + } }, + props.colGroupNode, + createElement("tbody", null, props.cells.map(function (cells, row) { return (createElement(TableRow, { ref: _this.rowRefs.createRef(row), key: cells.length + ? cells[0].date.toISOString() /* best? or put key on cell? or use diff formatter? */ + : row // in case there are no cells (like when resource view is loading) + , showDayNumbers: rowCnt > 1, showWeekNumbers: props.showWeekNumbers, todayRange: todayRange, dateProfile: dateProfile, cells: cells, renderIntro: props.renderRowIntro, businessHourSegs: businessHourSegsByRow[row], eventSelection: props.eventSelection, bgEventSegs: bgEventSegsByRow[row].filter(isSegAllDay) /* hack */, fgEventSegs: fgEventSegsByRow[row], dateSelectionSegs: dateSelectionSegsByRow[row], eventDrag: eventDragByRow[row], eventResize: eventResizeByRow[row], dayMaxEvents: dayMaxEvents, dayMaxEventRows: dayMaxEventRows, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint })); }))))); }))); + }; + // Hit System + // ---------------------------------------------------------------------------------------------------- + Table.prototype.prepareHits = function () { + this.rowPositions = new PositionCache(this.rootEl, this.rowRefs.collect().map(function (rowObj) { return rowObj.getCellEls()[0]; }), // first cell el in each row. TODO: not optimal + false, true); + this.colPositions = new PositionCache(this.rootEl, this.rowRefs.currentMap[0].getCellEls(), // cell els in first row + true, // horizontal + false); + }; + Table.prototype.queryHit = function (positionLeft, positionTop) { + var _a = this, colPositions = _a.colPositions, rowPositions = _a.rowPositions; + var col = colPositions.leftToIndex(positionLeft); + var row = rowPositions.topToIndex(positionTop); + if (row != null && col != null) { + var cell = this.props.cells[row][col]; + return { + dateProfile: this.props.dateProfile, + dateSpan: __assign({ range: this.getCellRange(row, col), allDay: true }, cell.extraDateSpan), + dayEl: this.getCellEl(row, col), + rect: { + left: colPositions.lefts[col], + right: colPositions.rights[col], + top: rowPositions.tops[row], + bottom: rowPositions.bottoms[row], + }, + layer: 0, + }; + } + return null; + }; + Table.prototype.getCellEl = function (row, col) { + return this.rowRefs.currentMap[row].getCellEls()[col]; // TODO: not optimal + }; + Table.prototype.getCellRange = function (row, col) { + var start = this.props.cells[row][col].date; + var end = addDays(start, 1); + return { start: start, end: end }; + }; + return Table; + }(DateComponent)); + function isSegAllDay(seg) { + return seg.eventRange.def.allDay; + } + + var DayTableSlicer = /** @class */ (function (_super) { + __extends(DayTableSlicer, _super); + function DayTableSlicer() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.forceDayIfListItem = true; + return _this; + } + DayTableSlicer.prototype.sliceRange = function (dateRange, dayTableModel) { + return dayTableModel.sliceRange(dateRange); + }; + return DayTableSlicer; + }(Slicer)); + + var DayTable = /** @class */ (function (_super) { + __extends(DayTable, _super); + function DayTable() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.slicer = new DayTableSlicer(); + _this.tableRef = createRef(); + return _this; + } + DayTable.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + return (createElement(Table, __assign({ ref: this.tableRef }, this.slicer.sliceProps(props, props.dateProfile, props.nextDayThreshold, context, props.dayTableModel), { dateProfile: props.dateProfile, cells: props.dayTableModel.cells, colGroupNode: props.colGroupNode, tableMinWidth: props.tableMinWidth, renderRowIntro: props.renderRowIntro, dayMaxEvents: props.dayMaxEvents, dayMaxEventRows: props.dayMaxEventRows, showWeekNumbers: props.showWeekNumbers, expandRows: props.expandRows, headerAlignElRef: props.headerAlignElRef, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint }))); + }; + return DayTable; + }(DateComponent)); + + var DayTableView = /** @class */ (function (_super) { + __extends(DayTableView, _super); + function DayTableView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildDayTableModel = memoize(buildDayTableModel); + _this.headerRef = createRef(); + _this.tableRef = createRef(); + return _this; + } + DayTableView.prototype.render = function () { + var _this = this; + var _a = this.context, options = _a.options, dateProfileGenerator = _a.dateProfileGenerator; + var props = this.props; + var dayTableModel = this.buildDayTableModel(props.dateProfile, dateProfileGenerator); + var headerContent = options.dayHeaders && (createElement(DayHeader, { ref: this.headerRef, dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: dayTableModel.rowCnt === 1 })); + var bodyContent = function (contentArg) { return (createElement(DayTable, { ref: _this.tableRef, dateProfile: props.dateProfile, dayTableModel: dayTableModel, businessHours: props.businessHours, dateSelection: props.dateSelection, eventStore: props.eventStore, eventUiBases: props.eventUiBases, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, nextDayThreshold: options.nextDayThreshold, colGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, dayMaxEvents: options.dayMaxEvents, dayMaxEventRows: options.dayMaxEventRows, showWeekNumbers: options.weekNumbers, expandRows: !props.isHeightAuto, headerAlignElRef: _this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint })); }; + return options.dayMinWidth + ? this.renderHScrollLayout(headerContent, bodyContent, dayTableModel.colCnt, options.dayMinWidth) + : this.renderSimpleLayout(headerContent, bodyContent); + }; + return DayTableView; + }(TableView)); + function buildDayTableModel(dateProfile, dateProfileGenerator) { + var daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator); + return new DayTableModel(daySeries, /year|month|week/.test(dateProfile.currentRangeUnit)); + } + + var TableDateProfileGenerator = /** @class */ (function (_super) { + __extends(TableDateProfileGenerator, _super); + function TableDateProfileGenerator() { + return _super !== null && _super.apply(this, arguments) || this; + } + // Computes the date range that will be rendered. + TableDateProfileGenerator.prototype.buildRenderRange = function (currentRange, currentRangeUnit, isRangeAllDay) { + var dateEnv = this.props.dateEnv; + var renderRange = _super.prototype.buildRenderRange.call(this, currentRange, currentRangeUnit, isRangeAllDay); + var start = renderRange.start; + var end = renderRange.end; + var endOfWeek; + // year and month views should be aligned with weeks. this is already done for week + if (/^(year|month)$/.test(currentRangeUnit)) { + start = dateEnv.startOfWeek(start); + // make end-of-week if not already + endOfWeek = dateEnv.startOfWeek(end); + if (endOfWeek.valueOf() !== end.valueOf()) { + end = addWeeks(endOfWeek, 1); + } + } + // ensure 6 weeks + if (this.props.monthMode && + this.props.fixedWeekCount) { + var rowCnt = Math.ceil(// could be partial weeks due to hiddenDays + diffWeeks(start, end)); + end = addWeeks(end, 6 - rowCnt); + } + return { start: start, end: end }; + }; + return TableDateProfileGenerator; + }(DateProfileGenerator)); + + var dayGridPlugin = createPlugin({ + initialView: 'dayGridMonth', + views: { + dayGrid: { + component: DayTableView, + dateProfileGeneratorClass: TableDateProfileGenerator, + }, + dayGridDay: { + type: 'dayGrid', + duration: { days: 1 }, + }, + dayGridWeek: { + type: 'dayGrid', + duration: { weeks: 1 }, + }, + dayGridMonth: { + type: 'dayGrid', + duration: { months: 1 }, + monthMode: true, + fixedWeekCount: true, + }, + }, + }); + + var AllDaySplitter = /** @class */ (function (_super) { + __extends(AllDaySplitter, _super); + function AllDaySplitter() { + return _super !== null && _super.apply(this, arguments) || this; + } + AllDaySplitter.prototype.getKeyInfo = function () { + return { + allDay: {}, + timed: {}, + }; + }; + AllDaySplitter.prototype.getKeysForDateSpan = function (dateSpan) { + if (dateSpan.allDay) { + return ['allDay']; + } + return ['timed']; + }; + AllDaySplitter.prototype.getKeysForEventDef = function (eventDef) { + if (!eventDef.allDay) { + return ['timed']; + } + if (hasBgRendering(eventDef)) { + return ['timed', 'allDay']; + } + return ['allDay']; + }; + return AllDaySplitter; + }(Splitter)); + + var DEFAULT_SLAT_LABEL_FORMAT = createFormatter({ + hour: 'numeric', + minute: '2-digit', + omitZeroMinute: true, + meridiem: 'short', + }); + function TimeColsAxisCell(props) { + var classNames = [ + 'fc-timegrid-slot', + 'fc-timegrid-slot-label', + props.isLabeled ? 'fc-scrollgrid-shrink' : 'fc-timegrid-slot-minor', + ]; + return (createElement(ViewContextType.Consumer, null, function (context) { + if (!props.isLabeled) { + return (createElement("td", { className: classNames.join(' '), "data-time": props.isoTimeStr })); + } + var dateEnv = context.dateEnv, options = context.options, viewApi = context.viewApi; + var labelFormat = // TODO: fully pre-parse + options.slotLabelFormat == null ? DEFAULT_SLAT_LABEL_FORMAT : + Array.isArray(options.slotLabelFormat) ? createFormatter(options.slotLabelFormat[0]) : + createFormatter(options.slotLabelFormat); + var hookProps = { + level: 0, + time: props.time, + date: dateEnv.toDate(props.date), + view: viewApi, + text: dateEnv.format(props.date, labelFormat), + }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.slotLabelClassNames, content: options.slotLabelContent, defaultContent: renderInnerContent$1, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-time": props.isoTimeStr }, + createElement("div", { className: "fc-timegrid-slot-label-frame fc-scrollgrid-shrink-frame" }, + createElement("div", { className: "fc-timegrid-slot-label-cushion fc-scrollgrid-shrink-cushion", ref: innerElRef }, innerContent)))); })); + })); + } + function renderInnerContent$1(props) { + return props.text; + } + + var TimeBodyAxis = /** @class */ (function (_super) { + __extends(TimeBodyAxis, _super); + function TimeBodyAxis() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeBodyAxis.prototype.render = function () { + return this.props.slatMetas.map(function (slatMeta) { return (createElement("tr", { key: slatMeta.key }, + createElement(TimeColsAxisCell, __assign({}, slatMeta)))); }); + }; + return TimeBodyAxis; + }(BaseComponent)); + + var DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'short' }); + var AUTO_ALL_DAY_MAX_EVENT_ROWS = 5; + var TimeColsView = /** @class */ (function (_super) { + __extends(TimeColsView, _super); + function TimeColsView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.allDaySplitter = new AllDaySplitter(); // for use by subclasses + _this.headerElRef = createRef(); + _this.rootElRef = createRef(); + _this.scrollerElRef = createRef(); + _this.state = { + slatCoords: null, + }; + _this.handleScrollTopRequest = function (scrollTop) { + var scrollerEl = _this.scrollerElRef.current; + if (scrollerEl) { // TODO: not sure how this could ever be null. weirdness with the reducer + scrollerEl.scrollTop = scrollTop; + } + }; + /* Header Render Methods + ------------------------------------------------------------------------------------------------------------------*/ + _this.renderHeadAxis = function (rowKey, frameHeight) { + if (frameHeight === void 0) { frameHeight = ''; } + var options = _this.context.options; + var dateProfile = _this.props.dateProfile; + var range = dateProfile.renderRange; + var dayCnt = diffDays(range.start, range.end); + var navLinkAttrs = (options.navLinks && dayCnt === 1) // only do in day views (to avoid doing in week views that dont need it) + ? { 'data-navlink': buildNavLinkData(range.start, 'week'), tabIndex: 0 } + : {}; + if (options.weekNumbers && rowKey === 'day') { + return (createElement(WeekNumberRoot, { date: range.start, defaultFormat: DEFAULT_WEEK_NUM_FORMAT }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("th", { ref: rootElRef, className: [ + 'fc-timegrid-axis', + 'fc-scrollgrid-shrink', + ].concat(classNames).join(' ') }, + createElement("div", { className: "fc-timegrid-axis-frame fc-scrollgrid-shrink-frame fc-timegrid-axis-frame-liquid", style: { height: frameHeight } }, + createElement("a", __assign({ ref: innerElRef, className: "fc-timegrid-axis-cushion fc-scrollgrid-shrink-cushion fc-scrollgrid-sync-inner" }, navLinkAttrs), innerContent)))); })); + } + return (createElement("th", { className: "fc-timegrid-axis" }, + createElement("div", { className: "fc-timegrid-axis-frame", style: { height: frameHeight } }))); + }; + /* Table Component Render Methods + ------------------------------------------------------------------------------------------------------------------*/ + // only a one-way height sync. we don't send the axis inner-content height to the DayGrid, + // but DayGrid still needs to have classNames on inner elements in order to measure. + _this.renderTableRowAxis = function (rowHeight) { + var _a = _this.context, options = _a.options, viewApi = _a.viewApi; + var hookProps = { + text: options.allDayText, + view: viewApi, + }; + return ( + // TODO: make reusable hook. used in list view too + createElement(RenderHook, { hookProps: hookProps, classNames: options.allDayClassNames, content: options.allDayContent, defaultContent: renderAllDayInner$1, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: [ + 'fc-timegrid-axis', + 'fc-scrollgrid-shrink', + ].concat(classNames).join(' ') }, + createElement("div", { className: 'fc-timegrid-axis-frame fc-scrollgrid-shrink-frame' + (rowHeight == null ? ' fc-timegrid-axis-frame-liquid' : ''), style: { height: rowHeight } }, + createElement("span", { className: "fc-timegrid-axis-cushion fc-scrollgrid-shrink-cushion fc-scrollgrid-sync-inner", ref: innerElRef }, innerContent)))); })); + }; + _this.handleSlatCoords = function (slatCoords) { + _this.setState({ slatCoords: slatCoords }); + }; + return _this; + } + // rendering + // ---------------------------------------------------------------------------------------------------- + TimeColsView.prototype.renderSimpleLayout = function (headerRowContent, allDayContent, timeContent) { + var _a = this, context = _a.context, props = _a.props; + var sections = []; + var stickyHeaderDates = getStickyHeaderDates(context.options); + if (headerRowContent) { + sections.push({ + type: 'header', + key: 'header', + isSticky: stickyHeaderDates, + chunk: { + elRef: this.headerElRef, + tableClassName: 'fc-col-header', + rowContent: headerRowContent, + }, + }); + } + if (allDayContent) { + sections.push({ + type: 'body', + key: 'all-day', + chunk: { content: allDayContent }, + }); + sections.push({ + type: 'body', + key: 'all-day-divider', + outerContent: ( // TODO: rename to cellContent so don't need to define
*/ + } +.fc-daygrid-event { /* make root-level, because will be dragged-and-dropped outside of a component root */ + position: relative; /* for z-indexes assigned later */ + white-space: nowrap; + border-radius: 3px; /* dot event needs this to when selected */ + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); +} +/* --- the rectangle ("block") style of event --- */ +.fc-daygrid-block-event .fc-event-time { + font-weight: bold; + } +.fc-daygrid-block-event .fc-event-time, + .fc-daygrid-block-event .fc-event-title { + padding: 1px; + } +/* --- the dot style of event --- */ +.fc-daygrid-dot-event { + display: flex; + align-items: center; + padding: 2px 0 + +} +.fc-daygrid-dot-event .fc-event-title { + flex-grow: 1; + flex-shrink: 1; + min-width: 0; /* important for allowing to shrink all the way */ + overflow: hidden; + font-weight: bold; + } +.fc-daygrid-dot-event:hover, + .fc-daygrid-dot-event.fc-event-mirror { + background: rgba(0, 0, 0, 0.1); + } +.fc-daygrid-dot-event.fc-event-selected:before { + /* expand hit area */ + top: -10px; + bottom: -10px; + } +.fc-daygrid-event-dot { /* the actual dot */ + margin: 0 4px; + box-sizing: content-box; + width: 0; + height: 0; + border: 4px solid #3788d8; + border: calc(var(--fc-daygrid-event-dot-width, 8px) / 2) solid var(--fc-event-border-color, #3788d8); + border-radius: 4px; + border-radius: calc(var(--fc-daygrid-event-dot-width, 8px) / 2); +} +/* --- spacing between time and title --- */ +.fc-direction-ltr .fc-daygrid-event .fc-event-time { + margin-right: 3px; + } +.fc-direction-rtl .fc-daygrid-event .fc-event-time { + margin-left: 3px; + } + + +/* +A VERTICAL event +*/ + +.fc-v-event { /* allowed to be top-level */ + display: block; + border: 1px solid #3788d8; + border: 1px solid var(--fc-event-border-color, #3788d8); + background-color: #3788d8; + background-color: var(--fc-event-bg-color, #3788d8) + +} + +.fc-v-event .fc-event-main { + color: #fff; + color: var(--fc-event-text-color, #fff); + height: 100%; + } + +.fc-v-event .fc-event-main-frame { + height: 100%; + display: flex; + flex-direction: column; + } + +.fc-v-event .fc-event-time { + flex-grow: 0; + flex-shrink: 0; + max-height: 100%; + overflow: hidden; + } + +.fc-v-event .fc-event-title-container { /* a container for the sticky cushion */ + flex-grow: 1; + flex-shrink: 1; + min-height: 0; /* important for allowing to shrink all the way */ + } + +.fc-v-event .fc-event-title { /* will have fc-sticky on it */ + top: 0; + bottom: 0; + max-height: 100%; /* clip overflow */ + overflow: hidden; + } + +.fc-v-event:not(.fc-event-start) { + border-top-width: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + } + +.fc-v-event:not(.fc-event-end) { + border-bottom-width: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + +.fc-v-event.fc-event-selected:before { + /* expand hit area */ + left: -10px; + right: -10px; + } + +.fc-v-event { + + /* resizer (mouse AND touch) */ + +} + +.fc-v-event .fc-event-resizer-start { + cursor: n-resize; + } + +.fc-v-event .fc-event-resizer-end { + cursor: s-resize; + } + +.fc-v-event { + + /* resizer for MOUSE */ + +} + +.fc-v-event:not(.fc-event-selected) .fc-event-resizer { + height: 8px; + height: var(--fc-event-resizer-thickness, 8px); + left: 0; + right: 0; + } + +.fc-v-event:not(.fc-event-selected) .fc-event-resizer-start { + top: -4px; + top: calc(var(--fc-event-resizer-thickness, 8px) / -2); + } + +.fc-v-event:not(.fc-event-selected) .fc-event-resizer-end { + bottom: -4px; + bottom: calc(var(--fc-event-resizer-thickness, 8px) / -2); + } + +.fc-v-event { + + /* resizer for TOUCH (when event is "selected") */ + +} + +.fc-v-event.fc-event-selected .fc-event-resizer { + left: 50%; + margin-left: -4px; + margin-left: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); + } + +.fc-v-event.fc-event-selected .fc-event-resizer-start { + top: -4px; + top: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); + } + +.fc-v-event.fc-event-selected .fc-event-resizer-end { + bottom: -4px; + bottom: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); + } +.fc .fc-timegrid .fc-daygrid-body { /* the all-day daygrid within the timegrid view */ + z-index: 2; /* put above the timegrid-body so that more-popover is above everything. TODO: better solution */ + } +.fc .fc-timegrid-divider { + padding: 0 0 2px; /* browsers get confused when you set height. use padding instead */ + } +.fc .fc-timegrid-body { + position: relative; + z-index: 1; /* scope the z-indexes of slots and cols */ + min-height: 100%; /* fill height always, even when slat table doesn't grow */ + } +.fc .fc-timegrid-axis-chunk { /* for advanced ScrollGrid */ + position: relative /* offset parent for now-indicator-container */ + + } +.fc .fc-timegrid-axis-chunk > table { + position: relative; + z-index: 1; /* above the now-indicator-container */ + } +.fc .fc-timegrid-slots { + position: relative; + z-index: 1; + } +.fc .fc-timegrid-slot { /* a */ + height: 1.5em; + border-bottom: 0 /* each cell owns its top border */ + } +.fc .fc-timegrid-slot:empty:before { + content: '\00a0'; /* make sure there's at least an empty space to create height for height syncing */ + } +.fc .fc-timegrid-slot-minor { + border-top-style: dotted; + } +.fc .fc-timegrid-slot-label-cushion { + display: inline-block; + white-space: nowrap; + } +.fc .fc-timegrid-slot-label { + vertical-align: middle; /* vertical align the slots */ + } +.fc { + + + /* slots AND axis cells (top-left corner of view including the "all-day" text) */ + +} +.fc .fc-timegrid-axis-cushion, + .fc .fc-timegrid-slot-label-cushion { + padding: 0 4px; + } +.fc { + + + /* axis cells (top-left corner of view including the "all-day" text) */ + /* vertical align is more complicated, uses flexbox */ + +} +.fc .fc-timegrid-axis-frame-liquid { + height: 100%; /* will need liquid-hack in FF */ + } +.fc .fc-timegrid-axis-frame { + overflow: hidden; + display: flex; + align-items: center; /* vertical align */ + justify-content: flex-end; /* horizontal align. matches text-align below */ + } +.fc .fc-timegrid-axis-cushion { + max-width: 60px; /* limits the width of the "all-day" text */ + flex-shrink: 0; /* allows text to expand how it normally would, regardless of constrained width */ + } +.fc-direction-ltr .fc-timegrid-slot-label-frame { + text-align: right; + } +.fc-direction-rtl .fc-timegrid-slot-label-frame { + text-align: left; + } +.fc-liquid-hack .fc-timegrid-axis-frame-liquid { + height: auto; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +.fc .fc-timegrid-col.fc-day-today { + background-color: rgba(255, 220, 40, 0.15); + background-color: var(--fc-today-bg-color, rgba(255, 220, 40, 0.15)); + } +.fc .fc-timegrid-col-frame { + min-height: 100%; /* liquid-hack is below */ + position: relative; + } +.fc-media-screen.fc-liquid-hack .fc-timegrid-col-frame { + height: auto; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +.fc-media-screen .fc-timegrid-cols { + position: absolute; /* no z-index. children will decide and go above slots */ + top: 0; + left: 0; + right: 0; + bottom: 0 + } +.fc-media-screen .fc-timegrid-cols > table { + height: 100%; + } +.fc-media-screen .fc-timegrid-col-bg, + .fc-media-screen .fc-timegrid-col-events, + .fc-media-screen .fc-timegrid-now-indicator-container { + position: absolute; + top: 0; + left: 0; + right: 0; + } +.fc { + + /* bg */ + +} +.fc .fc-timegrid-col-bg { + z-index: 2; /* TODO: kill */ + } +.fc .fc-timegrid-col-bg .fc-non-business { z-index: 1 } +.fc .fc-timegrid-col-bg .fc-bg-event { z-index: 2 } +.fc .fc-timegrid-col-bg .fc-highlight { z-index: 3 } +.fc .fc-timegrid-bg-harness { + position: absolute; /* top/bottom will be set by JS */ + left: 0; + right: 0; + } +.fc { + + /* fg events */ + /* (the mirror segs are put into a separate container with same classname, */ + /* and they must be after the normal seg container to appear at a higher z-index) */ + +} +.fc .fc-timegrid-col-events { + z-index: 3; + /* child event segs have z-indexes that are scoped within this div */ + } +.fc { + + /* now indicator */ + +} +.fc .fc-timegrid-now-indicator-container { + bottom: 0; + overflow: hidden; /* don't let overflow of lines/arrows cause unnecessary scrolling */ + /* z-index is set on the individual elements */ + } +.fc-direction-ltr .fc-timegrid-col-events { + margin: 0 2.5% 0 2px; + } +.fc-direction-rtl .fc-timegrid-col-events { + margin: 0 2px 0 2.5%; + } +.fc-timegrid-event-harness { + position: absolute /* top/left/right/bottom will all be set by JS */ +} +.fc-timegrid-event-harness > .fc-timegrid-event { + position: absolute; /* absolute WITHIN the harness */ + top: 0; /* for when not yet positioned */ + bottom: 0; /* " */ + left: 0; + right: 0; + } +.fc-timegrid-event-harness-inset .fc-timegrid-event, +.fc-timegrid-event.fc-event-mirror, +.fc-timegrid-more-link { + box-shadow: 0px 0px 0px 1px #fff; + box-shadow: 0px 0px 0px 1px var(--fc-page-bg-color, #fff); +} +.fc-timegrid-event, +.fc-timegrid-more-link { /* events need to be root */ + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); + border-radius: 3px; +} +.fc-timegrid-event { /* events need to be root */ + margin-bottom: 1px /* give some space from bottom */ +} +.fc-timegrid-event .fc-event-main { + padding: 1px 1px 0; + } +.fc-timegrid-event .fc-event-time { + white-space: nowrap; + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); + margin-bottom: 1px; + } +.fc-timegrid-event-short .fc-event-main-frame { + flex-direction: row; + overflow: hidden; + } +.fc-timegrid-event-short .fc-event-time:after { + content: '\00a0-\00a0'; /* dash surrounded by non-breaking spaces */ + } +.fc-timegrid-event-short .fc-event-title { + font-size: .85em; + font-size: var(--fc-small-font-size, .85em) + } +.fc-timegrid-more-link { /* does NOT inherit from fc-timegrid-event */ + position: absolute; + z-index: 9999; /* hack */ + color: inherit; + color: var(--fc-more-link-text-color, inherit); + background: #d0d0d0; + background: var(--fc-more-link-bg-color, #d0d0d0); + cursor: pointer; + margin-bottom: 1px; /* match space below fc-timegrid-event */ +} +.fc-timegrid-more-link-inner { /* has fc-sticky */ + padding: 3px 2px; + top: 0; +} +.fc-direction-ltr .fc-timegrid-more-link { + right: 0; + } +.fc-direction-rtl .fc-timegrid-more-link { + left: 0; + } +.fc { + + /* line */ + +} +.fc .fc-timegrid-now-indicator-line { + position: absolute; + z-index: 4; + left: 0; + right: 0; + border-style: solid; + border-color: red; + border-color: var(--fc-now-indicator-color, red); + border-width: 1px 0 0; + } +.fc { + + /* arrow */ + +} +.fc .fc-timegrid-now-indicator-arrow { + position: absolute; + z-index: 4; + margin-top: -5px; /* vertically center on top coordinate */ + border-style: solid; + border-color: red; + border-color: var(--fc-now-indicator-color, red); + } +.fc-direction-ltr .fc-timegrid-now-indicator-arrow { + left: 0; + + /* triangle pointing right. TODO: mixin */ + border-width: 5px 0 5px 6px; + border-top-color: transparent; + border-bottom-color: transparent; + } +.fc-direction-rtl .fc-timegrid-now-indicator-arrow { + right: 0; + + /* triangle pointing left. TODO: mixin */ + border-width: 5px 6px 5px 0; + border-top-color: transparent; + border-bottom-color: transparent; + } + + +:root { + --fc-list-event-dot-width: 10px; + --fc-list-event-hover-bg-color: #f5f5f5; +} +.fc-theme-standard .fc-list { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); + } +.fc { + + /* message when no events */ + +} +.fc .fc-list-empty { + background-color: rgba(208, 208, 208, 0.3); + background-color: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + height: 100%; + display: flex; + justify-content: center; + align-items: center; /* vertically aligns fc-list-empty-inner */ + } +.fc .fc-list-empty-cushion { + margin: 5em 0; + } +.fc { + + /* table within the scroller */ + /* ---------------------------------------------------------------------------------------------------- */ + +} +.fc .fc-list-table { + width: 100%; + border-style: hidden; /* kill outer border on theme */ + } +.fc .fc-list-table tr > * { + border-left: 0; + border-right: 0; + } +.fc .fc-list-sticky .fc-list-day > * { /* the cells */ + position: sticky; + top: 0; + background: #fff; + background: var(--fc-page-bg-color, #fff); /* for when headers are styled to be transparent and sticky */ + } +.fc .fc-list-table th { + padding: 0; /* uses an inner-wrapper instead... */ + } +.fc .fc-list-table td, + .fc .fc-list-day-cushion { + padding: 8px 14px; + } +.fc { + + + /* date heading rows */ + /* ---------------------------------------------------------------------------------------------------- */ + +} +.fc .fc-list-day-cushion:after { + content: ""; + clear: both; + display: table; /* clear floating */ + } +.fc-theme-standard .fc-list-day-cushion { + background-color: rgba(208, 208, 208, 0.3); + background-color: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + } +.fc-direction-ltr .fc-list-day-text, +.fc-direction-rtl .fc-list-day-side-text { + float: left; +} +.fc-direction-ltr .fc-list-day-side-text, +.fc-direction-rtl .fc-list-day-text { + float: right; +} +/* make the dot closer to the event title */ +.fc-direction-ltr .fc-list-table .fc-list-event-graphic { padding-right: 0 } +.fc-direction-rtl .fc-list-table .fc-list-event-graphic { padding-left: 0 } +.fc .fc-list-event.fc-event-forced-url { + cursor: pointer; /* whole row will seem clickable */ + } +.fc .fc-list-event:hover td { + background-color: #f5f5f5; + background-color: var(--fc-list-event-hover-bg-color, #f5f5f5); + } +.fc { + + /* shrink certain cols */ + +} +.fc .fc-list-event-graphic, + .fc .fc-list-event-time { + white-space: nowrap; + width: 1px; + } +.fc .fc-list-event-dot { + display: inline-block; + box-sizing: content-box; + width: 0; + height: 0; + border: 5px solid #3788d8; + border: calc(var(--fc-list-event-dot-width, 10px) / 2) solid var(--fc-event-border-color, #3788d8); + border-radius: 5px; + border-radius: calc(var(--fc-list-event-dot-width, 10px) / 2); + } +.fc { + + /* reset styling */ + +} +.fc .fc-list-event-title a { + color: inherit; + text-decoration: none; + } +.fc { + + /* underline link when hovering over any part of row */ + +} +.fc .fc-list-event.fc-event-forced-url:hover a { + text-decoration: underline; + } + + + + .fc-theme-bootstrap a:not([href]) { + color: inherit; /* natural color for navlinks */ + } + diff --git a/apps/schoolCalendar/fullcalendar/main.js b/apps/schoolCalendar/fullcalendar/main.js new file mode 100644 index 000000000..54bf45d3f --- /dev/null +++ b/apps/schoolCalendar/fullcalendar/main.js @@ -0,0 +1,14738 @@ +/*! +FullCalendar v5.9.0 +Docs & License: https://fullcalendar.io/ +(c) 2021 Adam Shaw +*/ +var FullCalendar = (function (exports) { + 'use strict'; + + /*! ***************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + /* global Reflect, Promise */ + + var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + + function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + + function __spreadArray(to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || from); + } + + var n,u,i$1,t,o,r$1={},f$1=[],e$1=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function c$1(n,l){for(var u in l)n[u]=l[u];return n}function s(n){var l=n.parentNode;l&&l.removeChild(n);}function a$1(n,l,u){var i,t,o,r=arguments,f={};for(o in l)"key"==o?i=l[o]:"ref"==o?t=l[o]:f[o]=l[o];if(arguments.length>3)for(u=[u],o=3;o0?v$1(k.type,k.props,k.key,null,k.__v):k)){if(k.__=u,k.__b=u.__b+1,null===(_=A[h])||_&&k.key==_.key&&k.type===_.type)A[h]=void 0;else for(p=0;p3;)e.pop()();if(e[1]>>1,1),t.i.removeChild(n);}}),N(a$1(T,{context:t.context},n.__v),t.l)):t.l&&t.componentWillUnmount();}function I(n,t){return a$1(j,{__v:n,i:t})}(F.prototype=new p).__e=function(n){var t=this,e=U(t.__v),r=t.o.get(n);return r[0]++,function(u){var o=function(){t.props.revealOrder?(r.push(u),M(t,n,r)):u();};e?e(o):o();}},F.prototype.render=function(n){this.u=null,this.o=new Map;var t=w$1(n.children);n.revealOrder&&"b"===n.revealOrder[0]&&t.reverse();for(var e=t.length;e--;)this.o.set(t[e],this.u=[1,0,this.u]);return n.children},F.prototype.componentDidUpdate=F.prototype.componentDidMount=function(){var n=this;this.o.forEach(function(t,e){M(n,e,t);});};var W="undefined"!=typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103,P=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|fill|flood|font|glyph(?!R)|horiz|marker(?!H|W|U)|overline|paint|stop|strikethrough|stroke|text(?!L)|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,V=function(n){return ("undefined"!=typeof Symbol&&"symbol"==typeof Symbol()?/fil|che|rad/i:/fil|che|ra/i).test(n)};p.prototype.isReactComponent={},["componentWillMount","componentWillReceiveProps","componentWillUpdate"].forEach(function(n){Object.defineProperty(p.prototype,n,{configurable:!0,get:function(){return this["UNSAFE_"+n]},set:function(t){Object.defineProperty(this,n,{configurable:!0,writable:!0,value:t});}});});var H=n.event;function Z(){}function Y(){return this.cancelBubble}function $(){return this.defaultPrevented}n.event=function(n){return H&&(n=H(n)),n.persist=Z,n.isPropagationStopped=Y,n.isDefaultPrevented=$,n.nativeEvent=n};var G={configurable:!0,get:function(){return this.class}},J=n.vnode;n.vnode=function(n){var t=n.type,e=n.props,r=e;if("string"==typeof t){for(var u in r={},e){var o=e[u];"value"===u&&"defaultValue"in e&&null==o||("defaultValue"===u&&"value"in e&&null==e.value?u="value":"download"===u&&!0===o?o="":/ondoubleclick/i.test(u)?u="ondblclick":/^onchange(textarea|input)/i.test(u+t)&&!V(e.type)?u="oninput":/^on(Ani|Tra|Tou|BeforeInp)/.test(u)?u=u.toLowerCase():P.test(u)?u=u.replace(/[A-Z0-9]/,"-$&").toLowerCase():null===o&&(o=void 0),r[u]=o);}"select"==t&&r.multiple&&Array.isArray(r.value)&&(r.value=w$1(e.children).forEach(function(n){n.props.selected=-1!=r.value.indexOf(n.props.value);})),"select"==t&&null!=r.defaultValue&&(r.value=w$1(e.children).forEach(function(n){n.props.selected=r.multiple?-1!=r.defaultValue.indexOf(n.props.value):r.defaultValue==n.props.value;})),n.props=r;}t&&e.class!=e.className&&(G.enumerable="className"in e,null!=e.className&&(r.class=e.className),Object.defineProperty(r,"className",G)),n.$$typeof=W,J&&J(n);};var K=n.__r;n.__r=function(n){K&&K(n);};"object"==typeof performance&&"function"==typeof performance.now?performance.now.bind(performance):function(){return Date.now()}; + + var globalObj = typeof globalThis !== 'undefined' ? globalThis : window; // // TODO: streamline when killing IE11 support + if (globalObj.FullCalendarVDom) { + console.warn('FullCalendar VDOM already loaded'); + } + else { + globalObj.FullCalendarVDom = { + Component: p, + createElement: a$1, + render: N, + createRef: h, + Fragment: y, + createContext: createContext$1, + createPortal: I, + flushToDom: flushToDom$1, + unmountComponentAtNode: unmountComponentAtNode$1, + }; + } + // HACKS... + // TODO: lock version + // TODO: link gh issues + function flushToDom$1() { + var oldDebounceRendering = n.debounceRendering; // orig + var callbackQ = []; + function execCallbackSync(callback) { + callbackQ.push(callback); + } + n.debounceRendering = execCallbackSync; + N(a$1(FakeComponent, {}), document.createElement('div')); + while (callbackQ.length) { + callbackQ.shift()(); + } + n.debounceRendering = oldDebounceRendering; + } + var FakeComponent = /** @class */ (function (_super) { + __extends(FakeComponent, _super); + function FakeComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + FakeComponent.prototype.render = function () { return a$1('div', {}); }; + FakeComponent.prototype.componentDidMount = function () { this.setState({}); }; + return FakeComponent; + }(p)); + function createContext$1(defaultValue) { + var ContextType = q(defaultValue); + var origProvider = ContextType.Provider; + ContextType.Provider = function () { + var _this = this; + var isNew = !this.getChildContext; + var children = origProvider.apply(this, arguments); // eslint-disable-line prefer-rest-params + if (isNew) { + var subs_1 = []; + this.shouldComponentUpdate = function (_props) { + if (_this.props.value !== _props.value) { + subs_1.forEach(function (c) { + c.context = _props.value; + c.forceUpdate(); + }); + } + }; + this.sub = function (c) { + subs_1.push(c); + var old = c.componentWillUnmount; + c.componentWillUnmount = function () { + subs_1.splice(subs_1.indexOf(c), 1); + old && old.call(c); + }; + }; + } + return children; + }; + return ContextType; + } + function unmountComponentAtNode$1(node) { + N(null, node); + } + + // no public types yet. when there are, export from: + // import {} from './api-type-deps' + var EventSourceApi = /** @class */ (function () { + function EventSourceApi(context, internalEventSource) { + this.context = context; + this.internalEventSource = internalEventSource; + } + EventSourceApi.prototype.remove = function () { + this.context.dispatch({ + type: 'REMOVE_EVENT_SOURCE', + sourceId: this.internalEventSource.sourceId, + }); + }; + EventSourceApi.prototype.refetch = function () { + this.context.dispatch({ + type: 'FETCH_EVENT_SOURCES', + sourceIds: [this.internalEventSource.sourceId], + isRefetch: true, + }); + }; + Object.defineProperty(EventSourceApi.prototype, "id", { + get: function () { + return this.internalEventSource.publicId; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventSourceApi.prototype, "url", { + get: function () { + return this.internalEventSource.meta.url; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventSourceApi.prototype, "format", { + get: function () { + return this.internalEventSource.meta.format; // TODO: bad. not guaranteed + }, + enumerable: false, + configurable: true + }); + return EventSourceApi; + }()); + + function removeElement(el) { + if (el.parentNode) { + el.parentNode.removeChild(el); + } + } + // Querying + // ---------------------------------------------------------------------------------------------------------------- + function elementClosest(el, selector) { + if (el.closest) { + return el.closest(selector); + // really bad fallback for IE + // from https://developer.mozilla.org/en-US/docs/Web/API/Element/closest + } + if (!document.documentElement.contains(el)) { + return null; + } + do { + if (elementMatches(el, selector)) { + return el; + } + el = (el.parentElement || el.parentNode); + } while (el !== null && el.nodeType === 1); + return null; + } + function elementMatches(el, selector) { + var method = el.matches || el.matchesSelector || el.msMatchesSelector; + return method.call(el, selector); + } + // accepts multiple subject els + // returns a real array. good for methods like forEach + // TODO: accept the document + function findElements(container, selector) { + var containers = container instanceof HTMLElement ? [container] : container; + var allMatches = []; + for (var i = 0; i < containers.length; i += 1) { + var matches = containers[i].querySelectorAll(selector); + for (var j = 0; j < matches.length; j += 1) { + allMatches.push(matches[j]); + } + } + return allMatches; + } + // accepts multiple subject els + // only queries direct child elements // TODO: rename to findDirectChildren! + function findDirectChildren(parent, selector) { + var parents = parent instanceof HTMLElement ? [parent] : parent; + var allMatches = []; + for (var i = 0; i < parents.length; i += 1) { + var childNodes = parents[i].children; // only ever elements + for (var j = 0; j < childNodes.length; j += 1) { + var childNode = childNodes[j]; + if (!selector || elementMatches(childNode, selector)) { + allMatches.push(childNode); + } + } + } + return allMatches; + } + // Style + // ---------------------------------------------------------------------------------------------------------------- + var PIXEL_PROP_RE = /(top|left|right|bottom|width|height)$/i; + function applyStyle(el, props) { + for (var propName in props) { + applyStyleProp(el, propName, props[propName]); + } + } + function applyStyleProp(el, name, val) { + if (val == null) { + el.style[name] = ''; + } + else if (typeof val === 'number' && PIXEL_PROP_RE.test(name)) { + el.style[name] = val + "px"; + } + else { + el.style[name] = val; + } + } + // Event Handling + // ---------------------------------------------------------------------------------------------------------------- + // if intercepting bubbled events at the document/window/body level, + // and want to see originating element (the 'target'), use this util instead + // of `ev.target` because it goes within web-component boundaries. + function getEventTargetViaRoot(ev) { + var _a, _b; + return (_b = (_a = ev.composedPath) === null || _a === void 0 ? void 0 : _a.call(ev)[0]) !== null && _b !== void 0 ? _b : ev.target; + } + // Shadow DOM consuderations + // ---------------------------------------------------------------------------------------------------------------- + function getElRoot(el) { + return el.getRootNode ? el.getRootNode() : document; + } + + // Stops a mouse/touch event from doing it's native browser action + function preventDefault(ev) { + ev.preventDefault(); + } + // Event Delegation + // ---------------------------------------------------------------------------------------------------------------- + function buildDelegationHandler(selector, handler) { + return function (ev) { + var matchedChild = elementClosest(ev.target, selector); + if (matchedChild) { + handler.call(matchedChild, ev, matchedChild); + } + }; + } + function listenBySelector(container, eventType, selector, handler) { + var attachedHandler = buildDelegationHandler(selector, handler); + container.addEventListener(eventType, attachedHandler); + return function () { + container.removeEventListener(eventType, attachedHandler); + }; + } + function listenToHoverBySelector(container, selector, onMouseEnter, onMouseLeave) { + var currentMatchedChild; + return listenBySelector(container, 'mouseover', selector, function (mouseOverEv, matchedChild) { + if (matchedChild !== currentMatchedChild) { + currentMatchedChild = matchedChild; + onMouseEnter(mouseOverEv, matchedChild); + var realOnMouseLeave_1 = function (mouseLeaveEv) { + currentMatchedChild = null; + onMouseLeave(mouseLeaveEv, matchedChild); + matchedChild.removeEventListener('mouseleave', realOnMouseLeave_1); + }; + // listen to the next mouseleave, and then unattach + matchedChild.addEventListener('mouseleave', realOnMouseLeave_1); + } + }); + } + // Animation + // ---------------------------------------------------------------------------------------------------------------- + var transitionEventNames = [ + 'webkitTransitionEnd', + 'otransitionend', + 'oTransitionEnd', + 'msTransitionEnd', + 'transitionend', + ]; + // triggered only when the next single subsequent transition finishes + function whenTransitionDone(el, callback) { + var realCallback = function (ev) { + callback(ev); + transitionEventNames.forEach(function (eventName) { + el.removeEventListener(eventName, realCallback); + }); + }; + transitionEventNames.forEach(function (eventName) { + el.addEventListener(eventName, realCallback); // cross-browser way to determine when the transition finishes + }); + } + + var guidNumber = 0; + function guid() { + guidNumber += 1; + return String(guidNumber); + } + /* FullCalendar-specific DOM Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + // Make the mouse cursor express that an event is not allowed in the current area + function disableCursor() { + document.body.classList.add('fc-not-allowed'); + } + // Returns the mouse cursor to its original look + function enableCursor() { + document.body.classList.remove('fc-not-allowed'); + } + /* Selection + ----------------------------------------------------------------------------------------------------------------------*/ + function preventSelection(el) { + el.classList.add('fc-unselectable'); + el.addEventListener('selectstart', preventDefault); + } + function allowSelection(el) { + el.classList.remove('fc-unselectable'); + el.removeEventListener('selectstart', preventDefault); + } + /* Context Menu + ----------------------------------------------------------------------------------------------------------------------*/ + function preventContextMenu(el) { + el.addEventListener('contextmenu', preventDefault); + } + function allowContextMenu(el) { + el.removeEventListener('contextmenu', preventDefault); + } + function parseFieldSpecs(input) { + var specs = []; + var tokens = []; + var i; + var token; + if (typeof input === 'string') { + tokens = input.split(/\s*,\s*/); + } + else if (typeof input === 'function') { + tokens = [input]; + } + else if (Array.isArray(input)) { + tokens = input; + } + for (i = 0; i < tokens.length; i += 1) { + token = tokens[i]; + if (typeof token === 'string') { + specs.push(token.charAt(0) === '-' ? + { field: token.substring(1), order: -1 } : + { field: token, order: 1 }); + } + else if (typeof token === 'function') { + specs.push({ func: token }); + } + } + return specs; + } + function compareByFieldSpecs(obj0, obj1, fieldSpecs) { + var i; + var cmp; + for (i = 0; i < fieldSpecs.length; i += 1) { + cmp = compareByFieldSpec(obj0, obj1, fieldSpecs[i]); + if (cmp) { + return cmp; + } + } + return 0; + } + function compareByFieldSpec(obj0, obj1, fieldSpec) { + if (fieldSpec.func) { + return fieldSpec.func(obj0, obj1); + } + return flexibleCompare(obj0[fieldSpec.field], obj1[fieldSpec.field]) + * (fieldSpec.order || 1); + } + function flexibleCompare(a, b) { + if (!a && !b) { + return 0; + } + if (b == null) { + return -1; + } + if (a == null) { + return 1; + } + if (typeof a === 'string' || typeof b === 'string') { + return String(a).localeCompare(String(b)); + } + return a - b; + } + /* String Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + function padStart(val, len) { + var s = String(val); + return '000'.substr(0, len - s.length) + s; + } + /* Number Utilities + ----------------------------------------------------------------------------------------------------------------------*/ + function compareNumbers(a, b) { + return a - b; + } + function isInt(n) { + return n % 1 === 0; + } + /* FC-specific DOM dimension stuff + ----------------------------------------------------------------------------------------------------------------------*/ + function computeSmallestCellWidth(cellEl) { + var allWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-frame'); + var contentWidthEl = cellEl.querySelector('.fc-scrollgrid-shrink-cushion'); + if (!allWidthEl) { + throw new Error('needs fc-scrollgrid-shrink-frame className'); // TODO: use const + } + if (!contentWidthEl) { + throw new Error('needs fc-scrollgrid-shrink-cushion className'); + } + return cellEl.getBoundingClientRect().width - allWidthEl.getBoundingClientRect().width + // the cell padding+border + contentWidthEl.getBoundingClientRect().width; + } + + var DAY_IDS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; + // Adding + function addWeeks(m, n) { + var a = dateToUtcArray(m); + a[2] += n * 7; + return arrayToUtcDate(a); + } + function addDays(m, n) { + var a = dateToUtcArray(m); + a[2] += n; + return arrayToUtcDate(a); + } + function addMs(m, n) { + var a = dateToUtcArray(m); + a[6] += n; + return arrayToUtcDate(a); + } + // Diffing (all return floats) + // TODO: why not use ranges? + function diffWeeks(m0, m1) { + return diffDays(m0, m1) / 7; + } + function diffDays(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60 * 24); + } + function diffHours(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60 * 60); + } + function diffMinutes(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / (1000 * 60); + } + function diffSeconds(m0, m1) { + return (m1.valueOf() - m0.valueOf()) / 1000; + } + function diffDayAndTime(m0, m1) { + var m0day = startOfDay(m0); + var m1day = startOfDay(m1); + return { + years: 0, + months: 0, + days: Math.round(diffDays(m0day, m1day)), + milliseconds: (m1.valueOf() - m1day.valueOf()) - (m0.valueOf() - m0day.valueOf()), + }; + } + // Diffing Whole Units + function diffWholeWeeks(m0, m1) { + var d = diffWholeDays(m0, m1); + if (d !== null && d % 7 === 0) { + return d / 7; + } + return null; + } + function diffWholeDays(m0, m1) { + if (timeAsMs(m0) === timeAsMs(m1)) { + return Math.round(diffDays(m0, m1)); + } + return null; + } + // Start-Of + function startOfDay(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + ]); + } + function startOfHour(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + ]); + } + function startOfMinute(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + m.getUTCMinutes(), + ]); + } + function startOfSecond(m) { + return arrayToUtcDate([ + m.getUTCFullYear(), + m.getUTCMonth(), + m.getUTCDate(), + m.getUTCHours(), + m.getUTCMinutes(), + m.getUTCSeconds(), + ]); + } + // Week Computation + function weekOfYear(marker, dow, doy) { + var y = marker.getUTCFullYear(); + var w = weekOfGivenYear(marker, y, dow, doy); + if (w < 1) { + return weekOfGivenYear(marker, y - 1, dow, doy); + } + var nextW = weekOfGivenYear(marker, y + 1, dow, doy); + if (nextW >= 1) { + return Math.min(w, nextW); + } + return w; + } + function weekOfGivenYear(marker, year, dow, doy) { + var firstWeekStart = arrayToUtcDate([year, 0, 1 + firstWeekOffset(year, dow, doy)]); + var dayStart = startOfDay(marker); + var days = Math.round(diffDays(firstWeekStart, dayStart)); + return Math.floor(days / 7) + 1; // zero-indexed + } + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + // first-week day -- which january is always in the first week (4 for iso, 1 for other) + var fwd = 7 + dow - doy; + // first-week day local weekday -- which local weekday is fwd + var fwdlw = (7 + arrayToUtcDate([year, 0, fwd]).getUTCDay() - dow) % 7; + return -fwdlw + fwd - 1; + } + // Array Conversion + function dateToLocalArray(date) { + return [ + date.getFullYear(), + date.getMonth(), + date.getDate(), + date.getHours(), + date.getMinutes(), + date.getSeconds(), + date.getMilliseconds(), + ]; + } + function arrayToLocalDate(a) { + return new Date(a[0], a[1] || 0, a[2] == null ? 1 : a[2], // day of month + a[3] || 0, a[4] || 0, a[5] || 0); + } + function dateToUtcArray(date) { + return [ + date.getUTCFullYear(), + date.getUTCMonth(), + date.getUTCDate(), + date.getUTCHours(), + date.getUTCMinutes(), + date.getUTCSeconds(), + date.getUTCMilliseconds(), + ]; + } + function arrayToUtcDate(a) { + // according to web standards (and Safari), a month index is required. + // massage if only given a year. + if (a.length === 1) { + a = a.concat([0]); + } + return new Date(Date.UTC.apply(Date, a)); + } + // Other Utils + function isValidDate(m) { + return !isNaN(m.valueOf()); + } + function timeAsMs(m) { + return m.getUTCHours() * 1000 * 60 * 60 + + m.getUTCMinutes() * 1000 * 60 + + m.getUTCSeconds() * 1000 + + m.getUTCMilliseconds(); + } + + function createEventInstance(defId, range, forcedStartTzo, forcedEndTzo) { + return { + instanceId: guid(), + defId: defId, + range: range, + forcedStartTzo: forcedStartTzo == null ? null : forcedStartTzo, + forcedEndTzo: forcedEndTzo == null ? null : forcedEndTzo, + }; + } + + var hasOwnProperty = Object.prototype.hasOwnProperty; + // Merges an array of objects into a single object. + // The second argument allows for an array of property names who's object values will be merged together. + function mergeProps(propObjs, complexPropsMap) { + var dest = {}; + if (complexPropsMap) { + for (var name_1 in complexPropsMap) { + var complexObjs = []; + // collect the trailing object values, stopping when a non-object is discovered + for (var i = propObjs.length - 1; i >= 0; i -= 1) { + var val = propObjs[i][name_1]; + if (typeof val === 'object' && val) { // non-null object + complexObjs.unshift(val); + } + else if (val !== undefined) { + dest[name_1] = val; // if there were no objects, this value will be used + break; + } + } + // if the trailing values were objects, use the merged value + if (complexObjs.length) { + dest[name_1] = mergeProps(complexObjs); + } + } + } + // copy values into the destination, going from last to first + for (var i = propObjs.length - 1; i >= 0; i -= 1) { + var props = propObjs[i]; + for (var name_2 in props) { + if (!(name_2 in dest)) { // if already assigned by previous props or complex props, don't reassign + dest[name_2] = props[name_2]; + } + } + } + return dest; + } + function filterHash(hash, func) { + var filtered = {}; + for (var key in hash) { + if (func(hash[key], key)) { + filtered[key] = hash[key]; + } + } + return filtered; + } + function mapHash(hash, func) { + var newHash = {}; + for (var key in hash) { + newHash[key] = func(hash[key], key); + } + return newHash; + } + function arrayToHash(a) { + var hash = {}; + for (var _i = 0, a_1 = a; _i < a_1.length; _i++) { + var item = a_1[_i]; + hash[item] = true; + } + return hash; + } + function buildHashFromArray(a, func) { + var hash = {}; + for (var i = 0; i < a.length; i += 1) { + var tuple = func(a[i], i); + hash[tuple[0]] = tuple[1]; + } + return hash; + } + function hashValuesToArray(obj) { + var a = []; + for (var key in obj) { + a.push(obj[key]); + } + return a; + } + function isPropsEqual(obj0, obj1) { + if (obj0 === obj1) { + return true; + } + for (var key in obj0) { + if (hasOwnProperty.call(obj0, key)) { + if (!(key in obj1)) { + return false; + } + } + } + for (var key in obj1) { + if (hasOwnProperty.call(obj1, key)) { + if (obj0[key] !== obj1[key]) { + return false; + } + } + } + return true; + } + function getUnequalProps(obj0, obj1) { + var keys = []; + for (var key in obj0) { + if (hasOwnProperty.call(obj0, key)) { + if (!(key in obj1)) { + keys.push(key); + } + } + } + for (var key in obj1) { + if (hasOwnProperty.call(obj1, key)) { + if (obj0[key] !== obj1[key]) { + keys.push(key); + } + } + } + return keys; + } + function compareObjs(oldProps, newProps, equalityFuncs) { + if (equalityFuncs === void 0) { equalityFuncs = {}; } + if (oldProps === newProps) { + return true; + } + for (var key in newProps) { + if (key in oldProps && isObjValsEqual(oldProps[key], newProps[key], equalityFuncs[key])) ; + else { + return false; + } + } + // check for props that were omitted in the new + for (var key in oldProps) { + if (!(key in newProps)) { + return false; + } + } + return true; + } + /* + assumed "true" equality for handler names like "onReceiveSomething" + */ + function isObjValsEqual(val0, val1, comparator) { + if (val0 === val1 || comparator === true) { + return true; + } + if (comparator) { + return comparator(val0, val1); + } + return false; + } + function collectFromHash(hash, startIndex, endIndex, step) { + if (startIndex === void 0) { startIndex = 0; } + if (step === void 0) { step = 1; } + var res = []; + if (endIndex == null) { + endIndex = Object.keys(hash).length; + } + for (var i = startIndex; i < endIndex; i += step) { + var val = hash[i]; + if (val !== undefined) { // will disregard undefined for sparse arrays + res.push(val); + } + } + return res; + } + + function parseRecurring(refined, defaultAllDay, dateEnv, recurringTypes) { + for (var i = 0; i < recurringTypes.length; i += 1) { + var parsed = recurringTypes[i].parse(refined, dateEnv); + if (parsed) { + var allDay = refined.allDay; + if (allDay == null) { + allDay = defaultAllDay; + if (allDay == null) { + allDay = parsed.allDayGuess; + if (allDay == null) { + allDay = false; + } + } + } + return { + allDay: allDay, + duration: parsed.duration, + typeData: parsed.typeData, + typeId: i, + }; + } + } + return null; + } + function expandRecurring(eventStore, framingRange, context) { + var dateEnv = context.dateEnv, pluginHooks = context.pluginHooks, options = context.options; + var defs = eventStore.defs, instances = eventStore.instances; + // remove existing recurring instances + // TODO: bad. always expand events as a second step + instances = filterHash(instances, function (instance) { return !defs[instance.defId].recurringDef; }); + for (var defId in defs) { + var def = defs[defId]; + if (def.recurringDef) { + var duration = def.recurringDef.duration; + if (!duration) { + duration = def.allDay ? + options.defaultAllDayEventDuration : + options.defaultTimedEventDuration; + } + var starts = expandRecurringRanges(def, duration, framingRange, dateEnv, pluginHooks.recurringTypes); + for (var _i = 0, starts_1 = starts; _i < starts_1.length; _i++) { + var start = starts_1[_i]; + var instance = createEventInstance(defId, { + start: start, + end: dateEnv.add(start, duration), + }); + instances[instance.instanceId] = instance; + } + } + } + return { defs: defs, instances: instances }; + } + /* + Event MUST have a recurringDef + */ + function expandRecurringRanges(eventDef, duration, framingRange, dateEnv, recurringTypes) { + var typeDef = recurringTypes[eventDef.recurringDef.typeId]; + var markers = typeDef.expand(eventDef.recurringDef.typeData, { + start: dateEnv.subtract(framingRange.start, duration), + end: framingRange.end, + }, dateEnv); + // the recurrence plugins don't guarantee that all-day events are start-of-day, so we have to + if (eventDef.allDay) { + markers = markers.map(startOfDay); + } + return markers; + } + + var INTERNAL_UNITS = ['years', 'months', 'days', 'milliseconds']; + var PARSE_RE = /^(-?)(?:(\d+)\.)?(\d+):(\d\d)(?::(\d\d)(?:\.(\d\d\d))?)?/; + // Parsing and Creation + function createDuration(input, unit) { + var _a; + if (typeof input === 'string') { + return parseString(input); + } + if (typeof input === 'object' && input) { // non-null object + return parseObject(input); + } + if (typeof input === 'number') { + return parseObject((_a = {}, _a[unit || 'milliseconds'] = input, _a)); + } + return null; + } + function parseString(s) { + var m = PARSE_RE.exec(s); + if (m) { + var sign = m[1] ? -1 : 1; + return { + years: 0, + months: 0, + days: sign * (m[2] ? parseInt(m[2], 10) : 0), + milliseconds: sign * ((m[3] ? parseInt(m[3], 10) : 0) * 60 * 60 * 1000 + // hours + (m[4] ? parseInt(m[4], 10) : 0) * 60 * 1000 + // minutes + (m[5] ? parseInt(m[5], 10) : 0) * 1000 + // seconds + (m[6] ? parseInt(m[6], 10) : 0) // ms + ), + }; + } + return null; + } + function parseObject(obj) { + var duration = { + years: obj.years || obj.year || 0, + months: obj.months || obj.month || 0, + days: obj.days || obj.day || 0, + milliseconds: (obj.hours || obj.hour || 0) * 60 * 60 * 1000 + // hours + (obj.minutes || obj.minute || 0) * 60 * 1000 + // minutes + (obj.seconds || obj.second || 0) * 1000 + // seconds + (obj.milliseconds || obj.millisecond || obj.ms || 0), // ms + }; + var weeks = obj.weeks || obj.week; + if (weeks) { + duration.days += weeks * 7; + duration.specifiedWeeks = true; + } + return duration; + } + // Equality + function durationsEqual(d0, d1) { + return d0.years === d1.years && + d0.months === d1.months && + d0.days === d1.days && + d0.milliseconds === d1.milliseconds; + } + function asCleanDays(dur) { + if (!dur.years && !dur.months && !dur.milliseconds) { + return dur.days; + } + return 0; + } + // Simple Math + function addDurations(d0, d1) { + return { + years: d0.years + d1.years, + months: d0.months + d1.months, + days: d0.days + d1.days, + milliseconds: d0.milliseconds + d1.milliseconds, + }; + } + function subtractDurations(d1, d0) { + return { + years: d1.years - d0.years, + months: d1.months - d0.months, + days: d1.days - d0.days, + milliseconds: d1.milliseconds - d0.milliseconds, + }; + } + function multiplyDuration(d, n) { + return { + years: d.years * n, + months: d.months * n, + days: d.days * n, + milliseconds: d.milliseconds * n, + }; + } + // Conversions + // "Rough" because they are based on average-case Gregorian months/years + function asRoughYears(dur) { + return asRoughDays(dur) / 365; + } + function asRoughMonths(dur) { + return asRoughDays(dur) / 30; + } + function asRoughDays(dur) { + return asRoughMs(dur) / 864e5; + } + function asRoughMinutes(dur) { + return asRoughMs(dur) / (1000 * 60); + } + function asRoughSeconds(dur) { + return asRoughMs(dur) / 1000; + } + function asRoughMs(dur) { + return dur.years * (365 * 864e5) + + dur.months * (30 * 864e5) + + dur.days * 864e5 + + dur.milliseconds; + } + // Advanced Math + function wholeDivideDurations(numerator, denominator) { + var res = null; + for (var i = 0; i < INTERNAL_UNITS.length; i += 1) { + var unit = INTERNAL_UNITS[i]; + if (denominator[unit]) { + var localRes = numerator[unit] / denominator[unit]; + if (!isInt(localRes) || (res !== null && res !== localRes)) { + return null; + } + res = localRes; + } + else if (numerator[unit]) { + // needs to divide by something but can't! + return null; + } + } + return res; + } + function greatestDurationDenominator(dur) { + var ms = dur.milliseconds; + if (ms) { + if (ms % 1000 !== 0) { + return { unit: 'millisecond', value: ms }; + } + if (ms % (1000 * 60) !== 0) { + return { unit: 'second', value: ms / 1000 }; + } + if (ms % (1000 * 60 * 60) !== 0) { + return { unit: 'minute', value: ms / (1000 * 60) }; + } + if (ms) { + return { unit: 'hour', value: ms / (1000 * 60 * 60) }; + } + } + if (dur.days) { + if (dur.specifiedWeeks && dur.days % 7 === 0) { + return { unit: 'week', value: dur.days / 7 }; + } + return { unit: 'day', value: dur.days }; + } + if (dur.months) { + return { unit: 'month', value: dur.months }; + } + if (dur.years) { + return { unit: 'year', value: dur.years }; + } + return { unit: 'millisecond', value: 0 }; + } + + // timeZoneOffset is in minutes + function buildIsoString(marker, timeZoneOffset, stripZeroTime) { + if (stripZeroTime === void 0) { stripZeroTime = false; } + var s = marker.toISOString(); + s = s.replace('.000', ''); + if (stripZeroTime) { + s = s.replace('T00:00:00Z', ''); + } + if (s.length > 10) { // time part wasn't stripped, can add timezone info + if (timeZoneOffset == null) { + s = s.replace('Z', ''); + } + else if (timeZoneOffset !== 0) { + s = s.replace('Z', formatTimeZoneOffset(timeZoneOffset, true)); + } + // otherwise, its UTC-0 and we want to keep the Z + } + return s; + } + // formats the date, but with no time part + // TODO: somehow merge with buildIsoString and stripZeroTime + // TODO: rename. omit "string" + function formatDayString(marker) { + return marker.toISOString().replace(/T.*$/, ''); + } + // TODO: use Date::toISOString and use everything after the T? + function formatIsoTimeString(marker) { + return padStart(marker.getUTCHours(), 2) + ':' + + padStart(marker.getUTCMinutes(), 2) + ':' + + padStart(marker.getUTCSeconds(), 2); + } + function formatTimeZoneOffset(minutes, doIso) { + if (doIso === void 0) { doIso = false; } + var sign = minutes < 0 ? '-' : '+'; + var abs = Math.abs(minutes); + var hours = Math.floor(abs / 60); + var mins = Math.round(abs % 60); + if (doIso) { + return sign + padStart(hours, 2) + ":" + padStart(mins, 2); + } + return "GMT" + sign + hours + (mins ? ":" + padStart(mins, 2) : ''); + } + + // TODO: new util arrayify? + function removeExact(array, exactVal) { + var removeCnt = 0; + var i = 0; + while (i < array.length) { + if (array[i] === exactVal) { + array.splice(i, 1); + removeCnt += 1; + } + else { + i += 1; + } + } + return removeCnt; + } + function isArraysEqual(a0, a1, equalityFunc) { + if (a0 === a1) { + return true; + } + var len = a0.length; + var i; + if (len !== a1.length) { // not array? or not same length? + return false; + } + for (i = 0; i < len; i += 1) { + if (!(equalityFunc ? equalityFunc(a0[i], a1[i]) : a0[i] === a1[i])) { + return false; + } + } + return true; + } + + function memoize(workerFunc, resEquality, teardownFunc) { + var currentArgs; + var currentRes; + return function () { + var newArgs = []; + for (var _i = 0; _i < arguments.length; _i++) { + newArgs[_i] = arguments[_i]; + } + if (!currentArgs) { + currentRes = workerFunc.apply(this, newArgs); + } + else if (!isArraysEqual(currentArgs, newArgs)) { + if (teardownFunc) { + teardownFunc(currentRes); + } + var res = workerFunc.apply(this, newArgs); + if (!resEquality || !resEquality(res, currentRes)) { + currentRes = res; + } + } + currentArgs = newArgs; + return currentRes; + }; + } + function memoizeObjArg(workerFunc, resEquality, teardownFunc) { + var _this = this; + var currentArg; + var currentRes; + return function (newArg) { + if (!currentArg) { + currentRes = workerFunc.call(_this, newArg); + } + else if (!isPropsEqual(currentArg, newArg)) { + if (teardownFunc) { + teardownFunc(currentRes); + } + var res = workerFunc.call(_this, newArg); + if (!resEquality || !resEquality(res, currentRes)) { + currentRes = res; + } + } + currentArg = newArg; + return currentRes; + }; + } + function memoizeArraylike(// used at all? + workerFunc, resEquality, teardownFunc) { + var _this = this; + var currentArgSets = []; + var currentResults = []; + return function (newArgSets) { + var currentLen = currentArgSets.length; + var newLen = newArgSets.length; + var i = 0; + for (; i < currentLen; i += 1) { + if (!newArgSets[i]) { // one of the old sets no longer exists + if (teardownFunc) { + teardownFunc(currentResults[i]); + } + } + else if (!isArraysEqual(currentArgSets[i], newArgSets[i])) { + if (teardownFunc) { + teardownFunc(currentResults[i]); + } + var res = workerFunc.apply(_this, newArgSets[i]); + if (!resEquality || !resEquality(res, currentResults[i])) { + currentResults[i] = res; + } + } + } + for (; i < newLen; i += 1) { + currentResults[i] = workerFunc.apply(_this, newArgSets[i]); + } + currentArgSets = newArgSets; + currentResults.splice(newLen); // remove excess + return currentResults; + }; + } + function memoizeHashlike(// used? + workerFunc, resEquality, teardownFunc) { + var _this = this; + var currentArgHash = {}; + var currentResHash = {}; + return function (newArgHash) { + var newResHash = {}; + for (var key in newArgHash) { + if (!currentResHash[key]) { + newResHash[key] = workerFunc.apply(_this, newArgHash[key]); + } + else if (!isArraysEqual(currentArgHash[key], newArgHash[key])) { + if (teardownFunc) { + teardownFunc(currentResHash[key]); + } + var res = workerFunc.apply(_this, newArgHash[key]); + newResHash[key] = (resEquality && resEquality(res, currentResHash[key])) + ? currentResHash[key] + : res; + } + else { + newResHash[key] = currentResHash[key]; + } + } + currentArgHash = newArgHash; + currentResHash = newResHash; + return newResHash; + }; + } + + var EXTENDED_SETTINGS_AND_SEVERITIES = { + week: 3, + separator: 0, + omitZeroMinute: 0, + meridiem: 0, + omitCommas: 0, + }; + var STANDARD_DATE_PROP_SEVERITIES = { + timeZoneName: 7, + era: 6, + year: 5, + month: 4, + day: 2, + weekday: 2, + hour: 1, + minute: 1, + second: 1, + }; + var MERIDIEM_RE = /\s*([ap])\.?m\.?/i; // eats up leading spaces too + var COMMA_RE = /,/g; // we need re for globalness + var MULTI_SPACE_RE = /\s+/g; + var LTR_RE = /\u200e/g; // control character + var UTC_RE = /UTC|GMT/; + var NativeFormatter = /** @class */ (function () { + function NativeFormatter(formatSettings) { + var standardDateProps = {}; + var extendedSettings = {}; + var severity = 0; + for (var name_1 in formatSettings) { + if (name_1 in EXTENDED_SETTINGS_AND_SEVERITIES) { + extendedSettings[name_1] = formatSettings[name_1]; + severity = Math.max(EXTENDED_SETTINGS_AND_SEVERITIES[name_1], severity); + } + else { + standardDateProps[name_1] = formatSettings[name_1]; + if (name_1 in STANDARD_DATE_PROP_SEVERITIES) { // TODO: what about hour12? no severity + severity = Math.max(STANDARD_DATE_PROP_SEVERITIES[name_1], severity); + } + } + } + this.standardDateProps = standardDateProps; + this.extendedSettings = extendedSettings; + this.severity = severity; + this.buildFormattingFunc = memoize(buildFormattingFunc); + } + NativeFormatter.prototype.format = function (date, context) { + return this.buildFormattingFunc(this.standardDateProps, this.extendedSettings, context)(date); + }; + NativeFormatter.prototype.formatRange = function (start, end, context, betterDefaultSeparator) { + var _a = this, standardDateProps = _a.standardDateProps, extendedSettings = _a.extendedSettings; + var diffSeverity = computeMarkerDiffSeverity(start.marker, end.marker, context.calendarSystem); + if (!diffSeverity) { + return this.format(start, context); + } + var biggestUnitForPartial = diffSeverity; + if (biggestUnitForPartial > 1 && // the two dates are different in a way that's larger scale than time + (standardDateProps.year === 'numeric' || standardDateProps.year === '2-digit') && + (standardDateProps.month === 'numeric' || standardDateProps.month === '2-digit') && + (standardDateProps.day === 'numeric' || standardDateProps.day === '2-digit')) { + biggestUnitForPartial = 1; // make it look like the dates are only different in terms of time + } + var full0 = this.format(start, context); + var full1 = this.format(end, context); + if (full0 === full1) { + return full0; + } + var partialDateProps = computePartialFormattingOptions(standardDateProps, biggestUnitForPartial); + var partialFormattingFunc = buildFormattingFunc(partialDateProps, extendedSettings, context); + var partial0 = partialFormattingFunc(start); + var partial1 = partialFormattingFunc(end); + var insertion = findCommonInsertion(full0, partial0, full1, partial1); + var separator = extendedSettings.separator || betterDefaultSeparator || context.defaultSeparator || ''; + if (insertion) { + return insertion.before + partial0 + separator + partial1 + insertion.after; + } + return full0 + separator + full1; + }; + NativeFormatter.prototype.getLargestUnit = function () { + switch (this.severity) { + case 7: + case 6: + case 5: + return 'year'; + case 4: + return 'month'; + case 3: + return 'week'; + case 2: + return 'day'; + default: + return 'time'; // really? + } + }; + return NativeFormatter; + }()); + function buildFormattingFunc(standardDateProps, extendedSettings, context) { + var standardDatePropCnt = Object.keys(standardDateProps).length; + if (standardDatePropCnt === 1 && standardDateProps.timeZoneName === 'short') { + return function (date) { return (formatTimeZoneOffset(date.timeZoneOffset)); }; + } + if (standardDatePropCnt === 0 && extendedSettings.week) { + return function (date) { return (formatWeekNumber(context.computeWeekNumber(date.marker), context.weekText, context.locale, extendedSettings.week)); }; + } + return buildNativeFormattingFunc(standardDateProps, extendedSettings, context); + } + function buildNativeFormattingFunc(standardDateProps, extendedSettings, context) { + standardDateProps = __assign({}, standardDateProps); // copy + extendedSettings = __assign({}, extendedSettings); // copy + sanitizeSettings(standardDateProps, extendedSettings); + standardDateProps.timeZone = 'UTC'; // we leverage the only guaranteed timeZone for our UTC markers + var normalFormat = new Intl.DateTimeFormat(context.locale.codes, standardDateProps); + var zeroFormat; // needed? + if (extendedSettings.omitZeroMinute) { + var zeroProps = __assign({}, standardDateProps); + delete zeroProps.minute; // seconds and ms were already considered in sanitizeSettings + zeroFormat = new Intl.DateTimeFormat(context.locale.codes, zeroProps); + } + return function (date) { + var marker = date.marker; + var format; + if (zeroFormat && !marker.getUTCMinutes()) { + format = zeroFormat; + } + else { + format = normalFormat; + } + var s = format.format(marker); + return postProcess(s, date, standardDateProps, extendedSettings, context); + }; + } + function sanitizeSettings(standardDateProps, extendedSettings) { + // deal with a browser inconsistency where formatting the timezone + // requires that the hour/minute be present. + if (standardDateProps.timeZoneName) { + if (!standardDateProps.hour) { + standardDateProps.hour = '2-digit'; + } + if (!standardDateProps.minute) { + standardDateProps.minute = '2-digit'; + } + } + // only support short timezone names + if (standardDateProps.timeZoneName === 'long') { + standardDateProps.timeZoneName = 'short'; + } + // if requesting to display seconds, MUST display minutes + if (extendedSettings.omitZeroMinute && (standardDateProps.second || standardDateProps.millisecond)) { + delete extendedSettings.omitZeroMinute; + } + } + function postProcess(s, date, standardDateProps, extendedSettings, context) { + s = s.replace(LTR_RE, ''); // remove left-to-right control chars. do first. good for other regexes + if (standardDateProps.timeZoneName === 'short') { + s = injectTzoStr(s, (context.timeZone === 'UTC' || date.timeZoneOffset == null) ? + 'UTC' : // important to normalize for IE, which does "GMT" + formatTimeZoneOffset(date.timeZoneOffset)); + } + if (extendedSettings.omitCommas) { + s = s.replace(COMMA_RE, '').trim(); + } + if (extendedSettings.omitZeroMinute) { + s = s.replace(':00', ''); // zeroFormat doesn't always achieve this + } + // ^ do anything that might create adjacent spaces before this point, + // because MERIDIEM_RE likes to eat up loading spaces + if (extendedSettings.meridiem === false) { + s = s.replace(MERIDIEM_RE, '').trim(); + } + else if (extendedSettings.meridiem === 'narrow') { // a/p + s = s.replace(MERIDIEM_RE, function (m0, m1) { return m1.toLocaleLowerCase(); }); + } + else if (extendedSettings.meridiem === 'short') { // am/pm + s = s.replace(MERIDIEM_RE, function (m0, m1) { return m1.toLocaleLowerCase() + "m"; }); + } + else if (extendedSettings.meridiem === 'lowercase') { // other meridiem transformers already converted to lowercase + s = s.replace(MERIDIEM_RE, function (m0) { return m0.toLocaleLowerCase(); }); + } + s = s.replace(MULTI_SPACE_RE, ' '); + s = s.trim(); + return s; + } + function injectTzoStr(s, tzoStr) { + var replaced = false; + s = s.replace(UTC_RE, function () { + replaced = true; + return tzoStr; + }); + // IE11 doesn't include UTC/GMT in the original string, so append to end + if (!replaced) { + s += " " + tzoStr; + } + return s; + } + function formatWeekNumber(num, weekText, locale, display) { + var parts = []; + if (display === 'narrow') { + parts.push(weekText); + } + else if (display === 'short') { + parts.push(weekText, ' '); + } + // otherwise, considered 'numeric' + parts.push(locale.simpleNumberFormat.format(num)); + if (locale.options.direction === 'rtl') { // TODO: use control characters instead? + parts.reverse(); + } + return parts.join(''); + } + // Range Formatting Utils + // 0 = exactly the same + // 1 = different by time + // and bigger + function computeMarkerDiffSeverity(d0, d1, ca) { + if (ca.getMarkerYear(d0) !== ca.getMarkerYear(d1)) { + return 5; + } + if (ca.getMarkerMonth(d0) !== ca.getMarkerMonth(d1)) { + return 4; + } + if (ca.getMarkerDay(d0) !== ca.getMarkerDay(d1)) { + return 2; + } + if (timeAsMs(d0) !== timeAsMs(d1)) { + return 1; + } + return 0; + } + function computePartialFormattingOptions(options, biggestUnit) { + var partialOptions = {}; + for (var name_2 in options) { + if (!(name_2 in STANDARD_DATE_PROP_SEVERITIES) || // not a date part prop (like timeZone) + STANDARD_DATE_PROP_SEVERITIES[name_2] <= biggestUnit) { + partialOptions[name_2] = options[name_2]; + } + } + return partialOptions; + } + function findCommonInsertion(full0, partial0, full1, partial1) { + var i0 = 0; + while (i0 < full0.length) { + var found0 = full0.indexOf(partial0, i0); + if (found0 === -1) { + break; + } + var before0 = full0.substr(0, found0); + i0 = found0 + partial0.length; + var after0 = full0.substr(i0); + var i1 = 0; + while (i1 < full1.length) { + var found1 = full1.indexOf(partial1, i1); + if (found1 === -1) { + break; + } + var before1 = full1.substr(0, found1); + i1 = found1 + partial1.length; + var after1 = full1.substr(i1); + if (before0 === before1 && after0 === after1) { + return { + before: before0, + after: after0, + }; + } + } + } + return null; + } + + function expandZonedMarker(dateInfo, calendarSystem) { + var a = calendarSystem.markerToArray(dateInfo.marker); + return { + marker: dateInfo.marker, + timeZoneOffset: dateInfo.timeZoneOffset, + array: a, + year: a[0], + month: a[1], + day: a[2], + hour: a[3], + minute: a[4], + second: a[5], + millisecond: a[6], + }; + } + + function createVerboseFormattingArg(start, end, context, betterDefaultSeparator) { + var startInfo = expandZonedMarker(start, context.calendarSystem); + var endInfo = end ? expandZonedMarker(end, context.calendarSystem) : null; + return { + date: startInfo, + start: startInfo, + end: endInfo, + timeZone: context.timeZone, + localeCodes: context.locale.codes, + defaultSeparator: betterDefaultSeparator || context.defaultSeparator, + }; + } + + /* + TODO: fix the terminology of "formatter" vs "formatting func" + */ + /* + At the time of instantiation, this object does not know which cmd-formatting system it will use. + It receives this at the time of formatting, as a setting. + */ + var CmdFormatter = /** @class */ (function () { + function CmdFormatter(cmdStr) { + this.cmdStr = cmdStr; + } + CmdFormatter.prototype.format = function (date, context, betterDefaultSeparator) { + return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(date, null, context, betterDefaultSeparator)); + }; + CmdFormatter.prototype.formatRange = function (start, end, context, betterDefaultSeparator) { + return context.cmdFormatter(this.cmdStr, createVerboseFormattingArg(start, end, context, betterDefaultSeparator)); + }; + return CmdFormatter; + }()); + + var FuncFormatter = /** @class */ (function () { + function FuncFormatter(func) { + this.func = func; + } + FuncFormatter.prototype.format = function (date, context, betterDefaultSeparator) { + return this.func(createVerboseFormattingArg(date, null, context, betterDefaultSeparator)); + }; + FuncFormatter.prototype.formatRange = function (start, end, context, betterDefaultSeparator) { + return this.func(createVerboseFormattingArg(start, end, context, betterDefaultSeparator)); + }; + return FuncFormatter; + }()); + + function createFormatter(input) { + if (typeof input === 'object' && input) { // non-null object + return new NativeFormatter(input); + } + if (typeof input === 'string') { + return new CmdFormatter(input); + } + if (typeof input === 'function') { + return new FuncFormatter(input); + } + return null; + } + + // base options + // ------------ + var BASE_OPTION_REFINERS = { + navLinkDayClick: identity, + navLinkWeekClick: identity, + duration: createDuration, + bootstrapFontAwesome: identity, + buttonIcons: identity, + customButtons: identity, + defaultAllDayEventDuration: createDuration, + defaultTimedEventDuration: createDuration, + nextDayThreshold: createDuration, + scrollTime: createDuration, + scrollTimeReset: Boolean, + slotMinTime: createDuration, + slotMaxTime: createDuration, + dayPopoverFormat: createFormatter, + slotDuration: createDuration, + snapDuration: createDuration, + headerToolbar: identity, + footerToolbar: identity, + defaultRangeSeparator: String, + titleRangeSeparator: String, + forceEventDuration: Boolean, + dayHeaders: Boolean, + dayHeaderFormat: createFormatter, + dayHeaderClassNames: identity, + dayHeaderContent: identity, + dayHeaderDidMount: identity, + dayHeaderWillUnmount: identity, + dayCellClassNames: identity, + dayCellContent: identity, + dayCellDidMount: identity, + dayCellWillUnmount: identity, + initialView: String, + aspectRatio: Number, + weekends: Boolean, + weekNumberCalculation: identity, + weekNumbers: Boolean, + weekNumberClassNames: identity, + weekNumberContent: identity, + weekNumberDidMount: identity, + weekNumberWillUnmount: identity, + editable: Boolean, + viewClassNames: identity, + viewDidMount: identity, + viewWillUnmount: identity, + nowIndicator: Boolean, + nowIndicatorClassNames: identity, + nowIndicatorContent: identity, + nowIndicatorDidMount: identity, + nowIndicatorWillUnmount: identity, + showNonCurrentDates: Boolean, + lazyFetching: Boolean, + startParam: String, + endParam: String, + timeZoneParam: String, + timeZone: String, + locales: identity, + locale: identity, + themeSystem: String, + dragRevertDuration: Number, + dragScroll: Boolean, + allDayMaintainDuration: Boolean, + unselectAuto: Boolean, + dropAccept: identity, + eventOrder: parseFieldSpecs, + eventOrderStrict: Boolean, + handleWindowResize: Boolean, + windowResizeDelay: Number, + longPressDelay: Number, + eventDragMinDistance: Number, + expandRows: Boolean, + height: identity, + contentHeight: identity, + direction: String, + weekNumberFormat: createFormatter, + eventResizableFromStart: Boolean, + displayEventTime: Boolean, + displayEventEnd: Boolean, + weekText: String, + progressiveEventRendering: Boolean, + businessHours: identity, + initialDate: identity, + now: identity, + eventDataTransform: identity, + stickyHeaderDates: identity, + stickyFooterScrollbar: identity, + viewHeight: identity, + defaultAllDay: Boolean, + eventSourceFailure: identity, + eventSourceSuccess: identity, + eventDisplay: String, + eventStartEditable: Boolean, + eventDurationEditable: Boolean, + eventOverlap: identity, + eventConstraint: identity, + eventAllow: identity, + eventBackgroundColor: String, + eventBorderColor: String, + eventTextColor: String, + eventColor: String, + eventClassNames: identity, + eventContent: identity, + eventDidMount: identity, + eventWillUnmount: identity, + selectConstraint: identity, + selectOverlap: identity, + selectAllow: identity, + droppable: Boolean, + unselectCancel: String, + slotLabelFormat: identity, + slotLaneClassNames: identity, + slotLaneContent: identity, + slotLaneDidMount: identity, + slotLaneWillUnmount: identity, + slotLabelClassNames: identity, + slotLabelContent: identity, + slotLabelDidMount: identity, + slotLabelWillUnmount: identity, + dayMaxEvents: identity, + dayMaxEventRows: identity, + dayMinWidth: Number, + slotLabelInterval: createDuration, + allDayText: String, + allDayClassNames: identity, + allDayContent: identity, + allDayDidMount: identity, + allDayWillUnmount: identity, + slotMinWidth: Number, + navLinks: Boolean, + eventTimeFormat: createFormatter, + rerenderDelay: Number, + moreLinkText: identity, + selectMinDistance: Number, + selectable: Boolean, + selectLongPressDelay: Number, + eventLongPressDelay: Number, + selectMirror: Boolean, + eventMaxStack: Number, + eventMinHeight: Number, + eventMinWidth: Number, + eventShortHeight: Number, + slotEventOverlap: Boolean, + plugins: identity, + firstDay: Number, + dayCount: Number, + dateAlignment: String, + dateIncrement: createDuration, + hiddenDays: identity, + monthMode: Boolean, + fixedWeekCount: Boolean, + validRange: identity, + visibleRange: identity, + titleFormat: identity, + // only used by list-view, but languages define the value, so we need it in base options + noEventsText: String, + moreLinkClick: identity, + moreLinkClassNames: identity, + moreLinkContent: identity, + moreLinkDidMount: identity, + moreLinkWillUnmount: identity, + }; + // do NOT give a type here. need `typeof BASE_OPTION_DEFAULTS` to give real results. + // raw values. + var BASE_OPTION_DEFAULTS = { + eventDisplay: 'auto', + defaultRangeSeparator: ' - ', + titleRangeSeparator: ' \u2013 ', + defaultTimedEventDuration: '01:00:00', + defaultAllDayEventDuration: { day: 1 }, + forceEventDuration: false, + nextDayThreshold: '00:00:00', + dayHeaders: true, + initialView: '', + aspectRatio: 1.35, + headerToolbar: { + start: 'title', + center: '', + end: 'today prev,next', + }, + weekends: true, + weekNumbers: false, + weekNumberCalculation: 'local', + editable: false, + nowIndicator: false, + scrollTime: '06:00:00', + scrollTimeReset: true, + slotMinTime: '00:00:00', + slotMaxTime: '24:00:00', + showNonCurrentDates: true, + lazyFetching: true, + startParam: 'start', + endParam: 'end', + timeZoneParam: 'timeZone', + timeZone: 'local', + locales: [], + locale: '', + themeSystem: 'standard', + dragRevertDuration: 500, + dragScroll: true, + allDayMaintainDuration: false, + unselectAuto: true, + dropAccept: '*', + eventOrder: 'start,-duration,allDay,title', + dayPopoverFormat: { month: 'long', day: 'numeric', year: 'numeric' }, + handleWindowResize: true, + windowResizeDelay: 100, + longPressDelay: 1000, + eventDragMinDistance: 5, + expandRows: false, + navLinks: false, + selectable: false, + eventMinHeight: 15, + eventMinWidth: 30, + eventShortHeight: 30, + }; + // calendar listeners + // ------------------ + var CALENDAR_LISTENER_REFINERS = { + datesSet: identity, + eventsSet: identity, + eventAdd: identity, + eventChange: identity, + eventRemove: identity, + windowResize: identity, + eventClick: identity, + eventMouseEnter: identity, + eventMouseLeave: identity, + select: identity, + unselect: identity, + loading: identity, + // internal + _unmount: identity, + _beforeprint: identity, + _afterprint: identity, + _noEventDrop: identity, + _noEventResize: identity, + _resize: identity, + _scrollRequest: identity, + }; + // calendar-specific options + // ------------------------- + var CALENDAR_OPTION_REFINERS = { + buttonText: identity, + views: identity, + plugins: identity, + initialEvents: identity, + events: identity, + eventSources: identity, + }; + var COMPLEX_OPTION_COMPARATORS = { + headerToolbar: isBoolComplexEqual, + footerToolbar: isBoolComplexEqual, + buttonText: isBoolComplexEqual, + buttonIcons: isBoolComplexEqual, + }; + function isBoolComplexEqual(a, b) { + if (typeof a === 'object' && typeof b === 'object' && a && b) { // both non-null objects + return isPropsEqual(a, b); + } + return a === b; + } + // view-specific options + // --------------------- + var VIEW_OPTION_REFINERS = { + type: String, + component: identity, + buttonText: String, + buttonTextKey: String, + dateProfileGeneratorClass: identity, + usesMinMaxTime: Boolean, + classNames: identity, + content: identity, + didMount: identity, + willUnmount: identity, + }; + // util funcs + // ---------------------------------------------------------------------------------------------------- + function mergeRawOptions(optionSets) { + return mergeProps(optionSets, COMPLEX_OPTION_COMPARATORS); + } + function refineProps(input, refiners) { + var refined = {}; + var extra = {}; + for (var propName in refiners) { + if (propName in input) { + refined[propName] = refiners[propName](input[propName]); + } + } + for (var propName in input) { + if (!(propName in refiners)) { + extra[propName] = input[propName]; + } + } + return { refined: refined, extra: extra }; + } + function identity(raw) { + return raw; + } + + function parseEvents(rawEvents, eventSource, context, allowOpenRange) { + var eventStore = createEmptyEventStore(); + var eventRefiners = buildEventRefiners(context); + for (var _i = 0, rawEvents_1 = rawEvents; _i < rawEvents_1.length; _i++) { + var rawEvent = rawEvents_1[_i]; + var tuple = parseEvent(rawEvent, eventSource, context, allowOpenRange, eventRefiners); + if (tuple) { + eventTupleToStore(tuple, eventStore); + } + } + return eventStore; + } + function eventTupleToStore(tuple, eventStore) { + if (eventStore === void 0) { eventStore = createEmptyEventStore(); } + eventStore.defs[tuple.def.defId] = tuple.def; + if (tuple.instance) { + eventStore.instances[tuple.instance.instanceId] = tuple.instance; + } + return eventStore; + } + // retrieves events that have the same groupId as the instance specified by `instanceId` + // or they are the same as the instance. + // why might instanceId not be in the store? an event from another calendar? + function getRelevantEvents(eventStore, instanceId) { + var instance = eventStore.instances[instanceId]; + if (instance) { + var def_1 = eventStore.defs[instance.defId]; + // get events/instances with same group + var newStore = filterEventStoreDefs(eventStore, function (lookDef) { return isEventDefsGrouped(def_1, lookDef); }); + // add the original + // TODO: wish we could use eventTupleToStore or something like it + newStore.defs[def_1.defId] = def_1; + newStore.instances[instance.instanceId] = instance; + return newStore; + } + return createEmptyEventStore(); + } + function isEventDefsGrouped(def0, def1) { + return Boolean(def0.groupId && def0.groupId === def1.groupId); + } + function createEmptyEventStore() { + return { defs: {}, instances: {} }; + } + function mergeEventStores(store0, store1) { + return { + defs: __assign(__assign({}, store0.defs), store1.defs), + instances: __assign(__assign({}, store0.instances), store1.instances), + }; + } + function filterEventStoreDefs(eventStore, filterFunc) { + var defs = filterHash(eventStore.defs, filterFunc); + var instances = filterHash(eventStore.instances, function (instance) { return (defs[instance.defId] // still exists? + ); }); + return { defs: defs, instances: instances }; + } + function excludeSubEventStore(master, sub) { + var defs = master.defs, instances = master.instances; + var filteredDefs = {}; + var filteredInstances = {}; + for (var defId in defs) { + if (!sub.defs[defId]) { // not explicitly excluded + filteredDefs[defId] = defs[defId]; + } + } + for (var instanceId in instances) { + if (!sub.instances[instanceId] && // not explicitly excluded + filteredDefs[instances[instanceId].defId] // def wasn't filtered away + ) { + filteredInstances[instanceId] = instances[instanceId]; + } + } + return { + defs: filteredDefs, + instances: filteredInstances, + }; + } + + function normalizeConstraint(input, context) { + if (Array.isArray(input)) { + return parseEvents(input, null, context, true); // allowOpenRange=true + } + if (typeof input === 'object' && input) { // non-null object + return parseEvents([input], null, context, true); // allowOpenRange=true + } + if (input != null) { + return String(input); + } + return null; + } + + function parseClassNames(raw) { + if (Array.isArray(raw)) { + return raw; + } + if (typeof raw === 'string') { + return raw.split(/\s+/); + } + return []; + } + + // TODO: better called "EventSettings" or "EventConfig" + // TODO: move this file into structs + // TODO: separate constraint/overlap/allow, because selection uses only that, not other props + var EVENT_UI_REFINERS = { + display: String, + editable: Boolean, + startEditable: Boolean, + durationEditable: Boolean, + constraint: identity, + overlap: identity, + allow: identity, + className: parseClassNames, + classNames: parseClassNames, + color: String, + backgroundColor: String, + borderColor: String, + textColor: String, + }; + var EMPTY_EVENT_UI = { + display: null, + startEditable: null, + durationEditable: null, + constraints: [], + overlap: null, + allows: [], + backgroundColor: '', + borderColor: '', + textColor: '', + classNames: [], + }; + function createEventUi(refined, context) { + var constraint = normalizeConstraint(refined.constraint, context); + return { + display: refined.display || null, + startEditable: refined.startEditable != null ? refined.startEditable : refined.editable, + durationEditable: refined.durationEditable != null ? refined.durationEditable : refined.editable, + constraints: constraint != null ? [constraint] : [], + overlap: refined.overlap != null ? refined.overlap : null, + allows: refined.allow != null ? [refined.allow] : [], + backgroundColor: refined.backgroundColor || refined.color || '', + borderColor: refined.borderColor || refined.color || '', + textColor: refined.textColor || '', + classNames: (refined.className || []).concat(refined.classNames || []), // join singular and plural + }; + } + // TODO: prevent against problems with <2 args! + function combineEventUis(uis) { + return uis.reduce(combineTwoEventUis, EMPTY_EVENT_UI); + } + function combineTwoEventUis(item0, item1) { + return { + display: item1.display != null ? item1.display : item0.display, + startEditable: item1.startEditable != null ? item1.startEditable : item0.startEditable, + durationEditable: item1.durationEditable != null ? item1.durationEditable : item0.durationEditable, + constraints: item0.constraints.concat(item1.constraints), + overlap: typeof item1.overlap === 'boolean' ? item1.overlap : item0.overlap, + allows: item0.allows.concat(item1.allows), + backgroundColor: item1.backgroundColor || item0.backgroundColor, + borderColor: item1.borderColor || item0.borderColor, + textColor: item1.textColor || item0.textColor, + classNames: item0.classNames.concat(item1.classNames), + }; + } + + var EVENT_NON_DATE_REFINERS = { + id: String, + groupId: String, + title: String, + url: String, + }; + var EVENT_DATE_REFINERS = { + start: identity, + end: identity, + date: identity, + allDay: Boolean, + }; + var EVENT_REFINERS = __assign(__assign(__assign({}, EVENT_NON_DATE_REFINERS), EVENT_DATE_REFINERS), { extendedProps: identity }); + function parseEvent(raw, eventSource, context, allowOpenRange, refiners) { + if (refiners === void 0) { refiners = buildEventRefiners(context); } + var _a = refineEventDef(raw, context, refiners), refined = _a.refined, extra = _a.extra; + var defaultAllDay = computeIsDefaultAllDay(eventSource, context); + var recurringRes = parseRecurring(refined, defaultAllDay, context.dateEnv, context.pluginHooks.recurringTypes); + if (recurringRes) { + var def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', recurringRes.allDay, Boolean(recurringRes.duration), context); + def.recurringDef = { + typeId: recurringRes.typeId, + typeData: recurringRes.typeData, + duration: recurringRes.duration, + }; + return { def: def, instance: null }; + } + var singleRes = parseSingle(refined, defaultAllDay, context, allowOpenRange); + if (singleRes) { + var def = parseEventDef(refined, extra, eventSource ? eventSource.sourceId : '', singleRes.allDay, singleRes.hasEnd, context); + var instance = createEventInstance(def.defId, singleRes.range, singleRes.forcedStartTzo, singleRes.forcedEndTzo); + return { def: def, instance: instance }; + } + return null; + } + function refineEventDef(raw, context, refiners) { + if (refiners === void 0) { refiners = buildEventRefiners(context); } + return refineProps(raw, refiners); + } + function buildEventRefiners(context) { + return __assign(__assign(__assign({}, EVENT_UI_REFINERS), EVENT_REFINERS), context.pluginHooks.eventRefiners); + } + /* + Will NOT populate extendedProps with the leftover properties. + Will NOT populate date-related props. + */ + function parseEventDef(refined, extra, sourceId, allDay, hasEnd, context) { + var def = { + title: refined.title || '', + groupId: refined.groupId || '', + publicId: refined.id || '', + url: refined.url || '', + recurringDef: null, + defId: guid(), + sourceId: sourceId, + allDay: allDay, + hasEnd: hasEnd, + ui: createEventUi(refined, context), + extendedProps: __assign(__assign({}, (refined.extendedProps || {})), extra), + }; + for (var _i = 0, _a = context.pluginHooks.eventDefMemberAdders; _i < _a.length; _i++) { + var memberAdder = _a[_i]; + __assign(def, memberAdder(refined)); + } + // help out EventApi from having user modify props + Object.freeze(def.ui.classNames); + Object.freeze(def.extendedProps); + return def; + } + function parseSingle(refined, defaultAllDay, context, allowOpenRange) { + var allDay = refined.allDay; + var startMeta; + var startMarker = null; + var hasEnd = false; + var endMeta; + var endMarker = null; + var startInput = refined.start != null ? refined.start : refined.date; + startMeta = context.dateEnv.createMarkerMeta(startInput); + if (startMeta) { + startMarker = startMeta.marker; + } + else if (!allowOpenRange) { + return null; + } + if (refined.end != null) { + endMeta = context.dateEnv.createMarkerMeta(refined.end); + } + if (allDay == null) { + if (defaultAllDay != null) { + allDay = defaultAllDay; + } + else { + // fall back to the date props LAST + allDay = (!startMeta || startMeta.isTimeUnspecified) && + (!endMeta || endMeta.isTimeUnspecified); + } + } + if (allDay && startMarker) { + startMarker = startOfDay(startMarker); + } + if (endMeta) { + endMarker = endMeta.marker; + if (allDay) { + endMarker = startOfDay(endMarker); + } + if (startMarker && endMarker <= startMarker) { + endMarker = null; + } + } + if (endMarker) { + hasEnd = true; + } + else if (!allowOpenRange) { + hasEnd = context.options.forceEventDuration || false; + endMarker = context.dateEnv.add(startMarker, allDay ? + context.options.defaultAllDayEventDuration : + context.options.defaultTimedEventDuration); + } + return { + allDay: allDay, + hasEnd: hasEnd, + range: { start: startMarker, end: endMarker }, + forcedStartTzo: startMeta ? startMeta.forcedTzo : null, + forcedEndTzo: endMeta ? endMeta.forcedTzo : null, + }; + } + function computeIsDefaultAllDay(eventSource, context) { + var res = null; + if (eventSource) { + res = eventSource.defaultAllDay; + } + if (res == null) { + res = context.options.defaultAllDay; + } + return res; + } + + /* Date stuff that doesn't belong in datelib core + ----------------------------------------------------------------------------------------------------------------------*/ + // given a timed range, computes an all-day range that has the same exact duration, + // but whose start time is aligned with the start of the day. + function computeAlignedDayRange(timedRange) { + var dayCnt = Math.floor(diffDays(timedRange.start, timedRange.end)) || 1; + var start = startOfDay(timedRange.start); + var end = addDays(start, dayCnt); + return { start: start, end: end }; + } + // given a timed range, computes an all-day range based on how for the end date bleeds into the next day + // TODO: give nextDayThreshold a default arg + function computeVisibleDayRange(timedRange, nextDayThreshold) { + if (nextDayThreshold === void 0) { nextDayThreshold = createDuration(0); } + var startDay = null; + var endDay = null; + if (timedRange.end) { + endDay = startOfDay(timedRange.end); + var endTimeMS = timedRange.end.valueOf() - endDay.valueOf(); // # of milliseconds into `endDay` + // If the end time is actually inclusively part of the next day and is equal to or + // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`. + // Otherwise, leaving it as inclusive will cause it to exclude `endDay`. + if (endTimeMS && endTimeMS >= asRoughMs(nextDayThreshold)) { + endDay = addDays(endDay, 1); + } + } + if (timedRange.start) { + startDay = startOfDay(timedRange.start); // the beginning of the day the range starts + // If end is within `startDay` but not past nextDayThreshold, assign the default duration of one day. + if (endDay && endDay <= startDay) { + endDay = addDays(startDay, 1); + } + } + return { start: startDay, end: endDay }; + } + // spans from one day into another? + function isMultiDayRange(range) { + var visibleRange = computeVisibleDayRange(range); + return diffDays(visibleRange.start, visibleRange.end) > 1; + } + function diffDates(date0, date1, dateEnv, largeUnit) { + if (largeUnit === 'year') { + return createDuration(dateEnv.diffWholeYears(date0, date1), 'year'); + } + if (largeUnit === 'month') { + return createDuration(dateEnv.diffWholeMonths(date0, date1), 'month'); + } + return diffDayAndTime(date0, date1); // returns a duration + } + + function parseRange(input, dateEnv) { + var start = null; + var end = null; + if (input.start) { + start = dateEnv.createMarker(input.start); + } + if (input.end) { + end = dateEnv.createMarker(input.end); + } + if (!start && !end) { + return null; + } + if (start && end && end < start) { + return null; + } + return { start: start, end: end }; + } + // SIDE-EFFECT: will mutate ranges. + // Will return a new array result. + function invertRanges(ranges, constraintRange) { + var invertedRanges = []; + var start = constraintRange.start; // the end of the previous range. the start of the new range + var i; + var dateRange; + // ranges need to be in order. required for our date-walking algorithm + ranges.sort(compareRanges); + for (i = 0; i < ranges.length; i += 1) { + dateRange = ranges[i]; + // add the span of time before the event (if there is any) + if (dateRange.start > start) { // compare millisecond time (skip any ambig logic) + invertedRanges.push({ start: start, end: dateRange.start }); + } + if (dateRange.end > start) { + start = dateRange.end; + } + } + // add the span of time after the last event (if there is any) + if (start < constraintRange.end) { // compare millisecond time (skip any ambig logic) + invertedRanges.push({ start: start, end: constraintRange.end }); + } + return invertedRanges; + } + function compareRanges(range0, range1) { + return range0.start.valueOf() - range1.start.valueOf(); // earlier ranges go first + } + function intersectRanges(range0, range1) { + var start = range0.start, end = range0.end; + var newRange = null; + if (range1.start !== null) { + if (start === null) { + start = range1.start; + } + else { + start = new Date(Math.max(start.valueOf(), range1.start.valueOf())); + } + } + if (range1.end != null) { + if (end === null) { + end = range1.end; + } + else { + end = new Date(Math.min(end.valueOf(), range1.end.valueOf())); + } + } + if (start === null || end === null || start < end) { + newRange = { start: start, end: end }; + } + return newRange; + } + function rangesEqual(range0, range1) { + return (range0.start === null ? null : range0.start.valueOf()) === (range1.start === null ? null : range1.start.valueOf()) && + (range0.end === null ? null : range0.end.valueOf()) === (range1.end === null ? null : range1.end.valueOf()); + } + function rangesIntersect(range0, range1) { + return (range0.end === null || range1.start === null || range0.end > range1.start) && + (range0.start === null || range1.end === null || range0.start < range1.end); + } + function rangeContainsRange(outerRange, innerRange) { + return (outerRange.start === null || (innerRange.start !== null && innerRange.start >= outerRange.start)) && + (outerRange.end === null || (innerRange.end !== null && innerRange.end <= outerRange.end)); + } + function rangeContainsMarker(range, date) { + return (range.start === null || date >= range.start) && + (range.end === null || date < range.end); + } + // If the given date is not within the given range, move it inside. + // (If it's past the end, make it one millisecond before the end). + function constrainMarkerToRange(date, range) { + if (range.start != null && date < range.start) { + return range.start; + } + if (range.end != null && date >= range.end) { + return new Date(range.end.valueOf() - 1); + } + return date; + } + + /* + Specifying nextDayThreshold signals that all-day ranges should be sliced. + */ + function sliceEventStore(eventStore, eventUiBases, framingRange, nextDayThreshold) { + var inverseBgByGroupId = {}; + var inverseBgByDefId = {}; + var defByGroupId = {}; + var bgRanges = []; + var fgRanges = []; + var eventUis = compileEventUis(eventStore.defs, eventUiBases); + for (var defId in eventStore.defs) { + var def = eventStore.defs[defId]; + var ui = eventUis[def.defId]; + if (ui.display === 'inverse-background') { + if (def.groupId) { + inverseBgByGroupId[def.groupId] = []; + if (!defByGroupId[def.groupId]) { + defByGroupId[def.groupId] = def; + } + } + else { + inverseBgByDefId[defId] = []; + } + } + } + for (var instanceId in eventStore.instances) { + var instance = eventStore.instances[instanceId]; + var def = eventStore.defs[instance.defId]; + var ui = eventUis[def.defId]; + var origRange = instance.range; + var normalRange = (!def.allDay && nextDayThreshold) ? + computeVisibleDayRange(origRange, nextDayThreshold) : + origRange; + var slicedRange = intersectRanges(normalRange, framingRange); + if (slicedRange) { + if (ui.display === 'inverse-background') { + if (def.groupId) { + inverseBgByGroupId[def.groupId].push(slicedRange); + } + else { + inverseBgByDefId[instance.defId].push(slicedRange); + } + } + else if (ui.display !== 'none') { + (ui.display === 'background' ? bgRanges : fgRanges).push({ + def: def, + ui: ui, + instance: instance, + range: slicedRange, + isStart: normalRange.start && normalRange.start.valueOf() === slicedRange.start.valueOf(), + isEnd: normalRange.end && normalRange.end.valueOf() === slicedRange.end.valueOf(), + }); + } + } + } + for (var groupId in inverseBgByGroupId) { // BY GROUP + var ranges = inverseBgByGroupId[groupId]; + var invertedRanges = invertRanges(ranges, framingRange); + for (var _i = 0, invertedRanges_1 = invertedRanges; _i < invertedRanges_1.length; _i++) { + var invertedRange = invertedRanges_1[_i]; + var def = defByGroupId[groupId]; + var ui = eventUis[def.defId]; + bgRanges.push({ + def: def, + ui: ui, + instance: null, + range: invertedRange, + isStart: false, + isEnd: false, + }); + } + } + for (var defId in inverseBgByDefId) { + var ranges = inverseBgByDefId[defId]; + var invertedRanges = invertRanges(ranges, framingRange); + for (var _a = 0, invertedRanges_2 = invertedRanges; _a < invertedRanges_2.length; _a++) { + var invertedRange = invertedRanges_2[_a]; + bgRanges.push({ + def: eventStore.defs[defId], + ui: eventUis[defId], + instance: null, + range: invertedRange, + isStart: false, + isEnd: false, + }); + } + } + return { bg: bgRanges, fg: fgRanges }; + } + function hasBgRendering(def) { + return def.ui.display === 'background' || def.ui.display === 'inverse-background'; + } + function setElSeg(el, seg) { + el.fcSeg = seg; + } + function getElSeg(el) { + return el.fcSeg || + el.parentNode.fcSeg || // for the harness + null; + } + // event ui computation + function compileEventUis(eventDefs, eventUiBases) { + return mapHash(eventDefs, function (eventDef) { return compileEventUi(eventDef, eventUiBases); }); + } + function compileEventUi(eventDef, eventUiBases) { + var uis = []; + if (eventUiBases['']) { + uis.push(eventUiBases['']); + } + if (eventUiBases[eventDef.defId]) { + uis.push(eventUiBases[eventDef.defId]); + } + uis.push(eventDef.ui); + return combineEventUis(uis); + } + function sortEventSegs(segs, eventOrderSpecs) { + var objs = segs.map(buildSegCompareObj); + objs.sort(function (obj0, obj1) { return compareByFieldSpecs(obj0, obj1, eventOrderSpecs); }); + return objs.map(function (c) { return c._seg; }); + } + // returns a object with all primitive props that can be compared + function buildSegCompareObj(seg) { + var eventRange = seg.eventRange; + var eventDef = eventRange.def; + var range = eventRange.instance ? eventRange.instance.range : eventRange.range; + var start = range.start ? range.start.valueOf() : 0; // TODO: better support for open-range events + var end = range.end ? range.end.valueOf() : 0; // " + return __assign(__assign(__assign({}, eventDef.extendedProps), eventDef), { id: eventDef.publicId, start: start, + end: end, duration: end - start, allDay: Number(eventDef.allDay), _seg: seg }); + } + function computeSegDraggable(seg, context) { + var pluginHooks = context.pluginHooks; + var transformers = pluginHooks.isDraggableTransformers; + var _a = seg.eventRange, def = _a.def, ui = _a.ui; + var val = ui.startEditable; + for (var _i = 0, transformers_1 = transformers; _i < transformers_1.length; _i++) { + var transformer = transformers_1[_i]; + val = transformer(val, def, ui, context); + } + return val; + } + function computeSegStartResizable(seg, context) { + return seg.isStart && seg.eventRange.ui.durationEditable && context.options.eventResizableFromStart; + } + function computeSegEndResizable(seg, context) { + return seg.isEnd && seg.eventRange.ui.durationEditable; + } + function buildSegTimeText(seg, timeFormat, context, defaultDisplayEventTime, // defaults to true + defaultDisplayEventEnd, // defaults to true + startOverride, endOverride) { + var dateEnv = context.dateEnv, options = context.options; + var displayEventTime = options.displayEventTime, displayEventEnd = options.displayEventEnd; + var eventDef = seg.eventRange.def; + var eventInstance = seg.eventRange.instance; + if (displayEventTime == null) { + displayEventTime = defaultDisplayEventTime !== false; + } + if (displayEventEnd == null) { + displayEventEnd = defaultDisplayEventEnd !== false; + } + var wholeEventStart = eventInstance.range.start; + var wholeEventEnd = eventInstance.range.end; + var segStart = startOverride || seg.start || seg.eventRange.range.start; + var segEnd = endOverride || seg.end || seg.eventRange.range.end; + var isStartDay = startOfDay(wholeEventStart).valueOf() === startOfDay(segStart).valueOf(); + var isEndDay = startOfDay(addMs(wholeEventEnd, -1)).valueOf() === startOfDay(addMs(segEnd, -1)).valueOf(); + if (displayEventTime && !eventDef.allDay && (isStartDay || isEndDay)) { + segStart = isStartDay ? wholeEventStart : segStart; + segEnd = isEndDay ? wholeEventEnd : segEnd; + if (displayEventEnd && eventDef.hasEnd) { + return dateEnv.formatRange(segStart, segEnd, timeFormat, { + forcedStartTzo: startOverride ? null : eventInstance.forcedStartTzo, + forcedEndTzo: endOverride ? null : eventInstance.forcedEndTzo, + }); + } + return dateEnv.format(segStart, timeFormat, { + forcedTzo: startOverride ? null : eventInstance.forcedStartTzo, // nooooo, same + }); + } + return ''; + } + function getSegMeta(seg, todayRange, nowDate) { + var segRange = seg.eventRange.range; + return { + isPast: segRange.end < (nowDate || todayRange.start), + isFuture: segRange.start >= (nowDate || todayRange.end), + isToday: todayRange && rangeContainsMarker(todayRange, segRange.start), + }; + } + function getEventClassNames(props) { + var classNames = ['fc-event']; + if (props.isMirror) { + classNames.push('fc-event-mirror'); + } + if (props.isDraggable) { + classNames.push('fc-event-draggable'); + } + if (props.isStartResizable || props.isEndResizable) { + classNames.push('fc-event-resizable'); + } + if (props.isDragging) { + classNames.push('fc-event-dragging'); + } + if (props.isResizing) { + classNames.push('fc-event-resizing'); + } + if (props.isSelected) { + classNames.push('fc-event-selected'); + } + if (props.isStart) { + classNames.push('fc-event-start'); + } + if (props.isEnd) { + classNames.push('fc-event-end'); + } + if (props.isPast) { + classNames.push('fc-event-past'); + } + if (props.isToday) { + classNames.push('fc-event-today'); + } + if (props.isFuture) { + classNames.push('fc-event-future'); + } + return classNames; + } + function buildEventRangeKey(eventRange) { + return eventRange.instance + ? eventRange.instance.instanceId + : eventRange.def.defId + ":" + eventRange.range.start.toISOString(); + // inverse-background events don't have specific instances. TODO: better solution + } + + var STANDARD_PROPS = { + start: identity, + end: identity, + allDay: Boolean, + }; + function parseDateSpan(raw, dateEnv, defaultDuration) { + var span = parseOpenDateSpan(raw, dateEnv); + var range = span.range; + if (!range.start) { + return null; + } + if (!range.end) { + if (defaultDuration == null) { + return null; + } + range.end = dateEnv.add(range.start, defaultDuration); + } + return span; + } + /* + TODO: somehow combine with parseRange? + Will return null if the start/end props were present but parsed invalidly. + */ + function parseOpenDateSpan(raw, dateEnv) { + var _a = refineProps(raw, STANDARD_PROPS), standardProps = _a.refined, extra = _a.extra; + var startMeta = standardProps.start ? dateEnv.createMarkerMeta(standardProps.start) : null; + var endMeta = standardProps.end ? dateEnv.createMarkerMeta(standardProps.end) : null; + var allDay = standardProps.allDay; + if (allDay == null) { + allDay = (startMeta && startMeta.isTimeUnspecified) && + (!endMeta || endMeta.isTimeUnspecified); + } + return __assign({ range: { + start: startMeta ? startMeta.marker : null, + end: endMeta ? endMeta.marker : null, + }, allDay: allDay }, extra); + } + function isDateSpansEqual(span0, span1) { + return rangesEqual(span0.range, span1.range) && + span0.allDay === span1.allDay && + isSpanPropsEqual(span0, span1); + } + // the NON-DATE-RELATED props + function isSpanPropsEqual(span0, span1) { + for (var propName in span1) { + if (propName !== 'range' && propName !== 'allDay') { + if (span0[propName] !== span1[propName]) { + return false; + } + } + } + // are there any props that span0 has that span1 DOESN'T have? + // both have range/allDay, so no need to special-case. + for (var propName in span0) { + if (!(propName in span1)) { + return false; + } + } + return true; + } + function buildDateSpanApi(span, dateEnv) { + return __assign(__assign({}, buildRangeApi(span.range, dateEnv, span.allDay)), { allDay: span.allDay }); + } + function buildRangeApiWithTimeZone(range, dateEnv, omitTime) { + return __assign(__assign({}, buildRangeApi(range, dateEnv, omitTime)), { timeZone: dateEnv.timeZone }); + } + function buildRangeApi(range, dateEnv, omitTime) { + return { + start: dateEnv.toDate(range.start), + end: dateEnv.toDate(range.end), + startStr: dateEnv.formatIso(range.start, { omitTime: omitTime }), + endStr: dateEnv.formatIso(range.end, { omitTime: omitTime }), + }; + } + function fabricateEventRange(dateSpan, eventUiBases, context) { + var res = refineEventDef({ editable: false }, context); + var def = parseEventDef(res.refined, res.extra, '', // sourceId + dateSpan.allDay, true, // hasEnd + context); + return { + def: def, + ui: compileEventUi(def, eventUiBases), + instance: createEventInstance(def.defId, dateSpan.range), + range: dateSpan.range, + isStart: true, + isEnd: true, + }; + } + + function triggerDateSelect(selection, pev, context) { + context.emitter.trigger('select', __assign(__assign({}, buildDateSpanApiWithContext(selection, context)), { jsEvent: pev ? pev.origEvent : null, view: context.viewApi || context.calendarApi.view })); + } + function triggerDateUnselect(pev, context) { + context.emitter.trigger('unselect', { + jsEvent: pev ? pev.origEvent : null, + view: context.viewApi || context.calendarApi.view, + }); + } + function buildDateSpanApiWithContext(dateSpan, context) { + var props = {}; + for (var _i = 0, _a = context.pluginHooks.dateSpanTransforms; _i < _a.length; _i++) { + var transform = _a[_i]; + __assign(props, transform(dateSpan, context)); + } + __assign(props, buildDateSpanApi(dateSpan, context.dateEnv)); + return props; + } + // Given an event's allDay status and start date, return what its fallback end date should be. + // TODO: rename to computeDefaultEventEnd + function getDefaultEventEnd(allDay, marker, context) { + var dateEnv = context.dateEnv, options = context.options; + var end = marker; + if (allDay) { + end = startOfDay(end); + end = dateEnv.add(end, options.defaultAllDayEventDuration); + } + else { + end = dateEnv.add(end, options.defaultTimedEventDuration); + } + return end; + } + + // applies the mutation to ALL defs/instances within the event store + function applyMutationToEventStore(eventStore, eventConfigBase, mutation, context) { + var eventConfigs = compileEventUis(eventStore.defs, eventConfigBase); + var dest = createEmptyEventStore(); + for (var defId in eventStore.defs) { + var def = eventStore.defs[defId]; + dest.defs[defId] = applyMutationToEventDef(def, eventConfigs[defId], mutation, context); + } + for (var instanceId in eventStore.instances) { + var instance = eventStore.instances[instanceId]; + var def = dest.defs[instance.defId]; // important to grab the newly modified def + dest.instances[instanceId] = applyMutationToEventInstance(instance, def, eventConfigs[instance.defId], mutation, context); + } + return dest; + } + function applyMutationToEventDef(eventDef, eventConfig, mutation, context) { + var standardProps = mutation.standardProps || {}; + // if hasEnd has not been specified, guess a good value based on deltas. + // if duration will change, there's no way the default duration will persist, + // and thus, we need to mark the event as having a real end + if (standardProps.hasEnd == null && + eventConfig.durationEditable && + (mutation.startDelta || mutation.endDelta)) { + standardProps.hasEnd = true; // TODO: is this mutation okay? + } + var copy = __assign(__assign(__assign({}, eventDef), standardProps), { ui: __assign(__assign({}, eventDef.ui), standardProps.ui) }); + if (mutation.extendedProps) { + copy.extendedProps = __assign(__assign({}, copy.extendedProps), mutation.extendedProps); + } + for (var _i = 0, _a = context.pluginHooks.eventDefMutationAppliers; _i < _a.length; _i++) { + var applier = _a[_i]; + applier(copy, mutation, context); + } + if (!copy.hasEnd && context.options.forceEventDuration) { + copy.hasEnd = true; + } + return copy; + } + function applyMutationToEventInstance(eventInstance, eventDef, // must first be modified by applyMutationToEventDef + eventConfig, mutation, context) { + var dateEnv = context.dateEnv; + var forceAllDay = mutation.standardProps && mutation.standardProps.allDay === true; + var clearEnd = mutation.standardProps && mutation.standardProps.hasEnd === false; + var copy = __assign({}, eventInstance); + if (forceAllDay) { + copy.range = computeAlignedDayRange(copy.range); + } + if (mutation.datesDelta && eventConfig.startEditable) { + copy.range = { + start: dateEnv.add(copy.range.start, mutation.datesDelta), + end: dateEnv.add(copy.range.end, mutation.datesDelta), + }; + } + if (mutation.startDelta && eventConfig.durationEditable) { + copy.range = { + start: dateEnv.add(copy.range.start, mutation.startDelta), + end: copy.range.end, + }; + } + if (mutation.endDelta && eventConfig.durationEditable) { + copy.range = { + start: copy.range.start, + end: dateEnv.add(copy.range.end, mutation.endDelta), + }; + } + if (clearEnd) { + copy.range = { + start: copy.range.start, + end: getDefaultEventEnd(eventDef.allDay, copy.range.start, context), + }; + } + // in case event was all-day but the supplied deltas were not + // better util for this? + if (eventDef.allDay) { + copy.range = { + start: startOfDay(copy.range.start), + end: startOfDay(copy.range.end), + }; + } + // handle invalid durations + if (copy.range.end < copy.range.start) { + copy.range.end = getDefaultEventEnd(eventDef.allDay, copy.range.start, context); + } + return copy; + } + + // no public types yet. when there are, export from: + // import {} from './api-type-deps' + var ViewApi = /** @class */ (function () { + function ViewApi(type, getCurrentData, dateEnv) { + this.type = type; + this.getCurrentData = getCurrentData; + this.dateEnv = dateEnv; + } + Object.defineProperty(ViewApi.prototype, "calendar", { + get: function () { + return this.getCurrentData().calendarApi; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "title", { + get: function () { + return this.getCurrentData().viewTitle; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "activeStart", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.start); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "activeEnd", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.activeRange.end); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "currentStart", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.start); + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(ViewApi.prototype, "currentEnd", { + get: function () { + return this.dateEnv.toDate(this.getCurrentData().dateProfile.currentRange.end); + }, + enumerable: false, + configurable: true + }); + ViewApi.prototype.getOption = function (name) { + return this.getCurrentData().options[name]; // are the view-specific options + }; + return ViewApi; + }()); + + var EVENT_SOURCE_REFINERS$1 = { + id: String, + defaultAllDay: Boolean, + url: String, + format: String, + events: identity, + eventDataTransform: identity, + // for any network-related sources + success: identity, + failure: identity, + }; + function parseEventSource(raw, context, refiners) { + if (refiners === void 0) { refiners = buildEventSourceRefiners(context); } + var rawObj; + if (typeof raw === 'string') { + rawObj = { url: raw }; + } + else if (typeof raw === 'function' || Array.isArray(raw)) { + rawObj = { events: raw }; + } + else if (typeof raw === 'object' && raw) { // not null + rawObj = raw; + } + if (rawObj) { + var _a = refineProps(rawObj, refiners), refined = _a.refined, extra = _a.extra; + var metaRes = buildEventSourceMeta(refined, context); + if (metaRes) { + return { + _raw: raw, + isFetching: false, + latestFetchId: '', + fetchRange: null, + defaultAllDay: refined.defaultAllDay, + eventDataTransform: refined.eventDataTransform, + success: refined.success, + failure: refined.failure, + publicId: refined.id || '', + sourceId: guid(), + sourceDefId: metaRes.sourceDefId, + meta: metaRes.meta, + ui: createEventUi(refined, context), + extendedProps: extra, + }; + } + } + return null; + } + function buildEventSourceRefiners(context) { + return __assign(__assign(__assign({}, EVENT_UI_REFINERS), EVENT_SOURCE_REFINERS$1), context.pluginHooks.eventSourceRefiners); + } + function buildEventSourceMeta(raw, context) { + var defs = context.pluginHooks.eventSourceDefs; + for (var i = defs.length - 1; i >= 0; i -= 1) { // later-added plugins take precedence + var def = defs[i]; + var meta = def.parseMeta(raw); + if (meta) { + return { sourceDefId: i, meta: meta }; + } + } + return null; + } + + function reduceCurrentDate(currentDate, action) { + switch (action.type) { + case 'CHANGE_DATE': + return action.dateMarker; + default: + return currentDate; + } + } + function getInitialDate(options, dateEnv) { + var initialDateInput = options.initialDate; + // compute the initial ambig-timezone date + if (initialDateInput != null) { + return dateEnv.createMarker(initialDateInput); + } + return getNow(options.now, dateEnv); // getNow already returns unzoned + } + function getNow(nowInput, dateEnv) { + if (typeof nowInput === 'function') { + nowInput = nowInput(); + } + if (nowInput == null) { + return dateEnv.createNowMarker(); + } + return dateEnv.createMarker(nowInput); + } + + var CalendarApi = /** @class */ (function () { + function CalendarApi() { + } + CalendarApi.prototype.getCurrentData = function () { + return this.currentDataManager.getCurrentData(); + }; + CalendarApi.prototype.dispatch = function (action) { + return this.currentDataManager.dispatch(action); + }; + Object.defineProperty(CalendarApi.prototype, "view", { + get: function () { return this.getCurrentData().viewApi; } // for public API + , + enumerable: false, + configurable: true + }); + CalendarApi.prototype.batchRendering = function (callback) { + callback(); + }; + CalendarApi.prototype.updateSize = function () { + this.trigger('_resize', true); + }; + // Options + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.setOption = function (name, val) { + this.dispatch({ + type: 'SET_OPTION', + optionName: name, + rawOptionValue: val, + }); + }; + CalendarApi.prototype.getOption = function (name) { + return this.currentDataManager.currentCalendarOptionsInput[name]; + }; + CalendarApi.prototype.getAvailableLocaleCodes = function () { + return Object.keys(this.getCurrentData().availableRawLocales); + }; + // Trigger + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.on = function (handlerName, handler) { + var currentDataManager = this.currentDataManager; + if (currentDataManager.currentCalendarOptionsRefiners[handlerName]) { + currentDataManager.emitter.on(handlerName, handler); + } + else { + console.warn("Unknown listener name '" + handlerName + "'"); + } + }; + CalendarApi.prototype.off = function (handlerName, handler) { + this.currentDataManager.emitter.off(handlerName, handler); + }; + // not meant for public use + CalendarApi.prototype.trigger = function (handlerName) { + var _a; + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + (_a = this.currentDataManager.emitter).trigger.apply(_a, __spreadArray([handlerName], args)); + }; + // View + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.changeView = function (viewType, dateOrRange) { + var _this = this; + this.batchRendering(function () { + _this.unselect(); + if (dateOrRange) { + if (dateOrRange.start && dateOrRange.end) { // a range + _this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: viewType, + }); + _this.dispatch({ + type: 'SET_OPTION', + optionName: 'visibleRange', + rawOptionValue: dateOrRange, + }); + } + else { + var dateEnv = _this.getCurrentData().dateEnv; + _this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: viewType, + dateMarker: dateEnv.createMarker(dateOrRange), + }); + } + } + else { + _this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: viewType, + }); + } + }); + }; + // Forces navigation to a view for the given date. + // `viewType` can be a specific view name or a generic one like "week" or "day". + // needs to change + CalendarApi.prototype.zoomTo = function (dateMarker, viewType) { + var state = this.getCurrentData(); + var spec; + viewType = viewType || 'day'; // day is default zoom + spec = state.viewSpecs[viewType] || this.getUnitViewSpec(viewType); + this.unselect(); + if (spec) { + this.dispatch({ + type: 'CHANGE_VIEW_TYPE', + viewType: spec.type, + dateMarker: dateMarker, + }); + } + else { + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: dateMarker, + }); + } + }; + // Given a duration singular unit, like "week" or "day", finds a matching view spec. + // Preference is given to views that have corresponding buttons. + CalendarApi.prototype.getUnitViewSpec = function (unit) { + var _a = this.getCurrentData(), viewSpecs = _a.viewSpecs, toolbarConfig = _a.toolbarConfig; + var viewTypes = [].concat(toolbarConfig.viewsWithButtons); + var i; + var spec; + for (var viewType in viewSpecs) { + viewTypes.push(viewType); + } + for (i = 0; i < viewTypes.length; i += 1) { + spec = viewSpecs[viewTypes[i]]; + if (spec) { + if (spec.singleUnit === unit) { + return spec; + } + } + } + return null; + }; + // Current Date + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.prev = function () { + this.unselect(); + this.dispatch({ type: 'PREV' }); + }; + CalendarApi.prototype.next = function () { + this.unselect(); + this.dispatch({ type: 'NEXT' }); + }; + CalendarApi.prototype.prevYear = function () { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.addYears(state.currentDate, -1), + }); + }; + CalendarApi.prototype.nextYear = function () { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.addYears(state.currentDate, 1), + }); + }; + CalendarApi.prototype.today = function () { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: getNow(state.calendarOptions.now, state.dateEnv), + }); + }; + CalendarApi.prototype.gotoDate = function (zonedDateInput) { + var state = this.getCurrentData(); + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.createMarker(zonedDateInput), + }); + }; + CalendarApi.prototype.incrementDate = function (deltaInput) { + var state = this.getCurrentData(); + var delta = createDuration(deltaInput); + if (delta) { // else, warn about invalid input? + this.unselect(); + this.dispatch({ + type: 'CHANGE_DATE', + dateMarker: state.dateEnv.add(state.currentDate, delta), + }); + } + }; + // for external API + CalendarApi.prototype.getDate = function () { + var state = this.getCurrentData(); + return state.dateEnv.toDate(state.currentDate); + }; + // Date Formatting Utils + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.formatDate = function (d, formatter) { + var dateEnv = this.getCurrentData().dateEnv; + return dateEnv.format(dateEnv.createMarker(d), createFormatter(formatter)); + }; + // `settings` is for formatter AND isEndExclusive + CalendarApi.prototype.formatRange = function (d0, d1, settings) { + var dateEnv = this.getCurrentData().dateEnv; + return dateEnv.formatRange(dateEnv.createMarker(d0), dateEnv.createMarker(d1), createFormatter(settings), settings); + }; + CalendarApi.prototype.formatIso = function (d, omitTime) { + var dateEnv = this.getCurrentData().dateEnv; + return dateEnv.formatIso(dateEnv.createMarker(d), { omitTime: omitTime }); + }; + // Date Selection / Event Selection / DayClick + // ----------------------------------------------------------------------------------------------------------------- + // this public method receives start/end dates in any format, with any timezone + // NOTE: args were changed from v3 + CalendarApi.prototype.select = function (dateOrObj, endDate) { + var selectionInput; + if (endDate == null) { + if (dateOrObj.start != null) { + selectionInput = dateOrObj; + } + else { + selectionInput = { + start: dateOrObj, + end: null, + }; + } + } + else { + selectionInput = { + start: dateOrObj, + end: endDate, + }; + } + var state = this.getCurrentData(); + var selection = parseDateSpan(selectionInput, state.dateEnv, createDuration({ days: 1 })); + if (selection) { // throw parse error otherwise? + this.dispatch({ type: 'SELECT_DATES', selection: selection }); + triggerDateSelect(selection, null, state); + } + }; + // public method + CalendarApi.prototype.unselect = function (pev) { + var state = this.getCurrentData(); + if (state.dateSelection) { + this.dispatch({ type: 'UNSELECT_DATES' }); + triggerDateUnselect(pev, state); + } + }; + // Public Events API + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.addEvent = function (eventInput, sourceInput) { + if (eventInput instanceof EventApi) { + var def = eventInput._def; + var instance = eventInput._instance; + var currentData = this.getCurrentData(); + // not already present? don't want to add an old snapshot + if (!currentData.eventStore.defs[def.defId]) { + this.dispatch({ + type: 'ADD_EVENTS', + eventStore: eventTupleToStore({ def: def, instance: instance }), // TODO: better util for two args? + }); + this.triggerEventAdd(eventInput); + } + return eventInput; + } + var state = this.getCurrentData(); + var eventSource; + if (sourceInput instanceof EventSourceApi) { + eventSource = sourceInput.internalEventSource; + } + else if (typeof sourceInput === 'boolean') { + if (sourceInput) { // true. part of the first event source + eventSource = hashValuesToArray(state.eventSources)[0]; + } + } + else if (sourceInput != null) { // an ID. accepts a number too + var sourceApi = this.getEventSourceById(sourceInput); // TODO: use an internal function + if (!sourceApi) { + console.warn("Could not find an event source with ID \"" + sourceInput + "\""); // TODO: test + return null; + } + eventSource = sourceApi.internalEventSource; + } + var tuple = parseEvent(eventInput, eventSource, state, false); + if (tuple) { + var newEventApi = new EventApi(state, tuple.def, tuple.def.recurringDef ? null : tuple.instance); + this.dispatch({ + type: 'ADD_EVENTS', + eventStore: eventTupleToStore(tuple), + }); + this.triggerEventAdd(newEventApi); + return newEventApi; + } + return null; + }; + CalendarApi.prototype.triggerEventAdd = function (eventApi) { + var _this = this; + var emitter = this.getCurrentData().emitter; + emitter.trigger('eventAdd', { + event: eventApi, + relatedEvents: [], + revert: function () { + _this.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: eventApiToStore(eventApi), + }); + }, + }); + }; + // TODO: optimize + CalendarApi.prototype.getEventById = function (id) { + var state = this.getCurrentData(); + var _a = state.eventStore, defs = _a.defs, instances = _a.instances; + id = String(id); + for (var defId in defs) { + var def = defs[defId]; + if (def.publicId === id) { + if (def.recurringDef) { + return new EventApi(state, def, null); + } + for (var instanceId in instances) { + var instance = instances[instanceId]; + if (instance.defId === def.defId) { + return new EventApi(state, def, instance); + } + } + } + } + return null; + }; + CalendarApi.prototype.getEvents = function () { + var currentData = this.getCurrentData(); + return buildEventApis(currentData.eventStore, currentData); + }; + CalendarApi.prototype.removeAllEvents = function () { + this.dispatch({ type: 'REMOVE_ALL_EVENTS' }); + }; + // Public Event Sources API + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.getEventSources = function () { + var state = this.getCurrentData(); + var sourceHash = state.eventSources; + var sourceApis = []; + for (var internalId in sourceHash) { + sourceApis.push(new EventSourceApi(state, sourceHash[internalId])); + } + return sourceApis; + }; + CalendarApi.prototype.getEventSourceById = function (id) { + var state = this.getCurrentData(); + var sourceHash = state.eventSources; + id = String(id); + for (var sourceId in sourceHash) { + if (sourceHash[sourceId].publicId === id) { + return new EventSourceApi(state, sourceHash[sourceId]); + } + } + return null; + }; + CalendarApi.prototype.addEventSource = function (sourceInput) { + var state = this.getCurrentData(); + if (sourceInput instanceof EventSourceApi) { + // not already present? don't want to add an old snapshot + if (!state.eventSources[sourceInput.internalEventSource.sourceId]) { + this.dispatch({ + type: 'ADD_EVENT_SOURCES', + sources: [sourceInput.internalEventSource], + }); + } + return sourceInput; + } + var eventSource = parseEventSource(sourceInput, state); + if (eventSource) { // TODO: error otherwise? + this.dispatch({ type: 'ADD_EVENT_SOURCES', sources: [eventSource] }); + return new EventSourceApi(state, eventSource); + } + return null; + }; + CalendarApi.prototype.removeAllEventSources = function () { + this.dispatch({ type: 'REMOVE_ALL_EVENT_SOURCES' }); + }; + CalendarApi.prototype.refetchEvents = function () { + this.dispatch({ type: 'FETCH_EVENT_SOURCES', isRefetch: true }); + }; + // Scroll + // ----------------------------------------------------------------------------------------------------------------- + CalendarApi.prototype.scrollToTime = function (timeInput) { + var time = createDuration(timeInput); + if (time) { + this.trigger('_scrollRequest', { time: time }); + } + }; + return CalendarApi; + }()); + + var EventApi = /** @class */ (function () { + // instance will be null if expressing a recurring event that has no current instances, + // OR if trying to validate an incoming external event that has no dates assigned + function EventApi(context, def, instance) { + this._context = context; + this._def = def; + this._instance = instance || null; + } + /* + TODO: make event struct more responsible for this + */ + EventApi.prototype.setProp = function (name, val) { + var _a, _b; + if (name in EVENT_DATE_REFINERS) { + console.warn('Could not set date-related prop \'name\'. Use one of the date-related methods instead.'); + // TODO: make proper aliasing system? + } + else if (name === 'id') { + val = EVENT_NON_DATE_REFINERS[name](val); + this.mutate({ + standardProps: { publicId: val }, // hardcoded internal name + }); + } + else if (name in EVENT_NON_DATE_REFINERS) { + val = EVENT_NON_DATE_REFINERS[name](val); + this.mutate({ + standardProps: (_a = {}, _a[name] = val, _a), + }); + } + else if (name in EVENT_UI_REFINERS) { + var ui = EVENT_UI_REFINERS[name](val); + if (name === 'color') { + ui = { backgroundColor: val, borderColor: val }; + } + else if (name === 'editable') { + ui = { startEditable: val, durationEditable: val }; + } + else { + ui = (_b = {}, _b[name] = val, _b); + } + this.mutate({ + standardProps: { ui: ui }, + }); + } + else { + console.warn("Could not set prop '" + name + "'. Use setExtendedProp instead."); + } + }; + EventApi.prototype.setExtendedProp = function (name, val) { + var _a; + this.mutate({ + extendedProps: (_a = {}, _a[name] = val, _a), + }); + }; + EventApi.prototype.setStart = function (startInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = this._context.dateEnv; + var start = dateEnv.createMarker(startInput); + if (start && this._instance) { // TODO: warning if parsed bad + var instanceRange = this._instance.range; + var startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); // what if parsed bad!? + if (options.maintainDuration) { + this.mutate({ datesDelta: startDelta }); + } + else { + this.mutate({ startDelta: startDelta }); + } + } + }; + EventApi.prototype.setEnd = function (endInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = this._context.dateEnv; + var end; + if (endInput != null) { + end = dateEnv.createMarker(endInput); + if (!end) { + return; // TODO: warning if parsed bad + } + } + if (this._instance) { + if (end) { + var endDelta = diffDates(this._instance.range.end, end, dateEnv, options.granularity); + this.mutate({ endDelta: endDelta }); + } + else { + this.mutate({ standardProps: { hasEnd: false } }); + } + } + }; + EventApi.prototype.setDates = function (startInput, endInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = this._context.dateEnv; + var standardProps = { allDay: options.allDay }; + var start = dateEnv.createMarker(startInput); + var end; + if (!start) { + return; // TODO: warning if parsed bad + } + if (endInput != null) { + end = dateEnv.createMarker(endInput); + if (!end) { // TODO: warning if parsed bad + return; + } + } + if (this._instance) { + var instanceRange = this._instance.range; + // when computing the diff for an event being converted to all-day, + // compute diff off of the all-day values the way event-mutation does. + if (options.allDay === true) { + instanceRange = computeAlignedDayRange(instanceRange); + } + var startDelta = diffDates(instanceRange.start, start, dateEnv, options.granularity); + if (end) { + var endDelta = diffDates(instanceRange.end, end, dateEnv, options.granularity); + if (durationsEqual(startDelta, endDelta)) { + this.mutate({ datesDelta: startDelta, standardProps: standardProps }); + } + else { + this.mutate({ startDelta: startDelta, endDelta: endDelta, standardProps: standardProps }); + } + } + else { // means "clear the end" + standardProps.hasEnd = false; + this.mutate({ datesDelta: startDelta, standardProps: standardProps }); + } + } + }; + EventApi.prototype.moveStart = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ startDelta: delta }); + } + }; + EventApi.prototype.moveEnd = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ endDelta: delta }); + } + }; + EventApi.prototype.moveDates = function (deltaInput) { + var delta = createDuration(deltaInput); + if (delta) { // TODO: warning if parsed bad + this.mutate({ datesDelta: delta }); + } + }; + EventApi.prototype.setAllDay = function (allDay, options) { + if (options === void 0) { options = {}; } + var standardProps = { allDay: allDay }; + var maintainDuration = options.maintainDuration; + if (maintainDuration == null) { + maintainDuration = this._context.options.allDayMaintainDuration; + } + if (this._def.allDay !== allDay) { + standardProps.hasEnd = maintainDuration; + } + this.mutate({ standardProps: standardProps }); + }; + EventApi.prototype.formatRange = function (formatInput) { + var dateEnv = this._context.dateEnv; + var instance = this._instance; + var formatter = createFormatter(formatInput); + if (this._def.hasEnd) { + return dateEnv.formatRange(instance.range.start, instance.range.end, formatter, { + forcedStartTzo: instance.forcedStartTzo, + forcedEndTzo: instance.forcedEndTzo, + }); + } + return dateEnv.format(instance.range.start, formatter, { + forcedTzo: instance.forcedStartTzo, + }); + }; + EventApi.prototype.mutate = function (mutation) { + var instance = this._instance; + if (instance) { + var def = this._def; + var context_1 = this._context; + var eventStore_1 = context_1.getCurrentData().eventStore; + var relevantEvents = getRelevantEvents(eventStore_1, instance.instanceId); + var eventConfigBase = { + '': { + display: '', + startEditable: true, + durationEditable: true, + constraints: [], + overlap: null, + allows: [], + backgroundColor: '', + borderColor: '', + textColor: '', + classNames: [], + }, + }; + relevantEvents = applyMutationToEventStore(relevantEvents, eventConfigBase, mutation, context_1); + var oldEvent = new EventApi(context_1, def, instance); // snapshot + this._def = relevantEvents.defs[def.defId]; + this._instance = relevantEvents.instances[instance.instanceId]; + context_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, + }); + context_1.emitter.trigger('eventChange', { + oldEvent: oldEvent, + event: this, + relatedEvents: buildEventApis(relevantEvents, context_1, instance), + revert: function () { + context_1.dispatch({ + type: 'RESET_EVENTS', + eventStore: eventStore_1, + }); + }, + }); + } + }; + EventApi.prototype.remove = function () { + var context = this._context; + var asStore = eventApiToStore(this); + context.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: asStore, + }); + context.emitter.trigger('eventRemove', { + event: this, + relatedEvents: [], + revert: function () { + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: asStore, + }); + }, + }); + }; + Object.defineProperty(EventApi.prototype, "source", { + get: function () { + var sourceId = this._def.sourceId; + if (sourceId) { + return new EventSourceApi(this._context, this._context.getCurrentData().eventSources[sourceId]); + } + return null; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "start", { + get: function () { + return this._instance ? + this._context.dateEnv.toDate(this._instance.range.start) : + null; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "end", { + get: function () { + return (this._instance && this._def.hasEnd) ? + this._context.dateEnv.toDate(this._instance.range.end) : + null; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "startStr", { + get: function () { + var instance = this._instance; + if (instance) { + return this._context.dateEnv.formatIso(instance.range.start, { + omitTime: this._def.allDay, + forcedTzo: instance.forcedStartTzo, + }); + } + return ''; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "endStr", { + get: function () { + var instance = this._instance; + if (instance && this._def.hasEnd) { + return this._context.dateEnv.formatIso(instance.range.end, { + omitTime: this._def.allDay, + forcedTzo: instance.forcedEndTzo, + }); + } + return ''; + }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "id", { + // computable props that all access the def + // TODO: find a TypeScript-compatible way to do this at scale + get: function () { return this._def.publicId; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "groupId", { + get: function () { return this._def.groupId; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "allDay", { + get: function () { return this._def.allDay; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "title", { + get: function () { return this._def.title; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "url", { + get: function () { return this._def.url; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "display", { + get: function () { return this._def.ui.display || 'auto'; } // bad. just normalize the type earlier + , + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "startEditable", { + get: function () { return this._def.ui.startEditable; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "durationEditable", { + get: function () { return this._def.ui.durationEditable; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "constraint", { + get: function () { return this._def.ui.constraints[0] || null; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "overlap", { + get: function () { return this._def.ui.overlap; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "allow", { + get: function () { return this._def.ui.allows[0] || null; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "backgroundColor", { + get: function () { return this._def.ui.backgroundColor; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "borderColor", { + get: function () { return this._def.ui.borderColor; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "textColor", { + get: function () { return this._def.ui.textColor; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "classNames", { + // NOTE: user can't modify these because Object.freeze was called in event-def parsing + get: function () { return this._def.ui.classNames; }, + enumerable: false, + configurable: true + }); + Object.defineProperty(EventApi.prototype, "extendedProps", { + get: function () { return this._def.extendedProps; }, + enumerable: false, + configurable: true + }); + EventApi.prototype.toPlainObject = function (settings) { + if (settings === void 0) { settings = {}; } + var def = this._def; + var ui = def.ui; + var _a = this, startStr = _a.startStr, endStr = _a.endStr; + var res = {}; + if (def.title) { + res.title = def.title; + } + if (startStr) { + res.start = startStr; + } + if (endStr) { + res.end = endStr; + } + if (def.publicId) { + res.id = def.publicId; + } + if (def.groupId) { + res.groupId = def.groupId; + } + if (def.url) { + res.url = def.url; + } + if (ui.display && ui.display !== 'auto') { + res.display = ui.display; + } + // TODO: what about recurring-event properties??? + // TODO: include startEditable/durationEditable/constraint/overlap/allow + if (settings.collapseColor && ui.backgroundColor && ui.backgroundColor === ui.borderColor) { + res.color = ui.backgroundColor; + } + else { + if (ui.backgroundColor) { + res.backgroundColor = ui.backgroundColor; + } + if (ui.borderColor) { + res.borderColor = ui.borderColor; + } + } + if (ui.textColor) { + res.textColor = ui.textColor; + } + if (ui.classNames.length) { + res.classNames = ui.classNames; + } + if (Object.keys(def.extendedProps).length) { + if (settings.collapseExtendedProps) { + __assign(res, def.extendedProps); + } + else { + res.extendedProps = def.extendedProps; + } + } + return res; + }; + EventApi.prototype.toJSON = function () { + return this.toPlainObject(); + }; + return EventApi; + }()); + function eventApiToStore(eventApi) { + var _a, _b; + var def = eventApi._def; + var instance = eventApi._instance; + return { + defs: (_a = {}, _a[def.defId] = def, _a), + instances: instance + ? (_b = {}, _b[instance.instanceId] = instance, _b) : {}, + }; + } + function buildEventApis(eventStore, context, excludeInstance) { + var defs = eventStore.defs, instances = eventStore.instances; + var eventApis = []; + var excludeInstanceId = excludeInstance ? excludeInstance.instanceId : ''; + for (var id in instances) { + var instance = instances[id]; + var def = defs[instance.defId]; + if (instance.instanceId !== excludeInstanceId) { + eventApis.push(new EventApi(context, def, instance)); + } + } + return eventApis; + } + + var calendarSystemClassMap = {}; + function registerCalendarSystem(name, theClass) { + calendarSystemClassMap[name] = theClass; + } + function createCalendarSystem(name) { + return new calendarSystemClassMap[name](); + } + var GregorianCalendarSystem = /** @class */ (function () { + function GregorianCalendarSystem() { + } + GregorianCalendarSystem.prototype.getMarkerYear = function (d) { + return d.getUTCFullYear(); + }; + GregorianCalendarSystem.prototype.getMarkerMonth = function (d) { + return d.getUTCMonth(); + }; + GregorianCalendarSystem.prototype.getMarkerDay = function (d) { + return d.getUTCDate(); + }; + GregorianCalendarSystem.prototype.arrayToMarker = function (arr) { + return arrayToUtcDate(arr); + }; + GregorianCalendarSystem.prototype.markerToArray = function (marker) { + return dateToUtcArray(marker); + }; + return GregorianCalendarSystem; + }()); + registerCalendarSystem('gregory', GregorianCalendarSystem); + + var ISO_RE = /^\s*(\d{4})(-?(\d{2})(-?(\d{2})([T ](\d{2}):?(\d{2})(:?(\d{2})(\.(\d+))?)?(Z|(([-+])(\d{2})(:?(\d{2}))?))?)?)?)?$/; + function parse(str) { + var m = ISO_RE.exec(str); + if (m) { + var marker = new Date(Date.UTC(Number(m[1]), m[3] ? Number(m[3]) - 1 : 0, Number(m[5] || 1), Number(m[7] || 0), Number(m[8] || 0), Number(m[10] || 0), m[12] ? Number("0." + m[12]) * 1000 : 0)); + if (isValidDate(marker)) { + var timeZoneOffset = null; + if (m[13]) { + timeZoneOffset = (m[15] === '-' ? -1 : 1) * (Number(m[16] || 0) * 60 + + Number(m[18] || 0)); + } + return { + marker: marker, + isTimeUnspecified: !m[6], + timeZoneOffset: timeZoneOffset, + }; + } + } + return null; + } + + var DateEnv = /** @class */ (function () { + function DateEnv(settings) { + var timeZone = this.timeZone = settings.timeZone; + var isNamedTimeZone = timeZone !== 'local' && timeZone !== 'UTC'; + if (settings.namedTimeZoneImpl && isNamedTimeZone) { + this.namedTimeZoneImpl = new settings.namedTimeZoneImpl(timeZone); + } + this.canComputeOffset = Boolean(!isNamedTimeZone || this.namedTimeZoneImpl); + this.calendarSystem = createCalendarSystem(settings.calendarSystem); + this.locale = settings.locale; + this.weekDow = settings.locale.week.dow; + this.weekDoy = settings.locale.week.doy; + if (settings.weekNumberCalculation === 'ISO') { + this.weekDow = 1; + this.weekDoy = 4; + } + if (typeof settings.firstDay === 'number') { + this.weekDow = settings.firstDay; + } + if (typeof settings.weekNumberCalculation === 'function') { + this.weekNumberFunc = settings.weekNumberCalculation; + } + this.weekText = settings.weekText != null ? settings.weekText : settings.locale.options.weekText; + this.cmdFormatter = settings.cmdFormatter; + this.defaultSeparator = settings.defaultSeparator; + } + // Creating / Parsing + DateEnv.prototype.createMarker = function (input) { + var meta = this.createMarkerMeta(input); + if (meta === null) { + return null; + } + return meta.marker; + }; + DateEnv.prototype.createNowMarker = function () { + if (this.canComputeOffset) { + return this.timestampToMarker(new Date().valueOf()); + } + // if we can't compute the current date val for a timezone, + // better to give the current local date vals than UTC + return arrayToUtcDate(dateToLocalArray(new Date())); + }; + DateEnv.prototype.createMarkerMeta = function (input) { + if (typeof input === 'string') { + return this.parse(input); + } + var marker = null; + if (typeof input === 'number') { + marker = this.timestampToMarker(input); + } + else if (input instanceof Date) { + input = input.valueOf(); + if (!isNaN(input)) { + marker = this.timestampToMarker(input); + } + } + else if (Array.isArray(input)) { + marker = arrayToUtcDate(input); + } + if (marker === null || !isValidDate(marker)) { + return null; + } + return { marker: marker, isTimeUnspecified: false, forcedTzo: null }; + }; + DateEnv.prototype.parse = function (s) { + var parts = parse(s); + if (parts === null) { + return null; + } + var marker = parts.marker; + var forcedTzo = null; + if (parts.timeZoneOffset !== null) { + if (this.canComputeOffset) { + marker = this.timestampToMarker(marker.valueOf() - parts.timeZoneOffset * 60 * 1000); + } + else { + forcedTzo = parts.timeZoneOffset; + } + } + return { marker: marker, isTimeUnspecified: parts.isTimeUnspecified, forcedTzo: forcedTzo }; + }; + // Accessors + DateEnv.prototype.getYear = function (marker) { + return this.calendarSystem.getMarkerYear(marker); + }; + DateEnv.prototype.getMonth = function (marker) { + return this.calendarSystem.getMarkerMonth(marker); + }; + // Adding / Subtracting + DateEnv.prototype.add = function (marker, dur) { + var a = this.calendarSystem.markerToArray(marker); + a[0] += dur.years; + a[1] += dur.months; + a[2] += dur.days; + a[6] += dur.milliseconds; + return this.calendarSystem.arrayToMarker(a); + }; + DateEnv.prototype.subtract = function (marker, dur) { + var a = this.calendarSystem.markerToArray(marker); + a[0] -= dur.years; + a[1] -= dur.months; + a[2] -= dur.days; + a[6] -= dur.milliseconds; + return this.calendarSystem.arrayToMarker(a); + }; + DateEnv.prototype.addYears = function (marker, n) { + var a = this.calendarSystem.markerToArray(marker); + a[0] += n; + return this.calendarSystem.arrayToMarker(a); + }; + DateEnv.prototype.addMonths = function (marker, n) { + var a = this.calendarSystem.markerToArray(marker); + a[1] += n; + return this.calendarSystem.arrayToMarker(a); + }; + // Diffing Whole Units + DateEnv.prototype.diffWholeYears = function (m0, m1) { + var calendarSystem = this.calendarSystem; + if (timeAsMs(m0) === timeAsMs(m1) && + calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1) && + calendarSystem.getMarkerMonth(m0) === calendarSystem.getMarkerMonth(m1)) { + return calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0); + } + return null; + }; + DateEnv.prototype.diffWholeMonths = function (m0, m1) { + var calendarSystem = this.calendarSystem; + if (timeAsMs(m0) === timeAsMs(m1) && + calendarSystem.getMarkerDay(m0) === calendarSystem.getMarkerDay(m1)) { + return (calendarSystem.getMarkerMonth(m1) - calendarSystem.getMarkerMonth(m0)) + + (calendarSystem.getMarkerYear(m1) - calendarSystem.getMarkerYear(m0)) * 12; + } + return null; + }; + // Range / Duration + DateEnv.prototype.greatestWholeUnit = function (m0, m1) { + var n = this.diffWholeYears(m0, m1); + if (n !== null) { + return { unit: 'year', value: n }; + } + n = this.diffWholeMonths(m0, m1); + if (n !== null) { + return { unit: 'month', value: n }; + } + n = diffWholeWeeks(m0, m1); + if (n !== null) { + return { unit: 'week', value: n }; + } + n = diffWholeDays(m0, m1); + if (n !== null) { + return { unit: 'day', value: n }; + } + n = diffHours(m0, m1); + if (isInt(n)) { + return { unit: 'hour', value: n }; + } + n = diffMinutes(m0, m1); + if (isInt(n)) { + return { unit: 'minute', value: n }; + } + n = diffSeconds(m0, m1); + if (isInt(n)) { + return { unit: 'second', value: n }; + } + return { unit: 'millisecond', value: m1.valueOf() - m0.valueOf() }; + }; + DateEnv.prototype.countDurationsBetween = function (m0, m1, d) { + // TODO: can use greatestWholeUnit + var diff; + if (d.years) { + diff = this.diffWholeYears(m0, m1); + if (diff !== null) { + return diff / asRoughYears(d); + } + } + if (d.months) { + diff = this.diffWholeMonths(m0, m1); + if (diff !== null) { + return diff / asRoughMonths(d); + } + } + if (d.days) { + diff = diffWholeDays(m0, m1); + if (diff !== null) { + return diff / asRoughDays(d); + } + } + return (m1.valueOf() - m0.valueOf()) / asRoughMs(d); + }; + // Start-Of + // these DON'T return zoned-dates. only UTC start-of dates + DateEnv.prototype.startOf = function (m, unit) { + if (unit === 'year') { + return this.startOfYear(m); + } + if (unit === 'month') { + return this.startOfMonth(m); + } + if (unit === 'week') { + return this.startOfWeek(m); + } + if (unit === 'day') { + return startOfDay(m); + } + if (unit === 'hour') { + return startOfHour(m); + } + if (unit === 'minute') { + return startOfMinute(m); + } + if (unit === 'second') { + return startOfSecond(m); + } + return null; + }; + DateEnv.prototype.startOfYear = function (m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + ]); + }; + DateEnv.prototype.startOfMonth = function (m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + this.calendarSystem.getMarkerMonth(m), + ]); + }; + DateEnv.prototype.startOfWeek = function (m) { + return this.calendarSystem.arrayToMarker([ + this.calendarSystem.getMarkerYear(m), + this.calendarSystem.getMarkerMonth(m), + m.getUTCDate() - ((m.getUTCDay() - this.weekDow + 7) % 7), + ]); + }; + // Week Number + DateEnv.prototype.computeWeekNumber = function (marker) { + if (this.weekNumberFunc) { + return this.weekNumberFunc(this.toDate(marker)); + } + return weekOfYear(marker, this.weekDow, this.weekDoy); + }; + // TODO: choke on timeZoneName: long + DateEnv.prototype.format = function (marker, formatter, dateOptions) { + if (dateOptions === void 0) { dateOptions = {}; } + return formatter.format({ + marker: marker, + timeZoneOffset: dateOptions.forcedTzo != null ? + dateOptions.forcedTzo : + this.offsetForMarker(marker), + }, this); + }; + DateEnv.prototype.formatRange = function (start, end, formatter, dateOptions) { + if (dateOptions === void 0) { dateOptions = {}; } + if (dateOptions.isEndExclusive) { + end = addMs(end, -1); + } + return formatter.formatRange({ + marker: start, + timeZoneOffset: dateOptions.forcedStartTzo != null ? + dateOptions.forcedStartTzo : + this.offsetForMarker(start), + }, { + marker: end, + timeZoneOffset: dateOptions.forcedEndTzo != null ? + dateOptions.forcedEndTzo : + this.offsetForMarker(end), + }, this, dateOptions.defaultSeparator); + }; + /* + DUMB: the omitTime arg is dumb. if we omit the time, we want to omit the timezone offset. and if we do that, + might as well use buildIsoString or some other util directly + */ + DateEnv.prototype.formatIso = function (marker, extraOptions) { + if (extraOptions === void 0) { extraOptions = {}; } + var timeZoneOffset = null; + if (!extraOptions.omitTimeZoneOffset) { + if (extraOptions.forcedTzo != null) { + timeZoneOffset = extraOptions.forcedTzo; + } + else { + timeZoneOffset = this.offsetForMarker(marker); + } + } + return buildIsoString(marker, timeZoneOffset, extraOptions.omitTime); + }; + // TimeZone + DateEnv.prototype.timestampToMarker = function (ms) { + if (this.timeZone === 'local') { + return arrayToUtcDate(dateToLocalArray(new Date(ms))); + } + if (this.timeZone === 'UTC' || !this.namedTimeZoneImpl) { + return new Date(ms); + } + return arrayToUtcDate(this.namedTimeZoneImpl.timestampToArray(ms)); + }; + DateEnv.prototype.offsetForMarker = function (m) { + if (this.timeZone === 'local') { + return -arrayToLocalDate(dateToUtcArray(m)).getTimezoneOffset(); // convert "inverse" offset to "normal" offset + } + if (this.timeZone === 'UTC') { + return 0; + } + if (this.namedTimeZoneImpl) { + return this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)); + } + return null; + }; + // Conversion + DateEnv.prototype.toDate = function (m, forcedTzo) { + if (this.timeZone === 'local') { + return arrayToLocalDate(dateToUtcArray(m)); + } + if (this.timeZone === 'UTC') { + return new Date(m.valueOf()); // make sure it's a copy + } + if (!this.namedTimeZoneImpl) { + return new Date(m.valueOf() - (forcedTzo || 0)); + } + return new Date(m.valueOf() - + this.namedTimeZoneImpl.offsetForArray(dateToUtcArray(m)) * 1000 * 60); + }; + return DateEnv; + }()); + + var globalLocales = []; + + var RAW_EN_LOCALE = { + code: 'en', + week: { + dow: 0, + doy: 4, // 4 days need to be within the year to be considered the first week + }, + direction: 'ltr', + buttonText: { + prev: 'prev', + next: 'next', + prevYear: 'prev year', + nextYear: 'next year', + year: 'year', + today: 'today', + month: 'month', + week: 'week', + day: 'day', + list: 'list', + }, + weekText: 'W', + allDayText: 'all-day', + moreLinkText: 'more', + noEventsText: 'No events to display', + }; + function organizeRawLocales(explicitRawLocales) { + var defaultCode = explicitRawLocales.length > 0 ? explicitRawLocales[0].code : 'en'; + var allRawLocales = globalLocales.concat(explicitRawLocales); + var rawLocaleMap = { + en: RAW_EN_LOCALE, // necessary? + }; + for (var _i = 0, allRawLocales_1 = allRawLocales; _i < allRawLocales_1.length; _i++) { + var rawLocale = allRawLocales_1[_i]; + rawLocaleMap[rawLocale.code] = rawLocale; + } + return { + map: rawLocaleMap, + defaultCode: defaultCode, + }; + } + function buildLocale(inputSingular, available) { + if (typeof inputSingular === 'object' && !Array.isArray(inputSingular)) { + return parseLocale(inputSingular.code, [inputSingular.code], inputSingular); + } + return queryLocale(inputSingular, available); + } + function queryLocale(codeArg, available) { + var codes = [].concat(codeArg || []); // will convert to array + var raw = queryRawLocale(codes, available) || RAW_EN_LOCALE; + return parseLocale(codeArg, codes, raw); + } + function queryRawLocale(codes, available) { + for (var i = 0; i < codes.length; i += 1) { + var parts = codes[i].toLocaleLowerCase().split('-'); + for (var j = parts.length; j > 0; j -= 1) { + var simpleId = parts.slice(0, j).join('-'); + if (available[simpleId]) { + return available[simpleId]; + } + } + } + return null; + } + function parseLocale(codeArg, codes, raw) { + var merged = mergeProps([RAW_EN_LOCALE, raw], ['buttonText']); + delete merged.code; // don't want this part of the options + var week = merged.week; + delete merged.week; + return { + codeArg: codeArg, + codes: codes, + week: week, + simpleNumberFormat: new Intl.NumberFormat(codeArg), + options: merged, + }; + } + + function formatDate(dateInput, options) { + if (options === void 0) { options = {}; } + var dateEnv = buildDateEnv$1(options); + var formatter = createFormatter(options); + var dateMeta = dateEnv.createMarkerMeta(dateInput); + if (!dateMeta) { // TODO: warning? + return ''; + } + return dateEnv.format(dateMeta.marker, formatter, { + forcedTzo: dateMeta.forcedTzo, + }); + } + function formatRange(startInput, endInput, options) { + var dateEnv = buildDateEnv$1(typeof options === 'object' && options ? options : {}); // pass in if non-null object + var formatter = createFormatter(options); + var startMeta = dateEnv.createMarkerMeta(startInput); + var endMeta = dateEnv.createMarkerMeta(endInput); + if (!startMeta || !endMeta) { // TODO: warning? + return ''; + } + return dateEnv.formatRange(startMeta.marker, endMeta.marker, formatter, { + forcedStartTzo: startMeta.forcedTzo, + forcedEndTzo: endMeta.forcedTzo, + isEndExclusive: options.isEndExclusive, + defaultSeparator: BASE_OPTION_DEFAULTS.defaultRangeSeparator, + }); + } + // TODO: more DRY and optimized + function buildDateEnv$1(settings) { + var locale = buildLocale(settings.locale || 'en', organizeRawLocales([]).map); // TODO: don't hardcode 'en' everywhere + return new DateEnv(__assign(__assign({ timeZone: BASE_OPTION_DEFAULTS.timeZone, calendarSystem: 'gregory' }, settings), { locale: locale })); + } + + var DEF_DEFAULTS = { + startTime: '09:00', + endTime: '17:00', + daysOfWeek: [1, 2, 3, 4, 5], + display: 'inverse-background', + classNames: 'fc-non-business', + groupId: '_businessHours', // so multiple defs get grouped + }; + /* + TODO: pass around as EventDefHash!!! + */ + function parseBusinessHours(input, context) { + return parseEvents(refineInputs(input), null, context); + } + function refineInputs(input) { + var rawDefs; + if (input === true) { + rawDefs = [{}]; // will get DEF_DEFAULTS verbatim + } + else if (Array.isArray(input)) { + // if specifying an array, every sub-definition NEEDS a day-of-week + rawDefs = input.filter(function (rawDef) { return rawDef.daysOfWeek; }); + } + else if (typeof input === 'object' && input) { // non-null object + rawDefs = [input]; + } + else { // is probably false + rawDefs = []; + } + rawDefs = rawDefs.map(function (rawDef) { return (__assign(__assign({}, DEF_DEFAULTS), rawDef)); }); + return rawDefs; + } + + function pointInsideRect(point, rect) { + return point.left >= rect.left && + point.left < rect.right && + point.top >= rect.top && + point.top < rect.bottom; + } + // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false + function intersectRects(rect1, rect2) { + var res = { + left: Math.max(rect1.left, rect2.left), + right: Math.min(rect1.right, rect2.right), + top: Math.max(rect1.top, rect2.top), + bottom: Math.min(rect1.bottom, rect2.bottom), + }; + if (res.left < res.right && res.top < res.bottom) { + return res; + } + return false; + } + function translateRect(rect, deltaX, deltaY) { + return { + left: rect.left + deltaX, + right: rect.right + deltaX, + top: rect.top + deltaY, + bottom: rect.bottom + deltaY, + }; + } + // Returns a new point that will have been moved to reside within the given rectangle + function constrainPoint(point, rect) { + return { + left: Math.min(Math.max(point.left, rect.left), rect.right), + top: Math.min(Math.max(point.top, rect.top), rect.bottom), + }; + } + // Returns a point that is the center of the given rectangle + function getRectCenter(rect) { + return { + left: (rect.left + rect.right) / 2, + top: (rect.top + rect.bottom) / 2, + }; + } + // Subtracts point2's coordinates from point1's coordinates, returning a delta + function diffPoints(point1, point2) { + return { + left: point1.left - point2.left, + top: point1.top - point2.top, + }; + } + + var canVGrowWithinCell; + function getCanVGrowWithinCell() { + if (canVGrowWithinCell == null) { + canVGrowWithinCell = computeCanVGrowWithinCell(); + } + return canVGrowWithinCell; + } + function computeCanVGrowWithinCell() { + // for SSR, because this function is call immediately at top-level + // TODO: just make this logic execute top-level, immediately, instead of doing lazily + if (typeof document === 'undefined') { + return true; + } + var el = document.createElement('div'); + el.style.position = 'absolute'; + el.style.top = '0px'; + el.style.left = '0px'; + el.innerHTML = '
'; + el.querySelector('table').style.height = '100px'; + el.querySelector('div').style.height = '100%'; + document.body.appendChild(el); + var div = el.querySelector('div'); + var possible = div.offsetHeight > 0; + document.body.removeChild(el); + return possible; + } + + var EMPTY_EVENT_STORE = createEmptyEventStore(); // for purecomponents. TODO: keep elsewhere + var Splitter = /** @class */ (function () { + function Splitter() { + this.getKeysForEventDefs = memoize(this._getKeysForEventDefs); + this.splitDateSelection = memoize(this._splitDateSpan); + this.splitEventStore = memoize(this._splitEventStore); + this.splitIndividualUi = memoize(this._splitIndividualUi); + this.splitEventDrag = memoize(this._splitInteraction); + this.splitEventResize = memoize(this._splitInteraction); + this.eventUiBuilders = {}; // TODO: typescript protection + } + Splitter.prototype.splitProps = function (props) { + var _this = this; + var keyInfos = this.getKeyInfo(props); + var defKeys = this.getKeysForEventDefs(props.eventStore); + var dateSelections = this.splitDateSelection(props.dateSelection); + var individualUi = this.splitIndividualUi(props.eventUiBases, defKeys); // the individual *bases* + var eventStores = this.splitEventStore(props.eventStore, defKeys); + var eventDrags = this.splitEventDrag(props.eventDrag); + var eventResizes = this.splitEventResize(props.eventResize); + var splitProps = {}; + this.eventUiBuilders = mapHash(keyInfos, function (info, key) { return _this.eventUiBuilders[key] || memoize(buildEventUiForKey); }); + for (var key in keyInfos) { + var keyInfo = keyInfos[key]; + var eventStore = eventStores[key] || EMPTY_EVENT_STORE; + var buildEventUi = this.eventUiBuilders[key]; + splitProps[key] = { + businessHours: keyInfo.businessHours || props.businessHours, + dateSelection: dateSelections[key] || null, + eventStore: eventStore, + eventUiBases: buildEventUi(props.eventUiBases[''], keyInfo.ui, individualUi[key]), + eventSelection: eventStore.instances[props.eventSelection] ? props.eventSelection : '', + eventDrag: eventDrags[key] || null, + eventResize: eventResizes[key] || null, + }; + } + return splitProps; + }; + Splitter.prototype._splitDateSpan = function (dateSpan) { + var dateSpans = {}; + if (dateSpan) { + var keys = this.getKeysForDateSpan(dateSpan); + for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { + var key = keys_1[_i]; + dateSpans[key] = dateSpan; + } + } + return dateSpans; + }; + Splitter.prototype._getKeysForEventDefs = function (eventStore) { + var _this = this; + return mapHash(eventStore.defs, function (eventDef) { return _this.getKeysForEventDef(eventDef); }); + }; + Splitter.prototype._splitEventStore = function (eventStore, defKeys) { + var defs = eventStore.defs, instances = eventStore.instances; + var splitStores = {}; + for (var defId in defs) { + for (var _i = 0, _a = defKeys[defId]; _i < _a.length; _i++) { + var key = _a[_i]; + if (!splitStores[key]) { + splitStores[key] = createEmptyEventStore(); + } + splitStores[key].defs[defId] = defs[defId]; + } + } + for (var instanceId in instances) { + var instance = instances[instanceId]; + for (var _b = 0, _c = defKeys[instance.defId]; _b < _c.length; _b++) { + var key = _c[_b]; + if (splitStores[key]) { // must have already been created + splitStores[key].instances[instanceId] = instance; + } + } + } + return splitStores; + }; + Splitter.prototype._splitIndividualUi = function (eventUiBases, defKeys) { + var splitHashes = {}; + for (var defId in eventUiBases) { + if (defId) { // not the '' key + for (var _i = 0, _a = defKeys[defId]; _i < _a.length; _i++) { + var key = _a[_i]; + if (!splitHashes[key]) { + splitHashes[key] = {}; + } + splitHashes[key][defId] = eventUiBases[defId]; + } + } + } + return splitHashes; + }; + Splitter.prototype._splitInteraction = function (interaction) { + var splitStates = {}; + if (interaction) { + var affectedStores_1 = this._splitEventStore(interaction.affectedEvents, this._getKeysForEventDefs(interaction.affectedEvents)); + // can't rely on defKeys because event data is mutated + var mutatedKeysByDefId = this._getKeysForEventDefs(interaction.mutatedEvents); + var mutatedStores_1 = this._splitEventStore(interaction.mutatedEvents, mutatedKeysByDefId); + var populate = function (key) { + if (!splitStates[key]) { + splitStates[key] = { + affectedEvents: affectedStores_1[key] || EMPTY_EVENT_STORE, + mutatedEvents: mutatedStores_1[key] || EMPTY_EVENT_STORE, + isEvent: interaction.isEvent, + }; + } + }; + for (var key in affectedStores_1) { + populate(key); + } + for (var key in mutatedStores_1) { + populate(key); + } + } + return splitStates; + }; + return Splitter; + }()); + function buildEventUiForKey(allUi, eventUiForKey, individualUi) { + var baseParts = []; + if (allUi) { + baseParts.push(allUi); + } + if (eventUiForKey) { + baseParts.push(eventUiForKey); + } + var stuff = { + '': combineEventUis(baseParts), + }; + if (individualUi) { + __assign(stuff, individualUi); + } + return stuff; + } + + function getDateMeta(date, todayRange, nowDate, dateProfile) { + return { + dow: date.getUTCDay(), + isDisabled: Boolean(dateProfile && !rangeContainsMarker(dateProfile.activeRange, date)), + isOther: Boolean(dateProfile && !rangeContainsMarker(dateProfile.currentRange, date)), + isToday: Boolean(todayRange && rangeContainsMarker(todayRange, date)), + isPast: Boolean(nowDate ? (date < nowDate) : todayRange ? (date < todayRange.start) : false), + isFuture: Boolean(nowDate ? (date > nowDate) : todayRange ? (date >= todayRange.end) : false), + }; + } + function getDayClassNames(meta, theme) { + var classNames = [ + 'fc-day', + "fc-day-" + DAY_IDS[meta.dow], + ]; + if (meta.isDisabled) { + classNames.push('fc-day-disabled'); + } + else { + if (meta.isToday) { + classNames.push('fc-day-today'); + classNames.push(theme.getClass('today')); + } + if (meta.isPast) { + classNames.push('fc-day-past'); + } + if (meta.isFuture) { + classNames.push('fc-day-future'); + } + if (meta.isOther) { + classNames.push('fc-day-other'); + } + } + return classNames; + } + function getSlotClassNames(meta, theme) { + var classNames = [ + 'fc-slot', + "fc-slot-" + DAY_IDS[meta.dow], + ]; + if (meta.isDisabled) { + classNames.push('fc-slot-disabled'); + } + else { + if (meta.isToday) { + classNames.push('fc-slot-today'); + classNames.push(theme.getClass('today')); + } + if (meta.isPast) { + classNames.push('fc-slot-past'); + } + if (meta.isFuture) { + classNames.push('fc-slot-future'); + } + } + return classNames; + } + + function buildNavLinkData(date, type) { + if (type === void 0) { type = 'day'; } + return JSON.stringify({ + date: formatDayString(date), + type: type, + }); + } + + var _isRtlScrollbarOnLeft = null; + function getIsRtlScrollbarOnLeft() { + if (_isRtlScrollbarOnLeft === null) { + _isRtlScrollbarOnLeft = computeIsRtlScrollbarOnLeft(); + } + return _isRtlScrollbarOnLeft; + } + function computeIsRtlScrollbarOnLeft() { + var outerEl = document.createElement('div'); + applyStyle(outerEl, { + position: 'absolute', + top: -1000, + left: 0, + border: 0, + padding: 0, + overflow: 'scroll', + direction: 'rtl', + }); + outerEl.innerHTML = '
'; + document.body.appendChild(outerEl); + var innerEl = outerEl.firstChild; + var res = innerEl.getBoundingClientRect().left > outerEl.getBoundingClientRect().left; + removeElement(outerEl); + return res; + } + + var _scrollbarWidths; + function getScrollbarWidths() { + if (!_scrollbarWidths) { + _scrollbarWidths = computeScrollbarWidths(); + } + return _scrollbarWidths; + } + function computeScrollbarWidths() { + var el = document.createElement('div'); + el.style.overflow = 'scroll'; + el.style.position = 'absolute'; + el.style.top = '-9999px'; + el.style.left = '-9999px'; + document.body.appendChild(el); + var res = computeScrollbarWidthsForEl(el); + document.body.removeChild(el); + return res; + } + // WARNING: will include border + function computeScrollbarWidthsForEl(el) { + return { + x: el.offsetHeight - el.clientHeight, + y: el.offsetWidth - el.clientWidth, + }; + } + + function computeEdges(el, getPadding) { + if (getPadding === void 0) { getPadding = false; } + var computedStyle = window.getComputedStyle(el); + var borderLeft = parseInt(computedStyle.borderLeftWidth, 10) || 0; + var borderRight = parseInt(computedStyle.borderRightWidth, 10) || 0; + var borderTop = parseInt(computedStyle.borderTopWidth, 10) || 0; + var borderBottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; + var badScrollbarWidths = computeScrollbarWidthsForEl(el); // includes border! + var scrollbarLeftRight = badScrollbarWidths.y - borderLeft - borderRight; + var scrollbarBottom = badScrollbarWidths.x - borderTop - borderBottom; + var res = { + borderLeft: borderLeft, + borderRight: borderRight, + borderTop: borderTop, + borderBottom: borderBottom, + scrollbarBottom: scrollbarBottom, + scrollbarLeft: 0, + scrollbarRight: 0, + }; + if (getIsRtlScrollbarOnLeft() && computedStyle.direction === 'rtl') { // is the scrollbar on the left side? + res.scrollbarLeft = scrollbarLeftRight; + } + else { + res.scrollbarRight = scrollbarLeftRight; + } + if (getPadding) { + res.paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0; + res.paddingRight = parseInt(computedStyle.paddingRight, 10) || 0; + res.paddingTop = parseInt(computedStyle.paddingTop, 10) || 0; + res.paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0; + } + return res; + } + function computeInnerRect(el, goWithinPadding, doFromWindowViewport) { + if (goWithinPadding === void 0) { goWithinPadding = false; } + var outerRect = doFromWindowViewport ? el.getBoundingClientRect() : computeRect(el); + var edges = computeEdges(el, goWithinPadding); + var res = { + left: outerRect.left + edges.borderLeft + edges.scrollbarLeft, + right: outerRect.right - edges.borderRight - edges.scrollbarRight, + top: outerRect.top + edges.borderTop, + bottom: outerRect.bottom - edges.borderBottom - edges.scrollbarBottom, + }; + if (goWithinPadding) { + res.left += edges.paddingLeft; + res.right -= edges.paddingRight; + res.top += edges.paddingTop; + res.bottom -= edges.paddingBottom; + } + return res; + } + function computeRect(el) { + var rect = el.getBoundingClientRect(); + return { + left: rect.left + window.pageXOffset, + top: rect.top + window.pageYOffset, + right: rect.right + window.pageXOffset, + bottom: rect.bottom + window.pageYOffset, + }; + } + function computeClippedClientRect(el) { + var clippingParents = getClippingParents(el); + var rect = el.getBoundingClientRect(); + for (var _i = 0, clippingParents_1 = clippingParents; _i < clippingParents_1.length; _i++) { + var clippingParent = clippingParents_1[_i]; + var intersection = intersectRects(rect, clippingParent.getBoundingClientRect()); + if (intersection) { + rect = intersection; + } + else { + return null; + } + } + return rect; + } + function computeHeightAndMargins(el) { + return el.getBoundingClientRect().height + computeVMargins(el); + } + function computeVMargins(el) { + var computed = window.getComputedStyle(el); + return parseInt(computed.marginTop, 10) + + parseInt(computed.marginBottom, 10); + } + // does not return window + function getClippingParents(el) { + var parents = []; + while (el instanceof HTMLElement) { // will stop when gets to document or null + var computedStyle = window.getComputedStyle(el); + if (computedStyle.position === 'fixed') { + break; + } + if ((/(auto|scroll)/).test(computedStyle.overflow + computedStyle.overflowY + computedStyle.overflowX)) { + parents.push(el); + } + el = el.parentNode; + } + return parents; + } + + // given a function that resolves a result asynchronously. + // the function can either call passed-in success and failure callbacks, + // or it can return a promise. + // if you need to pass additional params to func, bind them first. + function unpromisify(func, success, failure) { + // guard against success/failure callbacks being called more than once + // and guard against a promise AND callback being used together. + var isResolved = false; + var wrappedSuccess = function () { + if (!isResolved) { + isResolved = true; + success.apply(this, arguments); // eslint-disable-line prefer-rest-params + } + }; + var wrappedFailure = function () { + if (!isResolved) { + isResolved = true; + if (failure) { + failure.apply(this, arguments); // eslint-disable-line prefer-rest-params + } + } + }; + var res = func(wrappedSuccess, wrappedFailure); + if (res && typeof res.then === 'function') { + res.then(wrappedSuccess, wrappedFailure); + } + } + + var Emitter = /** @class */ (function () { + function Emitter() { + this.handlers = {}; + this.thisContext = null; + } + Emitter.prototype.setThisContext = function (thisContext) { + this.thisContext = thisContext; + }; + Emitter.prototype.setOptions = function (options) { + this.options = options; + }; + Emitter.prototype.on = function (type, handler) { + addToHash(this.handlers, type, handler); + }; + Emitter.prototype.off = function (type, handler) { + removeFromHash(this.handlers, type, handler); + }; + Emitter.prototype.trigger = function (type) { + var args = []; + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + var attachedHandlers = this.handlers[type] || []; + var optionHandler = this.options && this.options[type]; + var handlers = [].concat(optionHandler || [], attachedHandlers); + for (var _a = 0, handlers_1 = handlers; _a < handlers_1.length; _a++) { + var handler = handlers_1[_a]; + handler.apply(this.thisContext, args); + } + }; + Emitter.prototype.hasHandlers = function (type) { + return (this.handlers[type] && this.handlers[type].length) || + (this.options && this.options[type]); + }; + return Emitter; + }()); + function addToHash(hash, type, handler) { + (hash[type] || (hash[type] = [])) + .push(handler); + } + function removeFromHash(hash, type, handler) { + if (handler) { + if (hash[type]) { + hash[type] = hash[type].filter(function (func) { return func !== handler; }); + } + } + else { + delete hash[type]; // remove all handler funcs for this type + } + } + + /* + Records offset information for a set of elements, relative to an origin element. + Can record the left/right OR the top/bottom OR both. + Provides methods for querying the cache by position. + */ + var PositionCache = /** @class */ (function () { + function PositionCache(originEl, els, isHorizontal, isVertical) { + this.els = els; + var originClientRect = this.originClientRect = originEl.getBoundingClientRect(); // relative to viewport top-left + if (isHorizontal) { + this.buildElHorizontals(originClientRect.left); + } + if (isVertical) { + this.buildElVerticals(originClientRect.top); + } + } + // Populates the left/right internal coordinate arrays + PositionCache.prototype.buildElHorizontals = function (originClientLeft) { + var lefts = []; + var rights = []; + for (var _i = 0, _a = this.els; _i < _a.length; _i++) { + var el = _a[_i]; + var rect = el.getBoundingClientRect(); + lefts.push(rect.left - originClientLeft); + rights.push(rect.right - originClientLeft); + } + this.lefts = lefts; + this.rights = rights; + }; + // Populates the top/bottom internal coordinate arrays + PositionCache.prototype.buildElVerticals = function (originClientTop) { + var tops = []; + var bottoms = []; + for (var _i = 0, _a = this.els; _i < _a.length; _i++) { + var el = _a[_i]; + var rect = el.getBoundingClientRect(); + tops.push(rect.top - originClientTop); + bottoms.push(rect.bottom - originClientTop); + } + this.tops = tops; + this.bottoms = bottoms; + }; + // Given a left offset (from document left), returns the index of the el that it horizontally intersects. + // If no intersection is made, returns undefined. + PositionCache.prototype.leftToIndex = function (leftPosition) { + var _a = this, lefts = _a.lefts, rights = _a.rights; + var len = lefts.length; + var i; + for (i = 0; i < len; i += 1) { + if (leftPosition >= lefts[i] && leftPosition < rights[i]) { + return i; + } + } + return undefined; // TODO: better + }; + // Given a top offset (from document top), returns the index of the el that it vertically intersects. + // If no intersection is made, returns undefined. + PositionCache.prototype.topToIndex = function (topPosition) { + var _a = this, tops = _a.tops, bottoms = _a.bottoms; + var len = tops.length; + var i; + for (i = 0; i < len; i += 1) { + if (topPosition >= tops[i] && topPosition < bottoms[i]) { + return i; + } + } + return undefined; // TODO: better + }; + // Gets the width of the element at the given index + PositionCache.prototype.getWidth = function (leftIndex) { + return this.rights[leftIndex] - this.lefts[leftIndex]; + }; + // Gets the height of the element at the given index + PositionCache.prototype.getHeight = function (topIndex) { + return this.bottoms[topIndex] - this.tops[topIndex]; + }; + return PositionCache; + }()); + + /* eslint max-classes-per-file: "off" */ + /* + An object for getting/setting scroll-related information for an element. + Internally, this is done very differently for window versus DOM element, + so this object serves as a common interface. + */ + var ScrollController = /** @class */ (function () { + function ScrollController() { + } + ScrollController.prototype.getMaxScrollTop = function () { + return this.getScrollHeight() - this.getClientHeight(); + }; + ScrollController.prototype.getMaxScrollLeft = function () { + return this.getScrollWidth() - this.getClientWidth(); + }; + ScrollController.prototype.canScrollVertically = function () { + return this.getMaxScrollTop() > 0; + }; + ScrollController.prototype.canScrollHorizontally = function () { + return this.getMaxScrollLeft() > 0; + }; + ScrollController.prototype.canScrollUp = function () { + return this.getScrollTop() > 0; + }; + ScrollController.prototype.canScrollDown = function () { + return this.getScrollTop() < this.getMaxScrollTop(); + }; + ScrollController.prototype.canScrollLeft = function () { + return this.getScrollLeft() > 0; + }; + ScrollController.prototype.canScrollRight = function () { + return this.getScrollLeft() < this.getMaxScrollLeft(); + }; + return ScrollController; + }()); + var ElementScrollController = /** @class */ (function (_super) { + __extends(ElementScrollController, _super); + function ElementScrollController(el) { + var _this = _super.call(this) || this; + _this.el = el; + return _this; + } + ElementScrollController.prototype.getScrollTop = function () { + return this.el.scrollTop; + }; + ElementScrollController.prototype.getScrollLeft = function () { + return this.el.scrollLeft; + }; + ElementScrollController.prototype.setScrollTop = function (top) { + this.el.scrollTop = top; + }; + ElementScrollController.prototype.setScrollLeft = function (left) { + this.el.scrollLeft = left; + }; + ElementScrollController.prototype.getScrollWidth = function () { + return this.el.scrollWidth; + }; + ElementScrollController.prototype.getScrollHeight = function () { + return this.el.scrollHeight; + }; + ElementScrollController.prototype.getClientHeight = function () { + return this.el.clientHeight; + }; + ElementScrollController.prototype.getClientWidth = function () { + return this.el.clientWidth; + }; + return ElementScrollController; + }(ScrollController)); + var WindowScrollController = /** @class */ (function (_super) { + __extends(WindowScrollController, _super); + function WindowScrollController() { + return _super !== null && _super.apply(this, arguments) || this; + } + WindowScrollController.prototype.getScrollTop = function () { + return window.pageYOffset; + }; + WindowScrollController.prototype.getScrollLeft = function () { + return window.pageXOffset; + }; + WindowScrollController.prototype.setScrollTop = function (n) { + window.scroll(window.pageXOffset, n); + }; + WindowScrollController.prototype.setScrollLeft = function (n) { + window.scroll(n, window.pageYOffset); + }; + WindowScrollController.prototype.getScrollWidth = function () { + return document.documentElement.scrollWidth; + }; + WindowScrollController.prototype.getScrollHeight = function () { + return document.documentElement.scrollHeight; + }; + WindowScrollController.prototype.getClientHeight = function () { + return document.documentElement.clientHeight; + }; + WindowScrollController.prototype.getClientWidth = function () { + return document.documentElement.clientWidth; + }; + return WindowScrollController; + }(ScrollController)); + + var Theme = /** @class */ (function () { + function Theme(calendarOptions) { + if (this.iconOverrideOption) { + this.setIconOverride(calendarOptions[this.iconOverrideOption]); + } + } + Theme.prototype.setIconOverride = function (iconOverrideHash) { + var iconClassesCopy; + var buttonName; + if (typeof iconOverrideHash === 'object' && iconOverrideHash) { // non-null object + iconClassesCopy = __assign({}, this.iconClasses); + for (buttonName in iconOverrideHash) { + iconClassesCopy[buttonName] = this.applyIconOverridePrefix(iconOverrideHash[buttonName]); + } + this.iconClasses = iconClassesCopy; + } + else if (iconOverrideHash === false) { + this.iconClasses = {}; + } + }; + Theme.prototype.applyIconOverridePrefix = function (className) { + var prefix = this.iconOverridePrefix; + if (prefix && className.indexOf(prefix) !== 0) { // if not already present + className = prefix + className; + } + return className; + }; + Theme.prototype.getClass = function (key) { + return this.classes[key] || ''; + }; + Theme.prototype.getIconClass = function (buttonName, isRtl) { + var className; + if (isRtl && this.rtlIconClasses) { + className = this.rtlIconClasses[buttonName] || this.iconClasses[buttonName]; + } + else { + className = this.iconClasses[buttonName]; + } + if (className) { + return this.baseIconClass + " " + className; + } + return ''; + }; + Theme.prototype.getCustomButtonIconClass = function (customButtonProps) { + var className; + if (this.iconOverrideCustomButtonOption) { + className = customButtonProps[this.iconOverrideCustomButtonOption]; + if (className) { + return this.baseIconClass + " " + this.applyIconOverridePrefix(className); + } + } + return ''; + }; + return Theme; + }()); + Theme.prototype.classes = {}; + Theme.prototype.iconClasses = {}; + Theme.prototype.baseIconClass = ''; + Theme.prototype.iconOverridePrefix = ''; + + /// + if (typeof FullCalendarVDom === 'undefined') { + throw new Error('Please import the top-level fullcalendar lib before attempting to import a plugin.'); + } + var Component = FullCalendarVDom.Component; + var createElement = FullCalendarVDom.createElement; + var render = FullCalendarVDom.render; + var createRef = FullCalendarVDom.createRef; + var Fragment = FullCalendarVDom.Fragment; + var createContext = FullCalendarVDom.createContext; + var createPortal = FullCalendarVDom.createPortal; + var flushToDom = FullCalendarVDom.flushToDom; + var unmountComponentAtNode = FullCalendarVDom.unmountComponentAtNode; + /* eslint-enable */ + + var ScrollResponder = /** @class */ (function () { + function ScrollResponder(execFunc, emitter, scrollTime, scrollTimeReset) { + var _this = this; + this.execFunc = execFunc; + this.emitter = emitter; + this.scrollTime = scrollTime; + this.scrollTimeReset = scrollTimeReset; + this.handleScrollRequest = function (request) { + _this.queuedRequest = __assign({}, _this.queuedRequest || {}, request); + _this.drain(); + }; + emitter.on('_scrollRequest', this.handleScrollRequest); + this.fireInitialScroll(); + } + ScrollResponder.prototype.detach = function () { + this.emitter.off('_scrollRequest', this.handleScrollRequest); + }; + ScrollResponder.prototype.update = function (isDatesNew) { + if (isDatesNew && this.scrollTimeReset) { + this.fireInitialScroll(); // will drain + } + else { + this.drain(); + } + }; + ScrollResponder.prototype.fireInitialScroll = function () { + this.handleScrollRequest({ + time: this.scrollTime, + }); + }; + ScrollResponder.prototype.drain = function () { + if (this.queuedRequest && this.execFunc(this.queuedRequest)) { + this.queuedRequest = null; + } + }; + return ScrollResponder; + }()); + + var ViewContextType = createContext({}); // for Components + function buildViewContext(viewSpec, viewApi, viewOptions, dateProfileGenerator, dateEnv, theme, pluginHooks, dispatch, getCurrentData, emitter, calendarApi, registerInteractiveComponent, unregisterInteractiveComponent) { + return { + dateEnv: dateEnv, + options: viewOptions, + pluginHooks: pluginHooks, + emitter: emitter, + dispatch: dispatch, + getCurrentData: getCurrentData, + calendarApi: calendarApi, + viewSpec: viewSpec, + viewApi: viewApi, + dateProfileGenerator: dateProfileGenerator, + theme: theme, + isRtl: viewOptions.direction === 'rtl', + addResizeHandler: function (handler) { + emitter.on('_resize', handler); + }, + removeResizeHandler: function (handler) { + emitter.off('_resize', handler); + }, + createScrollResponder: function (execFunc) { + return new ScrollResponder(execFunc, emitter, createDuration(viewOptions.scrollTime), viewOptions.scrollTimeReset); + }, + registerInteractiveComponent: registerInteractiveComponent, + unregisterInteractiveComponent: unregisterInteractiveComponent, + }; + } + + /* eslint max-classes-per-file: off */ + var PureComponent = /** @class */ (function (_super) { + __extends(PureComponent, _super); + function PureComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + PureComponent.prototype.shouldComponentUpdate = function (nextProps, nextState) { + if (this.debug) { + // eslint-disable-next-line no-console + console.log(getUnequalProps(nextProps, this.props), getUnequalProps(nextState, this.state)); + } + return !compareObjs(this.props, nextProps, this.propEquality) || + !compareObjs(this.state, nextState, this.stateEquality); + }; + PureComponent.addPropsEquality = addPropsEquality; + PureComponent.addStateEquality = addStateEquality; + PureComponent.contextType = ViewContextType; + return PureComponent; + }(Component)); + PureComponent.prototype.propEquality = {}; + PureComponent.prototype.stateEquality = {}; + var BaseComponent = /** @class */ (function (_super) { + __extends(BaseComponent, _super); + function BaseComponent() { + return _super !== null && _super.apply(this, arguments) || this; + } + BaseComponent.contextType = ViewContextType; + return BaseComponent; + }(PureComponent)); + function addPropsEquality(propEquality) { + var hash = Object.create(this.prototype.propEquality); + __assign(hash, propEquality); + this.prototype.propEquality = hash; + } + function addStateEquality(stateEquality) { + var hash = Object.create(this.prototype.stateEquality); + __assign(hash, stateEquality); + this.prototype.stateEquality = hash; + } + // use other one + function setRef(ref, current) { + if (typeof ref === 'function') { + ref(current); + } + else if (ref) { + // see https://github.com/facebook/react/issues/13029 + ref.current = current; + } + } + + /* + an INTERACTABLE date component + + PURPOSES: + - hook up to fg, fill, and mirror renderers + - interface for dragging and hits + */ + var DateComponent = /** @class */ (function (_super) { + __extends(DateComponent, _super); + function DateComponent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.uid = guid(); + return _this; + } + // Hit System + // ----------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.prepareHits = function () { + }; + DateComponent.prototype.queryHit = function (positionLeft, positionTop, elWidth, elHeight) { + return null; // this should be abstract + }; + // Pointer Interaction Utils + // ----------------------------------------------------------------------------------------------------------------- + DateComponent.prototype.isValidSegDownEl = function (el) { + return !this.props.eventDrag && // HACK + !this.props.eventResize && // HACK + !elementClosest(el, '.fc-event-mirror'); + }; + DateComponent.prototype.isValidDateDownEl = function (el) { + return !elementClosest(el, '.fc-event:not(.fc-bg-event)') && + !elementClosest(el, '.fc-more-link') && // a "more.." link + !elementClosest(el, 'a[data-navlink]') && // a clickable nav link + !elementClosest(el, '.fc-popover'); // hack + }; + return DateComponent; + }(BaseComponent)); + + // TODO: easier way to add new hooks? need to update a million things + function createPlugin(input) { + return { + id: guid(), + deps: input.deps || [], + reducers: input.reducers || [], + isLoadingFuncs: input.isLoadingFuncs || [], + contextInit: [].concat(input.contextInit || []), + eventRefiners: input.eventRefiners || {}, + eventDefMemberAdders: input.eventDefMemberAdders || [], + eventSourceRefiners: input.eventSourceRefiners || {}, + isDraggableTransformers: input.isDraggableTransformers || [], + eventDragMutationMassagers: input.eventDragMutationMassagers || [], + eventDefMutationAppliers: input.eventDefMutationAppliers || [], + dateSelectionTransformers: input.dateSelectionTransformers || [], + datePointTransforms: input.datePointTransforms || [], + dateSpanTransforms: input.dateSpanTransforms || [], + views: input.views || {}, + viewPropsTransformers: input.viewPropsTransformers || [], + isPropsValid: input.isPropsValid || null, + externalDefTransforms: input.externalDefTransforms || [], + viewContainerAppends: input.viewContainerAppends || [], + eventDropTransformers: input.eventDropTransformers || [], + componentInteractions: input.componentInteractions || [], + calendarInteractions: input.calendarInteractions || [], + themeClasses: input.themeClasses || {}, + eventSourceDefs: input.eventSourceDefs || [], + cmdFormatter: input.cmdFormatter, + recurringTypes: input.recurringTypes || [], + namedTimeZonedImpl: input.namedTimeZonedImpl, + initialView: input.initialView || '', + elementDraggingImpl: input.elementDraggingImpl, + optionChangeHandlers: input.optionChangeHandlers || {}, + scrollGridImpl: input.scrollGridImpl || null, + contentTypeHandlers: input.contentTypeHandlers || {}, + listenerRefiners: input.listenerRefiners || {}, + optionRefiners: input.optionRefiners || {}, + propSetHandlers: input.propSetHandlers || {}, + }; + } + function buildPluginHooks(pluginDefs, globalDefs) { + var isAdded = {}; + var hooks = { + reducers: [], + isLoadingFuncs: [], + contextInit: [], + eventRefiners: {}, + eventDefMemberAdders: [], + eventSourceRefiners: {}, + isDraggableTransformers: [], + eventDragMutationMassagers: [], + eventDefMutationAppliers: [], + dateSelectionTransformers: [], + datePointTransforms: [], + dateSpanTransforms: [], + views: {}, + viewPropsTransformers: [], + isPropsValid: null, + externalDefTransforms: [], + viewContainerAppends: [], + eventDropTransformers: [], + componentInteractions: [], + calendarInteractions: [], + themeClasses: {}, + eventSourceDefs: [], + cmdFormatter: null, + recurringTypes: [], + namedTimeZonedImpl: null, + initialView: '', + elementDraggingImpl: null, + optionChangeHandlers: {}, + scrollGridImpl: null, + contentTypeHandlers: {}, + listenerRefiners: {}, + optionRefiners: {}, + propSetHandlers: {}, + }; + function addDefs(defs) { + for (var _i = 0, defs_1 = defs; _i < defs_1.length; _i++) { + var def = defs_1[_i]; + if (!isAdded[def.id]) { + isAdded[def.id] = true; + addDefs(def.deps); + hooks = combineHooks(hooks, def); + } + } + } + if (pluginDefs) { + addDefs(pluginDefs); + } + addDefs(globalDefs); + return hooks; + } + function buildBuildPluginHooks() { + var currentOverrideDefs = []; + var currentGlobalDefs = []; + var currentHooks; + return function (overrideDefs, globalDefs) { + if (!currentHooks || !isArraysEqual(overrideDefs, currentOverrideDefs) || !isArraysEqual(globalDefs, currentGlobalDefs)) { + currentHooks = buildPluginHooks(overrideDefs, globalDefs); + } + currentOverrideDefs = overrideDefs; + currentGlobalDefs = globalDefs; + return currentHooks; + }; + } + function combineHooks(hooks0, hooks1) { + return { + reducers: hooks0.reducers.concat(hooks1.reducers), + isLoadingFuncs: hooks0.isLoadingFuncs.concat(hooks1.isLoadingFuncs), + contextInit: hooks0.contextInit.concat(hooks1.contextInit), + eventRefiners: __assign(__assign({}, hooks0.eventRefiners), hooks1.eventRefiners), + eventDefMemberAdders: hooks0.eventDefMemberAdders.concat(hooks1.eventDefMemberAdders), + eventSourceRefiners: __assign(__assign({}, hooks0.eventSourceRefiners), hooks1.eventSourceRefiners), + isDraggableTransformers: hooks0.isDraggableTransformers.concat(hooks1.isDraggableTransformers), + eventDragMutationMassagers: hooks0.eventDragMutationMassagers.concat(hooks1.eventDragMutationMassagers), + eventDefMutationAppliers: hooks0.eventDefMutationAppliers.concat(hooks1.eventDefMutationAppliers), + dateSelectionTransformers: hooks0.dateSelectionTransformers.concat(hooks1.dateSelectionTransformers), + datePointTransforms: hooks0.datePointTransforms.concat(hooks1.datePointTransforms), + dateSpanTransforms: hooks0.dateSpanTransforms.concat(hooks1.dateSpanTransforms), + views: __assign(__assign({}, hooks0.views), hooks1.views), + viewPropsTransformers: hooks0.viewPropsTransformers.concat(hooks1.viewPropsTransformers), + isPropsValid: hooks1.isPropsValid || hooks0.isPropsValid, + externalDefTransforms: hooks0.externalDefTransforms.concat(hooks1.externalDefTransforms), + viewContainerAppends: hooks0.viewContainerAppends.concat(hooks1.viewContainerAppends), + eventDropTransformers: hooks0.eventDropTransformers.concat(hooks1.eventDropTransformers), + calendarInteractions: hooks0.calendarInteractions.concat(hooks1.calendarInteractions), + componentInteractions: hooks0.componentInteractions.concat(hooks1.componentInteractions), + themeClasses: __assign(__assign({}, hooks0.themeClasses), hooks1.themeClasses), + eventSourceDefs: hooks0.eventSourceDefs.concat(hooks1.eventSourceDefs), + cmdFormatter: hooks1.cmdFormatter || hooks0.cmdFormatter, + recurringTypes: hooks0.recurringTypes.concat(hooks1.recurringTypes), + namedTimeZonedImpl: hooks1.namedTimeZonedImpl || hooks0.namedTimeZonedImpl, + initialView: hooks0.initialView || hooks1.initialView, + elementDraggingImpl: hooks0.elementDraggingImpl || hooks1.elementDraggingImpl, + optionChangeHandlers: __assign(__assign({}, hooks0.optionChangeHandlers), hooks1.optionChangeHandlers), + scrollGridImpl: hooks1.scrollGridImpl || hooks0.scrollGridImpl, + contentTypeHandlers: __assign(__assign({}, hooks0.contentTypeHandlers), hooks1.contentTypeHandlers), + listenerRefiners: __assign(__assign({}, hooks0.listenerRefiners), hooks1.listenerRefiners), + optionRefiners: __assign(__assign({}, hooks0.optionRefiners), hooks1.optionRefiners), + propSetHandlers: __assign(__assign({}, hooks0.propSetHandlers), hooks1.propSetHandlers), + }; + } + + var StandardTheme = /** @class */ (function (_super) { + __extends(StandardTheme, _super); + function StandardTheme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return StandardTheme; + }(Theme)); + StandardTheme.prototype.classes = { + root: 'fc-theme-standard', + tableCellShaded: 'fc-cell-shaded', + buttonGroup: 'fc-button-group', + button: 'fc-button fc-button-primary', + buttonActive: 'fc-button-active', + }; + StandardTheme.prototype.baseIconClass = 'fc-icon'; + StandardTheme.prototype.iconClasses = { + close: 'fc-icon-x', + prev: 'fc-icon-chevron-left', + next: 'fc-icon-chevron-right', + prevYear: 'fc-icon-chevrons-left', + nextYear: 'fc-icon-chevrons-right', + }; + StandardTheme.prototype.rtlIconClasses = { + prev: 'fc-icon-chevron-right', + next: 'fc-icon-chevron-left', + prevYear: 'fc-icon-chevrons-right', + nextYear: 'fc-icon-chevrons-left', + }; + StandardTheme.prototype.iconOverrideOption = 'buttonIcons'; // TODO: make TS-friendly + StandardTheme.prototype.iconOverrideCustomButtonOption = 'icon'; + StandardTheme.prototype.iconOverridePrefix = 'fc-icon-'; + + function compileViewDefs(defaultConfigs, overrideConfigs) { + var hash = {}; + var viewType; + for (viewType in defaultConfigs) { + ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); + } + for (viewType in overrideConfigs) { + ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs); + } + return hash; + } + function ensureViewDef(viewType, hash, defaultConfigs, overrideConfigs) { + if (hash[viewType]) { + return hash[viewType]; + } + var viewDef = buildViewDef(viewType, hash, defaultConfigs, overrideConfigs); + if (viewDef) { + hash[viewType] = viewDef; + } + return viewDef; + } + function buildViewDef(viewType, hash, defaultConfigs, overrideConfigs) { + var defaultConfig = defaultConfigs[viewType]; + var overrideConfig = overrideConfigs[viewType]; + var queryProp = function (name) { return ((defaultConfig && defaultConfig[name] !== null) ? defaultConfig[name] : + ((overrideConfig && overrideConfig[name] !== null) ? overrideConfig[name] : null)); }; + var theComponent = queryProp('component'); + var superType = queryProp('superType'); + var superDef = null; + if (superType) { + if (superType === viewType) { + throw new Error('Can\'t have a custom view type that references itself'); + } + superDef = ensureViewDef(superType, hash, defaultConfigs, overrideConfigs); + } + if (!theComponent && superDef) { + theComponent = superDef.component; + } + if (!theComponent) { + return null; // don't throw a warning, might be settings for a single-unit view + } + return { + type: viewType, + component: theComponent, + defaults: __assign(__assign({}, (superDef ? superDef.defaults : {})), (defaultConfig ? defaultConfig.rawOptions : {})), + overrides: __assign(__assign({}, (superDef ? superDef.overrides : {})), (overrideConfig ? overrideConfig.rawOptions : {})), + }; + } + + /* eslint max-classes-per-file: off */ + // NOTE: in JSX, you should always use this class with arg. otherwise, will default to any??? + var RenderHook = /** @class */ (function (_super) { + __extends(RenderHook, _super); + function RenderHook() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + _this.handleRootEl = function (el) { + setRef(_this.rootElRef, el); + if (_this.props.elRef) { + setRef(_this.props.elRef, el); + } + }; + return _this; + } + RenderHook.prototype.render = function () { + var _this = this; + var props = this.props; + var hookProps = props.hookProps; + return (createElement(MountHook, { hookProps: hookProps, didMount: props.didMount, willUnmount: props.willUnmount, elRef: this.handleRootEl }, function (rootElRef) { return (createElement(ContentHook, { hookProps: hookProps, content: props.content, defaultContent: props.defaultContent, backupElRef: _this.rootElRef }, function (innerElRef, innerContent) { return props.children(rootElRef, normalizeClassNames(props.classNames, hookProps), innerElRef, innerContent); })); })); + }; + return RenderHook; + }(BaseComponent)); + // TODO: rename to be about function, not default. use in above type + // for forcing rerender of components that use the ContentHook + var CustomContentRenderContext = createContext(0); + function ContentHook(props) { + return (createElement(CustomContentRenderContext.Consumer, null, function (renderId) { return (createElement(ContentHookInner, __assign({ renderId: renderId }, props))); })); + } + var ContentHookInner = /** @class */ (function (_super) { + __extends(ContentHookInner, _super); + function ContentHookInner() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.innerElRef = createRef(); + return _this; + } + ContentHookInner.prototype.render = function () { + return this.props.children(this.innerElRef, this.renderInnerContent()); + }; + ContentHookInner.prototype.componentDidMount = function () { + this.updateCustomContent(); + }; + ContentHookInner.prototype.componentDidUpdate = function () { + this.updateCustomContent(); + }; + ContentHookInner.prototype.componentWillUnmount = function () { + if (this.customContentInfo && this.customContentInfo.destroy) { + this.customContentInfo.destroy(); + } + }; + ContentHookInner.prototype.renderInnerContent = function () { + var customContentInfo = this.customContentInfo; // only populated if using non-[p]react node(s) + var innerContent = this.getInnerContent(); + var meta = this.getContentMeta(innerContent); + // initial run, or content-type changing? (from vue -> react for example) + if (!customContentInfo || customContentInfo.contentKey !== meta.contentKey) { + // clearing old value + if (customContentInfo) { + if (customContentInfo.destroy) { + customContentInfo.destroy(); + } + customContentInfo = this.customContentInfo = null; + } + // assigning new value + if (meta.contentKey) { + customContentInfo = this.customContentInfo = __assign({ contentKey: meta.contentKey, contentVal: innerContent[meta.contentKey] }, meta.buildLifecycleFuncs()); + } + // updating + } + else if (customContentInfo) { + customContentInfo.contentVal = innerContent[meta.contentKey]; + } + return customContentInfo + ? [] // signal that something was specified + : innerContent; // assume a [p]react vdom node. use it + }; + ContentHookInner.prototype.getInnerContent = function () { + var props = this.props; + var innerContent = normalizeContent(props.content, props.hookProps); + if (innerContent === undefined) { // use the default + innerContent = normalizeContent(props.defaultContent, props.hookProps); + } + return innerContent == null ? null : innerContent; // convert undefined to null (better for React) + }; + ContentHookInner.prototype.getContentMeta = function (innerContent) { + var contentTypeHandlers = this.context.pluginHooks.contentTypeHandlers; + var contentKey = ''; + var buildLifecycleFuncs = null; + if (innerContent) { // allowed to be null, for convenience to caller + for (var searchKey in contentTypeHandlers) { + if (innerContent[searchKey] !== undefined) { + contentKey = searchKey; + buildLifecycleFuncs = contentTypeHandlers[searchKey]; + break; + } + } + } + return { contentKey: contentKey, buildLifecycleFuncs: buildLifecycleFuncs }; + }; + ContentHookInner.prototype.updateCustomContent = function () { + if (this.customContentInfo) { // for non-[p]react + this.customContentInfo.render(this.innerElRef.current || this.props.backupElRef.current, // the element to render into + this.customContentInfo.contentVal); + } + }; + return ContentHookInner; + }(BaseComponent)); + var MountHook = /** @class */ (function (_super) { + __extends(MountHook, _super); + function MountHook() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handleRootEl = function (rootEl) { + _this.rootEl = rootEl; + if (_this.props.elRef) { + setRef(_this.props.elRef, rootEl); + } + }; + return _this; + } + MountHook.prototype.render = function () { + return this.props.children(this.handleRootEl); + }; + MountHook.prototype.componentDidMount = function () { + var callback = this.props.didMount; + if (callback) { + callback(__assign(__assign({}, this.props.hookProps), { el: this.rootEl })); + } + }; + MountHook.prototype.componentWillUnmount = function () { + var callback = this.props.willUnmount; + if (callback) { + callback(__assign(__assign({}, this.props.hookProps), { el: this.rootEl })); + } + }; + return MountHook; + }(BaseComponent)); + function buildClassNameNormalizer() { + var currentGenerator; + var currentHookProps; + var currentClassNames = []; + return function (generator, hookProps) { + if (!currentHookProps || !isPropsEqual(currentHookProps, hookProps) || generator !== currentGenerator) { + currentGenerator = generator; + currentHookProps = hookProps; + currentClassNames = normalizeClassNames(generator, hookProps); + } + return currentClassNames; + }; + } + function normalizeClassNames(classNames, hookProps) { + if (typeof classNames === 'function') { + classNames = classNames(hookProps); + } + return parseClassNames(classNames); + } + function normalizeContent(input, hookProps) { + if (typeof input === 'function') { + return input(hookProps, createElement); // give the function the vdom-creation func + } + return input; + } + + var ViewRoot = /** @class */ (function (_super) { + __extends(ViewRoot, _super); + function ViewRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.normalizeClassNames = buildClassNameNormalizer(); + return _this; + } + ViewRoot.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var hookProps = { view: context.viewApi }; + var customClassNames = this.normalizeClassNames(options.viewClassNames, hookProps); + return (createElement(MountHook, { hookProps: hookProps, didMount: options.viewDidMount, willUnmount: options.viewWillUnmount, elRef: props.elRef }, function (rootElRef) { return props.children(rootElRef, ["fc-" + props.viewSpec.type + "-view", 'fc-view'].concat(customClassNames)); })); + }; + return ViewRoot; + }(BaseComponent)); + + function parseViewConfigs(inputs) { + return mapHash(inputs, parseViewConfig); + } + function parseViewConfig(input) { + var rawOptions = typeof input === 'function' ? + { component: input } : + input; + var component = rawOptions.component; + if (rawOptions.content) { + component = createViewHookComponent(rawOptions); + // TODO: remove content/classNames/didMount/etc from options? + } + return { + superType: rawOptions.type, + component: component, + rawOptions: rawOptions, + }; + } + function createViewHookComponent(options) { + return function (viewProps) { return (createElement(ViewContextType.Consumer, null, function (context) { return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (viewElRef, viewClassNames) { + var hookProps = __assign(__assign({}, viewProps), { nextDayThreshold: context.options.nextDayThreshold }); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.classNames, content: options.content, didMount: options.didMount, willUnmount: options.willUnmount, elRef: viewElRef }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("div", { className: viewClassNames.concat(customClassNames).join(' '), ref: rootElRef }, innerContent)); })); + })); })); }; + } + + function buildViewSpecs(defaultInputs, optionOverrides, dynamicOptionOverrides, localeDefaults) { + var defaultConfigs = parseViewConfigs(defaultInputs); + var overrideConfigs = parseViewConfigs(optionOverrides.views); + var viewDefs = compileViewDefs(defaultConfigs, overrideConfigs); + return mapHash(viewDefs, function (viewDef) { return buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults); }); + } + function buildViewSpec(viewDef, overrideConfigs, optionOverrides, dynamicOptionOverrides, localeDefaults) { + var durationInput = viewDef.overrides.duration || + viewDef.defaults.duration || + dynamicOptionOverrides.duration || + optionOverrides.duration; + var duration = null; + var durationUnit = ''; + var singleUnit = ''; + var singleUnitOverrides = {}; + if (durationInput) { + duration = createDurationCached(durationInput); + if (duration) { // valid? + var denom = greatestDurationDenominator(duration); + durationUnit = denom.unit; + if (denom.value === 1) { + singleUnit = durationUnit; + singleUnitOverrides = overrideConfigs[durationUnit] ? overrideConfigs[durationUnit].rawOptions : {}; + } + } + } + var queryButtonText = function (optionsSubset) { + var buttonTextMap = optionsSubset.buttonText || {}; + var buttonTextKey = viewDef.defaults.buttonTextKey; + if (buttonTextKey != null && buttonTextMap[buttonTextKey] != null) { + return buttonTextMap[buttonTextKey]; + } + if (buttonTextMap[viewDef.type] != null) { + return buttonTextMap[viewDef.type]; + } + if (buttonTextMap[singleUnit] != null) { + return buttonTextMap[singleUnit]; + } + return null; + }; + return { + type: viewDef.type, + component: viewDef.component, + duration: duration, + durationUnit: durationUnit, + singleUnit: singleUnit, + optionDefaults: viewDef.defaults, + optionOverrides: __assign(__assign({}, singleUnitOverrides), viewDef.overrides), + buttonTextOverride: queryButtonText(dynamicOptionOverrides) || + queryButtonText(optionOverrides) || // constructor-specified buttonText lookup hash takes precedence + viewDef.overrides.buttonText, + buttonTextDefault: queryButtonText(localeDefaults) || + viewDef.defaults.buttonText || + queryButtonText(BASE_OPTION_DEFAULTS) || + viewDef.type, // fall back to given view name + }; + } + // hack to get memoization working + var durationInputMap = {}; + function createDurationCached(durationInput) { + var json = JSON.stringify(durationInput); + var res = durationInputMap[json]; + if (res === undefined) { + res = createDuration(durationInput); + durationInputMap[json] = res; + } + return res; + } + + var DateProfileGenerator = /** @class */ (function () { + function DateProfileGenerator(props) { + this.props = props; + this.nowDate = getNow(props.nowInput, props.dateEnv); + this.initHiddenDays(); + } + /* Date Range Computation + ------------------------------------------------------------------------------------------------------------------*/ + // Builds a structure with info about what the dates/ranges will be for the "prev" view. + DateProfileGenerator.prototype.buildPrev = function (currentDateProfile, currentDate, forceToValid) { + var dateEnv = this.props.dateEnv; + var prevDate = dateEnv.subtract(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month + currentDateProfile.dateIncrement); + return this.build(prevDate, -1, forceToValid); + }; + // Builds a structure with info about what the dates/ranges will be for the "next" view. + DateProfileGenerator.prototype.buildNext = function (currentDateProfile, currentDate, forceToValid) { + var dateEnv = this.props.dateEnv; + var nextDate = dateEnv.add(dateEnv.startOf(currentDate, currentDateProfile.currentRangeUnit), // important for start-of-month + currentDateProfile.dateIncrement); + return this.build(nextDate, 1, forceToValid); + }; + // Builds a structure holding dates/ranges for rendering around the given date. + // Optional direction param indicates whether the date is being incremented/decremented + // from its previous value. decremented = -1, incremented = 1 (default). + DateProfileGenerator.prototype.build = function (currentDate, direction, forceToValid) { + if (forceToValid === void 0) { forceToValid = true; } + var props = this.props; + var validRange; + var currentInfo; + var isRangeAllDay; + var renderRange; + var activeRange; + var isValid; + validRange = this.buildValidRange(); + validRange = this.trimHiddenDays(validRange); + if (forceToValid) { + currentDate = constrainMarkerToRange(currentDate, validRange); + } + currentInfo = this.buildCurrentRangeInfo(currentDate, direction); + isRangeAllDay = /^(year|month|week|day)$/.test(currentInfo.unit); + renderRange = this.buildRenderRange(this.trimHiddenDays(currentInfo.range), currentInfo.unit, isRangeAllDay); + renderRange = this.trimHiddenDays(renderRange); + activeRange = renderRange; + if (!props.showNonCurrentDates) { + activeRange = intersectRanges(activeRange, currentInfo.range); + } + activeRange = this.adjustActiveRange(activeRange); + activeRange = intersectRanges(activeRange, validRange); // might return null + // it's invalid if the originally requested date is not contained, + // or if the range is completely outside of the valid range. + isValid = rangesIntersect(currentInfo.range, validRange); + return { + // constraint for where prev/next operations can go and where events can be dragged/resized to. + // an object with optional start and end properties. + validRange: validRange, + // range the view is formally responsible for. + // for example, a month view might have 1st-31st, excluding padded dates + currentRange: currentInfo.range, + // name of largest unit being displayed, like "month" or "week" + currentRangeUnit: currentInfo.unit, + isRangeAllDay: isRangeAllDay, + // dates that display events and accept drag-n-drop + // will be `null` if no dates accept events + activeRange: activeRange, + // date range with a rendered skeleton + // includes not-active days that need some sort of DOM + renderRange: renderRange, + // Duration object that denotes the first visible time of any given day + slotMinTime: props.slotMinTime, + // Duration object that denotes the exclusive visible end time of any given day + slotMaxTime: props.slotMaxTime, + isValid: isValid, + // how far the current date will move for a prev/next operation + dateIncrement: this.buildDateIncrement(currentInfo.duration), + // pass a fallback (might be null) ^ + }; + }; + // Builds an object with optional start/end properties. + // Indicates the minimum/maximum dates to display. + // not responsible for trimming hidden days. + DateProfileGenerator.prototype.buildValidRange = function () { + var input = this.props.validRangeInput; + var simpleInput = typeof input === 'function' + ? input.call(this.props.calendarApi, this.nowDate) + : input; + return this.refineRange(simpleInput) || + { start: null, end: null }; // completely open-ended + }; + // Builds a structure with info about the "current" range, the range that is + // highlighted as being the current month for example. + // See build() for a description of `direction`. + // Guaranteed to have `range` and `unit` properties. `duration` is optional. + DateProfileGenerator.prototype.buildCurrentRangeInfo = function (date, direction) { + var props = this.props; + var duration = null; + var unit = null; + var range = null; + var dayCount; + if (props.duration) { + duration = props.duration; + unit = props.durationUnit; + range = this.buildRangeFromDuration(date, direction, duration, unit); + } + else if ((dayCount = this.props.dayCount)) { + unit = 'day'; + range = this.buildRangeFromDayCount(date, direction, dayCount); + } + else if ((range = this.buildCustomVisibleRange(date))) { + unit = props.dateEnv.greatestWholeUnit(range.start, range.end).unit; + } + else { + duration = this.getFallbackDuration(); + unit = greatestDurationDenominator(duration).unit; + range = this.buildRangeFromDuration(date, direction, duration, unit); + } + return { duration: duration, unit: unit, range: range }; + }; + DateProfileGenerator.prototype.getFallbackDuration = function () { + return createDuration({ day: 1 }); + }; + // Returns a new activeRange to have time values (un-ambiguate) + // slotMinTime or slotMaxTime causes the range to expand. + DateProfileGenerator.prototype.adjustActiveRange = function (range) { + var _a = this.props, dateEnv = _a.dateEnv, usesMinMaxTime = _a.usesMinMaxTime, slotMinTime = _a.slotMinTime, slotMaxTime = _a.slotMaxTime; + var start = range.start, end = range.end; + if (usesMinMaxTime) { + // expand active range if slotMinTime is negative (why not when positive?) + if (asRoughDays(slotMinTime) < 0) { + start = startOfDay(start); // necessary? + start = dateEnv.add(start, slotMinTime); + } + // expand active range if slotMaxTime is beyond one day (why not when negative?) + if (asRoughDays(slotMaxTime) > 1) { + end = startOfDay(end); // necessary? + end = addDays(end, -1); + end = dateEnv.add(end, slotMaxTime); + } + } + return { start: start, end: end }; + }; + // Builds the "current" range when it is specified as an explicit duration. + // `unit` is the already-computed greatestDurationDenominator unit of duration. + DateProfileGenerator.prototype.buildRangeFromDuration = function (date, direction, duration, unit) { + var _a = this.props, dateEnv = _a.dateEnv, dateAlignment = _a.dateAlignment; + var start; + var end; + var res; + // compute what the alignment should be + if (!dateAlignment) { + var dateIncrement = this.props.dateIncrement; + if (dateIncrement) { + // use the smaller of the two units + if (asRoughMs(dateIncrement) < asRoughMs(duration)) { + dateAlignment = greatestDurationDenominator(dateIncrement).unit; + } + else { + dateAlignment = unit; + } + } + else { + dateAlignment = unit; + } + } + // if the view displays a single day or smaller + if (asRoughDays(duration) <= 1) { + if (this.isHiddenDay(start)) { + start = this.skipHiddenDays(start, direction); + start = startOfDay(start); + } + } + function computeRes() { + start = dateEnv.startOf(date, dateAlignment); + end = dateEnv.add(start, duration); + res = { start: start, end: end }; + } + computeRes(); + // if range is completely enveloped by hidden days, go past the hidden days + if (!this.trimHiddenDays(res)) { + date = this.skipHiddenDays(date, direction); + computeRes(); + } + return res; + }; + // Builds the "current" range when a dayCount is specified. + DateProfileGenerator.prototype.buildRangeFromDayCount = function (date, direction, dayCount) { + var _a = this.props, dateEnv = _a.dateEnv, dateAlignment = _a.dateAlignment; + var runningCount = 0; + var start = date; + var end; + if (dateAlignment) { + start = dateEnv.startOf(start, dateAlignment); + } + start = startOfDay(start); + start = this.skipHiddenDays(start, direction); + end = start; + do { + end = addDays(end, 1); + if (!this.isHiddenDay(end)) { + runningCount += 1; + } + } while (runningCount < dayCount); + return { start: start, end: end }; + }; + // Builds a normalized range object for the "visible" range, + // which is a way to define the currentRange and activeRange at the same time. + DateProfileGenerator.prototype.buildCustomVisibleRange = function (date) { + var props = this.props; + var input = props.visibleRangeInput; + var simpleInput = typeof input === 'function' + ? input.call(props.calendarApi, props.dateEnv.toDate(date)) + : input; + var range = this.refineRange(simpleInput); + if (range && (range.start == null || range.end == null)) { + return null; + } + return range; + }; + // Computes the range that will represent the element/cells for *rendering*, + // but which may have voided days/times. + // not responsible for trimming hidden days. + DateProfileGenerator.prototype.buildRenderRange = function (currentRange, currentRangeUnit, isRangeAllDay) { + return currentRange; + }; + // Compute the duration value that should be added/substracted to the current date + // when a prev/next operation happens. + DateProfileGenerator.prototype.buildDateIncrement = function (fallback) { + var dateIncrement = this.props.dateIncrement; + var customAlignment; + if (dateIncrement) { + return dateIncrement; + } + if ((customAlignment = this.props.dateAlignment)) { + return createDuration(1, customAlignment); + } + if (fallback) { + return fallback; + } + return createDuration({ days: 1 }); + }; + DateProfileGenerator.prototype.refineRange = function (rangeInput) { + if (rangeInput) { + var range = parseRange(rangeInput, this.props.dateEnv); + if (range) { + range = computeVisibleDayRange(range); + } + return range; + } + return null; + }; + /* Hidden Days + ------------------------------------------------------------------------------------------------------------------*/ + // Initializes internal variables related to calculating hidden days-of-week + DateProfileGenerator.prototype.initHiddenDays = function () { + var hiddenDays = this.props.hiddenDays || []; // array of day-of-week indices that are hidden + var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool) + var dayCnt = 0; + var i; + if (this.props.weekends === false) { + hiddenDays.push(0, 6); // 0=sunday, 6=saturday + } + for (i = 0; i < 7; i += 1) { + if (!(isHiddenDayHash[i] = hiddenDays.indexOf(i) !== -1)) { + dayCnt += 1; + } + } + if (!dayCnt) { + throw new Error('invalid hiddenDays'); // all days were hidden? bad. + } + this.isHiddenDayHash = isHiddenDayHash; + }; + // Remove days from the beginning and end of the range that are computed as hidden. + // If the whole range is trimmed off, returns null + DateProfileGenerator.prototype.trimHiddenDays = function (range) { + var start = range.start, end = range.end; + if (start) { + start = this.skipHiddenDays(start); + } + if (end) { + end = this.skipHiddenDays(end, -1, true); + } + if (start == null || end == null || start < end) { + return { start: start, end: end }; + } + return null; + }; + // Is the current day hidden? + // `day` is a day-of-week index (0-6), or a Date (used for UTC) + DateProfileGenerator.prototype.isHiddenDay = function (day) { + if (day instanceof Date) { + day = day.getUTCDay(); + } + return this.isHiddenDayHash[day]; + }; + // Incrementing the current day until it is no longer a hidden day, returning a copy. + // DOES NOT CONSIDER validRange! + // If the initial value of `date` is not a hidden day, don't do anything. + // Pass `isExclusive` as `true` if you are dealing with an end date. + // `inc` defaults to `1` (increment one day forward each time) + DateProfileGenerator.prototype.skipHiddenDays = function (date, inc, isExclusive) { + if (inc === void 0) { inc = 1; } + if (isExclusive === void 0) { isExclusive = false; } + while (this.isHiddenDayHash[(date.getUTCDay() + (isExclusive ? inc : 0) + 7) % 7]) { + date = addDays(date, inc); + } + return date; + }; + return DateProfileGenerator; + }()); + + function reduceViewType(viewType, action) { + switch (action.type) { + case 'CHANGE_VIEW_TYPE': + viewType = action.viewType; + } + return viewType; + } + + function reduceDynamicOptionOverrides(dynamicOptionOverrides, action) { + var _a; + switch (action.type) { + case 'SET_OPTION': + return __assign(__assign({}, dynamicOptionOverrides), (_a = {}, _a[action.optionName] = action.rawOptionValue, _a)); + default: + return dynamicOptionOverrides; + } + } + + function reduceDateProfile(currentDateProfile, action, currentDate, dateProfileGenerator) { + var dp; + switch (action.type) { + case 'CHANGE_VIEW_TYPE': + return dateProfileGenerator.build(action.dateMarker || currentDate); + case 'CHANGE_DATE': + return dateProfileGenerator.build(action.dateMarker); + case 'PREV': + dp = dateProfileGenerator.buildPrev(currentDateProfile, currentDate); + if (dp.isValid) { + return dp; + } + break; + case 'NEXT': + dp = dateProfileGenerator.buildNext(currentDateProfile, currentDate); + if (dp.isValid) { + return dp; + } + break; + } + return currentDateProfile; + } + + function initEventSources(calendarOptions, dateProfile, context) { + var activeRange = dateProfile ? dateProfile.activeRange : null; + return addSources({}, parseInitialSources(calendarOptions, context), activeRange, context); + } + function reduceEventSources(eventSources, action, dateProfile, context) { + var activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? + switch (action.type) { + case 'ADD_EVENT_SOURCES': // already parsed + return addSources(eventSources, action.sources, activeRange, context); + case 'REMOVE_EVENT_SOURCE': + return removeSource(eventSources, action.sourceId); + case 'PREV': // TODO: how do we track all actions that affect dateProfile :( + case 'NEXT': + case 'CHANGE_DATE': + case 'CHANGE_VIEW_TYPE': + if (dateProfile) { + return fetchDirtySources(eventSources, activeRange, context); + } + return eventSources; + case 'FETCH_EVENT_SOURCES': + return fetchSourcesByIds(eventSources, action.sourceIds ? // why no type? + arrayToHash(action.sourceIds) : + excludeStaticSources(eventSources, context), activeRange, action.isRefetch || false, context); + case 'RECEIVE_EVENTS': + case 'RECEIVE_EVENT_ERROR': + return receiveResponse(eventSources, action.sourceId, action.fetchId, action.fetchRange); + case 'REMOVE_ALL_EVENT_SOURCES': + return {}; + default: + return eventSources; + } + } + function reduceEventSourcesNewTimeZone(eventSources, dateProfile, context) { + var activeRange = dateProfile ? dateProfile.activeRange : null; // need this check? + return fetchSourcesByIds(eventSources, excludeStaticSources(eventSources, context), activeRange, true, context); + } + function computeEventSourcesLoading(eventSources) { + for (var sourceId in eventSources) { + if (eventSources[sourceId].isFetching) { + return true; + } + } + return false; + } + function addSources(eventSourceHash, sources, fetchRange, context) { + var hash = {}; + for (var _i = 0, sources_1 = sources; _i < sources_1.length; _i++) { + var source = sources_1[_i]; + hash[source.sourceId] = source; + } + if (fetchRange) { + hash = fetchDirtySources(hash, fetchRange, context); + } + return __assign(__assign({}, eventSourceHash), hash); + } + function removeSource(eventSourceHash, sourceId) { + return filterHash(eventSourceHash, function (eventSource) { return eventSource.sourceId !== sourceId; }); + } + function fetchDirtySources(sourceHash, fetchRange, context) { + return fetchSourcesByIds(sourceHash, filterHash(sourceHash, function (eventSource) { return isSourceDirty(eventSource, fetchRange, context); }), fetchRange, false, context); + } + function isSourceDirty(eventSource, fetchRange, context) { + if (!doesSourceNeedRange(eventSource, context)) { + return !eventSource.latestFetchId; + } + return !context.options.lazyFetching || + !eventSource.fetchRange || + eventSource.isFetching || // always cancel outdated in-progress fetches + fetchRange.start < eventSource.fetchRange.start || + fetchRange.end > eventSource.fetchRange.end; + } + function fetchSourcesByIds(prevSources, sourceIdHash, fetchRange, isRefetch, context) { + var nextSources = {}; + for (var sourceId in prevSources) { + var source = prevSources[sourceId]; + if (sourceIdHash[sourceId]) { + nextSources[sourceId] = fetchSource(source, fetchRange, isRefetch, context); + } + else { + nextSources[sourceId] = source; + } + } + return nextSources; + } + function fetchSource(eventSource, fetchRange, isRefetch, context) { + var options = context.options, calendarApi = context.calendarApi; + var sourceDef = context.pluginHooks.eventSourceDefs[eventSource.sourceDefId]; + var fetchId = guid(); + sourceDef.fetch({ + eventSource: eventSource, + range: fetchRange, + isRefetch: isRefetch, + context: context, + }, function (res) { + var rawEvents = res.rawEvents; + if (options.eventSourceSuccess) { + rawEvents = options.eventSourceSuccess.call(calendarApi, rawEvents, res.xhr) || rawEvents; + } + if (eventSource.success) { + rawEvents = eventSource.success.call(calendarApi, rawEvents, res.xhr) || rawEvents; + } + context.dispatch({ + type: 'RECEIVE_EVENTS', + sourceId: eventSource.sourceId, + fetchId: fetchId, + fetchRange: fetchRange, + rawEvents: rawEvents, + }); + }, function (error) { + console.warn(error.message, error); + if (options.eventSourceFailure) { + options.eventSourceFailure.call(calendarApi, error); + } + if (eventSource.failure) { + eventSource.failure(error); + } + context.dispatch({ + type: 'RECEIVE_EVENT_ERROR', + sourceId: eventSource.sourceId, + fetchId: fetchId, + fetchRange: fetchRange, + error: error, + }); + }); + return __assign(__assign({}, eventSource), { isFetching: true, latestFetchId: fetchId }); + } + function receiveResponse(sourceHash, sourceId, fetchId, fetchRange) { + var _a; + var eventSource = sourceHash[sourceId]; + if (eventSource && // not already removed + fetchId === eventSource.latestFetchId) { + return __assign(__assign({}, sourceHash), (_a = {}, _a[sourceId] = __assign(__assign({}, eventSource), { isFetching: false, fetchRange: fetchRange }), _a)); + } + return sourceHash; + } + function excludeStaticSources(eventSources, context) { + return filterHash(eventSources, function (eventSource) { return doesSourceNeedRange(eventSource, context); }); + } + function parseInitialSources(rawOptions, context) { + var refiners = buildEventSourceRefiners(context); + var rawSources = [].concat(rawOptions.eventSources || []); + var sources = []; // parsed + if (rawOptions.initialEvents) { + rawSources.unshift(rawOptions.initialEvents); + } + if (rawOptions.events) { + rawSources.unshift(rawOptions.events); + } + for (var _i = 0, rawSources_1 = rawSources; _i < rawSources_1.length; _i++) { + var rawSource = rawSources_1[_i]; + var source = parseEventSource(rawSource, context, refiners); + if (source) { + sources.push(source); + } + } + return sources; + } + function doesSourceNeedRange(eventSource, context) { + var defs = context.pluginHooks.eventSourceDefs; + return !defs[eventSource.sourceDefId].ignoreRange; + } + + function reduceEventStore(eventStore, action, eventSources, dateProfile, context) { + switch (action.type) { + case 'RECEIVE_EVENTS': // raw + return receiveRawEvents(eventStore, eventSources[action.sourceId], action.fetchId, action.fetchRange, action.rawEvents, context); + case 'ADD_EVENTS': // already parsed, but not expanded + return addEvent(eventStore, action.eventStore, // new ones + dateProfile ? dateProfile.activeRange : null, context); + case 'RESET_EVENTS': + return action.eventStore; + case 'MERGE_EVENTS': // already parsed and expanded + return mergeEventStores(eventStore, action.eventStore); + case 'PREV': // TODO: how do we track all actions that affect dateProfile :( + case 'NEXT': + case 'CHANGE_DATE': + case 'CHANGE_VIEW_TYPE': + if (dateProfile) { + return expandRecurring(eventStore, dateProfile.activeRange, context); + } + return eventStore; + case 'REMOVE_EVENTS': + return excludeSubEventStore(eventStore, action.eventStore); + case 'REMOVE_EVENT_SOURCE': + return excludeEventsBySourceId(eventStore, action.sourceId); + case 'REMOVE_ALL_EVENT_SOURCES': + return filterEventStoreDefs(eventStore, function (eventDef) { return (!eventDef.sourceId // only keep events with no source id + ); }); + case 'REMOVE_ALL_EVENTS': + return createEmptyEventStore(); + default: + return eventStore; + } + } + function receiveRawEvents(eventStore, eventSource, fetchId, fetchRange, rawEvents, context) { + if (eventSource && // not already removed + fetchId === eventSource.latestFetchId // TODO: wish this logic was always in event-sources + ) { + var subset = parseEvents(transformRawEvents(rawEvents, eventSource, context), eventSource, context); + if (fetchRange) { + subset = expandRecurring(subset, fetchRange, context); + } + return mergeEventStores(excludeEventsBySourceId(eventStore, eventSource.sourceId), subset); + } + return eventStore; + } + function transformRawEvents(rawEvents, eventSource, context) { + var calEachTransform = context.options.eventDataTransform; + var sourceEachTransform = eventSource ? eventSource.eventDataTransform : null; + if (sourceEachTransform) { + rawEvents = transformEachRawEvent(rawEvents, sourceEachTransform); + } + if (calEachTransform) { + rawEvents = transformEachRawEvent(rawEvents, calEachTransform); + } + return rawEvents; + } + function transformEachRawEvent(rawEvents, func) { + var refinedEvents; + if (!func) { + refinedEvents = rawEvents; + } + else { + refinedEvents = []; + for (var _i = 0, rawEvents_1 = rawEvents; _i < rawEvents_1.length; _i++) { + var rawEvent = rawEvents_1[_i]; + var refinedEvent = func(rawEvent); + if (refinedEvent) { + refinedEvents.push(refinedEvent); + } + else if (refinedEvent == null) { + refinedEvents.push(rawEvent); + } // if a different falsy value, do nothing + } + } + return refinedEvents; + } + function addEvent(eventStore, subset, expandRange, context) { + if (expandRange) { + subset = expandRecurring(subset, expandRange, context); + } + return mergeEventStores(eventStore, subset); + } + function rezoneEventStoreDates(eventStore, oldDateEnv, newDateEnv) { + var defs = eventStore.defs; + var instances = mapHash(eventStore.instances, function (instance) { + var def = defs[instance.defId]; + if (def.allDay || def.recurringDef) { + return instance; // isn't dependent on timezone + } + return __assign(__assign({}, instance), { range: { + start: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.start, instance.forcedStartTzo)), + end: newDateEnv.createMarker(oldDateEnv.toDate(instance.range.end, instance.forcedEndTzo)), + }, forcedStartTzo: newDateEnv.canComputeOffset ? null : instance.forcedStartTzo, forcedEndTzo: newDateEnv.canComputeOffset ? null : instance.forcedEndTzo }); + }); + return { defs: defs, instances: instances }; + } + function excludeEventsBySourceId(eventStore, sourceId) { + return filterEventStoreDefs(eventStore, function (eventDef) { return eventDef.sourceId !== sourceId; }); + } + // QUESTION: why not just return instances? do a general object-property-exclusion util + function excludeInstances(eventStore, removals) { + return { + defs: eventStore.defs, + instances: filterHash(eventStore.instances, function (instance) { return !removals[instance.instanceId]; }), + }; + } + + function reduceDateSelection(currentSelection, action) { + switch (action.type) { + case 'UNSELECT_DATES': + return null; + case 'SELECT_DATES': + return action.selection; + default: + return currentSelection; + } + } + + function reduceSelectedEvent(currentInstanceId, action) { + switch (action.type) { + case 'UNSELECT_EVENT': + return ''; + case 'SELECT_EVENT': + return action.eventInstanceId; + default: + return currentInstanceId; + } + } + + function reduceEventDrag(currentDrag, action) { + var newDrag; + switch (action.type) { + case 'UNSET_EVENT_DRAG': + return null; + case 'SET_EVENT_DRAG': + newDrag = action.state; + return { + affectedEvents: newDrag.affectedEvents, + mutatedEvents: newDrag.mutatedEvents, + isEvent: newDrag.isEvent, + }; + default: + return currentDrag; + } + } + + function reduceEventResize(currentResize, action) { + var newResize; + switch (action.type) { + case 'UNSET_EVENT_RESIZE': + return null; + case 'SET_EVENT_RESIZE': + newResize = action.state; + return { + affectedEvents: newResize.affectedEvents, + mutatedEvents: newResize.mutatedEvents, + isEvent: newResize.isEvent, + }; + default: + return currentResize; + } + } + + function parseToolbars(calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi) { + var viewsWithButtons = []; + var headerToolbar = calendarOptions.headerToolbar ? parseToolbar(calendarOptions.headerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) : null; + var footerToolbar = calendarOptions.footerToolbar ? parseToolbar(calendarOptions.footerToolbar, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) : null; + return { headerToolbar: headerToolbar, footerToolbar: footerToolbar, viewsWithButtons: viewsWithButtons }; + } + function parseToolbar(sectionStrHash, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) { + return mapHash(sectionStrHash, function (sectionStr) { return parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons); }); + } + /* + BAD: querying icons and text here. should be done at render time + */ + function parseSection(sectionStr, calendarOptions, calendarOptionOverrides, theme, viewSpecs, calendarApi, viewsWithButtons) { + var isRtl = calendarOptions.direction === 'rtl'; + var calendarCustomButtons = calendarOptions.customButtons || {}; + var calendarButtonTextOverrides = calendarOptionOverrides.buttonText || {}; + var calendarButtonText = calendarOptions.buttonText || {}; + var sectionSubstrs = sectionStr ? sectionStr.split(' ') : []; + return sectionSubstrs.map(function (buttonGroupStr) { return (buttonGroupStr.split(',').map(function (buttonName) { + if (buttonName === 'title') { + return { buttonName: buttonName }; + } + var customButtonProps; + var viewSpec; + var buttonClick; + var buttonIcon; // only one of these will be set + var buttonText; // " + if ((customButtonProps = calendarCustomButtons[buttonName])) { + buttonClick = function (ev) { + if (customButtonProps.click) { + customButtonProps.click.call(ev.target, ev, ev.target); // TODO: use Calendar this context? + } + }; + (buttonIcon = theme.getCustomButtonIconClass(customButtonProps)) || + (buttonIcon = theme.getIconClass(buttonName, isRtl)) || + (buttonText = customButtonProps.text); + } + else if ((viewSpec = viewSpecs[buttonName])) { + viewsWithButtons.push(buttonName); + buttonClick = function () { + calendarApi.changeView(buttonName); + }; + (buttonText = viewSpec.buttonTextOverride) || + (buttonIcon = theme.getIconClass(buttonName, isRtl)) || + (buttonText = viewSpec.buttonTextDefault); + } + else if (calendarApi[buttonName]) { // a calendarApi method + buttonClick = function () { + calendarApi[buttonName](); + }; + (buttonText = calendarButtonTextOverrides[buttonName]) || + (buttonIcon = theme.getIconClass(buttonName, isRtl)) || + (buttonText = calendarButtonText[buttonName]); + // ^ everything else is considered default + } + return { buttonName: buttonName, buttonClick: buttonClick, buttonIcon: buttonIcon, buttonText: buttonText }; + })); }); + } + + var eventSourceDef$3 = { + ignoreRange: true, + parseMeta: function (refined) { + if (Array.isArray(refined.events)) { + return refined.events; + } + return null; + }, + fetch: function (arg, success) { + success({ + rawEvents: arg.eventSource.meta, + }); + }, + }; + var arrayEventSourcePlugin = createPlugin({ + eventSourceDefs: [eventSourceDef$3], + }); + + var eventSourceDef$2 = { + parseMeta: function (refined) { + if (typeof refined.events === 'function') { + return refined.events; + } + return null; + }, + fetch: function (arg, success, failure) { + var dateEnv = arg.context.dateEnv; + var func = arg.eventSource.meta; + unpromisify(func.bind(null, buildRangeApiWithTimeZone(arg.range, dateEnv)), function (rawEvents) { + success({ rawEvents: rawEvents }); // needs an object response + }, failure); + }, + }; + var funcEventSourcePlugin = createPlugin({ + eventSourceDefs: [eventSourceDef$2], + }); + + function requestJson(method, url, params, successCallback, failureCallback) { + method = method.toUpperCase(); + var body = null; + if (method === 'GET') { + url = injectQueryStringParams(url, params); + } + else { + body = encodeParams(params); + } + var xhr = new XMLHttpRequest(); + xhr.open(method, url, true); + if (method !== 'GET') { + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + } + xhr.onload = function () { + if (xhr.status >= 200 && xhr.status < 400) { + var parsed = false; + var res = void 0; + try { + res = JSON.parse(xhr.responseText); + parsed = true; + } + catch (err) { + // will handle parsed=false + } + if (parsed) { + successCallback(res, xhr); + } + else { + failureCallback('Failure parsing JSON', xhr); + } + } + else { + failureCallback('Request failed', xhr); + } + }; + xhr.onerror = function () { + failureCallback('Request failed', xhr); + }; + xhr.send(body); + } + function injectQueryStringParams(url, params) { + return url + + (url.indexOf('?') === -1 ? '?' : '&') + + encodeParams(params); + } + function encodeParams(params) { + var parts = []; + for (var key in params) { + parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(params[key])); + } + return parts.join('&'); + } + + var JSON_FEED_EVENT_SOURCE_REFINERS = { + method: String, + extraParams: identity, + startParam: String, + endParam: String, + timeZoneParam: String, + }; + + var eventSourceDef$1 = { + parseMeta: function (refined) { + if (refined.url && (refined.format === 'json' || !refined.format)) { + return { + url: refined.url, + format: 'json', + method: (refined.method || 'GET').toUpperCase(), + extraParams: refined.extraParams, + startParam: refined.startParam, + endParam: refined.endParam, + timeZoneParam: refined.timeZoneParam, + }; + } + return null; + }, + fetch: function (arg, success, failure) { + var meta = arg.eventSource.meta; + var requestParams = buildRequestParams$1(meta, arg.range, arg.context); + requestJson(meta.method, meta.url, requestParams, function (rawEvents, xhr) { + success({ rawEvents: rawEvents, xhr: xhr }); + }, function (errorMessage, xhr) { + failure({ message: errorMessage, xhr: xhr }); + }); + }, + }; + var jsonFeedEventSourcePlugin = createPlugin({ + eventSourceRefiners: JSON_FEED_EVENT_SOURCE_REFINERS, + eventSourceDefs: [eventSourceDef$1], + }); + function buildRequestParams$1(meta, range, context) { + var dateEnv = context.dateEnv, options = context.options; + var startParam; + var endParam; + var timeZoneParam; + var customRequestParams; + var params = {}; + startParam = meta.startParam; + if (startParam == null) { + startParam = options.startParam; + } + endParam = meta.endParam; + if (endParam == null) { + endParam = options.endParam; + } + timeZoneParam = meta.timeZoneParam; + if (timeZoneParam == null) { + timeZoneParam = options.timeZoneParam; + } + // retrieve any outbound GET/POST data from the options + if (typeof meta.extraParams === 'function') { + // supplied as a function that returns a key/value object + customRequestParams = meta.extraParams(); + } + else { + // probably supplied as a straight key/value object + customRequestParams = meta.extraParams || {}; + } + __assign(params, customRequestParams); + params[startParam] = dateEnv.formatIso(range.start); + params[endParam] = dateEnv.formatIso(range.end); + if (dateEnv.timeZone !== 'local') { + params[timeZoneParam] = dateEnv.timeZone; + } + return params; + } + + var SIMPLE_RECURRING_REFINERS = { + daysOfWeek: identity, + startTime: createDuration, + endTime: createDuration, + duration: createDuration, + startRecur: identity, + endRecur: identity, + }; + + var recurring = { + parse: function (refined, dateEnv) { + if (refined.daysOfWeek || refined.startTime || refined.endTime || refined.startRecur || refined.endRecur) { + var recurringData = { + daysOfWeek: refined.daysOfWeek || null, + startTime: refined.startTime || null, + endTime: refined.endTime || null, + startRecur: refined.startRecur ? dateEnv.createMarker(refined.startRecur) : null, + endRecur: refined.endRecur ? dateEnv.createMarker(refined.endRecur) : null, + }; + var duration = void 0; + if (refined.duration) { + duration = refined.duration; + } + if (!duration && refined.startTime && refined.endTime) { + duration = subtractDurations(refined.endTime, refined.startTime); + } + return { + allDayGuess: Boolean(!refined.startTime && !refined.endTime), + duration: duration, + typeData: recurringData, // doesn't need endTime anymore but oh well + }; + } + return null; + }, + expand: function (typeData, framingRange, dateEnv) { + var clippedFramingRange = intersectRanges(framingRange, { start: typeData.startRecur, end: typeData.endRecur }); + if (clippedFramingRange) { + return expandRanges(typeData.daysOfWeek, typeData.startTime, clippedFramingRange, dateEnv); + } + return []; + }, + }; + var simpleRecurringEventsPlugin = createPlugin({ + recurringTypes: [recurring], + eventRefiners: SIMPLE_RECURRING_REFINERS, + }); + function expandRanges(daysOfWeek, startTime, framingRange, dateEnv) { + var dowHash = daysOfWeek ? arrayToHash(daysOfWeek) : null; + var dayMarker = startOfDay(framingRange.start); + var endMarker = framingRange.end; + var instanceStarts = []; + while (dayMarker < endMarker) { + var instanceStart + // if everyday, or this particular day-of-week + = void 0; + // if everyday, or this particular day-of-week + if (!dowHash || dowHash[dayMarker.getUTCDay()]) { + if (startTime) { + instanceStart = dateEnv.add(dayMarker, startTime); + } + else { + instanceStart = dayMarker; + } + instanceStarts.push(instanceStart); + } + dayMarker = addDays(dayMarker, 1); + } + return instanceStarts; + } + + var changeHandlerPlugin = createPlugin({ + optionChangeHandlers: { + events: function (events, context) { + handleEventSources([events], context); + }, + eventSources: handleEventSources, + }, + }); + /* + BUG: if `event` was supplied, all previously-given `eventSources` will be wiped out + */ + function handleEventSources(inputs, context) { + var unfoundSources = hashValuesToArray(context.getCurrentData().eventSources); + var newInputs = []; + for (var _i = 0, inputs_1 = inputs; _i < inputs_1.length; _i++) { + var input = inputs_1[_i]; + var inputFound = false; + for (var i = 0; i < unfoundSources.length; i += 1) { + if (unfoundSources[i]._raw === input) { + unfoundSources.splice(i, 1); // delete + inputFound = true; + break; + } + } + if (!inputFound) { + newInputs.push(input); + } + } + for (var _a = 0, unfoundSources_1 = unfoundSources; _a < unfoundSources_1.length; _a++) { + var unfoundSource = unfoundSources_1[_a]; + context.dispatch({ + type: 'REMOVE_EVENT_SOURCE', + sourceId: unfoundSource.sourceId, + }); + } + for (var _b = 0, newInputs_1 = newInputs; _b < newInputs_1.length; _b++) { + var newInput = newInputs_1[_b]; + context.calendarApi.addEventSource(newInput); + } + } + + function handleDateProfile(dateProfile, context) { + context.emitter.trigger('datesSet', __assign(__assign({}, buildRangeApiWithTimeZone(dateProfile.activeRange, context.dateEnv)), { view: context.viewApi })); + } + + function handleEventStore(eventStore, context) { + var emitter = context.emitter; + if (emitter.hasHandlers('eventsSet')) { + emitter.trigger('eventsSet', buildEventApis(eventStore, context)); + } + } + + /* + this array is exposed on the root namespace so that UMD plugins can add to it. + see the rollup-bundles script. + */ + var globalPlugins = [ + arrayEventSourcePlugin, + funcEventSourcePlugin, + jsonFeedEventSourcePlugin, + simpleRecurringEventsPlugin, + changeHandlerPlugin, + createPlugin({ + isLoadingFuncs: [ + function (state) { return computeEventSourcesLoading(state.eventSources); }, + ], + contentTypeHandlers: { + html: function () { return ({ render: injectHtml }); }, + domNodes: function () { return ({ render: injectDomNodes }); }, + }, + propSetHandlers: { + dateProfile: handleDateProfile, + eventStore: handleEventStore, + }, + }), + ]; + function injectHtml(el, html) { + el.innerHTML = html; + } + function injectDomNodes(el, domNodes) { + var oldNodes = Array.prototype.slice.call(el.childNodes); // TODO: use array util + var newNodes = Array.prototype.slice.call(domNodes); // TODO: use array util + if (!isArraysEqual(oldNodes, newNodes)) { + for (var _i = 0, newNodes_1 = newNodes; _i < newNodes_1.length; _i++) { + var newNode = newNodes_1[_i]; + el.appendChild(newNode); + } + oldNodes.forEach(removeElement); + } + } + + var DelayedRunner = /** @class */ (function () { + function DelayedRunner(drainedOption) { + this.drainedOption = drainedOption; + this.isRunning = false; + this.isDirty = false; + this.pauseDepths = {}; + this.timeoutId = 0; + } + DelayedRunner.prototype.request = function (delay) { + this.isDirty = true; + if (!this.isPaused()) { + this.clearTimeout(); + if (delay == null) { + this.tryDrain(); + } + else { + this.timeoutId = setTimeout(// NOT OPTIMAL! TODO: look at debounce + this.tryDrain.bind(this), delay); + } + } + }; + DelayedRunner.prototype.pause = function (scope) { + if (scope === void 0) { scope = ''; } + var pauseDepths = this.pauseDepths; + pauseDepths[scope] = (pauseDepths[scope] || 0) + 1; + this.clearTimeout(); + }; + DelayedRunner.prototype.resume = function (scope, force) { + if (scope === void 0) { scope = ''; } + var pauseDepths = this.pauseDepths; + if (scope in pauseDepths) { + if (force) { + delete pauseDepths[scope]; + } + else { + pauseDepths[scope] -= 1; + var depth = pauseDepths[scope]; + if (depth <= 0) { + delete pauseDepths[scope]; + } + } + this.tryDrain(); + } + }; + DelayedRunner.prototype.isPaused = function () { + return Object.keys(this.pauseDepths).length; + }; + DelayedRunner.prototype.tryDrain = function () { + if (!this.isRunning && !this.isPaused()) { + this.isRunning = true; + while (this.isDirty) { + this.isDirty = false; + this.drained(); // might set isDirty to true again + } + this.isRunning = false; + } + }; + DelayedRunner.prototype.clear = function () { + this.clearTimeout(); + this.isDirty = false; + this.pauseDepths = {}; + }; + DelayedRunner.prototype.clearTimeout = function () { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + this.timeoutId = 0; + } + }; + DelayedRunner.prototype.drained = function () { + if (this.drainedOption) { + this.drainedOption(); + } + }; + return DelayedRunner; + }()); + + var TaskRunner = /** @class */ (function () { + function TaskRunner(runTaskOption, drainedOption) { + this.runTaskOption = runTaskOption; + this.drainedOption = drainedOption; + this.queue = []; + this.delayedRunner = new DelayedRunner(this.drain.bind(this)); + } + TaskRunner.prototype.request = function (task, delay) { + this.queue.push(task); + this.delayedRunner.request(delay); + }; + TaskRunner.prototype.pause = function (scope) { + this.delayedRunner.pause(scope); + }; + TaskRunner.prototype.resume = function (scope, force) { + this.delayedRunner.resume(scope, force); + }; + TaskRunner.prototype.drain = function () { + var queue = this.queue; + while (queue.length) { + var completedTasks = []; + var task = void 0; + while ((task = queue.shift())) { + this.runTask(task); + completedTasks.push(task); + } + this.drained(completedTasks); + } // keep going, in case new tasks were added in the drained handler + }; + TaskRunner.prototype.runTask = function (task) { + if (this.runTaskOption) { + this.runTaskOption(task); + } + }; + TaskRunner.prototype.drained = function (completedTasks) { + if (this.drainedOption) { + this.drainedOption(completedTasks); + } + }; + return TaskRunner; + }()); + + // Computes what the title at the top of the calendarApi should be for this view + function buildTitle(dateProfile, viewOptions, dateEnv) { + var range; + // for views that span a large unit of time, show the proper interval, ignoring stray days before and after + if (/^(year|month)$/.test(dateProfile.currentRangeUnit)) { + range = dateProfile.currentRange; + } + else { // for day units or smaller, use the actual day range + range = dateProfile.activeRange; + } + return dateEnv.formatRange(range.start, range.end, createFormatter(viewOptions.titleFormat || buildTitleFormat(dateProfile)), { + isEndExclusive: dateProfile.isRangeAllDay, + defaultSeparator: viewOptions.titleRangeSeparator, + }); + } + // Generates the format string that should be used to generate the title for the current date range. + // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`. + function buildTitleFormat(dateProfile) { + var currentRangeUnit = dateProfile.currentRangeUnit; + if (currentRangeUnit === 'year') { + return { year: 'numeric' }; + } + if (currentRangeUnit === 'month') { + return { year: 'numeric', month: 'long' }; // like "September 2014" + } + var days = diffWholeDays(dateProfile.currentRange.start, dateProfile.currentRange.end); + if (days !== null && days > 1) { + // multi-day range. shorter, like "Sep 9 - 10 2014" + return { year: 'numeric', month: 'short', day: 'numeric' }; + } + // one day. longer, like "September 9 2014" + return { year: 'numeric', month: 'long', day: 'numeric' }; + } + + // in future refactor, do the redux-style function(state=initial) for initial-state + // also, whatever is happening in constructor, have it happen in action queue too + var CalendarDataManager = /** @class */ (function () { + function CalendarDataManager(props) { + var _this = this; + this.computeOptionsData = memoize(this._computeOptionsData); + this.computeCurrentViewData = memoize(this._computeCurrentViewData); + this.organizeRawLocales = memoize(organizeRawLocales); + this.buildLocale = memoize(buildLocale); + this.buildPluginHooks = buildBuildPluginHooks(); + this.buildDateEnv = memoize(buildDateEnv); + this.buildTheme = memoize(buildTheme); + this.parseToolbars = memoize(parseToolbars); + this.buildViewSpecs = memoize(buildViewSpecs); + this.buildDateProfileGenerator = memoizeObjArg(buildDateProfileGenerator); + this.buildViewApi = memoize(buildViewApi); + this.buildViewUiProps = memoizeObjArg(buildViewUiProps); + this.buildEventUiBySource = memoize(buildEventUiBySource, isPropsEqual); + this.buildEventUiBases = memoize(buildEventUiBases); + this.parseContextBusinessHours = memoizeObjArg(parseContextBusinessHours); + this.buildTitle = memoize(buildTitle); + this.emitter = new Emitter(); + this.actionRunner = new TaskRunner(this._handleAction.bind(this), this.updateData.bind(this)); + this.currentCalendarOptionsInput = {}; + this.currentCalendarOptionsRefined = {}; + this.currentViewOptionsInput = {}; + this.currentViewOptionsRefined = {}; + this.currentCalendarOptionsRefiners = {}; + this.getCurrentData = function () { return _this.data; }; + this.dispatch = function (action) { + _this.actionRunner.request(action); // protects against recursive calls to _handleAction + }; + this.props = props; + this.actionRunner.pause(); + var dynamicOptionOverrides = {}; + var optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); + var currentViewType = optionsData.calendarOptions.initialView || optionsData.pluginHooks.initialView; + var currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides); + // wire things up + // TODO: not DRY + props.calendarApi.currentDataManager = this; + this.emitter.setThisContext(props.calendarApi); + this.emitter.setOptions(currentViewData.options); + var currentDate = getInitialDate(optionsData.calendarOptions, optionsData.dateEnv); + var dateProfile = currentViewData.dateProfileGenerator.build(currentDate); + if (!rangeContainsMarker(dateProfile.activeRange, currentDate)) { + currentDate = dateProfile.currentRange.start; + } + var calendarContext = { + dateEnv: optionsData.dateEnv, + options: optionsData.calendarOptions, + pluginHooks: optionsData.pluginHooks, + calendarApi: props.calendarApi, + dispatch: this.dispatch, + emitter: this.emitter, + getCurrentData: this.getCurrentData, + }; + // needs to be after setThisContext + for (var _i = 0, _a = optionsData.pluginHooks.contextInit; _i < _a.length; _i++) { + var callback = _a[_i]; + callback(calendarContext); + } + // NOT DRY + var eventSources = initEventSources(optionsData.calendarOptions, dateProfile, calendarContext); + var initialState = { + dynamicOptionOverrides: dynamicOptionOverrides, + currentViewType: currentViewType, + currentDate: currentDate, + dateProfile: dateProfile, + businessHours: this.parseContextBusinessHours(calendarContext), + eventSources: eventSources, + eventUiBases: {}, + eventStore: createEmptyEventStore(), + renderableEventStore: createEmptyEventStore(), + dateSelection: null, + eventSelection: '', + eventDrag: null, + eventResize: null, + selectionConfig: this.buildViewUiProps(calendarContext).selectionConfig, + }; + var contextAndState = __assign(__assign({}, calendarContext), initialState); + for (var _b = 0, _c = optionsData.pluginHooks.reducers; _b < _c.length; _b++) { + var reducer = _c[_b]; + __assign(initialState, reducer(null, null, contextAndState)); + } + if (computeIsLoading(initialState, calendarContext)) { + this.emitter.trigger('loading', true); // NOT DRY + } + this.state = initialState; + this.updateData(); + this.actionRunner.resume(); + } + CalendarDataManager.prototype.resetOptions = function (optionOverrides, append) { + var props = this.props; + props.optionOverrides = append + ? __assign(__assign({}, props.optionOverrides), optionOverrides) : optionOverrides; + this.actionRunner.request({ + type: 'NOTHING', + }); + }; + CalendarDataManager.prototype._handleAction = function (action) { + var _a = this, props = _a.props, state = _a.state, emitter = _a.emitter; + var dynamicOptionOverrides = reduceDynamicOptionOverrides(state.dynamicOptionOverrides, action); + var optionsData = this.computeOptionsData(props.optionOverrides, dynamicOptionOverrides, props.calendarApi); + var currentViewType = reduceViewType(state.currentViewType, action); + var currentViewData = this.computeCurrentViewData(currentViewType, optionsData, props.optionOverrides, dynamicOptionOverrides); + // wire things up + // TODO: not DRY + props.calendarApi.currentDataManager = this; + emitter.setThisContext(props.calendarApi); + emitter.setOptions(currentViewData.options); + var calendarContext = { + dateEnv: optionsData.dateEnv, + options: optionsData.calendarOptions, + pluginHooks: optionsData.pluginHooks, + calendarApi: props.calendarApi, + dispatch: this.dispatch, + emitter: emitter, + getCurrentData: this.getCurrentData, + }; + var currentDate = state.currentDate, dateProfile = state.dateProfile; + if (this.data && this.data.dateProfileGenerator !== currentViewData.dateProfileGenerator) { // hack + dateProfile = currentViewData.dateProfileGenerator.build(currentDate); + } + currentDate = reduceCurrentDate(currentDate, action); + dateProfile = reduceDateProfile(dateProfile, action, currentDate, currentViewData.dateProfileGenerator); + if (action.type === 'PREV' || // TODO: move this logic into DateProfileGenerator + action.type === 'NEXT' || // " + !rangeContainsMarker(dateProfile.currentRange, currentDate)) { + currentDate = dateProfile.currentRange.start; + } + var eventSources = reduceEventSources(state.eventSources, action, dateProfile, calendarContext); + var eventStore = reduceEventStore(state.eventStore, action, eventSources, dateProfile, calendarContext); + var isEventsLoading = computeEventSourcesLoading(eventSources); // BAD. also called in this func in computeIsLoading + var renderableEventStore = (isEventsLoading && !currentViewData.options.progressiveEventRendering) ? + (state.renderableEventStore || eventStore) : // try from previous state + eventStore; + var _b = this.buildViewUiProps(calendarContext), eventUiSingleBase = _b.eventUiSingleBase, selectionConfig = _b.selectionConfig; // will memoize obj + var eventUiBySource = this.buildEventUiBySource(eventSources); + var eventUiBases = this.buildEventUiBases(renderableEventStore.defs, eventUiSingleBase, eventUiBySource); + var newState = { + dynamicOptionOverrides: dynamicOptionOverrides, + currentViewType: currentViewType, + currentDate: currentDate, + dateProfile: dateProfile, + eventSources: eventSources, + eventStore: eventStore, + renderableEventStore: renderableEventStore, + selectionConfig: selectionConfig, + eventUiBases: eventUiBases, + businessHours: this.parseContextBusinessHours(calendarContext), + dateSelection: reduceDateSelection(state.dateSelection, action), + eventSelection: reduceSelectedEvent(state.eventSelection, action), + eventDrag: reduceEventDrag(state.eventDrag, action), + eventResize: reduceEventResize(state.eventResize, action), + }; + var contextAndState = __assign(__assign({}, calendarContext), newState); + for (var _i = 0, _c = optionsData.pluginHooks.reducers; _i < _c.length; _i++) { + var reducer = _c[_i]; + __assign(newState, reducer(state, action, contextAndState)); // give the OLD state, for old value + } + var wasLoading = computeIsLoading(state, calendarContext); + var isLoading = computeIsLoading(newState, calendarContext); + // TODO: use propSetHandlers in plugin system + if (!wasLoading && isLoading) { + emitter.trigger('loading', true); + } + else if (wasLoading && !isLoading) { + emitter.trigger('loading', false); + } + this.state = newState; + if (props.onAction) { + props.onAction(action); + } + }; + CalendarDataManager.prototype.updateData = function () { + var _a = this, props = _a.props, state = _a.state; + var oldData = this.data; + var optionsData = this.computeOptionsData(props.optionOverrides, state.dynamicOptionOverrides, props.calendarApi); + var currentViewData = this.computeCurrentViewData(state.currentViewType, optionsData, props.optionOverrides, state.dynamicOptionOverrides); + var data = this.data = __assign(__assign(__assign({ viewTitle: this.buildTitle(state.dateProfile, currentViewData.options, optionsData.dateEnv), calendarApi: props.calendarApi, dispatch: this.dispatch, emitter: this.emitter, getCurrentData: this.getCurrentData }, optionsData), currentViewData), state); + var changeHandlers = optionsData.pluginHooks.optionChangeHandlers; + var oldCalendarOptions = oldData && oldData.calendarOptions; + var newCalendarOptions = optionsData.calendarOptions; + if (oldCalendarOptions && oldCalendarOptions !== newCalendarOptions) { + if (oldCalendarOptions.timeZone !== newCalendarOptions.timeZone) { + // hack + state.eventSources = data.eventSources = reduceEventSourcesNewTimeZone(data.eventSources, state.dateProfile, data); + state.eventStore = data.eventStore = rezoneEventStoreDates(data.eventStore, oldData.dateEnv, data.dateEnv); + } + for (var optionName in changeHandlers) { + if (oldCalendarOptions[optionName] !== newCalendarOptions[optionName]) { + changeHandlers[optionName](newCalendarOptions[optionName], data); + } + } + } + if (props.onData) { + props.onData(data); + } + }; + CalendarDataManager.prototype._computeOptionsData = function (optionOverrides, dynamicOptionOverrides, calendarApi) { + // TODO: blacklist options that are handled by optionChangeHandlers + var _a = this.processRawCalendarOptions(optionOverrides, dynamicOptionOverrides), refinedOptions = _a.refinedOptions, pluginHooks = _a.pluginHooks, localeDefaults = _a.localeDefaults, availableLocaleData = _a.availableLocaleData, extra = _a.extra; + warnUnknownOptions(extra); + var dateEnv = this.buildDateEnv(refinedOptions.timeZone, refinedOptions.locale, refinedOptions.weekNumberCalculation, refinedOptions.firstDay, refinedOptions.weekText, pluginHooks, availableLocaleData, refinedOptions.defaultRangeSeparator); + var viewSpecs = this.buildViewSpecs(pluginHooks.views, optionOverrides, dynamicOptionOverrides, localeDefaults); + var theme = this.buildTheme(refinedOptions, pluginHooks); + var toolbarConfig = this.parseToolbars(refinedOptions, optionOverrides, theme, viewSpecs, calendarApi); + return { + calendarOptions: refinedOptions, + pluginHooks: pluginHooks, + dateEnv: dateEnv, + viewSpecs: viewSpecs, + theme: theme, + toolbarConfig: toolbarConfig, + localeDefaults: localeDefaults, + availableRawLocales: availableLocaleData.map, + }; + }; + // always called from behind a memoizer + CalendarDataManager.prototype.processRawCalendarOptions = function (optionOverrides, dynamicOptionOverrides) { + var _a = mergeRawOptions([ + BASE_OPTION_DEFAULTS, + optionOverrides, + dynamicOptionOverrides, + ]), locales = _a.locales, locale = _a.locale; + var availableLocaleData = this.organizeRawLocales(locales); + var availableRawLocales = availableLocaleData.map; + var localeDefaults = this.buildLocale(locale || availableLocaleData.defaultCode, availableRawLocales).options; + var pluginHooks = this.buildPluginHooks(optionOverrides.plugins || [], globalPlugins); + var refiners = this.currentCalendarOptionsRefiners = __assign(__assign(__assign(__assign(__assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners); + var extra = {}; + var raw = mergeRawOptions([ + BASE_OPTION_DEFAULTS, + localeDefaults, + optionOverrides, + dynamicOptionOverrides, + ]); + var refined = {}; + var currentRaw = this.currentCalendarOptionsInput; + var currentRefined = this.currentCalendarOptionsRefined; + var anyChanges = false; + for (var optionName in raw) { + if (optionName !== 'plugins') { // because plugins is special-cased + if (raw[optionName] === currentRaw[optionName] || + (COMPLEX_OPTION_COMPARATORS[optionName] && + (optionName in currentRaw) && + COMPLEX_OPTION_COMPARATORS[optionName](currentRaw[optionName], raw[optionName]))) { + refined[optionName] = currentRefined[optionName]; + } + else if (refiners[optionName]) { + refined[optionName] = refiners[optionName](raw[optionName]); + anyChanges = true; + } + else { + extra[optionName] = currentRaw[optionName]; + } + } + } + if (anyChanges) { + this.currentCalendarOptionsInput = raw; + this.currentCalendarOptionsRefined = refined; + } + return { + rawOptions: this.currentCalendarOptionsInput, + refinedOptions: this.currentCalendarOptionsRefined, + pluginHooks: pluginHooks, + availableLocaleData: availableLocaleData, + localeDefaults: localeDefaults, + extra: extra, + }; + }; + CalendarDataManager.prototype._computeCurrentViewData = function (viewType, optionsData, optionOverrides, dynamicOptionOverrides) { + var viewSpec = optionsData.viewSpecs[viewType]; + if (!viewSpec) { + throw new Error("viewType \"" + viewType + "\" is not available. Please make sure you've loaded all neccessary plugins"); + } + var _a = this.processRawViewOptions(viewSpec, optionsData.pluginHooks, optionsData.localeDefaults, optionOverrides, dynamicOptionOverrides), refinedOptions = _a.refinedOptions, extra = _a.extra; + warnUnknownOptions(extra); + var dateProfileGenerator = this.buildDateProfileGenerator({ + dateProfileGeneratorClass: viewSpec.optionDefaults.dateProfileGeneratorClass, + duration: viewSpec.duration, + durationUnit: viewSpec.durationUnit, + usesMinMaxTime: viewSpec.optionDefaults.usesMinMaxTime, + dateEnv: optionsData.dateEnv, + calendarApi: this.props.calendarApi, + slotMinTime: refinedOptions.slotMinTime, + slotMaxTime: refinedOptions.slotMaxTime, + showNonCurrentDates: refinedOptions.showNonCurrentDates, + dayCount: refinedOptions.dayCount, + dateAlignment: refinedOptions.dateAlignment, + dateIncrement: refinedOptions.dateIncrement, + hiddenDays: refinedOptions.hiddenDays, + weekends: refinedOptions.weekends, + nowInput: refinedOptions.now, + validRangeInput: refinedOptions.validRange, + visibleRangeInput: refinedOptions.visibleRange, + monthMode: refinedOptions.monthMode, + fixedWeekCount: refinedOptions.fixedWeekCount, + }); + var viewApi = this.buildViewApi(viewType, this.getCurrentData, optionsData.dateEnv); + return { viewSpec: viewSpec, options: refinedOptions, dateProfileGenerator: dateProfileGenerator, viewApi: viewApi }; + }; + CalendarDataManager.prototype.processRawViewOptions = function (viewSpec, pluginHooks, localeDefaults, optionOverrides, dynamicOptionOverrides) { + var raw = mergeRawOptions([ + BASE_OPTION_DEFAULTS, + viewSpec.optionDefaults, + localeDefaults, + optionOverrides, + viewSpec.optionOverrides, + dynamicOptionOverrides, + ]); + var refiners = __assign(__assign(__assign(__assign(__assign(__assign({}, BASE_OPTION_REFINERS), CALENDAR_LISTENER_REFINERS), CALENDAR_OPTION_REFINERS), VIEW_OPTION_REFINERS), pluginHooks.listenerRefiners), pluginHooks.optionRefiners); + var refined = {}; + var currentRaw = this.currentViewOptionsInput; + var currentRefined = this.currentViewOptionsRefined; + var anyChanges = false; + var extra = {}; + for (var optionName in raw) { + if (raw[optionName] === currentRaw[optionName]) { + refined[optionName] = currentRefined[optionName]; + } + else { + if (raw[optionName] === this.currentCalendarOptionsInput[optionName]) { + if (optionName in this.currentCalendarOptionsRefined) { // might be an "extra" prop + refined[optionName] = this.currentCalendarOptionsRefined[optionName]; + } + } + else if (refiners[optionName]) { + refined[optionName] = refiners[optionName](raw[optionName]); + } + else { + extra[optionName] = raw[optionName]; + } + anyChanges = true; + } + } + if (anyChanges) { + this.currentViewOptionsInput = raw; + this.currentViewOptionsRefined = refined; + } + return { + rawOptions: this.currentViewOptionsInput, + refinedOptions: this.currentViewOptionsRefined, + extra: extra, + }; + }; + return CalendarDataManager; + }()); + function buildDateEnv(timeZone, explicitLocale, weekNumberCalculation, firstDay, weekText, pluginHooks, availableLocaleData, defaultSeparator) { + var locale = buildLocale(explicitLocale || availableLocaleData.defaultCode, availableLocaleData.map); + return new DateEnv({ + calendarSystem: 'gregory', + timeZone: timeZone, + namedTimeZoneImpl: pluginHooks.namedTimeZonedImpl, + locale: locale, + weekNumberCalculation: weekNumberCalculation, + firstDay: firstDay, + weekText: weekText, + cmdFormatter: pluginHooks.cmdFormatter, + defaultSeparator: defaultSeparator, + }); + } + function buildTheme(options, pluginHooks) { + var ThemeClass = pluginHooks.themeClasses[options.themeSystem] || StandardTheme; + return new ThemeClass(options); + } + function buildDateProfileGenerator(props) { + var DateProfileGeneratorClass = props.dateProfileGeneratorClass || DateProfileGenerator; + return new DateProfileGeneratorClass(props); + } + function buildViewApi(type, getCurrentData, dateEnv) { + return new ViewApi(type, getCurrentData, dateEnv); + } + function buildEventUiBySource(eventSources) { + return mapHash(eventSources, function (eventSource) { return eventSource.ui; }); + } + function buildEventUiBases(eventDefs, eventUiSingleBase, eventUiBySource) { + var eventUiBases = { '': eventUiSingleBase }; + for (var defId in eventDefs) { + var def = eventDefs[defId]; + if (def.sourceId && eventUiBySource[def.sourceId]) { + eventUiBases[defId] = eventUiBySource[def.sourceId]; + } + } + return eventUiBases; + } + function buildViewUiProps(calendarContext) { + var options = calendarContext.options; + return { + eventUiSingleBase: createEventUi({ + display: options.eventDisplay, + editable: options.editable, + startEditable: options.eventStartEditable, + durationEditable: options.eventDurationEditable, + constraint: options.eventConstraint, + overlap: typeof options.eventOverlap === 'boolean' ? options.eventOverlap : undefined, + allow: options.eventAllow, + backgroundColor: options.eventBackgroundColor, + borderColor: options.eventBorderColor, + textColor: options.eventTextColor, + color: options.eventColor, + // classNames: options.eventClassNames // render hook will handle this + }, calendarContext), + selectionConfig: createEventUi({ + constraint: options.selectConstraint, + overlap: typeof options.selectOverlap === 'boolean' ? options.selectOverlap : undefined, + allow: options.selectAllow, + }, calendarContext), + }; + } + function computeIsLoading(state, context) { + for (var _i = 0, _a = context.pluginHooks.isLoadingFuncs; _i < _a.length; _i++) { + var isLoadingFunc = _a[_i]; + if (isLoadingFunc(state)) { + return true; + } + } + return false; + } + function parseContextBusinessHours(calendarContext) { + return parseBusinessHours(calendarContext.options.businessHours, calendarContext); + } + function warnUnknownOptions(options, viewName) { + for (var optionName in options) { + console.warn("Unknown option '" + optionName + "'" + + (viewName ? " for view '" + viewName + "'" : '')); + } + } + + // TODO: move this to react plugin? + var CalendarDataProvider = /** @class */ (function (_super) { + __extends(CalendarDataProvider, _super); + function CalendarDataProvider(props) { + var _this = _super.call(this, props) || this; + _this.handleData = function (data) { + if (!_this.dataManager) { // still within initial run, before assignment in constructor + // eslint-disable-next-line react/no-direct-mutation-state + _this.state = data; // can't use setState yet + } + else { + _this.setState(data); + } + }; + _this.dataManager = new CalendarDataManager({ + optionOverrides: props.optionOverrides, + calendarApi: props.calendarApi, + onData: _this.handleData, + }); + return _this; + } + CalendarDataProvider.prototype.render = function () { + return this.props.children(this.state); + }; + CalendarDataProvider.prototype.componentDidUpdate = function (prevProps) { + var newOptionOverrides = this.props.optionOverrides; + if (newOptionOverrides !== prevProps.optionOverrides) { // prevent recursive handleData + this.dataManager.resetOptions(newOptionOverrides); + } + }; + return CalendarDataProvider; + }(Component)); + + // HELPERS + /* + if nextDayThreshold is specified, slicing is done in an all-day fashion. + you can get nextDayThreshold from context.nextDayThreshold + */ + function sliceEvents(props, allDay) { + return sliceEventStore(props.eventStore, props.eventUiBases, props.dateProfile.activeRange, allDay ? props.nextDayThreshold : null).fg; + } + + var NamedTimeZoneImpl = /** @class */ (function () { + function NamedTimeZoneImpl(timeZoneName) { + this.timeZoneName = timeZoneName; + } + return NamedTimeZoneImpl; + }()); + + var SegHierarchy = /** @class */ (function () { + function SegHierarchy() { + // settings + this.strictOrder = false; + this.allowReslicing = false; + this.maxCoord = -1; // -1 means no max + this.maxStackCnt = -1; // -1 means no max + this.levelCoords = []; // ordered + this.entriesByLevel = []; // parallel with levelCoords + this.stackCnts = {}; // TODO: use better technique!? + } + SegHierarchy.prototype.addSegs = function (inputs) { + var hiddenEntries = []; + for (var _i = 0, inputs_1 = inputs; _i < inputs_1.length; _i++) { + var input = inputs_1[_i]; + this.insertEntry(input, hiddenEntries); + } + return hiddenEntries; + }; + SegHierarchy.prototype.insertEntry = function (entry, hiddenEntries) { + var insertion = this.findInsertion(entry); + if (this.isInsertionValid(insertion, entry)) { + this.insertEntryAt(entry, insertion); + return 1; + } + return this.handleInvalidInsertion(insertion, entry, hiddenEntries); + }; + SegHierarchy.prototype.isInsertionValid = function (insertion, entry) { + return (this.maxCoord === -1 || insertion.levelCoord + entry.thickness <= this.maxCoord) && + (this.maxStackCnt === -1 || insertion.stackCnt < this.maxStackCnt); + }; + // returns number of new entries inserted + SegHierarchy.prototype.handleInvalidInsertion = function (insertion, entry, hiddenEntries) { + if (this.allowReslicing && insertion.touchingEntry) { + return this.splitEntry(entry, insertion.touchingEntry, hiddenEntries); + } + hiddenEntries.push(entry); + return 0; + }; + SegHierarchy.prototype.splitEntry = function (entry, barrier, hiddenEntries) { + var partCnt = 0; + var splitHiddenEntries = []; + var entrySpan = entry.span; + var barrierSpan = barrier.span; + if (entrySpan.start < barrierSpan.start) { + partCnt += this.insertEntry({ + index: entry.index, + thickness: entry.thickness, + span: { start: entrySpan.start, end: barrierSpan.start }, + }, splitHiddenEntries); + } + if (entrySpan.end > barrierSpan.end) { + partCnt += this.insertEntry({ + index: entry.index, + thickness: entry.thickness, + span: { start: barrierSpan.end, end: entrySpan.end }, + }, splitHiddenEntries); + } + if (partCnt) { + hiddenEntries.push.apply(hiddenEntries, __spreadArray([{ + index: entry.index, + thickness: entry.thickness, + span: intersectSpans(barrierSpan, entrySpan), // guaranteed to intersect + }], splitHiddenEntries)); + return partCnt; + } + hiddenEntries.push(entry); + return 0; + }; + SegHierarchy.prototype.insertEntryAt = function (entry, insertion) { + var _a = this, entriesByLevel = _a.entriesByLevel, levelCoords = _a.levelCoords; + if (insertion.lateral === -1) { + // create a new level + insertAt(levelCoords, insertion.level, insertion.levelCoord); + insertAt(entriesByLevel, insertion.level, [entry]); + } + else { + // insert into existing level + insertAt(entriesByLevel[insertion.level], insertion.lateral, entry); + } + this.stackCnts[buildEntryKey(entry)] = insertion.stackCnt; + }; + SegHierarchy.prototype.findInsertion = function (newEntry) { + var _a = this, levelCoords = _a.levelCoords, entriesByLevel = _a.entriesByLevel, strictOrder = _a.strictOrder, stackCnts = _a.stackCnts; + var levelCnt = levelCoords.length; + var candidateCoord = 0; + var touchingLevel = -1; + var touchingLateral = -1; + var touchingEntry = null; + var stackCnt = 0; + for (var trackingLevel = 0; trackingLevel < levelCnt; trackingLevel += 1) { + var trackingCoord = levelCoords[trackingLevel]; + // if the current level is past the placed entry, we have found a good empty space and can stop. + // if strictOrder, keep finding more lateral intersections. + if (!strictOrder && trackingCoord >= candidateCoord + newEntry.thickness) { + break; + } + var trackingEntries = entriesByLevel[trackingLevel]; + var trackingEntry = void 0; + var searchRes = binarySearch(trackingEntries, newEntry.span.start, getEntrySpanEnd); // find first entry after newEntry's end + var lateralIndex = searchRes[0] + searchRes[1]; // if exact match (which doesn't collide), go to next one + while ( // loop through entries that horizontally intersect + (trackingEntry = trackingEntries[lateralIndex]) && // but not past the whole entry list + trackingEntry.span.start < newEntry.span.end // and not entirely past newEntry + ) { + var trackingEntryBottom = trackingCoord + trackingEntry.thickness; + // intersects into the top of the candidate? + if (trackingEntryBottom > candidateCoord) { + candidateCoord = trackingEntryBottom; + touchingEntry = trackingEntry; + touchingLevel = trackingLevel; + touchingLateral = lateralIndex; + } + // butts up against top of candidate? (will happen if just intersected as well) + if (trackingEntryBottom === candidateCoord) { + // accumulate the highest possible stackCnt of the trackingEntries that butt up + stackCnt = Math.max(stackCnt, stackCnts[buildEntryKey(trackingEntry)] + 1); + } + lateralIndex += 1; + } + } + // the destination level will be after touchingEntry's level. find it + var destLevel = 0; + if (touchingEntry) { + destLevel = touchingLevel + 1; + while (destLevel < levelCnt && levelCoords[destLevel] < candidateCoord) { + destLevel += 1; + } + } + // if adding to an existing level, find where to insert + var destLateral = -1; + if (destLevel < levelCnt && levelCoords[destLevel] === candidateCoord) { + destLateral = binarySearch(entriesByLevel[destLevel], newEntry.span.end, getEntrySpanEnd)[0]; + } + return { + touchingLevel: touchingLevel, + touchingLateral: touchingLateral, + touchingEntry: touchingEntry, + stackCnt: stackCnt, + levelCoord: candidateCoord, + level: destLevel, + lateral: destLateral, + }; + }; + // sorted by levelCoord (lowest to highest) + SegHierarchy.prototype.toRects = function () { + var _a = this, entriesByLevel = _a.entriesByLevel, levelCoords = _a.levelCoords; + var levelCnt = entriesByLevel.length; + var rects = []; + for (var level = 0; level < levelCnt; level += 1) { + var entries = entriesByLevel[level]; + var levelCoord = levelCoords[level]; + for (var _i = 0, entries_1 = entries; _i < entries_1.length; _i++) { + var entry = entries_1[_i]; + rects.push(__assign(__assign({}, entry), { levelCoord: levelCoord })); + } + } + return rects; + }; + return SegHierarchy; + }()); + function getEntrySpanEnd(entry) { + return entry.span.end; + } + function buildEntryKey(entry) { + return entry.index + ':' + entry.span.start; + } + // returns groups with entries sorted by input order + function groupIntersectingEntries(entries) { + var merges = []; + for (var _i = 0, entries_2 = entries; _i < entries_2.length; _i++) { + var entry = entries_2[_i]; + var filteredMerges = []; + var hungryMerge = { + span: entry.span, + entries: [entry], + }; + for (var _a = 0, merges_1 = merges; _a < merges_1.length; _a++) { + var merge = merges_1[_a]; + if (intersectSpans(merge.span, hungryMerge.span)) { + hungryMerge = { + entries: merge.entries.concat(hungryMerge.entries), + span: joinSpans(merge.span, hungryMerge.span), + }; + } + else { + filteredMerges.push(merge); + } + } + filteredMerges.push(hungryMerge); + merges = filteredMerges; + } + return merges; + } + function joinSpans(span0, span1) { + return { + start: Math.min(span0.start, span1.start), + end: Math.max(span0.end, span1.end), + }; + } + function intersectSpans(span0, span1) { + var start = Math.max(span0.start, span1.start); + var end = Math.min(span0.end, span1.end); + if (start < end) { + return { start: start, end: end }; + } + return null; + } + // general util + // --------------------------------------------------------------------------------------------------------------------- + function insertAt(arr, index, item) { + arr.splice(index, 0, item); + } + function binarySearch(a, searchVal, getItemVal) { + var startIndex = 0; + var endIndex = a.length; // exclusive + if (!endIndex || searchVal < getItemVal(a[startIndex])) { // no items OR before first item + return [0, 0]; + } + if (searchVal > getItemVal(a[endIndex - 1])) { // after last item + return [endIndex, 0]; + } + while (startIndex < endIndex) { + var middleIndex = Math.floor(startIndex + (endIndex - startIndex) / 2); + var middleVal = getItemVal(a[middleIndex]); + if (searchVal < middleVal) { + endIndex = middleIndex; + } + else if (searchVal > middleVal) { + startIndex = middleIndex + 1; + } + else { // equal! + return [middleIndex, 1]; + } + } + return [startIndex, 0]; + } + + var Interaction = /** @class */ (function () { + function Interaction(settings) { + this.component = settings.component; + this.isHitComboAllowed = settings.isHitComboAllowed || null; + } + Interaction.prototype.destroy = function () { + }; + return Interaction; + }()); + function parseInteractionSettings(component, input) { + return { + component: component, + el: input.el, + useEventCenter: input.useEventCenter != null ? input.useEventCenter : true, + isHitComboAllowed: input.isHitComboAllowed || null, + }; + } + function interactionSettingsToStore(settings) { + var _a; + return _a = {}, + _a[settings.component.uid] = settings, + _a; + } + // global state + var interactionSettingsStore = {}; + + /* + An abstraction for a dragging interaction originating on an event. + Does higher-level things than PointerDragger, such as possibly: + - a "mirror" that moves with the pointer + - a minimum number of pixels or other criteria for a true drag to begin + + subclasses must emit: + - pointerdown + - dragstart + - dragmove + - pointerup + - dragend + */ + var ElementDragging = /** @class */ (function () { + function ElementDragging(el, selector) { + this.emitter = new Emitter(); + } + ElementDragging.prototype.destroy = function () { + }; + ElementDragging.prototype.setMirrorIsVisible = function (bool) { + // optional if subclass doesn't want to support a mirror + }; + ElementDragging.prototype.setMirrorNeedsRevert = function (bool) { + // optional if subclass doesn't want to support a mirror + }; + ElementDragging.prototype.setAutoScrollEnabled = function (bool) { + // optional + }; + return ElementDragging; + }()); + + // TODO: get rid of this in favor of options system, + // tho it's really easy to access this globally rather than pass thru options. + var config = {}; + + /* + Information about what will happen when an external element is dragged-and-dropped + onto a calendar. Contains information for creating an event. + */ + var DRAG_META_REFINERS = { + startTime: createDuration, + duration: createDuration, + create: Boolean, + sourceId: String, + }; + function parseDragMeta(raw) { + var _a = refineProps(raw, DRAG_META_REFINERS), refined = _a.refined, extra = _a.extra; + return { + startTime: refined.startTime || null, + duration: refined.duration || null, + create: refined.create != null ? refined.create : true, + sourceId: refined.sourceId, + leftoverProps: extra, + }; + } + + var ToolbarSection = /** @class */ (function (_super) { + __extends(ToolbarSection, _super); + function ToolbarSection() { + return _super !== null && _super.apply(this, arguments) || this; + } + ToolbarSection.prototype.render = function () { + var _this = this; + var children = this.props.widgetGroups.map(function (widgetGroup) { return _this.renderWidgetGroup(widgetGroup); }); + return createElement.apply(void 0, __spreadArray(['div', { className: 'fc-toolbar-chunk' }], children)); + }; + ToolbarSection.prototype.renderWidgetGroup = function (widgetGroup) { + var props = this.props; + var theme = this.context.theme; + var children = []; + var isOnlyButtons = true; + for (var _i = 0, widgetGroup_1 = widgetGroup; _i < widgetGroup_1.length; _i++) { + var widget = widgetGroup_1[_i]; + var buttonName = widget.buttonName, buttonClick = widget.buttonClick, buttonText = widget.buttonText, buttonIcon = widget.buttonIcon; + if (buttonName === 'title') { + isOnlyButtons = false; + children.push(createElement("h2", { className: "fc-toolbar-title" }, props.title)); + } + else { + var ariaAttrs = buttonIcon ? { 'aria-label': buttonName } : {}; + var buttonClasses = ["fc-" + buttonName + "-button", theme.getClass('button')]; + if (buttonName === props.activeButton) { + buttonClasses.push(theme.getClass('buttonActive')); + } + var isDisabled = (!props.isTodayEnabled && buttonName === 'today') || + (!props.isPrevEnabled && buttonName === 'prev') || + (!props.isNextEnabled && buttonName === 'next'); + children.push(createElement("button", __assign({ disabled: isDisabled, className: buttonClasses.join(' '), onClick: buttonClick, type: "button" }, ariaAttrs), buttonText || (buttonIcon ? createElement("span", { className: buttonIcon }) : ''))); + } + } + if (children.length > 1) { + var groupClassName = (isOnlyButtons && theme.getClass('buttonGroup')) || ''; + return createElement.apply(void 0, __spreadArray(['div', { className: groupClassName }], children)); + } + return children[0]; + }; + return ToolbarSection; + }(BaseComponent)); + + var Toolbar = /** @class */ (function (_super) { + __extends(Toolbar, _super); + function Toolbar() { + return _super !== null && _super.apply(this, arguments) || this; + } + Toolbar.prototype.render = function () { + var _a = this.props, model = _a.model, extraClassName = _a.extraClassName; + var forceLtr = false; + var startContent; + var endContent; + var centerContent = model.center; + if (model.left) { + forceLtr = true; + startContent = model.left; + } + else { + startContent = model.start; + } + if (model.right) { + forceLtr = true; + endContent = model.right; + } + else { + endContent = model.end; + } + var classNames = [ + extraClassName || '', + 'fc-toolbar', + forceLtr ? 'fc-toolbar-ltr' : '', + ]; + return (createElement("div", { className: classNames.join(' ') }, + this.renderSection('start', startContent || []), + this.renderSection('center', centerContent || []), + this.renderSection('end', endContent || []))); + }; + Toolbar.prototype.renderSection = function (key, widgetGroups) { + var props = this.props; + return (createElement(ToolbarSection, { key: key, widgetGroups: widgetGroups, title: props.title, activeButton: props.activeButton, isTodayEnabled: props.isTodayEnabled, isPrevEnabled: props.isPrevEnabled, isNextEnabled: props.isNextEnabled })); + }; + return Toolbar; + }(BaseComponent)); + + // TODO: do function component? + var ViewContainer = /** @class */ (function (_super) { + __extends(ViewContainer, _super); + function ViewContainer() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.state = { + availableWidth: null, + }; + _this.handleEl = function (el) { + _this.el = el; + setRef(_this.props.elRef, el); + _this.updateAvailableWidth(); + }; + _this.handleResize = function () { + _this.updateAvailableWidth(); + }; + return _this; + } + ViewContainer.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state; + var aspectRatio = props.aspectRatio; + var classNames = [ + 'fc-view-harness', + (aspectRatio || props.liquid || props.height) + ? 'fc-view-harness-active' // harness controls the height + : 'fc-view-harness-passive', // let the view do the height + ]; + var height = ''; + var paddingBottom = ''; + if (aspectRatio) { + if (state.availableWidth !== null) { + height = state.availableWidth / aspectRatio; + } + else { + // while waiting to know availableWidth, we can't set height to *zero* + // because will cause lots of unnecessary scrollbars within scrollgrid. + // BETTER: don't start rendering ANYTHING yet until we know container width + // NOTE: why not always use paddingBottom? Causes height oscillation (issue 5606) + paddingBottom = (1 / aspectRatio) * 100 + "%"; + } + } + else { + height = props.height || ''; + } + return (createElement("div", { ref: this.handleEl, onClick: props.onClick, className: classNames.join(' '), style: { height: height, paddingBottom: paddingBottom } }, props.children)); + }; + ViewContainer.prototype.componentDidMount = function () { + this.context.addResizeHandler(this.handleResize); + }; + ViewContainer.prototype.componentWillUnmount = function () { + this.context.removeResizeHandler(this.handleResize); + }; + ViewContainer.prototype.updateAvailableWidth = function () { + if (this.el && // needed. but why? + this.props.aspectRatio // aspectRatio is the only height setting that needs availableWidth + ) { + this.setState({ availableWidth: this.el.offsetWidth }); + } + }; + return ViewContainer; + }(BaseComponent)); + + /* + Detects when the user clicks on an event within a DateComponent + */ + var EventClicking = /** @class */ (function (_super) { + __extends(EventClicking, _super); + function EventClicking(settings) { + var _this = _super.call(this, settings) || this; + _this.handleSegClick = function (ev, segEl) { + var component = _this.component; + var context = component.context; + var seg = getElSeg(segEl); + if (seg && // might be the
surrounding the more link + component.isValidSegDownEl(ev.target)) { + // our way to simulate a link click for elements that can't be tags + // grab before trigger fired in case trigger trashes DOM thru rerendering + var hasUrlContainer = elementClosest(ev.target, '.fc-event-forced-url'); + var url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : ''; + context.emitter.trigger('eventClick', { + el: segEl, + event: new EventApi(component.context, seg.eventRange.def, seg.eventRange.instance), + jsEvent: ev, + view: context.viewApi, + }); + if (url && !ev.defaultPrevented) { + window.location.href = url; + } + } + }; + _this.destroy = listenBySelector(settings.el, 'click', '.fc-event', // on both fg and bg events + _this.handleSegClick); + return _this; + } + return EventClicking; + }(Interaction)); + + /* + Triggers events and adds/removes core classNames when the user's pointer + enters/leaves event-elements of a component. + */ + var EventHovering = /** @class */ (function (_super) { + __extends(EventHovering, _super); + function EventHovering(settings) { + var _this = _super.call(this, settings) || this; + // for simulating an eventMouseLeave when the event el is destroyed while mouse is over it + _this.handleEventElRemove = function (el) { + if (el === _this.currentSegEl) { + _this.handleSegLeave(null, _this.currentSegEl); + } + }; + _this.handleSegEnter = function (ev, segEl) { + if (getElSeg(segEl)) { // TODO: better way to make sure not hovering over more+ link or its wrapper + _this.currentSegEl = segEl; + _this.triggerEvent('eventMouseEnter', ev, segEl); + } + }; + _this.handleSegLeave = function (ev, segEl) { + if (_this.currentSegEl) { + _this.currentSegEl = null; + _this.triggerEvent('eventMouseLeave', ev, segEl); + } + }; + _this.removeHoverListeners = listenToHoverBySelector(settings.el, '.fc-event', // on both fg and bg events + _this.handleSegEnter, _this.handleSegLeave); + return _this; + } + EventHovering.prototype.destroy = function () { + this.removeHoverListeners(); + }; + EventHovering.prototype.triggerEvent = function (publicEvName, ev, segEl) { + var component = this.component; + var context = component.context; + var seg = getElSeg(segEl); + if (!ev || component.isValidSegDownEl(ev.target)) { + context.emitter.trigger(publicEvName, { + el: segEl, + event: new EventApi(context, seg.eventRange.def, seg.eventRange.instance), + jsEvent: ev, + view: context.viewApi, + }); + } + }; + return EventHovering; + }(Interaction)); + + var CalendarContent = /** @class */ (function (_super) { + __extends(CalendarContent, _super); + function CalendarContent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildViewContext = memoize(buildViewContext); + _this.buildViewPropTransformers = memoize(buildViewPropTransformers); + _this.buildToolbarProps = memoize(buildToolbarProps); + _this.handleNavLinkClick = buildDelegationHandler('a[data-navlink]', _this._handleNavLinkClick.bind(_this)); + _this.headerRef = createRef(); + _this.footerRef = createRef(); + _this.interactionsStore = {}; + // Component Registration + // ----------------------------------------------------------------------------------------------------------------- + _this.registerInteractiveComponent = function (component, settingsInput) { + var settings = parseInteractionSettings(component, settingsInput); + var DEFAULT_INTERACTIONS = [ + EventClicking, + EventHovering, + ]; + var interactionClasses = DEFAULT_INTERACTIONS.concat(_this.props.pluginHooks.componentInteractions); + var interactions = interactionClasses.map(function (TheInteractionClass) { return new TheInteractionClass(settings); }); + _this.interactionsStore[component.uid] = interactions; + interactionSettingsStore[component.uid] = settings; + }; + _this.unregisterInteractiveComponent = function (component) { + for (var _i = 0, _a = _this.interactionsStore[component.uid]; _i < _a.length; _i++) { + var listener = _a[_i]; + listener.destroy(); + } + delete _this.interactionsStore[component.uid]; + delete interactionSettingsStore[component.uid]; + }; + // Resizing + // ----------------------------------------------------------------------------------------------------------------- + _this.resizeRunner = new DelayedRunner(function () { + _this.props.emitter.trigger('_resize', true); // should window resizes be considered "forced" ? + _this.props.emitter.trigger('windowResize', { view: _this.props.viewApi }); + }); + _this.handleWindowResize = function (ev) { + var options = _this.props.options; + if (options.handleWindowResize && + ev.target === window // avoid jqui events + ) { + _this.resizeRunner.request(options.windowResizeDelay); + } + }; + return _this; + } + /* + renders INSIDE of an outer div + */ + CalendarContent.prototype.render = function () { + var props = this.props; + var toolbarConfig = props.toolbarConfig, options = props.options; + var toolbarProps = this.buildToolbarProps(props.viewSpec, props.dateProfile, props.dateProfileGenerator, props.currentDate, getNow(props.options.now, props.dateEnv), // TODO: use NowTimer???? + props.viewTitle); + var viewVGrow = false; + var viewHeight = ''; + var viewAspectRatio; + if (props.isHeightAuto || props.forPrint) { + viewHeight = ''; + } + else if (options.height != null) { + viewVGrow = true; + } + else if (options.contentHeight != null) { + viewHeight = options.contentHeight; + } + else { + viewAspectRatio = Math.max(options.aspectRatio, 0.5); // prevent from getting too tall + } + var viewContext = this.buildViewContext(props.viewSpec, props.viewApi, props.options, props.dateProfileGenerator, props.dateEnv, props.theme, props.pluginHooks, props.dispatch, props.getCurrentData, props.emitter, props.calendarApi, this.registerInteractiveComponent, this.unregisterInteractiveComponent); + return (createElement(ViewContextType.Provider, { value: viewContext }, + toolbarConfig.headerToolbar && (createElement(Toolbar, __assign({ ref: this.headerRef, extraClassName: "fc-header-toolbar", model: toolbarConfig.headerToolbar }, toolbarProps))), + createElement(ViewContainer, { liquid: viewVGrow, height: viewHeight, aspectRatio: viewAspectRatio, onClick: this.handleNavLinkClick }, + this.renderView(props), + this.buildAppendContent()), + toolbarConfig.footerToolbar && (createElement(Toolbar, __assign({ ref: this.footerRef, extraClassName: "fc-footer-toolbar", model: toolbarConfig.footerToolbar }, toolbarProps))))); + }; + CalendarContent.prototype.componentDidMount = function () { + var props = this.props; + this.calendarInteractions = props.pluginHooks.calendarInteractions + .map(function (CalendarInteractionClass) { return new CalendarInteractionClass(props); }); + window.addEventListener('resize', this.handleWindowResize); + var propSetHandlers = props.pluginHooks.propSetHandlers; + for (var propName in propSetHandlers) { + propSetHandlers[propName](props[propName], props); + } + }; + CalendarContent.prototype.componentDidUpdate = function (prevProps) { + var props = this.props; + var propSetHandlers = props.pluginHooks.propSetHandlers; + for (var propName in propSetHandlers) { + if (props[propName] !== prevProps[propName]) { + propSetHandlers[propName](props[propName], props); + } + } + }; + CalendarContent.prototype.componentWillUnmount = function () { + window.removeEventListener('resize', this.handleWindowResize); + this.resizeRunner.clear(); + for (var _i = 0, _a = this.calendarInteractions; _i < _a.length; _i++) { + var interaction = _a[_i]; + interaction.destroy(); + } + this.props.emitter.trigger('_unmount'); + }; + CalendarContent.prototype._handleNavLinkClick = function (ev, anchorEl) { + var _a = this.props, dateEnv = _a.dateEnv, options = _a.options, calendarApi = _a.calendarApi; + var navLinkOptions = anchorEl.getAttribute('data-navlink'); + navLinkOptions = navLinkOptions ? JSON.parse(navLinkOptions) : {}; + var dateMarker = dateEnv.createMarker(navLinkOptions.date); + var viewType = navLinkOptions.type; + var customAction = viewType === 'day' ? options.navLinkDayClick : + viewType === 'week' ? options.navLinkWeekClick : null; + if (typeof customAction === 'function') { + customAction.call(calendarApi, dateEnv.toDate(dateMarker), ev); + } + else { + if (typeof customAction === 'string') { + viewType = customAction; + } + calendarApi.zoomTo(dateMarker, viewType); + } + }; + CalendarContent.prototype.buildAppendContent = function () { + var props = this.props; + var children = props.pluginHooks.viewContainerAppends.map(function (buildAppendContent) { return buildAppendContent(props); }); + return createElement.apply(void 0, __spreadArray([Fragment, {}], children)); + }; + CalendarContent.prototype.renderView = function (props) { + var pluginHooks = props.pluginHooks; + var viewSpec = props.viewSpec; + var viewProps = { + dateProfile: props.dateProfile, + businessHours: props.businessHours, + eventStore: props.renderableEventStore, + eventUiBases: props.eventUiBases, + dateSelection: props.dateSelection, + eventSelection: props.eventSelection, + eventDrag: props.eventDrag, + eventResize: props.eventResize, + isHeightAuto: props.isHeightAuto, + forPrint: props.forPrint, + }; + var transformers = this.buildViewPropTransformers(pluginHooks.viewPropsTransformers); + for (var _i = 0, transformers_1 = transformers; _i < transformers_1.length; _i++) { + var transformer = transformers_1[_i]; + __assign(viewProps, transformer.transform(viewProps, props)); + } + var ViewComponent = viewSpec.component; + return (createElement(ViewComponent, __assign({}, viewProps))); + }; + return CalendarContent; + }(PureComponent)); + function buildToolbarProps(viewSpec, dateProfile, dateProfileGenerator, currentDate, now, title) { + // don't force any date-profiles to valid date profiles (the `false`) so that we can tell if it's invalid + var todayInfo = dateProfileGenerator.build(now, undefined, false); // TODO: need `undefined` or else INFINITE LOOP for some reason + var prevInfo = dateProfileGenerator.buildPrev(dateProfile, currentDate, false); + var nextInfo = dateProfileGenerator.buildNext(dateProfile, currentDate, false); + return { + title: title, + activeButton: viewSpec.type, + isTodayEnabled: todayInfo.isValid && !rangeContainsMarker(dateProfile.currentRange, now), + isPrevEnabled: prevInfo.isValid, + isNextEnabled: nextInfo.isValid, + }; + } + // Plugin + // ----------------------------------------------------------------------------------------------------------------- + function buildViewPropTransformers(theClasses) { + return theClasses.map(function (TheClass) { return new TheClass(); }); + } + + var CalendarRoot = /** @class */ (function (_super) { + __extends(CalendarRoot, _super); + function CalendarRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.state = { + forPrint: false, + }; + _this.handleBeforePrint = function () { + _this.setState({ forPrint: true }); + }; + _this.handleAfterPrint = function () { + _this.setState({ forPrint: false }); + }; + return _this; + } + CalendarRoot.prototype.render = function () { + var props = this.props; + var options = props.options; + var forPrint = this.state.forPrint; + var isHeightAuto = forPrint || options.height === 'auto' || options.contentHeight === 'auto'; + var height = (!isHeightAuto && options.height != null) ? options.height : ''; + var classNames = [ + 'fc', + forPrint ? 'fc-media-print' : 'fc-media-screen', + "fc-direction-" + options.direction, + props.theme.getClass('root'), + ]; + if (!getCanVGrowWithinCell()) { + classNames.push('fc-liquid-hack'); + } + return props.children(classNames, height, isHeightAuto, forPrint); + }; + CalendarRoot.prototype.componentDidMount = function () { + var emitter = this.props.emitter; + emitter.on('_beforeprint', this.handleBeforePrint); + emitter.on('_afterprint', this.handleAfterPrint); + }; + CalendarRoot.prototype.componentWillUnmount = function () { + var emitter = this.props.emitter; + emitter.off('_beforeprint', this.handleBeforePrint); + emitter.off('_afterprint', this.handleAfterPrint); + }; + return CalendarRoot; + }(BaseComponent)); + + // Computes a default column header formatting string if `colFormat` is not explicitly defined + function computeFallbackHeaderFormat(datesRepDistinctDays, dayCnt) { + // if more than one week row, or if there are a lot of columns with not much space, + // put just the day numbers will be in each cell + if (!datesRepDistinctDays || dayCnt > 10) { + return createFormatter({ weekday: 'short' }); // "Sat" + } + if (dayCnt > 1) { + return createFormatter({ weekday: 'short', month: 'numeric', day: 'numeric', omitCommas: true }); // "Sat 11/12" + } + return createFormatter({ weekday: 'long' }); // "Saturday" + } + + var CLASS_NAME = 'fc-col-header-cell'; // do the cushion too? no + function renderInner$1(hookProps) { + return hookProps.text; + } + + var TableDateCell = /** @class */ (function (_super) { + __extends(TableDateCell, _super); + function TableDateCell() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableDateCell.prototype.render = function () { + var _a = this.context, dateEnv = _a.dateEnv, options = _a.options, theme = _a.theme, viewApi = _a.viewApi; + var props = this.props; + var date = props.date, dateProfile = props.dateProfile; + var dayMeta = getDateMeta(date, props.todayRange, null, dateProfile); + var classNames = [CLASS_NAME].concat(getDayClassNames(dayMeta, theme)); + var text = dateEnv.format(date, props.dayHeaderFormat); + // if colCnt is 1, we are already in a day-view and don't need a navlink + var navLinkAttrs = (options.navLinks && !dayMeta.isDisabled && props.colCnt > 1) + ? { 'data-navlink': buildNavLinkData(date), tabIndex: 0 } + : {}; + var hookProps = __assign(__assign(__assign({ date: dateEnv.toDate(date), view: viewApi }, props.extraHookProps), { text: text }), dayMeta); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.dayHeaderClassNames, content: options.dayHeaderContent, defaultContent: renderInner$1, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("th", __assign({ ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-date": !dayMeta.isDisabled ? formatDayString(date) : undefined, colSpan: props.colSpan }, props.extraDataAttrs), + createElement("div", { className: "fc-scrollgrid-sync-inner" }, !dayMeta.isDisabled && (createElement("a", __assign({ ref: innerElRef, className: [ + 'fc-col-header-cell-cushion', + props.isSticky ? 'fc-sticky' : '', + ].join(' ') }, navLinkAttrs), innerContent))))); })); + }; + return TableDateCell; + }(BaseComponent)); + + var TableDowCell = /** @class */ (function (_super) { + __extends(TableDowCell, _super); + function TableDowCell() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableDowCell.prototype.render = function () { + var props = this.props; + var _a = this.context, dateEnv = _a.dateEnv, theme = _a.theme, viewApi = _a.viewApi, options = _a.options; + var date = addDays(new Date(259200000), props.dow); // start with Sun, 04 Jan 1970 00:00:00 GMT + var dateMeta = { + dow: props.dow, + isDisabled: false, + isFuture: false, + isPast: false, + isToday: false, + isOther: false, + }; + var classNames = [CLASS_NAME].concat(getDayClassNames(dateMeta, theme), props.extraClassNames || []); + var text = dateEnv.format(date, props.dayHeaderFormat); + var hookProps = __assign(__assign(__assign(__assign({ // TODO: make this public? + date: date }, dateMeta), { view: viewApi }), props.extraHookProps), { text: text }); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.dayHeaderClassNames, content: options.dayHeaderContent, defaultContent: renderInner$1, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("th", __assign({ ref: rootElRef, className: classNames.concat(customClassNames).join(' '), colSpan: props.colSpan }, props.extraDataAttrs), + createElement("div", { className: "fc-scrollgrid-sync-inner" }, + createElement("a", { className: [ + 'fc-col-header-cell-cushion', + props.isSticky ? 'fc-sticky' : '', + ].join(' '), ref: innerElRef }, innerContent)))); })); + }; + return TableDowCell; + }(BaseComponent)); + + var NowTimer = /** @class */ (function (_super) { + __extends(NowTimer, _super); + function NowTimer(props, context) { + var _this = _super.call(this, props, context) || this; + _this.initialNowDate = getNow(context.options.now, context.dateEnv); + _this.initialNowQueriedMs = new Date().valueOf(); + _this.state = _this.computeTiming().currentState; + return _this; + } + NowTimer.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state; + return props.children(state.nowDate, state.todayRange); + }; + NowTimer.prototype.componentDidMount = function () { + this.setTimeout(); + }; + NowTimer.prototype.componentDidUpdate = function (prevProps) { + if (prevProps.unit !== this.props.unit) { + this.clearTimeout(); + this.setTimeout(); + } + }; + NowTimer.prototype.componentWillUnmount = function () { + this.clearTimeout(); + }; + NowTimer.prototype.computeTiming = function () { + var _a = this, props = _a.props, context = _a.context; + var unroundedNow = addMs(this.initialNowDate, new Date().valueOf() - this.initialNowQueriedMs); + var currentUnitStart = context.dateEnv.startOf(unroundedNow, props.unit); + var nextUnitStart = context.dateEnv.add(currentUnitStart, createDuration(1, props.unit)); + var waitMs = nextUnitStart.valueOf() - unroundedNow.valueOf(); + // there is a max setTimeout ms value (https://stackoverflow.com/a/3468650/96342) + // ensure no longer than a day + waitMs = Math.min(1000 * 60 * 60 * 24, waitMs); + return { + currentState: { nowDate: currentUnitStart, todayRange: buildDayRange(currentUnitStart) }, + nextState: { nowDate: nextUnitStart, todayRange: buildDayRange(nextUnitStart) }, + waitMs: waitMs, + }; + }; + NowTimer.prototype.setTimeout = function () { + var _this = this; + var _a = this.computeTiming(), nextState = _a.nextState, waitMs = _a.waitMs; + this.timeoutId = setTimeout(function () { + _this.setState(nextState, function () { + _this.setTimeout(); + }); + }, waitMs); + }; + NowTimer.prototype.clearTimeout = function () { + if (this.timeoutId) { + clearTimeout(this.timeoutId); + } + }; + NowTimer.contextType = ViewContextType; + return NowTimer; + }(Component)); + function buildDayRange(date) { + var start = startOfDay(date); + var end = addDays(start, 1); + return { start: start, end: end }; + } + + var DayHeader = /** @class */ (function (_super) { + __extends(DayHeader, _super); + function DayHeader() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.createDayHeaderFormatter = memoize(createDayHeaderFormatter); + return _this; + } + DayHeader.prototype.render = function () { + var context = this.context; + var _a = this.props, dates = _a.dates, dateProfile = _a.dateProfile, datesRepDistinctDays = _a.datesRepDistinctDays, renderIntro = _a.renderIntro; + var dayHeaderFormat = this.createDayHeaderFormatter(context.options.dayHeaderFormat, datesRepDistinctDays, dates.length); + return (createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { return (createElement("tr", null, + renderIntro && renderIntro('day'), + dates.map(function (date) { return (datesRepDistinctDays ? (createElement(TableDateCell, { key: date.toISOString(), date: date, dateProfile: dateProfile, todayRange: todayRange, colCnt: dates.length, dayHeaderFormat: dayHeaderFormat })) : (createElement(TableDowCell, { key: date.getUTCDay(), dow: date.getUTCDay(), dayHeaderFormat: dayHeaderFormat }))); }))); })); + }; + return DayHeader; + }(BaseComponent)); + function createDayHeaderFormatter(explicitFormat, datesRepDistinctDays, dateCnt) { + return explicitFormat || computeFallbackHeaderFormat(datesRepDistinctDays, dateCnt); + } + + var DaySeriesModel = /** @class */ (function () { + function DaySeriesModel(range, dateProfileGenerator) { + var date = range.start; + var end = range.end; + var indices = []; + var dates = []; + var dayIndex = -1; + while (date < end) { // loop each day from start to end + if (dateProfileGenerator.isHiddenDay(date)) { + indices.push(dayIndex + 0.5); // mark that it's between indices + } + else { + dayIndex += 1; + indices.push(dayIndex); + dates.push(date); + } + date = addDays(date, 1); + } + this.dates = dates; + this.indices = indices; + this.cnt = dates.length; + } + DaySeriesModel.prototype.sliceRange = function (range) { + var firstIndex = this.getDateDayIndex(range.start); // inclusive first index + var lastIndex = this.getDateDayIndex(addDays(range.end, -1)); // inclusive last index + var clippedFirstIndex = Math.max(0, firstIndex); + var clippedLastIndex = Math.min(this.cnt - 1, lastIndex); + // deal with in-between indices + clippedFirstIndex = Math.ceil(clippedFirstIndex); // in-between starts round to next cell + clippedLastIndex = Math.floor(clippedLastIndex); // in-between ends round to prev cell + if (clippedFirstIndex <= clippedLastIndex) { + return { + firstIndex: clippedFirstIndex, + lastIndex: clippedLastIndex, + isStart: firstIndex === clippedFirstIndex, + isEnd: lastIndex === clippedLastIndex, + }; + } + return null; + }; + // Given a date, returns its chronolocial cell-index from the first cell of the grid. + // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets. + // If before the first offset, returns a negative number. + // If after the last offset, returns an offset past the last cell offset. + // Only works for *start* dates of cells. Will not work for exclusive end dates for cells. + DaySeriesModel.prototype.getDateDayIndex = function (date) { + var indices = this.indices; + var dayOffset = Math.floor(diffDays(this.dates[0], date)); + if (dayOffset < 0) { + return indices[0] - 1; + } + if (dayOffset >= indices.length) { + return indices[indices.length - 1] + 1; + } + return indices[dayOffset]; + }; + return DaySeriesModel; + }()); + + var DayTableModel = /** @class */ (function () { + function DayTableModel(daySeries, breakOnWeeks) { + var dates = daySeries.dates; + var daysPerRow; + var firstDay; + var rowCnt; + if (breakOnWeeks) { + // count columns until the day-of-week repeats + firstDay = dates[0].getUTCDay(); + for (daysPerRow = 1; daysPerRow < dates.length; daysPerRow += 1) { + if (dates[daysPerRow].getUTCDay() === firstDay) { + break; + } + } + rowCnt = Math.ceil(dates.length / daysPerRow); + } + else { + rowCnt = 1; + daysPerRow = dates.length; + } + this.rowCnt = rowCnt; + this.colCnt = daysPerRow; + this.daySeries = daySeries; + this.cells = this.buildCells(); + this.headerDates = this.buildHeaderDates(); + } + DayTableModel.prototype.buildCells = function () { + var rows = []; + for (var row = 0; row < this.rowCnt; row += 1) { + var cells = []; + for (var col = 0; col < this.colCnt; col += 1) { + cells.push(this.buildCell(row, col)); + } + rows.push(cells); + } + return rows; + }; + DayTableModel.prototype.buildCell = function (row, col) { + var date = this.daySeries.dates[row * this.colCnt + col]; + return { + key: date.toISOString(), + date: date, + }; + }; + DayTableModel.prototype.buildHeaderDates = function () { + var dates = []; + for (var col = 0; col < this.colCnt; col += 1) { + dates.push(this.cells[0][col].date); + } + return dates; + }; + DayTableModel.prototype.sliceRange = function (range) { + var colCnt = this.colCnt; + var seriesSeg = this.daySeries.sliceRange(range); + var segs = []; + if (seriesSeg) { + var firstIndex = seriesSeg.firstIndex, lastIndex = seriesSeg.lastIndex; + var index = firstIndex; + while (index <= lastIndex) { + var row = Math.floor(index / colCnt); + var nextIndex = Math.min((row + 1) * colCnt, lastIndex + 1); + segs.push({ + row: row, + firstCol: index % colCnt, + lastCol: (nextIndex - 1) % colCnt, + isStart: seriesSeg.isStart && index === firstIndex, + isEnd: seriesSeg.isEnd && (nextIndex - 1) === lastIndex, + }); + index = nextIndex; + } + } + return segs; + }; + return DayTableModel; + }()); + + var Slicer = /** @class */ (function () { + function Slicer() { + this.sliceBusinessHours = memoize(this._sliceBusinessHours); + this.sliceDateSelection = memoize(this._sliceDateSpan); + this.sliceEventStore = memoize(this._sliceEventStore); + this.sliceEventDrag = memoize(this._sliceInteraction); + this.sliceEventResize = memoize(this._sliceInteraction); + this.forceDayIfListItem = false; // hack + } + Slicer.prototype.sliceProps = function (props, dateProfile, nextDayThreshold, context) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + var eventUiBases = props.eventUiBases; + var eventSegs = this.sliceEventStore.apply(this, __spreadArray([props.eventStore, eventUiBases, dateProfile, nextDayThreshold], extraArgs)); + return { + dateSelectionSegs: this.sliceDateSelection.apply(this, __spreadArray([props.dateSelection, eventUiBases, context], extraArgs)), + businessHourSegs: this.sliceBusinessHours.apply(this, __spreadArray([props.businessHours, dateProfile, nextDayThreshold, context], extraArgs)), + fgEventSegs: eventSegs.fg, + bgEventSegs: eventSegs.bg, + eventDrag: this.sliceEventDrag.apply(this, __spreadArray([props.eventDrag, eventUiBases, dateProfile, nextDayThreshold], extraArgs)), + eventResize: this.sliceEventResize.apply(this, __spreadArray([props.eventResize, eventUiBases, dateProfile, nextDayThreshold], extraArgs)), + eventSelection: props.eventSelection, + }; // TODO: give interactionSegs? + }; + Slicer.prototype.sliceNowDate = function (// does not memoize + date, context) { + var extraArgs = []; + for (var _i = 2; _i < arguments.length; _i++) { + extraArgs[_i - 2] = arguments[_i]; + } + return this._sliceDateSpan.apply(this, __spreadArray([{ range: { start: date, end: addMs(date, 1) }, allDay: false }, + {}, + context], extraArgs)); + }; + Slicer.prototype._sliceBusinessHours = function (businessHours, dateProfile, nextDayThreshold, context) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (!businessHours) { + return []; + } + return this._sliceEventStore.apply(this, __spreadArray([expandRecurring(businessHours, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), context), + {}, + dateProfile, + nextDayThreshold], extraArgs)).bg; + }; + Slicer.prototype._sliceEventStore = function (eventStore, eventUiBases, dateProfile, nextDayThreshold) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (eventStore) { + var rangeRes = sliceEventStore(eventStore, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); + return { + bg: this.sliceEventRanges(rangeRes.bg, extraArgs), + fg: this.sliceEventRanges(rangeRes.fg, extraArgs), + }; + } + return { bg: [], fg: [] }; + }; + Slicer.prototype._sliceInteraction = function (interaction, eventUiBases, dateProfile, nextDayThreshold) { + var extraArgs = []; + for (var _i = 4; _i < arguments.length; _i++) { + extraArgs[_i - 4] = arguments[_i]; + } + if (!interaction) { + return null; + } + var rangeRes = sliceEventStore(interaction.mutatedEvents, eventUiBases, computeActiveRange(dateProfile, Boolean(nextDayThreshold)), nextDayThreshold); + return { + segs: this.sliceEventRanges(rangeRes.fg, extraArgs), + affectedInstances: interaction.affectedEvents.instances, + isEvent: interaction.isEvent, + }; + }; + Slicer.prototype._sliceDateSpan = function (dateSpan, eventUiBases, context) { + var extraArgs = []; + for (var _i = 3; _i < arguments.length; _i++) { + extraArgs[_i - 3] = arguments[_i]; + } + if (!dateSpan) { + return []; + } + var eventRange = fabricateEventRange(dateSpan, eventUiBases, context); + var segs = this.sliceRange.apply(this, __spreadArray([dateSpan.range], extraArgs)); + for (var _a = 0, segs_1 = segs; _a < segs_1.length; _a++) { + var seg = segs_1[_a]; + seg.eventRange = eventRange; + } + return segs; + }; + /* + "complete" seg means it has component and eventRange + */ + Slicer.prototype.sliceEventRanges = function (eventRanges, extraArgs) { + var segs = []; + for (var _i = 0, eventRanges_1 = eventRanges; _i < eventRanges_1.length; _i++) { + var eventRange = eventRanges_1[_i]; + segs.push.apply(segs, this.sliceEventRange(eventRange, extraArgs)); + } + return segs; + }; + /* + "complete" seg means it has component and eventRange + */ + Slicer.prototype.sliceEventRange = function (eventRange, extraArgs) { + var dateRange = eventRange.range; + // hack to make multi-day events that are being force-displayed as list-items to take up only one day + if (this.forceDayIfListItem && eventRange.ui.display === 'list-item') { + dateRange = { + start: dateRange.start, + end: addDays(dateRange.start, 1), + }; + } + var segs = this.sliceRange.apply(this, __spreadArray([dateRange], extraArgs)); + for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) { + var seg = segs_2[_i]; + seg.eventRange = eventRange; + seg.isStart = eventRange.isStart && seg.isStart; + seg.isEnd = eventRange.isEnd && seg.isEnd; + } + return segs; + }; + return Slicer; + }()); + /* + for incorporating slotMinTime/slotMaxTime if appropriate + TODO: should be part of DateProfile! + TimelineDateProfile already does this btw + */ + function computeActiveRange(dateProfile, isComponentAllDay) { + var range = dateProfile.activeRange; + if (isComponentAllDay) { + return range; + } + return { + start: addMs(range.start, dateProfile.slotMinTime.milliseconds), + end: addMs(range.end, dateProfile.slotMaxTime.milliseconds - 864e5), // 864e5 = ms in a day + }; + } + + // high-level segmenting-aware tester functions + // ------------------------------------------------------------------------------------------------------------------------ + function isInteractionValid(interaction, dateProfile, context) { + var instances = interaction.mutatedEvents.instances; + for (var instanceId in instances) { + if (!rangeContainsRange(dateProfile.validRange, instances[instanceId].range)) { + return false; + } + } + return isNewPropsValid({ eventDrag: interaction }, context); // HACK: the eventDrag props is used for ALL interactions + } + function isDateSelectionValid(dateSelection, dateProfile, context) { + if (!rangeContainsRange(dateProfile.validRange, dateSelection.range)) { + return false; + } + return isNewPropsValid({ dateSelection: dateSelection }, context); + } + function isNewPropsValid(newProps, context) { + var calendarState = context.getCurrentData(); + var props = __assign({ businessHours: calendarState.businessHours, dateSelection: '', eventStore: calendarState.eventStore, eventUiBases: calendarState.eventUiBases, eventSelection: '', eventDrag: null, eventResize: null }, newProps); + return (context.pluginHooks.isPropsValid || isPropsValid)(props, context); + } + function isPropsValid(state, context, dateSpanMeta, filterConfig) { + if (dateSpanMeta === void 0) { dateSpanMeta = {}; } + if (state.eventDrag && !isInteractionPropsValid(state, context, dateSpanMeta, filterConfig)) { + return false; + } + if (state.dateSelection && !isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig)) { + return false; + } + return true; + } + // Moving Event Validation + // ------------------------------------------------------------------------------------------------------------------------ + function isInteractionPropsValid(state, context, dateSpanMeta, filterConfig) { + var currentState = context.getCurrentData(); + var interaction = state.eventDrag; // HACK: the eventDrag props is used for ALL interactions + var subjectEventStore = interaction.mutatedEvents; + var subjectDefs = subjectEventStore.defs; + var subjectInstances = subjectEventStore.instances; + var subjectConfigs = compileEventUis(subjectDefs, interaction.isEvent ? + state.eventUiBases : + { '': currentState.selectionConfig }); + if (filterConfig) { + subjectConfigs = mapHash(subjectConfigs, filterConfig); + } + // exclude the subject events. TODO: exclude defs too? + var otherEventStore = excludeInstances(state.eventStore, interaction.affectedEvents.instances); + var otherDefs = otherEventStore.defs; + var otherInstances = otherEventStore.instances; + var otherConfigs = compileEventUis(otherDefs, state.eventUiBases); + for (var subjectInstanceId in subjectInstances) { + var subjectInstance = subjectInstances[subjectInstanceId]; + var subjectRange = subjectInstance.range; + var subjectConfig = subjectConfigs[subjectInstance.defId]; + var subjectDef = subjectDefs[subjectInstance.defId]; + // constraint + if (!allConstraintsPass(subjectConfig.constraints, subjectRange, otherEventStore, state.businessHours, context)) { + return false; + } + // overlap + var eventOverlap = context.options.eventOverlap; + var eventOverlapFunc = typeof eventOverlap === 'function' ? eventOverlap : null; + for (var otherInstanceId in otherInstances) { + var otherInstance = otherInstances[otherInstanceId]; + // intersect! evaluate + if (rangesIntersect(subjectRange, otherInstance.range)) { + var otherOverlap = otherConfigs[otherInstance.defId].overlap; + // consider the other event's overlap. only do this if the subject event is a "real" event + if (otherOverlap === false && interaction.isEvent) { + return false; + } + if (subjectConfig.overlap === false) { + return false; + } + if (eventOverlapFunc && !eventOverlapFunc(new EventApi(context, otherDefs[otherInstance.defId], otherInstance), // still event + new EventApi(context, subjectDef, subjectInstance))) { + return false; + } + } + } + // allow (a function) + var calendarEventStore = currentState.eventStore; // need global-to-calendar, not local to component (splittable)state + for (var _i = 0, _a = subjectConfig.allows; _i < _a.length; _i++) { + var subjectAllow = _a[_i]; + var subjectDateSpan = __assign(__assign({}, dateSpanMeta), { range: subjectInstance.range, allDay: subjectDef.allDay }); + var origDef = calendarEventStore.defs[subjectDef.defId]; + var origInstance = calendarEventStore.instances[subjectInstanceId]; + var eventApi = void 0; + if (origDef) { // was previously in the calendar + eventApi = new EventApi(context, origDef, origInstance); + } + else { // was an external event + eventApi = new EventApi(context, subjectDef); // no instance, because had no dates + } + if (!subjectAllow(buildDateSpanApiWithContext(subjectDateSpan, context), eventApi)) { + return false; + } + } + } + return true; + } + // Date Selection Validation + // ------------------------------------------------------------------------------------------------------------------------ + function isDateSelectionPropsValid(state, context, dateSpanMeta, filterConfig) { + var relevantEventStore = state.eventStore; + var relevantDefs = relevantEventStore.defs; + var relevantInstances = relevantEventStore.instances; + var selection = state.dateSelection; + var selectionRange = selection.range; + var selectionConfig = context.getCurrentData().selectionConfig; + if (filterConfig) { + selectionConfig = filterConfig(selectionConfig); + } + // constraint + if (!allConstraintsPass(selectionConfig.constraints, selectionRange, relevantEventStore, state.businessHours, context)) { + return false; + } + // overlap + var selectOverlap = context.options.selectOverlap; + var selectOverlapFunc = typeof selectOverlap === 'function' ? selectOverlap : null; + for (var relevantInstanceId in relevantInstances) { + var relevantInstance = relevantInstances[relevantInstanceId]; + // intersect! evaluate + if (rangesIntersect(selectionRange, relevantInstance.range)) { + if (selectionConfig.overlap === false) { + return false; + } + if (selectOverlapFunc && !selectOverlapFunc(new EventApi(context, relevantDefs[relevantInstance.defId], relevantInstance), null)) { + return false; + } + } + } + // allow (a function) + for (var _i = 0, _a = selectionConfig.allows; _i < _a.length; _i++) { + var selectionAllow = _a[_i]; + var fullDateSpan = __assign(__assign({}, dateSpanMeta), selection); + if (!selectionAllow(buildDateSpanApiWithContext(fullDateSpan, context), null)) { + return false; + } + } + return true; + } + // Constraint Utils + // ------------------------------------------------------------------------------------------------------------------------ + function allConstraintsPass(constraints, subjectRange, otherEventStore, businessHoursUnexpanded, context) { + for (var _i = 0, constraints_1 = constraints; _i < constraints_1.length; _i++) { + var constraint = constraints_1[_i]; + if (!anyRangesContainRange(constraintToRanges(constraint, subjectRange, otherEventStore, businessHoursUnexpanded, context), subjectRange)) { + return false; + } + } + return true; + } + function constraintToRanges(constraint, subjectRange, // for expanding a recurring constraint, or expanding business hours + otherEventStore, // for if constraint is an even group ID + businessHoursUnexpanded, // for if constraint is 'businessHours' + context) { + if (constraint === 'businessHours') { + return eventStoreToRanges(expandRecurring(businessHoursUnexpanded, subjectRange, context)); + } + if (typeof constraint === 'string') { // an group ID + return eventStoreToRanges(filterEventStoreDefs(otherEventStore, function (eventDef) { return eventDef.groupId === constraint; })); + } + if (typeof constraint === 'object' && constraint) { // non-null object + return eventStoreToRanges(expandRecurring(constraint, subjectRange, context)); + } + return []; // if it's false + } + // TODO: move to event-store file? + function eventStoreToRanges(eventStore) { + var instances = eventStore.instances; + var ranges = []; + for (var instanceId in instances) { + ranges.push(instances[instanceId].range); + } + return ranges; + } + // TODO: move to geom file? + function anyRangesContainRange(outerRanges, innerRange) { + for (var _i = 0, outerRanges_1 = outerRanges; _i < outerRanges_1.length; _i++) { + var outerRange = outerRanges_1[_i]; + if (rangeContainsRange(outerRange, innerRange)) { + return true; + } + } + return false; + } + + var VISIBLE_HIDDEN_RE = /^(visible|hidden)$/; + var Scroller = /** @class */ (function (_super) { + __extends(Scroller, _super); + function Scroller() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handleEl = function (el) { + _this.el = el; + setRef(_this.props.elRef, el); + }; + return _this; + } + Scroller.prototype.render = function () { + var props = this.props; + var liquid = props.liquid, liquidIsAbsolute = props.liquidIsAbsolute; + var isAbsolute = liquid && liquidIsAbsolute; + var className = ['fc-scroller']; + if (liquid) { + if (liquidIsAbsolute) { + className.push('fc-scroller-liquid-absolute'); + } + else { + className.push('fc-scroller-liquid'); + } + } + return (createElement("div", { ref: this.handleEl, className: className.join(' '), style: { + overflowX: props.overflowX, + overflowY: props.overflowY, + left: (isAbsolute && -(props.overcomeLeft || 0)) || '', + right: (isAbsolute && -(props.overcomeRight || 0)) || '', + bottom: (isAbsolute && -(props.overcomeBottom || 0)) || '', + marginLeft: (!isAbsolute && -(props.overcomeLeft || 0)) || '', + marginRight: (!isAbsolute && -(props.overcomeRight || 0)) || '', + marginBottom: (!isAbsolute && -(props.overcomeBottom || 0)) || '', + maxHeight: props.maxHeight || '', + } }, props.children)); + }; + Scroller.prototype.needsXScrolling = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) { + return false; + } + // testing scrollWidth>clientWidth is unreliable cross-browser when pixel heights aren't integers. + // much more reliable to see if children are taller than the scroller, even tho doesn't account for + // inner-child margins and absolute positioning + var el = this.el; + var realClientWidth = this.el.getBoundingClientRect().width - this.getYScrollbarWidth(); + var children = el.children; + for (var i = 0; i < children.length; i += 1) { + var childEl = children[i]; + if (childEl.getBoundingClientRect().width > realClientWidth) { + return true; + } + } + return false; + }; + Scroller.prototype.needsYScrolling = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) { + return false; + } + // testing scrollHeight>clientHeight is unreliable cross-browser when pixel heights aren't integers. + // much more reliable to see if children are taller than the scroller, even tho doesn't account for + // inner-child margins and absolute positioning + var el = this.el; + var realClientHeight = this.el.getBoundingClientRect().height - this.getXScrollbarWidth(); + var children = el.children; + for (var i = 0; i < children.length; i += 1) { + var childEl = children[i]; + if (childEl.getBoundingClientRect().height > realClientHeight) { + return true; + } + } + return false; + }; + Scroller.prototype.getXScrollbarWidth = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowX)) { + return 0; + } + return this.el.offsetHeight - this.el.clientHeight; // only works because we guarantee no borders. TODO: add to CSS with important? + }; + Scroller.prototype.getYScrollbarWidth = function () { + if (VISIBLE_HIDDEN_RE.test(this.props.overflowY)) { + return 0; + } + return this.el.offsetWidth - this.el.clientWidth; // only works because we guarantee no borders. TODO: add to CSS with important? + }; + return Scroller; + }(BaseComponent)); + + /* + TODO: somehow infer OtherArgs from masterCallback? + TODO: infer RefType from masterCallback if provided + */ + var RefMap = /** @class */ (function () { + function RefMap(masterCallback) { + var _this = this; + this.masterCallback = masterCallback; + this.currentMap = {}; + this.depths = {}; + this.callbackMap = {}; + this.handleValue = function (val, key) { + var _a = _this, depths = _a.depths, currentMap = _a.currentMap; + var removed = false; + var added = false; + if (val !== null) { + // for bug... ACTUALLY: can probably do away with this now that callers don't share numeric indices anymore + removed = (key in currentMap); + currentMap[key] = val; + depths[key] = (depths[key] || 0) + 1; + added = true; + } + else { + depths[key] -= 1; + if (!depths[key]) { + delete currentMap[key]; + delete _this.callbackMap[key]; + removed = true; + } + } + if (_this.masterCallback) { + if (removed) { + _this.masterCallback(null, String(key)); + } + if (added) { + _this.masterCallback(val, String(key)); + } + } + }; + } + RefMap.prototype.createRef = function (key) { + var _this = this; + var refCallback = this.callbackMap[key]; + if (!refCallback) { + refCallback = this.callbackMap[key] = function (val) { + _this.handleValue(val, String(key)); + }; + } + return refCallback; + }; + // TODO: check callers that don't care about order. should use getAll instead + // NOTE: this method has become less valuable now that we are encouraged to map order by some other index + // TODO: provide ONE array-export function, buildArray, which fails on non-numeric indexes. caller can manipulate and "collect" + RefMap.prototype.collect = function (startIndex, endIndex, step) { + return collectFromHash(this.currentMap, startIndex, endIndex, step); + }; + RefMap.prototype.getAll = function () { + return hashValuesToArray(this.currentMap); + }; + return RefMap; + }()); + + function computeShrinkWidth(chunkEls) { + var shrinkCells = findElements(chunkEls, '.fc-scrollgrid-shrink'); + var largestWidth = 0; + for (var _i = 0, shrinkCells_1 = shrinkCells; _i < shrinkCells_1.length; _i++) { + var shrinkCell = shrinkCells_1[_i]; + largestWidth = Math.max(largestWidth, computeSmallestCellWidth(shrinkCell)); + } + return Math.ceil(largestWidth); // elements work best with integers. round up to ensure contents fits + } + function getSectionHasLiquidHeight(props, sectionConfig) { + return props.liquid && sectionConfig.liquid; // does the section do liquid-height? (need to have whole scrollgrid liquid-height as well) + } + function getAllowYScrolling(props, sectionConfig) { + return sectionConfig.maxHeight != null || // if its possible for the height to max out, we might need scrollbars + getSectionHasLiquidHeight(props, sectionConfig); // if the section is liquid height, it might condense enough to require scrollbars + } + // TODO: ONLY use `arg`. force out internal function to use same API + function renderChunkContent(sectionConfig, chunkConfig, arg) { + var expandRows = arg.expandRows; + var content = typeof chunkConfig.content === 'function' ? + chunkConfig.content(arg) : + createElement('table', { + className: [ + chunkConfig.tableClassName, + sectionConfig.syncRowHeights ? 'fc-scrollgrid-sync-table' : '', + ].join(' '), + style: { + minWidth: arg.tableMinWidth, + width: arg.clientWidth, + height: expandRows ? arg.clientHeight : '', // css `height` on a
serves as a min-height + }, + }, arg.tableColGroupNode, createElement('tbody', {}, typeof chunkConfig.rowContent === 'function' ? chunkConfig.rowContent(arg) : chunkConfig.rowContent)); + return content; + } + function isColPropsEqual(cols0, cols1) { + return isArraysEqual(cols0, cols1, isPropsEqual); + } + function renderMicroColGroup(cols, shrinkWidth) { + var colNodes = []; + /* + for ColProps with spans, it would have been great to make a single + HOWEVER, Chrome was getting messing up distributing the width to elements makes Chrome behave. + */ + for (var _i = 0, cols_1 = cols; _i < cols_1.length; _i++) { + var colProps = cols_1[_i]; + var span = colProps.span || 1; + for (var i = 0; i < span; i += 1) { + colNodes.push(createElement("col", { style: { + width: colProps.width === 'shrink' ? sanitizeShrinkWidth(shrinkWidth) : (colProps.width || ''), + minWidth: colProps.minWidth || '', + } })); + } + } + return createElement.apply(void 0, __spreadArray(['colgroup', {}], colNodes)); + } + function sanitizeShrinkWidth(shrinkWidth) { + /* why 4? if we do 0, it will kill any border, which are needed for computeSmallestCellWidth + 4 accounts for 2 2-pixel borders. TODO: better solution? */ + return shrinkWidth == null ? 4 : shrinkWidth; + } + function hasShrinkWidth(cols) { + for (var _i = 0, cols_2 = cols; _i < cols_2.length; _i++) { + var col = cols_2[_i]; + if (col.width === 'shrink') { + return true; + } + } + return false; + } + function getScrollGridClassNames(liquid, context) { + var classNames = [ + 'fc-scrollgrid', + context.theme.getClass('table'), + ]; + if (liquid) { + classNames.push('fc-scrollgrid-liquid'); + } + return classNames; + } + function getSectionClassNames(sectionConfig, wholeTableVGrow) { + var classNames = [ + 'fc-scrollgrid-section', + "fc-scrollgrid-section-" + sectionConfig.type, + sectionConfig.className, // used? + ]; + if (wholeTableVGrow && sectionConfig.liquid && sectionConfig.maxHeight == null) { + classNames.push('fc-scrollgrid-section-liquid'); + } + if (sectionConfig.isSticky) { + classNames.push('fc-scrollgrid-section-sticky'); + } + return classNames; + } + function renderScrollShim(arg) { + return (createElement("div", { className: "fc-scrollgrid-sticky-shim", style: { + width: arg.clientWidth, + minWidth: arg.tableMinWidth, + } })); + } + function getStickyHeaderDates(options) { + var stickyHeaderDates = options.stickyHeaderDates; + if (stickyHeaderDates == null || stickyHeaderDates === 'auto') { + stickyHeaderDates = options.height === 'auto' || options.viewHeight === 'auto'; + } + return stickyHeaderDates; + } + function getStickyFooterScrollbar(options) { + var stickyFooterScrollbar = options.stickyFooterScrollbar; + if (stickyFooterScrollbar == null || stickyFooterScrollbar === 'auto') { + stickyFooterScrollbar = options.height === 'auto' || options.viewHeight === 'auto'; + } + return stickyFooterScrollbar; + } + + var SimpleScrollGrid = /** @class */ (function (_super) { + __extends(SimpleScrollGrid, _super); + function SimpleScrollGrid() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.processCols = memoize(function (a) { return a; }, isColPropsEqual); // so we get same `cols` props every time + // yucky to memoize VNodes, but much more efficient for consumers + _this.renderMicroColGroup = memoize(renderMicroColGroup); + _this.scrollerRefs = new RefMap(); + _this.scrollerElRefs = new RefMap(_this._handleScrollerEl.bind(_this)); + _this.state = { + shrinkWidth: null, + forceYScrollbars: false, + scrollerClientWidths: {}, + scrollerClientHeights: {}, + }; + // TODO: can do a really simple print-view. dont need to join rows + _this.handleSizing = function () { + _this.setState(__assign({ shrinkWidth: _this.computeShrinkWidth() }, _this.computeScrollerDims())); + }; + return _this; + } + SimpleScrollGrid.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state, context = _a.context; + var sectionConfigs = props.sections || []; + var cols = this.processCols(props.cols); + var microColGroupNode = this.renderMicroColGroup(cols, state.shrinkWidth); + var classNames = getScrollGridClassNames(props.liquid, context); + if (props.collapsibleWidth) { + classNames.push('fc-scrollgrid-collapsible'); + } + // TODO: make DRY + var configCnt = sectionConfigs.length; + var configI = 0; + var currentConfig; + var headSectionNodes = []; + var bodySectionNodes = []; + var footSectionNodes = []; + while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'header') { + headSectionNodes.push(this.renderSection(currentConfig, microColGroupNode)); + configI += 1; + } + while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'body') { + bodySectionNodes.push(this.renderSection(currentConfig, microColGroupNode)); + configI += 1; + } + while (configI < configCnt && (currentConfig = sectionConfigs[configI]).type === 'footer') { + footSectionNodes.push(this.renderSection(currentConfig, microColGroupNode)); + configI += 1; + } + // firefox bug: when setting height on table and there is a thead or tfoot, + // the necessary height:100% on the liquid-height body section forces the *whole* table to be taller. (bug #5524) + // use getCanVGrowWithinCell as a way to detect table-stupid firefox. + // if so, use a simpler dom structure, jam everything into a lone tbody. + var isBuggy = !getCanVGrowWithinCell(); + return createElement('table', { + className: classNames.join(' '), + style: { height: props.height }, + }, Boolean(!isBuggy && headSectionNodes.length) && createElement.apply(void 0, __spreadArray(['thead', {}], headSectionNodes)), Boolean(!isBuggy && bodySectionNodes.length) && createElement.apply(void 0, __spreadArray(['tbody', {}], bodySectionNodes)), Boolean(!isBuggy && footSectionNodes.length) && createElement.apply(void 0, __spreadArray(['tfoot', {}], footSectionNodes)), isBuggy && createElement.apply(void 0, __spreadArray(__spreadArray(__spreadArray(['tbody', {}], headSectionNodes), bodySectionNodes), footSectionNodes))); + }; + SimpleScrollGrid.prototype.renderSection = function (sectionConfig, microColGroupNode) { + if ('outerContent' in sectionConfig) { + return (createElement(Fragment, { key: sectionConfig.key }, sectionConfig.outerContent)); + } + return (createElement("tr", { key: sectionConfig.key, className: getSectionClassNames(sectionConfig, this.props.liquid).join(' ') }, this.renderChunkTd(sectionConfig, microColGroupNode, sectionConfig.chunk))); + }; + SimpleScrollGrid.prototype.renderChunkTd = function (sectionConfig, microColGroupNode, chunkConfig) { + if ('outerContent' in chunkConfig) { + return chunkConfig.outerContent; + } + var props = this.props; + var _a = this.state, forceYScrollbars = _a.forceYScrollbars, scrollerClientWidths = _a.scrollerClientWidths, scrollerClientHeights = _a.scrollerClientHeights; + var needsYScrolling = getAllowYScrolling(props, sectionConfig); // TODO: do lazily. do in section config? + var isLiquid = getSectionHasLiquidHeight(props, sectionConfig); + // for `!props.liquid` - is WHOLE scrollgrid natural height? + // TODO: do same thing in advanced scrollgrid? prolly not b/c always has horizontal scrollbars + var overflowY = !props.liquid ? 'visible' : + forceYScrollbars ? 'scroll' : + !needsYScrolling ? 'hidden' : + 'auto'; + var sectionKey = sectionConfig.key; + var content = renderChunkContent(sectionConfig, chunkConfig, { + tableColGroupNode: microColGroupNode, + tableMinWidth: '', + clientWidth: (!props.collapsibleWidth && scrollerClientWidths[sectionKey] !== undefined) ? scrollerClientWidths[sectionKey] : null, + clientHeight: scrollerClientHeights[sectionKey] !== undefined ? scrollerClientHeights[sectionKey] : null, + expandRows: sectionConfig.expandRows, + syncRowHeights: false, + rowSyncHeights: [], + reportRowHeightChange: function () { }, + }); + return (createElement("td", { ref: chunkConfig.elRef }, + createElement("div", { className: "fc-scroller-harness" + (isLiquid ? ' fc-scroller-harness-liquid' : '') }, + createElement(Scroller, { ref: this.scrollerRefs.createRef(sectionKey), elRef: this.scrollerElRefs.createRef(sectionKey), overflowY: overflowY, overflowX: !props.liquid ? 'visible' : 'hidden' /* natural height? */, maxHeight: sectionConfig.maxHeight, liquid: isLiquid, liquidIsAbsolute // because its within a harness + : true }, content)))); + }; + SimpleScrollGrid.prototype._handleScrollerEl = function (scrollerEl, key) { + var section = getSectionByKey(this.props.sections, key); + if (section) { + setRef(section.chunk.scrollerElRef, scrollerEl); + } + }; + SimpleScrollGrid.prototype.componentDidMount = function () { + this.handleSizing(); + this.context.addResizeHandler(this.handleSizing); + }; + SimpleScrollGrid.prototype.componentDidUpdate = function () { + // TODO: need better solution when state contains non-sizing things + this.handleSizing(); + }; + SimpleScrollGrid.prototype.componentWillUnmount = function () { + this.context.removeResizeHandler(this.handleSizing); + }; + SimpleScrollGrid.prototype.computeShrinkWidth = function () { + return hasShrinkWidth(this.props.cols) + ? computeShrinkWidth(this.scrollerElRefs.getAll()) + : 0; + }; + SimpleScrollGrid.prototype.computeScrollerDims = function () { + var scrollbarWidth = getScrollbarWidths(); + var _a = this, scrollerRefs = _a.scrollerRefs, scrollerElRefs = _a.scrollerElRefs; + var forceYScrollbars = false; + var scrollerClientWidths = {}; + var scrollerClientHeights = {}; + for (var sectionKey in scrollerRefs.currentMap) { + var scroller = scrollerRefs.currentMap[sectionKey]; + if (scroller && scroller.needsYScrolling()) { + forceYScrollbars = true; + break; + } + } + for (var _i = 0, _b = this.props.sections; _i < _b.length; _i++) { + var section = _b[_i]; + var sectionKey = section.key; + var scrollerEl = scrollerElRefs.currentMap[sectionKey]; + if (scrollerEl) { + var harnessEl = scrollerEl.parentNode; // TODO: weird way to get this. need harness b/c doesn't include table borders + scrollerClientWidths[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().width - (forceYScrollbars + ? scrollbarWidth.y // use global because scroller might not have scrollbars yet but will need them in future + : 0)); + scrollerClientHeights[sectionKey] = Math.floor(harnessEl.getBoundingClientRect().height); + } + } + return { forceYScrollbars: forceYScrollbars, scrollerClientWidths: scrollerClientWidths, scrollerClientHeights: scrollerClientHeights }; + }; + return SimpleScrollGrid; + }(BaseComponent)); + SimpleScrollGrid.addStateEquality({ + scrollerClientWidths: isPropsEqual, + scrollerClientHeights: isPropsEqual, + }); + function getSectionByKey(sections, key) { + for (var _i = 0, sections_1 = sections; _i < sections_1.length; _i++) { + var section = sections_1[_i]; + if (section.key === key) { + return section; + } + } + return null; + } + + var EventRoot = /** @class */ (function (_super) { + __extends(EventRoot, _super); + function EventRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.elRef = createRef(); + return _this; + } + EventRoot.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var seg = props.seg; + var eventRange = seg.eventRange; + var ui = eventRange.ui; + var hookProps = { + event: new EventApi(context, eventRange.def, eventRange.instance), + view: context.viewApi, + timeText: props.timeText, + textColor: ui.textColor, + backgroundColor: ui.backgroundColor, + borderColor: ui.borderColor, + isDraggable: !props.disableDragging && computeSegDraggable(seg, context), + isStartResizable: !props.disableResizing && computeSegStartResizable(seg, context), + isEndResizable: !props.disableResizing && computeSegEndResizable(seg), + isMirror: Boolean(props.isDragging || props.isResizing || props.isDateSelecting), + isStart: Boolean(seg.isStart), + isEnd: Boolean(seg.isEnd), + isPast: Boolean(props.isPast), + isFuture: Boolean(props.isFuture), + isToday: Boolean(props.isToday), + isSelected: Boolean(props.isSelected), + isDragging: Boolean(props.isDragging), + isResizing: Boolean(props.isResizing), + }; + var standardClassNames = getEventClassNames(hookProps).concat(ui.classNames); + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.eventClassNames, content: options.eventContent, defaultContent: props.defaultContent, didMount: options.eventDidMount, willUnmount: options.eventWillUnmount, elRef: this.elRef }, function (rootElRef, customClassNames, innerElRef, innerContent) { return props.children(rootElRef, standardClassNames.concat(customClassNames), innerElRef, innerContent, hookProps); })); + }; + EventRoot.prototype.componentDidMount = function () { + setElSeg(this.elRef.current, this.props.seg); + }; + /* + need to re-assign seg to the element if seg changes, even if the element is the same + */ + EventRoot.prototype.componentDidUpdate = function (prevProps) { + var seg = this.props.seg; + if (seg !== prevProps.seg) { + setElSeg(this.elRef.current, seg); + } + }; + return EventRoot; + }(BaseComponent)); + + // should not be a purecomponent + var StandardEvent = /** @class */ (function (_super) { + __extends(StandardEvent, _super); + function StandardEvent() { + return _super !== null && _super.apply(this, arguments) || this; + } + StandardEvent.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var seg = props.seg; + var timeFormat = context.options.eventTimeFormat || props.defaultTimeFormat; + var timeText = buildSegTimeText(seg, timeFormat, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd); + return (createElement(EventRoot, { seg: seg, timeText: timeText, disableDragging: props.disableDragging, disableResizing: props.disableResizing, defaultContent: props.defaultContent || renderInnerContent$4, isDragging: props.isDragging, isResizing: props.isResizing, isDateSelecting: props.isDateSelecting, isSelected: props.isSelected, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("a", __assign({ className: props.extraClassNames.concat(classNames).join(' '), style: { + borderColor: hookProps.borderColor, + backgroundColor: hookProps.backgroundColor, + }, ref: rootElRef }, getSegAnchorAttrs$1(seg)), + createElement("div", { className: "fc-event-main", ref: innerElRef, style: { color: hookProps.textColor } }, innerContent), + hookProps.isStartResizable && + createElement("div", { className: "fc-event-resizer fc-event-resizer-start" }), + hookProps.isEndResizable && + createElement("div", { className: "fc-event-resizer fc-event-resizer-end" }))); })); + }; + return StandardEvent; + }(BaseComponent)); + function renderInnerContent$4(innerProps) { + return (createElement("div", { className: "fc-event-main-frame" }, + innerProps.timeText && (createElement("div", { className: "fc-event-time" }, innerProps.timeText)), + createElement("div", { className: "fc-event-title-container" }, + createElement("div", { className: "fc-event-title fc-sticky" }, innerProps.event.title || createElement(Fragment, null, "\u00A0"))))); + } + function getSegAnchorAttrs$1(seg) { + var url = seg.eventRange.def.url; + return url ? { href: url } : {}; + } + + var NowIndicatorRoot = function (props) { return (createElement(ViewContextType.Consumer, null, function (context) { + var options = context.options; + var hookProps = { + isAxis: props.isAxis, + date: context.dateEnv.toDate(props.date), + view: context.viewApi, + }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.nowIndicatorClassNames, content: options.nowIndicatorContent, didMount: options.nowIndicatorDidMount, willUnmount: options.nowIndicatorWillUnmount }, props.children)); + })); }; + + var DAY_NUM_FORMAT = createFormatter({ day: 'numeric' }); + var DayCellContent = /** @class */ (function (_super) { + __extends(DayCellContent, _super); + function DayCellContent() { + return _super !== null && _super.apply(this, arguments) || this; + } + DayCellContent.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var hookProps = refineDayCellHookProps({ + date: props.date, + dateProfile: props.dateProfile, + todayRange: props.todayRange, + showDayNumber: props.showDayNumber, + extraProps: props.extraHookProps, + viewApi: context.viewApi, + dateEnv: context.dateEnv, + }); + return (createElement(ContentHook, { hookProps: hookProps, content: options.dayCellContent, defaultContent: props.defaultContent }, props.children)); + }; + return DayCellContent; + }(BaseComponent)); + function refineDayCellHookProps(raw) { + var date = raw.date, dateEnv = raw.dateEnv; + var dayMeta = getDateMeta(date, raw.todayRange, null, raw.dateProfile); + return __assign(__assign(__assign({ date: dateEnv.toDate(date), view: raw.viewApi }, dayMeta), { dayNumberText: raw.showDayNumber ? dateEnv.format(date, DAY_NUM_FORMAT) : '' }), raw.extraProps); + } + + var DayCellRoot = /** @class */ (function (_super) { + __extends(DayCellRoot, _super); + function DayCellRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.refineHookProps = memoizeObjArg(refineDayCellHookProps); + _this.normalizeClassNames = buildClassNameNormalizer(); + return _this; + } + DayCellRoot.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var hookProps = this.refineHookProps({ + date: props.date, + dateProfile: props.dateProfile, + todayRange: props.todayRange, + showDayNumber: props.showDayNumber, + extraProps: props.extraHookProps, + viewApi: context.viewApi, + dateEnv: context.dateEnv, + }); + var classNames = getDayClassNames(hookProps, context.theme).concat(hookProps.isDisabled + ? [] // don't use custom classNames if disabled + : this.normalizeClassNames(options.dayCellClassNames, hookProps)); + var dataAttrs = hookProps.isDisabled ? {} : { + 'data-date': formatDayString(props.date), + }; + return (createElement(MountHook, { hookProps: hookProps, didMount: options.dayCellDidMount, willUnmount: options.dayCellWillUnmount, elRef: props.elRef }, function (rootElRef) { return props.children(rootElRef, classNames, dataAttrs, hookProps.isDisabled); })); + }; + return DayCellRoot; + }(BaseComponent)); + + function renderFill(fillType) { + return (createElement("div", { className: "fc-" + fillType })); + } + var BgEvent = function (props) { return (createElement(EventRoot, { defaultContent: renderInnerContent$3, seg: props.seg /* uselesss i think */, timeText: "", disableDragging: true, disableResizing: true, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("div", { ref: rootElRef, className: ['fc-bg-event'].concat(classNames).join(' '), style: { + backgroundColor: hookProps.backgroundColor, + } }, innerContent)); })); }; + function renderInnerContent$3(props) { + var title = props.event.title; + return title && (createElement("div", { className: "fc-event-title" }, props.event.title)); + } + + var WeekNumberRoot = function (props) { return (createElement(ViewContextType.Consumer, null, function (context) { + var dateEnv = context.dateEnv, options = context.options; + var date = props.date; + var format = options.weekNumberFormat || props.defaultFormat; + var num = dateEnv.computeWeekNumber(date); // TODO: somehow use for formatting as well? + var text = dateEnv.format(date, format); + var hookProps = { num: num, text: text, date: date }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.weekNumberClassNames, content: options.weekNumberContent, defaultContent: renderInner, didMount: options.weekNumberDidMount, willUnmount: options.weekNumberWillUnmount }, props.children)); + })); }; + function renderInner(innerProps) { + return innerProps.text; + } + + var PADDING_FROM_VIEWPORT = 10; + var Popover = /** @class */ (function (_super) { + __extends(Popover, _super); + function Popover() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handleRootEl = function (el) { + _this.rootEl = el; + if (_this.props.elRef) { + setRef(_this.props.elRef, el); + } + }; + // Triggered when the user clicks *anywhere* in the document, for the autoHide feature + _this.handleDocumentMousedown = function (ev) { + // only hide the popover if the click happened outside the popover + var target = getEventTargetViaRoot(ev); + if (!_this.rootEl.contains(target)) { + _this.handleCloseClick(); + } + }; + _this.handleCloseClick = function () { + var onClose = _this.props.onClose; + if (onClose) { + onClose(); + } + }; + return _this; + } + Popover.prototype.render = function () { + var theme = this.context.theme; + var props = this.props; + var classNames = [ + 'fc-popover', + theme.getClass('popover'), + ].concat(props.extraClassNames || []); + return createPortal(createElement("div", __assign({ className: classNames.join(' ') }, props.extraAttrs, { ref: this.handleRootEl }), + createElement("div", { className: 'fc-popover-header ' + theme.getClass('popoverHeader') }, + createElement("span", { className: "fc-popover-title" }, props.title), + createElement("span", { className: 'fc-popover-close ' + theme.getIconClass('close'), onClick: this.handleCloseClick })), + createElement("div", { className: 'fc-popover-body ' + theme.getClass('popoverContent') }, props.children)), props.parentEl); + }; + Popover.prototype.componentDidMount = function () { + document.addEventListener('mousedown', this.handleDocumentMousedown); + this.updateSize(); + }; + Popover.prototype.componentWillUnmount = function () { + document.removeEventListener('mousedown', this.handleDocumentMousedown); + }; + Popover.prototype.updateSize = function () { + var isRtl = this.context.isRtl; + var _a = this.props, alignmentEl = _a.alignmentEl, alignGridTop = _a.alignGridTop; + var rootEl = this.rootEl; + var alignmentRect = computeClippedClientRect(alignmentEl); + if (alignmentRect) { + var popoverDims = rootEl.getBoundingClientRect(); + // position relative to viewport + var popoverTop = alignGridTop + ? elementClosest(alignmentEl, '.fc-scrollgrid').getBoundingClientRect().top + : alignmentRect.top; + var popoverLeft = isRtl ? alignmentRect.right - popoverDims.width : alignmentRect.left; + // constrain + popoverTop = Math.max(popoverTop, PADDING_FROM_VIEWPORT); + popoverLeft = Math.min(popoverLeft, document.documentElement.clientWidth - PADDING_FROM_VIEWPORT - popoverDims.width); + popoverLeft = Math.max(popoverLeft, PADDING_FROM_VIEWPORT); + var origin_1 = rootEl.offsetParent.getBoundingClientRect(); + applyStyle(rootEl, { + top: popoverTop - origin_1.top, + left: popoverLeft - origin_1.left, + }); + } + }; + return Popover; + }(BaseComponent)); + + var MorePopover = /** @class */ (function (_super) { + __extends(MorePopover, _super); + function MorePopover() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.handleRootEl = function (rootEl) { + _this.rootEl = rootEl; + if (rootEl) { + _this.context.registerInteractiveComponent(_this, { + el: rootEl, + useEventCenter: false, + }); + } + else { + _this.context.unregisterInteractiveComponent(_this); + } + }; + return _this; + } + MorePopover.prototype.render = function () { + var _a = this.context, options = _a.options, dateEnv = _a.dateEnv; + var props = this.props; + var startDate = props.startDate, todayRange = props.todayRange, dateProfile = props.dateProfile; + var title = dateEnv.format(startDate, options.dayPopoverFormat); + return (createElement(DayCellRoot, { date: startDate, dateProfile: dateProfile, todayRange: todayRange, elRef: this.handleRootEl }, function (rootElRef, dayClassNames, dataAttrs) { return (createElement(Popover, { elRef: rootElRef, title: title, extraClassNames: ['fc-more-popover'].concat(dayClassNames), extraAttrs: dataAttrs /* TODO: make these time-based when not whole-day? */, parentEl: props.parentEl, alignmentEl: props.alignmentEl, alignGridTop: props.alignGridTop, onClose: props.onClose }, + createElement(DayCellContent, { date: startDate, dateProfile: dateProfile, todayRange: todayRange }, function (innerElRef, innerContent) { return (innerContent && + createElement("div", { className: "fc-more-popover-misc", ref: innerElRef }, innerContent)); }), + props.children)); })); + }; + MorePopover.prototype.queryHit = function (positionLeft, positionTop, elWidth, elHeight) { + var _a = this, rootEl = _a.rootEl, props = _a.props; + if (positionLeft >= 0 && positionLeft < elWidth && + positionTop >= 0 && positionTop < elHeight) { + return { + dateProfile: props.dateProfile, + dateSpan: __assign({ allDay: true, range: { + start: props.startDate, + end: props.endDate, + } }, props.extraDateSpan), + dayEl: rootEl, + rect: { + left: 0, + top: 0, + right: elWidth, + bottom: elHeight, + }, + layer: 1, // important when comparing with hits from other components + }; + } + return null; + }; + return MorePopover; + }(DateComponent)); + + var MoreLinkRoot = /** @class */ (function (_super) { + __extends(MoreLinkRoot, _super); + function MoreLinkRoot() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.linkElRef = createRef(); + _this.state = { + isPopoverOpen: false, + }; + _this.handleClick = function (ev) { + var _a = _this, props = _a.props, context = _a.context; + var moreLinkClick = context.options.moreLinkClick; + var date = computeRange(props).start; + function buildPublicSeg(seg) { + var _a = seg.eventRange, def = _a.def, instance = _a.instance, range = _a.range; + return { + event: new EventApi(context, def, instance), + start: context.dateEnv.toDate(range.start), + end: context.dateEnv.toDate(range.end), + isStart: seg.isStart, + isEnd: seg.isEnd, + }; + } + if (typeof moreLinkClick === 'function') { + moreLinkClick = moreLinkClick({ + date: date, + allDay: Boolean(props.allDayDate), + allSegs: props.allSegs.map(buildPublicSeg), + hiddenSegs: props.hiddenSegs.map(buildPublicSeg), + jsEvent: ev, + view: context.viewApi, + }); + } + if (!moreLinkClick || moreLinkClick === 'popover') { + _this.setState({ isPopoverOpen: true }); + } + else if (typeof moreLinkClick === 'string') { // a view name + context.calendarApi.zoomTo(date, moreLinkClick); + } + }; + _this.handlePopoverClose = function () { + _this.setState({ isPopoverOpen: false }); + }; + return _this; + } + MoreLinkRoot.prototype.render = function () { + var _this = this; + var props = this.props; + return (createElement(ViewContextType.Consumer, null, function (context) { + var viewApi = context.viewApi, options = context.options, calendarApi = context.calendarApi; + var moreLinkText = options.moreLinkText; + var moreCnt = props.moreCnt; + var range = computeRange(props); + var hookProps = { + num: moreCnt, + shortText: "+" + moreCnt, + text: typeof moreLinkText === 'function' + ? moreLinkText.call(calendarApi, moreCnt) + : "+" + moreCnt + " " + moreLinkText, + view: viewApi, + }; + return (createElement(Fragment, null, + Boolean(props.moreCnt) && (createElement(RenderHook, { elRef: _this.linkElRef, hookProps: hookProps, classNames: options.moreLinkClassNames, content: options.moreLinkContent, defaultContent: props.defaultContent || renderMoreLinkInner$1, didMount: options.moreLinkDidMount, willUnmount: options.moreLinkWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return props.children(rootElRef, ['fc-more-link'].concat(customClassNames), innerElRef, innerContent, _this.handleClick); })), + _this.state.isPopoverOpen && (createElement(MorePopover, { startDate: range.start, endDate: range.end, dateProfile: props.dateProfile, todayRange: props.todayRange, extraDateSpan: props.extraDateSpan, parentEl: _this.parentEl, alignmentEl: props.alignmentElRef.current, alignGridTop: props.alignGridTop, onClose: _this.handlePopoverClose }, props.popoverContent())))); + })); + }; + MoreLinkRoot.prototype.componentDidMount = function () { + this.updateParentEl(); + }; + MoreLinkRoot.prototype.componentDidUpdate = function () { + this.updateParentEl(); + }; + MoreLinkRoot.prototype.updateParentEl = function () { + if (this.linkElRef.current) { + this.parentEl = elementClosest(this.linkElRef.current, '.fc-view-harness'); + } + }; + return MoreLinkRoot; + }(BaseComponent)); + function renderMoreLinkInner$1(props) { + return props.text; + } + function computeRange(props) { + if (props.allDayDate) { + return { + start: props.allDayDate, + end: addDays(props.allDayDate, 1), + }; + } + var hiddenSegs = props.hiddenSegs; + return { + start: computeEarliestSegStart(hiddenSegs), + end: computeLatestSegEnd(hiddenSegs), + }; + } + function computeEarliestSegStart(segs) { + return segs.reduce(pickEarliestStart).eventRange.range.start; + } + function pickEarliestStart(seg0, seg1) { + return seg0.eventRange.range.start < seg1.eventRange.range.start ? seg0 : seg1; + } + function computeLatestSegEnd(segs) { + return segs.reduce(pickLatestEnd).eventRange.range.end; + } + function pickLatestEnd(seg0, seg1) { + return seg0.eventRange.range.end > seg1.eventRange.range.end ? seg0 : seg1; + } + + // exports + // -------------------------------------------------------------------------------------------------- + var version = '5.9.0'; // important to type it, so .d.ts has generic string + + var Calendar = /** @class */ (function (_super) { + __extends(Calendar, _super); + function Calendar(el, optionOverrides) { + if (optionOverrides === void 0) { optionOverrides = {}; } + var _this = _super.call(this) || this; + _this.isRendering = false; + _this.isRendered = false; + _this.currentClassNames = []; + _this.customContentRenderId = 0; // will affect custom generated classNames? + _this.handleAction = function (action) { + // actions we know we want to render immediately + switch (action.type) { + case 'SET_EVENT_DRAG': + case 'SET_EVENT_RESIZE': + _this.renderRunner.tryDrain(); + } + }; + _this.handleData = function (data) { + _this.currentData = data; + _this.renderRunner.request(data.calendarOptions.rerenderDelay); + }; + _this.handleRenderRequest = function () { + if (_this.isRendering) { + _this.isRendered = true; + var currentData_1 = _this.currentData; + render(createElement(CalendarRoot, { options: currentData_1.calendarOptions, theme: currentData_1.theme, emitter: currentData_1.emitter }, function (classNames, height, isHeightAuto, forPrint) { + _this.setClassNames(classNames); + _this.setHeight(height); + return (createElement(CustomContentRenderContext.Provider, { value: _this.customContentRenderId }, + createElement(CalendarContent, __assign({ isHeightAuto: isHeightAuto, forPrint: forPrint }, currentData_1)))); + }), _this.el); + } + else if (_this.isRendered) { + _this.isRendered = false; + unmountComponentAtNode(_this.el); + _this.setClassNames([]); + _this.setHeight(''); + } + flushToDom(); + }; + _this.el = el; + _this.renderRunner = new DelayedRunner(_this.handleRenderRequest); + new CalendarDataManager({ + optionOverrides: optionOverrides, + calendarApi: _this, + onAction: _this.handleAction, + onData: _this.handleData, + }); + return _this; + } + Object.defineProperty(Calendar.prototype, "view", { + get: function () { return this.currentData.viewApi; } // for public API + , + enumerable: false, + configurable: true + }); + Calendar.prototype.render = function () { + var wasRendering = this.isRendering; + if (!wasRendering) { + this.isRendering = true; + } + else { + this.customContentRenderId += 1; + } + this.renderRunner.request(); + if (wasRendering) { + this.updateSize(); + } + }; + Calendar.prototype.destroy = function () { + if (this.isRendering) { + this.isRendering = false; + this.renderRunner.request(); + } + }; + Calendar.prototype.updateSize = function () { + _super.prototype.updateSize.call(this); + flushToDom(); + }; + Calendar.prototype.batchRendering = function (func) { + this.renderRunner.pause('batchRendering'); + func(); + this.renderRunner.resume('batchRendering'); + }; + Calendar.prototype.pauseRendering = function () { + this.renderRunner.pause('pauseRendering'); + }; + Calendar.prototype.resumeRendering = function () { + this.renderRunner.resume('pauseRendering', true); + }; + Calendar.prototype.resetOptions = function (optionOverrides, append) { + this.currentDataManager.resetOptions(optionOverrides, append); + }; + Calendar.prototype.setClassNames = function (classNames) { + if (!isArraysEqual(classNames, this.currentClassNames)) { + var classList = this.el.classList; + for (var _i = 0, _a = this.currentClassNames; _i < _a.length; _i++) { + var className = _a[_i]; + classList.remove(className); + } + for (var _b = 0, classNames_1 = classNames; _b < classNames_1.length; _b++) { + var className = classNames_1[_b]; + classList.add(className); + } + this.currentClassNames = classNames; + } + }; + Calendar.prototype.setHeight = function (height) { + applyStyleProp(this.el, 'height', height); + }; + return Calendar; + }(CalendarApi)); + + config.touchMouseIgnoreWait = 500; + var ignoreMouseDepth = 0; + var listenerCnt = 0; + var isWindowTouchMoveCancelled = false; + /* + Uses a "pointer" abstraction, which monitors UI events for both mouse and touch. + Tracks when the pointer "drags" on a certain element, meaning down+move+up. + + Also, tracks if there was touch-scrolling. + Also, can prevent touch-scrolling from happening. + Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement. + + emits: + - pointerdown + - pointermove + - pointerup + */ + var PointerDragging = /** @class */ (function () { + function PointerDragging(containerEl) { + var _this = this; + this.subjectEl = null; + // options that can be directly assigned by caller + this.selector = ''; // will cause subjectEl in all emitted events to be this element + this.handleSelector = ''; + this.shouldIgnoreMove = false; + this.shouldWatchScroll = true; // for simulating pointermove on scroll + // internal states + this.isDragging = false; + this.isTouchDragging = false; + this.wasTouchScroll = false; + // Mouse + // ---------------------------------------------------------------------------------------------------- + this.handleMouseDown = function (ev) { + if (!_this.shouldIgnoreMouse() && + isPrimaryMouseButton(ev) && + _this.tryStart(ev)) { + var pev = _this.createEventFromMouse(ev, true); + _this.emitter.trigger('pointerdown', pev); + _this.initScrollWatch(pev); + if (!_this.shouldIgnoreMove) { + document.addEventListener('mousemove', _this.handleMouseMove); + } + document.addEventListener('mouseup', _this.handleMouseUp); + } + }; + this.handleMouseMove = function (ev) { + var pev = _this.createEventFromMouse(ev); + _this.recordCoords(pev); + _this.emitter.trigger('pointermove', pev); + }; + this.handleMouseUp = function (ev) { + document.removeEventListener('mousemove', _this.handleMouseMove); + document.removeEventListener('mouseup', _this.handleMouseUp); + _this.emitter.trigger('pointerup', _this.createEventFromMouse(ev)); + _this.cleanup(); // call last so that pointerup has access to props + }; + // Touch + // ---------------------------------------------------------------------------------------------------- + this.handleTouchStart = function (ev) { + if (_this.tryStart(ev)) { + _this.isTouchDragging = true; + var pev = _this.createEventFromTouch(ev, true); + _this.emitter.trigger('pointerdown', pev); + _this.initScrollWatch(pev); + // unlike mouse, need to attach to target, not document + // https://stackoverflow.com/a/45760014 + var targetEl = ev.target; + if (!_this.shouldIgnoreMove) { + targetEl.addEventListener('touchmove', _this.handleTouchMove); + } + targetEl.addEventListener('touchend', _this.handleTouchEnd); + targetEl.addEventListener('touchcancel', _this.handleTouchEnd); // treat it as a touch end + // attach a handler to get called when ANY scroll action happens on the page. + // this was impossible to do with normal on/off because 'scroll' doesn't bubble. + // http://stackoverflow.com/a/32954565/96342 + window.addEventListener('scroll', _this.handleTouchScroll, true); + } + }; + this.handleTouchMove = function (ev) { + var pev = _this.createEventFromTouch(ev); + _this.recordCoords(pev); + _this.emitter.trigger('pointermove', pev); + }; + this.handleTouchEnd = function (ev) { + if (_this.isDragging) { // done to guard against touchend followed by touchcancel + var targetEl = ev.target; + targetEl.removeEventListener('touchmove', _this.handleTouchMove); + targetEl.removeEventListener('touchend', _this.handleTouchEnd); + targetEl.removeEventListener('touchcancel', _this.handleTouchEnd); + window.removeEventListener('scroll', _this.handleTouchScroll, true); // useCaptured=true + _this.emitter.trigger('pointerup', _this.createEventFromTouch(ev)); + _this.cleanup(); // call last so that pointerup has access to props + _this.isTouchDragging = false; + startIgnoringMouse(); + } + }; + this.handleTouchScroll = function () { + _this.wasTouchScroll = true; + }; + this.handleScroll = function (ev) { + if (!_this.shouldIgnoreMove) { + var pageX = (window.pageXOffset - _this.prevScrollX) + _this.prevPageX; + var pageY = (window.pageYOffset - _this.prevScrollY) + _this.prevPageY; + _this.emitter.trigger('pointermove', { + origEvent: ev, + isTouch: _this.isTouchDragging, + subjectEl: _this.subjectEl, + pageX: pageX, + pageY: pageY, + deltaX: pageX - _this.origPageX, + deltaY: pageY - _this.origPageY, + }); + } + }; + this.containerEl = containerEl; + this.emitter = new Emitter(); + containerEl.addEventListener('mousedown', this.handleMouseDown); + containerEl.addEventListener('touchstart', this.handleTouchStart, { passive: true }); + listenerCreated(); + } + PointerDragging.prototype.destroy = function () { + this.containerEl.removeEventListener('mousedown', this.handleMouseDown); + this.containerEl.removeEventListener('touchstart', this.handleTouchStart, { passive: true }); + listenerDestroyed(); + }; + PointerDragging.prototype.tryStart = function (ev) { + var subjectEl = this.querySubjectEl(ev); + var downEl = ev.target; + if (subjectEl && + (!this.handleSelector || elementClosest(downEl, this.handleSelector))) { + this.subjectEl = subjectEl; + this.isDragging = true; // do this first so cancelTouchScroll will work + this.wasTouchScroll = false; + return true; + } + return false; + }; + PointerDragging.prototype.cleanup = function () { + isWindowTouchMoveCancelled = false; + this.isDragging = false; + this.subjectEl = null; + // keep wasTouchScroll around for later access + this.destroyScrollWatch(); + }; + PointerDragging.prototype.querySubjectEl = function (ev) { + if (this.selector) { + return elementClosest(ev.target, this.selector); + } + return this.containerEl; + }; + PointerDragging.prototype.shouldIgnoreMouse = function () { + return ignoreMouseDepth || this.isTouchDragging; + }; + // can be called by user of this class, to cancel touch-based scrolling for the current drag + PointerDragging.prototype.cancelTouchScroll = function () { + if (this.isDragging) { + isWindowTouchMoveCancelled = true; + } + }; + // Scrolling that simulates pointermoves + // ---------------------------------------------------------------------------------------------------- + PointerDragging.prototype.initScrollWatch = function (ev) { + if (this.shouldWatchScroll) { + this.recordCoords(ev); + window.addEventListener('scroll', this.handleScroll, true); // useCapture=true + } + }; + PointerDragging.prototype.recordCoords = function (ev) { + if (this.shouldWatchScroll) { + this.prevPageX = ev.pageX; + this.prevPageY = ev.pageY; + this.prevScrollX = window.pageXOffset; + this.prevScrollY = window.pageYOffset; + } + }; + PointerDragging.prototype.destroyScrollWatch = function () { + if (this.shouldWatchScroll) { + window.removeEventListener('scroll', this.handleScroll, true); // useCaptured=true + } + }; + // Event Normalization + // ---------------------------------------------------------------------------------------------------- + PointerDragging.prototype.createEventFromMouse = function (ev, isFirst) { + var deltaX = 0; + var deltaY = 0; + // TODO: repeat code + if (isFirst) { + this.origPageX = ev.pageX; + this.origPageY = ev.pageY; + } + else { + deltaX = ev.pageX - this.origPageX; + deltaY = ev.pageY - this.origPageY; + } + return { + origEvent: ev, + isTouch: false, + subjectEl: this.subjectEl, + pageX: ev.pageX, + pageY: ev.pageY, + deltaX: deltaX, + deltaY: deltaY, + }; + }; + PointerDragging.prototype.createEventFromTouch = function (ev, isFirst) { + var touches = ev.touches; + var pageX; + var pageY; + var deltaX = 0; + var deltaY = 0; + // if touch coords available, prefer, + // because FF would give bad ev.pageX ev.pageY + if (touches && touches.length) { + pageX = touches[0].pageX; + pageY = touches[0].pageY; + } + else { + pageX = ev.pageX; + pageY = ev.pageY; + } + // TODO: repeat code + if (isFirst) { + this.origPageX = pageX; + this.origPageY = pageY; + } + else { + deltaX = pageX - this.origPageX; + deltaY = pageY - this.origPageY; + } + return { + origEvent: ev, + isTouch: true, + subjectEl: this.subjectEl, + pageX: pageX, + pageY: pageY, + deltaX: deltaX, + deltaY: deltaY, + }; + }; + return PointerDragging; + }()); + // Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) + function isPrimaryMouseButton(ev) { + return ev.button === 0 && !ev.ctrlKey; + } + // Ignoring fake mouse events generated by touch + // ---------------------------------------------------------------------------------------------------- + function startIgnoringMouse() { + ignoreMouseDepth += 1; + setTimeout(function () { + ignoreMouseDepth -= 1; + }, config.touchMouseIgnoreWait); + } + // We want to attach touchmove as early as possible for Safari + // ---------------------------------------------------------------------------------------------------- + function listenerCreated() { + listenerCnt += 1; + if (listenerCnt === 1) { + window.addEventListener('touchmove', onWindowTouchMove, { passive: false }); + } + } + function listenerDestroyed() { + listenerCnt -= 1; + if (!listenerCnt) { + window.removeEventListener('touchmove', onWindowTouchMove, { passive: false }); + } + } + function onWindowTouchMove(ev) { + if (isWindowTouchMoveCancelled) { + ev.preventDefault(); + } + } + + /* + An effect in which an element follows the movement of a pointer across the screen. + The moving element is a clone of some other element. + Must call start + handleMove + stop. + */ + var ElementMirror = /** @class */ (function () { + function ElementMirror() { + this.isVisible = false; // must be explicitly enabled + this.sourceEl = null; + this.mirrorEl = null; + this.sourceElRect = null; // screen coords relative to viewport + // options that can be set directly by caller + this.parentNode = document.body; // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues + this.zIndex = 9999; + this.revertDuration = 0; + } + ElementMirror.prototype.start = function (sourceEl, pageX, pageY) { + this.sourceEl = sourceEl; + this.sourceElRect = this.sourceEl.getBoundingClientRect(); + this.origScreenX = pageX - window.pageXOffset; + this.origScreenY = pageY - window.pageYOffset; + this.deltaX = 0; + this.deltaY = 0; + this.updateElPosition(); + }; + ElementMirror.prototype.handleMove = function (pageX, pageY) { + this.deltaX = (pageX - window.pageXOffset) - this.origScreenX; + this.deltaY = (pageY - window.pageYOffset) - this.origScreenY; + this.updateElPosition(); + }; + // can be called before start + ElementMirror.prototype.setIsVisible = function (bool) { + if (bool) { + if (!this.isVisible) { + if (this.mirrorEl) { + this.mirrorEl.style.display = ''; + } + this.isVisible = bool; // needs to happen before updateElPosition + this.updateElPosition(); // because was not updating the position while invisible + } + } + else if (this.isVisible) { + if (this.mirrorEl) { + this.mirrorEl.style.display = 'none'; + } + this.isVisible = bool; + } + }; + // always async + ElementMirror.prototype.stop = function (needsRevertAnimation, callback) { + var _this = this; + var done = function () { + _this.cleanup(); + callback(); + }; + if (needsRevertAnimation && + this.mirrorEl && + this.isVisible && + this.revertDuration && // if 0, transition won't work + (this.deltaX || this.deltaY) // if same coords, transition won't work + ) { + this.doRevertAnimation(done, this.revertDuration); + } + else { + setTimeout(done, 0); + } + }; + ElementMirror.prototype.doRevertAnimation = function (callback, revertDuration) { + var mirrorEl = this.mirrorEl; + var finalSourceElRect = this.sourceEl.getBoundingClientRect(); // because autoscrolling might have happened + mirrorEl.style.transition = + 'top ' + revertDuration + 'ms,' + + 'left ' + revertDuration + 'ms'; + applyStyle(mirrorEl, { + left: finalSourceElRect.left, + top: finalSourceElRect.top, + }); + whenTransitionDone(mirrorEl, function () { + mirrorEl.style.transition = ''; + callback(); + }); + }; + ElementMirror.prototype.cleanup = function () { + if (this.mirrorEl) { + removeElement(this.mirrorEl); + this.mirrorEl = null; + } + this.sourceEl = null; + }; + ElementMirror.prototype.updateElPosition = function () { + if (this.sourceEl && this.isVisible) { + applyStyle(this.getMirrorEl(), { + left: this.sourceElRect.left + this.deltaX, + top: this.sourceElRect.top + this.deltaY, + }); + } + }; + ElementMirror.prototype.getMirrorEl = function () { + var sourceElRect = this.sourceElRect; + var mirrorEl = this.mirrorEl; + if (!mirrorEl) { + mirrorEl = this.mirrorEl = this.sourceEl.cloneNode(true); // cloneChildren=true + // we don't want long taps or any mouse interaction causing selection/menus. + // would use preventSelection(), but that prevents selectstart, causing problems. + mirrorEl.classList.add('fc-unselectable'); + mirrorEl.classList.add('fc-event-dragging'); + applyStyle(mirrorEl, { + position: 'fixed', + zIndex: this.zIndex, + visibility: '', + boxSizing: 'border-box', + width: sourceElRect.right - sourceElRect.left, + height: sourceElRect.bottom - sourceElRect.top, + right: 'auto', + bottom: 'auto', + margin: 0, + }); + this.parentNode.appendChild(mirrorEl); + } + return mirrorEl; + }; + return ElementMirror; + }()); + + /* + Is a cache for a given element's scroll information (all the info that ScrollController stores) + in addition the "client rectangle" of the element.. the area within the scrollbars. + + The cache can be in one of two modes: + - doesListening:false - ignores when the container is scrolled by someone else + - doesListening:true - watch for scrolling and update the cache + */ + var ScrollGeomCache = /** @class */ (function (_super) { + __extends(ScrollGeomCache, _super); + function ScrollGeomCache(scrollController, doesListening) { + var _this = _super.call(this) || this; + _this.handleScroll = function () { + _this.scrollTop = _this.scrollController.getScrollTop(); + _this.scrollLeft = _this.scrollController.getScrollLeft(); + _this.handleScrollChange(); + }; + _this.scrollController = scrollController; + _this.doesListening = doesListening; + _this.scrollTop = _this.origScrollTop = scrollController.getScrollTop(); + _this.scrollLeft = _this.origScrollLeft = scrollController.getScrollLeft(); + _this.scrollWidth = scrollController.getScrollWidth(); + _this.scrollHeight = scrollController.getScrollHeight(); + _this.clientWidth = scrollController.getClientWidth(); + _this.clientHeight = scrollController.getClientHeight(); + _this.clientRect = _this.computeClientRect(); // do last in case it needs cached values + if (_this.doesListening) { + _this.getEventTarget().addEventListener('scroll', _this.handleScroll); + } + return _this; + } + ScrollGeomCache.prototype.destroy = function () { + if (this.doesListening) { + this.getEventTarget().removeEventListener('scroll', this.handleScroll); + } + }; + ScrollGeomCache.prototype.getScrollTop = function () { + return this.scrollTop; + }; + ScrollGeomCache.prototype.getScrollLeft = function () { + return this.scrollLeft; + }; + ScrollGeomCache.prototype.setScrollTop = function (top) { + this.scrollController.setScrollTop(top); + if (!this.doesListening) { + // we are not relying on the element to normalize out-of-bounds scroll values + // so we need to sanitize ourselves + this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0); + this.handleScrollChange(); + } + }; + ScrollGeomCache.prototype.setScrollLeft = function (top) { + this.scrollController.setScrollLeft(top); + if (!this.doesListening) { + // we are not relying on the element to normalize out-of-bounds scroll values + // so we need to sanitize ourselves + this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0); + this.handleScrollChange(); + } + }; + ScrollGeomCache.prototype.getClientWidth = function () { + return this.clientWidth; + }; + ScrollGeomCache.prototype.getClientHeight = function () { + return this.clientHeight; + }; + ScrollGeomCache.prototype.getScrollWidth = function () { + return this.scrollWidth; + }; + ScrollGeomCache.prototype.getScrollHeight = function () { + return this.scrollHeight; + }; + ScrollGeomCache.prototype.handleScrollChange = function () { + }; + return ScrollGeomCache; + }(ScrollController)); + + var ElementScrollGeomCache = /** @class */ (function (_super) { + __extends(ElementScrollGeomCache, _super); + function ElementScrollGeomCache(el, doesListening) { + return _super.call(this, new ElementScrollController(el), doesListening) || this; + } + ElementScrollGeomCache.prototype.getEventTarget = function () { + return this.scrollController.el; + }; + ElementScrollGeomCache.prototype.computeClientRect = function () { + return computeInnerRect(this.scrollController.el); + }; + return ElementScrollGeomCache; + }(ScrollGeomCache)); + + var WindowScrollGeomCache = /** @class */ (function (_super) { + __extends(WindowScrollGeomCache, _super); + function WindowScrollGeomCache(doesListening) { + return _super.call(this, new WindowScrollController(), doesListening) || this; + } + WindowScrollGeomCache.prototype.getEventTarget = function () { + return window; + }; + WindowScrollGeomCache.prototype.computeClientRect = function () { + return { + left: this.scrollLeft, + right: this.scrollLeft + this.clientWidth, + top: this.scrollTop, + bottom: this.scrollTop + this.clientHeight, + }; + }; + // the window is the only scroll object that changes it's rectangle relative + // to the document's topleft as it scrolls + WindowScrollGeomCache.prototype.handleScrollChange = function () { + this.clientRect = this.computeClientRect(); + }; + return WindowScrollGeomCache; + }(ScrollGeomCache)); + + // If available we are using native "performance" API instead of "Date" + // Read more about it on MDN: + // https://developer.mozilla.org/en-US/docs/Web/API/Performance + var getTime = typeof performance === 'function' ? performance.now : Date.now; + /* + For a pointer interaction, automatically scrolls certain scroll containers when the pointer + approaches the edge. + + The caller must call start + handleMove + stop. + */ + var AutoScroller = /** @class */ (function () { + function AutoScroller() { + var _this = this; + // options that can be set by caller + this.isEnabled = true; + this.scrollQuery = [window, '.fc-scroller']; + this.edgeThreshold = 50; // pixels + this.maxVelocity = 300; // pixels per second + // internal state + this.pointerScreenX = null; + this.pointerScreenY = null; + this.isAnimating = false; + this.scrollCaches = null; + // protect against the initial pointerdown being too close to an edge and starting the scroll + this.everMovedUp = false; + this.everMovedDown = false; + this.everMovedLeft = false; + this.everMovedRight = false; + this.animate = function () { + if (_this.isAnimating) { // wasn't cancelled between animation calls + var edge = _this.computeBestEdge(_this.pointerScreenX + window.pageXOffset, _this.pointerScreenY + window.pageYOffset); + if (edge) { + var now = getTime(); + _this.handleSide(edge, (now - _this.msSinceRequest) / 1000); + _this.requestAnimation(now); + } + else { + _this.isAnimating = false; // will stop animation + } + } + }; + } + AutoScroller.prototype.start = function (pageX, pageY, scrollStartEl) { + if (this.isEnabled) { + this.scrollCaches = this.buildCaches(scrollStartEl); + this.pointerScreenX = null; + this.pointerScreenY = null; + this.everMovedUp = false; + this.everMovedDown = false; + this.everMovedLeft = false; + this.everMovedRight = false; + this.handleMove(pageX, pageY); + } + }; + AutoScroller.prototype.handleMove = function (pageX, pageY) { + if (this.isEnabled) { + var pointerScreenX = pageX - window.pageXOffset; + var pointerScreenY = pageY - window.pageYOffset; + var yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY; + var xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX; + if (yDelta < 0) { + this.everMovedUp = true; + } + else if (yDelta > 0) { + this.everMovedDown = true; + } + if (xDelta < 0) { + this.everMovedLeft = true; + } + else if (xDelta > 0) { + this.everMovedRight = true; + } + this.pointerScreenX = pointerScreenX; + this.pointerScreenY = pointerScreenY; + if (!this.isAnimating) { + this.isAnimating = true; + this.requestAnimation(getTime()); + } + } + }; + AutoScroller.prototype.stop = function () { + if (this.isEnabled) { + this.isAnimating = false; // will stop animation + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + scrollCache.destroy(); + } + this.scrollCaches = null; + } + }; + AutoScroller.prototype.requestAnimation = function (now) { + this.msSinceRequest = now; + requestAnimationFrame(this.animate); + }; + AutoScroller.prototype.handleSide = function (edge, seconds) { + var scrollCache = edge.scrollCache; + var edgeThreshold = this.edgeThreshold; + var invDistance = edgeThreshold - edge.distance; + var velocity = // the closer to the edge, the faster we scroll + ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic + this.maxVelocity * seconds; + var sign = 1; + switch (edge.name) { + case 'left': + sign = -1; + // falls through + case 'right': + scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign); + break; + case 'top': + sign = -1; + // falls through + case 'bottom': + scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign); + break; + } + }; + // left/top are relative to document topleft + AutoScroller.prototype.computeBestEdge = function (left, top) { + var edgeThreshold = this.edgeThreshold; + var bestSide = null; + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + var rect = scrollCache.clientRect; + var leftDist = left - rect.left; + var rightDist = rect.right - left; + var topDist = top - rect.top; + var bottomDist = rect.bottom - top; + // completely within the rect? + if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) { + if (topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() && + (!bestSide || bestSide.distance > topDist)) { + bestSide = { scrollCache: scrollCache, name: 'top', distance: topDist }; + } + if (bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() && + (!bestSide || bestSide.distance > bottomDist)) { + bestSide = { scrollCache: scrollCache, name: 'bottom', distance: bottomDist }; + } + if (leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() && + (!bestSide || bestSide.distance > leftDist)) { + bestSide = { scrollCache: scrollCache, name: 'left', distance: leftDist }; + } + if (rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() && + (!bestSide || bestSide.distance > rightDist)) { + bestSide = { scrollCache: scrollCache, name: 'right', distance: rightDist }; + } + } + } + return bestSide; + }; + AutoScroller.prototype.buildCaches = function (scrollStartEl) { + return this.queryScrollEls(scrollStartEl).map(function (el) { + if (el === window) { + return new WindowScrollGeomCache(false); // false = don't listen to user-generated scrolls + } + return new ElementScrollGeomCache(el, false); // false = don't listen to user-generated scrolls + }); + }; + AutoScroller.prototype.queryScrollEls = function (scrollStartEl) { + var els = []; + for (var _i = 0, _a = this.scrollQuery; _i < _a.length; _i++) { + var query = _a[_i]; + if (typeof query === 'object') { + els.push(query); + } + else { + els.push.apply(els, Array.prototype.slice.call(getElRoot(scrollStartEl).querySelectorAll(query))); + } + } + return els; + }; + return AutoScroller; + }()); + + /* + Monitors dragging on an element. Has a number of high-level features: + - minimum distance required before dragging + - minimum wait time ("delay") before dragging + - a mirror element that follows the pointer + */ + var FeaturefulElementDragging = /** @class */ (function (_super) { + __extends(FeaturefulElementDragging, _super); + function FeaturefulElementDragging(containerEl, selector) { + var _this = _super.call(this, containerEl) || this; + _this.containerEl = containerEl; + // options that can be directly set by caller + // the caller can also set the PointerDragging's options as well + _this.delay = null; + _this.minDistance = 0; + _this.touchScrollAllowed = true; // prevents drag from starting and blocks scrolling during drag + _this.mirrorNeedsRevert = false; + _this.isInteracting = false; // is the user validly moving the pointer? lasts until pointerup + _this.isDragging = false; // is it INTENTFULLY dragging? lasts until after revert animation + _this.isDelayEnded = false; + _this.isDistanceSurpassed = false; + _this.delayTimeoutId = null; + _this.onPointerDown = function (ev) { + if (!_this.isDragging) { // so new drag doesn't happen while revert animation is going + _this.isInteracting = true; + _this.isDelayEnded = false; + _this.isDistanceSurpassed = false; + preventSelection(document.body); + preventContextMenu(document.body); + // prevent links from being visited if there's an eventual drag. + // also prevents selection in older browsers (maybe?). + // not necessary for touch, besides, browser would complain about passiveness. + if (!ev.isTouch) { + ev.origEvent.preventDefault(); + } + _this.emitter.trigger('pointerdown', ev); + if (_this.isInteracting && // not destroyed via pointerdown handler + !_this.pointer.shouldIgnoreMove) { + // actions related to initiating dragstart+dragmove+dragend... + _this.mirror.setIsVisible(false); // reset. caller must set-visible + _this.mirror.start(ev.subjectEl, ev.pageX, ev.pageY); // must happen on first pointer down + _this.startDelay(ev); + if (!_this.minDistance) { + _this.handleDistanceSurpassed(ev); + } + } + } + }; + _this.onPointerMove = function (ev) { + if (_this.isInteracting) { + _this.emitter.trigger('pointermove', ev); + if (!_this.isDistanceSurpassed) { + var minDistance = _this.minDistance; + var distanceSq = void 0; // current distance from the origin, squared + var deltaX = ev.deltaX, deltaY = ev.deltaY; + distanceSq = deltaX * deltaX + deltaY * deltaY; + if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem + _this.handleDistanceSurpassed(ev); + } + } + if (_this.isDragging) { + // a real pointer move? (not one simulated by scrolling) + if (ev.origEvent.type !== 'scroll') { + _this.mirror.handleMove(ev.pageX, ev.pageY); + _this.autoScroller.handleMove(ev.pageX, ev.pageY); + } + _this.emitter.trigger('dragmove', ev); + } + } + }; + _this.onPointerUp = function (ev) { + if (_this.isInteracting) { + _this.isInteracting = false; + allowSelection(document.body); + allowContextMenu(document.body); + _this.emitter.trigger('pointerup', ev); // can potentially set mirrorNeedsRevert + if (_this.isDragging) { + _this.autoScroller.stop(); + _this.tryStopDrag(ev); // which will stop the mirror + } + if (_this.delayTimeoutId) { + clearTimeout(_this.delayTimeoutId); + _this.delayTimeoutId = null; + } + } + }; + var pointer = _this.pointer = new PointerDragging(containerEl); + pointer.emitter.on('pointerdown', _this.onPointerDown); + pointer.emitter.on('pointermove', _this.onPointerMove); + pointer.emitter.on('pointerup', _this.onPointerUp); + if (selector) { + pointer.selector = selector; + } + _this.mirror = new ElementMirror(); + _this.autoScroller = new AutoScroller(); + return _this; + } + FeaturefulElementDragging.prototype.destroy = function () { + this.pointer.destroy(); + // HACK: simulate a pointer-up to end the current drag + // TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire) + this.onPointerUp({}); + }; + FeaturefulElementDragging.prototype.startDelay = function (ev) { + var _this = this; + if (typeof this.delay === 'number') { + this.delayTimeoutId = setTimeout(function () { + _this.delayTimeoutId = null; + _this.handleDelayEnd(ev); + }, this.delay); // not assignable to number! + } + else { + this.handleDelayEnd(ev); + } + }; + FeaturefulElementDragging.prototype.handleDelayEnd = function (ev) { + this.isDelayEnded = true; + this.tryStartDrag(ev); + }; + FeaturefulElementDragging.prototype.handleDistanceSurpassed = function (ev) { + this.isDistanceSurpassed = true; + this.tryStartDrag(ev); + }; + FeaturefulElementDragging.prototype.tryStartDrag = function (ev) { + if (this.isDelayEnded && this.isDistanceSurpassed) { + if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) { + this.isDragging = true; + this.mirrorNeedsRevert = false; + this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl); + this.emitter.trigger('dragstart', ev); + if (this.touchScrollAllowed === false) { + this.pointer.cancelTouchScroll(); + } + } + } + }; + FeaturefulElementDragging.prototype.tryStopDrag = function (ev) { + // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events + // that come from the document to fire beforehand. much more convenient this way. + this.mirror.stop(this.mirrorNeedsRevert, this.stopDrag.bind(this, ev)); + }; + FeaturefulElementDragging.prototype.stopDrag = function (ev) { + this.isDragging = false; + this.emitter.trigger('dragend', ev); + }; + // fill in the implementations... + FeaturefulElementDragging.prototype.setIgnoreMove = function (bool) { + this.pointer.shouldIgnoreMove = bool; + }; + FeaturefulElementDragging.prototype.setMirrorIsVisible = function (bool) { + this.mirror.setIsVisible(bool); + }; + FeaturefulElementDragging.prototype.setMirrorNeedsRevert = function (bool) { + this.mirrorNeedsRevert = bool; + }; + FeaturefulElementDragging.prototype.setAutoScrollEnabled = function (bool) { + this.autoScroller.isEnabled = bool; + }; + return FeaturefulElementDragging; + }(ElementDragging)); + + /* + When this class is instantiated, it records the offset of an element (relative to the document topleft), + and continues to monitor scrolling, updating the cached coordinates if it needs to. + Does not access the DOM after instantiation, so highly performant. + + Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element + and an determine if a given point is inside the combined clipping rectangle. + */ + var OffsetTracker = /** @class */ (function () { + function OffsetTracker(el) { + this.origRect = computeRect(el); + // will work fine for divs that have overflow:hidden + this.scrollCaches = getClippingParents(el).map(function (scrollEl) { return new ElementScrollGeomCache(scrollEl, true); }); + } + OffsetTracker.prototype.destroy = function () { + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + scrollCache.destroy(); + } + }; + OffsetTracker.prototype.computeLeft = function () { + var left = this.origRect.left; + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + left += scrollCache.origScrollLeft - scrollCache.getScrollLeft(); + } + return left; + }; + OffsetTracker.prototype.computeTop = function () { + var top = this.origRect.top; + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + top += scrollCache.origScrollTop - scrollCache.getScrollTop(); + } + return top; + }; + OffsetTracker.prototype.isWithinClipping = function (pageX, pageY) { + var point = { left: pageX, top: pageY }; + for (var _i = 0, _a = this.scrollCaches; _i < _a.length; _i++) { + var scrollCache = _a[_i]; + if (!isIgnoredClipping(scrollCache.getEventTarget()) && + !pointInsideRect(point, scrollCache.clientRect)) { + return false; + } + } + return true; + }; + return OffsetTracker; + }()); + // certain clipping containers should never constrain interactions, like and + // https://github.com/fullcalendar/fullcalendar/issues/3615 + function isIgnoredClipping(node) { + var tagName = node.tagName; + return tagName === 'HTML' || tagName === 'BODY'; + } + + /* + Tracks movement over multiple droppable areas (aka "hits") + that exist in one or more DateComponents. + Relies on an existing draggable. + + emits: + - pointerdown + - dragstart + - hitchange - fires initially, even if not over a hit + - pointerup + - (hitchange - again, to null, if ended over a hit) + - dragend + */ + var HitDragging = /** @class */ (function () { + function HitDragging(dragging, droppableStore) { + var _this = this; + // options that can be set by caller + this.useSubjectCenter = false; + this.requireInitial = true; // if doesn't start out on a hit, won't emit any events + this.initialHit = null; + this.movingHit = null; + this.finalHit = null; // won't ever be populated if shouldIgnoreMove + this.handlePointerDown = function (ev) { + var dragging = _this.dragging; + _this.initialHit = null; + _this.movingHit = null; + _this.finalHit = null; + _this.prepareHits(); + _this.processFirstCoord(ev); + if (_this.initialHit || !_this.requireInitial) { + dragging.setIgnoreMove(false); + // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :( + _this.emitter.trigger('pointerdown', ev); + } + else { + dragging.setIgnoreMove(true); + } + }; + this.handleDragStart = function (ev) { + _this.emitter.trigger('dragstart', ev); + _this.handleMove(ev, true); // force = fire even if initially null + }; + this.handleDragMove = function (ev) { + _this.emitter.trigger('dragmove', ev); + _this.handleMove(ev); + }; + this.handlePointerUp = function (ev) { + _this.releaseHits(); + _this.emitter.trigger('pointerup', ev); + }; + this.handleDragEnd = function (ev) { + if (_this.movingHit) { + _this.emitter.trigger('hitupdate', null, true, ev); + } + _this.finalHit = _this.movingHit; + _this.movingHit = null; + _this.emitter.trigger('dragend', ev); + }; + this.droppableStore = droppableStore; + dragging.emitter.on('pointerdown', this.handlePointerDown); + dragging.emitter.on('dragstart', this.handleDragStart); + dragging.emitter.on('dragmove', this.handleDragMove); + dragging.emitter.on('pointerup', this.handlePointerUp); + dragging.emitter.on('dragend', this.handleDragEnd); + this.dragging = dragging; + this.emitter = new Emitter(); + } + // sets initialHit + // sets coordAdjust + HitDragging.prototype.processFirstCoord = function (ev) { + var origPoint = { left: ev.pageX, top: ev.pageY }; + var adjustedPoint = origPoint; + var subjectEl = ev.subjectEl; + var subjectRect; + if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot + subjectRect = computeRect(subjectEl); + adjustedPoint = constrainPoint(adjustedPoint, subjectRect); + } + var initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top); + if (initialHit) { + if (this.useSubjectCenter && subjectRect) { + var slicedSubjectRect = intersectRects(subjectRect, initialHit.rect); + if (slicedSubjectRect) { + adjustedPoint = getRectCenter(slicedSubjectRect); + } + } + this.coordAdjust = diffPoints(adjustedPoint, origPoint); + } + else { + this.coordAdjust = { left: 0, top: 0 }; + } + }; + HitDragging.prototype.handleMove = function (ev, forceHandle) { + var hit = this.queryHitForOffset(ev.pageX + this.coordAdjust.left, ev.pageY + this.coordAdjust.top); + if (forceHandle || !isHitsEqual(this.movingHit, hit)) { + this.movingHit = hit; + this.emitter.trigger('hitupdate', hit, false, ev); + } + }; + HitDragging.prototype.prepareHits = function () { + this.offsetTrackers = mapHash(this.droppableStore, function (interactionSettings) { + interactionSettings.component.prepareHits(); + return new OffsetTracker(interactionSettings.el); + }); + }; + HitDragging.prototype.releaseHits = function () { + var offsetTrackers = this.offsetTrackers; + for (var id in offsetTrackers) { + offsetTrackers[id].destroy(); + } + this.offsetTrackers = {}; + }; + HitDragging.prototype.queryHitForOffset = function (offsetLeft, offsetTop) { + var _a = this, droppableStore = _a.droppableStore, offsetTrackers = _a.offsetTrackers; + var bestHit = null; + for (var id in droppableStore) { + var component = droppableStore[id].component; + var offsetTracker = offsetTrackers[id]; + if (offsetTracker && // wasn't destroyed mid-drag + offsetTracker.isWithinClipping(offsetLeft, offsetTop)) { + var originLeft = offsetTracker.computeLeft(); + var originTop = offsetTracker.computeTop(); + var positionLeft = offsetLeft - originLeft; + var positionTop = offsetTop - originTop; + var origRect = offsetTracker.origRect; + var width = origRect.right - origRect.left; + var height = origRect.bottom - origRect.top; + if ( + // must be within the element's bounds + positionLeft >= 0 && positionLeft < width && + positionTop >= 0 && positionTop < height) { + var hit = component.queryHit(positionLeft, positionTop, width, height); + if (hit && ( + // make sure the hit is within activeRange, meaning it's not a dead cell + rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range)) && + (!bestHit || hit.layer > bestHit.layer)) { + hit.componentId = id; + hit.context = component.context; + // TODO: better way to re-orient rectangle + hit.rect.left += originLeft; + hit.rect.right += originLeft; + hit.rect.top += originTop; + hit.rect.bottom += originTop; + bestHit = hit; + } + } + } + } + return bestHit; + }; + return HitDragging; + }()); + function isHitsEqual(hit0, hit1) { + if (!hit0 && !hit1) { + return true; + } + if (Boolean(hit0) !== Boolean(hit1)) { + return false; + } + return isDateSpansEqual(hit0.dateSpan, hit1.dateSpan); + } + + function buildDatePointApiWithContext(dateSpan, context) { + var props = {}; + for (var _i = 0, _a = context.pluginHooks.datePointTransforms; _i < _a.length; _i++) { + var transform = _a[_i]; + __assign(props, transform(dateSpan, context)); + } + __assign(props, buildDatePointApi(dateSpan, context.dateEnv)); + return props; + } + function buildDatePointApi(span, dateEnv) { + return { + date: dateEnv.toDate(span.range.start), + dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }), + allDay: span.allDay, + }; + } + + /* + Monitors when the user clicks on a specific date/time of a component. + A pointerdown+pointerup on the same "hit" constitutes a click. + */ + var DateClicking = /** @class */ (function (_super) { + __extends(DateClicking, _super); + function DateClicking(settings) { + var _this = _super.call(this, settings) || this; + _this.handlePointerDown = function (pev) { + var dragging = _this.dragging; + var downEl = pev.origEvent.target; + // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired + dragging.setIgnoreMove(!_this.component.isValidDateDownEl(downEl)); + }; + // won't even fire if moving was ignored + _this.handleDragEnd = function (ev) { + var component = _this.component; + var pointer = _this.dragging.pointer; + if (!pointer.wasTouchScroll) { + var _a = _this.hitDragging, initialHit = _a.initialHit, finalHit = _a.finalHit; + if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) { + var context = component.context; + var arg = __assign(__assign({}, buildDatePointApiWithContext(initialHit.dateSpan, context)), { dayEl: initialHit.dayEl, jsEvent: ev.origEvent, view: context.viewApi || context.calendarApi.view }); + context.emitter.trigger('dateClick', arg); + } + } + }; + // we DO want to watch pointer moves because otherwise finalHit won't get populated + _this.dragging = new FeaturefulElementDragging(settings.el); + _this.dragging.autoScroller.isEnabled = false; + var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings)); + hitDragging.emitter.on('pointerdown', _this.handlePointerDown); + hitDragging.emitter.on('dragend', _this.handleDragEnd); + return _this; + } + DateClicking.prototype.destroy = function () { + this.dragging.destroy(); + }; + return DateClicking; + }(Interaction)); + + /* + Tracks when the user selects a portion of time of a component, + constituted by a drag over date cells, with a possible delay at the beginning of the drag. + */ + var DateSelecting = /** @class */ (function (_super) { + __extends(DateSelecting, _super); + function DateSelecting(settings) { + var _this = _super.call(this, settings) || this; + _this.dragSelection = null; + _this.handlePointerDown = function (ev) { + var _a = _this, component = _a.component, dragging = _a.dragging; + var options = component.context.options; + var canSelect = options.selectable && + component.isValidDateDownEl(ev.origEvent.target); + // don't bother to watch expensive moves if component won't do selection + dragging.setIgnoreMove(!canSelect); + // if touch, require user to hold down + dragging.delay = ev.isTouch ? getComponentTouchDelay$1(component) : null; + }; + _this.handleDragStart = function (ev) { + _this.component.context.calendarApi.unselect(ev); // unselect previous selections + }; + _this.handleHitUpdate = function (hit, isFinal) { + var context = _this.component.context; + var dragSelection = null; + var isInvalid = false; + if (hit) { + var initialHit = _this.hitDragging.initialHit; + var disallowed = hit.componentId === initialHit.componentId + && _this.isHitComboAllowed + && !_this.isHitComboAllowed(initialHit, hit); + if (!disallowed) { + dragSelection = joinHitsIntoSelection(initialHit, hit, context.pluginHooks.dateSelectionTransformers); + } + if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) { + isInvalid = true; + dragSelection = null; + } + } + if (dragSelection) { + context.dispatch({ type: 'SELECT_DATES', selection: dragSelection }); + } + else if (!isFinal) { // only unselect if moved away while dragging + context.dispatch({ type: 'UNSELECT_DATES' }); + } + if (!isInvalid) { + enableCursor(); + } + else { + disableCursor(); + } + if (!isFinal) { + _this.dragSelection = dragSelection; // only clear if moved away from all hits while dragging + } + }; + _this.handlePointerUp = function (pev) { + if (_this.dragSelection) { + // selection is already rendered, so just need to report selection + triggerDateSelect(_this.dragSelection, pev, _this.component.context); + _this.dragSelection = null; + } + }; + var component = settings.component; + var options = component.context.options; + var dragging = _this.dragging = new FeaturefulElementDragging(settings.el); + dragging.touchScrollAllowed = false; + dragging.minDistance = options.selectMinDistance || 0; + dragging.autoScroller.isEnabled = options.dragScroll; + var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings)); + hitDragging.emitter.on('pointerdown', _this.handlePointerDown); + hitDragging.emitter.on('dragstart', _this.handleDragStart); + hitDragging.emitter.on('hitupdate', _this.handleHitUpdate); + hitDragging.emitter.on('pointerup', _this.handlePointerUp); + return _this; + } + DateSelecting.prototype.destroy = function () { + this.dragging.destroy(); + }; + return DateSelecting; + }(Interaction)); + function getComponentTouchDelay$1(component) { + var options = component.context.options; + var delay = options.selectLongPressDelay; + if (delay == null) { + delay = options.longPressDelay; + } + return delay; + } + function joinHitsIntoSelection(hit0, hit1, dateSelectionTransformers) { + var dateSpan0 = hit0.dateSpan; + var dateSpan1 = hit1.dateSpan; + var ms = [ + dateSpan0.range.start, + dateSpan0.range.end, + dateSpan1.range.start, + dateSpan1.range.end, + ]; + ms.sort(compareNumbers); + var props = {}; + for (var _i = 0, dateSelectionTransformers_1 = dateSelectionTransformers; _i < dateSelectionTransformers_1.length; _i++) { + var transformer = dateSelectionTransformers_1[_i]; + var res = transformer(hit0, hit1); + if (res === false) { + return null; + } + if (res) { + __assign(props, res); + } + } + props.range = { start: ms[0], end: ms[3] }; + props.allDay = dateSpan0.allDay; + return props; + } + + var EventDragging = /** @class */ (function (_super) { + __extends(EventDragging, _super); + function EventDragging(settings) { + var _this = _super.call(this, settings) || this; + // internal state + _this.subjectEl = null; + _this.subjectSeg = null; // the seg being selected/dragged + _this.isDragging = false; + _this.eventRange = null; + _this.relevantEvents = null; // the events being dragged + _this.receivingContext = null; + _this.validMutation = null; + _this.mutatedRelevantEvents = null; + _this.handlePointerDown = function (ev) { + var origTarget = ev.origEvent.target; + var _a = _this, component = _a.component, dragging = _a.dragging; + var mirror = dragging.mirror; + var options = component.context.options; + var initialContext = component.context; + _this.subjectEl = ev.subjectEl; + var subjectSeg = _this.subjectSeg = getElSeg(ev.subjectEl); + var eventRange = _this.eventRange = subjectSeg.eventRange; + var eventInstanceId = eventRange.instance.instanceId; + _this.relevantEvents = getRelevantEvents(initialContext.getCurrentData().eventStore, eventInstanceId); + dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance; + dragging.delay = + // only do a touch delay if touch and this event hasn't been selected yet + (ev.isTouch && eventInstanceId !== component.props.eventSelection) ? + getComponentTouchDelay(component) : + null; + if (options.fixedMirrorParent) { + mirror.parentNode = options.fixedMirrorParent; + } + else { + mirror.parentNode = elementClosest(origTarget, '.fc'); + } + mirror.revertDuration = options.dragRevertDuration; + var isValid = component.isValidSegDownEl(origTarget) && + !elementClosest(origTarget, '.fc-event-resizer'); // NOT on a resizer + dragging.setIgnoreMove(!isValid); + // disable dragging for elements that are resizable (ie, selectable) + // but are not draggable + _this.isDragging = isValid && + ev.subjectEl.classList.contains('fc-event-draggable'); + }; + _this.handleDragStart = function (ev) { + var initialContext = _this.component.context; + var eventRange = _this.eventRange; + var eventInstanceId = eventRange.instance.instanceId; + if (ev.isTouch) { + // need to select a different event? + if (eventInstanceId !== _this.component.props.eventSelection) { + initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId: eventInstanceId }); + } + } + else { + // if now using mouse, but was previous touch interaction, clear selected event + initialContext.dispatch({ type: 'UNSELECT_EVENT' }); + } + if (_this.isDragging) { + initialContext.calendarApi.unselect(ev); // unselect *date* selection + initialContext.emitter.trigger('eventDragStart', { + el: _this.subjectEl, + event: new EventApi(initialContext, eventRange.def, eventRange.instance), + jsEvent: ev.origEvent, + view: initialContext.viewApi, + }); + } + }; + _this.handleHitUpdate = function (hit, isFinal) { + if (!_this.isDragging) { + return; + } + var relevantEvents = _this.relevantEvents; + var initialHit = _this.hitDragging.initialHit; + var initialContext = _this.component.context; + // states based on new hit + var receivingContext = null; + var mutation = null; + var mutatedRelevantEvents = null; + var isInvalid = false; + var interaction = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + }; + if (hit) { + receivingContext = hit.context; + var receivingOptions = receivingContext.options; + if (initialContext === receivingContext || + (receivingOptions.editable && receivingOptions.droppable)) { + mutation = computeEventMutation(initialHit, hit, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers); + if (mutation) { + mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, receivingContext.getCurrentData().eventUiBases, mutation, receivingContext); + interaction.mutatedEvents = mutatedRelevantEvents; + if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) { + isInvalid = true; + mutation = null; + mutatedRelevantEvents = null; + interaction.mutatedEvents = createEmptyEventStore(); + } + } + } + else { + receivingContext = null; + } + } + _this.displayDrag(receivingContext, interaction); + if (!isInvalid) { + enableCursor(); + } + else { + disableCursor(); + } + if (!isFinal) { + if (initialContext === receivingContext && // TODO: write test for this + isHitsEqual(initialHit, hit)) { + mutation = null; + } + _this.dragging.setMirrorNeedsRevert(!mutation); + // render the mirror if no already-rendered mirror + // TODO: wish we could somehow wait for dispatch to guarantee render + _this.dragging.setMirrorIsVisible(!hit || !getElRoot(_this.subjectEl).querySelector('.fc-event-mirror')); + // assign states based on new hit + _this.receivingContext = receivingContext; + _this.validMutation = mutation; + _this.mutatedRelevantEvents = mutatedRelevantEvents; + } + }; + _this.handlePointerUp = function () { + if (!_this.isDragging) { + _this.cleanup(); // because handleDragEnd won't fire + } + }; + _this.handleDragEnd = function (ev) { + if (_this.isDragging) { + var initialContext_1 = _this.component.context; + var initialView = initialContext_1.viewApi; + var _a = _this, receivingContext_1 = _a.receivingContext, validMutation = _a.validMutation; + var eventDef = _this.eventRange.def; + var eventInstance = _this.eventRange.instance; + var eventApi = new EventApi(initialContext_1, eventDef, eventInstance); + var relevantEvents_1 = _this.relevantEvents; + var mutatedRelevantEvents_1 = _this.mutatedRelevantEvents; + var finalHit = _this.hitDragging.finalHit; + _this.clearDrag(); // must happen after revert animation + initialContext_1.emitter.trigger('eventDragStop', { + el: _this.subjectEl, + event: eventApi, + jsEvent: ev.origEvent, + view: initialView, + }); + if (validMutation) { + // dropped within same calendar + if (receivingContext_1 === initialContext_1) { + var updatedEventApi = new EventApi(initialContext_1, mutatedRelevantEvents_1.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents_1.instances[eventInstance.instanceId] : null); + initialContext_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents_1, + }); + var eventChangeArg = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents_1, initialContext_1, eventInstance), + revert: function () { + initialContext_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents_1, // the pre-change data + }); + }, + }; + var transformed = {}; + for (var _i = 0, _b = initialContext_1.getCurrentData().pluginHooks.eventDropTransformers; _i < _b.length; _i++) { + var transformer = _b[_i]; + __assign(transformed, transformer(validMutation, initialContext_1)); + } + initialContext_1.emitter.trigger('eventDrop', __assign(__assign(__assign({}, eventChangeArg), transformed), { el: ev.subjectEl, delta: validMutation.datesDelta, jsEvent: ev.origEvent, view: initialView })); + initialContext_1.emitter.trigger('eventChange', eventChangeArg); + // dropped in different calendar + } + else if (receivingContext_1) { + var eventRemoveArg = { + event: eventApi, + relatedEvents: buildEventApis(relevantEvents_1, initialContext_1, eventInstance), + revert: function () { + initialContext_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents_1, + }); + }, + }; + initialContext_1.emitter.trigger('eventLeave', __assign(__assign({}, eventRemoveArg), { draggedEl: ev.subjectEl, view: initialView })); + initialContext_1.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: relevantEvents_1, + }); + initialContext_1.emitter.trigger('eventRemove', eventRemoveArg); + var addedEventDef = mutatedRelevantEvents_1.defs[eventDef.defId]; + var addedEventInstance = mutatedRelevantEvents_1.instances[eventInstance.instanceId]; + var addedEventApi = new EventApi(receivingContext_1, addedEventDef, addedEventInstance); + receivingContext_1.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents_1, + }); + var eventAddArg = { + event: addedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents_1, receivingContext_1, addedEventInstance), + revert: function () { + receivingContext_1.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: mutatedRelevantEvents_1, + }); + }, + }; + receivingContext_1.emitter.trigger('eventAdd', eventAddArg); + if (ev.isTouch) { + receivingContext_1.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: eventInstance.instanceId, + }); + } + receivingContext_1.emitter.trigger('drop', __assign(__assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext_1)), { draggedEl: ev.subjectEl, jsEvent: ev.origEvent, view: finalHit.context.viewApi })); + receivingContext_1.emitter.trigger('eventReceive', __assign(__assign({}, eventAddArg), { draggedEl: ev.subjectEl, view: finalHit.context.viewApi })); + } + } + else { + initialContext_1.emitter.trigger('_noEventDrop'); + } + } + _this.cleanup(); + }; + var component = _this.component; + var options = component.context.options; + var dragging = _this.dragging = new FeaturefulElementDragging(settings.el); + dragging.pointer.selector = EventDragging.SELECTOR; + dragging.touchScrollAllowed = false; + dragging.autoScroller.isEnabled = options.dragScroll; + var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsStore); + hitDragging.useSubjectCenter = settings.useEventCenter; + hitDragging.emitter.on('pointerdown', _this.handlePointerDown); + hitDragging.emitter.on('dragstart', _this.handleDragStart); + hitDragging.emitter.on('hitupdate', _this.handleHitUpdate); + hitDragging.emitter.on('pointerup', _this.handlePointerUp); + hitDragging.emitter.on('dragend', _this.handleDragEnd); + return _this; + } + EventDragging.prototype.destroy = function () { + this.dragging.destroy(); + }; + // render a drag state on the next receivingCalendar + EventDragging.prototype.displayDrag = function (nextContext, state) { + var initialContext = this.component.context; + var prevContext = this.receivingContext; + // does the previous calendar need to be cleared? + if (prevContext && prevContext !== nextContext) { + // does the initial calendar need to be cleared? + // if so, don't clear all the way. we still need to to hide the affectedEvents + if (prevContext === initialContext) { + prevContext.dispatch({ + type: 'SET_EVENT_DRAG', + state: { + affectedEvents: state.affectedEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + }, + }); + // completely clear the old calendar if it wasn't the initial + } + else { + prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + } + if (nextContext) { + nextContext.dispatch({ type: 'SET_EVENT_DRAG', state: state }); + } + }; + EventDragging.prototype.clearDrag = function () { + var initialCalendar = this.component.context; + var receivingContext = this.receivingContext; + if (receivingContext) { + receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + // the initial calendar might have an dummy drag state from displayDrag + if (initialCalendar !== receivingContext) { + initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + }; + EventDragging.prototype.cleanup = function () { + this.subjectSeg = null; + this.isDragging = false; + this.eventRange = null; + this.relevantEvents = null; + this.receivingContext = null; + this.validMutation = null; + this.mutatedRelevantEvents = null; + }; + // TODO: test this in IE11 + // QUESTION: why do we need it on the resizable??? + EventDragging.SELECTOR = '.fc-event-draggable, .fc-event-resizable'; + return EventDragging; + }(Interaction)); + function computeEventMutation(hit0, hit1, massagers) { + var dateSpan0 = hit0.dateSpan; + var dateSpan1 = hit1.dateSpan; + var date0 = dateSpan0.range.start; + var date1 = dateSpan1.range.start; + var standardProps = {}; + if (dateSpan0.allDay !== dateSpan1.allDay) { + standardProps.allDay = dateSpan1.allDay; + standardProps.hasEnd = hit1.context.options.allDayMaintainDuration; + if (dateSpan1.allDay) { + // means date1 is already start-of-day, + // but date0 needs to be converted + date0 = startOfDay(date0); + } + } + var delta = diffDates(date0, date1, hit0.context.dateEnv, hit0.componentId === hit1.componentId ? + hit0.largeUnit : + null); + if (delta.milliseconds) { // has hours/minutes/seconds + standardProps.allDay = false; + } + var mutation = { + datesDelta: delta, + standardProps: standardProps, + }; + for (var _i = 0, massagers_1 = massagers; _i < massagers_1.length; _i++) { + var massager = massagers_1[_i]; + massager(mutation, hit0, hit1); + } + return mutation; + } + function getComponentTouchDelay(component) { + var options = component.context.options; + var delay = options.eventLongPressDelay; + if (delay == null) { + delay = options.longPressDelay; + } + return delay; + } + + var EventResizing = /** @class */ (function (_super) { + __extends(EventResizing, _super); + function EventResizing(settings) { + var _this = _super.call(this, settings) || this; + // internal state + _this.draggingSegEl = null; + _this.draggingSeg = null; // TODO: rename to resizingSeg? subjectSeg? + _this.eventRange = null; + _this.relevantEvents = null; + _this.validMutation = null; + _this.mutatedRelevantEvents = null; + _this.handlePointerDown = function (ev) { + var component = _this.component; + var segEl = _this.querySegEl(ev); + var seg = getElSeg(segEl); + var eventRange = _this.eventRange = seg.eventRange; + _this.dragging.minDistance = component.context.options.eventDragMinDistance; + // if touch, need to be working with a selected event + _this.dragging.setIgnoreMove(!_this.component.isValidSegDownEl(ev.origEvent.target) || + (ev.isTouch && _this.component.props.eventSelection !== eventRange.instance.instanceId)); + }; + _this.handleDragStart = function (ev) { + var context = _this.component.context; + var eventRange = _this.eventRange; + _this.relevantEvents = getRelevantEvents(context.getCurrentData().eventStore, _this.eventRange.instance.instanceId); + var segEl = _this.querySegEl(ev); + _this.draggingSegEl = segEl; + _this.draggingSeg = getElSeg(segEl); + context.calendarApi.unselect(); + context.emitter.trigger('eventResizeStart', { + el: segEl, + event: new EventApi(context, eventRange.def, eventRange.instance), + jsEvent: ev.origEvent, + view: context.viewApi, + }); + }; + _this.handleHitUpdate = function (hit, isFinal, ev) { + var context = _this.component.context; + var relevantEvents = _this.relevantEvents; + var initialHit = _this.hitDragging.initialHit; + var eventInstance = _this.eventRange.instance; + var mutation = null; + var mutatedRelevantEvents = null; + var isInvalid = false; + var interaction = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + }; + if (hit) { + var disallowed = hit.componentId === initialHit.componentId + && _this.isHitComboAllowed + && !_this.isHitComboAllowed(initialHit, hit); + if (!disallowed) { + mutation = computeMutation(initialHit, hit, ev.subjectEl.classList.contains('fc-event-resizer-start'), eventInstance.range); + } + } + if (mutation) { + mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context); + interaction.mutatedEvents = mutatedRelevantEvents; + if (!isInteractionValid(interaction, hit.dateProfile, context)) { + isInvalid = true; + mutation = null; + mutatedRelevantEvents = null; + interaction.mutatedEvents = null; + } + } + if (mutatedRelevantEvents) { + context.dispatch({ + type: 'SET_EVENT_RESIZE', + state: interaction, + }); + } + else { + context.dispatch({ type: 'UNSET_EVENT_RESIZE' }); + } + if (!isInvalid) { + enableCursor(); + } + else { + disableCursor(); + } + if (!isFinal) { + if (mutation && isHitsEqual(initialHit, hit)) { + mutation = null; + } + _this.validMutation = mutation; + _this.mutatedRelevantEvents = mutatedRelevantEvents; + } + }; + _this.handleDragEnd = function (ev) { + var context = _this.component.context; + var eventDef = _this.eventRange.def; + var eventInstance = _this.eventRange.instance; + var eventApi = new EventApi(context, eventDef, eventInstance); + var relevantEvents = _this.relevantEvents; + var mutatedRelevantEvents = _this.mutatedRelevantEvents; + context.emitter.trigger('eventResizeStop', { + el: _this.draggingSegEl, + event: eventApi, + jsEvent: ev.origEvent, + view: context.viewApi, + }); + if (_this.validMutation) { + var updatedEventApi = new EventApi(context, mutatedRelevantEvents.defs[eventDef.defId], eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null); + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }); + var eventChangeArg = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance), + revert: function () { + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, // the pre-change events + }); + }, + }; + context.emitter.trigger('eventResize', __assign(__assign({}, eventChangeArg), { el: _this.draggingSegEl, startDelta: _this.validMutation.startDelta || createDuration(0), endDelta: _this.validMutation.endDelta || createDuration(0), jsEvent: ev.origEvent, view: context.viewApi })); + context.emitter.trigger('eventChange', eventChangeArg); + } + else { + context.emitter.trigger('_noEventResize'); + } + // reset all internal state + _this.draggingSeg = null; + _this.relevantEvents = null; + _this.validMutation = null; + // okay to keep eventInstance around. useful to set it in handlePointerDown + }; + var component = settings.component; + var dragging = _this.dragging = new FeaturefulElementDragging(settings.el); + dragging.pointer.selector = '.fc-event-resizer'; + dragging.touchScrollAllowed = false; + dragging.autoScroller.isEnabled = component.context.options.dragScroll; + var hitDragging = _this.hitDragging = new HitDragging(_this.dragging, interactionSettingsToStore(settings)); + hitDragging.emitter.on('pointerdown', _this.handlePointerDown); + hitDragging.emitter.on('dragstart', _this.handleDragStart); + hitDragging.emitter.on('hitupdate', _this.handleHitUpdate); + hitDragging.emitter.on('dragend', _this.handleDragEnd); + return _this; + } + EventResizing.prototype.destroy = function () { + this.dragging.destroy(); + }; + EventResizing.prototype.querySegEl = function (ev) { + return elementClosest(ev.subjectEl, '.fc-event'); + }; + return EventResizing; + }(Interaction)); + function computeMutation(hit0, hit1, isFromStart, instanceRange) { + var dateEnv = hit0.context.dateEnv; + var date0 = hit0.dateSpan.range.start; + var date1 = hit1.dateSpan.range.start; + var delta = diffDates(date0, date1, dateEnv, hit0.largeUnit); + if (isFromStart) { + if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) { + return { startDelta: delta }; + } + } + else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) { + return { endDelta: delta }; + } + return null; + } + + var UnselectAuto = /** @class */ (function () { + function UnselectAuto(context) { + var _this = this; + this.context = context; + this.isRecentPointerDateSelect = false; // wish we could use a selector to detect date selection, but uses hit system + this.matchesCancel = false; + this.matchesEvent = false; + this.onSelect = function (selectInfo) { + if (selectInfo.jsEvent) { + _this.isRecentPointerDateSelect = true; + } + }; + this.onDocumentPointerDown = function (pev) { + var unselectCancel = _this.context.options.unselectCancel; + var downEl = getEventTargetViaRoot(pev.origEvent); + _this.matchesCancel = !!elementClosest(downEl, unselectCancel); + _this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR); // interaction started on an event? + }; + this.onDocumentPointerUp = function (pev) { + var context = _this.context; + var documentPointer = _this.documentPointer; + var calendarState = context.getCurrentData(); + // touch-scrolling should never unfocus any type of selection + if (!documentPointer.wasTouchScroll) { + if (calendarState.dateSelection && // an existing date selection? + !_this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp? + ) { + var unselectAuto = context.options.unselectAuto; + if (unselectAuto && (!unselectAuto || !_this.matchesCancel)) { + context.calendarApi.unselect(pev); + } + } + if (calendarState.eventSelection && // an existing event selected? + !_this.matchesEvent // interaction DIDN'T start on an event + ) { + context.dispatch({ type: 'UNSELECT_EVENT' }); + } + } + _this.isRecentPointerDateSelect = false; + }; + var documentPointer = this.documentPointer = new PointerDragging(document); + documentPointer.shouldIgnoreMove = true; + documentPointer.shouldWatchScroll = false; + documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown); + documentPointer.emitter.on('pointerup', this.onDocumentPointerUp); + /* + TODO: better way to know about whether there was a selection with the pointer + */ + context.emitter.on('select', this.onSelect); + } + UnselectAuto.prototype.destroy = function () { + this.context.emitter.off('select', this.onSelect); + this.documentPointer.destroy(); + }; + return UnselectAuto; + }()); + + var OPTION_REFINERS$3 = { + fixedMirrorParent: identity, + }; + var LISTENER_REFINERS = { + dateClick: identity, + eventDragStart: identity, + eventDragStop: identity, + eventDrop: identity, + eventResizeStart: identity, + eventResizeStop: identity, + eventResize: identity, + drop: identity, + eventReceive: identity, + eventLeave: identity, + }; + + /* + Given an already instantiated draggable object for one-or-more elements, + Interprets any dragging as an attempt to drag an events that lives outside + of a calendar onto a calendar. + */ + var ExternalElementDragging = /** @class */ (function () { + function ExternalElementDragging(dragging, suppliedDragMeta) { + var _this = this; + this.receivingContext = null; + this.droppableEvent = null; // will exist for all drags, even if create:false + this.suppliedDragMeta = null; + this.dragMeta = null; + this.handleDragStart = function (ev) { + _this.dragMeta = _this.buildDragMeta(ev.subjectEl); + }; + this.handleHitUpdate = function (hit, isFinal, ev) { + var dragging = _this.hitDragging.dragging; + var receivingContext = null; + var droppableEvent = null; + var isInvalid = false; + var interaction = { + affectedEvents: createEmptyEventStore(), + mutatedEvents: createEmptyEventStore(), + isEvent: _this.dragMeta.create, + }; + if (hit) { + receivingContext = hit.context; + if (_this.canDropElOnCalendar(ev.subjectEl, receivingContext)) { + droppableEvent = computeEventForDateSpan(hit.dateSpan, _this.dragMeta, receivingContext); + interaction.mutatedEvents = eventTupleToStore(droppableEvent); + isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext); + if (isInvalid) { + interaction.mutatedEvents = createEmptyEventStore(); + droppableEvent = null; + } + } + } + _this.displayDrag(receivingContext, interaction); + // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?) + // TODO: wish we could somehow wait for dispatch to guarantee render + dragging.setMirrorIsVisible(isFinal || !droppableEvent || !document.querySelector('.fc-event-mirror')); + if (!isInvalid) { + enableCursor(); + } + else { + disableCursor(); + } + if (!isFinal) { + dragging.setMirrorNeedsRevert(!droppableEvent); + _this.receivingContext = receivingContext; + _this.droppableEvent = droppableEvent; + } + }; + this.handleDragEnd = function (pev) { + var _a = _this, receivingContext = _a.receivingContext, droppableEvent = _a.droppableEvent; + _this.clearDrag(); + if (receivingContext && droppableEvent) { + var finalHit = _this.hitDragging.finalHit; + var finalView = finalHit.context.viewApi; + var dragMeta = _this.dragMeta; + receivingContext.emitter.trigger('drop', __assign(__assign({}, buildDatePointApiWithContext(finalHit.dateSpan, receivingContext)), { draggedEl: pev.subjectEl, jsEvent: pev.origEvent, view: finalView })); + if (dragMeta.create) { + var addingEvents_1 = eventTupleToStore(droppableEvent); + receivingContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: addingEvents_1, + }); + if (pev.isTouch) { + receivingContext.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: droppableEvent.instance.instanceId, + }); + } + // signal that an external event landed + receivingContext.emitter.trigger('eventReceive', { + event: new EventApi(receivingContext, droppableEvent.def, droppableEvent.instance), + relatedEvents: [], + revert: function () { + receivingContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: addingEvents_1, + }); + }, + draggedEl: pev.subjectEl, + view: finalView, + }); + } + } + _this.receivingContext = null; + _this.droppableEvent = null; + }; + var hitDragging = this.hitDragging = new HitDragging(dragging, interactionSettingsStore); + hitDragging.requireInitial = false; // will start outside of a component + hitDragging.emitter.on('dragstart', this.handleDragStart); + hitDragging.emitter.on('hitupdate', this.handleHitUpdate); + hitDragging.emitter.on('dragend', this.handleDragEnd); + this.suppliedDragMeta = suppliedDragMeta; + } + ExternalElementDragging.prototype.buildDragMeta = function (subjectEl) { + if (typeof this.suppliedDragMeta === 'object') { + return parseDragMeta(this.suppliedDragMeta); + } + if (typeof this.suppliedDragMeta === 'function') { + return parseDragMeta(this.suppliedDragMeta(subjectEl)); + } + return getDragMetaFromEl(subjectEl); + }; + ExternalElementDragging.prototype.displayDrag = function (nextContext, state) { + var prevContext = this.receivingContext; + if (prevContext && prevContext !== nextContext) { + prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + if (nextContext) { + nextContext.dispatch({ type: 'SET_EVENT_DRAG', state: state }); + } + }; + ExternalElementDragging.prototype.clearDrag = function () { + if (this.receivingContext) { + this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }); + } + }; + ExternalElementDragging.prototype.canDropElOnCalendar = function (el, receivingContext) { + var dropAccept = receivingContext.options.dropAccept; + if (typeof dropAccept === 'function') { + return dropAccept.call(receivingContext.calendarApi, el); + } + if (typeof dropAccept === 'string' && dropAccept) { + return Boolean(elementMatches(el, dropAccept)); + } + return true; + }; + return ExternalElementDragging; + }()); + // Utils for computing event store from the DragMeta + // ---------------------------------------------------------------------------------------------------- + function computeEventForDateSpan(dateSpan, dragMeta, context) { + var defProps = __assign({}, dragMeta.leftoverProps); + for (var _i = 0, _a = context.pluginHooks.externalDefTransforms; _i < _a.length; _i++) { + var transform = _a[_i]; + __assign(defProps, transform(dateSpan, dragMeta)); + } + var _b = refineEventDef(defProps, context), refined = _b.refined, extra = _b.extra; + var def = parseEventDef(refined, extra, dragMeta.sourceId, dateSpan.allDay, context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd + context); + var start = dateSpan.range.start; + // only rely on time info if drop zone is all-day, + // otherwise, we already know the time + if (dateSpan.allDay && dragMeta.startTime) { + start = context.dateEnv.add(start, dragMeta.startTime); + } + var end = dragMeta.duration ? + context.dateEnv.add(start, dragMeta.duration) : + getDefaultEventEnd(dateSpan.allDay, start, context); + var instance = createEventInstance(def.defId, { start: start, end: end }); + return { def: def, instance: instance }; + } + // Utils for extracting data from element + // ---------------------------------------------------------------------------------------------------- + function getDragMetaFromEl(el) { + var str = getEmbeddedElData(el, 'event'); + var obj = str ? + JSON.parse(str) : + { create: false }; // if no embedded data, assume no event creation + return parseDragMeta(obj); + } + config.dataAttrPrefix = ''; + function getEmbeddedElData(el, name) { + var prefix = config.dataAttrPrefix; + var prefixedName = (prefix ? prefix + '-' : '') + name; + return el.getAttribute('data-' + prefixedName) || ''; + } + + /* + Makes an element (that is *external* to any calendar) draggable. + Can pass in data that determines how an event will be created when dropped onto a calendar. + Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system. + */ + var ExternalDraggable = /** @class */ (function () { + function ExternalDraggable(el, settings) { + var _this = this; + if (settings === void 0) { settings = {}; } + this.handlePointerDown = function (ev) { + var dragging = _this.dragging; + var _a = _this.settings, minDistance = _a.minDistance, longPressDelay = _a.longPressDelay; + dragging.minDistance = + minDistance != null ? + minDistance : + (ev.isTouch ? 0 : BASE_OPTION_DEFAULTS.eventDragMinDistance); + dragging.delay = + ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv + (longPressDelay != null ? longPressDelay : BASE_OPTION_DEFAULTS.longPressDelay) : + 0; + }; + this.handleDragStart = function (ev) { + if (ev.isTouch && + _this.dragging.delay && + ev.subjectEl.classList.contains('fc-event')) { + _this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected'); + } + }; + this.settings = settings; + var dragging = this.dragging = new FeaturefulElementDragging(el); + dragging.touchScrollAllowed = false; + if (settings.itemSelector != null) { + dragging.pointer.selector = settings.itemSelector; + } + if (settings.appendTo != null) { + dragging.mirror.parentNode = settings.appendTo; // TODO: write tests + } + dragging.emitter.on('pointerdown', this.handlePointerDown); + dragging.emitter.on('dragstart', this.handleDragStart); + new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new + } + ExternalDraggable.prototype.destroy = function () { + this.dragging.destroy(); + }; + return ExternalDraggable; + }()); + + /* + Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements. + The third-party system is responsible for drawing the visuals effects of the drag. + This class simply monitors for pointer movements and fires events. + It also has the ability to hide the moving element (the "mirror") during the drag. + */ + var InferredElementDragging = /** @class */ (function (_super) { + __extends(InferredElementDragging, _super); + function InferredElementDragging(containerEl) { + var _this = _super.call(this, containerEl) || this; + _this.shouldIgnoreMove = false; + _this.mirrorSelector = ''; + _this.currentMirrorEl = null; + _this.handlePointerDown = function (ev) { + _this.emitter.trigger('pointerdown', ev); + if (!_this.shouldIgnoreMove) { + // fire dragstart right away. does not support delay or min-distance + _this.emitter.trigger('dragstart', ev); + } + }; + _this.handlePointerMove = function (ev) { + if (!_this.shouldIgnoreMove) { + _this.emitter.trigger('dragmove', ev); + } + }; + _this.handlePointerUp = function (ev) { + _this.emitter.trigger('pointerup', ev); + if (!_this.shouldIgnoreMove) { + // fire dragend right away. does not support a revert animation + _this.emitter.trigger('dragend', ev); + } + }; + var pointer = _this.pointer = new PointerDragging(containerEl); + pointer.emitter.on('pointerdown', _this.handlePointerDown); + pointer.emitter.on('pointermove', _this.handlePointerMove); + pointer.emitter.on('pointerup', _this.handlePointerUp); + return _this; + } + InferredElementDragging.prototype.destroy = function () { + this.pointer.destroy(); + }; + InferredElementDragging.prototype.setIgnoreMove = function (bool) { + this.shouldIgnoreMove = bool; + }; + InferredElementDragging.prototype.setMirrorIsVisible = function (bool) { + if (bool) { + // restore a previously hidden element. + // use the reference in case the selector class has already been removed. + if (this.currentMirrorEl) { + this.currentMirrorEl.style.visibility = ''; + this.currentMirrorEl = null; + } + } + else { + var mirrorEl = this.mirrorSelector + // TODO: somehow query FullCalendars WITHIN shadow-roots + ? document.querySelector(this.mirrorSelector) + : null; + if (mirrorEl) { + this.currentMirrorEl = mirrorEl; + mirrorEl.style.visibility = 'hidden'; + } + } + }; + return InferredElementDragging; + }(ElementDragging)); + + /* + Bridges third-party drag-n-drop systems with FullCalendar. + Must be instantiated and destroyed by caller. + */ + var ThirdPartyDraggable = /** @class */ (function () { + function ThirdPartyDraggable(containerOrSettings, settings) { + var containerEl = document; + if ( + // wish we could just test instanceof EventTarget, but doesn't work in IE11 + containerOrSettings === document || + containerOrSettings instanceof Element) { + containerEl = containerOrSettings; + settings = settings || {}; + } + else { + settings = (containerOrSettings || {}); + } + var dragging = this.dragging = new InferredElementDragging(containerEl); + if (typeof settings.itemSelector === 'string') { + dragging.pointer.selector = settings.itemSelector; + } + else if (containerEl === document) { + dragging.pointer.selector = '[data-event]'; + } + if (typeof settings.mirrorSelector === 'string') { + dragging.mirrorSelector = settings.mirrorSelector; + } + new ExternalElementDragging(dragging, settings.eventData); // eslint-disable-line no-new + } + ThirdPartyDraggable.prototype.destroy = function () { + this.dragging.destroy(); + }; + return ThirdPartyDraggable; + }()); + + var interactionPlugin = createPlugin({ + componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing], + calendarInteractions: [UnselectAuto], + elementDraggingImpl: FeaturefulElementDragging, + optionRefiners: OPTION_REFINERS$3, + listenerRefiners: LISTENER_REFINERS, + }); + + /* An abstract class for the daygrid views, as well as month view. Renders one or more rows of day cells. + ----------------------------------------------------------------------------------------------------------------------*/ + // It is a manager for a Table subcomponent, which does most of the heavy lifting. + // It is responsible for managing width/height. + var TableView = /** @class */ (function (_super) { + __extends(TableView, _super); + function TableView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.headerElRef = createRef(); + return _this; + } + TableView.prototype.renderSimpleLayout = function (headerRowContent, bodyContent) { + var _a = this, props = _a.props, context = _a.context; + var sections = []; + var stickyHeaderDates = getStickyHeaderDates(context.options); + if (headerRowContent) { + sections.push({ + type: 'header', + key: 'header', + isSticky: stickyHeaderDates, + chunk: { + elRef: this.headerElRef, + tableClassName: 'fc-col-header', + rowContent: headerRowContent, + }, + }); + } + sections.push({ + type: 'body', + key: 'body', + liquid: true, + chunk: { content: bodyContent }, + }); + return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: ['fc-daygrid'].concat(classNames).join(' ') }, + createElement(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [] /* TODO: make optional? */, sections: sections }))); })); + }; + TableView.prototype.renderHScrollLayout = function (headerRowContent, bodyContent, colCnt, dayMinWidth) { + var ScrollGrid = this.context.pluginHooks.scrollGridImpl; + if (!ScrollGrid) { + throw new Error('No ScrollGrid implementation'); + } + var _a = this, props = _a.props, context = _a.context; + var stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options); + var stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options); + var sections = []; + if (headerRowContent) { + sections.push({ + type: 'header', + key: 'header', + isSticky: stickyHeaderDates, + chunks: [{ + key: 'main', + elRef: this.headerElRef, + tableClassName: 'fc-col-header', + rowContent: headerRowContent, + }], + }); + } + sections.push({ + type: 'body', + key: 'body', + liquid: true, + chunks: [{ + key: 'main', + content: bodyContent, + }], + }); + if (stickyFooterScrollbar) { + sections.push({ + type: 'footer', + key: 'footer', + isSticky: true, + chunks: [{ + key: 'main', + content: renderScrollShim, + }], + }); + } + return (createElement(ViewRoot, { viewSpec: context.viewSpec }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: ['fc-daygrid'].concat(classNames).join(' ') }, + createElement(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, colGroups: [{ cols: [{ span: colCnt, minWidth: dayMinWidth }] }], sections: sections }))); })); + }; + return TableView; + }(DateComponent)); + + function splitSegsByRow(segs, rowCnt) { + var byRow = []; + for (var i = 0; i < rowCnt; i += 1) { + byRow[i] = []; + } + for (var _i = 0, segs_1 = segs; _i < segs_1.length; _i++) { + var seg = segs_1[_i]; + byRow[seg.row].push(seg); + } + return byRow; + } + function splitSegsByFirstCol(segs, colCnt) { + var byCol = []; + for (var i = 0; i < colCnt; i += 1) { + byCol[i] = []; + } + for (var _i = 0, segs_2 = segs; _i < segs_2.length; _i++) { + var seg = segs_2[_i]; + byCol[seg.firstCol].push(seg); + } + return byCol; + } + function splitInteractionByRow(ui, rowCnt) { + var byRow = []; + if (!ui) { + for (var i = 0; i < rowCnt; i += 1) { + byRow[i] = null; + } + } + else { + for (var i = 0; i < rowCnt; i += 1) { + byRow[i] = { + affectedInstances: ui.affectedInstances, + isEvent: ui.isEvent, + segs: [], + }; + } + for (var _i = 0, _a = ui.segs; _i < _a.length; _i++) { + var seg = _a[_i]; + byRow[seg.row].segs.push(seg); + } + } + return byRow; + } + + var TableCellTop = /** @class */ (function (_super) { + __extends(TableCellTop, _super); + function TableCellTop() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableCellTop.prototype.render = function () { + var props = this.props; + var navLinkAttrs = this.context.options.navLinks + ? { 'data-navlink': buildNavLinkData(props.date), tabIndex: 0 } + : {}; + return (createElement(DayCellContent, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, showDayNumber: props.showDayNumber, extraHookProps: props.extraHookProps, defaultContent: renderTopInner }, function (innerElRef, innerContent) { return ((innerContent || props.forceDayTop) && (createElement("div", { className: "fc-daygrid-day-top", ref: innerElRef }, + createElement("a", __assign({ className: "fc-daygrid-day-number" }, navLinkAttrs), innerContent || createElement(Fragment, null, "\u00A0"))))); })); + }; + return TableCellTop; + }(BaseComponent)); + function renderTopInner(props) { + return props.dayNumberText; + } + + var DEFAULT_TABLE_EVENT_TIME_FORMAT = createFormatter({ + hour: 'numeric', + minute: '2-digit', + omitZeroMinute: true, + meridiem: 'narrow', + }); + function hasListItemDisplay(seg) { + var display = seg.eventRange.ui.display; + return display === 'list-item' || (display === 'auto' && + !seg.eventRange.def.allDay && + seg.firstCol === seg.lastCol && // can't be multi-day + seg.isStart && // " + seg.isEnd // " + ); + } + + var TableBlockEvent = /** @class */ (function (_super) { + __extends(TableBlockEvent, _super); + function TableBlockEvent() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableBlockEvent.prototype.render = function () { + var props = this.props; + return (createElement(StandardEvent, __assign({}, props, { extraClassNames: ['fc-daygrid-event', 'fc-daygrid-block-event', 'fc-h-event'], defaultTimeFormat: DEFAULT_TABLE_EVENT_TIME_FORMAT, defaultDisplayEventEnd: props.defaultDisplayEventEnd, disableResizing: !props.seg.eventRange.def.allDay }))); + }; + return TableBlockEvent; + }(BaseComponent)); + + var TableListItemEvent = /** @class */ (function (_super) { + __extends(TableListItemEvent, _super); + function TableListItemEvent() { + return _super !== null && _super.apply(this, arguments) || this; + } + TableListItemEvent.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var timeFormat = context.options.eventTimeFormat || DEFAULT_TABLE_EVENT_TIME_FORMAT; + var timeText = buildSegTimeText(props.seg, timeFormat, context, true, props.defaultDisplayEventEnd); + return (createElement(EventRoot, { seg: props.seg, timeText: timeText, defaultContent: renderInnerContent$2, isDragging: props.isDragging, isResizing: false, isDateSelecting: false, isSelected: props.isSelected, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent) { return ( // we don't use styles! + createElement("a", __assign({ className: ['fc-daygrid-event', 'fc-daygrid-dot-event'].concat(classNames).join(' '), ref: rootElRef }, getSegAnchorAttrs(props.seg)), innerContent)); })); + }; + return TableListItemEvent; + }(BaseComponent)); + function renderInnerContent$2(innerProps) { + return (createElement(Fragment, null, + createElement("div", { className: "fc-daygrid-event-dot", style: { borderColor: innerProps.borderColor || innerProps.backgroundColor } }), + innerProps.timeText && (createElement("div", { className: "fc-event-time" }, innerProps.timeText)), + createElement("div", { className: "fc-event-title" }, innerProps.event.title || createElement(Fragment, null, "\u00A0")))); + } + function getSegAnchorAttrs(seg) { + var url = seg.eventRange.def.url; + return url ? { href: url } : {}; + } + + var TableCellMoreLink = /** @class */ (function (_super) { + __extends(TableCellMoreLink, _super); + function TableCellMoreLink() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.compileSegs = memoize(compileSegs); + return _this; + } + TableCellMoreLink.prototype.render = function () { + var props = this.props; + var _a = this.compileSegs(props.singlePlacements), allSegs = _a.allSegs, invisibleSegs = _a.invisibleSegs; + return (createElement(MoreLinkRoot, { dateProfile: props.dateProfile, todayRange: props.todayRange, allDayDate: props.allDayDate, moreCnt: props.moreCnt, allSegs: allSegs, hiddenSegs: invisibleSegs, alignmentElRef: props.alignmentElRef, alignGridTop: props.alignGridTop, extraDateSpan: props.extraDateSpan, popoverContent: function () { + var isForcedInvisible = (props.eventDrag ? props.eventDrag.affectedInstances : null) || + (props.eventResize ? props.eventResize.affectedInstances : null) || + {}; + return (createElement(Fragment, null, allSegs.map(function (seg) { + var instanceId = seg.eventRange.instance.instanceId; + return (createElement("div", { className: "fc-daygrid-event-harness", key: instanceId, style: { + visibility: isForcedInvisible[instanceId] ? 'hidden' : '', + } }, hasListItemDisplay(seg) ? (createElement(TableListItemEvent, __assign({ seg: seg, isDragging: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange)))) : (createElement(TableBlockEvent, __assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === props.eventSelection, defaultDisplayEventEnd: false }, getSegMeta(seg, props.todayRange)))))); + }))); + } }, function (rootElRef, classNames, innerElRef, innerContent, handleClick) { return (createElement("a", { ref: rootElRef, className: ['fc-daygrid-more-link'].concat(classNames).join(' '), onClick: handleClick }, innerContent)); })); + }; + return TableCellMoreLink; + }(BaseComponent)); + function compileSegs(singlePlacements) { + var allSegs = []; + var invisibleSegs = []; + for (var _i = 0, singlePlacements_1 = singlePlacements; _i < singlePlacements_1.length; _i++) { + var placement = singlePlacements_1[_i]; + allSegs.push(placement.seg); + if (!placement.isVisible) { + invisibleSegs.push(placement.seg); + } + } + return { allSegs: allSegs, invisibleSegs: invisibleSegs }; + } + + var DEFAULT_WEEK_NUM_FORMAT$1 = createFormatter({ week: 'narrow' }); + var TableCell = /** @class */ (function (_super) { + __extends(TableCell, _super); + function TableCell() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + _this.handleRootEl = function (el) { + setRef(_this.rootElRef, el); + setRef(_this.props.elRef, el); + }; + return _this; + } + TableCell.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context, rootElRef = _a.rootElRef; + var options = context.options; + var date = props.date, dateProfile = props.dateProfile; + var navLinkAttrs = options.navLinks + ? { 'data-navlink': buildNavLinkData(date, 'week'), tabIndex: 0 } + : {}; + return (createElement(DayCellRoot, { date: date, dateProfile: dateProfile, todayRange: props.todayRange, showDayNumber: props.showDayNumber, extraHookProps: props.extraHookProps, elRef: this.handleRootEl }, function (dayElRef, dayClassNames, rootDataAttrs, isDisabled) { return (createElement("td", __assign({ ref: dayElRef, className: ['fc-daygrid-day'].concat(dayClassNames, props.extraClassNames || []).join(' ') }, rootDataAttrs, props.extraDataAttrs), + createElement("div", { className: "fc-daygrid-day-frame fc-scrollgrid-sync-inner", ref: props.innerElRef /* different from hook system! RENAME */ }, + props.showWeekNumber && (createElement(WeekNumberRoot, { date: date, defaultFormat: DEFAULT_WEEK_NUM_FORMAT$1 }, function (weekElRef, weekClassNames, innerElRef, innerContent) { return (createElement("a", __assign({ ref: weekElRef, className: ['fc-daygrid-week-number'].concat(weekClassNames).join(' ') }, navLinkAttrs), innerContent)); })), + !isDisabled && (createElement(TableCellTop, { date: date, dateProfile: dateProfile, showDayNumber: props.showDayNumber, forceDayTop: props.forceDayTop, todayRange: props.todayRange, extraHookProps: props.extraHookProps })), + createElement("div", { className: "fc-daygrid-day-events", ref: props.fgContentElRef }, + props.fgContent, + createElement("div", { className: "fc-daygrid-day-bottom", style: { marginTop: props.moreMarginTop } }, + createElement(TableCellMoreLink, { allDayDate: date, singlePlacements: props.singlePlacements, moreCnt: props.moreCnt, alignmentElRef: rootElRef, alignGridTop: !props.showDayNumber, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange }))), + createElement("div", { className: "fc-daygrid-day-bg" }, props.bgContent)))); })); + }; + return TableCell; + }(DateComponent)); + + function computeFgSegPlacement(segs, // assumed already sorted + dayMaxEvents, dayMaxEventRows, strictOrder, eventInstanceHeights, maxContentHeight, cells) { + var hierarchy = new DayGridSegHierarchy(); + hierarchy.allowReslicing = true; + hierarchy.strictOrder = strictOrder; + if (dayMaxEvents === true || dayMaxEventRows === true) { + hierarchy.maxCoord = maxContentHeight; + hierarchy.hiddenConsumes = true; + } + else if (typeof dayMaxEvents === 'number') { + hierarchy.maxStackCnt = dayMaxEvents; + } + else if (typeof dayMaxEventRows === 'number') { + hierarchy.maxStackCnt = dayMaxEventRows; + hierarchy.hiddenConsumes = true; + } + // create segInputs only for segs with known heights + var segInputs = []; + var unknownHeightSegs = []; + for (var i = 0; i < segs.length; i += 1) { + var seg = segs[i]; + var instanceId = seg.eventRange.instance.instanceId; + var eventHeight = eventInstanceHeights[instanceId]; + if (eventHeight != null) { + segInputs.push({ + index: i, + thickness: eventHeight, + span: { + start: seg.firstCol, + end: seg.lastCol + 1, + }, + }); + } + else { + unknownHeightSegs.push(seg); + } + } + var hiddenEntries = hierarchy.addSegs(segInputs); + var segRects = hierarchy.toRects(); + var _a = placeRects(segRects, segs, cells), singleColPlacements = _a.singleColPlacements, multiColPlacements = _a.multiColPlacements, leftoverMargins = _a.leftoverMargins; + var moreCnts = []; + var moreMarginTops = []; + // add segs with unknown heights + for (var _i = 0, unknownHeightSegs_1 = unknownHeightSegs; _i < unknownHeightSegs_1.length; _i++) { + var seg = unknownHeightSegs_1[_i]; + multiColPlacements[seg.firstCol].push({ + seg: seg, + isVisible: false, + isAbsolute: true, + absoluteTop: 0, + marginTop: 0, + }); + for (var col = seg.firstCol; col <= seg.lastCol; col += 1) { + singleColPlacements[col].push({ + seg: resliceSeg(seg, col, col + 1, cells), + isVisible: false, + isAbsolute: false, + absoluteTop: 0, + marginTop: 0, + }); + } + } + // add the hidden entries + for (var col = 0; col < cells.length; col += 1) { + moreCnts.push(0); + } + for (var _b = 0, hiddenEntries_1 = hiddenEntries; _b < hiddenEntries_1.length; _b++) { + var hiddenEntry = hiddenEntries_1[_b]; + var seg = segs[hiddenEntry.index]; + var hiddenSpan = hiddenEntry.span; + multiColPlacements[hiddenSpan.start].push({ + seg: resliceSeg(seg, hiddenSpan.start, hiddenSpan.end, cells), + isVisible: false, + isAbsolute: true, + absoluteTop: 0, + marginTop: 0, + }); + for (var col = hiddenSpan.start; col < hiddenSpan.end; col += 1) { + moreCnts[col] += 1; + singleColPlacements[col].push({ + seg: resliceSeg(seg, col, col + 1, cells), + isVisible: false, + isAbsolute: false, + absoluteTop: 0, + marginTop: 0, + }); + } + } + // deal with leftover margins + for (var col = 0; col < cells.length; col += 1) { + moreMarginTops.push(leftoverMargins[col]); + } + return { singleColPlacements: singleColPlacements, multiColPlacements: multiColPlacements, moreCnts: moreCnts, moreMarginTops: moreMarginTops }; + } + // rects ordered by top coord, then left + function placeRects(allRects, segs, cells) { + var rectsByEachCol = groupRectsByEachCol(allRects, cells.length); + var singleColPlacements = []; + var multiColPlacements = []; + var leftoverMargins = []; + for (var col = 0; col < cells.length; col += 1) { + var rects = rectsByEachCol[col]; + // compute all static segs in singlePlacements + var singlePlacements = []; + var currentHeight = 0; + var currentMarginTop = 0; + for (var _i = 0, rects_1 = rects; _i < rects_1.length; _i++) { + var rect = rects_1[_i]; + var seg = segs[rect.index]; + singlePlacements.push({ + seg: resliceSeg(seg, col, col + 1, cells), + isVisible: true, + isAbsolute: false, + absoluteTop: rect.levelCoord, + marginTop: rect.levelCoord - currentHeight, + }); + currentHeight = rect.levelCoord + rect.thickness; + } + // compute mixed static/absolute segs in multiPlacements + var multiPlacements = []; + currentHeight = 0; + currentMarginTop = 0; + for (var _a = 0, rects_2 = rects; _a < rects_2.length; _a++) { + var rect = rects_2[_a]; + var seg = segs[rect.index]; + var isAbsolute = rect.span.end - rect.span.start > 1; // multi-column? + var isFirstCol = rect.span.start === col; + currentMarginTop += rect.levelCoord - currentHeight; // amount of space since bottom of previous seg + currentHeight = rect.levelCoord + rect.thickness; // height will now be bottom of current seg + if (isAbsolute) { + currentMarginTop += rect.thickness; + if (isFirstCol) { + multiPlacements.push({ + seg: resliceSeg(seg, rect.span.start, rect.span.end, cells), + isVisible: true, + isAbsolute: true, + absoluteTop: rect.levelCoord, + marginTop: 0, + }); + } + } + else if (isFirstCol) { + multiPlacements.push({ + seg: resliceSeg(seg, rect.span.start, rect.span.end, cells), + isVisible: true, + isAbsolute: false, + absoluteTop: rect.levelCoord, + marginTop: currentMarginTop, // claim the margin + }); + currentMarginTop = 0; + } + } + singleColPlacements.push(singlePlacements); + multiColPlacements.push(multiPlacements); + leftoverMargins.push(currentMarginTop); + } + return { singleColPlacements: singleColPlacements, multiColPlacements: multiColPlacements, leftoverMargins: leftoverMargins }; + } + function groupRectsByEachCol(rects, colCnt) { + var rectsByEachCol = []; + for (var col = 0; col < colCnt; col += 1) { + rectsByEachCol.push([]); + } + for (var _i = 0, rects_3 = rects; _i < rects_3.length; _i++) { + var rect = rects_3[_i]; + for (var col = rect.span.start; col < rect.span.end; col += 1) { + rectsByEachCol[col].push(rect); + } + } + return rectsByEachCol; + } + function resliceSeg(seg, spanStart, spanEnd, cells) { + if (seg.firstCol === spanStart && seg.lastCol === spanEnd - 1) { + return seg; + } + var eventRange = seg.eventRange; + var origRange = eventRange.range; + var slicedRange = intersectRanges(origRange, { + start: cells[spanStart].date, + end: addDays(cells[spanEnd - 1].date, 1), + }); + return __assign(__assign({}, seg), { firstCol: spanStart, lastCol: spanEnd - 1, eventRange: { + def: eventRange.def, + ui: __assign(__assign({}, eventRange.ui), { durationEditable: false }), + instance: eventRange.instance, + range: slicedRange, + }, isStart: seg.isStart && slicedRange.start.valueOf() === origRange.start.valueOf(), isEnd: seg.isEnd && slicedRange.end.valueOf() === origRange.end.valueOf() }); + } + var DayGridSegHierarchy = /** @class */ (function (_super) { + __extends(DayGridSegHierarchy, _super); + function DayGridSegHierarchy() { + var _this = _super !== null && _super.apply(this, arguments) || this; + // config + _this.hiddenConsumes = false; + // allows us to keep hidden entries in the hierarchy so they take up space + _this.forceHidden = {}; + return _this; + } + DayGridSegHierarchy.prototype.addSegs = function (segInputs) { + var _this = this; + var hiddenSegs = _super.prototype.addSegs.call(this, segInputs); + var entriesByLevel = this.entriesByLevel; + var excludeHidden = function (entry) { return !_this.forceHidden[buildEntryKey(entry)]; }; + // remove the forced-hidden segs + for (var level = 0; level < entriesByLevel.length; level += 1) { + entriesByLevel[level] = entriesByLevel[level].filter(excludeHidden); + } + return hiddenSegs; + }; + DayGridSegHierarchy.prototype.handleInvalidInsertion = function (insertion, entry, hiddenEntries) { + var _a = this, entriesByLevel = _a.entriesByLevel, forceHidden = _a.forceHidden; + var touchingEntry = insertion.touchingEntry, touchingLevel = insertion.touchingLevel, touchingLateral = insertion.touchingLateral; + if (this.hiddenConsumes && touchingEntry) { + var touchingEntryId = buildEntryKey(touchingEntry); + // if not already hidden + if (!forceHidden[touchingEntryId]) { + if (this.allowReslicing) { + var placeholderEntry = __assign(__assign({}, touchingEntry), { span: intersectSpans(touchingEntry.span, entry.span) }); + var placeholderEntryId = buildEntryKey(placeholderEntry); + forceHidden[placeholderEntryId] = true; + entriesByLevel[touchingLevel][touchingLateral] = placeholderEntry; // replace touchingEntry with our placeholder + this.splitEntry(touchingEntry, entry, hiddenEntries); // split up the touchingEntry, reinsert it + } + else { + forceHidden[touchingEntryId] = true; + hiddenEntries.push(touchingEntry); + } + } + } + return _super.prototype.handleInvalidInsertion.call(this, insertion, entry, hiddenEntries); + }; + return DayGridSegHierarchy; + }(SegHierarchy)); + + var TableRow = /** @class */ (function (_super) { + __extends(TableRow, _super); + function TableRow() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.cellElRefs = new RefMap(); // the ? + createElement("tr", { className: "fc-scrollgrid-section" }, + createElement("td", { className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))), + }); + } + sections.push({ + type: 'body', + key: 'body', + liquid: true, + expandRows: Boolean(context.options.expandRows), + chunk: { + scrollerElRef: this.scrollerElRef, + content: timeContent, + }, + }); + return (createElement(ViewRoot, { viewSpec: context.viewSpec, elRef: this.rootElRef }, function (rootElRef, classNames) { return (createElement("div", { className: ['fc-timegrid'].concat(classNames).join(' '), ref: rootElRef }, + createElement(SimpleScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: props.forPrint, cols: [{ width: 'shrink' }], sections: sections }))); })); + }; + TimeColsView.prototype.renderHScrollLayout = function (headerRowContent, allDayContent, timeContent, colCnt, dayMinWidth, slatMetas, slatCoords) { + var _this = this; + var ScrollGrid = this.context.pluginHooks.scrollGridImpl; + if (!ScrollGrid) { + throw new Error('No ScrollGrid implementation'); + } + var _a = this, context = _a.context, props = _a.props; + var stickyHeaderDates = !props.forPrint && getStickyHeaderDates(context.options); + var stickyFooterScrollbar = !props.forPrint && getStickyFooterScrollbar(context.options); + var sections = []; + if (headerRowContent) { + sections.push({ + type: 'header', + key: 'header', + isSticky: stickyHeaderDates, + syncRowHeights: true, + chunks: [ + { + key: 'axis', + rowContent: function (arg) { return (createElement("tr", null, _this.renderHeadAxis('day', arg.rowSyncHeights[0]))); }, + }, + { + key: 'cols', + elRef: this.headerElRef, + tableClassName: 'fc-col-header', + rowContent: headerRowContent, + }, + ], + }); + } + if (allDayContent) { + sections.push({ + type: 'body', + key: 'all-day', + syncRowHeights: true, + chunks: [ + { + key: 'axis', + rowContent: function (contentArg) { return (createElement("tr", null, _this.renderTableRowAxis(contentArg.rowSyncHeights[0]))); }, + }, + { + key: 'cols', + content: allDayContent, + }, + ], + }); + sections.push({ + key: 'all-day-divider', + type: 'body', + outerContent: ( // TODO: rename to cellContent so don't need to define ? + createElement("tr", { className: "fc-scrollgrid-section" }, + createElement("td", { colSpan: 2, className: 'fc-timegrid-divider ' + context.theme.getClass('tableCellShaded') }))), + }); + } + var isNowIndicator = context.options.nowIndicator; + sections.push({ + type: 'body', + key: 'body', + liquid: true, + expandRows: Boolean(context.options.expandRows), + chunks: [ + { + key: 'axis', + content: function (arg) { return ( + // TODO: make this now-indicator arrow more DRY with TimeColsContent + createElement("div", { className: "fc-timegrid-axis-chunk" }, + createElement("table", { style: { height: arg.expandRows ? arg.clientHeight : '' } }, + arg.tableColGroupNode, + createElement("tbody", null, + createElement(TimeBodyAxis, { slatMetas: slatMetas }))), + createElement("div", { className: "fc-timegrid-now-indicator-container" }, + createElement(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' /* hacky */ }, function (nowDate) { + var nowIndicatorTop = isNowIndicator && + slatCoords && + slatCoords.safeComputeTop(nowDate); // might return void + if (typeof nowIndicatorTop === 'number') { + return (createElement(NowIndicatorRoot, { isAxis: true, date: nowDate }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-arrow'].concat(classNames).join(' '), style: { top: nowIndicatorTop } }, innerContent)); })); + } + return null; + })))); }, + }, + { + key: 'cols', + scrollerElRef: this.scrollerElRef, + content: timeContent, + }, + ], + }); + if (stickyFooterScrollbar) { + sections.push({ + key: 'footer', + type: 'footer', + isSticky: true, + chunks: [ + { + key: 'axis', + content: renderScrollShim, + }, + { + key: 'cols', + content: renderScrollShim, + }, + ], + }); + } + return (createElement(ViewRoot, { viewSpec: context.viewSpec, elRef: this.rootElRef }, function (rootElRef, classNames) { return (createElement("div", { className: ['fc-timegrid'].concat(classNames).join(' '), ref: rootElRef }, + createElement(ScrollGrid, { liquid: !props.isHeightAuto && !props.forPrint, collapsibleWidth: false, colGroups: [ + { width: 'shrink', cols: [{ width: 'shrink' }] }, + { cols: [{ span: colCnt, minWidth: dayMinWidth }] }, + ], sections: sections }))); })); + }; + /* Dimensions + ------------------------------------------------------------------------------------------------------------------*/ + TimeColsView.prototype.getAllDayMaxEventProps = function () { + var _a = this.context.options, dayMaxEvents = _a.dayMaxEvents, dayMaxEventRows = _a.dayMaxEventRows; + if (dayMaxEvents === true || dayMaxEventRows === true) { // is auto? + dayMaxEvents = undefined; + dayMaxEventRows = AUTO_ALL_DAY_MAX_EVENT_ROWS; // make sure "auto" goes to a real number + } + return { dayMaxEvents: dayMaxEvents, dayMaxEventRows: dayMaxEventRows }; + }; + return TimeColsView; + }(DateComponent)); + function renderAllDayInner$1(hookProps) { + return hookProps.text; + } + + var TimeColsSlatsCoords = /** @class */ (function () { + function TimeColsSlatsCoords(positions, dateProfile, slotDuration) { + this.positions = positions; + this.dateProfile = dateProfile; + this.slotDuration = slotDuration; + } + TimeColsSlatsCoords.prototype.safeComputeTop = function (date) { + var dateProfile = this.dateProfile; + if (rangeContainsMarker(dateProfile.currentRange, date)) { + var startOfDayDate = startOfDay(date); + var timeMs = date.valueOf() - startOfDayDate.valueOf(); + if (timeMs >= asRoughMs(dateProfile.slotMinTime) && + timeMs < asRoughMs(dateProfile.slotMaxTime)) { + return this.computeTimeTop(createDuration(timeMs)); + } + } + return null; + }; + // Computes the top coordinate, relative to the bounds of the grid, of the given date. + // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight. + TimeColsSlatsCoords.prototype.computeDateTop = function (when, startOfDayDate) { + if (!startOfDayDate) { + startOfDayDate = startOfDay(when); + } + return this.computeTimeTop(createDuration(when.valueOf() - startOfDayDate.valueOf())); + }; + // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration). + // This is a makeshify way to compute the time-top. Assumes all slatMetas dates are uniform. + // Eventually allow computation with arbirary slat dates. + TimeColsSlatsCoords.prototype.computeTimeTop = function (duration) { + var _a = this, positions = _a.positions, dateProfile = _a.dateProfile; + var len = positions.els.length; + // floating-point value of # of slots covered + var slatCoverage = (duration.milliseconds - asRoughMs(dateProfile.slotMinTime)) / asRoughMs(this.slotDuration); + var slatIndex; + var slatRemainder; + // compute a floating-point number for how many slats should be progressed through. + // from 0 to number of slats (inclusive) + // constrained because slotMinTime/slotMaxTime might be customized. + slatCoverage = Math.max(0, slatCoverage); + slatCoverage = Math.min(len, slatCoverage); + // an integer index of the furthest whole slat + // from 0 to number slats (*exclusive*, so len-1) + slatIndex = Math.floor(slatCoverage); + slatIndex = Math.min(slatIndex, len - 1); + // how much further through the slatIndex slat (from 0.0-1.0) must be covered in addition. + // could be 1.0 if slatCoverage is covering *all* the slots + slatRemainder = slatCoverage - slatIndex; + return positions.tops[slatIndex] + + positions.getHeight(slatIndex) * slatRemainder; + }; + return TimeColsSlatsCoords; + }()); + + var TimeColsSlatsBody = /** @class */ (function (_super) { + __extends(TimeColsSlatsBody, _super); + function TimeColsSlatsBody() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeColsSlatsBody.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var options = context.options; + var slatElRefs = props.slatElRefs; + return (createElement("tbody", null, props.slatMetas.map(function (slatMeta, i) { + var hookProps = { + time: slatMeta.time, + date: context.dateEnv.toDate(slatMeta.date), + view: context.viewApi, + }; + var classNames = [ + 'fc-timegrid-slot', + 'fc-timegrid-slot-lane', + slatMeta.isLabeled ? '' : 'fc-timegrid-slot-minor', + ]; + return (createElement("tr", { key: slatMeta.key, ref: slatElRefs.createRef(slatMeta.key) }, + props.axis && (createElement(TimeColsAxisCell, __assign({}, slatMeta))), + createElement(RenderHook, { hookProps: hookProps, classNames: options.slotLaneClassNames, content: options.slotLaneContent, didMount: options.slotLaneDidMount, willUnmount: options.slotLaneWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-time": slatMeta.isoTimeStr }, innerContent)); }))); + }))); + }; + return TimeColsSlatsBody; + }(BaseComponent)); + + /* + for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL. + */ + var TimeColsSlats = /** @class */ (function (_super) { + __extends(TimeColsSlats, _super); + function TimeColsSlats() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + _this.slatElRefs = new RefMap(); + return _this; + } + TimeColsSlats.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + return (createElement("div", { className: "fc-timegrid-slots", ref: this.rootElRef }, + createElement("table", { className: context.theme.getClass('table'), style: { + minWidth: props.tableMinWidth, + width: props.clientWidth, + height: props.minHeight, + } }, + props.tableColGroupNode /* relies on there only being a single for the axis */, + createElement(TimeColsSlatsBody, { slatElRefs: this.slatElRefs, axis: props.axis, slatMetas: props.slatMetas })))); + }; + TimeColsSlats.prototype.componentDidMount = function () { + this.updateSizing(); + }; + TimeColsSlats.prototype.componentDidUpdate = function () { + this.updateSizing(); + }; + TimeColsSlats.prototype.componentWillUnmount = function () { + if (this.props.onCoords) { + this.props.onCoords(null); + } + }; + TimeColsSlats.prototype.updateSizing = function () { + var _a = this, context = _a.context, props = _a.props; + if (props.onCoords && + props.clientWidth !== null // means sizing has stabilized + ) { + var rootEl = this.rootElRef.current; + if (rootEl.offsetHeight) { // not hidden by css + props.onCoords(new TimeColsSlatsCoords(new PositionCache(this.rootElRef.current, collectSlatEls(this.slatElRefs.currentMap, props.slatMetas), false, true), this.props.dateProfile, context.options.slotDuration)); + } + } + }; + return TimeColsSlats; + }(BaseComponent)); + function collectSlatEls(elMap, slatMetas) { + return slatMetas.map(function (slatMeta) { return elMap[slatMeta.key]; }); + } + + function splitSegsByCol(segs, colCnt) { + var segsByCol = []; + var i; + for (i = 0; i < colCnt; i += 1) { + segsByCol.push([]); + } + if (segs) { + for (i = 0; i < segs.length; i += 1) { + segsByCol[segs[i].col].push(segs[i]); + } + } + return segsByCol; + } + function splitInteractionByCol(ui, colCnt) { + var byRow = []; + if (!ui) { + for (var i = 0; i < colCnt; i += 1) { + byRow[i] = null; + } + } + else { + for (var i = 0; i < colCnt; i += 1) { + byRow[i] = { + affectedInstances: ui.affectedInstances, + isEvent: ui.isEvent, + segs: [], + }; + } + for (var _i = 0, _a = ui.segs; _i < _a.length; _i++) { + var seg = _a[_i]; + byRow[seg.col].segs.push(seg); + } + } + return byRow; + } + + var TimeColMoreLink = /** @class */ (function (_super) { + __extends(TimeColMoreLink, _super); + function TimeColMoreLink() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.rootElRef = createRef(); + return _this; + } + TimeColMoreLink.prototype.render = function () { + var _this = this; + var props = this.props; + return (createElement(MoreLinkRoot, { allDayDate: null, moreCnt: props.hiddenSegs.length, allSegs: props.hiddenSegs, hiddenSegs: props.hiddenSegs, alignmentElRef: this.rootElRef, defaultContent: renderMoreLinkInner, extraDateSpan: props.extraDateSpan, dateProfile: props.dateProfile, todayRange: props.todayRange, popoverContent: function () { return renderPlainFgSegs(props.hiddenSegs, props); } }, function (rootElRef, classNames, innerElRef, innerContent, handleClick) { return (createElement("a", { ref: function (el) { + setRef(rootElRef, el); + setRef(_this.rootElRef, el); + }, className: ['fc-timegrid-more-link'].concat(classNames).join(' '), style: { top: props.top, bottom: props.bottom }, onClick: handleClick }, + createElement("div", { ref: innerElRef, className: "fc-timegrid-more-link-inner fc-sticky" }, innerContent))); })); + }; + return TimeColMoreLink; + }(BaseComponent)); + function renderMoreLinkInner(props) { + return props.shortText; + } + + // segInputs assumed sorted + function buildPositioning(segInputs, strictOrder, maxStackCnt) { + var hierarchy = new SegHierarchy(); + if (strictOrder != null) { + hierarchy.strictOrder = strictOrder; + } + if (maxStackCnt != null) { + hierarchy.maxStackCnt = maxStackCnt; + } + var hiddenEntries = hierarchy.addSegs(segInputs); + var hiddenGroups = groupIntersectingEntries(hiddenEntries); + var web = buildWeb(hierarchy); + web = stretchWeb(web, 1); // all levelCoords/thickness will have 0.0-1.0 + var segRects = webToRects(web); + return { segRects: segRects, hiddenGroups: hiddenGroups }; + } + function buildWeb(hierarchy) { + var entriesByLevel = hierarchy.entriesByLevel; + var buildNode = cacheable(function (level, lateral) { return level + ':' + lateral; }, function (level, lateral) { + var siblingRange = findNextLevelSegs(hierarchy, level, lateral); + var nextLevelRes = buildNodes(siblingRange, buildNode); + var entry = entriesByLevel[level][lateral]; + return [ + __assign(__assign({}, entry), { nextLevelNodes: nextLevelRes[0] }), + entry.thickness + nextLevelRes[1], // the pressure builds + ]; + }); + return buildNodes(entriesByLevel.length + ? { level: 0, lateralStart: 0, lateralEnd: entriesByLevel[0].length } + : null, buildNode)[0]; + } + function buildNodes(siblingRange, buildNode) { + if (!siblingRange) { + return [[], 0]; + } + var level = siblingRange.level, lateralStart = siblingRange.lateralStart, lateralEnd = siblingRange.lateralEnd; + var lateral = lateralStart; + var pairs = []; + while (lateral < lateralEnd) { + pairs.push(buildNode(level, lateral)); + lateral += 1; + } + pairs.sort(cmpDescPressures); + return [ + pairs.map(extractNode), + pairs[0][1], // first item's pressure + ]; + } + function cmpDescPressures(a, b) { + return b[1] - a[1]; + } + function extractNode(a) { + return a[0]; + } + function findNextLevelSegs(hierarchy, subjectLevel, subjectLateral) { + var levelCoords = hierarchy.levelCoords, entriesByLevel = hierarchy.entriesByLevel; + var subjectEntry = entriesByLevel[subjectLevel][subjectLateral]; + var afterSubject = levelCoords[subjectLevel] + subjectEntry.thickness; + var levelCnt = levelCoords.length; + var level = subjectLevel; + // skip past levels that are too high up + for (; level < levelCnt && levelCoords[level] < afterSubject; level += 1) + ; // do nothing + for (; level < levelCnt; level += 1) { + var entries = entriesByLevel[level]; + var entry = void 0; + var searchIndex = binarySearch(entries, subjectEntry.span.start, getEntrySpanEnd); + var lateralStart = searchIndex[0] + searchIndex[1]; // if exact match (which doesn't collide), go to next one + var lateralEnd = lateralStart; + while ( // loop through entries that horizontally intersect + (entry = entries[lateralEnd]) && // but not past the whole seg list + entry.span.start < subjectEntry.span.end) { + lateralEnd += 1; + } + if (lateralStart < lateralEnd) { + return { level: level, lateralStart: lateralStart, lateralEnd: lateralEnd }; + } + } + return null; + } + function stretchWeb(topLevelNodes, totalThickness) { + var stretchNode = cacheable(function (node, startCoord, prevThickness) { return buildEntryKey(node); }, function (node, startCoord, prevThickness) { + var nextLevelNodes = node.nextLevelNodes, thickness = node.thickness; + var allThickness = thickness + prevThickness; + var thicknessFraction = thickness / allThickness; + var endCoord; + var newChildren = []; + if (!nextLevelNodes.length) { + endCoord = totalThickness; + } + else { + for (var _i = 0, nextLevelNodes_1 = nextLevelNodes; _i < nextLevelNodes_1.length; _i++) { + var childNode = nextLevelNodes_1[_i]; + if (endCoord === undefined) { + var res = stretchNode(childNode, startCoord, allThickness); + endCoord = res[0]; + newChildren.push(res[1]); + } + else { + var res = stretchNode(childNode, endCoord, 0); + newChildren.push(res[1]); + } + } + } + var newThickness = (endCoord - startCoord) * thicknessFraction; + return [endCoord - newThickness, __assign(__assign({}, node), { thickness: newThickness, nextLevelNodes: newChildren })]; + }); + return topLevelNodes.map(function (node) { return stretchNode(node, 0, 0)[1]; }); + } + // not sorted in any particular order + function webToRects(topLevelNodes) { + var rects = []; + var processNode = cacheable(function (node, levelCoord, stackDepth) { return buildEntryKey(node); }, function (node, levelCoord, stackDepth) { + var rect = __assign(__assign({}, node), { levelCoord: levelCoord, + stackDepth: stackDepth, stackForward: 0 }); + rects.push(rect); + return (rect.stackForward = processNodes(node.nextLevelNodes, levelCoord + node.thickness, stackDepth + 1) + 1); + }); + function processNodes(nodes, levelCoord, stackDepth) { + var stackForward = 0; + for (var _i = 0, nodes_1 = nodes; _i < nodes_1.length; _i++) { + var node = nodes_1[_i]; + stackForward = Math.max(processNode(node, levelCoord, stackDepth), stackForward); + } + return stackForward; + } + processNodes(topLevelNodes, 0, 0); + return rects; // TODO: sort rects by levelCoord to be consistent with toRects? + } + // TODO: move to general util + function cacheable(keyFunc, workFunc) { + var cache = {}; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var key = keyFunc.apply(void 0, args); + return (key in cache) + ? cache[key] + : (cache[key] = workFunc.apply(void 0, args)); + }; + } + + function computeSegVCoords(segs, colDate, slatCoords, eventMinHeight) { + if (slatCoords === void 0) { slatCoords = null; } + if (eventMinHeight === void 0) { eventMinHeight = 0; } + var vcoords = []; + if (slatCoords) { + for (var i = 0; i < segs.length; i += 1) { + var seg = segs[i]; + var spanStart = slatCoords.computeDateTop(seg.start, colDate); + var spanEnd = Math.max(spanStart + (eventMinHeight || 0), // :( + slatCoords.computeDateTop(seg.end, colDate)); + vcoords.push({ + start: Math.round(spanStart), + end: Math.round(spanEnd), // + }); + } + } + return vcoords; + } + function computeFgSegPlacements(segs, segVCoords, // might not have for every seg + eventOrderStrict, eventMaxStack) { + var segInputs = []; + var dumbSegs = []; // segs without coords + for (var i = 0; i < segs.length; i += 1) { + var vcoords = segVCoords[i]; + if (vcoords) { + segInputs.push({ + index: i, + thickness: 1, + span: vcoords, + }); + } + else { + dumbSegs.push(segs[i]); + } + } + var _a = buildPositioning(segInputs, eventOrderStrict, eventMaxStack), segRects = _a.segRects, hiddenGroups = _a.hiddenGroups; + var segPlacements = []; + for (var _i = 0, segRects_1 = segRects; _i < segRects_1.length; _i++) { + var segRect = segRects_1[_i]; + segPlacements.push({ + seg: segs[segRect.index], + rect: segRect, + }); + } + for (var _b = 0, dumbSegs_1 = dumbSegs; _b < dumbSegs_1.length; _b++) { + var dumbSeg = dumbSegs_1[_b]; + segPlacements.push({ seg: dumbSeg, rect: null }); + } + return { segPlacements: segPlacements, hiddenGroups: hiddenGroups }; + } + + var DEFAULT_TIME_FORMAT$1 = createFormatter({ + hour: 'numeric', + minute: '2-digit', + meridiem: false, + }); + var TimeColEvent = /** @class */ (function (_super) { + __extends(TimeColEvent, _super); + function TimeColEvent() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeColEvent.prototype.render = function () { + var classNames = [ + 'fc-timegrid-event', + 'fc-v-event', + ]; + if (this.props.isShort) { + classNames.push('fc-timegrid-event-short'); + } + return (createElement(StandardEvent, __assign({}, this.props, { defaultTimeFormat: DEFAULT_TIME_FORMAT$1, extraClassNames: classNames }))); + }; + return TimeColEvent; + }(BaseComponent)); + + var TimeColMisc = /** @class */ (function (_super) { + __extends(TimeColMisc, _super); + function TimeColMisc() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeColMisc.prototype.render = function () { + var props = this.props; + return (createElement(DayCellContent, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraHookProps: props.extraHookProps }, function (innerElRef, innerContent) { return (innerContent && + createElement("div", { className: "fc-timegrid-col-misc", ref: innerElRef }, innerContent)); })); + }; + return TimeColMisc; + }(BaseComponent)); + + var TimeCol = /** @class */ (function (_super) { + __extends(TimeCol, _super); + function TimeCol() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.sortEventSegs = memoize(sortEventSegs); + return _this; + } + // TODO: memoize event-placement? + TimeCol.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var isSelectMirror = context.options.selectMirror; + var mirrorSegs = (props.eventDrag && props.eventDrag.segs) || + (props.eventResize && props.eventResize.segs) || + (isSelectMirror && props.dateSelectionSegs) || + []; + var interactionAffectedInstances = // TODO: messy way to compute this + (props.eventDrag && props.eventDrag.affectedInstances) || + (props.eventResize && props.eventResize.affectedInstances) || + {}; + var sortedFgSegs = this.sortEventSegs(props.fgEventSegs, context.options.eventOrder); + return (createElement(DayCellRoot, { elRef: props.elRef, date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraHookProps: props.extraHookProps }, function (rootElRef, classNames, dataAttrs) { return (createElement("td", __assign({ ref: rootElRef, className: ['fc-timegrid-col'].concat(classNames, props.extraClassNames || []).join(' ') }, dataAttrs, props.extraDataAttrs), + createElement("div", { className: "fc-timegrid-col-frame" }, + createElement("div", { className: "fc-timegrid-col-bg" }, + _this.renderFillSegs(props.businessHourSegs, 'non-business'), + _this.renderFillSegs(props.bgEventSegs, 'bg-event'), + _this.renderFillSegs(props.dateSelectionSegs, 'highlight')), + createElement("div", { className: "fc-timegrid-col-events" }, _this.renderFgSegs(sortedFgSegs, interactionAffectedInstances, false, false, false)), + createElement("div", { className: "fc-timegrid-col-events" }, _this.renderFgSegs(mirrorSegs, {}, Boolean(props.eventDrag), Boolean(props.eventResize), Boolean(isSelectMirror))), + createElement("div", { className: "fc-timegrid-now-indicator-container" }, _this.renderNowIndicator(props.nowIndicatorSegs)), + createElement(TimeColMisc, { date: props.date, dateProfile: props.dateProfile, todayRange: props.todayRange, extraHookProps: props.extraHookProps })))); })); + }; + TimeCol.prototype.renderFgSegs = function (sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting) { + var props = this.props; + if (props.forPrint) { + return renderPlainFgSegs(sortedFgSegs, props); + } + return this.renderPositionedFgSegs(sortedFgSegs, segIsInvisible, isDragging, isResizing, isDateSelecting); + }; + TimeCol.prototype.renderPositionedFgSegs = function (segs, // if not mirror, needs to be sorted + segIsInvisible, isDragging, isResizing, isDateSelecting) { + var _this = this; + var _a = this.context.options, eventMaxStack = _a.eventMaxStack, eventShortHeight = _a.eventShortHeight, eventOrderStrict = _a.eventOrderStrict, eventMinHeight = _a.eventMinHeight; + var _b = this.props, date = _b.date, slatCoords = _b.slatCoords, eventSelection = _b.eventSelection, todayRange = _b.todayRange, nowDate = _b.nowDate; + var isMirror = isDragging || isResizing || isDateSelecting; + var segVCoords = computeSegVCoords(segs, date, slatCoords, eventMinHeight); + var _c = computeFgSegPlacements(segs, segVCoords, eventOrderStrict, eventMaxStack), segPlacements = _c.segPlacements, hiddenGroups = _c.hiddenGroups; + return (createElement(Fragment, null, + this.renderHiddenGroups(hiddenGroups, segs), + segPlacements.map(function (segPlacement) { + var seg = segPlacement.seg, rect = segPlacement.rect; + var instanceId = seg.eventRange.instance.instanceId; + var isVisible = isMirror || Boolean(!segIsInvisible[instanceId] && rect); + var vStyle = computeSegVStyle(rect && rect.span); + var hStyle = (!isMirror && rect) ? _this.computeSegHStyle(rect) : { left: 0, right: 0 }; + var isInset = Boolean(rect) && rect.stackForward > 0; + var isShort = Boolean(rect) && (rect.span.end - rect.span.start) < eventShortHeight; // look at other places for this problem + return (createElement("div", { className: 'fc-timegrid-event-harness' + + (isInset ? ' fc-timegrid-event-harness-inset' : ''), key: instanceId, style: __assign(__assign({ visibility: isVisible ? '' : 'hidden' }, vStyle), hStyle) }, + createElement(TimeColEvent, __assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, isShort: isShort }, getSegMeta(seg, todayRange, nowDate))))); + }))); + }; + // will already have eventMinHeight applied because segInputs already had it + TimeCol.prototype.renderHiddenGroups = function (hiddenGroups, segs) { + var _a = this.props, extraDateSpan = _a.extraDateSpan, dateProfile = _a.dateProfile, todayRange = _a.todayRange, nowDate = _a.nowDate, eventSelection = _a.eventSelection, eventDrag = _a.eventDrag, eventResize = _a.eventResize; + return (createElement(Fragment, null, hiddenGroups.map(function (hiddenGroup) { + var positionCss = computeSegVStyle(hiddenGroup.span); + var hiddenSegs = compileSegsFromEntries(hiddenGroup.entries, segs); + return (createElement(TimeColMoreLink, { key: buildIsoString(computeEarliestSegStart(hiddenSegs)), hiddenSegs: hiddenSegs, top: positionCss.top, bottom: positionCss.bottom, extraDateSpan: extraDateSpan, dateProfile: dateProfile, todayRange: todayRange, nowDate: nowDate, eventSelection: eventSelection, eventDrag: eventDrag, eventResize: eventResize })); + }))); + }; + TimeCol.prototype.renderFillSegs = function (segs, fillType) { + var _a = this, props = _a.props, context = _a.context; + var segVCoords = computeSegVCoords(segs, props.date, props.slatCoords, context.options.eventMinHeight); // don't assume all populated + var children = segVCoords.map(function (vcoords, i) { + var seg = segs[i]; + return (createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-timegrid-bg-harness", style: computeSegVStyle(vcoords) }, fillType === 'bg-event' ? + createElement(BgEvent, __assign({ seg: seg }, getSegMeta(seg, props.todayRange, props.nowDate))) : + renderFill(fillType))); + }); + return createElement(Fragment, null, children); + }; + TimeCol.prototype.renderNowIndicator = function (segs) { + var _a = this.props, slatCoords = _a.slatCoords, date = _a.date; + if (!slatCoords) { + return null; + } + return segs.map(function (seg, i) { return (createElement(NowIndicatorRoot, { isAxis: false, date: date, + // key doesn't matter. will only ever be one + key: i }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-line'].concat(classNames).join(' '), style: { top: slatCoords.computeDateTop(seg.start, date) } }, innerContent)); })); }); + }; + TimeCol.prototype.computeSegHStyle = function (segHCoords) { + var _a = this.context, isRtl = _a.isRtl, options = _a.options; + var shouldOverlap = options.slotEventOverlap; + var nearCoord = segHCoords.levelCoord; // the left side if LTR. the right side if RTL. floating-point + var farCoord = segHCoords.levelCoord + segHCoords.thickness; // the right side if LTR. the left side if RTL. floating-point + var left; // amount of space from left edge, a fraction of the total width + var right; // amount of space from right edge, a fraction of the total width + if (shouldOverlap) { + // double the width, but don't go beyond the maximum forward coordinate (1.0) + farCoord = Math.min(1, nearCoord + (farCoord - nearCoord) * 2); + } + if (isRtl) { + left = 1 - farCoord; + right = nearCoord; + } + else { + left = nearCoord; + right = 1 - farCoord; + } + var props = { + zIndex: segHCoords.stackDepth + 1, + left: left * 100 + '%', + right: right * 100 + '%', + }; + if (shouldOverlap && !segHCoords.stackForward) { + // add padding to the edge so that forward stacked events don't cover the resizer's icon + props[isRtl ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width + } + return props; + }; + return TimeCol; + }(BaseComponent)); + function renderPlainFgSegs(sortedFgSegs, _a) { + var todayRange = _a.todayRange, nowDate = _a.nowDate, eventSelection = _a.eventSelection, eventDrag = _a.eventDrag, eventResize = _a.eventResize; + var hiddenInstances = (eventDrag ? eventDrag.affectedInstances : null) || + (eventResize ? eventResize.affectedInstances : null) || + {}; + return (createElement(Fragment, null, sortedFgSegs.map(function (seg) { + var instanceId = seg.eventRange.instance.instanceId; + return (createElement("div", { key: instanceId, style: { visibility: hiddenInstances[instanceId] ? 'hidden' : '' } }, + createElement(TimeColEvent, __assign({ seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: instanceId === eventSelection, isShort: false }, getSegMeta(seg, todayRange, nowDate))))); + }))); + } + function computeSegVStyle(segVCoords) { + if (!segVCoords) { + return { top: '', bottom: '' }; + } + return { + top: segVCoords.start, + bottom: -segVCoords.end, + }; + } + function compileSegsFromEntries(segEntries, allSegs) { + return segEntries.map(function (segEntry) { return allSegs[segEntry.index]; }); + } + + var TimeColsContent = /** @class */ (function (_super) { + __extends(TimeColsContent, _super); + function TimeColsContent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.splitFgEventSegs = memoize(splitSegsByCol); + _this.splitBgEventSegs = memoize(splitSegsByCol); + _this.splitBusinessHourSegs = memoize(splitSegsByCol); + _this.splitNowIndicatorSegs = memoize(splitSegsByCol); + _this.splitDateSelectionSegs = memoize(splitSegsByCol); + _this.splitEventDrag = memoize(splitInteractionByCol); + _this.splitEventResize = memoize(splitInteractionByCol); + _this.rootElRef = createRef(); + _this.cellElRefs = new RefMap(); + return _this; + } + TimeColsContent.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var nowIndicatorTop = context.options.nowIndicator && + props.slatCoords && + props.slatCoords.safeComputeTop(props.nowDate); // might return void + var colCnt = props.cells.length; + var fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, colCnt); + var bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, colCnt); + var businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, colCnt); + var nowIndicatorSegsByRow = this.splitNowIndicatorSegs(props.nowIndicatorSegs, colCnt); + var dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, colCnt); + var eventDragByRow = this.splitEventDrag(props.eventDrag, colCnt); + var eventResizeByRow = this.splitEventResize(props.eventResize, colCnt); + return (createElement("div", { className: "fc-timegrid-cols", ref: this.rootElRef }, + createElement("table", { style: { + minWidth: props.tableMinWidth, + width: props.clientWidth, + } }, + props.tableColGroupNode, + createElement("tbody", null, + createElement("tr", null, + props.axis && (createElement("td", { className: "fc-timegrid-col fc-timegrid-axis" }, + createElement("div", { className: "fc-timegrid-col-frame" }, + createElement("div", { className: "fc-timegrid-now-indicator-container" }, typeof nowIndicatorTop === 'number' && (createElement(NowIndicatorRoot, { isAxis: true, date: props.nowDate }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { ref: rootElRef, className: ['fc-timegrid-now-indicator-arrow'].concat(classNames).join(' '), style: { top: nowIndicatorTop } }, innerContent)); })))))), + props.cells.map(function (cell, i) { return (createElement(TimeCol, { key: cell.key, elRef: _this.cellElRefs.createRef(cell.key), dateProfile: props.dateProfile, date: cell.date, nowDate: props.nowDate, todayRange: props.todayRange, extraHookProps: cell.extraHookProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, fgEventSegs: fgEventSegsByRow[i], bgEventSegs: bgEventSegsByRow[i], businessHourSegs: businessHourSegsByRow[i], nowIndicatorSegs: nowIndicatorSegsByRow[i], dateSelectionSegs: dateSelectionSegsByRow[i], eventDrag: eventDragByRow[i], eventResize: eventResizeByRow[i], slatCoords: props.slatCoords, eventSelection: props.eventSelection, forPrint: props.forPrint })); })))))); + }; + TimeColsContent.prototype.componentDidMount = function () { + this.updateCoords(); + }; + TimeColsContent.prototype.componentDidUpdate = function () { + this.updateCoords(); + }; + TimeColsContent.prototype.updateCoords = function () { + var props = this.props; + if (props.onColCoords && + props.clientWidth !== null // means sizing has stabilized + ) { + props.onColCoords(new PositionCache(this.rootElRef.current, collectCellEls(this.cellElRefs.currentMap, props.cells), true, // horizontal + false)); + } + }; + return TimeColsContent; + }(BaseComponent)); + function collectCellEls(elMap, cells) { + return cells.map(function (cell) { return elMap[cell.key]; }); + } + + /* A component that renders one or more columns of vertical time slots + ----------------------------------------------------------------------------------------------------------------------*/ + var TimeCols = /** @class */ (function (_super) { + __extends(TimeCols, _super); + function TimeCols() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.processSlotOptions = memoize(processSlotOptions); + _this.state = { + slatCoords: null, + }; + _this.handleRootEl = function (el) { + if (el) { + _this.context.registerInteractiveComponent(_this, { + el: el, + isHitComboAllowed: _this.props.isHitComboAllowed, + }); + } + else { + _this.context.unregisterInteractiveComponent(_this); + } + }; + _this.handleScrollRequest = function (request) { + var onScrollTopRequest = _this.props.onScrollTopRequest; + var slatCoords = _this.state.slatCoords; + if (onScrollTopRequest && slatCoords) { + if (request.time) { + var top_1 = slatCoords.computeTimeTop(request.time); + top_1 = Math.ceil(top_1); // zoom can give weird floating-point values. rather scroll a little bit further + if (top_1) { + top_1 += 1; // to overcome top border that slots beyond the first have. looks better + } + onScrollTopRequest(top_1); + } + return true; + } + return false; + }; + _this.handleColCoords = function (colCoords) { + _this.colCoords = colCoords; + }; + _this.handleSlatCoords = function (slatCoords) { + _this.setState({ slatCoords: slatCoords }); + if (_this.props.onSlatCoords) { + _this.props.onSlatCoords(slatCoords); + } + }; + return _this; + } + TimeCols.prototype.render = function () { + var _a = this, props = _a.props, state = _a.state; + return (createElement("div", { className: "fc-timegrid-body", ref: this.handleRootEl, style: { + // these props are important to give this wrapper correct dimensions for interactions + // TODO: if we set it here, can we avoid giving to inner tables? + width: props.clientWidth, + minWidth: props.tableMinWidth, + } }, + createElement(TimeColsSlats, { axis: props.axis, dateProfile: props.dateProfile, slatMetas: props.slatMetas, clientWidth: props.clientWidth, minHeight: props.expandRows ? props.clientHeight : '', tableMinWidth: props.tableMinWidth, tableColGroupNode: props.axis ? props.tableColGroupNode : null /* axis depends on the colgroup's shrinking */, onCoords: this.handleSlatCoords }), + createElement(TimeColsContent, { cells: props.cells, axis: props.axis, dateProfile: props.dateProfile, businessHourSegs: props.businessHourSegs, bgEventSegs: props.bgEventSegs, fgEventSegs: props.fgEventSegs, dateSelectionSegs: props.dateSelectionSegs, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, todayRange: props.todayRange, nowDate: props.nowDate, nowIndicatorSegs: props.nowIndicatorSegs, clientWidth: props.clientWidth, tableMinWidth: props.tableMinWidth, tableColGroupNode: props.tableColGroupNode, slatCoords: state.slatCoords, onColCoords: this.handleColCoords, forPrint: props.forPrint }))); + }; + TimeCols.prototype.componentDidMount = function () { + this.scrollResponder = this.context.createScrollResponder(this.handleScrollRequest); + }; + TimeCols.prototype.componentDidUpdate = function (prevProps) { + this.scrollResponder.update(prevProps.dateProfile !== this.props.dateProfile); + }; + TimeCols.prototype.componentWillUnmount = function () { + this.scrollResponder.detach(); + }; + TimeCols.prototype.queryHit = function (positionLeft, positionTop) { + var _a = this.context, dateEnv = _a.dateEnv, options = _a.options; + var colCoords = this.colCoords; + var dateProfile = this.props.dateProfile; + var slatCoords = this.state.slatCoords; + var _b = this.processSlotOptions(this.props.slotDuration, options.snapDuration), snapDuration = _b.snapDuration, snapsPerSlot = _b.snapsPerSlot; + var colIndex = colCoords.leftToIndex(positionLeft); + var slatIndex = slatCoords.positions.topToIndex(positionTop); + if (colIndex != null && slatIndex != null) { + var cell = this.props.cells[colIndex]; + var slatTop = slatCoords.positions.tops[slatIndex]; + var slatHeight = slatCoords.positions.getHeight(slatIndex); + var partial = (positionTop - slatTop) / slatHeight; // floating point number between 0 and 1 + var localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat + var snapIndex = slatIndex * snapsPerSlot + localSnapIndex; + var dayDate = this.props.cells[colIndex].date; + var time = addDurations(dateProfile.slotMinTime, multiplyDuration(snapDuration, snapIndex)); + var start = dateEnv.add(dayDate, time); + var end = dateEnv.add(start, snapDuration); + return { + dateProfile: dateProfile, + dateSpan: __assign({ range: { start: start, end: end }, allDay: false }, cell.extraDateSpan), + dayEl: colCoords.els[colIndex], + rect: { + left: colCoords.lefts[colIndex], + right: colCoords.rights[colIndex], + top: slatTop, + bottom: slatTop + slatHeight, + }, + layer: 0, + }; + } + return null; + }; + return TimeCols; + }(DateComponent)); + function processSlotOptions(slotDuration, snapDurationOverride) { + var snapDuration = snapDurationOverride || slotDuration; + var snapsPerSlot = wholeDivideDurations(slotDuration, snapDuration); + if (snapsPerSlot === null) { + snapDuration = slotDuration; + snapsPerSlot = 1; + // TODO: say warning? + } + return { snapDuration: snapDuration, snapsPerSlot: snapsPerSlot }; + } + + var DayTimeColsSlicer = /** @class */ (function (_super) { + __extends(DayTimeColsSlicer, _super); + function DayTimeColsSlicer() { + return _super !== null && _super.apply(this, arguments) || this; + } + DayTimeColsSlicer.prototype.sliceRange = function (range, dayRanges) { + var segs = []; + for (var col = 0; col < dayRanges.length; col += 1) { + var segRange = intersectRanges(range, dayRanges[col]); + if (segRange) { + segs.push({ + start: segRange.start, + end: segRange.end, + isStart: segRange.start.valueOf() === range.start.valueOf(), + isEnd: segRange.end.valueOf() === range.end.valueOf(), + col: col, + }); + } + } + return segs; + }; + return DayTimeColsSlicer; + }(Slicer)); + + var DayTimeCols = /** @class */ (function (_super) { + __extends(DayTimeCols, _super); + function DayTimeCols() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildDayRanges = memoize(buildDayRanges); + _this.slicer = new DayTimeColsSlicer(); + _this.timeColsRef = createRef(); + return _this; + } + DayTimeCols.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var dateProfile = props.dateProfile, dayTableModel = props.dayTableModel; + var isNowIndicator = context.options.nowIndicator; + var dayRanges = this.buildDayRanges(dayTableModel, dateProfile, context.dateEnv); + // give it the first row of cells + // TODO: would move this further down hierarchy, but sliceNowDate needs it + return (createElement(NowTimer, { unit: isNowIndicator ? 'minute' : 'day' }, function (nowDate, todayRange) { return (createElement(TimeCols, __assign({ ref: _this.timeColsRef }, _this.slicer.sliceProps(props, dateProfile, null, context, dayRanges), { forPrint: props.forPrint, axis: props.axis, dateProfile: dateProfile, slatMetas: props.slatMetas, slotDuration: props.slotDuration, cells: dayTableModel.cells[0], tableColGroupNode: props.tableColGroupNode, tableMinWidth: props.tableMinWidth, clientWidth: props.clientWidth, clientHeight: props.clientHeight, expandRows: props.expandRows, nowDate: nowDate, nowIndicatorSegs: isNowIndicator && _this.slicer.sliceNowDate(nowDate, context, dayRanges), todayRange: todayRange, onScrollTopRequest: props.onScrollTopRequest, onSlatCoords: props.onSlatCoords }))); })); + }; + return DayTimeCols; + }(DateComponent)); + function buildDayRanges(dayTableModel, dateProfile, dateEnv) { + var ranges = []; + for (var _i = 0, _a = dayTableModel.headerDates; _i < _a.length; _i++) { + var date = _a[_i]; + ranges.push({ + start: dateEnv.add(date, dateProfile.slotMinTime), + end: dateEnv.add(date, dateProfile.slotMaxTime), + }); + } + return ranges; + } + + // potential nice values for the slot-duration and interval-duration + // from largest to smallest + var STOCK_SUB_DURATIONS = [ + { hours: 1 }, + { minutes: 30 }, + { minutes: 15 }, + { seconds: 30 }, + { seconds: 15 }, + ]; + function buildSlatMetas(slotMinTime, slotMaxTime, explicitLabelInterval, slotDuration, dateEnv) { + var dayStart = new Date(0); + var slatTime = slotMinTime; + var slatIterator = createDuration(0); + var labelInterval = explicitLabelInterval || computeLabelInterval(slotDuration); + var metas = []; + while (asRoughMs(slatTime) < asRoughMs(slotMaxTime)) { + var date = dateEnv.add(dayStart, slatTime); + var isLabeled = wholeDivideDurations(slatIterator, labelInterval) !== null; + metas.push({ + date: date, + time: slatTime, + key: date.toISOString(), + isoTimeStr: formatIsoTimeString(date), + isLabeled: isLabeled, + }); + slatTime = addDurations(slatTime, slotDuration); + slatIterator = addDurations(slatIterator, slotDuration); + } + return metas; + } + // Computes an automatic value for slotLabelInterval + function computeLabelInterval(slotDuration) { + var i; + var labelInterval; + var slotsPerLabel; + // find the smallest stock label interval that results in more than one slots-per-label + for (i = STOCK_SUB_DURATIONS.length - 1; i >= 0; i -= 1) { + labelInterval = createDuration(STOCK_SUB_DURATIONS[i]); + slotsPerLabel = wholeDivideDurations(labelInterval, slotDuration); + if (slotsPerLabel !== null && slotsPerLabel > 1) { + return labelInterval; + } + } + return slotDuration; // fall back + } + + var DayTimeColsView = /** @class */ (function (_super) { + __extends(DayTimeColsView, _super); + function DayTimeColsView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildTimeColsModel = memoize(buildTimeColsModel); + _this.buildSlatMetas = memoize(buildSlatMetas); + return _this; + } + DayTimeColsView.prototype.render = function () { + var _this = this; + var _a = this.context, options = _a.options, dateEnv = _a.dateEnv, dateProfileGenerator = _a.dateProfileGenerator; + var props = this.props; + var dateProfile = props.dateProfile; + var dayTableModel = this.buildTimeColsModel(dateProfile, dateProfileGenerator); + var splitProps = this.allDaySplitter.splitProps(props); + var slatMetas = this.buildSlatMetas(dateProfile.slotMinTime, dateProfile.slotMaxTime, options.slotLabelInterval, options.slotDuration, dateEnv); + var dayMinWidth = options.dayMinWidth; + var hasAttachedAxis = !dayMinWidth; + var hasDetachedAxis = dayMinWidth; + var headerContent = options.dayHeaders && (createElement(DayHeader, { dates: dayTableModel.headerDates, dateProfile: dateProfile, datesRepDistinctDays: true, renderIntro: hasAttachedAxis ? this.renderHeadAxis : null })); + var allDayContent = (options.allDaySlot !== false) && (function (contentArg) { return (createElement(DayTable, __assign({}, splitProps.allDay, { dateProfile: dateProfile, dayTableModel: dayTableModel, nextDayThreshold: options.nextDayThreshold, tableMinWidth: contentArg.tableMinWidth, colGroupNode: contentArg.tableColGroupNode, renderRowIntro: hasAttachedAxis ? _this.renderTableRowAxis : null, showWeekNumbers: false, expandRows: false, headerAlignElRef: _this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint }, _this.getAllDayMaxEventProps()))); }); + var timeGridContent = function (contentArg) { return (createElement(DayTimeCols, __assign({}, splitProps.timed, { dayTableModel: dayTableModel, dateProfile: dateProfile, axis: hasAttachedAxis, slotDuration: options.slotDuration, slatMetas: slatMetas, forPrint: props.forPrint, tableColGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, onSlatCoords: _this.handleSlatCoords, expandRows: contentArg.expandRows, onScrollTopRequest: _this.handleScrollTopRequest }))); }; + return hasDetachedAxis + ? this.renderHScrollLayout(headerContent, allDayContent, timeGridContent, dayTableModel.colCnt, dayMinWidth, slatMetas, this.state.slatCoords) + : this.renderSimpleLayout(headerContent, allDayContent, timeGridContent); + }; + return DayTimeColsView; + }(TimeColsView)); + function buildTimeColsModel(dateProfile, dateProfileGenerator) { + var daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator); + return new DayTableModel(daySeries, false); + } + + var OPTION_REFINERS$2 = { + allDaySlot: Boolean, + }; + + var timeGridPlugin = createPlugin({ + initialView: 'timeGridWeek', + optionRefiners: OPTION_REFINERS$2, + views: { + timeGrid: { + component: DayTimeColsView, + usesMinMaxTime: true, + allDaySlot: true, + slotDuration: '00:30:00', + slotEventOverlap: true, // a bad name. confused with overlap/constraint system + }, + timeGridDay: { + type: 'timeGrid', + duration: { days: 1 }, + }, + timeGridWeek: { + type: 'timeGrid', + duration: { weeks: 1 }, + }, + }, + }); + + var ListViewHeaderRow = /** @class */ (function (_super) { + __extends(ListViewHeaderRow, _super); + function ListViewHeaderRow() { + return _super !== null && _super.apply(this, arguments) || this; + } + ListViewHeaderRow.prototype.render = function () { + var _a = this.props, dayDate = _a.dayDate, todayRange = _a.todayRange; + var _b = this.context, theme = _b.theme, dateEnv = _b.dateEnv, options = _b.options, viewApi = _b.viewApi; + var dayMeta = getDateMeta(dayDate, todayRange); + // will ever be falsy? + var text = options.listDayFormat ? dateEnv.format(dayDate, options.listDayFormat) : ''; + // will ever be falsy? also, BAD NAME "alt" + var sideText = options.listDaySideFormat ? dateEnv.format(dayDate, options.listDaySideFormat) : ''; + var navLinkData = options.navLinks + ? buildNavLinkData(dayDate) + : null; + var hookProps = __assign({ date: dateEnv.toDate(dayDate), view: viewApi, text: text, + sideText: sideText, + navLinkData: navLinkData }, dayMeta); + var classNames = ['fc-list-day'].concat(getDayClassNames(dayMeta, theme)); + // TODO: make a reusable HOC for dayHeader (used in daygrid/timegrid too) + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.dayHeaderClassNames, content: options.dayHeaderContent, defaultContent: renderInnerContent, didMount: options.dayHeaderDidMount, willUnmount: options.dayHeaderWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("tr", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-date": formatDayString(dayDate) }, + createElement("th", { colSpan: 3 }, + createElement("div", { className: 'fc-list-day-cushion ' + theme.getClass('tableCellShaded'), ref: innerElRef }, innerContent)))); })); + }; + return ListViewHeaderRow; + }(BaseComponent)); + function renderInnerContent(props) { + var navLinkAttrs = props.navLinkData // is there a type for this? + ? { 'data-navlink': props.navLinkData, tabIndex: 0 } + : {}; + return (createElement(Fragment, null, + props.text && (createElement("a", __assign({ className: "fc-list-day-text" }, navLinkAttrs), props.text)), + props.sideText && (createElement("a", __assign({ className: "fc-list-day-side-text" }, navLinkAttrs), props.sideText)))); + } + + var DEFAULT_TIME_FORMAT = createFormatter({ + hour: 'numeric', + minute: '2-digit', + meridiem: 'short', + }); + var ListViewEventRow = /** @class */ (function (_super) { + __extends(ListViewEventRow, _super); + function ListViewEventRow() { + return _super !== null && _super.apply(this, arguments) || this; + } + ListViewEventRow.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + var seg = props.seg; + var timeFormat = context.options.eventTimeFormat || DEFAULT_TIME_FORMAT; + return (createElement(EventRoot, { seg: seg, timeText: "" // BAD. because of all-day content + , disableDragging: true, disableResizing: true, defaultContent: renderEventInnerContent, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday, isSelected: props.isSelected, isDragging: props.isDragging, isResizing: props.isResizing, isDateSelecting: props.isDateSelecting }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("tr", { className: ['fc-list-event', hookProps.event.url ? 'fc-event-forced-url' : ''].concat(classNames).join(' '), ref: rootElRef }, + buildTimeContent(seg, timeFormat, context), + createElement("td", { className: "fc-list-event-graphic" }, + createElement("span", { className: "fc-list-event-dot", style: { borderColor: hookProps.borderColor || hookProps.backgroundColor } })), + createElement("td", { className: "fc-list-event-title", ref: innerElRef }, innerContent))); })); + }; + return ListViewEventRow; + }(BaseComponent)); + function renderEventInnerContent(props) { + var event = props.event; + var url = event.url; + var anchorAttrs = url ? { href: url } : {}; + return (createElement("a", __assign({}, anchorAttrs), event.title)); + } + function buildTimeContent(seg, timeFormat, context) { + var options = context.options; + if (options.displayEventTime !== false) { + var eventDef = seg.eventRange.def; + var eventInstance = seg.eventRange.instance; + var doAllDay = false; + var timeText = void 0; + if (eventDef.allDay) { + doAllDay = true; + } + else if (isMultiDayRange(seg.eventRange.range)) { // TODO: use (!isStart || !isEnd) instead? + if (seg.isStart) { + timeText = buildSegTimeText(seg, timeFormat, context, null, null, eventInstance.range.start, seg.end); + } + else if (seg.isEnd) { + timeText = buildSegTimeText(seg, timeFormat, context, null, null, seg.start, eventInstance.range.end); + } + else { + doAllDay = true; + } + } + else { + timeText = buildSegTimeText(seg, timeFormat, context); + } + if (doAllDay) { + var hookProps = { + text: context.options.allDayText, + view: context.viewApi, + }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.allDayClassNames, content: options.allDayContent, defaultContent: renderAllDayInner, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("td", { className: ['fc-list-event-time'].concat(classNames).join(' '), ref: rootElRef }, innerContent)); })); + } + return (createElement("td", { className: "fc-list-event-time" }, timeText)); + } + return null; + } + function renderAllDayInner(hookProps) { + return hookProps.text; + } + + /* + Responsible for the scroller, and forwarding event-related actions into the "grid". + */ + var ListView = /** @class */ (function (_super) { + __extends(ListView, _super); + function ListView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.computeDateVars = memoize(computeDateVars); + _this.eventStoreToSegs = memoize(_this._eventStoreToSegs); + _this.setRootEl = function (rootEl) { + if (rootEl) { + _this.context.registerInteractiveComponent(_this, { + el: rootEl, + }); + } + else { + _this.context.unregisterInteractiveComponent(_this); + } + }; + return _this; + } + ListView.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, context = _a.context; + var extraClassNames = [ + 'fc-list', + context.theme.getClass('table'), + context.options.stickyHeaderDates !== false ? 'fc-list-sticky' : '', + ]; + var _b = this.computeDateVars(props.dateProfile), dayDates = _b.dayDates, dayRanges = _b.dayRanges; + var eventSegs = this.eventStoreToSegs(props.eventStore, props.eventUiBases, dayRanges); + return (createElement(ViewRoot, { viewSpec: context.viewSpec, elRef: this.setRootEl }, function (rootElRef, classNames) { return (createElement("div", { ref: rootElRef, className: extraClassNames.concat(classNames).join(' ') }, + createElement(Scroller, { liquid: !props.isHeightAuto, overflowX: props.isHeightAuto ? 'visible' : 'hidden', overflowY: props.isHeightAuto ? 'visible' : 'auto' }, eventSegs.length > 0 ? + _this.renderSegList(eventSegs, dayDates) : + _this.renderEmptyMessage()))); })); + }; + ListView.prototype.renderEmptyMessage = function () { + var _a = this.context, options = _a.options, viewApi = _a.viewApi; + var hookProps = { + text: options.noEventsText, + view: viewApi, + }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.noEventsClassNames, content: options.noEventsContent, defaultContent: renderNoEventsInner, didMount: options.noEventsDidMount, willUnmount: options.noEventsWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("div", { className: ['fc-list-empty'].concat(classNames).join(' '), ref: rootElRef }, + createElement("div", { className: "fc-list-empty-cushion", ref: innerElRef }, innerContent))); })); + }; + ListView.prototype.renderSegList = function (allSegs, dayDates) { + var _a = this.context, theme = _a.theme, options = _a.options; + var segsByDay = groupSegsByDay(allSegs); // sparse array + return (createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { + var innerNodes = []; + for (var dayIndex = 0; dayIndex < segsByDay.length; dayIndex += 1) { + var daySegs = segsByDay[dayIndex]; + if (daySegs) { // sparse array, so might be undefined + var dayStr = dayDates[dayIndex].toISOString(); + // append a day header + innerNodes.push(createElement(ListViewHeaderRow, { key: dayStr, dayDate: dayDates[dayIndex], todayRange: todayRange })); + daySegs = sortEventSegs(daySegs, options.eventOrder); + for (var _i = 0, daySegs_1 = daySegs; _i < daySegs_1.length; _i++) { + var seg = daySegs_1[_i]; + innerNodes.push(createElement(ListViewEventRow, __assign({ key: dayStr + ':' + seg.eventRange.instance.instanceId /* are multiple segs for an instanceId */, seg: seg, isDragging: false, isResizing: false, isDateSelecting: false, isSelected: false }, getSegMeta(seg, todayRange, nowDate)))); + } + } + } + return (createElement("table", { className: 'fc-list-table ' + theme.getClass('table') }, + createElement("tbody", null, innerNodes))); + })); + }; + ListView.prototype._eventStoreToSegs = function (eventStore, eventUiBases, dayRanges) { + return this.eventRangesToSegs(sliceEventStore(eventStore, eventUiBases, this.props.dateProfile.activeRange, this.context.options.nextDayThreshold).fg, dayRanges); + }; + ListView.prototype.eventRangesToSegs = function (eventRanges, dayRanges) { + var segs = []; + for (var _i = 0, eventRanges_1 = eventRanges; _i < eventRanges_1.length; _i++) { + var eventRange = eventRanges_1[_i]; + segs.push.apply(segs, this.eventRangeToSegs(eventRange, dayRanges)); + } + return segs; + }; + ListView.prototype.eventRangeToSegs = function (eventRange, dayRanges) { + var dateEnv = this.context.dateEnv; + var nextDayThreshold = this.context.options.nextDayThreshold; + var range = eventRange.range; + var allDay = eventRange.def.allDay; + var dayIndex; + var segRange; + var seg; + var segs = []; + for (dayIndex = 0; dayIndex < dayRanges.length; dayIndex += 1) { + segRange = intersectRanges(range, dayRanges[dayIndex]); + if (segRange) { + seg = { + component: this, + eventRange: eventRange, + start: segRange.start, + end: segRange.end, + isStart: eventRange.isStart && segRange.start.valueOf() === range.start.valueOf(), + isEnd: eventRange.isEnd && segRange.end.valueOf() === range.end.valueOf(), + dayIndex: dayIndex, + }; + segs.push(seg); + // detect when range won't go fully into the next day, + // and mutate the latest seg to the be the end. + if (!seg.isEnd && !allDay && + dayIndex + 1 < dayRanges.length && + range.end < + dateEnv.add(dayRanges[dayIndex + 1].start, nextDayThreshold)) { + seg.end = range.end; + seg.isEnd = true; + break; + } + } + } + return segs; + }; + return ListView; + }(DateComponent)); + function renderNoEventsInner(hookProps) { + return hookProps.text; + } + function computeDateVars(dateProfile) { + var dayStart = startOfDay(dateProfile.renderRange.start); + var viewEnd = dateProfile.renderRange.end; + var dayDates = []; + var dayRanges = []; + while (dayStart < viewEnd) { + dayDates.push(dayStart); + dayRanges.push({ + start: dayStart, + end: addDays(dayStart, 1), + }); + dayStart = addDays(dayStart, 1); + } + return { dayDates: dayDates, dayRanges: dayRanges }; + } + // Returns a sparse array of arrays, segs grouped by their dayIndex + function groupSegsByDay(segs) { + var segsByDay = []; // sparse array + var i; + var seg; + for (i = 0; i < segs.length; i += 1) { + seg = segs[i]; + (segsByDay[seg.dayIndex] || (segsByDay[seg.dayIndex] = [])) + .push(seg); + } + return segsByDay; + } + + var OPTION_REFINERS$1 = { + listDayFormat: createFalsableFormatter, + listDaySideFormat: createFalsableFormatter, + noEventsClassNames: identity, + noEventsContent: identity, + noEventsDidMount: identity, + noEventsWillUnmount: identity, + // noEventsText is defined in base options + }; + function createFalsableFormatter(input) { + return input === false ? null : createFormatter(input); + } + + var listPlugin = createPlugin({ + optionRefiners: OPTION_REFINERS$1, + views: { + list: { + component: ListView, + buttonTextKey: 'list', + listDayFormat: { month: 'long', day: 'numeric', year: 'numeric' }, // like "January 1, 2016" + }, + listDay: { + type: 'list', + duration: { days: 1 }, + listDayFormat: { weekday: 'long' }, // day-of-week is all we need. full date is probably in headerToolbar + }, + listWeek: { + type: 'list', + duration: { weeks: 1 }, + listDayFormat: { weekday: 'long' }, + listDaySideFormat: { month: 'long', day: 'numeric', year: 'numeric' }, + }, + listMonth: { + type: 'list', + duration: { month: 1 }, + listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have + }, + listYear: { + type: 'list', + duration: { year: 1 }, + listDaySideFormat: { weekday: 'long' }, // day-of-week is nice-to-have + }, + }, + }); + + var BootstrapTheme = /** @class */ (function (_super) { + __extends(BootstrapTheme, _super); + function BootstrapTheme() { + return _super !== null && _super.apply(this, arguments) || this; + } + return BootstrapTheme; + }(Theme)); + BootstrapTheme.prototype.classes = { + root: 'fc-theme-bootstrap', + table: 'table-bordered', + tableCellShaded: 'table-active', + buttonGroup: 'btn-group', + button: 'btn btn-primary', + buttonActive: 'active', + popover: 'popover', + popoverHeader: 'popover-header', + popoverContent: 'popover-body', + }; + BootstrapTheme.prototype.baseIconClass = 'fa'; + BootstrapTheme.prototype.iconClasses = { + close: 'fa-times', + prev: 'fa-chevron-left', + next: 'fa-chevron-right', + prevYear: 'fa-angle-double-left', + nextYear: 'fa-angle-double-right', + }; + BootstrapTheme.prototype.rtlIconClasses = { + prev: 'fa-chevron-right', + next: 'fa-chevron-left', + prevYear: 'fa-angle-double-right', + nextYear: 'fa-angle-double-left', + }; + BootstrapTheme.prototype.iconOverrideOption = 'bootstrapFontAwesome'; // TODO: make TS-friendly. move the option-processing into this plugin + BootstrapTheme.prototype.iconOverrideCustomButtonOption = 'bootstrapFontAwesome'; + BootstrapTheme.prototype.iconOverridePrefix = 'fa-'; + var plugin = createPlugin({ + themeClasses: { + bootstrap: BootstrapTheme, + }, + }); + + // rename this file to options.ts like other packages? + var OPTION_REFINERS = { + googleCalendarApiKey: String, + }; + + var EVENT_SOURCE_REFINERS = { + googleCalendarApiKey: String, + googleCalendarId: String, + googleCalendarApiBase: String, + extraParams: identity, + }; + + // TODO: expose somehow + var API_BASE = 'https://www.googleapis.com/calendar/v3/calendars'; + var eventSourceDef = { + parseMeta: function (refined) { + var googleCalendarId = refined.googleCalendarId; + if (!googleCalendarId && refined.url) { + googleCalendarId = parseGoogleCalendarId(refined.url); + } + if (googleCalendarId) { + return { + googleCalendarId: googleCalendarId, + googleCalendarApiKey: refined.googleCalendarApiKey, + googleCalendarApiBase: refined.googleCalendarApiBase, + extraParams: refined.extraParams, + }; + } + return null; + }, + fetch: function (arg, onSuccess, onFailure) { + var _a = arg.context, dateEnv = _a.dateEnv, options = _a.options; + var meta = arg.eventSource.meta; + var apiKey = meta.googleCalendarApiKey || options.googleCalendarApiKey; + if (!apiKey) { + onFailure({ + message: 'Specify a googleCalendarApiKey. See http://fullcalendar.io/docs/google_calendar/', + }); + } + else { + var url = buildUrl(meta); + // TODO: make DRY with json-feed-event-source + var extraParams = meta.extraParams; + var extraParamsObj = typeof extraParams === 'function' ? extraParams() : extraParams; + var requestParams_1 = buildRequestParams(arg.range, apiKey, extraParamsObj, dateEnv); + requestJson('GET', url, requestParams_1, function (body, xhr) { + if (body.error) { + onFailure({ + message: 'Google Calendar API: ' + body.error.message, + errors: body.error.errors, + xhr: xhr, + }); + } + else { + onSuccess({ + rawEvents: gcalItemsToRawEventDefs(body.items, requestParams_1.timeZone), + xhr: xhr, + }); + } + }, function (message, xhr) { + onFailure({ message: message, xhr: xhr }); + }); + } + }, + }; + function parseGoogleCalendarId(url) { + var match; + // detect if the ID was specified as a single string. + // will match calendars like "asdf1234@calendar.google.com" in addition to person email calendars. + if (/^[^/]+@([^/.]+\.)*(google|googlemail|gmail)\.com$/.test(url)) { + return url; + } + if ((match = /^https:\/\/www.googleapis.com\/calendar\/v3\/calendars\/([^/]*)/.exec(url)) || + (match = /^https?:\/\/www.google.com\/calendar\/feeds\/([^/]*)/.exec(url))) { + return decodeURIComponent(match[1]); + } + return null; + } + function buildUrl(meta) { + var apiBase = meta.googleCalendarApiBase; + if (!apiBase) { + apiBase = API_BASE; + } + return apiBase + '/' + encodeURIComponent(meta.googleCalendarId) + '/events'; + } + function buildRequestParams(range, apiKey, extraParams, dateEnv) { + var params; + var startStr; + var endStr; + if (dateEnv.canComputeOffset) { + // strings will naturally have offsets, which GCal needs + startStr = dateEnv.formatIso(range.start); + endStr = dateEnv.formatIso(range.end); + } + else { + // when timezone isn't known, we don't know what the UTC offset should be, so ask for +/- 1 day + // from the UTC day-start to guarantee we're getting all the events + // (start/end will be UTC-coerced dates, so toISOString is okay) + startStr = addDays(range.start, -1).toISOString(); + endStr = addDays(range.end, 1).toISOString(); + } + params = __assign(__assign({}, (extraParams || {})), { key: apiKey, timeMin: startStr, timeMax: endStr, singleEvents: true, maxResults: 9999 }); + if (dateEnv.timeZone !== 'local') { + params.timeZone = dateEnv.timeZone; + } + return params; + } + function gcalItemsToRawEventDefs(items, gcalTimezone) { + return items.map(function (item) { return gcalItemToRawEventDef(item, gcalTimezone); }); + } + function gcalItemToRawEventDef(item, gcalTimezone) { + var url = item.htmlLink || null; + // make the URLs for each event show times in the correct timezone + if (url && gcalTimezone) { + url = injectQsComponent(url, 'ctz=' + gcalTimezone); + } + return { + id: item.id, + title: item.summary, + start: item.start.dateTime || item.start.date, + end: item.end.dateTime || item.end.date, + url: url, + location: item.location, + description: item.description, + attachments: item.attachments || [], + extendedProps: (item.extendedProperties || {}).shared || {}, + }; + } + // Injects a string like "arg=value" into the querystring of a URL + // TODO: move to a general util file? + function injectQsComponent(url, component) { + // inject it after the querystring but before the fragment + return url.replace(/(\?.*?)?(#|$)/, function (whole, qs, hash) { return (qs ? qs + '&' : '?') + component + hash; }); + } + var googleCalendarPlugin = createPlugin({ + eventSourceDefs: [eventSourceDef], + optionRefiners: OPTION_REFINERS, + eventSourceRefiners: EVENT_SOURCE_REFINERS, + }); + + globalPlugins.push(interactionPlugin, dayGridPlugin, timeGridPlugin, listPlugin, plugin, googleCalendarPlugin); + + exports.BASE_OPTION_DEFAULTS = BASE_OPTION_DEFAULTS; + exports.BASE_OPTION_REFINERS = BASE_OPTION_REFINERS; + exports.BaseComponent = BaseComponent; + exports.BgEvent = BgEvent; + exports.BootstrapTheme = BootstrapTheme; + exports.Calendar = Calendar; + exports.CalendarApi = CalendarApi; + exports.CalendarContent = CalendarContent; + exports.CalendarDataManager = CalendarDataManager; + exports.CalendarDataProvider = CalendarDataProvider; + exports.CalendarRoot = CalendarRoot; + exports.Component = Component; + exports.ContentHook = ContentHook; + exports.CustomContentRenderContext = CustomContentRenderContext; + exports.DateComponent = DateComponent; + exports.DateEnv = DateEnv; + exports.DateProfileGenerator = DateProfileGenerator; + exports.DayCellContent = DayCellContent; + exports.DayCellRoot = DayCellRoot; + exports.DayGridView = DayTableView; + exports.DayHeader = DayHeader; + exports.DaySeriesModel = DaySeriesModel; + exports.DayTable = DayTable; + exports.DayTableModel = DayTableModel; + exports.DayTableSlicer = DayTableSlicer; + exports.DayTimeCols = DayTimeCols; + exports.DayTimeColsSlicer = DayTimeColsSlicer; + exports.DayTimeColsView = DayTimeColsView; + exports.DelayedRunner = DelayedRunner; + exports.Draggable = ExternalDraggable; + exports.ElementDragging = ElementDragging; + exports.ElementScrollController = ElementScrollController; + exports.Emitter = Emitter; + exports.EventApi = EventApi; + exports.EventRoot = EventRoot; + exports.EventSourceApi = EventSourceApi; + exports.FeaturefulElementDragging = FeaturefulElementDragging; + exports.Fragment = Fragment; + exports.Interaction = Interaction; + exports.ListView = ListView; + exports.MoreLinkRoot = MoreLinkRoot; + exports.MountHook = MountHook; + exports.NamedTimeZoneImpl = NamedTimeZoneImpl; + exports.NowIndicatorRoot = NowIndicatorRoot; + exports.NowTimer = NowTimer; + exports.PointerDragging = PointerDragging; + exports.PositionCache = PositionCache; + exports.RefMap = RefMap; + exports.RenderHook = RenderHook; + exports.ScrollController = ScrollController; + exports.ScrollResponder = ScrollResponder; + exports.Scroller = Scroller; + exports.SegHierarchy = SegHierarchy; + exports.SimpleScrollGrid = SimpleScrollGrid; + exports.Slicer = Slicer; + exports.Splitter = Splitter; + exports.StandardEvent = StandardEvent; + exports.Table = Table; + exports.TableDateCell = TableDateCell; + exports.TableDowCell = TableDowCell; + exports.TableView = TableView; + exports.Theme = Theme; + exports.ThirdPartyDraggable = ThirdPartyDraggable; + exports.TimeCols = TimeCols; + exports.TimeColsSlatsCoords = TimeColsSlatsCoords; + exports.TimeColsView = TimeColsView; + exports.ViewApi = ViewApi; + exports.ViewContextType = ViewContextType; + exports.ViewRoot = ViewRoot; + exports.WeekNumberRoot = WeekNumberRoot; + exports.WindowScrollController = WindowScrollController; + exports.addDays = addDays; + exports.addDurations = addDurations; + exports.addMs = addMs; + exports.addWeeks = addWeeks; + exports.allowContextMenu = allowContextMenu; + exports.allowSelection = allowSelection; + exports.applyMutationToEventStore = applyMutationToEventStore; + exports.applyStyle = applyStyle; + exports.applyStyleProp = applyStyleProp; + exports.asCleanDays = asCleanDays; + exports.asRoughMinutes = asRoughMinutes; + exports.asRoughMs = asRoughMs; + exports.asRoughSeconds = asRoughSeconds; + exports.binarySearch = binarySearch; + exports.buildClassNameNormalizer = buildClassNameNormalizer; + exports.buildDayRanges = buildDayRanges; + exports.buildDayTableModel = buildDayTableModel; + exports.buildEntryKey = buildEntryKey; + exports.buildEventApis = buildEventApis; + exports.buildEventRangeKey = buildEventRangeKey; + exports.buildHashFromArray = buildHashFromArray; + exports.buildIsoString = buildIsoString; + exports.buildNavLinkData = buildNavLinkData; + exports.buildSegCompareObj = buildSegCompareObj; + exports.buildSegTimeText = buildSegTimeText; + exports.buildSlatMetas = buildSlatMetas; + exports.buildTimeColsModel = buildTimeColsModel; + exports.collectFromHash = collectFromHash; + exports.combineEventUis = combineEventUis; + exports.compareByFieldSpec = compareByFieldSpec; + exports.compareByFieldSpecs = compareByFieldSpecs; + exports.compareNumbers = compareNumbers; + exports.compareObjs = compareObjs; + exports.computeEarliestSegStart = computeEarliestSegStart; + exports.computeEdges = computeEdges; + exports.computeFallbackHeaderFormat = computeFallbackHeaderFormat; + exports.computeHeightAndMargins = computeHeightAndMargins; + exports.computeInnerRect = computeInnerRect; + exports.computeRect = computeRect; + exports.computeSegDraggable = computeSegDraggable; + exports.computeSegEndResizable = computeSegEndResizable; + exports.computeSegStartResizable = computeSegStartResizable; + exports.computeShrinkWidth = computeShrinkWidth; + exports.computeSmallestCellWidth = computeSmallestCellWidth; + exports.computeVisibleDayRange = computeVisibleDayRange; + exports.config = config; + exports.constrainPoint = constrainPoint; + exports.createContext = createContext; + exports.createDuration = createDuration; + exports.createElement = createElement; + exports.createEmptyEventStore = createEmptyEventStore; + exports.createEventInstance = createEventInstance; + exports.createEventUi = createEventUi; + exports.createFormatter = createFormatter; + exports.createPlugin = createPlugin; + exports.createPortal = createPortal; + exports.createRef = createRef; + exports.diffDates = diffDates; + exports.diffDayAndTime = diffDayAndTime; + exports.diffDays = diffDays; + exports.diffPoints = diffPoints; + exports.diffWeeks = diffWeeks; + exports.diffWholeDays = diffWholeDays; + exports.diffWholeWeeks = diffWholeWeeks; + exports.disableCursor = disableCursor; + exports.elementClosest = elementClosest; + exports.elementMatches = elementMatches; + exports.enableCursor = enableCursor; + exports.eventTupleToStore = eventTupleToStore; + exports.filterEventStoreDefs = filterEventStoreDefs; + exports.filterHash = filterHash; + exports.findDirectChildren = findDirectChildren; + exports.findElements = findElements; + exports.flexibleCompare = flexibleCompare; + exports.flushToDom = flushToDom; + exports.formatDate = formatDate; + exports.formatDayString = formatDayString; + exports.formatIsoTimeString = formatIsoTimeString; + exports.formatRange = formatRange; + exports.getAllowYScrolling = getAllowYScrolling; + exports.getCanVGrowWithinCell = getCanVGrowWithinCell; + exports.getClippingParents = getClippingParents; + exports.getDateMeta = getDateMeta; + exports.getDayClassNames = getDayClassNames; + exports.getDefaultEventEnd = getDefaultEventEnd; + exports.getElRoot = getElRoot; + exports.getElSeg = getElSeg; + exports.getEntrySpanEnd = getEntrySpanEnd; + exports.getEventClassNames = getEventClassNames; + exports.getEventTargetViaRoot = getEventTargetViaRoot; + exports.getIsRtlScrollbarOnLeft = getIsRtlScrollbarOnLeft; + exports.getRectCenter = getRectCenter; + exports.getRelevantEvents = getRelevantEvents; + exports.getScrollGridClassNames = getScrollGridClassNames; + exports.getScrollbarWidths = getScrollbarWidths; + exports.getSectionClassNames = getSectionClassNames; + exports.getSectionHasLiquidHeight = getSectionHasLiquidHeight; + exports.getSegMeta = getSegMeta; + exports.getSlotClassNames = getSlotClassNames; + exports.getStickyFooterScrollbar = getStickyFooterScrollbar; + exports.getStickyHeaderDates = getStickyHeaderDates; + exports.getUnequalProps = getUnequalProps; + exports.globalLocales = globalLocales; + exports.globalPlugins = globalPlugins; + exports.greatestDurationDenominator = greatestDurationDenominator; + exports.groupIntersectingEntries = groupIntersectingEntries; + exports.guid = guid; + exports.hasBgRendering = hasBgRendering; + exports.hasShrinkWidth = hasShrinkWidth; + exports.identity = identity; + exports.interactionSettingsStore = interactionSettingsStore; + exports.interactionSettingsToStore = interactionSettingsToStore; + exports.intersectRanges = intersectRanges; + exports.intersectRects = intersectRects; + exports.intersectSpans = intersectSpans; + exports.isArraysEqual = isArraysEqual; + exports.isColPropsEqual = isColPropsEqual; + exports.isDateSelectionValid = isDateSelectionValid; + exports.isDateSpansEqual = isDateSpansEqual; + exports.isInt = isInt; + exports.isInteractionValid = isInteractionValid; + exports.isMultiDayRange = isMultiDayRange; + exports.isPropsEqual = isPropsEqual; + exports.isPropsValid = isPropsValid; + exports.isValidDate = isValidDate; + exports.joinSpans = joinSpans; + exports.listenBySelector = listenBySelector; + exports.mapHash = mapHash; + exports.memoize = memoize; + exports.memoizeArraylike = memoizeArraylike; + exports.memoizeHashlike = memoizeHashlike; + exports.memoizeObjArg = memoizeObjArg; + exports.mergeEventStores = mergeEventStores; + exports.multiplyDuration = multiplyDuration; + exports.padStart = padStart; + exports.parseBusinessHours = parseBusinessHours; + exports.parseClassNames = parseClassNames; + exports.parseDragMeta = parseDragMeta; + exports.parseEventDef = parseEventDef; + exports.parseFieldSpecs = parseFieldSpecs; + exports.parseMarker = parse; + exports.pointInsideRect = pointInsideRect; + exports.preventContextMenu = preventContextMenu; + exports.preventDefault = preventDefault; + exports.preventSelection = preventSelection; + exports.rangeContainsMarker = rangeContainsMarker; + exports.rangeContainsRange = rangeContainsRange; + exports.rangesEqual = rangesEqual; + exports.rangesIntersect = rangesIntersect; + exports.refineEventDef = refineEventDef; + exports.refineProps = refineProps; + exports.removeElement = removeElement; + exports.removeExact = removeExact; + exports.render = render; + exports.renderChunkContent = renderChunkContent; + exports.renderFill = renderFill; + exports.renderMicroColGroup = renderMicroColGroup; + exports.renderScrollShim = renderScrollShim; + exports.requestJson = requestJson; + exports.sanitizeShrinkWidth = sanitizeShrinkWidth; + exports.setElSeg = setElSeg; + exports.setRef = setRef; + exports.sliceEventStore = sliceEventStore; + exports.sliceEvents = sliceEvents; + exports.sortEventSegs = sortEventSegs; + exports.startOfDay = startOfDay; + exports.translateRect = translateRect; + exports.triggerDateSelect = triggerDateSelect; + exports.unmountComponentAtNode = unmountComponentAtNode; + exports.unpromisify = unpromisify; + exports.version = version; + exports.whenTransitionDone = whenTransitionDone; + exports.wholeDivideDurations = wholeDivideDurations; + + Object.defineProperty(exports, '__esModule', { value: true }); + + return exports; + +}({})); diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/interface.html new file mode 100644 index 000000000..b26c07723 --- /dev/null +++ b/apps/schoolCalendar/interface.html @@ -0,0 +1,82 @@ + + + +
+

Create your events on the current week. Keep in note that your events repeat weekly.

+

One you have created your events, Click

+
+ + + + + + + +
+ + diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js new file mode 100644 index 000000000..be4c45d45 --- /dev/null +++ b/apps/schoolCalendar/schoolCalendar.js @@ -0,0 +1,229 @@ +require("FontTeletext5x9Mode7").add(Graphics); +Bangle.setLCDMode(); + +function getBackgroundImage() { + return require("heatshrink").decompress(atob("gMwyEgBAsAgQBCgcAggBCgsAgwBCg8AhABChMAhQBChcAhgBChsAhwBCh8AiEAiIBCiUAiYBCikAioBCi0Ai4BCjEAjIBCjUAjYBCjkAjoBCj0Aj4BBA")); +} + +Graphics.prototype.setFontAudiowide = function() { + // Actual height 33 (36 - 4) + var widths = atob("BxYfDBkYGhkZFRkZCA=="); + var font = atob("AAAAAAAAA8AAAAHgAAAB8AAAAHgAAAA4AAAAAAAAAAEAAAABgAAAA8AAAAPgAAAH8AAAB/gAAA/4AAAf+AAAH/AAAD/wAAA/4AAAf8AAAP/AAAD/gAAB/4AAAf8AAAD+AAAAfgAAADwAAAAcAAAAAAAAAAAAAAAAAAAAAAP/AAAH//AAB//8AAf//wAH///AA///4APwD/gB8A/8APgP/gB8D98AfAfvgD4H58AfB/PgD4Px8AfD8PgD4/h8AfH4PgD5+B8AP/wPgB/8B8AP/APgB/4D8AH8B/AA///4AD//+AAP//gAA//4AAB/8AAAAAAAAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAAAAD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAA/8AAAf/gD4H/8AfA//gD4P/8AfB+PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//B8AP/4PgA/+B8AD/gPgABgA8AAAAAAAAAAAAPA4HgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP///gB///8AH///AA///wAB//8AAAAAAAAAAAAB/8AAAf/4AAD//gAAf/8AAD//gAAf/8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAP///gD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8B8Af/wPgD//B8Af/4PgD//h8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AfA//AAAD/4AAAP8AAAAAAAAAAAAAH//AAB//8AAf//wAH///AB///8AP58/gB8Ph8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AAA//AAAD/4AAAP8AAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAABgD4AA8AfAAPgD4AH8AfAD/gD4A/8AfAf/AD4P/gAfH/wAD5/8AAf/+AAD//AAAf/gAAD/4AAAf8AAAB+AAAAPAAAAAAAAAAAAAAAAAB/gAAAf+AAP//4AH///gA///8AP/+PgB//h8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//h8AP///gA///8AD///AADz/4AAAP8AAAAMAAAAAAAAAAAAAB/gAAA/+AAAH/4AAB//B8AP/8PgB8Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8APh8PgB8Ph8APx8fgB///8AH///AAf//wAD//8AAH//AAAAAAAAAAAAAAAAAAAAAAAAAOAHgAD4A8AAfAPgAD4A8AAOAHAAAAAAA=="); + var scale = 1; // size multiplier for this font + g.setFontCustom(font, 46, widths, 33+(scale<<8)+(1<<16)); +}; + +function logDebug(message){ + //console.log(message); +} + +var NEXTCLASS = 4; +var CURRRENTCLASS = 3; +var NEXTNEXTCLASS = 5; +var BEHINDCLASS = 2; +var BEHINDBEHINDCLASS = 1; +var NEXTNEXTNEXTCLASS = 6; +var stage = 3; + +function drawInfo(){ + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var currentMinuteUpdated; + var currentHourUpdated; + if (currentMinute<10){ + currentMinuteUpdated = "0"+currentMinute; + }else{ + currentMinuteUpdated = currentMinute; + }if(currentHour >= 13){ + currentHourUpdated = currentHour-12; + }else{ + currentHourUpdated = currentHour; + } + for(var i = 0;i<=240;i++){ + g.drawImage(getBackgroundImage(),i,120,{scale:5,rotate:0}); + } + g.setColor(255,255,255); + g.setFont("Audiowide"); + g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 145, 16); + g.setFont("Teletext5x9Mode7", 2); + foundClass = processDay(); + if (foundClass.startingTimeMinute<10){ + classMinuteUpdated = "0"+foundClass.startingTimeMinute; + }else{ + classMinuteUpdated = foundClass.startingTimeMinute; + } + if (foundClass.endingTimeMinute<10){ + classEndingMinuteUpdated = "0"+foundClass.endingTimeMinute; + }else{ + classEndingMinuteUpdated = foundClass.endingTimeMinute; + }if(foundClass.startingTimeHour >= 13){ + classHourUpdated = foundClass.startingTimeHour-12; + }else{ + classHourUpdated = foundClass.startingTimeHour; + }if(foundClass.endingTimeHour >= 13){ + classEndingHourUpdated = foundClass.endingTimeHour-12; + }else{ + classEndingHourUpdated = foundClass.endingTimeHour; + } + switch (foundClass.dayOfWeek) { + case 0: + updatedDay = "Sun"; + break; + case 1: + updatedDay = "Mon"; + break; + case 2: + updatedDay = "Tue"; + break; + case 3: + updatedDay = "Wed"; + break; + case 4: + updatedDay = "Thur"; + break; + case 5: + updatedDay = "Fri"; + break; + case 6: + updatedDay = "Sat"; +} + if (foundClass != null) { + g.drawString(classHourUpdated+":"+classMinuteUpdated+" - "+classEndingHourUpdated+":"+classEndingMinuteUpdated+" "+updatedDay, 25, 50); + g.drawString(foundClass.className, 25, 80); + g.drawString(foundClass.teacher, 25, 110); + g.drawString(foundClass.roomNumber, 25, 140); + } +} +setInterval(drawInfo, 60000); + +function processDay(){ + let schedule = [ + //Sunday + + //Monday: + {className: "Biblical Theology", dayOfWeek:1, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "English", dayOfWeek:1, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"English 7B 4B Dr. Wong Block 4B M206", teacher:"Dr. Wong"}, + {className: "Break", dayOfWeek:1, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", teacher:""}, + {className: "MS Robotics", dayOfWeek:1, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "MS Physical Education Boys", dayOfWeek:1, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Physical Education Boys S1B Mr. Mendezona MS MF Elective Block B Gym", roomNumber:"GYM", teacher:"Mr. Mendezona"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:1, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:"Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:1, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:1, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Latin", dayOfWeek:1, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Latin 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + {className: "Algebra 1", dayOfWeek:1, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + + //Tuesday: + {className: "Logic", dayOfWeek:2, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 0, description:"Logic 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + {className: "Algebra 1", dayOfWeek:2, startingTimeHour: 9, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {className: "Chapel", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 25, description:"Chapel MF MS", roomNumber:"Advisory", teacher:""}, + {className: "Break", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 25, endingTimeHour:10, endingTimeMinute: 35, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "Advisory Besaw", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 35, endingTimeHour:11, endingTimeMinute: 0, description:"Advisory Besaw Mr. Besaw Advisory MF MS M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "MS Robotics", dayOfWeek:2, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:2, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 5, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Medieval Western Civilization", dayOfWeek:2, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1BM205", roomNumber:"205", teacher:"Mr. Khule"}, + {className: "Introductory Biology and Epidemiology", dayOfWeek:2, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + + //Wensday: + {className: "English", dayOfWeek:3, startingTimeHour: 9, startingTimeMinute: 0, endingTimeHour:9, endingTimeMinute: 55, description:"English 7B 4B Dr. Wong Block 4B M206", roomNumber:"206", teacher:"Dr. Wong"}, + {className: "Biblical Theology", dayOfWeek:3, startingTimeHour: 9, startingTimeMinute: 55, endingTimeHour:10, endingTimeMinute: 50, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "Break", dayOfWeek:3, startingTimeHour: 10, startingTimeMinute: 50, endingTimeHour:11, endingTimeMinute: 0, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "MS Physical Education Boys", dayOfWeek:3, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Physical Education Boys S1B Mr. Mendezona MS MF Elective Block B Gym", roomNumber:"GYM", teacher:"Mr. Mendezona"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:3, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:3, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Introductory Biology and Epidemiology", dayOfWeek:3, startingTimeHour: 13, startingTimeMinute: 0, endingTimeHour:14, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + {className: "Medieval Western Civilization", dayOfWeek:3, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1B M205", roomNumber:"205", teacher:"Mr. Khule"}, + + //Thursday: + {className: "Algebra 1", dayOfWeek:4, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {className: "Latin", dayOfWeek:4, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"Latin 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + {className: "Break", dayOfWeek:4, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "MS Robotics", dayOfWeek:4, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "Advisory Besaw", dayOfWeek:4, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Advisory Besaw Mr. Besaw Advisory MF MS M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "Lunch", dayOfWeek:4, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:4, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Biblical Theology", dayOfWeek:4, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, + {className: "English", dayOfWeek:4, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"English 7B 4B Dr. Wong Block 4B M206", roomNumber:"206", teacher:"Dr. Wong"}, + + //Friday: + {className: "Medieval Western Civilization", dayOfWeek:5, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1B M205", roomNumber:"205", teacher:"Mr. Khule"}, + {className: "Introductory Biology and Epidemiology", dayOfWeek:5, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + {className: "Break", dayOfWeek:5, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", roomNumber:"Outside", teacher:""}, + {className: "MS Robotics", dayOfWeek:5, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, + {className: "Office Hours Besaw/Nunez", dayOfWeek:5, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, + {className: "Lunch", dayOfWeek:5, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, + {className: "Activity Period", dayOfWeek:5, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, + {className: "Algebra 1", dayOfWeek:5, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {className: "Logic", dayOfWeek:5, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Logic 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, + + //Sataturday: + ]; + + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var minofDay = (currentHour*60)+currentMinute; + var i; + var currentPositon; + for(i = 0;i= (schedule[i].startingTimeHour*60+schedule[i].startingTimeMinute) && minofDay < (schedule[i].endingTimeHour*60+schedule[i].endingTimeMinute) ){ + console.log("Match:" + schedule[i].className); + console.log("stage:" + stage); + if(stage == 3){ + return schedule[i]; + }else if(stage == 4 && ++currentPositon <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 5 && (currentPositon+=2) <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 6 && (currentPositon+=3) <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 2 && (currentPositon-=1) <= schedule.length){ + return schedule[currentPositon]; + }else if(stage == 1 && (currentPositon-=2) <= schedule.length){ + return schedule[currentPositon]; + } + } + } + } + return null; +} + + +setWatch(() => { + if(stage<=1){ + }else{ + stage -= 1; + drawInfo(); + } +}, BTN1, {repeat:true}); + +setWatch(() => { +}, BTN2, {repeat:true}); + +setWatch(() => { + if(stage>=6){ + }else{ + stage += 1; + drawInfo(); + } +}, BTN3, {repeat:true}); + +setWatch(() => { + +}, BTN4, {repeat:true}); + +setWatch(() => { + +}, BTN5, {repeat:true}); + +drawInfo(); From 3c9e46c2160f4ff58708afc0d08ce2e5d27df097 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 4 Oct 2021 15:59:03 -0700 Subject: [PATCH 0127/1062] Update apps.json --- apps.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps.json b/apps.json index 7e7540581..6fcd930c2 100644 --- a/apps.json +++ b/apps.json @@ -3499,9 +3499,9 @@ {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} ] }, -{ "id": "schoolCalender", +{ "id": "schoolCalendar", "name": "School Calendar", - "shortName":"SCalender", + "shortName":"SCalendar", "icon": "CalenderLogo.png", "version":"0.01", "description": "A simple calendar that you can see your upcoming events. Keep in note that your events reapeat weekly.", @@ -3509,7 +3509,7 @@ "readme": "README.md", "custom":"interface.html", "storage": [ - {"name":"schoolCalender.app.js","url":"app.js"}, + {"name":"schoolCalendar.app.js","url":"app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} ], "data": [ From a61879753285b4469772f51474c29fe86a5aa942 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 4 Oct 2021 19:55:30 -0700 Subject: [PATCH 0128/1062] Update schoolCalendar.js --- apps/schoolCalendar/schoolCalendar.js | 354 +++++++++++++------------- 1 file changed, 172 insertions(+), 182 deletions(-) diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js index be4c45d45..a6537aa66 100644 --- a/apps/schoolCalendar/schoolCalendar.js +++ b/apps/schoolCalendar/schoolCalendar.js @@ -1,197 +1,106 @@ -require("FontTeletext5x9Mode7").add(Graphics); -Bangle.setLCDMode(); +require("Font8x12").add(Graphics); -function getBackgroundImage() { - return require("heatshrink").decompress(atob("gMwyEgBAsAgQBCgcAggBCgsAgwBCg8AhABChMAhQBChcAhgBChsAhwBCh8AiEAiIBCiUAiYBCikAioBCi0Ai4BCjEAjIBCjUAjYBCjkAjoBCj0Aj4BBA")); -} - -Graphics.prototype.setFontAudiowide = function() { - // Actual height 33 (36 - 4) +Graphics.prototype.setFontAudiowide = function () { var widths = atob("BxYfDBkYGhkZFRkZCA=="); var font = atob("AAAAAAAAA8AAAAHgAAAB8AAAAHgAAAA4AAAAAAAAAAEAAAABgAAAA8AAAAPgAAAH8AAAB/gAAA/4AAAf+AAAH/AAAD/wAAA/4AAAf8AAAP/AAAD/gAAB/4AAAf8AAAD+AAAAfgAAADwAAAAcAAAAAAAAAAAAAAAAAAAAAAP/AAAH//AAB//8AAf//wAH///AA///4APwD/gB8A/8APgP/gB8D98AfAfvgD4H58AfB/PgD4Px8AfD8PgD4/h8AfH4PgD5+B8AP/wPgB/8B8AP/APgB/4D8AH8B/AA///4AD//+AAP//gAA//4AAB/8AAAAAAAAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAAAAD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAA/8AAAf/gD4H/8AfA//gD4P/8AfB+PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//B8AP/4PgA/+B8AD/gPgABgA8AAAAAAAAAAAAPA4HgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP///gB///8AH///AA///wAB//8AAAAAAAAAAAAB/8AAAf/4AAD//gAAf/8AAD//gAAf/8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAP///gD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8B8Af/wPgD//B8Af/4PgD//h8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AfA//AAAD/4AAAP8AAAAAAAAAAAAAH//AAB//8AAf//wAH///AB///8AP58/gB8Ph8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AAA//AAAD/4AAAP8AAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAABgD4AA8AfAAPgD4AH8AfAD/gD4A/8AfAf/AD4P/gAfH/wAD5/8AAf/+AAD//AAAf/gAAD/4AAAf8AAAB+AAAAPAAAAAAAAAAAAAAAAAB/gAAAf+AAP//4AH///gA///8AP/+PgB//h8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//h8AP///gA///8AD///AADz/4AAAP8AAAAMAAAAAAAAAAAAAB/gAAA/+AAAH/4AAB//B8AP/8PgB8Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8APh8PgB8Ph8APx8fgB///8AH///AAf//wAD//8AAH//AAAAAAAAAAAAAAAAAAAAAAAAAOAHgAD4A8AAfAPgAD4A8AAOAHAAAAAAA=="); var scale = 1; // size multiplier for this font g.setFontCustom(font, 46, widths, 33+(scale<<8)+(1<<16)); }; -function logDebug(message){ - //console.log(message); -} +function getBackgroundImage() {return require("heatshrink").decompress(atob("gMwyEgBAsAgQBCgcAggBCgsAgwBCg8AhABChMAhQBChcAhgBChsAhwBCh8AiEAiIBCiUAiYBCikAioBCi0Ai4BCjEAjIBCjUAjYBCjkAjoBCj0Aj4BBA"));} + +Bangle.setLCDMode("doublebuffered"); +g.clear(); +g.setFont("Audiowide"); +g.drawString("...",115,60); +g.flip(); + +LIST = 1; +INFORMATION = 2; +currentStage = LIST; -var NEXTCLASS = 4; -var CURRRENTCLASS = 3; -var NEXTNEXTCLASS = 5; -var BEHINDCLASS = 2; -var BEHINDBEHINDCLASS = 1; -var NEXTNEXTNEXTCLASS = 6; var stage = 3; - -function drawInfo(){ - var currentDate = new Date(); - var currentDayOfWeek = currentDate.getDay(); - var currentHour = currentDate.getHours(); - var currentMinute = currentDate.getMinutes(); - var currentMinuteUpdated; - var currentHourUpdated; - if (currentMinute<10){ - currentMinuteUpdated = "0"+currentMinute; - }else{ - currentMinuteUpdated = currentMinute; - }if(currentHour >= 13){ - currentHourUpdated = currentHour-12; - }else{ - currentHourUpdated = currentHour; - } - for(var i = 0;i<=240;i++){ - g.drawImage(getBackgroundImage(),i,120,{scale:5,rotate:0}); - } - g.setColor(255,255,255); - g.setFont("Audiowide"); - g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 145, 16); - g.setFont("Teletext5x9Mode7", 2); - foundClass = processDay(); - if (foundClass.startingTimeMinute<10){ - classMinuteUpdated = "0"+foundClass.startingTimeMinute; - }else{ - classMinuteUpdated = foundClass.startingTimeMinute; - } - if (foundClass.endingTimeMinute<10){ - classEndingMinuteUpdated = "0"+foundClass.endingTimeMinute; - }else{ - classEndingMinuteUpdated = foundClass.endingTimeMinute; - }if(foundClass.startingTimeHour >= 13){ - classHourUpdated = foundClass.startingTimeHour-12; - }else{ - classHourUpdated = foundClass.startingTimeHour; - }if(foundClass.endingTimeHour >= 13){ - classEndingHourUpdated = foundClass.endingTimeHour-12; - }else{ - classEndingHourUpdated = foundClass.endingTimeHour; - } - switch (foundClass.dayOfWeek) { - case 0: - updatedDay = "Sun"; - break; - case 1: - updatedDay = "Mon"; - break; - case 2: - updatedDay = "Tue"; - break; - case 3: - updatedDay = "Wed"; - break; - case 4: - updatedDay = "Thur"; - break; - case 5: - updatedDay = "Fri"; - break; - case 6: - updatedDay = "Sat"; -} - if (foundClass != null) { - g.drawString(classHourUpdated+":"+classMinuteUpdated+" - "+classEndingHourUpdated+":"+classEndingMinuteUpdated+" "+updatedDay, 25, 50); - g.drawString(foundClass.className, 25, 80); - g.drawString(foundClass.teacher, 25, 110); - g.drawString(foundClass.roomNumber, 25, 140); - } -} -setInterval(drawInfo, 60000); - -function processDay(){ +function getScheduleTable() { let schedule = [ //Sunday //Monday: - {className: "Biblical Theology", dayOfWeek:1, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, - {className: "English", dayOfWeek:1, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"English 7B 4B Dr. Wong Block 4B M206", teacher:"Dr. Wong"}, - {className: "Break", dayOfWeek:1, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", teacher:""}, - {className: "MS Robotics", dayOfWeek:1, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, - {className: "MS Physical Education Boys", dayOfWeek:1, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Physical Education Boys S1B Mr. Mendezona MS MF Elective Block B Gym", roomNumber:"GYM", teacher:"Mr. Mendezona"}, - {className: "Office Hours Besaw/Nunez", dayOfWeek:1, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:"Besaw/Nunez"}, - {className: "Lunch", dayOfWeek:1, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, - {className: "Activity Period", dayOfWeek:1, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, - {className: "Latin", dayOfWeek:1, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Latin 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, - {className: "Algebra 1", dayOfWeek:1, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, + {cn: "Biblical Theology", dow:1, sh: 8, sm: 10, eh:9, em: 5, r:"207", t:"Mr. Besaw"}, + {cn: "English", dow:1, sh: 9, sm: 5, eh:10, em: 0, t:"Dr. Wong"}, + {cn: "Break", dow:1, sh: 10, sm: 0, eh:10, em: 10, t:""}, + {cn: "MS Robotics", dow:1, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}, + {cn: "MS Physical Education Boys", dow:1, sh: 11, sm: 0, eh:11, em: 50, r:"GYM", t:"Mr. Mendezona"}, + {cn: "Office Hours Besaw/Nunez", dow:1, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:"Besaw/Nunez"}, + {cn: "Lunch", dow:1, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, + {cn: "Activity Period", dow:1, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, + {cn: "Latin", dow:1, sh: 13, sm: 5, eh:14, em: 0, r:"208", t:"Mrs.Scrivner"}, + {cn: "Algebra 1", dow:1, sh: 14, sm: 0, eh:15, em: 0, r:"204", t:"Mr. Benson"}, //Tuesday: - {className: "Logic", dayOfWeek:2, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 0, description:"Logic 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, - {className: "Algebra 1", dayOfWeek:2, startingTimeHour: 9, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, - {className: "Chapel", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 25, description:"Chapel MF MS", roomNumber:"Advisory", teacher:""}, - {className: "Break", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 25, endingTimeHour:10, endingTimeMinute: 35, description:"Break MF MS", roomNumber:"Outside", teacher:""}, - {className: "Advisory Besaw", dayOfWeek:2, startingTimeHour: 10, startingTimeMinute: 35, endingTimeHour:11, endingTimeMinute: 0, description:"Advisory Besaw Mr. Besaw Advisory MF MS M207", roomNumber:"207", teacher:"Mr. Besaw"}, - {className: "MS Robotics", dayOfWeek:2, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, - {className: "Office Hours Besaw/Nunez", dayOfWeek:2, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, - {className: "Lunch", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, - {className: "Activity Period", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 5, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, - {className: "Medieval Western Civilization", dayOfWeek:2, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1BM205", roomNumber:"205", teacher:"Mr. Khule"}, - {className: "Introductory Biology and Epidemiology", dayOfWeek:2, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, + {cn: "Logic", dow:2, sh: 8, sm: 10, eh:9, em: 0, r:"208", t:"Mrs.Scrivner"}, + {cn: "Algebra 1", dow:2, sh: 9, sm: 0, eh:10, em: 0, r:"204", t:"Mr. Benson"}, + {cn: "Chapel", dow:2, sh: 10, sm: 0, eh:10, em: 25, r:"Advisory", t:""}, + {cn: "Break", dow:2, sh: 10, sm: 25, eh:10, em: 35, r:"Outside", t:""}, + {cn: "Advisory Besaw", dow:2, sh: 10, sm: 35, eh:11, em: 0, r:"207", t:"Mr. Besaw"}, + {cn: "MS Robotics", dow:2, sh: 11, sm: 0, eh:11, em: 50, r:"211", t:"Mr. Broyles"}, + {cn: "Office Hours Besaw/Nunez", dow:2, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"}, + {cn: "Lunch", dow:2, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, + {cn: "Activity Period", dow:2, sh: 12, sm: 50, eh:13, em: 5, r:"Outside", t:""}, + {cn: "Medieval Western Civilization", dow:2, sh: 13, sm: 5, eh:14, em: 0, r:"205", t:"Mr. Khule"}, + {cn: "Introductory Biology and Epidemiology", dow:2, sh: 14, sm: 0, eh:15, em: 0, r:"202", t:"Mrs. Brown"}, //Wensday: - {className: "English", dayOfWeek:3, startingTimeHour: 9, startingTimeMinute: 0, endingTimeHour:9, endingTimeMinute: 55, description:"English 7B 4B Dr. Wong Block 4B M206", roomNumber:"206", teacher:"Dr. Wong"}, - {className: "Biblical Theology", dayOfWeek:3, startingTimeHour: 9, startingTimeMinute: 55, endingTimeHour:10, endingTimeMinute: 50, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, - {className: "Break", dayOfWeek:3, startingTimeHour: 10, startingTimeMinute: 50, endingTimeHour:11, endingTimeMinute: 0, description:"Break MF MS", roomNumber:"Outside", teacher:""}, - {className: "MS Physical Education Boys", dayOfWeek:3, startingTimeHour: 11, startingTimeMinute: 0, endingTimeHour:11, endingTimeMinute: 50, description:"MS Physical Education Boys S1B Mr. Mendezona MS MF Elective Block B Gym", roomNumber:"GYM", teacher:"Mr. Mendezona"}, - {className: "Office Hours Besaw/Nunez", dayOfWeek:3, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, - {className: "Lunch", dayOfWeek:3, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, - {className: "Activity Period", dayOfWeek:2, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, - {className: "Introductory Biology and Epidemiology", dayOfWeek:3, startingTimeHour: 13, startingTimeMinute: 0, endingTimeHour:14, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, - {className: "Medieval Western Civilization", dayOfWeek:3, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1B M205", roomNumber:"205", teacher:"Mr. Khule"}, + {cn: "English", dow:3, sh: 9, sm: 0, eh:9, em: 55, r:"206", t:"Dr. Wong"}, + {cn: "Biblical Theology", dow:3, sh: 9, sm: 55, eh:10, em: 50, r:"207", t:"Mr. Besaw"}, + {cn: "Break", dow:3, sh: 10, sm: 50, eh:11, em: 0, r:"Outside", t:"_"}, + {cn: "MS Physical Education Boys", dow:3, sh: 11, sm: 0, eh:11, em: 50, r:"GYM", t:"Mr. Mendezona"}, + {cn: "Office Hours Besaw/Nunez", dow:3, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"}, + {cn: "Lunch", dow:3, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, + {cn: "Activity Period", dow:2, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, + {cn: "Introductory Biology and Epidemiology", dow:3, sh: 13, sm: 0, eh:14, em: 0, r:"202", t:"Mrs. Brown"}, + {cn: "Medieval Western Civilization", dow:3, sh: 14, sm: 0, eh:15, em: 0, r:"205", t:"Mr. Khule"}, + //Thursday: - {className: "Algebra 1", dayOfWeek:4, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, - {className: "Latin", dayOfWeek:4, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"Latin 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, - {className: "Break", dayOfWeek:4, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", roomNumber:"Outside", teacher:""}, - {className: "MS Robotics", dayOfWeek:4, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, - {className: "Advisory Besaw", dayOfWeek:4, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Advisory Besaw Mr. Besaw Advisory MF MS M207", roomNumber:"207", teacher:"Mr. Besaw"}, - {className: "Lunch", dayOfWeek:4, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, - {className: "Activity Period", dayOfWeek:4, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, - {className: "Biblical Theology", dayOfWeek:4, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Biblical Theology 7B 3B Mr. Besaw Block 3B M207", roomNumber:"207", teacher:"Mr. Besaw"}, - {className: "English", dayOfWeek:4, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"English 7B 4B Dr. Wong Block 4B M206", roomNumber:"206", teacher:"Dr. Wong"}, + {cn: "Algebra 1", dow:4, sh: 8, sm: 10, eh:9, em: 5, r:"204", t:"Mr. Benson"}, + {cn: "Latin", dow:4, sh: 9, sm: 5, eh:10, em: 0, r:"208", t:"Mrs.Scrivner"}, + {cn: "Break", dow:4, sh: 10, sm: 0, eh:10, em: 10, r:"Outside", t:""}, + {cn: "MS Robotics", dow:4, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}, + {cn: "Advisory Besaw", dow:4, sh: 11, sm: 50, eh:12, em: 25, r:"207", t:"Mr. Besaw"}, + {cn: "Lunch", dow:4, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, + {cn: "Activity Period", dow:4, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, + {cn: "Biblical Theology", dow:4, sh: 13, sm: 5, eh:14, em: 0, r:"207", t:"Mr. Besaw"}, + {cn: "English", dow:4, sh: 14, sm: 0, eh:15, em: 0, r:"206", t:"Dr. Wong"}, //Friday: - {className: "Medieval Western Civilization", dayOfWeek:5, startingTimeHour: 8, startingTimeMinute: 10, endingTimeHour:9, endingTimeMinute: 5, description:"Medieval Western Civilization 7B 1B Mr. Kuhle Block 1B M205", roomNumber:"205", teacher:"Mr. Khule"}, - {className: "Introductory Biology and Epidemiology", dayOfWeek:5, startingTimeHour: 9, startingTimeMinute: 5, endingTimeHour:10, endingTimeMinute: 0, description:"Introductory Biology and Epidemiology 7B 2B Mrs. Brown Block 2B M202", roomNumber:"202", teacher:"Mrs. Brown"}, - {className: "Break", dayOfWeek:5, startingTimeHour: 10, startingTimeMinute: 0, endingTimeHour:10, endingTimeMinute: 10, description:"Break MF MS", roomNumber:"Outside", teacher:""}, - {className: "MS Robotics", dayOfWeek:5, startingTimeHour: 10, startingTimeMinute: 10, endingTimeHour:11, endingTimeMinute: 0, description:"MS Robotics S1A Mr. Broyles MS MF Elective Block A M211", roomNumber:"211", teacher:"Mr. Broyles"}, - {className: "Office Hours Besaw/Nunez", dayOfWeek:5, startingTimeHour: 11, startingTimeMinute: 50, endingTimeHour:12, endingTimeMinute: 25, description:"Office Hours Besaw/Nunez Mr. Besaw, Dr. Nunez, Mrs.McDonough, Mr. Pettit Office Hours MF MS M203", roomNumber:"203", teacher:" Besaw/Nunez"}, - {className: "Lunch", dayOfWeek:5, startingTimeHour: 12, startingTimeMinute: 25, endingTimeHour:12, endingTimeMinute: 50, description:"Lunch MF MS", roomNumber:"Commence or Advisory", teacher:""}, - {className: "Activity Period", dayOfWeek:5, startingTimeHour: 12, startingTimeMinute: 50, endingTimeHour:13, endingTimeMinute: 0, description:"Activity Period MF MS", roomNumber:"Outside", teacher:""}, - {className: "Algebra 1", dayOfWeek:5, startingTimeHour: 13, startingTimeMinute: 5, endingTimeHour:14, endingTimeMinute: 0, description:"Algebra 1 7B 6B Mr. Benson Block 6B M204", roomNumber:"204", teacher:"Mr. Benson"}, - {className: "Logic", dayOfWeek:5, startingTimeHour: 14, startingTimeMinute: 0, endingTimeHour:15, endingTimeMinute: 0, description:"Logic 7B 5B Mrs. Scrivner Block 5B M208", roomNumber:"208", teacher:"Mrs.Scrivner"}, - + {cn: "Medieval Western Civilization", dow:5, sh: 8, sm: 10, eh:9, em: 5, r:"205", t:"Mr. Khule"}, + {cn: "Introductory Biology and Epidemiology", dow:5, sh: 9, sm: 5, eh:10, em: 0, r:"202", t:"Mrs. Brown"}, + {cn: "Break", dow:5, sh: 10, sm: 0, eh:10, em: 10, dr:"Outside", t:""}, + {cn: "MS Robotics", dow:5, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}, + {cn: "Office Hours Besaw/Nunez", dow:5, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"}, + {cn: "Lunch", dow:5, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, + {cn: "Activity Period", dow:5, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, + {cn: "Algebra 1", dow:5, sh: 13, sm: 5, eh:14, em: 0, r:"204", t:"Mr. Benson"}, + {cn: "Logic", dow:5, sh: 14, sm: 0, eh:15, em: 0, r:"208", t:"Mrs.Scrivner"}, //Sataturday: ]; + return schedule; +} +function processDay() { + var schedule = getScheduleTable(); var currentDate = new Date(); - var currentDayOfWeek = currentDate.getDay(); - var currentHour = currentDate.getHours(); - var currentMinute = currentDate.getMinutes(); + var currentDayOfWeek = 2;//currentDate.getDay(); + var currentHour = 9;//currentDate.getHours(); + var currentMinute = 30;//currentDate.getMinutes(); var minofDay = (currentHour*60)+currentMinute; var i; var currentPositon; for(i = 0;i= (schedule[i].startingTimeHour*60+schedule[i].startingTimeMinute) && minofDay < (schedule[i].endingTimeHour*60+schedule[i].endingTimeMinute) ){ - console.log("Match:" + schedule[i].className); - console.log("stage:" + stage); - if(stage == 3){ - return schedule[i]; - }else if(stage == 4 && ++currentPositon <= schedule.length){ - return schedule[currentPositon]; - }else if(stage == 5 && (currentPositon+=2) <= schedule.length){ - return schedule[currentPositon]; - }else if(stage == 6 && (currentPositon+=3) <= schedule.length){ - return schedule[currentPositon]; - }else if(stage == 2 && (currentPositon-=1) <= schedule.length){ - return schedule[currentPositon]; - }else if(stage == 1 && (currentPositon-=2) <= schedule.length){ - return schedule[currentPositon]; - } + if(schedule[i].dow == currentDayOfWeek){ + if(minofDay >= (schedule[i].sh*60+schedule[i].sm) && minofDay < (schedule[i].eh*60+schedule[i].em) ){ + return currentPositon; } } } @@ -199,31 +108,112 @@ function processDay(){ } -setWatch(() => { - if(stage<=1){ +var currentPositionTable = 0; +var numberOfItemsShown = 5; + +function logDebug(message) {console.log(message);} + +function updateMinutesToCurrentTime(currentMinuteFunction) { + if (currentMinuteFunction<10){ + currentMinuteUpdatedFunction = "0"+currentMinuteFunction; }else{ - stage -= 1; - drawInfo(); + currentMinuteUpdatedFunction = currentMinuteFunction; } -}, BTN1, {repeat:true}); + return currentMinuteUpdatedFunction; +} -setWatch(() => { -}, BTN2, {repeat:true}); - -setWatch(() => { - if(stage>=6){ +function updateHoursToCurrentTime(currentHourFunction) { + if(currentHourFunction >= 13){ + currentHourUpdatedFunction = currentHourFunction-12; }else{ - stage += 1; - drawInfo(); + currentHourUpdatedFunction = currentHourFunction; } -}, BTN3, {repeat:true}); + return currentHourUpdatedFunction; +} -setWatch(() => { -}, BTN4, {repeat:true}); -setWatch(() => { +function RedRectDown() { + if(currentPositionTable > 0){ + currentPositionTable -= 1; + displayClock(); + } +} -}, BTN5, {repeat:true}); +function RedRectUp() { + if(currentPositionTable < numberOfItemsShown){ + currentPositionTable += 1; + displayClock(); + } +} -drawInfo(); +function changeScene(){ + if(currentStage == INFORMATION){ + currentStage = LIST; + }else if(currentStage == LIST){ + currentStage = INFORMATION; + } +displayClock(); +} + +function displayClock() { + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var currentMinuteUpdated; + var currentHourUpdated; + currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); + currentHourUpdated = updateHoursToCurrentTime(currentHour); + g.setColor(255,255,255); + g.setFont("Audiowide"); + g.clear(); + var foundNumber = processDay(); + var foundSchedule = getScheduleTable(); + var scheduleHourUpdated; + var scheduleMinuteUpdated; + for(var i = 0;i<=240;i++){ + g.drawImage(getBackgroundImage(),i,120,{scale:5,rotate:0}); + } + g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 150, 0); + if(currentStage == LIST){ + for(var x = 0;x<=numberOfItemsShown;x++){ + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+x)].sm); + scheduleHourUpdatedStart = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+x)].sh); + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+x)].em); + scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+x)].eh); + g.setColor(255,255,255); + g.drawRect(10,30+(x*20),230,50+(20*x)); + g.reset(); + g.setFont("8x12"); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+foundSchedule[((foundNumber-2)+x)].cn,13,35+(x*20)); + g.setColor(255,0,0); + g.drawRect(10,30+(currentPositionTable*20),230,50+(20*currentPositionTable)); + } + }else if(currentStage == INFORMATION){ + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].sm); + scheduleHourUpdatedStart = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].sh); + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].em); + scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].eh); + g.setColor(255,255,255); + g.reset(); + g.setFont("8x12"); + g.drawString(foundSchedule[((foundNumber-2)+currentPositionTable)].cn,13,30); + } + g.flip(); +} + +var currentMinuteUpdatedFunction = "00"; +var currentHourUpdatedFunction = 11; +var scheduleMinuteUpdatedStart = 35; +var scheduleHourUpdatedStart = 10; +var scheduleMinuteUpdatedEnd = currentMinuteUpdatedFunction; +var scheduleHourUpdatedEnd = 11; + +setWatch(RedRectUp, D23, { repeat:true, edge:'rising', debounce : 50 }); +setWatch(RedRectDown, D24, { repeat:true, edge:'rising', debounce : 50 }); +setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); + +displayClock(); + +setInterval(displayClock, 5000); From 7107d510a5301f50ca5dc1bf9d4463ad99a33af8 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 4 Oct 2021 20:06:28 -0700 Subject: [PATCH 0129/1062] Update schoolCalendar.js --- apps/schoolCalendar/schoolCalendar.js | 54 ++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js index a6537aa66..36f9110a7 100644 --- a/apps/schoolCalendar/schoolCalendar.js +++ b/apps/schoolCalendar/schoolCalendar.js @@ -1,4 +1,5 @@ require("Font8x12").add(Graphics); +require("Font8x16").add(Graphics); Graphics.prototype.setFontAudiowide = function () { var widths = atob("BxYfDBkYGhkZFRkZCA=="); @@ -131,7 +132,55 @@ function updateHoursToCurrentTime(currentHourFunction) { return currentHourUpdatedFunction; } - +function updateDay(ffunction,day){ + if(ffunction == 1){ + switch (day) { + case 0: + return "Sunday"; + case 1: + day = "Monday"; + break; + case 2: + day = "Tuesday"; + break; + case 3: + day = "Wednesday"; + break; + case 4: + day = "Thursday"; + break; + case 5: + day = "Friday"; + break; + case 6: + day = "Saturday"; + return day; +} + }else if(ffunction == 2){ + switch (day) { + case 0: + return "Sun"; + case 1: + day = "Mon"; + break; + case 2: + day = "Tue"; + break; + case 3: + day = "Wed"; + break; + case 4: + day = "Thu"; + break; + case 5: + day = "Fri"; + break; + case 6: + day = "Sat"; + return day; + } + } +} function RedRectDown() { if(currentPositionTable > 0){ @@ -197,8 +246,9 @@ function displayClock() { scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].eh); g.setColor(255,255,255); g.reset(); - g.setFont("8x12"); + g.setFont("8x16"); g.drawString(foundSchedule[((foundNumber-2)+currentPositionTable)].cn,13,30); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,50); } g.flip(); } From c9f42fda27c2eea8682c3728849bd6e628969b92 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 5 Oct 2021 14:58:21 +0100 Subject: [PATCH 0130/1062] emulator --- apps.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps.json b/apps.json index 83690cf3e..a7577945e 100644 --- a/apps.json +++ b/apps.json @@ -3524,6 +3524,7 @@ "description": "A simple clock using the bold Anton font.", "tags":"clock,b2", "type":"clock", + "allow_emulator":true, "storage": [ {"name":"antonclk.app.js","url":"app.js"}, {"name":"antonclk.img","url":"app-icon.js","evaluate":true} @@ -3536,6 +3537,7 @@ "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires a bugfix for #2049 on Bangle.js 1**", "tags":"clock,b2", "type":"clock", + "allow_emulator":true, "storage": [ {"name":"waveclk.app.js","url":"app.js"}, {"name":"waveclk.img","url":"app-icon.js","evaluate":true} From f37b744ed56c68a3a935b71a03ccdfb06fbab0b8 Mon Sep 17 00:00:00 2001 From: Ben Whittaker Date: Mon, 4 Oct 2021 18:45:57 -0400 Subject: [PATCH 0131/1062] Layout: defer initial update until first render --- modules/Layout.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/Layout.js b/modules/Layout.js index 69f257b3b..087aff249 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -140,7 +140,7 @@ function Layout(layout, buttons, options) { if (l.c) l.c.forEach(idRecurser); } idRecurser(layout); - this.update(); + this.updateNeeded = true; } Layout.prototype.remove = function (l) { @@ -196,6 +196,7 @@ function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) { Layout.prototype.render = function (l) { if (!l) l = this._l; + if (this.updateNeeded) this.update(); function render(l) {"ram" g.reset(); @@ -309,6 +310,7 @@ Layout.prototype.debug = function(l,c) { if (l.c) l.c.forEach(n => this.debug(n,c)); }; Layout.prototype.update = function() { + delete this.updateNeeded; var l = this._l; var w = g.getWidth(); var y = this.yOffset; From e088b3be339733129d3eee9c60fa355700e4d1cb Mon Sep 17 00:00:00 2001 From: Ben Whittaker Date: Mon, 27 Sep 2021 13:53:23 -0400 Subject: [PATCH 0132/1062] weather: Use Layout library --- apps/weather/app.js | 169 +++++++++++++++++++++++--------------------- 1 file changed, 90 insertions(+), 79 deletions(-) diff --git a/apps/weather/app.js b/apps/weather/app.js index 9d64583e9..634a669b4 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -1,96 +1,107 @@ -(() => { - const weather = require('weather'); - let current = weather.get(); +const Layout = require('Layout'); +const locale = require('locale'); +const weather = require('weather'); +let current = weather.get(); - function formatDuration(millis) { - let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); - if (millis < 60000) return "< 1 minute"; - if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute"); - if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour"); - return pluralize(Math.floor(millis/86400000), "day"); - } +Bangle.loadWidgets(); - function draw() { - g.reset(); - g.clearRect(0, 24, 239, 239); +var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [ + {filly: 1}, + {type: "h", filly: 0, c: [ + {type: "custom", width: g.getWidth()/2, height: g.getWidth()/2, valign: -1, txt: "unknown", id: "icon", + render: l => weather.drawIcon(l.txt, l.x+l.w/2, l.y+l.h/2, l.w/2-5)}, + {type: "v", c: [ + {type: "h", pad: 2, c: [ + {type: "txt", font: "18%", id: "temp", label: "000"}, + {type: "txt", font: "12%", valign: -1, id: "tempUnit", label: "°C"}, + ]}, + {filly: 1}, + {type: "txt", font: "6x8", pad: 2, halign: 1, label: "Humidity"}, + {type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"}, + {filly: 1}, + {type: "txt", font: "6x8", pad: 2, halign: -1, label: "Wind"}, + {type: "h", halign: -1, c: [ + {type: "txt", font: "9%", pad: 2, id: "wind", label: "00"}, + {type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"}, + ]}, + ]}, + ]}, + {filly: 1}, + {type: "txt", font: "9%", id: "cond", label: "Weather line 1"}, + {filly: 1}, + {type: "h", c: [ + {type: "txt", font: "6x8", pad: 2, id: "loc", label: "Toronto"}, + {fillx: 1}, + {type: "txt", font: "6x8", pad: 2, id: "updateTime", label: "15 minutes ago"}, + ]}, + {filly: 1}, +]}, null, {lazy: true}); - weather.drawIcon(current.txt, 65, 90, 55); - const locale = require("locale"); +function formatDuration(millis) { + let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); + if (millis < 60000) return "< 1 minute"; + if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute"); + if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour"); + return pluralize(Math.floor(millis/86400000), "day"); +} - g.reset(); +function draw() { + layout.icon.txt = current.txt; + const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/); + layout.temp.label = temp[1]; + layout.tempUnit.label = temp[2]; + layout.hum.label = current.hum+"%"; + const wind = locale.speed(current.wind).match(/^(\D*\d*)(.*)$/); + layout.wind.label = wind[1]; + layout.windUnit.label = wind[2] + " " + current.wrose.toUpperCase(); + // TODO: split long weather conditions across multiple lines + layout.cond.label = current.txt.charAt(0).toUpperCase()+current.txt.slice(1); + layout.loc.label = current.loc; + layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; + layout.update(); + layout.render(); +} - const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/); - let width = g.setFont("Vector", 40).stringWidth(temp[1]); - width += g.setFont("Vector", 20).stringWidth(temp[2]); - g.setFont("Vector", 40).setFontAlign(-1, -1, 0); - g.drawString(temp[1], 180-width/2, 70); - g.setFont("Vector", 20).setFontAlign(1, -1, 0); - g.drawString(temp[2], 180+width/2, 70); +function drawUpdateTime() { + if (!current || !current.time) return; + layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; + layout.update(); + layout.render(); +} - g.setFont("6x8", 1); - g.setFontAlign(-1, 0, 0); - g.drawString("Humidity", 135, 130); - g.setFontAlign(1, 0, 0); - g.drawString(current.hum+"%", 225, 130); - if ('wind' in current) { - g.setFontAlign(-1, 0, 0); - g.drawString("Wind", 135, 142); - g.setFontAlign(1, 0, 0); - g.drawString(locale.speed(current.wind)+' '+current.wrose.toUpperCase(), 225, 142); - } - - g.setFont("6x8", 2).setFontAlign(0, 0, 0); - g.drawString(current.loc, 120, 170); - - g.setFont("6x8", 1).setFontAlign(0, 0, 0); - g.drawString(current.txt.charAt(0).toUpperCase()+current.txt.slice(1), 120, 190); - - drawUpdateTime(); - - g.flip(); - } - - function drawUpdateTime() { - if (!current || !current.time) return; - let text = `Last update received ${formatDuration(Date.now() - current.time)} ago`; - g.reset(); - g.clearRect(0, 202, 239, 210); - g.setFont("6x8", 1).setFontAlign(0, 0, 0); - g.drawString(text, 120, 206); - } - - function update() { - current = weather.get(); - NRF.removeListener("connect", update); - if (current) { - draw(); - } else if (NRF.getSecurityStatus().connected) { +function update() { + current = weather.get(); + NRF.removeListener("connect", update); + if (current) { + draw(); + } else { + delete layout.rects; + if (NRF.getSecurityStatus().connected) { E.showMessage("Weather unknown\n\nIs Gadgetbridge\nweather reporting\nset up on your\nphone?"); } else { E.showMessage("Weather unknown\n\nGadgetbridge\nnot connected"); NRF.on("connect", update); } } +} - let interval = setInterval(drawUpdateTime, 60000); - Bangle.on('lcdPower', (on) => { - if (interval) { - clearInterval(interval); - interval = undefined; - } - if (on) { - drawUpdateTime(); - interval = setInterval(drawUpdateTime, 60000); - } - }); +let interval = setInterval(drawUpdateTime, 60000); +Bangle.on('lcdPower', (on) => { + if (interval) { + clearInterval(interval); + interval = undefined; + } + if (on) { + drawUpdateTime(); + interval = setInterval(drawUpdateTime, 60000); + } +}); - weather.on("update", update); +weather.on("update", update); - update(); +update(); - // Show launcher when middle button pressed - Bangle.setUI("clock"); +// Show launcher when middle button pressed +Bangle.setUI("clock"); - Bangle.loadWidgets(); - Bangle.drawWidgets(); -})() +Bangle.drawWidgets(); From 3cfd94314e8ad563ff247814f87085de2b1672a4 Mon Sep 17 00:00:00 2001 From: Ben Whittaker Date: Mon, 4 Oct 2021 16:19:11 -0400 Subject: [PATCH 0133/1062] weather: Use wrapping text for condition --- apps/weather/app.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/weather/app.js b/apps/weather/app.js index 634a669b4..0f0512ba7 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -27,7 +27,7 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [ ]}, ]}, {filly: 1}, - {type: "txt", font: "9%", id: "cond", label: "Weather line 1"}, + {type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: "Weather condition"}, {filly: 1}, {type: "h", c: [ {type: "txt", font: "6x8", pad: 2, id: "loc", label: "Toronto"}, @@ -54,7 +54,6 @@ function draw() { const wind = locale.speed(current.wind).match(/^(\D*\d*)(.*)$/); layout.wind.label = wind[1]; layout.windUnit.label = wind[2] + " " + current.wrose.toUpperCase(); - // TODO: split long weather conditions across multiple lines layout.cond.label = current.txt.charAt(0).toUpperCase()+current.txt.slice(1); layout.loc.label = current.loc; layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; From cdf95566bd6d6b544dc26f61e5e9708067692bcc Mon Sep 17 00:00:00 2001 From: Ben Whittaker Date: Tue, 5 Oct 2021 20:57:10 -0400 Subject: [PATCH 0134/1062] Layout: Add `forgetLazyState` method --- apps/weather/app.js | 2 +- modules/Layout.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/weather/app.js b/apps/weather/app.js index 0f0512ba7..aab71f703 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -74,7 +74,7 @@ function update() { if (current) { draw(); } else { - delete layout.rects; + layout.forgetLazyState(); if (NRF.getSecurityStatus().connected) { E.showMessage("Weather unknown\n\nIs Gadgetbridge\nweather reporting\nset up on your\nphone?"); } else { diff --git a/modules/Layout.js b/modules/Layout.js index 087aff249..09e2a3d8c 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -73,6 +73,7 @@ Other functions: * `layout.update()` - update positions of everything if contents have changed * `layout.debug(obj)` - draw outlines for objects on screen * `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render) +* `layout.forgetLazyState()` - if lazy rendering is enabled, makes the next call to `render()` perform a full re-render */ @@ -258,6 +259,10 @@ Layout.prototype.render = function (l) { } }; +Layout.prototype.forgetLazyState = function () { + this.rects = {}; +} + Layout.prototype.layout = function (l) { // l = current layout element // exw,exh = extra width/height available From a84588aca1c07ce2763585485bf2e62906be957a Mon Sep 17 00:00:00 2001 From: Ben Whittaker Date: Tue, 5 Oct 2021 21:04:21 -0400 Subject: [PATCH 0135/1062] weather: tweaks + changelog --- apps.json | 2 +- apps/weather/ChangeLog | 3 ++- apps/weather/app.js | 10 +++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps.json b/apps.json index a7577945e..f3b8badd5 100644 --- a/apps.json +++ b/apps.json @@ -585,7 +585,7 @@ { "id": "weather", "name": "Weather", "icon": "icon.png", - "version":"0.09", + "version":"0.10", "description": "Show Gadgetbridge weather report", "readme": "readme.md", "tags": "widget,outdoors", diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog index 09e159045..8f997a83e 100644 --- a/apps/weather/ChangeLog +++ b/apps/weather/ChangeLog @@ -5,4 +5,5 @@ 0.06: Use setUI for launcher. 0.07: Add theme support and unknown icon. 0.08: Refactor and reduce widget ram usage. -0.09: Fix crash when weather.json is absent. \ No newline at end of file +0.09: Fix crash when weather.json is absent. +0.10: Use new Layout library \ No newline at end of file diff --git a/apps/weather/app.js b/apps/weather/app.js index aab71f703..6dba14143 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -10,7 +10,7 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [ {type: "h", filly: 0, c: [ {type: "custom", width: g.getWidth()/2, height: g.getWidth()/2, valign: -1, txt: "unknown", id: "icon", render: l => weather.drawIcon(l.txt, l.x+l.w/2, l.y+l.h/2, l.w/2-5)}, - {type: "v", c: [ + {type: "v", fillx: 1, c: [ {type: "h", pad: 2, c: [ {type: "txt", font: "18%", id: "temp", label: "000"}, {type: "txt", font: "12%", valign: -1, id: "tempUnit", label: "°C"}, @@ -30,9 +30,9 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [ {type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: "Weather condition"}, {filly: 1}, {type: "h", c: [ - {type: "txt", font: "6x8", pad: 2, id: "loc", label: "Toronto"}, + {type: "txt", font: "6x8", pad: 4, id: "loc", label: "Toronto"}, {fillx: 1}, - {type: "txt", font: "6x8", pad: 2, id: "updateTime", label: "15 minutes ago"}, + {type: "txt", font: "6x8", pad: 4, id: "updateTime", label: "15 minutes ago"}, ]}, {filly: 1}, ]}, null, {lazy: true}); @@ -76,9 +76,9 @@ function update() { } else { layout.forgetLazyState(); if (NRF.getSecurityStatus().connected) { - E.showMessage("Weather unknown\n\nIs Gadgetbridge\nweather reporting\nset up on your\nphone?"); + E.showMessage("Weather\nunknown\n\nIs Gadgetbridge\nweather\nreporting set\nup on your\nphone?"); } else { - E.showMessage("Weather unknown\n\nGadgetbridge\nnot connected"); + E.showMessage("Weather\nunknown\n\nGadgetbridge\nnot connected"); NRF.on("connect", update); } } From f78c9b7c22964d05f0c8a5dcc58ef7ae66313e7e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 6 Oct 2021 09:45:12 +0100 Subject: [PATCH 0136/1062] settings 0.29: Move '< Back' to the top of menus --- apps.json | 2 +- apps/setting/ChangeLog | 1 + apps/setting/settings.js | 14 +++++++------- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/apps.json b/apps.json index a7577945e..cb77a7290 100644 --- a/apps.json +++ b/apps.json @@ -185,7 +185,7 @@ { "id": "setting", "name": "Settings", "icon": "settings.png", - "version":"0.28", + "version":"0.29", "description": "A menu for setting up Bangle.js", "tags": "tool,system,b2", "readme": "README.md", diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 5a96451f2..0ecac7e42 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -31,3 +31,4 @@ 0.26: Use Bangle.softOff if available as this keeps the time 0.27: Add Theme menu 0.28: Update Quiet Mode widget (if present) +0.29: Move '< Back' to the top of menus diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 1b1cc5478..d3c7d42c7 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -75,6 +75,7 @@ function showMainMenu() { var beepN = ["Off", "Piezo", "Vibrate"]; const mainmenu = { '': { 'title': 'Settings' }, + '< Back': ()=>load(), 'Make Connectable': ()=>makeConnectable(), 'App/Widget Settings': ()=>showAppSettingsMenu(), 'BLE': ()=>showBLEMenu(), @@ -117,7 +118,6 @@ function showMainMenu() { 'Theme': ()=>showThemeMenu(), 'Reset Settings': ()=>showResetMenu(), 'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }, - '< Back': ()=>load() }; return E.showMenu(mainmenu); } @@ -126,6 +126,7 @@ function showBLEMenu() { var hidV = [false, "kbmedia", "kb", "joy"]; var hidN = ["Off", "Kbrd & Media", "Kbrd","Joystick"]; E.showMenu({ + '< Back': ()=>showMainMenu(), 'BLE': { value: settings.ble, format: boolFormat, @@ -158,8 +159,7 @@ function showBLEMenu() { 'Whitelist': { value: settings.whitelist?(settings.whitelist.length+" devs"):"off", onchange: () => setTimeout(showWhitelistMenu) // graphical_menu redraws after the call - }, - '< Back': ()=>showMainMenu() + } }); } @@ -178,6 +178,7 @@ function showThemeMenu() { m.draw(); } var m = E.showMenu({ + '< Back': ()=>showMainMenu(), 'Dark BW': ()=>{ upd({ fg:cl("#fff"), bg:cl("#000"), @@ -193,13 +194,13 @@ function showThemeMenu() { fgH:cl("#000"), bgH:cl("#0ff"), dark:false }); - }, - '< Back': ()=>showMainMenu() + } }); } function showPasskeyMenu() { var menu = { + "< Back" : ()=>showBLEMenu(), "Disable" : () => { settings.passkey = undefined; updateSettings(); @@ -220,12 +221,12 @@ function showPasskeyMenu() { } }; })(i); - menu['< Back']=()=>showBLEMenu(); E.showMenu(menu); } function showWhitelistMenu() { var menu = { + "< Back" : ()=>showBLEMenu(), "Disable" : () => { settings.whitelist = undefined; updateSettings(); @@ -257,7 +258,6 @@ function showWhitelistMenu() { showWhitelistMenu(); }); }; - menu['< Back']=()=>showBLEMenu(); E.showMenu(menu); } From ce358e4b55cc65f5d16c424ae550df0e5de2b4f0 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 6 Oct 2021 09:47:37 +0100 Subject: [PATCH 0137/1062] Add timer capability to alarms app --- apps.json | 6 ++-- apps/alarm/alarm.js | 2 +- apps/alarm/app.js | 80 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 78 insertions(+), 10 deletions(-) diff --git a/apps.json b/apps.json index cb77a7290..49b144412 100644 --- a/apps.json +++ b/apps.json @@ -199,11 +199,11 @@ "sortorder" : -2 }, { "id": "alarm", - "name": "Default Alarm", + "name": "Default Alarm & Timer", "shortName":"Alarms", "icon": "app.png", - "version":"0.12", - "description": "Set and respond to alarms", + "version":"0.13", + "description": "Set and respond to alarms and timers", "tags": "tool,alarm,widget,b2", "storage": [ {"name":"alarm.app.js","url":"app.js"}, diff --git a/apps/alarm/alarm.js b/apps/alarm/alarm.js index 26345e887..3598909fc 100644 --- a/apps/alarm/alarm.js +++ b/apps/alarm/alarm.js @@ -19,7 +19,7 @@ function showAlarm(alarm) { if (alarm.msg) msg += "\n"+alarm.msg; E.showPrompt(msg,{ - title:"ALARM!", + title:alarm.timer ? "TIMER!" : "ALARM!", buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins }).then(function(sleep) { buzzCount = 0; diff --git a/apps/alarm/app.js b/apps/alarm/app.js index b6019ca08..343eb5b21 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -9,6 +9,7 @@ var alarms = require("Storage").readJSON("alarm.json",1)||[]; last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day! rp : true, // repeat as : false, // auto snooze + timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes } ];*/ @@ -18,6 +19,12 @@ function formatTime(t) { return hrs+":"+("0"+mins).substr(-2); } +function formatMins(t) { + mins = (0|t)%60; + hrs = 0|(t/60); + return hrs+":"+("0"+mins).substr(-2); +} + function getCurrentHr() { var time = new Date(); return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); @@ -25,14 +32,20 @@ function getCurrentHr() { function showMainMenu() { const menu = { - '': { 'title': 'Alarms' }, - 'New Alarm': ()=>editAlarm(-1) + '': { 'title': 'Alarm/Timer' }, + 'New Alarm': ()=>editAlarm(-1), + 'New Timer': ()=>editTimer(-1) }; alarms.forEach((alarm,idx)=>{ - txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr); - if (alarm.rp) txt += " (repeat)"; + if (alarm.timer) { + txt = "TIMER "+(alarm.on?"on ":"off ")+formatMins(alarm.timer); + } else { + txt = "ALARM "+(alarm.on?"on ":"off ")+formatTime(alarm.hr); + if (alarm.rp) txt += " (repeat)"; + } menu[txt] = function() { - editAlarm(idx); + if (alarm.timer) editTimer(idx); + else editAlarm(idx); }; }); menu['< Back'] = ()=>{load();}; @@ -55,7 +68,7 @@ function editAlarm(alarmIndex) { as = a.as; } const menu = { - '': { 'title': 'Alarms' }, + '': { 'title': 'Alarm' }, 'Hours': { value: hrs, onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' @@ -109,4 +122,59 @@ function editAlarm(alarmIndex) { return E.showMenu(menu); } +function editTimer(alarmIndex) { + var newAlarm = alarmIndex<0; + var hrs = 0; + var mins = 5; + var en = true; + if (!newAlarm) { + var a = alarms[alarmIndex]; + mins = (0|a.timer)%60; + hrs = 0|(a.timer/60); + en = a.on; + } + const menu = { + '': { 'title': 'Timer' }, + 'Hours': { + value: hrs, + onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' + }, + 'Minutes': { + value: mins, + onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this' + }, + 'Enabled': { + value: en, + format: v=>v?"On":"Off", + onchange: v=>en=v + } + }; + function getTimer() { + var d = new Date(Date.now() + ((hrs*60)+mins)*60000); + var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600); + // Save alarm + return { + on : en, + timer : (hrs*60)+mins, + hr : hr, + rp : false, as: false + }; + } + menu["> Save"] = function() { + if (newAlarm) alarms.push(getTimer()); + else alarms[alarmIndex] = getTimer(); + require("Storage").write("alarm.json",JSON.stringify(alarms)); + showMainMenu(); + }; + if (!newAlarm) { + menu["> Delete"] = function() { + alarms.splice(alarmIndex,1); + require("Storage").write("alarm.json",JSON.stringify(alarms)); + showMainMenu(); + }; + } + menu['< Back'] = showMainMenu; + return E.showMenu(menu); +} + showMainMenu(); From e53099aea6c1d159ad1717693e13f0b82c4a8c1d Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 6 Oct 2021 10:05:15 +0100 Subject: [PATCH 0138/1062] Widgets now shown on Alarm screen Alarm widget state now updates when setting/resetting an alarm --- apps/alarm/ChangeLog | 2 ++ apps/alarm/alarm.js | 2 ++ apps/alarm/app.js | 1 + apps/alarm/widget.js | 18 +++++++----------- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index 4084a7d3f..a151fd07e 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -10,3 +10,5 @@ 0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506) 0.11: Respect Quiet Mode 0.12: Fix widget for bangle 2, now uses theme + Widgets now shown on Alarm screen + Alarm widget state now updates when setting/resetting an alarm diff --git a/apps/alarm/alarm.js b/apps/alarm/alarm.js index 3598909fc..bb5722106 100644 --- a/apps/alarm/alarm.js +++ b/apps/alarm/alarm.js @@ -18,6 +18,8 @@ function showAlarm(alarm) { var buzzCount = 10; if (alarm.msg) msg += "\n"+alarm.msg; + Bangle.loadWidgets(); + Bangle.drawWidgets(); E.showPrompt(msg,{ title:alarm.timer ? "TIMER!" : "ALARM!", buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins diff --git a/apps/alarm/app.js b/apps/alarm/app.js index 343eb5b21..fdb7784f7 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -49,6 +49,7 @@ function showMainMenu() { }; }); menu['< Back'] = ()=>{load();}; + if (WIDGETS["alarm"]) WIDGETS["alarm"].reload(); return E.showMenu(menu); } diff --git a/apps/alarm/widget.js b/apps/alarm/widget.js index 7b9a88bb9..e8bb79fc7 100644 --- a/apps/alarm/widget.js +++ b/apps/alarm/widget.js @@ -1,11 +1,7 @@ -(() => { - var alarms = require('Storage').readJSON('alarm.json',1)||[]; - alarms = alarms.filter(alarm=>alarm.on); - if (!alarms.length) return; // no alarms, no widget! - delete alarms; - // add the widget - WIDGETS["alarm"]={area:"tl",width:24,draw:function() { - g.setColor(g.theme.fg); - g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); - }}; -})() +WIDGETS["alarm"]={area:"tl",width:0,draw:function() { + if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); + },reload:function() { + WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0; + } +}; +WIDGETS["alarm"].reload(); From 48d8031442ee452643135fb111a89e162f689dc7 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 6 Oct 2021 10:13:26 +0100 Subject: [PATCH 0139/1062] widclk 0.06: Use 7 segment font, update *on* the minute, use less memory --- apps.json | 2 +- apps/widclk/ChangeLog | 1 + apps/widclk/widget.js | 44 +++++++++++++++---------------------------- 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/apps.json b/apps.json index 49b144412..c182da13c 100644 --- a/apps.json +++ b/apps.json @@ -1187,7 +1187,7 @@ { "id": "widclk", "name": "Digital clock widget", "icon": "widget.png", - "version":"0.05", + "version":"0.06", "description": "A simple digital clock widget", "tags": "widget,clock", "type":"widget", diff --git a/apps/widclk/ChangeLog b/apps/widclk/ChangeLog index 655679515..c74857ab4 100644 --- a/apps/widclk/ChangeLog +++ b/apps/widclk/ChangeLog @@ -2,3 +2,4 @@ 0.03: Ensure redrawing works with variable size widget system 0.04: Fix regression stopping correct widget updates 0.05: Don't show clock widget if already showing clock app +0.06: Use 7 segment font, update *on* the minute, use less memory diff --git a/apps/widclk/widget.js b/apps/widclk/widget.js index cd4b29367..da3f60ce8 100644 --- a/apps/widclk/widget.js +++ b/apps/widclk/widget.js @@ -1,30 +1,16 @@ -(function() { - // don't show widget if we know we have a clock app running - if (Bangle.CLOCK) return; +/* Simple clock that appears in the widget bar if no other clock +is running. We update once per minute, but don't bother stopping +if the */ - let intervalRef = null; - var width = 5 * 6*2 - - function draw() { - g.reset().setFont("6x8", 2).setFontAlign(-1, 0); - var time = require("locale").time(new Date(),1); - g.drawString(time, this.x, this.y+11, true); // 5 * 6*2 = 60 - } - function clearTimers(){ - if(intervalRef) { - clearInterval(intervalRef); - intervalRef = null; - } - } - function startTimers(){ - intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000); - WIDGETS["wdclk"].draw(); - } - Bangle.on('lcdPower', (on) => { - clearTimers(); - if (on) startTimers(); - }); - - WIDGETS["wdclk"]={area:"tr",width:width,draw:draw}; - if (Bangle.isLCDOn) intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000); -})() +// don't show widget if we know we have a clock app running +if (!Bangle.CLOCK) WIDGETS["wdclk"]={area:"tl",width:52/* g.stringWidth("00:00") */,draw:function() { + g.reset().setFontCustom(atob("AAAAAAAAAAIAAAQCAQAAAd0BgMBdwAAAAAAAdwAB0RiMRcAAAERiMRdwAcAQCAQdwAcERiMRBwAd0RiMRBwAAEAgEAdwAd0RiMRdwAcERiMRdwAFAAd0QiEQdwAdwRCIRBwAd0BgMBAAABwRCIRdwAd0RiMRAAAd0QiEQAAAAAAAAAA="), 32, atob("BgAAAAAAAAAAAAAAAAYCAAYGBgYGBgYGBgYCAAAAAAAABgYGBgYG"), 512+9); + var time = require("locale").time(new Date(),1); + g.drawString(time, this.x, this.y+3, true); // 5 * 6*2 = 60 + // queue draw in one minute + if (drawTimeout) clearTimeout(drawTimeout); + this.drawTimeout = setTimeout(()=>{ + this.drawTimeout = undefined; + this.draw(); + }, 60000 - (Date.now() % 60000)); +}}; From 70702560c648e0463e55e4db32d31541ca50bd5d Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 6 Oct 2021 10:15:16 +0100 Subject: [PATCH 0140/1062] antonclk/waveclk 0.02: Load widgets after setUI so widclk knows when to hide --- apps.json | 4 ++-- apps/antonclk/ChangeLog | 1 + apps/antonclk/app.js | 9 ++++----- apps/waveclk/ChangeLog | 1 + apps/waveclk/app.js | 6 +++--- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/apps.json b/apps.json index c182da13c..35dfb9653 100644 --- a/apps.json +++ b/apps.json @@ -3520,7 +3520,7 @@ { "id": "antonclk", "name": "Anton Clock", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "A simple clock using the bold Anton font.", "tags":"clock,b2", "type":"clock", @@ -3533,7 +3533,7 @@ { "id": "waveclk", "name": "Wave Clock", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires a bugfix for #2049 on Bangle.js 1**", "tags":"clock,b2", "type":"clock", diff --git a/apps/antonclk/ChangeLog b/apps/antonclk/ChangeLog index 5560f00bc..8c2a33143 100644 --- a/apps/antonclk/ChangeLog +++ b/apps/antonclk/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Load widgets after setUI so widclk knows when to hide diff --git a/apps/antonclk/app.js b/apps/antonclk/app.js index 1b92d4a8b..f6fcf1708 100644 --- a/apps/antonclk/app.js +++ b/apps/antonclk/app.js @@ -19,7 +19,7 @@ function queueDraw() { function draw() { var x = g.getWidth()/2; var y = g.getHeight()/2; - g.reset(); + g.reset(); var date = new Date(); var timeStr = require("locale").time(date,1); var dateStr = require("locale").date(date).toUpperCase(); @@ -33,7 +33,7 @@ function draw() { g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background g.drawString(dateStr,x,y); // queue draw in one minute - queueDraw(); + queueDraw(); } // Clear the screen once, at startup @@ -49,9 +49,8 @@ Bangle.on('lcdPower',on=>{ drawTimeout = undefined; } }); +// Show launcher when middle button pressed +Bangle.setUI("clock"); // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); -// Show launcher when middle button pressed -Bangle.setUI("clock"); - diff --git a/apps/waveclk/ChangeLog b/apps/waveclk/ChangeLog index 5560f00bc..8c2a33143 100644 --- a/apps/waveclk/ChangeLog +++ b/apps/waveclk/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Load widgets after setUI so widclk knows when to hide diff --git a/apps/waveclk/app.js b/apps/waveclk/app.js index 7e1870aa7..f1c67ce2f 100644 --- a/apps/waveclk/app.js +++ b/apps/waveclk/app.js @@ -28,7 +28,7 @@ function queueDraw() { function draw() { var x = g.getWidth()/2; var y = 24+20; - + g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT); if (g.getWidth() == IMAGEWIDTH) g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT); @@ -65,8 +65,8 @@ Bangle.on('lcdPower',on=>{ drawTimeout = undefined; } }); +// Show launcher when middle button pressed +Bangle.setUI("clock"); // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); -// Show launcher when middle button pressed -Bangle.setUI("clock"); \ No newline at end of file From 192e8470d1c0f59000d4e2d02c9902dd61d41ff8 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 6 Oct 2021 16:08:11 +0100 Subject: [PATCH 0141/1062] demoapp 0.02: Minor adjustment to fix out of memory errors --- apps.json | 4 ++-- apps/demoapp/ChangeLog | 1 + apps/demoapp/app.js | 5 ++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index 35dfb9653..d45e8c947 100644 --- a/apps.json +++ b/apps.json @@ -1236,9 +1236,9 @@ { "id": "demoapp", "name": "Demo Loop", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "Simple demo app - displays Bangle.js, JS logo, graphics, and Bangle.js information", - "tags": "", + "tags": "bno2", "type":"app", "allow_emulator":true, "storage": [ diff --git a/apps/demoapp/ChangeLog b/apps/demoapp/ChangeLog index 5560f00bc..53e9cf268 100644 --- a/apps/demoapp/ChangeLog +++ b/apps/demoapp/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Minor adjustment to fix out of memory errors diff --git a/apps/demoapp/app.js b/apps/demoapp/app.js index 13c043587..f1cf5af07 100644 --- a/apps/demoapp/app.js +++ b/apps/demoapp/app.js @@ -60,8 +60,8 @@ var scenes = [ }; }, function() { + Bangle.setLCDMode("120x120"); var img = require("heatshrink").decompress(atob("oNBxH+5wA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHGpAAoQKv4ADCBQAeqsrAAejBw9/B4oABqt/IGepHw5CEQspALH5hBC5pAvv4/MAALFkIBWpPI6IHqpAu0Z3GfYOpRYdPQEhALYIp2FBYNVI4JAvvL4LH0yBYAFJAQQQ5Ay1JAFftBAQBYxCDv+qIGiCHIQiGnIBfOv5BJIQRAyIJkrvKEkIBrFBB4qEGIGRCNYsZAQIQV/IZDEiICRCDQVJAUIQVPC4lVIF6yJQYpAZ5t/FYvNIBepqtVIJGjIDoqBDY2pdYo3DfAhBIQLmpvIcDvIrC5oJEIAhTCGQmj5qgEC4t5e7YrBqt5BI6UFBg15v4XHbQwAQb4oAKv7NKABdVRoYATUAwnICqjZFIMdVE4+jXI4XGYCxBFFZN/M5OpCxUrvJ/ZFYmjvNVAAY+KCwpDBC6YAV5vNC9oA/AH4A/AHYA==")); - g.clear(); y = 0; var step = 4; @@ -70,8 +70,7 @@ var scenes = [ g.clear(); g.drawImage(img,60,60,{rotate:Math.sin(y*0.03)*0.5}); g.flip(); - }, 20); - Bangle.setLCDMode("120x120"); + }, 20); return function() { if (i) clearInterval(i); }; From 0be85f8b24ac101eb462a0e888321713a1834fa7 Mon Sep 17 00:00:00 2001 From: Sven Klomp Date: Wed, 6 Oct 2021 20:01:27 +0200 Subject: [PATCH 0142/1062] Grocery: Refactor to store grocery list in separate file --- apps.json | 5 ++-- apps/grocery/ChangeLog | 1 + apps/grocery/app.js | 29 +++++++++++++++++++++++ apps/grocery/grocery.html | 49 +-------------------------------------- 4 files changed, 33 insertions(+), 51 deletions(-) create mode 100644 apps/grocery/app.js diff --git a/apps.json b/apps.json index a7577945e..f85f54eea 100644 --- a/apps.json +++ b/apps.json @@ -1328,14 +1328,13 @@ "id": "grocery", "name": "Grocery", "icon": "grocery.png", - "version":"0.01", + "version":"0.02", "description": "Simple grocery (shopping) list - Display a list of product and track if you already put them in your cart.", "tags": "tool,outdoors,shopping,list", "type": "app", "custom":"grocery.html", "storage": [ - {"name":"grocery"}, - {"name":"grocery.app.js"}, + {"name":"grocery.app.js", "url":"app.js"}, {"name":"grocery.img","url":"grocery-icon.js","evaluate":true} ] }, diff --git a/apps/grocery/ChangeLog b/apps/grocery/ChangeLog index 5560f00bc..906046782 100644 --- a/apps/grocery/ChangeLog +++ b/apps/grocery/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Refactor code to store grocery list in separate file diff --git a/apps/grocery/app.js b/apps/grocery/app.js new file mode 100644 index 000000000..481efc3d9 --- /dev/null +++ b/apps/grocery/app.js @@ -0,0 +1,29 @@ +var filename = 'grocery_list.json'; +var settings = require("Storage").readJSON(filename,1)|| { products: [] }; + +function updateSettings() { + require("Storage").writeJSON(filename, settings); + Bangle.buzz(); +} + +function twoChat(n){ + if(n<10) return '0'+n; + return ''+n; +} + +const mainMenu = settings.products.reduce(function(m, p, i){ + const name = twoChat(p.quantity)+' '+p.name; + m[name] = { + value: p.ok, + format: v => v?'[x]':'[ ]', + onchange: v => { + settings.products[i].ok = v; + updateSettings(); + } + }; + return m; +}, { + '': { 'title': 'Grocery list' } +}); +mainMenu['< Back'] = ()=>{load();}; +E.showMenu(mainMenu); diff --git a/apps/grocery/grocery.html b/apps/grocery/grocery.html index 14c406d75..e717dee2e 100644 --- a/apps/grocery/grocery.html +++ b/apps/grocery/grocery.html @@ -105,56 +105,9 @@ } document.getElementById("upload").addEventListener("click", function() { - - - var app = ` -var newTime = ${Date.now()} -var products = ${JSON.stringify(products)} -var newTime = newTime; -var filename = 'grocery'; -var settings = require("Storage").readJSON(filename,1)|| null; -function getSettings(){ - return { - products : products, - date: newTime - }; -} -if(!settings || !settings.date || settings.date < newTime){ - settings = getSettings(); - Bangle.buzz(500); -} -function updateSettings() { - require("Storage").writeJSON(filename, settings); - Bangle.buzz(); -} -function twoChat(n){ - if(n<10) return '0'+n; - return ''+n; -} -const mainMenu = settings.products.reduce(function(m, p, i){ - const name = twoChat(p.quantity)+' '+p.name; - m[name] = { - value: p.ok, - format: v => v?'[x]':'[ ]', - onchange: v => { - settings.products[i].ok = v; - updateSettings(); - } - }; - return m; -}, { - '': { 'title': 'Grocery list' } -}); -mainMenu['< Back'] = ()=>{load();}; -E.showMenu(mainMenu); -`; - - var icon = `require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AQ0QACF1nGAAIxpFoYwqFwwwnRggwGB4eFAggACLzwHCMAeF1WGAgOGw2x2IGCLzYGEF4YpBwotCFwJfWFwo1GSAYtBAIIABRq4vFMhAwBzoAFdzIuKAAOc4IAGGC4qEMZOiF44wXFxovleBYvIGCwmB0WjE4V/AgfG1IvCzujFQOjwoECF6WFwovBDYOFEwN/AgIwCAgOFBwYrBBAQEBzodCF6AAHww1CBpIODAAYvRDAWG2IEBAYYJFBxICCF6Ox1WxAAQfBAYQlCAAIOJAQIvUADQvn1WGR4RfbP4gAFBwgFCF7a5EdwQADF46/cL9wAQF94AGF85bB1TvmF47vdJ4bvFF8qPRFgLv/L7jPCaQq/fYYrvgJgoAGd/7v/F/4v/F5oAdF54weFyAA/AH4A3A="))`; sendCustomizedApp({ storage:[ - {name:"grocery.app.js", url:"app.js", content:app}, - {name:"grocery.img", content:icon, evaluate:true}, - {name:"grocery"} + { name:"grocery_list.json", content: JSON.stringify({products: products}) } ] }); }); From 05145da819194ed00632b7fa55c6f192bbb65966 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 6 Oct 2021 20:14:22 +0100 Subject: [PATCH 0143/1062] tweaks to last commit --- apps/setting/settings.js | 139 ++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 540ecef2a..255a413cb 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -163,26 +163,25 @@ function showBLEMenu() { }); } -let m; -function updT(th) { - g.theme = th; - settings.theme = th; - updateSettings(); - delete g.reset; - g._reset = g.reset; - g.reset = function(n) { return g._reset().setColor(th.fg).setBgColor(th.bg); }; - g.clear = function(n) { if (n) g.reset(); return g.clearRect(0,0,g.getWidth(),g.getHeight()); }; - g.clear(1); - Bangle.drawWidgets(); - m.draw(); -} function showThemeMenu() { function cl(x) { return g.setColor(x).getColor(); } - m = E.showMenu({ + function upd(th) { + g.theme = th; + settings.theme = th; + updateSettings(); + delete g.reset; + g._reset = g.reset; + g.reset = function(n) { return g._reset().setColor(th.fg).setBgColor(th.bg); }; + g.clear = function(n) { if (n) g.reset(); return g.clearRect(0,0,g.getWidth(),g.getHeight()); }; + g.clear(1); + Bangle.drawWidgets(); + m.draw(); + } + var m = E.showMenu({ '':{title:'Theme'}, - '< Back': ()=>showMainMenu(), + '< Back': ()=>showMainMenu(), 'Dark BW': ()=>{ - updT({ + upd({ fg:cl("#fff"), bg:cl("#000"), fg2:cl("#0ff"), bg2:cl("#000"), fgH:cl("#fff"), bgH:cl("#00f"), @@ -190,7 +189,7 @@ function showThemeMenu() { }); }, 'Light BW': ()=>{ - updT({ + upd({ fg:cl("#000"), bg:cl("#fff"), fg2:cl("#00f"), bg2:cl("#0ff"), fgH:cl("#000"), bgH:cl("#0ff"), @@ -199,58 +198,62 @@ function showThemeMenu() { }, 'Customize': ()=>showCustomThemeMenu(), }); -} -function showCustomThemeMenu() { - function cv(x) { return g.setColor(x).getColor(); } - function setT(t, v) { - let th = g.theme; - th[t] = v; - if (t==="bg") { - th['dark'] = (v===cv("#000")); + + function showCustomThemeMenu() { + function cv(x) { return g.setColor(x).getColor(); } + function setT(t, v) { + let th = g.theme; + th[t] = v; + if (t==="bg") { + th['dark'] = (v===cv("#000")); + } + updT(th); } - updT(th); + const rgb = { + black: "#000", white: "#fff", + red: "#f00", green: "#0f0", blue: "#00f", + cyan: "#0ff", magenta: "#f0f", yellow: "#ff0", + }; + let colors = [], names = []; + for(const c in rgb) { + names.push(c); + colors.push(cv(rgb[c])); + } + function cn(v) { + const i = colors.indexOf(v); + return i!== -1 ? names[i] : v; // another color: just show value + } + let menu = { + '':{title:'Customize Theme'}, + "< Back"] : () => showThemeMenu(); + }; + const labels = { + fg: 'Foreground', bg: 'Background', + fg2: 'Foreground 2', bg2: 'Background 2', + fgH: 'Highlight FG', bgH: 'Highlight BG', + }; + ["fg", "bg", "fg2", "bg2", "fgH", "bgH"].forEach(t => { + menu[labels[t]] = { + value: colors.indexOf(g.theme[t]), + format: () => cn(g.theme[t]), + onchange: function(v) { + // wrap around + if (v>=colors.length) {v = 0;} + if (v<0) {v = colors.length-1;} + this.value = v; + const c = colors[v]; + // if we select the same fg and bg: set the other to the old color + // e.g. bg=black;fg=white, user selects fg=black -> bg changes to white automatically + // so users don't end up with a black-on-black menu + if (t === 'fg' && g.theme.bg === c) setT('bg', g.theme.fg); + if (t === 'bg' && g.theme.fg === c) setT('fg', g.theme.bg); + setT(t, c); + }, + }; + }); + menu["< Back"] = () => showThemeMenu(); + m = E.showMenu(menu); } - const rgb = { - black: "#000", white: "#fff", - red: "#f00", green: "#0f0", blue: "#00f", - cyan: "#0ff", magenta: "#f0f", yellow: "#ff0", - }; - let colors = [], names = []; - for(const c in rgb) { - names.push(c); - colors.push(cv(rgb[c])); - } - function cn(v) { - const i = colors.indexOf(v); - return i!== -1 ? names[i] : v; // another color: just show value - } - let menu = {'':{title:'Customize Theme'}}; - const labels = { - fg: 'Foreground', bg: 'Background', - fg2: 'Foreground 2', bg2: 'Background 2', - fgH: 'Highlight FG', bgH: 'Highlight BG', - }; - ["fg", "bg", "fg2", "bg2", "fgH", "bgH"].forEach(t => { - menu[labels[t]] = { - value: colors.indexOf(g.theme[t]), - format: () => cn(g.theme[t]), - onchange: function(v) { - // wrap around - if (v>=colors.length) {v = 0;} - if (v<0) {v = colors.length-1;} - this.value = v; - const c = colors[v]; - // if we select the same fg and bg: set the other to the old color - // e.g. bg=black;fg=white, user selects fg=black -> bg changes to white automatically - // so users don't end up with a black-on-black menu - if (t === 'fg' && g.theme.bg === c) setT('bg', g.theme.fg); - if (t === 'bg' && g.theme.fg === c) setT('fg', g.theme.bg); - setT(t, c); - }, - }; - }); - menu["< Back"] = () => {delete m;showThemeMenu();}; - m = E.showMenu(menu); } function showPasskeyMenu() { @@ -291,7 +294,7 @@ function showWhitelistMenu() { if (settings.whitelist) settings.whitelist.forEach(function(d){ menu[d.substr(0,17)] = function() { E.showPrompt('Remove\n'+d).then((v) => { - if (v) + if (v) { settings.whitelist.splice(settings.whitelist.indexOf(d),1); updateSettings(); } From b5337717e74aa9ef26529f2b3b2b18fb011906c4 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 6 Oct 2021 20:14:33 +0100 Subject: [PATCH 0144/1062] fix widclk regression --- apps/widclk/widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/widclk/widget.js b/apps/widclk/widget.js index da3f60ce8..9e035ca9a 100644 --- a/apps/widclk/widget.js +++ b/apps/widclk/widget.js @@ -8,7 +8,7 @@ if (!Bangle.CLOCK) WIDGETS["wdclk"]={area:"tl",width:52/* g.stringWidth("00:00") var time = require("locale").time(new Date(),1); g.drawString(time, this.x, this.y+3, true); // 5 * 6*2 = 60 // queue draw in one minute - if (drawTimeout) clearTimeout(drawTimeout); + if (this.drawTimeout) clearTimeout(this.drawTimeout); this.drawTimeout = setTimeout(()=>{ this.drawTimeout = undefined; this.draw(); From 45b5332c3cc431538e5b451135f061ed7db6481b Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 6 Oct 2021 20:17:34 +0100 Subject: [PATCH 0145/1062] oops, and update versions after merge --- apps.json | 2 +- apps/setting/settings.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps.json b/apps.json index d554b0354..532a0c898 100644 --- a/apps.json +++ b/apps.json @@ -185,7 +185,7 @@ { "id": "setting", "name": "Settings", "icon": "settings.png", - "version":"0.29", + "version":"0.30", "description": "A menu for setting up Bangle.js", "tags": "tool,system,b2", "readme": "README.md", diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 255a413cb..a0e535df7 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -207,7 +207,7 @@ function showThemeMenu() { if (t==="bg") { th['dark'] = (v===cv("#000")); } - updT(th); + upd(th); } const rgb = { black: "#000", white: "#fff", @@ -224,8 +224,8 @@ function showThemeMenu() { return i!== -1 ? names[i] : v; // another color: just show value } let menu = { - '':{title:'Customize Theme'}, - "< Back"] : () => showThemeMenu(); + '':{title:'Custom Theme'}, + "< Back": () => showThemeMenu() }; const labels = { fg: 'Foreground', bg: 'Background', From e6d4573f701cdaa6bb14c84868bc785740f02744 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 6 Oct 2021 23:47:09 +0100 Subject: [PATCH 0146/1062] accellog, dont show decimal places for seconds, set Exit as first menu option --- apps.json | 2 +- apps/accellog/ChangeLog | 1 + apps/accellog/app.js | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index 532a0c898..876b243ae 100644 --- a/apps.json +++ b/apps.json @@ -2413,7 +2413,7 @@ "name": "Acceleration Logger", "shortName":"Accel Log", "icon": "app.png", - "version":"0.02", + "version":"0.03", "interface": "interface.html", "description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC", "tags": "outdoor,b2", diff --git a/apps/accellog/ChangeLog b/apps/accellog/ChangeLog index 084f26aff..c0097db80 100644 --- a/apps/accellog/ChangeLog +++ b/apps/accellog/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Use the new multiplatform 'Layout' library +0.03: Exit as first menu option, dont show decimal places for seconds diff --git a/apps/accellog/app.js b/apps/accellog/app.js index 3ee88059b..fdb4d52be 100644 --- a/apps/accellog/app.js +++ b/apps/accellog/app.js @@ -8,6 +8,9 @@ function getFileName(n) { function showMenu() { var menu = { "" : { title : "Accel Logger" }, + "Exit" : function() { + load(); + }, "File No" : { value : fileNumber, min : 0, @@ -21,9 +24,6 @@ function showMenu() { "View Logs" : function() { viewLogs(); }, - "Exit" : function() { - load(); - }, }; E.showMenu(menu); } @@ -34,7 +34,7 @@ function viewLog(n) { var records = 0, l = "", ll=""; while ((l=f.readLine())!==undefined) {records++;ll=l;} var length = 0; - if (ll) length = (ll.split(",")[0]|0)/1000; + if (ll) length = Math.round( (ll.split(",")[0]|0)/1000 ); var menu = { "" : { title : "Log "+n } From ba97b157026de86558bd377359147486820c4573 Mon Sep 17 00:00:00 2001 From: Paul Spenke <50266156+deepDiverPaul@users.noreply.github.com> Date: Thu, 7 Oct 2021 09:41:19 +0200 Subject: [PATCH 0147/1062] Fix translation slidingtext.locale.de.js --- apps/slidingtext/slidingtext.locale.de.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/slidingtext/slidingtext.locale.de.js b/apps/slidingtext/slidingtext.locale.de.js index 11124c24a..3cb178232 100644 --- a/apps/slidingtext/slidingtext.locale.de.js +++ b/apps/slidingtext/slidingtext.locale.de.js @@ -1,15 +1,15 @@ var DateFormatter = require("slidingtext.dtfmt.js"); -const germanNumberStr = [ ["ZERO",""], // 0 +const germanNumberStr = [ ["NULL",""], // 0 ["EINS",""], // 1 ["ZWEI",""], //2 ["DREI",''], //3 ["VIER",''], //4 ["FÜNF",''], //5 ["SECHS",''], //6 - ["SEIBEN",''], //7 + ["SIEBEN",''], //7 ["ACHT",''], //8 - ["NUEN",''], // 9, + ["NEUN",''], // 9, ["ZEHN",''], // 10 ["ELF",''], // 11, ["ZWÖLF",''], // 12 @@ -22,7 +22,7 @@ const germanNumberStr = [ ["ZERO",""], // 0 ["NEUN",'ZEHN'], // 19 ]; -const germanTensStr = ["ZERO",//0 +const germanTensStr = ["NULL",//0 "ZEHN",//10 "ZWANZIG",//20 "DREIßIG",//30 @@ -38,7 +38,7 @@ const germanUnit = ["",//0 "VIERUND", //4 "FÜNFUND", //5 "SECHSUND", //6 - "SEIBENUND", //7 + "SIEBENUND", //7 "ACHTUND", //8 "NEUNUND" //9 ] @@ -91,4 +91,4 @@ class GermanDateFormatter extends DateFormatter { } } -module.exports = GermanDateFormatter; \ No newline at end of file +module.exports = GermanDateFormatter; From 9b8a01c23da192bd449a9687dc35a736dd726af3 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Thu, 7 Oct 2021 09:52:28 +0200 Subject: [PATCH 0148/1062] Added backgrounds for V1 and V2 + Bluetooth symbol --- apps/TheBinWatch/Background176_center.png | Bin 0 -> 4158 bytes apps/TheBinWatch/Background240_center.png | Bin 0 -> 6492 bytes apps/TheBinWatch/bt-icon.png | Bin 0 -> 628 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/TheBinWatch/Background176_center.png create mode 100644 apps/TheBinWatch/Background240_center.png create mode 100644 apps/TheBinWatch/bt-icon.png diff --git a/apps/TheBinWatch/Background176_center.png b/apps/TheBinWatch/Background176_center.png new file mode 100644 index 0000000000000000000000000000000000000000..9c8ecee53dae7bcf0f81fa2e42d8e3344317bc31 GIT binary patch literal 4158 zcma)9c~}!?8qdWb$3y7E3|f0G9PGmTG`f7PLj_g6-0-tJp3(A;%1D|L8nVj`_XU?|r}b zyQ-v;B&IvZokSurMZyFb_S<{_VPcitZ zAo|}w-Tv`hm0|p*=+-455rAERlN8Dx{no>$^0##i&0tF85}7286hASTi@mt1gxmL% zNOYFhU4$z?dqNLCh57~Yz|>h3Yx@&!1j_Qdsm8b8A| zC(}U>SV4I0VJH zNG}A|jlqK5Hcu3;xbQa+W8M9$LL~?YJV);I_xFRi>IG`ziX+{vcm*)~T?mzZac-l} z&j33V5aA>#Y-_X|ehi{IhF#b#myaFN(RQg5%x;n>$52q%3?wb{{cciB^(z7gJ)~ZC z6`)q*@esd_ZP_l>w6ZAs2qN_o_A$~}YrmVl5fF7bZG9Cpj>L;O?VygtZ&6Nto1>u^ z{-Q$DV(o)%MQ`-wkf|NydRn82fKCY~;E@epUgEH@u+XruB-s%NOdo!3(%xAJNk@|* zg5tLIRO${2wby$&W9+KgeAjH2>ThC$nkWIhB{$~hO+8l&h9%_i_< zw>4sk>wMGMw3b6q@Y!%kg>XX6=0!)Ao8$tJk*rEi?zEL3A-z$R!HjlI+DE1KdY0_} zlaa5_U@l9OVUH~ug{$BBzCR-L@&>n0GFsy=M0EdB0mPa+2uTt0G4xBEd13gKK6g zdq`dp9268~W7v}q1-5M(U?ii|J#ih3Qtk2DFNu=Brq|LQEMmIV(sTu_4$3wot!GfE zUlnr=l}=AzU>4by9UPN^2vSw=0?&n${DdCX>mwy!8}9 z#Wm3s#?Z z4gHlU6Ju=H=Y|b8u(5)WV{YsE%o++=hzhnSr6-UQ!nou@2N0@NaiYq z2*H%}$8D#$ctQw-g`n|!J#SnFj)!cHz-wcbP{~qM@Hyr#JCYD+Uh>SK(WH@9VOb5{ zW^8j#8Fmw+NVC-q3~{tY!|OQvKk^@b9(>tS7;IALb^uqa1Ds6+M}UVctSGWE zsoD3aWMwg`aIPmagtGP6BnnFKQ|hoi)dNv@2X^!Y?%Y8VQ9*Ir{e$f2(X!=sqIU4CRQ6EvzD9 zERG)25%kf^h#;uHWc7Kg+4mEo&w03@{od5@_`e8!f`XWjq|C;}JA3eMZ4j+AIa~dY zOdp~S=CO;)lN@Ml8R>93K|!VZyn1_6MKlcBUxC_e~kBG9B>hZ8G39hU%3o8xSnd01I;DQ&&9u0eZJxS$7 z@4kZ+XB=?ddmE=Ssr7qGuN#afeGv$|ullCh z_Z-8t@h(0SC!>s|)75dRj;>On2Q^i>pnlU2jI`wq5?=Gb1D9WmPW1e8Sg4e)^s-E9 zZ}Fp@tl{aDUYq3pqhbH+=Gias1+Q7WZ+v0f~q%#8)%! zirzcztIiGRz%uL*WjjK_D?@0{=-A$OP~W9+eN%JHI~dPpZ&!Hy&n|`1`dRQL3oDo! z@fqxFQ%cxT4VNKO4R)12i91*gxX`>qoR*XpFLYiQt2$C1cTnhWbgX?8?ug#|%G-u2 zCg)0d>_JGowibKr2ObKyiB-nGwLNYmE3T3u6B;m&j%|t(HX{?Ta(uGl(04X`bNP(P zM4vyIp`j^SI_l|z?UJ+Lp~l54E?ZB3$+5O06dGzuso?J1wJYY*MW3&3RnN>M?`z_- zm$1L7mdZM>?95$Cju4?UnJ%_yVJjMM)#MA=uV8lwZ#z`hD9MQT z+>w`;XQvd_-zY(~!55s4U}Z@CW>gzQM9})a0%Zp7x*_i?08k%JaCm{0kw(nsy=RLZ z!Qo+!;8j%1BX}9UA$T>z?1ja)g>Fzj^Wdnz*Sk4wMxn%fR50}t0l8;I0&y=5vu7ez zI1zYsb5b^{EjeJWTI)y>9(t0r9Tg1gmU8S>Zg{*ckx-b1#}mm5UQ4CK*BM}2p0K>4 w!Zwf%Gj%)i@PG|7!P$gYc=)cEsWsxksifxV!UIED&*pC>qC`nTGb~5{4@KTT(f|Me literal 0 HcmV?d00001 diff --git a/apps/TheBinWatch/Background240_center.png b/apps/TheBinWatch/Background240_center.png new file mode 100644 index 0000000000000000000000000000000000000000..6fa35f93fc6c5e92b98f781d6d42251f370eb8a8 GIT binary patch literal 6492 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 Q2>KnnWX0l>+zk1D04_NZ`v3p{ literal 0 HcmV?d00001 diff --git a/apps/TheBinWatch/bt-icon.png b/apps/TheBinWatch/bt-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c07800ea34b2935ba689265237bf2ccbc4fced55 GIT binary patch literal 628 zcmV-)0*n2LP)C}z~&b@7#oSU1p zg(Mbn7FO^8ayo+?Z-@cQLag!o1eS0gsx%Ei1s)H>I*swX*GEUl@rZfL!Cy{9VVO~>MAT{}{W{1Ek>m7dA>3zFE)&)3t_F;h zzfx-`k7yyxOll^XnUEolQT__b+4RQv1W3&c=3}tABfdGooGLuI7h{~6HNX&$J1AmR z8uGu4=c&xh+3^11-hFF;yn~DY!%}G O0000 Date: Thu, 7 Oct 2021 10:05:18 +0200 Subject: [PATCH 0149/1062] support V1 and V2 recognize the device we are running on, load related background image and set fitting values --- apps/TheBinWatch/app.js | 184 +++++++++++++++++++++++++++++++--------- 1 file changed, 146 insertions(+), 38 deletions(-) diff --git a/apps/TheBinWatch/app.js b/apps/TheBinWatch/app.js index c395c6684..7e7c67966 100644 --- a/apps/TheBinWatch/app.js +++ b/apps/TheBinWatch/app.js @@ -26,22 +26,73 @@ var MX = 10, MY = 40 + TIME_Y_OFFSET; var SX = 10, SY = 80 + TIME_Y_OFFSET; var BT_X = 30, BT_Y = 10; var DX = 160, DY = 148; +var screen_size_x = 176; +var screen_size_y = 176; */ + +const V2_X_STEP = 26; +const V2_Y_STEP = 34; + +const V2_TIME_Y_OFFSET = 30; +const V2_HX = 35; +const V2_HY = 0 + V2_TIME_Y_OFFSET; +const V2_MX = 10; +const V2_MY = 40 + V2_TIME_Y_OFFSET; +const V2_SX = 10; +const V2_SY = 80 + V2_TIME_Y_OFFSET; +const V2_BT_X = 30; +const V2_BT_Y = 10; +const V2_DX = 160; +const V2_DY = 148; + +const V2_BAT_POS_X = 150; +const V2_BAT_POS_Y = 16; +const V2_BAT_SIZE_X = 2; +const V2_BAT_SIZE_Y = 4; +const V2_SCREEN_SIZE_X = 176; +const V2_SCREEN_SIZE_Y = 176; +const V2_BACKGROUND_IMAGE = "Background176_center.png"; + /* Bangle 1: 240 x 240 */ -var x_step = 35; -var y_step = 46; +const V1_X_STEP = 35; +const V1_Y_STEP = 46; -var TIME_Y_OFFSET = 41; -var HX = 48, HY = 0 + TIME_Y_OFFSET; -var MX = 14, MY = 55 + TIME_Y_OFFSET; -var SX = 14, SY = 110 + TIME_Y_OFFSET; -var BT_X = 41, BT_Y = 14; +const V1_TIME_Y_OFFSET = 41; +const V1_HX = 48; +const V1_HY = 0 + V1_TIME_Y_OFFSET; +const V1_MX = 14; +const V1_MY = 55 + V1_TIME_Y_OFFSET; +const V1_SX = 14; +const V1_SY = 110 + V1_TIME_Y_OFFSET; +const V1_BT_X = 41; +const V1_BT_Y = 14; //var BT_X = 20, BT_Y = 14; -var DX = 160, DY = 205; +const V1_DX = 160; +const V1_DY = 205; -var BAT_POS_X = 175, BAT_POS_Y = 21; -var BAT_SIZE_X = 3, BAT_SIZE_Y = 5; +const V1_BAT_POS_X = 175; +const V1_BAT_POS_Y = 21; +const V1_BAT_SIZE_X = 3; +const V1_BAT_SIZE_Y = 5; +const V1_SCREEN_SIZE_X = 240; +const V1_SCREEN_SIZE_Y = 240; +const V1_BACKGROUND_IMAGE = "Background240_center.png"; +/* runtime settings */ +var x_step = 0; +var y_step = 0; + +var time_y_offset = 0; +var hx = 0, hy = 0; +var mx = 0, my = 0; +var sx = 0, sy = 0; +var bt_x = 0, bt_y = 0; +var dx = 0, dy = 0; + +var bat_pos_x, bat_pos_y, bat_size_x, bat_size_y; +var backgroundImage = ""; +var screen_size_x = 0; +var screen_size_y = 0; /* global variables */ @@ -49,14 +100,10 @@ var BAT_SIZE_X = 3, BAT_SIZE_Y = 5; //var screen_size_x = 176; //var screen_size_y = 176; -var screen_size_x = 240; -var screen_size_y = 240; var showDateTime = 2; /* show noting, time or date */ -var cg = Graphics.createArrayBuffer( - screen_size_x,screen_size_y, 1, {msb:true}); -var cgimg = {width:screen_size_x, height:screen_size_y, bpp:1, - transparent:0, buffer:cg.buffer}; +var cg; +var cgimg; /* local functions */ @@ -103,9 +150,9 @@ function drawBinary(gfx, hour, minute, second) { if(hour > 12) { hour -= 12; /* we use for bit for hours so we only display 12 hours*/ } - drawSquare(gfx, HX, HY, hour, 4); /* set hour */ - drawSquare(gfx, MX, MY, minute, 6); /* set minute */ - drawSquare(gfx, SX, SY, second, 6); /* set second */ + drawSquare(gfx, hx, hy, hour, 4); /* set hour */ + drawSquare(gfx, mx, my, minute, 6); /* set minute */ + drawSquare(gfx, sx, sy, second, 6); /* set second */ } /** @@ -145,7 +192,7 @@ function drawDate(gfx, d) { gfx.setFontAlign(0,-1); // align right bottom gfx.setFont("5x9Numeric7Seg",2); /* draw the current time font */ - gfx.drawString(dateString, gfx.getWidth() / 2, DY, false /*clear background*/); + gfx.drawString(dateString, gfx.getWidth() / 2, dy, false /*clear background*/); // gfx.setFont("6x8",2); // var date = locale.date(d, false); @@ -187,7 +234,7 @@ function updateVTime() { * @param level: current battery level */ function drawBattery(gfx, level) { - var pos_x = BAT_POS_X + 5 * (BAT_SIZE_X + 2); + var pos_x = bat_pos_x + 5 * (bat_size_x + 2); var stepLevel = Math.round((level + 10) / 20); /* if(stepLevel < 2) { @@ -197,12 +244,11 @@ function drawBattery(gfx, level) { } else { gfx.setColor(4); } -*/ - console.log("stepLevel: " + stepLevel); +*/ for(i = 0; i < stepLevel; i++) { - pos_x -= BAT_SIZE_X + 2; - gfx.fillRect(pos_x, BAT_POS_Y, - pos_x + BAT_SIZE_X, BAT_POS_Y + BAT_SIZE_Y); + pos_x -= bat_size_x + 2; + gfx.fillRect(pos_x, bat_pos_y, + pos_x + bat_size_x, bat_pos_y + bat_size_y); } } @@ -215,11 +261,66 @@ function drawBattery(gfx, level) { * @param level: current battery level */ function drawBT(status) { +} +function setRuntimeValues(resolution) { + if(240 == resolution) { + x_step = V1_X_STEP; + y_step = V1_Y_STEP; + + time_y_offset = V1_TIME_Y_OFFSET; + hx = V1_HX; + hy = V1_HY; + mx = V1_MX; + my = V1_MY; + sx = V1_SX; + sy = V1_SY; + bt_x = V1_BT_X; + bt_y = V1_BT_Y; + dx = V1_DX; + dy = V1_DY; + + screen_size_x = V1_SCREEN_SIZE_X; + screen_size_y = V1_SCREEN_SIZE_Y; + backgroundImage = V1_BACKGROUND_IMAGE; + + // TODO: set battery stuff + } else { + x_step = V2_X_STEP; + y_step = V2_Y_STEP; + + time_y_offset = V2_TIME_Y_OFFSET; + + hx = V2_HX; + hy = V2_HY; + mx = V2_MX; + my = V2_MY; + sx = V2_SX; + sy = V2_SY; + + bt_x = V2_BT_X; + bt_y = V2_BT_Y; + + dx = V2_DX; + dy = V2_DY; + + screen_size_x = V2_SCREEN_SIZE_X; + screen_size_y = V2_SCREEN_SIZE_Y; + backgroundImage = V2_BACKGROUND_IMAGE; + // TODO: set battery stuff + } + cg = Graphics.createArrayBuffer( + screen_size_x,screen_size_y, 1, {msb:true}); + + cgimg = {width:screen_size_x, height:screen_size_y, bpp:1, + transparent:0, buffer:cg.buffer}; + } var hour = 0, minute = 1, second = 50; var batVLevel = 0; + + function draw() { - + if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled var d = new Date(); var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds(); // gfx2(hour, minute, second); @@ -237,7 +338,6 @@ function draw() { default: /* do nothing */ } - console.log("BatLevel: " + batVLevel); drawBattery(cg, batVLevel /*E.getBattery()*/); drawBT(1); @@ -248,17 +348,25 @@ function draw() { updateVTime(); g.clear(); g.drawImages([{image:cgimg}, -// {image:require("Storage").read("Background176_center.png")}, - {image:require("Storage").read("Background240_center.png")}, - { x:BT_X, y:BT_Y, rotate: 0, image:require("Storage").read("bt-icon.png")}, + {image:require("Storage").read(backgroundImage)}, + { x:bt_x, y:bt_y, rotate: 0, image:require("Storage").read("bt-icon.png")}, ]); + const millis = d.getMilliseconds(); + setTimeout(draw, 1000-millis); } -g.clear(); -setInterval(draw, 1000); -var x_size = g.getWidth(); -console.log("Startup: X-W = " + x_size); -console.log("BatLevel: " + E.getBattery()); - +// Show launcher when button pressed +Bangle.setUI("clock"); +setRuntimeValues(g.getWidth()); +Bangle.on("lcdPower", function(on) { + if (on) { + draw(); + } +}); +g.reset().clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +//setInterval(draw, 1000); +//var x_size = g.getWidth(); setWatch(toggleDateTime, BTN1, { repeat : true, edge: "falling"}); - +draw(); From c60dd212f9598ab7ebd4a07288e9f2025f9a82b0 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Thu, 7 Oct 2021 10:42:25 +0200 Subject: [PATCH 0150/1062] minor changes fix wrong variable name (DY -> dy) remove unused code --- apps/TheBinWatch/app.js | 40 +++++++--------------------------------- 1 file changed, 7 insertions(+), 33 deletions(-) diff --git a/apps/TheBinWatch/app.js b/apps/TheBinWatch/app.js index 7e7c67966..6c0abc961 100644 --- a/apps/TheBinWatch/app.js +++ b/apps/TheBinWatch/app.js @@ -14,22 +14,9 @@ require("Font5x9Numeric7Seg").add(Graphics); /* constants and definitions */ + /* Bangle 2: 176 x 176 */ -/* -var x_step = 26; -var y_step = 34; - -var TIME_Y_OFFSET = 30; -var HX = 35, HY = 0 + TIME_Y_OFFSET; -var MX = 10, MY = 40 + TIME_Y_OFFSET; -var SX = 10, SY = 80 + TIME_Y_OFFSET; -var BT_X = 30, BT_Y = 10; -var DX = 160, DY = 148; -var screen_size_x = 176; -var screen_size_y = 176; -*/ - const V2_X_STEP = 26; const V2_Y_STEP = 34; @@ -78,7 +65,9 @@ const V1_BAT_SIZE_Y = 5; const V1_SCREEN_SIZE_X = 240; const V1_SCREEN_SIZE_Y = 240; const V1_BACKGROUND_IMAGE = "Background240_center.png"; + /* runtime settings */ + var x_step = 0; var y_step = 0; @@ -96,11 +85,6 @@ var screen_size_y = 0; /* global variables */ - -//var screen_size_x = 176; -//var screen_size_y = 176; - - var showDateTime = 2; /* show noting, time or date */ var cg; var cgimg; @@ -170,8 +154,7 @@ function drawTime(gfx, h, m, s) { gfx.setFontAlign(0,-1); // align right bottom gfx.setFont("5x9Numeric7Seg", 2); - gfx.drawString(time, gfx.getWidth() / 2, DY, false /*clear background*/); - + gfx.drawString(time, gfx.getWidth() / 2, dy, false /*clear background*/); } /** @@ -187,19 +170,11 @@ function drawDate(gfx, d) { var dateString = "" + ("0" + d.getDate()).substr(-2) + " " + ("0" + d.getMonth()).substr(-2) + " " - + ("0" + d.getFullYear()).substr(-4) - ; + + ("0" + d.getFullYear()).substr(-4); gfx.setFontAlign(0,-1); // align right bottom gfx.setFont("5x9Numeric7Seg",2); /* draw the current time font */ - gfx.drawString(dateString, gfx.getWidth() / 2, dy, false /*clear background*/); - -// gfx.setFont("6x8",2); -// var date = locale.date(d, false); -// gfx.drawString(date, DX, DY, false); -// draw the seconds (2x size 7 segment) -// gfx.setFont("7x11Numeric7Seg",1); -// gfx.drawString(("0"+s).substr(-2), X+30, Y, false /*clear background*/); + gfx.drawString(dateString, gfx.getWidth() / 2, dy, false /* don't clear background*/); } function toggleDateTime() { @@ -323,13 +298,12 @@ function draw() { if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled var d = new Date(); var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds(); -// gfx2(hour, minute, second); + drawBinary(cg, h, m, s); cg.setColor(0); switch(showDateTime) { case 1: -// drawTime(hour, minute, second); drawTime(cg, h, m, s); break; case 2: From ebda183d240192019cc3995cdc55460271c51d6a Mon Sep 17 00:00:00 2001 From: Vingelar Date: Thu, 7 Oct 2021 11:06:23 +0200 Subject: [PATCH 0151/1062] adjusted battery level display settings --- apps/TheBinWatch/app.js | 47 +++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/apps/TheBinWatch/app.js b/apps/TheBinWatch/app.js index 6c0abc961..8cb8669f6 100644 --- a/apps/TheBinWatch/app.js +++ b/apps/TheBinWatch/app.js @@ -32,13 +32,15 @@ const V2_BT_Y = 10; const V2_DX = 160; const V2_DY = 148; -const V2_BAT_POS_X = 150; -const V2_BAT_POS_Y = 16; +const V2_BAT_POS_X = 127; +const V2_BAT_POS_Y = 15; const V2_BAT_SIZE_X = 2; -const V2_BAT_SIZE_Y = 4; +const V2_BAT_SIZE_Y = 3; const V2_SCREEN_SIZE_X = 176; const V2_SCREEN_SIZE_Y = 176; const V2_BACKGROUND_IMAGE = "Background176_center.png"; +const V2_BG_COLOR = 0; +const V2_FG_COLOR = 1; /* Bangle 1: 240 x 240 */ @@ -65,6 +67,8 @@ const V1_BAT_SIZE_Y = 5; const V1_SCREEN_SIZE_X = 240; const V1_SCREEN_SIZE_Y = 240; const V1_BACKGROUND_IMAGE = "Background240_center.png"; +const V1_BG_COLOR = 1; +const V1_FG_COLOR = 0; /* runtime settings */ @@ -82,6 +86,8 @@ var bat_pos_x, bat_pos_y, bat_size_x, bat_size_y; var backgroundImage = ""; var screen_size_x = 0; var screen_size_y = 0; +var bg_color = 0; +var fg_color = 1; /* global variables */ @@ -127,9 +133,9 @@ function drawSquare(gfx, x, y, data, numOfBits) { */ function drawBinary(gfx, hour, minute, second) { gfx.clear(1); - gfx.setColor(1); + gfx.setColor(bg_color); gfx.fillRect(0, 0, screen_size_x, screen_size_y); - gfx.setColor(0); + gfx.setColor(fg_color); if(hour > 12) { hour -= 12; /* we use for bit for hours so we only display 12 hours*/ @@ -154,6 +160,8 @@ function drawTime(gfx, h, m, s) { gfx.setFontAlign(0,-1); // align right bottom gfx.setFont("5x9Numeric7Seg", 2); + gfx.setColor(fg_color); + gfx.drawString(time, gfx.getWidth() / 2, dy, false /*clear background*/); } @@ -174,6 +182,8 @@ function drawDate(gfx, d) { gfx.setFontAlign(0,-1); // align right bottom gfx.setFont("5x9Numeric7Seg",2); /* draw the current time font */ + gfx.setColor(fg_color); + gfx.drawString(dateString, gfx.getWidth() / 2, dy, false /* don't clear background*/); } @@ -220,6 +230,8 @@ function drawBattery(gfx, level) { gfx.setColor(4); } */ + gfx.setColor(fg_color); + for(i = 0; i < stepLevel; i++) { pos_x -= bat_size_x + 2; gfx.fillRect(pos_x, bat_pos_y, @@ -257,8 +269,14 @@ function setRuntimeValues(resolution) { screen_size_x = V1_SCREEN_SIZE_X; screen_size_y = V1_SCREEN_SIZE_Y; backgroundImage = V1_BACKGROUND_IMAGE; - - // TODO: set battery stuff + bg_color = V1_BG_COLOR; + fg_color = V1_FG_COLOR; + + bat_pos_x = V1_BAT_POS_X; + bat_pos_y = V1_BAT_POS_Y; + bat_size_x = V1_BAT_SIZE_X; + bat_size_y = V1_BAT_SIZE_Y; + } else { x_step = V2_X_STEP; y_step = V2_Y_STEP; @@ -281,8 +299,14 @@ function setRuntimeValues(resolution) { screen_size_x = V2_SCREEN_SIZE_X; screen_size_y = V2_SCREEN_SIZE_Y; backgroundImage = V2_BACKGROUND_IMAGE; - // TODO: set battery stuff - } + bg_color = V2_BG_COLOR; + fg_color = V2_FG_COLOR; + + bat_pos_x = V2_BAT_POS_X; + bat_pos_y = V2_BAT_POS_Y; + bat_size_x = V2_BAT_SIZE_X; + bat_size_y = V2_BAT_SIZE_Y; +} cg = Graphics.createArrayBuffer( screen_size_x,screen_size_y, 1, {msb:true}); @@ -291,7 +315,7 @@ function setRuntimeValues(resolution) { } var hour = 0, minute = 1, second = 50; -var batVLevel = 0; +var batVLevel = 20; function draw() { @@ -300,7 +324,7 @@ function draw() { var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds(); drawBinary(cg, h, m, s); - cg.setColor(0); + cg.setColor(fg_color); switch(showDateTime) { case 1: @@ -312,6 +336,7 @@ function draw() { default: /* do nothing */ } + cg.setColor(fg_color); drawBattery(cg, batVLevel /*E.getBattery()*/); drawBT(1); From be556e859e9e73c649ee19c05a60b42281a6ef89 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 7 Oct 2021 11:09:41 +0100 Subject: [PATCH 0152/1062] add floral clock --- apps.json | 15 +++++++- apps/alarm/ChangeLog | 2 +- apps/floralclk/ChangeLog | 1 + apps/floralclk/app-icon.js | 1 + apps/floralclk/app.js | 76 +++++++++++++++++++++++++++++++++++++ apps/floralclk/app.png | Bin 0 -> 4978 bytes 6 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 apps/floralclk/ChangeLog create mode 100644 apps/floralclk/app-icon.js create mode 100644 apps/floralclk/app.js create mode 100644 apps/floralclk/app.png diff --git a/apps.json b/apps.json index 532a0c898..c576eada6 100644 --- a/apps.json +++ b/apps.json @@ -3533,7 +3533,7 @@ "name": "Wave Clock", "icon": "app.png", "version":"0.02", - "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires a bugfix for #2049 on Bangle.js 1**", + "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", "tags":"clock,b2", "type":"clock", "allow_emulator":true, @@ -3541,5 +3541,18 @@ {"name":"waveclk.app.js","url":"app.js"}, {"name":"waveclk.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "floralclk", + "name": "Floral Clock", + "icon": "app.png", + "version":"0.01", + "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", + "tags":"clock,b2", + "type":"clock", + "allow_emulator":true, + "storage": [ + {"name":"floralclk.app.js","url":"app.js"}, + {"name":"floralclk.img","url":"app-icon.js","evaluate":true} + ] } ] diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index a151fd07e..fce54f273 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -11,4 +11,4 @@ 0.11: Respect Quiet Mode 0.12: Fix widget for bangle 2, now uses theme Widgets now shown on Alarm screen - Alarm widget state now updates when setting/resetting an alarm +0.13: Alarm widget state now updates when setting/resetting an alarm diff --git a/apps/floralclk/ChangeLog b/apps/floralclk/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/floralclk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/floralclk/app-icon.js b/apps/floralclk/app-icon.js new file mode 100644 index 000000000..4dfc57191 --- /dev/null +++ b/apps/floralclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwhE+sVin0/tVjsdim84sdro1GAQNrAAlHAYVqABk/FINosc/AoNpF4cTGoMTnIhBo1qEgIvFABJACoAvEFwJaDGoNjnFpn5eCik/DYQwBAAQwOFIMUDYKBBLwQwBnwoBBAM3GIIEBhs5E4RLBPYqMKFwU4AAM+nwCBF4SJCAwMxXII2BnBeDJogAGNQIAFBIJMBRIYvCLQK+Bow7BhsNCINjm45BXwZgHF5ITBigoBF4NpQoIwBLwLJBn8Oh0NBoU4F4J6CF4RiKR47iCtIrBiaGBEgdknMOnBABiYKBtNkKoaUIdo5hCQoKvBYgKGBGAIJBMANjhqfBHgLQBNgKcBEpAxBBA9HHoiwBF4S3BcoM4Nwdim83sVEGAINBMQIfBEASYGLII4ECISFBnEyFgKHBGwRsDHYKfBaQOGrifCXw4qBNgIEBXoQHBCQZXBnArCAQNpnBWBFwUTBINiwGkwFcsbzDEwJcFG4pcCAAMUik/EIKJBn6JBMYNpnzABsY0BwGeAAN6wLnCEQQACF4ztCF4UUJ4QNDGAKTCtMTnASBHwOezAwCveIP4ReEeQzNDFwgvDDQU/oDlDJYVkF4e8z2Hx2Px2IAAKKEGo1qnAuBtLYBBwRJCAoIuCbgVqxAsCAQWB1mBwN6AANWmSwBJwRcCDIIuDnxAGAYU5HQmPF4W84QBBvWlGoOY4TIBmMxnJGCYYc5ik+coNjn8UhsUiqRDGQQUBQQJUBz3CzAxBYYYADvWGsTZBDoNHcQUTXgU+n1pB4LmBFQSUCAoNkw4uCF4QBBF4QFBAAIFBF4IjBoBMCn84nwtCMAIABm8TnLREXYd7KQSLBzoBDAQJlBBQN5w1osU/VAQuBnCLBGQNpGYM4R4LRCAATTBRgJcCy4kBz3I5HO5HHy4JBYwRfBcYIfBoE4m5YCho0BA4M4FwzzCxCMBEIO73guB5wAC5BgBSoWAF4KQBsdkKoKNChoACik4nIuHF4i0BdQOdF4XNAQK8Cz2lqzjCnCIBFwTnBS4IrBTQQuBdoLuCBAWOdoOYWgRfC5ovDy4vDriDCAAS8CFQYADdQgvCowvDSAK0CF4SPC4QwCvVcmUymMxFwSUBFwQoDFQToBAoIsBBoSQBMASRBy6QDXwIJCSIWAFwjzDWowAKGYOHwIhBYIOezooC3YuBF4d6GAK/BYAVkF6CUExBiBYQQCBFQIvCzAvCAYM3LoUTLwIeDF5pBDSQRgCLQYDBZQZrBz1cw9iiZeCWoQvWeYQuBfIIxCLwd6w1inEULyQvFtYvFEwOB0uBz4zCX4QuBnDsCDortNCQNHL4mYFwN7ZAOIfgN6AANcsc+m6NBDoRgQFwNGo5FBx2HKoZeBHYNqsg7BqtVsS8BRoReCL6AgBSYQ1CwJWBveHZYlkmMxLwM4h05sgADPwRRCF5ouDMIKKCxwPDsk4mM4XgMTXwLvBikOYYQvOBoQOBoE/JA4DBn8UFwNim8NF4QABhsNnIvQUgVAnMOVoQ4CAANqscUidiRoMNm4zBAAQHBF6CLDO4JIBGAVHXgYiBn1jn0NGYVoAAIvBIwYvOBgM/hyxBAAQXBHYU5RANjscTLwNjLgIuBny+FF5xeBhtcPYU+DYJeDRog0CCIYSBoAvRGAMUmOHJgcbF4QuBFIUNmIBBeYItCIIRNBd54ABisUVgNED4QJBn69Dm4uBh0OnIsBoArCFBoPDHgNqoAvBL4YvCb4JeBnxiCslkDogvRNQVGGALrBVobwBfAMNXoMTigsHDINHAAIvJGIdGn9ro4FBscNMANpF4LoBm4DChq1BFAJDBDobmMMIgvDA4UULwKHBMoLlBG4MynBeBCYQfFF56MBoAbDMAKzBnETm7oBGoM4hxeCQoJfCcJC/KAgIvFMAMNAASNBsQ1BLwVqFwIeELppCBF4dq")) diff --git a/apps/floralclk/app.js b/apps/floralclk/app.js new file mode 100644 index 000000000..5fb9303a8 --- /dev/null +++ b/apps/floralclk/app.js @@ -0,0 +1,76 @@ +function getImg() { + return require("heatshrink").decompress(atob("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")); +} +var IMAGEWIDTH = 176; +var IMAGEHEIGHT = 109; + + +Graphics.prototype.setFontDancingScript = function() { + // Actual height 44 (44 - 1) + var widths = atob("DBIhFB4bGRoeFhweDQ=="); + var font = atob("AAAAAAAAAAAAAAMAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAAAAAD8AAAAAAD+AAAAAAD/AAAAAAD/gAAAAAH/gAAAAAH/wAAAAAH/gAAAAAP/gAAAAAP/gAAAAAf/AAAAAAf/AAAAAA/+AAAAAA/+AAAAAB/+AAAAAB/8AAAAAB/8AAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8AAAAAf//4AAAAf///AAAAf///4AAAf////AAAf/8Af4AAP/gAA/AAP/AAADwAH+AAAAcAD+AAAAHAA+AAAABwAfAAAAAcAPAAAAAHADgAAAABwB4AAAAA8AcAAAAAOAHAAAAAHgBgAAAAHwAYAAAAD4AGAAAAD+ABwAAAD/AAcAAAD/gAHgAAH/wAA+AAP/wAAH+H//4AAB////4AAAP///4AAAA///4AAAAD//gAAAAAD8AAAAAAAAAAAGAAAAAAABwAAAAAAAcAAAAAAAHAAAAAAABwAAAAAAAcAAGAAAAPAADwAAA/wAA4AAB/8AAeAAP//AAPAD//7wAHwf//w8AB////gPAA///+ABwAf//4AAcAP/+AAADAH/wAAAAQB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAB8AAAAAAAfAAAAAAAGwAAAAAABsAAAAAAAfAAAAAAAHwAAP4AAB4AAP/AAAeAAH/wAAHAAD/+AAD4AB+AgAB+AA8AAAA/gAOAAAAf8AHAAAAPvABwAAAPzwAYAAAH4eAGAAAH8HgBgAAD+B8AcAAD/AfAHAAH/AHwB4AH/gB8AP4//gAfAD///wAHwAf//wAB8AB//gAAeAAP/gAAHAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAP+AAAAAAH/wAAAAAB/+AAAAAAcfgAAAAAEB8AB/AAAAPAA/4AAABwAf+AAAAcAP/gAAAHADwQAAABwAwABAAAcAcAAYAAHAHAAOAADwBgAHwAB4AYAD+AB+AHAB/4B/ABwB+///wAeB/P//4AH//h//8AA//4P/+AAH/4B/+AAA/8AH/AAAH8AAEAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAA4AAAAAAAeAAAAAAAPAAAAAAAHwAAAAAAD8AAAAAAB/AAAAAAA/wAAAAAAe8AAAAAAPPAAAAAAHjwAAAAADw8AAAAAB4PAAwAAA8DwP+AAAeA///wAAPAP//8AAHh////AAH3////wAD////AAAB///wAAAA//88AAAAf/gPAAAAf8ADgAAAHgAA4AAAAAAAMAAAAAAAAAfwAAAAAAP+AAAAAAH/wAAAAAB/+AAAAAA8PgAAAAwEA8AAAH8AAHAAAP/AABwAA//gAAcAD/4YAAHAH/gGAABwB/ABgAAcAfgAYAAPAD4AHAADwA+ABwAB4APgAeAB+AD4AH4B/AA+AA///wAPgAP//4AD4AB//8AB+AAP/+AAfgAB//AAHwAAH/AAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAf/4AAAAA///gAAAB///+AAAB////wAAB////+AAA//+B/gAA//8AD8AAf+OAAPAAP8HAABwAH8BgAAcAD8A4AAHAB+AOAABwAeADgAAcAPAA4AAHADgAOAADwBwADgAA8AcAA8AA+AGAAPgAfgBgAD+A/wAYAAf//4AGAAH//8ABwAA//+AAfgAH//AAD4AAf/AAAcAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAHAA/AAAABwB/wAwAA8B/8AeAAPB//AHwADx//gB8AA9//wAfAAP/4IAHwAD/wAAB8AB/wAAAPAB/gAAADwB/wAAAA8A/8AAAAPA/PAAAADw/DwAAAA8/A8AAAAP/AOAAAAD/ADgAAAB/gAwAAAAAAAAAAAAAAAAABAAAAAAAH/AAAAAAD/4AAAAAB//AAAAAA//4AAOAAfA+AAf8APgHwAP/wHgA8AH/+DwAHAD//48ABwA///eAAcAeAf/AAHAHAB/gABwBwAP4AA8AYAB/AAPAGAAf4AHgBgAP/AD4AcAHv8B8AHgD5///AA8H8H//gAP/+A//wAB/+AH/4AAP/AAf8AAA/AAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAP/gAAeAAP/+AAPgAH//wAD8AD//+AAPAB///gAAwAfwD8AAMAPwAfAADADwADwAAwB4AAeAAcAcAAHgAHAHAAA4ADgBwAAOAB4AcAADAB8AHAAAwA/ABwAAcA/gAeAAGB/wAHwADh/4AA+AB//8AAP+D//8AAB////+AAAP///+AAAB///+AAAAP//8AAAAAf/4AAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAA4AAAAAAAeAAAAADAHgAAAAB4BwAAAAAeAIAAAAAHgAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + var scale = 1; // size multiplier for this font + g.setFontCustom(font, 46, widths, 50+(scale<<8)+(1<<16)); +} + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { + var x = g.getWidth()/2; + var y = 50; + g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT); + if (g.getWidth() == IMAGEWIDTH) + g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT); + else { + let scale = g.getWidth()/IMAGEWIDTH; + y *= scale; + g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT*scale,{scale:scale}); + } + + var date = new Date(); + var dateStr = require("locale").date(date); + // draw time + g.setFont("DancingScript").setFontAlign(0,0).setColor("#f00"); + g.drawString(date.getHours(), x,y); + y += 43; + g.drawString(date.getMinutes().toString().padStart(2,0), x,y); + // draw date + y += 22; + g.setFontAlign(0,0).setFont("6x8"); + var p = g.getWidth()-60; + g.clearRect(p,y-4,g.getWidth()-p,y+3); // clear the background + g.drawString(dateStr,x,y); + // queue draw in one minute + queueDraw(); +} + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); +// set background colour +g.setTheme({bg:"#0ff"}); +// Clear the screen once, at startup +g.clear(); +// draw immediately at first, queue update +draw(); +// Show launcher when middle button pressed +Bangle.setUI("clock"); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/floralclk/app.png b/apps/floralclk/app.png new file mode 100644 index 0000000000000000000000000000000000000000..a0284226eccdda08db3135319404d62daf4d973c GIT binary patch literal 4978 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 literal 0 HcmV?d00001 From e799550544e55ef94ef48f905283625e50f73e97 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 7 Oct 2021 17:02:57 -0700 Subject: [PATCH 0153/1062] Update schoolCalendar.js --- apps/schoolCalendar/schoolCalendar.js | 95 +++++++++++++++------------ 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js index 36f9110a7..f1901b7d1 100644 --- a/apps/schoolCalendar/schoolCalendar.js +++ b/apps/schoolCalendar/schoolCalendar.js @@ -1,19 +1,23 @@ require("Font8x12").add(Graphics); require("Font8x16").add(Graphics); - -Graphics.prototype.setFontAudiowide = function () { - var widths = atob("BxYfDBkYGhkZFRkZCA=="); - var font = atob("AAAAAAAAA8AAAAHgAAAB8AAAAHgAAAA4AAAAAAAAAAEAAAABgAAAA8AAAAPgAAAH8AAAB/gAAA/4AAAf+AAAH/AAAD/wAAA/4AAAf8AAAP/AAAD/gAAB/4AAAf8AAAD+AAAAfgAAADwAAAAcAAAAAAAAAAAAAAAAAAAAAAP/AAAH//AAB//8AAf//wAH///AA///4APwD/gB8A/8APgP/gB8D98AfAfvgD4H58AfB/PgD4Px8AfD8PgD4/h8AfH4PgD5+B8AP/wPgB/8B8AP/APgB/4D8AH8B/AA///4AD//+AAP//gAA//4AAB/8AAAAAAAAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAAAAD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAA/8AAAf/gD4H/8AfA//gD4P/8AfB+PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//B8AP/4PgA/+B8AD/gPgABgA8AAAAAAAAAAAAPA4HgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP///gB///8AH///AA///wAB//8AAAAAAAAAAAAB/8AAAf/4AAD//gAAf/8AAD//gAAf/8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAAB8AAAAPgAAP///gD///8Af///gD///8Af///gD///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8B8Af/wPgD//B8Af/4PgD//h8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AfA//AAAD/4AAAP8AAAAAAAAAAAAAH//AAB//8AAf//wAH///AB///8AP58/gB8Ph8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB//gD4H/8AAA//AAAD/4AAAP8AAAAAAAAAAAAD4AAAAfAAAAD4AAAAfAABgD4AA8AfAAPgD4AH8AfAD/gD4A/8AfAf/AD4P/gAfH/wAD5/8AAf/+AAD//AAAf/gAAD/4AAAf8AAAB+AAAAPAAAAAAAAAAAAAAAAAB/gAAAf+AAP//4AH///gA///8AP/+PgB//h8APh8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgB8Ph8AP/8PgB//h8AP///gA///8AD///AADz/4AAAP8AAAAMAAAAAAAAAAAAAB/gAAA/+AAAH/4AAB//B8AP/8PgB8Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8AfB8PgD4Ph8APh8PgB8Ph8APx8fgB///8AH///AAf//wAD//8AAH//AAAAAAAAAAAAAAAAAAAAAAAAAOAHgAD4A8AAfAPgAD4A8AAOAHAAAAAAA=="); - var scale = 1; // size multiplier for this font - g.setFontCustom(font, 46, widths, 33+(scale<<8)+(1<<16)); -}; +require("Font6x8").add(Graphics); +require("Font7x11Numeric7Seg").add(Graphics); +require("FontHaxorNarrow7x17").add(Graphics); function getBackgroundImage() {return require("heatshrink").decompress(atob("gMwyEgBAsAgQBCgcAggBCgsAgwBCg8AhABChMAhQBChcAhgBChsAhwBCh8AiEAiIBCiUAiYBCikAioBCi0Ai4BCjEAjIBCjUAjYBCjkAjoBCj0Aj4BBA"));} +function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} + +function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} + +function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} + +function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} + Bangle.setLCDMode("doublebuffered"); g.clear(); -g.setFont("Audiowide"); -g.drawString("...",115,60); +g.setFont("HaxorNarrow7x17"); +g.drawString("Loading...",115,60); g.flip(); LIST = 1; @@ -91,9 +95,9 @@ function getScheduleTable() { function processDay() { var schedule = getScheduleTable(); var currentDate = new Date(); - var currentDayOfWeek = 2;//currentDate.getDay(); - var currentHour = 9;//currentDate.getHours(); - var currentMinute = 30;//currentDate.getMinutes(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); var minofDay = (currentHour*60)+currentMinute; var i; var currentPositon; @@ -134,7 +138,7 @@ function updateHoursToCurrentTime(currentHourFunction) { function updateDay(ffunction,day){ if(ffunction == 1){ - switch (day) { + switch (day) { case 0: return "Sunday"; case 1: @@ -154,31 +158,31 @@ function updateDay(ffunction,day){ break; case 6: day = "Saturday"; - return day; -} + } + return day; }else if(ffunction == 2){ switch (day) { - case 0: - return "Sun"; - case 1: - day = "Mon"; - break; - case 2: - day = "Tue"; - break; - case 3: - day = "Wed"; - break; - case 4: - day = "Thu"; - break; - case 5: - day = "Fri"; - break; - case 6: - day = "Sat"; - return day; + case 0: + return "Sun"; + case 1: + day = "Mon"; + break; + case 2: + day = "Tue"; + break; + case 3: + day = "Wed"; + break; + case 4: + day = "Thu"; + break; + case 5: + day = "Fri"; + break; + case 6: + day = "Sat"; } + return day; } } @@ -215,7 +219,7 @@ function displayClock() { currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); currentHourUpdated = updateHoursToCurrentTime(currentHour); g.setColor(255,255,255); - g.setFont("Audiowide"); + g.setFont("7x11Numeric7Seg",2); g.clear(); var foundNumber = processDay(); var foundSchedule = getScheduleTable(); @@ -224,31 +228,38 @@ function displayClock() { for(var i = 0;i<=240;i++){ g.drawImage(getBackgroundImage(),i,120,{scale:5,rotate:0}); } - g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 150, 0); + g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 160, 0); + + g.drawImage(getUpArrow(),225,5); + g.drawImage(getDownArrow(),225,140); if(currentStage == LIST){ for(var x = 0;x<=numberOfItemsShown;x++){ + g.drawImage(getDotIcon(),223.5,66); scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+x)].sm); scheduleHourUpdatedStart = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+x)].sh); scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+x)].em); scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+x)].eh); + schduleDay = updateDay(2,foundSchedule[((foundNumber-2)+x)].dow); g.setColor(255,255,255); - g.drawRect(10,30+(x*20),230,50+(20*x)); + g.drawRect(10,30+(x*20),220,50+(20*x)); g.reset(); g.setFont("8x12"); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+foundSchedule[((foundNumber-2)+x)].cn,13,35+(x*20)); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+foundSchedule[((foundNumber-2)+x)].cn,13,35+(x*20)); g.setColor(255,0,0); - g.drawRect(10,30+(currentPositionTable*20),230,50+(20*currentPositionTable)); + g.drawRect(10,30+(currentPositionTable*20),220,50+(20*currentPositionTable)); } }else if(currentStage == INFORMATION){ + g.drawImage(getMenuIcon(),223.5,66); scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].sm); scheduleHourUpdatedStart = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].sh); scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].em); scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].eh); + schduleDay = updateDay(1,foundSchedule[((foundNumber-2)+currentPositionTable)].dow); g.setColor(255,255,255); g.reset(); - g.setFont("8x16"); + g.setFont("HaxorNarrow7x17"); g.drawString(foundSchedule[((foundNumber-2)+currentPositionTable)].cn,13,30); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,50); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" ",13,45); } g.flip(); } From bffb2577796ffd078944c242348b4ff1c3768aca Mon Sep 17 00:00:00 2001 From: Vingelar Date: Fri, 8 Oct 2021 15:58:40 +0200 Subject: [PATCH 0154/1062] added swipe for V2 use "swipe" to change time/date/nothing for V2 --- apps/TheBinWatch/app.js | 64 ++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/apps/TheBinWatch/app.js b/apps/TheBinWatch/app.js index 8cb8669f6..bc5de8f4c 100644 --- a/apps/TheBinWatch/app.js +++ b/apps/TheBinWatch/app.js @@ -94,6 +94,7 @@ var fg_color = 1; var showDateTime = 2; /* show noting, time or date */ var cg; var cgimg; +var btImage = null; /* local functions */ @@ -186,11 +187,15 @@ function drawDate(gfx, d) { gfx.drawString(dateString, gfx.getWidth() / 2, dy, false /* don't clear background*/); } - -function toggleDateTime() { - showDateTime++; +function toggleDateTimeUp() { + toggleDateTime(1); +} +function toggleDateTime(adder) { + showDateTime += adder; if(showDateTime > 2){ showDateTime = 0; + } else if(showDateTime < 0) { + showDateTime = 2; } draw(); } @@ -221,7 +226,7 @@ function updateVTime() { function drawBattery(gfx, level) { var pos_x = bat_pos_x + 5 * (bat_size_x + 2); var stepLevel = Math.round((level + 10) / 20); -/* +/* if(stepLevel < 2) { gfx.setColor(2); } else if(stepLevel < 4) { @@ -247,7 +252,12 @@ function drawBattery(gfx, level) { * @param gfx: graphic object * @param level: current battery level */ -function drawBT(status) { +function drawBT() { + if (NRF.getSecurityStatus().connected) { + btImage = "bt-icon.png"; + } else { + btImage = "nbt-icon.png"; + } } function setRuntimeValues(resolution) { if(240 == resolution) { @@ -277,6 +287,9 @@ function setRuntimeValues(resolution) { bat_size_x = V1_BAT_SIZE_X; bat_size_y = V1_BAT_SIZE_Y; + /* use button 1 to change date / time / nothing display */ + setWatch(toggleDateTimeUp, BTN1, { repeat : true, edge: "falling"}); + } else { x_step = V2_X_STEP; y_step = V2_Y_STEP; @@ -306,7 +319,17 @@ function setRuntimeValues(resolution) { bat_pos_y = V2_BAT_POS_Y; bat_size_x = V2_BAT_SIZE_X; bat_size_y = V2_BAT_SIZE_Y; -} + + /* use swipe to change date / time / nothing display */ + Bangle.on('swipe', function(direction) { toggleDateTime(direction);}); + + } + + dg = Graphics.createArrayBuffer( + screen_size_x,screen_size_y, 1, {msb:true}); + dgimg = {width:screen_size_x, height:screen_size_y, bpp:1, + transparent:0, buffer:dg.buffer}; + cg = Graphics.createArrayBuffer( screen_size_x,screen_size_y, 1, {msb:true}); @@ -315,7 +338,7 @@ function setRuntimeValues(resolution) { } var hour = 0, minute = 1, second = 50; -var batVLevel = 20; +var batVLevel = 20, batLevel = 0; function draw() { @@ -336,20 +359,28 @@ function draw() { default: /* do nothing */ } - cg.setColor(fg_color); - drawBattery(cg, batVLevel /*E.getBattery()*/); - drawBT(1); - batVLevel += 2; - if(batVLevel > 100) { - batVLevel = 0; + cg.setColor(fg_color); + + if (Bangle.isCharging()) { + batVLevel += 20; + if(batVLevel > 100) { + batVLevel = 0; + } + batLevel = batVLevel; + } else { + batLevel = E.getBattery(); } + + drawBattery(cg, batVLevel); + drawBT(); + updateVTime(); g.clear(); g.drawImages([{image:cgimg}, {image:require("Storage").read(backgroundImage)}, - { x:bt_x, y:bt_y, rotate: 0, image:require("Storage").read("bt-icon.png")}, - ]); + { x:bt_x, y:bt_y, rotate: 0, image:require("Storage").read(btImage)} + ]); const millis = d.getMilliseconds(); setTimeout(draw, 1000-millis); } @@ -365,7 +396,8 @@ Bangle.on("lcdPower", function(on) { g.reset().clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); + //setInterval(draw, 1000); //var x_size = g.getWidth(); -setWatch(toggleDateTime, BTN1, { repeat : true, edge: "falling"}); + draw(); From 98d625fb267125e22ed0ca543db18d3c9da155ff Mon Sep 17 00:00:00 2001 From: Vingelar Date: Fri, 8 Oct 2021 16:00:24 +0200 Subject: [PATCH 0155/1062] BT connected icon --- apps/TheBinWatch/nbt-icon.png | Bin 0 -> 789 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/TheBinWatch/nbt-icon.png diff --git a/apps/TheBinWatch/nbt-icon.png b/apps/TheBinWatch/nbt-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6b28c50dfecef751d0952530b8c55c7bb34a8e45 GIT binary patch literal 789 zcmV+w1M2*VP)Q2T*_KEk1X zh117?p~Jwgoj|z))HKlC2sCWtKZ{_HYZF90BbpNZn$XLW@fAekWS^$qY`~8zz+k@{a+}#YXJz0~f^L3xy{T+EoQP z9yfy5Ct;jv9d#EbPPDVT30<8mC>wC~R%Bmdq1jc_j+~rDf@-EOcnP3uH@C8zi{IU7 z0C5m@VZ>Tmuy@}l6W0{0INp8$78Y4hN~2bbPqj{z#e{RKQ+U3>brWEts?giRg1I?6 zTNr<>zI@}G=0bDO+B)vEj=89wkf z^Hm%fOu7-vRy7zz4V=-Gn~$#HQkF}hgHz=1N+mYS)et9InBg^iHW3x`NF&o``$4h) z6}FFgJL&z)x=B~iMrCvkNwEVrr>gFWB~CP*vKs2+XQIKggdYWEo#_&m6Wi@W5dZk@ z{w!j_eh^7F$;<8S;0>(&vT1AOp^(U!z93>j*5+)RK4{x8Eln&aXW5?2nj4CW1W7em zXX_hofN4yVap$kOCkNxJM`@GF@b76m8pBr3zP?asW@kR;JX|iZG8>awiYvgW3xpX? zPd+C>CJU~aY`{|1KJ%SzB7%a}_7aLKL@#qhg_6DelY3}Xn>aU5RIb=}>mm9J5=u^& Tmz64P00000NkvXXu0mjfG<;lH literal 0 HcmV?d00001 From 154652e51d9d7e46e4566a3b8c70b2d2e3cc8122 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Sun, 10 Oct 2021 22:08:20 +0200 Subject: [PATCH 0156/1062] create new app 'score' --- apps.json | 13 ++ apps/score/ChangeLog | 1 + apps/score/score.app-icon.js | 1 + apps/score/score.app.js | 288 +++++++++++++++++++++++++++++++++++ apps/score/score.app.png | Bin 0 -> 897 bytes apps/score/score.settings.js | 81 ++++++++++ 6 files changed, 384 insertions(+) create mode 100644 apps/score/ChangeLog create mode 100644 apps/score/score.app-icon.js create mode 100644 apps/score/score.app.js create mode 100644 apps/score/score.app.png create mode 100644 apps/score/score.settings.js diff --git a/apps.json b/apps.json index 6b0703601..314504276 100644 --- a/apps.json +++ b/apps.json @@ -3554,5 +3554,18 @@ {"name":"floralclk.app.js","url":"app.js"}, {"name":"floralclk.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "score", + "name": "Score Tracker", + "icon": "score.app.png", + "version":"0.01", + "description": "Score Tracker for sports that use plain numbers. (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...)", + "tags": "b2", + "type": "app", + "storage": [ + {"name":"score.app.js","url":"score.app.js"}, + {"name":"score.settings.js","url":"score.settings.js"}, + {"name":"score.img","url":"score.app-icon.js","evaluate":true} + ] } ] diff --git a/apps/score/ChangeLog b/apps/score/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/score/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/score/score.app-icon.js b/apps/score/score.app-icon.js new file mode 100644 index 000000000..b1d4631ba --- /dev/null +++ b/apps/score/score.app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AE2IxAKSCigv/F/4vS44ABB4IECAAoKECgM7AAIJBAgQAFBQguJF6HHEhAvKGAwvy4wPB4wuGBQwdCmgJBmguGBQwvJ0ulF5AKFEgeCwQvIBQqPJ4wuHBQ4lEFw4KHF5IAQFJAALF+vNACYv/F/4v053P64vPxPXAAOJF6vP6wbCF52zCQQAB2YvTDIgvOLoWzMJQvOL6JeCss7spgIF5nPMQgvNCAQEBr4FEd6YvVAowv/F/4v4d9WzCANlndlAgOzF82JFQWJGgWJF8xgDAAReGF8RhDLo4vRABQiHABgv/F/4v/F4owTCgIuZAH4A/AH4A/ADgA==")) diff --git a/apps/score/score.app.js b/apps/score/score.app.js new file mode 100644 index 000000000..7bfadb1ce --- /dev/null +++ b/apps/score/score.app.js @@ -0,0 +1,288 @@ +require('Font5x9Numeric7Seg').add(Graphics); +require('Font7x11Numeric7Seg').add(Graphics); +require('FontTeletext5x9Ascii').add(Graphics); +require('FontTeletext10x18Ascii').add(Graphics); + +let settingsMenu = eval(require('Storage').read('score.settings.js')); +let settings = settingsMenu(null, true); + +let scores = null; +let cSet = null; + +let firstShownSet = null; + +let settingsMenuOpened = null; +let correctionMode = false; + +let w = g.getWidth(); +let h = g.getHeight(); + +function setupInputWatchers() { + if (global.BTN4) { + setWatch(() => handleInput(2), BTN2, { repeat: true }); + setWatch(() => handleInput(3), BTN1, { repeat: true }); + setWatch(() => handleInput(4), BTN3, { repeat: true }); + } else { + setWatch(() => handleInput(2), BTN, { repeat: true }); + } + Bangle.on('touch', (b, e) => { + if (b) { + if (b === 1) { + handleInput(0); + } else { + handleInput(1); + } + } else { + if (e.x < w/2) { + handleInput(0); + } else { + handleInput(1); + } + } + }) +} + +function setupMatch() { + scores = []; + for (let s = 0; s < sets(); s++) { + scores.push([0,0,null]); + } + scores.push([0,0,null]); + + scores[0][2] = getTime(); + + cSet = 0; + firstShownSet = 0 - Math.floor(setsPerPage() / 2); +} + +function showSettingsMenu() { + settingsMenuOpened = getTime(); + settingsMenu(function (s, reset) { + E.showMenu(); + + settings = s; + + if (reset) { + setupMatch(); + } else if (getTime() - settingsMenuOpened < 0.5 || correctionMode) { + correctionMode = !correctionMode; + } + + settingsMenuOpened = null; + + draw(); + }); +} + +function setsPerPage() { + return Math.min(settings.setsPerPage, sets()); +} + +function sets() { + return settings.winSets * 2 - 1; +} + +function currentSet() { + return matchEnded() ? cSet - 1 : cSet; +} + +function formatNumber(num, length) { + return num.toString().padStart(length ? length : 2,"0"); +} + +function formatDuration(duration) { + let durS = Math.floor(duration); + let durM = Math.floor(durS / 60); + let durH = Math.floor(durM / 60); + durS = durS - durM * 60; + durM = durM - durH * 60; + + durS = formatNumber(durS); + durM = formatNumber(durM); + durH = formatNumber(durH); + + let dur = null; + if (durH > 0) { + dur = durH + ':' + durM; + } else { + dur = durM + ':' + durS; + } + + return dur; +} + +function setWon(set, player) { + let pScore = scores[set][player]; + let p2Score = scores[set][~~!player]; + + let winScoreReached = pScore >= settings.winScore; + let isTwoAhead = !settings.enableTwoAhead || pScore - p2Score >= 2; + let reachedMaxScore = settings.enableMaxScore && pScore >= settings.maxScore; + + return reachedMaxScore || (winScoreReached && isTwoAhead); +} + +function setEnded(set) { + return setWon(set, 0) || setWon(set, 1); +} + +function setsWon(player) { + return Array(sets()).fill(0).map((_, s) => ~~setWon(s, player)).reduce((a,v) => a+v, 0); +} + +function matchWon(player) { + return setsWon(player) >= settings.winSets; +} + +function matchEnded() { + return matchWon(0) || matchWon(1); +} + +function matchScore(player) { + return scores.reduce((acc, val) => acc += val[player], 0); +} + +function score(player) { + let updateCurrentSet = function (val) { + cSet += val; + firstShownSet = Math.max(0, currentSet() - settings.setsPerPage + 1); + } + + if (!matchEnded() || correctionMode) { + firstShownSet = currentSet() - Math.floor(setsPerPage() / 2); + } + + if (correctionMode) { + if ( + scores[cSet][0] === 0 && scores[cSet][1] === 0 && + cSet > 0 + ) { + updateCurrentSet(-1); + } + + if (scores[cSet][player] > 0) { + scores[cSet][player]--; + } + } else { + if (matchEnded()) return; + + scores[cSet][player]++; + + if (setEnded(cSet) && cSet < sets()) { + updateCurrentSet(1); + scores[cSet][2] = getTime(); + } + + if (matchEnded()) { + firstShownSet = 0; + } + } +} + +function handleInput(button) { + if (settingsMenuOpened) { + return; + } + + switch (button) { + case 0: + case 1: + score(button); + break; + case 2: + showSettingsMenu(); + return; + case 3: + case 4: + let hLimit = currentSet(); + let lLimit = 1 - setsPerPage(); + let val = (button * 2 - 7); + firstShownSet += val; + if (firstShownSet > hLimit) firstShownSet = hLimit; + if (firstShownSet < lLimit) firstShownSet = lLimit; + break; + } + + draw(); +} + +function draw() { + g.setFontAlign(0,0); + g.clear(); + + for (let p = 0; p < 2; p++) { + if (matchWon(p)) { + g.setFontAlign(0,0); + g.setFont('Teletext10x18Ascii',1); + g.drawString("WINNER", p === 0 ? w/4 : w/4*3, 15); + } else if (matchEnded()) { + g.setFontAlign(0,-1); + + let dur1 = formatDuration(scores[cSet][2] - scores[0][2]); + g.setFont('5x9Numeric7Seg',1); + g.drawString(dur1, p === 0 ? w/8 : w/8*5, 10); + + g.setFont('Teletext5x9Ascii',1); + g.drawString((currentSet()+1) + ' set' + (currentSet() > 1 ? 's' : ''), p === 0 ? w/8*3 : w/8*7, 12); + + } + + g.setFontAlign(p === 0 ? -1 : 1,1); + g.setFont('7x11Numeric7Seg',2); + g.drawString(setsWon(p), p === 0 ? 10 : w-8, h-5); + + g.setFontAlign(p === 0 ? 1 : -1,1); + g.setFont('7x11Numeric7Seg',2); + g.drawString(formatNumber(matchScore(p), 3), p === 0 ? w/2 - 8 : w/2 + 11, h-5); + } + g.setFontAlign(0,0); + + if (correctionMode) { + g.setFont('Teletext10x18Ascii',1); + g.drawString("R", w/2, h-10); + } + + let lastShownSet = Math.min( + sets(), + currentSet() + 1, + firstShownSet+setsPerPage() + ); + let setsOnCurrentPage = Math.min( + sets(), + setsPerPage() + ); + for (let set = firstShownSet; set < lastShownSet; set++) { + if (set < 0) continue; + + let y = (h-15)/(setsOnCurrentPage+1)*(set-firstShownSet+1)+5; + + g.setFontAlign(1,0); + g.setFont('7x11Numeric7Seg',1); + g.drawString(set+1, 40, y-10); + if (scores[set+1][2] != null) { + let dur2 = formatDuration(scores[set+1][2] - scores[set][2]); + g.drawString(dur2, 40, y+10); + } + + g.setFontAlign(0,0); + g.setFont('7x11Numeric7Seg',3); + for (let p = 0; p < 2; p++) { + if (!setWon(set, p === 0 ? 1 : 0) || matchEnded()) { + g.drawString( + formatNumber(scores[set][p]), + p === 0 ? (w-20)/4+20 : (w-20)/4*3+20, + y + ); + } + } + } + + // draw separator + g.drawLine(w/2,20,w/2,h-25); + + g.flip(); +} + +setupInputWatchers(); +setupMatch(); +draw(); diff --git a/apps/score/score.app.png b/apps/score/score.app.png new file mode 100644 index 0000000000000000000000000000000000000000..c1e7e22157fc295811c8b0291dd736d7d1878e5a GIT binary patch literal 897 zcmV-{1AhF8P)-q;>a)qdEv4t%nBoLyDDR~j<9&`&l zbnlXgNO%l7dGZn+>{ierq}uY3iGi7fhrmBe8YsJ!ZRKF5hsneX&? z@7wQvzu)iun+1IM@Zsag;`zG|(=XDKQ*LtI)|9FZ5{3?d@yx}|bz4*5QaZvo_5oOy zko+<=h%Gt*3K0Na?esrO?pyOcToDvOX{aMREff?CXj)klm;(SLM)Dl%$jTK2(7J#y z^yO~k;SR%E0h9SdgF=K%2d(B02Tg-mP_#jFpEUrJd6qArQo9mtxf&DA=4r8L3>7!4 zYT9w>u4LHm0lKe`mM57SG_L|kWOZVz^-?kMX^$|#^ zgzW@QxI(xVp)>ZTop4XqJs`B!xIRIGx}T#ZQ~TIgzz@J{RJ{l6KYO`()yg>E zR8B~V0^f2P0AOxi)5qJI_;Sl1z{-<@_}do^q4IMkUt;=KZ=qx?`uZT>gU_>LRIK37 zyk{j&XUF~?@GJmY;fORBMb-9>SAu5&{M}yfVbd;b3>hJ8$b27Co&~V*tDNH60KwUY zvW;Z3VQ29-UkMyNCEP$dLvf|GeAYj>7DPHj(c5O6{co2kmyTxvj4!IVaZW^gOhmF- zuw?)98pWGszQ>SfOGslt|AdUOc?D|)22}&r3JhcO3i>Bx-v1JuRY0A%?Br*Au%!Z{ zvkFFM6~`L>J>WG?TmUO>iZkH*f6~-p34j27y9tMxh%nLz=B}ndSf3kOK79D_;ST-* XivUhVT==`h00000NkvXXu0mjflmwo` literal 0 HcmV?d00001 diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js new file mode 100644 index 000000000..dde77a225 --- /dev/null +++ b/apps/score/score.settings.js @@ -0,0 +1,81 @@ +(function (back, ret) { + + const fileName = 'score.json' + let settings = require('Storage').readJSON(fileName, 1) || {}; + const offon = ['No', 'Yes']; + + let changed = false; + + function save(key, value) { + changed = true; + settings[key] = value; + if (key === 'winScore' && settings.maxScore < value) { + settings.maxScore = value; + } + require('Storage').writeJSON(fileName, settings); + } + + if (!settings.winSets) { + settings.winSets = 1; + } + if (!settings.winScore) { + settings.winScore = 21; + } + if (!settings.enableTwoAhead) { + settings.enableTwoAhead = true; + } + if (!settings.enableMaxScore) { + settings.enableMaxScore = true; + } + if (!settings.maxScore) { + settings.maxScore = 30; + } + if (!settings.setsPerPage) { + settings.setsPerPage = 5; + } + + if (ret) { + return settings; + } + + const appMenu = {}; + appMenu[''] = {'title': 'Score Settings'}, + appMenu['< Back'] = function () { back(settings, changed); }; + if (reset) { + appMenu['Reset match'] = function () { back(settings, true); }; + } + appMenu['Sets to win'] = { + value: settings.winSets, + min:1, + onchange: m => save('winSets', m) + }; + appMenu['Sets per page'] = { + value: settings.setsPerPage, + min:1, + max:5, + onchange: m => save('setsPerPage', m) + }; + appMenu['Score to win'] = { + value: settings.winScore, + min:1, + onchange: m => save('winScore', m) + }; + appMenu['2-point lead'] = { + value: settings['enableTwoAhead'], + format: m => offon[~~m], + onchange: m => save('enableTwoAhead', m) + }; + appMenu['Maximum score?'] = { + value: settings['enableMaxScore'], + format: m => offon[~~m], + onchange: m => save('enableMaxScore', m) + }; + appMenu['Maximum score'] = { + value: settings.maxScore, + min: settings.winScore, + onchange: m => save('maxScore', m) + }; + + E.showMenu(appMenu) + +}) From 2b612de6f6281651ccc611f77eef9d348880492e Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 03:45:48 +0200 Subject: [PATCH 0157/1062] score: refactor and fix settings --- apps/score/score.settings.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index dde77a225..4843b4a4c 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -15,22 +15,22 @@ require('Storage').writeJSON(fileName, settings); } - if (!settings.winSets) { + if (!settings.winSets == null) { settings.winSets = 1; } - if (!settings.winScore) { + if (!settings.winScore == null) { settings.winScore = 21; } - if (!settings.enableTwoAhead) { + if (!settings.enableTwoAhead == null) { settings.enableTwoAhead = true; } - if (!settings.enableMaxScore) { + if (settings.enableMaxScore == null) { settings.enableMaxScore = true; } - if (!settings.maxScore) { + if (!settings.maxScore == null) { settings.maxScore = 30; } - if (!settings.setsPerPage) { + if (!settings.setsPerPage == null) { settings.setsPerPage = 5; } @@ -61,12 +61,12 @@ onchange: m => save('winScore', m) }; appMenu['2-point lead'] = { - value: settings['enableTwoAhead'], + value: settings.enableTwoAhead, format: m => offon[~~m], onchange: m => save('enableTwoAhead', m) }; appMenu['Maximum score?'] = { - value: settings['enableMaxScore'], + value: settings.enableMaxScore, format: m => offon[~~m], onchange: m => save('enableMaxScore', m) }; From e598c4abf691bdbc052d9464e2419984a21dbe33 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 03:46:03 +0200 Subject: [PATCH 0158/1062] score: fix maxScore --- apps/score/score.app.js | 6 +++++- apps/score/score.settings.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 7bfadb1ce..dbe9f74ef 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -74,6 +74,10 @@ function showSettingsMenu() { }); } +function maxScore() { + return Math.max(settings.maxScore, settings.winScore); +} + function setsPerPage() { return Math.min(settings.setsPerPage, sets()); } @@ -117,7 +121,7 @@ function setWon(set, player) { let winScoreReached = pScore >= settings.winScore; let isTwoAhead = !settings.enableTwoAhead || pScore - p2Score >= 2; - let reachedMaxScore = settings.enableMaxScore && pScore >= settings.maxScore; + let reachedMaxScore = settings.enableMaxScore && pScore >= maxScore(); return reachedMaxScore || (winScoreReached && isTwoAhead); } diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index 4843b4a4c..d1a4332fd 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -72,7 +72,7 @@ }; appMenu['Maximum score'] = { value: settings.maxScore, - min: settings.winScore, + min: 1, onchange: m => save('maxScore', m) }; From 7a68984626ce8eace5e55d9a5be86fa30a3b9872 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 03:40:55 +0200 Subject: [PATCH 0159/1062] score: fix set/sets display --- apps/score/score.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index dbe9f74ef..7e02f8dfe 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -227,7 +227,7 @@ function draw() { g.drawString(dur1, p === 0 ? w/8 : w/8*5, 10); g.setFont('Teletext5x9Ascii',1); - g.drawString((currentSet()+1) + ' set' + (currentSet() > 1 ? 's' : ''), p === 0 ? w/8*3 : w/8*7, 12); + g.drawString((currentSet()+1) + ' set' + (currentSet() > 0 ? 's' : ''), p === 0 ? w/8*3 : w/8*7, 12); } From dcd15516f14696c9cff94b09bcf019b7f7a14a32 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 03:41:15 +0200 Subject: [PATCH 0160/1062] score: add tennis scoring --- apps.json | 2 +- apps/score/score.app.js | 66 ++++++++++++++++++++++++++++++------ apps/score/score.settings.js | 8 +++++ 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/apps.json b/apps.json index 314504276..c98686e72 100644 --- a/apps.json +++ b/apps.json @@ -3559,7 +3559,7 @@ "name": "Score Tracker", "icon": "score.app.png", "version":"0.01", - "description": "Score Tracker for sports that use plain numbers. (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...)", + "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.", "tags": "b2", "type": "app", "storage": [ diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 7e02f8dfe..11d158440 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -6,7 +6,10 @@ require('FontTeletext10x18Ascii').add(Graphics); let settingsMenu = eval(require('Storage').read('score.settings.js')); let settings = settingsMenu(null, true); +let tennisScores = ['00','15','30','40','DC','AD'] + let scores = null; +let tScores = null; let cSet = null; let firstShownSet = null; @@ -49,6 +52,12 @@ function setupMatch() { } scores.push([0,0,null]); + if (settings.enableTennisScoring) { + tScores = [0,0]; + } else { + tScores = null; + } + scores[0][2] = getTime(); cSet = 0; @@ -170,7 +179,22 @@ function score(player) { } else { if (matchEnded()) return; - scores[cSet][player]++; + if (settings.enableTennisScoring) { + if (tScores[player] === 4 && tScores[~~!player] === 5) { // DC : AD + tScores[~~!player]--; + } else if (tScores[player] === 2 && tScores[~~!player] === 3) { // 30 : 40 + tScores[0] = 4; + tScores[1] = 4; + } else if (tScores[player] === 3 || tScores[player] === 5) { // 40 / AD + tScores[0] = 0; + tScores[1] = 0; + scores[cSet][player]++; + } else { + tScores[player]++; + } + } else { + scores[cSet][player]++; + } if (setEnded(cSet) && cSet < sets()) { updateCurrentSet(1); @@ -235,9 +259,11 @@ function draw() { g.setFont('7x11Numeric7Seg',2); g.drawString(setsWon(p), p === 0 ? 10 : w-8, h-5); - g.setFontAlign(p === 0 ? 1 : -1,1); - g.setFont('7x11Numeric7Seg',2); - g.drawString(formatNumber(matchScore(p), 3), p === 0 ? w/2 - 8 : w/2 + 11, h-5); + if (!settings.enableTennisScoring) { + g.setFontAlign(p === 0 ? 1 : -1,1); + g.setFont('7x11Numeric7Seg',2); + g.drawString(formatNumber(matchScore(p), 3), p === 0 ? w/2 - 8 : w/2 + 11, h-5); + } } g.setFontAlign(0,0); @@ -268,15 +294,33 @@ function draw() { g.drawString(dur2, 40, y+10); } - g.setFontAlign(0,0); - g.setFont('7x11Numeric7Seg',3); for (let p = 0; p < 2; p++) { if (!setWon(set, p === 0 ? 1 : 0) || matchEnded()) { - g.drawString( - formatNumber(scores[set][p]), - p === 0 ? (w-20)/4+20 : (w-20)/4*3+20, - y - ); + if (settings.enableTennisScoring && set === cSet) { + g.setFontAlign(0,0); + g.setFont('7x11Numeric7Seg',3); + g.drawString( + formatNumber(tennisScores[tScores[p]]), + p === 0 ? (w-20)/4+20 : (w-20)/4*3+2, + y + ); + + g.setFontAlign(p === 0 ? 1 : -1,0); + g.setFont('7x11Numeric7Seg',1); + g.drawString( + formatNumber(scores[set][p]), + p === 0 ? w/2-5 : w/2+6, + y + ); + } else { + g.setFontAlign(0,0); + g.setFont('7x11Numeric7Seg',3); + g.drawString( + formatNumber(scores[set][p]), + p === 0 ? (w-20)/4+20 : (w-20)/4*3+2, + y + ); + } } } } diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index d1a4332fd..7d058f07f 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -33,6 +33,9 @@ if (!settings.setsPerPage == null) { settings.setsPerPage = 5; } + if (!settings.enableTennisScoring == null) { + settings.enableTennisScoring = false; + } if (ret) { return settings; @@ -75,6 +78,11 @@ min: 1, onchange: m => save('maxScore', m) }; + appMenu['Tennis scoring'] = { + value: settings.enableTennisScoring, + format: m => offon[~~m], + onchange: m => save('enableTennisScoring', m) + }; E.showMenu(appMenu) From a6a5f674a090473e1396647ea19e4e2a3f2c29d2 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 03:59:24 +0200 Subject: [PATCH 0161/1062] score: fix commit 58c7b6c4 --- apps/score/score.settings.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index 7d058f07f..ab8b8fb85 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -15,25 +15,25 @@ require('Storage').writeJSON(fileName, settings); } - if (!settings.winSets == null) { + if (settings.winSets == null) { settings.winSets = 1; } - if (!settings.winScore == null) { + if (settings.winScore == null) { settings.winScore = 21; } - if (!settings.enableTwoAhead == null) { + if (settings.enableTwoAhead == null) { settings.enableTwoAhead = true; } if (settings.enableMaxScore == null) { settings.enableMaxScore = true; } - if (!settings.maxScore == null) { + if (settings.maxScore == null) { settings.maxScore = 30; } - if (!settings.setsPerPage == null) { + if (settings.setsPerPage == null) { settings.setsPerPage = 5; } - if (!settings.enableTennisScoring == null) { + if (settings.enableTennisScoring == null) { settings.enableTennisScoring = false; } From a441d423a417538969d8fd6b39c73e0043acddfe Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 04:27:27 +0200 Subject: [PATCH 0162/1062] score: refactor --- apps/score/score.app.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 11d158440..2052df226 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -6,7 +6,7 @@ require('FontTeletext10x18Ascii').add(Graphics); let settingsMenu = eval(require('Storage').read('score.settings.js')); let settings = settingsMenu(null, true); -let tennisScores = ['00','15','30','40','DC','AD'] +let tennisScores = ['00','15','30','40','DC','AD']; let scores = null; let tScores = null; @@ -42,7 +42,7 @@ function setupInputWatchers() { handleInput(1); } } - }) + }); } function setupMatch() { @@ -159,17 +159,14 @@ function score(player) { let updateCurrentSet = function (val) { cSet += val; firstShownSet = Math.max(0, currentSet() - settings.setsPerPage + 1); - } + }; if (!matchEnded() || correctionMode) { firstShownSet = currentSet() - Math.floor(setsPerPage() / 2); } if (correctionMode) { - if ( - scores[cSet][0] === 0 && scores[cSet][1] === 0 && - cSet > 0 - ) { + if (scores[cSet][0] === 0 && scores[cSet][1] === 0 && cSet > 0) { updateCurrentSet(-1); } From 4c6e9a086049d1250f49cc499df99c2c807b54cd Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 04:34:30 +0200 Subject: [PATCH 0163/1062] score: tennis scoring: allow corrections --- apps/score/score.app.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 2052df226..636d3a97e 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -171,7 +171,12 @@ function score(player) { } if (scores[cSet][player] > 0) { - scores[cSet][player]--; + if (tScores[player] === 0 && tScores[~~!player] === 0) { + scores[cSet][player]--; + } else { + tScores[player] = 0; + tScores[~~!player] = 0; + } } } else { if (matchEnded()) return; From c45781bc8b536c91718ecb67ef25d94cbaaa90c9 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 04:42:35 +0200 Subject: [PATCH 0164/1062] score: add README --- apps/score/README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 apps/score/README.md diff --git a/apps/score/README.md b/apps/score/README.md new file mode 100644 index 000000000..cdb34902c --- /dev/null +++ b/apps/score/README.md @@ -0,0 +1,37 @@ +This app will allow you to keep scores for most kinds of sports. + +# Keybinds +*On Bangle.js 2 BTN is equivalent to BTN2 on Bangle.js 1* +| Keybinding | Description | +|-------------------|------------------------------| +| `BTN1` | Scroll up | +| `BTN3` | Scroll down | +| `BTN2` | Menu | +| tap on left side | increment left player score | +| tap on right side | increment right player score | + +To correct a falsely awarded point simply open and close the menu within .5 seconds. This will put the app into correction mode (indicated by the `R`). +In this mode any score increments will be decrements. To move back a set, reduce both players scores to 0, then decrement one of the scores once again. + +# Settings +| Setting | Description | +|----------------|------------------------------------------------------------------------------------------------------------------------------| +| Sets to win | How many sets a player has to win before the match is won (Maximum sets: this*2-1) | +| Sets per page | How many sets should be shown in the app. Further sets will be available by scrolling (ignored if higher than `Sets to win`) | +| Score to win | What score ends a given set | +| 2-point lead | Does winning a set require a two-point lead | +| Maximum score? | Should there be a maximum score, at which point the two-point lead rule falls away | +| Maximum score | At which score should the two-point lead rule fall away (ignored if lower than Sets to win) | +| Tennis scoring | If enabled, each point in a set will require a full tennis game | + +The settings can be changed both from within the app by simply pressing `BTN2` (`BTN1` on Bangle.js 2) or in the `App Settings` in the `Settings` app. + +If changes are made to the settings from within the app, a new match will automatically be initialized upon exiting the settings. + +By default the settings will reflect Badminton rules. + +## Tennis Scoring +While tennis scoring is available, correcting in this mode will reset to the beginning of the current game. +Resetting at the beginning of the current game will reset to the beginning of the previous game, leaving the user to fast-forward to the correct score once again. + +This might get changed at some point. From 97166e0d1a876c8ddbea00b12afcd7cd99b2bde1 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 13:45:23 +0200 Subject: [PATCH 0165/1062] score: refactor settings --- apps/score/score.settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index ab8b8fb85..b7a2fefad 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -18,6 +18,9 @@ if (settings.winSets == null) { settings.winSets = 1; } + if (settings.setsPerPage == null) { + settings.setsPerPage = 5; + } if (settings.winScore == null) { settings.winScore = 21; } @@ -30,9 +33,6 @@ if (settings.maxScore == null) { settings.maxScore = 30; } - if (settings.setsPerPage == null) { - settings.setsPerPage = 5; - } if (settings.enableTennisScoring == null) { settings.enableTennisScoring = false; } From c0a18add642da63d9afd74b740e06474587f4509 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 13:47:22 +0200 Subject: [PATCH 0166/1062] score: implement tie-breaks instead of fixed max scores --- apps/score/score.app.js | 86 ++++++++++++++++++++++++++++++------ apps/score/score.settings.js | 39 ++++++++++++++++ 2 files changed, 111 insertions(+), 14 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 636d3a97e..2566d6922 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -48,9 +48,9 @@ function setupInputWatchers() { function setupMatch() { scores = []; for (let s = 0; s < sets(); s++) { - scores.push([0,0,null]); + scores.push([0,0,null,0,0]); } - scores.push([0,0,null]); + scores.push([0,0,null,0,0]); if (settings.enableTennisScoring) { tScores = [0,0]; @@ -87,6 +87,10 @@ function maxScore() { return Math.max(settings.maxScore, settings.winScore); } +function tiebreakMaxScore() { + return Math.max(settings.maxScoreTiebreakMaxScore, settings.maxScoreTiebreakWinScore); +} + function setsPerPage() { return Math.min(settings.setsPerPage, sets()); } @@ -99,6 +103,11 @@ function currentSet() { return matchEnded() ? cSet - 1 : cSet; } +function shouldTiebreak() { + return settings.enableMaxScoreTiebreak && + scores[cSet][0] + scores[cSet][1] === (maxScore() - 1) * 2; +} + function formatNumber(num, length) { return num.toString().padStart(length ? length : 2,"0"); } @@ -124,15 +133,30 @@ function formatDuration(duration) { return dur; } +function tiebreakWon(set, player) { + let pScore = scores[set][3+player]; + let p2Score = scores[set][3+~~!player]; + + let winScoreReached = pScore >= settings.maxScoreTiebreakWinScore; + let isTwoAhead = !settings.maxScoreTiebreakEnableTwoAhead || pScore - p2Score >= 2; + let reachedMaxScore = settings.maxScoreTiebreakEnableMaxScore && pScore >= tiebreakMaxScore(); + + return reachedMaxScore || (winScoreReached && isTwoAhead); +} + function setWon(set, player) { let pScore = scores[set][player]; let p2Score = scores[set][~~!player]; let winScoreReached = pScore >= settings.winScore; let isTwoAhead = !settings.enableTwoAhead || pScore - p2Score >= 2; + let tiebreakW = tiebreakWon(set, player); let reachedMaxScore = settings.enableMaxScore && pScore >= maxScore(); - return reachedMaxScore || (winScoreReached && isTwoAhead); + return ( + (settings.enableMaxScoreTiebreak ? tiebreakW : reachedMaxScore) || + (winScoreReached && isTwoAhead) + ); } function setEnded(set) { @@ -166,11 +190,19 @@ function score(player) { } if (correctionMode) { - if (scores[cSet][0] === 0 && scores[cSet][1] === 0 && cSet > 0) { + if ( + scores[cSet][0] === 0 && scores[cSet][1] === 0 && + scores[cSet][3] === 0 && scores[cSet][4] === 0 && + cSet > 0 + ) { updateCurrentSet(-1); } - if (scores[cSet][player] > 0) { + if (scores[cSet][3] > 0 || scores[cSet][4] > 0) { + if (scores[cSet][3+player] > 0) { + scores[cSet][3+player]--; + } + } else if (scores[cSet][player] > 0) { if (tScores[player] === 0 && tScores[~~!player] === 0) { scores[cSet][player]--; } else { @@ -181,7 +213,9 @@ function score(player) { } else { if (matchEnded()) return; - if (settings.enableTennisScoring) { + if (shouldTiebreak()) { + scores[cSet][3+player]++; + } else if (settings.enableTennisScoring) { if (tScores[player] === 4 && tScores[~~!player] === 5) { // DC : AD tScores[~~!player]--; } else if (tScores[player] === 2 && tScores[~~!player] === 3) { // 30 : 40 @@ -199,6 +233,9 @@ function score(player) { } if (setEnded(cSet) && cSet < sets()) { + if (shouldTiebreak()) { + scores[cSet][player]++; + } updateCurrentSet(1); scores[cSet][2] = getTime(); } @@ -298,20 +335,23 @@ function draw() { for (let p = 0; p < 2; p++) { if (!setWon(set, p === 0 ? 1 : 0) || matchEnded()) { - if (settings.enableTennisScoring && set === cSet) { + let bigNumX = p === 0 ? (w-20)/4+18 : (w-20)/4*3+4; + let smallNumX = p === 0 ? w/2-2 : w/2+3; + + if (settings.enableTennisScoring && set === cSet && !shouldTiebreak()) { g.setFontAlign(0,0); g.setFont('7x11Numeric7Seg',3); g.drawString( formatNumber(tennisScores[tScores[p]]), - p === 0 ? (w-20)/4+20 : (w-20)/4*3+2, + bigNumX, y ); - - g.setFontAlign(p === 0 ? 1 : -1,0); - g.setFont('7x11Numeric7Seg',1); + } else if (shouldTiebreak() && set === cSet) { + g.setFontAlign(0,0); + g.setFont('7x11Numeric7Seg',3); g.drawString( - formatNumber(scores[set][p]), - p === 0 ? w/2-5 : w/2+6, + formatNumber(scores[set][3+p], 3), + bigNumX, y ); } else { @@ -319,7 +359,25 @@ function draw() { g.setFont('7x11Numeric7Seg',3); g.drawString( formatNumber(scores[set][p]), - p === 0 ? (w-20)/4+20 : (w-20)/4*3+2, + bigNumX, + y + ); + } + + if ((shouldTiebreak() || settings.enableTennisScoring) && set === cSet) { + g.setFontAlign(p === 0 ? 1 : -1,0); + g.setFont('7x11Numeric7Seg',1); + g.drawString( + formatNumber(scores[set][p]), + smallNumX, + y + ); + } else if ((scores[set][3] !== 0 || scores[set][4] !== 0) && set !== cSet) { + g.setFontAlign(p === 0 ? 1 : -1,0); + g.setFont('7x11Numeric7Seg',1); + g.drawString( + formatNumber(scores[set][3+p], 3), + smallNumX, y ); } diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index b7a2fefad..c1a0382d2 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -37,6 +37,22 @@ settings.enableTennisScoring = false; } + if (settings.enableMaxScoreTiebreak == null) { + settings.enableMaxScoreTiebreak = false; + } + if (settings.maxScoreTiebreakWinScore == null) { + settings.maxScoreTiebreakWinScore = 6; + } + if (settings.maxScoreTiebreakEnableTwoAhead == null) { + settings.maxScoreTiebreakEnableTwoAhead = true; + } + if (settings.maxScoreTiebreakEnableMaxScore == null) { + settings.maxScoreTiebreakEnableMaxScore = false; + } + if (settings.maxScoreTiebreakMaxScore == null) { + settings.maxScoreTiebreakMaxScore = 15; + } + if (ret) { return settings; } @@ -83,6 +99,29 @@ format: m => offon[~~m], onchange: m => save('enableTennisScoring', m) }; + appMenu['TB sets?'] = { + value: settings.enableMaxScoreTiebreak, + format: m => offon[~~m], + onchange: m => save('enableMaxScoreTiebreak', m) + } + appMenu['TB Score to win'] = { + value: settings.maxScoreTiebreakWinScore, + onchange: m => save('maxScoreTiebreakWinScore', m) + } + appMenu['TB 2-point lead'] = { + value: settings.maxScoreTiebreakEnableTwoAhead, + format: m => offon[~~m], + onchange: m => save('maxScoreTiebreakEnableTwoAhead', m) + } + appMenu['TB max score?'] = { + value: settings.maxScoreTiebreakEnableMaxScore, + format: m => offon[~~m], + onchange: m => save('maxScoreTiebreakEnableMaxScore', m) + } + appMenu['TB max score'] = { + value: settings.maxScoreTiebreakMaxScore, + onchange: m => save('maxScoreTiebreakMaxScore', m) + } E.showMenu(appMenu) From de2e897119b4644fd2744ca35ff4644a79e0e391 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 13:47:49 +0200 Subject: [PATCH 0167/1062] score: always disable correction mode on new match --- apps/score/score.app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 2566d6922..053a8df43 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -62,6 +62,8 @@ function setupMatch() { cSet = 0; firstShownSet = 0 - Math.floor(setsPerPage() / 2); + + correctionMode = false; } function showSettingsMenu() { From 64930265d7cf00a032894e239c65890a007f2948 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 13:48:12 +0200 Subject: [PATCH 0168/1062] score: fix automatic scrolling on score --- apps/score/score.app.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 053a8df43..9f22927af 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -182,13 +182,16 @@ function matchScore(player) { } function score(player) { + let setFirstShownSet = function () { + firstShownSet = currentSet() - Math.floor(setsPerPage() / 2); + }; let updateCurrentSet = function (val) { cSet += val; - firstShownSet = Math.max(0, currentSet() - settings.setsPerPage + 1); + setFirstShownSet(); }; if (!matchEnded() || correctionMode) { - firstShownSet = currentSet() - Math.floor(setsPerPage() / 2); + setFirstShownSet(); } if (correctionMode) { From 1983c37a819005179be5cc9b0cd410d272271973 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 13:53:24 +0200 Subject: [PATCH 0169/1062] score: document tiebreaks --- apps/score/README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/score/README.md b/apps/score/README.md index cdb34902c..ffd35e065 100644 --- a/apps/score/README.md +++ b/apps/score/README.md @@ -14,15 +14,17 @@ To correct a falsely awarded point simply open and close the menu within .5 seco In this mode any score increments will be decrements. To move back a set, reduce both players scores to 0, then decrement one of the scores once again. # Settings -| Setting | Description | -|----------------|------------------------------------------------------------------------------------------------------------------------------| -| Sets to win | How many sets a player has to win before the match is won (Maximum sets: this*2-1) | -| Sets per page | How many sets should be shown in the app. Further sets will be available by scrolling (ignored if higher than `Sets to win`) | -| Score to win | What score ends a given set | -| 2-point lead | Does winning a set require a two-point lead | -| Maximum score? | Should there be a maximum score, at which point the two-point lead rule falls away | -| Maximum score | At which score should the two-point lead rule fall away (ignored if lower than Sets to win) | -| Tennis scoring | If enabled, each point in a set will require a full tennis game | +| Setting | Description | +|------------------------------------|------------------------------------------------------------------------------------------------------------------------------| +| `Sets to win` | How many sets a player has to win before the match is won (Maximum sets: this*2-1) | +| `Sets per page` | How many sets should be shown in the app. Further sets will be available by scrolling (ignored if higher than `Sets to win`) | +| `Score to win` | What score ends a given set | +| `2-point lead` | Does winning a set require a two-point lead | +| `Maximum score?` | Should there be a maximum score, at which point the two-point lead rule falls away | +| `Maximum score` | At which score should the two-point lead rule fall away (ignored if lower than Sets to win) | +| `Tennis scoring` | If enabled, each point in a set will require a full tennis game | +| `TB sets?` | Should sets that have reached `(maxScore-1):(maxScore-1)` be decided with a tiebreak | +| All other options starting with TB | Equivalent to option with same name but applied to tiebreaks | The settings can be changed both from within the app by simply pressing `BTN2` (`BTN1` on Bangle.js 2) or in the `App Settings` in the `Settings` app. From 506918e77239cd67a7a78865670d82cc310473a3 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Mon, 27 Sep 2021 23:04:22 +0200 Subject: [PATCH 0170/1062] score: fix code style (indent by 2 spaces) --- apps/score/score.app.js | 554 +++++++++++++++++------------------ apps/score/score.settings.js | 232 +++++++-------- 2 files changed, 393 insertions(+), 393 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 9f22927af..7f0b04a59 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -21,93 +21,93 @@ let w = g.getWidth(); let h = g.getHeight(); function setupInputWatchers() { - if (global.BTN4) { - setWatch(() => handleInput(2), BTN2, { repeat: true }); - setWatch(() => handleInput(3), BTN1, { repeat: true }); - setWatch(() => handleInput(4), BTN3, { repeat: true }); + if (global.BTN4) { + setWatch(() => handleInput(2), BTN2, { repeat: true }); + setWatch(() => handleInput(3), BTN1, { repeat: true }); + setWatch(() => handleInput(4), BTN3, { repeat: true }); + } else { + setWatch(() => handleInput(2), BTN, { repeat: true }); + } + Bangle.on('touch', (b, e) => { + if (b) { + if (b === 1) { + handleInput(0); + } else { + handleInput(1); + } } else { - setWatch(() => handleInput(2), BTN, { repeat: true }); + if (e.x < w/2) { + handleInput(0); + } else { + handleInput(1); + } } - Bangle.on('touch', (b, e) => { - if (b) { - if (b === 1) { - handleInput(0); - } else { - handleInput(1); - } - } else { - if (e.x < w/2) { - handleInput(0); - } else { - handleInput(1); - } - } - }); + }); } function setupMatch() { - scores = []; - for (let s = 0; s < sets(); s++) { - scores.push([0,0,null,0,0]); - } + scores = []; + for (let s = 0; s < sets(); s++) { scores.push([0,0,null,0,0]); + } + scores.push([0,0,null,0,0]); - if (settings.enableTennisScoring) { - tScores = [0,0]; - } else { - tScores = null; - } + if (settings.enableTennisScoring) { + tScores = [0,0]; + } else { + tScores = null; + } - scores[0][2] = getTime(); + scores[0][2] = getTime(); - cSet = 0; - firstShownSet = 0 - Math.floor(setsPerPage() / 2); + cSet = 0; + firstShownSet = 0 - Math.floor(setsPerPage() / 2); - correctionMode = false; + correctionMode = false; } function showSettingsMenu() { - settingsMenuOpened = getTime(); - settingsMenu(function (s, reset) { - E.showMenu(); + settingsMenuOpened = getTime(); + settingsMenu(function (s, reset) { + E.showMenu(); - settings = s; + settings = s; - if (reset) { - setupMatch(); - } else if (getTime() - settingsMenuOpened < 0.5 || correctionMode) { - correctionMode = !correctionMode; - } + if (reset) { + setupMatch(); + } else if (getTime() - settingsMenuOpened < 0.5 || correctionMode) { + correctionMode = !correctionMode; + } - settingsMenuOpened = null; + settingsMenuOpened = null; - draw(); - }); + draw(); + }); } function maxScore() { - return Math.max(settings.maxScore, settings.winScore); + return Math.max(settings.maxScore, settings.winScore); } function tiebreakMaxScore() { - return Math.max(settings.maxScoreTiebreakMaxScore, settings.maxScoreTiebreakWinScore); + return Math.max(settings.maxScoreTiebreakMaxScore, settings.maxScoreTiebreakWinScore); } function setsPerPage() { - return Math.min(settings.setsPerPage, sets()); + return Math.min(settings.setsPerPage, sets()); } function sets() { - return settings.winSets * 2 - 1; + return settings.winSets * 2 - 1; } function currentSet() { - return matchEnded() ? cSet - 1 : cSet; + return matchEnded() ? cSet - 1 : cSet; } function shouldTiebreak() { - return settings.enableMaxScoreTiebreak && - scores[cSet][0] + scores[cSet][1] === (maxScore() - 1) * 2; + return settings.enableMaxScoreTiebreak && + scores[cSet][0] + scores[cSet][1] === (maxScore() - 1) * 2; } function formatNumber(num, length) { @@ -115,285 +115,285 @@ function formatNumber(num, length) { } function formatDuration(duration) { - let durS = Math.floor(duration); - let durM = Math.floor(durS / 60); - let durH = Math.floor(durM / 60); - durS = durS - durM * 60; - durM = durM - durH * 60; + let durS = Math.floor(duration); + let durM = Math.floor(durS / 60); + let durH = Math.floor(durM / 60); + durS = durS - durM * 60; + durM = durM - durH * 60; - durS = formatNumber(durS); - durM = formatNumber(durM); - durH = formatNumber(durH); + durS = formatNumber(durS); + durM = formatNumber(durM); + durH = formatNumber(durH); - let dur = null; - if (durH > 0) { - dur = durH + ':' + durM; - } else { - dur = durM + ':' + durS; - } + let dur = null; + if (durH > 0) { + dur = durH + ':' + durM; + } else { + dur = durM + ':' + durS; + } - return dur; + return dur; } function tiebreakWon(set, player) { - let pScore = scores[set][3+player]; - let p2Score = scores[set][3+~~!player]; + let pScore = scores[set][3+player]; + let p2Score = scores[set][3+~~!player]; - let winScoreReached = pScore >= settings.maxScoreTiebreakWinScore; - let isTwoAhead = !settings.maxScoreTiebreakEnableTwoAhead || pScore - p2Score >= 2; - let reachedMaxScore = settings.maxScoreTiebreakEnableMaxScore && pScore >= tiebreakMaxScore(); + let winScoreReached = pScore >= settings.maxScoreTiebreakWinScore; + let isTwoAhead = !settings.maxScoreTiebreakEnableTwoAhead || pScore - p2Score >= 2; + let reachedMaxScore = settings.maxScoreTiebreakEnableMaxScore && pScore >= tiebreakMaxScore(); - return reachedMaxScore || (winScoreReached && isTwoAhead); + return reachedMaxScore || (winScoreReached && isTwoAhead); } function setWon(set, player) { - let pScore = scores[set][player]; - let p2Score = scores[set][~~!player]; + let pScore = scores[set][player]; + let p2Score = scores[set][~~!player]; - let winScoreReached = pScore >= settings.winScore; - let isTwoAhead = !settings.enableTwoAhead || pScore - p2Score >= 2; - let tiebreakW = tiebreakWon(set, player); - let reachedMaxScore = settings.enableMaxScore && pScore >= maxScore(); + let winScoreReached = pScore >= settings.winScore; + let isTwoAhead = !settings.enableTwoAhead || pScore - p2Score >= 2; + let tiebreakW = tiebreakWon(set, player); + let reachedMaxScore = settings.enableMaxScore && pScore >= maxScore(); - return ( - (settings.enableMaxScoreTiebreak ? tiebreakW : reachedMaxScore) || - (winScoreReached && isTwoAhead) - ); + return ( + (settings.enableMaxScoreTiebreak ? tiebreakW : reachedMaxScore) || + (winScoreReached && isTwoAhead) + ); } function setEnded(set) { - return setWon(set, 0) || setWon(set, 1); + return setWon(set, 0) || setWon(set, 1); } function setsWon(player) { - return Array(sets()).fill(0).map((_, s) => ~~setWon(s, player)).reduce((a,v) => a+v, 0); + return Array(sets()).fill(0).map((_, s) => ~~setWon(s, player)).reduce((a,v) => a+v, 0); } function matchWon(player) { - return setsWon(player) >= settings.winSets; + return setsWon(player) >= settings.winSets; } function matchEnded() { - return matchWon(0) || matchWon(1); + return matchWon(0) || matchWon(1); } function matchScore(player) { - return scores.reduce((acc, val) => acc += val[player], 0); + return scores.reduce((acc, val) => acc += val[player], 0); } function score(player) { - let setFirstShownSet = function () { - firstShownSet = currentSet() - Math.floor(setsPerPage() / 2); - }; - let updateCurrentSet = function (val) { - cSet += val; - setFirstShownSet(); - }; + let setFirstShownSet = function () { + firstShownSet = currentSet() - Math.floor(setsPerPage() / 2); + }; + let updateCurrentSet = function (val) { + cSet += val; + setFirstShownSet(); + }; - if (!matchEnded() || correctionMode) { - setFirstShownSet(); + if (!matchEnded() || correctionMode) { + setFirstShownSet(); + } + + if (correctionMode) { + if ( + scores[cSet][0] === 0 && scores[cSet][1] === 0 && + scores[cSet][3] === 0 && scores[cSet][4] === 0 && + cSet > 0 + ) { + updateCurrentSet(-1); } - if (correctionMode) { - if ( - scores[cSet][0] === 0 && scores[cSet][1] === 0 && - scores[cSet][3] === 0 && scores[cSet][4] === 0 && - cSet > 0 - ) { - updateCurrentSet(-1); - } + if (scores[cSet][3] > 0 || scores[cSet][4] > 0) { + if (scores[cSet][3+player] > 0) { + scores[cSet][3+player]--; + } + } else if (scores[cSet][player] > 0) { + if (tScores[player] === 0 && tScores[~~!player] === 0) { + scores[cSet][player]--; + } else { + tScores[player] = 0; + tScores[~~!player] = 0; + } + } + } else { + if (matchEnded()) return; - if (scores[cSet][3] > 0 || scores[cSet][4] > 0) { - if (scores[cSet][3+player] > 0) { - scores[cSet][3+player]--; - } - } else if (scores[cSet][player] > 0) { - if (tScores[player] === 0 && tScores[~~!player] === 0) { - scores[cSet][player]--; - } else { - tScores[player] = 0; - tScores[~~!player] = 0; - } - } + if (shouldTiebreak()) { + scores[cSet][3+player]++; + } else if (settings.enableTennisScoring) { + if (tScores[player] === 4 && tScores[~~!player] === 5) { // DC : AD + tScores[~~!player]--; + } else if (tScores[player] === 2 && tScores[~~!player] === 3) { // 30 : 40 + tScores[0] = 4; + tScores[1] = 4; + } else if (tScores[player] === 3 || tScores[player] === 5) { // 40 / AD + tScores[0] = 0; + tScores[1] = 0; + scores[cSet][player]++; + } else { + tScores[player]++; + } } else { - if (matchEnded()) return; - - if (shouldTiebreak()) { - scores[cSet][3+player]++; - } else if (settings.enableTennisScoring) { - if (tScores[player] === 4 && tScores[~~!player] === 5) { // DC : AD - tScores[~~!player]--; - } else if (tScores[player] === 2 && tScores[~~!player] === 3) { // 30 : 40 - tScores[0] = 4; - tScores[1] = 4; - } else if (tScores[player] === 3 || tScores[player] === 5) { // 40 / AD - tScores[0] = 0; - tScores[1] = 0; - scores[cSet][player]++; - } else { - tScores[player]++; - } - } else { - scores[cSet][player]++; - } - - if (setEnded(cSet) && cSet < sets()) { - if (shouldTiebreak()) { - scores[cSet][player]++; - } - updateCurrentSet(1); - scores[cSet][2] = getTime(); - } - - if (matchEnded()) { - firstShownSet = 0; - } + scores[cSet][player]++; } + + if (setEnded(cSet) && cSet < sets()) { + if (shouldTiebreak()) { + scores[cSet][player]++; + } + updateCurrentSet(1); + scores[cSet][2] = getTime(); + } + + if (matchEnded()) { + firstShownSet = 0; + } + } } function handleInput(button) { - if (settingsMenuOpened) { + if (settingsMenuOpened) { + return; + } + + switch (button) { + case 0: + case 1: + score(button); + break; + case 2: + showSettingsMenu(); return; - } + case 3: + case 4: + let hLimit = currentSet(); + let lLimit = 1 - setsPerPage(); + let val = (button * 2 - 7); + firstShownSet += val; + if (firstShownSet > hLimit) firstShownSet = hLimit; + if (firstShownSet < lLimit) firstShownSet = lLimit; + break; + } - switch (button) { - case 0: - case 1: - score(button); - break; - case 2: - showSettingsMenu(); - return; - case 3: - case 4: - let hLimit = currentSet(); - let lLimit = 1 - setsPerPage(); - let val = (button * 2 - 7); - firstShownSet += val; - if (firstShownSet > hLimit) firstShownSet = hLimit; - if (firstShownSet < lLimit) firstShownSet = lLimit; - break; - } - - draw(); + draw(); } function draw() { - g.setFontAlign(0,0); - g.clear(); + g.setFontAlign(0,0); + g.clear(); + + for (let p = 0; p < 2; p++) { + if (matchWon(p)) { + g.setFontAlign(0,0); + g.setFont('Teletext10x18Ascii',1); + g.drawString("WINNER", p === 0 ? w/4 : w/4*3, 15); + } else if (matchEnded()) { + g.setFontAlign(0,-1); + + let dur1 = formatDuration(scores[cSet][2] - scores[0][2]); + g.setFont('5x9Numeric7Seg',1); + g.drawString(dur1, p === 0 ? w/8 : w/8*5, 10); + + g.setFont('Teletext5x9Ascii',1); + g.drawString((currentSet()+1) + ' set' + (currentSet() > 0 ? 's' : ''), p === 0 ? w/8*3 : w/8*7, 12); + + } + + g.setFontAlign(p === 0 ? -1 : 1,1); + g.setFont('7x11Numeric7Seg',2); + g.drawString(setsWon(p), p === 0 ? 10 : w-8, h-5); + + if (!settings.enableTennisScoring) { + g.setFontAlign(p === 0 ? 1 : -1,1); + g.setFont('7x11Numeric7Seg',2); + g.drawString(formatNumber(matchScore(p), 3), p === 0 ? w/2 - 8 : w/2 + 11, h-5); + } + } + g.setFontAlign(0,0); + + if (correctionMode) { + g.setFont('Teletext10x18Ascii',1); + g.drawString("R", w/2, h-10); + } + + let lastShownSet = Math.min( + sets(), + currentSet() + 1, + firstShownSet+setsPerPage() + ); + let setsOnCurrentPage = Math.min( + sets(), + setsPerPage() + ); + for (let set = firstShownSet; set < lastShownSet; set++) { + if (set < 0) continue; + + let y = (h-15)/(setsOnCurrentPage+1)*(set-firstShownSet+1)+5; + + g.setFontAlign(1,0); + g.setFont('7x11Numeric7Seg',1); + g.drawString(set+1, 40, y-10); + if (scores[set+1][2] != null) { + let dur2 = formatDuration(scores[set+1][2] - scores[set][2]); + g.drawString(dur2, 40, y+10); + } for (let p = 0; p < 2; p++) { - if (matchWon(p)) { - g.setFontAlign(0,0); - g.setFont('Teletext10x18Ascii',1); - g.drawString("WINNER", p === 0 ? w/4 : w/4*3, 15); - } else if (matchEnded()) { - g.setFontAlign(0,-1); - - let dur1 = formatDuration(scores[cSet][2] - scores[0][2]); - g.setFont('5x9Numeric7Seg',1); - g.drawString(dur1, p === 0 ? w/8 : w/8*5, 10); - - g.setFont('Teletext5x9Ascii',1); - g.drawString((currentSet()+1) + ' set' + (currentSet() > 0 ? 's' : ''), p === 0 ? w/8*3 : w/8*7, 12); + if (!setWon(set, p === 0 ? 1 : 0) || matchEnded()) { + let bigNumX = p === 0 ? (w-20)/4+18 : (w-20)/4*3+4; + let smallNumX = p === 0 ? w/2-2 : w/2+3; + if (settings.enableTennisScoring && set === cSet && !shouldTiebreak()) { + g.setFontAlign(0,0); + g.setFont('7x11Numeric7Seg',3); + g.drawString( + formatNumber(tennisScores[tScores[p]]), + bigNumX, + y + ); + } else if (shouldTiebreak() && set === cSet) { + g.setFontAlign(0,0); + g.setFont('7x11Numeric7Seg',3); + g.drawString( + formatNumber(scores[set][3+p], 3), + bigNumX, + y + ); + } else { + g.setFontAlign(0,0); + g.setFont('7x11Numeric7Seg',3); + g.drawString( + formatNumber(scores[set][p]), + bigNumX, + y + ); } - g.setFontAlign(p === 0 ? -1 : 1,1); - g.setFont('7x11Numeric7Seg',2); - g.drawString(setsWon(p), p === 0 ? 10 : w-8, h-5); - - if (!settings.enableTennisScoring) { - g.setFontAlign(p === 0 ? 1 : -1,1); - g.setFont('7x11Numeric7Seg',2); - g.drawString(formatNumber(matchScore(p), 3), p === 0 ? w/2 - 8 : w/2 + 11, h-5); + if ((shouldTiebreak() || settings.enableTennisScoring) && set === cSet) { + g.setFontAlign(p === 0 ? 1 : -1,0); + g.setFont('7x11Numeric7Seg',1); + g.drawString( + formatNumber(scores[set][p]), + smallNumX, + y + ); + } else if ((scores[set][3] !== 0 || scores[set][4] !== 0) && set !== cSet) { + g.setFontAlign(p === 0 ? 1 : -1,0); + g.setFont('7x11Numeric7Seg',1); + g.drawString( + formatNumber(scores[set][3+p], 3), + smallNumX, + y + ); } + } } - g.setFontAlign(0,0); + } - if (correctionMode) { - g.setFont('Teletext10x18Ascii',1); - g.drawString("R", w/2, h-10); - } + // draw separator + g.drawLine(w/2,20,w/2,h-25); - let lastShownSet = Math.min( - sets(), - currentSet() + 1, - firstShownSet+setsPerPage() - ); - let setsOnCurrentPage = Math.min( - sets(), - setsPerPage() - ); - for (let set = firstShownSet; set < lastShownSet; set++) { - if (set < 0) continue; - - let y = (h-15)/(setsOnCurrentPage+1)*(set-firstShownSet+1)+5; - - g.setFontAlign(1,0); - g.setFont('7x11Numeric7Seg',1); - g.drawString(set+1, 40, y-10); - if (scores[set+1][2] != null) { - let dur2 = formatDuration(scores[set+1][2] - scores[set][2]); - g.drawString(dur2, 40, y+10); - } - - for (let p = 0; p < 2; p++) { - if (!setWon(set, p === 0 ? 1 : 0) || matchEnded()) { - let bigNumX = p === 0 ? (w-20)/4+18 : (w-20)/4*3+4; - let smallNumX = p === 0 ? w/2-2 : w/2+3; - - if (settings.enableTennisScoring && set === cSet && !shouldTiebreak()) { - g.setFontAlign(0,0); - g.setFont('7x11Numeric7Seg',3); - g.drawString( - formatNumber(tennisScores[tScores[p]]), - bigNumX, - y - ); - } else if (shouldTiebreak() && set === cSet) { - g.setFontAlign(0,0); - g.setFont('7x11Numeric7Seg',3); - g.drawString( - formatNumber(scores[set][3+p], 3), - bigNumX, - y - ); - } else { - g.setFontAlign(0,0); - g.setFont('7x11Numeric7Seg',3); - g.drawString( - formatNumber(scores[set][p]), - bigNumX, - y - ); - } - - if ((shouldTiebreak() || settings.enableTennisScoring) && set === cSet) { - g.setFontAlign(p === 0 ? 1 : -1,0); - g.setFont('7x11Numeric7Seg',1); - g.drawString( - formatNumber(scores[set][p]), - smallNumX, - y - ); - } else if ((scores[set][3] !== 0 || scores[set][4] !== 0) && set !== cSet) { - g.setFontAlign(p === 0 ? 1 : -1,0); - g.setFont('7x11Numeric7Seg',1); - g.drawString( - formatNumber(scores[set][3+p], 3), - smallNumX, - y - ); - } - } - } - } - - // draw separator - g.drawLine(w/2,20,w/2,h-25); - - g.flip(); + g.flip(); } setupInputWatchers(); diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index c1a0382d2..901867d7b 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -1,128 +1,128 @@ (function (back, ret) { - const fileName = 'score.json' - let settings = require('Storage').readJSON(fileName, 1) || {}; - const offon = ['No', 'Yes']; + const fileName = 'score.json' + let settings = require('Storage').readJSON(fileName, 1) || {}; + const offon = ['No', 'Yes']; - let changed = false; + let changed = false; - function save(key, value) { - changed = true; - settings[key] = value; - if (key === 'winScore' && settings.maxScore < value) { - settings.maxScore = value; - } - require('Storage').writeJSON(fileName, settings); + function save(key, value) { + changed = true; + settings[key] = value; + if (key === 'winScore' && settings.maxScore < value) { + settings.maxScore = value; } + require('Storage').writeJSON(fileName, settings); + } - if (settings.winSets == null) { - settings.winSets = 1; - } - if (settings.setsPerPage == null) { - settings.setsPerPage = 5; - } - if (settings.winScore == null) { - settings.winScore = 21; - } - if (settings.enableTwoAhead == null) { - settings.enableTwoAhead = true; - } - if (settings.enableMaxScore == null) { - settings.enableMaxScore = true; - } - if (settings.maxScore == null) { - settings.maxScore = 30; - } - if (settings.enableTennisScoring == null) { - settings.enableTennisScoring = false; - } + if (settings.winSets == null) { + settings.winSets = 1; + } + if (settings.setsPerPage == null) { + settings.setsPerPage = 5; + } + if (settings.winScore == null) { + settings.winScore = 21; + } + if (settings.enableTwoAhead == null) { + settings.enableTwoAhead = true; + } + if (settings.enableMaxScore == null) { + settings.enableMaxScore = true; + } + if (settings.maxScore == null) { + settings.maxScore = 30; + } + if (settings.enableTennisScoring == null) { + settings.enableTennisScoring = false; + } - if (settings.enableMaxScoreTiebreak == null) { - settings.enableMaxScoreTiebreak = false; - } - if (settings.maxScoreTiebreakWinScore == null) { - settings.maxScoreTiebreakWinScore = 6; - } - if (settings.maxScoreTiebreakEnableTwoAhead == null) { - settings.maxScoreTiebreakEnableTwoAhead = true; - } - if (settings.maxScoreTiebreakEnableMaxScore == null) { - settings.maxScoreTiebreakEnableMaxScore = false; - } - if (settings.maxScoreTiebreakMaxScore == null) { - settings.maxScoreTiebreakMaxScore = 15; - } + if (settings.enableMaxScoreTiebreak == null) { + settings.enableMaxScoreTiebreak = false; + } + if (settings.maxScoreTiebreakWinScore == null) { + settings.maxScoreTiebreakWinScore = 6; + } + if (settings.maxScoreTiebreakEnableTwoAhead == null) { + settings.maxScoreTiebreakEnableTwoAhead = true; + } + if (settings.maxScoreTiebreakEnableMaxScore == null) { + settings.maxScoreTiebreakEnableMaxScore = false; + } + if (settings.maxScoreTiebreakMaxScore == null) { + settings.maxScoreTiebreakMaxScore = 15; + } - if (ret) { - return settings; - } + if (ret) { + return settings; + } - const appMenu = {}; - appMenu[''] = {'title': 'Score Settings'}, - appMenu['< Back'] = function () { back(settings, changed); }; - if (reset) { - appMenu['Reset match'] = function () { back(settings, true); }; - } - appMenu['Sets to win'] = { - value: settings.winSets, - min:1, - onchange: m => save('winSets', m) - }; - appMenu['Sets per page'] = { - value: settings.setsPerPage, - min:1, - max:5, - onchange: m => save('setsPerPage', m) - }; - appMenu['Score to win'] = { - value: settings.winScore, - min:1, - onchange: m => save('winScore', m) - }; - appMenu['2-point lead'] = { - value: settings.enableTwoAhead, - format: m => offon[~~m], - onchange: m => save('enableTwoAhead', m) - }; - appMenu['Maximum score?'] = { - value: settings.enableMaxScore, - format: m => offon[~~m], - onchange: m => save('enableMaxScore', m) - }; - appMenu['Maximum score'] = { - value: settings.maxScore, - min: 1, - onchange: m => save('maxScore', m) - }; - appMenu['Tennis scoring'] = { - value: settings.enableTennisScoring, - format: m => offon[~~m], - onchange: m => save('enableTennisScoring', m) - }; - appMenu['TB sets?'] = { - value: settings.enableMaxScoreTiebreak, - format: m => offon[~~m], - onchange: m => save('enableMaxScoreTiebreak', m) - } - appMenu['TB Score to win'] = { - value: settings.maxScoreTiebreakWinScore, - onchange: m => save('maxScoreTiebreakWinScore', m) - } - appMenu['TB 2-point lead'] = { - value: settings.maxScoreTiebreakEnableTwoAhead, - format: m => offon[~~m], - onchange: m => save('maxScoreTiebreakEnableTwoAhead', m) - } - appMenu['TB max score?'] = { - value: settings.maxScoreTiebreakEnableMaxScore, - format: m => offon[~~m], - onchange: m => save('maxScoreTiebreakEnableMaxScore', m) - } - appMenu['TB max score'] = { - value: settings.maxScoreTiebreakMaxScore, - onchange: m => save('maxScoreTiebreakMaxScore', m) - } + const appMenu = {}; + appMenu[''] = {'title': 'Score Settings'}, + appMenu['< Back'] = function () { back(settings, changed); }; + if (reset) { + appMenu['Reset match'] = function () { back(settings, true); }; + } + appMenu['Sets to win'] = { + value: settings.winSets, + min:1, + onchange: m => save('winSets', m) + }; + appMenu['Sets per page'] = { + value: settings.setsPerPage, + min:1, + max:5, + onchange: m => save('setsPerPage', m) + }; + appMenu['Score to win'] = { + value: settings.winScore, + min:1, + onchange: m => save('winScore', m) + }; + appMenu['2-point lead'] = { + value: settings.enableTwoAhead, + format: m => offon[~~m], + onchange: m => save('enableTwoAhead', m) + }; + appMenu['Maximum score?'] = { + value: settings.enableMaxScore, + format: m => offon[~~m], + onchange: m => save('enableMaxScore', m) + }; + appMenu['Maximum score'] = { + value: settings.maxScore, + min: 1, + onchange: m => save('maxScore', m) + }; + appMenu['Tennis scoring'] = { + value: settings.enableTennisScoring, + format: m => offon[~~m], + onchange: m => save('enableTennisScoring', m) + }; + appMenu['TB sets?'] = { + value: settings.enableMaxScoreTiebreak, + format: m => offon[~~m], + onchange: m => save('enableMaxScoreTiebreak', m) + } + appMenu['TB Score to win'] = { + value: settings.maxScoreTiebreakWinScore, + onchange: m => save('maxScoreTiebreakWinScore', m) + } + appMenu['TB 2-point lead'] = { + value: settings.maxScoreTiebreakEnableTwoAhead, + format: m => offon[~~m], + onchange: m => save('maxScoreTiebreakEnableTwoAhead', m) + } + appMenu['TB max score?'] = { + value: settings.maxScoreTiebreakEnableMaxScore, + format: m => offon[~~m], + onchange: m => save('maxScoreTiebreakEnableMaxScore', m) + } + appMenu['TB max score'] = { + value: settings.maxScoreTiebreakMaxScore, + onchange: m => save('maxScoreTiebreakMaxScore', m) + } - E.showMenu(appMenu) + E.showMenu(appMenu) }) From 9f73450b8318b930cdf6108184ebc9f757393c91 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 01:03:13 +0200 Subject: [PATCH 0171/1062] score: add configuration preset functionality --- apps.json | 1 + apps/score/README.md | 1 + apps/score/score.presets.json | 1 + apps/score/score.settings.js | 208 ++++++++++++++++++++-------------- 4 files changed, 127 insertions(+), 84 deletions(-) create mode 100644 apps/score/score.presets.json diff --git a/apps.json b/apps.json index c98686e72..b1e6f70fc 100644 --- a/apps.json +++ b/apps.json @@ -3565,6 +3565,7 @@ "storage": [ {"name":"score.app.js","url":"score.app.js"}, {"name":"score.settings.js","url":"score.settings.js"}, + {"name":"score.presets.json","url":"score.presets.json"}, {"name":"score.img","url":"score.app-icon.js","evaluate":true} ] } diff --git a/apps/score/README.md b/apps/score/README.md index ffd35e065..c3ad0e622 100644 --- a/apps/score/README.md +++ b/apps/score/README.md @@ -16,6 +16,7 @@ In this mode any score increments will be decrements. To move back a set, reduce # Settings | Setting | Description | |------------------------------------|------------------------------------------------------------------------------------------------------------------------------| +| `Presets` | Enable a preset for one of the configured sports | | `Sets to win` | How many sets a player has to win before the match is won (Maximum sets: this*2-1) | | `Sets per page` | How many sets should be shown in the app. Further sets will be available by scrolling (ignored if higher than `Sets to win`) | | `Score to win` | What score ends a given set | diff --git a/apps/score/score.presets.json b/apps/score/score.presets.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/apps/score/score.presets.json @@ -0,0 +1 @@ +{} diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index 901867d7b..cbae83f0c 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -1,20 +1,4 @@ -(function (back, ret) { - - const fileName = 'score.json' - let settings = require('Storage').readJSON(fileName, 1) || {}; - const offon = ['No', 'Yes']; - - let changed = false; - - function save(key, value) { - changed = true; - settings[key] = value; - if (key === 'winScore' && settings.maxScore < value) { - settings.maxScore = value; - } - require('Storage').writeJSON(fileName, settings); - } - +function fillSettingsWithDefaults(settings) { if (settings.winSets == null) { settings.winSets = 1; } @@ -53,76 +37,132 @@ settings.maxScoreTiebreakMaxScore = 15; } + return settings; +} + +(function (back, ret) { + + const fileName = 'score.json'; + let settings = require('Storage').readJSON(fileName, 1) || {}; + const offon = ['No', 'Yes']; + + let presetsFileName = 'score.presets.json'; + let presets = require('Storage').readJSON(presetsFileName); + let presetNames = Object.keys(presets); + + let changed = false; + + function save(settings) { + require('Storage').writeJSON(fileName, settings); + } + + function setAndSave(key, value) { + changed = true; + settings[key] = value; + if (key === 'winScore' && settings.maxScore < value) { + settings.maxScore = value; + } + save(settings); + } + + settings = fillSettingsWithDefaults(settings); + if (ret) { return settings; } - const appMenu = {}; - appMenu[''] = {'title': 'Score Settings'}, - appMenu['< Back'] = function () { back(settings, changed); }; - if (reset) { - appMenu['Reset match'] = function () { back(settings, true); }; - } - appMenu['Sets to win'] = { - value: settings.winSets, - min:1, - onchange: m => save('winSets', m) - }; - appMenu['Sets per page'] = { - value: settings.setsPerPage, - min:1, - max:5, - onchange: m => save('setsPerPage', m) - }; - appMenu['Score to win'] = { - value: settings.winScore, - min:1, - onchange: m => save('winScore', m) - }; - appMenu['2-point lead'] = { - value: settings.enableTwoAhead, - format: m => offon[~~m], - onchange: m => save('enableTwoAhead', m) - }; - appMenu['Maximum score?'] = { - value: settings.enableMaxScore, - format: m => offon[~~m], - onchange: m => save('enableMaxScore', m) - }; - appMenu['Maximum score'] = { - value: settings.maxScore, - min: 1, - onchange: m => save('maxScore', m) - }; - appMenu['Tennis scoring'] = { - value: settings.enableTennisScoring, - format: m => offon[~~m], - onchange: m => save('enableTennisScoring', m) - }; - appMenu['TB sets?'] = { - value: settings.enableMaxScoreTiebreak, - format: m => offon[~~m], - onchange: m => save('enableMaxScoreTiebreak', m) - } - appMenu['TB Score to win'] = { - value: settings.maxScoreTiebreakWinScore, - onchange: m => save('maxScoreTiebreakWinScore', m) - } - appMenu['TB 2-point lead'] = { - value: settings.maxScoreTiebreakEnableTwoAhead, - format: m => offon[~~m], - onchange: m => save('maxScoreTiebreakEnableTwoAhead', m) - } - appMenu['TB max score?'] = { - value: settings.maxScoreTiebreakEnableMaxScore, - format: m => offon[~~m], - onchange: m => save('maxScoreTiebreakEnableMaxScore', m) - } - appMenu['TB max score'] = { - value: settings.maxScoreTiebreakMaxScore, - onchange: m => save('maxScoreTiebreakMaxScore', m) - } + const presetMenu = function () { + let ret = function () { E.showMenu(appMenu()); }; + let m = { + '': {'title': 'Score Presets'}, + '< Back': ret, + }; + for (let i = 0; i < presetNames.length; i++) { + m[presetNames[i]] = (function (i) { + return function() { + changed = true; + settings = fillSettingsWithDefaults(presets[presetNames[i]]); + save(settings); + ret(); + }; + })(i); + } - E.showMenu(appMenu) + return m; + }; -}) + const appMenu = function () { + let m = {}; + + m[''] = {'title': 'Score Settings'}; + m['< Back'] = function () { back(settings, changed); }; + if (reset) { + m['Reset match'] = function () { back(settings, true); }; + } + m['Presets'] = function () { E.showMenu(presetMenu()); }; + m['Sets to win'] = { + value: settings.winSets, + min:1, + onchange: m => setAndSave('winSets', m), + }; + m['Sets per page'] = { + value: settings.setsPerPage, + min:1, + max:5, + onchange: m => setAndSave('setsPerPage', m), + }; + m['Score to win'] = { + value: settings.winScore, + min:1, + onchange: m => setAndSave('winScore', m), + }; + m['2-point lead'] = { + value: settings.enableTwoAhead, + format: m => offon[~~m], + onchange: m => setAndSave('enableTwoAhead', m), + }; + m['Maximum score?'] = { + value: settings.enableMaxScore, + format: m => offon[~~m], + onchange: m => setAndSave('enableMaxScore', m), + }; + m['Maximum score'] = { + value: settings.maxScore, + min: 1, + onchange: m => setAndSave('maxScore', m), + }; + m['Tennis scoring'] = { + value: settings.enableTennisScoring, + format: m => offon[~~m], + onchange: m => setAndSave('enableTennisScoring', m), + }; + m['TB sets?'] = { + value: settings.enableMaxScoreTiebreak, + format: m => offon[~~m], + onchange: m => setAndSave('enableMaxScoreTiebreak', m), + }; + m['TB Score to win'] = { + value: settings.maxScoreTiebreakWinScore, + onchange: m => setAndSave('maxScoreTiebreakWinScore', m), + }; + m['TB 2-point lead'] = { + value: settings.maxScoreTiebreakEnableTwoAhead, + format: m => offon[~~m], + onchange: m => setAndSave('maxScoreTiebreakEnableTwoAhead', m), + }; + m['TB max score?'] = { + value: settings.maxScoreTiebreakEnableMaxScore, + format: m => offon[~~m], + onchange: m => setAndSave('maxScoreTiebreakEnableMaxScore', m), + }; + m['TB max score'] = { + value: settings.maxScoreTiebreakMaxScore, + onchange: m => setAndSave('maxScoreTiebreakMaxScore', m), + }; + + return m; + }; + + E.showMenu(appMenu()); + +}); From a516d409919e0f4052321bc5713b4a6caa67a31c Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 01:04:13 +0200 Subject: [PATCH 0172/1062] score: add badminton config preset --- apps/score/score.presets.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/score/score.presets.json b/apps/score/score.presets.json index 0967ef424..12b030044 100644 --- a/apps/score/score.presets.json +++ b/apps/score/score.presets.json @@ -1 +1,8 @@ -{} +{ + "Badminton": { + "winScore": 21, + "enableTwoAhead": true, + "enableMaxScore": true, + "maxScore": 30 + } +} From 8567e3d8e156592640d4d6495199e5fcd7716b76 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 01:04:26 +0200 Subject: [PATCH 0173/1062] score: add tennis config preset --- apps/score/score.presets.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/score/score.presets.json b/apps/score/score.presets.json index 12b030044..fe11c9e6b 100644 --- a/apps/score/score.presets.json +++ b/apps/score/score.presets.json @@ -4,5 +4,16 @@ "enableTwoAhead": true, "enableMaxScore": true, "maxScore": 30 + }, + "Tennis": { + "winScore": 6, + "enableTwoAhead": true, + "enableMaxScore": true, + "maxScore": 7, + "enableMaxScoreTiebreak": true, + "maxScoreTiebreakWinScore": 7, + "maxScoreTiebreakEnableTwoAhead": true, + "maxScoreTiebreakEnableMaxScore": false, + "enableTennisScoring": true } } From ad90c410d7a3c4fa959a5cfdca5768c633b566e7 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 01:09:13 +0200 Subject: [PATCH 0174/1062] score: select winSets entry after selecting preset --- apps/score/score.settings.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index cbae83f0c..8442fd80b 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -72,7 +72,7 @@ function fillSettingsWithDefaults(settings) { } const presetMenu = function () { - let ret = function () { E.showMenu(appMenu()); }; + let ret = function (changed) { E.showMenu(appMenu(changed ? 3 : null)); }; let m = { '': {'title': 'Score Presets'}, '< Back': ret, @@ -83,7 +83,7 @@ function fillSettingsWithDefaults(settings) { changed = true; settings = fillSettingsWithDefaults(presets[presetNames[i]]); save(settings); - ret(); + ret(true); }; })(i); } @@ -91,10 +91,13 @@ function fillSettingsWithDefaults(settings) { return m; }; - const appMenu = function () { + const appMenu = function (selected) { let m = {}; m[''] = {'title': 'Score Settings'}; + if (selected != null) { + m[''].selected = selected; + } m['< Back'] = function () { back(settings, changed); }; if (reset) { m['Reset match'] = function () { back(settings, true); }; From ecc4e59a01fb82edc096040a05eeaa13eee0c43a Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 14:02:15 +0200 Subject: [PATCH 0175/1062] score: set default winSets to 2 --- apps/score/score.settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index 8442fd80b..83a98d93f 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -1,6 +1,6 @@ function fillSettingsWithDefaults(settings) { if (settings.winSets == null) { - settings.winSets = 1; + settings.winSets = 2; } if (settings.setsPerPage == null) { settings.setsPerPage = 5; From 7074340f444c25100434ee3101a30424ba626fba Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 14:03:23 +0200 Subject: [PATCH 0176/1062] score: add additional in-app menu, allow ending current set --- apps/score/score.app.js | 50 ++++++++++++++++++++++++------------ apps/score/score.settings.js | 33 +++++++++++++++++------- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 7f0b04a59..4b72f911c 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -4,7 +4,7 @@ require('FontTeletext5x9Ascii').add(Graphics); require('FontTeletext10x18Ascii').add(Graphics); let settingsMenu = eval(require('Storage').read('score.settings.js')); -let settings = settingsMenu(null, true); +let settings = settingsMenu(null, null, true); let tennisScores = ['00','15','30','40','DC','AD']; @@ -61,7 +61,7 @@ function setupMatch() { scores[0][2] = getTime(); cSet = 0; - firstShownSet = 0 - Math.floor(setsPerPage() / 2); + setFirstShownSet(); correctionMode = false; } @@ -82,6 +82,12 @@ function showSettingsMenu() { settingsMenuOpened = null; draw(); + }, function (msg) { + switch (msg) { + case 'end_set': + updateCurrentSet(1); + break; + } }); } @@ -154,10 +160,12 @@ function setWon(set, player) { let isTwoAhead = !settings.enableTwoAhead || pScore - p2Score >= 2; let tiebreakW = tiebreakWon(set, player); let reachedMaxScore = settings.enableMaxScore && pScore >= maxScore(); + let manuallyEndedWon = cSet > set ? pScore > p2Score : false return ( (settings.enableMaxScoreTiebreak ? tiebreakW : reachedMaxScore) || - (winScoreReached && isTwoAhead) + (winScoreReached && isTwoAhead) || + manuallyEndedWon ); } @@ -181,15 +189,30 @@ function matchScore(player) { return scores.reduce((acc, val) => acc += val[player], 0); } -function score(player) { - let setFirstShownSet = function () { - firstShownSet = currentSet() - Math.floor(setsPerPage() / 2); - }; - let updateCurrentSet = function (val) { - cSet += val; - setFirstShownSet(); - }; +function setFirstShownSet() { + firstShownSet = Math.max(0, currentSet() - setsPerPage() + 1); +} +function updateCurrentSet(val) { + if (val > 0) { + cSet++ + } else if (val < 0) { + cSet-- + } else { + return; + } + setFirstShownSet(); + + if (matchEnded()) { + firstShownSet = 0; + } + + if (val > 0) { + scores[cSet][2] = getTime(); + } +} + +function score(player) { if (!matchEnded() || correctionMode) { setFirstShownSet(); } @@ -242,11 +265,6 @@ function score(player) { scores[cSet][player]++; } updateCurrentSet(1); - scores[cSet][2] = getTime(); - } - - if (matchEnded()) { - firstShownSet = 0; } } } diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index 83a98d93f..4a4b5e56a 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -40,7 +40,7 @@ function fillSettingsWithDefaults(settings) { return settings; } -(function (back, ret) { +(function (back, inApp, ret) { const fileName = 'score.json'; let settings = require('Storage').readJSON(fileName, 1) || {}; @@ -71,8 +71,8 @@ function fillSettingsWithDefaults(settings) { return settings; } - const presetMenu = function () { - let ret = function (changed) { E.showMenu(appMenu(changed ? 3 : null)); }; + const presetMenu = function (appMenuBack) { + let ret = function (changed) { E.showMenu(appMenu(appMenuBack, changed ? 2 : null)); }; let m = { '': {'title': 'Score Presets'}, '< Back': ret, @@ -91,7 +91,7 @@ function fillSettingsWithDefaults(settings) { return m; }; - const appMenu = function (selected) { + const appMenu = function (back, selected) { let m = {}; m[''] = {'title': 'Score Settings'}; @@ -99,10 +99,7 @@ function fillSettingsWithDefaults(settings) { m[''].selected = selected; } m['< Back'] = function () { back(settings, changed); }; - if (reset) { - m['Reset match'] = function () { back(settings, true); }; - } - m['Presets'] = function () { E.showMenu(presetMenu()); }; + m['Presets'] = function () { E.showMenu(presetMenu(back)); }; m['Sets to win'] = { value: settings.winSets, min:1, @@ -166,6 +163,24 @@ function fillSettingsWithDefaults(settings) { return m; }; - E.showMenu(appMenu()); + const inAppMenu = function () { + let m = { + '': {'title': 'Score Menu'}, + '< Back': function () { back(settings, changed); }, + 'Reset match': function () { back(settings, true); }, + 'End current set': function () { inApp('end_set'); back(settings, changed); }, + 'Configuration': function () { E.showMenu(appMenu(function () { + E.showMenu(inAppMenu()); + })); }, + }; + + return m; + }; + + if (inApp != null) { + E.showMenu(inAppMenu()); + } else { + E.showMenu(appMenu(back)); + } }); From e27303a09a0d899675fea361e691de2a781df101 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 14:04:15 +0200 Subject: [PATCH 0177/1062] score: code style + firstShownSet limits --- apps/score/score.app.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 4b72f911c..4409b86b6 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -275,22 +275,22 @@ function handleInput(button) { } switch (button) { - case 0: - case 1: - score(button); - break; - case 2: - showSettingsMenu(); - return; - case 3: - case 4: - let hLimit = currentSet(); - let lLimit = 1 - setsPerPage(); - let val = (button * 2 - 7); - firstShownSet += val; - if (firstShownSet > hLimit) firstShownSet = hLimit; - if (firstShownSet < lLimit) firstShownSet = lLimit; - break; + case 0: + case 1: + score(button); + break; + case 2: + showSettingsMenu(); + return; + case 3: + case 4: + let hLimit = currentSet() - setsPerPage() + 1; + let lLimit = 0; + let val = (button * 2 - 7); + firstShownSet += val; + if (firstShownSet > hLimit) firstShownSet = hLimit; + if (firstShownSet < lLimit) firstShownSet = lLimit; + break; } draw(); From a504addd8f220ea786053bb613f3bf029d414958 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 14:29:34 +0200 Subject: [PATCH 0178/1062] score: remove reference to Teletext10x18Ascii font --- apps/score/score.app.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 4409b86b6..c9ed89260 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -1,7 +1,6 @@ require('Font5x9Numeric7Seg').add(Graphics); require('Font7x11Numeric7Seg').add(Graphics); require('FontTeletext5x9Ascii').add(Graphics); -require('FontTeletext10x18Ascii').add(Graphics); let settingsMenu = eval(require('Storage').read('score.settings.js')); let settings = settingsMenu(null, null, true); @@ -303,7 +302,7 @@ function draw() { for (let p = 0; p < 2; p++) { if (matchWon(p)) { g.setFontAlign(0,0); - g.setFont('Teletext10x18Ascii',1); + g.setFont('Teletext5x9Ascii',2); g.drawString("WINNER", p === 0 ? w/4 : w/4*3, 15); } else if (matchEnded()) { g.setFontAlign(0,-1); @@ -318,7 +317,7 @@ function draw() { } g.setFontAlign(p === 0 ? -1 : 1,1); - g.setFont('7x11Numeric7Seg',2); + g.setFont('5x9Numeric7Seg',2); g.drawString(setsWon(p), p === 0 ? 10 : w-8, h-5); if (!settings.enableTennisScoring) { @@ -330,7 +329,7 @@ function draw() { g.setFontAlign(0,0); if (correctionMode) { - g.setFont('Teletext10x18Ascii',1); + g.setFont('Teletext5x9Ascii',2); g.drawString("R", w/2, h-10); } From 978c181435f9afa14b6705e073fd95999109c495 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 14:30:05 +0200 Subject: [PATCH 0179/1062] score: fix correctionMode without tennis scoring --- apps/score/score.app.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index c9ed89260..eb373ed50 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -230,7 +230,10 @@ function score(player) { scores[cSet][3+player]--; } } else if (scores[cSet][player] > 0) { - if (tScores[player] === 0 && tScores[~~!player] === 0) { + if ( + !settings.enableTennisScoring || + (tScores[player] === 0 && tScores[~~!player] === 0) + ) { scores[cSet][player]--; } else { tScores[player] = 0; From f3398e65501ba3d0bbd52a2f703943ca3fb53ca0 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 14:32:14 +0200 Subject: [PATCH 0180/1062] score: add preset for soccer --- apps/score/score.presets.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/score/score.presets.json b/apps/score/score.presets.json index fe11c9e6b..3224556a6 100644 --- a/apps/score/score.presets.json +++ b/apps/score/score.presets.json @@ -15,5 +15,11 @@ "maxScoreTiebreakEnableTwoAhead": true, "maxScoreTiebreakEnableMaxScore": false, "enableTennisScoring": true + }, + "Soccer": { + "winSets": 1, + "winScore": 9999, + "enableTwoAhead": false, + "enableMaxScore": false } } From 6155893856c42248e0c3a070f9112aebd802050b Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 14:36:25 +0200 Subject: [PATCH 0181/1062] score: add preset for table tennis --- apps/score/score.presets.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/score/score.presets.json b/apps/score/score.presets.json index 3224556a6..b57b52157 100644 --- a/apps/score/score.presets.json +++ b/apps/score/score.presets.json @@ -21,5 +21,10 @@ "winScore": 9999, "enableTwoAhead": false, "enableMaxScore": false + }, + "Table Tennis": { + "winScore": 11, + "enableTwoAhead": true, + "enableMaxScore": false } } From f4d820bf607c4ec16c955464364a595c0f457f96 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 15:43:21 +0200 Subject: [PATCH 0182/1062] score: code style --- apps/score/score.app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index eb373ed50..48f503cb7 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -159,7 +159,7 @@ function setWon(set, player) { let isTwoAhead = !settings.enableTwoAhead || pScore - p2Score >= 2; let tiebreakW = tiebreakWon(set, player); let reachedMaxScore = settings.enableMaxScore && pScore >= maxScore(); - let manuallyEndedWon = cSet > set ? pScore > p2Score : false + let manuallyEndedWon = cSet > set ? pScore > p2Score : false; return ( (settings.enableMaxScoreTiebreak ? tiebreakW : reachedMaxScore) || @@ -194,9 +194,9 @@ function setFirstShownSet() { function updateCurrentSet(val) { if (val > 0) { - cSet++ + cSet++; } else if (val < 0) { - cSet-- + cSet--; } else { return; } From 7a98c039e705e67db0f10e94c7623cc6769c7b78 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 15:47:19 +0200 Subject: [PATCH 0183/1062] score: fix settings module --- apps/score/score.settings.js | 83 ++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index 4a4b5e56a..c4e22e45d 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -1,46 +1,45 @@ -function fillSettingsWithDefaults(settings) { - if (settings.winSets == null) { - settings.winSets = 2; - } - if (settings.setsPerPage == null) { - settings.setsPerPage = 5; - } - if (settings.winScore == null) { - settings.winScore = 21; - } - if (settings.enableTwoAhead == null) { - settings.enableTwoAhead = true; - } - if (settings.enableMaxScore == null) { - settings.enableMaxScore = true; - } - if (settings.maxScore == null) { - settings.maxScore = 30; - } - if (settings.enableTennisScoring == null) { - settings.enableTennisScoring = false; - } - - if (settings.enableMaxScoreTiebreak == null) { - settings.enableMaxScoreTiebreak = false; - } - if (settings.maxScoreTiebreakWinScore == null) { - settings.maxScoreTiebreakWinScore = 6; - } - if (settings.maxScoreTiebreakEnableTwoAhead == null) { - settings.maxScoreTiebreakEnableTwoAhead = true; - } - if (settings.maxScoreTiebreakEnableMaxScore == null) { - settings.maxScoreTiebreakEnableMaxScore = false; - } - if (settings.maxScoreTiebreakMaxScore == null) { - settings.maxScoreTiebreakMaxScore = 15; - } - - return settings; -} - (function (back, inApp, ret) { + function fillSettingsWithDefaults(settings) { + if (settings.winSets == null) { + settings.winSets = 2; + } + if (settings.setsPerPage == null) { + settings.setsPerPage = 5; + } + if (settings.winScore == null) { + settings.winScore = 21; + } + if (settings.enableTwoAhead == null) { + settings.enableTwoAhead = true; + } + if (settings.enableMaxScore == null) { + settings.enableMaxScore = true; + } + if (settings.maxScore == null) { + settings.maxScore = 30; + } + if (settings.enableTennisScoring == null) { + settings.enableTennisScoring = false; + } + + if (settings.enableMaxScoreTiebreak == null) { + settings.enableMaxScoreTiebreak = false; + } + if (settings.maxScoreTiebreakWinScore == null) { + settings.maxScoreTiebreakWinScore = 6; + } + if (settings.maxScoreTiebreakEnableTwoAhead == null) { + settings.maxScoreTiebreakEnableTwoAhead = true; + } + if (settings.maxScoreTiebreakEnableMaxScore == null) { + settings.maxScoreTiebreakEnableMaxScore = false; + } + if (settings.maxScoreTiebreakMaxScore == null) { + settings.maxScoreTiebreakMaxScore = 15; + } + + return settings; + } const fileName = 'score.json'; let settings = require('Storage').readJSON(fileName, 1) || {}; From ea3cfece674c5af827ca4fb077694fc1098c4daf Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 15:57:56 +0200 Subject: [PATCH 0184/1062] score: prevent warning in web ide for settings module --- apps/score/score.settings.js | 342 ++++++++++++++++++----------------- 1 file changed, 172 insertions(+), 170 deletions(-) diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index c4e22e45d..da35d7a21 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -1,185 +1,187 @@ -(function (back, inApp, ret) { - function fillSettingsWithDefaults(settings) { - if (settings.winSets == null) { - settings.winSets = 2; - } - if (settings.setsPerPage == null) { - settings.setsPerPage = 5; - } - if (settings.winScore == null) { - settings.winScore = 21; - } - if (settings.enableTwoAhead == null) { - settings.enableTwoAhead = true; - } - if (settings.enableMaxScore == null) { - settings.enableMaxScore = true; - } - if (settings.maxScore == null) { - settings.maxScore = 30; - } - if (settings.enableTennisScoring == null) { - settings.enableTennisScoring = false; +(function () { + return (function (back, inApp, ret) { + function fillSettingsWithDefaults(settings) { + if (settings.winSets == null) { + settings.winSets = 2; + } + if (settings.setsPerPage == null) { + settings.setsPerPage = 5; + } + if (settings.winScore == null) { + settings.winScore = 21; + } + if (settings.enableTwoAhead == null) { + settings.enableTwoAhead = true; + } + if (settings.enableMaxScore == null) { + settings.enableMaxScore = true; + } + if (settings.maxScore == null) { + settings.maxScore = 30; + } + if (settings.enableTennisScoring == null) { + settings.enableTennisScoring = false; + } + + if (settings.enableMaxScoreTiebreak == null) { + settings.enableMaxScoreTiebreak = false; + } + if (settings.maxScoreTiebreakWinScore == null) { + settings.maxScoreTiebreakWinScore = 6; + } + if (settings.maxScoreTiebreakEnableTwoAhead == null) { + settings.maxScoreTiebreakEnableTwoAhead = true; + } + if (settings.maxScoreTiebreakEnableMaxScore == null) { + settings.maxScoreTiebreakEnableMaxScore = false; + } + if (settings.maxScoreTiebreakMaxScore == null) { + settings.maxScoreTiebreakMaxScore = 15; + } + + return settings; } - if (settings.enableMaxScoreTiebreak == null) { - settings.enableMaxScoreTiebreak = false; - } - if (settings.maxScoreTiebreakWinScore == null) { - settings.maxScoreTiebreakWinScore = 6; - } - if (settings.maxScoreTiebreakEnableTwoAhead == null) { - settings.maxScoreTiebreakEnableTwoAhead = true; - } - if (settings.maxScoreTiebreakEnableMaxScore == null) { - settings.maxScoreTiebreakEnableMaxScore = false; - } - if (settings.maxScoreTiebreakMaxScore == null) { - settings.maxScoreTiebreakMaxScore = 15; + const fileName = 'score.json'; + let settings = require('Storage').readJSON(fileName, 1) || {}; + const offon = ['No', 'Yes']; + + let presetsFileName = 'score.presets.json'; + let presets = require('Storage').readJSON(presetsFileName); + let presetNames = Object.keys(presets); + + let changed = false; + + function save(settings) { + require('Storage').writeJSON(fileName, settings); } - return settings; - } - - const fileName = 'score.json'; - let settings = require('Storage').readJSON(fileName, 1) || {}; - const offon = ['No', 'Yes']; - - let presetsFileName = 'score.presets.json'; - let presets = require('Storage').readJSON(presetsFileName); - let presetNames = Object.keys(presets); - - let changed = false; - - function save(settings) { - require('Storage').writeJSON(fileName, settings); - } - - function setAndSave(key, value) { - changed = true; - settings[key] = value; - if (key === 'winScore' && settings.maxScore < value) { - settings.maxScore = value; - } - save(settings); - } - - settings = fillSettingsWithDefaults(settings); - - if (ret) { - return settings; - } - - const presetMenu = function (appMenuBack) { - let ret = function (changed) { E.showMenu(appMenu(appMenuBack, changed ? 2 : null)); }; - let m = { - '': {'title': 'Score Presets'}, - '< Back': ret, - }; - for (let i = 0; i < presetNames.length; i++) { - m[presetNames[i]] = (function (i) { - return function() { - changed = true; - settings = fillSettingsWithDefaults(presets[presetNames[i]]); - save(settings); - ret(true); - }; - })(i); + function setAndSave(key, value) { + changed = true; + settings[key] = value; + if (key === 'winScore' && settings.maxScore < value) { + settings.maxScore = value; + } + save(settings); } - return m; - }; + settings = fillSettingsWithDefaults(settings); - const appMenu = function (back, selected) { - let m = {}; - - m[''] = {'title': 'Score Settings'}; - if (selected != null) { - m[''].selected = selected; + if (ret) { + return settings; } - m['< Back'] = function () { back(settings, changed); }; - m['Presets'] = function () { E.showMenu(presetMenu(back)); }; - m['Sets to win'] = { - value: settings.winSets, - min:1, - onchange: m => setAndSave('winSets', m), - }; - m['Sets per page'] = { - value: settings.setsPerPage, - min:1, - max:5, - onchange: m => setAndSave('setsPerPage', m), - }; - m['Score to win'] = { - value: settings.winScore, - min:1, - onchange: m => setAndSave('winScore', m), - }; - m['2-point lead'] = { - value: settings.enableTwoAhead, - format: m => offon[~~m], - onchange: m => setAndSave('enableTwoAhead', m), - }; - m['Maximum score?'] = { - value: settings.enableMaxScore, - format: m => offon[~~m], - onchange: m => setAndSave('enableMaxScore', m), - }; - m['Maximum score'] = { - value: settings.maxScore, - min: 1, - onchange: m => setAndSave('maxScore', m), - }; - m['Tennis scoring'] = { - value: settings.enableTennisScoring, - format: m => offon[~~m], - onchange: m => setAndSave('enableTennisScoring', m), - }; - m['TB sets?'] = { - value: settings.enableMaxScoreTiebreak, - format: m => offon[~~m], - onchange: m => setAndSave('enableMaxScoreTiebreak', m), - }; - m['TB Score to win'] = { - value: settings.maxScoreTiebreakWinScore, - onchange: m => setAndSave('maxScoreTiebreakWinScore', m), - }; - m['TB 2-point lead'] = { - value: settings.maxScoreTiebreakEnableTwoAhead, - format: m => offon[~~m], - onchange: m => setAndSave('maxScoreTiebreakEnableTwoAhead', m), - }; - m['TB max score?'] = { - value: settings.maxScoreTiebreakEnableMaxScore, - format: m => offon[~~m], - onchange: m => setAndSave('maxScoreTiebreakEnableMaxScore', m), - }; - m['TB max score'] = { - value: settings.maxScoreTiebreakMaxScore, - onchange: m => setAndSave('maxScoreTiebreakMaxScore', m), + + const presetMenu = function (appMenuBack) { + let ret = function (changed) { E.showMenu(appMenu(appMenuBack, changed ? 2 : null)); }; + let m = { + '': {'title': 'Score Presets'}, + '< Back': ret, + }; + for (let i = 0; i < presetNames.length; i++) { + m[presetNames[i]] = (function (i) { + return function() { + changed = true; + settings = fillSettingsWithDefaults(presets[presetNames[i]]); + save(settings); + ret(true); + }; + })(i); + } + + return m; }; - return m; - }; + const appMenu = function (back, selected) { + let m = {}; - const inAppMenu = function () { - let m = { - '': {'title': 'Score Menu'}, - '< Back': function () { back(settings, changed); }, - 'Reset match': function () { back(settings, true); }, - 'End current set': function () { inApp('end_set'); back(settings, changed); }, - 'Configuration': function () { E.showMenu(appMenu(function () { - E.showMenu(inAppMenu()); - })); }, + m[''] = {'title': 'Score Settings'}; + if (selected != null) { + m[''].selected = selected; + } + m['< Back'] = function () { back(settings, changed); }; + m['Presets'] = function () { E.showMenu(presetMenu(back)); }; + m['Sets to win'] = { + value: settings.winSets, + min:1, + onchange: m => setAndSave('winSets', m), + }; + m['Sets per page'] = { + value: settings.setsPerPage, + min:1, + max:5, + onchange: m => setAndSave('setsPerPage', m), + }; + m['Score to win'] = { + value: settings.winScore, + min:1, + onchange: m => setAndSave('winScore', m), + }; + m['2-point lead'] = { + value: settings.enableTwoAhead, + format: m => offon[~~m], + onchange: m => setAndSave('enableTwoAhead', m), + }; + m['Maximum score?'] = { + value: settings.enableMaxScore, + format: m => offon[~~m], + onchange: m => setAndSave('enableMaxScore', m), + }; + m['Maximum score'] = { + value: settings.maxScore, + min: 1, + onchange: m => setAndSave('maxScore', m), + }; + m['Tennis scoring'] = { + value: settings.enableTennisScoring, + format: m => offon[~~m], + onchange: m => setAndSave('enableTennisScoring', m), + }; + m['TB sets?'] = { + value: settings.enableMaxScoreTiebreak, + format: m => offon[~~m], + onchange: m => setAndSave('enableMaxScoreTiebreak', m), + }; + m['TB Score to win'] = { + value: settings.maxScoreTiebreakWinScore, + onchange: m => setAndSave('maxScoreTiebreakWinScore', m), + }; + m['TB 2-point lead'] = { + value: settings.maxScoreTiebreakEnableTwoAhead, + format: m => offon[~~m], + onchange: m => setAndSave('maxScoreTiebreakEnableTwoAhead', m), + }; + m['TB max score?'] = { + value: settings.maxScoreTiebreakEnableMaxScore, + format: m => offon[~~m], + onchange: m => setAndSave('maxScoreTiebreakEnableMaxScore', m), + }; + m['TB max score'] = { + value: settings.maxScoreTiebreakMaxScore, + onchange: m => setAndSave('maxScoreTiebreakMaxScore', m), + }; + + return m; }; - return m; - }; + const inAppMenu = function () { + let m = { + '': {'title': 'Score Menu'}, + '< Back': function () { back(settings, changed); }, + 'Reset match': function () { back(settings, true); }, + 'End current set': function () { inApp('end_set'); back(settings, changed); }, + 'Configuration': function () { E.showMenu(appMenu(function () { + E.showMenu(inAppMenu()); + })); }, + }; - if (inApp != null) { - E.showMenu(inAppMenu()); - } else { - E.showMenu(appMenu(back)); - } + return m; + }; -}); + if (inApp != null) { + E.showMenu(inAppMenu()); + } else { + E.showMenu(appMenu(back)); + } + + }); +})(); From 656d3e65cd54b079e2fb5b47d75f2f5314238d3d Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 16:22:16 +0200 Subject: [PATCH 0185/1062] score: shift score display right --- apps/score/score.app.js | 46 +++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 48f503cb7..8c4a16a41 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -299,6 +299,10 @@ function handleInput(button) { } function draw() { + let getXCoord = function (func) { + let offset = 40; + return func(w-offset)+offset; + }; g.setFontAlign(0,0); g.clear(); @@ -306,34 +310,58 @@ function draw() { if (matchWon(p)) { g.setFontAlign(0,0); g.setFont('Teletext5x9Ascii',2); - g.drawString("WINNER", p === 0 ? w/4 : w/4*3, 15); + g.drawString( + "WINNER", + getXCoord(w => p === 0 ? w/4 : w/4*3), + 15 + ); } else if (matchEnded()) { g.setFontAlign(0,-1); let dur1 = formatDuration(scores[cSet][2] - scores[0][2]); g.setFont('5x9Numeric7Seg',1); - g.drawString(dur1, p === 0 ? w/8 : w/8*5, 10); + g.drawString( + dur1, + getXCoord(w => p === 0 ? w/8 : w/8*5), + 10 + ); g.setFont('Teletext5x9Ascii',1); - g.drawString((currentSet()+1) + ' set' + (currentSet() > 0 ? 's' : ''), p === 0 ? w/8*3 : w/8*7, 12); + g.drawString( + (currentSet()+1) + ' set' + (currentSet() > 0 ? 's' : ''), + getXCoord(w => p === 0 ? w/8*3 : w/8*7), + 12 + ); } g.setFontAlign(p === 0 ? -1 : 1,1); g.setFont('5x9Numeric7Seg',2); - g.drawString(setsWon(p), p === 0 ? 10 : w-8, h-5); + g.drawString( + setsWon(p), + getXCoord(w => p === 0 ? 10 : w-8), + h-5 + ); if (!settings.enableTennisScoring) { g.setFontAlign(p === 0 ? 1 : -1,1); g.setFont('7x11Numeric7Seg',2); - g.drawString(formatNumber(matchScore(p), 3), p === 0 ? w/2 - 8 : w/2 + 11, h-5); + g.drawString( + formatNumber(matchScore(p), 3), + getXCoord(w => p === 0 ? w/2 - 8 : w/2 + 11), + h-5 + ); } } g.setFontAlign(0,0); if (correctionMode) { g.setFont('Teletext5x9Ascii',2); - g.drawString("R", w/2, h-10); + g.drawString( + "R", + getXCoord(w => w/2), + h-10 + ); } let lastShownSet = Math.min( @@ -360,8 +388,8 @@ function draw() { for (let p = 0; p < 2; p++) { if (!setWon(set, p === 0 ? 1 : 0) || matchEnded()) { - let bigNumX = p === 0 ? (w-20)/4+18 : (w-20)/4*3+4; - let smallNumX = p === 0 ? w/2-2 : w/2+3; + let bigNumX = getXCoord(w => p === 0 ? w/4 : w/4*3+3); + let smallNumX = getXCoord(w => p === 0 ? w/2-2 : w/2+3); if (settings.enableTennisScoring && set === cSet && !shouldTiebreak()) { g.setFontAlign(0,0); @@ -411,7 +439,7 @@ function draw() { } // draw separator - g.drawLine(w/2,20,w/2,h-25); + g.drawLine(getXCoord(w => w/2), 20, getXCoord(w => w/2), h-25); g.flip(); } From df13e80b143b613a7d2d8e58edcedb054b28f965 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 16:22:31 +0200 Subject: [PATCH 0186/1062] score: move match sets and time tothe left --- apps/score/score.app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 8c4a16a41..73e1e8794 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -316,20 +316,20 @@ function draw() { 15 ); } else if (matchEnded()) { - g.setFontAlign(0,-1); + g.setFontAlign(1,0); let dur1 = formatDuration(scores[cSet][2] - scores[0][2]); g.setFont('5x9Numeric7Seg',1); g.drawString( dur1, - getXCoord(w => p === 0 ? w/8 : w/8*5), + 40, 10 ); g.setFont('Teletext5x9Ascii',1); g.drawString( (currentSet()+1) + ' set' + (currentSet() > 0 ? 's' : ''), - getXCoord(w => p === 0 ? w/8*3 : w/8*7), + 40, 12 ); From 7f5037066925f26088bc45e87265029795a23579 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 16:42:21 +0200 Subject: [PATCH 0187/1062] score: change b1 control scheme --- apps/score/README.md | 25 ++++++++++++++++--------- apps/score/score.app.js | 19 ++++++++++--------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/apps/score/README.md b/apps/score/README.md index c3ad0e622..1de1ccdb5 100644 --- a/apps/score/README.md +++ b/apps/score/README.md @@ -1,18 +1,25 @@ This app will allow you to keep scores for most kinds of sports. # Keybinds -*On Bangle.js 2 BTN is equivalent to BTN2 on Bangle.js 1* -| Keybinding | Description | -|-------------------|------------------------------| -| `BTN1` | Scroll up | -| `BTN3` | Scroll down | -| `BTN2` | Menu | -| tap on left side | increment left player score | -| tap on right side | increment right player score | - To correct a falsely awarded point simply open and close the menu within .5 seconds. This will put the app into correction mode (indicated by the `R`). In this mode any score increments will be decrements. To move back a set, reduce both players scores to 0, then decrement one of the scores once again. +## Bangle.js 1 +| Keybinding | Description | +|---------------------|------------------------------| +| `BTN1` | Increment left player score | +| `BTN3` | Increment right player score | +| `BTN2` | Menu | +| touch on left side | Scroll up | +| touch on right side | Scroll down | + +## Bangle.js 2 +| Keybinding | Description | +|-------------------------------------|------------------------------| +| `BTN1` | Menu | +| touch on left side of divider line | Increment left player score | +| touch on right side of divider line | Increment right player score | + # Settings | Setting | Description | |------------------------------------|------------------------------------------------------------------------------------------------------------------------------| diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 73e1e8794..072617e6b 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -19,23 +19,28 @@ let correctionMode = false; let w = g.getWidth(); let h = g.getHeight(); +function getXCoord(func) { + let offset = 40; + return func(w-offset)+offset; +}; + function setupInputWatchers() { if (global.BTN4) { setWatch(() => handleInput(2), BTN2, { repeat: true }); - setWatch(() => handleInput(3), BTN1, { repeat: true }); - setWatch(() => handleInput(4), BTN3, { repeat: true }); + setWatch(() => handleInput(0), BTN1, { repeat: true }); + setWatch(() => handleInput(1), BTN3, { repeat: true }); } else { setWatch(() => handleInput(2), BTN, { repeat: true }); } Bangle.on('touch', (b, e) => { if (b) { if (b === 1) { - handleInput(0); + handleInput(3); } else { - handleInput(1); + handleInput(4); } } else { - if (e.x < w/2) { + if (e.x < getXCoord(w => w/2)) { handleInput(0); } else { handleInput(1); @@ -299,10 +304,6 @@ function handleInput(button) { } function draw() { - let getXCoord = function (func) { - let offset = 40; - return func(w-offset)+offset; - }; g.setFontAlign(0,0); g.clear(); From f5b9c5d005e44b756fc4c5dc28e61c0bbed393eb Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 16:44:33 +0200 Subject: [PATCH 0188/1062] score: fix scrolling in correction mode --- apps/score/score.app.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 072617e6b..357cd965b 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -186,7 +186,7 @@ function matchWon(player) { } function matchEnded() { - return matchWon(0) || matchWon(1); + return (matchWon(0) || matchWon(1)) && cSet > (setsWon(0) + setsWon(1) - 1); } function matchScore(player) { @@ -207,17 +207,17 @@ function updateCurrentSet(val) { } setFirstShownSet(); - if (matchEnded()) { - firstShownSet = 0; - } - if (val > 0) { scores[cSet][2] = getTime(); + + if (matchEnded()) { + firstShownSet = 0; + } } } function score(player) { - if (!matchEnded() || correctionMode) { + if (!matchEnded()) { setFirstShownSet(); } From a749a382716664ebba33695e8337d6182d7c39c0 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 16:44:49 +0200 Subject: [PATCH 0189/1062] score: fix/reorder draw function --- apps/score/score.app.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 357cd965b..1ec15adb2 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -319,21 +319,20 @@ function draw() { } else if (matchEnded()) { g.setFontAlign(1,0); + g.setFont('Teletext5x9Ascii',1); + g.drawString( + (currentSet()+1) + ' set' + (currentSet() > 0 ? 's' : ''), + 40, + 8 + ); + let dur1 = formatDuration(scores[cSet][2] - scores[0][2]); g.setFont('5x9Numeric7Seg',1); g.drawString( dur1, 40, - 10 + 18 ); - - g.setFont('Teletext5x9Ascii',1); - g.drawString( - (currentSet()+1) + ' set' + (currentSet() > 0 ? 's' : ''), - 40, - 12 - ); - } g.setFontAlign(p === 0 ? -1 : 1,1); From 46fc78772b7bba41314f4feba4f3bbca0d76e06d Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 16:47:23 +0200 Subject: [PATCH 0190/1062] score: remove unnecessary semicolon --- apps/score/score.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 1ec15adb2..2f07c7de5 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -22,7 +22,7 @@ let h = g.getHeight(); function getXCoord(func) { let offset = 40; return func(w-offset)+offset; -}; +} function setupInputWatchers() { if (global.BTN4) { From fb3f55be7cd6af03638c3869b485de7cce5f7e1c Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 21:07:00 +0200 Subject: [PATCH 0191/1062] score: prevent floating point numbers from getTime() --- apps/score/score.app.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 2f07c7de5..0c5d66169 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -24,6 +24,10 @@ function getXCoord(func) { return func(w-offset)+offset; } +function getSecondsTime() { + return Math.floor(getTime() * 1000); +} + function setupInputWatchers() { if (global.BTN4) { setWatch(() => handleInput(2), BTN2, { repeat: true }); @@ -62,7 +66,7 @@ function setupMatch() { tScores = null; } - scores[0][2] = getTime(); + scores[0][2] = getSecondsTime(); cSet = 0; setFirstShownSet(); @@ -71,7 +75,8 @@ function setupMatch() { } function showSettingsMenu() { - settingsMenuOpened = getTime(); + settingsMenuOpened = getSecondsTime(); + l = null; settingsMenu(function (s, reset) { E.showMenu(); @@ -79,7 +84,7 @@ function showSettingsMenu() { if (reset) { setupMatch(); - } else if (getTime() - settingsMenuOpened < 0.5 || correctionMode) { + } else if (getSecondsTime() - settingsMenuOpened < 500 || correctionMode) { correctionMode = !correctionMode; } @@ -125,7 +130,7 @@ function formatNumber(num, length) { } function formatDuration(duration) { - let durS = Math.floor(duration); + let durS = Math.floor(duration / 1000); let durM = Math.floor(durS / 60); let durH = Math.floor(durM / 60); durS = durS - durM * 60; @@ -208,7 +213,7 @@ function updateCurrentSet(val) { setFirstShownSet(); if (val > 0) { - scores[cSet][2] = getTime(); + scores[cSet][2] = getSecondsTime(); if (matchEnded()) { firstShownSet = 0; From eb384cef31027b03d009cecfa761188e52ccfe8b Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 21:56:18 +0200 Subject: [PATCH 0192/1062] score: fix layout for 176x176 resolution --- apps/score/score.app.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 0c5d66169..8e1535f86 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -344,7 +344,7 @@ function draw() { g.setFont('5x9Numeric7Seg',2); g.drawString( setsWon(p), - getXCoord(w => p === 0 ? 10 : w-8), + getXCoord(w => p === 0 ? 5 : w-3), h-5 ); @@ -353,7 +353,7 @@ function draw() { g.setFont('7x11Numeric7Seg',2); g.drawString( formatNumber(matchScore(p), 3), - getXCoord(w => p === 0 ? w/2 - 8 : w/2 + 11), + getXCoord(w => p === 0 ? w/2 - 3 : w/2 + 6), h-5 ); } @@ -383,17 +383,17 @@ function draw() { let y = (h-15)/(setsOnCurrentPage+1)*(set-firstShownSet+1)+5; - g.setFontAlign(1,0); + g.setFontAlign(-1,0); g.setFont('7x11Numeric7Seg',1); - g.drawString(set+1, 40, y-10); + g.drawString(set+1, 5, y-10); if (scores[set+1][2] != null) { let dur2 = formatDuration(scores[set+1][2] - scores[set][2]); - g.drawString(dur2, 40, y+10); + g.drawString(dur2, 5, y+10); } for (let p = 0; p < 2; p++) { if (!setWon(set, p === 0 ? 1 : 0) || matchEnded()) { - let bigNumX = getXCoord(w => p === 0 ? w/4 : w/4*3+3); + let bigNumX = getXCoord(w => p === 0 ? w/4-12 : w/4*3+15); let smallNumX = getXCoord(w => p === 0 ? w/2-2 : w/2+3); if (settings.enableTennisScoring && set === cSet && !shouldTiebreak()) { From dca65f0490e25747e57f10c08afc6d8dfa293751 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Tue, 28 Sep 2021 21:56:33 +0200 Subject: [PATCH 0193/1062] score: set score limit of 999 --- apps/score/score.settings.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index da35d7a21..d63c52a9b 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -114,6 +114,7 @@ m['Score to win'] = { value: settings.winScore, min:1, + max: 999, onchange: m => setAndSave('winScore', m), }; m['2-point lead'] = { @@ -129,6 +130,7 @@ m['Maximum score'] = { value: settings.maxScore, min: 1, + max: 999, onchange: m => setAndSave('maxScore', m), }; m['Tennis scoring'] = { From a15622f00a89e434f085e4920642dbe4243563a0 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Fri, 1 Oct 2021 22:17:51 +0200 Subject: [PATCH 0194/1062] score: add readme to apps.json --- apps.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps.json b/apps.json index b1e6f70fc..f0690f0d3 100644 --- a/apps.json +++ b/apps.json @@ -3560,6 +3560,7 @@ "icon": "score.app.png", "version":"0.01", "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.", + "readme": "README.md", "tags": "b2", "type": "app", "storage": [ From e7d0903e82939d257b2a73b275b8a3e1fba1fa95 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Fri, 1 Oct 2021 22:18:07 +0200 Subject: [PATCH 0195/1062] score: add data "scores.json" to apps.json --- apps.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps.json b/apps.json index f0690f0d3..611edb53a 100644 --- a/apps.json +++ b/apps.json @@ -3568,6 +3568,9 @@ {"name":"score.settings.js","url":"score.settings.js"}, {"name":"score.presets.json","url":"score.presets.json"}, {"name":"score.img","url":"score.app-icon.js","evaluate":true} + ], + "data": [ + {"name":"score.json"} ] } ] From fbe14a79d1aa096ba3c2bb891a918f5ffc896885 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Sun, 3 Oct 2021 15:39:07 +0200 Subject: [PATCH 0196/1062] score: fix scrolling on b2, change b2 detection --- apps/score/score.app.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 8e1535f86..24e49fa66 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -29,15 +29,11 @@ function getSecondsTime() { } function setupInputWatchers() { - if (global.BTN4) { - setWatch(() => handleInput(2), BTN2, { repeat: true }); - setWatch(() => handleInput(0), BTN1, { repeat: true }); - setWatch(() => handleInput(1), BTN3, { repeat: true }); - } else { - setWatch(() => handleInput(2), BTN, { repeat: true }); - } + isBangle1 = !!global.BTN4; + setWatch(() => handleInput(2), isBangle1 != null ? BTN2 : BTN, { repeat: true }); + Bangle.setUI('updown', v => v && handleInput(Math.floor((v+2)/2)+(isBangle1 ? 0 : 3))); Bangle.on('touch', (b, e) => { - if (b) { + if (isBangle1) { if (b === 1) { handleInput(3); } else { From 01d33cbbfa0864b793c2022296607aae0330d3d9 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Sat, 9 Oct 2021 20:01:48 +0200 Subject: [PATCH 0197/1062] score: call setUI after menu --- apps/score/score.app.js | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 24e49fa66..b48995849 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -28,25 +28,27 @@ function getSecondsTime() { return Math.floor(getTime() * 1000); } -function setupInputWatchers() { +function setupInputWatchers(init) { isBangle1 = !!global.BTN4; - setWatch(() => handleInput(2), isBangle1 != null ? BTN2 : BTN, { repeat: true }); Bangle.setUI('updown', v => v && handleInput(Math.floor((v+2)/2)+(isBangle1 ? 0 : 3))); - Bangle.on('touch', (b, e) => { - if (isBangle1) { - if (b === 1) { - handleInput(3); + if (init) { + setWatch(() => handleInput(2), isBangle1 != null ? BTN2 : BTN, { repeat: true }); + Bangle.on('touch', (b, e) => { + if (isBangle1) { + if (b === 1) { + handleInput(3); + } else { + handleInput(4); + } } else { - handleInput(4); + if (e.x < getXCoord(w => w/2)) { + handleInput(0); + } else { + handleInput(1); + } } - } else { - if (e.x < getXCoord(w => w/2)) { - handleInput(0); - } else { - handleInput(1); - } - } - }); + }); + } } function setupMatch() { @@ -87,6 +89,8 @@ function showSettingsMenu() { settingsMenuOpened = null; draw(); + + setupInputWatchers(); }, function (msg) { switch (msg) { case 'end_set': @@ -445,6 +449,6 @@ function draw() { g.flip(); } -setupInputWatchers(); +setupInputWatchers(true); setupMatch(); draw(); From 88f17232bc6d99def85f20f1ed83ee1a546a4cac Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Sun, 10 Oct 2021 17:16:09 +0200 Subject: [PATCH 0198/1062] score: update b1 detection to use process.env --- apps/score/score.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index b48995849..7da068294 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -29,7 +29,7 @@ function getSecondsTime() { } function setupInputWatchers(init) { - isBangle1 = !!global.BTN4; + isBangle1 = process.env.BOARD === 'BANGLEJS'; Bangle.setUI('updown', v => v && handleInput(Math.floor((v+2)/2)+(isBangle1 ? 0 : 3))); if (init) { setWatch(() => handleInput(2), isBangle1 != null ? BTN2 : BTN, { repeat: true }); From 23ec132b4d7a7f4574a8bcc8c0e079612d27e8ea Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Sun, 10 Oct 2021 17:19:03 +0200 Subject: [PATCH 0199/1062] score: switch score buttons --- apps/score/score.app.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 7da068294..0c2d8c78d 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -30,7 +30,15 @@ function getSecondsTime() { function setupInputWatchers(init) { isBangle1 = process.env.BOARD === 'BANGLEJS'; - Bangle.setUI('updown', v => v && handleInput(Math.floor((v+2)/2)+(isBangle1 ? 0 : 3))); + Bangle.setUI('updown', v => { + if (v) { + if (isBangle1) { + handleInput(Math.floor(((v*-1)+2)/2)) + } else { + handleInput(Math.floor((v+2)/2)+3) + } + } + }); if (init) { setWatch(() => handleInput(2), isBangle1 != null ? BTN2 : BTN, { repeat: true }); Bangle.on('touch', (b, e) => { From f6f1a1c352b4f43037280e6bcd198ebe86b405f7 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Sun, 10 Oct 2021 17:26:35 +0200 Subject: [PATCH 0200/1062] score: allow user to mirror score buttons on b1 --- apps/score/score.app.js | 5 +++-- apps/score/score.settings.js | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 0c2d8c78d..4f1fd7b7b 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -33,9 +33,10 @@ function setupInputWatchers(init) { Bangle.setUI('updown', v => { if (v) { if (isBangle1) { - handleInput(Math.floor(((v*-1)+2)/2)) + let i = settings.mirrorScoreButtons ? v * -1 : v; + handleInput(Math.floor((i+2)/2)); } else { - handleInput(Math.floor((v+2)/2)+3) + handleInput(Math.floor((v+2)/2)+3); } } }); diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index d63c52a9b..3fc04839b 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -1,6 +1,13 @@ (function () { return (function (back, inApp, ret) { + const isBangle1 = process.env.BOARD === 'BANGLEJS' + function fillSettingsWithDefaults(settings) { + if (isBangle1) { + if (settings.mirrorScoreButtons == null) { + settings.mirrorScoreButtons = false; + } + } if (settings.winSets == null) { settings.winSets = 2; } @@ -100,6 +107,13 @@ } m['< Back'] = function () { back(settings, changed); }; m['Presets'] = function () { E.showMenu(presetMenu(back)); }; + if (isBangle1) { + m['Mirror Buttons'] = { + value: settings.mirrorScoreButtons, + format: m => offon[~~m], + onchange: m => setAndSave('mirrorScoreButtons', m), + }; + } m['Sets to win'] = { value: settings.winSets, min:1, From f5fefc7c993946a66ca7b62cf0f982d030ff05b8 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Sun, 10 Oct 2021 17:27:50 +0200 Subject: [PATCH 0201/1062] score: keep display powered on --- apps/score/score.app.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 4f1fd7b7b..87fdecf0c 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -19,6 +19,8 @@ let correctionMode = false; let w = g.getWidth(); let h = g.getHeight(); +let isBangle1 = process.env.BOARD === 'BANGLEJS'; + function getXCoord(func) { let offset = 40; return func(w-offset)+offset; @@ -29,7 +31,6 @@ function getSecondsTime() { } function setupInputWatchers(init) { - isBangle1 = process.env.BOARD === 'BANGLEJS'; Bangle.setUI('updown', v => { if (v) { if (isBangle1) { @@ -458,6 +459,12 @@ function draw() { g.flip(); } +// make sure LCD on Bangle.js 1 stays on +if (isBangle1) { + Bangle.setLCDTimeout(0); + Bangle.setLCDPower(true); +} + setupInputWatchers(true); setupMatch(); draw(); From a575f323d85fd00ac637bb68e9733e2446ef10c1 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Sun, 10 Oct 2021 17:41:54 +0200 Subject: [PATCH 0202/1062] score: don't reset match when changing button assignment --- apps/score/score.settings.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index 3fc04839b..20482db65 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -63,8 +63,10 @@ require('Storage').writeJSON(fileName, settings); } - function setAndSave(key, value) { - changed = true; + function setAndSave(key, value, notChanged) { + if (!notChanged) { + changed = true; + } settings[key] = value; if (key === 'winScore' && settings.maxScore < value) { settings.maxScore = value; @@ -111,7 +113,7 @@ m['Mirror Buttons'] = { value: settings.mirrorScoreButtons, format: m => offon[~~m], - onchange: m => setAndSave('mirrorScoreButtons', m), + onchange: m => setAndSave('mirrorScoreButtons', m, true), }; } m['Sets to win'] = { From 729f5baf16350329fb4f49d2d5ec754aacb8fac7 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Sun, 10 Oct 2021 17:42:21 +0200 Subject: [PATCH 0203/1062] score: keep specific settings when selecting new preset --- apps/score/score.settings.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index 20482db65..982652b94 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -90,7 +90,11 @@ m[presetNames[i]] = (function (i) { return function() { changed = true; + let mirrorScoreButtons = settings.mirrorScoreButtons; + settings = fillSettingsWithDefaults(presets[presetNames[i]]); + + settings.mirrorScoreButtons = mirrorScoreButtons; save(settings); ret(true); }; From ed9566dbb3486e8d2d82cdbb5b18bc49d3ffa814 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Sun, 10 Oct 2021 17:44:03 +0200 Subject: [PATCH 0204/1062] score: change default score button assignment --- apps/score/score.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 87fdecf0c..d3bc5d1e1 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -34,7 +34,7 @@ function setupInputWatchers(init) { Bangle.setUI('updown', v => { if (v) { if (isBangle1) { - let i = settings.mirrorScoreButtons ? v * -1 : v; + let i = settings.mirrorScoreButtons ? v : v * -1; handleInput(Math.floor((i+2)/2)); } else { handleInput(Math.floor((v+2)/2)+3); From 52bdf22352b8d8752b62189b7daffda90a68d033 Mon Sep 17 00:00:00 2001 From: Mika Dede Date: Sun, 10 Oct 2021 20:39:53 +0200 Subject: [PATCH 0205/1062] score: allow user to choose if screen of b1 should stay on --- apps/score/score.app.js | 20 ++++++++++++++------ apps/score/score.settings.js | 10 ++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index d3bc5d1e1..3a9b71505 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -30,6 +30,18 @@ function getSecondsTime() { return Math.floor(getTime() * 1000); } +function setupDisplay() { + // make sure LCD on Bangle.js 1 stays on + if (isBangle1) { + if (settings.keepDisplayOn) { + Bangle.setLCDTimeout(0); + Bangle.setLCDPower(true); + } else { + Bangle.setLCDTimeout(10); + } + } +} + function setupInputWatchers(init) { Bangle.setUI('updown', v => { if (v) { @@ -100,6 +112,7 @@ function showSettingsMenu() { draw(); + setupDisplay(); setupInputWatchers(); }, function (msg) { switch (msg) { @@ -459,12 +472,7 @@ function draw() { g.flip(); } -// make sure LCD on Bangle.js 1 stays on -if (isBangle1) { - Bangle.setLCDTimeout(0); - Bangle.setLCDPower(true); -} - +setupDisplay(); setupInputWatchers(true); setupMatch(); draw(); diff --git a/apps/score/score.settings.js b/apps/score/score.settings.js index 982652b94..88e367821 100644 --- a/apps/score/score.settings.js +++ b/apps/score/score.settings.js @@ -7,6 +7,9 @@ if (settings.mirrorScoreButtons == null) { settings.mirrorScoreButtons = false; } + if (settings.keepDisplayOn == null) { + settings.keepDisplayOn = true; + } } if (settings.winSets == null) { settings.winSets = 2; @@ -91,10 +94,12 @@ return function() { changed = true; let mirrorScoreButtons = settings.mirrorScoreButtons; + let keepDisplayOn = settings.keepDisplayOn; settings = fillSettingsWithDefaults(presets[presetNames[i]]); settings.mirrorScoreButtons = mirrorScoreButtons; + settings.keepDisplayOn = keepDisplayOn; save(settings); ret(true); }; @@ -119,6 +124,11 @@ format: m => offon[~~m], onchange: m => setAndSave('mirrorScoreButtons', m, true), }; + m['Keep display on'] = { + value: settings.keepDisplayOn, + format: m => offon[~~m], + onchange: m => setAndSave('keepDisplayOn', m, true), + } } m['Sets to win'] = { value: settings.winSets, From e8d8de648ba820c7be585e7f1b19a902317d3f76 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 11 Oct 2021 14:00:40 +0100 Subject: [PATCH 0206/1062] small tweak for Bangle2 --- apps/score/score.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/score/score.app.js b/apps/score/score.app.js index 3a9b71505..5a0f9c1e1 100644 --- a/apps/score/score.app.js +++ b/apps/score/score.app.js @@ -54,7 +54,7 @@ function setupInputWatchers(init) { } }); if (init) { - setWatch(() => handleInput(2), isBangle1 != null ? BTN2 : BTN, { repeat: true }); + setWatch(() => handleInput(2), isBangle1 ? BTN2 : BTN, { repeat: true }); Bangle.on('touch', (b, e) => { if (isBangle1) { if (b === 1) { From 54d98db6c84a6ab1d984472025faba6a30b9c9c4 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 11 Oct 2021 14:29:58 +0100 Subject: [PATCH 0207/1062] Bangle.js2 tweaks --- apps.json | 6 +++--- apps/gpsrec/ChangeLog | 1 + apps/gpsrec/app.js | 35 ++++++++++++++++++++--------------- apps/widbt/ChangeLog | 1 + apps/widbt/widget.js | 4 ++-- apps/widid/ChangeLog | 1 + apps/widid/widget.js | 3 +-- 7 files changed, 29 insertions(+), 22 deletions(-) diff --git a/apps.json b/apps.json index 611edb53a..1e2612fce 100644 --- a/apps.json +++ b/apps.json @@ -511,7 +511,7 @@ { "id": "gpsrec", "name": "GPS Recorder", "icon": "app.png", - "version":"0.23", + "version":"0.24", "interface": "interface.html", "description": "Application that allows you to record a GPS track. Can run in background", "tags": "tool,outdoors,gps,widget", @@ -687,7 +687,7 @@ { "id": "widbt", "name": "Bluetooth Widget", "icon": "widget.png", - "version":"0.05", + "version":"0.06", "description": "Show the current Bluetooth connection status in the top right of the clock", "tags": "widget,bluetooth,b2", "type":"widget", @@ -1316,7 +1316,7 @@ { "id": "widid", "name": "Bluetooth ID Widget", "icon": "widget.png", - "version":"0.02", + "version":"0.03", "description": "Display the last two tuple of your Bangle.js MAC address in the widget section. This is useful for figuring out which Bangle.js to connect to if you have more than one Bangle.js!", "tags": "widget,address,mac", "type":"widget", diff --git a/apps/gpsrec/ChangeLog b/apps/gpsrec/ChangeLog index 8d13df000..c91003914 100644 --- a/apps/gpsrec/ChangeLog +++ b/apps/gpsrec/ChangeLog @@ -25,3 +25,4 @@ 0.21: Fix issue where a period of 1s recorded every 2s, 5s every 6s, and so on 0.22: Ensure Bangle.setGPSPower uses 'gpsrec' as a tag 0.23: Fix issue where tracks wouldn't record when running from OpenStMap if a period hadn't been set up first +0.24: Better support for Bangle.js 2, avoid widget area for Graphs, smooth graphs more diff --git a/apps/gpsrec/app.js b/apps/gpsrec/app.js index 29594289d..164124257 100644 --- a/apps/gpsrec/app.js +++ b/apps/gpsrec/app.js @@ -102,7 +102,8 @@ function getTrackInfo(fn) { var lfactor = Math.cos(minLat*Math.PI/180); var ylen = (maxLat-minLat); var xlen = (maxLong-minLong)* lfactor; - var scale = xlen>ylen ? 200/xlen : 200/ylen; + var screenSize = g.getHeight()-48; // 24 for widgets, plus a border + var scale = xlen>ylen ? screenSize/xlen : screenSize/ylen; return { fn : fn, filename : filename, @@ -110,6 +111,7 @@ function getTrackInfo(fn) { records : nl, minLat : minLat, maxLat : maxLat, minLong : minLong, maxLong : maxLong, + lat : (minLat+maxLat)/2, lon : (minLong+maxLong)/2, lfactor : lfactor, scale : scale, duration : Math.round(duration/1000) @@ -180,16 +182,18 @@ function plotTrack(info) { getMapXY = osm.latLonToXY.bind(osm); } else { getMapXY = function(lat, lon) { "ram" - var ix = 30 + Math.round((long - info.minLong)*info.lfactor*info.scale); - var iy = 210 - Math.round((lat - info.minLat)*info.scale); - return {x:ix, y:iy}; + return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale), + y:cy + Math.round((info.lat - lat)*info.scale)}; } } E.showMenu(); // remove menu + E.showMessage("Drawing...","GPS Track "+info.fn); + g.flip(); // on buffered screens, draw a not saying we're busy + g.clear(1); var s = require("Storage"); var cx = g.getWidth()/2; - var cy = g.getHeight()/2; + var cy = 24 + (g.getHeight()-24)/2; g.setColor(1,0.5,0.5); g.setFont("Vector",16); g.drawString("Track"+info.fn.toString()+" - Loading",10,220); @@ -203,8 +207,8 @@ function plotTrack(info) { g.drawString("N",2,40); g.setColor(1,1,1); } else { - osm.lat = (info.minLat+info.maxLat)/2; - osm.lon = (info.minLong+info.maxLong)/2; + osm.lat = info.lat; + osm.lon = info.lon; osm.draw(); g.setColor(0, 0, 0); } @@ -251,7 +255,8 @@ function plotTrack(info) { g.drawString("Back",230,200); setWatch(function() { viewTrack(info.fn, info); - }, BTN3); + }, global.BTN3||BTN1); + Bangle.drawWidgets(); g.flip(); } @@ -260,8 +265,8 @@ function plotGraph(info, style) { E.showMenu(); // remove menu E.showMessage("Calculating...","GPS Track "+info.fn); var filename = getFN(info.fn); - var infn = new Float32Array(200); - var infc = new Uint16Array(200); + var infn = new Float32Array(80); + var infc = new Uint16Array(80); var title; var lt = 0; // last time var tn = 0; // count for each time period @@ -278,7 +283,7 @@ function plotGraph(info, style) { title = "Altitude (m)"; while(l!==undefined) { ++nl;c=l.split(","); - i = Math.round(200*(c[0]/1000 - strt)/dur); + i = Math.round(80*(c[0]/1000 - strt)/dur); infn[i]+=+c[3]; infc[i]++; l = f.readLine(f); @@ -289,7 +294,7 @@ function plotGraph(info, style) { var t,dx,dy,d,lt = c[0]/1000; while(l!==undefined) { ++nl;c=l.split(","); - i = Math.round(200*(c[0]/1000 - strt)/dur); + i = Math.round(80*(c[0]/1000 - strt)/dur); t = c[0]/1000; p = Bangle.project({lat:c[1],lon:c[2]}); dx = p.x-lp.x; @@ -320,9 +325,9 @@ function plotGraph(info, style) { // draw g.clear(1).setFont("6x8",1); var r = require("graph").drawLine(g, infn, { - x:4,y:0, + x:4,y:24, width: g.getWidth()-24, - height: g.getHeight()-8, + height: g.getHeight()-(24+8), axes : true, gridy : grid, gridx : 50, @@ -334,7 +339,7 @@ function plotGraph(info, style) { g.drawString("Back",230,200); setWatch(function() { viewTrack(info.fn, info); - }, BTN3); + }, global.BTN3||BTN1); g.flip(); } diff --git a/apps/widbt/ChangeLog b/apps/widbt/ChangeLog index e639f4044..4509487ac 100644 --- a/apps/widbt/ChangeLog +++ b/apps/widbt/ChangeLog @@ -2,3 +2,4 @@ 0.03: Ensure redrawing works with variable size widget system 0.04: Fix automatic update of Bluetooth connection status 0.05: Make Bluetooth widget thinner, and when on a bright theme use light grey for disabled color +0.06: Tweaking colors for dark/light themes and low bpp screens diff --git a/apps/widbt/widget.js b/apps/widbt/widget.js index 89a3cbdb8..a25d2c21c 100644 --- a/apps/widbt/widget.js +++ b/apps/widbt/widget.js @@ -2,9 +2,9 @@ function draw() { g.reset(); if (NRF.getSecurityStatus().connected) - g.setColor("#07f"); + g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); else - g.setColor(g.theme.bg ? "#AAA" : "#555"); + g.setColor(g.theme.dark ? "#666" : "#999"); g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); } function changed() { diff --git a/apps/widid/ChangeLog b/apps/widid/ChangeLog index 4152a8406..56f114d5f 100644 --- a/apps/widid/ChangeLog +++ b/apps/widid/ChangeLog @@ -1,2 +1,3 @@ 0.01: New Widget! 0.02: Tweaks for variable size widget system +0.03: Tweaking colors for dark/light themes diff --git a/apps/widid/widget.js b/apps/widid/widget.js index d4f4d6386..1c7a721bc 100644 --- a/apps/widid/widget.js +++ b/apps/widid/widget.js @@ -1,8 +1,7 @@ -/* jshint esversion: 6 */ (() => { function draw() { var id = NRF.getAddress().substr().substr(12).split(":"); - g.reset().setColor(0, 0.49, 1).setFont("6x8", 1); + g.reset().setColor(g.theme.dark ? "#0ff" : "#00f").setFont("6x8", 1); g.drawString(id[0], this.x+2, this.y+4, true); g.drawString(id[1], this.x+2, this.y+14, true); } From b2d389d56ea81855f36a2c44b6e69fe6bec4fcac Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 11 Oct 2021 14:31:10 +0100 Subject: [PATCH 0208/1062] Widbatpc: added option to fillbar with level color, see README for screenshots --- apps.json | 2 +- apps/widbatpc/ChangeLog | 1 + apps/widbatpc/README.md | 10 +++++++++- apps/widbatpc/screenshot.jpg | Bin 15828 -> 0 bytes apps/widbatpc/settings.js | 6 ++++++ apps/widbatpc/widbatpc.full.jpg | Bin 0 -> 3299 bytes apps/widbatpc/widbatpc.part.jpg | Bin 0 -> 2623 bytes apps/widbatpc/widget.js | 7 ++++++- 8 files changed, 23 insertions(+), 3 deletions(-) delete mode 100644 apps/widbatpc/screenshot.jpg create mode 100644 apps/widbatpc/widbatpc.full.jpg create mode 100644 apps/widbatpc/widbatpc.part.jpg diff --git a/apps.json b/apps.json index 876b243ae..7cc5e67f7 100644 --- a/apps.json +++ b/apps.json @@ -653,7 +653,7 @@ "name": "Battery Level Widget (with percentage)", "shortName": "Battery Widget", "icon": "widget.png", - "version":"0.12", + "version":"0.13", "description": "Show the current battery level and charging status in the top right of the clock, with charge percentage", "tags": "widget,battery,b2", "type":"widget", diff --git a/apps/widbatpc/ChangeLog b/apps/widbatpc/ChangeLog index 64482db71..09e4fabf4 100644 --- a/apps/widbatpc/ChangeLog +++ b/apps/widbatpc/ChangeLog @@ -9,3 +9,4 @@ 0.10: Add 'hide if charge greater than' 0.11: Don't overwrite existing settings on app update 0.12: Fixed for Bangle 2 +0.13: Fillbar setting added, see README diff --git a/apps/widbatpc/README.md b/apps/widbatpc/README.md index 6e8fd10cc..c75154f72 100644 --- a/apps/widbatpc/README.md +++ b/apps/widbatpc/README.md @@ -4,5 +4,13 @@ Show the current battery level and charging status in the top right of the clock Works with Bangle 1 and Bangle 2 -![](screenshot.jpg) +When the fillbar setting is on the level colour will fill the entire +bar. This makes for an easier to read dsiplay when the charge is +below 50%. + +![](widbatpc.full.jpg) + +When the fillbar setting is off the level colour will follow the battry percentage + +![](widbatpc.part.jpg) diff --git a/apps/widbatpc/screenshot.jpg b/apps/widbatpc/screenshot.jpg deleted file mode 100644 index 48f9893ecf124d426541f5fa7f455a938d2fa919..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15828 zcmb`uWmFx(voAUucXyYK6WrZlL$HlQaQEOEEO>B-;1b+J2=4Cg4oT1e!S(S!cb#?a zhx^w1a9{PB>EBdWRd-L@Oij=0^6NH$r6>oH13(}E0KEm^br*&aA}wvK233`VyqEp2 z1Q;UVjbN|ayKY9I!{!?N!OD8Rjw^;Y>AOqY0C?F3=z2W~~>3{C0b0Gi- zTmt}X!vFBhG6A4D3;>8$|Az<82LQ}a0QfQW(bU!SzX}8Qc80OC0)Wd>06^CR0NiN+ zKr#65g8q;0|Fp&bOI@knbjaT7>HHR~0SCYmpaLL(6JQRoy&*1u1KxL;}H}AOmmd z{8#|Y8-ZY9Vc|e9aENcs4f7^}jetXqh)V+?apG}FBI6U#s!N%o5Q3pZ#Pr-|!98z^ zWN!ln{w>@8Hv86CZ>s-g`z=)%`#%@32=H()02~7Bn-B<_8i3)HgjF}iaS2W=IERC; z@0mfsrP<-Sz{8h<5^(dFg(T5Ro4>9CXt1ES3cz9m;y_E9Q>|67MJAC=izuQCjTiEV z%tsH@dd6uJui7Z#yL*Y+PgS4fz;y49(IKp>NpcCK2uP_BdP)@(3(JWGY@go+cVK=5 zl(5_(A0=HjcEMO`eeCQBsoy;HNCbrzEZM+Vz`q9FSAewz^Jfin9~;U$(^r*=j=|6p(FNah zA9a%lsSE=_a%)CHNo(SUq3PQ%g@W&;dXoBo8A2Loh%88`UjZF^NMd=Izm8wpZj+03 zG*+B>HLFLz!#GiCrfCwt8frPqnm+2aAF^f2azwq`(#Fve)OQx|M z3-M~7$|b-A`Ys4x#ASGRLouXJA`@3qbD37W%Y~eu^-p- z!$e&$cbiYH75~?u@?Cqr<)H^_-$!C*dK7lmuDKu#ZoED^X14ej-%XKCF()3TxFl`O zDxBydzq_u*r*b#4ok*Grfq^HJ_$(@Z33)59B(p6dQ~avW?G2bb0)J3 zpjvOS$C^n=Nj3iJ!fNKn-=A4bn(d=(#PQM!f0pDXR9Gwa_;mf_lHvi0`n3KWO%E0E zZ_t=>ZxQ`fjf?mXkA?u@mg){S=l;xBfO+{Tu8Taa<1-VQcI?PuHOok$L<={|>v z^kV0SPO0LnN?#g0e2-GG8@y%{AuM;4R1y;+yd$}&1TYM%P9-~|6jYI#{5pzzb}aMJ ze=j~e<2&aC#9{b{SqfBLlts%=keVh$3nv1im-tyO=L?*gY%JGELkrgo8Jo)IYeEh% z5aLSZhGO4Qcudm6^0j9#&?Ku@XpUAlrjcjz7q1)>NvWf^PLlfRz}i}Zbi7De$t#dl zlBax{Z7X~DNZ@ny&^%bk%^LGk?Tc^68*E{#?E5r2lC~!M5NSss-)uyeSd^w|zbHhc zZ6Ml;$*LZr^`$B75tYn22g?Ox0`#jpNlkYPs<>7qVI&ki5L(mwCCb7(eeI_DyL4%f zq%fR}jNiKrNe_(h3vkF0FG#a%3aM8kbIzS@G-WuG)(hvXC8;tpJEU1D$#75jUg2nT zqTuvNipMD+LI6ooSe%F_87B-I<9go9Zh*7VlD?j7)+0$ijxbIX@g~GCAsm9H_#y0U z4Y4PA=(pp=Q;*!=CAs0>){h{`E-O+m)+)DHoNX?~fIn@3D#2PYxD*-`gbJhUaU@Bn zSSDnZX6RRi;`*36Ws`DhR_%LwTwB^1ZJI22MBu~>RmHQ7>VxW@T#EG1=)o5aniVM= zB&tisO^kG@MwfsWpf6HMde!2KTWR+fRL~ZB9i2~$F_7P^AeU(rgilvO zZTy$XQ7uaMQau|^~YugW$G=)jL1})3pS!pTsL0U;s1^<0kB1)UW%IXY^x(5A|t3Zs($@->@Op5_~i!l z&YZ~*y3gu|YFCwmJk6{!np@hJZ^8v}!@xKARZnBqVe`v7Jx+h!Yi_}NC%4%@z9itQ zWpRQYUf<(q_I~zvnIYOiOS#UjzOO*pPZkKuegkEBH3y1U7H)3DEdf9ug3A?9c$r=K z71#7G$MYSEkeL8KM)!gXma}6TnHWk8av_CmEUl)fxty^T5*#}f5C(2hp$p$8T|6Iz zhRN2D#;FdHBwto#51VCc>ERy)jKmd@=I(&3Y<6OYjbh58JFo&^Y@GJPWVK$X;IP$l zR{5*5aRs!8HKV>NzP~DpvElf{4R$2?bHWX|P{|LRMJ08}2W1krN+A{4)M6632$t`? zL%Vyakp4{8P|i|MZN-8MgGFA%Nc|ntOeN?S6eAGZOt1UneWY5!i)u{57?@ixR+Fnq z@C&{n#i-^Vhmh6T+#x#9fnc~!Y_7b^qw@18;j^$Rg)4celEL!)n4Ux7nfM5R7#aFsA` z#o1C&T;Mo|3X`7n!o->=K{jLJ(p15g8%4^efs4J2tFE+xtS`hd5bkA9i0vhYAUK`Q zn|8vqdJ4UD%Ctfc&!JE$uH(UgUFA6}#Pr&gqqHXrTKqnwqI`1F*+4cq+8syN^)i`6 z?(cFK6r6=fF-L0n0YsneHH~6{Qik`5r#oSQ-R+O$0UB2Y@J!L)IPk76a0c1bq$O&Z zzz0hJ2V--G%aa6WP?K`hxtlzGX_$; zG}k(`?{#}F#saH|sw{mh8sUg1rKXodATstsiA$xvk14_lFKFwdpw(EWYv4tq2-I{0xvrpQB>%S;U|FLVj>9jZtOj)!L z(vCsS^hWpfD70#7g@;s9-&V$-CmuSvsx#)c+U0SBgYWT}dOr9m9d{yjo7}x;`gs2F zfRwd>;F=Y6pXj&lLX+$5AAKXHk*Q8Q~zC zhHOV!nbpfvO#OgjmkmDjN(~}p0BSDnR;rv66UsY$gYrpR5}e+40(Y$;4eJTY!c0K= zQQ0UeZdso;<#T7%DJddiY6->Z;7uuttnG;M^KB0`<~#Y+*(x^G)TnK3RL<~>7}<50 zXc^|3{Vs(xX_B~c;nr{QGyBv?MeBzfRBB_8h9rs!ZU_mCX9B#A##)`*H?&nTPhwAL zd^j9qyARLL)N+4T2)J5YxQ64tRMdNkrlv7?dea+K44`$hKWcnZ3>GA&Pl+hrkFqo1L_htlWZb?LSaVXW(-m<_5?Q!!*F*Lq2ly5KM z?~&^FO(pWa9r&_SuoYFAc)^6SDKM0?;WeBPym)Pykl&yXtm4z1U6aR+3&2fFYzT&O z%ALkk;v~+}`+uOpeeAvoO)N~ig{88f%B}u}Q6;S$MeS+}y>%B7Htb#mgXyaPJ1=j- zX8L1n2TuV3shuz(b+Ks0{%}A5PDZJb$)vviR@$yZCgplTgkpR>i!HXoU`~fZ z%MPFW#v-_$>@ZoMY33In9Ut$irO|Lt|DB<`TwA40@-jUb9HDt6S*e|R;Ut=cx+q*% zR`M=OwN+uKnUz&Z@`;h8r_c=ur66C{TfJwMx-2A(xQU$0Jb2mp8JbDKKgIa(jGH%e zw=roiyA(0EpES)ZO*8yp-AYZKY_5mbLbqzUn&*re4kxe#@Nogx>088mie5%dgo%x| z>O4x0u6a`zA~m4xe%`olEW!kOz=aA8=U4`5d^ z)y1ea-E#Hh1dbO6cP*>3w8B`%blHQtJ>%3igt_m}p5<=mLwa_3X7OA)D~t;tM=q`v z@G2o>Tz=(@CKZ#^b>(#>qiMH&v_#z>H!>9UG#jH+nr(F^@f7j6$uM({c+;4D7>?ka zl4Q+jG-R552uOMNxWHe|w9@*06BW`M!VuCgSU2|j=JJD84tS&o=`r66B9>8vUseUa z+sRy?zEWKU$q{qoF8u+66->r5>(IsV?je_Jg18ufEw4d#FJ<9))EI$Q`KBMdtTq9Av0u=k+2AW6ugVrt@ zJr5O)x|tSC0WlgQaHtP{^-4Gv$9uLP9&xEMiPjcR+7`Jup&GoiZHGn%t+LjS-ygsB zJh%HQTW7Rtbd9+pY@&wkI1%fM)KWLR&%Mq%r?Y(p8g^;rM}E;M;T7eQ%T0YobaX&_jwZ@%PGv?9@fU;cDD#h7;<@eRrK2_0gff zCQbP*xFtBIJMr6X$(SyE%!skkf(>7m>k$i#U!fH)P!v+ajI~sxSfvgv(ryyE-c~ic z{0keg!(q{zq|P=h2WTZ;qd?ruZT`hm?zxC)GjWd>Zaf&WsF{H$FAeyu^oby%B|2i) zVavy)geseWyW~PNzRBO3KdVMt`ax9J^S_A^N_|duhKA0GmQyXmvNmoslO`%b`Z57M z>#`*fG%Vtjw5E&F|}3=P(kVCPUI&*6rajb5at(&<%bH z76#kkUR|Jn&N#k6KPEy$Mr_lKrL#n<2jyD?lp#I@8@3G+H_zyT>R=9~9hA51VRIS6 zEaJre4Q@AEsoENP74pH^=`z=bJ)P9Sd7@sWMB_!^q`FKzqdt8MHjdoo*JZtY!X3UT zI5{Hvv7cPpKdh3`n6w?wSh;lQ`4iOEO;6SZ@@WBH6hJ5glP@p^Ao&0e>ual*cpC3D#R=oW{Ne zG4a9+QUD1LUq42{GM6hB3R&bclz)cA%p-u?6nSim>c%;i`!kfu+@!w1UC6O6h8AD? zUE43~-;kT+_H&JCXV)tXJA5x^{S|}n{Usx98rid6ZEeacg4D`2Z|~xwz$=zbP~>~x zTn$>w24Pfs7xPQsi#j*0PMB6;JI^Q4(f&v1vOIC$+beN8atGVf&P~bGA~qx13&qj? zxqlZ^SZAb~aj))ITqp1{0#9RnBUCHqhOurC%5$~_!~f&aZnCx2UDMk|Ko*yQ!$+rB zxb(MC<#I0auj{Y#r7haiZANb6K7vj9vm<8%R!#lmUpST_N^k2WksvF|4OMNUW-7t) zV%?8}$+pMe1f{dDKxcdlrqaislcGcaUXrX`bjzHUDqaeo8$UJs=4I3>zCisaTK^7z zlD?`;`CS?^sj~64Q6~?4p=8zHDB@^3k!XP1=ZHg}rs1A}obE!^_-FUP>=%LH$_lp% zNOrk@AQ2k+TwbE4{e#Lm3N<+p>f9oxuCgdjXK)(r%WpTKxXQm=yW;QKVH@+sOz2;{ z#H^hX6J3&iyuMAzLdG}9RL9k$SviZ~ zoW2-UeUwGxT)}yBp8X)F5|3G792fVARg1K+{25Q&L6t{wQ$N8PGoh8D8%%pHRUEhL zI_k&ZN-ACPCxPEgFRFh2xYTix#)5$OccMx;l4GxJIgIe7=gQ5kp@OIyR&7f#n=Szr z{4{`_-}fiP41MzPNdhgXL-_A4rEu|Emr+>hH^8Hp!`HtN*n zUWTUO`y^CKRf4&Q@wF)U0pTd(MD@I*@u<5_)h#m?RSvgJ!vh>=++Xd?d?g0^o)uY_&R$`(a<`Hvw+CBsflyQB&m0<>0Be=6o ziF^L$=tsQZgh1~dd}md3gUk~)q14!R!X&k%GPC{=e1&>1&~huSoN8pBhtyGduH3|j zX(BRx99@YT~4MeDl6sxJ^BM%d%gn7%fp>O;qy<(ASn5 zm@&b_)46#n@sfQPXwAvKA z+h-eWOHvBc=px~2g4A*)4THC_EMYU?G))s*_d_wx5^2mTvziiuU)IR!5@PFNYajL~ zv3POns2s}76c`*NOOOy+m%s#O@MMwIf=KB#r_V2oS(>$YbIYmfSe~{-vG9jKo)3ay zjf74JpcU?fmTA(S0DTLFe)mqPb&lCKy-;e^hcwZ9*QQ-FxpY^q#)M7~3o4?a?PnOc z@%+NognYqgRqft@m|f0u3g4z>m z#`(X36%W0kNcT6gIhKhg?m&2yUsVjxn$g(_YFzhcW}&1m>IQP{{;HAw&TFG6G?WM1 zK4p{j>^<6*T+(S}O6UPayE+6W4YfpyLQv$!71sfz?P1tF7e&VdGUl#cdiNI-R(jM2 zJ%hN+poFJ@L~jSxNN|={7Dfkx4KlnTH)HejSfRIIeMI|82cK}`aSY0g{E4SNuEr_R z(jz(9NCB6Gagl)cougIii&41r0o!eWizc6~_Rf z3Y>4ij&E0)!jaP6Q>r*P2gxxU&fO^|-86XR$o~W=cyHCK1|i%k!F29KWn<2V(}mgm zK3$8pGZu58`*?Zv_8I?xX$mfj)2^ONB9}34Lz+ z(8gc0h5>D+VK{5~xkw`Ca57UjfoH5~?j_I#89mf^@XJEm3?o%gf&p_gyEIB*@d;dX zg%C`cyt8;a!WzL*KEa7^EEXT-!Ot{!Rw9}HgADsxmVYr2^|-G2@gYB;oHMqJT0x_P znNQRHlBn76hG2c=iV`ak(lkP~KxPqC?bBnbQ|X~|?*s3(O!%(E@wgr;N#RZ!AOdfu z$G~ejrt;_=j574_+(XK6=5+9{qG8bcr&>+T6^z1fU>Xo?X2JSDw# zRj-PpCdqiEW`haSjw!4wE+IHoynJ_WU26Ps-CFnvv1q*wn`WWcMN{(A4r-I3zW>}#1ViI;;{ zV-9u13Q5{sZ|rBLpgx#uL-Yq^_UVgzI@@2v|FLmqmS*%=yYSgA4V%+<|FX4kTxg5x zic6{k2zQ{uf61Y*7Nk9KXkkA(E50ky4A{E!RRlv%o7OLC%J4(ho!&Au`oo}1LNkJew z46#}b1vpp{B85m!`VPA_ybd?-$mZ7TMmsJ;d?+lY;1_SD^r!lvWfuE@5;0rygfMqn^2IE)2sb}>Cj}S<}h`ftM&L4IS zD-GTh<2i@BpS+xWNn8H!6>ts6M39P7U1h-F zjYz@P7(O43as(Mj3jW3Mt-RmNJ~mZZ>ONUx7^cJm&f8cqN?9vq0IybR!B2?R9aNYL z3Y%J)LfEf>0)qh>gp$r_mDG6Xw_PhRbs96J8Xw1j-of?!QgqP79D@1>CJ+t=ga(55 z$Okr4umE^>E#t$`VAKUcCo^6T;xGlr#)MP*IcABi%47wFTRx2MzuzCU9yZshJi+j7 z#kInfr;WGx_7RWRz570YQ|H3QefSVOqw9y{6{)xQu@ez2 zFU8*?(hiHY4tL8*T{|Xt6lzf_m%{qaK-(_e{Tm&4jlbno+T-Gqgpoe?GegIwND*y` zLU=TB>xlS`O8rdhd=o04^5Af<2Bj@b`_KPIeO8K+m!4}pZKbOb9KGZf8csV%g9k)? zRBh7t?&O#a#i$|oKH$Fwf3KMKXHeY8pUBZ!e}$i{{<_*BU_kX`Jz!*&Dw;C(q%*z$ zgd^_lv=PLN@%#g*s3T7o#?C5H*a?h(zq?41fKdMUm(f)g%ClyhfQY2wT$GO_6mzep z$avqiT~X;IeT5@Y^Kw5>HtXOzM&f4&QG><5xC@wA=DIP2Hvye?P*Qu8D|#yak$?Yn zO(Ar)GJ4(bcEOb7g`Zt0x7oEr>d)JfbB9@ZM-&b%s+fD9yg^fZ=j2v# zyRpmri`$ZEdGL~?VU*ppG~~b_=hMgsdH%_QfEp5UhsO@%AB%$6ns?Gg;6V!(L%XaE z$59Z6EMl}yLx)$NGe7ds_I4NuneGnn=CX6ZrsSvJpVJW=c}JKva<6TKgW}W@m`Fjn z`QMjcD{dcbBzjEWLFAO6=u-8_E5nvllNOoL4vsk^iIH#~;j{O+42hG$hEVr_o_#-n3^isW;f&{8aJ8<$dn(S)G(F7=Pm@T+fMk`9a#3^vgG zaJp^yqLz`w#r#9Y@o*rd<^=_HR&*K57dGZ%A6^0V1_ixz-_73SCa5PCMkheBm4u3; zO9{pSTTmfPoc06=9AJy|9(VFtG3R?a_lAVx-hp$jXEq~2@nb#cwHsG!49^|X*HMls z`9?XnKRJkBfrP4V4_2Z-_9mb2S>1KMr2rBDQt&pcUfUsLFFeD$Z zM@whRh@jJq45uNC1RByodQ->X^&BV6oO-GE#U1q#LY4fKyqpuf95*x;<%a7^U*TM= zkQr389t6EV^g@}55~`!qX*m87CA0=$ItK3_?{yGd=A(1crx}1Yzp+g5*4PjD_E=b1 zQNa{M1kxJ7rGb4eyJF)h|8>}~Mk>9JatS7o#<{zSf~gz~YlPh4B%Q>gOHg67SXCiQ1-oKSxAI>~n>Xv6LmoBla@lv*^) z%!DG{#!E=}dklu=M%`}{T{q%E9e-yR{kbmoQ|I?CXqpza*5c;dM=Zg z2=ahko#SL3jab*3KI~jWbS&N)li`boacj>WV$jU(Z)$~^``Pd05kr-pRDbJ_=VM`i`)fuP6+4?T9GvEe+M|4* z&nAz8-1PJ>eB^C9YXU&&^-!&0hW#T+I0EXZW-6rSHg_Z1*x~3#99Xwe7TH<9kwS!J z1ULX`+3FtMhWQVApKzwC}fR)by(tiFN_riCWL^;uVYFJuS3qf#F?arAfxJLf@^Y$p zDEj=;97iFyO47AST9Ji$1?89dU2QXGDiI!IMe9D7@)(Yc_>B2n+ntRdg7}&CNroSf z29x|d%5N!bdo|{kRcw+0s0+{13gAjnPn;n~;(|1Oc`5aU&L+zT7PR%s5*xiW=Cbla zX0@;5Kai31MhPUKk!?B31ziK;MZXIJ@<)8U#W(pV)5fC1zpv;DOd;*9$LXtoc$V?_ z_VZ_#W-*U!lgq|MzU>Tlio{@(39e3EDAAJ3!dE=XT$#T%* zG4@i}b}zA6Jcl-l7^-@`9VJhPt#Mdo6Aj@|v@!^c^XqOP86~~t>L%~9FwiQ|`7@I) z3k`<>J&%K*)5G3de+UICAc^;`y8Md2&g`Da>#l$wQX)`fSPjm8U6%xmRl z47aq6@TqYo_&cYhF!E7Xa(K1huVw1c?E0e-2@RwJvofVeA5u5FZ9d16Nsfr;w<}_{ z-HJj9Ne)>95+;WwwAp5*UP}EE;}4p^Ss%~v%22OBSK36z!&t@eH4pelqSS#|IUlNH z;qg+}E9I&PUx9s5E^67w%ppjLmJUVI#L9}I^Sa~_CwXhS=yT6nA%dp0DQ2t6;UXPlZ(v z2c?*jIQMzL=vYo2}{L6WMjdyz1`a%f5HP;u7#D%D;VAJgZ!NIjrg@lIZWfbx%Q z-r>K;sJ)VopX;E*g&Vc~mh`}9Qz9*`JNe6$QLU0=nC~AOUV+b+fuj$hD6V5dIm3x! z{Ajv0rq8FHb)UowD4f^ijDO~eG$eE7Zf0A6WfDISsT%*ov&0=*YkuU%y+)d=XiTS+ z6%@8(#WB@)KHX8;D>3s-pK*g~TH6G{)?{1Ivw?9>6nsf zKz(e$C}l8n|AR`WHM*Ut0Ygl#LZ!a#Es z;8FcCsDzrKNac{!WIy#-rooBNBMyQutkR?x!-@8csqsVnl7Uvri7XUh0>1A8nuOM> z)x6}X?D2*05b^3y_f%@hpxwisuW-wFQjtm0~h-E)ez}QE#qDq}kIS!G^ETO`{o+g0PTHBv^R=ZnVk+ONwj&7N(Erwv zfQM}I7w%#u&O+C>3Y+u%&H&e8+@wWplNbZ)SSz^K(xn4$1(2*PJ%xiB(}RE zPg+?GYB~NH;CZk#BW+Y`m%^?^(UUM4eHOYw2>?DOX)=~M-C>i9jNHQzE2pxg|Imt{ z=ak9NS?ncKHY#ae5TDIYcPg!;_UC_cMB0pMqQ;Xi6A9I(B8lUVu6JvvLRf87P+}A| zj^BrOY&@1IqpBm1oH=(=Ahgg3>pKk0h{+!vygEJ-<(KGglw@0XXQHs z?+*F(sz)u_jZlbZFUjSw*7y1!S3DwIF}6r@)S4xFiHk(z?Upi)i*42V^7@6hIHfnv zhZLll56uRlJKwHw;`rV(|2n(~eCn?c+s?>ZC^O|IX3@=V$C@AOiORbYh%)=oDfIg$ zCRL9kllTzw*GTdekPek_MgPD^H<3a^nDSxtAtm;uIHDnsjG;u zy~}>l;7|hAdKk6Hf){V2de&mOB?}yG5H^$iweX$a1MFcsbNxt%;C6fX6h%bnNY;+O z7u&jkTwN~BQawH^J4q7Jq1j^F_WR1%+=iJFd_B9YLl`h)rJ@JDo$IxK#_=ePVi5o@ z(08)G(ruN5E?3%15gh3;8=o8sJ7qMRYSeu@4eefGEH~WXJx238n}!+;v~r6;s1VDw zB)^&DhgnhmxXM!VR2Npqspxyw2yqMazY8~4(T)oi^uMbwO@ zuFw@i#}WM5lWJ5I=Jn8g^Gt)0kdtLU`zw&Z;m?qNt*Yq%6Y%@|=S!iVYs?)IFWP~E z%#GT&@Xz6{kwf*uD<>sid(%J}+%V%OV7O7nZv%`2kJ^!$3sUSuQzY#~e0Z2#`BEFI zK1ga30UH?&A^ljeaAq6f5ks{zVefhqe$4h(mjqpH-Veo#)mq)3@y~X(Mm~#7t?Y?p z-EO=Bbd?Ck#M$YvP690Ffe;79FB$fD^xrDsw1pD6sM1i*<)+~3iRE`W8Tv>SKU-?y z6OXE4*>oo6?LNsYOQS_45L;;$rrQBp6*DO30Uk$-{0SzAYzMiGd&lc7C#LoX&kp04 z>vD4Um0nbL$IBRqiJNc31cl8eH9DSo{n5`VWiwh(Yh|}(O6uGn*R3bMT z6r0COo6ltR)^o*`fSacu(D~UQQy~7cn*%|rg$^^I4oxqJ?S};N)b3!FY1>gUsj*{c z)0<<>kyFF1OZlJnAOf=J(VdMBeDaV^$U3hka*NHg^x}awK8^T##ZXkFv<{~6hre&z z;jc8R^0g>m<{HY7%iVpy78_j7X7pTYO?b=|yFnZS*M#AXdNS~lu9xLPVbrC9CiY=K zOX24mx|m407X6tH`F`$B^^3vEwq9e86Q;pwk;O7x3gmg~P>ljGPM-xRUp0B2=EpL{ z>d|bHN&d~?8Y}NA+8SMmHKdcjFouX~HwiR(j_Vj1hc*q}DEMk~(>V6>p zvFzZ8X0Vw=>v(Zz*Cehs9ieN{^q^n=nADjWl|c5cow z%FBc>_d;&}$F;BItjY7ac8YVe^&(u0Nxqi5=vu2)4GWo-sn#KKQB!i4ojao_^zv|) zYI*DQ7C)QO_lZ&N(Qb)$S59KV(L#D?OT_&aH4AYpxVq8Ii(GgP6Gj5nx{Y>qkpr`~ zC25>wRhHhFlcy8C%@MeXFZo`nUFP+en2W5w8yQ*>(5x`L5HMUij4UhU^v7}W8df&~ zrF2Fdx=AOV$nS66S>w~oymlc{Ei0^gF%oOV_=Dd-RFDDehb`j2a4z9nhVv^Uw7Q3yUKkW34J{YiTuGEQ6Hr0)bk}ojk36=jCIjQ zO^734lbWG796m~77AHWA(og|uY_G+V)R2R3_Y%5t>KK4_aE84r!ds1=R_BJLvbW!!}$BI4w z=u~7hr)sMXhaz4g_AYZ=h*3kuf~{T8W_1+vIA%S#iJD|V+9me@6E?kPXH=5@>tTQX zzO+nf)YZRv5^eL;&XxO5FM{gyB^q-dBG#SZI}WY?9QX0K7t|jPWESl~&p#d9Sm zA8_di{zcn&3h63cZ^V`R?(JYc;Yjq0`)o#@ayaZxOCIXV_u-lE}ETPXg(5eJ*!dW0AJX=|ifx0U@cC*n3Q?%>p5U zmn?AHBX}-7BER5e@H@{_d)g{pw)%-|!H=;~t!2H;c*(q1&BHeZbC>#0ZABV44(u=_E^uPGSwddy%Gso|&8rUt4B zp3tJcFx6#!*c^d6RenpHILKpX;Y8gSYbdkQivQk*F{)UmhG>E(?8Q(K1be6b_KVU% zHWw2$mnI>fl*ee|QbZcG3GxJ$*wL?%`kT{*2ai`U1g5bvzVS!>PmaQSp9J%W+p&BfZMz5@Xk&O1KS^W;N}V zr`C%Bbh4=hv77u!Z{E&IG~E8{jgt=V_o20izpnzB&iinCaU>xsc)Rh90@-NOnOM;Y zg#xbt=K$C4Xzl%bmJ^&fW!_N{*=zL3UPZajET_5v5kZl65)sCx0_D_FhSE#&D_~0o zLMCtDcF5VjFg?V`6)X5?hj9HI%`0prY)CZ&PLhBfysh2445)%gcP+~bMrpJ^`5~3x z6Np#-`U-&SaoyqK>WSzK`pfz*XC3lh0fV=$L?Q8H zfAr=Tcm?vp#h<}sqZA2|PgeY;kfJ8c?_c>yPm`yYZi~?|OGJc_M(b4)b^6pRaey58 zUZfd5Nvk8`FXCgz8|^8{+vmM?@ZvXd{1eCmjpeVv3D-G_#I~~mlr_}TL8zC)M^6z) ztJtiQK|)M;F~VwkG1yFEh2hu&htV7sFFo*@0rBoL_Id+zJ+1)NP`M;jjlvKoa*;Gr=JLxSDR*x{i25$|6bW z;GdFVxmq@nZp|2^rYgNlK6vDxw*?S~E-s$iDAmP}reOzO;6x~+ecm^O^hp{UOP{3gEvM&x{G3 z5z7+TZNfq=CtbLO2+b@0e=w(>D7U>S#8O1;J0`fWI-o7?MinrPY?DD0cnN%Yb;pJt zSNRPZ@%;cq0wuT9_Q0RKfxCYBUqheCU-LU!>qF^sZ@<=zs!vv}F88?41;4qMCVbcbG^M2&~caXtk{?>g0 zUvdIe7s#Mm1pBV<5EBwW@P{KBDPsAu!pqO~3UX11Hh@N*q!19`^$G-5$RueF1fTih z;urk|+#g-8m1=j+Il~e6#q>}%B^<*{7ZkY;6I1n28bUbQ?-I9vtKKLdl8C{7+u_`e z*p}zylfbZ`Y#l$lV4%=pKhJS^ThLp=Fhu+DR5`NPL-lhGxcUJb|;QMA=Tws8@${m zCWu7Q42': { value: s.hideifmorethan||100, min: 10, diff --git a/apps/widbatpc/widbatpc.full.jpg b/apps/widbatpc/widbatpc.full.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3df2184fe6ed9be0be2f694b973248dc4dd52db7 GIT binary patch literal 3299 zcmb_dcT|(f79WyO1tb&+QVdm!p$O7cy7UqVMGT($&3;wX)DRGQIGJ1BL>Sz&HT#^A9Fk8R(!d+FwE++P=wEm!N=e_-{1C zyES&m4!|hpZ~Xtu#O&%8AHtFz zf5Faw!C}Dx!B9;9VZ2KK@eofz_?(|_$l+dxa#%5|o4*YX(ifpA0D^!OFaSCb{=dri z`y`YAp!x*>c;1{ z7yuUhw?e|yn2^6mz#ktk6y@iSo*31t=4le&5b6Mz*?2UQA30!`4Ke%#w@ z?fsQ*b={ubC)N0FyN+;EewJ9Z=a={N6FwGNi}JPnBAI{hZK;0ug$F;Cm>A?nsa8-?Eb*!O@K@ z+u%Mdm6PLVNp1dMdicktYT9^wwGWFv3rD(HCCYxt1b@ zJh$@IxAb4XQm{Kj7ct2IJ zHB#Hm`(F7*^n3RJj9ODhfRRO5wf;@n$*z+TR8 zVZPa%?XH?r-Wv4^9=&;CIKKsG$J9=5TzY=Gsk5izjWyOoR&{1&@M&$I#uXural4Ww zt>Ho`Z9kjWiqYOi*G4OBY}0CcAIjs6`%k6MwS{cCIAUG7XB6gXA;Veh=aG%${8Onv z4(gBE%viE8R(L#V_OjITwDSMp6zSt48fW5v=T>#!lv8hd;(S+o>)N?o_^tf;w+5Gr zhAaXklIY1idLl#06TV&PXvr0jJp zP*xsiPHtl4uT%0a8pdkvg*0b{oTpVaJ-WkPuWRwLdLD1}e1u`&5zCDI)hX6~p`-DF}2{XH5FvkZ+}?X`nS z<<2lNlc#%FM?xMsvv&CgR@PhBC56gT#C(i1l`l>P7V6@zNbClMh=;z(_~P_SV8-jr zkXWL{(}aQ|o2^;%`xzN0$8RW;>O3fYN9v`ou6y7r?B9`mPVKNQio54KB-JzVV;yfi zye`f@Ypdn=r1HH*nRN@Mrp5NT0h+kAUDqA4zN;6~x_NPI64sW<&lq6gkSqo-C{(@c zTU`|o7mfAyG9$b1d~v(}l5?gk!fwbR|8Xj65Q{A9Hg>}5W%I1}#IL(bkx|7Kf(uqp z@@SqD;UAd*^CvL(@!zdHGVtyJN&jy)~>Y z6LT-rmYJ6avJ%}B=N1EH>(Qp}4?KcG#2D{h%W|6%eM}ITDEJZ8Y&|&c(I5PFYC8Y* z-S|EJ0B0du1tHIPDKrdE+GJJN=zfKmIWj8F^sfOzn|36o@c{hPXD2=;)g5TYR3p4# zXVLPBC_&O$FK@!}2b*e<=ej@jUmv)CJKj0?sBw;Ijq<$MLw)8%Z5u^oxe~h0Hz)m; zd_mmudxZbF0n)0XI?jGDvAwA+ov@x(@tft^cG?L?*54&!X=)W~4jJ2+pU3uokEmcZ zSzeX9l@^F~&qw*QQI>}obhCy+H%9$#*a$ZTno6&nD|^gyS1D!m?9t7_bN#{Dxj`AH z>RNhJt2Gs$rubTCQX@AVwa3=hM{yRh*ssDPOL8;b6ElKNQ92%~xNUi2=i=ZAA9Z`a zGcWi|#14Rrh*~WMpUtUvE@QrjQyBL+2(vga)t9Hd*`q;LHnJCT$#7mG_aDDwFW5DU z_NDY$9s{3<2pd>tKgW&iS-xOHlNUogII9Ama!wOL_k05}q!dR~S`4;-RWO>+FKN2u z(ZK5)bX1b3N6F!SvLoyD0fEw}-wYhZD~pYTPRBGdm#moLEtV~ej_qJP`1FOQKCHcx zRLm0~1Ye6VoX$Kdf5NwVBnTabE38YYdQWP*Y2)y2w5&|O+TSnu*h?~x0Cg}rI-h1U z^YqBA@Q~?XF)gi&cao@#A}gE7CNT=mDem3)X0f!jrT43{OLzr8U)O*)IW;%Ggzu(~ zpBClcN&2mIIlIHp7d_a*Rozng5ivZIuC7;(LG9iaHhx-HQqMl@l;jZ!->NzQ%xAsE z_bb-2xDS9ih4O6j;Z{XUdv1)1-B`%%=9%-lzd2wvo>Al;?cE${ui3Q=U5Zrd+P#;z zd`;tQL*V7AuQEp%GY-IyT>KwH@}kqCoA+*R(0UKR24mpU^5I%g?U!T$ldwcJ>$P2D{DYoVM7Sa_pG3Z&U4 zSWhxfcM60UVJ~f{$mo=1YwO+Hu+~l~8qS}e@4!hO%+Vl>B_2c zs>|^!@2TKUV5&zr(X;~~NQ`vEK|co(S#v^bgiMgneM(N+gT*$N)n9ub)@@;<3cKo7 zi$!3t?uGL|tMMA--z*j<7koZy{-TKKWl73d$;(n3qOeb=_MHI@mXgIK$tecu^-Glp222_mucLJ(TSz62pH84*kJ zM20*;5b{WDjfpJCki{+`^!z$`Ip(}G@4P?e-8%KFOww{F$1PBA7Ka{yyXG$8^Q z3;-C^0OKWG*2K`z)7sXGXku>s#Q_%pkijtkgq)6yuq7GboLyXT%(-vo?Mn&!#(y$G zx?AH+bpW0!{fYm7O|kj;Q+%PI6{tr>K#N1g`a#SS@Rg&P?DLf`GC9KD))0!RgjhD< z5A5>?J`))h3B^2O#`}gvFnI=I?U3Lo=2}c+R?Ojl+QAMQouMuQD8LqwfC0q+S35Y* z(5nDwZUTU~bTH>r1i+nC00igG}ZS^x+J{s0F|z-izMjzOdbRDdebU`zu;=z9D$V~YGW z1t^=*1+XZnU^N5`3*cB70t;h21VYehAei_1zz!cmu_4$Y9|5|{NCYbafnxu601N9O z6q1(@zz}Ra0*Co!xCM{Gu@cgFIrS424@bDK01tL}`j9hE(PmRC6%4S;cHJC8d*s(Gw+hMxjtH`A)PV zLs&k_Lo$IwMtCs`tM$`nL9tLd%dt7bmt)^QA@>l11r?TFsqix(aFY4z1|rVr1e2j(eR zOKwuTxUvb=1$P{N{*X#iHb%&F+$5}pF7wy zFO4K)HjqnKA3D^VwbUogZ!i1H^0u~!s(LBrc_-xdBFF0q(Sg^Zlf$HaZrK=71Sn^l zSkf`9HLQ)MH#Wvj)3gGM-KgqzMQ>7PEH#F+e_<`X7|4SFBI6fjkk3!_vRAQ-xo0#{ zohdV#UEP1b-p@}heDGq6Y%?U9ziLUT&f6fV?L9&*Q7kBYKUbd*Ho)S!i!l=`Y&Y5+ z#v%!G@BH^QQmp2Di<;ElPDV6^aC#np_g>N}V!P8duT6Ja*zZb6Rx{7O+o}Wt`78um zBy6E4IV&Aq7bl9Z$}Ugi?A7nC;@I=U-Nw}JThSGIusis8qTA)Ii5)fj&bQAySZl(q z=DBu6#*eHrz^_zEe%^}KZT>s*ekyyS%D>zCN^51H%H0t1#i~gCe~J(k#N&gr%x5;v z1iHSkG*#NTP&gaZyt!GXt|O(n=pn{A$<;rU(bH##<*AEYS-HAs>TA=S z1vE>dG@7G_7=z*P>ik{ncXDk;NhQ z)9)(p!ny>Ueah_GqVVMETLP#Z=&i9HwT)tNFuz8tj|pS|-If?*=3|35UnjA)#y-** zOv5-UB^4N)V?XR%EtzqG9c@(DpjRm~hHQPFkdtB$=Bc05Z4k+&i9}WbcniMf^Hr^S zBriH6qeIe#KPMY4c2rzgukNI_&64TekUpzoQE?YSK0$)ozFXzEdP8 zNU℘8zZgqbgH#U5d#VcX-f_+3i|nRrYs3)1ABD+)FjabVt5SHxbQ`d^PtHO_Hezc@i#ZjqqtJQYQAe-sc4wjybW+;G2lWuVV5kScZm!+4#?ibfxv zFjErTgX8s(;0@Lsmbo{ZpMnAzOHy34i$_1s|zP!gjEx5IN_x#kb z=a%QNn06el?R)LG`y*V-AD_G}ooC5aaT9!Urb!MmQ*57>$-G2Cf`T6cHuBC4{dQooJ!^5s3{*^dm>UTZFq1gZc literal 0 HcmV?d00001 diff --git a/apps/widbatpc/widget.js b/apps/widbatpc/widget.js index 223db5f70..caecf8ae4 100644 --- a/apps/widbatpc/widget.js +++ b/apps/widbatpc/widget.js @@ -80,7 +80,12 @@ var s = 39; var x = this.x, y = this.y; const l = E.getBattery(); - const xl = x+4+l*(s-12)/100 + let xl = x+4+l*(s-12)/100; + + // show bar full in the level color, as you cant see the color if the bar is too small + if (setting('fillbar')) + xl = x+4+100*(s-12)/100; + c = levelColor(l); if (Bangle.isCharging() && setting('charger')) { From 87017ce81764c2f69636b87d349a458066442a3c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 11 Oct 2021 15:37:43 +0100 Subject: [PATCH 0209/1062] More Bangle.js 2 tweaks --- apps.json | 4 +-- apps/openstmap/app.js | 1 + apps/speedo/ChangeLog | 1 + apps/speedo/speedo.js | 69 ++++++++++++++++++++++++++++++------------- 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/apps.json b/apps.json index 1e2612fce..c8e614625 100644 --- a/apps.json +++ b/apps.json @@ -500,9 +500,9 @@ { "id": "speedo", "name": "Speedo", "icon": "speedo.png", - "version":"0.04", + "version":"0.05", "description": "Show the current speed according to the GPS", - "tags": "tool,outdoors,gps", + "tags": "tool,outdoors,gps,b2", "storage": [ {"name":"speedo.app.js","url":"speedo.js"}, {"name":"speedo.img","url":"speedo-icon.js","evaluate":true} diff --git a/apps/openstmap/app.js b/apps/openstmap/app.js index 99f6d0c73..c33acd8ad 100644 --- a/apps/openstmap/app.js +++ b/apps/openstmap/app.js @@ -8,6 +8,7 @@ function redraw() { m.draw(); drawMarker(); if (WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) { + g.flip(); // force immediate draw on double-buffered screens - track will update later g.setColor(0.75,0.2,0); WIDGETS["gpsrec"].plotTrack(m); } diff --git a/apps/speedo/ChangeLog b/apps/speedo/ChangeLog index 35cef4520..91df52211 100644 --- a/apps/speedo/ChangeLog +++ b/apps/speedo/ChangeLog @@ -3,3 +3,4 @@ 0.03: Use offscreen buffer (not doublebuffer) Use 'locale' to get internationalised speed 0.04: Start GPS after loading app, just in case widgets affect it (#449) +0.05: Use Layout lib for Bangle.js 2 compatibility diff --git a/apps/speedo/speedo.js b/apps/speedo/speedo.js index 174702d71..1fcb16020 100644 --- a/apps/speedo/speedo.js +++ b/apps/speedo/speedo.js @@ -1,33 +1,62 @@ -var buf = Graphics.createArrayBuffer(240,120,1,{msb:true}); -var lastFix = {fix:0,satellites:0}; +var Layout = require("Layout"); +var layout; + +var lastFix = {fix:-1,satellites:0}; + +function speedoImage() { + return require("heatshrink").decompress(atob("kkdxH+ABteAAwWOECImZDQ2CAQglUD4us2fX68ymQDB1omFESWtDgIACEYYACrolPBwddmWIEZWsmVWJYgiLwXX2YcB1gdDq+BAodWGIWsEhQiDRAWBmQdEAAhGBroFC1ojMC4etERIlDAggkHNIgAWSYYjFVwNWGwgAP5KkBEYoFC1ihBagwAL5W72vKJAxpExCiDABnQ4W12vD6AHBEYxnT4YhB3ghCSIhqDe4SIP3giBM4LfFEYpiMDoQhC3fDCA7+DfBwiCAARmFAAmtEYlYagMywISHEQhEId4UyEYleqwABEZBHERQwABroZBq5rR6BGLNZKzMAAPKRZKzJr2tfaAAKxD7CfgRsD1g1GAAwME2YGDwQjFNgOzwMyCwuCwIAEBg0yHoKODEYmCcYNWCwutAAuzBgg4BCwJGEEgj7JV5r7BIwgjEWrDVCEQYkCWgYAWNYIjF/z8awQfD")); +} + function onGPS(fix) { + if (lastFix.fix != fix.fix) { + // if fix is different, change the layout + if (fix.fix) { + layout = new Layout( { + type:"v", c: [ + {type:"txt", font:"6x8:2", label:"Speed" }, + {type:"h", c: [ + {type:"img", src:speedoImage, pad:4 }, + {type:"txt", font:"35%", label:"--", fillx:true, id:"speed" }, + ]}, + {type:"txt", font:"6x8", label:"--", id:"units" }, + {type:"h", c: [ + {type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" }, + {type:"txt", font:"6x8", pad:3, label:"Satellites" } + ]}, + ]},[],{lazy:true}); + } else { + layout = new Layout( { + type:"v", c: [ + {type:"txt", font:"6x8:2", label:"Speed" }, + {type:"img", src:speedoImage, pad:4 }, + {type:"txt", font:"6x8", label:"Waiting for GPS" }, + {type:"h", c: [ + {type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" }, + {type:"txt", font:"6x8", pad:3, label:"Satellites" } + ]}, + ]},[],{lazy:true}); + } + g.clearRect(0,24,g.getWidth(),g.getHeight()); + layout.render(); + } lastFix = fix; - buf.clear(); - buf.setFontAlign(0,0); - buf.setFont("6x8"); - buf.drawString(fix.satellites+" satellites",120,6); + if (fix.fix) { + print(fix.fix); var speed = require("locale").speed(fix.speed); var m = speed.match(/([0-9,\.]+)(.*)/); // regex splits numbers from units var txt = (fix.speed<20) ? fix.speed.toFixed(1) : Math.round(fix.speed); - var value = m[1], units = m[2]; - var s = 80; - buf.setFontVector(s); - buf.drawString(value,120,10+s/2); - buf.setFont("6x8",2); - buf.drawString(units,120,s+26); - } else { - buf.setFont("6x8",2); - buf.drawString("Waiting for GPS",120,56); + layout.speed.label = m[1]; + layout.units.label = m[2]; } - g.reset(); - g.drawImage({width:buf.getWidth(),height:buf.getHeight(),bpp:1,buffer:buf.buffer},0,70); - g.flip(); + layout.sat.label = fix.satellites; + layout.render(); } g.clear(); -onGPS(lastFix); +onGPS({fix:0,satellites:0}); +onGPS({fix:1,satellites:3,speed:200}); // testing Bangle.loadWidgets(); Bangle.drawWidgets(); Bangle.on('GPS', onGPS); -Bangle.setGPSPower(1); +Bangle.setGPSPower(1, "app"); From 68cc9fbd40d0f80ad0309caa5f8133aada5c6ed4 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 11 Oct 2021 15:39:44 +0100 Subject: [PATCH 0210/1062] oops - remove debugging --- apps/speedo/speedo.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/speedo/speedo.js b/apps/speedo/speedo.js index 1fcb16020..2e729c114 100644 --- a/apps/speedo/speedo.js +++ b/apps/speedo/speedo.js @@ -41,8 +41,7 @@ function onGPS(fix) { } lastFix = fix; - if (fix.fix) { - print(fix.fix); + if (fix.fix && isFinite(fix.speed)) { var speed = require("locale").speed(fix.speed); var m = speed.match(/([0-9,\.]+)(.*)/); // regex splits numbers from units var txt = (fix.speed<20) ? fix.speed.toFixed(1) : Math.round(fix.speed); @@ -54,7 +53,7 @@ function onGPS(fix) { } g.clear(); onGPS({fix:0,satellites:0}); -onGPS({fix:1,satellites:3,speed:200}); // testing +// onGPS({fix:1,satellites:3,speed:200}); // testing Bangle.loadWidgets(); Bangle.drawWidgets(); From 36a52b8b001a4515d715950db56e411a6c4842b2 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 11 Oct 2021 17:43:52 -0700 Subject: [PATCH 0211/1062] Update README.md --- apps/schoolCalendar/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/schoolCalendar/README.md b/apps/schoolCalendar/README.md index 1cd152864..58cb86d2b 100644 --- a/apps/schoolCalendar/README.md +++ b/apps/schoolCalendar/README.md @@ -4,6 +4,6 @@ School Calendar is a calendar that you can see your upcoming classes or schedule ## Versions: -Version 1.00: Get Design Working +Version 1.0: Get Design Working -Version 2.00: Update Graphics +Version 2.0: Update Graphics From 300ed35bcac618b96a7baca7dd994e3cc117958f Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 12 Oct 2021 17:08:45 +0100 Subject: [PATCH 0213/1062] remove bangle.js banner --- index.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/index.html b/index.html index e1c195f7d..a5ae7bff0 100644 --- a/index.html +++ b/index.html @@ -40,10 +40,6 @@ -
- -  Bangle.js 2 is now on KickStarter!  Check it out here -
From c795b5e3c577fa37bb322ac4735a5df584968442 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 14 Oct 2021 16:22:49 +0100 Subject: [PATCH 0214/1062] Add menusmall for small menus on Bangle.js 2 Updated about with new images --- apps.json | 13 ++++- apps/about/ChangeLog | 1 + apps/about/app.js | 2 +- apps/menusmall/ChangeLog | 1 + apps/menusmall/app.png | Bin 0 -> 619 bytes apps/menusmall/boot.js | 121 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 apps/menusmall/ChangeLog create mode 100644 apps/menusmall/app.png create mode 100644 apps/menusmall/boot.js diff --git a/apps.json b/apps.json index c8e614625..c0923e50d 100644 --- a/apps.json +++ b/apps.json @@ -67,7 +67,7 @@ { "id": "about", "name": "About", "icon": "app.png", - "version":"0.08", + "version":"0.09", "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", "tags": "tool,system,b2", "allow_emulator":true, @@ -3572,5 +3572,16 @@ "data": [ {"name":"score.json"} ] +}, +{ "id": "menusmall", + "name": "Small Menus", + "icon": "app.png", + "version":"0.01", + "description": "Replace Bangle.js 2's menus with a version that contains smaller text", + "tags": "b2,bno1", + "type": "boot", + "storage": [ + {"name":"menusmall.boot.js","url":"boot.js"} + ] } ] diff --git a/apps/about/ChangeLog b/apps/about/ChangeLog index 62e8d0126..ccc80148c 100644 --- a/apps/about/ChangeLog +++ b/apps/about/ChangeLog @@ -6,3 +6,4 @@ 0.06: Actual pixels as of 12 Jun 2020 0.07: Pressing a button now exits immediately (fix #618) 0.08: Make about (mostly) work on non-240px screens +0.09: Actual Bangle.js 1 pixels as of 13 Oct 2021 diff --git a/apps/about/app.js b/apps/about/app.js index 43b45010b..28a292376 100644 --- a/apps/about/app.js +++ b/apps/about/app.js @@ -32,7 +32,7 @@ g.setFontAlign(0,-1); g.flip(); // Pixel chooser image -g.drawImage(require("heatshrink").decompress(atob("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")),0,y+8); +g.drawImage(require("heatshrink").decompress(atob("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")),0,y+8); g.drawString(NRF.getAddress(),g.getWidth()/2,g.getHeight()-8,true); g.flip(); diff --git a/apps/menusmall/ChangeLog b/apps/menusmall/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/menusmall/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/menusmall/app.png b/apps/menusmall/app.png new file mode 100644 index 0000000000000000000000000000000000000000..094ee447cd355a0a2fa4ba20b3708100d782a5de GIT binary patch literal 619 zcmV-x0+juUP)DL6JZd>pLf3wLK`SStkO#f5epGwg#H87L%>idD0m8ZY3QLK=9p8D0goOf zH@yf#+DIcF5`+@EX^|9di|Da}5;X}yySsV4ibyse6|=j~@3{NE!_33Z?92d!5JCtc zgb)%ygxi61E`J1|<WA%3s8cd0u&(V0t`|Ej=fw2`JW?%jMWKF zGFCDn`@Ppob=UaV2T%YCKmphadv+gCf9mRc(DwmRrT_(?0KVP_Tpz%9&H_vU3P1rU z00p1`6o3Ly0PX|04;X$HNaynMe|f*~{S(&TSD@g`UFZoRmbERjIMFrWZRi4?&0hT% zrnB0XS@c8^EML5W%K6CI8MIN*OZA7I+6O$jbWO~4n$h`gb85`sUw32SRg^ur50Hzo z89QXupdm71@E^4w_M)i1`RvI)K-3ydDa0)R-$uCJmWD|E8NBBjA!FM-RAlcC0DFjQ z4pil?v1uOoq7zhK?3WeUdoaY6`+9MAbL2i?Qn79(9zjFKCGQ{W<@!Be&H`FHye@t^ zulgLRYK?Bjx5JRw*|yY`hPJKNMidv&7&*>=uUw?btf?=eL=jNAz zZ1`cMwUUV&07?LaYRQ!Ha<3G)x!zfduQ^NcH4s9E;TMUolhx*FzTW@<002ovPDHLk FV1lfj3d#Tg literal 0 HcmV?d00001 diff --git a/apps/menusmall/boot.js b/apps/menusmall/boot.js new file mode 100644 index 000000000..968f8866c --- /dev/null +++ b/apps/menusmall/boot.js @@ -0,0 +1,121 @@ +;//not entirely sure why we need this - related to how bootupdate adds these to .boot0 +E.showMenu = function(items) { + g.clear(1).flip(); // clear screen if no menu supplied + Bangle.drawWidgets(); + if (!items) { + Bangle.setUI(); + return; + } + var w = g.getWidth(); + var h = g.getHeight(); + var menuItems = Object.keys(items); + var options = items[""]; + if (options) menuItems.splice(menuItems.indexOf(""),1); + if (!(options instanceof Object)) options = {}; + options.fontHeight=14; + options.x=0; + options.x2=w-1; + options.y=24; + options.y2=h-12; + if (options.selected === undefined) + options.selected = 0; + var x = 0|options.x; + var x2 = options.x2||(g.getWidth()-1); + var y = 0|options.y; + var y2 = options.y2||(g.getHeight()-1); + if (options.title) + y += 15; + var loc = require("locale"); + var l = { + lastIdx : 0, + draw : function(rowmin,rowmax) { + var rows = 0|Math.min((y2-y) / options.fontHeight,menuItems.length); + var idx = E.clip(options.selected-(rows>>1),0,menuItems.length-rows); + if (idx!=l.lastIdx) rowmin=undefined; // redraw all if we scrolled + l.lastIdx = idx; + var iy = y; + g.reset().setFontAlign(0,-1,0); + g.setFontCustom(atob("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"), 32, atob("AwIGCAgICAMFBQYIAwYDBwcFBgYHBgYGBgYDAwYHBgcHBgYGBgYGBgYEBgYGBgYGBgYGBgYGBggGBgYEBwQGBwQGBgYGBgYHBgYGBgYGBgYGBgYGBgYGBgYGBgQCBAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAHCAYGBgAGBgYGAAYGBQYABgMGBgQABgYHBgAGBgYGBgYGBgYGBgYGBgYEBAYGBgYGBgYGAAYGBgYGBgYHBgYGBgYGBgYGBgYGBgYGBwcGBgYGBgYABgYGBgYGBg=="), 15); + + if (rowmin===undefined && options.title) + g.drawString(options.title,(x+x2)/2,y-14).drawLine(x,y-2,x2,y-2). + setColor(g.theme.fg).setBgColor(g.theme.bg); + iy += 12; + g.setColor((idx>0)?g.theme.fg:g.theme.bg).fillPoly([72,iy,104,iy,88,iy-12]); + if (rowmin!==undefined) { + if (idxrowmax) { + rows = 1+rowmax-rowmin; + } + } + var less = idx>0; + while (rows--) { + var name = menuItems[idx]; + var item = items[name]; + var hl = (idx==options.selected && !l.selectEdit); + g.setColor(hl ? g.theme.bgH : g.theme.bg); + g.fillRect(x,iy,x2,iy+options.fontHeight-1); + g.setColor(hl ? g.theme.fgH : g.theme.fg); + g.setFontAlign(-1,-1); + g.drawString(loc.translate(name),x+1,iy+1); + if ("object" == typeof item) { + var xo = x2; + var v = item.value; + if (item.format) v=item.format(v); + v = loc.translate(""+v); + if (l.selectEdit && idx==options.selected) { + xo -= 24 + 1; + g.setColor(g.theme.bgH).fillRect(xo-(g.stringWidth(v)+4),iy,x2,iy+options.fontHeight-1); + g.setColor(g.theme.fgH).drawImage("\x0c\x05\x81\x00 \x07\x00\xF9\xF0\x0E\x00@",xo,iy+(options.fontHeight-10)/2,{scale:2}); + } + g.setFontAlign(1,-1); + g.drawString(v,xo-2,iy+1); + } + g.setColor(g.theme.fg); + iy += options.fontHeight; + idx++; + } + g.setFontAlign(-1,-1); + g.setColor((idxitem.max) item.value = item.max; + if (item.onchange) item.onchange(item.value); + l.draw(options.selected,options.selected); + } else { + var a=options.selected; + options.selected = (dir+options.selected)%menuItems.length; + if (options.selected<0) options.selected += menuItems.length; + l.draw(Math.min(a,options.selected), Math.max(a,options.selected)); + } + } + }; + l.draw(); + Bangle.setUI("updown",dir => { + if (dir) l.move(dir); + else l.select(); + }); + return l; +}; From 541dbb4a9b4fc95b4ae63f0db98067c14069c1e6 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 14 Oct 2021 16:34:32 +0100 Subject: [PATCH 0215/1062] fix lint error --- apps/menusmall/boot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/menusmall/boot.js b/apps/menusmall/boot.js index 968f8866c..59e47b178 100644 --- a/apps/menusmall/boot.js +++ b/apps/menusmall/boot.js @@ -1,4 +1,4 @@ -;//not entirely sure why we need this - related to how bootupdate adds these to .boot0 +"";//not entirely sure why we need this - related to how bootupdate adds these to .boot0 E.showMenu = function(items) { g.clear(1).flip(); // clear screen if no menu supplied Bangle.drawWidgets(); From be34e2b00d7536535b1fb768b6d94a2d413ea3a3 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 14 Oct 2021 17:14:10 +0100 Subject: [PATCH 0216/1062] new 'health' app for 2v11, improved bangle2 launcher --- apps.json | 19 +++++++++++-- apps/health/ChangeLog | 1 + apps/health/README.md | 38 +++++++++++++++++++++++++ apps/health/app-icon.js | 1 + apps/health/app.js | 60 +++++++++++++++++++++++++++++++++++++++ apps/health/app.png | Bin 0 -> 1104 bytes apps/health/boot.js | 38 +++++++++++++++++++++++++ apps/health/lib.js | 61 ++++++++++++++++++++++++++++++++++++++++ apps/launchb2/ChangeLog | 1 + apps/launchb2/app.js | 8 +++++- 10 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 apps/health/ChangeLog create mode 100644 apps/health/README.md create mode 100644 apps/health/app-icon.js create mode 100644 apps/health/app.js create mode 100644 apps/health/app.png create mode 100644 apps/health/boot.js create mode 100644 apps/health/lib.js diff --git a/apps.json b/apps.json index bebace661..439b7e1b2 100644 --- a/apps.json +++ b/apps.json @@ -13,6 +13,21 @@ ], "sortorder" : -10 }, + { "id": "health", + "name": "Health Tracking", + "tags": "tool,system,b2", + "icon": "app.png", + "version":"0.01", + "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)", + "readme": "README.md", + "storage": [ + {"name":"health.app.js","url":"app.js"}, + {"name":"health.img","url":"app-icon.js","evaluate":true}, + {"name":"health.boot.js","url":"boot.js"}, + {"name":"health","url":"lib.js"} + ], + "sortorder" : -10 + }, { "id": "moonphase", "name": "Moonphase", "icon": "app.png", @@ -55,7 +70,7 @@ "name": "Launcher (Bangle.js 2)", "shortName":"Launcher", "icon": "app.png", - "version":"0.02", + "version":"0.03", "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications. It will not work on Bangle.js 1.0.", "tags": "tool,system,launcher,b2,bno1", "type":"launch", @@ -3578,7 +3593,7 @@ "icon": "app.png", "version":"0.01", "description": "Replace Bangle.js 2's menus with a version that contains smaller text", - "tags": "b2,bno1", + "tags": "b2,bno1,system", "type": "boot", "storage": [ {"name":"menusmall.boot.js","url":"boot.js"} diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/health/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/health/README.md b/apps/health/README.md new file mode 100644 index 000000000..0ba0d8228 --- /dev/null +++ b/apps/health/README.md @@ -0,0 +1,38 @@ +# Health Tracking + +Logs health data to a file every 10 minutes, and provides an app to view it + +**BETA - requires firmware 2v11** + +## Usage + +Once installed, health data is logged automatically. + +To view data, run the `Health` app from your watch. + +## Features + +Stores: + +* Heart rate (TODO) +* Step count +* Movement + +## Technical Info + +Once installed, the `health.boot.js` hooks onto the `Bangle.health` event and +writes data to a binary file (one per month). + +A library (that can be used with `require("health").readXYZ` can then be used +to grab historical health info. + +## TODO + +* **Extend file format to include combined data for each day (to make graphs faster)** +* `interface` page for desktop to allow data to be viewed and exported in common formats +* More features in app: + * Step counting goal (ensure pedometers use this) + * Calendar view showing steps per day + * Yearly view + * Heart rate 'zone' graph + * .. other diff --git a/apps/health/app-icon.js b/apps/health/app-icon.js new file mode 100644 index 000000000..d522d9a9a --- /dev/null +++ b/apps/health/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///8H5AYM7/5L/ACsBqtQAgMFqtABYcVqtVAgIDBqgLDAwITBDYNVrQiEAANQEQNVtWAFIYfCE4Xq0AuEAAdX1W0BZFe1XUHQgADvWrJogAE9WtBYl66ouD2oLEtQGBFwQQBBYgeBFwYjFA4QuCBYgfCFwYLCL4IICFwacCPwetEwYLCR4QJBFwbFCU4QhBFwbMDNAYuCHQQwFFwowFFwowFFwwwEFwzNGFwjxFFwowEFw7aFBQwwDFwwwEFwwwEFw4wDBRAwBFxAwCFxAwCFxIA/AB4A=")) diff --git a/apps/health/app.js b/apps/health/app.js new file mode 100644 index 000000000..cb8651f4c --- /dev/null +++ b/apps/health/app.js @@ -0,0 +1,60 @@ +function menuMain() { + E.showMenu({ + "":{title:"Health Tracking"}, + "< Back":()=>load(), + "Step Counting":()=>menuStepCount(), + "Movement":()=>menuMovement() + }); +} + +function menuStepCount() { + E.showMenu({ + "":{title:"Step Counting"}, + "per hour":()=>stepsPerHour() + }); +} + +function menuMovement() { + E.showMenu({ + "":{title:"Movement"}, + "per hour":()=>movementPerHour() + }); +} + +function stepsPerHour() { + E.showMessage("Loading..."); + var data = new Uint16Array(24); + require("health").readDay(new Date(), h=>data[h.hr]+=h.steps); + g.clear(1); + Bangle.drawWidgets(); + g.reset(); + require("graph").drawBar(g, data, { + y:24, + miny: 0, + axes : true, + gridx : 6, + gridy : 500 + }); + Bangle.setUI("updown", ()=>menuStepCount()); +} + +function movementPerHour() { + E.showMessage("Loading..."); + var data = new Uint16Array(24); + require("health").readDay(new Date(), h=>data[h.hr]+=h.movement); + g.clear(1); + Bangle.drawWidgets(); + g.reset(); + require("graph").drawLine(g, data, { + y:24, + miny: 0, + axes : true, + gridx : 6, + ylabel : null + }); + Bangle.setUI("updown", ()=>menuStepCount()); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +menuMain(); diff --git a/apps/health/app.png b/apps/health/app.png new file mode 100644 index 0000000000000000000000000000000000000000..04f1fee5ebd50c744102fb878364858506b59c45 GIT binary patch literal 1104 zcmV-W1h4yvP)#2db4d?e4wiEO{}^ znxDEUsRm+)+1QLLsuUckw)3U> zPr)796h!-RdDRu;Ze-1Ft{!>r_{rl1-COPpvlo9=x@2%-qGHcC0uPt<^*fEg!?7cS zeE!y}C^Jy@1Y)P#MBmi`T))!b4sK8;HiIUao_Ouyzkq?<{`) z^B0!SDlJr9X*-_`4^v79%3CP7GPQ8~oPKy1a|3I4Gh#v(?EW=f-CQ%Oe6BJ)370{+ z0gj{=A9_jo*oX<)O^lvgY@6Yu%jeSJ;h)lhBm;k>0_1k^*RSQ{Sxk(+z8F&bXPT?`*nK6m(FF=w@N6zwb>N;(5u)3Zpr z-uiXjxMGV8G;wAu>FK29I#*G&y&N|wvfVbp!__h-!j^*do-W>6BPTWjPqaPcJ(AVH zB*A_RtC~2i_s_IMrMYT7->;*h=^?MoY2|ODo$e8RiHoYHPkRzcs%7UnCAdHIl=nZ$ zhZuw&-Gccz)l{I`;Cr#45~-!Df|yLkv{unW(*O7KNNovjk^00I}0!qD^Ws(Kfgi_(I?B=})pU)nou- zr-h;oGPK{>>oeO`8AZ>8y!UJ2so4O;4vVl}784m$;@Ob%Uabr30th)Di=ITGp7kY) zc&;9J8rZ>*w?Xif#!N)fl92Omy$Kp>fzV?;o~G}=huRdeG~~V02t&g?9CAKXyNQTp zA?NKz>o?^PhMW%sFJxX2tqeKqno+x1w}{YV4H--XD?{EJ&8pip0}yh$uskK?rO@N` zX4Y(~0SGy(1uM!XqL)L?Yt62A-V{5nh#g|z>y-=5Q { + // ensure we write health info for *last* block + var d = new Date(Date.now() - 590000); + + const DB_RECORD_LEN = 4; + const DB_RECORDS_PER_HR = 6; + const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24; + const DB_RECORDS_PER_MONTH = DB_RECORDS_PER_DAY*31; + const DB_HEADER_LEN = 8; + const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN; + + function getRecordFN(d) { + return "health-"+d.getFullYear()+"-"+d.getMonth()+".raw"; + } + function getRecordIdx(d) { + return (DB_RECORDS_PER_DAY*(d.getDate()-1)) + + (DB_RECORDS_PER_HR*d.getHours()) + + (0|(d.getMinutes()*DB_RECORDS_PER_HR/60)); + } + + var rec = getRecordIdx(d); + var fn = getRecordFN(d); + var f = require("Storage").read(fn); + if (f) { + var dt = f.substr(DB_HEADER_LEN+(rec*DB_RECORD_LEN), DB_RECORD_LEN); + if (dt!="\xFF\xFF\xFF\xFF") { + print("HEALTH ERR: Already written!"); + return; + } + } else { + require("Storage").write(fn, "HEALTH1\0", 0, DB_FILE_LEN); // header + } + var recordData = String.fromCharCode( + health.steps>>8,health.steps&255, // 16 bit steps + health.bpm, // 8 bit bpm + Math.min(health.movement / 8, 255)); // movement + require("Storage").write(fn, recordData, DB_HEADER_LEN+(rec*DB_RECORD_LEN), DB_FILE_LEN); +}); diff --git a/apps/health/lib.js b/apps/health/lib.js new file mode 100644 index 000000000..791c4ce22 --- /dev/null +++ b/apps/health/lib.js @@ -0,0 +1,61 @@ +const DB_RECORD_LEN = 4; +const DB_RECORDS_PER_HR = 6; +const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24; +const DB_RECORDS_PER_MONTH = DB_RECORDS_PER_DAY*31; +const DB_HEADER_LEN = 8; +const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN; + +function getRecordFN(d) { + return "health-"+d.getFullYear()+"-"+d.getMonth()+".raw"; +} +function getRecordIdx(d) { + return (DB_RECORDS_PER_DAY*(d.getDate()-1)) + + (DB_RECORDS_PER_HR*d.getHours()) + + (0|(d.getMinutes()*DB_RECORDS_PER_HR/60)); +} + +// Read all records from the given month +exports.readAllRecords = function(d, cb) { + var rec = getRecordIdx(d); + var fn = getRecordFN(d); + var f = require("Storage").read(fn); + var idx = DB_HEADER_LEN; + for (var day=0;day<31;day++) { + for (var hr=0;hr<24;hr++) { + for (var m=0;m{ if (app.icon) app.icon = s.read(app.icon); // should just be a link to a memory area }); +if (g.wrapString) { // FIXME: check not needed after 2v11 + g.setFont(font); + apps.forEach(app=>app.name = g.wrapString(app.name, g.getWidth()-64).join("\n")); +} function drawApp(i) { var y = 24+i*APPH-menuScroll; var app = apps[i]; if (!app || y<-APPH || y>=g.getHeight()) return; - g.setFont("6x8",2).setFontAlign(-1,0).drawString(app.name,64,y+32); + g.setFont(font).setFontAlign(-1,0).drawString(app.name,64,y+32); if (app.icon) try {g.drawImage(app.icon,8,y+8);} catch(e){} } From 3fb33811d0bf445fc2fb617c93e926363e6c61ad Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 14 Oct 2021 17:15:20 +0100 Subject: [PATCH 0217/1062] add back for health app --- apps/health/app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/health/app.js b/apps/health/app.js index cb8651f4c..f2df52972 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -10,6 +10,7 @@ function menuMain() { function menuStepCount() { E.showMenu({ "":{title:"Step Counting"}, + "< Back":()=>menuMain(), "per hour":()=>stepsPerHour() }); } @@ -17,6 +18,7 @@ function menuStepCount() { function menuMovement() { E.showMenu({ "":{title:"Movement"}, + "< Back":()=>menuMain(), "per hour":()=>movementPerHour() }); } From 8d808fa72cbde1909da0f00df3afa6c79f6500f2 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 14 Oct 2021 18:55:13 +0100 Subject: [PATCH 0218/1062] Chronowid README updates, screenshots, Pastel: leave space at bottom for Chronowid --- apps.json | 6 +++--- apps/chronowid/README.md | 8 ++++++-- apps/pastel/ChangeLog | 1 + apps/pastel/pastel.app.js | 8 ++++---- apps/pastel/pastel.settings.js | 4 ++-- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/apps.json b/apps.json index 0e73b5d9c..e49ab3b85 100644 --- a/apps.json +++ b/apps.json @@ -1421,7 +1421,7 @@ "icon": "chrono.png", "version":"0.01", "description": "Single click BTN1 to add 5 minutes. Single click BTN2 to add 30 seconds. Single click BTN3 to add 5 seconds. Tap to pause or play to timer. Double click BTN1 to reset. When timer finishes the watch vibrates.", - "tags": "Tools", + "tags": "tool", "storage": [ {"name":"chrono.app.js","url":"chrono.js"}, {"name":"chrono.img","url":"chrono-icon.js","evaluate":true} @@ -1562,7 +1562,7 @@ "icon": "app.png", "version":"0.03", "description": "Chronometer (timer) which runs as widget.", - "tags": "tools,widget", + "tags": "tool,widget,b2", "readme": "README.md", "storage": [ {"name":"chronowid.wid.js","url":"widget.js"}, @@ -3502,7 +3502,7 @@ "name": "Pastel Clock", "shortName": "Pastel", "icon": "pastel.png", - "version":"0.03", + "version":"0.04", "description": "A Configurable clock with custom fonts and background", "tags": "clock,b2", "type":"clock", diff --git a/apps/chronowid/README.md b/apps/chronowid/README.md index f422dd956..ec1d5dd46 100644 --- a/apps/chronowid/README.md +++ b/apps/chronowid/README.md @@ -5,11 +5,15 @@ The advantage is, that you can still see your normal watchface and other widgets The widget is always active, but only shown when the timer is on. Hours, minutes, seconds and timer status can be set with an app. -Depending on when you start the timer, it may alert up to 0,999 seconds early. This is because it checks only for full seconds. When there is less than one seconds left, it buzzes. This cannot be avoided without checking more than every second, which I would like to avoid. +When there is less than one seconds left on the timer it buzzes. + +The widget has been tested on Bangle 1 and Bangle 2 ## Screenshots -TBD +![](chrono_with_wave.jpg) +![](chrono_with_pastel.jpg) + ## Features diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index a0f660237..e0e967166 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -1,3 +1,4 @@ 0.01: First release 0.02: Display 12 hour clock as 12:xx not 00:xx when just into PM 0.03: Make it work with Gadgetbridge, Notifications fullscreen on a Bangle 2 +0.04: Leave space at the bottom for Chrono widget, set back option at first option diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index b97c02fc7..98f8af7f9 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -87,19 +87,19 @@ function draw() { // avoid flicker on a bangle 1 by comparing with previous minute if (mm_prev != mm) { mm_prev = mm; - g.clearRect(0, 30, w, h); + g.clearRect(0, 30, w, h - 24); } } else { // on a b2 safe to just clear anyway as there is no flicker - g.clearRect(0, 30, w, h); + g.clearRect(0, 30, w, h - 24); } // draw a grid like graph paper if (settings.grid && process.env.HWVERSION !=1) { g.setColor("#0f0"); for (var gx=20; gx <= w; gx += 20) - g.drawLine(gx, 30, gx, h); - for (var gy=30; gy <= h; gy += 20) + g.drawLine(gx, 30, gx, h - 24); + for (var gy=30; gy <= h - 24; gy += 20) g.drawLine(0, gy, w, gy); } diff --git a/apps/pastel/pastel.settings.js b/apps/pastel/pastel.settings.js index db7206dbb..2e4afadc8 100644 --- a/apps/pastel/pastel.settings.js +++ b/apps/pastel/pastel.settings.js @@ -26,6 +26,7 @@ E.showMenu({ '': { 'title': 'Pastel Clock' }, + '< Back': back, 'Font': { value: 0 | font_options.indexOf(s.font), min: 0, max: 4, @@ -50,7 +51,6 @@ s.date = !s.date save() }, - }, - '< Back': back, + } }) }) From 9c84d3f42020415200b0468fbbf0c79b5eb82c15 Mon Sep 17 00:00:00 2001 From: Etienne Deux Date: Fri, 15 Oct 2021 11:51:18 +0200 Subject: [PATCH 0219/1062] Adding locales to vclock-simple.js --- apps/svclock/vclock-simple.js | 41 +++++++++++++++-------------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/apps/svclock/vclock-simple.js b/apps/svclock/vclock-simple.js index f3ab911bc..62aad0dc3 100644 --- a/apps/svclock/vclock-simple.js +++ b/apps/svclock/vclock-simple.js @@ -1,4 +1,6 @@ /* jshint esversion: 6 */ +const locale = require("locale"); + const timeFontSize = 65; const dateFontSize = 20; const gmtFontSize = 10; @@ -18,50 +20,41 @@ function drawSimpleClock() { Bangle.drawWidgets(); // get date - var d = new Date(); - var da = d.toString().split(" "); + //var d = new Date(); + var d = new Date(Date.parse('2011-04-11T14:5:30Z')); g.reset(); // default draw styles // drawSting centered g.setFontAlign(0, 0); - // draw time - var time = da[4].substr(0, 5).split(":"); - var hours = time[0], - minutes = time[1]; - var meridian = ""; + // drawTime + var hours; if (is12Hour) { - hours = parseInt(hours,10); - meridian = "AM"; - if (hours == 0) { - hours = 12; - meridian = "AM"; - } else if (hours >= 12) { - meridian = "PM"; - if (hours>12) hours -= 12; - } - hours = (" "+hours).substr(-2); + hours = ("0" + d.getHours()%12).slice(-2); + } else { + hours = ("0" + d.getHours()).slice(-2); } + var minutes = ("0" + d.getMinutes()).slice(-2); g.setFont(font, timeFontSize); g.drawString(`${hours}:${minutes}`, xyCenter, yposTime, true); - g.setFont(font, gmtFontSize); - g.drawString(meridian, xyCenter + 102, yposTime + 10, true); + + if (is12Hour) { + g.setFont(font, gmtFontSize); + g.drawString(locale.meridian(d), xyCenter + 102, yposTime + 10, true); + } // draw Day, name of month, Date - var date = [da[0], da[1], da[2]].join(" "); g.setFont(font, dateFontSize); - - g.drawString(date, xyCenter, yposDate, true); + g.drawString([locale.dow(d,1), locale.month(d,1), d.getDate()].join(" "), xyCenter, yposDate, true); // draw year g.setFont(font, dateFontSize); g.drawString(d.getFullYear(), xyCenter, yposYear, true); // draw gmt - var gmt = da[5]; g.setFont(font, gmtFontSize); - g.drawString(gmt, xyCenter, yposGMT, true); + g.drawString(d.toString().match(/GMT[+-]\d+/), xyCenter, yposGMT, true); } // handle switch display on by pressing BTN1 From ebb020ca9f643bfe32f95593a7c825e4778b1fc7 Mon Sep 17 00:00:00 2001 From: Filipe Fradique Date: Fri, 15 Oct 2021 23:57:29 +0100 Subject: [PATCH 0220/1062] Created Nifty Clock A --- apps.json | 13 +++++ apps/ffcniftya/ChangeLog | 1 + apps/ffcniftya/app-icon.js | 1 + apps/ffcniftya/app.js | 95 +++++++++++++++++++++++++++++++++++++ apps/ffcniftya/app.png | Bin 0 -> 2188 bytes 5 files changed, 110 insertions(+) create mode 100644 apps/ffcniftya/ChangeLog create mode 100644 apps/ffcniftya/app-icon.js create mode 100644 apps/ffcniftya/app.js create mode 100644 apps/ffcniftya/app.png diff --git a/apps.json b/apps.json index 1e2612fce..0a4be55e2 100644 --- a/apps.json +++ b/apps.json @@ -3572,5 +3572,18 @@ "data": [ {"name":"score.json"} ] +}, +{ "id": "ffcniftya", + "name": "Nifty-A Clock", + "icon": "app.png", + "version":"0.01", + "description": "A nifty clock with time and date", + "tags":"clock,b2", + "type":"clock", + "allow_emulator":true, + "storage": [ + {"name":"ffcniftya.app.js","url":"app.js"}, + {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} + ] } ] diff --git a/apps/ffcniftya/ChangeLog b/apps/ffcniftya/ChangeLog new file mode 100644 index 000000000..18bc264a3 --- /dev/null +++ b/apps/ffcniftya/ChangeLog @@ -0,0 +1 @@ +0.01: New Clock Nifty A diff --git a/apps/ffcniftya/app-icon.js b/apps/ffcniftya/app-icon.js new file mode 100644 index 000000000..f0a2393b1 --- /dev/null +++ b/apps/ffcniftya/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEIf4A5gX/+AGEn//mIWLgP/C4gGCAAMgC5UvC4sDC4YICkIhBgMQiEBE4Uxn4XDj//iEAn/yA4ICBgUikEikYXBBAIXEn/xJYURAYMygERkQHBiYLBKYIXF+AVDC4czgUSmIXBCQgED+ZeBR4YXBLYICDC5CPGC4IAIC40zmaPDC4MSLQQXK+ayCR4QXCiRoEC44ECh4bCC4MTiTDBC6ZHOC5B3NLYcvC4kBgL5BAAUikT+BfIIrB/8ykf/eYQXBkUTI4cBW4YQCgQGDmAXDkJfEC46GBAoJKCR4geCAAMRAAZRDAoIODO4UBPRIAJR5QXWgKNCTApNDC5Mv/6/DAwR3GAAyHCC4anJIo3/+bvEa4Uia4oXHkEvC4cvIgUf+YXKHYIvEAgcPC5QSGC5UBSwYXJLYQXFkUhgABBC5Ef/4mBl4XEmETmIXKgaXBmYCBC4cTkMxiQXJS4IACL4p3MgESCwJHFR5oxCiB3FkERC5cSToQXFmUyiAZFR48Bn7zCAQMjkfykQkBN4n/XgKPBAAQgCUQIfBUwYXHFgIGCdI4XDmYADmIIEkAWJAH4A4A==")) \ No newline at end of file diff --git a/apps/ffcniftya/app.js b/apps/ffcniftya/app.js new file mode 100644 index 000000000..0b8865bec --- /dev/null +++ b/apps/ffcniftya/app.js @@ -0,0 +1,95 @@ +const locale = require("locale"); +const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; + +/* Clock *********************************************/ +const scale = g.getWidth() / 176; + +const widget = 24; + +const viewport = { + width: g.getWidth(), + height: g.getHeight(), +} + +const center = { + x: viewport.width / 2, + y: Math.round(((viewport.height - widget) / 2) + widget), +} + +function d02(value) { + return ('0' + value).substr(-2); +} + +function draw() { + g.reset(); + g.clearRect(0, widget, viewport.width, viewport.height); + const now = new Date(); + + const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); + const minutes = d02(now.getMinutes()); + const day = d02(now.getDay()); + const month = d02(now.getMonth() + 1); + const year = now.getFullYear(); + + const month2 = locale.month(now, 3); + const day2 = locale.dow(now, 3); + + g.setFontAlign(1, 0).setFont("Vector", 90 * scale); + g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); + g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale); + + g.fillRect(center.x + 30 * scale, center.y - 72 * scale, center.x + 32 * scale, center.y + 74 * scale); + + g.setFontAlign(-1, 0).setFont("Vector", 16 * scale); + g.drawString(year, center.x + 40 * scale, center.y - 62 * scale); + g.drawString(month, center.x + 40 * scale, center.y - 44 * scale); + g.drawString(day, center.x + 40 * scale, center.y - 26 * scale); + g.drawString(month2, center.x + 40 * scale, center.y + 48 * scale); + g.drawString(day2, center.x + 40 * scale, center.y + 66 * scale); +} + + +/* Minute Ticker *************************************/ + +let tickTimer; + +function clearTickTimer() { + if (tickTimer) { + clearTimeout(tickTimer); + tickTimer = undefined; + } +} + +function queueNextTick() { + clearTickTimer(); + tickTimer = setTimeout(tick, 60000 - (Date.now() % 60000)); + // tickTimer = setTimeout(tick, 3000); +} + +function tick() { + draw(); + queueNextTick(); +} + +/* Init **********************************************/ + +// Clear the screen once, at startup +g.clear(); +// Start ticking +tick(); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower', (on) => { + if (on) { + tick(); // Start ticking + } else { + clearTickTimer(); // stop ticking + } +}); + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); \ No newline at end of file diff --git a/apps/ffcniftya/app.png b/apps/ffcniftya/app.png new file mode 100644 index 0000000000000000000000000000000000000000..1cd8a49b7ba951670fb8c007055a64f6d29dfc98 GIT binary patch literal 2188 zcmV;72y^#|P)?hiUTNH2?4Z%C_80Yk+zhAr8q)Ct(B5m*P-K< z-4S(wt>O&pbbYMTu{_)o+B!O2s61R07z*;}3M7)4K*;;-#KXpzS5fKyvEwiQeD}M* z@7!O`ch5QBcfkw1z;l9R8_r-bNF)-3aDILsLg;*acXxLHfKIDhP%kWhF6az;eM7xW zE>kEJqZ6YH2E!2(!?1%N931{}cve0ul}o2)rkc(-@p!!DRFl3v{ipjs!T-Vl4d~z7 zV)dHUW8-7c9^Ni|dl{s7vOG`Jo-hyp;Nb%Zp+qKOGMRSy-`)JqOqR$b5}8D$RiR6z zN=d_MmgK6Bt2QQXM7EEQPf}7603bLdcvIpgGMSv3mda!@Jv=?uu3P&!W>exOG~B?@ zKzu@c)A=SRf?iqs3L?+SXXE1In2!)Z1YG&@3i6*hcgB*h(x?u6Z~)of-rh(5a8z_e zR9sSAeY~0z$SEi&`1JUv#U;hJ`fec(1pwgX?F9g+`BP2lzEZtj|9<)VPNuU;RE3PR zw6q)7Zy;N#RKB+5wOzTp$QW6nR9NzdhlgipXOSI11oYqO_hI{Jwc48FH8HDWW~4KN zLxU6J6LIlzj|E93(xN>@wI^zki%cdv5k#YzGNRRKZ(P5@<#L0Ag8={!A3p5t>^yts ztdl?hK*jzFI!@oYW9Qy|dp8Rx#ziP|x>+dTlEI>A$P7etQ!Rfe*iwlRt0RRLA2J!ejfn+kjAL7yUNPs&K!EzLp zx+V3{;X`h2Zg1tkRrPTdj^nnRXB0#wQ`^Pu(c#fTflw$Au6cP)T|*s&kV>YCj))vZ zjgOD(_4?QivD{#8Pj^pbROGS=A^}oXR+d_=Hhc2l%%4@5R#S9Tw3Dc?m0>Iv3#Z}X z5#jUm^R^tyQ%fN&9)EP4E(OxZ15qe?gB}22_b~_|AtB>qgsAsf63kw7Sp6vrBXQ(M5d5qV`FhT9vKz6Dtwh)j%kJhg93Nw?uGywj0ONe zWOSqf8fY|{=!i(8(U37R36Rbyv*(< z1R=cEe$8rd%YCT!bZu^4EX(ABjH7c|FI`^Z)=l|Jj+Dnd$52n=DL@ii~n~b+zS~mW;2TZ;CJl0%$ZE?MTKj z5+F6VG~fUCeXCm;+cP}eJs>ds#A5N-*qCj2Uw>ciiP}@Or_d#b!a z>xX65Szb|Yghum{aaa!kP+VL*H8W+k#7>?%$qD3GF`qVNiwF)0{`UH}mZLkVsSi~i za$h3!)~_>P`uqEv0}&o|9a5>?V7qW}aY2T= zhx^q1DYIXz)jDriOfO$~DU;5OPl$&Qj@=v6YPChhMYsEIpKm&E<~;SrC=xAJs4Xom zVXMNBO(Ky_*PmYDzQTE|Kp;5$;o%E^yKt`QT+N?r2!g;!80xFccCZW-2ykGP5P)~6>U_)I)Yg=pI?Y@e#iru-pU0q!}#2o^m;AbA`ybVIF zx4~er-Gp>d2O(^3ZMGy^t+LS2Q1lH0$8k5B8)|1Q`Ok8eiQC0IKJSyGpQsiVi$jf0 zQ}>s;iSdb_jU5~uR45dvuv9OYdW6Y|C%V;VJX}Y9965O4;L6aIn;(H6!q0yrpF}31 za>Y?pL16)o<4J;~#NQ-3;@C+!TK1-IEB0z?L9!r-6QtAY#2w<%k4=z`OVGE zA*V{ELNPdwpE_--w&oY+g~>v6bOeV4H=b+6-AsdsJH(bExLmHMrzf&I#2qoKWA60d ziHMA-I8gD~@z1DKDuqJf@p%Jx2Oh_4h})o0DC7z``ua4lnOE!83%UiVTq==Cb7j%g6wS>+dMrzgSbJ0WI_4<^6i=134-wQ z^78fbePSkUi+o#H7WsTWzxQ^pEk`bw=e?QdC|^s2DP=P=GXnzyMqos(rxhq<3I?$0 z>1nfjXlN)pGCDgudr$Em27>_s!~oXT)+Q2(WV5p86!Zcw@c#t>z<&U{^`%9!77N+{ O0000 Date: Sat, 16 Oct 2021 14:49:13 +0100 Subject: [PATCH 0221/1062] Toucher: support for b2, Pastel: added 2 new custom fonts --- apps.json | 6 +- apps/pastel/ChangeLog | 1 + apps/pastel/README.md | 5 +- apps/pastel/pastel.app.js | 14 ++++ apps/pastel/pastel.settings.js | 4 +- apps/toucher/ChangeLog | 3 +- apps/toucher/README.md | 22 +++++ apps/toucher/app.js | 145 +++++++++++++++++++++++---------- apps/toucher/screenshot1.jpg | Bin 0 -> 21329 bytes 9 files changed, 152 insertions(+), 48 deletions(-) create mode 100644 apps/toucher/README.md create mode 100644 apps/toucher/screenshot1.jpg diff --git a/apps.json b/apps.json index c7abcf9f0..8e0dd604d 100644 --- a/apps.json +++ b/apps.json @@ -1478,9 +1478,9 @@ "name": "Touch Launcher", "shortName":"Toucher", "icon": "app.png", - "version":"0.06", + "version":"0.07", "description": "Touch enable left to right launcher.", - "tags": "tool,system,launcher", + "tags": "tool,system,launcher,b2", "type":"launch", "data": [ {"name":"toucher.json"} @@ -3517,7 +3517,7 @@ "name": "Pastel Clock", "shortName": "Pastel", "icon": "pastel.png", - "version":"0.04", + "version":"0.05", "description": "A Configurable clock with custom fonts and background", "tags": "clock,b2", "type":"clock", diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index e0e967166..1277f0d9d 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -2,3 +2,4 @@ 0.02: Display 12 hour clock as 12:xx not 00:xx when just into PM 0.03: Make it work with Gadgetbridge, Notifications fullscreen on a Bangle 2 0.04: Leave space at the bottom for Chrono widget, set back option at first option +0.05: Added 2 new fonts diff --git a/apps/pastel/README.md b/apps/pastel/README.md index 9e8c133ec..324c3915a 100644 --- a/apps/pastel/README.md +++ b/apps/pastel/README.md @@ -1,7 +1,7 @@ # Pastel Clock - a configurable clock with custom fonts and background * Designed specifically for Bangle 1 and Bangle 2 -* A choice of 5 different custom fonts +* A choice of 7 different custom fonts * Supports the Light and Dark themes * Has a settings menu, change font, enable/disable the grid and the date display @@ -15,3 +15,6 @@ I came up with the name Pastel due to the shade of the grid background. ![](screenshot_b1_light.jpg) ![](screenshot_b2_dark.jpg) +![](screenshot_monoton.jpg) +![](screenshot_elite.jpg) + diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 98f8af7f9..1fe3e4a58 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -47,6 +47,16 @@ var scale = 1; // size multiplier for this font g.setFontCustom(font, 46, widths, 58+(scale<<8)+(1<<16)); }; +Graphics.prototype.setFontMonoton = function(scale) { + // Actual height 44 (43 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAABmwAAAAAAzYAAAAAAZsAAAAAAM2AAAAAAGbAAAAAADNgAAAAABmwAAAAAAzYAAAAAAZsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAD+AAAAAAf8AAAAAD/ggAAAAf8HwAAAD/g/4AAAf8H/AAAD/g/4OAAf8H/B/AD/g/4P+Af8H/B/wAfg/4P+AAMH/B/wAAA/4H+AAAD/A/4AAAB4H/AAAAAA/4AAAAAH/AAAAAAP4AAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH//gAAAAf//8AAAA/AAPgAAA8f/x8AAB4//+PAAB5+APxwABzwfwecAAzj//jnAA7n+P85gA7ngAPO4AbnH/xzsAdnP/+c3AN3PAHndgGzOAA5m4DbuAAO7MD9mAADN2Bs3AAB2bA2bAAAbNgbNgAANmwNmwAAGzYGzYAADZsDdmAADN2B+7AABuzAbMwABmbgNneAD3NgHZ3+/3MwBuc//nO4A7nB8HGYAM58AfOcAHeP/+OcABzx/8ecAAc+AA+cAAHH//8cAAB4//48AAAPg+B8AAAD+AP4AAAAP//wAAAAA/+AAAAAAAAAAAAAAAAAAABsAAAAAAA2AAAAAAAbAAAAAAANgAAAAAAGwAAAAAADf////8ABv////+AA3/////AAbAAAAAAAN/////wAG/////4ADYAAAAAABv////+AA3/////AAb/////gANgAAAAAAG/////4ADf////8AAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAADcAAAA2wBs2AADbYA2bAADtsAbZgADm2AftwAHjbAN24AHNtgGzYAPO2wDZsAPebYBs2AOeNsA2bAec22AbNge87bANmwc55tgG7c8542wD9355zbYA2Z5zztsAbODzjm2ANz/nnjbADc/nnhtgBnCPHA2wA74fPAbYAOf+OANsADj8eAG2AA8A+ADbAAP/8AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAABgG6AAAAuwDdsAAG3YBs2AADZuA27AABu3A/ZgAA7dgbtwAAduwN2w2zG3YGzYbZjZsDZsNsxs2Bs2G2Y2bA2bDbMbNgbNhtmNmwNmw2zGzYG7MbdnbsD939m/d2A2Z/7PM3AbuBtwO7AOz73eeZgDc/9n+dwB3H2Y8cwAZ4HnA84AGf/5/84ADz/OP44AAeALwB4AAH/+//4AAA/+H/wAAADwAfAAAAAAAAAAAAAAAAAAAAAAAZsAAAAAB82AAAAAD+bAAAAAHzNgAAAAPjmwAAAAfHzYAAAB+P5sAAAD8fM2AAAHw+ObAAAPj8fNgAAfH4/mwAA+Ph8zYAAcfH4ZsAAA+Px82AAB8fD+bAAD4+HzNgABh8PhmwAAH4/AzYAAPx+AZsAAPD4AM2AAGHwP+bfgAfgH/NvwA/AABmwAA8AAAzYAAYAA/5t+AAAAf82/AAAAAGbAAAAAADNgAAAAAAAAAAAAAAAAAAAAAAAGAAE///ADAAGf//gBwADP//wCcABmAAADmAAz//8C7gAZ//+DMwAMwAAA3YAGf//hZsADP//xu3ABn//4zdgAzDNsNuwAZhu2GzYAMw2bDZsAGYbNhs2ADMNmw2bABmGzYbNgAzDZsdmwAZhs2M3YAMw3d+zcAGYZm+ZsADMOzgd2ABmDM883AAzB3P87AAZgZx47gAMwOcB5gAGYDn/5gADMA4/zwAAAAPADwAAAAD8fgAAAAAf/gAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///4AAAD////AAAHwAADwAAHH//8eAAHP///ngAHfgAB8wAHeH/8PcADcf//x3ADsf//+ZgBu8AADu4B2c//8zMA3d///M2AbNwAB2bgduxs2bswP2Z2/O3YGzYzbDZsDZsbths2Bs2Nmw2bA2bGzYbNgbNjZsNmwNmxs2GzYH7c2bHbsDtmbtzdmA2bM3fs3AbMHZnO7AM3BuYOZgHZAzP+dgBmAMx+cwA7gHeAcwAMgB3584AHAAc/84ABgAHHx4AAAAB4D4AAAAAf/wAAAAAD/gAAAAAAAAAAAAAAAAAAZsAAAAAAM2AAAAAAGbAAAAAADNgAAAAABmwAAAAAAzYAAAAAAZsAAAAAAM2AAAAPAGbAAAB/gDNgAAP+ABmwAD/wYAzYAf+D8AZsD/wf8AM2f8D/gAGT/gf8HgAf8D/g/wB/g/8H/AA8H/g/4MAA/8H/B+AH/g/4P+AH4H/B/wADA/4P+AAAH/B/wAAA/4P+AAAAfB/wAAAAAP+AAAAAB/wAAAAAD+AAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAGAAwAAAA/8H/gAAB//v/8AAB4B/APgADz+PP54ABn/x//OABng8eDzAB3HHOcdwA3P9z/nYA7P/d/5mAbOBmYO7ANmebvzNwP3fs392YGzc3ZmbsDZsZsxs2Bs2M2Y2bA2bGbMbNgbNjNmNmwNmxmzGzYGzYzZjZsDZsZsxs2Bs2M2Y2bA2bGbMbNgbNzNmNmwP2Zm7s3YDbv7M+7MBszt3OZuA3MGZwd2ANn/uf8zAGY+zn47gDvAc4A7gA78/Pj5gAOf/z/zwADj8cPjwAA+A/gHgAAH/9//gAAA/4P/AAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/4AAAAAHw/AAAAAHADwADAAHP8cABgAHf/nAA4AHeB5gDuAHcfOYAzADc/7uBdwBs8ezBmYBu4DdgbsA2Z924s3AbN+bM3bgfsxt2duwNm4zbG3YGzYZtjZsDZsM2xs2Bs2GbY2bA2bDNsbNgbNhv2NmwP242zO3YHbszbm7sBs3AAHZuA2Z///M2Abuf//O7AGzh/8ObgDc8AA+dgB3P//+dwAdx//8cwAGeAAA8wADn///44AA8///54AAPgAAB4AAB+AAPwAAAP///gAAAA//+AAAAAAAAAAAAAAAAAAAAAAAAAAAADbBmwAAABtgzYAAAA2wZsAAAAbYM2AAAANsGbAAAAG2DNgAAADbBmwAAABtgzYAAAA2wZsAAAAAAAAAAAAAAAAAAA="), 46, atob("DRYpFR0eHiImHygmDQ=="), 49+(scale<<8)+(1<<16)); +} + +Graphics.prototype.setFontSpecialElite = function(scale) { + // Actual height 40 (39 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAYAAAAAAAfwAAAAAAP/AAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAAv8AAAAAAN6AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAfAAAAAAAPwAAAAAAP8AAAAAAH+AAAAAAH+AAAAAAD+AAAAAAD/AAAAAAD/AAAAAAB/AAAAAAB/AAAAAAB/AAAAAAB/gAAAAAB/gAAAAAB/gAAAAAA/gAAAAAB/wAAAAAA/4AAAAAA/wAAAAAA/4AAAAAA/4AAAAAAf8AAAAAAP8AAAAAAD8AAAAAAA8AAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//wAAAAP///gAAAH/9/+AAAD/gAf4AAB/AAB+AAA/AAAHwAAPAAAA+AADgAAAPgAAwAAAD4AAcAAAAfAAHAAAAHwABwAAAB8AA4AAAAfAAOAAAAHwABwAAAB8AAcAAAA/AAHgAAAPgAB+AAAH4AAPgAAD8AAD+AAD+AAA/4Af/AAAB////AAAAP///wAAAAP//gAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAOAAAAHAADgAAAD4AB4AAAA+AAeAAAAPgAHgAAAD4AB4AAAAeAAeAAAAHgAHgAAAB4AB4AAAAcAAeAAAAPAAH4AAP/wAB/////+AAf/////gAH/////4AB///+/+AAAAQAAPgAAAAAAB4AAAAAAAeAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAADwAAAAAAA+AAAAAAAPgAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAcAAAB+AAfwAAA/wAf/AAA/8Af/wAAf/AP/8AAGPwP//AADh8D48AAA4OB8OAAAOAAfDgAAHAAPg4AABwADwPAAAcAB8DwAAHAAeAeAABwAHAHgAAcADwB8AAHAB4APgAB4A+AB4AAPAfAAeAAD4fgADgAAf/4AA4AAD/8AAeAAAf+AAfAAAB8AAPwAAAAAAD4AAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAf8AAB/wAH/wAAf8AB/8AAH+AAffgAB4AcDx4AAcAPAAfAAHAPwAHwABwHwAA8AAcB+AAPAAHA/gADwABw/4AA8AAcf+AAPAAHP/gAHwABz74AB8AAf8fAA/AAH8DwAPgAD/A8AHwAA/gHwP8AAPwA//+AADwAH//AAAAAA//gAAAAAD/wAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAP8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/PAAAAAA/jwAAAAAfg8AAAAAPwPAAAAAH4DwAAAAD4A8HAAAD8APBwAAB+ADw8AAA+AA8PAAA/AAPDgAAPgADw8AAHwAB8/AAD+B///wAA/////8AAP/////AAB+f///wAAAAAHx8AAAAAB8PAAAAAAPDwAAAAADw8AAAAAA4PAAAAAAODwAAAAADgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAB/gAAH//wf8AAB//+H/gAAf//h/4AAHJ/wf/AABwD4D/4AAeA+AAeAAHgPAAHgAB4DwAB4AAeA4AAeAAHgOAAHgAA4DgAB4AAOA8AAeAADgPAAHgAB4D4ADwAAeAeAB4AAHAHwAeAABgAfAPgAAYAD8fgAAAAA//wAAAAAH/4AAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAA//wAAAAB///AAAAB////AAAB/7//wAAA/AfB+AAAfAPAPwAAPgHgB+AAHwBwAPgAB4A8AB8AAeAPAAfAAPADwAHwADgA8AB8AA8APAAfAAPADwAHwADwA8AB8AA8AHgA+AAP8B4APgAD/wfAH4AA/8D4D8AAH/A///AAB/wH//gAAH8A//wAAA8AH/4AAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAB/4AAAAAA/8AAAAAAP+AAAAAAD+AAAAAAAeAAAAAAAHAAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHwAAA/8AD+AAB//AA/gAB//wAP4AB//gAB+AB//AAAfwB//AAAH8A/wAAAA/A/wAAAAHw/wAAAAB8/wAAAAAffwAAAAAP/wAAAAAD/4AAAAAA/4AAAAAAP8AAAAAAD4AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAP/wAAAH8H/+AAAH/z//wAAD////+AAB///h/gAA/B/gH4AAPAP4A/AAHwB8AHwAB4APAB8AAeADgAfAAHgA4ADwABwAOAA8AAcADgAPAAHAA4ADwAB4AeAA8AAfAHwAPAADwB8AHgAA+A/gD4AAPgP4B+AAB+P/h/gAAP////wAAB/8f/4AAAP8D/8AAAAAAf8AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAA/4APAAAA//gH4AAAP/8D/gAAP4Pg/8AADwB8P/AAB4AfD/4AAeAD4d+AAHAA+AfwADwAHgH8AA4AA8A/AAOAAPAPgADgADwD4AA8AA4B+AAPAAeAfAAB4AHgHgAAeADwD4AAHwA8A8AAA+AfA/AAAHp/h/gAAA3//+gAAAB//+gAAAAd//gAAAACf/wAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAABwB/AAAAB/A/8AAAA/wf/gAAAP+H/4AAAH/h/+AAAB/8f/gAAAP+H/4AAAD/h/+AAAAfwf/gAAAH8C/wAAAAAA3oAAAAAABwAAAAAAAAAAAAAAAAAAAA=="), 46, atob("ERwfHB0cHxsdHB4dEQ=="), 50+(scale<<8)+(1<<16)); +} + const SETTINGS_FILE = "pastel.json"; let settings = undefined; @@ -113,6 +123,10 @@ function draw() { g.setFontCabinSketch(); else if (settings.font == "Orbitron") g.setFontOrbitron(); + else if (settings.font == "Monoton") + g.setFontMonoton(); + else if (settings.font == "Elite") + g.setFontSpecialElite(); else g.setFontLato(); diff --git a/apps/pastel/pastel.settings.js b/apps/pastel/pastel.settings.js index 2e4afadc8..a8aadd58f 100644 --- a/apps/pastel/pastel.settings.js +++ b/apps/pastel/pastel.settings.js @@ -22,14 +22,14 @@ storage.write(SETTINGS_FILE, settings) } - var font_options = ["Lato","Architect","GochiHand","CabinSketch","Orbitron"]; + var font_options = ["Lato","Architect","GochiHand","CabinSketch","Orbitron","Monoton","Elite"]; E.showMenu({ '': { 'title': 'Pastel Clock' }, '< Back': back, 'Font': { value: 0 | font_options.indexOf(s.font), - min: 0, max: 4, + min: 0, max: 6, format: v => font_options[v], onchange: v => { s.font = font_options[v]; diff --git a/apps/toucher/ChangeLog b/apps/toucher/ChangeLog index 494110d55..7b5c53de7 100644 --- a/apps/toucher/ChangeLog +++ b/apps/toucher/ChangeLog @@ -3,4 +3,5 @@ 0.03: Close launcher when lcd turn off 0.04: Complete rewrite to add animation and loop ( issue #210 ) 0.05: Improve perf -0.06: Complete rewrite in 80x80, better perf, add settings \ No newline at end of file +0.06: Complete rewrite in 80x80, better perf, add settings +0.07: Added suppport for Bangle 2, added README file diff --git a/apps/toucher/README.md b/apps/toucher/README.md new file mode 100644 index 000000000..a327a654c --- /dev/null +++ b/apps/toucher/README.md @@ -0,0 +1,22 @@ +# Toucher - A touch based launcher, swipe left, switch right, tap to launch + +* Designed specifically for Bangle 1 and Bangle 2 + +## Installation +- Use the App loader to install toucher +- Then delete the existing launcher +- When you restart the new launcher will be loaded +- To return to the default launcher, delete toucher and install the default launcher. + +## Bangle 1 +In the settings menu 'Low Res' refers to setting the Bangle 1 screen into 80x80 mode. +This significantly improves the animation performance. + +## Bangle 2 +The Hires/Lowres settings is ignored. +Touch the top third of the screen to launch the selected app. +Press button 1 to launch the selected app. + +## Screenshots + +![](screenshot1.jpg) diff --git a/apps/toucher/app.js b/apps/toucher/app.js index 455a29c5d..b6c37f0d0 100644 --- a/apps/toucher/app.js +++ b/apps/toucher/app.js @@ -7,8 +7,11 @@ let settings = Storage.readJSON(filename,1) || { debug: false }; -if(!settings.highres) Bangle.setLCDMode("80x80"); -else Bangle.setLCDMode(); +// this means that setFont('6x8',1) is actually setFont('6x8',3) +if (process.env.HWVERSION == 1) { + if(!settings.highres) Bangle.setLCDMode("80x80"); + else Bangle.setLCDMode(); +} g.clear(); g.flip(); @@ -23,7 +26,7 @@ const ORIGINAL_ICON_SIZE = 48; const STATE = { settings_open: false, index: 0, - target: 240, + target: g.getWidth(), offset: 0 }; @@ -63,7 +66,7 @@ const APPS = getApps(); function noIcon(x, y, scale){ if(scale < 0.2) return; - g.setColor(scale, scale, scale); + g.setColor(g.theme.fg); g.setFontAlign(0,0); g.setFont('6x8',settings.highres ? 6:3); g.drawString('x_x', x+1.5, y); @@ -81,23 +84,24 @@ function render(){ g.clear(); const visibleApps = APPS.filter(app => app.x >= STATE.offset-HALF && app.x <= STATE.offset+WIDTH-HALF ); + let cycle = 0; + let lastCycle = visibleApps.length; + visibleApps.forEach(app => { - - const x = app.x+HALF-STATE.offset; - const y = HALF - (HALF*0.3); + cycle++; + const x = app.x + HALF - STATE.offset; + const y = HALF; let dist = HALF - x; if(dist < 0) dist *= -1; - const scale = 1 - (dist / HALF); if(!scale) return; if(app.special){ - const font = settings.highres ? '6x8' : '4x6'; - const fontSize = settings.highres ? 2 : 1; - g.setFont(font, fontSize); - g.setColor(scale,scale,scale); + const fontSize = (process.env.HWVERSION == 2) ? 4 : (settings.highres ? 6 : 2); + g.setFont('6x8', fontSize); + g.setColor(g.theme.fg); g.setFontAlign(0,0); g.drawString(app.name, HALF, HALF); return; @@ -111,26 +115,69 @@ function render(){ if(icon){ icons[app.name] = icon; try { - const rescale = settings.highres ? scale*ORIGINAL_ICON_SIZE : (scale*(ORIGINAL_ICON_SIZE/2)); - const imageScale = settings.highres ? scale*2 : scale; + let rescale; + let imageScale; + + if (process.env.HWVERSION == 1) { + // on a bangle 1 !highres means 80x80 + rescale = settings.highres ? scale*ORIGINAL_ICON_SIZE : (scale*(ORIGINAL_ICON_SIZE/2)); + imageScale = settings.highres ? scale*2 : scale; + } else { + // !highres mode is meaningless on a bangle 2 at present + rescale = 1.25*scale*ORIGINAL_ICON_SIZE; + imageScale = 2.5*scale; + } + g.drawImage(icon, x-rescale, y-rescale, { scale: imageScale }); - } catch(e){ + } catch(e) { noIcon(x, y, scale); } - }else{ + } else { noIcon(x, y, scale); } //draw text - g.setColor(scale,scale,scale); - if(scale > 0.1){ - const font = settings.highres ? '6x8': '4x6'; - const fontSize = settings.highres ? 2 : 1; - g.setFont(font, fontSize); - g.setFontAlign(0,0); - g.drawString(app.name, HALF, HEIGHT/4*3); - } + g.setColor(g.theme.fg); + if (cycle == 2 && scale > 0.1) { + const fontSize = (process.env.HWVERSION == 2) ? 2 : 1; + if (process.env.HWVERSION == 1) { + fontSize = (settings.highres) ? 3 : 1; + } + + if (app.name.length <= 12) { + g.setFont("6x8", fontSize); + g.setFontAlign(0,1); + g.drawString(app.name, HALF, HEIGHT); + } else { + // some app names are too long for one line + var name = app.name; + var first = name.substring(0, name.indexOf(" ")); + var last = name.substring(name.indexOf(" ") + 1, name.length); + + // all this to handle long names like + // Simple 7 Segment Clock + if (last.length > 12 && process.env.HWVERSION == 1) { + g.setFont((settings.highres ? "6x8" : "4x6"),(settings.highres ? 2 : 1) ); + } else { + g.setFont("6x8", fontSize); + } + + g.setFontAlign(0,-1); + g.drawString(first, HALF, 0); + + if (last.length > 12 && process.env.HWVERSION == 1) { + g.setFont((settings.highres ? "6x8" : "4x6"),(settings.highres ? 2 : 1) ); + } else { + g.setFont("6x8", fontSize); + } + + g.setFontAlign(0,1); + g.drawString(last, HALF, HEIGHT); + } + } + + /* if(settings.highres){ const type = app.type ? app.type : 'App'; const version = app.version ? app.version : '0.00'; @@ -138,18 +185,21 @@ function render(){ g.setFontAlign(0,1); g.setFont('6x8', 1.5); g.setColor(scale,scale,scale); - g.drawString(info, HALF, 215, { scale: scale }); + g.drawString(info, HALF, HEIGHT/8*7, { scale: scale }); } + */ }); const duration = Math.floor(Date.now()-start); if(settings.debug){ g.setFontAlign(0,1); - g.setColor(0, 1, 0); - const fontSize = settings.highres ? 2 : 1; - g.setFont('4x6',fontSize); - g.drawString('Render: '+duration+'ms', HALF, HEIGHT); + g.setColor(g.theme.fgH); + const fontSize = (process.env.HWVERSION == 2) ? 2 : (settings.highres ? 2 : 1); + g.setFont(((process.env.HWVERSION == 2) ? '6x8' : (settings.highres ? '6x8' :'4x6')), fontSize); + // steal the bottom line, and print the duration + g.clearRect(0, HEIGHT - (process.env.HWVERSION == 1 && !settings.highres ? 8 : 24), WIDTH, HEIGHT); + g.drawString('Render: '+duration+' ms', HALF, HEIGHT); } g.flip(); if(STATE.offset == STATE.target) return; @@ -202,7 +252,7 @@ function run(){ E.showMessage("App Source\nNot found"); setTimeout(render, 2000); } else { - Bangle.setLCDMode(); + if (process.env.HWVERSION == 1) Bangle.setLCDMode(); g.clear(); g.flip(); E.showMessage("Loading..."); @@ -211,10 +261,11 @@ function run(){ } -// Screen event -Bangle.on('touch', function(button){ - if(STATE.settings_open) return; - switch(button){ +if (process.env.HWVERSION == 1) { + // Screen event + Bangle.on('touch', function(button){ + if(STATE.settings_open) return; + switch(button){ case 1: prev(); break; @@ -224,8 +275,17 @@ Bangle.on('touch', function(button){ case 3: run(); break; - } -}); + } + }); +} + +if (process.env.HWVERSION == 2) { + // tap at top 1/3 of screen to launch app + Bangle.on('touch', function(button, xy) { + if (xy.y < HEIGHT / 3) + run(); + }); +} Bangle.on('swipe', dir => { if(STATE.settings_open) return; @@ -238,9 +298,12 @@ Bangle.on('lcdPower', on => { if(!on) return load(); }); +if (process.env.HWVERSION == 1) { + setWatch(prev, BTN1, { repeat: true }); + setWatch(next, BTN3, { repeat: true }); + setWatch(run, BTN2, { repeat:true }); +} else { + setWatch(run, BTN1, { repeat:true }); +} -setWatch(prev, BTN1, { repeat: true }); -setWatch(next, BTN3, { repeat: true }); -setWatch(run, BTN2, { repeat:true }); - -jumpTo(1); \ No newline at end of file +jumpTo(1); diff --git a/apps/toucher/screenshot1.jpg b/apps/toucher/screenshot1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..698121cbe9790999983c97c5d1503c749c65f28e GIT binary patch literal 21329 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

Wm?3!Mt<_!7vupX1HX?3>?LHW#^ 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 OaCrVtk_Ynp^8WyThLmdn literal 0 HcmV?d00001 From ae8895f5dbce68270d4e8fd49a1d6f6435130b6e Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 16 Oct 2021 14:58:04 +0100 Subject: [PATCH 0222/1062] added screenshot files --- apps/chronowid/chrono_with_pastel.jpg | Bin 0 -> 29883 bytes apps/chronowid/chrono_with_wave.jpg | Bin 0 -> 61355 bytes apps/pastel/screenshot_elite.jpg | Bin 0 -> 9486 bytes apps/pastel/screenshot_monoton.jpg | Bin 0 -> 11281 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/chronowid/chrono_with_pastel.jpg create mode 100644 apps/chronowid/chrono_with_wave.jpg create mode 100644 apps/pastel/screenshot_elite.jpg create mode 100644 apps/pastel/screenshot_monoton.jpg diff --git a/apps/chronowid/chrono_with_pastel.jpg b/apps/chronowid/chrono_with_pastel.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2f5993e79cd54fc590a5ef534adfb09eb26d996b GIT binary patch literal 29883 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�*5)8`ksFQ}K!J!fzWzs*sQ<-DfniE=dGhj|j8`wO99X(_%? ztT43es(Yw~Yu*k>C$y`eThx(E&&Cj^D7%_HGz$+~R z>~BKHoC9stuS-~Xw(9Lv)L@X7lT*!F=J2{ab8{c}JXh)BOH_OqRMndVx7!r96qR(v zY3+&a7uUI^8P{Ieb&pcpnUwTZhf$&FG&N!n^O#*SJrh0m|C8DUfEVbU7uEzOmwDQ2R2@Y?;&&z(7R@Et8R#bK&#!+hQdjizJEq)Pr>~1>m|?B)GLek3|IAt7O0(j96WQQOmwsW7vH-W zmP?I3ZW5XCjbKvmj%b7MQoTP_uJzf8jpjNkt2&9LTLl2DJI~^3A>27Oks#`$-7^|j zR4HXVL8@KMp=_!qRBAGBK>lpFb1Ze;n7VoK#1CUSCcqN3R76=P`i*ik2iu_0bf1$%NH85f}n;ZqEEeTu6 z&qd`Yq+d)+pYv+m?w*|`3=-arj=p2lSJ$~N3x&$kgq_7E60!?5Y(S+&rTRgg$6dnGv63 z^#v*+&ze}knK;iuU1`e-Ii6z%f&pdTJMPp%J8CEga(R12Igp5iC_p{TF(V+yLREdn z!_&5N+;mdUs+r27?pqh{>Ym6fp)x6vt|_vxnM_`md@~w{R{QLP_ zuiUFgDCBQF$<#!L@bih&<_Eua>SG#DpVnUBsNXtDTWW$$L3^}L7!>o=v?~Of2mZEr zJ%6?NI0u#Ke*S`t@kZLS2qG9F*H}^Y15oG;*wD1<3B-P|As^hR;ojZ|3*eYbK1e84 zS^c=i&hx=W!fqkP4;BvI{mhf4bE+JJ)_IS)w2=L^rgrZ4Bh4NUyH8*qCL+?;u#X13p;(_J|k9=-BDoAedLYPSZr!Z+f!V*mCtv7n45ml%Dr7e9&_8600a@%fF^OO@Y`-LggCD=K zJftto$44@Re$cIFrdFX;huT8py25*w;QcA!hI?07>fa6@O__HQNKPO7c@EjX`?q&S z=$LM~=WFU(JqPy1{tp0|#GKv3?_}`Yr5TqQ%bKY~9-NW2=?1p!hLRC5eRP|4p`qhz z%44oMr99WwS#_jm`XydfB>v_Pz_leSb6p6WJuVHQvG!f~m?|99C}tbit>mkq;jE%h zfNz!Ji45Dow?HL($!Qz%wQoKLITcQUSL4xLILG92h?MTJB15QqMZTkU;fgstp+tDR zGC0-e(NWlK%#pai2c$4p?4_5ABZWW)M?Xz`U^+U$sN#O}RradW4MxW&+A5Q`rQh#z zUax4oHZ**>Qd)Z|qC$Hcz+2w(NN*h<&`;%vJawNaP{>GCH4KTp4Bk6`nh0fHhp4vr zY8`A_9XnNBo2z*TE~gOME2tXWti{{75s#yJpIu#LTqT%mmXB4jRpvphQePV?QgwPD z)=yWT+0Rqq<)D6oW)l1+;K)uqiw}7SQ-u^p1Xx|ShI@(XOVC~7A(oIr4u$jzC1?NbG*}(Y7J-zD9f;y0mggV&cC9J=&bpNyc*cX zcNs@bt5~Q*b|aQ(jjDyhXG=1*6-yry?l)5_N%S*>WoWDRDV8R2Je6Ql^04mHJ^(Fi b9~BJ{2M;nzaQP1a3-x8SY=!=3!k`ObG9 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 RNxorkG7uK~AHshd{{z#+XtV$T literal 0 HcmV?d00001 diff --git a/apps/pastel/screenshot_elite.jpg b/apps/pastel/screenshot_elite.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b881830ed9c15b927ac46fda6b7ad4b0eb3c300c GIT binary patch literal 9486 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 xeFAyxBs-Y|mReSOecL1idHXHzv=cbKkvx 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 Date: Sat, 16 Oct 2021 15:03:22 +0100 Subject: [PATCH 0223/1062] Updated for toucher README file --- apps.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps.json b/apps.json index 8e0dd604d..f9d1102d6 100644 --- a/apps.json +++ b/apps.json @@ -1482,6 +1482,7 @@ "description": "Touch enable left to right launcher.", "tags": "tool,system,launcher,b2", "type":"launch", + "readme": "README.md", "data": [ {"name":"toucher.json"} ], From 249b3f9de23ac7455086e02c52099fda83bd8446 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 16 Oct 2021 15:21:53 +0100 Subject: [PATCH 0224/1062] corrected typo --- apps/toucher/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/toucher/README.md b/apps/toucher/README.md index a327a654c..27cb32eeb 100644 --- a/apps/toucher/README.md +++ b/apps/toucher/README.md @@ -1,4 +1,4 @@ -# Toucher - A touch based launcher, swipe left, switch right, tap to launch +# Toucher - A touch based launcher, swipe left, swipe right, tap to launch * Designed specifically for Bangle 1 and Bangle 2 From cbfd73e122cd604c014a144e3d53f86a538757f6 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 16 Oct 2021 22:33:46 +0100 Subject: [PATCH 0225/1062] Matrix clock support for Bangle 2 --- apps.json | 4 ++-- apps/matrixclock/ChangeLog | 3 ++- apps/matrixclock/matrixclock.js | 35 ++++++++++++++++----------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/apps.json b/apps.json index f9d1102d6..4c557e847 100644 --- a/apps.json +++ b/apps.json @@ -337,9 +337,9 @@ { "id": "matrixclock", "name": "Matrix Clock", "icon": "matrixclock.png", - "version":"0.01", + "version":"0.02", "description": "inspired by The Matrix, a clock of the same style", - "tags": "clock", + "tags": "clock,b2", "type":"clock", "allow_emulator":true, "readme": "README.md", diff --git a/apps/matrixclock/ChangeLog b/apps/matrixclock/ChangeLog index d53df991b..7cc9144b1 100644 --- a/apps/matrixclock/ChangeLog +++ b/apps/matrixclock/ChangeLog @@ -1 +1,2 @@ -0.01: Initial Release +0.01: Initial Release +0.02: Support for Bangle 2 diff --git a/apps/matrixclock/matrixclock.js b/apps/matrixclock/matrixclock.js index 0bf33fd68..ab18c13b8 100644 --- a/apps/matrixclock/matrixclock.js +++ b/apps/matrixclock/matrixclock.js @@ -12,6 +12,8 @@ const Locale = require('locale'); const SHARD_COLOR =[0,1.0,0]; const SHARD_FONT_SIZE = 12; const SHARD_Y_START = 30; +const w = g.getWidth(); + /** * The text shard object is responsible for creating the * shards of text that move down the screen. As the @@ -111,7 +113,7 @@ var dateStr = ""; var last_draw_time = null; const TIME_X_COORD = 20; -const TIME_Y_COORD = 100; +const TIME_Y_COORD = g.getHeight() / 2; const DATE_X_COORD = 170; const DATE_Y_COORD = 30; const RESET_PROBABILITY = 0.5; @@ -141,29 +143,26 @@ function draw_clock(){ } var now = new Date(); // draw time. Have to draw time on every loop - g.setFont("Vector",45); - g.setFontAlign(-1,-1,0); + + g.setFont("Vector", g.getWidth() / 5); + g.setFontAlign(0,-1); if(last_draw_time == null || now.getMinutes() != last_draw_time.getMinutes()){ - g.setColor(0,0,0); - g.drawString(timeStr, TIME_X_COORD, TIME_Y_COORD); + g.setColor(g.theme.fg); + g.drawString(timeStr, w/2, TIME_Y_COORD); timeStr = format_time(now); } - g.setColor(SHARD_COLOR[0], - SHARD_COLOR[1], - SHARD_COLOR[2]); - g.drawString(timeStr, TIME_X_COORD, TIME_Y_COORD); + g.setColor(SHARD_COLOR[0], SHARD_COLOR[1], SHARD_COLOR[2]); + g.drawString(timeStr, w/2, TIME_Y_COORD); // // draw date when it changes g.setFont("Vector",15); - g.setFontAlign(-1,-1,0); + g.setFontAlign(0,-1,0); if(last_draw_time == null || now.getDate() != last_draw_time.getDate()){ - g.setColor(0,0,0); - g.drawString(dateStr, DATE_X_COORD, DATE_Y_COORD); + g.setColor(g.theme.fg); + g.drawString(dateStr, w/2, DATE_Y_COORD); dateStr = format_date(now); - g.setColor(SHARD_COLOR[0], - SHARD_COLOR[1], - SHARD_COLOR[2]); - g.drawString(dateStr, DATE_X_COORD, DATE_Y_COORD); + g.setColor(SHARD_COLOR[0], SHARD_COLOR[1], SHARD_COLOR[2]); + g.drawString(dateStr, w/2, DATE_Y_COORD); } last_draw_time = now; } @@ -232,10 +231,10 @@ function startTimers(){ Bangle.on('lcdPower', (on) => { if (on) { - console.log("lcdPower: on"); + //console.log("lcdPower: on"); startTimers(); } else { - console.log("lcdPower: off"); + //console.log("lcdPower: off"); clearTimers(); } }); From 3f26b8b88e1b75323e9df4ba58468e81ea2000a2 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 18 Oct 2021 11:32:46 +0100 Subject: [PATCH 0226/1062] Layout module now allows 'soft' buttons to be cycled through and selected using up/down on Bangle.js 1 --- apps/accellog/app.js | 4 +- apps/barclock/clock-bar.js | 2 +- apps/gpsinfo/gps-info.js | 8 +-- apps/speedo/speedo.js | 4 +- apps/weather/app.js | 2 +- modules/Layout.js | 69 +++++++++++++++------ tests/Layout/bin/runtest.sh | 6 +- tests/Layout/tests/accellog.js | 4 +- tests/Layout/tests/buttons_1_bangle1.bmp | Bin 0 -> 28874 bytes tests/Layout/tests/buttons_1_bangle1.js | 9 +++ tests/Layout/tests/buttons_1_bangle2.bmp | Bin 0 -> 15562 bytes tests/Layout/tests/buttons_1_bangle2.js | 6 ++ tests/Layout/tests/buttons_3_bangle1.bmp | Bin 0 -> 28874 bytes tests/Layout/tests/buttons_3_bangle1.js | 11 ++++ tests/Layout/tests/buttons_3_bangle2.bmp | Bin 0 -> 15562 bytes tests/Layout/tests/buttons_3_bangle2.js | 8 +++ tests/Layout/tests/buttons_osd_bangle1.bmp | Bin 0 -> 28874 bytes tests/Layout/tests/buttons_osd_bangle1.js | 17 +++++ tests/Layout/tests/buttons_osd_bangle2.bmp | Bin 0 -> 15562 bytes tests/Layout/tests/buttons_osd_bangle2.js | 10 +++ tests/Layout/tests/padding.bmp | Bin 15562 -> 15562 bytes tests/Layout/tests/padding_with_fill.bmp | Bin 15562 -> 15562 bytes 22 files changed, 126 insertions(+), 34 deletions(-) create mode 100644 tests/Layout/tests/buttons_1_bangle1.bmp create mode 100644 tests/Layout/tests/buttons_1_bangle1.js create mode 100644 tests/Layout/tests/buttons_1_bangle2.bmp create mode 100644 tests/Layout/tests/buttons_1_bangle2.js create mode 100644 tests/Layout/tests/buttons_3_bangle1.bmp create mode 100644 tests/Layout/tests/buttons_3_bangle1.js create mode 100644 tests/Layout/tests/buttons_3_bangle2.bmp create mode 100644 tests/Layout/tests/buttons_3_bangle2.js create mode 100644 tests/Layout/tests/buttons_osd_bangle1.bmp create mode 100644 tests/Layout/tests/buttons_osd_bangle1.js create mode 100644 tests/Layout/tests/buttons_osd_bangle2.bmp create mode 100644 tests/Layout/tests/buttons_osd_bangle2.js diff --git a/apps/accellog/app.js b/apps/accellog/app.js index fdb4d52be..c54c5002b 100644 --- a/apps/accellog/app.js +++ b/apps/accellog/app.js @@ -99,12 +99,12 @@ function startRecord(force) { {type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5, bgCol:g.theme.bg}, {type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1}, ] - },[ // Buttons... + },{btns:[ // Buttons... {label:"STOP", cb:()=>{ Bangle.removeListener('accel', accelHandler); showMenu(); }} - ]); + ]}); layout.render(); // now start writing diff --git a/apps/barclock/clock-bar.js b/apps/barclock/clock-bar.js index c2b4bde12..2c6d66e45 100644 --- a/apps/barclock/clock-bar.js +++ b/apps/barclock/clock-bar.js @@ -40,7 +40,7 @@ const layout = new Layout({ {height: 40}, {id: "date", type: "txt", font: "10%", valign: 1}, ], -}, false, {lazy: true}); +}, {lazy: true}); // adjustments based on screen size and whether we display am/pm let thickness; // bar thickness, same as time font "pixel block" size if (is12Hour) { diff --git a/apps/gpsinfo/gps-info.js b/apps/gpsinfo/gps-info.js index 047c1bc17..df888651a 100644 --- a/apps/gpsinfo/gps-info.js +++ b/apps/gpsinfo/gps-info.js @@ -68,7 +68,7 @@ function onGPS(fix) { {type:"txt", font:"6x8", label:"", fillx:true, id:"time" }, {type:"txt", font:"6x8", label:"", fillx:true, id:"sat" }, {type:"txt", font:"6x8", label:"", fillx:true, id:"maidenhead" }, - ]},[],{lazy:true}); + ]},{lazy:true}); } else { layout = new Layout( { type:"v", c: [ @@ -80,9 +80,9 @@ function onGPS(fix) { {type:"txt", font:"6x8", pad:3, label:"Satellites" } ]}, {type:"txt", font:"6x8", label:"", id:"progress" } - ]},[],{lazy:true}); + ]},{lazy:true}); } - g.clearRect(0,24,g.getWidth(),g.getHeight()); + g.clearRect(0,24,g.getWidth(),g.getHeight()); layout.render(); } lastFix = fix; @@ -103,7 +103,7 @@ function onGPS(fix) { nofix = (nofix+1) % 4; layout.progress.label = ".".repeat(nofix) + " ".repeat(4-nofix); } - layout.render(); + layout.render(); } Bangle.loadWidgets(); diff --git a/apps/speedo/speedo.js b/apps/speedo/speedo.js index 2e729c114..1d87859a8 100644 --- a/apps/speedo/speedo.js +++ b/apps/speedo/speedo.js @@ -23,7 +23,7 @@ function onGPS(fix) { {type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" }, {type:"txt", font:"6x8", pad:3, label:"Satellites" } ]}, - ]},[],{lazy:true}); + ]},{lazy:true}); } else { layout = new Layout( { type:"v", c: [ @@ -34,7 +34,7 @@ function onGPS(fix) { {type:"txt", font:"10%", label:fix.satellites, pad:2, id:"sat" }, {type:"txt", font:"6x8", pad:3, label:"Satellites" } ]}, - ]},[],{lazy:true}); + ]},{lazy:true}); } g.clearRect(0,24,g.getWidth(),g.getHeight()); layout.render(); diff --git a/apps/weather/app.js b/apps/weather/app.js index 6dba14143..6a0852f81 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -35,7 +35,7 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [ {type: "txt", font: "6x8", pad: 4, id: "updateTime", label: "15 minutes ago"}, ]}, {filly: 1}, -]}, null, {lazy: true}); +]}, {lazy: true}); function formatDuration(millis) { let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); diff --git a/modules/Layout.js b/modules/Layout.js index 09e2a3d8c..94abd42fd 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -4,7 +4,7 @@ Usage: ``` var Layout = require("Layout"); -var layout = new Layout( layoutObject, btns, options ) +var layout = new Layout( layoutObject, options ) layout.render(optionalObject); ``` @@ -47,15 +47,13 @@ layoutObject has: * A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space * `width` and `height` fields to optionally specify minimum size -btns is an array of objects containing: - -* `label` - the text on the button -* `cb` - a callback function -* `cbl` - a callback function for long presses - options is an object containing: * `lazy` - a boolean specifying whether to enable automatic lazy rendering +* `btns` - array of objects containing: + * `label` - the text on the button + * `cb` - a callback function + * `cbl` - a callback function for long presses If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically determine what objects have changed or moved, clear their previous locations, and re-render just those objects. @@ -78,9 +76,8 @@ Other functions: */ -function Layout(layout, buttons, options) { +function Layout(layout, options) { this._l = this.l = layout; - this.b = buttons; // Do we have >1 physical buttons? this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0; @@ -88,7 +85,43 @@ function Layout(layout, buttons, options) { options = options || {}; this.lazy = options.lazy || false; - if (buttons) { + var btnList; + if (process.env.HWVERSION!=2) { + // no touchscreen, find any buttons in 'layout' + btnList = []; + function btnRecurser(l) { + if (l.type=="btn") btnList.push(l); + if (l.c) l.c.forEach(btnRecurser); + } + btnRecurser(layout); + if (btnList.length) { // there are buttons in 'layout' + // disable physical buttons - use them for back/next/select + this.physBtns = 0; + this.buttons = btnList; + this.selectedButton = -1; + Bangle.setUI("updown", dir=>{ + var s = this.selectedButton, l=this.buttons.length; + if (dir===undefined && this.buttons[s]) + return this.buttons[s].cb(); + if (this.buttons[s]) { + delete this.buttons[s].selected; + this.render(this.buttons[s]); + } + s += dir; + if (s<0) s+=lh; + if (s>=l) s-=l; + if (this.buttons[s]) { + this.buttons[s].selected = 1; + this.render(this.buttons[s]); + } + this.selectedButton = s; + }); + } + } + + if (options.btns) { + var buttons = options.btns; + this.b = buttons; if (this.physBtns >= buttons.length) { // Handler for button watch events function pressHandler(btn,e) { @@ -114,12 +147,14 @@ function Layout(layout, buttons, options) { {type:"v", pad:1, filly:1, c: buttons.map(b=>(b.type="txt",b.font="6x8",b.height=btnHeight,b.r=1,b))} ]}; } else { - let btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length); - this._l.width = g.getWidth()-20; // button width + // add 'soft' buttons + this._l.width = g.getWidth()-32; // button width this._l = {type:"h", c: [ this._l, - {type:"v", c: buttons.map(b=>(b.type="btn",b.h=btnHeight,b.w=32,b.r=1,b))} + {type:"v", c: buttons.map(b=>(b.type="btn",b.filly=1,b.width=32,b.r=1,b))} ]}; + // if we're selecting with physical buttons, add these to the list + if (btnList) btnList.push.apply(btnList, this._l.c[1].c); } } if (process.env.HWVERSION==2) { @@ -233,7 +268,7 @@ Layout.prototype.render = function (l) { x,y+h-5, x,y+4 ]; - g.setColor(g.theme.bgH).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg).drawPoly(poly).setFont("4x6",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); + g.setColor(l.selected?g.theme.bgH:g.theme.bg2).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly).setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); }, "img":function(l){ g.drawImage(l.src(), l.x + (0|l.pad), l.y + (0|l.pad)); }, "custom":function(l){ @@ -347,8 +382,8 @@ Layout.prototype.update = function() { l._w = g.stringWidth(l.label); } }, "btn": function(l) { - l._h = 24; - l._w = 14 + l.label.length*8; + l._h = 32; + l._w = 20 + l.label.length*12; }, "img": function(l) { var src = l.src(); // get width and height out of image if (src[0]) { @@ -407,5 +442,3 @@ Layout.prototype.clear = function(l) { if (l.bgCol!==undefined) g.setBgColor(l.bgCol); g.clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); }; - -exports = Layout; diff --git a/tests/Layout/bin/runtest.sh b/tests/Layout/bin/runtest.sh index 5ce2ab21f..c85b3fe6c 100755 --- a/tests/Layout/bin/runtest.sh +++ b/tests/Layout/bin/runtest.sh @@ -1,7 +1,7 @@ #!/bin/bash # Requires Linux x64 (for ./espruino) # Also imagemagick for display - + cd `dirname $0`/.. if [ "$#" -ne 1 ]; then echo "USAGE:" @@ -19,7 +19,7 @@ SRCBMP=$SRCDIR/`basename $SRCJS .js`.bmp echo "TEST $SRCJS ($SRCBMP)" cat ../../modules/Layout.js > $TESTJS -echo 'Bangle = {};BTN1=0;process.env = process.env;process.env.HWVERSION=2;' >> $TESTJS +echo 'Bangle = { setUI : function(){} };BTN1=0;process.env = process.env;process.env.HWVERSION=2;' >> $TESTJS echo 'g = Graphics.createArrayBuffer(176,176,4);' >> $TESTJS cat $SRCJS >> $TESTJS || exit 1 echo 'layout.render()' >> $TESTJS @@ -39,5 +39,3 @@ else echo Files are the same exit 0 fi - - diff --git a/tests/Layout/tests/accellog.js b/tests/Layout/tests/accellog.js index 4ae865f4f..63b2ab410 100644 --- a/tests/Layout/tests/accellog.js +++ b/tests/Layout/tests/accellog.js @@ -6,8 +6,8 @@ var layout = new Layout({ type: "v", c: [ {type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5}, {type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1}, ] -},[ // Buttons... +}{btns:[ // Buttons... {label:"STOP", cb:()=>{}} -]); +]}); layout.samples.label = "123"; layout.time.label = "123s"; diff --git a/tests/Layout/tests/buttons_1_bangle1.bmp b/tests/Layout/tests/buttons_1_bangle1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8da6d1e8a10ce5e36176da7a3d23242f576f46af GIT binary patch literal 28874 zcmeI(!D+)V6adg)=&|5cGD0Xl6}m$Y8K7fi1dq@WGD7JD9YaSS0;-gxK7<@>kzi<^ zLV|20|Nozaor|B}zCYs7Ud!)O`KI!Dh{t+v{j%O}x4YeLwdrH^9-D~I82b2%D93#a z5kIjRVm}P~($@$OAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oRVi(4}ePu01Ms*S_Mk>)l!0obxo7e=@4e%k1YAv-fj%oYLjU zxu#t@^ILn|)6A~C#n;dp;>K}lYN4g%`u1Q$YgylPh*UG~>LNA8ZAkTRc~dPmr6`@6A>b2{8|Ztr>? zVX3%{kCXHC9TOlxfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk S1PBlyK!5-N0t5)$ufQ*2@=YoL literal 0 HcmV?d00001 diff --git a/tests/Layout/tests/buttons_1_bangle1.js b/tests/Layout/tests/buttons_1_bangle1.js new file mode 100644 index 000000000..fb6fb29fa --- /dev/null +++ b/tests/Layout/tests/buttons_1_bangle1.js @@ -0,0 +1,9 @@ +var BTN2 = 1, BTN3=2; +process.env = process.env;process.env.HWVERSION=1; +g = Graphics.createArrayBuffer(240,240,4); + +var layout = new Layout({ type: "v", c: [ + {type:"txt", font:"6x8", label:"A test"}, +]},{btns:[ // Buttons... + {label:"STOP", cb:()=>{}}, +]}); diff --git a/tests/Layout/tests/buttons_1_bangle2.bmp b/tests/Layout/tests/buttons_1_bangle2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2e4d1a2567b5a549702915a933b08c90709f984a GIT binary patch literal 15562 zcmeIzv1!9V7{&1yNEx_QJU|K)QU$U@3Js$XG(yUh8X;tYjB(`^hRb)7qrw>72j+tW z{%34X^7;AcC&_+#{d&|gUhMV3o*(wO(|y}_d_IojIM4H8m~?p4Aib+h`jl*YQjxwj z6rD;r*_?p@0tg_000IagfB*srAbmjRqJ*0I>s)p8T*rY%Fx#yKht^;)BYNcvLt#bC|dVKTJur){x&DFOrrC;9M z%GT^@(o(zU?Yv^ExlQ&v^>f$HZ*kjXFE{Rf^Y-0qckR8X+-u);`@Xopix`Kz+(Ne> w_hmvUw2iV|Db^rhX literal 0 HcmV?d00001 diff --git a/tests/Layout/tests/buttons_1_bangle2.js b/tests/Layout/tests/buttons_1_bangle2.js new file mode 100644 index 000000000..74b6c96af --- /dev/null +++ b/tests/Layout/tests/buttons_1_bangle2.js @@ -0,0 +1,6 @@ + +var layout = new Layout({ type: "v", c: [ + {type:"txt", font:"6x8", label:"A test"}, +]},{btns:[ // Buttons... + {label:"STOP", cb:()=>{}}, +]}); diff --git a/tests/Layout/tests/buttons_3_bangle1.bmp b/tests/Layout/tests/buttons_3_bangle1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4edc88d08e59157613ebf50cc7cde4e9f1b7f154 GIT binary patch literal 28874 zcmeI(&1u6h6ae5?=&|5cGD0Xl6}m$Y8K7figpAM;GD7JD9YaSS0;-&(J_U?Lj0*k~ z5)}XF>3tSX4$seT-yd;qujTWpyi<8S#N)7S`?B5d_s8RLvpdA*J$4bF(I4U~qI^F@ zAMq2rK2CjqDt(Or0RjXF5FkK+009C72oNAZfWU-6m(s)$%j`~Fx6Hs3qpmG(&Ur$_ zZUO`d5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV6Rg=-Sj#L~2tzQ47im#$o#Er|P84@ii4^IzPw3_RCDHR)Ld@kOq zid!+&zssA3WOJ&S`4DyXy1Je*ra5-ze5V pnp&~Dq|qvHY7}@~Vehg;3_^ea0RjXF5FkK+009C72oPv2@Cz$nTPpwn literal 0 HcmV?d00001 diff --git a/tests/Layout/tests/buttons_3_bangle1.js b/tests/Layout/tests/buttons_3_bangle1.js new file mode 100644 index 000000000..c8346f449 --- /dev/null +++ b/tests/Layout/tests/buttons_3_bangle1.js @@ -0,0 +1,11 @@ +var BTN2 = 1, BTN3=2; +process.env = process.env;process.env.HWVERSION=1; +g = Graphics.createArrayBuffer(240,240,4); + +var layout = new Layout({ type: "v", c: [ + {type:"txt", font:"6x8", label:"A test"}, +]},{btns:[ // Buttons... + {label:"A", cb:()=>{}}, + {label:"STOP", cb:()=>{}}, + {label:"B", cb:()=>{}}, +]}); diff --git a/tests/Layout/tests/buttons_3_bangle2.bmp b/tests/Layout/tests/buttons_3_bangle2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..4b9fa5b42b5e628d1a19a3cc5d1fd73f7bf59d28 GIT binary patch literal 15562 zcmeI1K~BRk5Jd-xB~q@^1F)zNDW}2yhC?@PXIhV zKnL&*yADoWcfzvP)@Pd*6FE)JYV(8Z$_&MVT*Vb67UdeS0EsZ`$<8J1TnE=7A4Tp5 z_rq^LG=1AdXDH9VMqJFbp@enwbuN$#ZcX2KixyH>_Ja{O4M5#|olE|89bAXO1Af0l zLWqe*$dU$3b%~KglxkSkfT38BtkRldww&+Rv|nmt#I@3aNy>sOU0~69L1Iyg01K4_ zNQ7BvRB+n4B$DgkI^@H}{osC>^8>Ev!)f*T*N7We^alAlm&|cnIy6S>c`Ghy9QNwt zZ9pq?gAf+;gAo_qi_*s3pvTo8boGCd+tNb?d{o@GJuaQ7rxz5f(1Ez<#Mq`iZ^;U7 z6PI_0Lr-G4fD1)FYa$7Em^h}R#vSCvOyh5mczKIDq_58=L7I(~&sL=z#;TVtF4UWF zQz5m^N-1B1@fPDaH`Rou%hn#O9C5k12S&P`Hom^9h_@B|GK`eFL}U92Qkrz zFq8l43ydV9)YDQ6hGIcdomC~H_l3CHP1~x(<{Me*z$9fs=JO~#D@ZI#A!4?g0EsZO zO^QxBmt=AsT!(zXxF2rV57V++c^zQI#dX{8P+NOmH{*tf@#qlse66@QrSI5l+WJ~? z)5NLmhmq`DQo?m`9mX*cKL4#B#D$C7wOpdgG9!s7l_*x7p;!=Bt^Odby2G2>A3S-) A-T(jq literal 0 HcmV?d00001 diff --git a/tests/Layout/tests/buttons_3_bangle2.js b/tests/Layout/tests/buttons_3_bangle2.js new file mode 100644 index 000000000..bf70fcb38 --- /dev/null +++ b/tests/Layout/tests/buttons_3_bangle2.js @@ -0,0 +1,8 @@ + +var layout = new Layout({ type: "v", c: [ + {type:"txt", font:"6x8", label:"A test"}, +]},{btns:[ // Buttons... + {label:"A", cb:()=>{}}, + {label:"STOP", cb:()=>{}}, + {label:"B", cb:()=>{}}, +]}); diff --git a/tests/Layout/tests/buttons_osd_bangle1.bmp b/tests/Layout/tests/buttons_osd_bangle1.bmp new file mode 100644 index 0000000000000000000000000000000000000000..72307538590627e10d4ef5495ae491ec376d4116 GIT binary patch literal 28874 zcmeHP(QOlF3tj2M+>U^BGF|z6$t8tvIIv=NPjI27xY8+>)&c|sRBdgA_8pqjdbv|9s zC(K~-=Y>VQQ0s{bo?EZ-9(MsbWLm}VZGQ2QjxBxidS1l4t)jX0D(|MiLDt&$!BCX0 zt02W#L+5v_o*bI$%qcUrUv(aoUK zd!VsrE)U$$&@KWxZ#Wd)HbO@a0hkIk_RJv-=0#*z7g1nKJ+2>_Y0#R*LeLkRqFaJb z(AXyZ2t$L`ECQl0Hbu7tpP;c#`Uwk_w`LL0j$d&rEV?E5GkN3nQ|5(A$e~yWlxx2M zuE}CoDdD+;1GfxU(WAvy=N zxxAqoYfz0t(%7bpE^yXeYS7KlAAP zNGC5B&J(&3?#7#{#H|?{4v35A{9QkruauVsC=;{kN_p{a4lnbjhRy1&^RCjFohVjG z9AMKWyz2GK#+PYppMR^oJIk%!=lHa_y8c7n`u(IArR7)`PwQPm>#^{-iZ@AuKN(&xPnC?kN#5|d zPP(lnB^6JK_11a0@FJ%jm)H3gg?J$XG0rWaH|xA5_=H%u9i zQJwoWlpv_i5|#RWROfyTB?#HONG7`kVW&^Rlx49KQWTa@B}s_F_dCTmxl2BAu<9(9 zs>@Mz?vhU&tU8OO>T*<_yW|rGFV#7Gz2Eq-UR>R+Bi6f$7rvCB9|xXX??PVtr5DV? z?9YSnV-#z6-_`v3)jWHCSMhG!$?Wy)nU&WoG5EOjeQ>Kq=XcX$sHO8eR8AI5b!Md* zI-oiam6HWiompvy4yevU73ozWNEPJ_75aXSr+~1JVHX1B*DiF8A-oq{|o4F Bg#7>j literal 0 HcmV?d00001 diff --git a/tests/Layout/tests/buttons_osd_bangle1.js b/tests/Layout/tests/buttons_osd_bangle1.js new file mode 100644 index 000000000..108cb62b0 --- /dev/null +++ b/tests/Layout/tests/buttons_osd_bangle1.js @@ -0,0 +1,17 @@ +var BTN2 = 1, BTN3=2; +process.env = process.env;process.env.HWVERSION=1; +g = Graphics.createArrayBuffer(240,240,4); + +/* When displaying OSD buttons on Bangle.js 1 we should turn +the side buttons into 'soft' buttons and then use the physical +buttons for up/down selection */ + +var layout = new Layout({ type: "v", c: [ + {type:"txt", font:"6x8", label:"A test"}, + {type:"btn", label:"Button 1"}, + {type:"btn", label:"Button 2"} +]},{btns:[ // Buttons... + {label:"A", cb:()=>{}}, + {label:"STOP", cb:()=>{}}, + {label:"B", cb:()=>{}}, +]}); diff --git a/tests/Layout/tests/buttons_osd_bangle2.bmp b/tests/Layout/tests/buttons_osd_bangle2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..397c9e8717da077fd2fe17f01b98755b5e8d1ccb GIT binary patch literal 15562 zcmeI2OO6yV3_yd#5~;5;2VgNmtdO_^i#g03VUEC(QyYL=$kDni(pPye}Umu=c!o#=l6vFp# zdI>*6fa8~N3gLHnI)%5>=?&yjpL6yZ#RSeFXC3o{>tus6L9XIDl8JH+*nwP_%gKw& zwOe&qb;x(o>W9@2+kQBlj|b|E!S~-uT*&pn36ITpaRYb3z15c<(J=MF{V<7}HsIKN z7nl2Y)nU~k_P~1H;X=3@jVnvqFx`?NxhUPR%)y{ckj&^3F?*dqU*cc#nZ%t{2UAiL zq;!H!dPg!*ng9hO2TEb`vbW0`rNm7E)U-uVoC@rvIHzx6qD{$P^fI^d)K2n|IW7p# zsnxA26aWQv6{iLTKtZi;RiOYVsH-?NC;$p-b*l;mKtWx_sX+l?)VR&SAx(Th8W`{> z@@PYnYyfc;CZkTkhzA1-#RDEi&rU9R#=ICoBWt3b$h?T6NsQKLFlyt^t~44wx$z>3 z20&{x7`5?dR~n6;eW@}i8UU|t)W-ipF1*oix%u_k=YlUA_5U$Ons9j?u#>3E zNSVoamRTPJmC;$rTYq(~=4XQ8Zb2~2XUQtHRTxZlcjuza2cM5 z%j?TMmpYGoHd^CB6iq+qvTc*=x$!N)wO`DRqjEx$R>~wwvS9O_#Dy*Feh<05pH=de z6EOMyORgkTnY;6?@MQU}xp*(5+q&)xIUPB@=2;pkK0kQwnCIV=jpnIBT5FLcU7uIN z;U@0?XlxC@n#5I$%Y+W@kxvf(tHk+8KPYg%CIPp$moIa{f_#p9rup{;h1)Ye(5(k7 z;GGQQJ<~oekzjztA?0D*SYF7K{_0ncsjD1x)R(!MrV4^Ga}Sps6{mvX$w4<)94qs^ zab1;M`jwO2TQ(#YWzVm+W>6+b&9kze-wZ3JUCWcWv+7_@0wo*$Jp@12 zn8UrR`bsy9uJ0^v+B>8BLCA~Cl~{FHb&x%Q^MBh9?!?{WT3(_~*^pe6PLwe>C={}}, + {label:"STOP", cb:()=>{}}, + {label:"B", cb:()=>{}}, +]}); diff --git a/tests/Layout/tests/padding.bmp b/tests/Layout/tests/padding.bmp index b2d192750fc3d6e454e2216e58e42dc4b5c3d000..84ae4dc1bcdebea1b531156b07448700812f843a 100644 GIT binary patch literal 15562 zcmeI2F>BjE6vwYnXi7u+Rq_Ekgwm+gtw3wX zY~4gR+aS39-#b~BW1Vg3J%yOuvn`zt>*v4!_xw&i`{kQoFAT?PYil(ib`?jFwNX5d2pY!`jwxbm`*JO2^6Y9nCKhP?0yP(hQZDNyE^dwsu>vk%+2jB) zk~a+>`s=N^aF$FUqvLw$Y;PZ!Tjc||w?e45;zH~ay63q0vU7d^>ik`QzV8_;xrXD} zS*JL0XRY7fE%#@$fVuL>J>P_SFR1d`<(W-^%TokbNK|xJDAPoN+d8vpaG??9oMk(` znAyZ!j8)srCg!?)Se$;KU5UFivvuJ9XqK_MNOI?g<&Wa#`Fnvn=9%B5T$+1OBymSw zryWNeNx8oHP0XF?h1gmzS~4!i$}_*oxHC5^TkD-vuLIeZ`Awwvy#B(vLF4!4`FrkG z1h9mGi>ln_v{FKACDfoE^rKz%bm8)XPM$Lm9OeQSTr4KD_zKyLA$yy>&3Xc2Z9u7SD&zk1ai5F0Ub!r| zzYkuy#z1i3Vl*F~5A=DXG^Wk$q2a?|+=fn7y?4WPW-H{`?7dfeJ?=t2Sk4Lkd{E>z z#|>tXA1Zo>;2MyX=^cp6b}nrmUY%UBfIA+P4{5sx)QbzL{?`wQ_re7BO1NIZ zkSpP0<*+?~6>;f&UkLp5A-O+rojpB$XnrQ1%$B6yHGAdlz;zxD=|fRq@bt&+$e6!F zaBqrYZwM~^A!Mt#dpCpVJt~Sp#w{8s&s^ov&)>jB=^3K#>{hG4_3lzWPY@w+osZk- z1O0Nt|4gO-b6mRjA>EP>^e0;048g^(D}>k8i{G)FW@Rq0`g$>FZrtc!PMSWz)p;o9 b!yJlM;5s|*xewAh5Y7({1P6ixTkXJq)aC)t delta 755 zcmX?Ad8%^5B-Y6fv@Nk>1vak9uUI2Dzh-3;pRB;fI{BlF#N> z5(8-lOMYNL0sP34^HHRL9FSI+PBe`S3?QvwH~p6rWd^DwmJM_U$fE!MK?Dea7$B>l zHW8MDXakynO($MSkOm;et`n~$USsh}LhZxpPmp6!B`4zuQBVZpj0TY8d{9Ut17N7a gf)zt6O4Q>_G=x$dRNj4argowr*0c;tJ~hToMDtinz373bs&k`bE?q&G8f?v`81%KiU80|NsP|7Z9=d84ArtE9h;Q+-nlC Jxz@;+3jnVndfNa1 From f41b3c5a885d5af9c559e39773e4ab24d9d4a60e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 18 Oct 2021 12:01:40 +0100 Subject: [PATCH 0227/1062] Update bootloader to include polyfills for g.wrapString, g.imageMetrics, g.stringMetrics - and modify Layout lib to use those functions --- apps.json | 2 +- apps/boot/ChangeLog | 1 + apps/boot/bootupdate.js | 35 ++++++++++++++++++++++++++++++++ modules/Layout.js | 41 +++++++------------------------------- tests/Layout/bin/espruino | Bin 4459888 -> 4462968 bytes 5 files changed, 44 insertions(+), 35 deletions(-) diff --git a/apps.json b/apps.json index f7bec7695..d16213364 100644 --- a/apps.json +++ b/apps.json @@ -4,7 +4,7 @@ "tags": "tool,system,b2", "type":"bootloader", "icon": "bootloader.png", - "version":"0.30", + "version":"0.31", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "storage": [ {"name":".boot0","url":"boot0.js"}, diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 7f2220572..6cdf1b0e5 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -30,3 +30,4 @@ 0.29: Update boot0 to avoid code block (faster execution) Fix issues where 'Uncaught Error: Function not found' could happen with multiple .boot.js 0.30: Remove 'Get GPS time' at boot. Latest firmwares keep time through reboots, so this is not needed now +0.31: Add polyfills for g.wrapString, g.imageMetrics, g.stringMetrics diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 199bff6e3..7210ae731 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -131,6 +131,41 @@ else if (mode=="updown") { throw new Error("Unknown UI mode"); };\n`; } +if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill + boot += `Graphics.prototype.imageMetrics=function(src) { + if (src[0]) return {width:src[0],height:src[1]}; + else if ('object'==typeof src) return { + width:("width" in src) ? src.width : src.getWidth(), + height:("height" in src) ? src.height : src.getHeight()}; + var im = E.toString(src); + return {width:im.charCodeAt(0), height:im.charCodeAt(1)}; +};\n`; +} +if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill + boot += `Graphics.prototype.stringMetrics=function(txt) { + return {width:this.stringWidth(txt), height:this.getFontHeight()}; +};\n`; +} +if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill + boot += `Graphics.prototype.wrapString=function(str, maxWidth) { + var lines = []; + for (var unwrappedLine of str.split("\n")) { + var words = unwrappedLine.split(" "); + var line = words.shift(); + for (var word of words) { + if (g.stringWidth(line + " " + word) > maxWidth) { + lines.push(line); + line = word; + } else { + line += " " + word; + } + } + lines.push(line); + } + return lines; +};\n`; +} + // Append *.boot.js files require('Storage').list(/\.boot\.js/).forEach(bootFile=>{ // we add a semicolon so if the file is wrapped in (function(){ ... }() diff --git a/modules/Layout.js b/modules/Layout.js index 94abd42fd..99ce9b042 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -190,24 +190,6 @@ Layout.prototype.remove = function (l) { } }; -function wrappedLines(str, maxWidth) { - var lines = []; - for (var unwrappedLine of str.split("\n")) { - var words = unwrappedLine.split(" "); - var line = words.shift(); - for (var word of words) { - if (g.stringWidth(line + " " + word) > maxWidth) { - lines.push(line); - line = word; - } else { - line += " " + word; - } - } - lines.push(line); - } - return lines; -} - function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) { var bgCol = l.bgCol == null ? parentBg : g.toColor(l.bgCol); if (bgCol != parentBg || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { @@ -246,8 +228,9 @@ Layout.prototype.render = function (l) { "txt":function(l){ if (l.wrap) { g.setFont(l.font,l.fsz).setFontAlign(0,-1); - var lines = wrappedLines(l.label, l.w); + var lines = g.wrapString(l.label, l.w); var y = l.y+((l.h-g.getFontHeight()*lines.length)>>1); + // TODO: on 2v11 we can just render in a single drawString call lines.forEach((line, i) => g.drawString(line, l.x+(l.w>>1), y+g.getFontHeight()*i)); } else { g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); @@ -377,26 +360,16 @@ Layout.prototype.update = function() { if (l.wrap) { l._h = l._w = 0; } else { - g.setFont(l.font,l.fsz); - l._h = g.getFontHeight(); - l._w = g.stringWidth(l.label); + var m = g.setFont(l.font,l.fsz).stringMetrics(l.label); + l._w = m.width; l._h = m.height; } }, "btn": function(l) { l._h = 32; l._w = 20 + l.label.length*12; }, "img": function(l) { - var src = l.src(); // get width and height out of image - if (src[0]) { - l._w = src[0]; - l._h = src[1]; - } else if ('object'==typeof src) { - l._w = ("width" in src) ? src.width : src.getWidth(); - l._h = ("height" in src) ? src.height : src.getHeight(); - } else { - var im = E.toString(src); - l._w = im.charCodeAt(0); - l._h = im.charCodeAt(1); - } + var m = g.imageMetrics(l.src()); // get width and height out of image + l._w = m.width; + l._h = m.height; }, "": function(l) { // size should already be set up in width/height l._w = 0; diff --git a/tests/Layout/bin/espruino b/tests/Layout/bin/espruino index 3a423c1859b9174b119f34b78b93491cc509553b..8f9c1d8787b532197c3f1b583a0ce354de973d6a 100755 GIT binary patch literal 4462968 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%0AnnRwYg+N-Legdio8(^VrL{3j2FgDsTg*Q6t3 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

?SvrdMcX6Q|O+66!YV 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
NL1 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%0D<*RT|}Hj4EYJWUyI} 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

!N{Ud;vx5uEIW($yc5vYu398}k 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

U-=@#bK!K5-d(1+&;TP-zarGF#yabxfd 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 Date: Mon, 18 Oct 2021 13:35:28 +0100 Subject: [PATCH 0228/1062] oops --- modules/Layout.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/Layout.js b/modules/Layout.js index 99ce9b042..172a18258 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -415,3 +415,5 @@ Layout.prototype.clear = function(l) { if (l.bgCol!==undefined) g.setBgColor(l.bgCol); g.clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); }; + +exports = Layout; From 1e0f6cd67551b35abb6af7c36802ea896f862554 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 18 Oct 2021 14:33:42 +0100 Subject: [PATCH 0229/1062] Allow non-function images, and fix touch handling for 'fake' buttons on Bangle.js 2 --- modules/Layout.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/Layout.js b/modules/Layout.js index 172a18258..03aa6249b 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -29,7 +29,7 @@ layoutObject has: * `undefined` - blank, can be used for padding * `"txt"` - a text label, with value `label` and `r` for text rotation. 'font' is required * `"btn"` - a button, with value `label` and callback `cb` - * `"img"` - an image where the function `src` is called to return an image to draw + * `"img"` - an image where `src` is an image, or a function which is called to return an image to draw * `"custom"` - a custom block where `render(layoutObj)` is called to render * `"h"` - Horizontal layout, `c` is an array of more `layoutObject` * `"v"` - Veritical layout, `c` is an array of more `layoutObject` @@ -160,11 +160,12 @@ function Layout(layout, options) { if (process.env.HWVERSION==2) { // Handler for touch events function touchHandler(l,e) { - if (l.type=="btn" && l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) - l.cb(e); + if (l.type=="btn" && l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) { + if (e.type==2 && l.cbl) l.cbl(e); else if (l.cb) l.cb(e); + } if (l.c) l.c.forEach(n => touchHandler(n,e)); } - Bangle.touchHandler = function(_,e){touchHandler(layout,e)}; + Bangle.touchHandler = (_,e)=>touchHandler(this._l,e); Bangle.on('touch',Bangle.touchHandler); } @@ -253,7 +254,7 @@ Layout.prototype.render = function (l) { ]; g.setColor(l.selected?g.theme.bgH:g.theme.bg2).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly).setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); }, "img":function(l){ - g.drawImage(l.src(), l.x + (0|l.pad), l.y + (0|l.pad)); + g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad)); }, "custom":function(l){ l.render(l); },"h":function(l) { l.c.forEach(render); }, @@ -367,7 +368,7 @@ Layout.prototype.update = function() { l._h = 32; l._w = 20 + l.label.length*12; }, "img": function(l) { - var m = g.imageMetrics(l.src()); // get width and height out of image + var m = g.imageMetrics("function"==typeof l.src?l.src():l.src); // get width and height out of image l._w = m.width; l._h = m.height; }, "": function(l) { From 75422b7ff85e7a004a6d512166aeba3bd3e15232 Mon Sep 17 00:00:00 2001 From: Etienne Deux Date: Mon, 18 Oct 2021 10:21:50 +0200 Subject: [PATCH 0230/1062] Scale UI to banglejs 2 --- apps.json | 2 +- apps/svclock/ChangeLog | 1 + apps/svclock/vclock-simple.js | 43 ++++++++++++++++++++++++++--------- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/apps.json b/apps.json index c8e614625..e15ff5b9c 100644 --- a/apps.json +++ b/apps.json @@ -946,7 +946,7 @@ { "id": "svclock", "name": "Simple V-Clock", "icon": "vclock-simple.png", - "version":"0.02", + "version":"0.03", "description": "Modification of Simple Clock 0.04 to use Vectorfont", "tags": "clock", "type":"clock", diff --git a/apps/svclock/ChangeLog b/apps/svclock/ChangeLog index 671de492c..4db60ecd5 100644 --- a/apps/svclock/ChangeLog +++ b/apps/svclock/ChangeLog @@ -1,2 +1,3 @@ 0.01: Modification of SimpleClock 0.04 to use Vectorfont 0.02: Use Bangle.setUI for button/launcher handling +0.03: Scale to BangleJS 2 and add locale \ No newline at end of file diff --git a/apps/svclock/vclock-simple.js b/apps/svclock/vclock-simple.js index 62aad0dc3..637701a3f 100644 --- a/apps/svclock/vclock-simple.js +++ b/apps/svclock/vclock-simple.js @@ -1,17 +1,39 @@ /* jshint esversion: 6 */ const locale = require("locale"); -const timeFontSize = 65; -const dateFontSize = 20; -const gmtFontSize = 10; -const font = "Vector"; +var timeFontSize; +var dateFontSize; +var gmtFontSize; +var font = "Vector"; -const xyCenter = g.getWidth() / 2; -const yposTime = 75; -const yposDate = 130; -const yposYear = 175; -const yposGMT = 220; +var xyCenter = g.getWidth() / 2; +var yposTime; +var yposDate; +var yposYear; +var yposGMT; +switch (process.env.BOARD) { + case "EMSCRIPTEN": + timeFontSize = 65; + dateFontSize = 20; + gmtFontSize = 10; + + yposTime = 75; + yposDate = 130; + yposYear = 175; + yposGMT = 220; + break; + case "EMSCRIPTEN2": + timeFontSize = 48; + dateFontSize = 15; + gmtFontSize = 10; + + yposTime = 55; + yposDate = 95; + yposYear = 128; + yposGMT = 161; + break; +} // Check settings for what type our clock should be var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; @@ -20,8 +42,7 @@ function drawSimpleClock() { Bangle.drawWidgets(); // get date - //var d = new Date(); - var d = new Date(Date.parse('2011-04-11T14:5:30Z')); + var d = new Date(); g.reset(); // default draw styles // drawSting centered From 09c2718d52513917aba239c8c1f5f040052711f5 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 18 Oct 2021 22:17:31 +0100 Subject: [PATCH 0231/1062] fixed travis build error in toucher, rordered options in App Manager, back first --- apps/files/files.js | 20 ++++++++++---------- apps/toucher/app.js | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/files/files.js b/apps/files/files.js index 9ac6ebb35..e7b42c101 100644 --- a/apps/files/files.js +++ b/apps/files/files.js @@ -7,13 +7,9 @@ function showMainMenu() { '': { 'title': 'App Manager', }, - 'Free': { - value: undefined, - format: (v) => { - return store.getFree(); - }, - onchange: () => {} - }, + '< Back': ()=> {load();}, + 'Sort Apps': () => showSortAppsMenu(), + 'Manage Apps': ()=> showApps(), 'Compact': () => { E.showMessage('Compacting...'); try { @@ -22,9 +18,13 @@ function showMainMenu() { } showMainMenu(); }, - 'Apps': ()=> showApps(), - 'Sort Apps': () => showSortAppsMenu(), - '< Back': ()=> {load();} + 'Free': { + value: undefined, + format: (v) => { + return store.getFree(); + }, + onchange: () => {} + }, }; E.showMenu(mainmenu); } diff --git a/apps/toucher/app.js b/apps/toucher/app.js index b6c37f0d0..8ac198f52 100644 --- a/apps/toucher/app.js +++ b/apps/toucher/app.js @@ -140,7 +140,7 @@ function render(){ g.setColor(g.theme.fg); if (cycle == 2 && scale > 0.1) { - const fontSize = (process.env.HWVERSION == 2) ? 2 : 1; + let fontSize = (process.env.HWVERSION == 2) ? 2 : 1; if (process.env.HWVERSION == 1) { fontSize = (settings.highres) ? 3 : 1; } From 526dfa58bea77123f31e1be4e02ce1c0cbd60bb3 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:02:29 +1300 Subject: [PATCH 0232/1062] Create app.js Initial import --- apps/speedalt2/app.js | 609 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 609 insertions(+) create mode 100644 apps/speedalt2/app.js diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js new file mode 100644 index 000000000..f96c1c320 --- /dev/null +++ b/apps/speedalt2/app.js @@ -0,0 +1,609 @@ +/* +Speed and Altitude [speedalt2] +Mike Bennett mike[at]kereru.com +0.01 : Initial +*/ +var v = '0.01a'; + +/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ +var KalmanFilter = (function () { + 'use strict'; + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + /** + * KalmanFilter + * @class + * @author Wouter Bulten + * @see {@link http://github.com/wouterbulten/kalmanjs} + * @version Version: 1.0.0-beta + * @copyright Copyright 2015-2018 Wouter Bulten + * @license MIT License + * @preserve + */ + var KalmanFilter = + /*#__PURE__*/ + function () { + /** + * Create 1-dimensional kalman filter + * @param {Number} options.R Process noise + * @param {Number} options.Q Measurement noise + * @param {Number} options.A State vector + * @param {Number} options.B Control vector + * @param {Number} options.C Measurement vector + * @return {KalmanFilter} + */ + function KalmanFilter() { + var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, + _ref$R = _ref.R, + R = _ref$R === void 0 ? 1 : _ref$R, + _ref$Q = _ref.Q, + Q = _ref$Q === void 0 ? 1 : _ref$Q, + _ref$A = _ref.A, + A = _ref$A === void 0 ? 1 : _ref$A, + _ref$B = _ref.B, + B = _ref$B === void 0 ? 0 : _ref$B, + _ref$C = _ref.C, + C = _ref$C === void 0 ? 1 : _ref$C; + + _classCallCheck(this, KalmanFilter); + + this.R = R; // noise power desirable + + this.Q = Q; // noise power estimated + + this.A = A; + this.C = C; + this.B = B; + this.cov = NaN; + this.x = NaN; // estimated signal without noise + } + /** + * Filter a new value + * @param {Number} z Measurement + * @param {Number} u Control + * @return {Number} + */ + + + _createClass(KalmanFilter, [{ + key: "filter", + value: function filter(z) { + var u = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + + if (isNaN(this.x)) { + this.x = 1 / this.C * z; + this.cov = 1 / this.C * this.Q * (1 / this.C); + } else { + // Compute prediction + var predX = this.predict(u); + var predCov = this.uncertainty(); // Kalman gain + + var K = predCov * this.C * (1 / (this.C * predCov * this.C + this.Q)); // Correction + + this.x = predX + K * (z - this.C * predX); + this.cov = predCov - K * this.C * predCov; + } + + return this.x; + } + /** + * Predict next value + * @param {Number} [u] Control + * @return {Number} + */ + + }, { + key: "predict", + value: function predict() { + var u = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + return this.A * this.x + this.B * u; + } + /** + * Return uncertainty of filter + * @return {Number} + */ + + }, { + key: "uncertainty", + value: function uncertainty() { + return this.A * this.cov * this.A + this.R; + } + /** + * Return the last filtered measurement + * @return {Number} + */ + + }, { + key: "lastMeasurement", + value: function lastMeasurement() { + return this.x; + } + /** + * Set measurement noise Q + * @param {Number} noise + */ + + }, { + key: "setMeasurementNoise", + value: function setMeasurementNoise(noise) { + this.Q = noise; + } + /** + * Set the process noise R + * @param {Number} noise + */ + + }, { + key: "setProcessNoise", + value: function setProcessNoise(noise) { + this.R = noise; + } + }]); + + return KalmanFilter; + }(); + + return KalmanFilter; + +}()); + + +var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); + +// Load fonts +require("Font7x11Numeric7Seg").add(Graphics); + +var lf = {fix:0,satellites:0}; +var showMax = 0; // 1 = display the max values. 0 = display the cur fix +var pwrSav = 1; // 1 = default power saving with watch screen off and GPS to PMOO mode. 0 = screen kept on. +var canDraw = 1; +var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time. +var tmrLP; // Timer for delay in switching to low power after screen turns off + +var max = {}; +max.spd = 0; +max.alt = 0; +max.n = 0; // counter. Only start comparing for max after a certain number of fixes to allow kalman filter to have smoohed the data. + +var emulator = (process.env.BOARD=="EMSCRIPTEN")?1:0; // 1 = running in emulator. Supplies test values; + +var wp = {}; // Waypoint to use for distance from cur position. + +function nxtWp(inc){ + cfg.wp+=inc; + loadWp(); +} + +function loadWp() { + var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}]; + if (cfg.wp>=w.length) cfg.wp=0; + if (cfg.wp<0) cfg.wp = w.length-1; + savSettings(); + wp = w[cfg.wp]; +} + +function radians(a) { + return a*Math.PI/180; +} + +function distance(a,b){ + var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2)); + var y = radians(b.lat-a.lat); + + // Distance in selected units + var d = Math.sqrt(x*x + y*y) * 6371000; + d = (d/parseFloat(cfg.dist)).toFixed(2); + if ( d >= 100 ) d = parseFloat(d).toFixed(1); + if ( d >= 1000 ) d = parseFloat(d).toFixed(0); + + return d; +} + +function drawScrn(dat) { + + if (!canDraw) return; + + buf.clear(); + + var n; + n = dat.val.toString(); + + var s=40; // Font size + var l=n.length; + + if ( l <= 7 ) s=48; + if ( l <= 6 ) s=55; + if ( l <= 5 ) s=68; + if ( l <= 4 ) s=90; + if ( l <= 3 ) s=110; + + buf.setFontAlign(0,-1); //Centre + buf.setColor(1); + buf.setFontVector(s); + buf.drawString(n,110,-0); + + // Primary Units + buf.setFontAlign(-1,1); //left, bottom + buf.setColor(2); + buf.setFontVector(35); + buf.drawString(dat.unit,5,164); + + if ( dat.max ) drawMax(); // MAX display indicator + if ( dat.wp ) drawWP(); // Waypoint name + + //Sats + if ( dat.sat ) { + if ( dat.age > 10 ) { + if ( dat.age > 90 ) dat.age = '>90'; + drawSats('Age:'+dat.age); + } + else drawSats('Sats:'+dat.sats); + } + + g.reset(); + g.drawImage(img,0,40); + +} + +function drawClock() { + if (!canDraw) return; + buf.clear(); + var x, y; + x=120; + y=0; + buf.setFontAlign(0,-1); + buf.setFontVector(80); + time = require("locale").time(new Date(),1); + buf.setColor(3); + buf.drawString(time,x,y); + + g.reset(); + g.drawImage(img,0,40); +} + +function drawWP() { + var nm = wp.name; + if ( nm == undefined || nm == 'NONE' || cfg.modeA ==1 ) nm = ''; + buf.setColor(2); + + buf.setFontAlign(0,1); //left, bottom + buf.setFontVector(48); + buf.drawString(nm.substring(0,8),120,140); + +} + +function drawSats(sats) { + buf.setColor(3); + buf.setFont("6x8", 2); + buf.setFontAlign(1,1); //right, bottom + buf.drawString(sats,240,160); +} + +function drawMax() { + buf.setFontVector(30); + buf.setColor(2); + buf.setFontAlign(0,1); //centre, bottom + buf.drawString('MAX',120,164); +} + +function onGPS(fix) { + + if ( emulator ) { + fix.fix = 1; + fix.speed = 10 + (Math.random()*5); + fix.alt = 354 + (Math.random()*50); + fix.lat = -38.92; + fix.lon = 175.7613350; + fix.course = 245; + fix.satellites = 12; + fix.time = new Date(); + fix.smoothed = 0; + } + + var m; + + var sp = '---'; + var al = '---'; + var di = '---'; + var age = '---'; + + if (fix.fix) lf = fix; + + if (lf.fix) { + + // Smooth data + if ( lf.smoothed !== 1 ) { + if ( cfg.spdFilt ) lf.speed = spdFilter.filter(lf.speed); + if ( cfg.altFilt ) lf.alt = altFilter.filter(lf.alt); + lf.smoothed = 1; + if ( max.n <= 15 ) max.n++; + } + + + // Speed + if ( cfg.spd == 0 ) { + m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units + sp = parseFloat(m[1]); + cfg.spd_unit = m[2]; + } + else sp = parseFloat(lf.speed)/parseFloat(cfg.spd); // Calculate for selected units + + if ( sp < 10 ) sp = sp.toFixed(1); + else sp = Math.round(sp); + if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp); + + // Altitude + al = lf.alt; + al = Math.round(parseFloat(al)/parseFloat(cfg.alt)); + if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = parseFloat(al); + + // Distance to waypoint + di = distance(lf,wp); + if (isNaN(di)) di = 0; + + // Age of last fix (secs) + age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); + } + + if ( cfg.modeA == 0 ) { + // Speed +// if ( di <= 0 ) +// drawScrn({ +// val:sp, +// unit:cfg.spd_unit, +// sats:lf.satellites, +// age:age, +// fix:lf.fix +// }); // No WP selected +// else + if ( showMax ) + drawScrn({ + val:max.spd, + unit:cfg.spd_unit, + sats:lf.satellites, + age:age, + fix:lf.fix, + max:true, + wp:false, + sat:true + }); // Speed maximums + else + drawScrn({ + val:sp, + unit:cfg.spd_unit, + sats:lf.satellites, + age:age, + fix:lf.fix, + max:false, + wp:false, + sat:true + }); + } + + if ( cfg.modeA == 1 ) { + // Alt + if ( showMax ) + drawScrn({ + val:max.alt, + unit:cfg.alt_unit, + sats:lf.satellites, + age:age, + fix:lf.fix, + max:true, + wp:false, + sat:true + }); // Alt maximums + else + drawScrn({ + val:al, + unit:cfg.alt_unit, + sats:lf.satellites, + age:age, + fix:lf.fix, + max:false, + wp:false, + sat:true + }); + } + + if ( cfg.modeA == 2 ) { + // Dist + drawScrn({ + val:di, + unit:cfg.dist_unit, + sats:lf.satellites, + age:age, + fix:lf.fix, + max:false, + wp:true, + sat:true + }); + } + + if ( cfg.modeA == 3 ) { + // Large clock + drawClock(); + } + +} + +function setButtons(){ + + // BTN1 - Max speed/alt or next waypoint + setWatch(function(e) { + var dur = e.time - e.lastTime; + if ( cfg.modeA == 0 || cfg.modeA == 1 ) { + // Spd+Alt mode - Switch between fix and MAX + if ( dur < 2 ) showMax = !showMax; // Short press toggle fix/max display + else { max.spd = 0; max.alt = 0; } // Long press resets max values. + } + else if ( cfg.modeA == 2) nxtWp(1); // Dist mode - Select next waypoint + onGPS(lf); + }, BTN1, { edge:"falling",repeat:true}); + + // Power saving on/off + setWatch(function(e){ + pwrSav=!pwrSav; + if ( pwrSav ) { + LED1.reset(); + var s = require('Storage').readJSON('setting.json',1)||{}; + var t = s.timeout||10; + Bangle.setLCDTimeout(t); + } + else { + Bangle.setLCDTimeout(0); + Bangle.setLCDPower(1); + LED1.set(); + } + }, BTN2, {repeat:true,edge:"falling"}); + + // BTN3 - next screen + setWatch(function(e){ + cfg.modeA = cfg.modeA+1; + if ( cfg.modeA > 3 ) cfg.modeA = 0; + savSettings(); + onGPS(lf); + }, BTN3, {repeat:true,edge:"falling"}); + +/* + // Touch left screen to toggle display + setWatch(function(e){ + cfg.primSpd = !cfg.primSpd; + savSettings(); + onGPS(lf); // Update display + }, BTN4, {repeat:true,edge:"falling"}); +*/ + +} + +function updateClock() { + if (!canDraw) return; +// drawTime(); + g.reset(); + g.drawImage(img,0,40); + if ( emulator ) {max.spd++;max.alt++;} +} + +function startDraw(){ + canDraw=true; + setLpMode('SuperE'); // off + g.clear(); + Bangle.drawWidgets(); + onGPS(lf); // draw app screen +} + +function stopDraw() { + canDraw=false; + if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 10000); //Drop to low power in 10 secs. Keep lp mode off until we have a first fix. +} + +function savSettings() { + require("Storage").write('speedalt.json',cfg); +} + +function setLpMode(m) { + if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power + if ( !gpssetup ) return; + gpssetup.setPowerMode({power_mode:m}); +} + +// =Main Prog + +// Read settings. +let cfg = require('Storage').readJSON('speedalt.json',1)||{}; + +cfg.spd = cfg.spd||0; // Multiplier for speed unit conversions. 0 = use the locale values for speed +cfg.spd_unit = cfg.spd_unit||''; // Displayed speed unit +cfg.alt = cfg.alt||0.3048;// Multiplier for altitude unit conversions. +cfg.alt_unit = cfg.alt_unit||'feet'; // Displayed altitude units +cfg.dist = cfg.dist||1000;// Multiplier for distnce unit conversions. +cfg.dist_unit = cfg.dist_unit||'km'; // Displayed altitude units +cfg.colour = cfg.colour||0; // Colour scheme. +cfg.wp = cfg.wp||0; // Last selected waypoint for dist +cfg.modeA = cfg.modeA||0; // 0=Speed 1=Alt 2=Dist 3=Clock [0 = [D]ist, 1 = [A]ltitude, 2 = [C]lock] +cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary + +cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt; +cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt; + +if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 }); +if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 }); + +loadWp(); + +/* +Colour Pallet Idx +0 : Background (black) +1 : Speed/Alt +2 : Units +3 : Sats +*/ +var img = { + width:buf.getWidth(), + height:buf.getHeight(), + bpp:2, + buffer:buf.buffer, + palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB]) +}; + +if ( cfg.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFF6,0xDFFF]); +if ( cfg.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xFAE0,0xF813]); + +var SCREENACCESS = { + withApp:true, + request:function(){this.withApp=false;stopDraw();}, + release:function(){this.withApp=true;startDraw();} +}; + +Bangle.on('lcdPower',function(on) { + if (!SCREENACCESS.withApp) return; + if (on) startDraw(); + else stopDraw(); +}); + +var gpssetup; +try { + gpssetup = require("gpssetup"); +} catch(e) { + gpssetup = false; +} + +// All set up. Lets go. +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +onGPS(lf); +Bangle.setGPSPower(1); + +if ( gpssetup ) { + gpssetup.setPowerMode({power_mode:"SuperE"}).then(function() { Bangle.setGPSPower(1); }); +} +else { + Bangle.setGPSPower(1); +} + +Bangle.on('GPS', onGPS); + +setButtons(); +setInterval(updateClock, 10000); From d18acdcee8cc0692ee1f3e5d376578a3a1dba73a Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:04:27 +1300 Subject: [PATCH 0233/1062] Create ChangeLog --- apps/speedalt2/ChangeLog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/speedalt2/ChangeLog diff --git a/apps/speedalt2/ChangeLog b/apps/speedalt2/ChangeLog new file mode 100644 index 000000000..6876bcec9 --- /dev/null +++ b/apps/speedalt2/ChangeLog @@ -0,0 +1 @@ +0.01: Initial import. From 62fcb5b9714bd4095db66e5f3664d629165bdb6a Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:06:26 +1300 Subject: [PATCH 0234/1062] Create README.md --- apps/speedalt2/README.md | 150 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 apps/speedalt2/README.md diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md new file mode 100644 index 000000000..24596e512 --- /dev/null +++ b/apps/speedalt2/README.md @@ -0,0 +1,150 @@ +# GPS Speed, Altimeter and Distance to Waypoint + +You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint. + +Within the [A]ltitude and [D]istance displays modes one figure is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed. + +The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. + +## Buttons and Controls + +BTN3 : Cycles the modes between Speed+[A]ltitude, Speed+[D]istance and large Time/Waypoint + +### [A]ltitude mode + +BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. + +BTN1 : Long press > 2 secs resets the recorded maximum values. + +### [D]istance mode + +BTN1 : Select next waypoint. Last fix distance from selected waypoint is displayed. + +### Large mode + +BTN1 : Select next waypoint. + +### All modes + +BTN2 : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts. + +BTN3 : Long press exit and return to watch. + +BTN4 : Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display. + +## App Settings + +Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance can be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default (three colours), high contrast (all white on black) or night ( all red on black ). + +## Kalman Filter + +This filter smooths the altitude and the speed values and reduces these values 'jumping around' from one GPS fix to the next. The down side of this is that if these values change rapidly ( eg. a quick change in altitude ) then it can take a few GPS fixes for the values to move to the new vlaues. Disabling the Kalman filter in the settings will cause the raw values to be displayed from each GPS fix as they are found. + +## Loss of fix + +When the GPS obtains a fix the number of satellites is displayed as 'Sats:nn'. When unable to obtain a fix then the last known fix is used and the age of that fix in seconds is displayed as 'Age:nn'. Seeing 'Sats' or 'Age' indicates whether the GPS has a current fix or not. + +## Screens + +Speed and Altitude:
+![](screen1.png)

+Left tap swaps displays:
+![](screen2.png)

+Distance to waypoint DeltaW:
+![](screen5.png)

+MAX Values instead:
+![](screen3.png)

+Settings:
+![](screen4.png)

+ +## Power Saving + +The The GPS Adv Sport app obeys the watch screen off timeouts as a power saving measure. Restore the screen as per any of the colck/watch apps. Use BTN2 to lock the screen on but doing this will use more battery. + +This app will work quite happily on its own but will use the [GPS Setup App](https://banglejs.com/apps/#gps%20setup) if it is installed. You may choose to use the GPS Setup App to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Setup App Readme to understand what this does. + +When using the GPS Setup App this app switches the GPS to SuperE (default) mode while the display is lit and showing fix information. This ensures that that fixes are updated every second or so. 10 seconds after the display is blanked by the watch this app will switch the GPS to PSMOO mode and will only attempt to get a fix every two minutes. This improves power saving while the display is off and the delay gives an opportunity to restore the display before the GPS power mode is switched. + +The MAX values continue to be collected with the display off so may appear a little odd after the intermittent fixes of the low power mode. + +## Waypoints + +Waypoints are used in [D]istance mode. Create a file waypoints.json and write to storage on the Bangle.js using the IDE. The first 6 characters of the name are displayed in Speed+[D]istance mode. + +The [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app in the App Loader has a really nice waypoints file editor. (Must be connected to your Bangle.JS and then click on the Download icon.) + +Sample waypoints.json (My sailing waypoints) + +

+[
+  {
+  "name":"NONE"
+  },
+  {
+  "name":"Omori",
+  "lat":-38.9058670,
+  "lon":175.7613350
+  },
+  {
+  "name":"DeltaW",
+  "lat":-38.9438550,
+  "lon":175.7676930
+  },
+  {
+  "name":"DeltaE",
+  "lat":-38.9395240,
+  "lon":175.7814420
+  },
+  {
+  "name":"BtClub",
+  "lat":-38.9446020,
+  "lon":175.8475720
+  },
+  {
+  "name":"Hapua",
+  "lat":-38.8177750,
+  "lon":175.8088720
+  },
+  {
+  "name":"Nook",
+  "lat":-38.7848090,
+  "lon":175.7839440
+  },
+  {
+  "name":"ChryBy",
+  "lat":-38.7975050,
+  "lon":175.7551960
+  },
+  {
+  "name":"Waiha",
+  "lat":-38.7219630,
+  "lon":175.7481520
+  },
+  {
+  "name":"KwaKwa",
+  "lat":-38.6632310,
+  "lon":175.8670320
+  },
+  {
+  "name":"Hatepe",
+  "lat":-38.8547420,
+  "lon":176.0089124
+  },
+  {
+  "name":"Kinloc",
+  "lat":-38.6614442,
+  "lon":175.9161607
+  }
+]
+
+ +## Comments and Feedback + +Developed for my use in sailing, cycling and motorcycling. If you find this software useful or have feedback drop me a line mike[at]kereru.com. Enjoy! + +## Thanks + +Many thanks to Gordon Williams. Awesome job. + +Special thanks also to @jeffmer, for the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app and @hughbarney for the Low power GPS code development and Wouter Bulten for the Kalman filter code. + From 54bb8b56607264d5f2794ed4402b6cc91ca280f1 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:07:32 +1300 Subject: [PATCH 0235/1062] Create app-icon.js --- apps/speedalt2/app-icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/speedalt2/app-icon.js diff --git a/apps/speedalt2/app-icon.js b/apps/speedalt2/app-icon.js new file mode 100644 index 000000000..f4f24a18b --- /dev/null +++ b/apps/speedalt2/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AE+sFtoABF12swItsF9QuFR4IwmFwwvnFw4vCGEYuIF4JgjFxIvkFxQvCGBfOAAQvqFwYwRFxYvDGBIvUFxgv/F6IuNF4n+0nB4TvXFxwvF4XBAALlPF7ZfBGC4uPF4rABGAYAGTQwvad4YwKFzYvIGBQvfFwgAE3Qvt4IvEFzgvCLxO7Lx7vULzIzTFwIvgGZheFRAiNRGSQvpGYouesYAGmQAKq3CE4PIC4wviq2eFwPCroveCRSGEC6Qv0DAwRLcoouWC4VdVYQXkr1eAgVdAoIABroNEB4gHHC5QvHwQSDAAOCA74vH1uICQIABxGtA74vIAEwv/F/4vXAH4A/AHY")) From 76d2616a6bb196fbb71facccb96c65f9e8a8251e Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:08:43 +1300 Subject: [PATCH 0236/1062] Add files via upload --- apps/speedalt2/app.png | Bin 0 -> 1639 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/speedalt2/app.png diff --git a/apps/speedalt2/app.png b/apps/speedalt2/app.png new file mode 100644 index 0000000000000000000000000000000000000000..93d8e57dcbfaf905321fffcf06df042aa49d7dc8 GIT binary patch literal 1639 zcmV-t2AKJYP)@pC8}P=li+m^Env72)@5?yIePa+S>ZbmYsjOa=2m` z!{OB3)^@Gi>3mz!4w$0Hsk)+sbUjipG!&a5|mi z0)`wD_))?j-{$=p(Hmdb{pQ4>#*6Vi6tc_Zs;~=3feFd%R;{|DPtl#K#zRAi7Y`X1 z;B-32+0egXk^BI1gjay&Ter42GL0Dz85MB$^yxyo$U$Iys+>m`&vy%a=qU``n@P+> zNP8DjRdIK?-TImuq^&ql&*I#FWmi3gE><5sB1FaOuOpr#vV?n<_ z(dFmE1qqjs%jGHp7J&{q>2Qby*D7qRpED;gC=2aCEBXiDExut%@3&{)$jNhX)r2YG z^dnNg&zDjGMkx7$Y$pHGo;N24gYr|qS+FpfUBG`~4S=I0`nS$#h_+3*gamysqb!pk zXv%7YEpd4d5ubDqMVujc>)h5J>qvFn-dc4qzLwO;v4OF4uHK zDiy5+ZXrpN_sZ@=#Xj{d&k>z5AX5;YnD;7bECtO;8eHUgNwAw_bgd#HLExkQ@!Xj+ zXB>X7_a+mwQjkhu24U}4pZZo9%HcI2rhoVL=_y*H6IEB8WWWn~`V z-Tpy)d;4g+$ZY~Y$B;^am0~yN4W?9AbqDCW6kH$2X1iauedJGMVA;$qJ+`S27!^;a zLFHfD)9b&=?EzjY{WpaYdh*?c4qo{24Fn`I6~AH7lb74K>t{82$^4}<13f@#JcasV z&KF6MZz%O8J%h>nRCsynRJse|UsOmac2ZauAatVf3o!C+e}8AsYqKqV^h0Z_b|Kt1 z@O_l9{PWVHU!u&4Ymd6Dd|G#zCMOdsSK06%Q^1oACnNf8o85N^@B&=+bs3j^T{fWdc+A|x(cEZ$IG!5wbnEk_ zzpY`zq1m!&pEnc#(LQ5#p(i)~!vyvkhHU0Pfl~n8(?8u^c&_xWf^(r)p||8Ly(MS& zk3BghbW*$AKFu`sF)m((MDVTj>G)q)J0s$u#}x4D?$7k|?)Yu_xT{0E#ii6gwD`J+ zoMU#ODHE@txF{F*m*+X}3AOBn4m;z^3mIFQ4{*u#;fR@m_fLG4-4jffF?;5ih@6Mz lPrm;rMhY0g2u5&e@jt$gS?$`E2( Date: Tue, 19 Oct 2021 14:15:17 +1300 Subject: [PATCH 0237/1062] Added speedalt2 Alternative version of the original speedalt. --- apps.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps.json b/apps.json index d16213364..957bf723b 100644 --- a/apps.json +++ b/apps.json @@ -2896,6 +2896,25 @@ {"name":"speedalt.json"} ] }, +{ "id": "speedalt2", + "name": "GPS Adventure Sports II", + "shortName":"GPS Adv Sport", + "icon": "app.png", + "version":"0.01", + "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", + "tags": "tool,outdoors", + "type":"app", + "allow_emulator":true, + "readme": "README.md", + "storage": [ + {"name":"speedalt.app.js","url":"app.js"}, + {"name":"speedalt.img","url":"app-icon.js","evaluate":true}, + {"name":"speedalt.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"speedalt.json"} + ] +}, { "id": "de-stress", "name": "De-Stress", "shortName":"De-Stress", From 1218892d1c8ff0601760450ad5820e9a533ff110 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:16:57 +1300 Subject: [PATCH 0238/1062] Create settings.js --- apps/speedalt2/settings.js | 89 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 apps/speedalt2/settings.js diff --git a/apps/speedalt2/settings.js b/apps/speedalt2/settings.js new file mode 100644 index 000000000..488ba3b81 --- /dev/null +++ b/apps/speedalt2/settings.js @@ -0,0 +1,89 @@ +(function(back) { + + let settings = require('Storage').readJSON('speedalt.json',1)||{}; + //settings.buzz = settings.buzz||1; + + function writeSettings() { + require('Storage').write('speedalt.json',settings); + } + + function setUnits(m,u) { + settings.spd = m; + settings.spd_unit = u; + writeSettings(); + } + + function setUnitsAlt(m,u) { + settings.alt = m; + settings.alt_unit = u; + writeSettings(); + } + + function setUnitsDist(d,u) { + settings.dist = d; + settings.dist_unit = u; + writeSettings(); + } + + function setColour(c) { + settings.colour = c; + writeSettings(); + } + + + const appMenu = { + '': {'title': 'GPS Speed Alt'}, + '< Back': back, + '< Load GPS Adv Sport': ()=>{load('speedalt.app.js');}, + 'Units' : function() { E.showMenu(unitsMenu); }, + 'Colours' : function() { E.showMenu(colMenu); }, + 'Kalman Filter' : function() { E.showMenu(kalMenu); }/*, + 'Vibrate' : { + value : settings.buzz, + format : v => v?"On":"Off", + onchange : () => { settings.buzz = !settings.buzz; writeSettings(); } + }*/ + }; + + const unitsMenu = { + '': {'title': 'Units'}, + '< Back': function() { E.showMenu(appMenu); }, + 'default (spd)' : function() { setUnits(0,''); }, + 'Kph (spd)' : function() { setUnits(1,'kph'); }, + 'Knots (spd)' : function() { setUnits(1.852,'kts'); }, + 'Mph (spd)' : function() { setUnits(1.60934,'mph'); }, + 'm/s (spd)' : function() { setUnits(3.6,'m/s'); }, + 'Km (dist)' : function() { setUnitsDist(1000,'km'); }, + 'Miles (dist)' : function() { setUnitsDist(1609.344,'mi'); }, + 'Nm (dist)' : function() { setUnitsDist(1852.001,'nm'); }, + 'Meters (alt)' : function() { setUnitsAlt(1,'m'); }, + 'Feet (alt)' : function() { setUnitsAlt(0.3048,'ft'); } + }; + + const colMenu = { + '': {'title': 'Colours'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Default' : function() { setColour(0); }, + 'Hi Contrast' : function() { setColour(1); }, + 'Night' : function() { setColour(2); } + }; + + const kalMenu = { + '': {'title': 'Kalman Filter'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Speed' : { + value : settings.spdFilt, + format : v => v?"On":"Off", + onchange : () => { settings.spdFilt = !settings.spdFilt; writeSettings(); } + }, + 'Altitude' : { + value : settings.altFilt, + format : v => v?"On":"Off", + onchange : () => { settings.altFilt = !settings.altFilt; writeSettings(); } + } + }; + + + E.showMenu(appMenu); + +}); From 9c4b020dc259b1c0c4a8ccbcb3522d3cfcd4c00f Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:25:25 +1300 Subject: [PATCH 0239/1062] Update README.md --- apps/speedalt2/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index 24596e512..f918a7c12 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -1,5 +1,10 @@ # GPS Speed, Altimeter and Distance to Waypoint +What is the difference between GPS Adventure Sports and GPS Adventure Sports II ? +GPS Adventure Sports has 3 screens, each of which display different sets of information. +GPS Adventure Sports II has 4 screens, each of which displays just one of Speed, Altitude, Distance to waypoint or Time. +Use [BTN3] to cycle through the screens. + You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint. Within the [A]ltitude and [D]istance displays modes one figure is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed. From 1a487fa7d81c470cdfb834d8115ef8cd643582d9 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:32:12 +1300 Subject: [PATCH 0240/1062] Update README.md --- apps/speedalt2/README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index f918a7c12..96deac7d3 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -1,9 +1,12 @@ # GPS Speed, Altimeter and Distance to Waypoint -What is the difference between GPS Adventure Sports and GPS Adventure Sports II ? -GPS Adventure Sports has 3 screens, each of which display different sets of information. -GPS Adventure Sports II has 4 screens, each of which displays just one of Speed, Altitude, Distance to waypoint or Time. -Use [BTN3] to cycle through the screens. +What is the difference between [GPS Adventure Sports] and [GPS Adventure Sports II] ? + +[GPS Adventure Sports] has 3 screens, each of which display different sets of information. + +[GPS Adventure Sports II] has 4 screens, each of which displays just one of Speed, Altitude, Distance to waypoint or Time. + +In all other respect they perform the same functions. Use BTN3 to cycle through the screens. You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint. From 3be92b67865bf0aa6b8bfde03763250f294f40cd Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:50:15 +1300 Subject: [PATCH 0241/1062] Update settings.js --- apps/speedalt2/settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/speedalt2/settings.js b/apps/speedalt2/settings.js index 488ba3b81..2b3de16d3 100644 --- a/apps/speedalt2/settings.js +++ b/apps/speedalt2/settings.js @@ -1,10 +1,10 @@ (function(back) { - let settings = require('Storage').readJSON('speedalt.json',1)||{}; + let settings = require('Storage').readJSON('speedalt2.json',1)||{}; //settings.buzz = settings.buzz||1; function writeSettings() { - require('Storage').write('speedalt.json',settings); + require('Storage').write('speedalt2.json',settings); } function setUnits(m,u) { @@ -34,7 +34,7 @@ const appMenu = { '': {'title': 'GPS Speed Alt'}, '< Back': back, - '< Load GPS Adv Sport': ()=>{load('speedalt.app.js');}, + '< Load GPS Adv Sport': ()=>{load('speedalt2.app.js');}, 'Units' : function() { E.showMenu(unitsMenu); }, 'Colours' : function() { E.showMenu(colMenu); }, 'Kalman Filter' : function() { E.showMenu(kalMenu); }/*, From 23f28c1698be53d32698de1af00d564ed3ea7470 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:52:54 +1300 Subject: [PATCH 0242/1062] Update apps.json --- apps.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps.json b/apps.json index 957bf723b..30ba692fc 100644 --- a/apps.json +++ b/apps.json @@ -2907,12 +2907,12 @@ "allow_emulator":true, "readme": "README.md", "storage": [ - {"name":"speedalt.app.js","url":"app.js"}, - {"name":"speedalt.img","url":"app-icon.js","evaluate":true}, - {"name":"speedalt.settings.js","url":"settings.js"} + {"name":"speedalt2.app.js","url":"app.js"}, + {"name":"speedalt2.img","url":"app-icon.js","evaluate":true}, + {"name":"speedalt2.settings.js","url":"settings.js"} ], "data": [ - {"name":"speedalt.json"} + {"name":"speedalt2.json"} ] }, { "id": "de-stress", From 893a4f66f920c64a1e5f310ffab75c8c0de7261a Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:56:23 +1300 Subject: [PATCH 0243/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 30ba692fc..514b75313 100644 --- a/apps.json +++ b/apps.json @@ -2898,7 +2898,7 @@ }, { "id": "speedalt2", "name": "GPS Adventure Sports II", - "shortName":"GPS Adv Sport", + "shortName":"GPS Adv Sport II", "icon": "app.png", "version":"0.01", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", From 39a4cb8e174c57511f0bfe0763f9786d2d0c6a2b Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 14:59:20 +1300 Subject: [PATCH 0244/1062] Update settings.js --- apps/speedalt2/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/settings.js b/apps/speedalt2/settings.js index 2b3de16d3..26ae010bb 100644 --- a/apps/speedalt2/settings.js +++ b/apps/speedalt2/settings.js @@ -32,7 +32,7 @@ const appMenu = { - '': {'title': 'GPS Speed Alt'}, + '': {'title': 'GPS Adv Sprt II'}, '< Back': back, '< Load GPS Adv Sport': ()=>{load('speedalt2.app.js');}, 'Units' : function() { E.showMenu(unitsMenu); }, From 8b6dadc3fbcafe0387db022605b65d5ce82ee00b Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 15:00:09 +1300 Subject: [PATCH 0245/1062] Update settings.js --- apps/speedalt/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt/settings.js b/apps/speedalt/settings.js index 488ba3b81..63d77971e 100644 --- a/apps/speedalt/settings.js +++ b/apps/speedalt/settings.js @@ -32,7 +32,7 @@ const appMenu = { - '': {'title': 'GPS Speed Alt'}, + '': {'title': 'GPS Adv Sprt'}, '< Back': back, '< Load GPS Adv Sport': ()=>{load('speedalt.app.js');}, 'Units' : function() { E.showMenu(unitsMenu); }, From b5e41bc3964c1f1810aa7074fecca12736585fa0 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 15:01:11 +1300 Subject: [PATCH 0246/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 514b75313..1b5e6470b 100644 --- a/apps.json +++ b/apps.json @@ -2900,7 +2900,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From 10ad5074d2eba1ec6216e6848ded17ebcfa7bba7 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 15:14:44 +1300 Subject: [PATCH 0247/1062] Update app.js --- apps/speedalt2/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index f96c1c320..ba5a6768c 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -3,7 +3,7 @@ Speed and Altitude [speedalt2] Mike Bennett mike[at]kereru.com 0.01 : Initial */ -var v = '0.01a'; +var v = '0.03a'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -519,7 +519,7 @@ function stopDraw() { } function savSettings() { - require("Storage").write('speedalt.json',cfg); + require("Storage").write('speedalt2.json',cfg); } function setLpMode(m) { @@ -531,7 +531,7 @@ function setLpMode(m) { // =Main Prog // Read settings. -let cfg = require('Storage').readJSON('speedalt.json',1)||{}; +let cfg = require('Storage').readJSON('speedalt2.json',1)||{}; cfg.spd = cfg.spd||0; // Multiplier for speed unit conversions. 0 = use the locale values for speed cfg.spd_unit = cfg.spd_unit||''; // Displayed speed unit From b3a4bde0ea15cf7a04d82249bdc61da035abb994 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 15:16:04 +1300 Subject: [PATCH 0248/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 1b5e6470b..1488aa4bc 100644 --- a/apps.json +++ b/apps.json @@ -2900,7 +2900,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"0.02", + "version":"0.03", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From b9672a18dc652d024986f47ff9b5e8552762bbf0 Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 15:29:48 +1300 Subject: [PATCH 0249/1062] Update README.md --- apps/speedalt2/README.md | 39 ++++----------------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index 96deac7d3..ab2047af6 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -8,37 +8,19 @@ What is the difference between [GPS Adventure Sports] and [GPS Adventure Sports In all other respect they perform the same functions. Use BTN3 to cycle through the screens. -You can switch between three display modes. One showing speed and altitude (A), one showing speed and distance to waypoint (D) and a large dispay of time and selected waypoint. - -Within the [A]ltitude and [D]istance displays modes one figure is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed. - The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. ## Buttons and Controls -BTN3 : Cycles the modes between Speed+[A]ltitude, Speed+[D]istance and large Time/Waypoint +BTN1 ( Speed and Altitude ) Short press < 2 secs toggles the display between last reading and maximum recorded. Long press > 2 secs resets the recorded maximum values. -### [A]ltitude mode - -BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum speed/alt values recorded. - -BTN1 : Long press > 2 secs resets the recorded maximum values. - -### [D]istance mode - -BTN1 : Select next waypoint. Last fix distance from selected waypoint is displayed. - -### Large mode - -BTN1 : Select next waypoint. - -### All modes +BTN1 ( Distance ) Select next waypoint. Last fix distance from selected waypoint is displayed. BTN2 : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts. -BTN3 : Long press exit and return to watch. +BTN3 : Cycles the modes between Speed, Altitude, Distance to waypoint and Time -BTN4 : Left Display Tap : Swaps which figure is in the large display. You can have either speed or [A]ltitude/[D]istance on the large primary display. +BTN3 : Long press exit and return to watch. ## App Settings @@ -52,19 +34,6 @@ This filter smooths the altitude and the speed values and reduces these values ' When the GPS obtains a fix the number of satellites is displayed as 'Sats:nn'. When unable to obtain a fix then the last known fix is used and the age of that fix in seconds is displayed as 'Age:nn'. Seeing 'Sats' or 'Age' indicates whether the GPS has a current fix or not. -## Screens - -Speed and Altitude:
-![](screen1.png)

-Left tap swaps displays:
-![](screen2.png)

-Distance to waypoint DeltaW:
-![](screen5.png)

-MAX Values instead:
-![](screen3.png)

-Settings:
-![](screen4.png)

- ## Power Saving The The GPS Adv Sport app obeys the watch screen off timeouts as a power saving measure. Restore the screen as per any of the colck/watch apps. Use BTN2 to lock the screen on but doing this will use more battery. From 47979bc979a36fe19215514e2ca6fa20fcc23e2b Mon Sep 17 00:00:00 2001 From: nujw Date: Tue, 19 Oct 2021 15:32:12 +1300 Subject: [PATCH 0250/1062] Update README.md --- apps/speedalt2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index ab2047af6..236558916 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -28,7 +28,7 @@ Select the desired display units. Speed can be as per the default locale, kph, k ## Kalman Filter -This filter smooths the altitude and the speed values and reduces these values 'jumping around' from one GPS fix to the next. The down side of this is that if these values change rapidly ( eg. a quick change in altitude ) then it can take a few GPS fixes for the values to move to the new vlaues. Disabling the Kalman filter in the settings will cause the raw values to be displayed from each GPS fix as they are found. +This filter smooths the altitude and the speed values and reduces these values 'jumping around' from one GPS fix to the next. The down side of this is that if these values change rapidly ( eg. a quick change in altitude ) then it can take a few GPS fixes for the values to move to the new values. Disabling the Kalman filter in the settings will cause the raw values to be displayed from each GPS fix as they are found. ## Loss of fix From 5bbfb1109910856575cc348012dbc44d489a8005 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 18 Oct 2021 19:43:11 -0700 Subject: [PATCH 0251/1062] Update interface.html update interface --- apps/schoolCalendar/interface.html | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/interface.html index b26c07723..0ff6a2af3 100644 --- a/apps/schoolCalendar/interface.html +++ b/apps/schoolCalendar/interface.html @@ -22,12 +22,28 @@

Create your events on the current week. Keep in note that your events repeat weekly.

One you have created your events, Click

+

All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.

+ + + + From a94790391a9f13ab83c9658ba48080b9d5967190 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 20 Oct 2021 15:11:04 +0100 Subject: [PATCH 0286/1062] Get rid of "b2" and "bno2" tags and add "supports" array. Use the opportunity to refactor apps.json so everything looks pretty --- README.md | 4 +- apps.json | 4404 +++++++++++++------------ apps/_example_app/add_to_apps.json | 5 +- apps/_example_widget/add_to_apps.json | 5 +- bin/create_app_supports_field.js | 83 + bin/sanitycheck.js | 11 +- 6 files changed, 2473 insertions(+), 2039 deletions(-) create mode 100644 bin/create_app_supports_field.js diff --git a/README.md b/README.md index 49f616964..3da301dba 100644 --- a/README.md +++ b/README.md @@ -217,8 +217,9 @@ and which gives information about the app for the Launcher. { "id": "appid", // 7 character app id "name": "Readable name", // readable name "shortName": "Short name", // short name for launcher - "icon": "icon.png", // icon in apps/ + "version": "0v01", // the version of this app "description": "...", // long description (can contain markdown) + "icon": "icon.png", // icon in apps/ "type":"...", // optional(if app) - // 'app' - an application // 'widget' - a widget @@ -226,6 +227,7 @@ and which gives information about the app for the Launcher. // 'bootloader' - code that runs at startup only // 'RAM' - code that runs and doesn't upload anything to storage "tags": "", // comma separated tag list for searching + "supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2 "dependencies" : { "notify":"type" } // optional, app 'types' we depend on // for instance this will use notify/notifyfs is they exist, or will pull in 'notify' "readme": "README.md", // if supplied, a link to a markdown-style text file diff --git a/apps.json b/apps.json index 0c1517035..8e7c4202b 100644 --- a/apps.json +++ b/apps.json @@ -1,35 +1,44 @@ [ - { "id": "fwupdate", + { + "id": "fwupdate", "name": "Firmware Update", - "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "Uploads new Espruino firmwares to Bangle.js 2", - "custom": "custom.html", "customConnect":true, - "tags": "tools,system,b2", + "icon": "app.png", "type": "RAM", - "storage": [ ], - "sortorder" : -20 + "tags": "tools,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "custom": "custom.html", + "customConnect": true, + "storage": [ + + ], + "sortorder": -20 }, - { "id": "boot", + { + "id": "boot", "name": "Bootloader", - "tags": "tool,system,b2", - "type":"bootloader", - "icon": "bootloader.png", - "version":"0.31", + "version": "0.31", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", + "icon": "bootloader.png", + "type": "bootloader", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":".boot0","url":"boot0.js"}, {"name":".bootcde","url":"bootloader.js"}, {"name":"bootupdate.js","url":"bootupdate.js"} ], - "sortorder" : -10 + "sortorder": -10 }, - { "id": "health", + { + "id": "health", "name": "Health Tracking", - "tags": "tool,system,b2", - "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)", + "icon": "app.png", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"health.app.js","url":"app.js"}, @@ -37,200 +46,220 @@ {"name":"health.boot.js","url":"boot.js"}, {"name":"health","url":"lib.js"} ], - "sortorder" : -10 + "sortorder": -10 }, - { "id": "moonphase", + { + "id": "moonphase", "name": "Moonphase", - "icon": "app.png", - "version":"0.02", + "version": "0.02", "description": "Shows current moon phase. Now with GPS function.", + "icon": "app.png", "tags": "", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"moonphase.app.js","url":"app.js"}, {"name":"moonphase.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "daysl", + { + "id": "daysl", "name": "Days left", - "icon": "app.png", - "version":"0.03", + "version": "0.03", "description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.", + "icon": "app.png", "tags": "", - "allow_emulator":false, + "supports": ["BANGLEJS"], + "allow_emulator": false, "storage": [ - {"name":"daysl.app.js","url":"app.js"}, - {"name":"daysl.img","url":"app-icon.js","evaluate":true}, - {"name":"daysl.wid.js","url":"widget.js"} + {"name":"daysl.app.js","url":"app.js"}, + {"name":"daysl.img","url":"app-icon.js","evaluate":true}, + {"name":"daysl.wid.js","url":"widget.js"} ] }, - { "id": "launch", + { + "id": "launch", "name": "Launcher (Default)", - "shortName":"Launcher", - "icon": "app.png", - "version":"0.07", + "shortName": "Launcher", + "version": "0.07", "description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", - "tags": "tool,system,launcher,b2", - "type":"launch", + "icon": "app.png", + "type": "launch", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"launch.app.js","url":"app.js"} ], - "sortorder" : -10 + "sortorder": -10 }, - { "id": "launchb2", + { + "id": "launchb2", "name": "Launcher (Bangle.js 2)", - "shortName":"Launcher", - "icon": "app.png", - "version":"0.03", + "shortName": "Launcher", + "version": "0.03", "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications. It will not work on Bangle.js 1.0.", - "tags": "tool,system,launcher,b2,bno1", - "type":"launch", + "icon": "app.png", + "type": "launch", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS2"], "storage": [ {"name":"launchb2.app.js","url":"app.js"} ], - "sortorder" : -10 + "sortorder": -10 }, - { "id": "about", + { + "id": "about", "name": "About", - "icon": "app.png", - "version":"0.09", + "version": "0.09", "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", - "tags": "tool,system,b2", - "allow_emulator":true, + "icon": "app.png", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"about.app.js","url":"app.js"}, {"name":"about.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "locale", + { + "id": "locale", "name": "Languages", - "icon": "locale.png", - "version":"0.09", + "version": "0.09", "description": "Translations for different countries", - "tags": "tool,system,locale,translate,b2", + "icon": "locale.png", "type": "locale", - "custom":"locale.html", + "tags": "tool,system,locale,translate", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", + "custom": "locale.html", "storage": [ {"name":"locale"} ], - "sortorder" : -10 + "sortorder": -10 }, - { "id": "notify", + { + "id": "notify", "name": "Notifications (default)", - "shortName":"Notifications", - "icon": "notify.png", - "version":"0.11", + "shortName": "Notifications", + "version": "0.11", "description": "Provides the default `notify` module used by applications to display notifications in a bar at the top of the screen. This module is installed by default by client applications such as the Gadgetbridge app. Installing `Fullscreen Notifications` replaces this module with a version that displays the notifications using the full screen", - "tags": "widget", + "icon": "notify.png", "type": "notify", + "tags": "widget", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"notify","url":"notify.js"} ] }, - { "id": "notifyfs", + { + "id": "notifyfs", "name": "Fullscreen Notifications", - "shortName":"Notifications", - "icon": "notify.png", - "version":"0.12", + "shortName": "Notifications", + "version": "0.12", "description": "Provides a replacement for the `Notifications (default)` `notify` module. This version is used by applications to display notifications fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notify module.", - "tags": "widget,b2", + "icon": "notify.png", "type": "notify", + "tags": "widget", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"notify","url":"notify.js"} ] }, - { "id": "welcome", + { + "id": "welcome", "name": "Welcome", - "icon": "app.png", - "version":"0.12", + "version": "0.12", "description": "Appears at first boot and explains how to use Bangle.js", + "icon": "app.png", "tags": "start,welcome", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"welcome.boot.js","url":"boot.js"}, {"name":"welcome.app.js","url":"app.js"}, {"name":"welcome.settings.js","url":"settings.js"}, {"name":"welcome.img","url":"app-icon.js","evaluate":true} ], - "data": [ - {"name":"welcome.json"} - ] + "data": [{"name":"welcome.json"}] }, - { "id": "mywelcome", + { + "id": "mywelcome", "name": "Customised Welcome", "shortName": "My Welcome", - "icon": "app.png", - "version":"0.12", + "version": "0.12", "description": "Appears at first boot and explains how to use Bangle.js. Like 'Welcome', but can be customised with a greeting", + "icon": "app.png", "tags": "start,welcome", - "custom":"custom.html", + "supports": ["BANGLEJS"], + "custom": "custom.html", "storage": [ {"name":"mywelcome.boot.js","url":"boot.js"}, {"name":"mywelcome.app.js","url":"app.js"}, {"name":"mywelcome.settings.js","url":"settings.js"}, {"name":"mywelcome.img","url":"app-icon.js","evaluate":true} ], - "data": [ - {"name":"mywelcome.json"} - ] + "data": [{"name":"mywelcome.json"}] }, - { "id": "gbridge", + { + "id": "gbridge", "name": "Gadgetbridge", - "icon": "app.png", - "version":"0.24", + "version": "0.24", "description": "The default notification handler for Gadgetbridge notifications from Android", - "tags": "tool,system,android,widget,b2", + "icon": "app.png", + "type": "widget", + "tags": "tool,system,android,widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "dependencies": {"notify":"type"}, "readme": "README.md", - "type":"widget", - "dependencies": { "notify":"type" }, "storage": [ {"name":"gbridge.settings.js","url":"settings.js"}, {"name":"gbridge.img","url":"app-icon.js","evaluate":true}, {"name":"gbridge.wid.js","url":"widget.js"} ], - "data": [ - {"name":"gbridge.json"} - ] + "data": [{"name":"gbridge.json"}] }, - { "id": "mclock", + { + "id": "mclock", "name": "Morphing Clock", - "icon": "clock-morphing.png", - "version":"0.07", + "version": "0.07", "description": "7 segment clock that morphs between minutes and hours", + "icon": "clock-morphing.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"mclock.app.js","url":"clock-morphing.js"}, {"name":"mclock.img","url":"clock-morphing-icon.js","evaluate":true} ], - "sortorder" : -9 + "sortorder": -9 }, - { "id": "setting", + { + "id": "setting", "name": "Settings", - "icon": "settings.png", - "version":"0.30", + "version": "0.30", "description": "A menu for setting up Bangle.js", - "tags": "tool,system,b2", + "icon": "settings.png", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"setting.app.js","url":"settings.js"}, {"name":"setting.img","url":"settings-icon.js","evaluate":true} ], - "data": [ - {"name":"setting.json", "url":"settings.min.json","evaluate":true} - ], - "sortorder" : -2 + "data": [{"name":"setting.json","url":"settings.min.json","evaluate":true}], + "sortorder": -2 }, - { "id": "alarm", + { + "id": "alarm", "name": "Default Alarm & Timer", - "shortName":"Alarms", - "icon": "app.png", - "version":"0.13", + "shortName": "Alarms", + "version": "0.13", "description": "Set and respond to alarms and timers", - "tags": "tool,alarm,widget,b2", + "icon": "app.png", + "tags": "tool,alarm,widget", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"alarm.app.js","url":"app.js"}, {"name":"alarm.boot.js","url":"boot.js"}, @@ -238,33 +267,35 @@ {"name":"alarm.img","url":"app-icon.js","evaluate":true}, {"name":"alarm.wid.js","url":"widget.js"} ], - "data": [ - {"name":"alarm.json"} - ] + "data": [{"name":"alarm.json"}] }, - { "id": "wclock", + { + "id": "wclock", "name": "Word Clock", - "icon": "clock-word.png", - "version":"0.03", + "version": "0.03", "description": "Display Time as Text", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "clock-word.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"wclock.app.js","url":"clock-word.js"}, {"name":"wclock.img","url":"clock-word-icon.js","evaluate":true} ] }, - { "id": "fontclock", + { + "id": "fontclock", "name": "Font Clock", - "icon": "fontclock.png", - "version":"0.01", + "version": "0.01", "description": "Choose the font and design of clock face from a library of available designs", + "icon": "fontclock.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":false, - "readme": "README.md", - "custom":"custom.html", + "supports": ["BANGLEJS"], + "readme": "README.md", + "custom": "custom.html", + "allow_emulator": false, "storage": [ {"name":"fontclock.app.js","url":"fontclock.js"}, {"name":"fontclock.img","url":"fontclock-icon.js","evaluate":true}, @@ -281,16 +312,18 @@ {"name":"fontclock.font.vector50.js","url":"fontclock.font.vector50.js"} ] }, - { "id": "slidingtext", + { + "id": "slidingtext", "name": "Sliding Clock", - "icon": "slidingtext.png", - "version":"0.07", + "version": "0.07", "description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently English, French, Japanese, Spanish and German are supported", + "icon": "slidingtext.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":false, - "readme": "README.md", - "custom":"custom.html", + "supports": ["BANGLEJS"], + "readme": "README.md", + "custom": "custom.html", + "allow_emulator": false, "storage": [ {"name":"slidingtext.app.js","url":"slidingtext.js"}, {"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true}, @@ -304,16 +337,18 @@ {"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"} ] }, - { "id": "solarclock", + { + "id": "solarclock", "name": "Solar Clock", - "icon": "solar_clock.png", - "version":"0.02", + "version": "0.02", "description": "Using your current or chosen location the solar watch face shows the Sun's sky position, time and date. Also allows you to wind backwards and forwards in time to see the sun's position", + "icon": "solar_clock.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":false, - "readme": "README.md", - "custom":"custom.html", + "supports": ["BANGLEJS"], + "readme": "README.md", + "custom": "custom.html", + "allow_emulator": false, "storage": [ {"name":"solarclock.app.js","url":"solar_clock.js"}, {"name":"solarclock.img","url":"solar_clock-icon.js","evaluate":true}, @@ -331,42 +366,48 @@ {"name":"solar_loc.Seoul.json","url":"solar_loc.Seoul.json"} ] }, - { "id": "sweepclock", + { + "id": "sweepclock", "name": "Sweep Clock", - "icon": "sweepclock.png", - "version":"0.04", + "version": "0.04", "description": "Smooth sweep secondhand with single hour numeral. Use button 1 to toggle the numeral font, button 3 to change the colour theme and button 4 to change the date placement", + "icon": "sweepclock.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, - "readme": "README.md", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"sweepclock.app.js","url":"sweepclock.js"}, {"name":"sweepclock.img","url":"sweepclock-icon.js","evaluate":true} ] }, - { "id": "matrixclock", + { + "id": "matrixclock", "name": "Matrix Clock", - "icon": "matrixclock.png", - "version":"0.02", + "version": "0.02", "description": "inspired by The Matrix, a clock of the same style", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, - "readme": "README.md", + "icon": "matrixclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"matrixclock.app.js","url":"matrixclock.js"}, {"name":"matrixclock.img","url":"matrixclock-icon.js","evaluate":true} ] }, - { "id": "imgclock", + { + "id": "imgclock", "name": "Image background clock", - "shortName":"Image Clock", - "icon": "app.png", - "version":"0.08", + "shortName": "Image Clock", + "version": "0.08", "description": "A clock with an image as a background", + "icon": "app.png", + "type": "clock", "tags": "clock", - "type" : "clock", + "supports": ["BANGLEJS"], "custom": "custom.html", "storage": [ {"name":"imgclock.app.js","url":"app.js"}, @@ -376,245 +417,261 @@ {"name":"imgclock.face.bg","content":""} ] }, - { "id": "impwclock", + { + "id": "impwclock", "name": "Imprecise Word Clock", - "icon": "clock-impword.png", - "version":"0.03", + "version": "0.03", "description": "Imprecise word clock for vacations, weekends, and those who never need accurate time.", + "icon": "clock-impword.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"impwclock.app.js","url":"clock-impword.js"}, {"name":"impwclock.img","url":"clock-impword-icon.js","evaluate":true} ] }, - { "id": "aclock", + { + "id": "aclock", "name": "Analog Clock", - "icon": "clock-analog.png", "version": "0.15", "description": "An Analog Clock", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "clock-analog.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"aclock.app.js","url":"clock-analog.js"}, {"name":"aclock.img","url":"clock-analog-icon.js","evaluate":true} ] }, - { "id": "clock2x3", + { + "id": "clock2x3", "name": "2x3 Pixel Clock", - "icon": "clock2x3.png", - "version":"0.05", + "version": "0.05", "description": "This is a simple clock using minimalist 2x3 pixel numerical digits", - "tags": "clock,b2", + "icon": "clock2x3.png", "type": "clock", - "allow_emulator":true, + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"clock2x3.app.js","url":"clock2x3-app.js"}, {"name":"clock2x3.img","url":"clock2x3-icon.js","evaluate":true} ] }, - { "id": "geissclk", + { + "id": "geissclk", "name": "Geiss Clock", - "icon": "clock.png", - "version":"0.03", + "version": "0.03", "description": "7 segment clock with animated background in the style of Ryan Geiss' music visualisation. NOTE: The first run will take ~1 minute to do some precalculation", - "tags": "clock,bno2", - "type":"clock", + "icon": "clock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "storage": [ {"name":"geissclk.app.js","url":"clock.js"}, {"name":"geissclk.precompute.js","url":"precompute.js"}, {"name":"geissclk.img","url":"clock-icon.js","evaluate":true} ], - "data": [ - {"name":"geissclk.0.map"}, - {"name":"geissclk.1.map"}, - {"name":"geissclk.2.map"}, - {"name":"geissclk.3.map"}, - {"name":"geissclk.4.map"}, - {"name":"geissclk.5.map"}, - {"name":"geissclk.0.pal"}, - {"name":"geissclk.1.pal"}, - {"name":"geissclk.2.pal"} - ] + "data": [{"name":"geissclk.0.map"},{"name":"geissclk.1.map"},{"name":"geissclk.2.map"},{"name":"geissclk.3.map"},{"name":"geissclk.4.map"},{"name":"geissclk.5.map"},{"name":"geissclk.0.pal"},{"name":"geissclk.1.pal"},{"name":"geissclk.2.pal"}] }, - { "id": "trex", + { + "id": "trex", "name": "T-Rex", - "icon": "trex.png", - "version":"0.04", + "version": "0.04", "description": "T-Rex game in the style of Chrome's offline game", - "tags": "game,b2", - "allow_emulator":true, + "icon": "trex.png", + "tags": "game", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"trex.app.js","url":"trex.js"}, {"name":"trex.img","url":"trex-icon.js","evaluate":true}, {"name":"trex.settings.js","url":"settings.js"} ], - "data": [ - {"name":"trex.score", "storageFile": true} - ] + "data": [{"name":"trex.score","storageFile":true}] }, - { "id": "astroid", + { + "id": "astroid", "name": "Asteroids!", - "icon": "asteroids.png", - "version":"0.03", + "version": "0.03", "description": "Retro asteroids game", - "tags": "game,b2", - "allow_emulator":true, + "icon": "asteroids.png", + "tags": "game", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"astroid.app.js","url":"asteroids.js"}, {"name":"astroid.img","url":"asteroids-icon.js","evaluate":true} ] }, - { "id": "clickms", + { + "id": "clickms", "name": "Click Master", - "icon": "click-master.png", - "version":"0.01", + "version": "0.01", "description": "Get several friends to start the game, then compete to see who can press BTN1 the most!", + "icon": "click-master.png", "tags": "game", + "supports": ["BANGLEJS"], "storage": [ {"name":"clickms.app.js","url":"click-master.js"}, {"name":"clickms.img","url":"click-master-icon.js","evaluate":true} ] }, - { "id": "horsey", + { + "id": "horsey", "name": "Horse Race!", - "icon": "horse-race.png", - "version":"0.01", + "version": "0.01", "description": "Get several friends to start the game, then compete to see who can press BTN1 the most!", + "icon": "horse-race.png", "tags": "game", + "supports": ["BANGLEJS"], "storage": [ {"name":"horsey.app.js","url":"horse-race.js"}, {"name":"horsey.img","url":"horse-race-icon.js","evaluate":true} ] }, - { "id": "compass", + { + "id": "compass", "name": "Compass", - "icon": "compass.png", - "version":"0.03", + "version": "0.03", "description": "Simple compass that points North", + "icon": "compass.png", "tags": "tool,outdoors", + "supports": ["BANGLEJS"], "storage": [ {"name":"compass.app.js","url":"compass.js"}, {"name":"compass.img","url":"compass-icon.js","evaluate":true} ] }, - { "id": "gpstime", + { + "id": "gpstime", "name": "GPS Time", - "icon": "gpstime.png", - "version":"0.04", + "version": "0.04", "description": "Update the Bangle.js's clock based on the time from the GPS receiver", + "icon": "gpstime.png", "tags": "tool,gps", + "supports": ["BANGLEJS"], "storage": [ {"name":"gpstime.app.js","url":"gpstime.js"}, {"name":"gpstime.img","url":"gpstime-icon.js","evaluate":true} ] }, - { "id": "openloc", + { + "id": "openloc", "name": "Open Location / Plus Codes", "shortName": "Open Location", - "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "Convert your current GPS location to a series of characters", + "icon": "app.png", "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], "storage": [ {"name":"openloc.app.js","url":"app.js"}, {"name":"openloc.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "speedo", + { + "id": "speedo", "name": "Speedo", - "icon": "speedo.png", - "version":"0.05", + "version": "0.05", "description": "Show the current speed according to the GPS", - "tags": "tool,outdoors,gps,b2", + "icon": "speedo.png", + "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"speedo.app.js","url":"speedo.js"}, {"name":"speedo.img","url":"speedo-icon.js","evaluate":true} ] }, - { "id": "gpsrec", + { + "id": "gpsrec", "name": "GPS Recorder", - "icon": "app.png", - "version":"0.24", - "interface": "interface.html", + "version": "0.24", "description": "Application that allows you to record a GPS track. Can run in background", + "icon": "app.png", "tags": "tool,outdoors,gps,widget", + "supports": ["BANGLEJS"], "readme": "README.md", + "interface": "interface.html", "storage": [ {"name":"gpsrec.app.js","url":"app.js"}, {"name":"gpsrec.img","url":"app-icon.js","evaluate":true}, {"name":"gpsrec.wid.js","url":"widget.js"}, {"name":"gpsrec.settings.js","url":"settings.js"} ], - "data": [ - {"name":"gpsrec.json"}, - {"wildcard":".gpsrc?","storageFile": true} - ] + "data": [{"name":"gpsrec.json"},{"wildcard":".gpsrc?","storageFile":true}] }, - { "id": "gpsnav", + { + "id": "gpsnav", "name": "GPS Navigation", - "icon": "icon.png", - "version":"0.05", + "version": "0.05", "description": "Displays GPS Course and Speed, + Directions to waypoint and waypoint recording, now with waypoint editor", + "icon": "icon.png", "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], "readme": "README.md", - "interface":"waypoints.html", + "interface": "waypoints.html", "storage": [ {"name":"gpsnav.app.js","url":"app.min.js"}, {"name":"gpsnav.img","url":"app-icon.js","evaluate":true} ], - "data": [ - {"name":"waypoints.json","url":"waypoints.json"} - ] + "data": [{"name":"waypoints.json","url":"waypoints.json"}] }, - { "id": "heart", + { + "id": "heart", "name": "Heart Rate Recorder", - "icon": "app.png", - "version":"0.06", - "interface": "interface.html", + "version": "0.06", "description": "Application that allows you to record your heart rate. Can run in background", + "icon": "app.png", "tags": "tool,health,widget", + "supports": ["BANGLEJS"], + "interface": "interface.html", "storage": [ {"name":"heart.app.js","url":"app.js"}, {"name":"heart.img","url":"app-icon.js","evaluate":true}, {"name":"heart.wid.js","url":"widget.js"} ], - "data": [ - {"name":"heart.json"}, - {"wildcard":".heart?","storageFile": true} - ] + "data": [{"name":"heart.json"},{"wildcard":".heart?","storageFile":true}] }, - { "id": "slevel", + { + "id": "slevel", "name": "Spirit Level", - "icon": "spiritlevel.png", - "version":"0.01", + "version": "0.01", "description": "Show the current angle of the watch, so you can use it to make sure something is absolutely flat", + "icon": "spiritlevel.png", "tags": "tool", + "supports": ["BANGLEJS"], "storage": [ {"name":"slevel.app.js","url":"spiritlevel.js"}, {"name":"slevel.img","url":"spiritlevel-icon.js","evaluate":true} ] }, - { "id": "files", + { + "id": "files", "name": "App Manager", - "icon": "files.png", - "version":"0.07", + "version": "0.07", "description": "Show currently installed apps, free space, and allow their deletion from the watch", + "icon": "files.png", "tags": "tool,system,files", + "supports": ["BANGLEJS"], "storage": [ {"name":"files.app.js","url":"files.js"}, {"name":"files.img","url":"files-icon.js","evaluate":true} ] }, - { "id": "weather", + { + "id": "weather", "name": "Weather", - "icon": "icon.png", - "version":"0.10", + "version": "0.10", "description": "Show Gadgetbridge weather report", - "readme": "readme.md", + "icon": "icon.png", "tags": "widget,outdoors", + "supports": ["BANGLEJS"], + "readme": "readme.md", "storage": [ {"name":"weather.app.js","url":"app.js"}, {"name":"weather.wid.js","url":"widget.js"}, @@ -622,30 +679,32 @@ {"name":"weather.img","url":"icon.js","evaluate":true}, {"name":"weather.settings.js","url":"settings.js"} ], - "data": [ - {"name": "weather.json"} - ] + "data": [{"name":"weather.json"}] }, - { "id": "chargeanim", + { + "id": "chargeanim", "name": "Charge Animation", - "icon": "icon.png", - "version":"0.01", + "version": "0.01", "description": "When charging, show a sideways charging animation and keep the screen on. When removed from the charger load the clock again.", + "icon": "icon.png", "tags": "battery", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"chargeanim.app.js","url":"app.js"}, {"name":"chargeanim.boot.js","url":"boot.js"}, {"name":"chargeanim.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "bluetoothdock", + { + "id": "bluetoothdock", "name": "Bluetooth Dock", - "shortName":"Dock", - "icon": "app.png", - "version":"0.01", + "shortName": "Dock", + "version": "0.01", "description": "When charging shows the time, scans Bluetooth for known devices (eg temperature) and shows them on the screen", + "icon": "app.png", "tags": "bluetooth", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"bluetoothdock.app.js","url":"app.js"}, @@ -653,190 +712,214 @@ {"name":"bluetoothdock.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "widbat", + { + "id": "widbat", "name": "Battery Level Widget", - "icon": "widget.png", - "version":"0.08", + "version": "0.08", "description": "Show the current battery level and charging status in the top right of the clock", - "tags": "widget,battery,b2", - "type":"widget", + "icon": "widget.png", + "type": "widget", + "tags": "widget,battery", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widbat.wid.js","url":"widget.js"} ] }, - { "id": "widlock", + { + "id": "widlock", "name": "Lock Widget", - "icon": "widget.png", - "version":"0.03", + "version": "0.03", "description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked", - "tags": "widget,lock,b2", - "type":"widget", + "icon": "widget.png", + "type": "widget", + "tags": "widget,lock", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widlock.wid.js","url":"widget.js"} ] }, - { "id": "widbatpc", + { + "id": "widbatpc", "name": "Battery Level Widget (with percentage)", "shortName": "Battery Widget", - "icon": "widget.png", - "version":"0.13", + "version": "0.13", "description": "Show the current battery level and charging status in the top right of the clock, with charge percentage", - "tags": "widget,battery,b2", - "type":"widget", + "icon": "widget.png", + "type": "widget", + "tags": "widget,battery", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"widbatpc.wid.js","url":"widget.js"}, {"name":"widbatpc.settings.js","url":"settings.js"} ], - "data": [ - {"name":"widbatpc.json"} - ] + "data": [{"name":"widbatpc.json"}] }, - { "id": "widbatwarn", + { + "id": "widbatwarn", "name": "Battery Warning", "shortName": "Battery Warning", - "icon": "widget.png", - "readme": "README.md", - "version":"0.02", + "version": "0.02", "description": "Show a warning when the battery runs low.", + "icon": "widget.png", + "type": "widget", "tags": "tool,battery", - "type":"widget", - "dependencies": { "notify":"type" }, + "supports": ["BANGLEJS"], + "dependencies": {"notify":"type"}, + "readme": "README.md", "storage": [ {"name":"widbatwarn.wid.js","url":"widget.js"}, {"name":"widbatwarn.settings.js","url":"settings.js"} ], - "data": [ - {"name":"widbatwarn.json"} - ] + "data": [{"name":"widbatwarn.json"}] }, - { "id": "widbt", + { + "id": "widbt", "name": "Bluetooth Widget", - "icon": "widget.png", - "version":"0.06", + "version": "0.06", "description": "Show the current Bluetooth connection status in the top right of the clock", - "tags": "widget,bluetooth,b2", - "type":"widget", + "icon": "widget.png", + "type": "widget", + "tags": "widget,bluetooth", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widbt.wid.js","url":"widget.js"} ] }, - { "id": "widchime", + { + "id": "widchime", "name": "Hour Chime", - "icon": "widget.png", - "version":"0.02", + "version": "0.02", "description": "Buzz or beep on every whole hour.", - "tags": "widget", + "icon": "widget.png", "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widchime.wid.js","url":"widget.js"}, {"name":"widchime.settings.js","url":"settings.js"} ], - "data": [ - {"name":"widchime.json"} - ] + "data": [{"name":"widchime.json"}] }, - { "id": "widram", + { + "id": "widram", "name": "RAM Widget", - "shortName":"RAM Widget", - "icon": "widget.png", - "version":"0.01", + "shortName": "RAM Widget", + "version": "0.01", "description": "Display your Bangle's available RAM percentage in a widget", - "tags": "widget", + "icon": "widget.png", "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widram.wid.js","url":"widget.js"} ] }, - { "id": "hrm", + { + "id": "hrm", "name": "Heart Rate Monitor", - "icon": "heartrate.png", - "version":"0.05", + "version": "0.05", "description": "Measure your heart rate and see live sensor data", + "icon": "heartrate.png", "tags": "health", + "supports": ["BANGLEJS"], "storage": [ {"name":"hrm.app.js","url":"heartrate.js"}, {"name":"hrm.img","url":"heartrate-icon.js","evaluate":true} ] }, - { "id": "widhrm", + { + "id": "widhrm", "name": "Simple Heart Rate widget", - "icon": "widget.png", - "version":"0.04", + "version": "0.04", "description": "When the screen is on, the widget turns on the heart rate monitor and displays the current heart rate (or last known in grey). For this to work well you'll need at least a 15 second LCD Timeout.", - "tags": "health,widget", + "icon": "widget.png", "type": "widget", + "tags": "health,widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widhrm.wid.js","url":"widget.js"} ] }, - { "id": "stetho", + { + "id": "stetho", "name": "Stethoscope", - "icon": "stetho.png", - "version":"0.01", + "version": "0.01", "description": "Hear your heart rate", + "icon": "stetho.png", "tags": "health", + "supports": ["BANGLEJS"], "storage": [ {"name":"stetho.app.js","url":"stetho.js"}, {"name":"stetho.img","url":"stetho-icon.js","evaluate":true} ] }, - { "id": "swatch", + { + "id": "swatch", "name": "Stopwatch", - "icon": "stopwatch.png", - "version":"0.07", - "interface": "interface.html", + "version": "0.07", "description": "Simple stopwatch with Lap Time logging to a JSON file", + "icon": "stopwatch.png", "tags": "health", - "allow_emulator":true, + "supports": ["BANGLEJS"], "readme": "README.md", + "interface": "interface.html", + "allow_emulator": true, "storage": [ {"name":"swatch.app.js","url":"stopwatch.js"}, {"name":"swatch.img","url":"stopwatch-icon.js","evaluate":true} ] }, - { "id": "hidmsic", + { + "id": "hidmsic", "name": "Bluetooth Music Controls", "shortName": "Music Control", - "icon": "hid-music.png", - "version":"0.02", + "version": "0.02", "description": "Enable HID in settings, pair with your phone, then use this app to control music from your watch!", + "icon": "hid-music.png", "tags": "bluetooth", + "supports": ["BANGLEJS"], "storage": [ {"name":"hidmsic.app.js","url":"hid-music.js"}, {"name":"hidmsic.img","url":"hid-music-icon.js","evaluate":true} ] }, - { "id": "hidkbd", + { + "id": "hidkbd", "name": "Bluetooth Keyboard", "shortName": "Bluetooth Kbd", - "icon": "hid-keyboard.png", - "version":"0.02", + "version": "0.02", "description": "Enable HID in settings, pair with your phone/PC, then use this app to control other apps", + "icon": "hid-keyboard.png", "tags": "bluetooth", + "supports": ["BANGLEJS"], "storage": [ {"name":"hidkbd.app.js","url":"hid-keyboard.js"}, {"name":"hidkbd.img","url":"hid-keyboard-icon.js","evaluate":true} ] }, - { "id": "hidbkbd", + { + "id": "hidbkbd", "name": "Binary Bluetooth Keyboard", "shortName": "Binary BT Kbd", - "icon": "hid-binary-keyboard.png", - "version":"0.02", + "version": "0.02", "description": "Enable HID in settings, pair with your phone/PC, then type messages using the onscreen keyboard by tapping repeatedly on the key you want", + "icon": "hid-binary-keyboard.png", "tags": "bluetooth", + "supports": ["BANGLEJS"], "storage": [ {"name":"hidbkbd.app.js","url":"hid-binary-keyboard.js"}, {"name":"hidbkbd.img","url":"hid-binary-keyboard-icon.js","evaluate":true} ] }, - { "id": "animals", + { + "id": "animals", "name": "Animals Game", - "icon": "animals.png", - "version":"0.01", + "version": "0.01", "description": "Simple toddler's game - displays a different number of animals each time the screen is pressed", + "icon": "animals.png", "tags": "game", + "supports": ["BANGLEJS"], "storage": [ {"name":"animals.app.js","url":"animals.js"}, {"name":"animals.img","url":"animals-icon.js","evaluate":true}, @@ -850,36 +933,43 @@ {"name":"animals-mouse.img","url":"animals-mouse.js","evaluate":true} ] }, - { "id": "qrcode", + { + "id": "qrcode", "name": "Custom QR Code", - "icon": "app.png", - "version":"0.02", + "version": "0.02", "description": "Use this to upload a customised QR code to Bangle.js", - "tags": "qrcode,b2", - "custom": "custom.html", "customConnect":true, + "icon": "app.png", + "tags": "qrcode", + "supports": ["BANGLEJS","BANGLEJS2"], + "custom": "custom.html", + "customConnect": true, "storage": [ {"name":"qrcode.app.js"}, {"name":"qrcode.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "beer", + { + "id": "beer", "name": "Beer Compass", - "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "Uploads all the pubs in an area onto your watch, so it can always point you at the nearest one", + "icon": "app.png", "tags": "", + "supports": ["BANGLEJS"], "custom": "custom.html", "storage": [ {"name":"beer.app.js"}, {"name":"beer.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "route", + { + "id": "route", "name": "Route Viewer", - "icon": "app.png", - "version":"0.02", + "version": "0.02", "description": "Upload a KML file of a route, and have your watch display a map with how far around it you are", + "icon": "app.png", "tags": "", + "supports": ["BANGLEJS"], "custom": "custom.html", "storage": [ {"name":"route.app.js"}, @@ -889,10 +979,11 @@ { "id": "ncstart", "name": "NCEU Startup", - "icon": "start.png", - "version":"0.06", + "version": "0.06", "description": "NodeConfEU 2019 'First Start' Sequence", + "icon": "start.png", "tags": "start,welcome", + "supports": ["BANGLEJS"], "storage": [ {"name":"ncstart.app.js","url":"start.js"}, {"name":"ncstart.boot.js","url":"boot.js"}, @@ -904,104 +995,118 @@ {"name":"nc-nodew.img","url":"start-nodew.js","evaluate":true}, {"name":"nc-tf.img","url":"start-tf.js","evaluate":true} ], - "data": [ - {"name":"ncstart.json"} - ] + "data": [{"name":"ncstart.json"}] }, - { "id": "ncfrun", + { + "id": "ncfrun", "name": "NCEU 5K Fun Run", - "icon": "nceu-funrun.png", - "version":"0.01", + "version": "0.01", "description": "Display a map of the NodeConf EU 2019 5K Fun Run route and your location on it", + "icon": "nceu-funrun.png", "tags": "health", + "supports": ["BANGLEJS"], "storage": [ {"name":"ncfrun.app.js","url":"nceu-funrun.js"}, {"name":"ncfrun.img","url":"nceu-funrun-icon.js","evaluate":true} ] }, - { "id": "widnceu", + { + "id": "widnceu", "name": "NCEU Logo Widget", - "icon": "widget.png", - "version":"0.02", + "version": "0.02", "description": "Show the NodeConf EU logo in the top left", + "icon": "widget.png", + "type": "widget", "tags": "widget", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widnceu.wid.js","url":"widget.js"} ] }, - { "id": "sclock", + { + "id": "sclock", "name": "Simple Clock", - "icon": "clock-simple.png", - "version":"0.06", + "version": "0.06", "description": "A Simple Digital Clock", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "clock-simple.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"sclock.app.js","url":"clock-simple.js"}, {"name":"sclock.img","url":"clock-simple-icon.js","evaluate":true} ] }, - { "id": "s7clk", + { + "id": "s7clk", "name": "Simple 7 segment Clock", - "icon": "icon.png", - "version":"0.03", + "version": "0.03", "description": "A simple 7 segment Clock with date", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "icon.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"s7clk.app.js","url":"app.js"}, {"name":"s7clk.img","url":"icon.js","evaluate":true} ] }, - { "id": "vibrclock", + { + "id": "vibrclock", "name": "Vibrate Clock", - "icon": "app.png", - "version":"0.03", + "version": "0.03", "description": "When BTN1 is pressed, vibrate out the time as a series of buzzes, one digit at a time. Hours, then Minutes. Zero is signified by one long buzz. Otherwise a simple digital clock.", + "icon": "app.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"vibrclock.app.js","url":"app.js"}, {"name":"vibrclock.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "svclock", + { + "id": "svclock", "name": "Simple V-Clock", - "icon": "vclock-simple.png", - "version":"0.03", + "version": "0.03", "description": "Modification of Simple Clock 0.04 to use Vectorfont", + "icon": "vclock-simple.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"svclock.app.js","url":"vclock-simple.js"}, {"name":"svclock.img","url":"vclock-simple-icon.js","evaluate":true} ] }, - { "id": "dclock", + { + "id": "dclock", "name": "Dev Clock", - "icon": "clock-dev.png", - "version":"0.10", + "version": "0.10", "description": "A Digital Clock including timestamp (tst), beats(@), days in current month (dm) and days since new moon (l)", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "clock-dev.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"dclock.app.js","url":"clock-dev.js"}, {"name":"dclock.img","url":"clock-dev-icon.js","evaluate":true} ] }, - { "id": "gesture", + { + "id": "gesture", "name": "Gesture Test", - "icon": "gesture.png", - "version":"0.01", + "version": "0.01", "description": "BETA! Uploads a basic Tensorflow Gesture model, and then outputs each gesture as a message", + "icon": "gesture.png", + "type": "app", "tags": "gesture,ai", - "type":"app", + "supports": ["BANGLEJS"], "storage": [ {"name":"gesture.app.js","url":"gesture.js"}, {"name":".tfnames","url":"gesture-tfnames.js","evaluate":true}, @@ -1009,39 +1114,45 @@ {"name":"gesture.img","url":"gesture-icon.js","evaluate":true} ] }, - { "id": "pparrot", + { + "id": "pparrot", "name": "Party Parrot", - "icon": "party-parrot.png", - "version":"0.01", + "version": "0.01", "description": "Party with a parrot on your wrist", + "icon": "party-parrot.png", + "type": "app", "tags": "party,parrot,lol", - "type":"app", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"pparrot.app.js","url":"party-parrot.js"}, {"name":"pparrot.img","url":"party-parrot-icon.js","evaluate":true} ] }, - { "id": "hrings", + { + "id": "hrings", "name": "Hypno Rings", - "icon": "hypno-rings.png", - "version":"0.01", + "version": "0.01", "description": "Experiment with trippy rings, press buttons for change", + "icon": "hypno-rings.png", + "type": "app", "tags": "rings,hypnosis,psychadelic", - "type":"app", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"hrings.app.js","url":"hypno-rings.js"}, {"name":"hrings.img","url":"hypno-rings-icon.js","evaluate":true} ] }, - { "id": "morse", + { + "id": "morse", "name": "Morse Code", - "icon": "morse-code.png", - "version":"0.01", + "version": "0.01", "description": "Learn morse code by hearing/seeing/feeling the code. Tap to toggle buzz!", + "icon": "morse-code.png", + "type": "app", "tags": "morse,sound,visual,input", - "type":"app", + "supports": ["BANGLEJS"], "storage": [ {"name":"morse.app.js","url":"morse-code.js"}, {"name":"morse.img","url":"morse-code-icon.js","evaluate":true} @@ -1050,97 +1161,112 @@ { "id": "blescan", "name": "BLE Scanner", - "icon": "blescan.png", - "version":"0.01", + "version": "0.01", "description": "Scan for advertising BLE devices", - "tags" : "bluetooth", - "storage" : [ + "icon": "blescan.png", + "tags": "bluetooth", + "supports": ["BANGLEJS"], + "storage": [ {"name":"blescan.app.js","url":"blescan.js"}, - {"name":"blescan.img","url":"blescan-icon.js", "evaluate":true} + {"name":"blescan.img","url":"blescan-icon.js","evaluate":true} ] }, - { "id": "mmonday", - "name": "Manic Monday Tone", - "icon": "manic-monday-icon.png", - "version":"0.02", - "description": "The Bangles make a comeback", - "tags": "sound", - "storage": [ - {"name":"mmonday.app.js","url":"manic-monday.js"}, - {"name":"mmonday.img","url":"manic-monday-icon.js","evaluate":true} - ] - }, - { "id": "jbells", - "name": "Jingle Bells", - "icon": "jbells.png", - "version":"0.01", - "description": "Play Jingle Bells", + { + "id": "mmonday", + "name": "Manic Monday Tone", + "version": "0.02", + "description": "The Bangles make a comeback", + "icon": "manic-monday-icon.png", "tags": "sound", - "type":"app", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"mmonday.app.js","url":"manic-monday.js"}, + {"name":"mmonday.img","url":"manic-monday-icon.js","evaluate":true} + ] + }, + { + "id": "jbells", + "name": "Jingle Bells", + "version": "0.01", + "description": "Play Jingle Bells", + "icon": "jbells.png", + "type": "app", + "tags": "sound", + "supports": ["BANGLEJS"], "storage": [ {"name":"jbells.app.js","url":"jbells.js"}, {"name":"jbells.img","url":"jbells-icon.js","evaluate":true} ] }, - { "id": "scolor", + { + "id": "scolor", "name": "Show Color", - "icon": "show-color.png", - "version":"0.01", + "version": "0.01", "description": "Display all available Colors and Names", + "icon": "show-color.png", + "type": "app", "tags": "tool", - "type":"app", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"scolor.app.js","url":"show-color.js"}, {"name":"scolor.img","url":"show-color-icon.js","evaluate":true} ] }, - { "id": "miclock", + { + "id": "miclock", "name": "Mixed Clock", - "icon": "clock-mixed.png", - "version":"0.05", + "version": "0.05", "description": "A mix of analog and digital Clock", + "icon": "clock-mixed.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"miclock.app.js","url":"clock-mixed.js"}, {"name":"miclock.img","url":"clock-mixed-icon.js","evaluate":true} ] }, - { "id": "bclock", + { + "id": "bclock", "name": "Binary Clock", - "icon": "clock-binary.png", - "version":"0.03", + "version": "0.03", "description": "A simple binary clock watch face", + "icon": "clock-binary.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"bclock.app.js","url":"clock-binary.js"}, {"name":"bclock.img","url":"clock-binary-icon.js","evaluate":true} ] }, - { "id": "clotris", + { + "id": "clotris", "name": "Clock-Tris", - "icon": "clock-tris.png", - "version":"0.01", + "version": "0.01", "description": "A fully functional clone of a classic game of falling blocks", + "icon": "clock-tris.png", "tags": "game", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"clotris.app.js","url":"clock-tris.js"}, {"name":"clotris.img","url":"clock-tris-icon.js","evaluate":true}, {"name":".trishig","url":"clock-tris-high"} ] }, - { "id": "flappy", + { + "id": "flappy", "name": "Flappy Bird", - "icon": "app.png", - "version":"0.05", + "version": "0.05", "description": "A Flappy Bird game clone", - "tags": "game,b2", - "allow_emulator":true, + "icon": "app.png", + "tags": "game", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"flappy.app.js","url":"app.js"}, {"name":"flappy.img","url":"app-icon.js","evaluate":true} @@ -1149,137 +1275,159 @@ { "id": "gpsinfo", "name": "GPS Info", - "icon": "gps-info.png", - "version":"0.05", + "version": "0.05", "description": "An application that displays information about altitude, lat/lon, satellites and time", - "tags": "gps,b2", + "icon": "gps-info.png", "type": "app", + "tags": "gps", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ - {"name":"gpsinfo.app.js","url": "gps-info.js"}, - {"name":"gpsinfo.img","url": "gps-info-icon.js","evaluate": true} + {"name":"gpsinfo.app.js","url":"gps-info.js"}, + {"name":"gpsinfo.img","url":"gps-info-icon.js","evaluate":true} ] }, - { "id": "assistedgps", + { + "id": "assistedgps", "name": "Assisted GPS Update (AGPS)", - "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.", - "custom": "custom.html", - "tags": "tool,outdoors,agps,bno2", + "icon": "app.png", "type": "RAM", - "storage": [ ] + "tags": "tool,outdoors,agps", + "supports": ["BANGLEJS"], + "custom": "custom.html", + "storage": [ + + ] }, { "id": "pomodo", - "name":"Pomodoro", - "icon":"pomodoro.png", - "version":"0.01", + "name": "Pomodoro", + "version": "0.01", "description": "A simple pomodoro timer.", - "tags": "pomodoro,cooking,tools", + "icon": "pomodoro.png", "type": "app", - "allow_emulator":true, + "tags": "pomodoro,cooking,tools", + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ - {"name":"pomodo.app.js","url": "pomodoro.js"}, - {"name":"pomodo.img","url": "pomodoro-icon.js","evaluate": true} + {"name":"pomodo.app.js","url":"pomodoro.js"}, + {"name":"pomodo.img","url":"pomodoro-icon.js","evaluate":true} ] }, - { "id": "blobclk", + { + "id": "blobclk", "name": "Large Digit Blob Clock", - "shortName" : "Blob Clock", - "icon": "clock-blob.png", - "version":"0.06", + "shortName": "Blob Clock", + "version": "0.06", "description": "A clock with big digits", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "clock-blob.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"blobclk.app.js","url":"clock-blob.js"}, {"name":"blobclk.img","url":"clock-blob-icon.js","evaluate":true} ] }, - { "id": "boldclk", + { + "id": "boldclk", "name": "Bold Clock", - "icon": "bold_clock.png", - "version":"0.05", + "version": "0.05", "description": "Simple, readable and practical clock", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "bold_clock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"boldclk.app.js","url":"bold_clock.js"}, {"name":"boldclk.img","url":"bold_clock-icon.js","evaluate":true} ] }, - { "id": "widclk", + { + "id": "widclk", "name": "Digital clock widget", - "icon": "widget.png", - "version":"0.06", + "version": "0.06", "description": "A simple digital clock widget", + "icon": "widget.png", + "type": "widget", "tags": "widget,clock", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widclk.wid.js","url":"widget.js"} ] }, - { "id": "widpedom", + { + "id": "widpedom", "name": "Pedometer widget", - "icon": "widget.png", - "version":"0.19", + "version": "0.19", "description": "Daily pedometer widget", - "tags": "widget,b2", - "type":"widget", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widpedom.wid.js","url":"widget.js"}, {"name":"widpedom.settings.js","url":"settings.js"} ] }, - { "id": "berlinc", + { + "id": "berlinc", "name": "Berlin Clock", - "icon": "berlin-clock.png", - "version":"0.04", + "version": "0.04", "description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)", + "icon": "berlin-clock.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"berlinc.app.js","url":"berlin-clock.js"}, {"name":"berlinc.img","url":"berlin-clock-icon.js","evaluate":true} ] }, - { "id": "ctrclk", + { + "id": "ctrclk", "name": "Centerclock", - "icon": "app.png", - "version":"0.03", + "version": "0.03", "description": "Watch-centered digital 24h clock with date in dd.mm.yyyy format.", - "tags": "clock,bno2", - "type":"clock", - "allow_emulator":true, + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"ctrclk.app.js","url":"app.js"}, {"name":"ctrclk.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "demoapp", + { + "id": "demoapp", "name": "Demo Loop", - "icon": "app.png", - "version":"0.02", + "version": "0.02", "description": "Simple demo app - displays Bangle.js, JS logo, graphics, and Bangle.js information", - "tags": "bno2", - "type":"app", - "allow_emulator":true, + "icon": "app.png", + "type": "app", + "tags": "", + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"demoapp.app.js","url":"app.js"}, {"name":"demoapp.img","url":"app-icon.js","evaluate":true} ], - "sortorder" : -9 + "sortorder": -9 }, - { "id": "flagrse", + { + "id": "flagrse", "name": "Espruino Flag Raiser", - "icon": "app.png", - "version":"0.01", - "readme": "README.md", + "version": "0.01", "description": "App to send a command to another Espruino to cause it to raise a flag", + "icon": "app.png", "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ {"name":"flagrse.app.js","url":"app.js"}, {"name":"flagrse.img","url":"app-icon.js","evaluate":true} @@ -1288,64 +1436,73 @@ { "id": "pipboy", "name": "Pipboy", - "icon": "app.png", "version": "0.04", "description": "Pipboy themed clock", - "tags": "clock,bno2", - "type":"clock", - "allow_emulator":true, + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"pipboy.app.js","url":"app.js"}, {"name":"pipboy.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "torch", + { + "id": "torch", "name": "Torch", - "shortName":"Torch", - "icon": "app.png", - "version":"0.02", + "shortName": "Torch", + "version": "0.02", "description": "Turns screen white to help you see in the dark. Select from the launcher or press BTN1,BTN3,BTN1,BTN3 quickly to start when in any app that shows widgets", + "icon": "app.png", "tags": "tool,torch", + "supports": ["BANGLEJS"], "storage": [ {"name":"torch.app.js","url":"app.js"}, {"name":"torch.wid.js","url":"widget.js"}, {"name":"torch.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "rtorch", + { + "id": "rtorch", "name": "Red Torch", - "shortName":"RedTorch", - "icon": "app.png", - "version":"0.01", + "shortName": "RedTorch", + "version": "0.01", "description": "Turns screen RED to help you see in the dark without breaking your night vision. Select from the launcher or press BTN3,BTN1,BTN3,BTN1 quickly to start when in any app that shows widgets", + "icon": "app.png", "tags": "tool,torch", + "supports": ["BANGLEJS"], "storage": [ {"name":"rtorch.app.js","url":"app.js"}, {"name":"rtorch.wid.js","url":"widget.js"}, {"name":"rtorch.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "wohrm", + { + "id": "wohrm", "name": "Workout HRM", - "icon": "app.png", - "version":"0.08", - "readme": "README.md", + "version": "0.08", "description": "Workout heart rate monitor notifies you with a buzz if your heart rate goes above or below the set limits.", - "tags": "hrm,workout", + "icon": "app.png", "type": "app", - "allow_emulator":true, + "tags": "hrm,workout", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"wohrm.app.js","url":"app.js"}, {"name":"wohrm.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "widid", + { + "id": "widid", "name": "Bluetooth ID Widget", - "icon": "widget.png", - "version":"0.03", + "version": "0.03", "description": "Display the last two tuple of your Bangle.js MAC address in the widget section. This is useful for figuring out which Bangle.js to connect to if you have more than one Bangle.js!", + "icon": "widget.png", + "type": "widget", "tags": "widget,address,mac", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widid.wid.js","url":"widget.js"} ] @@ -1353,113 +1510,130 @@ { "id": "grocery", "name": "Grocery", - "icon": "grocery.png", - "version":"0.02", + "version": "0.02", "description": "Simple grocery (shopping) list - Display a list of product and track if you already put them in your cart.", - "tags": "tool,outdoors,shopping,list", + "icon": "grocery.png", "type": "app", - "custom":"grocery.html", + "tags": "tool,outdoors,shopping,list", + "supports": ["BANGLEJS"], + "custom": "grocery.html", "storage": [ - {"name":"grocery.app.js", "url":"app.js"}, + {"name":"grocery.app.js","url":"app.js"}, {"name":"grocery.img","url":"grocery-icon.js","evaluate":true} ] }, - { "id": "marioclock", + { + "id": "marioclock", "name": "Mario Clock", - "icon": "marioclock.png", - "version":"0.15", + "version": "0.15", "description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.", - "tags": "clock,mario,retro", + "icon": "marioclock.png", "type": "clock", - "allow_emulator":false, + "tags": "clock,mario,retro", + "supports": ["BANGLEJS"], "readme": "README.md", + "allow_emulator": false, "storage": [ {"name":"marioclock.app.js","url":"marioclock-app.js"}, {"name":"marioclock.img","url":"marioclock-icon.js","evaluate":true} ] }, - { "id": "cliock", + { + "id": "cliock", "name": "Commandline-Clock", - "shortName":"CLI-Clock", - "icon": "app.png", - "version":"0.14", + "shortName": "CLI-Clock", + "version": "0.14", "description": "Simple CLI-Styled Clock", - "tags": "clock,cli,command,bash,shell,b2", - "type":"clock", - "allow_emulator":true, + "icon": "app.png", + "type": "clock", + "tags": "clock,cli,command,bash,shell", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"cliock.app.js","url":"app.js"}, {"name":"cliock.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "widver", + { + "id": "widver", "name": "Firmware Version Widget", - "icon": "widget.png", - "version":"0.01", + "version": "0.01", "description": "Display the version of the installed firmware in the top widget section.", + "icon": "widget.png", + "type": "widget", "tags": "widget,tool,system", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widver.wid.js","url":"widget.js"} ] }, - { "id": "barclock", + { + "id": "barclock", "name": "Bar Clock", - "icon": "clock-bar.png", - "version":"0.08", + "version": "0.08", "description": "A simple digital clock showing seconds as a bar", + "icon": "clock-bar.png", + "type": "clock", "tags": "clock", - "type":"clock", + "supports": ["BANGLEJS"], "readme": "README.md", - "allow_emulator":true, + "allow_emulator": true, "storage": [ {"name":"barclock.app.js","url":"clock-bar.js"}, {"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true} ] }, - { "id": "dotclock", + { + "id": "dotclock", "name": "Dot Clock", - "icon": "clock-dot.png", - "version":"0.03", + "version": "0.03", "description": "A Minimal Dot Analog Clock", - "tags": "clock,b2", - "type":"clock", - "allow_emulator":true, + "icon": "clock-dot.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"dotclock.app.js","url":"clock-dot.js"}, {"name":"dotclock.img","url":"clock-dot-icon.js","evaluate":true} ] }, - { "id": "widtbat", + { + "id": "widtbat", "name": "Tiny Battery Widget", - "icon": "widget.png", - "version":"0.01", + "version": "0.01", "description": "Tiny blueish battery widget, vibs and changes level color when charging", + "icon": "widget.png", + "type": "widget", "tags": "widget,tool,system", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widtbat.wid.js","url":"widget.js"} ] }, - { "id": "chrono", + { + "id": "chrono", "name": "Chrono", - "shortName":"Chrono", - "icon": "chrono.png", - "version":"0.01", + "shortName": "Chrono", + "version": "0.01", "description": "Single click BTN1 to add 5 minutes. Single click BTN2 to add 30 seconds. Single click BTN3 to add 5 seconds. Tap to pause or play to timer. Double click BTN1 to reset. When timer finishes the watch vibrates.", + "icon": "chrono.png", "tags": "tool", + "supports": ["BANGLEJS"], "storage": [ {"name":"chrono.app.js","url":"chrono.js"}, {"name":"chrono.img","url":"chrono-icon.js","evaluate":true} ] }, - { "id": "astrocalc", + { + "id": "astrocalc", "name": "Astrocalc", - "icon": "astrocalc.png", - "version":"0.02", + "version": "0.02", "description": "Calculates interesting information on the sun and moon cycles for the current day based on your location.", + "icon": "astrocalc.png", "tags": "app,sun,moon,cycles,tool,outdoors", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"astrocalc.app.js","url":"astrocalc-app.js"}, {"name":"suncalc.js","url":"suncalc.js"}, @@ -1474,108 +1648,121 @@ {"name":"waxing-crescent.img","url":"waxing-crescent-icon.js","evaluate":true} ] }, - { "id": "widhwt", + { + "id": "widhwt", "name": "Hand Wash Timer", - "icon": "widget.png", - "version":"0.01", + "version": "0.01", "description": "Swipe your wrist over the watch face to start your personal Bangle.js hand wash timer for 35 sec. Start washing after the short buzz and stop after the long buzz.", + "icon": "widget.png", + "type": "widget", "tags": "widget,tool", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widhwt.wid.js","url":"widget.js"} ] }, - { "id": "toucher", + { + "id": "toucher", "name": "Touch Launcher", - "shortName":"Toucher", - "icon": "app.png", - "version":"0.07", + "shortName": "Toucher", + "version": "0.07", "description": "Touch enable left to right launcher.", - "tags": "tool,system,launcher,b2", - "type":"launch", + "icon": "app.png", + "type": "launch", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", - "data": [ - {"name":"toucher.json"} - ], "storage": [ {"name":"toucher.app.js","url":"app.js"}, {"name":"toucher.settings.js","url":"settings.js"} ], - "sortorder" : -10 + "data": [{"name":"toucher.json"}], + "sortorder": -10 }, { "id": "balltastic", "name": "Balltastic", - "icon": "app.png", "version": "0.02", "description": "Simple but fun ball eats dots game.", - "tags": "game,fun", + "icon": "app.png", "type": "app", + "tags": "game,fun", + "supports": ["BANGLEJS"], "storage": [ - {"name":"balltastic.app.js","url":"app.js"}, - {"name":"balltastic.img","url":"app-icon.js","evaluate":true} - ] + {"name":"balltastic.app.js","url":"app.js"}, + {"name":"balltastic.img","url":"app-icon.js","evaluate":true} + ] }, { "id": "rpgdice", "name": "RPG dice", - "icon": "rpgdice.png", "version": "0.02", "description": "Simple RPG dice rolling app.", - "tags": "game,fun", + "icon": "rpgdice.png", "type": "app", + "tags": "game,fun", + "supports": ["BANGLEJS"], "allow_emulator": true, "storage": [ - {"name":"rpgdice.app.js","url": "app.js"}, - {"name":"rpgdice.img","url": "app-icon.js","evaluate":true} + {"name":"rpgdice.app.js","url":"app.js"}, + {"name":"rpgdice.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "widmp", + { + "id": "widmp", "name": "Moon Phase Widget", - "icon": "widget.png", - "version":"0.01", + "version": "0.01", "description": "Display the current moon phase in blueish for the northern hemisphere in eight phases", + "icon": "widget.png", + "type": "widget", "tags": "widget,tools", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widmp.wid.js","url":"widget.js"} ] }, - { "id": "minionclk", + { + "id": "minionclk", "name": "Minion clock", - "icon": "minionclk.png", "version": "0.05", "description": "Minion themed clock.", - "tags": "clock,minion", + "icon": "minionclk.png", "type": "clock", + "tags": "clock,minion", + "supports": ["BANGLEJS"], "allow_emulator": true, "storage": [ {"name":"minionclk.app.js","url":"app.js"}, {"name":"minionclk.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "openstmap", + { + "id": "openstmap", "name": "OpenStreetMap", - "shortName":"OpenStMap", - "icon": "app.png", - "version":"0.09", + "shortName": "OpenStMap", + "version": "0.09", "description": "[BETA] Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are", - "tags": "outdoors,gps,b2", - "custom": "custom.html", "customConnect":true, + "icon": "app.png", + "tags": "outdoors,gps", + "supports": ["BANGLEJS","BANGLEJS2"], + "custom": "custom.html", + "customConnect": true, "storage": [ {"name":"openstmap","url":"openstmap.js"}, {"name":"openstmap.app.js","url":"app.js"}, {"name":"openstmap.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "activepedom", + { + "id": "activepedom", "name": "Active Pedometer", - "shortName":"Active Pedometer", - "icon": "app.png", - "version":"0.09", + "shortName": "Active Pedometer", + "version": "0.09", "description": "Pedometer that filters out arm movement and displays a step goal progress. Steps are saved to a daily file and can be viewed as graph.", + "icon": "app.png", "tags": "outdoors,widget", - "readme": "README.md", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ {"name":"activepedom.wid.js","url":"widget.js"}, {"name":"activepedom.settings.js","url":"settings.js"}, @@ -1583,136 +1770,154 @@ {"name":"activepedom.app.js","url":"app.js"} ] }, - { "id": "chronowid", + { + "id": "chronowid", "name": "Chrono Widget", - "shortName":"Chrono Widget", - "icon": "app.png", - "version":"0.03", + "shortName": "Chrono Widget", + "version": "0.03", "description": "Chronometer (timer) which runs as widget.", - "tags": "tool,widget,b2", - "readme": "README.md", + "icon": "app.png", + "tags": "tool,widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "storage": [ {"name":"chronowid.wid.js","url":"widget.js"}, {"name":"chronowid.app.js","url":"app.js"}, {"name":"chronowid.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "tabata", + { + "id": "tabata", "name": "Tabata", "shortName": "Tabata - Control High-Intensity Interval Training", - "icon": "tabata.png", - "version":"0.01", + "version": "0.01", "description": "Control high-intensity interval training (according to tabata: https://en.wikipedia.org/wiki/Tabata_method).", + "icon": "tabata.png", "tags": "workout,health", + "supports": ["BANGLEJS"], "storage": [ {"name":"tabata.app.js","url":"tabata.js"}, {"name":"tabata.img","url":"tabata-icon.js","evaluate":true} ] }, - { "id": "custom", + { + "id": "custom", "name": "Custom Boot Code ", - "icon": "custom.png", - "version":"0.01", + "version": "0.01", "description": "Add code you want to run at boot time", - "tags": "tool,system", + "icon": "custom.png", "type": "bootloader", - "custom":"custom.html", + "tags": "tool,system", + "supports": ["BANGLEJS"], + "custom": "custom.html", "storage": [ - {"name":"custom"} + {"name":"custom"} ] }, - { "id": "devstopwatch", - "name": "Dev Stopwatch", - "shortName":"Dev Stopwatch", - "icon": "app.png", - "version":"0.03", - "description": "Stopwatch with 5 laps supported (cyclically replaced)", - "tags": "stopwatch,chrono,timer,chronometer,b2", - "allow_emulator":true, - "storage": [ - {"name":"devstopwatch.app.js","url":"app.js"}, - {"name":"devstopwatch.img","url":"app-icon.js","evaluate":true} - ] - }, - { "id": "batchart", - "name": "Battery Chart", - "shortName":"Battery Chart", + { + "id": "devstopwatch", + "name": "Dev Stopwatch", + "shortName": "Dev Stopwatch", + "version": "0.03", + "description": "Stopwatch with 5 laps supported (cyclically replaced)", "icon": "app.png", - "version":"0.10", - "readme": "README.md", + "tags": "stopwatch,chrono,timer,chronometer", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"devstopwatch.app.js","url":"app.js"}, + {"name":"devstopwatch.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "batchart", + "name": "Battery Chart", + "shortName": "Battery Chart", + "version": "0.10", "description": "A widget and an app for recording and visualizing battery percentage over time.", + "icon": "app.png", "tags": "app,widget,battery,time,record,chart,tool", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ {"name":"batchart.wid.js","url":"widget.js"}, {"name":"batchart.app.js","url":"app.js"}, {"name":"batchart.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "nato", + { + "id": "nato", "name": "NATO Alphabet", - "shortName" : "NATOAlphabet", - "icon": "nato.png", - "version":"0.01", - "type": "app", + "shortName": "NATOAlphabet", + "version": "0.01", "description": "Learn the NATO Phonetic alphabet plus some numbers.", + "icon": "nato.png", + "type": "app", "tags": "app,learn,visual", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"nato.app.js","url":"nato.js"}, {"name":"nato.img","url":"nato-icon.js","evaluate":true} ] }, - { "id": "numerals", + { + "id": "numerals", "name": "Numerals Clock", "shortName": "Numerals Clock", - "icon": "numerals.png", - "version":"0.09", + "version": "0.09", "description": "A simple big numerals clock", + "icon": "numerals.png", + "type": "clock", "tags": "numerals,clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"numerals.app.js","url":"numerals.app.js"}, {"name":"numerals.img","url":"numerals-icon.js","evaluate":true}, {"name":"numerals.settings.js","url":"numerals.settings.js"} ], - "data":[ - {"name":"numerals.json"} - ] + "data": [{"name":"numerals.json"}] }, - { "id": "bledetect", + { + "id": "bledetect", "name": "BLE Detector", - "shortName":"BLE Detector", - "icon": "bledetect.png", - "version":"0.03", + "shortName": "BLE Detector", + "version": "0.03", "description": "Detect BLE devices and show some informations.", + "icon": "bledetect.png", "tags": "app,bluetooth,tool", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"bledetect.app.js","url":"bledetect.js"}, {"name":"bledetect.img","url":"bledetect-icon.js","evaluate":true} ] }, - { "id": "snake", + { + "id": "snake", "name": "Snake", - "shortName":"Snake", - "icon": "snake.png", - "version":"0.02", + "shortName": "Snake", + "version": "0.02", "description": "The classic snake game. Eat apples and don't bite your tail.", + "icon": "snake.png", "tags": "game,fun", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"snake.app.js","url":"snake.js"}, {"name":"snake.img","url":"snake-icon.js","evaluate":true} ] }, - { "id": "calculator", + { + "id": "calculator", "name": "Calculator", - "shortName":"Calculator", - "icon": "calculator.png", - "version":"0.04", + "shortName": "Calculator", + "version": "0.04", "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.", - "tags": "app,tool,b2", + "icon": "calculator.png", + "tags": "app,tool", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"calculator.app.js","url":"app.js"}, {"name":"calculator.img","url":"calculator-icon.js","evaluate":true} @@ -1722,54 +1927,49 @@ "id": "dane", "name": "Digital Assistant, not EDITH", "shortName": "DANE", - "icon": "app.png", "version": "0.16", "description": "A Watchface inspired by Tony Stark's EDITH and based on https://arwes.dev/", - "tags": "clock", + "icon": "app.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "allow_emulator": true, "storage": [ - { - "name": "dane.app.js", - "url": "app.js" - }, - { - "name": "dane.img", - "url": "app-icon.js", - "evaluate": true - } + {"name":"dane.app.js","url":"app.js"}, + {"name":"dane.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "dane_tcr", + { + "id": "dane_tcr", "name": "DANE Touch Launcher", - "shortName":"DANE Toucher", - "icon": "app.png", - "version":"0.07", + "shortName": "DANE Toucher", + "version": "0.07", "description": "Touch enable left to right launcher in the style of the DANE Watchface", + "icon": "app.png", + "type": "launch", "tags": "tool,system,launcher", - "type":"launch", - "data": [ - {"name":"dane_tcr.json"} - ], + "supports": ["BANGLEJS"], "storage": [ {"name":"dane_tcr.app.js","url":"app.js"}, {"name":"dane_tcr.settings.js","url":"settings.js"} ], - "sortorder" : -10 + "data": [{"name":"dane_tcr.json"}], + "sortorder": -10 }, { "id": "buffgym", "name": "BuffGym", - "icon": "buffgym.png", - "version":"0.02", + "version": "0.02", "description": "BuffGym is the famous 5x5 workout program for the BangleJS", - "tags": "tool,outdoors,gym,exercise", + "icon": "buffgym.png", "type": "app", + "tags": "tool,outdoors,gym,exercise", + "supports": ["BANGLEJS"], + "readme": "README.md", "interface": "buffgym.html", "allow_emulator": false, - "readme": "README.md", "storage": [ - {"name":"buffgym.app.js", "url": "buffgym.app.js"}, + {"name":"buffgym.app.js","url":"buffgym.app.js"}, {"name":"buffgym-set.js","url":"buffgym-set.js"}, {"name":"buffgym-exercise.js","url":"buffgym-exercise.js"}, {"name":"buffgym-workout.js","url":"buffgym-workout.js"}, @@ -1783,82 +1983,76 @@ "id": "banglerun", "name": "BangleRun", "shortName": "BangleRun", - "icon": "banglerun.png", "version": "0.10", - "interface": "interface.html", "description": "An app for running sessions. Displays info and logs your run for later viewing.", + "icon": "banglerun.png", "tags": "run,running,fitness,outdoors", + "supports": ["BANGLEJS"], + "interface": "interface.html", "allow_emulator": false, "storage": [ - { - "name": "banglerun.app.js", - "url": "app.js" - }, - { - "name": "banglerun.img", - "url": "app-icon.js", - "evaluate": true - } + {"name":"banglerun.app.js","url":"app.js"}, + {"name":"banglerun.img","url":"app-icon.js","evaluate":true} ] }, { "id": "metronome", "name": "Metronome", - "icon": "metronome_icon.png", "version": "0.06", - "readme": "README.md", "description": "Makes the watch blinking and vibrating with a given rate", + "icon": "metronome_icon.png", "tags": "tool", + "supports": ["BANGLEJS"], + "readme": "README.md", "allow_emulator": true, "storage": [ - { - "name": "metronome.app.js", - "url": "metronome.js" - }, - { - "name": "metronome.img", - "url": "metronome-icon.js", - "evaluate": true - }, + {"name":"metronome.app.js","url":"metronome.js"}, + {"name":"metronome.img","url":"metronome-icon.js","evaluate":true}, {"name":"metronome.settings.js","url":"settings.js"} ] }, - { "id": "blackjack", + { + "id": "blackjack", "name": "Black Jack game", - "shortName":"Black Jack game", - "icon": "blackjack.png", - "version":"0.02", + "shortName": "Black Jack game", + "version": "0.02", "description": "Simple implementation of card game Black Jack", + "icon": "blackjack.png", "tags": "game", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"blackjack.app.js","url":"blackjack.app.js"}, {"name":"blackjack.img","url":"blackjack-icon.js","evaluate":true} ] }, - { "id": "hidcam", + { + "id": "hidcam", "name": "Camera shutter", - "shortName":"Cam shutter", - "icon": "app.png", - "version":"0.03", + "shortName": "Cam shutter", + "version": "0.03", "description": "Enable HID, connect to your phone, start your camera and trigger the shot on your Bangle", - "readme": "README.md", + "icon": "app.png", "tags": "bluetooth,tool", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ - {"name":"hidcam.app.js","url":"app.js"}, - {"name":"hidcam.img","url":"app-icon.js","evaluate":true} + {"name":"hidcam.app.js","url":"app.js"}, + {"name":"hidcam.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "swlclk", + { + "id": "swlclk", "name": "SWL Clock / Short Wave Listner Clock", "shortName": "SWL Clock", - "icon": "swlclk.png", - "version":"0.02", + "version": "0.02", "description": "Display Local, UTC time and some programs on the shorts waves along the day, with the frequencies", + "icon": "swlclk.png", + "type": "clock", "tags": "tool,clock", - "type":"clock", + "supports": ["BANGLEJS"], "readme": "README.md", - "allow_emulator":true, + "allow_emulator": true, "storage": [ {"name":"swlclk.app.js","url":"app.js"}, {"name":"swlclk.img","url":"app-icon.js","evaluate":true} @@ -1868,11 +2062,12 @@ "id": "rclock", "name": "Round clock with seconds, minutes and date", "shortName": "Round Clock", - "icon": "app.png", "version": "0.06", "description": "Designed round clock with ticks for minutes and seconds and heart rate indication", - "tags": "clock", + "icon": "app.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "storage": [ {"name":"rclock.app.js","url":"rclock.app.js"}, {"name":"rclock.img","url":"app-icon.js","evaluate":true} @@ -1882,35 +2077,40 @@ "id": "fclock", "name": "fclock", "shortName": "F Clock", - "icon": "app.png", "version": "0.02", "description": "Simple design of a digital clock", - "tags": "clock", + "icon": "app.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "storage": [ {"name":"fclock.app.js","url":"fclock.app.js"}, {"name":"fclock.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "hamloc", + { + "id": "hamloc", "name": "QTH Locator / Maidenhead Locator System", "shortName": "QTH Locator", - "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "Convert your current GPS location to the Maidenhead locator system used by HAM amateur radio operators", + "icon": "app.png", "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"hamloc.app.js","url":"app.js"}, {"name":"hamloc.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "osmpoi", + { + "id": "osmpoi", "name": "POI Compass", - "icon": "app.png", - "version":"0.03", + "version": "0.03", "description": "Uploads all the points of interest in an area onto your watch, same as Beer Compass with more p.o.i.", + "icon": "app.png", "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], "readme": "README.md", "custom": "custom.html", "storage": [ @@ -1918,64 +2118,63 @@ {"name":"osmpoi.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "pong", + { + "id": "pong", "name": "Pong", "shortName": "Pong", - "icon": "pong.png", "version": "0.03", "description": "A clone of the Atari game Pong", - "tags": "game", + "icon": "pong.png", "type": "app", - "allow_emulator": true, + "tags": "game", + "supports": ["BANGLEJS"], "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"pong.app.js","url":"app.js"}, {"name":"pong.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "ballmaze", + { + "id": "ballmaze", "name": "Ball Maze", - "icon": "icon.png", "version": "0.02", "description": "Navigate a ball through a maze by tilting your watch.", - "readme": "README.md", - "tags": "game", + "icon": "icon.png", "type": "app", + "tags": "game", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ - {"name": "ballmaze.app.js","url":"app.js"}, - {"name": "ballmaze.img","url":"icon.js","evaluate": true} + {"name":"ballmaze.app.js","url":"app.js"}, + {"name":"ballmaze.img","url":"icon.js","evaluate":true} ], - "data": [ - {"name": "ballmaze.json"} - ] + "data": [{"name":"ballmaze.json"}] }, - { "id": "calendar", + { + "id": "calendar", "name": "Calendar", - "icon": "calendar.png", "version": "0.02", "description": "Simple calendar", - "tags": "calendar,b2", + "icon": "calendar.png", + "tags": "calendar", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "allow_emulator": true, "storage": [ - { - "name": "calendar.app.js", - "url": "calendar.js" - }, - { - "name": "calendar.img", - "url": "calendar-icon.js", - "evaluate": true - } + {"name":"calendar.app.js","url":"calendar.js"}, + {"name":"calendar.img","url":"calendar-icon.js","evaluate":true} ] }, - { "id": "hidjoystick", + { + "id": "hidjoystick", "name": "Bluetooth Joystick", "shortName": "Joystick", - "icon": "app.png", - "version":"0.01", + "version": "0.01", "description": "Emulates a 2 axis/5 button Joystick using the accelerometer as stick input and buttons 1-3, touch left as button 4 and touch right as button 5.", + "icon": "app.png", "tags": "bluetooth", + "supports": ["BANGLEJS"], "storage": [ {"name":"hidjoystick.app.js","url":"app.js"}, {"name":"hidjoystick.img","url":"app-icon.js","evaluate":true} @@ -1984,30 +2183,31 @@ { "id": "largeclock", "name": "Large Clock", - "icon": "largeclock.png", "version": "0.10", "description": "A readable and informational digital watch, with date, seconds and moon phase", - "readme": "README.md", - "tags": "clock", + "icon": "largeclock.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", "allow_emulator": true, "storage": [ - {"name": "largeclock.app.js", "url": "largeclock.js"}, - {"name": "largeclock.img", "url": "largeclock-icon.js", "evaluate": true}, - {"name": "largeclock.settings.js", "url": "settings.js"} + {"name":"largeclock.app.js","url":"largeclock.js"}, + {"name":"largeclock.img","url":"largeclock-icon.js","evaluate":true}, + {"name":"largeclock.settings.js","url":"settings.js"} ], - "data": [ - {"name":"largeclock.json"} - ] + "data": [{"name":"largeclock.json"}] }, - { "id": "smtswch", + { + "id": "smtswch", "name": "Smart Switch", - "shortName":"Smart Switch", - "icon": "app.png", - "version":"0.01", + "shortName": "Smart Switch", + "version": "0.01", "description": "Using EspruinoHub, control your smart devices on and off via Bluetooth Low Energy!", - "tags": "bluetooth,btle,smart,switch", + "icon": "app.png", "type": "app", + "tags": "bluetooth,btle,smart,switch", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"smtswch.app.js","url":"app.js"}, @@ -2018,13 +2218,15 @@ {"name":"switch-off.img","url":"switch-off.js","evaluate":true} ] }, - { "id": "miplant", + { + "id": "miplant", "name": "Xiaomi Plant Sensor", - "shortName":"Mi Plant", - "icon": "app.png", - "version":"0.02", + "shortName": "Mi Plant", + "version": "0.02", "description": "Reads and displays data from Xiaomi bluetooth plant moisture sensors", + "icon": "app.png", "tags": "xiaomi,mi,plant,ble,bluetooth", + "supports": ["BANGLEJS"], "storage": [ {"name":"miplant.app.js","url":"app.js"}, {"name":"miplant.img","url":"app-icon.js","evaluate":true} @@ -2033,76 +2235,63 @@ { "id": "simpletimer", "name": "Timer", - "icon": "app.png", "version": "0.07", "description": "Simple timer, useful when playing board games or cooking", + "icon": "app.png", "tags": "timer", + "supports": ["BANGLEJS"], "readme": "README.md", "allow_emulator": true, "storage": [ - { - "name": "simpletimer.app.js", - "url": "app.js" - }, - { - "name": ".tfnames", - "url": "gesture-tfnames.js", - "evaluate": true - }, - { - "name": ".tfmodel", - "url": "gesture-tfmodel.js", - "evaluate": true - }, - { - "name": "simpletimer.img", - "url": "app-icon.js", - "evaluate": true - } + {"name":"simpletimer.app.js","url":"app.js"}, + {"name":".tfnames","url":"gesture-tfnames.js","evaluate":true}, + {"name":".tfmodel","url":"gesture-tfmodel.js","evaluate":true}, + {"name":"simpletimer.img","url":"app-icon.js","evaluate":true} ], - "data": [ - { - "name": "simpletimer.json" - } - ] + "data": [{"name":"simpletimer.json"}] }, { "id": "beebclock", "name": "Beeb Clock", - "icon": "beebclock.png", - "version":"0.05", + "version": "0.05", "description": "Clock face that may be coincidentally familiar to BBC viewers", - "tags": "clock", + "icon": "beebclock.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "allow_emulator": true, "storage": [ - {"name":"beebclock.app.js","url":"beebclock.js"}, - {"name":"beebclock.img","url":"beebclock-icon.js","evaluate":true} + {"name":"beebclock.app.js","url":"beebclock.js"}, + {"name":"beebclock.img","url":"beebclock-icon.js","evaluate":true} ] }, - { "id": "findphone", + { + "id": "findphone", "name": "Find Phone", - "shortName":"Find Phone", - "icon": "app.png", - "version":"0.03", + "shortName": "Find Phone", + "version": "0.03", "description": "Find your phone via Gadgetbridge. Click any button to let your phone ring. 📳 Note: The functionality is available even without this app, just go to Settings, App Settings, Gadgetbridge, Find Phone.", + "icon": "app.png", "tags": "tool,android", + "supports": ["BANGLEJS"], "readme": "README.md", "allow_emulator": true, "storage": [ - {"name":"findphone.app.js","url":"app.js"}, - {"name":"findphone.img","url":"app-icon.js","evaluate":true} + {"name":"findphone.app.js","url":"app.js"}, + {"name":"findphone.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "getup", + { + "id": "getup", "name": "Get Up", - "shortName":"Get Up", - "icon": "app.png", - "version":"0.01", + "shortName": "Get Up", + "version": "0.01", "description": "Reminds you to getup every x minutes. Sitting to long is dangerous!", + "icon": "app.png", "tags": "tools,health", + "supports": ["BANGLEJS"], "readme": "README.md", - "allow_emulator":true, + "allow_emulator": true, "storage": [ {"name":"getup.app.js","url":"app.js"}, {"name":"getup.settings.js","url":"settings.js"}, @@ -2113,43 +2302,46 @@ "id": "gallifr", "name": "Time Traveller's Chronometer", "shortName": "Time Travel Clock", - "icon": "gallifr.png", "version": "0.02", "description": "A clock for time travellers. The light pie segment shows the minutes, the black circle, the hour. The dial itself reads 'time' just in case you forget.", - "tags": "clock,b2", - "readme": "README.md", + "icon": "gallifr.png", "type": "clock", - "allow_emulator":true, - "storage": [ - { "name": "gallifr.app.js", "url": "app.js" }, - { "name": "gallifr.img", "url": "app-icon.js", "evaluate": true }, - { "name": "gallifr.settings.js", "url": "settings.js" } - ], - "data": [ - {"name":"gallifr.json"} - ] - }, - { "id": "rndmclk", - "name": "Random Clock Loader", - "icon": "rndmclk.png", - "version":"0.03", - "description": "Load a different clock whenever the LCD is switched on.", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"gallifr.app.js","url":"app.js"}, + {"name":"gallifr.img","url":"app-icon.js","evaluate":true}, + {"name":"gallifr.settings.js","url":"settings.js"} + ], + "data": [{"name":"gallifr.json"}] + }, + { + "id": "rndmclk", + "name": "Random Clock Loader", + "version": "0.03", + "description": "Load a different clock whenever the LCD is switched on.", + "icon": "rndmclk.png", + "type": "widget", "tags": "widget,clock", - "type":"widget", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ {"name":"rndmclk.wid.js","url":"widget.js"} ] }, - { "id": "dotmatrixclock", + { + "id": "dotmatrixclock", "name": "Dotmatrix Clock", - "icon": "dotmatrixclock.png", - "version":"0.01", + "version": "0.01", "description": "A clear white-on-blue dotmatrix simulated clock", - "tags": "clock,dotmatrix,retro", + "icon": "dotmatrixclock.png", "type": "clock", - "allow_emulator":true, + "tags": "clock,dotmatrix,retro", + "supports": ["BANGLEJS"], "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"dotmatrixclock.app.js","url":"app.js"}, {"name":"dotmatrixclock.img","url":"dotmatrixclock-icon.js","evaluate":true} @@ -2159,63 +2351,71 @@ "id": "jbm8b", "name": "Magic 8 Ball", "shortName": "Magic 8 Ball", - "icon": "app.png", - "description": "A simple fortune telling app", - "tags": "game", "version": "0.03", + "description": "A simple fortune telling app", + "icon": "app.png", + "tags": "game", + "supports": ["BANGLEJS"], "storage": [ - { "name": "jbm8b.app.js", "url": "app.js" }, - { "name": "jbm8b.img", "url": "app-icon.js", "evaluate": true } - ] + {"name":"jbm8b.app.js","url":"app.js"}, + {"name":"jbm8b.img","url":"app-icon.js","evaluate":true} + ] }, { "id": "jbm8b_IT", "name": "Magic 8 Ball Italiano", "shortName": "Magic 8 Ball IT", - "icon": "app.png", + "version": "0.01", "description": "La palla predice il futuro", + "icon": "app.png", "tags": "game", - "version": "0.01", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ - { "name": "jbm8b_IT.app.js", "url": "app.js" }, - { "name": "jbm8b_IT.img", "url": "app-icon.js", "evaluate": true } - ] - }, - { "id": "BLEcontroller", - "name": "BLE Customisable Controller with Joystick", - "shortName": "BLE Controller", - "icon": "BLEcontroller.png", - "version": "0.01", - "description": "A configurable controller for BLE devices and robots, with a basic four direction joystick. Designed to be easy to customise so you can add your own menus.", - "tags": "tool,bluetooth", - "readme": "README.md", - "allow_emulator":false, - "storage": [ - { "name": "BLEcontroller.app.js", "url": "app.js" }, - { "name": "BLEcontroller.img", "url": "app-icon.js", "evaluate": true } + {"name":"jbm8b_IT.app.js","url":"app.js"}, + {"name":"jbm8b_IT.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "widviz", + { + "id": "BLEcontroller", + "name": "BLE Customisable Controller with Joystick", + "shortName": "BLE Controller", + "version": "0.01", + "description": "A configurable controller for BLE devices and robots, with a basic four direction joystick. Designed to be easy to customise so you can add your own menus.", + "icon": "BLEcontroller.png", + "tags": "tool,bluetooth", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": false, + "storage": [ + {"name":"BLEcontroller.app.js","url":"app.js"}, + {"name":"BLEcontroller.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "widviz", "name": "Widget Visibility Widget", - "shortName":"Viz Widget", - "icon": "eye.png", - "version":"0.02", + "shortName": "Viz Widget", + "version": "0.02", "description": "Swipe left to hide top bar widgets, swipe right to redisplay.", - "tags": "widget", + "icon": "eye.png", "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widviz.wid.js","url":"widget.js"} ] }, - { "id": "binclock", + { + "id": "binclock", "name": "Binary Clock", - "shortName":"Binary Clock", - "icon": "app.png", - "version":"0.03", + "shortName": "Binary Clock", + "version": "0.03", "description": "A binary clock with hours and minutes. BTN1 toggles a digital clock.", - "tags": "clock,binary", + "icon": "app.png", "type": "clock", + "tags": "clock,binary", + "supports": ["BANGLEJS"], "storage": [ {"name":"binclock.app.js","url":"app.js"}, {"name":"binclock.img","url":"app-icon.js","evaluate":true} @@ -2224,25 +2424,28 @@ { "id": "pizzatimer", "name": "Pizza Timer", - "shortName":"Pizza Timer", - "icon": "pizza.png", - "version":"0.01", + "shortName": "Pizza Timer", + "version": "0.01", "description": "A timer app for when you cook Pizza. Some say it can also time other things", + "icon": "pizza.png", "tags": "timer,tool,pizza", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"pizzatimer.app.js","url":"app.js"}, {"name":"pizzatimer.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "animclk", + { + "id": "animclk", "name": "Animated Clock", - "shortName":"Anim Clock", - "icon": "app.png", - "version":"0.03", + "shortName": "Anim Clock", + "version": "0.03", "description": "An animated clock face using Mark Ferrari's amazing 8 bit game art and palette cycling: http://www.markferrari.com/art/8bit-game-art", - "tags": "clock,animated,bno2", + "icon": "app.png", "type": "clock", + "tags": "clock,animated", + "supports": ["BANGLEJS"], "storage": [ {"name":"animclk.app.js","url":"app.js"}, {"name":"animclk.pixels1","url":"animclk.pixels1"}, @@ -2251,14 +2454,16 @@ {"name":"animclk.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "analogimgclk", + { + "id": "analogimgclk", "name": "Analog Clock (Image background)", - "shortName":"Analog Clock", - "icon": "app.png", - "version":"0.03", + "shortName": "Analog Clock", + "version": "0.03", "description": "An analog clock with an image background", - "tags": "clock,bno2", + "icon": "app.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "storage": [ {"name":"analogimgclk.app.js","url":"app.js"}, {"name":"analogimgclk.bg.img","url":"bg.img"}, @@ -2268,116 +2473,131 @@ { "id": "verticalface", "name": "Vertical watch face", - "shortName":"Vertical Face", - "icon": "app.png", - "version":"0.09", + "shortName": "Vertical Face", + "version": "0.09", "description": "A simple vertical watch face with the date. Heart rate monitor is toggled with BTN1", + "icon": "app.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"verticalface.app.js","url":"app.js"}, {"name":"verticalface.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "sleepphasealarm", + { + "id": "sleepphasealarm", "name": "SleepPhaseAlarm", - "shortName":"SleepPhaseAlarm", - "icon": "app.png", - "version":"0.02", + "shortName": "SleepPhaseAlarm", + "version": "0.02", "description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.", + "icon": "app.png", "tags": "alarm", + "supports": ["BANGLEJS"], "storage": [ {"name":"sleepphasealarm.app.js","url":"app.js"}, {"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "life", + { + "id": "life", "name": "Game of Life", - "icon": "life.png", - "version":"0.04", + "version": "0.04", "description": "Conway's Game of Life - 16x16 board", + "icon": "life.png", "tags": "game", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"life.app.js","url":"life.min.js"}, {"name":"life.img","url":"life-icon.js","evaluate":true} ] }, - { "id": "magnav", + { + "id": "magnav", "name": "Navigation Compass", - "icon": "magnav.png", - "version":"0.04", + "version": "0.04", "description": "Compass with linear display as for GPSNAV. Has Tilt compensation and remembers calibration.", - "readme": "README.md", + "icon": "magnav.png", "tags": "tool,outdoors", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ {"name":"magnav.app.js","url":"magnav.min.js"}, {"name":"magnav.img","url":"magnav-icon.js","evaluate":true} ], - "data":[{"name":"magnav.json"}] + "data": [{"name":"magnav.json"}] }, - { "id": "gpspoilog", + { + "id": "gpspoilog", "name": "GPS POI Logger", - "shortName":"GPS POI Log", - "icon": "app.png", - "version":"0.01", + "shortName": "GPS POI Log", + "version": "0.01", "description": "A simple app to log points of interest with their GPS coordinates and read them back onto your PC. Based on the https://www.espruino.com/Bangle.js+Storage tutorial", + "icon": "app.png", "tags": "outdoors", + "supports": ["BANGLEJS"], "interface": "interface.html", "storage": [ {"name":"gpspoilog.app.js","url":"app.js"}, {"name":"gpspoilog.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "miclock2", + { + "id": "miclock2", "name": "Mixed Clock 2", - "icon": "clock-mixed.png", - "version":"0.01", + "version": "0.01", "description": "White color variant of the Mixed Clock with thicker clock hands for better readability in the bright sunlight, extra space under the clock for widgets and seconds in the digital clock.", + "icon": "clock-mixed.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ {"name":"miclock2.app.js","url":"clock-mixed.js"}, {"name":"miclock2.img","url":"clock-mixed-icon.js","evaluate":true} ] }, - { "id": "1button", + { + "id": "1button", "name": "One-Button-Tracker", - "icon": "widget.png", - "version":"0.01", - "interface": "interface.html", + "version": "0.01", "description": "A widget that turns BTN1 into a tracker, records time of button press/release.", - "tags": "tool,quantifiedself,widget", + "icon": "widget.png", "type": "widget", + "tags": "tool,quantifiedself,widget", + "supports": ["BANGLEJS"], "readme": "README.md", + "interface": "interface.html", "storage": [ {"name":"1button.wid.js","url":"widget.js"} ], - "data": [ - {"name":"one_button_presses.csv","storageFile": true} - ] + "data": [{"name":"one_button_presses.csv","storageFile":true}] }, - { "id": "gpsautotime", + { + "id": "gpsautotime", "name": "GPS auto time", - "shortName":"GPS auto time", - "icon": "widget.png", - "version":"0.01", + "shortName": "GPS auto time", + "version": "0.01", "description": "A widget that automatically updates the Bangle.js time to the GPS time whenever there is a valid GPS fix.", - "tags": "widget,gps", + "icon": "widget.png", "type": "widget", + "tags": "widget,gps", + "supports": ["BANGLEJS"], "storage": [ {"name":"gpsautotime.wid.js","url":"widget.js"} ] }, - { "id": "espruinoctrl", + { + "id": "espruinoctrl", "name": "Espruino Control", - "shortName":"Espruino Ctrl", - "icon": "app.png", - "version":"0.01", + "shortName": "Espruino Ctrl", + "version": "0.01", "description": "Send commands to other Espruino devices via the Bluetooth UART interface. Customisable commands!", + "icon": "app.png", "tags": "", + "supports": ["BANGLEJS"], "readme": "README.md", "custom": "custom.html", "storage": [ @@ -2385,15 +2605,17 @@ {"name":"espruinoctrl.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "multiclock", + { + "id": "multiclock", "name": "Multi Clock", - "icon": "multiclock.png", - "version":"0.13", + "version": "0.13", "description": "Clock with multiple faces - Big, Analogue, Digital, Text, Time-Date.\n Switch between faces with BTN1 & BTN3", - "readme": "README.md", + "icon": "multiclock.png", + "type": "clock", "tags": "clock", - "type":"clock", - "allow_emulator":true, + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"multiclock.app.js","url":"clock.js"}, {"name":"big.face.js","url":"big.js"}, @@ -2405,152 +2627,157 @@ {"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true} ] }, - { "id": "widancs", + { + "id": "widancs", "name": "Apple Notification Widget", - "shortName":"ANCS Widget", - "icon": "widget.png", - "version":"0.07", + "shortName": "ANCS Widget", + "version": "0.07", "description": "Displays call, message etc notifications from a paired iPhone. Read README before installation as it only works with compatible apps", - "readme": "README.md", - "tags": "widget", + "icon": "widget.png", "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], + "readme": "README.md", "storage": [ {"name":"widancs.wid.js","url":"ancs.min.js"}, {"name":"widancs.settings.js","url":"settings.js"} ] }, - { "id": "accelrec", + { + "id": "accelrec", "name": "Acceleration Recorder", - "shortName":"Accel Rec", - "icon": "app.png", - "version":"0.02", - "interface": "interface.html", + "shortName": "Accel Rec", + "version": "0.02", "description": "This app puts the Bangle's accelerometer into 100Hz mode and reads 2 seconds worth of data after movement starts. The data can then be exported back to the PC.", + "icon": "app.png", "tags": "", + "supports": ["BANGLEJS"], "readme": "README.md", + "interface": "interface.html", "storage": [ {"name":"accelrec.app.js","url":"app.js"}, {"name":"accelrec.img","url":"app-icon.js","evaluate":true} ], - "data": [ - {"wildcard":"accelrec.?.csv" } - ] + "data": [{"wildcard":"accelrec.?.csv"}] }, - { "id": "accellog", + { + "id": "accellog", "name": "Acceleration Logger", - "shortName":"Accel Log", - "icon": "app.png", - "version":"0.03", - "interface": "interface.html", + "shortName": "Accel Log", + "version": "0.03", "description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC", - "tags": "outdoor,b2", + "icon": "app.png", + "tags": "outdoor", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", + "interface": "interface.html", "storage": [ {"name":"accellog.app.js","url":"app.js"}, {"name":"accellog.img","url":"app-icon.js","evaluate":true} ], - "data": [ - {"wildcard":"accellog.?.csv" } - ] + "data": [{"wildcard":"accellog.?.csv"}] }, { "id": "cprassist", - "name":"CPR Assist", - "icon":"cprassist-icon.png", + "name": "CPR Assist", "version": "0.01", - "readme": "README.md", "description": "Provides assistance while performing a CPR", + "icon": "cprassist-icon.png", "tags": "tool,firstaid", + "supports": ["BANGLEJS"], + "readme": "README.md", "allow_emulator": true, "storage": [ - { - "name": "cprassist.app.js", - "url": "cprassist.js" - }, - { - "name": "cprassist.img", - "url": "cprassist-icon.js", - "evaluate": true - }, - { - "name": "cprassist.settings.js", - "url": "settings.js" - } + {"name":"cprassist.app.js","url":"cprassist.js"}, + {"name":"cprassist.img","url":"cprassist-icon.js","evaluate":true}, + {"name":"cprassist.settings.js","url":"settings.js"} ] }, - { "id": "osgridref", + { + "id": "osgridref", "name": "Ordnance Survey Grid Reference", - "shortName":"OS Grid ref", - "icon": "app.png", - "version":"0.01", + "shortName": "OS Grid ref", + "version": "0.01", "description": "Displays the UK Ordnance Survey grid reference of your current GPS location. Useful when in the United Kingdom with an Ordnance Survey map", + "icon": "app.png", "tags": "outdoors,gps", + "supports": ["BANGLEJS"], "storage": [ {"name":"osgridref.app.js","url":"app.js"}, {"name":"osgridref.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "openseizure", + { + "id": "openseizure", "name": "OpenSeizureDetector Widget", - "shortName":"Short Name", - "icon": "widget.png", - "version":"0.01", + "shortName": "Short Name", + "version": "0.01", "description": "[BETA!] A widget to work alongside [OpenSeizureDetector](https://www.openseizuredetector.org.uk/)", - "tags": "widget", + "icon": "widget.png", "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"openseizure.wid.js","url":"widget.js"} ] }, - {"id": "counter", - "name": "Counter", - "icon": "counter_icon.png", - "version": "0.03", - "description": "Simple counter", - "tags": "tool", - "allow_emulator": true, - "storage": [ - {"name": "counter.app.js", "url": "counter.js"}, - {"name": "counter.img", "url": "counter-icon.js", "evaluate": true} - ] + { + "id": "counter", + "name": "Counter", + "version": "0.03", + "description": "Simple counter", + "icon": "counter_icon.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"counter.app.js","url":"counter.js"}, + {"name":"counter.img","url":"counter-icon.js","evaluate":true} + ] }, - { "id": "bootgattbat", + { + "id": "bootgattbat", "name": "BLE GATT Battery Service", - "shortName":"BLE Battery Service", - "icon": "bluetooth.png", - "version":"0.01", + "shortName": "BLE Battery Service", + "version": "0.01", "description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n", - "tags": "battery,ble,bluetooth,gatt", + "icon": "bluetooth.png", "type": "bootloader", + "tags": "battery,ble,bluetooth,gatt", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"gattbat.boot.js","url":"boot.js"} ] }, - { "id": "viewstl", - "name": "STL file viewer", - "shortName":"ViewSTL", - "icon": "icons8-octahedron-48.png", - "version":"0.02", - "description": "This app allows you to view STL 3D models on your watch", - "tags": "tool", - "readme": "README.md", - "storage": [ - {"name":"viewstl.app.js","url":"viewstl.min.js"}, - {"name":"viewstl.img","url":"viewstl-icon.js","evaluate":true}, - {"name":"tetra.stl","url":"tetra.stl"}, - {"name":"cube.stl","url":"cube.stl"}, - {"name":"icosa.stl","url":"icosa.stl"} - ] + { + "id": "viewstl", + "name": "STL file viewer", + "shortName": "ViewSTL", + "version": "0.02", + "description": "This app allows you to view STL 3D models on your watch", + "icon": "icons8-octahedron-48.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"viewstl.app.js","url":"viewstl.min.js"}, + {"name":"viewstl.img","url":"viewstl-icon.js","evaluate":true}, + {"name":"tetra.stl","url":"tetra.stl"}, + {"name":"cube.stl","url":"cube.stl"}, + {"name":"icosa.stl","url":"icosa.stl"} + ] }, - { "id": "cscsensor", + { + "id": "cscsensor", "name": "Cycling speed sensor", - "shortName":"CSCSensor", - "icon": "icons8-cycling-48.png", - "version":"0.05", + "shortName": "CSCSensor", + "version": "0.05", "description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch", + "icon": "icons8-cycling-48.png", "tags": "outdoors,exercise,ble,bluetooth", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"cscsensor.app.js","url":"cscsensor.app.js"}, @@ -2558,70 +2785,78 @@ {"name":"cscsensor.img","url":"cscsensor-icon.js","evaluate":true} ] }, - { "id": "fileman", + { + "id": "fileman", "name": "File manager", - "shortName":"FileManager", - "icon": "icons8-filing-cabinet-48.png", - "version":"0.03", + "shortName": "FileManager", + "version": "0.03", "description": "Simple file manager, allows user to examine watch storage and display, load or delete individual files", + "icon": "icons8-filing-cabinet-48.png", "tags": "tools", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"fileman.app.js","url":"fileman.app.js"}, {"name":"fileman.img","url":"fileman-icon.js","evaluate":true} ] }, - { "id": "worldclock", + { + "id": "worldclock", "name": "World Clock - 4 time zones", - "shortName":"World Clock", - "icon": "app.png", - "version":"0.04", + "shortName": "World Clock", + "version": "0.04", "description": "Current time zone plus up to four others", + "icon": "app.png", + "type": "clock", "tags": "clock", - "type" : "clock", - "custom": "custom.html", + "supports": ["BANGLEJS"], "readme": "README.md", + "custom": "custom.html", "storage": [ {"name":"worldclock.app.js","url":"app.js"}, {"name":"worldclock.img","url":"worldclock-icon.js","evaluate":true} ], - "data": [ - {"name":"worldclock.settings.json"} + "data": [{"name":"worldclock.settings.json"}] + }, + { + "id": "digiclock", + "name": "Digital Clock Face", + "shortName": "Digi Clock", + "version": "0.02", + "description": "A simple digital clock with the time, day, month, and year", + "icon": "digiclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"digiclock.app.js","url":"digiclock.js"}, + {"name":"digiclock.img","url":"digiclock-icon.js","evaluate":true} ] - }, -{ "id": "digiclock", - "name": "Digital Clock Face", - "shortName":"Digi Clock", - "icon": "digiclock.png", - "version":"0.02", - "description": "A simple digital clock with the time, day, month, and year", - "tags": "clock,bno2", - "type" : "clock", - "storage": [ - {"name":"digiclock.app.js","url":"digiclock.js"}, - {"name":"digiclock.img","url":"digiclock-icon.js","evaluate":true} - ] -}, - { "id": "dsdrelay", + }, + { + "id": "dsdrelay", "name": "DSD BLE Relay controller", - "shortName":"DSDRelay", - "icon": "icons8-relay-48.png", - "version":"0.01", + "shortName": "DSDRelay", + "version": "0.01", "description": "Control BLE relay board from the watch", + "icon": "icons8-relay-48.png", "tags": "ble,bluetooth", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"dsdrelay.app.js","url":"dsdrelay.app.js"}, {"name":"dsdrelay.img","url":"dsdrelay-icon.js","evaluate":true} ] }, - { "id": "mandel", + { + "id": "mandel", "name": "Mandelbrot", - "shortName":"Mandel", - "icon": "mandel.png", - "version":"0.01", + "shortName": "Mandel", + "version": "0.01", "description": "Draw a zoomable Mandelbrot set", + "icon": "mandel.png", "tags": "game", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"mandel.app.js","url":"mandel.min.js"}, @@ -2631,997 +2866,1100 @@ { "id": "petrock", "name": "Pet rock", - "icon": "petrock.png", "version": "0.02", "description": "A virtual pet rock with wobbly eyes", - "tags": "game", + "icon": "petrock.png", "type": "app", + "tags": "game", + "supports": ["BANGLEJS"], "storage": [ - {"name": "petrock.app.js", "url": "app.js"}, - {"name": "petrock.img", "url": "app-icon.js", "evaluate": true} + {"name":"petrock.app.js","url":"app.js"}, + {"name":"petrock.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "smartibot", + { + "id": "smartibot", "name": "Smartibot controller", - "shortName":"Smartibot", - "icon": "app.png", - "version":"0.01", + "shortName": "Smartibot", + "version": "0.01", "description": "Control a [Smartibot Robot](https://thecraftyrobot.net/) straight from your Bangle.js", + "icon": "app.png", "tags": "", + "supports": ["BANGLEJS"], "storage": [ {"name":"smartibot.app.js","url":"app.js"}, {"name":"smartibot.img","url":"app-icon.js","evaluate":true} ] }, - { "id": "widncr", + { + "id": "widncr", "name": "NCR Logo Widget", - "icon": "widget.png", - "version":"0.01", + "version": "0.01", "description": "Show the NodeConf Remote logo in the top left", + "icon": "widget.png", + "type": "widget", "tags": "widget", - "type":"widget", + "supports": ["BANGLEJS"], "storage": [ {"name":"widncr.wid.js","url":"widget.js"} ] }, - { "id": "ncrclk", + { + "id": "ncrclk", "name": "NCR Clock", - "shortName":"NCR Clock", - "icon": "app.png", - "version":"0.02", + "shortName": "NCR Clock", + "version": "0.02", "description": "NodeConf Remote clock", - "tags": "clock", + "icon": "app.png", "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], "storage": [ {"name":"ncrclk.app.js","url":"app.js"}, {"name":"ncrclk.img","url":"app-icon.js","evaluate":true} ] }, -{ "id": "isoclock", - "name": "ISO Compliant Clock Face", - "shortName":"ISO Clock", - "icon": "isoclock.png", - "version":"0.02", - "description": "Tweaked fork of digiclock for ISO date and time", - "tags": "clock", - "type" : "clock", - "storage": [ - {"name":"isoclock.app.js","url":"isoclock.js"}, - {"name":"isoclock.img","url":"isoclock-icon.js","evaluate":true} - ] -}, -{ "id": "gpstimeserver", - "name": "GPS Time Server", - "icon": "widget.png", - "version":"0.01", - "description": "A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server.", - "tags": "widget", - "type": "widget", - "readme": "README.md", - "storage": [ - {"name":"gpstimeserver.wid.js","url":"widget.js"} - ] -}, -{ "id": "tilthydro", - "name": "Tilt Hydrometer Display", - "shortName":"Tilt Hydro", - "icon": "app.png", - "version":"0.01", - "description": "A display for the [Tilt Hydrometer](https://tilthydrometer.com/) - [more info here](http://www.espruino.com/Tilt+Hydrometer+Display)", - "tags": "tools,bluetooth", - "storage": [ - {"name":"tilthydro.app.js","url":"app.js"}, - {"name":"tilthydro.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "supmariodark", - "name": "Super mario clock night mode", - "shortName":"supmariodark", - "icon": "supmariodark.png", - "version":"0.01", - "description": "Super mario clock in night mode", - "tags": "clock", - "type" : "clock", - "storage": [ - {"name":"supmariodark.app.js","url":"supmariodark.js"}, - {"name":"supmariodark.img","url":"supmariodark-icon.js","evaluate":true}, - {"name":"supmario30x24.bin","url":"supmario30x24.bin.js"}, - {"name":"supmario30x24.wdt","url":"supmario30x24.wdt.js"}, - {"name":"banner-up.img","url":"banner-up.js","evaluate":true}, - {"name":"banner-down.img","url":"banner-down.js","evaluate":true}, - {"name":"brick2.img","url":"brick2.js","evaluate":true}, - {"name":"enemy.img","url":"enemy.js","evaluate":true}, - {"name":"flower.img","url":"flower.js","evaluate":true}, - {"name":"flower_b.img","url":"flower_b.js","evaluate":true}, - {"name":"mario_wh.img","url":"mario_wh.js","evaluate":true}, - {"name":"pipe.img","url":"pipe.js","evaluate":true} - ] -}, -{ "id": "gmeter", - "name": "G-Meter", - "shortName":"G-Meter", - "icon": "app.png", - "version":"0.01", - "description": "Simple G-Meter", - "tags": "", - "storage": [ - {"name":"gmeter.app.js","url":"app.js"}, - {"name":"gmeter.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "dtlaunch", - "name": "Desktop Launcher", - "icon": "icon.png", - "version":"0.04", - "description": "Desktop style App Launcher with six apps per page - fast access if you have lots of apps installed.", - "readme": "README.md", - "tags": "tool,system,launcher", - "type":"launch", - "storage": [ - {"name":"dtlaunch.app.js","url":"app.js"}, - {"name":"dtlaunch.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "HRV", - "name": "Heart Rate Variability monitor", - "shortName":"HRV monitor", - "icon": "hrv.png", - "version":"0.04", - "description": "Heart Rate Variability monitor, see Readme for more info", - "tags": "", - "readme": "README.md", - "storage": [ - {"name":"HRV.app.js","url":"app.js"}, - {"name":"HRV.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "hardalarm", - "name": "Hard Alarm", - "shortName":"HardAlarm", - "icon": "app.png", - "version":"0.02", - "description": "Make sure you wake up! Count to the right number to turn off the alarm", - "tags": "tool,alarm,widget", - "storage": [ - {"name":"hardalarm.app.js","url":"app.js"}, - {"name":"hardalarm.boot.js","url":"boot.js"}, - {"name":"hardalarm.js","url":"hardalarm.js"}, - {"name":"hardalarm.img","url":"app-icon.js","evaluate":true}, - {"name":"hardalarm.wid.js","url":"widget.js"} - ], - "data": [ - {"name":"hardalarm.json"} - ] -}, -{ "id": "edisonsball", - "name": "Edison's Ball", - "shortName":"Edison's Ball", - "icon": "app-icon.png", - "version":"0.01", - "description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness", - "tags": "", - "readme": "README.md", - "storage": [ - {"name":"edisonsball.app.js","url":"app.js"}, - {"name":"edisonsball.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "hrrawexp", - "name": "HRM Data Exporter", - "shortName":"HRM Data Exporter", - "icon": "app-icon.png", - "version":"0.01", - "description": "export raw hrm signal data to a csv file", - "tags": "", - "readme": "README.md", - "interface": "interface.html", - "storage": [ - {"name":"hrrawexp.app.js","url":"app.js"}, - {"name":"hrrawexp.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "breath", - "name": "Breathing App", - "shortName":"Breathing App", - "icon": "app-icon.png", - "version":"0.01", - "description": "app to aid relaxation and train breath syncronicity using haptics and visualisation, also displays HR", - "tags": "tools,health", - "readme": "README.md", - "storage": [ - {"name":"breath.app.js","url":"app.js"}, - {"name":"breath.img","url":"app-icon.js","evaluate":true} - ], - "data": [ - {"name":"breath.settings.json","url":"settings.json"} - ] -}, -{ "id": "lazyclock", - "name": "Lazy Clock", - "icon": "lazyclock.png", - "version":"0.03", - "readme": "README.md", - "description": "Tells the time, roughly", - "tags": "clock", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"lazyclock.app.js","url":"lazyclock-app.js"}, - {"name":"lazyclock.img","url":"lazyclock-icon.js","evaluate":true} - ] -}, -{ "id": "astral", - "name": "Astral Clock", - "icon": "app-icon.png", - "version":"0.03", - "readme": "README.md", - "description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.", - "tags": "clock", - "type":"clock", - "storage": [ - {"name":"astral.app.js","url":"app.js"}, - {"name":"astral.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "alpinenav", - "name": "Alpine Nav", - "icon": "app-icon.png", - "version":"0.01", - "readme": "README.md", - "description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime", - "tags": "outdoors,gps", - "storage": [ - {"name":"alpinenav.app.js","url":"app.js"}, - {"name":"alpinenav.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "lifeclk", - "name": "Game of Life Clock", - "shortName":"Conway's Clock", - "icon": "app.png", - "version":"0.06", - "description": "Modification and clockification of Conway's Game of Life", - "tags": "clock", - "type" : "clock", - "readme": "README.md", - "storage": [ - {"name":"lifeclk.app.js","url":"app.min.js"}, - {"name":"lifeclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "speedalt", - "name": "GPS Adventure Sports", - "shortName":"GPS Adv Sport", - "icon": "app.png", - "version":"1.02", - "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", - "tags": "tool,outdoors", - "type":"app", - "allow_emulator":true, - "readme": "README.md", - "storage": [ - {"name":"speedalt.app.js","url":"app.js"}, - {"name":"speedalt.img","url":"app-icon.js","evaluate":true}, - {"name":"speedalt.settings.js","url":"settings.js"} - ], - "data": [ - {"name":"speedalt.json"} + { + "id": "isoclock", + "name": "ISO Compliant Clock Face", + "shortName": "ISO Clock", + "version": "0.02", + "description": "Tweaked fork of digiclock for ISO date and time", + "icon": "isoclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"isoclock.app.js","url":"isoclock.js"}, + {"name":"isoclock.img","url":"isoclock-icon.js","evaluate":true} ] -}, -{ "id": "de-stress", - "name": "De-Stress", - "shortName":"De-Stress", - "icon": "app.png", - "version":"0.02", - "description": "Simple haptic heartbeat", - "storage": [ - {"name":"de-stress.app.js","url":"app.js"}, - {"name":"de-stress.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "mclockplus", - "name": "Morph Clock+", - "shortName":"Morph Clock+", - "icon": "mclockplus.png", - "version":"0.02", - "description": "Morphing Clock with more readable seconds and date and additional stopwatch", - "tags": "clock", - "type": "clock", - "readme": "README.md", - "storage": [ - {"name":"mclockplus.app.js","url":"mclockplus.app.js"}, - {"name":"mclockplus.img","url":"mclockplus-icon.js","evaluate":true} - ] -}, -{ "id": "intervals", - "name": "Intervals App", - "shortName":"Intervals", - "icon": "intervals.png", - "version":"0.01", - "description": "Intervals for training. It is possible to configure work time and rest time and number of sets.", - "tags": "", - "storage": [ - {"name":"intervals.app.js","url":"intervals.app.js"}, - {"name":"intervals.img","url":"intervals-icon.js","evaluate":true} - ] -}, -{ "id": "planetarium", - "name": "Planetarium", - "shortName":"Planetarium", - "icon": "planetarium.png", - "readme": "README.md", - "version":"0.03", - "description": "Planetarium showing up to 500 stars using the watch location and time", - "tags": "", - "storage": [ - {"name":"planetarium.app.js","url":"planetarium.app.js"}, - {"name":"planetarium.data.csv","url":"planetarium.data.csv"}, - {"name":"planetarium.const.csv","url":"planetarium.const.csv"}, - {"name":"planetarium.extra.csv","url":"planetarium.extra.csv"}, - {"name":"planetarium.settings.js","url":"settings.js"}, - {"name":"planetarium.img","url":"planetarium-icon.js","evaluate":true} - ], - "data":[ - {"name":"planetarium.json"} - ] -}, -{ "id": "tapelauncher", - "name": "Tape Launcher", - "icon": "icon.png", - "version":"0.02", - "description": "An App launcher, icons displayed in a horizontal tape, swipe or use buttons", - "readme": "README.md", - "tags": "tool,system,launcher", - "type":"launch", - "storage": [ - {"name":"tapelauncher.app.js","url":"app.js"}, - {"name":"tapelauncher.img","url":"icon.js","evaluate":true} - ] -}, -{ "id": "oblique", - "name": "Oblique Strategies", - "icon": "eno.png", - "version": "0.01", - "description": "Oblique Strategies for creativity. Copied from Brian Eno.", - "tags": "tool", - "storage": [ - {"name":"oblique.app.js","url":"app.js"}, - {"name":"oblique.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "testuserinput", - "name": "Test User Input", - "shortName":"Test User Input", - "icon": "app.png", - "version":"0.06", - "description": "App to test the bangle.js input interface. It displays the user action in text, circle buttons or on/off switch UI elements.", - "readme": "README.md", - "tags": "input,interface,buttons,touch,UI", - "storage": [ - {"name":"testuserinput.app.js","url":"app.js"}, - {"name":"testuserinput.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "gpssetup", - "name": "GPS Setup", - "shortName":"GPS Setup", - "icon": "gpssetup.png", - "version":"0.02", - "description": "Configure the GPS power options and store them in the GPS nvram", - "tags": "gps,tools,outdoors,bno2", - "readme": "README.md", - "storage": [ - {"name":"gpssetup","url":"gpssetup.js"}, - {"name":"gpssetup.settings.js","url":"settings.js"}, - {"name":"gpssetup.app.js","url":"app.js"}, - {"name":"gpssetup.img","url":"icon.js","evaluate":true} - ], - "data": [ - {"name":"gpssetup.settings.json","url":"settings.json"} - ] -}, -{ "id": "walkersclock", - "name": "Walkers Clock", - "shortName":"Walkers Clock", - "icon": "walkersclock48.png", - "version":"0.04", - "description": "A large font watch, displays steps, can switch GPS on/off, displays grid reference", - "type":"clock", - "tags": "clock, gps, tools, outdoors", - "readme": "README.md", - "storage": [ - {"name":"walkersclock.app.js","url":"app.js"}, - {"name":"walkersclock.img","url":"icon.js","evaluate":true} - ] -}, -{ "id": "widgps", - "name": "GPS Widget", - "icon": "widget.png", - "version":"0.02", - "description": "Tiny widget to show the power on/off status of the GPS. Require firmware v2.08.167 or later", - "tags": "widget,gps", - "type":"widget", - "readme": "README.md", - "storage": [ - {"name":"widgps.wid.js","url":"widget.js"} - ] -}, -{ "id": "widhrt", - "name": "HRM Widget", - "icon": "widget.png", - "version":"0.02", - "description": "Tiny widget to show the power on/off status of the Heart Rate Monitor. Requires firmware v2.08.167 or later", - "tags": "widget, hrm", - "type":"widget", - "readme": "README.md", - "storage": [ - {"name":"widhrt.wid.js","url":"widget.js"} - ] -}, -{ "id": "countdowntimer", - "name" : "Countdown Timer", - "icon": "countdowntimer.png", - "version": "0.01", - "description": "A simple countdown timer with a focus on usability", - "tags": "timer, tool", - "readme": "README.md", - "storage": [ - {"name": "countdowntimer.app.js", "url": "countdowntimer.js"}, - {"name": "countdowntimer.img", "url": "countdowntimer-icon.js", "evaluate": true} - ] -}, -{ "id": "helloworld", - "name": "hello, world!", - "shortName":"hello world", - "icon": "app.png", - "version":"0.02", - "description": "A cross cultural hello world!/hola mundo! app with colors and languages", - "readme": "README.md", - "tags": "input,interface,buttons,touch", - "storage": [ - {"name":"helloworld.app.js","url":"app.js"}, - {"name":"helloworld.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "widcom", - "name": "Compass Widget", - "icon": "widget.png", - "version":"0.01", - "description": "Tiny widget to show the power on/off status of the Compass. Requires firmware v2.08.167 or later", - "tags": "widget, compass", - "type":"widget", - "readme": "README.md", - "storage": [ - {"name":"widcom.wid.js","url":"widget.js"} - ] -}, -{ "id": "arrow", - "name": "Arrow Compass", - "icon": "arrow.png", - "type":"app", - "version":"0.04", - "description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass", - "tags": "tool,outdoors", - "readme": "README.md", - "storage": [ - {"name":"arrow.app.js","url":"app.js"}, - {"name":"arrow.img","url":"icon.js","evaluate":true} - ] -}, -{ "id": "waypointer", - "name": "Way Pointer", - "icon": "waypointer.png", - "version":"0.01", - "description": "Navigate to a waypoint using the GPS for bearing and compass to point way, uses the same waypoint interface as GPS Navigation", - "tags": "tool,outdoors,gps", - "readme": "README.md", - "interface":"waypoints.html", - "storage": [ - {"name":"waypointer.app.js","url":"app.js"}, - {"name":"waypointer.img","url":"icon.js","evaluate":true} - ], - "data": [ - {"name":"waypoints.json","url":"waypoints.json"} - ] -}, -{ "id": "color_catalog", - "name": "Colors Catalog", - "shortName":"Colors Catalog", - "icon": "app.png", - "version":"0.01", - "description": "Displays RGB565 and RGB888 colors, its name and code in screen.", - "readme": "README.md", - "tags": "Color,input,buttons,touch,UI,bno2", - "storage": [ - {"name":"color_catalog.app.js","url":"app.js"}, - {"name":"color_catalog.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "UI4swatch", - "name": "UI 4 swatch", - "shortName":"UI 4 swatch", - "icon": "app.png", - "version":"0.01", - "description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.", - "readme": "README.md", - "tags": "Color, input,buttons,touch,UI", - "storage": [ - {"name":"UI4swatch.app.js","url":"app.js"}, - {"name":"UI4swatch.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "simplest", - "name": "Simplest Clock", - "icon": "simplest.png", - "version":"0.02", - "description": "The simplest working clock, acts as a tutorial piece", - "tags": "clock", - "type":"clock", - "readme": "README.md", - "storage": [ - {"name":"simplest.app.js","url":"app.js"}, - {"name":"simplest.img","url":"icon.js","evaluate":true} - ] -}, -{ "id": "stepo", - "name": "Stepometer Clock", - "icon": "stepo.png", - "version":"0.03", - "description": "A large font watch, displays step count in a doughnut guage and warns of low battery, requires one of the steps widgets to be installed", - "tags": "clock", - "type":"clock", - "readme": "README.md", - "storage": [ - {"name":"stepo.app.js","url":"app.js"}, - {"name":"stepo.img","url":"icon.js","evaluate":true} - ] -}, -{ "id": "gbmusic", - "name": "Gadgetbridge Music Controls", - "shortName":"Music Controls", - "icon": "icon.png", - "version":"0.05", - "description": "Control the music on your Gadgetbridge-connected phone", - "tags": "tools,bluetooth,gadgetbridge,music", - "type":"app", - "allow_emulator": false, - "readme": "README.md", - "storage": [ - {"name":"gbmusic.app.js","url":"app.js"}, - {"name":"gbmusic.settings.js","url":"settings.js"}, - {"name":"gbmusic.wid.js","url":"widget.js"}, - {"name":"gbmusic.img","url":"icon.js","evaluate":true} - ], - "data": [ - {"name":"gbmusic.json"}, - {"name":"gbmusic.load.json"} - ] -}, -{ - "id": "battleship", - "name":"Battleship", - "icon":"battleship-icon.png", - "version": "0.01", - "readme": "README.md", - "description": "The classic game of battleship", - "tags": "game", - "allow_emulator": true, - "storage": [ - { - "name": "battleship.app.js", - "url": "battleship.js" - }, - { - "name": "battleship.img", - "url": "battleship-icon.js", - "evaluate": true - } - ] -}, -{ "id": "kitchen", - "name": "Kitchen Combo", - "icon": "kitchen.png", - "version":"0.13", - "description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later", - "tags": "tool,outdoors,gps", - "type":"clock", - "readme": "README.md", - "interface":"waypoints.html", - "storage": [ - {"name":"kitchen.app.js","url":"kitchen.app.js"}, - {"name":"stepo2.kit.js","url":"stepo2.kit.js"}, - {"name":"swatch.kit.js","url":"swatch.kit.js"}, - {"name":"gps.kit.js","url":"gps.kit.js"}, - {"name":"compass.kit.js","url":"compass.kit.js"}, - {"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true} - ], - "data": [ - {"name":"waypoints.json","url":"waypoints.json"} - ] -}, -{ "id": "banglebridge", - "name": "BangleBridge", - "shortName":"BangleBridge", + }, + { + "id": "gpstimeserver", + "name": "GPS Time Server", + "version": "0.01", + "description": "A widget which automatically starts the GPS and turns Bangle.js into a Bluetooth time server.", "icon": "widget.png", - "version":"0.01", - "description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App", - "tags": "widget", "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"gpstimeserver.wid.js","url":"widget.js"} + ] + }, + { + "id": "tilthydro", + "name": "Tilt Hydrometer Display", + "shortName": "Tilt Hydro", + "version": "0.01", + "description": "A display for the [Tilt Hydrometer](https://tilthydrometer.com/) - [more info here](http://www.espruino.com/Tilt+Hydrometer+Display)", + "icon": "app.png", + "tags": "tools,bluetooth", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"tilthydro.app.js","url":"app.js"}, + {"name":"tilthydro.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "supmariodark", + "name": "Super mario clock night mode", + "shortName": "supmariodark", + "version": "0.01", + "description": "Super mario clock in night mode", + "icon": "supmariodark.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"supmariodark.app.js","url":"supmariodark.js"}, + {"name":"supmariodark.img","url":"supmariodark-icon.js","evaluate":true}, + {"name":"supmario30x24.bin","url":"supmario30x24.bin.js"}, + {"name":"supmario30x24.wdt","url":"supmario30x24.wdt.js"}, + {"name":"banner-up.img","url":"banner-up.js","evaluate":true}, + {"name":"banner-down.img","url":"banner-down.js","evaluate":true}, + {"name":"brick2.img","url":"brick2.js","evaluate":true}, + {"name":"enemy.img","url":"enemy.js","evaluate":true}, + {"name":"flower.img","url":"flower.js","evaluate":true}, + {"name":"flower_b.img","url":"flower_b.js","evaluate":true}, + {"name":"mario_wh.img","url":"mario_wh.js","evaluate":true}, + {"name":"pipe.img","url":"pipe.js","evaluate":true} + ] + }, + { + "id": "gmeter", + "name": "G-Meter", + "shortName": "G-Meter", + "version": "0.01", + "description": "Simple G-Meter", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"gmeter.app.js","url":"app.js"}, + {"name":"gmeter.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "dtlaunch", + "name": "Desktop Launcher", + "version": "0.04", + "description": "Desktop style App Launcher with six apps per page - fast access if you have lots of apps installed.", + "icon": "icon.png", + "type": "launch", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"dtlaunch.app.js","url":"app.js"}, + {"name":"dtlaunch.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "HRV", + "name": "Heart Rate Variability monitor", + "shortName": "HRV monitor", + "version": "0.04", + "description": "Heart Rate Variability monitor, see Readme for more info", + "icon": "hrv.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"HRV.app.js","url":"app.js"}, + {"name":"HRV.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "hardalarm", + "name": "Hard Alarm", + "shortName": "HardAlarm", + "version": "0.02", + "description": "Make sure you wake up! Count to the right number to turn off the alarm", + "icon": "app.png", + "tags": "tool,alarm,widget", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"hardalarm.app.js","url":"app.js"}, + {"name":"hardalarm.boot.js","url":"boot.js"}, + {"name":"hardalarm.js","url":"hardalarm.js"}, + {"name":"hardalarm.img","url":"app-icon.js","evaluate":true}, + {"name":"hardalarm.wid.js","url":"widget.js"} + ], + "data": [{"name":"hardalarm.json"}] + }, + { + "id": "edisonsball", + "name": "Edison's Ball", + "shortName": "Edison's Ball", + "version": "0.01", + "description": "Hypnagogia/Micro-Sleep alarm for experimental use in exploring sleep transition and combating drowsiness", + "icon": "app-icon.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"edisonsball.app.js","url":"app.js"}, + {"name":"edisonsball.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "hrrawexp", + "name": "HRM Data Exporter", + "shortName": "HRM Data Exporter", + "version": "0.01", + "description": "export raw hrm signal data to a csv file", + "icon": "app-icon.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"hrrawexp.app.js","url":"app.js"}, + {"name":"hrrawexp.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "breath", + "name": "Breathing App", + "shortName": "Breathing App", + "version": "0.01", + "description": "app to aid relaxation and train breath syncronicity using haptics and visualisation, also displays HR", + "icon": "app-icon.png", + "tags": "tools,health", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"breath.app.js","url":"app.js"}, + {"name":"breath.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"breath.settings.json","url":"settings.json"}] + }, + { + "id": "lazyclock", + "name": "Lazy Clock", + "version": "0.03", + "description": "Tells the time, roughly", + "icon": "lazyclock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"lazyclock.app.js","url":"lazyclock-app.js"}, + {"name":"lazyclock.img","url":"lazyclock-icon.js","evaluate":true} + ] + }, + { + "id": "astral", + "name": "Astral Clock", + "version": "0.03", + "description": "Clock that calculates and displays Alt Az positions of all planets, Sun as well as several other astronomy targets (customizable) and current Moon phase. Coordinates are calculated by GPS & time and onscreen compass assists orienting. See Readme before using.", + "icon": "app-icon.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"astral.app.js","url":"app.js"}, + {"name":"astral.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "alpinenav", + "name": "Alpine Nav", + "version": "0.01", + "description": "App that performs GPS monitoring to track and display position relative to a given origin in realtime", + "icon": "app-icon.png", + "tags": "outdoors,gps", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"alpinenav.app.js","url":"app.js"}, + {"name":"alpinenav.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "lifeclk", + "name": "Game of Life Clock", + "shortName": "Conway's Clock", + "version": "0.06", + "description": "Modification and clockification of Conway's Game of Life", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"lifeclk.app.js","url":"app.min.js"}, + {"name":"lifeclk.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "speedalt", + "name": "GPS Adventure Sports", + "shortName": "GPS Adv Sport", + "version": "1.02", + "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", + "icon": "app.png", + "type": "app", + "tags": "tool,outdoors", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"speedalt.app.js","url":"app.js"}, + {"name":"speedalt.img","url":"app-icon.js","evaluate":true}, + {"name":"speedalt.settings.js","url":"settings.js"} + ], + "data": [{"name":"speedalt.json"}] + }, + { + "id": "de-stress", + "name": "De-Stress", + "shortName": "De-Stress", + "version": "0.02", + "description": "Simple haptic heartbeat", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"de-stress.app.js","url":"app.js"}, + {"name":"de-stress.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "mclockplus", + "name": "Morph Clock+", + "shortName": "Morph Clock+", + "version": "0.02", + "description": "Morphing Clock with more readable seconds and date and additional stopwatch", + "icon": "mclockplus.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"mclockplus.app.js","url":"mclockplus.app.js"}, + {"name":"mclockplus.img","url":"mclockplus-icon.js","evaluate":true} + ] + }, + { + "id": "intervals", + "name": "Intervals App", + "shortName": "Intervals", + "version": "0.01", + "description": "Intervals for training. It is possible to configure work time and rest time and number of sets.", + "icon": "intervals.png", + "tags": "", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"intervals.app.js","url":"intervals.app.js"}, + {"name":"intervals.img","url":"intervals-icon.js","evaluate":true} + ] + }, + { + "id": "planetarium", + "name": "Planetarium", + "shortName": "Planetarium", + "version": "0.03", + "description": "Planetarium showing up to 500 stars using the watch location and time", + "icon": "planetarium.png", + "tags": "", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"planetarium.app.js","url":"planetarium.app.js"}, + {"name":"planetarium.data.csv","url":"planetarium.data.csv"}, + {"name":"planetarium.const.csv","url":"planetarium.const.csv"}, + {"name":"planetarium.extra.csv","url":"planetarium.extra.csv"}, + {"name":"planetarium.settings.js","url":"settings.js"}, + {"name":"planetarium.img","url":"planetarium-icon.js","evaluate":true} + ], + "data": [{"name":"planetarium.json"}] + }, + { + "id": "tapelauncher", + "name": "Tape Launcher", + "version": "0.02", + "description": "An App launcher, icons displayed in a horizontal tape, swipe or use buttons", + "icon": "icon.png", + "type": "launch", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"tapelauncher.app.js","url":"app.js"}, + {"name":"tapelauncher.img","url":"icon.js","evaluate":true} + ] + }, + { + "id": "oblique", + "name": "Oblique Strategies", + "version": "0.01", + "description": "Oblique Strategies for creativity. Copied from Brian Eno.", + "icon": "eno.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"oblique.app.js","url":"app.js"}, + {"name":"oblique.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "testuserinput", + "name": "Test User Input", + "shortName": "Test User Input", + "version": "0.06", + "description": "App to test the bangle.js input interface. It displays the user action in text, circle buttons or on/off switch UI elements.", + "icon": "app.png", + "tags": "input,interface,buttons,touch,UI", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"testuserinput.app.js","url":"app.js"}, + {"name":"testuserinput.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "gpssetup", + "name": "GPS Setup", + "shortName": "GPS Setup", + "version": "0.02", + "description": "Configure the GPS power options and store them in the GPS nvram", + "icon": "gpssetup.png", + "tags": "gps,tools,outdoors", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"gpssetup","url":"gpssetup.js"}, + {"name":"gpssetup.settings.js","url":"settings.js"}, + {"name":"gpssetup.app.js","url":"app.js"}, + {"name":"gpssetup.img","url":"icon.js","evaluate":true} + ], + "data": [{"name":"gpssetup.settings.json","url":"settings.json"}] + }, + { + "id": "walkersclock", + "name": "Walkers Clock", + "shortName": "Walkers Clock", + "version": "0.04", + "description": "A large font watch, displays steps, can switch GPS on/off, displays grid reference", + "icon": "walkersclock48.png", + "type": "clock", + "tags": "clock,gps,tools,outdoors", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"walkersclock.app.js","url":"app.js"}, + {"name":"walkersclock.img","url":"icon.js","evaluate":true} + ] + }, + { + "id": "widgps", + "name": "GPS Widget", + "version": "0.02", + "description": "Tiny widget to show the power on/off status of the GPS. Require firmware v2.08.167 or later", + "icon": "widget.png", + "type": "widget", + "tags": "widget,gps", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"widgps.wid.js","url":"widget.js"} + ] + }, + { + "id": "widhrt", + "name": "HRM Widget", + "version": "0.02", + "description": "Tiny widget to show the power on/off status of the Heart Rate Monitor. Requires firmware v2.08.167 or later", + "icon": "widget.png", + "type": "widget", + "tags": "widget,hrm", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"widhrt.wid.js","url":"widget.js"} + ] + }, + { + "id": "countdowntimer", + "name": "Countdown Timer", + "version": "0.01", + "description": "A simple countdown timer with a focus on usability", + "icon": "countdowntimer.png", + "tags": "timer,tool", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"countdowntimer.app.js","url":"countdowntimer.js"}, + {"name":"countdowntimer.img","url":"countdowntimer-icon.js","evaluate":true} + ] + }, + { + "id": "helloworld", + "name": "hello, world!", + "shortName": "hello world", + "version": "0.02", + "description": "A cross cultural hello world!/hola mundo! app with colors and languages", + "icon": "app.png", + "tags": "input,interface,buttons,touch", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"helloworld.app.js","url":"app.js"}, + {"name":"helloworld.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "widcom", + "name": "Compass Widget", + "version": "0.01", + "description": "Tiny widget to show the power on/off status of the Compass. Requires firmware v2.08.167 or later", + "icon": "widget.png", + "type": "widget", + "tags": "widget,compass", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"widcom.wid.js","url":"widget.js"} + ] + }, + { + "id": "arrow", + "name": "Arrow Compass", + "version": "0.04", + "description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass", + "icon": "arrow.png", + "type": "app", + "tags": "tool,outdoors", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"arrow.app.js","url":"app.js"}, + {"name":"arrow.img","url":"icon.js","evaluate":true} + ] + }, + { + "id": "waypointer", + "name": "Way Pointer", + "version": "0.01", + "description": "Navigate to a waypoint using the GPS for bearing and compass to point way, uses the same waypoint interface as GPS Navigation", + "icon": "waypointer.png", + "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], + "readme": "README.md", + "interface": "waypoints.html", + "storage": [ + {"name":"waypointer.app.js","url":"app.js"}, + {"name":"waypointer.img","url":"icon.js","evaluate":true} + ], + "data": [{"name":"waypoints.json","url":"waypoints.json"}] + }, + { + "id": "color_catalog", + "name": "Colors Catalog", + "shortName": "Colors Catalog", + "version": "0.01", + "description": "Displays RGB565 and RGB888 colors, its name and code in screen.", + "icon": "app.png", + "tags": "Color,input,buttons,touch,UI", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"color_catalog.app.js","url":"app.js"}, + {"name":"color_catalog.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "UI4swatch", + "name": "UI 4 swatch", + "shortName": "UI 4 swatch", + "version": "0.01", + "description": "A UI/UX for espruino smartwatches, displays dinamically calc. x,y coordinates.", + "icon": "app.png", + "tags": "Color,input,buttons,touch,UI", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"UI4swatch.app.js","url":"app.js"}, + {"name":"UI4swatch.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "simplest", + "name": "Simplest Clock", + "version": "0.02", + "description": "The simplest working clock, acts as a tutorial piece", + "icon": "simplest.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"simplest.app.js","url":"app.js"}, + {"name":"simplest.img","url":"icon.js","evaluate":true} + ] + }, + { + "id": "stepo", + "name": "Stepometer Clock", + "version": "0.03", + "description": "A large font watch, displays step count in a doughnut guage and warns of low battery, requires one of the steps widgets to be installed", + "icon": "stepo.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"stepo.app.js","url":"app.js"}, + {"name":"stepo.img","url":"icon.js","evaluate":true} + ] + }, + { + "id": "gbmusic", + "name": "Gadgetbridge Music Controls", + "shortName": "Music Controls", + "version": "0.05", + "description": "Control the music on your Gadgetbridge-connected phone", + "icon": "icon.png", + "type": "app", + "tags": "tools,bluetooth,gadgetbridge,music", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": false, + "storage": [ + {"name":"gbmusic.app.js","url":"app.js"}, + {"name":"gbmusic.settings.js","url":"settings.js"}, + {"name":"gbmusic.wid.js","url":"widget.js"}, + {"name":"gbmusic.img","url":"icon.js","evaluate":true} + ], + "data": [{"name":"gbmusic.json"},{"name":"gbmusic.load.json"}] + }, + { + "id": "battleship", + "name": "Battleship", + "version": "0.01", + "description": "The classic game of battleship", + "icon": "battleship-icon.png", + "tags": "game", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"battleship.app.js","url":"battleship.js"}, + {"name":"battleship.img","url":"battleship-icon.js","evaluate":true} + ] + }, + { + "id": "kitchen", + "name": "Kitchen Combo", + "version": "0.13", + "description": "Combination of the Stepo, Walkersclock, Arrow and Waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later", + "icon": "kitchen.png", + "type": "clock", + "tags": "tool,outdoors,gps", + "supports": ["BANGLEJS"], + "readme": "README.md", + "interface": "waypoints.html", + "storage": [ + {"name":"kitchen.app.js","url":"kitchen.app.js"}, + {"name":"stepo2.kit.js","url":"stepo2.kit.js"}, + {"name":"swatch.kit.js","url":"swatch.kit.js"}, + {"name":"gps.kit.js","url":"gps.kit.js"}, + {"name":"compass.kit.js","url":"compass.kit.js"}, + {"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true} + ], + "data": [{"name":"waypoints.json","url":"waypoints.json"}] + }, + { + "id": "banglebridge", + "name": "BangleBridge", + "shortName": "BangleBridge", + "version": "0.01", + "description": "Widget that allows Bangle Js to record pair and end data using Bluetooth Low Energy in combination with the BangleBridge Android App", + "icon": "widget.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ {"name":"banglebridge.wid.js","url":"widget.js"}, - {"name":"banglebridge.watch.img","url":"watch.img"}, - {"name":"banglebridge.heart.img","url":"heart.img"} + {"name":"banglebridge.watch.img","url":"watch.img"}, + {"name":"banglebridge.heart.img","url":"heart.img"} ] - }, -{ "id": "qmsched", - "name": "Quiet Mode Schedule and Widget", - "shortName":"Quiet Mode", - "icon": "app.png", - "version":"0.02", - "description": "Automatically turn Quiet Mode on or off at set times", - "readme": "README.md", - "tags": "tool,widget", - "storage": [ - {"name":"qmsched","url":"lib.js"}, - {"name":"qmsched.app.js","url":"app.js"}, - {"name":"qmsched.boot.js","url":"boot.js"}, - {"name":"qmsched.img","url":"icon.js","evaluate":true}, - {"name":"qmsched.wid.js","url":"widget.js"} - ], - "data": [ - {"name":"qmsched.json"} - ] -}, -{ - "id": "hourstrike", - "name": "Hour Strike", - "shortName": "Hour Strike", - "icon": "app-icon.png", - "version": "0.08", - "description": "Strike the clock on the hour. A great tool to remind you an hour has passed!", - "tags": "tool,alarm", - "readme": "README.md", - "storage": [ - {"name":"hourstrike.app.js","url":"app.js"}, - {"name":"hourstrike.boot.js","url":"boot.js"}, - {"name":"hourstrike.img","url":"app-icon.js","evaluate":true}, - {"name":"hourstrike.json","url":"hourstrike.json"} - ] -}, -{ "id": "whereworld", - "name": "Where in the World?", - "shortName" : "Where World", - "icon": "app.png", - "version": "0.01", - "description": "Shows your current location on the world map", - "tags": "gps", - "storage": [ - {"name":"whereworld.app.js","url":"app.js"}, - {"name":"whereworld.img","url":"app-icon.js","evaluate":true}, - {"name":"whereworld.worldmap","url":"worldmap"} - ] -}, -{ - "id": "omnitrix", - "name":"Omnitrix", - "icon":"omnitrix.png", - "version": "0.01", - "readme": "README.md", - "description": "An Omnitrix Showpiece", - "tags": "game", - "storage": [ - {"name":"omnitrix.app.js","url":"omnitrix.app.js"}, - {"name":"omnitrix.img","url":"omnitrix.icon.js","evaluate":true} - ] -}, -{ "id": "batclock", - "name": "Bat Clock", - "shortName":"Bat Clock", - "icon": "bat-clock.png", - "version":"0.02", - "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.", - "tags": "clock", - "type": "clock", - "readme": "README.md", - "storage": [ - {"name":"batclock.app.js","url":"bat-clock.app.js"}, - {"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true} - ] -}, -{ "id":"doztime", - "name":"Dozenal Time", - "shortName":"Dozenal Time", - "icon":"app.png", - "version":"0.04", - "description":"A dozenal Holocene calendar and dozenal diurnal clock", - "tags":"clock", - "type":"clock", - "allow_emulator":true, - "readme": "README.md", - "storage": [ - {"name":"doztime.app.js","url":"app.js"}, - {"name":"doztime.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id":"gbtwist", - "name":"Gadgetbridge Twist Control", - "shortName":"Twist Control", - "icon":"app.png", - "version":"0.01", - "description":"Shake your wrist to control your music app via Gadgetbridge", - "tags":"tools,bluetooth,gadgetbridge,music", - "type":"app", - "allow_emulator":false, - "readme": "README.md", - "storage": [ - {"name":"gbtwist.app.js","url":"app.js"}, - {"name":"gbtwist.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "thermom", - "name": "Thermometer", - "icon": "app.png", - "version":"0.02", - "description": "Displays the current temperature, updated every 20 seconds", - "tags": "tool", - "allow_emulator":true, - "storage": [ - {"name":"thermom.app.js","url":"app.js"}, - {"name":"thermom.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "mysticdock", - "name": "Mystic Dock", - "icon": "mystic-dock.png", - "version":"1.00", - "description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.", - "tags": "dock", - "type":"dock", - "readme": "README.md", - "storage": [ - {"name":"mysticdock.app.js","url":"mystic-dock-app.js"}, - {"name":"mysticdock.boot.js","url":"mystic-dock-boot.js"}, - {"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"}, - {"name":"mysticdock.img","url":"mystic-dock-icon.js","evaluate":true} - ] -}, -{ "id": "mysticclock", - "name": "Mystic Clock", - "icon": "mystic-clock.png", - "version":"1.01", - "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.", - "tags": "clock", - "type":"clock", - "readme": "README.md", - "allow_emulator":true, - "storage": [ - {"name":"mysticclock.app.js","url":"mystic-clock-app.js"}, - {"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"}, - {"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true} - ] -}, -{ "id": "hcclock", - "name": "Hi-Contrast Clock", - "icon": "hcclock-icon.png", - "version":"0.01", - "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", - "tags": "clock", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"hcclock.app.js","url":"hcclock.app.js"}, - {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true} - ] -}, -{ "id": "thermomF", - "name": "Fahrenheit Temp", - "icon": "thermf.png", - "version":"0.01", - "description": "A modification of the Thermometer App to display temprature in Fahrenheit", - "tags": "tool", - "storage": [ - {"name":"thermomF.app.js","url":"app.js"}, - {"name":"thermomF.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "nixie", - "name": "Nixie Clock", - "shortName":"Nixie", - "icon": "nixie.png", - "version":"0.01", - "description": "A nixie tube clock for both Bangle 1 and 2.", - "tags": "clock", - "type":"clock", - "readme": "README.md", - "storage": [ - {"name":"nixie.app.js","url":"app.js"}, - {"name":"nixie.img","url":"app-icon.js","evaluate":true}, - {"name":"m_vatch.js","url":"m_vatch.js"} - ] -}, -{ "id": "carcrazy", - "name": "Car Crazy", - "shortName":"Car Crazy", - "icon": "carcrash.png", - "version":"0.03", - "description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.", - "tags": "game", - "readme": "README.md", - "storage": [ - {"name":"carcrazy.app.js","url":"app.js"}, - {"name":"carcrazy.img","url":"app-icon.js","evaluate":true}, - {"name":"carcrazy.settings.js","url":"settings.js"} - ], - "data": [ - {"name":"app.json"} - ] -}, -{ "id": "shortcuts", - "name": "Shortcuts", - "shortName":"Shortcuts", - "icon": "app.png", - "version":"0.01", - "description": "Quickly load your favourite apps from (almost) any watch face.", - "tags": "tool", - "type": "bootloader", - "readme": "README.md", - "storage": [ - {"name":"shortcuts.boot.js","url":"boot.js"}, - {"name":"shortcuts.settings.js","url":"settings.js"} - ], - "data": [ - {"name":"shortcuts.json"} - ] -}, -{ "id": "vectorclock", - "name": "Vector Clock", - "icon": "app.png", - "version": "0.02", - "description": "A digital clock that uses the built-in vector font.", - "tags": "clock", - "type": "clock", - "allow_emulator": true, - "storage": [ - {"name":"vectorclock.app.js","url":"app.js"}, - {"name":"vectorclock.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "fd6fdetect", - "name": "fd6fdetect", - "shortName":"fd6fdetect", - "icon": "app.png", - "version":"0.1", - "description": "Allows you to see 0xFD6F beacons near you.", - "tags": "tool", - "storage": [ - {"name":"fd6fdetect.app.js","url":"app.js"}, - {"name":"fd6fdetect.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "choozi", - "name": "Choozi", - "icon": "app.png", - "version":"0.01", - "description": "Choose people or things at random using Bangle.js.", - "tags": "tool", - "readme": "README.md", - "allow_emulator":true, - "storage": [ - {"name":"choozi.app.js","url":"app.js"}, - {"name":"choozi.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "widclkbttm", - "name": "Digital clock (Bottom) widget", - "shortName":"Digital clock Bottom Widget", - "icon": "widclkbttm.png", - "version":"0.03", - "description": "Displays time in the bottom area.", - "readme": "README.md", - "tags": "widget", - "type": "widget", - "storage": [ - {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} - ] -}, -{ "id": "pastel", - "name": "Pastel Clock", - "shortName": "Pastel", - "icon": "pastel.png", - "version":"0.05", - "description": "A Configurable clock with custom fonts and background", - "tags": "clock,b2", - "type":"clock", - "readme": "README.md", - "storage": [ - {"name":"pastel.app.js","url":"pastel.app.js"}, - {"name":"pastel.img","url":"pastel.icon.js","evaluate":true}, - {"name":"pastel.settings.js","url":"pastel.settings.js"} - ], - "data": [ - {"name":"pastel.json"} - ] -}, -{ "id": "antonclk", - "name": "Anton Clock", - "icon": "app.png", - "version":"0.02", - "description": "A simple clock using the bold Anton font.", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"antonclk.app.js","url":"app.js"}, - {"name":"antonclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "waveclk", - "name": "Wave Clock", - "icon": "app.png", - "version":"0.02", - "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"waveclk.app.js","url":"app.js"}, - {"name":"waveclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "floralclk", - "name": "Floral Clock", - "icon": "app.png", - "version":"0.01", - "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"floralclk.app.js","url":"app.js"}, - {"name":"floralclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "score", - "name": "Score Tracker", - "icon": "score.app.png", - "version":"0.01", - "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.", - "readme": "README.md", - "tags": "b2", - "type": "app", - "storage": [ - {"name":"score.app.js","url":"score.app.js"}, - {"name":"score.settings.js","url":"score.settings.js"}, - {"name":"score.presets.json","url":"score.presets.json"}, - {"name":"score.img","url":"score.app-icon.js","evaluate":true} - ], - "data": [ - {"name":"score.json"} - ] -}, -{ "id": "menusmall", - "name": "Small Menus", - "icon": "app.png", - "version":"0.01", - "description": "Replace Bangle.js 2's menus with a version that contains smaller text", - "tags": "b2,bno1,system", - "type": "boot", - "storage": [ - {"name":"menusmall.boot.js","url":"boot.js"} - ] -}, -{ "id": "ffcniftya", - "name": "Nifty-A Clock", - "icon": "app.png", - "version":"0.01", - "description": "A nifty clock with time and date", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"ffcniftya.app.js","url":"app.js"}, - {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} - ] -} + }, + { + "id": "qmsched", + "name": "Quiet Mode Schedule and Widget", + "shortName": "Quiet Mode", + "version": "0.02", + "description": "Automatically turn Quiet Mode on or off at set times", + "icon": "app.png", + "tags": "tool,widget", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"qmsched","url":"lib.js"}, + {"name":"qmsched.app.js","url":"app.js"}, + {"name":"qmsched.boot.js","url":"boot.js"}, + {"name":"qmsched.img","url":"icon.js","evaluate":true}, + {"name":"qmsched.wid.js","url":"widget.js"} + ], + "data": [{"name":"qmsched.json"}] + }, + { + "id": "hourstrike", + "name": "Hour Strike", + "shortName": "Hour Strike", + "version": "0.08", + "description": "Strike the clock on the hour. A great tool to remind you an hour has passed!", + "icon": "app-icon.png", + "tags": "tool,alarm", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"hourstrike.app.js","url":"app.js"}, + {"name":"hourstrike.boot.js","url":"boot.js"}, + {"name":"hourstrike.img","url":"app-icon.js","evaluate":true}, + {"name":"hourstrike.json","url":"hourstrike.json"} + ] + }, + { + "id": "whereworld", + "name": "Where in the World?", + "shortName": "Where World", + "version": "0.01", + "description": "Shows your current location on the world map", + "icon": "app.png", + "tags": "gps", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"whereworld.app.js","url":"app.js"}, + {"name":"whereworld.img","url":"app-icon.js","evaluate":true}, + {"name":"whereworld.worldmap","url":"worldmap"} + ] + }, + { + "id": "omnitrix", + "name": "Omnitrix", + "version": "0.01", + "description": "An Omnitrix Showpiece", + "icon": "omnitrix.png", + "tags": "game", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"omnitrix.app.js","url":"omnitrix.app.js"}, + {"name":"omnitrix.img","url":"omnitrix.icon.js","evaluate":true} + ] + }, + { + "id": "batclock", + "name": "Bat Clock", + "shortName": "Bat Clock", + "version": "0.02", + "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.", + "icon": "bat-clock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"batclock.app.js","url":"bat-clock.app.js"}, + {"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true} + ] + }, + { + "id": "doztime", + "name": "Dozenal Time", + "shortName": "Dozenal Time", + "version": "0.04", + "description": "A dozenal Holocene calendar and dozenal diurnal clock", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"doztime.app.js","url":"app.js"}, + {"name":"doztime.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "gbtwist", + "name": "Gadgetbridge Twist Control", + "shortName": "Twist Control", + "version": "0.01", + "description": "Shake your wrist to control your music app via Gadgetbridge", + "icon": "app.png", + "type": "app", + "tags": "tools,bluetooth,gadgetbridge,music", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": false, + "storage": [ + {"name":"gbtwist.app.js","url":"app.js"}, + {"name":"gbtwist.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "thermom", + "name": "Thermometer", + "version": "0.02", + "description": "Displays the current temperature, updated every 20 seconds", + "icon": "app.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"thermom.app.js","url":"app.js"}, + {"name":"thermom.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "mysticdock", + "name": "Mystic Dock", + "version": "1.00", + "description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.", + "icon": "mystic-dock.png", + "type": "dock", + "tags": "dock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"mysticdock.app.js","url":"mystic-dock-app.js"}, + {"name":"mysticdock.boot.js","url":"mystic-dock-boot.js"}, + {"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"}, + {"name":"mysticdock.img","url":"mystic-dock-icon.js","evaluate":true} + ] + }, + { + "id": "mysticclock", + "name": "Mystic Clock", + "version": "1.01", + "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.", + "icon": "mystic-clock.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"mysticclock.app.js","url":"mystic-clock-app.js"}, + {"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"}, + {"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true} + ] + }, + { + "id": "hcclock", + "name": "Hi-Contrast Clock", + "version": "0.01", + "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", + "icon": "hcclock-icon.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"hcclock.app.js","url":"hcclock.app.js"}, + {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true} + ] + }, + { + "id": "thermomF", + "name": "Fahrenheit Temp", + "version": "0.01", + "description": "A modification of the Thermometer App to display temprature in Fahrenheit", + "icon": "thermf.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"thermomF.app.js","url":"app.js"}, + {"name":"thermomF.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "nixie", + "name": "Nixie Clock", + "shortName": "Nixie", + "version": "0.01", + "description": "A nixie tube clock for both Bangle 1 and 2.", + "icon": "nixie.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"nixie.app.js","url":"app.js"}, + {"name":"nixie.img","url":"app-icon.js","evaluate":true}, + {"name":"m_vatch.js","url":"m_vatch.js"} + ] + }, + { + "id": "carcrazy", + "name": "Car Crazy", + "shortName": "Car Crazy", + "version": "0.03", + "description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.", + "icon": "carcrash.png", + "tags": "game", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"carcrazy.app.js","url":"app.js"}, + {"name":"carcrazy.img","url":"app-icon.js","evaluate":true}, + {"name":"carcrazy.settings.js","url":"settings.js"} + ], + "data": [{"name":"app.json"}] + }, + { + "id": "shortcuts", + "name": "Shortcuts", + "shortName": "Shortcuts", + "version": "0.01", + "description": "Quickly load your favourite apps from (almost) any watch face.", + "icon": "app.png", + "type": "bootloader", + "tags": "tool", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"shortcuts.boot.js","url":"boot.js"}, + {"name":"shortcuts.settings.js","url":"settings.js"} + ], + "data": [{"name":"shortcuts.json"}] + }, + { + "id": "vectorclock", + "name": "Vector Clock", + "version": "0.02", + "description": "A digital clock that uses the built-in vector font.", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS"], + "allow_emulator": true, + "storage": [ + {"name":"vectorclock.app.js","url":"app.js"}, + {"name":"vectorclock.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "fd6fdetect", + "name": "fd6fdetect", + "shortName": "fd6fdetect", + "version": "0.1", + "description": "Allows you to see 0xFD6F beacons near you.", + "icon": "app.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"fd6fdetect.app.js","url":"app.js"}, + {"name":"fd6fdetect.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "choozi", + "name": "Choozi", + "version": "0.01", + "description": "Choose people or things at random using Bangle.js.", + "icon": "app.png", + "tags": "tool", + "supports": ["BANGLEJS"], + "readme": "README.md", + "allow_emulator": true, + "storage": [ + {"name":"choozi.app.js","url":"app.js"}, + {"name":"choozi.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "widclkbttm", + "name": "Digital clock (Bottom) widget", + "shortName": "Digital clock Bottom Widget", + "version": "0.03", + "description": "Displays time in the bottom area.", + "icon": "widclkbttm.png", + "type": "widget", + "tags": "widget", + "supports": ["BANGLEJS"], + "readme": "README.md", + "storage": [ + {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} + ] + }, + { + "id": "pastel", + "name": "Pastel Clock", + "shortName": "Pastel", + "version": "0.05", + "description": "A Configurable clock with custom fonts and background", + "icon": "pastel.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"pastel.app.js","url":"pastel.app.js"}, + {"name":"pastel.img","url":"pastel.icon.js","evaluate":true}, + {"name":"pastel.settings.js","url":"pastel.settings.js"} + ], + "data": [{"name":"pastel.json"}] + }, + { + "id": "antonclk", + "name": "Anton Clock", + "version": "0.02", + "description": "A simple clock using the bold Anton font.", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"antonclk.app.js","url":"app.js"}, + {"name":"antonclk.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "waveclk", + "name": "Wave Clock", + "version": "0.02", + "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"waveclk.app.js","url":"app.js"}, + {"name":"waveclk.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "floralclk", + "name": "Floral Clock", + "version": "0.01", + "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"floralclk.app.js","url":"app.js"}, + {"name":"floralclk.img","url":"app-icon.js","evaluate":true} + ] + }, + { + "id": "score", + "name": "Score Tracker", + "version": "0.01", + "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.", + "icon": "score.app.png", + "type": "app", + "tags": "", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"score.app.js","url":"score.app.js"}, + {"name":"score.settings.js","url":"score.settings.js"}, + {"name":"score.presets.json","url":"score.presets.json"}, + {"name":"score.img","url":"score.app-icon.js","evaluate":true} + ], + "data": [{"name":"score.json"}] + }, + { + "id": "menusmall", + "name": "Small Menus", + "version": "0.01", + "description": "Replace Bangle.js 2's menus with a version that contains smaller text", + "icon": "app.png", + "type": "boot", + "tags": "system", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"menusmall.boot.js","url":"boot.js"} + ] + }, + { + "id": "ffcniftya", + "name": "Nifty-A Clock", + "version": "0.01", + "description": "A nifty clock with time and date", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"ffcniftya.app.js","url":"app.js"}, + {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} + ] + } ] diff --git a/apps/_example_app/add_to_apps.json b/apps/_example_app/add_to_apps.json index 1585ab73d..cc28e1e93 100644 --- a/apps/_example_app/add_to_apps.json +++ b/apps/_example_app/add_to_apps.json @@ -2,13 +2,14 @@ { "id": "7chname", "name": "My app's human readable name", "shortName":"Short Name", - "icon": "app.png", "version":"0.01", "description": "A detailed description of my great app", + "icon": "app.png", "tags": "", + "supports" : ["BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"7chname.app.js","url":"app.js"}, {"name":"7chname.img","url":"app-icon.js","evaluate":true} ] -} \ No newline at end of file +} diff --git a/apps/_example_widget/add_to_apps.json b/apps/_example_widget/add_to_apps.json index 527c698a0..b55adce9d 100644 --- a/apps/_example_widget/add_to_apps.json +++ b/apps/_example_widget/add_to_apps.json @@ -2,11 +2,12 @@ { "id": "7chname", "name": "My widget's human readable name", "shortName":"Short Name", - "icon": "widget.png", "version":"0.01", "description": "A detailed description of my great widget", - "tags": "widget", + "icon": "widget.png", "type": "widget", + "tags": "widget", + "supports" : ["BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"7chname.wid.js","url":"widget.js"} diff --git a/bin/create_app_supports_field.js b/bin/create_app_supports_field.js new file mode 100644 index 000000000..6908591a5 --- /dev/null +++ b/bin/create_app_supports_field.js @@ -0,0 +1,83 @@ +#!/usr/bin/nodejs +/* Quick hack to add proper 'supports' field to apps.json +*/ + +var fs = require("fs"); + +var BASEDIR = __dirname+"/../"; + +var appsFile, apps; +try { + appsFile = fs.readFileSync(BASEDIR+"apps.json").toString(); +} catch (e) { + ERROR("apps.json not found"); +} +try{ + apps = JSON.parse(appsFile); +} catch (e) { + console.log(e); + var m = e.toString().match(/in JSON at position (\d+)/); + if (m) { + var char = parseInt(m[1]); + console.log("==============================================="); + console.log("LINE "+appsFile.substr(0,char).split("\n").length); + console.log("==============================================="); + console.log(appsFile.substr(char-10, 20)); + console.log("==============================================="); + } + console.log(m); + ERROR("apps.json not valid JSON"); + +} + +apps = apps.map((app,appIdx) => { + var tags = []; + if (app.tags) tags = app.tags.split(",").map(t=>t.trim()); + var supportsB1 = true; + var supportsB2 = false; + if (tags.includes("b2")) { + tags = tags.filter(x=>x!="b2"); + supportsB2 = true; + } + if (tags.includes("bno2")) { + tags = tags.filter(x=>x!="bno2"); + supportsB2 = false; + } + if (tags.includes("bno1")) { + tags = tags.filter(x=>x!="bno1"); + supportsB1 = false; + } + app.tags = tags.join(","); + app.supports = []; + if (supportsB1) app.supports.push("BANGLEJS"); + if (supportsB2) app.supports.push("BANGLEJS2"); + return app; +}); + +var KEY_ORDER = [ + "id","name","shortName","version","description","icon","type","tags","supports", + "dependencies", "readme", "custom", "customConnect", "interface", + "allow_emulator", "storage", "data", "sortorder" +]; + +var JS = JSON.stringify; +var json = "[\n "+apps.map(app=>{ + var keys = KEY_ORDER.filter(k=>k in app); + Object.keys(app).forEach(k=>{ + if (!KEY_ORDER.includes(k)) + throw new Error(`Key named ${k} not known!`); + }); + + + return "{\n "+keys.map(k=>{ + var js = JS(app[k]); + if (k=="storage") + js = "[\n "+app.storage.map(s=>JS(s)).join(",\n ")+"\n ]"; + return JS(k)+": "+js; + }).join(",\n ")+"\n }"; +}).join(",\n ")+"\n]\n"; + +//console.log(json); + +console.log("new apps.json written"); +fs.writeFileSync(BASEDIR+"apps.json", json); diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index b98aa9ef3..dbce9c855 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -51,7 +51,8 @@ try{ const APP_KEYS = [ 'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type', - 'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', 'allow_emulator', + 'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', + 'supports', 'allow_emulator', 'dependencies' ]; const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite']; @@ -81,6 +82,14 @@ apps.forEach((app,appIdx) => { if (!app.name) ERROR(`App ${app.id} has no name`); var isApp = !app.type || app.type=="app"; if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`); + if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`); + else { + app.supports.forEach(dev => { + if (!["BANGLEJS","BANGLEJS2"].includes(dev)) + ERROR(`App ${app.id} has unknown device in 'supports' field - ${dev}`); + }); + } + if (!app.version) WARN(`App ${app.id} has no version`); else { if (!fs.existsSync(appDir+"ChangeLog")) { From d1935c3860aecd5de7c7e2b6e11b1fe4d5919b0a Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 20 Oct 2021 15:20:25 +0100 Subject: [PATCH 0287/1062] Add filtering for Bangle.js 1 and 2 --- core | 2 +- css/main.css | 3 ++ index.html | 11 +++++ loader.js | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) diff --git a/core b/core index 0fd608f08..bc5b1284f 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 0fd608f085deff9b39f2db3559ecc88edb232aba +Subproject commit bc5b1284f41b0fcfdd264e1e2f12872e0b18c479 diff --git a/css/main.css b/css/main.css index 0dbe8da14..90b4ff280 100644 --- a/css/main.css +++ b/css/main.css @@ -23,6 +23,9 @@ .filter-nav { display: inline-block; } +.device-nav { + display: inline-block; +} .sort-nav { float: right; } diff --git a/index.html b/index.html index a5ae7bff0..0185f1bae 100644 --- a/index.html +++ b/index.html @@ -60,6 +60,17 @@
- + + From 6d72c4480dedb029b92bbac3dddf4e31d385765c Mon Sep 17 00:00:00 2001 From: qucchia Date: Wed, 27 Oct 2021 18:54:06 +0200 Subject: [PATCH 0414/1062] Add Q Alarm app --- apps.json | 19 +++ apps/qalarm/ChangeLog | 1 + apps/qalarm/app-icon.js | 1 + apps/qalarm/app.js | 278 +++++++++++++++++++++++++++++++++++++ apps/qalarm/app.png | Bin 0 -> 1531 bytes apps/qalarm/boot.js | 1 + apps/qalarm/qalarm.js | 157 +++++++++++++++++++++ apps/qalarm/qalarmcheck.js | 42 ++++++ apps/qalarm/widget.js | 22 +++ 9 files changed, 521 insertions(+) create mode 100644 apps/qalarm/ChangeLog create mode 100644 apps/qalarm/app-icon.js create mode 100644 apps/qalarm/app.js create mode 100644 apps/qalarm/app.png create mode 100644 apps/qalarm/boot.js create mode 100644 apps/qalarm/qalarm.js create mode 100644 apps/qalarm/qalarmcheck.js create mode 100644 apps/qalarm/widget.js diff --git a/apps.json b/apps.json index d1ebe5249..72de1a4ad 100644 --- a/apps.json +++ b/apps.json @@ -4166,5 +4166,24 @@ "storage": [ {"name":"swiperclocklaunch.boot.js","url":"boot.js"} ] + }, + { + "id": "qalarm", + "name": "Q Alarm and Timer", + "shortName": "Q Alarm", + "icon": "app.png", + "version": "0.01", + "description": "Alarm and timer app with days of week and 'hard' option.", + "tags": "tool,alarm,widget", + "supports": ["BANGLEJS", "BANGLEJS2"], + "storage": [ + { "name": "qalarm.app.js", "url": "app.js" }, + { "name": "qalarm.boot.js", "url": "boot.js" }, + { "name": "qalarm.js", "url": "qalarm.js" }, + { "name": "qalarmcheck.js", "url": "qalarmcheck.js" }, + { "name": "qalarm.img", "url": "app-icon.js", "evaluate": true }, + { "name": "qalarm.wid.js", "url": "widget.js" } + ], + "data": [{ "name": "qalarm.json" }] } ] diff --git a/apps/qalarm/ChangeLog b/apps/qalarm/ChangeLog new file mode 100644 index 000000000..4022f485c --- /dev/null +++ b/apps/qalarm/ChangeLog @@ -0,0 +1 @@ +0.01: First version! diff --git a/apps/qalarm/app-icon.js b/apps/qalarm/app-icon.js new file mode 100644 index 000000000..1a014b796 --- /dev/null +++ b/apps/qalarm/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("/wA/AH4A/AH4AF0WiF1wwtF73GB53MAAgkY4wABFqIxPEhQuXGB4vUFxYwMEpBpGBwouNGAwfFF5I1KF6ZQHGAwNLFx4wHF/4v/F/4v/AoYGDF6gaFF5AwHL7QuMBJQvWEpwvxBQ4uRGBAkJT4wuWGBIuIRjKRNF8wwXFy4wWFzIwU53NFzPN5wuR5/PGK4tBDYSNQ5wVCCwIzBAAQoIAAQWGSJ5HFDYYAQIYTCRKRIeBAAYmDAAZsJMCQAbeCAybFiQ0XFTQAIzgAGFcYvz0QAGF84wGF1AwFF1QA/AH4A/ADQ=")) diff --git a/apps/qalarm/app.js b/apps/qalarm/app.js new file mode 100644 index 000000000..4d27739cf --- /dev/null +++ b/apps/qalarm/app.js @@ -0,0 +1,278 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +let alarms = require("Storage").readJSON("qalarm.json", 1) || []; +/* +Alarm format: +{ + on : true, + t : 23400000, // Time of day since midnight in ms + msg : "Eat chocolate", // (optional) Must be set manually from the IDE + last : 0, // Last day of the month we alarmed on - so we don't alarm twice in one day! + rp : true, // Repeat + as : false, // Auto snooze + hard: true, // Whether the alarm will be like HardAlarm or not + timer : 300, // (optional) If set, this is a timer and it's the time in seconds + daysOfWeek: [true,true,true,true,true,true,true] // What days of the week the alarm is on. First item is Sunday, 2nd is Monday, etc. +} +*/ + +function formatTime(t) { + mins = 0 | (t / 60000) % 60; + hrs = 0 | (t / 3600000); + return hrs + ":" + ("0" + mins).substr(-2); +} + +function formatTimer(t) { + mins = 0 | (t / 60) % 60; + hrs = 0 | (t / 3600); + return hrs + ":" + ("0" + mins).substr(-2); +} + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +function showMainMenu() { + const menu = { + "": { title: "Alarms" }, + "New Alarm": () => showEditAlarmMenu(-1), + "New Timer": () => showEditTimerMenu(-1), + }; + alarms.forEach((alarm, idx) => { + let txt = + (alarm.timer ? "TIMER " : "ALARM ") + + (alarm.on ? "on " : "off ") + + (alarm.timer ? formatTimer(alarm.timer) : formatTime(alarm.t)); + menu[txt] = function () { + if (alarm.timer) showEditTimerMenu(idx); + else showEditAlarmMenu(idx); + }; + }); + menu["< Back"] = () => { + load(); + }; + + if (WIDGETS["qalarm"]) WIDGETS["qalarm"].reload(); + return E.showMenu(menu); +} + +function showEditAlarmMenu(alarmIndex, alarm) { + const newAlarm = alarmIndex < 0; + + if (!alarm) { + if (newAlarm) { + alarm = { + t: 43200000, + on: true, + rp: true, + as: false, + hard: false, + daysOfWeek: new Array(7).fill(true), + }; + } else { + alarm = Object.assign({}, alarms[alarmIndex]); // Copy object in case we don't save it + } + } + + let hrs = 0 | (alarm.t / 3600000); + let mins = 0 | (alarm.t / 60000) % 60; + let secs = 0 | (alarm.t / 1000) % 60; + + const menu = { + "": { title: alarm.msg ? alarm.msg : "Alarms" }, + Hours: { + value: hrs, + onchange: function (v) { + if (v < 0) v = 23; + if (v > 23) v = 0; + hrs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Minutes: { + value: mins, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + mins = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Seconds: { + value: secs, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + secs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Enabled: { + value: alarm.on, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (alarm.on = v), + }, + Repeat: { + value: alarm.rp, + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.rp = v), + }, + "Auto snooze": { + value: alarm.as, + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.as = v), + }, + Hard: { + value: alarm.hard, + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.hard = v), + }, + "Days of week": () => showDaysMenu(alarmIndex, getAlarm()), + }; + + function getAlarm() { + alarm.t = hrs * 3600000 + mins * 60000 + secs * 1000; + + alarm.last = 0; + // If alarm is for tomorrow not today (eg, in the past), set day + if (alarm.t < getCurrentTime()) alarm.last = new Date().getDate(); + + return alarm; + } + + menu["> Save"] = function () { + if (newAlarm) alarms.push(getAlarm()); + else alarms[alarmIndex] = getAlarm(); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + + if (!newAlarm) { + menu["> Delete"] = function () { + alarms.splice(alarmIndex, 1); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + } + menu["< Back"] = showMainMenu; + return E.showMenu(menu); +} + +function showDaysMenu(alarmIndex, alarm) { + const menu = { + "": { title: alarm.msg ? alarm.msg : "Alarms" }, + "< Back": () => showEditAlarmMenu(alarmIndex, alarm), + }; + + [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ].forEach((dayOfWeek, i) => { + menu[dayOfWeek] = { + value: alarm.daysOfWeek[i], + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.daysOfWeek[i] = v), + }; + }); + + return E.showMenu(menu); +} + +function showEditTimerMenu(timerIndex) { + var newAlarm = timerIndex < 0; + + let alarm; + if (newAlarm) { + alarm = { + timer: 300, + on: true, + rp: false, + as: false, + hard: false, + }; + } else { + alarm = alarms[timerIndex]; + } + + let hrs = 0 | (alarm.timer / 3600); + let mins = 0 | (alarm.timer / 60) % 60; + let secs = (0 | alarm.timer) % 60; + + const menu = { + "": { title: "Timer" }, + Hours: { + value: hrs, + onchange: function (v) { + if (v < 0) v = 23; + if (v > 23) v = 0; + hrs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Minutes: { + value: mins, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + mins = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Seconds: { + value: secs, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + secs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Enabled: { + value: alarm.on, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (alarm.on = v), + }, + Hard: { + value: alarm.hard, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (alarm.hard = v), + }, + }; + function getTimer() { + alarm.timer = hrs * 3600 + mins * 60 + secs; + alarm.t = (getCurrentTime() + alarm.timer * 1000) % 86400000; + return alarm; + } + menu["> Save"] = function () { + if (newAlarm) alarms.push(getTimer()); + else alarms[timerIndex] = getTimer(); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + if (!newAlarm) { + menu["> Delete"] = function () { + alarms.splice(timerIndex, 1); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + } + menu["< Back"] = showMainMenu; + return E.showMenu(menu); +} + +showMainMenu(); diff --git a/apps/qalarm/app.png b/apps/qalarm/app.png new file mode 100644 index 0000000000000000000000000000000000000000..14edf415069c0df4b92316a58719ad356affa395 GIT binary patch literal 1531 zcmVYq`X4DteAXTyb(wC0gV-yH-YTe z9r4F1jgQS2-fWq+5-C*qSHNTeP!AlFr5wu>OcT`&j4n!kSF8cp3|dz)A+!!*v$n=2 z71eHFw5Eyne3zFwj&K~P=OXh;7vQkSn{D;UO%@R4ux|1@dp_%`BcG>PkCDH!z0yt0 zBg$d#ViqX)*_$mY6bH}1OwV|K!1-yE@i;8@iQNOcuzK=&H0sWHb0F8VUaqGC@`C_~ z$I*ko0AZ>pJsxm=#FOs~*uQkM%zI;N%JyjKOvmK;D@kSmW06;gfmE>nF@0NP>%Y|{n+k(KIxO% z>>^k?v0Tx7h%ot+iwFJeRPKEVHo#DR zL|bcSVHmrC+MA+QIXZEiMp>9UREcp#1!(KA(HPkSz{CMjs~rE3i_DeBZ(Qilu-^jo z&~pjm<_S7}OOid`pLsZlj)9>Mo~7HqPafKmDQ6lfrvxA|VmFC~BtvII*BR*i6EY{k zx*8f^m&r&N(g3wnA(jM4VxHU$%r56vb_KL8-iTSdgW>aqVfTZ?dX-iOWY0nF667v} za|5glXa<}dI9>i=MjVV9FcM%Uz^sMHD5x3j5;hB-sM$U^3rI{+f} z5WN#3bx<=B%sPl9+*OhYh}pYh+6`)hTJGG+fPDpWSHZdh+5f<5FU7(tN3nbXR*APHKJN}X*n5s|1Nig;_LHaIE>+HIJeMzkHI9XJPi3OI#u zg!9=G!H2cb3+NBqS0)^%#6y`O=%P4D&7{qOk{{g3Sz02%OoB#j-002ovPDHLkV1h(U(Ov)m literal 0 HcmV?d00001 diff --git a/apps/qalarm/boot.js b/apps/qalarm/boot.js new file mode 100644 index 000000000..6713ad9e1 --- /dev/null +++ b/apps/qalarm/boot.js @@ -0,0 +1 @@ +eval(require("Storage").read("qalarmcheck.js")); diff --git a/apps/qalarm/qalarm.js b/apps/qalarm/qalarm.js new file mode 100644 index 000000000..38571987e --- /dev/null +++ b/apps/qalarm/qalarm.js @@ -0,0 +1,157 @@ +// This file shows the alarm + +print("Starting alarm"); + +function formatTime(t) { + let hrs = Math.floor(t / 3600000); + let mins = Math.round((t / 60000) % 60); + return hrs + ":" + ("0" + mins).substr(-2); +} + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +function getRandomInt(max) { + return Math.floor(Math.random() * Math.floor(max)); +} + +function getRandomFromRange( + lowerRangeMin, + lowerRangeMax, + higherRangeMin, + higherRangeMax +) { + let lowerRange = lowerRangeMax - lowerRangeMin; + let higherRange = higherRangeMax - higherRangeMin; + let fullRange = lowerRange + higherRange; + let randomNum = getRandomInt(fullRange); + if (randomNum <= lowerRangeMax - lowerRangeMin) { + return randomNum + lowerRangeMin; + } else { + return randomNum + (higherRangeMin - lowerRangeMax); + } +} + +function showNumberPicker(currentGuess, randomNum) { + if (currentGuess == randomNum) { + E.showMessage("" + currentGuess + "\n PRESS ENTER", "Get to " + randomNum); + } else { + E.showMessage("" + currentGuess, "Get to " + randomNum); + } +} + +function showPrompt(msg, buzzCount, alarm) { + E.showPrompt(msg, { + title: alarm.timer ? "TIMER!" : "ALARM!", + buttons: { Sleep: true, Ok: false }, // default is sleep so it'll come back in 10 mins + }).then(function (sleep) { + buzzCount = 0; + if (sleep) { + if (alarm.ohr === undefined) alarm.ohr = alarm.t; + alarm.t += 10 / 60; // 10 minutes + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + load(); + } else { + alarm.last = new Date().getDate(); + if (alarm.ohr !== undefined) { + alarm.t = alarm.ohr; + delete alarm.ohr; + } + if (!alarm.rp) alarm.on = false; + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + load(); + } + }); +} + +function showAlarm(alarm) { + if ((require("Storage").readJSON("setting.json", 1) || {}).quiet > 1) return; // total silence + let msg = formatTime(alarm.t); + let buzzCount = 20; + if (alarm.msg) msg += "\n" + alarm.msg + "!"; + + if (alarm.hard) { + let okClicked = false; + let currentGuess = 10; + let randomNum = getRandomFromRange(0, 7, 13, 20); + showNumberPicker(currentGuess, randomNum); + setWatch( + (o) => { + if (!okClicked && currentGuess < 20) { + currentGuess = currentGuess + 1; + showNumberPicker(currentGuess, randomNum); + } + }, + BTN1, + { repeat: true, edge: "rising" } + ); + + setWatch( + (o) => { + if (currentGuess == randomNum) { + okClicked = true; + showPrompt(msg, buzzCount, alarm); + } + }, + BTN2, + { repeat: true, edge: "rising" } + ); + + setWatch( + (o) => { + if (!okClicked && currentGuess > 0) { + currentGuess = currentGuess - 1; + showNumberPicker(currentGuess, randomNum); + } + }, + BTN3, + { repeat: true, edge: "rising" } + ); + } else { + showPrompt(msg, buzzCount, alarm); + } + + function buzz() { + Bangle.buzz(500).then(() => { + setTimeout(() => { + Bangle.buzz(500).then(function () { + setTimeout(() => { + Bangle.buzz(2000).then(function () { + if (buzzCount--) setTimeout(buzz, 2000); + else if (alarm.as) { + // auto-snooze + buzzCount = 20; + setTimeout(buzz, 600000); // 10 minutes + } + }); + }, 100); + }); + }, 100); + }); + } + buzz(); +} + +let time = new Date(); +let t = getCurrentTime(); +let alarms = require("Storage").readJSON("qalarm.json", 1) || []; + +let active = alarms.filter( + (alarm) => + alarm.on && + alarm.t < t && + alarm.last != time.getDate() && + (alarm.timer || alarm.daysOfWeek[time.getDay()]) +); + +print(active); + +if (active.length) { + showAlarm(active.sort((a, b) => a.t - b.t)[0]); +} diff --git a/apps/qalarm/qalarmcheck.js b/apps/qalarm/qalarmcheck.js new file mode 100644 index 000000000..de3db68ab --- /dev/null +++ b/apps/qalarm/qalarmcheck.js @@ -0,0 +1,42 @@ +/** + * This file checks for upcoming alarms and schedules qalarm.js to deal with them and itself to continue doing these checks. + */ + +print("Checking for alarms..."); + +clearInterval(); + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +let time = new Date(); +let t = getCurrentTime(); + +let nextAlarms = (require("Storage").readJSON("qalarm.json", 1) || []) + .filter( + (alarm) => + alarm.on && + alarm.t > t && + alarm.last != time.getDate() && + (alarm.timer || alarm.daysOfWeek[time.getDay()]) + ) + .sort((a, b) => a.t - b.t); + +if (nextAlarms[0]) { + print("Found alarm, scheduling...", nextAlarms[0].t - t); + setTimeout(() => { + load("qalarm.js"); + eval(require("Storage").read("qalarmcheck.js")); + }, 3600000 * (nextAlarms[0].t - t)); +} else { + print("No alarms found. Will re-check at midnight."); + setTimeout(() => { + eval(require("Storage").read("qalarmcheck.js")); + }, 86400000 - t); +} diff --git a/apps/qalarm/widget.js b/apps/qalarm/widget.js new file mode 100644 index 000000000..f80aff653 --- /dev/null +++ b/apps/qalarm/widget.js @@ -0,0 +1,22 @@ +WIDGETS["qalarm"] = { + area: "tl", + width: 0, + draw: function () { + if (this.width) + g.reset().drawImage( + atob( + "GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA" + ), + this.x, + this.y + ); + }, + reload: function () { + WIDGETS["qalarm"].width = ( + require("Storage").readJSON("qalarm.json", 1) || [] + ).some((alarm) => alarm.on) + ? 24 + : 0; + }, +}; +WIDGETS["qalarm"].reload(); From d10adb0684dfdf6c36c1e0ef81842d44f9c3668e Mon Sep 17 00:00:00 2001 From: David Skrabal Date: Wed, 27 Oct 2021 13:17:15 -0400 Subject: [PATCH 0415/1062] Revert "Customize" This reverts commit 9e404c625296d008314a547ff8e798767f65e426. --- README.md | 7 +++---- index.html | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a620121b8..d60d46fd3 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ -labarks' testing Bangle.js App Loader (and Apps) +Bangle.js App Loader (and Apps) ================================ [![Build Status](https://travis-ci.org/espruino/BangleApps.svg?branch=master)](https://travis-ci.org/espruino/BangleApps) -* Try my **development version** at [github.io](https://labarks.github.io/BangleApps/) -* Try the official **release version** at [banglejs.com/apps](https://banglejs.com/apps) -* Try the official **development version** at [github.io](https://espruino.github.io/BangleApps/) +* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps) +* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/) **All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By submitting code to this repository you confirm that you are happy with it being MIT licensed, diff --git a/index.html b/index.html index a5aa94e6a..0185f1bae 100644 --- a/index.html +++ b/index.html @@ -17,7 +17,7 @@ - labarks' testing Bangle.js App Loader + Bangle.js App Loader
- - + From 48ecdc5614b0eb9151dfcd0cdc530abf206d78cb Mon Sep 17 00:00:00 2001 From: David Skrabal Date: Wed, 27 Oct 2021 13:25:22 -0400 Subject: [PATCH 0416/1062] Update widver to 0.02 Display "Rel" (Release) instead of 'undefined' when there is no Build number. --- apps.json | 2 +- apps/widver/ChangeLog | 1 + apps/widver/widget.js | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 3a9925650..7338c438c 100644 --- a/apps.json +++ b/apps.json @@ -1614,7 +1614,7 @@ { "id": "widver", "name": "Firmware Version Widget", - "version": "0.01", + "version": "0.02", "description": "Display the version of the installed firmware in the top widget section.", "icon": "widget.png", "type": "widget", diff --git a/apps/widver/ChangeLog b/apps/widver/ChangeLog index adb5b038a..30581c35b 100644 --- a/apps/widver/ChangeLog +++ b/apps/widver/ChangeLog @@ -1 +1,2 @@ 0.01: New Widget +0.02: Display "Rel" (Release) instead of 'undefined' when there is no Build number. diff --git a/apps/widver/widget.js b/apps/widver/widget.js index 5da66444f..eb751ca23 100644 --- a/apps/widver/widget.js +++ b/apps/widver/widget.js @@ -2,6 +2,10 @@ (() => { var width = 28, ver = process.env.VERSION.split('.'); + + // Example: if ver is 2v11 instead of 2v10.142 write "Rel" (Release) instead of Build number + if(typeof ver[1] === 'undefined'){ver[1] = "Rel";} + function draw() { g.reset().setColor(0, 0.5, 1).setFont("6x8", 1); g.drawString(ver[0], this.x + 2, this.y + 4, true); From eefa209af467fbb993a59db57a7104eecb78ed07 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 12:14:02 +0100 Subject: [PATCH 0417/1062] support for 'supports:["DEVICEID"]` for files in apps, merge launchb2 and launch --- README.md | 3 ++ apps.json | 26 +++-------- apps/launch/ChangeLog | 3 +- apps/launch/{app.js => app-bangle1.js} | 0 .../app.js => launch/app-bangle2.js} | 0 apps/launchb2/ChangeLog | 4 -- apps/launchb2/app.png | Bin 899 -> 0 bytes bin/firmwaremaker.js | 4 +- bin/firmwaremaker_c.js | 5 ++- bin/sanitycheck.js | 18 +++++--- bin/thumbnailer.js | 41 ++++++++++++++---- core | 2 +- 12 files changed, 63 insertions(+), 43 deletions(-) rename apps/launch/{app.js => app-bangle1.js} (100%) rename apps/{launchb2/app.js => launch/app-bangle2.js} (100%) delete mode 100644 apps/launchb2/ChangeLog delete mode 100644 apps/launchb2/app.png diff --git a/README.md b/README.md index d60d46fd3..531114a34 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,9 @@ and which gives information about the app for the Launcher. // (eg it's evaluated as JS) "noOverwrite":true // if supplied, this file will not be overwritten if it // already exists + "supports": ["BANGLEJS2"]// if supplied, this file will ONLY be uploaded to the device + // types named in the array. This allows different versions of + // the app to be uploaded for different platforms }, ] "data": [ // list of files the app writes to diff --git a/apps.json b/apps.json index d1ebe5249..7593a4b63 100644 --- a/apps.json +++ b/apps.json @@ -94,31 +94,17 @@ }, { "id": "launch", - "name": "Launcher (Bangle.js 1 default)", + "name": "Launcher", "shortName": "Launcher", - "version": "0.07", - "description": "This is needed by Bangle.js 1.0 to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", + "version": "0.08", + "description": "This is needed to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", "icon": "app.png", "type": "launch", "tags": "tool,system,launcher", "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ - {"name":"launch.app.js","url":"app.js"} - ], - "sortorder": -10 - }, - { - "id": "launchb2", - "name": "Launcher (Bangle.js 2 default)", - "shortName": "Launcher", - "version": "0.04", - "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications.", - "icon": "app.png", - "type": "launch", - "tags": "tool,system,launcher", - "supports": ["BANGLEJS2"], - "storage": [ - {"name":"launchb2.app.js","url":"app.js"} + {"name":"launch.app.js","url":"app-bangle1.js","supports":["BANGLEJS"]}, + {"name":"launch.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]} ], "sortorder": -10 }, @@ -4114,7 +4100,7 @@ "description": "A touch based stop watch for Bangle JS 2", "icon": "stopwatch.png", "screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}], - "tags": "tools,app,b2", + "tags": "tools,app", "supports": ["BANGLEJS2"], "readme": "README.md", "storage": [ diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog index 09569d8da..bd8a9bd03 100644 --- a/apps/launch/ChangeLog +++ b/apps/launch/ChangeLog @@ -4,4 +4,5 @@ 0.04: Now displays widgets 0.05: Use g.theme for colours 0.06: Use Bangle.setUI for buttons -0.07: Theme colours fix \ No newline at end of file +0.07: Theme colours fix +0.08: Merge Bangle.js 1 and 2 launchers diff --git a/apps/launch/app.js b/apps/launch/app-bangle1.js similarity index 100% rename from apps/launch/app.js rename to apps/launch/app-bangle1.js diff --git a/apps/launchb2/app.js b/apps/launch/app-bangle2.js similarity index 100% rename from apps/launchb2/app.js rename to apps/launch/app-bangle2.js diff --git a/apps/launchb2/ChangeLog b/apps/launchb2/ChangeLog deleted file mode 100644 index a84587b7e..000000000 --- a/apps/launchb2/ChangeLog +++ /dev/null @@ -1,4 +0,0 @@ -0.01: New App! -0.02: Fix occasional missed image when scrolling up -0.03: Text wrapping, better font -0.04: Reduce code duplication and use new E.showScroller diff --git a/apps/launchb2/app.png b/apps/launchb2/app.png deleted file mode 100644 index 8b4e6caa2fe4720a32f492bd00c6e68751fabfbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 899 zcmV-}1AP36P)EZi8a;XNfX0KK)EG5t2&jmXSR!CR3JOvSF^w(VT@Oq6O4&}^ElAua zX|l63`{wtXcjoO3*x1-CYovub^cOBY?cfcO1>;+VoWa;>Pk;!SG_WW*Es5gZ1-`R@ z4t>oKYJ)f#xYia)IV)#&xZ*BHYck+F1K9eED0&f|F;Lm{VL;rb?(c)W{8d& zzrH4`vJJ?>K!fln4K4LjpAx19%+|UjgL{LF?3gt^suTXG8HN@KQv>tY{cLjA%Q)j? zIX0ma_G_?6n>drF(WulAaitj}A^+b$v5k$*zr})O^uogfX;+bphn_9#OZ}pdk^!&c zqrUVgUd3m%o}@|o!cmyJiIaP-*BlK-1F7-kNoN^X5h5L~tnN`_& zI3(jUhvcNZU>jbg3|-gg8hTDln@m+>N(dP^vh_T*8x8>Qb*ytvU&Y#;lz3_Z*tBLl zEghFFmS~QUfhzCr>E}|Fxr&yKOBoXz4vqjF zlzKwtw{idZM95W(Tbb#rO0Wkqaj6$F@MWZp>h=2o=nr;LTzCQiH!$$4i=w<5W87%F zs6NXOGI0OH6?#VB0k9%#HO2Wg(|z6FTj}`r1kmXWJk5wmGlUFGsuA7}JOW^&y9!O$ zkR=&S*XaHEp1^o_Mn#&D^ig6k6`9rJMEHEc@fMjg8GR Z=Px>?AD_-bQjY)t002ovPDHLkV1j2qp7Q_z diff --git a/bin/firmwaremaker.js b/bin/firmwaremaker.js index 4e22dd168..4bc2a70b2 100755 --- a/bin/firmwaremaker.js +++ b/bin/firmwaremaker.js @@ -12,6 +12,7 @@ var ROOTDIR = path.join(__dirname, '..'); var APPDIR = ROOTDIR+'/apps'; var APPJSON = ROOTDIR+'/apps.json'; var OUTFILE = ROOTDIR+'/firmware.js'; +var DEVICE = "BANGLEJS"; var APPS = [ // IDs of apps to install "boot","launch","mclock","setting", "about","alarm","widbat","widbt","welcome" @@ -61,7 +62,8 @@ Promise.all(APPS.map(appid => { if (app===undefined) throw new Error(`App ${appid} not found`); return AppInfo.getFiles(app, { fileGetter : fileGetter, - settings : SETTINGS + settings : SETTINGS, + device : { id : DEVICE } }).then(files => { appfiles = appfiles.concat(files); }); diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 15092ced7..7fb842755 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -29,7 +29,7 @@ if (DEVICE=="BANGLEJS") { } else if (DEVICE=="BANGLEJS2") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install - "boot","launchb2","s7clk","setting", + "boot","launch","s7clk","setting", "about","alarm","widlock","widbat","widbt","widid" ]; } else { @@ -132,7 +132,8 @@ Promise.all(APPS.map(appid => { if (app===undefined) throw new Error(`App ${appid} not found`); return AppInfo.getFiles(app, { fileGetter : fileGetter, - settings : SETTINGS + settings : SETTINGS, + device : { id : DEVICE } }).then(files => { appfiles = appfiles.concat(files); }); diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index dbce9c855..ef795871d 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -50,12 +50,12 @@ try{ } const APP_KEYS = [ - 'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type', + 'id', 'name', 'shortName', 'version', 'icon', 'screenshots', 'description', 'tags', 'type', 'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', - 'supports', 'allow_emulator', + 'supports', 'allow_emulator', 'dependencies' ]; -const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite']; +const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports']; const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate']; const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ]; @@ -107,6 +107,13 @@ apps.forEach((app,appIdx) => { if (!app.description) ERROR(`App ${app.id} has no description`); if (!app.icon) ERROR(`App ${app.id} has no icon`); if (!fs.existsSync(appDir+app.icon)) ERROR(`App ${app.id} icon doesn't exist`); + if (app.screenshots) { + if (!Array.isArray(app.screenshots)) ERROR(`App ${app.id} screenshots is not an array`); + app.screenshots.forEach(screenshot => { + if (!fs.existsSync(appDir+screenshot.url)) + ERROR(`App ${app.id} screenshot file ${screenshot.url} not found`); + }); + } if (app.readme && !fs.existsSync(appDir+app.readme)) ERROR(`App ${app.id} README file doesn't exist`); if (app.custom && !fs.existsSync(appDir+app.custom)) ERROR(`App ${app.id} custom HTML doesn't exist`); if (app.customConnect && !app.custom) ERROR(`App ${app.id} has customConnect but no customn HTML`); @@ -128,13 +135,14 @@ apps.forEach((app,appIdx) => { if (char) ERROR(`App ${app.id} storage file ${file.name} contains invalid character "${char[0]}"`) if (fileNames.includes(file.name)) ERROR(`App ${app.id} file ${file.name} is a duplicate`); - fileNames.push(file.name); + if (!file.supports) fileNames.push(file.name); // assume that there aren't duplicates if 'supports' is set allFiles.push({app: app.id, file: file.name}); if (file.url) if (!fs.existsSync(appDir+file.url)) ERROR(`App ${app.id} file ${file.url} doesn't exist`); if (!file.url && !file.content && !app.custom) ERROR(`App ${app.id} file ${file.name} has no contents`); var fileContents = ""; if (file.content) fileContents = file.content; if (file.url) fileContents = fs.readFileSync(appDir+file.url).toString(); + if (file.supports && !Array.isArray(file.supports)) ERROR(`App ${app.id} file ${file.name} supports field is not an array`); if (file.evaluate) { try { acorn.parse("("+fileContents+")"); @@ -165,7 +173,7 @@ apps.forEach((app,appIdx) => { } } for (const key in file) { - if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id}'s ${file.name} has unknown key ${key}`); + if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id} file ${file.name} has unknown key ${key}`); } }); let dataNames = []; diff --git a/bin/thumbnailer.js b/bin/thumbnailer.js index db658b01e..b6862741a 100755 --- a/bin/thumbnailer.js +++ b/bin/thumbnailer.js @@ -1,6 +1,11 @@ #!/usr/bin/node +/* +var EMULATOR = "banglejs2"; +var DEVICEID = "BANGLEJS2"; +*/ var EMULATOR = "banglejs1"; +var DEVICEID = "BANGLEJS"; var singleAppId; @@ -40,6 +45,10 @@ var apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json")); /* we factory reset ONCE, get this, then we can use it to reset state quickly for each new app */ var factoryFlashMemory = new Uint8Array(FLASH_SIZE); +// Log of messages from app +var appLog = ""; +// List of apps that errored +var erroredApps = []; jsRXCallback = function() {}; jsUpdateGfx = function() {}; @@ -49,6 +58,10 @@ function ERROR(s) { process.exit(1); } +function onConsoleOutput(txt) { + appLog += txt + "\n"; +} + function getThumbnail(appId, imageFn) { console.log("Thumbnail for "+appId); var app = apps.find(a=>a.id==appId); @@ -61,7 +74,10 @@ function getThumbnail(appId, imageFn) { fileGetter:function(url) { console.log(__dirname+"/"+url); return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); - }, settings : SETTINGS}).then(files => { + }, + settings : SETTINGS, + device : { id : DEVICEID } + }).then(files => { console.log("AppInfo returned");//, files); flashMemory.set(factoryFlashMemory); jsTransmitString("reset()\n"); @@ -69,6 +85,7 @@ function getThumbnail(appId, imageFn) { jsTransmitString("g.clear()\n"); var command = files.map(f=>f.cmd).join("\n")+"\n"; command += `load("${appId}.app.js")\n`; + appLog = ""; jsTransmitString(command); console.log("Done."); jsStopIdle(); @@ -79,6 +96,9 @@ function getThumbnail(appId, imageFn) { var firstPixel = rgba32[0]; var blankImage = rgba32.every(col=>col==firstPixel) + if (appLog.indexOf("Uncaught")>=0) + erroredApps.push( { id : app.id, log : appLog } ); + if (!blankImage) { var Jimp = require("jimp"); let image = new Jimp(GFX_WIDTH, GFX_HEIGHT, function (err, image) { @@ -113,20 +133,22 @@ setTimeout(function() { console.log("Ready!"); if (singleAppId) { - getThumbnail(singleAppId, "screenshots/"+singleAppId+".png") - + getThumbnail(singleAppId, "screenshots/"+singleAppId+"-"+EMULATOR+".png"); return; } - var appList = apps.filter(app => (!app.type || app.type=="clock") && !app.custom).map(app=>app.id); - // TODO: Work out about Bangle.js 1 or 2 + var appList = apps.filter(app => (!app.type || app.type=="clock") && !app.custom); + appList = appList.filter(app => !app.screenshots && app.supports.includes(DEVICEID)); + var promise = Promise.resolve(); - appList.forEach(appId => { + appList.forEach(app => { promise = promise.then(() => { - return getThumbnail(appId, "screenshots/"+appId+".png").then(ok => { + var imageFile = "screenshots/"+app.id+"-"+EMULATOR+".png"; + return getThumbnail(app.id, imageFile).then(ok => { screenshots.push({ - id : appId, - url : "screenshots/"+appId+".png" + id : app.id, + url : imageFile, + version: app.version }); }); }); @@ -135,6 +157,7 @@ setTimeout(function() { promise.then(function() { console.log("Complete!"); require("fs").writeFileSync("screenshots.json", JSON.stringify(screenshots,null,2)); + console.log("Errored Apps", erroredApps); }); diff --git a/core b/core index a7d82825d..70b49d8db 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit a7d82825d3a43e1da7919591ed6fa776f1f0545a +Subproject commit 70b49d8dbd2afa76f4485aadf679dc75e0a8b4ac From cbf69eeaa2adfac6ec5996cc6e4319be349fae92 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 12:14:14 +0100 Subject: [PATCH 0418/1062] fix lint errors --- apps/boot/bootupdate.js | 2 +- apps/gpstouch/gpstouch.app.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index dfd745de2..8ad61f763 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -182,7 +182,7 @@ if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill } return lines; };\n`; -}; +} delete Bangle.appRect; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares boot += `Bangle.appRect = ((y,w,h)=>({x:0,y:0,w:w,h:h,x2:w-1,y2:h-1}))(g.getWidth(),g.getHeight()); diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js index 0425cdc23..4e49dd1e5 100644 --- a/apps/gpstouch/gpstouch.app.js +++ b/apps/gpstouch/gpstouch.app.js @@ -24,7 +24,7 @@ function resetLastFix() { function processFix(fix) { last_fix.time = fix.time; log_debug(fix); - + if (fix.fix) { if (!last_fix.fix) { // we dont need to suppress this in quiet mode as it is user initiated @@ -78,7 +78,7 @@ function drawInfo() { g.setColor("#fff"); else g.setColor("#000"); - + g.drawString((infoData[infoMode].calc()), w/2, (3*(h-24)/4) + 24); } } @@ -196,7 +196,7 @@ function prevInfo() { } Bangle.on('swipe', dir => { - if (dir == 1) prevInfo() else nextInfo(); + if (dir == 1) prevInfo(); else nextInfo(); draw(); }); From 3c5de5ff701ebd71af4fffa0838b29af7b858e6b Mon Sep 17 00:00:00 2001 From: Victor Serain Date: Thu, 28 Oct 2021 14:45:29 +0200 Subject: [PATCH 0419/1062] fix: add semicolumn --- apps/gpstouch/gpstouch.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js index 0425cdc23..cac19cdef 100644 --- a/apps/gpstouch/gpstouch.app.js +++ b/apps/gpstouch/gpstouch.app.js @@ -196,7 +196,7 @@ function prevInfo() { } Bangle.on('swipe', dir => { - if (dir == 1) prevInfo() else nextInfo(); + if (dir == 1) prevInfo(); else nextInfo(); draw(); }); From 2e252814a85432e394088f10a6913ba49a6545f8 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 14:23:29 +0100 Subject: [PATCH 0420/1062] Add first draft of more flexible 'recorder' app for GPS/etc recording --- apps.json | 21 +- apps/recorder/ChangeLog | 1 + apps/recorder/README.md | 27 +++ apps/recorder/app-icon.js | 1 + apps/recorder/app-settings.json | 6 + apps/recorder/app.js | 406 ++++++++++++++++++++++++++++++++ apps/recorder/app.png | Bin 0 -> 1530 bytes apps/recorder/interface.html | 179 ++++++++++++++ apps/recorder/settings.js | 4 + apps/recorder/widget.js | 222 +++++++++++++++++ 10 files changed, 866 insertions(+), 1 deletion(-) create mode 100644 apps/recorder/ChangeLog create mode 100644 apps/recorder/README.md create mode 100644 apps/recorder/app-icon.js create mode 100644 apps/recorder/app-settings.json create mode 100644 apps/recorder/app.js create mode 100644 apps/recorder/app.png create mode 100644 apps/recorder/interface.html create mode 100644 apps/recorder/settings.js create mode 100644 apps/recorder/widget.js diff --git a/apps.json b/apps.json index 7593a4b63..8c7db5bda 100644 --- a/apps.json +++ b/apps.json @@ -631,6 +631,25 @@ ], "data": [{"name":"gpsrec.json"},{"wildcard":".gpsrc?","storageFile":true}] }, + { + "id": "recorder", + "name": "Recorder (BETA)", + "shortName": "Recorder", + "version": "0.01", + "description": "Record GPS position, heart rate and more in the background, then download to your PC.", + "icon": "app.png", + "tags": "tool,outdoors,gps,widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "interface": "interface.html", + "storage": [ + {"name":"recorder.app.js","url":"app.js"}, + {"name":"recorder.img","url":"app-icon.js","evaluate":true}, + {"name":"recorder.wid.js","url":"widget.js"}, + {"name":"recorder.settings.js","url":"settings.js"} + ], + "data": [{"name":"recorder.json"},{"wildcard":"recorder.log?.csv","storageFile":true}] + }, { "id": "gpsnav", "name": "GPS Navigation", @@ -1352,7 +1371,7 @@ "id": "assistedgps", "name": "Assisted GPS Update (AGPS)", "version": "0.01", - "description": "Downloads assisted GPS (AGPS) data to Bangle.js for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.", + "description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.", "icon": "app.png", "type": "RAM", "tags": "tool,outdoors,agps", diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/recorder/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/recorder/README.md b/apps/recorder/README.md new file mode 100644 index 000000000..ba53a99f2 --- /dev/null +++ b/apps/recorder/README.md @@ -0,0 +1,27 @@ +# Recorder + +![icon](app.png) + +This app allows you to record data every few seconds - it can run in background. + +Usually you'd record GPS (but this is not required). The data can later be exported as CSV, KML or GPX files via the Download button in the Bangle.js App Store entry for Recorder. + +## Usage + +First run the `Recorder` app, here you can configure what you want to record, how often, +and you can start and stop recordings. + +You can record + +* **Time** The current time +* **GPS** GPS Latitude, Longitude and Altitude +* **Steps** Steps counted by the step counter +* **HR** Heart rate + +**Note:** It is possible for other apps to record information using this app +as well. They need to define a `foobar.recorder.js` file - see the `getRecorders` +function in `widget.js` for more information. + +## Tips + +When recording GPS, it usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). There is a grey satellite symbol, which you will see turn red when you get an actual GPS Fix. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix. diff --git a/apps/recorder/app-icon.js b/apps/recorder/app-icon.js new file mode 100644 index 000000000..4181d2b12 --- /dev/null +++ b/apps/recorder/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cA///vPWvN8kvkuu14/s3OMjN0Kf4AQ2vaCB0Ftu3oARfgdt23AGsO2NaHQT+MB2XJCJ1MyXJsAQMgUky3JkARMjIRBpIRNvMl2VJlAQLldvtmSpN+CJcbt2ECJsBregggRBv4RRdJfbgEkyVLq3ACJ1Jq3dCBMKBYIRBpFW7d0CJUDsgRBhdbtvQCJHYgUTm1IgEttuwCI8GCIMSpMwA4MVEZPoCIUkaxj7BoQRPiQRCwARNpARByARNpMJCJyNBgKjBCJy1CCJ79BfZYNDCJoxBBoQnCABBVFN4IRJPIoRLV4sCpMgCJTbECJYKFCJUJBQsJfpoA/A")) diff --git a/apps/recorder/app-settings.json b/apps/recorder/app-settings.json new file mode 100644 index 000000000..4a3117a17 --- /dev/null +++ b/apps/recorder/app-settings.json @@ -0,0 +1,6 @@ +{ + "recording":false, + "file":"record.log0.csv", + "period":10, + "record" : ["gps"] +} diff --git a/apps/recorder/app.js b/apps/recorder/app.js new file mode 100644 index 000000000..9c8380c07 --- /dev/null +++ b/apps/recorder/app.js @@ -0,0 +1,406 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var settings; + +var osm; +try { // if it's installed, use the OpenStreetMap module + osm = require("openstmap"); +} catch (e) {} + +function loadSettings() { + settings = require("Storage").readJSON("recorder.json",1)||{}; + var changed = false; + if (!settings.file) { + changed = true; + settings.file = "record.log0.csv"; + } + if (!Array.isArray(settings.record)) { + settings.record = ["gps"]; + changed = true; + } + if (changed) + require("Storage").writeJSON("recorder.json", settings); +} +loadSettings(); + +function updateSettings() { + require("Storage").writeJSON("recorder.json", settings); + if (WIDGETS["recorder"]) + WIDGETS["recorder"].reload(); +} + +function getTrackNumber(filename) { + return parseInt(filename.match(/^record\.log(.*)\.csv$/)[1]||0); +} + +function showMainMenu() { + function boolFormat(v) { return v?"Yes":"No"; } + function menuRecord(id) { + return { + value: settings.record.includes(id), + format: boolFormat, + onchange: v => { + settings.recording = false; // stop recording if we change anything + settings.record = settings.record.filter(r=>r!=id); + if (v) settings.record.push(id); + updateSettings(); + } + }; + } + const mainmenu = { + '': { 'title': 'GPS Record' }, + '< Back': ()=>{load();}, + 'RECORD': { + value: !!settings.recording, + format: v=>v?"On":"Off", + onchange: v => { + setTimeout(function() { + E.showMenu(); + WIDGETS["recorder"].setRecording(v).then(function() { + print("Complete"); + loadSettings(); + print(settings.recording); + showMainMenu(); + }); + }, 1); + } + }, + 'File #': { + value: getTrackNumber(settings.file), + min: 0, + max: 99, + step: 1, + onchange: v => { + settings.recording = false; // stop recording if we change anything + settings.file = "record.log"+v+".csv"; + updateSettings(); + } + }, + 'View Tracks': ()=>{viewTracks();}, + 'Time Period': { + value: settings.period||10, + min: 1, + max: 120, + step: 1, + format: v=>v+"s", + onchange: v => { + settings.recording = false; // stop recording if we change anything + settings.period = v; + updateSettings(); + } + } + }; + var recorders = WIDGETS["recorder"].getRecorders(); + Object.keys(recorders).forEach(id=>{ + mainmenu["Log "+recorders[id]().name] = menuRecord(id); + }); + return E.showMenu(mainmenu); +} + + + +function viewTracks() { + const menu = { + '': { 'title': 'GPS Tracks' } + }; + var found = false; + require("Storage").list(/^record\.log.*\.csv$/,{sf:true}).forEach(filename=>{ + found = true; + menu["Track "+getTrackNumber(filename)] = ()=>viewTrack(filename,false); + }); + if (!found) + menu["No Tracks found"] = function(){}; + menu['< Back'] = () => { showMainMenu(); }; + return E.showMenu(menu); +} + +function getTrackInfo(filename) { + "ram" + var minLat = 90; + var maxLat = -90; + var minLong = 180; + var maxLong = -180; + var starttime, duration=0; + var f = require("Storage").open(filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + var fields, timeIdx, latIdx, lonIdx; + var nl = 0, c, n; + if (l!==undefined) { + fields = l.trim().split(","); + timeIdx = fields.indexOf("Time"); + latIdx = fields.indexOf("Latitude"); + lonIdx = fields.indexOf("Longitude"); + l = f.readLine(f); + } + if (l!==undefined) { + c = l.split(","); + starttime = parseInt(c[timeIdx]); + } + // pushed this loop together to try and bump loading speed a little + while(l!==undefined) { + ++nl;c=l.split(","); + n = +c[latIdx];if(n>maxLat)maxLat=n;if(nmaxLong)maxLong=n;if(nylen ? screenSize/xlen : screenSize/ylen; + return { + fn : getTrackNumber(filename), + fields : fields, + filename : filename, + time : new Date(starttime*1000), + records : nl, + minLat : minLat, maxLat : maxLat, + minLong : minLong, maxLong : maxLong, + lat : (minLat+maxLat)/2, lon : (minLong+maxLong)/2, + lfactor : lfactor, + scale : scale, + duration : Math.round(duration) + }; +} + +function asTime(v){ + var mins = Math.floor(v/60); + var secs = v-mins*60; + return ""+mins.toString()+"m "+secs.toString()+"s"; +} + +function viewTrack(filename, info) { + if (!info) { + E.showMessage("Loading...","GPS Track "+getTrackNumber(filename)); + info = getTrackInfo(filename); + } + console.log(info); + const menu = { + '': { 'title': 'GPS Track '+info.fn } + }; + if (info.time) + menu[info.time.toISOString().substr(0,16).replace("T"," ")] = function(){}; + menu["Duration"] = { value : asTime(info.duration)}; + menu["Records"] = { value : ""+info.records }; + if (info.fields.includes("Latitude")) + menu['Plot Map'] = function() { + info.qOSTM = false; + plotTrack(info); + }; + if (osm && info.fields.includes("Latitude")) + menu['Plot OpenStMap'] = function() { + info.qOSTM = true; + plotTrack(info); + } + if (info.fields.includes("Altitude")) + menu['Plot Alt.'] = function() { + plotGraph(info, "Altitude"); + }; + menu['Plot Speed'] = function() { + plotGraph(info, "Speed"); + }; + // TODO: steps, heart rate? + menu['Erase'] = function() { + E.showPrompt("Delete Track?").then(function(v) { + if (v) { + settings.recording = false; + updateSettings(); + var f = require("Storage").open(filename,"r"); + f.erase(); + viewTracks(); + } else + viewTrack(n, info); + }); + }; + menu['< Back'] = () => { viewTracks(); }; + return E.showMenu(menu); +} + +function plotTrack(info) { + "ram" + + function distance(lat1,long1,lat2,long2) { "ram" + var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360); + var y = lat2 - lat1; + return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180; + } + + // Function to convert lat/lon to XY + var getMapXY; + if (info.qOSTM) { + getMapXY = osm.latLonToXY.bind(osm); + } else { + getMapXY = function(lat, lon) { "ram" + return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale), + y:cy + Math.round((info.lat - lat)*info.scale)}; + }; + } + + E.showMenu(); // remove menu + E.showMessage("Drawing...","GPS Track "+info.fn); + g.flip(); // on buffered screens, draw a not saying we're busy + g.clear(1); + var s = require("Storage"); + var cx = g.getWidth()/2; + var cy = 24 + (g.getHeight()-24)/2; + g.setColor(1,0.5,0.5); + g.setFont("Vector",16); + g.drawString("Track"+info.fn.toString()+" - Loading",10,220); + g.setColor(0,0,0); + g.fillRect(0,220,239,239); + if (!info.qOSTM) { + g.setColor(1, 0, 0); + g.fillRect(9,80,11,120); + g.fillPoly([9,60,19,80,0,80]); + g.setColor(1,1,1); + g.drawString("N",2,40); + g.setColor(1,1,1); + } else { + osm.lat = info.lat; + osm.lon = info.lon; + osm.draw(); + g.setColor(0, 0, 0); + } + var latIdx = info.fields.indexOf("Latitude"); + var lonIdx = info.fields.indexOf("Longitude"); + g.drawString(asTime(info.duration),10,220); + var f = require("Storage").open(info.filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + l = f.readLine(f); // skip headers + var ox=0; + var oy=0; + var olat,olong,dist=0; + var i=0; + var c = l.split(","); + var lat = +c[latIdx]; + var long = +c[lonIdx]; + var mp = getMapXY(lat, long); + g.moveTo(mp.x,mp.y); + g.setColor(0,1,0); + g.fillCircle(mp.x,mp.y,5); + if (info.qOSTM) g.setColor(1,0,0.55); + else g.setColor(1,1,1); + l = f.readLine(f); + while(l!==undefined) { + c = l.split(","); + lat = +c[latIdx]; + long = +c[lonIdx]; + mp = getMapXY(lat, long); + g.lineTo(mp.x,mp.y); + if (info.qOSTM) g.fillCircle(mp.x,mp.y,2); // make the track more visible + var d = distance(olat,olong,lat,long); + if (!isNaN(d)) dist+=d; + olat = lat; + olong = long; + ox = mp.x; + oy = mp.y; + l = f.readLine(f); + } + g.setColor(1,0,0); + g.fillCircle(ox,oy,5); + if (info.qOSTM) g.setColor(0, 0, 0); + else g.setColor(1,1,1); + g.drawString(require("locale").distance(dist),120,220); + g.setFont("6x8",2); + g.setFontAlign(0,0,3); + g.drawString("Back",230,200); + setWatch(function() { + viewTrack(info.fn, info); + }, global.BTN3||BTN1); + Bangle.drawWidgets(); + g.flip(); +} + +function plotGraph(info, style) { + "ram" + E.showMenu(); // remove menu + E.showMessage("Calculating...","GPS Track "+info.fn); + var filename = info.filename; + var infn = new Float32Array(80); + var infc = new Uint16Array(80); + var title; + var lt = 0; // last time + var tn = 0; // count for each time period + var strt, dur = info.duration; + var f = require("Storage").open(filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + l = f.readLine(f); // skip headers + var nl = 0, c, i; + var timeIdx = info.fields.indexOf("Time"); + if (l!==undefined) { + c = l.split(","); + strt = c[timeIdx]; + } + if (style=="Altitude") { + title = "Altitude (m)"; + var altIdx = info.fields.indexOf("Altitude"); + while(l!==undefined) { + ++nl;c=l.split(","); + i = Math.round(80*(c[timeIdx] - strt)/dur); + infn[i]+=+c[altIdx]; + infc[i]++; + l = f.readLine(f); + } + } else if (style=="Speed") { + title = "Speed (m/s)"; + var latIdx = info.fields.indexOf("Latitude"); + var lonIdx = info.fields.indexOf("Longitude"); + var p,lp = Bangle.project({lat:c[1],lon:c[2]}); + var t,dx,dy,d,lt = c[timeIdx]; + while(l!==undefined) { + ++nl;c=l.split(","); + t = c[timeIdx]; + i = Math.round(80*(t - strt)/dur); + p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]}); + dx = p.x-lp.x; + dy = p.y-lp.y; + d = Math.sqrt(dx*dx+dy*dy); + if (t!=lt) { + infn[i]+=d / (t-lt); // speed + infc[i]++; + } + lp = p; + lt = t; + l = f.readLine(f); + } + } else throw new Error("Unknown type "+style); + var min=100000,max=-100000; + for (var i=0;i0) infn[i]/=infc[i]; + var n = infn[i]; + if (n>max) max=n; + if (n 8) { + grid*=2; + } + // draw + g.clear(1).setFont("6x8",1); + var r = require("graph").drawLine(g, infn, { + x:4,y:24, + width: g.getWidth()-24, + height: g.getHeight()-(24+8), + axes : true, + gridy : grid, + gridx : 50, + title: title, + xlabel : x=>Math.round(x*dur/(60*infn.length))+" min" // minutes + }); + g.setFont("6x8",2); + g.setFontAlign(0,0,3); + g.drawString("Back",230,200); + setWatch(function() { + viewTrack(info.filename, info); + }, global.BTN3||BTN1); + g.flip(); +} + +showMainMenu(); diff --git a/apps/recorder/app.png b/apps/recorder/app.png new file mode 100644 index 0000000000000000000000000000000000000000..036f5d132181624c52b034f07732d39a958903fa GIT binary patch literal 1530 zcmV*0sE5goeq8BC!bd2RBEJ`$A<#X(ZKv9FiT; z;*uCW9uG-1z*)G#XmK+iJsuB9HDGbtcSno!F6%C%G?KpxeYCJb^aaW#QF54h^C0iC z?n+;KsxsNSqx@eO{sNjj4VHs@}e3_BNyS3^mYDRek&q zdCnpMw^zxDbO|@u3@`#x<&Ng^t;?S_Qqycik-EByl;E{V|D2a+yGgUKStx)WHU$7c z4DeD$p%Hpo5?)4+)ofeVbuGL9FXV+a4*+BEH$s|UgE$)wytBPDQB5rg8Ss8sQ1-|` z^HDPiVUJ=ebz?kTK_`1KY`&~0@c3QZ6VZ$$ZUenF^S|=-`l=y`rYA#t;Uo`u2$)0{ zWgkn!O*U;rjvy$wE@WmU)NurH8Sv~Ycq`!XF9Qu-JJ64(_|I<4;f}59U^7A#DvKnD?%@Kw zsebsL!j{I$v%=?JlU9W_=~b9U7wr8XKUSc>-I3@v;PrN_BxUH~=gcO>U3%iQdXE@p z#>x{+X$%o;~@$c|Tp4ueS#+`hNkLR;XjnX6aJLW!3F20Pd(s0EnT% z;;?)K?F)eBH1ak{tjCr;XsuBuvdz0090V!_`i%Pg{eP)Q13+11R_IE7%zFi`&FNXo5A> z-j;L&Hka*0&>LK2b=zAV8@}&Q|@N<}sL>`8=#CQB5rg8z7>U5uw4N;^NwzoSZ)at^^F>Z#P2z zg|lZ?z#tMb(A3mWN(^CNE{f*QIx8=)k$^WM^Ayl0iRfyH+dyMuV?JcIPrGCipt!xh zE!uh6bMoH>P#=~|7i+E=m`rl zrGkhOJ{8fw8znLcgU&Vp_V74P78Mt7yhGmlh>-o<*)ws!fmL)HINEslClID+W5jU% z*YoEezf)fJ+<6TEyR3~d!rJ!sc9-5BwCFO>c=&KNBNq836|gz{xfK-^(tqXiob-Iy zlEh#R_InQK=^+wb1{lN3+G_$7#l$TsEPUbS-Q+)`Yh#C(dn28QBW*_XK$t7s*~7$QkMaOsS*q_7?VJ#EGjOp)hn2tz4!_Py$*r|KxPUI gJ*gXIlzWYT0cWfgM6O1!vj6}907*qoM6N<$f+S4TzyJUM literal 0 HcmV?d00001 diff --git a/apps/recorder/interface.html b/apps/recorder/interface.html new file mode 100644 index 000000000..2ae1c3e71 --- /dev/null +++ b/apps/recorder/interface.html @@ -0,0 +1,179 @@ + + + + + + THIS IS NOT CURRENTLY IMPLEMENTED +
+ + + + + diff --git a/apps/recorder/settings.js b/apps/recorder/settings.js new file mode 100644 index 000000000..2a9a7a0d8 --- /dev/null +++ b/apps/recorder/settings.js @@ -0,0 +1,4 @@ +(function(back) { + // just go right to our app - we need all the memory + load("record.app.js"); +})(); diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js new file mode 100644 index 000000000..38b1d69d2 --- /dev/null +++ b/apps/recorder/widget.js @@ -0,0 +1,222 @@ +(() => { + var storageFile; // file for GPS track + var entriesWritten = 0; + var activeRecorders = []; + var writeInterval; + + function loadSettings() { + var settings = require("Storage").readJSON("recorder.json",1)||{}; + settings.period = settings.period||10; + if (!settings.file || !settings.file.startsWith("record.log")) + settings.recording = false; + return settings; + } + + function getRecorders() { + var recorders = { + gps:function() { + var lat = 0; + var lon = 0; + var alt = 0; + var samples = 0; + var hasFix = 0; + function onGPS(f) { + hasFix = f.fix; + if (!hasFix) return; + lat += fix.lat; + lon += fix.lon; + alt += fix.alt; + samples++; + } + return { + name : "GPS", + fields : ["Latitude","Longitude","Altitude"], + getValues : () => { + var r = ["","",""]; + if (samples) + r = [(lat/samples).toFixed(6),(lon/samples).toFixed(6),Math.round(alt/samples)]; + samples = 0; lat = 0; lon = 0; alt = 0; + return r; + }, + start : () => { + hasFix = false; + Bangle.on('GPS', onGPS); + Bangle.setGPSPower(1,"recorder"); + }, + stop : () => { + hasFix = false; + Bangle.removeListener('GPS', onGPS); + Bangle.setGPSPower(0,"recorder"); + }, + draw : (x,y) => g.setColor(hasFix?"#f00":"#888").drawImage(atob("DAyBAAACADgDuBOAeA4AzAHADgAAAA=="),x,y) + }; + }, + hrm:function() { + var bpm = 0, bpmConfidence = 0; + var hasBPM = false; + function onHRM(h) { + if (h.confidence >= bpmConfidence) { + bpmConfidence = h.confidence; + bpm = h.bpm; + if (bpmConfidence) hasBPM = true; + } + } + return { + name : "HR", + fields : ["Heartrate"], + getValues : () => { + var r = [bpmConfidence?bpm:""]; + bpm = 0; bpmConfidence = 0; + return r; + }, + start : () => { + hasBPM = false; + Bangle.on('HRM', onHRM); + Bangle.setHRMPower(1,"recorder"); + }, + stop : () => { + hasBPM = false; + Bangle.removeListener('HRM', onHRM); + Bangle.setHRMPower(0,"recorder"); + }, + draw : (x,y) => g.setColor(hasBPM?"#f00":"#888").drawImage(atob("DAyBAAAAAD/H/n/n/j/D/B+AYAAAAA=="),x,y) + }; + }, + steps:function() { + var lastSteps = 0; + return { + name : "Steps", + fields : ["Steps"], + getValues : () => { + var c = Bangle.getStepCount(), r=[c-lastSteps]; + lastSteps = c; + return r; + }, + start : () => { lastSteps = Bangle.getStepCount(); }, + stop : () => {}, + draw : (x,y) => g.reset().drawImage(atob("DAyBAAADDHnnnnnnnnnnjDmDnDnAAA=="),x,y) + }; + } + // TODO: recAltitude from pressure sensor + }; + /* eg. foobar.recorder.js + (function(recorders) { + recorders.foobar = { + name : "Foobar", + fields : ["foobar"], + getValues : () => [123], + start : () => {}, + stop : () => {}, + draw (x,y) => {} // draw 12x12px status image + } + }) + */ + require("Storage").list(/^.*\.recorder\.js$/).forEach(fn=>eval(fn)(recorders)); + return recorders; + } + + function writeLog() { + entriesWritten++; + WIDGETS["recorder"].draw(); + try { + var fields = [Math.round(getTime())]; + activeRecorders.forEach(recorder => fields.push.apply(fields,recorder.getValues())); + if (storageFile) storageFile.write(fields.join(",")+"\n"); + } catch(e) { + // If storage.write caused an error, disable + // GPS recording so we don't keep getting errors! + console.log("recorder: error", e); + var settings = loadSettings(); + settings.recording = false; + require("Storage").write("recorder.json", settings); + reload(); + } + } + + // Called by the GPS app to reload settings and decide what to do + function reload() { + var settings = loadSettings(); + if (writeInterval) clearInterval(writeInterval); + writeInterval = undefined; + + activeRecorders.forEach(rec => rec.stop()); + activeRecorders = []; + entriesWritten = 0; + + if (settings.recording) { + // set up recorders + var recorders = getRecorders(); // TODO: order?? + settings.record.forEach(r => { + var recorder = recorders[r]; + if (!recorder) { + console.log("Recorder for "+E.toJS(r)+"+not found"); + return; + } + var activeRecorder = recorder(); + activeRecorder.start(); + activeRecorders.push(activeRecorder); + // TODO: write field names? + }); + WIDGETS["recorder"].width = 15 + ((activeRecorders.length+1)>>1)*12; // 12px per recorder + // open/create file + if (require("Storage").list(settings.file).length) { // Append + storageFile = require("Storage").open(settings.file,"a"); + // TODO: what if loaded modules are different?? + } else { + storageFile = require("Storage").open(settings.file,"w"); + // New file - write headers + var fields = ["Time"]; + activeRecorders.forEach(recorder => fields.push.apply(fields,recorder.fields)); + storageFile.write(fields.join(",")+"\n"); + } + // start recording... + WIDGETS["recorder"].draw(); + writeInterval = setInterval(writeLog, settings.period*1000); + } else { + WIDGETS["recorder"].width = 0; + storageFile = undefined; + } + } + // add the widget + WIDGETS["recorder"]={area:"tl",width:0,draw:function() { + if (!writeInterval) return; + g.reset(); g.drawImage(atob("DRSBAAGAHgDwAwAAA8B/D/hvx38zzh4w8A+AbgMwGYDMDGBjAA=="),this.x+1,this.y+2); + activeRecorders.forEach((recorder,i)=>{ + recorder.draw(this.y+15+(i>>1)*12, this.y+(i&1)*12); + }); + },getRecorders:getRecorders,reload:function() { + reload(); + Bangle.drawWidgets(); // relayout all widgets + },setRecording:function(isOn) { + var settings = loadSettings(); + if (isOn && !settings.recording && require("Storage").list(settings.file).length) + return E.showPrompt("Overwrite\nLog 0?",{title:"Recorder",buttons:{Yes:"yes",No:"no"}}).then(selection=>{ + if (selection=="no") return false; // just cancel + if (selection=="yes") require("Storage").open(settings.file,"r").erase(); + // TODO: Add 'new file' option + return WIDGETS["recorder"].setRecording(1); + }); + settings.recording = isOn; + require("Storage").write("recorder.json", settings); + WIDGETS["recorder"].reload(); + return Promise.resolve(settings.recording); + }/*,plotTrack:function(m) { // m=instance of openstmap module + // if we're here, settings was already loaded + var f = require("Storage").open(settings.file,"r"); + var l = f.readLine(f); + if (l===undefined) return; + var c = l.split(","); + var mp = m.latLonToXY(+c[1], +c[2]); + g.moveTo(mp.x,mp.y); + l = f.readLine(f); + while(l!==undefined) { + c = l.split(","); + mp = m.latLonToXY(+c[1], +c[2]); + g.lineTo(mp.x,mp.y); + g.fillCircle(mp.x,mp.y,2); // make the track more visible + l = f.readLine(f); + } + }*/}; + // load settings, set correct widget width + reload(); +})() From 1b539fee8dbe16dbf9c2984898e1b4e19a25f784 Mon Sep 17 00:00:00 2001 From: Victor Serain Date: Thu, 28 Oct 2021 15:14:51 +0200 Subject: [PATCH 0421/1062] fix: app json --- apps.json | 7 ++++--- apps/gpstouch/gpstouch.app.js | 2 +- apps/swiperclocklaunch/icon.js | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 apps/swiperclocklaunch/icon.js diff --git a/apps.json b/apps.json index 440257e3c..a9a3b2f53 100644 --- a/apps.json +++ b/apps.json @@ -4108,11 +4108,12 @@ "version": "0.01", "description": "Navigate between clock and launcher with Swipe action", "icon": "swiperclocklaunch.png", - "type": "boot", - "tags": "system", + "type": "bootloader", + "tags": "tools, system", "supports": ["BANGLEJS", "BANGLEJS2"], "storage": [ - {"name":"swiperclocklaunch.boot.js","url":"boot.js"} + {"name":"swiperclocklaunch.boot.js","url":"boot.js"}, + {"name":"swiperclocklaunch.img","url":"icon.js","evaluate":true} ] } ] diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js index 0425cdc23..cac19cdef 100644 --- a/apps/gpstouch/gpstouch.app.js +++ b/apps/gpstouch/gpstouch.app.js @@ -196,7 +196,7 @@ function prevInfo() { } Bangle.on('swipe', dir => { - if (dir == 1) prevInfo() else nextInfo(); + if (dir == 1) prevInfo(); else nextInfo(); draw(); }); diff --git a/apps/swiperclocklaunch/icon.js b/apps/swiperclocklaunch/icon.js new file mode 100644 index 000000000..c9089ce5c --- /dev/null +++ b/apps/swiperclocklaunch/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("lEoxH+AB8WAAwYQEaQrdEp4pWEyYoRC49kxGs2fX6+z1mIsgpUCQtAxAjCAA+zxFAFCAQFxAkJAAuIFBxMF1oeHgEABI+sFBomEORInJPgJ7EEyonLFAJQJBIh0IE5x6GE47CME5nXsgnGOojmME5p5HJyAnO6+IE5LEKE6JQEE4lkC5gnPUIh2SE6B4EAAesC5oAP1gnHTxpPDAQIAFeJQACH5wnP64nWAA3CBJB3WAA203fQBAp3IY4plENQ4HC2gABkjHNxAnX2nJBYeIEYf+AYVkE5oDGE4e0UgdkEwYnDUAITEACikBTwgnFxAnZFAJ2FE4lAJ7dAE4pQFY6yfCToYmDE4kW1jvX1geEE4YoF2YfFABRzD67EEEwqiGFCAmETg5QJPQYAMTQJ0GE5AoGshSPYQgmKFA72BFJWzxBzEExgoIKYOI1grC2esxBLGExwpKABolPFCwmSFKQlVFZoXP")) \ No newline at end of file From 577bcd39b1a25c41d725655943401717080ae04a Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 14:28:30 +0100 Subject: [PATCH 0422/1062] Merge bangle 1 and 2 welcome screens --- apps.json | 27 ++++-------------- apps/welcome/ChangeLog | 1 + apps/welcome/{app.js => app-bangle1.js} | 8 ------ .../app.js => welcome/app-bangle2.js} | 8 ------ apps/welcome2/ChangeLog | 17 ----------- apps/welcome2/app-icon.js | 1 - apps/welcome2/app.png | Bin 1939 -> 0 bytes apps/welcome2/boot.js | 9 ------ apps/welcome2/settings.js | 18 ------------ bin/sanitycheck.js | 4 +-- 10 files changed, 8 insertions(+), 85 deletions(-) rename apps/welcome/{app.js => app-bangle1.js} (97%) rename apps/{welcome2/app.js => welcome/app-bangle2.js} (97%) delete mode 100644 apps/welcome2/ChangeLog delete mode 100644 apps/welcome2/app-icon.js delete mode 100644 apps/welcome2/app.png delete mode 100644 apps/welcome2/boot.js delete mode 100644 apps/welcome2/settings.js diff --git a/apps.json b/apps.json index 23769e299..2362c2e9d 100644 --- a/apps.json +++ b/apps.json @@ -169,17 +169,18 @@ }, { "id": "welcome", - "name": "Welcome (Bangle.js 1)", + "name": "Welcome", "shortName": "Welcome", - "version": "0.12", + "version": "0.13", "description": "Appears at first boot and explains how to use Bangle.js", "icon": "app.png", "tags": "start,welcome", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ {"name":"welcome.boot.js","url":"boot.js"}, - {"name":"welcome.app.js","url":"app.js"}, + {"name":"welcome.app.js","url":"app-bangle1.js","supports": ["BANGLEJS"]}, + {"name":"welcome.app.js","url":"app-bangle2.js","supports": ["BANGLEJS2"]}, {"name":"welcome.settings.js","url":"settings.js"}, {"name":"welcome.img","url":"app-icon.js","evaluate":true} ], @@ -203,24 +204,6 @@ ], "data": [{"name":"mywelcome.json"}] }, - { - "id": "welcome2", - "name": "Welcome (Bangle.js 2)", - "shortName": "Welcome", - "version": "0.13", - "description": "Appears at first boot and explains how to use Bangle.js 2", - "icon": "app.png", - "tags": "start,welcome", - "supports": ["BANGLEJS2"], - "allow_emulator": true, - "storage": [ - {"name":"welcome2.boot.js","url":"boot.js"}, - {"name":"welcome2.app.js","url":"app.js"}, - {"name":"welcome2.settings.js","url":"settings.js"}, - {"name":"welcome2.img","url":"app-icon.js","evaluate":true} - ], - "data": [{"name":"welcome2.json"}] - }, { "id": "gbridge", "name": "Gadgetbridge", diff --git a/apps/welcome/ChangeLog b/apps/welcome/ChangeLog index 519222c52..f72f77a4b 100644 --- a/apps/welcome/ChangeLog +++ b/apps/welcome/ChangeLog @@ -14,3 +14,4 @@ 0.10: Tweaks to reduce memory usage 0.11: Fix initial screen fill colour 0.12: Fix swipe direction (#800) +0.13: Mods for Bangle.js 2 diff --git a/apps/welcome/app.js b/apps/welcome/app-bangle1.js similarity index 97% rename from apps/welcome/app.js rename to apps/welcome/app-bangle1.js index 047b0cdb2..949750b38 100644 --- a/apps/welcome/app.js +++ b/apps/welcome/app-bangle1.js @@ -290,14 +290,6 @@ setWatch(()=>{ }, BTN2, {repeat:true,edge:"falling"}); setWatch(()=>move(-1), BTN1, {repeat:true}); -(function migrateSettings(){ - let global_settings = require('Storage').readJSON('setting.json', 1) - if (global_settings) { - delete global_settings.welcomed - require('Storage').write('setting.json', global_settings) - } -})() - Bangle.setLCDTimeout(0); Bangle.setLCDPower(1); move(0); diff --git a/apps/welcome2/app.js b/apps/welcome/app-bangle2.js similarity index 97% rename from apps/welcome2/app.js rename to apps/welcome/app-bangle2.js index d9a967d8a..93d1c5657 100644 --- a/apps/welcome2/app.js +++ b/apps/welcome/app-bangle2.js @@ -243,14 +243,6 @@ setWatch(()=>{ move(1); }, BTN1, {repeat:true}); -(function migrateSettings(){ - let global_settings = require('Storage').readJSON('setting.json', 1) - if (global_settings) { - delete global_settings.welcomed - require('Storage').write('setting.json', global_settings) - } -})() - Bangle.setLCDTimeout(0); Bangle.setLCDPower(1); move(0); diff --git a/apps/welcome2/ChangeLog b/apps/welcome2/ChangeLog deleted file mode 100644 index f72f77a4b..000000000 --- a/apps/welcome2/ChangeLog +++ /dev/null @@ -1,17 +0,0 @@ -0.01: New App! -0.02: Animate balloon intro -0.03: BTN3 now won't restart when at the end -0.04: Fix regression after tweaks to Storage.readJSON -0.05: Move configuration into App/widget settings -0.06: Move loader into welcome.boot.js -0.07: Run again when updated - Don't run again when settings app is updated (or absent) - Add "Run Now" option to settings -0.08: Don't overwrite existing settings on app update -0.09: Allow welcome to run after a fresh install - More useful app menu - BTN2 now goes to menu on release -0.10: Tweaks to reduce memory usage -0.11: Fix initial screen fill colour -0.12: Fix swipe direction (#800) -0.13: Mods for Bangle.js 2 diff --git a/apps/welcome2/app-icon.js b/apps/welcome2/app-icon.js deleted file mode 100644 index 5c1373e17..000000000 --- a/apps/welcome2/app-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AU5gAEFtoxnEwXN53WAAXO5oJB42Wy26AAIueFoPXFggAD4AwEGTQiB6otBFgwAD3QvFGC5dCFxiRGGClhrdbv67BXAIuLMBIwPsIABF4OpLwXOFxjBCF6gtBw2r1mHXoXWFxqQWFwOH62rL4IeB6xeOAAIvHGBYuC6+rR4QvCXpovXw3X1i/DR4QuPR5AvKFQOs6+GF4eod4IvPd5AvLwvWLwQvCv4fBR54vURwOHF4iQCX0yOCF4aQBX0QvHSAoAN3SOSd4WyF4yQPLyhgD1YvDMCJeIFxhgCF47BN4BeHFxpgDSAiRORpAuPMIYAFGBYuaF5aSHFwQvEFqQwOeggSBLa4xNF4X+4wAC/xeCFjIADrYwGBIIvlMQiPDBAOk0gDBz2XF8BlEF4eIxADFF8lcF9n+wIrFF05bHF9AsGF9wupGAYv/F8QupGAov/F/4wOF1gA/AH4Ap")) diff --git a/apps/welcome2/app.png b/apps/welcome2/app.png deleted file mode 100644 index ebbf254bd7c3546e8337c97648a7eb56747c81ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1939 zcmV;E2WP)^Vse?M*ABwfS z8MZ{CGrE{9Te8V!(51`XGM!;roJ%xW2E^g2NO{qXsim}(VOBaof>d5Dolx3xFQxb1 z-nZxMkEd_+KDRC9c8eL`e{OQldCvKM@9+H1@Ao`#6F2ey4asCOZNf#^U4iltNGVVP zD9{Fa2%!dFY=)EdIoSdkU1+ff<1#70}x@Hu6Kz{*xG?E@iS0CV6Z_UXTXX+c`#EfepLM z7bkj*!-~A7=*pg+Sbi*K%7RHf4giWO!Qq3`KOGl&fb97ejd1$m`W-Ff7CVP?6!NP@ z*#dZSJOwP6bwBxEm_^gcdS*L+X9eY#LFhPi?iv?)0Qct&s7(@2rVYtLc?9_Na7Wq| z*s!a7VL$mhO77$FB^z-&y=>gMfp-sY$1lBBpl3RC?8G{+#s}vNi$-#!6vBPB&)OMR zSFd&$bZt`bEWGtWzIxkQ4xXu@;q+gK#Us}SYaD|6$H%;%Ti;I9kp;}PGuy7fJm2Nj z3iFC{=TP>!hdI^J%>L8W066-F^;LCQ!M&eAbbP(vkK`q5{1_wcNTtxuZ2ySr8h}N& zRszc2md$8ii@03Y-n|s>8yCR?kc`--87pEZEdlP41PEPrX4^*;%A5{2vnS8zR7W$x z$ly8R&9b`gL{GM5xU>zNlg7h)0KF}J0vsa*DW~nsDFU8oDs=&cd09m`4HsuS|8}Go}cbc4m$MYKWCB5WaYg-fnm1w~?N@s{QIvQxc8y;|@aI_lzb}w~BisS# zemenx-!nLkY_L)4u$>fXwvAaIiTBWc+v}7`&~p6!uCs3uaov50-F-3g3k#a`wiTU zvP%9=C|wR)0Y87>{T;xe;}`am?ajxK)a*r5vJpf0+j$=?(bQEM4dHi_T)m(OP>_0! z1_+^4NFyyH<*{D@K^5csiY97XHdE-Io)Y4AJ18jdq6!;N6w__G$4oxaYSOXO>ixVd z+ws{6d|mcaH(IW=leb~(nby14&Nh=~ygVGcVjJW0xrjs(bVp2%hH_X~(1Taf-^c)x z7fdd^7$=di#^z*S%ALL$;a+Lr=xF3e6aY{@cU@z0T5E~G9qD5HeiuEVI9(y3DL9Gw z1yOPg`+ITpuxVa+DK3e)^{?OJ2V;=^WrSx4^Q0WKKXvUe>I+R(Rh1DhZFp7C$I`_l zlS*4#H@%Sr4uO?(LjPWs*V=<2)&jK*IPU`jS>z$Ju_P`2WvTIvXn zmH3zi^y9hR%brk`mjK)IQWZKoBLssHEiBBw=l9z94dC&#M}GJIl*eYU7cZ~QEIAx*jx#4V*6tXFt75D0`S3i z9}Y}9+K^{NLmzuXR~F6skih>$z;LX3{!6Pxw0a<^>ZsE89`|RJp-`2xV9n22`K4{uPal12c#LV76{gFJ8nP!- zaw36avkT_Uy!ZAWT)neo#k6g;jWdVG2vqd_X5^b_KQbcVPgUo7pa0hDpH9h{^Jkwc z-!McT8S3Bl{(_CIEp)Rdry0B}FR$L8xE8Y*}X}Yi3ly zimxsC2XF#(_RiV^WkXzj?IRg&AIPu(06{kcC_FmEycEFvEC@)5a|@6}ST)4#1`by) zyX#!0>t$TP%4Lh%RHy~~L+v8eWV#kifB-A0ZW?Y&s`=Rw5cL>u!8JF_b1TZrj!Z7V#$X$n89sf7V!{MeE?v?nqF**mb8fyw8z-vHP29u{ Z;y>o9o!*JS4-fzV002ovPDHLkV1gG+pke?3 diff --git a/apps/welcome2/boot.js b/apps/welcome2/boot.js deleted file mode 100644 index 07b7386f1..000000000 --- a/apps/welcome2/boot.js +++ /dev/null @@ -1,9 +0,0 @@ -(function() { - let s = require('Storage').readJSON('welcome2.json', 1) || {}; - if (!s.welcomed) { - setTimeout(() => { - require('Storage').write('welcome2.json', {welcomed: true}) - load('welcome2.app.js') - }) - } -})() diff --git a/apps/welcome2/settings.js b/apps/welcome2/settings.js deleted file mode 100644 index d87cf4b55..000000000 --- a/apps/welcome2/settings.js +++ /dev/null @@ -1,18 +0,0 @@ -(function(back) { - let settings = require('Storage').readJSON('welcome2.json', 1) - || require('Storage').readJSON('setting.json', 1) || {} - E.showMenu({ - '': { 'title': 'Welcome App' }, - 'Run next boot': { - value: !settings.welcomed, - format: v => v ? 'Yes' : 'No', - onchange: v => require('Storage').write('welcome2.json', {welcomed: !v}), - }, - 'Run Now': () => load('welcome2.app.js'), - 'Turn off & run next': () => { - require('Storage').write('welcome2.json', {welcomed: false}); - Bangle.off(); - }, - '< Back': back, - }) -}) diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index ef795871d..9c5f4c916 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -133,9 +133,9 @@ apps.forEach((app,appIdx) => { if (isGlob(file.name)) ERROR(`App ${app.id} storage file ${file.name} contains wildcards`); let char = file.name.match(FORBIDDEN_FILE_NAME_CHARS) if (char) ERROR(`App ${app.id} storage file ${file.name} contains invalid character "${char[0]}"`) - if (fileNames.includes(file.name)) + if (fileNames.includes(file.name) && !file.supports) // assume that there aren't duplicates if 'supports' is set ERROR(`App ${app.id} file ${file.name} is a duplicate`); - if (!file.supports) fileNames.push(file.name); // assume that there aren't duplicates if 'supports' is set + fileNames.push(file.name); allFiles.push({app: app.id, file: file.name}); if (file.url) if (!fs.existsSync(appDir+file.url)) ERROR(`App ${app.id} file ${file.url} doesn't exist`); if (!file.url && !file.content && !app.custom) ERROR(`App ${app.id} file ${file.name} has no contents`); From 5ad849b8670d704725ff8311f83d7b9030a1e8e8 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 14:31:50 +0100 Subject: [PATCH 0423/1062] naming --- apps/recorder/app.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/recorder/app.js b/apps/recorder/app.js index 9c8380c07..9b9c06c78 100644 --- a/apps/recorder/app.js +++ b/apps/recorder/app.js @@ -49,7 +49,7 @@ function showMainMenu() { }; } const mainmenu = { - '': { 'title': 'GPS Record' }, + '': { 'title': 'Recorder' }, '< Back': ()=>{load();}, 'RECORD': { value: !!settings.recording, @@ -102,7 +102,7 @@ function showMainMenu() { function viewTracks() { const menu = { - '': { 'title': 'GPS Tracks' } + '': { 'title': 'Tracks' } }; var found = false; require("Storage").list(/^record\.log.*\.csv$/,{sf:true}).forEach(filename=>{ @@ -174,12 +174,12 @@ function asTime(v){ function viewTrack(filename, info) { if (!info) { - E.showMessage("Loading...","GPS Track "+getTrackNumber(filename)); + E.showMessage("Loading...","Track "+getTrackNumber(filename)); info = getTrackInfo(filename); } console.log(info); const menu = { - '': { 'title': 'GPS Track '+info.fn } + '': { 'title': 'Track '+info.fn } }; if (info.time) menu[info.time.toISOString().substr(0,16).replace("T"," ")] = function(){}; @@ -240,7 +240,7 @@ function plotTrack(info) { } E.showMenu(); // remove menu - E.showMessage("Drawing...","GPS Track "+info.fn); + E.showMessage("Drawing...","Track "+info.fn); g.flip(); // on buffered screens, draw a not saying we're busy g.clear(1); var s = require("Storage"); @@ -318,7 +318,7 @@ function plotTrack(info) { function plotGraph(info, style) { "ram" E.showMenu(); // remove menu - E.showMessage("Calculating...","GPS Track "+info.fn); + E.showMessage("Calculating...","Track "+info.fn); var filename = info.filename; var infn = new Float32Array(80); var infc = new Uint16Array(80); From 5bbe209a44e8cd15dbcd293197d8a865f2a62427 Mon Sep 17 00:00:00 2001 From: qucchia Date: Thu, 28 Oct 2021 16:00:43 +0200 Subject: [PATCH 0424/1062] Update Q Alarm --- apps/qalarm/ChangeLog | 1 + apps/qalarm/app.js | 13 +++---------- apps/qalarm/qalarm.js | 4 ---- apps/qalarm/qalarmcheck.js | 7 +++---- 4 files changed, 7 insertions(+), 18 deletions(-) diff --git a/apps/qalarm/ChangeLog b/apps/qalarm/ChangeLog index 4022f485c..135e69d23 100644 --- a/apps/qalarm/ChangeLog +++ b/apps/qalarm/ChangeLog @@ -1 +1,2 @@ 0.01: First version! +0.02: Fixed alarms not working and localised days of week. \ No newline at end of file diff --git a/apps/qalarm/app.js b/apps/qalarm/app.js index 4d27739cf..64f601bf6 100644 --- a/apps/qalarm/app.js +++ b/apps/qalarm/app.js @@ -172,21 +172,14 @@ function showDaysMenu(alarmIndex, alarm) { "< Back": () => showEditAlarmMenu(alarmIndex, alarm), }; - [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ].forEach((dayOfWeek, i) => { + for (let i = 0; i < 7; i++) { + let dayOfWeek = require("locale").dow({ getDay: () => i }); menu[dayOfWeek] = { value: alarm.daysOfWeek[i], format: (v) => (v ? "Yes" : "No"), onchange: (v) => (alarm.daysOfWeek[i] = v), }; - }); + } return E.showMenu(menu); } diff --git a/apps/qalarm/qalarm.js b/apps/qalarm/qalarm.js index 38571987e..6b31ba645 100644 --- a/apps/qalarm/qalarm.js +++ b/apps/qalarm/qalarm.js @@ -1,7 +1,5 @@ // This file shows the alarm -print("Starting alarm"); - function formatTime(t) { let hrs = Math.floor(t / 3600000); let mins = Math.round((t / 60000) % 60); @@ -150,8 +148,6 @@ let active = alarms.filter( (alarm.timer || alarm.daysOfWeek[time.getDay()]) ); -print(active); - if (active.length) { showAlarm(active.sort((a, b) => a.t - b.t)[0]); } diff --git a/apps/qalarm/qalarmcheck.js b/apps/qalarm/qalarmcheck.js index de3db68ab..9a3f10d5e 100644 --- a/apps/qalarm/qalarmcheck.js +++ b/apps/qalarm/qalarmcheck.js @@ -29,13 +29,12 @@ let nextAlarms = (require("Storage").readJSON("qalarm.json", 1) || []) .sort((a, b) => a.t - b.t); if (nextAlarms[0]) { - print("Found alarm, scheduling...", nextAlarms[0].t - t); setTimeout(() => { - load("qalarm.js"); eval(require("Storage").read("qalarmcheck.js")); - }, 3600000 * (nextAlarms[0].t - t)); + load("qalarm.js"); + }, nextAlarms[0].t - t); } else { - print("No alarms found. Will re-check at midnight."); + // No alarms found: will re-check at midnight setTimeout(() => { eval(require("Storage").read("qalarmcheck.js")); }, 86400000 - t); From 7ad7fa6c325395539ec5f5d7d8a91daab3e25f2b Mon Sep 17 00:00:00 2001 From: qucchia Date: Thu, 28 Oct 2021 16:03:01 +0200 Subject: [PATCH 0425/1062] Update Q Alarm --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 4d2c4fdb9..12c51ff72 100644 --- a/apps.json +++ b/apps.json @@ -4172,7 +4172,7 @@ "name": "Q Alarm and Timer", "shortName": "Q Alarm", "icon": "app.png", - "version": "0.01", + "version": "0.02", "description": "Alarm and timer app with days of week and 'hard' option.", "tags": "tool,alarm,widget", "supports": ["BANGLEJS", "BANGLEJS2"], From 78ba542aa347f037ebb77b42b6d05dd15ea27377 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 15:11:35 +0100 Subject: [PATCH 0426/1062] oops - widget fix --- apps/recorder/widget.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 38b1d69d2..785f3b03e 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -23,9 +23,9 @@ function onGPS(f) { hasFix = f.fix; if (!hasFix) return; - lat += fix.lat; - lon += fix.lon; - alt += fix.alt; + lat += f.lat; + lon += f.lon; + alt += f.alt; samples++; } return { From ce08d82367810866ffc0d4d701f463ca9eb73ba0 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 15:12:43 +0100 Subject: [PATCH 0427/1062] location --- apps/recorder/widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 785f3b03e..df0be1d20 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -182,7 +182,7 @@ if (!writeInterval) return; g.reset(); g.drawImage(atob("DRSBAAGAHgDwAwAAA8B/D/hvx38zzh4w8A+AbgMwGYDMDGBjAA=="),this.x+1,this.y+2); activeRecorders.forEach((recorder,i)=>{ - recorder.draw(this.y+15+(i>>1)*12, this.y+(i&1)*12); + recorder.draw(this.x+15+(i>>1)*12, this.y+(i&1)*12); }); },getRecorders:getRecorders,reload:function() { reload(); From b04bd5d158a62f65d2047c1f90c2a298d7349a84 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 28 Oct 2021 16:13:11 +0100 Subject: [PATCH 0428/1062] 0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021) --- apps.json | 5 +- apps/about/ChangeLog | 1 + apps/about/{app.js => app-bangle1.js} | 0 apps/about/app-bangle2.js | 71 +++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 2 deletions(-) rename apps/about/{app.js => app-bangle1.js} (100%) create mode 100644 apps/about/app-bangle2.js diff --git a/apps.json b/apps.json index a1c3583a8..ed9b9ca5c 100644 --- a/apps.json +++ b/apps.json @@ -111,14 +111,15 @@ { "id": "about", "name": "About", - "version": "0.09", + "version": "0.10", "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", "icon": "app.png", "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ - {"name":"about.app.js","url":"app.js"}, + {"name":"about.app.js","url":"app-bangle1.js","supports": ["BANGLEJS"]}, + {"name":"about.app.js","url":"app-bangle2.js","supports": ["BANGLEJS2"]}, {"name":"about.img","url":"app-icon.js","evaluate":true} ] }, diff --git a/apps/about/ChangeLog b/apps/about/ChangeLog index ccc80148c..9557e448d 100644 --- a/apps/about/ChangeLog +++ b/apps/about/ChangeLog @@ -7,3 +7,4 @@ 0.07: Pressing a button now exits immediately (fix #618) 0.08: Make about (mostly) work on non-240px screens 0.09: Actual Bangle.js 1 pixels as of 13 Oct 2021 +0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021) diff --git a/apps/about/app.js b/apps/about/app-bangle1.js similarity index 100% rename from apps/about/app.js rename to apps/about/app-bangle1.js diff --git a/apps/about/app-bangle2.js b/apps/about/app-bangle2.js new file mode 100644 index 000000000..8a0be9f3d --- /dev/null +++ b/apps/about/app-bangle2.js @@ -0,0 +1,71 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var W = g.getWidth(), H = g.getHeight(); +var ENV = process.env; +var MEM = process.memory(); +var s = require("Storage"); + +var img = atob("sIwDAG2S66DYwA2AAAAAHAHGSRxJEkAAgmGGBxDIADIdAFJIbAHF9HP00kBUC6DtzDgAgGOxwkgAGbA86CW2222kkgB4BO26/XDDwAwkEEEgYYA+VW22wEAAggwAG2AZZZTFotMIDAA9vB520AJUnXAtwAgAgGxOw2wo+bAmiSAAAAQUkAHMAO2/66TY2GwgggghB5/+SRxJAEAAlm2ABxADLKYFFFBADA/99HP00kHoC6DuzAAAAGOxwkg+uzG86CQADbSUgAB+CSQAAADDAAtEkEkAAAA2khxIAHAAgmGLADIDLLAAAAEDDSQQCAAAAAHA4AAuwAAAAAAAAAAAwAIIAAMgAYQUAAA4CongAAAGABqEkkkAHHGGhhxIHHXa66ADYbAACcEHzUBDbQCSSQAAAAHAAADDDDAAAAAAAAA14GGGABEEAYQWBAIDCQ84AAowwIYQkkiS4g42khxIA4inNPAA1wAATkkABCSAASQQikm2SQHAAAAAGwAAAAotoouJwAIIABEgYYAAJIIoCI84AFt2wBCAEkbYEEHPABxIAAfSqqSQ1wACcEEAACSAAAAAggmACAH/41gGgEwH/AtoFFG2wGGAABEEDYAAIJAtAI2Gw9twwwYAAm2AAAB5AR0kAAAHNPKo2wAT4AAoFCShYCSAgkmwCAAAAWoWgEyCSAottoub2GAAAAMgAAAAJIAFCAwww9FAGwAA9th5AgHPAR22A/AC66KoBGCd8kksAEATQCAAggmFCAEnAawGgEwEkAAADbutwGAIBIAAAAABmhAYAAwA35xAg22A9t5PQAAAARySA44kAEikAAzo+22vJPAZgCCEAgm2CAgAoAGvGwBJAAAGADGGGGxBBAAAAAAMEEIASQAA5+2EEwzb9tn/QAAuARwAA/An4kkkgCeA9ttsAEAAACSAAAAAAAgAooAGAABAAxwGADGGGAAIBIAAAAAMAEP/QUkAH5xAgADD4AAUkQQuQRASQ4AEAEkkATow4AAAAASjDFCSAAAAQAgAtAAoAABIAvgGDDG2GAAAAAAAAAABghHJSG2CSwAACQDb4EEAgGCuQQAyw4HkAAkggdAG4AQAAA0zDFCAC2wDDAEnooH19At2AywGAYGGAAAAAAAAAAAAMIH/QQAACGAASAAA4DjAgwCuCAASwAAABJEEDbbbbbbYAAiTbtqVCbYQQQAAoFAAoFAGAQAG2GGGGwAAAQAAAAAABAGmLTIkCwDACAPIAAAAgAQuQAAJIAAABAEn/JIAIMAEEAADDFCAFAoDDAAAAAAAoAu2SRJAAIAAAAAAAAAAAAAAAAEkNK6iCDDAAA+PADbAG2AuCAAJQAIopSEz7BAABEgEgAADDFCAAAAAQEEAAAAAoAFAQQIAJIAKcu4AACAAAH/AH/GmjaSkSAYAYAJ4ADrAAFAuAQAKIAAtpBmXvBNBIMkEEAADDFCAAJJIAkkgAAAAFFoASQMghIAAAAAAACAAA/H4/HAAZK6EAAAAAAAAADbDYFAuACADAAwFBMydABAABEgG22AAAEsHAALbAkkgAABJACQSAFIggkggkgkkAACaAAJ/4AGoQRYkEAHA/+AGAAAYAAtAwAQHYG2ABmToABIAIMAwAAwAAggn/4LAAEkAAABAIQCAVlAHA2222222AAwH/BxJAYEAgHHAA4AAd2w22EADAAAAAACA7AwAKSdAAA44AAAGG2AAAEkA//LAAAgAA4BAISSAVoAkASqSSSSSEkwH/BIIDbDggn4EgjYAMWGGGEAAYAAAAEAEHYAAjTowAAEAAAAGAAAbbYgAAALFdHHC2ABBAQCSloHcAbbbbYYDEAwH/IIAbbaAkAAskDDASWEmwEgbEHEAAAxOAABJzdAAAA44AAAA2wAAYAAAADbDDH4CGABJAQCklAAkAtootottEkCaAJIADbBoggFtAjYDQWgmGE84H//AAACQAAAhRpAoCA84ACSSwAAYYoAAAAAFdAACSJJJJJJklAAkADAAYAAAEAAbYMIAAYAAggtoADDDSQkgAEmgA44SSSCIEAAadAAgkg/gACJKGAADYAACQAAAAAAAYAAAABMkoAAAADADAQAAEADAAggAAAADL1tAAYAYCAggIE84CHAQCAALoAmboggYggkgttJKAYAAAAAVqBgQAS/kIGAwwxJkoAAAADAYAQAAH/DAAkgAAA2AattFoAAAigwBBAAAB/4SSSAJAEyd84YQkgAAACJKJJIkkAAQCAnQAS/kYwwwwx5koAAAADD2QQAGHXDADlgAAAwxVgtFAkgEkkJAIwAABkgSSSQLomT4/gCIDADAACSSQQQ//AACQAgWAS/kI2w2wxJkoAAAADe2CAAgg/DADFAAAA2CZc8loBgAggBAAggQAAASSSAIEydA84IADDDHXAAAAAASSAAADbAAAS/kYwwwwB5kNAAAADe2AAEGEHAbYlAAAAwzJP/9ABgAAAGGAbYAAAGAEbAAmTsAABJADYYH6AADbAAAAAAMIY2w2w2kIwwwwwJJNAAAADDwAAgGAgAAEFYAAAAEZc8loBgAAABJAggACAAAEYYEydBMJEkHnAACSQAADBSJllIJLQGAwA2kAAAAAAKSIAH/ADAYASHMbtbh4kAAAAwFIAAABBgGWWWWAAwAAAAEEbAmToBJJEEEkGJwAAAAYbAAlAIMLb0w2wEkEAgnJJKSIAAAADADACHMkkkh4HAAAASGQJJIgIgAAAAAE8888G2AgYcydABhhABHnGkwQCAAYYAAAJxIAAAAwAEkEgg/O2LJIAoAoAAAFksAkAAAHAHAABJPYIAIEkAAAGOEkQAAAG2AEkmkgABJJAAAABkIAAAAAEggAAIAA/EgJAEkEEgJO1NAAFjDlAAGAsgAgAAAAAAAwxJOgIIIAADbIBxAgQEAkG2AEEicIAEAAAAAA0AACAAAAEEgAAIAAHAgBA44EAgA2FtAAFkclAGGGEAAkAAA4HJ4AAJFoAAAA/ADIGOAiQEEAm2AEmjkkJ0w/4AAAgwCAAUkkkkgAAAAA/EgJAHAAAAGwADAAAckYAGGGAAAEAAAAIAACABJwFAAAADDAACQAZYkEmAAEylIBN01/4AAAwDASSH//8kgAAAAA4EAICXkAAE82AbAgkjjAAAwwAAAkAAAHAHEkkBJIFwYAHAYkkgeYLIAAGAAkEhABJAEgAAGAgYYEgBJJIkAAAAoAAAAAAXgAOP/AwAA//4YAAAA444AAAtoSXAEEEBJQEGYAHAAAgQYYZfA4HAEEdgAAIgggAAAmAbYEAAAAAAAAAAAA4EHNtHAA008AIAAJJIAACSQ444AAAAoQAgAAAAcgEAwAHAAAgCkgAAHHAAmUpgAAIEgAAAAAAYZEgE8H/AGAwACQAAHHBA2AOIEWAAAPkkkgQAQEAgAAAFASAb2QQAjYYYbfHAAAgAEGwHAAAEycIgAAAgjCDAAAAAQAgE8EkAGGEMQCAAX9pIwAAAAiAAAPn//4AAQgggAAWVAQAf2AAAcAEkAA4AAAgAEGBJHHAmkhAAAAEgg44884AHXEg8kA/4GwBAQAAAXHBA2AAACEAAAPhJJIQCAkggADADASW59QQAjSQiAAAAAH/AEAxAHBAxkMdGA2AGADAn/gAwQwAAAH//GGBAQSAAXFAAAooAIAAAAkkgAAAAAgggAo2woAwwAAAAcQAiAAYFYBJAkmxIHBBRhTr2wwwwwII884HAAHAAAAkgGA0MQCAAQAAAAtooAAAiA//4A4AAAAAAAgwwgEGEAFAAjSACQAoDAHPAAABAHBBFIUd2www2wCAHnAAAAAAAAEEEAAABCQBJu2AAYoooAAEAQJJISSQAA444AAwwAAkgAbYAcQAAAFYdAHPAAABAABJBASw1N2AwwAAAAAAEUQwAA9gg4AAEAAABoBADDAAHHAAiAAXn/AtoAAAAAAGAAAEAADAAjQgAAAEEEAAAAAHAbYHAJIwAIAAtoAAAABJMkQ/4H4EA/AEIAABJohCDbAHAwAAgQA45JA2wAAAbYAAAACECAYYAcAABBJA2wAwAAA64bY64IwwAwAAooCCoSQJMUQkgAHAHAbYAAGxAoACDAAA/4AACIInUkbbYAADDDbbbYAUQAAABAAAHB5EEEGGAAAAAbYAAJGSQoAAtoCSoSRIAAAwAAA/4AA4AAGGJbbaAbAA44AIIIIQBIbAAYADDDtttvHEAAGAGQkSHB5AAAAwAAAAAbYAAIASQwAAoAiCtQQP/H4wAAHgnAw4SAGGAAA/HAAIAAABAII4kkAAAD4AbY///77EXNkgG8gSIAAAAACAO0AADbbAAIASQAAAowgAE0BPJJ4DYf8EE/bYSQGGAAHA5AAIgggQQJIBJJIAAAYAPItttv/EzjEAAUkRLADAAkiQMAAbbbbbYIAAAHgDAmgAGOADAAYDbY4gg4AAQAGwC8A/HA/JEEACAEESAAttAADA/4bbbY4AIAggABkJLtrAA2wQOmAcjjjkYAAAAHkJY0wDE0AB//IAbAAEmRACSAAAC8AggA/CqqAggGmSAFAAoAAYPIAAAH/HKXIAAAkALmDgAAAAIkAcktskYAAAAH8JDAAbAAAAAAAAAAAkyNgASFo/68EssATAVWGEwBxSAo2wFAAAAAAAAHH4R4FtEM/AIgwlllADbAAbbbbbYAAAggjYAbDAYAAAAAAAACCWRtwoQQt4CEEssA/ACG2GAAISwowGFIIACCQAAHHAAgAoEkAAMm2kEAAA4AAAAAAAACSAggjDEAYAAAAAAQAQbaSQAIIoCAo/CEAjgA/AABxAwAASAowGHHHACCAAS8ADAgAoEM/AAwwwFAAA4AAIDvrAACCAEMDYxwAAAAAAASCQtqCFtJIAAAA4CEAjgBJAHAwHGAASAo2wBABASCQAX4AYE8ooBJkAGww2EAAAAABADvrAACSABhDEIMAALJBAAQQQ2wACSIIAAAA/4AAbYBJAAoAowAASAowAvAHAAAAA/gmwA/ooBAQAWG2GFAAAAAAIDvrAlKCAc8bAxwAALIYDAAAAAAAFtIIAAAAAAAAAAG2VRPv4AAASYFwFAQAAAAAAgEAwAntoBISCWwA2H44AAADADvrAlLApkchAEAhgLJBAAAAABJJMkAHPAAAHnSQAAG2VRpvvAAASOGtoAAA4HAHBIAAQAn/4BAQQQ22wHHGGGGAYDHrHlIookkgAAAIAAAAAA4AAAAAH/kh5CBEBJSCAAEkSRNv9HwAAIAEA84HHAAAwBAACCSHgB/AAAAAAH/+wAwCAAAAAlLFoEkAwAAgAACAAH/AAH//8k23PAAAHnQSAAEkVRpvvA+AAW2kgkgH/AEA2IAAwAEEABAAAgAAHFtuGAwIQAAA4lLAsEgjwEAIAAUQAHAAAAgggAAAAAAAAACSAAAAVRNv4HwAAQEEE84HHHAHwBAAAAAgAAJBYAwE49AAAAAeAAAAwAAAggggwgghgASQAA4AAA444A/4D///7AAAQARAIASAAAAAAAPMAAAAAA/4BIAH/AkkgGADIAwC44tAAAAIwAAA4AEgE0A/wEAAAAQQAAHA1AkEAA8kAEkkAACQAABLICQAAAAAEk58AQAbbYAAAAA4C4ggg22ADA2249AAgkAYYAAAAAm0AgwwEikH/kn/AH/G1oAAAA88AAEAAkQCAABZICQkkkkkG2PIAAAYYYAAAAHwS3AAAGABBA2KHDrYwwwIwAAHkgm0AGGGACAGJ03nAA4ASAAAAA/8AAEYYkgATACSSAMkkkkkHGwEiQAbb88AAA+yC24AgAADIAwCAAYGwAADAABDWQEgAGGGACAEk03/ADAAQAAAAA8kDEEbYtgkKIAQQBBkkkkkH+wEtVAc0f/DYA+QC24EAEkAAEQGgDbYA23AHAAHQQCAkkmACCCAAAAAAeYGwoAAAAQAbckYYJgERAAQCPHIAAEkHGAEtVA/H88DDA+QW24EgEk/4AAAAABAIkwAAABYEASACCAAASQAAIAIAbYwAtbAAC6DYbAAAJEgAAAAAIAIAAEkAAAEiQA//4AEwA+yW24EAEk44IBBIJAAAA3HHAA4AACQCCACAAAHABBAAYYwAtbAHXXXbYoAoAAAAAAAABJwAAEkAAAAAAA//4AEGAf2W3EkgAA/4AAAIIBAIwwAwAAoAACAAAQSQQDA4DLAQAAwAAYAAC6ADAoAoAAAABSIAAAwAAEkABIAFFtP/IAEGAb//4AuoDYA4IBBIIBAI2wAIAA4AAAAE8QCAQrACDDCSkAGwAiIAAQ/4GFFBJAAAB/I4A4wAAkkABBAFFAJJIAEGAb4/4AywYDkgIBAAAAJAkgAQAAAAAoAggiSSATA4AACCAAAAA1gAA484w1FBBAAABSIAAA222EkABIAFFo5J4AAAAb/A4ALIYD0wIBAAAAA4AAQwgHAAAAEAAESQATHAX/CSg0AAAKYBAA/4AwoBBAAAAAAAwADMQSkQBBAFtA4A4AAAAbH/AAAAYDkgBIAAEAAAAAHPAAAAQAgAAAgAAtAAGACCA0D4AAABrwAGAwAIIAAACAAgAgZiCCCABIAAAA//4AIImTA4AAAAAYAAC4AAkgAAgEAAAACCXvAAA/n8/k8Q2wCCk0HYAAABrYAw2AAAAAwQDAQEkDMCCCQAASAAAAAAAAJMyYAAAAAAAA//HCAEkkAAEgAIIBCCrroAAHn8nn8W2wAAAAAAAABBtYggAAAA4AADFDAAgAAHDAAACAAAAAAAAAIIAAAAAAkgFtA46HAkkggAEgAIIIKCAAAAAHk88n/Wx2AbkkkkkkhJBAggAH/AAAAAuoA/4AAHYYwFCAAAAAAAAAIIAJaQwEEEFAA/HQEkkHEAAAAww2wQAAAAAHn88k8W22Aee22222wAA4GFdHvA/4Cd31akgCAHYYwFASAAAAAAAgAEAAYQwAkgFtw4wwAkkgAA2wA2wwwAAAAAA/EAAAAW22AbAAPIH/AngAgjrH/A/4AAuoASQAA/DGAFtAAAAEAAE5EkAIaQAEEEFA22wowEkkHA2AAAAAAGAAAAA2E/8ro2w2AYYA/44A4/4AgldAAA/4ADFDAoEkkAoFEEkkETA2DAnIgggJYQwAAAFAwwwwAAkgAAAH/4wwwGebbAA2E88dY2AGADAAPI4H4nmwABJAAA/4AQDAQoAAAFAkEkkkkjEkYA5AEkAAAAAAAAAAAAAuoAEAAH/44HwwAG222AA/E/8rowmlFtoAAA444AGBBBHAAAAAQACAAobYAoFFE0kk0TGzAAAAAFtCSAAAAAA4AAAAAAAAAHAH4H2wwWebbAA+glVGgm21tFEkAAH/AAGxIBBAAAAACABAAoYYFAgFEGkmEjEaBJAAAFtCSAAAAAAFSQAwigEg2HCA4HwwwQAgAAF+giKGEGmlFFAQQSAAAAGBBBJAAAACSJJJIobYoFAEEA0wETDyBAAAADbCSA2SQEECSQAARQEAYYVQ/4IAAWAggAA2glVGEGkk64ASAQQAAAAAAHArrAAAAAAAAoYFAhttEAGAEjfyBIAAAttoAAAQAwwwSQAAigAgbCACBJBASSQkAH/3gggG/+AgigAQASAAFtAAAA4trAAwAwAEgoYoFZosEAGAETDyBAAAABJ85JGSAQAXS2kFAHcgAAH////AAQAggHfkAEEk8ggg64EgYQQQAoA64/4rtAABBAwEAoFAoZtt8DeAcjAaBAAAEBJ85JAQADDBS2kFovD4HoskkkkEAQAgEH/AAAAA/4kgDYYAAAAAAoAURxIAAAABhGGH4ooEAZos8YADETADBAE2gh5855AQAAoGS2kFFHY4Hov////EAQAAAkAbAQAQBIkAYDAA//AJIoA6+2wEkgABBAwBAtAoAZtt8YADEjAAYAmm0B/8/5AAAAADAAAFAHDFItoAAAEkAQAAAggYYQQQIAggDYYA8nAIFtAABxKSkjYAAQABIoEAAbYE8DYAcTAADAEGAAAAAAAAAAAAAAAFAHYBoooAAAERJIAAAkAbACCABIkAAgAA8ngJIAAbYAAQkgDAkgAAAAAAAAABJAAAAAAAAACAYYY2AAAAAAAAAAAAAAGuAAAAAFEhAAAAAgAAAAAAAAAAACA4/8gAI44Aa8QQgAAkAgAAHHH/P/H/3/H/H/H/HASAbYYwAJJJJIAAAAAAGwFtAIAAAAAhAAAFAggAAAAAAAACCCAAAAAJIAAYYASUgAAYAAAAAAABJIAAwAAAAAAAAACAYYY22AAAAAAAAAAAzGGuHGAAAAAhJAAAFkAAAAAtoAABJJAAA4AAEAEbekDYbAbYAAH/4DDHP4AAADDAYYAAAAAAEkkgw////4AAAAAAwGAAQgQAAAAhAAAFogAAAAAoFDbFAF22wAAAEEEAG0GGGAAYkgHAHDYbAACAIYAL47AAAGwGEEAgwADbbYAAAAAAGwAAGHAAFAAhAAASWGIJAAAoFYAFotAwAAAAAggAAAtttoAYAgHAADDDAAQQAAoAbYwQAGwwEkggtttttoBJAAAAAAAoAIAAFtAhAYACG2BBSQAooYAFFFAwAHAAAAAAAFEkkFAYEgA4HAAAAAGAAYAIAAwQAGGGYAFo223//4BJAAAAAAFoAPIAFFFmsAASWGABRIAAADbFFFAwGgA44AAAFAm22goEEgH4H4BJBIwzDBBAAA0QAAAAYDAAbbdttoBJAAA2AB2oAPIAAFtiFAAAAAAASQEkkAAdFFAwGgHHHHXAtEyaa0FAkA/AA/A4MhwwgAABbZwQAAAAYDoFAADbbYCSAAAxwB2QAJIAAFFhWAAAG22wJIEEEDbFAFDwGgA4446AAm7rr+goAA/H4/HHBIGoArbDADF+AAADYbFoAAAA/kiCAH/2/5KSk/IAAAAgAAAAAAAxawEgkAAAAAYYAYHHHHXA4ndllfgoAwH4H4AAG2AFtAYDGDF+SQQAAAAAHIPHHAiQAH/3//4AAJIAAAAgJAACAG2xW4A2wAAAAAYAEEA4466AAmdklegoAGA//AAAwAwA4AYDADAGSQEEAAAAAAAAHEiCEH/3//4AG54AAAAbZAAAQGGxawACAAAAAADAIYPHHHXAomTsrWguwGAAADTGSreAAAABbZAASQEkAAAAEAEAHAiSnn////t2wJIAAAAYZQSSQAYwJIACAEAAAGAZAgB4464AogydawgoGwAAACqGAoGHHAAAAA/H4AEEAAAAA2wEHkgAEHH/n/9tAAAAAAAbYQQAgDbAAAACBW2GAGYYAz3HHHXAFEGTWEFGzbAAADTASrYAgADDAH///AEEAAAAAAAAAAAAYTH8k/4AAAAAAAAACQSAAAAAAAAIOOwGAGDAAbY4464AoogzwksADbAAAAAAQAYHHAltA/k8n4AAAAAAACQCAAAAYQf/n/20kkkgAAQAAGwGBJAAAAAIRuOo2wDAAz3HHAAAAFEGEBBADbHJIIIASrYAAAgoA/kkn4AAAAAAAQCCAwAAaAf/n/km222xAKQAAwAGBhAAAABMH3DwwAQgAAAAAAAFAFogmoIwAAHPJIIAAAAAwAgoAH8k/AAFllllAACCGGCWAAH/n/2wAAABBCQAAGEmBJFoAABIoooOwAPoAddoCEkFFFAEwwAGwAHJIIJBAAAG2AjDAA/n4AAAAAAAAAQCwAyQ1wH///4AAAABICQDAA0mBAFqABIHCSH2wAgw4FDCCHnAooAoGAAAAAwAAAGAAAAGGAkkkkn/FtrADAAAAAQCgAiCtoH///4AmbYBBCQZY2AGAAACABIoqSookAAAADFCSEnAAAAFigAAAMEAAA2wAAA/q6gAAAA4AAFBBEAkgEAAgAgS1wH///4A0cYBAIDLLAAAAAkCABAHCSHCAABBHAJKCDbFAoAAEEgkIMkAAAGAAAAvq6AAAAYGPOFAAEAggEQCGGGSbY////4AmbYAAABZZA2wEggiQQQAowoAQCBIHBICADAFFAA2GEEENMEAAAGAQQYtqSwwADDAHAFCCEYggcAAAwAAfYH/ADAA0BJAAAALIAAAggkACCkEnAHACABB/JJIADbFoDSww0AEAIwAAAkgSTYAgAwwgjbGIOFBJEYkgcAAH/BpbYAAADAAmIQA9FABHMkgEAggAAgkgggEgEAAG222HvvFFCAwwwBJJIAAAAAAQQYAgA2wgjbAAAADAAbADYAUg4FFkgAAADgE0IQHFoAABMkgAAgEAAkEEABJJJAA2GG2HvvFAoSDADBJJAAGWAAAoAAAAAwwAAAAAARP8gYYYYAWg4FIijbDDbkEmQSQ9FAAEkkgAAAAgAAAAggAAggA2222HvvAAAADYbBJJAACiAAAooAgA4AAH/CACCBP8gbADYAUg4BFkjAADDgk0gQQAAAAGAAAGAAAg2AAHEHAAEAF2AA1HvvDbAYDDDYBAYAGWAAAoEkA/AAA//6CCARP8gAAAAAAAAAAtrDDDDwAmgSQAEkkAYAAFGSAkGAAASkkAggA+224F//AYDDDAAbBAYAbYAAAov/gA/AA446HCCAJJFAFAEkAAAMgSrDDDDSQMEkB4AAwGAAAouWAAGAAAW0kA44AFotAAASAaQYDAAGxYYYYYAAos//8HAAA446A6AQABFAFAEAgAAIESrbDDbQBIoQYAAGAAYAAFCWAAwAAASkkAIIkQDAAAACACAA2AAABbbYbYgAtt7D8AAto//6/6CAABFAFAEAgAAIEtoAAAgSIIGABAAAAGAAADH/AAAAAA4A4ABAigFAASQiCChABgdoBAAAAFtAAAgAgAA/oH/CQSAQIBFAFAEkAAJMgJIBJEgRAIoAA4AAAAYAAYfnAkgHHAAH/AIIkQBQAAQgQQhABgYqBAAAAttoAAFEAJA94A4ASQCABIFtFtEAgAAAA54BAggAAEAABAAAAGAAADH/AgEnHAAAAAAAigoDAAQEEEBhhgdqCH/AAssoAAAwAIA/4AAACAAQ4w4w4w4w4AAAAJIBBkkAAAAAP/4AAAYAFvAAAkggAGAAAAJABOAEAAQAggAgggaCSJJAAttoAAAAAIAAAAAAAAAAbbgAAAAAAAAAA2wBJAgAg5//J/4AAGAAF/AAAAgmWWWWLIJGBIAoAQQAAAAEAkiSA//AAllgAAARAJAAwAAAAAAAbbAAG2wAG2wAAOIAAAAEEPJJJP8kkAEAFvAJIkggwwwwLIAGAAGAACJASQYAAAACAIAAAEkAAACIABAAAAAFAAAADAAAwAG2wnOAAEAAAAQAE5/JJJ8nkFEAAAA/4AEgwxwwLIAGAA4AAAIIAQb7AwASQAAAAgAkgARIABAAA4AAAAAHHHEn8gA2AIgAAggAQAQAgAAAJJM/8tEACAAtqSJAGAGAAAgAAgIEAAIOCG4GAAGFwAAuAgAgACIBAJAAAAAEAAAAggEn8gGAwgIM3EACQSSUkAAbeecnkFEAQQ1wAQIIwxwwkgEkkACYAAIIQAYAwwwAQAGAIgAkgRABAAAAAAomgoAAkgEn8gADY5hg+EAAAVtQAH4YGmkkkAICCCGuCQIIAAAAoAAAAAAAAAJASQ4AG2AAozAGAgAAgIJIAAAAAAAECAAAEAAAAAAoFQDPcggASV1SQAADEgkngBxAQQA1wAJAAHgAFAAkkAFwwAwAGAD7AwAQQAAoAkgkgFEkAGAAoooAWSACAQAAAAAHAQYAxggGwVtTbbAAYAE//AIACAAAAAJIIk/AAoAAgAHI0kwAAABJAAAGAAGMIAAAArsJAwwAFFFFSSQSQQAkQ/9AoEAGGVQk1SSVroAbYAEngBxFtAA/HABBA4kASQ4EAAAAwwwAAAFotAFFoAtAAAAAFdEMAwAAoooAQSCCCQAmgJIbAkkG2qoG2wQDrtoqSJEACAIFkoAAHABGIE4AAAAkkAAAmGgAAAIAIAAAAAAAAH//FoFFAGbbYAAAACACAQAUgSQSQAAGGqsE2ASDbvooABEATQbdkoA/HQAAACQAkkgAAAAAkEgAAAJAIGG2GAYAAYHAQQFFAAwZJAAEgkBIAA444QQQQCSSSVUkxwXUgttqSBEAQQYdtAA/AAAAAQCw/X4w84AAgggAAAIIIGGAGADADknAEAYAYwwZAH/EEEIAAABJASQSABAiBqsE2ISU8oooBBECACbdAAAACSAAAWyA6S4AigAAgAgAAAIBIGAGGADDD2nGEGD7AGAZBHPEEEIAAAAgAQAQQBkEBVUExwQUgooqSJMgFAYFFgAASSQAAQCAkkgA84AAgAgAAAIAI2G2GwAYY03A2wXAH/6ZJH/EAEBIAAHHAQASABgkB"); +var imgHeight = g.imageMetrics(img).height; +var imgScroll = Math.floor(Math.random()*imgHeight); + +g.reset().setFont("6x15").setFontAlign(0,0); +g.drawString(ENV.VERSION + " " + NRF.getAddress(), g.getWidth()/2, 171); +g.drawImage(img,0,24); + +function getVersion(name,file) { + var j = s.readJSON(file,1); + var v = ("object"==typeof j)?j.version:false; + return v?(name+" "+(v?"v"+v:"Unknown")):"NO "+name; +} + +var versions = [ + getVersion("Bootloader","boot.info"), + getVersion("Launcher","launch.info"), + getVersion("Settings","setting.info") +]; +var logo = E.toArrayBuffer(atob("PBwBAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAD/w+AAAAQAHA4hAAAAQAMAMhAAAAQAYBmhAAAAQAYBGiAAAAQAQCD/H74+R4wGDhoKJCSEwEDgoKJCT8wFDgoKJCSAwHDhoKJCSEQHj/H6I+R4YHmAAAACAAYEGAAABCAAMEMAAAA8AAHA4AAAAAAAD/wAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAB/gAAAAAAAB/g")); + +var imageTop = 24; + +function drawInfo() { + g.reset().clearRect(Bangle.appRect); + g.drawImage(logo,W-60,24); + g.setFont("4x6").setFontAlign(0,0).drawString("BANGLEJS.COM",W-30,56); + var h=8, y = 24-h; + g.setFont("6x8").setFontAlign(-1,-1); + g.drawString("Powered by Espruino",0,y+=4+h); + g.drawString("Version "+ENV.VERSION,0,y+=h); + g.drawString("Commit "+ENV.GIT_COMMIT,0,y+=h); + + getVersion("Bootloader","boot.info"); + getVersion("Launcher","launch.info"); + getVersion("Settings","setting.info"); + + g.drawString(MEM.total+" JS Vars",0,y+=h); + g.drawString("Storage: "+(require("Storage").getFree()>>10)+"k free",0,y+=h); + if (ENV.STORAGE) g.drawString(" "+(ENV.STORAGE>>10)+"k total",0,y+=h); + if (ENV.SPIFLASH) g.drawString("SPI Flash: "+(ENV.SPIFLASH>>10)+"k",0,y+=h); + imageTop = y+h; + imgScroll = imgHeight-imageTop; + g.reset().setFont("6x15").setFontAlign(0,0); + g.drawString(ENV.VERSION + " " + NRF.getAddress(), g.getWidth()/2, 171); + + drawImage(); + setInterval(function() { + drawImage(); + g.flip(); + imgScroll = (imgScroll+1) % imgHeight; + }, 20); +} + +function drawImage() { + g.setClipRect(0,imageTop,W-1,H-14); + g.drawImage(img,0,imageTop-imgScroll); + g.drawImage(img,0,imageTop+imgHeight-imgScroll); + g.setClipRect(0,0,W-1,H-1); +} + +// TODO: a nice little animation before +setTimeout(drawInfo, 1000); From 52ffb2f3c282ab532a0e3a827282724d6f44749d Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 28 Oct 2021 21:51:11 +0100 Subject: [PATCH 0429/1062] added more screenshots and put them into apps.json --- apps.json | 13 ++++++++++--- apps/calculator/screenshot_calculator.png | Bin 0 -> 2733 bytes apps/calendar/screenshot_calendar.png | Bin 0 -> 3866 bytes apps/cliock/screenshot_cli.png | Bin 0 -> 2115 bytes apps/gallifr/screenshot_time.png | Bin 0 -> 3807 bytes apps/pastel/screenshot_pastel.png | Bin 0 -> 4014 bytes apps/sclock/screenshot_simplec.png | Bin 0 -> 2217 bytes apps/simplest/screenshot_simplest.png | Bin 0 -> 2106 bytes apps/wclock/screenshot_word.png | Bin 0 -> 3540 bytes apps/welcome/screenshot_welcome.png | Bin 0 -> 2618 bytes apps/worldclock/screenshot_world.png | Bin 0 -> 2937 bytes 11 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 apps/calculator/screenshot_calculator.png create mode 100644 apps/calendar/screenshot_calendar.png create mode 100644 apps/cliock/screenshot_cli.png create mode 100644 apps/gallifr/screenshot_time.png create mode 100644 apps/pastel/screenshot_pastel.png create mode 100644 apps/sclock/screenshot_simplec.png create mode 100644 apps/simplest/screenshot_simplest.png create mode 100644 apps/wclock/screenshot_word.png create mode 100644 apps/welcome/screenshot_welcome.png create mode 100644 apps/worldclock/screenshot_world.png diff --git a/apps.json b/apps.json index ed9b9ca5c..d52b2c9b5 100644 --- a/apps.json +++ b/apps.json @@ -175,6 +175,7 @@ "version": "0.13", "description": "Appears at first boot and explains how to use Bangle.js", "icon": "app.png", + "screenshots": [{"url":"screenshot_welcome.png"}], "tags": "start,welcome", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, @@ -279,6 +280,7 @@ "version": "0.03", "description": "Display Time as Text", "icon": "clock-word.png", + "screenshots": [{"url":"screenshot_word.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -1090,6 +1092,7 @@ "version": "0.07", "description": "A Simple Digital Clock", "icon": "clock-simple.png", + "screenshots": [{"url":"screenshot_simplec.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -1610,6 +1613,7 @@ "version": "0.14", "description": "Simple CLI-Styled Clock", "icon": "app.png", + "screenshots": [{"url":"screenshot_cli.png"}], "type": "clock", "tags": "clock,cli,command,bash,shell", "supports": ["BANGLEJS","BANGLEJS2"], @@ -1982,6 +1986,7 @@ "version": "0.04", "description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.", "icon": "calculator.png", + "screenshots": [{"url":"screenshot_calculator.png"}], "tags": "app,tool", "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ @@ -2223,6 +2228,7 @@ "version": "0.02", "description": "Simple calendar", "icon": "calendar.png", + "screenshots": [{"url":"screenshot_calendar.png"}], "tags": "calendar", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", @@ -2371,6 +2377,7 @@ "version": "0.02", "description": "A clock for time travellers. The light pie segment shows the minutes, the black circle, the hour. The dial itself reads 'time' just in case you forget.", "icon": "gallifr.png", + "screenshots": [{"url":"screenshot_time.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -2873,6 +2880,7 @@ "version": "0.05", "description": "Current time zone plus up to four others", "icon": "app.png", + "screenshots": [{"url":"screenshot_world.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -3559,6 +3567,7 @@ "version": "0.03", "description": "The simplest working clock, acts as a tutorial piece", "icon": "simplest.png", + "screenshots": [{"url":"screenshot_simplest.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -3968,6 +3977,7 @@ "version": "0.05", "description": "A Configurable clock with custom fonts and background", "icon": "pastel.png", + "screenshots": [{"url":"screenshot_pastel.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -4005,7 +4015,6 @@ "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"waveclk.app.js","url":"app.js"}, @@ -4022,7 +4031,6 @@ "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"floralclk.app.js","url":"app.js"}, @@ -4039,7 +4047,6 @@ "type": "app", "tags": "", "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", "storage": [ {"name":"score.app.js","url":"score.app.js"}, {"name":"score.settings.js","url":"score.settings.js"}, diff --git a/apps/calculator/screenshot_calculator.png b/apps/calculator/screenshot_calculator.png new file mode 100644 index 0000000000000000000000000000000000000000..7a259fe2c010fb9c7d2ced201506267537d76077 GIT binary patch literal 2733 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 literal 0 HcmV?d00001 diff --git a/apps/calendar/screenshot_calendar.png b/apps/calendar/screenshot_calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..4507d77dd0cbf962e6f435c5d310f481a12f254c GIT binary patch literal 3866 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 Uc?BnRAa?nz%xqBQCSJt<11SArXaE2J literal 0 HcmV?d00001 diff --git a/apps/cliock/screenshot_cli.png b/apps/cliock/screenshot_cli.png new file mode 100644 index 0000000000000000000000000000000000000000..fe1c6299b4fef932353ee02149f65bd9ce18a5f1 GIT binary patch literal 2115 zcmeH}Sv=c`7RTc+MlhYG493!`QEsb6YfW1!_Hyl#P}I^TS~DHdjN1^RVOqMRX|Gay zXnWB_gSHxL{fAC+Rq44A>Xy+{F4VL0vTPkpbQi=8*Y31fu;p(ANt^NLZ*EzFc_>uC<_2 z{r-6L2rIthpalg{={GSHmse^cb9RKqZzvTC#p=%%utPy21G4dg`T5*k$Ryu32ePNo zO`Enegr@QmZM~$v`HI|AGBKxbb-4V)1JFlQ(ALxV8kQ}TNAN9|^L+zmm|!l+Ug#N4 z`bWu1C#8PN46ZnU3pfLvs(BX|Jk8fVZnfOi?#ivrg>r%dc%yxGI5CECVok<&m~_@v1&E|R=D^vJ z0T_Zc%-uJUGco*W;SImKUQP?(FtQLNCt%UoBsB}x2uoKwlgr%F>x=XufIZ-pHWS}| zLzDa-$|M+E>DUh-lkS6H!eOB%wf9_nHKZrfBn5TJsrZ~u1)`FU4A*5)dRt@&v{ANgODsf-nh+~Zp+m$7V} zZj}M*V+Xx1)r0qG^)Ui}7Xo-{@;YnagmA`(=54#5@Vuy&EKXc9Hz)^JF*tTycsh~U zGnwKoO0oOlnxAMg0k&xw`V@QT$$@0L3yQ&-n$ibWnXcG~|x5xog$A?&@IMLuZN35R7*4*2RxV@?mT8>*Yw3~@JcMl75Z+)eG8 zlr$$K!`w%lTB`~`tFIkQiUj^qQ3%Y-sqTx!S6S0Fcc`Q9PiWsBt)0Ouoi(7_JCc_$ z8sxBBbGmnPily{&1YDs;*wE~(@fprqTiZ{%0>HY61#UD8AZw6?~lSylUHS7wr=Mbez}p zv%hD7yQGoi6R}%3;Ix% z<}eCEIGXD)n(>|ft{~Dhlojf+8Qy*b3QcHlhbddHHIZ;nKb%PwRzm(l(QP6)dS8((LQF4(qe~@P zXiBb3VXgJ*;RWk|^Qp<$=ur$r7Lvay_wMdkNXLDeAFMa+$q*?z%V`VjB=_*yxI$Y{ zLV#`uK*+SxnscT-F`HJrH{9wc7y!M>H&jVC&{$jAG3LsXRl_htRj)Y9_}d6~Ks7Z( zPvTXh#fkgRxx6qM&kS?l{I%8;Cs#=Ocr7c8>(S!%#iPXaYTMi_uZGwZbLyWaecp?5 z*yiiS^P;Z=eiWsa%`o&ASVfz0iHOKNdS=5auWw$e>oM}2cGiC` zg7TgE2*rj3R4Kg_?L{|6xsYUlTOq>i+fGUy9ktcyLB?Loe2|sJvRP105|y?VF57ux zn9_`U+P;CMuaHtJ^q7t&D?hjhpYGAY2{ia=mdu$S-I5nE@6aF8;L)D11vMvwt|F-r zK+GP2!U^+o^_pVvFRgssKdwXT>)h)QfL6hD_fF>mq{ z`d)LXzX5(Hr_g(JE|7Pk8egBp`v9=)zPj)cgofSWgG-1If40tEocU3mD(JXBeq4$186cCK!h6rF zCzQDH-$LQzU*5-^0$`ONLtLFSpst$M@g)!-C9`Po|FZur5~bF8ADlgk;1Q+%3Lt@g Lp}tJ-q`W@>Y#QRL literal 0 HcmV?d00001 diff --git a/apps/gallifr/screenshot_time.png b/apps/gallifr/screenshot_time.png new file mode 100644 index 0000000000000000000000000000000000000000..2754138c45a49d8f7aa866525744342cd9b19047 GIT binary patch literal 3807 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 literal 0 HcmV?d00001 diff --git a/apps/sclock/screenshot_simplec.png b/apps/sclock/screenshot_simplec.png new file mode 100644 index 0000000000000000000000000000000000000000..a12db3ec82a8b9443b565eca26eeed33a53b1a79 GIT binary patch literal 2217 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 Ang9R* literal 0 HcmV?d00001 diff --git a/apps/wclock/screenshot_word.png b/apps/wclock/screenshot_word.png new file mode 100644 index 0000000000000000000000000000000000000000..56e0ce98f36e6a1b4152f0eb5b44491c13ec0734 GIT binary patch literal 3540 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 literal 0 HcmV?d00001 From 6668727aca1910a50ca69396bf605d22ffd87264 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 28 Oct 2021 22:34:43 +0100 Subject: [PATCH 0430/1062] more screenshots and remove some redundant references to README's --- apps.json | 5 +++-- apps/astroid/screenshot_asteroids.png | Bin 0 -> 1498 bytes apps/compass/screenshot_compass.png | Bin 0 -> 2632 bytes apps/matrixclock/screenshot_matrix.png | Bin 0 -> 4990 bytes apps/simplest/screenshot_simplest.png | Bin 2106 -> 2180 bytes 5 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 apps/astroid/screenshot_asteroids.png create mode 100644 apps/compass/screenshot_compass.png create mode 100644 apps/matrixclock/screenshot_matrix.png diff --git a/apps.json b/apps.json index d52b2c9b5..6c444455b 100644 --- a/apps.json +++ b/apps.json @@ -394,6 +394,7 @@ "version": "0.02", "description": "inspired by The Matrix, a clock of the same style", "icon": "matrixclock.png", + "screenshots": [{"url":"screenshot_matrix.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -448,7 +449,6 @@ "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"aclock.app.js","url":"clock-analog.js"}, @@ -512,6 +512,7 @@ "version": "0.03", "description": "Retro asteroids game", "icon": "asteroids.png", + "screenshots": [{"url":"screenshot_asteroids.png"}], "tags": "game", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, @@ -552,6 +553,7 @@ "version": "0.04", "description": "Simple compass that points North", "icon": "compass.png", + "screenshots": [{"url":"screenshot_compass.png"}], "tags": "tool,outdoors", "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ @@ -3571,7 +3573,6 @@ "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", "storage": [ {"name":"simplest.app.js","url":"app.js"}, {"name":"simplest.img","url":"icon.js","evaluate":true} diff --git a/apps/astroid/screenshot_asteroids.png b/apps/astroid/screenshot_asteroids.png new file mode 100644 index 0000000000000000000000000000000000000000..4474c7a664b37661a5982b2872d541d103de26bd GIT binary patch literal 1498 zcmeAS@N?(olHy`uVBq!ia0vp^8$g(Y4M?uv{v-}aF%}28J29*~C-ahlfpvOOu)uKV8GtEy(ObD4m`2az*QK`b0|lvckpVXC&bi;@1$VVGOfBm@*&uvKh9i^ym))Xl*O;Zw}{xb9thm?xx1gsa#!6y z(Y<~uA8x8`+BiQ@(fxXUPX71HoO`S3Q~vrp5+6dr6AU|iv) zv_YJ)U%+1F{riyjUozM3b+OEz+b{k@?cLrj{r}3ZG1gVZv($ZxW_|xj>+fCG{~3+Z zleYEk-z_7$uK$33-HeU{&%^#`)*-Quo~grl$WZ z8voa8Jb8C}_qWE^Cwv}QZNBu9nTchYYbFzmo~_+k;Tg{=4o4pA6IQ)jEyNbMEFkCve)ZubE=Wa;Ncm!89QyhX(=?sn3CF zSms=i6AOn8!?!yB?`5;hwb&TgrU~s3ezmyv?(eso&KXU6>(g*R)?xn1YU{0~dz24N zn##g)hvB+*`ukl|i;HdvGqKbyJO)aekvr!yG;;2_yNFp^V8#LVSKDo89bmbacd=2F z!{-6>*V}Tw3A+Vu>soDKYP5c^y-YmnxV4f_q-Vo{xexXTU+2vFWW>bJ@IA7FF=FyG zp$ef4LrErAB z+%cD6`d&C|-zhy8`63|8DrahK!tVp^C)F+d56D`ak8qm6(AZos^_;Lm!Mu$v3XDwb z54Gm1J3O#S=5%CY;X4#+<==3i@|aK)3x`b0>XLa3jh}l|SvUnOIKp4rFfu)t^yUD@ zkifc2l|X+OPZLmac%Tro^fRz*NY_#HXgJ`w!|ORv^{7Q9^A6Y-xZjg2I~ZIJEUXzk MUHx3vIVCg!06kBM&j0`b literal 0 HcmV?d00001 diff --git a/apps/compass/screenshot_compass.png b/apps/compass/screenshot_compass.png new file mode 100644 index 0000000000000000000000000000000000000000..63579bab72211b0d7d1a69838a6244e9b225285f GIT binary patch literal 2632 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 literal 2106 zcmeH}`%}^h7sl}`RI+QPCX?o^P0=z}*G4lLMXml;tB*FsCBUM~ub7NNpA^s@S-NS3FO!lebfQy)Q=rwdie$(^ ztz@-UaD2uFbBzH8P#@MTBDP40-`z;^SLciLwQW4bSdh#ncl0S*)zzKwg;VZ(@A5H*?7rgSRSTx=X?_)pCXl!mG4<>;WCCp+kB4=;=&3PkJ*sz-V*93Rbi2y-e4 z!B12Rnp0I__&bpKjb0R@1ob}@h;3Q7_k}A+I_R25h)#06*%{m#_j05y5rEfxJ&?|W zZWmPYFZM#W`&vKzkZj_b=1x?UZNMw?<)B3*h#9-@Y#9kAHdHn_H;8zh$B!Z#M7=2Y zn4>6qN>+8{F%-R=j$v2k0YeKu?p1k!i!m?G-3Z41a0%mO1dG^|{5uAK|FhvelWR+# z{#&n$)kO`)2At7-$0e{19#YoTo;abD)OgYA5L7}xS0r-`rTwz7A929%mc%pxNzRaR zQ#|*jO(rI}%x~d5;7CU0utWAkid;7?C8uhbH8xkTU!pdTxO;l{4i^+IdRJRx?}-Ja zUv^)iO@`^x29r+Yp5XW-b?rXCv|vj&s8WT?tce(1=u_FVomJy6;?M`BX-w>Y*mlf1 zye5%g`1q>&oa=jN;gV(T)IQmTAP-u86BF;OzMvF8TeRiEhkgodWebD!ee&3R@x(X5 zZ8M1j)N3fgi`$91d|$8j?(yjT{Ygy`_jd z=a|KL1n>iv;O0p1SWA6;W-?CxhY~=~nYlSi;Xh}u!O6(Wj5^Yv(O`Zj--Oe`9KPN@ z!}6T^%^rtL8??q4wSLp+Jp1|6UN$S|H)iKy@QB#T(aj6Z7vx2uvqzmu4HzS6gY;YE+NY(&L~&yuQ=3o8|2=nL05dTo&{jsweYz5OJ@gd z`|PMzVoshPnU`$W0UzQcsLo=IllDj)1K?lXec7R(0&ZML`F*60`DrV_5%bXa^jgIn zJ?2Jdho~t%+l?M`vy&@wBV#}8J-{>8K&WUs1r$COb)d$MgIGc67&l6@^x&`iY%haNE_+oa(~1G9$jxi~NIeKmIHzJIgMOTDx{^84=t^ zZjcpMs%(c!L>rE-rOBH4lP)e>$VLg3tc?@G9TP#3aqcS9LpwGRfQX5KmtIxf^AaxzX*M(F4%>$rux@pGc@QyX0drb$oRg|}X*MetEN zZDS4gb!{dX&Drk~dJEBY^JXSk1z}V`!cp{ap|Z!s)wB3Dq%038&tMfOIU-Ojw1a;YW{7|%C#+!zfX`qS44)?>aCWf6%l zWAjJ=yl}Tqo>PO!37aww-M#~z`hTN;ZFFBCXN>FuY=)Tu8_xhFJS5^UI~afcFUgqD Ang9R* From 516ff5b609e64a7d312efc93acf6dae53ec9ef91 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Oct 2021 08:32:42 +0100 Subject: [PATCH 0431/1062] make firmware update beta --- apps.json | 2 +- apps/fwupdate/custom.html | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index ed9b9ca5c..4bc4f802a 100644 --- a/apps.json +++ b/apps.json @@ -1,7 +1,7 @@ [ { "id": "fwupdate", - "name": "Firmware Update", + "name": "Firmware Update (BETA)", "version": "0.01", "description": "Uploads new Espruino firmwares to Bangle.js 2", "icon": "app.png", diff --git a/apps/fwupdate/custom.html b/apps/fwupdate/custom.html index 5286ef062..7230a77a8 100644 --- a/apps/fwupdate/custom.html +++ b/apps/fwupdate/custom.html @@ -3,6 +3,8 @@ +

Firmware updates using the App Loader are only possible on Bangle.js 2. For firmware updates on Bangle.js 1 please From 011b4bc97514d7eb7a05f1a6758f080dca0ddad3 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Oct 2021 08:33:13 +0100 Subject: [PATCH 0432/1062] Change default apps --- bin/firmwaremaker_c.js | 2 +- defaultapps_banglejs2.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 7fb842755..ec49aa19a 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -29,7 +29,7 @@ if (DEVICE=="BANGLEJS") { } else if (DEVICE=="BANGLEJS2") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install - "boot","launch","s7clk","setting", + "boot","launch","antonclk","setting","health", "about","alarm","widlock","widbat","widbt","widid" ]; } else { diff --git a/defaultapps_banglejs2.json b/defaultapps_banglejs2.json index 2d32d285c..04bd44504 100644 --- a/defaultapps_banglejs2.json +++ b/defaultapps_banglejs2.json @@ -1 +1 @@ -["boot","launch","s7clk","setting","about","widbat","widbt","widlock","widid"] +["boot","launch","antonclk","health","setting","about","widbat","widbt","widlock","widid"] From f6ea1b116f1b45b4e1151d88202b3c104859f3b2 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Oct 2021 08:33:31 +0100 Subject: [PATCH 0433/1062] Fix default app loading for Bangle.js 1 --- loader.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/loader.js b/loader.js index c4d8d5972..61e2b1880 100644 --- a/loader.js +++ b/loader.js @@ -27,18 +27,23 @@ DEVICEINFO = DEVICEINFO.filter(x=>x.id.startsWith("BANGLEJS")); // When a device is found, filter the apps accordingly function onFoundDeviceInfo(deviceId, deviceVersion) { - if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") { - showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); - } else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) { - showToast(`You're using an old Bangle.js firmware (${deviceVersion}). You can update with the instructions here` ,"warning", 20000); - } + var fwURL = "#"; if (deviceId == "BANGLEJS") { + fwURL = "https://www.espruino.com/Bangle.js#firmware-updates"; Const.MESSAGE_RELOAD = 'Hold BTN3\nto reload'; } if (deviceId == "BANGLEJS2") { + fwURL = "https://www.espruino.com/Bangle.js2#firmware-updates"; Const.MESSAGE_RELOAD = 'Hold button\nto reload'; } + if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") { + showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); + } else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) { + showToast(`You're using an old Bangle.js firmware (${deviceVersion}). You can update with the instructions here` ,"warning", 20000); + } + + // check against features shown? filterAppsForDevice(deviceId); /* if we'd saved a device ID but this device is different, ensure @@ -149,7 +154,7 @@ window.addEventListener('load', (event) => { document.getElementById("installdefault").addEventListener("click",event=>{ getInstalledApps().then(() => { if (device.id == "BANGLEJS") - return httpGet("defaultapps_banglejs.json"); + return httpGet("defaultapps_banglejs1.json"); if (device.id == "BANGLEJS2") return httpGet("defaultapps_banglejs2.json"); throw new Error("Unknown device "+device.id); From 2a97f49ded79407ab519426ea523e6b15cf52c37 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Oct 2021 10:34:14 +0100 Subject: [PATCH 0434/1062] health 0.02: Modified data format to include daily summaries --- apps.json | 116 +++++++++++++++++++++--------------------- apps/health/ChangeLog | 1 + apps/health/README.md | 1 - apps/health/app.js | 40 ++++++++++++++- apps/health/boot.js | 39 +++++++++++--- apps/health/lib.js | 32 ++++++++++-- 6 files changed, 158 insertions(+), 71 deletions(-) diff --git a/apps.json b/apps.json index 4bc4f802a..024fb1d54 100644 --- a/apps.json +++ b/apps.json @@ -49,7 +49,7 @@ { "id": "health", "name": "Health Tracking", - "version": "0.01", + "version": "0.02", "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)", "icon": "app.png", "tags": "tool,system", @@ -61,36 +61,7 @@ {"name":"health.boot.js","url":"boot.js"}, {"name":"health","url":"lib.js"} ], - "sortorder": -8 - }, - { - "id": "moonphase", - "name": "Moonphase", - "version": "0.02", - "description": "Shows current moon phase. Now with GPS function.", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "allow_emulator": true, - "storage": [ - {"name":"moonphase.app.js","url":"app.js"}, - {"name":"moonphase.img","url":"app-icon.js","evaluate":true} - ] - }, - { - "id": "daysl", - "name": "Days left", - "version": "0.03", - "description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.", - "icon": "app.png", - "tags": "", - "supports": ["BANGLEJS"], - "allow_emulator": false, - "storage": [ - {"name":"daysl.app.js","url":"app.js"}, - {"name":"daysl.img","url":"app-icon.js","evaluate":true}, - {"name":"daysl.wid.js","url":"widget.js"} - ] + "sortorder": -2 }, { "id": "launch", @@ -108,6 +79,22 @@ ], "sortorder": -10 }, + { + "id": "setting", + "name": "Settings", + "version": "0.31", + "description": "A menu for setting up Bangle.js", + "icon": "settings.png", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"setting.app.js","url":"settings.js"}, + {"name":"setting.img","url":"settings-icon.js","evaluate":true} + ], + "data": [{"name":"setting.json","url":"settings.min.json","evaluate":true}], + "sortorder": -5 + }, { "id": "about", "name": "About", @@ -123,6 +110,24 @@ {"name":"about.img","url":"app-icon.js","evaluate":true} ] }, + { + "id": "alarm", + "name": "Default Alarm & Timer", + "shortName": "Alarms", + "version": "0.13", + "description": "Set and respond to alarms and timers", + "icon": "app.png", + "tags": "tool,alarm,widget", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"alarm.app.js","url":"app.js"}, + {"name":"alarm.boot.js","url":"boot.js"}, + {"name":"alarm.js","url":"alarm.js"}, + {"name":"alarm.img","url":"app-icon.js","evaluate":true}, + {"name":"alarm.wid.js","url":"widget.js"} + ], + "data": [{"name":"alarm.json"}] + }, { "id": "locale", "name": "Languages", @@ -240,38 +245,33 @@ "sortorder": -9 }, { - "id": "setting", - "name": "Settings", - "version": "0.31", - "description": "A menu for setting up Bangle.js", - "icon": "settings.png", - "tags": "tool,system", - "supports": ["BANGLEJS","BANGLEJS2"], - "readme": "README.md", + "id": "moonphase", + "name": "Moonphase", + "version": "0.02", + "description": "Shows current moon phase. Now with GPS function.", + "icon": "app.png", + "tags": "", + "supports": ["BANGLEJS"], + "allow_emulator": true, "storage": [ - {"name":"setting.app.js","url":"settings.js"}, - {"name":"setting.img","url":"settings-icon.js","evaluate":true} - ], - "data": [{"name":"setting.json","url":"settings.min.json","evaluate":true}], - "sortorder": -2 + {"name":"moonphase.app.js","url":"app.js"}, + {"name":"moonphase.img","url":"app-icon.js","evaluate":true} + ] }, { - "id": "alarm", - "name": "Default Alarm & Timer", - "shortName": "Alarms", - "version": "0.13", - "description": "Set and respond to alarms and timers", + "id": "daysl", + "name": "Days left", + "version": "0.03", + "description": "Shows you the days left until a certain date. Date can be set with a settings app and is written to a file.", "icon": "app.png", - "tags": "tool,alarm,widget", - "supports": ["BANGLEJS","BANGLEJS2"], + "tags": "", + "supports": ["BANGLEJS"], + "allow_emulator": false, "storage": [ - {"name":"alarm.app.js","url":"app.js"}, - {"name":"alarm.boot.js","url":"boot.js"}, - {"name":"alarm.js","url":"alarm.js"}, - {"name":"alarm.img","url":"app-icon.js","evaluate":true}, - {"name":"alarm.wid.js","url":"widget.js"} - ], - "data": [{"name":"alarm.json"}] + {"name":"daysl.app.js","url":"app.js"}, + {"name":"daysl.img","url":"app-icon.js","evaluate":true}, + {"name":"daysl.wid.js","url":"widget.js"} + ] }, { "id": "wclock", diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 5560f00bc..acf786e65 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Modified data format to include daily summaries diff --git a/apps/health/README.md b/apps/health/README.md index 0ba0d8228..456143844 100644 --- a/apps/health/README.md +++ b/apps/health/README.md @@ -28,7 +28,6 @@ to grab historical health info. ## TODO -* **Extend file format to include combined data for each day (to make graphs faster)** * `interface` page for desktop to allow data to be viewed and exported in common formats * More features in app: * Step counting goal (ensure pedometers use this) diff --git a/apps/health/app.js b/apps/health/app.js index f2df52972..e6fea91b2 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -11,7 +11,8 @@ function menuStepCount() { E.showMenu({ "":{title:"Step Counting"}, "< Back":()=>menuMain(), - "per hour":()=>stepsPerHour() + "per hour":()=>stepsPerHour(), + "per day":()=>stepsPerDay() }); } @@ -19,7 +20,8 @@ function menuMovement() { E.showMenu({ "":{title:"Movement"}, "< Back":()=>menuMain(), - "per hour":()=>movementPerHour() + "per hour":()=>movementPerHour(), + "per day":()=>movementPerDay(), }); } @@ -40,6 +42,23 @@ function stepsPerHour() { Bangle.setUI("updown", ()=>menuStepCount()); } +function stepsPerDay() { + E.showMessage("Loading..."); + var data = new Uint16Array(24); + require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps); + g.clear(1); + Bangle.drawWidgets(); + g.reset(); + require("graph").drawBar(g, data, { + y:24, + miny: 0, + axes : true, + gridx : 5, + gridy : 2000 + }); + Bangle.setUI("updown", ()=>menuStepCount()); +} + function movementPerHour() { E.showMessage("Loading..."); var data = new Uint16Array(24); @@ -57,6 +76,23 @@ function movementPerHour() { Bangle.setUI("updown", ()=>menuStepCount()); } +function movementPerDay() { + E.showMessage("Loading..."); + var data = new Uint16Array(24); + require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement); + g.clear(1); + Bangle.drawWidgets(); + g.reset(); + require("graph").drawBar(g, data, { + y:24, + miny: 0, + axes : true, + gridx : 5, + ylabel : null + }); + Bangle.setUI("updown", ()=>menuStepCount()); +} + Bangle.loadWidgets(); Bangle.drawWidgets(); menuMain(); diff --git a/apps/health/boot.js b/apps/health/boot.js index d6b84ce98..0da4af086 100644 --- a/apps/health/boot.js +++ b/apps/health/boot.js @@ -4,7 +4,7 @@ Bangle.on("health", health => { const DB_RECORD_LEN = 4; const DB_RECORDS_PER_HR = 6; - const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24; + const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24 + 1/*summary*/; const DB_RECORDS_PER_MONTH = DB_RECORDS_PER_DAY*31; const DB_HEADER_LEN = 8; const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN; @@ -17,6 +17,12 @@ Bangle.on("health", health => { (DB_RECORDS_PER_HR*d.getHours()) + (0|(d.getMinutes()*DB_RECORDS_PER_HR/60)); } + function getRecordData(health) { + return String.fromCharCode( + health.steps>>8,health.steps&255, // 16 bit steps + health.bpm, // 8 bit bpm + Math.min(health.movement / 8, 255)); // movement + } var rec = getRecordIdx(d); var fn = getRecordFN(d); @@ -30,9 +36,30 @@ Bangle.on("health", health => { } else { require("Storage").write(fn, "HEALTH1\0", 0, DB_FILE_LEN); // header } - var recordData = String.fromCharCode( - health.steps>>8,health.steps&255, // 16 bit steps - health.bpm, // 8 bit bpm - Math.min(health.movement / 8, 255)); // movement - require("Storage").write(fn, recordData, DB_HEADER_LEN+(rec*DB_RECORD_LEN), DB_FILE_LEN); + var recordPos = DB_HEADER_LEN+(rec*DB_RECORD_LEN); + require("Storage").write(fn, getRecordData(health), recordPos, DB_FILE_LEN); + if (rec%DB_RECORDS_PER_DAY != DB_RECORDS_PER_DAY-1) return; + // we're at the end of the day. Read in all of the data for the day and sum it up + var sumPos = recordPos + DB_RECORD_LEN; // record after the current one is the sum + if (f.substr(sumPos, DB_RECORD_LEN)!="\xFF\xFF\xFF\xFF") { + print("HEALTH ERR: Daily summary already written!"); + return; + } + health = { steps:0, bpm:0, movement:0, records:0}; + var records = DB_RECORDS_PER_HR*24; + for (var i=0;i Date: Fri, 29 Oct 2021 13:55:57 +0100 Subject: [PATCH 0435/1062] banglejs2 support for widgets --- apps.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index a3c77ff00..edc70c8ee 100644 --- a/apps.json +++ b/apps.json @@ -1424,7 +1424,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widclk.wid.js","url":"widget.js"} ] @@ -1633,7 +1633,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,tool,system", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widver.wid.js","url":"widget.js"} ] @@ -1788,7 +1788,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,tools", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widmp.wid.js","url":"widget.js"} ] @@ -3439,7 +3439,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,gps", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"widgps.wid.js","url":"widget.js"} @@ -3965,7 +3965,7 @@ "icon": "widclkbttm.png", "type": "widget", "tags": "widget", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} From 7747ec8a9ef7d18a67af3c0adc7bbeaf0a4dd461 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Fri, 29 Oct 2021 21:37:38 +0800 Subject: [PATCH 0436/1062] Create app.js --- apps/authentiwatch/app.js | 263 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 apps/authentiwatch/app.js diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js new file mode 100644 index 000000000..19c54a175 --- /dev/null +++ b/apps/authentiwatch/app.js @@ -0,0 +1,263 @@ +const tokenentryheight = 46; +// Hash functions +const crypto = require("crypto"); +const sha1 = crypto.SHA1; +const sha224 = crypto.SHA224; +const sha256 = crypto.SHA256; +const sha384 = crypto.SHA384; +const sha512 = crypto.SHA512; + +var tokens = require("Storage").readJSON("authentiwatch.tokens.json", true) || [ + {algorithm:"SHA512",digits:8,period:60,secret:"aaaa aaaa aaaa aaaa",label:"AgAgAg"}, + {algorithm:"SHA1",digits:6,period:30,secret:"bbbb bbbb bbbb bbbb",label:"BgBgBg"}, + {algorithm:"SHA1",digits:6,period:30,secret:"6crw upgx ntjb 3wuj",label:"Discord"}, + {algorithm:"SHA1",digits:6,period:60,secret:"yyyy yyyy yyyy yyyy",label:"YgYgYg"}, + {algorithm:"SHA1",digits:8,period:30,secret:"zzzz zzzz zzzz zzzz",label:"ZgZgZg"}, +]; + +// QR Code Text +// +// Example: +// +// otpauth://totp/${url}:AA_${algorithm}_${digits}dig_${period}s@${url}?algorithm=${algorithm}&digits=${digits}&issuer=${url}&period=${period}&secret=${secret} +// +// ${algorithm} : one of SHA1 / SHA256 / SHA512 +// ${digits} : one of 6 / 8 +// ${period} : one of 30 / 60 +// ${url} : a domain name "example.com" +// ${secret} : the seed code + +function b32decode(seedstr) { + // RFC4648 + var i, buf = 0, bitcount = 0, retstr = ""; + for (i in seedstr) { + let c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(seedstr.charAt(i).toUpperCase(), 0); + if (c != -1) { + buf <<= 5; + buf |= c; + bitcount += 5; + if (bitcount >= 8) { + retstr += String.fromCharCode(buf >> (bitcount - 8)); + buf &= (0xFF >> (16 - bitcount)); + bitcount -= 8; + } + } + } + if (bitcount > 0) { + retstr += String.fromCharCode(buf << (8 - bitcount)); + } + var retbuf = new Uint8Array(retstr.length); + for (i in retstr) { + retbuf[i] = retstr.charCodeAt(i); + } + return retbuf; +} +function do_hmac(key, message, algo) { + var sha, retsz, blksz; + if (algo == "SHA512") { + sha = sha512; + retsz = 64; + blksz = 128; + } else if (algo == "SHA384") { + sha = sha384; + retsz = 48; + blksz = 128; + } else if (algo == "SHA256") { + sha = sha256; + retsz = 32; + blksz = 64; + } else if (algo == "SHA224") { + sha = sha224; + retsz = 28; + blksz = 64; + } else { + sha = sha1; + retsz = 20; + blksz = 64; + } + // RFC2104 + if (key.length > blksz) { + key = sha(key); + } + var istr = new Uint8Array(blksz + message.length); + var ostr = new Uint8Array(blksz + retsz); + for (var i = 0; i < blksz; ++i) { + let c = (i < key.length) ? key[i] : 0; + istr[i] = c ^ 0x36; + ostr[i] = c ^ 0x5C; + } + istr.set(message, blksz); + ostr.set(sha(istr), blksz); + var ret = sha(ostr); + // RFC4226 dynamic truncation + var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4); + return v.getUint32(0) & 0x7FFFFFFF; +} +function hotp_timed(seed, digits, period, algo) { + // RFC6238 + var d = new Date(); + var seconds = Math.floor(d.getTime() / 1000); + var tick = Math.floor(seconds / period); + var msg = new Uint8Array(8); + var v = new DataView(msg.buffer); + v.setUint32(0, tick >> 16 >> 16); + v.setUint32(4, tick & 0xFFFFFFFF); + var hash = do_hmac(b32decode(seed), msg, algo.toUpperCase()); + var ret = "" + hash % Math.pow(10, digits); + while (ret.length < digits) { + ret = "0" + ret; + } + return {hotp:ret, next:(tick + 1) * period * 1000}; +} + +var state = { + listy: 0, + curtoken:-1, + nextTime:0, + otp:"", + rem:0 +}; + +function drawToken(id, r) { + var x1 = r.x; + var y1 = r.y; + var x2 = r.x + r.w - 1; + var y2 = r.y + r.h - 1; + var ylabel; + if (id == state.curtoken) { + // current token + g.setColor(g.theme.fgH); + g.setBgColor(g.theme.bgH); + g.setFont6x15(1); + // center just below top line + g.setFontAlign(0, -1, 0); + ylabel = y1 + 2; + } else { + g.setColor(g.theme.fg); + g.setBgColor(g.theme.bg); + g.setFont6x15(2); + // center in box + g.setFontAlign(0, 0, 0); + ylabel = (y1 + y2) / 2; + } + g.clearRect(x1, y1, x2, y2); + g.drawString(tokens[id].label, x2 / 2, ylabel, false); + if (id == state.curtoken) { + // digits just below label + g.setFont6x15(2); + g.drawString(state.otp, x2 / 2, y1 + 17, false); + // draw progress bar + let xr = Math.floor(g.getWidth() * state.rem / tokens[id].period); + g.fillRect(x1, y2 - 4, xr, y2 - 1); + } + // shaded lines top and bottom + if (g.theme.dark) { + g.setColor(0.25, 0.25, 0.25); + } else { + g.setColor(0.75, 0.75, 0.75); + } + g.drawLine(x1, y1, x2, y1); + g.drawLine(x1, y2, x2, y2); +} + +function draw() { + if (state.curtoken != -1) { + var t = tokens[state.curtoken]; + var d = new Date(); + if (d.getTime() > state.nextTime) { + try { + var r = hotp_timed(t.secret, t.digits, t.period, t.algorithm); + state.nextTime = r.next; + state.otp = r.hotp; + } catch (err) { + state.nextTime = 0; + state.otp = "Not supported"; + } + } + state.rem = Math.max(0, Math.floor((state.nextTime - d.getTime()) / 1000)); + } + if (tokens.length > 0) { + var drewcur = false; + var id = Math.floor(state.listy / tokenentryheight); + var y = id * tokenentryheight - state.listy; + while (id < tokens.length && y < g.getHeight()) { + drawToken(id, {x:0, y:y, w:g.getWidth(), h:tokenentryheight}); + if (id == state.curtoken && state.nextTime != 0) { + drewcur = true; + } + id += 1; + y += tokenentryheight; + } + if (drewcur) { + if (state.drawtimer) { + clearTimeout(state.drawtimer); + } + state.drawtimer = setTimeout(draw, 1000); + } + } else { + g.setFont6x15(2); + g.setFontAlign(0, 0, 0); + g.drawString("No tokens", g.getWidth() / 2, g.getHeight() / 2, false); + } +} + +function onTouch(zone, e) { + var id = Math.floor((state.listy + e.y) / tokenentryheight); + if (id == state.curtoken) { + id = -1; + } + if (state.curtoken != id) { + if (id != -1) { + var y = id * tokenentryheight - state.listy; + if (y < 0) { + state.listy += y; + y = 0; + } + y += tokenentryheight; + if (y > g.getHeight()) { + state.listy += (y - g.getHeight()); + } + } + state.nextTime = 0; + state.curtoken = id; + draw(); + } +} + +function onDrag(e) { + if (e.x > g.getWidth() || e.y > g.getHeight()) return; + if (e.dx == 0 && e.dy == 0) return; + var maxy = tokens.length * tokenentryheight - g.getHeight(); + var newy = state.listy - e.dy; + if (newy > maxy) { + newy = maxy; + } + if (newy < 0) { + newy = 0; + } + if (newy != state.listy) { + state.listy = newy; + draw(); + } +} + +function onSwipe(e) { + if (e == 1) { + Bangle.showLauncher(); + } +} + +function tokenSelected(id) { + state.curtoken = (id == state.curtoken) ? -1 : id; + scroller.drawMenu(); +} + +Bangle.on('touch', onTouch); +Bangle.on('drag' , onDrag ); +Bangle.on('swipe', onSwipe); + +// Clear the screen once, at startup +g.clear(); +draw(); + +//var scroller = E.showScroller({h:tokenentryheight,c:tokens.length,draw:drawToken,select:tokenSelected}); From d2fd5af3e6741e811c66279b47ca74d1c9a79361 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Fri, 29 Oct 2021 21:41:01 +0800 Subject: [PATCH 0437/1062] Create app-icon.js --- apps/authentiwatch/app-icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/authentiwatch/app-icon.js diff --git a/apps/authentiwatch/app-icon.js b/apps/authentiwatch/app-icon.js new file mode 100644 index 000000000..27ced695e --- /dev/null +++ b/apps/authentiwatch/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mUywkBiIADCxoTFAAcQGBwY/DDQIKDBiMDDCgGCBI4YMGAIDFDCAFEBQwYLFgIYEGQgYMApoYJGAJjFMogYMSQgCDDBwDCY4oMEDBZgHHQQYQf4oYVBgwYQBogYPPYZpFDBKMEDAbdDCxT9IDYIFFABqSEAogySQYoWNFgrFDJZoQBJggYRBwhLGDBwyFDCZGEDCYAEDGrIMbwhnGDEpLGAwxlLFQgQDJiYoFDDAZDDCpMDMpQOCNxQYNBo4KKBpwYYBYJ8NeJgYkLBQY8UYQXVGQIwN")) From e60f6447a5db602a8dcacb5bf481baffa0527852 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Fri, 29 Oct 2021 21:46:35 +0800 Subject: [PATCH 0438/1062] Add authentiwatch --- apps.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps.json b/apps.json index edc70c8ee..ca0919db4 100644 --- a/apps.json +++ b/apps.json @@ -4183,5 +4183,18 @@ { "name": "qalarm.wid.js", "url": "widget.js" } ], "data": [{ "name": "qalarm.json" }] + }, + { + "id": "authentiwatch", + "name": "2FA Authenticator", + "shortName": "AuthWatch", + "icon": "app.png", + "version": "0.01", + "description": "Google Authenticator compatible tool.", + "tags": "", + "storage": [ + {"name":"authentiwatch.app.js","url":"authentiwatch.js"}, + {"name":"authentiwatch.img","url":"app-icon.js","evaluate":true} + ] } ] From 2cbb0b46854f95ddfcae0fe949e069269e7af8ca Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Fri, 29 Oct 2021 21:50:54 +0800 Subject: [PATCH 0439/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index ca0919db4..476be59c8 100644 --- a/apps.json +++ b/apps.json @@ -4193,7 +4193,7 @@ "description": "Google Authenticator compatible tool.", "tags": "", "storage": [ - {"name":"authentiwatch.app.js","url":"authentiwatch.js"}, + {"name":"authentiwatch.app.js","url":"app.js"}, {"name":"authentiwatch.img","url":"app-icon.js","evaluate":true} ] } From 38559fce4ea0329299426187fcfebd65e8ef5f90 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Fri, 29 Oct 2021 21:52:06 +0800 Subject: [PATCH 0440/1062] Update apps.json --- apps.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 476be59c8..12b8a658b 100644 --- a/apps.json +++ b/apps.json @@ -4191,7 +4191,8 @@ "icon": "app.png", "version": "0.01", "description": "Google Authenticator compatible tool.", - "tags": "", + "tags": "tool", + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"authentiwatch.app.js","url":"app.js"}, {"name":"authentiwatch.img","url":"app-icon.js","evaluate":true} From c7232538cf87003b3e8eccf9c85cb438fe8e4efc Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Fri, 29 Oct 2021 21:56:16 +0800 Subject: [PATCH 0441/1062] Add files via upload --- apps/authentiwatch/app.png | Bin 0 -> 964 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/authentiwatch/app.png diff --git a/apps/authentiwatch/app.png b/apps/authentiwatch/app.png new file mode 100644 index 0000000000000000000000000000000000000000..208fb63b348a960529406347f23aa9363271a388 GIT binary patch literal 964 zcmV;#13UbQP)xsf51t(*UgawVs8_EtK4Uj6!KPV)p)DWINn^zpuWJITwM`Wz=ZH; z2ril046 z3E3dFLQDKf@v|{0A?0HG9WmfPz)j#7(15DJ(x^HNV2#FC>_Rq)?OOy9_fSi2F4O^c zh*3yb`s@?i1G#?*97nz3G*AI-1NQs;>;U?JQQ$A|3-|SWjpa|Gb^Y>RlRNsji4&tdEh;84fQ2lbO2fOf*u2FEycbB zt*D*a^tSta0d^R;4l32GB87MU=)x?Tc_ z^|h?vqb7i@K2EKMGa~i|jokr$#;!mxrwQO8N=z+k{)BPdlVXL&?f@Sw>EMSUh+J(! ze54JWo_vBSF(P9Z=2FA7*|{al?Ff7; zxRIv_|3+7jI-`_?kQCuZU0r)BeL$5c3#irU3TkgL^^HBv)}h|ADb2E|(j=wv;iB8% z!-^PCd9Mqg$~WT0 Date: Fri, 29 Oct 2021 22:01:23 +0800 Subject: [PATCH 0442/1062] Update apps.json --- apps.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps.json b/apps.json index 12b8a658b..146608bdc 100644 --- a/apps.json +++ b/apps.json @@ -4193,6 +4193,7 @@ "description": "Google Authenticator compatible tool.", "tags": "tool", "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "storage": [ {"name":"authentiwatch.app.js","url":"app.js"}, {"name":"authentiwatch.img","url":"app-icon.js","evaluate":true} From 3235c642bd90a673f1e18d638acb02c9eae89569 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Fri, 29 Oct 2021 22:05:16 +0800 Subject: [PATCH 0443/1062] Update apps.json --- apps.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 146608bdc..bcea14b1d 100644 --- a/apps.json +++ b/apps.json @@ -4197,6 +4197,7 @@ "storage": [ {"name":"authentiwatch.app.js","url":"app.js"}, {"name":"authentiwatch.img","url":"app-icon.js","evaluate":true} - ] + ], + "data": [{"name":"authentiwatch.json"}] } ] From 9b2d8f8cc42cb777aa96fa9c75efcaac7b34c105 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Fri, 29 Oct 2021 22:10:43 +0800 Subject: [PATCH 0444/1062] Update app.js --- apps/authentiwatch/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index 19c54a175..d0d951e99 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -10,7 +10,8 @@ const sha512 = crypto.SHA512; var tokens = require("Storage").readJSON("authentiwatch.tokens.json", true) || [ {algorithm:"SHA512",digits:8,period:60,secret:"aaaa aaaa aaaa aaaa",label:"AgAgAg"}, {algorithm:"SHA1",digits:6,period:30,secret:"bbbb bbbb bbbb bbbb",label:"BgBgBg"}, - {algorithm:"SHA1",digits:6,period:30,secret:"6crw upgx ntjb 3wuj",label:"Discord"}, + {algorithm:"SHA1",digits:6,period:30,secret:"cccc cccc cccc cccc",label:"CgCgCg"}, + {algorithm:"SHA1",digits:6,period:30,secret:"xxxx xxxx xxxx xxxx",label:"XgXgXg"}, {algorithm:"SHA1",digits:6,period:60,secret:"yyyy yyyy yyyy yyyy",label:"YgYgYg"}, {algorithm:"SHA1",digits:8,period:30,secret:"zzzz zzzz zzzz zzzz",label:"ZgZgZg"}, ]; From 654e0b1e38246286e4d3d665eb98f1cfdea7f413 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Fri, 29 Oct 2021 22:25:26 +0800 Subject: [PATCH 0445/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index bcea14b1d..cba05c73a 100644 --- a/apps.json +++ b/apps.json @@ -4198,6 +4198,6 @@ {"name":"authentiwatch.app.js","url":"app.js"}, {"name":"authentiwatch.img","url":"app-icon.js","evaluate":true} ], - "data": [{"name":"authentiwatch.json"}] + "data": [{"name":"authentiwatch.tokens.json"}] } ] From 9b5bda19c557b052da0a2b7e1e567dc4d4bdc873 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Fri, 29 Oct 2021 22:27:14 +0800 Subject: [PATCH 0446/1062] Update apps.json --- apps.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps.json b/apps.json index cba05c73a..5c9ac4889 100644 --- a/apps.json +++ b/apps.json @@ -4192,6 +4192,7 @@ "version": "0.01", "description": "Google Authenticator compatible tool.", "tags": "tool", + "interface": "interface.html", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ From ba9025172403bf61d7ec0a500e140bce7010f29b Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Fri, 29 Oct 2021 22:36:30 +0800 Subject: [PATCH 0447/1062] Create interface.html --- apps/authentiwatch/interface.html | 50 +++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 apps/authentiwatch/interface.html diff --git a/apps/authentiwatch/interface.html b/apps/authentiwatch/interface.html new file mode 100644 index 000000000..154f3e150 --- /dev/null +++ b/apps/authentiwatch/interface.html @@ -0,0 +1,50 @@ + + + + + +

+ + + + + + + From f2f500e474109e91b8f83d5d2e76ee79bb09c194 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Fri, 29 Oct 2021 18:35:55 +0200 Subject: [PATCH 0448/1062] menusmall: add `wrap` option --- apps.json | 2 +- apps/menusmall/ChangeLog | 1 + apps/menusmall/boot.js | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps.json b/apps.json index edc70c8ee..ab56b2429 100644 --- a/apps.json +++ b/apps.json @@ -4059,7 +4059,7 @@ { "id": "menusmall", "name": "Small Menus", - "version": "0.01", + "version": "0.02", "description": "Replace Bangle.js 2's menus with a version that contains smaller text", "icon": "app.png", "type": "boot", diff --git a/apps/menusmall/ChangeLog b/apps/menusmall/ChangeLog index 5560f00bc..2f2e25462 100644 --- a/apps/menusmall/ChangeLog +++ b/apps/menusmall/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: add `wrap` option \ No newline at end of file diff --git a/apps/menusmall/boot.js b/apps/menusmall/boot.js index 59e47b178..7ee3dfda1 100644 --- a/apps/menusmall/boot.js +++ b/apps/menusmall/boot.js @@ -100,8 +100,10 @@ E.showMenu = function(items) { if (l.selectEdit) { var item = l.selectEdit; item.value -= (dir||1)*(item.step||1); - if (item.min!==undefined && item.valueitem.max) item.value = item.max; + if (item.min!==undefined && item.valueitem.max) + item.value = (item.wrap && item.min!==undefined) ? item.min : item.max; if (item.onchange) item.onchange(item.value); l.draw(options.selected,options.selected); } else { From 310bf60ed76bd7e36380ca6725a12c4ad6dc2efd Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Fri, 29 Oct 2021 18:45:30 +0200 Subject: [PATCH 0449/1062] menusmall: use Bangle.appRect --- apps/menusmall/ChangeLog | 2 +- apps/menusmall/boot.js | 21 ++++++++------------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/apps/menusmall/ChangeLog b/apps/menusmall/ChangeLog index 2f2e25462..6de3d41f4 100644 --- a/apps/menusmall/ChangeLog +++ b/apps/menusmall/ChangeLog @@ -1,2 +1,2 @@ 0.01: New App! -0.02: add `wrap` option \ No newline at end of file +0.02: add `wrap` option, use Bangle.appRect \ No newline at end of file diff --git a/apps/menusmall/boot.js b/apps/menusmall/boot.js index 7ee3dfda1..805413e2b 100644 --- a/apps/menusmall/boot.js +++ b/apps/menusmall/boot.js @@ -1,28 +1,23 @@ "";//not entirely sure why we need this - related to how bootupdate adds these to .boot0 E.showMenu = function(items) { - g.clear(1).flip(); // clear screen if no menu supplied - Bangle.drawWidgets(); + g.clearRect(Bangle.appRect); // clear screen if no menu supplied if (!items) { Bangle.setUI(); return; } - var w = g.getWidth(); - var h = g.getHeight(); + var menuItems = Object.keys(items); var options = items[""]; if (options) menuItems.splice(menuItems.indexOf(""),1); if (!(options instanceof Object)) options = {}; - options.fontHeight=14; - options.x=0; - options.x2=w-1; - options.y=24; - options.y2=h-12; + options.fontHeight = options.fontHeight|14; if (options.selected === undefined) options.selected = 0; - var x = 0|options.x; - var x2 = options.x2||(g.getWidth()-1); - var y = 0|options.y; - var y2 = options.y2||(g.getHeight()-1); + var ar = Bangle.appRect; + var x = ar.x; + var x2 = ar.x2; + var y = ar.y; + var y2 = ar.y2 - 11; // padding at end for arrow if (options.title) y += 15; var loc = require("locale"); From aeaf508897cf5c6434b947a3291ae479ce6ed330 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Oct 2021 20:21:12 +0100 Subject: [PATCH 0450/1062] general widget fixes and tweaks --- apps.json | 6 +++--- apps/widgps/ChangeLog | 1 + apps/widgps/widget.js | 6 +++--- apps/widmp/ChangeLog | 2 ++ apps/widmp/widget.js | 41 +++++++++++++++++------------------------ apps/widver/ChangeLog | 1 + apps/widver/widget.js | 20 +++++++------------- 7 files changed, 34 insertions(+), 43 deletions(-) diff --git a/apps.json b/apps.json index edc70c8ee..7316c7802 100644 --- a/apps.json +++ b/apps.json @@ -1628,7 +1628,7 @@ { "id": "widver", "name": "Firmware Version Widget", - "version": "0.02", + "version": "0.03", "description": "Display the version of the installed firmware in the top widget section.", "icon": "widget.png", "type": "widget", @@ -1783,7 +1783,7 @@ { "id": "widmp", "name": "Moon Phase Widget", - "version": "0.01", + "version": "0.02", "description": "Display the current moon phase in blueish for the northern hemisphere in eight phases", "icon": "widget.png", "type": "widget", @@ -3434,7 +3434,7 @@ { "id": "widgps", "name": "GPS Widget", - "version": "0.02", + "version": "0.03", "description": "Tiny widget to show the power on/off status of the GPS. Require firmware v2.08.167 or later", "icon": "widget.png", "type": "widget", diff --git a/apps/widgps/ChangeLog b/apps/widgps/ChangeLog index d80e09912..57bb53bb7 100644 --- a/apps/widgps/ChangeLog +++ b/apps/widgps/ChangeLog @@ -1,2 +1,3 @@ 0.01: First version 0.02: Don't break if running on 2v08 firmware (just don't display anything) +0.03: Fix positioning diff --git a/apps/widgps/widget.js b/apps/widgps/widget.js index 19be2abaf..6ef55e27b 100644 --- a/apps/widgps/widget.js +++ b/apps/widgps/widget.js @@ -4,11 +4,11 @@ function draw() { g.reset(); if (Bangle.isGPSOn()) { - g.setColor(1,0.8,0); // on = amber + g.setColor("#FD0"); // on = amber } else { - g.setColor(0.3,0.3,0.3); // off = grey + g.setColor("#888"); // off = grey } - g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), 10+this.x, 2+this.y); + g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), this.x, 2+this.y); } var timerInterval; diff --git a/apps/widmp/ChangeLog b/apps/widmp/ChangeLog index 5560f00bc..3996d9e74 100644 --- a/apps/widmp/ChangeLog +++ b/apps/widmp/ChangeLog @@ -1 +1,3 @@ 0.01: New App! +0.02: Fix position and overdraw bugs + Better memory usage, theme support diff --git a/apps/widmp/widget.js b/apps/widmp/widget.js index cebdb60f5..d8abc3a9c 100644 --- a/apps/widmp/widget.js +++ b/apps/widmp/widget.js @@ -1,20 +1,6 @@ -/* jshint esversion: 6 */ -(() => { - - const BLACK = 0, MOON = 0x41f, MC = 29.5305882, NM = 694039.09; - var r = 12, mx = 0, my = 0; - - var moon = { - 0: () => { g.reset().setColor(BLACK).fillRect(mx - r, my - r, mx + r, my + r);}, - 1: () => { moon[0](); g.setColor(MOON).drawCircle(mx, my, r);}, - 2: () => { moon[3](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, - 3: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx - r, my - r, mx, my + r);}, - 4: () => { moon[3](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, - 5: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r);}, - 6: () => { moon[7](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, - 7: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx, my - r, mx + r + r, my + r);}, - 8: () => { moon[7](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);} - }; +WIDGETS["widmoon"] = { area: "tr", width: 24, draw: function() { + const MC = 29.5305882, NM = 694039.09; + var r = 11, mx = this.x + 12; my = this.y + 12; function moonPhase(d) { var tmp, month = d.getMonth(), year = d.getFullYear(), day = d.getDate(); @@ -23,11 +9,18 @@ return Math.round(((tmp - (tmp | 0)) * 7)+1); } - function draw() { - mx = this.x; my = this.y + 12; - moon[moonPhase(Date())](); - } + const BLACK = g.theme.bg, MOON = 0x41f; + var moon = { + 0: () => { g.reset().setColor(BLACK).fillRect(mx - r, my - r, mx + r, my + r);}, + 1: () => { moon[0](); g.setColor(MOON).drawCircle(mx, my, r);}, + 2: () => { moon[3](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 3: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx - r, my - r, mx, my + r);}, + 4: () => { moon[3](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 5: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r);}, + 6: () => { moon[7](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 7: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx, my - r, mx + r, my + r);}, + 8: () => { moon[7](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);} + }; + moon[moonPhase(Date())](); +} }; - WIDGETS["widmoon"] = { area: "tr", width: 24, draw: draw }; - -})(); diff --git a/apps/widver/ChangeLog b/apps/widver/ChangeLog index 30581c35b..06bf45fbd 100644 --- a/apps/widver/ChangeLog +++ b/apps/widver/ChangeLog @@ -1,2 +1,3 @@ 0.01: New Widget 0.02: Display "Rel" (Release) instead of 'undefined' when there is no Build number. +0.03: Add theme support, lower memory usage diff --git a/apps/widver/widget.js b/apps/widver/widget.js index eb751ca23..b6a8b7432 100644 --- a/apps/widver/widget.js +++ b/apps/widver/widget.js @@ -1,15 +1,9 @@ -/* jshint esversion: 6 */ -(() => { - var width = 28, - ver = process.env.VERSION.split('.'); - +WIDGETS["version"] = { area: "tr", width: 28, draw: function() { + var ver = process.env.VERSION.split('.'); // Example: if ver is 2v11 instead of 2v10.142 write "Rel" (Release) instead of Build number - if(typeof ver[1] === 'undefined'){ver[1] = "Rel";} + if(typeof ver[1] === 'undefined') ver[1] = "Rel"; - function draw() { - g.reset().setColor(0, 0.5, 1).setFont("6x8", 1); - g.drawString(ver[0], this.x + 2, this.y + 4, true); - g.setFontAlign(0, -1, 0).drawString(ver[1], this.x + width / 2, this.y + 14, true); - } - WIDGETS["version"] = { area: "tr", width: width, draw: draw }; -})(); + g.reset().setColor((g.getBPP()<8)?(g.theme.dark?"#0ff":"#00f"):"#08f").setFont("6x8"); + g.drawString(ver[0], this.x + 2, this.y + 4, true); + g.setFontAlign(0, -1, 0).drawString(ver[1], this.x + this.width / 2, this.y + 14, true); +} }; From 6d27159cddb910921217cb2c4ca74fa14cb5d887 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Oct 2021 20:50:51 +0100 Subject: [PATCH 0451/1062] health: Settings to turn HRM on --- apps.json | 2 +- apps/health/ChangeLog | 1 + apps/health/README.md | 10 +++++++++- apps/health/app.js | 25 ++++++++++++++++++++++++- apps/health/boot.js | 17 +++++++++++++++++ 5 files changed, 52 insertions(+), 3 deletions(-) diff --git a/apps.json b/apps.json index 7316c7802..e3e65a296 100644 --- a/apps.json +++ b/apps.json @@ -49,7 +49,7 @@ { "id": "health", "name": "Health Tracking", - "version": "0.02", + "version": "0.03", "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)", "icon": "app.png", "tags": "tool,system", diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index acf786e65..9ce15a982 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Modified data format to include daily summaries +0.03: Settings to turn HRM on diff --git a/apps/health/README.md b/apps/health/README.md index 456143844..c69e2e45b 100644 --- a/apps/health/README.md +++ b/apps/health/README.md @@ -14,10 +14,18 @@ To view data, run the `Health` app from your watch. Stores: -* Heart rate (TODO) +* Heart rate * Step count * Movement +## Settings + +* **Heart Rt** - Whether to monitor heart rate or not + * **Off** - Don't turn HRM on, but record heart rate if the HRM was turned on by another app/widget + * **10 Min** - Turn HRM on every 10 minutes (for each heath entry) and turn it off after 2 minutes, or when a good reading is found + * **Always** - Keep HRM on all the time (more accurate recording, but reduces battery life to ~36 hours) + + ## Technical Info Once installed, the `health.boot.js` hooks onto the `Bangle.health` event and diff --git a/apps/health/app.js b/apps/health/app.js index e6fea91b2..6cc46298b 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -1,9 +1,32 @@ +function getSettings() { + return require("Storage").readJSON("health.json",1)||{}; +} + +function setSettings(s) { + require("Storage").writeJSON("health.json",s); +} + function menuMain() { E.showMenu({ "":{title:"Health Tracking"}, "< Back":()=>load(), "Step Counting":()=>menuStepCount(), - "Movement":()=>menuMovement() + "Movement":()=>menuMovement(), + "Settings":()=>menuSettings() + }); +} + +function menuSettings() { + var s=getSettings(); + E.showMenu({ + "":{title:"Health Tracking"}, + "< Back":()=>load(), + "Heart Rt":{ + value : 0|s.hrm, + min : 0, max : 2, + format : v=>["Off","10 mins","Always"][v], + onchange : v => { s.hrm=v;setSettings(s); } + } }); } diff --git a/apps/health/boot.js b/apps/health/boot.js index 0da4af086..200a89f67 100644 --- a/apps/health/boot.js +++ b/apps/health/boot.js @@ -1,3 +1,20 @@ +(function(){ + var settings = require("Storage").readJSON("health.json",1)||{}; + var hrm = 0|settings.hrm; + Bangle.setHRMPower(hrm!=0, "health"); + if (hrm==1) { + function onHealth() { + Bangle.setHRMPower(1, "health"); + setTimeout(()=>Bangle.setHRMPower(0, "health"),2*60000); // give it 2 minutes + } + Bangle.on("health", onHealth); + Bangle.on('HRM', h => { + if (h.confidence>80) Bangle.setHRMPower(0, "health"); + }); + onHealth(); + } +})(); + Bangle.on("health", health => { // ensure we write health info for *last* block var d = new Date(Date.now() - 590000); From 77a74fbeb7a2f7e446ab09d3451cfe7ef6e7aa4d Mon Sep 17 00:00:00 2001 From: David Skrabal Date: Fri, 29 Oct 2021 17:37:33 -0400 Subject: [PATCH 0452/1062] Update README.md Updated links to travis-ci.org to app.travis-ci.com per "ORG Shutdown" https://blog.travis-ci.com/2021-05-07-orgshutdown --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 531114a34..21f5dbff9 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ Bangle.js App Loader (and Apps) ================================ -[![Build Status](https://travis-ci.org/espruino/BangleApps.svg?branch=master)](https://travis-ci.org/espruino/BangleApps) +[![Build Status](https://app.travis-ci.com/espruino/BangleApps.svg?branch=master)](https://app.travis-ci.com/github/espruino/BangleApps) * Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps) -* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/) +* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/) **All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By submitting code to this repository you confirm that you are happy with it being MIT licensed, From 36afdad86b09d54bf23891ec33cafe0b50656e79 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Sun, 31 Oct 2021 22:15:25 +0800 Subject: [PATCH 0453/1062] Update app.js Add widget support. Add custom font for digits. --- apps/authentiwatch/app.js | 40 ++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index d0d951e99..cd35712b7 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -7,15 +7,19 @@ const sha256 = crypto.SHA256; const sha384 = crypto.SHA384; const sha512 = crypto.SHA512; -var tokens = require("Storage").readJSON("authentiwatch.tokens.json", true) || [ +var tokens = require("Storage").readJSON("authentiwatch.json", true) || [ {algorithm:"SHA512",digits:8,period:60,secret:"aaaa aaaa aaaa aaaa",label:"AgAgAg"}, {algorithm:"SHA1",digits:6,period:30,secret:"bbbb bbbb bbbb bbbb",label:"BgBgBg"}, {algorithm:"SHA1",digits:6,period:30,secret:"cccc cccc cccc cccc",label:"CgCgCg"}, - {algorithm:"SHA1",digits:6,period:30,secret:"xxxx xxxx xxxx xxxx",label:"XgXgXg"}, {algorithm:"SHA1",digits:6,period:60,secret:"yyyy yyyy yyyy yyyy",label:"YgYgYg"}, {algorithm:"SHA1",digits:8,period:30,secret:"zzzz zzzz zzzz zzzz",label:"ZgZgZg"}, ]; +Graphics.prototype.setFontInconsolata = function(scale) { + // Actual height 21 (25 - 5) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAADgAAADgAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAHwAAAfgAAB+AAAH4AAA/gAAD+AAAPwAAA/AAAB8AAABwAAAAAAAAAAAAAAAAAAAA/gAAH/8AAf//AA+A/gA4BzgAwHhgAwPBgAwcBgA54DgA/wPgAf//AAH/8AAA/gAAAAAAAAAAAAAAAAAIAAAAMAAAAYAAAAYAAAA4AAAA///gA///gA///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAOABgAeADgA4AHgAwAPgAwAdgAwA5gAwDxgAwHhgA8fBgAf+BgAP4BgADgBgAAAAAAAAAAAAAAAAAADAAYAHAA4EDgAwMBgAwMBgAwMBgAwOBgA4+BgA///gAfz/AAHB+AAAAAAAAAAAAAAAAAAAwAAABwAAAHwAAAPwAAA+wAAB4wAAHwwAAPAwAA///gA///gA///gAAAwAAAAwAAAAQAAAAAAAAAAAAP8GAA/8HAA/8DgAwYBgAwYBgAwYBgAwYBgAwYBgAwcHgAwP/AAwH+AAAD4AAAAAAAAAAAAAHAAAD/8AAP/+AAfufAA8cDgA4YBgAwYBgAwYBgAwYBgAwcHgA4P/AAYH+AAAB4AAAAAAAAAAAAAAAAAwAAAAwAAAAwAAAAwAHgAwA/gAwH/gAwf4AAz/AAA/4AAA/AAAA8AAAAgAAAAAAAAAAAAAAAAcAAHB+AAfj/AA/3DgA4+BgAwcBgAwcBgAwcBgAw+BgA/3DgAfz/AAPB+AAAA8AAAAAAAAAAAABAAAAP4BAAf8DgA8eDgAwGBgAwGBgAwGBgAwGBgA4GDgA+OfAAf/+AAP/4AAB/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBgAAcDgAAcDgAAYBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="), 46, 15, 30+(scale<<8)+(1<<16)); +}; + // QR Code Text // // Example: @@ -32,7 +36,7 @@ function b32decode(seedstr) { // RFC4648 var i, buf = 0, bitcount = 0, retstr = ""; for (i in seedstr) { - let c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(seedstr.charAt(i).toUpperCase(), 0); + var c = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".indexOf(seedstr.charAt(i).toUpperCase(), 0); if (c != -1) { buf <<= 5; buf |= c; @@ -83,7 +87,7 @@ function do_hmac(key, message, algo) { var istr = new Uint8Array(blksz + message.length); var ostr = new Uint8Array(blksz + retsz); for (var i = 0; i < blksz; ++i) { - let c = (i < key.length) ? key[i] : 0; + var c = (i < key.length) ? key[i] : 0; istr[i] = c ^ 0x36; ostr[i] = c ^ 0x5C; } @@ -125,6 +129,8 @@ function drawToken(id, r) { var x2 = r.x + r.w - 1; var y2 = r.y + r.h - 1; var ylabel; + g.setClipRect(Math.max(x1, Bangle.appRect.x ), Math.max(y1, Bangle.appRect.y ), + Math.min(x2, Bangle.appRect.x2), Math.min(y2, Bangle.appRect.y2)); if (id == state.curtoken) { // current token g.setColor(g.theme.fgH); @@ -145,8 +151,8 @@ function drawToken(id, r) { g.drawString(tokens[id].label, x2 / 2, ylabel, false); if (id == state.curtoken) { // digits just below label - g.setFont6x15(2); - g.drawString(state.otp, x2 / 2, y1 + 17, false); + g.setFontInconsolata(1); + g.drawString(state.otp, x2 / 2, y1 + 12, false); // draw progress bar let xr = Math.floor(g.getWidth() * state.rem / tokens[id].period); g.fillRect(x1, y2 - 4, xr, y2 - 1); @@ -159,6 +165,7 @@ function drawToken(id, r) { } g.drawLine(x1, y1, x2, y1); g.drawLine(x1, y2, x2, y2); + g.setClipRect(0, 0, g.getWidth(), g.getHeight()); } function draw() { @@ -180,9 +187,9 @@ function draw() { if (tokens.length > 0) { var drewcur = false; var id = Math.floor(state.listy / tokenentryheight); - var y = id * tokenentryheight - state.listy; + var y = id * tokenentryheight + Bangle.appRect.y - state.listy; while (id < tokens.length && y < g.getHeight()) { - drawToken(id, {x:0, y:y, w:g.getWidth(), h:tokenentryheight}); + drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenentryheight}); if (id == state.curtoken && state.nextTime != 0) { drewcur = true; } @@ -203,7 +210,7 @@ function draw() { } function onTouch(zone, e) { - var id = Math.floor((state.listy + e.y) / tokenentryheight); + var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / tokenentryheight); if (id == state.curtoken) { id = -1; } @@ -215,8 +222,8 @@ function onTouch(zone, e) { y = 0; } y += tokenentryheight; - if (y > g.getHeight()) { - state.listy += (y - g.getHeight()); + if (y > Bangle.appRect.h) { + state.listy += (y - Bangle.appRect.h); } } state.nextTime = 0; @@ -228,7 +235,7 @@ function onTouch(zone, e) { function onDrag(e) { if (e.x > g.getWidth() || e.y > g.getHeight()) return; if (e.dx == 0 && e.dy == 0) return; - var maxy = tokens.length * tokenentryheight - g.getHeight(); + var maxy = tokens.length * tokenentryheight - Bangle.appRect.h; var newy = state.listy - e.dy; if (newy > maxy) { newy = maxy; @@ -248,17 +255,12 @@ function onSwipe(e) { } } -function tokenSelected(id) { - state.curtoken = (id == state.curtoken) ? -1 : id; - scroller.drawMenu(); -} - Bangle.on('touch', onTouch); Bangle.on('drag' , onDrag ); Bangle.on('swipe', onSwipe); +Bangle.loadWidgets(); // Clear the screen once, at startup g.clear(); draw(); - -//var scroller = E.showScroller({h:tokenentryheight,c:tokens.length,draw:drawToken,select:tokenSelected}); +Bangle.drawWidgets(); From e373988fea6c4b9fba9a05078342fc539fc6f18e Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Sun, 31 Oct 2021 22:16:46 +0800 Subject: [PATCH 0454/1062] Update interface.html Implement token editing. --- apps/authentiwatch/interface.html | 296 +++++++++++++++++++++++++----- 1 file changed, 252 insertions(+), 44 deletions(-) diff --git a/apps/authentiwatch/interface.html b/apps/authentiwatch/interface.html index 154f3e150..255c5e4ca 100644 --- a/apps/authentiwatch/interface.html +++ b/apps/authentiwatch/interface.html @@ -1,50 +1,258 @@ + - - - - -
- - - - - + + + - +function onInit() { + loadTokens(); + updateTokens(); +} + + + +

Authentiwatch

+
+

No watch comms.

+
+
+

THIS IS CURRENTLY BETA - PLEASE USE THE NORMAL FIRMWARE UPDATE + INSTRUCTIONS FOR BANGLE.JS 1 AND BANGLE.JS 2

+
diff --git a/loader.js b/loader.js index 6528ffc98..90c1c5d96 100644 --- a/loader.js +++ b/loader.js @@ -14,6 +14,10 @@ if (window.location.host=="banglejs.com") { var RECOMMENDED_VERSION = "2v10"; // could check http://www.espruino.com/json/BANGLEJS.json for this +// We're only interested in Bangles +DEVICEINFO = DEVICEINFO.filter(x=>x.id.startsWith("BANGLEJS")); + +// Set up source code URL (function() { let username = "espruino"; let githubMatch = window.location.href.match(/\/(\w+)\.github\.io/); @@ -21,6 +25,7 @@ var RECOMMENDED_VERSION = "2v10"; Const.APP_SOURCECODE_URL = `https://github.com/${username}/BangleApps/tree/master/apps`; })(); +// When a device is found, filter the apps accordingly function onFoundDeviceInfo(deviceId, deviceVersion) { if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") { showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); @@ -33,4 +38,110 @@ function onFoundDeviceInfo(deviceId, deviceVersion) { if (deviceId == "BANGLEJS2") { Const.MESSAGE_RELOAD = 'Hold button\nto reload'; } + + // check against features shown? + filterAppsForDevice(deviceId); + /* if we'd saved a device ID but this device is different, ensure + we ask again next time */ + var savedDeviceId = getSavedDeviceId(); + if (savedDeviceId!==undefined && savedDeviceId!=deviceId) + setSavedDeviceId(undefined); } + +var originalAppJSON = undefined; +function filterAppsForDevice(deviceId) { + if (originalAppJSON===undefined) + originalAppJSON = appJSON; + + var device = DEVICEINFO.find(d=>d.id==deviceId); + // set the device dropdown + document.querySelector(".devicetype-nav span").innerText = device ? device.name : "All apps"; + + if (!device) { + if (deviceId!==undefined) + showToast(`Device ID ${deviceId} not recognised. Some apps may not work`, "warning"); + appJSON = originalAppJSON; + } else { + // Now filter apps + appJSON = originalAppJSON.filter(app => { + var supported = ["BANGLEJS"]; + if (!app.supports) { + console.log(`App ${app.id} doesn't include a 'supports' field - ignoring`); + return false; + } + if (app.supports.includes(deviceId)) return true; + //console.log(`Dropping ${app.id} because ${deviceId} is not in supported list ${app.supports.join(",")}`); + return false; + }); + } + refreshLibrary(); +} + +// If 'remember' was checked in the window below, this is the device +function getSavedDeviceId() { + let deviceId = localStorage.getItem("deviceId"); + if (("string"==typeof deviceId) && DEVICEINFO.find(d=>d.id == deviceId)) + return deviceId; + return undefined; +} + +function setSavedDeviceId(deviceId) { + localStorage.setItem("deviceId", deviceId); +} + +// At boot, show a window to choose which type of device you have... +window.addEventListener('load', (event) => { + let deviceId = getSavedDeviceId() + if (deviceId !== undefined) + return filterAppsForDevice(deviceId); + + var html = `
+ ${DEVICEINFO.map(d=>` +
+
+
+
${d.name}
+ +
+
+ ${d.name} +
+
+
`).join("\n")} +
+
+
+ +
+
+
`; + showPrompt("Which Bangle.js?",html,{},false); + htmlToArray(document.querySelectorAll(".devicechooser")).forEach(button => { + button.addEventListener("click",event => { + let rememberDevice = document.getElementById("remember_device").checked; + + let button = event.currentTarget; + let deviceId = button.getAttribute("deviceid"); + hidePrompt(); + console.log("Chosen device", deviceId); + setSavedDeviceId(rememberDevice ? deviceId : undefined); + filterAppsForDevice(deviceId); + }); + }); +}); + +// Hook onto device chooser dropdown +window.addEventListener('load', (event) => { + htmlToArray(document.querySelectorAll(".devicetype-nav .menu-item")).forEach(button => { + button.addEventListener("click", event => { + var a = event.target; + var deviceId = a.getAttribute("dt")||undefined; + filterAppsForDevice(deviceId); // also sets the device dropdown + setSavedDeviceId(undefined); // ask at startup next time + document.querySelector(".devicetype-nav span").innerText = a.innerText; + }); + }); +}); From a89545e02a89871591955cd4dfcc479884d45ff7 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 20 Oct 2021 18:35:03 +0100 Subject: [PATCH 0288/1062] Stopwatch: touch based stopwatch for Bangle 2 --- apps.json | 13 ++ apps/stopwatch/ChangeLog | 1 + apps/stopwatch/README.md | 20 +++ apps/stopwatch/pause-24.png | Bin 0 -> 161 bytes apps/stopwatch/pause-24a.png | Bin 0 -> 123 bytes apps/stopwatch/play-24.png | Bin 0 -> 297 bytes apps/stopwatch/stop-24.png | Bin 0 -> 177 bytes apps/stopwatch/stop-24a.png | Bin 0 -> 192 bytes apps/stopwatch/stopwatch.app.js | 222 +++++++++++++++++++++++++++++++ apps/stopwatch/stopwatch.icon.js | 1 + apps/stopwatch/stopwatch.png | Bin 0 -> 1566 bytes 11 files changed, 257 insertions(+) create mode 100644 apps/stopwatch/ChangeLog create mode 100644 apps/stopwatch/README.md create mode 100644 apps/stopwatch/pause-24.png create mode 100644 apps/stopwatch/pause-24a.png create mode 100644 apps/stopwatch/play-24.png create mode 100644 apps/stopwatch/stop-24.png create mode 100644 apps/stopwatch/stop-24a.png create mode 100644 apps/stopwatch/stopwatch.app.js create mode 100644 apps/stopwatch/stopwatch.icon.js create mode 100644 apps/stopwatch/stopwatch.png diff --git a/apps.json b/apps.json index 269e9577e..84e95330c 100644 --- a/apps.json +++ b/apps.json @@ -3612,5 +3612,18 @@ {"name":"ffcniftya.app.js","url":"app.js"}, {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "stopwatch", + "name": "Stopwatch", + "shortName":"Stopwatch", + "icon": "stopwatch.png", + "version":"0.01", + "description": "A touch controlled stopwatch for Bangle 2", + "readme": "README.md", + "tags": "tool, b2", + "storage": [ + {"name":"stopwatch.app.js","url":"stopwatch.js"}, + {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} + ] } ] diff --git a/apps/stopwatch/ChangeLog b/apps/stopwatch/ChangeLog new file mode 100644 index 000000000..9db0e26c5 --- /dev/null +++ b/apps/stopwatch/ChangeLog @@ -0,0 +1 @@ +0.01: first release diff --git a/apps/stopwatch/README.md b/apps/stopwatch/README.md new file mode 100644 index 000000000..1924cc343 --- /dev/null +++ b/apps/stopwatch/README.md @@ -0,0 +1,20 @@ +# Stopwatch + +A touch screen based stop watch for Bangle 2 + +## Screenshots + +## Features + +* Feature A +* Feature B + +## Future features + +I'm keen to complete this project with + +* Ability to dismiss the app and leave it running in the background +* A small widget to show the elapsed time on the curren active clock +* Laptimes, with a way to view all the laptimes on a scrollable screen + + diff --git a/apps/stopwatch/pause-24.png b/apps/stopwatch/pause-24.png new file mode 100644 index 0000000000000000000000000000000000000000..eb3d8feaa578ddf2868ca2ebfe6c1016b89076b6 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt^(o-U3d z9-YYv609r?%6x@>Y;0_-CL6c&@bIi(kYfnsC7S$X2lDQBZoM(BvGJk3)I0Y{?~g0x zcb`>IOUhLd?};+VI=F&$u~<)(1Fu1X=sArBW(KQ1!Ho<&4aPtl7(8A5T-G@yGywo& C|1Q-4 literal 0 HcmV?d00001 diff --git a/apps/stopwatch/pause-24a.png b/apps/stopwatch/pause-24a.png new file mode 100644 index 0000000000000000000000000000000000000000..7838ef640b48c569532e978aa4ea89960d1b0533 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt@zo-U3d z9-YYv60C>i3?5DV?eCzgSjh$gYC*9sS`u3mt~3R5yXi{si1joHGB8XvWs;ff*UPx#<4Ht8R7gv;*0D;&KoADtFVAWvTu`u3ff&Q zddffoheq6(ovFkBk;%?%8p+ip=j#J@@r2_VcpvbIcRXMd*JGgHfc4CCOxm~l7>bnu zk@<#w%y2gZDFwRBN1S4cml9|w5Sc5yVh=}uA^!o^Gw-p1+qR8Ayso-U3d z9-YYv609mr{A_G($9Z^o{+~S3zaZ@aOTUJJ%uS&=49hxV8Wzs)WIEizWU{@XmBX~9 zsYh`BlMDQd0=eB9SdTnm-t%&ffW$Aymb`<>8oqgfy*X@Q4-Ps+&UM&uNRk!kj6GuR V;!)cRW&mwu@O1TaS?83{1OQ$|I{5$q literal 0 HcmV?d00001 diff --git a/apps/stopwatch/stop-24a.png b/apps/stopwatch/stop-24a.png new file mode 100644 index 0000000000000000000000000000000000000000..e89ddae054cbda36b088abab6577f064049dbdf9 GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt_oo-U3d z9-VKedh<0X@VLyE^E>)`zQZ1Ny$-idk)JA?UU=`)n(L`3>2NZIRp$ZMX(l(L6@vEJ zySe!q8&BV{IJVT%o@zpKGUG`7qs_<9thpacs;)sXzG!+_r}04vQBZNXTE1 nxpdjV=apyUC!A4#dXRD61xvr9nF3)z2QYZL`njxgN@xNA(C= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) { + log_debug(this.name + ":callback\n"); + this.callback(); + return true; + } + return false; +}; + +BUTTON.prototype.draw = function() { + g.setColor(this.color); + g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h); + g.setColor("#000"); // the icons and boxes are drawn black + if (this.img != undefined) { + let iw = iconScale * 24; // the images were loaded as 24 pixels, we will scale + let ix = this.x + ((this.w - iw) /2); + let iy = this.y + ((this.h - iw) /2); + log_debug("g.drawImage(" + ix + "," + iy + "{scale: " + iconScale + "})"); + g.drawImage(this.img, ix, iy, {scale: iconScale}); + } + g.drawRect(this.x, this.y, this.x + this.w, this.y + this.h); +}; + + +var bigPlayPauseBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", stopStart, play_img); +var smallPlayPauseBtn = new BUTTON("small",w/2, 3*h/4 ,w/2, h/4, "#0ff", stopStart, play_img); +var resetBtn = new BUTTON("rst",0, 3*h/4, w/2, h/4, "#ff0", lapReset, pause_img); + +bigPlayPauseBtn.setImage(play_img); +smallPlayPauseBtn.setImage(play_img); +resetBtn.setImage(pause_img); + + +Bangle.on('touch', function(button, xy) { + // not running, and reset + if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(xy.x, xy.y)) return; + + // paused and hit play + if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(xy.x, xy.y)) return; + + // paused and press reset + if (!running && tCurrent != tTotal && resetBtn.check(xy.x, xy.y)) return; + + // must be running + if (running && bigPlayPauseBtn.check(xy.x, xy.y)) return; +}); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +// Clear the screen once, at startup +g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); +// above not working, hence using next 2 lines +g.setColor("#000"); +g.fillRect(0,0,w,h); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); +Bangle.setUI("clock"); // Show launcher when button pressed diff --git a/apps/stopwatch/stopwatch.icon.js b/apps/stopwatch/stopwatch.icon.js new file mode 100644 index 000000000..867b95bd2 --- /dev/null +++ b/apps/stopwatch/stopwatch.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA=")); diff --git a/apps/stopwatch/stopwatch.png b/apps/stopwatch/stopwatch.png new file mode 100644 index 0000000000000000000000000000000000000000..92ffe73b7f172f7019486231d5bc0ba327c13a67 GIT binary patch literal 1566 zcmV+(2I2XMP)rZGih zXw&#$)jrm=iAGIK8xtz&C7L#UsP>_)mll=wMNO+ffoiD_T1#yUim(D(ys%vMfQPei zrVqOZR|L*1ZtO$(Kh4bfZvQzm-^@2KjcNS9p~TbJ{=+BFgR;y*?EJRZOY-C8(-tp7 zVORza@KQWI#+kt5$25We8u2D@-cmyFb37f!H4B+o`PybN-uT(0hvM!pG2RAaMai~Z zb8H5+fL|F7l^*}>gRWTg<2NIR8$39)H7FE$0)fri3@0JG9e^ZV$1ylSfBMXW2&%E$ zPO&yWxOMB+q<~{)6;Ked;unf2IeFmyUmqWV%KAJEV+T+Q%#hR?1dbK`*5J(O?<*bi zO;Z%!IDVk)sEnbh6R5b5f)d(`!dpynDcXcVZikO&eb-4ajOeKPP8=vpld&G6#BUZr!wEAw+((0R3 zrXbzLH_tucuqlj0hL$qu+9ey79D&OEyhyeIpghm3SOpJZ0ylh<6M!&@vIX9#5%D$^ zHQ6$u$q@+W`9uhB*iAW^QhKevc3RtRO1aoFFEin3XYxR#>%x|>M@G*&ki!ig!iN+9 z<#}G0?8a$r^U>1QCMBx?`|`Zb`y*l_6*ZQ>*_wbuTSMf8&2iX+vZ=DKzn?&FFTtQn zcOW38Q<&~zjy+M0be$yF)>!FS6Hq8Jo3G6BI3^Q~Myv1s(rIb4-UlX1veWFN9dY0H zM6f0xl*MMVIK!PJ8_3nan@8J?SO1b#qd3!TkLoU-P%r|SKQP~H&P}mTCOL|}{(ipr z>d0vS%cB+Cx!WzJJ2&|OAM+=YGa>N@@R-ePyWG|3Y)vhL_wLbMu#!7PtICQ=hz{kT!_!z`=nUlJl03sr&j0}&?P-3E-D?`>v1Tssg-)!E}hoy|JDk@}lRu&l- zYRIfV%j~QyRj3EpMT1a)jt8Ss%SYLw_Cy$ zv}^iU-;=_cfKaZ0SOQSht?Gs8E~%aDal2W+_GKB-2XL;tKLmucox&9>d6`XIZ8j@! z_e$Lm9|hmt>bvj55SBaNntrAr?+kNN?GIAf(_O<-+) z5IAObcQpIe7!!pQ3<2AlI-_PsIm(6=ma`>W50nETfTn>V*P-_A5exa4aj@g=$hAS; zoP8Zd*(akXM__&7H4yvF?#}Ce>f^YS>UvamU;ih1=wsFuSY{xIr6LQ8JwOA1AxJ1~ z^l9-RQP-pLYNJmx>tf&nGX~z3)sL;HKUp^iAua-QAzWIPE%35s-uOlRWH1CA?7VC2 zZtlWBm=oEKVJu<83hC#?DLkvxEdp_h2nm2Zr(G@2&2bfG$kht9Ju2IqI@PE(573Um z8a5O*#uCodUgj!lva#8|1C~Y)XWJ9ib=hh5;!L+aCpigk)WcS%7NR;3)K7Nx>QSlE zjN%tYjFMPp{?P~koIaf#emxNH08UH!@u4HcM>q%HlYK@Ri${Olt*FTk7vN$e@QR9l#uqzNePq-K{A831lR^T#FgJGPOIi#jBjcHipUslrouSl-W Q%m4rY07*qoM6N<$g48qYF8}}l literal 0 HcmV?d00001 From 4ccc61822fcd39a4ba676b5dad68d99baff4a376 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 20 Oct 2021 18:38:52 +0100 Subject: [PATCH 0289/1062] Stopwatch: touch based stopwatch for Bangle 2 --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 84e95330c..1f1937daf 100644 --- a/apps.json +++ b/apps.json @@ -3614,7 +3614,7 @@ ] }, { "id": "stopwatch", - "name": "Stopwatch", + "name": "Stopwatch Touch", "shortName":"Stopwatch", "icon": "stopwatch.png", "version":"0.01", From 572dac6889658182e98bbcd25d1ea649624b1cb5 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 20 Oct 2021 19:13:29 +0100 Subject: [PATCH 0290/1062] Widbatpc: setting to hide widget, apps.json fix for stopwatch touch --- apps.json | 4 ++-- apps/widbatpc/ChangeLog | 1 + apps/widbatpc/settings.js | 9 ++++++++- apps/widbatpc/widget.js | 1 + 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/apps.json b/apps.json index 1f1937daf..8beaee6f1 100644 --- a/apps.json +++ b/apps.json @@ -668,7 +668,7 @@ "name": "Battery Level Widget (with percentage)", "shortName": "Battery Widget", "icon": "widget.png", - "version":"0.13", + "version":"0.14", "description": "Show the current battery level and charging status in the top right of the clock, with charge percentage", "tags": "widget,battery,b2", "type":"widget", @@ -3622,7 +3622,7 @@ "readme": "README.md", "tags": "tool, b2", "storage": [ - {"name":"stopwatch.app.js","url":"stopwatch.js"}, + {"name":"stopwatch.app.js","url":"stopwatch.app.js"}, {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} ] } diff --git a/apps/widbatpc/ChangeLog b/apps/widbatpc/ChangeLog index 09e4fabf4..b8e594fc4 100644 --- a/apps/widbatpc/ChangeLog +++ b/apps/widbatpc/ChangeLog @@ -10,3 +10,4 @@ 0.11: Don't overwrite existing settings on app update 0.12: Fixed for Bangle 2 0.13: Fillbar setting added, see README +0.14: Added setting to completely hide the widget diff --git a/apps/widbatpc/settings.js b/apps/widbatpc/settings.js index b7a5db9e6..55588238d 100644 --- a/apps/widbatpc/settings.js +++ b/apps/widbatpc/settings.js @@ -13,6 +13,7 @@ 'fillbar': false, 'charger': true, 'hideifmorethan': 100, + 'hidewidget': false, } // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings @@ -31,7 +32,8 @@ } } - const onOffFormat = b => (b ? 'on' : 'off') + const onOffFormat = b => (b ? 'On' : 'Off') + const yesNoFormat = b => (b ? 'Yes' : 'No') const menu = { '': { 'title': 'Battery Widget' }, '< Back': back, @@ -68,6 +70,11 @@ format: x => x+"%", onchange: save('hideifmorethan'), }, + 'Hide Widget': { + value: s.hidewidget, + format: yesNoFormat, + onchange: save('hidewidget'), + }, } E.showMenu(menu) }) diff --git a/apps/widbatpc/widget.js b/apps/widbatpc/widget.js index caecf8ae4..574c22f6c 100644 --- a/apps/widbatpc/widget.js +++ b/apps/widbatpc/widget.js @@ -76,6 +76,7 @@ function draw() { // if hidden, don't draw if (!WIDGETS["batpc"].width) return; + if (setting('hidewidget')) return; // else... var s = 39; var x = this.x, y = this.y; From 8d439968ba71c98b818d9ecbb543ef643dfb15f0 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 20 Oct 2021 19:28:09 +0100 Subject: [PATCH 0291/1062] Stopwatch Touch: update short name --- apps.json | 2 +- apps/stopwatch/stopwatch.app.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 8beaee6f1..54b15d970 100644 --- a/apps.json +++ b/apps.json @@ -3615,7 +3615,7 @@ }, { "id": "stopwatch", "name": "Stopwatch Touch", - "shortName":"Stopwatch", + "shortName":"Stopwatch Touch", "icon": "stopwatch.png", "version":"0.01", "description": "A touch controlled stopwatch for Bangle 2", diff --git a/apps/stopwatch/stopwatch.app.js b/apps/stopwatch/stopwatch.app.js index 80fa7902e..1d6791b57 100644 --- a/apps/stopwatch/stopwatch.app.js +++ b/apps/stopwatch/stopwatch.app.js @@ -30,7 +30,7 @@ function timeToText(t) { if (hrs === 0) text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth; else - text = (""+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth; + text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2); //log_debug(text); return text; From fc3ce860091b89230b507d1eb070864fec0a58cb Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 20 Oct 2021 20:28:28 +0100 Subject: [PATCH 0292/1062] misc tweaks for layout/gps time/bootloader --- apps.json | 6 +-- apps/boot/ChangeLog | 3 ++ apps/boot/bootupdate.js | 5 +- apps/gpstime/ChangeLog | 3 +- apps/gpstime/gpstime.js | 117 +++++++++++++++++++++------------------- modules/Layout.js | 23 ++++---- 6 files changed, 86 insertions(+), 71 deletions(-) diff --git a/apps.json b/apps.json index ac3911a71..7afcd9cb5 100644 --- a/apps.json +++ b/apps.json @@ -18,7 +18,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.31", + "version": "0.32", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", @@ -550,11 +550,11 @@ { "id": "gpstime", "name": "GPS Time", - "version": "0.04", + "version": "0.05", "description": "Update the Bangle.js's clock based on the time from the GPS receiver", "icon": "gpstime.png", "tags": "tool,gps", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"gpstime.app.js","url":"gpstime.js"}, {"name":"gpstime.img","url":"gpstime-icon.js","evaluate":true} diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 6cdf1b0e5..4ce4dbe65 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -31,3 +31,6 @@ Fix issues where 'Uncaught Error: Function not found' could happen with multiple .boot.js 0.30: Remove 'Get GPS time' at boot. Latest firmwares keep time through reboots, so this is not needed now 0.31: Add polyfills for g.wrapString, g.imageMetrics, g.stringMetrics +0.32: Fix single quote error in g.wrapString polyfill + improve g.stringMetrics polyfill + diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 7210ae731..a5ec01fa4 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -143,13 +143,14 @@ if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfi } if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.stringMetrics=function(txt) { - return {width:this.stringWidth(txt), height:this.getFontHeight()}; + txt = txt.toString().split("\\n"); + return {width:Math.max.apply(null,txt.map(x=>g.stringWidth(x))), height:this.getFontHeight()*txt.length}; };\n`; } if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.wrapString=function(str, maxWidth) { var lines = []; - for (var unwrappedLine of str.split("\n")) { + for (var unwrappedLine of str.split("\\n")) { var words = unwrappedLine.split(" "); var line = words.shift(); for (var word of words) { diff --git a/apps/gpstime/ChangeLog b/apps/gpstime/ChangeLog index a3bd6351e..4d9bbc8a2 100644 --- a/apps/gpstime/ChangeLog +++ b/apps/gpstime/ChangeLog @@ -1,2 +1,3 @@ 0.03: Fix time output on new firmwares when no GPS time set (fix #104) -0.04: Fix shown UTC time zone sign \ No newline at end of file +0.04: Fix shown UTC time zone sign +0.05: Use new 'layout library for Bangle2, fix #764 by adding a back button diff --git a/apps/gpstime/gpstime.js b/apps/gpstime/gpstime.js index a061d2e23..8c80953fa 100644 --- a/apps/gpstime/gpstime.js +++ b/apps/gpstime/gpstime.js @@ -1,68 +1,75 @@ -var img = require("heatshrink").decompress(atob("mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA=")); +function satelliteImage() { + return require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4AGnE4F1wvsF34wgFldcLdyMYsoACF1WJF4YxPFzOtF4wxNFzAvKSiIvU1ovIGAkJAAQucF5QxCFwYwbF4QwLrwvjYIVfrwABrtdq9Wqwvkq4oCAAtXmYvi1teE4NXrphCrxoCGAbvdSIoAHNQNeFzQvGeRQvCsowrYYNfF8YwHZQQFCF8QwGF4owjeYovBroHEMERhEF8IwNrtWryYFF8YwCq4vhGBeJF5AwaxIwKwVXFwwvandfMJeJF8M6nZiLGQIvdstfGAVlGBZkCxJeZJQIwCGIRjMFzYACGIc6r/+FsIvGGIYABEzYvPGQYvusovkAH4A/AH4A/ACo=")); +} + +var fix; Bangle.setLCDPower(1); Bangle.setLCDTimeout(0); +var Layout = require("Layout"); +Bangle.setGPSPower(1, "app"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +E.showMessage("Loading..."); // avoid showing rubbish on screen -g.clear(); - -var fix; -Bangle.setGPSPower(1); -Bangle.on('GPS',function(f) { - fix = f; - g.reset(1); - g.setFont("6x8",2); - g.setFontAlign(0,0); - g.clearRect(90,30,239,90); - if (fix.fix) { - g.drawString("GPS",170,40); - g.drawString("Acquired",170,60); +function setGPSTime() { + if (fix.time!==undefined) { + setTime(fix.time.getTime()/1000); + E.showMessage("System time set", {img:require("heatshrink").decompress(atob("lEo4UBvvv///vEFBYNVAAWq1QFDBAgKGrQJD0oJDtQJD1IICqwGBFoIDByocDwAJBgQeDtWoJwcqDwWq0EAgfAgEKHoQcCBIQeBGAQaBBIQzBytaEwQJDlWlrQmBBIkK0tqBI+ptRNCBIcCBKhECBIh6CAgUL8AJHl/4BI8+3gJRl/8GJH/BI8Ah6MDLIZQB+BjGAAIoBBI84BIaVCAAaVBVIYJEWYLkEXobRDAAbRBcoYACcoT5DEwYJCtQoElWpBINaDwYcB0oJBGQIzCAYIwBDwQGBAAIcCDwYACDgQACBIYIEBQYFDA="))}); } else { - g.drawString("Waiting for",170,40); - g.drawString("GPS Fix",170,60); + E.showMessage("No GPS time to set"); } - g.setFont("6x8"); - g.drawString(fix.satellites+" satellites",170,80); - g.clearRect(0,100,239,239); - var t = ["","","","---",""]; - if (fix.time!==undefined) + Bangle.removeListener('GPS',onGPS); + setTimeout(function() { + fix = undefined; + layout.forgetLazyState(); // redraw all next time + Bangle.on('GPS',onGPS); + }, 2000); +} + +var layout = new Layout( { + type:"v", c: [ + {type:"h", c:[ + {type:"img", src:satelliteImage }, + { type:"v", fillx:1, c: [ + {type:"txt", font:"6x8:2", label:"Waiting\nfor GPS", id:"status" }, + {type:"txt", font:"6x8", label:"---", id:"sat" }, + ]}, + ]}, + {type:"txt", fillx:1, filly:1, font:"6x8:2", label:"---", id:"gpstime" } + ]},{lazy:true, btns: [ + { label : "Set", cb : setGPSTime}, + { label : "Back", cb : ()=>load() } + ]}); + + +function onGPS(f) { + if (fix===undefined) { + g.clear(); + Bangle.drawWidgets(); + } + fix = f; + if (fix.fix) { + layout.status.label = "GPS\nAcquired"; + } else { + layout.status.label = "Waiting\nfor GPS"; + } + layout.sat.label = fix.satellites+" satellites"; + + var t = ["","---",""]; + if (fix.time!==undefined) { t = fix.time.toString().split(" "); - /* - [ - "Sun", - "Nov", - "10", - "2019", - "15:55:35", - "GMT+0100" - ] - */ - //g.setFont("6x8",2); - //g.drawString(t[0],120,110); // day - g.setFont("6x8",3); - g.drawString(t[1]+" "+t[2],120,135); // date - g.setFont("6x8",2); - g.drawString(t[3],120,160); // year - g.setFont("6x8",3); - g.drawString(t[4],120,185); // time - if (fix.time) { - // timezone var tz = (new Date()).getTimezoneOffset()/-60; if (tz==0) tz="UTC"; else if (tz>0) tz="UTC+"+tz; else tz="UTC"+tz; - g.setFont("6x8",2); - g.drawString(tz,120,210); // gmt - g.setFontAlign(0,0,3); - g.drawString("Set",230,120); - g.setFontAlign(0,0); - } -}); -setInterval(function() { - g.drawImage(img,48,48,{scale:1.5,rotate:Math.sin(getTime()*2)/2}); -},100); -setWatch(function() { - if (fix.time!==undefined) - setTime(fix.time.getTime()/1000); -}, BTN2, {repeat:true}); + t = [t[1]+" "+t[2],t[3],t[4],t[5],tz]; + } + + layout.gpstime.label = t.join("\n"); + layout.render(); +} + +Bangle.on('GPS',onGPS); diff --git a/modules/Layout.js b/modules/Layout.js index 03aa6249b..319f6901e 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -169,14 +169,23 @@ function Layout(layout, options) { Bangle.on('touch',Bangle.touchHandler); } - // add IDs + // recurse over layout doing some fixing up if needed var ll = this; - function idRecurser(l) { + function recurser(l) { + // add IDs if (l.id) ll[l.id] = l; + // fix type up if (!l.type) l.type=""; - if (l.c) l.c.forEach(idRecurser); + // FIXME ':'/fsz not needed in new firmwares - Font:12 is handled internally + // fix fonts for pre-2v11 firmware + if (l.font && l.font.includes(":")) { + var f = l.font.split(":"); + l.font = f[0]; + l.fsz = f[1]; + } + if (l.c) l.c.forEach(recurser); } - idRecurser(layout); + recurser(this._l); this.updateNeeded = true; } @@ -352,12 +361,6 @@ Layout.prototype.update = function() { "txt" : function(l) { if (l.font.endsWith("%")) l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); - // FIXME ':'/fsz not needed in new firmwares - it's handled internally - if (l.font.includes(":")) { - var f = l.font.split(":"); - l.font = f[0]; - l.fsz = f[1]; - } if (l.wrap) { l._h = l._w = 0; } else { From 1cc7674aa7f990f88644e78d9d19cd981ea34324 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 20 Oct 2021 20:48:38 +0100 Subject: [PATCH 0293/1062] Fix issue where re-running bootupdate could disable existing polyfills --- apps/boot/ChangeLog | 2 +- apps/boot/bootupdate.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 4ce4dbe65..59d9e4c65 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -33,4 +33,4 @@ 0.31: Add polyfills for g.wrapString, g.imageMetrics, g.stringMetrics 0.32: Fix single quote error in g.wrapString polyfill improve g.stringMetrics polyfill - + Fix issue where re-running bootupdate could disable existing polyfills diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index a5ec01fa4..269a80831 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -81,9 +81,11 @@ if (s.quiet && s.qmTimeout) boot+=`Bangle.setLCDTimeout(${s.qmTimeout});\n`; if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${s.passkey}, mitm:1, display:1});\n`; if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`; // Pre-2v10 firmwares without a theme/setUI +delete g.theme; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.theme) { boot += `g.theme={fg:-1,bg:0,fg2:-1,bg2:7,fgH:-1,bgH:0x02F7,dark:true};\n`; } +delete Bangle.setUI; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!Bangle.setUI) { // assume this is just for F18 - Q3 should already have it boot += `Bangle.setUI=function(mode, cb) { if (Bangle.btnWatches) { @@ -131,6 +133,7 @@ else if (mode=="updown") { throw new Error("Unknown UI mode"); };\n`; } +delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.imageMetrics=function(src) { if (src[0]) return {width:src[0],height:src[1]}; @@ -141,12 +144,14 @@ if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfi return {width:im.charCodeAt(0), height:im.charCodeAt(1)}; };\n`; } +delete g.stringMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.stringMetrics) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.stringMetrics=function(txt) { txt = txt.toString().split("\\n"); return {width:Math.max.apply(null,txt.map(x=>g.stringWidth(x))), height:this.getFontHeight()*txt.length}; };\n`; } +delete g.wrapString; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.wrapString=function(str, maxWidth) { var lines = []; From 7d99cde28dd587de1f0140eaedf65efca48bc89b Mon Sep 17 00:00:00 2001 From: hughbarney Date: Wed, 20 Oct 2021 21:13:40 +0100 Subject: [PATCH 0294/1062] Stopwatch Touch: README and screenshots --- apps/stopwatch/A.jpg | Bin 0 -> 77328 bytes apps/stopwatch/B.jpg | Bin 0 -> 69988 bytes apps/stopwatch/README.md | 21 +++++++++++++++++---- apps/stopwatch/screenshot1.png | Bin 0 -> 1783 bytes apps/stopwatch/screenshot2.png | Bin 0 -> 1765 bytes apps/stopwatch/screenshot3.png | Bin 0 -> 1938 bytes apps/stopwatch/stopwatch.app.js | 4 +--- 7 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 apps/stopwatch/A.jpg create mode 100644 apps/stopwatch/B.jpg create mode 100644 apps/stopwatch/screenshot1.png create mode 100644 apps/stopwatch/screenshot2.png create mode 100644 apps/stopwatch/screenshot3.png diff --git a/apps/stopwatch/A.jpg b/apps/stopwatch/A.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9155b9986e05f68312ad383e52490d45f8584542 GIT binary patch literal 77328 zcmb@tWmFu&(>J=fyA#~q-CY)278ZB65Fn5Q2=2Blu#3C1xCa6O5(plG2MYuXPSD`F z{Gazc_jy0O_uLQnR-c*vO?7ow)pXC9GgUqRe*D`25Cb)|H2^3m007E!0sPxU<hiIj<0?G8~-2b=W(wW{xdoN zFw6gcX#am>;W#a$GoGv;#r z->}2~hTr=8_&>{x{72r=$L~LQ^%={(^a}WouK&<~EXH^Ch8R89X3qy5;57gY&;+PF z5P^;v?16q5{#oS6lHO~EQG zLaB^HMa?FvVrXPyngK`5&dsxfjUnlo(=%de3!At!_P!kAs%9<`CI35^J zMJ|HPsshHLaG0TCV;40s^^2sW5>qvLRy{{8E}?FWD4kz$iiVf9w0$1j{I?9iM?-mb z9gPH_2-pN~N(}`)>6f4{sq*uwAd9h%Jm+s(ttlEQTizS+D1B>(g=&^RL&zmL>;e5$i(|x?2z;`1u z=7RSGer*!O!(yyKxH23gj6G)62$IH|rrbdjB-^d`{ykh|-lilX&;31UpOr`3OfJ(a zHe8r7l@MY$Xl&>Xel;0oR1jOnYB;~eN=E5EYGX2k0Gi!r2Ryj8EZs6NESe>0IV^bj z!ofD3heOoRR>^xV!(rA|9KJ0OYISA4{hv5jk3xgR)&ZfLct=;xJj-tl^P>w#?ZL^b z67OqlOh=+Clv)D^KDHT;+|`V!WN*k`mC)mC*3CVdWl$&y6jw8gJy0Ss5)Aleu_Rh4 zMqJujwwZ&8)Qbucu9~f#CKfD^ygyam4HJ{gMfMUPXoXb3_Qio*cB2JxV(OK`W%7ew zHbSLCE!4lhEy_Gw{PXPZmfEc^L})xk2w*~sqxG=G2YyZaTTqh+*1ezrSxP|$t&v=@ zZV8;htulr>vSZRj)VHY$0c+mBwrVqL{3Lp=P-|FQ_>s5Ph#~chK^M@NVY{0OCB#@9 zJceVzY=Sq!+0g$LRogiH^RL@7KXppUWa}#IDPeg6^*bhB^>#fL<)PcRg9YfdH0L$? z`1Lrn?05;mM37$UZy{Xx^k_VkSk0UN02g3@BFOToo{?#X0;kjku8%%TNmJ8_WC7=T zq7uP-q|8XWbU-q)oJ-D<@So!x{@I;d`pGS(7zoqbF>^=8JZ=>&!3n`;EX^4XAx?xb zu{7Bw{oHVlt>Dc!LPE0zeg7t+LzQIcg0eTYP-#mjHeR@vgLT!u*_fv#87V{z^H&IU zWSg4;1OC_WE6nh2Cx*cxm&uDMSVtc2?vy-8{9`Fn5Rc`{qL@62JSwup@W`R$ha}Te z?L&XtE56XPU?L1m%{1|cE1~ACB9_}XwXQh7XICQLM^<;BprpFX~ASURPTgu5h{7
_eFged36pqxjC*S-i?4ZXFE0(~^RL^#kiSiJl_DlPfRrEvrT4#@PaF zuDmmhfXK3{%^~+2z&}9x_vPeDH7?a$!CJ%&9MV3SqxOX7=XcC4l~{ z(aWJ&f*jKz{~Sxd35vKPSSa{SD`Mg~G}Rwh9l=AYQC+vj6{u7M1gIh3>gM#`*zJQm zo%!}+=3Rz3{sHtyZH_`Ofa*YG!owh;G{T^q#1NwKHUs1MIt^S`l?xKyEx^6I^Qpi& zWa0`ECI@|fU2^aKE4+~R6|y3&N~U5=&ya15;xTzvg!HORD$C#Si|_Qfv>i4(2uA`x zvS<2kXF~16*R~OIxr8uppZtuh>IHX=c>T2ky>LE`9VbIjpUqe?4^nsoL>!bJsciV; zrhY1SQ1{UOq^dPgC(69SN+^lhpN~G05+m&+_b=GB3jTPB!^DofN5LLrqJxT`zFY6e z!=Sb$p#zS8*3-m4z?1Ypz^0=5`?%NFu}Q&|%Pvm3)0IVfz#4`0zR?H69Q55sn`*6t zI+`!AHfNRco3CuKR_1UJ0>5@1jZ1o4JaWU>-Y?)5A_aUPvsEjYK?8}PUOt{h} z%0m8A6%s7Hss}YUjtVXOC1-3lyS3-_z&SdLKeS9%e=83mn3K_0`P6b?)9R7_sFH}& ztg$0S$+zpLMFF8Zhs^XwD-e>vtnx$)GtcLZZxylR<>H9kLeew!( zOeg|r)s^Klq*dZVx6DjguinRLtAtC+Qlt4eP`{!>5mz}IR(f89`*YW^8=}$3lS3NC zOMdxltsoxoR|~<{4;UGk#8Nq^Txxd7pfnYyl#a3pUUdfyr5o{0Jl;Y}rOUQ{%C}yG z30f02UEU>k%b(L2R8f(Nk-kS7EU`Vv8XS2P8`sUptv-E3fjWQ5bN~^PoFpBfmhw=L zBqc<)>MlwcTJ;XwynPF~_c`O^BiCJ>c$k2@AozeJ7BRdI6&x8P{w6;8k`0ATyrvLA z@XJ~fg9EuD6Slm?iXle<{K|B!8Z}$zecXS5qkuCk9Curn(nq&qr>`m8Q!ye3E^T!p zTJ_-wtoOw zhgu>R6|BS&{YKMLa0*awkv%b*`vwct9{o|DZ%CV`#0sW8Vw9np&#k0{8cu*#4dP4C z?OMPZyUE*pz-wS`Z)T*u;J|Adcm?7Y_Zij74bKmzT29d)N^CcltMJn`-$ekc$&u^? z`TRd9B7DLjbZ@X3l3_ZW8&fDNqi0w5!S}b0CmyPT`{UbsuCQhxFh;hPBEVUH2Vy<& zz^cG^vTf~A?6may6oV9%cS@k>E%8d#v5g_nh5_QRzu;U%S2(Fge$qH6ebvgObRu=C zf$aQQRGd;pYAljYQtKV=X8|=7OqU9)An~F+QL!v@$1!~ZAfxmPl+%8+V~-RY{5ye4Sev=t#varzW4#%Bt)iGp=J7_MB0ci*zRr1Msa5N>Tm zxGXRz4T63|cf@j6CB_X?9R-|hz+nZoqSy|&OfBW{EFJ}dS;fY% zA@A$6cCpuK{oHD_(+?&%hKQQ+xSCAM(;egfP9DsH=5fiIT)5&fo3UBV*jj*s6;8J0 zzkDi4=1k|NcT~j%%rgB_b<+_+AWVSLXvXb$kBZ+2_M((N_2&c&<7=4B_`_;68%s!T zYc@Vbg@7}U-{dSTAvo9&hq#+w;m-_djT61d>~c=z2M*gMXKjc(XJZZMTTzbfD9J6( zl2sgp-oaU6+F?$#m9K<6M(}{_Lqja5u+pc1=w+6%aWU=ECE#CUbC~5H^RRToI02p3 zCj4ZT0A~(5CK#qQtfbp*j+tPBD7ji0X3(LtfLh79D_g!4&M>zFG-GX+UwVgI&cm$< z?m_IC386=VxJ2pZYM1e74hH@KpjnTM{{VS+aMfg|5QQ_>2N}xInT-&`Lk~f4Yd`?Q zsP8dEK7n8B~=U4aO5uPRn*`%Yb_Gg5B%dYUNR9??Bh;YVpkCeltB-7vxEwWmB!pr)wAobI45uIIP`_MiAbeX=P5 zuvfdQO?y>)<-z0Qr!S7;xwK5mKWZ9Hc>=d#n|V%HYHCMXG8yE_<>~Mc8At}(0?ApA zV#pS45tmTaxe*o@Dn2f`yCF4nKD!jkVHEZGoLl1n&Mk-fVa5!VOt6#V=nu_~gKALB zRB25oSG?lOA2;XcuULM7zPhI*N#j|k3o1I-D!u5L2Ybg?rZbx!!R60`cu4w9(yzGouc*#`9M<{#rYjzoNHW?N%vqu?RHt zBTQq-e+CYJ2wSA5>JiyR=uClYmZk+wjRLZNxz6;^p<15lM5rwh6MAnU=_VWJ!2yzujnHR6Zhe&1Sn2$}8BUEEqR;!ua9dUoE=K#1QVD4aasp4+bGy}T!n=vg?h9|l zOqw4wk)53Bt5VHP!#MIJ^0%zc7_eGVPP6@EOZj0}v1$kNF>5Y#V0l zO6&k{7v3VVa^mu6+@W4|R4ZlOG5Va_zr|D2loG#G)C(A@W>$d>n)NkIZgszCOK!ou z5_!3Z+KR3y~ncd^_S}%Xigu$#gUt941G%6`7Sj{`lndeGGXPLTwzV1 zjBcUKwV@nXEj{u_rfZW1ubaUf6-PVy&9;B1%0??it~9|ME>HN# zgiWvA{xG&W$#OahsxxsmFV2n?bNX1TJZ8}$iRfQxxDQq%V zLeYfZ^Iy90n@1Z`l&(#q;2Sjt3YGYezQJUVMkH0BJpC*m4KA9S%TdbASr{{AhSXI< z6VYJBsd8qR*>S83+&ubLWrnU)cBlrYO2h{KG1zzXDJtb+7ja`Q#P0x_oY&QrTZQzD;jd2eVa(zJb)yR&PW`$y?7z%wOPH96?#^HKgng}g zb*3i~E!^RpFts>yEqDt95XNwI@ESIBsbv1vw_QxaASSzhi^VBWdG@w$D%$MNw6 zTrql!u?o-xucSw5OR!A2jwFQ(#5v1n^@HuAbIYk0&Y$f}2j^TRxR_niYhxNs*0z%g zcg|dQ;^YcDl{9q-Yja7D`f?cg`rct0a&owYx+0dlRFt)Rj{ATf)-B1}gbs>j@>fBT z5SWgLKiA{lq?`xBfZ>x~Er}nbq*^8@!2qt97&kfrwHP*6D#>5HyBb?x-*j7UK}Xsw zU#}D%n82K2@Fgu=XAX}riHa_aVUdZU!CyJ~JhW2B9wBAAKq+k=!KFDV2-Z)Q2kC9M zzj;;+>tl!-U1xCdoy~xBX-Sm@=}7cP;S8Z5OcWrc@>g<0s=k-O>hAsMJ2o~Ztfw5e z3?D3}IA)!2wJaH(Q)MZI@dY^}N)c)Dyb^TXl&9qS(1JeMdjag*mJA16t8GGHa#XH5 zzLzpvIh@LpO1}CqZ zrI)eHpZY@|Z}^L7o5Z$aLV@rs5)JdvSLC1nnpTzGudS$GeeU^Yem$&TS1T#$K{GNH#a6Kg#hSncOw@T(s$ zNLq)cpv`BmQW1{4hUnz1OYZ6O0k164dl2(dzvO{q7}V$Nf|oN7kNddLmlWa|!!10g zR}@8W%fNBz^y83GjJ58+oP}J}d;|K}bSd}}SWMymj4i)WxAGJXq%UUJP7@`~LYcQs ze!AP;=a2sS#G_{q&YlU@THd1+NBIX>zL47bbD-0DU%5m4uC({*AHdqOn=H+AY;>P8 zNb!RrFFZL9IPXK*@R^d5GfkniA5A*^Mfa5)1o5JlcvN>pRNsF#D=SJTUhBS#!Sfi! z^!F5JRxe>@uk%o5BVUulW`>`a2?Z#hAE%M7rI}fsbtpC*#KtPEqAlh>KBc8~F1RX~ zwzKypXvF;m<LMfmKI(CM^}o41F;&KV;XbmWusQFooCF*|eNc;Vo$7B|`NFta z1DiiF_Xe{A@5=()CR#pe55hNks?;<_OUhzR(ZO{r@v)lC3g0G1W?6^O0oW)Ee>H0x zO8>lnMXB6dqJH70qLa}xgO{r>nS@;LU~ZAsqcJ+rI(cR=z;iwQg| zUrLPdcq#GP!5Yr!I1$eHr$&i>mc7$=feHn9879w!%l=a7q1Vt@zGgmEF~Ws?Tv_Hq za(ldV+T&pQ9oA*)w`sIVCdy5h&?P8!X>pBf^Y6PvUc{zY-906VvnJf}VeHMZ0z1<1 zSbctqt%f!hot6^vhw)}&{Z9BFVDa(+{14E+`VY`cM3y>`yh4OE-^(|p>t2@I z$J25Wfq41w_4)^I%*pJYtESV=fb!1AWmS%OMLaZNJ&MtQ&KrobI-6J!=F#BR0J^Xv zV#EToBA`VrYDYmesd^@~ZQo?bwBphx-+EL62IvtUN$m-*-Z{oL$>11Jl%7LIS*}cPDm&wG5M$;cx`0#r< z#W|zX7-etu>Qq)w3OPp$z^}bipv(T?gsO*IEicgSaT)GFN3%-K$%!80yH}#^yo3GD z=1rOTImTr+`Pa}Z(VR=EBfPeK@Ln`KiA9agY+gG6-wj3CJJ1C{3aKlE6e!H?k|OcH zBTL}`@abDlW_-r9W>M$ge(y9|VhthPpl049b5XHcZJt(QRX@ttoTYCJ_A-4qM8;6w zye{!cI#wgB*o5+xlX@vrmKYxN-l;*(*OMGP9~?P-N@yow(Jt1I$(Sg`&?5|LqCRyK zdSa0h)*sy~D4WA@3?`TsFj5P`GGQ!)WkmDBsYisg0UFaL!}zJ$>`DGSWH&Cb!&?7y z?jG2vLv^ICMkBj?v%;jo#<{SBtQlWXaIYfF+G<5CJKPsjg$pHG!BKWQjQ1Pf(fW2vDR*8M;Xn$uDKm;p%?A7 zNLyua4Y}WcR$wTV)y6oEiYq7Tz7IweYnVn+Asw}vj{gP`$ z(B`)b5K1m4Dpd$jO9lb90#B^>aj*r31W${|w#>Upv&Qzbhn*d`w##<9c@CVgA~HN8 zu37WyaAZ4jI2`)}DnKOOce}Gm!Yf16bh^~$rgh@YI>XkH5t(&VR$5z~nS$;@KTEM_ zZmV9j3!K-L4_dc|hZ^4cg;byUb!BeAZ+uQB*`im9Zdt!C!qO7`8}fiND(nYOjL?11 zQEaL{WXg7k>R9S6M7~Q^^sYvML9I#%7g<@6BduJKL!7#+T*779z~mDqHR4?L4ai806$nzW0AJ-o81K(+0T|4lBOK`YcaJH}>d$ z3;kS_+REn23bhER@vcVUI^-tm>Y1-(h<0wb0Fm;eRG;xL#1! zpz754i(KVR5>_8qy7_mUz}) zEkkwlE<*vPNs zElVcy3tnCLr&#t~Ww3?r2V;LT5yxrY6wgEE$V7+lge-8Lsiia`r_lUxd=7Xza$&6T z$6FiFU8YTE@QH11GkuuD=p@mkfBL|5VGhJL>srWXjrYbB-X9kLVa8OZ@Z7QS zp-D`Ce`nH$Ny@p^p|uo!5~&Nps*Fq{Q=#WXk)mZ2%5k zVk-+L){)0A4*Z|-F17O=&dWZN&In~11TMt$t&?~O3CuZ1dy{$eBVGO6ynR{SWBJ41 zvPstwjgi3uK2LCSjCq00m9t>>q|QvuxLELC2al0eJsYx3`^md-3LXlK9O_nbB<)ZS znOJozmAy?qB1uvew|jl%ch1e<++Qb$H6sNg_=Vt}y0z&o*r}_kL~-d?g@$$ga`WyT zF*BpR14l;Tg5ySvxA^6&6xt4wpe5*_OR0=`>SN3gIhh{Z_BKCgbpO_>wpt`Ot!_ zzV;h&4~?@$Nc@l;fPxM5RA1f%pM{?+m;i7A|>*mgpe7ZW{P3)C=iV8Dt9MS1$H8ZrtCJRa9VDGZ+r!d;Q&@rcA*r!_QE zR=^*dO>DNK_?!;RbY|i{svRr+dkqNsW6iwCvs~9^nOZTe#?ix}vSit~(xl>|X#uFN z*2FK#PnQL>;i87>8doQOwcChxnicwW(!Gb9Q4R@4VbL;NIR{ZUt_EP;`Jtd>z!U`l ztjOv(x~2Od(ZGBHoQs>f*^g+8C;dkzYd?hZ;K3tdCChVP)^_-uDEKs;cs7b->W5;@yQ3w za<(P$KrfxX#*4O_vYgjB?An_&2x|v-`_*g{_&Er^!mhS4$O*Xkn_A`i*sTxqFd$+? z>^xrO;>VTo=mi^frzv6ksyYR6`m0=k$YdPI4}yy(tjQUICEfUmR)Sa(OzBUR#ZO^3 z8LzC9%}jY|c-p9sfJu5d2Ij?hDrix}${+Sjs0#feA&(zg6q}n3*9xjnjmK=w-C3v! zLt0~r%_nO_CtCQ9ws}4V+cqK$`2Lin7{u1=rEL80qoTMPGaFKE_i{PpOW?`+>cMWM zmn4?&z}gbM@y(BAei|^Pkh65FS|ct$g(&B83SB>crGNdk^n>yueVL?1%LF09q~u|j z;*|4>=BtUVtsQ|`)A){EMVR;1Xbf29ikK0067G*xD16&zAtPnwlcwv=HqRR1LsrY> z&7}9z^mk@Mb>d$&*{8L0GH$S$d?oXm69_1EWOfEL=(O|WpF7(MNx1=6}H+?p4&Rf$ZS*Cp;SLN$vuO4%@>n?bRi~&kvy*| z*Aih-O8cV0ng)gotN5k(2;Kh&_)z~3;C!HXW?S<)A^kpIPe9@2S*@rV$eoAMq=gFQ z(?o@FA}ua+BGF)ty!nT-Eofp_Y4PRTqX1}wu0)B9Ot@QUL}2eX$5#Pev>lU{9qgf5 zxAFU;>~A!8Kx7p7<{uFp1c&$`Ar=NwVIn&?*L(49QA05bBqNKUg0Z)E{g>zFUd>3ETPU#9C?*r=#?|zULiil0tcDsJsaBAL@ zT-09!!p?qRgX48Wg6?%E;6z_OT+~O96jFlC~3E>FM*zsTii|}Fm?cp zrK-ObjwnLdv*IVyV9K+d<8QI*da^*xX7KX0^N#Q*6>k<345#S{RktNdl35 zb{)YUvoH#)+D&>tnSp62&Ei~10g(#8@Hx*u!iUr{RFQ!pQG8afx8g^I*&2h)VOD-r z@1)>a458mkNCqP|zU2$zjEl5XZ$T@7vGmEX)Fyfz#y)2@oyg>h`Ke%VJJmFS`WWfa zz?c+04J3jIZ%7*OX<{c4yXZYoIaoEqA7us;)T{D7RIk56U zo3;CI2?5Z!bc%U4&Cpr<+v_Z^C16BSY}hFvu_3bUY?`2m>;aqx#Q3WZ<8YnK)h}C1 zf|-0t{Gs=fiCl5%Y2G(wA~D$b4Z&hFtS;T}$q~EMLB}vJy4VT)2{9&WKY?R$xL+X+ z`r^s@n~^A^J@F&1ZftVNVGl0Lq=@xPg+qrykzVqcbjgYW-wAGM;c^Iw&Zbi}%jA&w zSB{~g*U-q$Re0axX>5Dp`e+QkO9{;j`lRb%K6pWE}HMKpooRW`Et8lYeWY@SCU{z*CIT zYvUsFucz^BH0bjZ`p&YvwEH@CM-l4!^=3M<`rE`XMQx*TtXQpNxi)6}l6W5Kn!a4v zn$`$VP>?YjiR@WS|2szOP6(7co}tUmO+NNqw8bPDdGAzai0n!GXYU7zJ^E~R0V(o?wi&gI;{Mu_1IH`R~w~lY#Z4;er z35$jrY6s&|Z_qS}2l-lpawYxM=&@1b4b9W6FnQIu3ah%bpDdJ+ygIp86E($3D#L=E z+FbdGA#~?RDWywc^WGFm8^|yi!dRAS#)k$q@EiE_D=`#X=58)pvHlj12_v=CV!qX_ z8SaedB2lvYja4TgplBsilf^4K(Z7)~fxe#(0kK;%z1Kv8h5>zrTk#X`LeFTzQ5{Yj z+)q4d*Y+0|aCl^5k%bS1WAE$+F`PJ&QmoZ}iiFyaguDh`Mf)gSNAcRO8;FN@1S{uk z01nhAdaZj>#dXPDnM|eChl`(BsCv^0WcV^@Yab4v4N@DI`zwi&)Kqj-0yrD2H4$Ph zKReWP8*1US&s#x)`!D!eEFAK$>5~YD%p&HExi$c0ae(Hh@+PCdEbhM|c7Kj_s z%=bVo9~#Cl{u5jR=2S56 zW&`FQ*Ox@=Jdd5wBk_qYa2#-4_xl}ic9p|=FqsTsTKc$8rP{d=yS-rksfwdXG%{kn z#eThuG0w)7PpiKka;nmXF+_i-L1)QXBJ0kWlb+L4*0is<6`NBah~RHJ3SpZlLQA{Z zW*?`uRZt-hbuYcUqAyAuknz>~)+*`MNwCo2ck>-2NUh8IX&DC&(3>|_?-mkizr{)h zYBm+!!Y)CugGfe>rs@^l)M;1=#=zc6Fx!NE-DEaGS}^vISn;bxQ0W_@Zk z)R99!kuojR3B!6Sj{w`G zy3vnn)`i{ns-#v_h+&{qAI~a7XRyx1gAqG@o;6*<&pHNzC}x?OLje&Lewu6_leXMU zXG|R#nTe-*?cU~urxv)N4+3xilIDWgU`hy~#qpr%ajAOH&Vi?y4~0X;*Hvp~4x7P0 zzG5uWqH1{x=vMdw#oWmd%zMnk_lA=swlwb6$?^y3sf)s%ytuV=qnzwLVn0xgm1HX6 zo`Fcrsf}0IW2vpSDKr(R3fhH`;N|%8=}eo7TyaIB$za(y&An1lJQwR#+{pbK)pE6ckt_eA_qfsWi|D@&GsxCz0}w#LnephEpo zu20G>-_o4_>ZickuUZr0HFs8y5nXD2KICPe1>UBgmb8o#$PCleUe1ridgY67^GpKk z86tY?I4aY`l7!e*0!;W?eP5FZ#|>EoMdIz$6P1f!-*)EiniBq8aaoFTdOL)#A*wC= zb8%y#a8@i+W)xkGUjuwJam~&LSEIec`5Tchgv;4G8Qdvl^g|u^{jD7n z2BZ&THKFYP^ zE)b0a!yHtCWj^OsdfD1Jeo05> z3W8q$Cdcn5Ka=4;LC03UmuA=D+9*t}YP4Ak|J@S6heW}GnGus^vZ=UdNi-kS?iJXz zHgncmrv0@G=DC=wQ6W)*nlU-;6^ALWJ|@s@BEfh3=lJ$T#SQ-|Px!PI_L;ejr^4h579 zl1}uB;^wKHVa*s_$BW_G_gsv-D0WiY(7~iwXQkUE*>JojeIIC{=c{tOV}V~dhD&LY zypd5b9Z+~NL-F_P`_$3yCO>1H7sAOGex347ini{w{^aAf)u_TqjIui(Mf7+Ft0^OCAS#i9IL(w?w)z-s#D zo6uQgmN5>}W(*aykVq82w?iTUHT9~nw7_}c>C#m5NezmX)AVf<{>iRa z787&U_@}ay9HS`QVhob6#G^=nO1IrKaw}+QQlEiBE zY04mbk9IF18A4)ZBU+}qk3ClrwD3~+jvF{@_=Q}Ovw#xqlf!5du~6>p*SwJ;Z#1Ge zyq4!6^uk&Bg@#WbM&sF71~15!+QisRA#R|qtu84W`E@G$d`4Qczg}K4dLq>qNs}wg zP9!0*$|a;odLF-EinaLV3j97`L zS&D7NCviFECwwvq$QE*T_2!5c(ztWwieNL$VP&DKA{Gbohlp-3fm!)V7E0vqCT5Is7^hMlPqfZx#Lls$Ol^50}fqrLd&#Ae%{l34S4xdsbpAN4~pL5ymhv>1tGwqC=e1zy+01NgU^MV23uNel_ z5`V{pbI(r}nk+@%n!saNR(m%1At3}Wn8yxjGtl`}Naw4sP)vS81Kr(|kLhrZMTy1E zoUZrf_@J|>e-Ufb1zcaJgVR!7M+zcf>K=yS96zP6G~!X!ag`KIk;!7HL?vJ6%tRBm z^M=%0e$XS%ep3v!ueprTLi zd2ODvXpcy<}FfC&304soehZsuZrUvj4X6t_=A&%i}aJ1 zGfRD#uxn^4DAcGDl*nGfNqyRG8?xQavElk<(_|__im61GK50siQQO`El=`#7MA6~0 zBy|c)H0!6~Kv%>$iu;%t>X>Y+;AAAYogbwV#Mf+ErBq-Rt%56xi4V`fWMjDconbRj zH2)Svx-qf`-OFh}HYf*x^&Ascc1Rs2g;>hB%M`TL@RtxtXE~)7N%J|bfALFZ(_@ra zPoC!=J)H`16jV5ddJ3o(owC~ETR*%?<Tu6HtpB*>Mp#$3jx%7B8Y$~!sh2sG9J$4z>1}s{a(se6( zn}W;o3ej7>`J)*lIx+VTf>2vssUgkN*~?2D1AicG?E6f7qpCCX?@ghYZ#(td(XmWb zh%il_ND?#cn~ECqku6V zR(sIzhGrSJ-&NKYQwrJH{*FUjUAPyRhJ-TuqPfOW?vX%!|^D3 zCS*}K%nRNeF;P7SSZ%CY6H73~v2TxZFKBMQxM21auyN7XH-XrvNI8fu)DA7b)QpvD zUbT2L*6VCZyMP3u3Q=BZKAyrNVsMe07pv*O0i3+A`_J?7lG;CKHl9czO&xl znV+&5&4Y8}w9e=De7JTFF2;_P;L?>UvVL=&dNLZgQ42S=OtFfCKsTAKzttvbBwn=R z35MaC;H~hd#hXxu`M2hQBzInkP6{o$$-+Jx)no_iR#f)pV$=I3{Fn^0s{F8L`*Qg^~BqiKU=ytqJYr6jftnewZdJ7a7$qL*sQZHl`c#30$R%@s$ly zELBAAcD>W30)#8xh}N zB}p~Vd!zW9k=%XThxW zA3MIjP_Zg;sh`)5?xP89OszrKKbNG=X8O64m=_PY8{72_jYypddG%wvFUmA26aq%X zZ#BIPA>mi0d`HjWP!n&nCKG{AnZeT9>EA|g{~Z0S?M{E0tDT#k2HHS@NmHSz2W-79 zVWq%LlHuV#_$^w3+tirVkfcA#7g%F#ajX&n~9>wuGlP)>}*wPhd zOds!EN9-$(yRWg`{7Y*u(0HVYm6`*XH1Y5F$2_=`kupYTTL z9)to;`Ma2ZCG*XJ0e)W&R%(~w)BXUTlyHz}jZ-JDNb~Qu|^D_Q?m6xjl;!enUrlRcwd{kp+%?!U)xl`Dd~h^RqQlbkt7L6SoCpR zUz*Wl(7+zrzcJG)&F2;79B{9Bdd-Q{x6V#ft02GPe;3OIfJv0ubIfiALTrknzPDH8 z#3`M~IBhZD1^6T>85rsR1Ek?%F}^zdd(9Id*0Cy@(6Xs*J1bWI=C>0M(9oT-i9=BY zMDUrnv>ENAsZs^&-ao*vQ+lsH;|2_%U`~9 zi9Ls$kh8~f9|s3u>Yzptw`!Q5D0JOL^nDjQyAonW`D!$tmy&GwiFffr zpnY~cfAuTvr13Rbp6A&)yX>X`%XRd*r&Pil_}P6SMN)2>2mbaRH*z>cqcbEUwW@zI z&sN;mIo;3CppPct1NiH38UH%-mfya_C|ymiOvRcuQ%$fUGFSjsGDP=^>uhKgim9Jr z{X(C-xAT_wL8ZndhI{->J9*#9d2~TYnj}0JeMqEUFiFhnjxKS9?fn-D5}FE|$%X4< ze{3a)>d@H~{rpkFN+5u{iTWO;;Y@3{W0+H53BP?vYYS_H=rA$ho!^Z!9}g&CvyfpchKg1 zAw!Rsi}tol$*^%=FRc$V3h}foXVE)4Q3(an#=_NI5LmlM*&-deV8qJUdQIjpp_fwlBu68pGvPZX8^06#gOGN_*Z0j{Y1W!^Hr zYe+3yc0cH%eZ*e!{P`7BU^^Q$fX3|+Oft957bz@FBt0m8e##wl{aTTmF5qhF&s9p4 zFjq&6BEz)Hn=dc$dlX-cl<}%sk74_Y=mdjSR`mI$oi)YpR8^-Y zI?^k|r1Y}h?{DhR!Y5}fU};pBJX@kl-}zp9Jnv@NJI~ZCtvO^S5TM~kfUq9av{9^a z@u%!8+YhxM^S@5sSRQ{;Xf!I#@eA(3l)iaakesOd}mu<;D!O>CFdc0j= zRP#~;cp#{s%8{e`)?5<(=AAzf{Xwv#dv3L#Gq?21{w}1_1?4>F>+nq>gTk;Q@~b%2 z(g|w@H)Q(s`Hw~XFJn34dMO507Ub~h+F1N)&%?}EOcGJx{{?P9k-x=MP!g`C2SDPn zR(wJFofx)=I=fPjtZ={QyFGqm^8-v#j`GjSZ>4Z5VgA~pEMA0kXA|YA%2ay`rFt^D ze!WX{N}DW_kXMFPD76Y6wC;-O9A@*xNzd3q@U~8V`~%dI zTb58)@eji~@)XdqJeLo&001Q|=ba-gl1VwYOrR3zjS|puJgI7?pU`3LbbpnMft*PzC3Y#B@D)EzJ)L#|QkCREcnsi_L3W0Y?3@ zg&HC&kYBh{LV4x;5CXCM8|=e_$8JbKN{_KBR|z#BDJFrQl_kcg3@F^55Asx+zg~Nm z>PhIqk5Z_kkljad*Qq0@{7{^rfh@Fe48Ur0$-60lf*3dR2rdERs_?^*+%F_@KmZWL z4p*KCAbz4`2R}Tb@`z1U&YY+{pivV~!6){JKtLy#Vn57|$^#I;au|+zZVBh~A`{N- zR8>f50OPlMl8T_O;;efGAP>oT{M@-H0+#9k3;}Md`e;xiy0akViY|j(bcOu*-F}xo6!@w2R8ODQ=P9C5RW~1JSb|nJ2c-@$8}YP&o+{#cE=*|3^eIy zoxQA;63D{HqU2+yEtYE~oe`19X)o%|6so$Oz<%(?j1=Xi*Y=IPyyoO?&btF&mA1^Q zC0l2=WyjNk)T&+e*8$myXULezjO^)!*>ULCHoWYh%kh#MXaq4J1r!B2Cz>s-51nR* zW2M{QcZ2>O@ytwY4A;)vKSj$ohBh`fJ!J;AK!!d{6Epb(B^ok`$0o}1Y9$$roa&d= zwSbbUb3X9Z^8WxZ@th6z4qlsmu=5Whnhb0leCpNOlh^v%KADUt;iF_tW)R5)$dE;h zv4>|A_=GA^QN7mRv`StV2B)zZb6;SWhd%eue1mC>ZRDAHZ4R~ZaVwd8N#Z{1q?;iV z4J_`yv09~s)-6fnJt)SvXX{`bN2g2kUv_NXAAlN6M4vzZ01Th*#9IuX6!ILLi|#zHFI8l&c12pb8B3S^={8MQ>ZvSzXr8$- zFle+|__&WH>bKVI#-PI_GoME}{yhW%<9V378|BkK!?s>isE)@o16#3_C!1{K z#tB@j3j4*xXOZZ_n&gD~%(Qp5AEl!u^Vk?e4XBA%YE^~WNR^JT4TG3Wo~jZGN_Ql%h=7^VMs#@0}B5D zP@^(0jg^sxCe!swaA~Cl=<8G+ZAGV)0il?()_HOxl3;v=@nWM-WtnG{ z63E$?RH75~jQn6F6BA~->Vl3RqB^$-{%DzRm;By24P2W?Z<&vLt9MIeRbI<%%OcEA zy=q#fa|TnY^ysTTiRwF}{{SYF{>Vl}0Rx-8Jt-V+*ayv-g-<@F#B)*X(?~4K^-42L zA7e9>AOa6x$FE`bZ(AO&iNJ0->~*4nkml>F`@L0A49$!BRI(G=pUg4`v3G8usp_lU zsCuv*r#*oj0<5k2B!LZ_E~6pU&2orGjVX=+r&*dZmRD04yG>`F1=amLX)JyE?NLehQj?M6n$HrX*0jK`*0tv`(oC1w?M@ zfORAJoq+qO;Zv(~_6Ms4Q|;{Gl!Cwzs7eVbFl2nHr=vX|loCaDvmebvngE8i5wMYY zvwuz*cq9(x9HCZb3K_X!&kOjlWBDjNmokAVKuN6}XUQE!o*H#yWX+dOYt5*S08TC{ zDK!)v^6t*av_s2;L<~6lkV#R&A&RR4pP)W5NFbKf~_4_3BXxKa)Ei#3*Jwb_0Ms za1IV4(Fr9|%s#rNK*imh51k=OO)WH-SZGXr1b)|>_`k!R7>TjIzdd|MQT#V61VCWw zQz}^a*?D=Xk3^&9JQpV$7RuGgi!MBvO>(Z^uNK}Thit?YA26`Z##w|k*G~IqKitSN z0gIB%Xq0YAQ5FkLu<-inGqT~g^8)&stK{u6<&}Jmo=LVYImH#aHZ^btf2#{*akD*n zdp8(a2Fr&Su4qzQI-JVq>FE|HAfh`3WaO8>i4#waZ>8f}wZ_fy^vB5FuD&@!Tsv9z z1l{A914g!vy^S-r*%=ipl|>^GMcu2FZw!%;EkpQvUs}8hVA&(CMM7BC=?^IQ$PK94 z`DXhsd$7!%^6BC_*Zv=ii!zg4ew!7gG|aXxCLH0Jo~Usxe7c&D`ogrzwhDk<{PNjA z%*2k5M}I^6vyS0Q=7KK#@YOJ;@&PPlZlP9^BL!2 z8S4gZDDDeRIJvI}m`E*f>j}>y1D-%ap2)&!wfTs*J{tPHul>#*W0b@eE`!VSIRHcWflXhGd?AnnZEbWfI~l4nw~+ z&(fk7HhBI|uGL{|u`#qeKQP!VGIkpqTLpFAKZ&Y*iQ^=3GL}=-(0o*joWx19Z#&$s=W~VUn0lEuT4iP9sF~IMJ!f{r)Z->)8rR0hZgKsj zFlo|rv5r-iwUM9vsAz}Y^~t%z$Or{0{Fzn*Ii&BocEQ;U@LqwZ4z(w>_I zDGVjfp-bBN85tDjiHd+@sS&$Qw-)u&jw7kDf6mGj1wXo*HeQ52b}&&hxFn^sp=v)Z zBj@kWYnz$!HPU<=^(473 zQ}m-iO>N9N^EW(z)k=~J!=N2V0o#H7N$T7MU|WIIc5(;^$`kF}g+k&Z=RSRMK}}<1 z!>4zQ)mFw-4NeDCEzeRE5EHi_@b+WKB!UY|^qFXdZ0-P3XvF|q>f#+2-Zq{+6;a-f z(^cl3db+SIM=$nZSg7ajcm$ru`#_B#q@b5F08o?((4Wb?3X%-2hnG^Ea*7A_R_BHb z!nfT!OgRJ+c?y3-;yffJC3Y7mO6sZvsXiQtqL~Bw`GV`(n@XEqBI#7Cd)Rhm>->YhC`=&1_oNKWm$dgNuaow{;K+ znUr$!uK5m)j~YpFUX>W5$2YX8wqg@`rlI|;e=*!_gq$fsbQvXrcYQ)d&hh>K0FY|5 z>51pr{yl-Pg{P6Tj-v+O!!**e~dNO`jhry=e2y5}_vC&0f9e zRo#*=2^mSK<_E4_If#7xX`|SA79Q7Yx`p*Q7t6hL+aDqKb8Jkzvf<@nV0$E*{Cs^x z1;hf}t?e68vX>!dmZX%BnJ!IK@762gSaVU@u_5ogiL?B>KL=H)wzCVxwX?;R`!6=w zdc3WRPO@BRIyO!6E17Xes^XIPxcGRpzgy(u+UEcY1i$Z}U!)E<@%njA(>E`_m0@CO z^_VzV-bJ~#L|18ThE7&iLttu_BO4zZ88IaWzEvq!j!gX!!Z{z$z$CJQ<@)==f?pze zhr{?=TrE%d=X~#nvx{YF64^T!%$~anu*%eA!F`vmCT?Uz_L&&%uV*nnKNp;_GhmNA zw*#F#cw`@L-m0Zd))u^%OPSn;a z#j&p}oT*~wPv{}7*@m%@CY4-RWn7`*fqZOFFT~2uOg!lL&yM5`cAMRw{NE?0rf+)W zE9x zh=&P0Pf3}Pk10Qy{F#os3=CeWcA0!S(NcJn{MQQ>RQ~{U&zE~vi&^kyKAt(`5nF}n z8iICv4*b;TlID#H3xO1iNTJMcxX(b@?*H1Q2k9ediwFE1HJgBM@!$$K(?FfKt1+zEb7-nAwSCfyLn@lU_ z=Hu$)kC2(S%AlPp`EzN^1;}}}AZiz^5YL8LJIk*Qj9}M%{?G(<+1|f)p;{O(+eN0)`e-9!5deviDVU`6{xh`;*a6<^8PO zkjmfF@?JsGmQ|>3pr6LQqd1o(yDGlFVBWecB;6-Y%DjDS-970^-Au$eBs6Y}elQR8Hs)#SZPiT6Xv>H{jN0jKvW zXspUvRF0tu2e&_=84U<0zN60Y&?;jsRT-vvS|utKRQ4{-*;oW_(_ox&!m{z(@A(FC zA##XyzdQ9qriuyeu?*zyXVNR`Ed9~H1^_5lP&lI=qw-cN8|T`f3b8aicS#wKu` zW$ApI>}7M5BnwB$RrEi9NVteQfH?v8)+}oI1|Fuj7@0?ZM{u(i9@?)2Kc-TwfV?yw)l z^R@X>myNz=J8eJorZiYB9|@p9ss z6ADFbx~fmr22%)QnnJ)bi*-I;%UDqF$&ZlbPgj?% z_*2C3^7j@=(`}I~OsQ%mk!hgG+2hX=bEC@_9A@OkdA7N;VyTu}y4EgeS0bqlqh7?f zxOy@df&8a0#Wq?|r4+&P>*DKkb~_v`Zcxa`u0}--OkRbr4xfA^h8)*SsYE0FC^c{#oij>)P-9C+K_<T&c^Nn?2qV@r&j(v*7SylN4)9gEhvIX0}zc1bdmMYroH z%uRAic>0Ldf*BhPM#parkCCQlmw6^ub~=sm#FX)I<%40D9Be7a8y5!_4?#=Sv85?x zSTR?yi>yV6P*|IE@8bKNRytr{%P!YVe6g^2SY9SX!9kSzd<;x<610IwW z&#dWTHU<(5zQfA4WSQbCS*oS!Bn)j~_woJICfCNib4QJ{+j%Z{^H&%*I-=Q6oVwY> zEs?Icb@iHtSH*rKOMY}KGucwO@N6vG1XDd51J~?|$&Bkt4 zvs@ytkE$66+*e_Xf5swLF2@@!k}CxQ$@mcZTO5LHY%OM&-2+?z=hVh_(o+u-#S3Yw zFzM4yPHUx1-zM9APbB?bRU8H{*`B1hB{)_M)wwF59-*AEZb!0i`2@Had2oiI3II(C z4|YT+&$hMs)3X9VM+m)Y6ZcqmtkDzJy-)@xupj_i?nmRn_tLSh}d~$8Nk5tH=x2fQvvwBLp8+@j&{R92t}k zJp%>fw-1fJxdv{4E3_vZ_bPM7CI^HvpN?o^qv;lCh-&~g2tyKrc{@&F)k z#Xw*{ViYf#p**TzC1y+4ybid0a%1-jsLS{TprCStx&luMP%;m1J%J`ix{9}9$9k&r%^`35r>)nl`(6$wy0z+2KTRXocR8(}1g?NTO2ZU@> z#`L>|+U+thh8|ACS-4DW92{+CF~FNPy@cul#Iy`T3eiuo@BHk$V&wuBucbs>m@rU* z?^VqaJ1=H_cy3-UskgB49bU&Dcd@okX3NO-`9A1wDK6z&5{_Clv0o{DNqb{ep z@ZAeD7)%Q2=aMh)k!Yoe5I$*F2bN6c%xMaKN2;bC+;6rUy_b`D9?M^fvW7OkSQ^Yc zbd@OagzICAkA-W--u9<#oU<~*&s0l+Pd`BDh0ihf&Lb?Kqvj}-8T35>PwEg)FUfvL z**=Cie;VqmkaoI}nY0<0_|etjs0O>-toqW_lyR?-OzN%!Q!KZla)wKWc2eU82B3s4 zDSNL}T%HutBZ9zR_K6c+q1)_s8Av{8#mR;)#z!|pgwWS(adq=7)YiiXCnGC4VKyI0 zKV_*(`>9HzP$`w3GF&o>jJXvWxL%DAf)G0GcIf!GgkswTZ1&FbuOudwuGY9lIM7AC zL=4)QId`R;iApIo#Mi+v%~otcKtbj_x0i_Uf=M7%y}d(vhcK)BJH-5VU9jD4=S;6D z-DOi3J3k*E5~SdDGR5^An;c9C8CIwlPj`Osjt@^f}9QqSx`-AB76hKx<|S za`EE4eK*Gc0P!1t`&j<~<3B=&h{tEP&)c+dzC+B?X>=I*%J}$lIq+<{IpYxJW;$Wy zVq+vbeM=D@m3VhpJIxysX@F%zWK-P(KjoS?4jVI*Wev@EQTemz6>+mOvhzQI;%ITT z{yi$%Ddf3G(E7{_d_y*#Gc3{9>XRP4g<>*Dk+l@b(rjf{1RVLMo^yy0gp8qww2=S z3~=z^k9Cr9>9W>!_Rvh^7FnE!XH-C*!4p1cp+`9d5QZ=N(KG24pU?JC++leV>-TcU zj!)I?_Zcw{OEfVs`u#t=C6zL=dP)*V_N_3DfLXhJT{U-leEcmBw8J5KbyqQwwja?t z6j@58Z)Iy=-Z@F*v9Qpq7^mfBZAsyTv^BaMSg-F)}LqY{{YdDms8v910ZhHP(Ul$cql@4ALP8B)#Uqm zoKPi6ImU^Ez&k3IBdYMkxPA#FuRmefsZyiw$UOc_z?`xxnsr@2E29-C&?@2gBaz4A zrAR04Pjk>78Te)aN)NFB_UHP3oZ;)qe{Ww%QIp4B8f5_q!99Qo$g9b#FJ({)20!|} zi7f5db#TToCoY#6srCDGY-YSPB6$uQbuB#8wLT29;*vW*QbO+ zoFnTDrCE3_)sJ3V?tO#;s!2x!t>LHSbENPpt=Uwokch;U zMF6lTumzBmTLmPspT7b55#}V+McMC=5vp(2LYnh1^+Vq2ITk z9P$c$Dd77I4Vw7=2fHEGtkW4gEIDj%hn1U;iW<~jmBp?Mb24XTIXGDXRHW{b@jd*5 zY{WD?NO}N-41pbrni%x$)>mbf$>)=O!s=cwe}HcnIu_@>_o_W1FgUPEu81Y%* zHVy^jBZr+Vc6&|-8;HgjOE@RgQzF>ySS6;hIYia)3^e=3b|5L$O(=`@@I$vV&G2%! zI35k-+MJ!XTI;be^E{ott2MtK#!R!r`eeC*6@YQk_& z&^>ic7%Mq;fdWDC;adw^=DsPDq{_tey+&@IG3kAejOr~MY{9CAMJhT%&u;vaad+f61O z7S%MqSFqgqHgz^rrQaQ1xXAXXoN-hv-pqZEh9?E8D^`->(Zfh#a*b$~_8T4YEp?uI zu*TH*`N9~w?R52}uCt0vVdFk#5FJb7`Wq;#9H+gl(qQ#xG@yV1h?zlD0e-$dr6Iez z&!3KNwK-lrriYVju`wQRldm|9Nb!xA0^uFAU1V%sKgY?Q`#86MM#cWiuLOPMt6XSKX?mD)8v zuRc?0#zfB*p0mWY8zD;LmrQn|Rjf(rv9JPmj5{7FKp7SUtI&c* zbwp7a8(~~5LJf#TcYev2sHZkOBNcwPIm#3*d$LWJy&59S6P0ga1QI=q$0vd83c#TD z&4spXC;tEl$QA%A0hieXaJb|9$-}w#t&=)_B2-*COetf+mMWo`zuC#>?y9TU7h-rS zM{j3g!Bc)v0ODMqFH`u6aihqOwmAGg>B5vWeo&5Ef>jA&*nP@`C4ZR80B$#Du8Wcn zqnElmK012anMAA?SkzJb_7C`~DWL^R1pI;^qVzquMR0j5%S}`gR~}%EJ-cl7=Mu76HB+tNVsfWm!2WLv(8uLN3A>QYz_}%QhjOE#X8b~)PdtP6 zEO_MprX*0SR6P&XIU$1O$apl$4K5%NPNS=K?%b*~zhd0bf`*I0EUK;5dW8x{v$2ts zB2xW)^5sO<p|Md4e#rN_*iezq`{V%f1*Wo)eEe@vxxrgmD|h;i9rSMDA& zKIW(%5Ag)XZMzm!KqgM4vAEKqO6VigcU0f;SqSTQq5NbL;3d8B`(@|2o<<%Pj|zBn zY2z)WM9SEGWgD6%3TET|OQYPRDCr@X=KND+B4LHZK`I##f>aMfYUdiIOspJ=bLW=5 zVrud1dEjgIyH70I=I^zzz}a7AsJ2>UuD@TCJg=#pwXp{$F)58RiHxRM4@R|Vk0z&~ zCt_LXt_oaw*>k$8#d+mj2=&q(T20QVc%5aM;0Nn8dQI}3evbK#GQK`=dZ^OrWbiqm10$>^Th>F*jpjr=pF@lRBIhjojU zoscuLu%y+#c78&%FmZ6HnJCWG)Zt}f=Fze7=&6}n+uqfA-@8ml38q7z-mqLbDh(>M zm;^SatW1Un3q8_%P}IabSKKIdX<8Fy1y^X zK0kr{tN#GBYQOFMSN;D0>EXBzW7q8vC&#`aTf=3SuE)#XZ6~nG%59_Nki#h3@w3ri z8K$Dl<*X6Pd`lBFB#WDeyK}C1Fs@$prM+&p736GKHZR9w~ z; zcDp^4ZRI(*xSJNLVe2D_OG6V~jY>1L>1e!{`J&kIz5bqS3#HW&8r3M#0Dyv0v@1;V z@{kscK`?~o)w?~ua8!5Qf@2f@Bc#b}0sgsqDmw}5pi0h~CR!x2^ zjWZoG;KQKx`J$f_6*XFbN1=M|<5MaR=6{r#e={I9Y@;~UQ>-eA5PM^yUn z8`dT~6pPaotgmOAcUs$^kjz2aaV*>ns*ASv?*9PFaK=*tO-UI)D!p@f$=f6c4rfAy z)}Ykc4>lcRp8f#HrzY;}_cRj;RxBBqlB^V!{-kvwgAS@#s-P#@Hb9sdVAVfs<&<#y zKmvK03ZVMYkW(yxR(`fimIYW8dUyVSuX0E|Nc@i_l^7{dI3bXKRky@bhTm+D@1#(e z6_}w<6#alzsLBCa_v=+|C)}$B1fOYDW7*KFAR+sX#Hm&RM??0EF$Il?P*gr>?)~G% z%N)3O=8OY_!4Am01IwNeCnNDagCSym-IN)kvy-5$6H-x40-NUCy-$C@=^Bj3JbPR! zeZvmHm+V6QfnQ^i%fMhz9tmVFIYtEt_Mo9nGfOykg#%;cbJwE-`6ngRGJpzjNdExV z{$FnG!z*z?$P5Vtz|+q>sEAD|nJMe-PPOd{zf`J8azJj1;T&}KQ>Y(q7=A%OAyl%K zA&F&H5~Wi&PJYoI1E+KLg&AheS(Jbjvk}ScreIQzQ1jEW@JFyK7GgQ4Cy&uY;)nnq z{Rdp2v~SB=4la8#9_=86qp2h>;t4;IW81d`@U1r}wcBQg0(3+r1EU{ zXIit)Q&}fI7x4(;+p%<}KkLha2J(+^eX)(fdKCtSB zh)0z3?~e+@KDvF*$7Qj})nNFpn*v!mcE&oSeSC9Tw`^mKP4*icY0D%CP2lYpUthOd z3`|6ps^0w^s)eywl(Mb56$7~ZTC|BnGG8<7E1fS7$?@p;(8rtz&9!TviHf|_C9F?u zTjwGp!QUOSZn)!#qU4a$p@}-(=-#Sfz%|#yJb^mOO1qW3=a=n`(#`0ZW|fZR>i(rIjj* zwaV4X8aVLHqJM~X()4Ze64=;&KaWhjp*SFt#(6SYJ~+J#y%N#lNsoTn4#R|wgo;F< z5{aEXY*Dm>i2zqtLN2wn1rs;Q7XJXjzCr&0ALHNk&4>OE^*8?jG{5^(ec~KH?MvPo zJ~@qnp~j^&-c6B-H^qTm7udESv&b1u>jOol7?*c5bwf;=Jt)i=vuAw6kn`)!VsnNE zRP&Q^U14Vv&_-mhxrvYy9qJHt~NQy$Jk9~gBG;D6Wq|gLtfom7_6l=+ywZk%N%DKUxiM+u zYqGP5l_)|6wnXAtn-p3Z9Q4SlrA4NUIixa@5TAcFq(o>nFM{Jh=Gq&&cz( zdjpw}&ea&$^YvjxO){N|8BL_MPohf(zCp-m$W=c~@<>(3DpgxH?7724Y2+JBkFQE> zbFH0`jjB|~#GMS`JjUivEPu6F}oTGOGf~iKy)KI440~C5(hX4sdxPP zb>|h!18?u)v$VH-%tVaO3 zZ_SS*s{a5*J&Cc#TT1GlK5Lj{}~6NmuqyJU4-P=Z^-X zmpSmIf;*{D3jLdvKipkgkOw5QgV2}$;M{(k5({$g?amwp=aznXsI2Vf*R7t8k1orS3Q-^Md?HCcNKyX8&W4;gablb>~&QmsqMPytIX zV|96x)e^Il_>aWRx8e`>(AwkBe$zFK3-a(TI(q0G!mpj0_EF09=_dWDU}wt5eG zj+MMUHaGDdZjRFM$m_E<(+Qcd%XOeN$-H4UhGk{V#W6^1ib~Gcg5HQ1ys}sICnRRy zio|9LLKmT-KtVjmaH@wy61shc-hM*4)6d>ROZ3y%WXj1LE82`}m5+=u?HN-HwnVkq zwoj&d<<}?{$2~F4H4kf#KD4|p5znCWvRGOdb-ycXpHoj8-h5}N$;hI@bJ(T&Y;7FI z$(aRFN7E!lvC{TTie!C7Br%}sD>zleER{7!4EKnTjVh{G8ViycdAYER$oR+POJdV` zrSm88{4Bg5Tj4g+rpmdQxh%@D!kS+f4sm>ZKr%bsvtlw*wC^E}R%Ae#W>4(vPIrlg zK)&CY_a6A~dw8A_c_v?zC%Y#zU~%b8OpN&;##`Fgq%fr?bB#Pl7H?xTy4B-PV3d-A zYu%GoY>Pj5G_nRISF#oF%8u?D7Iiq;_z=d`l+gTCuNG5^H?SwGqJ_J&m)kO zaf)&)EB^qsmM%kq7;&rP}B0NV{_iw@ki(fW)`B?7H1tdgVZEe990JD5_4qhoj$Pj@o$nfxR}p}zWC$3sn;|6uP<{OEoG4Dr#l^-=VaH2g#fM~Ju0bl4h=TT54&tKIodUH*;d zjJJHeIb#p!+an>5I850MH4%1dsTYMLk0s*X44{xbY^bN2S?!@p_?VPHSSl2%-48(J z`NdQDe`kLd@IBU~!xuMey^T)KW-oG>u=axq#4$uM?6iqiLsZ5V@x=b_VoE6HiBL#XU^GV!6oNde)M3Zm;pKy%zW)GX0SsBVEyFnn zp&*{_L3L2vSP$X)r?7QJRe{``>-$sMm$tuHc^m*o+$ys4<%uN$CBL(^Wld* zig#WCU+-b}1BK`u5%&Q?p8Nn&DMVdJ_YFXF^`s55^|!0)NzAE$7uLw3or)-7*q^xk zobVM%qAhPLb%d5<(Kp2UFMvVZi%w z)yM=h1z)oOdHV(&{*=aSs_j>nmR7A&hbU+jFLBv@su@TGv2F`DU$TF~Re!4nP($`o zy+WDFNmW}}1qZ7>r#Mh`X+RHVVJ0B=<;U1|ZhDpKoc1KJA7%=qwH*59$I2zl?2IgkCo+@g?5fns%*wVwlxRiajvD1C;H8#{P@RbT{bZCqeG{E3 zuSgfhFg3mg`yGdn`3KZ)Ht@$4j!d4_tm-4`#(>0it~ae_rNt}gKFTuMm7q$grp-`( z%5`^x2^s1~X7?55GF4R9`9AkUuF2V9_>PZ1GjX)5L8}Yen#?>|N_bVUHJEo5sb~pf z=Q$X)YO)u^Y>LwoRIYaBKoq8`v@sc_O+jx*TP5r0A*HeYJ&$eA8-ZG8m(0n)8CR1v zRxeeCJ}S*+_z%WCV86_(3#)p|Ctu{Bs-r1^)oFVgCSs`j;Hp`VX`QBk`{B9~8vK+G^JyY2~Uhb8D?xvtDC2)%6g^ z$Ye};T`}FDxs;^r;mV@9ddRT|#)qPXzEoS-!5UzLLatG%MbDcL@PJ&{-zDYwPJXAx zvHmzX7})P@L7N-CRvw8cS@?Un&nkxNFDv;|{3MsrFED}q0N zsCH^YLqi8~@y4e!H&f?d;tUF6-xn6r$oh=B=U*M<8dg{A?2*VbOxQZobZOczFqN95 zKTse6kh&LR&crDw3bZH_VXUslCtrU6#kMzxI@d2BDwy=gnXUZ|le3mPVn-CDULy`{ zj9(mJ5hrUqi4q|>V)}XL-$;P`tAFu_giCFMrt+@~#qtc=Vb0LlZE*gmM zC6g?0qLfUAM#EoQ!g((AoNu$c_kPv3P5s^!=2b&5l<<^AD@(E|%Q(r}cAPvil*3WMpLejPeRTQV@q8W(p@ve`INS zNXIKD9+P_aJBxAcI2%aB#t0Qa{Z3yxE{p)Wl;*dN-zFrcNeLiUfqzo~K?;aVHgtOB zr133`u|k+#yA)bM(~Z_P(Ftwu|L(Z=s(=U{{VXgVubbJ_aF}I+p98xFXg5pfsw(_Ht^CTHXuY) z&p%B04$A0Pvns)nW&^5^`FWkgT@7;k{bD~dndP6aR8Fzu zcwY4NDX+gF2Uh~0(66x={{SdaOD_kpPyt`eAt3&j^7Gky8s+0_6XTaZLjn&B@+SLN zUs?UQo2VVXx%gEecKlfs5}<`r5!jbK(7>AH4=T41{8HvSkH4B0gA3t%{>&c-EAl9x z?o=X;_xmFKzRU=6>OmrkKy~}9{{WXHDm&3ab7NhptzS}Q| zTgNdqTC*&D=3Xvt&p$e;Tw#*qGR*j>j0ADxmME64OvyPB;WB4BtUP1l-IE$!x^uv0 z1gIb?gp^fFgezOKi1Z!Hj4`mKmTG{VK&1i_0L?;$XuJL&h}sQqJvF->ZpS|>BKXx~ zutT@Wm7w>`b+i(e!`B%4rH8|+=34Te5m?J7B79+%WkV_J$x~L#TZBCU?`ko2a(E#D;gjz8-E?K)fM!Sz|lZs_@9KLJHrrc1TqMGbNmR`KL-j z1w!T_*W|k|9@S>y`0cPaIet%zhdeC3h9)$2701cT#)a&+%M~e)MvN08Xs}Bt$jF>P zmR*Kp5TX74$T^@M&|p+9a5lc5Fl*_oeI-s_mol%7wb*PlNG&* zfQ;fMazY|`;^zYY02R@H?6v;@tKa_s3Ho0oY23shw~?-c#pR2=@eSs$%6zA9O5bA| z;Vghk2W-8bUVFvyVcve!#KdSl)~kBi%SD<%nzi+!RY|4JNbxC21p<{;ba8aBYPBGW ze7ljc)TfVQ_+<6FOq_7A%@5K*RXS59l=eM6>U!mm?DbA_veBzdaYmHzmGrk-tT7~& zK4s!vkV2LR#$HFK&{qRZxbi_<={9!%0B*Tup`VS$GwQIO?UcCx07X3OW@NrP#`T&} z-yfyc`1=C!%b6^HV7bFWRW`pL`0g#Swz!)CTt8+Vb8kGFW@P2%O0wHXtEvh+;}J)9 zEMMv})3cXUpvsbXRQg?h$hv6?8*$le_F5a`YWJDCj|n=Q>tkw@4O8iLkgajyfj6Ba zBuvb1l}kijR~Snc3nh|cPVNRD%$`s|D=-W*zrWrEjCf?4Y=K1366tSlrZ`jOS)*g; zo>8>=uJX3eB@+2|Z}$-G>-DP+W~v=n$W4-csntN|x3cc63_LB93`-!CnL(MZYO3MY zk3a-@=J3XzZKDuz6b6FA$;8b$U$2>6% zP>@vgI3!AoEfS%RR0=^fdNshuoXs+!b+5Wr7e8Z3rxWMNYo@VJ9mldQC?>>!#n}pa z!mEJdY#fAQSMAFE)GKP??Rav8Jjp6lgl$19$;~3Ml$5ALuRez?p;fvP-VqxXlWWT5(SD|~W4zV5rtXXegJ1iQ9?w1P@ z0Ea+$zr|m|HI!merk0(3fXtD^`c*&$(oW1ME)%>?LK%*H9JqE zFR7&VkYTmpfPe=KcuIZqx@f`(WScKM$`YUVP2;viKj7SM^I*tFv4T4G{D9z1(Uq0Q zV(g_;tMyiDG}~qouv2k-kf5Ng)snEdCi4DjkR<;AbN)L;Wj-Y-dmMvAu1|H4XpR^t zDq6$8--YxS;EFQ`Gm~h;C=e2tKxnSI3frtM1E+fEAs2h|kBaVA^M}<;==DEZB}xC3%g%V!_&`nrulVn3Y0NLaJYgmwM}&+SxXo zU7l2uV=LIcW%RYX2!8x;^52+_D&?6=tB(ocqys?}iGIh1a{ zQzoj-Te-pB=_c2a)ESVA$CB{qfUd zc;QLC`E!Z&$uO{@czoA#^vZfy7H(>k_Rd*>AMJVvbM4*@9_7=<@w}#Vj!n0_*y1_a zzTXxwwZyUMk_}r1b4*BQJe?(th&#Ysplg>3&k>lXI~`Wsk$Y(cx1&?#TTO1e%knh7 zB|UN-vum)SvDU{Wec3r{IhAR9DVARt3C7UJ5-X377}M%er6?*r(<=F#2b>ty>%2z~ zJNRxcUX5H--zPgV`4P2o?SJ6Nq(fcZC>RXfh!h-m{XhT$2xvX0@gF4E&GNPWQ{*{5H;#>)w)Jg+ zms~Y>o;h<-I>Cx5X+ZOlb#YlM!x&C-%1HqW(TH21D4l<3P{N1G9~5hFb`|qj?{)p> zxn|AI!lf{y5hXXtfqZs&WDH!jXRFwKBCj^1Yt##6B68W)F>Lfvm>KrVF;_`rLOagA*EJ-$Gg{ zeRn|FX(}@+%B-MzCgk{V2_?qbB?e%hr4b&rAgKf~P{6Q`Yi-{XB+^S)3TP~#3W6LU z>Yvj|^0giIcvDb%laLLW$@i7D=6Ae z6W(F$;zjMFHnxIxW|_Q_wMA6>7RFJA=N z8K?l6?B}$#a6>CJrRY^pP!%t#AgjVrn2$cM#YicU zks~%&49vW!BcNJvq9quEl@Bi+ZCH50li_rm5rgHDWIQ8E zdfcACGb=R>HYlpSLo*?h1R+*`amvT^pYWD9N}Sv)+SG*@ay%V_D36*=yVXK}4%4@J z&M)gL#^}1URftejeUT`7mTCT^0;)=N2)@hvt>H%JBfyWf7QSf}9e7%4HB8OQ_jLpK z*(ag~1(&$349Fg~WBObnTzN!IIYNL{6~7Sbm;R7&skDXXQEFM!a}yB5}Fd35mH>u9qvY;FHG~-sa+L-CnERL1kttu3Plv z0YbF3MSU5wS|iF*huw8**~pEPE5!gw&ux>2Da$)#;b-F(>r;r?cY-s9(Wb2(H4#A%Wu}Q+pBiGpD*Ho>h`3ps9tbCt+?ro=H zntVbyu#D3YFa!XlKs5mdXtpIAu-*G~xalg8b?;?lvg?#j3F6~Xm51ZAz*^GSNn32A zx2wvto)WsssVtYc$-i8jkYv<=A$v_=tB(A%@(Gr;LaNEpQBy~)k(R-D~%v7Qa|3pf5rrCTSh*)yu6Dzksogn&8| zuWXz16BA4?`(@%XWddcUkH?N>_j#Md^H@h8Fs2>~WaGPK2Bs!Oa}x+-iQR(LWY1Lf zIg{1`RyfZHl1i9V&^|t1=UKAzoSzuM+RmPhPS-zPl!i47?yyV@!d;k;bgs3>qs{on zlC9QKca*akd3x*Nh@QDMLineZ4Iz7b;o1!{W@7mMeuH_l-)2iEZ{#?|VUuG(R}Tv( zD8{)d2snCUp;U^DtgEs`$qY^~DKQ=5JSqrvs+20V{{RRn)S8mW0{8e|#y{l8i2nfa z<$w16r}Gc_homk6ne_WbaKX2V{BN+<(^IC!@~mkKW@a2%oJtIvr7@w8HWH!8jtpzg zI4E(_@9*^nVI!VNCE$dwD{{9~0ZrmX@?6bd$(}uj;{Gqzo^63#JAD5{}Y2kcX<+@;EcH0dXjNoGA ze;Ik^)XI>P3S>fba%n^_7I(RgsX0#XYfmAh(moy;o@qc;BrvIZvxT!A7LY7lS!9rs zSqUoE^-7y0a#QF30F+bZFFMKaUydI>ehse7v>!V!K5cI&%EYO*cHcWCEO0SAZ(U4! z1v^shk1`AZKc-u@A*so-=uErg^3kOhT&? zs90#89L!hTovmw?PZRJu*|qh@eRkV(g7-|HE2|csN~LKjWa0SZuo9LLoUOK+jP1m{$#pBT$@u%lcN%SX#oiAr{90Zbza!q|$7AF^ zP2{+F7s8gpdJKIRjbYypVTXz38ys0;ZMItjbzw>nN)QrO=Tri?YVmW@M#>p{i(Y zvagMUcNv&1TBZ{J09Ih%4IEGxLD3YF^%jWDj6htBzW)g9`a7Xx3z;LKq zq7wJ_%tdLj=2A&+$2PiY`$LPzKhg8@mh(3+7iIe_*lVdGD(i>lUMaXdPRoyF`b^ts zHLGquqBX}{*j1~o*-5cIP!uixs=+7vYHkP`-=ACL^6e7=RYp)&lohvCc%OWC{SR8r ztb6$n!9Hu__Q<8SvKt~B z^;6^0ANi?)sOm?tRP#uZwV&uZ_`^FKis^i#QI%G9soCp1Sw=jMD_+%yooBRD8@nz= z4lt@sah(TaXR7}IRP5vhloQl<0Q6&@F(I{LErpgAY5xG|{d`HAE4;kN$8{8^dNg;K zGzpIg)c#e+*IlUU>T&U0wao|OQq)z(lSpo@lpx>;D9hDJUXBycgKM1~Ggr+lh3U`| z0Wl8w-}(jqAMou}cN;(4f9=;vjTnb?@;#ozQ6Th8Z$z7Av)y7sYND@M&nbFj8Z5D> z)fDzQ+}k|O!itW1<{ek>P@pfwAYkb4v-;rm2 z@t$Xbc`hd6UfG5A{X^jG^BP{Kqf6PAkoQ$V_qDMt!iv43R|k!bQfB>JJ3zGX<~_}nChl{D=(rv)5< z)nMI6WggSq2<{0Irel#n1!%{J>Rs01AU3Q)GL1zw7N<+Zce?$p_}jaPHbQbsk5^tt z*gA(7EU(0sMG{t+4qeBnhErP~a?2xtN#>Q%0nUw8c)5qQ%yk`$dnLoKD64*P@P7Nk zt!<8*BPJez^_AkSI`tK1c+wqNZ7*0NzSX2$I8xoquj`-M_k8#&OsW>B$}rnvcVSR*`p)CP?& zi$n~#^TFD-Ts@*mu<>OES(G?d%g0x=Y3#dH*fWXcC_x1+s)L=b3IOIhJ^OC`&0COdtDdEw*B!c4z1fk=8Vb-5NKfC5n7YyRJUV%cCF#tzRTO4RM0 z47?qXzD6=4<78q>5>?4$#FE&ry>aO)R`~IqP2-Vvr#Uc`01)*{SlP6t#B9ko7+*Z` zVDsj9#9IT!vUWTD-^f2mC$K4tO`a|@>-ys!`EbRaFbx!hXk_7X2c%D|X&-$|K+^%5 zXLt1Q!KIWUkhcl-wQ4e>v^BFgFv=S37s>ufg7?H}!<_X7kR}hqj|@ljFMmq49l&qjltY4+_T*FBY)~ zhHkK>-Bz~8#mmKPT$!X=X_GeuR}h;D&JeQG&kk_k{y+1#{{Z5j^&I~I{DbulBmP6{ z_JXhbQu+S?dLxY7oKK2;z7xmQ$K*^XYXos&tIBF(;9DgqK)YHBSJNX)9EXKL1?oL_ zNI&Q)=ci7I0`JnH?XRR6#_}A$IgG!D=`(a0`wXYa%k|gjvfI)1JW5u{jN>e5L)9XI#ePcBv1O5u z6gcN?dzOGmia1 z9;BGzh-WI4cIsAg3_0w*Phq>ZaWRn1DJ}>l864K7X_3uVGd7$;m7~|cYYQ3&G0{&Q zjPPKxNC=ezk>i$P^7XqgW>Pw<{{UlkLJ#!m$S1bJ!j@S86lyZ}6#T|i(<$@6++8k_ zW3)fIAD?`CU4x_X@0`9|(rR_sNv^@y>Hb3U{7f7Taz=Rb!^XzP-AfrTWf(^v9vH}y zlEwuQ3{=eA124^&_GQ)}u5xnyFQwZ07*b!|ckV`QWv9=dHhV8fwpE+uFFU)s%n7U6 zslH|wJrPj!WuToNq_+W4lo3EWeUp?9Y1M-dcyD6aSIYkYA^AAhY%%v5 znQwg8$TthC*x}tTkG0jyD`k`PTP%C3v^F@R84_R~Hk0sPCP!kN-^a5hqO`?He^mhA z)({uvUy#>npS*|29(9Gi+TRk}y`~?Q@3A%;+iTK`gI(5UHo`eOmd0CDVy3{|aoyeI z1zxpDODG!a_Jt`R6aeau7hZ~v%5{SIe+LdaA0T#KeF~kUaN-$fassx!>LZea>i=tM!&QDKL!Q#;QBY#pr86Wd6jf^aa(L zZpKOquj#lYRrI%<;Ezv5qug|-BLLHLvAZh1Ra)V_U*BTx8dd64KVTJz9QQ5xxZ@ln z^$vhg0n!09%5=9c&+kYon@qp4c3LJ#nBfbtR%T>=Q9+xBSK+Fjvfv{xRes_?EJrb& zV;^a5p0a3@E~GI+LF}xS3c*;OSrno$D!p0Cw_@L^@N4M?SC*ic(u!`aWBGvFI*&$W ztFkccQiab96$HX7{y%jE6tHdz@&`SS>LyfWT)isn$(M{rMWGH^X7?KHrJ^ceWb%IGU5~nA7QW?8_ULM9Yn?g75 zGkSLX7LlT`U|6r%IbX^|6(ka&`zOCQY1!q(kjg_4iUN3-AxQ3*F!2Zgg8HM?Ik)CH z+4IlFzrk|<0E+77kuO1cTOLOl=V*{wq+;#svMb2Qt4)s<;C7K_;r9KIU{5jvgsID& zS!(yCqfZMJK(ZDup0st+_lUP!y1wsJo4hADzDeU*F-)V2)(qtVVU`ye)Fw7uX7xlx zRH;plC#%yn=a}9bE*{f^J{;qS5I|4~1S(W9YatxVYiyY34sbyrDyE_B;nF)F`+V{r zlYUhA^HH<$k0|nL;$AXbokkw%^0FbFueQTpiIY6DYD*mKXOz<7nE4wlV=T%ixclYf zyT@jmWXGQfiN8$66d)l=B?9)iMvq3=Cm7f=%%xG^s8UrC+>*JI64n=yYdj}=k*?lk zZePu*iuibW8mvkBe~ahh(!~8_-4)&69<{ng2GE8L8gA(qRm(D&iiB}j<&uRQ`Ni^h zKteL+226gzZqQ`sPsTHGt@5sU!p1b7bocw89lln`c-+2l;r-_wslP*=l&`4fBo^{zy3cW{{X`N zo8%Ak9>N=lcxC?pdLhX9FMxbwg|P7tDe-${Wa{>kz`gL7FKLyXVbH8)c-ECkVMxmv zqkM_dZc`rWYrsFE!)d z@tv-LMHLltB#t5}HOOh73k)dGgk1DWAx{JjdF-r+eX4X$w~kwW@;7=-r^$F)tsZVx zP6nG^%bS~!Eh%ShwgNeswT3Q5O;R|n#|O2hl&i|D10yXDq5M07M1o^QB@a5OYmuS3 zc;vU%BFtq3u?{U(Pec_=ltd5Z@Z-sJI2H&-vsbIbQx*jQUt`y(1SlTIr3((B{hX1) zf_)2S1ByvPs)v4t=C&*Jj!zVj6>Lvj&wi18{vN^$t-}tXePUe@j#Q+&lQGHc5vkj& z^&MSYf&)yR`*&eWEV3020->kSQS6A3kF-prspZW|bIjg-#1imH#4-bkx&+PpIE?x0U)tSOPiQ|kx*#x5_YeUp zSTCu2u;fWt4i>Ch+*g^f>Pt;aT017tpYwAfUQ$=lNUa+?L^^(a?WI&d{UgN`Po(6E z3amm1hHl;5mYo73swY)tD6Lm+yoUV-eV-ji_hmETDDP4gvk7)oheOxYczs~kE*sdX zQvU$hpJr~rngRp5hLnjf{(X>kUQa5bAIoDENeN1^PW+!+DTd&$>8lczMs`!y12)KH zEi?BNEWsyMV6z96RqB%rs;}RaVb}xrJsb4n0~%64L3)0YNg4nY^gmcrO3tHE>OCA7 z_Em4o*jjAdvSWoSQ7HXb5>K_nZ>d{KOibL{LjnbP=n)D!5==^PWVJo6u7<>jX~pgJ zC)I0uMs*5Ys`W0ZML>>l}kcN(2ntl7A;wZg`riyOAV09?>L3&=Ku_Js`$DOFsbhRZ<_avK96? z>c!QQs<@|aNG(CYqR;|^^%E<+rSIY$8DNqJCn|cue9VB38B23N!>|fR<_kCXm3n=V z$P-W$7AKB4{YGgJFd&26C%cTf6%ePDFViP}fk5VU5Q~(ilp&P3>u*yTs*0AY{gO#X zmmakeZ&~Q=K8y^P@uiD~ibxgZTNSonn3fr>S;6c+kpTS7fgPWXUW~K$;xy~f0EhWB z`cj}LK{6zur=9ah4wpte8 zW9~J10HG?VnOI4Ep{sQT$rv%s#b3Hj4J$*yE5|2^C^c|>yvmnz8qzQXF<(B>d4FM> zFEq~bFFWwB3eNHzUH+ab3nH(Wxu3xWkvy>u7Q%HI^l^S0dC(dI#&z zJ}kh(0AfoZ<6aXmN?qKISUV|>=W}D9l!|TYsufh4fIv`6x<^Z+lc3k(ZX=bG2VM84^Olt84kiB zZC9QATcq4=Vdq{qyZVf5n?kpoGjA`e$D`9YLosP3yU_%DzGrNC_nfONfX_NSvfPe9 z+nhkoY~8;hAy<9mTivz4{sX4h#y%~sGhb`7{u&m!m{c>PY;x&rSsY|JKFA}ZB2x$) z$%@kFrOZ4q)K(=_7p=SB(gQd2o}S`3oBNs?Hu-aRiR0PFYpTOsZGJv+M_A{+JjAPP zbL-)uK&Xh}8Z3w&W|Qd8=-Y=6`NWJ zk+WaVa}`MI8^U3D4Zv=^0r;uw`7)jy@H&ovs6CF%56zGTs0)=Ueytq#BL&HM`JYI? zUllylSAw)6Y*7mGpsP`W093c;MGF$j<%;luRfyyD-)v{}Ve$gS*x3{k7eGRo#Kmc* zP&qOJoc*3JC&c(zy9{iZFRiFm#XmX{5koUHkI)~(-P^M5 zcoWT!hA|CNV5I>eg3Q-YY!h090t*Gr0{W+Zu``Vhq}3z*HyMyJO_ra_5pFxb1adni zxU|cbW*qbD?7(`c_Ut^V)n;089|~@hO`cGJzov{w_*5xEh{g~W5vzEIj4IMc07?6Y z;x4i@cl@I6^9F#sGB+e4b>={-l0zSacS#5PiB#&N{{ZP8-5`ezS#)s6TE1eL6e|QW zJTJ!;!ki#kRYQ7%pq$bDV51UKp6bkkXZdXI70F&)IG+XXnb-S?bVMeIg@Ajd99|$K zLKGz*)4jhC1$;Bc7`&-~{Z*A*8@oHOJ?73Co7l_T@c#gJWpUa1u~nx(!5gdp07b|U zDVQyiXINv&Jk|tX2=UKRQ;TG0lZpXAmFq$Z0DVEE!~i*FcL7RCi7I3h@Gj~v^1||# znQPX}i3tIySnzy*<1>io9n}xyPgT2iLhVkbO0+IPvvu}5u&3tKAB(sC0Pe}DD;Gbe z`jD48`YbmLxesq-joahT7q5DBsl3z%MaVPc5>{P;CoV^JAQL8IAgfc+3I>1S-tL)L z4vMVF$-5W4BpDBAZeQaq0p#N&^jR+<%650LJhEOED8E0hUsl0^uy`!WwB&j)%`ttHe9!x{N(8L}P%?ioo`vp&i|s zr#4dG%4^A`%^FCo#nAQaML%9W@jmX8cI|WE;r{?kt;(SVOAz@fDnV$8K=J(K1XZ7v zvSj`>wtup?oRyiomTqeTdQnk++}RIO3zw%;`339dd*W1(n<3<+5<5_2hRC`bHC~_! zD^y!W(P-%$^Vdpt`Ev)^-|dCH4yJ(*bK|>bn-(FM_F*|Wo0ItjUU)9tW+1QoBhoO zho-;8@HN|P9PcdMY5qivWD)jyte1*;21YG*4TO(;WEnWkdvQ$5m7XNybC79dGyQqc zn}$%8Q^^&>t%@thPlgBQNe!Y)^8=XWf}EmK*63;NDKRn{O1+O zMu@|8x7yclweas9@?QS{9ucjUG1)?xxVbXLe0-@jtkILM1#cCdrXK3;7Dp5I0=F=~ z!V;--ld!6W&nS%F(tPvVHjY93?Sqen^A^`%Eu410NwbVCos5J^*)yn}sGuGlOECL~ zbR)M{UWLD2o4I8l%gHgKN{OJAN`&kv3KcQM?^%JJMb_etT9*`Ya6^sm zhV-1(txC^-L+%POku^~Xlh)-_J6uSFMaOkC&DiZS~mn?*ZWpr7FW}z$9 zLOsELqj{F_^OXcRLm5?l^w)D!2i zX5jFLukIdeA&3xH7%HuKT|e1w$jWs&^itP>THp%vhj zQqBs|R%~SM7oY616FMZtri_YttM~PPXnp`t2kz>chd`NLf-#+Dl4*TM4cPk_xkJb{ z>YY86Z(@HlwH0U^A)pb0^bja1F;%ez4y%@?BrwtVX#PBHXqQu2cA$S+8$Vu2V$?NK z{iq)Nfb(Cd5kPd@Kg#Bv{c^|GZfp^w z*b>@I$f}8lQpQEqt>2JVXMi{>`s49G#JQg8-m#~Ks0c$vTKV&dd?_ zGv>=2F?xDrASQ^{W-`KtHN9fLuRSu56x@~;s%FvC$PgWUq3?yQ%<{jZ@!vo4uMzR{ zdE#cnwk$9*DU~r#Ym9oaA4r@=S#+bXSL0^{1VYTx$!Py^goR z?<}7l9vS7EC&S0cmM+@=08TRTb@?v%t>_Q44_wP7G2od~yh8_)H0Q&D!c`1wk`g(N zl&(-;{gA&af6bT2pY@H8{{WN!0HqQASswEJq9oPm{wThuGs^sr%=Na*!NZ0^>d(>p zwmT;iGQgj@vk{IusrJ=F%_yrZi!ZXv`i!8U2I!S-6PozZ0zt6_=u~qRDWdAH5$pM0 z-^#G|dPxmNF0V3|36j{u6Jb1gGL^?wRhARJL`j9MFKxn=oY>Xc=H!x-G=i=NMF5mq zdejGiV-O^na$;aDjICW z@JM6Jt1Gvt?r@tv5`k4}tf?rg7E~=-M-#kH79`n&q7)s$=oip6d0Kg{#=VDAx@DQ? zvXBoXFXUB1@rQ29eo#X2kCP3qR*&D9#eOsNJ4 z$|BthEZ@}NAnLOfH9WOVX59OIf3HT^fZ)SrQ7IKiVl1S(#wIc=8~zWlyj`z}x{Y1A z6RqcFELB*yYjrhMW6yKJ1^EFT#|%N_x6*&Zc@trnqa?afbuY=FF*b{IN&SSL;FjuMjecY>XK?j8>5b-tYq;*y;&ngsZ6TJs!G(*QEH?4}N zavUHbhd**zk6uSrA8yK_z$B-M8d8hghBOZ4_#VCh;T=h&_+XC7N=&MNAMUKj@eZJz zAxehls^lp}70KXidlk9$t19uqWJ{Am$Uz>%L;+O)05zAhFI0XNyD(J^!1Zfq3tiL` zU{yk>JO^4JC7cl5Q-`Sht3NGt^ljCffp%E~L9{Qds`7D9U}KOf5j^c+$?T*O&j zttllHE2&0UuQaNwQTfqP{?So>Q?jn+flw)5V=Gbn8Z`@b1$~UN0ZAMZtzPqfNnW)1 zbK}X$-gv_)rUh8BDg_jw6t19%gtD5lcVEXhY6BGuSi(0 z?!L&(I4_2>1*j$c+~gtS>}q&YPiJN0v59_O`X`rU5Ux|r!Ho3qErySA%4V?3PPOjTRs<3*~v>9wtsRAotWDL79ei|5}I6_hHF zsx_b`Qiu>_LWVKh?j_+a9R+)pV9KPrp>uAa?RR>tT+8O$%XQmNG{?$qljhf1pG-Zr zMpj&N?Sjf;_mt~fW-Q=8+Tr0jvy6Mn!-g`b3L1c-QB&NXOJl_{0bV+0|Zu0#^NCD3*>JPXXLrg#@ zimac5_^r{Kd#sK3$ClA~RA0k9i+PQFn-iIZKVCK|V3_1(QLM(*pIqfbDGY3zO6wBQ zjaEbyN*XS((w8FR*%1#Va2aWMm4Dec<^KTlb>ZLi>Hh$i{)fXq`Mpd@21BrhM$`7! zTio)9S9|2S-gl(hYBhcrsMY$h^0DzVQzm1*4YBc~v&YKdN+~v4GOPnFT|S+1 z(F?q^a3%dxulbampXfz1XsM1?Nzl+tMp{`El1*(&M<91P_Qm;RVPu)pBNND_Jb=`H zSgdm9YLuxz%rXIzrfo^k`x#3HBzk7(B<%800RaVBDl~FSDvKm>+h`nf0z)xMqGa|< zS44*PxrRrU=JGo?@tUwG8-^2gV4M&P_*2MM<+&uO9P)ZykbiwCIfQLPjY38)I9Ux< z4K#aQY0f$;;A&G_9>5MKFabt>;FT&zRqPL5e%*oR`<2l{{=Elc#Nik~ssacJV2HVr zl|=0r7=>m&RpRk{PNG^>WPVn+YBGJ-ggmnu`pM#CkjxY)B%aJrh5da${5uJ|R8;~U z5sGRNVgk%gVHdyTB`T>B>(Dz%y=00}kxNNk*mY(y>P<@|+tff~PO{-!f!)ue@uv|` z9r@Lk4KYbgbFCEV()1UHZ}>0Fv3y}XzbnMNiasn;v^>TvXlA-QMvDW=is~cRg-bGFLcjijT?mC&7uB^02Kmxj=FN8sIV(tx+ooncC2BWCk=V9gS@im$GTHI0p z0HkxFiIIFfBR6Gq$jOpYc&|9G!I5Vc~0SZZr|>T8R{6~D=6vr^IF8XrO)0KWqMx?Ec11dX)t%2 zo+vw(D_(liotgMms|8|MhMIRH;|7_b(_h0xPADI^9Q#InN3NYg?!i-rN9a_2x`GrQ zlpOY6KOeYYR3j2WF%&F$7=fIst!OLQcA#GHB&zuxQmoDQMEelMnD?VA2Pe5zRdR!o zG@mpkY@jilGCQYK*80SRQAOsqO2RUegZ6G3H0T92ua^tu63q_rsX{2g6G zbPxXK95aS+6}owD_cDnJ2O$sw1<7VA5ay*xR*Tq35e<-i)q=5ZaZ9{P!93&2YNP-> zb7VR?`wwrd)l3eck;?Wwh3a~TQNZPo9f$#~)O6|h1^Zu;!6=HlYd}C!xuQj+{grtq zkof1HdG$PZ_#B%*2NPXtZ}sOsF{9+vX8XQDe$vR|n*(8Ryv{tdJFW8(X3 zc77@2I2)a7oc7C2j}4KJlJr_Umm2-$Zqx?9PEt9`)Y9Jd$Hy}O0L4d7pbz$e8J)|L z5j%Q9rcZ?Gd=JO9zHjB83Uga?X_mp(?loB0*${eVT`iS}j+s#y^YnczWRk<65WvI8 zo7*8oXGGSc&%1S`(OiArtSQFKyAFVEJMFCP|iNn)BW;(mdH~NybjtuSva^!y9?E;=!kZR@V`W37s ze7(By?beFv;QH?+!`;@n(fSN&`f&9)Fhw;W!i=~`e0 zAh#$>e!VMn05D_$K>*nYF{wqrHb~>P$;ri;Y`O}NGN=GEaw?>JY5Vyd`J?y9+g_rN z%LHb(+@}hmm~s%7URip8!FVUB*yaZqH$F}1dOT!f79z;L?|N6>Iw9d|-$f2VXH^D) zndjAy<=il21$pjQhWtS1l?hJ0w#;7$0z)X#r9zYkkVwJB1Wa=9zcq`&@p$W^$0M%~ zRy~)aDkCpNuM8A^T>8OJ$PO7m3`qK)_;?d_OVYx>s`crb(S#3As(#$!>i*k&yW#%; z0{IUg%zkR|EU%EfbI0}9+iH9wBOUPklglNsz-?9@>FBkEbkl`%^YdkxuZK!uM;kK_ zX9M(EU7p=0J}%9Vhk=T4F(;JCE-;o^=E5ZwVJVtbm;w4yU{w&_4oU|EfP{v3756EZ zL)j1C&&gRIBYwobDf3;Pn+HcvHO}$f-WHc#k6Ges_1PGj?2UH66tt#kJX~wzPA;*W znsQo9PaJkU&g0o|wk_+tq{5pM5is^nIq_#R;!i#-`%WbD%%Fa(Nu}Z?KdDW~fZ}2k z!U+Pb0{}Q(G*ll{NaQ!|hxYH}pSf?8v|aS#FJ*j4UbBN!Dt#97-Z)zh&54aMa0Iw=08IFplYd4WfJ-qmPcll<%z&AQ z;g)1e#6VoTW^)0`{Qyqg{^;?(8q)s3Hrfw_A+2u-@$KGEgK77eGRCpqXX%6#;#npxR``n;Q!b zaW*Chm*z@`^qfFUoJs;^HePn-nD|CCvt{7r%KLNW8x74KCqFi+ose{Uk1`c0RR0KZMRvX`O?G6Y%A-HFf{nsclYTflaGn9 z*;^YEZC1mU&tJ5j*|%*U%3#g2@Zn=^Q{Z7qG~f}3HaJxyhG3wvJTq5`4PbK4x zs>-!-n+P0Mq^~Tjmz5+I5!$vHwt2C3+-k+yDj;##~q)}bBa zjZBNH)xgVNST;&nbQS1MFAS=rRg|FSa&>3QB_BZF>Ld2=`)$F}crFj^i{uZQZ?V-% z{&Di4@!Ib(bWdDNAAPZFY_*=dC)G)8Z>sU!d`sYJ!qWMBT>CvTxja3ych6 zWW$zu#sIZzjQkP0E~5CD($eoB*oic_^FmFPFwv}PNL%V#)eC~VdYN~AyevUb^P%0F>NC= zg(HktBPXlbrM{wGp)5GRPz*^bVsgw#C|L4wn^Ro*MPACH zm1iL+u2`h|sO9~osD9zyM{+o0HUojekQ@SH&Hc>fXeM){r4;&Pg3!|fK?BE%ZSs9( z{ChKo?2)^EZrxdZtW{=&qKgnoRiR$TsSNZoB>boGrYi3|Y{c^^yS)IfBazZ0AUA7f zL#kwl@*=1Fs6{l*Irk=+NWYo_8B@JyF2$F+k-~HxQGk9q;gNj1d(2490R?KPMR_SY z#q0r1SoJ?)6wkul<>j7ZcN9W=do(wp3q>nU-joTF%v^Icpqn=eUz8{rrTXQuiQ(1n znE^_Ub~i`l z`n&9OxY5I=&sCYLZ2i+s1Tr>qTlC0xOPscVd%#N?)Oe5OKwTOd7iHb{#6zB}DsyH9T-()pRd$j&K!Rv!BsI}t{`jv2lTG4t{viA-Ty zMh|tHwCwarvSck96RJ0$KK6piQlVQSeY1Ew{CDCX`MvWW`(A(df1tsX=6jfh(?#Xj z@2|+*Z@jA~$1!kio1K-H?Uo)g)yhcOgv+$mUX^!Y8YIM~I=y6wX=M_SSMcDQW*74U zo}-qZyY_C{{FSu+4UvT}kNm)F`4l|^1yORlBb@;o`YcGII^Rg(u#6Wo-t^}#wEx<=IWH`=26iv7;d;Q zzj8^NCk2N97W{$xg2+`-NKa={jJ+5s*LKT@xZb9-)lZNC#{#=^%qGii6A&q z;>*UsO~X9gCUAuD0HR?j3BnAjR+NMjC?D*wbOZ(FsG{J-?fL%zb;cg?%9`yX9-FsN@MIB2HkuGQra%YjnpK=7QA5<% zASyFNjzjK$Z~p+e{{SS@d;z8TH_UMK-W%uDhL_@uKPvM%WNLJHS-PaIwQ=>hRLO<} z_Ikg03>~c-VVRq>mQFk`otc}O_1Rr; zrHXO!an2@wMs0Exu%n8+zW2x7c-T{7;u&!<<&caiWfP2PWHl20Zs%ZrX4NhC6GoKOJb0$FBM z1erh|s$dkr5fmbV2$iy>TzV6fH-BZH3jCM*aPgnrf13QyqNjxT{@=sey4?Ma!(UJ0 zIe2>SH^|7QJ~kCHwg_6g%vr56OpasHX@?x9CI0|W+cB`Qu{Ir_XP#VaL$>VLq+v`a z5@F!t%RV&bX=Wl`8D;*J;&lOu4=jbr2slUtvy?y<$4Dicr;Q!r`ODPz6Y|WslZimXGfTou zhD1q3)0w40PXG~`Y(p|iW}_o0xzvgi>m0ZL07{l0j_SUB#KqK%1m0UggO}v%iQTBV z`IynJE7brUKA`rhFLOoZdc5SnOr0*|o@{-G0&*r2%y9&qK_)nIVo!&LO#%H9NiPsy zBm|N{gUAe|jFY-q83$HJ&z7%#1BPJ47pJWXt1Rfi7Ajbg>w>Z^0b(KJ}Dc^6|5yGh8g39EOBt>jkx#em6g= z%J~l`$$VP}Ic2iL$zS4Is0_>;JJ)0}-8;_K$;x4ih)=(>P)Xq#52yV?X;Hv$Z&)<5 z0$8~8?mS};pW@#T{{ZjLG5-MN&42#@f9erFIkWU1XsoAU^1qX7W1#XK{w6Pq&RF=J zb~yOj?EJhs^5%}%a25d;J@y%v6qv_chP_>pSM@1EpUNJO>fl};dWuu8Up=BU^6!yw zd_Ny2UEuQ7Zt`+dIAv^K8LK>bWWf{ZFx^#Js)57zFaFk`m}2w<$5VIV3HFHFe3Q)oknI0#A+(<5s3j$mtE)$`no*YTpAg)7lp?_q_IPXwRsH9eV?c_Fz` z_Uyi+umUjU86&2JSDt#)J{N5Mxq+gf<_nD&HiZz}DVhrku-UAZfp$1p{{@*RhzOvf#mmW?!{5c zRpM0B`6lnjFKg&`dtD|?=4Wi3UdJC*v5rayjUt)izhQCYENcml(8E5 z+u32}bSmio07R~9Q=K9Ejq_*9_;Cw|@^8o(AqnkO$t zdp}o1TB5VVcJ;(DJ3+WBtxJH>Q)4U?$xtgQUJ zjV`Yyrj)iuF1?&T0SPoxLXtp7B%o>lB4S2yYFug)N&*gA!|!4Hu>Ha8{7d`^ zu=#(@@_bRS+YZ`_ZR%y4hVyB@E`DUzdHWoDb5m?RhF)GhOC_r!@SR0tvf%OYoGA?9 z#QYMOf&v$b6%#5rQV9k413PthzaY#t(jy-Z`76zSIr1!DGw`1z+j#c-8j2G?S(qU8 zhqg7QK2+VQhqA-HAvepw*+VB^mFi!q!lEk89&8zA2+7EK(i&+3DC=DNwa$$apUB_4 zFWfK7SPP-~7v(=D@(m)aQ0ktV?M{~dicYxn%GY@I9%3?@ruY;x$i`FK#o*=*#Q8!NI)X_cEZ*&RC4Nng>1&nFqg;E+yb5}Eiy z@PbGSaITGI(p(0L)zh|qEKc4r{*cezSBQ9oygNtoUS#m`aN7$DYqQsCx1L1|RmV7( z&n?E^A7f01jHf4svl_NaOcrllEsuKh?v00NpXOt1*f^65%`H_=>XZ-*%wAzKpe0PA z7Yx*;{k>_HjImR^pUb?P&HRgL;`)#&W^0qnIH>0~+<G=+z_WY;Yfx;=ECBGJaKOmsi!3wh)`*fsic-%kKB$$GgP({cZb7m!B6+)G_IHq4A zX07(wuQ^>ylQ68xmjDt~hhV*0iWQ3z0)o7Nj#1&!<|#cSDp2#TlSXc_$TCBFP)i+UIg-|kuK;(A0%Xh>z`YIoI#dhQRaN3eRF>+ zh$@4x+ufAHU+!||MluihZJd9TQS zFgy6R<5jNl%u$amZdQ6>r_(k)@*`V9)W{;nG#x7$rEyfL;+H9BcCm#Z7ore?xo6ap z{LB(*jY(ZPTPH=+Sc*K`XQBAkzDeU)9yhAr<;xdiiu;U9O>iS>sxmazO)DZnuVv#9 z{W&O=E8LJbwh#pJ%s{fPYoiWfDzQTvawV8nw=Y_iAHDA!z0|W;%(hf2QABqYW7u$q z=q^)$KIA1>AGZaH{y+l9_22H|vM4GM52;^ZxvIQzyKx5)=An8@gUsW9j-*#_ill~N zXLRbPs8wY@Vp^PV#epnUlkTh7@*AYxm+5r-pDvss*c+q^cLx|(6 z39l-l!h})-so*(3W@5}{#2yOt?{*-5ZV)vFP-~bR-5b&|@fC@oer4B2%CC`& zx8Vh2pw_|Lrx@s^JJo+KKQ5y0{Opw)^%ZObBI_Hq%K7`Irf6%W`1n>&Db>^aCKvis!_p1mV^WX7JwZ*L}G%|Q+UL&rUo zc4Xz0@xq0A1KE)X1DK&5SJT%g&!j?JE{;85yIoiP{{R&LC?T4N%4kcgr&jzxn$1}7 z>I()as?YjiJxL)I0#})G- zJ(hL_mB=9qGM7LnPQ^eSoDYotCqMF*&=F4=?4wn<_l5upMO^tkg5pg(DzbaHL%}lv zK;bA^hX=H}f>EXsP%5IL&(8Oh#H~?Cy#Qr&Kxvd;zyZGb1r+^_+A7b2k@VhmX9I;A z*6d4lMGdv?RroUNMsxwFQ=Wm9oE}FtjXM)pYcQwNo89968M2`iutJ{4@fH{MowCqk zd2f#E;PAlnZI7N)4;y)Udoi*6cW2{UTpZ){#)PXy?~v(fNV}TMH9!FeIlc;Pvk(HK zlu{nGbPfY$c-`HmZ<+vc#Nic@Pjm^_IO+cYjrcdm``?ha9ygig4XD-$!!FzHwo0RK z^9D`E18YrXj`C|{Qym>LGE=6%u3*THZE>)Vo|>M1QabruhFv`R^@T4Z&hlRa#=nE< z7fIzlHGyqxGk4h<{F{9ZHahq|F-ltoXpFPFoLsCod?#899I0h?q=PVWYW^yH*EcY5 zEIjQFe-7}C&&c_CwAx_lF|c%cOHTQ?dDxTFS0f^;R%_n)nLVqM)`KkwMQ8;_$dR~b ziMn*kAB3enoAR^>Tsktp{{X?h8UFw$bU*w!-}ir@aLxWx-e32kD04oaXo3Dt^SJ!u zs)momd|x9&u}q9Stf#G2Z3LH9!-~EYBPJO&Gi!&j>iDx9SdPhe1O`?Hi*yA&TbmsP zGOPX*PiU_{AnWx01>&1O4Bp^APn`OixfjLB$G$WHjSD75ekM*#X#LBrpq9vsQ@sVK zW{pj~mOy8dgt~-a$*CYZuDY0^j#5dAX4x`Ssk*w}IjN~mJd4O385Cpdc5cP_Jy^`C z=%tAyiWOc8@!3ISP+Rrh?&(ChU@<=2xuevm=Ny*UR~BL{p$T%tu41eiLaP`*@!GK3 z;jJc0l;v5-EZv9VofMuCiQo^i^U3aiuj{AXQiF`7k>J*iQkM%D+fVe9kktsdH5WAs z6s2PC=xAY&ju`_NQNUiMfnKFl1IS*bl<+|gRI;}`AJ=)Ywl)|_O(kv?t5D-bDgY@M z)2+qWxd@lb#Jb)802om(9>K>HEDk)X?xZWS(Ft&k+5Z49?H~i(DRu=}10Sxw5t%nm z&Hn&kXi$n9l|H~kcuJyuyNcG&4k_gZLZ6EQQBk>5$S%`79#2vis4Toh z;IB_yx-1egzLfCis&*%6t`FCh=) zLt2o3NagwR@cdHdJHm?H6djbf1MG{zu@4@Ljv7ns zc1d*!$z=`D63ze%+Kya~4M0+^IA%fmJhJe$@6XTLu%SOPlM|vVa^9SlNEJm60P3L6 zmK-tY11fNUlnlPm4tDlDJHKc-VV=MhPJoX{V=xsze-CJ+K4*V+<1`Q)@{Bm7(b@e0 z53S~F*OA#7Tfa6}qZS}K{8jNB!hPel5`+80qPiwzg(TL3!Vn;ZQLdf@BSH*Tf0TV` zk3UqAI!?iW9ZRaCg$zUW1P343iO4F$)&h0l_k^I5jEYD>njVB-BNy;&x%#KxD*ph5 zjx@e&O1p|zCrqV!s5~m}xnjXvr#z@E0=o`oKOeZ}_M9?>F7^XHc0X9Xh8&)rA1e=k z+lI3vBjr6#@{cSmEj#4anU}@Lz97lSo8w9?41dn=Nm_*=GNGAAwUrA4$8X$j3AWbS7ow{lVOc4g^gn- zQH_-giM6wCsgHM(wV7cxSeJvoNs3p7Eq}d0VWR8Sk<-aCzDd?2$YuFgM83A3LgyTA5m63V=hdiJ-R8+HC$?eW8N>l{( z`uXVqXuNO8zDU(WUE?}jc*#DeN#xhS-Mrztve{CTFfNkKIXU>rl*QX46C4(%J(f7w z@i2plfdRs{kEI=GMfyaa{-wTH{{Wc375@OOYCrs;{U~G~=Rf;GAP}>3bvfc>_S^2c7e2Qi=wG4=Q^4v3x zor+`gWfT-B1q0ssqE}d>w4c*6BB@VQbT?}vy$o|lQUh*0vaX}3!P)ZYs(W_0L{N%P z*p>tA6<34!tDpzH*hvxss?=8LWD1RNY$KA|myU6od(`wjp~v8n5!-7NIRokBSr0ss z>z~<3JpK6`_TaZ9DF6}r>i0yz;!(j=P^dYSa-mqVUNUxo#c7dSD_Y}0)-Fbk%Puf( zvn^S$ABwFQpIcM99so4wk=VBihJ5fLcYkSne>jSrE=`>XhYgS&Q2J)44K!61}Ol|)j+tzFucg2{n2)##aa`ef!LJg@XheAhSYhvvQ& zlRsH@dYsrZGJ;-GkeDvcioAAdD^dF^5A9ukZ7G^k)9*X2qf7o;ztN0qUMl zuU0H(890R*D#-O7p0Nt)P%LFgvt$zw;tuLR)iQp?%J+NQBsO`CLCY_{F7~2!SNb^r z0Cdp`((v7OYn8cVkCAD}R)edsXMCib%d;6EYE=uPN`Xv?k!AS%bWEe>A?S4!%?dH0 zygAusXSgFum8e@K6=)%ay#1H{Y2eCnZDKjKHiScoOx8#0;$Fcq`RkA6=ZOZM;cvJ%blcbFp! zq>U%0$h^-e=_(A2RIFKYR3XJ)%}X$!2iElcS(6O2JC!Uo8s&GSUc>n7;jBVadTge^ z&4>W2DzIGCg5+6AC&IrktGPcI`Mb$7BPv=xIkqwOWVDtj_Do(T_NGz+)SxP-yDBIR zYFr`$-&*v`^H{eh#(xgXiM=*3Yz{Pn=|WPd5aujN1u5~T$!KV_@GmV;#7b0e9omCp zAf;Ar@KXJzMHiyggpE1uJ2Fp$TYCMxXs|vj+%qQ*^Vti46B`ZveG8k2N!WNFoJM&@ zo*m@If|)oTzu3QzgX>4^?N# zUMDZaJDVC|E*|jNCz#MnjUkw;1_y}LFb0Wn9P{$Ywl3#kkC3lBLiERcTcCi%X?Fn~ zYsXXe1w%h+5kAVG)xTd9{6mIh-djwCf9I#kl-KY6EYV|c24OU!U|5v|AqdD;iK~me zRxg!aY=PE*@sE^GWIu?SfL!$pGsAyF*N@2ZoVRx1H;T)MWm8mSpf%NkFefq!2-3nd zQixK+kn2p(Sgk%Hp!oj)l-u$qn@n_80lN=gK2LB#a1VcR9T9qfNp){pbiv1+LTO`k z=6Rh0#)b^MMJT6RavGxeZa3{)GMWDX9&LOZ8^!SVdHzf07QFK^J#@~BM;jIxxSIL; z+SuqaCwki^Rfww@u1ifL2_H82gB1MU9w2lX0GS}oD4?T|M`62gz{3R`fka?gD&+?i z${s>G1FQJ|0OgA2SHbnrzwu8I)nK;jm|jnqxX(Q0$F2-+ey85h8~_AA>h9m8B* zC6I&l%^9Ty4N+F9^~<=8i9!G?58oWzd}pTc{kM0hgDoOw3it zmSs~b9{AJA!x&iE3jvRllDW=AtZya%0F|F0{{ZuDcmDuy>MC{Yf82>eFUVWnAAw_Q z^!^F4(fK0VHu<^vdVPD}EK1EgInUW+@_nslSz}``Esuhd5rtBY@iq}c33AyBgGQ

5%2T4Dz|IU;f>liup3Kanl0p2s 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 Z{{Z>S*8c$2^MCJuM&kbfW^T}<|Jf0-Ls0+# literal 0 HcmV?d00001 diff --git a/apps/stopwatch/B.jpg b/apps/stopwatch/B.jpg new file mode 100644 index 0000000000000000000000000000000000000000..639ff5d42b66c0d14fe451e0834cf174fb626050 GIT binary patch literal 69988 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 literal 0 HcmV?d00001 diff --git a/apps/stopwatch/README.md b/apps/stopwatch/README.md index 1924cc343..30a9306d1 100644 --- a/apps/stopwatch/README.md +++ b/apps/stopwatch/README.md @@ -1,20 +1,33 @@ -# Stopwatch +# Stopwatch Touch A touch screen based stop watch for Bangle 2 ## Screenshots +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) + ## Features -* Feature A -* Feature B +* Attractive UI design +* Will run up to 99 hours +* Shows 10th of seconds up to 1 hour +* Start / Pause button +* Reset button ## Future features I'm keen to complete this project with * Ability to dismiss the app and leave it running in the background -* A small widget to show the elapsed time on the curren active clock +* A small widget to show the elapsed time on the current active clock * Laptimes, with a way to view all the laptimes on a scrollable screen +## One of these is a genuine Bangle Js 2 Open Source Smartwatch, the other isn't + +Which one is which ? + +![](A.jpg) +![](B.jpg) diff --git a/apps/stopwatch/screenshot1.png b/apps/stopwatch/screenshot1.png new file mode 100644 index 0000000000000000000000000000000000000000..6d94ce05c0f472e51b65eeba67e124de4b7f239a GIT binary patch literal 1783 zcmeAS@N?(olHy`uVBq!ia0vp^8$g(Y4M?uv{v-}aF%}28J29*~C-ahlfo-Fwi(^Pd z+}qn1gQUHATmygn?|&x7sI{-}ZJ_t%o{+7dW`&)9S-WjE=Y;Ruw{Pz^uNRQ;dNB8Z z^K9YY{ESS(i6)Fp{p1~!ihxHwMs{eCbxGP!q{{F+Bi9GAC zwDJ$YVMPp+Sz`h7R3{$)<=R|kq2{ybIIAV z#{M|Yd9|K>_im)WlW*Misd4vReJkpk0+Pgj%D+cI=n`X&`I%TKcU>|F9=!+TaBSLuVICewX|Pq#Db6J6?Nl`gXZd8>VX z&$;&p`uYAozpc#w?|2d0g;W0oC+s$Ub8r7)NoC1J*?zUHKWbihF-q<%doRzp-TLsm zzt(dMCn;`v7S7VgvNLw_PkmSI^A+3bL*}H*?=d#A+07F6t?rv>&eNuwwa2c0wu!Fa zb>J$~ruWr>3ajP&L~ouy@nN&O>jKU6o=;U<`wp0wEHdA~|8is1#O@ar(;tgQvz)u5 zG?RJ#ouG+-!&%CX9WQdVkk6Kk-x&J9<7B$bkEtiLf2RY*^_jHiJg$;k|3|d3KRKe& zp^u@l_>PaMqQe6<*N%zG4i99^gMB#!c6bUVJ>d|rIDR%nn29AXs71n(iRE0O@zoZF z#@Ql@Zll@=1<421569i}Ziz673oCp$%34v=%g~tp$0y#W;efdKgO8?6EbBfR*BUZ1 z#m6)pzYYxXW9PpdW#OnPWR!~sMuU|7WlW7%neN@c|3_mVm!ZA?1DoCJPx?33|NAj_ zlGfR$l}!4|yQO!C&NvY|>-1-qI5#l?Aam8jN~ZQe+sGd;*!IQ0y1K~g=S!Q$)fvi6 z^>)oiLND<@=ZrZjC3+*r*%O0mWPOmSjtv28A{c-T>4_tvq zBSmlgE?mjTH2uTfzY5B`r(f^X*=f$S{9-MCSboB%X;KOWobeCY)bghOnP15iZn*B= zfg@{${@(}MX0ez_c)QoF??4N9YUF3^iM~+XYQg+^we*Y~=>l~-+rvAwlJ@&Wsz%IG zFW{UXpRxYY>T|+7d@j^$u2NcaMtFzQ`8Q92;Zn)U6aow-YViKBKlrQl{6c1w2Cyb$ N@O1TaS?83{1OV%-^rrv- literal 0 HcmV?d00001 diff --git a/apps/stopwatch/screenshot2.png b/apps/stopwatch/screenshot2.png new file mode 100644 index 0000000000000000000000000000000000000000..0baa733313973f63c117f606e7eaedf43cd46a21 GIT binary patch literal 1765 zcmd^=X;70_6o&6dgeXE1)KG{(3e=WGL7->^1Z9z6M@*3J#(<0EPi%-ykH`e_G4+kX_t39Ek)dCCJ|!h#r}Z0ieeV@ZBDq9yxijn|pDi zfp4ca_ekd1ig?MlFqwPu?m+e_T;+u#C(p@*P_Cw(h&k`ZL`6RxE?znU*wC$h2uM$3 zXMh1pfdTcYSsZkt6YIfDU#83dEZehaeNZT5}LRv4F$E8>1%y2)|p94o*OE zXwd8Cc^kFSAjBQOiWy4__CEN(hb-5dUdquv7Cf6w(v-;tm++-{g6AAXy>zAe$qTx+ z&{Q&;Rh|^e)ppvo=C2|bb+>8kX~7Iirfr#p>xP@6sb60UTX`NoII>IglvdVy@6=+G zULT+8QLci+3*YLTX{WAP&}Na3f^yTvbs?rV({g`rjU)gWBb^E6!DykR`&(9^;1hvc?UDeI0mc>L?C04N} zUXoUxnD4W0wr(htm6ScflU&HdHB!W)X^>!*W0UFP+IH~4q5gqUx(E)P?U|6fjjVKQ z5zZ-!C!<#QNtK(gr$y!ADe_xAZs3tsFDqgbkK3Zd>}rcAkEb#sDDQ1YUUXsL#BMvt z#&x{p+~Pis;F&v~GMIc2S3Xzmw-&4gwR#_R70aNh*>@+;H07q-SlYPu*w^b%*Qn!* zUHHb`7f8oLw%{_diI>jU!2;D(0$$#Q-^viTH5*A?YvI)imV^5y3}?FY6`GG;iEE{} zs}1Cc0EvIb1!WR#PyE}ZIz^+@u@=6yMp7?OUP;R`dyyZjN@=^R+*l@9F4Vr}vGPwGG8}WbMHkBhh-!g~e$V4cR_52P0 zycwI%nQ8U`)2R|(Ofsmjx-)@+VCBP{!cHi?&_wLM3`A|#*nW*x*~YZ0asO6C?R#NC z6Mc--k{MHtLQT|lETHRRVQS!sMZo++bSVailAYyfwhs$pmPSysLDPeP_b`>V13A83 z*A^zleNJ^i6pK0sshq#*^9~L*9YeuJjlJH%H>e6UyQX$OcJIi30TvXziNWrTrBjK( zA?55=W@%}OCooGYLR5Gx0>8Htf&b<0fT+KLDEeoh!2A&eEF4xX^D_e~PPiO13IO(t z700YF$aOX_3Q_>OplI24BQzil88>un0dR-kv0pf-Pagd*k(!rQ+bOppaJtEFl8FJ+ s%#tr{h5+y%xXdD-62a*rtgvw=`Kq>SQ6eX^0V-T%NU4ixs&ohqguEj-nDrQBM?I zo=P!uH+8agafzvPT*~EGgfz*lWM*}Cp6C1%=Q%%oKJOns&*$=dp6B)XoC%^*R-)FT z5D3IdUmtR?MyviY=qHUAG`^>6M2j6v@kCUwbq zX_hEkVBz7U>sz(JcoWkuOZ!!I>raUJW4Dfht#BWR7N+pFlma< zt$QpW`Z{!1kwwUzES6gb$MBe2%A)rQn|XC^ zZyT82cq89AYT}AN-zIqVkfh#c$|GD9fzQIvlsYsV;i_M1qn>ZuYWak@<1y`}AWl^_ zBB2-5*APB$b917Jgbzj4?*JIKcm;brrEi%i{31Xn_$UB|Ay++doHvMaXpe6E-7xasIl z?LDO-vYtO<>*52I-QRD9wR>pBLNCDXEx7BRQq~KW3x`x7XPsw7g&(Lp?L4d9^wuo8 z)Kq!CiWAU=&wam>5dw!g^ghVfqh5>T8%>#Weu6P+{g_bydbi@29A0FryJCb6Q&b^@GQ|E@VY8R)${C zEfI4QRQL6q97P|d;@_g+T36nIu1UP;?bFxJvj-1c>B?yjbk@ztwexK24*D&# z!jkg9={Vi)lFehC0yMWDs^4JMV|b?Ku8OUe3_ukT*ZT`0mt-t))FxH%h)+}y2}nSM zr3t6}CTw=;(8#oBXB&>L3yn^_mfC^7=J^vsFWb7#oXSaudC z#2E8d%{{0}aoMs4<1Wock7?+CvmIY_4l+w5XD4Y`k2BHj^8R16gUiK}wJCZ0VrO-r z@GJgmF-1Ch6fM-Z2m0rR|5b14;pQPY7Rd(ID7>Zn#k_2yjs$me_2*XH&7w_&MB85~ z(JefT(7-+{J6IfL?k7(^W!?JFJkdcDs6T-bUJ{IC2|JCxdhI(hh~&9euTLMUsdk1< zwH`iR+dk|k4L_EkO6_QgU{DyoXKZ!wxY>CiXGbZMP}qnTR<}bN4>{3${G_KXEN6wq zuP@r_G*3&W`rl|B;=;GMa?6-X^b=>C*71@jQA|*ObLoadTwoi<5RRE%U7|LG13tGw z8-1KY=GZ{-a2_VDmHI9m)He$#Fq;Kk#=A@P-b{O$07QLi&r*&!dh{;~4=IvVNEuun zXA^G(<9ug|mEW@5sggm|G+3=zV+dU}H|w$4$J~#;edAQUm&BF8^E#3WFSm2KC-jiN`j1%tbxyIDe_c>zyxad z*~=M20s}$+i07V!c_Rh;RM~A!PUmIaq+VW=c0YCkw3|35ZaJYuhoKhS Date: Wed, 20 Oct 2021 22:55:33 +0100 Subject: [PATCH 0295/1062] fixed merge issue with apps.json --- apps.json | 408 ++---------------------------------------------------- 1 file changed, 15 insertions(+), 393 deletions(-) diff --git a/apps.json b/apps.json index 9a53a821e..3a19ba230 100644 --- a/apps.json +++ b/apps.json @@ -742,8 +742,7 @@ "id": "widbatpc", "name": "Battery Level Widget (with percentage)", "shortName": "Battery Widget", - "icon": "widget.png", - "version":"0.14", + "version": "0.14", "description": "Show the current battery level and charging status in the top right of the clock, with charge percentage", "icon": "widget.png", "type": "widget", @@ -2946,396 +2945,6 @@ "supports": ["BANGLEJS"], "readme": "README.md", "storage": [ -<<<<<<< HEAD - {"name":"banglebridge.wid.js","url":"widget.js"}, - {"name":"banglebridge.watch.img","url":"watch.img"}, - {"name":"banglebridge.heart.img","url":"heart.img"} - ] - }, -{ "id": "qmsched", - "name": "Quiet Mode Schedule and Widget", - "shortName":"Quiet Mode", - "icon": "app.png", - "version":"0.02", - "description": "Automatically turn Quiet Mode on or off at set times", - "readme": "README.md", - "tags": "tool,widget", - "storage": [ - {"name":"qmsched","url":"lib.js"}, - {"name":"qmsched.app.js","url":"app.js"}, - {"name":"qmsched.boot.js","url":"boot.js"}, - {"name":"qmsched.img","url":"icon.js","evaluate":true}, - {"name":"qmsched.wid.js","url":"widget.js"} - ], - "data": [ - {"name":"qmsched.json"} - ] -}, -{ - "id": "hourstrike", - "name": "Hour Strike", - "shortName": "Hour Strike", - "icon": "app-icon.png", - "version": "0.08", - "description": "Strike the clock on the hour. A great tool to remind you an hour has passed!", - "tags": "tool,alarm", - "readme": "README.md", - "storage": [ - {"name":"hourstrike.app.js","url":"app.js"}, - {"name":"hourstrike.boot.js","url":"boot.js"}, - {"name":"hourstrike.img","url":"app-icon.js","evaluate":true}, - {"name":"hourstrike.json","url":"hourstrike.json"} - ] -}, -{ "id": "whereworld", - "name": "Where in the World?", - "shortName" : "Where World", - "icon": "app.png", - "version": "0.01", - "description": "Shows your current location on the world map", - "tags": "gps", - "storage": [ - {"name":"whereworld.app.js","url":"app.js"}, - {"name":"whereworld.img","url":"app-icon.js","evaluate":true}, - {"name":"whereworld.worldmap","url":"worldmap"} - ] -}, -{ - "id": "omnitrix", - "name":"Omnitrix", - "icon":"omnitrix.png", - "version": "0.01", - "readme": "README.md", - "description": "An Omnitrix Showpiece", - "tags": "game", - "storage": [ - {"name":"omnitrix.app.js","url":"omnitrix.app.js"}, - {"name":"omnitrix.img","url":"omnitrix.icon.js","evaluate":true} - ] -}, -{ "id": "batclock", - "name": "Bat Clock", - "shortName":"Bat Clock", - "icon": "bat-clock.png", - "version":"0.02", - "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.", - "tags": "clock", - "type": "clock", - "readme": "README.md", - "storage": [ - {"name":"batclock.app.js","url":"bat-clock.app.js"}, - {"name":"batclock.img","url":"bat-clock.icon.js","evaluate":true} - ] -}, -{ "id":"doztime", - "name":"Dozenal Time", - "shortName":"Dozenal Time", - "icon":"app.png", - "version":"0.04", - "description":"A dozenal Holocene calendar and dozenal diurnal clock", - "tags":"clock", - "type":"clock", - "allow_emulator":true, - "readme": "README.md", - "storage": [ - {"name":"doztime.app.js","url":"app.js"}, - {"name":"doztime.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id":"gbtwist", - "name":"Gadgetbridge Twist Control", - "shortName":"Twist Control", - "icon":"app.png", - "version":"0.01", - "description":"Shake your wrist to control your music app via Gadgetbridge", - "tags":"tools,bluetooth,gadgetbridge,music", - "type":"app", - "allow_emulator":false, - "readme": "README.md", - "storage": [ - {"name":"gbtwist.app.js","url":"app.js"}, - {"name":"gbtwist.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "thermom", - "name": "Thermometer", - "icon": "app.png", - "version":"0.02", - "description": "Displays the current temperature, updated every 20 seconds", - "tags": "tool", - "allow_emulator":true, - "storage": [ - {"name":"thermom.app.js","url":"app.js"}, - {"name":"thermom.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "mysticdock", - "name": "Mystic Dock", - "icon": "mystic-dock.png", - "version":"1.00", - "description": "A retro-inspired dockface that displays the current time and battery charge while plugged in, and which features an interactive mode that shows the time, date, and a rotating data display line.", - "tags": "dock", - "type":"dock", - "readme": "README.md", - "storage": [ - {"name":"mysticdock.app.js","url":"mystic-dock-app.js"}, - {"name":"mysticdock.boot.js","url":"mystic-dock-boot.js"}, - {"name":"mysticdock.settings.js","url":"mystic-dock-settings.js"}, - {"name":"mysticdock.img","url":"mystic-dock-icon.js","evaluate":true} - ] -}, -{ "id": "mysticclock", - "name": "Mystic Clock", - "icon": "mystic-clock.png", - "version":"1.01", - "description": "A retro-inspired watchface featuring time, date, and an interactive data display line.", - "tags": "clock", - "type":"clock", - "readme": "README.md", - "allow_emulator":true, - "storage": [ - {"name":"mysticclock.app.js","url":"mystic-clock-app.js"}, - {"name":"mysticclock.settings.js","url":"mystic-clock-settings.js"}, - {"name":"mysticclock.img","url":"mystic-clock-icon.js","evaluate":true} - ] -}, -{ "id": "hcclock", - "name": "Hi-Contrast Clock", - "icon": "hcclock-icon.png", - "version":"0.01", - "description": "Hi-Contrast Clock : A simple yet very bold clock that aims to be readable in high luninosity environments. Uses big 10x5 pixel digits. Use BTN 1 to switch background and foreground colors.", - "tags": "clock", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"hcclock.app.js","url":"hcclock.app.js"}, - {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true} - ] -}, -{ "id": "thermomF", - "name": "Fahrenheit Temp", - "icon": "thermf.png", - "version":"0.01", - "description": "A modification of the Thermometer App to display temprature in Fahrenheit", - "tags": "tool", - "storage": [ - {"name":"thermomF.app.js","url":"app.js"}, - {"name":"thermomF.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "nixie", - "name": "Nixie Clock", - "shortName":"Nixie", - "icon": "nixie.png", - "version":"0.01", - "description": "A nixie tube clock for both Bangle 1 and 2.", - "tags": "clock", - "type":"clock", - "readme": "README.md", - "storage": [ - {"name":"nixie.app.js","url":"app.js"}, - {"name":"nixie.img","url":"app-icon.js","evaluate":true}, - {"name":"m_vatch.js","url":"m_vatch.js"} - ] -}, -{ "id": "carcrazy", - "name": "Car Crazy", - "shortName":"Car Crazy", - "icon": "carcrash.png", - "version":"0.03", - "description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.", - "tags": "game", - "readme": "README.md", - "storage": [ - {"name":"carcrazy.app.js","url":"app.js"}, - {"name":"carcrazy.img","url":"app-icon.js","evaluate":true}, - {"name":"carcrazy.settings.js","url":"settings.js"} - ], - "data": [ - {"name":"app.json"} - ] -}, -{ "id": "shortcuts", - "name": "Shortcuts", - "shortName":"Shortcuts", - "icon": "app.png", - "version":"0.01", - "description": "Quickly load your favourite apps from (almost) any watch face.", - "tags": "tool", - "type": "bootloader", - "readme": "README.md", - "storage": [ - {"name":"shortcuts.boot.js","url":"boot.js"}, - {"name":"shortcuts.settings.js","url":"settings.js"} - ], - "data": [ - {"name":"shortcuts.json"} - ] -}, -{ "id": "vectorclock", - "name": "Vector Clock", - "icon": "app.png", - "version": "0.02", - "description": "A digital clock that uses the built-in vector font.", - "tags": "clock", - "type": "clock", - "allow_emulator": true, - "storage": [ - {"name":"vectorclock.app.js","url":"app.js"}, - {"name":"vectorclock.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "fd6fdetect", - "name": "fd6fdetect", - "shortName":"fd6fdetect", - "icon": "app.png", - "version":"0.1", - "description": "Allows you to see 0xFD6F beacons near you.", - "tags": "tool", - "storage": [ - {"name":"fd6fdetect.app.js","url":"app.js"}, - {"name":"fd6fdetect.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "choozi", - "name": "Choozi", - "icon": "app.png", - "version":"0.01", - "description": "Choose people or things at random using Bangle.js.", - "tags": "tool", - "readme": "README.md", - "allow_emulator":true, - "storage": [ - {"name":"choozi.app.js","url":"app.js"}, - {"name":"choozi.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "widclkbttm", - "name": "Digital clock (Bottom) widget", - "shortName":"Digital clock Bottom Widget", - "icon": "widclkbttm.png", - "version":"0.03", - "description": "Displays time in the bottom area.", - "readme": "README.md", - "tags": "widget", - "type": "widget", - "storage": [ - {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} - ] -}, -{ "id": "pastel", - "name": "Pastel Clock", - "shortName": "Pastel", - "icon": "pastel.png", - "version":"0.05", - "description": "A Configurable clock with custom fonts and background", - "tags": "clock,b2", - "type":"clock", - "readme": "README.md", - "storage": [ - {"name":"pastel.app.js","url":"pastel.app.js"}, - {"name":"pastel.img","url":"pastel.icon.js","evaluate":true}, - {"name":"pastel.settings.js","url":"pastel.settings.js"} - ], - "data": [ - {"name":"pastel.json"} - ] -}, -{ "id": "antonclk", - "name": "Anton Clock", - "icon": "app.png", - "version":"0.02", - "description": "A simple clock using the bold Anton font.", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"antonclk.app.js","url":"app.js"}, - {"name":"antonclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "waveclk", - "name": "Wave Clock", - "icon": "app.png", - "version":"0.02", - "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"waveclk.app.js","url":"app.js"}, - {"name":"waveclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "floralclk", - "name": "Floral Clock", - "icon": "app.png", - "version":"0.01", - "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"floralclk.app.js","url":"app.js"}, - {"name":"floralclk.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "score", - "name": "Score Tracker", - "icon": "score.app.png", - "version":"0.01", - "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.", - "readme": "README.md", - "tags": "b2", - "type": "app", - "storage": [ - {"name":"score.app.js","url":"score.app.js"}, - {"name":"score.settings.js","url":"score.settings.js"}, - {"name":"score.presets.json","url":"score.presets.json"}, - {"name":"score.img","url":"score.app-icon.js","evaluate":true} - ], - "data": [ - {"name":"score.json"} - ] -}, -{ "id": "menusmall", - "name": "Small Menus", - "icon": "app.png", - "version":"0.01", - "description": "Replace Bangle.js 2's menus with a version that contains smaller text", - "tags": "b2,bno1,system", - "type": "boot", - "storage": [ - {"name":"menusmall.boot.js","url":"boot.js"} - ] -}, -{ "id": "ffcniftya", - "name": "Nifty-A Clock", - "icon": "app.png", - "version":"0.01", - "description": "A nifty clock with time and date", - "tags":"clock,b2", - "type":"clock", - "allow_emulator":true, - "storage": [ - {"name":"ffcniftya.app.js","url":"app.js"}, - {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} - ] -}, -{ "id": "stopwatch", - "name": "Stopwatch Touch", - "shortName":"Stopwatch Touch", - "icon": "stopwatch.png", - "version":"0.01", - "description": "A touch controlled stopwatch for Bangle 2", - "readme": "README.md", - "tags": "tool, b2", - "storage": [ - {"name":"stopwatch.app.js","url":"stopwatch.app.js"}, - {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} - ] -} -======= {"name":"gpstimeserver.wid.js","url":"widget.js"} ] }, @@ -4352,6 +3961,19 @@ {"name":"ffcniftya.app.js","url":"app.js"}, {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} ] + }, + { + "id": "stopwatch", + "name": "Stopwatch Touch", + "version": "0.01", + "description": "A touch based stop watch for Bangle JS 2", + "icon": "stopwatch.png", + "tags": "tools,app,b2", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"stopwatch.app.js","url":"stopwatch.app.js"}, + {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} + ] } ->>>>>>> upstream/master ] From 96685e531d915a4321b672535f2f1ba3d47f8f56 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 08:29:07 +0100 Subject: [PATCH 0296/1062] Rename launchers --- apps.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps.json b/apps.json index 7afcd9cb5..e098d466d 100644 --- a/apps.json +++ b/apps.json @@ -79,10 +79,10 @@ }, { "id": "launch", - "name": "Launcher (Default)", + "name": "Launcher (Bangle.js 1 default)", "shortName": "Launcher", "version": "0.07", - "description": "This is needed by Bangle.js to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", + "description": "This is needed by Bangle.js 1.0 to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.", "icon": "app.png", "type": "launch", "tags": "tool,system,launcher", @@ -94,10 +94,10 @@ }, { "id": "launchb2", - "name": "Launcher (Bangle.js 2)", + "name": "Launcher (Bangle.js 2 default)", "shortName": "Launcher", "version": "0.03", - "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications. It will not work on Bangle.js 1.0.", + "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications.", "icon": "app.png", "type": "launch", "tags": "tool,system,launcher", From d2d1f5b8cdc3e857323b3a37f2e1d51cb9813af0 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 09:13:17 +0100 Subject: [PATCH 0297/1062] General clock update - fix svclock after last commit, ensure other clocks update on the minute (not 1-15 seconds later), add Bangle.js 2 and theme compatibility --- apps.json | 14 ++--- apps/berlinc/ChangeLog | 3 + apps/berlinc/berlin-clock.js | 66 ++++++++++++---------- apps/sclock/ChangeLog | 1 + apps/sclock/clock-simple.js | 33 ++++++++--- apps/svclock/ChangeLog | 3 +- apps/svclock/vclock-simple.js | 45 +++++++++------ apps/worldclock/ChangeLog | 2 + apps/worldclock/app.js | 101 +++++++++++++++++++--------------- 9 files changed, 160 insertions(+), 108 deletions(-) diff --git a/apps.json b/apps.json index e098d466d..d0fe261e4 100644 --- a/apps.json +++ b/apps.json @@ -1026,7 +1026,7 @@ { "id": "sclock", "name": "Simple Clock", - "version": "0.06", + "version": "0.07", "description": "A Simple Digital Clock", "icon": "clock-simple.png", "type": "clock", @@ -1071,12 +1071,12 @@ { "id": "svclock", "name": "Simple V-Clock", - "version": "0.03", + "version": "0.04", "description": "Modification of Simple Clock 0.04 to use Vectorfont", "icon": "vclock-simple.png", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ {"name":"svclock.app.js","url":"vclock-simple.js"}, @@ -1376,12 +1376,12 @@ { "id": "berlinc", "name": "Berlin Clock", - "version": "0.04", + "version": "0.05", "description": "Berlin Clock (see https://en.wikipedia.org/wiki/Mengenlehreuhr)", "icon": "berlin-clock.png", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ {"name":"berlinc.app.js","url":"berlin-clock.js"}, @@ -2804,12 +2804,12 @@ "id": "worldclock", "name": "World Clock - 4 time zones", "shortName": "World Clock", - "version": "0.04", + "version": "0.05", "description": "Current time zone plus up to four others", "icon": "app.png", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "custom": "custom.html", "storage": [ diff --git a/apps/berlinc/ChangeLog b/apps/berlinc/ChangeLog index 65ed0505f..9e9c1a6aa 100644 --- a/apps/berlinc/ChangeLog +++ b/apps/berlinc/ChangeLog @@ -1,3 +1,6 @@ 0.02: Modified for use with new bootloader and firmware 0.03: Shrinked size to avoid cut-off edges on the physical device. BTN3: show date. BTN1: show time in decimal. 0.04: Update to use Bangle.setUI instead of setWatch +0.05: Update *on* the minute rather than every 15 secs + Now show widgets + Make compatible with themes, and Bangle.js 2 diff --git a/apps/berlinc/berlin-clock.js b/apps/berlinc/berlin-clock.js index 144fa5ba7..0dd8ff8ee 100644 --- a/apps/berlinc/berlin-clock.js +++ b/apps/berlinc/berlin-clock.js @@ -1,7 +1,7 @@ // Berlin Clock see https://en.wikipedia.org/wiki/Mengenlehreuhr // https://github.com/eska-muc/BangleApps const fields = [4, 4, 11, 4]; -const offset = 20; +const offset = 24; const width = g.getWidth() - 2 * offset; const height = g.getHeight() - 2 * offset; const rowHeight = height / 4; @@ -10,11 +10,23 @@ var show_date = false; var show_time = false; var yy = 0; -rowlights = []; -time_digit = []; +var rowlights = []; +var time_digit = []; -function drawBerlinClock() { - g.clear(); +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { + g.reset().clearRect(0,24,g.getWidth(),g.getHeight()); var now = new Date(); // show date below the clock @@ -24,8 +36,7 @@ function drawBerlinClock() { var day = now.getDate(); var dateString = `${yr}-${month < 10 ? '0' : ''}${month}-${day < 10 ? '0' : ''}${day}`; var strWidth = g.stringWidth(dateString); - g.setColor(1, 1, 1); - g.setFontAlign(-1,-1); + g.setColor(g.theme.fg).setFontAlign(-1,-1); g.drawString(dateString, ( g.getWidth() - strWidth ) / 2, height + offset + 4); } @@ -50,8 +61,7 @@ function drawBerlinClock() { x2 = (col + 1) * boxWidth + offset; y2 = (row + 1) * rowHeight + offset; - g.setColor(1, 1, 1); - g.drawRect(x1, y1, x2, y2); + g.setColor(g.theme.fg).drawRect(x1, y1, x2, y2); if (col < rowlights[row]) { if (row === 2) { if (((col + 1) % 3) === 0) { @@ -65,46 +75,42 @@ function drawBerlinClock() { g.fillRect(x1 + 2, y1 + 2, x2 - 2, y2 - 2); } if (row == 3 && show_time) { - g.setColor(1,1,1); - g.setFontAlign(0,0); + g.setColor(g.theme.fg).setFontAlign(0,0); g.drawString(time_digit[col],(x1+x2)/2,(y1+y2)/2); } } } + + queueDraw(); } function toggleDate() { show_date = ! show_date; - drawBerlinClock(); + draw(); } function toggleTime() { show_time = ! show_time; - drawBerlinClock(); + draw(); } -// special function to handle display switch on -Bangle.on('lcdPower', (on) => { - g.clear(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ if (on) { - Bangle.drawWidgets(); - // call your app function here - drawBerlinClock(); + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; } }); -// refesh every 15 sec -setInterval(drawBerlinClock, 15E3); +// Show launcher when button pressed, handle up/down +Bangle.setUI("clockupdown", dir=> { + if (dir<0) toggleTime(); + if (dir>0) toggleDate(); +}); g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); -drawBerlinClock(); -if (BTN3) { - // Toggle date display, when BTN3 is pressed - setWatch(toggleTime,BTN1, { repeat : true, edge: "falling"}); - // Toggle date display, when BTN3 is pressed - setWatch(toggleDate,BTN3, { repeat : true, edge: "falling"}); -} -// Show launcher when button pressed -Bangle.setUI("clock"); +draw(); diff --git a/apps/sclock/ChangeLog b/apps/sclock/ChangeLog index 44a0ec504..dc76b8299 100644 --- a/apps/sclock/ChangeLog +++ b/apps/sclock/ChangeLog @@ -3,3 +3,4 @@ 0.04: Make this clock do 12h and 24h 0.05: setUI, screen size changes 0.06: Use Bangle.setUI for button/launcher handling +0.07: Update *on* the minute rather than every 15 secs diff --git a/apps/sclock/clock-simple.js b/apps/sclock/clock-simple.js index 8fb204d22..a399b05a7 100644 --- a/apps/sclock/clock-simple.js +++ b/apps/sclock/clock-simple.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ const big = g.getWidth()>200; const timeFontSize = big?6:5; const dateFontSize = big?3:2; @@ -14,7 +13,19 @@ const yposGMT = xyCenter*1.9; // Check settings for what type our clock should be var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -function drawSimpleClock() { +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { // get date var d = new Date(); var da = d.toString().split(" "); @@ -60,11 +71,18 @@ function drawSimpleClock() { var gmt = da[5]; g.setFont(font, gmtFontSize); g.drawString(gmt, xyCenter, yposGMT, true); + + queueDraw(); } -// handle switch display on by pressing BTN1 -Bangle.on('lcdPower', function(on) { - if (on) drawSimpleClock(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } }); // clean app screen @@ -74,8 +92,5 @@ Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); -// refesh every 15 sec -setInterval(drawSimpleClock, 15E3); - // draw now -drawSimpleClock(); +draw(); diff --git a/apps/svclock/ChangeLog b/apps/svclock/ChangeLog index 4db60ecd5..fb71fbeb8 100644 --- a/apps/svclock/ChangeLog +++ b/apps/svclock/ChangeLog @@ -1,3 +1,4 @@ 0.01: Modification of SimpleClock 0.04 to use Vectorfont 0.02: Use Bangle.setUI for button/launcher handling -0.03: Scale to BangleJS 2 and add locale \ No newline at end of file +0.03: Scale to BangleJS 2 and add locale +0.04: Fix rendering issue on real hardware, now update *on* the minute rather than every 15 secs diff --git a/apps/svclock/vclock-simple.js b/apps/svclock/vclock-simple.js index 637701a3f..e08c6fa2c 100644 --- a/apps/svclock/vclock-simple.js +++ b/apps/svclock/vclock-simple.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ const locale = require("locale"); var timeFontSize; @@ -12,8 +11,7 @@ var yposDate; var yposYear; var yposGMT; -switch (process.env.BOARD) { - case "EMSCRIPTEN": +if (g.getWidth() > 200) { timeFontSize = 65; dateFontSize = 20; gmtFontSize = 10; @@ -22,8 +20,7 @@ switch (process.env.BOARD) { yposDate = 130; yposYear = 175; yposGMT = 220; - break; - case "EMSCRIPTEN2": +} else { timeFontSize = 48; dateFontSize = 15; gmtFontSize = 10; @@ -32,12 +29,23 @@ switch (process.env.BOARD) { yposDate = 95; yposYear = 128; yposGMT = 161; - break; } // Check settings for what type our clock should be var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -function drawSimpleClock() { +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { g.clear(); Bangle.drawWidgets(); @@ -76,23 +84,26 @@ function drawSimpleClock() { // draw gmt g.setFont(font, gmtFontSize); g.drawString(d.toString().match(/GMT[+-]\d+/), xyCenter, yposGMT, true); + + queueDraw(); } -// handle switch display on by pressing BTN1 -Bangle.on('lcdPower', function(on) { - if (on) drawSimpleClock(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } }); +// Show launcher when button pressed +Bangle.setUI("clock"); // clean app screen g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); -// refesh every 15 sec -setInterval(drawSimpleClock, 15E3); - // draw now -drawSimpleClock(); - -// Show launcher when button pressed -Bangle.setUI("clock"); +draw(); diff --git a/apps/worldclock/ChangeLog b/apps/worldclock/ChangeLog index e922ef2a4..831dd3b5c 100644 --- a/apps/worldclock/ChangeLog +++ b/apps/worldclock/ChangeLog @@ -2,3 +2,5 @@ 0.02: Update custom.html for refactor; add README 0.03: Update for larger secondary timezone display (#610) 0.04: setUI, different screen sizes +0.05: Now update *on* the minute rather than every 15 secs + Fix rendering of single extra timezone on Bangle.js 2 diff --git a/apps/worldclock/app.js b/apps/worldclock/app.js index 84cb29874..2627e056c 100644 --- a/apps/worldclock/app.js +++ b/apps/worldclock/app.js @@ -1,5 +1,3 @@ -/* jshint esversion: 6 */ - const big = g.getWidth()>200; // Font for primary time and date const primaryTimeFontSize = big?6:5; @@ -16,8 +14,13 @@ const xcol2 = g.getWidth() - xcol1; const font = "6x8"; +/* TODO: we could totally use 'Layout' here and +avoid a whole bunch of hard-coded offsets */ + + const xyCenter = g.getWidth() / 2; const yposTime = big ? 75 : 60; +const yposTime2 = yposTime + (big ? 100 : 60); const yposDate = big ? 130 : 90; const yposWorld = big ? 170 : 120; @@ -29,41 +32,52 @@ var offsets = require("Storage").readJSON("worldclock.settings.json") || []; // TESTING CODE // Used to test offset array values during development. // Uncomment to override secondary offsets value - -// const mockOffsets = { -// zeroOffsets: [], -// oneOffset: [["UTC", 0]], -// twoOffsets: [ -// ["Tokyo", 9], -// ["UTC", 0], -// ], -// fourOffsets: [ -// ["Tokyo", 9], -// ["UTC", 0], -// ["Denver", -7], -// ["Miami", -5], -// ], -// fiveOffsets: [ -// ["Tokyo", 9], -// ["UTC", 0], -// ["Denver", -7], -// ["Chicago", -6], -// ["Miami", -5], -// ], -// }; +/* +const mockOffsets = { + zeroOffsets: [], + oneOffset: [["UTC", 0]], + twoOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ], + fourOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ["Denver", -7], + ["Miami", -5], + ], + fiveOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ["Denver", -7], + ["Chicago", -6], + ["Miami", -5], + ], +};*/ // Uncomment one at a time to test various offsets array scenarios -// offsets = mockOffsets.zeroOffsets; // should render nothing below primary time -// offsets = mockOffsets.oneOffset; // should render larger in two rows -// offsets = mockOffsets.twoOffsets; // should render two in columns -// offsets = mockOffsets.fourOffsets; // should render in columns -// offsets = mockOffsets.fiveOffsets; // should render first four in columns +//offsets = mockOffsets.zeroOffsets; // should render nothing below primary time +//offsets = mockOffsets.oneOffset; // should render larger in two rows +//offsets = mockOffsets.twoOffsets; // should render two in columns +//offsets = mockOffsets.fourOffsets; // should render in columns +//offsets = mockOffsets.fiveOffsets; // should render first four in columns // END TESTING CODE // Check settings for what type our clock should be //var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -var secondInterval; + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} function doublenum(x) { return x < 10 ? "0" + x : "" + x; @@ -73,7 +87,7 @@ function getCurrentTimeFromOffset(dt, offset) { return new Date(dt.getTime() + offset * 60 * 60 * 1000); } -function drawSimpleClock() { +function draw() { // get date var d = new Date(); var da = d.toString().split(" "); @@ -111,9 +125,9 @@ function drawSimpleClock() { // For a single secondary timezone, draw it bigger and drop time zone to second line const xOffset = 30; g.setFont(font, secondaryTimeFontSize); - g.drawString(`${hours}:${minutes}`, xyCenter, yposTime + 100, true); + g.drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true); g.setFont(font, secondaryTimeZoneFontSize); - g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime + 130, true); + g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true); // draw Day, name of month, Date g.setFont(font, secondaryTimeZoneFontSize); @@ -132,6 +146,8 @@ function drawSimpleClock() { g.drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true); } }); + + queueDraw(); } // clean app screen @@ -141,18 +157,15 @@ Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); -// refesh every 15 sec when screen is on -Bangle.on("lcdPower", (on) => { - if (secondInterval) clearInterval(secondInterval); - secondInterval = undefined; +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ if (on) { - secondInterval = setInterval(drawSimpleClock, 15e3); - drawSimpleClock(); // draw immediately + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; } }); -// draw now and every 15 sec until display goes off -drawSimpleClock(); -if (Bangle.isLCDOn()) { - secondInterval = setInterval(drawSimpleClock, 15e3); -} +// draw now +draw(); From e829ad54ac4b71bfcc3a1993473b85e548eaaef4 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 10:14:00 +0100 Subject: [PATCH 0298/1062] widid works on bangle2 --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index d0fe261e4..ed9091d66 100644 --- a/apps.json +++ b/apps.json @@ -1502,7 +1502,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,address,mac", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widid.wid.js","url":"widget.js"} ] From 26f43a787ae180bd00ea1c345fe6aa2a36cbb746 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 10:14:40 +0100 Subject: [PATCH 0299/1062] Move default app handling into loader.js, clear list of installed apps on disconnect different default apps for the 2 bangle versions --- core | 2 +- defaultapps.json => defaultapps_banglejs1.json | 0 defaultapps_banglejs2.json | 1 + loader.js | 18 +++++++++++++++++- 4 files changed, 19 insertions(+), 2 deletions(-) rename defaultapps.json => defaultapps_banglejs1.json (100%) create mode 100644 defaultapps_banglejs2.json diff --git a/core b/core index bc5b1284f..cdbf46fea 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit bc5b1284f41b0fcfdd264e1e2f12872e0b18c479 +Subproject commit cdbf46feaeefcb0d48ce9c170e90786dab11a03f diff --git a/defaultapps.json b/defaultapps_banglejs1.json similarity index 100% rename from defaultapps.json rename to defaultapps_banglejs1.json diff --git a/defaultapps_banglejs2.json b/defaultapps_banglejs2.json new file mode 100644 index 000000000..2d32d285c --- /dev/null +++ b/defaultapps_banglejs2.json @@ -0,0 +1 @@ +["boot","launch","s7clk","setting","about","widbat","widbt","widlock","widid"] diff --git a/loader.js b/loader.js index 90c1c5d96..45ec87df3 100644 --- a/loader.js +++ b/loader.js @@ -133,8 +133,8 @@ window.addEventListener('load', (event) => { }); }); -// Hook onto device chooser dropdown window.addEventListener('load', (event) => { + // Hook onto device chooser dropdown htmlToArray(document.querySelectorAll(".devicetype-nav .menu-item")).forEach(button => { button.addEventListener("click", event => { var a = event.target; @@ -144,4 +144,20 @@ window.addEventListener('load', (event) => { document.querySelector(".devicetype-nav span").innerText = a.innerText; }); }); + + // Button to install all default apps in one go + document.getElementById("installdefault").addEventListener("click",event=>{ + getInstalledApps().then(() => { + if (device.id == "BANGLEJS") + return httpGet("defaultapps_banglejs.json"); + if (device.id == "BANGLEJS2") + return httpGet("defaultapps_banglejs2.json"); + throw new Error("Unknown device "+device.id); + }).then(json=>{ + return installMultipleApps(JSON.parse(json), "default"); + }).catch(err=>{ + Progress.hide({sticky:true}); + showToast("App Install failed, "+err,"error"); + }); + }); }); From 86ad7ee037666b876d75480ad6ca6c48b45a7b94 Mon Sep 17 00:00:00 2001 From: Victor Serain Date: Thu, 21 Oct 2021 10:16:39 +0200 Subject: [PATCH 0300/1062] feat: set compass power off when screen is off --- apps/arrow/app.js | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/apps/arrow/app.js b/apps/arrow/app.js index 2cb3a42ad..ffa821a09 100644 --- a/apps/arrow/app.js +++ b/apps/arrow/app.js @@ -142,10 +142,13 @@ function docalibrate(e,first){ startdraw(); setTimeout(setButtons,1000); } - } - if (first===undefined) first=false; - stopdraw(); + } + + if (first === undefined) first = false; + + stopdraw(false); clearWatch(); + if (first) E.showAlert(msg,title).then(action.bind(null,true)); else @@ -153,16 +156,30 @@ function docalibrate(e,first){ } function startdraw(){ + if (!Bangle.isCompassOn()) { + Bangle.setCompassPower(1); + } + g.clear(); g.setColor(1,1,1); Bangle.drawWidgets(); candraw = true; + if (intervalRef) clearInterval(intervalRef); intervalRef = setInterval(reading,500); } -function stopdraw() { +function stopdraw(powerOffCompass) { + if (powerOffCompass === undefined) { + powerOffCompass = true; + } candraw=false; - if(intervalRef) {clearInterval(intervalRef);} + + if (powerOffCompass) { + Bangle.setCompassPower(0); + } + if (intervalRef) { + clearInterval(intervalRef); + } } function setButtons(){ @@ -170,7 +187,7 @@ function setButtons(){ setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); setWatch(docalibrate, BTN3, {repeat:false,edge:"falling"}); } - + Bangle.on('lcdPower',function(on) { if (on) { startdraw(); @@ -182,6 +199,5 @@ Bangle.on('lcdPower',function(on) { Bangle.on('kill',()=>{Bangle.setCompassPower(0);}); Bangle.loadWidgets(); -Bangle.setCompassPower(1); startdraw(); setButtons(); From 9f05531ba7fe8bdecfeb22e6dadc7ab5fe8589fc Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 11:08:53 +0100 Subject: [PATCH 0301/1062] new spectre css --- css/spectre-exp.min.css | 2 +- css/spectre-icons.min.css | 2 +- css/spectre.min.css | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/css/spectre-exp.min.css b/css/spectre-exp.min.css index 942cf59bf..d3137743a 100644 --- a/css/spectre-exp.min.css +++ b/css/spectre-exp.min.css @@ -1 +1 @@ -/*! Spectre.css Experimentals v0.5.8 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:flex;display:-ms-flexbox;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:flex;display:-ms-flexbox;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:flex;display:-ms-flexbox;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:flex;display:-ms-flexbox;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:flex;display:-ms-flexbox;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/css/spectre-icons.min.css b/css/spectre-icons.min.css index 9b6167caa..0276f7b84 100644 --- a/css/spectre-icons.min.css +++ b/css/spectre-icons.min.css @@ -1 +1 @@ -/*! Spectre.css Icons v0.5.8 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/css/spectre.min.css b/css/spectre.min.css index 8df0bf64f..0fe23d9c0 100644 --- a/css/spectre.min.css +++ b/css/spectre.min.css @@ -1 +1 @@ -/*! Spectre.css v0.5.8 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:inline-flex;display:-ms-inline-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:flex;display:-ms-flexbox}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:flex;display:-ms-flexbox}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:inline-flex;display:-ms-inline-flexbox}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.columns{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.columns.col-gapless{margin-left:0;margin-right:0}.columns.col-gapless>.column{padding-left:0;padding-right:0}.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:flex;display:-ms-flexbox;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:flex;display:-ms-flexbox;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:flex;display:-ms-flexbox;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header .icon,.accordion[open] .accordion-header .icon{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:inline-flex;display:-ms-inline-flexbox;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:flex;display:-ms-flexbox;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:flex;display:-ms-flexbox;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:flex;display:-ms-flexbox;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:flex;display:-ms-flexbox;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:flex;display:-ms-flexbox}.d-inline-flex{display:inline-flex;display:-ms-inline-flexbox}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:sticky!important;position:-webkit-sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file From 47f32f34fa32af68206809523184c8b39d24b490 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 11:43:13 +0100 Subject: [PATCH 0302/1062] more bangle 2 compatibility --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index ed9091d66..98d9c672e 100644 --- a/apps.json +++ b/apps.json @@ -320,7 +320,7 @@ "icon": "slidingtext.png", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "custom": "custom.html", "allow_emulator": false, @@ -594,7 +594,7 @@ "description": "Application that allows you to record a GPS track. Can run in background", "icon": "app.png", "tags": "tool,outdoors,gps,widget", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "interface": "interface.html", "storage": [ From 69c5c9d8a2335a2910248ccf2d7dbea1f3e8c673 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 11:43:30 +0100 Subject: [PATCH 0303/1062] move to icons defined in main.css --- core | 2 +- css/main.css | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/core b/core index cdbf46fea..3a2c706b4 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit cdbf46feaeefcb0d48ce9c170e90786dab11a03f +Subproject commit 3a2c706b4cdf02e5365b191103c80d587b3ace5a diff --git a/css/main.css b/css/main.css index 90b4ff280..44137e9b1 100644 --- a/css/main.css +++ b/css/main.css @@ -35,6 +35,21 @@ top: 36px; left: -24px; } -.btn-favourite { - color: red; +.btn.btn-favourite { color: red; } +.btn.btn-favourite:hover { color: red; } + +.icon.icon-emulator { text-indent: 0px; } /*override spectre*/ +.icon.icon-emulator::before { + content: "\01F5B5"; +} +.icon.icon-favourite { text-indent: 0px; } /*override spectre*/ +.icon.icon-favourite::before { + content: "\02661"; /* 0x2661 = empty heart; 0x2606 = empty star */ +} +.icon.icon-favourite-active::before { + content: "\02665"; /* 0x2665 = solid heart; 0x2605 = solid star */ +} +.icon.icon-interface {text-indent: 0px;font-size: 130%;vertical-align: -30%;} /*override spectre*/ +.icon.icon-interface::before { + content: "\01F5AB"; } From 38ab8521d12338305bf65991641b625b86df9c6d Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 12:17:18 +0100 Subject: [PATCH 0304/1062] Fix compass and compass widget --- apps.json | 6 ++-- apps/compass/ChangeLog | 3 +- apps/compass/compass.js | 78 ++++++++++++++++++++++++----------------- apps/widcom/ChangeLog | 3 ++ apps/widcom/widget.js | 35 ++++++------------ 5 files changed, 64 insertions(+), 61 deletions(-) create mode 100644 apps/widcom/ChangeLog diff --git a/apps.json b/apps.json index 98d9c672e..6e2998d59 100644 --- a/apps.json +++ b/apps.json @@ -537,11 +537,11 @@ { "id": "compass", "name": "Compass", - "version": "0.03", + "version": "0.04", "description": "Simple compass that points North", "icon": "compass.png", "tags": "tool,outdoors", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"compass.app.js","url":"compass.js"}, {"name":"compass.img","url":"compass-icon.js","evaluate":true} @@ -3382,7 +3382,7 @@ "icon": "widget.png", "type": "widget", "tags": "widget,compass", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"widcom.wid.js","url":"widget.js"} diff --git a/apps/compass/ChangeLog b/apps/compass/ChangeLog index e70a5688b..d2bfbd4fa 100644 --- a/apps/compass/ChangeLog +++ b/apps/compass/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Show text if uncalibrated -0.03: Eliminate flickering \ No newline at end of file +0.03: Eliminate flickering +0.04: Fix for Bangle.js 2 and themes diff --git a/apps/compass/compass.js b/apps/compass/compass.js index 9b7ed56b7..d26081dd5 100644 --- a/apps/compass/compass.js +++ b/apps/compass/compass.js @@ -1,60 +1,72 @@ -var tg = Graphics.createArrayBuffer(120,20,1,{msb:true}); -var timg = { - width:tg.getWidth(), - height:tg.getHeight(), - bpp:1, - buffer:tg.buffer -}; - -var ag = Graphics.createArrayBuffer(160,160,2,{msb:true}); +var W = g.getWidth(); +var M = W/2; // middle of screen +// Angle buffer +var AGS = W > 200 ? 160 : 120; // buffer size +var AGM = AGS/2; // midpoint/radius +var AGH = AGM-10; // hand size +var ag = Graphics.createArrayBuffer(AGS,AGS,2,{msb:true}); var aimg = { width:ag.getWidth(), height:ag.getHeight(), bpp:2, buffer:ag.buffer, - palette:new Uint16Array([0,0x03FF,0xF800,0x001F]) + palette:new Uint16Array([ + g.theme.bg, + g.toColor("#07f"), + g.toColor("#f00"), + g.toColor("#00f")]) }; -ag.setColor(1); -ag.fillCircle(80,80,79,79); -ag.setColor(0); -ag.fillCircle(80,80,69,69); +ag.setColor(1).fillCircle(AGM,AGM,AGM-1,AGM-1); +ag.setColor(0).fillCircle(AGM,AGM,AGM-11,AGM-11); function arrow(r,c) { r=r*Math.PI/180; var p = Math.PI/2; - ag.setColor(c); - ag.fillPoly([ - 80+60*Math.sin(r), 80-60*Math.cos(r), - 80+10*Math.sin(r+p), 80-10*Math.cos(r+p), - 80+10*Math.sin(r-p), 80-10*Math.cos(r-p), + ag.setColor(c).fillPoly([ + AGM+AGH*Math.sin(r), AGM-AGH*Math.cos(r), + AGM+10*Math.sin(r+p), AGM-10*Math.cos(r+p), + AGM+10*Math.sin(r-p), AGM-10*Math.cos(r-p), ]); } +var wasUncalibrated = false; var oldHeading = 0; Bangle.on('mag', function(m) { if (!Bangle.isLCDOn()) return; - tg.clear(); - tg.setFont("6x8",1); - tg.setColor(1); + g.reset(); if (isNaN(m.heading)) { - tg.setFontAlign(0,-1); - tg.setFont("6x8",1); - tg.drawString("Uncalibrated",60,4); - tg.drawString("turn 360° around",60,12); + if (!wasUncalibrated) { + g.clearRect(0,24,W,48); + g.setFontAlign(0,-1).setFont("6x8"); + g.drawString("Uncalibrated\nturn 360° around",M,24+4); + wasUncalibrated = true; + } + } else { + if (wasUncalibrated) { + g.clearRect(0,24,W,48); + wasUncalibrated = false; + } + g.setFontAlign(0,0).setFont("6x8",3); + var y = 36; + g.clearRect(M-40,y,M+40,y+24); + g.drawString(Math.round(m.heading),M,y,true); } - else { - tg.setFontAlign(0,0); - tg.setFont("6x8",2); - tg.drawString(Math.round(m.heading),60,12); - } - g.drawImage(timg,0,0,{scale:2}); + ag.setColor(0); arrow(oldHeading,0); arrow(oldHeading+180,0); arrow(m.heading,2); arrow(m.heading+180,3); - g.drawImage(aimg,40,50); + g.drawImage(aimg, + (W-ag.getWidth())/2, + g.getHeight()-(ag.getHeight()+4)); oldHeading = m.heading; }); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); Bangle.setCompassPower(1); +Bangle.setLCDPower(1); +Bangle.setLCDTimeout(0); diff --git a/apps/widcom/ChangeLog b/apps/widcom/ChangeLog new file mode 100644 index 000000000..5d08e91e6 --- /dev/null +++ b/apps/widcom/ChangeLog @@ -0,0 +1,3 @@ +0.02: Works with light theme + Doesn't drain battery by updating every 2 secs + Fix alignment diff --git a/apps/widcom/widget.js b/apps/widcom/widget.js index b9c911dbf..bce9453c5 100644 --- a/apps/widcom/widget.js +++ b/apps/widcom/widget.js @@ -1,30 +1,17 @@ (function(){ - //var img = E.toArrayBuffer(atob("FBSBAAAAAAAAA/wAf+AP/wH/2D/zw/w8PwfD9nw+b8Pg/Dw/w8/8G/+A//AH/gA/wAAAAAAA")); - //var img = E.toArrayBuffer(atob("GBiBAAB+AAP/wAeB4A4AcBgAGDAADHAADmABhmAHhsAfA8A/A8BmA8BmA8D8A8D4A2HgBmGABnAADjAADBgAGA4AcAeB4AP/wAB+AA==")); - var img = E.toArrayBuffer(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A")); - - function draw() { + var cp = Bangle.setCompassPower; + Bangle.setCompassPower = () => { + cp.apply(Bangle, arguments); + WIDGETS.compass.draw(); + }; + + WIDGETS.compass={area:"tr",width:24,draw:function() { g.reset(); if (Bangle.isCompassOn()) { - g.setColor(1,0.8,0); // on = amber + g.setColor(g.theme.dark ? "#FC0" : "#F00"); } else { - g.setColor(0.3,0.3,0.3); // off = grey + g.setColor(g.theme.dark ? "#333" : "#CCC"); } - g.drawImage(img, 10+this.x, 2+this.var); - } - - var timerInterval; - Bangle.on('lcdPower', function(on) { - if (on) { - WIDGETS.compass.draw(); - if (!timerInterval) timerInterval = setInterval(()=>WIDGETS.compass.draw(), 2000); - } else { - if (timerInterval) { - clearInterval(timerInterval); - timerInterval = undefined; - } - } - }); - - WIDGETS.compass={area:"tr",width:24,draw:draw}; + g.drawImage(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A"), 2+this.x, 2+this.var); + }}; })(); From 852f911dcfce73a2f03f0a474ba1e965be99b587 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 12:40:34 +0100 Subject: [PATCH 0305/1062] testing/converting heart rate apps for Bangle.js 2 --- apps.json | 16 +++++------ apps/heart/ChangeLog | 1 + apps/heart/app.js | 6 ++-- apps/hrm/ChangeLog | 1 + apps/hrm/heartrate-icon.js | 2 +- apps/hrm/heartrate.js | 26 ++++++++++------- apps/widhrm/ChangeLog | 1 + apps/widhrm/widget.js | 57 +++++++++++++++++--------------------- apps/widhrt/ChangeLog | 4 ++- apps/widhrt/widget.js | 30 +++++++------------- 10 files changed, 69 insertions(+), 75 deletions(-) diff --git a/apps.json b/apps.json index 6e2998d59..94d5e49e7 100644 --- a/apps.json +++ b/apps.json @@ -624,11 +624,11 @@ { "id": "heart", "name": "Heart Rate Recorder", - "version": "0.06", + "version": "0.07", "description": "Application that allows you to record your heart rate. Can run in background", "icon": "app.png", "tags": "tool,health,widget", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "interface": "interface.html", "storage": [ {"name":"heart.app.js","url":"app.js"}, @@ -818,11 +818,11 @@ { "id": "hrm", "name": "Heart Rate Monitor", - "version": "0.05", + "version": "0.06", "description": "Measure your heart rate and see live sensor data", "icon": "heartrate.png", "tags": "health", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"hrm.app.js","url":"heartrate.js"}, {"name":"hrm.img","url":"heartrate-icon.js","evaluate":true} @@ -831,12 +831,12 @@ { "id": "widhrm", "name": "Simple Heart Rate widget", - "version": "0.04", + "version": "0.05", "description": "When the screen is on, the widget turns on the heart rate monitor and displays the current heart rate (or last known in grey). For this to work well you'll need at least a 15 second LCD Timeout.", "icon": "widget.png", "type": "widget", "tags": "health,widget", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widhrm.wid.js","url":"widget.js"} ] @@ -3334,12 +3334,12 @@ { "id": "widhrt", "name": "HRM Widget", - "version": "0.02", + "version": "0.03", "description": "Tiny widget to show the power on/off status of the Heart Rate Monitor. Requires firmware v2.08.167 or later", "icon": "widget.png", "type": "widget", "tags": "widget,hrm", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"widhrt.wid.js","url":"widget.js"} diff --git a/apps/heart/ChangeLog b/apps/heart/ChangeLog index 8274169ee..f6fd9793e 100644 --- a/apps/heart/ChangeLog +++ b/apps/heart/ChangeLog @@ -12,3 +12,4 @@ Generate scale based on defined minimum and maximum measurement Added background line on 50% to ease estimation of drawn values 0.06: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799) +0.07: theme support diff --git a/apps/heart/app.js b/apps/heart/app.js index 77a1c2106..ed249c343 100644 --- a/apps/heart/app.js +++ b/apps/heart/app.js @@ -221,9 +221,9 @@ function graphRecord(n) { if (tempCount == startLine) { // generating rgaph in loop when reaching startLine to keep loading // message on screen until graph can be drawn - g.clear(). + g.reset().clearRect(0,24,g.getWidth(),g.getHeight()). // Home for Btn2 - setColor(1, 1, 1). + setColor(g.theme.fg). drawLine(220, 118, 227, 110). drawLine(227, 110, 234, 118). drawPoly([222,117,222,125,232,125,232,117], false). @@ -245,7 +245,7 @@ function graphRecord(n) { // scale indicator line for 50% drawLine(GraphXZero - GraphMarkerOffset, GraphY100 + (GraphYZero - GraphY100)/2, GraphXZero, GraphY100 + (GraphYZero - GraphY100)/2). // background line for 50% - setColor(1, 1, 1). + setColor(g.theme.fg). drawLine(GraphXZero + 1, GraphY100 + (GraphYZero - GraphY100)/2, GraphXMax, GraphY100 + (GraphYZero - GraphY100)/2). setFontAlign(1, -1, 0). setFont("Vector", 10); diff --git a/apps/hrm/ChangeLog b/apps/hrm/ChangeLog index d27886b15..9b390b63e 100644 --- a/apps/hrm/ChangeLog +++ b/apps/hrm/ChangeLog @@ -3,3 +3,4 @@ 0.03: Fix timing issues, and use 1/2 scale to keep graph on screen 0.04: Update for new firmwares that have a 'HRM-raw' event 0.05: Tweaks for 'HRM-raw' handling +0.06: Add widgets diff --git a/apps/hrm/heartrate-icon.js b/apps/hrm/heartrate-icon.js index cadbc7dfa..20c9b15f7 100644 --- a/apps/hrm/heartrate-icon.js +++ b/apps/hrm/heartrate-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwghC/AH4AThnMAAXABJoMHBwgJJAAYMFAAIJLFxImCBJIuLABYuI4gXNNZFCC6AIFkZIQA4szC6vEmdMC60sC6nDmc8C6RDBC4irLC4gTBocymgGBoYXO4UyUwNEAYKrMC4ZEBUwNMVAR7LC4dDCoYBBSYJ7DoZQCC4kCmczkc0JIVM4UzmgaBAAQWD4AXBggJBJAIkBocs4c0BAQXJJARBD4c8oc8HAKZCI4gWCVAYXEJIJoCOovNC4cMUIQPB4RFBTAYAFIwapEC4JyCZAalHGAvCJYZYCVAYuIMIhjE5heGCwxhDMYTtIFw4wFoYsGFxIwF4YuRGAh7DFxxhGFyIYKCxqrGIpwwKFx4YGCyJJFCyQYDCygA/AH4AFA=")) +require("heatshrink").decompress(atob("mEw4UA///g3yrv/7f+Jf4AJgNVoAEGAANVAAIEGCIQABoAEEBYMFAwVQAggLBioGCqgEEFIgAGFwdXBYw1Dr4LKrwLHIIVaBYxNDvXVBanVteVBZGVt+VKooLBq+19u1JItQgNW0vlBYIxEL4Ne1u18taGIN9BYUD1XvBYN62+q1a0D1d7ytttYLEWYV6BYNt93VEYKzCita6t59vqX4sFIgN70tqa4pUBTgO1vbvFgB0BKQNZawYACdYNeytdFwgwCBYJ2DFwQwCqoxBFwwABBYoKEGAKyDFwgwDFw4kDERBVDEQ4kEEQ4kDBRAYBERBuCNAoA/AA4=")) diff --git a/apps/hrm/heartrate.js b/apps/hrm/heartrate.js index a6b8e791a..a47251010 100644 --- a/apps/hrm/heartrate.js +++ b/apps/hrm/heartrate.js @@ -4,13 +4,14 @@ Bangle.setHRMPower(1); var hrmInfo, hrmOffset = 0; var hrmInterval; var btm = g.getHeight()-1; +var lastHrmPt = []; // last xy coords we draw a line to function onHRM(h) { if (counter!==undefined) { // the first time we're called remove // the countdown counter = undefined; - g.clear(); + g.clearRect(0,24,g.getWidth(),g.getHeight()); } hrmInfo = h; /* On 2v09 and earlier firmwares the only solution for realtime @@ -28,7 +29,7 @@ function onHRM(h) { var px = g.getWidth()/2; g.setFontAlign(0,0); - g.clearRect(0,24,239,80); + g.clearRect(0,24,g.getWidth(),80); g.setFont("6x8").drawString("Confidence "+hrmInfo.confidence+"%", px, 75); var str = hrmInfo.bpm; g.setFontVector(40).drawString(str,px,45); @@ -43,17 +44,18 @@ Bangle.on('HRM-raw', function(v) { hrmOffset++; if (hrmOffset>g.getWidth()) { hrmOffset=0; - g.clearRect(0,80,239,239); - g.moveTo(-100,0); + g.clearRect(0,80,g.getWidth(),g.getHeight()); + lastHrmPt = [-100,0]; } y = E.clip(btm-v.filt/4,btm-10,btm); g.setColor(1,0,0).fillRect(hrmOffset,btm, hrmOffset, y); y = E.clip(170 - (v.raw/2),80,btm); - g.setColor(g.theme.fg).lineTo(hrmOffset, y); + g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y); + lastHrmPt = [hrmOffset, y]; if (counter !==undefined) { counter = undefined; - g.clear(); + g.clearRect(0,24,g.getWidth(),g.getHeight()); } }); @@ -65,7 +67,10 @@ function countDown() { setTimeout(countDown, 1000); } } -g.clear().setFont("6x8",2).setFontAlign(0,0); +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +g.reset().setFont("6x8",2).setFontAlign(0,0); g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); countDown(); @@ -79,13 +84,14 @@ function readHRM() { if (!hrmInfo) return; if (hrmOffset==0) { - g.clearRect(0,100,239,239); - g.moveTo(-100,0); + g.clearRect(0,100,g.getWidth(),g.getHeight()); + lastHrmPt = [-100,0]; } for (var i=0;i<2;i++) { var a = hrmInfo.raw[hrmOffset]; hrmOffset++; y = E.clip(170 - (a*2),100,230); - g.setColor(g.theme.fg).lineTo(hrmOffset, y); + g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y); + lastHrmPt = [hrmOffset, y]; } } diff --git a/apps/widhrm/ChangeLog b/apps/widhrm/ChangeLog index 45dcfa87e..93e2eaf66 100644 --- a/apps/widhrm/ChangeLog +++ b/apps/widhrm/ChangeLog @@ -2,3 +2,4 @@ 0.02: Tweaks for variable size widget system 0.03: Ensure redrawing works with variable size widget system 0.04: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799) +0.05: Use new 'lock' event, not LCD (so it works on Bangle.js 2) diff --git a/apps/widhrm/widget.js b/apps/widhrm/widget.js index 54b105d1e..f3b3ff45e 100644 --- a/apps/widhrm/widget.js +++ b/apps/widhrm/widget.js @@ -1,9 +1,28 @@ (() => { - var currentBPM = undefined; - var lastBPM = undefined; - var firstBPM = true; // first reading since sensor turned on + if (!Bangle.isLocked) return; // old firmware + var currentBPM; + var lastBPM; - function draw() { + // turn on sensor when the LCD is unlocked + Bangle.on('lock', function(isLocked) { + if (!isLocked) { + Bangle.setHRMPower(1,"widhrm"); + currentBPM = undefined; + WIDGETS["hrm"].draw(); + } else { + Bangle.setHRMPower(0,"widhrm"); + } + }); + + Bangle.on('HRM',function(d) { + currentBPM = d.bpm; + lastBPM = currentBPM; + WIDGETS["hrm"].draw(); + }); + Bangle.setHRMPower(!Bangle.isLocked(),"widhrm"); + + // add your widget + WIDGETS["hrm"]={area:"tl",width:24,draw:function() { var width = 24; g.reset(); g.setFont("6x8", 1); @@ -16,36 +35,10 @@ } if (bpm===undefined) bpm = "--"; - g.setColor(isCurrent ? "#ffffff" : "#808080"); + g.setColor(isCurrent ? g.theme.fg : "#808080"); g.drawString(bpm, this.x+width/2, this.y+19); g.setColor(isCurrent ? "#ff0033" : "#808080"); g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),this.x+(width-10)/2,this.y+1); g.setColor(-1); - } - - // redraw when the LCD turns on - Bangle.on('lcdPower', function(on) { - if (on) { - Bangle.setHRMPower(1,"widhrm"); - firstBPM = true; - currentBPM = undefined; - WIDGETS["hrm"].draw(); - } else { - Bangle.setHRMPower(0,"widhrm"); - } - }); - - Bangle.on('HRM',function(d) { - if (firstBPM) - firstBPM=false; // ignore the first one as it's usually rubbish - else { - currentBPM = d.bpm; - lastBPM = currentBPM; - } - WIDGETS["hrm"].draw(); - }); - Bangle.setHRMPower(Bangle.isLCDOn(),"widhrm"); - - // add your widget - WIDGETS["hrm"]={area:"tl",width:24,draw:draw}; + }}; })(); diff --git a/apps/widhrt/ChangeLog b/apps/widhrt/ChangeLog index fdb495797..39520ad6a 100644 --- a/apps/widhrt/ChangeLog +++ b/apps/widhrt/ChangeLog @@ -1,3 +1,5 @@ 0.01: First version 0.02: Don't break if running on 2v08 firmware (just don't display anything) - +0.03: Works with light theme + Doesn't drain battery by updating every 2 secs + fix alignment diff --git a/apps/widhrt/widget.js b/apps/widhrt/widget.js index 8ac76def8..d9716fa24 100644 --- a/apps/widhrt/widget.js +++ b/apps/widhrt/widget.js @@ -1,28 +1,18 @@ (function(){ if (!Bangle.isHRMOn) return; // old firmware + var hp = Bangle.setHRMPower; + Bangle.setHRMPower = () => { + hp.apply(Bangle, arguments); + WIDGETS.widhrt.draw(); + }; - function draw() { + WIDGETS.widhrt={area:"tr",width:24,draw:function() { g.reset(); if (Bangle.isHRMOn()) { - g.setColor(1,0,0); // on = red + g.setColor("#f00"); // on = red } else { - g.setColor(0.3,0.3,0.3); // off = grey + g.setColor(g.theme.dark ? "#333" : "#CCC"); // off = grey } - g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 10+this.x, 2+this.y); - } - - var timerInterval; - Bangle.on('lcdPower', function(on) { - if (on) { - WIDGETS.widhrt.draw(); - if (!timerInterval) timerInterval = setInterval(()=>WIDGETS["widhrt"].draw(), 2000); - } else { - if (timerInterval) { - clearInterval(timerInterval); - timerInterval = undefined; - } - } - }); - - WIDGETS.widhrt={area:"tr",width:24,draw:draw}; + g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 1+this.x, 1+this.y); + }}; })(); From f1b94d4c667dbfc820fc846cb060a1cf09f7a74d Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 13:42:46 +0100 Subject: [PATCH 0306/1062] Bluetooth Heart Rate Monitor - Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one. --- apps.json | 16 ++++++++++ apps/bthrm/ChangeLog | 1 + apps/bthrm/README.md | 45 ++++++++++++++++++++++++++ apps/bthrm/app-icon.js | 1 + apps/bthrm/app.png | Bin 0 -> 2756 bytes apps/bthrm/boot.js | 71 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 134 insertions(+) create mode 100644 apps/bthrm/ChangeLog create mode 100644 apps/bthrm/README.md create mode 100644 apps/bthrm/app-icon.js create mode 100644 apps/bthrm/app.png create mode 100644 apps/bthrm/boot.js diff --git a/apps.json b/apps.json index 94d5e49e7..50e53f4f4 100644 --- a/apps.json +++ b/apps.json @@ -624,6 +624,7 @@ { "id": "heart", "name": "Heart Rate Recorder", + "shortName": "HRM Record", "version": "0.07", "description": "Application that allows you to record your heart rate. Can run in background", "icon": "app.png", @@ -841,6 +842,21 @@ {"name":"widhrm.wid.js","url":"widget.js"} ] }, + { "id": "bthrm", + "name": "Bluetooth Heart Rate Monitor", + "shortName":"BT HRM", + "version":"0.01", + "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", + "icon": "app.png", + "tags": "health,bluetooth", + "type": "boot", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"bthrm.boot.js","url":"boot.js"}, + {"name":"bthrm.img","url":"app-icon.js","evaluate":true} + ] + }, { "id": "stetho", "name": "Stethoscope", diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/bthrm/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/bthrm/README.md b/apps/bthrm/README.md new file mode 100644 index 000000000..f0c7775c2 --- /dev/null +++ b/apps/bthrm/README.md @@ -0,0 +1,45 @@ +# Bluetooth Heart Rate Monitor + +When this app is installed it overrides Bangle.js's build in heart rate monitor with an external Bluetooth one. + +HRM is requested it searches on Bluetooth for a heart rate monitor, connects, and sends data back using the `Bangle.on('HRM'` event as if it came from the on board monitor. + +This means it's compatible with many Bangle.js apps including: + +* [Heart Rate Widget](https://banglejs.com/apps/#widhrt) +* [Heart Rate Recorder](https://banglejs.com/apps/#heart) + +It it NOT COMPATIBLE with [Heart Rate Monitor](https://banglejs.com/apps/#hrm) +as that requires live sensor data (rather than just BPM readings). + +## Usage + +Just install the app, then install an app that uses the heart rate monitor. + +Once installed it'll automatically try and connect to the first bluetooth +heart rate monitor it finds. + +**To disable this and return to normal HRM, uninstall the app** + +## Compatible Heart Rate Monitors + +This works with any heart rate monitor providing the standard Bluetooth +Heart Rate Service (`180D`) and characteristic (`2A37`). + +So far it has been tested on: + +* CooSpo Bluetooth Heart Rate Monitor + +## Internals + +This replaces `Bangle.setHRMPower` with its own implementation. + +## TODO + +* Maybe a `bthrm.settings.js` and app (that calls it) to enable it to be turned on and off +* A widget to show connection state? +* Specify a specific device by address? + +## Creator + +Gordon Williams diff --git a/apps/bthrm/app-icon.js b/apps/bthrm/app-icon.js new file mode 100644 index 000000000..04a5ee610 --- /dev/null +++ b/apps/bthrm/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///g3yy06AoIZNitUAg8AgtVqtQAgoRCAwITBAggABAoIABAgsAgIGDoIEDoApDAAwwBFIV1BYo1E+oLTAgQLGJon9BZNXBatdBYRVFBYN/r9fHoxTBBYYlEL4QLFq/a1WUgE///fr4xBv/+1Wq1EAh/3/tX6/fv/6BYOqwCzBBYf9tWq9QLF79X+oLBDIOgKgILEEIIxBGAMVNAP/BYf/BYUFBYJSB6wLC9QLBeAQLBqwLCGAL9BBYmr9X+GAILBbIIlBBYP6/wwBBYMFBYZGB/4XDGAILD34vEcwYLB15HBBYYkBBYWrFwILDKoRTCVIQLCEgQXIEgVaF44YCoRHHAAMUgQuBNgILFgECO4W/BZCPFBYinGBY6/CAArXFBY7vDAAsq1QuB0ALIOwOABY0KEgJGGGAguHDAYDBA==")) diff --git a/apps/bthrm/app.png b/apps/bthrm/app.png new file mode 100644 index 0000000000000000000000000000000000000000..40c2ab024622fd92cf3908991804eb1def70e7ac GIT binary patch literal 2756 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 KMNUMnLSTaZ|1XvR literal 0 HcmV?d00001 diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js new file mode 100644 index 000000000..06ead0965 --- /dev/null +++ b/apps/bthrm/boot.js @@ -0,0 +1,71 @@ +(function() { + var log = function() {};//print + var gatt; + + Bangle.setHRMPower = function(isOn, app) { + // Do app power handling + if (!app) app="?"; + log("setHRMPower ->", isOn, app); + if (Bangle._PWR===undefined) Bangle._PWR={}; + if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[]; + if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app); + if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app); + isOn = Bangle._PWR.HRM.length; + // so now we know if we're really on + if (isOn) { + log("setHRMPower on", app); + if (!gatt) { + log("HRM not already on"); + NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) { + log("Found device "+device.id); + device.on('gattserverdisconnected', function(reason) { + gatt = undefined; + }); + return device.gatt.connect(); + }).then(function(g) { + log("Connected"); + gatt = g; + return gatt.getPrimaryService(0x180D); + }).then(function(service) { + return service.getCharacteristic(0x2A37); + }).then(function(characteristic) { + log("Got characteristic"); + characteristic.on('characteristicvaluechanged', function(event) { + var dv = event.target.value; + var flags = dv.getUint8(0); + // 0 = 8 or 16 bit + // 1,2 = sensor contact + // 3 = energy expended shown + // 4 = RR interval + var bpm = (flags&1) ? (dv.getUint16(1)/100/* ? */) : dv.getUint8(1); // 8 or 16 bit + /* var idx = 2 + (flags&1); // index of next field + if (flags&8) idx += 2; // energy expended + if (flags&16) { + var interval = dv.getUint16(idx,1); // in milliseconds + }*/ + Bangle.emit('HRM',{ + bpm:bpm, + confidence:100 + }); + }); + return characteristic.startNotifications(); + }).then(function() { + log("Ready"); + console.log("Done!"); + }).catch(function(err) { + console.log("Error",err); + gatt = undefined; + }); + } + } else { // not on + log("setHRMPower off", app); + if (gatt) { + log("HRM connected - disconnecting"); + try {gatt.disconnect();}catch(e) { + log("HRM disconnect error", e); + } + gatt = undefined; + } + } + }; +})(); From ce4830956847355f310bb24e5def43af6e1069a9 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 13:50:19 +0100 Subject: [PATCH 0307/1062] Minor app tweaks --- apps/bthrm/boot.js | 3 +++ apps/heart/app.js | 2 +- apps/widhrm/widget.js | 16 ++++++++++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js index 06ead0965..5e348672d 100644 --- a/apps/bthrm/boot.js +++ b/apps/bthrm/boot.js @@ -2,6 +2,9 @@ var log = function() {};//print var gatt; + Bangle.isHRMOn = function() { + return gatt!==undefined; + } Bangle.setHRMPower = function(isOn, app) { // Do app power handling if (!app) app="?"; diff --git a/apps/heart/app.js b/apps/heart/app.js index ed249c343..5428ea06b 100644 --- a/apps/heart/app.js +++ b/apps/heart/app.js @@ -303,7 +303,7 @@ function graphRecord(n) { log("Finished rendering data"); Bangle.buzz(200, 0.3); g.flip(); - setWatch(stop, BTN2, {edge:"falling", debounce:50, repeat:false}); + setWatch(stop, (global.BTN2!==undefined)?BTN2:BTN1, {edge:"falling", debounce:50, repeat:false}); return; } diff --git a/apps/widhrm/widget.js b/apps/widhrm/widget.js index f3b3ff45e..7ffe1aa6d 100644 --- a/apps/widhrm/widget.js +++ b/apps/widhrm/widget.js @@ -2,6 +2,7 @@ if (!Bangle.isLocked) return; // old firmware var currentBPM; var lastBPM; + var isHRMOn = false; // turn on sensor when the LCD is unlocked Bangle.on('lock', function(isLocked) { @@ -14,19 +15,24 @@ } }); + var hp = Bangle.setHRMPower; + Bangle.setHRMPower = () => { + hp.apply(Bangle, arguments); + isHRMOn = Bangle.isHRMOn(); + WIDGETS["hrm"].draw(); + }; + Bangle.on('HRM',function(d) { currentBPM = d.bpm; lastBPM = currentBPM; WIDGETS["hrm"].draw(); }); - Bangle.setHRMPower(!Bangle.isLocked(),"widhrm"); // add your widget WIDGETS["hrm"]={area:"tl",width:24,draw:function() { var width = 24; g.reset(); - g.setFont("6x8", 1); - g.setFontAlign(0, 0); + g.setFont("6x8", 1).setFontAlign(0, 0); g.clearRect(this.x,this.y+15,this.x+width,this.y+23); // erase background var bpm = currentBPM, isCurrent = true; if (bpm===undefined) { @@ -37,8 +43,10 @@ bpm = "--"; g.setColor(isCurrent ? g.theme.fg : "#808080"); g.drawString(bpm, this.x+width/2, this.y+19); - g.setColor(isCurrent ? "#ff0033" : "#808080"); + g.setColor(isHRMOn ? "#ff0033" : "#808080"); g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),this.x+(width-10)/2,this.y+1); g.setColor(-1); }}; + + Bangle.setHRMPower(!Bangle.isLocked(),"widhrm"); })(); From f6184f0fd1c8171b079cbee746f67baf6bec5173 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 13:57:14 +0100 Subject: [PATCH 0308/1062] minor tweaks --- apps/bthrm/boot.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js index 5e348672d..88e574480 100644 --- a/apps/bthrm/boot.js +++ b/apps/bthrm/boot.js @@ -1,9 +1,10 @@ (function() { var log = function() {};//print var gatt; + var status; Bangle.isHRMOn = function() { - return gatt!==undefined; + return (status=="searching" || status=="connecting") || (gatt!==undefined); } Bangle.setHRMPower = function(isOn, app) { // Do app power handling @@ -17,10 +18,12 @@ // so now we know if we're really on if (isOn) { log("setHRMPower on", app); - if (!gatt) { + if (!Bangle.isHRMOn()) { log("HRM not already on"); + status = "searching"; NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) { log("Found device "+device.id); + status = "connecting"; device.on('gattserverdisconnected', function(reason) { gatt = undefined; }); @@ -54,16 +57,18 @@ return characteristic.startNotifications(); }).then(function() { log("Ready"); - console.log("Done!"); + status = "ok"; }).catch(function(err) { - console.log("Error",err); + log("Error",err); gatt = undefined; + status = "error"; }); } } else { // not on log("setHRMPower off", app); if (gatt) { log("HRM connected - disconnecting"); + status = undefined; try {gatt.disconnect();}catch(e) { log("HRM disconnect error", e); } From d9441b3a40e11f3a57d13fc40dd805d951424c43 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 14:49:51 +0100 Subject: [PATCH 0309/1062] Added vernier respiration app --- apps.json | 15 ++ apps/vernierrespirate/ChangeLog | 1 + apps/vernierrespirate/README.md | 26 +++ apps/vernierrespirate/app-icon.js | 1 + apps/vernierrespirate/app.js | 256 ++++++++++++++++++++++++++++++ apps/vernierrespirate/app.png | Bin 0 -> 1986 bytes 6 files changed, 299 insertions(+) create mode 100644 apps/vernierrespirate/ChangeLog create mode 100644 apps/vernierrespirate/README.md create mode 100644 apps/vernierrespirate/app-icon.js create mode 100644 apps/vernierrespirate/app.js create mode 100644 apps/vernierrespirate/app.png diff --git a/apps.json b/apps.json index 50e53f4f4..8715ede73 100644 --- a/apps.json +++ b/apps.json @@ -3977,5 +3977,20 @@ {"name":"ffcniftya.app.js","url":"app.js"}, {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} ] + }, + { "id": "vernierrespirate", + "name": "Vernier Go Direct Respiration Belt", + "shortName":"Respiration Belt", + "version":"0.01", + "description": "Connects to a Go Direct Respiration Belt and shows respiration rate", + "icon": "app.png", + "tags": "health,bluetooth", + "supports" : ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"vernierrespirate.app.js","url":"app.js"}, + {"name":"vernierrespirate.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"vernierrespirate.json"}] } ] diff --git a/apps/vernierrespirate/ChangeLog b/apps/vernierrespirate/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/vernierrespirate/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/vernierrespirate/README.md b/apps/vernierrespirate/README.md new file mode 100644 index 000000000..54dfc274b --- /dev/null +++ b/apps/vernierrespirate/README.md @@ -0,0 +1,26 @@ +# Vernier Go Direct Respiration Belt + +Connects to a [Go Direct Respiration Belt](https://www.vernier.com/product/go-direct-respiration-belt/) via Bluetooth and shows respiration rate + +![](data:image/png;base64,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) + +## Usage + +In the main menu: + +* `Connect` - connect and start displaying respiration +* `Vib` - Should we vibrate if the breaths per minute (BPM) is above a certain value? + * `No` - don't vibrate + * `Calculated` - vibrate if the app's reading is high. This is based on raw + sensor data and it responds quickly but may not be accurate. + * `Vernier` - vibrate if the Vernier sensor's own reading is high. This is + more accurate but responds very slowly. +* `Connect` - connect and start displaying respiration + +## TODO + +* Logging to a file? + +## Creator + +Gordon Williams diff --git a/apps/vernierrespirate/app-icon.js b/apps/vernierrespirate/app-icon.js new file mode 100644 index 000000000..f687d2a9d --- /dev/null +++ b/apps/vernierrespirate/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///9nou30h/qJf8Ah/wBasK0ALhHcMBqALBgtABYsVqgLBAYILFqtUF4MVqoKEgoLFqALGEYQLFAwILEGAlV6tUlWoitXGAgLC1WqBYsBq+VqwLBAYPVMIUFBYN61Uq1oLBHgQLC1WohWqrwLDiteEIOggQDB2pICivqA4IFBlWq1YLCq/V9WkAoMa1YHBKQVf9XUAoMX1f1KgVVEYIpDEYILBLwIuBC4YFBMAMBLAILFLQILBrdV12UBYMW3VV8tAgt9q+2BYee6t9qEFuoLHroLBqte6wvDy+1ZoILBvdWSoeV9oLD+tfBYYFBBYTEBrq5CgN1aQNQgNVAALdEAAISBAYL1EfgISCAgIKDDAQSEAH4AQ")) diff --git a/apps/vernierrespirate/app.js b/apps/vernierrespirate/app.js new file mode 100644 index 000000000..945b72b77 --- /dev/null +++ b/apps/vernierrespirate/app.js @@ -0,0 +1,256 @@ + +// get settings +var settings = require("Storage").readJSON("vernierrespirate.json",1)||{}; +settings.vibrateBPM = settings.vibrateBPM||27; +// settings.vibrate; // undefined / "calculated" / "vernier" + +function saveSettings() { + require("Storage").writeJSON("vernierrespirate.json", settings); +} + + +g.clear(); +var graphHeight = g.getHeight()-100; +var last = { + time : Date.now(), + x : 0, + y : 24, +}; +var avrValue; +var aboveAvr = false; +var lastBreath; +var lastBreaths = []; +var vibrateInterval; + +function onMsg(txt) { + print(txt); + E.showMessage(txt); +} + +function setVibrate(isOn) { + var wasOn = vibrateInterval!==undefined; + if (isOn == wasOn) return; + + if (isOn) { + vibrateInterval = setInterval(function() { + Bangle.buzz(); + }, 1000); + } else { + clearInterval(vibrateInterval); + vibrateInterval = undefined; + } +} + +function onBreath() { + var t = Date.now(); + if (lastBreath!==undefined) { + // time between breaths + var value = 60000 / (t-lastBreath); + // average of last 3 + while (lastBreaths.length>=3) lastBreaths.shift(); // keep length small + lastBreaths.push(value); + value = E.sum(lastBreaths) / lastBreaths.length; + // draw value + g.reset(); + g.clearRect(0,g.getHeight()-100,g.getWidth(),g.getHeight()-50); + g.setFont("6x8").setFontAlign(0,0); + g.drawString("Calculated measurement", g.getWidth()/2, g.getHeight()-95); + g.setFont("Vector",40).setFontAlign(0,0); + g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-70); + // set vibration IF we're doing it from our calculations + if (settings.vibrate == "calculated") + setVibrate(value > settings.vibrateBPM); + } + lastBreath = t; +} + +function onData(n, value) { + g.reset(); + if (n==2) { + function scale(v) { + return Math.max(graphHeight - (1+v*4),24); + } + if (avrValue==undefined) avrValue=value; + avrValue = avrValue*0.95 + value*0.05; + if (avrValue < 1) avrValue = 1; + if (value > avrValue) { + if (!aboveAvr) onBreath(); + aboveAvr = true; + } else aboveAvr = false; + + var t = Date.now(); + var x = Math.round((t - last.time) / 100) // 10 per second + if (last.x>=g.getWidth()) { + x = 0; + last.x = 0; + last.time = t; + g.clearRect(0,24,g.getWidth(),graphHeight); + } + var y = scale(value); + g.setPixel(x, scale(avrValue), "#f00"); + g.drawLine(last.x, last.y, x, y); + last.x = x; + last.y = y; + } + if (n==4) { + g.clearRect(0,g.getHeight()-50,g.getWidth(),g.getHeight()); + g.setFont("6x8").setFontAlign(0,0); + g.drawString("GoDirect measurement", g.getWidth()/2, g.getHeight()-45); + g.setFont("Vector",40).setFontAlign(0,0); + g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-20); + // set vibration IF we're doing it from our calculations + if (settings.vibrate == "vernier") + setVibrate(value > settings.vibrateBPM); + } + Bangle.setLCDPower(1); // ensure LCD is on +} + +function connect() { + var gatt, service, rx, tx; + var rollingCounter = 0xFF; + + // any button to exit + Bangle.setUI("updown", function() { + setVibrate(false); + Bangle.buzz(); + try { + if (gatt) gatt.disconnect(); + } catch (e) { + } + setTimeout(mainMenu, 1000); + }); + + function sendCommand(subCommand) { + const command = new Uint8Array(4 + subCommand.length); + command.set(new Uint8Array(subCommand), 4); + // Populate the packet header bytes + command[0] = 0x58; // header + command[1] = command.length; + command[2] = --rollingCounter; + command[3] = E.sum(command) & 0xFF; // checksum + return tx.writeValue(command); + } + function firstSetBit(v) { + return v & -v; + } + function handleResponse(dv) { + //print(dv.buffer); + var resType = dv.getUint8(0); + if (resType==0x20) { + // [32, 25, 207, 216, 6, 6, 0, 2, 252, 128, 138, 7, 191, 0, 0, 192, 127, 128, 49, 8, 191, 0, 0, 192, 127]) + // 6 = data type = real + // 6,0 = bit mask for sensors + // 2 = value count + if (dv.getUint8(4)!=6) return; //throw "Not float32 data"; + var sensorIds = dv.getUint16(5, true); + // var count = dv.getUint8(7); doesn't seem right + var offs = 9; + while (sensorIds) { + var value = dv.getFloat32(offs, true); + var s = firstSetBit(sensorIds); + if (isFinite(value)) onData(s,value); + //else print(s,value); + sensorIds &= ~s; + offs += 4; + } + } else { + var cmd = dv.getUint8(4); // cmd + //print("CMD",dv.buffer); + } + } + + onMsg("Searching..."); + NRF.requestDevice({ filters: [{ namePrefix: 'GDX-RB' }] }).then(function(device) { + device.on("gattserverdisconnected", function() { + onMsg("Device disconnected"); + }); + onMsg("Found. Connecting..."); + return device.gatt.connect({minInterval:20, maxInterval:20}); + }).then(function(g) { + gatt = g; + return gatt.getPrimaryService("d91714ef-28b9-4f91-ba16-f0d9a604f112"); + }).then(function(s) { + service = s; + return service.getCharacteristic("f4bf14a6-c7d5-4b6d-8aa8-df1a7c83adcb"); + }).then(function(c) { + tx = c; + return service.getCharacteristic("b41e6675-a329-40e0-aa01-44d2f444babe"); + }).then(function(c) { + rx = c; + rx.on('characteristicvaluechanged', function(event) { + //print("EVT",event.target.value.buffer); + handleResponse(event.target.value); + }); + return rx.startNotifications(); + }).then(function() { + onMsg("Init"); + sendCommand([ // init + 0x1a, 0xa5, 0x4a, 0x06, + 0x49, 0x07, 0x48, 0x08, + 0x47, 0x09, 0x46, 0x0a, + 0x45, 0x0b, 0x44, 0x0c, + 0x43, 0x0d, 0x42, 0x0e, + 0x41, + ]); + /*setTimeout(function() { + print("Set measurement period"); + var us = 100000; // period in us + sendCommand([0x1b, 0xff, 0x00, + us & 255, + (us >> 8) & 255, + (us >> 16) & 255, + (us >> 24) & 255, + 0x00, + 0x00, + 0x00, + 0x00]); + }, 100);*/ + + /* setTimeout(function() { + print("Get sensor info"); + sendCommand([0x51, 0]); // get sensor IDs + // returns [152, 10, 1, 39, 81, 253, 54, 0, 0, 0] + // 54 is the bit mask of available channels + //sendCommand([106, 16]); // get sensor info + }, 2000);*/ + + setTimeout(function() { + onMsg("Start measurements"); + //https://github.com/VernierST/godirect-js/blob/main/src/Device.js#L588 + var channels = 6; // data channels 4 and 2 + sendCommand([ // start measurements + 0x18, 0xff, 0x01, channels, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 + ]); + }, 500); + }).catch(function() { + onMsg("Connect Fail"); + }); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +function mainMenu() { + var vibText = ["No","Calculated","Vernier"]; + var vibValue = ["","calculated","vernier"]; + E.showMenu({"":{title:"Respiration Belt"}, + "< Back" : () => { saveSettings(); load(); }, + "Connect" : () => { saveSettings(); E.showMenu(); connect(); }, + "Vib" : { + value : Math.max(vibValue.indexOf(settings.vibrate),0), + format : v => vibText[v], + min:0,max:2, + onchange : v => { settings.vibrate=vibValue[v]; } + }, + "BPM" : { + value : settings.vibrateBPM, + min:10,max:50, + onchange : v => { settings.vibrateBPM=v; } + } + }); +} + +mainMenu(); diff --git a/apps/vernierrespirate/app.png b/apps/vernierrespirate/app.png new file mode 100644 index 0000000000000000000000000000000000000000..0f6b22af17e76005f58c052a2c72f7e43f90959f GIT binary patch literal 1986 zcmV;z2R-R7Gvns%k)0zo3?cw!x?&4iT7|;ukj9 zYnwP=hZ+o zbVaO?x(w1%1t$kWyY`h{Z#rv&4mJEtDRq#kpC3ip`T`24kSjr63x{^SnysU( z-Y4Ojo!>>&+cW8vT|XZ4H~Py{E+VnD>u4qUKiN725Dqm+Ci^5}qIQ<{G&aqoH)96+ ztLkeJb!;ZRM2sGJw>FVHfTp{fXX8VZ{Cc3KVeL$M(+1E}_3fb2^gjbRF@ws5pv{mt zr5(&5eMR1U)0moxLMu(WSpk7}I+xT3rrT=T01Q2UFR&tU6Jt#cxKrPN(W561fn@3? zwRgwMUz^g<6?|W=Nj@B5Wzg~MosQ#_0Sr~WP^pAh5@mX2F_@Vfm(q1cj-L80@Y5tY z+)W^~cl5}~*Qd&$3YzE@#mO=y`))4V9h!9jj%FMN@{*LROVEP;G+jMZLU4N>3fYyi zdy^=>30Mgcy&Qb@YiMN!>12W?T4+(KI7hdg1G5TXXvyv`0^8D*^Zcmt0<;3JBvg){ zz?Me{Jo6~3C@)!A)ZcQ0eJ|#{^weoVwgKZYKb6G{Tn&Z~ab2_pK~Nduj#%I~4{MtBM$QkO>CJR7^6DSWy9NH2h*&?z%+QcT za7V^bng*6l;GsQYetiwT<%<&zz-H|CXOZDiZ*BO{D;FZ8lej~-qim0s zbszunlmT=usSo71`VC-Vl9HjLFDpY8=cDBX@vW}*KDa{UUstd$TmzCP@pOnzV&kh{ zz2>!b5kvm53Ue7eGwU( z^fu5mR9*nFY^>(10O;w1Cu``{B>-5>R}srbxneC5joo$ z_miK4{Yf-aaw>cN?z;f?CtYayxiN9%Y%7uGHvCVo_qtD}iJ!tXp^Zvk+CGL4bHhr+ zahYg3+oVYl`mqZ0t_r6s;UWXpeO!N@y6yNr%wU+F~^-3vkltEGd9rZb4+ zVs2Q8GBQ2iPEPgAr1TGVyo zv->5U37;<8T@bO{CzX;qg{%`)72f}v0zf~a&WX Date: Thu, 21 Oct 2021 15:44:26 +0100 Subject: [PATCH 0310/1062] Calibrate at start if no info --- apps.json | 2 +- apps/arrow/ChangeLog | 4 +++- apps/arrow/app.js | 55 ++++++++++++++++++++++---------------------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/apps.json b/apps.json index 8715ede73..c03e325ac 100644 --- a/apps.json +++ b/apps.json @@ -3407,7 +3407,7 @@ { "id": "arrow", "name": "Arrow Compass", - "version": "0.04", + "version": "0.05", "description": "Moving arrow compass that points North, shows heading, with tilt correction. Based on jeffmer's Navigation Compass", "icon": "arrow.png", "type": "app", diff --git a/apps/arrow/ChangeLog b/apps/arrow/ChangeLog index 2f1b2b4c4..edd5ccb3d 100644 --- a/apps/arrow/ChangeLog +++ b/apps/arrow/ChangeLog @@ -1,4 +1,6 @@ 0.01: First version 0.02: Moved arrow image load to global scope 0.03: faster drawCompass() function, does not cause buttons to become unresponsive -0.04: removed LCD1.write() as it was keeping LCD on +0.04: removed LED1.write() as it was keeping LCD on +0.05: Turn compass off when screen off + Calibrate at start if no info diff --git a/apps/arrow/app.js b/apps/arrow/app.js index ffa821a09..f1f85e880 100644 --- a/apps/arrow/app.js +++ b/apps/arrow/app.js @@ -1,5 +1,5 @@ -var pal1color = new Uint16Array([0x0000,0xFFC0],0,1); -var pal2color = new Uint16Array([0x0000,0xffff],0,1); +var pal1color = new Uint16Array([g.theme.bg,0xFFC0],0,1); +var pal2color = new Uint16Array([g.theme.bg,g.theme.fg],0,1); var buf1 = Graphics.createArrayBuffer(128,128,1,{msb:true}); var buf2 = Graphics.createArrayBuffer(80,40,1,{msb:true}); var intervalRef; @@ -7,6 +7,7 @@ var bearing=0; // always point north var heading = 0; var oldHeading = 0; var candraw = false; +var isCalibrating = false; var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null; function flip1(x,y) { @@ -29,7 +30,7 @@ function drawCompass(hd) { if (Math.abs(hd - oldHeading) < 2) return 0; hd=hd*Math.PI/180; var p = [0, 1.1071, Math.PI/4, 2.8198, 3.4633, 7*Math.PI/4 , 5.1760]; - + // using polar cordinates, 64,64 is the offset from the 0,0 origin var poly = [ 64+60*Math.sin(hd+p[0]), 64-60*Math.cos(hd+p[0]), @@ -40,16 +41,16 @@ function drawCompass(hd) { 64+28.2843*Math.sin(hd+p[5]), 64-28.2843*Math.cos(hd+p[5]), 64+44.7214*Math.sin(hd+p[6]), 64-44.7214*Math.cos(hd+p[6]) ]; - + buf1.fillPoly(poly); flip1(56, 56); } // stops violent compass swings and wobbles, takes 3ms -function newHeading(m,h){ +function newHeading(m,h){ var s = Math.abs(m - h); var delta = (m>h)?1:-1; - if (s>=180){s=360-s; delta = -delta;} + if (s>=180){s=360-s; delta = -delta;} if (s<2) return h; var hd = h + delta*(1 + Math.round(s/5)); if (hd<0) hd+=360; @@ -76,7 +77,7 @@ function tiltfixread(O,S){ return psi; } -function reading() { +function reading(m) { var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale); heading = newHeading(d,heading); var dir = bearing - heading; @@ -97,18 +98,19 @@ function reading() { function calibrate(){ var max={x:-32000, y:-32000, z:-32000}, min={x:32000, y:32000, z:32000}; - var ref = setInterval(()=>{ - var m = Bangle.getCompass(); + function onMag(m) { max.x = m.x>max.x?m.x:max.x; max.y = m.y>max.y?m.y:max.y; max.z = m.z>max.z?m.z:max.z; min.x = m.x { setTimeout(()=>{ - if(ref) clearInterval(ref); + Bangle.removeListener('mag', onMag); var offset = {x:(max.x+min.x)/2,y:(max.y+min.y)/2,z:(max.z+min.z)/2}; var delta = {x:(max.x-min.x)/2,y:(max.y-min.y)/2,z:(max.z-min.z)/2}; var avg = (delta.x+delta.y+delta.z)/3; @@ -132,6 +134,7 @@ function docalibrate(e,first){ flip1(56,56); calibrate().then((r)=>{ + isCalibrating = false; require("Storage").write("magnav.json",r); Bangle.buzz(); CALIBDATA = r; @@ -146,39 +149,34 @@ function docalibrate(e,first){ if (first === undefined) first = false; - stopdraw(false); + stopdraw(); clearWatch(); + isCalibrating = true; - if (first) + if (first) E.showAlert(msg,title).then(action.bind(null,true)); - else + else E.showPrompt(msg,{title:title,buttons:{"Start":true,"Cancel":false}}).then(action); } function startdraw(){ - if (!Bangle.isCompassOn()) { - Bangle.setCompassPower(1); - } + Bangle.setCompassPower(1, "app"); g.clear(); g.setColor(1,1,1); Bangle.drawWidgets(); candraw = true; if (intervalRef) clearInterval(intervalRef); - intervalRef = setInterval(reading,500); + intervalRef = setInterval(reading,200); } -function stopdraw(powerOffCompass) { - if (powerOffCompass === undefined) { - powerOffCompass = true; - } +function stopdraw() { candraw=false; - if (powerOffCompass) { - Bangle.setCompassPower(0); - } + Bangle.setCompassPower(0, "app"); if (intervalRef) { clearInterval(intervalRef); + intervalRef = undefined; } } @@ -189,6 +187,7 @@ function setButtons(){ } Bangle.on('lcdPower',function(on) { + if (isCalibrating) return; if (on) { startdraw(); } else { @@ -196,8 +195,8 @@ Bangle.on('lcdPower',function(on) { } }); -Bangle.on('kill',()=>{Bangle.setCompassPower(0);}); - Bangle.loadWidgets(); -startdraw(); setButtons(); + +Bangle.setLCDPower(1); +if (CALIBDATA) startdraw(); else docalibrate({},true); From 7ae044e14b3049f3e131b5e93ffeb5faada65980 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 21 Oct 2021 17:18:55 +0100 Subject: [PATCH 0311/1062] updated image --- apps/gpstime/gpstime-icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/gpstime/gpstime-icon.js b/apps/gpstime/gpstime-icon.js index 665c8d5f6..99998c6c4 100644 --- a/apps/gpstime/gpstime-icon.js +++ b/apps/gpstime/gpstime-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwghC/AH8A1QWVhWq0AuVAAIuVAAIwT1WinQwTFwMzmQwTCYMjlUqGCIuBlWi0UzC6JdBIoMjC4UDmAuOkYXBPAWgmczLp2ilUiVAUDC4IwLFwIUBLoJ2BFwQwM1WjCgJ1DFwQwLFwJ1B0SQCkQWDGBQXBCgK9BDgKQBAAgwJOwUzRgIDBC54wCkZdGPBwACRgguDBIIwLFxEJBQIwLFxGaBYQwKFxQwLgAWGmQuBcAQwJC48ifYYwJgUidgsyC4L7DGBIXBdohnBCgL7BcYIXIGAqMCIoL7DL5IwERgIUBLoL7BO5QXBGAK7DkWiOxQXGFwOjFoUyFxZhDgBdCCgJ1CCxYxCgBABkcqOwIuNGAQXC0S9BLpgAFXoIwBmYuPAAYwCLp4wHFyYwDFyYwDFygwCCyoA/AFQA=")) +require("heatshrink").decompress(atob("mEw4UA////G161hyd8Jf4ALlQLK1WABREC1WgBZEK32oFxPW1QuJ7QwIFwOqvQLHhW31NaBY8qy2rtUFoAuG3W61EVqALF1+qr2gqtUHQu11dawNVqo6F22q9XFBYIwEhWqz2r6oLBGAheBqwuBBYx2CFwQLGlWqgoLCMAsKLoILChR6EgQuDqkqYYsBFweqYYoLDoWnYYoLD/WVYYv8FwXqPoIwEn52BqGrPoILEh/1FwOl9SsBBYcD/pdB2uq/QvEh/8LoOu1xHFh8/gGp9WWL4oMBgWltXeO4owBgWt1ReFYYh2GYYmXEQzDD3wiHegYKIGAJRGAAguJAH4AC")) From d64f120cbd7ce98c6b20a76f8eac185c2ebe3dc1 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 08:16:59 +1300 Subject: [PATCH 0312/1062] Update app.js --- apps/speedalt2/app.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 886c6ab70..a4f0216b5 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '1.01'; +var v = '1.01b'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -440,7 +440,6 @@ function onGPS(fix) { unit:cfg.spd_unit, sats:lf.satellites, age:age, - // fix:lf.fix, max:true, wp:false, sat:true @@ -451,7 +450,6 @@ function onGPS(fix) { unit:cfg.spd_unit, sats:lf.satellites, age:age, -// fix:lf.fix, max:false, wp:false, sat:true @@ -466,7 +464,6 @@ function onGPS(fix) { unit:cfg.alt_unit, sats:lf.satellites, age:age, - // fix:lf.fix, max:true, wp:false, sat:true @@ -477,7 +474,6 @@ function onGPS(fix) { unit:cfg.alt_unit, sats:lf.satellites, age:age, -// fix:lf.fix, max:false, wp:false, sat:true @@ -491,7 +487,6 @@ function onGPS(fix) { unit:cfg.dist_unit, sats:lf.satellites, age:age, -// fix:lf.fix, max:false, wp:true, sat:true @@ -562,7 +557,7 @@ function setButtons(){ } else { Bangle.setLCDTimeout(0); - Bangle.setLCDPower(1); +// Bangle.setLCDPower(1); LED1.set(); } }, BTN2, {repeat:true,edge:"falling"}); From 5f91cf37cd7e8276fdce26ae450b4d29a2f7dbfd Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 21 Oct 2021 20:22:46 +0100 Subject: [PATCH 0313/1062] Widbatpc: - revert hide change --- apps/widbatpc/ChangeLog | 1 - apps/widbatpc/settings.js | 11 ++--------- apps/widbatpc/widget.js | 3 +-- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/apps/widbatpc/ChangeLog b/apps/widbatpc/ChangeLog index b8e594fc4..09e4fabf4 100644 --- a/apps/widbatpc/ChangeLog +++ b/apps/widbatpc/ChangeLog @@ -10,4 +10,3 @@ 0.11: Don't overwrite existing settings on app update 0.12: Fixed for Bangle 2 0.13: Fillbar setting added, see README -0.14: Added setting to completely hide the widget diff --git a/apps/widbatpc/settings.js b/apps/widbatpc/settings.js index 55588238d..7dceaa2a8 100644 --- a/apps/widbatpc/settings.js +++ b/apps/widbatpc/settings.js @@ -12,8 +12,7 @@ 'percentage': true, 'fillbar': false, 'charger': true, - 'hideifmorethan': 100, - 'hidewidget': false, + 'hideifmorethan': 100 } // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings @@ -33,7 +32,6 @@ } const onOffFormat = b => (b ? 'On' : 'Off') - const yesNoFormat = b => (b ? 'Yes' : 'No') const menu = { '': { 'title': 'Battery Widget' }, '< Back': back, @@ -69,12 +67,7 @@ step: 10, format: x => x+"%", onchange: save('hideifmorethan'), - }, - 'Hide Widget': { - value: s.hidewidget, - format: yesNoFormat, - onchange: save('hidewidget'), - }, + } } E.showMenu(menu) }) diff --git a/apps/widbatpc/widget.js b/apps/widbatpc/widget.js index 574c22f6c..f6477c9bb 100644 --- a/apps/widbatpc/widget.js +++ b/apps/widbatpc/widget.js @@ -28,7 +28,7 @@ 'color': 'By Level', 'percentage': true, 'charger': true, - 'hideifmorethan': 100, + 'hideifmorethan': 100 }; Object.keys(DEFAULTS).forEach(k=>{ if (settings[k]===undefined) settings[k]=DEFAULTS[k] @@ -76,7 +76,6 @@ function draw() { // if hidden, don't draw if (!WIDGETS["batpc"].width) return; - if (setting('hidewidget')) return; // else... var s = 39; var x = this.x, y = this.y; From d1d1cf10ae38c7290ebe77adf73fb0341d09a566 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 21 Oct 2021 20:23:42 +0100 Subject: [PATCH 0314/1062] Widbatpc: - revert hide change --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index e3e1de7b8..03421020d 100644 --- a/apps.json +++ b/apps.json @@ -743,7 +743,7 @@ "id": "widbatpc", "name": "Battery Level Widget (with percentage)", "shortName": "Battery Widget", - "version": "0.14", + "version": "0.13", "description": "Show the current battery level and charging status in the top right of the clock, with charge percentage", "icon": "widget.png", "type": "widget", From 633552e0a3ad455b01e784fb4de8c20b6413a05f Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 08:30:07 +1300 Subject: [PATCH 0315/1062] Update app.js --- apps/speedalt2/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index a4f0216b5..b21e8120e 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '1.01b'; +var v = '1.02'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { From cc4f93d246a0a67e2d3f5f28b2ab94e9c91f5b4b Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 08:30:43 +1300 Subject: [PATCH 0316/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 153f026da..07bf11c6d 100644 --- a/apps.json +++ b/apps.json @@ -2901,7 +2901,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"1.01", + "version":"1.02", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From 8667cf1b04ec470b34ed6ffbe69a5f8c7a1a4781 Mon Sep 17 00:00:00 2001 From: peeweek <4037271+peeweek@users.noreply.github.com> Date: Thu, 21 Oct 2021 21:34:04 +0200 Subject: [PATCH 0317/1062] Simplified (Removed settings) --- apps.json | 1 - apps/hcclock/ChangeLog | 2 +- apps/hcclock/hcclock.settings.js | 33 -------------------------------- 3 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 apps/hcclock/hcclock.settings.js diff --git a/apps.json b/apps.json index a9a6d736d..ee81805a8 100644 --- a/apps.json +++ b/apps.json @@ -3397,7 +3397,6 @@ "allow_emulator":true, "storage": [ {"name":"hcclock.app.js","url":"hcclock.app.js"}, - {"name":"hcclock.settings.js","url":"hcclock.settings.js"}, {"name":"hcclock.img","url":"hcclock-icon.js","evaluate":true} ] }, diff --git a/apps/hcclock/ChangeLog b/apps/hcclock/ChangeLog index 343be7f07..aaa55d01a 100644 --- a/apps/hcclock/ChangeLog +++ b/apps/hcclock/ChangeLog @@ -1,2 +1,2 @@ 0.01: base code -0.02: added settings for color schemes \ No newline at end of file +0.02: saved settings when switching color scheme \ No newline at end of file diff --git a/apps/hcclock/hcclock.settings.js b/apps/hcclock/hcclock.settings.js deleted file mode 100644 index 92d5f47e5..000000000 --- a/apps/hcclock/hcclock.settings.js +++ /dev/null @@ -1,33 +0,0 @@ -(function(back) { - - function getColorScheme() - { - let settings = require('Storage').readJSON("hcclock.json", true) || {}; - if (!("scheme" in settings)) { - settings.scheme = 0; - } - return settings.scheme; - } - function setColorScheme(value) - { - value = value + 1 % 2; - let settings = require('Storage').readJSON("hcclock.json", true) || {}; - settings.scheme = value? 1 : 0; - require('Storage').writeJSON('hcclock.json', settings); - } - function setIcon(visible) { - updateSetting('showIcon', visible); - - } - var mainmenu = { - "" : { "title" : "Hi-Contrast Clock" }, - "Color Scheme" : { - value: getColorScheme, - format: v => v == 0?"White":"Black", - onchange: setColorScheme - }, - "< Back" : back, - }; - E.showMenu(mainmenu); - }) - \ No newline at end of file From e0b7a8efef542f1e200d00204aaa64b627b947ac Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 21 Oct 2021 20:41:04 +0100 Subject: [PATCH 0319/1062] Widbatpc: - revert hide change --- apps/widbatpc/settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/widbatpc/settings.js b/apps/widbatpc/settings.js index 7dceaa2a8..b7a5db9e6 100644 --- a/apps/widbatpc/settings.js +++ b/apps/widbatpc/settings.js @@ -12,7 +12,7 @@ 'percentage': true, 'fillbar': false, 'charger': true, - 'hideifmorethan': 100 + 'hideifmorethan': 100, } // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings @@ -31,7 +31,7 @@ } } - const onOffFormat = b => (b ? 'On' : 'Off') + const onOffFormat = b => (b ? 'on' : 'off') const menu = { '': { 'title': 'Battery Widget' }, '< Back': back, @@ -67,7 +67,7 @@ step: 10, format: x => x+"%", onchange: save('hideifmorethan'), - } + }, } E.showMenu(menu) }) From f6a6f03f70c55d9dfa58c68b4799a904b19aa4e3 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 21 Oct 2021 20:42:39 +0100 Subject: [PATCH 0320/1062] Widbatpc: - revert hide change --- apps/widbatpc/widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/widbatpc/widget.js b/apps/widbatpc/widget.js index f6477c9bb..caecf8ae4 100644 --- a/apps/widbatpc/widget.js +++ b/apps/widbatpc/widget.js @@ -28,7 +28,7 @@ 'color': 'By Level', 'percentage': true, 'charger': true, - 'hideifmorethan': 100 + 'hideifmorethan': 100, }; Object.keys(DEFAULTS).forEach(k=>{ if (settings[k]===undefined) settings[k]=DEFAULTS[k] From 0e52707ac5de98681910a29e2699e8107b8db8fd Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:48:36 +1300 Subject: [PATCH 0321/1062] Update app.js --- apps/speedalt2/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index b21e8120e..4fbe458db 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -588,9 +588,9 @@ function updateClock() { function startDraw(){ canDraw=true; - setLpMode('SuperE'); // off g.clear(); Bangle.drawWidgets(); + setLpMode('SuperE'); // off onGPS(lf); // draw app screen } From 78cc981126b7190d26e42bef7f48e65721034999 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:50:30 +1300 Subject: [PATCH 0322/1062] Create app.js --- apps/speedclock/app.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/speedclock/app.js diff --git a/apps/speedclock/app.js b/apps/speedclock/app.js new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/speedclock/app.js @@ -0,0 +1 @@ + From abab41825589f6cb368f6f2020b49925da7883ad Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:52:17 +1300 Subject: [PATCH 0323/1062] Update app.js --- apps/speedclock/app.js | 317 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 317 insertions(+) diff --git a/apps/speedclock/app.js b/apps/speedclock/app.js index 8b1378917..4c74ce1be 100644 --- a/apps/speedclock/app.js +++ b/apps/speedclock/app.js @@ -1 +1,318 @@ +// Morphing Clock + +// Modifies original Morphing Clock to make seconds and date more readable, and adds a simple stopwatch +// Icon by https://icons8.com +var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; +var locale = require("locale"); +var CHARW = 28; // how tall are digits? +var CHARP = 2; // how chunky are digits? +var Y = 50; // start height +// Offscreen buffer +var buf = Graphics.createArrayBuffer(CHARW+CHARP*2,CHARW*2 + CHARP*2,1,{msb:true}); +var bufimg = {width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer}; +// The last time that we displayed +var lastTime = "-----"; +// If animating, this is the interval's id +var animInterval; +var timeInterval; +// Variables for the stopwatch +var counter = -1; // Counts seconds +var oldDate = new Date(2020,0,1); // Initialize to a past date +var swInterval; // The interval's id +var B3 = 0; // Flag to track BTN3's current function +var w1; // watch id for BTN1 +var w3; // watch id for BTN3 +/* Get array of lines from digit d to d+1. + n is the amount (0..1) + maxFive is true is this digit only counts 0..5 */ +const DIGITS = { + " ":n=>[], + "0":n=>[ + [n,0,1,0], + [1,0,1,1], + [1,1,1,2], + [n,2,1,2], + [n,1,n,2], + [n,0,n,1]], + "1":n=>[ + [1-n,0,1,0], + [1,0,1,1], + [1-n,1,1,1], + [1-n,1,1-n,2], + [1-n,2,1,2]], + "2":n=>[ + [0,0,1,0], + [1,0,1,1], + [0,1,1,1], + [0,1+n,0,2], + [1,2-n,1,2], + [0,2,1,2]], + "3":n=>[ + [0,0,1-n,0], + [0,0,0,n], + [1,0,1,1], + [0,1,1,1], + [1,1,1,2], + [n,2,1,2]], + "4":n=>[ + [0,0,0,1], + [1,0,1-n,0], + [1,0,1,1-n], + [0,1,1,1], + [1,1,1,2], + [1-n,2,1,2]], + "5to0": n=>[ // 5 -> 0 + [0,0,0,1], + [0,0,1,0], + [n,1,1,1], + [1,1,1,2], + [0,2,1,2], + [0,2,0,2], + [1,1-n,1,1], + [0,1,0,1+n]], + "5to6": n=>[ // 5 -> 6 + [0,0,0,1], + [0,0,1,0], + [0,1,1,1], + [1,1,1,2], + [0,2,1,2], + [0,2-n,0,2]], + "6":n=>[ + [0,0,0,1-n], + [0,0,1,0], + [n,1,1,1], + [1,1-n,1,1], + [1,1,1,2], + [n,2,1,2], + [0,1-n,0,2-2*n]], + "7":n=>[ + [0,0,0,n], + [0,0,1,0], + [1,0,1,1], + [1-n,1,1,1], + [1,1,1,2], + [1-n,2,1,2], + [1-n,1,1-n,2]], + "8":n=>[ + [0,0,0,1], + [0,0,1,0], + [1,0,1,1], + [0,1,1,1], + [1,1,1,2], + [0,2,1,2], + [0,1,0,2-n]], + "9":n=>[ + [0,0,0,1], + [0,0,1,0], + [1,0,1,1], + [0,1,1-n,1], + [0,1,0,1+n], + [1,1,1,2], + [0,2,1,2]], + ":":n=>[ + [0.4,0.4,0.6,0.4], + [0.6,0.4,0.6,0.6], + [0.6,0.6,0.4,0.6], + [0.4,0.4,0.4,0.6], + [0.4,1.4,0.6,1.4], + [0.6,1.4,0.6,1.6], + [0.6,1.6,0.4,1.6], + [0.4,1.4,0.4,1.6]] +}; + +/* Draw a transition between lastText and thisText. + 'n' is the amount - 0..1 */ +function drawDigits(lastText,thisText,n) { + "ram" + const p = CHARP; // padding around digits + const s = CHARW; // character size + var x = 16; // x offset + g.reset(); + for (var i=0;i{ + if (c[0]!=c[2]) // horiz + buf.fillRect(p+c[0]*s,c[1]*s,p+c[2]*s,2*p+c[3]*s); + else if (c[1]!=c[3]) // vert + buf.fillRect(c[0]*s,p+c[1]*s,2*p+c[2]*s,p+c[3]*s); + }); + g.drawImage(bufimg,x,Y); + } + if (thisCh==":") x-=4; + x+=s+p+7; + } +} +function drawDate() { + var x = (CHARW + CHARP + 8)*5; + var y = Y + 2*CHARW + CHARP; + var d = new Date(); + // meridian + g.reset(); + g.setFont("6x8",2); + g.setFontAlign(-1,-1); + if (is12Hour) g.drawString((d.getHours() < 12) ? "AM" : "PM", x+8, Y+0, true); + // date + g.setFont("Vector16"); + g.setFontAlign(0,-1); + // Only draw the date if it has changed: + if ((d.getDate()!=oldDate.getDate())||(d.getMonth()!=oldDate.getMonth())||(d.getFullYear()!=oldDate.getFullYear())) { + var date = locale.date(d,false); + g.clearRect(1,y+8,g.getWidth(),y+24); + g.drawString(date, g.getWidth()/2, y+8, true); + oldDate = d; + } +} + +function drawSeconds() { + var x = (CHARW + CHARP + 8)*5; + var y = Y + 2*CHARW + CHARP; + var d = new Date(); + // seconds + g.reset(); + g.setFont("6x8",2); + g.setFontAlign(-1,-1); + g.drawString(("0"+d.getSeconds()).substr(-2), x+8, y-12, true); +} + +/* Show the current time, and animate if needed */ +function showTime() { + if (animInterval) return; // in animation - quit + var d = new Date(); + var hours = d.getHours(); + if (is12Hour) hours = ((hours + 11) % 12) + 1; + var t = (" "+hours).substr(-2)+":"+ + ("0"+d.getMinutes()).substr(-2); + var l = lastTime; + // same - don't animate + if (t==l || l=="-----") { + drawDigits(l,t,0); + drawDate(); + drawSeconds(); + lastTime = t; + return; + } + var n = 0; + animInterval = setInterval(function() { + n += 1/10; + if (n>=1) { + n=1; + clearInterval(animInterval); + animInterval = undefined; + } + drawDigits(l,t,n); + drawSeconds(); + }, 20); + lastTime = t; +} + +function stopWatch() { + + counter++; + + var hrs = Math.floor(counter/3600); + var mins = Math.floor((counter-hrs*3600)/60); + var secs = counter - mins*60 - hrs*3600; + + // When starting the stopwatch: + if (B3) { + // Set BTN3 to stop the stopwatch and bind itself to restart it: + w3=setWatch(() => {clearInterval(swInterval); + swInterval=undefined; + if (w3) {clearWatch(w3);w3=undefined;} + setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, + BTN3, {repeat:false,edge:"falling"}); + B3 = 1;}, + BTN3, {repeat:false,edge:"falling"}); + B3 = 0; // BTN3 is bound to stop the stopwatch + } + + // Bind BTN1 to call the reset function: + if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); + + // Draw elapsed time: + g.reset(); + g.setColor(0.0,0.5,1.0).setFontAlign(0,-1).setFont("Vector24"); + g.clearRect(1,180,g.getWidth(),210); + if (hrs>0) { + g.drawString(("0"+parseInt(hrs)).substr(-2), g.getWidth()/2 - 72, 180, true); + g.drawString( ":", g.getWidth()/2 - 48, 180, true); + } + g.drawString(("0"+parseInt(mins)).substr(-2), g.getWidth()/2 - 24, 180, true); + g.drawString( ":", g.getWidth()/2, 180, true); + g.drawString(("0"+parseInt(secs)).substr(-2), g.getWidth()/2 + 24, 180, true); + +} + +function resetStopWatch() { + + // Stop the interval if necessary: + if (swInterval) { + clearInterval(swInterval); + swInterval=undefined; + } + + // Clear the stopwatch: + g.clearRect(1,180,g.getWidth(),210); + + // Reset the counter: + counter = -1; + + // Set BTN3 to start the stopwatch again: + if (!B3) { + // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: + if (w3) {clearWatch(w3);w3=undefined;} + // Set BTN3 to start the watch again: + setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); + B3 = 1; // BTN3 is bound to start the stopwatch + } + + // Reset watch on BTN1: + if (w1) {clearWatch(w1);w1=undefined;} +} + + +Bangle.on('lcdPower',function(on) { + if (animInterval) { + clearInterval(animInterval); + animInterval = undefined; + } + if (timeInterval) { + clearInterval(timeInterval); + timeInterval = undefined; + } + if (on) { + showTime(); + timeInterval = setInterval(showTime, 1000); + } else { + lastTime = "-----"; + } +}); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +// Update time once a second +timeInterval = setInterval(showTime, 1000); +showTime(); + +// Show launcher when button pressed +Bangle.setUI("clock"); + +// Start stopwatch when BTN3 is pressed +setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); +B3 = 1; // BTN3 is bound to start the stopwatch From 551f6e38a7e6779a0c2ad7fa2ddac76e509c1993 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:53:37 +1300 Subject: [PATCH 0324/1062] Create ChangeLog --- apps/speedclock/ChangeLog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/speedclock/ChangeLog diff --git a/apps/speedclock/ChangeLog b/apps/speedclock/ChangeLog new file mode 100644 index 000000000..c31405e08 --- /dev/null +++ b/apps/speedclock/ChangeLog @@ -0,0 +1 @@ +0.01: Created app From 4e28f9f3e7f59d7ebc21fc64b97c6f7691d99cfe Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:54:42 +1300 Subject: [PATCH 0325/1062] Create README.md --- apps/speedclock/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 apps/speedclock/README.md diff --git a/apps/speedclock/README.md b/apps/speedclock/README.md new file mode 100644 index 000000000..b4a3c83a7 --- /dev/null +++ b/apps/speedclock/README.md @@ -0,0 +1,21 @@ +# Morphing Clock Plus + +Based on Morphing Clock with more readable seconds and date, and an additional simple stopwatch. + +![](Screenshot.JPG) + +## Usage + +In addition to the Morphing Clock, a simple stopwatch can be started in the lower part of the display. + +BTN3 starts and stops the stopwatch. + +BTN1 resets/clears the stopwatch. + +## Requests + +Please leave bug reports and requests by raising an issue [here](https://github.com/skauertz/BangleApps). + +## Creator + +Sebastian Kauertz https://github.com/skauertz From 462537385a5afc0ec1851a02077b3ee5704976e0 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:55:51 +1300 Subject: [PATCH 0326/1062] Create icon.js --- apps/speedclock/icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/speedclock/icon.js diff --git a/apps/speedclock/icon.js b/apps/speedclock/icon.js new file mode 100644 index 000000000..41a59f503 --- /dev/null +++ b/apps/speedclock/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEogA/AFGIAAQVVDKQWHDB1IC5OECx8z///mYYOBoWDCoIADnBJLFwQWGDAgwIEYU/CQXwh4EC+YwKBIOPFQYXE//4C5BGCIQgXF/5IILo4XGMIQXHLoYXIMIRGMC45IHC4KkGC45IBC4yNEC5KRBC7h2HC5B4GC5EggQXOBwvygEAl6QHC4sikRGEhGAJAgNBC75HIgZHNO48AgIJER54xCiYXKa5AxCGAjvPGA4XIwYXHbQs4C46QGGAbZDB4IXEPBQAEOwwXDJBJGEC4xILIxQwDSJCNDFwwXDMIh0ELoQXIJARhDC4hdCIw4wEDAQXDCwQuIGAgABmYXBmYHDFxIYGAAoWLJIgAGCxgYJCxwZGCqIA/AC4A=")) From 04359031900f3bbc3b0b7fae993714f24ac7b0ba Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 09:57:17 +1300 Subject: [PATCH 0327/1062] Add files via upload --- apps/speedclock/app.png | Bin 0 -> 1639 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/speedclock/app.png diff --git a/apps/speedclock/app.png b/apps/speedclock/app.png new file mode 100644 index 0000000000000000000000000000000000000000..93d8e57dcbfaf905321fffcf06df042aa49d7dc8 GIT binary patch literal 1639 zcmV-t2AKJYP)@pC8}P=li+m^Env72)@5?yIePa+S>ZbmYsjOa=2m` z!{OB3)^@Gi>3mz!4w$0Hsk)+sbUjipG!&a5|mi z0)`wD_))?j-{$=p(Hmdb{pQ4>#*6Vi6tc_Zs;~=3feFd%R;{|DPtl#K#zRAi7Y`X1 z;B-32+0egXk^BI1gjay&Ter42GL0Dz85MB$^yxyo$U$Iys+>m`&vy%a=qU``n@P+> zNP8DjRdIK?-TImuq^&ql&*I#FWmi3gE><5sB1FaOuOpr#vV?n<_ z(dFmE1qqjs%jGHp7J&{q>2Qby*D7qRpED;gC=2aCEBXiDExut%@3&{)$jNhX)r2YG z^dnNg&zDjGMkx7$Y$pHGo;N24gYr|qS+FpfUBG`~4S=I0`nS$#h_+3*gamysqb!pk zXv%7YEpd4d5ubDqMVujc>)h5J>qvFn-dc4qzLwO;v4OF4uHK zDiy5+ZXrpN_sZ@=#Xj{d&k>z5AX5;YnD;7bECtO;8eHUgNwAw_bgd#HLExkQ@!Xj+ zXB>X7_a+mwQjkhu24U}4pZZo9%HcI2rhoVL=_y*H6IEB8WWWn~`V z-Tpy)d;4g+$ZY~Y$B;^am0~yN4W?9AbqDCW6kH$2X1iauedJGMVA;$qJ+`S27!^;a zLFHfD)9b&=?EzjY{WpaYdh*?c4qo{24Fn`I6~AH7lb74K>t{82$^4}<13f@#JcasV z&KF6MZz%O8J%h>nRCsynRJse|UsOmac2ZauAatVf3o!C+e}8AsYqKqV^h0Z_b|Kt1 z@O_l9{PWVHU!u&4Ymd6Dd|G#zCMOdsSK06%Q^1oACnNf8o85N^@B&=+bs3j^T{fWdc+A|x(cEZ$IG!5wbnEk_ zzpY`zq1m!&pEnc#(LQ5#p(i)~!vyvkhHU0Pfl~n8(?8u^c&_xWf^(r)p||8Ly(MS& zk3BghbW*$AKFu`sF)m((MDVTj>G)q)J0s$u#}x4D?$7k|?)Yu_xT{0E#ii6gwD`J+ zoMU#ODHE@txF{F*m*+X}3AOBn4m;z^3mIFQ4{*u#;fR@m_fLG4-4jffF?;5ih@6Mz lPrm;rMhY0g2u5&e@jt$gS?$`E2( Date: Fri, 22 Oct 2021 10:00:04 +1300 Subject: [PATCH 0328/1062] Update and rename icon.js to app-icon.js --- apps/speedclock/app-icon.js | 1 + apps/speedclock/icon.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 apps/speedclock/app-icon.js delete mode 100644 apps/speedclock/icon.js diff --git a/apps/speedclock/app-icon.js b/apps/speedclock/app-icon.js new file mode 100644 index 000000000..f4f24a18b --- /dev/null +++ b/apps/speedclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AE+sFtoABF12swItsF9QuFR4IwmFwwvnFw4vCGEYuIF4JgjFxIvkFxQvCGBfOAAQvqFwYwRFxYvDGBIvUFxgv/F6IuNF4n+0nB4TvXFxwvF4XBAALlPF7ZfBGC4uPF4rABGAYAGTQwvad4YwKFzYvIGBQvfFwgAE3Qvt4IvEFzgvCLxO7Lx7vULzIzTFwIvgGZheFRAiNRGSQvpGYouesYAGmQAKq3CE4PIC4wviq2eFwPCroveCRSGEC6Qv0DAwRLcoouWC4VdVYQXkr1eAgVdAoIABroNEB4gHHC5QvHwQSDAAOCA74vH1uICQIABxGtA74vIAEwv/F/4vXAH4A/AHY")) diff --git a/apps/speedclock/icon.js b/apps/speedclock/icon.js deleted file mode 100644 index 41a59f503..000000000 --- a/apps/speedclock/icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("mEwwkEogA/AFGIAAQVVDKQWHDB1IC5OECx8z///mYYOBoWDCoIADnBJLFwQWGDAgwIEYU/CQXwh4EC+YwKBIOPFQYXE//4C5BGCIQgXF/5IILo4XGMIQXHLoYXIMIRGMC45IHC4KkGC45IBC4yNEC5KRBC7h2HC5B4GC5EggQXOBwvygEAl6QHC4sikRGEhGAJAgNBC75HIgZHNO48AgIJER54xCiYXKa5AxCGAjvPGA4XIwYXHbQs4C46QGGAbZDB4IXEPBQAEOwwXDJBJGEC4xILIxQwDSJCNDFwwXDMIh0ELoQXIJARhDC4hdCIw4wEDAQXDCwQuIGAgABmYXBmYHDFxIYGAAoWLJIgAGCxgYJCxwZGCqIA/AC4A=")) From e7f4edbd3288408f7dc4d31149aaa1307598c690 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 21 Oct 2021 22:33:13 +0100 Subject: [PATCH 0329/1062] Added basic README and screenshots for a number of Apps --- apps/aclock/README.md | 4 ++++ apps/aclock/screenshot_analog.png | Bin 0 -> 3069 bytes apps/boldclk/README.md | 4 ++++ apps/boldclk/screenshot_bold.png | Bin 0 -> 3563 bytes apps/clock2x3/README.md | 4 ++++ apps/clock2x3/screenshot_pixel.png | Bin 0 -> 1926 bytes apps/ffcniftya/README.md | 4 ++++ apps/ffcniftya/screenshot_nifty.png | Bin 0 -> 3487 bytes apps/flappy/README.md | 5 +++++ apps/flappy/screenshot1_flappy.png | Bin 0 -> 2509 bytes apps/flappy/screenshot2_flappy.png | Bin 0 -> 2805 bytes apps/floralclk/README.md | 4 ++++ apps/floralclk/screenshot_floral.png | Bin 0 -> 6073 bytes apps/s7clk/README.md | 4 ++++ apps/s7clk/screenshot_s7segment.png | Bin 0 -> 2144 bytes apps/score/README.md | 3 +++ apps/score/screenshot_score.png | Bin 0 -> 1796 bytes apps/trex/README.md | 4 ++++ apps/trex/screenshot_trex.png | Bin 0 -> 668 bytes apps/waveclk/README.md | 4 ++++ 20 files changed, 40 insertions(+) create mode 100644 apps/aclock/README.md create mode 100644 apps/aclock/screenshot_analog.png create mode 100644 apps/boldclk/README.md create mode 100644 apps/boldclk/screenshot_bold.png create mode 100644 apps/clock2x3/README.md create mode 100644 apps/clock2x3/screenshot_pixel.png create mode 100644 apps/ffcniftya/README.md create mode 100644 apps/ffcniftya/screenshot_nifty.png create mode 100644 apps/flappy/README.md create mode 100644 apps/flappy/screenshot1_flappy.png create mode 100644 apps/flappy/screenshot2_flappy.png create mode 100644 apps/floralclk/README.md create mode 100644 apps/floralclk/screenshot_floral.png create mode 100644 apps/s7clk/README.md create mode 100644 apps/s7clk/screenshot_s7segment.png create mode 100644 apps/score/screenshot_score.png create mode 100644 apps/trex/README.md create mode 100644 apps/trex/screenshot_trex.png create mode 100644 apps/waveclk/README.md diff --git a/apps/aclock/README.md b/apps/aclock/README.md new file mode 100644 index 000000000..7d31cdae2 --- /dev/null +++ b/apps/aclock/README.md @@ -0,0 +1,4 @@ +# Analogue Clock + +![](screenshot_analog.png) + diff --git a/apps/aclock/screenshot_analog.png b/apps/aclock/screenshot_analog.png new file mode 100644 index 0000000000000000000000000000000000000000..f0e84b4282a99cd420fd84f1bd07c1ce2bb43a04 GIT binary patch literal 3069 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 LNkvXXu0mjfYyh|2 literal 0 HcmV?d00001 diff --git a/apps/boldclk/README.md b/apps/boldclk/README.md new file mode 100644 index 000000000..0e7865b99 --- /dev/null +++ b/apps/boldclk/README.md @@ -0,0 +1,4 @@ +# Bold Clock + +![](screenshot_bold.png) + diff --git a/apps/boldclk/screenshot_bold.png b/apps/boldclk/screenshot_bold.png new file mode 100644 index 0000000000000000000000000000000000000000..4024fca40e6014286442a37fb3d5518ad9747380 GIT binary patch literal 3563 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�Qbsi zcX6$Zh;(4hP5bL`E8nXFOsNT9yVlC6f+|Jb|&sSw6yY0XX7liF55UX3pYMbATmZGd%-={jJOZu{pq$OWppie69u2 z0m=p73*uR>Ta}6Yo8aZ6kq^B-U~g4&19;?~ESG`&_{6R-3Q%NqD8SLj4-$v_9KDs= zwc3(7$blE)-zSD`&3r3*i6i zagpoKD`1brEjVf0$mO$`B|j+ z(GeJJqS<8`U8KD>AO+g=mpJN=qfkWmNFA;@G5}14m>Uc{P7f};Z(B3vz%v6-iorcP zC1RmISpc3ve1VAcM7Cymz6HP(u?ygzNKy2l=$Zx8(K^xtOc9ylEDuLyioldqf}*X5 zZYy&Xz$@xCeRl(Rw~|@vG8*D&R6e6bUsQTSb5oyZ0JtrSarDVrX_(%PcDclLcOT(} zh37FD3Gk?@KmnM#K{K{XpF(ge6K&ayQZCYROh$kyxv-Y~5#wwPFv{Uj^wmx+yZ~;~ zgq{x68hO_GE_E|(5Ae)+DEgx3R1vRsmH?x*Qa9_}rrqIv`+nyf7y&kOI&%1ZV^AHf z1u!MT_AXoqa-`Re&yNKd0g9Y0=}!TebJ;>^0D8V-Re`&Sjs>_)Es(P{p05Bb$zv(s zj8IPxE;w1*9}VzGWE5ScFnI&~gb0f^N&!6hPFIpYO8~w@h|(I$#zGE1?Rf#L0PyS! zuimYnng>^kfVbNt0Y`%T`f5HefIDiUqudJMN)h;I7r=bYhCL`b lu;O$z)afrW%ERX?@E3q@&z3pg2j~C*002ovPDHLkV1kctqHX{H literal 0 HcmV?d00001 diff --git a/apps/clock2x3/README.md b/apps/clock2x3/README.md new file mode 100644 index 000000000..0b5d25f9d --- /dev/null +++ b/apps/clock2x3/README.md @@ -0,0 +1,4 @@ +# 2x3 Pixel Clock + +![](screenshot_pixel.png) + diff --git a/apps/clock2x3/screenshot_pixel.png b/apps/clock2x3/screenshot_pixel.png new file mode 100644 index 0000000000000000000000000000000000000000..4b09f06a1386602290e86ac4d2a42c2dff285b02 GIT binary patch literal 1926 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 L+?}f(34i+&w))`% literal 0 HcmV?d00001 diff --git a/apps/score/README.md b/apps/score/README.md index 1de1ccdb5..6a5bf8172 100644 --- a/apps/score/README.md +++ b/apps/score/README.md @@ -4,6 +4,9 @@ This app will allow you to keep scores for most kinds of sports. To correct a falsely awarded point simply open and close the menu within .5 seconds. This will put the app into correction mode (indicated by the `R`). In this mode any score increments will be decrements. To move back a set, reduce both players scores to 0, then decrement one of the scores once again. +## Screenshot +![](screenshot_score.png) + ## Bangle.js 1 | Keybinding | Description | |---------------------|------------------------------| diff --git a/apps/score/screenshot_score.png b/apps/score/screenshot_score.png new file mode 100644 index 0000000000000000000000000000000000000000..662c59e9ea49f1d1bcfc4ac093a998c9c3d83405 GIT binary patch literal 1796 zcmeHIc~g^j5dP&R5sN5A2UQwY`5=D^^H4qCWTndN~OE?T=})^_(LgRyBlv zK4sYJh~{Q<$&HRx>l(-0OWNn1!bGP-#R+dO;C9qO8zDDamsfc>prcm12gv&0nWTW@ zMhPfT+Lp0HgQi}^0Sw=kyipjN+5b79PcG)$F)S{%!2v~Te;I>E`vd@}_O;6g(B(LZ z1B3*nt0AQ08VUn7v0wBcbP)d(-L;?IF4fMwIQP;DAP7OIp%*B6!=eQfR?42{9E&j? z!gV4kUB%d5B*pm(HhloUlWr;Uq^YoB9itn7Kue}P8Z~-*?E2bSYZIcgXk|=0oLm+x z_H4n#@@3wE&UdyQ+(z6A&18Ar)19^1&~kcKEyiSkeT`kJ6xy?I`c9SX265&M5gv48 zrk-uMP>sCsM#6CEua%bo>XliI?j|e893>kuJGK8=h7n8$s!{ko5vFRrju%Elbp@5C z0y!#fmt=mJ-)y>99H{pM83A{=!{+Ud)@&}t&{T8LF&BA#X`CQ_PEm$jZ0B}!;vqXD z(94i7DweX!2c2VvK`)D7|7f9PpUV`_o7pGs$g(+~IUdvVl)w0L3$I$O?(X|TF?@lm z-BGnUr;w^&_)k%@w{HM6-rE?yr7I)3YLJAS%=p7M(wnBph7a`DDwPvdpMr2mBj?;=cR%^$C{@`8uG}EBH^LW!F z2k_eb40l;FC8T6mtww-n96Dfl44~^-WQz>|5m+^%iKcHA>w)$S0+FwTHUZNS?!#0k9WxW^g%n_MzGwDO+d_?!@*NIll^;GIi z3R-qD$P#7`zm{EMfRTBja)&xSj4C{OW6PIPt-S)oqPWAuz+(4+hEaG&I zP9g;xH9=s&?9?NLou=2SN36LIi1S|_p4^%yOX153zsD^TA<{+rAAZCoHN7k{2#Du5 z#`}bE$AY)ZtI~0rtxyR@-e}Lvi)m|HoaRyaN{0~3RWow!c=bs7*%7CZ8NJ<2)W3V^ z!^O(c&Ci~n0SI18&$HC^b^uLR3z;eKaP;Vgsmq3TI!zTauH+(gAY*zZC6grOhNYS{ zJFG>O$x|pDTLNyK96NPnsd$N4|Lubca3Xf8FxI)cq84`bXU%SI7z~7UbYB&aKv)}J e+(G!4$IH6DqRVPDJzuVRYryRgh0Gz*vi}6(DCm;_ literal 0 HcmV?d00001 diff --git a/apps/trex/README.md b/apps/trex/README.md new file mode 100644 index 000000000..03e3f5883 --- /dev/null +++ b/apps/trex/README.md @@ -0,0 +1,4 @@ +# T-Rex + +![](screenshot_trex.png) + diff --git a/apps/trex/screenshot_trex.png b/apps/trex/screenshot_trex.png new file mode 100644 index 0000000000000000000000000000000000000000..a66cc013fa349cf661d12c4e511d0fd7529f0bda GIT binary patch literal 668 zcmV;N0%QG&P)Px%R!KxbRCr$P+~JagAPfXxegB8HV^4Z%iny#qN7(+kn>Nt$86#-zye!Ky=>ZGO z^E@T8_5bx@3GM6aYqNl1+bIY7M%>W-UjgH`pU9sKH5qNOQ-hEUPFs5K^FO&NdEm=4 z_y{+lKM~oEZT{?xNKcHRfNKUiYUWm->_M_M2QT0Xfm-B>u4{IRN5#S=;*;{*S$t%a z0w%9$Aek)OzZ9&_U?h`;`@etFz-dx#r*3<5Wu%<+8q);WUsbhh@m}H3ah(QMIVe+~ z23EEP0;9Xh$Z@AKcw{(q>;gum$T$I$aA-3Dqf%s?fJr#CnSfC#GETrG9NJ94s1z9| zU=j{(CSX*Gj1w>khc**1Dn-T#n1n-{2^f_k;{;5?q0I!0N|ABH0$+83R=~k`5)TJ# zzwW>8_0bn?hXT&s!)I^oj=&c1RwAOl!z^F{lk=lYGnI&Tmf4<4cR{H{G{X1DI>(gc-s#zT%ii6e&P4=o0Ram*SRlB2Ww3yQ1%kW3 zEQ8lmqQPSsx_fvY?0=Ugw=!xqr)+Uv;HnJuix+S31DqS2f9~klPmb32HT|*a?-Qal zuD$V~<1Br~H?ZvpeKR)8(EeFiWY6-aElYdeS>zWB@m^wF=+_(o0000 Date: Fri, 22 Oct 2021 10:59:37 +1300 Subject: [PATCH 0330/1062] Update app.js --- apps/speedalt2/app.js | 123 ++++++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 53 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 4fbe458db..8203988f4 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '1.02'; +var v = '1.03'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -407,12 +407,14 @@ function onGPS(fix) { if ( sp < 10 ) sp = sp.toFixed(1); else sp = Math.round(sp); - if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp); + + if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = sp; // Altitude al = lf.alt; al = Math.round(parseFloat(al)/parseFloat(cfg.alt)); - if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = parseFloat(al); + + if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = al; // Distance to waypoint di = distance(lf,wp); @@ -538,6 +540,39 @@ function nextFunc(dur) { onGPS(lf); } + +function updateClock() { + if (!canDraw) return; + if ( cfg.modeA != 4 ) return; + drawClock(); + if ( emulator ) {max.spd++;max.alt++;} +} + +function startDraw(){ + canDraw=true; + g.clear(); + Bangle.drawWidgets(); + setLpMode('SuperE'); // off + onGPS(lf); // draw app screen +} + +function stopDraw() { + canDraw=false; + if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 10000); //Drop to low power in 10 secs. Keep lp mode off until we have a first fix. +} + +function savSettings() { + require("Storage").write('speedalt2.json',cfg); +} + +function setLpMode(m) { + if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power + if ( !gpssetup ) return; + gpssetup.setPowerMode({power_mode:m}); +} + +// == Events + function setButtons(){ // BTN1 - Max speed/alt or next waypoint @@ -579,37 +614,42 @@ function setButtons(){ } -function updateClock() { - if (!canDraw) return; - if ( cfg.modeA != 4 ) return; - drawClock(); - if ( emulator ) {max.spd++;max.alt++;} -} +Bangle.on('lcdPower',function(on) { + if (!SCREENACCESS.withApp) return; + if (on) startDraw(); + else stopDraw(); +}); -function startDraw(){ - canDraw=true; - g.clear(); - Bangle.drawWidgets(); - setLpMode('SuperE'); // off - onGPS(lf); // draw app screen -} +Bangle.on('swipe',function(dir) { + if(dir == 1) { +console.log('RIGHT'); + prevScrn(); + } + else { +console.log('LEFT'); + nextScrn(); + } +}); -function stopDraw() { - canDraw=false; - if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 10000); //Drop to low power in 10 secs. Keep lp mode off until we have a first fix. -} +Bangle.on('touch', function(button){ + switch(button){ + case 1: // BTN4 +console.log('BTN4'); + prevScrn(); + break; + case 2: // BTN5 +console.log('BTN5'); + nextScrn(); + break; + case 3: +console.log('MDL'); + nextFunc(0); // Centre - same function as short BTN1 + break; + } + }); -function savSettings() { - require("Storage").write('speedalt2.json',cfg); -} -function setLpMode(m) { - if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power - if ( !gpssetup ) return; - gpssetup.setPowerMode({power_mode:m}); -} - -// =Main Prog +// == Main Prog // Read settings. let cfg = require('Storage').readJSON('speedalt2.json',1)||{}; @@ -657,29 +697,6 @@ var SCREENACCESS = { release:function(){this.withApp=true;startDraw();} }; -Bangle.on('lcdPower',function(on) { - if (!SCREENACCESS.withApp) return; - if (on) startDraw(); - else stopDraw(); -}); - -Bangle.on('swipe',function(dir) { - if(dir == 1) prevScrn(); - else nextScrn(); -}); - -/* -dir : "left/right/top/bottom/front/back", - double : true/false // was this a double-tap? - x : -2 .. 2, // the axis of the tap - y : -2 .. 2, // the axis of the tap - z : -2 .. 2 // the axis of the tap -Bangle.on('tap',function(tap) { - console.log('Tap : '+tap.dir); - if ( tap.dir == 'front' && tap.double ) nextFunc(1); // Same as short BTN1 -}); -*/ - var gpssetup; try { gpssetup = require("gpssetup"); From 0d79ca3f0cd9f584b97f9ea6d1af2dd69a92e324 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 11:00:31 +1300 Subject: [PATCH 0331/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 07bf11c6d..70b7d3086 100644 --- a/apps.json +++ b/apps.json @@ -2901,7 +2901,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"1.02", + "version":"1.03", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From e797480d166bbc6bcd890430e3656ed1b3a11e2a Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 11:14:38 +1300 Subject: [PATCH 0332/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 70b7d3086..1ec109fbc 100644 --- a/apps.json +++ b/apps.json @@ -2901,7 +2901,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"1.03", + "version":"1.04", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From 4088f7ec768bc86f440e3f9564420f4bc43649c0 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 11:16:13 +1300 Subject: [PATCH 0333/1062] Update app.js --- apps/speedalt2/app.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 8203988f4..6bd967b1b 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '1.03'; +var v = '1.04'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -621,17 +621,13 @@ Bangle.on('lcdPower',function(on) { }); Bangle.on('swipe',function(dir) { - if(dir == 1) { -console.log('RIGHT'); - prevScrn(); - } - else { -console.log('LEFT'); - nextScrn(); - } + if(dir == 1) prevScrn(); + else nextScrn(); }); Bangle.on('touch', function(button){ + nextFunc(0); // Same function as short BTN1 +/* switch(button){ case 1: // BTN4 console.log('BTN4'); @@ -646,9 +642,11 @@ console.log('MDL'); nextFunc(0); // Centre - same function as short BTN1 break; } +*/ }); + // == Main Prog // Read settings. From 70202e32ea27cc5b1745ac933c91c8fcd4735f99 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 14:46:37 +1300 Subject: [PATCH 0334/1062] Update app.js --- apps/speedclock/app.js | 371 ++++++++++++++--------------------------- 1 file changed, 128 insertions(+), 243 deletions(-) diff --git a/apps/speedclock/app.js b/apps/speedclock/app.js index 4c74ce1be..ed4e2b47e 100644 --- a/apps/speedclock/app.js +++ b/apps/speedclock/app.js @@ -1,19 +1,9 @@ -// Morphing Clock + -// Modifies original Morphing Clock to make seconds and date more readable, and adds a simple stopwatch -// Icon by https://icons8.com -var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -var locale = require("locale"); -var CHARW = 28; // how tall are digits? -var CHARP = 2; // how chunky are digits? -var Y = 50; // start height -// Offscreen buffer -var buf = Graphics.createArrayBuffer(CHARW+CHARP*2,CHARW*2 + CHARP*2,1,{msb:true}); -var bufimg = {width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer}; -// The last time that we displayed -var lastTime = "-----"; -// If animating, this is the interval's id -var animInterval; -var timeInterval; + +var v='0.01'; + +// timeout used to update every minute +var drawTimeout; +var x,y,w,h; // Variables for the stopwatch var counter = -1; // Counts seconds var oldDate = new Date(2020,0,1); // Initialize to a past date @@ -22,206 +12,25 @@ var B3 = 0; // Flag to track BTN3's current function var w1; // watch id for BTN1 var w3; // watch id for BTN3 -/* Get array of lines from digit d to d+1. - n is the amount (0..1) - maxFive is true is this digit only counts 0..5 */ -const DIGITS = { - " ":n=>[], - "0":n=>[ - [n,0,1,0], - [1,0,1,1], - [1,1,1,2], - [n,2,1,2], - [n,1,n,2], - [n,0,n,1]], - "1":n=>[ - [1-n,0,1,0], - [1,0,1,1], - [1-n,1,1,1], - [1-n,1,1-n,2], - [1-n,2,1,2]], - "2":n=>[ - [0,0,1,0], - [1,0,1,1], - [0,1,1,1], - [0,1+n,0,2], - [1,2-n,1,2], - [0,2,1,2]], - "3":n=>[ - [0,0,1-n,0], - [0,0,0,n], - [1,0,1,1], - [0,1,1,1], - [1,1,1,2], - [n,2,1,2]], - "4":n=>[ - [0,0,0,1], - [1,0,1-n,0], - [1,0,1,1-n], - [0,1,1,1], - [1,1,1,2], - [1-n,2,1,2]], - "5to0": n=>[ // 5 -> 0 - [0,0,0,1], - [0,0,1,0], - [n,1,1,1], - [1,1,1,2], - [0,2,1,2], - [0,2,0,2], - [1,1-n,1,1], - [0,1,0,1+n]], - "5to6": n=>[ // 5 -> 6 - [0,0,0,1], - [0,0,1,0], - [0,1,1,1], - [1,1,1,2], - [0,2,1,2], - [0,2-n,0,2]], - "6":n=>[ - [0,0,0,1-n], - [0,0,1,0], - [n,1,1,1], - [1,1-n,1,1], - [1,1,1,2], - [n,2,1,2], - [0,1-n,0,2-2*n]], - "7":n=>[ - [0,0,0,n], - [0,0,1,0], - [1,0,1,1], - [1-n,1,1,1], - [1,1,1,2], - [1-n,2,1,2], - [1-n,1,1-n,2]], - "8":n=>[ - [0,0,0,1], - [0,0,1,0], - [1,0,1,1], - [0,1,1,1], - [1,1,1,2], - [0,2,1,2], - [0,1,0,2-n]], - "9":n=>[ - [0,0,0,1], - [0,0,1,0], - [1,0,1,1], - [0,1,1-n,1], - [0,1,0,1+n], - [1,1,1,2], - [0,2,1,2]], - ":":n=>[ - [0.4,0.4,0.6,0.4], - [0.6,0.4,0.6,0.6], - [0.6,0.6,0.4,0.6], - [0.4,0.4,0.4,0.6], - [0.4,1.4,0.6,1.4], - [0.6,1.4,0.6,1.6], - [0.6,1.6,0.4,1.6], - [0.4,1.4,0.4,1.6]] -}; - -/* Draw a transition between lastText and thisText. - 'n' is the amount - 0..1 */ -function drawDigits(lastText,thisText,n) { - "ram" - const p = CHARP; // padding around digits - const s = CHARW; // character size - var x = 16; // x offset - g.reset(); - for (var i=0;i{ - if (c[0]!=c[2]) // horiz - buf.fillRect(p+c[0]*s,c[1]*s,p+c[2]*s,2*p+c[3]*s); - else if (c[1]!=c[3]) // vert - buf.fillRect(c[0]*s,p+c[1]*s,2*p+c[2]*s,p+c[3]*s); - }); - g.drawImage(bufimg,x,Y); - } - if (thisCh==":") x-=4; - x+=s+p+7; - } -} -function drawDate() { - var x = (CHARW + CHARP + 8)*5; - var y = Y + 2*CHARW + CHARP; - var d = new Date(); - // meridian - g.reset(); - g.setFont("6x8",2); - g.setFontAlign(-1,-1); - if (is12Hour) g.drawString((d.getHours() < 12) ? "AM" : "PM", x+8, Y+0, true); - // date - g.setFont("Vector16"); - g.setFontAlign(0,-1); - // Only draw the date if it has changed: - if ((d.getDate()!=oldDate.getDate())||(d.getMonth()!=oldDate.getMonth())||(d.getFullYear()!=oldDate.getFullYear())) { - var date = locale.date(d,false); - g.clearRect(1,y+8,g.getWidth(),y+24); - g.drawString(date, g.getWidth()/2, y+8, true); - oldDate = d; - } +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); } -function drawSeconds() { - var x = (CHARW + CHARP + 8)*5; - var y = Y + 2*CHARW + CHARP; - var d = new Date(); - // seconds - g.reset(); - g.setFont("6x8",2); - g.setFontAlign(-1,-1); - g.drawString(("0"+d.getSeconds()).substr(-2), x+8, y-12, true); -} +function stopWatch(clear) { -/* Show the current time, and animate if needed */ -function showTime() { - if (animInterval) return; // in animation - quit - var d = new Date(); - var hours = d.getHours(); - if (is12Hour) hours = ((hours + 11) % 12) + 1; - var t = (" "+hours).substr(-2)+":"+ - ("0"+d.getMinutes()).substr(-2); - var l = lastTime; - // same - don't animate - if (t==l || l=="-----") { - drawDigits(l,t,0); - drawDate(); - drawSeconds(); - lastTime = t; - return; - } - var n = 0; - animInterval = setInterval(function() { - n += 1/10; - if (n>=1) { - n=1; - clearInterval(animInterval); - animInterval = undefined; - } - drawDigits(l,t,n); - drawSeconds(); - }, 20); - lastTime = t; -} - -function stopWatch() { + x = 240; + y = 200; + w = 110; + h = 24; + g.clearRect(x-w,y-h,x,y); // clear the background + if (clear) return; + counter++; var hrs = Math.floor(counter/3600); @@ -240,21 +49,22 @@ function stopWatch() { BTN3, {repeat:false,edge:"falling"}); B3 = 0; // BTN3 is bound to stop the stopwatch } - + // Bind BTN1 to call the reset function: if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); // Draw elapsed time: + g.reset(); - g.setColor(0.0,0.5,1.0).setFontAlign(0,-1).setFont("Vector24"); - g.clearRect(1,180,g.getWidth(),210); - if (hrs>0) { - g.drawString(("0"+parseInt(hrs)).substr(-2), g.getWidth()/2 - 72, 180, true); - g.drawString( ":", g.getWidth()/2 - 48, 180, true); - } - g.drawString(("0"+parseInt(mins)).substr(-2), g.getWidth()/2 - 24, 180, true); - g.drawString( ":", g.getWidth()/2, 180, true); - g.drawString(("0"+parseInt(secs)).substr(-2), g.getWidth()/2 + 24, 180, true); + g.setColor(0.0,0.5,1.0); + g.setFontAlign(1,1); + g.setFont("Vector24"); + + var swStr = ("0"+parseInt(mins)).substr(-2) + ':' + ("0"+parseInt(secs)).substr(-2); + + if (hrs>0) swStr = ("0"+parseInt(hrs)).substr(-2) + ':' + swStr; + + g.drawString(swStr, x, y, true); } @@ -267,7 +77,8 @@ function resetStopWatch() { } // Clear the stopwatch: - g.clearRect(1,180,g.getWidth(),210); + stopWatch(true); +// g.clearRect(1,180,g.getWidth(),210); // Reset the counter: counter = -1; @@ -286,33 +97,107 @@ function resetStopWatch() { } -Bangle.on('lcdPower',function(on) { - if (animInterval) { - clearInterval(animInterval); - animInterval = undefined; +function drawDate() { + // draw date + x = 0; + y = 200; + w = 70; + h = 10; + + var date = new Date(); + var dateStr = require("locale").date(date); + g.reset(); + + g.clearRect(x,y-h,x+w,y); // clear the background + + g.setFontAlign(-1,1).setFont("6x8"); + g.drawString(dateStr,x,y); + +} + + +function drawTime() { + x = 120; + y = 107; + w = 120; + h = 134; + + var date = new Date(); + var timeStr = require("locale").time(date,1); + g.reset(); + + g.clearRect(x-(w/2),y-(h/2),x+(w/2),y+(h/2)); // clear the background + + g.setFontAlign(0,0); + g.setFontVector(85); +// g.setColor(0.5,0.5,0.5); + g.drawString(timeStr.substring(0,2),x,y-30); + g.drawString(timeStr.substring(3,5),x,y+38); +// g.drawString(timeStr,x,y); +} + +function resetStopWatch() { + + // Stop the interval if necessary: + if (swInterval) { + clearInterval(swInterval); + swInterval=undefined; } - if (timeInterval) { - clearInterval(timeInterval); - timeInterval = undefined; + + // Clear the stopwatch: + stopWatch(true); + + // Reset the counter: + counter = -1; + + // Set BTN3 to start the stopwatch again: + if (!B3) { + // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: + if (w3) {clearWatch(w3);w3=undefined;} + // Set BTN3 to start the watch again: + setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); + B3 = 1; // BTN3 is bound to start the stopwatch } + + // Reset watch on BTN1: + if (w1) {clearWatch(w1);w1=undefined;} +} + +function draw() { + x = g.getWidth()/2; + y = g.getHeight()/2; + g.reset(); + + + drawTime(); + drawDate(); + + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); + +// draw immediately at first, queue update +draw(); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ if (on) { - showTime(); - timeInterval = setInterval(showTime, 1000); - } else { - lastTime = "-----"; + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; } }); - -g.clear(); -Bangle.loadWidgets(); -Bangle.drawWidgets(); -// Update time once a second -timeInterval = setInterval(showTime, 1000); -showTime(); - -// Show launcher when button pressed +// Show launcher when middle button pressed Bangle.setUI("clock"); // Start stopwatch when BTN3 is pressed setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); B3 = 1; // BTN3 is bound to start the stopwatch + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); From 49600be430a30f1ee5e9bff06d4c765944743cbf Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 16:01:48 +1300 Subject: [PATCH 0335/1062] Update app.js --- apps/speedclock/app.js | 84 +++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 50 deletions(-) diff --git a/apps/speedclock/app.js b/apps/speedclock/app.js index ed4e2b47e..28e9494f4 100644 --- a/apps/speedclock/app.js +++ b/apps/speedclock/app.js @@ -1,9 +1,15 @@ +/* +Simple watch [speedwatch] +Mike Bennett mike[at]kereru.com +0.01 : Initial +*/ var v='0.01'; // timeout used to update every minute var drawTimeout; var x,y,w,h; + // Variables for the stopwatch var counter = -1; // Counts seconds var oldDate = new Date(2020,0,1); // Initialize to a past date @@ -12,6 +18,11 @@ var B3 = 0; // Flag to track BTN3's current function var w1; // watch id for BTN1 var w3; // watch id for BTN3 +// Colours +var colTime = 0x4FE0; +var colDate = 0xEFE0; +var colSW = 0x1DFD; + // schedule a draw for the next minute function queueDraw() { if (drawTimeout) clearTimeout(drawTimeout); @@ -23,12 +34,14 @@ function queueDraw() { function stopWatch(clear) { - x = 240; + x = 120; y = 200; - w = 110; - h = 24; + w = 240; + h = 25; - g.clearRect(x-w,y-h,x,y); // clear the background + g.reset(); + g.setColor(colSW); + g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background if (clear) return; counter++; @@ -54,10 +67,7 @@ function stopWatch(clear) { if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); // Draw elapsed time: - - g.reset(); - g.setColor(0.0,0.5,1.0); - g.setFontAlign(1,1); + g.setFontAlign(0,1); g.setFont("Vector24"); var swStr = ("0"+parseInt(mins)).substr(-2) + ':' + ("0"+parseInt(secs)).substr(-2); @@ -78,7 +88,9 @@ function resetStopWatch() { // Clear the stopwatch: stopWatch(true); -// g.clearRect(1,180,g.getWidth(),210); + + // Restore the date + drawDate(); // Reset the counter: counter = -1; @@ -99,18 +111,20 @@ function resetStopWatch() { function drawDate() { // draw date - x = 0; + x = 120; y = 200; - w = 70; - h = 10; + w = 240; + h = 25; - var date = new Date(); - var dateStr = require("locale").date(date); g.reset(); - - g.clearRect(x,y-h,x+w,y); // clear the background + g.setColor(colDate); + g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background - g.setFontAlign(-1,1).setFont("6x8"); + var date = new Date(); +// var dateStr = require("locale").date(date,1); + var dateStr = date.getDate() + ' ' +require("locale").month(date,1); + g.setFontAlign(0,1); + g.setFont("Vector24"); g.drawString(dateStr,x,y); } @@ -125,42 +139,12 @@ function drawTime() { var date = new Date(); var timeStr = require("locale").time(date,1); g.reset(); - g.clearRect(x-(w/2),y-(h/2),x+(w/2),y+(h/2)); // clear the background - g.setFontAlign(0,0); g.setFontVector(85); -// g.setColor(0.5,0.5,0.5); + g.setColor(colTime); g.drawString(timeStr.substring(0,2),x,y-30); g.drawString(timeStr.substring(3,5),x,y+38); -// g.drawString(timeStr,x,y); -} - -function resetStopWatch() { - - // Stop the interval if necessary: - if (swInterval) { - clearInterval(swInterval); - swInterval=undefined; - } - - // Clear the stopwatch: - stopWatch(true); - - // Reset the counter: - counter = -1; - - // Set BTN3 to start the stopwatch again: - if (!B3) { - // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: - if (w3) {clearWatch(w3);w3=undefined;} - // Set BTN3 to start the watch again: - setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); - B3 = 1; // BTN3 is bound to start the stopwatch - } - - // Reset watch on BTN1: - if (w1) {clearWatch(w1);w1=undefined;} } function draw() { @@ -168,9 +152,8 @@ function draw() { y = g.getHeight()/2; g.reset(); - drawTime(); - drawDate(); + if ( counter < 0 ) drawDate(); // Only draw date when SW is not running. // queue draw in one minute queueDraw(); @@ -191,6 +174,7 @@ Bangle.on('lcdPower',on=>{ drawTimeout = undefined; } }); + // Show launcher when middle button pressed Bangle.setUI("clock"); From 314d0cf3b0dc700852f970f71e1a69d75175e0ee Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 16:20:51 +1300 Subject: [PATCH 0336/1062] Update app-icon.js --- apps/speedclock/app-icon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedclock/app-icon.js b/apps/speedclock/app-icon.js index f4f24a18b..22e264124 100644 --- a/apps/speedclock/app-icon.js +++ b/apps/speedclock/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwxH+AH4A/AE+sFtoABF12swItsF9QuFR4IwmFwwvnFw4vCGEYuIF4JgjFxIvkFxQvCGBfOAAQvqFwYwRFxYvDGBIvUFxgv/F6IuNF4n+0nB4TvXFxwvF4XBAALlPF7ZfBGC4uPF4rABGAYAGTQwvad4YwKFzYvIGBQvfFwgAE3Qvt4IvEFzgvCLxO7Lx7vULzIzTFwIvgGZheFRAiNRGSQvpGYouesYAGmQAKq3CE4PIC4wviq2eFwPCroveCRSGEC6Qv0DAwRLcoouWC4VdVYQXkr1eAgVdAoIABroNEB4gHHC5QvHwQSDAAOCA74vH1uICQIABxGtA74vIAEwv/F/4vXAH4A/AHY")) +require("heatshrink").decompress(atob("oFAwhC/ABOIABgfymYAKD+Z/9hGDL5c4wAf/XzjASTxqgQhAfPMB2IPxiACIBo+BDxqACIBg+CLxpANHwQPBABgvCIBT8CJ5owDD5iPOOAQfLBojiDCYQGFGIQfICIQfdBYJNMOI6SHD8jeNOIYzID8hfRD9LfEAoTdFBIifLAAIffBoQRBAJpxMD84JCD+S/GL56fID8ALBb6ZhID8qtJCZ4fgT4YDBABq/PD7RNEL6IRKD8WID5pfCD5kzNhKSFmYfMBwSeOGBoPDABgvCJ5wAON5pADABivPIAIAOd5xABABweOD4J+OD58IQBj8LD/6gUDyAfhXzgfiP/wA2")) From eeab8a5397102cfe02b5eeaa4e0cafd8679cab9f Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 16:21:27 +1300 Subject: [PATCH 0337/1062] Add files via upload --- apps/speedclock/watch.png | Bin 0 -> 1439 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/speedclock/watch.png diff --git a/apps/speedclock/watch.png b/apps/speedclock/watch.png new file mode 100644 index 0000000000000000000000000000000000000000..b77f302d5291d39650ffd4c5400d0a7dd3e30d70 GIT binary patch literal 1439 zcmV;Q1z`G#P)ZZKIyp>XUbK#j8aEHE7Ds0zs-Qe=?maLsuD zcwH7zo-85*pPUNHrrgZ`eM~B!Lz#R329Fd4q?A-M035B_i!(14%b?7NZzmsOB(?~JQq2Hvi}HypfQSL?Z2&l2ULZ_o zg+qaL8-!-vwitv1pv#|LDDtV;SL79?vqWW)7sdH`07Fnl8VCo#kpJO8fxNdW6EFY= z%l80)kp{wij?s8|GN7v7I{+#~JK_+3YT9bUtq*JBP6-?m=93VkXj=a=!t=$vcL13+R`L*M8e?$7=E zR0)1v31I3q9xl!|L4C>F+7N)>qhMlw8GU1O(1^eoyEN2R*(Kxs+`J3Kj`vPq)QCE- zj?bf|t_%lC^U>N6z^B7A;9ZtGng#qG{kDPV0<@Hi&I15rG}Knv#TdJ!r3eGjg-w0p zg{1;ccK=qJf^vcwJDGmXkbS!Iq41PxAR--ATaG=p?_-RHh`K^Ou};y{GYvo}92?X? zr!86Bc=B7>3{MHuSSUbuzV5jKR2Td3_5FWrk!E_r+jU<$(v)Pne`qUl&K@eoD;0&p z9m^6-_YbA)2Uz8<+c5Se?d(qF_5@{+$jV(_$WWfG09?B_kLqHdsI46d^8he52%0NtVRREr&>#+Z@)vNV}NW(Tno5*=D%GKUkmWOTLadkx!v}5ucsrMYk}ePKP={9tMkm8@Yd~E zwU<=~WQN=|V{8idvY={d+dx^FjR^heGZAfZwtTQ-FP;P>i> zqQ`ruFS;6p_0yq^eQ*j#0Z1fM0Dz|25~vCT5p}v2gmveMs8dxKnrcfn^@$f;9X8em z0l(+}^TEl5W%T_rhc$9{B2XE_J9VXascesaLVPEVcp?da-HLPskJ;tnQh=tdyaix1 zo=D=epW}G4qM@cFAMe(e2}_X&ey@s_`m(3a-_@Nbuqa47%frPj+0d2Qa;zn@ULXO; zsS= z_SOj-_-5iEZci_Q7%mLASA8tPC0;4Ylzsphk1xTEiI4#NI=5sP?-%704#3T}eLn*1 zkH;$-rXR10(ph5W(JCG-YXBiO+)??HD4+ORfH4B-O8~eRw>~}-#rWU44N`DbMrDee z?**9BuTbjzNNf=!u|*k_c8m(kACUFg91#ok#GJj1J4B>CsO%U8Y%ExE?W-|Kg;}{h tLD__^eKjTtG8N$3{-Nx%fgE--{sVHTSvKO1ec%89002ovPDHLkV1gjPojw2n literal 0 HcmV?d00001 From 642efb242e8a52c9f7a34aa61ac3de8982dad444 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 16:22:17 +1300 Subject: [PATCH 0338/1062] Delete app.png --- apps/speedclock/app.png | Bin 1639 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 apps/speedclock/app.png diff --git a/apps/speedclock/app.png b/apps/speedclock/app.png deleted file mode 100644 index 93d8e57dcbfaf905321fffcf06df042aa49d7dc8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1639 zcmV-t2AKJYP)@pC8}P=li+m^Env72)@5?yIePa+S>ZbmYsjOa=2m` z!{OB3)^@Gi>3mz!4w$0Hsk)+sbUjipG!&a5|mi z0)`wD_))?j-{$=p(Hmdb{pQ4>#*6Vi6tc_Zs;~=3feFd%R;{|DPtl#K#zRAi7Y`X1 z;B-32+0egXk^BI1gjay&Ter42GL0Dz85MB$^yxyo$U$Iys+>m`&vy%a=qU``n@P+> zNP8DjRdIK?-TImuq^&ql&*I#FWmi3gE><5sB1FaOuOpr#vV?n<_ z(dFmE1qqjs%jGHp7J&{q>2Qby*D7qRpED;gC=2aCEBXiDExut%@3&{)$jNhX)r2YG z^dnNg&zDjGMkx7$Y$pHGo;N24gYr|qS+FpfUBG`~4S=I0`nS$#h_+3*gamysqb!pk zXv%7YEpd4d5ubDqMVujc>)h5J>qvFn-dc4qzLwO;v4OF4uHK zDiy5+ZXrpN_sZ@=#Xj{d&k>z5AX5;YnD;7bECtO;8eHUgNwAw_bgd#HLExkQ@!Xj+ zXB>X7_a+mwQjkhu24U}4pZZo9%HcI2rhoVL=_y*H6IEB8WWWn~`V z-Tpy)d;4g+$ZY~Y$B;^am0~yN4W?9AbqDCW6kH$2X1iauedJGMVA;$qJ+`S27!^;a zLFHfD)9b&=?EzjY{WpaYdh*?c4qo{24Fn`I6~AH7lb74K>t{82$^4}<13f@#JcasV z&KF6MZz%O8J%h>nRCsynRJse|UsOmac2ZauAatVf3o!C+e}8AsYqKqV^h0Z_b|Kt1 z@O_l9{PWVHU!u&4Ymd6Dd|G#zCMOdsSK06%Q^1oACnNf8o85N^@B&=+bs3j^T{fWdc+A|x(cEZ$IG!5wbnEk_ zzpY`zq1m!&pEnc#(LQ5#p(i)~!vyvkhHU0Pfl~n8(?8u^c&_xWf^(r)p||8Ly(MS& zk3BghbW*$AKFu`sF)m((MDVTj>G)q)J0s$u#}x4D?$7k|?)Yu_xT{0E#ii6gwD`J+ zoMU#ODHE@txF{F*m*+X}3AOBn4m;z^3mIFQ4{*u#;fR@m_fLG4-4jffF?;5ih@6Mz lPrm;rMhY0g2u5&e@jt$gS?$`E2( Date: Fri, 22 Oct 2021 16:28:38 +1300 Subject: [PATCH 0339/1062] Update apps.json --- apps.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps.json b/apps.json index 1ec109fbc..062e363ba 100644 --- a/apps.json +++ b/apps.json @@ -2916,6 +2916,25 @@ {"name":"speedalt2.json"} ] }, +{ "id": "speedclock", + "name": "SloMo Clock", + "shortName":"SloMo Clock", + "icon": "watch.png", + "version":"0.01", + "description": "Simple clock face with large digits hour above minutes.", + "tags": "clock", + "type":"clock", + "allow_emulator":true, + "readme": "README.md", + "storage": [ + {"name":"speedclock.app.js","url":"app.js"}, + {"name":"speedclock.img","url":"app-icon.js","evaluate":true}, + {"name":"speedclock.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"speedclock.json"} + ] +}, { "id": "de-stress", "name": "De-Stress", "shortName":"De-Stress", From 18d0e7e161fbc0dd85964fed10f47e1f89ab6d46 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 16:32:16 +1300 Subject: [PATCH 0340/1062] Update apps.json --- apps.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps.json b/apps.json index 062e363ba..f848201af 100644 --- a/apps.json +++ b/apps.json @@ -2929,7 +2929,6 @@ "storage": [ {"name":"speedclock.app.js","url":"app.js"}, {"name":"speedclock.img","url":"app-icon.js","evaluate":true}, - {"name":"speedclock.settings.js","url":"settings.js"} ], "data": [ {"name":"speedclock.json"} From e262d1bfdb2f38a7971ed6872a19555dfe048d56 Mon Sep 17 00:00:00 2001 From: nujw Date: Fri, 22 Oct 2021 16:34:33 +1300 Subject: [PATCH 0341/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index f848201af..781a36434 100644 --- a/apps.json +++ b/apps.json @@ -2928,7 +2928,7 @@ "readme": "README.md", "storage": [ {"name":"speedclock.app.js","url":"app.js"}, - {"name":"speedclock.img","url":"app-icon.js","evaluate":true}, + {"name":"speedclock.img","url":"app-icon.js","evaluate":true} ], "data": [ {"name":"speedclock.json"} From f413930ced017c08b18c4f909657d0cda7388144 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 22 Oct 2021 08:52:05 +0100 Subject: [PATCH 0342/1062] Misc battery widget tweaks, and make icon = the actual widget's icon --- apps.json | 19 ++++++++++++++++--- apps/widbat/ChangeLog | 1 + apps/widbat/widget.js | 29 ++++++++++++----------------- apps/widbat/widget.png | Bin 297 -> 280 bytes apps/widbatv/ChangeLog | 1 + apps/widbatv/widget.js | 19 +++++++++++++++++++ apps/widbatv/widget.png | Bin 0 -> 221 bytes apps/widtbat/ChangeLog | 1 + apps/widtbat/widget.js | 28 +++++++++++----------------- apps/widtbat/widget.png | Bin 911 -> 238 bytes 10 files changed, 61 insertions(+), 37 deletions(-) create mode 100644 apps/widbatv/ChangeLog create mode 100644 apps/widbatv/widget.js create mode 100644 apps/widbatv/widget.png diff --git a/apps.json b/apps.json index 839787a2b..4a7517e22 100644 --- a/apps.json +++ b/apps.json @@ -716,7 +716,7 @@ { "id": "widbat", "name": "Battery Level Widget", - "version": "0.08", + "version": "0.09", "description": "Show the current battery level and charging status in the top right of the clock", "icon": "widget.png", "type": "widget", @@ -726,6 +726,19 @@ {"name":"widbat.wid.js","url":"widget.js"} ] }, + { + "id": "widbatv", + "name": "Battery Level Widget (Vertical)", + "version": "0.01", + "description": "Slim, vertical battery widget that only takes up 14px", + "icon": "widget.png", + "type": "widget", + "tags": "widget,battery", + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"widbatv.wid.js","url":"widget.js"} + ] + }, { "id": "widlock", "name": "Lock Widget", @@ -1617,12 +1630,12 @@ { "id": "widtbat", "name": "Tiny Battery Widget", - "version": "0.01", + "version": "0.02", "description": "Tiny blueish battery widget, vibs and changes level color when charging", "icon": "widget.png", "type": "widget", "tags": "widget,tool,system", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"widtbat.wid.js","url":"widget.js"} ] diff --git a/apps/widbat/ChangeLog b/apps/widbat/ChangeLog index a5fdc31cc..5986ecf3f 100644 --- a/apps/widbat/ChangeLog +++ b/apps/widbat/ChangeLog @@ -5,3 +5,4 @@ 0.06: Use 'g.theme' (requires bootloader 0.23) 0.07: Move CHARGING variable to more readable string 0.08: Ensure battery updates every 60s even if LCD was on at boot and stays on +0.09: Misc speed/memory tweaks diff --git a/apps/widbat/widget.js b/apps/widbat/widget.js index 739326df0..a8a0c5382 100644 --- a/apps/widbat/widget.js +++ b/apps/widbat/widget.js @@ -1,26 +1,11 @@ (function(){ - function setWidth() { WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0); } - function draw() { - var s = 39; - var x = this.x, y = this.y; - g.reset(); - if (Bangle.isCharging()) { - g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); - x+=16; - } - g.setColor(g.theme.fg); - g.fillRect(x,y+2,x+s-4,y+21); - g.clearRect(x+2,y+4,x+s-6,y+19); - g.fillRect(x+s-3,y+10,x+s,y+14); - g.setColor("#0f0").fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17); - } Bangle.on('charging',function(charging) { if(charging) Bangle.buzz(); setWidth(); - Bangle.drawWidgets(); // relayout widgets + Bangle.drawWidgets(); // re-layout widgets g.flip(); }); var batteryInterval = Bangle.isLCDOn() ? setInterval(()=>WIDGETS["bat"].draw(), 60000) : undefined; @@ -37,6 +22,16 @@ } } }); - WIDGETS["bat"]={area:"tr",width:40,draw:draw}; + WIDGETS["bat"]={area:"tr",width:40,draw:function() { + var s = 39; + var x = this.x, y = this.y; + g.reset(); + if (Bangle.isCharging()) { + g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); + x+=16; + } + g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14); + g.setColor("#0f0").fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17); + }}; setWidth(); })() diff --git a/apps/widbat/widget.png b/apps/widbat/widget.png index 630692e38e3b9ba5fbb62b2bc3a33cd61be04836..4f7491ee9f2b43d2eb9d76638d0ac7236f71b3c3 100644 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGH$&H|6fVg?3oVGw3ym^DWND9B#o z>Fdh=luL|VRno>I?jle~HZvrm#5q4VH#M&W$Yo$~E=o--Nlj5G&n(GMaQE~LNYP7W z2a5N3x;Tbp+k%W^7#OjF z>xR6YxZwP~msj(vrfOZ-XeRq|Me#Trz%PXw%s%enD+*Q(4(O$7{!;ZftnH z;OBlT>zT*Z6fWiIJ=^fB#+Gq&KYwNU9G>d#i1%lbc&mQzv1$1Hd4@jEzQ$ig(U1P} ztw_>iy0bUgRQAhv#r8w_3THnth|Y_Bu~2P(K-W{of(yL1!oTHzSg}U@{@LL7qVaI@ z+@$_vb^q8z5_}o2;UU)6GFFD!1nXQXDJyW8*}q*=>C>8uX+JZBtJj^_U(L8LQa1YE T%Q{h@PZ>O2{an^LB{Ts5c#wMk diff --git a/apps/widbatv/ChangeLog b/apps/widbatv/ChangeLog new file mode 100644 index 000000000..55cda0f21 --- /dev/null +++ b/apps/widbatv/ChangeLog @@ -0,0 +1 @@ +0.01: New widget diff --git a/apps/widbatv/widget.js b/apps/widbatv/widget.js new file mode 100644 index 000000000..cc52a0f8e --- /dev/null +++ b/apps/widbatv/widget.js @@ -0,0 +1,19 @@ +Bangle.on('charging',function(charging) { + if(charging) Bangle.buzz(); + WIDGETS["batv"].draw(); +}); +setInterval(()=>WIDGETS["batv"].draw(), 60000); +Bangle.on('lcdPower', function(on) { + if (on) WIDGETS["batv"].draw(); // refresh at power on +}); +WIDGETS["batv"]={area:"tr",width:14,draw:function() { + var x = this.x, y = this.y; + g.reset(); + if (Bangle.isCharging()) { + g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); + } else { + g.clearRect(x,y,x+14,y+24); + g.setColor(g.theme.fg).fillRect(x+2,y+2,x+12,y+22).clearRect(x+4,y+4,x+10,y+20).fillRect(x+5,y+1,x+9,y+2); + g.setColor("#0f0").fillRect(x+4,y+20-(E.getBattery()*16/100),x+10,y+20); + } +}}; diff --git a/apps/widbatv/widget.png b/apps/widbatv/widget.png new file mode 100644 index 0000000000000000000000000000000000000000..e31704d7bb7d90eeee09b4d116729f723a5f9490 GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WI14-?iy0WWg+Z8+Vb&Z8pdfpR zr>`sfQ!X)fMY&@Rd}Tl(+02lL66gHf+|;}hAeVu`xhOTUBsE2$JhLQ2!QIn0AVn{g z9Vi~`>EamTas2HxOD+Zl9v0(A|M$y82O63^I+&;4;^W8jF3);u&Sn;lsp4-Ne(kiA zI^!$7f3MReSyibvNwwvMwORZISC|=D1RngEf99y { - const CBS = 0x41f, CBC = 0x07E0; - var xo = 6, xl = 22, yo = 9, h = 17; - - function draw() { - g.reset().setColor(CBS).drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4); - g.setColor(0).fillRect(this.x + xo, this.y + yo, this.x + xl, this.y + h); - var cbc = (Bangle.isCharging()) ? CBC : CBS; - g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h); - } - Bangle.on('charging', function(charging) { - if (charging) Bangle.buzz(); - Bangle.drawWidgets(); - }); - WIDGETS["widtbat"] = { area:"tr", width:32, draw: draw }; -})(); +Bangle.on('charging', function(charging) { + if (charging) Bangle.buzz(); + WIDGETS["widtbat"].draw(); +}); +WIDGETS["widtbat"] = { area:"tr", width:32, draw: function() { + const xo = 6, xl = 22, yo = 9, h = 17; + g.reset().setColor("#08f").drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4); + g.clearRect(this.x + xo, this.y + yo, this.x + xl, this.y + h); + var cbc = (Bangle.isCharging()) ? "#0f0" : "#08f"; + g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h); +} }; diff --git a/apps/widtbat/widget.png b/apps/widtbat/widget.png index 4294f0ca32299687f44c631a14109629f857f4a1..f2943bc52013b52db5cba8383076118faf2cee42 100644 GIT binary patch delta 210 zcmeBYf5$jMrJl3EBeIx*fm;}a85w5Hkzin8U@!6Xb!C6bCB|;Z|IEndDp07`)5S3) zc47kxh(UJ5u}CVu5^^4XIdJ^guk&< uHnkdA$(@|PJx4l!-G_RZkwCiPK9iWFx9rgeFQONMggssTT-G@yGywoz*h~We literal 911 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC#^NA%Cx&(BWL^R}0jUw5X}-P; zT0k}j0~4bV12aeo5Hhr9GO&Qz3=C>Ont_3N0V6_o0TW!-U;#6N4N`dWm6H(AkjTuC zh>{3jAFJg2T)o7U{G?R9irfN_0tTB3D*7iAWdWaj57fXq!y$}cUk zRZ;?31P2gzmSmu1yMhW`HkzTTSoaJ~2f8+kKR6OrFMCe`Z3#?B8 zC49*z`DeL<{|n(yEW*2#>kN&)@-iK7o}>EZyS}siuHP4aO2{#|b+YeT#Q0>l@TS?U z-Aq$V>eZY@m1ibibT(C-EV=!^vgEPuBgal1dB(`on$ebYVzQRQSqH^a%uB2$%z8DE z>HW$2WB(=7FJIlJG`oKy`vg^X$>_F+_K$r^%z0J$4H%3Uf0^)&Vb^2FKZ>Hvi{%c8 zF){0!&1!K=Za5?$k>$y;xZYrj&4(F{orb)&huCVPI|2^(Yw!ygvDk*ZIC=b8qrU_D zf!{11YaOe0ai@u|sP7R^IL*BK!>a|WRUYR#T}!NZa6kT-*Meo`Ehd*7|H&FNC$lP6 z8`u=+ML*@cApFdsH@o47vLbVa!hgXV%VUoI`al2PNuH;-O$_R Date: Fri, 22 Oct 2021 08:54:44 +0100 Subject: [PATCH 0343/1062] widbt: memory usage improvements --- apps.json | 2 +- apps/widbt/ChangeLog | 1 + apps/widbt/widget.js | 30 +++++++++++++----------------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/apps.json b/apps.json index 4a7517e22..d6dc915e9 100644 --- a/apps.json +++ b/apps.json @@ -790,7 +790,7 @@ { "id": "widbt", "name": "Bluetooth Widget", - "version": "0.06", + "version": "0.07", "description": "Show the current Bluetooth connection status in the top right of the clock", "icon": "widget.png", "type": "widget", diff --git a/apps/widbt/ChangeLog b/apps/widbt/ChangeLog index 4509487ac..7aa96ce5c 100644 --- a/apps/widbt/ChangeLog +++ b/apps/widbt/ChangeLog @@ -3,3 +3,4 @@ 0.04: Fix automatic update of Bluetooth connection status 0.05: Make Bluetooth widget thinner, and when on a bright theme use light grey for disabled color 0.06: Tweaking colors for dark/light themes and low bpp screens +0.07: Memory usage improvements diff --git a/apps/widbt/widget.js b/apps/widbt/widget.js index a25d2c21c..209a8c8d8 100644 --- a/apps/widbt/widget.js +++ b/apps/widbt/widget.js @@ -1,17 +1,13 @@ -(function(){ - function draw() { - g.reset(); - if (NRF.getSecurityStatus().connected) - g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); - else - g.setColor(g.theme.dark ? "#666" : "#999"); - g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); - } - function changed() { - WIDGETS["bluetooth"].draw(); - g.flip();// turns screen on - } - NRF.on('connect',changed); - NRF.on('disconnect',changed); - WIDGETS["bluetooth"]={area:"tr",width:15,draw:draw}; -})() +NRF.on('connect',WIDGETS["bluetooth"].changed); +NRF.on('disconnect',WIDGETS["bluetooth"].changed); +WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() { + g.reset(); + if (NRF.getSecurityStatus().connected) + g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); + else + g.setColor(g.theme.dark ? "#666" : "#999"); + g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); +},changed:function() { + WIDGETS["bluetooth"].draw(); + Bangle.setLCDPower(1); // turn screen on +}}; From 1942d5d6846c5dc093ce304f635ab9aaaea8788d Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 22 Oct 2021 11:46:58 +0100 Subject: [PATCH 0344/1062] minor original launcher tweak --- apps/launch/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/launch/app.js b/apps/launch/app.js index 449e16e62..3d4682e55 100644 --- a/apps/launch/app.js +++ b/apps/launch/app.js @@ -16,7 +16,7 @@ function drawMenu() { var w = g.getWidth(); var h = g.getHeight(); var m = w/2; - var n = (h-48)/64; + var n = Math.floor((h-48)/64); if (selected>=n+menuScroll) menuScroll = 1+selected-n; if (selected Date: Fri, 22 Oct 2021 11:47:23 +0100 Subject: [PATCH 0345/1062] Adding BETA messages app --- apps.json | 20 ++- apps/messages/README.md | 21 +++ apps/messages/app-icon.js | 1 + apps/messages/app.js | 273 ++++++++++++++++++++++++++++++++++++++ apps/messages/app.png | Bin 0 -> 917 bytes apps/messages/boot.js | 36 +++++ apps/messages/lib.js | 0 apps/messages/widget.js | 20 +++ 8 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 apps/messages/README.md create mode 100644 apps/messages/app-icon.js create mode 100644 apps/messages/app.js create mode 100644 apps/messages/app.png create mode 100644 apps/messages/boot.js create mode 100644 apps/messages/lib.js create mode 100644 apps/messages/widget.js diff --git a/apps.json b/apps.json index d6dc915e9..a372476a0 100644 --- a/apps.json +++ b/apps.json @@ -31,6 +31,23 @@ ], "sortorder": -10 }, + { + "id": "messages", + "name": "Messages", + "version": "0.01", + "description": "App to display notifications from iOS and Gadgetbridge (BETA)", + "icon": "app.png", + "tags": "tool,system", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"messages.app.js","url":"app.js"}, + {"name":"messages.img","url":"app-icon.js","evaluate":true}, + {"name":"messages.boot.js","url":"boot.js"}, + {"name":"messages.wid.js","url":"widget.js"} + ], + "sortorder": -9 + }, { "id": "health", "name": "Health Tracking", @@ -46,8 +63,9 @@ {"name":"health.boot.js","url":"boot.js"}, {"name":"health","url":"lib.js"} ], - "sortorder": -10 + "sortorder": -8 }, + { "id": "moonphase", "name": "Moonphase", diff --git a/apps/messages/README.md b/apps/messages/README.md new file mode 100644 index 000000000..c243ec06a --- /dev/null +++ b/apps/messages/README.md @@ -0,0 +1,21 @@ +# Messages app + +**THIS APP IS CURRENTLY BETA** + +This app handles the display of messages and message notifications. It stores +a list of currently received messages and allows them to be listed, viewed, +and responded to. + +It is a replacement for the old `notify`/`gadgetbridge` apps. + +## Usage + +... + +## Requests + +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messages%20app + +## Creator + +Gordon Williams diff --git a/apps/messages/app-icon.js b/apps/messages/app-icon.js new file mode 100644 index 000000000..6ed3c1141 --- /dev/null +++ b/apps/messages/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///rkcAYP9ohL/ABMBqoAEoALDioLFqgLDBQoABERIkEBZcFBY9QBed61QAC1oLF7wLD24LF24LD7wLF1vqBQOrvQLFA4IuC9QLFD4IuC1QLGGAQOBBYwgBEwQLHvQBBEZHVq4jI7wWBHY5TLNZaDLTZazLffMBBY9ABZsABY4KCgEVBQtUBYYkGEQYA/AAwA=")) diff --git a/apps/messages/app.js b/apps/messages/app.js new file mode 100644 index 000000000..d369ee175 --- /dev/null +++ b/apps/messages/app.js @@ -0,0 +1,273 @@ +/* MESSAGES is a list of: + {id:int, + src, + title, + subject, + body, + sender, + tel:string, + new:true // not read yet + } +*/ + +/* For example for maps: + +GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"}) +GB({"t":"notify","id":2,"src":"Hangouts","title":"Gordon","body":"Hello world quite a lot of text here..."}) +GB({"t":"notify","id":3,"src":"Messages","title":"Ted","body":"Bed time."}) +GB({"t":"notify","id":4,"src":"Messages","title":"Kailo","body":"Mmm... food"}) +GB({"t":"notify-","id":1}) + +GB({"t":"notify","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"Y2MBAA....AAAAAAAAAAAAAA="}) +GB({"t":"notify~","id":1,"body":"Campton - 11:54 ETA"}) +GB({"t":"notify~","id":1,"title":"High St"}) +GB({"t":"notify~","id":1,"body":"Campton - 11:55 ETA"}) +GB({"t":"notify~","id":1,"title":"0 yd - High St"}) +GB({"t":"notify~","id":1,"body":"Campton - 11:56 ETA"}) + +*/ + +var Layout = require("Layout"); + +var MESSAGES = require("Storage").readJSON("messages.json",1)||[]; +if (!Array.isArray(MESSAGES)) MESSAGES=[]; +var onMessagesModified = function(msg) { + // TODO: if new, show this new one + if (msg.new) Bangle.buzz(); + showMessage(msg.id); +}; +function saveMessages() { + require("Storage").writeJSON("messages.json",MESSAGES) +} + +function showMapMessage(msg) { + var m; + var distance, street, target, eta; + m=msg.title.match(/(.*) - (.*)/); + if (m) { + distance = m[1]; + street = m[2]; + } else street=msg.title; + m=msg.body.match(/(.*) - (.*)/); + if (m) { + target = m[1]; + eta = m[2]; + } else target=msg.body; + layout = new Layout({ + type:"v", c: [ + {type:"txt", font:"6x15", label:target, bgCol:"#0f0", fillx:1, pad:2 }, + {type:"h", bgCol:"#0f0", fillx:1, c: [ + {type:"txt", font:"6x8", label:"Towards" }, + {type:"txt", font:"6x15:2", label:street } + ]}, + {type:"h",fillx:1, filly:1, c: [ + {type:"img",src:atob(msg.img)}, + {type:"v", fillx:1, c: [ + {type:"txt", font:"6x15:2", label:distance||"" } + ]}, + ]}, + + {type:"txt", font:"6x8:2", label:eta } + ] + }); + g.clearRect(0,24,g.getWidth()-1,g.getHeight()-1); + layout.render(); + Bangle.setUI("updown",function() { + // any input to mark as not new and return to menu + msg.new = false; + saveMessages(); + checkMessages(); + }); +} + +function showMessage(msgid) { + var msg = MESSAGES.find(m=>m.id==msgid); + if (!msg) return checkMessages(); // go home if no message found + if (msg.src=="Maps") return showMapMessage(msg); + var m = msg.title+"\n"+msg.body; + E.showPrompt(m,{title:"Message", buttons : {"Read":"read", "Back":"back"}}).then(chosen => { + if (chosen=="read") { + // any input to mark as not new and return to menu + msg.new = false; + saveMessages(); + checkMessages(); + } else { + checkMessages(true); + } + }); +} + +// Show a single menu item for the message +function showMessageMenuItem(y, idx) { + var msg = MESSAGES[idx]; + var W = g.getWidth(), H=48; + if (msg.new) g.setBgColor("#4F4"); + else g.setBgColor("#CFC"); + g.clearRect(0,y,W-1,y+H-1).setColor(g.theme.fg); + var m = msg.title+"\n"+msg.body; + if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, W-2, y+2); + if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, 2,y+2); + if (msg.body) { + g.setFontAlign(-1,-1).setFont("6x8"); + var l = g.wrapString(msg.body, W-14); + if (l.length>3) { + l = l.slice(0,3); + l[l.length-1]+="..."; + } + g.drawString(l.join("\n"), 12,y+20); + } +} +//test +//g.clear(1); showMessageMenuItem(MESSAGES[0],24) + +if (process.env.HWVERSION==1) { // Bangle.js 1 + showBigMenu = function(options) { + /* options = { + h = height + items = # of items + draw = function(y, idx) + onSelect = function(idx) + }*/ + var selected = 0; + var menuScroll = 0; + var menuShowing = false; + var w = g.getWidth(); + var h = g.getHeight(); + var m = w/2; + var n = Math.floor((h-48)/options.h); + + function drawMenu() { + g.reset(); + if (selected>=n+menuScroll) menuScroll = 1+selected-n; + if (selected=options.items) break; + var y = 24+i*options.h; + options.draw(y, idx); + // border for selected + if (i+menuScroll==selected) { + g.setColor(g.theme.fgG).drawRect(0,y,w-1,y+options.h-1).drawRect(1,y+1,w-2,y+options.h-2); + } + } + // arrows + g.setColor(menuScroll ? g.theme.fg : g.theme.bg); + g.fillPoly([m,6,m-14,20,m+14,20]); + g.setColor((options.items>n+menuScroll) ? g.theme.fg : g.theme.bg); + g.fillPoly([m,h-7,m-14,h-21,m+14,h-21]); + } + g.clearRect(0,24,w-1,h-1); + drawMenu(); + Bangle.setUI("updown",dir=>{ + if (dir) { + selected += dir; + if (selected<0) selected = options.items-1; + if (selected>=options.items) selected = 0; + drawMenu(); + } else { + options.onSelect(selected); + } + }); + } +} else { // Bangle.js 2 + showBigMenu = function(options) { + /* options = { + h = height + items = # of items + draw = function(y, idx) + onSelect = function(idx) + }*/ + var menuScroll = 0; + var menuShowing = false; + var w = g.getWidth(); + var h = g.getHeight(); + var n = Math.ceil((h-24)/options.h); + var menuScrollMax = options.h*options.items - (h-24); + + function drawItem(i) { + var y = 24+i*options.h-menuScroll; + if (i<0 || i>=options.items || y<-options.h || y>=h) return; + options.draw(y, i); + } + + function drawMenu() { + g.reset().clearRect(0,24,w-1,h-1); + g.setClipRect(0,24,w-1,h-1); + for (var i=0;i{ + var dy = e.dy; + if (menuScroll - dy < 0) + dy = menuScroll; + if (menuScroll - dy > menuScrollMax) + dy = menuScroll - menuScrollMax; + if (!dy) return; + g.reset().setClipRect(0,24,g.getWidth()-1,g.getHeight()-1); + g.scroll(0,dy); + menuScroll -= dy; + if (e.dy < 0) { + drawItem(Math.floor((menuScroll+24+g.getHeight())/options.h)-1); + if (e.dy <= -options.h) drawItem(Math.floor((menuScroll+24+h)/options.h)-2); + } else { + drawItem(Math.floor((menuScroll+24)/options.h)); + if (e.dy >= options.h) drawItem(Math.floor((menuScroll+24)/options.h)+1); + } + g.setClipRect(0,0,w-1,h-1); + }; + Bangle.on('drag',Bangle.dragHandler); + Bangle.touchHandler = (_,e)=>{ + if (e.y<20) return; + var i = Math.floor((e.y+menuScroll-24) / options.h); + if (i>=0 && i { load() }); + // we have >0 messages + // TODO: IF A NEW MESSAGE, SHOW IT + if (!forceShowMenu) { + var newMessages = MESSAGES.filter(m=>m.new); + if (newMessages.length) + return showMessage(newMessages[0].id); + } + // Otherwise show a menu + var m = { + "":{title:"Messages"}, + "< Back": ()=>load() + }; + /*g.setFont("6x8"); + MESSAGES.forEach(msg=>{ + // "id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents" + var title = g.wrapString(msg.title, g.getWidth())[0]; + m[title] = function() { + showMessage(msg.id); + } + }); + E.showMenu(m);*/ + showBigMenu({ + h : 48, + items : MESSAGES.length, + draw : showMessageMenuItem, + onSelect : idx => showMessage(MESSAGES[idx].id) + }); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +checkMessages(); diff --git a/apps/messages/app.png b/apps/messages/app.png new file mode 100644 index 0000000000000000000000000000000000000000..c9177692e282e1247ced30f6ec0e2d14dc6dfa25 GIT binary patch literal 917 zcmV;G18V$CmK~!jg?U~I_6G0fppJ|s5%ZG(_jU{sLoS)M|Tx5HNwbU93{dM|XETG(}U{r87GP zP5QgOGds^S`_B9Bv_O#}MT#6JB;SE&AFiJ#PVFW@+5j{Fs1U4W3&1i!=cz7@tv)#Y zDW6G)8t?@prO7h)FeSJHz+qQqp6HZfw0bu&7zz6JtOi;d@C75Ko8|7;0Ims@mp=#J3E*{IZSCaRo7jz0My7S0nqA z?9Rp%4b8HI>M{r3e%-^}7vKLHd-Y5ye(oBGDVaVJ^4o8QwhZKo5Ba?~SxzwZA%&Tb z+lVS@06>def}RU5^jtiFA3Jn^jtCRn26CIwMDK4Qfz}EHS`WVSdt3w|zjyyIcTdI< z_Iq%ulJDxlW!-Ky5!DO<4g;b}p(qo~D|bzZD}}iwxHqISKZAMo>{p|R3Ib$Ig#6z9 zuUuBR4)M~4hRY-CJX3}9-`~ir3~U~mio^M77O*m~S^yz@5V~R(vM@mB3ZaDu3db9> zn1uo77y$nJqBwM-ljmkZQv)kQbrDK2S{O|%(5EZ+>pq)BEvr!VZekF?f^bdwGcVVy z-?JKEX&@5x?N#k0IslB|Xwyjp=o7hSt>fM8D`~5NdH=;!|7gueKnEx>+CfPJ#Q*e| r1fk1>I_9WBo>`?$ks?Kk{5$*tT^fbQe@cvs00000NkvXXu0mjfYw(!I literal 0 HcmV?d00001 diff --git a/apps/messages/boot.js b/apps/messages/boot.js new file mode 100644 index 000000000..dce3979da --- /dev/null +++ b/apps/messages/boot.js @@ -0,0 +1,36 @@ +(function() { + var _GB = global.GB; + global.GB = (event) => { + if (_GB) setTimeout(_GB,0,event); + // call handling? + if (!event.t.startsWith("notify")) return; + /* event is: + {t:"notify",id:int, src,title,subject,body,sender,tel:string} + {t:"notify~",id:int, title:string} // modified + {t:"notify-",id:int} // remove + */ + var messages, inApp = "undefined"!=typeof MESSAGES; + if (inApp) + messages = MESSAGES; // we're in an app that has already loaded messages + else // no app - load messages + messages = require("Storage").readJSON("messages.json",1)||[]; + // now modify/delete as appropriate + var mIdx = messages.findIndex(m=>m.id==event.id); + if (event.t=="notify-") { + if (mIdx>=0) messages.splice(mIdx, 1); // remove item + mIdx=-1; + } else { // add/modify + if (event.t=="notify") event.new=true; // new message + if (mIdx<0) mIdx=messages.push(event)-1; + else Object.assign(messages[mIdx], event); + } + require("Storage").writeJSON("messages.json",messages); + if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]); + // ok, saved now - we only care if it's new + if (event.t!="notify") return; + // if we're in a clock, go straight to messages app + if (Bangle.CLOCK) return load("messages.app.js"); + if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know + WIDGETS.messages.newMessage(); + }; +})() diff --git a/apps/messages/lib.js b/apps/messages/lib.js new file mode 100644 index 000000000..e69de29bb diff --git a/apps/messages/widget.js b/apps/messages/widget.js new file mode 100644 index 000000000..eda4a85a5 --- /dev/null +++ b/apps/messages/widget.js @@ -0,0 +1,20 @@ +WIDGETS["messages"]={area:"tl",width:0,draw:function() { + if (!this.width) return; + var c = (Date.now()-this.t)/1000; + g.reset().setBgColor((c&1) ? "#0f0" : "#030").setColor((c&1) ? "#000" : "#fff"); + g.clearRect(this.x,this.y,this.x+this.width,this.y+23); + g.setFont("6x8:1x2").setFontAlign(0,0).drawString("MESSAGES", this.x+this.width/2, this.y+12); + //if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute + if (c<120 && (Date.now()-this.l)>4000) { + this.l = Date.now(); + Bangle.buzz(); // buzz every 4 seconds + } + setTimeout(()=>WIDGETS["messages"].draw(), 1000); +},newMessage:function() { + WIDGETS["messages"].t=Date.now(); // first time + WIDGETS["messages"].l=Date.now()-10000; // last buzz + if (WIDGETS["messages"].c!==undefined) return; // already called + WIDGETS["messages"].width=64; + Bangle.drawWidgets(); + Bangle.setLCDPower(1);// turns screen on +}}; From 4fae5fc05dad52ad997221493528f77b12cfa17e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 22 Oct 2021 12:57:53 +0100 Subject: [PATCH 0346/1062] oops --- apps/widbt/widget.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/widbt/widget.js b/apps/widbt/widget.js index 209a8c8d8..88be3d5c9 100644 --- a/apps/widbt/widget.js +++ b/apps/widbt/widget.js @@ -1,5 +1,3 @@ -NRF.on('connect',WIDGETS["bluetooth"].changed); -NRF.on('disconnect',WIDGETS["bluetooth"].changed); WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() { g.reset(); if (NRF.getSecurityStatus().connected) @@ -11,3 +9,5 @@ WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() { WIDGETS["bluetooth"].draw(); Bangle.setLCDPower(1); // turn screen on }}; +NRF.on('connect',WIDGETS["bluetooth"].changed); +NRF.on('disconnect',WIDGETS["bluetooth"].changed); From 68d7d80341de375d70582627ddf568dd1137ddd2 Mon Sep 17 00:00:00 2001 From: Filipe Fradique Date: Fri, 22 Oct 2021 23:29:49 +0100 Subject: [PATCH 0347/1062] Created Nifty Clock B --- apps/ffcniftyb/ChangeLog | 1 + apps/ffcniftyb/app-icon.js | 1 + apps/ffcniftyb/app.js | 158 +++++++++++++++++++++++++++++++++++++ apps/ffcniftyb/app.png | Bin 0 -> 2188 bytes apps/ffcniftyb/settings.js | 50 ++++++++++++ 5 files changed, 210 insertions(+) create mode 100644 apps/ffcniftyb/ChangeLog create mode 100644 apps/ffcniftyb/app-icon.js create mode 100644 apps/ffcniftyb/app.js create mode 100644 apps/ffcniftyb/app.png create mode 100644 apps/ffcniftyb/settings.js diff --git a/apps/ffcniftyb/ChangeLog b/apps/ffcniftyb/ChangeLog new file mode 100644 index 000000000..f6516c6de --- /dev/null +++ b/apps/ffcniftyb/ChangeLog @@ -0,0 +1 @@ +0.01: New Clock Nifty B diff --git a/apps/ffcniftyb/app-icon.js b/apps/ffcniftyb/app-icon.js new file mode 100644 index 000000000..f0a2393b1 --- /dev/null +++ b/apps/ffcniftyb/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEIf4A5gX/+AGEn//mIWLgP/C4gGCAAMgC5UvC4sDC4YICkIhBgMQiEBE4Uxn4XDj//iEAn/yA4ICBgUikEikYXBBAIXEn/xJYURAYMygERkQHBiYLBKYIXF+AVDC4czgUSmIXBCQgED+ZeBR4YXBLYICDC5CPGC4IAIC40zmaPDC4MSLQQXK+ayCR4QXCiRoEC44ECh4bCC4MTiTDBC6ZHOC5B3NLYcvC4kBgL5BAAUikT+BfIIrB/8ykf/eYQXBkUTI4cBW4YQCgQGDmAXDkJfEC46GBAoJKCR4geCAAMRAAZRDAoIODO4UBPRIAJR5QXWgKNCTApNDC5Mv/6/DAwR3GAAyHCC4anJIo3/+bvEa4Uia4oXHkEvC4cvIgUf+YXKHYIvEAgcPC5QSGC5UBSwYXJLYQXFkUhgABBC5Ef/4mBl4XEmETmIXKgaXBmYCBC4cTkMxiQXJS4IACL4p3MgESCwJHFR5oxCiB3FkERC5cSToQXFmUyiAZFR48Bn7zCAQMjkfykQkBN4n/XgKPBAAQgCUQIfBUwYXHFgIGCdI4XDmYADmIIEkAWJAH4A4A==")) \ No newline at end of file diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js new file mode 100644 index 000000000..60d76ff0a --- /dev/null +++ b/apps/ffcniftyb/app.js @@ -0,0 +1,158 @@ +// setTimeout(load,100);Bangle.factoryReset(); +console.log('mem', process.memory().usage); +const locale = require("locale"); +const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; + + +/* Clock *********************************************/ +const scale = g.getWidth() / 176; + +const screen = { + width: g.getWidth(), + height: g.getHeight() - 24, +}; + +const center = { + x: screen.width / 2, + y: screen.height / 2, +}; + +const color = g.toColor(255, 0, 0); +console.log('color', color); + +function d02(value) { + return ('0' + value).substr(-2); +} + +// const c = E.compiledC(` +// // void xor(int, int, int) +// void xor(int len, int *dst, int *src){ +// len = len>>2; +// while (len--) { +// *dst ^= *src; +// dst++; +// src++; +// } +// } +// `); + +// function combineLayers(l1, l2) { +// // const l1ptr = E.getAddressOf(l1.buffer, true); +// // const l2ptr = E.getAddressOf(l2.buffer, true); +// // if (l1ptr && l2ptr) { +// // c.xor(l1.buffer.length, l1ptr, l2ptr); +// // } +// if (l1 && l1.buffer && l2 && l2.buffer) { +// for (let i = 0; i < l1.buffer.length; i++) { +// l1.buffer[i] ^= l2.buffer[i]; +// } +// } +// return l1; +// } + +function renderEllipse(g) { + g.fillEllipse(center.x - 5 * scale, center.y - 70 * scale, center.x + 160 * scale, center.y + 90 * scale); +} + +function renderText(g) { + const now = new Date(); + + const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); + const minutes = d02(now.getMinutes()); + const day = d02(now.getDay()); + const month = d02(now.getMonth() + 1); + const year = now.getFullYear(); + + const month2 = locale.month(now, 3); + const day2 = locale.dow(now, 3); + + g.setFontAlign(1, 0).setFont("Vector", 90 * scale); + g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); + g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale); + + g.setFontAlign(1, 0).setFont("Vector", 16 * scale); + g.drawString(year, center.x + 80 * scale, center.y - 42 * scale); + g.drawString(month, center.x + 80 * scale, center.y - 26 * scale); + g.drawString(day, center.x + 80 * scale, center.y - 10 * scale); + g.drawString(month2, center.x + 80 * scale, center.y + 44 * scale); + g.drawString(day2, center.x + 80 * scale, center.y + 60 * scale); +} + +function draw() { + const s = new Date().getTime(); + console.log('mem.b', process.memory().usage); + + let buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { + msb: true + }); + + let img = { + width: screen.width, + height: screen.height, + transparent: 0, + bpp: 1, + buffer: buf.buffer + }; + + // cleat screen area + g.clearRect(0, 24, g.getWidth(), g.getHeight()); + + // render outside text with ellipse + buf.clear() + renderText(buf.setColor(1)); + renderEllipse(buf.setColor(0)); + g.setColor(color).drawImage(img, 0, 24); + + // render ellipse with inside text + buf.clear() + renderEllipse(buf.setColor(1)); + renderText(buf.setColor(0)); + g.setColor(color).drawImage(img, 0, 24); + + buf = undefined; + img = undefined; + + console.log('mem.e', process.memory().usage); + console.log('draw', new Date().getTime() - s); +} + + +/* Minute Ticker *************************************/ + +let ticker; + +function stopTick() { + if (ticker) { + clearTimeout(ticker); + ticker = undefined; + } +} + +function startTick(run) { + stopTick(); + run(); + ticker = setTimeout(() => startTick(run), 60000 - (Date.now() % 60000)); + // ticker = setTimeout(() => startTick(run), 3000); +} + +/* Init **********************************************/ + +g.clear(); +startTick(draw); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower', (on) => { + if (on) { + startTick(draw); + } else { + stopTick(); + } +}); + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); +console.log('mem', process.memory().usage); \ No newline at end of file diff --git a/apps/ffcniftyb/app.png b/apps/ffcniftyb/app.png new file mode 100644 index 0000000000000000000000000000000000000000..1cd8a49b7ba951670fb8c007055a64f6d29dfc98 GIT binary patch literal 2188 zcmV;72y^#|P)?hiUTNH2?4Z%C_80Yk+zhAr8q)Ct(B5m*P-K< z-4S(wt>O&pbbYMTu{_)o+B!O2s61R07z*;}3M7)4K*;;-#KXpzS5fKyvEwiQeD}M* z@7!O`ch5QBcfkw1z;l9R8_r-bNF)-3aDILsLg;*acXxLHfKIDhP%kWhF6az;eM7xW zE>kEJqZ6YH2E!2(!?1%N931{}cve0ul}o2)rkc(-@p!!DRFl3v{ipjs!T-Vl4d~z7 zV)dHUW8-7c9^Ni|dl{s7vOG`Jo-hyp;Nb%Zp+qKOGMRSy-`)JqOqR$b5}8D$RiR6z zN=d_MmgK6Bt2QQXM7EEQPf}7603bLdcvIpgGMSv3mda!@Jv=?uu3P&!W>exOG~B?@ zKzu@c)A=SRf?iqs3L?+SXXE1In2!)Z1YG&@3i6*hcgB*h(x?u6Z~)of-rh(5a8z_e zR9sSAeY~0z$SEi&`1JUv#U;hJ`fec(1pwgX?F9g+`BP2lzEZtj|9<)VPNuU;RE3PR zw6q)7Zy;N#RKB+5wOzTp$QW6nR9NzdhlgipXOSI11oYqO_hI{Jwc48FH8HDWW~4KN zLxU6J6LIlzj|E93(xN>@wI^zki%cdv5k#YzGNRRKZ(P5@<#L0Ag8={!A3p5t>^yts ztdl?hK*jzFI!@oYW9Qy|dp8Rx#ziP|x>+dTlEI>A$P7etQ!Rfe*iwlRt0RRLA2J!ejfn+kjAL7yUNPs&K!EzLp zx+V3{;X`h2Zg1tkRrPTdj^nnRXB0#wQ`^Pu(c#fTflw$Au6cP)T|*s&kV>YCj))vZ zjgOD(_4?QivD{#8Pj^pbROGS=A^}oXR+d_=Hhc2l%%4@5R#S9Tw3Dc?m0>Iv3#Z}X z5#jUm^R^tyQ%fN&9)EP4E(OxZ15qe?gB}22_b~_|AtB>qgsAsf63kw7Sp6vrBXQ(M5d5qV`FhT9vKz6Dtwh)j%kJhg93Nw?uGywj0ONe zWOSqf8fY|{=!i(8(U37R36Rbyv*(< z1R=cEe$8rd%YCT!bZu^4EX(ABjH7c|FI`^Z)=l|Jj+Dnd$52n=DL@ii~n~b+zS~mW;2TZ;CJl0%$ZE?MTKj z5+F6VG~fUCeXCm;+cP}eJs>ds#A5N-*qCj2Uw>ciiP}@Or_d#b!a z>xX65Szb|Yghum{aaa!kP+VL*H8W+k#7>?%$qD3GF`qVNiwF)0{`UH}mZLkVsSi~i za$h3!)~_>P`uqEv0}&o|9a5>?V7qW}aY2T= zhx^q1DYIXz)jDriOfO$~DU;5OPl$&Qj@=v6YPChhMYsEIpKm&E<~;SrC=xAJs4Xom zVXMNBO(Ky_*PmYDzQTE|Kp;5$;o%E^yKt`QT+N?r2!g;!80xFccCZW-2ykGP5P)~6>U_)I)Yg=pI?Y@e#iru-pU0q!}#2o^m;AbA`ybVIF zx4~er-Gp>d2O(^3ZMGy^t+LS2Q1lH0$8k5B8)|1Q`Ok8eiQC0IKJSyGpQsiVi$jf0 zQ}>s;iSdb_jU5~uR45dvuv9OYdW6Y|C%V;VJX}Y9965O4;L6aIn;(H6!q0yrpF}31 za>Y?pL16)o<4J;~#NQ-3;@C+!TK1-IEB0z?L9!r-6QtAY#2w<%k4=z`OVGE zA*V{ELNPdwpE_--w&oY+g~>v6bOeV4H=b+6-AsdsJH(bExLmHMrzf&I#2qoKWA60d ziHMA-I8gD~@z1DKDuqJf@p%Jx2Oh_4h})o0DC7z``ua4lnOE!83%UiVTq==Cb7j%g6wS>+dMrzgSbJ0WI_4<^6i=134-wQ z^78fbePSkUi+o#H7WsTWzxQ^pEk`bw=e?QdC|^s2DP=P=GXnzyMqos(rxhq<3I?$0 z>1nfjXlN)pGCDgudr$Em27>_s!~oXT)+Q2(WV5p86!Zcw@c#t>z<&U{^`%9!77N+{ O0000 () => { + settings.color = color; + save(settings); + showSettings(); + } + + E.showMenu({ + '': { 'title': 'Colors' }, + '< Back': showSettings, + 'White': saveColor(65535), + 'Red': saveColor(63488), + 'Yellow': saveColor(65504), + 'Cyan': saveColor(2047), + 'Green': saveColor(2016), + 'Blue': saveColor(31), + 'Black': saveColor(0), + }) + } + + function showSettings() { + E.showMenu({ + '': { 'title': 'Nifty B Clock' }, + '< Back': back, + 'Color': showColors, + }) + } + + showColors(); +}) From 383f07606d2a3592229bf6d645492db4bb2227b8 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:15:45 +1300 Subject: [PATCH 0348/1062] Update apps.json --- apps.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index 781a36434..e5577b0ce 100644 --- a/apps.json +++ b/apps.json @@ -2916,22 +2916,22 @@ {"name":"speedalt2.json"} ] }, -{ "id": "speedclock", +{ "id": "slomoclock", "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", "version":"0.01", - "description": "Simple clock face with large digits hour above minutes.", + "description": "Simple 24h clock face with large digits hours above minutes.", "tags": "clock", "type":"clock", "allow_emulator":true, "readme": "README.md", "storage": [ - {"name":"speedclock.app.js","url":"app.js"}, - {"name":"speedclock.img","url":"app-icon.js","evaluate":true} + {"name":"slomoclock.app.js","url":"app.js"}, + {"name":"slomoclock.img","url":"app-icon.js","evaluate":true} ], "data": [ - {"name":"speedclock.json"} + {"name":"slomoclock.json"} ] }, { "id": "de-stress", From d3519e4290c2893f06f5a9d736ed4d93f1099776 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:23:41 +1300 Subject: [PATCH 0349/1062] Update apps.json --- apps.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index e5577b0ce..781a36434 100644 --- a/apps.json +++ b/apps.json @@ -2916,22 +2916,22 @@ {"name":"speedalt2.json"} ] }, -{ "id": "slomoclock", +{ "id": "speedclock", "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", "version":"0.01", - "description": "Simple 24h clock face with large digits hours above minutes.", + "description": "Simple clock face with large digits hour above minutes.", "tags": "clock", "type":"clock", "allow_emulator":true, "readme": "README.md", "storage": [ - {"name":"slomoclock.app.js","url":"app.js"}, - {"name":"slomoclock.img","url":"app-icon.js","evaluate":true} + {"name":"speedclock.app.js","url":"app.js"}, + {"name":"speedclock.img","url":"app-icon.js","evaluate":true} ], "data": [ - {"name":"slomoclock.json"} + {"name":"speedclock.json"} ] }, { "id": "de-stress", From 5e2a740f850605031ee2e033901a52c86af2cc7b Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:27:58 +1300 Subject: [PATCH 0350/1062] Create ChangeLog --- apps/slomoclock/ChangeLog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/slomoclock/ChangeLog diff --git a/apps/slomoclock/ChangeLog b/apps/slomoclock/ChangeLog new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/slomoclock/ChangeLog @@ -0,0 +1 @@ + From 8671ff9ae80944efc06bcff2c86d74c6ccf5da77 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:29:33 +1300 Subject: [PATCH 0351/1062] Update ChangeLog --- apps/slomoclock/ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/slomoclock/ChangeLog b/apps/slomoclock/ChangeLog index 8b1378917..c31405e08 100644 --- a/apps/slomoclock/ChangeLog +++ b/apps/slomoclock/ChangeLog @@ -1 +1 @@ - +0.01: Created app From 443afe923e761f0354158ef02c63e744c6ca4dbe Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:31:48 +1300 Subject: [PATCH 0352/1062] Create README.md --- apps/slomoclock/README.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 apps/slomoclock/README.md diff --git a/apps/slomoclock/README.md b/apps/slomoclock/README.md new file mode 100644 index 000000000..9a6bbbdd2 --- /dev/null +++ b/apps/slomoclock/README.md @@ -0,0 +1,6 @@ +# SloMo Clock + +Simple 24h clock with large digits. + +![](Screenshot.JPG) + From 5b3310f9e7f1bd2373853b4a437297d1f5bea2de Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:32:35 +1300 Subject: [PATCH 0353/1062] Create app-icon.js --- apps/slomoclock/app-icon.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/slomoclock/app-icon.js diff --git a/apps/slomoclock/app-icon.js b/apps/slomoclock/app-icon.js new file mode 100644 index 000000000..22e264124 --- /dev/null +++ b/apps/slomoclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("oFAwhC/ABOIABgfymYAKD+Z/9hGDL5c4wAf/XzjASTxqgQhAfPMB2IPxiACIBo+BDxqACIBg+CLxpANHwQPBABgvCIBT8CJ5owDD5iPOOAQfLBojiDCYQGFGIQfICIQfdBYJNMOI6SHD8jeNOIYzID8hfRD9LfEAoTdFBIifLAAIffBoQRBAJpxMD84JCD+S/GL56fID8ALBb6ZhID8qtJCZ4fgT4YDBABq/PD7RNEL6IRKD8WID5pfCD5kzNhKSFmYfMBwSeOGBoPDABgvCJ5wAON5pADABivPIAIAOd5xABABweOD4J+OD58IQBj8LD/6gUDyAfhXzgfiP/wA2")) From 189444f74008b5f2cd55311165cac95d1d28f8e8 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:33:23 +1300 Subject: [PATCH 0354/1062] Create app.js --- apps/slomoclock/app.js | 187 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 apps/slomoclock/app.js diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js new file mode 100644 index 000000000..28e9494f4 --- /dev/null +++ b/apps/slomoclock/app.js @@ -0,0 +1,187 @@ +/* +Simple watch [speedwatch] +Mike Bennett mike[at]kereru.com +0.01 : Initial +*/ + +var v='0.01'; + +// timeout used to update every minute +var drawTimeout; +var x,y,w,h; + +// Variables for the stopwatch +var counter = -1; // Counts seconds +var oldDate = new Date(2020,0,1); // Initialize to a past date +var swInterval; // The interval's id +var B3 = 0; // Flag to track BTN3's current function +var w1; // watch id for BTN1 +var w3; // watch id for BTN3 + +// Colours +var colTime = 0x4FE0; +var colDate = 0xEFE0; +var colSW = 0x1DFD; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function stopWatch(clear) { + + x = 120; + y = 200; + w = 240; + h = 25; + + g.reset(); + g.setColor(colSW); + g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background + if (clear) return; + + counter++; + + var hrs = Math.floor(counter/3600); + var mins = Math.floor((counter-hrs*3600)/60); + var secs = counter - mins*60 - hrs*3600; + + // When starting the stopwatch: + if (B3) { + // Set BTN3 to stop the stopwatch and bind itself to restart it: + w3=setWatch(() => {clearInterval(swInterval); + swInterval=undefined; + if (w3) {clearWatch(w3);w3=undefined;} + setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, + BTN3, {repeat:false,edge:"falling"}); + B3 = 1;}, + BTN3, {repeat:false,edge:"falling"}); + B3 = 0; // BTN3 is bound to stop the stopwatch + } + + // Bind BTN1 to call the reset function: + if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); + + // Draw elapsed time: + g.setFontAlign(0,1); + g.setFont("Vector24"); + + var swStr = ("0"+parseInt(mins)).substr(-2) + ':' + ("0"+parseInt(secs)).substr(-2); + + if (hrs>0) swStr = ("0"+parseInt(hrs)).substr(-2) + ':' + swStr; + + g.drawString(swStr, x, y, true); + +} + +function resetStopWatch() { + + // Stop the interval if necessary: + if (swInterval) { + clearInterval(swInterval); + swInterval=undefined; + } + + // Clear the stopwatch: + stopWatch(true); + + // Restore the date + drawDate(); + + // Reset the counter: + counter = -1; + + // Set BTN3 to start the stopwatch again: + if (!B3) { + // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: + if (w3) {clearWatch(w3);w3=undefined;} + // Set BTN3 to start the watch again: + setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); + B3 = 1; // BTN3 is bound to start the stopwatch + } + + // Reset watch on BTN1: + if (w1) {clearWatch(w1);w1=undefined;} +} + + +function drawDate() { + // draw date + x = 120; + y = 200; + w = 240; + h = 25; + + g.reset(); + g.setColor(colDate); + g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background + + var date = new Date(); +// var dateStr = require("locale").date(date,1); + var dateStr = date.getDate() + ' ' +require("locale").month(date,1); + g.setFontAlign(0,1); + g.setFont("Vector24"); + g.drawString(dateStr,x,y); + +} + + +function drawTime() { + x = 120; + y = 107; + w = 120; + h = 134; + + var date = new Date(); + var timeStr = require("locale").time(date,1); + g.reset(); + g.clearRect(x-(w/2),y-(h/2),x+(w/2),y+(h/2)); // clear the background + g.setFontAlign(0,0); + g.setFontVector(85); + g.setColor(colTime); + g.drawString(timeStr.substring(0,2),x,y-30); + g.drawString(timeStr.substring(3,5),x,y+38); +} + +function draw() { + x = g.getWidth()/2; + y = g.getHeight()/2; + g.reset(); + + drawTime(); + if ( counter < 0 ) drawDate(); // Only draw date when SW is not running. + + // queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.clear(); + +// draw immediately at first, queue update +draw(); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +// Start stopwatch when BTN3 is pressed +setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); +B3 = 1; // BTN3 is bound to start the stopwatch + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); From 85fd2b89673dd947d18d3503dd2488d1356e40d7 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:33:47 +1300 Subject: [PATCH 0355/1062] Add files via upload --- apps/slomoclock/watch.png | Bin 0 -> 1439 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/slomoclock/watch.png diff --git a/apps/slomoclock/watch.png b/apps/slomoclock/watch.png new file mode 100644 index 0000000000000000000000000000000000000000..b77f302d5291d39650ffd4c5400d0a7dd3e30d70 GIT binary patch literal 1439 zcmV;Q1z`G#P)ZZKIyp>XUbK#j8aEHE7Ds0zs-Qe=?maLsuD zcwH7zo-85*pPUNHrrgZ`eM~B!Lz#R329Fd4q?A-M035B_i!(14%b?7NZzmsOB(?~JQq2Hvi}HypfQSL?Z2&l2ULZ_o zg+qaL8-!-vwitv1pv#|LDDtV;SL79?vqWW)7sdH`07Fnl8VCo#kpJO8fxNdW6EFY= z%l80)kp{wij?s8|GN7v7I{+#~JK_+3YT9bUtq*JBP6-?m=93VkXj=a=!t=$vcL13+R`L*M8e?$7=E zR0)1v31I3q9xl!|L4C>F+7N)>qhMlw8GU1O(1^eoyEN2R*(Kxs+`J3Kj`vPq)QCE- zj?bf|t_%lC^U>N6z^B7A;9ZtGng#qG{kDPV0<@Hi&I15rG}Knv#TdJ!r3eGjg-w0p zg{1;ccK=qJf^vcwJDGmXkbS!Iq41PxAR--ATaG=p?_-RHh`K^Ou};y{GYvo}92?X? zr!86Bc=B7>3{MHuSSUbuzV5jKR2Td3_5FWrk!E_r+jU<$(v)Pne`qUl&K@eoD;0&p z9m^6-_YbA)2Uz8<+c5Se?d(qF_5@{+$jV(_$WWfG09?B_kLqHdsI46d^8he52%0NtVRREr&>#+Z@)vNV}NW(Tno5*=D%GKUkmWOTLadkx!v}5ucsrMYk}ePKP={9tMkm8@Yd~E zwU<=~WQN=|V{8idvY={d+dx^FjR^heGZAfZwtTQ-FP;P>i> zqQ`ruFS;6p_0yq^eQ*j#0Z1fM0Dz|25~vCT5p}v2gmveMs8dxKnrcfn^@$f;9X8em z0l(+}^TEl5W%T_rhc$9{B2XE_J9VXascesaLVPEVcp?da-HLPskJ;tnQh=tdyaix1 zo=D=epW}G4qM@cFAMe(e2}_X&ey@s_`m(3a-_@Nbuqa47%frPj+0d2Qa;zn@ULXO; zsS= z_SOj-_-5iEZci_Q7%mLASA8tPC0;4Ylzsphk1xTEiI4#NI=5sP?-%704#3T}eLn*1 zkH;$-rXR10(ph5W(JCG-YXBiO+)??HD4+ORfH4B-O8~eRw>~}-#rWU44N`DbMrDee z?**9BuTbjzNNf=!u|*k_c8m(kACUFg91#ok#GJj1J4B>CsO%U8Y%ExE?W-|Kg;}{h tLD__^eKjTtG8N$3{-Nx%fgE--{sVHTSvKO1ec%89002ovPDHLkV1gjPojw2n literal 0 HcmV?d00001 From cfbef5ae350a0ac8249a0a73f60768bcd6bd6cfa Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:35:33 +1300 Subject: [PATCH 0356/1062] Update apps.json --- apps.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index 781a36434..22f75982a 100644 --- a/apps.json +++ b/apps.json @@ -2916,22 +2916,22 @@ {"name":"speedalt2.json"} ] }, -{ "id": "speedclock", +{ "id": "slomoclock", "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", "version":"0.01", - "description": "Simple clock face with large digits hour above minutes.", + "description": "Simple 24h clock face with large digits hour above minutes.", "tags": "clock", "type":"clock", "allow_emulator":true, "readme": "README.md", "storage": [ - {"name":"speedclock.app.js","url":"app.js"}, - {"name":"speedclock.img","url":"app-icon.js","evaluate":true} + {"name":"slomoclock.app.js","url":"app.js"}, + {"name":"slomoclock.img","url":"app-icon.js","evaluate":true} ], "data": [ - {"name":"speedclock.json"} + {"name":"slomoclock.json"} ] }, { "id": "de-stress", From 3ff66912860887546c6c52e704042a5d1872091b Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:36:23 +1300 Subject: [PATCH 0357/1062] Delete apps/speedclock directory --- apps/speedclock/ChangeLog | 1 - apps/speedclock/README.md | 21 ---- apps/speedclock/app-icon.js | 1 - apps/speedclock/app.js | 187 ------------------------------------ apps/speedclock/watch.png | Bin 1439 -> 0 bytes 5 files changed, 210 deletions(-) delete mode 100644 apps/speedclock/ChangeLog delete mode 100644 apps/speedclock/README.md delete mode 100644 apps/speedclock/app-icon.js delete mode 100644 apps/speedclock/app.js delete mode 100644 apps/speedclock/watch.png diff --git a/apps/speedclock/ChangeLog b/apps/speedclock/ChangeLog deleted file mode 100644 index c31405e08..000000000 --- a/apps/speedclock/ChangeLog +++ /dev/null @@ -1 +0,0 @@ -0.01: Created app diff --git a/apps/speedclock/README.md b/apps/speedclock/README.md deleted file mode 100644 index b4a3c83a7..000000000 --- a/apps/speedclock/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Morphing Clock Plus - -Based on Morphing Clock with more readable seconds and date, and an additional simple stopwatch. - -![](Screenshot.JPG) - -## Usage - -In addition to the Morphing Clock, a simple stopwatch can be started in the lower part of the display. - -BTN3 starts and stops the stopwatch. - -BTN1 resets/clears the stopwatch. - -## Requests - -Please leave bug reports and requests by raising an issue [here](https://github.com/skauertz/BangleApps). - -## Creator - -Sebastian Kauertz https://github.com/skauertz diff --git a/apps/speedclock/app-icon.js b/apps/speedclock/app-icon.js deleted file mode 100644 index 22e264124..000000000 --- a/apps/speedclock/app-icon.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("oFAwhC/ABOIABgfymYAKD+Z/9hGDL5c4wAf/XzjASTxqgQhAfPMB2IPxiACIBo+BDxqACIBg+CLxpANHwQPBABgvCIBT8CJ5owDD5iPOOAQfLBojiDCYQGFGIQfICIQfdBYJNMOI6SHD8jeNOIYzID8hfRD9LfEAoTdFBIifLAAIffBoQRBAJpxMD84JCD+S/GL56fID8ALBb6ZhID8qtJCZ4fgT4YDBABq/PD7RNEL6IRKD8WID5pfCD5kzNhKSFmYfMBwSeOGBoPDABgvCJ5wAON5pADABivPIAIAOd5xABABweOD4J+OD58IQBj8LD/6gUDyAfhXzgfiP/wA2")) diff --git a/apps/speedclock/app.js b/apps/speedclock/app.js deleted file mode 100644 index 28e9494f4..000000000 --- a/apps/speedclock/app.js +++ /dev/null @@ -1,187 +0,0 @@ -/* -Simple watch [speedwatch] -Mike Bennett mike[at]kereru.com -0.01 : Initial -*/ - -var v='0.01'; - -// timeout used to update every minute -var drawTimeout; -var x,y,w,h; - -// Variables for the stopwatch -var counter = -1; // Counts seconds -var oldDate = new Date(2020,0,1); // Initialize to a past date -var swInterval; // The interval's id -var B3 = 0; // Flag to track BTN3's current function -var w1; // watch id for BTN1 -var w3; // watch id for BTN3 - -// Colours -var colTime = 0x4FE0; -var colDate = 0xEFE0; -var colSW = 0x1DFD; - -// schedule a draw for the next minute -function queueDraw() { - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function() { - drawTimeout = undefined; - draw(); - }, 60000 - (Date.now() % 60000)); -} - -function stopWatch(clear) { - - x = 120; - y = 200; - w = 240; - h = 25; - - g.reset(); - g.setColor(colSW); - g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background - if (clear) return; - - counter++; - - var hrs = Math.floor(counter/3600); - var mins = Math.floor((counter-hrs*3600)/60); - var secs = counter - mins*60 - hrs*3600; - - // When starting the stopwatch: - if (B3) { - // Set BTN3 to stop the stopwatch and bind itself to restart it: - w3=setWatch(() => {clearInterval(swInterval); - swInterval=undefined; - if (w3) {clearWatch(w3);w3=undefined;} - setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, - BTN3, {repeat:false,edge:"falling"}); - B3 = 1;}, - BTN3, {repeat:false,edge:"falling"}); - B3 = 0; // BTN3 is bound to stop the stopwatch - } - - // Bind BTN1 to call the reset function: - if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); - - // Draw elapsed time: - g.setFontAlign(0,1); - g.setFont("Vector24"); - - var swStr = ("0"+parseInt(mins)).substr(-2) + ':' + ("0"+parseInt(secs)).substr(-2); - - if (hrs>0) swStr = ("0"+parseInt(hrs)).substr(-2) + ':' + swStr; - - g.drawString(swStr, x, y, true); - -} - -function resetStopWatch() { - - // Stop the interval if necessary: - if (swInterval) { - clearInterval(swInterval); - swInterval=undefined; - } - - // Clear the stopwatch: - stopWatch(true); - - // Restore the date - drawDate(); - - // Reset the counter: - counter = -1; - - // Set BTN3 to start the stopwatch again: - if (!B3) { - // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: - if (w3) {clearWatch(w3);w3=undefined;} - // Set BTN3 to start the watch again: - setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); - B3 = 1; // BTN3 is bound to start the stopwatch - } - - // Reset watch on BTN1: - if (w1) {clearWatch(w1);w1=undefined;} -} - - -function drawDate() { - // draw date - x = 120; - y = 200; - w = 240; - h = 25; - - g.reset(); - g.setColor(colDate); - g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background - - var date = new Date(); -// var dateStr = require("locale").date(date,1); - var dateStr = date.getDate() + ' ' +require("locale").month(date,1); - g.setFontAlign(0,1); - g.setFont("Vector24"); - g.drawString(dateStr,x,y); - -} - - -function drawTime() { - x = 120; - y = 107; - w = 120; - h = 134; - - var date = new Date(); - var timeStr = require("locale").time(date,1); - g.reset(); - g.clearRect(x-(w/2),y-(h/2),x+(w/2),y+(h/2)); // clear the background - g.setFontAlign(0,0); - g.setFontVector(85); - g.setColor(colTime); - g.drawString(timeStr.substring(0,2),x,y-30); - g.drawString(timeStr.substring(3,5),x,y+38); -} - -function draw() { - x = g.getWidth()/2; - y = g.getHeight()/2; - g.reset(); - - drawTime(); - if ( counter < 0 ) drawDate(); // Only draw date when SW is not running. - - // queue draw in one minute - queueDraw(); -} - -// Clear the screen once, at startup -g.clear(); - -// draw immediately at first, queue update -draw(); - -// Stop updates when LCD is off, restart when on -Bangle.on('lcdPower',on=>{ - if (on) { - draw(); // draw immediately, queue redraw - } else { // stop draw timer - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = undefined; - } -}); - -// Show launcher when middle button pressed -Bangle.setUI("clock"); - -// Start stopwatch when BTN3 is pressed -setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); -B3 = 1; // BTN3 is bound to start the stopwatch - -// Load widgets -Bangle.loadWidgets(); -Bangle.drawWidgets(); diff --git a/apps/speedclock/watch.png b/apps/speedclock/watch.png deleted file mode 100644 index b77f302d5291d39650ffd4c5400d0a7dd3e30d70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1439 zcmV;Q1z`G#P)ZZKIyp>XUbK#j8aEHE7Ds0zs-Qe=?maLsuD zcwH7zo-85*pPUNHrrgZ`eM~B!Lz#R329Fd4q?A-M035B_i!(14%b?7NZzmsOB(?~JQq2Hvi}HypfQSL?Z2&l2ULZ_o zg+qaL8-!-vwitv1pv#|LDDtV;SL79?vqWW)7sdH`07Fnl8VCo#kpJO8fxNdW6EFY= z%l80)kp{wij?s8|GN7v7I{+#~JK_+3YT9bUtq*JBP6-?m=93VkXj=a=!t=$vcL13+R`L*M8e?$7=E zR0)1v31I3q9xl!|L4C>F+7N)>qhMlw8GU1O(1^eoyEN2R*(Kxs+`J3Kj`vPq)QCE- zj?bf|t_%lC^U>N6z^B7A;9ZtGng#qG{kDPV0<@Hi&I15rG}Knv#TdJ!r3eGjg-w0p zg{1;ccK=qJf^vcwJDGmXkbS!Iq41PxAR--ATaG=p?_-RHh`K^Ou};y{GYvo}92?X? zr!86Bc=B7>3{MHuSSUbuzV5jKR2Td3_5FWrk!E_r+jU<$(v)Pne`qUl&K@eoD;0&p z9m^6-_YbA)2Uz8<+c5Se?d(qF_5@{+$jV(_$WWfG09?B_kLqHdsI46d^8he52%0NtVRREr&>#+Z@)vNV}NW(Tno5*=D%GKUkmWOTLadkx!v}5ucsrMYk}ePKP={9tMkm8@Yd~E zwU<=~WQN=|V{8idvY={d+dx^FjR^heGZAfZwtTQ-FP;P>i> zqQ`ruFS;6p_0yq^eQ*j#0Z1fM0Dz|25~vCT5p}v2gmveMs8dxKnrcfn^@$f;9X8em z0l(+}^TEl5W%T_rhc$9{B2XE_J9VXascesaLVPEVcp?da-HLPskJ;tnQh=tdyaix1 zo=D=epW}G4qM@cFAMe(e2}_X&ey@s_`m(3a-_@Nbuqa47%frPj+0d2Qa;zn@ULXO; zsS= z_SOj-_-5iEZci_Q7%mLASA8tPC0;4Ylzsphk1xTEiI4#NI=5sP?-%704#3T}eLn*1 zkH;$-rXR10(ph5W(JCG-YXBiO+)??HD4+ORfH4B-O8~eRw>~}-#rWU44N`DbMrDee z?**9BuTbjzNNf=!u|*k_c8m(kACUFg91#ok#GJj1J4B>CsO%U8Y%ExE?W-|Kg;}{h tLD__^eKjTtG8N$3{-Nx%fgE--{sVHTSvKO1ec%89002ovPDHLkV1gjPojw2n From 91a5c56eeecede87d395fff331ea7707a34f47ee Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 21:55:48 +1300 Subject: [PATCH 0358/1062] Update app.js --- apps/slomoclock/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 28e9494f4..c2b504a75 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -1,5 +1,5 @@ /* -Simple watch [speedwatch] +Simple watch [slomoclock] Mike Bennett mike[at]kereru.com 0.01 : Initial */ From d415356bb750356de2359496bfec6993c21a418a Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 22:52:09 +1300 Subject: [PATCH 0359/1062] Update app.js --- apps/slomoclock/app.js | 43 +++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index c2b504a75..b4c6727bd 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -4,7 +4,7 @@ Mike Bennett mike[at]kereru.com 0.01 : Initial */ -var v='0.01'; +var v='0.02'; // timeout used to update every minute var drawTimeout; @@ -32,6 +32,7 @@ function queueDraw() { }, 60000 - (Date.now() % 60000)); } +/* function stopWatch(clear) { x = 120; @@ -108,22 +109,23 @@ function resetStopWatch() { if (w1) {clearWatch(w1);w1=undefined;} } +*/ function drawDate() { // draw date - x = 120; - y = 200; - w = 240; - h = 25; + x = 240; + y = 40; + w = 25; + h = 90; g.reset(); g.setColor(colDate); - g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background + g.clearRect(x-w,y,x,y+h); // clear the background var date = new Date(); // var dateStr = require("locale").date(date,1); var dateStr = date.getDate() + ' ' +require("locale").month(date,1); - g.setFontAlign(0,1); + g.setFontAlign(1,1,3); g.setFont("Vector24"); g.drawString(dateStr,x,y); @@ -132,19 +134,30 @@ function drawDate() { function drawTime() { x = 120; - y = 107; - w = 120; - h = 134; + y = 120; + w = 130; + h = 160; var date = new Date(); var timeStr = require("locale").time(date,1); + var t = parseFloat(timeStr); + + if ( t < 24 ) colTime = 0x01BD; + if ( t < 19 ) colTime = 0x701F; + if ( t < 18 ) colTime = 0xEC80; + if ( t < 17 ) colTime = 0xF780; + if ( t < 12 ) colTime = 0xAEC2; + if ( t < 7 ) colTime = 0x1EC2; + if ( t < 6 ) colTime = 0x01BD; + + g.reset(); g.clearRect(x-(w/2),y-(h/2),x+(w/2),y+(h/2)); // clear the background g.setFontAlign(0,0); - g.setFontVector(85); + g.setFontVector(100); g.setColor(colTime); - g.drawString(timeStr.substring(0,2),x,y-30); - g.drawString(timeStr.substring(3,5),x,y+38); + g.drawString(timeStr.substring(0,2),x,y-35); + g.drawString(timeStr.substring(3,5),x,y+49); } function draw() { @@ -179,8 +192,8 @@ Bangle.on('lcdPower',on=>{ Bangle.setUI("clock"); // Start stopwatch when BTN3 is pressed -setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); -B3 = 1; // BTN3 is bound to start the stopwatch +//setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); +//B3 = 1; // BTN3 is bound to start the stopwatch // Load widgets Bangle.loadWidgets(); From 96109735716f1de861c7b57c648b6341c2ce1425 Mon Sep 17 00:00:00 2001 From: nujw Date: Sat, 23 Oct 2021 22:52:49 +1300 Subject: [PATCH 0360/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 22f75982a..d251f4193 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.01", + "version":"0.02", "description": "Simple 24h clock face with large digits hour above minutes.", "tags": "clock", "type":"clock", From c139651b4f70ec9b07910ebdd86df12098986e08 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 23 Oct 2021 20:25:35 +0100 Subject: [PATCH 0361/1062] GPS Touch: swipe left, right to change display --- apps.json | 15 +++ apps/gpstouch/README.md | 5 + apps/gpstouch/geotools.js | 128 +++++++++++++++++++ apps/gpstouch/gpstouch.app.js | 222 +++++++++++++++++++++++++++++++++ apps/gpstouch/gpstouch.icon.js | 1 + apps/gpstouch/gpstouch.png | Bin 0 -> 1571 bytes 6 files changed, 371 insertions(+) create mode 100644 apps/gpstouch/README.md create mode 100644 apps/gpstouch/geotools.js create mode 100644 apps/gpstouch/gpstouch.app.js create mode 100644 apps/gpstouch/gpstouch.icon.js create mode 100644 apps/gpstouch/gpstouch.png diff --git a/apps.json b/apps.json index 51cca784b..b1ad57074 100644 --- a/apps.json +++ b/apps.json @@ -4037,5 +4037,20 @@ {"name":"vernierrespirate.img","url":"app-icon.js","evaluate":true} ], "data": [{"name":"vernierrespirate.json"}] + }, + { + "id": "gpstouch", + "name": "GPS Touch", + "version": "0.01", + "description": "A touch based GPS watch, shows OS map reference", + "icon": "gpstouch.png", + "tags": "tools,app", + "supports": ["BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"geotools","url":"geotools.js"}, + {"name":"gpstouch.app.js","url":"gpstouch.app.js"}, + {"name":"gpstouch.img","url":"gpstouch.icon.js","evaluate":true} + ] } ] diff --git a/apps/gpstouch/README.md b/apps/gpstouch/README.md new file mode 100644 index 000000000..1d6bb5d17 --- /dev/null +++ b/apps/gpstouch/README.md @@ -0,0 +1,5 @@ +# GPS Touch + +## Screenshots + + diff --git a/apps/gpstouch/geotools.js b/apps/gpstouch/geotools.js new file mode 100644 index 000000000..5adc57872 --- /dev/null +++ b/apps/gpstouch/geotools.js @@ -0,0 +1,128 @@ +/** + * + * A module of Geo functions for use with gps fixes + * + * let geo = require("geotools"); + * let os = geo.gpsToOSGrid(fix); + * let ref = geo.gpsToOSMapRef(fix); + * + */ + +Number.prototype.toRad = function() { return this*Math.PI/180; }; +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Ordnance Survey Grid Reference functions (c) Chris Veness 2005-2014 */ +/* - www.movable-type.co.uk/scripts/gridref.js */ +/* - www.movable-type.co.uk/scripts/latlon-gridref.html */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +function OsGridRef(easting, northing) { + this.easting = 0|easting; + this.northing = 0|northing; +} +OsGridRef.latLongToOsGrid = function(point) { + var lat = point.lat.toRad(); + var lon = point.lon.toRad(); + + var a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes + var F0 = 0.9996012717; // NatGrid scale factor on central meridian + var lat0 = (49).toRad(), lon0 = (-2).toRad(); // NatGrid true origin is 49�N,2�W + var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres + var e2 = 1 - (b*b)/(a*a); // eccentricity squared + var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n; + + var cosLat = Math.cos(lat), sinLat = Math.sin(lat); + var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature + var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature + var eta2 = nu/rho-1; + + var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0); + var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0); + var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0)); + var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0)); + var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc + + var cos3lat = cosLat*cosLat*cosLat; + var cos5lat = cos3lat*cosLat*cosLat; + var tan2lat = Math.tan(lat)*Math.tan(lat); + var tan4lat = tan2lat*tan2lat; + + var I = M + N0; + var II = (nu/2)*sinLat*cosLat; + var III = (nu/24)*sinLat*cos3lat*(5-tan2lat+9*eta2); + var IIIA = (nu/720)*sinLat*cos5lat*(61-58*tan2lat+tan4lat); + var IV = nu*cosLat; + var V = (nu/6)*cos3lat*(nu/rho-tan2lat); + var VI = (nu/120) * cos5lat * (5 - 18*tan2lat + tan4lat + 14*eta2 - 58*tan2lat*eta2); + + var dLon = lon-lon0; + var dLon2 = dLon*dLon, dLon3 = dLon2*dLon, dLon4 = dLon3*dLon, dLon5 = dLon4*dLon, dLon6 = dLon5*dLon; + + var N = I + II*dLon2 + III*dLon4 + IIIA*dLon6; + var E = E0 + IV*dLon + V*dLon3 + VI*dLon5; + + return new OsGridRef(E, N); +}; + +/* + * converts northing, easting to standard OS grid reference. + * + * [digits=10] - precision (10 digits = metres) + * to_map_ref(8, 651409, 313177); => 'TG 5140 1317' + * to_map_ref(0, 651409, 313177); => '651409,313177' + * + */ +function to_map_ref(digits, easting, northing) { + if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing + + let e = easting; + let n = northing; + + // use digits = 0 to return numeric format (in metres) - note northing may be >= 1e7 + if (digits == 0) { + const format = { useGrouping: false, minimumIntegerDigits: 6, maximumFractionDigits: 3 }; + const ePad = e.toLocaleString('en', format); + const nPad = n.toLocaleString('en', format); + return `${ePad},${nPad}`; + } + + // get the 100km-grid indices + const e100km = Math.floor(e / 100000), n100km = Math.floor(n / 100000); + + // translate those into numeric equivalents of the grid letters + let l1 = (19 - n100km) - (19 - n100km) % 5 + Math.floor((e100km + 10) / 5); + let l2 = (19 - n100km) * 5 % 25 + e100km % 5; + + // compensate for skipped 'I' and build grid letter-pairs + if (l1 > 7) l1++; + if (l2 > 7) l2++; + const letterPair = String.fromCharCode(l1 + 'A'.charCodeAt(0), l2 + 'A'.charCodeAt(0)); + + // strip 100km-grid indices from easting & northing, and reduce precision + e = Math.floor((e % 100000) / Math.pow(10, 5 - digits / 2)); + n = Math.floor((n % 100000) / Math.pow(10, 5 - digits / 2)); + + // pad eastings & northings with leading zeros + e = e.toString().padStart(digits/2, '0'); + n = n.toString().padStart(digits/2, '0'); + + return `${letterPair} ${e} ${n}`; +} + +/** + * + * Module exports section, example code below + * + * let geo = require("geotools"); + * let os = geo.gpsToOSGrid(fix); + * let ref = geo.gpsToOSMapRef(fix); + */ + +// get easting and northings +exports.gpsToOSGrid = function(gps_fix) { + return OsGridRef.latLongToOsGrid(gps_fix); +} + +// string with an OS Map grid reference +exports.gpsToOSMapRef = function(gps_fix) { + let os = OsGridRef.latLongToOsGrid(last_fix); + return to_map_ref(6, os.easting, os.northing); +} diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js new file mode 100644 index 000000000..3b2c54569 --- /dev/null +++ b/apps/gpstouch/gpstouch.app.js @@ -0,0 +1,222 @@ +const h = g.getHeight(); +const w = g.getWidth(); +let last_fix; + +function resetLastFix() { + last_fix = { + fix: 0, + alt: 0, + lat: 0, + lon: 0, + speed: 0, + time: 0, + course: 0, + satellites: 0 + }; +} + +function processFix(fix) { + last_fix.time = fix.time; + + if (fix.fix) { + if (!last_fix.fix) { + // we dont need to suppress this in quiet mode as it is user initiated + Bangle.buzz(); // buzz on first position + } + last_fix = fix; + } +} + +function draw() { + var d = new Date(); + var da = d.toString().split(" "); + var time = da[4].substr(0,5); + var hh = da[4].substr(0,2); + var mm = da[4].substr(3,2); + + g.reset(); + drawTop(d,hh,mm); + drawInfo(); +} + +function drawTop(d,hh,mm) { + g.setFont("Vector", w/3); + g.setFontAlign(0, 0); + g.setColor(g.theme.bg); + g.fillRect(0, 24, w, ((h-24)/2) + 24); + g.setColor(g.theme.fg); + + g.setFontAlign(1,0); // right aligned + g.drawString(hh, (w/2) - 6, ((h-24)/4) + 24); + g.setFontAlign(-1,0); // left aligned + g.drawString(mm, (w/2) + 6, ((h-24)/4) + 24); + + // for the colon + g.setFontAlign(0,0); // centre aligned + if (d.getSeconds()&1) g.drawString(":", w/2, ((h-24)/4) + 24); +} + +function drawInfo() { + if (infoData[infoMode] && infoData[infoMode].calc) { + g.setFont("Vector", w/7); + g.setFontAlign(0, 0); + + if (infoData[infoMode].get_color) + g.setColor(infoData[infoMode].get_color()); + else + g.setColor(g.theme.bgH); + g.fillRect(0, ((h-24)/2) + 24 + 1, w, h); + + if (infoData[infoMode].is_control) + g.setColor("#fff"); + else + g.setColor(g.theme.fg); + + g.drawString((infoData[infoMode].calc()), w/2, (3*(h-24)/4) + 24); + } +} + +const infoData = { + ID_LAT: { + calc: () => 'Lat: ' + last_fix.lat.toFixed(4), + }, + ID_LON: { + calc: () => 'Lon: ' + last_fix.lon.toFixed(4), + }, + ID_SPEED: { + calc: () => 'Speed: ' + last_fix.speed.toFixed(1), + }, + ID_ALT: { + calc: () => 'Alt: ' + last_fix.alt.toFixed(0), + }, + ID_COURSE: { + calc: () => 'Course: '+ last_fix.course.toFixed(0), + }, + ID_SATS: { + calc: () => 'Satelites: ' + last_fix.satellites, + }, + ID_TIME: { + calc: () => formatTime(last_fix.time), + }, + OS_REF: { + calc: () => 'NZ 208 987', + }, + GPS_POWER: { + calc: () => (Bangle.isGPSOn()) ? 'GPS On' : 'GPS Off', + action: () => toggleGPS(), + get_color: () => Bangle.isGPSOn() ? '#f00' : '#00f', + is_control: true, + }, + GPS_LOGGER: { + calc: () => 'Logger ' + loggerStatus(), + action: () => toggleLogger(), + get_color: () => loggerStatus() == "ON" ? '#f00' : '#00f', + is_control: true, + }, +}; + +function toggleGPS() { + Bangle.setGPSPower(Bangle.isGPSOn() ? 0 : 1, 'gpstouch'); + // add or remove listenner + if (Bangle.isGPSOn()) + Bangle.on('GPS', processFix); + else + Bangle.removeListener("GPS", processFix); + resetLastFix(); +} + +function loggerStatus() { + var settings = require("Storage").readJSON("gpsrec.json",1)||{}; + if (settings == {}) return "Install"; + return settings.recording ? "ON" : "OFF"; +} + +function toggleLogger() { + var settings = require("Storage").readJSON("gpsrec.json",1)||{}; + if (settings == {}) return; + + settings.recording = !settings.recording; + require("Storage").write("gpsrec.json", settings); + + if (WIDGETS["gpsrec"]) + WIDGETS["gpsrec"].reload(); + + // not sure if safe to register a listenner again + if (Bangle.isGPSOn()) + Bangle.on('GPS', processFix); +} + +function formatTime(now) { + try { + var fd = now.toUTCString().split(" "); + return fd[4]; + } catch (e) { + return "00:00:00"; + } +} + +const infoList = Object.keys(infoData).sort(); +let infoMode = infoList[0]; + +function nextInfo() { + let idx = infoList.indexOf(infoMode); + if (idx > -1) { + if (idx === infoList.length - 1) infoMode = infoList[0]; + else infoMode = infoList[idx + 1]; + } +} + +function prevInfo() { + let idx = infoList.indexOf(infoMode); + if (idx > -1) { + if (idx === 0) infoMode = infoList[infoList.length - 1]; + else infoMode = infoList[idx - 1]; + } +} + +Bangle.on('swipe', dir => { + if (dir == 1) prevInfo() else nextInfo(); + draw(); +}); + +let prevTouch = 0; + +Bangle.on('touch', function(button, xy) { + let dur = 1000*(getTime() - prevTouch); + prevTouch = getTime(); + + if (dur <= 1000 && xy.y < h/2 && infoData[infoMode].is_control) { + Bangle.buzz(); + if (infoData[infoMode] && infoData[infoMode].action) { + infoData[infoMode].action(); + draw(); + } + } +}); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower', on => { + if (secondInterval) + clearInterval(secondInterval); + secondInterval = undefined; + if (on) + secondInterval = setInterval(draw, 1000); + draw(); +}); + + +resetLastFix(); + +// add listenner if already powered on, plus tag app +if (Bangle.isGPSOn()) { + Bangle.setGPSPower(1, 'gpstouch'); + Bangle.on('GPS', processFix); +} + +g.clear(); +var secondInterval = setInterval(draw, 1000); +draw(); +// Show launcher when button pressed +Bangle.setUI("clock"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/gpstouch/gpstouch.icon.js b/apps/gpstouch/gpstouch.icon.js new file mode 100644 index 000000000..c4cf85676 --- /dev/null +++ b/apps/gpstouch/gpstouch.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///j+EAYO/uYDB//wCYcPBA4AFh/ABZMDBbkX6gLIgtX6tQBY9VBYNVBY0BBYdABYsFqoACEgQLDitVtWpqtUBYtVq2q1WVGAQLErQLB0oLFHQNqBYIkBHgMDIwYKBAAJIDIweqz/2BYJtDBYI6Bv/9HgILHYwILGh4gBBYWfbooLF6AjPBYW//wLGL4Wv/RfGNZaDIBYibEBYizIBYjLDBYzXBd4TXCBZ60BBYRqEBZpUBBYRSFJAQLCA4b7BHgQLFgYLGIwYLEgoLBHQYLEgILBHQYLEgALBAoYLFi/UBZMHBZUD6ALKApQAFBbHwBZMP/4ABBwgIDA=")) diff --git a/apps/gpstouch/gpstouch.png b/apps/gpstouch/gpstouch.png new file mode 100644 index 0000000000000000000000000000000000000000..c411356ae69347a83882fe195782df211aa629e9 GIT binary patch literal 1571 zcmV+;2Hg3HP)7!JOvVz*38;=4<&H5LVRE7i! zwE@)hpWulvKu;xEMTMGS7!-*xS1GdF+PWqHWMt@9fSV%xU>F?sdga$tN-6RC zc>^dn3?@fxJ#7!&a)Df>SSW(u?^pGJ*X^c8L{vPhdGzZM^83|YAaiF|hEwH1-av$*f)f8Y~qFARB^73HnR2V(_5xr7y^Cn!p2z7PP*%_}x)2Obf zP;<)4n!xDr40nTEmM?i{9J z%~@k5kQZyu%Y))#NKfyZBO?P=tbmy_6E;7m>%3)26G%{rT%EM6S> zm(4;1mMnqs;}bUf%y4L^%OFA1X#)Y3uN1-FHeH935*Ru(-v95`dt7R40${T1S%zi~ ziSxO87pkft9FDdt#Z0ePem0}abSE|pmd5_e)2GLaS4y$B<^+e1pP{Q;nXWrnws;OJ zUwaAZMUs^j;i#*NOZO^ZssPh{K!h2wwkcEM{XReR9o0uqJrobn)fMK;+VA<|tCMm5 zi8vDFN(L*Pcyz2GCnx&){(}$)zCN9lp!(Qp9<+y|Y-7fl%ashKAEDOPuBocmFDfA%F)r$N#l)Xawm>^XmdIt6zZhC{gI!hNv>I4rVDU57=DanXj1DWu*^Xjx}7r9)0c9bymH(Fe$;R6^qdI=sR%jT0++WDIjjJyKo`i zY2KU}Y!%hP&Te!&dbkFkMqBHF`h%j+z<*D9wANRq+vwc>g%Do8AgqY zw!b)g8gpmm(%fwaSFggJJtp?dz)Q7`-09fd@eM2eM+u0+sRe;YYe<;Prc4CuYpVuq#b8Qo_y)TrN zg%?t?&EBGEyl*$yb^+_wNl4Rp7wE9d(T&w*Wpc}IuvSo787wNV7 zk4FyJ2`nnvzNDbEGN_3n-#{kWRWNB1+eP@+YMfGBN=swC-Mj`eNfi10J|8G9mT*@W z%YjA9ioJUX_XXY Date: Sat, 23 Oct 2021 21:08:32 +0100 Subject: [PATCH 0362/1062] Added change log for GPS Touch --- apps/gpstouch/Changelog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/gpstouch/Changelog diff --git a/apps/gpstouch/Changelog b/apps/gpstouch/Changelog new file mode 100644 index 000000000..7f837e50e --- /dev/null +++ b/apps/gpstouch/Changelog @@ -0,0 +1 @@ +0.01: First version From 9c7f734afe35ea18ebea6507bde0e983a3417f06 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Sat, 23 Oct 2021 21:21:36 +0100 Subject: [PATCH 0363/1062] Add BETA thumbnailer code that can use the JS emulator to create thumbnails for Bangle.js 1 and 2 --- bin/thumbnailer.js | 84 ++++++++++++++++++++++++++++++++++++++++++++++ core | 2 +- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100755 bin/thumbnailer.js diff --git a/bin/thumbnailer.js b/bin/thumbnailer.js new file mode 100755 index 000000000..922217fb2 --- /dev/null +++ b/bin/thumbnailer.js @@ -0,0 +1,84 @@ +#!/usr/bin/node + +var EMULATOR = "banglejs1"; + +var appId; + +if (process.argv.length!=3) { + console.log("USAGE:"); + console.log(" bin/thumbnailer.jd APP_ID"); + process.exit(1); +} +appId = process.argv[2]; +imageFn = "out.png"; + +if (!require("fs").existsSync(__dirname + "/../../EspruinoWebIDE")) { + console.log("You need to:"); + console.log(" git clone https://github.com/espruino/EspruinoWebIDE"); + console.log("At the same level as this project"); + process.exit(1); +} + +eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/emulator_"+EMULATOR+".js").toString()); +eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/emu_"+EMULATOR+".js").toString()); +eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/common.js").toString()); + +var SETTINGS = { + pretokenise : true +}; +var Const = { +}; +module = undefined; +eval(require("fs").readFileSync(__dirname + "/../core/lib/espruinotools.js").toString()); +eval(require("fs").readFileSync(__dirname + "/../core/js/utils.js").toString()); +eval(require("fs").readFileSync(__dirname + "/../core/js/appinfo.js").toString()); +var apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json")); +var app = apps.find(a=>a.id==appId); +if (!app) ERROR(`App ${JSON.stringify(appId)} not found`); +if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); + + +jsRXCallback = function() {}; +jsUpdateGfx = function() {}; + +// wait until loaded... +setTimeout(function() { + console.log("Loaded..."); + jsInit(); + jsIdle(true); // not automatic + + AppInfo.getFiles(app, { + fileGetter:function(url) { + console.log(__dirname+"/"+url); + return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); + }, settings : SETTINGS}).then(files => { + //console.log(files); + var command = "Bangle.factoryReset()\n"; + command += files.map(f=>f.cmd).join("\n")+"\n"; + command += `load("${appId}.app.js")\n`; + //console.log(command); + console.log("Uploading..."); + jsTransmitString(command); + console.log("Done."); + jsIdle(); + jsIdle(); + jsIdle(); + jsStopIdle(); + + var rgba = new Uint8Array(GFX_WIDTH*GFX_HEIGHT*4); + jsGetGfxContents(rgba); + + var Jimp = require("jimp"); + let image = new Jimp(GFX_WIDTH, GFX_HEIGHT, function (err, image) { + if (err) throw err; + let buffer = image.bitmap.data; + buffer.set(rgba); + image.write(imageFn, (err) => { + if (err) throw err; + console.log("Image written as "+imageFn); + }); + }); +}); + + +}); diff --git a/core b/core index 3a2c706b4..8bbdf6992 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 3a2c706b4cdf02e5365b191103c80d587b3ace5a +Subproject commit 8bbdf699210ab4d265a29a2bb0fd823cb5bca78a From dc3dda8235a5ba6892c671c124a149e4dd14e2bb Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 23 Oct 2021 21:46:43 +0100 Subject: [PATCH 0364/1062] Added README.md files into apps.json --- apps.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps.json b/apps.json index b1ad57074..8ff29bdd3 100644 --- a/apps.json +++ b/apps.json @@ -458,6 +458,7 @@ "icon": "clock-analog.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -473,6 +474,7 @@ "icon": "clock2x3.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -503,6 +505,7 @@ "description": "T-Rex game in the style of Chrome's offline game", "icon": "trex.png", "tags": "game", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -1093,6 +1096,7 @@ "icon": "icon.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -1312,6 +1316,7 @@ "description": "A Flappy Bird game clone", "icon": "app.png", "tags": "game", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -1386,6 +1391,7 @@ "icon": "bold_clock.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -3941,6 +3947,7 @@ "icon": "app.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -3956,6 +3963,7 @@ "icon": "app.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ @@ -4002,6 +4010,7 @@ "icon": "app.png", "type": "clock", "tags": "clock", + "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, "storage": [ From f5fa689b42849522626040e782ae47476591f8a0 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 10:26:28 +1300 Subject: [PATCH 0365/1062] Update app.js --- apps/slomoclock/app.js | 208 ++++++++++------------------------------- 1 file changed, 47 insertions(+), 161 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index b4c6727bd..d9e0683c7 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -2,145 +2,41 @@ Simple watch [slomoclock] Mike Bennett mike[at]kereru.com 0.01 : Initial +0.03 : Use Layout library */ -var v='0.02'; +var v='0.03'; -// timeout used to update every minute -var drawTimeout; -var x,y,w,h; +var Layout = require("Layout"); +var layout = new Layout( { + type:"v", c: [ + {type:undefined, height:40 }, // Widgets top -// Variables for the stopwatch -var counter = -1; // Counts seconds -var oldDate = new Date(2020,0,1); // Initialize to a past date -var swInterval; // The interval's id -var B3 = 0; // Flag to track BTN3's current function -var w1; // watch id for BTN1 -var w3; // watch id for BTN3 + {type:"h", c: [ + {type:"v", c: [ + {type:"txt", font:"40%", label:"", id:"hour", valign:1}, + {type:"txt", font:"40%", label:"", id:"min", valign:-1}, + ]}, + {type:"v", c: [ + {type:"txt", font:"10%", label:"", id:"day", col:0xEFE0, halign:1}, + {type:"txt", font:"10%", label:"", id:"mon", col:0xEFE0, halign:1}, + ]} + ]}, -// Colours -var colTime = 0x4FE0; -var colDate = 0xEFE0; -var colSW = 0x1DFD; + {type:undefined, height:40 }, // Widgets bottom -// schedule a draw for the next minute -function queueDraw() { - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = setTimeout(function() { - drawTimeout = undefined; - draw(); - }, 60000 - (Date.now() % 60000)); -} - -/* -function stopWatch(clear) { - - x = 120; - y = 200; - w = 240; - h = 25; - - g.reset(); - g.setColor(colSW); - g.clearRect(x-(w/2),y-h,x+(w/2),y); // clear the background - if (clear) return; + ] - counter++; +}, {lazy:true}); - var hrs = Math.floor(counter/3600); - var mins = Math.floor((counter-hrs*3600)/60); - var secs = counter - mins*60 - hrs*3600; - - // When starting the stopwatch: - if (B3) { - // Set BTN3 to stop the stopwatch and bind itself to restart it: - w3=setWatch(() => {clearInterval(swInterval); - swInterval=undefined; - if (w3) {clearWatch(w3);w3=undefined;} - setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, - BTN3, {repeat:false,edge:"falling"}); - B3 = 1;}, - BTN3, {repeat:false,edge:"falling"}); - B3 = 0; // BTN3 is bound to stop the stopwatch - } - - // Bind BTN1 to call the reset function: - if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); - - // Draw elapsed time: - g.setFontAlign(0,1); - g.setFont("Vector24"); - - var swStr = ("0"+parseInt(mins)).substr(-2) + ':' + ("0"+parseInt(secs)).substr(-2); - - if (hrs>0) swStr = ("0"+parseInt(hrs)).substr(-2) + ':' + swStr; - - g.drawString(swStr, x, y, true); - -} - -function resetStopWatch() { - - // Stop the interval if necessary: - if (swInterval) { - clearInterval(swInterval); - swInterval=undefined; - } - - // Clear the stopwatch: - stopWatch(true); - - // Restore the date - drawDate(); - - // Reset the counter: - counter = -1; - - // Set BTN3 to start the stopwatch again: - if (!B3) { - // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: - if (w3) {clearWatch(w3);w3=undefined;} - // Set BTN3 to start the watch again: - setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); - B3 = 1; // BTN3 is bound to start the stopwatch - } - - // Reset watch on BTN1: - if (w1) {clearWatch(w1);w1=undefined;} -} - -*/ - -function drawDate() { - // draw date - x = 240; - y = 40; - w = 25; - h = 90; - - g.reset(); - g.setColor(colDate); - g.clearRect(x-w,y,x,y+h); // clear the background - +// update the screen +function draw() { var date = new Date(); -// var dateStr = require("locale").date(date,1); - var dateStr = date.getDate() + ' ' +require("locale").month(date,1); - g.setFontAlign(1,1,3); - g.setFont("Vector24"); - g.drawString(dateStr,x,y); -} - - -function drawTime() { - x = 120; - y = 120; - w = 130; - h = 160; - - var date = new Date(); + // Update time var timeStr = require("locale").time(date,1); var t = parseFloat(timeStr); + var colTime; if ( t < 24 ) colTime = 0x01BD; if ( t < 19 ) colTime = 0x701F; @@ -148,53 +44,43 @@ function drawTime() { if ( t < 17 ) colTime = 0xF780; if ( t < 12 ) colTime = 0xAEC2; if ( t < 7 ) colTime = 0x1EC2; - if ( t < 6 ) colTime = 0x01BD; + if ( t < 6 ) colTime = 0x01BD; + + layout.hour.label = timeStr.substring(0,2); + layout.min.label = timeStr.substring(3,5); + layout.hour.col = colTime; + layout.min.col = colTime; - - g.reset(); - g.clearRect(x-(w/2),y-(h/2),x+(w/2),y+(h/2)); // clear the background - g.setFontAlign(0,0); - g.setFontVector(100); - g.setColor(colTime); - g.drawString(timeStr.substring(0,2),x,y-35); - g.drawString(timeStr.substring(3,5),x,y+49); + // Update date + layout.day.label = date.getDate(); + layout.mon.label = require("locale").month(date,1); + + layout.render(); } -function draw() { - x = g.getWidth()/2; - y = g.getHeight()/2; - g.reset(); - - drawTime(); - if ( counter < 0 ) drawDate(); // Only draw date when SW is not running. - - // queue draw in one minute - queueDraw(); -} - -// Clear the screen once, at startup -g.clear(); - -// draw immediately at first, queue update -draw(); +// Events // Stop updates when LCD is off, restart when on Bangle.on('lcdPower',on=>{ + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; if (on) { - draw(); // draw immediately, queue redraw - } else { // stop draw timer - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = undefined; + secondInterval = setInterval(draw, 10000); + draw(); // draw immediately } }); +var secondInterval = setInterval(draw, 10000); + + + +// update time and draw +g.clear(); +draw(); + // Show launcher when middle button pressed Bangle.setUI("clock"); -// Start stopwatch when BTN3 is pressed -//setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); -//B3 = 1; // BTN3 is bound to start the stopwatch - // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); From 13dc813cc14bbad52d6b7ef93452b1c7677be06b Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 10:28:11 +1300 Subject: [PATCH 0366/1062] Update apps.json --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index d251f4193..56c77d6e9 100644 --- a/apps.json +++ b/apps.json @@ -2920,8 +2920,8 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.02", - "description": "Simple 24h clock face with large digits hour above minutes.", + "version":"0.03", + "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", "allow_emulator":true, From bce96a00007af45e8af77e21c84eea59ee39297e Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sat, 23 Oct 2021 22:31:12 +0100 Subject: [PATCH 0367/1062] Updated README and added screenshots --- apps/gpstouch/README.md | 13 ++++++++++++- apps/gpstouch/gpstouch.app.js | 4 ++-- apps/gpstouch/screenshot1.png | Bin 0 -> 2627 bytes apps/gpstouch/screenshot2.png | Bin 0 -> 2555 bytes apps/gpstouch/screenshot3.png | Bin 0 -> 2474 bytes apps/gpstouch/screenshot4.png | Bin 0 -> 2750 bytes 6 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 apps/gpstouch/screenshot1.png create mode 100644 apps/gpstouch/screenshot2.png create mode 100644 apps/gpstouch/screenshot3.png create mode 100644 apps/gpstouch/screenshot4.png diff --git a/apps/gpstouch/README.md b/apps/gpstouch/README.md index 1d6bb5d17..7329f9833 100644 --- a/apps/gpstouch/README.md +++ b/apps/gpstouch/README.md @@ -1,5 +1,16 @@ # GPS Touch +- A touch controlled GPS watch for Bangle JS 2 +- Key feature is the conversion of Lat/Lon into Ordinance Servey Grid Reference +- Swipe left and right to change the display +- Select GPS and switch the GPS On or Off by touching twice in the top half of the display +- Select LOGGER and switch the GPS Recorder On or Off by touching twice in the top half of the display +- Displays the GPS time in the bottom half of the screen when the GPS is powered on, otherwise 00:00:00 +- Select display of Course, Speed, Altitude, Longitude, Latitude, Ordinance Servey Grid Reference + ## Screenshots - +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) +![](screenshot4.png) diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js index 3b2c54569..9a022095d 100644 --- a/apps/gpstouch/gpstouch.app.js +++ b/apps/gpstouch/gpstouch.app.js @@ -1,5 +1,6 @@ const h = g.getHeight(); const w = g.getWidth(); +let geo = require("geotools"); let last_fix; function resetLastFix() { @@ -99,7 +100,7 @@ const infoData = { calc: () => formatTime(last_fix.time), }, OS_REF: { - calc: () => 'NZ 208 987', + calc: () => last_fix.lat == 0 ? "Searching.." : geo.gpsToOSMapRef(last_fix), }, GPS_POWER: { calc: () => (Bangle.isGPSOn()) ? 'GPS On' : 'GPS Off', @@ -204,7 +205,6 @@ Bangle.on('lcdPower', on => { draw(); }); - resetLastFix(); // add listenner if already powered on, plus tag app diff --git a/apps/gpstouch/screenshot1.png b/apps/gpstouch/screenshot1.png new file mode 100644 index 0000000000000000000000000000000000000000..03cb1e2a953dc8945c1c7dcacf0644992e5019e2 GIT binary patch literal 2627 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 literal 0 HcmV?d00001 From 81d34b22a4781cdef5f8c56e11f33f1ed66099b1 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 12:23:11 +1300 Subject: [PATCH 0368/1062] Create settings.js --- apps/slomoclock/settings.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 apps/slomoclock/settings.js diff --git a/apps/slomoclock/settings.js b/apps/slomoclock/settings.js new file mode 100644 index 000000000..0485400fe --- /dev/null +++ b/apps/slomoclock/settings.js @@ -0,0 +1,18 @@ +(function(back) { + + let settings = require('Storage').readJSON('slomoclock.json',1)||{}; + + function writeSettings() { + require('Storage').write('slomoclock.json',settings); + } + + const appMenu = { + '': {'title': 'SloMo Clock'}, + '< Back': back, + 'Widget Space Top' : {value : settings.widTop, format : v => v?"On":"Off",onchange : () => { settings.widTop = !settings.widTop; writeSettings(); }, + 'Widget Space Bottom' : {value : settings.widBot, format : v => v?"On":"Off",onchange : () => { settings.widBot = !settings.widBot; writeSettings(); } + }; + + E.showMenu(appMenu); + +}); From a534b8c688b564899e798a4fd3a9d3aa32262543 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 12:24:30 +1300 Subject: [PATCH 0369/1062] Update apps.json --- apps.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 56c77d6e9..e31fa73f7 100644 --- a/apps.json +++ b/apps.json @@ -2928,7 +2928,8 @@ "readme": "README.md", "storage": [ {"name":"slomoclock.app.js","url":"app.js"}, - {"name":"slomoclock.img","url":"app-icon.js","evaluate":true} + {"name":"slomoclock.img","url":"app-icon.js","evaluate":true}, + {"name":"slomoclock.settings.js","url":"settings.js"} ], "data": [ {"name":"slomoclock.json"} From 55cf1a1d64a4e59a6367caa745bfdf4a7c0953fc Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 12:29:58 +1300 Subject: [PATCH 0370/1062] Update app.js --- apps/slomoclock/app.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index d9e0683c7..96a269b4f 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -7,10 +7,15 @@ Mike Bennett mike[at]kereru.com var v='0.03'; +// Read settings. +let cfg = require('Storage').readJSON('slomoclock.json',1)||{}; +cfg.widTop = cfg.widTop==undefined?true:cfg.widTop; +cfg.widBot = cfg.widBot==undefined?true:cfg.widBot; + var Layout = require("Layout"); var layout = new Layout( { type:"v", c: [ - {type:undefined, height:40 }, // Widgets top + {type:undefined, height:widTop?40:0 }, // Widgets top {type:"h", c: [ {type:"v", c: [ @@ -23,7 +28,7 @@ var layout = new Layout( { ]} ]}, - {type:undefined, height:40 }, // Widgets bottom + {type:undefined, height:widBot?40:0 }, // Widgets bottom ] From 3ea42d0ab90df177e83fd66adb5eabc955cec6d7 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 12:59:42 +1300 Subject: [PATCH 0371/1062] Update app.js --- apps/slomoclock/app.js | 61 ++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 96a269b4f..271974b98 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -5,19 +5,38 @@ Mike Bennett mike[at]kereru.com 0.03 : Use Layout library */ -var v='0.03'; +var v='0.04'; -// Read settings. -let cfg = require('Storage').readJSON('slomoclock.json',1)||{}; -cfg.widTop = cfg.widTop==undefined?true:cfg.widTop; -cfg.widBot = cfg.widBot==undefined?true:cfg.widBot; +// Colours +const col = []; +col[0]= 0x001F; +col[1]= 0x023F; +col[2]= 0x039F; +col[3]= 0x051F; +col[4]= 0x067F; +col[5]= 0x07FD; +col[6]= 0x07F6; +col[7]= 0x07EF; +col[8]= 0x07E8; +col[9]= 0x07E3; +col[10]= 0x07E0; +col[11]= 0x5FE0; +col[12]= 0x97E0; +col[13]= 0xCFE0; +col[14]= 0xFFE0; +col[15]= 0xFE60; +col[16]= 0xFC60; +col[17]= 0xFAA0; +col[18]= 0xF920; +col[19]= 0xF803; +col[20]= 0xF80E; +col[21]= 0xF817; +col[22]= 0xE81F; +col[23]= 0x801F; var Layout = require("Layout"); var layout = new Layout( { - type:"v", c: [ - {type:undefined, height:widTop?40:0 }, // Widgets top - - {type:"h", c: [ + type:"h", c: [ {type:"v", c: [ {type:"txt", font:"40%", label:"", id:"hour", valign:1}, {type:"txt", font:"40%", label:"", id:"min", valign:-1}, @@ -26,12 +45,7 @@ var layout = new Layout( { {type:"txt", font:"10%", label:"", id:"day", col:0xEFE0, halign:1}, {type:"txt", font:"10%", label:"", id:"mon", col:0xEFE0, halign:1}, ]} - ]}, - - {type:undefined, height:widBot?40:0 }, // Widgets bottom - - ] - + ] }, {lazy:true}); // update the screen @@ -40,21 +54,12 @@ function draw() { // Update time var timeStr = require("locale").time(date,1); - var t = parseFloat(timeStr); - var colTime; + var hh = parseFloat(timeStr.substring(0,2)); - if ( t < 24 ) colTime = 0x01BD; - if ( t < 19 ) colTime = 0x701F; - if ( t < 18 ) colTime = 0xEC80; - if ( t < 17 ) colTime = 0xF780; - if ( t < 12 ) colTime = 0xAEC2; - if ( t < 7 ) colTime = 0x1EC2; - if ( t < 6 ) colTime = 0x01BD; - layout.hour.label = timeStr.substring(0,2); layout.min.label = timeStr.substring(3,5); - layout.hour.col = colTime; - layout.min.col = colTime; + layout.hour.col = col[hh]; + layout.min.col = col[hh]; // Update date layout.day.label = date.getDate(); @@ -77,8 +82,6 @@ Bangle.on('lcdPower',on=>{ var secondInterval = setInterval(draw, 10000); - - // update time and draw g.clear(); draw(); From 6de5ba92321f635aea29d17a6e8fe55bb1c62da0 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 13:00:59 +1300 Subject: [PATCH 0372/1062] Update apps.json --- apps.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps.json b/apps.json index e31fa73f7..ffca3dfcd 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.03", + "version":"0.04", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", @@ -2928,8 +2928,7 @@ "readme": "README.md", "storage": [ {"name":"slomoclock.app.js","url":"app.js"}, - {"name":"slomoclock.img","url":"app-icon.js","evaluate":true}, - {"name":"slomoclock.settings.js","url":"settings.js"} + {"name":"slomoclock.img","url":"app-icon.js","evaluate":true} ], "data": [ {"name":"slomoclock.json"} From e7e6b0db175a5281b102a38425f9c0ada0226c74 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 21:12:19 +1300 Subject: [PATCH 0373/1062] Update app.js --- apps/slomoclock/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 271974b98..96bcc08a0 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -30,9 +30,9 @@ col[17]= 0xFAA0; col[18]= 0xF920; col[19]= 0xF803; col[20]= 0xF80E; -col[21]= 0xF817; -col[22]= 0xE81F; -col[23]= 0x801F; +col[21]= 0x981F; +col[22]= 0x681F; +col[23]= 0x301F; var Layout = require("Layout"); var layout = new Layout( { From 3151c6b3a0da2797466addd5188ec455122c7d45 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 21:12:34 +1300 Subject: [PATCH 0374/1062] Update app.js --- apps/slomoclock/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 96bcc08a0..73ad7ba7d 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.03 : Use Layout library */ -var v='0.04'; +var v='0.05'; // Colours const col = []; From ec65ca790b32c791f1487ef06a193e28abfd5f62 Mon Sep 17 00:00:00 2001 From: nujw Date: Sun, 24 Oct 2021 21:13:11 +1300 Subject: [PATCH 0375/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index ffca3dfcd..5ad1d046d 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.04", + "version":"0.05", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", From 5e068166e133b8c9d8ec5ae29031380d4839952d Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sun, 24 Oct 2021 18:11:52 +0100 Subject: [PATCH 0376/1062] GPS touch, tweaks after real world testing --- apps.json | 2 +- apps/gpstouch/gpstouch.app.js | 50 ++++++++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/apps.json b/apps.json index 8ff29bdd3..f67557bdd 100644 --- a/apps.json +++ b/apps.json @@ -3510,7 +3510,7 @@ "icon": "simplest.png", "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"simplest.app.js","url":"app.js"}, diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js index 9a022095d..0425cdc23 100644 --- a/apps/gpstouch/gpstouch.app.js +++ b/apps/gpstouch/gpstouch.app.js @@ -2,6 +2,11 @@ const h = g.getHeight(); const w = g.getWidth(); let geo = require("geotools"); let last_fix; +let listennerCount = 0; + +function log_debug(o) { + //console.log(o); +} function resetLastFix() { last_fix = { @@ -18,11 +23,12 @@ function resetLastFix() { function processFix(fix) { last_fix.time = fix.time; - + log_debug(fix); + if (fix.fix) { if (!last_fix.fix) { // we dont need to suppress this in quiet mode as it is user initiated - Bangle.buzz(); // buzz on first position + Bangle.buzz(1500); // buzz on first position } last_fix = fix; } @@ -65,13 +71,13 @@ function drawInfo() { if (infoData[infoMode].get_color) g.setColor(infoData[infoMode].get_color()); else - g.setColor(g.theme.bgH); + g.setColor("#0ff"); g.fillRect(0, ((h-24)/2) + 24 + 1, w, h); if (infoData[infoMode].is_control) g.setColor("#fff"); else - g.setColor(g.theme.fg); + g.setColor("#000"); g.drawString((infoData[infoMode].calc()), w/2, (3*(h-24)/4) + 24); } @@ -100,7 +106,7 @@ const infoData = { calc: () => formatTime(last_fix.time), }, OS_REF: { - calc: () => last_fix.lat == 0 ? "Searching.." : geo.gpsToOSMapRef(last_fix), + calc: () => !last_fix.fix ? "OO 000 000" : geo.gpsToOSMapRef(last_fix), }, GPS_POWER: { calc: () => (Bangle.isGPSOn()) ? 'GPS On' : 'GPS Off', @@ -117,12 +123,24 @@ const infoData = { }; function toggleGPS() { + if (loggerStatus() == "ON") + return; + Bangle.setGPSPower(Bangle.isGPSOn() ? 0 : 1, 'gpstouch'); // add or remove listenner - if (Bangle.isGPSOn()) - Bangle.on('GPS', processFix); - else - Bangle.removeListener("GPS", processFix); + if (Bangle.isGPSOn()) { + if (listennerCount == 0) { + Bangle.on('GPS', processFix); + listennerCount++; + log_debug("listennerCount=" + listennerCount); + } + } else { + if (listennerCount > 0) { + Bangle.removeListener("GPS", processFix); + listennerCount--; + log_debug("listennerCount=" + listennerCount); + } + } resetLastFix(); } @@ -142,9 +160,11 @@ function toggleLogger() { if (WIDGETS["gpsrec"]) WIDGETS["gpsrec"].reload(); - // not sure if safe to register a listenner again - if (Bangle.isGPSOn()) + if (settings.recording && listennerCount == 0) { Bangle.on('GPS', processFix); + listennerCount++; + log_debug("listennerCount=" + listennerCount); + } } function formatTime(now) { @@ -208,9 +228,13 @@ Bangle.on('lcdPower', on => { resetLastFix(); // add listenner if already powered on, plus tag app -if (Bangle.isGPSOn()) { +if (Bangle.isGPSOn() || loggerStatus() == "ON") { Bangle.setGPSPower(1, 'gpstouch'); - Bangle.on('GPS', processFix); + if (listennerCount == 0) { + Bangle.on('GPS', processFix); + listennerCount++; + log_debug("listennerCount=" + listennerCount); + } } g.clear(); From 474d705c300b29be00ce91b5d3cfeef1542d9015 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 08:55:35 +1300 Subject: [PATCH 0377/1062] Update settings.js --- apps/slomoclock/settings.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/apps/slomoclock/settings.js b/apps/slomoclock/settings.js index 0485400fe..abf767fdc 100644 --- a/apps/slomoclock/settings.js +++ b/apps/slomoclock/settings.js @@ -6,11 +6,29 @@ require('Storage').write('slomoclock.json',settings); } + function setColour(c) { + settings.colour = c; + writeSettings(); + } + const appMenu = { '': {'title': 'SloMo Clock'}, '< Back': back, - 'Widget Space Top' : {value : settings.widTop, format : v => v?"On":"Off",onchange : () => { settings.widTop = !settings.widTop; writeSettings(); }, - 'Widget Space Bottom' : {value : settings.widBot, format : v => v?"On":"Off",onchange : () => { settings.widBot = !settings.widBot; writeSettings(); } + 'Colours' : function() { E.showMenu(colMenu); } + //,'Widget Space Top' : {value : settings.widTop, format : v => v?"On":"Off",onchange : () => { settings.widTop = !settings.widTop; writeSettings(); } + //,'Widget Space Bottom' : {value : settings.widBot, format : v => v?"On":"Off",onchange : () => { settings.widBot = !settings.widBot; writeSettings(); } + }; + + const colMenu = { + '': {'title': 'Colours'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Surprise' : function() { setColour(0); }, + 'Red' : function() { setColour(1); }, + 'Orange' : function() { setColour(2); }, + 'Yellow' : function() { setColour(3); }, + 'Green' : function() { setColour(4); }, + 'Blue' : function() { setColour(5); }, + 'Violet' : function() { setColour(6); } }; E.showMenu(appMenu); From 270e2f04733d9e2b25689dddc70b03939ebaf333 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:09:21 +1300 Subject: [PATCH 0378/1062] Update app.js --- apps/slomoclock/app.js | 66 +++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 73ad7ba7d..8956f37b6 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -5,34 +5,42 @@ Mike Bennett mike[at]kereru.com 0.03 : Use Layout library */ -var v='0.05'; +var v='0.06'; // Colours const col = []; -col[0]= 0x001F; -col[1]= 0x023F; -col[2]= 0x039F; -col[3]= 0x051F; -col[4]= 0x067F; -col[5]= 0x07FD; -col[6]= 0x07F6; -col[7]= 0x07EF; -col[8]= 0x07E8; -col[9]= 0x07E3; -col[10]= 0x07E0; -col[11]= 0x5FE0; -col[12]= 0x97E0; -col[13]= 0xCFE0; -col[14]= 0xFFE0; -col[15]= 0xFE60; -col[16]= 0xFC60; -col[17]= 0xFAA0; -col[18]= 0xF920; -col[19]= 0xF803; -col[20]= 0xF80E; -col[21]= 0x981F; -col[22]= 0x681F; -col[23]= 0x301F; +col[1] = 0xF800; +col[2] = 0xFAE0; +col[3] = 0xF7E0; +col[4] = 0x4FE0; +col[5] = 0x019F; +col[6] = 0x681F; + +const colH = []; +colH[0]= 0x001F; +colH[1]= 0x023F; +colH[2]= 0x039F; +colH[3]= 0x051F; +colH[4]= 0x067F; +colH[5]= 0x07FD; +colH[6]= 0x07F6; +colH[7]= 0x07EF; +colH[8]= 0x07E8; +colH[9]= 0x07E3; +colH[10]= 0x07E0; +colH[11]= 0x5FE0; +colH[12]= 0x97E0; +colH[13]= 0xCFE0; +colH[14]= 0xFFE0; +colH[15]= 0xFE60; +colH[16]= 0xFC60; +colH[17]= 0xFAA0; +colH[18]= 0xF920; +colH[19]= 0xF803; +colH[20]= 0xF80E; +colH[21]= 0x981F; +colH[22]= 0x681F; +colH[23]= 0x301F; var Layout = require("Layout"); var layout = new Layout( { @@ -58,8 +66,8 @@ function draw() { layout.hour.label = timeStr.substring(0,2); layout.min.label = timeStr.substring(3,5); - layout.hour.col = col[hh]; - layout.min.col = col[hh]; + layout.hour.col = cfg.colour==0 ? colH[hh] : col[cfg.colour]; + layout.min.col = cfg.colour==0 ? colH[hh] : col[cfg.colour]; // Update date layout.day.label = date.getDate(); @@ -82,6 +90,10 @@ Bangle.on('lcdPower',on=>{ var secondInterval = setInterval(draw, 10000); +// Configuration +let cfg = require('Storage').readJSON('slomoclock.json',1)||{}; +cfg.colour = cfg.colour||0; // Colours + // update time and draw g.clear(); draw(); From 0c9d200eb7a88e95d60f2342f46e9f584f3d9294 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:10:29 +1300 Subject: [PATCH 0379/1062] Update apps.json --- apps.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 5ad1d046d..15dc96926 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.05", + "version":"0.06", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", @@ -2928,7 +2928,8 @@ "readme": "README.md", "storage": [ {"name":"slomoclock.app.js","url":"app.js"}, - {"name":"slomoclock.img","url":"app-icon.js","evaluate":true} + {"name":"slomoclock.img","url":"app-icon.js","evaluate":true}, + {"name":"slomoclock.settings.js","url":"settings.js"} ], "data": [ {"name":"slomoclock.json"} From 44259c6e151ddfd376c94b43619c3403e7b53c6a Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:20:36 +1300 Subject: [PATCH 0380/1062] Update settings.js --- apps/speedalt2/settings.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/speedalt2/settings.js b/apps/speedalt2/settings.js index 26ae010bb..96174a89b 100644 --- a/apps/speedalt2/settings.js +++ b/apps/speedalt2/settings.js @@ -37,12 +37,12 @@ '< Load GPS Adv Sport': ()=>{load('speedalt2.app.js');}, 'Units' : function() { E.showMenu(unitsMenu); }, 'Colours' : function() { E.showMenu(colMenu); }, - 'Kalman Filter' : function() { E.showMenu(kalMenu); }/*, - 'Vibrate' : { - value : settings.buzz, - format : v => v?"On":"Off", - onchange : () => { settings.buzz = !settings.buzz; writeSettings(); } - }*/ + 'Kalman Filter' : function() { E.showMenu(kalMenu); }, + 'Touch' : { + value : settings.touch, + format : v => v?"On":"Off", + onchange : () => { settings.touch = !settings.touch; writeSettings(); } + } }; const unitsMenu = { From 8d9966c2829300e2339a0a545f3f07d3663aed7c Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:25:06 +1300 Subject: [PATCH 0381/1062] Update app.js --- apps/speedalt2/app.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js index 6bd967b1b..0db9629c7 100644 --- a/apps/speedalt2/app.js +++ b/apps/speedalt2/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.06 : Add Posn screen 0.07 : Add swipe to change screens same as BTN3 */ -var v = '1.04'; +var v = '1.05'; /*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ var KalmanFilter = (function () { @@ -621,11 +621,13 @@ Bangle.on('lcdPower',function(on) { }); Bangle.on('swipe',function(dir) { + if ( ! cfg.touch ) return; if(dir == 1) prevScrn(); else nextScrn(); }); Bangle.on('touch', function(button){ + if ( ! cfg.touch ) return; nextFunc(0); // Same function as short BTN1 /* switch(button){ @@ -665,6 +667,7 @@ cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt; cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt; +cfg.touch = cfg.touch==undefined?true:cfg.touch; if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 }); if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 }); From 167e068a9c0d8387de7ff3477503900033be8338 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:26:02 +1300 Subject: [PATCH 0382/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 15dc96926..a354cde39 100644 --- a/apps.json +++ b/apps.json @@ -2901,7 +2901,7 @@ "name": "GPS Adventure Sports II", "shortName":"GPS Adv Sport II", "icon": "app.png", - "version":"1.04", + "version":"1.05", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", "tags": "tool,outdoors", "type":"app", From 719dacb85f4ee968c024b641bb5e5babe544f6db Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:30:00 +1300 Subject: [PATCH 0383/1062] Update README.md --- apps/speedalt2/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index 511932298..24d21cbef 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -22,6 +22,13 @@ BTN3 : Cycles the screens between Speed, Altitude, Distance to waypoint, Positio BTN3 : Long press exit and return to watch. +[Touch Screen] If the 'Touch' setting is ON then : + +Swipe Left/Right cycles between the five screens. + +Touch functions as BTN1 short press. + + ## App Settings Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance can be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default (three colours), high contrast (all white on black) or night ( all red on black ). From 24da4c609289ef5b14afa7aa656fb8ab3c0fc0ff Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:39:58 +1300 Subject: [PATCH 0384/1062] Update README.md --- apps/speedalt2/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index 24d21cbef..a39690338 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -1,10 +1,10 @@ # GPS Speed, Altimeter and Distance to Waypoint -What is the difference between [GPS Adventure Sports] and [GPS Adventure Sports II] ? +What is the difference between **GPS Adventure Sports** and **GPS Adventure Sports II** ? -[GPS Adventure Sports] has 3 screens, each of which display different sets of information. +**GPS Adventure Sports** has 3 screens, each of which display different sets of information. -[GPS Adventure Sports II] has 5 screens, each of which displays just one of Speed, Altitude, Distance to waypoint, Position or Time. +**GPS Adventure Sports II** has 5 screens, each of which displays just one of Speed, Altitude, Distance to waypoint, Position or Time. In all other respect they perform the same functions. Use BTN3 or swipe left/right to cycle through the screens. @@ -12,17 +12,17 @@ The waypoints list is the same as that used with the [GPS Navigation](https://ba ## Buttons and Controls -BTN1 ( Speed and Altitude ) Short press < 2 secs toggles the display between last reading and maximum recorded. Long press > 2 secs resets the recorded maximum values. +**BTN1** ( Speed and Altitude ) Short press < 2 secs toggles the display between last reading and maximum recorded. Long press > 2 secs resets the recorded maximum values. -BTN1 ( Distance ) Select next waypoint. Last fix distance from selected waypoint is displayed. +**BTN1** ( Distance ) Select next waypoint. Last fix distance from selected waypoint is displayed. -BTN2 : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts. +**BTN2** : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts. -BTN3 : Cycles the screens between Speed, Altitude, Distance to waypoint, Position and Time +**BTN3** : Cycles the screens between Speed, Altitude, Distance to waypoint, Position and Time -BTN3 : Long press exit and return to watch. +**BTN3** : Long press exit and return to watch. -[Touch Screen] If the 'Touch' setting is ON then : +**Touch Screen** If the 'Touch' setting is ON then : Swipe Left/Right cycles between the five screens. From 6bbec69ea994c8769948408d48aef3b3c2499673 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 09:41:05 +1300 Subject: [PATCH 0385/1062] Update README.md --- apps/speedalt2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md index a39690338..30a706b7b 100644 --- a/apps/speedalt2/README.md +++ b/apps/speedalt2/README.md @@ -6,7 +6,7 @@ What is the difference between **GPS Adventure Sports** and **GPS Adventure Spor **GPS Adventure Sports II** has 5 screens, each of which displays just one of Speed, Altitude, Distance to waypoint, Position or Time. -In all other respect they perform the same functions. Use BTN3 or swipe left/right to cycle through the screens. +In all other respect they perform the same functions. The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. From c23b3ce8e533087cd9261e45f90c083aca666298 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 16:37:57 +1300 Subject: [PATCH 0386/1062] Update settings.js --- apps/slomoclock/settings.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/slomoclock/settings.js b/apps/slomoclock/settings.js index abf767fdc..e073dda7e 100644 --- a/apps/slomoclock/settings.js +++ b/apps/slomoclock/settings.js @@ -22,13 +22,15 @@ const colMenu = { '': {'title': 'Colours'}, '< Back': function() { E.showMenu(appMenu); }, + 'Mysterion' : function() { setColour(0); }, 'Surprise' : function() { setColour(0); }, - 'Red' : function() { setColour(1); }, - 'Orange' : function() { setColour(2); }, - 'Yellow' : function() { setColour(3); }, - 'Green' : function() { setColour(4); }, - 'Blue' : function() { setColour(5); }, - 'Violet' : function() { setColour(6); } + 'Red' : function() { setColour(2); }, + 'Orange' : function() { setColour(3); }, + 'Yellow' : function() { setColour(4); }, + 'Green' : function() { setColour(5); }, + 'Blue' : function() { setColour(6); }, + 'Violet' : function() { setColour(7); }, + 'White' : function() { setColour(8); }\ }; E.showMenu(appMenu); From 3c5986b9869d197a53bbd3c1cb38bd271c4a5e00 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:06:26 +1300 Subject: [PATCH 0387/1062] Update app.js --- apps/slomoclock/app.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 8956f37b6..6afecc1fa 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -5,16 +5,17 @@ Mike Bennett mike[at]kereru.com 0.03 : Use Layout library */ -var v='0.06'; +var v='0.07'; // Colours const col = []; -col[1] = 0xF800; -col[2] = 0xFAE0; -col[3] = 0xF7E0; -col[4] = 0x4FE0; -col[5] = 0x019F; -col[6] = 0x681F; +col[2] = 0xF800; +col[3] = 0xFAE0; +col[4] = 0xF7E0; +col[5] = 0x4FE0; +col[6] = 0x019F; +col[7] = 0x681F; +col[8] = 0xFFFF; const colH = []; colH[0]= 0x001F; @@ -42,6 +43,9 @@ colH[21]= 0x981F; colH[22]= 0x681F; colH[23]= 0x301F; +// Colour incremented with every 10 sec timer event +var colNum = 0; + var Layout = require("Layout"); var layout = new Layout( { type:"h", c: [ @@ -60,14 +64,19 @@ var layout = new Layout( { function draw() { var date = new Date(); + // Surprise colours + colNum = (colNum+256)%65536; + // Update time var timeStr = require("locale").time(date,1); var hh = parseFloat(timeStr.substring(0,2)); layout.hour.label = timeStr.substring(0,2); layout.min.label = timeStr.substring(3,5); - layout.hour.col = cfg.colour==0 ? colH[hh] : col[cfg.colour]; - layout.min.col = cfg.colour==0 ? colH[hh] : col[cfg.colour]; + + // Mysterion (0) different colour each hour. Surprise (1) different colour every 10 secs. + layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colNum : col[cfg.colour]; + layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colNum :col[cfg.colour]; // Update date layout.day.label = date.getDate(); From f826f9c2c47b171e57dc419120ddab90ed6e72fb Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:07:34 +1300 Subject: [PATCH 0388/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index a354cde39..874ae3ae1 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.06", + "version":"0.07", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", From 894676045954322c98bbf4dae81924119f9acb60 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:25:43 +1300 Subject: [PATCH 0389/1062] Update app.js --- apps/slomoclock/app.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 6afecc1fa..3bb67b0ca 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.03 : Use Layout library */ -var v='0.07'; +var v='0.08'; // Colours const col = []; @@ -45,6 +45,7 @@ colH[23]= 0x301F; // Colour incremented with every 10 sec timer event var colNum = 0; +var lastMin = -1; var Layout = require("Layout"); var layout = new Layout( { @@ -64,19 +65,21 @@ var layout = new Layout( { function draw() { var date = new Date(); - // Surprise colours - colNum = (colNum+256)%65536; - // Update time var timeStr = require("locale").time(date,1); var hh = parseFloat(timeStr.substring(0,2)); + var mm = parseFloat(timeStr.substring(3,5)); + + // Surprise colours + if ( lastMin != mm ) colNum = Math.floor(Math.random() * 24); + lastMin = mm; layout.hour.label = timeStr.substring(0,2); layout.min.label = timeStr.substring(3,5); // Mysterion (0) different colour each hour. Surprise (1) different colour every 10 secs. - layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colNum : col[cfg.colour]; - layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colNum :col[cfg.colour]; + layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colH[colNum] : col[cfg.colour]; + layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colH[colNum] :col[cfg.colour]; // Update date layout.day.label = date.getDate(); From d70a7eef542ab45f73d8eaeacc02b59fec975302 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:26:24 +1300 Subject: [PATCH 0390/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 874ae3ae1..6e393b8aa 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.07", + "version":"0.08", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", From 87c354e7b5572798ff4d579f30a804f3aa14315d Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:35:38 +1300 Subject: [PATCH 0391/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 6e393b8aa..8aa251542 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.08", + "version":"0.09", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", From 9d456a2c728e0426b9a06fe228632095d1b4b215 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:44:49 +1300 Subject: [PATCH 0392/1062] Update settings.js --- apps/slomoclock/settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/slomoclock/settings.js b/apps/slomoclock/settings.js index e073dda7e..af67069dc 100644 --- a/apps/slomoclock/settings.js +++ b/apps/slomoclock/settings.js @@ -23,14 +23,14 @@ '': {'title': 'Colours'}, '< Back': function() { E.showMenu(appMenu); }, 'Mysterion' : function() { setColour(0); }, - 'Surprise' : function() { setColour(0); }, + 'Surprise' : function() { setColour(1); }, 'Red' : function() { setColour(2); }, 'Orange' : function() { setColour(3); }, 'Yellow' : function() { setColour(4); }, 'Green' : function() { setColour(5); }, 'Blue' : function() { setColour(6); }, 'Violet' : function() { setColour(7); }, - 'White' : function() { setColour(8); }\ + 'White' : function() { setColour(8); } }; E.showMenu(appMenu); From 0f23695bc671653943f9aaea7c7425635c8200e7 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:45:31 +1300 Subject: [PATCH 0393/1062] Update app.js --- apps/slomoclock/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 3bb67b0ca..8d830907e 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -78,8 +78,8 @@ function draw() { layout.min.label = timeStr.substring(3,5); // Mysterion (0) different colour each hour. Surprise (1) different colour every 10 secs. - layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colH[colNum] : col[cfg.colour]; - layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==2 ? colH[colNum] :col[cfg.colour]; + layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==1 ? colH[colNum] : col[cfg.colour]; + layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==1 ? colH[colNum] :col[cfg.colour]; // Update date layout.day.label = date.getDate(); From c0f0ebbd60bed1d5a137b2204c4b0f3f531c2c92 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:45:50 +1300 Subject: [PATCH 0394/1062] Update app.js --- apps/slomoclock/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js index 8d830907e..e3933af1b 100644 --- a/apps/slomoclock/app.js +++ b/apps/slomoclock/app.js @@ -5,7 +5,7 @@ Mike Bennett mike[at]kereru.com 0.03 : Use Layout library */ -var v='0.08'; +var v='0.10'; // Colours const col = []; From f3168a43aab918d343a7a34b5b256e72f574bfe7 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:46:30 +1300 Subject: [PATCH 0395/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 8aa251542..8c792a687 100644 --- a/apps.json +++ b/apps.json @@ -2920,7 +2920,7 @@ "name": "SloMo Clock", "shortName":"SloMo Clock", "icon": "watch.png", - "version":"0.09", + "version":"0.10", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", "tags": "clock", "type":"clock", From 5eccc0ae0b3a5be62abd526067557b41ffe4e604 Mon Sep 17 00:00:00 2001 From: nujw Date: Mon, 25 Oct 2021 17:51:34 +1300 Subject: [PATCH 0396/1062] Update ChangeLog --- apps/slomoclock/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/slomoclock/ChangeLog b/apps/slomoclock/ChangeLog index c31405e08..cfab5da55 100644 --- a/apps/slomoclock/ChangeLog +++ b/apps/slomoclock/ChangeLog @@ -1 +1,2 @@ 0.01: Created app +0.10: Different colour schemes selectable in SloMo Clock settings. From 1624bb5435b622302dfa4cad8ad42b8d95d9e7f3 Mon Sep 17 00:00:00 2001 From: Victor Serain Date: Fri, 22 Oct 2021 16:56:20 +0200 Subject: [PATCH 0397/1062] feat: add app for navigate between clock and launcher with Swipe action --- apps.json | 13 +++++++++++++ apps/swiperclocklaunch/ChangeLog | 1 + apps/swiperclocklaunch/boot.js | 17 +++++++++++++++++ apps/swiperclocklaunch/swiperclocklaunch.png | Bin 0 -> 889 bytes 4 files changed, 31 insertions(+) create mode 100644 apps/swiperclocklaunch/ChangeLog create mode 100644 apps/swiperclocklaunch/boot.js create mode 100644 apps/swiperclocklaunch/swiperclocklaunch.png diff --git a/apps.json b/apps.json index f187ff3d6..440257e3c 100644 --- a/apps.json +++ b/apps.json @@ -4101,5 +4101,18 @@ {"name":"gpstouch.app.js","url":"gpstouch.app.js"}, {"name":"gpstouch.img","url":"gpstouch.icon.js","evaluate":true} ] + }, + { + "id": "swiperclocklaunch", + "name": "Swiper Clock Launch", + "version": "0.01", + "description": "Navigate between clock and launcher with Swipe action", + "icon": "swiperclocklaunch.png", + "type": "boot", + "tags": "system", + "supports": ["BANGLEJS", "BANGLEJS2"], + "storage": [ + {"name":"swiperclocklaunch.boot.js","url":"boot.js"} + ] } ] diff --git a/apps/swiperclocklaunch/ChangeLog b/apps/swiperclocklaunch/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/swiperclocklaunch/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/swiperclocklaunch/boot.js b/apps/swiperclocklaunch/boot.js new file mode 100644 index 000000000..0bb8d588a --- /dev/null +++ b/apps/swiperclocklaunch/boot.js @@ -0,0 +1,17 @@ +// clock -> launcher +(function() { + var sui = Bangle.setUI; + Bangle.setUI = function(mode, cb) { + sui(mode,cb); + if (!mode.startsWith("clock")) return; + Bangle.swipeHandler = dir => { if (dir<0) Bangle.showLauncher(); }; + Bangle.on("swipe", Bangle.swipeHandler); + }; +})(); +// launcher -> clock +setTimeout(function() { + if (global.__FILE__ && __FILE__.endsWith(".app.js") && (require("Storage").readJSON(__FILE__.slice(0,-6)+"info",1)||{}).type=="launch") { + Bangle.swipeHandler = dir => { if (dir>0) load(); }; + Bangle.on("swipe", Bangle.swipeHandler); + } +}, 10); \ No newline at end of file diff --git a/apps/swiperclocklaunch/swiperclocklaunch.png b/apps/swiperclocklaunch/swiperclocklaunch.png new file mode 100644 index 0000000000000000000000000000000000000000..c7f16c2b18ed67170e425ec92af33da5da40a295 GIT binary patch literal 889 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sjKx9jPK-BC>eK@{oCO|{#S9GG z!XV7ZFl&wkQ1DEEPlzj!X4rg$VbuYKH3v}xyxJbZy+<7uF2O5096dHv?yyLU|&ZykC33~1G^2T#N{9o5@; z>hklKT02ex)l{Fm@%+sjAme9bNl-ptoK!J3d9KezuImvO-+7QVE=9u}?IR}Rw{eoa~A#?;&0A)Me7rJ`|R zdEB3^{FytZZ0cgUaW!p{L-~xhg!5|G^n?;b|Kv3GPdy>jvZK4nhl542RpB)+hm)21 zStg6Q0)N$>{BxXOBDY#sP+-&3eo^l0{#=LnxlSm@-VQuvCnOS?EueW#`kGXG$mf%+ zPc*OAc=6tjK38Ao_}QXMZ-PeVr&I9@9(3(;I(S9!L|L z(^Qjt#7tP~=9}5?mmWB=$0Yt_<{7Q&Cuej03rd@+ZanX1q4Bn-X=~=Z-Iu?6+56e+ z_Hw_yH`#Yx*ynI&R{!gd-KR}A+@#^P^+|wD8o%o_ufVHeRf`v{-}3k2ryGBd&wt3i XqO?C%)!fY;ltVmS{an^LB{Ts5&i}k1 literal 0 HcmV?d00001 From 92cb0a5a0764b59f7a012a09208160f1e1495afe Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 25 Oct 2021 14:35:00 +0100 Subject: [PATCH 0398/1062] Add showscroller polyfill, message app tidying --- apps.json | 6 +- apps/boot/ChangeLog | 1 + apps/boot/bootupdate.js | 5 ++ apps/messages/app.js | 181 ++++++---------------------------------- 4 files changed, 35 insertions(+), 158 deletions(-) diff --git a/apps.json b/apps.json index f187ff3d6..27e1f92d5 100644 --- a/apps.json +++ b/apps.json @@ -18,7 +18,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.32", + "version": "0.33", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", @@ -3248,7 +3248,7 @@ "data": [ {"name":"speedalt2.json"} ] - }, + }, { "id": "slomoclock", "name": "SloMo Clock", "shortName":"SloMo Clock", @@ -3268,7 +3268,7 @@ "data": [ {"name":"slomoclock.json"} ] - }, + }, { "id": "de-stress", "name": "De-Stress", diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 59d9e4c65..104afed63 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -34,3 +34,4 @@ 0.32: Fix single quote error in g.wrapString polyfill improve g.stringMetrics polyfill Fix issue where re-running bootupdate could disable existing polyfills +0.33: Add E.showScroller polyfill diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index 269a80831..c0f494726 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -133,6 +133,11 @@ else if (mode=="updown") { throw new Error("Unknown UI mode"); };\n`; } +delete E.showScroller; // deleting stops us getting confused by our own decl. builtins can't be deleted +if (!E.showScroller) { // added in 2v11 - this is a limited functionality polyfill + boot += `E.showScroller = (function(a){function n(){g.reset();b>=l+c&&(c=1+b-l);bm||m>=a.c)break;var f=24+d*a.h;a.draw(m,{x:0,y:f,w:h,h:a.h});d+c==b&&g.setColor(g.theme.fgG).drawRect(0,f,h-1,f+a.h-1).drawRect(1,f+1,h-2,f+a.h-2)}g.setColor(c?g.theme.fg:g.theme.bg);g.fillPoly([e,6,e-14,20,e+14,20]);g.setColor(a.c>l+c?g.theme.fg:g.theme.bg);g.fillPoly([e,k-7,e-14,k-21,e+14,k-21])}if(!a)return Bangle.setUI();var b=0,c=0,h=g.getWidth(), +k=g.getHeight(),e=h/2,l=Math.floor((k-48)/a.h);g.clearRect(0,24,h-1,k-1);n();Bangle.setUI("updown",d=>{d?(b+=d,0>b&&(b=a.c-1),b>=a.c&&(b=0),n()):a.select(b)})});\n`; +} delete g.imageMetrics; // deleting stops us getting confused by our own decl. builtins can't be deleted if (!g.imageMetrics) { // added in 2v11 - this is a limited functionality polyfill boot += `Graphics.prototype.imageMetrics=function(src) { diff --git a/apps/messages/app.js b/apps/messages/app.js index d369ee175..801434498 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -21,9 +21,6 @@ GB({"t":"notify-","id":1}) GB({"t":"notify","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"Y2MBAA....AAAAAAAAAAAAAA="}) GB({"t":"notify~","id":1,"body":"Campton - 11:54 ETA"}) GB({"t":"notify~","id":1,"title":"High St"}) -GB({"t":"notify~","id":1,"body":"Campton - 11:55 ETA"}) -GB({"t":"notify~","id":1,"title":"0 yd - High St"}) -GB({"t":"notify~","id":1,"body":"Campton - 11:56 ETA"}) */ @@ -97,140 +94,6 @@ function showMessage(msgid) { }); } -// Show a single menu item for the message -function showMessageMenuItem(y, idx) { - var msg = MESSAGES[idx]; - var W = g.getWidth(), H=48; - if (msg.new) g.setBgColor("#4F4"); - else g.setBgColor("#CFC"); - g.clearRect(0,y,W-1,y+H-1).setColor(g.theme.fg); - var m = msg.title+"\n"+msg.body; - if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, W-2, y+2); - if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, 2,y+2); - if (msg.body) { - g.setFontAlign(-1,-1).setFont("6x8"); - var l = g.wrapString(msg.body, W-14); - if (l.length>3) { - l = l.slice(0,3); - l[l.length-1]+="..."; - } - g.drawString(l.join("\n"), 12,y+20); - } -} -//test -//g.clear(1); showMessageMenuItem(MESSAGES[0],24) - -if (process.env.HWVERSION==1) { // Bangle.js 1 - showBigMenu = function(options) { - /* options = { - h = height - items = # of items - draw = function(y, idx) - onSelect = function(idx) - }*/ - var selected = 0; - var menuScroll = 0; - var menuShowing = false; - var w = g.getWidth(); - var h = g.getHeight(); - var m = w/2; - var n = Math.floor((h-48)/options.h); - - function drawMenu() { - g.reset(); - if (selected>=n+menuScroll) menuScroll = 1+selected-n; - if (selected=options.items) break; - var y = 24+i*options.h; - options.draw(y, idx); - // border for selected - if (i+menuScroll==selected) { - g.setColor(g.theme.fgG).drawRect(0,y,w-1,y+options.h-1).drawRect(1,y+1,w-2,y+options.h-2); - } - } - // arrows - g.setColor(menuScroll ? g.theme.fg : g.theme.bg); - g.fillPoly([m,6,m-14,20,m+14,20]); - g.setColor((options.items>n+menuScroll) ? g.theme.fg : g.theme.bg); - g.fillPoly([m,h-7,m-14,h-21,m+14,h-21]); - } - g.clearRect(0,24,w-1,h-1); - drawMenu(); - Bangle.setUI("updown",dir=>{ - if (dir) { - selected += dir; - if (selected<0) selected = options.items-1; - if (selected>=options.items) selected = 0; - drawMenu(); - } else { - options.onSelect(selected); - } - }); - } -} else { // Bangle.js 2 - showBigMenu = function(options) { - /* options = { - h = height - items = # of items - draw = function(y, idx) - onSelect = function(idx) - }*/ - var menuScroll = 0; - var menuShowing = false; - var w = g.getWidth(); - var h = g.getHeight(); - var n = Math.ceil((h-24)/options.h); - var menuScrollMax = options.h*options.items - (h-24); - - function drawItem(i) { - var y = 24+i*options.h-menuScroll; - if (i<0 || i>=options.items || y<-options.h || y>=h) return; - options.draw(y, i); - } - - function drawMenu() { - g.reset().clearRect(0,24,w-1,h-1); - g.setClipRect(0,24,w-1,h-1); - for (var i=0;i{ - var dy = e.dy; - if (menuScroll - dy < 0) - dy = menuScroll; - if (menuScroll - dy > menuScrollMax) - dy = menuScroll - menuScrollMax; - if (!dy) return; - g.reset().setClipRect(0,24,g.getWidth()-1,g.getHeight()-1); - g.scroll(0,dy); - menuScroll -= dy; - if (e.dy < 0) { - drawItem(Math.floor((menuScroll+24+g.getHeight())/options.h)-1); - if (e.dy <= -options.h) drawItem(Math.floor((menuScroll+24+h)/options.h)-2); - } else { - drawItem(Math.floor((menuScroll+24)/options.h)); - if (e.dy >= options.h) drawItem(Math.floor((menuScroll+24)/options.h)+1); - } - g.setClipRect(0,0,w-1,h-1); - }; - Bangle.on('drag',Bangle.dragHandler); - Bangle.touchHandler = (_,e)=>{ - if (e.y<20) return; - var i = Math.floor((e.y+menuScroll-24) / options.h); - if (i>=0 && i { load() }); // we have >0 messages - // TODO: IF A NEW MESSAGE, SHOW IT + // If we have a new message, show it if (!forceShowMenu) { var newMessages = MESSAGES.filter(m=>m.new); if (newMessages.length) return showMessage(newMessages[0].id); } // Otherwise show a menu - var m = { - "":{title:"Messages"}, - "< Back": ()=>load() - }; - /*g.setFont("6x8"); - MESSAGES.forEach(msg=>{ - // "id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents" - var title = g.wrapString(msg.title, g.getWidth())[0]; - m[title] = function() { - showMessage(msg.id); - } - }); - E.showMenu(m);*/ - showBigMenu({ + E.showScroller({ h : 48, - items : MESSAGES.length, - draw : showMessageMenuItem, - onSelect : idx => showMessage(MESSAGES[idx].id) + c : MESSAGES.length, + draw : function(idx, r) {"ram" + var msg = MESSAGES[idx-1]; + if (msg && msg.new) g.setBgColor("#4F4"); + else g.setBgColor((idx&1) ? "#CFC" : "#9F9"); + g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setColor(g.theme.fg); + if (idx==0) msg = {title:"< Back"}; + var m = msg.title+"\n"+msg.body; + if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, r.x+r.w-2, r.t+2); + if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, r.x+2,r.y+2); + if (msg.body) { + g.setFontAlign(-1,-1).setFont("6x8"); + var l = g.wrapString(msg.body, r.w-14); + if (l.length>3) { + l = l.slice(0,3); + l[l.length-1]+="..."; + } + g.drawString(l.join("\n"), r.x+12,r.y+20); + } + }, + select : idx => { + if (idx==0) load(); + else showMessage(MESSAGES[idx-1].id); + } }); } From 294729717d11c6faf8ff8f478f7ec1fe794e935e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 25 Oct 2021 15:30:52 +0100 Subject: [PATCH 0399/1062] Remove emoji that Android didn't like, use inline SVG --- css/main.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/css/main.css b/css/main.css index 44137e9b1..9b4bd3d84 100644 --- a/css/main.css +++ b/css/main.css @@ -39,8 +39,8 @@ .btn.btn-favourite:hover { color: red; } .icon.icon-emulator { text-indent: 0px; } /*override spectre*/ -.icon.icon-emulator::before { - content: "\01F5B5"; +.icon.icon-emulator { + content: url("data:image/svg+xml,%3Csvg fill='rgb(87, 85, 217)' xmlns='http://www.w3.org/2000/svg' viewBox='4 4 40 40' width='1em' height='1em'%3E%3Cpath d='M 8.5 5 C 6.0324991 5 4 7.0324991 4 9.5 L 4 30.5 C 4 32.967501 6.0324991 35 8.5 35 L 17 35 L 17 40 L 13.5 40 A 1.50015 1.50015 0 1 0 13.5 43 L 18.253906 43 A 1.50015 1.50015 0 0 0 18.740234 43 L 29.253906 43 A 1.50015 1.50015 0 0 0 29.740234 43 L 34.5 43 A 1.50015 1.50015 0 1 0 34.5 40 L 31 40 L 31 35 L 39.5 35 C 41.967501 35 44 32.967501 44 30.5 L 44 9.5 C 44 7.0324991 41.967501 5 39.5 5 L 8.5 5 z M 8.5 8 L 39.5 8 C 40.346499 8 41 8.6535009 41 9.5 L 41 30.5 C 41 31.346499 40.346499 32 39.5 32 L 29.746094 32 A 1.50015 1.50015 0 0 0 29.259766 32 L 18.746094 32 A 1.50015 1.50015 0 0 0 18.259766 32 L 8.5 32 C 7.6535009 32 7 31.346499 7 30.5 L 7 9.5 C 7 8.6535009 7.6535009 8 8.5 8 z M 17.5 12 C 16.136406 12 15 13.136406 15 14.5 L 15 25.5 C 15 26.863594 16.136406 28 17.5 28 L 30.5 28 C 31.863594 28 33 26.863594 33 25.5 L 33 14.5 C 33 13.136406 31.863594 12 30.5 12 L 17.5 12 z M 18 18 L 30 18 L 30 25 L 18 25 L 18 18 z M 20 35 L 28 35 L 28 40 L 20 40 L 20 35 z'/%3E%3C/svg%3E"); } .icon.icon-favourite { text-indent: 0px; } /*override spectre*/ .icon.icon-favourite::before { @@ -53,3 +53,4 @@ .icon.icon-interface::before { content: "\01F5AB"; } + From c8950400554bcf7ec282a282aa67a77301c9f048 Mon Sep 17 00:00:00 2001 From: Filipe Fradique Date: Tue, 26 Oct 2021 01:27:05 +0100 Subject: [PATCH 0400/1062] Nifty Clock B - Some code refactoring --- apps.json | 17 +++++++++ apps/ffcniftyb/ChangeLog | 1 + apps/ffcniftyb/app-icon.js | 2 +- apps/ffcniftyb/app.js | 58 +++++------------------------- apps/ffcniftyb/app.png | Bin 2188 -> 1218 bytes apps/ffcniftyb/settings.js | 71 ++++++++++++++++++------------------- 6 files changed, 63 insertions(+), 86 deletions(-) diff --git a/apps.json b/apps.json index 51cca784b..102def062 100644 --- a/apps.json +++ b/apps.json @@ -4009,6 +4009,23 @@ {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} ] }, + { + "id": "ffcniftyb", + "name": "Nifty-B Clock", + "version": "0.02", + "description": "A nifty clock (series B) with time, date and color configuration", + "icon": "app.png", + "type": "clock", + "tags": "clock", + "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"ffcniftyb.app.js","url":"app.js"}, + {"name":"ffcniftyb.img","url":"app-icon.js","evaluate":true}, + {"name":"ffcniftyb.settings.js","url":"settings.js"} + ], + "data": [{"name":"ffcniftyb.json"}] + }, { "id": "stopwatch", "name": "Stopwatch Touch", diff --git a/apps/ffcniftyb/ChangeLog b/apps/ffcniftyb/ChangeLog index f6516c6de..dedd31452 100644 --- a/apps/ffcniftyb/ChangeLog +++ b/apps/ffcniftyb/ChangeLog @@ -1 +1,2 @@ 0.01: New Clock Nifty B +0.02: Added configuration \ No newline at end of file diff --git a/apps/ffcniftyb/app-icon.js b/apps/ffcniftyb/app-icon.js index f0a2393b1..1aac04351 100644 --- a/apps/ffcniftyb/app-icon.js +++ b/apps/ffcniftyb/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwwkEIf4A5gX/+AGEn//mIWLgP/C4gGCAAMgC5UvC4sDC4YICkIhBgMQiEBE4Uxn4XDj//iEAn/yA4ICBgUikEikYXBBAIXEn/xJYURAYMygERkQHBiYLBKYIXF+AVDC4czgUSmIXBCQgED+ZeBR4YXBLYICDC5CPGC4IAIC40zmaPDC4MSLQQXK+ayCR4QXCiRoEC44ECh4bCC4MTiTDBC6ZHOC5B3NLYcvC4kBgL5BAAUikT+BfIIrB/8ykf/eYQXBkUTI4cBW4YQCgQGDmAXDkJfEC46GBAoJKCR4geCAAMRAAZRDAoIODO4UBPRIAJR5QXWgKNCTApNDC5Mv/6/DAwR3GAAyHCC4anJIo3/+bvEa4Uia4oXHkEvC4cvIgUf+YXKHYIvEAgcPC5QSGC5UBSwYXJLYQXFkUhgABBC5Ef/4mBl4XEmETmIXKgaXBmYCBC4cTkMxiQXJS4IACL4p3MgESCwJHFR5oxCiB3FkERC5cSToQXFmUyiAZFR48Bn7zCAQMjkfykQkBN4n/XgKPBAAQgCUQIfBUwYXHFgIGCdI4XDmYADmIIEkAWJAH4A4A==")) \ No newline at end of file +require("heatshrink").decompress(atob("mEwwkB/4A/AH4ARgMRBA3xBBIJCAYIFDAAYHGCAYJBDYQABj4PD+AXFCwgXGCAg9ECwwJBJQooGCxAXCIYQpBAgg9IC5yPCCw4XKBYIsFPwUBXQQXHAYREIF5ZEC+MfWQYXODQYTGC5ZDEOw0QMAIXMPggvSC44vRL5b8EAYIACC5i0FCwaOBC5C0DA4ZLCC5hfC/4DBIwwXKCInwgAWEKIwXJAA4XXCxYXCEwR2EgJeLR5LbCGRYXIAgzvKh7zGZg4XGIYisBA4JJCC6B5DAoYXWF6xfRC4fwAgMBC6cBU5I6CC5AECCo0QJwQXJaZJHMEYR1JC5QKBXo8QC4oCBAZAwHgKXBTQwSDBIKmGgJ3DEYheEA4ZfJKgkPdJQXHDAQWBC44eIC4QAMDA4A==")) \ No newline at end of file diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js index 60d76ff0a..b5fc94c32 100644 --- a/apps/ffcniftyb/app.js +++ b/apps/ffcniftyb/app.js @@ -1,7 +1,8 @@ -// setTimeout(load,100);Bangle.factoryReset(); -console.log('mem', process.memory().usage); const locale = require("locale"); -const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; +const storage = require('Storage'); + +const is12Hour = (storage.readJSON("setting.json", 1) || {})["12hour"]; +const color = (storage.readJSON("ffcniftyb.json", 1) || {})["color"] || 63488 /* red */; /* Clock *********************************************/ @@ -17,39 +18,10 @@ const center = { y: screen.height / 2, }; -const color = g.toColor(255, 0, 0); -console.log('color', color); - function d02(value) { return ('0' + value).substr(-2); } -// const c = E.compiledC(` -// // void xor(int, int, int) -// void xor(int len, int *dst, int *src){ -// len = len>>2; -// while (len--) { -// *dst ^= *src; -// dst++; -// src++; -// } -// } -// `); - -// function combineLayers(l1, l2) { -// // const l1ptr = E.getAddressOf(l1.buffer, true); -// // const l2ptr = E.getAddressOf(l2.buffer, true); -// // if (l1ptr && l2ptr) { -// // c.xor(l1.buffer.length, l1ptr, l2ptr); -// // } -// if (l1 && l1.buffer && l2 && l2.buffer) { -// for (let i = 0; i < l1.buffer.length; i++) { -// l1.buffer[i] ^= l2.buffer[i]; -// } -// } -// return l1; -// } - function renderEllipse(g) { g.fillEllipse(center.x - 5 * scale, center.y - 70 * scale, center.x + 160 * scale, center.y + 90 * scale); } @@ -78,15 +50,13 @@ function renderText(g) { g.drawString(day2, center.x + 80 * scale, center.y + 60 * scale); } +const buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { + msb: true +}); + function draw() { - const s = new Date().getTime(); - console.log('mem.b', process.memory().usage); - let buf = Graphics.createArrayBuffer(screen.width, screen.height, 1, { - msb: true - }); - - let img = { + const img = { width: screen.width, height: screen.height, transparent: 0, @@ -108,12 +78,6 @@ function draw() { renderEllipse(buf.setColor(1)); renderText(buf.setColor(0)); g.setColor(color).drawImage(img, 0, 24); - - buf = undefined; - img = undefined; - - console.log('mem.e', process.memory().usage); - console.log('draw', new Date().getTime() - s); } @@ -140,7 +104,6 @@ function startTick(run) { g.clear(); startTick(draw); -// Stop updates when LCD is off, restart when on Bangle.on('lcdPower', (on) => { if (on) { startTick(draw); @@ -149,10 +112,7 @@ Bangle.on('lcdPower', (on) => { } }); -// Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); -// Show launcher when middle button pressed Bangle.setUI("clock"); -console.log('mem', process.memory().usage); \ No newline at end of file diff --git a/apps/ffcniftyb/app.png b/apps/ffcniftyb/app.png index 1cd8a49b7ba951670fb8c007055a64f6d29dfc98..a6acf01213bece9c3692d0d075b0dd5f5c8eb6b4 100644 GIT binary patch literal 1218 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyacjS1AIbUfr6ue)*-Or5z`Z(9c3j!e!&c^OrMww*%&jwrSppw-(zPHo+k0* zAaC<6g{U^qnRCU3E}IBG&f{)ilzUm&!+*818f$*<`w;w9&%27tZIN_um(iqO zGLklG+M3@_-TiZP|Bj#MpX*<}6q69)utCHwSx4@r)tO>-qLTzV|`Zj+2@cq zD`glMm?}M8978;gKb=}yecM3b*nNwQcaQR!Z&CA=)+>MO6)hPiV{B~v^0R-DO0?e- z)8EzKZKjo8`nzOuX*t_Z_k;C3JTG_E-LzkPykx%JjNYRwrZP`?;Ndp!{DsEAg)Pa7 z9ErP6a7|}jcCPG~Igi7(oi8=hZNq0CiPX<#s%tP)o4wL2dWN1stDd5GjPp zL@$5;Qy<-rvNuAR7e7kwFMM#;Zk3xBSFtm1fvi1e$R*~UfZ9h-jx1|#|I}N{QsyJN zps+c<|7R- zuFz(&RR7lm)ql?GcOO__{^P<{`vW`VH$=aEzW&FoZAyDC^2&t1>3#V5PTDSGv&T34 zOI5kJYaeXg{vyb3{F%@a zZdZAIho9kG<*CrL<6+liN_=^@WX{$T$@~0w-i7c|&Wc-qV_6SedeYUg^mR+lb-sC) ze|4SILJ#baS-X0X-ZvND0;Zi4y?+<~W&W%b{hRfaZrEkp%~yjPd|Nm>tG^jeNl=*0 z#T3WD*6CHZM)B)Yb~&Gwjpuxyr5v9f-`H)d=i0C${8e~UlfKS-14pY?&th-8>3eH$ z*%-MBmWrj>UFY2S?AQjLCzH~wCi`0ir{21k`{K+|7Sq)i%5!&Lyl{HuGVzBqUnV8W zoqSh+NB2>vU6^5~mHeTkg^5B1yKN=Q&tFnvThFw#Ls_c!%;Kv3mb(9c{r$wAUUyke T)qzP6l+`?4{an^LB{Ts5zomlw delta 2187 zcmV;62z2+t35*et8Gix*007uvZqNV#2vbQ!K~z|U?O1zIQ&$>4xe0;XBtTpUk8%T9 zNDz4yovDfgC|d~uw1Ox*Wm%E7l!B!=LP4#Sl3LfHHe|fFaLb^yT9+;Uw_Vb&pF?B!3(^=bAn_W z&R{S|Boc&hetsT8=zM&4cXt4QPODo`FD!p9=nQ&&L%mEcQz#Up6Qc|U!x0n1u!A2Q z9R6{5Rz540OQ&Y0n$9=zc)aCQlfFIur~5y_|H1$b=-=C7^_taV<73bs-Y$H58Kii! zJWte~Fc1IW;eP`Np+qKOGMRSy-`)JqOqR$b5}8D$RiR6zN=d_MmgK6Bt2QQXM7EEQ zPf}7603bLdcvIpgGMSv3mda!@Jv=?uu3P&!W>exOG~B?@Kzu@c)A=SRf?iqs3L?+S zXXE1In2!)Z1YG&@3i6*hcgB*h(x?u6Z~)of-rh(5aDP;EL{wZ-Tz$Nn6UZqjDERdF zr^O}3xB6}&4g~<<Aq6EUjKgi`%b2_N>qi6w6wGv*KZ(OsZ_qU<+WY8 zyT}+>p;TD%hlht}XJ?TeKm_#P>Gxs#Xtmm!<25m>V`ii?gF}N8;}dc5agPN_CDNij zMYShtk$;O!COZ*CqnR?I)oE{Bzrp2lgM)(s01qEN?Ck73d*-Z@Kmb6+{t7xy-??Mw z-hF#F3pR5I&W@csa(G!BIv3#Z}X5#jUm^R^tyQ%fN& z9)EP4E(OxZ15qe?gB}22_b~_|AtB>qRi#on5=5quV`F1+IvyDnxhi~>U5;sn0)ql~=kA688jJ=2KxA~J0UBsDn&^l~ zqtTEtG6|5*Hl7_F83h1Tepp#rR!VoHzfHF~v=poDL}Sz#puy6!b=y`mzpT8>?j{5wyw-lrYH-Va zsP=SiZeA`y5ScqN>p!ozE5?*EgTY|srIM)Fli9T7R3EF(*_G4X(``vmsZ}3|KFWDL z$I*t#+Opc0-a({JowdI(WjIW<>iZBHNXfzt_NX9S{AT_r%-~ac0 zt6Le{Gd$cqATa&JV)5A6m~D7pe_!p1+EcZs&?Se%nVy~|ID}5Co1C1qor_-_|D&Vh zmxJ{_6cHJbk)DBJm`bgx`D2YnqjB_TcMtdM*R$DdwhGKxhks?(Szb|Y zghum{aaa!kP+VL*H8W+k#7>?%$qD3GF`qVNiwF)0{`UH}mZLkVsSi~ia(;L`U;6v|n*$LZbsbWv-C(OS?~|jSs1_EBLyb;T_m{ef z@rj>}9UL4~C={r$R4Y?p zL16)o<4J;~#NQ-3;@C+!TK1-IEB0z?L9!r-6QtAY#2w<%k4=z`OVGEA*V{E zLNPdwpE_--wtwaq=7q^ZbaVuV1UH^*#NAATh&#lVBDh?xr>7^fJH#C^t7Go;--(Ef zs5nsZ+40Y)R4Ro+;qiF`cLyHFY>3;SP$=XIIr{oEubEft)eE`>saz_NN#qK-LZOJ? z7;om^?7V4t21{fT^QBUyv}%Lu8tS%e-Gc0G8QVNPJ%59^L4ssK`Tp|lncE41@bdEV z_49pVCT)v+TUZwPd_KSTcCRf*E|=%Mndc~9ON1$9Gcz*-0|Q22M6IV4C}au-u<7Y( zvwLW0C^|AaJ3D(%@g4?)0RhAS*4EZ05{YE9vgZ`^0x$6Y1pvT*0K4_2MY0wP+5i9m N07*qoL () => { - settings.color = color; - save(settings); - showSettings(); + const saveColor = (color) => () => { + settings.color = color; + save(settings); + back(); + }; + + function showMenu(items, opt) { + items[''] = opt || {}; + items['< Back'] = back; + E.showMenu(items); + } + + showMenu( + Object.keys(colors).reduce((menu, color) => { + menu[colors[color]] = saveColor(color); + return menu; + }, {}), + { + title: 'Color', + selected: Object.keys(colors).indexOf(settings.color) } - - E.showMenu({ - '': { 'title': 'Colors' }, - '< Back': showSettings, - 'White': saveColor(65535), - 'Red': saveColor(63488), - 'Yellow': saveColor(65504), - 'Cyan': saveColor(2047), - 'Green': saveColor(2016), - 'Blue': saveColor(31), - 'Black': saveColor(0), - }) - } - - function showSettings() { - E.showMenu({ - '': { 'title': 'Nifty B Clock' }, - '< Back': back, - 'Color': showColors, - }) - } - - showColors(); -}) + ); +}); From 727c57241b7210adbd19df8d71c953ba6aed50d8 Mon Sep 17 00:00:00 2001 From: Filipe Fradique Date: Tue, 26 Oct 2021 01:45:26 +0100 Subject: [PATCH 0401/1062] Nifty Clock B - Added readme, screenshot and some small linting fix --- apps/ffcniftyb/README.md | 9 +++++++++ apps/ffcniftyb/app.js | 4 ++-- apps/ffcniftyb/screenshot.png | Bin 0 -> 4343 bytes 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 apps/ffcniftyb/README.md create mode 100644 apps/ffcniftyb/screenshot.png diff --git a/apps/ffcniftyb/README.md b/apps/ffcniftyb/README.md new file mode 100644 index 000000000..e04243a0b --- /dev/null +++ b/apps/ffcniftyb/README.md @@ -0,0 +1,9 @@ +# Nifty Series B Clock + +- Display Time and Date +- Color Configuration + +## + +![](screenshot.png) + diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js index b5fc94c32..58bf0b24b 100644 --- a/apps/ffcniftyb/app.js +++ b/apps/ffcniftyb/app.js @@ -68,13 +68,13 @@ function draw() { g.clearRect(0, 24, g.getWidth(), g.getHeight()); // render outside text with ellipse - buf.clear() + buf.clear(); renderText(buf.setColor(1)); renderEllipse(buf.setColor(0)); g.setColor(color).drawImage(img, 0, 24); // render ellipse with inside text - buf.clear() + buf.clear(); renderEllipse(buf.setColor(1)); renderText(buf.setColor(0)); g.setColor(color).drawImage(img, 0, 24); diff --git a/apps/ffcniftyb/screenshot.png b/apps/ffcniftyb/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..f7d3fd678ea6cb11743065871efd2116df7e70aa GIT binary patch literal 4343 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 SJn-Y^*UZSuutDD|^?v}R)7t0& literal 0 HcmV?d00001 From 9cbcff08ac20efe55ceb6b5e45addc7f29397ebe Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 09:19:02 +0100 Subject: [PATCH 0402/1062] launchb2 0.04: Reduce code duplication and use new E.showScroller --- apps.json | 2 +- apps/launchb2/ChangeLog | 1 + apps/launchb2/app.js | 85 +++++++++++++---------------------------- 3 files changed, 29 insertions(+), 59 deletions(-) diff --git a/apps.json b/apps.json index 0136a744a..a0185c8db 100644 --- a/apps.json +++ b/apps.json @@ -114,7 +114,7 @@ "id": "launchb2", "name": "Launcher (Bangle.js 2 default)", "shortName": "Launcher", - "version": "0.03", + "version": "0.04", "description": "This is needed by Bangle.js 2.0 to display a menu allowing you to choose your own applications.", "icon": "app.png", "type": "launch", diff --git a/apps/launchb2/ChangeLog b/apps/launchb2/ChangeLog index a96ee84e1..a84587b7e 100644 --- a/apps/launchb2/ChangeLog +++ b/apps/launchb2/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Fix occasional missed image when scrolling up 0.03: Text wrapping, better font +0.04: Reduce code duplication and use new E.showScroller diff --git a/apps/launchb2/app.js b/apps/launchb2/app.js index 371326498..8b66247c5 100644 --- a/apps/launchb2/app.js +++ b/apps/launchb2/app.js @@ -7,73 +7,42 @@ apps.sort((a,b)=>{ if (a.name>b.name) return 1; return 0; }); -var APPH = 64; -var menuScroll = 0; -var menuShowing = false; -var w = g.getWidth(); -var h = g.getHeight(); -var n = Math.ceil((h-24)/APPH); -var menuScrollMax = APPH*apps.length - (h-24); -// FIXME: not needed after 2v11 -var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2"; - apps.forEach(app=>{ if (app.icon) app.icon = s.read(app.icon); // should just be a link to a memory area }); -if (g.wrapString) { // FIXME: check not needed after 2v11 +// FIXME: not needed after 2v11 +var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2"; +// FIXME: check not needed after 2v11 +if (g.wrapString) { g.setFont(font); apps.forEach(app=>app.name = g.wrapString(app.name, g.getWidth()-64).join("\n")); } -function drawApp(i) { - var y = 24+i*APPH-menuScroll; - var app = apps[i]; - if (!app || y<-APPH || y>=g.getHeight()) return; - g.setFont(font).setFontAlign(-1,0).drawString(app.name,64,y+32); - if (app.icon) try {g.drawImage(app.icon,8,y+8);} catch(e){} -} - -function drawMenu() { - g.reset().clearRect(0,24,w-1,h-1); - g.setClipRect(0,24,g.getWidth()-1,g.getHeight()-1); - for (var i=0;i{ - var dy = e.dy; - if (menuScroll - dy < 0) - dy = menuScroll; - if (menuScroll - dy > menuScrollMax) - dy = menuScroll - menuScrollMax; - if (!dy) return; - g.reset().setClipRect(0,24,g.getWidth()-1,g.getHeight()-1); - g.scroll(0,dy); - menuScroll -= dy; - if (e.dy < 0) { - drawApp(Math.floor((menuScroll+24+g.getHeight())/APPH)-1); - if (e.dy <= -APPH) drawApp(Math.floor((menuScroll+24+g.getHeight())/APPH)-2); - } else { - drawApp(Math.floor((menuScroll+24)/APPH)); - if (e.dy >= APPH) drawApp(Math.floor((menuScroll+24)/APPH)+1); - } - g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); -}); -Bangle.on("touch",(_,e)=>{ - if (e.y<20) return; - var i = Math.floor((e.y+menuScroll-24) / APPH); +function drawApp(i, r) { var app = apps[i]; if (!app) return; - if (!app.src || require("Storage").read(app.src)===undefined) { - E.showMessage("App Source\nNot found"); - setTimeout(drawMenu, 2000); - } else { - E.showMessage("Loading..."); - load(app.src); - } -}); + g.clearRect(r.x,r.y,r.x+r.w-1, r.y+r.h-1); + g.setFont(font).setFontAlign(-1,0).drawString(app.name,64,r.y+32); + if (app.icon) try {g.drawImage(app.icon,8,r.y+8);} catch(e){} +} + +g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); + +E.showScroller({ + h : 64, c : apps.length, + draw : drawApp, + select : i => { + var app = apps[i]; + if (!app) return; + if (!app.src || require("Storage").read(app.src)===undefined) { + E.showMessage("App Source\nNot found"); + setTimeout(drawMenu, 2000); + } else { + E.showMessage("Loading..."); + load(app.src); + } + } +}); From 481f3c7965746d75f2e9c69121ad90185e851af0 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 09:19:13 +0100 Subject: [PATCH 0403/1062] include ID widget in bangle2 --- bin/firmwaremaker_c.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 2cb993d00..15092ced7 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -30,7 +30,7 @@ if (DEVICE=="BANGLEJS") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install "boot","launchb2","s7clk","setting", - "about","alarm","widlock","widbat","widbt" + "about","alarm","widlock","widbat","widbt","widid" ]; } else { console.log("USAGE:"); From d0c2211727e4c0a487ba9063914a41b950920dc2 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 10:23:39 +0100 Subject: [PATCH 0404/1062] boot 0.34: Use Storage.hash if available + Rearrange NRF.setServices to allow .boot.js files to add services (eg ANCS) --- apps.json | 2 +- apps/boot/ChangeLog | 2 ++ apps/boot/bootupdate.js | 17 ++++++++++++++--- apps/setting/settings-icon.js | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index a0185c8db..83f08f47e 100644 --- a/apps.json +++ b/apps.json @@ -18,7 +18,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.33", + "version": "0.34", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index 104afed63..d3a241d7c 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -35,3 +35,5 @@ improve g.stringMetrics polyfill Fix issue where re-running bootupdate could disable existing polyfills 0.33: Add E.showScroller polyfill +0.34: Use Storage.hash if available + Rearrange NRF.setServices to allow .boot.js files to add services (eg ANCS) diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index c0f494726..c96cc8e83 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -5,15 +5,22 @@ E.showMessage("Updating boot0..."); var s = require('Storage').readJSON('setting.json',1)||{}; var isB2 = process.env.HWVERSION; // Is Bangle.js 2 var boot = ""; -var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/)); -boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))!=${CRC}) { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`; +if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed + var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/); + boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)!=${CRC})`; +} else { + var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/)); + boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))!=${CRC})`; +} +boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`; boot += `E.setFlags({pretokenise:1});\n`; +boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`; if (s.ble!==false) { if (s.HID) { // Human interface device if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`; else if (s.HID=="kb") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBBQcZ4CnnFQAlAXUBlQiBApUBdQiBAZUFdQEFCBkBKQWRApUBdQORAZUGdQgVACVzBQcZAClzgQAJBRUAJv8AdQiVArECwA=="));` else /*kbmedia*/boot += `Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));`; - boot += `NRF.setServices({}, {uart:true, hid:Bangle.HID});\n`; + boot += `bleServiceOptions.hid=Bangle.HID;\n`; } } if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth @@ -178,12 +185,16 @@ if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill } // Append *.boot.js files +// These could change bleServices/bleServiceOptions if needed require('Storage').list(/\.boot\.js/).forEach(bootFile=>{ // we add a semicolon so if the file is wrapped in (function(){ ... }() // with no semicolon we don't end up with (function(){ ... }()(function(){ ... }() // which would cause an error! boot += require('Storage').read(bootFile)+";\n"; }); +// update ble +boot += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`; +// write file require('Storage').write('.boot0',boot); delete boot; E.showMessage("Reloading..."); diff --git a/apps/setting/settings-icon.js b/apps/setting/settings-icon.js index 7b68f80c0..abc7a3060 100644 --- a/apps/setting/settings-icon.js +++ b/apps/setting/settings-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwghC/AFEiAAgX/C/4SFkADBgQXFBIgECAAYSCkAWGBIoXGyQTHABBZLkUhiMRiQXLIQwVBAAZlIC44tCAAYxGIxIWFGA4XIFwwwHXBAWHGAwXHFxAwGPAYXTX44XDiAJBgIXGyDAHFAYKDMAq+EGAgXNCwwX/C453XU6IWHa6ZFCC6JJCC4hgEAAoOEC5AwIFwhgEBAgwIBoqmGGBIuFVAgXFGAwLFYAoLFGIYtFeA4MGABMpC4pICkBMGBIpGFC4SuIBIoWFAAxZLC/4X/AFQ")) +require("heatshrink").decompress(atob("mEw4UA///+Nj5lCt9TH+cBqtVoALWqALTgoLUiALFgoLDqoBBAAQGCHAdRBYdFKwZECqv614ECGQQsCr2q1W1BYkVAoPqBYOrAoNUBYdXBQIAB6oLDEQgkEBYdaBYelBYt6BYetBYYvBtWq0EK1WpF4ZfBIwIFBJATCDBY6PDiuq1AEBlWqBdA7KKZZrLQZabNWZLLLcZb7LBYVV/WvAgRfCNYNRBAVVoq/FJQRECR4gnBEwQEBggLDGQg4CBag4DBaBWBBaoATA")) From cc821be6efc3a96b3af8d4fbee80533c4b9ec449 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 12:38:58 +0100 Subject: [PATCH 0405/1062] make running this less destructive --- bin/create_app_supports_field.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/bin/create_app_supports_field.js b/bin/create_app_supports_field.js index 6908591a5..b901b0448 100644 --- a/bin/create_app_supports_field.js +++ b/bin/create_app_supports_field.js @@ -31,6 +31,7 @@ try{ } apps = apps.map((app,appIdx) => { + if (app.supports) return app; // already sorted var tags = []; if (app.tags) tags = app.tags.split(",").map(t=>t.trim()); var supportsB1 = true; @@ -62,17 +63,21 @@ var KEY_ORDER = [ var JS = JSON.stringify; var json = "[\n "+apps.map(app=>{ - var keys = KEY_ORDER.filter(k=>k in app); + /*var keys = KEY_ORDER.filter(k=>k in app); Object.keys(app).forEach(k=>{ if (!KEY_ORDER.includes(k)) throw new Error(`Key named ${k} not known!`); - }); - + });*/ + var keys = Object.keys(app); // don't re-order return "{\n "+keys.map(k=>{ var js = JS(app[k]); - if (k=="storage") - js = "[\n "+app.storage.map(s=>JS(s)).join(",\n ")+"\n ]"; + if (k=="storage") { + if (app.storage.length) + js = "[\n "+app.storage.map(s=>JS(s)).join(",\n ")+"\n ]"; + else + js = "[]"; + } return JS(k)+": "+js; }).join(",\n ")+"\n }"; }).join(",\n ")+"\n]\n"; From 8b9d43deced6b325e4d1855c9817098275c9b0c3 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 12:40:08 +0100 Subject: [PATCH 0406/1062] automatic thumbnailing --- bin/thumbnailer.js | 141 +++++++++++++++++++++++++++++++-------------- 1 file changed, 99 insertions(+), 42 deletions(-) diff --git a/bin/thumbnailer.js b/bin/thumbnailer.js index 922217fb2..db658b01e 100755 --- a/bin/thumbnailer.js +++ b/bin/thumbnailer.js @@ -2,15 +2,18 @@ var EMULATOR = "banglejs1"; -var appId; +var singleAppId; -if (process.argv.length!=3) { +if (process.argv.length!=3 && process.argv.length!=2) { console.log("USAGE:"); - console.log(" bin/thumbnailer.jd APP_ID"); + console.log(" bin/thumbnailer.js"); + console.log(" - all thumbnails"); + console.log(" bin/thumbnailer.js APP_ID"); + console.log(" - just one app"); process.exit(1); } -appId = process.argv[2]; -imageFn = "out.png"; +if (process.argv.length==3) + singleAppId = process.argv[2]; if (!require("fs").existsSync(__dirname + "/../../EspruinoWebIDE")) { console.log("You need to:"); @@ -33,52 +36,106 @@ eval(require("fs").readFileSync(__dirname + "/../core/lib/espruinotools.js").toS eval(require("fs").readFileSync(__dirname + "/../core/js/utils.js").toString()); eval(require("fs").readFileSync(__dirname + "/../core/js/appinfo.js").toString()); var apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json")); -var app = apps.find(a=>a.id==appId); -if (!app) ERROR(`App ${JSON.stringify(appId)} not found`); -if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); +/* we factory reset ONCE, get this, then we can use it to reset +state quickly for each new app */ +var factoryFlashMemory = new Uint8Array(FLASH_SIZE); jsRXCallback = function() {}; jsUpdateGfx = function() {}; +function ERROR(s) { + console.error(s); + process.exit(1); +} + +function getThumbnail(appId, imageFn) { + console.log("Thumbnail for "+appId); + var app = apps.find(a=>a.id==appId); + if (!app) ERROR(`App ${JSON.stringify(appId)} not found`); + if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); + + + return new Promise(resolve => { + AppInfo.getFiles(app, { + fileGetter:function(url) { + console.log(__dirname+"/"+url); + return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); + }, settings : SETTINGS}).then(files => { + console.log("AppInfo returned");//, files); + flashMemory.set(factoryFlashMemory); + jsTransmitString("reset()\n"); + console.log("Uploading..."); + jsTransmitString("g.clear()\n"); + var command = files.map(f=>f.cmd).join("\n")+"\n"; + command += `load("${appId}.app.js")\n`; + jsTransmitString(command); + console.log("Done."); + jsStopIdle(); + + var rgba = new Uint8Array(GFX_WIDTH*GFX_HEIGHT*4); + jsGetGfxContents(rgba); + var rgba32 = new Uint32Array(rgba.buffer); + var firstPixel = rgba32[0]; + var blankImage = rgba32.every(col=>col==firstPixel) + + if (!blankImage) { + var Jimp = require("jimp"); + let image = new Jimp(GFX_WIDTH, GFX_HEIGHT, function (err, image) { + if (err) throw err; + let buffer = image.bitmap.data; + buffer.set(rgba); + image.write(imageFn, (err) => { + if (err) throw err; + console.log("Image written as "+imageFn); + resolve(true); + }); + }); + } else { + console.log("Image is empty"); + resolve(false); + } + + }); + }); +} + +var screenshots = []; + // wait until loaded... setTimeout(function() { console.log("Loaded..."); jsInit(); - jsIdle(true); // not automatic - - AppInfo.getFiles(app, { - fileGetter:function(url) { - console.log(__dirname+"/"+url); - return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); - }, settings : SETTINGS}).then(files => { - //console.log(files); - var command = "Bangle.factoryReset()\n"; - command += files.map(f=>f.cmd).join("\n")+"\n"; - command += `load("${appId}.app.js")\n`; - //console.log(command); - console.log("Uploading..."); - jsTransmitString(command); - console.log("Done."); - jsIdle(); - jsIdle(); - jsIdle(); - jsStopIdle(); - - var rgba = new Uint8Array(GFX_WIDTH*GFX_HEIGHT*4); - jsGetGfxContents(rgba); - - var Jimp = require("jimp"); - let image = new Jimp(GFX_WIDTH, GFX_HEIGHT, function (err, image) { - if (err) throw err; - let buffer = image.bitmap.data; - buffer.set(rgba); - image.write(imageFn, (err) => { - if (err) throw err; - console.log("Image written as "+imageFn); + jsIdle(); + console.log("Factory reset"); + jsTransmitString("Bangle.factoryReset()\n"); + factoryFlashMemory.set(flashMemory); + console.log("Ready!"); + + if (singleAppId) { + getThumbnail(singleAppId, "screenshots/"+singleAppId+".png") + + return; + } + + var appList = apps.filter(app => (!app.type || app.type=="clock") && !app.custom).map(app=>app.id); + // TODO: Work out about Bangle.js 1 or 2 + var promise = Promise.resolve(); + appList.forEach(appId => { + promise = promise.then(() => { + return getThumbnail(appId, "screenshots/"+appId+".png").then(ok => { + screenshots.push({ + id : appId, + url : "screenshots/"+appId+".png" }); }); -}); - - + }); + }); + + promise.then(function() { + console.log("Complete!"); + require("fs").writeFileSync("screenshots.json", JSON.stringify(screenshots,null,2)); + }); + + }); From 0161b1a8d26dd98cc249fd631badec02663c1805 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 13:15:21 +0100 Subject: [PATCH 0407/1062] Added screenshot display in app list (automatic screenshots should follow soonish) --- README.md | 1 + apps.json | 104 ++++++++++++++++++------------- bin/create_app_supports_field.js | 19 ++++-- core | 2 +- css/main.css | 15 ++++- loader.js | 22 +++++++ 6 files changed, 113 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 3da301dba..d60d46fd3 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,7 @@ and which gives information about the app for the Launcher. "version": "0v01", // the version of this app "description": "...", // long description (can contain markdown) "icon": "icon.png", // icon in apps/ + "screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app "type":"...", // optional(if app) - // 'app' - an application // 'widget' - a widget diff --git a/apps.json b/apps.json index c483de6e8..3a9925650 100644 --- a/apps.json +++ b/apps.json @@ -10,9 +10,7 @@ "supports": ["BANGLEJS","BANGLEJS2"], "custom": "custom.html", "customConnect": true, - "storage": [ - - ], + "storage": [], "sortorder": -20 }, { @@ -65,7 +63,6 @@ ], "sortorder": -8 }, - { "id": "moonphase", "name": "Moonphase", @@ -456,10 +453,11 @@ "version": "0.15", "description": "An Analog Clock", "icon": "clock-analog.png", + "screenshots": [{"url":"screenshot_analog.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"aclock.app.js","url":"clock-analog.js"}, @@ -472,10 +470,11 @@ "version": "0.05", "description": "This is a simple clock using minimalist 2x3 pixel numerical digits", "icon": "clock2x3.png", + "screenshots": [{"url":"screenshot_pixel.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"clock2x3.app.js","url":"clock2x3-app.js"}, @@ -504,9 +503,10 @@ "version": "0.04", "description": "T-Rex game in the style of Chrome's offline game", "icon": "trex.png", + "screenshots": [{"url":"screenshot_trex.png"}], "tags": "game", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"trex.app.js","url":"trex.js"}, @@ -691,6 +691,7 @@ "version": "0.10", "description": "Show Gadgetbridge weather report", "icon": "icon.png", + "screenshots": [{"url":"screenshot.png"}], "tags": "widget,outdoors", "supports": ["BANGLEJS"], "readme": "readme.md", @@ -797,6 +798,7 @@ "version": "0.02", "description": "Show a warning when the battery runs low.", "icon": "widget.png", + "screenshots": [{"url":"screenshot.png"}], "type": "widget", "tags": "tool,battery", "supports": ["BANGLEJS"], @@ -876,15 +878,16 @@ {"name":"widhrm.wid.js","url":"widget.js"} ] }, - { "id": "bthrm", + { + "id": "bthrm", "name": "Bluetooth Heart Rate Monitor", - "shortName":"BT HRM", - "version":"0.01", + "shortName": "BT HRM", + "version": "0.01", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "icon": "app.png", - "tags": "health,bluetooth", "type": "boot", - "supports" : ["BANGLEJS","BANGLEJS2"], + "tags": "health,bluetooth", + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"bthrm.boot.js","url":"boot.js"}, @@ -1094,10 +1097,11 @@ "version": "0.03", "description": "A simple 7 segment Clock with date", "icon": "icon.png", + "screenshots": [{"url":"screenshot_s7segment.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"s7clk.app.js","url":"app.js"}, @@ -1315,9 +1319,10 @@ "version": "0.05", "description": "A Flappy Bird game clone", "icon": "app.png", + "screenshots": [{"url":"screenshot1_flappy.png"},{"url":"screenshot2_flappy.png"}], "tags": "game", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"flappy.app.js","url":"app.js"}, @@ -1348,9 +1353,7 @@ "tags": "tool,outdoors,agps", "supports": ["BANGLEJS"], "custom": "custom.html", - "storage": [ - - ] + "storage": [] }, { "id": "pomodo", @@ -1389,10 +1392,11 @@ "version": "0.05", "description": "Simple, readable and practical clock", "icon": "bold_clock.png", + "screenshots": [{"url":"screenshot_bold.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"boldclk.app.js","url":"bold_clock.js"}, @@ -1626,6 +1630,7 @@ "version": "0.08", "description": "A simple digital clock showing seconds as a bar", "icon": "clock-bar.png", + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS"], @@ -3229,45 +3234,43 @@ ], "data": [{"name":"speedalt.json"}] }, - { "id": "speedalt2", + { + "id": "speedalt2", "name": "GPS Adventure Sports II", - "shortName":"GPS Adv Sport II", - "icon": "app.png", - "version":"0.07", + "shortName": "GPS Adv Sport II", + "version": "0.07", "description": "GPS speed, altitude and distance to waypoint display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", + "icon": "app.png", + "type": "app", "tags": "tool,outdoors", "supports": ["BANGLEJS"], - "type":"app", - "allow_emulator":true, "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"speedalt2.app.js","url":"app.js"}, {"name":"speedalt2.img","url":"app-icon.js","evaluate":true}, {"name":"speedalt2.settings.js","url":"settings.js"} ], - "data": [ - {"name":"speedalt2.json"} - ] + "data": [{"name":"speedalt2.json"}] }, - { "id": "slomoclock", + { + "id": "slomoclock", "name": "SloMo Clock", - "shortName":"SloMo Clock", - "icon": "watch.png", - "version":"0.10", + "shortName": "SloMo Clock", + "version": "0.10", "description": "Simple 24h clock face with large digits, hour above minute. Uses Layout library.", + "icon": "watch.png", + "type": "clock", "tags": "clock", "supports": ["BANGLEJS"], - "type":"clock", - "allow_emulator":true, "readme": "README.md", + "allow_emulator": true, "storage": [ {"name":"slomoclock.app.js","url":"app.js"}, {"name":"slomoclock.img","url":"app-icon.js","evaluate":true}, {"name":"slomoclock.settings.js","url":"settings.js"} ], - "data": [ - {"name":"slomoclock.json"} - ] + "data": [{"name":"slomoclock.json"}] }, { "id": "de-stress", @@ -3579,6 +3582,7 @@ "version": "0.05", "description": "Control the music on your Gadgetbridge-connected phone", "icon": "icon.png", + "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_2.png"}], "type": "app", "tags": "tools,bluetooth,gadgetbridge,music", "supports": ["BANGLEJS"], @@ -3652,6 +3656,7 @@ "version": "0.02", "description": "Automatically turn Quiet Mode on or off at set times", "icon": "app.png", + "screenshots": [{"url":"screenshot_edit.png"},{"url":"screenshot_main.png"},{"url":"screenshot_widget_alarms.png"},{"url":"screenshot_widget_silent.png"}], "tags": "tool,widget", "supports": ["BANGLEJS"], "readme": "README.md", @@ -3702,6 +3707,7 @@ "version": "0.01", "description": "An Omnitrix Showpiece", "icon": "omnitrix.png", + "screenshots": [{"url":"screenshot.png"}], "tags": "game", "supports": ["BANGLEJS"], "readme": "README.md", @@ -3717,6 +3723,7 @@ "version": "0.02", "description": "Morphing Clock, with an awesome \"The Dark Knight\" themed logo.", "icon": "bat-clock.png", + "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS"], @@ -3970,6 +3977,7 @@ "version": "0.02", "description": "A simple clock using the bold Anton font.", "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -3985,10 +3993,11 @@ "version": "0.02", "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"waveclk.app.js","url":"app.js"}, @@ -4001,10 +4010,11 @@ "version": "0.01", "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", "icon": "app.png", + "screenshots": [{"url":"screenshot_floral.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"floralclk.app.js","url":"app.js"}, @@ -4017,6 +4027,7 @@ "version": "0.01", "description": "Score Tracker for sports that use plain numbers (e.g. Badminton, Volleyball, Soccer, Table Tennis, ...). Also supports tennis scoring.", "icon": "score.app.png", + "screenshots": [{"url":"screenshot_score.png"}], "type": "app", "tags": "", "supports": ["BANGLEJS","BANGLEJS2"], @@ -4048,10 +4059,11 @@ "version": "0.01", "description": "A nifty clock with time and date", "icon": "app.png", + "screenshots": [{"url":"screenshot_nifty.png"}], "type": "clock", "tags": "clock", - "readme": "README.md", "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", "allow_emulator": true, "storage": [ {"name":"ffcniftya.app.js","url":"app.js"}, @@ -4064,6 +4076,7 @@ "version": "0.02", "description": "A nifty clock (series B) with time, date and color configuration", "icon": "app.png", + "screenshots": [{"url":"screenshot.png"}], "type": "clock", "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], @@ -4081,6 +4094,7 @@ "version": "0.01", "description": "A touch based stop watch for Bangle JS 2", "icon": "stopwatch.png", + "screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}], "tags": "tools,app,b2", "supports": ["BANGLEJS2"], "readme": "README.md", @@ -4089,14 +4103,15 @@ {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} ] }, - { "id": "vernierrespirate", + { + "id": "vernierrespirate", "name": "Vernier Go Direct Respiration Belt", - "shortName":"Respiration Belt", - "version":"0.01", + "shortName": "Respiration Belt", + "version": "0.01", "description": "Connects to a Go Direct Respiration Belt and shows respiration rate", "icon": "app.png", "tags": "health,bluetooth", - "supports" : ["BANGLEJS","BANGLEJS2"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"vernierrespirate.app.js","url":"app.js"}, @@ -4110,6 +4125,7 @@ "version": "0.01", "description": "A touch based GPS watch, shows OS map reference", "icon": "gpstouch.png", + "screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}], "tags": "tools,app", "supports": ["BANGLEJS2"], "readme": "README.md", @@ -4127,7 +4143,7 @@ "icon": "swiperclocklaunch.png", "type": "boot", "tags": "system", - "supports": ["BANGLEJS", "BANGLEJS2"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"swiperclocklaunch.boot.js","url":"boot.js"} ] diff --git a/bin/create_app_supports_field.js b/bin/create_app_supports_field.js index b901b0448..d6aada357 100644 --- a/bin/create_app_supports_field.js +++ b/bin/create_app_supports_field.js @@ -55,20 +55,31 @@ apps = apps.map((app,appIdx) => { return app; }); +// search for screenshots +apps = apps.map((app,appIdx) => { + if (app.screenshots) return app; // already sorted + + var files = require("fs").readdirSync(__dirname+"/../apps/"+app.id); + var screenshots = files.filter(fn=>fn.startsWith("screenshot") && fn.endsWith(".png")); + if (screenshots.length) + app.screenshots = screenshots.map(fn => ({url:fn})); + return app; +}); + var KEY_ORDER = [ - "id","name","shortName","version","description","icon","type","tags","supports", + "id","name","shortName","version","description","icon","screenshots","type","tags","supports", "dependencies", "readme", "custom", "customConnect", "interface", "allow_emulator", "storage", "data", "sortorder" ]; var JS = JSON.stringify; var json = "[\n "+apps.map(app=>{ - /*var keys = KEY_ORDER.filter(k=>k in app); + var keys = KEY_ORDER.filter(k=>k in app); Object.keys(app).forEach(k=>{ if (!KEY_ORDER.includes(k)) throw new Error(`Key named ${k} not known!`); - });*/ - var keys = Object.keys(app); // don't re-order + }); + //var keys = Object.keys(app); // don't re-order return "{\n "+keys.map(k=>{ var js = JS(app[k]); diff --git a/core b/core index 8bbdf6992..df02cd052 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 8bbdf699210ab4d265a29a2bb0fd823cb5bca78a +Subproject commit df02cd0529d81439fb859b553576398a445ef1b8 diff --git a/css/main.css b/css/main.css index 9b4bd3d84..60a760905 100644 --- a/css/main.css +++ b/css/main.css @@ -29,12 +29,26 @@ .sort-nav { float: right; } + +.app-tile { + position: relative; + border-bottom: 1px solid #EEE; + margin-bottom: 4px; +} + .tile-content { position: relative; } .link-github { position:absolute; top: 36px; left: -24px; } +.tile-screenshot { + position:absolute;bottom:1em;right:1em; + width:4em;height:4em; + padding:2px;border:1px solid black; + cursor:pointer; +} + .btn.btn-favourite { color: red; } .btn.btn-favourite:hover { color: red; } @@ -53,4 +67,3 @@ .icon.icon-interface::before { content: "\01F5AB"; } - diff --git a/loader.js b/loader.js index 45ec87df3..c4d8d5972 100644 --- a/loader.js +++ b/loader.js @@ -161,3 +161,25 @@ window.addEventListener('load', (event) => { }); }); }); + +function onAppJSONLoaded() { + return new Promise(resolve => { + httpGet("screenshots.json").then(screenshotJSON=>{ + var screenshots = []; + try { + screenshots = JSON.parse(screenshotJSON); + } catch(e) { + console.error("Screenshot JSON Corrupted", e); + } + screenshots.forEach(s => { + var app = appJSON.find(a=>a.id==s.id); + if (!app) return; + if (!app.screenshots) app.screenshots = []; + app.screenshots.push({url:s.url}); + }) + }).catch(err=>{ + console.log("No screenshots.json found"); + resolve(); + }); + }); +} From 762b1e6be31c5313b7e45852d0a796f54806a94e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 26 Oct 2021 20:58:27 +0100 Subject: [PATCH 0408/1062] Bangle.js 2 welcome --- apps.json | 21 +++- apps/welcome2/ChangeLog | 17 +++ apps/welcome2/app-icon.js | 1 + apps/welcome2/app.js | 256 ++++++++++++++++++++++++++++++++++++++ apps/welcome2/app.png | Bin 0 -> 1939 bytes apps/welcome2/boot.js | 9 ++ apps/welcome2/settings.js | 18 +++ 7 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 apps/welcome2/ChangeLog create mode 100644 apps/welcome2/app-icon.js create mode 100644 apps/welcome2/app.js create mode 100644 apps/welcome2/app.png create mode 100644 apps/welcome2/boot.js create mode 100644 apps/welcome2/settings.js diff --git a/apps.json b/apps.json index 3a9925650..53478f638 100644 --- a/apps.json +++ b/apps.json @@ -183,7 +183,8 @@ }, { "id": "welcome", - "name": "Welcome", + "name": "Welcome (Bangle.js 1)", + "shortName": "Welcome", "version": "0.12", "description": "Appears at first boot and explains how to use Bangle.js", "icon": "app.png", @@ -216,6 +217,24 @@ ], "data": [{"name":"mywelcome.json"}] }, + { + "id": "welcome2", + "name": "Welcome (Bangle.js 2)", + "shortName": "Welcome", + "version": "0.13", + "description": "Appears at first boot and explains how to use Bangle.js 2", + "icon": "app.png", + "tags": "start,welcome", + "supports": ["BANGLEJS2"], + "allow_emulator": true, + "storage": [ + {"name":"welcome2.boot.js","url":"boot.js"}, + {"name":"welcome2.app.js","url":"app.js"}, + {"name":"welcome2.settings.js","url":"settings.js"}, + {"name":"welcome2.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"welcome2.json"}] + }, { "id": "gbridge", "name": "Gadgetbridge", diff --git a/apps/welcome2/ChangeLog b/apps/welcome2/ChangeLog new file mode 100644 index 000000000..f72f77a4b --- /dev/null +++ b/apps/welcome2/ChangeLog @@ -0,0 +1,17 @@ +0.01: New App! +0.02: Animate balloon intro +0.03: BTN3 now won't restart when at the end +0.04: Fix regression after tweaks to Storage.readJSON +0.05: Move configuration into App/widget settings +0.06: Move loader into welcome.boot.js +0.07: Run again when updated + Don't run again when settings app is updated (or absent) + Add "Run Now" option to settings +0.08: Don't overwrite existing settings on app update +0.09: Allow welcome to run after a fresh install + More useful app menu + BTN2 now goes to menu on release +0.10: Tweaks to reduce memory usage +0.11: Fix initial screen fill colour +0.12: Fix swipe direction (#800) +0.13: Mods for Bangle.js 2 diff --git a/apps/welcome2/app-icon.js b/apps/welcome2/app-icon.js new file mode 100644 index 000000000..5c1373e17 --- /dev/null +++ b/apps/welcome2/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AU5gAEFtoxnEwXN53WAAXO5oJB42Wy26AAIueFoPXFggAD4AwEGTQiB6otBFgwAD3QvFGC5dCFxiRGGClhrdbv67BXAIuLMBIwPsIABF4OpLwXOFxjBCF6gtBw2r1mHXoXWFxqQWFwOH62rL4IeB6xeOAAIvHGBYuC6+rR4QvCXpovXw3X1i/DR4QuPR5AvKFQOs6+GF4eod4IvPd5AvLwvWLwQvCv4fBR54vURwOHF4iQCX0yOCF4aQBX0QvHSAoAN3SOSd4WyF4yQPLyhgD1YvDMCJeIFxhgCF47BN4BeHFxpgDSAiRORpAuPMIYAFGBYuaF5aSHFwQvEFqQwOeggSBLa4xNF4X+4wAC/xeCFjIADrYwGBIIvlMQiPDBAOk0gDBz2XF8BlEF4eIxADFF8lcF9n+wIrFF05bHF9AsGF9wupGAYv/F8QupGAov/F/4wOF1gA/AH4Ap")) diff --git a/apps/welcome2/app.js b/apps/welcome2/app.js new file mode 100644 index 000000000..d9a967d8a --- /dev/null +++ b/apps/welcome2/app.js @@ -0,0 +1,256 @@ +// exec each function from seq one after the other +function animate(seq,period) { + var c = g.getColor(); + var i = setInterval(function() { + if (seq.length) { + var f = seq.shift(); + g.setColor(c); + if (f) f(); + } else clearInterval(i); + },period); +} + +// Fade in to FG color with angled lines +function fade(col, callback) { + var n = 0; + function f() {"ram" + g.setColor(col); + for (var i=n;i<240;i+=10) g.drawLine(i,0,0,i).drawLine(i,240,240,i); + g.flip(); + n++; + if (n<10) setTimeout(f,0); + else callback(); + } + f(); +} + + +var SCENE_COUNT=10; +function getScene(n) { + if (n==0) return function() { + g.reset().setBgColor(0).clearRect(0,0,176,176); + g.setFont("6x15"); + var n=0; + var l = Bangle.getLogo(); + var im = g.imageMetrics(l); + var i = setInterval(function() { + n+=0.1; + g.setColor(n,n,n); + g.drawImage(l,(176-im.width)/2,(176-im.height)/2); + if (n>=1) { + clearInterval(i); + setTimeout(()=>g.drawString("Open",44,104), 500); + setTimeout(()=>g.drawString("Hackable",44,116), 1000); + setTimeout(()=>g.drawString("Smart Watch",44,128), 1500); + } + },50); + }; + if (n==1) return function() { + var img = require("heatshrink").decompress(atob("ptR4n/j/4gH+8H5wl+jOukVVoHZ8dt/n//n37OtgH9sHhwHp4H5xmkGiH72MRje/LL/7iIAEE7sPEgoAC+AlagIlIiMQErPxDwUYxAABwIHCj8N7nOl3uEqa6BEggnFjfM5nCkUil3gEq5KDAAQmC6QmBE4JxSEhIABiQmB8QmSXoQlCYRMdEwIlCAAIlNhYlOiO85nNEyMPEoZwIAAcsYIYmPXoYlMiKaFExX/u9VEqLBBOYrCH+czmtVqJyDEpiaCOYsgSYszmc3qtTEqMR7hzG8AlGmd1OQglOOY6aEgYlCmmZoJMCTBrnD6SaIEoU/zOUuolSjbnBJgqaCEoU5zOXX4RyQYBBzCS4X5zNDqqZCJiERJg5zBEoVJEoM1JgYlQjhMHc4JLEmZMEEp6ZIJgPzS4WTmZMVTILmFYAK+BmglCmd1JgUYJiPNEorABEIOZygDBm5MCiJMQlhMH8ByBXwIlBJgUxJiMd5nOTIzlBTAK+BAANVq4jPAAS/HJgJyCTATAEACC/B4S/IJgIlCYAgAPiS/Kn5yEYANTEyPc5niOQxMB/LlCOapyJJgbpBYAZzROQK/Gl0ATIWfEoZzBc6IlB6SYGgBJBJgpzSlhyH8EAh5MBTIjnCuIlOjjlHTAJzC/LmDTSSYIEoTABOYIlETSKYHXwIABOYM0yYmETSCYHEobnDOYqaBExu8TAwlEc4U5EoiaCmK+NTAolFEwX0TQzBMXwXiEpTBCAAomNEoS+EEo4mIYIImKEoS+EEpDoBEyUbEo3gEo4mJdAImIJY4lJEycdEoPOOBYmPuIlE+HcJYhKKTZ1fhYkB2EAhnNcYMuEhomMr8A3YABEoJyB5gjOAAYmHm9VgELEoJMBEoXAEyXzE45YBJgXwEqx1I+ByDOYJyVJw5yCgEB3cQGgJMWJwQnCu6/CgFBigDB13S/glVAAf1qomCglEoADB1QDBADEPEoNVqEAolEgEKolKErJMDYAJMD0lE0AmaEoNaAgJMCFIYAahV/IgIiDOTgABNYJMEOToiCIoJMCOTzfCN4RMBOTxsDJIRyfIwZMBKQZzfJgRyfOYZMBOUBzCJgNKOT5zDJgLoCADxKBOAIABOT6aCAARyfOYRyjOYRyjOYlKEsBzEEsBzEOUJzDOUIABOUiaDOURzCOUZzCEscKCiY")); + var im = g.imageMetrics(img); + g.reset(); + g.setBgColor("#ff00ff"); + var y = 176, speed = 5; + function balloon(callback) { + y-=speed; + var x = (176-im.width)/2; + g.drawImage(img,x,y); + g.clearRect(x,y+81,x+77,y+81+speed); + if (y>30) setTimeout(balloon,0,callback); + else callback(); + } + fade("#ff00ff", function() { + balloon(function() { + g.setColor(-1).setFont("6x15:2").setFontAlign(0,0); + g.drawString("Welcome.",88,130); + }); + }); + setTimeout(function() { + var n=0; + var i = setInterval(function() { + n+=4; + g.scroll(0,-4); + if (n>150) + clearInterval(i); + },20); + },3500); + + }; + if (n==2) return function() { + g.reset(); + g.setBgColor("#ffff00").setColor(0).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 70, y = 25, h=25; + animate([ + ()=>g.drawString("Your",x,y+=h), + ()=>g.drawString("Bangle.js",x,y+=h), + ()=>g.drawString("has one",x,y+=h), + ()=>g.drawString("button",x,y+=h), + ()=>{g.setFont("12x20:2").setFontAlign(0,0,1).drawString("HERE!",150,88);} + ],200); + }; + if (n==3) return function() { + g.reset(); + g.setBgColor("#00ffff").setColor(0).clear(); + g.setFontAlign(0,0).setFont("6x15:2"); + g.drawString("Press",88,40).setFontAlign(0,-1); + g.setFont("12x20"); + g.drawString("To wake the\nscreen up, or to\nselect", 88,60); + }; + if (n==4) return function() { + g.reset(); + g.setBgColor("#00ffff").setColor(0).clear(); + g.setFontAlign(0,0).setFont("6x15:2"); + g.drawString("Long Press",88,40).setFontAlign(0,-1); + g.setFont("12x20"); + g.drawString("To go back to\nthe clock", 88,60); + }; + if (n==5) return function() { + g.reset(); + g.setBgColor("#ff0000").setColor(0).clear(); + g.setFontAlign(0,0).setFont("12x20"); + g.drawString("If Bangle.js ever\nstops, hold the\nbutton for\nten seconds.\n\nBangle.js will\nthen reboot.", 88,78); + }; + if (n==6) return function() { + g.reset(); + g.setBgColor("#0000ff").setColor(-1).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 88, y = -20, h=60; + animate([ + ()=>{g.drawString("Bangle.js has a\nfull touchscreen",x,y+=h);}, + 0,0, + ()=>{g.drawString("Drag up and down\nto scroll and\ntap to select",x,y+=h);}, + ],300); + }; + if (n==7) return function() { + g.reset(); + g.setBgColor("#00ff00").setColor(0).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 88, y = -35, h=80; + animate([ + ()=>{g.drawString("Bangle.js comes\nwith a few\napps installed",x,y+=h);}, + 0,0, + ()=>{g.drawString("To add more, visit\nbanglejs.com/apps",x,y+=h);}, + ],400); + }; + if (n==8) return function() { + g.reset(); + g.setBgColor("#ff0000").setColor(0).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 88; + g.drawString("You can also make\nyour own apps!",x,30); + g.drawString("Check out\nbanglejs.com",x,130); + + var rx = 0, ry = 0; + // draw a cube + function draw() { + // rotate + rx += 0.1; + ry += 0.11; + var rcx=Math.cos(rx), + rsx=Math.sin(rx), + rcy=Math.cos(ry), + rsy=Math.sin(ry); + // Project 3D coordinates into 2D + function p(x,y,z) { + var t; + t = x*rcy + z*rsy; + z = z*rcy - x*rsy; + x=t; + t = y*rcx + z*rsx; + z = z*rcx - y*rsx; + y=t; + z += 4; + return [88 + 60*x/z, 78+ 60*y/z]; + } + + var a; + // draw a series of lines to make up our cube + var s = 30; + g.clearRect(88-s,78-s,88+s,78+s); + a = p(-1,-1,-1); g.moveTo(a[0],a[1]); + a = p(1,-1,-1); g.lineTo(a[0],a[1]); + a = p(1,1,-1); g.lineTo(a[0],a[1]); + a = p(-1,1,-1); g.lineTo(a[0],a[1]); + a = p(-1,-1,-1); g.lineTo(a[0],a[1]); + a = p(-1,-1,1); g.moveTo(a[0],a[1]); + a = p(1,-1,1); g.lineTo(a[0],a[1]); + a = p(1,1,1); g.lineTo(a[0],a[1]); + a = p(-1,1,1); g.lineTo(a[0],a[1]); + a = p(-1,-1,1); g.lineTo(a[0],a[1]); + a = p(-1,-1,-1); g.moveTo(a[0],a[1]); + a = p(-1,-1,1); g.lineTo(a[0],a[1]); + a = p(1,-1,-1); g.moveTo(a[0],a[1]); + a = p(1,-1,1); g.lineTo(a[0],a[1]); + a = p(1,1,-1); g.moveTo(a[0],a[1]); + a = p(1,1,1); g.lineTo(a[0],a[1]); + a = p(-1,1,-1); g.moveTo(a[0],a[1]); + a = p(-1,1,1); g.lineTo(a[0],a[1]); + } + + setInterval(draw,50); + }; + if (n==9) return function() { + g.reset(); + g.setBgColor("#ffffff");g.clear(); + g.setFontAlign(0,0); + g.setFont("12x20"); + + var x = 88, y = 10, h=21; + animate([ + ()=>g.drawString("That's it!",x,y+=h), + ()=>{g.drawString("Press",x,y+=h*2); + g.drawString("the button",x,y+=h); + g.drawString("to start",x,y+=h); + g.drawString("Bangle.js",x,y+=h);} + ],400); + } +} + +var sceneNumber = 0; + +function move(dir) { + if (dir>0 && sceneNumber+1 == SCENE_COUNT) return; // at the end + sceneNumber = (sceneNumber+dir)%SCENE_COUNT; + if (sceneNumber<0) sceneNumber=0; + clearInterval(); + getScene(sceneNumber)(); + if (sceneNumber>1) { + var l = SCENE_COUNT; + for (var i=0;i move(dir)); +setWatch(()=>{ + if (sceneNumber == SCENE_COUNT-1) + load(); + else + move(1); +}, BTN1, {repeat:true}); + +(function migrateSettings(){ + let global_settings = require('Storage').readJSON('setting.json', 1) + if (global_settings) { + delete global_settings.welcomed + require('Storage').write('setting.json', global_settings) + } +})() + +Bangle.setLCDTimeout(0); +Bangle.setLCDPower(1); +move(0); diff --git a/apps/welcome2/app.png b/apps/welcome2/app.png new file mode 100644 index 0000000000000000000000000000000000000000..ebbf254bd7c3546e8337c97648a7eb56747c81ac GIT binary patch literal 1939 zcmV;E2WP)^Vse?M*ABwfS z8MZ{CGrE{9Te8V!(51`XGM!;roJ%xW2E^g2NO{qXsim}(VOBaof>d5Dolx3xFQxb1 z-nZxMkEd_+KDRC9c8eL`e{OQldCvKM@9+H1@Ao`#6F2ey4asCOZNf#^U4iltNGVVP zD9{Fa2%!dFY=)EdIoSdkU1+ff<1#70}x@Hu6Kz{*xG?E@iS0CV6Z_UXTXX+c`#EfepLM z7bkj*!-~A7=*pg+Sbi*K%7RHf4giWO!Qq3`KOGl&fb97ejd1$m`W-Ff7CVP?6!NP@ z*#dZSJOwP6bwBxEm_^gcdS*L+X9eY#LFhPi?iv?)0Qct&s7(@2rVYtLc?9_Na7Wq| z*s!a7VL$mhO77$FB^z-&y=>gMfp-sY$1lBBpl3RC?8G{+#s}vNi$-#!6vBPB&)OMR zSFd&$bZt`bEWGtWzIxkQ4xXu@;q+gK#Us}SYaD|6$H%;%Ti;I9kp;}PGuy7fJm2Nj z3iFC{=TP>!hdI^J%>L8W066-F^;LCQ!M&eAbbP(vkK`q5{1_wcNTtxuZ2ySr8h}N& zRszc2md$8ii@03Y-n|s>8yCR?kc`--87pEZEdlP41PEPrX4^*;%A5{2vnS8zR7W$x z$ly8R&9b`gL{GM5xU>zNlg7h)0KF}J0vsa*DW~nsDFU8oDs=&cd09m`4HsuS|8}Go}cbc4m$MYKWCB5WaYg-fnm1w~?N@s{QIvQxc8y;|@aI_lzb}w~BisS# zemenx-!nLkY_L)4u$>fXwvAaIiTBWc+v}7`&~p6!uCs3uaov50-F-3g3k#a`wiTU zvP%9=C|wR)0Y87>{T;xe;}`am?ajxK)a*r5vJpf0+j$=?(bQEM4dHi_T)m(OP>_0! z1_+^4NFyyH<*{D@K^5csiY97XHdE-Io)Y4AJ18jdq6!;N6w__G$4oxaYSOXO>ixVd z+ws{6d|mcaH(IW=leb~(nby14&Nh=~ygVGcVjJW0xrjs(bVp2%hH_X~(1Taf-^c)x z7fdd^7$=di#^z*S%ALL$;a+Lr=xF3e6aY{@cU@z0T5E~G9qD5HeiuEVI9(y3DL9Gw z1yOPg`+ITpuxVa+DK3e)^{?OJ2V;=^WrSx4^Q0WKKXvUe>I+R(Rh1DhZFp7C$I`_l zlS*4#H@%Sr4uO?(LjPWs*V=<2)&jK*IPU`jS>z$Ju_P`2WvTIvXn zmH3zi^y9hR%brk`mjK)IQWZKoBLssHEiBBw=l9z94dC&#M}GJIl*eYU7cZ~QEIAx*jx#4V*6tXFt75D0`S3i z9}Y}9+K^{NLmzuXR~F6skih>$z;LX3{!6Pxw0a<^>ZsE89`|RJp-`2xV9n22`K4{uPal12c#LV76{gFJ8nP!- zaw36avkT_Uy!ZAWT)neo#k6g;jWdVG2vqd_X5^b_KQbcVPgUo7pa0hDpH9h{^Jkwc z-!McT8S3Bl{(_CIEp)Rdry0B}FR$L8xE8Y*}X}Yi3ly zimxsC2XF#(_RiV^WkXzj?IRg&AIPu(06{kcC_FmEycEFvEC@)5a|@6}ST)4#1`by) zyX#!0>t$TP%4Lh%RHy~~L+v8eWV#kifB-A0ZW?Y&s`=Rw5cL>u!8JF_b1TZrj!Z7V#$X$n89sf7V!{MeE?v?nqF**mb8fyw8z-vHP29u{ Z;y>o9o!*JS4-fzV002ovPDHLkV1gG+pke?3 literal 0 HcmV?d00001 diff --git a/apps/welcome2/boot.js b/apps/welcome2/boot.js new file mode 100644 index 000000000..07b7386f1 --- /dev/null +++ b/apps/welcome2/boot.js @@ -0,0 +1,9 @@ +(function() { + let s = require('Storage').readJSON('welcome2.json', 1) || {}; + if (!s.welcomed) { + setTimeout(() => { + require('Storage').write('welcome2.json', {welcomed: true}) + load('welcome2.app.js') + }) + } +})() diff --git a/apps/welcome2/settings.js b/apps/welcome2/settings.js new file mode 100644 index 000000000..d87cf4b55 --- /dev/null +++ b/apps/welcome2/settings.js @@ -0,0 +1,18 @@ +(function(back) { + let settings = require('Storage').readJSON('welcome2.json', 1) + || require('Storage').readJSON('setting.json', 1) || {} + E.showMenu({ + '': { 'title': 'Welcome App' }, + 'Run next boot': { + value: !settings.welcomed, + format: v => v ? 'Yes' : 'No', + onchange: v => require('Storage').write('welcome2.json', {welcomed: !v}), + }, + 'Run Now': () => load('welcome2.app.js'), + 'Turn off & run next': () => { + require('Storage').write('welcome2.json', {welcomed: false}); + Bangle.off(); + }, + '< Back': back, + }) +}) From 641cd01230947f706518064acd54fa55c4bf4da5 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 27 Oct 2021 16:42:54 +0100 Subject: [PATCH 0409/1062] Option to update device time automatically. And always fix it if time has obviously never been set --- core | 2 +- index.html | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core b/core index df02cd052..a7d82825d 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit df02cd0529d81439fb859b553576398a445ef1b8 +Subproject commit a7d82825d3a43e1da7919591ed6fa776f1f0545a diff --git a/index.html b/index.html index 0185f1bae..e7c7c31cd 100644 --- a/index.html +++ b/index.html @@ -136,6 +136,10 @@ Pretokenise apps before upload (smaller, faster apps) +

From d771a6a732100a4cf0934faef1f58928292cb372 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 27 Oct 2021 16:49:53 +0100 Subject: [PATCH 0410/1062] boot 0.35: Add Bangle.appRect polyfill Don't set beep vibration up on Bangle.js 2 (built in) settings 0.31: Remove Bangle 1 settings when running on Bangle 2 --- apps.json | 4 ++-- apps/boot/ChangeLog | 2 ++ apps/boot/bootupdate.js | 9 +++++++-- apps/setting/ChangeLog | 3 ++- apps/setting/settings.js | 16 +++++++++++----- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/apps.json b/apps.json index 53478f638..d1ebe5249 100644 --- a/apps.json +++ b/apps.json @@ -16,7 +16,7 @@ { "id": "boot", "name": "Bootloader", - "version": "0.34", + "version": "0.35", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "icon": "bootloader.png", "type": "bootloader", @@ -272,7 +272,7 @@ { "id": "setting", "name": "Settings", - "version": "0.30", + "version": "0.31", "description": "A menu for setting up Bangle.js", "icon": "settings.png", "tags": "tool,system", diff --git a/apps/boot/ChangeLog b/apps/boot/ChangeLog index d3a241d7c..6dc2a3577 100644 --- a/apps/boot/ChangeLog +++ b/apps/boot/ChangeLog @@ -37,3 +37,5 @@ 0.33: Add E.showScroller polyfill 0.34: Use Storage.hash if available Rearrange NRF.setServices to allow .boot.js files to add services (eg ANCS) +0.35: Add Bangle.appRect polyfill + Don't set beep vibration up on Bangle.js 2 (built in) diff --git a/apps/boot/bootupdate.js b/apps/boot/bootupdate.js index c96cc8e83..dfd745de2 100644 --- a/apps/boot/bootupdate.js +++ b/apps/boot/bootupdate.js @@ -3,7 +3,7 @@ recalculates, but this avoids us doing a whole bunch of reconfiguration most of the time. */ E.showMessage("Updating boot0..."); var s = require('Storage').readJSON('setting.json',1)||{}; -var isB2 = process.env.HWVERSION; // Is Bangle.js 2 +var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2 var boot = ""; if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/); @@ -55,7 +55,7 @@ boot += `E.setTimeZone(${s.timezone});`; if (!Bangle.F_BEEPSET) { if (!s.vibrate) boot += `Bangle.buzz=Promise.resolve;\n` if (s.beep===false) boot += `Bangle.beep=Promise.resolve;\n` - else if (s.beep=="vib") boot += `Bangle.beep = function (time, freq) { + else if (s.beep=="vib" && !BANGLEJS2) boot += `Bangle.beep = function (time, freq) { return new Promise(function(resolve) { if ((0|freq)<=0) freq=4000; if ((0|time)<=0) time=200; @@ -182,6 +182,11 @@ if (!g.wrapString) { // added in 2v11 - this is a limited functionality polyfill } return lines; };\n`; +}; +delete Bangle.appRect; // deleting stops us getting confused by our own decl. builtins can't be deleted +if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares + boot += `Bangle.appRect = ((y,w,h)=>({x:0,y:0,w:w,h:h,x2:w-1,y2:h-1}))(g.getWidth(),g.getHeight()); + (lw=>{ Bangle.loadWidgets = () => { lw(); Bangle.appRect = ((y,w,h)=>({x:0,y:y,w:w,h:h-y,x2:w-1,y2:h-(1+h)}))(global.WIDGETS?24:0,g.getWidth(),g.getHeight()); }; })(Bangle.loadWidgets);\n`; } // Append *.boot.js files diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 49915ee21..0890cf510 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -32,4 +32,5 @@ 0.27: Add Theme menu 0.28: Update Quiet Mode widget (if present) 0.29: Add Customize to Theme menu -0.30: Move '< Back' to the top of menus \ No newline at end of file +0.30: Move '< Back' to the top of menus +0.31: Remove Bangle 1 settings when running on Bangle 2 diff --git a/apps/setting/settings.js b/apps/setting/settings.js index a0e535df7..0c2930086 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -1,6 +1,7 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); +const BANGLEJS2 = process.env.HWVERSION==2; const storage = require('Storage'); let settings; @@ -71,8 +72,8 @@ if (!('qmOptions' in settings)) settings.qmOptions = {}; // easier if this alway const boolFormat = v => v ? "On" : "Off"; function showMainMenu() { - var beepV = [false, true, "vib"]; - var beepN = ["Off", "Piezo", "Vibrate"]; + var beepV = BANGLEJS2 ? [false,true] : [false, true, "vib"]; + var beepN = BANGLEJS2 ? ["Off","On"] : ["Off", "Piezo", "Vibrate"]; const mainmenu = { '': { 'title': 'Settings' }, '< Back': ()=>load(), @@ -119,6 +120,7 @@ function showMainMenu() { 'Reset Settings': ()=>showResetMenu(), 'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }, }; + return E.showMenu(mainmenu); } @@ -356,7 +358,10 @@ function showLCDMenu() { settings.options.wakeOnBTN1 = !settings.options.wakeOnBTN1; updateOptions(); } - }, + } + }; + if (!BANGLEJS2) + Object.assign(lcdMenu, { 'Wake on BTN2': { value: settings.options.wakeOnBTN2, format: boolFormat, @@ -372,7 +377,8 @@ function showLCDMenu() { settings.options.wakeOnBTN3 = !settings.options.wakeOnBTN3; updateOptions(); } - }, + }}); + Object.assign(lcdMenu, { 'Wake on FaceUp': { value: settings.options.wakeOnFaceUp, format: boolFormat, @@ -427,7 +433,7 @@ function showLCDMenu() { updateOptions(); } } - } + }); return E.showMenu(lcdMenu) } function showQuietModeMenu() { From f87f0a19f44df0e75a5ffc64eb4f5dba4819ea3c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 27 Oct 2021 16:50:16 +0100 Subject: [PATCH 0411/1062] Layout now uses appRect (should be fine with the new polyFill) --- modules/Layout.js | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/modules/Layout.js b/modules/Layout.js index 319f6901e..8a5b0a0a5 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -80,7 +80,6 @@ function Layout(layout, options) { this._l = this.l = layout; // Do we have >1 physical buttons? this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; - this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0; options = options || {}; this.lazy = options.lazy || false; @@ -131,7 +130,7 @@ function Layout(layout, options) { if (this.b[btn].cb) this.b[btn].cb(e); } // enough physical buttons - let btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns); + let btnHeight = Math.floor(Bangle.appRect.h / this.physBtns); if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch); Bangle.btnWatch = []; if (this.physBtns > 2 && buttons.length==1) @@ -344,10 +343,6 @@ Layout.prototype.debug = function(l,c) { }; Layout.prototype.update = function() { delete this.updateNeeded; - var l = this._l; - var w = g.getWidth(); - var y = this.yOffset; - var h = g.getHeight()-y; // update sizes function updateMin(l) {"ram" cb[l.type](l); @@ -396,18 +391,19 @@ Layout.prototype.update = function() { if (l.filly == null && l.c.some(c=>c.filly)) l.filly = 1; } }; + + var l = this._l; updateMin(l); - // center - if (l.fillx || l.filly) { - l.w = w; - l.h = h; - l.x = 0; - l.y = y; - } else { + if (l.fillx || l.filly) { // fill all + l.w = Bangle.appRect.w; + l.h = Bangle.appRect.h; + l.x = Bangle.appRect.x; + l.y = Bangle.appRect.y; + } else { // or center l.w = l._w; l.h = l._h; - l.x = (w-l.w)>>1; - l.y = y+((h-l.h)>>1); + l.x = (Bangle.appRect.w-l.w)>>1; + l.y = Bangle.appRect.y+((Bangle.appRect.h-l.h)>>1); } // layout children this.layout(l); From 375cbf3166150a8d4d41d3e57cb80787495cde54 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 27 Oct 2021 16:51:09 +0100 Subject: [PATCH 0412/1062] set minimum height for apps now we need room for the screenshot --- css/main.css | 1 + 1 file changed, 1 insertion(+) diff --git a/css/main.css b/css/main.css index 60a760905..82f6fbcfe 100644 --- a/css/main.css +++ b/css/main.css @@ -34,6 +34,7 @@ position: relative; border-bottom: 1px solid #EEE; margin-bottom: 4px; + min-height: 8em; } .tile-content { position: relative; } From 9e404c625296d008314a547ff8e798767f65e426 Mon Sep 17 00:00:00 2001 From: David Skrabal Date: Wed, 27 Oct 2021 12:25:05 -0400 Subject: [PATCH 0413/1062] Customize --- README.md | 7 ++++--- index.html | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d60d46fd3..a620121b8 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ -Bangle.js App Loader (and Apps) +labarks' testing Bangle.js App Loader (and Apps) ================================ [![Build Status](https://travis-ci.org/espruino/BangleApps.svg?branch=master)](https://travis-ci.org/espruino/BangleApps) -* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps) -* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/) +* Try my **development version** at [github.io](https://labarks.github.io/BangleApps/) +* Try the official **release version** at [banglejs.com/apps](https://banglejs.com/apps) +* Try the official **development version** at [github.io](https://espruino.github.io/BangleApps/) **All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By submitting code to this repository you confirm that you are happy with it being MIT licensed, diff --git a/index.html b/index.html index 0185f1bae..a5aa94e6a 100644 --- a/index.html +++ b/index.html @@ -17,7 +17,7 @@ - Bangle.js App Loader + labarks' testing Bangle.js App Loader
/ elements with colspans. + SOLUTION: making individual
+ _this.frameElRefs = new RefMap(); // the fc-daygrid-day-frame + _this.fgElRefs = new RefMap(); // the fc-daygrid-day-events + _this.segHarnessRefs = new RefMap(); // indexed by "instanceId:firstCol" + _this.rootElRef = createRef(); + _this.state = { + framePositions: null, + maxContentHeight: null, + eventInstanceHeights: {}, + }; + return _this; + } + TableRow.prototype.render = function () { + var _this = this; + var _a = this, props = _a.props, state = _a.state, context = _a.context; + var options = context.options; + var colCnt = props.cells.length; + var businessHoursByCol = splitSegsByFirstCol(props.businessHourSegs, colCnt); + var bgEventSegsByCol = splitSegsByFirstCol(props.bgEventSegs, colCnt); + var highlightSegsByCol = splitSegsByFirstCol(this.getHighlightSegs(), colCnt); + var mirrorSegsByCol = splitSegsByFirstCol(this.getMirrorSegs(), colCnt); + var _b = computeFgSegPlacement(sortEventSegs(props.fgEventSegs, options.eventOrder), props.dayMaxEvents, props.dayMaxEventRows, options.eventOrderStrict, state.eventInstanceHeights, state.maxContentHeight, props.cells), singleColPlacements = _b.singleColPlacements, multiColPlacements = _b.multiColPlacements, moreCnts = _b.moreCnts, moreMarginTops = _b.moreMarginTops; + var isForcedInvisible = // TODO: messy way to compute this + (props.eventDrag && props.eventDrag.affectedInstances) || + (props.eventResize && props.eventResize.affectedInstances) || + {}; + return (createElement("tr", { ref: this.rootElRef }, + props.renderIntro && props.renderIntro(), + props.cells.map(function (cell, col) { + var normalFgNodes = _this.renderFgSegs(col, props.forPrint ? singleColPlacements[col] : multiColPlacements[col], props.todayRange, isForcedInvisible); + var mirrorFgNodes = _this.renderFgSegs(col, buildMirrorPlacements(mirrorSegsByCol[col], multiColPlacements), props.todayRange, {}, Boolean(props.eventDrag), Boolean(props.eventResize), false); + return (createElement(TableCell, { key: cell.key, elRef: _this.cellElRefs.createRef(cell.key), innerElRef: _this.frameElRefs.createRef(cell.key) /* FF problem, but okay to use for left/right. TODO: rename prop */, dateProfile: props.dateProfile, date: cell.date, showDayNumber: props.showDayNumbers, showWeekNumber: props.showWeekNumbers && col === 0, forceDayTop: props.showWeekNumbers /* even displaying weeknum for row, not necessarily day */, todayRange: props.todayRange, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, extraHookProps: cell.extraHookProps, extraDataAttrs: cell.extraDataAttrs, extraClassNames: cell.extraClassNames, extraDateSpan: cell.extraDateSpan, moreCnt: moreCnts[col], moreMarginTop: moreMarginTops[col], singlePlacements: singleColPlacements[col], fgContentElRef: _this.fgElRefs.createRef(cell.key), fgContent: ( // Fragment scopes the keys + createElement(Fragment, null, + createElement(Fragment, null, normalFgNodes), + createElement(Fragment, null, mirrorFgNodes))), bgContent: ( // Fragment scopes the keys + createElement(Fragment, null, + _this.renderFillSegs(highlightSegsByCol[col], 'highlight'), + _this.renderFillSegs(businessHoursByCol[col], 'non-business'), + _this.renderFillSegs(bgEventSegsByCol[col], 'bg-event'))) })); + }))); + }; + TableRow.prototype.componentDidMount = function () { + this.updateSizing(true); + }; + TableRow.prototype.componentDidUpdate = function (prevProps, prevState) { + var currentProps = this.props; + this.updateSizing(!isPropsEqual(prevProps, currentProps)); + }; + TableRow.prototype.getHighlightSegs = function () { + var props = this.props; + if (props.eventDrag && props.eventDrag.segs.length) { // messy check + return props.eventDrag.segs; + } + if (props.eventResize && props.eventResize.segs.length) { // messy check + return props.eventResize.segs; + } + return props.dateSelectionSegs; + }; + TableRow.prototype.getMirrorSegs = function () { + var props = this.props; + if (props.eventResize && props.eventResize.segs.length) { // messy check + return props.eventResize.segs; + } + return []; + }; + TableRow.prototype.renderFgSegs = function (col, segPlacements, todayRange, isForcedInvisible, isDragging, isResizing, isDateSelecting) { + var context = this.context; + var eventSelection = this.props.eventSelection; + var framePositions = this.state.framePositions; + var defaultDisplayEventEnd = this.props.cells.length === 1; // colCnt === 1 + var isMirror = isDragging || isResizing || isDateSelecting; + var nodes = []; + if (framePositions) { + for (var _i = 0, segPlacements_1 = segPlacements; _i < segPlacements_1.length; _i++) { + var placement = segPlacements_1[_i]; + var seg = placement.seg; + var instanceId = seg.eventRange.instance.instanceId; + var key = instanceId + ':' + col; + var isVisible = placement.isVisible && !isForcedInvisible[instanceId]; + var isAbsolute = placement.isAbsolute; + var left = ''; + var right = ''; + if (isAbsolute) { + if (context.isRtl) { + right = 0; + left = framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol]; + } + else { + left = 0; + right = framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol]; + } + } + /* + known bug: events that are force to be list-item but span multiple days still take up space in later columns + todo: in print view, for multi-day events, don't display title within non-start/end segs + */ + nodes.push(createElement("div", { className: 'fc-daygrid-event-harness' + (isAbsolute ? ' fc-daygrid-event-harness-abs' : ''), key: key, ref: isMirror ? null : this.segHarnessRefs.createRef(key), style: { + visibility: isVisible ? '' : 'hidden', + marginTop: isAbsolute ? '' : placement.marginTop, + top: isAbsolute ? placement.absoluteTop : '', + left: left, + right: right, + } }, hasListItemDisplay(seg) ? (createElement(TableListItemEvent, __assign({ seg: seg, isDragging: isDragging, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange)))) : (createElement(TableBlockEvent, __assign({ seg: seg, isDragging: isDragging, isResizing: isResizing, isDateSelecting: isDateSelecting, isSelected: instanceId === eventSelection, defaultDisplayEventEnd: defaultDisplayEventEnd }, getSegMeta(seg, todayRange)))))); + } + } + return nodes; + }; + TableRow.prototype.renderFillSegs = function (segs, fillType) { + var isRtl = this.context.isRtl; + var todayRange = this.props.todayRange; + var framePositions = this.state.framePositions; + var nodes = []; + if (framePositions) { + for (var _i = 0, segs_1 = segs; _i < segs_1.length; _i++) { + var seg = segs_1[_i]; + var leftRightCss = isRtl ? { + right: 0, + left: framePositions.lefts[seg.lastCol] - framePositions.lefts[seg.firstCol], + } : { + left: 0, + right: framePositions.rights[seg.firstCol] - framePositions.rights[seg.lastCol], + }; + nodes.push(createElement("div", { key: buildEventRangeKey(seg.eventRange), className: "fc-daygrid-bg-harness", style: leftRightCss }, fillType === 'bg-event' ? + createElement(BgEvent, __assign({ seg: seg }, getSegMeta(seg, todayRange))) : + renderFill(fillType))); + } + } + return createElement.apply(void 0, __spreadArray([Fragment, {}], nodes)); + }; + TableRow.prototype.updateSizing = function (isExternalSizingChange) { + var _a = this, props = _a.props, frameElRefs = _a.frameElRefs; + if (!props.forPrint && + props.clientWidth !== null // positioning ready? + ) { + if (isExternalSizingChange) { + var frameEls = props.cells.map(function (cell) { return frameElRefs.currentMap[cell.key]; }); + if (frameEls.length) { + var originEl = this.rootElRef.current; + this.setState({ + framePositions: new PositionCache(originEl, frameEls, true, // isHorizontal + false), + }); + } + } + var limitByContentHeight = props.dayMaxEvents === true || props.dayMaxEventRows === true; + this.setState({ + eventInstanceHeights: this.queryEventInstanceHeights(), + maxContentHeight: limitByContentHeight ? this.computeMaxContentHeight() : null, + }); + } + }; + TableRow.prototype.queryEventInstanceHeights = function () { + var segElMap = this.segHarnessRefs.currentMap; + var eventInstanceHeights = {}; + // get the max height amongst instance segs + for (var key in segElMap) { + var height = Math.round(segElMap[key].getBoundingClientRect().height); + var instanceId = key.split(':')[0]; // deconstruct how renderFgSegs makes the key + eventInstanceHeights[instanceId] = Math.max(eventInstanceHeights[instanceId] || 0, height); + } + return eventInstanceHeights; + }; + TableRow.prototype.computeMaxContentHeight = function () { + var firstKey = this.props.cells[0].key; + var cellEl = this.cellElRefs.currentMap[firstKey]; + var fcContainerEl = this.fgElRefs.currentMap[firstKey]; + return cellEl.getBoundingClientRect().bottom - fcContainerEl.getBoundingClientRect().top; + }; + TableRow.prototype.getCellEls = function () { + var elMap = this.cellElRefs.currentMap; + return this.props.cells.map(function (cell) { return elMap[cell.key]; }); + }; + return TableRow; + }(DateComponent)); + TableRow.addStateEquality({ + eventInstanceHeights: isPropsEqual, + }); + function buildMirrorPlacements(mirrorSegs, colPlacements) { + if (!mirrorSegs.length) { + return []; + } + var topsByInstanceId = buildAbsoluteTopHash(colPlacements); // TODO: cache this at first render? + return mirrorSegs.map(function (seg) { return ({ + seg: seg, + isVisible: true, + isAbsolute: true, + absoluteTop: topsByInstanceId[seg.eventRange.instance.instanceId], + marginTop: 0, + }); }); + } + function buildAbsoluteTopHash(colPlacements) { + var topsByInstanceId = {}; + for (var _i = 0, colPlacements_1 = colPlacements; _i < colPlacements_1.length; _i++) { + var placements = colPlacements_1[_i]; + for (var _a = 0, placements_1 = placements; _a < placements_1.length; _a++) { + var placement = placements_1[_a]; + topsByInstanceId[placement.seg.eventRange.instance.instanceId] = placement.absoluteTop; + } + } + return topsByInstanceId; + } + + var Table = /** @class */ (function (_super) { + __extends(Table, _super); + function Table() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.splitBusinessHourSegs = memoize(splitSegsByRow); + _this.splitBgEventSegs = memoize(splitSegsByRow); + _this.splitFgEventSegs = memoize(splitSegsByRow); + _this.splitDateSelectionSegs = memoize(splitSegsByRow); + _this.splitEventDrag = memoize(splitInteractionByRow); + _this.splitEventResize = memoize(splitInteractionByRow); + _this.rowRefs = new RefMap(); + _this.handleRootEl = function (rootEl) { + _this.rootEl = rootEl; + if (rootEl) { + _this.context.registerInteractiveComponent(_this, { + el: rootEl, + isHitComboAllowed: _this.props.isHitComboAllowed, + }); + } + else { + _this.context.unregisterInteractiveComponent(_this); + } + }; + return _this; + } + Table.prototype.render = function () { + var _this = this; + var props = this.props; + var dateProfile = props.dateProfile, dayMaxEventRows = props.dayMaxEventRows, dayMaxEvents = props.dayMaxEvents, expandRows = props.expandRows; + var rowCnt = props.cells.length; + var businessHourSegsByRow = this.splitBusinessHourSegs(props.businessHourSegs, rowCnt); + var bgEventSegsByRow = this.splitBgEventSegs(props.bgEventSegs, rowCnt); + var fgEventSegsByRow = this.splitFgEventSegs(props.fgEventSegs, rowCnt); + var dateSelectionSegsByRow = this.splitDateSelectionSegs(props.dateSelectionSegs, rowCnt); + var eventDragByRow = this.splitEventDrag(props.eventDrag, rowCnt); + var eventResizeByRow = this.splitEventResize(props.eventResize, rowCnt); + var limitViaBalanced = dayMaxEvents === true || dayMaxEventRows === true; + // if rows can't expand to fill fixed height, can't do balanced-height event limit + // TODO: best place to normalize these options? + if (limitViaBalanced && !expandRows) { + limitViaBalanced = false; + dayMaxEventRows = null; + dayMaxEvents = null; + } + var classNames = [ + 'fc-daygrid-body', + limitViaBalanced ? 'fc-daygrid-body-balanced' : 'fc-daygrid-body-unbalanced', + expandRows ? '' : 'fc-daygrid-body-natural', // will height of one row depend on the others? + ]; + return (createElement("div", { className: classNames.join(' '), ref: this.handleRootEl, style: { + // these props are important to give this wrapper correct dimensions for interactions + // TODO: if we set it here, can we avoid giving to inner tables? + width: props.clientWidth, + minWidth: props.tableMinWidth, + } }, + createElement(NowTimer, { unit: "day" }, function (nowDate, todayRange) { return (createElement(Fragment, null, + createElement("table", { className: "fc-scrollgrid-sync-table", style: { + width: props.clientWidth, + minWidth: props.tableMinWidth, + height: expandRows ? props.clientHeight : '', + } }, + props.colGroupNode, + createElement("tbody", null, props.cells.map(function (cells, row) { return (createElement(TableRow, { ref: _this.rowRefs.createRef(row), key: cells.length + ? cells[0].date.toISOString() /* best? or put key on cell? or use diff formatter? */ + : row // in case there are no cells (like when resource view is loading) + , showDayNumbers: rowCnt > 1, showWeekNumbers: props.showWeekNumbers, todayRange: todayRange, dateProfile: dateProfile, cells: cells, renderIntro: props.renderRowIntro, businessHourSegs: businessHourSegsByRow[row], eventSelection: props.eventSelection, bgEventSegs: bgEventSegsByRow[row].filter(isSegAllDay) /* hack */, fgEventSegs: fgEventSegsByRow[row], dateSelectionSegs: dateSelectionSegsByRow[row], eventDrag: eventDragByRow[row], eventResize: eventResizeByRow[row], dayMaxEvents: dayMaxEvents, dayMaxEventRows: dayMaxEventRows, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint })); }))))); }))); + }; + // Hit System + // ---------------------------------------------------------------------------------------------------- + Table.prototype.prepareHits = function () { + this.rowPositions = new PositionCache(this.rootEl, this.rowRefs.collect().map(function (rowObj) { return rowObj.getCellEls()[0]; }), // first cell el in each row. TODO: not optimal + false, true); + this.colPositions = new PositionCache(this.rootEl, this.rowRefs.currentMap[0].getCellEls(), // cell els in first row + true, // horizontal + false); + }; + Table.prototype.queryHit = function (positionLeft, positionTop) { + var _a = this, colPositions = _a.colPositions, rowPositions = _a.rowPositions; + var col = colPositions.leftToIndex(positionLeft); + var row = rowPositions.topToIndex(positionTop); + if (row != null && col != null) { + var cell = this.props.cells[row][col]; + return { + dateProfile: this.props.dateProfile, + dateSpan: __assign({ range: this.getCellRange(row, col), allDay: true }, cell.extraDateSpan), + dayEl: this.getCellEl(row, col), + rect: { + left: colPositions.lefts[col], + right: colPositions.rights[col], + top: rowPositions.tops[row], + bottom: rowPositions.bottoms[row], + }, + layer: 0, + }; + } + return null; + }; + Table.prototype.getCellEl = function (row, col) { + return this.rowRefs.currentMap[row].getCellEls()[col]; // TODO: not optimal + }; + Table.prototype.getCellRange = function (row, col) { + var start = this.props.cells[row][col].date; + var end = addDays(start, 1); + return { start: start, end: end }; + }; + return Table; + }(DateComponent)); + function isSegAllDay(seg) { + return seg.eventRange.def.allDay; + } + + var DayTableSlicer = /** @class */ (function (_super) { + __extends(DayTableSlicer, _super); + function DayTableSlicer() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.forceDayIfListItem = true; + return _this; + } + DayTableSlicer.prototype.sliceRange = function (dateRange, dayTableModel) { + return dayTableModel.sliceRange(dateRange); + }; + return DayTableSlicer; + }(Slicer)); + + var DayTable = /** @class */ (function (_super) { + __extends(DayTable, _super); + function DayTable() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.slicer = new DayTableSlicer(); + _this.tableRef = createRef(); + return _this; + } + DayTable.prototype.render = function () { + var _a = this, props = _a.props, context = _a.context; + return (createElement(Table, __assign({ ref: this.tableRef }, this.slicer.sliceProps(props, props.dateProfile, props.nextDayThreshold, context, props.dayTableModel), { dateProfile: props.dateProfile, cells: props.dayTableModel.cells, colGroupNode: props.colGroupNode, tableMinWidth: props.tableMinWidth, renderRowIntro: props.renderRowIntro, dayMaxEvents: props.dayMaxEvents, dayMaxEventRows: props.dayMaxEventRows, showWeekNumbers: props.showWeekNumbers, expandRows: props.expandRows, headerAlignElRef: props.headerAlignElRef, clientWidth: props.clientWidth, clientHeight: props.clientHeight, forPrint: props.forPrint }))); + }; + return DayTable; + }(DateComponent)); + + var DayTableView = /** @class */ (function (_super) { + __extends(DayTableView, _super); + function DayTableView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.buildDayTableModel = memoize(buildDayTableModel); + _this.headerRef = createRef(); + _this.tableRef = createRef(); + return _this; + } + DayTableView.prototype.render = function () { + var _this = this; + var _a = this.context, options = _a.options, dateProfileGenerator = _a.dateProfileGenerator; + var props = this.props; + var dayTableModel = this.buildDayTableModel(props.dateProfile, dateProfileGenerator); + var headerContent = options.dayHeaders && (createElement(DayHeader, { ref: this.headerRef, dateProfile: props.dateProfile, dates: dayTableModel.headerDates, datesRepDistinctDays: dayTableModel.rowCnt === 1 })); + var bodyContent = function (contentArg) { return (createElement(DayTable, { ref: _this.tableRef, dateProfile: props.dateProfile, dayTableModel: dayTableModel, businessHours: props.businessHours, dateSelection: props.dateSelection, eventStore: props.eventStore, eventUiBases: props.eventUiBases, eventSelection: props.eventSelection, eventDrag: props.eventDrag, eventResize: props.eventResize, nextDayThreshold: options.nextDayThreshold, colGroupNode: contentArg.tableColGroupNode, tableMinWidth: contentArg.tableMinWidth, dayMaxEvents: options.dayMaxEvents, dayMaxEventRows: options.dayMaxEventRows, showWeekNumbers: options.weekNumbers, expandRows: !props.isHeightAuto, headerAlignElRef: _this.headerElRef, clientWidth: contentArg.clientWidth, clientHeight: contentArg.clientHeight, forPrint: props.forPrint })); }; + return options.dayMinWidth + ? this.renderHScrollLayout(headerContent, bodyContent, dayTableModel.colCnt, options.dayMinWidth) + : this.renderSimpleLayout(headerContent, bodyContent); + }; + return DayTableView; + }(TableView)); + function buildDayTableModel(dateProfile, dateProfileGenerator) { + var daySeries = new DaySeriesModel(dateProfile.renderRange, dateProfileGenerator); + return new DayTableModel(daySeries, /year|month|week/.test(dateProfile.currentRangeUnit)); + } + + var TableDateProfileGenerator = /** @class */ (function (_super) { + __extends(TableDateProfileGenerator, _super); + function TableDateProfileGenerator() { + return _super !== null && _super.apply(this, arguments) || this; + } + // Computes the date range that will be rendered. + TableDateProfileGenerator.prototype.buildRenderRange = function (currentRange, currentRangeUnit, isRangeAllDay) { + var dateEnv = this.props.dateEnv; + var renderRange = _super.prototype.buildRenderRange.call(this, currentRange, currentRangeUnit, isRangeAllDay); + var start = renderRange.start; + var end = renderRange.end; + var endOfWeek; + // year and month views should be aligned with weeks. this is already done for week + if (/^(year|month)$/.test(currentRangeUnit)) { + start = dateEnv.startOfWeek(start); + // make end-of-week if not already + endOfWeek = dateEnv.startOfWeek(end); + if (endOfWeek.valueOf() !== end.valueOf()) { + end = addWeeks(endOfWeek, 1); + } + } + // ensure 6 weeks + if (this.props.monthMode && + this.props.fixedWeekCount) { + var rowCnt = Math.ceil(// could be partial weeks due to hiddenDays + diffWeeks(start, end)); + end = addWeeks(end, 6 - rowCnt); + } + return { start: start, end: end }; + }; + return TableDateProfileGenerator; + }(DateProfileGenerator)); + + var dayGridPlugin = createPlugin({ + initialView: 'dayGridMonth', + views: { + dayGrid: { + component: DayTableView, + dateProfileGeneratorClass: TableDateProfileGenerator, + }, + dayGridDay: { + type: 'dayGrid', + duration: { days: 1 }, + }, + dayGridWeek: { + type: 'dayGrid', + duration: { weeks: 1 }, + }, + dayGridMonth: { + type: 'dayGrid', + duration: { months: 1 }, + monthMode: true, + fixedWeekCount: true, + }, + }, + }); + + var AllDaySplitter = /** @class */ (function (_super) { + __extends(AllDaySplitter, _super); + function AllDaySplitter() { + return _super !== null && _super.apply(this, arguments) || this; + } + AllDaySplitter.prototype.getKeyInfo = function () { + return { + allDay: {}, + timed: {}, + }; + }; + AllDaySplitter.prototype.getKeysForDateSpan = function (dateSpan) { + if (dateSpan.allDay) { + return ['allDay']; + } + return ['timed']; + }; + AllDaySplitter.prototype.getKeysForEventDef = function (eventDef) { + if (!eventDef.allDay) { + return ['timed']; + } + if (hasBgRendering(eventDef)) { + return ['timed', 'allDay']; + } + return ['allDay']; + }; + return AllDaySplitter; + }(Splitter)); + + var DEFAULT_SLAT_LABEL_FORMAT = createFormatter({ + hour: 'numeric', + minute: '2-digit', + omitZeroMinute: true, + meridiem: 'short', + }); + function TimeColsAxisCell(props) { + var classNames = [ + 'fc-timegrid-slot', + 'fc-timegrid-slot-label', + props.isLabeled ? 'fc-scrollgrid-shrink' : 'fc-timegrid-slot-minor', + ]; + return (createElement(ViewContextType.Consumer, null, function (context) { + if (!props.isLabeled) { + return (createElement("td", { className: classNames.join(' '), "data-time": props.isoTimeStr })); + } + var dateEnv = context.dateEnv, options = context.options, viewApi = context.viewApi; + var labelFormat = // TODO: fully pre-parse + options.slotLabelFormat == null ? DEFAULT_SLAT_LABEL_FORMAT : + Array.isArray(options.slotLabelFormat) ? createFormatter(options.slotLabelFormat[0]) : + createFormatter(options.slotLabelFormat); + var hookProps = { + level: 0, + time: props.time, + date: dateEnv.toDate(props.date), + view: viewApi, + text: dateEnv.format(props.date, labelFormat), + }; + return (createElement(RenderHook, { hookProps: hookProps, classNames: options.slotLabelClassNames, content: options.slotLabelContent, defaultContent: renderInnerContent$1, didMount: options.slotLabelDidMount, willUnmount: options.slotLabelWillUnmount }, function (rootElRef, customClassNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: classNames.concat(customClassNames).join(' '), "data-time": props.isoTimeStr }, + createElement("div", { className: "fc-timegrid-slot-label-frame fc-scrollgrid-shrink-frame" }, + createElement("div", { className: "fc-timegrid-slot-label-cushion fc-scrollgrid-shrink-cushion", ref: innerElRef }, innerContent)))); })); + })); + } + function renderInnerContent$1(props) { + return props.text; + } + + var TimeBodyAxis = /** @class */ (function (_super) { + __extends(TimeBodyAxis, _super); + function TimeBodyAxis() { + return _super !== null && _super.apply(this, arguments) || this; + } + TimeBodyAxis.prototype.render = function () { + return this.props.slatMetas.map(function (slatMeta) { return (createElement("tr", { key: slatMeta.key }, + createElement(TimeColsAxisCell, __assign({}, slatMeta)))); }); + }; + return TimeBodyAxis; + }(BaseComponent)); + + var DEFAULT_WEEK_NUM_FORMAT = createFormatter({ week: 'short' }); + var AUTO_ALL_DAY_MAX_EVENT_ROWS = 5; + var TimeColsView = /** @class */ (function (_super) { + __extends(TimeColsView, _super); + function TimeColsView() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.allDaySplitter = new AllDaySplitter(); // for use by subclasses + _this.headerElRef = createRef(); + _this.rootElRef = createRef(); + _this.scrollerElRef = createRef(); + _this.state = { + slatCoords: null, + }; + _this.handleScrollTopRequest = function (scrollTop) { + var scrollerEl = _this.scrollerElRef.current; + if (scrollerEl) { // TODO: not sure how this could ever be null. weirdness with the reducer + scrollerEl.scrollTop = scrollTop; + } + }; + /* Header Render Methods + ------------------------------------------------------------------------------------------------------------------*/ + _this.renderHeadAxis = function (rowKey, frameHeight) { + if (frameHeight === void 0) { frameHeight = ''; } + var options = _this.context.options; + var dateProfile = _this.props.dateProfile; + var range = dateProfile.renderRange; + var dayCnt = diffDays(range.start, range.end); + var navLinkAttrs = (options.navLinks && dayCnt === 1) // only do in day views (to avoid doing in week views that dont need it) + ? { 'data-navlink': buildNavLinkData(range.start, 'week'), tabIndex: 0 } + : {}; + if (options.weekNumbers && rowKey === 'day') { + return (createElement(WeekNumberRoot, { date: range.start, defaultFormat: DEFAULT_WEEK_NUM_FORMAT }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("th", { ref: rootElRef, className: [ + 'fc-timegrid-axis', + 'fc-scrollgrid-shrink', + ].concat(classNames).join(' ') }, + createElement("div", { className: "fc-timegrid-axis-frame fc-scrollgrid-shrink-frame fc-timegrid-axis-frame-liquid", style: { height: frameHeight } }, + createElement("a", __assign({ ref: innerElRef, className: "fc-timegrid-axis-cushion fc-scrollgrid-shrink-cushion fc-scrollgrid-sync-inner" }, navLinkAttrs), innerContent)))); })); + } + return (createElement("th", { className: "fc-timegrid-axis" }, + createElement("div", { className: "fc-timegrid-axis-frame", style: { height: frameHeight } }))); + }; + /* Table Component Render Methods + ------------------------------------------------------------------------------------------------------------------*/ + // only a one-way height sync. we don't send the axis inner-content height to the DayGrid, + // but DayGrid still needs to have classNames on inner elements in order to measure. + _this.renderTableRowAxis = function (rowHeight) { + var _a = _this.context, options = _a.options, viewApi = _a.viewApi; + var hookProps = { + text: options.allDayText, + view: viewApi, + }; + return ( + // TODO: make reusable hook. used in list view too + createElement(RenderHook, { hookProps: hookProps, classNames: options.allDayClassNames, content: options.allDayContent, defaultContent: renderAllDayInner$1, didMount: options.allDayDidMount, willUnmount: options.allDayWillUnmount }, function (rootElRef, classNames, innerElRef, innerContent) { return (createElement("td", { ref: rootElRef, className: [ + 'fc-timegrid-axis', + 'fc-scrollgrid-shrink', + ].concat(classNames).join(' ') }, + createElement("div", { className: 'fc-timegrid-axis-frame fc-scrollgrid-shrink-frame' + (rowHeight == null ? ' fc-timegrid-axis-frame-liquid' : ''), style: { height: rowHeight } }, + createElement("span", { className: "fc-timegrid-axis-cushion fc-scrollgrid-shrink-cushion fc-scrollgrid-sync-inner", ref: innerElRef }, innerContent)))); })); + }; + _this.handleSlatCoords = function (slatCoords) { + _this.setState({ slatCoords: slatCoords }); + }; + return _this; + } + // rendering + // ---------------------------------------------------------------------------------------------------- + TimeColsView.prototype.renderSimpleLayout = function (headerRowContent, allDayContent, timeContent) { + var _a = this, context = _a.context, props = _a.props; + var sections = []; + var stickyHeaderDates = getStickyHeaderDates(context.options); + if (headerRowContent) { + sections.push({ + type: 'header', + key: 'header', + isSticky: stickyHeaderDates, + chunk: { + elRef: this.headerElRef, + tableClassName: 'fc-col-header', + rowContent: headerRowContent, + }, + }); + } + if (allDayContent) { + sections.push({ + type: 'body', + key: 'all-day', + chunk: { content: allDayContent }, + }); + sections.push({ + type: 'body', + key: 'all-day-divider', + outerContent: ( // TODO: rename to cellContent so don't need to define
+ + +
+
+
+
+
+
+ +
+
+ + + From f8bef1781201d8868de10fbf5d58dc55959f2155 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Sun, 31 Oct 2021 22:17:27 +0800 Subject: [PATCH 0455/1062] Create qr_packed.js --- apps/authentiwatch/qr_packed.js | 107 ++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 apps/authentiwatch/qr_packed.js diff --git a/apps/authentiwatch/qr_packed.js b/apps/authentiwatch/qr_packed.js new file mode 100644 index 000000000..28b1bddd0 --- /dev/null +++ b/apps/authentiwatch/qr_packed.js @@ -0,0 +1,107 @@ +/* Packed with Google Closure +* +* Ported to JavaScript by Lazar Laszlo 2011 +* lazarsoft@gmail.com, www.lazarsoft.info +* +* Copyright 2007 ZXing authors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +var qrcode=function(){"use strict";function a(h,b){this.count=h;this.dataCodewords=b;this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("DataCodewords",function(){return this.dataCodewords})}function f(h,b,e){this.ecCodewordsPerBlock=h;this.ecBlocks=e?[b,e]:Array(b);this.__defineGetter__("ECCodewordsPerBlock",function(){return this.ecCodewordsPerBlock});this.__defineGetter__("TotalECCodewords",function(){return this.ecCodewordsPerBlock*this.NumBlocks});this.__defineGetter__("NumBlocks", +function(){for(var d=0,c=0;cMath.abs(d-b);if(h){var a=b;b=e;e=a;a=d;d=c;c=a}for(var m=Math.abs(d-b),f=Math.abs(c-e),q=-m>>1,k=ed?(h=b/(b-d),d=0):d>=g.width&&(h=(g.width-1-b)/(d-b),d=g.width-1);c=Math.floor(e-(c-e)*h);h=1;0>c?(h=e/(e-c),c=0):c>=g.height&&(h=(g.height-1-e)/(c-e),c=g.height-1);d=Math.floor(b+(d-b)*h);a+=this.sizeOfBlackWhiteBlackRun(b,e,d,c);return a-1};this.calculateModuleSizeOneWay=function(b,e){var d=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(b.X), +Math.floor(b.Y),Math.floor(e.X),Math.floor(e.Y)),c=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(e.X),Math.floor(e.Y),Math.floor(b.X),Math.floor(b.Y));return isNaN(d)?c/7:isNaN(c)?d/7:(d+c)/14};this.calculateModuleSize=function(b,e,d){return(this.calculateModuleSizeOneWay(b,e)+this.calculateModuleSizeOneWay(b,d))/2};this.distance=function(b,e){var d=b.X-e.X,c=b.Y-e.Y;return Math.sqrt(d*d+c*c)};this.computeDimension=function(b,e,d,c){e=Math.round(this.distance(b,e)/c);b=Math.round(this.distance(b, +d)/c);b=(e+b>>1)+7;switch(b&3){case 0:b++;break;case 2:b--;break;case 3:throw"Error";}return b};this.findAlignmentInRegion=function(b,e,d,c){c=Math.floor(c*b);var h=Math.max(0,e-c);e=Math.min(g.width-1,e+c);if(e-h<3*b)throw"Error";var a=Math.max(0,d-c);return(new R(this.image,h,a,e-h,Math.min(g.height-1,d+c)-a,b,this.resultPointCallback)).find()};this.createTransform=function(b,e,d,c,h){h-=3.5;var a;if(null!=c){var p=c.X;c=c.Y;var f=a=h-3}else p=e.X-b.X+d.X,c=e.Y-b.Y+d.Y,f=a=h;return z.quadrilateralToQuadrilateral(3.5, +3.5,h,3.5,f,a,3.5,h,b.X,b.Y,e.X,e.Y,p,c,d.X,d.Y)};this.sampleGrid=function(b,e,d){return F.sampleGrid3(b,d,e)};this.processFinderPatternInfo=function(b){var e=b.TopLeft,d=b.TopRight;b=b.BottomLeft;var c=this.calculateModuleSize(e,d,b);if(1>c)throw"Error";var h=this.computeDimension(e,d,b,c),a=k.getProvisionalVersionForDimension(h),m=a.DimensionForVersion-7,f=null;if(0>3&3);this.dataMask=h&7;this.__defineGetter__("ErrorCorrectionLevel",function(){return this.errorCorrectionLevel});this.__defineGetter__("DataMask",function(){return this.dataMask});this.GetHashCode=function(){return this.errorCorrectionLevel.ordinal()<< +3|this.dataMask};this.Equals=function(b){return this.errorCorrectionLevel==b.errorCorrectionLevel&&this.dataMask==b.dataMask}}function C(h,b,e){this.ordinal_Renamed_Field=h;this.bits=b;this.name=e;this.__defineGetter__("Bits",function(){return this.bits});this.__defineGetter__("Name",function(){return this.name});this.ordinal=function(){return this.ordinal_Renamed_Field}}function I(h,b){b||(b=h);if(1>h||1>b)throw"Both dimensions must be greater than 0";this.width=h;this.height=b;var e=h>>5;0!=(h& +31)&&e++;this.rowSize=e;this.bits=Array(e*b);for(e=0;e>5)],d&31)&1)};this.set_Renamed=function(d,c){this.bits[c*this.rowSize+ +(d>>5)]|=1<<(d&31)};this.flip=function(d,c){this.bits[c*this.rowSize+(d>>5)]^=1<<(d&31)};this.clear=function(){for(var d=this.bits.length,c=0;cc||0>d)throw"Left and top must be nonnegative";if(1>b||1>e)throw"Height and width must be at least 1";e=d+e;b=c+b;if(b>this.height||e>this.width)throw"The region must fit inside the matrix";for(;c>5)]|=1<<(a&31)}}function G(a,b){this.numDataCodewords= +a;this.codewords=b;this.__defineGetter__("NumDataCodewords",function(){return this.numDataCodewords});this.__defineGetter__("Codewords",function(){return this.codewords})}function T(a){var b=a.Dimension;if(21>b||1!=(b&3))throw"Error BitMatrixParser";this.bitMatrix=a;this.parsedFormatInfo=this.parsedVersion=null;this.copyBit=function(e,d,c){return this.bitMatrix.get_Renamed(e,d)?c<<1|1:c<<1};this.readFormatInformation=function(){if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;for(var e= +0,d=0;6>d;d++)e=this.copyBit(d,8,e);e=this.copyBit(7,8,e);e=this.copyBit(8,8,e);e=this.copyBit(8,7,e);for(d=5;0<=d;d--)e=this.copyBit(8,d,e);this.parsedFormatInfo=r.decodeFormatInformation(e);if(null!=this.parsedFormatInfo)return this.parsedFormatInfo;for(var c=this.bitMatrix.Dimension,e=0,b=c-8,d=c-1;d>=b;d--)e=this.copyBit(d,8,e);for(d=c-7;d>2;if(6>=d)return k.getVersionForNumber(d);for(var d=0,c=e-11,b=5;0<=b;b--)for(var a=e-9;a>=c;a--)d=this.copyBit(a,b,d);this.parsedVersion=k.decodeVersionInformation(d);if(null!=this.parsedVersion&&this.parsedVersion.DimensionForVersion==e)return this.parsedVersion;d=0;for(a=5;0<=a;a--)for(b=e-9;b>=c;b--)d=this.copyBit(a,b,d);this.parsedVersion=k.decodeVersionInformation(d);if(null!= +this.parsedVersion&&this.parsedVersion.DimensionForVersion==e)return this.parsedVersion;throw"Error readVersion";};this.readCodewords=function(){var b=this.readFormatInformation(),d=this.readVersion(),c=H.forReference(b.DataMask),b=this.bitMatrix.Dimension;c.unmaskBitMatrix(this.bitMatrix,b);for(var c=d.buildFunctionPattern(),a=!0,h=Array(d.TotalCodewords),m=0,f=0,g=0,k=b-1;0t;t++)c.get_Renamed(k-t,v)||(g++,f<<=1,this.bitMatrix.get_Renamed(k- +t,v)&&(f|=1),8==g&&(h[m++]=f,f=g=0));a^=1}if(m!=d.TotalCodewords)throw"Error readCodewords";return h}}function w(a,b){if(null==b||0==b.length)throw"System.ArgumentException";this.field=a;var e=b.length;if(1c.length){var b=d,d=c;c=b}for(var b=Array(c.length),e=c.length-d.length,h=0;hc)throw"System.ArgumentException";if(0==d)return this.field.Zero;for(var b=this.coefficients.length,e=Array(b+c),a=0;a=c.Degree&&!b.Zero;)var a=b.Degree-c.Degree, +h=this.field.multiply(b.getCoefficient(b.Degree),e),f=c.multiplyByMonomial(a,h),a=this.field.buildMonomial(a,h),d=d.addOrSubtract(a),b=b.addOrSubtract(f);return[d,b]}}function n(a){this.expTable=Array(256);this.logTable=Array(256);for(var b=1,e=0;256>e;e++)this.expTable[e]=b,b<<=1,256<=b&&(b^=a);for(e=0;255>e;e++)this.logTable[this.expTable[e]]=e;a=Array(1);a[0]=0;this.zero=new w(this,Array(a));a=Array(1);a[0]=1;this.one=new w(this,Array(a));this.__defineGetter__("Zero",function(){return this.zero}); +this.__defineGetter__("One",function(){return this.one});this.buildMonomial=function(d,c){if(0>d)throw"System.ArgumentException";if(0==c)return this.zero;for(var b=Array(d+1),e=0;e>b:(a>>b)+(2<<~b)}function U(a,b,e){this.x=a;this.y=b;this.count=1;this.estimatedModuleSize=e;this.__defineGetter__("EstimatedModuleSize",function(){return this.estimatedModuleSize});this.__defineGetter__("Count",function(){return this.count});this.__defineGetter__("X",function(){return this.x});this.__defineGetter__("Y",function(){return this.y});this.incrementCount=function(){this.count++}; +this.aboutEquals=function(d,c,b){return Math.abs(c-this.y)<=d&&Math.abs(b-this.x)<=d?(d=Math.abs(d-this.estimatedModuleSize),1>=d||1>=d/this.estimatedModuleSize):!1}}function V(a){this.bottomLeft=a[0];this.topLeft=a[1];this.topRight=a[2];this.__defineGetter__("BottomLeft",function(){return this.bottomLeft});this.__defineGetter__("TopLeft",function(){return this.topLeft});this.__defineGetter__("TopRight",function(){return this.topRight})}function S(){this.image=null;this.possibleCenters=[];this.hasSkipped= +!1;this.crossCheckStateCount=[0,0,0,0,0];this.resultPointCallback=null;this.__defineGetter__("CrossCheckStateCount",function(){this.crossCheckStateCount[0]=0;this.crossCheckStateCount[1]=0;this.crossCheckStateCount[2]=0;this.crossCheckStateCount[3]=0;this.crossCheckStateCount[4]=0;return this.crossCheckStateCount});this.foundPatternCross=function(a){for(var b=0,e=0;5>e;e++){var d=a[e];if(0==d)return!1;b+=d}if(7>b)return!1;b=Math.floor((b<m)return NaN;for(;0<=m&&!c[b+m*g.width]&&l[1]<=e;)l[1]++,m--;if(0>m||l[1]>e)return NaN;for(;0<=m&&c[b+m*g.width]&&l[0]<=e;)l[0]++,m--;if(l[0]>e)return NaN;for(m=a+1;m=e)return NaN;for(;m=e||5*Math.abs(l[0]+l[1]+l[2]+l[3]+l[4]-d)>=2*d?NaN:this.foundPatternCross(l)?this.centerFromEnd(l,m):NaN};this.crossCheckHorizontal=function(a,b,e,d){for(var c=this.image,h=g.width,l=this.CrossCheckStateCount,m=a;0<=m&&c[m+b*g.width];)l[2]++,m--;if(0>m)return NaN;for(;0<=m&&!c[m+b*g.width]&&l[1]<=e;)l[1]++,m--;if(0>m||l[1]>e)return NaN;for(;0<=m&&c[m+b*g.width]&&l[0]<= +e;)l[0]++,m--;if(l[0]>e)return NaN;for(m=a+1;m=e)return NaN;for(;m=e||5*Math.abs(l[0]+l[1]+l[2]+l[3]+l[4]-d)>=d?NaN:this.foundPatternCross(l)?this.centerFromEnd(l,m):NaN};this.handlePossibleCenter=function(a,b,e){var d=a[0]+a[1]+a[2]+a[3]+a[4];e=this.centerFromEnd(a,e);b=this.crossCheckVertical(b,Math.floor(e),a[2],d);if(!isNaN(b)&&(e=this.crossCheckHorizontal(Math.floor(e), +Math.floor(b),a[2],d),!isNaN(e))){a=d/7;for(var d=!1,c=this.possibleCenters.length,h=0;ha)throw"Couldn't find enough finder patterns (found "+a+")";if(3a&&this.possibleCenters.splice(d,1)}3d.count?-1:c.count=a)return 0;for(var b=null,e=0;e=K)if(null==b)b=d;else return this.hasSkipped=!0,Math.floor((Math.abs(b.X-d.X)-Math.abs(b.Y-d.Y))/2)}return 0};this.haveMultiplyConfirmedCenters=function(){for(var a,b=0,e=0,d=this.possibleCenters.length,c=0;c=K&&(b++,e+=a.EstimatedModuleSize); +if(3>b)return!1;for(var b=e/d,p=0,c=0;cl[2]&&(m+=b-l[2]-c,f=d-1));else{do f++;while(f=d||1>=d/this.estimatedModuleSize):!1}}function R(a,b,e,d,c,f,l){this.image=a;this.possibleCenters= +[];this.startX=b;this.startY=e;this.width=d;this.height=c;this.moduleSize=f;this.crossCheckStateCount=[0,0,0];this.resultPointCallback=l;this.centerFromEnd=function(c,d){return d-c[2]-c[1]/2};this.foundPatternCross=function(c){for(var d=this.moduleSize,b=d/2,a=0;3>a;a++)if(Math.abs(d-c[a])>=b)return!1;return!0};this.crossCheckVertical=function(c,d,b,a){var e=this.image,h=g.height,f=this.crossCheckStateCount;f[0]=0;f[1]=0;f[2]=0;for(var l=c;0<=l&&e[d+l*g.width]&&f[1]<=b;)f[1]++,l--;if(0>l||f[1]>b)return NaN; +for(;0<=l&&!e[d+l*g.width]&&f[0]<=b;)f[0]++,l--;if(f[0]>b)return NaN;for(l=c+1;lb)return NaN;for(;lb||5*Math.abs(f[0]+f[1]+f[2]-a)>=2*a?NaN:this.foundPatternCross(f)?this.centerFromEnd(f,l):NaN};this.handlePossibleCenter=function(c,d,b){var a=c[0]+c[1]+c[2];b=this.centerFromEnd(c,b);d=this.crossCheckVertical(d,Math.floor(b),2*c[1],a);if(!isNaN(d)){c=(c[0]+c[1]+c[2])/3;for(var a=this.possibleCenters.length, +e=0;e>1),p=[0,0,0],k=0;k>1:-(k+1>>1));p[0]=0;p[1]=0;p[2]=0;for(var A=b;A=b?this.dataLengthMode=0:10<=b&&26>=b?this.dataLengthMode=1:27<= +b&&40>=b&&(this.dataLengthMode=2);this.getNextBits=function(b){var c,d;if(b>this.bitPointer-b+1;this.bitPointer-=b;return d}if(b>8-(b-(this.bitPointer+1));this.bitPointer-=b%8;0>this.bitPointer&&(this.bitPointer= +8+this.bitPointer);return d}if(b>8-(b-(this.bitPointer+1+8));this.bitPointer-=(b-8)%8;0>this.bitPointer&&(this.bitPointer=8+this.bitPointer);return c+e+d}return 0}; +this.NextMode=function(){return this.blockPointer>this.blocks.length-this.numErrorCorrectionCode-2?0:this.getNextBits(4)};this.getDataLength=function(b){for(var c=0;1!=b>>c;)c++;return this.getNextBits(g.sizeOfDataLengthInfo[this.dataLengthMode][c])};this.getRomanAndFigureString=function(b){var c="",d="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".split("");do if(1c&&(d+="0"),10>c&&(d+="0"),b-=3):2==b?(c=this.getNextBits(7),10>c&&(d+="0"),b-=2):1==b&&(c=this.getNextBits(4),--b),d+=c;while(0=d+33088?d+33088:d+49472);b--}while(0< +b);return c};this.parseECIValue=function(){var b=0,c=this.getNextBits(8);0==(c&128)&&(b=c&127);128==(c&192)&&(b=this.getNextBits(8),b|=(c&63)<<8);192==(c&224)&&(b=this.getNextBits(8),b|=(c&31)<<16);return b};this.__defineGetter__("DataByte",function(){var b=[];do{var c=this.NextMode();if(0==c)if(0a)throw"Invalid data length: "+a;switch(c){case 1:c=this.getFigureString(a);for(var a=Array(c.length),e=0;ed||d>c||-1>e||e>h)throw"Error.checkAndNudgePoints ";f=!1;-1==d?(b[m]=0,f=!0):d==c&&(b[m]=c-1,f=!0);-1==e?(b[m+1]=0,f=!0):e==h&&(b[m+1]=h-1,f=!0)}f=!0;for(m=b.length-2;0<=m&&f;m-=2){d=Math.floor(b[m]);e=Math.floor(b[m+1]);if(-1>d||d>c||-1>e||e>h)throw"Error.checkAndNudgePoints ";f=!1;-1==d?(b[m]=0,f=!0):d==c&&(b[m]=c-1,f=!0);-1==e?(b[m+1]=0,f=!0):e==h&&(b[m+1]=h-1,f=!0)}},sampleGrid3:function(a,b,e){for(var d=new I(b),c=Array(b<<1),h=0;h>1)+.5,c[k+1]=m;e.transformPoints1(c);F.checkAndNudgePoints(a,c);try{for(k=0;k>1,h)}catch(q){throw"Error.checkAndNudgePoints";}}return d},sampleGridx:function(a,b,e,d,c,f,l,g,k,q,n,x,v,t,r,A,u,w){e=z.quadrilateralToQuadrilateral(e,d,c,f,l,g,k,q,n,x,v,t,r,A,u,w);return F.sampleGrid3(a,b,e)}};k.VERSION_DECODE_INFO=[31892,34236,39577,42195,48118,51042,55367,58893,63784,68472,70749,76311,79154, +84390,87683,92361,96236,102084,102881,110507,110734,117786,119615,126325,127568,133589,136944,141498,145311,150283,152622,158308,161089,167017];k.VERSIONS=[new k(1,[],new f(7,new a(1,19)),new f(10,new a(1,16)),new f(13,new a(1,13)),new f(17,new a(1,9))),new k(2,[6,18],new f(10,new a(1,34)),new f(16,new a(1,28)),new f(22,new a(1,22)),new f(28,new a(1,16))),new k(3,[6,22],new f(15,new a(1,55)),new f(26,new a(1,44)),new f(18,new a(2,17)),new f(22,new a(2,13))),new k(4,[6,26],new f(20,new a(1,80)),new f(18, +new a(2,32)),new f(26,new a(2,24)),new f(16,new a(4,9))),new k(5,[6,30],new f(26,new a(1,108)),new f(24,new a(2,43)),new f(18,new a(2,15),new a(2,16)),new f(22,new a(2,11),new a(2,12))),new k(6,[6,34],new f(18,new a(2,68)),new f(16,new a(4,27)),new f(24,new a(4,19)),new f(28,new a(4,15))),new k(7,[6,22,38],new f(20,new a(2,78)),new f(18,new a(4,31)),new f(18,new a(2,14),new a(4,15)),new f(26,new a(4,13),new a(1,14))),new k(8,[6,24,42],new f(24,new a(2,97)),new f(22,new a(2,38),new a(2,39)),new f(22, +new a(4,18),new a(2,19)),new f(26,new a(4,14),new a(2,15))),new k(9,[6,26,46],new f(30,new a(2,116)),new f(22,new a(3,36),new a(2,37)),new f(20,new a(4,16),new a(4,17)),new f(24,new a(4,12),new a(4,13))),new k(10,[6,28,50],new f(18,new a(2,68),new a(2,69)),new f(26,new a(4,43),new a(1,44)),new f(24,new a(6,19),new a(2,20)),new f(28,new a(6,15),new a(2,16))),new k(11,[6,30,54],new f(20,new a(4,81)),new f(30,new a(1,50),new a(4,51)),new f(28,new a(4,22),new a(4,23)),new f(24,new a(3,12),new a(8,13))), +new k(12,[6,32,58],new f(24,new a(2,92),new a(2,93)),new f(22,new a(6,36),new a(2,37)),new f(26,new a(4,20),new a(6,21)),new f(28,new a(7,14),new a(4,15))),new k(13,[6,34,62],new f(26,new a(4,107)),new f(22,new a(8,37),new a(1,38)),new f(24,new a(8,20),new a(4,21)),new f(22,new a(12,11),new a(4,12))),new k(14,[6,26,46,66],new f(30,new a(3,115),new a(1,116)),new f(24,new a(4,40),new a(5,41)),new f(20,new a(11,16),new a(5,17)),new f(24,new a(11,12),new a(5,13))),new k(15,[6,26,48,70],new f(22,new a(5, +87),new a(1,88)),new f(24,new a(5,41),new a(5,42)),new f(30,new a(5,24),new a(7,25)),new f(24,new a(11,12),new a(7,13))),new k(16,[6,26,50,74],new f(24,new a(5,98),new a(1,99)),new f(28,new a(7,45),new a(3,46)),new f(24,new a(15,19),new a(2,20)),new f(30,new a(3,15),new a(13,16))),new k(17,[6,30,54,78],new f(28,new a(1,107),new a(5,108)),new f(28,new a(10,46),new a(1,47)),new f(28,new a(1,22),new a(15,23)),new f(28,new a(2,14),new a(17,15))),new k(18,[6,30,56,82],new f(30,new a(5,120),new a(1,121)), +new f(26,new a(9,43),new a(4,44)),new f(28,new a(17,22),new a(1,23)),new f(28,new a(2,14),new a(19,15))),new k(19,[6,30,58,86],new f(28,new a(3,113),new a(4,114)),new f(26,new a(3,44),new a(11,45)),new f(26,new a(17,21),new a(4,22)),new f(26,new a(9,13),new a(16,14))),new k(20,[6,34,62,90],new f(28,new a(3,107),new a(5,108)),new f(26,new a(3,41),new a(13,42)),new f(30,new a(15,24),new a(5,25)),new f(28,new a(15,15),new a(10,16))),new k(21,[6,28,50,72,94],new f(28,new a(4,116),new a(4,117)),new f(26, +new a(17,42)),new f(28,new a(17,22),new a(6,23)),new f(30,new a(19,16),new a(6,17))),new k(22,[6,26,50,74,98],new f(28,new a(2,111),new a(7,112)),new f(28,new a(17,46)),new f(30,new a(7,24),new a(16,25)),new f(24,new a(34,13))),new k(23,[6,30,54,74,102],new f(30,new a(4,121),new a(5,122)),new f(28,new a(4,47),new a(14,48)),new f(30,new a(11,24),new a(14,25)),new f(30,new a(16,15),new a(14,16))),new k(24,[6,28,54,80,106],new f(30,new a(6,117),new a(4,118)),new f(28,new a(6,45),new a(14,46)),new f(30, +new a(11,24),new a(16,25)),new f(30,new a(30,16),new a(2,17))),new k(25,[6,32,58,84,110],new f(26,new a(8,106),new a(4,107)),new f(28,new a(8,47),new a(13,48)),new f(30,new a(7,24),new a(22,25)),new f(30,new a(22,15),new a(13,16))),new k(26,[6,30,58,86,114],new f(28,new a(10,114),new a(2,115)),new f(28,new a(19,46),new a(4,47)),new f(28,new a(28,22),new a(6,23)),new f(30,new a(33,16),new a(4,17))),new k(27,[6,34,62,90,118],new f(30,new a(8,122),new a(4,123)),new f(28,new a(22,45),new a(3,46)),new f(30, +new a(8,23),new a(26,24)),new f(30,new a(12,15),new a(28,16))),new k(28,[6,26,50,74,98,122],new f(30,new a(3,117),new a(10,118)),new f(28,new a(3,45),new a(23,46)),new f(30,new a(4,24),new a(31,25)),new f(30,new a(11,15),new a(31,16))),new k(29,[6,30,54,78,102,126],new f(30,new a(7,116),new a(7,117)),new f(28,new a(21,45),new a(7,46)),new f(30,new a(1,23),new a(37,24)),new f(30,new a(19,15),new a(26,16))),new k(30,[6,26,52,78,104,130],new f(30,new a(5,115),new a(10,116)),new f(28,new a(19,47),new a(10, +48)),new f(30,new a(15,24),new a(25,25)),new f(30,new a(23,15),new a(25,16))),new k(31,[6,30,56,82,108,134],new f(30,new a(13,115),new a(3,116)),new f(28,new a(2,46),new a(29,47)),new f(30,new a(42,24),new a(1,25)),new f(30,new a(23,15),new a(28,16))),new k(32,[6,34,60,86,112,138],new f(30,new a(17,115)),new f(28,new a(10,46),new a(23,47)),new f(30,new a(10,24),new a(35,25)),new f(30,new a(19,15),new a(35,16))),new k(33,[6,30,58,86,114,142],new f(30,new a(17,115),new a(1,116)),new f(28,new a(14,46), +new a(21,47)),new f(30,new a(29,24),new a(19,25)),new f(30,new a(11,15),new a(46,16))),new k(34,[6,34,62,90,118,146],new f(30,new a(13,115),new a(6,116)),new f(28,new a(14,46),new a(23,47)),new f(30,new a(44,24),new a(7,25)),new f(30,new a(59,16),new a(1,17))),new k(35,[6,30,54,78,102,126,150],new f(30,new a(12,121),new a(7,122)),new f(28,new a(12,47),new a(26,48)),new f(30,new a(39,24),new a(14,25)),new f(30,new a(22,15),new a(41,16))),new k(36,[6,24,50,76,102,128,154],new f(30,new a(6,121),new a(14, +122)),new f(28,new a(6,47),new a(34,48)),new f(30,new a(46,24),new a(10,25)),new f(30,new a(2,15),new a(64,16))),new k(37,[6,28,54,80,106,132,158],new f(30,new a(17,122),new a(4,123)),new f(28,new a(29,46),new a(14,47)),new f(30,new a(49,24),new a(10,25)),new f(30,new a(24,15),new a(46,16))),new k(38,[6,32,58,84,110,136,162],new f(30,new a(4,122),new a(18,123)),new f(28,new a(13,46),new a(32,47)),new f(30,new a(48,24),new a(14,25)),new f(30,new a(42,15),new a(32,16))),new k(39,[6,26,54,82,110,138, +166],new f(30,new a(20,117),new a(4,118)),new f(28,new a(40,47),new a(7,48)),new f(30,new a(43,24),new a(22,25)),new f(30,new a(10,15),new a(67,16))),new k(40,[6,30,58,86,114,142,170],new f(30,new a(19,118),new a(6,119)),new f(28,new a(18,47),new a(31,48)),new f(30,new a(34,24),new a(34,25)),new f(30,new a(20,15),new a(61,16)))];k.getVersionForNumber=function(a){if(1>a||40>2)}catch(b){throw"Error getVersionForNumber";}};k.decodeVersionInformation=function(a){for(var b=4294967295,e=0,d=0;d=b?this.getVersionForNumber(e):null};z.quadrilateralToQuadrilateral=function(a,b,e,d,c,f,g,m,k,q,n,x,v,t,r,u){a=this.quadrilateralToSquare(a,b,e,d,c,f,g,m);return this.squareToQuadrilateral(k, +q,n,x,v,t,r,u).times(a)};z.squareToQuadrilateral=function(a,b,e,d,c,f,g,m){var h=m-f,l=b-d+f-m;if(0==h&&0==l)return new z(e-a,c-e,a,d-b,f-d,b,0,0,1);var p=e-c,k=g-c;c=a-e+c-g;f=d-f;var n=p*h-k*f,h=(c*h-k*l)/n,l=(p*l-c*f)/n;return new z(e-a+h*e,g-a+l*g,a,d-b+h*d,m-b+l*m,b,h,l,1)};z.quadrilateralToSquare=function(a,b,e,d,c,f,g,m){return this.squareToQuadrilateral(a,b,e,d,c,f,g,m).buildAdjoint()};var N=[[21522,0],[20773,1],[24188,2],[23371,3],[17913,4],[16590,5],[20375,6],[19104,7],[30660,8],[29427, +9],[32170,10],[30877,11],[26159,12],[25368,13],[27713,14],[26998,15],[5769,16],[5054,17],[7399,18],[6608,19],[1890,20],[597,21],[3340,22],[2107,23],[13663,24],[12392,25],[16177,26],[14854,27],[9396,28],[8579,29],[11994,30],[11245,31]],B=[0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4];r.numBitsDiffering=function(a,b){a^=b;return B[a&15]+B[u(a,4)&15]+B[u(a,8)&15]+B[u(a,12)&15]+B[u(a,16)&15]+B[u(a,20)&15]+B[u(a,24)&15]+B[u(a,28)&15]};r.decodeFormatInformation=function(a){var b=r.doDecodeFormatInformation(a);return null!= +b?b:r.doDecodeFormatInformation(a^21522)};r.doDecodeFormatInformation=function(a){for(var b=4294967295,e=0,d=0;d=b?new r(e):null};C.forBits=function(a){if(0>a||a>=O.length)throw"ArgumentException";return O[a]};var Y=new C(0,1,"L"),Z=new C(1,0,"M"),aa=new C(2,3,"Q"),ba=new C(3,2,"H"),O=[Z,Y,ba,aa];G.getDataBlocks=function(a,b,e){if(a.length!=b.TotalCodewords)throw"ArgumentException"; +var d=b.getECBlocksForLevel(e);e=0;var c=d.getECBlocks();for(b=0;ba||7h)throw"ReedSolomonException Bad error location";a[h]=n.addOrSubtract(a[h],c[f])}};this.runEuclideanAlgorithm=function(a,e,d){if(a.Degree=Math.floor(d/2);){var k=a,q=b,n=h;a=e;b=f;h=g;if(a.Zero)throw"r_{i-1} was zero";e=k;g=this.field.Zero;f=a.getCoefficient(a.Degree); +for(f=this.field.inverse(f);e.Degree>=a.Degree&&!e.Zero;){var k=e.Degree-a.Degree,r=this.field.multiply(e.getCoefficient(e.Degree),f),g=g.addOrSubtract(this.field.buildMonomial(k,r));e=e.addOrSubtract(a.multiplyByMonomial(k,r))}f=g.multiply1(b).addOrSubtract(q);g=g.multiply1(h).addOrSubtract(n)}d=g.getCoefficient(0);if(0==d)throw"ReedSolomonException sigmaTilde(0) was zero";d=this.field.inverse(d);a=g.multiply2(d);d=e.multiply2(d);return[a,d]};this.findErrorLocations=function(a){var b=a.Degree;if(1== +b)return Array(a.getCoefficient(1));for(var d=Array(b),c=0,f=1;256>f&&cg.maxImgSize&&(f=d.width/d.height,e=Math.sqrt(g.maxImgSize/f),f*=e);a.width=f;a.height=e;b.drawImage(d,0,0,a.width,a.height);g.width=a.width;g.height=a.height;try{g.imagedata= +b.getImageData(0,0,a.width,a.height)}catch(y){g.result=Error("Cross domain image reading not supported in your browser! Save it to your computer then drag and drop the file!");null!=g.callback&&g.callback(g.result);return}try{g.result=g.process(b)}catch(y){console.log(y),g.result=Error("error decoding QR Code")}null!=g.callback&&g.callback(g.result)};d.onerror=function(){null!=g.callback&&g.callback(Error("Failed to load the image"))};d.src=a},isUrl:function(a){return/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/.test(a)}, +decode_url:function(a){var b="";try{b=escape(a)}catch(e){console.log(e),b=a}a="";try{a=decodeURIComponent(b)}catch(e){console.log(e),a=b}return a},decode_utf8:function(a){return g.isUrl(a)?g.decode_url(a):a},process:function(a){var b=(new Date).getTime(),e=g.grayScaleToBitmap(g.grayscale());if(g.debug){for(var d=0;dc;c++){d[c]=Array(4);for(var f=0;4>f;f++)d[c][f]=[0,0]}for(c=0;4>c;c++)for(f= +0;4>f;f++){d[f][c][0]=255;for(var h=0;hd[f][c][1]&&(d[f][c][1]=k)}}a=Array(4);for(b=0;4>b;b++)a[b]=Array(4);for(c=0;4>c;c++)for(f=0;4>f;f++)a[f][c]=Math.floor((d[f][c][0]+d[f][c][1])/2);return a},grayScaleToBitmap:function(a){for(var b=g.getMiddleBrightnessPerArea(a),e=b.length,d=Math.floor(g.width/e),c=Math.floor(g.height/e),f=new ArrayBuffer(g.width*g.height),f=new Uint8Array(f),h=0;h=e&&d>=c?(d=a[0],e=a[1],c=a[2]):c>=d&&c>=e?(d=a[1], +e=a[0],c=a[2]):(d=a[2],e=a[0],c=a[1]);if(0>function(a,b,c){var d=b.x;b=b.y;return(c.x-d)*(a.y-b)-(c.y-b)*(a.x-d)}(e,d,c))var f=e,e=c,c=f;a[0]=e;a[1]=d;a[2]=c};return g}(); From c0b59266194c5de86a3a3f75bf22cce0eaba5933 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Sun, 31 Oct 2021 22:17:57 +0800 Subject: [PATCH 0456/1062] Create qrcode.min.js --- apps/authentiwatch/qrcode.min.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/authentiwatch/qrcode.min.js diff --git a/apps/authentiwatch/qrcode.min.js b/apps/authentiwatch/qrcode.min.js new file mode 100644 index 000000000..d5f3ca88b --- /dev/null +++ b/apps/authentiwatch/qrcode.min.js @@ -0,0 +1 @@ +var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); From 151b6390615dd878421f20cf3cafacf6cc3dc919 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Sun, 31 Oct 2021 22:20:59 +0800 Subject: [PATCH 0457/1062] Update apps.json Use conventional data filename. --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 7d6057df5..18fec8eea 100644 --- a/apps.json +++ b/apps.json @@ -4199,6 +4199,6 @@ {"name":"authentiwatch.app.js","url":"app.js"}, {"name":"authentiwatch.img","url":"app-icon.js","evaluate":true} ], - "data": [{"name":"authentiwatch.tokens.json"}] + "data": [{"name":"authentiwatch.json"}] } ] From 066eb28760e78458bf2938dbb034685a55de5727 Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Mon, 1 Nov 2021 10:07:45 +0800 Subject: [PATCH 0458/1062] Update interface.html Switch to Espruino Core copy of QRCode generator. --- apps/authentiwatch/interface.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/authentiwatch/interface.html b/apps/authentiwatch/interface.html index 255c5e4ca..201401a99 100644 --- a/apps/authentiwatch/interface.html +++ b/apps/authentiwatch/interface.html @@ -22,7 +22,7 @@ button{height:3em} - + + + + diff --git a/apps/health/lib.js b/apps/health/lib.js index 20d73a907..70305bff8 100644 --- a/apps/health/lib.js +++ b/apps/health/lib.js @@ -16,7 +16,6 @@ function getRecordIdx(d) { // Read all records from the given month exports.readAllRecords = function(d, cb) { - var rec = getRecordIdx(d); var fn = getRecordFN(d); var f = require("Storage").read(fn); if (f===undefined) return; diff --git a/core b/core index 70b49d8db..5ef454a1a 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 70b49d8dbd2afa76f4485aadf679dc75e0a8b4ac +Subproject commit 5ef454a1acce54f6420015b519a7ecf461f9bc37 From 47ba763a9d24cd59e0b57549a3d7bcefb54c7aa2 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 2 Nov 2021 20:06:27 +0000 Subject: [PATCH 0476/1062] recorder 0.02: Use 'recorder.log..' rather than 'record.log..' + Fix interface.html --- apps.json | 2 +- apps/recorder/ChangeLog | 2 + apps/recorder/app.js | 8 +- apps/recorder/interface.html | 272 ++++++++++++++++++++++------------- apps/recorder/widget.js | 2 +- 5 files changed, 183 insertions(+), 103 deletions(-) diff --git a/apps.json b/apps.json index 1a79e59f4..35fe0b49a 100644 --- a/apps.json +++ b/apps.json @@ -624,7 +624,7 @@ "id": "recorder", "name": "Recorder (BETA)", "shortName": "Recorder", - "version": "0.01", + "version": "0.02", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index 5560f00bc..bf90d0384 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -1 +1,3 @@ 0.01: New App! +0.02: Use 'recorder.log..' rather than 'record.log..' + Fix interface.html diff --git a/apps/recorder/app.js b/apps/recorder/app.js index 9b9c06c78..ac3e391fc 100644 --- a/apps/recorder/app.js +++ b/apps/recorder/app.js @@ -13,7 +13,7 @@ function loadSettings() { var changed = false; if (!settings.file) { changed = true; - settings.file = "record.log0.csv"; + settings.file = "recorder.log0.csv"; } if (!Array.isArray(settings.record)) { settings.record = ["gps"]; @@ -31,7 +31,7 @@ function updateSettings() { } function getTrackNumber(filename) { - return parseInt(filename.match(/^record\.log(.*)\.csv$/)[1]||0); + return parseInt(filename.match(/^recorder\.log(.*)\.csv$/)[1]||0); } function showMainMenu() { @@ -73,7 +73,7 @@ function showMainMenu() { step: 1, onchange: v => { settings.recording = false; // stop recording if we change anything - settings.file = "record.log"+v+".csv"; + settings.file = "recorder.log"+v+".csv"; updateSettings(); } }, @@ -105,7 +105,7 @@ function viewTracks() { '': { 'title': 'Tracks' } }; var found = false; - require("Storage").list(/^record\.log.*\.csv$/,{sf:true}).forEach(filename=>{ + require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).forEach(filename=>{ found = true; menu["Track "+getTrackNumber(filename)] = ()=>viewTrack(filename,false); }); diff --git a/apps/recorder/interface.html b/apps/recorder/interface.html index 2ae1c3e71..eca13d263 100644 --- a/apps/recorder/interface.html +++ b/apps/recorder/interface.html @@ -3,7 +3,6 @@ - THIS IS NOT CURRENTLY IMPLEMENTED
@@ -14,14 +13,35 @@ function saveKML(track,title) { var kml = ` - - ${title} - - - ${track.map(pt=>[pt.lon, pt.lat, pt.alt].join(",")).join("\n ")} - - - + +${track[0].Heartrate!==undefined ? ` + Heart Rate + `:``} +${track[0].Steps!==undefined ? ` + Step Count`:``} + + + + Tracks + + ${title} + +${track.map(pt=>` ${pt.Time.toISOString()}\n`).join("")} +${track.map(pt=>` ${pt.Longitude} ${pt.Latitude} ${pt.Altitude}\n`).join("")} + + + +${track[0].Heartrate!==undefined ? ` +${track.map(pt=>` ${0|pt.Heartrate}\n`).join("")} + `:``} +${track[0].Steps!==undefined ? ` +${track.map(pt=>` ${0|pt.Steps}\n`).join("")} + `:``} + + + + + `; var a = document.createElement("a"), @@ -39,6 +59,7 @@ function saveKML(track,title) { function saveGPX(track, title) { var gpx = ` + @@ -48,11 +69,19 @@ function saveGPX(track, title) { `; track.forEach(pt=>{ gpx += ` - - ${pt.alt} - + + ${pt.Altitude} + + + + ${pt.Heartrate ? `${pt.Heartrate}`:``} + ${""/*...*/} + ${""/* 65 */} + + `; }); + // https://www8.garmin.com/xmlschemas/TrackPointExtensionv1.xsd gpx += ` @@ -70,104 +99,153 @@ function saveGPX(track, title) { }, 0); } -function trackLineToObject(l, hasTrackNumber) { +function saveCSV(track, title) { + var headers = Object.keys(track[0]); + var csv = headers.join(",")+"\n"; + track.forEach(t=>{ + csv += headers.map(k=>{ + if (t[k] instanceof Date) return t[k].toISOString(); + return t[k]; + }).join(",")+"\n"; + }); + Util.saveCSV(title, csv); +} + +function trackLineToObject(headers, l) { var t = l.trim().split(","); - var n = hasTrackNumber ? 1 : 0; - var o = { - date : new Date(parseInt(t[n+0])), - lat : parseFloat(t[n+1]), - lon : parseFloat(t[n+2]), - alt : parseFloat(t[n+3]) - }; - if (hasTrackNumber) - o.number = t[0]; + var o = {}; + headers.forEach((header,i) => o[header] = t[i]); + if (o.Time) o.Time = new Date(o.Time*1000); return o; } -function downloadTrack(trackid, callback) { +function downloadTrack(filename, callback) { Util.showModal("Downloading Track..."); - Util.readStorageFile(`.gpsrc${trackid.toString(36)}`,data=>{ + Util.readStorageFile(filename,data=>{ Util.hideModal(); - var track = data.trim().split("\n").map(l=>trackLineToObject(l,false)); + var lines = data.trim().split("\n"); + var headers = lines.shift().split(","); + var track = lines.map(l=>trackLineToObject(headers, l)); callback(track); }); } + function getTrackList() { - Util.showModal("Loading Tracks..."); + Util.showModal("Loading Track List..."); domTracks.innerHTML = ""; - Puck.write(`\x10(function() { - Bluetooth.println(""); - for (var n=0;n<36;n++) { - var f = require("Storage").open(".gpsrc"+n.toString(36),"r"); - var l = f.readLine(); - Bluetooth.println((l!==undefined) ? (n + "," + l.trim()) : ""); - } - })()\n`,tracklist=>{ - var trackLines = tracklist.trim().split("\n").filter(l=>l!=""); - var html = `
-
\n`; - trackLines.forEach(l => { - var track = trackLineToObject(l, true /*has track number*/); - html += ` -
-
-
Track ${track.number}
-
${track.date.toString().substr(0,24)}
-
-
- -
-
- -
- `; - }); - if (trackLines.length==0) { - html += ` -
-
-
No tracks
-
No GPS tracks found
-
-
- `; - } - html += ` -
-
`; - domTracks.innerHTML = html; - Util.hideModal(); - var buttons = domTracks.querySelectorAll("button"); - for (var i=0;i { - var button = event.currentTarget; - var trackid = parseInt(button.getAttribute("trackid")); - var task = button.getAttribute("task"); - if (task=="delete") { - Util.showModal("Deleting Track..."); - Util.eraseStorageFile(`.gpsrc${trackid.toString(36)}`,()=>{ - Util.hideModal(); - getTrackList(); + Puck.eval(`require("Storage").list(/^recorder\\.log.*\\.csv$/,{sf:1})`,files=>{ + var trackList = []; + var promise = Promise.resolve(); + /* For each file ask Bangle.js for info. Since we now start recording even + before we have a GPS trace, we get the Bangle to do a *quick* search for us + to see if it found any data first. */ + files.forEach(filename => { + promise = promise.then(()=>new Promise(resolve => { + var trackNo = filename.match(/^recorder\.log(.*)\.csv$/)[1]; + Util.showModal(`Loading Track ${trackNo}...`); + Puck.eval(`(function(fn) { + var f = require("Storage").open(fn,"r"); + var headers = f.readLine(); + var data = f.readLine(); + var lIdx = headers.split(",").indexOf("Latitude"); + if (lIdx >= 0) { + var tries = 100; + var l = data; + while (l && l.split(",")[lIdx]=="" && tries++) + l = f.readLine(); + if (l) data = l; + } + return {headers:headers,l:data}; +})(${JSON.stringify(filename)})`, trackInfo=>{ + console.log(filename," => ",trackInfo); + trackInfo.headers = trackInfo.headers.split(","); + trackList.push({ + filename : filename, + number : trackNo, + info : trackInfo }); + resolve(); + }); + })); + }); + // ================================================ + // When 'promise' completes we now have all the info in trackList + promise.then(() => { + var html = `
+
\n`; + trackList.forEach(track => { + var trackData = trackLineToObject(track.info.headers, track.info.l); + console.log("track", track); + console.log("trackData", trackData); + html += ` +
+
+
Track ${track.number}
+
${trackData.Time.toLocaleDateString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}
+
+${trackData.Latitude ? ` +
+ +
` : `
No GPS info
`} + +
+ `; + }); + if (trackList.length==0) { + html += ` +
+
+
No tracks
+
No GPS tracks found
+
+
+ `; } - if (task=="downloadkml") { - downloadTrack(trackid, track => saveKML(track, `Bangle.js Track ${trackid}`)); - } - if (task=="downloadgpx") { - downloadTrack(trackid, track => saveGPX(track, `Bangle.js Track ${trackid}`)); - } - }); - } - }) + html += ` +
+
`; + domTracks.innerHTML = html; + Util.hideModal(); + var buttons = domTracks.querySelectorAll("button"); + for (var i=0;i { + var button = event.currentTarget; + var filename = button.getAttribute("filename"); + var trackid = parseInt(button.getAttribute("trackid")); + if (!filename || trackid===undefined) return; + var task = button.getAttribute("task"); + if (task=="delete") { + Util.showModal("Deleting Track..."); + Util.eraseStorageFile(filename,()=>{ + Util.hideModal(); + getTrackList(); + }); + } + if (task=="downloadkml") { + downloadTrack(filename, track => saveKML(track, `Bangle.js Track ${trackid}`)); + } + if (task=="downloadgpx") { + downloadTrack(filename, track => saveGPX(track, `Bangle.js Track ${trackid}`)); + } + if (task=="downloadcsv") { + downloadTrack(filename, track => saveCSV(track, `Bangle.js Track ${trackid}`)); + } + }); + } + }); + }); } function onInit() { diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index df0be1d20..09893bbb7 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -7,7 +7,7 @@ function loadSettings() { var settings = require("Storage").readJSON("recorder.json",1)||{}; settings.period = settings.period||10; - if (!settings.file || !settings.file.startsWith("record.log")) + if (!settings.file || !settings.file.startsWith("recorder.log")) settings.recording = false; return settings; } From c280e41560a3812ee83ca461e098bc35b80ef439 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 2 Nov 2021 18:42:39 -0700 Subject: [PATCH 0477/1062] Update README.md --- apps/schoolCalendar/README.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/schoolCalendar/README.md b/apps/schoolCalendar/README.md index 58cb86d2b..225469d7e 100644 --- a/apps/schoolCalendar/README.md +++ b/apps/schoolCalendar/README.md @@ -1,9 +1,23 @@ -# Bangle.js Calendar +# Bangle.js Calendar📆 School Calendar is a calendar that you can see your upcoming classes or schedule. -## Versions: +## Usage: +Enter your calendar events on the customizer then upload. (all day events are not supported yet) -Version 1.0: Get Design Working +Once uploaded on the watch when in the table mode you can use BTN1 and BTN3 to scroll up and down on the list. (The red rectangle indicates your current position on the table and your yellow rectangle indicates your current schedule item or your next schedule item.) + +If you press BTN2 it will go into detail mode, and you can see additional information about your schedule item. Also, in this mode you can scroll up and down with BTN1 and BTN3 to move around in the table. + + +## Updates Coming Soon: +- [ ] Notifications +- [ ] All Day Events +- [ ] Improved Rendering Screen +- [ ] Better Graphics +- [ ] Scrolling Table +- [ ] Bangle.js V2 Compatibility + +## Creator +Ronin0000 -Version 2.0: Update Graphics From fa392008d1e30a6df05245eac969badf4188fb74 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 2 Nov 2021 18:45:24 -0700 Subject: [PATCH 0478/1062] Update README.md --- apps/schoolCalendar/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/schoolCalendar/README.md b/apps/schoolCalendar/README.md index 225469d7e..bea43e2e9 100644 --- a/apps/schoolCalendar/README.md +++ b/apps/schoolCalendar/README.md @@ -1,4 +1,4 @@ -# Bangle.js Calendar📆 +# School Calendar📆 School Calendar is a calendar that you can see your upcoming classes or schedule. @@ -17,6 +17,7 @@ If you press BTN2 it will go into detail mode, and you can see additional inform - [ ] Better Graphics - [ ] Scrolling Table - [ ] Bangle.js V2 Compatibility +- [ ] Orginal Calendar ## Creator Ronin0000 From fa5cbd71c618a2053ec3217048c74a723c46f03f Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 2 Nov 2021 18:49:22 -0700 Subject: [PATCH 0479/1062] Update README.md --- apps/schoolCalendar/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/README.md b/apps/schoolCalendar/README.md index bea43e2e9..47815cc87 100644 --- a/apps/schoolCalendar/README.md +++ b/apps/schoolCalendar/README.md @@ -1,4 +1,4 @@ -# School Calendar📆 +# School Calendar School Calendar is a calendar that you can see your upcoming classes or schedule. From 73819933eeb57f32935e6ad93a146bdbee6d8be6 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 2 Nov 2021 18:52:09 -0700 Subject: [PATCH 0480/1062] Update schoolCalendar.js --- apps/schoolCalendar/schoolCalendar.js | 486 ++++++++++++++------------ 1 file changed, 267 insertions(+), 219 deletions(-) diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js index 588d11b0e..29de1aec4 100644 --- a/apps/schoolCalendar/schoolCalendar.js +++ b/apps/schoolCalendar/schoolCalendar.js @@ -1,27 +1,86 @@ require("Font8x12").add(Graphics); -require("Font7x11Numeric7Seg").add(Graphics); +require("Font7x11Numeric7Seg", 2).add(Graphics); -function getBackgroundImage() {return require("heatshrink").decompress(atob("gMwyEgBAsAgQBCgcAggBCgsAgwBCg8AhABChMAhQBChcAhgBChsAhwBCh8AiEAiIBCiUAiYBCikAioBCi0Ai4BCjEAjIBCjUAjYBCjkAjoBCj0Aj4BBA"));} +let nIntervId; -function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} +function redrawScreen() { + layout.render(layout.background); + layout.render(layout.buttons); + draw(); +} -function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} - -function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} - -function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} - -Bangle.setLCDMode("doublebuffered"); -g.clear(); -g.setFont("8x12"); -g.drawString("Loading...",115,60); -g.flip(); - -LIST = 1; -INFORMATION = 2; -currentStage = LIST; - -var stage = 3; +function updateDay(ffunction,day){ + if(ffunction == 1){ + switch (day) { + case 0: + return "Sunday"; + case 1: + day = "Monday"; + break; + case 2: + day = "Tuesday"; + break; + case 3: + day = "Wednesday"; + break; + case 4: + day = "Thursday"; + break; + case 5: + day = "Friday"; + break; + case 6: + day = "Saturday"; + } + return day; + }else if(ffunction == 2){ + switch (day) { + case 0: + return "Sun"; + case 1: + day = "Mon"; + break; + case 2: + day = "Tue"; + break; + case 3: + day = "Wed"; + break; + case 4: + day = "Thu"; + break; + case 5: + day = "Fri"; + break; + case 6: + day = "Sat"; + } + return day; + }else if(ffunction == 3){ + switch (day) { + case 0: + return "S"; + case 1: + day = "M"; + break; + case 2: + day = "T"; + break; + case 3: + day = "W"; + break; + case 4: + day = "R"; + break; + case 5: + day = "F"; + break; + case 6: + day = "S"; + } + return day; + } +} function getScheduleTable() { let schedule = [ @@ -90,21 +149,6 @@ function getScheduleTable() { return schedule; } -function splitter(str, l){ - var strs = []; - while(str.length > l){ - var pos = str.substring(0, l).lastIndexOf(' '); - pos = pos <= 0 ? l : pos; - strs.push(str.substring(0, pos)); - var i = str.indexOf(' ', pos)+1; - if(i < pos || i > pos+l) - i = pos; - str = str.substring(i); - } - strs.push(str); - return strs; -} - function findNextScheduleIndex() { var schedule = getScheduleTable(); var currentDate = new Date(); @@ -120,10 +164,41 @@ function findNextScheduleIndex() { return 0; } -var currentPositionTable = 0; -var numberOfItemsShown = 5; -function logDebug(message) {console.log(message);} +function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} + +function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} + +function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} + +function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} + +var currentPositionTable = 0; +var numberOfItemsShown = 8; +//Table Positions: +var rectStart = 45; +var rectEnd = 65; +var rectStartX = 10; +var rectEndX = 210; +//Scences: +LIST = 1; +INFORMATION = 2; +currentStage = LIST; + +function splitter(str, l){ + var strs = []; + while(str.length > l){ + var pos = str.substring(0, l).lastIndexOf(' '); + pos = pos <= 0 ? l : pos; + strs.push(str.substring(0, pos)); + var i = str.indexOf(' ', pos)+1; + if(i < pos || i > pos+l) + i = pos; + str = str.substring(i); + } + strs.push(str); + return strs; +} function updateMinutesToCurrentTime(currentMinuteFunction) { if (currentMinuteFunction<10){ @@ -134,219 +209,192 @@ function updateMinutesToCurrentTime(currentMinuteFunction) { return currentMinuteUpdatedFunction; } -function updateHoursToCurrentTime(currentHourFunction) { - if(currentHourFunction >= 13){ - currentHourUpdatedFunction = currentHourFunction-12; - }else{ - currentHourUpdatedFunction = currentHourFunction; - } - return currentHourUpdatedFunction; +function renderBackground(l) { + g.clearRect(0,0,240,20); + g.drawImage(getBackgroundImage(),110,130,{scale:9,rotate:0}); } -function updateDay(ffunction,day){ - if(ffunction == 1){ - switch (day) { - case 0: - return "Sunday"; - case 1: - day = "Monday"; - break; - case 2: - day = "Tuesday"; - break; - case 3: - day = "Wednesday"; - break; - case 4: - day = "Thursday"; - break; - case 5: - day = "Friday"; - break; - case 6: - day = "Saturday"; - } - return day; - }else if(ffunction == 2){ - switch (day) { - case 0: - return "Sun"; - case 1: - day = "Mon"; - break; - case 2: - day = "Tue"; - break; - case 3: - day = "Wed"; - break; - case 4: - day = "Thu"; - break; - case 5: - day = "Fri"; - break; - case 6: - day = "Sat"; - } - return day; - } -} - -function minimalDraw(mode) { - var foundSchedule = getScheduleTable(); - var foundNumber = findNextScheduleIndex(); - if(mode == 1){ - if(currentStage == LIST){ - for(var x = 0;x<=numberOfItemsShown;x++){ - g.setColor(255,255,255); - g.drawRect(10,30+(x*20),220,50+(20*x)); - g.setColor(255,205,0); - g.drawRect(10,30+(2*20),220,50+(2*20)); - g.setColor(255,0,0); - g.drawRect(10,30+(currentPositionTable*20),220,50+(20*currentPositionTable)); - g.setColor(255,255,255); - } - } - }else if(mode == 2){ - for(var i = 0;i<=240;i++){ - g.drawImage(getBackgroundImage(),i,120,{scale:5,rotate:0}); - } - g.drawImage(getMenuIcon(),223.5,70); - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].sm); - scheduleHourUpdatedStart = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].sh); - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].em); - scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].eh); - schduleDay = updateDay(1,foundSchedule[((foundNumber-2)+currentPositionTable)].dow); +function renderTable(l) { + for(var x = 0;x<=numberOfItemsShown;x++){ g.setColor(255,255,255); - g.reset(); - g.setFont("8x12"); - g.drawString(foundSchedule[((foundNumber-2)+currentPositionTable)].cn,13,30); - g.drawString(schduleDay,13,45); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,60); + g.drawRect(rectStartX,rectStart+(x*20),rectEndX,rectEnd+(20*x)); + g.setColor(255,205,0); + g.drawRect(rectStartX,rectStart+(2*20),rectEndX,rectEnd+(2*20)); + g.setColor(255,0,0); + g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); + } +} + +function renderTableText(l){ + var foundNumber = findNextScheduleIndex(); + var foundSchedule = getScheduleTable(); + var scheduleHourUpdated; + var scheduleMinuteUpdated; + var beforeFoundNumber = foundNumber - 2; + for(var x = 0;x<=numberOfItemsShown;x++){ + var currentNumber = beforeFoundNumber + x; + if (beforeFoundNumber + x < 0) { + currentNumber = foundSchedule.length + beforeFoundNumber + x; + } else if (beforeFoundNumber + x > foundSchedule.length - 1) { + currentNumber = beforeFoundNumber + x - foundSchedule.length; + } + + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm); + scheduleHourUpdatedStart = foundSchedule[currentNumber].sh; + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em); + scheduleHourUpdatedEnd = foundSchedule[currentNumber].eh; + scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20); + if(foundSchedule[currentNumber].cn.length >= 15){ + scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20)+"..."; + } + schduleDay = updateDay(3,foundSchedule[currentNumber].dow); + g.setFont("8x12"); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(x*20)); + } +} + +function buttonsF(l){ + if(currentStage == LIST){ + g.drawImage(getDotIcon(),223.5,115); + }else{ + g.drawImage(getMenuIcon(),223.5,115); + } + g.drawImage(getUpArrow(),225,30); + g.drawImage(getDownArrow(),225,215); +} + +function draw() { + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); + if (layout) { + if(currentStage == LIST){ + layout.time.label = currentHour+":"+currentMinuteUpdated; + layout.time.x = 147; + layout.time.y = 10; + layout.render(layout.table); + layout.render(layout.tableText); + logDebug("Rendered"+currentPositionTable); + }else{ + layout.time.label = currentHour+":"+currentMinuteUpdated; + layout.time.x = 147; + layout.time.y = 10; + layout.render(layout.info); + logDebug("Rendered"+currentPositionTable); + } + g.clearRect(150,0,220,35); + layout.render(layout.time); } - g.flip(); - displayClock(); } function RedRectDown() { if(currentPositionTable > 0){ currentPositionTable -= 1; - minimalDraw(1); + if(currentStage == INFORMATION){ + redrawScreen(); + }else{ + draw(); + } } } function RedRectUp() { if(currentPositionTable < numberOfItemsShown){ currentPositionTable += 1; - minimalDraw(1); + if(currentStage == INFORMATION){ + redrawScreen(); + }else{ + draw(); + } } } -function changeScene(){ - if(currentStage == INFORMATION){ - currentStage = LIST; - displayClock(); - setInterval(displayClock,500); - }else if(currentStage == LIST){ - currentStage = INFORMATION; +function renderMiniBackground(l){ + for(var i = 233;i<=240;i++){ + g.drawImage(getBackgroundImage(),i,123,{scale:10,rotate:0}); } - minimalDraw(2); - Bangle.buzz(1000,1000); } -function displayClock() { - var currentDate = new Date(); - var currentDayOfWeek = currentDate.getDay(); - var currentHour = currentDate.getHours(); - var currentMinute = currentDate.getMinutes(); - var currentMinuteUpdated; - var currentHourUpdated; - currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); - currentHourUpdated = updateHoursToCurrentTime(currentHour); - g.setColor(255,255,255); - g.setFont("7x11Numeric7Seg",2); +function renderLoading(l){ + g.setFont("8x12"); + g.drawString("Loading...",240/2-20,240/2-20); +} + +function renderInformation(l){ var foundNumber = findNextScheduleIndex(); var foundSchedule = getScheduleTable(); - var scheduleHourUpdated; - var scheduleMinuteUpdated; - for(var i = 0;i<=240;i++){ - g.drawImage(getBackgroundImage(),i,120,{scale:5,rotate:0}); - } - g.drawString(currentHourUpdated+":"+currentMinuteUpdated, 160, 0); - - g.drawImage(getUpArrow(),225,5); - g.drawImage(getDownArrow(),225,140); - if(currentStage == LIST){ - var beforeFoundNumber = foundNumber - 2; - for(var x = 0;x<=numberOfItemsShown;x++){ - var currentNumber = beforeFoundNumber + x; - if (beforeFoundNumber + x < 0) { - currentNumber = foundSchedule.length + beforeFoundNumber + x; - } else if (beforeFoundNumber + x > foundSchedule.length - 1) { - currentNumber = beforeFoundNumber + x - foundSchedule.length; - } - - g.drawImage(getDotIcon(),223.5,70); - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm); - scheduleHourUpdatedStart = updateHoursToCurrentTime(foundSchedule[currentNumber].sh); - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em); - scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[currentNumber].eh); - scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20); - if(foundSchedule[currentNumber].cn.length >= 20){ - scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20)+"..."; - } - schduleDay = updateDay(2,foundSchedule[currentNumber].dow); - g.setColor(255,255,255); - g.drawRect(10,30+(x*20),220,50+(20*x)); - g.reset(); - g.setFont("8x12"); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,35+(x*20)); - g.setColor(255,205,0); - g.drawRect(10,30+(2*20),220,50+(2*20)); - g.setColor(255,0,0); - g.drawRect(10,30+(currentPositionTable*20),220,50+(20*currentPositionTable)); - } - }else if(currentStage == INFORMATION){ - g.drawImage(getMenuIcon(),223.5,70); - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].sm); - scheduleHourUpdatedStart = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].sh); - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].em); - scheduleHourUpdatedEnd = updateHoursToCurrentTime(foundSchedule[((foundNumber-2)+currentPositionTable)].eh); - schduleDay = updateDay(1,foundSchedule[((foundNumber-2)+currentPositionTable)].dow); - g.setColor(255,255,255); - g.reset(); - g.setFont("8x12",2); - - var splitClassNames = splitter(foundSchedule[((foundNumber-2)+currentPositionTable)].cn, 15); - var currentY = 5; - for (var j=0; j < splitClassNames.length; j++) { - g.drawString(splitClassNames[j],13,currentY); - currentY = currentY + 25; - } - g.setFont("8x12"); - g.drawString(schduleDay,13,currentY); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15); - } - g.flip(); + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); + scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); + scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; + scheduleDay = updateDay(1,foundSchedule[((foundNumber-2)+currentPositionTable)].dow); g.setColor(255,255,255); + g.setFont("8x12",2); + var splitClassNames = splitter(foundSchedule[((foundNumber-2)+currentPositionTable)].cn, 15); + var currentY = 5; + for (var j=0; j < splitClassNames.length; j++) { + g.drawString(splitClassNames[j],13,currentY+50); + currentY = currentY + 25; + } + g.setFont("8x12"); + g.drawString(schduleDay,13,currentY+50); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); } +var Layout = require("Layout"); +var layout = new Layout( + {type:"h", c: [ + {type:"custom", render:renderTableText, id:"tableText"}, + {type:"custom", render:buttonsF, id:"buttons"}, + {type:"custom", render:renderBackground, id:"background"}, + {type:"custom", render:renderTable, id:"table"}, + {type:"custom", render:renderMiniBackground, id:"miniBackground"}, + {type:"custom", render:renderLoading, id:"loading"}, + {type:"custom", render:renderInformation, id:"info"}, + {type:"txt", font:"7x11Numeric7Seg:2", label:"00:00", id:"time"}, + ]}, + {type:"v", c:[ + ]}, + {btns:[ + {label:"", cb: RedRectUp()}, + {label:"", cb: l=>print("Two")}, + {label:"", cb: RedRectDown()} +]}); -displayClock(); +function getBackgroundImage() {return require("heatshrink").decompress(atob("j0ZyEKIf4A4gIB6gQB6gYB6ggB6goB6gwB6g4B6hAABAYIBHBZIVLAK8IhIBXgAThhQB6hYB6hgB6hoB6hwB6h4B6iAB6iIB6iQBHiAJOB54XSiYB6igB6ioB6iwB6i4B5A="));} + +function logDebug(message) {console.log(message);} + +function changeScene(){ + layout.render(layout.buttons); + if(currentStage == INFORMATION){ + currentStage = LIST; + nIntervId = setInterval(redrawScreen, 100000); + }else if(currentStage == LIST){ + currentStage = INFORMATION; + clearInterval(); + } + layout.render(layout.background); + layout.render(layout.buttons); + draw(); +} + +// timeout used to update every minute +var drawTimeout; + +setInterval(draw, 15000); -var currentMinuteUpdatedFunction = "00"; -var currentHourUpdatedFunction = 11; -var scheduleMinuteUpdatedStart = 35; -var scheduleHourUpdatedStart = 10; -var scheduleMinuteUpdatedEnd = currentMinuteUpdatedFunction; -var scheduleHourUpdatedEnd = 11; setWatch(RedRectUp, BTN3, { repeat:true, edge:'rising', debounce : 50 }); setWatch(RedRectDown, BTN1, { repeat:true, edge:'rising', debounce : 50 }); setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); -setInterval(displayClock, 20000); +layout.update(); +layout.render(layout.loading); +layout.render(layout.background); +layout.render(layout.buttons); -setTimeout(displayClock,500); +draw(); From aaee5f3c0610de7a48bdda7fe9c6064dff97780e Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 2 Nov 2021 19:13:08 -0700 Subject: [PATCH 0481/1062] Update interface.html --- apps/schoolCalendar/interface.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/interface.html index 0ff6a2af3..2a2d24df3 100644 --- a/apps/schoolCalendar/interface.html +++ b/apps/schoolCalendar/interface.html @@ -28,6 +28,7 @@ + - + From ada94b11cfbebfeaa010cee1b39d0f92591d4e13 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Wed, 3 Nov 2021 08:06:50 -0700 Subject: [PATCH 0485/1062] Update interface.html --- apps/schoolCalendar/interface.html | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/interface.html index 90eaacc85..4c2db202b 100644 --- a/apps/schoolCalendar/interface.html +++ b/apps/schoolCalendar/interface.html @@ -69,6 +69,29 @@ var calendarEvents = calendar.getEvents(); let schedule = [] + + for(i=0;i>calendarEvents.length;i++){ + var calendarEntry = {} + calendarEntry['cn'] = calendarEvents[i].title; + calendarEntry['dow'] = calendarEvents[i].start.getDate(); + calendarEntry['sh'] = calendarEvents[i].start.getHours(); + calendarEntry['sm'] = calendarEvents[i].start.getMinutes(); + calendarEntry['eh'] = calendarEvents[i].end.getHours(); + calendarEntry['em'] = calendarEvents[i].end.getMinutes(); + schedule.push(calendarEntry) + } + + content = schedule + + document.getElementById("upload").addEventListener("click", function() { + sendCustomizedApp({ + id : "schoolCalender", + storage:[ + {"name":"customFile.app.json",url:"app.js",content:content} + ] + }); + }); + From df7f9cc530194025708b712565a410aed1d73e95 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Wed, 3 Nov 2021 08:20:09 -0700 Subject: [PATCH 0486/1062] Update interface.html --- apps/schoolCalendar/interface.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/interface.html index 4c2db202b..9c527591e 100644 --- a/apps/schoolCalendar/interface.html +++ b/apps/schoolCalendar/interface.html @@ -81,11 +81,10 @@ schedule.push(calendarEntry) } - content = schedule document.getElementById("upload").addEventListener("click", function() { + content = schedule sendCustomizedApp({ - id : "schoolCalender", storage:[ {"name":"customFile.app.json",url:"app.js",content:content} ] From cd7579269efe8d54a5a5680b8126ea38f9ce54be Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Wed, 3 Nov 2021 08:22:04 -0700 Subject: [PATCH 0487/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 810128f45..4ff587714 100644 --- a/apps.json +++ b/apps.json @@ -3521,7 +3521,7 @@ "description": "A simple calendar that you can see your upcoming events. Keep in note that your events reapeat weekly.", "tags": "tool", "readme": "README.md", - "custom":"interface.html", + "custom":"custom.html", "storage": [ {"name":"schoolCalendar.app.js","url":"app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} From e2928498690565ba7d0454aaa4265bc8adfda712 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Wed, 3 Nov 2021 08:22:32 -0700 Subject: [PATCH 0488/1062] Rename interface.html to custom.html --- apps/schoolCalendar/{interface.html => custom.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/schoolCalendar/{interface.html => custom.html} (100%) diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/custom.html similarity index 100% rename from apps/schoolCalendar/interface.html rename to apps/schoolCalendar/custom.html From ab3de04c2deaa14265d8887446d5824aa5e2f360 Mon Sep 17 00:00:00 2001 From: Filipe Fradique Date: Thu, 4 Nov 2021 05:36:09 +0000 Subject: [PATCH 0489/1062] Fixed wrong day being displayed in nifty clocks --- apps/ffcniftya/app.js | 2 +- apps/ffcniftyb/app.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/ffcniftya/app.js b/apps/ffcniftya/app.js index 0b8865bec..31742f64a 100644 --- a/apps/ffcniftya/app.js +++ b/apps/ffcniftya/app.js @@ -27,7 +27,7 @@ function draw() { const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); const minutes = d02(now.getMinutes()); - const day = d02(now.getDay()); + const day = d02(now.getDate()); const month = d02(now.getMonth() + 1); const year = now.getFullYear(); diff --git a/apps/ffcniftyb/app.js b/apps/ffcniftyb/app.js index 58bf0b24b..75d217ab4 100644 --- a/apps/ffcniftyb/app.js +++ b/apps/ffcniftyb/app.js @@ -31,7 +31,7 @@ function renderText(g) { const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); const minutes = d02(now.getMinutes()); - const day = d02(now.getDay()); + const day = d02(now.getDate()); const month = d02(now.getMonth() + 1); const year = now.getFullYear(); From 935d409f4c46f527b8e306fad854113419b69b9e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 4 Nov 2021 17:16:02 +0000 Subject: [PATCH 0490/1062] ability to depend on a specific app ID Layout can display images in buttons iOS and Android integration apps --- README.md | 1 + apps.json | 43 +++++++++-- apps/android/ChangeLog | 1 + apps/android/app-icon.js | 1 + apps/android/app.js | 2 + apps/android/app.png | Bin 0 -> 636 bytes apps/android/boot.js | 55 ++++++++++++++ apps/ios/ChangeLog | 1 + apps/ios/app-icon.js | 1 + apps/ios/app.js | 2 + apps/ios/app.png | Bin 0 -> 1301 bytes apps/ios/boot.js | 129 ++++++++++++++++++++++++++++++++ apps/messages/ChangeLog | 2 + apps/messages/app.js | 157 ++++++++++++++++++++++++++++----------- apps/messages/boot.js | 36 --------- apps/messages/lib.js | 37 +++++++++ core | 2 +- modules/Layout.js | 31 ++++---- 18 files changed, 403 insertions(+), 98 deletions(-) create mode 100644 apps/android/ChangeLog create mode 100644 apps/android/app-icon.js create mode 100644 apps/android/app.js create mode 100644 apps/android/app.png create mode 100644 apps/android/boot.js create mode 100644 apps/ios/ChangeLog create mode 100644 apps/ios/app-icon.js create mode 100644 apps/ios/app.js create mode 100644 apps/ios/app.png create mode 100644 apps/ios/boot.js create mode 100644 apps/messages/ChangeLog delete mode 100644 apps/messages/boot.js diff --git a/README.md b/README.md index 21f5dbff9..527cb1188 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,7 @@ and which gives information about the app for the Launcher. "tags": "", // comma separated tag list for searching "supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2 "dependencies" : { "notify":"type" } // optional, app 'types' we depend on + "dependencies" : { "messages":"app" } // optional, depend on a specific app ID // for instance this will use notify/notifyfs is they exist, or will pull in 'notify' "readme": "README.md", // if supplied, a link to a markdown-style text file // that contains more information about this app (usage, etc) diff --git a/apps.json b/apps.json index 35fe0b49a..156b6e5b0 100644 --- a/apps.json +++ b/apps.json @@ -32,17 +32,50 @@ { "id": "messages", "name": "Messages", - "version": "0.01", - "description": "App to display notifications from iOS and Gadgetbridge (BETA)", + "version": "0.02", + "description": "App to display notifications from iOS and Gadgetbridge", "icon": "app.png", + "type": "app", "tags": "tool,system", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"messages.app.js","url":"app.js"}, {"name":"messages.img","url":"app-icon.js","evaluate":true}, - {"name":"messages.boot.js","url":"boot.js"}, - {"name":"messages.wid.js","url":"widget.js"} + {"name":"messages.wid.js","url":"widget.js"}, + {"name":"messages","url":"lib.js"} + ], + "sortorder": -9 + }, + { + "id": "android", + "name": "Android Integration", + "version": "0.01", + "description": "(BETA) App to display notifications from Gadgetbridge on Android. This will eventually replace the Gadgetbridge widget.", + "icon": "app.png", + "tags": "tool,system,messages,notifications", + "dependencies": {"messages":"app"}, + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"android.app.js","url":"app.js"}, + {"name":"android.img","url":"app-icon.js","evaluate":true}, + {"name":"android.boot.js","url":"boot.js"} + ], + "sortorder": -9 + }, + { + "id": "ios", + "name": "iOS Integration", + "version": "0.01", + "description": "(BETA) App to display notifications from iOS devices", + "icon": "app.png", + "tags": "tool,system,ios,apple,messages,notifications", + "dependencies": {"messages":"app"}, + "supports": ["BANGLEJS","BANGLEJS2"], + "storage": [ + {"name":"ios.app.js","url":"app.js"}, + {"name":"ios.img","url":"app-icon.js","evaluate":true}, + {"name":"ios.boot.js","url":"boot.js"} ], "sortorder": -9 }, @@ -216,7 +249,7 @@ "id": "gbridge", "name": "Gadgetbridge", "version": "0.24", - "description": "The default notification handler for Gadgetbridge notifications from Android", + "description": "The default notification handler for Gadgetbridge notifications from Android. This will eventually be replaced by the 'Android' app.", "icon": "app.png", "type": "widget", "tags": "tool,system,android,widget", diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/android/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/android/app-icon.js b/apps/android/app-icon.js new file mode 100644 index 000000000..9253ec839 --- /dev/null +++ b/apps/android/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4MA///xF9FstggwFDuEOAoc//gFJv/+AoZHBAgUB8/nwAFCBYIFCgYFB4AFHABdjCIPGAoPzAoPPAvpHFMpYFFPosAnk8NgYFdjEYfMo=")) diff --git a/apps/android/app.js b/apps/android/app.js new file mode 100644 index 000000000..5c5c7ddaf --- /dev/null +++ b/apps/android/app.js @@ -0,0 +1,2 @@ +// Config app not implemented yet +load("messages.app.js"); diff --git a/apps/android/app.png b/apps/android/app.png new file mode 100644 index 0000000000000000000000000000000000000000..65150f08de4205995f37e8b83cd6bf94aa25352b GIT binary patch literal 636 zcmV-?0)zdDP)1F^Gb$6(WiXLUDF7wIWE{$ymfapyKRc zhJZgN!NDnue?Ui5XhrNIRB-CxBGf_BO|ys^>r38o=#!L?Jx&ow@sJKT6@ zBexX+fQph&|875t3T*lQ3sLew(?;SM8=C}h--8bzcOI@4L{u&Sr-2h;NCWr`dg3jk65lA7^+8@Ur7q4HW`x#fA=;jxH?ikzp- zcKOwn#iaQq$>MTt;W7lTflN5G6~15Uv!r`|PeiUhT6}A4Xk!7D+ET8?kB=ay{*C}-+Ha-HLtMqtJ$1;e)GMNsf|6*;^*yy#RH@z`%Kcop?;rBuX%wpM{?O) zz)O=lj0GsHbeAcjr9oe6zj;+z!BBv)i3R3N*Y@C|#l0M3CGuCOTq|4$sR9H_>FM(3 z`u_O5+AQSp(xwc`*N+$;kSgInsV7=ZUaH-EaZEhhxs;Q}b;W0>e73sLiQxoH8Yf`V zI02JpDg^9yF10?gN(}Qw3J8?a{zh8I^GeBTi)Pq-#7S-RO|Gn0w1u zKg=+0;W0000 { + // feed a copy to other handlers if there were any + if (_GB) setTimeout(_GB,0,Object.assign({},event)); + + /* TODO: Call handling, fitness */ + var HANDLERS = { + // {t:"notify",id:int, src,title,subject,body,sender,tel:string} add + "notify" : function() { event.t="add";require("messages").pushMessage(event); }, + // {t:"notify~",id:int, title:string} // modified + "notify~" : function() { event.t="modify";require("messages").pushMessage(event); }, + // {t:"notify-",id:int} // remove + "notify-" : function() { event.t="remove";require("messages").pushMessage(event); }, + // {t:"find", n:bool} // find my phone + "find" : function() { + if (Bangle.findDeviceInterval) { + clearInterval(Bangle.findDeviceInterval); + delete Bangle.findDeviceInterval; + } + if (event.n) // Ignore quiet mode: we always want to find our watch + Bangle.findDeviceInterval = setInterval(_=>Bangle.buzz(),1000); + }, + // {t:"musicstate", state:"play/pause",position,shuffle,repeat} + "musicstate" : function() { + require("messages").pushMessage({t:"modify",id:"music",title:"Music",state:event.state}); + }, + // {t:"musicinfo", artist,album,track,dur,c(track count),n(track num} + "musicinfo" : function() { + require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"})); + } + }; + var h = HANDLERS[event.t]; + if (h) h(); else console.log("GB Unknown",event); + }; + + // Battery monitor + function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); } + NRF.on("connect", () => setTimeout(sendBattery, 2000)); + setInterval(sendBattery, 10*60*1000); + // Health tracking + Bangle.on('health', health=>{ + gbSend({ t: "act", stp: health.steps, hrm: health.bpm }); + }); + // Music control + Bangle.musicControl = cmd => { + // play/pause/next/previous/volumeup/volumedown + gbSend({ t: "music", m:cmd }); + } +})(); diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/ios/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/ios/app-icon.js b/apps/ios/app-icon.js new file mode 100644 index 000000000..b74048750 --- /dev/null +++ b/apps/ios/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwZC/AGEB/4AGwARHv4RH/wQGj4QHAAP4CIoQJAAIRWg4RL8ARVn4RL/gR/CJv9BIP934DFEZH+v/0AgMv+wRK+YCBz/7C4PfCJOfAQO//JHMCIX3/d/CJ//t4RJF4JlCCIP/koRKEYh+DCIxlBCIQADCJQgCn4DCCJSbBHIIDBXYQRI/+Sp4DB7ZsCfdQRzg4RL8ARVgARLCAgRSj4QJ/ARFgF/CA/+CA0AgIRHwARHAH4AnA")) diff --git a/apps/ios/app.js b/apps/ios/app.js new file mode 100644 index 000000000..5c5c7ddaf --- /dev/null +++ b/apps/ios/app.js @@ -0,0 +1,2 @@ +// Config app not implemented yet +load("messages.app.js"); diff --git a/apps/ios/app.png b/apps/ios/app.png new file mode 100644 index 0000000000000000000000000000000000000000..79aa78f3a3cbe6d008a396f65cf4b6e739a5d494 GIT binary patch literal 1301 zcmV+w1?u{VP)7hf>g#sri^Q3NGIK}89)+*_c9?slgwz3g`9 z_^{GuU1oM>+XYPa_q6k!Gc*4)-}%n@W)^bf$dTi2qU<<}h&!xl%Rwy@ojE8b0)->g za1)_bIXDhdAMp9=l~PfwCtD666p5@r)K-Bhnd!95InXzi`u%&XIHwIjM4aJ>wnb3u zN3P8}N@RP$@2gcx>1KY8bQiiC;sHdd4WUkLb2>lM83;upD@4>+qbDm9)bdilx8BHV zI0GW$7ExQWBg#60><|&Rk=-zWM73dRR#e$Q!Q`-}Ei47H@~1NS<4c2Dp%KSqDaY6b!?C5<~d_?M z6Ca$$>2WY~L&a_HQ|kBKR;%q9$p7I>oL8C?2cn~n9i@BaJ1OD;2A4ih-;L?Wx4#dK z$(1nr+y_#8t6rbNq-X3GKHCAf|2l*-d<_8AatRv$W^jf<7^AxQ}==kq(abYv^U;A>5sss&y3%_6@Rc7P2&0$kN3@!jS=L zn_X-?mD*3PW?r2zEy#d&vci1yp0e0Qvu zg+)!NhBVj?;AUVPeeU6{$>dd2(PMEAuls|G=X!|6bh>+De0QLY&2^_(ySf7JSQh|3 zuM2N=kQW;r{Mrdo9R_us{Z}vX=HcBuU1l$RAcIEs=k_LoPn1zIyLf0!ABgk)fi}K5 z*v|EiehLcnm^!JDnk5r?aJnz)d|hB?i{jAr4qC#8xX{%>RdESxD`&GfSZ*lPsF97h z6W=LDtrOpCm<7;t$5f=J%gA6Bz||}W$qZ$z#V`P+Xv~h93=obPnM`It3_m8_X_S%% zLz|I7L|mar*C}9HR#aI;!TCV3x6{ + /* eg: + { + event:"add", + uid:42, + category:4, + categoryCnt:42, + silent:true, + important:false, + preExisting:true, + positive:false, + negative:true + } */ + + //console.log("ANCS",msg.event,msg.id); + // don't need info for remove events - pass these on + if (msg.event=="remove") + return E.emit("notify", msg); + + // not a remove - we need to get the message info first + function ancsHandler() { + var msg = Bangle.ancsMessageQueue[0]; + NRF.ancsGetNotificationInfo( msg.uid ).then( info => { + E.emit("notify", Object.assign(msg, info)); + Bangle.ancsMessageQueue.shift(); + if (Bangle.ancsMessageQueue.length) + ancsHandler(); + }); + } + Bangle.ancsMessageQueue.push(msg); + // if this is the first item in the queue, kick off ancsHandler, + // otherwise ancsHandler will handle the rest + if (Bangle.ancsMessageQueue.length==1) + ancsHandler(); +}); + +// Handle ANCS events with all the data +E.on('notify',msg=>{ +/* Info from ANCS event plus + "uid" : int, + "appId" : string, + "title" : string, + "subtitle" : string, + "message" : string, + "messageSize" : string, + "date" : string, + "posAction" : string, + "negAction" : string, + "name" : string, +*/ + var appNames = { + "com.netflix.Netflix" : "Netflix", + "com.google.ios.youtube" : "YouTube", + "com.google.hangouts" : "Hangouts" + // could also use NRF.ancsGetAppInfo(msg.appId) here + }; + var unicodeRemap = { + '2019':"'" + }; + var replacer = ""; //(n)=>print('Unknown unicode '+n.toString(16)); + if (appNames[msg.appId]) msg.a + require("messages").pushMessage({ + t : msg.event, + id : msg.uid, + src : appNames[msg.appId] || msg.appId, + title : msg.title&&E.decodeUTF8(msg.title, unicodeRemap, replacer), + subject : msg.subtitle&&E.decodeUTF8(msg.subtitle, unicodeRemap, replacer), + body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer) + }); + // TODO: posaction/negaction? +}); + +// Apple media service +E.on('AMS',a=>{ + function push(m) { + var msg = { t : "modify", id : "music", title:"Music" }; + if (a.id=="artist") msg.artist = m; + else if (a.id=="album") msg.artist = m; + else if (a.id=="title") msg.tracl = m; + else return; // duration? need to reformat + require("messages").pushMessage(msg); + } + if (a.truncated) NRF.amsGetMusicInfo(a.id).then(push) + else push(a.value); +}); + +// Music control +Bangle.musicControl = cmd => { + // play, pause, playpause, next, prev, volup, voldown, repeat, shuffle, skipforward, skipback, like, dislike, bookmark + NRF.amsCommand(cmd); +} + +/* +// For testing... + +NRF.ancsGetNotificationInfo = function(uid) { + print("ancsGetNotificationInfo",uid); + return Promise.resolve({ + "uid" : uid, + "appId" : "Hangouts", + "title" : "Hello", + "subtitle" : "There", + "message" : "Lots and lots of text", + "messageSize" : 100, + "date" : "...", + "posAction" : "ok", + "negAction" : "cancel", + "name" : "Fred", + }); +}; + +E.emit("ANCS", { + event:"add", + uid:42, + category:4, + categoryCnt:42, + silent:true, + important:false, + preExisting:true, + positive:false, + negative:true +}); + +*/ diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog new file mode 100644 index 000000000..bbeb8b717 --- /dev/null +++ b/apps/messages/ChangeLog @@ -0,0 +1,2 @@ +0.01: New App! +0.02: Add 'messages' library diff --git a/apps/messages/app.js b/apps/messages/app.js index 801434498..2fb0c613b 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -12,15 +12,10 @@ /* For example for maps: -GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"}) -GB({"t":"notify","id":2,"src":"Hangouts","title":"Gordon","body":"Hello world quite a lot of text here..."}) -GB({"t":"notify","id":3,"src":"Messages","title":"Ted","body":"Bed time."}) -GB({"t":"notify","id":4,"src":"Messages","title":"Kailo","body":"Mmm... food"}) -GB({"t":"notify-","id":1}) - -GB({"t":"notify","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"Y2MBAA....AAAAAAAAAAAAAA="}) -GB({"t":"notify~","id":1,"body":"Campton - 11:54 ETA"}) -GB({"t":"notify~","id":1,"title":"High St"}) +// a message +{"t":"add","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"} +// maps +{"t":"add","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"GhqBAAAMAAAHgAAD8AAB/gAA/8AAf/gAP/8AH//gD/98B//Pg/4B8f8Afv+PP//n3/f5//j+f/wfn/4D5/8Aef+AD//AAf/gAD/wAAf4AAD8AAAeAAADAAA="} */ @@ -37,6 +32,21 @@ function saveMessages() { require("Storage").writeJSON("messages.json",MESSAGES) } +function getBackImage() { + return atob("FhYBAAAAEAAAwAAHAAA//wH//wf//g///BwB+DAB4EAHwAAPAAA8AADwAAPAAB4AAHgAB+AH/wA/+AD/wAH8AA=="); +} +function getMessageImage(msg) { + if (msg.img) return atob(msg.img); + var s = (msg.src||"").toLowerCase(); + if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); + if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); + if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); + if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); + if (msg.id=="back") return getBackImage(); + return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A=="); +} + + function showMapMessage(msg) { var m; var distance, street, target, eta; @@ -50,48 +60,98 @@ function showMapMessage(msg) { target = m[1]; eta = m[2]; } else target=msg.body; - layout = new Layout({ - type:"v", c: [ - {type:"txt", font:"6x15", label:target, bgCol:"#0f0", fillx:1, pad:2 }, - {type:"h", bgCol:"#0f0", fillx:1, c: [ - {type:"txt", font:"6x8", label:"Towards" }, - {type:"txt", font:"6x15:2", label:street } + layout = new Layout({ type:"v", c: [ + {type:"txt", font:"6x15", label:target, bgCol:"#0f0", fillx:1, pad:2 }, + {type:"h", bgCol:"#0f0", fillx:1, c: [ + {type:"txt", font:"6x8", label:"Towards" }, + {type:"txt", font:"6x15:2", label:street } + ]}, + {type:"h",fillx:1, filly:1, c: [ + msg.img?{type:"img",src:atob(msg.img), scale:2}:{}, + {type:"v", fillx:1, c: [ + {type:"txt", font:"6x15:2", label:distance||"" } ]}, - {type:"h",fillx:1, filly:1, c: [ - {type:"img",src:atob(msg.img)}, - {type:"v", fillx:1, c: [ - {type:"txt", font:"6x15:2", label:distance||"" } - ]}, - ]}, - - {type:"txt", font:"6x8:2", label:eta } - ] - }); - g.clearRect(0,24,g.getWidth()-1,g.getHeight()-1); + ]}, + {type:"txt", font:"6x8:2", label:eta } + ]}); + g.clearRect(Bangle.appRect); layout.render(); Bangle.setUI("updown",function() { // any input to mark as not new and return to menu msg.new = false; saveMessages(); + layout = undefined; checkMessages(); }); } +function showMusicMessage(msg) { + function fmtTime(s) { + var m = Math.floor(s/60); + s = (s%60).toString().padStart(2,0); + return m+":"+s; + } + + function back() { + msg.new = false; + saveMessages(); + layout = undefined; + checkMessages(); + } + layout = new Layout({ type:"v", c: [ + {type:"h", fillx:1, bgCol:"#0f0", c: [ + { type:"btn", src:getBackImage, cb:back }, + { type:"v", fillx:1, c: [ + { type:"txt", font:"6x15:2", label:msg.artist, pad:2 }, + { type:"txt", font:"6x15", label:msg.album, pad:2 } + ]} + ]}, + {type:"txt", font:"6x15:2", label:msg.track, fillx:1, filly:1, pad:2 }, + Bangle.musicControl?{type:"h",fillx:1, c: [ + {type:"btn", pad:8, label:"\0"+atob("FhgBwAADwAAPwAA/wAD/gAP/gA//gD//gP//g///j///P//////////P//4//+D//gP/4A/+AD/gAP8AA/AADwAAMAAA"), cb:()=>Bangle.musicControl("play")}, // play + {type:"btn", pad:8, label:"\0"+atob("EhaBAHgHvwP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP3gHg"), cb:()=>Bangle.musicControl("pause")}, // pause + {type:"btn", pad:8, label:"\0"+atob("EhKBAMAB+AB/gB/wB/8B/+B//B//x//5//5//x//B/+B/8B/wB/gB+AB8ABw"), cb:()=>Bangle.musicControl("next")}, // next + ]}:{}, + {type:"txt", font:"6x8:2", label:msg.dur?fmtTime(msg.dur):"--:--" } + ]}); + g.clearRect(Bangle.appRect); + layout.render(); +} + function showMessage(msgid) { var msg = MESSAGES.find(m=>m.id==msgid); if (!msg) return checkMessages(); // go home if no message found if (msg.src=="Maps") return showMapMessage(msg); - var m = msg.title+"\n"+msg.body; - E.showPrompt(m,{title:"Message", buttons : {"Read":"read", "Back":"back"}}).then(chosen => { - if (chosen=="read") { - // any input to mark as not new and return to menu - msg.new = false; - saveMessages(); - checkMessages(); - } else { - checkMessages(true); - } - }); + if (msg.id=="music") return showMusicMessage(msg); + // Normal text message display + var title=msg.title, titleFont = "6x15:2"; + if (title) { + var w = g.getWidth()-40; + if (g.setFont(titleFont).stringWidth(title) > w) + titleFont = "6x15"; + if (g.setFont(titleFont).stringWidth(title) > w) + title = g.wrapString(title, w).join("\n"); + } + layout = new Layout({ type:"v", c: [ + {type:"h", fillx:1, bgCol:"#0f0", c: [ + { type:"img", src:getMessageImage(msg), pad:2 }, + { type:"v", fillx:1, c: [ + {type:"txt", font:"6x15", label:msg.src||"Message", bgCol:"#0f0", fillx:1, pad:2 }, + title?{type:"txt", font:titleFont, label:title, bgCol:"#0f0", fillx:1, pad:2 }:{}, + ]}, + ]}, + {type:"txt", font:"6x15", label:msg.body||"", wrap:true, fillx:1, filly:1, pad:2 }, + {type:"h",fillx:1, c: [ + {type:"btn", src:getBackImage(), cb:()=>checkMessages(true)}, // back + msg.new?{type:"btn", src:atob("HRiBAD///8D///wj///Fj//8bj//x3z//Hvx/8/fx/j+/x+Ad/B4AL8Rh+HxwH+PHwf+cf5/+x/n/PH/P8cf+cx5/84HwAB4fgAD5/AAD/8AAD/wAAD/AAAD8A=="), cb:()=>{ + msg.new = false; // read mail + saveMessages(); + checkMessages(); + }}:{} + ]} + ]}); + g.clearRect(Bangle.appRect); + layout.render(); } function checkMessages(forceShowMenu) { @@ -112,24 +172,35 @@ function checkMessages(forceShowMenu) { // Otherwise show a menu E.showScroller({ h : 48, - c : MESSAGES.length, + c : MESSAGES.length+1, draw : function(idx, r) {"ram" var msg = MESSAGES[idx-1]; if (msg && msg.new) g.setBgColor("#4F4"); else g.setBgColor((idx&1) ? "#CFC" : "#9F9"); g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setColor(g.theme.fg); - if (idx==0) msg = {title:"< Back"}; + if (idx==0) msg = {id:"back", title:"< Back"}; + if (!msg) return; + var x = r.x+2, title = msg.title, body = msg.body; + var img = getMessageImage(msg); + if (msg.id=="music") { + title = msg.artist || "Music"; + body = msg.track; + } + if (img) { + g.drawImage(img, x+24, r.y+24, {rotate:0}); // force centering + x += 50; + } var m = msg.title+"\n"+msg.body; if (msg.src) g.setFontAlign(1,-1).drawString(msg.src, r.x+r.w-2, r.t+2); - if (msg.title) g.setFontAlign(-1,-1).setFont("12x20").drawString(msg.title, r.x+2,r.y+2); - if (msg.body) { + if (title) g.setFontAlign(-1,-1).setFont("12x20").drawString(title, x,r.y+2); + if (body) { g.setFontAlign(-1,-1).setFont("6x8"); - var l = g.wrapString(msg.body, r.w-14); + var l = g.wrapString(body, r.w-14); if (l.length>3) { l = l.slice(0,3); l[l.length-1]+="..."; } - g.drawString(l.join("\n"), r.x+12,r.y+20); + g.drawString(l.join("\n"), x+10,r.y+20); } }, select : idx => { diff --git a/apps/messages/boot.js b/apps/messages/boot.js deleted file mode 100644 index dce3979da..000000000 --- a/apps/messages/boot.js +++ /dev/null @@ -1,36 +0,0 @@ -(function() { - var _GB = global.GB; - global.GB = (event) => { - if (_GB) setTimeout(_GB,0,event); - // call handling? - if (!event.t.startsWith("notify")) return; - /* event is: - {t:"notify",id:int, src,title,subject,body,sender,tel:string} - {t:"notify~",id:int, title:string} // modified - {t:"notify-",id:int} // remove - */ - var messages, inApp = "undefined"!=typeof MESSAGES; - if (inApp) - messages = MESSAGES; // we're in an app that has already loaded messages - else // no app - load messages - messages = require("Storage").readJSON("messages.json",1)||[]; - // now modify/delete as appropriate - var mIdx = messages.findIndex(m=>m.id==event.id); - if (event.t=="notify-") { - if (mIdx>=0) messages.splice(mIdx, 1); // remove item - mIdx=-1; - } else { // add/modify - if (event.t=="notify") event.new=true; // new message - if (mIdx<0) mIdx=messages.push(event)-1; - else Object.assign(messages[mIdx], event); - } - require("Storage").writeJSON("messages.json",messages); - if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]); - // ok, saved now - we only care if it's new - if (event.t!="notify") return; - // if we're in a clock, go straight to messages app - if (Bangle.CLOCK) return load("messages.app.js"); - if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know - WIDGETS.messages.newMessage(); - }; -})() diff --git a/apps/messages/lib.js b/apps/messages/lib.js index e69de29bb..f3ea242e5 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -0,0 +1,37 @@ +exports.pushMessage = function(event) { + /* event is: + {t:"add",id:int, src,title,subject,body,sender,tel, important:bool} // add new + {t:"add",id:int, id:"music", state, artist, track, etc} // add new + {t:"remove-",id:int} // remove + {t:"modify",id:int, title:string} // modified + */ + var messages, inApp = "undefined"!=typeof MESSAGES; + if (inApp) + messages = MESSAGES; // we're in an app that has already loaded messages + else // no app - load messages + messages = require("Storage").readJSON("messages.json",1)||[]; + // now modify/delete as appropriate + var mIdx = messages.findIndex(m=>m.id==event.id); + if (event.t=="remove") { + if (mIdx>=0) messages.splice(mIdx, 1); // remove item + mIdx=-1; + } else { // add/modify + if (event.t=="add") event.new=true; // new message + if (mIdx<0) mIdx=messages.push(event)-1; + else Object.assign(messages[mIdx], event); + } + require("Storage").writeJSON("messages.json",messages); + // if in app, process immediately + if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]); + // ok, saved now - we only care if it's new + if (event.t!="add") return; + // otherwise load after a delay, to ensure we have all the messages + if (exports.messageTimeout) clearTimeout(exports.messageTimeout); + exports.messageTimeout = setTimeout(function() { + exports.messageTimeout = undefined; + // if we're in a clock or it's important, go straight to messages app + if (Bangle.CLOCK || event.important) return load("messages.app.js"); + if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know + WIDGETS.messages.newMessage(); + }, 500); +} diff --git a/core b/core index 5ef454a1a..59f80bb52 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 5ef454a1acce54f6420015b519a7ecf461f9bc37 +Subproject commit 59f80bb52a38da12cb272f9106cb3951b49dab2e diff --git a/modules/Layout.js b/modules/Layout.js index 8a5b0a0a5..c7d44ab9b 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -29,7 +29,9 @@ layoutObject has: * `undefined` - blank, can be used for padding * `"txt"` - a text label, with value `label` and `r` for text rotation. 'font' is required * `"btn"` - a button, with value `label` and callback `cb` - * `"img"` - an image where `src` is an image, or a function which is called to return an image to draw + optional `src` specifies an image (like img) in which case label is ignored + * `"img"` - an image where `src` is an image, or a function which is called to return an image to draw. + optional `scale` specifies if image should be scaled up or not * `"custom"` - a custom block where `render(layoutObj)` is called to render * `"h"` - Horizontal layout, `c` is an array of more `layoutObject` * `"v"` - Veritical layout, `c` is an array of more `layoutObject` @@ -85,6 +87,7 @@ function Layout(layout, options) { this.lazy = options.lazy || false; var btnList; + Bangle.setUI(); // remove all existing input handlers if (process.env.HWVERSION!=2) { // no touchscreen, find any buttons in 'layout' btnList = []; @@ -157,6 +160,7 @@ function Layout(layout, options) { } } if (process.env.HWVERSION==2) { + // Handler for touch events function touchHandler(l,e) { if (l.type=="btn" && l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) { @@ -245,10 +249,8 @@ Layout.prototype.render = function (l) { g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); } }, "btn":function(l){ - var x = l.x+(0|l.pad); - var y = l.y+(0|l.pad); - var w = l.w-(l.pad<<1); - var h = l.h-(l.pad<<1); + var x = l.x+(0|l.pad), y = l.y+(0|l.pad), + w = l.w-(l.pad<<1), h = l.h-(l.pad<<1); var poly = [ x,y+4, x+4,y, @@ -259,10 +261,12 @@ Layout.prototype.render = function (l) { x+4,y+h-1, x,y+h-5, x,y+4 - ]; - g.setColor(l.selected?g.theme.bgH:g.theme.bg2).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly).setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); + ], bg = l.selected?g.theme.bgH:g.theme.bg2; + g.setColor(bg).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly); + if (l.src) g.setBgColor(bg).drawImage("function"==typeof l.src?l.src():l.src, l.x + 10 + (0|l.pad), l.y + 8 + (0|l.pad)); + else g.setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); }, "img":function(l){ - g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad)); + g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad), l.scale?{scale:l.scale}:undefined); }, "custom":function(l){ l.render(l); },"h":function(l) { l.c.forEach(render); }, @@ -363,12 +367,13 @@ Layout.prototype.update = function() { l._w = m.width; l._h = m.height; } }, "btn": function(l) { - l._h = 32; - l._w = 20 + l.label.length*12; + var m = l.src?g.imageMetrics("function"==typeof l.src?l.src():l.src):g.setFont("6x8",2).stringMetrics(l.label); + l._h = 16 + m.height; + l._w = 20 + m.width; }, "img": function(l) { - var m = g.imageMetrics("function"==typeof l.src?l.src():l.src); // get width and height out of image - l._w = m.width; - l._h = m.height; + var m = g.imageMetrics("function"==typeof l.src?l.src():l.src), s=l.scale||1; // get width and height out of image + l._w = m.width*s; + l._h = m.height*s; }, "": function(l) { // size should already be set up in width/height l._w = 0; From a530c7e391a27d3c47469dbcfa9e1dc4e3b51398 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 4 Nov 2021 19:38:06 +0000 Subject: [PATCH 0491/1062] more icons --- apps/ios/boot.js | 4 +++- apps/messages/app.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/ios/boot.js b/apps/ios/boot.js index 69e5e2a26..c3ccb9275 100644 --- a/apps/ios/boot.js +++ b/apps/ios/boot.js @@ -56,7 +56,9 @@ E.on('notify',msg=>{ var appNames = { "com.netflix.Netflix" : "Netflix", "com.google.ios.youtube" : "YouTube", - "com.google.hangouts" : "Hangouts" + "com.google.hangouts" : "Hangouts", + "com.skype.SkypeForiPad": "Skype", + "com.atebits.Tweetie2": "Twitter" // could also use NRF.ancsGetAppInfo(msg.appId) here }; var unicodeRemap = { diff --git a/apps/messages/app.js b/apps/messages/app.js index 2fb0c613b..749cf3c73 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -41,6 +41,7 @@ function getMessageImage(msg) { if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); + if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA"); if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); if (msg.id=="back") return getBackImage(); return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A=="); From 0a527c08c60515bf7b1e0842179b4a22e0b65696 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 4 Nov 2021 19:38:24 +0000 Subject: [PATCH 0492/1062] fix sanity check --- bin/sanitycheck.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index 9c5f4c916..a84d26efd 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -121,8 +121,8 @@ apps.forEach((app,appIdx) => { if (app.dependencies) { if (("object"==typeof app.dependencies) && !Array.isArray(app.dependencies)) { Object.keys(app.dependencies).forEach(dependency => { - if (app.dependencies[dependency]!="type") - ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' right now`); + if (!["type","app"].includes(app.dependencies[dependency])) + ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' or 'app' right now`); }); } else ERROR(`App ${app.id} 'dependencies' must be an object`); From 7a7175fa483e4e9fcd5912d83e9519274d67b4f0 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 4 Nov 2021 16:00:41 -0700 Subject: [PATCH 0493/1062] Update custom.html --- apps/schoolCalendar/custom.html | 106 +++++++------------------------- 1 file changed, 21 insertions(+), 85 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 9c527591e..83d1bb939 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,99 +1,35 @@ - -
-

Create your events on the current week. Keep in note that your events repeat weekly.

-

One you have created your events, Click

-

All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.

-
- - - + + + +

Some text:

+

Click

+ + - - -
From baa36bdd91b9e89e4eb8ae3d4febc8e296fac211 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 8 Nov 2021 09:42:40 +0000 Subject: [PATCH 0494/1062] about --- apps.json | 2 +- apps/about/ChangeLog | 1 + apps/about/app-bangle2.js | 3 ++- apps/android/app.js | 2 +- apps/ios/app.js | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps.json b/apps.json index 156b6e5b0..eb7289153 100644 --- a/apps.json +++ b/apps.json @@ -132,7 +132,7 @@ { "id": "about", "name": "About", - "version": "0.10", + "version": "0.11", "description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers", "icon": "app.png", "tags": "tool,system", diff --git a/apps/about/ChangeLog b/apps/about/ChangeLog index 9557e448d..03e920a9a 100644 --- a/apps/about/ChangeLog +++ b/apps/about/ChangeLog @@ -8,3 +8,4 @@ 0.08: Make about (mostly) work on non-240px screens 0.09: Actual Bangle.js 1 pixels as of 13 Oct 2021 0.10: Added separate Bangle.js 2 file with Bangle.js 2 kickstarter pixels (as of 28 Oct 2021) +0.11: Bangle.js2: New pixels, btn1 to exit diff --git a/apps/about/app-bangle2.js b/apps/about/app-bangle2.js index 8a0be9f3d..32e5bafae 100644 --- a/apps/about/app-bangle2.js +++ b/apps/about/app-bangle2.js @@ -6,7 +6,7 @@ var ENV = process.env; var MEM = process.memory(); var s = require("Storage"); -var img = atob("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"); +var img = atob("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"); var imgHeight = g.imageMetrics(img).height; var imgScroll = Math.floor(Math.random()*imgHeight); @@ -69,3 +69,4 @@ function drawImage() { // TODO: a nice little animation before setTimeout(drawInfo, 1000); +setWatch(_=>load(), BTN1); diff --git a/apps/android/app.js b/apps/android/app.js index 5c5c7ddaf..b210886fd 100644 --- a/apps/android/app.js +++ b/apps/android/app.js @@ -1,2 +1,2 @@ // Config app not implemented yet -load("messages.app.js"); +setTimeout(()=>load("messages.app.js"),10); diff --git a/apps/ios/app.js b/apps/ios/app.js index 5c5c7ddaf..b210886fd 100644 --- a/apps/ios/app.js +++ b/apps/ios/app.js @@ -1,2 +1,2 @@ // Config app not implemented yet -load("messages.app.js"); +setTimeout(()=>load("messages.app.js"),10); From 4a3ef4829c8036394a21efc274c8d4484d21c82c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 8 Nov 2021 10:03:33 +0000 Subject: [PATCH 0495/1062] alarm 0.14: Order of 'back' menu item --- apps.json | 8 ++++---- apps/alarm/ChangeLog | 1 + apps/alarm/app.js | 6 +++--- bin/firmwaremaker_c.js | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/apps.json b/apps.json index eb7289153..a700dc58c 100644 --- a/apps.json +++ b/apps.json @@ -94,8 +94,7 @@ {"name":"health.img","url":"app-icon.js","evaluate":true}, {"name":"health.boot.js","url":"boot.js"}, {"name":"health","url":"lib.js"} - ], - "sortorder": -2 + ] }, { "id": "launch", @@ -142,13 +141,14 @@ {"name":"about.app.js","url":"app-bangle1.js","supports": ["BANGLEJS"]}, {"name":"about.app.js","url":"app-bangle2.js","supports": ["BANGLEJS2"]}, {"name":"about.img","url":"app-icon.js","evaluate":true} - ] + ], + "sortorder": -4 }, { "id": "alarm", "name": "Default Alarm & Timer", "shortName": "Alarms", - "version": "0.13", + "version": "0.14", "description": "Set and respond to alarms and timers", "icon": "app.png", "tags": "tool,alarm,widget", diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index fce54f273..d129e9f9f 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -12,3 +12,4 @@ 0.12: Fix widget for bangle 2, now uses theme Widgets now shown on Alarm screen 0.13: Alarm widget state now updates when setting/resetting an alarm +0.14: Order of 'back' menu item diff --git a/apps/alarm/app.js b/apps/alarm/app.js index fdb7784f7..53c7154bc 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -33,6 +33,7 @@ function getCurrentHr() { function showMainMenu() { const menu = { '': { 'title': 'Alarm/Timer' }, + '< Back' : ()=>{load();}, 'New Alarm': ()=>editAlarm(-1), 'New Timer': ()=>editTimer(-1) }; @@ -48,7 +49,7 @@ function showMainMenu() { else editAlarm(idx); }; }); - menu['< Back'] = ()=>{load();}; + if (WIDGETS["alarm"]) WIDGETS["alarm"].reload(); return E.showMenu(menu); } @@ -70,6 +71,7 @@ function editAlarm(alarmIndex) { } const menu = { '': { 'title': 'Alarm' }, + '< Back' : showMainMenu, 'Hours': { value: hrs, onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' @@ -119,7 +121,6 @@ function editAlarm(alarmIndex) { showMainMenu(); }; } - menu['< Back'] = showMainMenu; return E.showMenu(menu); } @@ -174,7 +175,6 @@ function editTimer(alarmIndex) { showMainMenu(); }; } - menu['< Back'] = showMainMenu; return E.showMenu(menu); } diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 28fbd2f05..eef54f226 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -29,8 +29,8 @@ if (DEVICE=="BANGLEJS") { } else if (DEVICE=="BANGLEJS2") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install - "boot","launch","antonclk","setting","health", - "about","alarm","widlock","widbat","widbt","widid" + "boot","launch","antonclk","setting", + "about","alarm","health","widlock","widbat","widbt","widid" ]; } else { console.log("USAGE:"); From 9022b2811ad107e94bb9d11eb43ef97d9020ebbc Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Tue, 9 Nov 2021 23:24:05 +0800 Subject: [PATCH 0496/1062] Update app.js --- apps/authentiwatch/app.js | 68 ++++++++++++--------------------------- 1 file changed, 21 insertions(+), 47 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index fe22332cc..d5a8352cb 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -1,11 +1,11 @@ const tokenentryheight = 46; // Hash functions const crypto = require("crypto"); -const sha1 = crypto.SHA1; -const sha224 = crypto.SHA224; -const sha256 = crypto.SHA256; -const sha384 = crypto.SHA384; -const sha512 = crypto.SHA512; +const algos = { + "SHA512":{sha:crypto.SHA512,retsz:64,blksz:128}, + "SHA256":{sha:crypto.SHA256,retsz:32,blksz:64 }, + "SHA1" :{sha:crypto.SHA1 ,retsz:20,blksz:64 }, +}; var tokens = require("Storage").readJSON("authentiwatch.json", true) || [ {algorithm:"SHA512",digits:8,period:60,secret:"aaaa aaaa aaaa aaaa",label:"AgAgAg"}, @@ -15,11 +15,6 @@ var tokens = require("Storage").readJSON("authentiwatch.json", true) || [ {algorithm:"SHA1",digits:8,period:30,secret:"zzzz zzzz zzzz zzzz",label:"ZgZgZg"}, ]; -Graphics.prototype.setFontInconsolata = function(scale) { - // Actual height 21 (25 - 5) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAADgAAADgAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAHwAAAfgAAB+AAAH4AAA/gAAD+AAAPwAAA/AAAB8AAABwAAAAAAAAAAAAAAAAAAAA/gAAH/8AAf//AA+A/gA4BzgAwHhgAwPBgAwcBgA54DgA/wPgAf//AAH/8AAA/gAAAAAAAAAAAAAAAAAIAAAAMAAAAYAAAAYAAAA4AAAA///gA///gA///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAOABgAeADgA4AHgAwAPgAwAdgAwA5gAwDxgAwHhgA8fBgAf+BgAP4BgADgBgAAAAAAAAAAAAAAAAAADAAYAHAA4EDgAwMBgAwMBgAwMBgAwOBgA4+BgA///gAfz/AAHB+AAAAAAAAAAAAAAAAAAAwAAABwAAAHwAAAPwAAA+wAAB4wAAHwwAAPAwAA///gA///gA///gAAAwAAAAwAAAAQAAAAAAAAAAAAP8GAA/8HAA/8DgAwYBgAwYBgAwYBgAwYBgAwYBgAwcHgAwP/AAwH+AAAD4AAAAAAAAAAAAAHAAAD/8AAP/+AAfufAA8cDgA4YBgAwYBgAwYBgAwYBgAwcHgA4P/AAYH+AAAB4AAAAAAAAAAAAAAAAAwAAAAwAAAAwAAAAwAHgAwA/gAwH/gAwf4AAz/AAA/4AAA/AAAA8AAAAgAAAAAAAAAAAAAAAAcAAHB+AAfj/AA/3DgA4+BgAwcBgAwcBgAwcBgAw+BgA/3DgAfz/AAPB+AAAA8AAAAAAAAAAAABAAAAP4BAAf8DgA8eDgAwGBgAwGBgAwGBgAwGBgA4GDgA+OfAAf/+AAP/4AAB/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYBgAAcDgAAcDgAAYBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="), 46, 15, 30+(scale<<8)+(1<<16)); -}; - // QR Code Text // // Example: @@ -58,42 +53,21 @@ function b32decode(seedstr) { return retbuf; } function do_hmac(key, message, algo) { - var sha, retsz, blksz; - if (algo == "SHA512") { - sha = sha512; - retsz = 64; - blksz = 128; - } else if (algo == "SHA384") { - sha = sha384; - retsz = 48; - blksz = 128; - } else if (algo == "SHA256") { - sha = sha256; - retsz = 32; - blksz = 64; - } else if (algo == "SHA224") { - sha = sha224; - retsz = 28; - blksz = 64; - } else { - sha = sha1; - retsz = 20; - blksz = 64; - } + var a = algos[algo]; // RFC2104 - if (key.length > blksz) { - key = sha(key); + if (key.length > a.blksz) { + key = a.sha(key); } - var istr = new Uint8Array(blksz + message.length); - var ostr = new Uint8Array(blksz + retsz); - for (var i = 0; i < blksz; ++i) { + var istr = new Uint8Array(a.blksz + message.length); + var ostr = new Uint8Array(a.blksz + a.retsz); + for (var i = 0; i < a.blksz; ++i) { var c = (i < key.length) ? key[i] : 0; istr[i] = c ^ 0x36; ostr[i] = c ^ 0x5C; } - istr.set(message, blksz); - ostr.set(sha(istr), blksz); - var ret = sha(ostr); + istr.set(message, a.blksz); + ostr.set(a.sha(istr), a.blksz); + var ret = a.sha(ostr); // RFC4226 dynamic truncation var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4); return v.getUint32(0) & 0x7FFFFFFF; @@ -135,24 +109,24 @@ function drawToken(id, r) { // current token g.setColor(g.theme.fgH); g.setBgColor(g.theme.bgH); - g.setFont6x15(1); + g.setFont("Vector", 16); // center just below top line g.setFontAlign(0, -1, 0); - ylabel = y1 + 2; + ylabel = y1; } else { g.setColor(g.theme.fg); g.setBgColor(g.theme.bg); - g.setFont6x15(2); + g.setFont("Vector", 30); // center in box g.setFontAlign(0, 0, 0); ylabel = (y1 + y2) / 2; } g.clearRect(x1, y1, x2, y2); - g.drawString(tokens[id].label, x2 / 2, ylabel, false); + g.drawString(tokens[id].label, (x1 + x2) / 2, ylabel, false); if (id == state.curtoken) { // digits just below label - g.setFontInconsolata(1); - g.drawString(state.otp, x2 / 2, y1 + 12, false); + g.setFont("Vector", 30); + g.drawString(state.otp, (x1 + x2) / 2, y1 + 16, false); // draw progress bar let xr = Math.floor(Bangle.appRect.w * state.rem / tokens[id].period); g.fillRect(x1, y2 - 4, xr, y2 - 1); @@ -203,7 +177,7 @@ function draw() { state.drawtimer = setTimeout(draw, 1000); } } else { - g.setFont6x15(2); + g.setFont("Vector", 30); g.setFontAlign(0, 0, 0); g.drawString("No tokens", Bangle.appRect.x + Bangle.appRect.w / 2,Bangle.appRect.y + Bangle.appRect.h / 2, false); } From 876666fa0f1b71c51bcce349532583c8f414ff6d Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Tue, 9 Nov 2021 23:24:47 +0800 Subject: [PATCH 0497/1062] Update interface.html --- apps/authentiwatch/interface.html | 150 +++++++++++++++++++----------- 1 file changed, 98 insertions(+), 52 deletions(-) diff --git a/apps/authentiwatch/interface.html b/apps/authentiwatch/interface.html index ab16bd2dc..79013d27a 100644 --- a/apps/authentiwatch/interface.html +++ b/apps/authentiwatch/interface.html @@ -2,6 +2,7 @@ + + + + From 36d156a8a9f9c17b7f14579a64ed6ece455e1456 Mon Sep 17 00:00:00 2001 From: jonathan Date: Thu, 11 Nov 2021 18:56:18 +0000 Subject: [PATCH 0509/1062] BLE GATT Battery Service works on Bangle JS 2 --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 2e122159d..c4b0eb41a 100644 --- a/apps.json +++ b/apps.json @@ -2852,7 +2852,7 @@ "icon": "bluetooth.png", "type": "bootloader", "tags": "battery,ble,bluetooth,gatt", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ {"name":"gattbat.boot.js","url":"boot.js"} From 4b3a9a09e2e6f7cc4887c002e5699c6872d4119b Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 12 Nov 2021 09:30:28 +0000 Subject: [PATCH 0510/1062] health: 0.06: Fix daily health summary for movement --- apps.json | 2 +- apps/health/ChangeLog | 1 + apps/health/boot.js | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 89d06093a..70506bdd8 100644 --- a/apps.json +++ b/apps.json @@ -82,7 +82,7 @@ { "id": "health", "name": "Health Tracking", - "version": "0.05", + "version": "0.06", "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)", "icon": "app.png", "tags": "tool,system,health", diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 808282b64..ef1b1bffc 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -4,3 +4,4 @@ 0.04: Add HRM graph view Don't restart HRM when changing apps if we've already got a good BPM value 0.05: Fix daily summary calculation +0.06: Fix daily health summary for movement (a line got deleted!) diff --git a/apps/health/boot.js b/apps/health/boot.js index a8d78f685..386d75833 100644 --- a/apps/health/boot.js +++ b/apps/health/boot.js @@ -79,5 +79,6 @@ Bangle.on("health", health => { if (health.bpmCnt) health.bpm /= health.bpmCnt; if (health.movCnt) + health.movement /= health.movCnt; require("Storage").write(fn, getRecordData(health), sumPos, DB_FILE_LEN); }); From 3ed953fcd21b3234ecc182b9cc0a7d161b91f7cf Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Sat, 13 Nov 2021 00:58:50 +0800 Subject: [PATCH 0511/1062] Add HOTP support --- apps/authentiwatch/app.js | 72 ++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/apps/authentiwatch/app.js b/apps/authentiwatch/app.js index d5a8352cb..180035e14 100644 --- a/apps/authentiwatch/app.js +++ b/apps/authentiwatch/app.js @@ -10,7 +10,7 @@ const algos = { var tokens = require("Storage").readJSON("authentiwatch.json", true) || [ {algorithm:"SHA512",digits:8,period:60,secret:"aaaa aaaa aaaa aaaa",label:"AgAgAg"}, {algorithm:"SHA1",digits:6,period:30,secret:"bbbb bbbb bbbb bbbb",label:"BgBgBg"}, - {algorithm:"SHA1",digits:6,period:30,secret:"cccc cccc cccc cccc",label:"CgCgCg"}, + {algorithm:"SHA1",digits:6,period:-30,secret:"cccc cccc cccc cccc",label:"CgCgCg"}, {algorithm:"SHA1",digits:6,period:60,secret:"yyyy yyyy yyyy yyyy",label:"YgYgYg"}, {algorithm:"SHA1",digits:8,period:30,secret:"zzzz zzzz zzzz zzzz",label:"ZgZgZg"}, ]; @@ -72,21 +72,27 @@ function do_hmac(key, message, algo) { var v = new DataView(ret, ret[ret.length - 1] & 0x0F, 4); return v.getUint32(0) & 0x7FFFFFFF; } -function hotp_timed(seed, digits, period, algo) { - // RFC6238 +function hotp(token) { + var tick; var d = new Date(); - var seconds = Math.floor(d.getTime() / 1000); - var tick = Math.floor(seconds / period); + if (token.period > 0) { + // RFC6238 - timed + var seconds = Math.floor(d.getTime() / 1000); + tick = Math.floor(seconds / token.period); + } else { + // RFC4226 - counter + tick = -token.period; + } var msg = new Uint8Array(8); var v = new DataView(msg.buffer); v.setUint32(0, tick >> 16 >> 16); v.setUint32(4, tick & 0xFFFFFFFF); - var hash = do_hmac(b32decode(seed), msg, algo.toUpperCase()); - var ret = "" + hash % Math.pow(10, digits); - while (ret.length < digits) { + var hash = do_hmac(b32decode(token.secret), msg, token.algorithm.toUpperCase()); + var ret = "" + hash % Math.pow(10, token.digits); + while (ret.length < token.digits) { ret = "0" + ret; } - return {hotp:ret, next:(tick + 1) * period * 1000}; + return {hotp:ret, next:((token.period > 0) ? ((tick + 1) * token.period * 1000) : d.getTime() + 30000)}; } var state = { @@ -94,7 +100,8 @@ var state = { curtoken:-1, nextTime:0, otp:"", - rem:0 + rem:0, + hide:0 }; function drawToken(id, r) { @@ -143,17 +150,28 @@ function drawToken(id, r) { } function draw() { + var d = new Date(); if (state.curtoken != -1) { var t = tokens[state.curtoken]; - var d = new Date(); if (d.getTime() > state.nextTime) { - try { - var r = hotp_timed(t.secret, t.digits, t.period, t.algorithm); - state.nextTime = r.next; - state.otp = r.hotp; - } catch (err) { + if (state.hide == 0) { + // auto-hide the current token + state.curtoken = -1; state.nextTime = 0; - state.otp = "Not supported"; + } else { + // time to generate a new token + try { + var r = hotp(t); + state.nextTime = r.next; + state.otp = r.hotp; + if (t.period <= 0) { + state.hide = 1; + } + } catch (err) { + state.nextTime = 0; + state.otp = "Not supported"; + } + state.hide--; } } state.rem = Math.max(0, Math.floor((state.nextTime - d.getTime()) / 1000)); @@ -164,17 +182,25 @@ function draw() { var y = id * tokenentryheight + Bangle.appRect.y - state.listy; while (id < tokens.length && y < Bangle.appRect.y2) { drawToken(id, {x:Bangle.appRect.x, y:y, w:Bangle.appRect.w, h:tokenentryheight}); - if (id == state.curtoken && state.nextTime != 0) { + if (id == state.curtoken && (tokens[id].period <= 0 || state.nextTime != 0)) { drewcur = true; } id += 1; y += tokenentryheight; } if (drewcur) { + // the current token has been drawn - draw it again in 1sec if (state.drawtimer) { clearTimeout(state.drawtimer); } - state.drawtimer = setTimeout(draw, 1000); + state.drawtimer = setTimeout(draw, (tokens[state.curtoken].period > 0) ? 1000 : state.nexttime - d.getTime()); + if (tokens[state.curtoken].period <= 0) { + state.hide = 0; + } + } else { + // de-select the current token if it is scrolled out of view + state.curtoken = -1; + state.nexttime = 0; } } else { g.setFont("Vector", 30); @@ -202,6 +228,7 @@ function onTouch(zone, e) { } state.nextTime = 0; state.curtoken = id; + state.hide = 2; draw(); } } @@ -221,6 +248,13 @@ function onSwipe(e) { if (e == 1) { Bangle.showLauncher(); } + if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) { + tokens[state.curtoken].period--; + require("Storage").writeJSON("authentiwatch.json", tokens); + state.nextTime = 0; + state.hide = 2; + draw(); + } } Bangle.on('touch', onTouch); From 1b9bc2a8c2df73a52586a852815f6158d020bb1d Mon Sep 17 00:00:00 2001 From: Andrew Gregory Date: Sat, 13 Nov 2021 00:59:34 +0800 Subject: [PATCH 0512/1062] Add HOTP support --- apps/authentiwatch/interface.html | 67 +++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/apps/authentiwatch/interface.html b/apps/authentiwatch/interface.html index 823047d8b..b0ac7de45 100644 --- a/apps/authentiwatch/interface.html +++ b/apps/authentiwatch/interface.html @@ -19,6 +19,7 @@ body.select div#tokens,body.editing div#edit,body.scanning div#scan,body.showqr #edittoken.showadv #advbtn button:before,#edittoken.showadv #advbtn button:after{content:"\25b2"} button{height:3em} table button{width:100%} +form.totp tr.hotp,form.hotp tr.totp{display:none} @@ -30,7 +31,9 @@ table button{width:100%} + + + + +
From c8ad92a3dcb7b8a0df83f2e720b6730dd785e829 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:20:33 -0800 Subject: [PATCH 0677/1062] Update apps.json --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 4ff587714..4d16a3cef 100644 --- a/apps.json +++ b/apps.json @@ -3518,10 +3518,10 @@ "shortName":"SCalendar", "icon": "CalenderLogo.png", "version":"0.01", - "description": "A simple calendar that you can see your upcoming events. Keep in note that your events reapeat weekly.", + "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", "tags": "tool", "readme": "README.md", - "custom":"custom.html", + "custom":"interface.html", "storage": [ {"name":"schoolCalendar.app.js","url":"app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} From 3a3962bfdc0365e75f8804b61293ec91084f6698 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:30:12 -0800 Subject: [PATCH 0678/1062] Rename custom.html to interface.html --- apps/schoolCalendar/{custom.html => interface.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/schoolCalendar/{custom.html => interface.html} (100%) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/interface.html similarity index 100% rename from apps/schoolCalendar/custom.html rename to apps/schoolCalendar/interface.html From 993bb7298c8fbc9348e6668b70a555c51dc54a09 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:31:41 -0800 Subject: [PATCH 0679/1062] Rename interface.html to custom.html --- apps/schoolCalendar/{interface.html => custom.html} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/schoolCalendar/{interface.html => custom.html} (100%) diff --git a/apps/schoolCalendar/interface.html b/apps/schoolCalendar/custom.html similarity index 100% rename from apps/schoolCalendar/interface.html rename to apps/schoolCalendar/custom.html From 1a53e8d34af89f0e6c889653961350a898deea81 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:33:43 -0800 Subject: [PATCH 0680/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 8eac0baa1..e9ca2d3da 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -105,7 +105,7 @@ g.drawString(txt,120,120); // send finished app (in addition to contents of app.json) sendCustomizedApp({ storage:[ - {"name":"customFile.app.json",url:"app.js",content:content} + {"name":"customFile.app.json",url:"app.js",content:content}, {name:"myapp.app.js", url:"app.js", content:app}, ] }); From 98048dc23cb1caf3aafbf1d1ecfa461ba5943925 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:37:23 -0800 Subject: [PATCH 0681/1062] Update apps.json --- apps.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 4d16a3cef..32b2ba209 100644 --- a/apps.json +++ b/apps.json @@ -3521,10 +3521,11 @@ "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", "tags": "tool", "readme": "README.md", - "custom":"interface.html", + "custom":"custom.html", "storage": [ {"name":"schoolCalendar.app.js","url":"app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} ], "data": [ {"name":"app.json"} From ba2aee642bf90307757bb85fda6aa0db01bed5e5 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:39:06 -0800 Subject: [PATCH 0682/1062] Update custom.html --- apps/schoolCalendar/custom.html | 84 --------------------------------- 1 file changed, 84 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index e9ca2d3da..83d1bb939 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,88 +1,9 @@ -@@ -1,99 +1,35 @@ - -
-

Create your events on the current week. Keep in note that your events repeat weekly.

-

One you have created your events, Click

-

All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.

-
- - - - - - - -
From 37f4329ec57bc4a96a1b2a0847406f6a05cad520 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:39:49 -0800 Subject: [PATCH 0683/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 32b2ba209..16a768a91 100644 --- a/apps.json +++ b/apps.json @@ -3524,7 +3524,7 @@ "custom":"custom.html", "storage": [ {"name":"schoolCalendar.app.js","url":"app.js"}, - {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} ], "data": [ From 99112920b287568db32c5e43b47cfc2ee0cf71a3 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:47:34 -0800 Subject: [PATCH 0684/1062] Update apps.json --- apps.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps.json b/apps.json index 16a768a91..f5d60d3e9 100644 --- a/apps.json +++ b/apps.json @@ -3525,7 +3525,6 @@ "storage": [ {"name":"schoolCalendar.app.js","url":"app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true}, - {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} ], "data": [ {"name":"app.json"} From dfa404072616d877f2d0aa2e8e1d7a82bbed5dfc Mon Sep 17 00:00:00 2001 From: t0m1o1 <94725994+t0m1o1@users.noreply.github.com> Date: Sat, 20 Nov 2021 20:47:58 +0000 Subject: [PATCH 0685/1062] Stop the swiping coming up if mode is undefined --- apps/swiperclocklaunch/boot.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/swiperclocklaunch/boot.js b/apps/swiperclocklaunch/boot.js index 0a4e617e1..e9b203eee 100644 --- a/apps/swiperclocklaunch/boot.js +++ b/apps/swiperclocklaunch/boot.js @@ -3,7 +3,8 @@ var sui = Bangle.setUI; Bangle.setUI = function(mode, cb) { sui(mode,cb); - if (mode && !mode.startsWith("clock")) return; + if(!mode) return; + if (!mode.startsWith("clock")) return; Bangle.swipeHandler = dir => { if (dir<0) Bangle.showLauncher(); }; Bangle.on("swipe", Bangle.swipeHandler); }; From d871855f8e217b27ea73ed28b389bbc8aea59d62 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:55:58 -0800 Subject: [PATCH 0686/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index f5d60d3e9..d4d3a04fa 100644 --- a/apps.json +++ b/apps.json @@ -3524,7 +3524,7 @@ "custom":"custom.html", "storage": [ {"name":"schoolCalendar.app.js","url":"app.js"}, - {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true}, + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} ], "data": [ {"name":"app.json"} From b108bc45fb8bfa266e4d4ad3e66a9f65cec96c92 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 12:58:24 -0800 Subject: [PATCH 0687/1062] Update custom.html --- apps/schoolCalendar/custom.html | 84 +++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 83d1bb939..8eac0baa1 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,9 +1,88 @@ +@@ -1,99 +1,35 @@ + +
+

Create your events on the current week. Keep in note that your events repeat weekly.

+

One you have created your events, Click

+

All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.

+
+ + + + + + + +
From 9c07800c5ef948a1b9b2071e7bdfe46809fefa1d Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 13:07:38 -0800 Subject: [PATCH 0688/1062] Update custom.html --- apps/schoolCalendar/custom.html | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 8eac0baa1..73da4e6b5 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -30,6 +30,8 @@ + + + + + +

Some text:

+

Click

+ - - - - - -
- + From 58df0a5ca26684b4da84929c8c388ca5c37acabd Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:01:03 -0800 Subject: [PATCH 0693/1062] Update apps.json --- apps.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps.json b/apps.json index d4d3a04fa..c41c1268e 100644 --- a/apps.json +++ b/apps.json @@ -3523,8 +3523,7 @@ "readme": "README.md", "custom":"custom.html", "storage": [ - {"name":"schoolCalendar.app.js","url":"app.js"}, - {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} + {"name":"schoolCalendar.app.js"} ], "data": [ {"name":"app.json"} From dc9db8a4767d4c16d92645131d977263e2468521 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:01:58 -0800 Subject: [PATCH 0694/1062] Update schoolCalendar.js --- apps/schoolCalendar/schoolCalendar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js index 29de1aec4..2b517533d 100644 --- a/apps/schoolCalendar/schoolCalendar.js +++ b/apps/schoolCalendar/schoolCalendar.js @@ -152,8 +152,8 @@ function getScheduleTable() { function findNextScheduleIndex() { var schedule = getScheduleTable(); var currentDate = new Date(); - //var minuteOfWeek = (currentDate.getDay()*3600)+(currentDate.getHours()*60)+currentDate.getMinutes(); - var minuteOfWeek = (4*3600)+(16*60)+0; + var minuteOfWeek = (currentDate.getDay()*3600)+(currentDate.getHours()*60)+currentDate.getMinutes(); + //var minuteOfWeek = (4*3600)+(16*60)+0; var currentPosition; for(currentPosition = 0;currentPosition < schedule.length; currentPosition++){ var scheduleItemStartMinuteOfWeek = schedule[currentPosition].dow*3600 + schedule[currentPosition].eh*60+schedule[currentPosition].em; From 345d9c155135fc4d0a2b8d00b3528b363a584ced Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:02:12 -0800 Subject: [PATCH 0695/1062] Delete schoolCalendar.js --- apps/schoolCalendar/schoolCalendar.js | 400 -------------------------- 1 file changed, 400 deletions(-) delete mode 100644 apps/schoolCalendar/schoolCalendar.js diff --git a/apps/schoolCalendar/schoolCalendar.js b/apps/schoolCalendar/schoolCalendar.js deleted file mode 100644 index 2b517533d..000000000 --- a/apps/schoolCalendar/schoolCalendar.js +++ /dev/null @@ -1,400 +0,0 @@ -require("Font8x12").add(Graphics); -require("Font7x11Numeric7Seg", 2).add(Graphics); - -let nIntervId; - -function redrawScreen() { - layout.render(layout.background); - layout.render(layout.buttons); - draw(); -} - -function updateDay(ffunction,day){ - if(ffunction == 1){ - switch (day) { - case 0: - return "Sunday"; - case 1: - day = "Monday"; - break; - case 2: - day = "Tuesday"; - break; - case 3: - day = "Wednesday"; - break; - case 4: - day = "Thursday"; - break; - case 5: - day = "Friday"; - break; - case 6: - day = "Saturday"; - } - return day; - }else if(ffunction == 2){ - switch (day) { - case 0: - return "Sun"; - case 1: - day = "Mon"; - break; - case 2: - day = "Tue"; - break; - case 3: - day = "Wed"; - break; - case 4: - day = "Thu"; - break; - case 5: - day = "Fri"; - break; - case 6: - day = "Sat"; - } - return day; - }else if(ffunction == 3){ - switch (day) { - case 0: - return "S"; - case 1: - day = "M"; - break; - case 2: - day = "T"; - break; - case 3: - day = "W"; - break; - case 4: - day = "R"; - break; - case 5: - day = "F"; - break; - case 6: - day = "S"; - } - return day; - } -} - -function getScheduleTable() { - let schedule = [ - //Sunday - - //Monday: - {cn: "Biblical Theology", dow:1, sh: 8, sm: 10, eh:9, em: 5, r:"207", t:"Mr. Besaw"}, - {cn: "English", dow:1, sh: 9, sm: 5, eh:10, em: 0, t:"Dr. Wong"}, - {cn: "Break", dow:1, sh: 10, sm: 0, eh:10, em: 10, t:""}, - {cn: "MS Robotics", dow:1, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}, - {cn: "MS Physical Education Boys", dow:1, sh: 11, sm: 0, eh:11, em: 50, r:"GYM", t:"Mr. Mendezona"}, - {cn: "Office Hours Besaw/Nunez", dow:1, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:"Besaw/Nunez"}, - {cn: "Lunch", dow:1, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, - {cn: "Activity Period", dow:1, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, - {cn: "Latin", dow:1, sh: 13, sm: 5, eh:14, em: 0, r:"208", t:"Mrs.Scrivner"}, - {cn: "Algebra 1", dow:1, sh: 14, sm: 0, eh:15, em: 0, r:"204", t:"Mr. Benson"}, - - //Tuesday: - {cn: "Logic", dow:2, sh: 8, sm: 10, eh:9, em: 0, r:"208", t:"Mrs.Scrivner"}, - {cn: "Algebra 1", dow:2, sh: 9, sm: 0, eh:10, em: 0, r:"204", t:"Mr. Benson"}, - {cn: "Chapel", dow:2, sh: 10, sm: 0, eh:10, em: 25, r:"Advisory", t:""}, - {cn: "Break", dow:2, sh: 10, sm: 25, eh:10, em: 35, r:"Outside", t:""}, - {cn: "Advisory Besaw", dow:2, sh: 10, sm: 35, eh:11, em: 0, r:"207", t:"Mr. Besaw"}, - {cn: "MS Robotics", dow:2, sh: 11, sm: 0, eh:11, em: 50, r:"211", t:"Mr. Broyles"}, - {cn: "Office Hours Besaw/Nunez", dow:2, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"}, - {cn: "Lunch", dow:2, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, - {cn: "Activity Period", dow:2, sh: 12, sm: 50, eh:13, em: 5, r:"Outside", t:""}, - {cn: "Medieval Western Civilization", dow:2, sh: 13, sm: 5, eh:14, em: 0, r:"205", t:"Mr. Khule"}, - {cn: "Introductory Biology and Epidemiology", dow:2, sh: 14, sm: 0, eh:15, em: 0, r:"202", t:"Mrs. Brown"}, - - //Wensday: - {cn: "English", dow:3, sh: 9, sm: 0, eh:9, em: 55, r:"206", t:"Dr. Wong"}, - {cn: "Biblical Theology", dow:3, sh: 9, sm: 55, eh:10, em: 50, r:"207", t:"Mr. Besaw"}, - {cn: "Break", dow:3, sh: 10, sm: 50, eh:11, em: 0, r:"Outside", t:"_"}, - {cn: "MS Physical Education Boys", dow:3, sh: 11, sm: 0, eh:11, em: 50, r:"GYM", t:"Mr. Mendezona"}, - {cn: "Office Hours Besaw/Nunez", dow:3, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"}, - {cn: "Lunch", dow:3, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, - {cn: "Activity Period", dow:2, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, - {cn: "Introductory Biology and Epidemiology", dow:3, sh: 13, sm: 0, eh:14, em: 0, r:"202", t:"Mrs. Brown"}, - {cn: "Medieval Western Civilization", dow:3, sh: 14, sm: 0, eh:15, em: 0, r:"205", t:"Mr. Khule"}, - - - //Thursday: - {cn: "Algebra 1", dow:4, sh: 8, sm: 10, eh:9, em: 5, r:"204", t:"Mr. Benson"}, - {cn: "Latin", dow:4, sh: 9, sm: 5, eh:10, em: 0, r:"208", t:"Mrs.Scrivner"}, - {cn: "Break", dow:4, sh: 10, sm: 0, eh:10, em: 10, r:"Outside", t:""}, - {cn: "MS Robotics", dow:4, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}, - {cn: "Advisory Besaw", dow:4, sh: 11, sm: 50, eh:12, em: 25, r:"207", t:"Mr. Besaw"}, - {cn: "Lunch", dow:4, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, - {cn: "Activity Period", dow:4, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, - {cn: "Biblical Theology", dow:4, sh: 13, sm: 5, eh:14, em: 0, r:"207", t:"Mr. Besaw"}, - {cn: "English", dow:4, sh: 14, sm: 0, eh:15, em: 0, r:"206", t:"Dr. Wong"}, - - //Friday: - {cn: "Medieval Western Civilization", dow:5, sh: 8, sm: 10, eh:9, em: 5, r:"205", t:"Mr. Khule"}, - {cn: "Introductory Biology and Epidemiology", dow:5, sh: 9, sm: 5, eh:10, em: 0, r:"202", t:"Mrs. Brown"}, - {cn: "Break", dow:5, sh: 10, sm: 0, eh:10, em: 10, dr:"Outside", t:""}, - {cn: "MS Robotics", dow:5, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}, - {cn: "Office Hours Besaw/Nunez", dow:5, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"}, - {cn: "Lunch", dow:5, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, - {cn: "Activity Period", dow:5, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, - {cn: "Algebra 1", dow:5, sh: 13, sm: 5, eh:14, em: 0, r:"204", t:"Mr. Benson"}, - {cn: "Logic", dow:5, sh: 14, sm: 0, eh:15, em: 0, r:"208", t:"Mrs.Scrivner"}, - //Sataturday: - ]; - return schedule; -} - -function findNextScheduleIndex() { - var schedule = getScheduleTable(); - var currentDate = new Date(); - var minuteOfWeek = (currentDate.getDay()*3600)+(currentDate.getHours()*60)+currentDate.getMinutes(); - //var minuteOfWeek = (4*3600)+(16*60)+0; - var currentPosition; - for(currentPosition = 0;currentPosition < schedule.length; currentPosition++){ - var scheduleItemStartMinuteOfWeek = schedule[currentPosition].dow*3600 + schedule[currentPosition].eh*60+schedule[currentPosition].em; - if(scheduleItemStartMinuteOfWeek > minuteOfWeek) { - return currentPosition; - } - } - return 0; -} - - -function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} - -function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} - -function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} - -function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} - -var currentPositionTable = 0; -var numberOfItemsShown = 8; -//Table Positions: -var rectStart = 45; -var rectEnd = 65; -var rectStartX = 10; -var rectEndX = 210; -//Scences: -LIST = 1; -INFORMATION = 2; -currentStage = LIST; - -function splitter(str, l){ - var strs = []; - while(str.length > l){ - var pos = str.substring(0, l).lastIndexOf(' '); - pos = pos <= 0 ? l : pos; - strs.push(str.substring(0, pos)); - var i = str.indexOf(' ', pos)+1; - if(i < pos || i > pos+l) - i = pos; - str = str.substring(i); - } - strs.push(str); - return strs; -} - -function updateMinutesToCurrentTime(currentMinuteFunction) { - if (currentMinuteFunction<10){ - currentMinuteUpdatedFunction = "0"+currentMinuteFunction; - }else{ - currentMinuteUpdatedFunction = currentMinuteFunction; - } - return currentMinuteUpdatedFunction; -} - -function renderBackground(l) { - g.clearRect(0,0,240,20); - g.drawImage(getBackgroundImage(),110,130,{scale:9,rotate:0}); -} - -function renderTable(l) { - for(var x = 0;x<=numberOfItemsShown;x++){ - g.setColor(255,255,255); - g.drawRect(rectStartX,rectStart+(x*20),rectEndX,rectEnd+(20*x)); - g.setColor(255,205,0); - g.drawRect(rectStartX,rectStart+(2*20),rectEndX,rectEnd+(2*20)); - g.setColor(255,0,0); - g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); - } -} - -function renderTableText(l){ - var foundNumber = findNextScheduleIndex(); - var foundSchedule = getScheduleTable(); - var scheduleHourUpdated; - var scheduleMinuteUpdated; - var beforeFoundNumber = foundNumber - 2; - for(var x = 0;x<=numberOfItemsShown;x++){ - var currentNumber = beforeFoundNumber + x; - if (beforeFoundNumber + x < 0) { - currentNumber = foundSchedule.length + beforeFoundNumber + x; - } else if (beforeFoundNumber + x > foundSchedule.length - 1) { - currentNumber = beforeFoundNumber + x - foundSchedule.length; - } - - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm); - scheduleHourUpdatedStart = foundSchedule[currentNumber].sh; - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em); - scheduleHourUpdatedEnd = foundSchedule[currentNumber].eh; - scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20); - if(foundSchedule[currentNumber].cn.length >= 15){ - scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20)+"..."; - } - schduleDay = updateDay(3,foundSchedule[currentNumber].dow); - g.setFont("8x12"); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(x*20)); - } -} - -function buttonsF(l){ - if(currentStage == LIST){ - g.drawImage(getDotIcon(),223.5,115); - }else{ - g.drawImage(getMenuIcon(),223.5,115); - } - g.drawImage(getUpArrow(),225,30); - g.drawImage(getDownArrow(),225,215); -} - -function draw() { - var currentDate = new Date(); - var currentDayOfWeek = currentDate.getDay(); - var currentHour = currentDate.getHours(); - var currentMinute = currentDate.getMinutes(); - var currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); - if (layout) { - if(currentStage == LIST){ - layout.time.label = currentHour+":"+currentMinuteUpdated; - layout.time.x = 147; - layout.time.y = 10; - layout.render(layout.table); - layout.render(layout.tableText); - logDebug("Rendered"+currentPositionTable); - }else{ - layout.time.label = currentHour+":"+currentMinuteUpdated; - layout.time.x = 147; - layout.time.y = 10; - layout.render(layout.info); - logDebug("Rendered"+currentPositionTable); - } - g.clearRect(150,0,220,35); - layout.render(layout.time); - } -} - -function RedRectDown() { - if(currentPositionTable > 0){ - currentPositionTable -= 1; - if(currentStage == INFORMATION){ - redrawScreen(); - }else{ - draw(); - } - } -} - -function RedRectUp() { - if(currentPositionTable < numberOfItemsShown){ - currentPositionTable += 1; - if(currentStage == INFORMATION){ - redrawScreen(); - }else{ - draw(); - } - } -} - -function renderMiniBackground(l){ - for(var i = 233;i<=240;i++){ - g.drawImage(getBackgroundImage(),i,123,{scale:10,rotate:0}); - } -} - -function renderLoading(l){ - g.setFont("8x12"); - g.drawString("Loading...",240/2-20,240/2-20); -} - -function renderInformation(l){ - var foundNumber = findNextScheduleIndex(); - var foundSchedule = getScheduleTable(); - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); - scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); - scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; - scheduleDay = updateDay(1,foundSchedule[((foundNumber-2)+currentPositionTable)].dow); - g.setColor(255,255,255); - g.setFont("8x12",2); - var splitClassNames = splitter(foundSchedule[((foundNumber-2)+currentPositionTable)].cn, 15); - var currentY = 5; - for (var j=0; j < splitClassNames.length; j++) { - g.drawString(splitClassNames[j],13,currentY+50); - currentY = currentY + 25; - } - g.setFont("8x12"); - g.drawString(schduleDay,13,currentY+50); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); -} - -var Layout = require("Layout"); -var layout = new Layout( - {type:"h", c: [ - {type:"custom", render:renderTableText, id:"tableText"}, - {type:"custom", render:buttonsF, id:"buttons"}, - {type:"custom", render:renderBackground, id:"background"}, - {type:"custom", render:renderTable, id:"table"}, - {type:"custom", render:renderMiniBackground, id:"miniBackground"}, - {type:"custom", render:renderLoading, id:"loading"}, - {type:"custom", render:renderInformation, id:"info"}, - {type:"txt", font:"7x11Numeric7Seg:2", label:"00:00", id:"time"}, - ]}, - {type:"v", c:[ - ]}, - {btns:[ - {label:"", cb: RedRectUp()}, - {label:"", cb: l=>print("Two")}, - {label:"", cb: RedRectDown()} -]}); - - -function getBackgroundImage() {return require("heatshrink").decompress(atob("j0ZyEKIf4A4gIB6gQB6gYB6ggB6goB6gwB6g4B6hAABAYIBHBZIVLAK8IhIBXgAThhQB6hYB6hgB6hoB6hwB6h4B6iAB6iIB6iQBHiAJOB54XSiYB6igB6ioB6iwB6i4B5A="));} - -function logDebug(message) {console.log(message);} - -function changeScene(){ - layout.render(layout.buttons); - if(currentStage == INFORMATION){ - currentStage = LIST; - nIntervId = setInterval(redrawScreen, 100000); - }else if(currentStage == LIST){ - currentStage = INFORMATION; - clearInterval(); - } - layout.render(layout.background); - layout.render(layout.buttons); - draw(); -} - -// timeout used to update every minute -var drawTimeout; - -setInterval(draw, 15000); - - -setWatch(RedRectUp, BTN3, { repeat:true, edge:'rising', debounce : 50 }); -setWatch(RedRectDown, BTN1, { repeat:true, edge:'rising', debounce : 50 }); -setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); - -layout.update(); -layout.render(layout.loading); -layout.render(layout.background); -layout.render(layout.buttons); - -draw(); From e965bd7d962108a2944824eb6579fce435876966 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:02:43 -0800 Subject: [PATCH 0696/1062] Update custom.html --- apps/schoolCalendar/custom.html | 405 +++++++++++++++++++++++++++++++- 1 file changed, 400 insertions(+), 5 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index f26deb61c..36e8ad7c7 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -16,11 +16,406 @@ var text = document.getElementById("mytext").value; // build the app's text using a templated String var app = ` -var txt = ${JSON.stringify(text)}; -g.clear(1); -g.setFontAlign(0,0); -g.setFont("6x8"); -g.drawString(txt,120,120); +require("Font8x12").add(Graphics); +require("Font7x11Numeric7Seg", 2).add(Graphics); + +let nIntervId; + +function redrawScreen() { + layout.render(layout.background); + layout.render(layout.buttons); + draw(); +} + +function updateDay(ffunction,day){ + if(ffunction == 1){ + switch (day) { + case 0: + return "Sunday"; + case 1: + day = "Monday"; + break; + case 2: + day = "Tuesday"; + break; + case 3: + day = "Wednesday"; + break; + case 4: + day = "Thursday"; + break; + case 5: + day = "Friday"; + break; + case 6: + day = "Saturday"; + } + return day; + }else if(ffunction == 2){ + switch (day) { + case 0: + return "Sun"; + case 1: + day = "Mon"; + break; + case 2: + day = "Tue"; + break; + case 3: + day = "Wed"; + break; + case 4: + day = "Thu"; + break; + case 5: + day = "Fri"; + break; + case 6: + day = "Sat"; + } + return day; + }else if(ffunction == 3){ + switch (day) { + case 0: + return "S"; + case 1: + day = "M"; + break; + case 2: + day = "T"; + break; + case 3: + day = "W"; + break; + case 4: + day = "R"; + break; + case 5: + day = "F"; + break; + case 6: + day = "S"; + } + return day; + } +} + +function getScheduleTable() { + let schedule = [ + //Sunday + + //Monday: + {cn: "Biblical Theology", dow:1, sh: 8, sm: 10, eh:9, em: 5, r:"207", t:"Mr. Besaw"}, + {cn: "English", dow:1, sh: 9, sm: 5, eh:10, em: 0, t:"Dr. Wong"}, + {cn: "Break", dow:1, sh: 10, sm: 0, eh:10, em: 10, t:""}, + {cn: "MS Robotics", dow:1, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}, + {cn: "MS Physical Education Boys", dow:1, sh: 11, sm: 0, eh:11, em: 50, r:"GYM", t:"Mr. Mendezona"}, + {cn: "Office Hours Besaw/Nunez", dow:1, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:"Besaw/Nunez"}, + {cn: "Lunch", dow:1, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, + {cn: "Activity Period", dow:1, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, + {cn: "Latin", dow:1, sh: 13, sm: 5, eh:14, em: 0, r:"208", t:"Mrs.Scrivner"}, + {cn: "Algebra 1", dow:1, sh: 14, sm: 0, eh:15, em: 0, r:"204", t:"Mr. Benson"}, + + //Tuesday: + {cn: "Logic", dow:2, sh: 8, sm: 10, eh:9, em: 0, r:"208", t:"Mrs.Scrivner"}, + {cn: "Algebra 1", dow:2, sh: 9, sm: 0, eh:10, em: 0, r:"204", t:"Mr. Benson"}, + {cn: "Chapel", dow:2, sh: 10, sm: 0, eh:10, em: 25, r:"Advisory", t:""}, + {cn: "Break", dow:2, sh: 10, sm: 25, eh:10, em: 35, r:"Outside", t:""}, + {cn: "Advisory Besaw", dow:2, sh: 10, sm: 35, eh:11, em: 0, r:"207", t:"Mr. Besaw"}, + {cn: "MS Robotics", dow:2, sh: 11, sm: 0, eh:11, em: 50, r:"211", t:"Mr. Broyles"}, + {cn: "Office Hours Besaw/Nunez", dow:2, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"}, + {cn: "Lunch", dow:2, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, + {cn: "Activity Period", dow:2, sh: 12, sm: 50, eh:13, em: 5, r:"Outside", t:""}, + {cn: "Medieval Western Civilization", dow:2, sh: 13, sm: 5, eh:14, em: 0, r:"205", t:"Mr. Khule"}, + {cn: "Introductory Biology and Epidemiology", dow:2, sh: 14, sm: 0, eh:15, em: 0, r:"202", t:"Mrs. Brown"}, + + //Wensday: + {cn: "English", dow:3, sh: 9, sm: 0, eh:9, em: 55, r:"206", t:"Dr. Wong"}, + {cn: "Biblical Theology", dow:3, sh: 9, sm: 55, eh:10, em: 50, r:"207", t:"Mr. Besaw"}, + {cn: "Break", dow:3, sh: 10, sm: 50, eh:11, em: 0, r:"Outside", t:"_"}, + {cn: "MS Physical Education Boys", dow:3, sh: 11, sm: 0, eh:11, em: 50, r:"GYM", t:"Mr. Mendezona"}, + {cn: "Office Hours Besaw/Nunez", dow:3, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"}, + {cn: "Lunch", dow:3, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, + {cn: "Activity Period", dow:2, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, + {cn: "Introductory Biology and Epidemiology", dow:3, sh: 13, sm: 0, eh:14, em: 0, r:"202", t:"Mrs. Brown"}, + {cn: "Medieval Western Civilization", dow:3, sh: 14, sm: 0, eh:15, em: 0, r:"205", t:"Mr. Khule"}, + + + //Thursday: + {cn: "Algebra 1", dow:4, sh: 8, sm: 10, eh:9, em: 5, r:"204", t:"Mr. Benson"}, + {cn: "Latin", dow:4, sh: 9, sm: 5, eh:10, em: 0, r:"208", t:"Mrs.Scrivner"}, + {cn: "Break", dow:4, sh: 10, sm: 0, eh:10, em: 10, r:"Outside", t:""}, + {cn: "MS Robotics", dow:4, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}, + {cn: "Advisory Besaw", dow:4, sh: 11, sm: 50, eh:12, em: 25, r:"207", t:"Mr. Besaw"}, + {cn: "Lunch", dow:4, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, + {cn: "Activity Period", dow:4, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, + {cn: "Biblical Theology", dow:4, sh: 13, sm: 5, eh:14, em: 0, r:"207", t:"Mr. Besaw"}, + {cn: "English", dow:4, sh: 14, sm: 0, eh:15, em: 0, r:"206", t:"Dr. Wong"}, + + //Friday: + {cn: "Medieval Western Civilization", dow:5, sh: 8, sm: 10, eh:9, em: 5, r:"205", t:"Mr. Khule"}, + {cn: "Introductory Biology and Epidemiology", dow:5, sh: 9, sm: 5, eh:10, em: 0, r:"202", t:"Mrs. Brown"}, + {cn: "Break", dow:5, sh: 10, sm: 0, eh:10, em: 10, dr:"Outside", t:""}, + {cn: "MS Robotics", dow:5, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}, + {cn: "Office Hours Besaw/Nunez", dow:5, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"}, + {cn: "Lunch", dow:5, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, + {cn: "Activity Period", dow:5, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, + {cn: "Algebra 1", dow:5, sh: 13, sm: 5, eh:14, em: 0, r:"204", t:"Mr. Benson"}, + {cn: "Logic", dow:5, sh: 14, sm: 0, eh:15, em: 0, r:"208", t:"Mrs.Scrivner"}, + //Sataturday: + ]; + return schedule; +} + +function findNextScheduleIndex() { + var schedule = getScheduleTable(); + var currentDate = new Date(); + var minuteOfWeek = (currentDate.getDay()*3600)+(currentDate.getHours()*60)+currentDate.getMinutes(); + //var minuteOfWeek = (4*3600)+(16*60)+0; + var currentPosition; + for(currentPosition = 0;currentPosition < schedule.length; currentPosition++){ + var scheduleItemStartMinuteOfWeek = schedule[currentPosition].dow*3600 + schedule[currentPosition].eh*60+schedule[currentPosition].em; + if(scheduleItemStartMinuteOfWeek > minuteOfWeek) { + return currentPosition; + } + } + return 0; +} + + +function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} + +function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} + +function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} + +function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} + +var currentPositionTable = 0; +var numberOfItemsShown = 8; +//Table Positions: +var rectStart = 45; +var rectEnd = 65; +var rectStartX = 10; +var rectEndX = 210; +//Scences: +LIST = 1; +INFORMATION = 2; +currentStage = LIST; + +function splitter(str, l){ + var strs = []; + while(str.length > l){ + var pos = str.substring(0, l).lastIndexOf(' '); + pos = pos <= 0 ? l : pos; + strs.push(str.substring(0, pos)); + var i = str.indexOf(' ', pos)+1; + if(i < pos || i > pos+l) + i = pos; + str = str.substring(i); + } + strs.push(str); + return strs; +} + +function updateMinutesToCurrentTime(currentMinuteFunction) { + if (currentMinuteFunction<10){ + currentMinuteUpdatedFunction = "0"+currentMinuteFunction; + }else{ + currentMinuteUpdatedFunction = currentMinuteFunction; + } + return currentMinuteUpdatedFunction; +} + +function renderBackground(l) { + g.clearRect(0,0,240,20); + g.drawImage(getBackgroundImage(),110,130,{scale:9,rotate:0}); +} + +function renderTable(l) { + for(var x = 0;x<=numberOfItemsShown;x++){ + g.setColor(255,255,255); + g.drawRect(rectStartX,rectStart+(x*20),rectEndX,rectEnd+(20*x)); + g.setColor(255,205,0); + g.drawRect(rectStartX,rectStart+(2*20),rectEndX,rectEnd+(2*20)); + g.setColor(255,0,0); + g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); + } +} + +function renderTableText(l){ + var foundNumber = findNextScheduleIndex(); + var foundSchedule = getScheduleTable(); + var scheduleHourUpdated; + var scheduleMinuteUpdated; + var beforeFoundNumber = foundNumber - 2; + for(var x = 0;x<=numberOfItemsShown;x++){ + var currentNumber = beforeFoundNumber + x; + if (beforeFoundNumber + x < 0) { + currentNumber = foundSchedule.length + beforeFoundNumber + x; + } else if (beforeFoundNumber + x > foundSchedule.length - 1) { + currentNumber = beforeFoundNumber + x - foundSchedule.length; + } + + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm); + scheduleHourUpdatedStart = foundSchedule[currentNumber].sh; + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em); + scheduleHourUpdatedEnd = foundSchedule[currentNumber].eh; + scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20); + if(foundSchedule[currentNumber].cn.length >= 15){ + scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20)+"..."; + } + schduleDay = updateDay(3,foundSchedule[currentNumber].dow); + g.setFont("8x12"); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(x*20)); + } +} + +function buttonsF(l){ + if(currentStage == LIST){ + g.drawImage(getDotIcon(),223.5,115); + }else{ + g.drawImage(getMenuIcon(),223.5,115); + } + g.drawImage(getUpArrow(),225,30); + g.drawImage(getDownArrow(),225,215); +} + +function draw() { + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); + if (layout) { + if(currentStage == LIST){ + layout.time.label = currentHour+":"+currentMinuteUpdated; + layout.time.x = 147; + layout.time.y = 10; + layout.render(layout.table); + layout.render(layout.tableText); + logDebug("Rendered"+currentPositionTable); + }else{ + layout.time.label = currentHour+":"+currentMinuteUpdated; + layout.time.x = 147; + layout.time.y = 10; + layout.render(layout.info); + logDebug("Rendered"+currentPositionTable); + } + g.clearRect(150,0,220,35); + layout.render(layout.time); + } +} + +function RedRectDown() { + if(currentPositionTable > 0){ + currentPositionTable -= 1; + if(currentStage == INFORMATION){ + redrawScreen(); + }else{ + draw(); + } + } +} + +function RedRectUp() { + if(currentPositionTable < numberOfItemsShown){ + currentPositionTable += 1; + if(currentStage == INFORMATION){ + redrawScreen(); + }else{ + draw(); + } + } +} + +function renderMiniBackground(l){ + for(var i = 233;i<=240;i++){ + g.drawImage(getBackgroundImage(),i,123,{scale:10,rotate:0}); + } +} + +function renderLoading(l){ + g.setFont("8x12"); + g.drawString("Loading...",240/2-20,240/2-20); +} + +function renderInformation(l){ + var foundNumber = findNextScheduleIndex(); + var foundSchedule = getScheduleTable(); + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); + scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); + scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; + scheduleDay = updateDay(1,foundSchedule[((foundNumber-2)+currentPositionTable)].dow); + g.setColor(255,255,255); + g.setFont("8x12",2); + var splitClassNames = splitter(foundSchedule[((foundNumber-2)+currentPositionTable)].cn, 15); + var currentY = 5; + for (var j=0; j < splitClassNames.length; j++) { + g.drawString(splitClassNames[j],13,currentY+50); + currentY = currentY + 25; + } + g.setFont("8x12"); + g.drawString(schduleDay,13,currentY+50); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); +} + +var Layout = require("Layout"); +var layout = new Layout( + {type:"h", c: [ + {type:"custom", render:renderTableText, id:"tableText"}, + {type:"custom", render:buttonsF, id:"buttons"}, + {type:"custom", render:renderBackground, id:"background"}, + {type:"custom", render:renderTable, id:"table"}, + {type:"custom", render:renderMiniBackground, id:"miniBackground"}, + {type:"custom", render:renderLoading, id:"loading"}, + {type:"custom", render:renderInformation, id:"info"}, + {type:"txt", font:"7x11Numeric7Seg:2", label:"00:00", id:"time"}, + ]}, + {type:"v", c:[ + ]}, + {btns:[ + {label:"", cb: RedRectUp()}, + {label:"", cb: l=>print("Two")}, + {label:"", cb: RedRectDown()} +]}); + + +function getBackgroundImage() {return require("heatshrink").decompress(atob("j0ZyEKIf4A4gIB6gQB6gYB6ggB6goB6gwB6g4B6hAABAYIBHBZIVLAK8IhIBXgAThhQB6hYB6hgB6hoB6hwB6h4B6iAB6iIB6iQBHiAJOB54XSiYB6igB6ioB6iwB6i4B5A="));} + +function logDebug(message) {console.log(message);} + +function changeScene(){ + layout.render(layout.buttons); + if(currentStage == INFORMATION){ + currentStage = LIST; + nIntervId = setInterval(redrawScreen, 100000); + }else if(currentStage == LIST){ + currentStage = INFORMATION; + clearInterval(); + } + layout.render(layout.background); + layout.render(layout.buttons); + draw(); +} + +// timeout used to update every minute +var drawTimeout; + +setInterval(draw, 15000); + + +setWatch(RedRectUp, BTN3, { repeat:true, edge:'rising', debounce : 50 }); +setWatch(RedRectDown, BTN1, { repeat:true, edge:'rising', debounce : 50 }); +setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); + +layout.update(); +layout.render(layout.loading); +layout.render(layout.background); +layout.render(layout.buttons); + +draw(); `; // send finished app (in addition to contents of app.json) sendCustomizedApp({ From 5d24a09ffa2d9cad10a5fae3c7b4d5fafc944aa9 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:04:41 -0800 Subject: [PATCH 0697/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index c41c1268e..81ecbb177 100644 --- a/apps.json +++ b/apps.json @@ -3517,7 +3517,7 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version":"0.01", + "version":"0.02", "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", "tags": "tool", "readme": "README.md", From dfd9e59993944f6323120f8af660d880c63c6793 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:08:52 -0800 Subject: [PATCH 0698/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 36e8ad7c7..9a9abcbd3 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -420,7 +420,7 @@ draw(); // send finished app (in addition to contents of app.json) sendCustomizedApp({ storage: [ - { name: "myapp.app.js", url: "app.js", content: app }, + { name: "schoolCalendar.app.js", url: "app.js", content: app }, ] }); }); From d3ca682cff08be0bb2a64ab057f6efdf36b4e094 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:09:56 -0800 Subject: [PATCH 0699/1062] Update apps.json --- apps.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 81ecbb177..a6f8a9ee8 100644 --- a/apps.json +++ b/apps.json @@ -3517,13 +3517,14 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version":"0.02", + "version":"0.01", "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", "tags": "tool", "readme": "README.md", "custom":"custom.html", "storage": [ - {"name":"schoolCalendar.app.js"} + {"name":"schoolCalendar.app.js"}, + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} ], "data": [ {"name":"app.json"} From 92707b9d13ed269a41684ce45111376694cb6e58 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:12:50 -0800 Subject: [PATCH 0700/1062] Update ChangeLog.md --- apps/schoolCalendar/ChangeLog.md | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/schoolCalendar/ChangeLog.md b/apps/schoolCalendar/ChangeLog.md index c1cd00ff7..85a7cc698 100644 --- a/apps/schoolCalendar/ChangeLog.md +++ b/apps/schoolCalendar/ChangeLog.md @@ -1,2 +1 @@ -# Changelog for the bangle.hs-calenderapp repository: 0.01: App is created with gradient background. From 37da3c68aeeb5fec1ef3d8965fa3d8170074d791 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:19:46 -0800 Subject: [PATCH 0701/1062] Update custom.html --- apps/schoolCalendar/custom.html | 73 +++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 9a9abcbd3..e1c2d0c03 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,15 +1,79 @@ + + + + +
+

Create your events on the current week. Keep in note that your events repeat weekly.

+

One you have created your events, Click

+

All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.

+
-

Some text:

-

Click

- - +
+ From 6c43de3766433c1f9fa1d01fd24cbfe0839e19c0 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:21:11 -0800 Subject: [PATCH 0702/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index a6f8a9ee8..cf9c446fe 100644 --- a/apps.json +++ b/apps.json @@ -3517,7 +3517,7 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version":"0.01", + "version":"0.02", "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", "tags": "tool", "readme": "README.md", From ebc2d8096144e6150920efc4b63e8f1e3f52eb00 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:33:27 -0800 Subject: [PATCH 0703/1062] Update custom.html --- apps/schoolCalendar/custom.html | 35 ++++++--------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index e1c2d0c03..d97f17066 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,35 +1,12 @@ - - - + - -
-

Create your events on the current week. Keep in note that your events repeat weekly.

-

One you have created your events, Click

-

All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.

-
- - + + +

Create your events on the current week. Keep in note that your events repeat weekly.

+

One you have created your events, Click

+

All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.

From 5625414e90c78677040420f342f29e96f088469b Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 15:34:30 -0800 Subject: [PATCH 0704/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index cf9c446fe..859f66b5c 100644 --- a/apps.json +++ b/apps.json @@ -3517,7 +3517,7 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version":"0.02", + "version":"0.03", "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", "tags": "tool", "readme": "README.md", From aa20fac50ed19adfb9248fd9cda1ebb7c31f95b1 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 16:02:52 -0800 Subject: [PATCH 0705/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 859f66b5c..72ff998ee 100644 --- a/apps.json +++ b/apps.json @@ -3517,7 +3517,7 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version":"0.03", + "version":"0.04", "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", "tags": "tool", "readme": "README.md", From 1a6c1acce50fe2fe2e49bfc26b3ac98c1db5aa13 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 16:04:06 -0800 Subject: [PATCH 0706/1062] Update custom.html --- apps/schoolCalendar/custom.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index d97f17066..8a214009e 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,7 +1,7 @@ - - + // + //

Create your events on the current week. Keep in note that your events repeat weekly.

@@ -467,6 +467,5 @@ draw(); }); -
From e206bd4e1cad4aab266fd376fa525500941878a8 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 16:08:00 -0800 Subject: [PATCH 0707/1062] Update custom.html --- apps/schoolCalendar/custom.html | 45 +-------------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 8a214009e..86b1bae56 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -1,7 +1,6 @@ - // - // +

Create your events on the current week. Keep in note that your events repeat weekly.

@@ -11,50 +10,8 @@ +
From a1a387dd600f7e5c3879d74cd5a516d7566fb7bb Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 17:47:52 -0800 Subject: [PATCH 0716/1062] Update custom.html --- apps/schoolCalendar/custom.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index e9276b1bf..5353e4bf0 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -23,9 +23,11 @@ -

Create your events on the current week. Keep in note that your events repeat weekly.

-

One you have created your events, Click

-

All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.

+
+

Create your events on the current week. Keep in note that your events repeat weekly.

+

One you have created your events, Click

+

All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.

+
From a65742ce346039ef901e70157620aabd95bbca35 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 17:48:25 -0800 Subject: [PATCH 0717/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 643b67c79..9e9f239d1 100644 --- a/apps.json +++ b/apps.json @@ -3517,7 +3517,7 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version":" your ugly nuts", + "version":" my testing is hard work a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", "tags": "tool", "readme": "README.md", From ab6f56c381a54e4ab3466625a9eb794eef5dd2a0 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 17:52:34 -0800 Subject: [PATCH 0718/1062] Update custom.html --- apps/schoolCalendar/custom.html | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 5353e4bf0..7a60af319 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -73,6 +73,25 @@ // When the 'upload' button is clicked... document.getElementById("upload").addEventListener("click", function () { + //Cacultate data: + var calendarEvents = calendar.getEvents(); + let schedule = [] + //-------------------- + if(calendarEvents) + for(i=0;i>calendarEvents.length;i++){ + var calendarEntry = {} + calendarEntry['cn'] = calendarEvents[i].title; + calendarEntry['dow'] = calendarEvents[i].start.getDate(); + calendarEntry['sh'] = calendarEvents[i].start.getHours(); + calendarEntry['sm'] = calendarEvents[i].start.getMinutes(); + calendarEntry['eh'] = calendarEvents[i].end.getHours(); + calendarEntry['em'] = calendarEvents[i].end.getMinutes(); + schedule.push(calendarEntry) + } + }else{ + alert("Add some events!"); + } + //--------------------- // build the app's text using a templated String var app = ` require("Font8x12").add(Graphics); From 352fea34791ccf06450a516ab2a2daf22353c641 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 17:53:38 -0800 Subject: [PATCH 0719/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 9e9f239d1..19c5bca91 100644 --- a/apps.json +++ b/apps.json @@ -3517,7 +3517,7 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version":" my testing is hard work a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "version":" da frick", "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", "tags": "tool", "readme": "README.md", From a9c5a32bc1359d8288f4fd6b6da50eaf40910c9a Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 17:56:54 -0800 Subject: [PATCH 0720/1062] Update custom.html --- apps/schoolCalendar/custom.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 7a60af319..584d42261 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -74,10 +74,10 @@ // When the 'upload' button is clicked... document.getElementById("upload").addEventListener("click", function () { //Cacultate data: - var calendarEvents = calendar.getEvents(); + //var calendarEvents = calendar.getEvents(); let schedule = [] //-------------------- - if(calendarEvents) + /*if(calendarEvents) for(i=0;i>calendarEvents.length;i++){ var calendarEntry = {} calendarEntry['cn'] = calendarEvents[i].title; @@ -88,9 +88,9 @@ calendarEntry['em'] = calendarEvents[i].end.getMinutes(); schedule.push(calendarEntry) } - }else{ + }else{*/ alert("Add some events!"); - } + //} //--------------------- // build the app's text using a templated String var app = ` From 4da82197f32b280e4192d5bc9a5ba174fbad4ace Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 18:01:20 -0800 Subject: [PATCH 0721/1062] Update custom.html --- apps/schoolCalendar/custom.html | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 584d42261..edc37be5a 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -89,6 +89,7 @@ schedule.push(calendarEntry) } }else{*/ + consle.log(calendar.getEvents()); alert("Add some events!"); //} //--------------------- From 34843d703aae0b6a33be352ad0f8408d8d17d07b Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 18:11:54 -0800 Subject: [PATCH 0722/1062] Update custom.html --- apps/schoolCalendar/custom.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index edc37be5a..4b820a98f 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -89,7 +89,8 @@ schedule.push(calendarEntry) } }else{*/ - consle.log(calendar.getEvents()); + console.log = console.log || function(){}; + console.log("my pro"); alert("Add some events!"); //} //--------------------- From ff519bdc0d9bca530284d98cad9fd023ad37d50d Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 18:15:29 -0800 Subject: [PATCH 0723/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 4b820a98f..0cf29f933 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -90,7 +90,7 @@ } }else{*/ console.log = console.log || function(){}; - console.log("my pro"); + console.log(calendar.getEvents()); alert("Add some events!"); //} //--------------------- From 6c20945898b84a02d6b440923a5afd994c7d97aa Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sat, 20 Nov 2021 18:37:41 -0800 Subject: [PATCH 0724/1062] Update custom.html --- apps/schoolCalendar/custom.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 0cf29f933..26141e31a 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -33,10 +33,10 @@
From daf8dde32bc81b8f04402c4728eac41784751cd5 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 07:49:25 -0800 Subject: [PATCH 0753/1062] Update custom.html --- apps/mywelcome/custom.html | 192 ++++++++++++++++++++++--------------- 1 file changed, 115 insertions(+), 77 deletions(-) diff --git a/apps/mywelcome/custom.html b/apps/mywelcome/custom.html index 6b73a4ac4..c4c721765 100644 --- a/apps/mywelcome/custom.html +++ b/apps/mywelcome/custom.html @@ -1,92 +1,130 @@ - -
-

Create your events on the current week. Keep in note that your events repeat weekly.

-

One you have created your events, Click

-
- - - + + +
+

Style: +

+

Line 1:

+

Line 2:

+

Line 3 (smaller):

+

Line 4 (smaller):

+ +

+

+

This is currently Christmas-themed, but more themes will be added in the future.

+ + + - - -
From 98f172fad442e89f53c45ac47f2388a08049855a Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 07:53:01 -0800 Subject: [PATCH 0754/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index d7c9aa920..a3669c4a3 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -508,7 +508,7 @@ draw(); }); document.getElementById("upload").addEventListener("click", function () { - + window.open("https://www.espruino.com/ide/emulator.html?code="+encodeURIComponent(getApp())+"&upload"); }
From d42fe03dc8fcbc0f68857dd9f887c8cc4eff1c06 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 07:55:08 -0800 Subject: [PATCH 0755/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index a3669c4a3..3be357f2c 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -509,7 +509,7 @@ draw(); document.getElementById("upload").addEventListener("click", function () { window.open("https://www.espruino.com/ide/emulator.html?code="+encodeURIComponent(getApp())+"&upload"); - } + });
From 7f7dcb0bcf89b1d66f60481efaa09f5956ee4ecc Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 07:58:22 -0800 Subject: [PATCH 0756/1062] Update custom.html --- apps/schoolCalendar/custom.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 3be357f2c..9a7341c7b 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,6 +50,7 @@ nowIndicator: true, editable: true, height: 600, + initialDate: '2018-06-01', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { @@ -61,6 +62,7 @@ }) } calendar.unselect() + }, eventClick: function(arg) { if (confirm('Are you sure you want to delete this event?')) { @@ -69,7 +71,6 @@ }, }); calendar.render(); - calendar.gotoDate(2021-01-10); }); // When the 'upload' button is clicked... From 0f8f1e38e85a8c1c49057f4b891209bdfad67aef Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 07:58:44 -0800 Subject: [PATCH 0757/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 9a7341c7b..9d97e3cd6 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -24,7 +24,7 @@
-

Create your events on the current week. Keep in note that your events repeat weekly.

+

Create your events on the week shown. Keep in note that your events repeat weekly.

One you have created your events, Click or try in

All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.

From 604153cb2408399ce1420b43ef87f41c09d3daa6 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:01:45 -0800 Subject: [PATCH 0758/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 9d97e3cd6..16f3b06a2 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2018-06-01', // will be parsed as local + initialDate: '2018-06-10', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From 640c8e491e489e9ea88282cf0bfe14004838b3db Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:03:42 -0800 Subject: [PATCH 0759/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 16f3b06a2..1b558b991 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2018-06-10', // will be parsed as local + initialDate: '2018-06-8', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From d0336b2854d7e0892c440ef8c5f83bcbe1374a9b Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:06:56 -0800 Subject: [PATCH 0760/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 1b558b991..cc1d6cfdf 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2018-06-8', // will be parsed as local + initialDate: '2018-06-4', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From f35b24844810a90f08f731cc9e63535be217e4cf Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:09:44 -0800 Subject: [PATCH 0761/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index cc1d6cfdf..65d3db542 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2018-06-4', // will be parsed as local + initialDate: '2020-01-1', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From 4d9d6abb9ef466e4058510efe9eec295f60127e9 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:14:02 -0800 Subject: [PATCH 0762/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 65d3db542..d2c1c806a 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2020-01-1', // will be parsed as local + initialDate: '2018-06-03', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From a712c37c7652f7bc46c4d7761df93b3d28e17229 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:17:58 -0800 Subject: [PATCH 0763/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index d2c1c806a..123829f66 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2018-06-03', // will be parsed as local + initialDate: '2018-01-01', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From e77d9980023f041a87743ac1fd3c48a3f0652a8e Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:18:21 -0800 Subject: [PATCH 0764/1062] Update custom.html update --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 123829f66..68a672804 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2018-01-01', // will be parsed as local + initialDate: '2021-01-01', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From 529b0cb4637652dfc0a3ba6688b863b5f47437ca Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:19:53 -0800 Subject: [PATCH 0765/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 68a672804..21e0b0bbd 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2021-01-01', // will be parsed as local + initialDate: '2021-01-02', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From 3472ca355a1a71d3fa3f464ece2b81dce7f4eeaa Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:21:57 -0800 Subject: [PATCH 0766/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 21e0b0bbd..271550416 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2021-01-02', // will be parsed as local + initialDate: '2021-06-01', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From 626498da9d296fecf6776b836def4eda87027d85 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:24:01 -0800 Subject: [PATCH 0767/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 271550416..9d97e3cd6 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2021-06-01', // will be parsed as local + initialDate: '2018-06-01', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From 87a9504912b8753dc63700207ee89acacbb34726 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:30:44 -0800 Subject: [PATCH 0768/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 9d97e3cd6..6dfc97d51 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2018-06-01', // will be parsed as local + initialDate: '2018-06-25', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From 66f061112301898b011412e91982dbefc7cc2d1e Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:32:48 -0800 Subject: [PATCH 0769/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 6dfc97d51..d620b834e 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2018-06-25', // will be parsed as local + initialDate: '2018-06-3', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From 8f2cc6e2137cf29e03435123e96d9c6c550864d1 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:34:09 -0800 Subject: [PATCH 0770/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index d620b834e..d2c1c806a 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -50,7 +50,7 @@ nowIndicator: true, editable: true, height: 600, - initialDate: '2018-06-3', // will be parsed as local + initialDate: '2018-06-03', // will be parsed as local select: function(arg) { var title = prompt('Event Title:'); if (title) { From 26bd9e35be993aae4deece775ac85f53f877b122 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 08:35:47 -0800 Subject: [PATCH 0771/1062] Update custom.html --- apps/schoolCalendar/custom.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index d2c1c806a..0141bf067 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -84,7 +84,7 @@ for(i=0;i Date: Sun, 21 Nov 2021 08:37:41 -0800 Subject: [PATCH 0772/1062] Update custom.html --- apps/schoolCalendar/custom.html | 64 +-------------------------------- 1 file changed, 1 insertion(+), 63 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 0141bf067..fd899f5b5 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -184,69 +184,7 @@ function updateDay(ffunction,day){ } function getScheduleTable() { - let schedule = [ - //Sunday - - //Monday: - {cn: "Biblical Theology", dow:1, sh: 8, sm: 10, eh:9, em: 5, r:"207", t:"Mr. Besaw"}, - {cn: "English", dow:1, sh: 9, sm: 5, eh:10, em: 0, t:"Dr. Wong"}, - {cn: "Break", dow:1, sh: 10, sm: 0, eh:10, em: 10, t:""}, - {cn: "MS Robotics", dow:1, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}, - {cn: "MS Physical Education Boys", dow:1, sh: 11, sm: 0, eh:11, em: 50, r:"GYM", t:"Mr. Mendezona"}, - {cn: "Office Hours Besaw/Nunez", dow:1, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:"Besaw/Nunez"}, - {cn: "Lunch", dow:1, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, - {cn: "Activity Period", dow:1, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, - {cn: "Latin", dow:1, sh: 13, sm: 5, eh:14, em: 0, r:"208", t:"Mrs.Scrivner"}, - {cn: "Algebra 1", dow:1, sh: 14, sm: 0, eh:15, em: 0, r:"204", t:"Mr. Benson"}, - - //Tuesday: - {cn: "Logic", dow:2, sh: 8, sm: 10, eh:9, em: 0, r:"208", t:"Mrs.Scrivner"}, - {cn: "Algebra 1", dow:2, sh: 9, sm: 0, eh:10, em: 0, r:"204", t:"Mr. Benson"}, - {cn: "Chapel", dow:2, sh: 10, sm: 0, eh:10, em: 25, r:"Advisory", t:""}, - {cn: "Break", dow:2, sh: 10, sm: 25, eh:10, em: 35, r:"Outside", t:""}, - {cn: "Advisory Besaw", dow:2, sh: 10, sm: 35, eh:11, em: 0, r:"207", t:"Mr. Besaw"}, - {cn: "MS Robotics", dow:2, sh: 11, sm: 0, eh:11, em: 50, r:"211", t:"Mr. Broyles"}, - {cn: "Office Hours Besaw/Nunez", dow:2, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"}, - {cn: "Lunch", dow:2, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, - {cn: "Activity Period", dow:2, sh: 12, sm: 50, eh:13, em: 5, r:"Outside", t:""}, - {cn: "Medieval Western Civilization", dow:2, sh: 13, sm: 5, eh:14, em: 0, r:"205", t:"Mr. Khule"}, - {cn: "Introductory Biology and Epidemiology", dow:2, sh: 14, sm: 0, eh:15, em: 0, r:"202", t:"Mrs. Brown"}, - - //Wensday: - {cn: "English", dow:3, sh: 9, sm: 0, eh:9, em: 55, r:"206", t:"Dr. Wong"}, - {cn: "Biblical Theology", dow:3, sh: 9, sm: 55, eh:10, em: 50, r:"207", t:"Mr. Besaw"}, - {cn: "Break", dow:3, sh: 10, sm: 50, eh:11, em: 0, r:"Outside", t:"_"}, - {cn: "MS Physical Education Boys", dow:3, sh: 11, sm: 0, eh:11, em: 50, r:"GYM", t:"Mr. Mendezona"}, - {cn: "Office Hours Besaw/Nunez", dow:3, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"}, - {cn: "Lunch", dow:3, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, - {cn: "Activity Period", dow:2, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, - {cn: "Introductory Biology and Epidemiology", dow:3, sh: 13, sm: 0, eh:14, em: 0, r:"202", t:"Mrs. Brown"}, - {cn: "Medieval Western Civilization", dow:3, sh: 14, sm: 0, eh:15, em: 0, r:"205", t:"Mr. Khule"}, - - - //Thursday: - {cn: "Algebra 1", dow:4, sh: 8, sm: 10, eh:9, em: 5, r:"204", t:"Mr. Benson"}, - {cn: "Latin", dow:4, sh: 9, sm: 5, eh:10, em: 0, r:"208", t:"Mrs.Scrivner"}, - {cn: "Break", dow:4, sh: 10, sm: 0, eh:10, em: 10, r:"Outside", t:""}, - {cn: "MS Robotics", dow:4, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}, - {cn: "Advisory Besaw", dow:4, sh: 11, sm: 50, eh:12, em: 25, r:"207", t:"Mr. Besaw"}, - {cn: "Lunch", dow:4, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, - {cn: "Activity Period", dow:4, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, - {cn: "Biblical Theology", dow:4, sh: 13, sm: 5, eh:14, em: 0, r:"207", t:"Mr. Besaw"}, - {cn: "English", dow:4, sh: 14, sm: 0, eh:15, em: 0, r:"206", t:"Dr. Wong"}, - - //Friday: - {cn: "Medieval Western Civilization", dow:5, sh: 8, sm: 10, eh:9, em: 5, r:"205", t:"Mr. Khule"}, - {cn: "Introductory Biology and Epidemiology", dow:5, sh: 9, sm: 5, eh:10, em: 0, r:"202", t:"Mrs. Brown"}, - {cn: "Break", dow:5, sh: 10, sm: 0, eh:10, em: 10, dr:"Outside", t:""}, - {cn: "MS Robotics", dow:5, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}, - {cn: "Office Hours Besaw/Nunez", dow:5, sh: 11, sm: 50, eh:12, em: 25, r:"203", t:" Besaw/Nunez"}, - {cn: "Lunch", dow:5, sh: 12, sm: 25, eh:12, em: 50, r:"Commence or Advisory", t:""}, - {cn: "Activity Period", dow:5, sh: 12, sm: 50, eh:13, em: 0, r:"Outside", t:""}, - {cn: "Algebra 1", dow:5, sh: 13, sm: 5, eh:14, em: 0, r:"204", t:"Mr. Benson"}, - {cn: "Logic", dow:5, sh: 14, sm: 0, eh:15, em: 0, r:"208", t:"Mrs.Scrivner"}, - //Sataturday: - ]; + let schedule = ${schedule}; return schedule; } From 87a192cc5e2e2a62a9c849c7bef20a17ae29a73f Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 21 Nov 2021 20:53:49 +0300 Subject: [PATCH 0773/1062] Update README.md Original control method changed to alternative method. --- apps/snake/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/snake/README.md b/apps/snake/README.md index 483eae7a9..278dffbc4 100644 --- a/apps/snake/README.md +++ b/apps/snake/README.md @@ -7,8 +7,7 @@ Eat apples and don't bite your tail. ## Controls -- UP: BTN1 -- DOWN: BTN3 -- LEFT: BTN4 -- RIGHT: BTN5 -- PAUSE: BTN2 +- BTN1: turn to left +- BTN2: pause +- BTN3: turn to right + From a5901f82e8d782a027d69c0536113bfbe716a054 Mon Sep 17 00:00:00 2001 From: David Peer Date: Sun, 21 Nov 2021 20:02:09 +0100 Subject: [PATCH 0774/1062] Minor design change. --- apps/lcars/lcars.app.js | 24 ++++++++++++------------ apps/lcars/screenshot.png | Bin 2118 -> 2434 bytes 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index bc3323c93..819caa8ef 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -62,18 +62,6 @@ function draw(queue){ var alrmText = alarm >= 0 ? "T-"+alarm : "OFF"; g.drawString(alrmText, 70, 107); - // GPS - var gpsText = Bangle.isGPSOn() ? "GPS-ON" : "GPS-OFF"; - g.drawString(gpsText, 120, 107); - - // HRM - var gpsText = Bangle.isHRMOn() ? "HRS-ON" : "HRS-OFF"; - g.drawString(gpsText, 120, 127); - - // CMP - var compassText = Bangle.isCompassOn() ? "CMP-ON" : "CMP-OFF"; - g.drawString(compassText, 120, 147); - // Draw battery var bat = E.getBattery(); var charging = Bangle.isCharging() ? "*" : ""; @@ -85,6 +73,18 @@ function draw(queue){ g.drawString("STEP:", 30, 147); g.drawString(steps, 70, 147); + // GPS + var gpsText = Bangle.isGPSOn() ? "GPS-ON" : "GPS-OFF"; + g.drawString(gpsText, 115, 107); + + // HRM + var gpsText = Bangle.isHRMOn() ? "HRS-ON" : "HRS-OFF"; + g.drawString(gpsText, 115, 127); + + // CMP + var compassText = Bangle.isCompassOn() ? "CMP-ON" : "CMP-OFF"; + g.drawString(compassText, 115, 147); + // Queue draw in one minute if(queue){ queueDraw(); diff --git a/apps/lcars/screenshot.png b/apps/lcars/screenshot.png index 6cb48387d1b1bfb9192d5fa3dc17f2a19b8d51ac..dc577c4d0629489e567bb31deb1ca2f3554264b6 100644 GIT binary patch delta 2408 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 From 2b9ff26d854169404da81ff792628b68f0b792e1 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Sun, 21 Nov 2021 22:03:23 +0000 Subject: [PATCH 0775/1062] Health, supress bleed through of Showmenus when displaying barcharts, Toucher: Debugging default = false --- apps.json | 2 +- apps/health/ChangeLog | 1 + apps/health/app.js | 3 +++ apps/toucher/settings.js | 4 ++-- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/apps.json b/apps.json index 7fe259899..31817758d 100644 --- a/apps.json +++ b/apps.json @@ -82,7 +82,7 @@ { "id": "health", "name": "Health Tracking", - "version": "0.07", + "version": "0.08", "description": "Logs health data and provides an app to view it (BETA - requires firmware 2v11)", "icon": "app.png", "tags": "tool,system,health", diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 5eb96a0ea..bde4f8ab8 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -6,3 +6,4 @@ 0.05: Fix daily summary calculation 0.06: Fix daily health summary for movement (a line got deleted!) 0.07: Added coloured bar charts +0.08: Suppress bleed through of E.showMenu's when displaying bar charts diff --git a/apps/health/app.js b/apps/health/app.js index eae45c190..08d6ead17 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -236,6 +236,9 @@ Bangle.on('swipe', dir => { // use setWatch() as Bangle.setUI("updown",..) interacts with swipes function setButton(fn) { + // cancel callback, otherwise a slight up down movement will show the E.showMenu() + Bangle.setUI("updown", undefined); + if (process.env.HWVERSION == 1) btn = setWatch(fn, BTN2); else diff --git a/apps/toucher/settings.js b/apps/toucher/settings.js index 6f7320513..51275d846 100644 --- a/apps/toucher/settings.js +++ b/apps/toucher/settings.js @@ -9,7 +9,7 @@ highres: true, animation : true, frame : 3, - debug: true + debug: false }; } @@ -56,4 +56,4 @@ }, '< Back': back }); -}); \ No newline at end of file +}); From 3bde4d2e48711186ae26036baea8f880c2aac7c4 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 16:12:16 -0800 Subject: [PATCH 0776/1062] Update custom.html --- apps/schoolCalendar/custom.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index fd899f5b5..de3511e19 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -90,12 +90,12 @@ calendarEntry['eh'] = calendarEvents[i].end.getHours(); calendarEntry['em'] = calendarEvents[i].end.getMinutes(); schedule.push(calendarEntry) - console.log(schedule[0].cn); - console.log(schedule[0].dow); - console.log(schedule[0].sh); - console.log(schedule[0].sm); - console.log(schedule[0].eh); - console.log(schedule[0].em); + console.log(schedule[i].cn); + console.log(schedule[i].dow); + console.log(schedule[i].sh); + console.log(schedule[i].sm); + console.log(schedule[i].eh); + console.log(schedule[i].em); } // build the app's text using a templated String var app = ` @@ -184,7 +184,7 @@ function updateDay(ffunction,day){ } function getScheduleTable() { - let schedule = ${schedule}; + let schedule = ${JSON.stringify(schedule)}; return schedule; } From e878e5478713b0b3cae0bb86e28f998cbfeb9b10 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 18:11:43 -0800 Subject: [PATCH 0777/1062] merge --- README.md | 52 +- _config.yml | 1 + apps.json | 4945 ++++++++++------- apps/_example_app/add_to_apps.json | 5 +- apps/_example_widget/add_to_apps.json | 5 +- apps/a_battery_widget/ChangeLog | 1 + apps/a_battery_widget/README.md | 14 + .../a_battery_widget/a_battery_widget-pic.jpg | Bin 0 -> 66429 bytes apps/a_battery_widget/widget.js | 45 + apps/a_battery_widget/widget.png | Bin 0 -> 2385 bytes apps/about/ChangeLog | 2 + apps/about/{app.js => app-bangle1.js} | 0 apps/about/app-bangle2.js | 72 + apps/aclock/README.md | 4 + apps/aclock/screenshot_analog.png | Bin 0 -> 3069 bytes apps/alarm/ChangeLog | 1 + apps/alarm/app.js | 6 +- apps/android/ChangeLog | 1 + apps/android/app-icon.js | 1 + apps/android/app.js | 2 + apps/android/app.png | Bin 0 -> 636 bytes apps/android/boot.js | 55 + apps/arrow/ChangeLog | 4 +- apps/arrow/app.js | 59 +- apps/astroid/screenshot_asteroids.png | Bin 0 -> 1498 bytes apps/berlinc/ChangeLog | 3 + apps/berlinc/berlin-clock.js | 66 +- apps/binwatch/Background176_center.png | Bin 0 -> 4463 bytes apps/binwatch/Background240_center.png | Bin 0 -> 6492 bytes apps/binwatch/ChangeLog | 2 + apps/binwatch/README.md | 10 + apps/binwatch/app-icon.js | 1 + apps/binwatch/app.js | 381 ++ apps/binwatch/app.png | Bin 0 -> 3794 bytes apps/binwatch/bt-icon.png | Bin 0 -> 708 bytes apps/boldclk/README.md | 4 + apps/boldclk/screenshot_bold.png | Bin 0 -> 3563 bytes apps/boot/ChangeLog | 9 + apps/boot/bootupdate.js | 45 +- apps/bthrm/ChangeLog | 1 + apps/bthrm/README.md | 45 + apps/bthrm/app-icon.js | 1 + apps/bthrm/app.png | Bin 0 -> 2756 bytes apps/bthrm/boot.js | 79 + apps/calculator/screenshot_calculator.png | Bin 0 -> 2733 bytes apps/calendar/screenshot_calendar.png | Bin 0 -> 3866 bytes apps/chronowid/chrono_with_pastel.jpg | Bin 0 -> 29883 bytes apps/chronowid/chrono_with_wave.jpg | Bin 0 -> 61355 bytes apps/cliclockJS2Enhanced/app.icon.js | 1 + apps/cliclockJS2Enhanced/app.js | 216 + apps/cliclockJS2Enhanced/app.js.png | Bin 0 -> 6100 bytes apps/cliclockJS2Enhanced/app.png | Bin 0 -> 421 bytes apps/cliclockJS2Enhanced/screengrab.png | Bin 0 -> 2310 bytes apps/cliock/ChangeLog | 2 + apps/cliock/app.js | 23 +- apps/cliock/screenshot_cli.png | Bin 0 -> 2115 bytes apps/clock2x3/README.md | 4 + apps/clock2x3/screenshot_pixel.png | Bin 0 -> 1926 bytes apps/compass/ChangeLog | 3 +- apps/compass/compass.js | 78 +- apps/compass/screenshot_compass.png | Bin 0 -> 2632 bytes apps/cubescramble/ChangeLog | 2 + apps/cubescramble/README.md | 18 + apps/cubescramble/cube-scramble-icon.js | 1 + apps/cubescramble/cube-scramble.js | 74 + apps/cubescramble/cube-scramble.png | Bin 0 -> 1288 bytes apps/emojuino/ChangeLog | 1 + apps/emojuino/README.md | 28 + apps/emojuino/emojuino-icon.js | 1 + apps/emojuino/emojuino.js | 145 + apps/emojuino/emojuino.png | Bin 0 -> 1966 bytes apps/ffcniftya/README.md | 4 + apps/ffcniftya/app.js | 2 +- apps/ffcniftya/screenshot_nifty.png | Bin 0 -> 3487 bytes apps/ffcniftyb/ChangeLog | 2 + apps/ffcniftyb/README.md | 9 + apps/ffcniftyb/app-icon.js | 1 + apps/ffcniftyb/app.js | 118 + apps/ffcniftyb/app.png | Bin 0 -> 1218 bytes apps/ffcniftyb/screenshot.png | Bin 0 -> 4343 bytes apps/ffcniftyb/settings.js | 49 + apps/files/files.js | 20 +- apps/flappy/README.md | 5 + apps/flappy/screenshot1_flappy.png | Bin 0 -> 2509 bytes apps/flappy/screenshot2_flappy.png | Bin 0 -> 2805 bytes apps/floralclk/README.md | 4 + apps/floralclk/screenshot_floral.png | Bin 0 -> 6073 bytes apps/fwupdate/ChangeLog | 1 + apps/fwupdate/app.png | Bin 0 -> 1024 bytes apps/fwupdate/custom.html | 286 + apps/gallifr/screenshot_time.png | Bin 0 -> 3807 bytes apps/gpstime/ChangeLog | 3 +- apps/gpstime/gpstime-icon.js | 2 +- apps/gpstime/gpstime.js | 117 +- apps/gpstouch/Changelog | 1 + apps/gpstouch/README.md | 16 + apps/gpstouch/geotools.js | 128 + apps/gpstouch/gpstouch.app.js | 246 + apps/gpstouch/gpstouch.icon.js | 1 + apps/gpstouch/gpstouch.png | Bin 0 -> 1571 bytes apps/gpstouch/screenshot1.png | Bin 0 -> 2627 bytes apps/gpstouch/screenshot2.png | Bin 0 -> 2555 bytes apps/gpstouch/screenshot3.png | Bin 0 -> 2474 bytes apps/gpstouch/screenshot4.png | Bin 0 -> 2750 bytes apps/hcclock/ChangeLog | 2 +- apps/hcclock/hcclock.app.js | 47 +- apps/health/ChangeLog | 7 + apps/health/README.md | 11 +- apps/health/app.js | 228 +- apps/health/boot.js | 58 +- apps/health/interface.html | 133 + apps/health/lib.js | 33 +- apps/heart/ChangeLog | 1 + apps/heart/app.js | 8 +- apps/hrm/ChangeLog | 1 + apps/hrm/heartrate-icon.js | 2 +- apps/hrm/heartrate.js | 26 +- apps/ios/ChangeLog | 1 + apps/ios/app-icon.js | 1 + apps/ios/app.js | 2 + apps/ios/app.png | Bin 0 -> 1301 bytes apps/ios/boot.js | 131 + apps/launch/ChangeLog | 3 +- apps/launch/{app.js => app-bangle1.js} | 2 +- apps/launch/app-bangle2.js | 48 + apps/launchb2/ChangeLog | 3 - apps/launchb2/app.js | 79 - apps/launchb2/app.png | Bin 899 -> 0 bytes apps/lcars/ChangeLog | 1 + apps/lcars/README.md | 8 + apps/lcars/background.png | Bin 0 -> 1497 bytes apps/lcars/lcars.app.js | 99 + apps/lcars/lcars.icon.js | 1 + apps/lcars/lcars.png | Bin 0 -> 1823 bytes apps/matrixclock/ChangeLog | 3 +- apps/matrixclock/matrixclock.js | 35 +- apps/matrixclock/screenshot_matrix.png | Bin 0 -> 4990 bytes apps/menusmall/ChangeLog | 1 + apps/menusmall/boot.js | 31 +- apps/messages/ChangeLog | 3 + apps/messages/README.md | 21 + apps/messages/app-icon.js | 1 + apps/messages/app.js | 237 + apps/messages/app.png | Bin 0 -> 917 bytes apps/messages/lib.js | 37 + apps/messages/widget.js | 20 + apps/multiclock/ChangeLog | 24 +- apps/multiclock/README.md | 28 +- apps/multiclock/{ana.js => ana.face.js} | 46 +- apps/multiclock/anaface.jpg | Bin 44568 -> 0 bytes apps/multiclock/apps_entry.json | 19 - apps/multiclock/big.face.js | 31 + apps/multiclock/big.js | 32 - apps/multiclock/bigface.jpg | Bin 40453 -> 0 bytes apps/multiclock/clock.info | 1 + apps/multiclock/clock.js | 69 - apps/multiclock/digi.face.js | 38 + apps/multiclock/digi.js | 33 - apps/multiclock/digiface.jpg | Bin 48073 -> 0 bytes apps/multiclock/dk.face.js | 40 + apps/multiclock/multiclock-icon.img | Bin 0 -> 1156 bytes apps/multiclock/multiclock-icon.js | 2 +- apps/multiclock/multiclock.app.js | 86 + apps/multiclock/nifty.face.js | 55 + apps/multiclock/ped.js | 41 - apps/multiclock/timdat.js | 47 - apps/multiclock/{txt.js => txt.face.js} | 39 +- apps/multiclock/txtface.jpg | Bin 51801 -> 0 bytes apps/pastel/ChangeLog | 1 + apps/pastel/README.md | 5 +- apps/pastel/pastel.app.js | 14 + apps/pastel/pastel.settings.js | 4 +- apps/pastel/screenshot_elite.jpg | Bin 0 -> 9486 bytes apps/pastel/screenshot_monoton.jpg | Bin 0 -> 11281 bytes apps/pastel/screenshot_pastel.png | Bin 0 -> 4014 bytes apps/pomodo/CHANGELOG.md | 4 + apps/pomodo/ChangeLog | 1 + apps/pomodo/pomodoro.js | 140 +- apps/qalarm/ChangeLog | 2 + apps/qalarm/app-icon.js | 1 + apps/qalarm/app.js | 271 + apps/qalarm/app.png | Bin 0 -> 1531 bytes apps/qalarm/boot.js | 1 + apps/qalarm/qalarm.js | 153 + apps/qalarm/qalarmcheck.js | 41 + apps/qalarm/widget.js | 22 + apps/recorder/ChangeLog | 4 + apps/recorder/README.md | 27 + apps/recorder/app-icon.js | 1 + apps/recorder/app-settings.json | 6 + apps/recorder/app.js | 412 ++ apps/recorder/app.png | Bin 0 -> 1530 bytes apps/recorder/interface.html | 255 + apps/recorder/settings.js | 4 + apps/recorder/widget.js | 222 + apps/s7clk/README.md | 4 + apps/s7clk/screenshot_s7segment.png | Bin 0 -> 2144 bytes apps/sclock/ChangeLog | 1 + apps/sclock/clock-simple.js | 33 +- apps/sclock/screenshot_simplec.png | Bin 0 -> 2217 bytes apps/score/README.md | 3 + apps/score/screenshot_score.png | Bin 0 -> 1796 bytes apps/setting/ChangeLog | 5 +- apps/setting/settings-icon.js | 2 +- apps/setting/settings.js | 63 +- apps/simplest/ChangeLog | 1 + apps/simplest/app.js | 9 +- apps/simplest/screenshot_simplest.png | Bin 0 -> 2180 bytes apps/slomoclock/ChangeLog | 2 + apps/slomoclock/README.md | 6 + apps/slomoclock/app-icon.js | 1 + apps/slomoclock/app.js | 118 + apps/slomoclock/settings.js | 38 + apps/slomoclock/watch.png | Bin 0 -> 1439 bytes apps/speedalt/settings.js | 2 +- apps/speedalt2/ChangeLog | 2 + apps/speedalt2/README.md | 134 + apps/speedalt2/app-icon.js | 1 + apps/speedalt2/app.js | 725 +++ apps/speedalt2/app.png | Bin 0 -> 1639 bytes apps/speedalt2/settings.js | 89 + apps/stopwatch/A.jpg | Bin 0 -> 77328 bytes apps/stopwatch/B.jpg | Bin 0 -> 69988 bytes apps/stopwatch/ChangeLog | 1 + apps/stopwatch/README.md | 33 + apps/stopwatch/pause-24.png | Bin 0 -> 161 bytes apps/stopwatch/pause-24a.png | Bin 0 -> 123 bytes apps/stopwatch/play-24.png | Bin 0 -> 297 bytes apps/stopwatch/screenshot1.png | Bin 0 -> 1783 bytes apps/stopwatch/screenshot2.png | Bin 0 -> 1765 bytes apps/stopwatch/screenshot3.png | Bin 0 -> 1938 bytes apps/stopwatch/stop-24.png | Bin 0 -> 177 bytes apps/stopwatch/stop-24a.png | Bin 0 -> 192 bytes apps/stopwatch/stopwatch.app.js | 220 + apps/stopwatch/stopwatch.icon.js | 1 + apps/stopwatch/stopwatch.png | Bin 0 -> 1566 bytes apps/svclock/ChangeLog | 2 + apps/svclock/vclock-simple.js | 113 +- apps/swiperclocklaunch/ChangeLog | 1 + apps/swiperclocklaunch/boot.js | 17 + apps/swiperclocklaunch/icon.js | 1 + apps/swiperclocklaunch/swiperclocklaunch.png | Bin 0 -> 889 bytes apps/toucher/ChangeLog | 3 +- apps/toucher/README.md | 22 + apps/toucher/app.js | 145 +- apps/toucher/screenshot1.jpg | Bin 0 -> 21329 bytes apps/trex/README.md | 4 + apps/trex/screenshot_trex.png | Bin 0 -> 668 bytes apps/vernierrespirate/ChangeLog | 1 + apps/vernierrespirate/README.md | 26 + apps/vernierrespirate/app-icon.js | 1 + apps/vernierrespirate/app.js | 256 + apps/vernierrespirate/app.png | Bin 0 -> 1986 bytes apps/waveclk/README.md | 4 + apps/wclock/screenshot_word.png | Bin 0 -> 3540 bytes apps/welcome/ChangeLog | 1 + apps/welcome/{app.js => app-bangle1.js} | 8 - apps/welcome/app-bangle2.js | 248 + apps/welcome/screenshot_welcome.png | Bin 0 -> 2618 bytes apps/widbat/ChangeLog | 1 + apps/widbat/widget.js | 29 +- apps/widbat/widget.png | Bin 297 -> 280 bytes apps/widbatv/ChangeLog | 1 + apps/widbatv/widget.js | 19 + apps/widbatv/widget.png | Bin 0 -> 221 bytes apps/widbt/ChangeLog | 1 + apps/widbt/widget.js | 30 +- apps/widcom/ChangeLog | 3 + apps/widcom/widget.js | 35 +- apps/widgps/ChangeLog | 1 + apps/widgps/widget.js | 6 +- apps/widhrm/ChangeLog | 1 + apps/widhrm/widget.js | 69 +- apps/widhrt/ChangeLog | 4 +- apps/widhrt/widget.js | 30 +- apps/widmp/ChangeLog | 2 + apps/widmp/widget.js | 41 +- apps/widtbat/ChangeLog | 1 + apps/widtbat/widget.js | 28 +- apps/widtbat/widget.png | Bin 911 -> 238 bytes apps/widver/ChangeLog | 2 + apps/widver/widget.js | 20 +- apps/worldclock/ChangeLog | 2 + apps/worldclock/app.js | 101 +- apps/worldclock/screenshot_world.png | Bin 0 -> 2937 bytes bin/create_app_supports_field.js | 99 + bin/firmwaremaker.js | 4 +- bin/firmwaremaker_c.js | 15 +- bin/sanitycheck.js | 31 +- bin/thumbnailer.js | 164 + core | 2 +- css/main.css | 37 +- css/spectre-exp.min.css | 2 +- css/spectre-icons.min.css | 2 +- css/spectre.min.css | 2 +- ...ultapps.json => defaultapps_banglejs1.json | 0 defaultapps_banglejs2.json | 1 + index.html | 15 + loader.js | 167 +- modules/Layout.js | 84 +- 300 files changed, 11197 insertions(+), 3283 deletions(-) create mode 100644 _config.yml create mode 100644 apps/a_battery_widget/ChangeLog create mode 100644 apps/a_battery_widget/README.md create mode 100644 apps/a_battery_widget/a_battery_widget-pic.jpg create mode 100644 apps/a_battery_widget/widget.js create mode 100644 apps/a_battery_widget/widget.png rename apps/about/{app.js => app-bangle1.js} (100%) create mode 100644 apps/about/app-bangle2.js create mode 100644 apps/aclock/README.md create mode 100644 apps/aclock/screenshot_analog.png create mode 100644 apps/android/ChangeLog create mode 100644 apps/android/app-icon.js create mode 100644 apps/android/app.js create mode 100644 apps/android/app.png create mode 100644 apps/android/boot.js create mode 100644 apps/astroid/screenshot_asteroids.png create mode 100644 apps/binwatch/Background176_center.png create mode 100644 apps/binwatch/Background240_center.png create mode 100644 apps/binwatch/ChangeLog create mode 100644 apps/binwatch/README.md create mode 100644 apps/binwatch/app-icon.js create mode 100644 apps/binwatch/app.js create mode 100644 apps/binwatch/app.png create mode 100644 apps/binwatch/bt-icon.png create mode 100644 apps/boldclk/README.md create mode 100644 apps/boldclk/screenshot_bold.png create mode 100644 apps/bthrm/ChangeLog create mode 100644 apps/bthrm/README.md create mode 100644 apps/bthrm/app-icon.js create mode 100644 apps/bthrm/app.png create mode 100644 apps/bthrm/boot.js create mode 100644 apps/calculator/screenshot_calculator.png create mode 100644 apps/calendar/screenshot_calendar.png create mode 100644 apps/chronowid/chrono_with_pastel.jpg create mode 100644 apps/chronowid/chrono_with_wave.jpg create mode 100644 apps/cliclockJS2Enhanced/app.icon.js create mode 100644 apps/cliclockJS2Enhanced/app.js create mode 100644 apps/cliclockJS2Enhanced/app.js.png create mode 100644 apps/cliclockJS2Enhanced/app.png create mode 100644 apps/cliclockJS2Enhanced/screengrab.png create mode 100644 apps/cliock/screenshot_cli.png create mode 100644 apps/clock2x3/README.md create mode 100644 apps/clock2x3/screenshot_pixel.png create mode 100644 apps/compass/screenshot_compass.png create mode 100644 apps/cubescramble/ChangeLog create mode 100644 apps/cubescramble/README.md create mode 100644 apps/cubescramble/cube-scramble-icon.js create mode 100644 apps/cubescramble/cube-scramble.js create mode 100644 apps/cubescramble/cube-scramble.png create mode 100644 apps/emojuino/ChangeLog create mode 100644 apps/emojuino/README.md create mode 100644 apps/emojuino/emojuino-icon.js create mode 100644 apps/emojuino/emojuino.js create mode 100644 apps/emojuino/emojuino.png create mode 100644 apps/ffcniftya/README.md create mode 100644 apps/ffcniftya/screenshot_nifty.png create mode 100644 apps/ffcniftyb/ChangeLog create mode 100644 apps/ffcniftyb/README.md create mode 100644 apps/ffcniftyb/app-icon.js create mode 100644 apps/ffcniftyb/app.js create mode 100644 apps/ffcniftyb/app.png create mode 100644 apps/ffcniftyb/screenshot.png create mode 100644 apps/ffcniftyb/settings.js create mode 100644 apps/flappy/README.md create mode 100644 apps/flappy/screenshot1_flappy.png create mode 100644 apps/flappy/screenshot2_flappy.png create mode 100644 apps/floralclk/README.md create mode 100644 apps/floralclk/screenshot_floral.png create mode 100644 apps/fwupdate/ChangeLog create mode 100644 apps/fwupdate/app.png create mode 100644 apps/fwupdate/custom.html create mode 100644 apps/gallifr/screenshot_time.png create mode 100644 apps/gpstouch/Changelog create mode 100644 apps/gpstouch/README.md create mode 100644 apps/gpstouch/geotools.js create mode 100644 apps/gpstouch/gpstouch.app.js create mode 100644 apps/gpstouch/gpstouch.icon.js create mode 100644 apps/gpstouch/gpstouch.png create mode 100644 apps/gpstouch/screenshot1.png create mode 100644 apps/gpstouch/screenshot2.png create mode 100644 apps/gpstouch/screenshot3.png create mode 100644 apps/gpstouch/screenshot4.png create mode 100644 apps/health/interface.html create mode 100644 apps/ios/ChangeLog create mode 100644 apps/ios/app-icon.js create mode 100644 apps/ios/app.js create mode 100644 apps/ios/app.png create mode 100644 apps/ios/boot.js rename apps/launch/{app.js => app-bangle1.js} (98%) create mode 100644 apps/launch/app-bangle2.js delete mode 100644 apps/launchb2/ChangeLog delete mode 100644 apps/launchb2/app.js delete mode 100644 apps/launchb2/app.png create mode 100644 apps/lcars/ChangeLog create mode 100644 apps/lcars/README.md create mode 100644 apps/lcars/background.png create mode 100644 apps/lcars/lcars.app.js create mode 100644 apps/lcars/lcars.icon.js create mode 100644 apps/lcars/lcars.png create mode 100644 apps/matrixclock/screenshot_matrix.png create mode 100644 apps/messages/ChangeLog create mode 100644 apps/messages/README.md create mode 100644 apps/messages/app-icon.js create mode 100644 apps/messages/app.js create mode 100644 apps/messages/app.png create mode 100644 apps/messages/lib.js create mode 100644 apps/messages/widget.js rename apps/multiclock/{ana.js => ana.face.js} (59%) delete mode 100644 apps/multiclock/anaface.jpg delete mode 100644 apps/multiclock/apps_entry.json create mode 100644 apps/multiclock/big.face.js delete mode 100644 apps/multiclock/big.js delete mode 100644 apps/multiclock/bigface.jpg create mode 100644 apps/multiclock/clock.info delete mode 100644 apps/multiclock/clock.js create mode 100644 apps/multiclock/digi.face.js delete mode 100644 apps/multiclock/digi.js delete mode 100644 apps/multiclock/digiface.jpg create mode 100644 apps/multiclock/dk.face.js create mode 100644 apps/multiclock/multiclock-icon.img create mode 100644 apps/multiclock/multiclock.app.js create mode 100644 apps/multiclock/nifty.face.js delete mode 100644 apps/multiclock/ped.js delete mode 100644 apps/multiclock/timdat.js rename apps/multiclock/{txt.js => txt.face.js} (53%) delete mode 100644 apps/multiclock/txtface.jpg create mode 100644 apps/pastel/screenshot_elite.jpg create mode 100644 apps/pastel/screenshot_monoton.jpg create mode 100644 apps/pastel/screenshot_pastel.png create mode 100644 apps/qalarm/ChangeLog create mode 100644 apps/qalarm/app-icon.js create mode 100644 apps/qalarm/app.js create mode 100644 apps/qalarm/app.png create mode 100644 apps/qalarm/boot.js create mode 100644 apps/qalarm/qalarm.js create mode 100644 apps/qalarm/qalarmcheck.js create mode 100644 apps/qalarm/widget.js create mode 100644 apps/recorder/ChangeLog create mode 100644 apps/recorder/README.md create mode 100644 apps/recorder/app-icon.js create mode 100644 apps/recorder/app-settings.json create mode 100644 apps/recorder/app.js create mode 100644 apps/recorder/app.png create mode 100644 apps/recorder/interface.html create mode 100644 apps/recorder/settings.js create mode 100644 apps/recorder/widget.js create mode 100644 apps/s7clk/README.md create mode 100644 apps/s7clk/screenshot_s7segment.png create mode 100644 apps/sclock/screenshot_simplec.png create mode 100644 apps/score/screenshot_score.png create mode 100644 apps/simplest/screenshot_simplest.png create mode 100644 apps/slomoclock/ChangeLog create mode 100644 apps/slomoclock/README.md create mode 100644 apps/slomoclock/app-icon.js create mode 100644 apps/slomoclock/app.js create mode 100644 apps/slomoclock/settings.js create mode 100644 apps/slomoclock/watch.png create mode 100644 apps/speedalt2/ChangeLog create mode 100644 apps/speedalt2/README.md create mode 100644 apps/speedalt2/app-icon.js create mode 100644 apps/speedalt2/app.js create mode 100644 apps/speedalt2/app.png create mode 100644 apps/speedalt2/settings.js create mode 100644 apps/stopwatch/A.jpg create mode 100644 apps/stopwatch/B.jpg create mode 100644 apps/stopwatch/ChangeLog create mode 100644 apps/stopwatch/README.md create mode 100644 apps/stopwatch/pause-24.png create mode 100644 apps/stopwatch/pause-24a.png create mode 100644 apps/stopwatch/play-24.png create mode 100644 apps/stopwatch/screenshot1.png create mode 100644 apps/stopwatch/screenshot2.png create mode 100644 apps/stopwatch/screenshot3.png create mode 100644 apps/stopwatch/stop-24.png create mode 100644 apps/stopwatch/stop-24a.png create mode 100644 apps/stopwatch/stopwatch.app.js create mode 100644 apps/stopwatch/stopwatch.icon.js create mode 100644 apps/stopwatch/stopwatch.png create mode 100644 apps/swiperclocklaunch/ChangeLog create mode 100644 apps/swiperclocklaunch/boot.js create mode 100644 apps/swiperclocklaunch/icon.js create mode 100644 apps/swiperclocklaunch/swiperclocklaunch.png create mode 100644 apps/toucher/README.md create mode 100644 apps/toucher/screenshot1.jpg create mode 100644 apps/trex/README.md create mode 100644 apps/trex/screenshot_trex.png create mode 100644 apps/vernierrespirate/ChangeLog create mode 100644 apps/vernierrespirate/README.md create mode 100644 apps/vernierrespirate/app-icon.js create mode 100644 apps/vernierrespirate/app.js create mode 100644 apps/vernierrespirate/app.png create mode 100644 apps/waveclk/README.md create mode 100644 apps/wclock/screenshot_word.png rename apps/welcome/{app.js => app-bangle1.js} (97%) create mode 100644 apps/welcome/app-bangle2.js create mode 100644 apps/welcome/screenshot_welcome.png create mode 100644 apps/widbatv/ChangeLog create mode 100644 apps/widbatv/widget.js create mode 100644 apps/widbatv/widget.png create mode 100644 apps/widcom/ChangeLog create mode 100644 apps/worldclock/screenshot_world.png create mode 100644 bin/create_app_supports_field.js create mode 100755 bin/thumbnailer.js rename defaultapps.json => defaultapps_banglejs1.json (100%) create mode 100644 defaultapps_banglejs2.json diff --git a/README.md b/README.md index 49f616964..ac80b8270 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ Bangle.js App Loader (and Apps) ================================ -[![Build Status](https://travis-ci.org/espruino/BangleApps.svg?branch=master)](https://travis-ci.org/espruino/BangleApps) +[![Build Status](https://app.travis-ci.com/espruino/BangleApps.svg?branch=master)](https://app.travis-ci.com/github/espruino/BangleApps) * Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps) -* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/) +* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/) **All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By submitting code to this repository you confirm that you are happy with it being MIT licensed, @@ -49,25 +49,25 @@ easily distinguish between file types, we use the following: ## Adding your app to the menu -* Come up with a unique (all lowercase, no spaces) name, we'll assume `7chname`. Bangle.js +* Come up with a unique (all lowercase, no spaces) name, we'll assume `myappid`. Bangle.js is limited to 28 char filenames and appends a file extension (eg `.js`) so please try and keep filenames short to avoid overflowing the buffer. -* Create a folder called `apps/`, lets assume `apps/7chname` +* Create a folder called `apps/`, lets assume `apps/myappid` * We'd recommend that you copy files from 'Example Applications' (below) as a base, or... -* `apps/7chname/app.png` should be a 48px icon -* Use http://www.espruino.com/Image+Converter to create `apps/7chname/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String" +* `apps/myappid/app.png` should be a 48px icon +* Use http://www.espruino.com/Image+Converter to create `apps/myappid/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String" * Create an entry in `apps.json` as follows: ``` -{ "id": "7chname", +{ "id": "myappid", "name": "My app's human readable name", "shortName" : "Short Name", "icon": "app.png", "description": "A detailed description of my great app", "tags": "", "storage": [ - {"name":"7chname.app.js","url":"app.js"}, - {"name":"7chname.img","url":"app-icon.js","evaluate":true} + {"name":"myappid.app.js","url":"app.js"}, + {"name":"myappid.img","url":"app-icon.js","evaluate":true} ], }, ``` @@ -95,12 +95,12 @@ Be aware of the delay between commits and updates on github.io - it can take a f Using the 'Storage' icon in [the Web IDE](https://www.espruino.com/ide/) (4 discs), upload your files into the places described in your JSON: -* `app-icon.js` -> `7chname.img` +* `app-icon.js` -> `myappid.img` Now load `app.js` up in the editor, and click the down-arrow to the bottom right of the `Send to Espruino` icon. Click `Storage` and then either choose -`7chname.app.js` (if you'd uploaded your app previously), or `New File` -and then enter `7chname.app.js` as the name. +`myappid.app.js` (if you'd uploaded your app previously), or `New File` +and then enter `myappid.app.js` as the name. Now, clicking the `Send to Espruino` icon will load the app directly into Espruino **and** will automatically run it. @@ -115,10 +115,13 @@ and set it to `Load default application`. ## Example Applications To make the process easier we've come up with some example applications that you can use as a base -when creating your own. Just come up with a unique 7 character name, copy `apps/_example_app` -or `apps/_example_widget` to `apps/7chname`, and add `apps/_example_X/add_to_apps.json` to +when creating your own. Just come up with a unique name (ideally lowercase, under 20 chars), copy `apps/_example_app` +or `apps/_example_widget` to `apps/myappid`, and add `apps/_example_X/add_to_apps.json` to `apps.json`. +**Note:** the max filename length is 28 chars, so we suggest an app ID of under +20 so that when `.app.js`/etc gets added to the end the filename isn't cropped. + **If you're making a widget** please start the name with `wid` to make it easy to find! @@ -192,8 +195,8 @@ and which gives information about the app for the Launcher. ``` { "name":"Short Name", // for Bangle.js menu - "icon":"*7chname", // for Bangle.js menu - "src":"-7chname", // source file + "icon":"*myappid", // for Bangle.js menu + "src":"-myappid", // source file "type":"widget/clock/app/bootloader", // optional, default "app" // if this is 'widget' then it's not displayed in the menu // if it's 'clock' then it'll be loaded by default at boot time @@ -217,8 +220,10 @@ and which gives information about the app for the Launcher. { "id": "appid", // 7 character app id "name": "Readable name", // readable name "shortName": "Short name", // short name for launcher - "icon": "icon.png", // icon in apps/ + "version": "0v01", // the version of this app "description": "...", // long description (can contain markdown) + "icon": "icon.png", // icon in apps/ + "screenshots" : [ { url:"screenshot.png" } ], // optional screenshot for app "type":"...", // optional(if app) - // 'app' - an application // 'widget' - a widget @@ -226,7 +231,9 @@ and which gives information about the app for the Launcher. // 'bootloader' - code that runs at startup only // 'RAM' - code that runs and doesn't upload anything to storage "tags": "", // comma separated tag list for searching + "supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2 "dependencies" : { "notify":"type" } // optional, app 'types' we depend on + "dependencies" : { "messages":"app" } // optional, depend on a specific app ID // for instance this will use notify/notifyfs is they exist, or will pull in 'notify' "readme": "README.md", // if supplied, a link to a markdown-style text file // that contains more information about this app (usage, etc) @@ -259,6 +266,9 @@ and which gives information about the app for the Launcher. // (eg it's evaluated as JS) "noOverwrite":true // if supplied, this file will not be overwritten if it // already exists + "supports": ["BANGLEJS2"]// if supplied, this file will ONLY be uploaded to the device + // types named in the array. This allows different versions of + // the app to be uploaded for different platforms }, ] "data": [ // list of files the app writes to @@ -306,10 +316,10 @@ version of what's in `apps.json`: + + + + diff --git a/apps/gallifr/screenshot_time.png b/apps/gallifr/screenshot_time.png new file mode 100644 index 0000000000000000000000000000000000000000..2754138c45a49d8f7aa866525744342cd9b19047 GIT binary patch literal 3807 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 } + ]}); + + +function onGPS(f) { + if (fix===undefined) { + g.clear(); + Bangle.drawWidgets(); + } + fix = f; + if (fix.fix) { + layout.status.label = "GPS\nAcquired"; + } else { + layout.status.label = "Waiting\nfor GPS"; + } + layout.sat.label = fix.satellites+" satellites"; + + var t = ["","---",""]; + if (fix.time!==undefined) { t = fix.time.toString().split(" "); - /* - [ - "Sun", - "Nov", - "10", - "2019", - "15:55:35", - "GMT+0100" - ] - */ - //g.setFont("6x8",2); - //g.drawString(t[0],120,110); // day - g.setFont("6x8",3); - g.drawString(t[1]+" "+t[2],120,135); // date - g.setFont("6x8",2); - g.drawString(t[3],120,160); // year - g.setFont("6x8",3); - g.drawString(t[4],120,185); // time - if (fix.time) { - // timezone var tz = (new Date()).getTimezoneOffset()/-60; if (tz==0) tz="UTC"; else if (tz>0) tz="UTC+"+tz; else tz="UTC"+tz; - g.setFont("6x8",2); - g.drawString(tz,120,210); // gmt - g.setFontAlign(0,0,3); - g.drawString("Set",230,120); - g.setFontAlign(0,0); - } -}); -setInterval(function() { - g.drawImage(img,48,48,{scale:1.5,rotate:Math.sin(getTime()*2)/2}); -},100); -setWatch(function() { - if (fix.time!==undefined) - setTime(fix.time.getTime()/1000); -}, BTN2, {repeat:true}); + t = [t[1]+" "+t[2],t[3],t[4],t[5],tz]; + } + + layout.gpstime.label = t.join("\n"); + layout.render(); +} + +Bangle.on('GPS',onGPS); diff --git a/apps/gpstouch/Changelog b/apps/gpstouch/Changelog new file mode 100644 index 000000000..7f837e50e --- /dev/null +++ b/apps/gpstouch/Changelog @@ -0,0 +1 @@ +0.01: First version diff --git a/apps/gpstouch/README.md b/apps/gpstouch/README.md new file mode 100644 index 000000000..7329f9833 --- /dev/null +++ b/apps/gpstouch/README.md @@ -0,0 +1,16 @@ +# GPS Touch + +- A touch controlled GPS watch for Bangle JS 2 +- Key feature is the conversion of Lat/Lon into Ordinance Servey Grid Reference +- Swipe left and right to change the display +- Select GPS and switch the GPS On or Off by touching twice in the top half of the display +- Select LOGGER and switch the GPS Recorder On or Off by touching twice in the top half of the display +- Displays the GPS time in the bottom half of the screen when the GPS is powered on, otherwise 00:00:00 +- Select display of Course, Speed, Altitude, Longitude, Latitude, Ordinance Servey Grid Reference + +## Screenshots + +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) +![](screenshot4.png) diff --git a/apps/gpstouch/geotools.js b/apps/gpstouch/geotools.js new file mode 100644 index 000000000..5adc57872 --- /dev/null +++ b/apps/gpstouch/geotools.js @@ -0,0 +1,128 @@ +/** + * + * A module of Geo functions for use with gps fixes + * + * let geo = require("geotools"); + * let os = geo.gpsToOSGrid(fix); + * let ref = geo.gpsToOSMapRef(fix); + * + */ + +Number.prototype.toRad = function() { return this*Math.PI/180; }; +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Ordnance Survey Grid Reference functions (c) Chris Veness 2005-2014 */ +/* - www.movable-type.co.uk/scripts/gridref.js */ +/* - www.movable-type.co.uk/scripts/latlon-gridref.html */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +function OsGridRef(easting, northing) { + this.easting = 0|easting; + this.northing = 0|northing; +} +OsGridRef.latLongToOsGrid = function(point) { + var lat = point.lat.toRad(); + var lon = point.lon.toRad(); + + var a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes + var F0 = 0.9996012717; // NatGrid scale factor on central meridian + var lat0 = (49).toRad(), lon0 = (-2).toRad(); // NatGrid true origin is 49�N,2�W + var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres + var e2 = 1 - (b*b)/(a*a); // eccentricity squared + var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n; + + var cosLat = Math.cos(lat), sinLat = Math.sin(lat); + var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature + var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature + var eta2 = nu/rho-1; + + var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0); + var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0); + var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0)); + var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0)); + var M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc + + var cos3lat = cosLat*cosLat*cosLat; + var cos5lat = cos3lat*cosLat*cosLat; + var tan2lat = Math.tan(lat)*Math.tan(lat); + var tan4lat = tan2lat*tan2lat; + + var I = M + N0; + var II = (nu/2)*sinLat*cosLat; + var III = (nu/24)*sinLat*cos3lat*(5-tan2lat+9*eta2); + var IIIA = (nu/720)*sinLat*cos5lat*(61-58*tan2lat+tan4lat); + var IV = nu*cosLat; + var V = (nu/6)*cos3lat*(nu/rho-tan2lat); + var VI = (nu/120) * cos5lat * (5 - 18*tan2lat + tan4lat + 14*eta2 - 58*tan2lat*eta2); + + var dLon = lon-lon0; + var dLon2 = dLon*dLon, dLon3 = dLon2*dLon, dLon4 = dLon3*dLon, dLon5 = dLon4*dLon, dLon6 = dLon5*dLon; + + var N = I + II*dLon2 + III*dLon4 + IIIA*dLon6; + var E = E0 + IV*dLon + V*dLon3 + VI*dLon5; + + return new OsGridRef(E, N); +}; + +/* + * converts northing, easting to standard OS grid reference. + * + * [digits=10] - precision (10 digits = metres) + * to_map_ref(8, 651409, 313177); => 'TG 5140 1317' + * to_map_ref(0, 651409, 313177); => '651409,313177' + * + */ +function to_map_ref(digits, easting, northing) { + if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing + + let e = easting; + let n = northing; + + // use digits = 0 to return numeric format (in metres) - note northing may be >= 1e7 + if (digits == 0) { + const format = { useGrouping: false, minimumIntegerDigits: 6, maximumFractionDigits: 3 }; + const ePad = e.toLocaleString('en', format); + const nPad = n.toLocaleString('en', format); + return `${ePad},${nPad}`; + } + + // get the 100km-grid indices + const e100km = Math.floor(e / 100000), n100km = Math.floor(n / 100000); + + // translate those into numeric equivalents of the grid letters + let l1 = (19 - n100km) - (19 - n100km) % 5 + Math.floor((e100km + 10) / 5); + let l2 = (19 - n100km) * 5 % 25 + e100km % 5; + + // compensate for skipped 'I' and build grid letter-pairs + if (l1 > 7) l1++; + if (l2 > 7) l2++; + const letterPair = String.fromCharCode(l1 + 'A'.charCodeAt(0), l2 + 'A'.charCodeAt(0)); + + // strip 100km-grid indices from easting & northing, and reduce precision + e = Math.floor((e % 100000) / Math.pow(10, 5 - digits / 2)); + n = Math.floor((n % 100000) / Math.pow(10, 5 - digits / 2)); + + // pad eastings & northings with leading zeros + e = e.toString().padStart(digits/2, '0'); + n = n.toString().padStart(digits/2, '0'); + + return `${letterPair} ${e} ${n}`; +} + +/** + * + * Module exports section, example code below + * + * let geo = require("geotools"); + * let os = geo.gpsToOSGrid(fix); + * let ref = geo.gpsToOSMapRef(fix); + */ + +// get easting and northings +exports.gpsToOSGrid = function(gps_fix) { + return OsGridRef.latLongToOsGrid(gps_fix); +} + +// string with an OS Map grid reference +exports.gpsToOSMapRef = function(gps_fix) { + let os = OsGridRef.latLongToOsGrid(last_fix); + return to_map_ref(6, os.easting, os.northing); +} diff --git a/apps/gpstouch/gpstouch.app.js b/apps/gpstouch/gpstouch.app.js new file mode 100644 index 000000000..4e49dd1e5 --- /dev/null +++ b/apps/gpstouch/gpstouch.app.js @@ -0,0 +1,246 @@ +const h = g.getHeight(); +const w = g.getWidth(); +let geo = require("geotools"); +let last_fix; +let listennerCount = 0; + +function log_debug(o) { + //console.log(o); +} + +function resetLastFix() { + last_fix = { + fix: 0, + alt: 0, + lat: 0, + lon: 0, + speed: 0, + time: 0, + course: 0, + satellites: 0 + }; +} + +function processFix(fix) { + last_fix.time = fix.time; + log_debug(fix); + + if (fix.fix) { + if (!last_fix.fix) { + // we dont need to suppress this in quiet mode as it is user initiated + Bangle.buzz(1500); // buzz on first position + } + last_fix = fix; + } +} + +function draw() { + var d = new Date(); + var da = d.toString().split(" "); + var time = da[4].substr(0,5); + var hh = da[4].substr(0,2); + var mm = da[4].substr(3,2); + + g.reset(); + drawTop(d,hh,mm); + drawInfo(); +} + +function drawTop(d,hh,mm) { + g.setFont("Vector", w/3); + g.setFontAlign(0, 0); + g.setColor(g.theme.bg); + g.fillRect(0, 24, w, ((h-24)/2) + 24); + g.setColor(g.theme.fg); + + g.setFontAlign(1,0); // right aligned + g.drawString(hh, (w/2) - 6, ((h-24)/4) + 24); + g.setFontAlign(-1,0); // left aligned + g.drawString(mm, (w/2) + 6, ((h-24)/4) + 24); + + // for the colon + g.setFontAlign(0,0); // centre aligned + if (d.getSeconds()&1) g.drawString(":", w/2, ((h-24)/4) + 24); +} + +function drawInfo() { + if (infoData[infoMode] && infoData[infoMode].calc) { + g.setFont("Vector", w/7); + g.setFontAlign(0, 0); + + if (infoData[infoMode].get_color) + g.setColor(infoData[infoMode].get_color()); + else + g.setColor("#0ff"); + g.fillRect(0, ((h-24)/2) + 24 + 1, w, h); + + if (infoData[infoMode].is_control) + g.setColor("#fff"); + else + g.setColor("#000"); + + g.drawString((infoData[infoMode].calc()), w/2, (3*(h-24)/4) + 24); + } +} + +const infoData = { + ID_LAT: { + calc: () => 'Lat: ' + last_fix.lat.toFixed(4), + }, + ID_LON: { + calc: () => 'Lon: ' + last_fix.lon.toFixed(4), + }, + ID_SPEED: { + calc: () => 'Speed: ' + last_fix.speed.toFixed(1), + }, + ID_ALT: { + calc: () => 'Alt: ' + last_fix.alt.toFixed(0), + }, + ID_COURSE: { + calc: () => 'Course: '+ last_fix.course.toFixed(0), + }, + ID_SATS: { + calc: () => 'Satelites: ' + last_fix.satellites, + }, + ID_TIME: { + calc: () => formatTime(last_fix.time), + }, + OS_REF: { + calc: () => !last_fix.fix ? "OO 000 000" : geo.gpsToOSMapRef(last_fix), + }, + GPS_POWER: { + calc: () => (Bangle.isGPSOn()) ? 'GPS On' : 'GPS Off', + action: () => toggleGPS(), + get_color: () => Bangle.isGPSOn() ? '#f00' : '#00f', + is_control: true, + }, + GPS_LOGGER: { + calc: () => 'Logger ' + loggerStatus(), + action: () => toggleLogger(), + get_color: () => loggerStatus() == "ON" ? '#f00' : '#00f', + is_control: true, + }, +}; + +function toggleGPS() { + if (loggerStatus() == "ON") + return; + + Bangle.setGPSPower(Bangle.isGPSOn() ? 0 : 1, 'gpstouch'); + // add or remove listenner + if (Bangle.isGPSOn()) { + if (listennerCount == 0) { + Bangle.on('GPS', processFix); + listennerCount++; + log_debug("listennerCount=" + listennerCount); + } + } else { + if (listennerCount > 0) { + Bangle.removeListener("GPS", processFix); + listennerCount--; + log_debug("listennerCount=" + listennerCount); + } + } + resetLastFix(); +} + +function loggerStatus() { + var settings = require("Storage").readJSON("gpsrec.json",1)||{}; + if (settings == {}) return "Install"; + return settings.recording ? "ON" : "OFF"; +} + +function toggleLogger() { + var settings = require("Storage").readJSON("gpsrec.json",1)||{}; + if (settings == {}) return; + + settings.recording = !settings.recording; + require("Storage").write("gpsrec.json", settings); + + if (WIDGETS["gpsrec"]) + WIDGETS["gpsrec"].reload(); + + if (settings.recording && listennerCount == 0) { + Bangle.on('GPS', processFix); + listennerCount++; + log_debug("listennerCount=" + listennerCount); + } +} + +function formatTime(now) { + try { + var fd = now.toUTCString().split(" "); + return fd[4]; + } catch (e) { + return "00:00:00"; + } +} + +const infoList = Object.keys(infoData).sort(); +let infoMode = infoList[0]; + +function nextInfo() { + let idx = infoList.indexOf(infoMode); + if (idx > -1) { + if (idx === infoList.length - 1) infoMode = infoList[0]; + else infoMode = infoList[idx + 1]; + } +} + +function prevInfo() { + let idx = infoList.indexOf(infoMode); + if (idx > -1) { + if (idx === 0) infoMode = infoList[infoList.length - 1]; + else infoMode = infoList[idx - 1]; + } +} + +Bangle.on('swipe', dir => { + if (dir == 1) prevInfo(); else nextInfo(); + draw(); +}); + +let prevTouch = 0; + +Bangle.on('touch', function(button, xy) { + let dur = 1000*(getTime() - prevTouch); + prevTouch = getTime(); + + if (dur <= 1000 && xy.y < h/2 && infoData[infoMode].is_control) { + Bangle.buzz(); + if (infoData[infoMode] && infoData[infoMode].action) { + infoData[infoMode].action(); + draw(); + } + } +}); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower', on => { + if (secondInterval) + clearInterval(secondInterval); + secondInterval = undefined; + if (on) + secondInterval = setInterval(draw, 1000); + draw(); +}); + +resetLastFix(); + +// add listenner if already powered on, plus tag app +if (Bangle.isGPSOn() || loggerStatus() == "ON") { + Bangle.setGPSPower(1, 'gpstouch'); + if (listennerCount == 0) { + Bangle.on('GPS', processFix); + listennerCount++; + log_debug("listennerCount=" + listennerCount); + } +} + +g.clear(); +var secondInterval = setInterval(draw, 1000); +draw(); +// Show launcher when button pressed +Bangle.setUI("clock"); +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/gpstouch/gpstouch.icon.js b/apps/gpstouch/gpstouch.icon.js new file mode 100644 index 000000000..c4cf85676 --- /dev/null +++ b/apps/gpstouch/gpstouch.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///j+EAYO/uYDB//wCYcPBA4AFh/ABZMDBbkX6gLIgtX6tQBY9VBYNVBY0BBYdABYsFqoACEgQLDitVtWpqtUBYtVq2q1WVGAQLErQLB0oLFHQNqBYIkBHgMDIwYKBAAJIDIweqz/2BYJtDBYI6Bv/9HgILHYwILGh4gBBYWfbooLF6AjPBYW//wLGL4Wv/RfGNZaDIBYibEBYizIBYjLDBYzXBd4TXCBZ60BBYRqEBZpUBBYRSFJAQLCA4b7BHgQLFgYLGIwYLEgoLBHQYLEgILBHQYLEgALBAoYLFi/UBZMHBZUD6ALKApQAFBbHwBZMP/4ABBwgIDA=")) diff --git a/apps/gpstouch/gpstouch.png b/apps/gpstouch/gpstouch.png new file mode 100644 index 0000000000000000000000000000000000000000..c411356ae69347a83882fe195782df211aa629e9 GIT binary patch literal 1571 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 literal 0 HcmV?d00001 diff --git a/apps/hcclock/ChangeLog b/apps/hcclock/ChangeLog index 0ca30d066..aaa55d01a 100644 --- a/apps/hcclock/ChangeLog +++ b/apps/hcclock/ChangeLog @@ -1,2 +1,2 @@ 0.01: base code - +0.02: saved settings when switching color scheme \ No newline at end of file diff --git a/apps/hcclock/hcclock.app.js b/apps/hcclock/hcclock.app.js index 98abbc6f3..4664dd763 100644 --- a/apps/hcclock/hcclock.app.js +++ b/apps/hcclock/hcclock.app.js @@ -174,19 +174,52 @@ function fmtDate(day,month,year,hour) let ap = "(AM)"; if(hour == 0 || hour > 12) ap = "(PM)"; - return months[month] + " " + day + " " + year + " "+ ap; + return months[month] + " " + day + " " + year + " "+ ap; } else return months[month] + ". " + day + " " + year; } -// Handles Flipping colors, then refreshes the UI + +////////////////////////////////////////// +// +// HANDLE COLORS + SETTINGS +// + +function getColorScheme() +{ + let settings = require('Storage').readJSON("hcclock.json", true) || {}; + if (!("scheme" in settings)) { + settings.scheme = 0; + } + return settings.scheme; +} + +function setColorScheme(value) +{ + let settings = require('Storage').readJSON("hcclock.json", true) || {}; + settings.scheme = value; + require('Storage').writeJSON('hcclock.json', settings); + + if(value == 0) // White + { + bg = 255; + fg = 0; + } + else // Black + { + bg = 0; + fg = 255; + } + redraw(); +} + function flipColors() { - let t = bg; - bg = fg; - fg = t; - redraw(); + if(getColorScheme() == 0) + setColorScheme(1); + else + setColorScheme(0); } ////////////////////////////////////////// @@ -197,7 +230,7 @@ function flipColors() // Initialize g.clear(); Bangle.loadWidgets(); -redraw(); +setColorScheme(getColorScheme()); // Define Refresh Interval setInterval(updateTime, interval); diff --git a/apps/health/ChangeLog b/apps/health/ChangeLog index 5560f00bc..5eb96a0ea 100644 --- a/apps/health/ChangeLog +++ b/apps/health/ChangeLog @@ -1 +1,8 @@ 0.01: New App! +0.02: Modified data format to include daily summaries +0.03: Settings to turn HRM on +0.04: Add HRM graph view + Don't restart HRM when changing apps if we've already got a good BPM value +0.05: Fix daily summary calculation +0.06: Fix daily health summary for movement (a line got deleted!) +0.07: Added coloured bar charts diff --git a/apps/health/README.md b/apps/health/README.md index 0ba0d8228..c69e2e45b 100644 --- a/apps/health/README.md +++ b/apps/health/README.md @@ -14,10 +14,18 @@ To view data, run the `Health` app from your watch. Stores: -* Heart rate (TODO) +* Heart rate * Step count * Movement +## Settings + +* **Heart Rt** - Whether to monitor heart rate or not + * **Off** - Don't turn HRM on, but record heart rate if the HRM was turned on by another app/widget + * **10 Min** - Turn HRM on every 10 minutes (for each heath entry) and turn it off after 2 minutes, or when a good reading is found + * **Always** - Keep HRM on all the time (more accurate recording, but reduces battery life to ~36 hours) + + ## Technical Info Once installed, the `health.boot.js` hooks onto the `Bangle.health` event and @@ -28,7 +36,6 @@ to grab historical health info. ## TODO -* **Extend file format to include combined data for each day (to make graphs faster)** * `interface` page for desktop to allow data to be viewed and exported in common formats * More features in app: * Step counting goal (ensure pedometers use this) diff --git a/apps/health/app.js b/apps/health/app.js index f2df52972..eae45c190 100644 --- a/apps/health/app.js +++ b/apps/health/app.js @@ -1,28 +1,74 @@ +function getSettings() { + return require("Storage").readJSON("health.json",1)||{}; +} + +function setSettings(s) { + require("Storage").writeJSON("health.json",s); +} + function menuMain() { + swipe_enabled = false; + clearButton(); E.showMenu({ "":{title:"Health Tracking"}, "< Back":()=>load(), "Step Counting":()=>menuStepCount(), - "Movement":()=>menuMovement() + "Movement":()=>menuMovement(), + "Heart Rate":()=>menuHRM(), + "Settings":()=>menuSettings() + }); +} + +function menuSettings() { + swipe_enabled = false; + clearButton(); + var s=getSettings(); + E.showMenu({ + "":{title:"Health Tracking"}, + "< Back":()=>menuMain(), + "Heart Rt":{ + value : 0|s.hrm, + min : 0, max : 2, + format : v=>["Off","10 mins","Always"][v], + onchange : v => { s.hrm=v;setSettings(s); } + } }); } function menuStepCount() { + swipe_enabled = false; + clearButton(); E.showMenu({ "":{title:"Step Counting"}, "< Back":()=>menuMain(), - "per hour":()=>stepsPerHour() + "per hour":()=>stepsPerHour(), + "per day":()=>stepsPerDay() }); } function menuMovement() { + swipe_enabled = false; + clearButton(); E.showMenu({ "":{title:"Movement"}, "< Back":()=>menuMain(), - "per hour":()=>movementPerHour() + "per hour":()=>movementPerHour(), + "per day":()=>movementPerDay(), }); } +function menuHRM() { + swipe_enabled = false; + clearButton(); + E.showMenu({ + "":{title:"Heart Rate"}, + "< Back":()=>menuMain(), + "per hour":()=>hrmPerHour(), + "per day":()=>hrmPerDay(), + }); +} + + function stepsPerHour() { E.showMessage("Loading..."); var data = new Uint16Array(24); @@ -30,14 +76,51 @@ function stepsPerHour() { g.clear(1); Bangle.drawWidgets(); g.reset(); - require("graph").drawBar(g, data, { - y:24, - miny: 0, - axes : true, - gridx : 6, - gridy : 500 + setButton(menuStepCount); + barChart("HOUR", data); +} + +function stepsPerDay() { + E.showMessage("Loading..."); + var data = new Uint16Array(31); + require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.steps); + g.clear(1); + Bangle.drawWidgets(); + g.reset(); + setButton(menuStepCount); + barChart("DAY", data); +} + +function hrmPerHour() { + E.showMessage("Loading..."); + var data = new Uint16Array(24); + var cnt = new Uint8Array(23); + require("health").readDay(new Date(), h=>{ + data[h.hr]+=h.bpm; + if (h.bpm) cnt[h.hr]++; }); - Bangle.setUI("updown", ()=>menuStepCount()); + data.forEach((d,i)=>data[i] = d/cnt[i]); + g.clear(1); + Bangle.drawWidgets(); + g.reset(); + setButton(menuHRM); + barChart("HOUR", data); +} + +function hrmPerDay() { + E.showMessage("Loading..."); + var data = new Uint16Array(31); + var cnt = new Uint8Array(31); + require("health").readDailySummaries(new Date(), h=>{ + data[h.day]+=h.bpm; + if (h.bpm) cnt[h.day]++; + }); + data.forEach((d,i)=>data[i] = d/cnt[i]); + g.clear(1); + Bangle.drawWidgets(); + g.reset(); + setButton(menuHRM); + barChart("DAY", data); } function movementPerHour() { @@ -47,14 +130,123 @@ function movementPerHour() { g.clear(1); Bangle.drawWidgets(); g.reset(); - require("graph").drawLine(g, data, { - y:24, - miny: 0, - axes : true, - gridx : 6, - ylabel : null - }); - Bangle.setUI("updown", ()=>menuStepCount()); + setButton(menuMovement); + barChart("HOUR", data); +} + +function movementPerDay() { + E.showMessage("Loading..."); + var data = new Uint16Array(31); + require("health").readDailySummaries(new Date(), h=>data[h.day]+=h.movement); + g.clear(1); + Bangle.drawWidgets(); + g.reset(); + setButton(menuMovement); + barChart("DAY", data); +} + +// Bar Chart Code + +const w = g.getWidth(); +const h = g.getHeight(); + +var data_len; +var chart_index; +var chart_max_datum; +var chart_label; +var chart_data; +var swipe_enabled = false; +var btn; + +// find the max value in the array, using a loop due to array size +function max(arr) { + var m = -Infinity; + + for(var i=0; i< arr.length; i++) + if(arr[i] > m) m = arr[i]; + return m; +} + +// find the end of the data, the array might be for 31 days but only have 2 days of data in it +function get_data_length(arr) { + var nlen = arr.length; + + for(var i = arr.length - 1; i > 0 && arr[i] == 0; i--) + nlen--; + + return nlen; +} + +function barChart(label, dt) { + data_len = get_data_length(dt); + chart_index = Math.max(data_len - 5, -5); // choose initial index that puts the last day on the end + chart_max_datum = max(dt); // find highest bar, for scaling + chart_label = label; + chart_data = dt; + drawBarChart(); + swipe_enabled = true; +} + +function drawBarChart() { + const bar_bot = 140; + const bar_width = (w - 2) / 9; // we want 9 bars, bar 5 in the centre + var bar_top; + var bar; + + g.setColor(g.theme.bg); + g.fillRect(0,24,w,h); + + for (bar = 1; bar < 10; bar++) { + if (bar == 5) { + g.setFont('6x8', 2); + g.setFontAlign(0,-1) + g.setColor(g.theme.fg); + g.drawString(chart_label + " " + (chart_index + bar -1) + " " + chart_data[chart_index + bar - 1], g.getWidth()/2, 150); + g.setColor("#00f"); + } else { + g.setColor("#0ff"); + } + + // draw a fake 0 height bar if chart_index is outside the bounds of the array + if ((chart_index + bar - 1) >= 0 && (chart_index + bar - 1) < data_len) + bar_top = bar_bot - 100 * (chart_data[chart_index + bar - 1]) / chart_max_datum; + else + bar_top = bar_bot; + + g.fillRect( 1 + (bar - 1)* bar_width, bar_bot, 1 + bar*bar_width, bar_top); + g.setColor(g.theme.fg); + g.drawRect( 1 + (bar - 1)* bar_width, bar_bot, 1 + bar*bar_width, bar_top); + } +} + +function next_bar() { + chart_index = Math.min(data_len - 5, chart_index + 1); +} + +function prev_bar() { + // HOUR data starts at index 0, DAY data starts at index 1 + chart_index = Math.max((chart_label == "DAY") ? -3 : -4, chart_index - 1); +} + +Bangle.on('swipe', dir => { + if (!swipe_enabled) return; + if (dir == 1) prev_bar(); else next_bar(); + drawBarChart(); +}); + +// use setWatch() as Bangle.setUI("updown",..) interacts with swipes +function setButton(fn) { + if (process.env.HWVERSION == 1) + btn = setWatch(fn, BTN2); + else + btn = setWatch(fn, BTN1); +} + +function clearButton() { + if (btn !== undefined) { + clearWatch(btn); + btn = undefined; + } } Bangle.loadWidgets(); diff --git a/apps/health/boot.js b/apps/health/boot.js index d6b84ce98..386d75833 100644 --- a/apps/health/boot.js +++ b/apps/health/boot.js @@ -1,10 +1,27 @@ +(function(){ + var settings = require("Storage").readJSON("health.json",1)||{}; + var hrm = 0|settings.hrm; + if (hrm==1) { + function onHealth() { + Bangle.setHRMPower(1, "health"); + setTimeout(()=>Bangle.setHRMPower(0, "health"),2*60000); // give it 2 minutes + } + Bangle.on("health", onHealth); + Bangle.on('HRM', h => { + if (h.confidence>80) Bangle.setHRMPower(0, "health"); + }); + if (Bangle.getHealthStatus().bpmConfidence) return; + onHealth(); + } else Bangle.setHRMPower(hrm!=0, "health"); +})(); + Bangle.on("health", health => { // ensure we write health info for *last* block var d = new Date(Date.now() - 590000); const DB_RECORD_LEN = 4; const DB_RECORDS_PER_HR = 6; - const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24; + const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24 + 1/*summary*/; const DB_RECORDS_PER_MONTH = DB_RECORDS_PER_DAY*31; const DB_HEADER_LEN = 8; const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN; @@ -17,6 +34,12 @@ Bangle.on("health", health => { (DB_RECORDS_PER_HR*d.getHours()) + (0|(d.getMinutes()*DB_RECORDS_PER_HR/60)); } + function getRecordData(health) { + return String.fromCharCode( + health.steps>>8,health.steps&255, // 16 bit steps + health.bpm, // 8 bit bpm + Math.min(health.movement / 8, 255)); // movement + } var rec = getRecordIdx(d); var fn = getRecordFN(d); @@ -30,9 +53,32 @@ Bangle.on("health", health => { } else { require("Storage").write(fn, "HEALTH1\0", 0, DB_FILE_LEN); // header } - var recordData = String.fromCharCode( - health.steps>>8,health.steps&255, // 16 bit steps - health.bpm, // 8 bit bpm - Math.min(health.movement / 8, 255)); // movement - require("Storage").write(fn, recordData, DB_HEADER_LEN+(rec*DB_RECORD_LEN), DB_FILE_LEN); + var recordPos = DB_HEADER_LEN+(rec*DB_RECORD_LEN); + require("Storage").write(fn, getRecordData(health), recordPos, DB_FILE_LEN); + if (rec%DB_RECORDS_PER_DAY != DB_RECORDS_PER_DAY-2) return; + // we're at the end of the day. Read in all of the data for the day and sum it up + var sumPos = recordPos + DB_RECORD_LEN; // record after the current one is the sum + if (f.substr(sumPos, DB_RECORD_LEN)!="\xFF\xFF\xFF\xFF") { + print("HEALTH ERR: Daily summary already written!"); + return; + } + health = { steps:0, bpm:0, movement:0, movCnt:0, bpmCnt:0}; + var records = DB_RECORDS_PER_HR*24; + for (var i=0;i + + + + +
+ + + + + diff --git a/apps/health/lib.js b/apps/health/lib.js index 791c4ce22..70305bff8 100644 --- a/apps/health/lib.js +++ b/apps/health/lib.js @@ -1,6 +1,6 @@ const DB_RECORD_LEN = 4; const DB_RECORDS_PER_HR = 6; -const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24; +const DB_RECORDS_PER_DAY = DB_RECORDS_PER_HR*24 + 1/*summary*/; const DB_RECORDS_PER_MONTH = DB_RECORDS_PER_DAY*31; const DB_HEADER_LEN = 8; const DB_FILE_LEN = DB_HEADER_LEN + DB_RECORDS_PER_MONTH*DB_RECORD_LEN; @@ -16,12 +16,12 @@ function getRecordIdx(d) { // Read all records from the given month exports.readAllRecords = function(d, cb) { - var rec = getRecordIdx(d); var fn = getRecordFN(d); var f = require("Storage").read(fn); + if (f===undefined) return; var idx = DB_HEADER_LEN; for (var day=0;day<31;day++) { - for (var hr=0;hr<24;hr++) { + for (var hr=0;hr<24;hr++) { // actually 25, see below for (var m=0;mg.getWidth()) { hrmOffset=0; - g.clearRect(0,80,239,239); - g.moveTo(-100,0); + g.clearRect(0,80,g.getWidth(),g.getHeight()); + lastHrmPt = [-100,0]; } y = E.clip(btm-v.filt/4,btm-10,btm); g.setColor(1,0,0).fillRect(hrmOffset,btm, hrmOffset, y); y = E.clip(170 - (v.raw/2),80,btm); - g.setColor(g.theme.fg).lineTo(hrmOffset, y); + g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y); + lastHrmPt = [hrmOffset, y]; if (counter !==undefined) { counter = undefined; - g.clear(); + g.clearRect(0,24,g.getWidth(),g.getHeight()); } }); @@ -65,7 +67,10 @@ function countDown() { setTimeout(countDown, 1000); } } -g.clear().setFont("6x8",2).setFontAlign(0,0); +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +g.reset().setFont("6x8",2).setFontAlign(0,0); g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); countDown(); @@ -79,13 +84,14 @@ function readHRM() { if (!hrmInfo) return; if (hrmOffset==0) { - g.clearRect(0,100,239,239); - g.moveTo(-100,0); + g.clearRect(0,100,g.getWidth(),g.getHeight()); + lastHrmPt = [-100,0]; } for (var i=0;i<2;i++) { var a = hrmInfo.raw[hrmOffset]; hrmOffset++; y = E.clip(170 - (a*2),100,230); - g.setColor(g.theme.fg).lineTo(hrmOffset, y); + g.setColor(g.theme.fg).drawLine(lastHrmPt[0],lastHrmPt[1],hrmOffset, y); + lastHrmPt = [hrmOffset, y]; } } diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/ios/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/ios/app-icon.js b/apps/ios/app-icon.js new file mode 100644 index 000000000..b74048750 --- /dev/null +++ b/apps/ios/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwZC/AGEB/4AGwARHv4RH/wQGj4QHAAP4CIoQJAAIRWg4RL8ARVn4RL/gR/CJv9BIP934DFEZH+v/0AgMv+wRK+YCBz/7C4PfCJOfAQO//JHMCIX3/d/CJ//t4RJF4JlCCIP/koRKEYh+DCIxlBCIQADCJQgCn4DCCJSbBHIIDBXYQRI/+Sp4DB7ZsCfdQRzg4RL8ARVgARLCAgRSj4QJ/ARFgF/CA/+CA0AgIRHwARHAH4AnA")) diff --git a/apps/ios/app.js b/apps/ios/app.js new file mode 100644 index 000000000..b210886fd --- /dev/null +++ b/apps/ios/app.js @@ -0,0 +1,2 @@ +// Config app not implemented yet +setTimeout(()=>load("messages.app.js"),10); diff --git a/apps/ios/app.png b/apps/ios/app.png new file mode 100644 index 0000000000000000000000000000000000000000..79aa78f3a3cbe6d008a396f65cf4b6e739a5d494 GIT binary patch literal 1301 zcmV+w1?u{VP)7hf>g#sri^Q3NGIK}89)+*_c9?slgwz3g`9 z_^{GuU1oM>+XYPa_q6k!Gc*4)-}%n@W)^bf$dTi2qU<<}h&!xl%Rwy@ojE8b0)->g za1)_bIXDhdAMp9=l~PfwCtD666p5@r)K-Bhnd!95InXzi`u%&XIHwIjM4aJ>wnb3u zN3P8}N@RP$@2gcx>1KY8bQiiC;sHdd4WUkLb2>lM83;upD@4>+qbDm9)bdilx8BHV zI0GW$7ExQWBg#60><|&Rk=-zWM73dRR#e$Q!Q`-}Ei47H@~1NS<4c2Dp%KSqDaY6b!?C5<~d_?M z6Ca$$>2WY~L&a_HQ|kBKR;%q9$p7I>oL8C?2cn~n9i@BaJ1OD;2A4ih-;L?Wx4#dK z$(1nr+y_#8t6rbNq-X3GKHCAf|2l*-d<_8AatRv$W^jf<7^AxQ}==kq(abYv^U;A>5sss&y3%_6@Rc7P2&0$kN3@!jS=L zn_X-?mD*3PW?r2zEy#d&vci1yp0e0Qvu zg+)!NhBVj?;AUVPeeU6{$>dd2(PMEAuls|G=X!|6bh>+De0QLY&2^_(ySf7JSQh|3 zuM2N=kQW;r{Mrdo9R_us{Z}vX=HcBuU1l$RAcIEs=k_LoPn1zIyLf0!ABgk)fi}K5 z*v|EiehLcnm^!JDnk5r?aJnz)d|hB?i{jAr4qC#8xX{%>RdESxD`&GfSZ*lPsF97h z6W=LDtrOpCm<7;t$5f=J%gA6Bz||}W$qZ$z#V`P+Xv~h93=obPnM`It3_m8_X_S%% zLz|I7L|mar*C}9HR#aI;!TCV3x6{ + /* eg: + { + event:"add", + uid:42, + category:4, + categoryCnt:42, + silent:true, + important:false, + preExisting:true, + positive:false, + negative:true + } */ + + //console.log("ANCS",msg.event,msg.id); + // don't need info for remove events - pass these on + if (msg.event=="remove") + return E.emit("notify", msg); + + // not a remove - we need to get the message info first + function ancsHandler() { + var msg = Bangle.ancsMessageQueue[0]; + NRF.ancsGetNotificationInfo( msg.uid ).then( info => { + E.emit("notify", Object.assign(msg, info)); + Bangle.ancsMessageQueue.shift(); + if (Bangle.ancsMessageQueue.length) + ancsHandler(); + }); + } + Bangle.ancsMessageQueue.push(msg); + // if this is the first item in the queue, kick off ancsHandler, + // otherwise ancsHandler will handle the rest + if (Bangle.ancsMessageQueue.length==1) + ancsHandler(); +}); + +// Handle ANCS events with all the data +E.on('notify',msg=>{ +/* Info from ANCS event plus + "uid" : int, + "appId" : string, + "title" : string, + "subtitle" : string, + "message" : string, + "messageSize" : string, + "date" : string, + "posAction" : string, + "negAction" : string, + "name" : string, +*/ + var appNames = { + "com.netflix.Netflix" : "Netflix", + "com.google.ios.youtube" : "YouTube", + "com.google.hangouts" : "Hangouts", + "com.skype.SkypeForiPad": "Skype", + "com.atebits.Tweetie2": "Twitter" + // could also use NRF.ancsGetAppInfo(msg.appId) here + }; + var unicodeRemap = { + '2019':"'" + }; + var replacer = ""; //(n)=>print('Unknown unicode '+n.toString(16)); + if (appNames[msg.appId]) msg.a + require("messages").pushMessage({ + t : msg.event, + id : msg.uid, + src : appNames[msg.appId] || msg.appId, + title : msg.title&&E.decodeUTF8(msg.title, unicodeRemap, replacer), + subject : msg.subtitle&&E.decodeUTF8(msg.subtitle, unicodeRemap, replacer), + body : msg.message&&E.decodeUTF8(msg.message, unicodeRemap, replacer) + }); + // TODO: posaction/negaction? +}); + +// Apple media service +E.on('AMS',a=>{ + function push(m) { + var msg = { t : "modify", id : "music", title:"Music" }; + if (a.id=="artist") msg.artist = m; + else if (a.id=="album") msg.artist = m; + else if (a.id=="title") msg.tracl = m; + else return; // duration? need to reformat + require("messages").pushMessage(msg); + } + if (a.truncated) NRF.amsGetMusicInfo(a.id).then(push) + else push(a.value); +}); + +// Music control +Bangle.musicControl = cmd => { + // play, pause, playpause, next, prev, volup, voldown, repeat, shuffle, skipforward, skipback, like, dislike, bookmark + NRF.amsCommand(cmd); +} + +/* +// For testing... + +NRF.ancsGetNotificationInfo = function(uid) { + print("ancsGetNotificationInfo",uid); + return Promise.resolve({ + "uid" : uid, + "appId" : "Hangouts", + "title" : "Hello", + "subtitle" : "There", + "message" : "Lots and lots of text", + "messageSize" : 100, + "date" : "...", + "posAction" : "ok", + "negAction" : "cancel", + "name" : "Fred", + }); +}; + +E.emit("ANCS", { + event:"add", + uid:42, + category:4, + categoryCnt:42, + silent:true, + important:false, + preExisting:true, + positive:false, + negative:true +}); + +*/ diff --git a/apps/launch/ChangeLog b/apps/launch/ChangeLog index 09569d8da..bd8a9bd03 100644 --- a/apps/launch/ChangeLog +++ b/apps/launch/ChangeLog @@ -4,4 +4,5 @@ 0.04: Now displays widgets 0.05: Use g.theme for colours 0.06: Use Bangle.setUI for buttons -0.07: Theme colours fix \ No newline at end of file +0.07: Theme colours fix +0.08: Merge Bangle.js 1 and 2 launchers diff --git a/apps/launch/app.js b/apps/launch/app-bangle1.js similarity index 98% rename from apps/launch/app.js rename to apps/launch/app-bangle1.js index 449e16e62..3d4682e55 100644 --- a/apps/launch/app.js +++ b/apps/launch/app-bangle1.js @@ -16,7 +16,7 @@ function drawMenu() { var w = g.getWidth(); var h = g.getHeight(); var m = w/2; - var n = (h-48)/64; + var n = Math.floor((h-48)/64); if (selected>=n+menuScroll) menuScroll = 1+selected-n; if (selected{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type)); +apps.sort((a,b)=>{ + var n=(0|a.sortorder)-(0|b.sortorder); + if (n) return n; // do sortorder first + if (a.nameb.name) return 1; + return 0; +}); +apps.forEach(app=>{ + if (app.icon) + app.icon = s.read(app.icon); // should just be a link to a memory area +}); +// FIXME: not needed after 2v11 +var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2"; +// FIXME: check not needed after 2v11 +if (g.wrapString) { + g.setFont(font); + apps.forEach(app=>app.name = g.wrapString(app.name, g.getWidth()-64).join("\n")); +} + +function drawApp(i, r) { + var app = apps[i]; + if (!app) return; + g.clearRect(r.x,r.y,r.x+r.w-1, r.y+r.h-1); + g.setFont(font).setFontAlign(-1,0).drawString(app.name,64,r.y+32); + if (app.icon) try {g.drawImage(app.icon,8,r.y+8);} catch(e){} +} + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +E.showScroller({ + h : 64, c : apps.length, + draw : drawApp, + select : i => { + var app = apps[i]; + if (!app) return; + if (!app.src || require("Storage").read(app.src)===undefined) { + E.showMessage("App Source\nNot found"); + setTimeout(drawMenu, 2000); + } else { + E.showMessage("Loading..."); + load(app.src); + } + } +}); diff --git a/apps/launchb2/ChangeLog b/apps/launchb2/ChangeLog deleted file mode 100644 index a96ee84e1..000000000 --- a/apps/launchb2/ChangeLog +++ /dev/null @@ -1,3 +0,0 @@ -0.01: New App! -0.02: Fix occasional missed image when scrolling up -0.03: Text wrapping, better font diff --git a/apps/launchb2/app.js b/apps/launchb2/app.js deleted file mode 100644 index 371326498..000000000 --- a/apps/launchb2/app.js +++ /dev/null @@ -1,79 +0,0 @@ -var s = require("Storage"); -var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type)); -apps.sort((a,b)=>{ - var n=(0|a.sortorder)-(0|b.sortorder); - if (n) return n; // do sortorder first - if (a.nameb.name) return 1; - return 0; -}); -var APPH = 64; -var menuScroll = 0; -var menuShowing = false; -var w = g.getWidth(); -var h = g.getHeight(); -var n = Math.ceil((h-24)/APPH); -var menuScrollMax = APPH*apps.length - (h-24); -// FIXME: not needed after 2v11 -var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2"; - -apps.forEach(app=>{ - if (app.icon) - app.icon = s.read(app.icon); // should just be a link to a memory area -}); -if (g.wrapString) { // FIXME: check not needed after 2v11 - g.setFont(font); - apps.forEach(app=>app.name = g.wrapString(app.name, g.getWidth()-64).join("\n")); -} - -function drawApp(i) { - var y = 24+i*APPH-menuScroll; - var app = apps[i]; - if (!app || y<-APPH || y>=g.getHeight()) return; - g.setFont(font).setFontAlign(-1,0).drawString(app.name,64,y+32); - if (app.icon) try {g.drawImage(app.icon,8,y+8);} catch(e){} -} - -function drawMenu() { - g.reset().clearRect(0,24,w-1,h-1); - g.setClipRect(0,24,g.getWidth()-1,g.getHeight()-1); - for (var i=0;i{ - var dy = e.dy; - if (menuScroll - dy < 0) - dy = menuScroll; - if (menuScroll - dy > menuScrollMax) - dy = menuScroll - menuScrollMax; - if (!dy) return; - g.reset().setClipRect(0,24,g.getWidth()-1,g.getHeight()-1); - g.scroll(0,dy); - menuScroll -= dy; - if (e.dy < 0) { - drawApp(Math.floor((menuScroll+24+g.getHeight())/APPH)-1); - if (e.dy <= -APPH) drawApp(Math.floor((menuScroll+24+g.getHeight())/APPH)-2); - } else { - drawApp(Math.floor((menuScroll+24)/APPH)); - if (e.dy >= APPH) drawApp(Math.floor((menuScroll+24)/APPH)+1); - } - g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1); -}); -Bangle.on("touch",(_,e)=>{ - if (e.y<20) return; - var i = Math.floor((e.y+menuScroll-24) / APPH); - var app = apps[i]; - if (!app) return; - if (!app.src || require("Storage").read(app.src)===undefined) { - E.showMessage("App Source\nNot found"); - setTimeout(drawMenu, 2000); - } else { - E.showMessage("Loading..."); - load(app.src); - } -}); -Bangle.loadWidgets(); -Bangle.drawWidgets(); diff --git a/apps/launchb2/app.png b/apps/launchb2/app.png deleted file mode 100644 index 8b4e6caa2fe4720a32f492bd00c6e68751fabfbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 899 zcmV-}1AP36P)EZi8a;XNfX0KK)EG5t2&jmXSR!CR3JOvSF^w(VT@Oq6O4&}^ElAua zX|l63`{wtXcjoO3*x1-CYovub^cOBY?cfcO1>;+VoWa;>Pk;!SG_WW*Es5gZ1-`R@ z4t>oKYJ)f#xYia)IV)#&xZ*BHYck+F1K9eED0&f|F;Lm{VL;rb?(c)W{8d& zzrH4`vJJ?>K!fln4K4LjpAx19%+|UjgL{LF?3gt^suTXG8HN@KQv>tY{cLjA%Q)j? zIX0ma_G_?6n>drF(WulAaitj}A^+b$v5k$*zr})O^uogfX;+bphn_9#OZ}pdk^!&c zqrUVgUd3m%o}@|o!cmyJiIaP-*BlK-1F7-kNoN^X5h5L~tnN`_& zI3(jUhvcNZU>jbg3|-gg8hTDln@m+>N(dP^vh_T*8x8>Qb*ytvU&Y#;lz3_Z*tBLl zEghFFmS~QUfhzCr>E}|Fxr&yKOBoXz4vqjF zlzKwtw{idZM95W(Tbb#rO0Wkqaj6$F@MWZp>h=2o=nr;LTzCQiH!$$4i=w<5W87%F zs6NXOGI0OH6?#VB0k9%#HO2Wg(|z6FTj}`r1kmXWJk5wmGlUFGsuA7}JOW^&y9!O$ zkR=&S*XaHEp1^o_Mn#&D^ig6k6`9rJMEHEc@fMjg8GR Z=Px>?AD_-bQjY)t002ovPDHLkV1j2qp7Q_z diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog new file mode 100644 index 000000000..c7ec09d30 --- /dev/null +++ b/apps/lcars/ChangeLog @@ -0,0 +1 @@ +0.01: Launch app diff --git a/apps/lcars/README.md b/apps/lcars/README.md new file mode 100644 index 000000000..fdce30c1b --- /dev/null +++ b/apps/lcars/README.md @@ -0,0 +1,8 @@ +# LCARS clock + +A simple LCARS inspired clock that shows: + * Current time + * Current date + * Battery level + * Steps + diff --git a/apps/lcars/background.png b/apps/lcars/background.png new file mode 100644 index 0000000000000000000000000000000000000000..1ee4297c642db661ff1b3441ef7ba950eec72d70 GIT binary patch literal 1497 zcmbW1`#Tc~7{@n@xt%UyUQNs?AsxA# zHa$wwL<{8w(?NLPZvM zgqGC!_1lo1NR(<*ycWkd+gJ$>hHoq(#fk) zLr#*bQ#P23x1u?)>qU$z&|pPITm7o2V(%y5%6q3FiXEm1)+OB-agv*xxqU*W?+2_&E#{>qVmBG75RC=YK~v7|*o)k1$aVa205hn>OKf3wHA zo@6FsSXuGW*yy)=-W}e7<~Z5p^z-C+@BCe#A3KDuf{xgnfTE3EJN; z6eOh>E>#*jk}WZCo2mpR$UGKP)j>E80Q5I?hj@3=b4vRk63;BOHAZeUjeT8dx$IEV zvN1lIn9-ezu)xF?giA+@ZLjStm1hj;zo6$B6tT@~ONSc=o5rO#D!YqE3T*3z-}Uf! z)Ez>w{wLfw!|!g!N_(Cpf1M}^nDjJB3Fj`~!#}s)d2ocMC0*QVVq6DIj}=g#z#E<+ zPJ?)w4y;q*y&B+{K5k2=D+IbPPL=5NjqMWC~IeV&wSi$Akn9<&6hociQ&Q> zsOPIaOH-P!B{#aXB5^TX{a)_7i|Fa}PWoEJQ4p@h3ck8`*$l!|aklqs{r6NgkSO?H zF91ngTF4AtL*J$5keYO^7uKxKegzG!Z@e3L+Kn4D2OKL$r_#AH?AGLuVwg$46U;P} zK@IycRst&NyX=gni4u)N1_?(yW}3*LKlA5!RIPYz@Xu5H)0qF-LD=+FDdUpmKxTdO z4!ds_byd*X`<4?CM)~(iIGF9a=h=KWiyGJA`^T|_@J}z>Hzugiu0>Appt7KkfGjMY z@p=C-jTQ8qmFgd%?3=`iS6Vo>N|C3}hBcku?>$@tVcc`W^bvAB_n4X!V0mcelHq=~ z*NT1-l+cwN6giI0IY%rb)-8@a(ES2iELz1`zes7ThV~Gv-bLBkI@&$7|6;XipMx-; z8ylfPQR)r06*a?$56X~*y1M7#A{!|7JR>9Yp3>8G#OG}hyFuAkkc#gHKzU+3>fJ)G F{0C>+p!fg) literal 0 HcmV?d00001 diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js new file mode 100644 index 000000000..cf884a6b7 --- /dev/null +++ b/apps/lcars/lcars.app.js @@ -0,0 +1,99 @@ +const locale = require('locale'); + + +/* + * Assets: Images, fonts etc. + */ +var img = { + width : 176, height : 151, bpp : 3, + transparent : 0, + buffer : require("heatshrink").decompress(atob("gF58+eAR14IN1fvv374CN7yD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AH4A/AH4A/AB1z588+YCN+RBuj158+eARyD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4AUhyD/gEDQaHz4BCuQaNAIN0PQaHIIN0BQaF5IN0AQaHPkBBug6DQ8iEvQaE8yBBuhyDPAQNAINsBQaACBkhCuQaACpVo0cQaACo4CFGjyD/AAMPQf4ACQf4ADgiD+AH4A/AH8J02atICIwEAgPnz15AR3gEgM27dt2wCTF4IABgYROgN9+/fAR14ILsaQBKDakwjKF5oABKZ6DwgxTPQeEmQf5cPQeMBLhyDxgJTRQd0JKaKDuhKD/gENQf6D/F4VNQf8AKaKDvKBYnBAGZQKzBB1QZOwIGqDJsBA2QZJA3QZGYIPCDH4CD/0xA4QY+wIPKDGwCD/tpB6Qf6DHthA5QY1oIPSD/QY9gQf/bIPaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AF8JQYgCdsEHnnz54CJgIdLwEAhqDEATtggPnz15ARHkgIdLIIKAgQcCAgQcAA/gAA==")) +} + +Graphics.prototype.setFontMinaSmall = function(scale) { + // Actual height 18 (17 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAA/8w/8wAAQAAAAAA4AA8AAAAA8AAwAAAAAAEABEABEQB/w/8AxEABEwB/w/8AxEABEABEAAAAH4MP8MMEM8GPcGOMGMMGMMH4ABwAAA/gAwgAggAggQwhw/nAAOAA4ADgAOfw8YwwQwAQQAYwAfwAPgAGAAfg85w/wwzgww4wwdgwHggHgAPwAIQAAA8AAwAAAAAAfwH/+fAP4ABgAAgAA4ABfAPH/+A/wAAAAAAEAAHgAfAAfAAHgAMAAAAAAAABgABgABgAf4AP4ABgABgABAAAAAADAADwADAAAAAgAAwAAwAAwAAwAAwAAAAADAADAADAAAAAAEAA8AH4A+AHwA+AAwAAAAAf/g//wwAwwAwwAwwAwwAwwAwf/gH+AAAAAAAYAAwAAwAA//wAAAAAAAAAwAwwBwwDwwDwwGwwcww4wfwwPAwAAAAAAwAwwQwwQwwQwwQwwYww4wf/gHHAAAAAEAAeAA+ADmAPGAcGAwGAh/wD/wAEAAEAAAAf4w/4wwwwwwwwwwwwwwwww/wgfgAAAAAAP/Af/gwwwwwwwwwwwwwwwwwww/gAAAgAAwAAwAAwAwwHww/Az4A/AA8AAAAAAAAfPg//wxwwwwwwwwwwwwww//wffgAAAAAAfwQ/wwwQwwYwwQwwQwwQw//wP/AAAAAAAMDAMDAMDAAAAAAAMDAMDwMDAAAAAAADgADgAHwAGwAMYAMYAIIAAAAEQAGYAGYAGYAGYAGYAGYAGYAAAAMYAMYAGwAGwAHgADgADAAAAAwAAwAAwAAwcwwcwwQAwQA/wAfgAAAAAAAB/8D/+TAGbHjbPzbMTbMzbMzb/zZ/zYAGf/+H/8AAAAAAABwAPwA+AH+A+GA8GAfmAD+AAfgADwAAQAAA//w//wwwwwwwwwwwwwxww//wffgAAAAAAP/Af/gwBwwAwwAwwAwwAwwAwwAwAAA//w//wwAwwAwwAwwAwwAw4Bwf/gH+AAAAAAAf/g//wwQwgQQgQQgQQgQQgQQgAQAAAf/w//wwQAgQAgQAgQAgQAgQAgAAAAAP/Af/gwAwwAwwAwwYwwYwwfwwfwAAAAAA//w//wAYAAYAAYAAYAAYAAYA//w//wAAA//w//wAAAAAAAAwAAwAAw//w//AAAA//w//wAYAA4AD8AHHAeDg4AwgAQAAAAAA//g//wAAwAAwAAwAAwAAwAAwAAQAAAP/w//w+AAPwAB+AAHwADwA/gH4A/AA/4A//wAAwAAA//w//wcAAPAADgAA4AAeAAHAADw//wAAAAAAH/Af/g4AwwAwwAwwAwwAwwAwcDwP/gB4AAAA//w//wwQAwQAwQAwYAwwA/wAPgAAAAH/Af/gwAwwAwwAwwA8wA8wA2cDkP/gB4AAAA//w//wwYAwYAwYAwcAwfA/zwPgwAAAAAAfgA/wwwQwwYwwYwwYwwYwwfwAPgAAAAAAwAAwAAwAA//w//wwAAwAAwAAwAAAAA/+A//gABwAAwAAwAAwAAwAAwAPg//AAAAAAA4AA/AAH4AA/AAHwADwAfgD8AfgA8AAgAAAAA4AA/AAH4AA/AAHwAHwA/AP4A/4Aw/AAHwAHwA/AP4A+AAwAAAAAwAw8DwOHAD8AB4AD8AOHA8DwwAwAAAAAAwAA8AAPAADwAA/wB/wHgAeAA4AAgAAgAQwBwwHwwOww8wxww3gw+Aw4AwwAQAAAH//f//YAAYAAQAAwAA+AAPwAB+AAPwAB8AAMQAAYAAYAAf//AAAAAA"), 32, atob("BgUHDAoRCwMGBggJBQYFBwwHCwsLCwsKCwsFBQkICQoPDAsKDAoKCwsEBgsKDgwMCgwLCwoMDBELCwoGBwY="), 18+(scale<<8)+(1<<16)); +} + +Graphics.prototype.setFontMinaLarge = function(scale) { + // Actual height 35 (34 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAB4AAAAAPgAAAAA+AAAAAD4AAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAH4AAAAD/gAAAB/8AAAA/+AAAAf/AAAAP/gAAAH/wAAAD/4AAAD/8AAAB/+AAAAP/AAAAA/AAAAADgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///4AA////wAH////gA+AAAfADwAAA8AOAAABwA4AAAHADgAAAcAOAAABwA4AAAHADgAAAcAOAAABwA4AAAHADgAAAcAOAAABwA8AAAPAD4AAB8AH////gAP///8AAf///gAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAcAAAAADgAAAAAOAAAAAB4AAAAAHAAAAAA8AAAAAD////8AP////wA/////AD////8AAAAAAAAAAAAAAAAAAAAAGAAAAAA4AAAHADgAAA8AOAAAHwA4AAA/ADgAAD8AOAAAfwA4AAD/ADgAAfcAOAAD5wA4AAfHADgAD4cAOAAfBwA4AD8HADwAfgcAPAD8BwAeA/AHAB+f4AcAD//ABwAH/wAHAAH8AAcAAAAAAAAAAAAAAAAAAAAAGAAABgAYAAAGADgAAAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADwB4A8APAPgDwAfD//+AB////4AD/8//AAD/B/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAA8AAAAAPwAAAAD/AAAAAf8AAAAH9wAAAB/HAAAAPwcAAAD+BwAAAfgHAAAH8AcAAB/ABwAAPwAHAAA+AAcAADgABwAAIAAHgAAAH///AAB///8AAH///wAAAAcAAAAABwAAAAAHAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAH/8AYAP//wBgA///AHAD//wAcAOAPABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgA8AOAOADwA4A8APADgD8H4AOAH//gA4AP/8AAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAD//+AAB///+AAP///8AB/BgH4APgOAHwA8A4APADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDgAcAOAOABwA4A4AHADgDwA8AOAP//wA4Af/+ABgA//wAAAA/8AAAAAAAAAAAAAAAAAAAAAA4AAAAADgAAAAAOAAAAAA4AAAAADgAAAAAOAAAAQA4AAAHADgAAD8AOAAA/wA4AAf+ADgAP/gAOAD/wAA4B/8AADg/+AAAOP/AAAA//wAAAD/4AAAAP8AAAAA/AAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/h/4AA//P/wAH////gA+B/AfADwD4A8AOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAOAHABwA4AcAHADgBwAcAPAPgDwA8A+APAB////4AH////gAP/j/8AAD4B8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/wAAAA//gAYAH//ABwA//+AHADwB4AcAOADgBwA4AOAHADgA4AcAOADgBwA4AOAHADgA4AcAOADgBwA4AOAHADgA4AcAPADgDwA+AOAfAB+A4f4AD////AAH///4AAD//8AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAeAAB8AD4AAHwAPgAAfAA+AAB4AB4AAAAAAAAAAAAAAAAAAAAAA="), 46, atob("CxAaDhgYGBgZFhkZCw=="), 40+(scale<<8)+(1<<16)); +} + + +/* + * Queue drawing every minute + */ +var drawTimeout; +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + + +/* + * Draw watch face + */ +function draw(){ + g.reset(); + g.clearRect(0, 24, g.getWidth(), g.getHeight()); + + // Draw background image + g.drawImage(img, 0, 24); + + // Write time + var currentDate = new Date(); + var timeStr = locale.time(currentDate,1); + g.setFontAlign(0,0,0); + g.setFontMinaLarge(); + g.drawString(timeStr, 115, 53); + + // Write date + g.setFontAlign(-1,-1,0); + g.setFontMinaSmall(); + + var dayName = locale.dow(currentDate, true).toUpperCase(); + var day = currentDate.getDate(); + g.drawString("DATE:", 40, 107); + g.drawString(dayName + " " + day, 100, 105); + + // Draw battery + var bat = E.getBattery(); + g.drawString("BAT:", 40, 127); + g.drawString(bat+"%", 100, 127); + + // Draw steps + var steps = Bangle.getStepCount(); + g.drawString("STEP:", 40, 147); + g.drawString(steps, 100, 147); + + // Queue draw in one minute + queueDraw(); +} + +// Clear the screen once, at startup +g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); + +// draw immediately at first, queue update +draw(); + + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); \ No newline at end of file diff --git a/apps/lcars/lcars.icon.js b/apps/lcars/lcars.icon.js new file mode 100644 index 000000000..c404728e0 --- /dev/null +++ b/apps/lcars/lcars.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwgeevPnAQsc+fPngCE+/fvoCEvAbIA4/AgFzEZwRBjwjNvBUBEZ3eCIMOEZtwCIMBEZuARYU5EZecTocHEZf0CIcBEbvgaggjKTwIAEbQpoHAAiSEeoYQHJQr1CCBJKEIgcBI4xKFaIdt3AOFgfuAYMeEYLRBj1pLQ4ICuYjBAgPbtoRHhu3AYN5VoMGzVpI49502AgPPVoM27dsK48N23cgE5CgOmzVoCI4LBzCSB8EP2wjJgILBAYMAhIjBsAjJzVwg47C7YRJEYhfBEZXmEZ53CI4q2BEAiVCkwjCNYaMGboQjDkBfDCAbdB04EBgyPDC4YAD/dt2wRCHIM5njXCCAcHboOmCIQ0B5/nfYT6DFIIjBeAcOvM8+EAjitFEYJEBAANzEYOeeowjCFgUDzwjB+YrDgAgBEYWcA4Mc+YjCvAQCgftEANuDIYOBEYXPNwIAIg4OCCgXkCBEOEZDvBEAhEB4AjF/inB8+OJQOOvILBoAjGU4IFDAQYjGbQIdCAQt4EY0DEZACDEYceEZACDC4bLBEZwCO")) diff --git a/apps/lcars/lcars.png b/apps/lcars/lcars.png new file mode 100644 index 0000000000000000000000000000000000000000..167352ef4bd6db8c6de6bc845396944af7bcc040 GIT binary patch literal 1823 zcmV+)2jKXLP)@u77`!)Bw=brPOnLGD!jCw8=Ov8ZqvPg{2d`k`HpD>tb0K0#?jIy#aTr1jOuownQ zAmwQdN=s+nd__eCp7k9wNF0MCP~~iaq@*MYmzVrbaXKGwYIk6Y1a`><8huqR#ukA{?yb|PUCT!z(;99OiycGm6b{*e!a1t zvzVMEP^fy2`uciSwhtdZ#9MaRoW$iUfph21>12QMF$%w7!v;)W1DX^6 zL9|;8nwy(3JUq;id|=(Wbr{PXM|5;FEG#T2eG-WTSFd|(PavxD7ShwxDF)8Y&dATt zNBOr8D11Rd0T;Sy{)B=v7?zA+`*!NIkxHeIqOr zm^0Dyar^ddMm{$;7skfMWc$>qx#g7rfb#NklJM~GK*p9#je)qHKC@6Nl}H~cTazHETHLdxO(+! z8b^$dj*^YVix+DR@c8j#{gml|qm_}2ysfP*jU%kBt;t4jZ?9J8hMfPiPFJ?y z1fS0*8&y?R8XYsO5W2$L+#K^`GNHP4y1upD#ZsEH`1KWU$L~o%*+gFX=%9ZtGQCAZsVMNS)#5K#Ky*wJV6ln=0M9_ zd-fsrSReUg05ivn6|wGQ`>HaZwRn4bdnkSx!vWiaT5Y4=iH4`ACyj4vYQnxhLJ<)W zL8Z<}#N;1P2G}G+k!l=H`Z0p?;VDMj1nU&qzn@N8^skdu?cC?3#I*g6>6gs$H2 zkeZrGyB{;b-`^jA_DY*%I14hF4As@usI06+e}6xKL`GXapO5_^jTr1x?~gWxm{8mB zHSM~|20R`QDwRs_(u=33Cw2u^YP4Zh8(s6RL@xnKrINLHdwY9?hK53Pej1jRmH>eE z_I9n~^s0>lfU&VLHgDdHy1F_H3=HTz-qh3-0)YVD-riX5ltSr?ii$#9TparPG|ZjX zV3mNieGZLFNJxN%X$-b)o#RwQ3c9*-78ClO?SPMu51f|Y&}hf%3(PEaGe27Ju0I#9jwSp)DMP1B zvkj{+Ff=p-f*=qV)z5(}Zow)6g+hUt82X(5KY-N~`q-yLyU~Aye*r)QY$kz4$b$d? N002ovPDHLkV1l5}TXFyZ literal 0 HcmV?d00001 diff --git a/apps/matrixclock/ChangeLog b/apps/matrixclock/ChangeLog index d53df991b..7cc9144b1 100644 --- a/apps/matrixclock/ChangeLog +++ b/apps/matrixclock/ChangeLog @@ -1 +1,2 @@ -0.01: Initial Release +0.01: Initial Release +0.02: Support for Bangle 2 diff --git a/apps/matrixclock/matrixclock.js b/apps/matrixclock/matrixclock.js index 0bf33fd68..ab18c13b8 100644 --- a/apps/matrixclock/matrixclock.js +++ b/apps/matrixclock/matrixclock.js @@ -12,6 +12,8 @@ const Locale = require('locale'); const SHARD_COLOR =[0,1.0,0]; const SHARD_FONT_SIZE = 12; const SHARD_Y_START = 30; +const w = g.getWidth(); + /** * The text shard object is responsible for creating the * shards of text that move down the screen. As the @@ -111,7 +113,7 @@ var dateStr = ""; var last_draw_time = null; const TIME_X_COORD = 20; -const TIME_Y_COORD = 100; +const TIME_Y_COORD = g.getHeight() / 2; const DATE_X_COORD = 170; const DATE_Y_COORD = 30; const RESET_PROBABILITY = 0.5; @@ -141,29 +143,26 @@ function draw_clock(){ } var now = new Date(); // draw time. Have to draw time on every loop - g.setFont("Vector",45); - g.setFontAlign(-1,-1,0); + + g.setFont("Vector", g.getWidth() / 5); + g.setFontAlign(0,-1); if(last_draw_time == null || now.getMinutes() != last_draw_time.getMinutes()){ - g.setColor(0,0,0); - g.drawString(timeStr, TIME_X_COORD, TIME_Y_COORD); + g.setColor(g.theme.fg); + g.drawString(timeStr, w/2, TIME_Y_COORD); timeStr = format_time(now); } - g.setColor(SHARD_COLOR[0], - SHARD_COLOR[1], - SHARD_COLOR[2]); - g.drawString(timeStr, TIME_X_COORD, TIME_Y_COORD); + g.setColor(SHARD_COLOR[0], SHARD_COLOR[1], SHARD_COLOR[2]); + g.drawString(timeStr, w/2, TIME_Y_COORD); // // draw date when it changes g.setFont("Vector",15); - g.setFontAlign(-1,-1,0); + g.setFontAlign(0,-1,0); if(last_draw_time == null || now.getDate() != last_draw_time.getDate()){ - g.setColor(0,0,0); - g.drawString(dateStr, DATE_X_COORD, DATE_Y_COORD); + g.setColor(g.theme.fg); + g.drawString(dateStr, w/2, DATE_Y_COORD); dateStr = format_date(now); - g.setColor(SHARD_COLOR[0], - SHARD_COLOR[1], - SHARD_COLOR[2]); - g.drawString(dateStr, DATE_X_COORD, DATE_Y_COORD); + g.setColor(SHARD_COLOR[0], SHARD_COLOR[1], SHARD_COLOR[2]); + g.drawString(dateStr, w/2, DATE_Y_COORD); } last_draw_time = now; } @@ -232,10 +231,10 @@ function startTimers(){ Bangle.on('lcdPower', (on) => { if (on) { - console.log("lcdPower: on"); + //console.log("lcdPower: on"); startTimers(); } else { - console.log("lcdPower: off"); + //console.log("lcdPower: off"); clearTimers(); } }); diff --git a/apps/matrixclock/screenshot_matrix.png b/apps/matrixclock/screenshot_matrix.png new file mode 100644 index 0000000000000000000000000000000000000000..3d843848cb1a1d1fdbaaf9cc1b236ba4cb8ebdc5 GIT binary patch literal 4990 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 while (rows--) { var name = menuItems[idx]; var item = items[name]; @@ -82,7 +76,7 @@ E.showMenu = function(items) { g.setColor((idxitem.max) item.value = item.max; + if (item.min!==undefined && item.valueitem.max) item.value = item.wrap ? item.min : item.max; if (item.onchange) item.onchange(item.value); l.draw(options.selected,options.selected); } else { var a=options.selected; - options.selected = (dir+options.selected)%menuItems.length; - if (options.selected<0) options.selected += menuItems.length; + options.selected = (dir+options.selected+menuItems.length)%menuItems.length; l.draw(Math.min(a,options.selected), Math.max(a,options.selected)); } } diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog new file mode 100644 index 000000000..4f7df3859 --- /dev/null +++ b/apps/messages/ChangeLog @@ -0,0 +1,3 @@ +0.01: New App! +0.02: Add 'messages' library +0.03: Fixes for Bangle.js 1 diff --git a/apps/messages/README.md b/apps/messages/README.md new file mode 100644 index 000000000..c243ec06a --- /dev/null +++ b/apps/messages/README.md @@ -0,0 +1,21 @@ +# Messages app + +**THIS APP IS CURRENTLY BETA** + +This app handles the display of messages and message notifications. It stores +a list of currently received messages and allows them to be listed, viewed, +and responded to. + +It is a replacement for the old `notify`/`gadgetbridge` apps. + +## Usage + +... + +## Requests + +Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=messages%20app + +## Creator + +Gordon Williams diff --git a/apps/messages/app-icon.js b/apps/messages/app-icon.js new file mode 100644 index 000000000..6ed3c1141 --- /dev/null +++ b/apps/messages/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///rkcAYP9ohL/ABMBqoAEoALDioLFqgLDBQoABERIkEBZcFBY9QBed61QAC1oLF7wLD24LF24LD7wLF1vqBQOrvQLFA4IuC9QLFD4IuC1QLGGAQOBBYwgBEwQLHvQBBEZHVq4jI7wWBHY5TLNZaDLTZazLffMBBY9ABZsABY4KCgEVBQtUBYYkGEQYA/AAwA=")) diff --git a/apps/messages/app.js b/apps/messages/app.js new file mode 100644 index 000000000..6c7cf5fc9 --- /dev/null +++ b/apps/messages/app.js @@ -0,0 +1,237 @@ +/* MESSAGES is a list of: + {id:int, + src, + title, + subject, + body, + sender, + tel:string, + new:true // not read yet + } +*/ + +/* For example for maps: + +// a message +{"t":"add","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"} +// maps +{"t":"add","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"GhqBAAAMAAAHgAAD8AAB/gAA/8AAf/gAP/8AH//gD/98B//Pg/4B8f8Afv+PP//n3/f5//j+f/wfn/4D5/8Aef+AD//AAf/gAD/wAAf4AAD8AAAeAAADAAA="} + +*/ + +var Layout = require("Layout"); +var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2"; +var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2"; +var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4"; +var colBg = g.theme.dark ? "#141":"#4f4"; +var colSBg1 = g.theme.dark ? "#121":"#cFc"; +var colSBg2 = g.theme.dark ? "#242":"#9F9"; +// hack for 2v10 firmware's lack of ':size' font handling +try { + g.setFont("6x8:2"); +} catch (e) { + g._setFont = g.setFont; + g.setFont = function(f,s) { + if (f.includes(":")) { + f = f.split(":"); + return g._setFont(f[0],f[1]); + } + return g._setFont(f,s); + }; +} + + +var MESSAGES = require("Storage").readJSON("messages.json",1)||[]; +if (!Array.isArray(MESSAGES)) MESSAGES=[]; +var onMessagesModified = function(msg) { + // TODO: if new, show this new one + if (msg.new) Bangle.buzz(); + showMessage(msg.id); +}; +function saveMessages() { + require("Storage").writeJSON("messages.json",MESSAGES) +} + +function getBackImage() { + return atob("FhYBAAAAEAAAwAAHAAA//wH//wf//g///BwB+DAB4EAHwAAPAAA8AADwAAPAAB4AAHgAB+AH/wA/+AD/wAH8AA=="); +} +function getMessageImage(msg) { + if (msg.img) return atob(msg.img); + var s = (msg.src||"").toLowerCase(); + if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); + if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); + if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); + if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA"); + if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); + if (msg.id=="back") return getBackImage(); + return atob("HBKBAD///8H///iP//8cf//j4//8f5//j/x/8//j/H//H4//4PB//EYj/44HH/Hw+P4//8fH//44///xH///g////A=="); +} + + +function showMapMessage(msg) { + var m; + var distance, street, target, eta; + m=msg.title.match(/(.*) - (.*)/); + if (m) { + distance = m[1]; + street = m[2]; + } else street=msg.title; + m=msg.body.match(/(.*) - (.*)/); + if (m) { + target = m[1]; + eta = m[2]; + } else target=msg.body; + layout = new Layout({ type:"v", c: [ + {type:"txt", font:fontMedium, label:target, bgCol:colBg, fillx:1, pad:2 }, + {type:"h", bgCol:colBg, fillx:1, c: [ + {type:"txt", font:"6x8", label:"Towards" }, + {type:"txt", font:fontLarge, label:street } + ]}, + {type:"h",fillx:1, filly:1, c: [ + msg.img?{type:"img",src:atob(msg.img), scale:2}:{}, + {type:"v", fillx:1, c: [ + {type:"txt", font:fontLarge, label:distance||"" } + ]}, + ]}, + {type:"txt", font:"6x8:2", label:eta } + ]}); + g.clearRect(Bangle.appRect); + layout.render(); + Bangle.setUI("updown",function() { + // any input to mark as not new and return to menu + msg.new = false; + saveMessages(); + layout = undefined; + checkMessages(); + }); +} + +function showMusicMessage(msg) { + function fmtTime(s) { + var m = Math.floor(s/60); + s = (s%60).toString().padStart(2,0); + return m+":"+s; + } + + function back() { + msg.new = false; + saveMessages(); + layout = undefined; + checkMessages(); + } + layout = new Layout({ type:"v", c: [ + {type:"h", fillx:1, bgCol:colBg, c: [ + { type:"btn", src:getBackImage, cb:back }, + { type:"v", fillx:1, c: [ + { type:"txt", font:fontLarge, label:msg.artist, pad:2 }, + { type:"txt", font:fontMedium, label:msg.album, pad:2 } + ]} + ]}, + {type:"txt", font:fontLarge, label:msg.track, fillx:1, filly:1, pad:2 }, + Bangle.musicControl?{type:"h",fillx:1, c: [ + {type:"btn", pad:8, label:"\0"+atob("FhgBwAADwAAPwAA/wAD/gAP/gA//gD//gP//g///j///P//////////P//4//+D//gP/4A/+AD/gAP8AA/AADwAAMAAA"), cb:()=>Bangle.musicControl("play")}, // play + {type:"btn", pad:8, label:"\0"+atob("EhaBAHgHvwP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP/wP3gHg"), cb:()=>Bangle.musicControl("pause")}, // pause + {type:"btn", pad:8, label:"\0"+atob("EhKBAMAB+AB/gB/wB/8B/+B//B//x//5//5//x//B/+B/8B/wB/gB+AB8ABw"), cb:()=>Bangle.musicControl("next")}, // next + ]}:{}, + {type:"txt", font:"6x8:2", label:msg.dur?fmtTime(msg.dur):"--:--" } + ]}); + g.clearRect(Bangle.appRect); + layout.render(); +} + +function showMessage(msgid) { + var msg = MESSAGES.find(m=>m.id==msgid); + if (!msg) return checkMessages(); // go home if no message found + if (msg.src=="Maps") return showMapMessage(msg); + if (msg.id=="music") return showMusicMessage(msg); + // Normal text message display + var title=msg.title, titleFont = fontLarge; + if (title) { + var w = g.getWidth()-40; + if (g.setFont(titleFont).stringWidth(title) > w) + titleFont = fontMedium; + if (g.setFont(titleFont).stringWidth(title) > w) + title = g.wrapString(title, w).join("\n"); + } + layout = new Layout({ type:"v", c: [ + {type:"h", fillx:1, bgCol:colBg, c: [ + { type:"img", src:getMessageImage(msg), pad:2 }, + { type:"v", fillx:1, c: [ + {type:"txt", font:fontMedium, label:msg.src||"Message", bgCol:colBg, fillx:1, pad:2 }, + title?{type:"txt", font:titleFont, label:title, bgCol:colBg, fillx:1, pad:2 }:{}, + ]}, + ]}, + {type:"txt", font:fontMedium, label:msg.body||"", wrap:true, fillx:1, filly:1, pad:2 }, + {type:"h",fillx:1, c: [ + {type:"btn", src:getBackImage(), cb:()=>checkMessages(true)}, // back + msg.new?{type:"btn", src:atob("HRiBAD///8D///wj///Fj//8bj//x3z//Hvx/8/fx/j+/x+Ad/B4AL8Rh+HxwH+PHwf+cf5/+x/n/PH/P8cf+cx5/84HwAB4fgAD5/AAD/8AAD/wAAD/AAAD8A=="), cb:()=>{ + msg.new = false; // read mail + saveMessages(); + checkMessages(); + }}:{} + ]} + ]}); + g.clearRect(Bangle.appRect); + layout.render(); +} + +function checkMessages(forceShowMenu) { + // If no messages, just show 'no messages' and return + if (!MESSAGES.length) + return E.showPrompt("No Messages",{ + title:"Messages", + img:require("heatshrink").decompress(atob("kkk4UBrkc/4AC/tEqtACQkBqtUDg0VqAIGgoZFDYQIIM1sD1QAD4AIBhnqA4WrmAIBhc6BAWs8AIBhXOBAWz0AIC2YIC5wID1gkB1c6BAYFBEQPqBAYXBEQOqBAnDAIQaEnkAngaEEAPDFgo+IKA5iIOhCGIAFb7RqAIGgtUBA0VqobFgNVA")), + buttons : {"Ok":1} + }).then(() => { load() }); + // we have >0 messages + // If we have a new message, show it + if (!forceShowMenu) { + var newMessages = MESSAGES.filter(m=>m.new); + if (newMessages.length) + return showMessage(newMessages[0].id); + } + // Otherwise show a menu + E.showScroller({ + h : 48, + c : MESSAGES.length+1, + draw : function(idx, r) {"ram" + var msg = MESSAGES[idx-1]; + if (msg && msg.new) g.setBgColor(colBg); + else g.setBgColor((idx&1) ? colSBg1 : colSBg2); + g.clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1).setColor(g.theme.fg); + if (idx==0) msg = {id:"back", title:"< Back"}; + if (!msg) return; + var x = r.x+2, title = msg.title, body = msg.body; + var img = getMessageImage(msg); + if (msg.id=="music") { + title = msg.artist || "Music"; + body = msg.track; + } + if (img) { + g.drawImage(img, x+24, r.y+24, {rotate:0}); // force centering + x += 50; + } + var m = msg.title+"\n"+msg.body; + if (msg.src) g.setFontAlign(1,-1).setFont("6x8").drawString(msg.src, r.x+r.w-2, r.y+2); + if (title) g.setFontAlign(-1,-1).setFont(fontBig).drawString(title, x,r.y+2); + if (body) { + g.setFontAlign(-1,-1).setFont("6x8"); + var l = g.wrapString(body, r.w-14); + if (l.length>3) { + l = l.slice(0,3); + l[l.length-1]+="..."; + } + g.drawString(l.join("\n"), x+10,r.y+20); + } + }, + select : idx => { + if (idx==0) load(); + else showMessage(MESSAGES[idx-1].id); + } + }); +} + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +checkMessages(); diff --git a/apps/messages/app.png b/apps/messages/app.png new file mode 100644 index 0000000000000000000000000000000000000000..c9177692e282e1247ced30f6ec0e2d14dc6dfa25 GIT binary patch literal 917 zcmV;G18V$CmK~!jg?U~I_6G0fppJ|s5%ZG(_jU{sLoS)M|Tx5HNwbU93{dM|XETG(}U{r87GP zP5QgOGds^S`_B9Bv_O#}MT#6JB;SE&AFiJ#PVFW@+5j{Fs1U4W3&1i!=cz7@tv)#Y zDW6G)8t?@prO7h)FeSJHz+qQqp6HZfw0bu&7zz6JtOi;d@C75Ko8|7;0Ims@mp=#J3E*{IZSCaRo7jz0My7S0nqA z?9Rp%4b8HI>M{r3e%-^}7vKLHd-Y5ye(oBGDVaVJ^4o8QwhZKo5Ba?~SxzwZA%&Tb z+lVS@06>def}RU5^jtiFA3Jn^jtCRn26CIwMDK4Qfz}EHS`WVSdt3w|zjyyIcTdI< z_Iq%ulJDxlW!-Ky5!DO<4g;b}p(qo~D|bzZD}}iwxHqISKZAMo>{p|R3Ib$Ig#6z9 zuUuBR4)M~4hRY-CJX3}9-`~ir3~U~mio^M77O*m~S^yz@5V~R(vM@mB3ZaDu3db9> zn1uo77y$nJqBwM-ljmkZQv)kQbrDK2S{O|%(5EZ+>pq)BEvr!VZekF?f^bdwGcVVy z-?JKEX&@5x?N#k0IslB|Xwyjp=o7hSt>fM8D`~5NdH=;!|7gueKnEx>+CfPJ#Q*e| r1fk1>I_9WBo>`?$ks?Kk{5$*tT^fbQe@cvs00000NkvXXu0mjfYw(!I literal 0 HcmV?d00001 diff --git a/apps/messages/lib.js b/apps/messages/lib.js new file mode 100644 index 000000000..f3ea242e5 --- /dev/null +++ b/apps/messages/lib.js @@ -0,0 +1,37 @@ +exports.pushMessage = function(event) { + /* event is: + {t:"add",id:int, src,title,subject,body,sender,tel, important:bool} // add new + {t:"add",id:int, id:"music", state, artist, track, etc} // add new + {t:"remove-",id:int} // remove + {t:"modify",id:int, title:string} // modified + */ + var messages, inApp = "undefined"!=typeof MESSAGES; + if (inApp) + messages = MESSAGES; // we're in an app that has already loaded messages + else // no app - load messages + messages = require("Storage").readJSON("messages.json",1)||[]; + // now modify/delete as appropriate + var mIdx = messages.findIndex(m=>m.id==event.id); + if (event.t=="remove") { + if (mIdx>=0) messages.splice(mIdx, 1); // remove item + mIdx=-1; + } else { // add/modify + if (event.t=="add") event.new=true; // new message + if (mIdx<0) mIdx=messages.push(event)-1; + else Object.assign(messages[mIdx], event); + } + require("Storage").writeJSON("messages.json",messages); + // if in app, process immediately + if (inApp) return onMessagesModified(mIdx<0 ? {id:event.id} : messages[mIdx]); + // ok, saved now - we only care if it's new + if (event.t!="add") return; + // otherwise load after a delay, to ensure we have all the messages + if (exports.messageTimeout) clearTimeout(exports.messageTimeout); + exports.messageTimeout = setTimeout(function() { + exports.messageTimeout = undefined; + // if we're in a clock or it's important, go straight to messages app + if (Bangle.CLOCK || event.important) return load("messages.app.js"); + if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know + WIDGETS.messages.newMessage(); + }, 500); +} diff --git a/apps/messages/widget.js b/apps/messages/widget.js new file mode 100644 index 000000000..eda4a85a5 --- /dev/null +++ b/apps/messages/widget.js @@ -0,0 +1,20 @@ +WIDGETS["messages"]={area:"tl",width:0,draw:function() { + if (!this.width) return; + var c = (Date.now()-this.t)/1000; + g.reset().setBgColor((c&1) ? "#0f0" : "#030").setColor((c&1) ? "#000" : "#fff"); + g.clearRect(this.x,this.y,this.x+this.width,this.y+23); + g.setFont("6x8:1x2").setFontAlign(0,0).drawString("MESSAGES", this.x+this.width/2, this.y+12); + //if (c<60) Bangle.setLCDPower(1); // keep LCD on for 1 minute + if (c<120 && (Date.now()-this.l)>4000) { + this.l = Date.now(); + Bangle.buzz(); // buzz every 4 seconds + } + setTimeout(()=>WIDGETS["messages"].draw(), 1000); +},newMessage:function() { + WIDGETS["messages"].t=Date.now(); // first time + WIDGETS["messages"].l=Date.now()-10000; // last buzz + if (WIDGETS["messages"].c!==undefined) return; // already called + WIDGETS["messages"].width=64; + Bangle.drawWidgets(); + Bangle.setLCDPower(1);// turns screen on +}}; diff --git a/apps/multiclock/ChangeLog b/apps/multiclock/ChangeLog index 2f27f7f28..9d02ae85e 100644 --- a/apps/multiclock/ChangeLog +++ b/apps/multiclock/ChangeLog @@ -1,13 +1,11 @@ -0.01: New App! -0.02: Separate *.face.js files for faces -0.03: Renaming -0.04: Bug Fixes -0.05: Add README -0.06: Add txt clock -0.07: Add Time Date clock and fix font sizes -0.08: Add pinned clock face -0.09: Added Pedometer clock -0.10: Added GPS and Grid Ref clock faces -0.11: Updated Pedometer clock to retrieve steps from either wpedom or activepedom -0.12: Removed GPS and Grid Ref clock faces, superceded by GPS setup and Walkers Clock -0.13: Localised digi.js and timdat.js \ No newline at end of file +0.01: Initial version +0.02: Add pinned clock facility +0.03: Lnng touch switch to night clock - ANCS off, dimmed +0.04: use theme, font heights etc +0.05: make Bangle compatible +0.06: add minute tick for efficiency and nifty A clock +0.07: compatible with Bang;e.js 2 +0.08: fix minute tick bug + + + diff --git a/apps/multiclock/README.md b/apps/multiclock/README.md index b1773b8df..e8b8335ea 100644 --- a/apps/multiclock/README.md +++ b/apps/multiclock/README.md @@ -1,30 +1,11 @@ # Multiclock -This is a clock app that supports multiple clock faces. The user can switch between faces while retaining widget state which makes the switch fast and preserves state such as bluetooth connections. Currently there are four clock faces as shown below. To my eye, these faces look better when widgets are hidden using **widviz**. - +This is a clock app that supports multiple clock faces. The user can switch between faces while retaining widget state which makes the switch fast. Currently there are four clock faces as shown below. There are currently an anlog, digital, text, big digit, time and date, and a clone of the Nifty-A-Clock faces. ### Analog Clock Face -![](anaface.jpg) - -### Digital Clock Face -![](digiface.jpg) - -### Big Digit Clock Face -![](bigface.jpg) - -### Text Clock Face -![](txtface.jpg) - -### Time and Date Clock Face ## Controls -Clock faces are kept in a circular list. - -*BTN1* - switches to the next clock face. - -*BTN2* - switches to the app launcher. - -*BTN3* - switches to the previous clock face. +Swipe left and right on both the Bangle and Bangle 2 switch between faces. BTN1 & BTH3 also switch faces on the Bangle. ## Adding a new face Clock faces are described in javascript storage files named `name.face.js`. For example, the Analog Clock Face is described in `ana.face.js`. These files have the following structure: @@ -38,7 +19,7 @@ Clock faces are described in javascript storage files named `name.face.js`. For function drawAll(){ //draw background + initial state of digits, hands etc } - return {init:drawAll, tick:onSecond}; + return {init:drawAll, tick:onSecond, tickpersec:true}; } return getFace; })(); @@ -47,6 +28,5 @@ For those familiar with the structure of widgets, this is similar, however, ther The app at start up loads all files `*.face.js`. The simplest way of adding a face is thus to load it into `Storage` using the WebIDE. Similarly, to remove an unwanted face, simply delete it from `Storage` using the WebIDE. -## Support +If `tickpersec` is false then `tick` is only called each minute as this is more power effcient - especially on the BAngle 2. -Please report bugs etc. by raising an issue [here](https://github.com/jeffmer/JeffsBangleAppsDev). \ No newline at end of file diff --git a/apps/multiclock/ana.js b/apps/multiclock/ana.face.js similarity index 59% rename from apps/multiclock/ana.js rename to apps/multiclock/ana.face.js index 4fd5a7251..af1c84c9f 100644 --- a/apps/multiclock/ana.js +++ b/apps/multiclock/ana.face.js @@ -5,54 +5,60 @@ const p = Math.PI/2; const PRad = Math.PI/180; + var cx = g.getWidth()/2; + var cy = 12+g.getHeight()/2; + var scale = (g.getHeight()-24)/(240-24); + scale = scale>=1 ? 1 : scale; + function seconds(angle, r) { const a = angle*PRad; - const x = 120+Math.sin(a)*r; - const y = 134-Math.cos(a)*r; + const x = cx+Math.sin(a)*r; + const y = cy-Math.cos(a)*r; if (angle % 90 == 0) { - g.setColor(0,1,1); + g.setColor(g.theme.fg2); g.fillRect(x-6,y-6,x+6,y+6); } else if (angle % 30 == 0){ - g.setColor(0,1,1); + g.setColor(g.theme.fg); g.fillRect(x-4,y-4,x+4,y+4); } else { - g.setColor(1,1,1); + g.setColor(g.theme.fg); g.fillRect(x-1,y-1,x+1,y+1); } } function hand(angle, r1,r2, r3) { + r1 = scale*r1; r2=scale*r2; r3 = scale*r3; const a = angle*PRad; g.fillPoly([ - 120+Math.sin(a)*r1, - 134-Math.cos(a)*r1, - 120+Math.sin(a+p)*r3, - 134-Math.cos(a+p)*r3, - 120+Math.sin(a)*r2, - 134-Math.cos(a)*r2, - 120+Math.sin(a-p)*r3, - 134-Math.cos(a-p)*r3]); + cx+Math.sin(a)*r1, + cy-Math.cos(a)*r1, + cx+Math.sin(a+p)*r3, + cy-Math.cos(a+p)*r3, + cx+Math.sin(a)*r2, + cy-Math.cos(a)*r2, + cx+Math.sin(a-p)*r3, + cy-Math.cos(a-p)*r3]); } var minuteDate; var secondDate; function onSecond() { - g.setColor(0,0,0); + g.setColor(g.theme.bg); hand(360*secondDate.getSeconds()/60, -5, 90, 3); if (secondDate.getSeconds() === 0) { hand(360*(minuteDate.getHours() + (minuteDate.getMinutes()/60))/12, -16, 60, 7); hand(360*minuteDate.getMinutes()/60, -16, 86, 7); minuteDate = new Date(); } - g.setColor(1,1,1); + g.setColor(g.theme.fg); hand(360*(minuteDate.getHours() + (minuteDate.getMinutes()/60))/12, -16, 60, 7); hand(360*minuteDate.getMinutes()/60, -16, 86, 7); - g.setColor(0,1,1); + g.setColor(g.theme.fg2); secondDate = new Date(); hand(360*secondDate.getSeconds()/60, -5, 90, 3); - g.setColor(0,0,0); - g.fillCircle(120,134,2); + g.setColor(g.theme.bg); + g.fillCircle(cx,cy,2); } function drawAll() { @@ -60,11 +66,11 @@ // draw seconds g.setColor(1,1,1); for (let i=0;i<60;i++) - seconds(360*i/60, 100); + seconds(360*i/60, 100*scale); onSecond(); } - return {init:drawAll, tick:onSecond}; + return {init:drawAll, tick:onSecond, tickpersec:true}; } return getFace; diff --git a/apps/multiclock/anaface.jpg b/apps/multiclock/anaface.jpg deleted file mode 100644 index 86aaccd5496cbd435baa8bc522eec38b71e768ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44568 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 GfB)IuGe&O! diff --git a/apps/multiclock/apps_entry.json b/apps/multiclock/apps_entry.json deleted file mode 100644 index 6383609c1..000000000 --- a/apps/multiclock/apps_entry.json +++ /dev/null @@ -1,19 +0,0 @@ -{ "id": "multiclock", - "name": "Multi Clock", - "icon": "multiclock.png", - "version":"0.06", - "description": "Clock with multiple faces - Big, Analogue, Digital, Text.\n Switch between faces with BT1 & BTN3", - "readme": "README.md", - "tags": "clock", - "type":"clock", - "allow_emulator":false, - "storage": [ - {"name":"multiclock.app.js","url":"clock.min.js"}, - {"name":"big.face.js","url":"big.min.js"}, - {"name":"ana.face.js","url":"ana.min.js"}, - {"name":"digi.face.js","url":"digi.min.js"}, - {"name":"txt.face.js","url":"txt.min.js"}, - {"name":"ped.face.js","url":"ped.js"}, - {"name":"multiclock.img","url":"multiclock-icon.js","evaluate":true} - ] - }, diff --git a/apps/multiclock/big.face.js b/apps/multiclock/big.face.js new file mode 100644 index 000000000..2db4ee4d4 --- /dev/null +++ b/apps/multiclock/big.face.js @@ -0,0 +1,31 @@ +(() => { + + function getFace(){ + + const W = g.getWidth(); + const H = g.getHeight(); + const F = 132*H/240; // reasonable approximation + + function drawTime() { + d = new Date() + g.reset(); + var da = d.toString().split(" "); + var time = da[4].substr(0, 5).split(":"); + var hours = time[0], + minutes = time[1]; + g.clearRect(0,24,W-1,H-1); + g.setColor(g.theme.fg); + g.setFont("Vector",F); + g.setFontAlign(0,-1); + g.drawString(hours,W/2,24,true); + g.setColor(g.theme.fg2); + g.drawString(minutes,W/2,12+H/2,true); + } + + + return {init:drawTime, tick:drawTime, tickpersecond:false}; + } + + return getFace; + +})(); \ No newline at end of file diff --git a/apps/multiclock/big.js b/apps/multiclock/big.js deleted file mode 100644 index 2e83d8fb5..000000000 --- a/apps/multiclock/big.js +++ /dev/null @@ -1,32 +0,0 @@ -(() => { - - function getFace(){ - - function drawTime(d) { - g.reset(); - var da = d.toString().split(" "); - var time = da[4].substr(0, 5).split(":"); - var hours = time[0], - minutes = time[1]; - g.clearRect(0,24,239,239); - g.setColor(1,1,1); - g.setFont("Vector",132); - g.drawString(hours,50,24,true); - g.drawString(minutes,50,132,true); - } - - function onSecond(){ - var t = new Date(); - if (t.getSeconds() === 0) drawTime(t); - } - - function drawAll(){ - drawTime(new Date()); - } - - return {init:drawAll, tick:onSecond}; - } - - return getFace; - -})(); \ No newline at end of file diff --git a/apps/multiclock/bigface.jpg b/apps/multiclock/bigface.jpg deleted file mode 100644 index 6857268644e9f09aca81bdd2b8ef32c9633b2f66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40453 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 diff --git a/apps/multiclock/clock.info b/apps/multiclock/clock.info new file mode 100644 index 000000000..441de1463 --- /dev/null +++ b/apps/multiclock/clock.info @@ -0,0 +1 @@ +{"id":"clock","name":"Clock","type":"clock","src":"clock.app.js","icon":"clock.img","version":"0.06","files":"clock.info,clock.app.js,big.face.js,ana.face.js,digi.face.js,txt.face.js"} \ No newline at end of file diff --git a/apps/multiclock/clock.js b/apps/multiclock/clock.js deleted file mode 100644 index 50410f096..000000000 --- a/apps/multiclock/clock.js +++ /dev/null @@ -1,69 +0,0 @@ -var FACES = []; -var STOR = require("Storage"); -STOR.list(/\.face\.js$/).forEach(face=>FACES.push(eval(require("Storage").read(face)))); -var lastface = STOR.readJSON("multiclock.json")||{pinned:0}; -var iface = lastface.pinned; -var face = FACES[iface](); -var intervalRefSec; - -function stopdraw() { - if(intervalRefSec) {intervalRefSec=clearInterval(intervalRefSec);} -} - -function startdraw() { - g.clear(); - g.reset(); - Bangle.drawWidgets(); - face.init(); - intervalRefSec = setInterval(face.tick,1000); -} - -function setButtons(){ - function newFace(inc){ - var n = FACES.length-1; - iface+=inc; - iface = iface>n?0:iface<0?n:iface; - stopdraw(); - face = FACES[iface](); - startdraw(); - } - function finish(){ - if (lastface.pinned!=iface){ - lastface.pinned=iface; - STOR.write("multiclock.json",lastface); - } - Bangle.showLauncher(); - } - setWatch(finish, BTN2, {repeat:false,edge:"falling"}); - setWatch(newFace.bind(null,1), BTN1, {repeat:true,edge:"rising"}); - setWatch(newFace.bind(null,-1), BTN3, {repeat:true,edge:"rising"}); -} - -var SCREENACCESS = { - withApp:true, - request:function(){ - this.withApp=false; - stopdraw(); - clearWatch(); - }, - release:function(){ - this.withApp=true; - startdraw(); - setButtons(); - } -}; - -Bangle.on('lcdPower',function(on) { - if (!SCREENACCESS.withApp) return; - if (on) { - startdraw(); - } else { - stopdraw(); - } -}); - -g.clear(); -Bangle.loadWidgets(); -startdraw(); -setButtons(); - diff --git a/apps/multiclock/digi.face.js b/apps/multiclock/digi.face.js new file mode 100644 index 000000000..21f339afc --- /dev/null +++ b/apps/multiclock/digi.face.js @@ -0,0 +1,38 @@ +(() => { + +function getFace(){ + + var W = g.getWidth(); + var H = g.getHeight(); + var scale = W/240; + + var buf = Graphics.createArrayBuffer(W,92,1,{msb:true}); + function flip() { + g.setColor(g.theme.fg); + g.drawImage({width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer},0,H/2-34); + } + + var W = g.getWidth(); + var H = g.getHeight(); + + function drawTime() { + buf.clear(); + buf.setColor(1); + var d = new Date(); + var da = d.toString().split(" "); + var time = da[4]; + buf.setFont("Vector",54*scale); + buf.setFontAlign(0,-1); + buf.drawString(time,W/2,0); + buf.setFont("6x8",scale<1?1:2); + buf.setFontAlign(0,-1); + var date = d.toString().substr(0,15); + buf.drawString(date, W/2, 70*scale); + flip(); + } + return {init:drawTime, tick:drawTime, tickpersec:true}; +} + +return getFace; + +})(); \ No newline at end of file diff --git a/apps/multiclock/digi.js b/apps/multiclock/digi.js deleted file mode 100644 index 0b2ca4aaa..000000000 --- a/apps/multiclock/digi.js +++ /dev/null @@ -1,33 +0,0 @@ -(() => { - -var locale = require("locale"); - -function getFace(){ - - var buf = Graphics.createArrayBuffer(240,92,1,{msb:true}); - function flip() { - g.setColor(1,1,1); - g.drawImage({width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer},0,85); - } - - function drawTime() { - buf.clear(); - buf.setColor(1); - var d = new Date(); - var da = d.toString().split(" "); - var time = da[4]; - buf.setFont("Vector",54); - buf.setFontAlign(0,-1); - buf.drawString(time,buf.getWidth()/2,0); - buf.setFont("6x8",2); - buf.setFontAlign(0,-1); - var date = locale.dow(d, 1) + " " + locale.date(d, 1); - buf.drawString(date, buf.getWidth()/2, 70); - flip(); - } - return {init:drawTime, tick:drawTime}; -} - -return getFace; - -})(); \ No newline at end of file diff --git a/apps/multiclock/digiface.jpg b/apps/multiclock/digiface.jpg deleted file mode 100644 index b0323bd55d1e9947e5573171b77d851445c5b8f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48073 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
+ + + + + diff --git a/apps/recorder/settings.js b/apps/recorder/settings.js new file mode 100644 index 000000000..2a9a7a0d8 --- /dev/null +++ b/apps/recorder/settings.js @@ -0,0 +1,4 @@ +(function(back) { + // just go right to our app - we need all the memory + load("record.app.js"); +})(); diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js new file mode 100644 index 000000000..09893bbb7 --- /dev/null +++ b/apps/recorder/widget.js @@ -0,0 +1,222 @@ +(() => { + var storageFile; // file for GPS track + var entriesWritten = 0; + var activeRecorders = []; + var writeInterval; + + function loadSettings() { + var settings = require("Storage").readJSON("recorder.json",1)||{}; + settings.period = settings.period||10; + if (!settings.file || !settings.file.startsWith("recorder.log")) + settings.recording = false; + return settings; + } + + function getRecorders() { + var recorders = { + gps:function() { + var lat = 0; + var lon = 0; + var alt = 0; + var samples = 0; + var hasFix = 0; + function onGPS(f) { + hasFix = f.fix; + if (!hasFix) return; + lat += f.lat; + lon += f.lon; + alt += f.alt; + samples++; + } + return { + name : "GPS", + fields : ["Latitude","Longitude","Altitude"], + getValues : () => { + var r = ["","",""]; + if (samples) + r = [(lat/samples).toFixed(6),(lon/samples).toFixed(6),Math.round(alt/samples)]; + samples = 0; lat = 0; lon = 0; alt = 0; + return r; + }, + start : () => { + hasFix = false; + Bangle.on('GPS', onGPS); + Bangle.setGPSPower(1,"recorder"); + }, + stop : () => { + hasFix = false; + Bangle.removeListener('GPS', onGPS); + Bangle.setGPSPower(0,"recorder"); + }, + draw : (x,y) => g.setColor(hasFix?"#f00":"#888").drawImage(atob("DAyBAAACADgDuBOAeA4AzAHADgAAAA=="),x,y) + }; + }, + hrm:function() { + var bpm = 0, bpmConfidence = 0; + var hasBPM = false; + function onHRM(h) { + if (h.confidence >= bpmConfidence) { + bpmConfidence = h.confidence; + bpm = h.bpm; + if (bpmConfidence) hasBPM = true; + } + } + return { + name : "HR", + fields : ["Heartrate"], + getValues : () => { + var r = [bpmConfidence?bpm:""]; + bpm = 0; bpmConfidence = 0; + return r; + }, + start : () => { + hasBPM = false; + Bangle.on('HRM', onHRM); + Bangle.setHRMPower(1,"recorder"); + }, + stop : () => { + hasBPM = false; + Bangle.removeListener('HRM', onHRM); + Bangle.setHRMPower(0,"recorder"); + }, + draw : (x,y) => g.setColor(hasBPM?"#f00":"#888").drawImage(atob("DAyBAAAAAD/H/n/n/j/D/B+AYAAAAA=="),x,y) + }; + }, + steps:function() { + var lastSteps = 0; + return { + name : "Steps", + fields : ["Steps"], + getValues : () => { + var c = Bangle.getStepCount(), r=[c-lastSteps]; + lastSteps = c; + return r; + }, + start : () => { lastSteps = Bangle.getStepCount(); }, + stop : () => {}, + draw : (x,y) => g.reset().drawImage(atob("DAyBAAADDHnnnnnnnnnnjDmDnDnAAA=="),x,y) + }; + } + // TODO: recAltitude from pressure sensor + }; + /* eg. foobar.recorder.js + (function(recorders) { + recorders.foobar = { + name : "Foobar", + fields : ["foobar"], + getValues : () => [123], + start : () => {}, + stop : () => {}, + draw (x,y) => {} // draw 12x12px status image + } + }) + */ + require("Storage").list(/^.*\.recorder\.js$/).forEach(fn=>eval(fn)(recorders)); + return recorders; + } + + function writeLog() { + entriesWritten++; + WIDGETS["recorder"].draw(); + try { + var fields = [Math.round(getTime())]; + activeRecorders.forEach(recorder => fields.push.apply(fields,recorder.getValues())); + if (storageFile) storageFile.write(fields.join(",")+"\n"); + } catch(e) { + // If storage.write caused an error, disable + // GPS recording so we don't keep getting errors! + console.log("recorder: error", e); + var settings = loadSettings(); + settings.recording = false; + require("Storage").write("recorder.json", settings); + reload(); + } + } + + // Called by the GPS app to reload settings and decide what to do + function reload() { + var settings = loadSettings(); + if (writeInterval) clearInterval(writeInterval); + writeInterval = undefined; + + activeRecorders.forEach(rec => rec.stop()); + activeRecorders = []; + entriesWritten = 0; + + if (settings.recording) { + // set up recorders + var recorders = getRecorders(); // TODO: order?? + settings.record.forEach(r => { + var recorder = recorders[r]; + if (!recorder) { + console.log("Recorder for "+E.toJS(r)+"+not found"); + return; + } + var activeRecorder = recorder(); + activeRecorder.start(); + activeRecorders.push(activeRecorder); + // TODO: write field names? + }); + WIDGETS["recorder"].width = 15 + ((activeRecorders.length+1)>>1)*12; // 12px per recorder + // open/create file + if (require("Storage").list(settings.file).length) { // Append + storageFile = require("Storage").open(settings.file,"a"); + // TODO: what if loaded modules are different?? + } else { + storageFile = require("Storage").open(settings.file,"w"); + // New file - write headers + var fields = ["Time"]; + activeRecorders.forEach(recorder => fields.push.apply(fields,recorder.fields)); + storageFile.write(fields.join(",")+"\n"); + } + // start recording... + WIDGETS["recorder"].draw(); + writeInterval = setInterval(writeLog, settings.period*1000); + } else { + WIDGETS["recorder"].width = 0; + storageFile = undefined; + } + } + // add the widget + WIDGETS["recorder"]={area:"tl",width:0,draw:function() { + if (!writeInterval) return; + g.reset(); g.drawImage(atob("DRSBAAGAHgDwAwAAA8B/D/hvx38zzh4w8A+AbgMwGYDMDGBjAA=="),this.x+1,this.y+2); + activeRecorders.forEach((recorder,i)=>{ + recorder.draw(this.x+15+(i>>1)*12, this.y+(i&1)*12); + }); + },getRecorders:getRecorders,reload:function() { + reload(); + Bangle.drawWidgets(); // relayout all widgets + },setRecording:function(isOn) { + var settings = loadSettings(); + if (isOn && !settings.recording && require("Storage").list(settings.file).length) + return E.showPrompt("Overwrite\nLog 0?",{title:"Recorder",buttons:{Yes:"yes",No:"no"}}).then(selection=>{ + if (selection=="no") return false; // just cancel + if (selection=="yes") require("Storage").open(settings.file,"r").erase(); + // TODO: Add 'new file' option + return WIDGETS["recorder"].setRecording(1); + }); + settings.recording = isOn; + require("Storage").write("recorder.json", settings); + WIDGETS["recorder"].reload(); + return Promise.resolve(settings.recording); + }/*,plotTrack:function(m) { // m=instance of openstmap module + // if we're here, settings was already loaded + var f = require("Storage").open(settings.file,"r"); + var l = f.readLine(f); + if (l===undefined) return; + var c = l.split(","); + var mp = m.latLonToXY(+c[1], +c[2]); + g.moveTo(mp.x,mp.y); + l = f.readLine(f); + while(l!==undefined) { + c = l.split(","); + mp = m.latLonToXY(+c[1], +c[2]); + g.lineTo(mp.x,mp.y); + g.fillCircle(mp.x,mp.y,2); // make the track more visible + l = f.readLine(f); + } + }*/}; + // load settings, set correct widget width + reload(); +})() diff --git a/apps/s7clk/README.md b/apps/s7clk/README.md new file mode 100644 index 000000000..6b91abfe3 --- /dev/null +++ b/apps/s7clk/README.md @@ -0,0 +1,4 @@ +# Simple 7 Segment Clock + +![](screenshot_s7segment.png) + diff --git a/apps/s7clk/screenshot_s7segment.png b/apps/s7clk/screenshot_s7segment.png new file mode 100644 index 0000000000000000000000000000000000000000..a0386e540ffbcc711c41c9ec10213e6121dc6c8d GIT binary patch literal 2144 zcmeH}>pR;C7RTe)&`?FFsi7`$DW&Mru1h6Nk)*1w)u3)My=dLF8maql%VLda8J+4N zDmrZ?b(bJSmJ&MDQcbm#5VwkuGK8^Yc(?l>?2CPI&Uv2i^Z7pKJm~g``d#~2+2TX1+*Dz;-_lnG2Z=55f`rWaw5C}}u#reDsDOglGb%$!Nh%J<) zEsVPaY#djbYj_A(wc8F0`$tqC?jZL`d}5k|PQI2@&+{jaHkgYiH>Sb##7A>uJEZPx z$@j@2nuei`41YA=u-ncc&-dI%LoDCA%CSEb@Bk^(8~AAqzd^T55S^&}| zBVUHvbJ^vT`wMMPE*uH1vg#^dI-b-tSkItb-N{wlII$oF@*@!_ zJVB_HL7YYXAdBVaj(#rgp}pi~HLC*5HJzX>EK_AdJ@5&v;s|>Jub^(Knt)U}^$MkemB)nXAM;w{?HRP>L z9E@J2k&q&x%_soeP{Uz=sKw5WvW9Y18*=)(TVUYLl}E&s;(7Y@94#EWR5m|aQepL1 zCq=^Rz~VJkH>zyAZm#svZz>1$p; zp3W(zIYk#h09WVJy_X=MwQttAp$znK_N|A-5Fo5tsl^3=orDkRS<67*1!~_ZgsS-I zSDe2Iz)D)|XO7G3)jB&r=!dG<-o4lBriv!yWXt`JH6(mDy3OpOr@gneFJtKER*mHBgGqo>M8^k3*%iKonf*U=DzLk(vY&ZYa9ayu^6@%GI z^#350%}$w#SqET|ZO(BkuaC;4OMqeB8+g?S>YXfQ;ZT13$c$R0>4Pfmf4GJA@S62x-8 z$}ig2gzY<6n!X2u{Txl@jO6BqTk2zlEZ;YKvJm7@EA2&C+1S6^(OGZPT2e>yzG~}& zrQs$rC0)7`L9;9Bm)tmKQGi+JqFzd@@>j*XehE;NqNoJC0=9XPbtZqIEY_?_svlCn zNus^{K=n{OUl&M{y-aGif$h0qKXTl0fnPr0DkH>ZZGEaqq!oo%n~B^oS$u{+=ns~X zn_C-z7=hMogCz6KL@E^%CaYug;kJ!wx^;i(7u?#U+4P>O_84Fuc$MKp5WbI$cPQ#; z4K@!sLm3i^f_W^aHkromf54%dq>UlOE0kC?|6EL!yq9^%nCMf?fPzCaw-I1Q`_l!K z9r@XT1iaJ=7<_t45SR-ALTF_nHd@f#)`F0TPFV8m#MCd5Dz@b_9=#2^i)0OJNTywe zeUQuc*|@o4ozZ7S-KK*Vwm&-N^`fli%nBwplcp^M?75b(e?PL*6GQ8mP@NOw)|uuf zUJIC*E1i;_w4JTqLlB{@!J2oAhsh+u=}Cx@Zb3J{+!c{1wd`vAI7X3l=4ICrms!kB z>Y5jcE-*`TM!s6P5+rfrgbK;~r{ z)pTSCkW4ej~h zgBSO%+VHHweP|Cj3{`24-@2d!#+9Fzfm#Hi3UGvD|40981DncRto&^fT)lAbWq`O~ L+?}f(34i+&w))`% literal 0 HcmV?d00001 diff --git a/apps/sclock/ChangeLog b/apps/sclock/ChangeLog index 44a0ec504..dc76b8299 100644 --- a/apps/sclock/ChangeLog +++ b/apps/sclock/ChangeLog @@ -3,3 +3,4 @@ 0.04: Make this clock do 12h and 24h 0.05: setUI, screen size changes 0.06: Use Bangle.setUI for button/launcher handling +0.07: Update *on* the minute rather than every 15 secs diff --git a/apps/sclock/clock-simple.js b/apps/sclock/clock-simple.js index 8fb204d22..a399b05a7 100644 --- a/apps/sclock/clock-simple.js +++ b/apps/sclock/clock-simple.js @@ -1,4 +1,3 @@ -/* jshint esversion: 6 */ const big = g.getWidth()>200; const timeFontSize = big?6:5; const dateFontSize = big?3:2; @@ -14,7 +13,19 @@ const yposGMT = xyCenter*1.9; // Check settings for what type our clock should be var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -function drawSimpleClock() { +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { // get date var d = new Date(); var da = d.toString().split(" "); @@ -60,11 +71,18 @@ function drawSimpleClock() { var gmt = da[5]; g.setFont(font, gmtFontSize); g.drawString(gmt, xyCenter, yposGMT, true); + + queueDraw(); } -// handle switch display on by pressing BTN1 -Bangle.on('lcdPower', function(on) { - if (on) drawSimpleClock(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } }); // clean app screen @@ -74,8 +92,5 @@ Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); -// refesh every 15 sec -setInterval(drawSimpleClock, 15E3); - // draw now -drawSimpleClock(); +draw(); diff --git a/apps/sclock/screenshot_simplec.png b/apps/sclock/screenshot_simplec.png new file mode 100644 index 0000000000000000000000000000000000000000..a12db3ec82a8b9443b565eca26eeed33a53b1a79 GIT binary patch literal 2217 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 literal 0 HcmV?d00001 diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 49915ee21..faa50405f 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -32,4 +32,7 @@ 0.27: Add Theme menu 0.28: Update Quiet Mode widget (if present) 0.29: Add Customize to Theme menu -0.30: Move '< Back' to the top of menus \ No newline at end of file +0.30: Move '< Back' to the top of menus +0.31: Remove Bangle 1 settings when running on Bangle 2 +0.32: Fix 'beep' menu on Bangle.js 2 +0.33: Really fix 'beep' menu on Bangle.js 2 this time diff --git a/apps/setting/settings-icon.js b/apps/setting/settings-icon.js index 7b68f80c0..abc7a3060 100644 --- a/apps/setting/settings-icon.js +++ b/apps/setting/settings-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwghC/AFEiAAgX/C/4SFkADBgQXFBIgECAAYSCkAWGBIoXGyQTHABBZLkUhiMRiQXLIQwVBAAZlIC44tCAAYxGIxIWFGA4XIFwwwHXBAWHGAwXHFxAwGPAYXTX44XDiAJBgIXGyDAHFAYKDMAq+EGAgXNCwwX/C453XU6IWHa6ZFCC6JJCC4hgEAAoOEC5AwIFwhgEBAgwIBoqmGGBIuFVAgXFGAwLFYAoLFGIYtFeA4MGABMpC4pICkBMGBIpGFC4SuIBIoWFAAxZLC/4X/AFQ")) +require("heatshrink").decompress(atob("mEw4UA///+Nj5lCt9TH+cBqtVoALWqALTgoLUiALFgoLDqoBBAAQGCHAdRBYdFKwZECqv614ECGQQsCr2q1W1BYkVAoPqBYOrAoNUBYdXBQIAB6oLDEQgkEBYdaBYelBYt6BYetBYYvBtWq0EK1WpF4ZfBIwIFBJATCDBY6PDiuq1AEBlWqBdA7KKZZrLQZabNWZLLLcZb7LBYVV/WvAgRfCNYNRBAVVoq/FJQRECR4gnBEwQEBggLDGQg4CBag4DBaBWBBaoATA")) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index a0e535df7..fcf651b6f 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -1,6 +1,7 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); +const BANGLEJS2 = process.env.HWVERSION==2; const storage = require('Storage'); let settings; @@ -37,7 +38,7 @@ function resetSettings() { quiet: 0, // quiet mode: 0: off, 1: priority only, 2: total silence timeout: 10, // Default LCD timeout in seconds vibrate: true, // Vibration enabled by default. App must support - beep: "vib", // Beep enabled by default. App must support + beep: BANGLEJS2?true:"vib", // Beep enabled by default. App must support timezone: 0, // Set the timezone for the device HID: false, // BLE HID mode, off by default clock: null, // a string for the default clock's name @@ -71,8 +72,37 @@ if (!('qmOptions' in settings)) settings.qmOptions = {}; // easier if this alway const boolFormat = v => v ? "On" : "Off"; function showMainMenu() { - var beepV = [false, true, "vib"]; - var beepN = ["Off", "Piezo", "Vibrate"]; + var beepMenuItem; + if (BANGLEJS2) { + beepMenuItem = { + value: settings.beep!=false, + format: boolFormat, + onchange: v => { + settings.beep = v; + updateSettings(); + if (settings.beep) { + analogWrite(VIBRATE,0.1,{freq:2000}); + setTimeout(()=>VIBRATE.reset(),200); + } // beep with vibration moter + } + }; + } else { // Bangle.js 1 + var beepV = [false, true, "vib"]; + var beepN = ["Off", "Piezo", "Vibrate"]; + beepMenuItem = { + value: Math.max(0 | beepV.indexOf(settings.beep),0), + min: 0, max: beepV.length-1, + format: v => beepN[v], + onchange: v => { + settings.beep = beepV[v]; + if (v==1) { analogWrite(D18,0.5,{freq:2000});setTimeout(()=>D18.reset(),200); } // piezo on Bangle.js 1 + else if (v==2) { analogWrite(VIBRATE,0.1,{freq:2000});setTimeout(()=>VIBRATE.reset(),200); } // vibrate + updateSettings(); + } + }; + } + + const mainmenu = { '': { 'title': 'Settings' }, '< Back': ()=>load(), @@ -87,17 +117,7 @@ function showMainMenu() { updateSettings(); } }, - 'Beep': { - value: 0 | beepV.indexOf(settings.beep), - min: 0, max: 2, - format: v => beepN[v], - onchange: v => { - settings.beep = beepV[v]; - if (v==1) { analogWrite(D18,0.5,{freq:2000});setTimeout(()=>D18.reset(),200); } // piezo - else if (v==2) { analogWrite(D13,0.1,{freq:2000});setTimeout(()=>D13.reset(),200); } // vibrate - updateSettings(); - } - }, + 'Beep': beepMenuItem, 'Vibration': { value: settings.vibrate, format: boolFormat, @@ -119,6 +139,7 @@ function showMainMenu() { 'Reset Settings': ()=>showResetMenu(), 'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }, }; + return E.showMenu(mainmenu); } @@ -144,7 +165,7 @@ function showBLEMenu() { } }, 'HID': { - value: 0 | hidV.indexOf(settings.HID), + value: Math.max(0,0 | hidV.indexOf(settings.HID)), min: 0, max: 3, format: v => hidN[v], onchange: v => { @@ -191,7 +212,7 @@ function showThemeMenu() { 'Light BW': ()=>{ upd({ fg:cl("#000"), bg:cl("#fff"), - fg2:cl("#00f"), bg2:cl("#0ff"), + fg2:cl("#000"), bg2:cl("#cff"), fgH:cl("#000"), bgH:cl("#0ff"), dark:false }); @@ -356,7 +377,10 @@ function showLCDMenu() { settings.options.wakeOnBTN1 = !settings.options.wakeOnBTN1; updateOptions(); } - }, + } + }; + if (!BANGLEJS2) + Object.assign(lcdMenu, { 'Wake on BTN2': { value: settings.options.wakeOnBTN2, format: boolFormat, @@ -372,7 +396,8 @@ function showLCDMenu() { settings.options.wakeOnBTN3 = !settings.options.wakeOnBTN3; updateOptions(); } - }, + }}); + Object.assign(lcdMenu, { 'Wake on FaceUp': { value: settings.options.wakeOnFaceUp, format: boolFormat, @@ -427,7 +452,7 @@ function showLCDMenu() { updateOptions(); } } - } + }); return E.showMenu(lcdMenu) } function showQuietModeMenu() { diff --git a/apps/simplest/ChangeLog b/apps/simplest/ChangeLog index d69da4ddc..f37015d6a 100644 --- a/apps/simplest/ChangeLog +++ b/apps/simplest/ChangeLog @@ -1,2 +1,3 @@ 0.01: Modified for use with new bootloader and firmware 0.02: Use Bangle.setUI for button/launcher handling +0.03: Fix display for Bangle 2 diff --git a/apps/simplest/app.js b/apps/simplest/app.js index 2ed4e5580..68564ff33 100644 --- a/apps/simplest/app.js +++ b/apps/simplest/app.js @@ -1,14 +1,17 @@ +const h = g.getHeight(); +const w = g.getWidth(); + function draw() { var d = new Date(); var da = d.toString().split(" "); var time = da[4].substr(0,5); g.reset(); - g.clearRect(0, 30, 239, 99); + g.clearRect(0, 30, w, 99); g.setFontAlign(0, -1); - g.setFont("Vector", 80); - g.drawString(time, 120, 40); + g.setFont("Vector", w/3); + g.drawString(time, w/2, 40); } // handle switch display on by pressing BTN1 diff --git a/apps/simplest/screenshot_simplest.png b/apps/simplest/screenshot_simplest.png new file mode 100644 index 0000000000000000000000000000000000000000..9affc3d1cbb9be291270cb57cd1474e03b685ea3 GIT binary patch literal 2180 zcmeHJ`!m~#7LSNFq7QG4TD79mCYD=m?;?bf2t`|^K}kwI(xOFMA!V(vUOPogqtPx) zwBp$kY0?!DtKZ#A>(yv(SCxupNUsVNgnHLaXZH8|2i$vR&iR}<^O^a~oSE~PlXvO_ zX(z%Q0f9hv`uY$~YmoOf;oCH|S*W0DK?8cLOw|IFED~c|;Q4-U9~) zer&36Ls`wW^pT=>0|PeRk*>q{zySHX*^Cw=)L&!OKwFX}`itSpWNT98-xPg%uxZ<5 zs4MIF3-B+3s;8nnb6!6g&&Cpa+netS1h0@1+9x^o^0CFRvPq};>_>>_ZU z^1+0+VpY!T_nuaB-G<4tc0hj`xNIi=Nb0$X6B|dI`!*XiiH{KIX!W3AIF)I_YHC0? zbzFaCdNd5rTTD0tqjs>*4Q6j_N5VJGd`Wap>^nK$QqHQy7kg+re6g|DC0~!VyRF}$ zs~d`J@2uK8Z5(?}GCJSdq&tTthCA$jc@WOi#vUksati`=@H!c#qXi?MvWZ4&!Kgp6 z|3E!L|0sk!f72+0wg`jAzle+B=E23oV;>ea@8C&%B^1r0nM8 zj{NDAH%t<8(mwMdaQM{Y0)|fDd%PJ!m6*RfW70f=EWgz*_4EDvM7(22ZUfb9eyW$U z2A9r5)rc5$JIRf7+o#yyL0S+xWI+-2!RS5HBLL3q0QT~c1e>I5oBgs!cZ)U+tc$FNDaHL27xA$IjEyF>+Y?g%2qrelFW z)-KN!oDW9fX!kErM5lo?mQdZY$Crc0_b!XFyvOaOZC7HUMSr{mgLPl&o*d*@qtD*h zp|&SeM6aQwR~T{j`9lKXSbx={^{M`XH>fh9-&5}BK>!ts>t6RQ(hr-b#gvLZH$LNz z^1%@1@?ITwspu=E7(^|23ma#+j#sJZ_R$xT8^kK&-M~u%3G#-?Xy%yPt0R-FC6w=o6mQcEU8hw(~o#iq-) zJkx57KP%jg`l*f|i5M$sOGL3tkeoy^VTuV2W8b>0M{SpgGTak# zL6mh`f#X`vq!PUZKg5%iBebwuu$J-KpUWW7$9cuH{n;RS)Hq1v-A4l#8e#fx^M5%X zuQ4p!#XN#^;Ro4;+U8O|1uO zZL`7nDn*!vVe);~pBkGBV7dNZ?0;vCD`qy|{M+>()y}q>w*ulzJVD@kM&|qnB+=s~ literal 0 HcmV?d00001 diff --git a/apps/slomoclock/ChangeLog b/apps/slomoclock/ChangeLog new file mode 100644 index 000000000..cfab5da55 --- /dev/null +++ b/apps/slomoclock/ChangeLog @@ -0,0 +1,2 @@ +0.01: Created app +0.10: Different colour schemes selectable in SloMo Clock settings. diff --git a/apps/slomoclock/README.md b/apps/slomoclock/README.md new file mode 100644 index 000000000..9a6bbbdd2 --- /dev/null +++ b/apps/slomoclock/README.md @@ -0,0 +1,6 @@ +# SloMo Clock + +Simple 24h clock with large digits. + +![](Screenshot.JPG) + diff --git a/apps/slomoclock/app-icon.js b/apps/slomoclock/app-icon.js new file mode 100644 index 000000000..22e264124 --- /dev/null +++ b/apps/slomoclock/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("oFAwhC/ABOIABgfymYAKD+Z/9hGDL5c4wAf/XzjASTxqgQhAfPMB2IPxiACIBo+BDxqACIBg+CLxpANHwQPBABgvCIBT8CJ5owDD5iPOOAQfLBojiDCYQGFGIQfICIQfdBYJNMOI6SHD8jeNOIYzID8hfRD9LfEAoTdFBIifLAAIffBoQRBAJpxMD84JCD+S/GL56fID8ALBb6ZhID8qtJCZ4fgT4YDBABq/PD7RNEL6IRKD8WID5pfCD5kzNhKSFmYfMBwSeOGBoPDABgvCJ5wAON5pADABivPIAIAOd5xABABweOD4J+OD58IQBj8LD/6gUDyAfhXzgfiP/wA2")) diff --git a/apps/slomoclock/app.js b/apps/slomoclock/app.js new file mode 100644 index 000000000..e3933af1b --- /dev/null +++ b/apps/slomoclock/app.js @@ -0,0 +1,118 @@ +/* +Simple watch [slomoclock] +Mike Bennett mike[at]kereru.com +0.01 : Initial +0.03 : Use Layout library +*/ + +var v='0.10'; + +// Colours +const col = []; +col[2] = 0xF800; +col[3] = 0xFAE0; +col[4] = 0xF7E0; +col[5] = 0x4FE0; +col[6] = 0x019F; +col[7] = 0x681F; +col[8] = 0xFFFF; + +const colH = []; +colH[0]= 0x001F; +colH[1]= 0x023F; +colH[2]= 0x039F; +colH[3]= 0x051F; +colH[4]= 0x067F; +colH[5]= 0x07FD; +colH[6]= 0x07F6; +colH[7]= 0x07EF; +colH[8]= 0x07E8; +colH[9]= 0x07E3; +colH[10]= 0x07E0; +colH[11]= 0x5FE0; +colH[12]= 0x97E0; +colH[13]= 0xCFE0; +colH[14]= 0xFFE0; +colH[15]= 0xFE60; +colH[16]= 0xFC60; +colH[17]= 0xFAA0; +colH[18]= 0xF920; +colH[19]= 0xF803; +colH[20]= 0xF80E; +colH[21]= 0x981F; +colH[22]= 0x681F; +colH[23]= 0x301F; + +// Colour incremented with every 10 sec timer event +var colNum = 0; +var lastMin = -1; + +var Layout = require("Layout"); +var layout = new Layout( { + type:"h", c: [ + {type:"v", c: [ + {type:"txt", font:"40%", label:"", id:"hour", valign:1}, + {type:"txt", font:"40%", label:"", id:"min", valign:-1}, + ]}, + {type:"v", c: [ + {type:"txt", font:"10%", label:"", id:"day", col:0xEFE0, halign:1}, + {type:"txt", font:"10%", label:"", id:"mon", col:0xEFE0, halign:1}, + ]} + ] +}, {lazy:true}); + +// update the screen +function draw() { + var date = new Date(); + + // Update time + var timeStr = require("locale").time(date,1); + var hh = parseFloat(timeStr.substring(0,2)); + var mm = parseFloat(timeStr.substring(3,5)); + + // Surprise colours + if ( lastMin != mm ) colNum = Math.floor(Math.random() * 24); + lastMin = mm; + + layout.hour.label = timeStr.substring(0,2); + layout.min.label = timeStr.substring(3,5); + + // Mysterion (0) different colour each hour. Surprise (1) different colour every 10 secs. + layout.hour.col = cfg.colour==0 ? colH[hh] : cfg.colour==1 ? colH[colNum] : col[cfg.colour]; + layout.min.col = cfg.colour==0 ? colH[hh] : cfg.colour==1 ? colH[colNum] :col[cfg.colour]; + + // Update date + layout.day.label = date.getDate(); + layout.mon.label = require("locale").month(date,1); + + layout.render(); +} + +// Events + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (secondInterval) clearInterval(secondInterval); + secondInterval = undefined; + if (on) { + secondInterval = setInterval(draw, 10000); + draw(); // draw immediately + } +}); + +var secondInterval = setInterval(draw, 10000); + +// Configuration +let cfg = require('Storage').readJSON('slomoclock.json',1)||{}; +cfg.colour = cfg.colour||0; // Colours + +// update time and draw +g.clear(); +draw(); + +// Show launcher when middle button pressed +Bangle.setUI("clock"); + +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/slomoclock/settings.js b/apps/slomoclock/settings.js new file mode 100644 index 000000000..af67069dc --- /dev/null +++ b/apps/slomoclock/settings.js @@ -0,0 +1,38 @@ +(function(back) { + + let settings = require('Storage').readJSON('slomoclock.json',1)||{}; + + function writeSettings() { + require('Storage').write('slomoclock.json',settings); + } + + function setColour(c) { + settings.colour = c; + writeSettings(); + } + + const appMenu = { + '': {'title': 'SloMo Clock'}, + '< Back': back, + 'Colours' : function() { E.showMenu(colMenu); } + //,'Widget Space Top' : {value : settings.widTop, format : v => v?"On":"Off",onchange : () => { settings.widTop = !settings.widTop; writeSettings(); } + //,'Widget Space Bottom' : {value : settings.widBot, format : v => v?"On":"Off",onchange : () => { settings.widBot = !settings.widBot; writeSettings(); } + }; + + const colMenu = { + '': {'title': 'Colours'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Mysterion' : function() { setColour(0); }, + 'Surprise' : function() { setColour(1); }, + 'Red' : function() { setColour(2); }, + 'Orange' : function() { setColour(3); }, + 'Yellow' : function() { setColour(4); }, + 'Green' : function() { setColour(5); }, + 'Blue' : function() { setColour(6); }, + 'Violet' : function() { setColour(7); }, + 'White' : function() { setColour(8); } + }; + + E.showMenu(appMenu); + +}); diff --git a/apps/slomoclock/watch.png b/apps/slomoclock/watch.png new file mode 100644 index 0000000000000000000000000000000000000000..b77f302d5291d39650ffd4c5400d0a7dd3e30d70 GIT binary patch literal 1439 zcmV;Q1z`G#P)ZZKIyp>XUbK#j8aEHE7Ds0zs-Qe=?maLsuD zcwH7zo-85*pPUNHrrgZ`eM~B!Lz#R329Fd4q?A-M035B_i!(14%b?7NZzmsOB(?~JQq2Hvi}HypfQSL?Z2&l2ULZ_o zg+qaL8-!-vwitv1pv#|LDDtV;SL79?vqWW)7sdH`07Fnl8VCo#kpJO8fxNdW6EFY= z%l80)kp{wij?s8|GN7v7I{+#~JK_+3YT9bUtq*JBP6-?m=93VkXj=a=!t=$vcL13+R`L*M8e?$7=E zR0)1v31I3q9xl!|L4C>F+7N)>qhMlw8GU1O(1^eoyEN2R*(Kxs+`J3Kj`vPq)QCE- zj?bf|t_%lC^U>N6z^B7A;9ZtGng#qG{kDPV0<@Hi&I15rG}Knv#TdJ!r3eGjg-w0p zg{1;ccK=qJf^vcwJDGmXkbS!Iq41PxAR--ATaG=p?_-RHh`K^Ou};y{GYvo}92?X? zr!86Bc=B7>3{MHuSSUbuzV5jKR2Td3_5FWrk!E_r+jU<$(v)Pne`qUl&K@eoD;0&p z9m^6-_YbA)2Uz8<+c5Se?d(qF_5@{+$jV(_$WWfG09?B_kLqHdsI46d^8he52%0NtVRREr&>#+Z@)vNV}NW(Tno5*=D%GKUkmWOTLadkx!v}5ucsrMYk}ePKP={9tMkm8@Yd~E zwU<=~WQN=|V{8idvY={d+dx^FjR^heGZAfZwtTQ-FP;P>i> zqQ`ruFS;6p_0yq^eQ*j#0Z1fM0Dz|25~vCT5p}v2gmveMs8dxKnrcfn^@$f;9X8em z0l(+}^TEl5W%T_rhc$9{B2XE_J9VXascesaLVPEVcp?da-HLPskJ;tnQh=tdyaix1 zo=D=epW}G4qM@cFAMe(e2}_X&ey@s_`m(3a-_@Nbuqa47%frPj+0d2Qa;zn@ULXO; zsS= z_SOj-_-5iEZci_Q7%mLASA8tPC0;4Ylzsphk1xTEiI4#NI=5sP?-%704#3T}eLn*1 zkH;$-rXR10(ph5W(JCG-YXBiO+)??HD4+ORfH4B-O8~eRw>~}-#rWU44N`DbMrDee z?**9BuTbjzNNf=!u|*k_c8m(kACUFg91#ok#GJj1J4B>CsO%U8Y%ExE?W-|Kg;}{h tLD__^eKjTtG8N$3{-Nx%fgE--{sVHTSvKO1ec%89002ovPDHLkV1gjPojw2n literal 0 HcmV?d00001 diff --git a/apps/speedalt/settings.js b/apps/speedalt/settings.js index 488ba3b81..63d77971e 100644 --- a/apps/speedalt/settings.js +++ b/apps/speedalt/settings.js @@ -32,7 +32,7 @@ const appMenu = { - '': {'title': 'GPS Speed Alt'}, + '': {'title': 'GPS Adv Sprt'}, '< Back': back, '< Load GPS Adv Sport': ()=>{load('speedalt.app.js');}, 'Units' : function() { E.showMenu(unitsMenu); }, diff --git a/apps/speedalt2/ChangeLog b/apps/speedalt2/ChangeLog new file mode 100644 index 000000000..91f01988e --- /dev/null +++ b/apps/speedalt2/ChangeLog @@ -0,0 +1,2 @@ +0.01: Initial import. +0.07: Add swipe to change screens. diff --git a/apps/speedalt2/README.md b/apps/speedalt2/README.md new file mode 100644 index 000000000..30a706b7b --- /dev/null +++ b/apps/speedalt2/README.md @@ -0,0 +1,134 @@ +# GPS Speed, Altimeter and Distance to Waypoint + +What is the difference between **GPS Adventure Sports** and **GPS Adventure Sports II** ? + +**GPS Adventure Sports** has 3 screens, each of which display different sets of information. + +**GPS Adventure Sports II** has 5 screens, each of which displays just one of Speed, Altitude, Distance to waypoint, Position or Time. + +In all other respect they perform the same functions. + +The waypoints list is the same as that used with the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app so the same set of waypoints can be used across both apps. Refer to that app for waypoint file information. + +## Buttons and Controls + +**BTN1** ( Speed and Altitude ) Short press < 2 secs toggles the display between last reading and maximum recorded. Long press > 2 secs resets the recorded maximum values. + +**BTN1** ( Distance ) Select next waypoint. Last fix distance from selected waypoint is displayed. + +**BTN2** : Disables/Restores power saving timeout. Locks the screen on and GPS in SuperE mode to enable reading for longer periods but uses maximum battery drain. Red LED (dot) at top of screen when screen is locked on. Press again to restore power saving timeouts. + +**BTN3** : Cycles the screens between Speed, Altitude, Distance to waypoint, Position and Time + +**BTN3** : Long press exit and return to watch. + +**Touch Screen** If the 'Touch' setting is ON then : + +Swipe Left/Right cycles between the five screens. + +Touch functions as BTN1 short press. + + +## App Settings + +Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Distance can be km, miles or nautical miles. Altitude can be feet or metres. Select one of three colour schemes. Default (three colours), high contrast (all white on black) or night ( all red on black ). + +## Kalman Filter + +This filter smooths the altitude and the speed values and reduces these values 'jumping around' from one GPS fix to the next. The down side of this is that if these values change rapidly ( eg. a quick change in altitude ) then it can take a few GPS fixes for the values to move to the new values. Disabling the Kalman filter in the settings will cause the raw values to be displayed from each GPS fix as they are found. + +## Loss of fix + +When the GPS obtains a fix the number of satellites is displayed as 'Sats:nn'. When unable to obtain a fix then the last known fix is used and the age of that fix in seconds is displayed as 'Age:nn'. Seeing 'Sats' or 'Age' indicates whether the GPS has a current fix or not. + +## Power Saving + +The The GPS Adv Sport app obeys the watch screen off timeouts as a power saving measure. Restore the screen as per any of the colck/watch apps. Use BTN2 to lock the screen on but doing this will use more battery. + +This app will work quite happily on its own but will use the [GPS Setup App](https://banglejs.com/apps/#gps%20setup) if it is installed. You may choose to use the GPS Setup App to gain significantly longer battery life while the GPS is on. Please read the Low Power GPS Setup App Readme to understand what this does. + +When using the GPS Setup App this app switches the GPS to SuperE (default) mode while the display is lit and showing fix information. This ensures that that fixes are updated every second or so. 10 seconds after the display is blanked by the watch this app will switch the GPS to PSMOO mode and will only attempt to get a fix every two minutes. This improves power saving while the display is off and the delay gives an opportunity to restore the display before the GPS power mode is switched. + +The MAX values continue to be collected with the display off so may appear a little odd after the intermittent fixes of the low power mode. + +## Waypoints + +Waypoints are used in [D]istance mode. Create a file waypoints.json and write to storage on the Bangle.js using the IDE. The first 6 characters of the name are displayed in Speed+[D]istance mode. + +The [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app in the App Loader has a really nice waypoints file editor. (Must be connected to your Bangle.JS and then click on the Download icon.) + +Sample waypoints.json (My sailing waypoints) + +
+[
+  {
+  "name":"NONE"
+  },
+  {
+  "name":"Omori",
+  "lat":-38.9058670,
+  "lon":175.7613350
+  },
+  {
+  "name":"DeltaW",
+  "lat":-38.9438550,
+  "lon":175.7676930
+  },
+  {
+  "name":"DeltaE",
+  "lat":-38.9395240,
+  "lon":175.7814420
+  },
+  {
+  "name":"BtClub",
+  "lat":-38.9446020,
+  "lon":175.8475720
+  },
+  {
+  "name":"Hapua",
+  "lat":-38.8177750,
+  "lon":175.8088720
+  },
+  {
+  "name":"Nook",
+  "lat":-38.7848090,
+  "lon":175.7839440
+  },
+  {
+  "name":"ChryBy",
+  "lat":-38.7975050,
+  "lon":175.7551960
+  },
+  {
+  "name":"Waiha",
+  "lat":-38.7219630,
+  "lon":175.7481520
+  },
+  {
+  "name":"KwaKwa",
+  "lat":-38.6632310,
+  "lon":175.8670320
+  },
+  {
+  "name":"Hatepe",
+  "lat":-38.8547420,
+  "lon":176.0089124
+  },
+  {
+  "name":"Kinloc",
+  "lat":-38.6614442,
+  "lon":175.9161607
+  }
+]
+
+ +## Comments and Feedback + +Developed for my use in sailing, cycling and motorcycling. If you find this software useful or have feedback drop me a line mike[at]kereru.com. Enjoy! + +## Thanks + +Many thanks to Gordon Williams. Awesome job. + +Special thanks also to @jeffmer, for the [GPS Navigation](https://banglejs.com/apps/#gps%20navigation) app and @hughbarney for the Low power GPS code development and Wouter Bulten for the Kalman filter code. + diff --git a/apps/speedalt2/app-icon.js b/apps/speedalt2/app-icon.js new file mode 100644 index 000000000..f4f24a18b --- /dev/null +++ b/apps/speedalt2/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AE+sFtoABF12swItsF9QuFR4IwmFwwvnFw4vCGEYuIF4JgjFxIvkFxQvCGBfOAAQvqFwYwRFxYvDGBIvUFxgv/F6IuNF4n+0nB4TvXFxwvF4XBAALlPF7ZfBGC4uPF4rABGAYAGTQwvad4YwKFzYvIGBQvfFwgAE3Qvt4IvEFzgvCLxO7Lx7vULzIzTFwIvgGZheFRAiNRGSQvpGYouesYAGmQAKq3CE4PIC4wviq2eFwPCroveCRSGEC6Qv0DAwRLcoouWC4VdVYQXkr1eAgVdAoIABroNEB4gHHC5QvHwQSDAAOCA74vH1uICQIABxGtA74vIAEwv/F/4vXAH4A/AHY")) diff --git a/apps/speedalt2/app.js b/apps/speedalt2/app.js new file mode 100644 index 000000000..0db9629c7 --- /dev/null +++ b/apps/speedalt2/app.js @@ -0,0 +1,725 @@ +/* +Speed and Altitude [speedalt2] +Mike Bennett mike[at]kereru.com +0.01 : Initial +0.06 : Add Posn screen +0.07 : Add swipe to change screens same as BTN3 +*/ +var v = '1.05'; + +/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */ +var KalmanFilter = (function () { + 'use strict'; + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + /** + * KalmanFilter + * @class + * @author Wouter Bulten + * @see {@link http://github.com/wouterbulten/kalmanjs} + * @version Version: 1.0.0-beta + * @copyright Copyright 2015-2018 Wouter Bulten + * @license MIT License + * @preserve + */ + var KalmanFilter = + /*#__PURE__*/ + function () { + /** + * Create 1-dimensional kalman filter + * @param {Number} options.R Process noise + * @param {Number} options.Q Measurement noise + * @param {Number} options.A State vector + * @param {Number} options.B Control vector + * @param {Number} options.C Measurement vector + * @return {KalmanFilter} + */ + function KalmanFilter() { + var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, + _ref$R = _ref.R, + R = _ref$R === void 0 ? 1 : _ref$R, + _ref$Q = _ref.Q, + Q = _ref$Q === void 0 ? 1 : _ref$Q, + _ref$A = _ref.A, + A = _ref$A === void 0 ? 1 : _ref$A, + _ref$B = _ref.B, + B = _ref$B === void 0 ? 0 : _ref$B, + _ref$C = _ref.C, + C = _ref$C === void 0 ? 1 : _ref$C; + + _classCallCheck(this, KalmanFilter); + + this.R = R; // noise power desirable + + this.Q = Q; // noise power estimated + + this.A = A; + this.C = C; + this.B = B; + this.cov = NaN; + this.x = NaN; // estimated signal without noise + } + /** + * Filter a new value + * @param {Number} z Measurement + * @param {Number} u Control + * @return {Number} + */ + + + _createClass(KalmanFilter, [{ + key: "filter", + value: function filter(z) { + var u = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + + if (isNaN(this.x)) { + this.x = 1 / this.C * z; + this.cov = 1 / this.C * this.Q * (1 / this.C); + } else { + // Compute prediction + var predX = this.predict(u); + var predCov = this.uncertainty(); // Kalman gain + + var K = predCov * this.C * (1 / (this.C * predCov * this.C + this.Q)); // Correction + + this.x = predX + K * (z - this.C * predX); + this.cov = predCov - K * this.C * predCov; + } + + return this.x; + } + /** + * Predict next value + * @param {Number} [u] Control + * @return {Number} + */ + + }, { + key: "predict", + value: function predict() { + var u = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; + return this.A * this.x + this.B * u; + } + /** + * Return uncertainty of filter + * @return {Number} + */ + + }, { + key: "uncertainty", + value: function uncertainty() { + return this.A * this.cov * this.A + this.R; + } + /** + * Return the last filtered measurement + * @return {Number} + */ + + }, { + key: "lastMeasurement", + value: function lastMeasurement() { + return this.x; + } + /** + * Set measurement noise Q + * @param {Number} noise + */ + + }, { + key: "setMeasurementNoise", + value: function setMeasurementNoise(noise) { + this.Q = noise; + } + /** + * Set the process noise R + * @param {Number} noise + */ + + }, { + key: "setProcessNoise", + value: function setProcessNoise(noise) { + this.R = noise; + } + }]); + + return KalmanFilter; + }(); + + return KalmanFilter; + +}()); + + +var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); + +// Load fonts +//require("Font7x11Numeric7Seg").add(Graphics); + +var lf = {fix:0,satellites:0}; +var showMax = 0; // 1 = display the max values. 0 = display the cur fix +var pwrSav = 1; // 1 = default power saving with watch screen off and GPS to PMOO mode. 0 = screen kept on. +var canDraw = 1; +var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time. +var tmrLP; // Timer for delay in switching to low power after screen turns off + +var max = {}; +max.spd = 0; +max.alt = 0; +max.n = 0; // counter. Only start comparing for max after a certain number of fixes to allow kalman filter to have smoohed the data. + +var emulator = (process.env.BOARD=="EMSCRIPTEN")?1:0; // 1 = running in emulator. Supplies test values; + +var wp = {}; // Waypoint to use for distance from cur position. + +function nxtWp(inc){ + cfg.wp+=inc; + loadWp(); +} + +function loadWp() { + var w = require("Storage").readJSON('waypoints.json')||[{name:"NONE"}]; + if (cfg.wp>=w.length) cfg.wp=0; + if (cfg.wp<0) cfg.wp = w.length-1; + savSettings(); + wp = w[cfg.wp]; +} + +function radians(a) { + return a*Math.PI/180; +} + +function distance(a,b){ + var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2)); + var y = radians(b.lat-a.lat); + + // Distance in selected units + var d = Math.sqrt(x*x + y*y) * 6371000; + d = (d/parseFloat(cfg.dist)).toFixed(2); + if ( d >= 100 ) d = parseFloat(d).toFixed(1); + if ( d >= 1000 ) d = parseFloat(d).toFixed(0); + + return d; +} + +function drawScrn(dat) { + + if (!canDraw) return; + + buf.clear(); + + var n; + n = dat.val.toString(); + + var s=50; // Font size + var l=n.length; + + if ( l <= 7 ) s=55; + if ( l <= 6 ) s=60; + if ( l <= 5 ) s=80; + if ( l <= 4 ) s=100; + if ( l <= 3 ) s=120; + + buf.setFontAlign(0,0); //Centre + buf.setColor(1); + buf.setFontVector(s); + buf.drawString(n,126,52); + + + // Primary Units + buf.setFontAlign(-1,1); //left, bottom + buf.setColor(2); + buf.setFontVector(35); + buf.drawString(dat.unit,5,164); + + if ( dat.max ) drawMax(); // MAX display indicator + if ( dat.wp ) drawWP(); // Waypoint name + + //Sats + if ( dat.sat ) { + if ( dat.age > 10 ) { + if ( dat.age > 90 ) dat.age = '>90'; + drawSats('Age:'+dat.age); + } + else drawSats('Sats:'+dat.sats); + } + + g.reset(); + g.drawImage(img,0,40); + + if ( pwrSav ) LED1.reset(); + else LED1.set(); + +} + +function drawPosn(dat) { + if (!canDraw) return; + buf.clear(); + + var x, y; + x=210; + y=0; + buf.setFontAlign(1,-1); + buf.setFontVector(60); + buf.setColor(1); + + buf.drawString(dat.lat,x,y); + buf.drawString(dat.lon,x,y+70); + + x = 240; + buf.setColor(2); + buf.setFontVector(40); + buf.drawString(dat.ns,x,y); + buf.drawString(dat.ew,x,y+70); + + + //Sats + if ( dat.sat ) { + if ( dat.age > 10 ) { + if ( dat.age > 90 ) dat.age = '>90'; + drawSats('Age:'+dat.age); + } + else drawSats('Sats:'+dat.sats); + } + + g.reset(); + g.drawImage(img,0,40); + + if ( pwrSav ) LED1.reset(); + else LED1.set(); + +} + +function drawClock() { + if (!canDraw) return; + + buf.clear(); + var x, y; + x=185; + y=0; + buf.setFontAlign(1,-1); + buf.setFontVector(94); + time = require("locale").time(new Date(),1); + + buf.setColor(1); + + buf.drawString(time.substring(0,2),x,y); + buf.drawString(time.substring(3,5),x,y+80); + + g.reset(); + g.drawImage(img,0,40); + + if ( pwrSav ) LED1.reset(); + else LED1.set(); +} + +function drawWP() { + var nm = wp.name; + if ( nm == undefined || nm == 'NONE' || cfg.modeA ==1 ) nm = ''; + buf.setColor(2); + + buf.setFontAlign(0,1); //left, bottom + buf.setFontVector(48); + buf.drawString(nm.substring(0,8),120,140); + +} + +function drawSats(sats) { + buf.setColor(3); + buf.setFont("6x8", 2); + buf.setFontAlign(1,1); //right, bottom + buf.drawString(sats,240,160); +} + +function drawMax() { + buf.setFontVector(30); + buf.setColor(2); + buf.setFontAlign(0,1); //centre, bottom + buf.drawString('MAX',120,164); +} + +function onGPS(fix) { + + if ( emulator ) { + fix.fix = 1; + fix.speed = 10 + (Math.random()*5); + fix.alt = 354 + (Math.random()*50); + fix.lat = -38.92; + fix.lon = 175.7613350; + fix.course = 245; + fix.satellites = 12; + fix.time = new Date(); + fix.smoothed = 0; + } + + var m; + + var sp = '---'; + var al = '---'; + var di = '---'; + var age = '---'; + var lat = '---.--'; + var ns = ''; + var ew = ''; + var lon = '---.--'; + + + if (fix.fix) lf = fix; + + if (lf.fix) { + + // Smooth data + if ( lf.smoothed !== 1 ) { + if ( cfg.spdFilt ) lf.speed = spdFilter.filter(lf.speed); + if ( cfg.altFilt ) lf.alt = altFilter.filter(lf.alt); + lf.smoothed = 1; + if ( max.n <= 15 ) max.n++; + } + + + // Speed + if ( cfg.spd == 0 ) { + m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units + sp = parseFloat(m[1]); + cfg.spd_unit = m[2]; + } + else sp = parseFloat(lf.speed)/parseFloat(cfg.spd); // Calculate for selected units + + if ( sp < 10 ) sp = sp.toFixed(1); + else sp = Math.round(sp); + + if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = sp; + + // Altitude + al = lf.alt; + al = Math.round(parseFloat(al)/parseFloat(cfg.alt)); + + if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = al; + + // Distance to waypoint + di = distance(lf,wp); + if (isNaN(di)) di = 0; + + // Age of last fix (secs) + age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000)); + + // Lat / Lon + ns = 'N'; + if ( lf.lat < 0 ) ns = 'S'; + lat = Math.abs(lf.lat.toFixed(2)); + + ew = 'E'; + if ( lf.lon < 0 ) ew = 'W'; + lon = Math.abs(lf.lon.toFixed(2)); + + } + + if ( cfg.modeA == 0 ) { + // Speed + if ( showMax ) + drawScrn({ + val:max.spd, + unit:cfg.spd_unit, + sats:lf.satellites, + age:age, + max:true, + wp:false, + sat:true + }); // Speed maximums + else + drawScrn({ + val:sp, + unit:cfg.spd_unit, + sats:lf.satellites, + age:age, + max:false, + wp:false, + sat:true + }); + } + + if ( cfg.modeA == 1 ) { + // Alt + if ( showMax ) + drawScrn({ + val:max.alt, + unit:cfg.alt_unit, + sats:lf.satellites, + age:age, + max:true, + wp:false, + sat:true + }); // Alt maximums + else + drawScrn({ + val:al, + unit:cfg.alt_unit, + sats:lf.satellites, + age:age, + max:false, + wp:false, + sat:true + }); + } + + if ( cfg.modeA == 2 ) { + // Dist + drawScrn({ + val:di, + unit:cfg.dist_unit, + sats:lf.satellites, + age:age, + max:false, + wp:true, + sat:true + }); + } + + if ( cfg.modeA == 3 ) { + // Position + drawPosn({ + sats:lf.satellites, + age:age, + lat:lat, + lon:lon, + ns:ns, + ew:ew, + sat:true + }); + } + + if ( cfg.modeA == 4 ) { + // Large clock + drawClock(); + } + +} + +function prevScrn() { + cfg.modeA = cfg.modeA-1; + if ( cfg.modeA < 0 ) cfg.modeA = 4; + savSettings(); + onGPS(lf); +} + +function nextScrn() { + cfg.modeA = cfg.modeA+1; + if ( cfg.modeA > 4 ) cfg.modeA = 0; + savSettings(); + onGPS(lf); +} + +// Next function on a screen +function nextFunc(dur) { + if ( cfg.modeA == 0 || cfg.modeA == 1 ) { + // Spd+Alt mode - Switch between fix and MAX + if ( dur < 2 ) showMax = !showMax; // Short press toggle fix/max display + else { max.spd = 0; max.alt = 0; } // Long press resets max values. + } + else if ( cfg.modeA == 2) nxtWp(1); // Dist mode - Select next waypoint + onGPS(lf); +} + + +function updateClock() { + if (!canDraw) return; + if ( cfg.modeA != 4 ) return; + drawClock(); + if ( emulator ) {max.spd++;max.alt++;} +} + +function startDraw(){ + canDraw=true; + g.clear(); + Bangle.drawWidgets(); + setLpMode('SuperE'); // off + onGPS(lf); // draw app screen +} + +function stopDraw() { + canDraw=false; + if (!tmrLP) tmrLP=setInterval(function () {if (lf.fix) setLpMode('PSMOO');}, 10000); //Drop to low power in 10 secs. Keep lp mode off until we have a first fix. +} + +function savSettings() { + require("Storage").write('speedalt2.json',cfg); +} + +function setLpMode(m) { + if (tmrLP) {clearInterval(tmrLP);tmrLP = false;} // Stop any scheduled drop to low power + if ( !gpssetup ) return; + gpssetup.setPowerMode({power_mode:m}); +} + +// == Events + +function setButtons(){ + + // BTN1 - Max speed/alt or next waypoint + setWatch(function(e) { + var dur = e.time - e.lastTime; + nextFunc(dur); + }, BTN1, { edge:"falling",repeat:true}); + + // Power saving on/off + setWatch(function(e){ + pwrSav=!pwrSav; + if ( pwrSav ) { + LED1.reset(); + var s = require('Storage').readJSON('setting.json',1)||{}; + var t = s.timeout||10; + Bangle.setLCDTimeout(t); + } + else { + Bangle.setLCDTimeout(0); +// Bangle.setLCDPower(1); + LED1.set(); + } + }, BTN2, {repeat:true,edge:"falling"}); + + // BTN3 - next screen + setWatch(function(e){ + nextScrn(); + }, BTN3, {repeat:true,edge:"falling"}); + +/* + // Touch screen same as BTN1 short + setWatch(function(e){ + nextFunc(1); // Same as BTN1 short + }, BTN4, {repeat:true,edge:"falling"}); + setWatch(function(e){ + nextFunc(1); // Same as BTN1 short + }, BTN5, {repeat:true,edge:"falling"}); +*/ + +} + +Bangle.on('lcdPower',function(on) { + if (!SCREENACCESS.withApp) return; + if (on) startDraw(); + else stopDraw(); +}); + +Bangle.on('swipe',function(dir) { + if ( ! cfg.touch ) return; + if(dir == 1) prevScrn(); + else nextScrn(); +}); + +Bangle.on('touch', function(button){ + if ( ! cfg.touch ) return; + nextFunc(0); // Same function as short BTN1 +/* + switch(button){ + case 1: // BTN4 +console.log('BTN4'); + prevScrn(); + break; + case 2: // BTN5 +console.log('BTN5'); + nextScrn(); + break; + case 3: +console.log('MDL'); + nextFunc(0); // Centre - same function as short BTN1 + break; + } +*/ + }); + + + +// == Main Prog + +// Read settings. +let cfg = require('Storage').readJSON('speedalt2.json',1)||{}; + +cfg.spd = cfg.spd||0; // Multiplier for speed unit conversions. 0 = use the locale values for speed +cfg.spd_unit = cfg.spd_unit||''; // Displayed speed unit +cfg.alt = cfg.alt||0.3048;// Multiplier for altitude unit conversions. +cfg.alt_unit = cfg.alt_unit||'feet'; // Displayed altitude units +cfg.dist = cfg.dist||1000;// Multiplier for distnce unit conversions. +cfg.dist_unit = cfg.dist_unit||'km'; // Displayed altitude units +cfg.colour = cfg.colour||0; // Colour scheme. +cfg.wp = cfg.wp||0; // Last selected waypoint for dist +cfg.modeA = cfg.modeA||0; // 0=Speed 1=Alt 2=Dist 3=Position 4=Clock +cfg.primSpd = cfg.primSpd||0; // 1 = Spd in primary, 0 = Spd in secondary + +cfg.spdFilt = cfg.spdFilt==undefined?true:cfg.spdFilt; +cfg.altFilt = cfg.altFilt==undefined?true:cfg.altFilt; +cfg.touch = cfg.touch==undefined?true:cfg.touch; + +if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 }); +if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 }); + +loadWp(); + +/* +Colour Pallet Idx +0 : Background (black) +1 : Speed/Alt +2 : Units +3 : Sats +*/ +var img = { + width:buf.getWidth(), + height:buf.getHeight(), + bpp:2, + buffer:buf.buffer, + palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB]) +}; + +if ( cfg.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFF6,0xDFFF]); +if ( cfg.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xFAE0,0xF813]); + +var SCREENACCESS = { + withApp:true, + request:function(){this.withApp=false;stopDraw();}, + release:function(){this.withApp=true;startDraw();} +}; + +var gpssetup; +try { + gpssetup = require("gpssetup"); +} catch(e) { + gpssetup = false; +} + +// All set up. Lets go. +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +onGPS(lf); +Bangle.setGPSPower(1); + +if ( gpssetup ) { + gpssetup.setPowerMode({power_mode:"SuperE"}).then(function() { Bangle.setGPSPower(1); }); +} +else { + Bangle.setGPSPower(1); +} + +Bangle.on('GPS', onGPS); + +setButtons(); +setInterval(updateClock, 10000); diff --git a/apps/speedalt2/app.png b/apps/speedalt2/app.png new file mode 100644 index 0000000000000000000000000000000000000000..93d8e57dcbfaf905321fffcf06df042aa49d7dc8 GIT binary patch literal 1639 zcmV-t2AKJYP)@pC8}P=li+m^Env72)@5?yIePa+S>ZbmYsjOa=2m` z!{OB3)^@Gi>3mz!4w$0Hsk)+sbUjipG!&a5|mi z0)`wD_))?j-{$=p(Hmdb{pQ4>#*6Vi6tc_Zs;~=3feFd%R;{|DPtl#K#zRAi7Y`X1 z;B-32+0egXk^BI1gjay&Ter42GL0Dz85MB$^yxyo$U$Iys+>m`&vy%a=qU``n@P+> zNP8DjRdIK?-TImuq^&ql&*I#FWmi3gE><5sB1FaOuOpr#vV?n<_ z(dFmE1qqjs%jGHp7J&{q>2Qby*D7qRpED;gC=2aCEBXiDExut%@3&{)$jNhX)r2YG z^dnNg&zDjGMkx7$Y$pHGo;N24gYr|qS+FpfUBG`~4S=I0`nS$#h_+3*gamysqb!pk zXv%7YEpd4d5ubDqMVujc>)h5J>qvFn-dc4qzLwO;v4OF4uHK zDiy5+ZXrpN_sZ@=#Xj{d&k>z5AX5;YnD;7bECtO;8eHUgNwAw_bgd#HLExkQ@!Xj+ zXB>X7_a+mwQjkhu24U}4pZZo9%HcI2rhoVL=_y*H6IEB8WWWn~`V z-Tpy)d;4g+$ZY~Y$B;^am0~yN4W?9AbqDCW6kH$2X1iauedJGMVA;$qJ+`S27!^;a zLFHfD)9b&=?EzjY{WpaYdh*?c4qo{24Fn`I6~AH7lb74K>t{82$^4}<13f@#JcasV z&KF6MZz%O8J%h>nRCsynRJse|UsOmac2ZauAatVf3o!C+e}8AsYqKqV^h0Z_b|Kt1 z@O_l9{PWVHU!u&4Ymd6Dd|G#zCMOdsSK06%Q^1oACnNf8o85N^@B&=+bs3j^T{fWdc+A|x(cEZ$IG!5wbnEk_ zzpY`zq1m!&pEnc#(LQ5#p(i)~!vyvkhHU0Pfl~n8(?8u^c&_xWf^(r)p||8Ly(MS& zk3BghbW*$AKFu`sF)m((MDVTj>G)q)J0s$u#}x4D?$7k|?)Yu_xT{0E#ii6gwD`J+ zoMU#ODHE@txF{F*m*+X}3AOBn4m;z^3mIFQ4{*u#;fR@m_fLG4-4jffF?;5ih@6Mz lPrm;rMhY0g2u5&e@jt$gS?$`E2({load('speedalt2.app.js');}, + 'Units' : function() { E.showMenu(unitsMenu); }, + 'Colours' : function() { E.showMenu(colMenu); }, + 'Kalman Filter' : function() { E.showMenu(kalMenu); }, + 'Touch' : { + value : settings.touch, + format : v => v?"On":"Off", + onchange : () => { settings.touch = !settings.touch; writeSettings(); } + } + }; + + const unitsMenu = { + '': {'title': 'Units'}, + '< Back': function() { E.showMenu(appMenu); }, + 'default (spd)' : function() { setUnits(0,''); }, + 'Kph (spd)' : function() { setUnits(1,'kph'); }, + 'Knots (spd)' : function() { setUnits(1.852,'kts'); }, + 'Mph (spd)' : function() { setUnits(1.60934,'mph'); }, + 'm/s (spd)' : function() { setUnits(3.6,'m/s'); }, + 'Km (dist)' : function() { setUnitsDist(1000,'km'); }, + 'Miles (dist)' : function() { setUnitsDist(1609.344,'mi'); }, + 'Nm (dist)' : function() { setUnitsDist(1852.001,'nm'); }, + 'Meters (alt)' : function() { setUnitsAlt(1,'m'); }, + 'Feet (alt)' : function() { setUnitsAlt(0.3048,'ft'); } + }; + + const colMenu = { + '': {'title': 'Colours'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Default' : function() { setColour(0); }, + 'Hi Contrast' : function() { setColour(1); }, + 'Night' : function() { setColour(2); } + }; + + const kalMenu = { + '': {'title': 'Kalman Filter'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Speed' : { + value : settings.spdFilt, + format : v => v?"On":"Off", + onchange : () => { settings.spdFilt = !settings.spdFilt; writeSettings(); } + }, + 'Altitude' : { + value : settings.altFilt, + format : v => v?"On":"Off", + onchange : () => { settings.altFilt = !settings.altFilt; writeSettings(); } + } + }; + + + E.showMenu(appMenu); + +}); diff --git a/apps/stopwatch/A.jpg b/apps/stopwatch/A.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9155b9986e05f68312ad383e52490d45f8584542 GIT binary patch literal 77328 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

5%2T4Dz|IU;f>liup3Kanl0p2s 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 Z{{Z>S*8c$2^MCJuM&kbfW^T}<|Jf0-Ls0+# literal 0 HcmV?d00001 diff --git a/apps/stopwatch/B.jpg b/apps/stopwatch/B.jpg new file mode 100644 index 0000000000000000000000000000000000000000..639ff5d42b66c0d14fe451e0834cf174fb626050 GIT binary patch literal 69988 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 literal 0 HcmV?d00001 diff --git a/apps/stopwatch/ChangeLog b/apps/stopwatch/ChangeLog new file mode 100644 index 000000000..9db0e26c5 --- /dev/null +++ b/apps/stopwatch/ChangeLog @@ -0,0 +1 @@ +0.01: first release diff --git a/apps/stopwatch/README.md b/apps/stopwatch/README.md new file mode 100644 index 000000000..30a9306d1 --- /dev/null +++ b/apps/stopwatch/README.md @@ -0,0 +1,33 @@ +# Stopwatch Touch + +A touch screen based stop watch for Bangle 2 + +## Screenshots + +![](screenshot1.png) +![](screenshot2.png) +![](screenshot3.png) + +## Features + +* Attractive UI design +* Will run up to 99 hours +* Shows 10th of seconds up to 1 hour +* Start / Pause button +* Reset button + +## Future features + +I'm keen to complete this project with + +* Ability to dismiss the app and leave it running in the background +* A small widget to show the elapsed time on the current active clock +* Laptimes, with a way to view all the laptimes on a scrollable screen + + +## One of these is a genuine Bangle Js 2 Open Source Smartwatch, the other isn't + +Which one is which ? + +![](A.jpg) +![](B.jpg) diff --git a/apps/stopwatch/pause-24.png b/apps/stopwatch/pause-24.png new file mode 100644 index 0000000000000000000000000000000000000000..eb3d8feaa578ddf2868ca2ebfe6c1016b89076b6 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt^(o-U3d z9-YYv609r?%6x@>Y;0_-CL6c&@bIi(kYfnsC7S$X2lDQBZoM(BvGJk3)I0Y{?~g0x zcb`>IOUhLd?};+VI=F&$u~<)(1Fu1X=sArBW(KQ1!Ho<&4aPtl7(8A5T-G@yGywo& C|1Q-4 literal 0 HcmV?d00001 diff --git a/apps/stopwatch/pause-24a.png b/apps/stopwatch/pause-24a.png new file mode 100644 index 0000000000000000000000000000000000000000..7838ef640b48c569532e978aa4ea89960d1b0533 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt@zo-U3d z9-YYv60C>i3?5DV?eCzgSjh$gYC*9sS`u3mt~3R5yXi{si1joHGB8XvWs;ff*UPx#<4Ht8R7gv;*0D;&KoADtFVAWvTu`u3ff&Q zddffoheq6(ovFkBk;%?%8p+ip=j#J@@r2_VcpvbIcRXMd*JGgHfc4CCOxm~l7>bnu zk@<#w%y2gZDFwRBN1S4cml9|w5Sc5yVh=}uA^!o^Gw-p1+qR8Ays1~!ihxHwMs{eCbxGP!q{{F+Bi9GAC zwDJ$YVMPp+Sz`h7R3{$)<=R|kq2{ybIIAV z#{M|Yd9|K>_im)WlW*Misd4vReJkpk0+Pgj%D+cI=n`X&`I%TKcU>|F9=!+TaBSLuVICewX|Pq#Db6J6?Nl`gXZd8>VX z&$;&p`uYAozpc#w?|2d0g;W0oC+s$Ub8r7)NoC1J*?zUHKWbihF-q<%doRzp-TLsm zzt(dMCn;`v7S7VgvNLw_PkmSI^A+3bL*}H*?=d#A+07F6t?rv>&eNuwwa2c0wu!Fa zb>J$~ruWr>3ajP&L~ouy@nN&O>jKU6o=;U<`wp0wEHdA~|8is1#O@ar(;tgQvz)u5 zG?RJ#ouG+-!&%CX9WQdVkk6Kk-x&J9<7B$bkEtiLf2RY*^_jHiJg$;k|3|d3KRKe& zp^u@l_>PaMqQe6<*N%zG4i99^gMB#!c6bUVJ>d|rIDR%nn29AXs71n(iRE0O@zoZF z#@Ql@Zll@=1<421569i}Ziz673oCp$%34v=%g~tp$0y#W;efdKgO8?6EbBfR*BUZ1 z#m6)pzYYxXW9PpdW#OnPWR!~sMuU|7WlW7%neN@c|3_mVm!ZA?1DoCJPx?33|NAj_ zlGfR$l}!4|yQO!C&NvY|>-1-qI5#l?Aam8jN~ZQe+sGd;*!IQ0y1K~g=S!Q$)fvi6 z^>)oiLND<@=ZrZjC3+*r*%O0mWPOmSjtv28A{c-T>4_tvq zBSmlgE?mjTH2uTfzY5B`r(f^X*=f$S{9-MCSboB%X;KOWobeCY)bghOnP15iZn*B= zfg@{${@(}MX0ez_c)QoF??4N9YUF3^iM~+XYQg+^we*Y~=>l~-+rvAwlJ@&Wsz%IG zFW{UXpRxYY>T|+7d@j^$u2NcaMtFzQ`8Q92;Zn)U6aow-YViKBKlrQl{6c1w2Cyb$ N@O1TaS?83{1OV%-^rrv- literal 0 HcmV?d00001 diff --git a/apps/stopwatch/screenshot2.png b/apps/stopwatch/screenshot2.png new file mode 100644 index 0000000000000000000000000000000000000000..0baa733313973f63c117f606e7eaedf43cd46a21 GIT binary patch literal 1765 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 V;!)cRW&mwu@O1TaS?83{1OQ$|I{5$q literal 0 HcmV?d00001 diff --git a/apps/stopwatch/stop-24a.png b/apps/stopwatch/stop-24a.png new file mode 100644 index 0000000000000000000000000000000000000000..e89ddae054cbda36b088abab6577f064049dbdf9 GIT binary patch literal 192 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt_oo-U3d z9-VKedh<0X@VLyE^E>)`zQZ1Ny$-idk)JA?UU=`)n(L`3>2NZIRp$ZMX(l(L6@vEJ zySe!q8&BV{IJVT%o@zpKGUG`7qs_<9thpacs;)sXzG!+_r}04vQBZNXTE1 nxpdjV=apyUC!A4#dXRD61xvr9nF3)z2QYZL`njxgN@xNA(C= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) { + log_debug(this.name + ":callback\n"); + this.callback(); + return true; + } + return false; +}; + +BUTTON.prototype.draw = function() { + g.setColor(this.color); + g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h); + g.setColor("#000"); // the icons and boxes are drawn black + if (this.img != undefined) { + let iw = iconScale * 24; // the images were loaded as 24 pixels, we will scale + let ix = this.x + ((this.w - iw) /2); + let iy = this.y + ((this.h - iw) /2); + log_debug("g.drawImage(" + ix + "," + iy + "{scale: " + iconScale + "})"); + g.drawImage(this.img, ix, iy, {scale: iconScale}); + } + g.drawRect(this.x, this.y, this.x + this.w, this.y + this.h); +}; + + +var bigPlayPauseBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", stopStart, play_img); +var smallPlayPauseBtn = new BUTTON("small",w/2, 3*h/4 ,w/2, h/4, "#0ff", stopStart, play_img); +var resetBtn = new BUTTON("rst",0, 3*h/4, w/2, h/4, "#ff0", lapReset, pause_img); + +bigPlayPauseBtn.setImage(play_img); +smallPlayPauseBtn.setImage(play_img); +resetBtn.setImage(pause_img); + + +Bangle.on('touch', function(button, xy) { + // not running, and reset + if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(xy.x, xy.y)) return; + + // paused and hit play + if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(xy.x, xy.y)) return; + + // paused and press reset + if (!running && tCurrent != tTotal && resetBtn.check(xy.x, xy.y)) return; + + // must be running + if (running && bigPlayPauseBtn.check(xy.x, xy.y)) return; +}); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +// Clear the screen once, at startup +g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); +// above not working, hence using next 2 lines +g.setColor("#000"); +g.fillRect(0,0,w,h); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); +Bangle.setUI("clock"); // Show launcher when button pressed diff --git a/apps/stopwatch/stopwatch.icon.js b/apps/stopwatch/stopwatch.icon.js new file mode 100644 index 000000000..32281b7ab --- /dev/null +++ b/apps/stopwatch/stopwatch.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA=")) diff --git a/apps/stopwatch/stopwatch.png b/apps/stopwatch/stopwatch.png new file mode 100644 index 0000000000000000000000000000000000000000..92ffe73b7f172f7019486231d5bc0ba327c13a67 GIT binary patch literal 1566 zcmV+(2I2XMP)rZGih zXw&#$)jrm=iAGIK8xtz&C7L#UsP>_)mll=wMNO+ffoiD_T1#yUim(D(ys%vMfQPei zrVqOZR|L*1ZtO$(Kh4bfZvQzm-^@2KjcNS9p~TbJ{=+BFgR;y*?EJRZOY-C8(-tp7 zVORza@KQWI#+kt5$25We8u2D@-cmyFb37f!H4B+o`PybN-uT(0hvM!pG2RAaMai~Z zb8H5+fL|F7l^*}>gRWTg<2NIR8$39)H7FE$0)fri3@0JG9e^ZV$1ylSfBMXW2&%E$ zPO&yWxOMB+q<~{)6;Ked;unf2IeFmyUmqWV%KAJEV+T+Q%#hR?1dbK`*5J(O?<*bi zO;Z%!IDVk)sEnbh6R5b5f)d(`!dpynDcXcVZikO&eb-4ajOeKPP8=vpld&G6#BUZr!wEAw+((0R3 zrXbzLH_tucuqlj0hL$qu+9ey79D&OEyhyeIpghm3SOpJZ0ylh<6M!&@vIX9#5%D$^ zHQ6$u$q@+W`9uhB*iAW^QhKevc3RtRO1aoFFEin3XYxR#>%x|>M@G*&ki!ig!iN+9 z<#}G0?8a$r^U>1QCMBx?`|`Zb`y*l_6*ZQ>*_wbuTSMf8&2iX+vZ=DKzn?&FFTtQn zcOW38Q<&~zjy+M0be$yF)>!FS6Hq8Jo3G6BI3^Q~Myv1s(rIb4-UlX1veWFN9dY0H zM6f0xl*MMVIK!PJ8_3nan@8J?SO1b#qd3!TkLoU-P%r|SKQP~H&P}mTCOL|}{(ipr z>d0vS%cB+Cx!WzJJ2&|OAM+=YGa>N@@R-ePyWG|3Y)vhL_wLbMu#!7PtICQ=hz{kT!_!z`=nUlJl03sr&j0}&?P-3E-D?`>v1Tssg-)!E}hoy|JDk@}lRu&l- zYRIfV%j~QyRj3EpMT1a)jt8Ss%SYLw_Cy$ zv}^iU-;=_cfKaZ0SOQSht?Gs8E~%aDal2W+_GKB-2XL;tKLmucox&9>d6`XIZ8j@! z_e$Lm9|hmt>bvj55SBaNntrAr?+kNN?GIAf(_O<-+) z5IAObcQpIe7!!pQ3<2AlI-_PsIm(6=ma`>W50nETfTn>V*P-_A5exa4aj@g=$hAS; zoP8Zd*(akXM__&7H4yvF?#}Ce>f^YS>UvamU;ih1=wsFuSY{xIr6LQ8JwOA1AxJ1~ z^l9-RQP-pLYNJmx>tf&nGX~z3)sL;HKUp^iAua-QAzWIPE%35s-uOlRWH1CA?7VC2 zZtlWBm=oEKVJu<83hC#?DLkvxEdp_h2nm2Zr(G@2&2bfG$kht9Ju2IqI@PE(573Um z8a5O*#uCodUgj!lva#8|1C~Y)XWJ9ib=hh5;!L+aCpigk)WcS%7NR;3)K7Nx>QSlE zjN%tYjFMPp{?P~koIaf#emxNH08UH!@u4HcM>q%HlYK@Ri${Olt*FTk7vN$e@QR9l#uqzNePq-K{A831lR^T#FgJGPOIi#jBjcHipUslrouSl-W Q%m4rY07*qoM6N<$g48qYF8}}l literal 0 HcmV?d00001 diff --git a/apps/svclock/ChangeLog b/apps/svclock/ChangeLog index 671de492c..fb71fbeb8 100644 --- a/apps/svclock/ChangeLog +++ b/apps/svclock/ChangeLog @@ -1,2 +1,4 @@ 0.01: Modification of SimpleClock 0.04 to use Vectorfont 0.02: Use Bangle.setUI for button/launcher handling +0.03: Scale to BangleJS 2 and add locale +0.04: Fix rendering issue on real hardware, now update *on* the minute rather than every 15 secs diff --git a/apps/svclock/vclock-simple.js b/apps/svclock/vclock-simple.js index f3ab911bc..e08c6fa2c 100644 --- a/apps/svclock/vclock-simple.js +++ b/apps/svclock/vclock-simple.js @@ -1,84 +1,109 @@ -/* jshint esversion: 6 */ -const timeFontSize = 65; -const dateFontSize = 20; -const gmtFontSize = 10; -const font = "Vector"; +const locale = require("locale"); -const xyCenter = g.getWidth() / 2; -const yposTime = 75; -const yposDate = 130; -const yposYear = 175; -const yposGMT = 220; +var timeFontSize; +var dateFontSize; +var gmtFontSize; +var font = "Vector"; +var xyCenter = g.getWidth() / 2; +var yposTime; +var yposDate; +var yposYear; +var yposGMT; + +if (g.getWidth() > 200) { + timeFontSize = 65; + dateFontSize = 20; + gmtFontSize = 10; + + yposTime = 75; + yposDate = 130; + yposYear = 175; + yposGMT = 220; +} else { + timeFontSize = 48; + dateFontSize = 15; + gmtFontSize = 10; + + yposTime = 55; + yposDate = 95; + yposYear = 128; + yposGMT = 161; +} // Check settings for what type our clock should be var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -function drawSimpleClock() { +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { g.clear(); Bangle.drawWidgets(); // get date var d = new Date(); - var da = d.toString().split(" "); g.reset(); // default draw styles // drawSting centered g.setFontAlign(0, 0); - // draw time - var time = da[4].substr(0, 5).split(":"); - var hours = time[0], - minutes = time[1]; - var meridian = ""; + // drawTime + var hours; if (is12Hour) { - hours = parseInt(hours,10); - meridian = "AM"; - if (hours == 0) { - hours = 12; - meridian = "AM"; - } else if (hours >= 12) { - meridian = "PM"; - if (hours>12) hours -= 12; - } - hours = (" "+hours).substr(-2); + hours = ("0" + d.getHours()%12).slice(-2); + } else { + hours = ("0" + d.getHours()).slice(-2); } + var minutes = ("0" + d.getMinutes()).slice(-2); g.setFont(font, timeFontSize); g.drawString(`${hours}:${minutes}`, xyCenter, yposTime, true); - g.setFont(font, gmtFontSize); - g.drawString(meridian, xyCenter + 102, yposTime + 10, true); + + if (is12Hour) { + g.setFont(font, gmtFontSize); + g.drawString(locale.meridian(d), xyCenter + 102, yposTime + 10, true); + } // draw Day, name of month, Date - var date = [da[0], da[1], da[2]].join(" "); g.setFont(font, dateFontSize); - - g.drawString(date, xyCenter, yposDate, true); + g.drawString([locale.dow(d,1), locale.month(d,1), d.getDate()].join(" "), xyCenter, yposDate, true); // draw year g.setFont(font, dateFontSize); g.drawString(d.getFullYear(), xyCenter, yposYear, true); // draw gmt - var gmt = da[5]; g.setFont(font, gmtFontSize); - g.drawString(gmt, xyCenter, yposGMT, true); + g.drawString(d.toString().match(/GMT[+-]\d+/), xyCenter, yposGMT, true); + + queueDraw(); } -// handle switch display on by pressing BTN1 -Bangle.on('lcdPower', function(on) { - if (on) drawSimpleClock(); +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } }); +// Show launcher when button pressed +Bangle.setUI("clock"); // clean app screen g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); -// refesh every 15 sec -setInterval(drawSimpleClock, 15E3); - // draw now -drawSimpleClock(); - -// Show launcher when button pressed -Bangle.setUI("clock"); +draw(); diff --git a/apps/swiperclocklaunch/ChangeLog b/apps/swiperclocklaunch/ChangeLog new file mode 100644 index 000000000..2286a7f70 --- /dev/null +++ b/apps/swiperclocklaunch/ChangeLog @@ -0,0 +1 @@ +0.01: New App! \ No newline at end of file diff --git a/apps/swiperclocklaunch/boot.js b/apps/swiperclocklaunch/boot.js new file mode 100644 index 000000000..0bb8d588a --- /dev/null +++ b/apps/swiperclocklaunch/boot.js @@ -0,0 +1,17 @@ +// clock -> launcher +(function() { + var sui = Bangle.setUI; + Bangle.setUI = function(mode, cb) { + sui(mode,cb); + if (!mode.startsWith("clock")) return; + Bangle.swipeHandler = dir => { if (dir<0) Bangle.showLauncher(); }; + Bangle.on("swipe", Bangle.swipeHandler); + }; +})(); +// launcher -> clock +setTimeout(function() { + if (global.__FILE__ && __FILE__.endsWith(".app.js") && (require("Storage").readJSON(__FILE__.slice(0,-6)+"info",1)||{}).type=="launch") { + Bangle.swipeHandler = dir => { if (dir>0) load(); }; + Bangle.on("swipe", Bangle.swipeHandler); + } +}, 10); \ No newline at end of file diff --git a/apps/swiperclocklaunch/icon.js b/apps/swiperclocklaunch/icon.js new file mode 100644 index 000000000..c9089ce5c --- /dev/null +++ b/apps/swiperclocklaunch/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("lEoxH+AB8WAAwYQEaQrdEp4pWEyYoRC49kxGs2fX6+z1mIsgpUCQtAxAjCAA+zxFAFCAQFxAkJAAuIFBxMF1oeHgEABI+sFBomEORInJPgJ7EEyonLFAJQJBIh0IE5x6GE47CME5nXsgnGOojmME5p5HJyAnO6+IE5LEKE6JQEE4lkC5gnPUIh2SE6B4EAAesC5oAP1gnHTxpPDAQIAFeJQACH5wnP64nWAA3CBJB3WAA203fQBAp3IY4plENQ4HC2gABkjHNxAnX2nJBYeIEYf+AYVkE5oDGE4e0UgdkEwYnDUAITEACikBTwgnFxAnZFAJ2FE4lAJ7dAE4pQFY6yfCToYmDE4kW1jvX1geEE4YoF2YfFABRzD67EEEwqiGFCAmETg5QJPQYAMTQJ0GE5AoGshSPYQgmKFA72BFJWzxBzEExgoIKYOI1grC2esxBLGExwpKABolPFCwmSFKQlVFZoXP")) \ No newline at end of file diff --git a/apps/swiperclocklaunch/swiperclocklaunch.png b/apps/swiperclocklaunch/swiperclocklaunch.png new file mode 100644 index 0000000000000000000000000000000000000000..c7f16c2b18ed67170e425ec92af33da5da40a295 GIT binary patch literal 889 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sjKx9jPK-BC>eK@{oCO|{#S9GG z!XV7ZFl&wkQ1DEEPlzj!X4rg$VbuYKH3v}xyxJbZy+<7uF2O5096dHv?yyLU|&ZykC33~1G^2T#N{9o5@; z>hklKT02ex)l{Fm@%+sjAme9bNl-ptoK!J3d9KezuImvO-+7QVE=9u}?IR}Rw{eoa~A#?;&0A)Me7rJ`|R zdEB3^{FytZZ0cgUaW!p{L-~xhg!5|G^n?;b|Kv3GPdy>jvZK4nhl542RpB)+hm)21 zStg6Q0)N$>{BxXOBDY#sP+-&3eo^l0{#=LnxlSm@-VQuvCnOS?EueW#`kGXG$mf%+ zPc*OAc=6tjK38Ao_}QXMZ-PeVr&I9@9(3(;I(S9!L|L z(^Qjt#7tP~=9}5?mmWB=$0Yt_<{7Q&Cuej03rd@+ZanX1q4Bn-X=~=Z-Iu?6+56e+ z_Hw_yH`#Yx*ynI&R{!gd-KR}A+@#^P^+|wD8o%o_ufVHeRf`v{-}3k2ryGBd&wt3i XqO?C%)!fY;ltVmS{an^LB{Ts5&i}k1 literal 0 HcmV?d00001 diff --git a/apps/toucher/ChangeLog b/apps/toucher/ChangeLog index 494110d55..7b5c53de7 100644 --- a/apps/toucher/ChangeLog +++ b/apps/toucher/ChangeLog @@ -3,4 +3,5 @@ 0.03: Close launcher when lcd turn off 0.04: Complete rewrite to add animation and loop ( issue #210 ) 0.05: Improve perf -0.06: Complete rewrite in 80x80, better perf, add settings \ No newline at end of file +0.06: Complete rewrite in 80x80, better perf, add settings +0.07: Added suppport for Bangle 2, added README file diff --git a/apps/toucher/README.md b/apps/toucher/README.md new file mode 100644 index 000000000..27cb32eeb --- /dev/null +++ b/apps/toucher/README.md @@ -0,0 +1,22 @@ +# Toucher - A touch based launcher, swipe left, swipe right, tap to launch + +* Designed specifically for Bangle 1 and Bangle 2 + +## Installation +- Use the App loader to install toucher +- Then delete the existing launcher +- When you restart the new launcher will be loaded +- To return to the default launcher, delete toucher and install the default launcher. + +## Bangle 1 +In the settings menu 'Low Res' refers to setting the Bangle 1 screen into 80x80 mode. +This significantly improves the animation performance. + +## Bangle 2 +The Hires/Lowres settings is ignored. +Touch the top third of the screen to launch the selected app. +Press button 1 to launch the selected app. + +## Screenshots + +![](screenshot1.jpg) diff --git a/apps/toucher/app.js b/apps/toucher/app.js index 455a29c5d..8ac198f52 100644 --- a/apps/toucher/app.js +++ b/apps/toucher/app.js @@ -7,8 +7,11 @@ let settings = Storage.readJSON(filename,1) || { debug: false }; -if(!settings.highres) Bangle.setLCDMode("80x80"); -else Bangle.setLCDMode(); +// this means that setFont('6x8',1) is actually setFont('6x8',3) +if (process.env.HWVERSION == 1) { + if(!settings.highres) Bangle.setLCDMode("80x80"); + else Bangle.setLCDMode(); +} g.clear(); g.flip(); @@ -23,7 +26,7 @@ const ORIGINAL_ICON_SIZE = 48; const STATE = { settings_open: false, index: 0, - target: 240, + target: g.getWidth(), offset: 0 }; @@ -63,7 +66,7 @@ const APPS = getApps(); function noIcon(x, y, scale){ if(scale < 0.2) return; - g.setColor(scale, scale, scale); + g.setColor(g.theme.fg); g.setFontAlign(0,0); g.setFont('6x8',settings.highres ? 6:3); g.drawString('x_x', x+1.5, y); @@ -81,23 +84,24 @@ function render(){ g.clear(); const visibleApps = APPS.filter(app => app.x >= STATE.offset-HALF && app.x <= STATE.offset+WIDTH-HALF ); + let cycle = 0; + let lastCycle = visibleApps.length; + visibleApps.forEach(app => { - - const x = app.x+HALF-STATE.offset; - const y = HALF - (HALF*0.3); + cycle++; + const x = app.x + HALF - STATE.offset; + const y = HALF; let dist = HALF - x; if(dist < 0) dist *= -1; - const scale = 1 - (dist / HALF); if(!scale) return; if(app.special){ - const font = settings.highres ? '6x8' : '4x6'; - const fontSize = settings.highres ? 2 : 1; - g.setFont(font, fontSize); - g.setColor(scale,scale,scale); + const fontSize = (process.env.HWVERSION == 2) ? 4 : (settings.highres ? 6 : 2); + g.setFont('6x8', fontSize); + g.setColor(g.theme.fg); g.setFontAlign(0,0); g.drawString(app.name, HALF, HALF); return; @@ -111,26 +115,69 @@ function render(){ if(icon){ icons[app.name] = icon; try { - const rescale = settings.highres ? scale*ORIGINAL_ICON_SIZE : (scale*(ORIGINAL_ICON_SIZE/2)); - const imageScale = settings.highres ? scale*2 : scale; + let rescale; + let imageScale; + + if (process.env.HWVERSION == 1) { + // on a bangle 1 !highres means 80x80 + rescale = settings.highres ? scale*ORIGINAL_ICON_SIZE : (scale*(ORIGINAL_ICON_SIZE/2)); + imageScale = settings.highres ? scale*2 : scale; + } else { + // !highres mode is meaningless on a bangle 2 at present + rescale = 1.25*scale*ORIGINAL_ICON_SIZE; + imageScale = 2.5*scale; + } + g.drawImage(icon, x-rescale, y-rescale, { scale: imageScale }); - } catch(e){ + } catch(e) { noIcon(x, y, scale); } - }else{ + } else { noIcon(x, y, scale); } //draw text - g.setColor(scale,scale,scale); - if(scale > 0.1){ - const font = settings.highres ? '6x8': '4x6'; - const fontSize = settings.highres ? 2 : 1; - g.setFont(font, fontSize); - g.setFontAlign(0,0); - g.drawString(app.name, HALF, HEIGHT/4*3); - } + g.setColor(g.theme.fg); + if (cycle == 2 && scale > 0.1) { + let fontSize = (process.env.HWVERSION == 2) ? 2 : 1; + if (process.env.HWVERSION == 1) { + fontSize = (settings.highres) ? 3 : 1; + } + + if (app.name.length <= 12) { + g.setFont("6x8", fontSize); + g.setFontAlign(0,1); + g.drawString(app.name, HALF, HEIGHT); + } else { + // some app names are too long for one line + var name = app.name; + var first = name.substring(0, name.indexOf(" ")); + var last = name.substring(name.indexOf(" ") + 1, name.length); + + // all this to handle long names like + // Simple 7 Segment Clock + if (last.length > 12 && process.env.HWVERSION == 1) { + g.setFont((settings.highres ? "6x8" : "4x6"),(settings.highres ? 2 : 1) ); + } else { + g.setFont("6x8", fontSize); + } + + g.setFontAlign(0,-1); + g.drawString(first, HALF, 0); + + if (last.length > 12 && process.env.HWVERSION == 1) { + g.setFont((settings.highres ? "6x8" : "4x6"),(settings.highres ? 2 : 1) ); + } else { + g.setFont("6x8", fontSize); + } + + g.setFontAlign(0,1); + g.drawString(last, HALF, HEIGHT); + } + } + + /* if(settings.highres){ const type = app.type ? app.type : 'App'; const version = app.version ? app.version : '0.00'; @@ -138,18 +185,21 @@ function render(){ g.setFontAlign(0,1); g.setFont('6x8', 1.5); g.setColor(scale,scale,scale); - g.drawString(info, HALF, 215, { scale: scale }); + g.drawString(info, HALF, HEIGHT/8*7, { scale: scale }); } + */ }); const duration = Math.floor(Date.now()-start); if(settings.debug){ g.setFontAlign(0,1); - g.setColor(0, 1, 0); - const fontSize = settings.highres ? 2 : 1; - g.setFont('4x6',fontSize); - g.drawString('Render: '+duration+'ms', HALF, HEIGHT); + g.setColor(g.theme.fgH); + const fontSize = (process.env.HWVERSION == 2) ? 2 : (settings.highres ? 2 : 1); + g.setFont(((process.env.HWVERSION == 2) ? '6x8' : (settings.highres ? '6x8' :'4x6')), fontSize); + // steal the bottom line, and print the duration + g.clearRect(0, HEIGHT - (process.env.HWVERSION == 1 && !settings.highres ? 8 : 24), WIDTH, HEIGHT); + g.drawString('Render: '+duration+' ms', HALF, HEIGHT); } g.flip(); if(STATE.offset == STATE.target) return; @@ -202,7 +252,7 @@ function run(){ E.showMessage("App Source\nNot found"); setTimeout(render, 2000); } else { - Bangle.setLCDMode(); + if (process.env.HWVERSION == 1) Bangle.setLCDMode(); g.clear(); g.flip(); E.showMessage("Loading..."); @@ -211,10 +261,11 @@ function run(){ } -// Screen event -Bangle.on('touch', function(button){ - if(STATE.settings_open) return; - switch(button){ +if (process.env.HWVERSION == 1) { + // Screen event + Bangle.on('touch', function(button){ + if(STATE.settings_open) return; + switch(button){ case 1: prev(); break; @@ -224,8 +275,17 @@ Bangle.on('touch', function(button){ case 3: run(); break; - } -}); + } + }); +} + +if (process.env.HWVERSION == 2) { + // tap at top 1/3 of screen to launch app + Bangle.on('touch', function(button, xy) { + if (xy.y < HEIGHT / 3) + run(); + }); +} Bangle.on('swipe', dir => { if(STATE.settings_open) return; @@ -238,9 +298,12 @@ Bangle.on('lcdPower', on => { if(!on) return load(); }); +if (process.env.HWVERSION == 1) { + setWatch(prev, BTN1, { repeat: true }); + setWatch(next, BTN3, { repeat: true }); + setWatch(run, BTN2, { repeat:true }); +} else { + setWatch(run, BTN1, { repeat:true }); +} -setWatch(prev, BTN1, { repeat: true }); -setWatch(next, BTN3, { repeat: true }); -setWatch(run, BTN2, { repeat:true }); - -jumpTo(1); \ No newline at end of file +jumpTo(1); diff --git a/apps/toucher/screenshot1.jpg b/apps/toucher/screenshot1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..698121cbe9790999983c97c5d1503c749c65f28e GIT binary patch literal 21329 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

    +
    @@ -125,6 +136,10 @@ Pretokenise apps before upload (smaller, faster apps) +
    diff --git a/loader.js b/loader.js index 6528ffc98..a28f7fe78 100644 --- a/loader.js +++ b/loader.js @@ -14,6 +14,10 @@ if (window.location.host=="banglejs.com") { var RECOMMENDED_VERSION = "2v10"; // could check http://www.espruino.com/json/BANGLEJS.json for this +// We're only interested in Bangles +DEVICEINFO = DEVICEINFO.filter(x=>x.id.startsWith("BANGLEJS")); + +// Set up source code URL (function() { let username = "espruino"; let githubMatch = window.location.href.match(/\/(\w+)\.github\.io/); @@ -21,16 +25,169 @@ var RECOMMENDED_VERSION = "2v10"; Const.APP_SOURCECODE_URL = `https://github.com/${username}/BangleApps/tree/master/apps`; })(); +// When a device is found, filter the apps accordingly function onFoundDeviceInfo(deviceId, deviceVersion) { - if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") { - showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); - } else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) { - showToast(`You're using an old Bangle.js firmware (${deviceVersion}). You can update with the instructions here` ,"warning", 20000); - } + var fwURL = "#"; if (deviceId == "BANGLEJS") { + fwURL = "https://www.espruino.com/Bangle.js#firmware-updates"; Const.MESSAGE_RELOAD = 'Hold BTN3\nto reload'; } if (deviceId == "BANGLEJS2") { + fwURL = "https://www.espruino.com/Bangle.js2#firmware-updates"; Const.MESSAGE_RELOAD = 'Hold button\nto reload'; } + + if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") { + showToast(`You're using ${deviceId}, not a Bangle.js. Did you want espruino.com/apps instead?` ,"warning", 20000); + } else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) { + showToast(`You're using an old Bangle.js firmware (${deviceVersion}). You can update with the instructions here` ,"warning", 20000); + } + + + // check against features shown? + filterAppsForDevice(deviceId); + /* if we'd saved a device ID but this device is different, ensure + we ask again next time */ + var savedDeviceId = getSavedDeviceId(); + if (savedDeviceId!==undefined && savedDeviceId!=deviceId) + setSavedDeviceId(undefined); +} + +var originalAppJSON = undefined; +function filterAppsForDevice(deviceId) { + if (originalAppJSON===undefined && appJSON.length) + originalAppJSON = appJSON; + + var device = DEVICEINFO.find(d=>d.id==deviceId); + // set the device dropdown + document.querySelector(".devicetype-nav span").innerText = device ? device.name : "All apps"; + + if (!device) { + if (deviceId!==undefined) + showToast(`Device ID ${deviceId} not recognised. Some apps may not work`, "warning"); + appJSON = originalAppJSON; + } else { + // Now filter apps + appJSON = originalAppJSON.filter(app => { + var supported = ["BANGLEJS"]; + if (!app.supports) { + console.log(`App ${app.id} doesn't include a 'supports' field - ignoring`); + return false; + } + if (app.supports.includes(deviceId)) return true; + //console.log(`Dropping ${app.id} because ${deviceId} is not in supported list ${app.supports.join(",")}`); + return false; + }); + } + refreshLibrary(); +} + +// If 'remember' was checked in the window below, this is the device +function getSavedDeviceId() { + let deviceId = localStorage.getItem("deviceId"); + if (("string"==typeof deviceId) && DEVICEINFO.find(d=>d.id == deviceId)) + return deviceId; + return undefined; +} + +function setSavedDeviceId(deviceId) { + localStorage.setItem("deviceId", deviceId); +} + +// At boot, show a window to choose which type of device you have... +window.addEventListener('load', (event) => { + let deviceId = getSavedDeviceId() + if (deviceId !== undefined) return; // already chosen + + var html = `
    + ${DEVICEINFO.map(d=>` +
    +
    +
    +
    ${d.name}
    + +
    +
    + ${d.name} +
    +
    +
    `).join("\n")} +
    +
    +
    + +
    +
    +
    `; + showPrompt("Which Bangle.js?",html,{},false); + htmlToArray(document.querySelectorAll(".devicechooser")).forEach(button => { + button.addEventListener("click",event => { + let rememberDevice = document.getElementById("remember_device").checked; + + let button = event.currentTarget; + let deviceId = button.getAttribute("deviceid"); + hidePrompt(); + console.log("Chosen device", deviceId); + setSavedDeviceId(rememberDevice ? deviceId : undefined); + filterAppsForDevice(deviceId); + }); + }); +}); + +window.addEventListener('load', (event) => { + // Hook onto device chooser dropdown + htmlToArray(document.querySelectorAll(".devicetype-nav .menu-item")).forEach(button => { + button.addEventListener("click", event => { + var a = event.target; + var deviceId = a.getAttribute("dt")||undefined; + filterAppsForDevice(deviceId); // also sets the device dropdown + setSavedDeviceId(undefined); // ask at startup next time + document.querySelector(".devicetype-nav span").innerText = a.innerText; + }); + }); + + // Button to install all default apps in one go + document.getElementById("installdefault").addEventListener("click",event=>{ + getInstalledApps().then(() => { + if (device.id == "BANGLEJS") + return httpGet("defaultapps_banglejs1.json"); + if (device.id == "BANGLEJS2") + return httpGet("defaultapps_banglejs2.json"); + throw new Error("Unknown device "+device.id); + }).then(json=>{ + return installMultipleApps(JSON.parse(json), "default"); + }).catch(err=>{ + Progress.hide({sticky:true}); + showToast("App Install failed, "+err,"error"); + }); + }); +}); + +function onAppJSONLoaded() { + let deviceId = getSavedDeviceId() + if (deviceId !== undefined) + filterAppsForDevice(deviceId); + + return new Promise(resolve => { + httpGet("screenshots.json").then(screenshotJSON=>{ + var screenshots = []; + try { + screenshots = JSON.parse(screenshotJSON); + } catch(e) { + console.error("Screenshot JSON Corrupted", e); + } + screenshots.forEach(s => { + var app = appJSON.find(a=>a.id==s.id); + if (!app) return; + if (!app.screenshots) app.screenshots = []; + app.screenshots.push({url:s.url}); + }) + }).catch(err=>{ + console.log("No screenshots.json found"); + resolve(); + }); + }); } diff --git a/modules/Layout.js b/modules/Layout.js index 03aa6249b..6dc4b6368 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -29,7 +29,9 @@ layoutObject has: * `undefined` - blank, can be used for padding * `"txt"` - a text label, with value `label` and `r` for text rotation. 'font' is required * `"btn"` - a button, with value `label` and callback `cb` - * `"img"` - an image where `src` is an image, or a function which is called to return an image to draw + optional `src` specifies an image (like img) in which case label is ignored + * `"img"` - an image where `src` is an image, or a function which is called to return an image to draw. + optional `scale` specifies if image should be scaled up or not * `"custom"` - a custom block where `render(layoutObj)` is called to render * `"h"` - Horizontal layout, `c` is an array of more `layoutObject` * `"v"` - Veritical layout, `c` is an array of more `layoutObject` @@ -80,12 +82,12 @@ function Layout(layout, options) { this._l = this.l = layout; // Do we have >1 physical buttons? this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; - this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0; options = options || {}; this.lazy = options.lazy || false; var btnList; + Bangle.setUI(); // remove all existing input handlers if (process.env.HWVERSION!=2) { // no touchscreen, find any buttons in 'layout' btnList = []; @@ -107,9 +109,7 @@ function Layout(layout, options) { delete this.buttons[s].selected; this.render(this.buttons[s]); } - s += dir; - if (s<0) s+=lh; - if (s>=l) s-=l; + s = (s+l+dir) % l; if (this.buttons[s]) { this.buttons[s].selected = 1; this.render(this.buttons[s]); @@ -131,7 +131,7 @@ function Layout(layout, options) { if (this.b[btn].cb) this.b[btn].cb(e); } // enough physical buttons - let btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns); + let btnHeight = Math.floor(Bangle.appRect.h / this.physBtns); if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch); Bangle.btnWatch = []; if (this.physBtns > 2 && buttons.length==1) @@ -158,6 +158,7 @@ function Layout(layout, options) { } } if (process.env.HWVERSION==2) { + // Handler for touch events function touchHandler(l,e) { if (l.type=="btn" && l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) { @@ -169,14 +170,23 @@ function Layout(layout, options) { Bangle.on('touch',Bangle.touchHandler); } - // add IDs + // recurse over layout doing some fixing up if needed var ll = this; - function idRecurser(l) { + function recurser(l) { + // add IDs if (l.id) ll[l.id] = l; + // fix type up if (!l.type) l.type=""; - if (l.c) l.c.forEach(idRecurser); + // FIXME ':'/fsz not needed in new firmwares - Font:12 is handled internally + // fix fonts for pre-2v11 firmware + if (l.font && l.font.includes(":")) { + var f = l.font.split(":"); + l.font = f[0]; + l.fsz = f[1]; + } + if (l.c) l.c.forEach(recurser); } - idRecurser(layout); + recurser(this._l); this.updateNeeded = true; } @@ -237,10 +247,8 @@ Layout.prototype.render = function (l) { g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); } }, "btn":function(l){ - var x = l.x+(0|l.pad); - var y = l.y+(0|l.pad); - var w = l.w-(l.pad<<1); - var h = l.h-(l.pad<<1); + var x = l.x+(0|l.pad), y = l.y+(0|l.pad), + w = l.w-(l.pad<<1), h = l.h-(l.pad<<1); var poly = [ x,y+4, x+4,y, @@ -251,10 +259,12 @@ Layout.prototype.render = function (l) { x+4,y+h-1, x,y+h-5, x,y+4 - ]; - g.setColor(l.selected?g.theme.bgH:g.theme.bg2).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly).setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); + ], bg = l.selected?g.theme.bgH:g.theme.bg2; + g.setColor(bg).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg2).drawPoly(poly); + if (l.src) g.setBgColor(bg).drawImage("function"==typeof l.src?l.src():l.src, l.x + 10 + (0|l.pad), l.y + 8 + (0|l.pad)); + else g.setFont("6x8",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); }, "img":function(l){ - g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad)); + g.drawImage("function"==typeof l.src?l.src():l.src, l.x + (0|l.pad), l.y + (0|l.pad), l.scale?{scale:l.scale}:undefined); }, "custom":function(l){ l.render(l); },"h":function(l) { l.c.forEach(render); }, @@ -335,10 +345,6 @@ Layout.prototype.debug = function(l,c) { }; Layout.prototype.update = function() { delete this.updateNeeded; - var l = this._l; - var w = g.getWidth(); - var y = this.yOffset; - var h = g.getHeight()-y; // update sizes function updateMin(l) {"ram" cb[l.type](l); @@ -352,12 +358,6 @@ Layout.prototype.update = function() { "txt" : function(l) { if (l.font.endsWith("%")) l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); - // FIXME ':'/fsz not needed in new firmwares - it's handled internally - if (l.font.includes(":")) { - var f = l.font.split(":"); - l.font = f[0]; - l.fsz = f[1]; - } if (l.wrap) { l._h = l._w = 0; } else { @@ -365,12 +365,13 @@ Layout.prototype.update = function() { l._w = m.width; l._h = m.height; } }, "btn": function(l) { - l._h = 32; - l._w = 20 + l.label.length*12; + var m = l.src?g.imageMetrics("function"==typeof l.src?l.src():l.src):g.setFont("6x8",2).stringMetrics(l.label); + l._h = 16 + m.height; + l._w = 20 + m.width; }, "img": function(l) { - var m = g.imageMetrics("function"==typeof l.src?l.src():l.src); // get width and height out of image - l._w = m.width; - l._h = m.height; + var m = g.imageMetrics("function"==typeof l.src?l.src():l.src), s=l.scale||1; // get width and height out of image + l._w = m.width*s; + l._h = m.height*s; }, "": function(l) { // size should already be set up in width/height l._w = 0; @@ -393,18 +394,19 @@ Layout.prototype.update = function() { if (l.filly == null && l.c.some(c=>c.filly)) l.filly = 1; } }; + + var l = this._l; updateMin(l); - // center - if (l.fillx || l.filly) { - l.w = w; - l.h = h; - l.x = 0; - l.y = y; - } else { + if (l.fillx || l.filly) { // fill all + l.w = Bangle.appRect.w; + l.h = Bangle.appRect.h; + l.x = Bangle.appRect.x; + l.y = Bangle.appRect.y; + } else { // or center l.w = l._w; l.h = l._h; - l.x = (w-l.w)>>1; - l.y = y+((h-l.h)>>1); + l.x = (Bangle.appRect.w-l.w)>>1; + l.y = Bangle.appRect.y+((Bangle.appRect.h-l.h)>>1); } // layout children this.layout(l); From 7c74c5d43f9f66b6374cabd4e247d1560e5f29a1 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 18:15:40 -0800 Subject: [PATCH 0778/1062] Update apps.json --- apps.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/apps.json b/apps.json index 7fe259899..2b5888acf 100644 --- a/apps.json +++ b/apps.json @@ -4307,5 +4307,22 @@ {"name":"binwatch.app.js","url":"app.js"}, {"name":"binwatch.img","url":"app-icon.js","evaluate":true} ] + }, + { "id": "schoolCalendar", + "name": "School Calendar", + "shortName":"SCalendar", + "icon": "CalenderLogo.png", + "version": "beta1", + "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", + "tags": "tool", + "readme": "README.md", + "custom":"custom.html", + "storage": [ + {"name":"schoolCalendar.app.js"}, + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + {"name":"app.json"} + ] } ] From bf994d9acc8b79ebda1e47dc5ebc5158210d6205 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 18:24:29 -0800 Subject: [PATCH 0779/1062] Update apps.json --- apps.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps.json b/apps.json index 2b5888acf..ce1f346be 100644 --- a/apps.json +++ b/apps.json @@ -4317,6 +4317,7 @@ "tags": "tool", "readme": "README.md", "custom":"custom.html", + "supports": ["BANGLEJS"], "storage": [ {"name":"schoolCalendar.app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} From 426768eb11e34ea7a04010e6fe7da1a5a5187f40 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:32:14 -0800 Subject: [PATCH 0780/1062] Update custom.html --- apps/schoolCalendar/custom.html | 68 ++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index de3511e19..6cdba962d 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -254,30 +254,31 @@ function renderBackground(l) { } function renderTable(l) { + var foundNumber = findNextScheduleIndex(); + var yellowIndex = 3; + if (foundNumber < 3) { yellowIndex = foundNumber; } for(var x = 0;x<=numberOfItemsShown;x++){ g.setColor(255,255,255); g.drawRect(rectStartX,rectStart+(x*20),rectEndX,rectEnd+(20*x)); - g.setColor(255,205,0); - g.drawRect(rectStartX,rectStart+(2*20),rectEndX,rectEnd+(2*20)); - g.setColor(255,0,0); - g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); } + g.setColor(255,205,0); + g.drawRect(rectStartX,rectStart+(yellowIndex*20),rectEndX,rectEnd+(20*yellowIndex)); + g.setColor(255,0,0); + g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); } -function renderTableText(l){ - var foundNumber = findNextScheduleIndex(); +function renderTableText(l) { var foundSchedule = getScheduleTable(); + var foundNumber = findNextScheduleIndex(); + var startNumber = foundNumber - 2; + if (startNumber < 0) { startNumber = 0; } + var endNumber = startNumber + 8 - (foundNumber - startNumber); + if (endNumber > foundSchedule.length-1) { endNumber = foundSchedule.length-1; } + + var scheduleHourUpdated; var scheduleMinuteUpdated; - var beforeFoundNumber = foundNumber - 2; - for(var x = 0;x<=numberOfItemsShown;x++){ - var currentNumber = beforeFoundNumber + x; - if (beforeFoundNumber + x < 0) { - currentNumber = foundSchedule.length + beforeFoundNumber + x; - } else if (beforeFoundNumber + x > foundSchedule.length - 1) { - currentNumber = beforeFoundNumber + x - foundSchedule.length; - } - + for(var currentNumber = startNumber; currentNumber<=endNumber; currentNumber++){ scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm); scheduleHourUpdatedStart = foundSchedule[currentNumber].sh; scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em); @@ -288,7 +289,7 @@ function renderTableText(l){ } schduleDay = updateDay(3,foundSchedule[currentNumber].dow); g.setFont("8x12"); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(x*20)); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(currentNumber*20)); } } @@ -364,22 +365,27 @@ function renderLoading(l){ function renderInformation(l){ var foundNumber = findNextScheduleIndex(); var foundSchedule = getScheduleTable(); - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); - scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); - scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; - scheduleDay = updateDay(1,foundSchedule[((foundNumber-2)+currentPositionTable)].dow); - g.setColor(255,255,255); - g.setFont("8x12",2); - var splitClassNames = splitter(foundSchedule[((foundNumber-2)+currentPositionTable)].cn, 15); - var currentY = 5; - for (var j=0; j < splitClassNames.length; j++) { - g.drawString(splitClassNames[j],13,currentY+50); - currentY = currentY + 25; + var startNumber = foundNumber - 2; + if (startNumber < 0) { startNumber = 0; } + + if ((startNumber+currentPositionTable) <= foundSchedule.length-1) { + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); + scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); + scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; + scheduleDay = updateDay(1,foundSchedule[(startNumber+currentPositionTable)].dow); + g.setColor(255,255,255); + g.setFont("8x12",2); + var splitClassNames = splitter(foundSchedule[(startNumber+currentPositionTable)].cn, 15); + var currentY = 5; + for (var j=0; j < splitClassNames.length; j++) { + g.drawString(splitClassNames[j],13,currentY+50); + currentY = currentY + 25; + } + g.setFont("8x12"); + g.drawString(schduleDay,13,currentY+50); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); } - g.setFont("8x12"); - g.drawString(schduleDay,13,currentY+50); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); } var Layout = require("Layout"); From 854ae5361d1b2400f6b6d1b2c2239f46d1e16bc9 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:34:28 -0800 Subject: [PATCH 0781/1062] Update README.md --- apps/schoolCalendar/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/schoolCalendar/README.md b/apps/schoolCalendar/README.md index 47815cc87..fd95ddf59 100644 --- a/apps/schoolCalendar/README.md +++ b/apps/schoolCalendar/README.md @@ -7,8 +7,11 @@ Enter your calendar events on the customizer then upload. (all day events are no Once uploaded on the watch when in the table mode you can use BTN1 and BTN3 to scroll up and down on the list. (The red rectangle indicates your current position on the table and your yellow rectangle indicates your current schedule item or your next schedule item.) -If you press BTN2 it will go into detail mode, and you can see additional information about your schedule item. Also, in this mode you can scroll up and down with BTN1 and BTN3 to move around in the table. +If you press BTN2 it will go into detail mode, and you can see additional information about your schedule item. Also, in this mode you can scroll up and down with BTN1 and BTN3 to move around in the table. To exit detail mode press BTN2 again. +## Screenshots: +![screenshot (5)](https://user-images.githubusercontent.com/89286474/142801592-485aa0b0-c417-44c8-8097-befa81d2599c.png) +![screenshot (6)](https://user-images.githubusercontent.com/89286474/142801595-47f73c63-501a-4221-baba-84dd97b65bf9.png) ## Updates Coming Soon: - [ ] Notifications @@ -17,7 +20,7 @@ If you press BTN2 it will go into detail mode, and you can see additional inform - [ ] Better Graphics - [ ] Scrolling Table - [ ] Bangle.js V2 Compatibility -- [ ] Orginal Calendar +- [ ] Full Calendar (Calendar that does not have repeating weekly events.) ## Creator Ronin0000 From 939d8f6a9ae71b49c13f7dbf1455bb2fec60462a Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:37:21 -0800 Subject: [PATCH 0782/1062] Add files via upload --- apps/schoolCalendar/screenshot_basic.png | Bin 0 -> 6482 bytes apps/schoolCalendar/screenshot_info.png | Bin 0 -> 4021 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/schoolCalendar/screenshot_basic.png create mode 100644 apps/schoolCalendar/screenshot_info.png diff --git a/apps/schoolCalendar/screenshot_basic.png b/apps/schoolCalendar/screenshot_basic.png new file mode 100644 index 0000000000000000000000000000000000000000..4872f02c4abc79547e948b31cd4d2cdba6a0bbdd GIT binary patch literal 6482 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 Gr~d_o@GQOn literal 0 HcmV?d00001 diff --git a/apps/schoolCalendar/screenshot_info.png b/apps/schoolCalendar/screenshot_info.png new file mode 100644 index 0000000000000000000000000000000000000000..c539b1b1c5c035e5970deb8347568f545bd96510 GIT binary patch literal 4021 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 Date: Sun, 21 Nov 2021 20:37:40 -0800 Subject: [PATCH 0783/1062] Update apps.json --- apps.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps.json b/apps.json index ce1f346be..a6badbce3 100644 --- a/apps.json +++ b/apps.json @@ -4318,6 +4318,7 @@ "readme": "README.md", "custom":"custom.html", "supports": ["BANGLEJS"], + "screenshots": [{"url":"screenshot_basic.png"},{"url":"screenshot_info.png"}], "storage": [ {"name":"schoolCalendar.app.js"}, {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} From 6e07f282b8918ac3b8f333c7c4156980a44ff1d6 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:41:26 -0800 Subject: [PATCH 0784/1062] Add files via upload --- apps/schoolCalendar/screenshot_basic.png | Bin 6482 -> 6464 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/schoolCalendar/screenshot_basic.png b/apps/schoolCalendar/screenshot_basic.png index 4872f02c4abc79547e948b31cd4d2cdba6a0bbdd..879ad6a3c7cc7e054a29ef66fb48fc7edee929ed 100644 GIT binary patch delta 5816 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 Date: Sun, 21 Nov 2021 20:48:14 -0800 Subject: [PATCH 0785/1062] Rename ChangeLog.md to ChangeLog --- apps/schoolCalendar/{ChangeLog.md => ChangeLog} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/schoolCalendar/{ChangeLog.md => ChangeLog} (100%) diff --git a/apps/schoolCalendar/ChangeLog.md b/apps/schoolCalendar/ChangeLog similarity index 100% rename from apps/schoolCalendar/ChangeLog.md rename to apps/schoolCalendar/ChangeLog From 3ee0e9bae6ee137c6e36672c9aa6a20d1c072caf Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:49:48 -0800 Subject: [PATCH 0786/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index a6badbce3..51e5978ee 100644 --- a/apps.json +++ b/apps.json @@ -4312,7 +4312,7 @@ "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", - "version": "beta1", + "version": "0.01", "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", "tags": "tool", "readme": "README.md", From f950fd2ee88486e3355efd30823d50de799e8418 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:57:52 -0800 Subject: [PATCH 0787/1062] Update custom.html --- apps/schoolCalendar/custom.html | 369 +++++++++++++++++++++++++++++++- 1 file changed, 361 insertions(+), 8 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 6cdba962d..6c288d600 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -79,8 +79,6 @@ var calendarEvents = calendar.getEvents(); let schedule = [] //-------------------- - console.log = console.log || function(){}; - //-------------------- for(i=0;i minuteOfWeek) { + return currentPosition; + } + } + return 0; +} + + +function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} + +function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} + +function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} + +function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} + +var currentPositionTable = 0; +var numberOfItemsShown = 8; +//Table Positions: +var rectStart = 45; +var rectEnd = 65; +var rectStartX = 10; +var rectEndX = 210; +//Scences: +LIST = 1; +INFORMATION = 2; +currentStage = LIST; + +function splitter(str, l){ + var strs = []; + while(str.length > l){ + var pos = str.substring(0, l).lastIndexOf(' '); + pos = pos <= 0 ? l : pos; + strs.push(str.substring(0, pos)); + var i = str.indexOf(' ', pos)+1; + if(i < pos || i > pos+l) + i = pos; + str = str.substring(i); + } + strs.push(str); + return strs; +} + +function updateMinutesToCurrentTime(currentMinuteFunction) { + if (currentMinuteFunction<10){ + currentMinuteUpdatedFunction = "0"+currentMinuteFunction; + }else{ + currentMinuteUpdatedFunction = currentMinuteFunction; + } + return currentMinuteUpdatedFunction; +} + +function renderBackground(l) { + g.clearRect(0,0,240,20); + g.drawImage(getBackgroundImage(),110,130,{scale:9,rotate:0}); +} + +function renderTable(l) { + var foundNumber = findNextScheduleIndex(); + var yellowIndex = 3; + if (foundNumber < 3) { yellowIndex = foundNumber; } + for(var x = 0;x<=numberOfItemsShown;x++){ + g.setColor(255,255,255); + g.drawRect(rectStartX,rectStart+(x*20),rectEndX,rectEnd+(20*x)); + } + g.setColor(255,205,0); + g.drawRect(rectStartX,rectStart+(yellowIndex*20),rectEndX,rectEnd+(20*yellowIndex)); + g.setColor(255,0,0); + g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); +} + +function renderTableText(l) { + var foundSchedule = getScheduleTable(); + var foundNumber = findNextScheduleIndex(); + var startNumber = foundNumber - 2; + if (startNumber < 0) { startNumber = 0; } + var endNumber = startNumber + 8 - (foundNumber - startNumber); + if (endNumber > foundSchedule.length-1) { endNumber = foundSchedule.length-1; } + + + var scheduleHourUpdated; + var scheduleMinuteUpdated; + for(var currentNumber = startNumber; currentNumber<=endNumber; currentNumber++){ + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm); + scheduleHourUpdatedStart = foundSchedule[currentNumber].sh; + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em); + scheduleHourUpdatedEnd = foundSchedule[currentNumber].eh; + scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20); + if(foundSchedule[currentNumber].cn.length >= 15){ + scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20)+"..."; + } + schduleDay = updateDay(3,foundSchedule[currentNumber].dow); + g.setFont("8x12"); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(currentNumber*20)); + } +} + +function buttonsF(l){ + if(currentStage == LIST){ + g.drawImage(getDotIcon(),223.5,115); + }else{ + g.drawImage(getMenuIcon(),223.5,115); + } + g.drawImage(getUpArrow(),225,30); + g.drawImage(getDownArrow(),225,215); +} + +function draw() { + var currentDate = new Date(); + var currentDayOfWeek = currentDate.getDay(); + var currentHour = currentDate.getHours(); + var currentMinute = currentDate.getMinutes(); + var currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); + if (layout) { + if(currentStage == LIST){ + layout.time.label = currentHour+":"+currentMinuteUpdated; + layout.time.x = 147; + layout.time.y = 10; + layout.render(layout.table); + layout.render(layout.tableText); + logDebug("Rendered"+currentPositionTable); + }else{ + layout.time.label = currentHour+":"+currentMinuteUpdated; + layout.time.x = 147; + layout.time.y = 10; + layout.render(layout.info); + logDebug("Rendered"+currentPositionTable); + } + g.clearRect(150,0,220,35); + layout.render(layout.time); + } +} + +function RedRectDown() { + if(currentPositionTable > 0){ + currentPositionTable -= 1; + if(currentStage == INFORMATION){ + redrawScreen(); + }else{ + draw(); + } + } +} + +function RedRectUp() { + if(currentPositionTable < numberOfItemsShown){ + currentPositionTable += 1; + if(currentStage == INFORMATION){ + redrawScreen(); + }else{ + draw(); + } + } +} + +function renderMiniBackground(l){ + for(var i = 233;i<=240;i++){ + g.drawImage(getBackgroundImage(),i,123,{scale:10,rotate:0}); + } +} + +function renderLoading(l){ + g.setFont("8x12"); + g.drawString("Loading...",240/2-20,240/2-20); +} + +function renderInformation(l){ + var foundNumber = findNextScheduleIndex(); + var foundSchedule = getScheduleTable(); + var startNumber = foundNumber - 2; + if (startNumber < 0) { startNumber = 0; } + + if ((startNumber+currentPositionTable) <= foundSchedule.length-1) { + scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); + scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; + scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); + scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; + scheduleDay = updateDay(1,foundSchedule[(startNumber+currentPositionTable)].dow); + g.setColor(255,255,255); + g.setFont("8x12",2); + var splitClassNames = splitter(foundSchedule[(startNumber+currentPositionTable)].cn, 15); + var currentY = 5; + for (var j=0; j < splitClassNames.length; j++) { + g.drawString(splitClassNames[j],13,currentY+50); + currentY = currentY + 25; + } + g.setFont("8x12"); + g.drawString(schduleDay,13,currentY+50); + g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); + } +} + +var Layout = require("Layout"); +var layout = new Layout( + {type:"h", c: [ + {type:"custom", render:renderTableText, id:"tableText"}, + {type:"custom", render:buttonsF, id:"buttons"}, + {type:"custom", render:renderBackground, id:"background"}, + {type:"custom", render:renderTable, id:"table"}, + {type:"custom", render:renderMiniBackground, id:"miniBackground"}, + {type:"custom", render:renderLoading, id:"loading"}, + {type:"custom", render:renderInformation, id:"info"}, + {type:"txt", font:"7x11Numeric7Seg:2", label:"00:00", id:"time"}, + ]}, + {type:"v", c:[ + ]}, + {btns:[ + {label:"", cb: RedRectUp()}, + {label:"", cb: l=>print("Two")}, + {label:"", cb: RedRectDown()} +]}); + + +function getBackgroundImage() {return require("heatshrink").decompress(atob("j0ZyEKIf4A4gIB6gQB6gYB6ggB6goB6gwB6g4B6hAABAYIBHBZIVLAK8IhIBXgAThhQB6hYB6hgB6hoB6hwB6h4B6iAB6iIB6iQBHiAJOB54XSiYB6igB6ioB6iwB6i4B5A="));} + +function logDebug(message) {console.log(message);} + +function changeScene(){ + layout.render(layout.buttons); + if(currentStage == INFORMATION){ + currentStage = LIST; + nIntervId = setInterval(redrawScreen, 100000); + }else if(currentStage == LIST){ + currentStage = INFORMATION; + clearInterval(); + } + layout.render(layout.background); + layout.render(layout.buttons); + draw(); +} + +// timeout used to update every minute +var drawTimeout; + +setInterval(draw, 15000); + + +setWatch(RedRectUp, BTN3, { repeat:true, edge:'rising', debounce : 50 }); +setWatch(RedRectDown, BTN1, { repeat:true, edge:'rising', debounce : 50 }); +setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); + +layout.update(); +layout.render(layout.loading); +layout.render(layout.background); +layout.render(layout.buttons); + +draw(); +`; window.open("https://www.espruino.com/ide/emulator.html?code="+encodeURIComponent(getApp())+"&upload"); }); From adfeb5e535564f8851fcdc080e2fa10d9acced9a Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 21:00:21 -0800 Subject: [PATCH 0788/1062] Update custom.html --- apps/schoolCalendar/custom.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 6c288d600..76388c01e 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -25,7 +25,7 @@

    Create your events on the week shown. Keep in note that your events repeat weekly.

    -

    One you have created your events, Click or try in

    One you have created your events, Click . or try in

    All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.

    @@ -444,7 +444,7 @@ draw(); }); }); - document.getElementById("upload").addEventListener("click", function () { + document.getElementById("emulator").addEventListener("click", function () { //Cacultate data: var calendarEvents = calendar.getEvents(); let schedule = [] From 618c5b7a10ebe610205ab463c395f65948e5a763 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Sun, 21 Nov 2021 21:02:29 -0800 Subject: [PATCH 0789/1062] Update custom.html --- apps/schoolCalendar/custom.html | 367 +------------------------------- 1 file changed, 1 insertion(+), 366 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 76388c01e..49e0acb13 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -25,7 +25,7 @@

    Create your events on the week shown. Keep in note that your events repeat weekly.

    -

    One you have created your events, Click . or try in

    One you have created your events, Click .

    All day events are not supported. A feature that lets you get the calendar from your watch will be added in a future update.

    @@ -443,371 +443,6 @@ draw(); ] }); }); - - document.getElementById("emulator").addEventListener("click", function () { - //Cacultate data: - var calendarEvents = calendar.getEvents(); - let schedule = [] - //-------------------- - for(i=0;i minuteOfWeek) { - return currentPosition; - } - } - return 0; -} - - -function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} - -function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} - -function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} - -function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} - -var currentPositionTable = 0; -var numberOfItemsShown = 8; -//Table Positions: -var rectStart = 45; -var rectEnd = 65; -var rectStartX = 10; -var rectEndX = 210; -//Scences: -LIST = 1; -INFORMATION = 2; -currentStage = LIST; - -function splitter(str, l){ - var strs = []; - while(str.length > l){ - var pos = str.substring(0, l).lastIndexOf(' '); - pos = pos <= 0 ? l : pos; - strs.push(str.substring(0, pos)); - var i = str.indexOf(' ', pos)+1; - if(i < pos || i > pos+l) - i = pos; - str = str.substring(i); - } - strs.push(str); - return strs; -} - -function updateMinutesToCurrentTime(currentMinuteFunction) { - if (currentMinuteFunction<10){ - currentMinuteUpdatedFunction = "0"+currentMinuteFunction; - }else{ - currentMinuteUpdatedFunction = currentMinuteFunction; - } - return currentMinuteUpdatedFunction; -} - -function renderBackground(l) { - g.clearRect(0,0,240,20); - g.drawImage(getBackgroundImage(),110,130,{scale:9,rotate:0}); -} - -function renderTable(l) { - var foundNumber = findNextScheduleIndex(); - var yellowIndex = 3; - if (foundNumber < 3) { yellowIndex = foundNumber; } - for(var x = 0;x<=numberOfItemsShown;x++){ - g.setColor(255,255,255); - g.drawRect(rectStartX,rectStart+(x*20),rectEndX,rectEnd+(20*x)); - } - g.setColor(255,205,0); - g.drawRect(rectStartX,rectStart+(yellowIndex*20),rectEndX,rectEnd+(20*yellowIndex)); - g.setColor(255,0,0); - g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); -} - -function renderTableText(l) { - var foundSchedule = getScheduleTable(); - var foundNumber = findNextScheduleIndex(); - var startNumber = foundNumber - 2; - if (startNumber < 0) { startNumber = 0; } - var endNumber = startNumber + 8 - (foundNumber - startNumber); - if (endNumber > foundSchedule.length-1) { endNumber = foundSchedule.length-1; } - - - var scheduleHourUpdated; - var scheduleMinuteUpdated; - for(var currentNumber = startNumber; currentNumber<=endNumber; currentNumber++){ - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[currentNumber].sm); - scheduleHourUpdatedStart = foundSchedule[currentNumber].sh; - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[currentNumber].em); - scheduleHourUpdatedEnd = foundSchedule[currentNumber].eh; - scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20); - if(foundSchedule[currentNumber].cn.length >= 15){ - scheduleDecriptionUpdated = foundSchedule[currentNumber].cn.substring(0, 20)+"..."; - } - schduleDay = updateDay(3,foundSchedule[currentNumber].dow); - g.setFont("8x12"); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(currentNumber*20)); - } -} - -function buttonsF(l){ - if(currentStage == LIST){ - g.drawImage(getDotIcon(),223.5,115); - }else{ - g.drawImage(getMenuIcon(),223.5,115); - } - g.drawImage(getUpArrow(),225,30); - g.drawImage(getDownArrow(),225,215); -} - -function draw() { - var currentDate = new Date(); - var currentDayOfWeek = currentDate.getDay(); - var currentHour = currentDate.getHours(); - var currentMinute = currentDate.getMinutes(); - var currentMinuteUpdated = updateMinutesToCurrentTime(currentMinute); - if (layout) { - if(currentStage == LIST){ - layout.time.label = currentHour+":"+currentMinuteUpdated; - layout.time.x = 147; - layout.time.y = 10; - layout.render(layout.table); - layout.render(layout.tableText); - logDebug("Rendered"+currentPositionTable); - }else{ - layout.time.label = currentHour+":"+currentMinuteUpdated; - layout.time.x = 147; - layout.time.y = 10; - layout.render(layout.info); - logDebug("Rendered"+currentPositionTable); - } - g.clearRect(150,0,220,35); - layout.render(layout.time); - } -} - -function RedRectDown() { - if(currentPositionTable > 0){ - currentPositionTable -= 1; - if(currentStage == INFORMATION){ - redrawScreen(); - }else{ - draw(); - } - } -} - -function RedRectUp() { - if(currentPositionTable < numberOfItemsShown){ - currentPositionTable += 1; - if(currentStage == INFORMATION){ - redrawScreen(); - }else{ - draw(); - } - } -} - -function renderMiniBackground(l){ - for(var i = 233;i<=240;i++){ - g.drawImage(getBackgroundImage(),i,123,{scale:10,rotate:0}); - } -} - -function renderLoading(l){ - g.setFont("8x12"); - g.drawString("Loading...",240/2-20,240/2-20); -} - -function renderInformation(l){ - var foundNumber = findNextScheduleIndex(); - var foundSchedule = getScheduleTable(); - var startNumber = foundNumber - 2; - if (startNumber < 0) { startNumber = 0; } - - if ((startNumber+currentPositionTable) <= foundSchedule.length-1) { - scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); - scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; - scheduleMinuteUpdatedEnd = updateMinutesToCurrentTime(foundSchedule[foundNumber].em); - scheduleHourUpdatedEnd = foundSchedule[foundNumber].eh; - scheduleDay = updateDay(1,foundSchedule[(startNumber+currentPositionTable)].dow); - g.setColor(255,255,255); - g.setFont("8x12",2); - var splitClassNames = splitter(foundSchedule[(startNumber+currentPositionTable)].cn, 15); - var currentY = 5; - for (var j=0; j < splitClassNames.length; j++) { - g.drawString(splitClassNames[j],13,currentY+50); - currentY = currentY + 25; - } - g.setFont("8x12"); - g.drawString(schduleDay,13,currentY+50); - g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); - } -} - -var Layout = require("Layout"); -var layout = new Layout( - {type:"h", c: [ - {type:"custom", render:renderTableText, id:"tableText"}, - {type:"custom", render:buttonsF, id:"buttons"}, - {type:"custom", render:renderBackground, id:"background"}, - {type:"custom", render:renderTable, id:"table"}, - {type:"custom", render:renderMiniBackground, id:"miniBackground"}, - {type:"custom", render:renderLoading, id:"loading"}, - {type:"custom", render:renderInformation, id:"info"}, - {type:"txt", font:"7x11Numeric7Seg:2", label:"00:00", id:"time"}, - ]}, - {type:"v", c:[ - ]}, - {btns:[ - {label:"", cb: RedRectUp()}, - {label:"", cb: l=>print("Two")}, - {label:"", cb: RedRectDown()} -]}); - - -function getBackgroundImage() {return require("heatshrink").decompress(atob("j0ZyEKIf4A4gIB6gQB6gYB6ggB6goB6gwB6g4B6hAABAYIBHBZIVLAK8IhIBXgAThhQB6hYB6hgB6hoB6hwB6h4B6iAB6iIB6iQBHiAJOB54XSiYB6igB6ioB6iwB6i4B5A="));} - -function logDebug(message) {console.log(message);} - -function changeScene(){ - layout.render(layout.buttons); - if(currentStage == INFORMATION){ - currentStage = LIST; - nIntervId = setInterval(redrawScreen, 100000); - }else if(currentStage == LIST){ - currentStage = INFORMATION; - clearInterval(); - } - layout.render(layout.background); - layout.render(layout.buttons); - draw(); -} - -// timeout used to update every minute -var drawTimeout; - -setInterval(draw, 15000); - - -setWatch(RedRectUp, BTN3, { repeat:true, edge:'rising', debounce : 50 }); -setWatch(RedRectDown, BTN1, { repeat:true, edge:'rising', debounce : 50 }); -setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); - -layout.update(); -layout.render(layout.loading); -layout.render(layout.background); -layout.render(layout.buttons); - -draw(); -`; - window.open("https://www.espruino.com/ide/emulator.html?code="+encodeURIComponent(getApp())+"&upload"); - });
    From 1a03cb98f6cf5f51ece24a2426797187e35f5a77 Mon Sep 17 00:00:00 2001 From: David Peer Date: Mon, 22 Nov 2021 07:15:42 +0100 Subject: [PATCH 0791/1062] Minor design change --- apps/lcars/lcars.app.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 819caa8ef..c30cdfda6 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -58,32 +58,36 @@ function draw(queue){ // Alarm g.setFontAlign(-1,-1,0); - g.drawString("ALRM:", 30, 107); + g.drawString("TMR:", 30, 107); var alrmText = alarm >= 0 ? "T-"+alarm : "OFF"; - g.drawString(alrmText, 70, 107); + g.drawString(alrmText, 65, 107); // Draw battery var bat = E.getBattery(); var charging = Bangle.isCharging() ? "*" : ""; g.drawString("BAT:", 30, 127); - g.drawString(charging + bat+ "%", 70, 127); + g.drawString(charging + bat+ "%", 65, 127); // Draw steps var steps = getSteps(); g.drawString("STEP:", 30, 147); - g.drawString(steps, 70, 147); + g.drawString(steps, 65, 147); // GPS - var gpsText = Bangle.isGPSOn() ? "GPS-ON" : "GPS-OFF"; - g.drawString(gpsText, 115, 107); + var gpsText = Bangle.isGPSOn() ? "ON" : "OFF"; + g.drawString("GPS:", 115, 107); + g.drawString(gpsText, 149, 107); + // HRM - var gpsText = Bangle.isHRMOn() ? "HRS-ON" : "HRS-OFF"; - g.drawString(gpsText, 115, 127); + var gpsText = Bangle.isHRMOn() ? "ON" : "OFF"; + g.drawString("HRM:", 115, 127); + g.drawString(gpsText, 149, 127); // CMP - var compassText = Bangle.isCompassOn() ? "CMP-ON" : "CMP-OFF"; - g.drawString(compassText, 115, 147); + var compassText = Bangle.isCompassOn() ? "ON" : "OFF"; + g.drawString("CMP:", 115, 147); + g.drawString(compassText, 149, 147); // Queue draw in one minute if(queue){ From fe2ccb83561b6aa47e1dc7cab86cd6b0aacaf912 Mon Sep 17 00:00:00 2001 From: Barnaby Gray Date: Mon, 22 Nov 2021 07:18:47 +0000 Subject: [PATCH 0792/1062] messages: add telegram icon --- apps/messages/app.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/messages/app.js b/apps/messages/app.js index 6c7cf5fc9..987d9184b 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -61,6 +61,7 @@ function getMessageImage(msg) { if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); + if (s=="telegram") return atob("GBiBAAAAAAAAAAAAAAAAAwAAHwAA/wAD/wAf3gD/Pgf+fh/4/v/z/P/H/D8P/Acf/AM//AF/+AF/+AH/+ADz+ADh+ADAcAAAMAAAAA=="); if (s=="twitter") return atob("GhYBAABgAAB+JgA/8cAf/ngH/5+B/8P8f+D///h///4f//+D///g///wD//8B//+AP//gD//wAP/8AB/+AB/+AH//AAf/AAAYAAA"); if (msg.id=="music") return atob("FhaBAH//+/////////////h/+AH/4Af/gB/+H3/7/f/v9/+/3/7+f/vB/w8H+Dwf4PD/x/////////////3//+A="); if (msg.id=="back") return getBackImage(); From 3ee03720eb47ab6e2b02758434e82c628a0be195 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 22 Nov 2021 11:08:10 +0000 Subject: [PATCH 0793/1062] fix build issues from minified code --- apps/.eslintrc.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/.eslintrc.json b/apps/.eslintrc.json index 9d4f8a4aa..d656c2555 100644 --- a/apps/.eslintrc.json +++ b/apps/.eslintrc.json @@ -152,6 +152,8 @@ "no-prototype-builtins": "off", "no-redeclare": "off", "no-unreachable": "warn", + "no-cond-assign": "warn", + "no-useless-catch": "warn", // TODO: "no-undef": "warn", "no-undef": "off", "no-unused-vars": "off", From 46383d4d5547bca38a2a07ea01d67afa0166bedc Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Mon, 22 Nov 2021 13:18:01 +0100 Subject: [PATCH 0794/1062] gbmusic: fix "previous" button image --- apps.json | 2 +- apps/gbmusic/ChangeLog | 1 + apps/gbmusic/app.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 8d90a1d1a..e8a4f9737 100644 --- a/apps.json +++ b/apps.json @@ -3649,7 +3649,7 @@ "id": "gbmusic", "name": "Gadgetbridge Music Controls", "shortName": "Music Controls", - "version": "0.06", + "version": "0.07", "description": "Control the music on your Gadgetbridge-connected phone", "icon": "icon.png", "screenshots": [{"url":"screenshot_v1.png"},{"url":"screenshot_v2.png"}], diff --git a/apps/gbmusic/ChangeLog b/apps/gbmusic/ChangeLog index 42ef60ab2..9cebf0a31 100644 --- a/apps/gbmusic/ChangeLog +++ b/apps/gbmusic/ChangeLog @@ -4,3 +4,4 @@ 0.04: Setting to disable touch controls, minor bugfix 0.05: Setting to disable double/triple press control, remove touch controls setting, reduce fadeout flicker 0.06: Bangle.js 2 support +0.07: Fix "previous" button image diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index 328b4a1ae..f514dfccd 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -303,7 +303,7 @@ function drawControls() { l.up.col = cc("volumeup" in tCommand); l.down.col = cc("volumedown" in tCommand); } - l.prev.icon = (stat==="play") ? "pause" : "prev"; + l.prev.icon = (stat==="play") ? "pause" : "previous"; l.prev.col = cc("prev" in tCommand || "pause" in tCommand); l.next.icon = (stat==="play") ? "next" : "play"; l.next.col = cc("next" in tCommand || "play" in tCommand); From eb20f15537d8b6be7226b2ae49fc6e6dd0474124 Mon Sep 17 00:00:00 2001 From: David Peer Date: Mon, 22 Nov 2021 16:33:42 +0100 Subject: [PATCH 0795/1062] Compeletely new design with icons for GPS, HRM and Compass. All Icons are open source and mentioned in the Readme.md file. --- apps.json | 2 +- apps/lcars/ChangeLog | 1 + apps/lcars/README.md | 3 ++ apps/lcars/background.png | Bin 1497 -> 0 bytes apps/lcars/bg_large.png | Bin 0 -> 4477 bytes apps/lcars/bg_small.png | Bin 0 -> 1672 bytes apps/lcars/icon_compass.png | Bin 0 -> 5469 bytes apps/lcars/icon_gps.png | Bin 0 -> 4253 bytes apps/lcars/icon_hrm.png | Bin 0 -> 2131 bytes apps/lcars/icon_planet.png | Bin 0 -> 3529 bytes apps/lcars/lcars.app.js | 65 ++++++++++++++++++++++-------------- apps/lcars/screenshot.png | Bin 2434 -> 2838 bytes 12 files changed, 45 insertions(+), 26 deletions(-) delete mode 100644 apps/lcars/background.png create mode 100644 apps/lcars/bg_large.png create mode 100644 apps/lcars/bg_small.png create mode 100644 apps/lcars/icon_compass.png create mode 100644 apps/lcars/icon_gps.png create mode 100644 apps/lcars/icon_hrm.png create mode 100644 apps/lcars/icon_planet.png diff --git a/apps.json b/apps.json index 8d90a1d1a..39c02dcb7 100644 --- a/apps.json +++ b/apps.json @@ -4288,7 +4288,7 @@ "name": "LCARS Clock", "shortName":"LCARS", "icon": "lcars.png", - "version":"0.02", + "version":"0.03", "supports": ["BANGLEJS2"], "description": "Library Computer Access Retrieval System (LCARS) clock.", "type": "clock", diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index 750e7ddfc..c8f9a262f 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -1,2 +1,3 @@ 0.01: Launch app 0.02: Swipe left/right to set an alarm. +0.03: New design with different icons if gps, hrm or compass is on. diff --git a/apps/lcars/README.md b/apps/lcars/README.md index 1a73a0d71..3acaacb4d 100644 --- a/apps/lcars/README.md +++ b/apps/lcars/README.md @@ -11,6 +11,9 @@ the [Pedometer widget](https://banglejs.com/apps/#pedometer%20widget). * Shows the number of daily steps * Swipe left/right to activate an alarm +## Icons +
    Icons made by Smashicons, Freepik from www.flaticon.com
    + ## Creator Made by [David Peer](https://github.com/peerdavid) \ No newline at end of file diff --git a/apps/lcars/background.png b/apps/lcars/background.png deleted file mode 100644 index 1ee4297c642db661ff1b3441ef7ba950eec72d70..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1497 zcmbW1`#Tc~7{@n@xt%UyUQNs?AsxA# zHa$wwL<{8w(?NLPZvM zgqGC!_1lo1NR(<*ycWkd+gJ$>hHoq(#fk) zLr#*bQ#P23x1u?)>qU$z&|pPITm7o2V(%y5%6q3FiXEm1)+OB-agv*xxqU*W?+2_&E#{>qVmBG75RC=YK~v7|*o)k1$aVa205hn>OKf3wHA zo@6FsSXuGW*yy)=-W}e7<~Z5p^z-C+@BCe#A3KDuf{xgnfTE3EJN; z6eOh>E>#*jk}WZCo2mpR$UGKP)j>E80Q5I?hj@3=b4vRk63;BOHAZeUjeT8dx$IEV zvN1lIn9-ezu)xF?giA+@ZLjStm1hj;zo6$B6tT@~ONSc=o5rO#D!YqE3T*3z-}Uf! z)Ez>w{wLfw!|!g!N_(Cpf1M}^nDjJB3Fj`~!#}s)d2ocMC0*QVVq6DIj}=g#z#E<+ zPJ?)w4y;q*y&B+{K5k2=D+IbPPL=5NjqMWC~IeV&wSi$Akn9<&6hociQ&Q> zsOPIaOH-P!B{#aXB5^TX{a)_7i|Fa}PWoEJQ4p@h3ck8`*$l!|aklqs{r6NgkSO?H zF91ngTF4AtL*J$5keYO^7uKxKegzG!Z@e3L+Kn4D2OKL$r_#AH?AGLuVwg$46U;P} zK@IycRst&NyX=gni4u)N1_?(yW}3*LKlA5!RIPYz@Xu5H)0qF-LD=+FDdUpmKxTdO z4!ds_byd*X`<4?CM)~(iIGF9a=h=KWiyGJA`^T|_@J}z>Hzugiu0>Appt7KkfGjMY z@p=C-jTQ8qmFgd%?3=`iS6Vo>N|C3}hBcku?>$@tVcc`W^bvAB_n4X!V0mcelHq=~ z*NT1-l+cwN6giI0IY%rb)-8@a(ES2iELz1`zes7ThV~Gv-bLBkI@&$7|6;XipMx-; z8ylfPQR)r06*a?$56X~*y1M7#A{!|7JR>9Yp3>8G#OG}hyFuAkkc#gHKzU+3>fJ)G F{0C>+p!fg) diff --git a/apps/lcars/bg_large.png b/apps/lcars/bg_large.png new file mode 100644 index 0000000000000000000000000000000000000000..250a4718d970e3370418c9f99f261e60bcdd2117 GIT binary patch literal 4477 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 Rw`E@o2#dknRvhq&{}<(U87u$* literal 0 HcmV?d00001 diff --git a/apps/lcars/icon_compass.png b/apps/lcars/icon_compass.png new file mode 100644 index 0000000000000000000000000000000000000000..f1fc46f2818d92f6b7b286b46d013d400dd7efc3 GIT binary patch literal 5469 zcmV-j6{6~iP)eVAQUeeXZNwf4(7XEMx8-bhFwjf4Qb5TQf}SV=Ho5NS(N zgqF6#r4s7Ho9Ok@VsAx7Xl=ELNTpi)Sf%y>jD!mU28b6F~r?sXYJp2eSdH3xAq9XrnaqG?JFh8DJrrQI1X5d zFc*(IUSN&I zvj!suBZ5f#BUKfric?3NI1-l-#c{*IQ-W@c620T_nKQS)RWiP5z^W>Hh6h)o=EFv0 zRp9$x;CXn)g1o`uRf%0fBaUfAQQ}m;i@0@%_RM%#MD)$b`=$Wz8X8(*;3jKiSt$s_ z_q+)V=FKTo$CaT`$ANl!ZdwR^U54JWV5X2nRf*!5TBD(f(%$1)Z>U+6jept z6|;J$fAiM@_~)u&PjzU$HFj;KR1#}i4D^pU)@>T+%a7IB9|OLm-DcgSKrwLaY|G|L zrXd0=zjlD<_ayCr9grv_K&%|%!v{|b_{fV6o214G{KMuNH{LhSa14Zoqvl%1RoFhN1;8m-1gH`gvNarSXRI!4(rEW&m7)j0k0Bm8W4f@;cEDF`U}!SV>c zw|ivd&^HCRTCMh3mpm8-{t2Py0fGYG+S1^(&+O-!JqptdR7?iYa`!6hyzQno^~It{V34AAT_O{1rhKZd8@eK2YO^d&kj|N!bXp?&l5)u6;+q zv_SaumPYERa?dR3BhOi25a9l8DO0HpYtxoj%rjiIB;fb&8Ee^eob^bdp-98=iB%=8 zJ1cC;mc{MIhI)G5GnGwG33%`D&~1VBFAsemkyM_)+BnYNZmL1W7QjjYYzo-=LfID% z#7qoBoh=7i_}t||mxaBfne)o*x&_)nL!qvGba}vy=a!l>)}x3hiN8Fnr}y$-8gPHL zdXX`5dpQgxPb5Bj|2Utxw}#hkIc&Dat7D{g=bI+8c)5d;cA#xf5t;~_K(jx^5TzN& z{FrFQbP9ZK3HpZ|@wnrL_XJ#bRwmPtWb0*e2NU!06~aG3JYtJ8P!LBWt@^zSwW6a&Ch;%Q@&tdtpLMp%+!(6pkjFOq-4G!7+0zkA1M4 z!)By@;**;i-1Te&6a=9Z1R^5042%xUe_g;D$Lj;n_8Cbpy|_E!&%Qp^wAo#C_}y=f z^X2!IIp-*^1=Q;VN?SN;W87)VTpRJ)0agmXZGrxc>6UxWudw#Pab6u$2n^~AANp2} z7xyG7C@}<{?bX;{e^9{vBSR~U$QsXB6o?Y#Pycz810rn>>OY5-vp0@&)Jy{&h2%me z2U%2U0ipyNu2YtYtvCT$|D8o1AAPLO_qHeLBnLnsjH+L#4jZ^h%0xb&H99C(!+3dJKRX@4|nzjp* z99wqXGd{?0Fujn1((bwI3U$ZLXNO#~A^_5CtPwSyoZj7YYQD;9GCVjue5Mhz%w$e# zV8n6L{k1G%koM4%JE_T7&B5lXNQde|%5Ed+ob&cc$&N=1|AJfYe?R}a{wR6}cD37i z(Y7-ZZhoXe|AAC{()G7Fd2o36%zXGJU}McSp7jI-Ram#Fo~4V0#KC5)<_{`*<4&-2 z*lCfyMv`{b0*l&qQy7o)p{H-+&b!{lkw4kQ;7LO zd$izLPmD1iZUa0pIyxU%m9x8lT=}OTG&(XFkimy<_Y>;PLfkH>W^%M^JMu>6^e?@_qHGi0ZgDSw>j3Z*1R!CSY;#1=bi( zGGXt0y21V!j-Kan)o+)Wb%<%rWYKd@#cUg+MKBq-Cd0HA4GQzBukeKj|B!W?F5>GAGblh5Y^e%2ayFX5C7Mg5(9-M!Cz~VWU{Sn;p;|S2y!5d>WvKKS~8_a5B zOjE9JKG}dk7^o|AdMrP@xQh+HS7uh1d3`kos3)z^Qv{lSjvT};y$2X22J7(gN9%mz$p*{c<}v0v*v(rfvz~$7qcEeCes0<*nJQ<$ ziyZIT^$36d#Pu98yfs}8YYS{x5^g=|Q`Ca4_M{~TJ)BUQX_*!XXWcowYmt!MqXstr zSHhnxH3)(+;>Y4G^8~jPESgFDqnDCGiYOCpcCyyyjkU${p%r-T0Q3Y<_Fz25A3DG# z+rP@?uii>I8P7_z6Twmn`O1l(=>RJ&u*HOAOrc!(p56PzwuC=k>ZKfrAet=o1db~f z8vlKJGNCLp;Q?*HCjpxuyZjXFa09A!P8<3G*Khq43&&rA4CG{Bg8=S2<_dNma%910 zbR;*OjdBT^M+(_Lo zPtIgft&19$%UVd_4m4nBtd+V7%=JX14+WL%Vm83Bvn^-;W`OmDtp}95w{;}@MRq$G zc3J2dt`m$l_`>Qr2r&KPYkB2_GjM*I>$;VHK=sl;A}sZ1VByI*XYu5m_e{=Y6L<$$ z5zbrev#8IIIOV~gNBrk52LeN5FrzD#OGJ7-MN5+9yrcCLurb2sOQtcW+hjy3=RQ#9 z{?}X!scIT;^Qti2%*583$J8PWx?dj%PG4|Evu1c^vSPy z25PEI1Lz9UTb)x@{>uU0du&Uk@86ctwgDspp-p?|BxEZ6xidp5fi!mr=ggYPGcUYA zBaTyfqKNY2n+QVKF>N6me&a*w>dZGCm6O78f=Vk7ycF~PyT?+MPT+z6YLH|W*cD1M zY8t3}DhxpixTlh;HVV)0aXi1j(O#s}%-AP{rNW79^s^WGoOevn1gom_4-7J=uaCj~ z`|+%0_KfL77yJoh-(1ga$KFVzR8AKx6AB4aIH;^Eu;IlheSTj1%0%lewPaJnkSD?} zRXrS_w+z!l;Xs?3O7q8U7~5J=orRKB{{EfqU|-$2gWdi8^mKPKy|0fbiYS#r!Vg}< z*K{e*?pxkswdkEq%3}LsrEpI%m+z3PD}cUoOJIRrMwOSFJPEvQrc8{fS(4utUyBB- ztw8}kw!~*qkF>z<*un0-{gg{3!Z4&#E;GHak3C~$*52jw*Za<(S!b0_b|*4AF^``( z|EO~0bitUmh1<)Xh^mI@>64++me@KQCPOnrolT+ zgIep=&aKV18EvWb=T7x0Sz63?fPL&0pH*KntbY{h#-vU!Fi`T+?V2cQf#@vXG6~p8 z*-hm=^$64aE+QD`o-uJ8KLMIWD`mye29p=bGKS|>gzK03O!GTHcYr_laKrl+m=?1G z{glHL?B4N^EAA_^_8ytRUtde>d&N&dx7lz3cyl(=L&b*`M|fCMVA+AFvf@aC zHUpDJaU4HkdV70^jPR6Xrw{89PCDE`qRj6SzHx4u?_F4-7C|k_+3ZkhO$(ZzJJoAv zHY|Y`@|9;RoO^qPM_w}R;U%x2be3w$9(l4bd1lqZT=9jgP73(hW!>DkqRh;a0jK=d zT*F~KlFf@E!j|6N-hE!Gk+RVk^V^9_gV%*8S)RT(reff}7b7lxphnaz;7Q87Y1iwI zEwL=OyVy3|5o+=^brXC6Q1kmF*9x&WM?5gD{Qliz%q|HR9pf{* zEDQ|7g-co`cVi5qvazWfqobqqoyaS(ORNGTHRZUQ>WqlVwkiw7JWD%Ez@kHhEo*%C z?%B!ifqtruF4jMF2#>rHwtyAAZ9Ptp6@L;hJXL~GWVRUhGp)<()D7rHdG6YPo^o2& z@Qh6?99gMUwi$q~uCDC{zMDI*?ohboWZNoq$Jz?$MJJYJGoM>#*}Hcquk9J&ju)nL z;f9$h*jz<(R`WXcByR>BJez9AIHBdTWIK(QoMh-JNoEy-flZZ4Wg8Ug(oSTZHF>;I zu3B#C@wB;H*L?3H(L#YvneM}4vzz<=eS}NzoyVl@{8!XXGHErvSF- zshbREBxEndbbD~sJ8e!q)`+UeI*LQ2zf&2l*M1ns@iLdivs)jF`9GVI=4%K9rn!Sx z%!;Nr8ti-7wcF){vP?nvB)d1TldJ1gVDnH^$RvnqsdHWV2fd9Hl|k{Zn=7i4-WSE0nGI$XSE2nsnRA(E;@j< z+3zIG0#}?Eu&_6auX*Uq-Vk{EY`A=xZAMFLtSbCv@$J2qD+9XA<;@n^=ot$No)K=l zz^4mMcic13@0x44<&2Q;Y>it9$}|gydhP1lbXW!OBCU#O^ZaBgUARAIUE!swBACYh8DmZ$Q@zyn)j);=)C|2@Bp!zuzXNDS-VXE}OS zIx8aJ8#9dR)t&ez^?hUY`bEwqx7VVmdHwU>i@9mD!>br(_gQwDy7n8li`}6(s?U~;gz14M+2`_$o{KUmg~kEt~I` z!|=+fa7_tVMC$c0T;m!0oNv<{8Q|I#mVaDhsiQCjb6MM?$bw%;{e`>s}z(YGF+vH#Hs z!_{(-_b#Ud4A3jGFNvb}1it-A;Q7t-tCtua{xgpgX3>1rfO^XIA0F*<#|sg0Jrht! zRX+o>Xu3(aJw#a4XWD?KYO8FoI-c5*u;wVgmFYFoXT5y}JanmL&G9YK1J5V$?SCXu zbXJeZ;K49?2fGN+-QB%MRo^XIzuOo&z1C;|aMUc}{>v<1`H|!A9&?P;08e@N#fbiz z7Ipz>*>D0$(xNoU!%DGb;IDspjEWVAfH=*dK5~X|`6&k9wrr#n1o*~$zwU>tdxFwI z!NS2a)OI;k!+I2bA#urNwMIjdLUmzqRJrwW$G>cGjKmab$+WL{fMaJGHecM004u*S z!t;9z`#*)nJ1#YoZeRJpiH0jrGtB5}iEBkD1%X;)zZ&}fl?PMfHv~9uWA*wU3*1tV z{M4pNn)coCUz59|5ci2EWYRATbme>O-^K8$@3oiZ~Zr(&NimHz>A^_ zRAjZPE=`>C<0K(*4yO+1S|zoX0D>`s5rZ`b&swZ8k%(*&)s3<5-%%0S_EyRGmI5v| zRqc!8c$um$1&&jZg`zrFp%*A+x77uPRAiT^z6|_SM4s_H?+Fok-Tz;BBklhKI~u{@ TJN0qL00000NkvXXu0mjf3t_;J literal 0 HcmV?d00001 diff --git a/apps/lcars/icon_gps.png b/apps/lcars/icon_gps.png new file mode 100644 index 0000000000000000000000000000000000000000..5619991c35fc2f5e87ad263e6599870b874452dc GIT binary patch literal 4253 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 literal 0 HcmV?d00001 diff --git a/apps/lcars/icon_hrm.png b/apps/lcars/icon_hrm.png new file mode 100644 index 0000000000000000000000000000000000000000..b6ba2ae193207b18d46f2ddc474327fe3e0bcc44 GIT binary patch literal 2131 zcmV-Z2(0&sP)BO|TU+8K*N6tAnwM4w=RnVp2S=gk>&C>8 zWOgn7W`z&qw-Y9#lp@X)3P6%Mob_X4Sbwd`Cx3$)Sb*3N&*j0F%dTso7!Vw&etiz= zF|2@#oo)TvUG8l22^I!9Fu+^&wbXTW_E~?!X65%cYkc-M=z$v@Ha1?r!u77+52YJ6 zE3ej7)8MfA>~GKm12oueyn4Ob1Nz_hU~k!dhlK$K&YU@&y}RFK*Ppf%9~X09+ivJD ztQ}mnb_4_q0}TA*7ruujGLi}7AK{s&miKGlV7Kv7?PXr9y-a-ourPqY;~yDEd|V7& zHXBMQ(o<7>TEnY*pFlNW!1J;)5fP3ZKSe`B6C*8=L`RJedbKVtZBQj!zt z>guAjtei6y=K)AhO$j6%zvMS?XsDS5*>m0Hd2=)UZgg(eEDQz%pPfESM@J{8-M~}* z_T{hq240Xom$0xEG4cjfhfX9s+L)x^d zbaZrbwCE%!PL%@QyS1iiJiq!WL}g!*=naZx9_d zfg3k&(%#-dM*1{jW1@R?=Wd-jdmg0}X{l3t&Bu!u%_ll)Ja_N5qLgBOc9z>lMEu%= z^XYwf+m>H5H966}lX>vaCjhjzwsH2{MP_8AF?&`9hmRETtBpTrUe;{4?XKPVBqb*B z#IhV#e0LdHvuC1|>IHnQ&NYrljkFLIHJ;kqI)3r;S{_}Pjiwm@c>CR*JooHM#*P`y zoXiOp{0wTin)D+_5V)^Fc1sIJ+ zm#@+?4iz5ZSkWm&gw4O)z_gSkcWj!ES@tQzYu0acXUyqzdeogbRmzJ$`5~DzGccJ< zhzNh)xtra4K0+ziBCw|R8fE1bWX_xp^zP2xI#Y3;#}>^eJuQW>urNe~j}I2|=C&Pf zUx)u$#ERwL!Duw{`4=^8*tnVImfP-nU4XrTcI#&SE&lcSRVGc0BWw0d>~=c`4j%3m zz^)JWkerx6M`tGm2MYTHc8%i{ES5+BN=nN;d{tIm;Ww{sB|a{OkM`~7_MJPvA^(+Z4MN`0l5{QK#F*u>LQUjqX^PvV1FeKB|)!&YnOLviH6o4f)$9CokA z1s(={J%%54{oesgL1m@M(%RJm5BI?|l(IHOgbWv@lvaHDLcJ#1$bpV2m{LyFJUb(K zlqMohki3BoD40@Cr9kD0h|>sQ2{&$NZnbGjcuo@;+Mffz8dD0T(4?P9T`8qRaOyqx zJC$k`c)$I2*ememLzX~(zB1_9pxInzYIii<7LohDs3@iEW(l)&b!E5f*?w-WIC_?Y zzLd)Q@X$n~kt@E~iZ~K-pU9~WW;{WkmJ9~x5?^f{4uj9OfhwrL1qB5rV4d%GsCD=5 z37UciylK-W?RKYOhlrN!b1<4D-sv>Hou8kt>vN>1bRWY?DQ)lmBWX^X?R60ew2Kys z(fIPpZ!fBFO`G~jH{i<3N>fvt{dG{QP+0n#hWe9IN(s`eggi^Q@s%l4rucSxv>)lV zr`6nM|D7hX7DSh?@!l6^5m?jIW(UyaUpKmpVWpI&P^`l=;g1c5)u-+L_`2wEsU$X!J002ov JPDHLkV1l0r3UvSg literal 0 HcmV?d00001 diff --git a/apps/lcars/icon_planet.png b/apps/lcars/icon_planet.png new file mode 100644 index 0000000000000000000000000000000000000000..fc1b733545de402f56c49ecb30b392035ad75540 GIT binary patch literal 3529 zcmV;)4L0(LP)6La=h>q|Z zLm0Y6X;PIR>kpdI>9Ha;?y~+3Clatq&)afxBPPRWObet;rY!E z%oO!)65-P#B6eOM8uTzbtB_%lDvG`C6go4STaX1JE^@pCv%eha^N*S0edHv6xu}O) zH)Ys5{Cvu*|Mjt?-n0B;-#J*Q?gbNG*t~IrDo+?A)g$+`dg{B&)V|)&SnxV zstO{jC|VoCO?$+jCfpd2Dxdl5OPTTN>o49jBOjR)5?*r0-`5W%qOTb-t8yw<9mihX z0e(ALW8_E)&Pz>5cQ7A)G7*jws7z!i>CdwKON_Z@SuoJD?Z5AvIg!Y52`}C7Xm!H# zZ5A=h$D5}$@l0=^K(`E z-pQI}-8A{mtr`JtWd?JkB&XDvXSeU{^gepcZQsip9&$P@Q{KHPU$OuabCFoyJ}^c0 z^0X{0O3lMb7$^~J50DLanc-Hl?uzBMK19#`GeSy$` zhh5%1e33RF;MPUCIV;&Pi=-?s`DG%XJ^sp9?#ebchVcC62WG1BL@sY!4CsXe;3}r2 z9@AQTmb8mFB~&A`i7`)Q+iS{9ncvu#8!P0X;QB<$gD|8&jLMv5WA)wt&Ky% zz(C4#n^Pgg4i^}XMK3u64n#Fo3N^Wwkqzacbdq2Zr3LRY5fN9-#r_L#`e92;i$a=( zFS_HwH3BP(#5MK8UO%{so)^L1a0+kjaqPT4)J-j0MX?w6WR#EZ#~e;2Gl?A`#vkaP zlnw#I@-KX3;1eU)X9?f_x&8OASlJ;=t=!B+uMeVWi>M-jy}S$W)+2b=pTw>{i7x8R zctjCe27M$(sqqFq^k|}B6-0z`W9}WfHUM9HcEbh1Q1Ygwmvs>;o!oxO`Z5HDvP3?g zqD@h>K9)80>9>ZrA}*RnI)U;sU8ojr5WjHa&dvADgJHsf3hxe)R1M@f#j~&)g-QN^ zd*Ti!l9@l&G2-lE5FND&ld8O@ef)=>$W^7j#ZPR1ZyA&aRk4*s;v>aru| zkQ{af#}bB;@_xeVV;&{=CABSj;^K?PeJfpK&RG}06lo7@$U}~Ypx2Ld2aq#m}lX$>! z;v6T=X)lgw!Vs13sKBO~{E*?me9loYwI8tzngR_t3%e1Fdg1tcXRY zo=#3DB<^5duTPbg3wqHRv2)2vX8`A&+OzvKH}s(U`j)00MojZ`D_jf+f}ALU6D6sx zNytmp+i^2N(?10jcDh(?WfI%g7^Sv0PF-V!`i3a&$4c3=yOJV*MUfMdG|->dS>Rcy z$UANzkk}qi$dC~;_B1*T#wblq5$YNv)HOy4*F{rV*)YHJ=k{^lh5fwwyG9~KsJ4m? zYc=5$whO?*=5K5jG1q0A=s1=I7xl5^(oRY%ChjT4qYnFaS9ADa86BsC=%2{q6Tzzf ze(y66U6V3}@;t&A!hwLtC)XdMyej{?HW2c-cts}{U)D)<(8tk3WkjP6K3DPkEki?w z_gcdw5``_tDMNW4hFd(ICr>+~-xd)kea3aG+Y2I_0fA6if(1*?j2W6WyPuYqXVTd@ zy*c{`RMm5A@@W`u#CNx~^o1ASaFvM6ADMLV(jFEq>z)=L6bO3EUD!(^>eAT};7{P_ zUTXW*!~YCvWw(evro%nZg)=;wuj$J;h%Po9SGnqjc2=)x$3KlFsj8}29vit%rz8vh z>KogPF)M#~YmBSTvqaV7Ln5>>$Z_JuY|&?+zsJvSp1)vPyt}G>>w{;$xsu@=E!}e{ zB41NfkM^d1hmbg2BIVpB4cx^{R#8nO6J;}nqr7T}6<2l?U9O@k=+{Y0NHbE7uH7vM z!;4ofs}8Z~qjS8BhXZ10lmJVllqHl=hD#jF0Gab9i|X1K#|~ByEoO>q^@R_fefV4H zx5h~KlHB|LDOs&bRkp)AL8uUy%c)=m<@Bk?ej@BA!m%kVdogguHK+K)+cPo?1U>h0 zWpA&KSUkV9P*r=Aj(6|q`&r%6;@dZFcID@bo& ze%%;FpUg%U#_0w}`;vcitViCklZ+U1rG({_PwwF?IDDX-eB!mLZuR=ld@0v>&JFRs zXSOG|cxzs0IbuHUbEuzNtvCf_g96MXKpPR_Ofk+Xy!A%IgbvNB?l6hK$M^ovqd8B{ z$MpamyIZ2poNJ$ZWuO11|N6jf0GL-}@#k4iIwDl!@&N_*mPeueXc;^ISj~7AwyHb) z&d^P}pM7ZD_?DMbTHL&`##4PxjJduls9ZhEV^*bdUWH{=rE*#0*n_SSs48#K$2NKj zc44ZpcSjBXyR8mubE~SVTO|=#*Y^AaT@%U7+Y1kWiukxezL5EKiSqc`Ay&;EKOUdd zINRvq5c&1Jy`=E=)&|=4ROOzes)|18^`H6DzRlmC@YD$=k+m;q-uPKTzb|6WYO=K6 z^h3NPsXtEO3e;_7fqB<@J|la$tW&Zdt0mir$mU8|T8L z%7c09HlAl(-6Y658MgMn{6&mw&!2h`;Hh$mD0lv(n!dtn8xvjpw4~ z2iGX_<)@m5K6XjHopLAt-4lk-KbL9_sHz9NESAUGe)Z56rubb|Q7hiVFMf4yZ9qO_ zjI1-lG9#1ehr+Wvoy0%<&fkjiyknD3@A=P1{$Kr3D=XSxy1%Z}k!u~16#`2zGTT6d zg<69e0E<{bJ5@Ptl>-7hJ*9PDxBKR0w>?_?dJ*_P^6yNC)4qXA00000NkvXXu0mjf DcMa0Y literal 0 HcmV?d00001 diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index c30cdfda6..f98e74e72 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -6,8 +6,30 @@ var alarm = -1; var img = { width : 176, height : 151, bpp : 3, - transparent : 0, - buffer : require("heatshrink").decompress(atob("gF58+eAR14IN1fvv374CN7yD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AH4A/AH4A/AB1z588+YCN+RBuj158+eARyD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4AUhyD/gEDQaHz4BCuQaNAIN0PQaHIIN0BQaF5IN0AQaHPkBBug6DQ8iEvQaE8yBBuhyDPAQNAINsBQaACBkhCuQaACpVo0cQaACo4CFGjyD/AAMPQf4ACQf4ADgiD+AH4A/AH8J02atICIwEAgPnz15AR3gEgM27dt2wCTF4IABgYROgN9+/fAR14ILsaQBKDakwjKF5oABKZ6DwgxTPQeEmQf5cPQeMBLhyDxgJTRQd0JKaKDuhKD/gENQf6D/F4VNQf8AKaKDvKBYnBAGZQKzBB1QZOwIGqDJsBA2QZJA3QZGYIPCDH4CD/0xA4QY+wIPKDGwCD/tpB6Qf6DHthA5QY1oIPSD/QY9gQf/bIPaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/AF8JQYgCdsEHnnz54CJgIdLwEAhqDEATtggPnz15ARHkgIdLIIKAgQcCAgQcAA/gAA==")) + buffer : require("heatshrink").decompress(atob("gFz588+YCbEYdx48cATMHz1584CcIAUCpMkyQCZQDqDjQDqD/QdOatOmATKDDhEgwQCZNAZmDAHKDC8hB9Qf6DE8CD/55A9QYegQf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/ABthw0YsCD9ps06dIQftp02aQf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6Dojlx46D+AGaDNAGaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qc9hw0YAQgJBg8cuPHASfAEoUaPQQCIQZkE6ZTCAQgLBnPnz15ASd4E4UTEw4CDQZcBLJILBp88+fPASfIQbcNLJKDqoBBKLJSDpzBAKgZZKQboAXLJaDdAC+TQf8AQBCD4QBCD/Qf6D/Qf6D/Qf6D/Qf6DKDzQAiPQWYIPqDC4CD/fwKD/mhB+Qf6D/AAcYsOGIPwA/AH4A/AH4AHyVJkhB+jlx46D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6DlwCD/48UQf8kyVIkGChACeFAImBFIQCSkCDCAUXEgEB02atICVLKxoPQYM06dNASqD/Qf6D/Qf6D/Qf6D/ARo=")) +} + +var iconPlanet = { + width : 60, height : 60, bpp : 3, + buffer : require("heatshrink").decompress(atob("AA8Dhkw4YCB4AVOoMEiACEChgpEAQcckAWLiEAwgsDAoMwoAUJFIwsDuJcJkGCgEBFIImBhEgwky7YtIFI4sE48cFhSACAoIsC2XbtuwQZwsGuAWFEYOCQYIFCwAvBggsC2hyEbQoCFFIIsCRIsCFgQCEcAUs2wsB23QWBsMmIsKFIoCBkLmBpctFga2EFhQpDAQawEFgmAAQMCiHLFgtsbphZHcYYsGwkQgACBoaGEAQIVBuAsKWYwsCmSGGFINBWYbgCFgWwfYIsihmy5dsF4QsO4YsGnmyFgwpCAQcy5ckcwIsQcYK5EFhACBgIvGXIWQFh6MFFhUhgDjBFglAggsUAQIsBoAsHXJEAwEAFi2wLJACDwAsEBAMcFKIsDgApJAQcAFgQICFKQCDFJYCEgAFDDopfQJob+BRhosEjlxfwNxFhpHCwmy5csAQJ9BdJUADQIpCAQosLgEhFIQCFQAgsFgExFhHHFhcztgsHljsHwAsMXJAVBgEDtu27YsG2AsJgIsKuAsNAQJcHF4gVCgEHFhJcG4AsDposDLg+QFgQrDCwIsQCwgABFga5GWwQUGhjjOCwykCmwvBFgkCWAgAG4YsHWAwAGgQsCRgfYFgQVJgcMFhIVJAAMEFgvQKxIAEFg4VNcwRcCmnQBw4A==")) +} + +var iconGps = { + width : 60, height : 60, bpp : 3, + transparent : 2, + buffer : require("heatshrink").decompress(atob("pMkyQCHy8pBZICJpe+6QURAQOPnmkFiVJ3//CiUl589+wIElMiC5dN2///oIDymSogsKkYsB+/aBAdJk4sLtu+///+gICzgsMufPnn379pBANc/JZLyYsB/YtBWwMm/YaCa4UcFhHz7990mXnhHDyVIMIQXDzdvFgPvFoNJ336VQnHglwDonzFgXPvv3RgI7EDIYsIcYP/3xQEuAsBuPCFhC2B75QExLTFFgy2GUINwoMciHHhIsHWwoXBWQtSFg62EB4MciJZC4a2DWYa2FBYLiHcAQsFWwYsEhgsBhi2CFgy2B+xZIWwWTtosFWwP9PoVwoccmHHjkxWwMjFgy2B7QnByIsFWwQsGWwshLILjBFgK2BkIsGWwtEFguSqmRFgy2Fw8MmKGCWwNJg4sGWwsSFgtkyUWFgy2GLIkeUAIsHWwJxCWwuW/ioBrYsG3/6CQS2BFgc8/NkyDjHE4i2Dk3O7fyBINvFgntYwshQwNxsmc+fny0kx4sDKwYCDWwVypM79/JSAW7FgIUGWwUcufEzxWBjgOGAQ8Stdv5Mk94HBChgCByQpBj1548EuAsNAYOv38kdYUlFhgQCPoNxFgNx4QsLpQDBye+/IKDygsLBgN5888uFBjkQ48JFhVIxIDBpwKFqQsKoKDB88ciJZC4a2LAIdCSJAXHkFBhlZ8QsChgsBhi2KRAWRkgNGWxVDhp0CuFDjkw48cmK2JpD3BAoIuBVI77MkJZBuAsCWxgCCogHGWxYCCw8MmKGC4cdFhtJiQsUyUcLIgsPWwwUOWwQsDChy2G0gsPWwKGBuPSFiC2DlIUQWwUcugURWwSwGA=")) +} + +var iconHrm = { + width : 60, height : 60, bpp : 3, + transparent : 2, + buffer : require("heatshrink").decompress(atob("pMkyQCTwArTiVMmHDhgCRkhYUgQstmPHjlxARws/FikeFikLlmy5YsSz158+cFlEeFikAFgPAWaYsBQx8P5AEC2CzQ/jgUv/4cCkf+IsMgfwAon/Fh3/EYn/Fh0f/wyFn4sOQAgsP/4sCh/4j+An/ggACCFg9/VoYCBL4cf/4sIgf8h/AQYX/wEHFgX8Fg4gEKwIFCGoP/AoP/+IsEgP/8BWB/6DBEwIsBGQS5DcAwpFAQZfDQwwvDXAKACR4UDwCzIFgKtFv7pFFg59BkAGEL4LgLARAsOABwsOAQos/Fg0wFlgaBASIsskv/ACf6rnz588ASOlFitIFic9kmSpICFMQf6BYwCKrjaCCiAsXjgsCugsQv5+D9IsPw4sDuPSChuXVg3+Fhi5IVpICD37aHIhgsWAR4A=")) +} + +var iconCompass = { + width : 60, height : 60, bpp : 3, + transparent : 2, + buffer : require("heatshrink").decompress(atob("pMkyQCFr//AAOkBYwUMC6FLCgoAB/osLl4VHAAIsK34OClmy5cs+YHC6RWL8gKEkofCLhF///8FIWz/YvCn4WB9JBI/1JsuWrN/21Zsp5DIgxBBEwOzFgRcC54FCIgyDB/mSrN9FglnAoMnRI2X//yFIXbQweyAoXPKALFF/ckEYJZCAQtkEoLOE36DDARSJBOIZuB5JWCFhFnySrBOIRuB9myVoYCGR4RxE//2WAKtCFg9ty1J//0boZWLF4jjCOoKwBEwYvGkIFCn6eBTYIpL5+QmQsDTwKbBFghQBAodmpMJkrjD6QsM2MkigyE/sl//kz/5EwNbbQeWpmSpCXBy3/sv//Vf/wsJiVJgkMAoM8FgP/0osBKYfZFgeJYAMCpJZCsm/FgPyVRFkyVBgaMF/+v/5QBVotJaoMkyBFBGoeffIIsICgVBhYLFCoP/JQQsEvmTC4OEGAILDFgfPEAs///fiFAGog+BFgTOBQYcky4JB/MkQ4IsCeoIsD//8Jovx48QqQIDl7yBv5ZC2wsDagP/9/5kLdBMQTsBQxKnB+4sBmSSGQxFnBIX+Q4KSFFhGzMoIKBXgwsC+rgGMQX9QYI4Fsm//VfFgu3/9/dJBlC0v/8mWvJ6CWIbdFy3Zsn/FgP+5d8EATdBXIrpDeQP/0m/9ggDLAO2QY44BSQPSpbOBagf3KxACCPQMk359BEAOXRgJWBFg9ZfAPSpN//YgCngpJX4fpkgnB2QgCFIIyCFg7pBHIIwBUgRWKAQO//4kCDQNJt4gBKxF9y1f//0dQX//mzQZf7lrIBFgSeB/MlFI4CDHoKbBSYRIB+QsLcAKbCC4TcB8hQDrNtAQlkEoKbBFgQzBcYRcC2a8CAQXfNwYXCOIP/WAImCv+2rNnAQI6BNwZxEWwTXDAoYLBNwgCCcYP/7KzCv6GDBQLdCC4pECRI3PBIJBGIgv/2wHCrYHCII6JFAASABAASDFAQyoBAAzFEARBcCAAZWJC5QUJA==")) } Graphics.prototype.setFontAntonioMedium = function(scale) { @@ -45,7 +67,7 @@ function draw(queue){ var timeStr = locale.time(currentDate,1); g.setFontAlign(0,0,0); g.setFontAntonioLarge(); - g.drawString(timeStr, 100, 50); + g.drawString(timeStr, 57, 57); // Write date g.setFontAlign(1,-1, 0); @@ -53,41 +75,34 @@ function draw(queue){ var dayName = locale.dow(currentDate, true).toUpperCase(); var day = currentDate.getDate(); - g.drawString(day, 170, 30); - g.drawString(dayName, 170, 50); + g.drawString(day, 133, 37); + g.drawString(dayName, 133, 57); // Alarm g.setFontAlign(-1,-1,0); - g.drawString("TMR:", 30, 107); + g.drawString("ALRM:", 20, 104); var alrmText = alarm >= 0 ? "T-"+alarm : "OFF"; - g.drawString(alrmText, 65, 107); + g.drawString(alrmText, 60, 104); // Draw battery var bat = E.getBattery(); var charging = Bangle.isCharging() ? "*" : ""; - g.drawString("BAT:", 30, 127); - g.drawString(charging + bat+ "%", 65, 127); + g.drawString("BAT:", 20, 124); + g.drawString(charging + bat+ "%", 60, 124); // Draw steps var steps = getSteps(); - g.drawString("STEP:", 30, 147); - g.drawString(steps, 65, 147); + g.drawString("STEP:", 20, 144); + g.drawString(steps, 60, 144); - // GPS - var gpsText = Bangle.isGPSOn() ? "ON" : "OFF"; - g.drawString("GPS:", 115, 107); - g.drawString(gpsText, 149, 107); + // Draw symbol + var iconImg = + Bangle.isGPSOn() ? iconGps : + Bangle.isHRMOn() ? iconHrm : + Bangle.isCompassOn() ? iconCompass : + iconPlanet; - - // HRM - var gpsText = Bangle.isHRMOn() ? "ON" : "OFF"; - g.drawString("HRM:", 115, 127); - g.drawString(gpsText, 149, 127); - - // CMP - var compassText = Bangle.isCompassOn() ? "ON" : "OFF"; - g.drawString("CMP:", 115, 147); - g.drawString(compassText, 149, 147); + g.drawImage(iconImg, 110, 95); // Queue draw in one minute if(queue){ diff --git a/apps/lcars/screenshot.png b/apps/lcars/screenshot.png index dc577c4d0629489e567bb31deb1ca2f3554264b6..a11b0431a060158a92dcdf96a8f9ac6138221e20 100644 GIT binary patch delta 2815 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 delta 2408 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 ai2ni4rfsEPzlnhW0000 Date: Mon, 22 Nov 2021 16:37:48 +0100 Subject: [PATCH 0796/1062] Added icon for alarm --- apps/lcars/icon_alarm.png | Bin 0 -> 1831 bytes apps/lcars/lcars.app.js | 7 +++++++ 2 files changed, 7 insertions(+) create mode 100644 apps/lcars/icon_alarm.png diff --git a/apps/lcars/icon_alarm.png b/apps/lcars/icon_alarm.png new file mode 100644 index 0000000000000000000000000000000000000000..5992863c38f2237afade2587b0415e615c130ab1 GIT binary patch literal 1831 zcmV+?2iW+DP)EVKpI*VQVQ*+O$9LpBGhq{DQVMjDr$?0 zR@5k%W+8A443D>wn8)Ha6N z_VBxa1;O*3;GCL*_7QpsXOH^kc7;SZBzsqEOTJL6;EF-xbt^IU2MAH%aXM1>i(qFe zpI30gI$dCT5D-|Gwsw7@H?pZ|dupiKftQ0Gaht)9W6X*PQ#%6@1F8FFs*`_6tJymB zQjldTEhGcIkKBkDx?paWKijdrsuYSSQGVz

    6E#!Sz4%7)axigi*_rgc%YJ$<1~bi{h@ zfl~aGN3s{S|G99f_Eaf7J4spl;TImc^oo$5+=&lX-2Q0j#2;_A#9Bo%K&XTeT&xYJ!JiH??`9j|emt zQ@)VkNvyTQs(MGmz==Os93)i|{EyfTn??97$R#rtinxSUH6zQfLF(&SS?A&%c>>Ol z74)y@8=#-9j~_o=X{;3sey*)GX7S@7i*nsJYpx@_`O}!zTM*a1V2?(l=qqP<`_5g( ziMINtRlBLuo~}sn;JWJ@)p7nN5==Q|aRZUBevQz&7~~kb(P$JAA^FoE<2}|_RD0Ii zVR6DAsbo&)Jpd?#I^hRI2#7VSnsIk`AxoB4;y!>eX1RLlp8=mM+ef(-huUx0g0Lmv zu&EihtGg(%_UxI=JT3t><}+_}-f~+>m8Rf8=8FK<#3s*;#ptLX@*bLL2c#f_mi;2r zr-U?Ij{BW1WKr&SmyVBPhX%1{|4Yg!UjGv?cCMs=4Tyyvg+B;rpbbSBh};gWWimd+ zg8WG~CJ#%Z&)I8z>5;58Le&nV-p#9b5J($`+Hc(8DgD`Nt^4h}2z@k`L+E+Z4mdUD;yInrjKI z`&clz)2Ddnp6_OQb3$yT7(_(vu57y=V+kr>&-o;c2+G~2)EBja2=d8ndwBDfz}r~I z&y)P&esr|t9KUcJBA?26L-GqUh`nD`gL46U8j}ia`<{=rT&r-!G^6WcT5k?GPxn29 zjuw4_Q!0(;I$E2j8e~1Mqx_s4da$a%kDOIs^m-D#q2pj;Dibd)E3c}02b#VVoWCB@ zl0BS5tJWZH-L!H#K8|TO(jOtpL_R@M*HYN`{08QKd zL*N&;Gm+rYs4TxaP|OYuR&CDC7>a5z7C|BPDDY^mIwh~eqBI0-ivuR?nczuKwNZkx z6ryCo3gLk9t+#>&i^z{FMx^A^w5ohP^#1tNv^TN7Hj4Mm(Q3G^Iu+&8lj!*|-nsXC zIPDz(c+VU~Yth4HP!++D5uHBtc(vTk8t=Ie;tPW3I}3v6J7o(FX6Ntzx-gMk@A$@^ zCCL!oz&79m1u6&?9{vGbcKOnZohYh`$@2`Xf{bMTM~d`3*uDFld|~&#r8b>(`?i6_HD*K{67lTQuvN2UV-8jG+9&^X*p--q-c|{{Zjx VpWh4-`H%nr002ovPDHLkV1lbwiu(Wn literal 0 HcmV?d00001 diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index f98e74e72..592a340ed 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -32,6 +32,12 @@ var iconCompass = { buffer : require("heatshrink").decompress(atob("pMkyQCFr//AAOkBYwUMC6FLCgoAB/osLl4VHAAIsK34OClmy5cs+YHC6RWL8gKEkofCLhF///8FIWz/YvCn4WB9JBI/1JsuWrN/21Zsp5DIgxBBEwOzFgRcC54FCIgyDB/mSrN9FglnAoMnRI2X//yFIXbQweyAoXPKALFF/ckEYJZCAQtkEoLOE36DDARSJBOIZuB5JWCFhFnySrBOIRuB9myVoYCGR4RxE//2WAKtCFg9ty1J//0boZWLF4jjCOoKwBEwYvGkIFCn6eBTYIpL5+QmQsDTwKbBFghQBAodmpMJkrjD6QsM2MkigyE/sl//kz/5EwNbbQeWpmSpCXBy3/sv//Vf/wsJiVJgkMAoM8FgP/0osBKYfZFgeJYAMCpJZCsm/FgPyVRFkyVBgaMF/+v/5QBVotJaoMkyBFBGoeffIIsICgVBhYLFCoP/JQQsEvmTC4OEGAILDFgfPEAs///fiFAGog+BFgTOBQYcky4JB/MkQ4IsCeoIsD//8Jovx48QqQIDl7yBv5ZC2wsDagP/9/5kLdBMQTsBQxKnB+4sBmSSGQxFnBIX+Q4KSFFhGzMoIKBXgwsC+rgGMQX9QYI4Fsm//VfFgu3/9/dJBlC0v/8mWvJ6CWIbdFy3Zsn/FgP+5d8EATdBXIrpDeQP/0m/9ggDLAO2QY44BSQPSpbOBagf3KxACCPQMk359BEAOXRgJWBFg9ZfAPSpN//YgCngpJX4fpkgnB2QgCFIIyCFg7pBHIIwBUgRWKAQO//4kCDQNJt4gBKxF9y1f//0dQX//mzQZf7lrIBFgSeB/MlFI4CDHoKbBSYRIB+QsLcAKbCC4TcB8hQDrNtAQlkEoKbBFgQzBcYRcC2a8CAQXfNwYXCOIP/WAImCv+2rNnAQI6BNwZxEWwTXDAoYLBNwgCCcYP/7KzCv6GDBQLdCC4pECRI3PBIJBGIgv/2wHCrYHCII6JFAASABAASDFAQyoBAAzFEARBcCAAZWJC5QUJA==")) } +var iconAlarm = { + width : 60, height : 60, bpp : 3, + transparent : 1, + buffer : require("heatshrink").decompress(atob("kmSpICGyehBZICK7diCiUk9OmFidOFn4sRyexFhgOGpOaBwosHBw3t23EFhYOHzVpxIODzIsFs2atAdEzu27cScBM7tu2JQs6tKAKGQOaEYgCBu3bti5KtpQEQAmEXJQLIFhjmIyYsL0L4OmAONAQscuPnz1yCh8mA4mQFh2R44sDvPnFhtMA41IFhtxFg2cCheTAgNN////IKCkKDN44VB/4sCRJnJFwIVC/4KDahOSp4sBCoYsDvARFu3EWAgUC+YyBWxE6tL+Co8cv///iGEAQIbBzOmzUd23biVJmVJm5WFAQYOB7dt22aFgWT48eFgKzFAQPhFgdjFgs//5WEFg+xk1oZAXHj//84sJTw4sOBI2RFgXHFiIBBFiqGBFikk+QiHFhYpBFioCJFn4s1rFhw0YAQtjx048YCGhM8EwYCQiwpGFhs4FKQCCuws/Fn4s/FhM8FikSrAsThI=")) +} + Graphics.prototype.setFontAntonioMedium = function(scale) { // Actual height 18 (17 - 0) g.setFontCustom(atob("AAAAAAAAAAAAAAAf4Mf/sYAMAAAAAAfgAfAAAAAfgAeAAAAAAiAAj8H/4fyEAv8f/gfiAAgAAAAD54H98eOPHn8Hz8AhwAAAP8Af+AYGAYCAf+AP8MAB8AHwA+AD4AfAAcf4A/8AwMAwMA/8Af4AAAAAwGD8f/8f8MY/cfz4PD8AHMAAAfAAeAAAAAAAAP/+f//YADAAAQABYADf//P/+AAAAAANAAPAAfwAfgAPAANAAAAAAEAAEAA/AA/AAEAAEAAAAAAZAAfAAYAAAAIAAIAAIAAIAAAAAAAAAMAAMAAAAAAAAEAB8Af4H+AfwAcAAAAAP/4f/8YAMf/8f/8H/wAAAAAAEAAMAAf/8f/8f/8AAAAAAAAAHgcfh8cH8YPMf8MPwEAAAAAAOB4eB8YYMY4Mf/8Pn4AAAAAgAHwA/wPwwf/8f/8AAwAAgAAAf54f58ZwMZwMY/8Qf4AAAAAAP/4f/8YYMYYMff8HP4AAAQAAYAAYD8Y/8f/AfgAcAAAAAAAAPv4f/8YYMY8Mf/8Pn4AAAAAAP94f98YGMcMMf/8H/wAAAAAABgwBgwAAAAAABgABg/Bg8AAAAEAAOAAbAA7gAxgBwwASAAbAAbAAbAAbAASAAAAAxwA5gAbAAPAAOAAAAPAAfHcYPcf8Af4AHgAAAAAAAB/gH/wOA4Y/MZ/sbAsbBkb/MZ/sOBsH/AAAAAAMAP8f/4fwwf4wH/8AH8AAMAAAf/8f/8YYMYYMf/8P/4ADgAAAP/4f/8YAMYAMfj8Pj4AAAAAAf/8f/8YAMYAMf/8P/4B/AAAAf/8f/8YMMYMMYIMAAAAAAf/8f/8YYAYYAYYAAAAAAAP/4f/8YAMYIMfP8Pv8AAAAAAf/8f/8AMAAMAf/8f/8f/8AAAAAAf/8f/8AAAAAAAD4AB8AAMf/8f/4f/gAAAAAAf/8f/8A+AD/gfj4eA8QAEAAAf/8f/8AAMAAMAAMAAAf/8f/8f8AB/wAB8AP8P/Af/8f/8AAAAAAf/8f/8HwAA+AAPwf/8f/8AAAAAAP/4f/8YAMYAMf/8P/4AAAAAAf/8f/8YGAYGAf8AP8ABAAAAAf/w//4wAYwAc//+f/yAAAAAAf/8f/8YMAYMAf/8f/8DA8CAAPj4fz8Y4MeeMfP8HD4YAAYAAf/8f/8YAAQAAAAAf/4f/8AAMAAMf/8f/4AAAYAAf4AP/4AP8AP8f/4fwAQAAYAAf8AP/8AD8D/8f8Af8AD/8AD8f/8f8AAAAQAEeB8P/4B/AP/4fA8QAEYAAfAAP4AB/8H/8fwAcAAAAMYD8Y/8f/MfwMcAMAAAf/+f//YADYADAAAAAAfAAf8AB/wAH8AAMQACYADf//f//AAAAA"), 32, atob("BAUHCAcTCAQFBQgGBAYFBggICAgICAgICAgEBQYGBggNCAgICAcHCAkECAgGCwkICAgIBwYICAwHBwYGBgY="), 18+(scale<<8)+(1<<16)); @@ -97,6 +103,7 @@ function draw(queue){ // Draw symbol var iconImg = + alarm >= 0 ? iconAlarm : Bangle.isGPSOn() ? iconGps : Bangle.isHRMOn() ? iconHrm : Bangle.isCompassOn() ? iconCompass : From c720fc314b516e1007452ad18ce51bc4fefefec1 Mon Sep 17 00:00:00 2001 From: David Peer Date: Mon, 22 Nov 2021 17:03:21 +0100 Subject: [PATCH 0797/1062] New alarm icon --- apps/lcars/icon_alarm.png | Bin 1831 -> 2584 bytes apps/lcars/lcars.app.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/lcars/icon_alarm.png b/apps/lcars/icon_alarm.png index 5992863c38f2237afade2587b0415e615c130ab1..3cabb8b34cb04263ef5c36c99c52d24c363fa4dd 100644 GIT binary patch delta 2559 zcmV|w|4ww{>Gk*!sNkll$Vx4 zftJ4BzTDoo-*fgyp%l0;zuS9-v2W5p`t&^KIp3c1Jil|!^MAlKz#Fq>jowl_qx@Q| z?OuyTivyfsz!+f6)n40N?FFwj^78POxpT`@V=s&6{A%TqBTa=eDit=(u9_x}^Nb<5 ze&wMXz6e?` z$h4$qcWtSzo`0T~z7*IryK0(slYbMDX((@)!;LTGqc1PQE4ShuJa~L9XuT1svaa`$ zA5W|BGNQlBCO6Ep)3MZZ=-8T-;N@epezfSyM6>$p0fFWSBD)bM!<@-)Z>u!XX* zpoE8`;kNlVIU)}VEK}r0Ba(3~tE!3~Rm(dnT3`Fm1Bbr}y9!RYVabA8$H)eRdyUA5 z4n?bKQGd33R-dZfv%jH`_7#lqr;8Si4hFsF1zjt~IGJQ8|LjppZkdPe>m%OULbUNH z{a+p+dh7`3#oeIRdY;N_Ho{|d4GrN!*jW(5`=TWkucYhW+{4ntwOp150b~Gs0#uQtEq>8kh^AKb31|qa?ev`IM^=Lc}fLiTOSY1`OXa8UGu{STm2bR{}C&D&k zq&Ocv7f>{5GUJ!8V(br>V}e1VCmK1q@d;v0#{saa@ua6e&WH4f2{+t6zs^VQ;V9dtl#4bZ0zV(&5V`w^SH$cZrb+ zLxUnnpcrx8{C*r4udkQxo!ilPoY10MDgXXmM2~(=y!j*|QY6Sd_f!P_u(>rH&DHi1 zm50^@UNA z2q_BCzU3`?_wJ^6Y85xEe&~WBMpZWEY=17oU))wVAJK-PG*Jw}S=D688Ub*|LD^D+ zGeL&rRTb}?(znOrb)=pbJADe<--kPX0@d|f5y$25>SY`{d=ylz6>q_+gD3v&mqSh- zR_vNfa$><3S$86o|HvUwIhx|o6o1^}ioxk3xDyRy?zR+-j%U=7Y;U7u+h$C0G2@r7 zI={XcQRgSQaPUo7K?IiNq8tE6DZ9g=C^VMR3Ev@DK8=#`a|wi|42g)Vl+O)=WUeSX z-ra&tB&fLiKExe7Z&g{Ii#fgtA6Qaz6C#zlCpg`{4th{+l4$s1S632)HGtHS};?qhJO-)^bq2m#k|`!hOii#nwqF^49`qS-8)cK66ab{dboIM6``WQ zi0sXquqNU}Pd4F>8H)*KKXkMtc+r`vc#-}TIuj(rU1&5-BBFXpvo9AE=0ZG~B-wt> zm#EGthrr)60g$vKzj?VP zgmr}b?*jVb*>mo6xF&?-@yxYv#<`wHM3w{G1s;Q8TWfkb?wVG-$5)&GL0DW4~1bYthfHBSN?E+1D_-uu|)UteWrGH0sC z{dXmBo%G8L#NZD*Tu!xR>Srl;*Cx5W#!DG_(l>zH(m`W8y!a0`)NkGSbUIBrCmyQG z`+KvKoqs*WxYRT2JS;!X3vn?I)+_so6vf$Ih|Er!o+YZnFsgI6s|HVW`2BtHNRpHIO z!rrekH_7K!DytWGxIO7*`eN|XhlZ2qG6&Z3`G1x>sw`xvF`C*ezj-H4cW-WqOGd)B zt%p}WG3+{;Ib3Qgm7gw9iZe7JdSmeH2SQ^i+ zFSgb`6qIO9dv9_6}Y^|LoB5PxeBIM%| z58#jR*|TS_%hWO+k7Kjc%qa;+qtWYE43B`yE}002ovPDHLkV1lXh0b&3E delta 1801 zcmV+k2ln`w6sHc5Gk*r&NklEVKpI*VQVQ*+O$9LpBGhq{ zDQVMjDr$?0R@=B_6G=2 z;Bh)q_lsa>Du16>aKbuWV0sV`SeLeTeWEwAscCy^sM>*-gC234!H;9iiV0IY0}%tM z`(~<>e@Ls@I`vYJWmYtv=#6}+@^C9d6E#!Sz4%7)axigi*_ zrgc%YJ$<1~bi{h@fl~aGN3s{S|G99f_Eaf7J4spl;TImc^oo$5+=&lX-2$4YP)fxhkhC4HsDH`(%#Ct2$Nf5tEwm?BKkYU{Bm*sUyjXIOtT_*ux;&DRp}Oy zn`SCARd6;|?Fj?-Uel-B%e!vP_q=4^yf&r|eqV_SI@SXKfK3laV zuYYQShhjIid7_U9G!|37kl;zIwZp1seXn;vIPc&W{!Jujm_~pRJD{KU`_76$^f@tuGjDX>a$8B2rr<#4ivZTdCV$V3 z#ptLX@*bLL2c#f_mi;2rr-U?Ij{BW1WKr&SmyVBPhX%1{|4Yg!UjGv?cCMs=4Tyyv zg+B;rpbbSBh};gWWimd+g8WG~CJ#%Z&)I8z>5;58Le&nV-p#9b5J($`+Hc(8DgD`N zt^4h}2z@k`L+E+Z4tb4O4meNuJ%o-HeS%XejpsUAo2MFNJ+GtuoE& z8aa&WTA23IdR8_j4?6%&+kgE-;1{Js&EWbKX%nl7!ZO+aZifS+xK_T@h@Mx|& zC9lJxGz4sm119X5;7L%mQG&4)qGZ7e;ehe2w}J(W$d4;Vq~z1Is(d~4{`l0iH?h7p ziucUXYPhaC73I>C==m|;x%YcG?HvGk&m2W-(Zgj>6~T}Zoj&w {}); } else if(lasty < -40){ writeLine('Up', 3); // setTimeout(drawApp, 1000); - Bluetooth.println(JSON.stringify({t:"music", n:"volumeup"})); + //Bluetooth.println(JSON.stringify({t:"music", n:"volumeup"})); up(() => {}); } else if(lastx < -40){ writeLine('Prev', 3); // setTimeout(drawApp, 1000); - Bluetooth.println(JSON.stringify({t:"music", n:"previous"})); + // Bluetooth.println(JSON.stringify({t:"music", n:"previous"})); prev(() => {}); } else if(lastx > 40){ writeLine('Next', 3); // setTimeout(drawApp, 1000); - Bluetooth.println(JSON.stringify({t:"music", n:"next"})); + // Bluetooth.println(JSON.stringify({t:"music", n:"next"})); next(() => {}); } else if(lastx==0 && lasty==0){ writeLine('play/pause', 3); //setTimeout(drawApp, 1000); - Bluetooth.println(JSON.stringify({t:"music", n:"play"})); + // Bluetooth.println(JSON.stringify({t:"music", n:"play"})); toggle(() => {}); } From 247ed9faa2e8b8463374fea05d4bb04ddda2cfc1 Mon Sep 17 00:00:00 2001 From: weeurey Date: Mon, 22 Nov 2021 16:07:39 +0000 Subject: [PATCH 0799/1062] Create ChangeLog --- apps/cliclockJS2Enhanced/ChangeLog | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 apps/cliclockJS2Enhanced/ChangeLog diff --git a/apps/cliclockJS2Enhanced/ChangeLog b/apps/cliclockJS2Enhanced/ChangeLog new file mode 100644 index 000000000..c7cb9e2c6 --- /dev/null +++ b/apps/cliclockJS2Enhanced/ChangeLog @@ -0,0 +1,2 @@ +0.01: Submitted to App Loader +0.02: Removed unneded code, added HID controlls thanks to t0m1o1 for his code :p From 741f7aab5239e6fa6ae48e1a5351216dfcfc4177 Mon Sep 17 00:00:00 2001 From: weeurey Date: Mon, 22 Nov 2021 16:08:38 +0000 Subject: [PATCH 0800/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 7fe259899..bc1f8402f 100644 --- a/apps.json +++ b/apps.json @@ -4250,7 +4250,7 @@ "id": "cliclockJS2Enhanced", "name": "Commandline-Clock JS2 Enhanced", "shortName": "CLI-Clock JS2", - "version": "0.1", + "version": "0.2", "description": "Simple CLI-Styled Clock with enhancements. Modes that are hard to use and unneded are removed (BPM, battery info, memory ect) credit to hughbarney for the original code and design", "icon": "app.png", "screenshots": [{"url":"screengrab.png"}], From dc24ae9bfba1786d4d8a3e22871c4da7bda9fb92 Mon Sep 17 00:00:00 2001 From: David Peer Date: Mon, 22 Nov 2021 17:10:58 +0100 Subject: [PATCH 0801/1062] Better HRM Icon --- apps/lcars/icon_hrm.png | Bin 2131 -> 692 bytes apps/lcars/lcars.app.js | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/lcars/icon_hrm.png b/apps/lcars/icon_hrm.png index b6ba2ae193207b18d46f2ddc474327fe3e0bcc44..46a4476fc29e2b9eb5fc7fa2dd15a88090fbcac9 100644 GIT binary patch delta 653 zcmV;80&@M+5VQr5Gk*eWNkl+D+SUNNC8~^cQ9pPxQa`n z*@;a6-|7)nj0RJ$MaZr-0|^Dz4(E3_Gnt(sbpdyWw+0--$9i^8)-o&4pqm*DN)L=Go_2j2E;(i&_*<^6nH1Z>c`>V>E@3fP6??)kPR2CRQtn(kbfT$HoF3wVF_FGZU`BN?IjEs|7>@J{lUVXU+4dXQ= zBve-3RmXMOU0_50YSDxWVFOGp@=E`eCBO|TU+8K*N6tAnwM4KgJcNfx)d%L- zV6(HkEC5rdt_%r_Y}^9cDjf|I(}=sa7EQ!F&_M-LN;$2~5h25kuA+EU8Vp8E zCNm=9<<`M3rGFGwYbOqey~kA-i_zN+CX*RW)BYE+1w|r)$z-Ozy|q`JSE>g!vpv-p z!-Jc~0QJBCdkZv>;JuFF!KA1mCVn?#emW+a)XTEnY*pFlNW!1J;)5fP3ZKSe`B6C*8=L`RJedbFVmDw5*&n73Tp+PfZCV9KYl@aA>HR1=(}m<#}^6{cdz_)+`JL z1D~BfOGig1r`^C){r2Ur{03f-J(sYsP%d1oq_wq;iE*)v9Xq;D`@|`enHV4IaV>kU ztA9HmKUo4m-SrzZG&T_#IfCe@34Qt+J!&MwhDG=rvWNTzUb zJa_N5qLgBOc9z>lMEu%=^XYwf+m>H5H966}lX>vaCjhjzwsH2{MP_8AF?&`9hmRET ztBpTrUe;{4?XKPVBqb*B#IhV#e0LdHvuC1|>IHnQ&NYrljkFLIHJ;kqI)3r;S{_}P zjiwm@c>CR*JooHM#*P`yoXi%{ucKybFS$xW{!C=5(a5Y+VqL}hC z=iGJfZk?6MjEpqqXJ=tF8W4fvk}?WEImW)fe~i=VBxmVjA|i%zwWgM9bvHcPP)bp8 z?gAph^wbpM<6`;d;sqFuMwhSBG7c3U;aJfrM1;-1+`zPyBzJ6@k6HF9!+&emZ**tO z>2!M3oj6s>i$D1xnKLsmnM{ZXf8M#9-FrSlDc2&fruG_TI=yG( zTHSRTo0=JEiNq2)0=wPLXQwNA*&R80oFhk%2NbI=FAoL(D0Bz=!GB*r?9(+Z4MN`0l5{QK#F*u>LQUjqX^PvV1F zeKB|)!&YnOLviH6n}56lI~;be#|0h+eLaRBcKzQ0OF?C&$N_b8a z8QPx%zZz2trO>3GNnI(WL~!ao_dAtp6?nh>cGxTM^O%KPxpM5B=_zSxR55^|r&sSaj5L7$ck2ImrA zZ54(l+qy$NQBFLwJ_64|MjJZ69A=@R8>`l=;g1c h5)u-+L_`2wEsU$X!J002ovPDHLkV1oVb3@QKs diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 7f00dd6bb..629a5b8a1 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -22,8 +22,8 @@ var iconGps = { var iconHrm = { width : 60, height : 60, bpp : 3, - transparent : 2, - buffer : require("heatshrink").decompress(atob("pMkyQCTwArTiVMmHDhgCRkhYUgQstmPHjlxARws/FikeFikLlmy5YsSz158+cFlEeFikAFgPAWaYsBQx8P5AEC2CzQ/jgUv/4cCkf+IsMgfwAon/Fh3/EYn/Fh0f/wyFn4sOQAgsP/4sCh/4j+An/ggACCFg9/VoYCBL4cf/4sIgf8h/AQYX/wEHFgX8Fg4gEKwIFCGoP/AoP/+IsEgP/8BWB/6DBEwIsBGQS5DcAwpFAQZfDQwwvDXAKACR4UDwCzIFgKtFv7pFFg59BkAGEL4LgLARAsOABwsOAQos/Fg0wFlgaBASIsskv/ACf6rnz588ASOlFitIFic9kmSpICFMQf6BYwCKrjaCCiAsXjgsCugsQv5+D9IsPw4sDuPSChuXVg3+Fhi5IVpICD37aHIhgsWAR4A=")) + transparent : 0, + buffer : require("heatshrink").decompress(atob("AH4A/AH4ANgmACqcCpIsUkmQCqcSpMgCqUBkmSFlMJFiuSpMkSSQUByVASSMEFhZlBFhEgFhKSJBAJxBMpSSGMQWSpCSQGoOQBYOAMqBiCiQsEQAaSIFgOAAQR3EMpZWCFghTDMoKSBHAhKDLgQRDEwRlEOgRKEdggIDMoh0DO4gsDCIJiEHAQUEKAjaEKYJlDFIxrGBwRWCBYbjJBAIRBwVJKYJlDKAbXGR4VAEwoFCCozFDHwRlEAoQsKHwSVCBYjLEFggpCF4YmDUgQsIKwRcCeQosIEwKJDZAQLEFg6DDHwUCTYILEFhLIBBY4yBBI4gBSRIAKEAJlJABRBBMpIsMMpAsNCqQsWZAYASeoYspZCQACZCYA/AH4A/AAo=")) } var iconCompass = { @@ -103,7 +103,7 @@ function draw(queue){ // Draw symbol var iconImg = - alarm >= 0 ? iconAlarm : + alarm >= 0 ? iconHrm : Bangle.isGPSOn() ? iconGps : Bangle.isHRMOn() ? iconHrm : Bangle.isCompassOn() ? iconCompass : From 162dc1f29346ab0f6ff4b7cd4128826292ca766e Mon Sep 17 00:00:00 2001 From: weeurey Date: Mon, 22 Nov 2021 16:11:07 +0000 Subject: [PATCH 0802/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index bf81d9d7d..4587950e5 100644 --- a/apps.json +++ b/apps.json @@ -4256,7 +4256,7 @@ "name": "Commandline-Clock JS2 Enhanced", "shortName": "CLI-Clock JS2", "version": "0.2", - "description": "Simple CLI-Styled Clock with enhancements. Modes that are hard to use and unneded are removed (BPM, battery info, memory ect) credit to hughbarney for the original code and design", + "description": "Simple CLI-Styled Clock with enhancements. Modes that are hard to use and unneded are removed (BPM, battery info, memory ect) credit to hughbarney for the original code and design. Also added HID media controlls, just swipe on the clock face to controll the media! Gadgetbride support coming soon(hopefully) Thanks to t0m1o1 for media controls!", "icon": "app.png", "screenshots": [{"url":"screengrab.png"}], "type": "clock", From db22bac0ecd48633d6acf0874127a1b28821d8ea Mon Sep 17 00:00:00 2001 From: David Peer Date: Mon, 22 Nov 2021 17:11:15 +0100 Subject: [PATCH 0803/1062] Use correct alarm icon --- apps/lcars/lcars.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 629a5b8a1..a8f0db85b 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -103,7 +103,7 @@ function draw(queue){ // Draw symbol var iconImg = - alarm >= 0 ? iconHrm : + alarm >= 0 ? iconAlarm : Bangle.isGPSOn() ? iconGps : Bangle.isHRMOn() ? iconHrm : Bangle.isCompassOn() ? iconCompass : From cab3d43085b2edf4dc97ebf05785bb5b446a88da Mon Sep 17 00:00:00 2001 From: David Peer Date: Mon, 22 Nov 2021 17:39:48 +0100 Subject: [PATCH 0804/1062] Better alarm - shown only in icon so we can display more data. --- apps/lcars/icon_alarm.png | Bin 2584 -> 2537 bytes apps/lcars/lcars.app.js | 37 +++++++++++++++++++++---------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/apps/lcars/icon_alarm.png b/apps/lcars/icon_alarm.png index 3cabb8b34cb04263ef5c36c99c52d24c363fa4dd..02e5ab1b6e25fa92c3342cf2319046f5d143f123 100644 GIT binary patch delta 2511 zcmV;=2{87U6zLO?G=HE;L_t(&f$dvOY~#fh|IPeAY_IK2EJy1PDS>#Yx$TFvhuwl2 zEl$F{qBo@G06hS!9$JJrRlTe#Xn_{NVK0c4KI&Nam*nit`T9y8!Nb$_PnfLR|ym{}<7&u`N(SKL;Z+v~ysVM{k0V#3E zabC=1GBF~0#w}^rOS(0)QnLH|`zRC&C=?1qk?lmI(ZIcX_fRMlu)n`AZD-r|k5^V! z-X2&&<<2Wyg>WB!{Y8Cl>9`*Y1JAX^{dL8+E9@T19y2l;Ic`=<%yV}!z z38&NPE4FR_PE9#aN2T%fQV2m7O}m(-HY_=nM~$~{rc*;4A$1xe6?Eb zE(F&2K#ASm-JSzDj)Q8oifXlry}do`@9)F1EC7J2s_=L`2m}I{oSekuS<8ug{ zifXlrVzDTcBO;W`Wo&M4N{RM^rfHa+oqxsD)Rfdh$6_&rLLpI=q3inNGB;GZeoWJR z6#}yR`}^41+7ikT5%T#wHa0efCfgbt8yg$Q=kwA%TPl@g)?07sE`;#w%jI&@&1UmU zboj51c5-kUYC;1=kv!-762-h3N|-4g)?2R*Ri*^Clb>g$9Y~<(M6buSSz0GEXRWf z4}?mU%Vm^GrGB~(jZ&$Ea=9!WTl8f(j`OU@8R;UN%jGUW5WF{=O`!*6Sr#@oH{EGI z0yZ}{+sD~qG#U*wn@y3(BbiL*Vt;307h%h?7DdvsBdAy`j%1+R!Zc0Xzkgp?C)16H zy0~-^Zsnjl1%-RXah$O(kr3|y)oS%bX7{ij z)oQh?+%!$09*nWxOL~2M-H%pw@h~KNL^$}kAsbVqBN0unuC4|TmmMKosee?WqRvFK z3qW=xj1{smWjZz*jo9JODiM8k@WHYyuSn3gZQ_Vb*Rw>_o_-aFjv!T4rA~V%h)8%$j2pIXm{ve+P12y?InFgDx1wdW*ElbJB7;S@{#P~ z0Y3ZeGt_Fe5!4?Xfj|J~&!6uqKXc{`rlzL4$~otcFDxu}+y!(w&ya2uaW{)PIpNcJK1#%l|lB zCNf%gItx@)g{Em;4JRijh2`Uds;a^-S<^J39z@i|rHe4<{L{`d#u$RZU}rHn=ZHpy zzRFk-jYfsXQ7{-3o*E)9UDBGSeIy!eVq!w5L_8ipky+L>4e@wfSSQnsG1kSUi}2js z+&X}KXSvtwh0o{f>VH&KRm{%L_Sbks%+AgVzgVBoC$ll%q*AH=OPVo8ilTfdC>NT2 z0GOJZ8tc-IL?S)MmMrHFME6ZXPfOFZ8+ds0eguI)0Dix}t36{3i9`b7aJawr10oy_ zBaui5N%{SL*)e5s&Toh+goImfD&FrbXN(~ljS2^6j3Jp!9)IghMqcVrc0B~K_ zch3l5d3pIYBI>%fIJj!r+1ZiGT9$?T_wS=rD!GoTR8?(XwetrGHYn3qclh*vqs=qtSl$pePEwUM~WH079YAaa^Y50i1jK>8GXlwtZu~ zTeogKVHn28jIpjj+09IMVrgj!r%#^-@G$xI-h1!4mhCmOL}V$7a&=*0VYOEuedFP$ zo_gvpobz9b>KJ2)L?VdAVy@$3#uzx~;G7R66N|-q8Gk7#itXo1 z$dn0%LP#VM!Xs-``2Bt)5($Jtp z(=^d+HendX5i86&$C)!{pzAukUN1CFbG5Hq2I`gh`S~{oeQ`VRd+F8yZ)Gx>zgm`c z6MbIeF~*?ldOIU=IPat=iquE$fxMz9KXqfEM1L4MYne)=*23ZNInMcSA@IA$0E031 z27q(Jk_AS55#M?(^Kv$ueaG$d;UQoExC!9;;^N}S_p-;DT+U=Nk2sF=eCtB}5mB1~ z-b;VOIsbrjenS=r41=-#1R#^iTqL4J$8oMO#x4@kG$1F;7`sPAcR1&tGR8hirBZjs z@kkSZvHZxRrRd*Ue*l2h)zx65(TM4~o<-~WL({Zv&iRjm!C#2 Z{4dFEeD6mKfdl{m002ovPDHLkV1hl;>R12( delta 2558 zcmVmzF_+ zmcHJ;+}^j}bM{A}6u2+H+k1tvZ_+>d^gQP|-=6b4zjMy>z<)Ks8?$DO-cmcG{93H- zUW-MG1Ds&M7+}oRUfW#l1+O*o^6-|qbIVj?FN^2=YUPn5O@%Tl6*kSTnkJ6(j3Kyw z<)K4;Bh{0ihb6jRj69%4-mbTAxp78bx-wu(b@g=PxE~oYtF1;K$wyaygy%NC2wE@5 zw4`TuZK6&q*BmP0E!HNo<{ZSxH_LN+P&}8@OcXIG{^?9g|e}r zgomTyw)r`_W?nTPG`Bi`CVwDBnY zUmhTO>U)yo;FN`MLLmZ^ zr!Z>v96}3krR3Z55M%HLBD8OQleSIuXgm&pTJ29*T~)Vd|6lX5H!s2mme$@U!Zu^1 zI3GP1P&8>W<^b?fDOkA6+O`6MDzB*;DYR0RI8xiuWk)%FpU zht>pMFh;6|lvIqx9XlRzM)d!?0;-DF6Mv@d&wnJ@)k)=Jzhvr@&(iqwhlw6Lf(ljf zg!3HmaIU&@ws8N_n#E$wOLG2*b;eW>xM31v3fD^z5lk?M2?Xf>VjngdCG@=|l+CZB zbNhSPM3T-(tY*c8F*~-lb)U{fcg|LdI2(s26mG@19A+!d7zoT10yDv>7#vG$Tz^>z zDGJcOmgfJLup4Ie~HG7&os501>8cnlruHuRXpAiT%3=YlTQ8$bAS8_3QWl^btP=?APM!N^tJBCxIwHJ13llwP(^zB0q_R0 zrva4qFsgX;n57V201e^&e*azHkqKc+b}&`PP{Xa(rWcQ?7)?us@bn@Vq$YMyVtGF z+2p*hOB3f>FjK3B5`Tg85aON1yxTQ~uo#+}ny7FL&rC_(J5W^;=UP&FxOi$6p`yTu z?9H37CgMa-HsOvLiwR~wbhIRR(V41vk^U4q6C}f3Xf#bCqIyZQFBcT%LOhuy*?!KK zsLm*dz~JXcy(IDGlQ}QfTK{{_oH>*8Jh(r7=KyU_PTrlkhCMedATNp zb%gux0{Y_FbMADwCWPbh%(ZXExt>QyBsSvtSA7qAo=1CUS9Zb6psGX?NzV30uz5s? zZ^F&p5ppjMQ|LK!nz$KU*0V};_{z`H4v5$RLa|C9!$ykwM<@_zxSDuny9=kR5tpeW1Hm`Fn3 zKxP>cC@&@Tm%=J|gO7arV{}IngpDyNpC@K?W9Pm#PXZe*A6Hf0``G1QUu9-8XR62j zcO`J0^vevy;14@oPPJv~XDN5rCb_-FOBs36H-OvHL1R0-_zyPJZ{7KHI!!qz9;(Xw zd$W_BJ%7dH{yQ_1jrt_}g9YiyUO-a^Z2VB@i)G%5IT6muvv($N-OOZP&(pIPaN?ZD zuRq967IL|2$&uns~32^hn`Txu$npDs{}Gc+N3WAN+;LSsiJUCN3YxVK7?&h%1^9r)g}mm}qj_3PI! zw$?rrlxR(RZ*oCK1pxDK|S{!Q^PGzYI%Zg#)x3ZUh`%VhIX`(n1i5!STA_qj| z^>-aRcI>(Q? diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index a8f0db85b..3be161fdc 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -33,9 +33,8 @@ var iconCompass = { } var iconAlarm = { - width : 60, height : 60, bpp : 3, - transparent : 1, - buffer : require("heatshrink").decompress(atob("kmSpICdmUSC6eTgIRPzAsIpgXLiAsIoQsKzMhFg2TkgsLmAjCFgkQOhlkwgIF5MJOJnABAtMgieQyRrLAQ1MAgdIbqAHFcZ3JAgX//wHBO4yGHAgWf///+VJQxhBFC4PkIhmZA43//LgLQYgCDn/+RJUmBRFv8mQFhGTAgNp02aAQekz/pgKwPAQe/OJJuBFIgCDyf5gghILwvbtoFCk/yoTdJEwWbvv27QFByV/4kETZm3//t2gICTxIsLp/kFhlN34sF//IZBIsJpP8iAsGsgsE2yGBFgef5MJFhlt2wsSpIsVAQoNBggsNknbAogsLyQpCsPAFgOkLKEkgAlEFhgCCoPABonkFigCBFiiGPFitAgAsvyFBFj0kEAQCCFgIFDFhEAACAXDo8cuPHARhLFFipRBAB4sEzu27YKChoFBtgGCm3btu0ghxEBAJQC4wFB2xWCj4FB7UAQwgsB4AkBgYOBGQ1twAsGPoYOCw4sCGQQjBFguwEoQOEGQlggAsF7AVCBwkAgIyC0AGBFgmYPooyDzYdB6AFBFglGBwUYsOGjAGCoIFBsKzHcFgsVAVQA=")) + width : 60, height : 60, bpp : 1, + buffer : require("heatshrink").decompress(atob("AAUPA43wByn/A4v/A4s/A4PAEYYGB/4GCgIGC/AVFCwcP/tfq4WCCoPq/W/CwU/6tfqt/CwMD/2q/Wr/gOBv9VBwNf8AkB/WogWqEoMB/tVgEVq4lBj/qwEAlW/wED+tQCQNVEoM/1QoBgW/4EH+tAA4NVvwzB1AOC1/gg/VBwUVBwN+0BsCBwMP6oGCit/gH+Bwer+AOEgtfBwsr+CZEg6RCNYJ0CAwP+BxgsOBxhKBNAJZDNAR3CNASGEBwSGGUgaVBUgsKUgLCBqg6BqrCDHgMq/7GB/9VqNVq/wNYP6d4R0CfwdffwX/BwOvfwUf+oOBEgQlB/X6/4kBCwXfr4VCCwKZCCoQWBAAIVCCwX/CocAh7FEA4YGEA4IGFgAjDBxw")) } Graphics.prototype.setFontAntonioMedium = function(scale) { @@ -68,6 +67,15 @@ function draw(queue){ // Draw background image g.drawImage(img, 0, 24); + // Draw symbol + var iconImg = + alarm >= 0 ? iconAlarm : + Bangle.isGPSOn() ? iconGps : + Bangle.isHRMOn() ? iconHrm : + Bangle.isCompassOn() ? iconCompass : + iconPlanet; + g.drawImage(iconImg, 110, 95); + // Write time var currentDate = new Date(); var timeStr = locale.time(currentDate,1); @@ -86,9 +94,16 @@ function draw(queue){ // Alarm g.setFontAlign(-1,-1,0); - g.drawString("ALRM:", 20, 104); - var alrmText = alarm >= 0 ? "T-"+alarm : "OFF"; - g.drawString(alrmText, 60, 104); + g.drawString("TEMP:", 20, 104); + var tempText = E.getTemperature() + "C"; + g.drawString(tempText, 60, 104); + + // Alarm within symbol + if(alarm > 0){ + g.setFontAlign(0,0,0); + g.drawString(alarm, 110+30, 95+30); + g.setFontAlign(-1,-1,0); + } // Draw battery var bat = E.getBattery(); @@ -101,16 +116,6 @@ function draw(queue){ g.drawString("STEP:", 20, 144); g.drawString(steps, 60, 144); - // Draw symbol - var iconImg = - alarm >= 0 ? iconAlarm : - Bangle.isGPSOn() ? iconGps : - Bangle.isHRMOn() ? iconHrm : - Bangle.isCompassOn() ? iconCompass : - iconPlanet; - - g.drawImage(iconImg, 110, 95); - // Queue draw in one minute if(queue){ queueDraw(); From 21134a1dcdc102755171af26e99ae86f3559996f Mon Sep 17 00:00:00 2001 From: David Peer Date: Mon, 22 Nov 2021 17:55:21 +0100 Subject: [PATCH 0805/1062] Better background --- apps/lcars/bg_large.png | Bin 4477 -> 4277 bytes apps/lcars/bg_small.png | Bin 1672 -> 1685 bytes apps/lcars/lcars.app.js | 7 ++++--- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/lcars/bg_large.png b/apps/lcars/bg_large.png index 250a4718d970e3370418c9f99f261e60bcdd2117..d50c602ab5a428d3a44672a3f873b62c51c6dbd8 100644 GIT binary patch delta 2727 zcmXw53sjOx8y+okH@wugWooPLR=T^ETHaDaYunArtTm<71S~ZRyp)6>il5bXJCfCU zl9berZD=T{8JY^pZeA*WK~uy_NM>Y$XqaeTfdAXw|DSW_oH_5z`^-FZ=6T-Hf5-dw zmIt&dasSV|!zuSPa>6Yvg4Urmx-zT3ZC(DIPnh;69y)A8d=$Uy2>yrg?|E-|hsx^0 zkK_HtpIIJ<68vUw2)fb|%QQK;MQqZoAFNXx{VeNtoppThsQl~DeNXA#yMFrWaU8is zG>&XTUXQDtAc_se`b_LZoKMr3QL?E(A20^p8ymRJ#;*j9t-WhncAPp{np;xL4k=6I zu91~QNuqr)WtBJ#)4P(}BY{E~{-zfKrX@bo{Ho}%+D{%Enc6DjNR72kvSA`ccIOy& zKxZ2pi!WvOaU#(P_a4_3&s8$|MBc`8s716{(=Zaesb*!VTD?f`Z4#tzNm;v{i&7^W z(3_({wvwQ_Sc^rE@ipYe;bkCZx6@@LxE1$UjQ}9`N}dT~8+4E)5RPQ(iHrDCSrPi( zGA793sk;{m?h|Ce3im7dXntagu79vm@%P=7Ofowm2!-SWV*n|YtUlg^9?nC!;YJhP4 zhfP>25zasY`h~txu=zsK$#&Z?D!W{L#sS^b@y?+){T}{BcG99HrV^J{`v<$$gFI*;I z$b5(_8PM1}&Cm%_jQpXsM#dAC=tg|(DWji z%uLZ-uWJ}R)6AYF36^i{+Rx{I?tSF9NayjO`N$q#dQXk+VpuL`Ss6~+_M_<4V!-_L zW?%7`A^umiS^m3->Q~WAlOBuw9@C;w23@ z4p$eHvvv{_!`H2Kn=;(7APvO(Y}L5}_jR*k4%hH2pZ+qmO~kV3=!LMe6N8_p2|+w7 z@blvIB{#NlsG-SHY}JU3hZ48$z=|_Q3KxX9L>r|mSe2sAogYjy9@Je8kyX$+h&EA4 zaeec!)(p2EMqi~VG}1z8iRx!On(QFFfVZ(-q~MRdYy4QBoEU2ju05eiB+?MFM0zz@ zpDfRsdLVA3_lLEM0tLEWzqz+|gM$cAny4V2q9r9rEg#>Gh$wIWNX4+MS4N&y)um`24NcLW*uU0OI_LCCp0Zk-I^UpH z%WzXiR__|J&paPd^7K&i&oa|`CUUELee#ClYYku`)yv^qtN@O4^ z^cW_Ks?p%>vezcNC*s{_792l@J3%u=F2m=eH1e4mZkh?$QR>htaw+z~B;Y=dyORGG zz+tEd7#RR;c^-$BR!C0X<*vb{VPs7JZ0Lp~P2!<`ojt6R?deM@cIOz4twsT`E^c~> zu|e^VfV|VihpRMHGmwp7?o>Af8IQaG08@}@*#8y*o^NX)_uRou0Z<*27i9PvE*E?9 zE+B$nscnMd*=j^_qWn_IqoRy4LVxCzem*PglJoHVYO{=Idjk3#Jf(IU78-~GR&nMv z)){;q`NPui{YS%Zx^7BlnfT)K^b_cBPDSfhgjiT&s!okvMe9a!?x&mQ5-QoY=2k6% z;Nu>V6PCO1VO}>?9}wj~=kUHMAZPozzN<}G69DB$k7=iG9M7tCH9p^?drtST1j5@S zTep(tg^ScFk`=kZ6s%&^+NA{&HJATFfE{?)vU7*9$Dp)MuIt`qU<9^%g|X`rhK>O3 zyVTm$wm?HFDplCo0EBO!*tyN|YXUMC4M&4a!RSLNCEb|t`aTluL~+_hP!b91LjVdj zDu=gF{Z^oV)Lv?-uB5XOW!6|>6;=}F(eDf&<4W0oAi$~zypp8lp!(~)6A16k ztVyaQext+mf)$NFIV{L{M+Dhg*~!BGNSPqxoqFrmazAaPRx){V3vgcnU;53Kb3_g<U;0H=#~QBW$eIv4g++S4Sk}&`rZ~M zCH*g5if@H_Ey#M)1{wc^ExN?m{v~NVl9>|ydo1b1`wN6-qa%LY;)>qZGH4C<*~tAB z8~xIH%qwIL82ao2aif1)OCYZEn*FLdGeNihxXp}O^QXbVDXjcPLhoP$fZP6G4*ksiDdysT0aqP>Z~y=R literal 4477 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 literal 1672 zcmZ`)3pCSv9N+TVktO%YD?M0LVl62)H&YCo?hHfdzY(sa2WrT;hIA=zamyo*6gH9f zBes;T~44w|r$1#(O3LsnJlbEIiBj&rC@S1pI{exBQrZ(y> zJIJ5P_W-jKVk9eY^ehvR7}os+IX;;?S1D9A7UyT57Tsr|mqxP#MNj0@?`~@NPHiWe z-P0YPtT1j7PyuF`i)1p&j$w%>=QXsHY`0A#&CgkvcOq`x1ZiH(oE9*;-xJ%$sw98tg_}MdoOXb$d>$lD$k^o5#nN=r=jKI06 z`4EqKIpV!}=4EFO0OF`(7t+FC4B{0CqEww@=0i@X)yTF>_}RV~u*hb*-3Wy^2x~#4 zg4kxORQe%b4HM%vBYqxQ+t#@lDU^^t^Kdlz$nyz zqJZ<=?dGTw;(a<#%8_x{GqpiGo*D%M+da)ZDTPzaKIJc9&XCDHC9S@zqlnauE@h254&p@6ISK1;FRxh zf@&};r21Qo4rFiYxf+e#)5xYhd^@F69{>zyo#ar_hL&-cE2OGpxMf0lXGu1{zl>3; z_e3Ct5R&;XEpytUVQ0uGSNSQAbyE@mBok)r_hI!)q*lUKx~6J&Y2Jg6!lHS@T_UYm zqkMU*=!Swzs%OerYah=A>s`^``PeaS>Qj0yE6M{lYA`E7kvyE{LwNThNL`M3b4Q<_ zSPU1@?meIFI{MEazau4FvbchxovryfFXBbXcU7*}aT;v2e!+pcb(`Ek5;UZGL>eI}r&esQ*}1o+3u}|I z@v`sh{KA+3O_5TrvVkZ#4Tx*h-ly`0V7iisbsyMD?wNpv7II(gkUd}=!t(TYwYbw% zES8;v&NW}_Q^Tr!xaN6>;jDzvPX?2E1$64mK>C;R!2ylNTMuTK43uiu zs*A$X<4wud^YN0szaIWbWM?md0k}p7d{*$tclnSvUF7`K& Rw`E@o2#dknRvhq&{}<(U87u$* diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 3be161fdc..5b9bca872 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -4,9 +4,10 @@ const locale = require('locale'); var alarm = -1; -var img = { +var backgroundImage = { width : 176, height : 151, bpp : 3, - buffer : require("heatshrink").decompress(atob("gFz588+YCbEYdx48cATMHz1584CcIAUCpMkyQCZQDqDjQDqD/QdOatOmATKDDhEgwQCZNAZmDAHKDC8hB9Qf6DE8CD/55A9QYegQf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/ABthw0YsCD9ps06dIQftp02aQf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6Dojlx46D+AGaDNAGaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qc9hw0YAQgJBg8cuPHASfAEoUaPQQCIQZkE6ZTCAQgLBnPnz15ASd4E4UTEw4CDQZcBLJILBp88+fPASfIQbcNLJKDqoBBKLJSDpzBAKgZZKQboAXLJaDdAC+TQf8AQBCD4QBCD/Qf6D/Qf6D/Qf6D/Qf6DKDzQAiPQWYIPqDC4CD/fwKD/mhB+Qf6D/AAcYsOGIPwA/AH4A/AH4AHyVJkhB+jlx46D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6DlwCD/48UQf8kyVIkGChACeFAImBFIQCSkCDCAUXEgEB02atICVLKxoPQYM06dNASqD/Qf6D/Qf6D/Qf6D/ARo=")) + transparent : 2, + buffer : require("heatshrink").decompress(atob("gFx48cATgiCC6fAIBGDx048YCcEYXnz15ASCCJQDqDEgM8+fPASCDJQDqD/Qf6DrBpIAzMoXgIPqD/Qf6DGIHqD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4ASsOGjFgQftNmnTpCD9tOmzSD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6DqAGaDNAGaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qc9x48cAQkAgMHBAoCQ4AlCjFhwwCGQaH3799AQl4BQM27dt2wCT2AmCiZ6CARCDLgJrJBYOnz1584CT8AoCjR6CARCDLh6AFQd1AIJTvKQdOYIBUDQAyDhAC6AIQcAAXy6D/Qf4ACQA6D/Qf6D/Qf6D/Qf6D/Qf6DLwANIkGChACVQbweaAESDCUTYAifwaD/vpB+Qf6D/Qf4A/AH4A/AH4ANyVJkBB+jlx46D/pMkQf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6DjwCD/48cQf8kyVIkGChACeFAMo0WKAQ+IDRiDDAUPEgEBDrCDDAUKDCnv3799ASd8Qf6D/Qf6D/Qf6D/Qf4CN")) } var iconPlanet = { @@ -65,7 +66,7 @@ function draw(queue){ g.clearRect(0, 24, g.getWidth(), g.getHeight()); // Draw background image - g.drawImage(img, 0, 24); + g.drawImage(backgroundImage, 0, 24); // Draw symbol var iconImg = From 5d8c13a7e6a934e8b750f0ab4871e3dcc67c09bf Mon Sep 17 00:00:00 2001 From: David Peer Date: Mon, 22 Nov 2021 17:56:29 +0100 Subject: [PATCH 0806/1062] Updated screenshot accordingly --- apps/lcars/screenshot.png | Bin 2838 -> 2743 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/lcars/screenshot.png b/apps/lcars/screenshot.png index a11b0431a060158a92dcdf96a8f9ac6138221e20..7670e4f5cacdb7177356af31bd96d1b02c3712ac 100644 GIT binary patch delta 2719 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 D3IZ-k delta 2814 zcma)-dpy$*7sq8aWF-8MigK;wZkkKUom^^N@RQ3Jxy-%7EUj+{*_6~~s6-@e=4s4j zF-xf2=RP#oez`7$vgPr-o`0S{pYzW-pV#@EKhEoQ-mgRdiy-&d2;1psV|g_`f9d%` zO`#sdE(tZU^UTab=py<3YvOCMyBIR5LiO^g`~RHnpWZ;tX<>?J4K_X}gE%wKxtnh!MExr1ZwRPqV<=vsw4Vy!F6FF?I^ToNkCp5WgcCp~S6s-owy+bWK zOHVF76jHr$TI>}uYRBDas;Pj53`K;n(XNcZC`iSaIuXi?J~A(+Zj#+4fu9+NgBlE? za%;(E5VtU7;P$e%Bxu`jaP`qU-7H5?gJKHLs#|0r%h+kfygHTXl6NQcO1dR zx>k^&L(0p@aRno4x#BQLdgEyEHSldsXea_(ko-_DnESF4aQ6xB`PWJf=LuwxNeL>d z9u~wdPtL5E9?Ai?K7*xtY)5Gd@A`2XB@Ytay*>QsNGkXNut!-TUa~xrohumibmtiH zekg^vx8lJs-uo^oiQsptxwH@fdri}2XhA=ii?zY(7RqW@B{VLh3-8u^59xEI?8F>a zy;&r+BTJusF;eNV6yA0O+y?m8=PjgSsMzn~9to~$na|xHfl&VEfb>zbcw6DY(P4ey z?gKAh;`&#F#GOc`dm58}DhVyG^ICKu5FDb1>40F0KzGsEJva~ugdJk;@}l=&Jw49c zy!L6Jit(Pu&TjOiK40-&S$1O4No@%90ju)cYe(RjjJuN@S3@()bu#9P&KmOm2=GRf z7*m5!wOp|$j=xwySDhJ7P#a-kPSonD{NtZ=sBAN zO!UPTT4Yy0be zGGW_>y&%EYBA(z*xy2;ZW}(3KW4Yi`=88DnT`l5>RP%x$K`WXtG1K$=!p>l9s73d{ z8rwUxqrYO&;bX4Jt-(g$=<`?=okq`jc5 z$eaYe!Huwq5KtWMKvQP9bup~QqWh#xI>glUuDJvMmA^r8E&a?jX!N0IkkeG}yjFS;#{0nI z9Kg&!l=~~U7FP;nYuR})wQt4z>jOCou8W%`Hulbg1_Jaje0BR>e69+ zOFg6equ=<=Jy-Y6CC-39cp~v0>Z#pd=2?okG`T}L1ch4YDR}vFx}w5A$AS{t{G`Mym#~KyYaN4CIG;`Ol<_FH#c!vZ+Y>NxS>?w zrBj`otZ`K3TTJKms|hdGO1eg6hFxsEPe8Jtyeb2E!zYkhbx}+&_GDcHm zsC`_}{q7^v?Z&N;2;Ujd*Ygqy%fADZC`3=w57-U3X!mBvz+zcU3>AX;`5lHdPx!HU z^G5Gl%}@u)!*YPBlx<`m5g-1g!LidL%MUX>77S#fBfaoe9x9DmB*TL~$+_s8C9|#V z!@kk>onC$mCu~+y8NRjMo*+1KG4dao(&H-61}abH#Saj>zK+wom4&s zJ2%x9t0ri_gS(9VsiEoJM3f6(Ud{31$BUKInbw`m?|a^bO+QP#Qn#aHFgQY^0@s{7 ztXr4}<$Rmw8xEemsR7KXT&TCL?+|ont5Zts&Uj+4INpMMKGB z{@lwfyvFzWs{Sm@1Z{S^S-|TsX$dMCAJep6xJ$ov*D_+{l$!?mlhar) zY$-DrHu0w{_e|g~FVSp~Rd*8|w)c;%w9N*UgYdJl-6V&ayhldb4k~*}q z#)j7x8qZCb7}Sr+W{KH{PJ23YcG85?Iz^OR{IeS3v^Y<^A_z2a;2^8hqbz#g#fWNn z+FuJ4=f$tuM-3_rs?Os%R*P7oDcfdRLuu2l=bGc%m11k45}t14hRbU$KVIFqlWoKs zvH7)s%;EZZ!lsTZGJ4?>|+}c5zo0Ty&ThcTib` zau&=yt>Qi<)MFKPPESbD++S9huj2xeX=X8Z(@S@{pFcu|0K_%96|M%Dv{YB^eHyc@ zOaPj2xKbH$k!3;eOdC=(2@E#9D#6$$H#;0L&?|JUP#iwL>XD{L^+RxDlK}~Ph1#Qj zh!0HWlIZR`(u*= Date: Mon, 22 Nov 2021 09:06:52 -0800 Subject: [PATCH 0807/1062] Delete apps/schoolCalendar/fullcalendar/interaction/src directory --- .../interaction/src/ElementScrollGeomCache.ts | 16 - .../interaction/src/OffsetTracker.ts | 76 --- .../interaction/src/ScrollGeomCache.ts | 107 ---- .../interaction/src/WindowScrollGeomCache.ts | 27 - .../interaction/src/api-type-deps.ts | 6 - .../interaction/src/dnd/AutoScroller.ts | 217 -------- .../interaction/src/dnd/ElementMirror.ts | 146 ------ .../src/dnd/FeaturefulElementDragging.ts | 213 -------- .../interaction/src/dnd/PointerDragging.ts | 344 ------------- .../ExternalDraggable.ts | 70 --- .../ExternalElementDragging.ts | 268 ---------- .../InferredElementDragging.ts | 77 --- .../ThirdPartyDraggable.ts | 52 -- .../src/interactions/DateClicking.ts | 71 --- .../src/interactions/DateSelecting.ts | 152 ------ .../src/interactions/EventDragging.ts | 482 ------------------ .../src/interactions/EventResizing.ts | 263 ---------- .../src/interactions/HitDragging.ts | 220 -------- .../src/interactions/UnselectAuto.ts | 77 --- .../interaction/src/main.global.ts | 7 - .../fullcalendar/interaction/src/main.ts | 23 - .../interaction/src/options-declare.ts | 9 - .../fullcalendar/interaction/src/options.ts | 26 - .../fullcalendar/interaction/src/utils.ts | 38 -- 24 files changed, 2987 deletions(-) delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/ElementScrollGeomCache.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/OffsetTracker.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/main.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/options.ts delete mode 100644 apps/schoolCalendar/fullcalendar/interaction/src/utils.ts diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/ElementScrollGeomCache.ts b/apps/schoolCalendar/fullcalendar/interaction/src/ElementScrollGeomCache.ts deleted file mode 100644 index 83be540cd..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/ElementScrollGeomCache.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { computeInnerRect, ElementScrollController } from '@fullcalendar/common' -import { ScrollGeomCache } from './ScrollGeomCache' - -export class ElementScrollGeomCache extends ScrollGeomCache { - constructor(el: HTMLElement, doesListening: boolean) { - super(new ElementScrollController(el), doesListening) - } - - getEventTarget(): EventTarget { - return (this.scrollController as ElementScrollController).el - } - - computeClientRect() { - return computeInnerRect((this.scrollController as ElementScrollController).el) - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/OffsetTracker.ts b/apps/schoolCalendar/fullcalendar/interaction/src/OffsetTracker.ts deleted file mode 100644 index b1fac23e6..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/OffsetTracker.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { - getClippingParents, computeRect, - pointInsideRect, Rect, -} from '@fullcalendar/common' -import { ElementScrollGeomCache } from './ElementScrollGeomCache' - -/* -When this class is instantiated, it records the offset of an element (relative to the document topleft), -and continues to monitor scrolling, updating the cached coordinates if it needs to. -Does not access the DOM after instantiation, so highly performant. - -Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element -and an determine if a given point is inside the combined clipping rectangle. -*/ -export class OffsetTracker { // ElementOffsetTracker - scrollCaches: ElementScrollGeomCache[] - origRect: Rect - - constructor(el: HTMLElement) { - this.origRect = computeRect(el) - - // will work fine for divs that have overflow:hidden - this.scrollCaches = getClippingParents(el).map( - (scrollEl) => new ElementScrollGeomCache(scrollEl, true), // listen=true - ) - } - - destroy() { - for (let scrollCache of this.scrollCaches) { - scrollCache.destroy() - } - } - - computeLeft() { - let left = this.origRect.left - - for (let scrollCache of this.scrollCaches) { - left += scrollCache.origScrollLeft - scrollCache.getScrollLeft() - } - - return left - } - - computeTop() { - let top = this.origRect.top - - for (let scrollCache of this.scrollCaches) { - top += scrollCache.origScrollTop - scrollCache.getScrollTop() - } - - return top - } - - isWithinClipping(pageX: number, pageY: number): boolean { - let point = { left: pageX, top: pageY } - - for (let scrollCache of this.scrollCaches) { - if ( - !isIgnoredClipping(scrollCache.getEventTarget()) && - !pointInsideRect(point, scrollCache.clientRect) - ) { - return false - } - } - - return true - } -} - -// certain clipping containers should never constrain interactions, like and -// https://github.com/fullcalendar/fullcalendar/issues/3615 -function isIgnoredClipping(node: EventTarget) { - let tagName = (node as HTMLElement).tagName - - return tagName === 'HTML' || tagName === 'BODY' -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts b/apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts deleted file mode 100644 index 63ec6a5e1..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/ScrollGeomCache.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Rect, ScrollController } from '@fullcalendar/common' - -/* -Is a cache for a given element's scroll information (all the info that ScrollController stores) -in addition the "client rectangle" of the element.. the area within the scrollbars. - -The cache can be in one of two modes: -- doesListening:false - ignores when the container is scrolled by someone else -- doesListening:true - watch for scrolling and update the cache -*/ -export abstract class ScrollGeomCache extends ScrollController { - clientRect: Rect - origScrollTop: number - origScrollLeft: number - - protected scrollController: ScrollController - protected doesListening: boolean - protected scrollTop: number - protected scrollLeft: number - protected scrollWidth: number - protected scrollHeight: number - protected clientWidth: number - protected clientHeight: number - - constructor(scrollController: ScrollController, doesListening: boolean) { - super() - this.scrollController = scrollController - this.doesListening = doesListening - this.scrollTop = this.origScrollTop = scrollController.getScrollTop() - this.scrollLeft = this.origScrollLeft = scrollController.getScrollLeft() - this.scrollWidth = scrollController.getScrollWidth() - this.scrollHeight = scrollController.getScrollHeight() - this.clientWidth = scrollController.getClientWidth() - this.clientHeight = scrollController.getClientHeight() - this.clientRect = this.computeClientRect() // do last in case it needs cached values - - if (this.doesListening) { - this.getEventTarget().addEventListener('scroll', this.handleScroll) - } - } - - abstract getEventTarget(): EventTarget - abstract computeClientRect(): Rect - - destroy() { - if (this.doesListening) { - this.getEventTarget().removeEventListener('scroll', this.handleScroll) - } - } - - handleScroll = () => { - this.scrollTop = this.scrollController.getScrollTop() - this.scrollLeft = this.scrollController.getScrollLeft() - this.handleScrollChange() - } - - getScrollTop() { - return this.scrollTop - } - - getScrollLeft() { - return this.scrollLeft - } - - setScrollTop(top: number) { - this.scrollController.setScrollTop(top) - - if (!this.doesListening) { - // we are not relying on the element to normalize out-of-bounds scroll values - // so we need to sanitize ourselves - this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0) - - this.handleScrollChange() - } - } - - setScrollLeft(top: number) { - this.scrollController.setScrollLeft(top) - - if (!this.doesListening) { - // we are not relying on the element to normalize out-of-bounds scroll values - // so we need to sanitize ourselves - this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0) - - this.handleScrollChange() - } - } - - getClientWidth() { - return this.clientWidth - } - - getClientHeight() { - return this.clientHeight - } - - getScrollWidth() { - return this.scrollWidth - } - - getScrollHeight() { - return this.scrollHeight - } - - handleScrollChange() { - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts b/apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts deleted file mode 100644 index ca65dee6e..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/WindowScrollGeomCache.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Rect, WindowScrollController } from '@fullcalendar/common' -import { ScrollGeomCache } from './ScrollGeomCache' - -export class WindowScrollGeomCache extends ScrollGeomCache { - constructor(doesListening: boolean) { - super(new WindowScrollController(), doesListening) - } - - getEventTarget(): EventTarget { - return window - } - - computeClientRect(): Rect { - return { - left: this.scrollLeft, - right: this.scrollLeft + this.clientWidth, - top: this.scrollTop, - bottom: this.scrollTop + this.clientHeight, - } - } - - // the window is the only scroll object that changes it's rectangle relative - // to the document's topleft as it scrolls - handleScrollChange() { - this.clientRect = this.computeClientRect() - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts b/apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts deleted file mode 100644 index 2b1b51b06..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/api-type-deps.ts +++ /dev/null @@ -1,6 +0,0 @@ -// TODO: rename file to public-types.ts - -export { DateClickArg } from './interactions/DateClicking' -export { EventDragStartArg, EventDragStopArg } from './interactions/EventDragging' -export { EventResizeStartArg, EventResizeStopArg, EventResizeDoneArg } from './interactions/EventResizing' -export { DropArg, EventReceiveArg, EventLeaveArg } from './utils' diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts deleted file mode 100644 index 8d4e8f015..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/AutoScroller.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { getElRoot } from '@fullcalendar/common' -import { ScrollGeomCache } from '../ScrollGeomCache' -import { ElementScrollGeomCache } from '../ElementScrollGeomCache' -import { WindowScrollGeomCache } from '../WindowScrollGeomCache' - -interface Edge { - scrollCache: ScrollGeomCache - name: 'top' | 'left' | 'right' | 'bottom' - distance: number // how many pixels the current pointer is from the edge -} - -// If available we are using native "performance" API instead of "Date" -// Read more about it on MDN: -// https://developer.mozilla.org/en-US/docs/Web/API/Performance -const getTime = typeof performance === 'function' ? (performance as any).now : Date.now - -/* -For a pointer interaction, automatically scrolls certain scroll containers when the pointer -approaches the edge. - -The caller must call start + handleMove + stop. -*/ -export class AutoScroller { - // options that can be set by caller - isEnabled: boolean = true - scrollQuery: (Window | string)[] = [window, '.fc-scroller'] - edgeThreshold: number = 50 // pixels - maxVelocity: number = 300 // pixels per second - - // internal state - pointerScreenX: number | null = null - pointerScreenY: number | null = null - isAnimating: boolean = false - scrollCaches: ScrollGeomCache[] | null = null - msSinceRequest?: number - - // protect against the initial pointerdown being too close to an edge and starting the scroll - everMovedUp: boolean = false - everMovedDown: boolean = false - everMovedLeft: boolean = false - everMovedRight: boolean = false - - start(pageX: number, pageY: number, scrollStartEl: HTMLElement) { - if (this.isEnabled) { - this.scrollCaches = this.buildCaches(scrollStartEl) - this.pointerScreenX = null - this.pointerScreenY = null - this.everMovedUp = false - this.everMovedDown = false - this.everMovedLeft = false - this.everMovedRight = false - this.handleMove(pageX, pageY) - } - } - - handleMove(pageX: number, pageY: number) { - if (this.isEnabled) { - let pointerScreenX = pageX - window.pageXOffset - let pointerScreenY = pageY - window.pageYOffset - - let yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY - let xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX - - if (yDelta < 0) { - this.everMovedUp = true - } else if (yDelta > 0) { - this.everMovedDown = true - } - - if (xDelta < 0) { - this.everMovedLeft = true - } else if (xDelta > 0) { - this.everMovedRight = true - } - - this.pointerScreenX = pointerScreenX - this.pointerScreenY = pointerScreenY - - if (!this.isAnimating) { - this.isAnimating = true - this.requestAnimation(getTime()) - } - } - } - - stop() { - if (this.isEnabled) { - this.isAnimating = false // will stop animation - - for (let scrollCache of this.scrollCaches!) { - scrollCache.destroy() - } - - this.scrollCaches = null - } - } - - requestAnimation(now: number) { - this.msSinceRequest = now - requestAnimationFrame(this.animate) - } - - private animate = () => { - if (this.isAnimating) { // wasn't cancelled between animation calls - let edge = this.computeBestEdge( - this.pointerScreenX! + window.pageXOffset, - this.pointerScreenY! + window.pageYOffset, - ) - - if (edge) { - let now = getTime() - this.handleSide(edge, (now - this.msSinceRequest!) / 1000) - this.requestAnimation(now) - } else { - this.isAnimating = false // will stop animation - } - } - } - - private handleSide(edge: Edge, seconds: number) { - let { scrollCache } = edge - let { edgeThreshold } = this - let invDistance = edgeThreshold - edge.distance - let velocity = // the closer to the edge, the faster we scroll - ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic - this.maxVelocity * seconds - let sign = 1 - - switch (edge.name) { - case 'left': - sign = -1 - // falls through - case 'right': - scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign) - break - - case 'top': - sign = -1 - // falls through - case 'bottom': - scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign) - break - } - } - - // left/top are relative to document topleft - private computeBestEdge(left: number, top: number): Edge | null { - let { edgeThreshold } = this - let bestSide: Edge | null = null - - for (let scrollCache of this.scrollCaches!) { - let rect = scrollCache.clientRect - let leftDist = left - rect.left - let rightDist = rect.right - left - let topDist = top - rect.top - let bottomDist = rect.bottom - top - - // completely within the rect? - if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) { - if ( - topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() && - (!bestSide || bestSide.distance > topDist) - ) { - bestSide = { scrollCache, name: 'top', distance: topDist } - } - - if ( - bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() && - (!bestSide || bestSide.distance > bottomDist) - ) { - bestSide = { scrollCache, name: 'bottom', distance: bottomDist } - } - - if ( - leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() && - (!bestSide || bestSide.distance > leftDist) - ) { - bestSide = { scrollCache, name: 'left', distance: leftDist } - } - - if ( - rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() && - (!bestSide || bestSide.distance > rightDist) - ) { - bestSide = { scrollCache, name: 'right', distance: rightDist } - } - } - } - - return bestSide - } - - private buildCaches(scrollStartEl: HTMLElement) { - return this.queryScrollEls(scrollStartEl).map((el) => { - if (el === window) { - return new WindowScrollGeomCache(false) // false = don't listen to user-generated scrolls - } - return new ElementScrollGeomCache(el, false) // false = don't listen to user-generated scrolls - }) - } - - private queryScrollEls(scrollStartEl: HTMLElement) { - let els = [] - - for (let query of this.scrollQuery) { - if (typeof query === 'object') { - els.push(query) - } else { - els.push(...Array.prototype.slice.call( - getElRoot(scrollStartEl).querySelectorAll(query), - )) - } - } - - return els - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts deleted file mode 100644 index 6c1e9f4a1..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/ElementMirror.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { removeElement, applyStyle, whenTransitionDone, Rect } from '@fullcalendar/common' - -/* -An effect in which an element follows the movement of a pointer across the screen. -The moving element is a clone of some other element. -Must call start + handleMove + stop. -*/ -export class ElementMirror { - isVisible: boolean = false // must be explicitly enabled - origScreenX?: number - origScreenY?: number - deltaX?: number - deltaY?: number - sourceEl: HTMLElement | null = null - mirrorEl: HTMLElement | null = null - sourceElRect: Rect | null = null // screen coords relative to viewport - - // options that can be set directly by caller - parentNode: HTMLElement = document.body // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues - zIndex: number = 9999 - revertDuration: number = 0 - - start(sourceEl: HTMLElement, pageX: number, pageY: number) { - this.sourceEl = sourceEl - this.sourceElRect = this.sourceEl.getBoundingClientRect() - this.origScreenX = pageX - window.pageXOffset - this.origScreenY = pageY - window.pageYOffset - this.deltaX = 0 - this.deltaY = 0 - this.updateElPosition() - } - - handleMove(pageX: number, pageY: number) { - this.deltaX = (pageX - window.pageXOffset) - this.origScreenX! - this.deltaY = (pageY - window.pageYOffset) - this.origScreenY! - this.updateElPosition() - } - - // can be called before start - setIsVisible(bool: boolean) { - if (bool) { - if (!this.isVisible) { - if (this.mirrorEl) { - this.mirrorEl.style.display = '' - } - - this.isVisible = bool // needs to happen before updateElPosition - this.updateElPosition() // because was not updating the position while invisible - } - } else if (this.isVisible) { - if (this.mirrorEl) { - this.mirrorEl.style.display = 'none' - } - - this.isVisible = bool - } - } - - // always async - stop(needsRevertAnimation: boolean, callback: () => void) { - let done = () => { - this.cleanup() - callback() - } - - if ( - needsRevertAnimation && - this.mirrorEl && - this.isVisible && - this.revertDuration && // if 0, transition won't work - (this.deltaX || this.deltaY) // if same coords, transition won't work - ) { - this.doRevertAnimation(done, this.revertDuration) - } else { - setTimeout(done, 0) - } - } - - doRevertAnimation(callback: () => void, revertDuration: number) { - let mirrorEl = this.mirrorEl! - let finalSourceElRect = this.sourceEl!.getBoundingClientRect() // because autoscrolling might have happened - - mirrorEl.style.transition = - 'top ' + revertDuration + 'ms,' + - 'left ' + revertDuration + 'ms' - - applyStyle(mirrorEl, { - left: finalSourceElRect.left, - top: finalSourceElRect.top, - }) - - whenTransitionDone(mirrorEl, () => { - mirrorEl.style.transition = '' - callback() - }) - } - - cleanup() { - if (this.mirrorEl) { - removeElement(this.mirrorEl) - this.mirrorEl = null - } - - this.sourceEl = null - } - - updateElPosition() { - if (this.sourceEl && this.isVisible) { - applyStyle(this.getMirrorEl(), { - left: this.sourceElRect!.left + this.deltaX!, - top: this.sourceElRect!.top + this.deltaY!, - }) - } - } - - getMirrorEl(): HTMLElement { - let sourceElRect = this.sourceElRect! - let mirrorEl = this.mirrorEl - - if (!mirrorEl) { - mirrorEl = this.mirrorEl = this.sourceEl!.cloneNode(true) as HTMLElement // cloneChildren=true - - // we don't want long taps or any mouse interaction causing selection/menus. - // would use preventSelection(), but that prevents selectstart, causing problems. - mirrorEl.classList.add('fc-unselectable') - - mirrorEl.classList.add('fc-event-dragging') - - applyStyle(mirrorEl, { - position: 'fixed', - zIndex: this.zIndex, - visibility: '', // in case original element was hidden by the drag effect - boxSizing: 'border-box', // for easy width/height - width: sourceElRect.right - sourceElRect.left, // explicit height in case there was a 'right' value - height: sourceElRect.bottom - sourceElRect.top, // explicit width in case there was a 'bottom' value - right: 'auto', // erase and set width instead - bottom: 'auto', // erase and set height instead - margin: 0, - }) - - this.parentNode.appendChild(mirrorEl) - } - - return mirrorEl - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts deleted file mode 100644 index 3f1c7826b..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { - PointerDragEvent, - preventSelection, - allowSelection, - preventContextMenu, - allowContextMenu, - ElementDragging, -} from '@fullcalendar/common' -import { PointerDragging } from './PointerDragging' -import { ElementMirror } from './ElementMirror' -import { AutoScroller } from './AutoScroller' - -/* -Monitors dragging on an element. Has a number of high-level features: -- minimum distance required before dragging -- minimum wait time ("delay") before dragging -- a mirror element that follows the pointer -*/ -export class FeaturefulElementDragging extends ElementDragging { - pointer: PointerDragging - mirror: ElementMirror - autoScroller: AutoScroller - - // options that can be directly set by caller - // the caller can also set the PointerDragging's options as well - delay: number | null = null - minDistance: number = 0 - touchScrollAllowed: boolean = true // prevents drag from starting and blocks scrolling during drag - - mirrorNeedsRevert: boolean = false - isInteracting: boolean = false // is the user validly moving the pointer? lasts until pointerup - isDragging: boolean = false // is it INTENTFULLY dragging? lasts until after revert animation - isDelayEnded: boolean = false - isDistanceSurpassed: boolean = false - delayTimeoutId: number | null = null - - constructor(private containerEl: HTMLElement, selector?: string) { - super(containerEl) - - let pointer = this.pointer = new PointerDragging(containerEl) - pointer.emitter.on('pointerdown', this.onPointerDown) - pointer.emitter.on('pointermove', this.onPointerMove) - pointer.emitter.on('pointerup', this.onPointerUp) - - if (selector) { - pointer.selector = selector - } - - this.mirror = new ElementMirror() - this.autoScroller = new AutoScroller() - } - - destroy() { - this.pointer.destroy() - - // HACK: simulate a pointer-up to end the current drag - // TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire) - this.onPointerUp({} as any) - } - - onPointerDown = (ev: PointerDragEvent) => { - if (!this.isDragging) { // so new drag doesn't happen while revert animation is going - this.isInteracting = true - this.isDelayEnded = false - this.isDistanceSurpassed = false - - preventSelection(document.body) - preventContextMenu(document.body) - - // prevent links from being visited if there's an eventual drag. - // also prevents selection in older browsers (maybe?). - // not necessary for touch, besides, browser would complain about passiveness. - if (!ev.isTouch) { - ev.origEvent.preventDefault() - } - - this.emitter.trigger('pointerdown', ev) - - if ( - this.isInteracting && // not destroyed via pointerdown handler - !this.pointer.shouldIgnoreMove - ) { - // actions related to initiating dragstart+dragmove+dragend... - - this.mirror.setIsVisible(false) // reset. caller must set-visible - this.mirror.start(ev.subjectEl as HTMLElement, ev.pageX, ev.pageY) // must happen on first pointer down - - this.startDelay(ev) - - if (!this.minDistance) { - this.handleDistanceSurpassed(ev) - } - } - } - } - - onPointerMove = (ev: PointerDragEvent) => { - if (this.isInteracting) { - this.emitter.trigger('pointermove', ev) - - if (!this.isDistanceSurpassed) { - let minDistance = this.minDistance - let distanceSq // current distance from the origin, squared - let { deltaX, deltaY } = ev - - distanceSq = deltaX * deltaX + deltaY * deltaY - if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem - this.handleDistanceSurpassed(ev) - } - } - - if (this.isDragging) { - // a real pointer move? (not one simulated by scrolling) - if (ev.origEvent.type !== 'scroll') { - this.mirror.handleMove(ev.pageX, ev.pageY) - this.autoScroller.handleMove(ev.pageX, ev.pageY) - } - - this.emitter.trigger('dragmove', ev) - } - } - } - - onPointerUp = (ev: PointerDragEvent) => { - if (this.isInteracting) { - this.isInteracting = false - - allowSelection(document.body) - allowContextMenu(document.body) - - this.emitter.trigger('pointerup', ev) // can potentially set mirrorNeedsRevert - - if (this.isDragging) { - this.autoScroller.stop() - this.tryStopDrag(ev) // which will stop the mirror - } - - if (this.delayTimeoutId) { - clearTimeout(this.delayTimeoutId) - this.delayTimeoutId = null - } - } - } - - startDelay(ev: PointerDragEvent) { - if (typeof this.delay === 'number') { - this.delayTimeoutId = setTimeout(() => { - this.delayTimeoutId = null - this.handleDelayEnd(ev) - }, this.delay) as any // not assignable to number! - } else { - this.handleDelayEnd(ev) - } - } - - handleDelayEnd(ev: PointerDragEvent) { - this.isDelayEnded = true - this.tryStartDrag(ev) - } - - handleDistanceSurpassed(ev: PointerDragEvent) { - this.isDistanceSurpassed = true - this.tryStartDrag(ev) - } - - tryStartDrag(ev: PointerDragEvent) { - if (this.isDelayEnded && this.isDistanceSurpassed) { - if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) { - this.isDragging = true - this.mirrorNeedsRevert = false - - this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl) - this.emitter.trigger('dragstart', ev) - - if (this.touchScrollAllowed === false) { - this.pointer.cancelTouchScroll() - } - } - } - } - - tryStopDrag(ev: PointerDragEvent) { - // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events - // that come from the document to fire beforehand. much more convenient this way. - this.mirror.stop( - this.mirrorNeedsRevert, - this.stopDrag.bind(this, ev), // bound with args - ) - } - - stopDrag(ev: PointerDragEvent) { - this.isDragging = false - this.emitter.trigger('dragend', ev) - } - - // fill in the implementations... - - setIgnoreMove(bool: boolean) { - this.pointer.shouldIgnoreMove = bool - } - - setMirrorIsVisible(bool: boolean) { - this.mirror.setIsVisible(bool) - } - - setMirrorNeedsRevert(bool: boolean) { - this.mirrorNeedsRevert = bool - } - - setAutoScrollEnabled(bool: boolean) { - this.autoScroller.isEnabled = bool - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts deleted file mode 100644 index dbe7d8abb..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/dnd/PointerDragging.ts +++ /dev/null @@ -1,344 +0,0 @@ -import { config, elementClosest, Emitter, PointerDragEvent } from '@fullcalendar/common' - -config.touchMouseIgnoreWait = 500 - -let ignoreMouseDepth = 0 -let listenerCnt = 0 -let isWindowTouchMoveCancelled = false - -/* -Uses a "pointer" abstraction, which monitors UI events for both mouse and touch. -Tracks when the pointer "drags" on a certain element, meaning down+move+up. - -Also, tracks if there was touch-scrolling. -Also, can prevent touch-scrolling from happening. -Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement. - -emits: -- pointerdown -- pointermove -- pointerup -*/ -export class PointerDragging { - containerEl: EventTarget - subjectEl: HTMLElement | null = null - emitter: Emitter - - // options that can be directly assigned by caller - selector: string = '' // will cause subjectEl in all emitted events to be this element - handleSelector: string = '' - shouldIgnoreMove: boolean = false - shouldWatchScroll: boolean = true // for simulating pointermove on scroll - - // internal states - isDragging: boolean = false - isTouchDragging: boolean = false - wasTouchScroll: boolean = false - origPageX: number - origPageY: number - prevPageX: number - prevPageY: number - prevScrollX: number // at time of last pointer pageX/pageY capture - prevScrollY: number // " - - constructor(containerEl: EventTarget) { - this.containerEl = containerEl - this.emitter = new Emitter() - containerEl.addEventListener('mousedown', this.handleMouseDown as EventListener) - containerEl.addEventListener('touchstart', this.handleTouchStart as EventListener, { passive: true }) - listenerCreated() - } - - destroy() { - this.containerEl.removeEventListener('mousedown', this.handleMouseDown as EventListener) - this.containerEl.removeEventListener('touchstart', this.handleTouchStart as EventListener, { passive: true } as AddEventListenerOptions) - listenerDestroyed() - } - - tryStart(ev: UIEvent): boolean { - let subjectEl = this.querySubjectEl(ev) - let downEl = ev.target as HTMLElement - - if ( - subjectEl && - (!this.handleSelector || elementClosest(downEl, this.handleSelector)) - ) { - this.subjectEl = subjectEl - this.isDragging = true // do this first so cancelTouchScroll will work - this.wasTouchScroll = false - - return true - } - - return false - } - - cleanup() { - isWindowTouchMoveCancelled = false - this.isDragging = false - this.subjectEl = null - // keep wasTouchScroll around for later access - this.destroyScrollWatch() - } - - querySubjectEl(ev: UIEvent): HTMLElement { - if (this.selector) { - return elementClosest(ev.target as HTMLElement, this.selector) - } - return this.containerEl as HTMLElement - } - - // Mouse - // ---------------------------------------------------------------------------------------------------- - - handleMouseDown = (ev: MouseEvent) => { - if ( - !this.shouldIgnoreMouse() && - isPrimaryMouseButton(ev) && - this.tryStart(ev) - ) { - let pev = this.createEventFromMouse(ev, true) - this.emitter.trigger('pointerdown', pev) - this.initScrollWatch(pev) - - if (!this.shouldIgnoreMove) { - document.addEventListener('mousemove', this.handleMouseMove) - } - - document.addEventListener('mouseup', this.handleMouseUp) - } - } - - handleMouseMove = (ev: MouseEvent) => { - let pev = this.createEventFromMouse(ev) - this.recordCoords(pev) - this.emitter.trigger('pointermove', pev) - } - - handleMouseUp = (ev: MouseEvent) => { - document.removeEventListener('mousemove', this.handleMouseMove) - document.removeEventListener('mouseup', this.handleMouseUp) - - this.emitter.trigger('pointerup', this.createEventFromMouse(ev)) - - this.cleanup() // call last so that pointerup has access to props - } - - shouldIgnoreMouse() { - return ignoreMouseDepth || this.isTouchDragging - } - - // Touch - // ---------------------------------------------------------------------------------------------------- - - handleTouchStart = (ev: TouchEvent) => { - if (this.tryStart(ev)) { - this.isTouchDragging = true - - let pev = this.createEventFromTouch(ev, true) - this.emitter.trigger('pointerdown', pev) - this.initScrollWatch(pev) - - // unlike mouse, need to attach to target, not document - // https://stackoverflow.com/a/45760014 - let targetEl = ev.target as HTMLElement - - if (!this.shouldIgnoreMove) { - targetEl.addEventListener('touchmove', this.handleTouchMove) - } - - targetEl.addEventListener('touchend', this.handleTouchEnd) - targetEl.addEventListener('touchcancel', this.handleTouchEnd) // treat it as a touch end - - // attach a handler to get called when ANY scroll action happens on the page. - // this was impossible to do with normal on/off because 'scroll' doesn't bubble. - // http://stackoverflow.com/a/32954565/96342 - window.addEventListener( - 'scroll', - this.handleTouchScroll, - true, // useCapture - ) - } - } - - handleTouchMove = (ev: TouchEvent) => { - let pev = this.createEventFromTouch(ev) - this.recordCoords(pev) - this.emitter.trigger('pointermove', pev) - } - - handleTouchEnd = (ev: TouchEvent) => { - if (this.isDragging) { // done to guard against touchend followed by touchcancel - let targetEl = ev.target as HTMLElement - - targetEl.removeEventListener('touchmove', this.handleTouchMove) - targetEl.removeEventListener('touchend', this.handleTouchEnd) - targetEl.removeEventListener('touchcancel', this.handleTouchEnd) - window.removeEventListener('scroll', this.handleTouchScroll, true) // useCaptured=true - - this.emitter.trigger('pointerup', this.createEventFromTouch(ev)) - - this.cleanup() // call last so that pointerup has access to props - this.isTouchDragging = false - startIgnoringMouse() - } - } - - handleTouchScroll = () => { - this.wasTouchScroll = true - } - - // can be called by user of this class, to cancel touch-based scrolling for the current drag - cancelTouchScroll() { - if (this.isDragging) { - isWindowTouchMoveCancelled = true - } - } - - // Scrolling that simulates pointermoves - // ---------------------------------------------------------------------------------------------------- - - initScrollWatch(ev: PointerDragEvent) { - if (this.shouldWatchScroll) { - this.recordCoords(ev) - window.addEventListener('scroll', this.handleScroll, true) // useCapture=true - } - } - - recordCoords(ev: PointerDragEvent) { - if (this.shouldWatchScroll) { - this.prevPageX = (ev as any).pageX - this.prevPageY = (ev as any).pageY - this.prevScrollX = window.pageXOffset - this.prevScrollY = window.pageYOffset - } - } - - handleScroll = (ev: UIEvent) => { - if (!this.shouldIgnoreMove) { - let pageX = (window.pageXOffset - this.prevScrollX) + this.prevPageX - let pageY = (window.pageYOffset - this.prevScrollY) + this.prevPageY - - this.emitter.trigger('pointermove', { - origEvent: ev, - isTouch: this.isTouchDragging, - subjectEl: this.subjectEl, - pageX, - pageY, - deltaX: pageX - this.origPageX, - deltaY: pageY - this.origPageY, - } as PointerDragEvent) - } - } - - destroyScrollWatch() { - if (this.shouldWatchScroll) { - window.removeEventListener('scroll', this.handleScroll, true) // useCaptured=true - } - } - - // Event Normalization - // ---------------------------------------------------------------------------------------------------- - - createEventFromMouse(ev: MouseEvent, isFirst?: boolean): PointerDragEvent { - let deltaX = 0 - let deltaY = 0 - - // TODO: repeat code - if (isFirst) { - this.origPageX = ev.pageX - this.origPageY = ev.pageY - } else { - deltaX = ev.pageX - this.origPageX - deltaY = ev.pageY - this.origPageY - } - - return { - origEvent: ev, - isTouch: false, - subjectEl: this.subjectEl, - pageX: ev.pageX, - pageY: ev.pageY, - deltaX, - deltaY, - } - } - - createEventFromTouch(ev: TouchEvent, isFirst?: boolean): PointerDragEvent { - let touches = ev.touches - let pageX - let pageY - let deltaX = 0 - let deltaY = 0 - - // if touch coords available, prefer, - // because FF would give bad ev.pageX ev.pageY - if (touches && touches.length) { - pageX = touches[0].pageX - pageY = touches[0].pageY - } else { - pageX = (ev as any).pageX - pageY = (ev as any).pageY - } - - // TODO: repeat code - if (isFirst) { - this.origPageX = pageX - this.origPageY = pageY - } else { - deltaX = pageX - this.origPageX - deltaY = pageY - this.origPageY - } - - return { - origEvent: ev, - isTouch: true, - subjectEl: this.subjectEl, - pageX, - pageY, - deltaX, - deltaY, - } - } -} - -// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) -function isPrimaryMouseButton(ev: MouseEvent) { - return ev.button === 0 && !ev.ctrlKey -} - -// Ignoring fake mouse events generated by touch -// ---------------------------------------------------------------------------------------------------- - -function startIgnoringMouse() { // can be made non-class function - ignoreMouseDepth += 1 - - setTimeout(() => { - ignoreMouseDepth -= 1 - }, config.touchMouseIgnoreWait) -} - -// We want to attach touchmove as early as possible for Safari -// ---------------------------------------------------------------------------------------------------- - -function listenerCreated() { - listenerCnt += 1 - - if (listenerCnt === 1) { - window.addEventListener('touchmove', onWindowTouchMove, { passive: false }) - } -} - -function listenerDestroyed() { - listenerCnt -= 1 - - if (!listenerCnt) { - window.removeEventListener('touchmove', onWindowTouchMove, { passive: false } as AddEventListenerOptions) - } -} - -function onWindowTouchMove(ev: UIEvent) { - if (isWindowTouchMoveCancelled) { - ev.preventDefault() - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts deleted file mode 100644 index 22aba108d..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { BASE_OPTION_DEFAULTS, PointerDragEvent } from '@fullcalendar/common' -import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' -import { ExternalElementDragging, DragMetaGenerator } from './ExternalElementDragging' - -export interface ExternalDraggableSettings { - eventData?: DragMetaGenerator - itemSelector?: string - minDistance?: number - longPressDelay?: number - appendTo?: HTMLElement -} - -/* -Makes an element (that is *external* to any calendar) draggable. -Can pass in data that determines how an event will be created when dropped onto a calendar. -Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system. -*/ -export class ExternalDraggable { - dragging: FeaturefulElementDragging - settings: ExternalDraggableSettings - - constructor(el: HTMLElement, settings: ExternalDraggableSettings = {}) { - this.settings = settings - - let dragging = this.dragging = new FeaturefulElementDragging(el) - dragging.touchScrollAllowed = false - - if (settings.itemSelector != null) { - dragging.pointer.selector = settings.itemSelector - } - - if (settings.appendTo != null) { - dragging.mirror.parentNode = settings.appendTo // TODO: write tests - } - - dragging.emitter.on('pointerdown', this.handlePointerDown) - dragging.emitter.on('dragstart', this.handleDragStart) - - new ExternalElementDragging(dragging, settings.eventData) // eslint-disable-line no-new - } - - handlePointerDown = (ev: PointerDragEvent) => { - let { dragging } = this - let { minDistance, longPressDelay } = this.settings - - dragging.minDistance = - minDistance != null ? - minDistance : - (ev.isTouch ? 0 : BASE_OPTION_DEFAULTS.eventDragMinDistance) - - dragging.delay = - ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv - (longPressDelay != null ? longPressDelay : BASE_OPTION_DEFAULTS.longPressDelay) : - 0 - } - - handleDragStart = (ev: PointerDragEvent) => { - if ( - ev.isTouch && - this.dragging.delay && - (ev.subjectEl as HTMLElement).classList.contains('fc-event') - ) { - this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected') - } - } - - destroy() { - this.dragging.destroy() - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts deleted file mode 100644 index 95ac7e0c2..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { - Hit, - interactionSettingsStore, - PointerDragEvent, - parseEventDef, createEventInstance, EventTuple, - createEmptyEventStore, eventTupleToStore, - config, - DateSpan, DatePointApi, - EventInteractionState, - DragMetaInput, DragMeta, parseDragMeta, - EventApi, - elementMatches, - enableCursor, disableCursor, - isInteractionValid, - ElementDragging, - ViewApi, - CalendarContext, - getDefaultEventEnd, - refineEventDef, -} from '@fullcalendar/common' -import { __assign } from 'tslib' -import { HitDragging } from '../interactions/HitDragging' -import { buildDatePointApiWithContext } from '../utils' - -export type DragMetaGenerator = DragMetaInput | ((el: HTMLElement) => DragMetaInput) - -export interface ExternalDropApi extends DatePointApi { - draggedEl: HTMLElement - jsEvent: UIEvent - view: ViewApi -} - -/* -Given an already instantiated draggable object for one-or-more elements, -Interprets any dragging as an attempt to drag an events that lives outside -of a calendar onto a calendar. -*/ -export class ExternalElementDragging { - hitDragging: HitDragging - receivingContext: CalendarContext | null = null - droppableEvent: EventTuple | null = null // will exist for all drags, even if create:false - suppliedDragMeta: DragMetaGenerator | null = null - dragMeta: DragMeta | null = null - - constructor(dragging: ElementDragging, suppliedDragMeta?: DragMetaGenerator) { - let hitDragging = this.hitDragging = new HitDragging(dragging, interactionSettingsStore) - hitDragging.requireInitial = false // will start outside of a component - hitDragging.emitter.on('dragstart', this.handleDragStart) - hitDragging.emitter.on('hitupdate', this.handleHitUpdate) - hitDragging.emitter.on('dragend', this.handleDragEnd) - - this.suppliedDragMeta = suppliedDragMeta - } - - handleDragStart = (ev: PointerDragEvent) => { - this.dragMeta = this.buildDragMeta(ev.subjectEl as HTMLElement) - } - - buildDragMeta(subjectEl: HTMLElement) { - if (typeof this.suppliedDragMeta === 'object') { - return parseDragMeta(this.suppliedDragMeta) - } - if (typeof this.suppliedDragMeta === 'function') { - return parseDragMeta(this.suppliedDragMeta(subjectEl)) - } - return getDragMetaFromEl(subjectEl) - } - - handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => { - let { dragging } = this.hitDragging - let receivingContext: CalendarContext | null = null - let droppableEvent: EventTuple | null = null - let isInvalid = false - let interaction: EventInteractionState = { - affectedEvents: createEmptyEventStore(), - mutatedEvents: createEmptyEventStore(), - isEvent: this.dragMeta!.create, - } - - if (hit) { - receivingContext = hit.context - - if (this.canDropElOnCalendar(ev.subjectEl as HTMLElement, receivingContext)) { - droppableEvent = computeEventForDateSpan( - hit.dateSpan, - this.dragMeta!, - receivingContext, - ) - - interaction.mutatedEvents = eventTupleToStore(droppableEvent) - isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext) - - if (isInvalid) { - interaction.mutatedEvents = createEmptyEventStore() - droppableEvent = null - } - } - } - - this.displayDrag(receivingContext, interaction) - - // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?) - // TODO: wish we could somehow wait for dispatch to guarantee render - dragging.setMirrorIsVisible( - isFinal || !droppableEvent || !document.querySelector('.fc-event-mirror'), // TODO: turn className into constant - // TODO: somehow query FullCalendars WITHIN shadow-roots for existing event-mirror els - ) - - if (!isInvalid) { - enableCursor() - } else { - disableCursor() - } - - if (!isFinal) { - dragging.setMirrorNeedsRevert(!droppableEvent) - - this.receivingContext = receivingContext - this.droppableEvent = droppableEvent - } - } - - handleDragEnd = (pev: PointerDragEvent) => { - let { receivingContext, droppableEvent } = this - - this.clearDrag() - - if (receivingContext && droppableEvent) { - let finalHit = this.hitDragging.finalHit! - let finalView = finalHit.context.viewApi - let dragMeta = this.dragMeta! - - receivingContext.emitter.trigger('drop', { - ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), - draggedEl: pev.subjectEl as HTMLElement, - jsEvent: pev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 - view: finalView, - }) - - if (dragMeta.create) { - let addingEvents = eventTupleToStore(droppableEvent) - - receivingContext.dispatch({ - type: 'MERGE_EVENTS', - eventStore: addingEvents, - }) - - if (pev.isTouch) { - receivingContext.dispatch({ - type: 'SELECT_EVENT', - eventInstanceId: droppableEvent.instance.instanceId, - }) - } - - // signal that an external event landed - receivingContext.emitter.trigger('eventReceive', { - event: new EventApi( - receivingContext, - droppableEvent.def, - droppableEvent.instance, - ), - relatedEvents: [], - revert() { - receivingContext.dispatch({ - type: 'REMOVE_EVENTS', - eventStore: addingEvents, - }) - }, - draggedEl: pev.subjectEl as HTMLElement, - view: finalView, - }) - } - } - - this.receivingContext = null - this.droppableEvent = null - } - - displayDrag(nextContext: CalendarContext | null, state: EventInteractionState) { - let prevContext = this.receivingContext - - if (prevContext && prevContext !== nextContext) { - prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) - } - - if (nextContext) { - nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }) - } - } - - clearDrag() { - if (this.receivingContext) { - this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) - } - } - - canDropElOnCalendar(el: HTMLElement, receivingContext: CalendarContext): boolean { - let dropAccept = receivingContext.options.dropAccept - - if (typeof dropAccept === 'function') { - return dropAccept.call(receivingContext.calendarApi, el) - } - - if (typeof dropAccept === 'string' && dropAccept) { - return Boolean(elementMatches(el, dropAccept)) - } - - return true - } -} - -// Utils for computing event store from the DragMeta -// ---------------------------------------------------------------------------------------------------- - -function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, context: CalendarContext): EventTuple { - let defProps = { ...dragMeta.leftoverProps } - - for (let transform of context.pluginHooks.externalDefTransforms) { - __assign(defProps, transform(dateSpan, dragMeta)) - } - - let { refined, extra } = refineEventDef(defProps, context) - let def = parseEventDef( - refined, - extra, - dragMeta.sourceId, - dateSpan.allDay, - context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd - context, - ) - - let start = dateSpan.range.start - - // only rely on time info if drop zone is all-day, - // otherwise, we already know the time - if (dateSpan.allDay && dragMeta.startTime) { - start = context.dateEnv.add(start, dragMeta.startTime) - } - - let end = dragMeta.duration ? - context.dateEnv.add(start, dragMeta.duration) : - getDefaultEventEnd(dateSpan.allDay, start, context) - - let instance = createEventInstance(def.defId, { start, end }) - - return { def, instance } -} - -// Utils for extracting data from element -// ---------------------------------------------------------------------------------------------------- - -function getDragMetaFromEl(el: HTMLElement): DragMeta { - let str = getEmbeddedElData(el, 'event') - let obj = str ? - JSON.parse(str) : - { create: false } // if no embedded data, assume no event creation - - return parseDragMeta(obj) -} - -config.dataAttrPrefix = '' - -function getEmbeddedElData(el: HTMLElement, name: string): string { - let prefix = config.dataAttrPrefix - let prefixedName = (prefix ? prefix + '-' : '') + name - - return el.getAttribute('data-' + prefixedName) || '' -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts deleted file mode 100644 index ab03cec00..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { PointerDragEvent, ElementDragging } from '@fullcalendar/common' -import { PointerDragging } from '../dnd/PointerDragging' - -/* -Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements. -The third-party system is responsible for drawing the visuals effects of the drag. -This class simply monitors for pointer movements and fires events. -It also has the ability to hide the moving element (the "mirror") during the drag. -*/ -export class InferredElementDragging extends ElementDragging { - pointer: PointerDragging - shouldIgnoreMove: boolean = false - mirrorSelector: string = '' - currentMirrorEl: HTMLElement | null = null - - constructor(containerEl: HTMLElement) { - super(containerEl) - - let pointer = this.pointer = new PointerDragging(containerEl) - pointer.emitter.on('pointerdown', this.handlePointerDown) - pointer.emitter.on('pointermove', this.handlePointerMove) - pointer.emitter.on('pointerup', this.handlePointerUp) - } - - destroy() { - this.pointer.destroy() - } - - handlePointerDown = (ev: PointerDragEvent) => { - this.emitter.trigger('pointerdown', ev) - - if (!this.shouldIgnoreMove) { - // fire dragstart right away. does not support delay or min-distance - this.emitter.trigger('dragstart', ev) - } - } - - handlePointerMove = (ev: PointerDragEvent) => { - if (!this.shouldIgnoreMove) { - this.emitter.trigger('dragmove', ev) - } - } - - handlePointerUp = (ev: PointerDragEvent) => { - this.emitter.trigger('pointerup', ev) - - if (!this.shouldIgnoreMove) { - // fire dragend right away. does not support a revert animation - this.emitter.trigger('dragend', ev) - } - } - - setIgnoreMove(bool: boolean) { - this.shouldIgnoreMove = bool - } - - setMirrorIsVisible(bool: boolean) { - if (bool) { - // restore a previously hidden element. - // use the reference in case the selector class has already been removed. - if (this.currentMirrorEl) { - this.currentMirrorEl.style.visibility = '' - this.currentMirrorEl = null - } - } else { - let mirrorEl = this.mirrorSelector - // TODO: somehow query FullCalendars WITHIN shadow-roots - ? document.querySelector(this.mirrorSelector) as HTMLElement - : null - - if (mirrorEl) { - this.currentMirrorEl = mirrorEl - mirrorEl.style.visibility = 'hidden' - } - } - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts deleted file mode 100644 index 324b22255..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ExternalElementDragging, DragMetaGenerator } from './ExternalElementDragging' -import { InferredElementDragging } from './InferredElementDragging' - -export interface ThirdPartyDraggableSettings { - eventData?: DragMetaGenerator - itemSelector?: string - mirrorSelector?: string -} - -/* -Bridges third-party drag-n-drop systems with FullCalendar. -Must be instantiated and destroyed by caller. -*/ -export class ThirdPartyDraggable { - dragging: InferredElementDragging - - constructor( - containerOrSettings?: EventTarget | ThirdPartyDraggableSettings, - settings?: ThirdPartyDraggableSettings, - ) { - let containerEl: EventTarget = document - - if ( - // wish we could just test instanceof EventTarget, but doesn't work in IE11 - containerOrSettings === document || - containerOrSettings instanceof Element - ) { - containerEl = containerOrSettings as EventTarget - settings = settings || {} - } else { - settings = (containerOrSettings || {}) as ThirdPartyDraggableSettings - } - - let dragging = this.dragging = new InferredElementDragging(containerEl as HTMLElement) - - if (typeof settings.itemSelector === 'string') { - dragging.pointer.selector = settings.itemSelector - } else if (containerEl === document) { - dragging.pointer.selector = '[data-event]' - } - - if (typeof settings.mirrorSelector === 'string') { - dragging.mirrorSelector = settings.mirrorSelector - } - - new ExternalElementDragging(dragging, settings.eventData) // eslint-disable-line no-new - } - - destroy() { - this.dragging.destroy() - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts deleted file mode 100644 index 06b352de9..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateClicking.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - PointerDragEvent, Interaction, InteractionSettings, interactionSettingsToStore, - DatePointApi, - ViewApi, -} from '@fullcalendar/common' -import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' -import { HitDragging, isHitsEqual } from './HitDragging' -import { buildDatePointApiWithContext } from '../utils' - -export interface DateClickArg extends DatePointApi { - dayEl: HTMLElement - jsEvent: MouseEvent - view: ViewApi -} - -/* -Monitors when the user clicks on a specific date/time of a component. -A pointerdown+pointerup on the same "hit" constitutes a click. -*/ -export class DateClicking extends Interaction { - dragging: FeaturefulElementDragging - hitDragging: HitDragging - - constructor(settings: InteractionSettings) { - super(settings) - - // we DO want to watch pointer moves because otherwise finalHit won't get populated - this.dragging = new FeaturefulElementDragging(settings.el) - this.dragging.autoScroller.isEnabled = false - - let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)) - hitDragging.emitter.on('pointerdown', this.handlePointerDown) - hitDragging.emitter.on('dragend', this.handleDragEnd) - } - - destroy() { - this.dragging.destroy() - } - - handlePointerDown = (pev: PointerDragEvent) => { - let { dragging } = this - let downEl = pev.origEvent.target as HTMLElement - - // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired - dragging.setIgnoreMove( - !this.component.isValidDateDownEl(downEl), - ) - } - - // won't even fire if moving was ignored - handleDragEnd = (ev: PointerDragEvent) => { - let { component } = this - let { pointer } = this.dragging - - if (!pointer.wasTouchScroll) { - let { initialHit, finalHit } = this.hitDragging - - if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) { - let { context } = component - let arg: DateClickArg = { - ...buildDatePointApiWithContext(initialHit.dateSpan, context), - dayEl: initialHit.dayEl, - jsEvent: ev.origEvent as MouseEvent, - view: context.viewApi || context.calendarApi.view, - } - - context.emitter.trigger('dateClick', arg) - } - } - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts deleted file mode 100644 index fa6b3ff09..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/DateSelecting.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { - compareNumbers, enableCursor, disableCursor, DateComponent, Hit, - DateSpan, PointerDragEvent, dateSelectionJoinTransformer, - Interaction, InteractionSettings, interactionSettingsToStore, - triggerDateSelect, isDateSelectionValid, -} from '@fullcalendar/common' -import { __assign } from 'tslib' -import { HitDragging } from './HitDragging' -import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' - -/* -Tracks when the user selects a portion of time of a component, -constituted by a drag over date cells, with a possible delay at the beginning of the drag. -*/ -export class DateSelecting extends Interaction { - dragging: FeaturefulElementDragging - hitDragging: HitDragging - dragSelection: DateSpan | null = null - - constructor(settings: InteractionSettings) { - super(settings) - let { component } = settings - let { options } = component.context - - let dragging = this.dragging = new FeaturefulElementDragging(settings.el) - dragging.touchScrollAllowed = false - dragging.minDistance = options.selectMinDistance || 0 - dragging.autoScroller.isEnabled = options.dragScroll - - let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)) - hitDragging.emitter.on('pointerdown', this.handlePointerDown) - hitDragging.emitter.on('dragstart', this.handleDragStart) - hitDragging.emitter.on('hitupdate', this.handleHitUpdate) - hitDragging.emitter.on('pointerup', this.handlePointerUp) - } - - destroy() { - this.dragging.destroy() - } - - handlePointerDown = (ev: PointerDragEvent) => { - let { component, dragging } = this - let { options } = component.context - - let canSelect = options.selectable && - component.isValidDateDownEl(ev.origEvent.target as HTMLElement) - - // don't bother to watch expensive moves if component won't do selection - dragging.setIgnoreMove(!canSelect) - - // if touch, require user to hold down - dragging.delay = ev.isTouch ? getComponentTouchDelay(component) : null - } - - handleDragStart = (ev: PointerDragEvent) => { - this.component.context.calendarApi.unselect(ev) // unselect previous selections - } - - handleHitUpdate = (hit: Hit | null, isFinal: boolean) => { - let { context } = this.component - let dragSelection: DateSpan | null = null - let isInvalid = false - - if (hit) { - let initialHit = this.hitDragging.initialHit! - let disallowed = hit.componentId === initialHit.componentId - && this.isHitComboAllowed - && !this.isHitComboAllowed(initialHit, hit) - - if (!disallowed) { - dragSelection = joinHitsIntoSelection( - initialHit, - hit, - context.pluginHooks.dateSelectionTransformers, - ) - } - - if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) { - isInvalid = true - dragSelection = null - } - } - - if (dragSelection) { - context.dispatch({ type: 'SELECT_DATES', selection: dragSelection }) - } else if (!isFinal) { // only unselect if moved away while dragging - context.dispatch({ type: 'UNSELECT_DATES' }) - } - - if (!isInvalid) { - enableCursor() - } else { - disableCursor() - } - - if (!isFinal) { - this.dragSelection = dragSelection // only clear if moved away from all hits while dragging - } - } - - handlePointerUp = (pev: PointerDragEvent) => { - if (this.dragSelection) { - // selection is already rendered, so just need to report selection - triggerDateSelect(this.dragSelection, pev, this.component.context) - - this.dragSelection = null - } - } -} - -function getComponentTouchDelay(component: DateComponent): number { - let { options } = component.context - let delay = options.selectLongPressDelay - - if (delay == null) { - delay = options.longPressDelay - } - - return delay -} - -function joinHitsIntoSelection(hit0: Hit, hit1: Hit, dateSelectionTransformers: dateSelectionJoinTransformer[]): DateSpan { - let dateSpan0 = hit0.dateSpan - let dateSpan1 = hit1.dateSpan - let ms = [ - dateSpan0.range.start, - dateSpan0.range.end, - dateSpan1.range.start, - dateSpan1.range.end, - ] - - ms.sort(compareNumbers) - - let props = {} as DateSpan - - for (let transformer of dateSelectionTransformers) { - let res = transformer(hit0, hit1) - - if (res === false) { - return null - } - - if (res) { - __assign(props, res) - } - } - - props.range = { start: ms[0], end: ms[3] } - props.allDay = dateSpan0.allDay - - return props -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts deleted file mode 100644 index 9a9ecfc49..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventDragging.ts +++ /dev/null @@ -1,482 +0,0 @@ -import { - DateComponent, Seg, - PointerDragEvent, Hit, - EventMutation, applyMutationToEventStore, - startOfDay, - elementClosest, - EventStore, getRelevantEvents, createEmptyEventStore, - EventInteractionState, - diffDates, enableCursor, disableCursor, - EventRenderRange, getElSeg, - EventApi, - eventDragMutationMassager, - Interaction, InteractionSettings, interactionSettingsStore, - EventDropTransformers, - CalendarContext, - ViewApi, - EventChangeArg, - buildEventApis, - EventAddArg, - EventRemoveArg, - isInteractionValid, - getElRoot, -} from '@fullcalendar/common' -import { __assign } from 'tslib' -import { HitDragging, isHitsEqual } from './HitDragging' -import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' -import { buildDatePointApiWithContext } from '../utils' - -export type EventDragStopArg = EventDragArg -export type EventDragStartArg = EventDragArg - -export interface EventDragArg { - el: HTMLElement - event: EventApi - jsEvent: MouseEvent - view: ViewApi -} - -export class EventDragging extends Interaction { // TODO: rename to EventSelectingAndDragging - // TODO: test this in IE11 - // QUESTION: why do we need it on the resizable??? - static SELECTOR = '.fc-event-draggable, .fc-event-resizable' - - dragging: FeaturefulElementDragging - hitDragging: HitDragging - - // internal state - subjectEl: HTMLElement | null = null - subjectSeg: Seg | null = null // the seg being selected/dragged - isDragging: boolean = false - eventRange: EventRenderRange | null = null - relevantEvents: EventStore | null = null // the events being dragged - receivingContext: CalendarContext | null = null - validMutation: EventMutation | null = null - mutatedRelevantEvents: EventStore | null = null - - constructor(settings: InteractionSettings) { - super(settings) - let { component } = this - let { options } = component.context - - let dragging = this.dragging = new FeaturefulElementDragging(settings.el) - dragging.pointer.selector = EventDragging.SELECTOR - dragging.touchScrollAllowed = false - dragging.autoScroller.isEnabled = options.dragScroll - - let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsStore) - hitDragging.useSubjectCenter = settings.useEventCenter - hitDragging.emitter.on('pointerdown', this.handlePointerDown) - hitDragging.emitter.on('dragstart', this.handleDragStart) - hitDragging.emitter.on('hitupdate', this.handleHitUpdate) - hitDragging.emitter.on('pointerup', this.handlePointerUp) - hitDragging.emitter.on('dragend', this.handleDragEnd) - } - - destroy() { - this.dragging.destroy() - } - - handlePointerDown = (ev: PointerDragEvent) => { - let origTarget = ev.origEvent.target as HTMLElement - let { component, dragging } = this - let { mirror } = dragging - let { options } = component.context - let initialContext = component.context - this.subjectEl = ev.subjectEl as HTMLElement - let subjectSeg = this.subjectSeg = getElSeg(ev.subjectEl as HTMLElement)! - let eventRange = this.eventRange = subjectSeg.eventRange! - let eventInstanceId = eventRange.instance!.instanceId - - this.relevantEvents = getRelevantEvents( - initialContext.getCurrentData().eventStore, - eventInstanceId, - ) - - dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance - dragging.delay = - // only do a touch delay if touch and this event hasn't been selected yet - (ev.isTouch && eventInstanceId !== component.props.eventSelection) ? - getComponentTouchDelay(component) : - null - - if (options.fixedMirrorParent) { - mirror.parentNode = options.fixedMirrorParent - } else { - mirror.parentNode = elementClosest(origTarget, '.fc') - } - - mirror.revertDuration = options.dragRevertDuration - - let isValid = - component.isValidSegDownEl(origTarget) && - !elementClosest(origTarget, '.fc-event-resizer') // NOT on a resizer - - dragging.setIgnoreMove(!isValid) - - // disable dragging for elements that are resizable (ie, selectable) - // but are not draggable - this.isDragging = isValid && - (ev.subjectEl as HTMLElement).classList.contains('fc-event-draggable') - } - - handleDragStart = (ev: PointerDragEvent) => { - let initialContext = this.component.context - let eventRange = this.eventRange! - let eventInstanceId = eventRange.instance.instanceId - - if (ev.isTouch) { - // need to select a different event? - if (eventInstanceId !== this.component.props.eventSelection) { - initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId }) - } - } else { - // if now using mouse, but was previous touch interaction, clear selected event - initialContext.dispatch({ type: 'UNSELECT_EVENT' }) - } - - if (this.isDragging) { - initialContext.calendarApi.unselect(ev) // unselect *date* selection - initialContext.emitter.trigger('eventDragStart', { - el: this.subjectEl, - event: new EventApi(initialContext, eventRange.def, eventRange.instance), - jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 - view: initialContext.viewApi, - } as EventDragStartArg) - } - } - - handleHitUpdate = (hit: Hit | null, isFinal: boolean) => { - if (!this.isDragging) { - return - } - - let relevantEvents = this.relevantEvents! - let initialHit = this.hitDragging.initialHit! - let initialContext = this.component.context - - // states based on new hit - let receivingContext: CalendarContext | null = null - let mutation: EventMutation | null = null - let mutatedRelevantEvents: EventStore | null = null - let isInvalid = false - let interaction: EventInteractionState = { - affectedEvents: relevantEvents, - mutatedEvents: createEmptyEventStore(), - isEvent: true, - } - - if (hit) { - receivingContext = hit.context - let receivingOptions = receivingContext.options - - if ( - initialContext === receivingContext || - (receivingOptions.editable && receivingOptions.droppable) - ) { - mutation = computeEventMutation(initialHit, hit, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers) - - if (mutation) { - mutatedRelevantEvents = applyMutationToEventStore( - relevantEvents, - receivingContext.getCurrentData().eventUiBases, - mutation, - receivingContext, - ) - interaction.mutatedEvents = mutatedRelevantEvents - - if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) { - isInvalid = true - mutation = null - mutatedRelevantEvents = null - interaction.mutatedEvents = createEmptyEventStore() - } - } - } else { - receivingContext = null - } - } - - this.displayDrag(receivingContext, interaction) - - if (!isInvalid) { - enableCursor() - } else { - disableCursor() - } - - if (!isFinal) { - if ( - initialContext === receivingContext && // TODO: write test for this - isHitsEqual(initialHit, hit) - ) { - mutation = null - } - - this.dragging.setMirrorNeedsRevert(!mutation) - - // render the mirror if no already-rendered mirror - // TODO: wish we could somehow wait for dispatch to guarantee render - this.dragging.setMirrorIsVisible( - !hit || !getElRoot(this.subjectEl).querySelector('.fc-event-mirror'), // TODO: turn className into constant - ) - - // assign states based on new hit - this.receivingContext = receivingContext - this.validMutation = mutation - this.mutatedRelevantEvents = mutatedRelevantEvents - } - } - - handlePointerUp = () => { - if (!this.isDragging) { - this.cleanup() // because handleDragEnd won't fire - } - } - - handleDragEnd = (ev: PointerDragEvent) => { - if (this.isDragging) { - let initialContext = this.component.context - let initialView = initialContext.viewApi - let { receivingContext, validMutation } = this - let eventDef = this.eventRange!.def - let eventInstance = this.eventRange!.instance - let eventApi = new EventApi(initialContext, eventDef, eventInstance) - let relevantEvents = this.relevantEvents! - let mutatedRelevantEvents = this.mutatedRelevantEvents! - let { finalHit } = this.hitDragging - - this.clearDrag() // must happen after revert animation - - initialContext.emitter.trigger('eventDragStop', { - el: this.subjectEl, - event: eventApi, - jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 - view: initialView, - } as EventDragStopArg) - - if (validMutation) { - // dropped within same calendar - if (receivingContext === initialContext) { - let updatedEventApi = new EventApi( - initialContext, - mutatedRelevantEvents.defs[eventDef.defId], - eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null, - ) - - initialContext.dispatch({ - type: 'MERGE_EVENTS', - eventStore: mutatedRelevantEvents, - }) - - let eventChangeArg: EventChangeArg = { - oldEvent: eventApi, - event: updatedEventApi, - relatedEvents: buildEventApis(mutatedRelevantEvents, initialContext, eventInstance), - revert() { - initialContext.dispatch({ - type: 'MERGE_EVENTS', - eventStore: relevantEvents, // the pre-change data - }) - }, - } - - let transformed: ReturnType = {} - for (let transformer of initialContext.getCurrentData().pluginHooks.eventDropTransformers) { - __assign(transformed, transformer(validMutation, initialContext)) - } - - initialContext.emitter.trigger('eventDrop', { - ...eventChangeArg, - ...transformed, - el: ev.subjectEl as HTMLElement, - delta: validMutation.datesDelta!, - jsEvent: ev.origEvent as MouseEvent, // bad - view: initialView, - }) - - initialContext.emitter.trigger('eventChange', eventChangeArg) - - // dropped in different calendar - } else if (receivingContext) { - let eventRemoveArg: EventRemoveArg = { - event: eventApi, - relatedEvents: buildEventApis(relevantEvents, initialContext, eventInstance), - revert() { - initialContext.dispatch({ - type: 'MERGE_EVENTS', - eventStore: relevantEvents, - }) - }, - } - - initialContext.emitter.trigger('eventLeave', { - ...eventRemoveArg, - draggedEl: ev.subjectEl as HTMLElement, - view: initialView, - }) - - initialContext.dispatch({ - type: 'REMOVE_EVENTS', - eventStore: relevantEvents, - }) - - initialContext.emitter.trigger('eventRemove', eventRemoveArg) - - let addedEventDef = mutatedRelevantEvents.defs[eventDef.defId] - let addedEventInstance = mutatedRelevantEvents.instances[eventInstance.instanceId] - let addedEventApi = new EventApi(receivingContext, addedEventDef, addedEventInstance) - - receivingContext.dispatch({ - type: 'MERGE_EVENTS', - eventStore: mutatedRelevantEvents, - }) - - let eventAddArg: EventAddArg = { - event: addedEventApi, - relatedEvents: buildEventApis(mutatedRelevantEvents, receivingContext, addedEventInstance), - revert() { - receivingContext.dispatch({ - type: 'REMOVE_EVENTS', - eventStore: mutatedRelevantEvents, - }) - }, - } - - receivingContext.emitter.trigger('eventAdd', eventAddArg) - - if (ev.isTouch) { - receivingContext.dispatch({ - type: 'SELECT_EVENT', - eventInstanceId: eventInstance.instanceId, - }) - } - - receivingContext.emitter.trigger('drop', { - ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), - draggedEl: ev.subjectEl as HTMLElement, - jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 - view: finalHit.context.viewApi, - }) - - receivingContext.emitter.trigger('eventReceive', { - ...eventAddArg, - draggedEl: ev.subjectEl as HTMLElement, - view: finalHit.context.viewApi, - }) - } - } else { - initialContext.emitter.trigger('_noEventDrop') - } - } - - this.cleanup() - } - - // render a drag state on the next receivingCalendar - displayDrag(nextContext: CalendarContext | null, state: EventInteractionState) { - let initialContext = this.component.context - let prevContext = this.receivingContext - - // does the previous calendar need to be cleared? - if (prevContext && prevContext !== nextContext) { - // does the initial calendar need to be cleared? - // if so, don't clear all the way. we still need to to hide the affectedEvents - if (prevContext === initialContext) { - prevContext.dispatch({ - type: 'SET_EVENT_DRAG', - state: { - affectedEvents: state.affectedEvents, - mutatedEvents: createEmptyEventStore(), - isEvent: true, - }, - }) - - // completely clear the old calendar if it wasn't the initial - } else { - prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) - } - } - - if (nextContext) { - nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }) - } - } - - clearDrag() { - let initialCalendar = this.component.context - let { receivingContext } = this - - if (receivingContext) { - receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) - } - - // the initial calendar might have an dummy drag state from displayDrag - if (initialCalendar !== receivingContext) { - initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }) - } - } - - cleanup() { // reset all internal state - this.subjectSeg = null - this.isDragging = false - this.eventRange = null - this.relevantEvents = null - this.receivingContext = null - this.validMutation = null - this.mutatedRelevantEvents = null - } -} - -function computeEventMutation(hit0: Hit, hit1: Hit, massagers: eventDragMutationMassager[]): EventMutation { - let dateSpan0 = hit0.dateSpan - let dateSpan1 = hit1.dateSpan - let date0 = dateSpan0.range.start - let date1 = dateSpan1.range.start - let standardProps = {} as any - - if (dateSpan0.allDay !== dateSpan1.allDay) { - standardProps.allDay = dateSpan1.allDay - standardProps.hasEnd = hit1.context.options.allDayMaintainDuration - - if (dateSpan1.allDay) { - // means date1 is already start-of-day, - // but date0 needs to be converted - date0 = startOfDay(date0) - } - } - - let delta = diffDates( - date0, date1, - hit0.context.dateEnv, - hit0.componentId === hit1.componentId ? - hit0.largeUnit : - null, - ) - - if (delta.milliseconds) { // has hours/minutes/seconds - standardProps.allDay = false - } - - let mutation: EventMutation = { - datesDelta: delta, - standardProps, - } - - for (let massager of massagers) { - massager(mutation, hit0, hit1) - } - - return mutation -} - -function getComponentTouchDelay(component: DateComponent): number | null { - let { options } = component.context - let delay = options.eventLongPressDelay - - if (delay == null) { - delay = options.longPressDelay - } - - return delay -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts deleted file mode 100644 index 013b399ae..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/EventResizing.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { - Seg, Hit, - EventMutation, applyMutationToEventStore, - elementClosest, - PointerDragEvent, - EventStore, getRelevantEvents, createEmptyEventStore, - diffDates, enableCursor, disableCursor, - DateRange, - EventApi, - EventRenderRange, getElSeg, - createDuration, - EventInteractionState, - Interaction, InteractionSettings, interactionSettingsToStore, ViewApi, Duration, EventChangeArg, buildEventApis, isInteractionValid, -} from '@fullcalendar/common' -import { __assign } from 'tslib' -import { HitDragging, isHitsEqual } from './HitDragging' -import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' - -export type EventResizeStartArg = EventResizeStartStopArg -export type EventResizeStopArg = EventResizeStartStopArg - -export interface EventResizeStartStopArg { - el: HTMLElement - event: EventApi - jsEvent: MouseEvent - view: ViewApi -} - -export interface EventResizeDoneArg extends EventChangeArg { - el: HTMLElement - startDelta: Duration - endDelta: Duration - jsEvent: MouseEvent - view: ViewApi -} - -export class EventResizing extends Interaction { - dragging: FeaturefulElementDragging - hitDragging: HitDragging - - // internal state - draggingSegEl: HTMLElement | null = null - draggingSeg: Seg | null = null // TODO: rename to resizingSeg? subjectSeg? - eventRange: EventRenderRange | null = null - relevantEvents: EventStore | null = null - validMutation: EventMutation | null = null - mutatedRelevantEvents: EventStore | null = null - - constructor(settings: InteractionSettings) { - super(settings) - let { component } = settings - - let dragging = this.dragging = new FeaturefulElementDragging(settings.el) - dragging.pointer.selector = '.fc-event-resizer' - dragging.touchScrollAllowed = false - dragging.autoScroller.isEnabled = component.context.options.dragScroll - - let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)) - hitDragging.emitter.on('pointerdown', this.handlePointerDown) - hitDragging.emitter.on('dragstart', this.handleDragStart) - hitDragging.emitter.on('hitupdate', this.handleHitUpdate) - hitDragging.emitter.on('dragend', this.handleDragEnd) - } - - destroy() { - this.dragging.destroy() - } - - handlePointerDown = (ev: PointerDragEvent) => { - let { component } = this - let segEl = this.querySegEl(ev) - let seg = getElSeg(segEl) - let eventRange = this.eventRange = seg.eventRange! - - this.dragging.minDistance = component.context.options.eventDragMinDistance - - // if touch, need to be working with a selected event - this.dragging.setIgnoreMove( - !this.component.isValidSegDownEl(ev.origEvent.target as HTMLElement) || - (ev.isTouch && this.component.props.eventSelection !== eventRange.instance!.instanceId), - ) - } - - handleDragStart = (ev: PointerDragEvent) => { - let { context } = this.component - let eventRange = this.eventRange! - - this.relevantEvents = getRelevantEvents( - context.getCurrentData().eventStore, - this.eventRange.instance!.instanceId, - ) - - let segEl = this.querySegEl(ev) - this.draggingSegEl = segEl - this.draggingSeg = getElSeg(segEl) - - context.calendarApi.unselect() - context.emitter.trigger('eventResizeStart', { - el: segEl, - event: new EventApi(context, eventRange.def, eventRange.instance), - jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 - view: context.viewApi, - } as EventResizeStartArg) - } - - handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => { - let { context } = this.component - let relevantEvents = this.relevantEvents! - let initialHit = this.hitDragging.initialHit! - let eventInstance = this.eventRange.instance! - let mutation: EventMutation | null = null - let mutatedRelevantEvents: EventStore | null = null - let isInvalid = false - let interaction: EventInteractionState = { - affectedEvents: relevantEvents, - mutatedEvents: createEmptyEventStore(), - isEvent: true, - } - - if (hit) { - let disallowed = hit.componentId === initialHit.componentId - && this.isHitComboAllowed - && !this.isHitComboAllowed(initialHit, hit) - - if (!disallowed) { - mutation = computeMutation( - initialHit, - hit, - (ev.subjectEl as HTMLElement).classList.contains('fc-event-resizer-start'), - eventInstance.range, - ) - } - } - - if (mutation) { - mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context) - interaction.mutatedEvents = mutatedRelevantEvents - - if (!isInteractionValid(interaction, hit.dateProfile, context)) { - isInvalid = true - mutation = null - mutatedRelevantEvents = null - interaction.mutatedEvents = null - } - } - - if (mutatedRelevantEvents) { - context.dispatch({ - type: 'SET_EVENT_RESIZE', - state: interaction, - }) - } else { - context.dispatch({ type: 'UNSET_EVENT_RESIZE' }) - } - - if (!isInvalid) { - enableCursor() - } else { - disableCursor() - } - - if (!isFinal) { - if (mutation && isHitsEqual(initialHit, hit)) { - mutation = null - } - - this.validMutation = mutation - this.mutatedRelevantEvents = mutatedRelevantEvents - } - } - - handleDragEnd = (ev: PointerDragEvent) => { - let { context } = this.component - let eventDef = this.eventRange!.def - let eventInstance = this.eventRange!.instance - let eventApi = new EventApi(context, eventDef, eventInstance) - let relevantEvents = this.relevantEvents! - let mutatedRelevantEvents = this.mutatedRelevantEvents! - - context.emitter.trigger('eventResizeStop', { - el: this.draggingSegEl, - event: eventApi, - jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 - view: context.viewApi, - } as EventResizeStopArg) - - if (this.validMutation) { - let updatedEventApi = new EventApi( - context, - mutatedRelevantEvents.defs[eventDef.defId], - eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null, - ) - - context.dispatch({ - type: 'MERGE_EVENTS', - eventStore: mutatedRelevantEvents, - }) - - let eventChangeArg: EventChangeArg = { - oldEvent: eventApi, - event: updatedEventApi, - relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance), - revert() { - context.dispatch({ - type: 'MERGE_EVENTS', - eventStore: relevantEvents, // the pre-change events - }) - }, - } - - context.emitter.trigger('eventResize', { - ...eventChangeArg, - el: this.draggingSegEl, - startDelta: this.validMutation.startDelta || createDuration(0), - endDelta: this.validMutation.endDelta || createDuration(0), - jsEvent: ev.origEvent as MouseEvent, - view: context.viewApi, - }) - - context.emitter.trigger('eventChange', eventChangeArg) - } else { - context.emitter.trigger('_noEventResize') - } - - // reset all internal state - this.draggingSeg = null - this.relevantEvents = null - this.validMutation = null - - // okay to keep eventInstance around. useful to set it in handlePointerDown - } - - querySegEl(ev: PointerDragEvent) { - return elementClosest(ev.subjectEl as HTMLElement, '.fc-event') - } -} - -function computeMutation( - hit0: Hit, - hit1: Hit, - isFromStart: boolean, - instanceRange: DateRange, -): EventMutation | null { - let dateEnv = hit0.context.dateEnv - let date0 = hit0.dateSpan.range.start - let date1 = hit1.dateSpan.range.start - - let delta = diffDates( - date0, date1, - dateEnv, - hit0.largeUnit, - ) - - if (isFromStart) { - if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) { - return { startDelta: delta } - } - } else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) { - return { endDelta: delta } - } - - return null -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts deleted file mode 100644 index d0a329e9c..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/HitDragging.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { - Emitter, PointerDragEvent, - isDateSpansEqual, - computeRect, - constrainPoint, intersectRects, getRectCenter, diffPoints, Point, - rangeContainsRange, - Hit, - InteractionSettingsStore, - mapHash, - ElementDragging, -} from '@fullcalendar/common' -import { OffsetTracker } from '../OffsetTracker' - -/* -Tracks movement over multiple droppable areas (aka "hits") -that exist in one or more DateComponents. -Relies on an existing draggable. - -emits: -- pointerdown -- dragstart -- hitchange - fires initially, even if not over a hit -- pointerup -- (hitchange - again, to null, if ended over a hit) -- dragend -*/ -export class HitDragging { - droppableStore: InteractionSettingsStore - dragging: ElementDragging - emitter: Emitter - - // options that can be set by caller - useSubjectCenter: boolean = false - requireInitial: boolean = true // if doesn't start out on a hit, won't emit any events - - // internal state - offsetTrackers: { [componentUid: string]: OffsetTracker } - initialHit: Hit | null = null - movingHit: Hit | null = null - finalHit: Hit | null = null // won't ever be populated if shouldIgnoreMove - coordAdjust?: Point - - constructor(dragging: ElementDragging, droppableStore: InteractionSettingsStore) { - this.droppableStore = droppableStore - - dragging.emitter.on('pointerdown', this.handlePointerDown) - dragging.emitter.on('dragstart', this.handleDragStart) - dragging.emitter.on('dragmove', this.handleDragMove) - dragging.emitter.on('pointerup', this.handlePointerUp) - dragging.emitter.on('dragend', this.handleDragEnd) - - this.dragging = dragging - this.emitter = new Emitter() - } - - handlePointerDown = (ev: PointerDragEvent) => { - let { dragging } = this - - this.initialHit = null - this.movingHit = null - this.finalHit = null - - this.prepareHits() - this.processFirstCoord(ev) - - if (this.initialHit || !this.requireInitial) { - dragging.setIgnoreMove(false) - - // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :( - this.emitter.trigger('pointerdown', ev) - } else { - dragging.setIgnoreMove(true) - } - } - - // sets initialHit - // sets coordAdjust - processFirstCoord(ev: PointerDragEvent) { - let origPoint = { left: ev.pageX, top: ev.pageY } - let adjustedPoint = origPoint - let subjectEl = ev.subjectEl - let subjectRect - - if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot - subjectRect = computeRect(subjectEl) - adjustedPoint = constrainPoint(adjustedPoint, subjectRect) - } - - let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top) - if (initialHit) { - if (this.useSubjectCenter && subjectRect) { - let slicedSubjectRect = intersectRects(subjectRect, initialHit.rect) - if (slicedSubjectRect) { - adjustedPoint = getRectCenter(slicedSubjectRect) - } - } - - this.coordAdjust = diffPoints(adjustedPoint, origPoint) - } else { - this.coordAdjust = { left: 0, top: 0 } - } - } - - handleDragStart = (ev: PointerDragEvent) => { - this.emitter.trigger('dragstart', ev) - this.handleMove(ev, true) // force = fire even if initially null - } - - handleDragMove = (ev: PointerDragEvent) => { - this.emitter.trigger('dragmove', ev) - this.handleMove(ev) - } - - handlePointerUp = (ev: PointerDragEvent) => { - this.releaseHits() - this.emitter.trigger('pointerup', ev) - } - - handleDragEnd = (ev: PointerDragEvent) => { - if (this.movingHit) { - this.emitter.trigger('hitupdate', null, true, ev) - } - - this.finalHit = this.movingHit - this.movingHit = null - this.emitter.trigger('dragend', ev) - } - - handleMove(ev: PointerDragEvent, forceHandle?: boolean) { - let hit = this.queryHitForOffset( - ev.pageX + this.coordAdjust!.left, - ev.pageY + this.coordAdjust!.top, - ) - - if (forceHandle || !isHitsEqual(this.movingHit, hit)) { - this.movingHit = hit - this.emitter.trigger('hitupdate', hit, false, ev) - } - } - - prepareHits() { - this.offsetTrackers = mapHash(this.droppableStore, (interactionSettings) => { - interactionSettings.component.prepareHits() - return new OffsetTracker(interactionSettings.el) - }) - } - - releaseHits() { - let { offsetTrackers } = this - - for (let id in offsetTrackers) { - offsetTrackers[id].destroy() - } - - this.offsetTrackers = {} - } - - queryHitForOffset(offsetLeft: number, offsetTop: number): Hit | null { - let { droppableStore, offsetTrackers } = this - let bestHit: Hit | null = null - - for (let id in droppableStore) { - let component = droppableStore[id].component - let offsetTracker = offsetTrackers[id] - - if ( - offsetTracker && // wasn't destroyed mid-drag - offsetTracker.isWithinClipping(offsetLeft, offsetTop) - ) { - let originLeft = offsetTracker.computeLeft() - let originTop = offsetTracker.computeTop() - let positionLeft = offsetLeft - originLeft - let positionTop = offsetTop - originTop - let { origRect } = offsetTracker - let width = origRect.right - origRect.left - let height = origRect.bottom - origRect.top - - if ( - // must be within the element's bounds - positionLeft >= 0 && positionLeft < width && - positionTop >= 0 && positionTop < height - ) { - let hit = component.queryHit(positionLeft, positionTop, width, height) - if ( - hit && ( - // make sure the hit is within activeRange, meaning it's not a dead cell - rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range) - ) && - (!bestHit || hit.layer > bestHit.layer) - ) { - hit.componentId = id - hit.context = component.context - - // TODO: better way to re-orient rectangle - hit.rect.left += originLeft - hit.rect.right += originLeft - hit.rect.top += originTop - hit.rect.bottom += originTop - - bestHit = hit - } - } - } - } - - return bestHit - } -} - -export function isHitsEqual(hit0: Hit | null, hit1: Hit | null): boolean { - if (!hit0 && !hit1) { - return true - } - - if (Boolean(hit0) !== Boolean(hit1)) { - return false - } - - return isDateSpansEqual(hit0!.dateSpan, hit1!.dateSpan) -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts b/apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts deleted file mode 100644 index 23e5b47cb..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/interactions/UnselectAuto.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - DateSelectionApi, - PointerDragEvent, - elementClosest, - CalendarContext, - getEventTargetViaRoot, -} from '@fullcalendar/common' -import { PointerDragging } from '../dnd/PointerDragging' -import { EventDragging } from './EventDragging' - -export class UnselectAuto { - documentPointer: PointerDragging // for unfocusing - isRecentPointerDateSelect = false // wish we could use a selector to detect date selection, but uses hit system - matchesCancel = false - matchesEvent = false - - constructor(private context: CalendarContext) { - let documentPointer = this.documentPointer = new PointerDragging(document) - documentPointer.shouldIgnoreMove = true - documentPointer.shouldWatchScroll = false - documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown) - documentPointer.emitter.on('pointerup', this.onDocumentPointerUp) - - /* - TODO: better way to know about whether there was a selection with the pointer - */ - context.emitter.on('select', this.onSelect) - } - - destroy() { - this.context.emitter.off('select', this.onSelect) - this.documentPointer.destroy() - } - - onSelect = (selectInfo: DateSelectionApi) => { - if (selectInfo.jsEvent) { - this.isRecentPointerDateSelect = true - } - } - - onDocumentPointerDown = (pev: PointerDragEvent) => { - let unselectCancel = this.context.options.unselectCancel - let downEl = getEventTargetViaRoot(pev.origEvent) as HTMLElement - - this.matchesCancel = !!elementClosest(downEl, unselectCancel) - this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR) // interaction started on an event? - } - - onDocumentPointerUp = (pev: PointerDragEvent) => { - let { context } = this - let { documentPointer } = this - let calendarState = context.getCurrentData() - - // touch-scrolling should never unfocus any type of selection - if (!documentPointer.wasTouchScroll) { - if ( - calendarState.dateSelection && // an existing date selection? - !this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp? - ) { - let unselectAuto = context.options.unselectAuto - - if (unselectAuto && (!unselectAuto || !this.matchesCancel)) { - context.calendarApi.unselect(pev) - } - } - - if ( - calendarState.eventSelection && // an existing event selected? - !this.matchesEvent // interaction DIDN'T start on an event - ) { - context.dispatch({ type: 'UNSELECT_EVENT' }) - } - } - - this.isRecentPointerDateSelect = false - } -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts b/apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts deleted file mode 100644 index 0af579774..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/main.global.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { globalPlugins } from '@fullcalendar/common' -import plugin from './main' - -globalPlugins.push(plugin) - -export default plugin -export * from './main' diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/main.ts b/apps/schoolCalendar/fullcalendar/interaction/src/main.ts deleted file mode 100644 index f57049ca3..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/main.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createPlugin } from '@fullcalendar/common' -import { DateClicking } from './interactions/DateClicking' -import { DateSelecting } from './interactions/DateSelecting' -import { EventDragging } from './interactions/EventDragging' -import { EventResizing } from './interactions/EventResizing' -import { UnselectAuto } from './interactions/UnselectAuto' -import { FeaturefulElementDragging } from './dnd/FeaturefulElementDragging' -import { OPTION_REFINERS, LISTENER_REFINERS } from './options' -import './options-declare' - -export default createPlugin({ - componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing], - calendarInteractions: [UnselectAuto], - elementDraggingImpl: FeaturefulElementDragging, - optionRefiners: OPTION_REFINERS, - listenerRefiners: LISTENER_REFINERS, -}) - -export * from './api-type-deps' -export { FeaturefulElementDragging } -export { PointerDragging } from './dnd/PointerDragging' -export { ExternalDraggable as Draggable } from './interactions-external/ExternalDraggable' -export { ThirdPartyDraggable } from './interactions-external/ThirdPartyDraggable' diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts b/apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts deleted file mode 100644 index 9cbc5a4a8..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/options-declare.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { OPTION_REFINERS, LISTENER_REFINERS } from './options' - -type ExtraOptionRefiners = typeof OPTION_REFINERS -type ExtraListenerRefiners = typeof LISTENER_REFINERS - -declare module '@fullcalendar/common' { - interface BaseOptionRefiners extends ExtraOptionRefiners {} - interface CalendarListenerRefiners extends ExtraListenerRefiners {} -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/options.ts b/apps/schoolCalendar/fullcalendar/interaction/src/options.ts deleted file mode 100644 index a11ce5399..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/options.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { identity, Identity, EventDropArg } from '@fullcalendar/common' - -// public -import { - DateClickArg, - EventDragStartArg, EventDragStopArg, - EventResizeStartArg, EventResizeStopArg, EventResizeDoneArg, - DropArg, EventReceiveArg, EventLeaveArg, -} from './api-type-deps' - -export const OPTION_REFINERS = { - fixedMirrorParent: identity as Identity, -} - -export const LISTENER_REFINERS = { - dateClick: identity as Identity<(arg: DateClickArg) => void>, - eventDragStart: identity as Identity<(arg: EventDragStartArg) => void>, - eventDragStop: identity as Identity<(arg: EventDragStopArg) => void>, - eventDrop: identity as Identity<(arg: EventDropArg) => void>, - eventResizeStart: identity as Identity<(arg: EventResizeStartArg) => void>, - eventResizeStop: identity as Identity<(arg: EventResizeStopArg) => void>, - eventResize: identity as Identity<(arg: EventResizeDoneArg) => void>, - drop: identity as Identity<(arg: DropArg) => void>, - eventReceive: identity as Identity<(arg: EventReceiveArg) => void>, - eventLeave: identity as Identity<(arg: EventLeaveArg) => void>, -} diff --git a/apps/schoolCalendar/fullcalendar/interaction/src/utils.ts b/apps/schoolCalendar/fullcalendar/interaction/src/utils.ts deleted file mode 100644 index 056a0040d..000000000 --- a/apps/schoolCalendar/fullcalendar/interaction/src/utils.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { DateSpan, CalendarContext, DatePointApi, DateEnv, ViewApi, EventApi } from '@fullcalendar/common' -import { __assign } from 'tslib' - -export interface DropArg extends DatePointApi { - draggedEl: HTMLElement - jsEvent: MouseEvent - view: ViewApi -} - -export type EventReceiveArg = EventReceiveLeaveArg -export type EventLeaveArg = EventReceiveLeaveArg -export interface EventReceiveLeaveArg { // will this become public? - draggedEl: HTMLElement - event: EventApi - relatedEvents: EventApi[] - revert: () => void - view: ViewApi -} - -export function buildDatePointApiWithContext(dateSpan: DateSpan, context: CalendarContext) { - let props = {} as DatePointApi - - for (let transform of context.pluginHooks.datePointTransforms) { - __assign(props, transform(dateSpan, context)) - } - - __assign(props, buildDatePointApi(dateSpan, context.dateEnv)) - - return props -} - -export function buildDatePointApi(span: DateSpan, dateEnv: DateEnv): DatePointApi { - return { - date: dateEnv.toDate(span.range.start), - dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }), - allDay: span.allDay, - } -} From 20310fdb75b11fe8b3ea38dc7810732910537169 Mon Sep 17 00:00:00 2001 From: David Peer Date: Mon, 22 Nov 2021 19:02:27 +0100 Subject: [PATCH 0808/1062] Smaller icons --- apps/lcars/icon_alarm.png | Bin 2537 -> 2831 bytes apps/lcars/icon_compass.png | Bin 5469 -> 4843 bytes apps/lcars/icon_gps.png | Bin 4253 -> 3981 bytes apps/lcars/icon_hrm.png | Bin 692 -> 1277 bytes apps/lcars/icon_planet.png | Bin 3529 -> 3558 bytes apps/lcars/lcars.app.js | 28 +++++++++++++++------------- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/apps/lcars/icon_alarm.png b/apps/lcars/icon_alarm.png index 02e5ab1b6e25fa92c3342cf2319046f5d143f123..2b1afd861cc9c75b3307795615553ba3726aaf8e 100644 GIT binary patch literal 2831 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 literal 2537 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 diff --git a/apps/lcars/icon_compass.png b/apps/lcars/icon_compass.png index f1fc46f2818d92f6b7b286b46d013d400dd7efc3..81311ca780f2faf8537098a9078897919567cdbc 100644 GIT binary patch literal 4843 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 literal 5469 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 TJN0qL00000NkvXXu0mjf3t_;J diff --git a/apps/lcars/icon_gps.png b/apps/lcars/icon_gps.png index 5619991c35fc2f5e87ad263e6599870b874452dc..d8d99e328235bdb5681705095160ccb47a8aab70 100644 GIT binary patch literal 3981 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 diff --git a/apps/lcars/icon_hrm.png b/apps/lcars/icon_hrm.png index 46a4476fc29e2b9eb5fc7fa2dd15a88090fbcac9..32c0a63d0a9e6129b5934960f49da244ad8110a8 100644 GIT binary patch literal 1277 zcmVco`AV2MI_e5mcfm z)dzhsrT8E!C56UPh{pEC+l$~O4_>j-LaDSWA|xiFF)?0ZH5WBClT0R=IWy;+z0c~y z*~hNz>^;t8j!wYyhxM@6Uh7-`|GIqNw-#M=(M1GZFSGl(499*4ygG}(ohkJ|2!xbI!t$BsRfLIFq<56L6^@7+ z_w`2KhZ9PLY~yTPp0BUdJ5j$5pueN`8(h=gQgKPxSmlzA4HO~eu`yQHc`7h4ZG(a1 zQ$8*d5@-f)>;UgPW~c1S&f=8EC|trJj;e4*`#a^UCe7@#Co3IA!rx_aszT3Z&RRa* zxb+#!qL~R>xuiW?fiT1^Iw)q43lp{!tp54aGMqBpHue?&-ps-f)$QZu?VQ{p9a5es z+QuJp>2!?#K8_tbxE;N*knEgHZ?-Zt_*Kd$MM6e-C2-Va>>PAPDlCd(M>vTQq#L;{ zYBrf|HG40A3akmtE^E}sITcPXG{6-OC2qaUNewo%XJmxGJisida%wzVpHJW(-(k)e ze{8R8%KJs_LTfd~qs(D@m7C&m-ryIL;ocWm5`8bjxlm)*FdOb<3}zAHj=Q;L9c#~{ zkvIBtuEgC@)encccph&?b(Jj~a|TDYE1LXsR%-`*jR9W5k8#8xzg)nHg-S|dMcCVN zhx6L)HZuPU{*^l?M;F=L&GiL=^aZV~0Bqpc?ffIQ_X+cA{4C+sXqC2QqOYj3sf=%_ zkd)_g{3`EpYp&fvLWWr|&Km_nwpF>d-EI}<#Ul2zB4pmKy@=PGCWKNA|$|fXXU7m9h-%{*mpvp&) z(X9ny+wrDZWy8Y^BsfOFoBTcV$xf) zU^>V{C8U~rxnDu7*u&BWUzTjAgH~Xwd4K)5%pF70>i>0Ow~c#6k@(asn10NIMaD+i zw28keaN+ZLriUGIU6ZhxN1mlNMcxM^K8Pu`vDS=2C1Gv*-#(sxjT7J_jvQopes=4; zh`tHNT9fZHt^6xS!W}82k@4ps<}t{8Ux`velpYCri2n9rK$a4i?>;tFcypWl9lTY z(ue$1;TDFnkh2Vd1I~f|Lbp)J@ctfpVAB<<@wOM6#eG|7Wo!lAiom~aZF&v nhb)%8wsg@&7hQDmy~6(h4QpUwCsj{d00000NkvXXu0mjf1DkQ# literal 692 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 literal 3529 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 DcMa0Y diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 5b9bca872..373e98ad6 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -11,31 +11,33 @@ var backgroundImage = { } var iconPlanet = { - width : 60, height : 60, bpp : 3, - buffer : require("heatshrink").decompress(atob("AA8Dhkw4YCB4AVOoMEiACEChgpEAQcckAWLiEAwgsDAoMwoAUJFIwsDuJcJkGCgEBFIImBhEgwky7YtIFI4sE48cFhSACAoIsC2XbtuwQZwsGuAWFEYOCQYIFCwAvBggsC2hyEbQoCFFIIsCRIsCFgQCEcAUs2wsB23QWBsMmIsKFIoCBkLmBpctFga2EFhQpDAQawEFgmAAQMCiHLFgtsbphZHcYYsGwkQgACBoaGEAQIVBuAsKWYwsCmSGGFINBWYbgCFgWwfYIsihmy5dsF4QsO4YsGnmyFgwpCAQcy5ckcwIsQcYK5EFhACBgIvGXIWQFh6MFFhUhgDjBFglAggsUAQIsBoAsHXJEAwEAFi2wLJACDwAsEBAMcFKIsDgApJAQcAFgQICFKQCDFJYCEgAFDDopfQJob+BRhosEjlxfwNxFhpHCwmy5csAQJ9BdJUADQIpCAQosLgEhFIQCFQAgsFgExFhHHFhcztgsHljsHwAsMXJAVBgEDtu27YsG2AsJgIsKuAsNAQJcHF4gVCgEHFhJcG4AsDposDLg+QFgQrDCwIsQCwgABFga5GWwQUGhjjOCwykCmwvBFgkCWAgAG4YsHWAwAGgQsCRgfYFgQVJgcMFhIVJAAMEFgvQKxIAEFg4VNcwRcCmnQBw4A==")) + width : 50, height : 50, bpp : 3, + transparent : 5, + buffer : require("heatshrink").decompress(atob("23btoCD6PHjlx9oLGAQuGiVJkmSpIRK2lxEYQCDCJOGjEhEYNBwUI5drEw/xEYwCB8oRGDoMhwmSsAFBkGM237NZICGj15OgnaDoOGI4cgwUa5dv332EwdHEZACB8+evYRCtAdBEAQpDscs3379+9HAW8EZPHz158+WSQQjFwUYsMs2QjBEwPrSRZuCJQN5TAJuCEYkhwUS5cvJQRxCNxZKDOIXgJQkh0mYtMk2XLJQXv1u0EZSVDOIWsJQsSpMkyVJljgB9gmB7YjLOgtq4BKEsIjCAQNLlgCBt+9EZwCCj8sJQpxB00aJoYCB5cBEZ4CB+RKFJoeGjAjCoOGzBKaAQeGJQQFBwJKSsAjIcweSBwRKRjojKOgYFCxZKRtAaBjHrlm4FJUN3hKQi3ShAjB2XLAQQmI7dHJR97tsh9gjEAQLpHlu2+PnExvF23an3794mF2BKFm3btsevImMjwRB23v3wmB3xNF5BuDCIPb8+eEwOeExIRCtojCJo5uEEwRxBEwRuJHAdI+YmCTYlgJQIREtrjCEwLdHCIiYBhF7OgnJSQgmFjhxCOgiSDAQvSX4QmB90IkQRIX4gmCEZICDvwmCBY3QA")) } var iconGps = { - width : 60, height : 60, bpp : 3, + width : 50, height : 50, bpp : 3, transparent : 2, - buffer : require("heatshrink").decompress(atob("pMkyQCHy8pBZICJpe+6QURAQOPnmkFiVJ3//CiUl589+wIElMiC5dN2///oIDymSogsKkYsB+/aBAdJk4sLtu+///+gICzgsMufPnn379pBANc/JZLyYsB/YtBWwMm/YaCa4UcFhHz7990mXnhHDyVIMIQXDzdvFgPvFoNJ336VQnHglwDonzFgXPvv3RgI7EDIYsIcYP/3xQEuAsBuPCFhC2B75QExLTFFgy2GUINwoMciHHhIsHWwoXBWQtSFg62EB4MciJZC4a2DWYa2FBYLiHcAQsFWwYsEhgsBhi2CFgy2B+xZIWwWTtosFWwP9PoVwoccmHHjkxWwMjFgy2B7QnByIsFWwQsGWwshLILjBFgK2BkIsGWwtEFguSqmRFgy2Fw8MmKGCWwNJg4sGWwsSFgtkyUWFgy2GLIkeUAIsHWwJxCWwuW/ioBrYsG3/6CQS2BFgc8/NkyDjHE4i2Dk3O7fyBINvFgntYwshQwNxsmc+fny0kx4sDKwYCDWwVypM79/JSAW7FgIUGWwUcufEzxWBjgOGAQ8Stdv5Mk94HBChgCByQpBj1548EuAsNAYOv38kdYUlFhgQCPoNxFgNx4QsLpQDBye+/IKDygsLBgN5888uFBjkQ48JFhVIxIDBpwKFqQsKoKDB88ciJZC4a2LAIdCSJAXHkFBhlZ8QsChgsBhi2KRAWRkgNGWxVDhp0CuFDjkw48cmK2JpD3BAoIuBVI77MkJZBuAsCWxgCCogHGWxYCCw8MmKGC4cdFhtJiQsUyUcLIgsPWwwUOWwQsDChy2G0gsPWwKGBuPSFiC2DlIUQWwUcugURWwSwGA=")) + buffer : require("heatshrink").decompress(atob("pMkyQCFpH0BAwCJv/6CJ8l589CJ0kyf//wIDpVEChM8+/fBAdZ8QRIp++///0gIBlMkxI4IuZKB+/SKAPHzpKJ/YkB//pKAP2BYeXhIFDx88+fPvqYBnibEkmUAofv34lC/RQBBYdcmPCXIYjBEwPfvnzJoILBQoUlHAUuJQYmCDodw48cuBKGTA0WEYIEBJQ6YEQwMMuImBJQyYEkmZFAVkyVSJQ6YCyUcmPDjgmBTAJKETAlJiS4ETANPJQpxCJQtxTALgBEwnfvohBI4NZkmWpNlcAgAD/wzBEYaYCy8cJQiYEyIjCTAWS3wlGTAVIEwkerJKFTAkmOIclToK8GAAIPBIgImCufHyxxG59pEIS8DvfypMr968HEwOHEwfx8+cEYkpCIeSoiYByVf/uSkmTEQP7ZIiYDnl5AQNwBYgCGyOn38k2+2pIRKyVeuPPj1x4ccCJVKSgP/5cJA4NSExMps+cSoMMKAIVCCg7SBpd7TANZkmUHBMevPnjlwcwXCCJFEzYDBA4WWKIIRHpEw4+eNwUxEwKYIkVJk1IyIKFHA+DR4VcJQYCBJRBoCkxHBAgNkyyYKkmXEYaYMAQMSEYKYNAQOHEwnSfBYjBAgVaCJdJJSMkTAK8KYQyVKAQ4jBNxiYEcBCYJXIkgA=")) } var iconHrm = { - width : 60, height : 60, bpp : 3, - transparent : 0, - buffer : require("heatshrink").decompress(atob("AH4A/AH4ANgmACqcCpIsUkmQCqcSpMgCqUBkmSFlMJFiuSpMkSSQUByVASSMEFhZlBFhEgFhKSJBAJxBMpSSGMQWSpCSQGoOQBYOAMqBiCiQsEQAaSIFgOAAQR3EMpZWCFghTDMoKSBHAhKDLgQRDEwRlEOgRKEdggIDMoh0DO4gsDCIJiEHAQUEKAjaEKYJlDFIxrGBwRWCBYbjJBAIRBwVJKYJlDKAbXGR4VAEwoFCCozFDHwRlEAoQsKHwSVCBYjLEFggpCF4YmDUgQsIKwRcCeQosIEwKJDZAQLEFg6DDHwUCTYILEFhLIBBY4yBBI4gBSRIAKEAJlJABRBBMpIsMMpAsNCqQsWZAYASeoYspZCQACZCYA/AH4A/AAo=")) + width : 50, height : 50, bpp : 3, + transparent : 1, + buffer : require("heatshrink").decompress(atob("kmSpIC/AX4CVmUSCJ8mpMJEx+Zkg4QsmSHB9MyVILiFJkILGyZBGLgMkwgvHyBcHJRBBHsmGJQ4vBIIpcBjKDH5JBGyVCshBGLgILBXI0yBAWTGQQvBYoq5C5IICFIeSoZBEFgOEAQQ+DF4UMPQaADF4IIBNYfJkMypA7CCIKABkyDCNYQsCzIUBLwZHBBAJrEJoT+BCIJ9EBYRrDHwQNCYoxZBNYRBDdg6YCJoImCHwjsFBAJrCEASqDoDsFBAMTNwZWCNwK8EBAVINAKbEZAb+GkK5DTYTUEJQuGF4WTGQZcGSQYvHAQ6MIARL4DCJrXCLgI4OfAY4O5JcPQYRcPQYRcPZwJcQkmYCKAC/AXuQ")) } var iconCompass = { - width : 60, height : 60, bpp : 3, + width : 50, height : 50, bpp : 3, transparent : 2, - buffer : require("heatshrink").decompress(atob("pMkyQCFr//AAOkBYwUMC6FLCgoAB/osLl4VHAAIsK34OClmy5cs+YHC6RWL8gKEkofCLhF///8FIWz/YvCn4WB9JBI/1JsuWrN/21Zsp5DIgxBBEwOzFgRcC54FCIgyDB/mSrN9FglnAoMnRI2X//yFIXbQweyAoXPKALFF/ckEYJZCAQtkEoLOE36DDARSJBOIZuB5JWCFhFnySrBOIRuB9myVoYCGR4RxE//2WAKtCFg9ty1J//0boZWLF4jjCOoKwBEwYvGkIFCn6eBTYIpL5+QmQsDTwKbBFghQBAodmpMJkrjD6QsM2MkigyE/sl//kz/5EwNbbQeWpmSpCXBy3/sv//Vf/wsJiVJgkMAoM8FgP/0osBKYfZFgeJYAMCpJZCsm/FgPyVRFkyVBgaMF/+v/5QBVotJaoMkyBFBGoeffIIsICgVBhYLFCoP/JQQsEvmTC4OEGAILDFgfPEAs///fiFAGog+BFgTOBQYcky4JB/MkQ4IsCeoIsD//8Jovx48QqQIDl7yBv5ZC2wsDagP/9/5kLdBMQTsBQxKnB+4sBmSSGQxFnBIX+Q4KSFFhGzMoIKBXgwsC+rgGMQX9QYI4Fsm//VfFgu3/9/dJBlC0v/8mWvJ6CWIbdFy3Zsn/FgP+5d8EATdBXIrpDeQP/0m/9ggDLAO2QY44BSQPSpbOBagf3KxACCPQMk359BEAOXRgJWBFg9ZfAPSpN//YgCngpJX4fpkgnB2QgCFIIyCFg7pBHIIwBUgRWKAQO//4kCDQNJt4gBKxF9y1f//0dQX//mzQZf7lrIBFgSeB/MlFI4CDHoKbBSYRIB+QsLcAKbCC4TcB8hQDrNtAQlkEoKbBFgQzBcYRcC2a8CAQXfNwYXCOIP/WAImCv+2rNnAQI6BNwZxEWwTXDAoYLBNwgCCcYP/7KzCv6GDBQLdCC4pECRI3PBIJBGIgv/2wHCrYHCII6JFAASABAASDFAQyoBAAzFEARBcCAAZWJC5QUJA==")) + buffer : require("heatshrink").decompress(atob("pMkyQCDl//AAPSBYwCFv4RCAAOkCJNLCAgACCJm2rNn34FB+g1Jvny5cs2XPn///QRI9uWEYP2rNly5NHNYN82YjB/4mC5YmBOgkl//9y1bsuW/4CB/Nlz//9I4D3/8I4M8EAICB55NCL4g/BIgRKBAQtnL4lf+QdCI4YCD2Y4DSQPZtojHsuerI4Dv/flnzEZB3CHAJuB8ojIAQY4CNwJHI2XHTAY4B/4gJrGBAoSqBpf2EZMQmRxEv/5Nw9YyVCAoO+rf/0v/Nw/PjFB4ZxCn/+y7dBJQyNBkAIDz/6/7dBJQsYsMEhgsE//+7IjFsTYBwAIE/4ABEYs8uPEiFyF4gRBXIImEBAPSpAjDtuX//9+YmERgMcuODBAU9+xKCr68Ev4lBNwm//IJCnhxDDQPx4xuFJQhBDDQXwTwpKBSos8//HjlwYQyVG34aB2zCG//1Nw6SFAQTgD/JuD+wjFrbgCr/yMQI+B/lxEY08UgPpl4jCNwP+I4wCBUgOk3/8DoXxI44CBn/0yREDzx0EAQlndANJv4gJAQf3/VJkq8CJoZuGXIPpkg4BOIZuI5/9CII4BEZAmDNwIRBHAJxDNxH+CII4CSQW+NALgBtomBt5uCHAbjB2ZoCAQPyJQP/NwIRCkm//4gBIgP/SQn/CImSYALjDviSDQAYUDL4ImEEYYRGL4X/76PCI4P/SQYCFl4MBAAgRJEwYRPOgZrHpMgA")) } var iconAlarm = { - width : 60, height : 60, bpp : 1, - buffer : require("heatshrink").decompress(atob("AAUPA43wByn/A4v/A4s/A4PAEYYGB/4GCgIGC/AVFCwcP/tfq4WCCoPq/W/CwU/6tfqt/CwMD/2q/Wr/gOBv9VBwNf8AkB/WogWqEoMB/tVgEVq4lBj/qwEAlW/wED+tQCQNVEoM/1QoBgW/4EH+tAA4NVvwzB1AOC1/gg/VBwUVBwN+0BsCBwMP6oGCit/gH+Bwer+AOEgtfBwsr+CZEg6RCNYJ0CAwP+BxgsOBxhKBNAJZDNAR3CNASGEBwSGGUgaVBUgsKUgLCBqg6BqrCDHgMq/7GB/9VqNVq/wNYP6d4R0CfwdffwX/BwOvfwUf+oOBEgQlB/X6/4kBCwXfr4VCCwKZCCoQWBAAIVCCwX/CocAh7FEA4YGEA4IGFgAjDBxw")) + width : 50, height : 50, bpp : 3, + transparent : 1, + buffer : require("heatshrink").decompress(atob("kmSpICEp//BAwCJn/+CJ8k//5CKAABCJs8uPH//x48EI5YjCAARNKEYUcv//jgFBExEnEYoAC+QmHIgIgC/gpCuPBCI2fIgU4AQXjA4P8CIuTEYZKBAolwHApXBEAWP//jxwpBAALaFDoYCIiQmDDIP4EAT+CEwnJEwYjLAQLaFEYomDKALmDNwoCIOIZuD8AkFgCYDHAQjMAQTdDNwOAEg0Dx0/cYeREZtxQYOTHgJuHOIvkXJy8DNwIACJQ8Ah4NDAAfxEZARHOIIkHg4jQAQb1CQ4KVJgEOnDIBSoIjNAQPBcAaVJcAKVBcDGOcD7OBMQM48BuH8f//JKCnhKNggRBkmfTQJxBEwhuD/gRCyVHJRlyCIVJXgYmB8ZQBAoIKBXIQmCOIt/NxAUCOIImCIgIpCBAJuDAQZEE/huIAQWTDgImBTYQGC8gRFcYpKFCI8kDwQAFCJBfBEAX/+IjBiQRIEw4jJAQc8v//NYwCIOgJrIJpA1OcwbaFAQWQA=")) } Graphics.prototype.setFontAntonioMedium = function(scale) { @@ -75,7 +77,7 @@ function draw(queue){ Bangle.isHRMOn() ? iconHrm : Bangle.isCompassOn() ? iconCompass : iconPlanet; - g.drawImage(iconImg, 110, 95); + g.drawImage(iconImg, 115, 105); // Write time var currentDate = new Date(); @@ -102,7 +104,7 @@ function draw(queue){ // Alarm within symbol if(alarm > 0){ g.setFontAlign(0,0,0); - g.drawString(alarm, 110+30, 95+30); + g.drawString(alarm, 115+25, 105+25); g.setFontAlign(-1,-1,0); } From d3538a03f10f6323afc8656cef397a561cf35bf0 Mon Sep 17 00:00:00 2001 From: David Peer Date: Mon, 22 Nov 2021 19:12:50 +0100 Subject: [PATCH 0809/1062] Added raster --- apps/lcars/lcars.app.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 373e98ad6..366b88018 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -70,6 +70,15 @@ function draw(queue){ // Draw background image g.drawImage(backgroundImage, 0, 24); + // Draw raster + for(var x=0; x<6; x++){ + g.drawLine(115+x*10, 100, 115+x*10, 160); + } + + for(var y=0; y<6; y++){ + g.drawLine(110, 105+y*10, 170, 105+y*10); + } + // Draw symbol var iconImg = alarm >= 0 ? iconAlarm : From 740f4a95665fd92ad87e83dd381da7e55c2e4b18 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:52:27 -0800 Subject: [PATCH 0810/1062] Create boot.js not part of the current merge --- apps/schoolCalendar/boot.js | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/schoolCalendar/boot.js diff --git a/apps/schoolCalendar/boot.js b/apps/schoolCalendar/boot.js new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/apps/schoolCalendar/boot.js @@ -0,0 +1 @@ + From 84d9ca4b337e36c4f62ffce4b3059f8f2909d482 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 22 Nov 2021 10:57:14 -0800 Subject: [PATCH 0811/1062] Update boot.js --- apps/schoolCalendar/boot.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/schoolCalendar/boot.js b/apps/schoolCalendar/boot.js index 8b1378917..e4f223c0c 100644 --- a/apps/schoolCalendar/boot.js +++ b/apps/schoolCalendar/boot.js @@ -1 +1,6 @@ - +// check for alarms +(function() { + var alarms = require('Storage').readJSON('schoolCalendarAlarms.json',1)||[]; + var time = new Date(); + E.showPrompt(School Calendar Alarm Test) +})(); From 7fd37f7e4e669c0aea7b5324b85f9d85ca394dcb Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 22 Nov 2021 12:18:44 -0800 Subject: [PATCH 0812/1062] Update custom.html --- apps/schoolCalendar/custom.html | 38 ++++----------------------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 49e0acb13..225049b28 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -94,14 +94,14 @@ require("Font8x12").add(Graphics); require("Font7x11Numeric7Seg", 2).add(Graphics); -let nIntervId; +var file = require("Storage").open("calendarItems.csv","w"); +let nIntervId; function redrawScreen() { layout.render(layout.background); layout.render(layout.buttons); draw(); } - function updateDay(ffunction,day){ if(ffunction == 1){ switch (day) { @@ -174,12 +174,13 @@ function updateDay(ffunction,day){ return day; } } - function getScheduleTable() { let schedule = ${JSON.stringify(schedule)}; return schedule; } +file.write(JSON.stringify(schedule)); + function findNextScheduleIndex() { var schedule = getScheduleTable(); var currentDate = new Date(); @@ -194,16 +195,10 @@ function findNextScheduleIndex() { } return 0; } - - function getUpArrow() {return require("heatshrink").decompress(atob("hkOyANKmv9AIIjRCoYZRlvdAI8U3YVK3oBJC4Mc7YVRC4sc7gVCzoBNC4oZDGowXGR58lvoBFC9FcAIoXongBFC58dngBFC6EcAIoPHA"));} - function getDownArrow() {return require("heatshrink").decompress(atob("hkOyALImv9AIojPmvdAIoXPlvdAIoXQ3oBFC9GdAIoXnkt9AIoPPAI8U3cc7cc7gBBDIVcAJYXFGYwXOLpU8AI4XBO5sdjgBFR54ZFBpIA=="));} - function getMenuIcon() {return require("heatshrink").decompress(atob("iEQyBC/AEU+rwBEn02js17st3stvklrkljkc/cc3cUzYBBD5AdUD4oA/P/4A/P/4A/ADoA=="));} - function getDotIcon() {return require("heatshrink").decompress(atob("iEQyBC/AA0t3oBBA4ndAIIPGA4gAFkt9lt9AYIHEzoBBBIwRED41cks8AYIJGA44RGP8xtGP44RJBYh1CAIIHHBJJ/KroBBPoqBFB4YRDAA8dngHHBJKdq3oBDBI4RNP4l9AIYHHBJJBJks8AIIHTAH4ABA="));} - var currentPositionTable = 0; var numberOfItemsShown = 8; //Table Positions: @@ -215,7 +210,6 @@ var rectEndX = 210; LIST = 1; INFORMATION = 2; currentStage = LIST; - function splitter(str, l){ var strs = []; while(str.length > l){ @@ -230,7 +224,6 @@ function splitter(str, l){ strs.push(str); return strs; } - function updateMinutesToCurrentTime(currentMinuteFunction) { if (currentMinuteFunction<10){ currentMinuteUpdatedFunction = "0"+currentMinuteFunction; @@ -239,12 +232,10 @@ function updateMinutesToCurrentTime(currentMinuteFunction) { } return currentMinuteUpdatedFunction; } - function renderBackground(l) { g.clearRect(0,0,240,20); g.drawImage(getBackgroundImage(),110,130,{scale:9,rotate:0}); } - function renderTable(l) { var foundNumber = findNextScheduleIndex(); var yellowIndex = 3; @@ -258,7 +249,6 @@ function renderTable(l) { g.setColor(255,0,0); g.drawRect(rectStartX,rectStart+(currentPositionTable*20),rectEndX,rectEnd+(20*currentPositionTable)); } - function renderTableText(l) { var foundSchedule = getScheduleTable(); var foundNumber = findNextScheduleIndex(); @@ -266,8 +256,6 @@ function renderTableText(l) { if (startNumber < 0) { startNumber = 0; } var endNumber = startNumber + 8 - (foundNumber - startNumber); if (endNumber > foundSchedule.length-1) { endNumber = foundSchedule.length-1; } - - var scheduleHourUpdated; var scheduleMinuteUpdated; for(var currentNumber = startNumber; currentNumber<=endNumber; currentNumber++){ @@ -284,7 +272,6 @@ function renderTableText(l) { g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd+" "+schduleDay+" "+scheduleDecriptionUpdated,13,50+(currentNumber*20)); } } - function buttonsF(l){ if(currentStage == LIST){ g.drawImage(getDotIcon(),223.5,115); @@ -294,7 +281,6 @@ function buttonsF(l){ g.drawImage(getUpArrow(),225,30); g.drawImage(getDownArrow(),225,215); } - function draw() { var currentDate = new Date(); var currentDayOfWeek = currentDate.getDay(); @@ -320,7 +306,6 @@ function draw() { layout.render(layout.time); } } - function RedRectDown() { if(currentPositionTable > 0){ currentPositionTable -= 1; @@ -331,7 +316,6 @@ function RedRectDown() { } } } - function RedRectUp() { if(currentPositionTable < numberOfItemsShown){ currentPositionTable += 1; @@ -342,24 +326,20 @@ function RedRectUp() { } } } - function renderMiniBackground(l){ for(var i = 233;i<=240;i++){ g.drawImage(getBackgroundImage(),i,123,{scale:10,rotate:0}); } } - function renderLoading(l){ g.setFont("8x12"); g.drawString("Loading...",240/2-20,240/2-20); } - function renderInformation(l){ var foundNumber = findNextScheduleIndex(); var foundSchedule = getScheduleTable(); var startNumber = foundNumber - 2; if (startNumber < 0) { startNumber = 0; } - if ((startNumber+currentPositionTable) <= foundSchedule.length-1) { scheduleMinuteUpdatedStart = updateMinutesToCurrentTime(foundSchedule[foundNumber].sm); scheduleHourUpdatedStart = foundSchedule[foundNumber].sh; @@ -379,7 +359,6 @@ function renderInformation(l){ g.drawString(scheduleHourUpdatedStart+":"+scheduleMinuteUpdatedStart+"-"+scheduleHourUpdatedEnd+":"+scheduleMinuteUpdatedEnd,13,currentY+15+50); } } - var Layout = require("Layout"); var layout = new Layout( {type:"h", c: [ @@ -399,12 +378,8 @@ var layout = new Layout( {label:"", cb: l=>print("Two")}, {label:"", cb: RedRectDown()} ]}); - - function getBackgroundImage() {return require("heatshrink").decompress(atob("j0ZyEKIf4A4gIB6gQB6gYB6ggB6goB6gwB6g4B6hAABAYIBHBZIVLAK8IhIBXgAThhQB6hYB6hgB6hoB6hwB6h4B6iAB6iIB6iQBHiAJOB54XSiYB6igB6ioB6iwB6i4B5A="));} - function logDebug(message) {console.log(message);} - function changeScene(){ layout.render(layout.buttons); if(currentStage == INFORMATION){ @@ -418,17 +393,12 @@ function changeScene(){ layout.render(layout.buttons); draw(); } - // timeout used to update every minute var drawTimeout; - setInterval(draw, 15000); - - setWatch(RedRectUp, BTN3, { repeat:true, edge:'rising', debounce : 50 }); setWatch(RedRectDown, BTN1, { repeat:true, edge:'rising', debounce : 50 }); setWatch(changeScene, BTN2, { repeat:true, edge:'rising', debounce : 50 }); - layout.update(); layout.render(layout.loading); layout.render(layout.background); From ff1d5f2d76ddf8a9cfc4ffea2ea5dcd636851a25 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 22 Nov 2021 21:03:53 +0000 Subject: [PATCH 0813/1062] Pastel: f_orbitron font module --- apps.json | 1 + apps/pastel/f_orbitron.js | 11 +++++++++++ apps/pastel/pastel.app.js | 24 ++++++++++++++++-------- 3 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 apps/pastel/f_orbitron.js diff --git a/apps.json b/apps.json index 8d90a1d1a..95c76374a 100644 --- a/apps.json +++ b/apps.json @@ -4036,6 +4036,7 @@ "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ + {"name":"f_orbitron","url":"f_orbitron.js"}, {"name":"pastel.app.js","url":"pastel.app.js"}, {"name":"pastel.img","url":"pastel.icon.js","evaluate":true}, {"name":"pastel.settings.js","url":"pastel.settings.js"} diff --git a/apps/pastel/f_orbitron.js b/apps/pastel/f_orbitron.js new file mode 100644 index 000000000..b58056c0e --- /dev/null +++ b/apps/pastel/f_orbitron.js @@ -0,0 +1,11 @@ + + +var widths = atob("ChcmEiUlISUlHiYlCg=="); +var font = atob("AAAAAAAAAAAAAAAAAAAAPAAAAAAB4AAAAAAPAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAPwAAAAAD8AAAAAA+AAAAAAPgAAAAAD4AAAAAB+AAAAAAfgAAAAAHwAAAAAB8AAAAAAfAAAAAAPwAAAAAD4AAAAAA+AAAAAAPgAAAAAH4AAAAAB+AAAAAAfAAAAAAHwAAAAAB8AAAAAA/AAAAAAPwAAAAAD4AAAAAAAAAAAAAAAAAAAAAB///+AAA////8AAP////wAD/////AAfgAA/4ADwAAH/AAeAAB94ADwAAfPAAeAAH54ADwAB+PAAeAAPh4ADwAD4PAAeAA+B4ADwAPgPAAeAD8B4ADwAfAPAAeAHwB4ADwB8APAAeAfAB4ADwH4APAAeB+AB4ADwPgAPAAeD4AB4ADw+AAPAAePwAB4ADz8AAPAAefAAB4AD3wAAPAAf8AAB4AD/////AAf////4AB////+AAH////gAAH///gAAAAAAAAAAAAAAAAAACAAAAAAAwAAAAAAOAAAAAADwAAAAAB+AAAAAAfwAAAAAH4AAAAAB+AAAAAAfgAAAAAD4AAAAAAf////4AD/////AAf////4AD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAf/4AA+AP//AAPwD//4AD+Af//AAfgH4H4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4AD//8APAAf//gB4AB//4APAAH/+AB4AAH+AAHAAAAAAAAAAAAAAAAAAAAAAAAAOAAA4AAHwAAHgAB+AAA+AAfwAAH4AD4AAAfAAeAAAB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAfAPgB4AD//8APAAP////4AA////+AAD////wAAAAP/8AAAAAAAAAAAAAAAAAAAAPgAAAAAD8AAAAAA/gAAAAAP8AAAAAB/gAAAAAf8AAAAAH3gAAAAB88AAAAAfHgAAAAHw8AAAAB+HgAAAAfg8AAAAH4HgAAAA+A8AAAAPgHgAAAD4A8AAAA/AHgAAAPwA8AAAD8AHgAAA/AA8AAAPwAHgAAB8AA8AAAfgAHgAAD/////AAf////4AD/////AAf////4AAAAA8AAAAAAHgAAAAAA8AAAAAAHgAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAD//4BwAAf//APgAD//4B+AAf//AP4AD8H4A/AAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAP//4ADwA///AAeAD//wADwAP/8AAcAAP8AAAAAAAAAAAAAAAAAAAAAAAAAB///+AAA////8AAP////wAD/////AAfg/AH4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAAAPAB4AAAB///AAAAH//4AAAAf/+AAAAB//gAAAAB/gAAAAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAf////4AB/////AAP////4AA/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+D/wAAH////gAB////+AAf////4AD8D+A/AAeAHgB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA8APAAf////4AB/////AAP////wAAf/v/8AAA/gP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAH/+ABgAB//4AOAAf//gB4AD4B8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA+APAAf////4AB////+AAP////wAAf///4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAB4AADwAAPAAAeAAB4AADwAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); + +exports.add = function(graphics) { + // Actual height 32 (35 - 4) + graphics.prototype.setFontOrbitron = function() { + this.setFontCustom(font, 46, widths, 45+(1<<8)+(1<<16)); + } +}; diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 1fe3e4a58..4aa74e910 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -1,12 +1,4 @@ -Graphics.prototype.setFontOrbitron = function() { -// Actual height 32 (35 - 4) -var widths = atob("ChcmEiUlISUlHiYlCg=="); -var font = atob("AAAAAAAAAAAAAAAAAAAAPAAAAAAB4AAAAAAPAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAAAPwAAAAAD8AAAAAA+AAAAAAPgAAAAAD4AAAAAB+AAAAAAfgAAAAAHwAAAAAB8AAAAAAfAAAAAAPwAAAAAD4AAAAAA+AAAAAAPgAAAAAH4AAAAAB+AAAAAAfAAAAAAHwAAAAAB8AAAAAA/AAAAAAPwAAAAAD4AAAAAAAAAAAAAAAAAAAAAB///+AAA////8AAP////wAD/////AAfgAA/4ADwAAH/AAeAAB94ADwAAfPAAeAAH54ADwAB+PAAeAAPh4ADwAD4PAAeAA+B4ADwAPgPAAeAD8B4ADwAfAPAAeAHwB4ADwB8APAAeAfAB4ADwH4APAAeB+AB4ADwPgAPAAeD4AB4ADw+AAPAAePwAB4ADz8AAPAAefAAB4AD3wAAPAAf8AAB4AD/////AAf////4AB////+AAH////gAAH///gAAAAAAAAAAAAAAAAAACAAAAAAAwAAAAAAOAAAAAADwAAAAAB+AAAAAAfwAAAAAH4AAAAAB+AAAAAAfgAAAAAD4AAAAAAf////4AD/////AAf////4AD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAf/4AA+AP//AAPwD//4AD+Af//AAfgH4H4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4AD//8APAAf//gB4AB//4APAAH/+AB4AAH+AAHAAAAAAAAAAAAAAAAAAAAAAAAAOAAA4AAHwAAHgAB+AAA+AAfwAAH4AD4AAAfAAeAAAB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAfAPgB4AD//8APAAP////4AA////+AAD////wAAAAP/8AAAAAAAAAAAAAAAAAAAAPgAAAAAD8AAAAAA/gAAAAAP8AAAAAB/gAAAAAf8AAAAAH3gAAAAB88AAAAAfHgAAAAHw8AAAAB+HgAAAAfg8AAAAH4HgAAAA+A8AAAAPgHgAAAD4A8AAAA/AHgAAAPwA8AAAD8AHgAAA/AA8AAAPwAHgAAB8AA8AAAfgAHgAAD/////AAf////4AD/////AAf////4AAAAA8AAAAAAHgAAAAAA8AAAAAAHgAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAD//4BwAAf//APgAD//4B+AAf//AP4AD8H4A/AAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAP//4ADwA///AAeAD//wADwAP/8AAcAAP8AAAAAAAAAAAAAAAAAAAAAAAAAB///+AAA////8AAP////wAD/////AAfg/AH4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAeAPAB4ADwB4APAAAAPAB4AAAB///AAAAH//4AAAAf/+AAAAB//gAAAAB/gAAAAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAeAAAAAADwAAAAAAf////4AB/////AAP////4AA/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+D/wAAH////gAB////+AAf////4AD8D+A/AAeAHgB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA4APAAeAHAB4ADwA8APAAf////4AB/////AAP////wAAf/v/8AAA/gP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/gAAAAH/+ABgAB//4AOAAf//gB4AD4B8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA8APAAeAHgB4ADwA+APAAf////4AB////+AAP////wAAf///4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAB4AADwAAPAAAeAAB4AADwAAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); -var scale = 1; // size multiplier for this font -g.setFontCustom(font, 46, widths, 45+(scale<<8)+(1<<16)); -}; - Graphics.prototype.setFontCabinSketch = function() { // Actual height 48 (51 - 4) var widths = atob("ECMtGCEiJSIkHyYlDw=="); @@ -69,6 +61,22 @@ function loadSettings() { //console.log(settings); } +// load font files based on settings.font +if (settings.font == "Architect") + require("f_artitect").add(Graphics); +else if (settings.font == "GochiHand") + require("f_gochihand").add(Graphics); +else if (settings.font == "CabinSketch") + require("f_cabin").add(Graphics); +else if (settings.font == "Orbitron") + require("f_orbitron").add(Graphics); +else if (settings.font == "Monoton") + require("f_monoton").add(Graphics); +else if (settings.font == "Elite") + require("f_elite").add(Graphics); +else + require("f_lato").add(Graphics); + var mm_prev = "xx"; function draw() { From d68bcf1ff18893e5e7fda5cda55db7ed69502f78 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 22 Nov 2021 21:05:28 +0000 Subject: [PATCH 0814/1062] Pastel: f_orbitron font module --- apps.json | 2 +- apps/pastel/ChangeLog | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 95c76374a..7a3cd5adc 100644 --- a/apps.json +++ b/apps.json @@ -4027,7 +4027,7 @@ "id": "pastel", "name": "Pastel Clock", "shortName": "Pastel", - "version": "0.05", + "version": "0.06", "description": "A Configurable clock with custom fonts and background", "icon": "pastel.png", "screenshots": [{"url":"screenshot_pastel.png"}], diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index 1277f0d9d..423e9da42 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -3,3 +3,4 @@ 0.03: Make it work with Gadgetbridge, Notifications fullscreen on a Bangle 2 0.04: Leave space at the bottom for Chrono widget, set back option at first option 0.05: Added 2 new fonts +0.06: COnverted fonts to font modules From ec7f9ac69d012a64c1dd383bd688ab8815fb49ad Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 22 Nov 2021 21:13:48 +0000 Subject: [PATCH 0815/1062] Pastel: f_orbitron font module --- apps/pastel/pastel.app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 4aa74e910..4bacf419a 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -61,6 +61,8 @@ function loadSettings() { //console.log(settings); } +loadSettings(); + // load font files based on settings.font if (settings.font == "Architect") require("f_artitect").add(Graphics); @@ -176,7 +178,6 @@ Bangle.on('lcdPower', function(on) { draw(); }); -loadSettings(); g.clear(); var secondInterval = setInterval(draw, 1000); draw(); From 723e1a6cb2752d12e1a1d1eed45a39000f50208f Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 22 Nov 2021 21:17:50 +0000 Subject: [PATCH 0816/1062] Pastel: f_orbitron font module --- apps/pastel/pastel.app.js | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 4bacf419a..f035f4b6d 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -61,23 +61,23 @@ function loadSettings() { //console.log(settings); } -loadSettings(); - -// load font files based on settings.font -if (settings.font == "Architect") - require("f_artitect").add(Graphics); -else if (settings.font == "GochiHand") - require("f_gochihand").add(Graphics); -else if (settings.font == "CabinSketch") - require("f_cabin").add(Graphics); -else if (settings.font == "Orbitron") - require("f_orbitron").add(Graphics); -else if (settings.font == "Monoton") - require("f_monoton").add(Graphics); -else if (settings.font == "Elite") - require("f_elite").add(Graphics); -else - require("f_lato").add(Graphics); +function loadFonts() { + // load font files based on settings.font + if (settings.font == "Architect") + require("f_artitect").add(Graphics); + else if (settings.font == "GochiHand") + require("f_gochihand").add(Graphics); + else if (settings.font == "CabinSketch") + require("f_cabin").add(Graphics); + else if (settings.font == "Orbitron") + require("f_orbitron").add(Graphics); + else if (settings.font == "Monoton") + require("f_monoton").add(Graphics); + else if (settings.font == "Elite") + require("f_elite").add(Graphics); + else + require("f_lato").add(Graphics); +} var mm_prev = "xx"; @@ -178,6 +178,8 @@ Bangle.on('lcdPower', function(on) { draw(); }); +loadSettings(); +loadFonts(); g.clear(); var secondInterval = setInterval(draw, 1000); draw(); From 8576d90e50c6c1099b78b1fb82808f788aeabba6 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 22 Nov 2021 21:24:27 +0000 Subject: [PATCH 0817/1062] Pastel: f_orbitron font module --- apps/pastel/pastel.app.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index f035f4b6d..8d88b5071 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -53,15 +53,17 @@ const SETTINGS_FILE = "pastel.json"; let settings = undefined; function loadSettings() { - //Console.log("loadSettings()"); + console.log("loadSettings()"); settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; settings.grid = settings.grid||false; settings.date = settings.date||false; settings.font = settings.font||"Lato"; - //console.log(settings); + console.log(settings); } function loadFonts() { + console.log("loadFonts()"); + console.log(settings); // load font files based on settings.font if (settings.font == "Architect") require("f_artitect").add(Graphics); From a29bba62cba6037a64286d89304e3f92051cbf2a Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 22 Nov 2021 21:29:26 +0000 Subject: [PATCH 0818/1062] Pastel: f_orbitron font module --- apps/pastel/pastel.app.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 8d88b5071..149abb11a 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -78,7 +78,8 @@ function loadFonts() { else if (settings.font == "Elite") require("f_elite").add(Graphics); else - require("f_lato").add(Graphics); + //require("f_lato").add(Graphics); + require("f_orbitron").add(Graphics); } var mm_prev = "xx"; @@ -140,7 +141,8 @@ function draw() { else if (settings.font == "Elite") g.setFontSpecialElite(); else - g.setFontLato(); + //g.setFontLato(); + g.setFontOrbitron(); g.setFontAlign(1,-1); // right aligned g.drawString(hh, x - 6, y); From 29ecb8daa20219d1657f2ac8d2c76885d5056753 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 22 Nov 2021 22:08:19 +0000 Subject: [PATCH 0819/1062] Pastel: font modules --- apps.json | 7 +++++ apps/pastel/pastel.app.js | 64 +++++---------------------------------- 2 files changed, 14 insertions(+), 57 deletions(-) diff --git a/apps.json b/apps.json index 7a3cd5adc..cd1e28520 100644 --- a/apps.json +++ b/apps.json @@ -4036,7 +4036,14 @@ "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ + {"name":"f_architect","url":"f_architect.js"}, + {"name":"f_gochihand","url":"f_gochihand.js"}, + {"name":"f_cabin","url":"f_cabin.js"}, {"name":"f_orbitron","url":"f_orbitron.js"}, + {"name":"f_monoton","url":"f_monoton.js"}, + {"name":"f_elite","url":"f_elite.js"}, + {"name":"f_lato","url":"f_lato.js"}, + {"name":"f_latosmall","url":"f_latosmall.js"}, {"name":"pastel.app.js","url":"pastel.app.js"}, {"name":"pastel.img","url":"pastel.icon.js","evaluate":true}, {"name":"pastel.settings.js","url":"pastel.settings.js"} diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 149abb11a..3c2dd1bba 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -1,69 +1,20 @@ - -Graphics.prototype.setFontCabinSketch = function() { -// Actual height 48 (51 - 4) -var widths = atob("ECMtGCEiJSIkHyYlDw=="); -var font = atob("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"); -var scale = 1; // size multiplier for this font -g.setFontCustom(font, 46, widths, 65+(scale<<8)+(1<<16)); -}; - -Graphics.prototype.setFontGochiHand = function() { -// Actual height 54 (59 - 6) -var widths = atob("GRMtICcqJiopKiwoGQ=="); -var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAA+AAAAAAAAAAAAfwAAAAAAAAAAAH+AAAAAAAAAAAB/gAAAAAAAAAAAf4AAAAAAAAAAAH+AAAAAAAAAAAB/gAAAAAAAAAAAP4AAAAAAAAAAAD8AAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAP+AAAAAAAAAAA//gAAAAAAAAAH//4AAAAAAAAA///+AAAAAAAAP////gAAAAAAB/////4AAAAAAP/////+AAAAAD//////+AAAAA///////wAAAAAf//////AAAAAAP/////4AAAAAAD/////AAAAAAAA////4AAAAAAAAP//+AAAAAAAAAD//gAAAAAAAAAA/8AAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAAAAAAAAAAH//AAAAAAAAAAH//8AAAAAAAAAH///wAAAAAAAAD///+AAAAAAAAB////wAAAAAAAA////+AAAAAAAAf////gAAAAAAAP/8//8AAAAAAAD/wAf/AAAAAAAB/wAD/4AAAAAAA/4AAP+AAAAAAAP8AAB/wAAAAAAD/AAAf8AAAAAAB/gAAD/AAAAAAAf4AAA/wAAAAAAH8AAAP8AAAAAAB/AAAD/AAAAAAAfwAAA/wAAAAAAP8AAAH8AAAAAAD/AAAD/AAAAAAAf4AAA/wAAAAAAH+AAAP8AAAAAAB/gAAD/AAAAAAAf4AAA/wAAAAAAH/AAAP4AAAAAAB/wAAH+AAAAAAAP+AAB/gAAAAAAD/wAA/wAAAAAAA/+AAf8AAAAAAAH/wAH+AAAAAAAB/+AH/gAAAAAAAP/4D/wAAAAAAAB////4AAAAAAAAf///+AAAAAAAAD////AAAAAAAAAf///gAAAAAAAAD///wAAAAAAAAAP//wAAAAAAAAAA//4AAAAAAAAAAD/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAAAAAAA/gAAAAAAAAAAAf4AAAAAAAAAAAP+AAAAAAAAAAAH/gAAAAAAAAAAB/wAAAAAAAAAAA/4AAAAAAAAAAAf8AAAAAAAAAAAH/AAAAAAAAAAAD/gAAAAAAAAAAA/wAAAAAAAAAAAf4AAAAAAAAAAAP+AAAAAAAAAAAD/AAAAAAAAAAAB/wAAAAAAAAAAAf+AAAAAAAAAAAH/4AAAAAAAAAAD//8AAAAAAAAAA////4AAAAAAAAP/////gAAAAAAB/////8AAAAAAAf/////AAAAAAAB/////wAAAAAAAP////8AAAAAAAAP////AAAAAAAAAH///wAAAAAAAAAAf/4AAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAHwAAAAAAD8AAAD+AAAAAAB/AAAB/gAAAAAA/4AAA/8AAAAAAf+AAAf/AAAAAAH/gAAH/wAAAAAD/wAAD/8AAAAAB/4AAB//AAAAAAf8AAA//wAAAAAH+AAAf/8AAAAAD/AAAP//AAAAAA/wAAD//wAAAAAP4AAB//8AAAAAD+AAA///AAAAAA/gAAf8/wAAAAAP4AAP+P8AAAAAD/AAP/j/AAAAAA/wAH/w/wAAAAAP+AH/4P8AAAAAD/wD/8D/AAAAAA//P/+A/wAAAAAH////AP+AAAAAB////AB/gAAAAAP///gAf4AAAAAD///wAH+AAAAAAf//wAB/gAAAAAD//4AAf4AAAAAAP/4AAH+AAAAAAA/wAAB/gAAAAAAAAAAAf4AAAAAAAAAAAH+AAAAAAAAAAAA/gAAAAAAAAAAAPwAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAHgAAAAAAAAAAAD8AAAAAAAAAAAB/gAAAAAAAAAAAf8AAAAAAAAAAAP+AAAAAAAAAAAD/gAAAAAAAAAAB/wAAAcAAAAAAAf8AAAPwAAAAAAH+AAAD/AAAAAAB/gAAA/4AAAAAA/wAAAP/AAAAAAP8AAAD/4AAAAAD/AB+A//AAAAAA/wA/wH/wAAAAAP4AP8A/+AAAAAD+AD/AD/gAAAAA/gB/wAf8AAAAAP8Af8AH/AAAAAD/AH/AA/wAAAAA/wB/gAP8AAAAAP+Af4AD/AAAAAD/gP+AA/wAAAAAf+H/gAP8AAAAAH///4AD/AAAAAB////AA/wAAAAAP///wAP8AAAAAB///+AD/AAAAAAf///gA/gAAAAAD///+Af4AAAAAAP/n/4f+AAAAAAB/g////AAAAAAAAAP///wAAAAAAAAB///4AAAAAAAAAP//8AAAAAAAAAB///AAAAAAAAAAP//AAAAAAAAAAA//gAAAAAAAAAAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAAAAAAAH+AAAAAAAAAAAH/wAAAAAAAAAAD/+AAAAAAAAAAD//gAAAAAAAAAB//8AAAAAAAAAB///AAAAAAAAAA///wAAAAAAAAAf//8AAAAAAAAAf/7/AAAAAAAAAP/4/4AAAAAAAAP/4H+AAAAAAAAH/8B/gAAAAAAB//8Af4AAAAAAA//+AH+AAAAAAAP//AB/gAAAAAAH//wAf4AAAAAAB///AH+AAAAAAAf//+B/gAAAAAAH///+f4AAAAAAA/////+AAAAAAAH/////wAAAAAAA/////8AAAAAAAAf////8AAAAAAAAf////+AAAAAAAA/////4AAAAAAAA/////AAAAAAAAB////wAAAAAAAAB///8AAAAAAAAAD///AAAAAAAAAA///wAAAAAAAAAP//4AAAAAAAAAD/D8AAAAAAAAAA/wAAAAAAAAAAAH4AAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAf4AP/wAAAAAAAf/AH/+AAAAAAAP/4D//wAAAAAAD//A//+AAAAAAB//wP//wAAAAAAf/8D//8AAAAAAH//g///gAAAAAB//4H//4AAAAAAf//AAf/AAAAAAP9/wAD/wAAAAAD/P+AAf8AAAAAA/j/gAD/AAAAAAP4f4AA/4AAAAAD+H/AAH+AAAAAA/h/wAB/gAAAAAP4P+AAf4AAAAAD+D/gAH+AAAAAA/g/4AA/gAAAAAP4H/AAP4AAAAAD+B/wAD+AAAAAB/gP+AA/gAAAAAf4D/gAP4AAAAAH+Af8AH+AAAAAB/gH/gB/gAAAAAf4B/4Af4AAAAAH+AP/AH8AAAAAB/gB/4D/AAAAAAf4Af/h/wAAAAAD+AD///4AAAAAA/gA///+AAAAAAP4AH///AAAAAAD+AA///gAAAAAA/gAH//wAAAAAAH4AA//4AAAAAAAMAAD/8AAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAH//wAAAAAAAAAH///AAAAAAAAAD///4AAAAAAAAD////gAAAAAAAB////8AAAAAAAA/////gAAAAAAAf////4AAAAAAAH/4B//AAAAAAAD/wAP/4AAAAAAA/4AD/+AAAAAAAf8AB//wAAAAAAH+AAf/8AAAAAAD/AAP//gAAAAAA/wAD//4AAAAAAP8AA/n+AAAAAAD/AAf5/gAAAAAA/wAH8P8AAAAAAP8AB/D/AAAAAAD/AA/w/wAAAAAA/4AP8P8AAAAAAP+AD+D/AAAAAAD/wA/g/wAAAAAAf8AP4P8AAAAAAH+AD+D/AAAAAAA/gA/g/wAAAAAAHwAP8P8AAAAAAAwAD/D/AAAAAAAAAA/x/wAAAAAAAAAP//4AAAAAAAAAD//+AAAAAAAAAAf//AAAAAAAAAAH//wAAAAAAAAAA//4AAAAAAAAAAH/8AAAAAAAAAAB/+AAAAAAAAAAAH/AAAAAAAAAAAAeAAAAAAAD+AAAAAAAAAAAA/gAAAAAAAAAAAP4AAAAAAAAAAAD+AAAAAAAAAAAA/gAeAAAAAAAAAP4AfwAAAAAAAAD+AH8AAAAAAAAA/gB/AAAAAAAAAP4AfwAAAAAAAAD+AH8AAAAAAAAA/gB/AAAAAAAAAP4AfwAAAAAAAAD+AH8AAAAAAAAA/wD/AAAAAAAAAP8A/wAAAAAAAAD/AP8AAAAAAAAA/wD/AAAAAAAAAP8A/wAAAAAAAAD/gP8AAAAAAAAA/4D/AAAAAAAAAH/A/wAAAAAAAAB/4P+AHwAAAAAAf//////AAAAAAD//////wAAAAAA//////8AAAAAAH//////AAAAAAB//////wAAAAAAP/////8AAAAAAB/////+AAAAAAAH/////AAAAAAAAAP+AAAAAAAAAAAB/gAAAAAAAAAAAf4AAAAAAAAAAAH+AAAAAAAAAAAB/gAAAAAAAAAAAf4AAAAAAAAAAAH+AAAAAAAAAAAB/gAAAAAAAAAAAP4AAAAAAAAAAAD+AAAAAAAAAAAA/gAAAAAAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAAAAD//AAAAAAAAD+D//8AAAAAAAD/9///gAAAAAAB/////8AAAAAAA//////AAAAAAAf/////4AAAAAAH//////AAAAAAD///4H/wAAAAAA///4Af+AAAAAAP4f+AD/gAAAAAH+D/gAf4AAAAAB/A/4AH/AAAAAAfwP+AA/wAAAAAH8D/wAP8AAAAAB/A/8AD/AAAAAAfwP/AAfwAAAAAH+D/wAH8AAAAAB/g/8AB/AAAAAAf4P/AAfwAAAAAH+D/wAH8AAAAAB/w/8AB/AAAAAAP+P+AA/wAAAAAD/x/gAP8AAAAAA///8AD+AAAAAAH///gB/gAAAAAB///4Af4AAAAAAP///gP8AAAAAAB///+H/AAAAAAAP/////gAAAAAAB/////4AAAAAAAH////8AAAAAAAAAH//+AAAAAAAAAA///AAAAAAAAAAD//gAAAAAAAAAAP/wAAAAAAAAAAA/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+AAAAAAAAAAB//wAAAAAAAAAA//+AAAAAAAAAAf//wAAAAAAAAAH//8AAAAAAAAAD///gAAAAAAAAA///4AAAAAAAAAf4H/AAAAAAAAAH+B/wAAAAAAAAB/AP8AAAAAAAAA/wD/AAAAAAAAAP4A/wAAAAAAAAD+AP8AAAAAAAAB/gD/AAAAAAAAAf4A/wAAAAAAAAH+AP8AAAAAAAAB/AD/AAAAAAAAAfwB/gAAAAAAAAH8Af4AAAAAAAAB/AP8AAAAAAAAAfwD/AAAAAAAAAH8B/wAAAAAAAAB/Af4AAAAAAAAAf4P8AAAAAAAAAH+H/AAAAAAAAAB/j/gAAAAAAAAAf//////wAAAAAH///////gAAAAA///////8AAAAAP///////AAAAAD///////wAAAAAf//////4AAAAAH//////+AAAAAB///////gAAAAAP//////wAAAAAB/gAAAAAAAAAAAfgAAAAAAAAAAADwAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAACAAAAAAAAAPgAD4AAAAAAAAH8AB/AAAAAAAAB/gAf4AAAAAAAAf4AH+AAAAAAAAH+AB/gAAAAAAAB/gAf4AAAAAAAAf4AH+AAAAAAAAD+AA/gAAAAAAAA/AAPwAAAAAAAADgAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); -var scale = 1; // size multiplier for this font -g.setFontCustom(font, 46, widths, 80+(scale<<8)+(1<<16)); -}; - -Graphics.prototype.setFontLatoSmall = function() { -// Actual height 21 (20 - 0) -var widths = atob("BAgJDQ0RDwUHBwkNBQgFCA0NDQ0NDQ0NDQ0GBg0NDQkSDw4PEQ0MEBEHCg8LFBESDRIODA0QDxYODg4HCAcNCQcLDAoMDAcLDAYGDAYSDAwMDAkKCAwLEQsLCgcHBw0A"); -var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAEA/84D/zgAAEAAAAAAAAAAAA+AAD8AAAAAAAAAD4AAPgAAAAAAAAAABAADGIAM/gB/8A/+AD8YAAx+AD/4B/4APxgAjGAAIAAAAAAAAAAAAADwMAfg4DnBgMMHg///P/5gMGGAwc4Bg/AEB4AAAAA4AAHwAA5gYDCDgMIcAxjgB84ADnAAA4AAHOABz8AOMYBwwgMDCAgP4AAfAAAAAAAAAAeAAH8APY4B/BgMcGAw4YDBxgMDmA4HwBwPAAB8AAf4ABhgAACAAAAD4AAPgAAAAAAAAAAAAAH/gB//wfAHzgAHAAAAAAAAAAAOAAcfAPwf/8Af+AAAAAAAAAAAANgAAUAABwAAfwAAcAADQAAJAAAAAAAAAAAGAAAYAABgAAGAAP/gA/+AAGAAAYAABgAAGAAAQAAAAAAAEAAA7AAD4AAAAAAAAAAAAGAAAYAABgAAGAAAYAAAgAAAAAAAAAADgAAOAAAYAAAAAAPAAD4AB8AAfAAHwAD4AAeAABAAAADgAB/wAf/wBwHAMAGAwAYDABgMAGA4A4B4PAD/4AH/AAAAAAAAAAAAAYAgDgGAcAYDgBgP/+A//4AABgAAGAAAYAAAAAAAAAAAAQBgHgOAcB4DgPgMA2AwGYDAxgOOGAfwYB+BgBgGAAAAAAAADA4AcDwDgDgMAGAwgYDDBgMcGA5w4B9/ADj4AAAAAAAAABgAAOAAB4AAfgADmAAcYADhgA4GAD//gP/+AAGAAAYAAAgAAAAAADAH4OA/gYDGBgMYGAxgYDGDgMccAw/wCB8AAAAAAAAAAYAAH4AB/wAPjgB8GAOwYDzBgOMGAg44AD/AAH4AACAAAAAAAAAwAADAAAMAGAwB4DAfAMHwAw8ADPAAPwAA+AADgAAAAAAAAAA4+AH38A/44DHBgMMGAwwYDHBgOeOAffwA4/AABwAAAAAAAAAAAAPgAB/AAOMGAww4DBngMF4Aw/ADj4AH+AAPwAAAAAAAAADg4AODgAwGAAAAAAAAAAAABAAAODsA4PgBAYAAAAAAAAAQAABgAAPAAA8AAG4AAZgADHAAMMABgwAAAAAAAAAAAAAAAAEQAAZgABmAAGYAAZgABmAAGYAAZgABmAAGYAAAAAAAAAAAAAAAAGDAAMMAAxwABmAAG4AAPAAA8AABgAAEAAAAAAAAAEAAA4AADABgMHOAw84DGAAP4AAfAAAAAAACAAD/gAePADgGAYAMBh8YMPxgxxGDGEIIYwgxOCDH8IMYRgYBGAwMwD/hAD8AAAAAAAGAAB4AAfgAP4AD+AA/YAPhgA4GAD4YAD9gAD+AAD+AAB+AAB4AABgAAAAAAAD//gP/+AwYYDBhgMGGAwYYDDhgOOGA/84B+/ABh4AAAAAAAAA/gAH/gA+/AHAcA4A4DgBgMAGAwAYDABgMAGA4A4BgDAGAMAAAAAAAAAAAA//4D//gMAGAwAYDABgMAGAwAYDABgOAOAYAwB4PAD/4AH/AAHwAAAAAAAAAAAAP/+A//4DDBgMMGAwwYDDBgMMGAwwYDABgMAGAAAAAAAAAAAA//4D//gMGAAwYADBgAMGAAwYADBgAMGAAwAAAAAAA/gAH/AA++AHAcA4A4DgBgMAGAwAYDABgMGGAwYYDhjgGH8AAfwAAAAAAAAAAAD//gP/+A//4ADAAAMAAAwAADAAAMAAAwAADAAAMAA//4D//gAAAAAAAAAAAAAAA//4D//gAAAAAAAAAAAAAAAAAYAABgAAGAAA4AAHgP/8A//gAAAAAAAAAAAAAAAP/+A//4ADAAAMAAB4AAPwABzgAOHABwPAOAeAwA4CAAgAAAAAAAAAAAP/+A//4AABgAAGAAAYAABgAAGAAAYAABgAAAA//4D//gP/+AeAAAeAAAeAAA+AAA8AAA4AAHgAB4AAeAAHwAA8AAPAAA//4D//gAAAAAAAAAAAAAAA//4D//gHAAAOAAAeAAA8AAA4AABwAADwAADgAAHAP/+A//4AAAAAAAAAAAAP4AD/4AeDwBwHAOAOAwAYDABgMAGAwAYDABgOAOAcBwB4PAD/4AD+AABAAAAAAAAAAAAAP/+A//4DBgAMGAAwYADBgAMGAA44AB/AAH4AAHAAAAAAA/gAP/gB4PAHAcA4A4DABgMAGAwAYDABgMAGA4A4BwHwHg/gP/nAP4MAEAQAAAAAAAAAAA//4D//gMGAAwYADBgAMHAAw/ADneAH4eAPA4AABgAAAAAAwA8DAH4OA5wYDDBgMMGAw4YDBjgOH8AYPgAAIAAAAAwAADAAAMAAAwAADAAAP/+A//4DAAAMAAAwAADAAAMAAAAAAAAAAAAAA//AD//AAAcAAA4AABgAAGAAAYAABgAAOAABwD//AP/4A/8AAAAAOAAA+AAB+AAB/AAA/AAA/AAAeAAD4AA/AAPwAH8AB+AAPgAA4AAAAAAOAAA/AAB/gAA/wAAf4AAPgAB+AA/gAfwAH4AA8AAD8AAD+AAB/AAB/gAA+AAH4AD/AD/gA/wAD4AAMAAAgAYDgDgPAeAeHgAe8AA/AAA4AAHwAB/wAPHgDwPgOAOAgAYAAAAIAAA4AADwAAHwAAHgAAHgAAP+AA/4APgAB4AAeAADwAAMAAAgAAAAAAMAGAwA4DAPgMB+AwPYDDxgMeGAzwYD8BgPgGA8AYDABgAAAAAAAH//8f//xAABEAAEAAAAAAAHAAAPAAAPgAAPgAAHwAAHwAAHgAADAAAAEAAEQAAR///H//8AAAAAAAAAAAAAAABgAAeAADwAA8AADgAAHgAAPAAAOAAAIAAAAAAAAAAAAQAABAAAEAAAQAABAAAEAAAQAABAAAAAAAAgAADAAAOAAAIAAAAAAAAAAAAAAAHAAY+ADnYAMYgAxiADGYAORAAf+AA/4AAAAAAAB//4H//gAYMADAYAMBgAwGADAYAPHgAf8AA/gAAAAAAAAA/gAH/AA4OADAYAMBgAwGADAYAMDgAQEAAAAAB+AAf8ADx4AMBgAwGADAYAMBgAYMB//4H//gAAAAAAAAB8AAf8ADpwAMhgAyGADIYAMhgA6GAB4wADhAAAAACAAAMAAH/+A//4DMAAMwAAzAAABAcAff4D/5gMbmAwmYDCZgMZmA/mYD8fAMA4AgAAAAAAAAAf/+B//4AGAAAwAADAAAMAAA4AAD/4AH/gAAAAAAACAAAc/+Bz/4CAAAAAAAAABiAAGc//5z//CAAAAAAAAAAAAAAf/+B//4AAYAADgAAfAAHuAA4cADAYAIAgAAAAAAAAAAAf/+B//4AAAAAAAAAAAAAAAA/+AD/4AEAAAwAADAAAMAAA/+AB/4AH/gAwAADAAAMAAA4AAD/4AD/gAAAAAAAAAAAA/+AD/4AGAAAwAADAAAMAAAwAAD/4AH/gAAAAAAAAD+AAf8ADg4AMBgAwGADAYAMBgA4OAB/wAD+AADgAAAAAP/+A//4BgwAMBgAwGADAYAMBgA8eAB/wAD8AAAAAAAAAB+AAf8ADx4AMBgAwGADAYAMBgAYMAD//gP/+AAAAAAAAP/gA/+ABwAAOAAAwAADAAAMAAAAAAAAQAHhgA/GADMYAMxgAxmADH4AEPAAAQAAAAAMAAAwAAf/wD//gAwGADAYAMBgAAAAAAAAP/AA/+AAAYAABgAAGAAAYAADAA/+AD/4AAAAAAAADgAAPgAAfgAAPwAAPgAAeAAHwAD8AA/AADgAAIAAA4AAD8AAD+AAB+AAB4AA/AAfgADwAAPgAAfwAAP4AAHgAB+AA/gAPwAA4AAAAAAAAgAwGADh4AHvAAPwAAOAAB8AAe8ADh4AMBgAgCACAAAOAAA+AAA/BgA/eAA/wAD8AA/AAPgAD4AAOAAAgGADA4AMHgAx+ADOYANxgA+GADgYAMBgAAAAAMAD//4f9/xgADEAAEAAAAAAAAAAAAAAB///n//+AAAAAAAAAAAAAABAABGAAMf9/w//+AAwAAAAAAAAAA4AADgAAYAABgAAHAAAMAAAwAADAAAcAADgAAAAAAAA"); -var scale = 1; // size multiplier for this font -g.setFontCustom(font, 32, widths, 22+(scale<<8)+(1<<16)); -}; - -Graphics.prototype.setFontLato = function() { -// Actual height 50 (53 - 4) -var widths = atob("DhglJSUlJSUlJSUlEA=="); -var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAAAHwAAAAAAAAA/gAAAAAAAAH/AAAAAAAAAf8AAAAAAAAB/wAAAAAAAAD+AAAAAAAAAHwAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAAAB/AAAAAAAAAf8AAAAAAAAP/wAAAAAAAD/8AAAAAAAB//AAAAAAAAf/wAAAAAAAP/4AAAAAAAD/+AAAAAAAA//AAAAAAAAf/wAAAAAAAH/4AAAAAAAD/+AAAAAAAA//AAAAAAAAf/wAAAAAAAH/4AAAAAAAD/+AAAAAAAA//AAAAAAAAf/wAAAAAAAD/4AAAAAAAAP+AAAAAAAAA/AAAAAAAAADwAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//+AAAAAAA////AAAAAAP////gAAAAD/////AAAAA//////AAAAH/////+AAAA//gAH/8AAAH/gAAB/4AAA/4AAAB/wAAD+AAAAB/AAAfwAAAAD+AAB+AAAAAH4AAH4AAAAAfgAAfAAAAAA+AAD8AAAAAD8AAPwAAAAAPwAA/AAAAAA/AAD8AAAAAD8AAPwAAAAAPwAAfAAAAAA+AAB+AAAAAD4AAH4AAAAAfgAAfwAAAAD+AAA/gAAAAfwAAD/gAAAH/AAAH/gAAB/4AAAP/4AB//AAAAf/////4AAAA//////AAAAA/////4AAAAB////+AAAAAA////AAAAAAAf//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAB8AAAAAAAAAPwAAAD4AAAB/AAAAPgAAAP4AAAA+AAAB/AAAAD4AAAP8AAAAPgAAA/gAAAA+AAAH8AAAAD4AAA/gAAAAPgAAH8AAAAA+AAA/gAAAAD4AAH///////gAAf//////+AAB///////4AAH///////gAAf//////+AAB///////4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAAADwAAAA+AAAA/AAAAH4AAAP8AAAA/gAAB/wAAAH+AAAP/AAAA/4AAB/4AAAH/gAAH+AAAA/+AAA/gAAAH/4AAD8AAAA/vgAAfgAAAH8+AAB+AAAA/n4AAHwAAAH8fgAA/AAAA/h+AAD8AAAH8H4AAPwAAA/gfgAA/AAAH8B+AAD8AAA/gH4AAPwAAH8AfgAAfAAB/gB+AAB+AAP8AH4AAH8AB/gAfgAAP4Af8AB+AAA/8f/gAH4AAB///8AAfgAAH///gAB+AAAP//4AAH4AAAP//AAAfgAAAf/wAAB+AAAAP4AAAD4AAAAAAAAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAHgAAAAA8AAA/gAAAAPwAAD/AAAAD/AAAP+AAAAf8AAA/8AAAD/wAAB/4AAAf+AAAB/wAAD/gAAAB/AAAP4AAAAD+AAB/AAAAAH4AAH4AAAAAfgAAfgAAAAA+AAB8AAAAAD8AAPwAB4AAPwAA/AAHgAA/AAD8AAfAAD8AAPwAD8AAPwAA/AAPwAA/AAD8AA/AAD4AAHwAH8AAfgAAfgAf4AB+AAB/AD/gAP4AAD+AffAB/AAAP//9/Af8AAAf//n///gAAB//+P//8AAAD//wf//gAAAD/+A//8AAAAH/gB//gAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAB+AAAAAAAAAf4AAAAAAAAD/gAAAAAAAAf+AAAAAAAAH/4AAAAAAAA//gAAAAAAAH++AAAAAAAB/z4AAAAAAAP+PgAAAAAAB/g+AAAAAAAf8D4AAAAAAD/gPgAAAAAAf4A+AAAAAAH/AD4AAAAAA/wAPgAAAAAH+AA+AAAAAB/wAD4AAAAAP8AAPgAAAAB/gAA+AAAAAf8AAD4AAAAD/AAAPgAAAAf4AAA+AAAAB///////4AAH///////gAAf//////+AAB///////4AAH///////gAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAADwAAAAAAQAAfgAAAAA/gAB+AAAAD/+AAH8AAAP//4AAPwAAH///gAAfgAAf//+AAB+AAB///4AAH4AAH/wPgAAPgAAfgB8AAA/AAB+AHwAAD8AAH4AfAAAPwAAfgB8AAA/AAB+AHwAAD8AAH4AfAAAPwAAfgB+AAA+AAB+AH4AAD4AAH4AfgAAfgAAfgA/AAD+AAB+AD8AAPwAAH4AP4AD/AAAfgAf4Af4AAB+AB////AAAH4AD///4AAAfAAH///AAAB8AAP//4AAAHwAAf//AAAAAAAAf/wAAAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAAAAAAAB//gAAAAAAAf//gAAAAAAH///gAAAAAA////AAAAAAP///8AAAAAB//Af4AAAAAf/wAfwAAAAD/8AA/AAAAAf/wAB+AAAAH/+AAH4AAAA/7wAAPgAAAH/PAAA/AAAB/58AAD8AAAP+HwAAPwAAB/wfAAA/AAAf+B8AAD8AAD/wHwAAPwAAf8AfAAA+AAB/gB8AAD4AAH8AH4AAfgAAfgAfgAB+AAB4AA/AAPwAAHAAD+AB/AAAYAAP+Af4AAAAAAf///AAAAAAA///8AAAAAAB///gAAAAAAD//4AAAAAAAH//AAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAAAAH4AAAAAAAAAfgAAAAAAAAB+AAAAAAAAAH4AAAAAAgAAfgAAAAAOAAB+AAAAAD4AAH4AAAAA/gAAfgAAAAP+AAB+AAAAD/4AAH4AAAA//AAAfgAAAP/4AAB+AAAD/+AAAH4AAA//gAAAfgAAP/4AAAB+AAD/+AAAAH4AA//gAAAAfgAP/4AAAAB+AB/+AAAAAH4Af/gAAAAAfgH/4AAAAAB+B/+AAAAAAH4f/gAAAAAAfn/4AAAAAAB//+AAAAAAAH//gAAAAAAAf/4AAAAAAAB/+AAAAAAAAH/gAAAAAAAAf4AAAAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAwAD/+AAAAA/8Af/8AAAAP/4D//4AAAB//4f//wAAAP//j///gAAB///P8H/AAAP////AH8AAA/gH/wAP4AAH4AH+AAfgAAfgAf4AA+AAB8AA/gAD4AAHwAD8AAPwAA+AAHwAAfAAD4AAfAAB8AAPgAB8AAHwAA+AAHwAAfAAD4AAfAAB8AAHwAD8AAPwAAfAAPwAA+AAB+AB/gAD4AAH4AH+AAfgAAP4B/8AD+AAA/8//4AfwAAB///P8H/AAAD//8///4AAAH//h///AAAAP/4D//4AAAAP/AH//AAAAAHAAP/4AAAAAAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAAP/8AAAAAAAD//4AAAAAAAf//4AAAAAAD///gAAAAAAf///AAAIAAB/gP+AABgAAP4AP4AAeAAA/AAfwAD4AAH4AA/AAfgAAfgAB8AH+AAB8AAHwA/4AAPwAAfAH/gAA/AAB8B/8AAD8AAHwP/AAAPwAAfB/4AAA/AAB8P+AAAD8AAHj/wAAAHwAAef8AAAAfgAD7/gAAAB+AAP/8AAAAD8AB//AAAAAP4AP/4AAAAAf4D/+AAAAAB////wAAAAAD///8AAAAAAH///gAAAAAAH//4AAAAAAAP/+AAAAAAAAH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAfAAAAAD8AAD+AAAAAf4AAP4AAAAB/gAB/wAAAAH+AAH/AAAAAf4AAP4AAAAA/AAA/gAAAAB4AAB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); -var scale = 1; // size multiplier for this font -g.setFontCustom(font, 46, widths, 64+(scale<<8)+(1<<16)); -}; - -Graphics.prototype.setFontArchitect = function() { -// Actual height 40 (41 - 2) -var widths = atob("CBolByEeJykkJCYhCg=="); -var font = atob("AAAAAAAAAAAAAAAAYAAAAAAAADgAAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAD4AAAAAAAA/AAAAAAAAH4AAAAAAAB/AAAAAAAAf4AAAAAAAD+AAAAAAAA/wAAAAAAAH+AAAAAAAB/gAAAAAAAP8AAAAAAAD/AAAAAAAAf4AAAAAAAH+AAAAAAAA/gAAAAAAAP8AAAAAAAB/AAAAAAAAfwAAAAAAAH8AAAAAAAA/AAAAAAAAPwAAAAAAAB8AAAAAAAAfAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAP/8AAAAAAH//4AAAAAB///wAAAAAf/APgAAAAD/gAeAAAAA/wAA8AAAAH8AABwAAAA/AAAHgAAAHwAAAeAAAA+AAAA4AAADgAAADgAAAcAAAAOAAABwAAAA4AAAOAAAADgAAA4AAAAOAAADgAAAA4AAAOAAAADgAAA4AAAAOAAADgAAAB4AAAOAAAAHAAAA4AAAAcAAADwAAADwAAAHAAAAOAAAAeAAAB4AAAA4AAAPAAAADwAAB4AAAAHwAAPgAAAAPgAD8AAAAAf4D/gAAAAAf//4AAAAAAf/+AAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAP////4AAAB/////gAAAH////+AAAAf////gAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAADwAADAAAAAeAAAeAAAAD4AAD4AAAAfAAAfgAAAD4AAD+AAAAPAAAf4AAAB8AAH/AAAAHgAA/8AAAAcAAH/wAAADwAA/vAAAAOAAP48AAAA4AB/DgAAADgAf4OAAAAPAD+A4AAAA8A/wHgAAAD8/8AcAAAAH//gBwAAAAP/wAPAAAAAf8AA8AAAAAAAADgAAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAA+AAAAAAAAD4AAAAAAAAPAAAAAAAAA8AAAAAAAAHwAAAAAAAAfAAAAAAAAA4AAAAAAAABAAAAAAIAAAAAAAADwAAAAAAAAPAAAAAAAAA8AAAAAAAADgAAAAAAAAeAAAAAAAAB4AYAAAAAAHgBwAAAAAAeAPABAAAADwA8AGAAAAPAHgAYAAAA8AeADgAAADwDwAOAAAAOAPAB4AAAB4B8AHgAAAHgPwA8AAAAeA+ADwAAAB4H4AeAAAAHgfgD4AAAAeD+AfAAAAB4e4D8AAAAHj7gfgAAAAf/PH8AAAAB/4//gAAAAH/D/8AAAAAP4H/gAAAAA+Af8AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAADwAAAAAAAAfAAAAAAAAD8AAAAAAAA/wAAAAAAAH/AAAAAAAA/8AAAAAAAPxwAAAAAAB+HAAAAAAAPwcAAAAAAB+BwAAAAAAfwPAAAAAAD+A8AAAAAAfwDwAAAAAD+APAAAAAAPwA8AAAAAB+ADwAAAAAP/////AAAA/////8AAAB/////wAAAD/////AAAAD////8AAAAAAH8AAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAGAAAAAAAAAAAAAAAAAAOAAAAAAAH/8AAAAAAf//wAAAAAD///AAAAAAP//8AAAAAA///wAAAAAAPgPAB4AAAA+A4APgAAAD4DgA+AAAAPAeAB4AAAA8BwAHgAAADwHAAeAAAAPAcAB4AAAB4BgAHgAAAHgGAAeAAAAeAYAD4AAAB4BgAPAAAAPgGAA8AAAA8AYADwAAADwBwAOAAAAPAHAB4AAAA8AcAHgAAAHwB4A8AAAAeAHgHgAAAB4APh+AAAAHgA//wAAAA+AB/+AAAADwAD/wAAAAPAAD8AAAAA8AAAAAAAAHwAAAAAAAAfAAAAAAAAB4AAAAAAAAHgAAAAAAAAeAAAAAAAAB4AAAAAAAAHAAAAAAAAAcAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAAH//AAAAAAB///AAAAAAP//+AAAAAD///8AAAAAf+B/4AAAAD/AA/wAAAA/wAA/gAAAD8AAB+AAAAfAAAD8AAAD4AAAPwAAAfAAAB/AAAB4AAAP+AAAPAAAB/4AAA8AAAP/gAAHgAAB++AAAeAAAPz4AABwAAB+PgAAHAAAPw+AAAcAAA+D4AABgAAHwPgAAAAAA/A+AAAAAAD4H4AAAAAAfAfAAAAAAB4D8AAAAAAPgPgAAAAAA8B+AAAAAADwPwAAAAAAPA+AAAAAAA8P4AAAAAAD//AAAAAAAP/4AAAAAAAf+AAAAAAAA/gAAAAAAAAAAAAAAAIAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAADwAAAAAAAAPAAAAAAAAA8AAAAAAAADwAAAAAAAAPAAAP4AAAA8AAP/gAAADwAH/+AAAAfAB//wAAAB8Af//AAAAHwH/4AAAAAfB/4AAAAAB8f8AAAAAAH//AAAAAAAf/wAAAAAAB/8AAAAAAAP/gAAAAAAA/4AAAAAAAD/AAAAAAAAPwAAAAAAAA+AAAAAAAADwAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAH+AAAAAAAA/8AAAAAAAP/4AAAAAfB//gAAAAH/Pw/AAAAA//8A8AAAAH//gDwAAAA//8AHgAAAD4fwAeAAAAeA+AB4AAAB4DwADgAAAPAPAAOAAAA4A4AA4AAADgDgADgAAAOAOAAOAAABwAwAA4AAAHAHAADgAAAcAcAAOAAABwBwAA4AAAHAPAAHgAAAcA8AAcAAABwDgABwAAAHAeAAHAAAAcB8AA4AAABwPwAHgAAAHg/AAcAAAAeH8ADwAAAB4/4AeAAAAD//gD4AAAAP+fA/AAAAAfx//4AAAAAAD//AAAAAAAP/wAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAA/wAAAAAAAH/gAAAAAAA/+AAAAAAAH/8AAAAAAA/nwAAAAAAD4PAAAAAAAeA8AAAAAADwDwAAAAAAPAPAAAAAAB4A8AAwAAAHgDwAHgAAAeAPAAeAAADwA8AD4AAAPADwAfgAAA8AOAB8AAADwA4APwAAAPADgB+AAAA8AeAPwAAAD4B4B/AAAAHgHgf4AAAAfA+D+AAAAA/D5/wAAAAB///+AAAAAH///gAAAAAH//4AAAAAAP/+AAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAA4AAAAAAAADwDAAAAAAAOAeAAAAAAAYB4AAAAAAAAHgAAAAAAAAMAAAAAAAAAAAAA="); -var scale = 1; // size multiplier for this font -g.setFontCustom(font, 46, widths, 58+(scale<<8)+(1<<16)); -}; - -Graphics.prototype.setFontMonoton = function(scale) { - // Actual height 44 (43 - 0) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAABmwAAAAAAzYAAAAAAZsAAAAAAM2AAAAAAGbAAAAAADNgAAAAABmwAAAAAAzYAAAAAAZsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAD+AAAAAAf8AAAAAD/ggAAAAf8HwAAAD/g/4AAAf8H/AAAD/g/4OAAf8H/B/AD/g/4P+Af8H/B/wAfg/4P+AAMH/B/wAAA/4H+AAAD/A/4AAAB4H/AAAAAA/4AAAAAH/AAAAAAP4AAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH//gAAAAf//8AAAA/AAPgAAA8f/x8AAB4//+PAAB5+APxwABzwfwecAAzj//jnAA7n+P85gA7ngAPO4AbnH/xzsAdnP/+c3AN3PAHndgGzOAA5m4DbuAAO7MD9mAADN2Bs3AAB2bA2bAAAbNgbNgAANmwNmwAAGzYGzYAADZsDdmAADN2B+7AABuzAbMwABmbgNneAD3NgHZ3+/3MwBuc//nO4A7nB8HGYAM58AfOcAHeP/+OcABzx/8ecAAc+AA+cAAHH//8cAAB4//48AAAPg+B8AAAD+AP4AAAAP//wAAAAA/+AAAAAAAAAAAAAAAAAAABsAAAAAAA2AAAAAAAbAAAAAAANgAAAAAAGwAAAAAADf////8ABv////+AA3/////AAbAAAAAAAN/////wAG/////4ADYAAAAAABv////+AA3/////AAb/////gANgAAAAAAG/////4ADf////8AAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAADcAAAA2wBs2AADbYA2bAADtsAbZgADm2AftwAHjbAN24AHNtgGzYAPO2wDZsAPebYBs2AOeNsA2bAec22AbNge87bANmwc55tgG7c8542wD9355zbYA2Z5zztsAbODzjm2ANz/nnjbADc/nnhtgBnCPHA2wA74fPAbYAOf+OANsADj8eAG2AA8A+ADbAAP/8AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAABgG6AAAAuwDdsAAG3YBs2AADZuA27AABu3A/ZgAA7dgbtwAAduwN2w2zG3YGzYbZjZsDZsNsxs2Bs2G2Y2bA2bDbMbNgbNhtmNmwNmw2zGzYG7MbdnbsD939m/d2A2Z/7PM3AbuBtwO7AOz73eeZgDc/9n+dwB3H2Y8cwAZ4HnA84AGf/5/84ADz/OP44AAeALwB4AAH/+//4AAA/+H/wAAADwAfAAAAAAAAAAAAAAAAAAAAAAAZsAAAAAB82AAAAAD+bAAAAAHzNgAAAAPjmwAAAAfHzYAAAB+P5sAAAD8fM2AAAHw+ObAAAPj8fNgAAfH4/mwAA+Ph8zYAAcfH4ZsAAA+Px82AAB8fD+bAAD4+HzNgABh8PhmwAAH4/AzYAAPx+AZsAAPD4AM2AAGHwP+bfgAfgH/NvwA/AABmwAA8AAAzYAAYAA/5t+AAAAf82/AAAAAGbAAAAAADNgAAAAAAAAAAAAAAAAAAAAAAAGAAE///ADAAGf//gBwADP//wCcABmAAADmAAz//8C7gAZ//+DMwAMwAAA3YAGf//hZsADP//xu3ABn//4zdgAzDNsNuwAZhu2GzYAMw2bDZsAGYbNhs2ADMNmw2bABmGzYbNgAzDZsdmwAZhs2M3YAMw3d+zcAGYZm+ZsADMOzgd2ABmDM883AAzB3P87AAZgZx47gAMwOcB5gAGYDn/5gADMA4/zwAAAAPADwAAAAD8fgAAAAAf/gAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///4AAAD////AAAHwAADwAAHH//8eAAHP///ngAHfgAB8wAHeH/8PcADcf//x3ADsf//+ZgBu8AADu4B2c//8zMA3d///M2AbNwAB2bgduxs2bswP2Z2/O3YGzYzbDZsDZsbths2Bs2Nmw2bA2bGzYbNgbNjZsNmwNmxs2GzYH7c2bHbsDtmbtzdmA2bM3fs3AbMHZnO7AM3BuYOZgHZAzP+dgBmAMx+cwA7gHeAcwAMgB3584AHAAc/84ABgAHHx4AAAAB4D4AAAAAf/wAAAAAD/gAAAAAAAAAAAAAAAAAAZsAAAAAAM2AAAAAAGbAAAAAADNgAAAAABmwAAAAAAzYAAAAAAZsAAAAAAM2AAAAPAGbAAAB/gDNgAAP+ABmwAD/wYAzYAf+D8AZsD/wf8AM2f8D/gAGT/gf8HgAf8D/g/wB/g/8H/AA8H/g/4MAA/8H/B+AH/g/4P+AH4H/B/wADA/4P+AAAH/B/wAAA/4P+AAAAfB/wAAAAAP+AAAAAB/wAAAAAD+AAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAGAAwAAAA/8H/gAAB//v/8AAB4B/APgADz+PP54ABn/x//OABng8eDzAB3HHOcdwA3P9z/nYA7P/d/5mAbOBmYO7ANmebvzNwP3fs392YGzc3ZmbsDZsZsxs2Bs2M2Y2bA2bGbMbNgbNjNmNmwNmxmzGzYGzYzZjZsDZsZsxs2Bs2M2Y2bA2bGbMbNgbNzNmNmwP2Zm7s3YDbv7M+7MBszt3OZuA3MGZwd2ANn/uf8zAGY+zn47gDvAc4A7gA78/Pj5gAOf/z/zwADj8cPjwAA+A/gHgAAH/9//gAAA/4P/AAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/4AAAAAHw/AAAAAHADwADAAHP8cABgAHf/nAA4AHeB5gDuAHcfOYAzADc/7uBdwBs8ezBmYBu4DdgbsA2Z924s3AbN+bM3bgfsxt2duwNm4zbG3YGzYZtjZsDZsM2xs2Bs2GbY2bA2bDNsbNgbNhv2NmwP242zO3YHbszbm7sBs3AAHZuA2Z///M2Abuf//O7AGzh/8ObgDc8AA+dgB3P//+dwAdx//8cwAGeAAA8wADn///44AA8///54AAPgAAB4AAB+AAPwAAAP///gAAAA//+AAAAAAAAAAAAAAAAAAAAAAAAAAAADbBmwAAABtgzYAAAA2wZsAAAAbYM2AAAANsGbAAAAG2DNgAAADbBmwAAABtgzYAAAA2wZsAAAAAAAAAAAAAAAAAAA="), 46, atob("DRYpFR0eHiImHygmDQ=="), 49+(scale<<8)+(1<<16)); -} - -Graphics.prototype.setFontSpecialElite = function(scale) { - // Actual height 40 (39 - 0) - g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAYAAAAAAAfwAAAAAAP/AAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAAv8AAAAAAN6AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAfAAAAAAAPwAAAAAAP8AAAAAAH+AAAAAAH+AAAAAAD+AAAAAAD/AAAAAAD/AAAAAAB/AAAAAAB/AAAAAAB/AAAAAAB/gAAAAAB/gAAAAAB/gAAAAAA/gAAAAAB/wAAAAAA/4AAAAAA/wAAAAAA/4AAAAAA/4AAAAAAf8AAAAAAP8AAAAAAD8AAAAAAA8AAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//wAAAAP///gAAAH/9/+AAAD/gAf4AAB/AAB+AAA/AAAHwAAPAAAA+AADgAAAPgAAwAAAD4AAcAAAAfAAHAAAAHwABwAAAB8AA4AAAAfAAOAAAAHwABwAAAB8AAcAAAA/AAHgAAAPgAB+AAAH4AAPgAAD8AAD+AAD+AAA/4Af/AAAB////AAAAP///wAAAAP//gAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAOAAAAHAADgAAAD4AB4AAAA+AAeAAAAPgAHgAAAD4AB4AAAAeAAeAAAAHgAHgAAAB4AB4AAAAcAAeAAAAPAAH4AAP/wAB/////+AAf/////gAH/////4AB///+/+AAAAQAAPgAAAAAAB4AAAAAAAeAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAADwAAAAAAA+AAAAAAAPgAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAcAAAB+AAfwAAA/wAf/AAA/8Af/wAAf/AP/8AAGPwP//AADh8D48AAA4OB8OAAAOAAfDgAAHAAPg4AABwADwPAAAcAB8DwAAHAAeAeAABwAHAHgAAcADwB8AAHAB4APgAB4A+AB4AAPAfAAeAAD4fgADgAAf/4AA4AAD/8AAeAAAf+AAfAAAB8AAPwAAAAAAD4AAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAf8AAB/wAH/wAAf8AB/8AAH+AAffgAB4AcDx4AAcAPAAfAAHAPwAHwABwHwAA8AAcB+AAPAAHA/gADwABw/4AA8AAcf+AAPAAHP/gAHwABz74AB8AAf8fAA/AAH8DwAPgAD/A8AHwAA/gHwP8AAPwA//+AADwAH//AAAAAA//gAAAAAD/wAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAP8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/PAAAAAA/jwAAAAAfg8AAAAAPwPAAAAAH4DwAAAAD4A8HAAAD8APBwAAB+ADw8AAA+AA8PAAA/AAPDgAAPgADw8AAHwAB8/AAD+B///wAA/////8AAP/////AAB+f///wAAAAAHx8AAAAAB8PAAAAAAPDwAAAAADw8AAAAAA4PAAAAAAODwAAAAADgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAB/gAAH//wf8AAB//+H/gAAf//h/4AAHJ/wf/AABwD4D/4AAeA+AAeAAHgPAAHgAB4DwAB4AAeA4AAeAAHgOAAHgAA4DgAB4AAOA8AAeAADgPAAHgAB4D4ADwAAeAeAB4AAHAHwAeAABgAfAPgAAYAD8fgAAAAA//wAAAAAH/4AAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAA//wAAAAB///AAAAB////AAAB/7//wAAA/AfB+AAAfAPAPwAAPgHgB+AAHwBwAPgAB4A8AB8AAeAPAAfAAPADwAHwADgA8AB8AA8APAAfAAPADwAHwADwA8AB8AA8AHgA+AAP8B4APgAD/wfAH4AA/8D4D8AAH/A///AAB/wH//gAAH8A//wAAA8AH/4AAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAB/4AAAAAA/8AAAAAAP+AAAAAAD+AAAAAAAeAAAAAAAHAAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHwAAA/8AD+AAB//AA/gAB//wAP4AB//gAB+AB//AAAfwB//AAAH8A/wAAAA/A/wAAAAHw/wAAAAB8/wAAAAAffwAAAAAP/wAAAAAD/4AAAAAA/4AAAAAAP8AAAAAAD4AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAP/wAAAH8H/+AAAH/z//wAAD////+AAB///h/gAA/B/gH4AAPAP4A/AAHwB8AHwAB4APAB8AAeADgAfAAHgA4ADwABwAOAA8AAcADgAPAAHAA4ADwAB4AeAA8AAfAHwAPAADwB8AHgAA+A/gD4AAPgP4B+AAB+P/h/gAAP////wAAB/8f/4AAAP8D/8AAAAAAf8AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAA/4APAAAA//gH4AAAP/8D/gAAP4Pg/8AADwB8P/AAB4AfD/4AAeAD4d+AAHAA+AfwADwAHgH8AA4AA8A/AAOAAPAPgADgADwD4AA8AA4B+AAPAAeAfAAB4AHgHgAAeADwD4AAHwA8A8AAA+AfA/AAAHp/h/gAAA3//+gAAAB//+gAAAAd//gAAAACf/wAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAABwB/AAAAB/A/8AAAA/wf/gAAAP+H/4AAAH/h/+AAAB/8f/gAAAP+H/4AAAD/h/+AAAAfwf/gAAAH8C/wAAAAAA3oAAAAAABwAAAAAAAAAAAAAAAAAAAA=="), 46, atob("ERwfHB0cHxsdHB4dEQ=="), 50+(scale<<8)+(1<<16)); -} - +require("f_latosmall").add(Graphics); const SETTINGS_FILE = "pastel.json"; let settings = undefined; function loadSettings() { - console.log("loadSettings()"); + //console.log("loadSettings()"); settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; settings.grid = settings.grid||false; settings.date = settings.date||false; settings.font = settings.font||"Lato"; - console.log(settings); + //console.log(settings); } function loadFonts() { - console.log("loadFonts()"); - console.log(settings); + //console.log("loadFonts()"); + //console.log(settings); + // load font files based on settings.font if (settings.font == "Architect") require("f_artitect").add(Graphics); @@ -78,8 +29,7 @@ function loadFonts() { else if (settings.font == "Elite") require("f_elite").add(Graphics); else - //require("f_lato").add(Graphics); - require("f_orbitron").add(Graphics); + require("f_lato").add(Graphics); } var mm_prev = "xx"; From cf66db35a95aabf22dd3be5d77b9264148db1956 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 22 Nov 2021 22:08:47 +0000 Subject: [PATCH 0820/1062] Pastel: font modules --- apps/pastel/f_architect.js | 10 ++++++++++ apps/pastel/f_cabin.js | 9 +++++++++ apps/pastel/f_elite.js | 7 +++++++ apps/pastel/f_gochihand.js | 10 ++++++++++ apps/pastel/f_lato.js | 10 ++++++++++ apps/pastel/f_latosmall.js | 10 ++++++++++ apps/pastel/f_monoton.js | 7 +++++++ apps/pastel/f_t.js | 7 +++++++ 8 files changed, 70 insertions(+) create mode 100644 apps/pastel/f_architect.js create mode 100644 apps/pastel/f_cabin.js create mode 100644 apps/pastel/f_elite.js create mode 100644 apps/pastel/f_gochihand.js create mode 100644 apps/pastel/f_lato.js create mode 100644 apps/pastel/f_latosmall.js create mode 100644 apps/pastel/f_monoton.js create mode 100644 apps/pastel/f_t.js diff --git a/apps/pastel/f_architect.js b/apps/pastel/f_architect.js new file mode 100644 index 000000000..ce44bfcec --- /dev/null +++ b/apps/pastel/f_architect.js @@ -0,0 +1,10 @@ + +var widths = atob("CBolByEeJykkJCYhCg=="); +var font = atob("AAAAAAAAAAAAAAAAYAAAAAAAADgAAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAD4AAAAAAAA/AAAAAAAAH4AAAAAAAB/AAAAAAAAf4AAAAAAAD+AAAAAAAA/wAAAAAAAH+AAAAAAAB/gAAAAAAAP8AAAAAAAD/AAAAAAAAf4AAAAAAAH+AAAAAAAA/gAAAAAAAP8AAAAAAAB/AAAAAAAAfwAAAAAAAH8AAAAAAAA/AAAAAAAAPwAAAAAAAB8AAAAAAAAfAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAP/8AAAAAAH//4AAAAAB///wAAAAAf/APgAAAAD/gAeAAAAA/wAA8AAAAH8AABwAAAA/AAAHgAAAHwAAAeAAAA+AAAA4AAADgAAADgAAAcAAAAOAAABwAAAA4AAAOAAAADgAAA4AAAAOAAADgAAAA4AAAOAAAADgAAA4AAAAOAAADgAAAB4AAAOAAAAHAAAA4AAAAcAAADwAAADwAAAHAAAAOAAAAeAAAB4AAAA4AAAPAAAADwAAB4AAAAHwAAPgAAAAPgAD8AAAAAf4D/gAAAAAf//4AAAAAAf/+AAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAP////4AAAB/////gAAAH////+AAAAf////gAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAADwAADAAAAAeAAAeAAAAD4AAD4AAAAfAAAfgAAAD4AAD+AAAAPAAAf4AAAB8AAH/AAAAHgAA/8AAAAcAAH/wAAADwAA/vAAAAOAAP48AAAA4AB/DgAAADgAf4OAAAAPAD+A4AAAA8A/wHgAAAD8/8AcAAAAH//gBwAAAAP/wAPAAAAAf8AA8AAAAAAAADgAAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAA+AAAAAAAAD4AAAAAAAAPAAAAAAAAA8AAAAAAAAHwAAAAAAAAfAAAAAAAAA4AAAAAAAABAAAAAAIAAAAAAAADwAAAAAAAAPAAAAAAAAA8AAAAAAAADgAAAAAAAAeAAAAAAAAB4AYAAAAAAHgBwAAAAAAeAPABAAAADwA8AGAAAAPAHgAYAAAA8AeADgAAADwDwAOAAAAOAPAB4AAAB4B8AHgAAAHgPwA8AAAAeA+ADwAAAB4H4AeAAAAHgfgD4AAAAeD+AfAAAAB4e4D8AAAAHj7gfgAAAAf/PH8AAAAB/4//gAAAAH/D/8AAAAAP4H/gAAAAA+Af8AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAADwAAAAAAAAfAAAAAAAAD8AAAAAAAA/wAAAAAAAH/AAAAAAAA/8AAAAAAAPxwAAAAAAB+HAAAAAAAPwcAAAAAAB+BwAAAAAAfwPAAAAAAD+A8AAAAAAfwDwAAAAAD+APAAAAAAPwA8AAAAAB+ADwAAAAAP/////AAAA/////8AAAB/////wAAAD/////AAAAD////8AAAAAAH8AAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAGAAAAAAAAAAAAAAAAAAOAAAAAAAH/8AAAAAAf//wAAAAAD///AAAAAAP//8AAAAAA///wAAAAAAPgPAB4AAAA+A4APgAAAD4DgA+AAAAPAeAB4AAAA8BwAHgAAADwHAAeAAAAPAcAB4AAAB4BgAHgAAAHgGAAeAAAAeAYAD4AAAB4BgAPAAAAPgGAA8AAAA8AYADwAAADwBwAOAAAAPAHAB4AAAA8AcAHgAAAHwB4A8AAAAeAHgHgAAAB4APh+AAAAHgA//wAAAA+AB/+AAAADwAD/wAAAAPAAD8AAAAA8AAAAAAAAHwAAAAAAAAfAAAAAAAAB4AAAAAAAAHgAAAAAAAAeAAAAAAAAB4AAAAAAAAHAAAAAAAAAcAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAAH//AAAAAAB///AAAAAAP//+AAAAAD///8AAAAAf+B/4AAAAD/AA/wAAAA/wAA/gAAAD8AAB+AAAAfAAAD8AAAD4AAAPwAAAfAAAB/AAAB4AAAP+AAAPAAAB/4AAA8AAAP/gAAHgAAB++AAAeAAAPz4AABwAAB+PgAAHAAAPw+AAAcAAA+D4AABgAAHwPgAAAAAA/A+AAAAAAD4H4AAAAAAfAfAAAAAAB4D8AAAAAAPgPgAAAAAA8B+AAAAAADwPwAAAAAAPA+AAAAAAA8P4AAAAAAD//AAAAAAAP/4AAAAAAAf+AAAAAAAA/gAAAAAAAAAAAAAAAIAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAADwAAAAAAAAPAAAAAAAAA8AAAAAAAADwAAAAAAAAPAAAP4AAAA8AAP/gAAADwAH/+AAAAfAB//wAAAB8Af//AAAAHwH/4AAAAAfB/4AAAAAB8f8AAAAAAH//AAAAAAAf/wAAAAAAB/8AAAAAAAP/gAAAAAAA/4AAAAAAAD/AAAAAAAAPwAAAAAAAA+AAAAAAAADwAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAH+AAAAAAAA/8AAAAAAAP/4AAAAAfB//gAAAAH/Pw/AAAAA//8A8AAAAH//gDwAAAA//8AHgAAAD4fwAeAAAAeA+AB4AAAB4DwADgAAAPAPAAOAAAA4A4AA4AAADgDgADgAAAOAOAAOAAABwAwAA4AAAHAHAADgAAAcAcAAOAAABwBwAA4AAAHAPAAHgAAAcA8AAcAAABwDgABwAAAHAeAAHAAAAcB8AA4AAABwPwAHgAAAHg/AAcAAAAeH8ADwAAAB4/4AeAAAAD//gD4AAAAP+fA/AAAAAfx//4AAAAAAD//AAAAAAAP/wAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAA/wAAAAAAAH/gAAAAAAA/+AAAAAAAH/8AAAAAAA/nwAAAAAAD4PAAAAAAAeA8AAAAAADwDwAAAAAAPAPAAAAAAB4A8AAwAAAHgDwAHgAAAeAPAAeAAADwA8AD4AAAPADwAfgAAA8AOAB8AAADwA4APwAAAPADgB+AAAA8AeAPwAAAD4B4B/AAAAHgHgf4AAAAfA+D+AAAAA/D5/wAAAAB///+AAAAAH///gAAAAAH//4AAAAAAP/+AAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAA4AAAAAAAADwDAAAAAAAOAeAAAAAAAYB4AAAAAAAAHgAAAAAAAAMAAAAAAAAAAAAA="); + +exports.add = function(graphics) { + graphics.prototype.setFontArchitect = function() { + // Actual height 40 (41 - 2) + this.setFontCustom(font, 32, widths, 22+(1<<8)+(1<<16)); + } +}; diff --git a/apps/pastel/f_cabin.js b/apps/pastel/f_cabin.js new file mode 100644 index 000000000..916677565 --- /dev/null +++ b/apps/pastel/f_cabin.js @@ -0,0 +1,9 @@ +var widths = atob("ECMtGCEiJSIkHyYlDw=="); +var font = atob("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"); + +exports.add = function(graphics) { + graphics.prototype.setFontCabinSketch = function() { + // Actual height 48 (51 - 4) + this.setFontCustom(font, 46, widths, 65+(1<<8)+(1<<16)); + } +}; diff --git a/apps/pastel/f_elite.js b/apps/pastel/f_elite.js new file mode 100644 index 000000000..a5cac2838 --- /dev/null +++ b/apps/pastel/f_elite.js @@ -0,0 +1,7 @@ + +exports.add = function(graphics) { + graphics.prototype.setFontSpecialElite = function(scale) { + // Actual height 40 (39 - 0) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAYAAAAAAAfwAAAAAAP/AAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAAv8AAAAAAN6AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAfAAAAAAAPwAAAAAAP8AAAAAAH+AAAAAAH+AAAAAAD+AAAAAAD/AAAAAAD/AAAAAAB/AAAAAAB/AAAAAAB/AAAAAAB/gAAAAAB/gAAAAAB/gAAAAAA/gAAAAAB/wAAAAAA/4AAAAAA/wAAAAAA/4AAAAAA/4AAAAAAf8AAAAAAP8AAAAAAD8AAAAAAA8AAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//wAAAAP///gAAAH/9/+AAAD/gAf4AAB/AAB+AAA/AAAHwAAPAAAA+AADgAAAPgAAwAAAD4AAcAAAAfAAHAAAAHwABwAAAB8AA4AAAAfAAOAAAAHwABwAAAB8AAcAAAA/AAHgAAAPgAB+AAAH4AAPgAAD8AAD+AAD+AAA/4Af/AAAB////AAAAP///wAAAAP//gAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAOAAAAHAADgAAAD4AB4AAAA+AAeAAAAPgAHgAAAD4AB4AAAAeAAeAAAAHgAHgAAAB4AB4AAAAcAAeAAAAPAAH4AAP/wAB/////+AAf/////gAH/////4AB///+/+AAAAQAAPgAAAAAAB4AAAAAAAeAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAADwAAAAAAA+AAAAAAAPgAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAcAAAB+AAfwAAA/wAf/AAA/8Af/wAAf/AP/8AAGPwP//AADh8D48AAA4OB8OAAAOAAfDgAAHAAPg4AABwADwPAAAcAB8DwAAHAAeAeAABwAHAHgAAcADwB8AAHAB4APgAB4A+AB4AAPAfAAeAAD4fgADgAAf/4AA4AAD/8AAeAAAf+AAfAAAB8AAPwAAAAAAD4AAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAf8AAB/wAH/wAAf8AB/8AAH+AAffgAB4AcDx4AAcAPAAfAAHAPwAHwABwHwAA8AAcB+AAPAAHA/gADwABw/4AA8AAcf+AAPAAHP/gAHwABz74AB8AAf8fAA/AAH8DwAPgAD/A8AHwAA/gHwP8AAPwA//+AADwAH//AAAAAA//gAAAAAD/wAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAP8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/PAAAAAA/jwAAAAAfg8AAAAAPwPAAAAAH4DwAAAAD4A8HAAAD8APBwAAB+ADw8AAA+AA8PAAA/AAPDgAAPgADw8AAHwAB8/AAD+B///wAA/////8AAP/////AAB+f///wAAAAAHx8AAAAAB8PAAAAAAPDwAAAAADw8AAAAAA4PAAAAAAODwAAAAADgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAB/gAAH//wf8AAB//+H/gAAf//h/4AAHJ/wf/AABwD4D/4AAeA+AAeAAHgPAAHgAB4DwAB4AAeA4AAeAAHgOAAHgAA4DgAB4AAOA8AAeAADgPAAHgAB4D4ADwAAeAeAB4AAHAHwAeAABgAfAPgAAYAD8fgAAAAA//wAAAAAH/4AAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAA//wAAAAB///AAAAB////AAAB/7//wAAA/AfB+AAAfAPAPwAAPgHgB+AAHwBwAPgAB4A8AB8AAeAPAAfAAPADwAHwADgA8AB8AA8APAAfAAPADwAHwADwA8AB8AA8AHgA+AAP8B4APgAD/wfAH4AA/8D4D8AAH/A///AAB/wH//gAAH8A//wAAA8AH/4AAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAB/4AAAAAA/8AAAAAAP+AAAAAAD+AAAAAAAeAAAAAAAHAAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHwAAA/8AD+AAB//AA/gAB//wAP4AB//gAB+AB//AAAfwB//AAAH8A/wAAAA/A/wAAAAHw/wAAAAB8/wAAAAAffwAAAAAP/wAAAAAD/4AAAAAA/4AAAAAAP8AAAAAAD4AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAP/wAAAH8H/+AAAH/z//wAAD////+AAB///h/gAA/B/gH4AAPAP4A/AAHwB8AHwAB4APAB8AAeADgAfAAHgA4ADwABwAOAA8AAcADgAPAAHAA4ADwAB4AeAA8AAfAHwAPAADwB8AHgAA+A/gD4AAPgP4B+AAB+P/h/gAAP////wAAB/8f/4AAAP8D/8AAAAAAf8AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAA/4APAAAA//gH4AAAP/8D/gAAP4Pg/8AADwB8P/AAB4AfD/4AAeAD4d+AAHAA+AfwADwAHgH8AA4AA8A/AAOAAPAPgADgADwD4AA8AA4B+AAPAAeAfAAB4AHgHgAAeADwD4AAHwA8A8AAA+AfA/AAAHp/h/gAAA3//+gAAAB//+gAAAAd//gAAAACf/wAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAABwB/AAAAB/A/8AAAA/wf/gAAAP+H/4AAAH/h/+AAAB/8f/gAAAP+H/4AAAD/h/+AAAAfwf/gAAAH8C/wAAAAAA3oAAAAAABwAAAAAAAAAAAAAAAAAAAA=="), 46, atob("ERwfHB0cHxsdHB4dEQ=="), 50+(scale<<8)+(1<<16)); + } +}; diff --git a/apps/pastel/f_gochihand.js b/apps/pastel/f_gochihand.js new file mode 100644 index 000000000..8ef926f39 --- /dev/null +++ b/apps/pastel/f_gochihand.js @@ -0,0 +1,10 @@ + +var widths = atob("GRMtICcqJiopKiwoGQ=="); +var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAA+AAAAAAAAAAAAfwAAAAAAAAAAAH+AAAAAAAAAAAB/gAAAAAAAAAAAf4AAAAAAAAAAAH+AAAAAAAAAAAB/gAAAAAAAAAAAP4AAAAAAAAAAAD8AAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAP+AAAAAAAAAAA//gAAAAAAAAAH//4AAAAAAAAA///+AAAAAAAAP////gAAAAAAB/////4AAAAAAP/////+AAAAAD//////+AAAAA///////wAAAAAf//////AAAAAAP/////4AAAAAAD/////AAAAAAAA////4AAAAAAAAP//+AAAAAAAAAD//gAAAAAAAAAA/8AAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAAAAAAAAAAH//AAAAAAAAAAH//8AAAAAAAAAH///wAAAAAAAAD///+AAAAAAAAB////wAAAAAAAA////+AAAAAAAAf////gAAAAAAAP/8//8AAAAAAAD/wAf/AAAAAAAB/wAD/4AAAAAAA/4AAP+AAAAAAAP8AAB/wAAAAAAD/AAAf8AAAAAAB/gAAD/AAAAAAAf4AAA/wAAAAAAH8AAAP8AAAAAAB/AAAD/AAAAAAAfwAAA/wAAAAAAP8AAAH8AAAAAAD/AAAD/AAAAAAAf4AAA/wAAAAAAH+AAAP8AAAAAAB/gAAD/AAAAAAAf4AAA/wAAAAAAH/AAAP4AAAAAAB/wAAH+AAAAAAAP+AAB/gAAAAAAD/wAA/wAAAAAAA/+AAf8AAAAAAAH/wAH+AAAAAAAB/+AH/gAAAAAAAP/4D/wAAAAAAAB////4AAAAAAAAf///+AAAAAAAAD////AAAAAAAAAf///gAAAAAAAAD///wAAAAAAAAAP//wAAAAAAAAAA//4AAAAAAAAAAD/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAAAAAAA/gAAAAAAAAAAAf4AAAAAAAAAAAP+AAAAAAAAAAAH/gAAAAAAAAAAB/wAAAAAAAAAAA/4AAAAAAAAAAAf8AAAAAAAAAAAH/AAAAAAAAAAAD/gAAAAAAAAAAA/wAAAAAAAAAAAf4AAAAAAAAAAAP+AAAAAAAAAAAD/AAAAAAAAAAAB/wAAAAAAAAAAAf+AAAAAAAAAAAH/4AAAAAAAAAAD//8AAAAAAAAAA////4AAAAAAAAP/////gAAAAAAB/////8AAAAAAAf/////AAAAAAAB/////wAAAAAAAP////8AAAAAAAAP////AAAAAAAAAH///wAAAAAAAAAAf/4AAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAHwAAAAAAD8AAAD+AAAAAAB/AAAB/gAAAAAA/4AAA/8AAAAAAf+AAAf/AAAAAAH/gAAH/wAAAAAD/wAAD/8AAAAAB/4AAB//AAAAAAf8AAA//wAAAAAH+AAAf/8AAAAAD/AAAP//AAAAAA/wAAD//wAAAAAP4AAB//8AAAAAD+AAA///AAAAAA/gAAf8/wAAAAAP4AAP+P8AAAAAD/AAP/j/AAAAAA/wAH/w/wAAAAAP+AH/4P8AAAAAD/wD/8D/AAAAAA//P/+A/wAAAAAH////AP+AAAAAB////AB/gAAAAAP///gAf4AAAAAD///wAH+AAAAAAf//wAB/gAAAAAD//4AAf4AAAAAAP/4AAH+AAAAAAA/wAAB/gAAAAAAAAAAAf4AAAAAAAAAAAH+AAAAAAAAAAAA/gAAAAAAAAAAAPwAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAHgAAAAAAAAAAAD8AAAAAAAAAAAB/gAAAAAAAAAAAf8AAAAAAAAAAAP+AAAAAAAAAAAD/gAAAAAAAAAAB/wAAAcAAAAAAAf8AAAPwAAAAAAH+AAAD/AAAAAAB/gAAA/4AAAAAA/wAAAP/AAAAAAP8AAAD/4AAAAAD/AB+A//AAAAAA/wA/wH/wAAAAAP4AP8A/+AAAAAD+AD/AD/gAAAAA/gB/wAf8AAAAAP8Af8AH/AAAAAD/AH/AA/wAAAAA/wB/gAP8AAAAAP+Af4AD/AAAAAD/gP+AA/wAAAAAf+H/gAP8AAAAAH///4AD/AAAAAB////AA/wAAAAAP///wAP8AAAAAB///+AD/AAAAAAf///gA/gAAAAAD///+Af4AAAAAAP/n/4f+AAAAAAB/g////AAAAAAAAAP///wAAAAAAAAB///4AAAAAAAAAP//8AAAAAAAAAB///AAAAAAAAAAP//AAAAAAAAAAA//gAAAAAAAAAAD/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAAAAAAAH+AAAAAAAAAAAH/wAAAAAAAAAAD/+AAAAAAAAAAD//gAAAAAAAAAB//8AAAAAAAAAB///AAAAAAAAAA///wAAAAAAAAAf//8AAAAAAAAAf/7/AAAAAAAAAP/4/4AAAAAAAAP/4H+AAAAAAAAH/8B/gAAAAAAB//8Af4AAAAAAA//+AH+AAAAAAAP//AB/gAAAAAAH//wAf4AAAAAAB///AH+AAAAAAAf//+B/gAAAAAAH///+f4AAAAAAA/////+AAAAAAAH/////wAAAAAAA/////8AAAAAAAAf////8AAAAAAAAf////+AAAAAAAA/////4AAAAAAAA/////AAAAAAAAB////wAAAAAAAAB///8AAAAAAAAAD///AAAAAAAAAA///wAAAAAAAAAP//4AAAAAAAAAD/D8AAAAAAAAAA/wAAAAAAAAAAAH4AAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf8AAAAAAAAf4AP/wAAAAAAAf/AH/+AAAAAAAP/4D//wAAAAAAD//A//+AAAAAAB//wP//wAAAAAAf/8D//8AAAAAAH//g///gAAAAAB//4H//4AAAAAAf//AAf/AAAAAAP9/wAD/wAAAAAD/P+AAf8AAAAAA/j/gAD/AAAAAAP4f4AA/4AAAAAD+H/AAH+AAAAAA/h/wAB/gAAAAAP4P+AAf4AAAAAD+D/gAH+AAAAAA/g/4AA/gAAAAAP4H/AAP4AAAAAD+B/wAD+AAAAAB/gP+AA/gAAAAAf4D/gAP4AAAAAH+Af8AH+AAAAAB/gH/gB/gAAAAAf4B/4Af4AAAAAH+AP/AH8AAAAAB/gB/4D/AAAAAAf4Af/h/wAAAAAD+AD///4AAAAAA/gA///+AAAAAAP4AH///AAAAAAD+AA///gAAAAAA/gAH//wAAAAAAH4AA//4AAAAAAAMAAD/8AAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/8AAAAAAAAAAH//wAAAAAAAAAH///AAAAAAAAAD///4AAAAAAAAD////gAAAAAAAB////8AAAAAAAA/////gAAAAAAAf////4AAAAAAAH/4B//AAAAAAAD/wAP/4AAAAAAA/4AD/+AAAAAAAf8AB//wAAAAAAH+AAf/8AAAAAAD/AAP//gAAAAAA/wAD//4AAAAAAP8AA/n+AAAAAAD/AAf5/gAAAAAA/wAH8P8AAAAAAP8AB/D/AAAAAAD/AA/w/wAAAAAA/4AP8P8AAAAAAP+AD+D/AAAAAAD/wA/g/wAAAAAAf8AP4P8AAAAAAH+AD+D/AAAAAAA/gA/g/wAAAAAAHwAP8P8AAAAAAAwAD/D/AAAAAAAAAA/x/wAAAAAAAAAP//4AAAAAAAAAD//+AAAAAAAAAAf//AAAAAAAAAAH//wAAAAAAAAAA//4AAAAAAAAAAH/8AAAAAAAAAAB/+AAAAAAAAAAAH/AAAAAAAAAAAAeAAAAAAAD+AAAAAAAAAAAA/gAAAAAAAAAAAP4AAAAAAAAAAAD+AAAAAAAAAAAA/gAeAAAAAAAAAP4AfwAAAAAAAAD+AH8AAAAAAAAA/gB/AAAAAAAAAP4AfwAAAAAAAAD+AH8AAAAAAAAA/gB/AAAAAAAAAP4AfwAAAAAAAAD+AH8AAAAAAAAA/wD/AAAAAAAAAP8A/wAAAAAAAAD/AP8AAAAAAAAA/wD/AAAAAAAAAP8A/wAAAAAAAAD/gP8AAAAAAAAA/4D/AAAAAAAAAH/A/wAAAAAAAAB/4P+AHwAAAAAAf//////AAAAAAD//////wAAAAAA//////8AAAAAAH//////AAAAAAB//////wAAAAAAP/////8AAAAAAB/////+AAAAAAAH/////AAAAAAAAAP+AAAAAAAAAAAB/gAAAAAAAAAAAf4AAAAAAAAAAAH+AAAAAAAAAAAB/gAAAAAAAAAAAf4AAAAAAAAAAAH+AAAAAAAAAAAB/gAAAAAAAAAAAP4AAAAAAAAAAAD+AAAAAAAAAAAA/gAAAAAAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/wAAAAAAAAAAD//AAAAAAAAD+D//8AAAAAAAD/9///gAAAAAAB/////8AAAAAAA//////AAAAAAAf/////4AAAAAAH//////AAAAAAD///4H/wAAAAAA///4Af+AAAAAAP4f+AD/gAAAAAH+D/gAf4AAAAAB/A/4AH/AAAAAAfwP+AA/wAAAAAH8D/wAP8AAAAAB/A/8AD/AAAAAAfwP/AAfwAAAAAH+D/wAH8AAAAAB/g/8AB/AAAAAAf4P/AAfwAAAAAH+D/wAH8AAAAAB/w/8AB/AAAAAAP+P+AA/wAAAAAD/x/gAP8AAAAAA///8AD+AAAAAAH///gB/gAAAAAB///4Af4AAAAAAP///gP8AAAAAAB///+H/AAAAAAAP/////gAAAAAAB/////4AAAAAAAH////8AAAAAAAAAH//+AAAAAAAAAA///AAAAAAAAAAD//gAAAAAAAAAAP/wAAAAAAAAAAA/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/+AAAAAAAAAAB//wAAAAAAAAAA//+AAAAAAAAAAf//wAAAAAAAAAH//8AAAAAAAAAD///gAAAAAAAAA///4AAAAAAAAAf4H/AAAAAAAAAH+B/wAAAAAAAAB/AP8AAAAAAAAA/wD/AAAAAAAAAP4A/wAAAAAAAAD+AP8AAAAAAAAB/gD/AAAAAAAAAf4A/wAAAAAAAAH+AP8AAAAAAAAB/AD/AAAAAAAAAfwB/gAAAAAAAAH8Af4AAAAAAAAB/AP8AAAAAAAAAfwD/AAAAAAAAAH8B/wAAAAAAAAB/Af4AAAAAAAAAf4P8AAAAAAAAAH+H/AAAAAAAAAB/j/gAAAAAAAAAf//////wAAAAAH///////gAAAAA///////8AAAAAP///////AAAAAD///////wAAAAAf//////4AAAAAH//////+AAAAAB///////gAAAAAP//////wAAAAAB/gAAAAAAAAAAAfgAAAAAAAAAAADwAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAACAAAAAAAAAPgAD4AAAAAAAAH8AB/AAAAAAAAB/gAf4AAAAAAAAf4AH+AAAAAAAAH+AB/gAAAAAAAB/gAf4AAAAAAAAf4AH+AAAAAAAAD+AA/gAAAAAAAA/AAPwAAAAAAAADgAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + +exports.add = function(graphics) { + graphics.prototype.setFontGochiHand = function() { + // Actual height 54 (59 - 6) + this.setFontCustom(font, 46, widths, 80+(1<<8)+(1<<16)); + } +}; diff --git a/apps/pastel/f_lato.js b/apps/pastel/f_lato.js new file mode 100644 index 000000000..a7c13fd30 --- /dev/null +++ b/apps/pastel/f_lato.js @@ -0,0 +1,10 @@ + +var widths = atob("DhglJSUlJSUlJSUlEA=="); +var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAAAHwAAAAAAAAA/gAAAAAAAAH/AAAAAAAAAf8AAAAAAAAB/wAAAAAAAAD+AAAAAAAAAHwAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAAAB/AAAAAAAAAf8AAAAAAAAP/wAAAAAAAD/8AAAAAAAB//AAAAAAAAf/wAAAAAAAP/4AAAAAAAD/+AAAAAAAA//AAAAAAAAf/wAAAAAAAH/4AAAAAAAD/+AAAAAAAA//AAAAAAAAf/wAAAAAAAH/4AAAAAAAD/+AAAAAAAA//AAAAAAAAf/wAAAAAAAD/4AAAAAAAAP+AAAAAAAAA/AAAAAAAAADwAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB//+AAAAAAA////AAAAAAP////gAAAAD/////AAAAA//////AAAAH/////+AAAA//gAH/8AAAH/gAAB/4AAA/4AAAB/wAAD+AAAAB/AAAfwAAAAD+AAB+AAAAAH4AAH4AAAAAfgAAfAAAAAA+AAD8AAAAAD8AAPwAAAAAPwAA/AAAAAA/AAD8AAAAAD8AAPwAAAAAPwAAfAAAAAA+AAB+AAAAAD4AAH4AAAAAfgAAfwAAAAD+AAA/gAAAAfwAAD/gAAAH/AAAH/gAAB/4AAAP/4AB//AAAAf/////4AAAA//////AAAAA/////4AAAAB////+AAAAAA////AAAAAAAf//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAB8AAAAAAAAAPwAAAD4AAAB/AAAAPgAAAP4AAAA+AAAB/AAAAD4AAAP8AAAAPgAAA/gAAAA+AAAH8AAAAD4AAA/gAAAAPgAAH8AAAAA+AAA/gAAAAD4AAH///////gAAf//////+AAB///////4AAH///////gAAf//////+AAB///////4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHgAAADwAAAA+AAAA/AAAAH4AAAP8AAAA/gAAB/wAAAH+AAAP/AAAA/4AAB/4AAAH/gAAH+AAAA/+AAA/gAAAH/4AAD8AAAA/vgAAfgAAAH8+AAB+AAAA/n4AAHwAAAH8fgAA/AAAA/h+AAD8AAAH8H4AAPwAAA/gfgAA/AAAH8B+AAD8AAA/gH4AAPwAAH8AfgAAfAAB/gB+AAB+AAP8AH4AAH8AB/gAfgAAP4Af8AB+AAA/8f/gAH4AAB///8AAfgAAH///gAB+AAAP//4AAH4AAAP//AAAfgAAAf/wAAB+AAAAP4AAAD4AAAAAAAAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAHgAAAAA8AAA/gAAAAPwAAD/AAAAD/AAAP+AAAAf8AAA/8AAAD/wAAB/4AAAf+AAAB/wAAD/gAAAB/AAAP4AAAAD+AAB/AAAAAH4AAH4AAAAAfgAAfgAAAAA+AAB8AAAAAD8AAPwAB4AAPwAA/AAHgAA/AAD8AAfAAD8AAPwAD8AAPwAA/AAPwAA/AAD8AA/AAD4AAHwAH8AAfgAAfgAf4AB+AAB/AD/gAP4AAD+AffAB/AAAP//9/Af8AAAf//n///gAAB//+P//8AAAD//wf//gAAAD/+A//8AAAAH/gB//gAAAAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAB+AAAAAAAAAf4AAAAAAAAD/gAAAAAAAAf+AAAAAAAAH/4AAAAAAAA//gAAAAAAAH++AAAAAAAB/z4AAAAAAAP+PgAAAAAAB/g+AAAAAAAf8D4AAAAAAD/gPgAAAAAAf4A+AAAAAAH/AD4AAAAAA/wAPgAAAAAH+AA+AAAAAB/wAD4AAAAAP8AAPgAAAAB/gAA+AAAAAf8AAD4AAAAD/AAAPgAAAAf4AAA+AAAAB///////4AAH///////gAAf//////+AAB///////4AAH///////gAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAD4AAAAAAAAAPgAAAAAAAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAADwAAAAAAQAAfgAAAAA/gAB+AAAAD/+AAH8AAAP//4AAPwAAH///gAAfgAAf//+AAB+AAB///4AAH4AAH/wPgAAPgAAfgB8AAA/AAB+AHwAAD8AAH4AfAAAPwAAfgB8AAA/AAB+AHwAAD8AAH4AfAAAPwAAfgB+AAA+AAB+AH4AAD4AAH4AfgAAfgAAfgA/AAD+AAB+AD8AAPwAAH4AP4AD/AAAfgAf4Af4AAB+AB////AAAH4AD///4AAAfAAH///AAAB8AAP//4AAAHwAAf//AAAAAAAAf/wAAAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/gAAAAAAAB//gAAAAAAAf//gAAAAAAH///gAAAAAA////AAAAAAP///8AAAAAB//Af4AAAAAf/wAfwAAAAD/8AA/AAAAAf/wAB+AAAAH/+AAH4AAAA/7wAAPgAAAH/PAAA/AAAB/58AAD8AAAP+HwAAPwAAB/wfAAA/AAAf+B8AAD8AAD/wHwAAPwAAf8AfAAA+AAB/gB8AAD4AAH8AH4AAfgAAfgAfgAB+AAB4AA/AAPwAAHAAD+AB/AAAYAAP+Af4AAAAAAf///AAAAAAA///8AAAAAAB///gAAAAAAD//4AAAAAAAH//AAAAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAAAAAH4AAAAAAAAAfgAAAAAAAAB+AAAAAAAAAH4AAAAAAgAAfgAAAAAOAAB+AAAAAD4AAH4AAAAA/gAAfgAAAAP+AAB+AAAAD/4AAH4AAAA//AAAfgAAAP/4AAB+AAAD/+AAAH4AAA//gAAAfgAAP/4AAAB+AAD/+AAAAH4AA//gAAAAfgAP/4AAAAB+AB/+AAAAAH4Af/gAAAAAfgH/4AAAAAB+B/+AAAAAAH4f/gAAAAAAfn/4AAAAAAB//+AAAAAAAH//gAAAAAAAf/4AAAAAAAB/+AAAAAAAAH/gAAAAAAAAf4AAAAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAwAD/+AAAAA/8Af/8AAAAP/4D//4AAAB//4f//wAAAP//j///gAAB///P8H/AAAP////AH8AAA/gH/wAP4AAH4AH+AAfgAAfgAf4AA+AAB8AA/gAD4AAHwAD8AAPwAA+AAHwAAfAAD4AAfAAB8AAPgAB8AAHwAA+AAHwAAfAAD4AAfAAB8AAHwAD8AAPwAAfAAPwAA+AAB+AB/gAD4AAH4AH+AAfgAAP4B/8AD+AAA/8//4AfwAAB///P8H/AAAD//8///4AAAH//h///AAAAP/4D//4AAAAP/AH//AAAAAHAAP/4AAAAAAAAP+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/8AAAAAAAAP/8AAAAAAAD//4AAAAAAAf//4AAAAAAD///gAAAAAAf///AAAIAAB/gP+AABgAAP4AP4AAeAAA/AAfwAD4AAH4AA/AAfgAAfgAB8AH+AAB8AAHwA/4AAPwAAfAH/gAA/AAB8B/8AAD8AAHwP/AAAPwAAfB/4AAA/AAB8P+AAAD8AAHj/wAAAHwAAef8AAAAfgAD7/gAAAB+AAP/8AAAAD8AB//AAAAAP4AP/4AAAAAf4D/+AAAAAB////wAAAAAD///8AAAAAAH///gAAAAAAH//4AAAAAAAP/+AAAAAAAAH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAfAAAAAD8AAD+AAAAAf4AAP4AAAAB/gAB/wAAAAH+AAH/AAAAAf4AAP4AAAAA/AAA/gAAAAB4AAB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="); + +exports.add = function(graphics) { + graphics.prototype.setFontLato = function() { + // Actual height 50 (53 - 4) + this.setFontCustom(font, 46, widths, 64+(1<<8)+(1<<16)); + } +}; diff --git a/apps/pastel/f_latosmall.js b/apps/pastel/f_latosmall.js new file mode 100644 index 000000000..8ceb61ccf --- /dev/null +++ b/apps/pastel/f_latosmall.js @@ -0,0 +1,10 @@ + +var widths = atob("BAgJDQ0RDwUHBwkNBQgFCA0NDQ0NDQ0NDQ0GBg0NDQkSDw4PEQ0MEBEHCg8LFBESDRIODA0QDxYODg4HCAcNCQcLDAoMDAcLDAYGDAYSDAwMDAkKCAwLEQsLCgcHBw0A"); +var font = atob("AAAAAAAAAAAAAAAAAAAAAAAAEA/84D/zgAAEAAAAAAAAAAAA+AAD8AAAAAAAAAD4AAPgAAAAAAAAAABAADGIAM/gB/8A/+AD8YAAx+AD/4B/4APxgAjGAAIAAAAAAAAAAAAADwMAfg4DnBgMMHg///P/5gMGGAwc4Bg/AEB4AAAAA4AAHwAA5gYDCDgMIcAxjgB84ADnAAA4AAHOABz8AOMYBwwgMDCAgP4AAfAAAAAAAAAAeAAH8APY4B/BgMcGAw4YDBxgMDmA4HwBwPAAB8AAf4ABhgAACAAAAD4AAPgAAAAAAAAAAAAAH/gB//wfAHzgAHAAAAAAAAAAAOAAcfAPwf/8Af+AAAAAAAAAAAANgAAUAABwAAfwAAcAADQAAJAAAAAAAAAAAGAAAYAABgAAGAAP/gA/+AAGAAAYAABgAAGAAAQAAAAAAAEAAA7AAD4AAAAAAAAAAAAGAAAYAABgAAGAAAYAAAgAAAAAAAAAADgAAOAAAYAAAAAAPAAD4AB8AAfAAHwAD4AAeAABAAAADgAB/wAf/wBwHAMAGAwAYDABgMAGA4A4B4PAD/4AH/AAAAAAAAAAAAAYAgDgGAcAYDgBgP/+A//4AABgAAGAAAYAAAAAAAAAAAAQBgHgOAcB4DgPgMA2AwGYDAxgOOGAfwYB+BgBgGAAAAAAAADA4AcDwDgDgMAGAwgYDDBgMcGA5w4B9/ADj4AAAAAAAAABgAAOAAB4AAfgADmAAcYADhgA4GAD//gP/+AAGAAAYAAAgAAAAAADAH4OA/gYDGBgMYGAxgYDGDgMccAw/wCB8AAAAAAAAAAYAAH4AB/wAPjgB8GAOwYDzBgOMGAg44AD/AAH4AACAAAAAAAAAwAADAAAMAGAwB4DAfAMHwAw8ADPAAPwAA+AADgAAAAAAAAAA4+AH38A/44DHBgMMGAwwYDHBgOeOAffwA4/AABwAAAAAAAAAAAAPgAB/AAOMGAww4DBngMF4Aw/ADj4AH+AAPwAAAAAAAAADg4AODgAwGAAAAAAAAAAAABAAAODsA4PgBAYAAAAAAAAAQAABgAAPAAA8AAG4AAZgADHAAMMABgwAAAAAAAAAAAAAAAAEQAAZgABmAAGYAAZgABmAAGYAAZgABmAAGYAAAAAAAAAAAAAAAAGDAAMMAAxwABmAAG4AAPAAA8AABgAAEAAAAAAAAAEAAA4AADABgMHOAw84DGAAP4AAfAAAAAAACAAD/gAePADgGAYAMBh8YMPxgxxGDGEIIYwgxOCDH8IMYRgYBGAwMwD/hAD8AAAAAAAGAAB4AAfgAP4AD+AA/YAPhgA4GAD4YAD9gAD+AAD+AAB+AAB4AABgAAAAAAAD//gP/+AwYYDBhgMGGAwYYDDhgOOGA/84B+/ABh4AAAAAAAAA/gAH/gA+/AHAcA4A4DgBgMAGAwAYDABgMAGA4A4BgDAGAMAAAAAAAAAAAA//4D//gMAGAwAYDABgMAGAwAYDABgOAOAYAwB4PAD/4AH/AAHwAAAAAAAAAAAAP/+A//4DDBgMMGAwwYDDBgMMGAwwYDABgMAGAAAAAAAAAAAA//4D//gMGAAwYADBgAMGAAwYADBgAMGAAwAAAAAAA/gAH/AA++AHAcA4A4DgBgMAGAwAYDABgMGGAwYYDhjgGH8AAfwAAAAAAAAAAAD//gP/+A//4ADAAAMAAAwAADAAAMAAAwAADAAAMAA//4D//gAAAAAAAAAAAAAAA//4D//gAAAAAAAAAAAAAAAAAYAABgAAGAAA4AAHgP/8A//gAAAAAAAAAAAAAAAP/+A//4ADAAAMAAB4AAPwABzgAOHABwPAOAeAwA4CAAgAAAAAAAAAAAP/+A//4AABgAAGAAAYAABgAAGAAAYAABgAAAA//4D//gP/+AeAAAeAAAeAAA+AAA8AAA4AAHgAB4AAeAAHwAA8AAPAAA//4D//gAAAAAAAAAAAAAAA//4D//gHAAAOAAAeAAA8AAA4AABwAADwAADgAAHAP/+A//4AAAAAAAAAAAAP4AD/4AeDwBwHAOAOAwAYDABgMAGAwAYDABgOAOAcBwB4PAD/4AD+AABAAAAAAAAAAAAAP/+A//4DBgAMGAAwYADBgAMGAA44AB/AAH4AAHAAAAAAA/gAP/gB4PAHAcA4A4DABgMAGAwAYDABgMAGA4A4BwHwHg/gP/nAP4MAEAQAAAAAAAAAAA//4D//gMGAAwYADBgAMHAAw/ADneAH4eAPA4AABgAAAAAAwA8DAH4OA5wYDDBgMMGAw4YDBjgOH8AYPgAAIAAAAAwAADAAAMAAAwAADAAAP/+A//4DAAAMAAAwAADAAAMAAAAAAAAAAAAAA//AD//AAAcAAA4AABgAAGAAAYAABgAAOAABwD//AP/4A/8AAAAAOAAA+AAB+AAB/AAA/AAA/AAAeAAD4AA/AAPwAH8AB+AAPgAA4AAAAAAOAAA/AAB/gAA/wAAf4AAPgAB+AA/gAfwAH4AA8AAD8AAD+AAB/AAB/gAA+AAH4AD/AD/gA/wAD4AAMAAAgAYDgDgPAeAeHgAe8AA/AAA4AAHwAB/wAPHgDwPgOAOAgAYAAAAIAAA4AADwAAHwAAHgAAHgAAP+AA/4APgAB4AAeAADwAAMAAAgAAAAAAMAGAwA4DAPgMB+AwPYDDxgMeGAzwYD8BgPgGA8AYDABgAAAAAAAH//8f//xAABEAAEAAAAAAAHAAAPAAAPgAAPgAAHwAAHwAAHgAADAAAAEAAEQAAR///H//8AAAAAAAAAAAAAAABgAAeAADwAA8AADgAAHgAAPAAAOAAAIAAAAAAAAAAAAQAABAAAEAAAQAABAAAEAAAQAABAAAAAAAAgAADAAAOAAAIAAAAAAAAAAAAAAAHAAY+ADnYAMYgAxiADGYAORAAf+AA/4AAAAAAAB//4H//gAYMADAYAMBgAwGADAYAPHgAf8AA/gAAAAAAAAA/gAH/AA4OADAYAMBgAwGADAYAMDgAQEAAAAAB+AAf8ADx4AMBgAwGADAYAMBgAYMB//4H//gAAAAAAAAB8AAf8ADpwAMhgAyGADIYAMhgA6GAB4wADhAAAAACAAAMAAH/+A//4DMAAMwAAzAAABAcAff4D/5gMbmAwmYDCZgMZmA/mYD8fAMA4AgAAAAAAAAAf/+B//4AGAAAwAADAAAMAAA4AAD/4AH/gAAAAAAACAAAc/+Bz/4CAAAAAAAAABiAAGc//5z//CAAAAAAAAAAAAAAf/+B//4AAYAADgAAfAAHuAA4cADAYAIAgAAAAAAAAAAAf/+B//4AAAAAAAAAAAAAAAA/+AD/4AEAAAwAADAAAMAAA/+AB/4AH/gAwAADAAAMAAA4AAD/4AD/gAAAAAAAAAAAA/+AD/4AGAAAwAADAAAMAAAwAAD/4AH/gAAAAAAAAD+AAf8ADg4AMBgAwGADAYAMBgA4OAB/wAD+AADgAAAAAP/+A//4BgwAMBgAwGADAYAMBgA8eAB/wAD8AAAAAAAAAB+AAf8ADx4AMBgAwGADAYAMBgAYMAD//gP/+AAAAAAAAP/gA/+ABwAAOAAAwAADAAAMAAAAAAAAQAHhgA/GADMYAMxgAxmADH4AEPAAAQAAAAAMAAAwAAf/wD//gAwGADAYAMBgAAAAAAAAP/AA/+AAAYAABgAAGAAAYAADAA/+AD/4AAAAAAAADgAAPgAAfgAAPwAAPgAAeAAHwAD8AA/AADgAAIAAA4AAD8AAD+AAB+AAB4AA/AAfgADwAAPgAAfwAAP4AAHgAB+AA/gAPwAA4AAAAAAAAgAwGADh4AHvAAPwAAOAAB8AAe8ADh4AMBgAgCACAAAOAAA+AAA/BgA/eAA/wAD8AA/AAPgAD4AAOAAAgGADA4AMHgAx+ADOYANxgA+GADgYAMBgAAAAAMAD//4f9/xgADEAAEAAAAAAAAAAAAAAB///n//+AAAAAAAAAAAAAABAABGAAMf9/w//+AAwAAAAAAAAAA4AADgAAYAABgAAHAAAMAAAwAADAAAcAADgAAAAAAAA"); + +exports.add = function(graphics) { + graphics.prototype.setFontLatoSmall = function() { + // Actual height 21 (20 - 0) + this.setFontCustom(font, 32, widths, 22+(1<<8)+(1<<16)); + } +}; diff --git a/apps/pastel/f_monoton.js b/apps/pastel/f_monoton.js new file mode 100644 index 000000000..34de6eca1 --- /dev/null +++ b/apps/pastel/f_monoton.js @@ -0,0 +1,7 @@ + +exports.add = function(graphics) { + graphics.prototype.setFontMonoton = function(scale) { + // Actual height 44 (43 - 0) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAABmwAAAAAAzYAAAAAAZsAAAAAAM2AAAAAAGbAAAAAADNgAAAAABmwAAAAAAzYAAAAAAZsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAD+AAAAAAf8AAAAAD/ggAAAAf8HwAAAD/g/4AAAf8H/AAAD/g/4OAAf8H/B/AD/g/4P+Af8H/B/wAfg/4P+AAMH/B/wAAA/4H+AAAD/A/4AAAB4H/AAAAAA/4AAAAAH/AAAAAAP4AAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH//gAAAAf//8AAAA/AAPgAAA8f/x8AAB4//+PAAB5+APxwABzwfwecAAzj//jnAA7n+P85gA7ngAPO4AbnH/xzsAdnP/+c3AN3PAHndgGzOAA5m4DbuAAO7MD9mAADN2Bs3AAB2bA2bAAAbNgbNgAANmwNmwAAGzYGzYAADZsDdmAADN2B+7AABuzAbMwABmbgNneAD3NgHZ3+/3MwBuc//nO4A7nB8HGYAM58AfOcAHeP/+OcABzx/8ecAAc+AA+cAAHH//8cAAB4//48AAAPg+B8AAAD+AP4AAAAP//wAAAAA/+AAAAAAAAAAAAAAAAAAABsAAAAAAA2AAAAAAAbAAAAAAANgAAAAAAGwAAAAAADf////8ABv////+AA3/////AAbAAAAAAAN/////wAG/////4ADYAAAAAABv////+AA3/////AAb/////gANgAAAAAAG/////4ADf////8AAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAADcAAAA2wBs2AADbYA2bAADtsAbZgADm2AftwAHjbAN24AHNtgGzYAPO2wDZsAPebYBs2AOeNsA2bAec22AbNge87bANmwc55tgG7c8542wD9355zbYA2Z5zztsAbODzjm2ANz/nnjbADc/nnhtgBnCPHA2wA74fPAbYAOf+OANsADj8eAG2AA8A+ADbAAP/8AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAABgG6AAAAuwDdsAAG3YBs2AADZuA27AABu3A/ZgAA7dgbtwAAduwN2w2zG3YGzYbZjZsDZsNsxs2Bs2G2Y2bA2bDbMbNgbNhtmNmwNmw2zGzYG7MbdnbsD939m/d2A2Z/7PM3AbuBtwO7AOz73eeZgDc/9n+dwB3H2Y8cwAZ4HnA84AGf/5/84ADz/OP44AAeALwB4AAH/+//4AAA/+H/wAAADwAfAAAAAAAAAAAAAAAAAAAAAAAZsAAAAAB82AAAAAD+bAAAAAHzNgAAAAPjmwAAAAfHzYAAAB+P5sAAAD8fM2AAAHw+ObAAAPj8fNgAAfH4/mwAA+Ph8zYAAcfH4ZsAAA+Px82AAB8fD+bAAD4+HzNgABh8PhmwAAH4/AzYAAPx+AZsAAPD4AM2AAGHwP+bfgAfgH/NvwA/AABmwAA8AAAzYAAYAA/5t+AAAAf82/AAAAAGbAAAAAADNgAAAAAAAAAAAAAAAAAAAAAAAGAAE///ADAAGf//gBwADP//wCcABmAAADmAAz//8C7gAZ//+DMwAMwAAA3YAGf//hZsADP//xu3ABn//4zdgAzDNsNuwAZhu2GzYAMw2bDZsAGYbNhs2ADMNmw2bABmGzYbNgAzDZsdmwAZhs2M3YAMw3d+zcAGYZm+ZsADMOzgd2ABmDM883AAzB3P87AAZgZx47gAMwOcB5gAGYDn/5gADMA4/zwAAAAPADwAAAAD8fgAAAAAf/gAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///4AAAD////AAAHwAADwAAHH//8eAAHP///ngAHfgAB8wAHeH/8PcADcf//x3ADsf//+ZgBu8AADu4B2c//8zMA3d///M2AbNwAB2bgduxs2bswP2Z2/O3YGzYzbDZsDZsbths2Bs2Nmw2bA2bGzYbNgbNjZsNmwNmxs2GzYH7c2bHbsDtmbtzdmA2bM3fs3AbMHZnO7AM3BuYOZgHZAzP+dgBmAMx+cwA7gHeAcwAMgB3584AHAAc/84ABgAHHx4AAAAB4D4AAAAAf/wAAAAAD/gAAAAAAAAAAAAAAAAAAZsAAAAAAM2AAAAAAGbAAAAAADNgAAAAABmwAAAAAAzYAAAAAAZsAAAAAAM2AAAAPAGbAAAB/gDNgAAP+ABmwAD/wYAzYAf+D8AZsD/wf8AM2f8D/gAGT/gf8HgAf8D/g/wB/g/8H/AA8H/g/4MAA/8H/B+AH/g/4P+AH4H/B/wADA/4P+AAAH/B/wAAA/4P+AAAAfB/wAAAAAP+AAAAAB/wAAAAAD+AAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAGAAwAAAA/8H/gAAB//v/8AAB4B/APgADz+PP54ABn/x//OABng8eDzAB3HHOcdwA3P9z/nYA7P/d/5mAbOBmYO7ANmebvzNwP3fs392YGzc3ZmbsDZsZsxs2Bs2M2Y2bA2bGbMbNgbNjNmNmwNmxmzGzYGzYzZjZsDZsZsxs2Bs2M2Y2bA2bGbMbNgbNzNmNmwP2Zm7s3YDbv7M+7MBszt3OZuA3MGZwd2ANn/uf8zAGY+zn47gDvAc4A7gA78/Pj5gAOf/z/zwADj8cPjwAA+A/gHgAAH/9//gAAA/4P/AAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/4AAAAAHw/AAAAAHADwADAAHP8cABgAHf/nAA4AHeB5gDuAHcfOYAzADc/7uBdwBs8ezBmYBu4DdgbsA2Z924s3AbN+bM3bgfsxt2duwNm4zbG3YGzYZtjZsDZsM2xs2Bs2GbY2bA2bDNsbNgbNhv2NmwP242zO3YHbszbm7sBs3AAHZuA2Z///M2Abuf//O7AGzh/8ObgDc8AA+dgB3P//+dwAdx//8cwAGeAAA8wADn///44AA8///54AAPgAAB4AAB+AAPwAAAP///gAAAA//+AAAAAAAAAAAAAAAAAAAAAAAAAAAADbBmwAAABtgzYAAAA2wZsAAAAbYM2AAAANsGbAAAAG2DNgAAADbBmwAAABtgzYAAAA2wZsAAAAAAAAAAAAAAAAAAA="), 46, atob("DRYpFR0eHiImHygmDQ=="), 49+(scale<<8)+(1<<16)); + } +}; diff --git a/apps/pastel/f_t.js b/apps/pastel/f_t.js new file mode 100644 index 000000000..a5cac2838 --- /dev/null +++ b/apps/pastel/f_t.js @@ -0,0 +1,7 @@ + +exports.add = function(graphics) { + graphics.prototype.setFontSpecialElite = function(scale) { + // Actual height 40 (39 - 0) + this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAYAAAAAAAfwAAAAAAP/AAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAAv8AAAAAAN6AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAfAAAAAAAPwAAAAAAP8AAAAAAH+AAAAAAH+AAAAAAD+AAAAAAD/AAAAAAD/AAAAAAB/AAAAAAB/AAAAAAB/AAAAAAB/gAAAAAB/gAAAAAB/gAAAAAA/gAAAAAB/wAAAAAA/4AAAAAA/wAAAAAA/4AAAAAA/4AAAAAAf8AAAAAAP8AAAAAAD8AAAAAAA8AAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//wAAAAP///gAAAH/9/+AAAD/gAf4AAB/AAB+AAA/AAAHwAAPAAAA+AADgAAAPgAAwAAAD4AAcAAAAfAAHAAAAHwABwAAAB8AA4AAAAfAAOAAAAHwABwAAAB8AAcAAAA/AAHgAAAPgAB+AAAH4AAPgAAD8AAD+AAD+AAA/4Af/AAAB////AAAAP///wAAAAP//gAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAOAAAAHAADgAAAD4AB4AAAA+AAeAAAAPgAHgAAAD4AB4AAAAeAAeAAAAHgAHgAAAB4AB4AAAAcAAeAAAAPAAH4AAP/wAB/////+AAf/////gAH/////4AB///+/+AAAAQAAPgAAAAAAB4AAAAAAAeAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAADwAAAAAAA+AAAAAAAPgAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAcAAAB+AAfwAAA/wAf/AAA/8Af/wAAf/AP/8AAGPwP//AADh8D48AAA4OB8OAAAOAAfDgAAHAAPg4AABwADwPAAAcAB8DwAAHAAeAeAABwAHAHgAAcADwB8AAHAB4APgAB4A+AB4AAPAfAAeAAD4fgADgAAf/4AA4AAD/8AAeAAAf+AAfAAAB8AAPwAAAAAAD4AAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAf8AAB/wAH/wAAf8AB/8AAH+AAffgAB4AcDx4AAcAPAAfAAHAPwAHwABwHwAA8AAcB+AAPAAHA/gADwABw/4AA8AAcf+AAPAAHP/gAHwABz74AB8AAf8fAA/AAH8DwAPgAD/A8AHwAA/gHwP8AAPwA//+AADwAH//AAAAAA//gAAAAAD/wAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAP8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/PAAAAAA/jwAAAAAfg8AAAAAPwPAAAAAH4DwAAAAD4A8HAAAD8APBwAAB+ADw8AAA+AA8PAAA/AAPDgAAPgADw8AAHwAB8/AAD+B///wAA/////8AAP/////AAB+f///wAAAAAHx8AAAAAB8PAAAAAAPDwAAAAADw8AAAAAA4PAAAAAAODwAAAAADgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAB/gAAH//wf8AAB//+H/gAAf//h/4AAHJ/wf/AABwD4D/4AAeA+AAeAAHgPAAHgAB4DwAB4AAeA4AAeAAHgOAAHgAA4DgAB4AAOA8AAeAADgPAAHgAB4D4ADwAAeAeAB4AAHAHwAeAABgAfAPgAAYAD8fgAAAAA//wAAAAAH/4AAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAA//wAAAAB///AAAAB////AAAB/7//wAAA/AfB+AAAfAPAPwAAPgHgB+AAHwBwAPgAB4A8AB8AAeAPAAfAAPADwAHwADgA8AB8AA8APAAfAAPADwAHwADwA8AB8AA8AHgA+AAP8B4APgAD/wfAH4AA/8D4D8AAH/A///AAB/wH//gAAH8A//wAAA8AH/4AAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAB/4AAAAAA/8AAAAAAP+AAAAAAD+AAAAAAAeAAAAAAAHAAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHwAAA/8AD+AAB//AA/gAB//wAP4AB//gAB+AB//AAAfwB//AAAH8A/wAAAA/A/wAAAAHw/wAAAAB8/wAAAAAffwAAAAAP/wAAAAAD/4AAAAAA/4AAAAAAP8AAAAAAD4AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAP/wAAAH8H/+AAAH/z//wAAD////+AAB///h/gAA/B/gH4AAPAP4A/AAHwB8AHwAB4APAB8AAeADgAfAAHgA4ADwABwAOAA8AAcADgAPAAHAA4ADwAB4AeAA8AAfAHwAPAADwB8AHgAA+A/gD4AAPgP4B+AAB+P/h/gAAP////wAAB/8f/4AAAP8D/8AAAAAAf8AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAA/4APAAAA//gH4AAAP/8D/gAAP4Pg/8AADwB8P/AAB4AfD/4AAeAD4d+AAHAA+AfwADwAHgH8AA4AA8A/AAOAAPAPgADgADwD4AA8AA4B+AAPAAeAfAAB4AHgHgAAeADwD4AAHwA8A8AAA+AfA/AAAHp/h/gAAA3//+gAAAB//+gAAAAd//gAAAACf/wAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAABwB/AAAAB/A/8AAAA/wf/gAAAP+H/4AAAH/h/+AAAB/8f/gAAAP+H/4AAAD/h/+AAAAfwf/gAAAH8C/wAAAAAA3oAAAAAABwAAAAAAAAAAAAAAAAAAAA=="), 46, atob("ERwfHB0cHxsdHB4dEQ=="), 50+(scale<<8)+(1<<16)); + } +}; From d450758f1ad9aa833f47873cd45c87f15ef151b4 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 22 Nov 2021 22:09:52 +0000 Subject: [PATCH 0821/1062] Pastel: font modules --- apps/pastel/f_t.js | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 apps/pastel/f_t.js diff --git a/apps/pastel/f_t.js b/apps/pastel/f_t.js deleted file mode 100644 index a5cac2838..000000000 --- a/apps/pastel/f_t.js +++ /dev/null @@ -1,7 +0,0 @@ - -exports.add = function(graphics) { - graphics.prototype.setFontSpecialElite = function(scale) { - // Actual height 40 (39 - 0) - this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAYAAAAAAAfwAAAAAAP/AAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAAv8AAAAAAN6AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAfAAAAAAAPwAAAAAAP8AAAAAAH+AAAAAAH+AAAAAAD+AAAAAAD/AAAAAAD/AAAAAAB/AAAAAAB/AAAAAAB/AAAAAAB/gAAAAAB/gAAAAAB/gAAAAAA/gAAAAAB/wAAAAAA/4AAAAAA/wAAAAAA/4AAAAAA/4AAAAAAf8AAAAAAP8AAAAAAD8AAAAAAA8AAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//wAAAAP///gAAAH/9/+AAAD/gAf4AAB/AAB+AAA/AAAHwAAPAAAA+AADgAAAPgAAwAAAD4AAcAAAAfAAHAAAAHwABwAAAB8AA4AAAAfAAOAAAAHwABwAAAB8AAcAAAA/AAHgAAAPgAB+AAAH4AAPgAAD8AAD+AAD+AAA/4Af/AAAB////AAAAP///wAAAAP//gAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAOAAAAHAADgAAAD4AB4AAAA+AAeAAAAPgAHgAAAD4AB4AAAAeAAeAAAAHgAHgAAAB4AB4AAAAcAAeAAAAPAAH4AAP/wAB/////+AAf/////gAH/////4AB///+/+AAAAQAAPgAAAAAAB4AAAAAAAeAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAADwAAAAAAA+AAAAAAAPgAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAcAAAB+AAfwAAA/wAf/AAA/8Af/wAAf/AP/8AAGPwP//AADh8D48AAA4OB8OAAAOAAfDgAAHAAPg4AABwADwPAAAcAB8DwAAHAAeAeAABwAHAHgAAcADwB8AAHAB4APgAB4A+AB4AAPAfAAeAAD4fgADgAAf/4AA4AAD/8AAeAAAf+AAfAAAB8AAPwAAAAAAD4AAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAf8AAB/wAH/wAAf8AB/8AAH+AAffgAB4AcDx4AAcAPAAfAAHAPwAHwABwHwAA8AAcB+AAPAAHA/gADwABw/4AA8AAcf+AAPAAHP/gAHwABz74AB8AAf8fAA/AAH8DwAPgAD/A8AHwAA/gHwP8AAPwA//+AADwAH//AAAAAA//gAAAAAD/wAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAP8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/PAAAAAA/jwAAAAAfg8AAAAAPwPAAAAAH4DwAAAAD4A8HAAAD8APBwAAB+ADw8AAA+AA8PAAA/AAPDgAAPgADw8AAHwAB8/AAD+B///wAA/////8AAP/////AAB+f///wAAAAAHx8AAAAAB8PAAAAAAPDwAAAAADw8AAAAAA4PAAAAAAODwAAAAADgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAB/gAAH//wf8AAB//+H/gAAf//h/4AAHJ/wf/AABwD4D/4AAeA+AAeAAHgPAAHgAB4DwAB4AAeA4AAeAAHgOAAHgAA4DgAB4AAOA8AAeAADgPAAHgAB4D4ADwAAeAeAB4AAHAHwAeAABgAfAPgAAYAD8fgAAAAA//wAAAAAH/4AAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAA//wAAAAB///AAAAB////AAAB/7//wAAA/AfB+AAAfAPAPwAAPgHgB+AAHwBwAPgAB4A8AB8AAeAPAAfAAPADwAHwADgA8AB8AA8APAAfAAPADwAHwADwA8AB8AA8AHgA+AAP8B4APgAD/wfAH4AA/8D4D8AAH/A///AAB/wH//gAAH8A//wAAA8AH/4AAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAB/4AAAAAA/8AAAAAAP+AAAAAAD+AAAAAAAeAAAAAAAHAAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHwAAA/8AD+AAB//AA/gAB//wAP4AB//gAB+AB//AAAfwB//AAAH8A/wAAAA/A/wAAAAHw/wAAAAB8/wAAAAAffwAAAAAP/wAAAAAD/4AAAAAA/4AAAAAAP8AAAAAAD4AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAP/wAAAH8H/+AAAH/z//wAAD////+AAB///h/gAA/B/gH4AAPAP4A/AAHwB8AHwAB4APAB8AAeADgAfAAHgA4ADwABwAOAA8AAcADgAPAAHAA4ADwAB4AeAA8AAfAHwAPAADwB8AHgAA+A/gD4AAPgP4B+AAB+P/h/gAAP////wAAB/8f/4AAAP8D/8AAAAAAf8AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAA/4APAAAA//gH4AAAP/8D/gAAP4Pg/8AADwB8P/AAB4AfD/4AAeAD4d+AAHAA+AfwADwAHgH8AA4AA8A/AAOAAPAPgADgADwD4AA8AA4B+AAPAAeAfAAB4AHgHgAAeADwD4AAHwA8A8AAA+AfA/AAAHp/h/gAAA3//+gAAAB//+gAAAAd//gAAAACf/wAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAABwB/AAAAB/A/8AAAA/wf/gAAAP+H/4AAAH/h/+AAAB/8f/gAAAP+H/4AAAD/h/+AAAAfwf/gAAAH8C/wAAAAAA3oAAAAAABwAAAAAAAAAAAAAAAAAAAA=="), 46, atob("ERwfHB0cHxsdHB4dEQ=="), 50+(scale<<8)+(1<<16)); - } -}; From 1a75f0813b5bae3f2b6b7b14dd563a18fa00225f Mon Sep 17 00:00:00 2001 From: hughbarney Date: Mon, 22 Nov 2021 22:50:27 +0000 Subject: [PATCH 0822/1062] Pastel: font modules --- apps/pastel/pastel.app.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 3c2dd1bba..c455cb64d 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -1,6 +1,6 @@ require("f_latosmall").add(Graphics); const SETTINGS_FILE = "pastel.json"; -let settings = undefined; +let settings; function loadSettings() { //console.log("loadSettings()"); @@ -13,11 +13,11 @@ function loadSettings() { function loadFonts() { //console.log("loadFonts()"); - //console.log(settings); + console.log(settings); // load font files based on settings.font if (settings.font == "Architect") - require("f_artitect").add(Graphics); + require("f_architect").add(Graphics); else if (settings.font == "GochiHand") require("f_gochihand").add(Graphics); else if (settings.font == "CabinSketch") @@ -91,8 +91,7 @@ function draw() { else if (settings.font == "Elite") g.setFontSpecialElite(); else - //g.setFontLato(); - g.setFontOrbitron(); + g.setFontLato(); g.setFontAlign(1,-1); // right aligned g.drawString(hh, x - 6, y); From 58f9e7cd5b59904651fb878a89844ee4ccfb23df Mon Sep 17 00:00:00 2001 From: jeffyactive Date: Mon, 22 Nov 2021 20:03:18 -0500 Subject: [PATCH 0823/1062] Upgrade to emoji images and supporting text --- apps/emojuino/emojuino.js | 54 +++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/apps/emojuino/emojuino.js b/apps/emojuino/emojuino.js index 3de92fa6c..5b7670652 100644 --- a/apps/emojuino/emojuino.js +++ b/apps/emojuino/emojuino.js @@ -4,29 +4,48 @@ */ -// Emojis are integer pairs with the form [ image, Unicode code point ] +// Emoji images are 96px x 96px, 4bpp (https://www.espruino.com/Image+Converter) +// and adapted from Font Awesome 5 +const GRIN = "sFgwkBiIATDwoaUFi4ynQZ4uuGDzlTF1wwaFyowYFy4wWiAvZgIutGCgubSKRecMCQudMCBeeMCAufMBxegMBwuhMBheiMBgujMBRekMBQvvF0qQIL0xgIF94unSA4vuR1CQGF94upSAovuR1SQEF94urSAY/PCBivQF5z/DEBQ+DEB5ePCJYOEMBgNNF8MBHpogNHwqBNF/4vsEAovOX7TviBhYgFD5Q/EEJoANEAY/OLxgAQPx5edAH4A/AH4A/AH4A/AEUQF1sBF/4v/F/4vviILJBRQANEZYLJHQIMKFpYABQhIiKC4QaMIhBHLF6AAVEhRQIF8ZuCF5B6GACYjMF9ZrOF8jAiKRgvvSEJROBo5gYEBw+IMCwfPB5BgWDxBPHCCBeVJxBgdJqIvJMCQcTCRAwRFxJ8KChQwODKwVJGBouKbZgXLDBQVLPBoZLDYxDMLxocQACLXOMBwARFxxgfLx5gfFyBgdLyIwcFyaRbFygwZFywwXFzAwVFzQwTFzgwRFzwxOFsIyKDSg"; +const MEH = "sFgwkBiIATDwoaUFi4ynQZ4uuGDzlTF1wwaFyowYFy4wWiAvZgIutGCgubSKRecMCQudMCBeeMCAufMBxegMBwuhMBheiMBgujMBRekMBQvvF0qQIL0xgIF94unSA4vuR1CQGF94upSAovuR1SQEF94urSAY/PCBivQF5z/DEBQ+DEB5ePCJYOEMBgNNF8MBHpogNHwqBNF/4vsEAovOX7TviBhYgFD5Q/EEJoANEAY/OLxgAQPx5edAH4A/AH4A/AH4A/AEUQF1sBF/4v/F/4vviIvtiIv/F9qeBACDgNB5ouSECAOLFyaBMKAYvrByQvgSBS/fD4jAfXxwQMADxAQF8iQLADjeGF96QoFwxgnLw4vwSEwuIMEpeJMEouKMEZeLMEYuMMEJeNMEIuOMD5ePMD4uQMDpeRGDguTSLYuUGDIuWGC4uYGCouaGCYucGCIueGJwthGRQaUA"; +const FROWN = "sFgwkBiIATDwoaUFi4ynQZ4uuGDzlTF1wwaFyowYFy4wWiAvZgIutGCgubSKRecMCQudMCBeeMCAufMBxegMBwuhMBheiMBgujMBRekMBQvvF0qQIL0xgIF94unSA4vuR1CQGF94upSAovuR1SQEF94urSAY/PCBivQF5z/DEBQ+DEB5ePCJYOEMBgNNF8MBHpogNHwqBNF/4vsEAovOX7TviBhYgFD5Q/EEJoANEAY/OLxgAQPx5edAH4A/AH4A/AH4A/AEUQF1sBF/4v/F/4vUgMRAAQZWFqwxWCgIuZGCYvSFxIcUFzYdTOZyNKSKQdCCJwuNMB5NDLzZOPIKAviCJguPJxpNEF94RLRyBONIKAvHNRQvRCKAMUJpIvOZxx9WAEbSTADReHF+CQmFxBglLxJglFxRgjLxZgjFxhghLxpghFxxgfLx5gfFyBgdLyIwcFyaRbFygwZFywwXFzAwVFzQwTFzgwRFzwxOFsIyKDSg"; +const THUMBS_UP = "sFgwkBiIAaiAiBDzYAQKYZQcLyAwsF4qSpcoxgoF4xgnRwwvxSEwvvFw4vwYEwv/F/4AOiAv/R1Av/F/6+PgIv/RzwvjLxQvkFxTujLxYvjFxaOiLxgvvR1wviR3gviR3YviFxg6iF7AwVRxowhFzUAgIvuMCSObF6YucSCJedF6IudSARQIHQheeAAIgKGAYufF+CbMF/4v/WYQv/F/6yPF/6OeF9wgNL/4v/F/4vhEQIv/R/4v/F/7ueF/4v/Xx4v/F/4v/F/4v/F/4v/F7ogOF/6OSEAgHCiAvrAwQHHRz4v/F/4v/F58QF8cBE4wPDGLYvHB5aTaKwQvUMS4vYGCx8QF5AwULwgvWYiZJQIAowXDowvYGJyqRFx4bKDRQA=="; +const THUMBS_DOWN = "sFgwkBiIAbiAoGEroAHLZgttMcK9RXEZgmFyZgHDZA/JFyogFDZQwHFqovXLiyQHB5wtaF6gubF/4v/F/4vwgIv/F7wgPF/6QTF/4v/F/4v/F/4v/F/4AdF/4v/YCIv/F/4v9EQIv/R/4v/F/7ueL+gFBiMQF8oiBE4wHHF/6QQF/4v/YigvugInBiAvrM5QvvM4gvqMFgvDMD0BF55gegJPKgIvEMDoeLF4pgdJ5QuGF7gjHABaQbFyRgbFygvZFyqQOEixgYF8RgMgIv/SH5gPYH6QfF8aQvMBgvjMBaQjMBYvkMBQv/SEAv/F/7APF/6QfF/4v/F/0BF8sQF/4vnF0rAJF9yOmSBAunF4xeoSAouqMAYTQA=="; +const HEART = "sFgwkBiIA/AH4A/AH4AogAADC1EQC4gaQCo8BIqYwRCyxdJDJoVLMJYuMGBIVNGBQYNDI5FOO5IXODI4WWI6BgGCywYTDIYVVO6gvXSAoYTDIQVTMAgYTDIJFUMAgYUACyOXAC7XWF7YurSAYvuR1iQCF/4v/F54utAH4A/AH4A/AH4A/AGMQF1sBF/4v/F58RF9sRF/4vgYFi+BMFouCF+CQqRwYvwSFQuEMFJeFMFIuGME5eHME4uIMEpeJMEouKMEZeLMEYuMMEJeNMEIuOMD5ePMD4uQMDpeRMDouSMDZeTMDYuUMDJeVMDIuWMC5eXMC4uYMCpeZMCouaMCZebMCYucMCJedF+CQQFzxgPFz5gPF8JgMXr5gPF0RgLL0ZgLF0hgJL0pgJF0xgHL05gHF1BgFL1JgFF1QwDF1gA/AH4A/AH4AJA="; +const TX = "k8XwkBiIAYEYogLHBAUIiBNKGxooKEggvJCYYHDKxAMFAoRrOCRAsHCYqbNHQibLKAauOLBCJHQw6JMQBIJBRJDWJThK5JJJi5KbpaJKFBaKEE5ybGHRhcOACEQA"; + + +// Emojis are pairs with the form [ Image String, Unicode code point ] // For code points see https://unicode.org/emoji/charts/emoji-list.html const EMOJIS = [ - [ ':)', 0x1f642 ], // Slightly smiling - [ ':|', 0x1f610 ], // Neutral - [ ':(', 0x1f641 ], // Slightly frowning - [ '+1', 0x1f44d ], // Thumbs up - [ '-1', 0x1f44e ], // Thumbs down - [ '<3', 0x02764 ], // Heart + [ GRIN, 0x1f642 ], // Slightly smiling + [ MEH, 0x1f610 ], // Neutral + [ FROWN, 0x1f641 ], // Slightly frowning + [ THUMBS_UP, 0x1f44d ], // Thumbs up + [ THUMBS_DOWN, 0x1f44e ], // Thumbs down + [ HEART, 0x02764 ], // Heart ]; const EMOJI_TRANSMISSION_MILLISECONDS = 5000; const BLINK_PERIOD_MILLISECONDS = 500; const TRANSMIT_BUZZ_MILLISECONDS = 200; const CYCLE_BUZZ_MILLISECONDS = 50; +const WELCOME_MESSAGE = 'Emojuino:\r\n\r\n< Swipe >\r\nto select\r\n\r\nTap\r\nto transmit'; // Non-user-configurable constants const IMAGE_INDEX = 0; const CODE_POINT_INDEX = 1; +const EMOJI_PX = 96; +const EMOJI_X = (g.getWidth() - EMOJI_PX) / 2; +const EMOJI_Y = (g.getHeight() - EMOJI_PX) / 2; +const TX_X = 68; +const TX_Y = 12; +const FONT_SIZE = 24; const BTN_WATCH_OPTIONS = { repeat: true, debounce: 20, edge: "falling" }; const UNICODE_CODE_POINT_ELIDED_UUID = [ 0x49, 0x6f, 0x49, 0x44, 0x55, 0x54, 0x46, 0x2d, 0x33, 0x32 ]; + // Global variables let emojiIndex = 0; let isToggleOn = false; @@ -72,6 +91,7 @@ function transmitEmoji(image, codePoint, duration) { require('ble_eddystone_uid').advertise(UNICODE_CODE_POINT_ELIDED_UUID, instance); isTransmitting = true; + drawImage(EMOJIS[emojiIndex][IMAGE_INDEX], true); let displayIntervalId = setInterval(toggleImage, BLINK_PERIOD_MILLISECONDS, image); @@ -85,14 +105,14 @@ function terminateEmoji(displayIntervalId) { NRF.setAdvertising({ }); isTransmitting = false; clearInterval(displayIntervalId); - drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]); + drawImage(EMOJIS[emojiIndex][IMAGE_INDEX], false); } // Toggle the display between image/off function toggleImage(image) { if(isToggleOn) { - drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]); + drawImage(EMOJIS[emojiIndex][IMAGE_INDEX], true); } else { g.clear(); @@ -102,9 +122,15 @@ function toggleImage(image) { // Draw the given emoji -function drawImage(image) { +function drawImage(image, isTx) { g.clear(); - g.drawString(image, g.getWidth() / 2, g.getHeight() / 2); + g.drawImage(require("heatshrink").decompress(atob(image)), EMOJI_X, EMOJI_Y); + if(isTx) { + g.drawImage(require("heatshrink").decompress(atob(TX)), TX_X, TX_Y); + } + else { + g.drawString("< Swipe >", g.getWidth() / 2, g.getHeight() - FONT_SIZE); + } g.flip(); } @@ -131,15 +157,15 @@ function handleDrag(event) { // Special function to handle display switch on Bangle.on('lcdPower', (on) => { if(on) { - drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]); + drawImage(EMOJIS[emojiIndex][IMAGE_INDEX], false); } }); // On start: display the first emoji and handle drag and touch events g.clear(); -g.setFont('Vector', 80); +g.setFont('Vector', FONT_SIZE); g.setFontAlign(0, 0); -drawImage(EMOJIS[emojiIndex][IMAGE_INDEX]); +g.drawString(WELCOME_MESSAGE, g.getWidth() / 2, g.getHeight() / 2); Bangle.on('touch', handleTouch); Bangle.on('drag', handleDrag); From ae55c2d39fc936cfa1954d44b7f76dc91c21ced3 Mon Sep 17 00:00:00 2001 From: jeffyactive Date: Mon, 22 Nov 2021 20:09:26 -0500 Subject: [PATCH 0824/1062] Allow emulator, specify type as app --- apps.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps.json b/apps.json index 8d90a1d1a..8636f9ba0 100644 --- a/apps.json +++ b/apps.json @@ -4243,8 +4243,10 @@ "version": "0.01", "description": "Emojis & Espruino: broadcast Unicode emojis via Bluetooth Low Energy.", "icon": "emojuino.png", + "type": "app", "tags": "emoji", "supports" : [ "BANGLEJS2" ], + "allow_emulator": true, "readme": "README.md", "storage": [ { "name": "emojuino.app.js", "url": "emojuino.js" }, From 5a01047cd0b30e4b7b253d784bd0b5755fc0c2e2 Mon Sep 17 00:00:00 2001 From: jeffyactive Date: Mon, 22 Nov 2021 20:21:29 -0500 Subject: [PATCH 0825/1062] Added screenshots --- apps.json | 5 +++++ apps/emojuino/screenshot-swipe.png | Bin 0 -> 2097 bytes apps/emojuino/screenshot-tx.png | Bin 0 -> 1812 bytes apps/emojuino/screenshot-welcome.png | Bin 0 -> 2697 bytes 4 files changed, 5 insertions(+) create mode 100644 apps/emojuino/screenshot-swipe.png create mode 100644 apps/emojuino/screenshot-tx.png create mode 100644 apps/emojuino/screenshot-welcome.png diff --git a/apps.json b/apps.json index 8636f9ba0..886afeea2 100644 --- a/apps.json +++ b/apps.json @@ -4243,6 +4243,11 @@ "version": "0.01", "description": "Emojis & Espruino: broadcast Unicode emojis via Bluetooth Low Energy.", "icon": "emojuino.png", + "screenshots": [ + { "url": "screenshot-tx.png" }, + { "url": "screenshot-swipe.png" }, + { "url": "screenshot-welcome.png" } + ], "type": "app", "tags": "emoji", "supports" : [ "BANGLEJS2" ], diff --git a/apps/emojuino/screenshot-swipe.png b/apps/emojuino/screenshot-swipe.png new file mode 100644 index 0000000000000000000000000000000000000000..a870724b94ca0317d2ffc6dde92f1d0e0606e168 GIT binary patch literal 2097 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 O=0HpoEm9E9F8&kk4>$4v literal 0 HcmV?d00001 diff --git a/apps/emojuino/screenshot-welcome.png b/apps/emojuino/screenshot-welcome.png new file mode 100644 index 0000000000000000000000000000000000000000..4cf1fecdf5930581842c6d548bb15d2405e92705 GIT binary patch literal 2697 zcmcJR`#;l*AICqRPn%|vA;sJ;k?lC-vXr|vCZdLsl@z&cBKc-+`M4Ewcj%B7W=`a? z%8g&OGLhhU2<%L}yG4|85^a$^emo1QG+f?=O zk|bAm7;q~r&hgr33wzyuXtiMExm|x*Qk-i%(b@eW*?D@R7oFlNH=FfSGHHk!XWt|x`UK2lXX-BJ z-~a1G!l2|6aV)-uouWL%AS1sq>bne% z?m%PwQi4r0*Z`PQDCKbW7z`*A*-k1*R7BZHBe@g^F`|Jz`q!5J<5Y6K;w}xogdkYX z5rkCq_GERJ10W&Yq0k(S5t={n(~&@;xOS$c-N#{|=Q&b)|CUO+QlOIqAc`@v#qyDow-0Z&)~P{?Aiu4K%1-V*KHCFW{FB;tw?u zI#1FFq~hWB(#^;=$nQU5Q$>m&9r>Nv$M#rQ#SK}js~uF-iWp!@@ma^50GZE=u^0PK ziZFD2{;*8VQmPU-^5vs`u?)`}ck8J`a;SrAm95@d7 zPs>fqA46i83YXlbS5vlGVl$4YvBqowXY9>JL0#`A3?IGp8k) ziw}CTaJl1M`4OT;kV#~rSM%&s{Mun$8&c?7X+>!@an#1R)$=#QTK9NO`FPsZ{_O+= zAy*Ex;Bj2UJx8RHRcK+eI%kvm@;y$Rxv@k}D`H%y3U~Kv1N7pkrNFtI#Y|^fMIdNd zDQo;7Xzez3kTb*qHTK)}P`$kC)hE_5LdR;KzM8-i5-vh& zXGmg0N!|%SC3{f;Xu-`~#KS0_yzoP3x8@HbV-X8$Xa3A`8olF~YZDreU>*-diB&2j z#VV&Z7mvWxsH5=o%n{gYw(aB9ERw&}TWP$wEQ$;D3yOUA=yO$tgE;rp1&8#mYvGWH zqcfuiS<0RTKFQsX^*gRLDIqJ&z@%KSMT|H*4Y(ngTHU~ly##H3CGnB87GGXd!q8Ka zd%d9i7D`M^2Y+*BG%E-v4o~$yF)+Dxro(@5DQTo!tnuiDNQ*#yMJgbek&)ueAb*2| zV1^EC_UL?|DFHEZp*o~n#Otts;Ft zJeOzty3JavteXh?_$HIen)T3N@C~6kl0RBSi?X*RZK^yvu%G6CH%oAqt&iQU>NSQD zsy{VwzS9&x`JAy4Ha;`?@&vx-?Rm2@aw1~2q5u)l2(5JM@==-11Vn44vQPG2;fbWM zW1)4;+p*B1kG0`fNB2sNgV^YhFK)~Oigu1 zYg72#-BqlBLzs}=$dPC*M%Bm_C+KCn>B%;jhdV(_6z8lWi_B@D+m|Is>w*sxzlExv7aD2_eIu?qzgdQL@_zxDN_r!jH1O1_1c_X6Hw?9=}IBj|Ti-jMTStm#jai$+8+RSg|bi zKor-W4Ao)i$KAJjDS+?x8Co)jX>E^Y$3m`+ND8FEf1I{~JG>7!c7SNC!FL5F z71l@f*pta|qr_w__g@f9jNh!c>!MY_gwE$bc7kevck)VPJ=n@Di2Nc zY@@xK5O;N*a=!{FgASdo8JB4#e`qx8{~IOQWg672DA{f&*ewC-@(Gd2-m-p>3W7va zM2w)s=LjDavmHYYuC50%M zzll`R-#w3LSPF`P!Y^%IZ}VfrZ*K5`y@d)Rj!a0zW{*mNROf2K2xor_=Cg;OJhqHC z>xhM1)K2YUdLrkn0!J)(Hx^h3(_&+=*qhp+3}qa(VBP76s?2PkuPl=;=oh><;Xv$x z@vfH9xE*TQq}b*?CCvH&rd58mKvLDM{uI3q(aLmtTomCE{KaJ%TK(Kv?5+RL8`m-4 z(y5hYV?vd>(;&+&iW0l4!ne#F7a^(Z(~%dACs)$Kjb5a(?+(>c`n}XQj9gr-dBocNKC?n`%bp1>$W>ugE@Y_NjX=4u|G*YTf9kGt4m%<~BkaS?R0SxNog)gkItF*q=YCO z{$^FsS^6~!&gXDEpIYJ!+rmi&DU7{U3f)q5K{mdQ@oWX}dn;E~)M@L5l}h*COyiI! zF^==Tf7cdwotTX(f)LE2H=>}-EoRn5wH*PW_9G$RO-LYQNunM)#Q{KHj_J>mN&sYO e;cqrjgog9!_phGE_7-gYmVgt Date: Mon, 22 Nov 2021 20:23:17 -0500 Subject: [PATCH 0826/1062] Version bump --- apps.json | 2 +- apps/emojuino/ChangeLog | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 886afeea2..b67d42398 100644 --- a/apps.json +++ b/apps.json @@ -4240,7 +4240,7 @@ "id": "emojuino", "name": "Emojuino", "shortName": "Emojuino", - "version": "0.01", + "version": "0.02", "description": "Emojis & Espruino: broadcast Unicode emojis via Bluetooth Low Energy.", "icon": "emojuino.png", "screenshots": [ diff --git a/apps/emojuino/ChangeLog b/apps/emojuino/ChangeLog index 5560f00bc..1c99f1970 100644 --- a/apps/emojuino/ChangeLog +++ b/apps/emojuino/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Upgraded text to images, added welcome screen and subtitles. From 0664a44c454107d4e3ff039d12baffea3e90cf5f Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 22 Nov 2021 18:52:43 -0800 Subject: [PATCH 0827/1062] Update apps.json --- apps.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apps.json b/apps.json index 8d90a1d1a..0b58abd97 100644 --- a/apps.json +++ b/apps.json @@ -4348,5 +4348,24 @@ {"name":"authentiwatch.img","url":"app-icon.js","evaluate":true} ], "data": [{"name":"authentiwatch.json"}] + }, + { "id": "schoolCalendar", + "name": "School Calendar", + "shortName":"SCalendar", + "icon": "CalenderLogo.png", + "version": "0.01", + "description": "A simple calendar that you can see your upcoming events that you create in the customizer. Keep in note that your events reapeat weekly.(Beta)", + "tags": "tool", + "readme":"README.md", + "custom":"custom.html", + "supports": ["BANGLEJS"], + "screenshots": [{"url":"screenshot_basic.png"},{"url":"screenshot_info.png"}], + "storage": [ + {"name":"schoolCalendar.app.js"}, + {"name":"schoolCalendar.img","url":"app-icon.js","evaluate":true} + ], + "data": [ + {"name":"app.json"} + ] } ] From be083113316ec7ba07d780b6593cb9adfd748298 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Mon, 22 Nov 2021 19:11:40 -0800 Subject: [PATCH 0828/1062] Update custom.html --- apps/schoolCalendar/custom.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 225049b28..6017b4396 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -93,9 +93,7 @@ var app = ` require("Font8x12").add(Graphics); require("Font7x11Numeric7Seg", 2).add(Graphics); - var file = require("Storage").open("calendarItems.csv","w"); - let nIntervId; function redrawScreen() { layout.render(layout.background); @@ -175,12 +173,15 @@ function updateDay(ffunction,day){ } } function getScheduleTable() { - let schedule = ${JSON.stringify(schedule)}; + let schedule = [//Monday: + {cn: "Biblical Theology", dow:1, sh: 8, sm: 10, eh:9, em: 5, r:"207", t:"Mr. Besaw"}, + {cn: "English", dow:1, sh: 9, sm: 5, eh:10, em: 0, t:"Dr. Wong"}, + {cn: "Break", dow:1, sh: 10, sm: 0, eh:10, em: 10, t:""}, + {cn: "MS Robotics", dow:1, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}];//${JSON.stringify(schedule)}; + logDebug(JSON.stringify(schedule)); return schedule; } -file.write(JSON.stringify(schedule)); - function findNextScheduleIndex() { var schedule = getScheduleTable(); var currentDate = new Date(); @@ -403,8 +404,8 @@ layout.update(); layout.render(layout.loading); layout.render(layout.background); layout.render(layout.buttons); - draw(); +file.write(JSON.stringify(schedule)); `; // send finished app (in addition to contents of app.json) sendCustomizedApp({ From a0cc4c633e0e6853a0f170a5358b7e1f11e310af Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 08:10:00 +0100 Subject: [PATCH 0829/1062] Minor changes --- apps/lcars/bg_small.png | Bin 1685 -> 5973 bytes apps/lcars/lcars.app.js | 2 +- apps/lcars/screenshot.png | Bin 2743 -> 24460 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/lcars/bg_small.png b/apps/lcars/bg_small.png index 360a47ea0446c608a09cc02dd6445dab9ec5ac88..514e2f17d795aa5084f84c4249e219f3c4b54510 100644 GIT binary patch literal 5973 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 diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 366b88018..22a6780d8 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -7,7 +7,7 @@ var alarm = -1; var backgroundImage = { width : 176, height : 151, bpp : 3, transparent : 2, - buffer : require("heatshrink").decompress(atob("gFx48cATgiCC6fAIBGDx048YCcEYXnz15ASCCJQDqDEgM8+fPASCDJQDqD/Qf6DrBpIAzMoXgIPqD/Qf6DGIHqD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf4ASsOGjFgQftNmnTpCD9tOmzSD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6DqAGaDNAGaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qc9x48cAQkAgMHBAoCQ4AlCjFhwwCGQaH3799AQl4BQM27dt2wCT2AmCiZ6CARCDLgJrJBYOnz1584CT8AoCjR6CARCDLh6AFQd1AIJTvKQdOYIBUDQAyDhAC6AIQcAAXy6D/Qf4ACQA6D/Qf6D/Qf6D/Qf6D/Qf6DLwANIkGChACVQbweaAESDCUTYAifwaD/vpB+Qf6D/Qf4A/AH4A/AH4ANyVJkBB+jlx46D/pMkQf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6DjwCD/48cQf8kyVIkGChACeFAMo0WKAQ+IDRiDDAUPEgEBDrCDDAUKDCnv3799ASd8Qf6D/Qf6D/Qf6D/Qf4CN")) + buffer : require("heatshrink").decompress(atob("AEcEiFBASFADpETps06YCcEYXnz15ASBBJzVp0wCdEYU8+fPASHAIJCAdQf6DpoUIkGCATCDWIBCDDzANJAGaDC6BB9QYWAQf804CD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/QbhA9Qf6D2oMEiFAQfoyB6ZBKQeYyBzVgQf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6Dnz1586D+AGaDNAGaD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qc8Bg8euPv337ASHwDQNz588+YCC5AlCjR6CARCDN+/fvoCV6AbBj158+eAQV4EwUTPQQCIQZcB859SQdkPQCyDpvaAWQbmYIBUDQC6DQAC8eQC6DQAC8XQf6D/AASAYQdCAYQf6DHAESDeAESD/Qf6D/Qf6DK+2LtmXQfu8FEiDb8BB9QYRA9QYZB+Qf6D/AAccuPHIPwA/AH4A/AH4AHyVJkhB+zVp0yD/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6D/Qf6DlwCD/02QQf8kyVIkGChACeFAMo0WKAQOIC56/CQYQCi0kAgMv33794CUQYYChQYU9+/fvoCTviD/Qf6D/Qf6D/Qf6DLjSD/94=")) } var iconPlanet = { diff --git a/apps/lcars/screenshot.png b/apps/lcars/screenshot.png index 7670e4f5cacdb7177356af31bd96d1b02c3712ac..03b229b95199877f9668493005e6786dc86be114 100644 GIT binary patch literal 24460 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 literal 2743 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 T6DceCM5Ty!&bAdce!u+}jJ7Ln From 6d8d98fb03e7bed42c3feeee771ddfcd78b60ea8 Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 08:21:41 +0100 Subject: [PATCH 0830/1062] Show HRM --- apps/lcars/lcars.app.js | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 22a6780d8..314b8d65f 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -3,6 +3,7 @@ */ const locale = require('locale'); var alarm = -1; +var hrmStr = "-" var backgroundImage = { width : 176, height : 151, bpp : 3, @@ -104,12 +105,6 @@ function draw(queue){ g.drawString(day, 133, 37); g.drawString(dayName, 133, 57); - // Alarm - g.setFontAlign(-1,-1,0); - g.drawString("TEMP:", 20, 104); - var tempText = E.getTemperature() + "C"; - g.drawString(tempText, 60, 104); - // Alarm within symbol if(alarm > 0){ g.setFontAlign(0,0,0); @@ -117,16 +112,22 @@ function draw(queue){ g.setFontAlign(-1,-1,0); } - // Draw battery - var bat = E.getBattery(); - var charging = Bangle.isCharging() ? "*" : ""; - g.drawString("BAT:", 20, 124); - g.drawString(charging + bat+ "%", 60, 124); + // HRM + g.setFontAlign(-1,-1,0); + g.drawString("HRM:", 20, 104); + g.drawString(hrmStr, 60, 104); // Draw steps var steps = getSteps(); - g.drawString("STEP:", 20, 144); - g.drawString(steps, 60, 144); + g.drawString("STEP:", 20, 124); + g.drawString(steps, 60, 124); + + // Draw battery + var bat = E.getBattery(); + var charging = Bangle.isCharging() ? "*" : ""; + g.drawString("BAT:", 20, 144); + g.drawString(charging + bat+ "%", 60, 144); + // Queue draw in one minute if(queue){ @@ -213,6 +214,18 @@ Bangle.on('swipe',function(dir) { }); +/* + * Measure heart rate + */ +Bangle.on('HRM', function(hrm) { + hrmStr = hrm.bpm; + + if(hrm.confidence < 50){ + hrmStr = "~" + hrmStr; + } +}); + + /* * Stop updates when LCD is off, restart when on */ From 7d95221e166770be91bf76374f926fe996356009 Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 08:24:15 +0100 Subject: [PATCH 0831/1062] Minor change --- apps/lcars/lcars.app.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 314b8d65f..2f488c154 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -219,10 +219,6 @@ Bangle.on('swipe',function(dir) { */ Bangle.on('HRM', function(hrm) { hrmStr = hrm.bpm; - - if(hrm.confidence < 50){ - hrmStr = "~" + hrmStr; - } }); From 1fb861b82c4cc75b94dc6875280fbd411390bc71 Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 08:25:11 +0100 Subject: [PATCH 0832/1062] HRM confidence of 80 to display --- apps/lcars/lcars.app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 2f488c154..ac855608b 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -218,7 +218,9 @@ Bangle.on('swipe',function(dir) { * Measure heart rate */ Bangle.on('HRM', function(hrm) { - hrmStr = hrm.bpm; + if(hrm.confidence > 80){ + hrmStr = hrm.bpm; + } }); From 7f3f7b9eee3c23c922645274dd8d7947def8158d Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 08:26:37 +0100 Subject: [PATCH 0833/1062] Currently, no confidence > 80 is reached... --- apps/lcars/lcars.app.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index ac855608b..2f488c154 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -218,9 +218,7 @@ Bangle.on('swipe',function(dir) { * Measure heart rate */ Bangle.on('HRM', function(hrm) { - if(hrm.confidence > 80){ - hrmStr = hrm.bpm; - } + hrmStr = hrm.bpm; }); From 930cfa13315046c621a5444632f7b12bbd956fae Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 09:43:24 +0100 Subject: [PATCH 0834/1062] New HRM Icon --- apps/lcars/icon_hrm.png | Bin 1277 -> 1294 bytes apps/lcars/lcars.app.js | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/lcars/icon_hrm.png b/apps/lcars/icon_hrm.png index 32c0a63d0a9e6129b5934960f49da244ad8110a8..38e50f83765c51ec198a1a627f818d71040d3c8b 100644 GIT binary patch delta 1258 zcmV%idjJ9}Hlc{U!Iq%$%8X{&yaC<_-`gN|Y#3qJKmV0YGs@bt)UKGJw`H zLsRV}A!Uhs!j&?ZrAeSRXnM`@LluLxkv-NC+$ao&N1Zs|ddZt6 z!SMkTyRcvbR(~v8s?@P+I=lNULI6Qx(V|gIR7kimGtBlYFn+!WHayDPSN`#v3D3zo zc#hWs07U+J#J+hLWV0!2e?fXg_H@J3aC|D=_6v-rMx{O$M2P3p6M)-o$#Wn32s|{X z%*VV7@pSzIqv`9wf}2Bd*L|qW&%6ui`M3`3a!p%iG=HCldF8SaKVb=BDPi>Y!RYPw zdwRfz5n%7>>crq|bAcJ|58eWsX4rxI6Y{EM^!MR<q{gxn+jU!0mz1OOPlJ-BgjKcv$M zV0>;2oPV+QhPG)l?7R1Z?pa~Uch?^Q4-JG9cL1ReEe>wt{`)U3 z_Hd!4+-N=x^Y_bOW-y2F*8B*0j%cZA$gXdIIe*Y^nVg=kPIQ#-o_hPtxbfPnfKM=F zw?i)}ok|nIURDn0Pc49R)LU+S7tTzd!xR+;dPvm34P}a)_SfnU~s_u@IXJzAHK8f3X}+$iS%c7!Es;iv}L4e zmVe&xMc=vul$9N{eI5+H5GWBsnuetZ4uN!gKv~LTTLOB+TTj;!v+n@lh?}|b_ylNM zii5_WZ1)>`?@DAoUxt4tRxJP^O-F|R@bSZzy9#k9zk=+z7BD0s1VmOgC@DFpf2G^_ zw5LlkeDMNC+kOQAq?hc3epm2gGbN|MmVdu)PQH~2Ax%Tp`!%@L`Xg+KiEyl1qq6x~ zQGT!@T}NWUM%B#*C32beEgmswXMUTxUl7AdA zF%S9XC8ebrz85{WMx5uRz|fMknVA2WObdp)ymad| zA`1VEJ3+)ks*fIxGe^vCiRgh~wqyQ6rbDv4z%lZX%I({ok;D`6(Bd(3TPPwz4ur&m z!bOiqTtCu^B*jRib!UR5&42F^G7$^8|L)XA29AMw&bKy_rAta#yUbyH6KNa&0j}4w U{8ySjG5`Po07*qoM6N<$f~a97L_t(&f$i8^h+Wkg2k_sV8Ao%m(Zp7~Hj1`Nl@_Xa84=M3 z2}mUoRH7)=2YoT6_#i4Jg~n2d#`eYAi{K>>Ua`_bskAC0BqpLUFs$Z-x_sZa7F~4FMSmAv9H!_=_;sCSm^;&C z4jBozrwk^H{+GLEnok)j-|)XDFi>GZFSGl(499*4ygG}(ohkJ|2!xbI!t$BsRfLIF zq<56L6^@7+_w`2KhZ9PLY~yTPp0BUdJ5j$5pueN`8(h=gQgKPxSmlzA4HO~eu`yQH zc`7h4ZG(a1Q-3}#5)xJZ7it%g*AI$0%IFA&#nWM*BPEt0v9tvnMMZMZ(`@ zajHVkX3knZ-MIA`%c7YHTe+k?TY)gdEjlP>kP8#G6s-RF(=wbg+&1HiN=jly*xPc4^V;n;GXD$yl{+U#7unp+^#y_S1+Ad-EI}<#Ul2zB4pmKy@=PGCWKNA|$|fXX zU4Nc)5#LhmW}wPPkB*Vr;tkY$IGAQ=b`@uMjb zFF^k;{xE@%<6_cVvtT;NLnWk|d%0ghtk}cS249wJr-N2ts(F9?xXc|x((3uJ;3JM4WO;se>%54*3C3EJ?=!9ZD@MW{DWj3`=OE?Llq3>)U)-<3qLjVu|Kseu zpmhr^#nmi~w`x{8lH)=D7^@?~&!e}&laW{Xyv7Z=ew8iUJeKYIAy!Xju-JLjS${vy znVk}HfJfQK-(Kgc2Jc2fUQm4gez29B11lC!((}|<)s7d#9li%0AY_~`_VYn*`SV%9 zA!8$R12<;nv`*Ots~8w5xkIGv?CA1;%jipZt4PQ=>t~wWlu+av1K~P;GSl3qgc2d8 zsr_M#w^^Dn7K`|jmFo`EC)J72A5uRN1;TDFnkh2Vd1I~f|Lbp)J@ctfpVAB<<@wOM z6#eG|7Wo!lAiom~aZF&vhb)%8wsg@&7hQDmy~6(h4QpUwCsj{d00000NkvXXu0mjf DO+Ilo diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 2f488c154..993d201c0 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -23,10 +23,10 @@ var iconGps = { buffer : require("heatshrink").decompress(atob("pMkyQCFpH0BAwCJv/6CJ8l589CJ0kyf//wIDpVEChM8+/fBAdZ8QRIp++///0gIBlMkxI4IuZKB+/SKAPHzpKJ/YkB//pKAP2BYeXhIFDx88+fPvqYBnibEkmUAofv34lC/RQBBYdcmPCXIYjBEwPfvnzJoILBQoUlHAUuJQYmCDodw48cuBKGTA0WEYIEBJQ6YEQwMMuImBJQyYEkmZFAVkyVSJQ6YCyUcmPDjgmBTAJKETAlJiS4ETANPJQpxCJQtxTALgBEwnfvohBI4NZkmWpNlcAgAD/wzBEYaYCy8cJQiYEyIjCTAWS3wlGTAVIEwkerJKFTAkmOIclToK8GAAIPBIgImCufHyxxG59pEIS8DvfypMr968HEwOHEwfx8+cEYkpCIeSoiYByVf/uSkmTEQP7ZIiYDnl5AQNwBYgCGyOn38k2+2pIRKyVeuPPj1x4ccCJVKSgP/5cJA4NSExMps+cSoMMKAIVCCg7SBpd7TANZkmUHBMevPnjlwcwXCCJFEzYDBA4WWKIIRHpEw4+eNwUxEwKYIkVJk1IyIKFHA+DR4VcJQYCBJRBoCkxHBAgNkyyYKkmXEYaYMAQMSEYKYNAQOHEwnSfBYjBAgVaCJdJJSMkTAK8KYQyVKAQ4jBNxiYEcBCYJXIkgA=")) } -var iconHrm = { +var iconHrm = { width : 50, height : 50, bpp : 3, transparent : 1, - buffer : require("heatshrink").decompress(atob("kmSpIC/AX4CVmUSCJ8mpMJEx+Zkg4QsmSHB9MyVILiFJkILGyZBGLgMkwgvHyBcHJRBBHsmGJQ4vBIIpcBjKDH5JBGyVCshBGLgILBXI0yBAWTGQQvBYoq5C5IICFIeSoZBEFgOEAQQ+DF4UMPQaADF4IIBNYfJkMypA7CCIKABkyDCNYQsCzIUBLwZHBBAJrEJoT+BCIJ9EBYRrDHwQNCYoxZBNYRBDdg6YCJoImCHwjsFBAJrCEASqDoDsFBAMTNwZWCNwK8EBAVINAKbEZAb+GkK5DTYTUEJQuGF4WTGQZcGSQYvHAQ6MIARL4DCJrXCLgI4OfAY4O5JcPQYRcPQYRcPZwJcQkmYCKAC/AXuQ")) + buffer : require("heatshrink").decompress(atob("kmSpIC/AX4CT+PHjlxARfBkmGjFhAR2REZwCC7AjPAQIjQ48dw0//4ANsOB49/CJv8JQNjEh32JQN3BY/5AwpKLkhKQ8+eBIhKK/jZBJR/+vPnJR/JkmTJR3xJQN5JRPypMkz5uByfJk5KI/zXCFQMev/nC4JKIkhrBn4pB/+Sp5KJfwnnOIqVHSQS5CFgaVIDQPHj4FBOIJNCSo/9EAI/CFIJNCSo/njiSC/KYDcBH6IgQAFcBHx44RGcBYAHcBIAHJRAAJJRAAJJSrdEARfYsOGjACOngjP48EyQdHx04BAtkyTnCAQYsCDoILGAQ2OnfvCJ2TIgNwCJuSpHj335CJnxNYvBChU48ZKC3378gRJp6SGiQ4JkaSBJQP7EwIOEyA")) } var iconCompass = { From b128f5c15569de9e2be0717f270cfd90e006d5ab Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 10:38:02 +0100 Subject: [PATCH 0835/1062] Update apps.json added background image to upload --- apps.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps.json b/apps.json index 46c0e6045..b484b7a9a 100644 --- a/apps.json +++ b/apps.json @@ -4308,6 +4308,7 @@ "storage": [ {"name":"binwatch.app.js","url":"app.js"}, {"name":"binwatch.img","url":"app-icon.js","evaluate":true} + {"name":"Background176_center.png","url":"Background176_center.png","evaluate":true} ] } ] From a03513812956c629f003824b46bb6763734eead8 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 23 Nov 2021 09:44:24 +0000 Subject: [PATCH 0836/1062] fix download icon on android --- css/main.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/css/main.css b/css/main.css index 82f6fbcfe..a27498397 100644 --- a/css/main.css +++ b/css/main.css @@ -64,7 +64,9 @@ .icon.icon-favourite-active::before { content: "\02665"; /* 0x2665 = solid heart; 0x2605 = solid star */ } -.icon.icon-interface {text-indent: 0px;font-size: 130%;vertical-align: -30%;} /*override spectre*/ +.icon.icon-interface {text-indent: 0px;} /*override spectre*/ .icon.icon-interface::before { - content: "\01F5AB"; + position: absolute; left: 50%; top: 70%; + transform: translate(-50%,-50%); + content: url("data:image/svg+xml,%3C%3Fxml version='1.0'%3F%3E%3Csvg fill='rgb(87, 85, 217)' xmlns='http://www.w3.org/2000/svg' viewBox='2 2 28 28' width='1.5em' height='1.5em'%3E%3Cpath d='M 6 4 C 4.895 4 4 4.895 4 6 L 4 24 C 4 25.105 4.895 26 6 26 L 24 26 C 25.105 26 26 25.105 26 24 L 26 8 L 22 4 L 20 4 L 20 10 C 20 10.552 19.552 11 19 11 L 10 11 C 9.448 11 9 10.552 9 10 L 9 4 L 6 4 z M 16 4 L 16 9 L 18 9 L 18 4 L 16 4 z M 10 16 L 20 16 C 21.105 16 22 16.895 22 18 L 22 24 L 8 24 L 8 18 C 8 16.895 8.895 16 10 16 z'/%3E%3C/svg%3E"); } From ff3223e1b906957811cad815df88ad495e70a95f Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 10:51:02 +0100 Subject: [PATCH 0837/1062] Update app.js added "require" for bg image --- apps/binwatch/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/binwatch/app.js b/apps/binwatch/app.js index 56e153dbf..7967fd5ed 100644 --- a/apps/binwatch/app.js +++ b/apps/binwatch/app.js @@ -11,7 +11,7 @@ /* reuqirements */ require("Font7x11Numeric7Seg").add(Graphics); require("Font5x7Numeric7Seg").add(Graphics); - +require("Background176_center.png"); /* constants and definitions */ From 3f4c49a8aef6ba79419f00d86fc4f02435b72e21 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 10:55:07 +0100 Subject: [PATCH 0838/1062] Update app.js integrated BG image into app --- apps/binwatch/app.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/binwatch/app.js b/apps/binwatch/app.js index 7967fd5ed..57f795c3b 100644 --- a/apps/binwatch/app.js +++ b/apps/binwatch/app.js @@ -11,7 +11,6 @@ /* reuqirements */ require("Font7x11Numeric7Seg").add(Graphics); require("Font5x7Numeric7Seg").add(Graphics); -require("Background176_center.png"); /* constants and definitions */ @@ -37,6 +36,11 @@ var month = [ /* DEZ */ {width : 51, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("n/wh//w//xP/gV/8F//Of4Fn/EH/04gUODAUHgHh4AFBnHgjk4BYUcgeHAoMB8eAuHgAwN4uEOjgFBh4jB4eAgED4ADBl/4gFwB4MD/4DBgQCB3/gC4PghgyBgPAGQl4gYyDjwgBGQQrBh0BGQVwDQM4F4MMLIJlEg3/gOfPAPgn/gk/+j/+h/8IoPh//gA="))} ]; +var img176 = { + width : 176, height : 176, bpp : 3, + transparent : 1, + buffer : require("heatshrink").decompress(atob("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")) +} var imgSquid = {width : 88, height : 26, bpp : 1, buffer : require("heatshrink").decompress(atob("gE/AYUYgEH////0B//gBQM8BQgDB/AKHh/A/gKBvwKBAgMOj8AnwKHBAIMBgH/BQgmCAoPnBQl4AoOAgPnwAKDuEAgYKB4YKIgfD4AKDMAMB4EDwIKIg+B8AKIgAKIh8A+AKHh0AuAKHj0AvBMG4EcgE4K458Bnh4HnEAjiOHBwMeBQpKBEgMOXQ/wBwIKDaAZQBg4KDcwT0BAAOHfgoKHgE/wDaBAAL8DA="))}; var imgNoBT = {width : 20, height : 20, bpp : 3, transparent : 0, buffer : require("heatshrink").decompress(atob("///8mSpM/AoP/yUT/8yuYGB5AMB/1MyYUBkmT/P85MP+USBwOT8mQ/8JBwXyoVnyGSv8//Mhk14pMn//8BYNMwmSp/+pFJkgyBDoMkkgODpOSuQOE5M/KgIOCsmfz/JknPhMyof5n+Ss/wzMhn4OBk1+smQLoWTn/mHAM/+VJz4KBwhZBEYJ/CkM8yZVBAAQxBCgP/A="))}; @@ -286,7 +290,7 @@ function setRuntimeValues(resolution) { screen_size_x = V1_SCREEN_SIZE_X; screen_size_y = V1_SCREEN_SIZE_Y; - backgroundImage = V1_BACKGROUND_IMAGE; + backgroundImage = img176; bat_pos_x = V1_BAT_POS_X; bat_pos_y = V1_BAT_POS_Y; From 336501059da7bceddba9e300c5b6d5bd1aba6a3a Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 10:56:25 +0100 Subject: [PATCH 0839/1062] Update apps.json removed background image --- apps.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps.json b/apps.json index b484b7a9a..46c0e6045 100644 --- a/apps.json +++ b/apps.json @@ -4308,7 +4308,6 @@ "storage": [ {"name":"binwatch.app.js","url":"app.js"}, {"name":"binwatch.img","url":"app-icon.js","evaluate":true} - {"name":"Background176_center.png","url":"Background176_center.png","evaluate":true} ] } ] From 5c3b0468d4ce9a51249f6f098c3e6ba84ba1cf41 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 10:59:26 +0100 Subject: [PATCH 0840/1062] Update apps.json corrected format --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 46c0e6045..6e32e220a 100644 --- a/apps.json +++ b/apps.json @@ -4305,7 +4305,7 @@ "description": "Famous binary watch", "tags": "clock", "type": "clock", - "storage": [ + "storage": [ {"name":"binwatch.app.js","url":"app.js"}, {"name":"binwatch.img","url":"app-icon.js","evaluate":true} ] From 36edd449c5398147536d20c2b4f11040cb3105b9 Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 11:10:33 +0100 Subject: [PATCH 0841/1062] Minor change --- apps/lcars/lcars.app.js | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 993d201c0..1f4e12959 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -3,7 +3,6 @@ */ const locale = require('locale'); var alarm = -1; -var hrmStr = "-" var backgroundImage = { width : 176, height : 151, bpp : 3, @@ -112,22 +111,21 @@ function draw(queue){ g.setFontAlign(-1,-1,0); } - // HRM + // Temperature g.setFontAlign(-1,-1,0); - g.drawString("HRM:", 20, 104); - g.drawString(hrmStr, 60, 104); - - // Draw steps - var steps = getSteps(); - g.drawString("STEP:", 20, 124); - g.drawString(steps, 60, 124); + g.drawString("Temp:", 20, 104); + g.drawString(E.getTemperature() + "C", 60, 104); // Draw battery var bat = E.getBattery(); var charging = Bangle.isCharging() ? "*" : ""; - g.drawString("BAT:", 20, 144); - g.drawString(charging + bat+ "%", 60, 144); + g.drawString("BAT:", 20, 124); + g.drawString(charging + bat+ "%", 60, 124); + // Draw steps + var steps = getSteps(); + g.drawString("STEP:", 20, 144); + g.drawString(steps, 60, 144); // Queue draw in one minute if(queue){ @@ -214,14 +212,6 @@ Bangle.on('swipe',function(dir) { }); -/* - * Measure heart rate - */ -Bangle.on('HRM', function(hrm) { - hrmStr = hrm.bpm; -}); - - /* * Stop updates when LCD is off, restart when on */ From 2e18795c5780148c6f52d50cd69849b5893b1cdb Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 11:11:33 +0100 Subject: [PATCH 0842/1062] Fixed string --- apps/lcars/lcars.app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/lcars/lcars.app.js b/apps/lcars/lcars.app.js index 1f4e12959..1197dc1d6 100644 --- a/apps/lcars/lcars.app.js +++ b/apps/lcars/lcars.app.js @@ -113,7 +113,7 @@ function draw(queue){ // Temperature g.setFontAlign(-1,-1,0); - g.drawString("Temp:", 20, 104); + g.drawString("TEMP:", 20, 104); g.drawString(E.getTemperature() + "C", 60, 104); // Draw battery From 1ccabca0c6f1cf79c1b5d6a56749ab752a4d1850 Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 11:18:50 +0100 Subject: [PATCH 0843/1062] No hrm icon - its shown too much. --- apps/lcars/icon_hrm.png | Bin 1294 -> 0 bytes apps/lcars/lcars.app.js | 41 ++++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 20 deletions(-) delete mode 100644 apps/lcars/icon_hrm.png diff --git a/apps/lcars/icon_hrm.png b/apps/lcars/icon_hrm.png deleted file mode 100644 index 38e50f83765c51ec198a1a627f818d71040d3c8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1294 zcmV+p1@ZccP)uP(`rH+KWH?*Qe!Oyn%YLH@kL`HO-+rl#7Z=lq*l_HhH6FRO(`t9d+!`S z0L$K8U>^)%n*AmB!_1tSbN+W8cjgWdB}$YiQKCc+0YGs@bt)UKGJw`HLsRV}A!Uhs z!j&?ZrAeSRXnM`@LluLxkv-NC+$ao&N1Zs|ddZt6!SMkTyRcvb zRxDep)Uj$hyZbCc06}8WqESp#NVqUF%=RlVe!d7cJj&Zw{_&d$&&fJ?j@JSJME-ij zzIhmAvngwTL3%{?bi>ndd@9}c3yh{lr9Ku!i09K2fZJ`!b07N%JT$1x$Gi*ibo~RP z>FdCPn?rEdeW=XOybJ01xDM=cO0-I*of%_Bks%7-|;d&i$#>Tu0S^s?6L$ci4=oOE;{N+DbRRto0O&nYH{KhQ=sNV0XF-~dn7vij zio-YIt~(NPDAN(LkkluiLhQ<%pmu$qH(==OnQ3~X2qN>&ai?q6!}f5YrQB#f4fFTQ zU}i9f@YehYd5&nQX~?c`fH}}_nVg=kPIQ#-o_hPtxbfPnfKM=Fw?i)}ok|nIURDn0 zPc49R)LU+S7tTzd!xR+ z;dPvm34P}a)_SfnU~s_u@IXJzAHK8f3X}+$iS%c7!Es;iv}L4emfrA1-?{^ol^wKw z9t^$^C=o)MhNTA%fpmL7S;}Ku0(!$+PuCH%?*QP4o4N7$1ZZ1|gT|n2_ZxffN@PA? zhJPnkEdU@*M~46K@xzw83UMdDg6y~!FeD)aL{>H^DLJTrrQ7(lr%N$>@d8HMegy!e zm+XXoSMXyqC8xlaziv*xl?owEL)QB>xYhb2Y>A0*tXiY8`B_nZup(VYV!=k$%?2fU zX8*qtFtpCKv?3*G_OEETDE~n>GU}HjS!0IN_iy95mR6-sf%*N34FD`@L=f@WdGoM9 zFriu41XnQQ&&3WVQY6dkY?D>qiyrB5zet32nBzr{%_fo@F)@j zrR3qc_>nA45H1;ptoh`P{R7GeZuNV$t0fz4zImzXnU*;s4Kr`wuHxTR`n_$I-`hsW zoQ4)N)4{_vIgrA;e`m-XX8D<~UBJ+iw3(Rym`n?XyS#MkH6jZCj5|TZLaL7*jWb8g zZ;9xEV7C53rbDv4z%lZX%I({ok;D`6(Bd(3TPPwz4ur&m!bOiqTtCu^B*jRib!UR5 z&42F^G7$^8|L)XA29AMw&bKy_rAta#yUbyH6KNa&0j}4w{8ySjG5`Po07*qoM6N<$ Eg3= 0 ? iconAlarm : - Bangle.isGPSOn() ? iconGps : - Bangle.isHRMOn() ? iconHrm : - Bangle.isCompassOn() ? iconCompass : - iconPlanet; + alarm >= 0 ? iconAlarm : + Bangle.isGPSOn() ? iconGps : + Bangle.isCompassOn() ? iconCompass : + iconPlanet; g.drawImage(iconImg, 115, 105); // Write time @@ -113,19 +107,19 @@ function draw(queue){ // Temperature g.setFontAlign(-1,-1,0); - g.drawString("TEMP:", 20, 104); - g.drawString(E.getTemperature() + "C", 60, 104); + g.drawString("HRM:", 20, 104); + g.drawString(hrmValue, 60, 104); + + // Draw steps + var steps = getSteps(); + g.drawString("STEP:", 20, 124); + g.drawString(steps, 60, 124); // Draw battery var bat = E.getBattery(); var charging = Bangle.isCharging() ? "*" : ""; - g.drawString("BAT:", 20, 124); - g.drawString(charging + bat+ "%", 60, 124); - - // Draw steps - var steps = getSteps(); - g.drawString("STEP:", 20, 144); - g.drawString(steps, 60, 144); + g.drawString("BAT:", 20, 144); + g.drawString(charging + bat+ "%", 60, 144); // Queue draw in one minute if(queue){ @@ -151,6 +145,13 @@ function stepsWidget() { return undefined; } +/* + * HRM + */ +Bangle.on('HRM',function(hrm) { + hrmValue = hrm.bpm; +}); + /* * Handle alarm */ From 9600f16671ab5a9fc772a959a7110622baa5e606 Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 11:19:48 +0100 Subject: [PATCH 0844/1062] Updated changelog --- apps/lcars/ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index c8f9a262f..0b86c5282 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -1,3 +1,4 @@ 0.01: Launch app 0.02: Swipe left/right to set an alarm. 0.03: New design with different icons if gps, hrm or compass is on. +0.04: Removed HRM Icon s its shown too much. \ No newline at end of file From 3ba779ee5826a60f127ed12f0eeaa2a848f53b4c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 23 Nov 2021 11:48:34 +0000 Subject: [PATCH 0845/1062] Fix bangleapps after recent PRs --- .eslintignore | 2 ++ apps.json | 2 +- apps/schoolCalendar/boot.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.eslintignore b/.eslintignore index 57fedb0da..e657b6260 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,4 @@ apps/animclk/V29.LBM.js apps/banglerun/rollup.config.js +apps/schoolCalendar/fullcalendar/main.js +apps/authentiwatch/qr_packed.js diff --git a/apps.json b/apps.json index d7d0f5257..1eaece8a4 100644 --- a/apps.json +++ b/apps.json @@ -4262,7 +4262,7 @@ "id": "cliclockJS2Enhanced", "name": "Commandline-Clock JS2 Enhanced", "shortName": "CLI-Clock JS2", - "version": "0.2", + "version": "0.02", "description": "Simple CLI-Styled Clock with enhancements. Modes that are hard to use and unneded are removed (BPM, battery info, memory ect) credit to hughbarney for the original code and design. Also added HID media controlls, just swipe on the clock face to controll the media! Gadgetbride support coming soon(hopefully) Thanks to t0m1o1 for media controls!", "icon": "app.png", "screenshots": [{"url":"screengrab.png"}], diff --git a/apps/schoolCalendar/boot.js b/apps/schoolCalendar/boot.js index e4f223c0c..cb22decb7 100644 --- a/apps/schoolCalendar/boot.js +++ b/apps/schoolCalendar/boot.js @@ -2,5 +2,5 @@ (function() { var alarms = require('Storage').readJSON('schoolCalendarAlarms.json',1)||[]; var time = new Date(); - E.showPrompt(School Calendar Alarm Test) + E.showPrompt("School Calendar Alarm Test"); })(); From e3310ffc32e9fab87bcafe96b43894dcecc60cc5 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Tue, 23 Nov 2021 13:13:17 +0000 Subject: [PATCH 0846/1062] Pastel: architect font module --- apps/pastel/f_architect.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/pastel/f_architect.js b/apps/pastel/f_architect.js index ce44bfcec..685b2fa03 100644 --- a/apps/pastel/f_architect.js +++ b/apps/pastel/f_architect.js @@ -1,10 +1,9 @@ - var widths = atob("CBolByEeJykkJCYhCg=="); var font = atob("AAAAAAAAAAAAAAAAYAAAAAAAADgAAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAD4AAAAAAAA/AAAAAAAAH4AAAAAAAB/AAAAAAAAf4AAAAAAAD+AAAAAAAA/wAAAAAAAH+AAAAAAAB/gAAAAAAAP8AAAAAAAD/AAAAAAAAf4AAAAAAAH+AAAAAAAA/gAAAAAAAP8AAAAAAAB/AAAAAAAAfwAAAAAAAH8AAAAAAAA/AAAAAAAAPwAAAAAAAB8AAAAAAAAfAAAAAAAADwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAP/8AAAAAAH//4AAAAAB///wAAAAAf/APgAAAAD/gAeAAAAA/wAA8AAAAH8AABwAAAA/AAAHgAAAHwAAAeAAAA+AAAA4AAADgAAADgAAAcAAAAOAAABwAAAA4AAAOAAAADgAAA4AAAAOAAADgAAAA4AAAOAAAADgAAA4AAAAOAAADgAAAB4AAAOAAAAHAAAA4AAAAcAAADwAAADwAAAHAAAAOAAAAeAAAB4AAAA4AAAPAAAADwAAB4AAAAHwAAPgAAAAPgAD8AAAAAf4D/gAAAAAf//4AAAAAAf/+AAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAP////4AAAB/////gAAAH////+AAAAf////gAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAADwAADAAAAAeAAAeAAAAD4AAD4AAAAfAAAfgAAAD4AAD+AAAAPAAAf4AAAB8AAH/AAAAHgAA/8AAAAcAAH/wAAADwAA/vAAAAOAAP48AAAA4AB/DgAAADgAf4OAAAAPAD+A4AAAA8A/wHgAAAD8/8AcAAAAH//gBwAAAAP/wAPAAAAAf8AA8AAAAAAAADgAAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAA+AAAAAAAAD4AAAAAAAAPAAAAAAAAA8AAAAAAAAHwAAAAAAAAfAAAAAAAAA4AAAAAAAABAAAAAAIAAAAAAAADwAAAAAAAAPAAAAAAAAA8AAAAAAAADgAAAAAAAAeAAAAAAAAB4AYAAAAAAHgBwAAAAAAeAPABAAAADwA8AGAAAAPAHgAYAAAA8AeADgAAADwDwAOAAAAOAPAB4AAAB4B8AHgAAAHgPwA8AAAAeA+ADwAAAB4H4AeAAAAHgfgD4AAAAeD+AfAAAAB4e4D8AAAAHj7gfgAAAAf/PH8AAAAB/4//gAAAAH/D/8AAAAAP4H/gAAAAA+Af8AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAADwAAAAAAAAfAAAAAAAAD8AAAAAAAA/wAAAAAAAH/AAAAAAAA/8AAAAAAAPxwAAAAAAB+HAAAAAAAPwcAAAAAAB+BwAAAAAAfwPAAAAAAD+A8AAAAAAfwDwAAAAAD+APAAAAAAPwA8AAAAAB+ADwAAAAAP/////AAAA/////8AAAB/////wAAAD/////AAAAD////8AAAAAAH8AAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAAeAAAAAAAAB4AAAAAAAAHgAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAGAAAAAAAAAAAAAAAAAAOAAAAAAAH/8AAAAAAf//wAAAAAD///AAAAAAP//8AAAAAA///wAAAAAAPgPAB4AAAA+A4APgAAAD4DgA+AAAAPAeAB4AAAA8BwAHgAAADwHAAeAAAAPAcAB4AAAB4BgAHgAAAHgGAAeAAAAeAYAD4AAAB4BgAPAAAAPgGAA8AAAA8AYADwAAADwBwAOAAAAPAHAB4AAAA8AcAHgAAAHwB4A8AAAAeAHgHgAAAB4APh+AAAAHgA//wAAAA+AB/+AAAADwAD/wAAAAPAAD8AAAAA8AAAAAAAAHwAAAAAAAAfAAAAAAAAB4AAAAAAAAHgAAAAAAAAeAAAAAAAAB4AAAAAAAAHAAAAAAAAAcAAAAAAAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAAAAH//AAAAAAB///AAAAAAP//+AAAAAD///8AAAAAf+B/4AAAAD/AA/wAAAA/wAA/gAAAD8AAB+AAAAfAAAD8AAAD4AAAPwAAAfAAAB/AAAB4AAAP+AAAPAAAB/4AAA8AAAP/gAAHgAAB++AAAeAAAPz4AABwAAB+PgAAHAAAPw+AAAcAAA+D4AABgAAHwPgAAAAAA/A+AAAAAAD4H4AAAAAAfAfAAAAAAB4D8AAAAAAPgPgAAAAAA8B+AAAAAADwPwAAAAAAPA+AAAAAAA8P4AAAAAAD//AAAAAAAP/4AAAAAAAf+AAAAAAAA/gAAAAAAAAAAAAAAAIAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAABwAAAAAAAAHAAAAAAAAAcAAAAAAAADwAAAAAAAAPAAAAAAAAA8AAAAAAAADwAAAAAAAAPAAAP4AAAA8AAP/gAAADwAH/+AAAAfAB//wAAAB8Af//AAAAHwH/4AAAAAfB/4AAAAAB8f8AAAAAAH//AAAAAAAf/wAAAAAAB/8AAAAAAAP/gAAAAAAA/4AAAAAAAD/AAAAAAAAPwAAAAAAAA+AAAAAAAADwAAAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAAH+AAAAAAAA/8AAAAAAAP/4AAAAAfB//gAAAAH/Pw/AAAAA//8A8AAAAH//gDwAAAA//8AHgAAAD4fwAeAAAAeA+AB4AAAB4DwADgAAAPAPAAOAAAA4A4AA4AAADgDgADgAAAOAOAAOAAABwAwAA4AAAHAHAADgAAAcAcAAOAAABwBwAA4AAAHAPAAHgAAAcA8AAcAAABwDgABwAAAHAeAAHAAAAcB8AA4AAABwPwAHgAAAHg/AAcAAAAeH8ADwAAAB4/4AeAAAAD//gD4AAAAP+fA/AAAAAfx//4AAAAAAD//AAAAAAAP/wAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAAA/wAAAAAAAH/gAAAAAAA/+AAAAAAAH/8AAAAAAA/nwAAAAAAD4PAAAAAAAeA8AAAAAADwDwAAAAAAPAPAAAAAAB4A8AAwAAAHgDwAHgAAAeAPAAeAAADwA8AD4AAAPADwAfgAAA8AOAB8AAADwA4APwAAAPADgB+AAAA8AeAPwAAAD4B4B/AAAAHgHgf4AAAAfA+D+AAAAA/D5/wAAAAB///+AAAAAH///gAAAAAH//4AAAAAAP/+AAAAAAAP/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAA4AAAAAAAADwDAAAAAAAOAeAAAAAAAYB4AAAAAAAAHgAAAAAAAAMAAAAAAAAAAAAA="); exports.add = function(graphics) { - graphics.prototype.setFontArchitect = function() { + graphics.prototype.setFontArchitect = function(scale) { // Actual height 40 (41 - 2) - this.setFontCustom(font, 32, widths, 22+(1<<8)+(1<<16)); + this.setFontCustom(font, 46, widths, 58+(scale<<8)+(1<<16)); } }; From a283cf58fb90b1852d8e414fb01010632a65ba46 Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 14:51:55 +0100 Subject: [PATCH 0847/1062] Included lcars logo --- apps.json | 2 +- apps/lcars/ChangeLog | 2 +- apps/lcars/bg_large.png | Bin 4277 -> 15345 bytes apps/lcars/bg_small.png | Bin 5973 -> 8437 bytes apps/lcars/lcars.app.js | 34 ++++++++++++++++++---------------- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/apps.json b/apps.json index d7d0f5257..79478be13 100644 --- a/apps.json +++ b/apps.json @@ -4295,7 +4295,7 @@ "name": "LCARS Clock", "shortName":"LCARS", "icon": "lcars.png", - "version":"0.03", + "version":"0.04", "supports": ["BANGLEJS2"], "description": "Library Computer Access Retrieval System (LCARS) clock.", "type": "clock", diff --git a/apps/lcars/ChangeLog b/apps/lcars/ChangeLog index 0b86c5282..ff0677a04 100644 --- a/apps/lcars/ChangeLog +++ b/apps/lcars/ChangeLog @@ -1,4 +1,4 @@ 0.01: Launch app 0.02: Swipe left/right to set an alarm. 0.03: New design with different icons if gps, hrm or compass is on. -0.04: Removed HRM Icon s its shown too much. \ No newline at end of file +0.04: Inluded LCARS Logo. \ No newline at end of file diff --git a/apps/lcars/bg_large.png b/apps/lcars/bg_large.png index d50c602ab5a428d3a44672a3f873b62c51c6dbd8..010e666a5f76873b01a00d11d9e6895968f77f66 100644 GIT binary patch literal 15345 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 z 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 literal 4277 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 0){ + g.setFontAlign(0,0,0); + g.drawString(alarm, 120+25, 107+25); + g.setFontAlign(-1,-1,0); + } // Write time var currentDate = new Date(); var timeStr = locale.time(currentDate,1); g.setFontAlign(0,0,0); g.setFontAntonioLarge(); - g.drawString(timeStr, 57, 57); + g.drawString(timeStr, 55, 57); // Write date - g.setFontAlign(1,-1, 0); + g.setFontAlign(-1,-1, 0); g.setFontAntonioMedium(); var dayName = locale.dow(currentDate, true).toUpperCase(); var day = currentDate.getDate(); - g.drawString(day, 133, 37); - g.drawString(dayName, 133, 57); - - // Alarm within symbol - if(alarm > 0){ - g.setFontAlign(0,0,0); - g.drawString(alarm, 115+25, 105+25); - g.setFontAlign(-1,-1,0); - } + g.drawString(day, 100, 37); + g.drawString(dayName, 100, 57); // Temperature g.setFontAlign(-1,-1,0); From d20f5e597e72d8392af3e9b745a4dfab36e8797f Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 14:53:27 +0100 Subject: [PATCH 0848/1062] Updated screenshot --- apps/lcars/screenshot.png | Bin 24460 -> 2944 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/lcars/screenshot.png b/apps/lcars/screenshot.png index 03b229b95199877f9668493005e6786dc86be114..99cb9d67e8a18a1ab26230b2b29694f70b60bdd5 100644 GIT binary patch literal 2944 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 literal 24460 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 From 684294830d75459982845920cb30815236dc0917 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 23 Nov 2021 13:57:46 +0000 Subject: [PATCH 0849/1062] app manager works in bjs2 --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 1eaece8a4..68d7abc13 100644 --- a/apps.json +++ b/apps.json @@ -740,7 +740,7 @@ "description": "Show currently installed apps, free space, and allow their deletion from the watch", "icon": "files.png", "tags": "tool,system,files", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "storage": [ {"name":"files.app.js","url":"files.js"}, {"name":"files.img","url":"files-icon.js","evaluate":true} From cfecc24f42d257d565f21571a50b825cffb2f1c3 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 07:05:47 -0800 Subject: [PATCH 0850/1062] Delete boot.js --- apps/schoolCalendar/boot.js | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 apps/schoolCalendar/boot.js diff --git a/apps/schoolCalendar/boot.js b/apps/schoolCalendar/boot.js deleted file mode 100644 index cb22decb7..000000000 --- a/apps/schoolCalendar/boot.js +++ /dev/null @@ -1,6 +0,0 @@ -// check for alarms -(function() { - var alarms = require('Storage').readJSON('schoolCalendarAlarms.json',1)||[]; - var time = new Date(); - E.showPrompt("School Calendar Alarm Test"); -})(); From 0a695e5143fbff3935e80b9df85720f065b1e185 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 07:09:14 -0800 Subject: [PATCH 0851/1062] Update custom.html fix bug that was for testing --- apps/schoolCalendar/custom.html | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/schoolCalendar/custom.html b/apps/schoolCalendar/custom.html index 6017b4396..b007d16e3 100644 --- a/apps/schoolCalendar/custom.html +++ b/apps/schoolCalendar/custom.html @@ -173,11 +173,7 @@ function updateDay(ffunction,day){ } } function getScheduleTable() { - let schedule = [//Monday: - {cn: "Biblical Theology", dow:1, sh: 8, sm: 10, eh:9, em: 5, r:"207", t:"Mr. Besaw"}, - {cn: "English", dow:1, sh: 9, sm: 5, eh:10, em: 0, t:"Dr. Wong"}, - {cn: "Break", dow:1, sh: 10, sm: 0, eh:10, em: 10, t:""}, - {cn: "MS Robotics", dow:1, sh: 10, sm: 10, eh:11, em: 0, r:"211", t:"Mr. Broyles"}];//${JSON.stringify(schedule)}; + let schedule = ${JSON.stringify(schedule)}; logDebug(JSON.stringify(schedule)); return schedule; } From 31f28093411e4f501dd49f98359af43e71e8b27e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 23 Nov 2021 15:44:49 +0000 Subject: [PATCH 0852/1062] Fix https://github.com/espruino/BangleApps/issues/927 - App loader offers to update unknown apps --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 59f80bb52..905adb6ce 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 59f80bb52a38da12cb272f9106cb3951b49dab2e +Subproject commit 905adb6ce4ae002e943a14d1724744e0c1326277 From dab36d05a77563e9a839f38807bfd7cfe09a1f69 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 16:48:50 +0100 Subject: [PATCH 0853/1062] Update apps.json added background images to stroage --- apps.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps.json b/apps.json index 6e32e220a..a39d9686d 100644 --- a/apps.json +++ b/apps.json @@ -4307,6 +4307,8 @@ "type": "clock", "storage": [ {"name":"binwatch.app.js","url":"app.js"}, + {"name":"Background176_center.png","Background176_center.png"}, + {"name":"Background240_center.png","Background240_center.png"}, {"name":"binwatch.img","url":"app-icon.js","evaluate":true} ] } From 51b73894e461af6312b6fca28e0b5888e9f98da6 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 16:51:40 +0100 Subject: [PATCH 0854/1062] Update app.js use background images from storage --- apps/binwatch/app.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/binwatch/app.js b/apps/binwatch/app.js index 57f795c3b..14dc36ac4 100644 --- a/apps/binwatch/app.js +++ b/apps/binwatch/app.js @@ -36,11 +36,6 @@ var month = [ /* DEZ */ {width : 51, height : 23, bpp : 1, buffer : require("heatshrink").decompress(atob("n/wh//w//xP/gV/8F//Of4Fn/EH/04gUODAUHgHh4AFBnHgjk4BYUcgeHAoMB8eAuHgAwN4uEOjgFBh4jB4eAgED4ADBl/4gFwB4MD/4DBgQCB3/gC4PghgyBgPAGQl4gYyDjwgBGQQrBh0BGQVwDQM4F4MMLIJlEg3/gOfPAPgn/gk/+j/+h/8IoPh//gA="))} ]; -var img176 = { - width : 176, height : 176, bpp : 3, - transparent : 1, - buffer : require("heatshrink").decompress(atob("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")) -} var imgSquid = {width : 88, height : 26, bpp : 1, buffer : require("heatshrink").decompress(atob("gE/AYUYgEH////0B//gBQM8BQgDB/AKHh/A/gKBvwKBAgMOj8AnwKHBAIMBgH/BQgmCAoPnBQl4AoOAgPnwAKDuEAgYKB4YKIgfD4AKDMAMB4EDwIKIg+B8AKIgAKIh8A+AKHh0AuAKHj0AvBMG4EcgE4K458Bnh4HnEAjiOHBwMeBQpKBEgMOXQ/wBwIKDaAZQBg4KDcwT0BAAOHfgoKHgE/wDaBAAL8DA="))}; var imgNoBT = {width : 20, height : 20, bpp : 3, transparent : 0, buffer : require("heatshrink").decompress(atob("///8mSpM/AoP/yUT/8yuYGB5AMB/1MyYUBkmT/P85MP+USBwOT8mQ/8JBwXyoVnyGSv8//Mhk14pMn//8BYNMwmSp/+pFJkgyBDoMkkgODpOSuQOE5M/KgIOCsmfz/JknPhMyof5n+Ss/wzMhn4OBk1+smQLoWTn/mHAM/+VJz4KBwhZBEYJ/CkM8yZVBAAQxBCgP/A="))}; @@ -290,14 +285,14 @@ function setRuntimeValues(resolution) { screen_size_x = V1_SCREEN_SIZE_X; screen_size_y = V1_SCREEN_SIZE_Y; - backgroundImage = img176; + backgroundImage = V1_BACKGROUND_IMAGE; bat_pos_x = V1_BAT_POS_X; bat_pos_y = V1_BAT_POS_Y; bat_size_x = V1_BAT_SIZE_X; bat_size_y = V1_BAT_SIZE_Y; - setWatch(toggleDateTime, BTN1, { repeat : true, edge: "falling"}); + setWatch(toggleDateTime, BTN1, { repeat : true, edge: "falling"}); } else { x_step = V2_X_STEP; From 7d81bfe2bbdf5095ac7550247eddbe6827cec4c0 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 16:53:40 +0100 Subject: [PATCH 0855/1062] Update apps.json added missing "url" to background storage --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index a39d9686d..2618168cf 100644 --- a/apps.json +++ b/apps.json @@ -4307,8 +4307,8 @@ "type": "clock", "storage": [ {"name":"binwatch.app.js","url":"app.js"}, - {"name":"Background176_center.png","Background176_center.png"}, - {"name":"Background240_center.png","Background240_center.png"}, + {"name":"Background176_center.png","url":"Background176_center.png"}, + {"name":"Background240_center.png","url":"Background240_center.png"}, {"name":"binwatch.img","url":"app-icon.js","evaluate":true} ] } From 03c5f996f4998ebc1e6bfea7e77d3a61743c0c69 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 17:07:21 +0100 Subject: [PATCH 0856/1062] Add files via upload added background files as js --- apps/binwatch/Background176_center.js | 1 + apps/binwatch/Background240_center.js | 1 + 2 files changed, 2 insertions(+) create mode 100644 apps/binwatch/Background176_center.js create mode 100644 apps/binwatch/Background240_center.js diff --git a/apps/binwatch/Background176_center.js b/apps/binwatch/Background176_center.js new file mode 100644 index 000000000..cb3c233c4 --- /dev/null +++ b/apps/binwatch/Background176_center.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("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")) diff --git a/apps/binwatch/Background240_center.js b/apps/binwatch/Background240_center.js new file mode 100644 index 000000000..87c0517a4 --- /dev/null +++ b/apps/binwatch/Background240_center.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("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")) From dd2bb22f076abff1aac2f32734b50fdcc5423861 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 17:08:57 +0100 Subject: [PATCH 0857/1062] Update apps.json use .js files as background images --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 2618168cf..d1340558b 100644 --- a/apps.json +++ b/apps.json @@ -4307,8 +4307,8 @@ "type": "clock", "storage": [ {"name":"binwatch.app.js","url":"app.js"}, - {"name":"Background176_center.png","url":"Background176_center.png"}, - {"name":"Background240_center.png","url":"Background240_center.png"}, + {"name":"Background176_center.png","url":"Background176_center.js"}, + {"name":"Background240_center.png","url":"Background240_center.js"}, {"name":"binwatch.img","url":"app-icon.js","evaluate":true} ] } From cd93a201f846e2c51a1ff9cc447dbbbdc2c5e2cc Mon Sep 17 00:00:00 2001 From: David Peer Date: Tue, 23 Nov 2021 17:15:15 +0100 Subject: [PATCH 0858/1062] Moved logo --- apps/lcars/bg_large.png | Bin 15345 -> 15219 bytes apps/lcars/bg_small.png | Bin 8437 -> 8456 bytes apps/lcars/lcars.app.js | 6 +++--- apps/lcars/screenshot.png | Bin 2944 -> 26456 bytes 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/lcars/bg_large.png b/apps/lcars/bg_large.png index 010e666a5f76873b01a00d11d9e6895968f77f66..e11fb8e88ee928f1836a28726c3a35477827602b 100644 GIT binary patch literal 15219 zcmdVBWmHvB+cvsE5d{S41_703OGuYWH%O;|ba#ge(%p@OfOIz~Y(i4HK}x#2&s@IG zdw!j9zIS|QoH5Sf&)RG6wdR`hj_bbeYc7Hm+7zp?x>{i^3>Me#@Ni#=&7Tdt

    S2JT6%yoJvS;L-8Alh%iE|>?o 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 Y^ZwKS0m>12UH||9 literal 15345 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 z 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 diff --git a/apps/lcars/bg_small.png b/apps/lcars/bg_small.png index 42f5368b96fcb33f8542be1e5bddd7e32d28606e..5b705562ddb9efd4f097662d9e9b51d8e98b5813 100644 GIT binary patch literal 8456 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 mn=GQ_ePor Date: Tue, 23 Nov 2021 17:28:25 +0100 Subject: [PATCH 0860/1062] Delete Background176_center.js remove backgounrd176_center.js (wrong format) --- apps/binwatch/Background176_center.js | 1 - 1 file changed, 1 deletion(-) delete mode 100644 apps/binwatch/Background176_center.js diff --git a/apps/binwatch/Background176_center.js b/apps/binwatch/Background176_center.js deleted file mode 100644 index cb3c233c4..000000000 --- a/apps/binwatch/Background176_center.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("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")) From 4719fada2843f1c8d6ae36d8d59d0fb95318d9b5 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 23 Nov 2021 16:28:34 +0000 Subject: [PATCH 0861/1062] Android Remove messages on disconnect, Fix music control (ref #909) --- apps.json | 10 +++++----- apps/android/ChangeLog | 2 ++ apps/android/boot.js | 11 +++++++++-- apps/files/files-icon.js | 2 +- apps/ios/ChangeLog | 1 + apps/ios/boot.js | 3 ++- apps/messages/ChangeLog | 1 + apps/messages/app.js | 3 ++- apps/messages/lib.js | 19 +++++++++++++++++-- apps/messages/widget.js | 8 ++++++-- core | 2 +- 11 files changed, 47 insertions(+), 15 deletions(-) diff --git a/apps.json b/apps.json index 68d7abc13..d8f6db3c3 100644 --- a/apps.json +++ b/apps.json @@ -32,7 +32,7 @@ { "id": "messages", "name": "Messages", - "version": "0.03", + "version": "0.04", "description": "App to display notifications from iOS and Gadgetbridge", "icon": "app.png", "type": "app", @@ -50,7 +50,7 @@ { "id": "android", "name": "Android Integration", - "version": "0.01", + "version": "0.02", "description": "(BETA) App to display notifications from Gadgetbridge on Android. This will eventually replace the Gadgetbridge widget.", "icon": "app.png", "tags": "tool,system,messages,notifications", @@ -61,12 +61,12 @@ {"name":"android.img","url":"app-icon.js","evaluate":true}, {"name":"android.boot.js","url":"boot.js"} ], - "sortorder": -9 + "sortorder": -8 }, { "id": "ios", "name": "iOS Integration", - "version": "0.01", + "version": "0.02", "description": "(BETA) App to display notifications from iOS devices", "icon": "app.png", "tags": "tool,system,ios,apple,messages,notifications", @@ -77,7 +77,7 @@ {"name":"ios.img","url":"app-icon.js","evaluate":true}, {"name":"ios.boot.js","url":"boot.js"} ], - "sortorder": -9 + "sortorder": -8 }, { "id": "health", diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index 5560f00bc..e881c9ec2 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -1 +1,3 @@ 0.01: New App! +0.02: Remove messages on disconnect + Fix music control diff --git a/apps/android/boot.js b/apps/android/boot.js index dd19f9500..1793dc895 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -33,7 +33,13 @@ // {t:"musicinfo", artist,album,track,dur,c(track count),n(track num} "musicinfo" : function() { require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"})); - } + }, + // {"t":"call","cmd":"incoming/end","name":"Bob","number":"12421312"}) + "notify" : function() { + event.t=t.cmd=="incoming"?"add":"remove"; + event.id="call"; + require("messages").pushMessage(event); + }, }; var h = HANDLERS[event.t]; if (h) h(); else console.log("GB Unknown",event); @@ -42,6 +48,7 @@ // Battery monitor function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); } NRF.on("connect", () => setTimeout(sendBattery, 2000)); + NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect setInterval(sendBattery, 10*60*1000); // Health tracking Bangle.on('health', health=>{ @@ -50,6 +57,6 @@ // Music control Bangle.musicControl = cmd => { // play/pause/next/previous/volumeup/volumedown - gbSend({ t: "music", m:cmd }); + gbSend({ t: "music", n:cmd }); } })(); diff --git a/apps/files/files-icon.js b/apps/files/files-icon.js index 7e55db9e0..7f7ea4d0c 100644 --- a/apps/files/files-icon.js +++ b/apps/files/files-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwghC/AEkIxAABwUiAAwKBC6+AC6ERiIXDGBAXPGA8JzIAByQXKGA4XUA4eDmYAGJwQXVxEizAXPIgIXDwWZC6uIxIwCC6eIGAQX/C9i/FC5mCCw0yC5wAMC/4Xnx//ABf4C/Xzdw8zn4XkL/5f/L+oUDI6YX3AB4XeAH4AdA==")) +require("heatshrink").decompress(atob("mEw4cA///7c0AYMXlm3gf42s1yvb5xT/ABdJkmStu27YCCtMkCKOACJdm7YRCyARQyQRLBwIRDoARTgVLtu3K4tJl4RQkvpCJdbtwRBkm5CKGZCKGTCKGSsgR/R4gRHpIMBCInaCJIIBARAR/CJtPB5FLCI1KEhMSCLN//4AE/QRbI/5H/CI4PCGpwRXp4RIpZFDCIQiJAQIRWAH4AGA")) diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog index 5560f00bc..ef674102a 100644 --- a/apps/ios/ChangeLog +++ b/apps/ios/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Remove messages on disconnect diff --git a/apps/ios/boot.js b/apps/ios/boot.js index c3ccb9275..54bd0b1c2 100644 --- a/apps/ios/boot.js +++ b/apps/ios/boot.js @@ -95,7 +95,8 @@ E.on('AMS',a=>{ Bangle.musicControl = cmd => { // play, pause, playpause, next, prev, volup, voldown, repeat, shuffle, skipforward, skipback, like, dislike, bookmark NRF.amsCommand(cmd); -} +}; +NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect /* // For testing... diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 4f7df3859..28906d608 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Add 'messages' library 0.03: Fixes for Bangle.js 1 +0.04: Add require("messages").clearAll() diff --git a/apps/messages/app.js b/apps/messages/app.js index 987d9184b..b12fa7f1f 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -16,7 +16,8 @@ {"t":"add","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"} // maps {"t":"add","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"GhqBAAAMAAAHgAAD8AAB/gAA/8AAf/gAP/8AH//gD/98B//Pg/4B8f8Afv+PP//n3/f5//j+f/wfn/4D5/8Aef+AD//AAf/gAD/wAAf4AAD8AAAeAAADAAA="} - +// call +{"t:"add","id:"call","name":"Bob","number":"12421312"} */ var Layout = require("Layout"); diff --git a/apps/messages/lib.js b/apps/messages/lib.js index f3ea242e5..4bda60e65 100644 --- a/apps/messages/lib.js +++ b/apps/messages/lib.js @@ -28,10 +28,25 @@ exports.pushMessage = function(event) { // otherwise load after a delay, to ensure we have all the messages if (exports.messageTimeout) clearTimeout(exports.messageTimeout); exports.messageTimeout = setTimeout(function() { - exports.messageTimeout = undefined; + exports.messageTimeout = undefined; // if we're in a clock or it's important, go straight to messages app if (Bangle.CLOCK || event.important) return load("messages.app.js"); if (!global.WIDGETS || !WIDGETS.messages) return Bangle.buzz(); // no widgets - just buzz to let someone know - WIDGETS.messages.newMessage(); + WIDGETS.messages.show(); }, 500); } +exports.clearAll = function(event) { + var messages, inApp = "undefined"!=typeof MESSAGES; + if (inApp) { + MESSAGES = []; + messages = MESSAGES; // we're in an app that has already loaded messages + } else // no app - empty messages + messages = []; + // Save all messages + require("Storage").writeJSON("messages.json",messages); + // update app if in app + if (inApp) return onMessagesModified(); + // if we have a widget, update it + if (global.WIDGETS && WIDGETS.messages) + WIDGETS.messages.hide(); +} diff --git a/apps/messages/widget.js b/apps/messages/widget.js index eda4a85a5..c40e9aa05 100644 --- a/apps/messages/widget.js +++ b/apps/messages/widget.js @@ -10,11 +10,15 @@ WIDGETS["messages"]={area:"tl",width:0,draw:function() { Bangle.buzz(); // buzz every 4 seconds } setTimeout(()=>WIDGETS["messages"].draw(), 1000); -},newMessage:function() { +},show:function() { WIDGETS["messages"].t=Date.now(); // first time WIDGETS["messages"].l=Date.now()-10000; // last buzz - if (WIDGETS["messages"].c!==undefined) return; // already called WIDGETS["messages"].width=64; Bangle.drawWidgets(); Bangle.setLCDPower(1);// turns screen on +},hide:function() { + delete WIDGETS["messages"].t; + delete WIDGETS["messages"].l; + WIDGETS["messages"].width=0; + Bangle.drawWidgets(); }}; diff --git a/core b/core index 905adb6ce..996299a28 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 905adb6ce4ae002e943a14d1724744e0c1326277 +Subproject commit 996299a285c95136ad0049febb5399ee837c42d3 From c36fb16a684085c2eb6ada2c50021d3776f5e215 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 17:28:54 +0100 Subject: [PATCH 0862/1062] Delete Background240_center.js removed background240_center.js (wrong format) --- apps/binwatch/Background240_center.js | 1 - 1 file changed, 1 deletion(-) delete mode 100644 apps/binwatch/Background240_center.js diff --git a/apps/binwatch/Background240_center.js b/apps/binwatch/Background240_center.js deleted file mode 100644 index 87c0517a4..000000000 --- a/apps/binwatch/Background240_center.js +++ /dev/null @@ -1 +0,0 @@ -require("heatshrink").decompress(atob("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")) From d328d94d34ea86dd99f958098c7f0a000b1e395f Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 17:30:20 +0100 Subject: [PATCH 0863/1062] Add files via upload added background files exported from Emulator --- apps/binwatch/Background176_center.img | Bin 0 -> 11686 bytes apps/binwatch/Background240_center.img | Bin 0 -> 21694 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/binwatch/Background176_center.img create mode 100644 apps/binwatch/Background240_center.img diff --git a/apps/binwatch/Background176_center.img b/apps/binwatch/Background176_center.img new file mode 100644 index 0000000000000000000000000000000000000000..4d4b587de7923776bcf4eb7eb20722589f91b529 GIT binary patch literal 11686 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 L0**w#Z{`02!0Q&~ literal 0 HcmV?d00001 From 5be6981acdf3935b936bb85caf37780537518b66 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 23 Nov 2021 16:31:42 +0000 Subject: [PATCH 0864/1062] oops - named stub wrong --- apps/android/boot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/android/boot.js b/apps/android/boot.js index 1793dc895..4b6c2c6ff 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -35,7 +35,7 @@ require("messages").pushMessage(Object.assign(event, {t:"modify",id:"music",title:"Music"})); }, // {"t":"call","cmd":"incoming/end","name":"Bob","number":"12421312"}) - "notify" : function() { + "call" : function() { event.t=t.cmd=="incoming"?"add":"remove"; event.id="call"; require("messages").pushMessage(event); From cf027b3783cbbf8deca98fb3e1ee1922557686bf Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 17:34:13 +0100 Subject: [PATCH 0865/1062] Update app.js changed names of background images --- apps/binwatch/app.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/binwatch/app.js b/apps/binwatch/app.js index 14dc36ac4..cc7a17914 100644 --- a/apps/binwatch/app.js +++ b/apps/binwatch/app.js @@ -62,7 +62,7 @@ const V2_BAT_SIZE_Y = 2; const V2_SCREEN_SIZE_X = 176; const V2_SCREEN_SIZE_Y = 176; -const V2_BACKGROUND_IMAGE = "Background176_center.png"; +const V2_BACKGROUND_IMAGE = "binwatch.Background176_center.img"; const V2_BG_COLOR = 0; const V2_FG_COLOR = 1; @@ -90,7 +90,7 @@ const V1_BAT_SIZE_X = 3; const V1_BAT_SIZE_Y = 5; const V1_SCREEN_SIZE_X = 240; const V1_SCREEN_SIZE_Y = 240; -const V1_BACKGROUND_IMAGE = "Background240_center.png"; +const V1_BACKGROUND_IMAGE = "binwatch.Background240_center.img"; const V1_BG_COLOR = 1; const V1_FG_COLOR = 0; @@ -361,8 +361,7 @@ function draw() { updateVTime(); g.clear(); g.drawImages([{image:cgimg}, - {image:require("Storage").read(backgroundImage)}, -// { x:bt_x, y:bt_y, rotate: 0, image:require("Storage").read("bt-icon.png")}, + {image:require("Storage").read(backgroundImage)} ]); drawBT(g, NRF.getSecurityStatus().connected); // Bangle.drawWidgets(); From 3dd723d10bd3195257205a48dc79872668bd53cb Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 17:58:48 +0100 Subject: [PATCH 0866/1062] Update apps.json binwatch: shorten names of background images --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 55e5353f5..b152ace0c 100644 --- a/apps.json +++ b/apps.json @@ -4320,8 +4320,8 @@ "type": "clock", "storage": [ {"name":"binwatch.app.js","url":"app.js"}, - {"name":"binwatch.Background176_center.img","url":"Background176_center.img"}, - {"name":"binwatch.Background240_center.img","url":"Background240_center.img"}, + {"name":"binwatch.bg176.img","url":"Background176_center.img"}, + {"name":"binwatch.bg240.img","url":"Background240_center.img"}, {"name":"binwatch.img","url":"app-icon.js","evaluate":true} ] }, From e021aa2c9d5c44f9130e4bf67cb4b066d02cbd6d Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 17:59:49 +0100 Subject: [PATCH 0867/1062] Update app.js binwatch: adapted names of bg images --- apps/binwatch/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/binwatch/app.js b/apps/binwatch/app.js index cc7a17914..28d7a06a5 100644 --- a/apps/binwatch/app.js +++ b/apps/binwatch/app.js @@ -62,7 +62,7 @@ const V2_BAT_SIZE_Y = 2; const V2_SCREEN_SIZE_X = 176; const V2_SCREEN_SIZE_Y = 176; -const V2_BACKGROUND_IMAGE = "binwatch.Background176_center.img"; +const V2_BACKGROUND_IMAGE = "binwatch.bg176.img"; const V2_BG_COLOR = 0; const V2_FG_COLOR = 1; @@ -90,7 +90,7 @@ const V1_BAT_SIZE_X = 3; const V1_BAT_SIZE_Y = 5; const V1_SCREEN_SIZE_X = 240; const V1_SCREEN_SIZE_Y = 240; -const V1_BACKGROUND_IMAGE = "binwatch.Background240_center.img"; +const V1_BACKGROUND_IMAGE = "binwatch.bg240.img"; const V1_BG_COLOR = 1; const V1_FG_COLOR = 0; From 1def233de2434d272c6f712c9c213b7caff3ae92 Mon Sep 17 00:00:00 2001 From: Vingelar Date: Tue, 23 Nov 2021 19:09:16 +0100 Subject: [PATCH 0868/1062] changed format of bg images to binary raw format (export from emulator --- apps/binwatch/Background240_center.img | Bin 21694 -> 21694 bytes apps/binwatch/Background240_center.png | Bin 6492 -> 6350 bytes apps/binwatch/ChangeLog | 1 + 3 files changed, 1 insertion(+) diff --git a/apps/binwatch/Background240_center.img b/apps/binwatch/Background240_center.img index 89e335afd98871d690e141c18277f073da7a327b..abf95107d726b4c632187081dddbfda7add25b98 100644 GIT binary patch literal 21694 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 L0**w#Z{`02!0Q&~ diff --git a/apps/binwatch/Background240_center.png b/apps/binwatch/Background240_center.png index 6fa35f93fc6c5e92b98f781d6d42251f370eb8a8..c2b108f4de91af0cbcd88e77044cad9204c92331 100644 GIT binary patch literal 6350 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 bp8+!j=Mj6Xjg-<41t)shs)+9y3BvyX(m>5N literal 6492 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 Q2>KnnWX0l>+zk1D04_NZ`v3p{ diff --git a/apps/binwatch/ChangeLog b/apps/binwatch/ChangeLog index bf4f5075a..1e54f489c 100644 --- a/apps/binwatch/ChangeLog +++ b/apps/binwatch/ChangeLog @@ -1,3 +1,4 @@ 0.01: start of development 0.02: first running version for BangleJs2 0.03: corrected icon, added screen shot, extended description +0.04: corrected format of background image (raw binary) From 32e3d326aa143eed99d26c81c9fbf870730591f4 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Sat, 20 Nov 2021 17:13:36 +0100 Subject: [PATCH 0869/1062] barclock works fine on the Bangle.js 2 :-) --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index f98c6a1f8..0b90db62e 100644 --- a/apps.json +++ b/apps.json @@ -1696,7 +1696,7 @@ "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}], "type": "clock", "tags": "clock", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "allow_emulator": true, "storage": [ From 6b05d928751b49d4910ddd3a6867e2d4cad6c93f Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Tue, 23 Nov 2021 19:42:12 +0100 Subject: [PATCH 0870/1062] barclock: Fix time/date disappearing after fullscreen notification By forcing a complete redraw at lcdPower-on --- apps.json | 2 +- apps/barclock/ChangeLog | 3 ++- apps/barclock/clock-bar.js | 10 +++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps.json b/apps.json index 0b90db62e..6e331e019 100644 --- a/apps.json +++ b/apps.json @@ -1690,7 +1690,7 @@ { "id": "barclock", "name": "Bar Clock", - "version": "0.08", + "version": "0.09", "description": "A simple digital clock showing seconds as a bar", "icon": "clock-bar.png", "screenshots": [{"url":"screenshot.png"},{"url":"screenshot_pm.png"}], diff --git a/apps/barclock/ChangeLog b/apps/barclock/ChangeLog index c56967d3d..316660fc6 100644 --- a/apps/barclock/ChangeLog +++ b/apps/barclock/ChangeLog @@ -5,4 +5,5 @@ 0.05: Clock does not start if app Languages is not installed 0.06: Improve accuracy 0.07: Update to use Bangle.setUI instead of setWatch -0.08: Use theme colors, Layout library \ No newline at end of file +0.08: Use theme colors, Layout library +0.09: Fix time/date disappearing after fullscreen notification diff --git a/apps/barclock/clock-bar.js b/apps/barclock/clock-bar.js index 2c6d66e45..5d46a1cb4 100644 --- a/apps/barclock/clock-bar.js +++ b/apps/barclock/clock-bar.js @@ -24,7 +24,7 @@ function renderBar(l) { return; } const width = this.fraction*l.w; - g.fillRect(l.x, l.y, width-1, l.y+l.height-1); + g.fillRect(l.x, l.y, l.x+width-1, l.y+l.height-1); } const Layout = require("Layout"); @@ -78,7 +78,7 @@ function dateText(date) { return `${dayName} ${dayMonth}`; } -draw = function draw() { +draw = function draw(force) { if (!Bangle.isLCDOn()) {return;} // no drawing, also no new update scheduled const date = new Date(); layout.time.label = timeText(date); @@ -86,6 +86,10 @@ draw = function draw() { layout.date.label = dateText(date); const SECONDS_PER_MINUTE = 60; layout.bar.fraction = date.getSeconds()/SECONDS_PER_MINUTE; + if (force) { + Bangle.drawWidgets(); + layout.forgetLazyState(); + } layout.render(); // schedule update at start of next second const millis = date.getMilliseconds(); @@ -96,7 +100,7 @@ draw = function draw() { Bangle.setUI("clock"); Bangle.on("lcdPower", function(on) { if (on) { - draw(); + draw(true); } }); g.reset().clear(); From b1d636bac2148007dd72becff0daddf24b80f0a4 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:25:10 -0800 Subject: [PATCH 0871/1062] Add files via upload --- apps/berlinc/berlin-clock-screenshot.png | Bin 0 -> 1846 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/berlinc/berlin-clock-screenshot.png diff --git a/apps/berlinc/berlin-clock-screenshot.png b/apps/berlinc/berlin-clock-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..92a4c79285dfb62741558b91c26d9186596caa2f GIT binary patch literal 1846 zcmc&#Yfw{16y6&`E(Q(p5lq`)z*KF5k7%)l5<*PX7^r{toTTCpf#Fbx87Q;ALm zp*}#SBA{qdYLTfCL=t+HM?`2eXiY4yOTbFXD=0{y1bWkH|Mq7)XLird&g`D=obTJS zUxx&VW|7|}0{~`;H>?fCeYt%&k?{FuWluEjAZe&b2+DK}GXNxuc^%^Ge1Nt5u(d#CpuhXpPVgH}`IP>ywA(6n>$fLfJ8bV0f0iH<-XGw!qi*(ouTfu-fuY#yjZTWA2g`hq0~#}!vS0UFpO zvVfTmfe`>XpPP+AbW&~^04tU;?>TU1{`DJT9#<|6g#*Cdz6>0t&)_S*qlSM zRTSQ*RZYAsy@_(I2a<$)Wa+)vOX-`WN$;FW<7}MTyo_mjN=a-Or_PVt35-zK&yEtX zB{QDV>=Qo{v7%RD#_jV1dAn6s)^>62%5O`l+I@^-pUi7jDyrS~pKp7oL|LTF+xv1z zo#=AKwA(hg1P#?Gh@6lwy2+UMC_c9nr`6_PA=zu zRuSh8KERBtZo9dMA4{CVOU%_iA(UEvHNcW}h))OQ8&2>?XqTs;lw^$VNLK~w5q$E zCcn@LFF5@D={`}J20N$bCk^S1+4Gv3KRCM6IbqK5#kitPhUkNblQluM4Z_(O&%WrR zfs~R^@q;sC4T{RkKMmha+r^HkR}AHN%}9CGQG6kW723JHR*`^ZpLW?AgNZyy5Al(sWGoTENB|h(k9jt^;yd z%RkfS#?GCQR;TE+^^%eLic7e1ij>yFXD#8c-Y;0-&84LLfI2y`3|Sq?BNME8T;4?l z(VUZ!pM}^`DJ9f=p=BeZvraGM6*CKm_u|58P~eKE!&7gVuD`c;1y!O$&3g*2#Dp-RueckJz8iwZ>AC@y?pmv#Zvak& z@=YIwcOa>@ztNf>*AbaKHd?s-*B>ea0BBt1GNgD^-^os_j_sr8Wb1Z(s5!b{y3ye2VY;OzaZmHjtJ2!VKA(AqMg Date: Tue, 23 Nov 2021 11:25:35 -0800 Subject: [PATCH 0872/1062] Add files via upload --- .../bangle-charge-animation-screenshot.png | Bin 0 -> 3401 bytes .../bangle2-charge-animation-screenshot.png | Bin 0 -> 2031 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/chargeanim/bangle-charge-animation-screenshot.png create mode 100644 apps/chargeanim/bangle2-charge-animation-screenshot.png diff --git a/apps/chargeanim/bangle-charge-animation-screenshot.png b/apps/chargeanim/bangle-charge-animation-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..83ef1dbdae1ebf16ee9df2f05fbc9220ac0a77a9 GIT binary patch literal 3401 zcmb7HdpMNa8XwE2z9D==822&bk}yoAP%;>dONiVGP1w6mW@p%9RNB5YG?~(fqLfNf z2@|=bQD3`}X}fmMCWftCLzHx)&g^rZ^E~I@^T%56dVbIQu4k?F-hPW8=xs`@6Z0iqIS&r?sgKwq8T3`C z@oQi#S^5_5@>}s2p7e523=NJ%8j%Ix{;0n6XB*H47ZNQ*3H1YVy?4zkQwP?7-R8M< zCLE207wH3kp&ERjign)Ew$Vwb=6fxkG*dg*HD+PKu@Q|2iq%2~W9*<6c#_D( z&&`!|4qIwvUb)bn?omjD6fD38eaC$|p3&vSh&G4;u(f9k9ezRKP-gCLo#IuUU6XQ$ zidqfSKkwi9VLcSBqh*mvHw39e7gHCa;-IS29x1a-V3Bsdf%bz+11>dt9FkRmPQa4k zhLnRFhHd$TMo;se77BjP#l?-lP3Cn*CA*42pKV{dxAZ-~P%YB3eDa+uL~W{GP`I*l z+K)1m>629&O75N;o%+(Y2dc^so}VQFc=1x)uP;8sOqXxX8y&0B*A;mGbp!?I;;IO? z76^k5n7xlR9mfMDn8ZO%MLZ|yle*&Ybd(jXA?W>xz>uhU$|vx%?)>+qIa*$#C4riW zbvU;?Um~L4l>xeT{Di$KzOOGpTuI^*F%HG@GUA}LG3QUh!l7n{i(+!*?eVfZ7nQ+8 z5v_CuD!f

    J$-eTS?J>s_6jVI)Jct;L*?GA_awD0Q)UpZ?o!1iw2zqovd^SU28^K zTKu$kEA{f0U{^bM6i>1<4Bu(rzRKg0*N}bF^qFN4HAKL^mYd-@w13z`c7N}#P)87i zzRuG@+qB5~dX!5K&T`q!!k+a9-)S5$UEa>iIG6DuQwP4~kJBV|`(oc8sz6g6N^_)3 z=BytwB~Wo1gWyldOxg@U4<pi>g>?IUnk>H1}5oXOZGs~2lVx|OIMoNDq)=}0ik zS9p3Y^CL1!nK@Q2+;_3>4d1@Z`HK9?Zn2?10iNyUf#jV9jk_0*McyU#oIi;J6I_}e zD1+NgMB|1d+B=;%ImmA%*;ph{k8+y!c9o_5ar#2N+~aaMo4yOckq(Y*RbT_)KC)!M zB(M-O0O!H5y~xu4NSOv*wr$Z`4W$8|QoovcbHX-&c3NGQ6yAkNGm&3o8jyt(I1r2= zg@yD)G_|Jm?6b(t0gq6h>0R9%7-?aUw$bRro7F27w0ASwlkd-15PV% zq9xX=DYyfe4WAA~j+tj*PbCrRAPYj?Am*1z$QB%0%C9y0pESe)FU{S~Fi{>T?uAE> z(AOzIC(Sz#AaNV>rEV!- z@T5Ah@WU~>2cE= zb0eO@(LNlXHYSZI@{7_zYiABB`+0T#v3H>fHsCFCQ$vo*jjp#}tIEXs7fOFhPc_bW^sv~)M2@Z@BO zf=MFbjp7`8_L6Yb zt%&-kWY8$yxa!Dt*gE5d!O)xZa-^_r{AWH_mCjPBsRBRxWZ5m7OJll)rOXEicV%6v zxV19<@X~T}t~tB%E6f&l;rOX&`NZSitTCx}NQ$l^TG{R7duSz>G4$dGfm9Pxw`9B= zwGl4!?V}{!#Kb*6Mf~!k#f{~AwG$-la88XmAqkD@TcK;1Ml=KJltpEPR9%PZeXX>M zdh9y9PYm~+m|90;$~N+Mtcl(~^~%6%-H^jEYtK4$2Ft!C2anF)*W?cLy!6_V3MjWd z6oy;qvaRzEXQz5!!e94IZXEM^n%q&YQXw)CDTf{YQUD)1le8Lb=%CzgTulfU+&n4P zq#e5iMyn_l~Nvx0!Wl#cHoG_tfec0WN1>hNG` zRsCWIDugdJ!)L2L-L0sES0AvhOV;Qo2$tU>UP zv;@R~LPB^TnrSp($WiJ?`7!uW7`#x zMi1#<)0#fwU3TeVdTCo$tDsx#QkE4lhdVX<1Ke4fS7G*%L^~-&KL6cQb5>2ey?P90 zXFVfzLuCEpyo5eGbYU(0pUkY0Chx3WqL;Y#|^7`ZvxpY z>K$gj6gJ39hSVNLatHxJ;RfqL*dVKE)Xj)Y;e^)^99HcA$h*bx?C)k!IIE_Haw3=l z?6S9~EU@pi^((72vso5I^pP;&&ZW>eNG*U0vMD7TSH4twJ}(^df4F(<$~41MmVcGZ zQJ#OI<9uAuc;gAvdV&{oNTNh~h;D)>^ zYPsaNvjjMh`!RMs6eO>Os}E~8Sj_amUw1u-i(x?iN4uWAg4AZGx~z9@t`?t^!0ae@ ziLdF58D~yqIpRR`wpK;0{-!YlD{HbO$t-sGQEX)5<)pZ^lsrS`j}YS#2e@zYelMTE zJt6+}xQG0}Wd1O*%e4ES3!{EKwZ(R)!52I{(=6J(--aOOhcp`WoI8U9@qC&QXo2sT zMwfqkhUOF)_1-q+n9XjVC5wJ)MpOVE;;I(oE|{6HJaA|w^V;LIFHH%}ZI8FTXwmkP z#o(VNj3lcJYRh^rFqyYToLW~v7byV=DVBE?@o0I#T!#X}9dGv;eye^=5?ro_hWQPg z#nhY9ervHlSr1?AcLsdmOEmfu(E>Zp3 zrDoLPC`^i-JpK)d`Og2NiJhKH9xMthN>{8e*PbF+H!{Ymr1uP z4&8W(rUXLdt-iodqz)$|beY;7Ka{Qonj8G<=ey!%+9u!C6cpv{7bipzsX5Cm=|kte z;P;*7iB+=X!*c6RaE>)`uu;?=PNSG^c*8xqEtc=D%8=rfHujo{EnH}iNB2niP` zqb=8oL{AwCz9jD&R;^%a2NAf}v5l0AoY8hKo4v}?Or%}SJFpSDB@s(I-ZC#-l zYQ#{+$Z5EozAR#eb)1om?^=gJ4y|=4%47e6=ZELIo*(Y_`*r_z|M0%vcj9H+^YU^B z(SDP z*G$eYfp-)RUx369XPXiZ-&$9CL~r}D5^nY-VmTH|SKD9{mPw*s%`NmC0e1@oF+0ww zYMprj7)low0N|yiPXvOqO9oMk>NRnIqeKL(6xAZ`K#U12T$-0694Dh2@mLXvZtl`= zP0`4z=!Mo|HY4#Teh99+veeMI2Z0Q^_prF0wYe${S=90PgAB{UObz+_kP>|5)UvA{ z;3cIK2}m*gS#J%4iUe`h)Y+X)mO*9dGG-CykiHm!s@&>kJaMb;BVs@+k{+F&MTkh< zkpe2OAPfx?r0h)?m((y9peE14jE2WyAtL>(d>>GHNtj|7r0K&g)!7dROzY;$!PoyQ z>nb35M=l-Qo8NKYgzgVufo(bTqr;kZVS_SyEPPenR_B``3)0t^cXEQB{1`m@c*TB@ zndork!VUPNv9*n^{0m6TP|R#+YZxF+W0?_XxVW{rgM{Yx&Qmv5vIO0@>i!80^2>cV z*zGnsP2*h(WqY&K#ecHMR{?bjcUxnhO6-cUF44t->XuPVh#tG>UyWFxdR9x!SehxMS|jwM4I@i|=vr@%6uqPYG0RC(pD{U*CvfAU+?S=3-; zYanwHEO*7`zSbGo{29(xW9{1T)z&+{leA0ip688$sMEn=&;QJoCWw6WfwpUJUU`dT zYGfqC@57y%hIH2Ffq1Mxc27)X(f}}P8&0D`^}kj&MHfwf%wsx}goNLU4t6wMhEO+8 zb28;XtUDP^856bCE3|?ekXBa}$k8tD*+~+oy-np?Tv}#lxK$V$ve&Zr9FC}#USLWs zYvb#Q`eP~%54p~8iS?TesT}R>c&MziG?Id4HNLU6l~UT2J1VS_f;SiKsVog~{iRuW zeF2|ASXI?O$q~%rm8`QOizV4LL^w}bHOhCLeoh#TmVgd$L#n_V&nYwN9SXQk^%|g@ z$ohKL0hk;Ni0KdIze=g@f@->Xj?NzTLmKVgNy6_6LrelEWFIPeSm4x8P|Z4k+5Mow zXFY>e*1{?2+)vKa?njc9kE)3^Gf7AJ!33Kr%+8sr6tBMF;+AV&C4Mg#E8W4Owb5J| zeYtV+CpGsPGfPddG;o{mme2{J)x+zN#<&7EMUU}X5dL6hQHUL;LgG1>yYrMVl#e$M zVj8M3ECD;_wZ80z(-E-Yv^k?kMA(rHAx?`i!N$>V`@b5?^hjXt8Z&;SP&2Bgp1SRF zz!9@-!?`w8{zy~tp7WC8g!H-h5Z-^93d{z^eAs^P2SE;@{B&39Zc{K$r!`kOTM`i& z%_M_Pawtg4t!CWe!^tf#Yw?;J)FM6C`J=U=54dlEi}8kTIaG7;0hjv!W1X8R(IhP3 z5za*Pse4^q6JbG5kHv4F1ZS4HpJNSxsWy0=+<13=$a&Nl!^*aEpPEMYAiZ9njlHO4 z*<;p|3kZY+kJDulNvToZOY9+q=sVBULEYPK9|`dWP=_-;b}zYS^`eM`Pm7qjqu-is z8aw5FuonVpnTeOJ&Y#}5z)^zZScyk&ZqbPFp9Q*(B!G|eu%9C$?Y|PJEGq+tA3ouX z0M9y|duZD{cRMPI@VYaM@%v*bJf$vN8exE3rM&4ueY@;H4s+aabcGC`)X9GSR&Hs{ zj4wD07FSa3>xke4HoMyh zybsvf#mc-Pg0c}qSz0K7wF*c@86ReRMG61LY~70QyZyA;p!JE*X=kPk_xM&1*Eas- zU|q!3cV1_~CQoX74NC|2Gur2tJII$pMu&6N)WYho1zb zg3}y-e&2MHoSJxipjjCbU)2C^@4gS^LyQ^oA??3%C=kQX@-=U#NdBM&)GtQ(0yHAB z$z>HY(d)I1Q4LfHY^+U$B>38kusqMTuFm(nzlE`R3TVdm)nmJA^H-Kl*ObF$x);zO zuO@w(*e}-vv5{X|r{8>rvB|3OH-j7No6igkq~;a{Yv z5I{UclF2=Fc`xG+b9<}70I*fn(7F&S_huWLkNH_4`H9Nn&Ibo{D_dHr1>^3204__b Az5oCK literal 0 HcmV?d00001 From 899e78539c3bb0e41e4d56aa854c4806715ccce0 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:26:06 -0800 Subject: [PATCH 0873/1062] Add files via upload --- .../bangle1-cube-scramble-screenshot.png | Bin 0 -> 2924 bytes .../bangle2-cube-scramble-screenshot.png | Bin 0 -> 2198 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/cubescramble/bangle1-cube-scramble-screenshot.png create mode 100644 apps/cubescramble/bangle2-cube-scramble-screenshot.png diff --git a/apps/cubescramble/bangle1-cube-scramble-screenshot.png b/apps/cubescramble/bangle1-cube-scramble-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..d75a60e8163639f3c51545a239a6ccfa302e39fa GIT binary patch literal 2924 zcmeHJ?^jcY84gJl(1B3QGNl+aOomQyr*McMBr1-An%nW~*4Pkx>`qC#K~{-@KoV|s zb+S`ashS=!37xVOav+^YC2}RX=M+juCvbuZXUK&}n$jeNB*2A`Uw7I6uunUFcz=C( z-t#`sc|N??-eQ*~Y}>t!Kp-TPy;)Kbt=|7e+}3C=u6A}rOU%WJ(&L2w@zOmU$p3G&#Aj_)h$S$pF)jPn#>JEkI?%3Jzj!ZGllX6R zak;t2SXr#~Rnm;x@P`u$tWs?W{!-{mYXXPSRkKZ%ILmJ(%J(zawkjdVRLBCwGdr1m z+K}GujQ{@8_|0e^FmLk1xsjQ%KL+;$Z<+x)^D1@wW|60 zqO9fRlo8YbkBU6`j(K$2(%jH;WxrQ*C|!uf$*(KPMPpwvlELe4< z+6UKW-QFTQv_?TJY5SoW>2y#)qtx&y6=B*ed-EuE@t^tw!aLvPDnNG+50=jKT}9yU zv&ZSe-%O4Obs_54xF_`+OY;SCEo!hQv%1vCdYZwwGdY=c{uTiOuZ2%2aDnFma3LZH zqERpajD6~dNxAQ}$g@^;OX30bDM(Bz#*+6+!*GD}q8bRe&V_F^DknTkn**U~KNviME}{_7Y?dsD#HMPjSQg7>nI*)>5?MiUpT>H^X2O3DX=im; zzU;jRs~;44oydHV$%XnJLR!UX9q1LWmTgASomSYc4S`{}AZLiA{~^dwBb_GpZG=gC zl2OM2NUCwbofUCOu%oljc-h`58ox&W9v8bWMtU=^!3v=`_HQmj##*L*W%ug0zDiKDv z3L+K>+pbq@vd?QT-wkwH!Hv(Z1JN7X<&W65GavTsB}Zm>U*x}#HP5(?Ryh5FhGS6o9`lgJZ6ka-VtP3~%Fx3s}PYL*U{|F3nzH>bJ&+{$6+(VqRubyM{jy z*`~G}4Cj6kAP#bHC54gqq4*x3z@7%(oM!`)R#B18SF2$5F;ylGuYDjuZK!x^30zV% zm`Ex93F}Zxn|cqj6K|A?Y~X6&9uo(!_?C!FtnNYZ+o~;(1J@t!&RMcM#jt)%j-~ls z{*0ytUR1IJ9ede=CQhdJ?7iNcU+KaHx=7%sOOVM$Z_M*P_x9^2{doA`ze;h9tm263 z4Prxt`(w42969}K-`kGPsPuEvm^0WMH`7a{FcN0bt>U4ty8}}JXQYOAq=vb#W_lo1 zfsp4^Dver6KI68W>~1C3W~ke;KMsEP(n34LcAu4M%Q5-z3+YuU6Y1=zRV?&WmIQB1yrqEj-v2W?0X=B8-zEAw4*yi~eI+y(AK30^^ zLi!}D4M~{nF6n9~K)N^EFSAH7#C(?WHRu45+`lg?k(fY^n literal 0 HcmV?d00001 diff --git a/apps/cubescramble/bangle2-cube-scramble-screenshot.png b/apps/cubescramble/bangle2-cube-scramble-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..b54be04b821bd2fa614715e5b50efd9805004603 GIT binary patch literal 2198 zcmeHJ?N`zV8~*td0xT^pD=j1EOlO^^HrG~}pk4ee7+7zb5#I6HWa?98wX3}41z{9p!#Wyag^O|b#<`tDnbJWZxdt3U)HmFn zxQCBv6bVSFcv`JG%7bmg}HJyp(2HsKonY~FyOeK?C~WHv`!)i{;&wa*vPcLzxRW#0BYBef`WtwAh^=LjA~O z%31_W`rWpuCUcmO)@0z!P1A8M-vne}mHY9^k}ck>-;XO3=dzL7^dDHSl9YtAO41U& zohT#NP6f?QOD8aJ=)o;90z*sx6M`dCKol+TXN3mx7x%8)Aj5V+ij8)Xw{oQ@|CeED<6^9&TOS!g%TMyiu+b`HA9Tk z#`ilP%?fOlOv70UTG4&$ognh%k-R$p<(z}6LCZ`^g)nKYP(L~$KMBRxXHxH{7Td>P zUbx|tEc?RVcJ7wpbi^Kzl(adxu^e-Rekbo`lKC`~y{2mL*{TNR?5V8m**5h($33>W6#lbJNVcspgw2@xVEA)09bbxsmP8MY=G6 zHn$&}_TcR(TwsBuoNrf&nr~03=sAwpX4H}NO(``PSq<ql5e(eW01}?1zQ^DJaq9x3+jgQ1FEP@kmH@!g3%;=!oB=@^sv@kC{UDY^WIVC1` zZ{UW8m`^_pCCbohPA!WD!@D@G5DzgGb znb6*I=D759i({Li$r^^TXOuH58$1NRiNTo3C)(BTi`~W{gE!B$eib_^MsBW2Fh5^O zt5HhO&FHhPu&nT>IVp%Ar}P(b2W{Q~#+RZ&Mn8O zQOl5S;Oru`6O|F)v*<7i_sb29PgPm-+L1AHV#}%ng(qL z#iNUlh7c$GxuY}wL*i+vc=;zNC1CtB&38SUk&OIbh@BMAS9%b`7?UB{Yu8_6AP!G* zw_{lMD#k^v_J<(!Vg J;i2^N{{tDZ7$pDz literal 0 HcmV?d00001 From b2be26fccedfa52f00f275db8050c0c3e532859b Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:27:07 -0800 Subject: [PATCH 0874/1062] Add files via upload --- apps/dclock/bangle1-dev-clock-screenshot.png | Bin 0 -> 4058 bytes apps/dclock/bangle2-dev-clock-screenshot.png | Bin 0 -> 2461 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/dclock/bangle1-dev-clock-screenshot.png create mode 100644 apps/dclock/bangle2-dev-clock-screenshot.png diff --git a/apps/dclock/bangle1-dev-clock-screenshot.png b/apps/dclock/bangle1-dev-clock-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ac136e48e7389f478df88e6afa2b92c23c6112f4 GIT binary patch literal 4058 zcmds4YdlnY+n%*rG}LT^RA?OXltQtI5ppi6Hqnf!)QoC7YzZX>F*77ZCOX)AcQSSo z&0y@2$Y4fo)Tr=eoW@`~ABGH#;~3BCeLuY)-Vg8l`#oQt4{QC`hwJ{YdtLwQzV3Ua z9ryA;sp_aA5D1j#VRv76o%wztmEpT1e*7N108zdku85M)+r|+HwFjQ=F1T3Y)YI0I zZ7+W~X#Y=#p*5b3n$f4jo~P|T?SC>w|LMf>$y)K&j2a0Fx8v;Gpfpy#&`(9v_`gCF zgZ@2}hg#5!KOulGNOb4#2(Y&@HsuEZI;Q3vZ~;Wd4i2c>L1>Euh$Om-K|lSr#~9j~ z{|y1z?4;4Kd-eWsg9F$7Cj|bwVX=e9BQj^^7>s=U!ztdrPV*=D#=hkfQbLDYae<_4 zkTFFX9_>~yJu3M$s6Jyl(*-`B;#DQ0BduG{Q;}N%YKJef@?@lN8f^jAKA;J;cC37~ z7Ew3MiNUC)p9k#|6%%rfu0rJ9|2nS!I|%w+@*ZnIR4_O>e*}S-nX0EJ9Sa!l!`QPG1<9~*b+D{c$KvzC>e;t-jbyL z(H@00$nKNq-agH<(=R7pWKcksGK|goAJ`MgzH?=$(-_|4OkG|#nu)coCv~ZpZr``0 zebTy;!~L7bbzu#9@YxW$ltl|lc!|GR1B6zMW1WOk;)iRxZq~1xC zGh7z02y+zh1f;Gqg-~9UxU5nHegw}@LHif`)obqDO+x85BjBS7L}nm@rv^a$k-TwgL*N*<*tP(3Ytkz855+yQOGYP z<$wqgx>Q6Rp{~{45+>kQvoV0FD{}D$ZDljH+_`O8P||H=Pg)|xKt)Ky07(9m+BWlk zo4V&jy(tw75icF<&nGv2Eg8+J+`~50ANCeD($~ZZIJv-a6^cnJq^Zv=q~P9F$&M>< zHR#gq56X=CQyHStF6C&Yd_nO5x#NTa=WxpL2~5W!K5|j?gA4zSZf18sXMP;~to%L! z>745g7Y63vRv2eo`FHyY4^i43m#uf^DMk)3YP7c*-_P*Pl(0gjf?lXhey>vD6u< z?lhgIa|-M498CGX=aWv+pLiK__Wp+*R$G9zd~9A8A>>={i#cht`2L(~9A3y0cppl6 z_y#Tl5g`PU7yQygH#chQ?Fj^9?q6oNOfTZ4m6KJ2EL+^t*o?5jWndu)?o8Fv_6Q~ z$3^L=`|i{uAnr<)1v6!*Qrvz%+u@yZDMH+975rDQaE|#nBIR7r#~oh%#{l%q=&Z-1 za;ExOr@N<(`YWHSmsaX=KW*CYb2h2IX({;mq?4sW5O>h##<4s4QDm%HMa=YKAXoza z?Z~P$e!bxpvz68U6yuG@h!a@t(W{k%;_4nLa?$+Ht{V@h`t~Gz=O`wPimyH32s3cA zq^HdL3MA23v{Gn*F>Va0jpgh%tvJ1s*+I)R*`Ds%SAs+oO~*jZZ*KqkN* zJg$liJB9!v&N#TBLue&9) z#Sjhz{@1`^kfI39pU`!`loqs7u*0BK6W;Pf`7JFF1wcK&s4P5>pqfuote<1_S644x zeL7ZYeCL$ZJtD|Q@i0?|VwQ-n4R`3mr~pR&<|;VrZ(rhYS<|rui&kl#Zb%!|ud;b5 z5cE@Nb_Ci!>s`tn^R(*@Nxv>|E1n$DrW;RGEPQ@O=49R>NMFT}t2H{q)UBuf@Q&(W-`O2WDAs{0t;*{bW&M@yyTD*+Pu|a3721Pa?ekevGXc>+Y63LVd_O!+wJAL~HIa z(N zd8i>b<6gVc+&vQ%5mL4&Ci27G3SBD|hZHUa2~r`pun40l8p0 z7_Ivj!yYKqpq(~0$tq;zI9WSgyh+7z*n@pcfS;}YS_l2A?#yp$DYJlPA#`Mpp`Jl$1_C6e=G-vMGN#eyHcAmmfv|Wi zznN`JasZ$w!MD~U&5UTY-~I?bha7^30S7k+=KyV38XFI6a5I4YZd7{!0Gp5o{efer zwt{gW(zY3`hCX=+0hqfrzeT@?#TSNay5BBDT}0QQca)Qmd>*UM}U-uTb-qr(Lfz10PJuNS{Yvv?29 zJb2+65{K>u-TvlrEZc zH&kD!HF?0vbeyA)ycqDAZ;j3)bap9(%7qe>+TMhP=Dzx)!&afAkXP*uKSCqZl;_`H zn~ioy{@znuq{wleFr#b2SP% z>~Q}4snWz1rcQVL*fL9xEys6hZGoG5&4&_*S>jb=;jxxeA%5e(CWyEy-$g7xmk}Of zlGEETG{S=P60eRAk2mDa5(Ay)wNI)Pk+Y-OxZ>l_D8X1Xs95$e39s=}v8tN30q^Ym z%NE&_h5&0%lz))fg2=_nd-qzN!cw-!KR%NQt3K0ow~F}x1-MPP_Dx?#a@b>E{Y8N) zL*-fl0O9VFq4mHTaGS;pFP%~<(mB&*JYMqgl8u5mSONGYVmM|}-W{A`y7_Qk95b2` zSH)Xqj$Eg)(l)Q$w(*TP1<8*n1CLSNqCcL|UpBnNSa97uIx=k<3R~LNgU=>|=RVBt z6BVp5ZfQ@kLKMxmtV(X2N29k>l7~wLVUrSPy@M#SHQ7Tcg_c*mv@BuOe)ExyZ;Li? zU-@U#m4t61XxpmYg~#cpxiQttYoQ0oi4~hpt%cSK%r6oqO}PX46TgXbuO(kyWor5l z?+^9hqxK6e!r!sp7AiM)i(|#I9%=f`bnIzHZyB%Z!^`{Cqgwzq3S)oO3Fg~Emd~rp z928ThQ}%T^O6jGJov1UN%VEj!Yn3E5yqqJuI7(7Lekv^R73w*{l-f#nt}nL9C_GT* z)y8OA5Agh)rBRdl4J0C8_0~mlBe(gmYNeezb4sY_(QF=8r&ow=pXpvSr)Z59Qirh@ z;q2g` zbfq!?CD85AN>D1SIK!}G$$V@0;bL3TE&~g91fX{Af(BILf&fT)Kez))hOm7ib?+NQ zqF|lsL>s6gH^PqM64uJdd`U75q#oDXxbUSkMBbc6&~y`z41Ju)J#=8d(h`p+n@v*6l0e5xol0uFDoaPzH^vxpj5qgitw& z*0?EyW~>t1ep4d0T*!Ho9pIs_-6Ly@#rARO9`FP_48@Sn?*@XV$ePa|n}aq=MP55) zAI}65ZrtfbJmAMSFX9zq8NQcXW)uLOS>9L#jdy?Dc^FHA!c;pC+--~PnBIJ{W}9!a zuav(0wT4c>Zo;m8Y5Wep0(vNot17MnL33_=m*k1Aio_+W<%Q}puiOy%^wAo&&DuXgfR0tw{~+7Jhx32ipr48f3L9V^S+0kF P))Afuz1&M&1Csv*&{v1? literal 0 HcmV?d00001 diff --git a/apps/dclock/bangle2-dev-clock-screenshot.png b/apps/dclock/bangle2-dev-clock-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..0deb6dc2eba11032fab8818d443b3429fec32021 GIT binary patch literal 2461 zcmdT`{XY}>9{+C3N^z)^ry@u53Q4COp!i+IGD%CNO=?T}> zT9S=yC>zt`El)MqIFoVAL&Mm_B;vTQ`w!gv)4k`1&*%Mme?FfdKcDyKo$c?7)n9A2 z7672{b;u)N1*`rQYv3!HS2sjl0m%6PtUKVgZ&3oE8|vlZ7DOegih5Zecj*1hSz0c$ zZ@HbAW%%p%^R}x}h&FvJx4n_)tHV_^QUBCj`r>7%+a@aHtpDhtg*qtETT{9T47}Ig zz&E0RY2WDCWvc3#YevxOrYoib0fZqos+_e zmPN&h;#vpkdvvS&yu~HeyA`=gju*l@pASRY!i{oh!pm)=g`)Xym@G zW3n<@hNbG%T!UfRMy=U1{66_& zLDuk_Y#*FMt2o^#syIVbvr)6-eINu(mfOVogaQT!=X=?8W#@{?66pmUKz%lJq31ts z4^RM(87wNX{KveAQ%0N{2Y0G4F(&TA?A*g&HY__6nkA=9Mv$_a5*ZR_I8~ z`_~<&pgSSb4V`C5D(aM0pyey%sUi)2S*{2nVJm|sQ~O2T-tq8v7D33MjTk>pQVRYl zK3%@ZU&1pfv>d!?@yIuUX%B6EDTLH#O^DRvW#p-4UBU#pNdYrBU4N&;e$}E>0UkdF z61{R=1MwYqdXC@f9`s280(ZAmv?uwqd0*O8s!$cJk)7l1lT$%ryO@|EaOFyg@dy50 zu56Slj>E!mnB$q1DJa`rQ;tpX6K&XQO-yVDw&KOJ!n(2H-(Ukn<6`oltJ7MB=+M_e z4r~I)j52y?55$HW{+m0`h=4R&G}A2lX^Mjlp3{~(Iq6qA2uLWbP^)^`u2$v4;fd(k^V4GW{P19sm8g1VyA z=euTm7tVLpY|chNSf1&zp6V_6;&T0aXwLZZW@;cTa19RTHs;)cwA_|tDY;5|`}yjP zuP5MfCYNN>#TREhVxnytI%`|6vztRGq2}A8Q$ninpC9W-btD|R*vItKcY@(?rM1V$ zBhKo^bc8fNxj70ruwa;vxr+?tZT_BVYfc?}0ysEr?B7u->^GDYsjz5EUTXBN;gh%7 zBGvVr!dfF)h=&ZNKP34$BLR0SwyDE(z?ZP`xgA>=b81bG-TyyX+`%XjX3mzk&95sf zseO^0up;-J9POnR^gqA0ADd#_Aa;$Z=B_%5>}I{t6$XEE0KSn8cg_`>$|#|1=ZA&F zS>A05-BF`|&v}3Oa;ld3MSk3;ci{^PbQ24fZBYv&)+z_}d?@UY<1KE+ebLDNB8QY# zv7nHIgb^;k8mgCuYAC>4MTw}5vl1dB%^bfq@I~*(!scC&>VbCxGNy*2Ove z$PlO`BE$#TQB!}1x=M)e9E&~NS#ug^oaB_jMz0T1PcJ$g*A402Rh{+z670nKjm2aF}zCtd;OG6SP7IozK{Jq0-Ml{ej#$twZv$%5kg&z%ka~ZS-1D^}z5=;mT)EUrO8AL@n>T^lod{ zB#(%8% OE#URDuLswil=&ye?Va}k literal 0 HcmV?d00001 From 93c113a95a21dccfde6bb171d15b0f93e6d0f58a Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:27:25 -0800 Subject: [PATCH 0875/1062] Add files via upload --- apps/dotclock/bangle1-dot-clock-screenshot.png | Bin 0 -> 3610 bytes apps/dotclock/bangle2-dot-clcok-screenshot.png | Bin 0 -> 3107 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/dotclock/bangle1-dot-clock-screenshot.png create mode 100644 apps/dotclock/bangle2-dot-clcok-screenshot.png diff --git a/apps/dotclock/bangle1-dot-clock-screenshot.png b/apps/dotclock/bangle1-dot-clock-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..767cd2d55ffafe25f12cec273a38746d9076d897 GIT binary patch literal 3610 zcmc&%`8$;F_aBeP^o%7lgh-4SWnV&;hOvjU54 zZ0&Yx!G>>HjtnZ>8L*dfGzB-rah)1hoB8ufb*E!4lJj{<-YlNPfEMyMeBxpdIqr02 z2-H%F$;-3@X9~1@kLYu5Pkj+43pchaRi8aMuk9!s?~P-CS8-yY64J{`?!L(dOp8vw z3zt@d0mx^V*9QJ4%FkIGcJ{vpjkY_<7Y3epz{(xA;CgW1>Y@9SN7(w3{mhtew0U;{ zfQdC_DBp)hX-jP~pakK!z9cNEZP}i7|C8K6e8~xRNOp3v%wcf%l$Uk)c3bDf0jqEv z0d8L1ACoEU8vVXQ9G;L5mHnCgwyMUcJgj_IM#Et}{3Bi!*bW+@B{hQ%4>Edzzs74O7}Z6IjXnJkGUNORvQ*M*#>{FWA&=xs{1-^S|2;&ZKxZ zOy9JBhxcRkzk_m~=f}uIqJTF>{Q1K{qzxXakR6B(E)1fml*j@-+G^Z@9J^e)Ut;%x?qCXcucx!)W}o@{ z-&&s)p37%AL5jae)HK{<$!#UyhyiQe7&yAP8*}{5&*mtH!;gYx8v`Zv6DIh1!UI03ihk?5xnI+bp0u}HELO2^ zZK@8LGEvvwL?w?_zdbv*9M8vp&9`(6cYmE-9mvxzEPgH(?{ zzha|6{nW4aZrNY|cG@`@z-50J3~~S>%>$XRi8aSfHvrL)HuvIoRTqcsNg6@`lJnzmzio>4pU*{nM4W6P zpEGF-36?wCpkgRZFoZs$7^}Wt;8UsqeNG6JP^>jQgEn^CG6IEhA&+BI2Pxf_bv#=xuKt_kJYAoMDC6VY#a&5KfDn}zj<)QZehA^|7c>=GHQ%-j zph5YK%odE#FCN9HtV$WM7YZ3Qlcqavl0$+3s3CMuQiK;l#T^HU3z8_WIoWcrsNnZ(1Zn+aI;RI} zQ2F6H-|ekV2Qfb2Dk!{=^A9%VRM*iG3;L5 zk~ky1?720}`r^pXmc(Z(#(Q&bpQ!#55nqcuWje?0u3etr{q->K+T$8#NtFa3yfW2DC<;h&(eaL9V~J5|@=<$-?0CByyPwU17lo-dMpdFQ#Bo?Q#>@!%bi(EK#f(#KD;Dy^#HzH*CJ?eM$gwtmIR@#dQG%WKZg&6-WDFpY8DHQNaO^Lw5K z7nGr!wi-0UHJVG9bscqii5+jY!rBExg3`7(tq#<)27hlJUH!5$uuwfne4R6})vK70 z>i-k8;VBJBkaY3}`~IYUeEQ?X05uk1Hg{G4Y8!;xV@lB6Mqbl_Upcs;CtIbCc_^Sc zJ;`ZqHTNXI4GI%sp?8%aD1ap~KvO&jtqUBH^1F7uHwp!YVve}DvF;+r*`q)PDHIO6 z#z|FEyG8)gL-)cC3n#A&;Ybvm?6*gjs+UC7@nqNg7)GFE>{<@kyrBRR5>2Rhkr zh{U6S?8lcbKtpV@P&*b;97oyR@9qPd6vg^c0is2gALAXyQtYm<>E6o_=s^dV)@#X- zzr>Q-C5$O0S1DuHL2Ui`E@(ecgGtkYuUwQ3g@&-I`-XPWeP~RHhsE-CF zWnW}OHLK#fo@h}cgv$Gxz6fUNO%&vGIvrahoSTA>TrlrlK{G)p)sRv zLN{iyGbDeYp;yoM^@>W-y?vv&B;-T`q)>1HFdLd}lwEq_ks1G?<=94xJ^+9e zBLg+wii{WM=cBpzX?>}%Zz2xyqzQ(MXf+qkDcMdmh_0>nVBqc7KYSmk9B(e%Pa04Q z>S)-xzPi+Mf3@i!i%{TKVOv8YxRZ78#~Bc-9eaG+hy+G-WUu$mMWfL=9}RsbW;#j7@)f^I}hOlwn>T{RR3k zoy)EzODD?-JJ*#Yrh{K5r#pLom9tNnF;HYgBr{o|iof zsg*Z%x&dErYF_R77^>{GI4D*&n?hXd{9IJT#RHBn?jG7qzFjlzMQmOg6Fa&UCS%a} z3flRH-(w?vM(&1{J|A4=utsdU={?+*EvP+@8MW=5)e-2@+=y}JgmvKNV8-x!tisgC zA7xq9d9%Hi3dQZG7)O1m34W6KYXL9q1*e^(v#QyxEjs^c-bX3_l81r$`CUCc%v=;8 zO)n4jE|H>x1U4+N20HL`Q1^ep{BT-epySI82a%Kx@5E%)Y2nxHI9L+0$Xd`c)Gen) ze4n+sJK%rir3s__^{2DWL$6&FJA<=sa)CdJm4V<3#teTSYX!WYh)oeP;O}fGlPmMy zQCHb3%c9kY^2w-ls6b)HXV~OUGqr^C5bKd|vEn;CXlDwZ#OauqI8bDv;d? zYrdwHA3hjN5b2O$i`u90B(18>dg_z@h$C$mukGUR*=0-7g}zxay)6S-A}6z`u9jIX z+#r_BG4)#@OF(Klgt$-|i3E8QcDORrk!K_&K}8@#zGk|>jA27K$SVx*;h4z|w_}8f zVv}=FK#!x4*iBirW34pqZ@fZq_Pyf0NeyG=SqJ>>&bK|Y8uzcL&+#nE$&&<>q_s8k~BlP4S zt*D)I{bMJBa9Bf7c^_gC1mgME%-G<1sOz%J*XOg^ypu=A;siwEcu*tHCV$1 zF-c))i<+nMmF7P<{sx1aCNN;oml{FPgIExhSGLdu1h&e%42A(Q95ewC$+iU662oBW z`+)UX!;402X*KN>@e^?E;C;9*l$Zbi7dKlx*sF&n#rQ3^oFpx)AFf(5XRovmbdMd! zwADn#Ft~ML1;{7AkD705r#MAVG{B&`jFqC|*c8f>w>1s5^u>R`TW^dR_JHnzCiB ze%OfnA$An03TTybl_?oM1qXN*DxMS0PAs)O=tDtI_3NNs8gcmse_h|$SS{P07*|Ya z#2so^14JTIbgz(_b-P;n0*2Hbc3wFU51iLKi?%`GaGh8?_mv2}qLq4v82b7M0LipWf@|VKZ>V1pV^GdHN-v1Fcm4^`-_tWTZq2&Z6ZZA31fBvH2;)8^!V7C$FE7Bqft{a2(1+|fE%4TW9hVyj%MQoN^ccUzIL zAaQ}lSsXpxbN2gIY^+(20O^f&y)t2S>LkB|HmkzeeXCfNk&-kM^>J{Qxj8g*oA@8KZrvwB{^DeG;GJw+xjSYnQ*->hZOyhJUu4(?N?*g_*`@I)GDgM4Bm6kY18!b#*E>+x7nN(DR z@H+3R=Rg3|010r;e|LH|FY1@nFLTxoV%oFzvV%%{l``gm(}Ia3^^|(MA%Y-sWiu4< zC2j2*O{1wfC~i#LOT$f+*Oxnj;^BP;90cvzWTd8@lf7>K%p_|(u(vuaSu?Y9yEW{A zPe0K`b0Ax=&OWlEybS>asd8f8*GsfK75!in?p2bPHJ=R@r5r0;3Vu#p#09)GJqofh z_*wK=phd%z$2GLHzMDxqK9DJOeB_Dm#0nrNtI}|T!vQY!kv#>VgAKTOIged z?*X!NFC3=GKE30>qq-MAq%vudC?yF@7LCw zhf3kA6=7tr`SO0q4w7}6^jv*F`HO`>?w!8ad?j{5JTuJFS&7}p<>tRUsifv-?5AYA z^r`)_sQLTwqgvy8KP?ex$?F7d&w?^NV8B>`mo%-jv%ejq_xrR>jJ|Q|3oCd01w#(C z{f9q`_-9y)k^LD~X!c^fb~CO*UN-%ALcYnxiy{y!XaD4Xp-W|qoZB}@;sJ=x;*}<( zTf>M9G}SdklB7e2J}!g1leV5Lyh+7{GWi6>oi-&9=F z>x)i6HLY+2Iv#(6wo2}-3?<~G61x_S&#N(m1o$iMRp8~2)gjD} zQXr~JWM~`5V}&~uiEq@)J>y1E;XwZ^ zL*`Akz6sP#q+_S$Dra9O!64<0Wr$9Wex{s#u7yr=)$u5|pGz!VK>|Z6jIaISB}skI z=>HZj1xQ>+6|$V+-bLzfWZ{-!Qe_-jU{?68>+*gza4eYeyQa)JZTp>J74YpP+Ar-= zQY$9Em_gZMDufuq$A9Er{P{g4SJ#`E+-kPK+8k=*fUHRSzhdOf5lk8W62Z1+qc+Hg zoDh6|T>?6}hNit{Y&SJ|Xba{E;3>rwsqOpUD`)#nUM^keHDF=%-tXX9iukf~*B{OA zC2@!-k9o;-7>Fs-6~czK&sU{|6BOyP4LDeYLuwP%b;!>n)Ez?dbtzgMUN-d6yAjzR zL@&Ua@(idronmJ5L@RVo)l=@Bt>xLdhuZ(Ql#Y34s88D49P{f#Pb%fofmc;+QVs7S@T?<>t*dbL;nNUjoerzEZlcEJZUSyw>mf zM>F}n?;oxnJXO9dIQiYcstZBmir8tJmG|YE`XGpqV?7T>s5V0|kzVc$@^Eo`GgcN1kEe5Vc zx`8{I?>N)O9H!6tIUl| U5|Y32&+7-7VXTbH4c(*v3j+hhhyVZp literal 0 HcmV?d00001 From ffbd331c1376c5f393dafe62eaa62a9991949a3e Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:28:29 -0800 Subject: [PATCH 0876/1062] Add files via upload --- ...bangle1-large-digit-blob-clock-screenshot.png | Bin 0 -> 2879 bytes ...bangle2-large-digit-blob-clock-screenshot.png | Bin 0 -> 1972 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/blobclk/bangle1-large-digit-blob-clock-screenshot.png create mode 100644 apps/blobclk/bangle2-large-digit-blob-clock-screenshot.png diff --git a/apps/blobclk/bangle1-large-digit-blob-clock-screenshot.png b/apps/blobclk/bangle1-large-digit-blob-clock-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..fcad01e50298f5f4dee15b746383a24ded9c3642 GIT binary patch literal 2879 zcmdT`X;c$g77mpH6BI~751?TQL6%W=B+v-RDh;6x?q~p!MT|lSq6B4;qFF_7WD}Go zA_%R32#BFss}Wg5fo?=VwgR*jh!W7y5m~zEp6NMfe$1J3=FFU#A62i;y|?P!cklOo zx02oVILSzFk;Y&!GR|K)>_b=K`h}B1@74#W)6qpDbf1$Q=3ck*GzKI6$=Sj7K)Bbt z;n6geb{WUxn}zXXYpf_D1=4K|(Pbr+9jp4>S3cNZ_PDV%RH|^iDe}|8oWUw3#)b|w z6Kh|O>_~-qAWj#D+o5HHv_0yx0KjYsaEm;JGd00dkpQ|dKq{V4J1=~YhJpMsP#cIF z2@gN&6ayQ_*!Th<+tRXJ!^V5F%}pZ-z)Us&1(9-p6Qwr?1Ciwzvp9mg6yc<644j%+ zat_@F`~R?w&;nW$52)!CuZn)DxV_X`{@i@^R03@JW+C8k*4Fn_jw!o-$`Kij)9$ID zhG;|MTa6%X$({4zlF9Y>WrbvXWD6`gSF0D|1dUUSAjOVzGf6i{+&_t@N%G>%sFbl# z`eZk->xd6vK$?AS1gdI8r3}0C=Sy(P0S9j_93liEq^Y$6JTQ>gWl&EQ4#MmZ@+@uj z3uvm$<-nNqEg27=W!im8nk12L#IEOE7?tV<$`wYKWF@{XWFi7r<4mCT*rZ`4zFOgp9X_to`Qr#qSskR~1 zp8n0S0(;0FubVXVG;SscArwzj0r_wXhbC;Wt&&A149ZGsGX1at$*KMFIk0c%4?!FG zf&x&XHSmO;h{nP7oh3qeOBL!k#HVahaq$KcW7GAjpz7AP8N4e}`3iX$&s=l^Hu|iW zg=`+js!M{weA%mWFq4oVt7!=Q{jaKTN#lt4tcvho6&r|y@vK@-_lnQ&~*GuQ9Nq-2j@*QXjnCHAj& z3KilC?9mk5+^QUDoa{k&D>Tn+<&lS+E>#zA+ovbA4g_QWXw!m9_*VV|6t}vojR%Y)OX6NzcnH}6xb$1Tfn zHQDl0a#k)V1R@*{whTHWX3nli1Uq5e!*-GlG&}kLiKcbTaOPwh&}{1ijEjs#i+|&p z`R(47NhXP_Anrs-*OYNb7MIr|Iy|q(_8o2GQ8{@Blb#LikJVUQgXHXo=%gBQ1|^Ny7Ic}p_-FE}Ob%bowv2(&|tT7!L=S zGn#K_G>q%hk?E_6vJ{l5qqo=#B(aAVE;3Ejd&A-h9Vyr=XXSvX2L`ClY%_p*UOi$I zm~r0gj=Cr}VJnJ0%~l5@kMr=$DmswjhFQgSVz^NNO1h9DYs)k^g@x)tiiRVSVS}

    ?YNFpmm`Mb8iyFo8I^bs|%H$tQTAZrT6P4eH356pWGl~Bh>B|Hj&+^vR;z` zF8-EGq?Qh7{pkD<6K9!Fg-X~vT%Kmw_h?0f5EEN<;pXnHF+0R4)3eq;*a=maF0@%E zu{^vq)Wh*wQ~<6N2vA+BUjzaS%2Xk3l&cxV)A|(}HpoEgNIcIW24-9@u5Unx_A#?F ziqRwqU>EE9`vr8oM~`-Rik(5+auKVp3!Tv7a~j-^E;yd41>OBo09iDx2CzKDV84r#))7#K8eiMtY zv=$hBAagV-ZL#K@Iay9yxVu*SikD^^TcYmc`7>cAb5!A`M!TLcVj-Ouzx(s??cjUk zJB%d(b@w&v`qo(3Q_B%q)42b+F#4K1p|-0C6%d|Z42s!p30HbgW1Wy1%ZhIwx?C1W zR;l?P7Uf0-FM4^-;79!TC0g4Bg!nm9IoZN?5$M(R;U%lIwZkh09bl(t@eSE;YWi@I zGAsRx^5Si)=M1!k7TMqKp>j%CQN7#IK6Sh@#pQv;%8(0)^I&bcU`uh1ft|gd5Whna llvP}y2&RMoivh^4sf@E$2gt_<(LZ90v*R9zdv@NjzXHdp`ilSn literal 0 HcmV?d00001 diff --git a/apps/blobclk/bangle2-large-digit-blob-clock-screenshot.png b/apps/blobclk/bangle2-large-digit-blob-clock-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..5cf48bda711433e4054c62bc897c4a87414ad6de GIT binary patch literal 1972 zcmcJQX;hO}8ppG|hF64;1wz1p2{D#UWEC31Vn~84Du~FE3Sm*CQY9<`Euyc20}{ew zt&vSOnF<6WplmvQEhrw9tt~V|#YO@MT7klhfJ&H`Gv~~FnGbV%PCwlHod0uw_nv$1 zv)sIZ{ocCT#@Z+pO4nyE?;v6o8w124^R4#D2*jY14|;P^%>%Sq6iVx)4{y(*^oX|= zFHal0>fm}lcsMQ>Io{W&Sf4G~nOC7HIC{c6CaT=6f9lKo5#Oz7LG^$Q_`EHtZfze5 zB33)0fs;qvOT0z2HxkWmh{-G$MtNT=E}$%K!&+k2+Sa)^JY}9 zy4m7j$jV0opNG4(L&H=@bOcj-RK#jSk6(v#i0Plv08;&2g>*OkI{x7+Be=W*LHTFm3xVe5Wt$J)Xt7{+Zw$N#mh@-R< zO=Nh4H?JTp?>&zONzMD4sdkM%Xo zqzEy7XlkLJG^-FE>vQv29#CQ?kR|gA7nYMO8U-ur_ zBz^IiJQInZ0_}Y4xDx}Ikjqr)lTA`{Q5wXgf=WFvLg@C{+1~5)M1wpc72;!VM<$GH zN1WbT0#RUvaTF30Cx87`Pt|2@>-UTAO&5~#Dt~Z{WI^&y-r0<{<`Wo8{8$!_vcyw4 ztD4LhiEU@lOMJtJr6s|owW~GBcc)A4G+()CFU2!z|G3(%HmsxZW6;o#Grq^=X zjjaHc)Qhknpskk0AUf0Bk@)Zg_!L*5xHf5ThT zfY;7s=U(h0jjlaE9N!@j3X6ZjKM#(W?~D6Kvd}qbw9Cq1KiS22+IO#avhYSV_LkiL z5?(zV>zYz)W8FSF{<6lhF`n>sxix)Pd&$zh6mq)W zlo~Rci|6?KZ27c1zLobRJdO1GPl-S!Y`~cRqSLy#WwgSRU#}4&CAcv>#fr8XBvqYNTl0TRr zaH8eHUcBG21Q)5r{|nB>15&Sd4wqlG2Y|E?U4Rm1rZ`2;S6=@fiF)2f)dewiAY#YG zyl4s%{2I2?_eh1FkeMHY{`cZa9p>kuLTmRN_+4adrnXCEEe;Jd2WqttHq#ZLRAG$t zM1jd;?sg=PJ1TB@g`ae7m}1heAgSls9wZZf$;@l~j~2>`dq)pLlye#fVP$Ohqr>F-aQDitr0j(uhkpx!gvQ`h&BfFL zvi`p0Ew|0$4!E}tYCh+fgwi{?I6vbC+s4JNPju%dT`wX5Qdk| zjYQKG_szkCI98#Alyz5qRBm;Fgdv8W+r719qhMMyIclQPYmi#Dp#;{99)b&?{@~NH zkzUvzzVo9m;Ksttj>@~y7MN2xnVs3zeOEg!Y-PL$2tWkzP5d&_b==CsjuO#-M6T$W X5^;XYkL_&a8%Fu?_w$;$qVxX(1uI2e literal 0 HcmV?d00001 From ff62b603a27d6acea382af80496b6a35787a73ce Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:28:51 -0800 Subject: [PATCH 0877/1062] Add files via upload --- apps/pomodo/bangle2-pomodoro-screenshot.png | Bin 0 -> 1839 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/pomodo/bangle2-pomodoro-screenshot.png diff --git a/apps/pomodo/bangle2-pomodoro-screenshot.png b/apps/pomodo/bangle2-pomodoro-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..df599e314b744150e28434048a5cd2b40e3d2021 GIT binary patch literal 1839 zcmeAS@N?(olHy`uVBq!ia0vp^8$g(Y4M?uv{v-}aF%}28J29*~C-ahlf$gQIi(^Pd z+}k;Ci*6h6usr@=pHVtVa{0RKZidPSoR8n$4`SG{ec8${kIQ6^e>|$sVWL{#{9xHh z!*BeIEN(5=SOj*I*6{Qnu(&XbfoXeR|3g6u_9$V82iHCt+AuwK$Y$cWV`bOoo*23I(r!NXRu7E-+lM z`}47d&dN9QUapzXb-;SsxqGYj-hG`TYQdbh{RGFF(CqNKXqIywzk3SVJ1*9U8uwIb zl{{jJTe5;@rPHGKZ(Y;YypVmlyW>B1qd3PZ<|3~rZ}ti>GOa%l%>Bo`o>%)W zPOfkLFS-83zVKh+2GyqiCuJ*GUgQ+-j$7ttYg?K5V0?entWSQEI%Xz`1dqQ=F&%CEH;QlCC$shd0H z)q<{yf^E+hv&cEF|8ZbX^Zw1(SE=a7&%5_0bJY*-T8m%DIcz4Cf8Q(8*q+xWRtE-pO{z`AkM8p3k`dKo zQV7mgo$+R~pri8d)m10Y+`C(T{^Re&p#KuOT(xW$c$V};%-nkZbNc*HuA2F&$2;fT zeZGo+@pJuBrhcchY*Sk2#{TCp2b#Tp@q>GR>!t;6^Dmo!^ViL<(*-P~($wahx$0QB z{WP2O-D0x~GxJww^uLi?TpE7uCTkyCr0#q3BU2kTop<|gE1cIBx~#6Of99>t*=w2H zozJitaaVoYDfwgK-t+&KF7FDicqX0O=KW2w`@qBxY4JW`DlwI2y#JouXLVz}qj}ZO zOmf<7*1l7-ZA;IuV_5BU=8O@Sqw4%s=iclQ&2iiO=kupMpO0ru{nW>D&cQPJHQ$@7 zIaik_m;Rlavu)0Sz{17qPj~aC|2pI3{9yl+bq5y3JXjIJC2=us^QU!n2f}{MvyHBF zdbexQnN6Ca7FtuB;?@cL*Iw`->gU55Q_D`vR&3wH(Q}}S|44rC!~Q__NtlGWn{wLuIl}n?kj6dHJK8YxZ=BuQ?@tc;?@pd5#ab=NwpU{X@g0b;n}KReZfC?wz})@S&AO&hv|w z$JPk#`zouA&e`-I5Erpf+7xwX^X>}Q^^%eCalfyWCtOXq(&*6A@Q0<(B$byXghe32 zb&{1B6GzTLzhF^DmavIS`dS&7qBmR)ab;)>Rau#=*l^t7AcOz_ literal 0 HcmV?d00001 From 51afd5ef049c67f331931ee18b53e9d4e835342a Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:29:27 -0800 Subject: [PATCH 0878/1062] Add files via upload --- .../bangle2-simple-v-clock-screenshot.png | Bin 0 -> 2253 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/svclock/bangle2-simple-v-clock-screenshot.png diff --git a/apps/svclock/bangle2-simple-v-clock-screenshot.png b/apps/svclock/bangle2-simple-v-clock-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..a2050bb89c3a046b0dfa8e9c436c03135e97a165 GIT binary patch literal 2253 zcmc&$`8ON*7XKz8u_e+fLj|4ErO!hY zDHCc5T6@rD+Ub<3XvLBuB2&skwP=YJHJSh5y(l!+gMRSmY20u!%G3gIQ=4 z1p$`t1a1{*z^b$Xd1(ErBY4b;Q3fwt(o8f37Jn}_Lqqm_jzG+KeP^ZmRa-^Sv-ic+ zLQh4wK+ku9E}LETdlF}f``xmPs?)2;M!&aC!U{sJM&4YNoADT%#DcLzQvV=@P~2=Q zCnS)2DhgUvs zbK>R0i~!O+ADD&i8;R}CzJ)tnk~3d7VFB=IKCQYkL$LCFTw}h|#;} za0WQ5EUP9*b$^=rU@8I|T+sODTVwP?Rp~pf232N7m1#|${$U$WiZG>{e?BvE3gDkm z#39^7E^4i%ifU zV<`qK{X%LQE##?f6#*kvFH$=HuKTaD@bE&5_Z5PS#f?*4=-xc2!=B)_?3Y&^Edu~6 zLeK&QvE$Cy5M*5o9ssx6@@F8r@RN$OuUazx9^^oV)Id|rrkCM=PnDVoL{k&|*vS=U z`&-A`XT!uvGgtPPEC&9L?TKPuZZ@(5h5^>f|3F083X17w*5}|e$IjPzv*=t(DUDm3 z0NJSdyk#XUDh%Nj>$4CAyq^0^rmXsiwO#aY8eT11L2^K~&yQRRhMMebh5O8qy_)70 z*9Y5kXVZm6ua!`670u;dzJ;=EzOeMs)(n9kKia+88ZPG8h%r5y^){ae5<%z&&tB-~ ziR8Dy+Pjs_@1{6ZD!nB=aOhXUu)YAP`UOmK+|wmUhqlxKH!83)svhX1`a z9z{O%fc_tWEMYPI*Py(6pT(0*Cnpg4O$c4FFqZyw(DZf}kY)K5R~7~3>Dev#M0GZP z10?^cW-QDcEu9r~i*!M}l@d$;L#;t|v7a$S@`G-~iQGSOpiQFHYWg!uRAu34ljvg< zgUtOhBL_lzZ0oQM2~a2VLZJYcG?Pc7!(rP?QDVNHtG;!FM07J|) z4G1hZKS-xh<&q;DnSY~8=_O7&r>v1PyK3zppgu3~N36V-RT%`qXea^|7iVCk9nL|Q zJ#UwPZ`GR4E-B0*X;*iq_QgB Date: Tue, 23 Nov 2021 20:31:33 +0100 Subject: [PATCH 0879/1062] Update metronome.js print instructuions for banglejs2 --- apps/metronome/metronome.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/apps/metronome/metronome.js b/apps/metronome/metronome.js index 3d952b390..ffcaa1cfb 100644 --- a/apps/metronome/metronome.js +++ b/apps/metronome/metronome.js @@ -40,10 +40,8 @@ function changecolor() { }; g.setColor(colors[cindex].value); if ((process.env.HWVERSION==2 )) { - g.drawLine(39,0,39,65); - g.drawLine(39,135,39,200); - g.drawLine(136,0,136,65); - g.drawLine(136,135,136,200); + g.drawLine(39,0,39,g.getWidth()/3); + g.drawLine(136,0,136,g.getWidth()/3); } if (cindex == setting('beatsperbar')-1) { cindex = 0; @@ -55,14 +53,27 @@ function changecolor() { } function updateScreen() { - g.reset().clearRect(0, 50, 250, 150); + g.reset().clearRect(0, 50, 250, 120); changecolor(); try { Bangle.buzz(50, setting('buzzintens')); } catch(err) { } g.setFont("Vector",40).setFontAlign(0,0); - g.drawString(Math.floor(bpm)+"bpm", g.getWidth()/2, 100); + g.drawString(Math.floor(bpm)+"bpm", g.getWidth()/2, g.getWidth()/2); +} + +//Write user instructuins to screen +function printInstructions() { + g.clear(1).setFont("4x6"); + g.setColor(-1); //set color to white + g.drawString('Drum the beat on the center\nof the screen to set tempo.', 30, g.getWidth()/3*2+15); + if(process.env.HWVERSION==1) { + g.drawString('Use BTN1 to increase, and\nBTN3 to decrease bpm value by 1.', 30, g.getWidth()/3*2+30); + } + else { + g.drawString('Touch left part of the screen\nto decrease, or the right site\nto increase bpm value by 1.', 30, g.getWidth()/3*2+30); + } } Bangle.on('touch', function(zone, e) { @@ -106,8 +117,8 @@ Bangle.on('touch', function(zone, e) { }} }); -// enable bpm finetuning via +// enable bpm finetuning if ((process.env.HWVERSION==1)) { setWatch(() => { bpm += 1; @@ -125,9 +136,8 @@ setWatch(() => { } interval = setInterval(updateScreen, 60000 / bpm); +printInstructions(); - g.clear(1).setFont("6x8"); - g.drawString('Touch the screen to set tempo.\nUse BTN1 to increase, and\nBTN3 to decrease bpm value by 1.', 25, 200); Bangle.loadWidgets(); Bangle.drawWidgets(); From aafc93f09a818f5bb6e4b6988246e4f8d049df87 Mon Sep 17 00:00:00 2001 From: bengwalker <63957296+bengwalker@users.noreply.github.com> Date: Tue, 23 Nov 2021 20:35:35 +0100 Subject: [PATCH 0880/1062] Update README.md update README --- apps/metronome/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/metronome/README.md b/apps/metronome/README.md index f67b4adf1..05bd62a96 100644 --- a/apps/metronome/README.md +++ b/apps/metronome/README.md @@ -4,11 +4,12 @@ This metronome makes your watch blink and vibrate with a given rate. ## Usage -* Tap the screen at least three times. The app calculates the mean rate of your tapping. This rate is displayed in bmp while the text blinks and the watch softly vibrates with every beat. -* Use `BTN1` to increase the bmp value by one. -* Use `BTN3` to decrease the bmp value by one. +* Tap the screen at least three times. The app calculates the mean rate of your tapping. This rate is displayed in bpm while the text blinks and the watch softly vibrates with every beat. +* Use `BTN1` to increase the bpm value by one. +* Use `BTN3` to decrease the bpm value by one. * You can change the bpm value any time by tapping the screen or using `BTN1` and `BTN3`. * Intensity of buzzing and the beats per bar (default 4) can be changed with the settings-app. The first beat per bar will be marked in red. +* On Bangle.js 2 tapping the center of the screen initiates bpm. in- or decreasing bpm can by 1 can be done by tapping left or right site of the screen. ## Attributions From fe340d75bbb6c7814241becacddaca50435c5fe3 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:39:31 -0800 Subject: [PATCH 0881/1062] Update apps.json --- apps.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps.json b/apps.json index f98c6a1f8..ca4d798cf 100644 --- a/apps.json +++ b/apps.json @@ -550,6 +550,7 @@ "supports" : ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "allow_emulator": true, + "screenshots": [{"url":"bangle2-cube-scrable-screenshot"},{"url":"bangle1-cube-scramble-screenshot"}], "storage": [ {"name":"cubescramble.app.js","url":"cube-scramble.js"}, {"name":"cubescramble.img","url":"cube-scramble-icon.js","evaluate":true} @@ -774,6 +775,7 @@ "tags": "battery", "supports": ["BANGLEJS", "BANGLEJS2"], "allow_emulator": true, + "screenshots": [{"url":"bangle2-charge-animation-screenshot"},{"url":"bangle-charge-animation-screenshot"}], "storage": [ {"name":"chargeanim.app.js","url":"app.js"}, {"name":"chargeanim.boot.js","url":"boot.js"}, @@ -1195,6 +1197,7 @@ "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, + "screenshots": [{"url":"bangle2-simple-v-clock-screenshot"}], "storage": [ {"name":"svclock.app.js","url":"vclock-simple.js"}, {"name":"svclock.img","url":"vclock-simple-icon.js","evaluate":true} @@ -1210,6 +1213,7 @@ "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, + "screenshots": [{"url":"bangle2-dev-clock-screenshot"},{"url":"bangle1-dev-clock-screenshot"}], "storage": [ {"name":"dclock.app.js","url":"clock-dev.js"}, {"name":"dclock.img","url":"clock-dev-icon.js","evaluate":true} @@ -1427,6 +1431,7 @@ "tags": "pomodoro,cooking,tools", "supports": ["BANGLEJS", "BANGLEJS2"], "allow_emulator": true, + "screenshots": [{"url":"bangle2-pomodoro-screenshot"}], "storage": [ {"name":"pomodo.app.js","url":"pomodoro.js"}, {"name":"pomodo.img","url":"pomodoro-icon.js","evaluate":true} @@ -1443,6 +1448,7 @@ "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, + "screenshots": [{"url":"bangle2-large-digit-blob-clock-screenshot"},{"url":"bangle1-large-digit-blob-clock-screenshot"}], "storage": [ {"name":"blobclk.app.js","url":"clock-blob.js"}, {"name":"blobclk.img","url":"clock-blob-icon.js","evaluate":true} @@ -1502,6 +1508,7 @@ "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, + "screenshots": [{"url":"berlin-clock-screenshot"}], "storage": [ {"name":"berlinc.app.js","url":"berlin-clock.js"}, {"name":"berlinc.img","url":"berlin-clock-icon.js","evaluate":true} @@ -1714,6 +1721,7 @@ "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, + "screenshots": [{"url":"bangle2-dot-clock-screenshot"},{"url":"bangle1-dot-clock-screenshot"}], "storage": [ {"name":"dotclock.app.js","url":"clock-dot.js"}, {"name":"dotclock.img","url":"clock-dot-icon.js","evaluate":true} From 52c07f6dc80e8eabd017fc324332c0594c173399 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:40:42 -0800 Subject: [PATCH 0882/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index ca4d798cf..7b38dc729 100644 --- a/apps.json +++ b/apps.json @@ -4364,7 +4364,7 @@ ], "data": [{"name":"authentiwatch.json"}] }, - { "id": "schoolCalendar", + { "id": "schoolCalendar", "name": "School Calendar", "shortName":"SCalendar", "icon": "CalenderLogo.png", From 298fa47cd87e37b19cd142f47867843e24d08b57 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:42:50 -0800 Subject: [PATCH 0883/1062] Update apps.json --- apps.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps.json b/apps.json index 7b38dc729..800130f68 100644 --- a/apps.json +++ b/apps.json @@ -1508,7 +1508,7 @@ "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, - "screenshots": [{"url":"berlin-clock-screenshot"}], + "screenshots": [{"url":"berlin-clock-screenshot.png"}], "storage": [ {"name":"berlinc.app.js","url":"berlin-clock.js"}, {"name":"berlinc.img","url":"berlin-clock-icon.js","evaluate":true} From 9bdc07f8617e457439522d5b4c84a65d1eba4ed9 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:47:36 -0800 Subject: [PATCH 0884/1062] Update apps.json --- apps.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps.json b/apps.json index 800130f68..17470b665 100644 --- a/apps.json +++ b/apps.json @@ -550,7 +550,7 @@ "supports" : ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "allow_emulator": true, - "screenshots": [{"url":"bangle2-cube-scrable-screenshot"},{"url":"bangle1-cube-scramble-screenshot"}], + "screenshots": [{"url":"bangle2-cube-scrable-screenshot.png"},{"url":"bangle1-cube-scramble-screenshot.png"}], "storage": [ {"name":"cubescramble.app.js","url":"cube-scramble.js"}, {"name":"cubescramble.img","url":"cube-scramble-icon.js","evaluate":true} @@ -775,7 +775,7 @@ "tags": "battery", "supports": ["BANGLEJS", "BANGLEJS2"], "allow_emulator": true, - "screenshots": [{"url":"bangle2-charge-animation-screenshot"},{"url":"bangle-charge-animation-screenshot"}], + "screenshots": [{"url":"bangle2-charge-animation-screenshot.png"},{"url":"bangle-charge-animation-screenshot.png"}], "storage": [ {"name":"chargeanim.app.js","url":"app.js"}, {"name":"chargeanim.boot.js","url":"boot.js"}, @@ -1197,7 +1197,7 @@ "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, - "screenshots": [{"url":"bangle2-simple-v-clock-screenshot"}], + "screenshots": [{"url":"bangle2-simple-v-clock-screenshot.png"}], "storage": [ {"name":"svclock.app.js","url":"vclock-simple.js"}, {"name":"svclock.img","url":"vclock-simple-icon.js","evaluate":true} @@ -1213,7 +1213,7 @@ "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, - "screenshots": [{"url":"bangle2-dev-clock-screenshot"},{"url":"bangle1-dev-clock-screenshot"}], + "screenshots": [{"url":"bangle2-dev-clock-screenshot.png"},{"url":"bangle1-dev-clock-screenshot.png"}], "storage": [ {"name":"dclock.app.js","url":"clock-dev.js"}, {"name":"dclock.img","url":"clock-dev-icon.js","evaluate":true} @@ -1431,7 +1431,7 @@ "tags": "pomodoro,cooking,tools", "supports": ["BANGLEJS", "BANGLEJS2"], "allow_emulator": true, - "screenshots": [{"url":"bangle2-pomodoro-screenshot"}], + "screenshots": [{"url":"bangle2-pomodoro-screenshot.png"}], "storage": [ {"name":"pomodo.app.js","url":"pomodoro.js"}, {"name":"pomodo.img","url":"pomodoro-icon.js","evaluate":true} @@ -1448,7 +1448,7 @@ "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, - "screenshots": [{"url":"bangle2-large-digit-blob-clock-screenshot"},{"url":"bangle1-large-digit-blob-clock-screenshot"}], + "screenshots": [{"url":"bangle2-large-digit-blob-clock-screenshot.png"},{"url":"bangle1-large-digit-blob-clock-screenshot.png"}], "storage": [ {"name":"blobclk.app.js","url":"clock-blob.js"}, {"name":"blobclk.img","url":"clock-blob-icon.js","evaluate":true} @@ -1721,7 +1721,7 @@ "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, - "screenshots": [{"url":"bangle2-dot-clock-screenshot"},{"url":"bangle1-dot-clock-screenshot"}], + "screenshots": [{"url":"bangle2-dot-clock-screenshot.png"},{"url":"bangle1-dot-clock-screenshot.png"}], "storage": [ {"name":"dotclock.app.js","url":"clock-dot.js"}, {"name":"dotclock.img","url":"clock-dot-icon.js","evaluate":true} From 12443640c95efbe9f25ed0ffe73280c84d90cd13 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 11:54:22 -0800 Subject: [PATCH 0885/1062] Update apps.json --- apps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps.json b/apps.json index 17470b665..e445fae42 100644 --- a/apps.json +++ b/apps.json @@ -550,7 +550,7 @@ "supports" : ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "allow_emulator": true, - "screenshots": [{"url":"bangle2-cube-scrable-screenshot.png"},{"url":"bangle1-cube-scramble-screenshot.png"}], + "screenshots": [{"url":"bangle2-cube-scramble-screenshot.png"},{"url":"bangle1-cube-scramble-screenshot.png"}], "storage": [ {"name":"cubescramble.app.js","url":"cube-scramble.js"}, {"name":"cubescramble.img","url":"cube-scramble-icon.js","evaluate":true} @@ -1721,7 +1721,7 @@ "tags": "clock", "supports": ["BANGLEJS","BANGLEJS2"], "allow_emulator": true, - "screenshots": [{"url":"bangle2-dot-clock-screenshot.png"},{"url":"bangle1-dot-clock-screenshot.png"}], + "screenshots": [{"url":"bangle2-dot-clcok-screenshot.png"},{"url":"bangle1-dot-clock-screenshot.png"}], "storage": [ {"name":"dotclock.app.js","url":"clock-dot.js"}, {"name":"dotclock.img","url":"clock-dot-icon.js","evaluate":true} From cb6e14f6c12816224bbc3dc127e9817d0df7ae78 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Tue, 23 Nov 2021 20:20:37 +0000 Subject: [PATCH 0886/1062] messages, start of call handling and message responses, fix for scroll issue when only 2 menu items --- apps.json | 7 +++--- apps/android/ChangeLog | 1 + apps/android/boot.js | 17 ++++++++++---- apps/ios/ChangeLog | 1 + apps/ios/boot.js | 8 ++++++- apps/messages/ChangeLog | 1 + apps/messages/app.js | 51 ++++++++++++++++++++++++++++++----------- 7 files changed, 65 insertions(+), 21 deletions(-) diff --git a/apps.json b/apps.json index d8f6db3c3..25947c327 100644 --- a/apps.json +++ b/apps.json @@ -32,7 +32,7 @@ { "id": "messages", "name": "Messages", - "version": "0.04", + "version": "0.05", "description": "App to display notifications from iOS and Gadgetbridge", "icon": "app.png", "type": "app", @@ -45,12 +45,13 @@ {"name":"messages.wid.js","url":"widget.js"}, {"name":"messages","url":"lib.js"} ], + "data": [{"name":"messages.json"}], "sortorder": -9 }, { "id": "android", "name": "Android Integration", - "version": "0.02", + "version": "0.03", "description": "(BETA) App to display notifications from Gadgetbridge on Android. This will eventually replace the Gadgetbridge widget.", "icon": "app.png", "tags": "tool,system,messages,notifications", @@ -66,7 +67,7 @@ { "id": "ios", "name": "iOS Integration", - "version": "0.02", + "version": "0.03", "description": "(BETA) App to display notifications from iOS devices", "icon": "app.png", "tags": "tool,system,ios,apple,messages,notifications", diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index e881c9ec2..2deea0c60 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Remove messages on disconnect Fix music control +0.03: Handling of message actions (ok/clear) diff --git a/apps/android/boot.js b/apps/android/boot.js index 4b6c2c6ff..97e3a5641 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -12,7 +12,7 @@ /* TODO: Call handling, fitness */ var HANDLERS = { // {t:"notify",id:int, src,title,subject,body,sender,tel:string} add - "notify" : function() { event.t="add";require("messages").pushMessage(event); }, + "notify" : function() { Object.assign(event,{t:"add",positive:true, negative:true});require("messages").pushMessage(event); }, // {t:"notify~",id:int, title:string} // modified "notify~" : function() { event.t="modify";require("messages").pushMessage(event); }, // {t:"notify-",id:int} // remove @@ -36,8 +36,11 @@ }, // {"t":"call","cmd":"incoming/end","name":"Bob","number":"12421312"}) "call" : function() { - event.t=t.cmd=="incoming"?"add":"remove"; - event.id="call"; + Object.assign(event, { + t:event.cmd=="incoming"?"add":"remove", + id:"call", src:"Phone", + positive:true, negative:true, + title:event.name||"Call", body:"Incoming call\n"+event.number}); require("messages").pushMessage(event); }, }; @@ -58,5 +61,11 @@ Bangle.musicControl = cmd => { // play/pause/next/previous/volumeup/volumedown gbSend({ t: "music", n:cmd }); - } + }; + // Message response + Bangle.messageResponse = (msg,response) => { + if (msg.id=="call") return gbSend({ t: "call", n:response?"ACCEPT":"REJECT" }); + if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS" }); + // error/warn here? + }; })(); diff --git a/apps/ios/ChangeLog b/apps/ios/ChangeLog index ef674102a..895f50e04 100644 --- a/apps/ios/ChangeLog +++ b/apps/ios/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Remove messages on disconnect +0.03: Handling of message actions (ok/clear) diff --git a/apps/ios/boot.js b/apps/ios/boot.js index 54bd0b1c2..c3a30170d 100644 --- a/apps/ios/boot.js +++ b/apps/ios/boot.js @@ -96,7 +96,13 @@ Bangle.musicControl = cmd => { // play, pause, playpause, next, prev, volup, voldown, repeat, shuffle, skipforward, skipback, like, dislike, bookmark NRF.amsCommand(cmd); }; -NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect +// Message response +Bangle.messageResponse = (msg,response) => { + if (isFinite(msg.id)) return NRF.sendANCSAction(msg.id, response);//true/false + // error/warn here? +}; +// remove all messages on disconnect +NRF.on("disconnect", () => require("messages").clearAll()); /* // For testing... diff --git a/apps/messages/ChangeLog b/apps/messages/ChangeLog index 28906d608..0c27d5a9b 100644 --- a/apps/messages/ChangeLog +++ b/apps/messages/ChangeLog @@ -2,3 +2,4 @@ 0.02: Add 'messages' library 0.03: Fixes for Bangle.js 1 0.04: Add require("messages").clearAll() +0.05: Handling of message actions (ok/clear) diff --git a/apps/messages/app.js b/apps/messages/app.js index b12fa7f1f..39a55f135 100644 --- a/apps/messages/app.js +++ b/apps/messages/app.js @@ -17,7 +17,7 @@ // maps {"t":"add","id":1,"src":"Maps","title":"0 yd - High St","body":"Campton - 11:48 ETA","img":"GhqBAAAMAAAHgAAD8AAB/gAA/8AAf/gAP/8AH//gD/98B//Pg/4B8f8Afv+PP//n3/f5//j+f/wfn/4D5/8Aef+AD//AAf/gAD/wAAf4AAD8AAAeAAADAAA="} // call -{"t:"add","id:"call","name":"Bob","number":"12421312"} +{"t":"add","id":"call","src":"Phone","name":"Bob","number":"12421312",positive:true,negative:true} */ var Layout = require("Layout"); @@ -56,9 +56,16 @@ function saveMessages() { function getBackImage() { return atob("FhYBAAAAEAAAwAAHAAA//wH//wf//g///BwB+DAB4EAHwAAPAAA8AADwAAPAAB4AAHgAB+AH/wA/+AD/wAH8AA=="); } +function getPosImage() { + return atob("GRSBAAAAAYAAAcAAAeAAAfAAAfAAAfAAAfAAAfAAAfBgAfA4AfAeAfAPgfAD4fAA+fAAP/AAD/AAA/AAAPAAADAAAA=="); +} +function getNegImage() { + return atob("FhaBADAAMeAB78AP/4B/fwP4/h/B/P4D//AH/4AP/AAf4AB/gAP/AB/+AP/8B/P4P4fx/A/v4B//AD94AHjAAMA="); +} function getMessageImage(msg) { if (msg.img) return atob(msg.img); var s = (msg.src||"").toLowerCase(); + if (s=="Phone") return atob("FxeBABgAAPgAAfAAB/AAD+AAH+AAP8AAP4AAfgAA/AAA+AAA+AAA+AAB+AAB+AAB+OAB//AB//gB//gA//AA/8AAf4AAPAA="); if (s=="skype") return atob("GhoBB8AAB//AA//+Af//wH//+D///w/8D+P8Afz/DD8/j4/H4fP5/A/+f4B/n/gP5//B+fj8fj4/H8+DB/PwA/x/A/8P///B///gP//4B//8AD/+AAA+AA=="); if (s=="hangouts") return atob("FBaBAAH4AH/gD/8B//g//8P//H5n58Y+fGPnxj5+d+fmfj//4//8H//B//gH/4A/8AA+AAHAABgAAAA="); if (s=="whatsapp") return atob("GBiBAAB+AAP/wAf/4A//8B//+D///H9//n5//nw//vw///x///5///4///8e//+EP3/APn/wPn/+/j///H//+H//8H//4H//wMB+AA=="); @@ -155,6 +162,28 @@ function showMessage(msgid) { if (g.setFont(titleFont).stringWidth(title) > w) title = g.wrapString(title, w).join("\n"); } + var buttons = [ + {type:"btn", src:getBackImage(), cb:()=>checkMessages(true)}, // back + msg.new?{type:"btn", src:atob("HRiBAD///8D///wj///Fj//8bj//x3z//Hvx/8/fx/j+/x+Ad/B4AL8Rh+HxwH+PHwf+cf5/+x/n/PH/P8cf+cx5/84HwAB4fgAD5/AAD/8AAD/wAAD/AAAD8A=="), cb:()=>{ + msg.new = false; // read mail + saveMessages(); + checkMessages(); + }}:{} + ]; + if (msg.positive) { + buttons.push({type:"btn", src:getPosImage(), cb:()=>{ + msg.new = false; saveMessages(); + Bangle.messageResponse(msg,true); + checkMessages(); + }}); + } + if (msg.negative) { + buttons.push({type:"btn", src:getNegImage(), cb:()=>{ + msg.new = false; saveMessages(); + Bangle.messageResponse(msg,true); + checkMessages(); + }}); + } layout = new Layout({ type:"v", c: [ {type:"h", fillx:1, bgCol:colBg, c: [ { type:"img", src:getMessageImage(msg), pad:2 }, @@ -164,14 +193,7 @@ function showMessage(msgid) { ]}, ]}, {type:"txt", font:fontMedium, label:msg.body||"", wrap:true, fillx:1, filly:1, pad:2 }, - {type:"h",fillx:1, c: [ - {type:"btn", src:getBackImage(), cb:()=>checkMessages(true)}, // back - msg.new?{type:"btn", src:atob("HRiBAD///8D///wj///Fj//8bj//x3z//Hvx/8/fx/j+/x+Ad/B4AL8Rh+HxwH+PHwf+cf5/+x/n/PH/P8cf+cx5/84HwAB4fgAD5/AAD/8AAD/wAAD/AAAD8A=="), cb:()=>{ - msg.new = false; // read mail - saveMessages(); - checkMessages(); - }}:{} - ]} + {type:"h",fillx:1, c: buttons} ]}); g.clearRect(Bangle.appRect); layout.render(); @@ -179,12 +201,15 @@ function showMessage(msgid) { function checkMessages(forceShowMenu) { // If no messages, just show 'no messages' and return - if (!MESSAGES.length) - return E.showPrompt("No Messages",{ + if (!MESSAGES.length) { + if (forceShowMenu) return E.showPrompt("No Messages",{ title:"Messages", img:require("heatshrink").decompress(atob("kkk4UBrkc/4AC/tEqtACQkBqtUDg0VqAIGgoZFDYQIIM1sD1QAD4AIBhnqA4WrmAIBhc6BAWs8AIBhXOBAWz0AIC2YIC5wID1gkB1c6BAYFBEQPqBAYXBEQOqBAnDAIQaEnkAngaEEAPDFgo+IKA5iIOhCGIAFb7RqAIGgtUBA0VqobFgNVA")), buttons : {"Ok":1} }).then(() => { load() }); + load(); + return; + } // we have >0 messages // If we have a new message, show it if (!forceShowMenu) { @@ -195,7 +220,7 @@ function checkMessages(forceShowMenu) { // Otherwise show a menu E.showScroller({ h : 48, - c : MESSAGES.length+1, + c : Math.min(MESSAGES.length+1,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11) draw : function(idx, r) {"ram" var msg = MESSAGES[idx-1]; if (msg && msg.new) g.setBgColor(colBg); @@ -236,4 +261,4 @@ function checkMessages(forceShowMenu) { g.clear(); Bangle.loadWidgets(); Bangle.drawWidgets(); -checkMessages(); +checkMessages(true); // force showing a menu From 15aa41c766b2a25c7a83a1a6744706a7b2852aa3 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Tue, 23 Nov 2021 12:23:03 -0800 Subject: [PATCH 0887/1062] Add files via upload --- apps/hrings/bangle1-hypno-rings-screenshot.png | Bin 0 -> 5168 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/hrings/bangle1-hypno-rings-screenshot.png diff --git a/apps/hrings/bangle1-hypno-rings-screenshot.png b/apps/hrings/bangle1-hypno-rings-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..66f8bcba24b270b9385e588c3fd4105ab0dba92f GIT binary patch literal 5168 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 Date: Tue, 23 Nov 2021 16:03:38 -0800 Subject: [PATCH 0888/1062] Add files via upload --- apps/about/bangle1-about-screenshot.png | Bin 0 -> 14911 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/about/bangle1-about-screenshot.png diff --git a/apps/about/bangle1-about-screenshot.png b/apps/about/bangle1-about-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..092f93dae470d0fa00488b9fec727eec5c9241a8 GIT binary patch literal 14911 zcmc(Gb*pBktGed&^h|%4bAEl!7p+$?d|YZ=004lmqO73v)E@tLVq-pCrQhtv zJ~coO9he-TY?yWz0AK{DD7@75F*`7YCeqfAJ7-!}@X0(omiqwc0)s$kAUu2scgqUK zKO9U7lLi!Ol#%hj1BK#yy2}+Ya^kQ0S10l3p4y^R^NT%di=(ebobi8)c;o+!ShZ#J=x^&vZv=&rbO?knM9-vDiEJ!<3M(QsDckQ*p+4@O`>Pefua z8x{$Q1Fa9qwIvI>4yWK%(Ij9&yvB@0(;q~lACvQ%#b(ed0}Ak1u*#C2uv}&>EK*N{ zy`oYkFyVi~>bLl)__;Fz(*&L#ZSjOH_c@6@-;f1~zGD<~?RmnQpRBR&_B~CcU-th! z5sGK`=bL|ontg*N?(y3vj!N3?%&+lWqSMdZR{f<3>hc0lj!MK-&t-41)2SDUGt6{S z(T`b6d#P-xfb8T!1X|!i6Ark{_*Fpo%n|#K8ehZZ9Fms^*p5V3EbZJo(6yGAnVC%j zMgpp<{R{x9L_xH6&QcWP8>ru~=bSaRkk@di0?g;b@yDBgvF0a)gOE*)+Kk%MbMCXX zUMZWGZPR3t%J%1y)o2 zW$-zPni%3YWqFXV(EY>hQA~cG21s=9=bmF>pv_7%~ju?Cmi{<961g}#dPiV&^+RslbN4x!|PMyGn7OM_K z`COo?H6C)kmLQGehx_!oWkTN!iUoY9ooNcqWj*G8o{#bHT#=hC@YU1WjPxz;ONhBz z6SVk0I)~gZlxBWK*1Pj$su9qlkWweScrrSJF+Ar$aDpid7CY7@$cKeA@C0yauh+U< zu2_@NFIg74yq)dgawn1U+t2I9{YI7T#aeJ?1dd6nqGt7e7Hm8vu{T2M>~<;xgE8_C z8AA_)O4N_0zgJi71)cq7AdLcBhrGMl5r(D_Ehqgk>9 z^9HJCj52?M4}f3M6g^%F4M3cZdOp#oQ_G63*BPw*u58vu_#M;(42%PQo@7#gX23+QNJwJ}W>< zs0v85jR`gYVb_LMDnq5r2e8m8CY_u|C?-)Z`yj|$_J+5~v5N1)GsaPVELGmX&Dd9j&Zj)RLRbz*($p&HG3t zl-lN((;4*R9buewWwHvB(bjF2dO6F6O*-Xu<&03wZg$ySQDcZ>v8&b%w`(@J9G`Ma zO-Yo#oHFqD?(u?mfL?YcnglL9;FS)oUR@&E@7Iz`O_2@5wEE{q389xJFUg!Wu%Vxf zAS7SLaGyzLO-o{c;c1PYj;}^y?tMH?Ab|r%eEBw}S?2h;*R5<7OVkd6D;^|(MxpJq z`jRXwq#F;tj9>~pK&|L&3E?Qc+brt`R*;X?v7^5f?PpL@ND^)X>tKrO2}=fsf*qI@ zR}i=GGX{NnL~czK;5g&E&H`NOuk^8{LGB)Mi9aq1KnZ+_;l1ID!$>^pv--6M$BFPw z73xpmWkYc&83hh}=>Ft+>BJ)MPUn5NpWVmK(ZgCs0$(182bY3ks|VClCJDnbgH~8kmHczjR#P@ zG*tH+qLblXe=qtKOTTCYy&WySvS*9(>DQ6?@iGpgAwRW1#jW<*AqYU`6S!6Akp6uZ z^QX?W3*vcYb~M{SX4Iyc$voB;Jik^?`WWlgr0# zN!wdfl|Ir3%xs36PO2}!Jf)Kp-8}jm%$s_6a`Rb^py_f+k2(*=y)kNHJ0cY52rl6_ zs&>s;_K0LNbn1&ZUho)N^AD-MxGKbuz7mdo_m72|IlK$>9et(H;<&eWIVW8EtXEXD zRn)+J+4vcj*K;S}520A@WGt%yrN7I!8YvCvEPZe4ZlY=mJQ7++hAht9#Et}|9We^8 zOwdso#yj`JzWh~@R=zk8t(H_i7QjU9$z>kY)&6w76eE$jDSe}H!YHQigA=nNTjD{~ zCL-51msoOod|=HlFD?f%bBZ{w_lVa~;@+d@`l=KS=|d&aN3(1OvmoO zYox6i7+I!l&i}p%0w9_KMH9S|Ugj|C%*eV-Z&<3bs4@c`%#{_00=N$L6MdX

    Wm?3!Mt<_!7vupX1HX?3>?LHW#^ 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 OaCrVtk_Ynp^8WyThLmdn literal 0 HcmV?d00001 diff --git a/apps/trex/README.md b/apps/trex/README.md new file mode 100644 index 000000000..03e3f5883 --- /dev/null +++ b/apps/trex/README.md @@ -0,0 +1,4 @@ +# T-Rex + +![](screenshot_trex.png) + diff --git a/apps/trex/screenshot_trex.png b/apps/trex/screenshot_trex.png new file mode 100644 index 0000000000000000000000000000000000000000..a66cc013fa349cf661d12c4e511d0fd7529f0bda GIT binary patch literal 668 zcmV;N0%QG&P)Px%R!KxbRCr$P+~JagAPfXxegB8HV^4Z%iny#qN7(+kn>Nt$86#-zye!Ky=>ZGO z^E@T8_5bx@3GM6aYqNl1+bIY7M%>W-UjgH`pU9sKH5qNOQ-hEUPFs5K^FO&NdEm=4 z_y{+lKM~oEZT{?xNKcHRfNKUiYUWm->_M_M2QT0Xfm-B>u4{IRN5#S=;*;{*S$t%a z0w%9$Aek)OzZ9&_U?h`;`@etFz-dx#r*3<5Wu%<+8q);WUsbhh@m}H3ah(QMIVe+~ z23EEP0;9Xh$Z@AKcw{(q>;gum$T$I$aA-3Dqf%s?fJr#CnSfC#GETrG9NJ94s1z9| zU=j{(CSX*Gj1w>khc**1Dn-T#n1n-{2^f_k;{;5?q0I!0N|ABH0$+83R=~k`5)TJ# zzwW>8_0bn?hXT&s!)I^oj=&c1RwAOl!z^F{lk=lYGnI&Tmf4<4cR{H{G{X1DI>(gc-s#zT%ii6e&P4=o0Ram*SRlB2Ww3yQ1%kW3 zEQ8lmqQPSsx_fvY?0=Ugw=!xqr)+Uv;HnJuix+S31DqS2f9~klPmb32HT|*a?-Qal zuD$V~<1Br~H?ZvpeKR)8(EeFiWY6-aElYdeS>zWB@m^wF=+_(o0000=3) lastBreaths.shift(); // keep length small + lastBreaths.push(value); + value = E.sum(lastBreaths) / lastBreaths.length; + // draw value + g.reset(); + g.clearRect(0,g.getHeight()-100,g.getWidth(),g.getHeight()-50); + g.setFont("6x8").setFontAlign(0,0); + g.drawString("Calculated measurement", g.getWidth()/2, g.getHeight()-95); + g.setFont("Vector",40).setFontAlign(0,0); + g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-70); + // set vibration IF we're doing it from our calculations + if (settings.vibrate == "calculated") + setVibrate(value > settings.vibrateBPM); + } + lastBreath = t; +} + +function onData(n, value) { + g.reset(); + if (n==2) { + function scale(v) { + return Math.max(graphHeight - (1+v*4),24); + } + if (avrValue==undefined) avrValue=value; + avrValue = avrValue*0.95 + value*0.05; + if (avrValue < 1) avrValue = 1; + if (value > avrValue) { + if (!aboveAvr) onBreath(); + aboveAvr = true; + } else aboveAvr = false; + + var t = Date.now(); + var x = Math.round((t - last.time) / 100) // 10 per second + if (last.x>=g.getWidth()) { + x = 0; + last.x = 0; + last.time = t; + g.clearRect(0,24,g.getWidth(),graphHeight); + } + var y = scale(value); + g.setPixel(x, scale(avrValue), "#f00"); + g.drawLine(last.x, last.y, x, y); + last.x = x; + last.y = y; + } + if (n==4) { + g.clearRect(0,g.getHeight()-50,g.getWidth(),g.getHeight()); + g.setFont("6x8").setFontAlign(0,0); + g.drawString("GoDirect measurement", g.getWidth()/2, g.getHeight()-45); + g.setFont("Vector",40).setFontAlign(0,0); + g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-20); + // set vibration IF we're doing it from our calculations + if (settings.vibrate == "vernier") + setVibrate(value > settings.vibrateBPM); + } + Bangle.setLCDPower(1); // ensure LCD is on +} + +function connect() { + var gatt, service, rx, tx; + var rollingCounter = 0xFF; + + // any button to exit + Bangle.setUI("updown", function() { + setVibrate(false); + Bangle.buzz(); + try { + if (gatt) gatt.disconnect(); + } catch (e) { + } + setTimeout(mainMenu, 1000); + }); + + function sendCommand(subCommand) { + const command = new Uint8Array(4 + subCommand.length); + command.set(new Uint8Array(subCommand), 4); + // Populate the packet header bytes + command[0] = 0x58; // header + command[1] = command.length; + command[2] = --rollingCounter; + command[3] = E.sum(command) & 0xFF; // checksum + return tx.writeValue(command); + } + function firstSetBit(v) { + return v & -v; + } + function handleResponse(dv) { + //print(dv.buffer); + var resType = dv.getUint8(0); + if (resType==0x20) { + // [32, 25, 207, 216, 6, 6, 0, 2, 252, 128, 138, 7, 191, 0, 0, 192, 127, 128, 49, 8, 191, 0, 0, 192, 127]) + // 6 = data type = real + // 6,0 = bit mask for sensors + // 2 = value count + if (dv.getUint8(4)!=6) return; //throw "Not float32 data"; + var sensorIds = dv.getUint16(5, true); + // var count = dv.getUint8(7); doesn't seem right + var offs = 9; + while (sensorIds) { + var value = dv.getFloat32(offs, true); + var s = firstSetBit(sensorIds); + if (isFinite(value)) onData(s,value); + //else print(s,value); + sensorIds &= ~s; + offs += 4; + } + } else { + var cmd = dv.getUint8(4); // cmd + //print("CMD",dv.buffer); + } + } + + onMsg("Searching..."); + NRF.requestDevice({ filters: [{ namePrefix: 'GDX-RB' }] }).then(function(device) { + device.on("gattserverdisconnected", function() { + onMsg("Device disconnected"); + }); + onMsg("Found. Connecting..."); + return device.gatt.connect({minInterval:20, maxInterval:20}); + }).then(function(g) { + gatt = g; + return gatt.getPrimaryService("d91714ef-28b9-4f91-ba16-f0d9a604f112"); + }).then(function(s) { + service = s; + return service.getCharacteristic("f4bf14a6-c7d5-4b6d-8aa8-df1a7c83adcb"); + }).then(function(c) { + tx = c; + return service.getCharacteristic("b41e6675-a329-40e0-aa01-44d2f444babe"); + }).then(function(c) { + rx = c; + rx.on('characteristicvaluechanged', function(event) { + //print("EVT",event.target.value.buffer); + handleResponse(event.target.value); + }); + return rx.startNotifications(); + }).then(function() { + onMsg("Init"); + sendCommand([ // init + 0x1a, 0xa5, 0x4a, 0x06, + 0x49, 0x07, 0x48, 0x08, + 0x47, 0x09, 0x46, 0x0a, + 0x45, 0x0b, 0x44, 0x0c, + 0x43, 0x0d, 0x42, 0x0e, + 0x41, + ]); + /*setTimeout(function() { + print("Set measurement period"); + var us = 100000; // period in us + sendCommand([0x1b, 0xff, 0x00, + us & 255, + (us >> 8) & 255, + (us >> 16) & 255, + (us >> 24) & 255, + 0x00, + 0x00, + 0x00, + 0x00]); + }, 100);*/ + + /* setTimeout(function() { + print("Get sensor info"); + sendCommand([0x51, 0]); // get sensor IDs + // returns [152, 10, 1, 39, 81, 253, 54, 0, 0, 0] + // 54 is the bit mask of available channels + //sendCommand([106, 16]); // get sensor info + }, 2000);*/ + + setTimeout(function() { + onMsg("Start measurements"); + //https://github.com/VernierST/godirect-js/blob/main/src/Device.js#L588 + var channels = 6; // data channels 4 and 2 + sendCommand([ // start measurements + 0x18, 0xff, 0x01, channels, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00 + ]); + }, 500); + }).catch(function() { + onMsg("Connect Fail"); + }); +} + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +function mainMenu() { + var vibText = ["No","Calculated","Vernier"]; + var vibValue = ["","calculated","vernier"]; + E.showMenu({"":{title:"Respiration Belt"}, + "< Back" : () => { saveSettings(); load(); }, + "Connect" : () => { saveSettings(); E.showMenu(); connect(); }, + "Vib" : { + value : Math.max(vibValue.indexOf(settings.vibrate),0), + format : v => vibText[v], + min:0,max:2, + onchange : v => { settings.vibrate=vibValue[v]; } + }, + "BPM" : { + value : settings.vibrateBPM, + min:10,max:50, + onchange : v => { settings.vibrateBPM=v; } + } + }); +} + +mainMenu(); diff --git a/apps/vernierrespirate/app.png b/apps/vernierrespirate/app.png new file mode 100644 index 0000000000000000000000000000000000000000..0f6b22af17e76005f58c052a2c72f7e43f90959f GIT binary patch literal 1986 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 }, BTN2, {repeat:true,edge:"falling"}); setWatch(()=>move(-1), BTN1, {repeat:true}); -(function migrateSettings(){ - let global_settings = require('Storage').readJSON('setting.json', 1) - if (global_settings) { - delete global_settings.welcomed - require('Storage').write('setting.json', global_settings) - } -})() - Bangle.setLCDTimeout(0); Bangle.setLCDPower(1); move(0); diff --git a/apps/welcome/app-bangle2.js b/apps/welcome/app-bangle2.js new file mode 100644 index 000000000..93d1c5657 --- /dev/null +++ b/apps/welcome/app-bangle2.js @@ -0,0 +1,248 @@ +// exec each function from seq one after the other +function animate(seq,period) { + var c = g.getColor(); + var i = setInterval(function() { + if (seq.length) { + var f = seq.shift(); + g.setColor(c); + if (f) f(); + } else clearInterval(i); + },period); +} + +// Fade in to FG color with angled lines +function fade(col, callback) { + var n = 0; + function f() {"ram" + g.setColor(col); + for (var i=n;i<240;i+=10) g.drawLine(i,0,0,i).drawLine(i,240,240,i); + g.flip(); + n++; + if (n<10) setTimeout(f,0); + else callback(); + } + f(); +} + + +var SCENE_COUNT=10; +function getScene(n) { + if (n==0) return function() { + g.reset().setBgColor(0).clearRect(0,0,176,176); + g.setFont("6x15"); + var n=0; + var l = Bangle.getLogo(); + var im = g.imageMetrics(l); + var i = setInterval(function() { + n+=0.1; + g.setColor(n,n,n); + g.drawImage(l,(176-im.width)/2,(176-im.height)/2); + if (n>=1) { + clearInterval(i); + setTimeout(()=>g.drawString("Open",44,104), 500); + setTimeout(()=>g.drawString("Hackable",44,116), 1000); + setTimeout(()=>g.drawString("Smart Watch",44,128), 1500); + } + },50); + }; + if (n==1) return function() { + var img = require("heatshrink").decompress(atob("ptR4n/j/4gH+8H5wl+jOukVVoHZ8dt/n//n37OtgH9sHhwHp4H5xmkGiH72MRje/LL/7iIAEE7sPEgoAC+AlagIlIiMQErPxDwUYxAABwIHCj8N7nOl3uEqa6BEggnFjfM5nCkUil3gEq5KDAAQmC6QmBE4JxSEhIABiQmB8QmSXoQlCYRMdEwIlCAAIlNhYlOiO85nNEyMPEoZwIAAcsYIYmPXoYlMiKaFExX/u9VEqLBBOYrCH+czmtVqJyDEpiaCOYsgSYszmc3qtTEqMR7hzG8AlGmd1OQglOOY6aEgYlCmmZoJMCTBrnD6SaIEoU/zOUuolSjbnBJgqaCEoU5zOXX4RyQYBBzCS4X5zNDqqZCJiERJg5zBEoVJEoM1JgYlQjhMHc4JLEmZMEEp6ZIJgPzS4WTmZMVTILmFYAK+BmglCmd1JgUYJiPNEorABEIOZygDBm5MCiJMQlhMH8ByBXwIlBJgUxJiMd5nOTIzlBTAK+BAANVq4jPAAS/HJgJyCTATAEACC/B4S/IJgIlCYAgAPiS/Kn5yEYANTEyPc5niOQxMB/LlCOapyJJgbpBYAZzROQK/Gl0ATIWfEoZzBc6IlB6SYGgBJBJgpzSlhyH8EAh5MBTIjnCuIlOjjlHTAJzC/LmDTSSYIEoTABOYIlETSKYHXwIABOYM0yYmETSCYHEobnDOYqaBExu8TAwlEc4U5EoiaCmK+NTAolFEwX0TQzBMXwXiEpTBCAAomNEoS+EEo4mIYIImKEoS+EEpDoBEyUbEo3gEo4mJdAImIJY4lJEycdEoPOOBYmPuIlE+HcJYhKKTZ1fhYkB2EAhnNcYMuEhomMr8A3YABEoJyB5gjOAAYmHm9VgELEoJMBEoXAEyXzE45YBJgXwEqx1I+ByDOYJyVJw5yCgEB3cQGgJMWJwQnCu6/CgFBigDB13S/glVAAf1qomCglEoADB1QDBADEPEoNVqEAolEgEKolKErJMDYAJMD0lE0AmaEoNaAgJMCFIYAahV/IgIiDOTgABNYJMEOToiCIoJMCOTzfCN4RMBOTxsDJIRyfIwZMBKQZzfJgRyfOYZMBOUBzCJgNKOT5zDJgLoCADxKBOAIABOT6aCAARyfOYRyjOYRyjOYlKEsBzEEsBzEOUJzDOUIABOUiaDOURzCOUZzCEscKCiY")); + var im = g.imageMetrics(img); + g.reset(); + g.setBgColor("#ff00ff"); + var y = 176, speed = 5; + function balloon(callback) { + y-=speed; + var x = (176-im.width)/2; + g.drawImage(img,x,y); + g.clearRect(x,y+81,x+77,y+81+speed); + if (y>30) setTimeout(balloon,0,callback); + else callback(); + } + fade("#ff00ff", function() { + balloon(function() { + g.setColor(-1).setFont("6x15:2").setFontAlign(0,0); + g.drawString("Welcome.",88,130); + }); + }); + setTimeout(function() { + var n=0; + var i = setInterval(function() { + n+=4; + g.scroll(0,-4); + if (n>150) + clearInterval(i); + },20); + },3500); + + }; + if (n==2) return function() { + g.reset(); + g.setBgColor("#ffff00").setColor(0).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 70, y = 25, h=25; + animate([ + ()=>g.drawString("Your",x,y+=h), + ()=>g.drawString("Bangle.js",x,y+=h), + ()=>g.drawString("has one",x,y+=h), + ()=>g.drawString("button",x,y+=h), + ()=>{g.setFont("12x20:2").setFontAlign(0,0,1).drawString("HERE!",150,88);} + ],200); + }; + if (n==3) return function() { + g.reset(); + g.setBgColor("#00ffff").setColor(0).clear(); + g.setFontAlign(0,0).setFont("6x15:2"); + g.drawString("Press",88,40).setFontAlign(0,-1); + g.setFont("12x20"); + g.drawString("To wake the\nscreen up, or to\nselect", 88,60); + }; + if (n==4) return function() { + g.reset(); + g.setBgColor("#00ffff").setColor(0).clear(); + g.setFontAlign(0,0).setFont("6x15:2"); + g.drawString("Long Press",88,40).setFontAlign(0,-1); + g.setFont("12x20"); + g.drawString("To go back to\nthe clock", 88,60); + }; + if (n==5) return function() { + g.reset(); + g.setBgColor("#ff0000").setColor(0).clear(); + g.setFontAlign(0,0).setFont("12x20"); + g.drawString("If Bangle.js ever\nstops, hold the\nbutton for\nten seconds.\n\nBangle.js will\nthen reboot.", 88,78); + }; + if (n==6) return function() { + g.reset(); + g.setBgColor("#0000ff").setColor(-1).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 88, y = -20, h=60; + animate([ + ()=>{g.drawString("Bangle.js has a\nfull touchscreen",x,y+=h);}, + 0,0, + ()=>{g.drawString("Drag up and down\nto scroll and\ntap to select",x,y+=h);}, + ],300); + }; + if (n==7) return function() { + g.reset(); + g.setBgColor("#00ff00").setColor(0).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 88, y = -35, h=80; + animate([ + ()=>{g.drawString("Bangle.js comes\nwith a few\napps installed",x,y+=h);}, + 0,0, + ()=>{g.drawString("To add more, visit\nbanglejs.com/apps",x,y+=h);}, + ],400); + }; + if (n==8) return function() { + g.reset(); + g.setBgColor("#ff0000").setColor(0).clear(); + g.setFont("12x20").setFontAlign(0,0); + var x = 88; + g.drawString("You can also make\nyour own apps!",x,30); + g.drawString("Check out\nbanglejs.com",x,130); + + var rx = 0, ry = 0; + // draw a cube + function draw() { + // rotate + rx += 0.1; + ry += 0.11; + var rcx=Math.cos(rx), + rsx=Math.sin(rx), + rcy=Math.cos(ry), + rsy=Math.sin(ry); + // Project 3D coordinates into 2D + function p(x,y,z) { + var t; + t = x*rcy + z*rsy; + z = z*rcy - x*rsy; + x=t; + t = y*rcx + z*rsx; + z = z*rcx - y*rsx; + y=t; + z += 4; + return [88 + 60*x/z, 78+ 60*y/z]; + } + + var a; + // draw a series of lines to make up our cube + var s = 30; + g.clearRect(88-s,78-s,88+s,78+s); + a = p(-1,-1,-1); g.moveTo(a[0],a[1]); + a = p(1,-1,-1); g.lineTo(a[0],a[1]); + a = p(1,1,-1); g.lineTo(a[0],a[1]); + a = p(-1,1,-1); g.lineTo(a[0],a[1]); + a = p(-1,-1,-1); g.lineTo(a[0],a[1]); + a = p(-1,-1,1); g.moveTo(a[0],a[1]); + a = p(1,-1,1); g.lineTo(a[0],a[1]); + a = p(1,1,1); g.lineTo(a[0],a[1]); + a = p(-1,1,1); g.lineTo(a[0],a[1]); + a = p(-1,-1,1); g.lineTo(a[0],a[1]); + a = p(-1,-1,-1); g.moveTo(a[0],a[1]); + a = p(-1,-1,1); g.lineTo(a[0],a[1]); + a = p(1,-1,-1); g.moveTo(a[0],a[1]); + a = p(1,-1,1); g.lineTo(a[0],a[1]); + a = p(1,1,-1); g.moveTo(a[0],a[1]); + a = p(1,1,1); g.lineTo(a[0],a[1]); + a = p(-1,1,-1); g.moveTo(a[0],a[1]); + a = p(-1,1,1); g.lineTo(a[0],a[1]); + } + + setInterval(draw,50); + }; + if (n==9) return function() { + g.reset(); + g.setBgColor("#ffffff");g.clear(); + g.setFontAlign(0,0); + g.setFont("12x20"); + + var x = 88, y = 10, h=21; + animate([ + ()=>g.drawString("That's it!",x,y+=h), + ()=>{g.drawString("Press",x,y+=h*2); + g.drawString("the button",x,y+=h); + g.drawString("to start",x,y+=h); + g.drawString("Bangle.js",x,y+=h);} + ],400); + } +} + +var sceneNumber = 0; + +function move(dir) { + if (dir>0 && sceneNumber+1 == SCENE_COUNT) return; // at the end + sceneNumber = (sceneNumber+dir)%SCENE_COUNT; + if (sceneNumber<0) sceneNumber=0; + clearInterval(); + getScene(sceneNumber)(); + if (sceneNumber>1) { + var l = SCENE_COUNT; + for (var i=0;i move(dir)); +setWatch(()=>{ + if (sceneNumber == SCENE_COUNT-1) + load(); + else + move(1); +}, BTN1, {repeat:true}); + +Bangle.setLCDTimeout(0); +Bangle.setLCDPower(1); +move(0); diff --git a/apps/welcome/screenshot_welcome.png b/apps/welcome/screenshot_welcome.png new file mode 100644 index 0000000000000000000000000000000000000000..4c574839df6876870221b4354b179c23ef10c972 GIT binary patch literal 2618 zcmchZ`9IYA7sub@gJEKDZ82l$mVJx321yNFA-klADc`P8cCsWhjf}*&w(Dj~;Syp{ zQHf91t1MT!V{BtEcCL}^+t>G(?;r5};rqimuV2pcc%1V%kMn$FZ)+ipl0^Xkge@IzN1HFxyk3c~^rk z4LtjKBj$;|_1V+6ZN1KZ81t{|NH&!aD1+rl6dHE)z8Zi@qE`_BZE`OZN4l=?iU(k$ zHfK+Rmb16`0RB6-FA~I)|3ZPl+3~_vAOt;=1nj?BEM^c#`3dR(enw5{fldm=xd4)x zhmuj;cYeIMC2b6k6p zX9Av-rM)6SnEvQy-$3S!^k&kOpt=zB02*$U4RTw00$j`c%du$pzJ+9J`UeG z9-1TA8=Hx~roxb&R&QefcgVF%D`WQSI}6oHm45wr`A%9TX96Tae%_JIPNwFtmm7SNayLwXk+rGdmorPInKBM}NEWAVZ}C#&c7tA|!*@m+b|`m^XC+ zHv5LlSE@0VoIMqR>@(5;kJweb^Eq9?~5h$IVhl zCNtE%hbo$H8BU_|eknT-ZS{kI4Ud%J$?&GJfE0_pv4O;>ZR3K~H{Y%orYn?-z}Y^H zr9$Z2(z2wbY?~J=q0HgEsrs!ZN#y>>0yCH`-`0?(i`LNr*3XS`b$EZ=k)|53Q?NPO zmp|&29?Nih&-kYIy|e+GE-NU$pBSXx<2J0h9XvbaiG$=LE(EceJLCmgd3}(2Wr1Wx z$dr{EHDutO_X;^?5V9MXP;BcVu;Fc+l~0`r_=D4!ebrRMmf5W;iL2`h<{?u{CM6=c zoqr6fdfihWPq*vHxrVazgsMKP&ycN8DWxP)HdDleMJ=Rbz)PXcGRy|09+uI^df)0gvU#|Kfct zfvkTbJVVtm@TJVCRc;3IpIOelnr(GJQAtiqoG1o!AXjg?uE@{mtr4iBQ;`vWMt19P zL$VuUOV*&h+BAG%QcnGo>VRm|!2FYEnERe>gz>JdsZU}vIt?XEK;_g;Jxuh=XvMqFYg-=MsjA#^tE?_ z_hfGAvl7YWc0`q@e_j~xm^l?>`rdixk(`nY7^a@9&^dMR7^<7%CyH6PA+Ql8S(M4? z6mDuXb^E@CORiLThBX?bZv%17tkK}SUgvJRz z6LGwosKJE6XgRQXp$UVhN8QwXs0r?!AdSVyL>_-Hv9s?xcCyD0()^deN{%aYMaGc& z`^I7qM)yc+Z?`9&{y8!4#z(tJYj0uks{=G>xykp$Jv;yHQ=#d38yB~(b;sGLel!c1 z_PHj2-hF{aSG4Vuru3FY@^j>S#&#>ki#cg_NFA+scQM9S4%x4C_e<()_3%riu-$`9 z9(I;QwHMLnFYl?xnChVU=AR<G5n8#~ z3@s?IPydJ~cX9Ceig3u*Wa8h!nLDQ4z~e*LO@zx-vZQ**THr)Tct#DXie9*T1*#E! z_agEN>G5KykuJJ|(p}hJj9>_Ciy2E}-!j`LS27*c??<@0*?#SN{2PMU23v1&t|M7v zM2{c~+6^J!^T1V91c@u~J9i1VBxsljCqwGyp=vxZo-TgXFPo1@>3JR4JP$`2$7|W^ zIRR4gg@$h(@3qyWH!K*nqLF(u@i_mHq(W{J9$<=E$ig=?)ljh1v)7TsKO171Ki0cT zk!~jMTd`sOYAH!RHtw0CayvQNd8rJ7b-e(p%u?{9}QwZit^2im{8RolK^%EfXqKpR4oI3LNU~6ke!6npaf8Vn3gyW zrxDI{AR?Yu;<@HJnKlTzqsoh;Xbn|P2$rNgpZMQUUCH$IBRR5#fz}8R&Zg`&p`W{C z^RmOPhA1KV0P~!4wtLATQhd!mWvBvR^z$zqBR&w3$H?tGcbFiWsWhMp;DuMy7tts{ zrNjd{%S&*w^?Z9#Y+3)Vh@!_xKuWJp@|5`{>?bKegSfKkfTTT`4o138F zPpP#$%KCnH?0B4wwAPg8SH1%GkmuRy4E~F#V~!?w-tL}`{J`=Q)ug{(FJ4IC(F2&n w!)L#Q0hj44D8gkYk~WIDGETS["bat"].draw(), 60000) : undefined; @@ -37,6 +22,16 @@ } } }); - WIDGETS["bat"]={area:"tr",width:40,draw:draw}; + WIDGETS["bat"]={area:"tr",width:40,draw:function() { + var s = 39; + var x = this.x, y = this.y; + g.reset(); + if (Bangle.isCharging()) { + g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); + x+=16; + } + g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14); + g.setColor("#0f0").fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17); + }}; setWidth(); })() diff --git a/apps/widbat/widget.png b/apps/widbat/widget.png index 630692e38e3b9ba5fbb62b2bc3a33cd61be04836..4f7491ee9f2b43d2eb9d76638d0ac7236f71b3c3 100644 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1SD0tpLGH$&H|6fVg?3oVGw3ym^DWND9B#o z>Fdh=luL|VRno>I?jle~HZvrm#5q4VH#M&W$Yo$~E=o--Nlj5G&n(GMaQE~LNYP7W z2a5N3x;Tbp+k%W^7#OjF z>xR6YxZwP~msj(vrfOZ-XeRq|Me#Trz%PXw%s%enD+*Q(4(O$7{!;ZftnH z;OBlT>zT*Z6fWiIJ=^fB#+Gq&KYwNU9G>d#i1%lbc&mQzv1$1Hd4@jEzQ$ig(U1P} ztw_>iy0bUgRQAhv#r8w_3THnth|Y_Bu~2P(K-W{of(yL1!oTHzSg}U@{@LL7qVaI@ z+@$_vb^q8z5_}o2;UU)6GFFD!1nXQXDJyW8*}q*=>C>8uX+JZBtJj^_U(L8LQa1YE T%Q{h@PZ>O2{an^LB{Ts5c#wMk diff --git a/apps/widbatv/ChangeLog b/apps/widbatv/ChangeLog new file mode 100644 index 000000000..55cda0f21 --- /dev/null +++ b/apps/widbatv/ChangeLog @@ -0,0 +1 @@ +0.01: New widget diff --git a/apps/widbatv/widget.js b/apps/widbatv/widget.js new file mode 100644 index 000000000..cc52a0f8e --- /dev/null +++ b/apps/widbatv/widget.js @@ -0,0 +1,19 @@ +Bangle.on('charging',function(charging) { + if(charging) Bangle.buzz(); + WIDGETS["batv"].draw(); +}); +setInterval(()=>WIDGETS["batv"].draw(), 60000); +Bangle.on('lcdPower', function(on) { + if (on) WIDGETS["batv"].draw(); // refresh at power on +}); +WIDGETS["batv"]={area:"tr",width:14,draw:function() { + var x = this.x, y = this.y; + g.reset(); + if (Bangle.isCharging()) { + g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y); + } else { + g.clearRect(x,y,x+14,y+24); + g.setColor(g.theme.fg).fillRect(x+2,y+2,x+12,y+22).clearRect(x+4,y+4,x+10,y+20).fillRect(x+5,y+1,x+9,y+2); + g.setColor("#0f0").fillRect(x+4,y+20-(E.getBattery()*16/100),x+10,y+20); + } +}}; diff --git a/apps/widbatv/widget.png b/apps/widbatv/widget.png new file mode 100644 index 0000000000000000000000000000000000000000..e31704d7bb7d90eeee09b4d116729f723a5f9490 GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WI14-?iy0WWg+Z8+Vb&Z8pdfpR zr>`sfQ!X)fMY&@Rd}Tl(+02lL66gHf+|;}hAeVu`xhOTUBsE2$JhLQ2!QIn0AVn{g z9Vi~`>EamTas2HxOD+Zl9v0(A|M$y82O63^I+&;4;^W8jF3);u&Sn;lsp4-Ne(kiA zI^!$7f3MReSyibvNwwvMwORZISC|=D1RngEf99y8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); - else - g.setColor(g.theme.dark ? "#666" : "#999"); - g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); - } - function changed() { - WIDGETS["bluetooth"].draw(); - g.flip();// turns screen on - } - NRF.on('connect',changed); - NRF.on('disconnect',changed); - WIDGETS["bluetooth"]={area:"tr",width:15,draw:draw}; -})() +WIDGETS["bluetooth"]={area:"tr",width:15,draw:function() { + g.reset(); + if (NRF.getSecurityStatus().connected) + g.setColor((g.getBPP()>8) ? "#07f" : (g.theme.dark ? "#0ff" : "#00f")); + else + g.setColor(g.theme.dark ? "#666" : "#999"); + g.drawImage(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="),2+this.x,2+this.y); +},changed:function() { + WIDGETS["bluetooth"].draw(); + Bangle.setLCDPower(1); // turn screen on +}}; +NRF.on('connect',WIDGETS["bluetooth"].changed); +NRF.on('disconnect',WIDGETS["bluetooth"].changed); diff --git a/apps/widcom/ChangeLog b/apps/widcom/ChangeLog new file mode 100644 index 000000000..5d08e91e6 --- /dev/null +++ b/apps/widcom/ChangeLog @@ -0,0 +1,3 @@ +0.02: Works with light theme + Doesn't drain battery by updating every 2 secs + Fix alignment diff --git a/apps/widcom/widget.js b/apps/widcom/widget.js index b9c911dbf..bce9453c5 100644 --- a/apps/widcom/widget.js +++ b/apps/widcom/widget.js @@ -1,30 +1,17 @@ (function(){ - //var img = E.toArrayBuffer(atob("FBSBAAAAAAAAA/wAf+AP/wH/2D/zw/w8PwfD9nw+b8Pg/Dw/w8/8G/+A//AH/gA/wAAAAAAA")); - //var img = E.toArrayBuffer(atob("GBiBAAB+AAP/wAeB4A4AcBgAGDAADHAADmABhmAHhsAfA8A/A8BmA8BmA8D8A8D4A2HgBmGABnAADjAADBgAGA4AcAeB4AP/wAB+AA==")); - var img = E.toArrayBuffer(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A")); - - function draw() { + var cp = Bangle.setCompassPower; + Bangle.setCompassPower = () => { + cp.apply(Bangle, arguments); + WIDGETS.compass.draw(); + }; + + WIDGETS.compass={area:"tr",width:24,draw:function() { g.reset(); if (Bangle.isCompassOn()) { - g.setColor(1,0.8,0); // on = amber + g.setColor(g.theme.dark ? "#FC0" : "#F00"); } else { - g.setColor(0.3,0.3,0.3); // off = grey + g.setColor(g.theme.dark ? "#333" : "#CCC"); } - g.drawImage(img, 10+this.x, 2+this.var); - } - - var timerInterval; - Bangle.on('lcdPower', function(on) { - if (on) { - WIDGETS.compass.draw(); - if (!timerInterval) timerInterval = setInterval(()=>WIDGETS.compass.draw(), 2000); - } else { - if (timerInterval) { - clearInterval(timerInterval); - timerInterval = undefined; - } - } - }); - - WIDGETS.compass={area:"tr",width:24,draw:draw}; + g.drawImage(atob("FBSBAAH4AH/gHAODgBwwAMYABkAMLAPDwPg8CYPBkDwfA8PANDACYABjAAw4AcHAOAf+AB+A"), 2+this.x, 2+this.var); + }}; })(); diff --git a/apps/widgps/ChangeLog b/apps/widgps/ChangeLog index d80e09912..57bb53bb7 100644 --- a/apps/widgps/ChangeLog +++ b/apps/widgps/ChangeLog @@ -1,2 +1,3 @@ 0.01: First version 0.02: Don't break if running on 2v08 firmware (just don't display anything) +0.03: Fix positioning diff --git a/apps/widgps/widget.js b/apps/widgps/widget.js index 19be2abaf..6ef55e27b 100644 --- a/apps/widgps/widget.js +++ b/apps/widgps/widget.js @@ -4,11 +4,11 @@ function draw() { g.reset(); if (Bangle.isGPSOn()) { - g.setColor(1,0.8,0); // on = amber + g.setColor("#FD0"); // on = amber } else { - g.setColor(0.3,0.3,0.3); // off = grey + g.setColor("#888"); // off = grey } - g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), 10+this.x, 2+this.y); + g.drawImage(atob("GBiBAAAAAAAAAAAAAA//8B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+B//+BgYGBgYGBgYGBgYGBgYGBgYGB//+A//8AAAAAAAAAAAAA=="), this.x, 2+this.y); } var timerInterval; diff --git a/apps/widhrm/ChangeLog b/apps/widhrm/ChangeLog index 45dcfa87e..93e2eaf66 100644 --- a/apps/widhrm/ChangeLog +++ b/apps/widhrm/ChangeLog @@ -2,3 +2,4 @@ 0.02: Tweaks for variable size widget system 0.03: Ensure redrawing works with variable size widget system 0.04: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799) +0.05: Use new 'lock' event, not LCD (so it works on Bangle.js 2) diff --git a/apps/widhrm/widget.js b/apps/widhrm/widget.js index 54b105d1e..7ffe1aa6d 100644 --- a/apps/widhrm/widget.js +++ b/apps/widhrm/widget.js @@ -1,13 +1,38 @@ (() => { - var currentBPM = undefined; - var lastBPM = undefined; - var firstBPM = true; // first reading since sensor turned on + if (!Bangle.isLocked) return; // old firmware + var currentBPM; + var lastBPM; + var isHRMOn = false; - function draw() { + // turn on sensor when the LCD is unlocked + Bangle.on('lock', function(isLocked) { + if (!isLocked) { + Bangle.setHRMPower(1,"widhrm"); + currentBPM = undefined; + WIDGETS["hrm"].draw(); + } else { + Bangle.setHRMPower(0,"widhrm"); + } + }); + + var hp = Bangle.setHRMPower; + Bangle.setHRMPower = () => { + hp.apply(Bangle, arguments); + isHRMOn = Bangle.isHRMOn(); + WIDGETS["hrm"].draw(); + }; + + Bangle.on('HRM',function(d) { + currentBPM = d.bpm; + lastBPM = currentBPM; + WIDGETS["hrm"].draw(); + }); + + // add your widget + WIDGETS["hrm"]={area:"tl",width:24,draw:function() { var width = 24; g.reset(); - g.setFont("6x8", 1); - g.setFontAlign(0, 0); + g.setFont("6x8", 1).setFontAlign(0, 0); g.clearRect(this.x,this.y+15,this.x+width,this.y+23); // erase background var bpm = currentBPM, isCurrent = true; if (bpm===undefined) { @@ -16,36 +41,12 @@ } if (bpm===undefined) bpm = "--"; - g.setColor(isCurrent ? "#ffffff" : "#808080"); + g.setColor(isCurrent ? g.theme.fg : "#808080"); g.drawString(bpm, this.x+width/2, this.y+19); - g.setColor(isCurrent ? "#ff0033" : "#808080"); + g.setColor(isHRMOn ? "#ff0033" : "#808080"); g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),this.x+(width-10)/2,this.y+1); g.setColor(-1); - } + }}; - // redraw when the LCD turns on - Bangle.on('lcdPower', function(on) { - if (on) { - Bangle.setHRMPower(1,"widhrm"); - firstBPM = true; - currentBPM = undefined; - WIDGETS["hrm"].draw(); - } else { - Bangle.setHRMPower(0,"widhrm"); - } - }); - - Bangle.on('HRM',function(d) { - if (firstBPM) - firstBPM=false; // ignore the first one as it's usually rubbish - else { - currentBPM = d.bpm; - lastBPM = currentBPM; - } - WIDGETS["hrm"].draw(); - }); - Bangle.setHRMPower(Bangle.isLCDOn(),"widhrm"); - - // add your widget - WIDGETS["hrm"]={area:"tl",width:24,draw:draw}; + Bangle.setHRMPower(!Bangle.isLocked(),"widhrm"); })(); diff --git a/apps/widhrt/ChangeLog b/apps/widhrt/ChangeLog index fdb495797..39520ad6a 100644 --- a/apps/widhrt/ChangeLog +++ b/apps/widhrt/ChangeLog @@ -1,3 +1,5 @@ 0.01: First version 0.02: Don't break if running on 2v08 firmware (just don't display anything) - +0.03: Works with light theme + Doesn't drain battery by updating every 2 secs + fix alignment diff --git a/apps/widhrt/widget.js b/apps/widhrt/widget.js index 8ac76def8..d9716fa24 100644 --- a/apps/widhrt/widget.js +++ b/apps/widhrt/widget.js @@ -1,28 +1,18 @@ (function(){ if (!Bangle.isHRMOn) return; // old firmware + var hp = Bangle.setHRMPower; + Bangle.setHRMPower = () => { + hp.apply(Bangle, arguments); + WIDGETS.widhrt.draw(); + }; - function draw() { + WIDGETS.widhrt={area:"tr",width:24,draw:function() { g.reset(); if (Bangle.isHRMOn()) { - g.setColor(1,0,0); // on = red + g.setColor("#f00"); // on = red } else { - g.setColor(0.3,0.3,0.3); // off = grey + g.setColor(g.theme.dark ? "#333" : "#CCC"); // off = grey } - g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 10+this.x, 2+this.y); - } - - var timerInterval; - Bangle.on('lcdPower', function(on) { - if (on) { - WIDGETS.widhrt.draw(); - if (!timerInterval) timerInterval = setInterval(()=>WIDGETS["widhrt"].draw(), 2000); - } else { - if (timerInterval) { - clearInterval(timerInterval); - timerInterval = undefined; - } - } - }); - - WIDGETS.widhrt={area:"tr",width:24,draw:draw}; + g.drawImage(atob("FhaBAAAAAAAAAAAAAcDgD8/AYeGDAwMMDAwwADDAAMOABwYAGAwAwBgGADAwAGGAAMwAAeAAAwAAAAAAAAAAAAA="), 1+this.x, 1+this.y); + }}; })(); diff --git a/apps/widmp/ChangeLog b/apps/widmp/ChangeLog index 5560f00bc..3996d9e74 100644 --- a/apps/widmp/ChangeLog +++ b/apps/widmp/ChangeLog @@ -1 +1,3 @@ 0.01: New App! +0.02: Fix position and overdraw bugs + Better memory usage, theme support diff --git a/apps/widmp/widget.js b/apps/widmp/widget.js index cebdb60f5..d8abc3a9c 100644 --- a/apps/widmp/widget.js +++ b/apps/widmp/widget.js @@ -1,20 +1,6 @@ -/* jshint esversion: 6 */ -(() => { - - const BLACK = 0, MOON = 0x41f, MC = 29.5305882, NM = 694039.09; - var r = 12, mx = 0, my = 0; - - var moon = { - 0: () => { g.reset().setColor(BLACK).fillRect(mx - r, my - r, mx + r, my + r);}, - 1: () => { moon[0](); g.setColor(MOON).drawCircle(mx, my, r);}, - 2: () => { moon[3](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, - 3: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx - r, my - r, mx, my + r);}, - 4: () => { moon[3](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, - 5: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r);}, - 6: () => { moon[7](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, - 7: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx, my - r, mx + r + r, my + r);}, - 8: () => { moon[7](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);} - }; +WIDGETS["widmoon"] = { area: "tr", width: 24, draw: function() { + const MC = 29.5305882, NM = 694039.09; + var r = 11, mx = this.x + 12; my = this.y + 12; function moonPhase(d) { var tmp, month = d.getMonth(), year = d.getFullYear(), day = d.getDate(); @@ -23,11 +9,18 @@ return Math.round(((tmp - (tmp | 0)) * 7)+1); } - function draw() { - mx = this.x; my = this.y + 12; - moon[moonPhase(Date())](); - } + const BLACK = g.theme.bg, MOON = 0x41f; + var moon = { + 0: () => { g.reset().setColor(BLACK).fillRect(mx - r, my - r, mx + r, my + r);}, + 1: () => { moon[0](); g.setColor(MOON).drawCircle(mx, my, r);}, + 2: () => { moon[3](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 3: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx - r, my - r, mx, my + r);}, + 4: () => { moon[3](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 5: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r);}, + 6: () => { moon[7](); g.setColor(MOON).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);}, + 7: () => { moon[0](); g.setColor(MOON).fillCircle(mx, my, r).setColor(BLACK).fillRect(mx, my - r, mx + r, my + r);}, + 8: () => { moon[7](); g.setColor(BLACK).fillEllipse(mx - r / 2, my - r, mx + r / 2, my + r);} + }; + moon[moonPhase(Date())](); +} }; - WIDGETS["widmoon"] = { area: "tr", width: 24, draw: draw }; - -})(); diff --git a/apps/widtbat/ChangeLog b/apps/widtbat/ChangeLog index 4c21f3ace..37513808a 100644 --- a/apps/widtbat/ChangeLog +++ b/apps/widtbat/ChangeLog @@ -1 +1,2 @@ 0.01: New Widget! +0.02: Theme support, memory savings diff --git a/apps/widtbat/widget.js b/apps/widtbat/widget.js index 6d5aded8b..c78653358 100644 --- a/apps/widtbat/widget.js +++ b/apps/widtbat/widget.js @@ -1,17 +1,11 @@ -/* jshint esversion: 6 */ -(() => { - const CBS = 0x41f, CBC = 0x07E0; - var xo = 6, xl = 22, yo = 9, h = 17; - - function draw() { - g.reset().setColor(CBS).drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4); - g.setColor(0).fillRect(this.x + xo, this.y + yo, this.x + xl, this.y + h); - var cbc = (Bangle.isCharging()) ? CBC : CBS; - g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h); - } - Bangle.on('charging', function(charging) { - if (charging) Bangle.buzz(); - Bangle.drawWidgets(); - }); - WIDGETS["widtbat"] = { area:"tr", width:32, draw: draw }; -})(); +Bangle.on('charging', function(charging) { + if (charging) Bangle.buzz(); + WIDGETS["widtbat"].draw(); +}); +WIDGETS["widtbat"] = { area:"tr", width:32, draw: function() { + const xo = 6, xl = 22, yo = 9, h = 17; + g.reset().setColor("#08f").drawImage(require("heatshrink").decompress(atob("j0TwIHEv///kD////EfAYPwuEAgPB4EAg/HCgMfzgDBvwOC/IOC84ONDoUcFgc/AYOAHYRDE")), this.x + 1, this.y + 4); + g.clearRect(this.x + xo, this.y + yo, this.x + xl, this.y + h); + var cbc = (Bangle.isCharging()) ? "#0f0" : "#08f"; + g.setColor(cbc).fillRect(this.x + xo, this.y + yo, this.x + (xl - xo) / 100 * E.getBattery() + xo, this.y + h); +} }; diff --git a/apps/widtbat/widget.png b/apps/widtbat/widget.png index 4294f0ca32299687f44c631a14109629f857f4a1..f2943bc52013b52db5cba8383076118faf2cee42 100644 GIT binary patch delta 210 zcmeBYf5$jMrJl3EBeIx*fm;}a85w5Hkzin8U@!6Xb!C6bCB|;Z|IEndDp07`)5S3) zc47kxh(UJ5u}CVu5^^4XIdJ^guk&< uHnkdA$(@|PJx4l!-G_RZkwCiPK9iWFx9rgeFQONMggssTT-G@yGywoz*h~We literal 911 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC#^NA%Cx&(BWL^R}0jUw5X}-P; zT0k}j0~4bV12aeo5Hhr9GO&Qz3=C>Ont_3N0V6_o0TW!-U;#6N4N`dWm6H(AkjTuC zh>{3jAFJg2T)o7U{G?R9irfN_0tTB3D*7iAWdWaj57fXq!y$}cUk zRZ;?31P2gzmSmu1yMhW`HkzTTSoaJ~2f8+kKR6OrFMCe`Z3#?B8 zC49*z`DeL<{|n(yEW*2#>kN&)@-iK7o}>EZyS}siuHP4aO2{#|b+YeT#Q0>l@TS?U z-Aq$V>eZY@m1ibibT(C-EV=!^vgEPuBgal1dB(`on$ebYVzQRQSqH^a%uB2$%z8DE z>HW$2WB(=7FJIlJG`oKy`vg^X$>_F+_K$r^%z0J$4H%3Uf0^)&Vb^2FKZ>Hvi{%c8 zF){0!&1!K=Za5?$k>$y;xZYrj&4(F{orb)&huCVPI|2^(Yw!ygvDk*ZIC=b8qrU_D zf!{11YaOe0ai@u|sP7R^IL*BK!>a|WRUYR#T}!NZa6kT-*Meo`Ehd*7|H&FNC$lP6 z8`u=+ML*@cApFdsH@o47vLbVa!hgXV%VUoI`al2PNuH;-O$_R { - var width = 28, - ver = process.env.VERSION.split('.'); - function draw() { - g.reset().setColor(0, 0.5, 1).setFont("6x8", 1); - g.drawString(ver[0], this.x + 2, this.y + 4, true); - g.setFontAlign(0, -1, 0).drawString(ver[1], this.x + width / 2, this.y + 14, true); - } - WIDGETS["version"] = { area: "tr", width: width, draw: draw }; -})(); +WIDGETS["version"] = { area: "tr", width: 28, draw: function() { + var ver = process.env.VERSION.split('.'); + // Example: if ver is 2v11 instead of 2v10.142 write "Rel" (Release) instead of Build number + if(typeof ver[1] === 'undefined') ver[1] = "Rel"; + + g.reset().setColor((g.getBPP()<8)?(g.theme.dark?"#0ff":"#00f"):"#08f").setFont("6x8"); + g.drawString(ver[0], this.x + 2, this.y + 4, true); + g.setFontAlign(0, -1, 0).drawString(ver[1], this.x + this.width / 2, this.y + 14, true); +} }; diff --git a/apps/worldclock/ChangeLog b/apps/worldclock/ChangeLog index e922ef2a4..831dd3b5c 100644 --- a/apps/worldclock/ChangeLog +++ b/apps/worldclock/ChangeLog @@ -2,3 +2,5 @@ 0.02: Update custom.html for refactor; add README 0.03: Update for larger secondary timezone display (#610) 0.04: setUI, different screen sizes +0.05: Now update *on* the minute rather than every 15 secs + Fix rendering of single extra timezone on Bangle.js 2 diff --git a/apps/worldclock/app.js b/apps/worldclock/app.js index 84cb29874..2627e056c 100644 --- a/apps/worldclock/app.js +++ b/apps/worldclock/app.js @@ -1,5 +1,3 @@ -/* jshint esversion: 6 */ - const big = g.getWidth()>200; // Font for primary time and date const primaryTimeFontSize = big?6:5; @@ -16,8 +14,13 @@ const xcol2 = g.getWidth() - xcol1; const font = "6x8"; +/* TODO: we could totally use 'Layout' here and +avoid a whole bunch of hard-coded offsets */ + + const xyCenter = g.getWidth() / 2; const yposTime = big ? 75 : 60; +const yposTime2 = yposTime + (big ? 100 : 60); const yposDate = big ? 130 : 90; const yposWorld = big ? 170 : 120; @@ -29,41 +32,52 @@ var offsets = require("Storage").readJSON("worldclock.settings.json") || []; // TESTING CODE // Used to test offset array values during development. // Uncomment to override secondary offsets value - -// const mockOffsets = { -// zeroOffsets: [], -// oneOffset: [["UTC", 0]], -// twoOffsets: [ -// ["Tokyo", 9], -// ["UTC", 0], -// ], -// fourOffsets: [ -// ["Tokyo", 9], -// ["UTC", 0], -// ["Denver", -7], -// ["Miami", -5], -// ], -// fiveOffsets: [ -// ["Tokyo", 9], -// ["UTC", 0], -// ["Denver", -7], -// ["Chicago", -6], -// ["Miami", -5], -// ], -// }; +/* +const mockOffsets = { + zeroOffsets: [], + oneOffset: [["UTC", 0]], + twoOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ], + fourOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ["Denver", -7], + ["Miami", -5], + ], + fiveOffsets: [ + ["Tokyo", 9], + ["UTC", 0], + ["Denver", -7], + ["Chicago", -6], + ["Miami", -5], + ], +};*/ // Uncomment one at a time to test various offsets array scenarios -// offsets = mockOffsets.zeroOffsets; // should render nothing below primary time -// offsets = mockOffsets.oneOffset; // should render larger in two rows -// offsets = mockOffsets.twoOffsets; // should render two in columns -// offsets = mockOffsets.fourOffsets; // should render in columns -// offsets = mockOffsets.fiveOffsets; // should render first four in columns +//offsets = mockOffsets.zeroOffsets; // should render nothing below primary time +//offsets = mockOffsets.oneOffset; // should render larger in two rows +//offsets = mockOffsets.twoOffsets; // should render two in columns +//offsets = mockOffsets.fourOffsets; // should render in columns +//offsets = mockOffsets.fiveOffsets; // should render first four in columns // END TESTING CODE // Check settings for what type our clock should be //var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; -var secondInterval; + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} function doublenum(x) { return x < 10 ? "0" + x : "" + x; @@ -73,7 +87,7 @@ function getCurrentTimeFromOffset(dt, offset) { return new Date(dt.getTime() + offset * 60 * 60 * 1000); } -function drawSimpleClock() { +function draw() { // get date var d = new Date(); var da = d.toString().split(" "); @@ -111,9 +125,9 @@ function drawSimpleClock() { // For a single secondary timezone, draw it bigger and drop time zone to second line const xOffset = 30; g.setFont(font, secondaryTimeFontSize); - g.drawString(`${hours}:${minutes}`, xyCenter, yposTime + 100, true); + g.drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true); g.setFont(font, secondaryTimeZoneFontSize); - g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime + 130, true); + g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true); // draw Day, name of month, Date g.setFont(font, secondaryTimeZoneFontSize); @@ -132,6 +146,8 @@ function drawSimpleClock() { g.drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true); } }); + + queueDraw(); } // clean app screen @@ -141,18 +157,15 @@ Bangle.setUI("clock"); Bangle.loadWidgets(); Bangle.drawWidgets(); -// refesh every 15 sec when screen is on -Bangle.on("lcdPower", (on) => { - if (secondInterval) clearInterval(secondInterval); - secondInterval = undefined; +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ if (on) { - secondInterval = setInterval(drawSimpleClock, 15e3); - drawSimpleClock(); // draw immediately + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; } }); -// draw now and every 15 sec until display goes off -drawSimpleClock(); -if (Bangle.isLCDOn()) { - secondInterval = setInterval(drawSimpleClock, 15e3); -} +// draw now +draw(); diff --git a/apps/worldclock/screenshot_world.png b/apps/worldclock/screenshot_world.png new file mode 100644 index 0000000000000000000000000000000000000000..ebae637b729adbcde712442d85a1493df5830286 GIT binary patch literal 2937 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 literal 0 HcmV?d00001 diff --git a/bin/create_app_supports_field.js b/bin/create_app_supports_field.js new file mode 100644 index 000000000..d6aada357 --- /dev/null +++ b/bin/create_app_supports_field.js @@ -0,0 +1,99 @@ +#!/usr/bin/nodejs +/* Quick hack to add proper 'supports' field to apps.json +*/ + +var fs = require("fs"); + +var BASEDIR = __dirname+"/../"; + +var appsFile, apps; +try { + appsFile = fs.readFileSync(BASEDIR+"apps.json").toString(); +} catch (e) { + ERROR("apps.json not found"); +} +try{ + apps = JSON.parse(appsFile); +} catch (e) { + console.log(e); + var m = e.toString().match(/in JSON at position (\d+)/); + if (m) { + var char = parseInt(m[1]); + console.log("==============================================="); + console.log("LINE "+appsFile.substr(0,char).split("\n").length); + console.log("==============================================="); + console.log(appsFile.substr(char-10, 20)); + console.log("==============================================="); + } + console.log(m); + ERROR("apps.json not valid JSON"); + +} + +apps = apps.map((app,appIdx) => { + if (app.supports) return app; // already sorted + var tags = []; + if (app.tags) tags = app.tags.split(",").map(t=>t.trim()); + var supportsB1 = true; + var supportsB2 = false; + if (tags.includes("b2")) { + tags = tags.filter(x=>x!="b2"); + supportsB2 = true; + } + if (tags.includes("bno2")) { + tags = tags.filter(x=>x!="bno2"); + supportsB2 = false; + } + if (tags.includes("bno1")) { + tags = tags.filter(x=>x!="bno1"); + supportsB1 = false; + } + app.tags = tags.join(","); + app.supports = []; + if (supportsB1) app.supports.push("BANGLEJS"); + if (supportsB2) app.supports.push("BANGLEJS2"); + return app; +}); + +// search for screenshots +apps = apps.map((app,appIdx) => { + if (app.screenshots) return app; // already sorted + + var files = require("fs").readdirSync(__dirname+"/../apps/"+app.id); + var screenshots = files.filter(fn=>fn.startsWith("screenshot") && fn.endsWith(".png")); + if (screenshots.length) + app.screenshots = screenshots.map(fn => ({url:fn})); + return app; +}); + +var KEY_ORDER = [ + "id","name","shortName","version","description","icon","screenshots","type","tags","supports", + "dependencies", "readme", "custom", "customConnect", "interface", + "allow_emulator", "storage", "data", "sortorder" +]; + +var JS = JSON.stringify; +var json = "[\n "+apps.map(app=>{ + var keys = KEY_ORDER.filter(k=>k in app); + Object.keys(app).forEach(k=>{ + if (!KEY_ORDER.includes(k)) + throw new Error(`Key named ${k} not known!`); + }); + //var keys = Object.keys(app); // don't re-order + + return "{\n "+keys.map(k=>{ + var js = JS(app[k]); + if (k=="storage") { + if (app.storage.length) + js = "[\n "+app.storage.map(s=>JS(s)).join(",\n ")+"\n ]"; + else + js = "[]"; + } + return JS(k)+": "+js; + }).join(",\n ")+"\n }"; +}).join(",\n ")+"\n]\n"; + +//console.log(json); + +console.log("new apps.json written"); +fs.writeFileSync(BASEDIR+"apps.json", json); diff --git a/bin/firmwaremaker.js b/bin/firmwaremaker.js index 4e22dd168..4bc2a70b2 100755 --- a/bin/firmwaremaker.js +++ b/bin/firmwaremaker.js @@ -12,6 +12,7 @@ var ROOTDIR = path.join(__dirname, '..'); var APPDIR = ROOTDIR+'/apps'; var APPJSON = ROOTDIR+'/apps.json'; var OUTFILE = ROOTDIR+'/firmware.js'; +var DEVICE = "BANGLEJS"; var APPS = [ // IDs of apps to install "boot","launch","mclock","setting", "about","alarm","widbat","widbt","welcome" @@ -61,7 +62,8 @@ Promise.all(APPS.map(appid => { if (app===undefined) throw new Error(`App ${appid} not found`); return AppInfo.getFiles(app, { fileGetter : fileGetter, - settings : SETTINGS + settings : SETTINGS, + device : { id : DEVICE } }).then(files => { appfiles = appfiles.concat(files); }); diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 2cb993d00..14ced9ef8 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -29,8 +29,8 @@ if (DEVICE=="BANGLEJS") { } else if (DEVICE=="BANGLEJS2") { var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); var APPS = [ // IDs of apps to install - "boot","launchb2","s7clk","setting", - "about","alarm","widlock","widbat","widbt" + "boot","launch","antonclk","setting", + "about","alarm","health","widlock","widbat","widbt","widid","welcome" ]; } else { console.log("USAGE:"); @@ -102,7 +102,13 @@ function fileGetter(url) { fs.writeFileSync(url, code); } } - return Promise.resolve(fs.readFileSync(url).toString("binary")); + var blob = fs.readFileSync(url); + var data; + if (url.endsWith(".js") || url.endsWith(".json")) + data = blob.toString(); // allow JS/etc to be written in UTF-8 + else + data = blob.toString("binary") + return Promise.resolve(data); } // If file should be evaluated, try and do it... @@ -132,7 +138,8 @@ Promise.all(APPS.map(appid => { if (app===undefined) throw new Error(`App ${appid} not found`); return AppInfo.getFiles(app, { fileGetter : fileGetter, - settings : SETTINGS + settings : SETTINGS, + device : { id : DEVICE } }).then(files => { appfiles = appfiles.concat(files); }); diff --git a/bin/sanitycheck.js b/bin/sanitycheck.js index b98aa9ef3..a84d26efd 100755 --- a/bin/sanitycheck.js +++ b/bin/sanitycheck.js @@ -50,11 +50,12 @@ try{ } const APP_KEYS = [ - 'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type', - 'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', 'allow_emulator', + 'id', 'name', 'shortName', 'version', 'icon', 'screenshots', 'description', 'tags', 'type', + 'sortorder', 'readme', 'custom', 'customConnect', 'interface', 'storage', 'data', + 'supports', 'allow_emulator', 'dependencies' ]; -const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite']; +const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports']; const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate']; const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ]; @@ -81,6 +82,14 @@ apps.forEach((app,appIdx) => { if (!app.name) ERROR(`App ${app.id} has no name`); var isApp = !app.type || app.type=="app"; if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`); + if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`); + else { + app.supports.forEach(dev => { + if (!["BANGLEJS","BANGLEJS2"].includes(dev)) + ERROR(`App ${app.id} has unknown device in 'supports' field - ${dev}`); + }); + } + if (!app.version) WARN(`App ${app.id} has no version`); else { if (!fs.existsSync(appDir+"ChangeLog")) { @@ -98,6 +107,13 @@ apps.forEach((app,appIdx) => { if (!app.description) ERROR(`App ${app.id} has no description`); if (!app.icon) ERROR(`App ${app.id} has no icon`); if (!fs.existsSync(appDir+app.icon)) ERROR(`App ${app.id} icon doesn't exist`); + if (app.screenshots) { + if (!Array.isArray(app.screenshots)) ERROR(`App ${app.id} screenshots is not an array`); + app.screenshots.forEach(screenshot => { + if (!fs.existsSync(appDir+screenshot.url)) + ERROR(`App ${app.id} screenshot file ${screenshot.url} not found`); + }); + } if (app.readme && !fs.existsSync(appDir+app.readme)) ERROR(`App ${app.id} README file doesn't exist`); if (app.custom && !fs.existsSync(appDir+app.custom)) ERROR(`App ${app.id} custom HTML doesn't exist`); if (app.customConnect && !app.custom) ERROR(`App ${app.id} has customConnect but no customn HTML`); @@ -105,8 +121,8 @@ apps.forEach((app,appIdx) => { if (app.dependencies) { if (("object"==typeof app.dependencies) && !Array.isArray(app.dependencies)) { Object.keys(app.dependencies).forEach(dependency => { - if (app.dependencies[dependency]!="type") - ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' right now`); + if (!["type","app"].includes(app.dependencies[dependency])) + ERROR(`App ${app.id} 'dependencies' must all be tagged 'type' or 'app' right now`); }); } else ERROR(`App ${app.id} 'dependencies' must be an object`); @@ -117,7 +133,7 @@ apps.forEach((app,appIdx) => { if (isGlob(file.name)) ERROR(`App ${app.id} storage file ${file.name} contains wildcards`); let char = file.name.match(FORBIDDEN_FILE_NAME_CHARS) if (char) ERROR(`App ${app.id} storage file ${file.name} contains invalid character "${char[0]}"`) - if (fileNames.includes(file.name)) + if (fileNames.includes(file.name) && !file.supports) // assume that there aren't duplicates if 'supports' is set ERROR(`App ${app.id} file ${file.name} is a duplicate`); fileNames.push(file.name); allFiles.push({app: app.id, file: file.name}); @@ -126,6 +142,7 @@ apps.forEach((app,appIdx) => { var fileContents = ""; if (file.content) fileContents = file.content; if (file.url) fileContents = fs.readFileSync(appDir+file.url).toString(); + if (file.supports && !Array.isArray(file.supports)) ERROR(`App ${app.id} file ${file.name} supports field is not an array`); if (file.evaluate) { try { acorn.parse("("+fileContents+")"); @@ -156,7 +173,7 @@ apps.forEach((app,appIdx) => { } } for (const key in file) { - if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id}'s ${file.name} has unknown key ${key}`); + if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id} file ${file.name} has unknown key ${key}`); } }); let dataNames = []; diff --git a/bin/thumbnailer.js b/bin/thumbnailer.js new file mode 100755 index 000000000..b6862741a --- /dev/null +++ b/bin/thumbnailer.js @@ -0,0 +1,164 @@ +#!/usr/bin/node + +/* +var EMULATOR = "banglejs2"; +var DEVICEID = "BANGLEJS2"; +*/ +var EMULATOR = "banglejs1"; +var DEVICEID = "BANGLEJS"; + +var singleAppId; + +if (process.argv.length!=3 && process.argv.length!=2) { + console.log("USAGE:"); + console.log(" bin/thumbnailer.js"); + console.log(" - all thumbnails"); + console.log(" bin/thumbnailer.js APP_ID"); + console.log(" - just one app"); + process.exit(1); +} +if (process.argv.length==3) + singleAppId = process.argv[2]; + +if (!require("fs").existsSync(__dirname + "/../../EspruinoWebIDE")) { + console.log("You need to:"); + console.log(" git clone https://github.com/espruino/EspruinoWebIDE"); + console.log("At the same level as this project"); + process.exit(1); +} + +eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/emulator_"+EMULATOR+".js").toString()); +eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/emu_"+EMULATOR+".js").toString()); +eval(require("fs").readFileSync(__dirname + "/../../EspruinoWebIDE/emu/common.js").toString()); + +var SETTINGS = { + pretokenise : true +}; +var Const = { +}; +module = undefined; +eval(require("fs").readFileSync(__dirname + "/../core/lib/espruinotools.js").toString()); +eval(require("fs").readFileSync(__dirname + "/../core/js/utils.js").toString()); +eval(require("fs").readFileSync(__dirname + "/../core/js/appinfo.js").toString()); +var apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json")); + +/* we factory reset ONCE, get this, then we can use it to reset +state quickly for each new app */ +var factoryFlashMemory = new Uint8Array(FLASH_SIZE); +// Log of messages from app +var appLog = ""; +// List of apps that errored +var erroredApps = []; + +jsRXCallback = function() {}; +jsUpdateGfx = function() {}; + +function ERROR(s) { + console.error(s); + process.exit(1); +} + +function onConsoleOutput(txt) { + appLog += txt + "\n"; +} + +function getThumbnail(appId, imageFn) { + console.log("Thumbnail for "+appId); + var app = apps.find(a=>a.id==appId); + if (!app) ERROR(`App ${JSON.stringify(appId)} not found`); + if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); + + + return new Promise(resolve => { + AppInfo.getFiles(app, { + fileGetter:function(url) { + console.log(__dirname+"/"+url); + return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); + }, + settings : SETTINGS, + device : { id : DEVICEID } + }).then(files => { + console.log("AppInfo returned");//, files); + flashMemory.set(factoryFlashMemory); + jsTransmitString("reset()\n"); + console.log("Uploading..."); + jsTransmitString("g.clear()\n"); + var command = files.map(f=>f.cmd).join("\n")+"\n"; + command += `load("${appId}.app.js")\n`; + appLog = ""; + jsTransmitString(command); + console.log("Done."); + jsStopIdle(); + + var rgba = new Uint8Array(GFX_WIDTH*GFX_HEIGHT*4); + jsGetGfxContents(rgba); + var rgba32 = new Uint32Array(rgba.buffer); + var firstPixel = rgba32[0]; + var blankImage = rgba32.every(col=>col==firstPixel) + + if (appLog.indexOf("Uncaught")>=0) + erroredApps.push( { id : app.id, log : appLog } ); + + if (!blankImage) { + var Jimp = require("jimp"); + let image = new Jimp(GFX_WIDTH, GFX_HEIGHT, function (err, image) { + if (err) throw err; + let buffer = image.bitmap.data; + buffer.set(rgba); + image.write(imageFn, (err) => { + if (err) throw err; + console.log("Image written as "+imageFn); + resolve(true); + }); + }); + } else { + console.log("Image is empty"); + resolve(false); + } + + }); + }); +} + +var screenshots = []; + +// wait until loaded... +setTimeout(function() { + console.log("Loaded..."); + jsInit(); + jsIdle(); + console.log("Factory reset"); + jsTransmitString("Bangle.factoryReset()\n"); + factoryFlashMemory.set(flashMemory); + console.log("Ready!"); + + if (singleAppId) { + getThumbnail(singleAppId, "screenshots/"+singleAppId+"-"+EMULATOR+".png"); + return; + } + + var appList = apps.filter(app => (!app.type || app.type=="clock") && !app.custom); + appList = appList.filter(app => !app.screenshots && app.supports.includes(DEVICEID)); + + var promise = Promise.resolve(); + appList.forEach(app => { + promise = promise.then(() => { + var imageFile = "screenshots/"+app.id+"-"+EMULATOR+".png"; + return getThumbnail(app.id, imageFile).then(ok => { + screenshots.push({ + id : app.id, + url : imageFile, + version: app.version + }); + }); + }); + }); + + promise.then(function() { + console.log("Complete!"); + require("fs").writeFileSync("screenshots.json", JSON.stringify(screenshots,null,2)); + console.log("Errored Apps", erroredApps); + }); + + +}); diff --git a/core b/core index 0fd608f08..59f80bb52 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 0fd608f085deff9b39f2db3559ecc88edb232aba +Subproject commit 59f80bb52a38da12cb272f9106cb3951b49dab2e diff --git a/css/main.css b/css/main.css index 0dbe8da14..82f6fbcfe 100644 --- a/css/main.css +++ b/css/main.css @@ -23,15 +23,48 @@ .filter-nav { display: inline-block; } +.device-nav { + display: inline-block; +} .sort-nav { float: right; } + +.app-tile { + position: relative; + border-bottom: 1px solid #EEE; + margin-bottom: 4px; + min-height: 8em; +} + .tile-content { position: relative; } .link-github { position:absolute; top: 36px; left: -24px; } -.btn-favourite { - color: red; +.tile-screenshot { + position:absolute;bottom:1em;right:1em; + width:4em;height:4em; + padding:2px;border:1px solid black; + cursor:pointer; +} + +.btn.btn-favourite { color: red; } +.btn.btn-favourite:hover { color: red; } + +.icon.icon-emulator { text-indent: 0px; } /*override spectre*/ +.icon.icon-emulator { + content: url("data:image/svg+xml,%3Csvg fill='rgb(87, 85, 217)' xmlns='http://www.w3.org/2000/svg' viewBox='4 4 40 40' width='1em' height='1em'%3E%3Cpath d='M 8.5 5 C 6.0324991 5 4 7.0324991 4 9.5 L 4 30.5 C 4 32.967501 6.0324991 35 8.5 35 L 17 35 L 17 40 L 13.5 40 A 1.50015 1.50015 0 1 0 13.5 43 L 18.253906 43 A 1.50015 1.50015 0 0 0 18.740234 43 L 29.253906 43 A 1.50015 1.50015 0 0 0 29.740234 43 L 34.5 43 A 1.50015 1.50015 0 1 0 34.5 40 L 31 40 L 31 35 L 39.5 35 C 41.967501 35 44 32.967501 44 30.5 L 44 9.5 C 44 7.0324991 41.967501 5 39.5 5 L 8.5 5 z M 8.5 8 L 39.5 8 C 40.346499 8 41 8.6535009 41 9.5 L 41 30.5 C 41 31.346499 40.346499 32 39.5 32 L 29.746094 32 A 1.50015 1.50015 0 0 0 29.259766 32 L 18.746094 32 A 1.50015 1.50015 0 0 0 18.259766 32 L 8.5 32 C 7.6535009 32 7 31.346499 7 30.5 L 7 9.5 C 7 8.6535009 7.6535009 8 8.5 8 z M 17.5 12 C 16.136406 12 15 13.136406 15 14.5 L 15 25.5 C 15 26.863594 16.136406 28 17.5 28 L 30.5 28 C 31.863594 28 33 26.863594 33 25.5 L 33 14.5 C 33 13.136406 31.863594 12 30.5 12 L 17.5 12 z M 18 18 L 30 18 L 30 25 L 18 25 L 18 18 z M 20 35 L 28 35 L 28 40 L 20 40 L 20 35 z'/%3E%3C/svg%3E"); +} +.icon.icon-favourite { text-indent: 0px; } /*override spectre*/ +.icon.icon-favourite::before { + content: "\02661"; /* 0x2661 = empty heart; 0x2606 = empty star */ +} +.icon.icon-favourite-active::before { + content: "\02665"; /* 0x2665 = solid heart; 0x2605 = solid star */ +} +.icon.icon-interface {text-indent: 0px;font-size: 130%;vertical-align: -30%;} /*override spectre*/ +.icon.icon-interface::before { + content: "\01F5AB"; } diff --git a/css/spectre-exp.min.css b/css/spectre-exp.min.css index 942cf59bf..d3137743a 100644 --- a/css/spectre-exp.min.css +++ b/css/spectre-exp.min.css @@ -1 +1 @@ -/*! Spectre.css Experimentals v0.5.8 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:flex;display:-ms-flexbox;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:flex;display:-ms-flexbox;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:flex;display:-ms-flexbox;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:flex;display:-ms-flexbox;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:flex;display:-ms-flexbox;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file +/*! Spectre.css Experimentals v0.5.9 | MIT License | github.com/picturepan2/spectre */.form-autocomplete{position:relative}.form-autocomplete .form-autocomplete-input{align-content:flex-start;display:-ms-flexbox;display:flex;-ms-flex-line-pack:start;-ms-flex-wrap:wrap;flex-wrap:wrap;height:auto;min-height:1.6rem;padding:.1rem}.form-autocomplete .form-autocomplete-input.is-focused{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-autocomplete .form-autocomplete-input .form-input{border-color:transparent;box-shadow:none;display:inline-block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.2rem;line-height:.8rem;margin:.1rem;width:auto}.form-autocomplete .menu{left:0;position:absolute;top:100%;width:100%}.form-autocomplete.autocomplete-oneline .form-autocomplete-input{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.form-autocomplete.autocomplete-oneline .chip{-ms-flex:1 0 auto;flex:1 0 auto}.calendar{border:.05rem solid #dadee4;border-radius:.1rem;display:block;min-width:280px}.calendar .calendar-nav{align-items:center;background:#f7f8f9;border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-align:center;font-size:.9rem;padding:.4rem}.calendar .calendar-body,.calendar .calendar-header{display:-ms-flexbox;display:flex;-ms-flex-pack:center;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:center;padding:.4rem 0}.calendar .calendar-body .calendar-date,.calendar .calendar-header .calendar-date{-ms-flex:0 0 14.28%;flex:0 0 14.28%;max-width:14.28%}.calendar .calendar-header{background:#f7f8f9;border-bottom:.05rem solid #dadee4;color:#bcc3ce;font-size:.7rem;text-align:center}.calendar .calendar-body{color:#66758c}.calendar .calendar-date{border:0;padding:.2rem}.calendar .calendar-date .date-item{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;border:.05rem solid transparent;border-radius:50%;color:#66758c;cursor:pointer;font-size:.7rem;height:1.4rem;line-height:1rem;outline:0;padding:.1rem;position:relative;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;vertical-align:middle;white-space:nowrap;width:1.4rem}.calendar .calendar-date .date-item.date-today{border-color:#e5e5f9;color:#5755d9}.calendar .calendar-date .date-item:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.calendar .calendar-date .date-item:focus,.calendar .calendar-date .date-item:hover{background:#fefeff;border-color:#e5e5f9;color:#5755d9;text-decoration:none}.calendar .calendar-date .date-item.active,.calendar .calendar-date .date-item:active{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-date .date-item.badge::after{position:absolute;right:3px;top:3px;transform:translate(50%,-50%)}.calendar .calendar-date .calendar-event.disabled,.calendar .calendar-date .calendar-event:disabled,.calendar .calendar-date .date-item.disabled,.calendar .calendar-date .date-item:disabled{cursor:default;opacity:.25;pointer-events:none}.calendar .calendar-date.next-month .calendar-event,.calendar .calendar-date.next-month .date-item,.calendar .calendar-date.prev-month .calendar-event,.calendar .calendar-date.prev-month .date-item{opacity:.25}.calendar .calendar-range{position:relative}.calendar .calendar-range::before{background:#f1f1fc;content:"";height:1.4rem;left:0;position:absolute;right:0;top:50%;transform:translateY(-50%)}.calendar .calendar-range.range-start::before{left:50%}.calendar .calendar-range.range-end::before{right:50%}.calendar .calendar-range.range-end .date-item,.calendar .calendar-range.range-start .date-item{background:#4b48d6;border-color:#3634d2;color:#fff}.calendar .calendar-range .date-item{color:#5755d9}.calendar.calendar-lg .calendar-body{padding:0}.calendar.calendar-lg .calendar-body .calendar-date{border-bottom:.05rem solid #dadee4;border-right:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;height:5.5rem;padding:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-child(7n){border-right:0}.calendar.calendar-lg .calendar-body .calendar-date:nth-last-child(-n+7){border-bottom:0}.calendar.calendar-lg .date-item{align-self:flex-end;-ms-flex-item-align:end;height:1.4rem;margin-right:.2rem;margin-top:.2rem}.calendar.calendar-lg .calendar-range::before{top:19px}.calendar.calendar-lg .calendar-range.range-start::before{left:auto;width:19px}.calendar.calendar-lg .calendar-range.range-end::before{right:19px}.calendar.calendar-lg .calendar-events{flex-grow:1;-ms-flex-positive:1;line-height:1;overflow-y:auto;padding:.2rem}.calendar.calendar-lg .calendar-event{border-radius:.1rem;display:block;font-size:.7rem;margin:.1rem auto;overflow:hidden;padding:3px 4px;text-overflow:ellipsis;white-space:nowrap}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-container .carousel-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-container .carousel-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-container .carousel-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-container .carousel-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-container .carousel-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-container .carousel-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-container .carousel-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-container .carousel-item:nth-of-type(8){animation:carousel-slidein .75s ease-in-out 1;opacity:1;z-index:100}.carousel .carousel-locator:nth-of-type(1):checked~.carousel-nav .nav-item:nth-of-type(1),.carousel .carousel-locator:nth-of-type(2):checked~.carousel-nav .nav-item:nth-of-type(2),.carousel .carousel-locator:nth-of-type(3):checked~.carousel-nav .nav-item:nth-of-type(3),.carousel .carousel-locator:nth-of-type(4):checked~.carousel-nav .nav-item:nth-of-type(4),.carousel .carousel-locator:nth-of-type(5):checked~.carousel-nav .nav-item:nth-of-type(5),.carousel .carousel-locator:nth-of-type(6):checked~.carousel-nav .nav-item:nth-of-type(6),.carousel .carousel-locator:nth-of-type(7):checked~.carousel-nav .nav-item:nth-of-type(7),.carousel .carousel-locator:nth-of-type(8):checked~.carousel-nav .nav-item:nth-of-type(8){color:#f7f8f9}.carousel{background:#f7f8f9;display:block;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%;z-index:1}.carousel .carousel-container{height:100%;left:0;position:relative}.carousel .carousel-container::before{content:"";display:block;padding-bottom:56.25%}.carousel .carousel-container .carousel-item{animation:carousel-slideout 1s ease-in-out 1;height:100%;left:0;margin:0;opacity:0;position:absolute;top:0;width:100%}.carousel .carousel-container .carousel-item:hover .item-next,.carousel .carousel-container .carousel-item:hover .item-prev{opacity:1}.carousel .carousel-container .item-next,.carousel .carousel-container .item-prev{background:rgba(247,248,249,.25);border-color:rgba(247,248,249,.5);color:#f7f8f9;opacity:0;position:absolute;top:50%;transform:translateY(-50%);transition:all .4s;z-index:100}.carousel .carousel-container .item-prev{left:1rem}.carousel .carousel-container .item-next{right:1rem}.carousel .carousel-nav{bottom:.4rem;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;left:50%;position:absolute;transform:translateX(-50%);width:10rem;z-index:100}.carousel .carousel-nav .nav-item{color:rgba(247,248,249,.5);display:block;-ms-flex:1 0 auto;flex:1 0 auto;height:1.6rem;margin:.2rem;max-width:2.5rem;position:relative}.carousel .carousel-nav .nav-item::before{background:currentColor;content:"";display:block;height:.1rem;position:absolute;top:.5rem;width:100%}@keyframes carousel-slidein{0%{transform:translateX(100%)}100%{transform:translateX(0)}}@keyframes carousel-slideout{0%{opacity:1;transform:translateX(0)}100%{opacity:1;transform:translateX(-50%)}}.comparison-slider{height:50vh;overflow:hidden;-webkit-overflow-scrolling:touch;position:relative;width:100%}.comparison-slider .comparison-after,.comparison-slider .comparison-before{height:100%;left:0;margin:0;overflow:hidden;position:absolute;top:0}.comparison-slider .comparison-after img,.comparison-slider .comparison-before img{height:100%;object-fit:cover;object-position:left center;position:absolute;width:100%}.comparison-slider .comparison-before{width:100%;z-index:1}.comparison-slider .comparison-before .comparison-label{right:.8rem}.comparison-slider .comparison-after{max-width:100%;min-width:0;z-index:2}.comparison-slider .comparison-after::before{background:0 0;content:"";cursor:default;height:100%;left:0;position:absolute;right:.8rem;top:0;z-index:1}.comparison-slider .comparison-after::after{background:currentColor;border-radius:50%;box-shadow:0 -5px,0 5px;color:#fff;content:"";height:3px;pointer-events:none;position:absolute;right:.4rem;top:50%;transform:translate(50%,-50%);width:3px}.comparison-slider .comparison-after .comparison-label{left:.8rem}.comparison-slider .comparison-resizer{animation:first-run 1.5s 1 ease-in-out;cursor:ew-resize;height:.8rem;left:0;max-width:100%;min-width:.8rem;opacity:0;outline:0;position:relative;resize:horizontal;top:50%;transform:translateY(-50%) scaleY(30);width:0}.comparison-slider .comparison-label{background:rgba(48,55,66,.5);bottom:.8rem;color:#fff;padding:.2rem .4rem;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}@keyframes first-run{0%{width:0}25%{width:2.4rem}50%{width:.8rem}75%{width:1.2rem}100%{width:0}}.filter .filter-tag#tag-0:checked~.filter-nav .chip[for=tag-0],.filter .filter-tag#tag-1:checked~.filter-nav .chip[for=tag-1],.filter .filter-tag#tag-2:checked~.filter-nav .chip[for=tag-2],.filter .filter-tag#tag-3:checked~.filter-nav .chip[for=tag-3],.filter .filter-tag#tag-4:checked~.filter-nav .chip[for=tag-4],.filter .filter-tag#tag-5:checked~.filter-nav .chip[for=tag-5],.filter .filter-tag#tag-6:checked~.filter-nav .chip[for=tag-6],.filter .filter-tag#tag-7:checked~.filter-nav .chip[for=tag-7],.filter .filter-tag#tag-8:checked~.filter-nav .chip[for=tag-8]{background:#5755d9;color:#fff}.filter .filter-tag#tag-1:checked~.filter-body .filter-item:not([data-tag~=tag-1]),.filter .filter-tag#tag-2:checked~.filter-body .filter-item:not([data-tag~=tag-2]),.filter .filter-tag#tag-3:checked~.filter-body .filter-item:not([data-tag~=tag-3]),.filter .filter-tag#tag-4:checked~.filter-body .filter-item:not([data-tag~=tag-4]),.filter .filter-tag#tag-5:checked~.filter-body .filter-item:not([data-tag~=tag-5]),.filter .filter-tag#tag-6:checked~.filter-body .filter-item:not([data-tag~=tag-6]),.filter .filter-tag#tag-7:checked~.filter-body .filter-item:not([data-tag~=tag-7]),.filter .filter-tag#tag-8:checked~.filter-body .filter-item:not([data-tag~=tag-8]){display:none}.filter .filter-nav{margin:.4rem 0}.filter .filter-body{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.meter{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f8f9;border:0;border-radius:.1rem;display:block;height:.8rem;width:100%}.meter::-webkit-meter-inner-element{display:block}.meter::-webkit-meter-bar,.meter::-webkit-meter-even-less-good-value,.meter::-webkit-meter-optimum-value,.meter::-webkit-meter-suboptimum-value{border-radius:.1rem}.meter::-webkit-meter-bar{background:#f7f8f9}.meter::-webkit-meter-optimum-value{background:#32b643}.meter::-webkit-meter-suboptimum-value{background:#ffb700}.meter::-webkit-meter-even-less-good-value{background:#e85600}.meter:-moz-meter-optimum,.meter:-moz-meter-sub-optimum,.meter:-moz-meter-sub-sub-optimum,.meter::-moz-meter-bar{border-radius:.1rem}.meter:-moz-meter-optimum::-moz-meter-bar{background:#32b643}.meter:-moz-meter-sub-optimum::-moz-meter-bar{background:#ffb700}.meter:-moz-meter-sub-sub-optimum::-moz-meter-bar{background:#e85600}.off-canvas{display:-ms-flexbox;display:flex;-ms-flex-flow:nowrap;flex-flow:nowrap;height:100%;position:relative;width:100%}.off-canvas .off-canvas-toggle{display:block;left:.4rem;position:absolute;top:.4rem;transition:none;z-index:1}.off-canvas .off-canvas-sidebar{background:#f7f8f9;bottom:0;left:0;min-width:10rem;overflow-y:auto;position:fixed;top:0;transform:translateX(-100%);transition:transform .25s;z-index:200}.off-canvas .off-canvas-content{-ms-flex:1 1 auto;flex:1 1 auto;height:100%;padding:.4rem .4rem .4rem 4rem}.off-canvas .off-canvas-overlay{background:rgba(48,55,66,.1);border-color:transparent;border-radius:0;bottom:0;display:none;height:100%;left:0;position:fixed;right:0;top:0;width:100%}.off-canvas .off-canvas-sidebar.active,.off-canvas .off-canvas-sidebar:target{transform:translateX(0)}.off-canvas .off-canvas-sidebar.active~.off-canvas-overlay,.off-canvas .off-canvas-sidebar:target~.off-canvas-overlay{display:block;z-index:100}@media (min-width:960px){.off-canvas.off-canvas-sidebar-show .off-canvas-toggle{display:none}.off-canvas.off-canvas-sidebar-show .off-canvas-sidebar{-ms-flex:0 0 auto;flex:0 0 auto;position:relative;transform:none}.off-canvas.off-canvas-sidebar-show .off-canvas-overlay{display:none!important}}.parallax{display:block;height:auto;position:relative;width:auto}.parallax .parallax-content{box-shadow:0 1rem 2.1rem rgba(48,55,66,.3);height:auto;transform:perspective(1000px);transform-style:preserve-3d;transition:all .4s ease;width:100%}.parallax .parallax-content::before{content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.parallax .parallax-front{align-items:center;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:100%;justify-content:center;left:0;position:absolute;text-align:center;text-shadow:0 0 20px rgba(48,55,66,.75);top:0;transform:translateZ(50px) scale(.95);transition:transform .4s;width:100%;z-index:1}.parallax .parallax-top-left{height:50%;left:0;outline:0;position:absolute;top:0;width:50%;z-index:100}.parallax .parallax-top-left:focus~.parallax-content,.parallax .parallax-top-left:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(-3deg)}.parallax .parallax-top-left:focus~.parallax-content::before,.parallax .parallax-top-left:hover~.parallax-content::before{background:linear-gradient(135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-left:focus~.parallax-content .parallax-front,.parallax .parallax-top-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,4.5px,50px) scale(.95)}.parallax .parallax-top-right{height:50%;outline:0;position:absolute;right:0;top:0;width:50%;z-index:100}.parallax .parallax-top-right:focus~.parallax-content,.parallax .parallax-top-right:hover~.parallax-content{transform:perspective(1000px) rotateX(3deg) rotateY(3deg)}.parallax .parallax-top-right:focus~.parallax-content::before,.parallax .parallax-top-right:hover~.parallax-content::before{background:linear-gradient(-135deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-top-right:focus~.parallax-content .parallax-front,.parallax .parallax-top-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,4.5px,50px) scale(.95)}.parallax .parallax-bottom-left{bottom:0;height:50%;left:0;outline:0;position:absolute;width:50%;z-index:100}.parallax .parallax-bottom-left:focus~.parallax-content,.parallax .parallax-bottom-left:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(-3deg)}.parallax .parallax-bottom-left:focus~.parallax-content::before,.parallax .parallax-bottom-left:hover~.parallax-content::before{background:linear-gradient(45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-left:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-left:hover~.parallax-content .parallax-front{transform:translate3d(4.5px,-4.5px,50px) scale(.95)}.parallax .parallax-bottom-right{bottom:0;height:50%;outline:0;position:absolute;right:0;width:50%;z-index:100}.parallax .parallax-bottom-right:focus~.parallax-content,.parallax .parallax-bottom-right:hover~.parallax-content{transform:perspective(1000px) rotateX(-3deg) rotateY(3deg)}.parallax .parallax-bottom-right:focus~.parallax-content::before,.parallax .parallax-bottom-right:hover~.parallax-content::before{background:linear-gradient(-45deg,rgba(255,255,255,.35) 0,transparent 50%)}.parallax .parallax-bottom-right:focus~.parallax-content .parallax-front,.parallax .parallax-bottom-right:hover~.parallax-content .parallax-front{transform:translate3d(-4.5px,-4.5px,50px) scale(.95)}.progress{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#eef0f3;border:0;border-radius:.1rem;color:#5755d9;height:.2rem;position:relative;width:100%}.progress::-webkit-progress-bar{background:0 0;border-radius:.1rem}.progress::-webkit-progress-value{background:#5755d9;border-radius:.1rem}.progress::-moz-progress-bar{background:#5755d9;border-radius:.1rem}.progress:indeterminate{animation:progress-indeterminate 1.5s linear infinite;background:#eef0f3 linear-gradient(to right,#5755d9 30%,#eef0f3 30%) top left/150% 150% no-repeat}.progress:indeterminate::-moz-progress-bar{background:0 0}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}.slider{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:0 0;display:block;height:1.2rem;width:100%}.slider:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2);outline:0}.slider.tooltip:not([data-tooltip])::after{content:attr(value)}.slider::-webkit-slider-thumb{-webkit-appearance:none;background:#5755d9;border:0;border-radius:50%;height:.6rem;margin-top:-.25rem;-webkit-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-moz-range-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-moz-transition:transform .2s;transition:transform .2s;width:.6rem}.slider::-ms-thumb{background:#5755d9;border:0;border-radius:50%;height:.6rem;-ms-transition:transform .2s;transition:transform .2s;width:.6rem}.slider:active::-webkit-slider-thumb{transform:scale(1.25)}.slider:active::-moz-range-thumb{transform:scale(1.25)}.slider:active::-ms-thumb{transform:scale(1.25)}.slider.disabled::-webkit-slider-thumb,.slider:disabled::-webkit-slider-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-moz-range-thumb,.slider:disabled::-moz-range-thumb{background:#f7f8f9;transform:scale(1)}.slider.disabled::-ms-thumb,.slider:disabled::-ms-thumb{background:#f7f8f9;transform:scale(1)}.slider::-webkit-slider-runnable-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-moz-range-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-track{background:#eef0f3;border-radius:.1rem;height:.1rem;width:100%}.slider::-ms-fill-lower{background:#5755d9}.timeline .timeline-item{display:-ms-flexbox;display:flex;margin-bottom:1.2rem;position:relative}.timeline .timeline-item::before{background:#dadee4;content:"";height:100%;left:11px;position:absolute;top:1.2rem;width:2px}.timeline .timeline-item .timeline-left{-ms-flex:0 0 auto;flex:0 0 auto}.timeline .timeline-item .timeline-content{-ms-flex:1 1 auto;flex:1 1 auto;padding:2px 0 2px .8rem}.timeline .timeline-item .timeline-icon{align-items:center;border-radius:50%;color:#fff;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;height:1.2rem;justify-content:center;text-align:center;width:1.2rem}.timeline .timeline-item .timeline-icon::before{border:.1rem solid #5755d9;border-radius:50%;content:"";display:block;height:.4rem;left:.4rem;position:absolute;top:.4rem;width:.4rem}.timeline .timeline-item .timeline-icon.icon-lg{background:#5755d9;line-height:1.2rem}.timeline .timeline-item .timeline-icon.icon-lg::before{content:none}.viewer-360{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-direction:column;flex-direction:column}.viewer-360 .viewer-slider[max="36"][value="1"]+.viewer-image{background-position-y:0}.viewer-360 .viewer-slider[max="36"][value="2"]+.viewer-image{background-position-y:2.8571428571%}.viewer-360 .viewer-slider[max="36"][value="3"]+.viewer-image{background-position-y:5.7142857143%}.viewer-360 .viewer-slider[max="36"][value="4"]+.viewer-image{background-position-y:8.5714285714%}.viewer-360 .viewer-slider[max="36"][value="5"]+.viewer-image{background-position-y:11.4285714286%}.viewer-360 .viewer-slider[max="36"][value="6"]+.viewer-image{background-position-y:14.2857142857%}.viewer-360 .viewer-slider[max="36"][value="7"]+.viewer-image{background-position-y:17.1428571429%}.viewer-360 .viewer-slider[max="36"][value="8"]+.viewer-image{background-position-y:20%}.viewer-360 .viewer-slider[max="36"][value="9"]+.viewer-image{background-position-y:22.8571428571%}.viewer-360 .viewer-slider[max="36"][value="10"]+.viewer-image{background-position-y:25.7142857143%}.viewer-360 .viewer-slider[max="36"][value="11"]+.viewer-image{background-position-y:28.5714285714%}.viewer-360 .viewer-slider[max="36"][value="12"]+.viewer-image{background-position-y:31.4285714286%}.viewer-360 .viewer-slider[max="36"][value="13"]+.viewer-image{background-position-y:34.2857142857%}.viewer-360 .viewer-slider[max="36"][value="14"]+.viewer-image{background-position-y:37.1428571429%}.viewer-360 .viewer-slider[max="36"][value="15"]+.viewer-image{background-position-y:40%}.viewer-360 .viewer-slider[max="36"][value="16"]+.viewer-image{background-position-y:42.8571428571%}.viewer-360 .viewer-slider[max="36"][value="17"]+.viewer-image{background-position-y:45.7142857143%}.viewer-360 .viewer-slider[max="36"][value="18"]+.viewer-image{background-position-y:48.5714285714%}.viewer-360 .viewer-slider[max="36"][value="19"]+.viewer-image{background-position-y:51.4285714286%}.viewer-360 .viewer-slider[max="36"][value="20"]+.viewer-image{background-position-y:54.2857142857%}.viewer-360 .viewer-slider[max="36"][value="21"]+.viewer-image{background-position-y:57.1428571429%}.viewer-360 .viewer-slider[max="36"][value="22"]+.viewer-image{background-position-y:60%}.viewer-360 .viewer-slider[max="36"][value="23"]+.viewer-image{background-position-y:62.8571428571%}.viewer-360 .viewer-slider[max="36"][value="24"]+.viewer-image{background-position-y:65.7142857143%}.viewer-360 .viewer-slider[max="36"][value="25"]+.viewer-image{background-position-y:68.5714285714%}.viewer-360 .viewer-slider[max="36"][value="26"]+.viewer-image{background-position-y:71.4285714286%}.viewer-360 .viewer-slider[max="36"][value="27"]+.viewer-image{background-position-y:74.2857142857%}.viewer-360 .viewer-slider[max="36"][value="28"]+.viewer-image{background-position-y:77.1428571429%}.viewer-360 .viewer-slider[max="36"][value="29"]+.viewer-image{background-position-y:80%}.viewer-360 .viewer-slider[max="36"][value="30"]+.viewer-image{background-position-y:82.8571428571%}.viewer-360 .viewer-slider[max="36"][value="31"]+.viewer-image{background-position-y:85.7142857143%}.viewer-360 .viewer-slider[max="36"][value="32"]+.viewer-image{background-position-y:88.5714285714%}.viewer-360 .viewer-slider[max="36"][value="33"]+.viewer-image{background-position-y:91.4285714286%}.viewer-360 .viewer-slider[max="36"][value="34"]+.viewer-image{background-position-y:94.2857142857%}.viewer-360 .viewer-slider[max="36"][value="35"]+.viewer-image{background-position-y:97.1428571429%}.viewer-360 .viewer-slider[max="36"][value="36"]+.viewer-image{background-position-y:100%}.viewer-360 .viewer-slider{cursor:ew-resize;-ms-flex-order:2;margin:1rem;order:2;width:60%}.viewer-360 .viewer-image{background-position-y:0;background-repeat:no-repeat;background-size:100%;-ms-flex-order:1;max-width:100%;order:1} \ No newline at end of file diff --git a/css/spectre-icons.min.css b/css/spectre-icons.min.css index 9b6167caa..0276f7b84 100644 --- a/css/spectre-icons.min.css +++ b/css/spectre-icons.min.css @@ -1 +1 @@ -/*! Spectre.css Icons v0.5.8 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file +/*! Spectre.css Icons v0.5.9 | MIT License | github.com/picturepan2/spectre */.icon{box-sizing:border-box;display:inline-block;font-size:inherit;font-style:normal;height:1em;position:relative;text-indent:-9999px;vertical-align:middle;width:1em}.icon::after,.icon::before{content:"";display:block;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.icon.icon-2x{font-size:1.6rem}.icon.icon-3x{font-size:2.4rem}.icon.icon-4x{font-size:3.2rem}.accordion .icon,.btn .icon,.menu .icon,.toast .icon{vertical-align:-10%}.btn-lg .icon{vertical-align:-15%}.icon-arrow-down::before,.icon-arrow-left::before,.icon-arrow-right::before,.icon-arrow-up::before,.icon-back::before,.icon-downward::before,.icon-forward::before,.icon-upward::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.65em;width:.65em}.icon-arrow-down::before{transform:translate(-50%,-75%) rotate(225deg)}.icon-arrow-left::before{transform:translate(-25%,-50%) rotate(-45deg)}.icon-arrow-right::before{transform:translate(-75%,-50%) rotate(135deg)}.icon-arrow-up::before{transform:translate(-50%,-25%) rotate(45deg)}.icon-back::after,.icon-forward::after{background:currentColor;height:.1rem;width:.8em}.icon-downward::after,.icon-upward::after{background:currentColor;height:.8em;width:.1rem}.icon-back::after{left:55%}.icon-back::before{transform:translate(-50%,-50%) rotate(-45deg)}.icon-downward::after{top:45%}.icon-downward::before{transform:translate(-50%,-50%) rotate(-135deg)}.icon-forward::after{left:45%}.icon-forward::before{transform:translate(-50%,-50%) rotate(135deg)}.icon-upward::after{top:55%}.icon-upward::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-caret::before{border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid currentColor;height:0;transform:translate(-50%,-25%);width:0}.icon-menu::before{background:currentColor;box-shadow:0 -.35em,0 .35em;height:.1rem;width:100%}.icon-apps::before{background:currentColor;box-shadow:-.35em -.35em,-.35em 0,-.35em .35em,0 -.35em,0 .35em,.35em -.35em,.35em 0,.35em .35em;height:3px;width:3px}.icon-resize-horiz::after,.icon-resize-horiz::before,.icon-resize-vert::after,.icon-resize-vert::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.45em;width:.45em}.icon-resize-horiz::before,.icon-resize-vert::before{transform:translate(-50%,-90%) rotate(45deg)}.icon-resize-horiz::after,.icon-resize-vert::after{transform:translate(-50%,-10%) rotate(225deg)}.icon-resize-horiz::before{transform:translate(-90%,-50%) rotate(-45deg)}.icon-resize-horiz::after{transform:translate(-10%,-50%) rotate(135deg)}.icon-more-horiz::before,.icon-more-vert::before{background:currentColor;border-radius:50%;box-shadow:-.4em 0,.4em 0;height:3px;width:3px}.icon-more-vert::before{box-shadow:0 -.4em,0 .4em}.icon-cross::before,.icon-minus::before,.icon-plus::before{background:currentColor;height:.1rem;width:100%}.icon-cross::after,.icon-plus::after{background:currentColor;height:100%;width:.1rem}.icon-cross::before{width:100%}.icon-cross::after{height:100%}.icon-cross::after,.icon-cross::before{transform:translate(-50%,-50%) rotate(45deg)}.icon-check::before{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-75%) rotate(-45deg);width:.9em}.icon-stop{border:.1rem solid currentColor;border-radius:50%}.icon-stop::before{background:currentColor;height:.1rem;transform:translate(-50%,-50%) rotate(45deg);width:1em}.icon-shutdown{border:.1rem solid currentColor;border-radius:50%;border-top-color:transparent}.icon-shutdown::before{background:currentColor;content:"";height:.5em;top:.1em;width:.1rem}.icon-refresh::before{border:.1rem solid currentColor;border-radius:50%;border-right-color:transparent;height:1em;width:1em}.icon-refresh::after{border:.2em solid currentColor;border-left-color:transparent;border-top-color:transparent;height:0;left:80%;top:20%;width:0}.icon-search::before{border:.1rem solid currentColor;border-radius:50%;height:.75em;left:5%;top:5%;transform:translate(0,0) rotate(45deg);width:.75em}.icon-search::after{background:currentColor;height:.1rem;left:80%;top:80%;transform:translate(-50%,-50%) rotate(45deg);width:.4em}.icon-edit::before{border:.1rem solid currentColor;height:.4em;transform:translate(-40%,-60%) rotate(-45deg);width:.85em}.icon-edit::after{border:.15em solid currentColor;border-right-color:transparent;border-top-color:transparent;height:0;left:5%;top:95%;transform:translate(0,-100%);width:0}.icon-delete::before{border:.1rem solid currentColor;border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top:0;height:.75em;top:60%;width:.75em}.icon-delete::after{background:currentColor;box-shadow:-.25em .2em,.25em .2em;height:.1rem;top:.05rem;width:.5em}.icon-share{border:.1rem solid currentColor;border-radius:.1rem;border-right:0;border-top:0}.icon-share::before{border:.1rem solid currentColor;border-left:0;border-top:0;height:.4em;left:100%;top:.25em;transform:translate(-125%,-50%) rotate(-45deg);width:.4em}.icon-share::after{border:.1rem solid currentColor;border-bottom:0;border-radius:75% 0;border-right:0;height:.5em;width:.6em}.icon-flag::before{background:currentColor;height:1em;left:15%;width:.1rem}.icon-flag::after{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top-right-radius:.1rem;height:.65em;left:60%;top:35%;width:.8em}.icon-bookmark::before{border:.1rem solid currentColor;border-bottom:0;border-top-left-radius:.1rem;border-top-right-radius:.1rem;height:.9em;width:.8em}.icon-bookmark::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;border-radius:.1rem;height:.5em;transform:translate(-50%,35%) rotate(-45deg) skew(15deg,15deg);width:.5em}.icon-download,.icon-upload{border-bottom:.1rem solid currentColor}.icon-download::before,.icon-upload::before{border:.1rem solid currentColor;border-bottom:0;border-right:0;height:.5em;transform:translate(-50%,-60%) rotate(-135deg);width:.5em}.icon-download::after,.icon-upload::after{background:currentColor;height:.6em;top:40%;width:.1rem}.icon-upload::before{transform:translate(-50%,-60%) rotate(45deg)}.icon-upload::after{top:50%}.icon-copy::before{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0;height:.8em;left:40%;top:35%;width:.8em}.icon-copy::after{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;left:60%;top:60%;width:.8em}.icon-time{border:.1rem solid currentColor;border-radius:50%}.icon-time::before{background:currentColor;height:.4em;transform:translate(-50%,-75%);width:.1rem}.icon-time::after{background:currentColor;height:.3em;transform:translate(-50%,-75%) rotate(90deg);transform-origin:50% 90%;width:.1rem}.icon-mail::before{border:.1rem solid currentColor;border-radius:.1rem;height:.8em;width:1em}.icon-mail::after{border:.1rem solid currentColor;border-right:0;border-top:0;height:.5em;transform:translate(-50%,-90%) rotate(-45deg) skew(10deg,10deg);width:.5em}.icon-people::before{border:.1rem solid currentColor;border-radius:50%;height:.45em;top:25%;width:.45em}.icon-people::after{border:.1rem solid currentColor;border-radius:50% 50% 0 0;height:.4em;top:75%;width:.9em}.icon-message{border:.1rem solid currentColor;border-bottom:0;border-radius:.1rem;border-right:0}.icon-message::before{border:.1rem solid currentColor;border-bottom-right-radius:.1rem;border-left:0;border-top:0;height:.8em;left:65%;top:40%;width:.7em}.icon-message::after{background:currentColor;border-radius:.1rem;height:.3em;left:10%;top:100%;transform:translate(0,-90%) rotate(45deg);width:.1rem}.icon-photo{border:.1rem solid currentColor;border-radius:.1rem}.icon-photo::before{border:.1rem solid currentColor;border-radius:50%;height:.25em;left:35%;top:35%;width:.25em}.icon-photo::after{border:.1rem solid currentColor;border-bottom:0;border-left:0;height:.5em;left:60%;transform:translate(-50%,25%) rotate(-45deg);width:.5em}.icon-link::after,.icon-link::before{border:.1rem solid currentColor;border-radius:5em 0 0 5em;border-right:0;height:.5em;width:.75em}.icon-link::before{transform:translate(-70%,-45%) rotate(-45deg)}.icon-link::after{transform:translate(-30%,-55%) rotate(135deg)}.icon-location::before{border:.1rem solid currentColor;border-radius:50% 50% 50% 0;height:.8em;transform:translate(-50%,-60%) rotate(-45deg);width:.8em}.icon-location::after{border:.1rem solid currentColor;border-radius:50%;height:.2em;transform:translate(-50%,-80%);width:.2em}.icon-emoji{border:.1rem solid currentColor;border-radius:50%}.icon-emoji::before{border-radius:50%;box-shadow:-.17em -.1em,.17em -.1em;height:.15em;width:.15em}.icon-emoji::after{border:.1rem solid currentColor;border-bottom-color:transparent;border-radius:50%;border-right-color:transparent;height:.5em;transform:translate(-50%,-40%) rotate(-135deg);width:.5em} \ No newline at end of file diff --git a/css/spectre.min.css b/css/spectre.min.css index 8df0bf64f..0fe23d9c0 100644 --- a/css/spectre.min.css +++ b/css/spectre.min.css @@ -1 +1 @@ -/*! Spectre.css v0.5.8 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:inline-flex;display:-ms-inline-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:flex;display:-ms-flexbox}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:flex;display:-ms-flexbox}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:inline-flex;display:-ms-inline-flexbox}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.columns{display:flex;display:-ms-flexbox;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.columns.col-gapless{margin-left:0;margin-right:0}.columns.col-gapless>.column{padding-left:0;padding-right:0}.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:flex;display:-ms-flexbox;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:flex;display:-ms-flexbox;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:flex;display:-ms-flexbox;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header .icon,.accordion[open] .accordion-header .icon{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:inline-flex;display:-ms-inline-flexbox;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:flex;display:-ms-flexbox;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:flex;display:-ms-flexbox;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:flex;display:-ms-flexbox;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:flex;display:-ms-flexbox;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:flex;display:-ms-flexbox;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:flex;display:-ms-flexbox}.d-inline-flex{display:inline-flex;display:-ms-inline-flexbox}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:sticky!important;position:-webkit-sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:flex;display:-ms-flexbox;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file +/*! Spectre.css v0.5.9 | MIT License | github.com/picturepan2/spectre */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}hr{box-sizing:content-box;height:0;overflow:visible}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}address{font-style:normal}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:"SF Mono","Segoe UI Mono","Roboto Mono",Menlo,Courier,monospace;font-size:1em}dfn{font-style:italic}small{font-size:80%;font-weight:400}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}fieldset{border:0;margin:0;padding:0}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item;outline:0}canvas{display:inline-block}template{display:none}[hidden]{display:none}*,::after,::before{box-sizing:inherit}html{box-sizing:border-box;font-size:20px;line-height:1.5;-webkit-tap-highlight-color:transparent}body{background:#fff;color:#3b4351;font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;font-size:.8rem;overflow-x:hidden;text-rendering:optimizeLegibility}a{color:#5755d9;outline:0;text-decoration:none}a:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}a.active,a:active,a:focus,a:hover{color:#302ecd;text-decoration:underline}a:visited{color:#807fe2}h1,h2,h3,h4,h5,h6{color:inherit;font-weight:500;line-height:1.2;margin-bottom:.5em;margin-top:0}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:500}.h1,h1{font-size:2rem}.h2,h2{font-size:1.6rem}.h3,h3{font-size:1.4rem}.h4,h4{font-size:1.2rem}.h5,h5{font-size:1rem}.h6,h6{font-size:.8rem}p{margin:0 0 1.2rem}a,ins,u{-webkit-text-decoration-skip:ink edges;text-decoration-skip:ink edges}abbr[title]{border-bottom:.05rem dotted;cursor:help;text-decoration:none}kbd{background:#303742;border-radius:.1rem;color:#fff;font-size:.7rem;line-height:1.25;padding:.1rem .2rem}mark{background:#ffe9b3;border-bottom:.05rem solid #ffd367;border-radius:.1rem;color:#3b4351;padding:.05rem .1rem 0}blockquote{border-left:.1rem solid #dadee4;margin-left:0;padding:.4rem .8rem}blockquote p:last-child{margin-bottom:0}ol,ul{margin:.8rem 0 .8rem .8rem;padding:0}ol ol,ol ul,ul ol,ul ul{margin:.8rem 0 .8rem .8rem}ol li,ul li{margin-top:.4rem}ul{list-style:disc inside}ul ul{list-style-type:circle}ol{list-style:decimal inside}ol ol{list-style-type:lower-alpha}dl dt{font-weight:700}dl dd{margin:.4rem 0 .8rem 0}.lang-zh,.lang-zh-hans,html:lang(zh),html:lang(zh-Hans){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Helvetica Neue",sans-serif}.lang-zh-hant,html:lang(zh-Hant){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang TC","Hiragino Sans CNS","Microsoft JhengHei","Helvetica Neue",sans-serif}.lang-ja,html:lang(ja){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Hiragino Sans","Hiragino Kaku Gothic Pro","Yu Gothic",YuGothic,Meiryo,"Helvetica Neue",sans-serif}.lang-ko,html:lang(ko){font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Malgun Gothic","Helvetica Neue",sans-serif}.lang-cjk ins,.lang-cjk u,:lang(ja) ins,:lang(ja) u,:lang(zh) ins,:lang(zh) u{border-bottom:.05rem solid;text-decoration:none}.lang-cjk del+del,.lang-cjk del+s,.lang-cjk ins+ins,.lang-cjk ins+u,.lang-cjk s+del,.lang-cjk s+s,.lang-cjk u+ins,.lang-cjk u+u,:lang(ja) del+del,:lang(ja) del+s,:lang(ja) ins+ins,:lang(ja) ins+u,:lang(ja) s+del,:lang(ja) s+s,:lang(ja) u+ins,:lang(ja) u+u,:lang(zh) del+del,:lang(zh) del+s,:lang(zh) ins+ins,:lang(zh) ins+u,:lang(zh) s+del,:lang(zh) s+s,:lang(zh) u+ins,:lang(zh) u+u{margin-left:.125em}.table{border-collapse:collapse;border-spacing:0;text-align:left;width:100%}.table.table-striped tbody tr:nth-of-type(odd){background:#f7f8f9}.table tbody tr.active,.table.table-striped tbody tr.active{background:#eef0f3}.table.table-hover tbody tr:hover{background:#eef0f3}.table.table-scroll{display:block;overflow-x:auto;padding-bottom:.75rem;white-space:nowrap}.table td,.table th{border-bottom:.05rem solid #dadee4;padding:.6rem .4rem}.table th{border-bottom-width:.1rem}.btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #5755d9;border-radius:.1rem;color:#5755d9;cursor:pointer;display:inline-block;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;text-align:center;text-decoration:none;transition:background .2s,border .2s,box-shadow .2s,color .2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.btn:focus{box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.btn:focus,.btn:hover{background:#f1f1fc;border-color:#4b48d6;text-decoration:none}.btn.active,.btn:active{background:#4b48d6;border-color:#3634d2;color:#fff;text-decoration:none}.btn.active.loading::after,.btn:active.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.disabled,.btn:disabled,.btn[disabled]{cursor:default;opacity:.5;pointer-events:none}.btn.btn-primary{background:#5755d9;border-color:#4b48d6;color:#fff}.btn.btn-primary:focus,.btn.btn-primary:hover{background:#4240d4;border-color:#3634d2;color:#fff}.btn.btn-primary.active,.btn.btn-primary:active{background:#3a38d2;border-color:#302ecd;color:#fff}.btn.btn-primary.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-success{background:#32b643;border-color:#2faa3f;color:#fff}.btn.btn-success:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.btn.btn-success:focus,.btn.btn-success:hover{background:#30ae40;border-color:#2da23c;color:#fff}.btn.btn-success.active,.btn.btn-success:active{background:#2a9a39;border-color:#278e34;color:#fff}.btn.btn-success.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-error{background:#e85600;border-color:#d95000;color:#fff}.btn.btn-error:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.btn.btn-error:focus,.btn.btn-error:hover{background:#de5200;border-color:#cf4d00;color:#fff}.btn.btn-error.active,.btn.btn-error:active{background:#c44900;border-color:#b54300;color:#fff}.btn.btn-error.loading::after{border-bottom-color:#fff;border-left-color:#fff}.btn.btn-link{background:0 0;border-color:transparent;color:#5755d9}.btn.btn-link.active,.btn.btn-link:active,.btn.btn-link:focus,.btn.btn-link:hover{color:#302ecd}.btn.btn-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.btn.btn-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.btn.btn-block{display:block;width:100%}.btn.btn-action{padding-left:0;padding-right:0;width:1.8rem}.btn.btn-action.btn-sm{width:1.4rem}.btn.btn-action.btn-lg{width:2rem}.btn.btn-clear{background:0 0;border:0;color:currentColor;height:1rem;line-height:.8rem;margin-left:.2rem;margin-right:-2px;opacity:1;padding:.1rem;text-decoration:none;width:1rem}.btn.btn-clear:focus,.btn.btn-clear:hover{background:rgba(247,248,249,.5);opacity:.95}.btn.btn-clear::before{content:"\2715"}.btn-group{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.btn-group .btn{-ms-flex:1 0 auto;flex:1 0 auto}.btn-group .btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.btn-group .btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.btn-group .btn.active,.btn-group .btn:active,.btn-group .btn:focus,.btn-group .btn:hover{z-index:1}.btn-group.btn-group-block{display:-ms-flexbox;display:flex}.btn-group.btn-group-block .btn{-ms-flex:1 0 0;flex:1 0 0}.form-group:not(:last-child){margin-bottom:.4rem}fieldset{margin-bottom:.8rem}legend{font-size:.9rem;font-weight:500;margin-bottom:.8rem}.form-label{display:block;line-height:1.2rem;padding:.3rem 0}.form-label.label-sm{font-size:.7rem;padding:.1rem 0}.form-label.label-lg{font-size:.9rem;padding:.4rem 0}.form-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;background-image:none;border:.05rem solid #bcc3ce;border-radius:.1rem;color:#3b4351;display:block;font-size:.8rem;height:1.8rem;line-height:1.2rem;max-width:100%;outline:0;padding:.25rem .4rem;position:relative;transition:background .2s,border .2s,box-shadow .2s,color .2s;width:100%}.form-input:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-input:-ms-input-placeholder{color:#bcc3ce}.form-input::-ms-input-placeholder{color:#bcc3ce}.form-input::placeholder{color:#bcc3ce}.form-input.input-sm{font-size:.7rem;height:1.4rem;padding:.05rem .3rem}.form-input.input-lg{font-size:.9rem;height:2rem;padding:.35rem .6rem}.form-input.input-inline{display:inline-block;vertical-align:middle;width:auto}.form-input[type=file]{height:auto}textarea.form-input,textarea.form-input.input-lg,textarea.form-input.input-sm{height:auto}.form-input-hint{color:#bcc3ce;font-size:.7rem;margin-top:.2rem}.has-success .form-input-hint,.is-success+.form-input-hint{color:#32b643}.has-error .form-input-hint,.is-error+.form-input-hint{color:#e85600}.form-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff;border:.05rem solid #bcc3ce;border-radius:.1rem;color:inherit;font-size:.8rem;height:1.8rem;line-height:1.2rem;outline:0;padding:.25rem .4rem;vertical-align:middle;width:100%}.form-select:focus{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-select::-ms-expand{display:none}.form-select.select-sm{font-size:.7rem;height:1.4rem;padding:.05rem 1.1rem .05rem .3rem}.form-select.select-lg{font-size:.9rem;height:2rem;padding:.35rem 1.4rem .35rem .6rem}.form-select[multiple],.form-select[size]{height:auto;padding:.25rem .4rem}.form-select[multiple] option,.form-select[size] option{padding:.1rem .2rem}.form-select:not([multiple]):not([size]){background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%204%205'%3E%3Cpath%20fill='%23667189'%20d='M2%200L0%202h4zm0%205L0%203h4z'/%3E%3C/svg%3E") no-repeat right .35rem center/.4rem .5rem;padding-right:1.2rem}.has-icon-left,.has-icon-right{position:relative}.has-icon-left .form-icon,.has-icon-right .form-icon{height:.8rem;margin:0 .25rem;position:absolute;top:50%;transform:translateY(-50%);width:.8rem;z-index:2}.has-icon-left .form-icon{left:.05rem}.has-icon-left .form-input{padding-left:1.3rem}.has-icon-right .form-icon{right:.05rem}.has-icon-right .form-input{padding-right:1.3rem}.form-checkbox,.form-radio,.form-switch{display:block;line-height:1.2rem;margin:.2rem 0;min-height:1.4rem;padding:.1rem .4rem .1rem 1.2rem;position:relative}.form-checkbox input,.form-radio input,.form-switch input{clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;position:absolute;width:1px}.form-checkbox input:focus+.form-icon,.form-radio input:focus+.form-icon,.form-switch input:focus+.form-icon{border-color:#5755d9;box-shadow:0 0 0 .1rem rgba(87,85,217,.2)}.form-checkbox input:checked+.form-icon,.form-radio input:checked+.form-icon,.form-switch input:checked+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox .form-icon,.form-radio .form-icon,.form-switch .form-icon{border:.05rem solid #bcc3ce;cursor:pointer;display:inline-block;position:absolute;transition:background .2s,border .2s,box-shadow .2s,color .2s}.form-checkbox.input-sm,.form-radio.input-sm,.form-switch.input-sm{font-size:.7rem;margin:0}.form-checkbox.input-lg,.form-radio.input-lg,.form-switch.input-lg{font-size:.9rem;margin:.3rem 0}.form-checkbox .form-icon,.form-radio .form-icon{background:#fff;height:.8rem;left:0;top:.3rem;width:.8rem}.form-checkbox input:active+.form-icon,.form-radio input:active+.form-icon{background:#eef0f3}.form-checkbox .form-icon{border-radius:.1rem}.form-checkbox input:checked+.form-icon::before{background-clip:padding-box;border:.1rem solid #fff;border-left-width:0;border-top-width:0;content:"";height:9px;left:50%;margin-left:-3px;margin-top:-6px;position:absolute;top:50%;transform:rotate(45deg);width:6px}.form-checkbox input:indeterminate+.form-icon{background:#5755d9;border-color:#5755d9}.form-checkbox input:indeterminate+.form-icon::before{background:#fff;content:"";height:2px;left:50%;margin-left:-5px;margin-top:-1px;position:absolute;top:50%;width:10px}.form-radio .form-icon{border-radius:50%}.form-radio input:checked+.form-icon::before{background:#fff;border-radius:50%;content:"";height:6px;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:6px}.form-switch{padding-left:2rem}.form-switch .form-icon{background:#bcc3ce;background-clip:padding-box;border-radius:.45rem;height:.9rem;left:0;top:.25rem;width:1.6rem}.form-switch .form-icon::before{background:#fff;border-radius:50%;content:"";display:block;height:.8rem;left:0;position:absolute;top:0;transition:background .2s,border .2s,box-shadow .2s,color .2s,left .2s;width:.8rem}.form-switch input:checked+.form-icon::before{left:14px}.form-switch input:active+.form-icon::before{background:#f7f8f9}.input-group{display:-ms-flexbox;display:flex}.input-group .input-group-addon{background:#f7f8f9;border:.05rem solid #bcc3ce;border-radius:.1rem;line-height:1.2rem;padding:.25rem .4rem;white-space:nowrap}.input-group .input-group-addon.addon-sm{font-size:.7rem;padding:.05rem .3rem}.input-group .input-group-addon.addon-lg{font-size:.9rem;padding:.35rem .6rem}.input-group .form-input,.input-group .form-select{-ms-flex:1 1 auto;flex:1 1 auto;width:1%}.input-group .input-group-btn{z-index:1}.input-group .form-input:first-child:not(:last-child),.input-group .form-select:first-child:not(:last-child),.input-group .input-group-addon:first-child:not(:last-child),.input-group .input-group-btn:first-child:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group .form-input:not(:first-child):not(:last-child),.input-group .form-select:not(:first-child):not(:last-child),.input-group .input-group-addon:not(:first-child):not(:last-child),.input-group .input-group-btn:not(:first-child):not(:last-child){border-radius:0;margin-left:-.05rem}.input-group .form-input:last-child:not(:first-child),.input-group .form-select:last-child:not(:first-child),.input-group .input-group-addon:last-child:not(:first-child),.input-group .input-group-btn:last-child:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0;margin-left:-.05rem}.input-group .form-input:focus,.input-group .form-select:focus,.input-group .input-group-addon:focus,.input-group .input-group-btn:focus{z-index:2}.input-group .form-select{width:auto}.input-group.input-inline{display:-ms-inline-flexbox;display:inline-flex}.form-input.is-success,.form-select.is-success,.has-success .form-input,.has-success .form-select{background:#f9fdfa;border-color:#32b643}.form-input.is-success:focus,.form-select.is-success:focus,.has-success .form-input:focus,.has-success .form-select:focus{box-shadow:0 0 0 .1rem rgba(50,182,67,.2)}.form-input.is-error,.form-select.is-error,.has-error .form-input,.has-error .form-select{background:#fffaf7;border-color:#e85600}.form-input.is-error:focus,.form-select.is-error:focus,.has-error .form-input:focus,.has-error .form-select:focus{box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error .form-icon,.form-radio.is-error .form-icon,.form-switch.is-error .form-icon,.has-error .form-checkbox .form-icon,.has-error .form-radio .form-icon,.has-error .form-switch .form-icon{border-color:#e85600}.form-checkbox.is-error input:checked+.form-icon,.form-radio.is-error input:checked+.form-icon,.form-switch.is-error input:checked+.form-icon,.has-error .form-checkbox input:checked+.form-icon,.has-error .form-radio input:checked+.form-icon,.has-error .form-switch input:checked+.form-icon{background:#e85600;border-color:#e85600}.form-checkbox.is-error input:focus+.form-icon,.form-radio.is-error input:focus+.form-icon,.form-switch.is-error input:focus+.form-icon,.has-error .form-checkbox input:focus+.form-icon,.has-error .form-radio input:focus+.form-icon,.has-error .form-switch input:focus+.form-icon{border-color:#e85600;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-checkbox.is-error input:indeterminate+.form-icon,.has-error .form-checkbox input:indeterminate+.form-icon{background:#e85600;border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid{border-color:#e85600}.form-input:not(:placeholder-shown):invalid{border-color:#e85600}.form-input:not(:-ms-input-placeholder):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:placeholder-shown):invalid:focus{background:#fffaf7;box-shadow:0 0 0 .1rem rgba(232,86,0,.2)}.form-input:not(:-ms-input-placeholder):invalid+.form-input-hint{color:#e85600}.form-input:not(:placeholder-shown):invalid+.form-input-hint{color:#e85600}.form-input.disabled,.form-input:disabled,.form-select.disabled,.form-select:disabled{background-color:#eef0f3;cursor:not-allowed;opacity:.5}.form-input[readonly]{background-color:#f7f8f9}input.disabled+.form-icon,input:disabled+.form-icon{background:#eef0f3;cursor:not-allowed;opacity:.5}.form-switch input.disabled+.form-icon::before,.form-switch input:disabled+.form-icon::before{background:#fff}.form-horizontal{padding:.4rem 0}.form-horizontal .form-group{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.form-inline{display:inline-block}.label{background:#eef0f3;border-radius:.1rem;color:#455060;display:inline-block;line-height:1.25;padding:.1rem .2rem}.label.label-rounded{border-radius:5rem;padding-left:.4rem;padding-right:.4rem}.label.label-primary{background:#5755d9;color:#fff}.label.label-secondary{background:#f1f1fc;color:#5755d9}.label.label-success{background:#32b643;color:#fff}.label.label-warning{background:#ffb700;color:#fff}.label.label-error{background:#e85600;color:#fff}code{background:#fcf2f2;border-radius:.1rem;color:#d73e48;font-size:85%;line-height:1.25;padding:.1rem .2rem}.code{border-radius:.1rem;color:#3b4351;position:relative}.code::before{color:#bcc3ce;content:attr(data-lang);font-size:.7rem;position:absolute;right:.4rem;top:.1rem}.code code{background:#f7f8f9;color:inherit;display:block;line-height:1.5;overflow-x:auto;padding:1rem;width:100%}.img-responsive{display:block;height:auto;max-width:100%}.img-fit-cover{object-fit:cover}.img-fit-contain{object-fit:contain}.video-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.video-responsive::before{content:"";display:block;padding-bottom:56.25%}.video-responsive embed,.video-responsive iframe,.video-responsive object{border:0;bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%}video.video-responsive{height:auto;max-width:100%}video.video-responsive::before{content:none}.video-responsive-4-3::before{padding-bottom:75%}.video-responsive-1-1::before{padding-bottom:100%}.figure{margin:0 0 .4rem 0}.figure .figure-caption{color:#66758c;margin-top:.4rem}.container{margin-left:auto;margin-right:auto;padding-left:.4rem;padding-right:.4rem;width:100%}.container.grid-xl{max-width:1296px}.container.grid-lg{max-width:976px}.container.grid-md{max-width:856px}.container.grid-sm{max-width:616px}.container.grid-xs{max-width:496px}.show-lg,.show-md,.show-sm,.show-xl,.show-xs{display:none!important}.cols,.columns{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-left:-.4rem;margin-right:-.4rem}.cols.col-gapless,.columns.col-gapless{margin-left:0;margin-right:0}.cols.col-gapless>.column,.columns.col-gapless>.column{padding-left:0;padding-right:0}.cols.col-oneline,.columns.col-oneline{-ms-flex-wrap:nowrap;flex-wrap:nowrap;overflow-x:auto}.column,[class~=col-]{-ms-flex:1;flex:1;max-width:100%;padding-left:.4rem;padding-right:.4rem}.column.col-1,.column.col-10,.column.col-11,.column.col-12,.column.col-2,.column.col-3,.column.col-4,.column.col-5,.column.col-6,.column.col-7,.column.col-8,.column.col-9,.column.col-auto,[class~=col-].col-1,[class~=col-].col-10,[class~=col-].col-11,[class~=col-].col-12,[class~=col-].col-2,[class~=col-].col-3,[class~=col-].col-4,[class~=col-].col-5,[class~=col-].col-6,[class~=col-].col-7,[class~=col-].col-8,[class~=col-].col-9,[class~=col-].col-auto{-ms-flex:none;flex:none}.col-12{width:100%}.col-11{width:91.66666667%}.col-10{width:83.33333333%}.col-9{width:75%}.col-8{width:66.66666667%}.col-7{width:58.33333333%}.col-6{width:50%}.col-5{width:41.66666667%}.col-4{width:33.33333333%}.col-3{width:25%}.col-2{width:16.66666667%}.col-1{width:8.33333333%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;max-width:none;width:auto}.col-mx-auto{margin-left:auto;margin-right:auto}.col-ml-auto{margin-left:auto}.col-mr-auto{margin-right:auto}@media (max-width:1280px){.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{-ms-flex:none;flex:none}.col-xl-12{width:100%}.col-xl-11{width:91.66666667%}.col-xl-10{width:83.33333333%}.col-xl-9{width:75%}.col-xl-8{width:66.66666667%}.col-xl-7{width:58.33333333%}.col-xl-6{width:50%}.col-xl-5{width:41.66666667%}.col-xl-4{width:33.33333333%}.col-xl-3{width:25%}.col-xl-2{width:16.66666667%}.col-xl-1{width:8.33333333%}.col-xl-auto{width:auto}.hide-xl{display:none!important}.show-xl{display:block!important}}@media (max-width:960px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto{-ms-flex:none;flex:none}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-auto{width:auto}.hide-lg{display:none!important}.show-lg{display:block!important}}@media (max-width:840px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto{-ms-flex:none;flex:none}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-auto{width:auto}.hide-md{display:none!important}.show-md{display:block!important}}@media (max-width:600px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto{-ms-flex:none;flex:none}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-auto{width:auto}.hide-sm{display:none!important}.show-sm{display:block!important}}@media (max-width:480px){.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-auto{-ms-flex:none;flex:none}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-auto{width:auto}.hide-xs{display:none!important}.show-xs{display:block!important}}.hero{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:justify;justify-content:space-between;padding-bottom:4rem;padding-top:4rem}.hero.hero-sm{padding-bottom:2rem;padding-top:2rem}.hero.hero-lg{padding-bottom:8rem;padding-top:8rem}.hero .hero-body{padding:.4rem}.navbar{align-items:stretch;display:-ms-flexbox;display:flex;-ms-flex-align:stretch;-ms-flex-pack:justify;-ms-flex-wrap:wrap;flex-wrap:wrap;justify-content:space-between}.navbar .navbar-section{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:1 0 0;flex:1 0 0;-ms-flex-align:center}.navbar .navbar-section:not(:first-child):last-child{-ms-flex-pack:end;justify-content:flex-end}.navbar .navbar-center{align-items:center;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-align:center}.navbar .navbar-brand{font-size:.9rem;text-decoration:none}.accordion input:checked~.accordion-header>.icon:first-child,.accordion[open] .accordion-header>.icon:first-child{transform:rotate(90deg)}.accordion input:checked~.accordion-body,.accordion[open] .accordion-body{max-height:50rem}.accordion .accordion-header{display:block;padding:.2rem .4rem}.accordion .accordion-header .icon{transition:transform .25s}.accordion .accordion-body{margin-bottom:.4rem;max-height:0;overflow:hidden;transition:max-height .25s}summary.accordion-header::-webkit-details-marker{display:none}.avatar{background:#5755d9;border-radius:50%;color:rgba(255,255,255,.85);display:inline-block;font-size:.8rem;font-weight:300;height:1.6rem;line-height:1.25;margin:0;position:relative;vertical-align:middle;width:1.6rem}.avatar.avatar-xs{font-size:.4rem;height:.8rem;width:.8rem}.avatar.avatar-sm{font-size:.6rem;height:1.2rem;width:1.2rem}.avatar.avatar-lg{font-size:1.2rem;height:2.4rem;width:2.4rem}.avatar.avatar-xl{font-size:1.6rem;height:3.2rem;width:3.2rem}.avatar img{border-radius:50%;height:100%;position:relative;width:100%;z-index:1}.avatar .avatar-icon,.avatar .avatar-presence{background:#fff;bottom:14.64%;height:50%;padding:.1rem;position:absolute;right:14.64%;transform:translate(50%,50%);width:50%;z-index:2}.avatar .avatar-presence{background:#bcc3ce;border-radius:50%;box-shadow:0 0 0 .1rem #fff;height:.5em;width:.5em}.avatar .avatar-presence.online{background:#32b643}.avatar .avatar-presence.busy{background:#e85600}.avatar .avatar-presence.away{background:#ffb700}.avatar[data-initial]::before{color:currentColor;content:attr(data-initial);left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);z-index:1}.badge{position:relative;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge]::after{background:#5755d9;background-clip:padding-box;border-radius:.5rem;box-shadow:0 0 0 .1rem #fff;color:#fff;content:attr(data-badge);display:inline-block;transform:translate(-.05rem,-.5rem)}.badge[data-badge]::after{font-size:.7rem;height:.9rem;line-height:1;min-width:.9rem;padding:.1rem .2rem;text-align:center;white-space:nowrap}.badge:not([data-badge])::after,.badge[data-badge=""]::after{height:6px;min-width:6px;padding:0;width:6px}.badge.btn::after{position:absolute;right:0;top:0;transform:translate(50%,-50%)}.badge.avatar::after{position:absolute;right:14.64%;top:14.64%;transform:translate(50%,-50%);z-index:100}.breadcrumb{list-style:none;margin:.2rem 0;padding:.2rem 0}.breadcrumb .breadcrumb-item{color:#66758c;display:inline-block;margin:0;padding:.2rem 0}.breadcrumb .breadcrumb-item:not(:last-child){margin-right:.2rem}.breadcrumb .breadcrumb-item:not(:last-child) a{color:#66758c}.breadcrumb .breadcrumb-item:not(:first-child)::before{color:#66758c;content:"/";padding-right:.4rem}.bar{background:#eef0f3;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;height:.8rem;width:100%}.bar.bar-sm{height:.2rem}.bar .bar-item{background:#5755d9;color:#fff;display:block;-ms-flex-negative:0;flex-shrink:0;font-size:.7rem;height:100%;line-height:.8rem;position:relative;text-align:center;width:0}.bar .bar-item:first-child{border-bottom-left-radius:.1rem;border-top-left-radius:.1rem}.bar .bar-item:last-child{border-bottom-right-radius:.1rem;border-top-right-radius:.1rem;-ms-flex-negative:1;flex-shrink:1}.bar-slider{height:.1rem;margin:.4rem 0;position:relative}.bar-slider .bar-item{left:0;padding:0;position:absolute}.bar-slider .bar-item:not(:last-child):first-child{background:#eef0f3;z-index:1}.bar-slider .bar-slider-btn{background:#5755d9;border:0;border-radius:50%;height:.6rem;padding:0;position:absolute;right:0;top:50%;transform:translate(50%,-50%);width:.6rem}.bar-slider .bar-slider-btn:active{box-shadow:0 0 0 .1rem #5755d9}.card{background:#fff;border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card .card-body,.card .card-footer,.card .card-header{padding:.8rem;padding-bottom:0}.card .card-body:last-child,.card .card-footer:last-child,.card .card-header:last-child{padding-bottom:.8rem}.card .card-body{-ms-flex:1 1 auto;flex:1 1 auto}.card .card-image{padding-top:.8rem}.card .card-image:first-child{padding-top:0}.card .card-image:first-child img{border-top-left-radius:.1rem;border-top-right-radius:.1rem}.card .card-image:last-child img{border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem}.chip{align-items:center;background:#eef0f3;border-radius:5rem;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;font-size:90%;height:1.2rem;line-height:.8rem;margin:.1rem;max-width:320px;overflow:hidden;padding:.2rem .4rem;text-decoration:none;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.chip.active{background:#5755d9;color:#fff}.chip .avatar{margin-left:-.4rem;margin-right:.2rem}.chip .btn-clear{border-radius:50%;transform:scale(.75)}.dropdown{display:inline-block;position:relative}.dropdown .menu{animation:slide-down .15s ease 1;display:none;left:0;max-height:50vh;overflow-y:auto;position:absolute;top:100%}.dropdown.dropdown-right .menu{left:auto;right:0}.dropdown .dropdown-toggle:focus+.menu,.dropdown .menu:hover,.dropdown.active .menu{display:block}.dropdown .btn-group .dropdown-toggle:nth-last-child(2){border-bottom-right-radius:.1rem;border-top-right-radius:.1rem}.empty{background:#f7f8f9;border-radius:.1rem;color:#66758c;padding:3.2rem 1.6rem;text-align:center}.empty .empty-icon{margin-bottom:.8rem}.empty .empty-subtitle,.empty .empty-title{margin:.4rem auto}.empty .empty-action{margin-top:.8rem}.menu{background:#fff;border-radius:.1rem;box-shadow:0 .05rem .2rem rgba(48,55,66,.3);list-style:none;margin:0;min-width:180px;padding:.4rem;transform:translateY(.2rem);z-index:300}.menu.menu-nav{background:0 0;box-shadow:none}.menu .menu-item{margin-top:0;padding:0 .4rem;position:relative;text-decoration:none}.menu .menu-item>a{border-radius:.1rem;color:inherit;display:block;margin:0 -.4rem;padding:.2rem .4rem;text-decoration:none}.menu .menu-item>a:focus,.menu .menu-item>a:hover{background:#f1f1fc;color:#5755d9}.menu .menu-item>a.active,.menu .menu-item>a:active{background:#f1f1fc;color:#5755d9}.menu .menu-item .form-checkbox,.menu .menu-item .form-radio,.menu .menu-item .form-switch{margin:.1rem 0}.menu .menu-item+.menu-item{margin-top:.2rem}.menu .menu-badge{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;height:100%;position:absolute;right:0;top:0}.menu .menu-badge .label{margin-right:.4rem}.modal{align-items:center;bottom:0;display:none;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center;left:0;opacity:0;overflow:hidden;padding:.4rem;position:fixed;right:0;top:0}.modal.active,.modal:target{display:-ms-flexbox;display:flex;opacity:1;z-index:400}.modal.active .modal-overlay,.modal:target .modal-overlay{background:rgba(247,248,249,.75);bottom:0;cursor:default;display:block;left:0;position:absolute;right:0;top:0}.modal.active .modal-container,.modal:target .modal-container{animation:slide-down .2s ease 1;z-index:1}.modal.modal-sm .modal-container{max-width:320px;padding:0 .4rem}.modal.modal-lg .modal-overlay{background:#fff}.modal.modal-lg .modal-container{box-shadow:none;max-width:960px}.modal-container{background:#fff;border-radius:.1rem;box-shadow:0 .2rem .5rem rgba(48,55,66,.3);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-height:75vh;max-width:640px;padding:0 .8rem;width:100%}.modal-container.modal-fullheight{max-height:100vh}.modal-container .modal-header{color:#303742;padding:.8rem}.modal-container .modal-body{overflow-y:auto;padding:.8rem;position:relative}.modal-container .modal-footer{padding:.8rem;text-align:right}.nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;list-style:none;margin:.2rem 0}.nav .nav-item a{color:#66758c;padding:.2rem .4rem;text-decoration:none}.nav .nav-item a:focus,.nav .nav-item a:hover{color:#5755d9}.nav .nav-item.active>a{color:#505c6e;font-weight:700}.nav .nav-item.active>a:focus,.nav .nav-item.active>a:hover{color:#5755d9}.nav .nav{margin-bottom:.4rem;margin-left:.8rem}.pagination{display:-ms-flexbox;display:flex;list-style:none;margin:.2rem 0;padding:.2rem 0}.pagination .page-item{margin:.2rem .05rem}.pagination .page-item span{display:inline-block;padding:.2rem .2rem}.pagination .page-item a{border-radius:.1rem;display:inline-block;padding:.2rem .4rem;text-decoration:none}.pagination .page-item a:focus,.pagination .page-item a:hover{color:#5755d9}.pagination .page-item.disabled a{cursor:default;opacity:.5;pointer-events:none}.pagination .page-item.active a{background:#5755d9;color:#fff}.pagination .page-item.page-next,.pagination .page-item.page-prev{-ms-flex:1 0 50%;flex:1 0 50%}.pagination .page-item.page-next{text-align:right}.pagination .page-item .page-item-title{margin:0}.pagination .page-item .page-item-subtitle{margin:0;opacity:.5}.panel{border:.05rem solid #dadee4;border-radius:.1rem;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.panel .panel-footer,.panel .panel-header{-ms-flex:0 0 auto;flex:0 0 auto;padding:.8rem}.panel .panel-nav{-ms-flex:0 0 auto;flex:0 0 auto}.panel .panel-body{-ms-flex:1 1 auto;flex:1 1 auto;overflow-y:auto;padding:0 .8rem}.popover{display:inline-block;position:relative}.popover .popover-container{left:50%;opacity:0;padding:.4rem;position:absolute;top:0;transform:translate(-50%,-50%) scale(0);transition:transform .2s;width:320px;z-index:300}.popover :focus+.popover-container,.popover:hover .popover-container{display:block;opacity:1;transform:translate(-50%,-100%) scale(1)}.popover.popover-right .popover-container{left:100%;top:50%}.popover.popover-right :focus+.popover-container,.popover.popover-right:hover .popover-container{transform:translate(0,-50%) scale(1)}.popover.popover-bottom .popover-container{left:50%;top:100%}.popover.popover-bottom :focus+.popover-container,.popover.popover-bottom:hover .popover-container{transform:translate(-50%,0) scale(1)}.popover.popover-left .popover-container{left:0;top:50%}.popover.popover-left :focus+.popover-container,.popover.popover-left:hover .popover-container{transform:translate(-100%,-50%) scale(1)}.popover .card{border:0;box-shadow:0 .2rem .5rem rgba(48,55,66,.3)}.step{display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;list-style:none;margin:.2rem 0;width:100%}.step .step-item{-ms-flex:1 1 0;flex:1 1 0;margin-top:0;min-height:1rem;position:relative;text-align:center}.step .step-item:not(:first-child)::before{background:#5755d9;content:"";height:2px;left:-50%;position:absolute;top:9px;width:100%}.step .step-item a{color:#5755d9;display:inline-block;padding:20px 10px 0;text-decoration:none}.step .step-item a::before{background:#5755d9;border:.1rem solid #fff;border-radius:50%;content:"";display:block;height:.6rem;left:50%;position:absolute;top:.2rem;transform:translateX(-50%);width:.6rem;z-index:1}.step .step-item.active a::before{background:#fff;border:.1rem solid #5755d9}.step .step-item.active~.step-item::before{background:#dadee4}.step .step-item.active~.step-item a{color:#bcc3ce}.step .step-item.active~.step-item a::before{background:#dadee4}.tab{align-items:center;border-bottom:.05rem solid #dadee4;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:.2rem 0 .15rem 0}.tab .tab-item{margin-top:0}.tab .tab-item a{border-bottom:.1rem solid transparent;color:inherit;display:block;margin:0 .4rem 0 0;padding:.4rem .2rem .3rem .2rem;text-decoration:none}.tab .tab-item a:focus,.tab .tab-item a:hover{color:#5755d9}.tab .tab-item a.active,.tab .tab-item.active a{border-bottom-color:#5755d9;color:#5755d9}.tab .tab-item.tab-action{-ms-flex:1 0 auto;flex:1 0 auto;text-align:right}.tab .tab-item .btn-clear{margin-top:-.2rem}.tab.tab-block .tab-item{-ms-flex:1 0 0;flex:1 0 0;text-align:center}.tab.tab-block .tab-item a{margin:0}.tab.tab-block .tab-item .badge[data-badge]::after{position:absolute;right:.1rem;top:.1rem;transform:translate(0,0)}.tab:not(.tab-block) .badge{padding-right:0}.tile{align-content:space-between;align-items:flex-start;display:-ms-flexbox;display:flex;-ms-flex-align:start;-ms-flex-line-pack:justify}.tile .tile-action,.tile .tile-icon{-ms-flex:0 0 auto;flex:0 0 auto}.tile .tile-content{-ms-flex:1 1 auto;flex:1 1 auto}.tile .tile-content:not(:first-child){padding-left:.4rem}.tile .tile-content:not(:last-child){padding-right:.4rem}.tile .tile-subtitle,.tile .tile-title{line-height:1.2rem}.tile.tile-centered{align-items:center;-ms-flex-align:center}.tile.tile-centered .tile-content{overflow:hidden}.tile.tile-centered .tile-subtitle,.tile.tile-centered .tile-title{margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.toast{background:rgba(48,55,66,.95);border:.05rem solid #303742;border-color:#303742;border-radius:.1rem;color:#fff;display:block;padding:.4rem;width:100%}.toast.toast-primary{background:rgba(87,85,217,.95);border-color:#5755d9}.toast.toast-success{background:rgba(50,182,67,.95);border-color:#32b643}.toast.toast-warning{background:rgba(255,183,0,.95);border-color:#ffb700}.toast.toast-error{background:rgba(232,86,0,.95);border-color:#e85600}.toast a{color:#fff;text-decoration:underline}.toast a.active,.toast a:active,.toast a:focus,.toast a:hover{opacity:.75}.toast .btn-clear{margin:.1rem}.toast p:last-child{margin-bottom:0}.tooltip{position:relative}.tooltip::after{background:rgba(48,55,66,.95);border-radius:.1rem;bottom:100%;color:#fff;content:attr(data-tooltip);display:block;font-size:.7rem;left:50%;max-width:320px;opacity:0;overflow:hidden;padding:.2rem .4rem;pointer-events:none;position:absolute;text-overflow:ellipsis;transform:translate(-50%,.4rem);transition:opacity .2s,transform .2s;white-space:pre;z-index:300}.tooltip:focus::after,.tooltip:hover::after{opacity:1;transform:translate(-50%,-.2rem)}.tooltip.disabled,.tooltip[disabled]{pointer-events:auto}.tooltip.tooltip-right::after{bottom:50%;left:100%;transform:translate(-.2rem,50%)}.tooltip.tooltip-right:focus::after,.tooltip.tooltip-right:hover::after{transform:translate(.2rem,50%)}.tooltip.tooltip-bottom::after{bottom:auto;top:100%;transform:translate(-50%,-.4rem)}.tooltip.tooltip-bottom:focus::after,.tooltip.tooltip-bottom:hover::after{transform:translate(-50%,.2rem)}.tooltip.tooltip-left::after{bottom:50%;left:auto;right:100%;transform:translate(.4rem,50%)}.tooltip.tooltip-left:focus::after,.tooltip.tooltip-left:hover::after{transform:translate(-.2rem,50%)}@keyframes loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes slide-down{0%{opacity:0;transform:translateY(-1.6rem)}100%{opacity:1;transform:translateY(0)}}.text-primary{color:#5755d9!important}a.text-primary:focus,a.text-primary:hover{color:#4240d4}a.text-primary:visited{color:#6c6ade}.text-secondary{color:#e5e5f9!important}a.text-secondary:focus,a.text-secondary:hover{color:#d1d0f4}a.text-secondary:visited{color:#fafafe}.text-gray{color:#bcc3ce!important}a.text-gray:focus,a.text-gray:hover{color:#adb6c4}a.text-gray:visited{color:#cbd0d9}.text-light{color:#fff!important}a.text-light:focus,a.text-light:hover{color:#f2f2f2}a.text-light:visited{color:#fff}.text-dark{color:#3b4351!important}a.text-dark:focus,a.text-dark:hover{color:#303742}a.text-dark:visited{color:#455060}.text-success{color:#32b643!important}a.text-success:focus,a.text-success:hover{color:#2da23c}a.text-success:visited{color:#39c94b}.text-warning{color:#ffb700!important}a.text-warning:focus,a.text-warning:hover{color:#e6a500}a.text-warning:visited{color:#ffbe1a}.text-error{color:#e85600!important}a.text-error:focus,a.text-error:hover{color:#cf4d00}a.text-error:visited{color:#ff6003}.bg-primary{background:#5755d9!important;color:#fff}.bg-secondary{background:#f1f1fc!important}.bg-dark{background:#303742!important;color:#fff}.bg-gray{background:#f7f8f9!important}.bg-success{background:#32b643!important;color:#fff}.bg-warning{background:#ffb700!important;color:#fff}.bg-error{background:#e85600!important;color:#fff}.c-hand{cursor:pointer}.c-move{cursor:move}.c-zoom-in{cursor:zoom-in}.c-zoom-out{cursor:zoom-out}.c-not-allowed{cursor:not-allowed}.c-auto{cursor:auto}.d-block{display:block}.d-inline{display:inline}.d-inline-block{display:inline-block}.d-flex{display:-ms-flexbox;display:flex}.d-inline-flex{display:-ms-inline-flexbox;display:inline-flex}.d-hide,.d-none{display:none!important}.d-visible{visibility:visible}.d-invisible{visibility:hidden}.text-hide{background:0 0;border:0;color:transparent;font-size:0;line-height:0;text-shadow:none}.text-assistive{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.divider,.divider-vert{display:block;position:relative}.divider-vert[data-content]::after,.divider[data-content]::after{background:#fff;color:#bcc3ce;content:attr(data-content);display:inline-block;font-size:.7rem;padding:0 .4rem;transform:translateY(-.65rem)}.divider{border-top:.05rem solid #f1f3f5;height:.05rem;margin:.4rem 0}.divider[data-content]{margin:.8rem 0}.divider-vert{display:block;padding:.8rem}.divider-vert::before{border-left:.05rem solid #dadee4;bottom:.4rem;content:"";display:block;left:50%;position:absolute;top:.4rem;transform:translateX(-50%)}.divider-vert[data-content]::after{left:50%;padding:.2rem 0;position:absolute;top:50%;transform:translate(-50%,-50%)}.loading{color:transparent!important;min-height:.8rem;pointer-events:none;position:relative}.loading::after{animation:loading .5s infinite linear;background:0 0;border:.1rem solid #5755d9;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:.8rem;left:50%;margin-left:-.4rem;margin-top:-.4rem;opacity:1;padding:0;position:absolute;top:50%;width:.8rem;z-index:1}.loading.loading-lg{min-height:2rem}.loading.loading-lg::after{height:1.6rem;margin-left:-.8rem;margin-top:-.8rem;width:1.6rem}.clearfix::after{clear:both;content:"";display:table}.float-left{float:left!important}.float-right{float:right!important}.p-relative{position:relative!important}.p-absolute{position:absolute!important}.p-fixed{position:fixed!important}.p-sticky{position:-webkit-sticky!important;position:sticky!important}.p-centered{display:block;float:none;margin-left:auto;margin-right:auto}.flex-centered{align-items:center;display:-ms-flexbox;display:flex;-ms-flex-align:center;-ms-flex-pack:center;justify-content:center}.m-0{margin:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mr-0{margin-right:0!important}.mt-0{margin-top:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-bottom:0!important;margin-top:0!important}.m-1{margin:.2rem!important}.mb-1{margin-bottom:.2rem!important}.ml-1{margin-left:.2rem!important}.mr-1{margin-right:.2rem!important}.mt-1{margin-top:.2rem!important}.mx-1{margin-left:.2rem!important;margin-right:.2rem!important}.my-1{margin-bottom:.2rem!important;margin-top:.2rem!important}.m-2{margin:.4rem!important}.mb-2{margin-bottom:.4rem!important}.ml-2{margin-left:.4rem!important}.mr-2{margin-right:.4rem!important}.mt-2{margin-top:.4rem!important}.mx-2{margin-left:.4rem!important;margin-right:.4rem!important}.my-2{margin-bottom:.4rem!important;margin-top:.4rem!important}.p-0{padding:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.pr-0{padding-right:0!important}.pt-0{padding-top:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-bottom:0!important;padding-top:0!important}.p-1{padding:.2rem!important}.pb-1{padding-bottom:.2rem!important}.pl-1{padding-left:.2rem!important}.pr-1{padding-right:.2rem!important}.pt-1{padding-top:.2rem!important}.px-1{padding-left:.2rem!important;padding-right:.2rem!important}.py-1{padding-bottom:.2rem!important;padding-top:.2rem!important}.p-2{padding:.4rem!important}.pb-2{padding-bottom:.4rem!important}.pl-2{padding-left:.4rem!important}.pr-2{padding-right:.4rem!important}.pt-2{padding-top:.4rem!important}.px-2{padding-left:.4rem!important;padding-right:.4rem!important}.py-2{padding-bottom:.4rem!important;padding-top:.4rem!important}.s-rounded{border-radius:.1rem}.s-circle{border-radius:50%}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-normal{font-weight:400}.text-bold{font-weight:700}.text-italic{font-style:italic}.text-large{font-size:1.2em}.text-small{font-size:.9em}.text-tiny{font-size:.8em}.text-muted{opacity:.8}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-clip{overflow:hidden;text-overflow:clip;white-space:nowrap}.text-break{-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;word-break:break-word;word-wrap:break-word} \ No newline at end of file diff --git a/defaultapps.json b/defaultapps_banglejs1.json similarity index 100% rename from defaultapps.json rename to defaultapps_banglejs1.json diff --git a/defaultapps_banglejs2.json b/defaultapps_banglejs2.json new file mode 100644 index 000000000..04bd44504 --- /dev/null +++ b/defaultapps_banglejs2.json @@ -0,0 +1 @@ +["boot","launch","antonclk","health","setting","about","widbat","widbt","widlock","widid"] diff --git a/index.html b/index.html index a5ae7bff0..e7c7c31cd 100644 --- a/index.html +++ b/index.html @@ -60,6 +60,17 @@

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 { + function getFace(){ + + const locale = require("locale"); + + var W = g.getWidth(); + var H = g.getHeight(); + var scale = W/240; + + function drawClock(){ + var now=Date(); + d=now.toString().split(' '); + var min=d[4].substr(3,2); + var sec=d[4].substr(-2); + var tm=d[4].substring(0,5); + var hr=d[4].substr(0,2); + lastmin=min; + g.reset(); + g.clearRect(0,24,W-1,H-1); + g.setColor(g.theme.fg); + g.setFontAlign(0,-1); + g.setFontVector(80*scale); + g.drawString(tm,4+W/2,H/2+24-80*scale); + g.setFontVector(36*scale); + g.setColor(g.theme.fg2); + d[1] = locale.month(now,3); + d[0] = locale.dow(now,3); + var dt=d[0]+" "+d[1]+" "+d[2];//+" "+d[3]; + g.drawString(dt,W/2,H/2+24); + g.flip(); + } + + + return {init:drawClock, tick:drawClock, tickpersec:false}; + } + + return getFace; + +})(); + diff --git a/apps/multiclock/multiclock-icon.img b/apps/multiclock/multiclock-icon.img new file mode 100644 index 0000000000000000000000000000000000000000..57e0a935f35d622d52dfb29a3e2af584d16eb56f GIT binary patch literal 1156 zcmchVy-veG5QNQ7K}V4nu>1mvce z)9La!{o86-w`QbVSCN1>fqCVZO@-spY-D&7!0yd_z2H*?v#=QgDnQN|*&omN`P>w= z_D&J!_9Y?65GOYYm3rjUA)G^9`EYPvGNn^ObDcePAmk^V<22I2iGJi|EB!PU@9(`0 zw#)-%SGIKBF#^Sh!P|X$FACES.push(eval(require("Storage").read(face)))); +var lastface = STOR.readJSON("clock.json") || {pinned:0} +var iface = lastface.pinned; +var face = FACES[iface](); +var intervalRefSec; +var intervalRefSec; +var tickTimeout; + +function stopdraw() { + if(intervalRefSec) {intervalRefSec=clearInterval(intervalRefSec);} + if(tickTimeout) {tickTimeout=clearTimeout(tickTimeout);} + g.clear(); +} + +function queueMinuteTick() { + if (tickTimeout) clearTimeout(tickTimeout); + tickTimeout = setTimeout(function() { + tickTimeout = undefined; + face.tick(); + queueMinuteTick(); + }, 60000 - (Date.now() % 60000)); +} + +function startdraw() { + g.reset(); + face.init(); + if (face.tickpersec) + intervalRefSec = setInterval(face.tick,1000); + else + queueMinuteTick(); + Bangle.drawWidgets(); +} + +var SCREENACCESS = { + withApp:true, + request:function(){ + this.withApp=false; + stopdraw(); + }, + release:function(){ + this.withapp=true; + startdraw(); + setButtons(); + } +}; + +Bangle.on('lcdPower',function(b) { + if (!SCREENACCESS.withApp) return; + if (b) { + startdraw(); + } else { + stopdraw(); + } +}); + +function setButtons(){ + function newFace(inc){ + if (!inc) Bangle.showLauncher(); + else { + var n = FACES.length-1; + iface+=inc; + iface = iface>n?0:iface<0?n:iface; + stopdraw(); + face = FACES[iface](); + startdraw(); + } + } + Bangle.setUI("leftright", newFace); +} + +E.on('kill',()=>{ + if (iface!=lastface.pinned){ + lastface.pinned=iface; + STOR.write("clock.json",lastface); + } +}); + +Bangle.loadWidgets(); +g.clear(); +startdraw(); +setButtons(); + + + diff --git a/apps/multiclock/nifty.face.js b/apps/multiclock/nifty.face.js new file mode 100644 index 000000000..2c2af6063 --- /dev/null +++ b/apps/multiclock/nifty.face.js @@ -0,0 +1,55 @@ +(() => { + function getFace(){ + + const locale = require("locale"); + const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; + + const scale = g.getWidth() / 176; + + const widget = 24; + + const viewport = { + width: g.getWidth(), + height: g.getHeight(), + } + + const center = { + x: viewport.width / 2, + y: Math.round(((viewport.height - widget) / 2) + widget), + } + + function d02(value) { + return ('0' + value).substr(-2); + } + + function drawClock() { + g.reset(); + g.clearRect(0, widget, viewport.width, viewport.height); + var now = new Date(); + const hour = d02(now.getHours() - (is12Hour && now.getHours() > 12 ? 12 : 0)); + const minutes = d02(now.getMinutes()); + const day = d02(now.getDay()); + const month = d02(now.getMonth() + 1); + const year = now.getFullYear(); + const month2 = locale.month(now, 3); + const day2 = locale.dow(now, 3); + g.setFontAlign(1, 0).setFont("Vector", 90 * scale); + g.drawString(hour, center.x + 32 * scale, center.y - 31 * scale); + g.drawString(minutes, center.x + 32 * scale, center.y + 46 * scale); + g.fillRect(center.x + 30 * scale, center.y - 72 * scale, center.x + 32 * scale, center.y + 74 * scale); + g.setFontAlign(-1, 0).setFont("Vector", 16 * scale); + g.drawString(year, center.x + 40 * scale, center.y - 62 * scale); + g.drawString(month, center.x + 40 * scale, center.y - 44 * scale); + g.drawString(day, center.x + 40 * scale, center.y - 26 * scale); + g.drawString(month2, center.x + 40 * scale, center.y + 48 * scale); + g.drawString(day2, center.x + 40 * scale, center.y + 66 * scale); + } + + + return {init:drawClock, tick:drawClock, tickpersec:false}; + } + + return getFace; + +})(); + diff --git a/apps/multiclock/ped.js b/apps/multiclock/ped.js deleted file mode 100644 index a0f81e2e5..000000000 --- a/apps/multiclock/ped.js +++ /dev/null @@ -1,41 +0,0 @@ -(() => { - - function getFace(){ - - function draw() { - let steps = "-"; - let show_steps = false; - - // only attempt to get steps if activepedom is loaded - if (WIDGETS.activepedom !== undefined) { - steps = WIDGETS.activepedom.getSteps(); - } else if (WIDGETS.wpedom !== undefined) { - steps = WIDGETS.wpedom.getSteps(); - } - - var d = new Date(); - var da = d.toString().split(" "); - var time = da[4].substr(0,5); - - g.reset(); - g.clearRect(0,24,239,239); - g.setFont("Vector", 80); - g.setColor(1,1,1); // white - g.setFontAlign(0, -1); - g.drawString(time, g.getWidth()/2, 60); - g.setColor(0,255,0); // green - g.setFont("Vector", 60); - g.drawString(steps, g.getWidth()/2, 160); - } - - function onSecond(){ - var t = new Date(); - if ((t.getSeconds() % 5) === 0) draw(); - } - - return {init:draw, tick:onSecond}; - } - - return getFace; - -})(); diff --git a/apps/multiclock/timdat.js b/apps/multiclock/timdat.js deleted file mode 100644 index a4a93a691..000000000 --- a/apps/multiclock/timdat.js +++ /dev/null @@ -1,47 +0,0 @@ -(() => { - var locale = require("locale"); - var dayFirst = ["en_GB", "en_IN", "en_NAV", "de_DE", "nl_NL", "fr_FR", "en_NZ", "en_AU", "de_AT", "en_IL", "es_ES", "fr_BE", "de_CH", "fr_CH", "it_CH", "it_IT", "tr_TR", "pt_BR", "cs_CZ", "pt_PT"]; - var withDot = ["de_DE", "nl_NL", "de_AT", "de_CH", "hu_HU", "cs_CZ", "sl_SI"]; - - function getFace(){ - - var lastmin=-1; - function drawClock(){ - var d=Date(); - if (d.getMinutes()==lastmin) return; - var tm=d.toString().split(' ')[4].substring(0,5); - lastmin=d.getMinutes(); - g.reset(); - g.clearRect(0,24,239,239); - var w=g.getWidth(); - g.setColor(0xffff); - g.setFontVector(80); - g.drawString(tm,4+(w-g.stringWidth(tm))/2,64); - g.setFontVector(36); - g.setColor(0x07ff); - var dt=locale.dow(d, 1) + " "; - if (dayFirst.includes(locale.name)) { - dt+=d.getDate(); - if (withDot.includes(locale.name)) { - dt+="."; - } - dt+=" " + locale.month(d, 1); - } else { - dt+=locale.month(d, 1) + " " + d.getDate(); - } - g.drawString(dt,(w-g.stringWidth(dt))/2,160); - g.flip(); - } - - function drawFirst(){ - lastmin=-1; - drawClock(); - } - - return {init:drawFirst, tick:drawClock}; - } - - return getFace; - -})(); - diff --git a/apps/multiclock/txt.js b/apps/multiclock/txt.face.js similarity index 53% rename from apps/multiclock/txt.js rename to apps/multiclock/txt.face.js index 130455176..fddc07214 100644 --- a/apps/multiclock/txt.js +++ b/apps/multiclock/txt.face.js @@ -1,8 +1,14 @@ (() => { function getFace(){ - - function drawTime(d) { + + + var W = g.getWidth(); + var H = g.getHeight(); + var scale = W/240; + var F = 44 * scale; + + function drawTime() { function convert(n){ var t0 = [" ","one","two","three","four","five","six","seven","eight","nine"]; var t1 = ["ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen"]; @@ -13,28 +19,25 @@ return "error"; } g.reset(); - g.clearRect(0,40,239,210); - g.setColor(1,1,1); + g.clearRect(0,24,W-1,H-1); + var d = new Date(); + g.setColor(g.theme.fg); g.setFontAlign(0,0); - g.setFont("Vector",44); + g.setFont("Vector",F); var txt = convert(d.getHours()); - g.drawString(txt.top,120,60); - g.drawString(txt.bot,120,100); + g.setColor(g.theme.fg); + g.drawString(txt.top,W/2,H/2+24-2*F); + g.setColor(g.theme.fg2); + g.drawString(txt.bot,W/2,H/2+24-F); txt = convert(d.getMinutes()); - g.drawString(txt.top,120,140); - g.drawString(txt.bot,120,180); + g.setColor(g.theme.fg); + g.drawString(txt.top,W/2,H/2+24); + g.setColor(g.theme.fg2); + g.drawString(txt.bot,W/2,H/2+24+F); } - function onSecond(){ - var t = new Date(); - if (t.getSeconds() === 0) drawTime(t); - } - function drawAll(){ - drawTime(new Date()); - } - - return {init:drawAll, tick:onSecond}; + return {init:drawTime, tick:drawTime, tickpersec:false}; } return getFace; diff --git a/apps/multiclock/txtface.jpg b/apps/multiclock/txtface.jpg deleted file mode 100644 index e3834125780c16c697d9fb22d5d3eead3cee2d30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51801 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 diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index e0e967166..1277f0d9d 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -2,3 +2,4 @@ 0.02: Display 12 hour clock as 12:xx not 00:xx when just into PM 0.03: Make it work with Gadgetbridge, Notifications fullscreen on a Bangle 2 0.04: Leave space at the bottom for Chrono widget, set back option at first option +0.05: Added 2 new fonts diff --git a/apps/pastel/README.md b/apps/pastel/README.md index 9e8c133ec..324c3915a 100644 --- a/apps/pastel/README.md +++ b/apps/pastel/README.md @@ -1,7 +1,7 @@ # Pastel Clock - a configurable clock with custom fonts and background * Designed specifically for Bangle 1 and Bangle 2 -* A choice of 5 different custom fonts +* A choice of 7 different custom fonts * Supports the Light and Dark themes * Has a settings menu, change font, enable/disable the grid and the date display @@ -15,3 +15,6 @@ I came up with the name Pastel due to the shade of the grid background. ![](screenshot_b1_light.jpg) ![](screenshot_b2_dark.jpg) +![](screenshot_monoton.jpg) +![](screenshot_elite.jpg) + diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 98f8af7f9..1fe3e4a58 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -47,6 +47,16 @@ var scale = 1; // size multiplier for this font g.setFontCustom(font, 46, widths, 58+(scale<<8)+(1<<16)); }; +Graphics.prototype.setFontMonoton = function(scale) { + // Actual height 44 (43 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAABmwAAAAAAzYAAAAAAZsAAAAAAM2AAAAAAGbAAAAAADNgAAAAABmwAAAAAAzYAAAAAAZsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAD+AAAAAAf8AAAAAD/ggAAAAf8HwAAAD/g/4AAAf8H/AAAD/g/4OAAf8H/B/AD/g/4P+Af8H/B/wAfg/4P+AAMH/B/wAAA/4H+AAAD/A/4AAAB4H/AAAAAA/4AAAAAH/AAAAAAP4AAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAH//gAAAAf//8AAAA/AAPgAAA8f/x8AAB4//+PAAB5+APxwABzwfwecAAzj//jnAA7n+P85gA7ngAPO4AbnH/xzsAdnP/+c3AN3PAHndgGzOAA5m4DbuAAO7MD9mAADN2Bs3AAB2bA2bAAAbNgbNgAANmwNmwAAGzYGzYAADZsDdmAADN2B+7AABuzAbMwABmbgNneAD3NgHZ3+/3MwBuc//nO4A7nB8HGYAM58AfOcAHeP/+OcABzx/8ecAAc+AA+cAAHH//8cAAB4//48AAAPg+B8AAAD+AP4AAAAP//wAAAAA/+AAAAAAAAAAAAAAAAAAABsAAAAAAA2AAAAAAAbAAAAAAANgAAAAAAGwAAAAAADf////8ABv////+AA3/////AAbAAAAAAAN/////wAG/////4ADYAAAAAABv////+AA3/////AAb/////gANgAAAAAAG/////4ADf////8AAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAAADcAAAA2wBs2AADbYA2bAADtsAbZgADm2AftwAHjbAN24AHNtgGzYAPO2wDZsAPebYBs2AOeNsA2bAec22AbNge87bANmwc55tgG7c8542wD9355zbYA2Z5zztsAbODzjm2ANz/nnjbADc/nnhtgBnCPHA2wA74fPAbYAOf+OANsADj8eAG2AA8A+ADbAAP/8AAAAAB/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAABgG6AAAAuwDdsAAG3YBs2AADZuA27AABu3A/ZgAA7dgbtwAAduwN2w2zG3YGzYbZjZsDZsNsxs2Bs2G2Y2bA2bDbMbNgbNhtmNmwNmw2zGzYG7MbdnbsD939m/d2A2Z/7PM3AbuBtwO7AOz73eeZgDc/9n+dwB3H2Y8cwAZ4HnA84AGf/5/84ADz/OP44AAeALwB4AAH/+//4AAA/+H/wAAADwAfAAAAAAAAAAAAAAAAAAAAAAAZsAAAAAB82AAAAAD+bAAAAAHzNgAAAAPjmwAAAAfHzYAAAB+P5sAAAD8fM2AAAHw+ObAAAPj8fNgAAfH4/mwAA+Ph8zYAAcfH4ZsAAA+Px82AAB8fD+bAAD4+HzNgABh8PhmwAAH4/AzYAAPx+AZsAAPD4AM2AAGHwP+bfgAfgH/NvwA/AABmwAA8AAAzYAAYAA/5t+AAAAf82/AAAAAGbAAAAAADNgAAAAAAAAAAAAAAAAAAAAAAAGAAE///ADAAGf//gBwADP//wCcABmAAADmAAz//8C7gAZ//+DMwAMwAAA3YAGf//hZsADP//xu3ABn//4zdgAzDNsNuwAZhu2GzYAMw2bDZsAGYbNhs2ADMNmw2bABmGzYbNgAzDZsdmwAZhs2M3YAMw3d+zcAGYZm+ZsADMOzgd2ABmDM883AAzB3P87AAZgZx47gAMwOcB5gAGYDn/5gADMA4/zwAAAAPADwAAAAD8fgAAAAAf/gAAAAAB+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///4AAAD////AAAHwAADwAAHH//8eAAHP///ngAHfgAB8wAHeH/8PcADcf//x3ADsf//+ZgBu8AADu4B2c//8zMA3d///M2AbNwAB2bgduxs2bswP2Z2/O3YGzYzbDZsDZsbths2Bs2Nmw2bA2bGzYbNgbNjZsNmwNmxs2GzYH7c2bHbsDtmbtzdmA2bM3fs3AbMHZnO7AM3BuYOZgHZAzP+dgBmAMx+cwA7gHeAcwAMgB3584AHAAc/84ABgAHHx4AAAAB4D4AAAAAf/wAAAAAD/gAAAAAAAAAAAAAAAAAAZsAAAAAAM2AAAAAAGbAAAAAADNgAAAAABmwAAAAAAzYAAAAAAZsAAAAAAM2AAAAPAGbAAAB/gDNgAAP+ABmwAD/wYAzYAf+D8AZsD/wf8AM2f8D/gAGT/gf8HgAf8D/g/wB/g/8H/AA8H/g/4MAA/8H/B+AH/g/4P+AH4H/B/wADA/4P+AAAH/B/wAAA/4P+AAAAfB/wAAAAAP+AAAAAB/wAAAAAD+AAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAGAAwAAAA/8H/gAAB//v/8AAB4B/APgADz+PP54ABn/x//OABng8eDzAB3HHOcdwA3P9z/nYA7P/d/5mAbOBmYO7ANmebvzNwP3fs392YGzc3ZmbsDZsZsxs2Bs2M2Y2bA2bGbMbNgbNjNmNmwNmxmzGzYGzYzZjZsDZsZsxs2Bs2M2Y2bA2bGbMbNgbNzNmNmwP2Zm7s3YDbv7M+7MBszt3OZuA3MGZwd2ANn/uf8zAGY+zn47gDvAc4A7gA78/Pj5gAOf/z/zwADj8cPjwAA+A/gHgAAH/9//gAAA/4P/AAAAAAAAAAAAAAAAAAAAAAAAAAAAB+AAAAAAD/4AAAAAHw/AAAAAHADwADAAHP8cABgAHf/nAA4AHeB5gDuAHcfOYAzADc/7uBdwBs8ezBmYBu4DdgbsA2Z924s3AbN+bM3bgfsxt2duwNm4zbG3YGzYZtjZsDZsM2xs2Bs2GbY2bA2bDNsbNgbNhv2NmwP242zO3YHbszbm7sBs3AAHZuA2Z///M2Abuf//O7AGzh/8ObgDc8AA+dgB3P//+dwAdx//8cwAGeAAA8wADn///44AA8///54AAPgAAB4AAB+AAPwAAAP///gAAAA//+AAAAAAAAAAAAAAAAAAAAAAAAAAAADbBmwAAABtgzYAAAA2wZsAAAAbYM2AAAANsGbAAAAG2DNgAAADbBmwAAABtgzYAAAA2wZsAAAAAAAAAAAAAAAAAAA="), 46, atob("DRYpFR0eHiImHygmDQ=="), 49+(scale<<8)+(1<<16)); +} + +Graphics.prototype.setFontSpecialElite = function(scale) { + // Actual height 40 (39 - 0) + g.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAYAAAAAAAfwAAAAAAP/AAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAB/+AAAAAAf/gAAAAAH/4AAAAAAv8AAAAAAN6AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAAfAAAAAAAPwAAAAAAP8AAAAAAH+AAAAAAH+AAAAAAD+AAAAAAD/AAAAAAD/AAAAAAB/AAAAAAB/AAAAAAB/AAAAAAB/gAAAAAB/gAAAAAB/gAAAAAA/gAAAAAB/wAAAAAA/4AAAAAA/wAAAAAA/4AAAAAA/4AAAAAAf8AAAAAAP8AAAAAAD8AAAAAAA8AAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//wAAAAP///gAAAH/9/+AAAD/gAf4AAB/AAB+AAA/AAAHwAAPAAAA+AADgAAAPgAAwAAAD4AAcAAAAfAAHAAAAHwABwAAAB8AA4AAAAfAAOAAAAHwABwAAAB8AAcAAAA/AAHgAAAPgAB+AAAH4AAPgAAD8AAD+AAD+AAA/4Af/AAAB////AAAAP///wAAAAP//gAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAOAAAAHAADgAAAD4AB4AAAA+AAeAAAAPgAHgAAAD4AB4AAAAeAAeAAAAHgAHgAAAB4AB4AAAAcAAeAAAAPAAH4AAP/wAB/////+AAf/////gAH/////4AB///+/+AAAAQAAPgAAAAAAB4AAAAAAAeAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAADwAAAAAAA+AAAAAAAPgAAAAAAB4AAAAAAAAAAAAAAAAAAAAAAAcAAAB+AAfwAAA/wAf/AAA/8Af/wAAf/AP/8AAGPwP//AADh8D48AAA4OB8OAAAOAAfDgAAHAAPg4AABwADwPAAAcAB8DwAAHAAeAeAABwAHAHgAAcADwB8AAHAB4APgAB4A+AB4AAPAfAAeAAD4fgADgAAf/4AA4AAD/8AAeAAAf+AAfAAAB8AAPwAAAAAAD4AAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAf8AAB/wAH/wAAf8AB/8AAH+AAffgAB4AcDx4AAcAPAAfAAHAPwAHwABwHwAA8AAcB+AAPAAHA/gADwABw/4AA8AAcf+AAPAAHP/gAHwABz74AB8AAf8fAA/AAH8DwAPgAD/A8AHwAA/gHwP8AAPwA//+AADwAH//AAAAAA//gAAAAAD/wAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAP8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAB/PAAAAAA/jwAAAAAfg8AAAAAPwPAAAAAH4DwAAAAD4A8HAAAD8APBwAAB+ADw8AAA+AA8PAAA/AAPDgAAPgADw8AAHwAB8/AAD+B///wAA/////8AAP/////AAB+f///wAAAAAHx8AAAAAB8PAAAAAAPDwAAAAADw8AAAAAA4PAAAAAAODwAAAAADgcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAB/gAAH//wf8AAB//+H/gAAf//h/4AAHJ/wf/AABwD4D/4AAeA+AAeAAHgPAAHgAB4DwAB4AAeA4AAeAAHgOAAHgAA4DgAB4AAOA8AAeAADgPAAHgAB4D4ADwAAeAeAB4AAHAHwAeAABgAfAPgAAYAD8fgAAAAA//wAAAAAH/4AAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAAA//wAAAAB///AAAAB////AAAB/7//wAAA/AfB+AAAfAPAPwAAPgHgB+AAHwBwAPgAB4A8AB8AAeAPAAfAAPADwAHwADgA8AB8AA8APAAfAAPADwAHwADwA8AB8AA8AHgA+AAP8B4APgAD/wfAH4AA/8D4D8AAH/A///AAB/wH//gAAH8A//wAAA8AH/4AAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAB/4AAAAAA/8AAAAAAP+AAAAAAD+AAAAAAAeAAAAAAAHAAAAAAADwAAAAAAB8AAAAAAAfAAAAAAAHwAAA/8AD+AAB//AA/gAB//wAP4AB//gAB+AB//AAAfwB//AAAH8A/wAAAA/A/wAAAAHw/wAAAAB8/wAAAAAffwAAAAAP/wAAAAAD/4AAAAAA/4AAAAAAP8AAAAAAD4AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAP/wAAAH8H/+AAAH/z//wAAD////+AAB///h/gAA/B/gH4AAPAP4A/AAHwB8AHwAB4APAB8AAeADgAfAAHgA4ADwABwAOAA8AAcADgAPAAHAA4ADwAB4AeAA8AAfAHwAPAADwB8AHgAA+A/gD4AAPgP4B+AAB+P/h/gAAP////wAAB/8f/4AAAP8D/8AAAAAAf8AAAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAA/4APAAAA//gH4AAAP/8D/gAAP4Pg/8AADwB8P/AAB4AfD/4AAeAD4d+AAHAA+AfwADwAHgH8AA4AA8A/AAOAAPAPgADgADwD4AA8AA4B+AAPAAeAfAAB4AHgHgAAeADwD4AAHwA8A8AAA+AfA/AAAHp/h/gAAA3//+gAAAB//+gAAAAd//gAAAACf/wAAAAAH/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAABwB/AAAAB/A/8AAAA/wf/gAAAP+H/4AAAH/h/+AAAB/8f/gAAAP+H/4AAAD/h/+AAAAfwf/gAAAH8C/wAAAAAA3oAAAAAABwAAAAAAAAAAAAAAAAAAAA=="), 46, atob("ERwfHB0cHxsdHB4dEQ=="), 50+(scale<<8)+(1<<16)); +} + const SETTINGS_FILE = "pastel.json"; let settings = undefined; @@ -113,6 +123,10 @@ function draw() { g.setFontCabinSketch(); else if (settings.font == "Orbitron") g.setFontOrbitron(); + else if (settings.font == "Monoton") + g.setFontMonoton(); + else if (settings.font == "Elite") + g.setFontSpecialElite(); else g.setFontLato(); diff --git a/apps/pastel/pastel.settings.js b/apps/pastel/pastel.settings.js index 2e4afadc8..a8aadd58f 100644 --- a/apps/pastel/pastel.settings.js +++ b/apps/pastel/pastel.settings.js @@ -22,14 +22,14 @@ storage.write(SETTINGS_FILE, settings) } - var font_options = ["Lato","Architect","GochiHand","CabinSketch","Orbitron"]; + var font_options = ["Lato","Architect","GochiHand","CabinSketch","Orbitron","Monoton","Elite"]; E.showMenu({ '': { 'title': 'Pastel Clock' }, '< Back': back, 'Font': { value: 0 | font_options.indexOf(s.font), - min: 0, max: 4, + min: 0, max: 6, format: v => font_options[v], onchange: v => { s.font = font_options[v]; diff --git a/apps/pastel/screenshot_elite.jpg b/apps/pastel/screenshot_elite.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b881830ed9c15b927ac46fda6b7ad4b0eb3c300c GIT binary patch literal 9486 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 xeFAyxBs-Y|mReSOecL1idHXHzv=cbKkvx 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 literal 0 HcmV?d00001 diff --git a/apps/pomodo/CHANGELOG.md b/apps/pomodo/CHANGELOG.md index b8c5dd621..b4667aff8 100644 --- a/apps/pomodo/CHANGELOG.md +++ b/apps/pomodo/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2021-11-18 + +- [Feature] Ported to Banglejs2 + ## 2019-11-27 - [Feature] App now saves the last interval value diff --git a/apps/pomodo/ChangeLog b/apps/pomodo/ChangeLog index 5560f00bc..3630ae7b6 100644 --- a/apps/pomodo/ChangeLog +++ b/apps/pomodo/ChangeLog @@ -1 +1,2 @@ +0.02: Ported to Banglejs2. 0.01: New App! diff --git a/apps/pomodo/pomodoro.js b/apps/pomodo/pomodoro.js index 3e11739da..96d2e8d6a 100644 --- a/apps/pomodo/pomodoro.js +++ b/apps/pomodo/pomodoro.js @@ -11,8 +11,9 @@ const STATES = { var counterInterval; class State { - constructor (state) { + constructor (state, device) { this.state = state; + this.device = device; this.next = null; } @@ -47,8 +48,8 @@ class State { } class InitState extends State { - constructor (time) { - super(STATES.INIT); + constructor (device) { + super(STATES.INIT, device); this.timeCounter = parseInt(storage.read(".pomodo") || DEFAULT_TIME, 10); } @@ -58,7 +59,7 @@ class InitState extends State { } setButtons () { - setWatch(() => { + this.device.setBTN1(() => { if (this.timeCounter + 300 > 3599) { this.timeCounter = 3599; } else { @@ -67,23 +68,23 @@ class InitState extends State { this.draw(); - }, BTN1, { repeat: true }); + }); - setWatch(() => { + this.device.setBTN3(() => { if (this.timeCounter - 300 > 0) { this.timeCounter -= 300; this.draw(); } - }, BTN3, { repeat: true }); + }); - setWatch(() => { + this.device.setBTN4(() => { if (this.timeCounter - 60 > 0) { this.timeCounter -= 60; this.draw(); } - }, BTN4, { repeat: true }); + }); - setWatch(() => { + this.device.setBTN5(() => { if (this.timeCounter + 60 > 3599) { this.timeCounter = 3599; } else { @@ -92,15 +93,15 @@ class InitState extends State { this.draw(); - }, BTN5, { repeat: true }); + }); - setWatch(() => { + this.device.setBTN2(() => { this.saveTime(); - const startedState = new StartedState(this.timeCounter); + const startedState = new StartedState(this.timeCounter, this.device); this.setNext(startedState); this.next.go(); - }, BTN2, { repeat: true }); + }); } draw () { @@ -112,14 +113,14 @@ class InitState extends State { } class StartedState extends State { - constructor (timeCounter) { - super(STATES.STARTED); + constructor (timeCounter, buttons) { + super(STATES.STARTED, buttons); this.timeCounter = timeCounter; } draw () { - drawCounter(this.timeCounter, 120, 120); + drawCounter(this.timeCounter, g.getWidth() / 2, g.getHeight() / 2); } init () { @@ -137,15 +138,15 @@ class StartedState extends State { this.draw(); } - const doneState = new DoneState(); + const doneState = new DoneState(this.device); this.setNext(doneState); counterInterval = setInterval(countDown.bind(this), 1000); } } class BreakState extends State { - constructor () { - super(STATES.BREAK); + constructor (buttons) { + super(STATES.BREAK, buttons); } draw () { @@ -153,44 +154,40 @@ class BreakState extends State { } init () { - const startedState = new StartedState(TIME_BREAK); + const startedState = new StartedState(TIME_BREAK, this.device); this.setNext(startedState); this.next.go(); } } + class DoneState extends State { - constructor () { - super(STATES.DONE); + constructor (device) { + super(STATES.DONE, device); } setButtons () { - setWatch(() => { - const initState = new InitState(); - clearTimeout(this.timeout); - initState.go(); - }, BTN1, { repeat: true }); + this.device.setBTN1(() => { + }); - setWatch(() => { - const breakState = new BreakState(); - clearTimeout(this.timeout); - breakState.go(); - }, BTN3, { repeat: true }); + this.device.setBTN3(() => { + }); - setWatch(() => { - }, BTN2, { repeat: true }); + this.device.setBTN2(() => { + }); } draw () { g.clear(); - g.setFont("6x8", 2); - g.setFontAlign(0, 0, 3); - g.drawString("AGAIN", 230, 50); - g.drawString("BREAK", 230, 190); - g.setFont("Vector", 45); - g.setFontAlign(-1, -1); - - g.drawString('You\nare\na\nhero!', 50, 40); + E.showPrompt("You are a hero!", { + buttons : {"AGAIN":1,"BREAK":2} + }).then((v) => { + var nextSate = (v == 1 + ? new InitState(this.device) + : new BreakState(this.device)); + clearTimeout(this.timeout); + nextSate.go(); + }); } init () { @@ -215,13 +212,61 @@ class DoneState extends State { } } +class Bangle1 { + setBTN1(callback) { + setWatch(callback, BTN1, { repeat: true }); + } + + setBTN2(callback) { + setWatch(callback, BTN2, { repeat: true }); + } + + setBTN3(callback) { + setWatch(callback, BTN3, { repeat: true }); + } + + setBTN4(callback) { + setWatch(callback, BTN4, { repeat: true }); + } + + setBTN5(callback) { + setWatch(callback, BTN5, { repeat: true }); + } +} + +class Bangle2 { + setBTN1(callback) { + Bangle.on('touch', function(zone, e) { + if (e.y < g.getHeight() / 2) { + callback(); + } + }); + } + + setBTN2(callback) { + setWatch(callback, BTN1, { repeat: true }); + } + + setBTN3(callback) { + Bangle.on('touch', function(zone, e) { + if (e.y > g.getHeight() / 2) { + callback(); + } + }); + } + + setBTN4(callback) { } + + setBTN5(callback) { } +} + function drawCounter (currentValue, x, y) { if (currentValue < 0) { return; } - x = x || 120; - y = y || 120; + x = x || g.getWidth() / 2; + y = y || g.getHeight() / 2; let minutes = 0; let seconds = 0; @@ -249,7 +294,10 @@ function drawCounter (currentValue, x, y) { } function init () { - const initState = new InitState(); + device = (process.env.HWVERSION==1 + ? new Bangle1() + : new Bangle2()); + const initState = new InitState(device); initState.go(); } diff --git a/apps/qalarm/ChangeLog b/apps/qalarm/ChangeLog new file mode 100644 index 000000000..135e69d23 --- /dev/null +++ b/apps/qalarm/ChangeLog @@ -0,0 +1,2 @@ +0.01: First version! +0.02: Fixed alarms not working and localised days of week. \ No newline at end of file diff --git a/apps/qalarm/app-icon.js b/apps/qalarm/app-icon.js new file mode 100644 index 000000000..1a014b796 --- /dev/null +++ b/apps/qalarm/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("/wA/AH4A/AH4AF0WiF1wwtF73GB53MAAgkY4wABFqIxPEhQuXGB4vUFxYwMEpBpGBwouNGAwfFF5I1KF6ZQHGAwNLFx4wHF/4v/F/4v/AoYGDF6gaFF5AwHL7QuMBJQvWEpwvxBQ4uRGBAkJT4wuWGBIuIRjKRNF8wwXFy4wWFzIwU53NFzPN5wuR5/PGK4tBDYSNQ5wVCCwIzBAAQoIAAQWGSJ5HFDYYAQIYTCRKRIeBAAYmDAAZsJMCQAbeCAybFiQ0XFTQAIzgAGFcYvz0QAGF84wGF1AwFF1QA/AH4A/ADQ=")) diff --git a/apps/qalarm/app.js b/apps/qalarm/app.js new file mode 100644 index 000000000..64f601bf6 --- /dev/null +++ b/apps/qalarm/app.js @@ -0,0 +1,271 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +let alarms = require("Storage").readJSON("qalarm.json", 1) || []; +/* +Alarm format: +{ + on : true, + t : 23400000, // Time of day since midnight in ms + msg : "Eat chocolate", // (optional) Must be set manually from the IDE + last : 0, // Last day of the month we alarmed on - so we don't alarm twice in one day! + rp : true, // Repeat + as : false, // Auto snooze + hard: true, // Whether the alarm will be like HardAlarm or not + timer : 300, // (optional) If set, this is a timer and it's the time in seconds + daysOfWeek: [true,true,true,true,true,true,true] // What days of the week the alarm is on. First item is Sunday, 2nd is Monday, etc. +} +*/ + +function formatTime(t) { + mins = 0 | (t / 60000) % 60; + hrs = 0 | (t / 3600000); + return hrs + ":" + ("0" + mins).substr(-2); +} + +function formatTimer(t) { + mins = 0 | (t / 60) % 60; + hrs = 0 | (t / 3600); + return hrs + ":" + ("0" + mins).substr(-2); +} + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +function showMainMenu() { + const menu = { + "": { title: "Alarms" }, + "New Alarm": () => showEditAlarmMenu(-1), + "New Timer": () => showEditTimerMenu(-1), + }; + alarms.forEach((alarm, idx) => { + let txt = + (alarm.timer ? "TIMER " : "ALARM ") + + (alarm.on ? "on " : "off ") + + (alarm.timer ? formatTimer(alarm.timer) : formatTime(alarm.t)); + menu[txt] = function () { + if (alarm.timer) showEditTimerMenu(idx); + else showEditAlarmMenu(idx); + }; + }); + menu["< Back"] = () => { + load(); + }; + + if (WIDGETS["qalarm"]) WIDGETS["qalarm"].reload(); + return E.showMenu(menu); +} + +function showEditAlarmMenu(alarmIndex, alarm) { + const newAlarm = alarmIndex < 0; + + if (!alarm) { + if (newAlarm) { + alarm = { + t: 43200000, + on: true, + rp: true, + as: false, + hard: false, + daysOfWeek: new Array(7).fill(true), + }; + } else { + alarm = Object.assign({}, alarms[alarmIndex]); // Copy object in case we don't save it + } + } + + let hrs = 0 | (alarm.t / 3600000); + let mins = 0 | (alarm.t / 60000) % 60; + let secs = 0 | (alarm.t / 1000) % 60; + + const menu = { + "": { title: alarm.msg ? alarm.msg : "Alarms" }, + Hours: { + value: hrs, + onchange: function (v) { + if (v < 0) v = 23; + if (v > 23) v = 0; + hrs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Minutes: { + value: mins, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + mins = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Seconds: { + value: secs, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + secs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Enabled: { + value: alarm.on, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (alarm.on = v), + }, + Repeat: { + value: alarm.rp, + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.rp = v), + }, + "Auto snooze": { + value: alarm.as, + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.as = v), + }, + Hard: { + value: alarm.hard, + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.hard = v), + }, + "Days of week": () => showDaysMenu(alarmIndex, getAlarm()), + }; + + function getAlarm() { + alarm.t = hrs * 3600000 + mins * 60000 + secs * 1000; + + alarm.last = 0; + // If alarm is for tomorrow not today (eg, in the past), set day + if (alarm.t < getCurrentTime()) alarm.last = new Date().getDate(); + + return alarm; + } + + menu["> Save"] = function () { + if (newAlarm) alarms.push(getAlarm()); + else alarms[alarmIndex] = getAlarm(); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + + if (!newAlarm) { + menu["> Delete"] = function () { + alarms.splice(alarmIndex, 1); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + } + menu["< Back"] = showMainMenu; + return E.showMenu(menu); +} + +function showDaysMenu(alarmIndex, alarm) { + const menu = { + "": { title: alarm.msg ? alarm.msg : "Alarms" }, + "< Back": () => showEditAlarmMenu(alarmIndex, alarm), + }; + + for (let i = 0; i < 7; i++) { + let dayOfWeek = require("locale").dow({ getDay: () => i }); + menu[dayOfWeek] = { + value: alarm.daysOfWeek[i], + format: (v) => (v ? "Yes" : "No"), + onchange: (v) => (alarm.daysOfWeek[i] = v), + }; + } + + return E.showMenu(menu); +} + +function showEditTimerMenu(timerIndex) { + var newAlarm = timerIndex < 0; + + let alarm; + if (newAlarm) { + alarm = { + timer: 300, + on: true, + rp: false, + as: false, + hard: false, + }; + } else { + alarm = alarms[timerIndex]; + } + + let hrs = 0 | (alarm.timer / 3600); + let mins = 0 | (alarm.timer / 60) % 60; + let secs = (0 | alarm.timer) % 60; + + const menu = { + "": { title: "Timer" }, + Hours: { + value: hrs, + onchange: function (v) { + if (v < 0) v = 23; + if (v > 23) v = 0; + hrs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Minutes: { + value: mins, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + mins = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Seconds: { + value: secs, + onchange: function (v) { + if (v < 0) v = 59; + if (v > 59) v = 0; + secs = v; + this.value = v; + }, // no arrow fn -> preserve 'this' + }, + Enabled: { + value: alarm.on, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (alarm.on = v), + }, + Hard: { + value: alarm.hard, + format: (v) => (v ? "On" : "Off"), + onchange: (v) => (alarm.hard = v), + }, + }; + function getTimer() { + alarm.timer = hrs * 3600 + mins * 60 + secs; + alarm.t = (getCurrentTime() + alarm.timer * 1000) % 86400000; + return alarm; + } + menu["> Save"] = function () { + if (newAlarm) alarms.push(getTimer()); + else alarms[timerIndex] = getTimer(); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + if (!newAlarm) { + menu["> Delete"] = function () { + alarms.splice(timerIndex, 1); + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + eval(require("Storage").read("qalarmcheck.js")); + showMainMenu(); + }; + } + menu["< Back"] = showMainMenu; + return E.showMenu(menu); +} + +showMainMenu(); diff --git a/apps/qalarm/app.png b/apps/qalarm/app.png new file mode 100644 index 0000000000000000000000000000000000000000..14edf415069c0df4b92316a58719ad356affa395 GIT binary patch literal 1531 zcmVYq`X4DteAXTyb(wC0gV-yH-YTe z9r4F1jgQS2-fWq+5-C*qSHNTeP!AlFr5wu>OcT`&j4n!kSF8cp3|dz)A+!!*v$n=2 z71eHFw5Eyne3zFwj&K~P=OXh;7vQkSn{D;UO%@R4ux|1@dp_%`BcG>PkCDH!z0yt0 zBg$d#ViqX)*_$mY6bH}1OwV|K!1-yE@i;8@iQNOcuzK=&H0sWHb0F8VUaqGC@`C_~ z$I*ko0AZ>pJsxm=#FOs~*uQkM%zI;N%JyjKOvmK;D@kSmW06;gfmE>nF@0NP>%Y|{n+k(KIxO% z>>^k?v0Tx7h%ot+iwFJeRPKEVHo#DR zL|bcSVHmrC+MA+QIXZEiMp>9UREcp#1!(KA(HPkSz{CMjs~rE3i_DeBZ(Qilu-^jo z&~pjm<_S7}OOid`pLsZlj)9>Mo~7HqPafKmDQ6lfrvxA|VmFC~BtvII*BR*i6EY{k zx*8f^m&r&N(g3wnA(jM4VxHU$%r56vb_KL8-iTSdgW>aqVfTZ?dX-iOWY0nF667v} za|5glXa<}dI9>i=MjVV9FcM%Uz^sMHD5x3j5;hB-sM$U^3rI{+f} z5WN#3bx<=B%sPl9+*OhYh}pYh+6`)hTJGG+fPDpWSHZdh+5f<5FU7(tN3nbXR*APHKJN}X*n5s|1Nig;_LHaIE>+HIJeMzkHI9XJPi3OI#u zg!9=G!H2cb3+NBqS0)^%#6y`O=%P4D&7{qOk{{g3Sz02%OoB#j-002ovPDHLkV1h(U(Ov)m literal 0 HcmV?d00001 diff --git a/apps/qalarm/boot.js b/apps/qalarm/boot.js new file mode 100644 index 000000000..6713ad9e1 --- /dev/null +++ b/apps/qalarm/boot.js @@ -0,0 +1 @@ +eval(require("Storage").read("qalarmcheck.js")); diff --git a/apps/qalarm/qalarm.js b/apps/qalarm/qalarm.js new file mode 100644 index 000000000..6b31ba645 --- /dev/null +++ b/apps/qalarm/qalarm.js @@ -0,0 +1,153 @@ +// This file shows the alarm + +function formatTime(t) { + let hrs = Math.floor(t / 3600000); + let mins = Math.round((t / 60000) % 60); + return hrs + ":" + ("0" + mins).substr(-2); +} + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +function getRandomInt(max) { + return Math.floor(Math.random() * Math.floor(max)); +} + +function getRandomFromRange( + lowerRangeMin, + lowerRangeMax, + higherRangeMin, + higherRangeMax +) { + let lowerRange = lowerRangeMax - lowerRangeMin; + let higherRange = higherRangeMax - higherRangeMin; + let fullRange = lowerRange + higherRange; + let randomNum = getRandomInt(fullRange); + if (randomNum <= lowerRangeMax - lowerRangeMin) { + return randomNum + lowerRangeMin; + } else { + return randomNum + (higherRangeMin - lowerRangeMax); + } +} + +function showNumberPicker(currentGuess, randomNum) { + if (currentGuess == randomNum) { + E.showMessage("" + currentGuess + "\n PRESS ENTER", "Get to " + randomNum); + } else { + E.showMessage("" + currentGuess, "Get to " + randomNum); + } +} + +function showPrompt(msg, buzzCount, alarm) { + E.showPrompt(msg, { + title: alarm.timer ? "TIMER!" : "ALARM!", + buttons: { Sleep: true, Ok: false }, // default is sleep so it'll come back in 10 mins + }).then(function (sleep) { + buzzCount = 0; + if (sleep) { + if (alarm.ohr === undefined) alarm.ohr = alarm.t; + alarm.t += 10 / 60; // 10 minutes + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + load(); + } else { + alarm.last = new Date().getDate(); + if (alarm.ohr !== undefined) { + alarm.t = alarm.ohr; + delete alarm.ohr; + } + if (!alarm.rp) alarm.on = false; + require("Storage").write("qalarm.json", JSON.stringify(alarms)); + load(); + } + }); +} + +function showAlarm(alarm) { + if ((require("Storage").readJSON("setting.json", 1) || {}).quiet > 1) return; // total silence + let msg = formatTime(alarm.t); + let buzzCount = 20; + if (alarm.msg) msg += "\n" + alarm.msg + "!"; + + if (alarm.hard) { + let okClicked = false; + let currentGuess = 10; + let randomNum = getRandomFromRange(0, 7, 13, 20); + showNumberPicker(currentGuess, randomNum); + setWatch( + (o) => { + if (!okClicked && currentGuess < 20) { + currentGuess = currentGuess + 1; + showNumberPicker(currentGuess, randomNum); + } + }, + BTN1, + { repeat: true, edge: "rising" } + ); + + setWatch( + (o) => { + if (currentGuess == randomNum) { + okClicked = true; + showPrompt(msg, buzzCount, alarm); + } + }, + BTN2, + { repeat: true, edge: "rising" } + ); + + setWatch( + (o) => { + if (!okClicked && currentGuess > 0) { + currentGuess = currentGuess - 1; + showNumberPicker(currentGuess, randomNum); + } + }, + BTN3, + { repeat: true, edge: "rising" } + ); + } else { + showPrompt(msg, buzzCount, alarm); + } + + function buzz() { + Bangle.buzz(500).then(() => { + setTimeout(() => { + Bangle.buzz(500).then(function () { + setTimeout(() => { + Bangle.buzz(2000).then(function () { + if (buzzCount--) setTimeout(buzz, 2000); + else if (alarm.as) { + // auto-snooze + buzzCount = 20; + setTimeout(buzz, 600000); // 10 minutes + } + }); + }, 100); + }); + }, 100); + }); + } + buzz(); +} + +let time = new Date(); +let t = getCurrentTime(); +let alarms = require("Storage").readJSON("qalarm.json", 1) || []; + +let active = alarms.filter( + (alarm) => + alarm.on && + alarm.t < t && + alarm.last != time.getDate() && + (alarm.timer || alarm.daysOfWeek[time.getDay()]) +); + +if (active.length) { + showAlarm(active.sort((a, b) => a.t - b.t)[0]); +} diff --git a/apps/qalarm/qalarmcheck.js b/apps/qalarm/qalarmcheck.js new file mode 100644 index 000000000..9a3f10d5e --- /dev/null +++ b/apps/qalarm/qalarmcheck.js @@ -0,0 +1,41 @@ +/** + * This file checks for upcoming alarms and schedules qalarm.js to deal with them and itself to continue doing these checks. + */ + +print("Checking for alarms..."); + +clearInterval(); + +function getCurrentTime() { + let time = new Date(); + return ( + time.getHours() * 3600000 + + time.getMinutes() * 60000 + + time.getSeconds() * 1000 + ); +} + +let time = new Date(); +let t = getCurrentTime(); + +let nextAlarms = (require("Storage").readJSON("qalarm.json", 1) || []) + .filter( + (alarm) => + alarm.on && + alarm.t > t && + alarm.last != time.getDate() && + (alarm.timer || alarm.daysOfWeek[time.getDay()]) + ) + .sort((a, b) => a.t - b.t); + +if (nextAlarms[0]) { + setTimeout(() => { + eval(require("Storage").read("qalarmcheck.js")); + load("qalarm.js"); + }, nextAlarms[0].t - t); +} else { + // No alarms found: will re-check at midnight + setTimeout(() => { + eval(require("Storage").read("qalarmcheck.js")); + }, 86400000 - t); +} diff --git a/apps/qalarm/widget.js b/apps/qalarm/widget.js new file mode 100644 index 000000000..f80aff653 --- /dev/null +++ b/apps/qalarm/widget.js @@ -0,0 +1,22 @@ +WIDGETS["qalarm"] = { + area: "tl", + width: 0, + draw: function () { + if (this.width) + g.reset().drawImage( + atob( + "GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA" + ), + this.x, + this.y + ); + }, + reload: function () { + WIDGETS["qalarm"].width = ( + require("Storage").readJSON("qalarm.json", 1) || [] + ).some((alarm) => alarm.on) + ? 24 + : 0; + }, +}; +WIDGETS["qalarm"].reload(); diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog new file mode 100644 index 000000000..2ea6e9fa8 --- /dev/null +++ b/apps/recorder/ChangeLog @@ -0,0 +1,4 @@ +0.01: New App! +0.02: Use 'recorder.log..' rather than 'record.log..' + Fix interface.html +0.03: Fix theme and maps/graphing if no GPS diff --git a/apps/recorder/README.md b/apps/recorder/README.md new file mode 100644 index 000000000..ba53a99f2 --- /dev/null +++ b/apps/recorder/README.md @@ -0,0 +1,27 @@ +# Recorder + +![icon](app.png) + +This app allows you to record data every few seconds - it can run in background. + +Usually you'd record GPS (but this is not required). The data can later be exported as CSV, KML or GPX files via the Download button in the Bangle.js App Store entry for Recorder. + +## Usage + +First run the `Recorder` app, here you can configure what you want to record, how often, +and you can start and stop recordings. + +You can record + +* **Time** The current time +* **GPS** GPS Latitude, Longitude and Altitude +* **Steps** Steps counted by the step counter +* **HR** Heart rate + +**Note:** It is possible for other apps to record information using this app +as well. They need to define a `foobar.recorder.js` file - see the `getRecorders` +function in `widget.js` for more information. + +## Tips + +When recording GPS, it usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). There is a grey satellite symbol, which you will see turn red when you get an actual GPS Fix. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix. diff --git a/apps/recorder/app-icon.js b/apps/recorder/app-icon.js new file mode 100644 index 000000000..4181d2b12 --- /dev/null +++ b/apps/recorder/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cA///vPWvN8kvkuu14/s3OMjN0Kf4AQ2vaCB0Ftu3oARfgdt23AGsO2NaHQT+MB2XJCJ1MyXJsAQMgUky3JkARMjIRBpIRNvMl2VJlAQLldvtmSpN+CJcbt2ECJsBregggRBv4RRdJfbgEkyVLq3ACJ1Jq3dCBMKBYIRBpFW7d0CJUDsgRBhdbtvQCJHYgUTm1IgEttuwCI8GCIMSpMwA4MVEZPoCIUkaxj7BoQRPiQRCwARNpARByARNpMJCJyNBgKjBCJy1CCJ79BfZYNDCJoxBBoQnCABBVFN4IRJPIoRLV4sCpMgCJTbECJYKFCJUJBQsJfpoA/A")) diff --git a/apps/recorder/app-settings.json b/apps/recorder/app-settings.json new file mode 100644 index 000000000..4a3117a17 --- /dev/null +++ b/apps/recorder/app-settings.json @@ -0,0 +1,6 @@ +{ + "recording":false, + "file":"record.log0.csv", + "period":10, + "record" : ["gps"] +} diff --git a/apps/recorder/app.js b/apps/recorder/app.js new file mode 100644 index 000000000..d29959e25 --- /dev/null +++ b/apps/recorder/app.js @@ -0,0 +1,412 @@ +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var settings; + +var osm; +try { // if it's installed, use the OpenStreetMap module + osm = require("openstmap"); +} catch (e) {} + +function loadSettings() { + settings = require("Storage").readJSON("recorder.json",1)||{}; + var changed = false; + if (!settings.file) { + changed = true; + settings.file = "recorder.log0.csv"; + } + if (!Array.isArray(settings.record)) { + settings.record = ["gps"]; + changed = true; + } + if (changed) + require("Storage").writeJSON("recorder.json", settings); +} +loadSettings(); + +function updateSettings() { + require("Storage").writeJSON("recorder.json", settings); + if (WIDGETS["recorder"]) + WIDGETS["recorder"].reload(); +} + +function getTrackNumber(filename) { + return parseInt(filename.match(/^recorder\.log(.*)\.csv$/)[1]||0); +} + +function showMainMenu() { + function boolFormat(v) { return v?"Yes":"No"; } + function menuRecord(id) { + return { + value: settings.record.includes(id), + format: boolFormat, + onchange: v => { + settings.recording = false; // stop recording if we change anything + settings.record = settings.record.filter(r=>r!=id); + if (v) settings.record.push(id); + updateSettings(); + } + }; + } + const mainmenu = { + '': { 'title': 'Recorder' }, + '< Back': ()=>{load();}, + 'RECORD': { + value: !!settings.recording, + format: v=>v?"On":"Off", + onchange: v => { + setTimeout(function() { + E.showMenu(); + WIDGETS["recorder"].setRecording(v).then(function() { + print("Complete"); + loadSettings(); + print(settings.recording); + showMainMenu(); + }); + }, 1); + } + }, + 'File #': { + value: getTrackNumber(settings.file), + min: 0, + max: 99, + step: 1, + onchange: v => { + settings.recording = false; // stop recording if we change anything + settings.file = "recorder.log"+v+".csv"; + updateSettings(); + } + }, + 'View Tracks': ()=>{viewTracks();}, + 'Time Period': { + value: settings.period||10, + min: 1, + max: 120, + step: 1, + format: v=>v+"s", + onchange: v => { + settings.recording = false; // stop recording if we change anything + settings.period = v; + updateSettings(); + } + } + }; + var recorders = WIDGETS["recorder"].getRecorders(); + Object.keys(recorders).forEach(id=>{ + mainmenu["Log "+recorders[id]().name] = menuRecord(id); + }); + return E.showMenu(mainmenu); +} + + + +function viewTracks() { + const menu = { + '': { 'title': 'Tracks' } + }; + var found = false; + require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).forEach(filename=>{ + found = true; + menu["Track "+getTrackNumber(filename)] = ()=>viewTrack(filename,false); + }); + if (!found) + menu["No Tracks found"] = function(){}; + menu['< Back'] = () => { showMainMenu(); }; + return E.showMenu(menu); +} + +function getTrackInfo(filename) { + "ram" + var minLat = 90; + var maxLat = -90; + var minLong = 180; + var maxLong = -180; + var starttime, duration=0; + var f = require("Storage").open(filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + var fields, timeIdx, latIdx, lonIdx; + var nl = 0, c, n; + if (l!==undefined) { + fields = l.trim().split(","); + timeIdx = fields.indexOf("Time"); + latIdx = fields.indexOf("Latitude"); + lonIdx = fields.indexOf("Longitude"); + l = f.readLine(f); + } + if (l!==undefined) { + c = l.split(","); + starttime = parseInt(c[timeIdx]); + } + // pushed this loop together to try and bump loading speed a little + while(l!==undefined) { + ++nl;c=l.split(",");l = f.readLine(f); + if (c[latIdx]=="")continue; + n = +c[latIdx];if(n>maxLat)maxLat=n;if(nmaxLong)maxLong=n;if(nylen ? screenSize/xlen : screenSize/ylen; + return { + fn : getTrackNumber(filename), + fields : fields, + filename : filename, + time : new Date(starttime*1000), + records : nl, + minLat : minLat, maxLat : maxLat, + minLong : minLong, maxLong : maxLong, + lat : (minLat+maxLat)/2, lon : (minLong+maxLong)/2, + lfactor : lfactor, + scale : scale, + duration : Math.round(duration) + }; +} + +function asTime(v){ + var mins = Math.floor(v/60); + var secs = v-mins*60; + return ""+mins.toString()+"m "+secs.toString()+"s"; +} + +function viewTrack(filename, info) { + if (!info) { + E.showMessage("Loading...","Track "+getTrackNumber(filename)); + info = getTrackInfo(filename); + } + //console.log(info); + const menu = { + '': { 'title': 'Track '+info.fn } + }; + if (info.time) + menu[info.time.toISOString().substr(0,16).replace("T"," ")] = function(){}; + menu["Duration"] = { value : asTime(info.duration)}; + menu["Records"] = { value : ""+info.records }; + if (info.fields.includes("Latitude")) + menu['Plot Map'] = function() { + info.qOSTM = false; + plotTrack(info); + }; + if (osm && info.fields.includes("Latitude")) + menu['Plot OpenStMap'] = function() { + info.qOSTM = true; + plotTrack(info); + } + if (info.fields.includes("Altitude")) + menu['Plot Alt.'] = function() { + plotGraph(info, "Altitude"); + }; + menu['Plot Speed'] = function() { + plotGraph(info, "Speed"); + }; + // TODO: steps, heart rate? + menu['Erase'] = function() { + E.showPrompt("Delete Track?").then(function(v) { + if (v) { + settings.recording = false; + updateSettings(); + var f = require("Storage").open(filename,"r"); + f.erase(); + viewTracks(); + } else + viewTrack(n, info); + }); + }; + menu['< Back'] = () => { viewTracks(); }; + return E.showMenu(menu); +} + +function plotTrack(info) { + "ram" + + function distance(lat1,long1,lat2,long2) { "ram" + var x = (long1-long2) * Math.cos((lat1+lat2)*Math.PI/360); + var y = lat2 - lat1; + return Math.sqrt(x*x + y*y) * 6371000 * Math.PI / 180; + } + + // Function to convert lat/lon to XY + var getMapXY; + if (info.qOSTM) { + getMapXY = osm.latLonToXY.bind(osm); + } else { + getMapXY = function(lat, lon) { "ram" + return {x:cx + Math.round((long - info.lon)*info.lfactor*info.scale), + y:cy + Math.round((info.lat - lat)*info.scale)}; + }; + } + + E.showMenu(); // remove menu + E.showMessage("Drawing...","Track "+info.fn); + g.flip(); // on buffered screens, draw a not saying we're busy + g.clear(1); + var s = require("Storage"); + var W = g.getWidth(); + var H = g.getHeight(); + var cx = W/2; + var cy = 24 + (H-24)/2; + if (!info.qOSTM) { + g.setColor("#f00").fillRect(9,80,11,120).fillPoly([9,60,19,80,0,80]); + g.setColor(g.theme.fg).setFont("6x8").setFontAlign(0,0).drawString("N",10,50); + } else { + osm.lat = info.lat; + osm.lon = info.lon; + osm.draw(); + g.setColor("#000"); + } + var latIdx = info.fields.indexOf("Latitude"); + var lonIdx = info.fields.indexOf("Longitude"); + g.drawString(asTime(info.duration),10,220); + var f = require("Storage").open(info.filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + l = f.readLine(f); // skip headers + var ox=0; + var oy=0; + var olat,olong,dist=0; + var i=0, c = l.split(","); + // skip until we find our first data + while(l!==undefined && c[latIdx]=="") { + c = l.split(","); + l = f.readLine(f); + } + // now start plotting + var lat = +c[latIdx]; + var long = +c[lonIdx]; + var mp = getMapXY(lat, long); + g.moveTo(mp.x,mp.y); + g.setColor("#0f0"); + g.fillCircle(mp.x,mp.y,5); + if (info.qOSTM) g.setColor("#f09"); + else g.setColor(g.theme.fg); + l = f.readLine(f); + g.flip(); // force update + while(l!==undefined) { + c = l.split(",");l = f.readLine(f); + if (c[latIdx]=="")continue; + lat = +c[latIdx]; + long = +c[lonIdx]; + mp = getMapXY(lat, long); + g.lineTo(mp.x,mp.y); + if (info.qOSTM) g.fillCircle(mp.x,mp.y,2); // make the track more visible + var d = distance(olat,olong,lat,long); + if (!isNaN(d)) dist+=d; + olat = lat; + olong = long; + ox = mp.x; + oy = mp.y; + if (++i > 100) { g.flip();i=0; } + } + g.setColor("#f00"); + g.fillCircle(ox,oy,5); + if (info.qOSTM) g.setColor("#000"); + else g.setColor(g.theme.fg); + g.drawString(require("locale").distance(dist),120,220); + g.setFont("6x8",2); + g.setFontAlign(0,0,3); + g.drawString("Back",230,200); + setWatch(function() { + viewTrack(info.fn, info); + }, global.BTN3||BTN1); + Bangle.drawWidgets(); + g.flip(); +} + +function plotGraph(info, style) { + "ram" + E.showMenu(); // remove menu + E.showMessage("Calculating...","Track "+info.fn); + var filename = info.filename; + var infn = new Float32Array(80); + var infc = new Uint16Array(80); + var title; + var lt = 0; // last time + var tn = 0; // count for each time period + var strt, dur = info.duration; + var f = require("Storage").open(filename,"r"); + if (f===undefined) return; + var l = f.readLine(f); + l = f.readLine(f); // skip headers + var nl = 0, c, i; + var timeIdx = info.fields.indexOf("Time"); + if (l!==undefined) { + c = l.split(","); + strt = c[timeIdx]; + } + if (style=="Altitude") { + title = "Altitude (m)"; + var altIdx = info.fields.indexOf("Altitude"); + while(l!==undefined) { + ++nl;c=l.split(",");l = f.readLine(f); + if (c[altIdx]=="") continue; + i = Math.round(80*(c[timeIdx] - strt)/dur); + infn[i]+=+c[altIdx]; + infc[i]++; + } + } else if (style=="Speed") { + title = "Speed (m/s)"; + var latIdx = info.fields.indexOf("Latitude"); + var lonIdx = info.fields.indexOf("Longitude"); + // skip until we find our first data + while(l!==undefined && c[latIdx]=="") { + c = l.split(","); + l = f.readLine(f); + } + // now iterate + var p,lp = Bangle.project({lat:c[1],lon:c[2]}); + var t,dx,dy,d,lt = c[timeIdx]; + while(l!==undefined) { + ++nl;c=l.split(","); + t = c[timeIdx]; + i = Math.round(80*(t - strt)/dur); + p = Bangle.project({lat:c[latIdx],lon:c[lonIdx]}); + dx = p.x-lp.x; + dy = p.y-lp.y; + d = Math.sqrt(dx*dx+dy*dy); + if (t!=lt) { + infn[i]+=d / (t-lt); // speed + infc[i]++; + } + lp = p; + lt = t; + l = f.readLine(f); + } + } else throw new Error("Unknown type "+style); + var min=100000,max=-100000; + for (var i=0;i0) infn[i]/=infc[i]; + var n = infn[i]; + if (n>max) max=n; + if (n 8) { + grid*=2; + } + // draw + g.clear(1).setFont("6x8",1); + var r = require("graph").drawLine(g, infn, { + x:4,y:24, + width: g.getWidth()-24, + height: g.getHeight()-(24+8), + axes : true, + gridy : grid, + gridx : 50, + title: title, + xlabel : x=>Math.round(x*dur/(60*infn.length))+" min" // minutes + }); + g.setFont("6x8",2); + g.setFontAlign(0,0,3); + g.drawString("Back",230,200); + setWatch(function() { + viewTrack(info.filename, info); + }, global.BTN3||BTN1); + g.flip(); +} + +showMainMenu(); diff --git a/apps/recorder/app.png b/apps/recorder/app.png new file mode 100644 index 0000000000000000000000000000000000000000..036f5d132181624c52b034f07732d39a958903fa GIT binary patch literal 1530 zcmV*0sE5goeq8BC!bd2RBEJ`$A<#X(ZKv9FiT; z;*uCW9uG-1z*)G#XmK+iJsuB9HDGbtcSno!F6%C%G?KpxeYCJb^aaW#QF54h^C0iC z?n+;KsxsNSqx@eO{sNjj4VHs@}e3_BNyS3^mYDRek&q zdCnpMw^zxDbO|@u3@`#x<&Ng^t;?S_Qqycik-EByl;E{V|D2a+yGgUKStx)WHU$7c z4DeD$p%Hpo5?)4+)ofeVbuGL9FXV+a4*+BEH$s|UgE$)wytBPDQB5rg8Ss8sQ1-|` z^HDPiVUJ=ebz?kTK_`1KY`&~0@c3QZ6VZ$$ZUenF^S|=-`l=y`rYA#t;Uo`u2$)0{ zWgkn!O*U;rjvy$wE@WmU)NurH8Sv~Ycq`!XF9Qu-JJ64(_|I<4;f}59U^7A#DvKnD?%@Kw zsebsL!j{I$v%=?JlU9W_=~b9U7wr8XKUSc>-I3@v;PrN_BxUH~=gcO>U3%iQdXE@p z#>x{+X$%o;~@$c|Tp4ueS#+`hNkLR;XjnX6aJLW!3F20Pd(s0EnT% z;;?)K?F)eBH1ak{tjCr;XsuBuvdz0090V!_`i%Pg{eP)Q13+11R_IE7%zFi`&FNXo5A> z-j;L&Hka*0&>LK2b=zAV8@}&Q|@N<}sL>`8=#CQB5rg8z7>U5uw4N;^NwzoSZ)at^^F>Z#P2z zg|lZ?z#tMb(A3mWN(^CNE{f*QIx8=)k$^WM^Ayl0iRfyH+dyMuV?JcIPrGCipt!xh zE!uh6bMoH>P#=~|7i+E=m`rl zrGkhOJ{8fw8znLcgU&Vp_V74P78Mt7yhGmlh>-o<*)ws!fmL)HINEslClID+W5jU% z*YoEezf)fJ+<6TEyR3~d!rJ!sc9-5BwCFO>c=&KNBNq836|gz{xfK-^(tqXiob-Iy zlEh#R_InQK=^+wb1{lN3+G_$7#l$TsEPUbS-Q+)`Yh#C(dn28QBW*_XK$t7s*~7$QkMaOsS*q_7?VJ#EGjOp)hn2tz4!_Py$*r|KxPUI gJ*gXIlzWYT0cWfgM6O1!vj6}907*qoM6N<$f+S4TzyJUM literal 0 HcmV?d00001 diff --git a/apps/recorder/interface.html b/apps/recorder/interface.html new file mode 100644 index 000000000..ad0de4887 --- /dev/null +++ b/apps/recorder/interface.html @@ -0,0 +1,255 @@ + + + + + +

1F<(MX1MLaf6yc+aPvu45C(a}*KAVxz&TL3$!A|ZLVmd*oZ zI@Zs;J6%l~1BnEj<*t?|7x z_Zh+K77LHSnSYf!y~Dh4i2=v$?^dyi#d zbr^rV^i#S6miR+Q_Svladp>f;N^#R+4eBp5Go3kPoosE`%c7l0YahztykYjf(J(T` zgoeU@Yjvps>g^Ed7`BODpNgO`#8;mm*C^cQ80|Q@hQiCL61!9JHr5R`nv()H3N=-gpK0-gHc?+E;YAGV4_-as|tvr|?cq%$6featT_QwJO5ylF>-j8jbyBGs3qt>7c=HU1^L6X>0bx>JlM-~2J z98yY;i!{w%TtJV}r#-p#UyjeBMK{qcPO?yf?*My1o;@vwTk22YciBL z005xH? zA{ZCysAN{%tZMH(c<=AFJnql?hzsiKR6t?N$m982Ig*$ut|-$@`r+|$=kc^H>FwP* zJF9L5E5m+YF);me8nwC(2S8CXYOU0Ny>;NPbPa5hi%W#|kH>pooWyJUS;R8MjUi%A zDMLNts4_{mcd%n($)s#JYR}yA|3EMNL3e#U4aO7N_It17H5l`Yb30KhI#qi6Zo<*k z;GZgP{em$Ey2;F-+NN{MPGj411qL>@jG9`)vd5*|QJaHwkK8V{2#}DF^tv8gkGFgS{bz=u7W%8RS?lXg z)h$_OSvDI3RM^$K8gNXPM=Gq zU@ECiFn4{eq@z0;@{2Q5qx!!ep<(x3nbgI@ci!{vW9!sob*GPw;w_6h6>g_!XrE9% zrFLAIhRWD3XK=HO9(PmNg=uYebIRMv5g7JCbA_`s{r7GP3hk2;^asV$KaFbNEZmzT z_xwKGdI~o0d75S3-s`u;Ihab$g-aAwTHVsSr@y|Q^R53TBlu(Rj%ZCxfR5wx$q6!@ zTXVPz3AsEA`UjEGty7AaxJljJ-o{xwByM6tQTDYWmzE1#+%^85OJtg5-1OLw=3L6<9o7)RH(h5%K}zc zhY*pG>&z!a;1Sly7{Bwfu;79x@Hwnp8lF$i0`T6~O6`H2p_+Zi&g}u6?Y+IYxH!MB zWjjE&0i99c?5u-Eg$}*O89MlT5)uPWC8ci!{4UWAR&$08zs3P17m68$?ry;q*lwo3 ziOFI2m9e;idau$*|05KKUoVX4<)>N*?Sb`>|N8yfugz$PPj;0LZRquoYI@eWyoAgK z8@B)4kvjYoglL3PX)RBlDN~=CpMR^UH#(ce#4*<$3zgL{`+cDi{$}x>rE*wtVkPI* zm>-L2q$OjcLw@HXiV93lRxF>zqowt&*4TuD!`)r_VFZEj{Zage{{fMG3{qA$8AU`p zJ;1mwgvXMhnK+^|1gF?Z4^<%72i5DQ4gwARBd~CxVPOU4Qgc6h#Tpt`&&(M)a`z*` zbr{yz(u|3nn|{>hgXik0?cXEqEHiO7W;P<5iBBry<{;>Q>KEpe`$(m+*SDu~HxMyl zu5<0-HXtrllaieLZg`iPJ(Wefa=?VIhS@G^v2xDLc zq7n=Cm#A!9-TeN5VAZskv~*0#$(cAYp;W&o%A!VP5K@bR78}yv)Iml=0~gfgqpT@8 zUjhTOg&xeDWblz&vH&wTFMFUa`dw5)^*rRXhHw=$i%0^rp-5lb&^6yDPPoA%8Qf?_ z5z$K=0s_4)fu z9Nl~MwgZntJv7%;zEy5PK>$FYN=uJa)n;PirzK=$pylLv7Z-hjF{hxVC7~$Oa=l2f zqKTD-4kD+FE;Tk5UPMGBnT71Xy{sQbgYnyR?HB+uCBNuI7%A6G5HmHUoSC`q?j7ws zMea~m>GHpEekd*T`H-7y3R=}KT3Y)}lmr40=vc}eg}nzyWGoYsDk=bC_F0~^+&xK^ zpzIqhT)`e*=#XcfBB^cs_-|vXQfgM0Q51VutgJzKVOlILS8-`2qU>*0Hz#ofrV|dL z5ft=7iB%=#_ekujh0f@zu&V-*s0PfLGiFpqYzhhr?hJVx+z$5Xfz6(hU=7f-1D#k= zPcLf1yxTZc;@h)Kea~3Ys)~%99Om(Hp6b^`@ck3Avf9J6R)md+_^9fnHlAr(;G7Q+ ztS%maF225I=V}~ZUY2!r{cK|cH$B&KP>CySAyd#?i*98ES??$`U=qBqui->Id$Lx` z{uyb_7PFzD;p*h1;0m^&{)r-Z*A(*O$76Ex;jSH4h|lzz`OMt&vgHz@Uo~*RCnF{i z>7MOQM=RC1fYVwPx=7`5bQH2vIH|d6ZBy9X95OmexjUvHR`rvWlT!jX2ak`B&G8$c zTJZd|7BQ~mh>u{ z=N$cF#d|#)tI_^qWrg_hBRmj$*4A+54;%*{i9J5GIG-l^_`GWdj1oC%t{5q)^!DG! zlQ{6uLW9+9g3CBf+v~H0kBELI|o!HwoZ#_T=(g8*lxJ?9`a~pLN z-A|vJHRuz|=COjhn8pN|>@V_3BO)SL-EXYl6f~ng4A*-T)W#(y;3YZXR_@!!o)jM( z!fbX?iU}e@!^OK80+vn&V$XVUYtA=y?&V$Vi;DokHcFS)*sB|E zn4-SZq$RO@Wu<(;q)HN@;u76}Kc=m9DkC-WYU8IPHNz19havtoSSo0ZNfOkeD5z_g zViDA#!EIV%vthZaL-&_^JD_Gt0;lLgjZu*G(l0p)Ny(j(BBHq5+&X~Kky*_##*B{2 z^LX4vgIN*&=I!GSP`09%mqkJOLUTCF5SN;2NkdB;`z8&({VnK@25*j+x-A>PN6SyL zH9pJhnpdjcZs{b97aIZc@RKC*!V_MxV+e`mZETB^@i% zRla(`KdKS-Gg60imFzNED(LwS7c0`8~VM z&0)}ufiYjtzZnZeg3|iyuAyO=gM;sJ@E*dU5q51?oGhJBnxMMSKOhN+ zy1Hgp3ISs%BvHPc*Ckx@x`eqCYUtJIgA{dM7C0b&(Z zTIU}Ntbd0b9c>?mzp=7%X7hX#Ju42n+8F{(=o^QAc4iCje7f7a6>#+tdCG_#bR=Uu z?l<%#BssFF>?t}e&NVLQ%AnU=^V0g_^#VyP%`q_{kjSVrxX~4q!fnTzlVgB^g=G(5 zjftr#QT4TVXx`fWFKqB=&hIGlLK#|MrL;7K%8Jr1)#Qwya-+8}*M}KQ2JVNpSqE86 zCyfycyIe!JxQdL9X@%E5M@}y^{va-mMtM3ouwJPP0&U8Mee<2O9zzg9rjQX+9uzmR zErSMfM~QX#2)M>Yk}6XMqf%qcf8Il1(FEyuRcb<$imOQw3CW(9CawxB(w&H5myB z7~l!(IlN{)U9{q{U5A(|l!nNhcXYdx3nH9Vt&lw5G_6GK!nX$`!_{)vl|L{v6?{> z1c)kjF0M=9)H7tql1hMlHZQi%HB@4p`Z&jR4! zbfNZ*{)sk8yBVOkv_fFLgDxVVLAGS=?Lq zQ>1(jX8HQ8K1O04Q%U~~+DMjhY@Yk0<8Z@tZ{EV{!#i)u$iqaz>Igxc%xkZ6*D_N55;m6xPR-`GE&xs4N|ek(j_dfZsuC$hfKW#n?RyLUF%g4h z1m2Hi?7D^U;B0Dm&5F#+z0c*}@+b{W@iM1X5#_1wJODfrF0tkEu*7r5MSDe&NV14; zTl@#{L7<($FT%ll0#b4d?GhPu z+frk;6iaX}?mxi8|Gx0>)vVaNvf$$SlOx=y;paNum6e1WTq8?CPw!t&&xaL;T!aop zm+y`E8S+8W1tJwU0Bsj8o`F9&fDZ{ly}F@Xzm8-T-l81X>M&s6g#GvI#VCo_WRi-^ zK$|}LdNd@!KOAjITj)B*cBF0)N9^;^cSXPxdRloqZeH1A717?btuRg;^4g^aMf6Cq zs*6Bd*&|6U$6k{)EpKG&(`ygdFh`}~7fjuygBIV$~O+nFNY^Go~mZ_Q909(D8 z%1z|KoYm}rp2DTjbFl+%I1wkRpn&rICluRmKKgkVX_Ek=F1+tZl0hfAhB=u{OaL&n zDypCd%VYO(%;le_WtC{KQ=#ul#?>$PAMwH(Fm6q z92y!J90a7xTb@EZk3+F`9ZXlNt5}LGMx{$&yu#MLcX<}DP3{h-h=Ohu0Da94Du7%- z2RLyEi9jZNMkLL`0!TXmL@#)v+}2itEfcaB%sJbOZNmJ*(T+K;n8>)TY=kYl;96O7 zPG?>PepFdSMb49Q`oG~}Mi;{=^qTD5odJ2nDf}r76cpMA|sOrtc+7d z3U8pZre-f{o5LPujX@X7?r4PRDnDV_S2Z?wXU;Gz=sJC2oH$Ck&5NBOC}`-MxkFdr z1qFCZ=hpf7)Ku5e0#CWd#PHKSEgCvo|Cl-|eww_s5QI-dgKQRO1gM`z7mt}uNX?mI zlg%mV>BAlNMp+Cxp?E9*BqGQpG8^?;_M&tX1ul{$2B zbcZGq6BOSX{@tSCTEu_`H2UqgUn;Cz^V&UyB~OY{p)$S!pHoU&D{XjWB;n_(t7N)- z(HnVTzuj+VVRIbgesUioo4HKQ!_!l1ze7w;+R2E3?ZtqAx^2(=f;-qFPj&BI&JA&U$gAqKO<82X0^SA^ani$s8x1ve^qj8y|B6HaKqvlzMV7u@&*p!) z+X4lXNwecYEdR^h7LZcO-2cv#X{|1hel1heb=|?_VX;b_HX#+%2y&Dak{`)r0I!Jc z*YPO~n<>%ta`%g&VQ#tZA5Cd#>HMZ9jZBY=FK=4pkH7a4Z+31_9bD`QzJ3EVKMgvS zd_3T$)KXJZ!7wfF%^?o}khqaFuA!Ib$2a*8ysz(Q=*mFtD7}+nmKpQ%;wc>aC~{|9JiOQld@kwH6gJk9QfaTJE#kK~nSjS$3?NyQmJOPq zETL_7*pt?h4z<>(lc24u%@4+{4OK%EkEhr70Y74BG-VnTY}9C!>VuQ+rwNI_I0wLj z^afA0>Bzg2pssuWa?oyKVp7}EkdQQbu%3XE9lFw@@~&woah9&R$+Pt?H;8zZGvwAv zxrx&-{7a!JEgc=O;NfwmMRars1ODyq;gB*n_i8xvbBy2isaPe*%iCp zV-n%n6$Xy+9QV;XBc6No9oO z)+=atw;i%_d>?T;Q}>Wn+um-ZlpOw7Io)epOWzjY=W#ErjM$*Vor8 zf(th=ARz`E4c*srV4l}CEM18lJ;>)^1056|SeO07>~Zm`U(EI}@bEL<0Q-alaVsl2 z&5FdP5cCr1FC+_w!Yk8afR0uvlHVC_hk#_n7R*csIXoXZwYUok;2YPx`1GPYA_9qE zXh_3gElinsMd0q&&J>9+S=zRKVi@hP@EaTFU#|Oc45#A?7Y>Lin@}826)GvJbOnCI z|I5W&y=Ypu!AR??J_xkco?kY?{y~p$$hUPiS}0btO%NR4e?mA1K`LbXsPQsO{s`UAkvPN;lvo3=J^(`J-f|^-BqAowC%5XlLJS%s$w06X0M# zpSvBL|GraUzH-&hcllGUjFIll7v|{ol6~Q`1Z`@2w@gihOo^JV?x>wL7CsddB76lN z5Xeef*9WiXGZ&_-1PZ|YrS1m z3T9?h!0&oCGk^dPzz;}BFgR7EwmU_+)HsMRzy|Hr_DY-DYt9a8_p06(q#(#F%?Q1s zuJ4WL-0~FdI~NcnO)I*<*vI3zKi*_oKBMa@K}9Lvaq7I)f)>`I%}80II*j}QD9(A* zZmqf`%*@f?)lj+=5ZR;q4Jyqrxb|#S0uf-5Jnn4Q+e6TqbdToka$w$@TRVb?C?9tA z4~$jVA|kf#{99-nw{RusC@9tEpbh{>V@Ii)CYZ}e>gW(61^H3P>>AY(iWQ^+2kyCt za$7>IJDU>n+~oYC%)#}~YyqdxLCA^e8M!{gd;bKwIg;F+x?B;)ilL6Lo|%qw-~^!3 zuR9hi7$m8oNNhN%I$v?dhZGeR`#D>C^Q#2-e}Y9H*^>`t%ZcwGR!Z>?u1etO1qOaQ z@u;d{Zd`ZfHp|p_w!qG8b8Cw5H~oE1<)|~A(9#-GXZRRjKH9105>3#S$c|`zzwMb= zHAWJs+vFJd!e_c(%r6=^Ggv;aapQGQVGt6cp`%1F4{c>CtZ+>c_?XsozdJx6==C4@ zpUf|ApHKfhGByrpMog~ADJh|XT2;v5A4WK|(6GpzA@*<z9T~dY&;)%(& zxDYzG1#fFU)F9-zX$a!0+d8*vOBk5+WtS~iH}|OtON5Yc0`SScWmd(;vB_Wb5ApS< za-f_4VCpffskUBVy|}y#iOQ0Ciih*)cE>3DIyGfP#a$Y6c-k5l_BYI&)&Jx=fgA5M4WGch3qoEO;1OUocP7pQd%2o_w+LMJ6EZ*vT=#hxN7o9XKd{ra&c4|ZUgM0$hE-4QTV z8-X}lX-R@5a``Wg%BX3N==yGMEMbZJN)aifX0H0~BbiyI-S}eh#xO2}g22Ig`#Z>u z4frWj7uO3&$P{=8ritfl zUs@COUKcy4*6^f)!Rfm+)N+c~e8`BcTNS#jO?bO@y1ozn*_mqDD=w^p68*AOcR0f9 zCX-#KGIHFw1QK?bk~wYOPgQEQQ|86MYBQE6F#)F| zE~oW;`$;vkV>{t@E^tXea26Mbp3`<1nejOXi|{;5@5vXaZOnF?V#>c}@c{yv+JR*4aVivA-bJs3F!R{n*8asBDp*!X>VAkVIhn;D~#x zQGT{{xWSa=V19r&Iceznd_CuSvtVXnX}KAQ|NZVogQiF>?U&tPJPqiK>~cy%K40$U z5aMJnY^?VmEhJl6N=3jKKD!~yB|v*TIC5I{gcPiG=9R@-*a!r5tdsC*Nq+evKtrQs z-XL24E8f2Ai9GP3jC+fKT#$-DW#o&r5#&MeWp)YwKto7~%hDo_xrKp=GTO-As9oi_ zq)i1@1ZC9G6%Jymu%_l<)6F9J!vn2db0C;<#Fv($r$;bPV*ZoRaSA;M37OZ_#QAQq zRWyWVZb7>lzpC)17VElz%(y#(%xRI1_Rheb6>?|CMM9z<2`;C!I$A0&M87DbbgC~U zCO)`gq2JaW;`|&QIH9&;rSns}9=f{Awhrc!OQsK`zLqp;D0gPhNQZ;?;sAXl$RJ#K zY|+s#^+s|~h<>#nMmc?HanM+es2RVqurNgx{}DZUZAMzv>+Jr^DB>wlD4QYzRtpOY z%iDYSw=nRT&1|z#lO-a&TED}WuChwV&?x6_6IFgJ-Cyola&4+>a@&}fQBR^~AsL_@ z;3Li#7efIfg{}YuataD1J-xA-Rox3>PmCuoY6c2UPApUsCZ;GLZvzb%5CTylAt5=1 zg@K^!17N=dIKRMT1dLi$l$3d-9Fx)I!Kl5=@GfFF7T{9>t6-OH-|EofjSD+XFzIS<7dmO)BLby8Xo#Lq$ed38%nhyn0m7~G#`TiY^uk>M8VZIJr>-~gCo##c ztte5ce&h04MvNZcuLz-=uo&#A=XX?OqPE=r!bQjmu)|>$y!h(1ogYQt4^1k}5o%ThA-`4aAIBgtkGV zCi8mA%`S!pqx8ZLd7&aHYf&(YPhQiX;qi#?tOZm_i^UZmJ!DaH#C>Y)NNIq&;Vf=| z(h-K7^A1|j%dl7YmpU_py01_)-Hdm=Kysh^T)m8tEu$}|*EoPgw!0ABs?jcFj>1T?3hlQ09tGO(wiA(NM$|8hNl zRJ-$rUZoctG{ehm5$?@N8Y|=ZIf~q_;9Y81GS{cuhL?qmL4i}@3_P2ds7xTg0kEwq zpi%UDGB2-U4!(OIgiUwaSf8rqi$s?-uI#EGF z(nkHXY-A%E44R~M7{PnMz|@*8^`lDE+3inl*CA%h3Zqw>9>_uC&e%%EFH#{NBfCJ( zQs}kjJiq4*>nB0A3l1g)>csN*TNky@Ejrzg3Bk7rsP0+8uSujQ?_)yIl)QYn(_*^Z zqkrmoeY56FYkj&H0^x5&Z-NUr@rA&&r`z+U{O$O91}j9m@W1Z-&9HyEvva+gkSu6x(|Zfe13@A&nVCe*uP={n>a!hf&&NMl z`T6PF9#>AGqDxeY1_^&IlYqJCrZaZv+ADU=QV>Y!uXs~T0MZUVF;|IQ?N(~--{i{| z_0h95)#|HmhbllTNL+i`WwO7s^--PS?QuzxastWS*Fd;byJ&^t_*09w}i}a8I-M>PD9B`q&>H zhwhtB(Du9%0vJxM3Im%`IAr?xPM;F;;wY8JCxkmI-rm=0h4O!5 z*7b?Epoi-~Zv))wf50x^4ofUFjaVJnW%l~tQJms3Qbf|(b9p=yl1MifbHQ{^+DE{0 zV~mfFL$v!L3xKSsJsc&B&aai*&tn2x?Kz^r5BxmTdFb3yqd~vDz0K^pWBgL-9Y)Mb zDES4_3<19KtIN|--B^?30h*1CO*+48?7x4OVAlm>6LtVzt))%PdY}A#?Ox}9I_r9K zX@a<^EoXdYyuBKWDOmb%wekP`2XS$6@#Z-cR#PJ&!1a0j?sKAJOM{+77qYmh!0meB z46-@naR!_2TsTMbTKZWJQuux{p8mNd0u!NvhKB1Fn*x5?lM*XWKHH{F#G!U7Q5EkvCm_WMl_ak9k0%0am;>DgszJz&LfSYye?=ad8l= zol4g7*tj@2c=*l!7_zI|sO+z4VDYEr;aDHS?N8|X zy1pvy45l0tM1p1X`nvINN8V*k1{wo{dMkD@d_*Xnkd$FlHXk3KWOlsWzyRSr;qpLg zUq7ncuVpMLvQQABmK-!BB*RCO8`qW}J|g7F<#piTNSSBD+c5c4CZB6T)-15+7X=yo z``B)cvFYpE+^8$A=?~YYH!>cb8XvD%{*_Q5D|LKa5l9*^e((GwL`h?spUe9U)ywd~>a_z774Ps85W3+yt>N$l1 zzLaK6@U;~fH}8Sv#&~ZugO!(;c1+;0JiX2F3$2yaPT%b^S%#;l{n?p8%Jr&y?#-jg z4R{)GEWI5l#82z6vD!=2=n7eJKy41~+K4`0Y8>KP^B@DV`kP4JY`ak#pQJwY2TU}6 zdbb^ojU*ro+zELIWC2<>TDz=SGx``133Xg%z&+f(=Vno<6 z%BUr!ct!Jwh`|IHF;QY--E^csg8ShtlAOQM&=rW|Z4alQziIET%hEJj;@fF<+aqLc z7|r1FrKPt!V0VY~f8-yng?U_%Y}Op^%p~nefzBCN76x<0!}0;W9*Kp~f2!xDX6@YQ zaWz5pc3!_J`)}tp7!$ni;q>c+c$VFM(1qq#R-U-)*}c5p9D-V>0B|}3fYUI3ygB~g zA$&jbBfq#<3dkGVi+m^X^N~a;v1v?os}Dizs3{ExHas3z#q~db4plU+P=c@MnkSu! zj7*qz6H3^?HkCtV#&Yn7-G~U5`@s`lZtma#)o=+TGZz5aB=1KGKNM=C%DA#Mt ztY=5qZRe~m-(DX$E*@qXPH`XuSzO_fk-94-u&}VrC>Z`K6}zc1s260U{kFUl?thjs zQ9CL-geeal_pjYxU}5jK#kk=}1imM4eIg}&Up5meUC?RGX;h=Z?Y$a4S7vx4LU#}3 za7W&h-Ahy~tlstYPY;vIS2{WxUn|K;Y2mYUqx{Ynk!LpyrY$*yNk}wsm;@{BeP`ff zV%EnajrzM#dl7y>Zwd!NBcNie15beCDDa!UZ&-v^^{XO)Q5RK$mPeH^Ge(cO<-I8( z-ry3MpKwK7y(3C$f!RMjH3PIa(6-X^CMMXaMh)*@K6q7I;uJaX%j^7ctR;H*e-@xL z0-A(HquyD>nxVU|ck}xXNs~J8HI`OW!}Rfirj~|GNH8@tYU|5E#wRAm5@39(HbqRUhyby!np#>wnN1!s1q)2EECL5n00zb?I6R_fZ7pgH zaKpT*i;J47-?`qAki=izpaE%YfBn-j2S;~L@1&(=59)rqEJ{Lpmw#k61OSh*mCEua zxPBSzABly8?oJlqwd!JOCM3qjQHTWvp|F(ygA%NKyj$ba1x&euzJ6hD= zjNihLEE8t?*R#OA(x0zrS^mu>tn33LLje_Q?DDeWuX#24kIwcg%JlN?Y+h||0vkOR z0_dRWHS72Fw%;ZmlnR-$psTL>`|z*L?C5p&cS{S8(?1MggewGNUx3lX#K!tASQGWZ zTx<2iM+sLUuK&8doVwyp&vTN>7D<+u^tPb+5)uM591a3r$NiQxc@2=%SJa&HRR^7L zW`w-{p=J(QQIJ`vY)!$*fz8OMgPY*X^t9^M`LMsihjW)oN>Ah@t__;gg5Rya>+ABJ z?=`av@Z!HdJRSlar~UMH3Y@u<^EkLj=A{8Cx*rIMOp7bira5tnkMr$}{js8g2LD<5 zQt?dmMk!4#N|)s-uC2Xh-Ofyl+1EzEp%c_k&#(*~QW%>DgJNC3mB^ZGoPbDPTn)2w z*Hi)@-@>X)SV{9B4+)HH@67X2`JCI|slH=io`?AC6kI#} zW%eJK{D{?6UKsr4J53{ta?8Sjaf!H^jKD%^4;Dy7H?J=l*EEaD5+%nK!ZXXV^L;C- z5ARJ=&?H6o%m5ZVyrfdxjAd#&hmpTH`?e`Zg7_6Vzto*Y1eGVK0#*6h9 zt>E?y#%5;TT*Tu$_HSN^owOf+QNzSzvR+j4>mB(ip8zC^AuiUdH|X7*`06W(y3-U~ ztAx?R0E)}Xqm?>IF;mvW)R-UQ z`3g$goTw!zF*2kV2&lL(KRllg!F9!EWieHO)(=oGF^#Ke3vGHB1?qqyl$!B=iFis% zLrn|jXCO5bkLN}GMjbjyLnbtOIcc3fo#x%$(b37=H})L^;HyT@t01$fMspC{eQ7%YGrS(kI|5R zbW+wr>ROv6&V|38EgBVH?`NvKDE5R)UkSHfh}(db!WihloA%AyAQU7X;1w2MF+$K| zu*75Dqq=?(TMi`!8FYUECNBvC1M`LkK79Yz-qF#44>-jr-6}KniEr>HqwTXEk>fAU z-y;Q+o zwElkT^`mcmgoF~LWMlvzom2a94sMqr4pfAhnHg2Jgv)6abZT1OU?scQyojAN2HIUJ z-`{14VF3ZlAUtX1U~0bQcas2VN^6xG6n=-Pl^Ng~@{5X8z&8*W6%Gy#fb|k0<2%z- zVFE}+6r@3b-_OrLPx3G}UJlwd{&*EBq=8ckB6E_wdwbW@6!b;j^9}xcL@kcgOo@~H`3iD-3?Mo zhqQo{NJ^)|D$*gm7thT5$IkAuGsxcj?r)syoX_Erj9?zkMO7tcGA1VTXE$$Uzr6f% z=#h%^3xlbk7Q0PVPT7pktAh&UD{v=)I+d4qufE>Qscq0PBxFq>40kbx^&_tH&q3bNi;&9ZUOW+QHRG}r<+KL*_ z&AWcW5E?T(kY&b;>%$mW+}?g|e7(F9fp-1nQLSB7ZQRr1DLx^N^tZK0b^noxS-$1o z87EJdSdn*d;ieA^90i?t&W=g5)=JZn8=6g?uA?zwrs&5o3Z@4jjv-wpO(=JTewKWU z&upah{%g$Mx@F?dO0*CgvWhhe?cQFxtMD-b0up?L&ON+(KU+Wd*258fYAt^+a4oPb z>77sw5073c^Nig94na8UgZE=5)7I(u6w&i`OuU+Ec6Qusm<&kBcngKke&9YE`YfZh zkJ!PKw9i^j5=T8K{0cosOXRY9oW@!ViFgBjKY#W^7XD6fb8|-U17zppy1{h!vmbqo zP52}pIq~tCsA%(1vefq~FaO3$nv+i{{c*FnEeU;sjcT7C0fjl@kr2!QU zEg$l#;8nr3Yr>nE?uB#<3&+DAYfJ_qQn>f0!y~4r2kKi&m9aku*wnUx<#%~jb=9>5 zT=;YFV8Tc-Ga@$Da;8FC^msz0s3g?n`(FAzl0cK{RE8w9T78uOBY(mVe+)Ca*^N65 z*dF~VF54|&*ZbQZ92Pgc)M7jmOKCi^x_Wm7Ins1FEYSKsJ0d1=5z%;ds%Y=m2?CIJ zLHLjeowKqu{LZ%^Q=1wZjmerLtexY3<9TOUxw}2oaP36u(n;qS(m7w$e~TRQrJG`= z^Yrwr_dK#Zj2e1Lm&i;9a4F|sZ$1GOE-5SRs9xiShJHQ%jj<2yZB)luY+PmQ_pYvK zIljW>HajI}2NGg~Zh;aKGxEjwdp0=TIH)C`o_5nR;7B&12?ii6Hs7;vUVWEoG4GsU zr{iP{2kS2*g{KBK5ZrjBiZ|T9qUo62wDC1wRFpH{k|~X01Nycegd=fW@9)3 zIB*Qze=tI85?#Fmjn}WUsP@Cqp`M^A%@@p3M4ZWy`Gn4PqdXS}N8?eIwQAx+`gpA7?V5XE=azd$9O`&o%v zE+=}3VCd;Q7WxlJfH@iUSMs)=(OjHwFjdXbOo>iWjhpE)`4 z0PPo({JV_gsIavkdO@f;Hg+kcJmTe;<&yoAVGuM7vQDD?IUfmS;1tHK=_)X$)SPlA zD>7<8{@gfvhwbWWZ+{;GI)vF@kv*Oz=CzPUYd2%+yniqI{A101^NNIYRvySUCS_;G ztgOho9}#T>KSAm(*1u<5F;Yb8nwpYku;PBo#E6sldahkb^0Sh*s5Yx)(mh`PBUTVA zqM2;(1+z8zA-`>I(9kv|e~fQ>+0D_oxG%D5UJlJ()-BlW zVcMS&B1r?!J0!n*Uk1kNZ%}?4LIxgE4${qC8I!!v% znJjcB>tN9EHFNWippHRV zFd{v8@Zk7(ZoY*t`TctuquM)V44AW#^}LeK&M+x~`;$-8D@RzWz(Bs?`0aiiCf2e8 z0>mWE;Iemj4uHYfpNsCv2W!6^FUa7=wNidsA%4wmA!D?x&i`IjzcD1MsID{VN{z zv8!L+E!SEIov~Pv(Xut2m$oqPLxcEjFb475fg_IJyyvAP`FvYvkY`9pn{zDDWb(B1-~!D9 z;%(EJkBR(MxsN)}#A2z0B$t*u`l^k`zn!h@Bn(pZ{yOnv?CWql&pr+O(dZ_>Ol{^WcB9)c>M_a0gT_;(`FRTCF zjBt(QK-~}6zVCX6?lc|M?aiS`k^kaPzw6A`Z%NHJJ2knux_yZP2G4R&kfy#q8>nt^ zFuofCL1+`)DQWQW^E@j~v-q9L0b^Er$UU*{j1$Y>fFV?Lq$o&0ub0d-&Am8CwIyu@ z;vs{@b6bg5@1B*r3R(n=z zzkQlcgxe#B8-rc1uL9+&=Q@3%t4rUu;&rp17n_(Nv13~sRPM2S`4WpqT~05wpmfvH zOF^FYZgE~-{sqU;^mO^~xZK`h-lXk(ivu-}lT+}?aUR!_(!#rg#`MR6N1xL@9PzBA z6nO^ZaMvQ=A@0yJ61qwuzHb#~c$|XY(sI6JzrMacVcW!#qIl=>0-Xg0UAvJ{8fUtW za;lp{33!;eJc4v|=k@0d!>q>YhQABEjQJpw1$u~+|7z4BKx zOv_L-5B?qrd7ZKgcjR1uQ?~8ha0C+w@nGy)XIbdo#~QQ`)Q1mWo&Q<8d&jws2BzQ4 z=(l@Pq^k+`7Ejkc5EPLMr{LUkLO)?rzi(8lu7RLaxrz{p@CfIg z7a`6y+1Ty4gBZ3bqlS+$57zF#DEgbXLZzxb4t{*U+v@ve$LqZ z$w`n9LorDub#+MsKX8k#DU?a_uzL(QS`IZCWE$YHdn|#L`R>m1^U3Q}F zv9{QmO7G#XIVzWo8@lt;y@`tsgSIgRMGVQA_Lt(SR#+Q@zepwgL?IK&35i4@Qc_Yd z>DYIi=zL2_DLOgjg?$x-r<|6zn5SBv^YT&mq>Hbqc}nQ7mO7Pf(k&qP8h;>P~F z{Rm4gb~L}5BRjBsU8knwV|0Ji8_ic&c0BrQm<^GHA?eA~z`k4FfT?qdZ2d1Mr`K*40D*LN=kXte!1sNIji1hJKVxP-B zKhYdVmsTZ@i>;Js5+{zevF;p+&vP65B6F%ESLF-@K5_1u0=%~0NdQCw&Y=?sX7jna zz6lQf?l>-X_5lXQ1qB5p@OXjCfed2=O2DTqE!`2>IDu95$3xV>`=H4O5Eyrx8Hkh6 z#xVq?oRU6D7^JRSqAo84Aap_}AdpyRK6ux}R`0kb36rV8IO-iZ_RE%wj@|qdU$r%% z{N3e`c6d1H^LLmTc%RiU1=Y})l-K!w03*!Iu}q1puM9|->%V&UT<-D$@Ml}w+cSY> z2Hf=yuJ<0na?8|q;6|6MXvxSl#b<_OT3y|^`S{NU^&+S2OG?p0>3DMc@P0cg1$lWO z@*y?|L8GE#=tB%fn7sV`>s(*odgukJ2Pt>vSa4Vv{uHL&#~vx3b)60Ili~A`nV^XOxQK_-;@N7j>*bHoGnhTJV@O*~h9uk{2G7Ogx z>ElV+?J!E4k6u{7!bLkbT&#pDlH}TVLSI!Ral8I@C%z?_5XIRAN$|sR5kFN%sHIbAFfa#_1+s?>9kxHOnb-aDa{}jGg{_ zFz>8z`f;HqB@O#%u)gp8%8&1Dr6{sHJ{3+D6=EIqml%feCBbXfsVi)9Be)D>jbtPNhvAV>(6}<6LS@d@j8su z!gZnwN>R+^QNbn!RXXV)HiII*V1g6W^VGosJ^-nvy;|)b=LR-O@H_k-isyz-sN*ZX zk`T_uE1ia_*!F;M6Mixjxt?27s3(B`d2SM>d*i-sF&ZqE7|H0HBfe+c?2RE0 zQF@|iVG%d_{zPeboGX56itS=8&hJ>8ZL322claV@GK|5LuqYR}roOW{A7S^s&|zX$ zg)nc9w<-lznB8)_-bcH6c7aU29Iu~fLLz^YwJcJ>m?x34xoMffZ5juWAAYA6EwIF- zK`YKiai_R;DtiX~)|M0_mqX*-n9r<9|J9FH*ZMb840}-#34>N+iEruU6g_oP!9hVe zwXgvAmV-nXg+xv7B}2=J%s`9?i;ayduhmz{@~Wz2`oY*?^YL4q%S(g1f+8UCyaeNz z5|!63?Ol+jpn&e~NAxe!?7XUh*sdq|yl&Qs{A6T8Ky{bD8}9+8>CV|W1UecRgNMM_ zz!K8m?_ATUbw|pvaiEqjzB5ajSZ}XJT@5n zuh5f4QYu5(l?{?@aN#0|L{~0J(q&)iZ7!rJ9#a{dDhD8t-}dOR#03|jsSwEL`%!B@Njv{-3gohg{G)SHO=q#myMKxK;n6-rz{s&;3&LjP#+9My*zMn zgO9>o(`#Z#1C>-&1r-z!!79cEXKXWI!MOm#6{%*fjP-SU$HBL~Wa>y+blbi5)%*e~+d6_Ebwt;LV!@ zX=#f*tx&VNZ;3fKv{-T!iZaBeN#hzzNs+W4Dg57Mte4>6eRLQsRX2H(K_XyBqEneN zHO2RBmKX;Za)F2mRS?)Aw6$`QrL&i#^z-Msb0fOC!U#_ZVlTqku`vJ5*QtbS^j+BH z_7(X&WCDf!Z$yDrXgv85ns4#RhzC6jajRv6md)}j8 zECa+6hR4Tm(*{Di3ch*n6+5T0^C0NwJ(S;E6DG!PJKkha-9mXoG|03 zM4Zg_YWLuPhT|c7Dwge4LHSW^VS7a^q_>k26D1+p(qn?dXX%AXi<2&CUPvmZPX=K| zm<+(eOcl>qUgzAxOsID96i*6PW^HY4Ozek@dez_)h_so$oUpdtc6LsLT|aNcekzzS=3a{_(pZScsCMNL6u3YG2@H{eVIn==>VlP52VN@Ok09TNs*!3Px&c@l@doudeF z+YRmH8ZUXA-!jj%C`o(CY~thcgin{pGw1Rv7#Zbgy?8OgxG@0g-L#Bi_DaOc?=(o# zv9`@IQdVH;k+iCF8Z^^5Z$}S}`*WH#eF1RSW?`H-9vJi>@3gzJp9E|3odvGnvf*Ko zd(BH|!2AbTFKn#K$lHO_W$)jH`y*=pBIU_UNj5U7UtQSPT&|Bx2ceym8sJ7jGYkFr zN*j(8=g|fH9PhP=#Y6-g|DE)H&>tx)7aDv!{wgWAi@al{r&ymO*Y^(stAYY?lSvl? zoWvkJ32D5Dm>8&o6@La?UPr;X#yzo`t{WC2C6O6d{d#CRSIwgN@*%T1Vh7r3Ri{eE z>9op&pIg!Vaud`+sKyGvp|V@X7%=5O<$`arp!Z?be01B0?Bkn_{3V0G0TZ;PBv1Ym z&GnfY9_^LOuL%6?eZADOt@?%S%dTrnc>AfofbZc0DTwyQ#JoC^c^ur8;%f>j2vmQl z>@Rm@maX;H$;utw^w+}=ijr30{~YMj@Q=_uGt{LOx>YG13(f`;8TT{Ynoi-YU$wKV z&M}hJIt4RiP78&kubdNUiIz|Fcf&EQ8V8k8RV5|Y_kIHxO?%EhM> zNmDL{mVuujSzn&&araxZ$iOm8$1B?97`0NQ->;(Gt|nOtf^GOMs8@n-Q3mC)#FPrW znL2k{JYFuMa?1C6%*gMnir6-hby>0;e8t2x6@T{(`|;z)qCjl*CwBd^Ih+Kq;a!Ng z^J&1_nVCCyc~H>V3I&+3?}o(dtN#!kAN{%L`ieruXNk$mr&6RAMI%I?q}|Vks=~m} zpRjI5NJ^Ta{I;!S+^)p>T;6_3FKn!Yj*~NH$-rxB{rmrW0m9?d1}mc1qGZMBWnK1KE=<*(u&ypGF*Ow#{ef6o(R3~k zrSBYt>^ONC&zEp|#>D?@dQok);**ggBJyUtquJ$MaL8;;rQHM;-jjve@JHuzQ}px7 z=icMFPdgS7O)8ceyJ_J=O3J`cblA2+STHGV!p)$rQ(`*zfqmai1e7!XGn7`deslm< z$^p&+9>5ZGI|p@K-3nnMhTgue%;GT*wJ<14-oNJKJsCDuY%Vq)aJZHhu)mgOPxbTs z3t#f{>r95XaI{%Z-Y#{r@a)T%6n}y^X5jSIDa!s9VTp)&!)v}5lc=G?G0&_zG7S-5 zT}rGAk7=g)>v^jw^M+w_i0+1jgupUID{*reSJcoTbNYLQ9tgFm0Z5w_0*D9RnpU|Tk>0cGc~ioR{uI}NF=H6x1`Y( zqVqA$ltC{0>{u>qij9r4-MM21@7Amt|ANJOU!28A>cP~HH{jqQ2}jHYKO6O5B{MU# z&H53}BvbH4UxwYPEsz8%-2N1FEFY?^Fnw=t5&}vVrJkPh$S4E&C{k02z=e0{A!=cX z1)bPm-_E~s!lV|@f^066iY={=H7ZHW#MI}3DQUA4KT9TkA$QJ@sw#Vi=g;1_@Z-PK zw~mPJE7jyFX-I+^tII2tzLLbmB1q)Ub-thY)B{{>amkG$1fwn5kP# zad8t!Ow^37k-)X}9lE%(&N!wTSQf_YVC0YMh)RrmVxp|V=(10c<9A6BCnk_Mp%gZW zLpmIsO7#BA7qD(JaKZAsB1U=xZu8BxFzRhk^6$3KAdD%Rn>{BJM%~p59~QQEv3_#| zG1XtgruU@p9OndtpaRWKFTU>I!z}JHc5K;aURn~cyYQei_msgkS-W|I>;&*MJNcWq6*h3sJuUi$}z5rG5_&De5=M}5IAQW-| zq(CI*KA|4+;3EO(>bvm4Y&gxw2n~FMYR?gQE)oAz3r|MEuP_PlHU11(HsQN@vfcFfmYXFH*zW~ z&oI|BJ0n8+%@TkMiEf7jgmQztr%Y;`@eqhJ(AU?u=nbU=0)U)Zo#5s$Gh)3%uG}3^ z2<6ifojKd1SKr|dajO5cN~g`|Jm1*uzhlM1X;!76XF_{!Bcz{R=Aa`N&+H|?S@fy5 z&sOt-^SuyGJVl(G0q5*fyK^&kAsn2#MHF4H_I1KiL9Q>k%_!_71Wtm|KAi|}1(8+% zdPp0*LM&>kAxq$LkMN&CuUov_1WtJp{08LB3!kTQVJm7Ryo3-J>z$zxLQzA|GtNZ* z#6G#EhEuQ8=ORzyiS>%j6&qGoc6P`5=5Sq>gai%u!-fFWg$0W9f727cqDY1LBa@SL z|DvM{3D6U!eM0c{Cd43S&+Dp)RO3fK&d}f7B(}hglB)hMzqKS@#>U2lka%PNKTAam zh8=85Iwnaqt>m!J4Png;j|t^`#SEL8iU3UPe#|2Ni9AMjEJsWvJT9)Vts%3abUZO9 zRn>IOQ&_}1JUrTBC0}ayAu`>Py`T??=JWiIL_c4nl^FCUm=k&k#tIiE{Wcfo_?0JcF!R#O``l;mh@+CE#AjAx5&lcA$ zS~GCaz0;Bree}XsuaPD7CYmKuN>Vo~N3~C8IC)~zLVW613p-mzLLFN0u9gKOBQ6zg z;EHu$Mr!DSwKOg+0@^#tD=a4`j0n5$_|83Q1M9i*iwlu{qa*_=@B2`weY&Gb`B0H$ zKUl`Z=3X;ooS&nD%5ivj=yP+igGniz3^YtQ_Cc4$o$>|#D1ealK76v&4e7lhIDLj=;fxCmPR{&w6(ll z68uV~H9dbn1I^9j%rHE<=&QZe$59>=uq61pXPbW+FITeDUaf zIlmC;%ZSj=vjzzMf&!=8_A&F_JHem7R!qSBH}OucPxN2?mj^fH5-Bp+1CVL?DgAx1Owgk?Z^RHV(vr))lx+eL9Xr?f&d?d zPg>pLbltqNat0oNQ0X(Rez8#Dh8VEq3DSog=Vivn$}*{SCY!RdN|RucU^+no87_Vy zGXq0xYpc9$40@@=BZQa95g@4XGce*7%;IMf42Ep7ga(tM4h&9{l6^@IM$$}5;vvCg z80b$RB3CLiQZS$hmSdrzyN`#j@m`sMK{_#AH;_sC;SC8v$Sabm(1`==;&P?lZ#ELe z9ktjQ+=0>M&&$ioXgE1zn*|>bDKWY)3z0?=-24ZU-M1R^oI)CpvRN^mPuR zn$``hdJ$n%lmfY!^`(}#aDBr%EqiwTN%x~2DTwk!AYnj~d_D6lum{NiIoj%U-&&{o z5*IdxvKd=A)IliPL_KD_l#YFL=<#fGvxE|YJ;##egHNd|0*~fCjmyY~C@_gQq6omviWMQsfw9`nY|D9^*gBe$(9u9Wj7eKD!9M> z)RmN!%~vm*+*_Ei75QV+^evWB$=HRTEd@QOCqQ$rNU5+$rZBKm9Ds1#6mdI>$|dUU z4VJX#19=$8bsZN>WSI|xdZJ|;aFY~d6chp)tp4pr%l;%pziiTnRIsnF@2-n5e}KZy z_LNdgFj>1br{+f4%+DSp;qY5r;cTqJ$-7c2&-DoA&I|OKmTfXpo&v3bn31%*z|yE? z10;|X1l}yrSvDBvcy0@U+LnB$w(Y4oBn=E;&9|N98TegLKL}B3_9=GU^P??%*1R32 z>9h6@OvDD;;%DvBdKA3Ay`|3c%r6-~E;79kKZ%9_jgF!s+tyETYB!dsX0-1p!5R6i zZx3^b+@YNjC6+4XQYV3WiCxp^QKGy4W(cu#mTJNs#03PMVm0X;yaYPjI~26{e|k(m zesDlFhrQlWsYJxeT6{`v2NMc5RWkT-^;%uZwY0S4?d@yYxvgS#dwX9UM3eHqS>Ojj zIv&hA!|6O$7T?O~T7E8jGZr^#m%wxnUZDKB198u-l-aF`I`j1eOE4`OITS;*+>twr z$J%%iM!<|*Txw8C`UtGwD+X+@zzJiw?DKBdnm)}~{2jRZvq0s&Pd_}uS7)6N5{XWa z6_!ZKcRAPgB(aJeTh^TN`L-`|SSPlMBM5FQ3^GPQ&7&7Mb>Xh?+Z z&^vY%xbLFglo@B=VSMSZ1}8eMae2OR5m-}HHRbreY*SHGY=9H zCRSF$VA~-flI1c%BCvlV@DhrTjNz6^r%tSTetK}c?1FE#xM*WWhZnw(GAKZeaEk^Wcln<_L2(`20@5oF3{Qd8TfrJ}7YrZ*H0*_KP1I%GGN0 zsvOt*E+Rn0VjKt|$Z6@%d^i9)D8>%FXmDmw`CmIhx@7bBntI6dx=&`Uour;G;4f))g zfVroEOJ%ziNgHuaak6@_Q{RZ%Dl(3bS1@ROJUyYW?5BBO*checMpZUizT1pdCZD*V ziS0c`EFLL-#Fy_<#y=i_AYb;F#BtXpxa9{ywqW41!k(QY4Nd2q;9v$x5ue?c`|@rA z#bRO}+NBYZY(*_Xk!59wEGar(ReqsHp22?Z=wJR~afh}Zh2?+@S^2LWw z+|kwbF{ElLwZ}LKJr>dXIx&|$_Be76?sv~oyDaS8HazLm$!b7pH!veW&7I3VRrH`G-siB zOxI0%a%sObzbATx@!PCBoO%1jMk_z`FV5MHw(sXDU|+ z=a?oq9NW5EqTCj{U!yHroV5K`q*=vFAjHF@N?BOQqTJu5KaT2b0L=ovT<=;-h;k+;;jS-S9X9cXAA zP=e}@Bsy+)1_QgF$`&I9A*yYzZeBr7i9wR6P{QE%rP+JFm`(SbS%O%y; zmdx3?_FMW3qUvNEK>qI8p8}&(CXZZPj(OWjx0)uk)?T*pR~1Vi4N9 z@Il0=um6yos;Q?Xt)oLE;{KoXhBJ>wq3MzLZytJ%hcZeS7hzZl+1V=4WL8~W-Pc#t zV1u8ojPu6f-B>2=BMe2wydsMFRK&pm^<65I-}b?JVF9*+h)ZZ;T)Q^z*^ECI`0SQ` zj7@zAeq1TCZM9f|QS<3d}VmvH9vkj3uPb=2)niDoKICEHwf|OzAa50 zGp&Z+g?ptljygSnz}PHKGXoY16gQBV%T??-s7T@;=Tnd=K9?m}!SFh4-r(^#QI6AG z{i*zHjR+6_q;GI=!?{I1Hkx75f{p}rRaJ$tOcQrqeR?WUntPWqove6|~kvGD6zwf2|FV9$l z@ZVXgV^wLGKw*Mj1E*9mo5lW|$LHR#gfyVVOF#Vg?*ML_+@60oYb__?67A66Xfu62 zKkhdGI2%=S{VJ9`VLDQ2_{Uo}MXia#LgyJ!vGl+UIrtCp&+< zS87%_53X?Pl;@@BMU9OHOh0_0`WoF7zvuY=JzaEAqQUterVfizm;nyWVDFq&kWq=* z%!a$N=uy?2ovL2jsriM)2+I;$ z(jC+)=hZF4`x>#$sJuAC12{ z`RU%j^%p##H%3_0;_R-juDk$RR8Ur?INn1(_+2kY*L5wN-6Azb`Mq9I7y<8O0Q$!8Qyo;H;A z6R!)2tVfcRyq0D>sVkt#@Rm!@NGzuTS0p$aIHhIRwabTWe{NR49X80ZW}e>6&!b6T zeZKHPUy3{Pp>p~J_+uj=?IauXlU_)O6bAB=`7~^tu{tz(>Sm=;oSO3axqj{65{L9m z(7BP8XeXxbE4;4rdcNmCBIn%m+$Ba_(N*M|uNz+ym46o3|IoJeCO!AR=7=fT+OomH z8s?mkNQ&+^qm_`z1uKas_n9Y-{}Z6O+C z6cSV_%F$tmQ&CqN(q0aqHsrPNAa4xAX|f=M6n&LX#VB&p_rK@imrg%(cBaFUqrPWq zs_*3mL|dLREkypu3JMDF;RWX_uF(gBds66q747Li*|~)icH?)W4*o;h55Tx4_GRg5ho$sL%4EINs zi=|kD-|1bY6lk8KL0 z?G~E@%V(pR^8Q;pE`MGWO*xYB?!JKlc|?TNd<#BwfP7!UYIEy3Kg7XvcB+p+{)-@? z`|iUHABj7u%T=BU$|xs8mk-Z>h~^|mdZflRXGyM54^6Mq?sLv90X<8t!os`%{bvck zcX*-S(aXgGTwi<+5#=z32Br$%>P{HGws+U~`Z@DLJ=%~-f)lQIZX9%s)$Smkot}QF zh`X+L3R8QZ`EqMJlbVo)fF(K8!A}eR{nf?*C15$_rWSuCGv{Is-TX5uuW}htc@m+e z-EwyV=DnSH(f@6hx&0#W!^PYDav9yr_$+Z>ov$9Ee?7Qa-f*VbEp8`yzWyg!``m+- z#(3MK|G+IOJbdvWRm!NgoeDL4Y%lrjpWpn}kwU`=;|OScJBQ(e8wn=reOqKE)3{y! zG#Z!VbtdJ+3Dgnt2Tv&k|CEFm8u#7SggeB^KkpdwJ?ekksjdocnsLWcmnqKNu?yvG z`#G9A%Laqn&lrhmY0Si&2JwsbZ7Q(I-X(>D#Fz4X&D{X7bm$SQ#C`tUsb#_9-}jTh zUoYJu4g^ccWj`ufC*3iPSxT_|BA-Gze%R9{lZkK~nw<-M&$N-^3L zx_+XDjCpy*X}`5mrF~E)v=l#{8ctS%5+o|-$`;!xewSZ?ART}r?&>YBudjQG&2d&x z0cVDa5k<#pLuGrSXGX6%k@yt0WC)LeP1L8FP`ATQ5B258Z#blTa)lAyJLLVrXn&@Z z{#@)7PW);^v$VjO_%-S3wmz=&w2jQLg|c>?J#KG5Z_F%0IFRv@2+M}|Lj4y~2cedD z&J1f$RVdzr3qOwVNRc4L8iBnDtQV_qw5L}p*VtDltcP^U!(T`?P~BI=VU)HtCBT`u zM)8hSMPVZZ4-L%h&_nJw>Bs0(qC_LBaFzM65Fo%8wY4D<%3*PVIrBcP3_r#w{nVBKy4$E`4HQ)A<`Qux1Ac0EZgt#8I+1GY^toKN*v{0AIvJl< zbBzqYPNShmcz%CTXscCpFC#v@ikq0?YCn&p5Y}Md`8qR%53%=eZtf$*LPGVHy~uS) z_v4 zq7b5k7SI6WcFP#~n`aP+jo%vieOIeBJUpBL3_ioVs|&~g&b<#886QL%=qhPKUwxD1U8?x-4rvI($kxz zq+A$~e6>tc$rSK4v`O*7t^4zL#MW{Qy|B7E1_}`u0-D6cM01jZS5hNZYH0;N=A53w z#8+qkRG;X_0&-@e=NInek*qZa2Ce!gZxS9`E_`jn_G3)}D1uX@(dc1PqKjkDSvydo zA<@FepML7?Ex_~~ENW4wr}+$wGu(E%ET*QWS~@yY-vuyZ2Bd5&GN2ku|C_DW(`%Xj zE2z;aDAayR&rPwwzFHBKf1sX?q{2y2!^Vd1FXg@S+`5S#JV$Wb#U&?KGB=N>6scl2 z&XLbqjN~G*c;?J&VI`%8o#5(f;o_#5l-w8CV0CAyxMgiP1T*O{h~W2zMn{29aa~Kb zGCO;g5(l1?v!;!(Ofa7E@!@By%0Y-2I*#E#Pfi{Z8csm$7Zj@(oprDM07aMa`1SYM zPRq=(Q|%j1KfjwNr-s*iYW`;bEwunn=O*n;r`Bj}-2djte}O9wn0|cTH$Hf^&F`w> zap2K61TKFRP|Xz|Tkns$^`am-4xp~qQQ$7y3c@x3l&+#nU7lOZ#OtQHcWh8?Y&`u- z`=0Hw*FzSHycb7iN$JQZwG z^ww`B^%ar-?**_G)hA>XwEkwxtr0uvPVFUj{+H@$(xn{#^O98;VU2RFqS9WKCoO2$ zvpkcyG$fdK6|+e->L%KihZR>Ef_^bN4S#dD5pQqKx~$;Iz*FCTor_OJ^|eSL>0T!= zia;T(bJ^3h98Cv~?w8pbW?I@na7ET4k39*wLyQ0lhSz?nxr=rG@6PN0_VzBq7Vj#% z#Lkv_311i-u3#% zd+@#1e*IF)75CL`uzUzty1Qsh?*oUtuF5QjDI-&O1YJWkU~Bp#$ND=aix#N;H)(^I zq`Y@H%UhPMe*y*u1|-03RY~v!yo-dQXV=4G&EaI$=QU2jw**KiC=Fl|1Q)}36Sv#G zE?ieeVUbi(&?Y1#gE5Y^edy?@D17^FcweAsgsxCF<`N5ESCc+iPzJljP-2yQRUjRA zq&??`a#}_=;>O`Vj1Q9sGc&xo zy54Msq~ker#-ehqrJ3cgbuL!It;OtQrRKklurE*e);CRGtqHhyw!BpGPJf49oS31{ zVPtPV%UiJY;|Hj!@(d4}g>#V)4Ac$pMUV>Pcl%;{Z9KJmWncEmL9WdLZT|iJipsYc zJR2IZK9{UU`T5cBc{co;7+WMQ^B6fe9#+nLDbpyNYp}u&Lct!G(eC-0josjJD9dTY zZaG;X^TZeD5gQxC?5>7)++AHYfS(5!n69JryR{!CPh?hBJGo10a&jn;P8ogw_?Oj) z51`;7{O+GR3d~65Y~hrTA3x@P8-RCiuKpDUpf^@LN4dm8IyYiPpykT~UQB~OXM+r` zX3B5XjHUt*yq1=}K-@+IsIGUB+Xom#OV;SwGHM4^tvf-Z)=x(&&Takpgx}Y%`(#nl zIXXG6USCjLo{$I4FUcz@QQBPnULD!i7iUif16^znC8XlhT2ZWoQklz(dV9cOl;8wS#y zbc$O_*@}Dm_cl_xOhu2BN$F)>R<~Z278E3W_KLVzltv~7n!TR|_4RmMTwE|fRA(UoV>3YBz}W`*QbX^N zgXNB^1(j9^z1Ux_wzRgc@65jC>UuM{y|u;Te{)Tq^T~V=3x@oDetxs3LLwqi-Tm9? z{EsPku4_e>dwUU66kUJh8TZ3T*kiwL(4NE+@E!PwC|K>PHo&pF7?il_fqex?Q$1>l z^D8U4+uN34AFRDvfskLzubMV{4|VV$<_SC*W+h-X*!Sj zfV--~ZMNgvx4qNTNr1qj8)?;GN!GHlos4bNT*+frB`d&*P$a zGgUD0C6gEJ)_3d7-=!JhxwvC|_hXo=Qz|2WW7dsM`}eOM-Jm5tHX5ydl@3t24Sg=l z4R_nB*15*#`wx=@OxqHXZ5rnLG%{7%%|2li&UnR04t}4Y>8pSB*=_CJ&=+B5xW@WT zE-cU`CMNa`#E{EaS(Tn&t=w{gsKE%qUuo!tfOiiM{NC$Lvqt!q4SFycjhV~NV&^A| z(QUL&fRQ&bT$l0VByO6mU%$qGd1r4+NVp}m@u2p0pk~ML*SEYw#ieZ*~ zL&Q2>CW&JvPD+XZ5OnB_d{4AZc{BPJnqJFTXlbdj6-%%G@fnW{#_A^SRv?IUbAFo* zMXhp`Kfdfu#}W{blvgeF)tS4izbKWVWnER{;mKNP2ovKaZRri`O*8(Sz@yH&Lp9C1 z-1dp9U&dzl{p<9DUn8Bhophpo8QtBS0>76&AI?u&>mMZ$o~i!?J1Tkcp%jkyCB$p*>iB`A(fcRNQ0A8 zr|*@YMoW!O_e07B6#rXJ@s)p!gFDjCS9}tp2#EzxbO}BtwS{Yn@LCMvKse%aFd9Ay zj~Rpa854aNZosMH+rw^~?^UEobD*X&i;JUXRHe>TnvUntK1BqG*dGjxLXtn&7=`{y z7zVZh52f-yQ09bv48Nzyrkby#)J^tu#)ed*r%qa?C10I|B%fT;v;*H?!ZmG7|zv{lwD8UyeJc2%iR%nR+Bk@4|3m_f9* z&A7OakczXUD!sS6e8k{!DY&kgsA!N!j-&ECN#yWQtZ8p%M*Bst{N%A<{g+yg{2hd# z3<=}@cF+FfU&PYt@q3Ypa`&2$P?Vg|QCB&;g}CEabb8%10o6d&9)GR=0ZaNE5Q7JmVrS2abB&2 z%w2LAlByl{xx*F(Ky(J3Fw-(q45r#1o}PxEjB96sG69QO$5+zFwghksZ>;{0o>iXHS2rGvSux~{Iv4Dx?m-EvR!0who;(9b39majt~ zxx0?-e;$O2Df%eyX}29h6CJ~5K)Ba$vA`dk1<+yj_2h*J9*N#)+;CN z+JuwDjG>{=)(4h^zLlHPC#H~(gX?1dtg!>W{+mqLiR5G~Ed!c8CSL&NT^CG5Q61iA zkxj4ZLxvd$Z?CNy1)YoH%BqfWHU>_J8JCyoI9TMX^qUmHH3_v)$hfB`SoyYZi0f{T zJ`8>RN(53R4&eJ4gx}f!%C-Qxz?#LvxrDr+Cx2keXr*=J0K`@Sk%B$4;Es4BO{{~?;8jv zcx5}l3VR3BfLj3lGVuM4$B^CJ-2RWIvka@M?Yg!%Al)6(jdYiUG)gxT(nxnpcOxK; zAT1&yASEe{f*>g(U4oP#4d2|)cf3FRp&lUaz1Ldv8gq$d+8pgX!6`V#x`X2>-_S!c^0!T}}qsLFEG*3-hvgSc;OVV`#LZd@es_ z`1z5x7dtccr7>PDcQ1Z(Yn`PoFzPS3&h?YNA3Kk9@{`oD2OjIdO2RD2xA*x+*MZA@ z7BM{0-8HI?goFfL;0g?BOUuh)iY48jt;n>i!ZtP}FtES8uCK>?=^+ukc@uLHsp0N$ zf1qmJOz<%EuUIo`NaF2anzgkxuc$XKPBQ5WmY0WLVhJYoBcIl*kn@)!D#Ag|0oUs) zyt)W4{^Ezd1GvoAqKT5;-3$Osp+4$CKRDizM77Kc)?s(;Qhgz z{wVx+KthuJ^rMbWiJgnQsKh66j6gGd(v9x>wSrn#rSbpJ+5h~hK|#H+;@{R(DgC)P zfOB1@Z%Let-V#1t&SD#QQQWXI?$CB{db*#3JNKvgKCz&%S#MuGEq_TG?8>gLugAB& zl*_b(f+iy)8%%PJuyo(pb|t*yri$jK2rhP=`1MTgTl7zAO3H3;&OvKuFI@wo<2gI8 zSN_?gNZSslWqd_RpaOldWe*GcdUk#eyfkpZLGCrgwd@N|5@KkEOLU^vywhxm9I_SG zHa6F5AHlyBqN%L_W>&ba&YFgK_wws~S_YYSnk&ZdH;rKO#W(>LsH zj8WG@Q1q+-X(m-0jpwdbRssR-WDP$;x!*@V)M3??vkQ~J2}^2jz3>pamA^Uuekm3r zmGb4W-KX{`xNe=)3x*YIed(ow83O+WjlQ=qOZ3tn|FqA>SpS)Qur>juWfXp?XL_Q{m#OgMBLT{BH^D zu*!;x5TRv21IGHwqZF$$Kf$Bj2$Omax5WIBVFnhKXiy>%D>j0TW6h1nOUxo`&{TH+NRyan+F5i54^uj*o!{Db)Vp@pR4z$11>ZY z@#9zAay3%%ZEX@jA%@%%i+aAF&W!hUF}b)G8!VzhfoB2ZDV#lB{7hhKujSd|34`6! z)d*7f^z^jZ;V$hXGbz7=r-&70L#jZw)$6f81pBwayz?TKIAHc;GB3yOxRUXu>j%E=N0$fk&$ffkJ|xoMwzBiOO5+c+Mji~YJ`oF?&b+yk^xQz#GIk)W zsvyLy#VL)L^sQ0& z!l2Vc4e7E)8gf1^=eJOqV|ChpOS%xy{{H!K`Fo~)d?7tO%k@MG1|C+WeDO^BB_4y7 zMU|D+l%`m?YU+S(;||$zPzLDsnpcy}nWe5^f;WTW7_5kHH-!xFQSiqPs21 z-hw>;EzdlKh)X_Vob-NTX}OZp`f~Al&-Zm*-C>}O6yS_bKb{H$T6#zZEgq#ocohLuEldJ>{uc6~%R01CbcNtZk@SrUt?o%moUn&_shoQEEQs zzj?zTbjFTG=V&6R?J z5=#rk2A8#py}Zo&wR9fZ+p|FyStk4Iaps6vog-P;f52C*8v2q!M9M-hae43S*RRcO zfsjF^zeioL?Xhnw9fLtnym_Tqlqf?bcZl=Z(~Ff= z-6=Lv_t&ZEtl#fo;KwyJN$5MWSoJ5$R!>dL&JKR^{|n9m(8l%_|LF%qw8pR_NbKh3 z#vq&Ood(6EyrK;BHY@RP5K-bNshE*X}e!g&4pfyHf{zMyi`mh|R)rZ094? zR?-GhBkZ#PYFYFh{`u{jjJEcKP|mGh@%6lG=ht|W`*1Jq?02ycq@3#vk-j|L-&erq z1@|Q(Az^T)===J50sfrC*jV&WpFSz8eO+1`LKz}(nTc%wEJ>zV zH1g-v6p2zb32PfQ{imUkgtB&1y7aJdw(g-J3f4dpT4Nh0clSFs|9viK16WNq^8srn z^1jONxF)Y6?URfJ_>Z`9y z&S8{f@5LF&_E%TYuM?l9EymQ>=MSZ4LL&{9n3ympDaI%bO}3R~s;F3T<=fJ>HmZca zBUhbYFt>uqoHeAv6s$udbN_z<;Hnw|$FFuL`zl>%^7`Wa(az1ytyXjNYY$>A0D@#k z{oC4i?~(!hNz3U8Udh@^-$plPKE65wLtZwvt>{14RKkg+rA(lp@66QRD$I(39Kp@a z4W@-VCZ>99nH%?MN$!Su{q@bT57p-U(ieqnsN6GYg{)C1Nio0t8C1vUUbVIQG2$=z zb400&O&Z)C6iwrGrc4ec#HuI93c^_a+%1J)s(maM!;tl= zPb-*!g(uR~2IV5`R3^1HX7;Ytx1_!DhX2KpO-$=yvPh)xl`t0-ZKcH0w*DyRA%@-5VB?e-WSuHD|r~+YySblOn@rdUTewj9b0u)~$;Y&zD;$6V{ zV{R@7v|>-lk~l`)NRvRBcN@!QzL=-WSr6gEqM04B;44u`@Lw8Ti;0Tz+6-sVdXIvv zLIIN_=+V#sw??M5l$weU4piuuF9Y)my!UCDCsUcR$s#VCHb~|=s_ca7yio&H)(;LC zmJ;7W;r}@-Ea|dFK&cnOlf+jtGvhFJzU#X{^5rELZM~HecIIXTg;`6q$V;x0vZ}a8 zSs(nb>bmVuUDs!A?YzVo0ak#sG5RM(*LbA6eeqx^m45A>SP!eSp7jES{M-hf%JC81 z`|1Sm{q}(mHlHhNr8O*;#c2Gk>4t{PSN2?NXL*&(o_h;{JOzq-G?Z>4N^I!Q=^d(= zL+z!CVy@@1JeH7`XhikUjQnRWPB&o?@3Q?%XGnoI)`<-bEn{!!#Yk54d%_Zhr)#9u zU}FVO;m0}rcWG&9gb4kgHgH}te;jsuJ{EG+ws@Tk1Tr{4 zKmROl5YgSGMEe{NQ4`QTazpIp>-&9n7C!%j-g+`{96&@vpycg#cZ$LsaH9I}O#Ib0 zsPsrFk`$%R*N70D7AS1Ta|r~=`gmt%zkC4?zGE0N8t6lv+o#60GdjRY0vFoh>FH)! zmJ~hLADY~}ozPRhR0U+5{1sxcwNwqM0WtOT*DW!b9-y0ojR4*nmB;zA0j-4Qi}VI7 z!K#Erc63<=o^RU=%3JP>2JnTF8?b^z@3s^g+4J9OsH&o!`3gjJip3|fqUJK^%)TFL zbEw27d^V%+_W2QZ$vXC$@mrQ~OFmHLsX{_Xl9Qin4WM`qs`V_fuWtLe7i-qo_FZ@O zjf`NCk&!8?EmP>M(9%`3cX)(;B#A&L10%QjsW!0%==__Hrd%Ukw*>~^8#D4R5`%qcG;Zb?8Nx0 z(1Eu_yK3#Hn8e;*I0n&L_E*BviNdDhxu0@V9O)RT^L6)$vug-t6e}K&5)wUV5ca^} z&w*u4m>P!yccz&q4X6xjgc#abZ0jD0+U!16#E`?1XOQK{WXK%i{g1!po;CklLObGk zQ6|DvRjxr2%rAr(6F)SqwKXpcx6HNv>*&}j)fS|@ySz+z56LDJJC2utD=Ul`n~;hG z^KP*)g*N?XYi)=pWmFVu?4x1Pm4MAzZ+22>UUv6pX(uY8F2EHSFGu5(t zIbQ<4qP}EM2n+8RJXDHC#_Xq8!gz~pO&jv_>MD-N^ON>}>7Ek<l!PP-{I3hE)Vnr*vwhhe%i^?N@@UToTZgj;LuOVWdZ7A1kub@Z{6{CR3a`h@zb8dRjqK_ zLa-dSZTe8;oD*pO?n_-^uXK^GpKtB%mVWvKH~#YF&CQ|uv0zojeg@RW8N!iElO-e^ zLEdQ3-rkMBN6t|{*(D-;+g{cNN`*H*j7@j?xz!UXwyNymX{BwTVWa*=wXY``SA9>a z?|DrC(q7?U{z97(F;znKsV0lF&+2SDwQd%NsHfr(k%qmP&ttU@c}YEnj7`ifT7qQ> zaz2JZ5Bn0Z8jdlxs(2@MPMTXe5)PIEz@8?>pAbzy5 z(ci$$UPtKW{y|m=O;p0_h4QzZCvKkeQ6^I8vB=9m{)X6)k}8l;Q~Lt%v%)VhhLBPm z{Bl7r^s4pg(}zSoH?U!xW)u-2OXvdy5(&mgCT?!rM&qxr3#A)i+mL* z#AD#P5k-vZ{Kp}*kRIv+8k%Nfeo>}x3@t{x-b2IUVx?U z2oYeY^TULFpPr8GOU6e8;ALNXI zwwYX1*Gf)CreQmJ%zS_6o~$fmj{VL?^=r&g4RRc361ubrUY2;zyJT*<^2NFwV5+fz zt=QyDn|4LS++p~e*9^qJe)ak(d2l25Cn803$GzZSWl4^nK9YR1=D~o5y_8tN`h44H zc=uBTtyNAa7LE*iY_X;;N(W)9WQkaya71dVhm$a}kr7QpgXGBh-+;&ELEt2QEF*IV z#(7vAj=qxC0H?KLNg9NGp+Qz1`u>6A-CBiwI>?yv-fMfpj6~X?(*sNa?d;dU{zhOX zpeCnwe6Y2pZ+%l(m0gTy2O8h|7&};N(WD(H>04S;7?tS1=rOmGP9=>r7G~xEN@jRa%Z{S1VQ&c zc0ajO=}8WfR2dPD5nEM>s~!o3-6+1ce!u*Z+t^=oEEK>hme;h#@w!SlXBIp-(k3@7*DXFR*ejpL*ApPKB+I`px%_O;AX zRY@YUe*t%3bj9M(=xAf7kNnutysdA3#%anq>q%Va7hp8>h)}*a?6ODE!NP8mv$bO_ z@DTGU!a%>fE5k_W)chZ1K(@DOQ4OZar@A+KlT#$Tc9k!2Bl0~dxfT|dAu{t+Smd?z zTP06Jxr5QC;z4Df7AHr4FJX9jXIk>=5bDgJU+A8qI+19rh6e5nb6reZbTtch-`6&Lhz?kHB$JT|($zG_+ za{J>8$4I!_i5!0j^z*wa&4NJP@K`R+RW8Je`W6-nKx7>rnHpU8$ju!Fd~kn}@HYkI zWhq+++UkMoItsn0m*GE*uNsYi-gMsa%?dqXVBe-1pS36OUF4GY`KV`Y_oQ#Z7Yp$x zDk_WUQieSDT3h9RCh)`q+A3j0znYvelC9pjFU?B$aCgMVg4eKjfOfv`ovHBn)4JHp zDE!SD{WRysc%?}EucQgH9SbbNPVcc26nJ+3kwt#>(S~5Tg5_&`5)yrj`nkvj7c$a> zvKaME)eoik58?57zslW*K2ibQ{lu`~{roGEo|i0LxT{fHs8z^O}3%6IJQlUC?L1XIaMvcw)f zgj1v?Y!2t=A!|tQ!g51WHVTb0>iMFF1xHt@9z9^}D>E=!9P3Ld zn`{~X=UAp49TgP^v#2i2sd2MU3h5ri($HUpZ;)ka2hK75CC!+|4i^2a@Bfm^qso30oIN0YETIxMnqGgY z{&(V$g0;=_@XrKTw{oQBN2CKe7=hM5%byI~A9<-|prx#)CIojDW+9=IPVo%)i{odY z%&Y0YH9Slo!Nmjfv-xG&rA8m^?U_Jeze7r z{rdHBb=5LgTfzKTKK-5MPCLwkJN9!Qap+J)`^p{}RESnId$FVN_7EwGudcFB+aIJ2 z9r(r-E%BnXD)1{eTg-^^+ny=^nAZ!dVCLPwCTjJ2Sab%_TJ^*@*9=0OPN6XW6* zFKwc8ugeg~pM>fE$A!b|LEJFC+-)4KDDz62HvqfDB{ogfVSCJ;a%&}rJX^R~I!P#p zEmQkbdpkQfHwzQfnq6A$?T^91K{`gph@v7o*gU~32_dG_H>cOaV4!%{*9E-Ev5Hs0 z0grZE2mz|C0mYxNEP+xfLW*ZH+%e!oj890Q`0_O9uyoLy^|@lAjIP0Vy2US*d;{pN zNS}ua1r7Z1)JFZga>h$OgUyHh{9LXUCC78FQ##G!Glv_clJ@D7{AVo^g8 zS7eltX0={vxoS2)^Bob9nET8T1fpVM^!vL^%C$-xn=u-1@d%j=Yxr|>!`(eSyXTF< zeeK!Ui0vOpzHe?`Wfa5xPp62PHkZ3=HiqsqS|in9xNI)wuafX*I56S+x-jNzrM$GF zc4lU>#s|Rar;T2fM~|$)p*th`(S^|9H7E?+pNkiV2Pt7z7XRA&L7Pk2`-lp7;wk z0kTjKa_StW{sQq2UBp);_;yk!s3Z6R^iC6#WgW~~r5wWE{O?#8@;0R?{(j&-v(29$ zh3h<>bKPL&7=R+bw2}2M*<$H-KvR0T;EBgE<#uyH2CXL^9=L8`oB~4x0Z=2=qH(EJ zJA$GRg9nmT)?or5cHII|~8*T>F2@H>iNmW9LpFA5A1 zF<=nThIRl1?;Fv92m0P~9W>oC?S*g1*PejIQ=Q4gU+N5p>@nC?1LMZqT3dtPf9dGx zXz$p9ryKB`9Xnxwzh%a_*3gq`H@i(p?3+5f<@pch`(MvbP0P7V1{awL_vDszHM?LY z9{}IPkYtw1`YxKk^28w3%}wIj&&OU~_pwO3vdm@VBI4(K#TnT-I0)yP&{nsfSzVps zj^<-L?Fuby`cd|%kcnwh$MjG1IGfeCA{JQ7VPO7#SR?HIJ41SPKt!|Dd-059e3 zre!M6sQ&$Kaq0?WB=X7W=yv&s7R3@67&yAo%@!|P_}@|>A<5Lbes&UG-P}}yhP=D# z>Vo28eA*@SgNw8<3Ij|7hOUO8AE}z*ZH=)P7brg$?z?3I(ufwt`dc|(x21dc*eSMP zV#K5KZ$bYlzKhHs+t#cFs>FX4#mswj%t?x>ADtPO$3n{Aw&tb_Q(aoE8U_ic9$FOi z_P<=-$&-D4Evl`3t49qA{LtX}URL=!JQBKfn~QoTW)*I49%LE-rluLaJ0~-p zyosPEX=(BpO^}hcNBY#$L%m~Ym!}|YdTNT#GBU1`4Ovf5jfr`VjeD=CxjCYgi3)zt zMvMxfMOgi=u3B}w?8{d;GKy1rwd!9*7HD;Von;O=%kT)-b)x8e-xtd)FC8d6b%(AGd6zF;C9+m0;loz;8NN*WCIK zn!oehe1yj}O_Shio8>lrosP<|7>0|r^&i?8ZqLqdH*LqjpL(H!%*=OPU8(ge^!Jn> zd`&aP+1@5o(Wou`IIZg7AgnH1<2SG+>irv~xOl;^=4A3C3BMDnZX>a!Zw;%pp$Mx6 zHS5Rv^iW9BZLaZ-JM)ErL%e_JE534$28C8eQ-B)QR)*!hw^ ziBK1u`)osUv(6IeD*0De0)jJoR*$6P-|b3LuahnJ5>&F6yrYYFkuO-eANrPvZm8$m zCQ&j?fpXd6Wd<8*s@4@s?td0*jin{hp3z(FdTZ*^!{<})+{USs^COQaM+>>=M4G}b zF6i?m4F1*oRk$pZx~!QaPOq?DUt3=<);8_`UY{?Zl#FktA1%fhfvZN@G$3oGfagj5 zJbroB;^6+4kvut^pW|?-oGjPWt7UX}43Dx!eYutLCG1Eay_}6wv?7$K(XZ>aB@!>t z0{T;XjP`&NsX!!t)>qp!hq~AqC*kpDf|#&idaGZ}oAVg4V5*-B;wpFK&xxFJAQ8^P zS1iHzX~QQ?Sf_j*f9~KlV@uRu?Tk^)i;@05q3|QWz*wyGgXgcl2@A!V&Ns#Kh=edj zel(4p(!hFT{Dzmzl+f~*4E-<IJ1W3k9Q6chtm z&xr8iZF7O!f%3|UprH|~(9i6ibRnWjxfdL}Fcn4tLE;98MeEc57VB*6>=FRrbO?<5 zg#LcTy62ZZz;Ik-BbBe$;-%xfsQp#mLEyemLzw-`W~!c=}RQ=mRS*Cl&^V zfACTl-NpSUv%yT%$r_&*&0=Dd&;;xP(sTUpm}epfvk*NS8e(FKd_9w;rK2-Aao~28 zLM>+@IH6IsHY>7-?`G5e4gwO00wWMip+M5l|75xAKZ@jJ?bf!P+0GCIB-*57_py6j z;(K1%-)W55Ns7e5m_CEP_;eUWbq#5W-zi|JoSvDf;J72zpE$BINpT=S8DUo?{j8k$ zVSho9?!1D^^SktaQ_?-Uanf~YK5W%HNn=K!(xT`RQ;YSyZ6B!07#20w?X7BXLJ7y7 z7&`fcO)79@D;eDClTS&R4Om)upf$I)eh51EK%`(I?kP06VTtrSKV2ArCWy-F>Jm`6 z-BcSu=N6|3ib8uGeipEaCttyf22 zu7QKpkkcdR52kQywc-9#nVOf3SrV4K%9>Rsbq4p}53V))w^${LFMo`zb8$n8JYY25Ym*xSv`S46>%AV?7Me{`T0No zBLX%lw7sLjWDo=8C5r)+uFFdBC@GoQ2^@ZgjLdiZC*hF}5N`?Km_}LshF4siL94n} za-_&(ciAj6$7gl9j>?Gm=}sK-$n;yIzhLw&RA%CcPSJLM=g8`-MiE)9gB^n>!R!4- zq_J^NP((t&UG+{CH}_dvGjH!a6&e~Eh-rrh2VH&RY2+axx<;37FXj#(Dfhx~8+u15 zIJgl0VFm`T<`xxAjskS_gYLd{^miTnpDqpEQk@t?{W#3b=tYeSV*jAaFdMAC(yg>6)V@z>m`qx6(4ua^N_}npdI)fusiHzW|ZdT zbhzqBx?<(tRNrv=v36>N6%ac-O8QR*-S9p?#gdR4`_kJCJj1z~M%^u(U`Fhu6 zRiS;OkV!{S4wwl6Zgw8hI(S7IYy>;}{uJ62ewJt}T-?Vehut$@YgoT*?2lPQgh`bs z3Cv0(#f(Y!TX8h|hC1@~s-xM7R95L6P3hnL(BI#`xj9N5|FC#@y?xPbqZ^G~BBT)C z8RzfD-FpAO;;7heB^s|>em&pxe%UwhJv&QjI!6Ez65#2)A_Qt*Wxc>#GaLat4=s;0 z>cCcVa1dZBU~1x^jLSV=RZ)lvgA{Je+1dKqo8Lo4q4bhSyLWfLt)Y)e<3*s#dJFCT z#Bg_a+rrv3T%9eiIuhC|DPWeGc7qlB%u#EWnl2oPST> zyt(UB6jt3yhWSHdmWrBMJ9mdaR?~qTqV@`c#zs(ey!EEmPX8$#)%4r$nfW|Wh<=6KK1Vc0 zL$*}Z{0bMDhGXUL!qdNDOQK86bhr*)4=_2?lt<%c%p5Jh(TOF~(0nr1>x03K28l&T z7(i~Sfq_i=)#0hB90*t3)>aWb(uRjsp|u$jy^25Ph9Xn(`Gw}k+1oL2sFkd(NjzE> zN!r?T3NMP4y~M>}A_sM;*n4t9O=LN8yuQnrnf%yI45g=}%R2lce{3a^+8y0V%V+8< z9$PxPjXoAl^M`+rUPJWF{go&|Sr*w;{VS%#MlUT{vMlsvDTl>3pWTZXwX1SBU)4gZ z5!`(~exI0lsH}{xl=9J1cs@s5C<9L_*79gg`1R|@larD7r26Xe^2{8WVIx^`XIFo@ z7LCGv;6*d_yZqAxO+iIqZvuSx?mxHOylZrv_Q#HndSmswn@Zq#-`m@ZWIu`2la}?L zH8x?aKJacqFYAo@T~liJylb~{R9SYI_Li;L3!{{U{l(bsH@0r>w5Lt;wWaMU_zyj} z?y57S(WNOv7o;-Ni^6AN_$C>u&qy)Q`R-KdBF$FTWNI4Bpuha8X<=;qbIbH=dF1 zSq5EI#_^V4R6~WjrX^Zc1FJA+nkGk@W%_JYTBst!-ODM*mRS580suG#v0cZ8)%Er$42}7m;gKq@SEL#G?a@8Wb^_$beTWu=1-BP zl$o`egkv+54>OhTJWDZ)b$=T2VF1;vS@ZH!#Ai!YRpPZxS_X+rn*Pak0iM>p-rjAe zg$t7Fi}RHo$Mw%C)4bNl_D5J)RiA?Xw{QjXd{B@Hg?NLsYsWb^aC?xK4sLW;F2^?4 z;w0bv7RKKTB$%@kQbw}u`gwOpDq1T-EGr}oVx>eP%{)$vxP%&R$x#N2krvnNZ)=yI z9$_H9^sLS#SB+|0AeeTi$?560RAR{o5_1hIBVdq#N7w3SJRy@x(}z2W*VYSv6mAAP zGNwC6O;5S{E$cjgPr8$G8p=S~ibqRQZ!aH+Kej6?(aRvOzvOxaApklD2ajj=Y&PSs zyO~%cw6~!J_kOiO=iv5t--+3+|D0M*V2sB@AoxHp2hT4W^!GcxX;>@cFI&|B}MEEs|7aAIJ+_ zxPj%{{q<{-`}JkX&Cw%lh;W3NY8d}kl59R>p1jGDakz8sTy%pP;ZLVuP3CaI^6i>* z?WE`vHnnf~4t=5zh`fB2oo7g&zvMjK?mAzy7ANhFGvUqoWpj0Qj);NK%SA7(5f8+Z zybC(XOde6oS<}}&*edY#I0%)Hk`jhaqd7?Yz{!#YVo)Vk;^GSrlWE5wkIQfSh5V!I zqIyt}Bp95#`}#-N4TKL42Vs@8tzCb6v~02Hke%M_R3Q+k-Yo6hV<80H^;2t zcK~zEQ&{xE*1hl@H{-}g%I80MGrTy#c-3f?O}#zO=Zp6EGd8;lQo=4}C&vC*{mZu+ z1Yp>8kZn0F6r$C)gc7layZLq|COnehkJNu_u=O9RYy06iqpTKpAly>=n{rI24dLVA&I)A=rc$XWoH_N@> z4fvwi9y{H^(YOkv>=h;lM-Sp#+wTrMLS;hkD3aMp9yN)1VvV`U(Y?*ooS~q(y!%*Q z9!0T)puHnOApyzW>5Bq$)5OeJ%8M0CB&4e})p^i?t-lq?8rWNNRZbCDTk8U690T$z zZqQ)C&;J7K=up1|&vsVUO~I#qPgp*SapBx%%b+HDT=!)yG)LV^C^KMml1o$SB|#jE zq}?oK$Qtp5a0|b>GC4f;E&lL2w(G#lAxXi$0bg+PKS3?JPV8VC&0VCMu*rtXa|cI` zi71@^MIF=M4F9{4&aAGk*73&mD$l#e#1;>F8X-xamX?=SzhpnfzGy(7dq3}QeAF*U z49XFNC}081`?gk&ydYTMIhxrzQFU@NKKSLQ#HsJ%qNm;7*{}J<;D0((6>oh0RQxmE z+-;l@Uv#}sYh1nkr>3~QJ?18Oc}^Wo!3?dt`=PlxUc0e~eniTzQFTQQOfBx2^)|t` z`uDlBir847a)x{7hputWC(3(Yj8f2C?O0{6ua6s>bW9f>J<_9X@R7Blo5T?OCuhVa zD8BXXPw|HdMMp=f4vzbipD?B`>DRDveObMKaSunld);!HVt+{^e``i-dcPHY5@qG| za$95;y#O2cX9^jd|@HT|3DxehKij%gTG*GO-i6A#|=1;+&bPebTG#B z1|Ggvc6j4=|Nc6RBS%nCDGQ7+HrltuHe@T#ifgjHPqKLsI5C894)2Zi_i~Y0%M7w#6PpQM}XlGICd+C#$qhCG~YF^U?K$J-^csUH1+DF$@oJs#rhlq^2dQN69dkr55BIb zWU3`qmJ%^7Ni2F)ENT=fIMl<%&hzMeIply|0*&6LKanC9{Z+_~m{JN&SyNLM+>AXS zad|sNR5`O#*4gtUz+Wap$%8^*WyLsQ?s-!1DMZgkg1DO0ePZe*3ne*_Q2P1$M2LT1~wG9LQ1G+{i#vs11_Iu9VPPx|@7`-}P0hiV@D@`iEoN2xT_1Al}l6;TYyE$ZCb6y*X; z?qwFrj2b2(&M6;-sU5u0i__AGku9{eAhis~Ovpj?+6!7H3lT}sQ#bCZ^K&wiQ{snq ze0-_G$A{9o00BoZV?J}nQ7DOHW)et$VSbJIND&qPvC@EyIjj2PvX4GU^z_@D=D{Kz z>!10isj8~08Ns2gl5X-i$C8!V2EE>Pn_PfpjepZkSC<&ls6h}BV`5aNvFE&V6OQq3x*Jp)22Ll`p&@uY{#yZRB}SaZxrdOMtEi$CxwkhZcz(H^ zyMDx_XcU*N+cx~*laFR9;t=9xaYC014=Yk{=Eitfdv~vs7?~<5yj52)Hyx4%t@!A~ z|K|nJi2G`roj@+cPUDT=TavVko7#Pl>voA3Th zy=pH8DS5M*xS-qi*vW)Ja)PQxZQ7Kzb?vO}|=g2FP^fkmGx zHlMwCpiEhKD!#pA#1c|IrAUDqW1LtWrqYU_r0g?!TCs;a$J-fRzTe?HgQ0b{neXMS zA&-HH)>r}$&H9|(fK{Pr1{OX2V`pa@W!e86YO~6R8;s}jXUh0Yut(u63I`}+7~WJ*t-%u9O(KxAiVbaVr@DFAT8{jo!LgCkYmG^6zVIjo84 z;g0ln8n@7&C6jo@f_S0~ZQi-u9>pbY~6Dv#l z^$XLbgQB%<TLkSBm71dbr@u;G;pg2_4)(-5Pym<9WLqp?^z9Tv7k@{+3 zw(_H*toRKg-hOqXYxkqSQE%S(eW*_%Akd01Gne)Ee~^>&eQW#B!qJIoVP`!kSSpi+ zXwzF9J_ZVKGy`!G(IEx}z@*?w@=fr+6@Zv~1}yEpzL}x@hyC7F>SR&}R&{R*~q0Sm-Na#?pGY873;zO?mr>Nllm$oTP59r!g4rbSU+{#tq%8x zT$pBO_ZG@*Y1JWI4$zt%HuY)Hs zqHgBtWyMEuf+J945Ln-<{f!YkhIMSe_@nfm-q#aefZ++>bC@6y#g(Av*%7Fl&7yxwzSI>`wRPy;z5(s8kx7JOY)0 zxOPc$XF9I_ugjm!JwHz}<^NOt{aY100Enp%^eu|2?}yh0<^*I!a)E(VEiFp$SIckB zG^&l&!iS?2>(Mef2=zRPa~~*aGMgr}_QYST{AWExk4qZ;TFfh%kNNzboA`O~l^^Al z17+uZi~V2LS2qF~D2<+9EW4ZR@F>Q|O}wY{0O&>$Iyk5dmor2VX+B|Nue=I3zrC0N zj@vxK;DnFg1$Ng#2w3_3{VD?8JTR&t;Li|?cK>%O;5^NVFg9rh3BQbW5F9Y6eBoMJ z^hNcVkpMfpZ&>-ANsiW5LHI{9o16WBKsTG;c=u1bh9dn+nE8c;?!BP=?lw}3Qd|>Z zl7t93zRMFoNn_5J==th69GL_fn)nrWTs848gc+}&Q})O)a%V+GMMuBZu0pq#A+?cl zm&8{7uZE^4P)2|_F$`bx?sMbt$IJ53{;`62&!Ee6@DEEvk47)g6uDT;dWCRMI|d4I zWt?DS+KUyr3M>X7BZ0PjD_2H$>&gqkFc_4j5am69DMPC{51}v%{38UZVi|MuMVn{( z;Mdy^K9xYkj6W}TxineT&jHOZq%6JWG%9rQ?lP&S1TW=O_E!uUmT$-nugAT>33g*( zxkj1ku>K)%pUZOAiR6unfk$9x++C4u40QC>C0RxRTSGPn?BX{9p>s2e>FHK*)JkpU zTMlyJb(3@B9i7%xeT3Y6*bl(C2tK37@=QwjMHqC0dtEChKt9^usV~xwTERmv;^Dw7 zm`faS>R-p}Ke&tCVnI)uK?f(6h@;ny?K|x~hdXC#8y7dyFjlW%EEG;FOcXgCPUSxLsAL?gVQUt}uR{E{1 zDj_R2GZi=r>Zj)T6S3&9;ozt_js6H%EORja==0}KBAAljXrf7u( zLZi-`2OpC$nL}a|ey_W_L&J`}LW4fzH$8r%q%6aTf%V>^e1RF@`tnyFyjf&&(HHYIV^t?X>^SiJT=mokCZeEt_h85 z+MxR!f3^^v4zBggn#_9=q;O2TN5=gY8cGgl|BSyiK9M?I43UPVL(utp7Q9rufBuw} zmPW#9iuc(>sVv|7TN^~K5_VLqg?=w;J8_vgIpZ;i7!W-Ph;sv^s}QiO7~0@$n5LEt zSD%{wl9XbmD#X~ z7vLq7rT_k|YiWgcR%mQ&sjODBVegCbz>KS@Pek)co~m+eX6PMOJw5i)vZ^#t59pd4$NnyLZv4e*k{jE3VWG;H>*zhM!pLUouZ+K^PA zAFEQ0{(;I%u3t2hvfk8r%{4cdf=uu;z)@oCK~_&J!82rEDLs7zN&cPK>WT7dGS=3tDJdyYmFaY}E7eu# zJId~cxCl8v`Jw!rJ5w-F*dm@tdqd6vhy3#$WKHB!KDvkYsBc1vJv^~;V5*p8I3kn7 z=jL0(malmeW+nY;b!?wR{^sgJCXy<2kc|$#6@{JXAWRvF;qZ3j@9jS^bdEO!Ke$g= zk?x_X%J!m2V#{}1(fL;6x0~$2GZc=+g|tzBkv?60i1*w??f*B^xleb)Y6&h{1SuCn zxSa=i9&g@27>kxzK{VK;FA57e`r6L9pH%ngF2i-muil$(GviQWS$PG$%x}MNpIXZ3 z&jhugove}Ek5zjkM{Cmm zW@woNXP^6GMU{!Mt*)dZ1zu@M|P&4e_saz z0H6YrDZuCPfcPKnc!t>+u$K`o7`XpEuwTB7;DccthJqn#-=E>ef6Ae2vTd(50Y=34 z-qo*87iqu3mmO~8vG2}WFa(a#ygLneCg~YHibIuctl9NT$k85q{_f#okDbxePF&Ty z$x}Y=>qTYa%Lui-$mw2v-nV-FbGE-&HyZ9uF*&}Q1} zk~Q+Y#f00vZD9i722!sy09k5SvkB#Wb;ghS{M};w7DKdJ=jYezn|{4`KqaDTWR&@( z;4&T>F!gcgG=|%Usk9fKuuAysrb21mGY5wk=2t6Z(yvXH3LtC!q^zUE3{eo;{d4Pf z=_8=Zw)b2JUhQkFcx}YOUY>ve9FP*eG{k=9MW#<9z?HA`CLnN~q|VYVI>hDQk zC3!h$0$s&uG(=!f1t~wxk@p+%@6VdSFB%#|B@0g_n2ltVmSgY4HuKUX`=q=3R&Fk@;F`>pDz^W#Ey+y7 zRSr>)arxai9s6CMQHi9rHE0*!?kC)^bx>vzVy(u&MQ34Si(Of<1b(rP5!0VnNkD8M z1G{lm4VT4mhLyFg{=xNWghBP~Gs7kq>d!HF74XS9I}@a&GKKqCTuKTnFpz)#l!Mmu zjfn}7!ecKSTu-XJ)m4lRzcjk)%rC$vc~?cxB$~UlL|*wWx>=N4SXg^uQr;Y_=b?^AsX|GeHaTfTpSfl2)7S1{$pB|8y3xBANeS`PQpSDYHFHChPRxZ8h zRl1Uh=4j}N>v^Ax@2!M)53MSD-#!`OGK1#jwWiHEC<0|a{fby&zfY2r#ZdSL^N{){ z>#)Q~@A~_ZnEU#J?`k_<^p~?3LF5K2C#ok_;Hb-UOyx;n+gXr#{A;IF#5o;Sry+tP zZ{UAC0xc80fA&Z_Za>}*1Xd1jZCf0RLQvZ<71*ULN*8DNZm86J)V58OlqNEqbPV55SA4~ww| zlKe$K*lAK?_e0q}Xq&7MDXfi^WpHFV%q@SGtKxJGn|R=6EjrX}@63VHn@`KhH1nW!$Ne@x33=jqf zFTtQ2v22}6&;B}>g#m2Z!q4Y_aq{nTThFGqIF0>V!W55k3Hq;k1^}I>VwD{CjrLDG z)+(gS27+kbxffkob34yBr8^Dvx}-ppSPCK@23=m#@dC#eCp!p2=H_TE@YVSI{gZU{ zg%8%!H7x7rf~~1sBF=8HUX3Zi4h&>NkVb)7FTXJOJ_GPCXWLY;+g!u$$UeFqtj?fle7KWk0nH`(0H1%QhxxmwT;RBQj8j%FO zY-J?{bGgql^aJ;6KYe&x%acPigqlr=PG;$UMM@&q^RTKKca>Gk;h-0Pm)G4YI_}*U zl!h?MhvDJ&iSZ0S_tET7+S_GI%P1)Lnf`;72qC$$o)f1y9lIcLLLU+(j{K9uyo+%M zovPYeB`p>re98O-wHS2V5EL@o+(43v39^Zqxg;o3J-j$1R%kkMc%9R-Od;6B`d+kK z=0_?q5)vnQ5;7-sUtgbeF7wMdCl)DZwnle30`EJrQnc`1fu{KaYQo?u36s@03*jA; zaD^f3`d9~XOnLT<9b&==YA=9s-Q0NTDJTTXN>d4HY)qoD%s-_>?Q>iE*^|+4@1RrE zvepP;wu@zCVIms8u z8#=yp_sz9;72ywRj(`6iU(kZ`Q=RyzW4Q7G*5QBuWrrit;qr=@4+oZM>FdkbZ?T0( zd>yuR-C@{;EZI#d4l`-F{8#d=cSQ}9Ysq#Tdlj9N*)F>d@b9RmY;fqIN_ zO+Toab%qF%RfWbey!D8i@v|*W?&wJTVr`2L^U&f&51$QiQ$t8yG~jrv$lyvkv`VMq z2+X&97Ck5o3=GhyKd|fu;|IEoqvL(}G;O|o;zIA!i+;p&u5eI>R1PmZVR@}gdPW5I z27|1^lh}J6WG_XDjCF+Eu^GSpjAEZuc-W)q)r$YHO;6q!N4Y3RKSWUVDX$_)B^7<| z9WE|p+{cd}CuY1f%K-g+2v(CoYF86>`N;$TKEmoL77giZ1OrJ`^%2kT+dNmAqRI1r zPtI45)?c4pAB{@v4|eQ#q{Ceb0kh`O`7OyamSDR30lIePKKa+j8Y--*5Qs5C9|$mv zUY9j0@GS0Pr0Lx^h+p|TL<~j-R@Q&1>{>f$l@gc# z;64L0gGXL$L)`8hdT1`?qz!PfaKv*XfC-moH8$M8V9=Y~s7%4Yk25Cjk)N`F}K>WmJ}H*R{8R2-4l% zozmT1f+8g?AR;9oBHbw=-5m-dASm4}jf9jmC@qbA>wd=f{@O7HxHnw)d98DE$0LPu`!Os%qsvEs|vv@oh;5Lc7kMN9?_jXlyTw9pR{T_9;TW?NiLQZku0+-uDRoLl7#FJ<`@lOs9O4n} zcARZ?ABsM{kYXvATw7cJY}5YH4hpUsIcaI{#}wy0QfAndD+qsN#QWw79Sf^{`-99! z>^3_>$5=Ha28O(ndyhSZ1n3mz3W)l1+ zkgkeS@Q&e)FZ&=z#QmXM3IfSJBYK9Xd2^EJ&jKRa9tIc^d|*`EJ`@)e{BqvF&wgLi zO`v7Z36d+$B9y?UfayaX_T30*w9VxVqF4SQv$&`zT*~ypX9!A$`^?oWIblfuQh>n= zn^#^U1?A;vTl>A%{9`|UC|g?6Lk9zObcA2;!|WTY#(`GuWJAWo+mr zpY^mSLNH89SkHHNK}v)T&1a84MhI;PXopk~LmN)!GDa{Y|K%}y+>$}C7J)R_ShEh) z(-SfCiVW@Y&USwPxwauSyO>#vh!EgS;gONQW~!~C;V;Dz1po7!si=^iq|@L5xD?=O z`uc+JvVFN8am-9Pj2CMaefW|LE-E>B`Tju;S}9*7shl(b#DI4L0at@(8kj)9bqrx> z!arfs(L*25$;BD<-zR4+4_!2ao&zdsi-rB`ghYXNyF_HA zmjjO!77wnvxBm_(`?mhpPZ~@TZ&CM4Ie(>)^dg`2dG~G|Y`@FTeU!T%n=$h<7&J{8 zxE?eH9TC4&mA*M@Z@{tsQEBS8H@KMyN=P5@fEYPBe=C#lNJ97T_|z0r29cr=d*sQ< zNgt2z&GVHmbj0?^V~oD6qACTre*FlIuF&C8CE@z~%c9CkE%;0ek$V0MpM#(4|Ca4@ zJMVLDGzxRuSYAuzNbH@dH~!#2tlFVgEOL0`{nEc_o-mv%h$NdhHLRePRaPbgrgs4H z%uHG(53gxH;Lp~XcFoh%gxF>;%;|uqL|65?vakMv4)Wm z`4Z073xRJXVf$YkEi^PR=e`KtkEMoFFhro_`{?*jxsG>eI{T;wjOE34lTZDE*e~8p zt}m4GJfg?3cB7RhooaWB7BOg(`Vc+Od*_vZIvi_y@Vvd+Ar$xYsD8Dp?flg!Ai>Xb z+Go2ct z%xCyPqB+S*OWBf)rmvZ>B0Ex+eAVGABqb$-Qd<0uK3}A?UNeCMyy>uq0(L}*e$58n zkhwX5nK$WaWO(h>3yl|2{0X18>?mhvHFB~8r;N1ZI~9E|CvsqA0m%ZTqhn>unm-IK z9Vmg5My%|?Ns5%>Pc+q|h|~i^sCKH*b<&)LL}ATG0+WdeCs)x)$6dg|&bGd!p+>ae z%EP|m@X$}Ti~tUuM!mTFb94-hk}B;GGwEEP=^s!z$w~P%@($ll#C`-FmuAqcV}1z> z3hDBFXtS&}GRiMdo!Z_KCZ(02tTWQo(ZO|i#o-)#$K%f36JNBbkYm~9*fxjzC56UO zsReWj~vR^kqE42V?*6o4n z?L^MME-Yc|giObxW=SM%O%SvrVQ?CCk+rrli&Nj9CGj+ zpaum@RXm1qUBq<_4ICRhV96pa+`M?{uT^3Nt{0P(B!ybE)|(%G-FAZctoQTsP`%$A zi@5c~8hxZ6@{8pyz>CU1C|lAtND48$dX4EI47m<)st(x-;Nguf?m_h@YS!RgUui=* z*(rIQcXwi#aUVF)hqw9P`KYCCs86U!j>VmbuBa<=7nfWO+&CZ(!gXQ5U5wj|yx#WW zXnpXM#Q30GBbo;9`+n;KKAVO#S}W2p9Evn4$)w^`+;2!o4cz<*Fh(LFBg1c`(@ro! zhKBgs4pWIFLziuCC}8)_;ls#_;|OjKm8 zl0@)46Tr)8%&ss$A1SW)*fZw@wmYtngXe`?fZjs`(wLHJ!^+LRc!#-ps-MoC3Xzf6 z@hK@Nspea52YvXZ^{%^VV#HkO{V@3QW(-GDOOh+<0#FM9Mg$%sVo&xsCAs;{UzxGo zlpEyZCP3ndBjWwltu(9MQ3?RO_tnLN>fz{UyzA5YC7^v4i1-+`uXx3EN0F>@vj5th zGfH=Air|^1UWpAR#V06$o(&F2%ynA2;Nt6jj}iV(D=1A9qjirUOt#I*(y|wq=zZ+m zOU4hv(dGucckbi`D-wh0)C9;kRxma%6YW?#;A~|klsjE*%$m)$A4+^;QFkQZ$ckWiXLH~gs0LTwlEgNd? z{ht@0mrUB1@<4RCn2LbsnBR(2-cT3Wap67zbKAcgop}s-;*N4{T1!fi)#a?Goj1y& z(Mvv^kJe3lV)u!=C|*mMLdpP$Y@grcxLAVI?x{?>M^ z2NJ}HL4Oz#XN`=s1aubQD23fUJpL`P_(BaJBm~s~ADZ-Ci$+(s#%nWc3dnC$Yn#SU zAOFzR%}?#+OQHiI!x32@P1oVxJkcVgf z9D>y$u(XtwGS)5`lI$9xp-lxzSyICV1-;cbH311oT#a{5u*0x_u&vHt#*AKh(E z)tI{ecSdymyAkEO_UNK%$L>qm|XeZxd z{P-<>Zp~VYg!t%Rpz+wHkJPn;tnS4a+kG6n5Y|Hm-_b~7MY6~N9EWPK?ck36Ityy; z#gX(bKOi7AR&+iP^&!HcXG^h@^=S!ss;(=;vd~B>-n8AyMqfK zggF%&kq>{0W;d;3^T$!I!)NZIHTz`gdP5j$Y>$ryd5(7~s`!CBqP{wA>U~l$bwBc# zyzb;Vp8iQqI90H#fLNf{9lHN$wt31TawK>wJHs9n7Rf4wcF^3Gt`?;P!_$WTNKscO z>CR>|AbJp;Eb!VUi;lRB(&1M939WS*@Jb+5{OZ%BcAYuZCNBk$rCdF9shq&2DtXy( zC5y79xbK@*h55O;ZLA@panI4zgOk-=zaV=UMkqb>A4v)CD_Ss9yfdb!&F=6kd^#W@ z-sSb~Jjn;7+VeNoGUR6o7%TU%kcIV170&|;jyV|MB_nsx`a`!@`q16c;}`Oto7 zMn9$TKTLb~26N|XJFX#A^QmK}gpUYzR0vkaBRFJozoGfgI| z6T8Kyb&+YSJ$bcXT%Dg{r=?+t_%yr|in}+M#O8Q;q^=Z0t_XqjCr_U8zg#p0AOf&- zWdQXJ;G+ls!WAaB8J;xT-W6Vwz>7$fl69Ge9C)mkd^oD3SP}_yx+2jzL|$1^|6MwL_~npt(};L{Q&iK{ z9VfoM-RGOHEw_kXloC;hmJ8XtE9xX-@4Ryo>z}M zSK1bDLfY!@XeyFr7v4%3+=Jv~j!e!Ruhu_nM9ytue%QxHIfHItq=K_$J$$r2l+GKV zQ}O=A6JwpaFJA_1qe7fpb9Rjud`{ESjGbKC(Po|tR!?H{q3TpLi%?<*1_mC07dh$2 z!D8*1*LB}&t%m`a+FXcve3M5<-)>!7EK}dzcr_|bqeggrb!OGNFZKEO7_N*%sV>OI zUz&zzss+N)V%Iz8omQ(&8||pYlK4L+i~sgGMMcHmI}^zNng*8>*WHixAc2T_9nd1i zKSW&61VN(Fe3YDqP9PB+VsN-!l5+$!n$gjB3I^|!=U>*>ygAd#FDOV$Pm8#i&~{n$ z6)$=72GN-L1ZvtLtI+nxqXsOpi(ac(fUgegwp@uYTQHEJ76mfEtT!C za0gLLQpwFt)W)U+SgOy^W*bRNy+lmJfmXYsvk(=TM|$U~=sG&`PBDkT^7g*6E+I_J zLKKsgG&06i1i;qUS!Q z@a8d=1w+famksNkFl_=OUD>on8KoX;6Yh}8MYBryw34T%_ciA-?dM}#y%LJx(=Bam z?3Z>Ahb1LEmZg>0wzvOQf`j#O2?*3c*1mg}==Ur^9AuB4Kc@oo7EwgR-%OkzF5FsC zD?UOI^7z2|tQ8bW;B}#A+NPSxzcxv9#exk26dEGRO<|UPSgg^$dT3rV3%!_pV9o?i z8`SuSfE~owl1aI_u160!Ylry@mLBz?#afI^j#B*W>6(w# zna7Q5*OAds!rQFhA!S<1D+mj`7ei?zkhWDR)IoUH&0DN@U?7}|KNw)9P#A*1K6*4# zg>|-H_fi*_1&_jf_9cuOewWWjjc-xG2CfC=Gp!s3uf(!IXidn>jPAF#+?gyB6cV!3 zulV}B3pa8k$@yUUtgE-)!hI&~l^4fbS!R=DR3(kUg3ohu&iZ(HF2g2Gr3jHKWw3j; z#`J^9>j!0JScZ`!@0`D6;upkkbt#BVI(&a1jGx1st*0r_EP}p`v*G0-bjMqA@1L*M z#u_dSg*q6!A!FRt-R*Mf$Q}8MS*v2*$3qC}1JU+(e#uM|PlCQ^cjG%LMRUW>(o-FX zzoF#P$QL=f?+z0H_?l-NzXa4Z_sZ+|heZ%*lEyeuVz;mJo7c^imhY4l_oym|#YB-b z3qF{&*ZZAx*K6C|Xbg_EtE=jH*hPd6)_#(nj2F|IT)k{Udalj$t(<+bt`1DeO=dh_ zHXUbf728+&EbGK{St`Z~?LQecx-<^Dh>(Apl@NT8=5?Yg+*(o5TU3!dG9C86=EJd- zac4er%Hed}YjSDG%s_m7V!QBmDR$72c{!59nF3RW())(@!=w527RN8xO^bStBZ5|b zV{!8@V7<6JY3GbRwrpx;Gl}IyPlX-m<59``KF>{3qiF|=zu;>7sxNZSgM>YE=7hTm zmYO+nsI58tK>^%h$bdS4-Nd#&FS+vSney^qZ*;W#9S`0JL4$!tM8wK!ob(1%0I$NC zw*mOk7N3NRrEnGbgp>NkDJ?O{0qX5?<7nPs{Z&_2eUc#^*L$ylM6zze zQI>ncG6MG9L@1#r5=6tsd8OB1b3##ZxVX60)bl=mq-iocN%7&8*Cjob$855nD7I1V z?4T7)f>H4UA87-#SbFY^q0rV}PE}R9@0N7DBB?`sR=i{}tYa(ZdM9@0MCj;e7J{d_ zKlV5OUHSDaQnEPwAuTq0#lX{II5I4}6f&DSQ2gG2^J4a45=BgHS1;lOhYTv10D)SI zC{R0s1D=3tA!ICd7#Zg}8ssZ*Kd17oLb1H|r^4G!M$gpJjISXj*+w1RGkSBwe|`$N zQ`^|=^SwFcW!6?H+-S~O-emJO^8P9=7?;CiI`Ez9ijN$*VW*q|giR`8o38xlyH=o? z0ohyK}rxl3qeXZ}SdA10sH)!WlZF++OfW70K8;N&f3+9*^ac}H~Ze@E2jvz5JdeL01`VSdO=MnEVTao zx+Wq|o&ox~%xC$z&KAXkzf7XVrfOU}o;%A020@DsWkcT5{uxh3d|E`y*R-HU&l>kG zG&MxXr`}=zXsP8EDyMXSIbZ**VB!VHBS=76h}|z)Qu+Ew zy`M!Cj4jesR7?sv3qyeuC9{i*T0Lw1aA;M(KDI@OfY5{j7@TEj!h|s+6idAS{P3>N zwF?(?x_ztN{*(;CK-NvoEbBi}>(QRrA3r{IF&J>p|1;*(@Jk*+&I7JXz{SNi{XPnI zCZ(|`<C)SwXy!OU@({9h$NmJ$PkhWG7QCoUYYlCI?Vk!mLQD-$ zkv&MJIWd@fnMiq2StvhgIw!s8pG_d*oE;b&5Y_&jeS9Tv=C}RNX{z4uXSs2GNwLE> z+l|TjmpKy)n@yV%B^Na#eM|MJmzT7WuHj+S19`QAI8~m(uw)7aK^QF4>hA7>Q2n?8 z^s?N7f}oer9Ym7j+qZgR)`H74((+dmwHG%ax*)4d2xL)?Sit-)_tBygeim^~*cTH| z^Vq`0Gca(ZDy7@}9+c_$d{yH)oBd$qYy$=(x!~;mmp29RPbvF)fBbAKzqfKw5y$WI zIb6YDeSo9N8oKkq@ep)y;4F3PBYqEOV|39>!OM#PT!=J~@ll{52(q?0oQC2mAk)}6 zI)Y*q2y;+oSpH3G?Z+oIs8ApWtv(I#L?T+!;M?6Ak9HzVC2m>L*x<1HUfkN2pXM|-fR!w=!-g$U^sQJH#Y@2z4Z?48b1L7ioFvL4 zVRHl#uIy}T=<@|p4>-yP8B5op7(*5fsE;iN#X1V}(q@Y8C=V^Eyrk($JrLyQNw2JQ z+WW>qW-}S|tgRFF33m4O767U)eD=)EV^H_=>)`E)XkMYAk1uT|o=Wwt6?H_D#n;wN znb&9Vk$&FVi)9g=jh>f`?p>wh63?hqP2`1zZsUT_o8t727rNFLz16c~kfUH0aQm;3 zD!K;+#GnSZ?koi}0(I?MlER;_qd&-S;Z=&skx(*KRuE*BWjY)4#lM&s?e-%62WfH&Yq*z`%%nqOVdfS!y+je08Y%7M!6^swy001 zjFwh0(QF0oG12c56XhR2CIK&N@uyGk4i8jyP)&=!OHD!%n5L& zHsPv;Sg#;lE1ZU({Gs&qf1oJMjL-$A-W4=D$!4mWS=8VfSftz*eSo?y1bhpT^oI|N zwJkp`fqe0BkNGVv8QJ~gXG{bWzNjx|)J39&m$#1cv@iWrhx!fkDQAYG(k3T@460a2 zsR3t%p;bcQ-`*~Zz$EO`SdJIc9&h&I5=_J@Q7ECX1*?m|P?0hi(rwx9h*R`TPXg5# z?a3H)va$1#(jWX~k#CGKW(gY~%WmsWl%$k8qMC!H79{3@fE8ZKDh7|0g+(f05k?&% z!XrBzg{<5gT^>BZ`*j>IV$M_|Kxo&tg4SIe!DvmJl$vVzySF#oPDe%U{`y|yNJ3g! zd+K||-aqvO@u@1g#i56ZEG|llV`rT>Ti$O}J&TX!`Y;N`9#-Q=yujs-3OJ}%4%f|I zMYP| z|GgxwprOHu+KEQ2jtf5^;681;=a0k|U!jJp{WqbAD@wr~iTV(yFjyv;$el~6c+Rv- zl`+&JJSJr2Q!8!!gbUek(%U(?T1zHt=XcqStqi~?+<)2Z#nIH&6>L@WF|3r%o73Za z_7s3|RC!6E?0$E3d@ z1J;e;X8KA)v>D!Ih<-N2wtRTl2Jof;-OKs0URsLg{tdgMLj&&oM-Un1_1yb;%PbD} zx6x4@@cBZel%iD)A1;0%2XpAb0UsrHNGTf0oCk(SK_ageacJy(8d-esgby8dXOYQo zcgyWv2Gt6i&%;VqR;w3oc%+nTpHoSF`8=U%mM}k`(oXQj3?H5nsaRHgX=z&OuogTi zS}9R2?{M9PC{|V;}PL~%KWV^;WIyA}(E3aj~Qv=kO` zkz{>m=Wg;rkQnh!fmbmO=;GbFIF&27}07)F!T_z+aGs6nzm$L|? zfWX7w9#^+xl7;A$f!EqP6g$F6#RO%ChzkaIHXynp zf(Q?QG5Ix=e^~#>mK>iRqx)g<=A(jwe#GD_jqEG=Z_zQN*2DFx;NuaCkEe@YlH}6U zXVf!bay=fVH#Maf_l)#84=8nzK|lx*AxahFj6R~r;geJSpi-+|E0qp@C^sHeJXe}xy{%{0BWd};rFNYt%M*O+G` zX!D&I1Pt_y$irqoB>1k7Uga00iArb?12hHv)R5-Po7mpk2NBWT^1!QY32p4n)}Ll_ zc}1*^F9at;et|EiiB{c>(gawt-(LOe`tU@X9$#%Xo1ge!)4R)jA0H0t)db_V3+e_E zr{ebaYKsf6O_~oK^xVqP1c>BER&nc4lSBX{mU*jmyIeCldCtajn;e31tMI2q32E=G ziO=zs2!^=?RgzBl&m6`5UsFkE(Xi-;2EAjEe}8|>BqlhU@@7b1)5UG(v=S!TISqt+rAIoed0Fhfsdt zME_%oSIcgl*qiuY{K0v%A(~w8gLk*g)(ijHic?-QnEd5)f3&h&k@sw|lk)O6uWp*I z!dlniTH>46f9hVF@cxYb6GpRj(MiMl(;!Bvg6_Cn0}!n32%jY^Xq96rEdY@LS!MxV z-Zlt~o&)X-zVOHQCDWth<1stI$*C#On~{F8v*TvPuJk9mP>Zx}C9x%WHDBNC`)$JA zZVfarh|MP#siy!rAm_7XcjT>==PYRQfF|5Tu)z?_e`q2>v=~;kT(ZN86VP*q;BWiy z6Zpj79gE-|M3L~=vU0@`*QDK*lq95)js-KMLAF_2rQJ5K`c+|Se+@9jl5AUsu6a((rZr-VZzb4T!Krir>mlzMpgUL@bk7P19+ z+gyF_{>8}H)md8qUL^&is5uL_p5jey7P&DNa}au%l!~Dt7eqL}q2{)tn2hK=^<$&_ z)8_RdLf{a+i`ftjddh`1=-78Gk?wFA7~E;`6AG-2)#aqg?h*V;ThO_jiTQRzeOtdb zqVdqi6`2Iz)+LaR>N}>aHBxk#e8xSK6qhfWoozAz6e}U6(qvh4$Lq-xCW}HXq3S^4 zGH}1lB!>Sm2r1ut=J3y<4DYt#yH9je+tF4WeMXONPNGzmCef3GT zunZP?1n3dDGbRgzItOU1PxdNEtSmZ1J*i1vV-Ow29x z+z=T%413>Dm{0d!_1y&C-50o2=y?U^X!qXYb~wmx7WPjnXVf8=bd8TL-{IV%$D+g# z50Yd|IBY>rC>mVP|5zClAj^Z7C!|!NjcF;k*tQ@-Lk)S zcGcSv6cX|YqLF3oX4ur%*e~G6yppz>iXUoUQEnMqp8sbHE}Y0j@7c>qi&soK%xxwN4B3{9ZH(^bi^B>s`;uWoi3%4FKFnl zpy#Kjqm`BC7d50t2i!r0=)$SKjO9Ijq7D_V=xJF~CX*zY`dQ`Yxis7({{J31?;L1E zY;;8sXrzrtPdriddyby+3=^M}wP4;@mNqZzXh3e<@m1m6PANMlV|5L|OU1i3HusT{ zF#x*S4UZ*+*bi5G?VX%9K-^iqgnPEGhfvEuUmqzNKu<$RAP}j&sro$C9Sx4#_QvaYOhnr%5?saH+xNq8BX6K zfYO^KeY1^fJJh#c-OIfKJuBDR#f?yApUmSI9D-5ee0x*V;Itw~Z1b$f`kxQ+Q+K?_ znrR))o=d|JvIngP-U9KNnL{%(qznuUPy+-#V&Qz;vG$KmyTL=#aDBv!tp;fSEH`z<$>UToeLM z%WC8l%7`H#DkU+~I@1N{!XgF_|X<2$J>{t&75= zqxxL&9y~2<(OS!Q)+MJ{lQGL~x*S*kb?5~ZSyG8F=D(u<#qL9_HZ>05&y2|GUI&4&aNQoqOq3NtB86}F>omorIlDWR58$x&_I&8 z+=z)@IGpt({SG%e>JpJ6x(e@kXz9*Ke(jgYpA9mT0|NQf;`HsCz8nLi?@)gDaJ9c$ z>RVpUmAJ}S>N_k@UO%xnAm+lifBsOxVCuN^{g0=9rU*~@z-$e@dUCjtDHnKh-*4vl znZdyU*ASQ;DTKoLd1;SMCi01i)n32;3R!)Hx%v5#!6OLJ?^L`Ts5E^8T~dmm{V_sQ zW}phNjcQO9RAnqY>y1n>mMEMZ7Wc68#XyV#c{B_|Ka<47`s;qVAF zjhr~TrlSE4unykR9Oy)r-osWon0l<}(zt#+A}w7qX!_Z4HcLZ z+e<}-!2IktSt&*3_({v66!#;`%g2jYU30>dD#|nU4PMJBToNh@y|_P6ZXioWWw18l zhdq!dUp`r0ZbC;t97*4JR(@139Qf;)?dY&qwtq+>Y%(G;()*&LkP&lXAydNH9~>%? zn2NRbb1Fv)1q)xOVl3|cS#%J3u|xOZmGpzU=A@VW?^?D;exviOhv_jemDuu2Tnp6L z{$XG&3`s~Jc4~aVqp2y?<0*QsxMF$#`tA(#KEt>{n8y-p`ee6Vx$3=AP=(yA9bDeEecGy1%=31koIWR<>+msEDT<9%##`b9B6Fs=}v1BzVIN#v^ z=})x#@Me32&2kv$oflp$LC@F`?b*^RlE1HBc{MX|nrxZh{@b6e_hk87SJ$wR1xd#% z&~DZom9gyOs1=OS$SF=VzZ?vo%5t}S@TaB9l)^gWG*;4l!Bx;=Irge}}6XPZVe z&kGAMXDMb~W;zonoN_A~GZq)I_ZoS!S9|@Gs=~~9`@mQ?2v!zd!jSSEhw=T^oIrtR z&i8^$ytj*fFEpk!HEEEO%N@RsOYmNJmGsKP^-EP8)PIG6O&v;Ibq(IbF8#B4yUDdL zOg8Ztd{G2AEp-(%wSH{~U`zFDN?{_e zc;#2x?oJ1h#t8kyqZT@rEGyJCH5yKvcn0CRXJ<}aW>u(f`~G|W;{%fvH!#k$;408X zWp6J?DIMEblgPoqz&7(Grpr)3Yg0t}_fk(w#W*SHhy3EAgq(+ZynhxoUBlzbM8$ls zU9hgW)e6r8WlzLg7#P8tB^Y!k2d%*bM!DY_%dVpi?dyS$SWrLWrEo+<-WT_6jWRc3 zRd$!eC5coo$*cEFDl9}9m&Ef5#Is`{=qLS@pZ;qr`T?&U1)~@rxto#TR zr^$o6eg1v2pOoZO2pQc2+W$)pK+C21Z7U~PZH*Z^zNyibG>zen6WtT&+1zDw-qzVfx<_ zB*T61AF;6a4T7k%^>^ExER>MO`UG$9i@8v5zWQf^`Hb+fAECK0KJy6*2AfqWmmH5R zJq_=)Xd533XekL>D`Xt+?I9)1o68W4Z+07fLwDE|Ya z@cGNdeF!7WfDQ5Dt)ebkOq}woxh))RD?x_jt!(P1MZuk*JHD}xA}CaHME+=?Y|=9@ z=+4g_*wrD-L{J;AR6xhc84bd_<6R+3smPVndGU*Np2WVv3LqIoU+C&aV`G|g@+0vs zlY%$nzM`((OPr&Vl0#ha0i-BYycK9dB5*5Ewegx%)$k#Sr^bmjf4A0$!jI}ms!@%b z9QY_BVDN#<;@>S7d~H#&YaxnQx1xync=I}7%=Rf{ERajK=ca{k*ZD@y{;cNW3>zRY92&+^sHXW5S!gjxJ(Q4-C$acMCHFk`t}>zJ-1ko} zsAb=XFee7}^>MJW>GG1-^r>iVY;NW}yEJ&5Cy>U<$!QJVDNJngPxAy{9oiLVu#>G+ zQ`Nl`go<LCe$Nw{ z@#HLO-`@V~y2(3xwB~<$f$cX#sqv9!G(Asm(|fZ#zZbN$?FQ3z$yO8nHa z-4OiZ^Z@qIX#M^7nG?~&I%Unws3Ev@e#<&;Vq!7`H6&g;$t7r^CA#+fE?+y_TNW%N zR`?2Xr~*F!82F%p!3#YaB+k85$E6>B=YRPU2MfPwaO>fbk*Qi+Q>rIBZo~(QHlWe{ zPN%DE`XjWpMJve{pZTN)A)6$|Y4x1gybJ2w7q~s0FB5SKz1pi_c~p~nb)wv0TaHM_ zB790ezq)Pjs97&b2?#Gy${KgGCA<>b(99mS(fl6sE4Eh;6tUxBXm zCfcAvhye2&FFDV0+OvOsrk)Ei`K-S$k8@sMuc^lB8>|fWTYxtaVc>QA>4nb3q+9DD z(c6tfwW07(wEHi2vGnx;3bhJ}`m;X4Z10^X+<@37zw!0hyb0;0k?wB~nmQ?anCkv_ z>fqniyG%@ZdFZ_3eIs-gM^yl?T6ug|M*=)y%*CyPk4! z7}oEe;_f;z?$zd&a4(ba9AyY_)I{_8=97&rlC z;nImPLu*U^B?OPhYRw1|A&D_7r%(L#8>f7Q0t=t3s3GrK+_$<+wPIx28f(q4_=Ph+ zt``CW%{`x^c!~=OgFCEx8{p_cz+NKr&4pNwd!;*G)Ccsj`ObZd=!HGf`S&b$K(fAj z*9Jb(&JZmYJ}WDUtJ4fh-vj(mBkd#WS9Gm@j^vUVt<1L=@AoVjShOStq1C3VP_@8u z?u05G8tqlJwZV^fd6fa?^x_g0Jo2{LIkn@p{>d{+MqE-x(ThjGqKXI)reDTCnzuo>G|uMll=MPVpq4_rKBk9CZM1!kUup(JFBXucE2Z& z?%?8Gmx7Uz(Wlv+Y9nPkhXF>>uXl1rhDPlP`u+Y1WVPN(#$exr53RM0O?=9ah$Wh} zFT0j1Wf$Ren+E3-6Dk^|K^y(J>7nzMd1Yl0Nl9W&W*nWJsCs(YB?YUm<>WfTBenuJ zIGtblE9E}NFDx8=z$-(GOUoxkZDd5ti8*9z=K#s6Pcz>o9a!Xz?*&h;#%&dU6~%bG zpf%bg^N@0KQbuFQTS+P5{A!5;7)1RjNcPcj8GU_r`Ot(7i#b>VmR45ULd785#t>{+ zMDW;Se=(-r9D#v`XA{>LckrNuu1S%=&x)T2b2KvSjmoCF!)reqQen-(=q9G)P(<#^zu zhT&(+&vY_&#X{rxGhPjB3Q&TL5+vl*+8nvxk}(urN!}5=pnc4M+e(#m;Ax*Oode4` zAV}oZr%dy-GVX$#Uthm#d6^c@pZ#x+)rRmt!wh=Pz<>)d@_-qs@g?&G{eYddQ_}nN z^j1EeZ43MfZdn&GkdK(*Q3hHmP;|hi4yE9uTYvSkQdLf8YoecUV$SCGJBCgw99-SA zBd07BV+vWTnshA-7wev`$nuP1pi#wk=ZJ`@!(Wi&Mjva^4X zI5#KQ-tJF&|K6oDMz~=3&>I~K6&*F`{Ki*7gH;WeX(lwarF)-7t^zA}g6;gq2{Y7G z206eV>pbDo|2?+>ZVmO*FLAk=Ve(V&_HgKepBAvy48*|pY$KjlU!pgT6!n~zV^NCI=qgWmmW!JH*ecKb{SI?Gka^GF58aQ zdNC|!^!N+DsyI$fByK30bGpnjt5bcP08Rl{@MY{j%XVw z2UlTyX)R2ojtr#RohP|!GMfx6xXZs}{;QuK*;_!}*=5lYBaXVOsH<{>@q^WyFegr3 zJ~sYi87(eayWRDJ7zf5@M0fGp{bDzcX#Q#&7$nL3hmm`JlUu%Z*Jm^QbG!LF2y>_g z=jP*LvZrdu(Jm`jlyB)1m*5hDtOPf{$9+S9*;n5YkYow5b&`h28)wO zENG}flw$7TJxW;7*6oig%z5t`#W-J6q%Nu0UwL9;VP$!eCD5NH?9GiN=d^Z7wkDC! zs_E+9a~9A+7tAp}yFyC+K3ju*y3ZqT@X|kly7c~kkNf@$YDv>jT zkX!h=l^;d=%_scdypZc#4P>U)OVm{Zah0^U;UhFg-PLJNOpXge8@1N)yw_=WcX7bX zkBCee*E`wV{=2#Te5dMZD|OCDFQ{X{q!2)aKfTMS4DafHzStOcU;G>|8c|2t;`uZhu#>>X(Lg|IfI@40Nzzab;1rxHqT!!eUrG`5_cwad>dTWZtC z78}X_e9d^yy)axI^w@I#h7$ea!soCX^*)B%>$R}|E(~Hg&3x{?e~(ovUAjiN@yTgj z>~jr<^H&d->vOuwW)Exzh6Pa3$>Bc`7*B{w61a!fQ3#ESN_{W;Cgu=Fv&p`E63>7_ zLD5eg@?c4W@i}BCT&;V}Hcszti9G6QYqPf;~abf2opws|Dh%S z+rIPjUD6okQM=0nuf2AMw^1_Ycgny02llx0ula7xsw1RUhM#iJFwT9rqyRjNhNkt9#PFVV;jOc)f*@S-zQReH4h2v zO_0(a4Nb4TAhbh3&I9g`?%&!(8LIh(ghI{yC<`j7d-hRwg>+0u`Ou}_;Ggl_s(pP7 zty0qJDcnq#T~2~`XtJNb>~Odd4`{MlWDA`gK)}je2h5w>)Xd zybG2PCjJCO8Uk=Ex+W&zKNpXa_TZStk&?+0dp}+F5W$&81U?c&8%Jqw0}h`y`+qg3 zz_-jjOL&eL&=3HO2|@B9zYC02nzvnf<9grH(;E(d-~d>EiO)(2HBD6Y=f#+^&+$QL zG!{0%%ey4ces)4)9*GqcUFd zNqpLRBcvQlQv#cft3BfZ@%;~*p9fu^q~eAAZ4QZ+o{FoycT)1N?c8+4aQz&agd-ODO9l2JozZ`KuW7o zOZoj`7f)}S(_MqVW3$`O*3~HL7k6ZJ1%EHTSPQt#sw@FEyO1syf%<5C5U# zvFG?YtoO6@mWoxBG-&lsc*fSeZG8XHSlfARYj}%UJLlhL4(Xh{?^oqw*#GvEES?o+2npCpa&Ux+Ze5kD@RZ+pcA4iz)MRej#b#;29 zkPPyb1dEZ;PUKiO*RnK{v;UcR5xPFj;>sbYRo z3{JwGoh+%6ZwRokbc4fE;*%e>m+?~_PQ;CVbbB~!VGUkRcXxN%c6yUUcHV5h0ym-w z`|Z3>-#bsX=gFu;4-c_3f6%NOS_#szXstRr6$>i##*>kGxwB7Ka{E5;5nB4>iI{fT*qsNyl}CM!)_v7hir+aM6;PkA_a{ zmneF!%bNXbKM*GNOwm0+#4#Q|P!=K3DZJw&G#-jHM5=oi)79;!w0z(1-$>Ky{}hmIg|pY}_aoav!({tT^%niGJE|H^QL<{bp+f7~iq@ z;q43b$C9yo@lS^^P#F8$Tx)LQzTg7Z41^fLs6rqBOd#UF(SStW6l^`B=Ve9e8JijX-UwoO6PToVax;`eq4r{IQKbQS zU?bbQP_WQ^_R@W$Dv8njLke5-F(A>c=ga*$sRjLzHAPUa)R!!&>JBZxzCHjlHGLS4 zK^v6yN|!ZBj;z<}oo31Jq}UQ++`4*F&xnKpTjLc+45{kCvl9$frO z=<1DB$Hs%!U(WK|*8;f<)W^rg$k2I3a<{Ic?&IZI@9Vt3m?L$2MuhK))5 zHR6Z`={lp;5+iCLwzd$&V|2|Y$(2(V)mbzts5U7ALa8)n?Db5Jtvga2)pTW|`K2Zb zLY`=Muc-EW`wLx(Yy{~LsF@1BJQCJ_yn6$>O^N!BEmC0I0?aaomuvbg(ShDN zk`MAw^@f65iQiye=Kq#NqO?jGX-grE9bOVssO{f?7DE6i6lqFJuUwwuMms#Rky+~t zxlSjG(KFo0<&D$6;GoE-4^ZlVhuK+n!p6sQ2AZnsq3)1?vP<7xM-1C@-(Bl2eJ6irtKr8ejUiF8^UIxzY6^pz2qB+@wiJK(ceaiIa3T~pTiG9tJ+6zE>wk+6 zK#8xZ5WvTAb#(=s5oD5h_~gOfe`j6eySBCmp#1aikk-}BZE$>i9~dTjdwb8pIuDEB zOVBd$z)-3^h&jKCW~cl51lfGpKk_)y99)^Q;(3ESs4jp>f6)?v_Y`+zK3?yNxV`nv zR-!fbzrprw%c(nH)2fR$aHSWpDy~GS=g?eeM1*?dd+qxgNUg80rm3VxAgvDViCCPS zAFvU}@G@{qns|F+Q@|3EXrt4C;d&ODgi2QvK8XKQTC}L*T^!GPXm+;ym#lxkhXoV> z46Dtf?XWoO_oVDA)FP#njv^v_q21~&NVsGR+HPIj$|^^^C8YUrH!%pvaT)BENT9_@ z#OwSCCYY2(`1=8*ZHnHgtNJ665U8)ZVKys`oDvcX>#56 zo4J9Vy){{z4){1o&vyLP_Qf{JXK~m2-Sa^n=S?Ye0wj?Dq&uR*Z*>u0lw_Z=E1xh7 zE?8}oa?wO8X+=bG=jNno#P^la5s1hc_Ln{3e2@%@VM_lSq2m~#X#31 z-Ov-iLaWLQeAj1KWiE8=ahz_Wa|J0a*7? zx$}(ro#Yc6)>B%{!+8&joLr`|cgdT8738~hwA57AA8_MLB|1++h0p&*l|cJxOpA$i zS8;OPn6qnMf&W(xfiD4!X`_rSrI6tv8qPy)b^PjW+RNY6>5m;Rn(2V;U+`c?y^@A3 zv`bsc#Jo3n>~Q;GO`&vfU?dYx_}{V6C61*^g>6y332j9B?v#3G?vJnB)ov{d*R$Fd z)=N=Q_~uW4YkoZ48nm`5O{(Y)nd|BvFE&Oo1^s8~D-`S0rP$rs*Vv>iR(A>PXj=mU9jVwV0I_SSVsvs&VFH90wb#Vklbh`uM5PHWS z+JoO~sh5vB-d8YJHR{O;EL-gfvS?l8{CnuSKO;Q6;`_2b#^)C6d229#_VEw|-4|&u zahZF!JVV~WvLj6%Q~|&>)hb@bZ!+kLS~IVX`!6=>L39rdQC>ke*4vZmPqA*XO3tG0+eQ0YA$iuV>tD`AI)uZLuTFdQ5aqwba& zI}d!H1I_ANrbCli+li6Fsl`Kbk#z+lBRH)rY#yBY!(X2e;|Id#E9Cxi`U^YrI=2|? z?7*;Q!8FW44ILfTuDD}`&k*%X_YX3(WBVJSRAM-%i)vxu9dCGs%mXDg>Bi5K@Mi-K z)qs#J80xrlk1~407RDH%uFDI!D$07DN9q$m=M$luW=?#7)be}p_rW-3wE`=5Daevw+k(~|_3SI8_yt3k= zApO=Gx2N65X4Fbeo$`lafr3IT@ucUUG&sJMv9V*MX{ ze0;D<)dpdh_k8}X5g*TPOu7FYoDc&@LRe^z>1#&!`F=ozor_EA@I(hz0+YG{ ziO%^Lxe`jETQ;^$lS4JyuZTjcv@IME_C6Bbs8sdzrf?x=TX{c6)!M#K2!@{TwJ*EohvTdWDiBug|xc59k%^p zRtO8jL!tj1r>~;!XA&L4_Jz@G_7Fj*3Pm6Xp%JA%g-jO@MQ;7J@{L9eEU?Z=7j(02 z{UnhUM+1qzs4RtEh2n;&fGnm@(|k~+KUSd;h9Mlj{+NB{tR{nv8_X^RleTy+{K>MZ zQHW~lYLT<^SoStQlMgA+q2Ku+3yyd}g7<{acY>w*Ofqt&zre-FK5tLj=xn$rIZX#y zVrrF$%X9LL&oE9HDF=Ocb2VCK6Vbg5RYdq}ogV~51{}K}@gNPpeArRSCnh;tUPyFSJ0X_l~9Wo&qcq4;Kk}K!Cgi5FkK;eXD*suZuJt zT-|bsn%v&~_gWm(n20OFq~DR_&f}d3Zt%9TztomH7hylG&-JE^4m)pTMO7@znr}!}fvGPV@rJA}enTPxOV=gR zAu%!I(b<02%QY=?<`)Xrznmq?PTSA_UPLg}vsEpJVD zoRdjN$UHpXQ3D1g^-^2w!AZoxi-QHbyAbWWXmj0En?y zY|MubA3n!DqhVuX->8*zBZ~c^bnly4f%`EpPc_Ir^znz_ z-jnl9o(T(ccXuvORiHID#}y4hM!q1k0}Z?yO2(ON0s9~s({ z;84_ML}|6s&mFk?n=_LMWx~<+!7Rxr*NmPNRcR_r0E{>8TVCo8?hJT-j1X}h?1dx_ zaX2j2$4ZJ@UhT2?h*z0Tx5_#C+~c-4xkq;K=|BAa!_#u36=mOg#}RlyXZ#{+HrvK> zaekHac*rG!_L4i0nUS%zGpL>|+z|q(nY-tkqk<1q=Yz1$SJ3CDt{V}9g3cht`Fm)H z-`?TYw?jc$c^WR;lfQ)>{oB=NV!0`owXo!rzkU!&3hynKTkj47LIhS-r`|Ijx5J`s zjp7osbW5!;`11!;bfg^RcgUsSp?-eZL=n(cTEnuevSs5kc~q)NvH<^pPfA+Sxz@Y! zE%QAyK4`pQ>vChE(&mPj6w}y#m{rmLvvWB-U_0!IKEI@cAF4(Qc~35Hk(mN0wr(d3 zL>JzdWABB=Dv$fm#?8UFz0$brtG8eF_rK#UhnFIz!>9knqNv+<|DooDZ+uZ^GH0aU zy*i;vWSyd|vgn#0mQq)T*f%Ylc!Dt^pT|%bdhPKedW63)&iI&_CPZFc?T4>lHhD~# zOF?4Hz`(_WU$3GIh2;6BxvLpwj*^#F1sGgU+B7jfjinL^Kk_ge`9jB}S$z6O2U87a zFU%Gabiv1@F8Fl?>CQKK7WyxZbJLr2YJqcEsABl;k9bz;VlpUAkN8m99ilg7dcgrA zxX;(j&o?#fT9v=S5iRGnvI7zS{QH_L-gnw}Q-SzdQ(aibjz};Z7CWtEh zK()W-&x1UFAm`GBvOO+3``_fEE)8=#dhU1x8$p;inzxt41_3Kv_ad?ZQlW%S*y5XW zlF-B13GP_ezi*+mh5ItcepxWqkQ86|7$n+c*4uHO*4j);d7AW z!mU}DbePg|yAt5n39X=5RaEaGJU_#~dFjG?{HZo=i!gY_yLVu=4-++Fq&tM(Z(rce zm#jZ6MOFwT?RdicLAx!KI*iMchJb#o49f*L^Pu(G&57dVmh@t z8Z;qHKc=Tuq2CmWe#-ug@(YX&Yk%&tv$IWpn$3z9CX&AN0?okiby9QjsckoY#HR0C z^*Sn$u(Y|k!nu3iBO?2Xj*c8xIfX3MZhSE@?aXy&b zs^4*bYUNn8g3E**7E#G7Dd}sRAiuo3U9^G|f5QoPBR`g>VTP!`kgct)LcB7^3ZDW1 z;DO9_@3spd2Lmg?ftotoUq1!KFj+-IvFgeJF$lF#vfVOD`DC1@mOy&5g7wH@;j|T{ zL_yxCI9##^7Vu!_C&lydUg~=)h#Q?A>%urWnlPnnVbWb?FVNa1|#fbseS_~*Q0N0<A}1%u>M+s(YEfP$xnIR_ls(6Uoy5IhNuH%}spoZ=R<=~~ zf%~C}Yw#@8L1_asjhU-SMk=3+8D>OJjAqieW-N9;R`iHZaC|3zdJ+w#aMtW;GJl-4 zHNNRp(T(BZ^9k6XIf?2x9DHV0;#S&FsN1SPsH4oI&gs+=mH%g#wc`+V0}Y1O4jbOadr!?So*4qKD>dBi*%n_6)d z)2TJ-hdKAOKm9KJw7GMn(meQ#|gkV`@c6X$eypD3Yg zjQxyGf={ljdT@B~GlTe7i)emcanSV|uiXI5c7VuL5L*{1IgRZ57(biuC^tfw<1^eFhp`3j3KJ{q@uW7Ve+qKs;QZOjkB$za zBoC0nY-|pC&$Mkn5a%y)4lTg4b3uQ|u(omz?cto~PFhSCbwrzBXBBiBDgX!o&>=#h(fWJG6gNuKKsV*h}tGizqs$>Cuh2VNl$K~Z_@ z7g-BLb#+)2_a4DFdE_>v5yJRKdPyG=_eZmes;V<9*I+JgU4RT4f`oSrXN;aEP4PO% zx8WR`nSrdXt|E}*5fNE{Zxl%iF*i4kD+g|*N-f`yA9Wk7-ezjOLn$vckmN~LV3VT4 z%xi6pwrPwYBlq3ETyG-2ON3lYl}lqGyPhD71rA@MTyn$E1EJi1v^Q<5Uu`%;d4Xh>tSh%R3eVX}%Yzfl8QS%gWFvpkY# zU8C>zq#Tb>y_eVvK~=jX`p8am#VZ#*=o3ZfvmUn3S(!MgmgHt`7_X-Sc1|?K7?$`o zLo~0q4z}`>CU#v0#dA^l7NI6KC1v!FT^#bGKbkIjntkJ;=vdJdof?`KLFLhlx>AC- z8R)ya7_cW~>)@nPwc+nD)yiF11AVLe6Xaxkm2kg;7%<`o`hcxbyAI5;0H~i< z%fLO+#@-1Rxcs)11OFyxw`fH$K?BXP^JvljDwoC5aVM~9HPA><6I;9rTYQBYVrk&8+N${~lcd0milikC zG)PHJLn^9xA4$!f=kA5r)Tj|zl8YnH$)It=MBe-T1slulJqy?%7*~{gYiouT(rt~4 zN1B&2@C#tq*`=a)bVT#I|Nl#!9lJ|h&LE8~wZafus;%krcLcv18V!Ml1}3Wu0;oAb z89AE0fluk^MCxHBh8Va;bdtuo6sSbJbl!VT!QKYvm-fHRMwR!1KuqB4@8Mx{2Zzr9 zz;j;kOWgO}yUf$G{Wf7zOCaj8WKSfHs=bS0Y1xVv7thmb75MX@=)zWVcVbRzYNthj z7;uGXS;2nD@i~E4eQ1;icXC#Sj;RH9l8E;#R>-*SDOP zF~7u>&Q@bIad)$K;XIG;Gp=0gCsENFo)*|p*YzYTuV8MvO_hoY$v!OpQp<{MT+~Gc z#-3SiTz0<2(5~jpfBT~auHz(;19J0+8$I!4)^DqzYi-qu;nCKlC??}p+X(M8Q~mtu zKCc@7Y8)8$jAM=W;j|lRv>`KAU1+dIE0zrITXO#%P)aUhED;#ULEFW&KelD27k3yL z-%Wk0iyH6%OHz`oLf1PW(6#xyDNG@b%Y@iwKW@c9>2xVdN%a%wTA(o)gAx4w`=zY3 z^xKag8K8QDi2&owpYy6m{1FqK;o7$7e@zz_76xF!Q!^kFJ5-cZ*M26@e&CO zQ#@W^>n}=A!-#ncOvr;Zqzf{JIdL5y?RnkyQ1VnFw+F_H0IiMlFVj3W&XlocU+>TV zUWXV1@uYySlk-~kblX?jGxVi5OzPcVZ*;R5XJJ#EP2g)w=jvv-EhSG2(=x5oD~;54 z?s+THe|Bcg$Sd{i!2`O{CUvYkH<$PXdvAz6JU}{D#PGI=Za__MP^+&T?fecS&x~c$|wnVQ8Ag79a3h$9hZ_LQVlc$d5wMiG(~Bu7*)P zDLEukG7^TVS6ZFTu6WYwUxFfyLmk4=rC%sp5L+oFcT$)VD#l!Eh8*vLNtUJL&XzhC z<1GV~ysKy`t7KL0b{Ssmw1Ul)4t>^rnLzaIx?VE>7G>r^3c6kuq3(C{rmn9@x*hcFulkreYsSFwFKBKtjq3e>Mk+cz8S=%Ba zh8JV|!EtD7(*ACf3`xQ<7Zm=q)|j|rfCU#Jjza6RgE0^pP#xq+2caNQAjfbvZ>G+j zC@!I%5)4i{cG%HnQYEAkMA|#Fb>-jykdcwGep~fR9cHr~-?zO}(~}Y6l4@!hwXJm# zIXb==p zX;L^D>z5owGv6=n5zyLp3>>03+{dcWy*&x4&CK@y$Gv<(LWlSK#tu>QE zv$_&g*z`;5ABwW?k|8srLPv1E=)=WAIB;phmd7DXfPNDxIq}CWY5*6|PHZ^khVy3k zsHoC73cgI*Pz?=jJ)ToD(c{Wft(=)W48M{0O9uy53?n;Xdv%3_8--r8LOc{aSJz0s z97&bI5)3}UaIGv~95jJLcT8t~w(*gK^yCn&3aKOg@Yh?TT>_wzM;js)CZK#3m%wL!z1^I{PUaAfvxqmVD^4sgW)phrO7OcF z?|UYl9V;n@k_qQg5F8Y;3JW&E?!@oI+lQ+V21G2p;iJEDxTa@47@Llahk_%{C31 z3A0AS`^-!&`upr@nO2hhmk6-M0BD_3e8m-fL!lrL2}+arh8nS`Hj!dqGdkI43q;_U@+0 zRVxnMB?k+E92+7ln!S&7HD%jA)RrnYiK)mn>ybR8@w$lHh+pGZ zEe42Cx4%@dHt1EMyyZJQn|Gd+dShqz87wx^SiETeE$S2_wqnj?j8=N_U-0Ju2ZwXI%?747cB3aj-EHLx+c7#fiqGlHC0KF1Q`|<7vn|gnK@OtjtDowukHV%tm3eN5z%x*~iE%z2L4^SMx$4ZksjD^cH7pNG}Yw zR<8lit$4zbt!_e?EZkVJu|bA zMZe!OJ;p=ZSP|SP09+ubQ%}sm@Efwd!)5#lSb#YD6b5k=BUVC6)(MU!;q(oeny6@` z+3pEBj{W;?SKCgED;;yuECXQ!4tr+t5pX3~A&UxiYokHXT4{`R38}lkEZI2PCcAY? zWAu+X9NG_{2-ZBD#X7@gW?=rDttsB**oz4utFah(VO-Cc^BRSBPefHWHQhul&sRx^ z@c!P<4FIl3dtT>_|HhTv)%_*ja5;7LHmyNi3seJcc9MyVo8!JL=?#?++XmhnEH(T0 z@+*JlvUd=?T{vsG=ufrbd~$fP<3P*yA}P5&wWOID@R;8Pye<~k(-z_SdMc9VWsZ~@ z!}p4s*kvBH_kAp5ro(AyK0WW;tWqn(i(~=|ygbI0S3WV2wNPNC?PT_XvG5B3e2QJkw%nCr+2) zfmQtbrE5M*Z%gqJfsybPJ72{do6yNN5Hf59BQt*kKtvEr1EjszW&7aoiCVvwpa{}{ z$lPalp*kI00WU}Enom$uZY9MbNZT+=u=jg;z zXFPjz(tH4j559qJ#*|Ol%!;1E)4v+G*?d@FVZSI5c}o0vsyn`8K?!`*wj>K zrAYs09yBngYp?b2*#0dxKG5B*8_Rw8AQU8uA@Y0??{n612g0mWn201Fk6&s7o`hzW z_tnGGW`CCCeo_o>LcKp5FnM6dQwUSu`+kagyd(vNEwHe#ug;CaLPkoQ;qAeQfIgvt zcn&pniJXBR%6P$FfISFcUfF@q58!=ezT&+d3$|^Yph(PxCiru0y5#WB*{kL?@cAk? zd;2DdN$&9|vr8r5OwSI!g3ex0Y?@^}&wn_=pmeD6yUu;h`g2;AR*m%aO`Y3E_Oy2l zx9F(b#{&*Y>u@ zoei#M-Uh@{q>PO*qze>$|57O``amF`IgDV=9{dFN?y&5X$w}sujd_1(F+Ef&=8#Y0 zY(~WTIOy-~XN$iBc@5B~T3T`~Wu@gkLB8zS6HLSXOdid7?=PHoeZ!XYCAxAtaao|Z zOAl#bBg?9jTHehKm74mA6I-vWZt+KRP5kMK!N>(Ka&htNorJr43YMWB_g%?l8+8Cp zM91&?wCrG0Bl|(?0v1VCm(OY0MDmXT;@pJfHL+6HYN90H2k}CE$!emy6DeAi=uiTL zes8TaTN#-k#Fn$yo4f(aa&WF_-Evu~Z_quu!>V7-{H~(|_~ZKaB;5L+Q&P}DUVkK4 zL?K^xJYk>*vnw}H>dg-CGjZrBpOqKb|k(2YJkSipwhS!BkID5U*;g76ElX5C!h7>WlxV^Acb zv|6W4rNARcz|pZ2xD*v_DqG1u7@v$wON$;#4}xKlar7{kShO5eARD9|UJcP5SA6>k zg)so%+#PEE;N+Sq{JWE=wa?d?n?DZ&0Dzj72`J7=~j-{TDJ|6x!)si46cZ*ST_${bVyWG)6Txg!2SIRLR}gd4T5P4i84H# zM9NxFSR)Q3!Hip7uo-Q69nH#(5LJf9-(Nc+r%L8~k0ho_@BKOJf5N$FUF*uA%;nqh zb*-9NZ_O`x2ATQicN+`<_6BxU6&<|(&5XhtCyc5pIrPV1SlgXlE)H(3Ptxk4hjb(kniI_*E=$Hojs_IQPj(ZDPf5XMf#jDP`IpwWfYGb)5Sm8*D+>zy?f0Ii-t^bE5GmxQq+M+#feU8Tq#0#O(ep z%$YDznXt2@DZmhJLVT3ZPVvspMSPQpaC5^>tIW_#6=grdut*U^jj;`lo3Sm&@we+F z6fPv#kkZYYeMo{-JTQ=mnq+_mW>OX=hp|8#vXzLio--{^WyYI>9xpTap6Z91W|TZ8 zoRrwRVe1oXaG;IEq&3BsokZZ$%T)Ib`$2U9-5_5qn-DqR0(D^nvA|bf2-axA^XTu- zV4TK`%`>4T(N^(iC~MZyDU^0Obf@!udhuo9!|5THRI{&^@Z}}yx6zoivDmu6LyZtk zGcUd1)gq6S#hm32q-+I`s6)HuHph(>?%#izO?XAV-QPK!)Xp+tD^szVBI$QaJ4 zeJ)P*>#2T(<=C^a4zIpaO7*jBQs`nO6e6g6zb?SFDzM4x*|NIcQ(?-ej`dVA=Fw41 z#0U&36eeNEhZre0)}k*a$CO)9$i=XhS@oW?*2TrO+#Hi7;z?>&$*R@=r-%v@$<0NM zIq0FBpAVBL3!|H+3W24^=>bwt$j**Rw(N#$OGd8jMlx)l zFxK;^S12l{IZ4M6)1a#DHE-X7Q5Tf|_%T=_Lc_|6s`E)JuTIsF^e z1n(bk3wL33%?X3YQ6;Q{KP(eu@=lNVn6t&52!ARt4;@)U{O>QH&(oXIE+F`qJ7Ic1W#(qU5UQkOB5sPl;WZM_KC0Snp%$h1u z*7;z99N?ugva>Bfzt{l8j38eW3U6;*wqtpcrk0Uct*orat*`c&c2d&K1>7SgB9yS= zjit=DQLMjY;l~Ut`k#|U2uSm;VQmzAFjjA%8maci&4Dgi^oHj2(yp~urgX|7u{e>* zx*urv7yN5OsBNSxr@N@<*kT!4UCmKxBqVKnsAN!$$~=0o*_rcEN6VF5B=YFT%^&jP zZIK;XV|V8hBf=8pnmzEgr)6=9o~-}`O|XwNJ2T>8P9KU+bqRR+9vcye-uDCInK8P8 zi|XdymKcS|)VxlzIjvk(sY9MzC%x%Yk-9F2DUr&*<}l-S&K>pMQ!lcp^q4-Xr+`~Y zkU**?!mv$YsG z^#qTXsHr7w#PU?GxT~jRhKAqW5Q4qmq?V^?CAm*Y)g? z(J3f&;)~TBZd!rOKEsPk8(yntOiR%5HkKxQ(Hp(6t$T;^4NQTmaQ&hVVf?n1=32du zw@%UlUtUSY`*Eh`(1)XU@A&u_ka?)QN9}gwY#?P?61HWDZKp{KS5=5oVP}R!yDX#& z_w!ONbG#aJoz|P_T`330Gt=`W+k08mMyn2%E}cVsf3Vuknl2k`QXUB2N*5$L^Ntub z;J=0A?%nWzXFvd-^$Be>*jN_x1P&Tnk4YqnihJ1sGQ@smk(pkl6+jZDQaSr|7Gw&o z^IXBFY!$b#_ND1pXlx>AAG|`sWDQqcxE4YO9&$O-f+~U@otE_ToIsO#ZGBcDi(NtH zZ5NXLf&#CGxlI$$S$YNjfbl&jZ}of3ul}^oUDRomrQvD*e~mD>iO3Ko`w39!zy%}V z+;VG<&K+3;_oofL4TnxlhcR?tp1yGq&8Jc;VHd9>`j<$hR=_*C&xh_a2a1o9!9qCB zWiDF%+wC73E<$WliasqmeEsOlos!f6BG?hvoC=UJ4v(b{iYRa9YQ2k>$2^f?xHppGc--T@ zH-Q&Cf=zql^dC>1sYLKF^YZX(Ckx04BPA&e0{ZUmmj|?}jgrYm^ME)=>$?Nu^kiO1 zx%@a~eCXo$(V;Y^R1FQIct_x*RE9t-fR5nsw0u**-W3tujHE?XtT3k@ZqPZ&{~dH;1d83N%lk5SFnM?az2S$H}+S(%coZ}s(Yz|qlX zW?R_omUPtPc>LWQVx|AvdBDa&P)SaXdDEE%3oC%RlQ--16zMZum_bKcIkW)%h^zz_ z!o<3YjL{nZ(I1(NFVWlXsm|Xj5o1QdI&bqn3X5@k(DZv-R@8FF8vB z)snJ{Dh_LSU~SZmj}OZ1?8OJV-pL|V##l>@2O<;!s(dC|zD|mW>P%+1kProz@wsn&9r12BJKt~%uF;xm$$8Sdm-LRTtZZC)ZCIG7hYx$X1r+W3*tGU6u#Oxo z?MmZ4@bTTVd4n_y$e~M@x^iLp_~cwA`DwP zDmk5O;H6J%3yzK-a~O14y#+f-BB7&iEsGK5RhH}d4#nFcpHZ7H_WENj{yi1`*Voqv z2Je8J93o^9T(0!nP*L#@jD$HRi1DY8@&$`kNU35H@ztkiXEVaV!5thN?_9PE`YPv2 zW5udjT&jLVa8>W-G2#^jglr+h4l4jZU_3U6scyRI0_RlE| zxZ%4Tk}vco0s+oEiHP5Zwf}X@%7gwt2;c+F6h=nIMs^BB4_Li>9BGEJO7b@G?BBb} zO8&*mI@qjVF=&0qP)e7~x0Z(X*N#{BKaWK117|O#mAbvqXS!G0kVYqXHt{++Yi=|>uW?*qDLryibT;0O;t$HAXS4y=C?dC zQ$Lt%f*^LLf_q;dWBZLKNtnhDj5MMAm8;mrrTA@e-N69s|w??_8TprA5ln@?VYd_z$fc}ib5|P z)u5FY8#C#Ey6}cVx8V#9m6mRn6wQrZqAZxF9bP@qo`i*s(t)Z%x6#0sN_-;}ou5yK z|C7SPCxe810x5_{>6<-vBqBB!O@R}Xr5a70hSUWTrlCut-kVZ7F?_=U(a-e1eX*tB zU38_HA*BS7Xf!JB=RZ{H%8||vVG*UI=Su08sG?=sM1K}WPi6SAcqrucmAbOi+^g~i z73+T^Z}H?8EoBf_oq&#J3@OjY4)&(tlhLRhtc8jkF;#wveLBQ}(xej6$?WN-bE^_f zS|5(t``*%;yc=@F_33=Fp^rFDrIMmlmmi-z8SCAu z!FxdriOKJj91`NH=)V4!CFlq4tu1%6G%Xc~(2-{V#uKunZo3(<#w3Xl)Z~r zbI|3CJ)fdI;V>~KqIcRA>POhWscPcmW7%*HKKj@E7lJN{iMSL>R#+~J1!@fF2p|nr zYF!6_X??P4NeoDX8ERpov`O<41sFE_)dfoFpAosfeRG2GP?M`}$Pe>|f-rUc#U9CZ z+IzZ7ck6k}_4|@aSuk&Vfz7fSJ09rcqe{NoNkx*C+wl<&v%g?)=mjb9G-H4&1-+4v z6i%*x8hS$#m6fLEh;+f$NM`B%>X`-`FJ08Q@G`Uau0*$m%6Y_oU#Guiep#934+EZ< zy**Et9a#gvJ1HsN;$Ok(Yq(iFfs}v$QD0=OmzrsMXjBRlV+Y=cFocIw0iByZY)v3z zMm9w7F56^P^$io0Bi01@T#X1L$l6k4AcggUf$6(@*AlwRe|>1AK9bq`>Xf|LSR(!k z*daYh3-#3G!cUy!G`~zQD)S?^>oRN{HlwQ%lIX!@A$a#zJf94!^Mw-KL1y*{+PjgNfo z9|gHIwVYrc-IfI-O?iQ8ovNmkHWkHf|Mr%mE)GXS!xon=&cAIrn8~gSFa)eWW+6h2 z8OKDtNR>s9la-VF92r#_IfhLsFCO_L!BRK0XIebX7(<*0BJPev9>9=4Y4uukzc_B_ z!U+-;_50gTxb-Jq7_+CRXRB_21gk_T2a{{E)LTb0%s8ocjOBHad>#x^H(N#N&y1+V zOHBOydVMF&xKU|oaIbp23&7cr8XS}(A0|akT==qsT~;26NM0U>-leKJ$s)Iv1+E|q zG6pK`JF0CL@+C^~6~<7TSV>7;U4_SO(r5&@o-GMooMkLTa=9dsG?6S&RaM2t=6O=W zetsk80Iq37J!9}@t+$b?q%9-O(h|j1aaO2}eN46#e^`$x`Eb4LqljKff$D{EIYT%& zZ7ipulLj)zL`Bu!Kb>K_Y=|l}HcIw>Xhxiz#K36tBE=DWPBLMjst!04dR>EV-o4*S zl+jRC#{%U29L#ycJKDr70Y;OUPs)<@U8n24pvNoe=BBaYGVsEamK!L8lNRf?ifCT9 zfF%>pT-@Xl8NNuIlA5C8c}*90VH9xR&&$mR(>ZcVn5R@B1-xkO1w zfzvkcn-GkBv7d5zmS>dssz#j1~sQAqNcD}v!^PG{rR#X*R;CIvW8Roc*z}(yKA78 z8icH@9~`DXu4t(Ja#nnsGs*=>v*NdBFPQPP0QKYxk`tr`4c-`b#-x`2&{Wl2S>VyM zI5;7I6Y>gKujQnMUBBiu_Pc(=vu^O~rzjWc^_;5DnP2g(qgAGsWv0@=@VL-G;u&+1 z;i=fuEt$xR4`PY(87&DZQ2%rQ=myTaY5&4dale~ES^#TA40y2>KIc*M@nXYj6S0=! zXVlH#%zg1otKS_yJUGffUBKw{{}vh*Ed(juHt@`hP!{${Uaera@q|ZoT#a=5@JHaguYE1&$fhTi4g-#O@?3BT~<( zk6W-yXpYPKeuRGfV|=En<4rg@d7D5rOuE=|MHTc@nz^m=vs2dx>&a&*D@@{W4X=tQem55-wJ^mn0wZnX6-OrY5 zT38s!k9;Edei|MO6<4Zp4ej+~)j_`@+#xnxK^e;Ug%AlTn32zt`dr}+(Q4;gkIIfw%Zx5Qs@xSzh%c z(YT=c&&?i@ts8#Tidnz&Zg|iadUd>48@7QJ0a(x@*jv-`&{Eg=+}P3VAIZwOfaJL` zV6c#X_yE={mXC+Z)4bl!$ok()%i4Ge^+e2{nzXUA}!6h!p`)w=?dA#X_#uXa5MV1&fm7!mE4kUbLC%$Gwhc2nWx1T z`M)%JdtbkUsJS!&^lJRPHZGGx5yhhj(^)E!mn-heJ&L_3hb&&m;+BP z@_R~AlpxF7{jJAQkz%x9bI1WJ=WUctDnnBKh{k+|Hz#${AwFWSw2XONtR)#&YHg{k znW73dJe&M^JRG(Gjp7s=y-+4z69E=YlB{?b(4i}2NW@jXG5NHxpj%kHm#=mxk*&id zDM^*EHIul|g|yw+pp#wY-Ed0z@6!J8;J0+iG-eh1w5m15JE3SfB(yqTp}OX3s^%Ir z|6aeX?Y*Q)cZkPUt}|UDz?LWE<|^y0BiLo}+|S6#k}(QqQ<33EYE=x%Do}Cj#dM=o z(W6I(`e9l02JurswF$-Lkqg1X!qF6!pciNXn}NiK4-|GyNxV&&V`F$EBa<71azD8X zsRO@cuFXL-@+r9IB+bnk2L~~{>$eVP4(V744&~&!%e0a)Nx(y)^s16dSvgi?rCr&% zc!|UavJa;{+`XEe4~r7g(`D3?GgBbcs;Zu9wda#gR;=9FGGr#e@|-;~LcsaOM(`{B z^({?Fk7O3QrLJEdNI?)oi26#hZeW?jQafvh-UmDG0YdKxszx z#TW|mOpiMj#kGkvn>3W+k!oKq&uXDc9-5}bAA~t>nVbEvV$7yDwVRrr_4{i}%ClCw zc@`KE0{;Oomd&O~$-VWz$w|2Ia7d|!o@*+>DIO}Ns#D>2t-dg1d3SzF0nbnl-csqR zxZ*C-Mm-_)uu+^HHdlU11Q?3R!y6O^h@@SUq@)cV391BOE3xJM+E&H*@Y!_PhF4;f zfIUCor`TAmTnk>(v{}Z4JF8^{U(A#&`XHf-t||>7HaxMG6-AoTC>5zjEo#oj;=K-@ zRue&i0KqOHdcg#xswyu<@b@%c3c3V3wMO5scq$3__zLML$jCi5ob2NjX4TqZ-6j_9 zoY|2GZ~N((%Ee&WCgA|sH40&|UPe-M20_md_X&xdp*8;88M&Zf6FlQm4F-*EhR+|Q zf6@F>L9;?%&3*qLY|a5b^(Wz9gHXv)2ayPPalwvEvZTrRvxQ;!2&5r+l-~KK;w7)o zPmoAjG88UCVt9b`9jUGh?*Fm?k&&$Ed=l}Sm`unDb&|{J7=4`XUo;LrV(0MZ)Yh`T zl3561=~f;4{e_p_!ugq++Q7q4qN2bQIldJkEr3og!`J^oD3yYpQw{&BEc@Ftb4-FZ zfNpN4Qf47pBwoW~*2JJv#%WQ}_&8TirfU7I8+VYe8B;<5Rhe^JYK8)v4W~JX72VJ9KTnj9 zeVc?`3~1yY*~2qnrVa?Cf|KAx>H`TSq$0JDS%UCx6UnVd+|MJncv!ZOienb_O z!F676r0uuZVlPRiWlY;3@&?enBqMdo7HVqtv#@kY5i_iS1uDoh*?~-`4+}yIb<$TO zO60Tl_n57JEsi=v*zM;App1ghEXTh-0j?lD=-5@v%yP-OT<~QI+*wW@N{1b;BZm>f z_SCYT@ohR}T4>AclSaAK*@iv8h1cipai?=uRq1)VjL418-2;oQXa=E zYJediqf)O;YtCtn5hq591X!=g_uCVI=jw-YYQXDj0p7<~xe@66P}ti4Ny2`o zB`YBjlP##rSo5+?%6{rP=w&|-21(17Ha3!g#%T*=-s#cN1iAo;MqTYBTYR!YX&ms- z#G8Y4dAoczG+7{~c@Jp1YG1y5*#Tb3R~=aN>S@|+MOSi_zSsX%bk1RUwqYDzSX#!L zTUg7sZQCuk?3S@?Tgz+NUbbs_E!VQO^xa>_`)|kd)bqTzuIqQ6qZ=DF{1^R6&YPkk z$p#geph5QU8Ls;=xai(zFoNdmlm z9cQ^oIWL7E`i||%Pc=Y+nisxEcexq%n)>{RCTD3`{o8$2wye?)+2kQbOWd+qZZi}` zwZ%Bdh#2y6-hqvqmjWP+IxXiU5pic}Wol@{72qA@C)s?y3!#Chq$v}FFtn*09%b!b zAFzSOV*jLCmM}oAH3Udq?EXs%e+V~~GNogluu}N!9UD}0cYE(kU*fNYk5N^vk^}_0 zJ4>}2MA|beE3{ctc|#TRb<_?d)u(LrzJVyVH|GyJ9^1OqR$F%ELG7nMqvK-xovl)% zu-yowAkVPB$ItvMH-D7#YPY%4P*Pq1T*~ZRyXV8M@3EH`#yw2U4{XCOI9)Z5MdCr? zD@kr2Zcv8Dl{BRTHqOodh%9a|*ZBRsv#k-TG?2NpGL+%0aa4hf-JTX-&dRBP3{0D2 zpf8=KvR{#)cD~aUk;f}KaB16A&A2R^Kek*965`_x7d3MwmRHp|UKW8w9U|*q%aRSJ z%fk}hpA}}1Bh>l%TT~$iwd+&QwT|i2kuv?Ff>R>F$@WGMo_$M??5A}Jps{E@{87&B z_rwj%hYbzO$IGUBqjW=#?S0>W%+~p|ru?8KhL`BsXZyD?O_t7y@*(f7f5c|}6uo?A zkz|>=Nv$?DZ6u|j-`8Xv#@qhEI#)ATEjxzx^703(2j?oVmT3jhyCEKrX?f#(m{)e8 z>cRf=+JW8x-580o9oh1brKLiT&Ini6ag#+xwz0IhjbX%6V79i73WJ+$fJ+B5I=EnrV7QxV0`{;G*IQ@xIsXYu{J!f)t=6Grz&_) zno3vYn2@|Sy0zB6S(5ebPw!Q64*$cBYGl+lGv1S#InPovQz{5C_MKH*-kq}Ep6K-q zMBR|peQhQ4t(3_R?-tyUZ21z8o9ccwR4m8i#v0ovCA(a^t=S8dUx$lK1jsEE{qa}%-fY-F>NyUpmt#DLvJ3MWRt zx#@1(1bB`gGWr9uC?gRK)APdZY||~(;gEj$g@#(QWMP5f`?Kxw-m)dzXPbAZu+#mLfD7}Sfl$YW=n73tC}w7{QB&hd zmChM4Q{&@N?{^1s4-VPknY2sG$oJ3xQJfEt>b&4pt(^Z`&>$|MFl|#-%FT(9Q~e(F z9e8>1Z*RM03!Uw5NeDPO!5ptw4_UnH+jqI#O^YZhZp0uy`N+sfP~I5%otpSinyJ0b z##oL>{Gpqq+k-sSYVNRRFk}vcWK!9a2TlwcX@GjWiewfO%c-GBm4|b%ycP_?5E8X4 z>G11mb2~FRAM(QpL0i^Q(5^>wbE7UHu8aeb)c52;M3j`2!f9x32Y}(;&Ci3?ncWo; z1FASNy4B*lnyd>%3MM9aot-h!F*iv@tc47fVw9ub2Gz{p_7fi91*QrTMnoK_bF9|J zEf#WeDRU%`=NGa6o3KFc>jQ|iP*r3BeRmyJ&QBQwN^NpkI0XfF zpQbmp%O71m#eyY1gXIXY!M`f=F57+>=x0{di(+KO#Mm^^z-gC{xQBtbV!@bwq%^j% zE&6j>%JVU@C|`E8(9}_in`LEHT2{V)ZaHGngo*|L9Hdb`nq$+fyy*;@Pfo{-K+>M( zfg2f@@3*iK{va$L9of@^ z{vI0c{FJJymV=*GWpAj{nldsLyv!PrSR3FSeT5?JW2|U|+ zdagII4XY1|lv5%V!G=N&ht0AH18Yt`Xa3!?)>j^C+q$1ZsYi4+Dt}j%Y4!dd*2QPG zg@@cCK)5MJ6b!o+h@Y_~SEdiL+1Y<#y%!Qm^G}CA3KJvf7JGAjlK~nCM5^<~ufxp@ z%MUC@rD>-)mpp#V3=r91`d8cUbDbPi9FOl#{E%cMlnxNO-(aHP&dK<-fC^ohK0)%M zy!`K}`^*ZjqLLkh)yawe(pL|>PpG&dS=Ge2g@up54k4->49fG+K@m^2 zBV#T%zR_{-8XI$L2?)2v;=&>$kxWeZgF|9wv=(I~!fqO~&`_X&LK`$%T@4x-vFB3P2wd#{OYQ20@+RZ@HrD8dgDrts*w+*% zXJ)2BN@4~`!&SjXJMA4PV(IDTl=WdZp8%{ZRZVbiE}~?#WV*mRa@}-R zN~Uz`nOV{Hb*1fT^xGa0)n`ATcK%$u{Mn_wtfI1*X+f?&+NA94W4l<*!re3xUOv;L zoVhQ9fhXgNnw{&fhQ!&^p&9OAF&^nrQK6im%I~Une=oDu=jUhM?-x@tN6)V)xN~aL zuZ2gW6CM>;Rl(&{afI)#XsYr-FA>6hwsmE7v0$z4`qLwnR+5mICZ!?9@qBT@{+NYF zl1s{Wgj7Z3%<_({wssD@ielAYFfdpYzkFdRD?3qFX9w(ns`&CMiXHK2R2tjCupM{F z0C}VMq7Pu&17KxS=7z*kviBlu+8qYV+8%vM{LszbYT{U&n{f z6T~yw>aGF?WSv~ytKK%X}%Ja@30R~>Y4F;-JUz`9MiUt*S#}lI#{%f!Z;f_!Pv(Vhb zQ!nn-73O)w@G7~pG<2XMMg~)$a3L%>*7m$$!gS;Yf~>BTm@G~(Oosp8xT>nFOZ)$t z8(6Z(y1N6ypFWW1JvFbPKTP;HKGKc8w+UxF5G5r|2@H6W6o-R>S|%gUiwYmYitl=* z_+Bx)0Es`gO-M}*AmSs5*D}O%YK6Hh4u7I1hsX(^c758}8VE>+hX%4KZD^?FkK~^s z!&iyp7hgWAy3&(YRzk@rpsdi-A`P#5|oULf_U%?M>o|>`tc%WW;bpI%w{ zUnU2(pYH(Ngp5H!PBt=1z?(BZ#~0=1#`H;fzJFx#Qn(RUkw%z@kbc#PI4`WtAY#!w zb^Vz!ZEp|Lw#nYUg)Fr%%pdr=B8xsydEeP@t=QDl5S8tgHW7F{`jghdlFWVUu!RGg zH0=)3nDcWIS-H52ZkW5drKNzmgN=Vj)w@7~WM+OKBV7ZiToC(~O)m>QhU1lGk&XU5 z1T-cxHZ^K`WYeRCc_M>sMff##2R;#%nV{>t#jCTswcoyr&LG<-tQZ?exNTb@)PiC^ zF3G1ZaWbikifz7SiJPq@rXh%IA8MK=VoKW2|I9oy!_hwwQ}0?9`25J_{8t%upHShq zkx*}FmP0QqbGK3_akdvb@oSlZS=d85ZEs0+SIKwktg0&a%*;q0=MQLkXLz_ws4AL2 zENyKm>MihD2&AZK|3&CIj37=7WO}&>*WCMi4>)*u9*>PTU-ovU{a*O{PELO3 z#&^%0tjffOldf%Vmfoz_`*?D4>QV4zGS<8n^ZzP-l!>XSHS+tw=U!o|=uEEi{A_FB z9bQnNe|*b^$C2|J^ZisprJjsRnY3H$Ql~Ktou(O^;6isS@$T_@UYKTkTmw72%bs|h z?;}AhQHFvy0(kSW`Y)>t#DEl+0(FQiF|ojjH>bj|Goqky5eT+KbADv>P3@w4x}zOf zpstrLzoQ5PX)XYg?#Rnsz3R)6318|S8_tdl?K!>E7K~ptnqxG zClTMecqo{*Hb6#u3>o{ytO^D{wUjuXDlHy8Fe+ywvFtT~zdsO?G7|y?l{Kl-S{dqE5RJ%AvQ3qJ9~5 zZtPuY^^J_Zvs~IX21ZvRcM~ef7KzfH?Zz4iZYj1Xh;s;b;8@6!;Q#sXM}JFlPGeYs z%gXcK7`L*LvB^Hh=yC^fYYT4Ly0At&csB}X5M$kFnf`M7TvU9yk-?-5_f)E71qCF~$gx=a9iM2Cc~pHT@KD){*kBMtbN zm?Vx&xNTmDV-WHo#*jgwk+2SvZXltP2!gT#lY9I7PC>+=9x%3;d2okZTsX3qy!rD> z9owF36oaGcMf(?qd(99FONC_h-PTnx#TFGg?N^vdv(tzg<8hV^nfXE&zJvsIA5-J+ z^PE3^z%A7*i8ZkV_Jd!P7q#ayjf4G@MPiL zcJ^`4=aeQo&P8E;QW4~0yK!Z@U>}20ARwmNE;E+d|F}?-tpW-nMg%HXUF2o051{{dT~5G7SEASoh3OJ3{h`W?us;jB74nOS;) zJmT+7#*m>;J3n)!9FJx6<7dSrBw$!5<&0jo@!H2ln>}Q6pLV^%y6hFz8i^2T>E|0S zRAhjPsyTqyg@oYaAyWoKbUiCftTfK=>?!Ug$_;w%B&tMSmN?C!(emetmke5b6T*=c z@nQ@L@;en=kT6=Hmp%#A7s4(HdQtyeg(o2@%D`qJ4~Ue^cyp-X{OGg8Rc>uXnXEUdDqXfta+K?(}5!J!!W9A2ChS&7Sg zZmYsVar8maOX( zRQSCIz@t)A(-*laOSRL5_VyB)n2s6!$bEe$w^_p(3ZF>kP_tZ{H5o?aVJyS}c*h zTX0BxAmoy*7$BE3^ii2KY$4USxpAI9^Jgl9fnj~z5Yg$lXG28$GD4Z=L&|1Jk{lh) zsnd4tURG|CpjJ9LdQ{y7r0R+~&GtdIe>x08LkD*r_CLjgvbC&%K@R`ZdExAu0CoG9 zv$TPzF7)DAvNXegxkQ3K`nr1UyL(v<3EBU=Hv17hUt+k-Ty5nSDs(8W_MWNx(Gawr zbSATfm;lh>5&-_?fFk$leopCfM(b0=ntj@rbziMmA|5gZh8*YTn?*TO(<~r3rcX1t zEfJTBd*?$}qkxI|4|FBurGj=n9{-C1$KAZ(59Q-I?NX4+e~Rp!)%v%$?X0Gf3GwqE z^nKkN1q!8Gj_pKDvF;^}!ah*mjHGA@(r9UEjm>LlT>fI4l72m}v0ZJHTc|RiBZ-p& z06JnGzYgs~!qt$YDy&RC^TC;<%Rk;{BmMosU<%iDKV75ighcVb^kb1-Kx%Ge=)FIw zUzgg?f1{wm18zSEcx~g=HRi{b>v4BN(mp}P?pthE+BAN?`V}Ih@$ED&zA%<5qjJvC70GH708X6=u|m?-j|5dU_e} z@nyhF4fZ<#h6*(en7qJOPW6us|3ci+MiU;~LEnDfP$<6z4(yTPG1=W0k>M!FTP=l9 zy}4x;oA`i+nx@Ja+;{KrPmfGZOuj}R+X|MKZI&lpoR`6UM_MM>FDni8yq@vN%w$zA z@9ZCnQ_;zg1T^MAVK!jE0>LLReD<}v_%+#2Yz4yqu(DGC9_U}f9LzzMx%j(@a+3e^ zmmmMRsx}%zms%ns%OTE`DT%*?dGrFC_;Xy}j4Na^63Rb}j@&^|2`u`SwKW==|YdJ?c% zxSBRg8P|#@<*kpzO^kF@U5bn&JbfZq^?yIEt=mYOs%3a!;@S}CLW1T$HW@NyV`NPK zK?=*w^lkf>sc)r2lh1d00X`sNYOyyF)o+wOPUui{v1BTiK7q8dVgfE)#6%}umNYjb z<>tnFaj|{xQ%V2nv@n;wXr$He#8ZL{KD17fv7`@>upG)5vkUr+FPIC&zx}$&d5s zMwSMriT}qP`-Pq*3q5pz-bY zLuKp*BK+p&CUuG|5h~xz3=}jJnxRAd>HLZN*G0MH_5Ts1XK}qp8bXH;o;}`>i=pyk3G)9L+{_XDs>OBFmFJYLjShLd%s^#S1>%PiVoS^+D(fA<0C9O zsQa_c?VGku4b}8b-hR#>I4c=D+PcCso<8~Z(e`VXDh<0Fi-VZ24zr!bw#2 zFwQ&2ll7^mY*_h^LN*Ie!aB>W|Nd17^KEV}I>2sHp;Lx=uBQa2?eBAs_8#NmiIXT7rVuI_4hH+B3 z>^Q&vir?Iv&9QZII==Fo#iTj+?UVRGt-h}Ab+^5d4mExkSA*}@(mYNUD^<9oh05*Y zW!r!M+NOhX>dEP%4(CcAXwZ#TuXQSXEpgE%@?_u{yFffd9FXae?@^)N);l{-3$2 zipsw0wDnei8Tq3WJ#Je4gSPxW~ie0}@P&1-G;71bpvNJ74%tosht@wnjG zIFtgw8Q;5iJl;D97=?l*-=l4pTVjifWB_LS^0~Dyc})<;dsQMc6#lzzdf;O>>fT^R zIF|9)-8#515wGNmc55;6R0uMs9rt+dkwGvHE&lQ~j2uLsLWf1(V?<=IYH}y5H^)!`vmwnp+nOP(KZGtqYs& zAf9%xMGX$tx2!#nXIYw^{UP3wkTPvp)oFJpG&kp|(AAhKQ4lhHP^XUi!Ey~@+@8)i z{P}9AAT{LNWM64IR2(F)YhhIE!nap5LPmcEU1hOYCBo@YW$DVEf-|QtS*5YH$yK3pt?qVB17lzN;Fml>NS$m7ATJr{r_@FrUaotktf#?0t6zzy0%9 zM2};ay4K=Yt^kSWgL$5ICFjFN(Af^iX(di7hJkIW@aU+%^UIT`=WdEtQG2`ILe(0{ z5AOzqC%KIl;mYo_w*+rvgZIcOpv%p-2iIm-4&weeTvY z18<7qH{+a0ecnjEo zSzzDMARHnb!T;|sA#mbRWpR0fn?pPxtQo_4-A|(^T~pQEOO|6qlr}9CYqGl<&T{w% zUUdDR=MQFGLlg`QsGgT20qUB(yMJfqF8i?@K!ZSl?eD)S6Iq?JtLsd0ofo0X`t<7R z2ucE!4NXXo{Fe(_g7}1lX;Xq%BY`j^R*u|ex#9Y*yu0T#%})mmUDs$U=6(IawijNn zM|cKR&I#T1DpSHlMbio+q)LZCTP-w7^llqyF9*>cwAb9(*Q!_-NSgIy2o~>9)By^hU|2)P7 ziBedo%!)r*?VPo09y^1E;@Ncuk_I{ZXF1^&UgFE7MY?rG`Qh^#1;KyAR9YlA2plM1 zA6|?Y9&m#M4JYPDW>Xdb9-^t1Co~9Wl{;py)epcKpba51-##PdKbPdh9~J2dYDSF;-Mm4$qB*naYSIQsNT3kq1^?9mJHkk5F16BdY_26M7ixgk)h^NyqR0R+o<(@_!L` zk*(V<-Kgn`F{$5qHpRuKat6p2Z|!Bx;S>BY>lGNssWv#@{At~|lUVKDyu9X`GLGyMnFy2NN?3f`Qg%tJMO z7Cw|AZ8lR=gWqmim5>& zDEh=*8-nUB60m>pK zK6xu|=-7Y=j|psUA1D)(10wwWUu7-;{i{}8RmxP57lP&VD~CH!ko+rbS4LeB?WJ1Z zxqH~$`u7hMFq63gVgj>7va(Xs&_Lyd_1fYT7sGS=^5#S$QTFwLvg0RF zi@i6X=K_^DGe}ZRK{gT*7;G}wcC-0;f(9k2l#zW9i{N(kY2L^N&WSkjga%gj7(1px z`5Hk8+;DK6O?dnZF*sklOnJC%QBCljRcO_eY0)q)HiEtmPl@+jmZN3!P=HT`-lC#~ z1?**>P$)z~IaAatz)Acg`iMk7qP}qc_s!lA5kp@NXX2$Z z0i%k2jWdL+{$NoS;sgxwwxy2c(Y=YlPsCkM*8b>-O(`jFkwT^yaf_TEiYecAW!r>^ zetovKE|}QeBHHGc#j%{!_PA1@)cW-7(zW^*YjZOIDSdFg2*0t#BjJFwFCNt-Sop}l zDkeIx0I8oG{lTTZXB~(9)s0x#*-`|35jdb;+a+~)Pu`yQLR3P+v2y;_s5$Ox%3o30 zuK%=u`W%Es!$k*OWe`Z|$UYyAJYWRJYm+2*6gccgtX0A6%HNM__ z^%gfSLph6c+?QCTYgW?b;9*E8)ZSgwr<-r2XlY(8TfP%R ztvcx*RqpUWZy^)6@zVGX-3QH&eQEC<2p=ez>&uzvc?CJ&iYjl}%aoh>OupP`g9}8Z z{o7V%L#Xfis{?R=&ODdCWJ-E7Dj`nTemn9>rK+0~ty1Ha5-BoRNG|ABI1 zA}tIX={EG=E|O|qv%xzX!QDpqlEl=e03{&}r&YTMaS68?CsPzhDrMNuL5FZYB$%nF zmJgROpn@xljCdg~A(fOE-!t^yhMGQ_jFK{e>8Em?vpJ@i7|X9ko0h9deNG3#@i|@r z^NOt3^wcZL(6F&x{>%#2yE zZAPaBrVnDzy)(#aCLvZrSnG3+qfn{-{H0)89&4ASlVnZMEaKc8b9PiiqVOjTS zs6gVNLa)?f-Ind>*M;_<*qt7DwZDgv`R#gr6U}DR{7qp=QAtLUoE83IcYj(!c6Nx! zDdvCseu0RI4ajH0rWv*+pZ$z7`NPlF+Nzj6$?$agr8!@dDk-^W_uoU>eBZcX z(jVT_Gu1jhHsX4opYl1juYvO)Fwdo_sj>=-_1Oh{rK`Hn7JAx*QddwY(xun;??XBa zYgTiXfXK@QG6^DYy0Ej)c3^SGscU?cTe2{tCHKzBn*c%zF_iEKDIgyPoJC zMBf%>J%{RX*AwG9c^?SVU7{Rmg5`*uR-Sw5%rr27Hn;dC;g>r6|Hx3Gr*jygXLVsY zIdc6b)IXhsTK-Jt6BFwE58Tlw+s}Fm^xDixXhKi%6jD{C`DDVL4~U9+e!&+Pv++a! zR^m;Kw5XrdQL?*tt%r=1FNP_Sz^)BOuc;rX*AY72)1UR?5xiccVvN~nTu6gyiz7NzKUYMAqLPF8txHrgj$iBFI1_gIY_+op5dh^xl z?AR}}V~hJmBp;GHghU*IWD>a2gX|^;DRi32@Yt+Nr?)D(GRbgSo`}|YqLA0GL*AeM zi<~%`rTe1;cU70335NkW40W_yRL4qZ=CYu;;ag3VAfv@2d{gwEOrbG-@8typl=$J%}qc_9HbN9Tk&O-}N;+m;GP65Zfk#X%Q8?px zbEvW7%riDN2K3FB2eHKC-u%cd`aWu^U+aJ7+Q>{Dyb7BgU!oEHPK(ng=`~?-Io3ub z6fj^X?CkftLV$}oUP%d+tQS8}+W*V#lfE&ItnY_>sz%SqP0biv@7>%z5M^1re7k)y ztgUamd+#I=6hu5esc6cK$DASm*EMT+EK0@DFPB29Z z3pDPBJGV~P&NI1@07LXB->>Wbz~+$blTNrhx-2*SfG;0Xq;Oy+n%1RN)3Wa?IX24e zdQSQVm$6~n*>~XSiAk8UR{K3T;Oq3hE6^ zBQHDZuBu8P4e1}{;xBMZbXtSgE&gdEOxEdZbv@Z2Y)saB&mEp5%v;jNNW;e&VcCKa z=}d+hxZ)i^Aaa}#jGpwl?b8*ymh857aq|5jEECI#s{dES>E(-m4DUHmV%9*-N&AWz z6~4;*2wfb1yg@_>%8sI|XRZ{X0AV|aUp9lBXmUW^@*~m0OA1P`Eo`kGZ!aWaZ#VU2 z^<6;mgp`*TTv^$M>q^VE2fT>7+~u^4sX+2!u0cS!g9*u}6ym1h-nI|l8{V`=$DkFc zPu3gv(Pb+VZaSiCXhLvsBsDdgDVP};h-lH<@rhPoWn7kQ-T#3YZ3U)oog)*IN@;bR zIC^4QkCnL*qc|yB(qSe+WKlGW5*BFX6yE-x>G zq!kc<=|l`|#NzZ$rH?9`ShK3AtBaGO8=3V-rz0*@!KwFwTM{U4-Q`3INh~g=wB_W} z(b2iS6Ed@+nd}t>g6YxYW1@qDtQITI!a3`V?Ck!dn*w>c!a0lWCS8<#s%Y&@Prc#~ zByoVjHaw!Td2l55*=vBE>3dnDs&=|ZZEZn5hAVGy1mgFrKz*5<)+j*qL4cF`Y-P0* zq4ic=M)uI?YES;ReRKclLn0{I(0BPnP$RH6vlq+IpT#06c*0om zbO&K_k(O9(MA?yQ26M5R?UDM7Qp`;s?dKv)RdK`kiwA-wt(8sRm4ir;#>UA&T_C?rH|arUAH^R zE0?sfMk4);p}~vP_r_3vn#4u%n$X42HUj@`?+K4rtb44Mf zaB^>EX>4U6ba`-PAZ2)IW&i+q+O54?lI%E=W&6)j)DVa_F&yGSk)FXF{#^@pA~Gwp zy1PoG^JEp%YEKSE0BzH(_#?X}+gFZbNz%Qw&c`(HnQ zhdZCY&;R0IzYG8Q=hxrAi2Nw=HU0jzy?K{5<}C9`c_H_5J&%_NV>xt&aHnI{&=z+{&MSQahLa`y&4P_0K=Zja6EO zSEU$t3h6$7KfgOj{u9#6KmHxl!|Pn=m*0fzeEI8i_-Szbt-y|NK1t%UaeyYs5GIi8aBI9T#@-d6e3_^0rFJAXQVp8LHgo3rkD5B|*1Lu6OJE99`k z8@{m5*BK_anB$I(uQ5(G%AY-{#U7{old^xp7hCFSrHB2SabIj-PVw)13FmYB`Men( zK6wRR8Uq&#yybuU=lVbW@_+K@`o8HBh5Pw?t+=n~f^!@0oPYBwHY9w1_N|-+{_D@5 z?Z5xYelS&bu$*Onc)<4_ze|h~{$X3`>~rFEg|9!a2v=KwF2EA;*@c_I1#-A+NF~&8 zw-{RptmDH5z1(?BISzIploA&=8FNZCUM=D>d^YcC;k-7MSff7%o7jkwYHI9FPcNI5 zbHRREez2jpq>@W1wY2hFrI)v+nro@GwkpWIEw$WAtF5)C_4YpL$xnIe)1K$)&)Z{9 zU|{LBxBm3r-#1<_AM|pqm)F1iWWe%RWVzWkN1e(ig{{=I+i+Miwhw?F@bT?>D9EuNk7b?xt6 z$u_VYs5^iH2YT($12hMw}< zS1Dhdt!{Sg``W{Ee|LR*TVS}Y1{_O1ssH{r@g%Z9TQt$({5=3?xpM(elb5RV_}xhJ4$`91YSI` zLn2b-f-%oa^r>fOOAYJkm_QBXSuyWc*sWeCmR#4%C)^!N##7$HEH;UFCi7vvqsJbv%jX4x{H5`tEaK^LV!5 zCmj3P1l4!x=sdjPWR9;5z3nQMcfrqQHJZH@P+&#JlQGe+6t6k4LTqSoQ?<{*0>2&m zo^8iCSl-E-+I91wY*c-78>P)wxJ69;d*J~uUwUtw>+Z##B%X8^miz9ru{0pa!cr3; zIxe33+<&aZD139bJeK<#6aIvSq!=)d_-%j%+>8%i+{P#1U=a`W`p!>%uW^eb&&E#) zef{h2=xaeH`K;%wdBstlxv`j(KAs6*D}XC_3w_P`z2CM$w2eWIA?4v|1DnE!0JZZ0 z4#KK=)S5q>W%nAfh}5_vOqo3f7E;(?vFjK2jrU{e37f=V$_ExaIbkOG)6>{R*R;$Y z^6mxp6+R8?BglX?;a+7XAGK6iE%AK=(y~9v3q!E%TCJ;;eFu#Jw_NEtukc<-^{KP7FPmWL{$-!lLpLRVZ9`OyO*1^Z39 zGt4XY7GFYEVLt>zY&&=h*yM84uend)gM!Tbd)o>uK_>#{X9w8zSS7`|<^~ghNl<(C z#HZ}E8Vh^_L;wr1zY_U#C*rJcfA@(I?>^XZC?h_*`vPpS&jnA!mDnbXtH$L5?(_vB z@rA4P1W0v*`wN^Fb_6)?p+7hVv>lHDRs$3PFbyCYF>@OKdb(?!Rfh6x_)*~g-i9v; zFek?AJ$i5zAsF8bC>K@J+_$z$+0R@Cg~*a_;x6u_vWqd#=X!>?FCRXw#GU`k_edf zA_UFVcs(crf0%iBQ9+Fu&qhHo2CE3-`g5ch*M;o?k54oT5c>n_;l1{1xYaYcmv4eY zy>dzHlQrplIM^6uMmrU3y#QAL@P>f^rSR^dB#sbHw(bJv z#x1iM(2fVDw|BsD-dw(Z_K%3bs-@5&qg$aGe;?W#dnG=i--88WM`j&?08@z~ zz*;Y8%@i&?<6{$~eZV{+Yk__>r6u6THxw+a!5KzLPlegMx6e2*(9WvneV+&%-U}jw zEQTo)z91uVkw6p%;~z*u#|68uMF>E1z|{Qkc&stJabbKpz~{jG_rgt2&f}YVB%To( zN3_IM?f}_j;o-G$a}iJneew0pI}B})w}+!O{N)=9%;v$d8f+Q%4Uy;3u=qo6Vj}cS zBT;?{&VV8=`rg>3Totklu;%$f2VNcxMv7GZy@2<=Dc9R;l9_ptVi zVt_K$+bBXX3DJO(%Y8Rt$5qdK6cP!mkRh^&AOwcB4O|CBVbsb8u#yiYIA7={qAt-` z3kgl!z)2u|NLBU$1~>?xA`mL=BR)PArtntrhHx{6f`f})i*@0rKsmG$x%m4CJcBz& zo)B5K+#2GAG}?DIU_%sIfVFgDL^2IVJiYiFnI+*Coxrl?9&m548fmFQK}bTgK%;`0 zj{pEyco_~3vS0)~%O-k-j3TRe7GR|S3vkZxU}pn6nK12Ff+ZVz`Zr-KfC&(JqxMi* zXpv73#J_U6?w6?iyblJCW&l#gejnC>n|vWZ1$<}AAdQfIL@k;R%;^Ljewp_K!#daQ z;ez>YVio*53o#Ra2V1$##iiY6_;AI)p&j=(;V1+Nm~D#hb7$z(=Ht0^xOmhmd-#ht z;SdO8{N)8v14{={C7kuZ&yW&j-r?XhYW1Bn!2g~=Y$ehnsPLsE%S8?bf*R|Elzr!d zNq=lU8{8zHyH0#(4Z=APj*1(mupkUj`Fs%x@ZjEpvTu$tBSN79`mJJ~fJ$zJJn$eq z=wyUhLmFX<4e3lYhDxkMv98@7+)hdgjw18X?f1={vBxSF5CggUPR*$hE5Y!AJwUTi z_52%<{@kVYcx9w(G)66K1MS}78 zF2Y*$R`cuMs58L3^wD^Bh&xL)_6NrTJSNJf-zPr+ljc!lmGcS)<15$1nHso{s3e5J zi3p8xn_H=Bs?f#71+DdLS)Mv#yUJpSU7aFfpS50q~=B;rXgn+QE_ z%3@RzA@<{@0D26a5FU|N>pg;JgJAc2#Lo?p`~cg97*!b)!vKMeIA#&yv`+#4z&nrt z=)*9v2=*SLvbjrZD7k;~sCP+g56dMaqe?>Imco7Y44OkD;cVbuU+AKc^iUiOu)(|8 zX5up81GGhlVh)0|A`lWTmFp&kkXeL8Vm$U*vFXlkKJu}6c#P~CtY47Vo&&y?31$H^ zU^xs)2QeN=A&d=y7J&sK3p{myzzSL&a)O(8ar$l$xPhpCkp3*^meY2{5@9zH#8894 zNR_6%d`Nj=JA&DVeuP5ylV|}f2nkVo{UHZ1wnM)0Vfbn@c}ff)5TA=XKv2PO1}6D} zu=x@2-5VA!{n3JE3G)NS5q}SfM1nlehYkz?7+w>QSuDda1Ju0ZV&;_4i$KS>i36qq z;N7svTKNo>qm>xUt{h_AI+PXGd;KBCP!eJ_?|K6G4Y#|IMlTWU%|zfvu*E4^A~+3i z4@8SBAMWfpu1i3=G8k@L0g8!8!wlmgqQyM$3-o??OsgMw2N(gQMC%Wr@4(11_F9Q! zKQ2Ne?zY{NQ>3&2hK!*VS zy{KeX5)c+sa8W>OqlPer!gDXvo_~@L@%rnVfB6cwyjx~PkWt@pFBy)G98E}JNz=Mn zC*m&miy5u!t&*XHW*-pU4ZXsJ*qZ8sz+X?Qqhh3uEi{FTyu?2G%bO4xgy!*N%>POl zQTC-QM~*D>MA)jr6Z`*wE(ifadKZ06g%Yab8e> zup5lY;YbjaC2TjaZ_kbT<--V=Rd4wP`E+E>)z|^-DzYPZgX}dm)&;!h3Q$X!n>Mp% zRwEJF3Bf*oj~I_B$n^19V_z6Wf}SkA5ROi)0y{xVRMB``%;Gc0_r3Gqq<)xXMg8FI z7GK~bnCrE`o`xZ?xZ}=dE0K8J6e=taV{P4!0K%=3J**$}6Xa_1UJK+D4298gL4*oF zgA*%73Ai7eY~F{pb1{ITn$lv^r+fsfJJp*T&x zB8}hb@}aH?3?UE8{*8e$)XrYeHzAYY1UmW?;IQOBh`0c8hK_(olHt3zBz zk`2=Z!|}NZ0%$#4%QO$Pus+Fx7vNLAx-EBKwN2Q%9PYyl2@-Gv)Wpj!ZGsecINphM z9`^R5yGyz|3#=Y9VaWsAS1<5lTD+Se<6&;8z*AtWoqaaD8A5#|{s_7*hr?6XZNZJj zw@R+20yLq1;5_@dZ@bn4RxPKbm$;QId=ayk z>j+)TzHdSQa$xuFR3Qz`Y=V&X;$j&if)a5@fH53fEHpGniR}n?>?nM~+dCw=g|`lq z;sZE=%B-!5#E@R@4HSC6CtSnw@KNmd3i*bK5qmOk0R4xH$`!k*G9nTJ95&o4Zp+q& zO!#;b93QxQorG$!I*8AsI=&;ufdk8J0XZB^V*N>GDJ}VJ zdA~+X#e_GURuwhMl_4C&ygH^OQL9E!Bd|UqC=3bMz1p*P)79~0x&X;A=c$JLqZo4i zUmfk=zOp{R%Zpy)^DxPdrN!@xubntZYDtKi^#$84MxfEzDOedimk>fgha|((Zoumy z_Q22#LpwJ3+xo7I74Tm6fu*qD$1WgLk7*bSw`QXzi^6h$83S4k_=S{rWx|Jkf=jaS zl_;Y7gr`6XAm(G*O|fc0&VmvTZm`U(5{N@a{9}f!86t@r{p=i&3p{UquT(oB8g+)% zfrJ*V3wo_uyw}GJ#~3Lbg|G(z%3)5D#cC}KbA30DGwKE5f1n7_0(#|vKI|NTM~rQl zCZdaLB;F2lvJ8K+s~dg)R=~z=3%V7C2J_!0SsKL3!f`jFMkte(#t&_6=tTp6V_4^9 zyKu@+_02OPj(WEM39z3)kRf9icqSmt^F4E?WU(D+F>t z&0Zh2W0wn=0G~HoUGQ~i263E?_)scu{rHipRLRd4!XeRL$T9Yb7@A(;z#c!0;d(7~ zf)}@C&cT+zmEd&vC4S7?rgc`y^^xJkWIUT#XsOF|(gfZOgTb~TR0ndlr4ed|h`}`w z=((_ogl}G0t(T$=stV^G%~~053Qw3=CwvqF-Y-}=3K0;jLU;)9kl_kix`Fb%#jI8R zC3;z0G4lZp&_C^@eS^t7f(0%D$zbs?K8P>kAjC4zJ+mb!D12Ah_6i4V6$}y32X_$4 zu^@GqJvWBVe7!>kmq+ec$u3%^nY~xd4`E9d8n*d$czIy?e~krj_W^lY_y#|0eMJ_V zAIJ(H%jiaED5FLzPsbakr8M5l`xk(2yTg=ymjOY5ln{9Bxf|D;bo@VyhFpLjSjc8e zVKg5A3mR4Xh}hN-Y8dZddTPs`+dPRrW#a``CltSMzivvEwIgzyAw=A7^f;7}NFhVV z;2}-Ona9fDMh&L?yC|xf3t6yHF3RNgM6cOD(V!^sc|!U4giStSseQcDo>J~6QrfAO z1>!;8&y|Q~!@QtbRtNw#LA)pM+!kdDZ%^O-47d|&P|Lt()S1H1BVFwF<1aE>r-Km}t! zH9{Dcvk>p^$;Xfs2`zE%QBGGS*5tv|NFMKg=|4f4xWHS*{sK%!^> zD_AO&s@Qsff#5q^`Panb8Ow*(v3yp)XulQs3A00(8pa!1gpHinpXDW%G#2!G;JOeh z0#K0AW1@uPse@FT6gKaH?FM89YxbB|Ikl^AUyxF$9XAMv2!w{hH3~LSUkiW)aHtU` zj0+LTtqp8KTSgWE7q1msWK0GtFO#xqD%Vjb$c~8_Dpm^?0U#qiV0z~IfJ3O#u8{6& z8`M)wNgD0sIQ=r;09my^;6deOg7>N5E0;3>K!n!^qDguT7vcxTF3okqLLzG9ctpSK z0A4o_SiYtHF2g(}Y&M$Oj8*`ka}cxj*bIZJo|rbF@_SB!3UbX8rhE~! zwT&t)9#2V#lgDE%{FYs^IB7;QXKC*IMAm6ek2lmWXeY0|86AFvYufq|Yy~==(LR_7 zN;491P6{U=E_byktO5s{p>7-6YMAd9z3blXidLGiv)BB2XzAAoVFk#z$WFzc7XbmV zMp>q5m6(lXE5m6h`heMK*I3j?U>rzT$Rdvh%NY_B`1(ZDR5>p(u=$b|k&r~eL`ZfLE{=^=U?c4dg9;zKsSmq7-Gg9mcM=Bg2%!BYG}a6@ zlcus=C-~B18Dc&yV_w|<4T)$*HM4c&u_4s?68sRN0w*S>B9QQgYU4{Gl341%2|6s? z1bxsIBP0b@OHALGA-v-0tmTKk`If~@bNQ@4e{az_MzEiIB2q?T2m(mgNj?wZVFR@& z;1j~ju)xJ8;AKXA1O!WqVt5knZE-P4_26U!<|C=9R)TO#`L%L=zT6sN9WT0VO~!5z z+EDY&;@}SrvO&P-X^1tm!}C_}#9)8d8w?UUa9>)C^`MFlym-$GE@ zZ3}0qCQ3`ms{PpzrEunHQ5=eMq4SgK2asYF#ODZ7+nd+zimdkB!JA;kRu=3R%fZqj zG!Ok~A(EYKHC{HWS@dIv*NF>o?g7P;xG#%Z#Fkwf;<0NPAh}hZ`}ZJ^YZ&yNbrDm! zE;s|ofYQO#sD^1?75zaNHSM2=)i+Q*o7xRdB;l!e8?Gz-<7+mXekM{PcWXcfdkd@) z#De}qkt26u$B3=4-%0gB8qGGO-R-i3P|uN{id zFp~N3i~_s-IzKMXEoX-;>UDbfBM*m-hmxOUGJ~%_g#e3S_fU}5gaRuJZYXcj5rk<3 zP!VP92NcVJR5k)Uf>7vmX5EOYm|6L=Y4cji{uAU%eUtE-yN1-$1WIAtgGGZ9v3Gn_ zH>rR@Lr(1Qu-bs)eOcSnS8M0^d!8W3*JH*V;kZ79Z7u+h?a8vmI1XiA;NZ=31NJ8; zaTh}46@j!$tbR@_k*xcqrm%8!Bh(ha+e8Cm)3B*2#im=|KcV!D_pesk3j})F+12;X zTde^!!MZ>}yDb(Be-v{U@4}+-M_|)&os$Co*oE8b=1Gj7Mk!>RU(Klu%kt}SqRgOW z$*Qn$AM!+|ln*t<7#zw#SM-3h#(nMC?Jk_r3t$L@Y=uEhv^j<#40)+kkS+29M8sE3}?xqu)w*?=5l$}h#7e+!zmi9~GoPNgv z4Kx3>95)<)UO(RNx(obRi2BuvSS6zVGqn&F{nt8JiqkpmUo$KxzoN&Qkh!X}ScE80 z75s&x4CEk2f-=yQVWC7h?hBDRZu65wJZMITm7z43iZSmAiGcXyn8gVxH)}l3SG=dy z4~rqPCNX^FREf!bC8q+ol?)`=4L?ItChY`_*x+v24t4}X$9CV16!40;F5IYp>o?vo z&Y;YhgRydw;sY1&*fFc0Y(NP%+uMr&IZgmJAR%FmBeG)YfjfNL#vZOdmxZP8wNf*! zA2p&>CIIyBGT_=^vPuqWwq%~*Plu)xN|rKNo)6Tzceg=k+fu5v%=oJ|@y_p&9;2ow zd>%hC&51XaV%PR9k8~cX5q}`~hN~g>rmon>W{dQ8S_6a1N;$M7Tl*GCBvi<<2Ykg$ zA#09!KYg6bl<+p&@>;SD$K0yyb44tn6u5M5^hIZ zM73cnj%gq8^BaWK7eJZdMhwGD`gVQ^{{A_Hg?`ccnr#snnd5sb71&*aZhG4>uIl`? zliEMk%*{nWJe=PD`ve99G;-2xLAFbFq?4q>XwM)oz*{cx1Jd=KHjXbAAHlwUbi_Cb zb)Ma5*h6-W-Ebk_=n9& zus{cMxN)AZ|MH{HciFl6pb9o9`D;YGfw6pk-JL1=2z&uUL=>d|gIQkKB^(uAuwJ`& zJ1#2ZAia<)E#NnJhOP#0fW9GHmZ{BEEz7^e|FvCIB*bJ{*(&&TWDgwwMvd@BVzRqn z&d+&^*H#R423T7GRW_Z~A*A2-zraH;ySm4HYJKQJ$al5$Pv{$Gs3ULG1E$Oax_iUa z4&faO$tx1FyEF-SaeNUTbVDI-w6FzGv(mPv*yI=cyRFG!@8(P3X-*B8v6yc;UHXnx zgYv-(1u27NCW12`V8!MLFQWlu(dnPun=KQlm|+rjo&HqZPD%i?&NCs``)N?sU+t$% ztHVWD1=Sfmdg1hK+Njo$92T*METSP1z=aaOX7m4>;?)4-gxYTwJ8@Vd=srSXK%61n zxbEUsenpk@H^Gk$Mf!|o^k3FY;77xR+im1W>Ib`}9eeyqUtj?~g5W0V;?)4`4S3mr zcRw!=j2O|Q%=GmuI9d1 zuIug(dW?W$w-fhxn%TH>zki(oI$iv11L30(nXn$n3v9vp_Kzry`M}}ZWlQ~g3`ULk z+QE#dSqQ3POI>FNaLj=~0wbMD!cICIbgZY_^ZpiSFy!V!Q4`PW{Tu3I?;wcmLbYS% z#{La<;k5i_yKPEy_JP1LjB%LnHZXk^qV;EbNcEZ9o#&tWw=EsdnIHYN;`5ximh>WJ zoO{#^NdbuR$%X>{+a?ua`?OO+HTW!o!{YlMW z#n*$Zm%u+Z987ewEz?NgY@`LHzr%VKTZX=O=nSL&<&Fk>3?1hd1CM_pJEpm&W?QfG z#e)8a>^SMi(x@5E;BZ2$ zzSV@6^FubhhB`qh?BhnVoi5g4!r1|aYV(_18h-6a*1m(?BIPj+Zg0PuP|CcVQIkeK zHRxMohH$|a@ii=Ya}!o~;YFApk;cRw^xOV$=oZq#H12I%qYVq&@^M4Duwsiqd=*QY z6R8flMhyfl4Gaxkia%`$W+!6GKFNZ`vn}%$U~KlqSkxzw*82j&@B$a;jCl{V10v~h zBBwjkfs;K6e(dDQKzze?xS1C(ANIs$SgPKzuID?A#geJS?FX(N%%V9k17n8_eXMS< z1{!`Di+!@h4-5?EqZs+U!_2He-elrdoQWj3_SJ9|EcnNnA58pP3i;w{M+k(SH z0KpW7FiLz4Q-gfn3Pr>KoF%vBbouINRft0>gyGX04(%Rrxu7OO>mi(=cWek27_MxA zBuZ$4Z4gA%?Bsdnx$FnS;QV+PytylwbFvJ0J2bp z5(cG^6&35y$!Q&!Fqbtuld-Sh5K%gT_EZ2aHp&a57*7ijyzfu@^ndFsF(+6Y+x0?7 zx07RoLf}+HoW?$U_YqH~%K?u&nWh2yPi@UMr17A4=f){PEWFDxSR9N6tcsK#dzX(* z7*00_D;@I%U70txsR?*PDq&?KXDw z5XePL!V6+-PjqY%ZjIQr>~$gb0TtW(Rg6Tkt2QCR^uF7!N4w^go-16$5w{g6)7o{`r0&8zN|O`y$|0 zIE6inju&EIkl9x_aWJ}JK4w0+SPrb$ZxqZTvps+g3GQtBbyd`;U_kx%nZ>gT_`SZ1 zO}(cgk2tao6U!hN5Dy7~k{px?xG`q`HMoU~N0!q%R{ETR`fVefrJS#uNoO}5pJ73Q zd2I_BqEG(Q*Z^2l;c-$nG{%)cWK zazE8SKoH#zir9HE0&ix8))}EZ4Zf9_@HwMu4@t)X9b_&_<8bEtKD94f4||9gdh_1A zWLDf)2z^1z4|Zx}3!ggC>EAZZa>m1l$9_13;;9e={sJ&or%W$CaylKHZO(nGU26`N z!@+|yZR!~U)~QQa+cy1HAu)zDt%XWq%Ilf~r95F9KpKu`J^l`}2&HBCA z5rk}bc7Q>kc~ViTr@77!q~j4YKg1rQ09>ItvJW|dk5~)Ci#yb>zqT0(;or-Hu^|Yh z+8#SYY(KU6o5cgg${++ME=6FvJw}O7QJ-`tmo8`IHxdeWeL-Y9kgT?UCYlxR5wBjr z@{8v~477xFjur8wi8Ijxm&ATAjSfDA*?Ei#pzq)))?DGUQcs&nkDDUJPTiH*`CozV z_4~#*mfNWsM05ci*id3+w_9pJN1(I;!<7JomDUBhH%O)FE;GyRVX-IdmOaEK``_cJ z5$z2CIE`vo{jcq}XgdfM3lp{rCX0ffeRqUccs3NaIuMLQS=mS0hiGTLkR_Nqz;KeC z%Qp1wDLe3`HMZlg0|tV?YUN__r|+4__Sd>6M7%}ihlj=3yu^DZUOm}XONZuCP9>6E z7?G8~LBSd?dkq5CSIkNi1q&vq9Q_@7K-}VoAdS_vfnhnd-j@qVXaGyVS99reT|8@b z-fjFDwb=;V=8+V`k_-xf{r+slUq~Q8{T{iY-fTxXL*aHlq3j5d#fI00>+&?GH@5)< zPP*YdWuH3Y$^4C5!HD;?lQ>V)-6xD=Ho~vp8QL4XV2px@H^*fj~bw2kq8to z=3KCcT%|*OB=FcMfDHG;AfwX;KH^bDHo%GkSmQ_q&00yG>{*RVu+fRXWJ$FRaxBX7SeJ_HB8Omm8*Zo5JyOQs5HGCpJ>x=1a7Vz> zaDGn6Se7cFHC45>3~Z?lXK`epb4}04ffnGbC;_Ccnewc_zC3D9J4c_} z93LHa@B22p0JoTUMbwpF&pR>Y<6&f&Cfo@l z-0yQwI~XRBid%k1#%QP6<8T+xUs)$t3M7N8idGW?idACgNN`?@h&C1j>QFLkL6}zY z^u`1cn3rc2xS0zK2W;Kan|M=jB=GS0P{sW@Inc-YiNhvPyyt;P<@I1OBY!|=yc{HO zmmE6x2(JdSGQqa`u-DJ-=SO<_*y}TjQy{1&T1*14}iY_&H2w=4O@`8qvF|cYlh~vpZcedFBeD1K&=v?ILB3B zZ1ATKuA3iR#X~W^F>sk;+3Cu|i8Z*K(`y-I^B@~+GZGfx?i@lt+41GsLLU94P=cfo zafrB4{5eN7T>vAWm1l$!H;Wq2acMifk%--zbvR{T6RE?qb)G5Zz%?7_z@_pTyGE{` z;VGCpD`307xc=wJ29_X8u~;$~nr$&A0&XOGY_{CKk~mLW5&WYhx4tWQpj2}7>29`S zo-<)`Z=O~OQ$tjq;n?2+U31>nCLC5pZD%p}s;7uF9LKX@pB%{tnr)>xW5Yc6ZBp40 zLueut$rF1#0>jdZjgq#^>~-;pm|eMvUN$F~h^wsT{;Db;ieX#gY7DIS8_QSUE+6>t+{~=U^7uAyQFd}miAI|Z}JxnnpxVon`It-{_eLlkz-cP{x zG|h9K2iLNB(&GR=JB11SaCG@)_l(4GE4!RIsOy}Qg5u7m=C-}Z%Lcaj!bZ4C!!X;a z5AeRI?Q?G@6T7Jj#X=hzl-F{^y^XiWJzZl+x zyb-D#wL}{T0PK5##3SKHa2Wc!onV;yNGz|No+&D4+G%WrV6JkKsUX{wepPWBk~^G; z%+nF8Gd{G4{S)T%Vm+~`#tEsXYi(MzTZg+M1w5)lvr6`7**pwim8(ySa3-F^6`kEf z$iMka27cWEa2uqoU=_llnW7Xbw_%9{L5TjyiE#8fL2D>mp!DNGliU77)CEE7^Asj? zEi89x9@awT(nJeo(J;lbHLnDrz;X$4JnEqt6R1 z1;vV%cb)JB;s00w&rzRNvGa7U-q`hFKV8oL2vKsoMNSO8j`0CIYKQtxyT^fn=XNp7XgPnsaPZ*yCl%4} zQDy6f;XR&I^LkX@QlTiJc*SeFu7!KZ^Vjv-FbN}mu?c`7JDuHU@{+eN`I zB+Xyi^_vzHoz5=A9(3HZ@qXk9!7@QIAbl8(lOg6=JX{jBkUToTdF_1aImA3zm}4;_ zdrHJp$oW0SRd3B%0;^0Qh(%AT@Z=juN014xDOz~)dwOzI3CJ8?h6ta}ZyFd?JFe!j z8Fs}Gma6A8zV_3bDR@{Xu6h@bVz%1q5lu*BEmqCJO}cIls}hYbGjjSuwIhRDU-n4s zKO?bc?t(4BXO=5yD)H0}h&CSe>8V_{v%*lXW=8BB3}b`&&e?dyT1!+_HvVOID4%DU}EPTJ=}^w5o^GWYu=wNF`j(slJ6*Z zazl>)b7#dPb+2ynvZUXrxy_xw(>yVxgIC4*!=fJHrL4|!%)v?^hNqn=i< zbj4K(XOAT9TkGQkWa7!TRqJLr^iji)y-nA+r|Nh?qFWrDMAKE z-s|J_`EU$)t?a$DMn6pUc4y)A5$t2fEaQl{|GPf1aeSMkkuLh5b8`WxkAP zZ;QCypXEjs+~yF!%fN4q??a3?k6{6Rk{MPHQ5z1~Bf&CO(EA;XpXhJT@xZAF>5uWC zn7X`xAWYsGlie%?d@F6H`l|mm0?!HInK}Ul!cw6?JFDT}%pdg&Q6IJu>>{(1P2=U*17!<3<9Pg2k4Y`1PjiEX%lXVeyqA6kPa zO>pDa&@$|1v4F*ea2e;xn{Z=a6Q=qd>BfyLs65QyV_#3s@rxIH#)Akvq%1qLc52lp zak%&()0+U);2`*)zhhG9K$&1-u; zs}u%W&7Iqr=vkef_F#h6(2(~@FiAR$5>|QCbZL3Sj~!M}{Cs)*Bo=|$IpOtHF(I5j z1INlX@f~hl77)+<$eM`vp8mwuJ=wz2sK+B%7XdN4r!?U1c;pvtO&oS(*}ZB9<%uVs zr(TH}HkOv(nO^6d2JRC7=Z-vOs0Qqr&$tcf{wFI;GNKGUaHn~mvl0$1%1xg71HX#) z#@irgG=Otl9yamLWk1qs`|&9A>BuWe$aJ;&!*hyuKT}~i1StMF`{+3d1fVRi6@l%X zi@Wj9?D+;_tD*JPbTU|@fp9t^Sku}ZpL zZRb7*l!e>lQ@97hD@M3~WZrHFR{uSTr6*m0MaBFGR2~9g78NzkuHT;j>3LJU;jT=8;_!x4*6jK00D(*LqkwWLqi~Na&Km7Y-Iod zc$|HaJxIeq9K~N#wW1XVGl)85s7@9{MZ`&~P=pGhR%q41eDn*NG$bi5j)H5!!H>nN zgNw7S4z7YA_yOYP=A`H%CH^ldw21NGxF7HCJ?`ECT&<*NR@)e$>9(1S$AoldRS3PJ z8-w7{ff-4$j5$$Ez;}GzBf!?XD9iFc_vh$Qvz7t`1mao6Fm2)u;;BvBV7yPvD+Ng< zJ|`YE>4L7rbawT98oo$^0~CfD&sB2TB)Km z?#W&l&gv`6Os6@7I2N%45dvgXP(%qfqO|IySV+=-%*Q|A`bBcdZ7y~vC0^@`j zlD0$R=9o^VbtXehJ85uRN^vtxW&$K>DNdMx6Os^!aqQT|B92WG+l;LPTh_5UtYfuW zNqZl^-QUqaG6VIPbkY{Sf4(#C_x;TCKF{|N{^hTpF|}_e;i(A2<2hb?AHj<+1xw>xR94WK#TpPpXJslg#iFFCmTZU=s! zjADXpI9V1XTI(FRLlO}#Bh)pcieXkn3yNUHDd(_G&!gpHv_{sVne!B9Qm9G*#aRTP zQqH4hlLQ@C2xQw(Tvel>1fC0Xv0;uGaW0lDeR)%YS0n#;t_HT4_gT~WLT8^ z+;g)PBW*$r*ilL2IC78#By^FQD*`-+ay7a!V(I=%9`~b5raw(6ts%z_^TsW(~QL zi6|R*Bs;e995zcCU$7Rf5Jxl&3b_>diGD6Dc46skVDi|{`R%{}uO2$YAFGdl{NrrD z>pog<+|JnfH~8y^AHH@-?%MY7zFe`IbX^S*DMWH=iuU#f@Hnw4I#r59y_9AATntG& z$H+wuXK9%H^fJS__4s@iu8d9ay@TK9QI<3_%KD+HEE{shwF)?v9 z;BEKazt8QjM#{^C>nvo);~2RDK354(VS$;8bIi^zQu4dFQVM|LW2IQli6I|tT`o?Y z>EkCqeu@76e*UjIdGaLrOo27mUB|C}^{cA^{my&#(dwnv?IEaS@wWMypH|5&O;RvY z*o&7K%hXZU-S}mdk>MfUeB&s$ed0#aiCLa`=GzPn4e|fgxuGFkt~ToHeN0VFy%#gq zP760hoXjm>VO*)lqU+SJF5>mBqY4{ZxreI3*EuuSOs1)czxm2nFbsoGVX&-3!jFaLfb&z&Qm&l8PC>F(}+Z&Lr;c~4Id0Jq+HD+>z?Jn_U6 zSNV=6JbdQQzKVF_#0tfU6!FCrOQ|eYYaU&$AeSYa*#zeaQ%@ukVP<9qfZe-yBZ?wkua`rI4)Mq%k8t?#VWQC} zkw}Em(NPW_JjmeSAe%RD=Je^)eCbPHqP4Y^4I4Hf%Q7cUoZ#58V^^_tb+r=7WRV*? z{1j)4tnUgTxdi^iDWIBuJhn25bp`9pC33k1YLy|T%`NB%R~h}_2S4D{sZ#(LhQYwV z06TZ?A<S4IgR_ z_V)IYip^1@oJUTWS#D`6v5;`!ibPo5tuQw~!SbSq#Y{WikQ+-j#`k~v5FVnGz~P>J@>|r_x(GYttd7(o)^+&ku62^DNUT^L2!?_w`|vcW=(9;YZa8w} z$Tdb&Q+amn`q&2ww`!M3?WCQFvMK42C=IO zr|4wMmMzzqwY7QJdhag2x_9pf2Df(YTCOBY+!h(5+pkip3L&?Kz%4_q47MsaHggfb zs1O@Xv0-x)|NQv#w6wHbV|dqnxAV(Um5*+_ojt0`@#Dw2bm`Ln3GR!#c5%l852FS= z_zb9(TXD5C5>s8svqpvLDhZFAKrUP)R8W|{5@$B-At+1y`KLZcRdqcd+qjWm4xC38 zWO{phsjKcqd}e^cmN1{V{gYHxwQ%952l&~*z#k2^b?a7qJ|FjgaSvB6S8xyfmgRNp z>8vv-$X$^GeTTs$I7K`5N&?Hp8FE51Q|Cts`#QMy{&tcUFSS~M zh2Oo&9iJZ|m2ohTyo=_>b=YNY}l}YJMOrNFMs*( zNT%aNtV?ul&oYyOu?3mVa-Q^J3E{beubJEK?4c?*PXBKfNefPTJYxjwI}ig-rUy?G zcDXp0bCWVQ5{mr}u}UNCg{F=sl5vxjc!I?ulzrEejbEnGA7;@kGCN;DEG+Spqi4Bp zbrYLzY{B9PlCH>DtunEkK#QkLFc>7C9B1?T4w8Z!U9&P3P;}tVRzs;LwX?ovZM56!Fxrpq)R#m8B96 zr;iI)rtywCQP;1+Whrp{_zS%GriB;p>|^3gnm~<4oiakiVIg`+r(EYHP$QBlRF5qmuo|Nfe85@Gl zMy`}5D3?gnK~L8RHlda+4F=IjAB#&)EEW})%f+pmHc_qQsH<6|VU0j-Z4DJwLl=ig zc-pzHr=1nkMNU;ICbP^}BqEZ6tVSYC>J4OKB6Y7Wq}%R&aG%vtO{xf5!6b)3#?YZ;P&PDD z#WJ#`j5BMplFL&G1X;KOf<>oTs8BHkDrSX>zZOk$vr+(?vj)4kNT+X{$-x;C?g#-- z2qpU#j)rau{vgXaH-^iBD61?j>14AhG+&tMxe6`^7}EwL`DNtNvQD|86P-)p5p9%< zR?=pSPytkr6Tde=(SVg|H*S}c=v@cJ zlS4_`Gb&b}hOW#J^3@O@UBKQdaxsy_bXqBBb|~s(0Fr6^O##wrmEL+8of5?b&>KYD zW(KR%MWt-RqS}y^EH!wzB{Ic)#mkarC7sJrRCP*ak!(^WW&_jdVLG~kW`R%k`cX>; zkG+&e`it$K*>`7K2+3yQtFL5u=%zY6K^s%qG&e?SSRIy_&yJkR%30igK|d3P$U#|d{tJK$D$N;Xj>mXxmW3f`r zOj0GD0m0BY-=D_o6?rF?Ve>~6E}YCz*C=!7l@t@B!`QqIM(0HS3!$fyQzS>jeE zaB^>EX>4U6ba`-PAZ2)IW&i+q+N_;flI*yaUH>_X8UoRgz;Hk`W}1P9ueIPeUstb6 zB{SvC%!nJ|PA7rW?0q_1>wo{Ref`IO{70;{+*dAdecJQf{F7U5vGYy6fB&@e|8VE? z|MSoH=U4pwpMHMz{pS~vA0@t~-(TPU=k=YRm%qQ^ug?hIKmYuye|{6|p9}r@#a|ap zesbiS`Hwfr&*SgsA^*8hTfc9fR^NYmg!ub9{=D$q%Af!C^LthJ^IHFY{o@bv%PP&n z2c;NS3h6%oufID;{uff`KmQ+7<#mCtJ(bk1-;a zGw{+FxR_i0U;erNyI=ml{JHiXKas3w>%(t@qjJq{xO4uWSFs^s|Jk>47Wl6}f42Yr z2f;#A*}-y_x$=Ph{(hJEO8C2MrL)h8*A>40R482U{Br@8h|ezE3@VVr{e)D)6Ye+0 zHw4zPvO%3Yk15BG9SEhw$xX(b(i5*YqB49o?|H*{Z7lJL{utZDMwIlVm%VAJvspP8 z=}&s{Q=aE}AB|jp)0^M&KJWY1x4pflnro>~eQT|)_7+>h z2A1};x7F5K@A2wvNE8p|IU;WzG2jVmGDB~ILsH2TO<4hAXF`xO) zI@|0kTnkIF@+#|D@2abR0<@z)(dprd`pU{oFV^_R~Vw^iF8s3Tc#yWbgOu z>l-h(b&vMc{_v)<*Ql+BQWwiGYGGm0*YX_)yR%v7ws^qFVxvR(4VN#SyylB`ijNTZvqvB>s}#H{Z|K3Qyb~9-Os!+FI9>8tdG`U|CjYM?>22UW=XC z^X002Ei6rnW#yOkxZ^C(DDCE|U)|{~rOmtNE3H!J*md^zS@)V@J@;Q?TC=871J0$Tw7$3Z!K+K>_mAsVu{P{@zw3J2lg7Q;p7EOx zs#DfIC*qwa#-F>NZd2Wb4`X{?UcU++9t-uY_l^B);Tv&J6FmAC@=ojbu%vxR1A%Gy?b8AE2GG^o_~q_Q39?yS?H05*u-`J>23qR=5!+ZVk)L zLZs0V86-aR^?YURz>4h3BIWPJ?WL#mjL2ku_I>SMSKE=7yfuD1MJ_fGt2ntNzO*$x z_v^0Am~h4{G;6>jzJ>cJ;lqOPlZ`B0Vb}VtXFS=Re0hvCM(43#{B-wyyDdC=f9sri zxiYAF|(cp zbcw-YeX~v>&E4mG(02B*zGYo(5q4#KG3WWkfe4~5JX&mZu$bH~Yk|Cs^j0J*@)Q$f z(c5z)3<$~1J(T!eY4)CQL2tpEResF=Y%W+577mz*egQb&wLZ)fl7k81Bk#UHEE@_G z+Y@Tf-xXPn?-?LQJ|&}IQ=wV8AC!;uVXX(-J3x}$+B-VVhE>O&U;=YyU0K;ZFMSTS z3txu94X%YYeltC9e|PCM)L3T5hz0aNKYg{Z@XV@hV6ia+Ed~G6_$Ne#MMB1<3E0q& zh3y{zY+}#jR>Gk>XxhTCBfSk|*UAjJeo2_{;yW<4*z1JFyLAA_!RQIop16-%GCTXQ zg<69aS$~8zg%9b3xM>G!nO{RLK(#Tg-dg0QW9hShW%rJ0kL%eeFAx7=RxrZv)G^ob zyx$s_-2ASMYuJUo3B4tBt$f_?_sj>|fphMwBh?T=q!6`v=0eckjM32mtn~s6(0W#8 zEQI&F(SMCUUSTwCHD6bMc#Js&P@9K+aC#V8@WZFA4B!lw2<*jUJ$D5R0c8gA`vvR~ zh?pBkhWV_F5xJGW@vc_{ct2b19hd=c3PBtlF!CV0_XCUkaH?ay;%wFk<^$^(n4WLH zMYo8;p|D`Jmid4l8n)>TST@XkEaT-bO%Dfh!H!69CfmVd$YY8!?++-csf=H5!v(R6WQ&dh5o!armvyP7ixR_mgh7Yh&8>nQRQf!v|i324vn1?v5Pd|-5x0;khg(h4Eo zxXsPF6crcKfDNmFhm8Bg^kNjcUa$Lh-XAV(G0=|>g~1|G@P))fgK(Qx@EFyPHC1sz za=;7lO$P;nFu|yR#kSJ7OStx{Yh^hhcXMLf@hJ!ka)#m2Z~-Vfju96<@dd03cE2HB z0pA0R2;d-GR=R-t@4}D)2Hr8)cz`7QF?_5zy7@MC8=M%>&Sy?30|xoQ4L|wggOMHl z9p6JmRXOe&1A(e6b?RenHV=f%?{$xF$0T%$nk4LH*=>M6pXI$3@;Q1+{>pxpV{j$_Xaj&1PsG`fKi;_KyeXR zKA6ivY57=0EvK($1U@8H9`jN(?Z_J(4Kyf%Wj|Ge)cUn*0co%bA9sbwVPo8}Tmgz0 zOq4h#qaCONMkL@G+}a1ehDu9~5c-!3TMe$*y zY1_R8$-iZ;!ghl8dSYrk37Pt|?{bc2()wod6cySHf zHwuoQOlUo}UA z+}m%B__|@K6Ep;Hf8Of_li$_y^&{@6I9cLxw?9sRJ;?G6xLfI)kqieR4U%~g0mQuK|W>T?A8Wn zAyorB0CjvT^a51|U!vnj+p%mAu=IZFKjNNQt}rj51Deb&;0s|&3Eu?JLTTDl@dg3Q z!wX>DU_W$JQ`2lYQ6C5Po8IM@nj99&u^J44h0K^8O$?-@Su3Ww}IKYW{G&<@MOFiZdPLn z`AGGILD&IsU^?LzbmQS}6kk9OKL@7jP1MWa$*5BeBP z1jq>X3bT7fCC2>m0N8ho8~8b~;|ZREYZ#IDCV^BhBA*QhWGS(m5H|SLyE?4x6+;-P zY*)|69fMy0mdF><02Bd(Vgd&+0AkZIMOHhfMI18a9Y5q^^S0 z5-iS;7D)g30{S=K$_`*AK-o`)r-v{zcW_5U7KY&MC$Jv7IWB$Ohx3PH7IzN}gD$5j zRswa8f`X&sxFVDWyCjGJY`N+}L@ZwA`QCcH0}`t0;ApZ!QhJ=2bMP+$^Z}R95;hJ& z<0Hbztv)Zj2XdTo8 zoWLv_^07tt*OCD9>|E{(AOk!tf)qi0sA3$IOTu9>r~!l?3SPL3lj*}L@_ybn!wTPf zxu|up9fqE_Y|rSygCRk|FzhkxZiZ`NohO_!v9zFm#Lwum@uevCf3Nwitl`530#HGd zELcQ4z-r*m@vQ_r9RWsx(3Iyc5`a4c0l)8FX0~buL)VKvJwb1j9=g(uYjN#Yjh&T; z1R?+$K4W3g9i3Ruz%szTQI>;`@TUeeA@dScU{;2DPnDE4Xf1>Q9ZMOc*FgjwUiK86 z$Ae)4HSi`-A>chz3@cbl$*8M12qlWGEJt_Fk~!3)gbP za2uKh2Y<{L3{|7Dm#`P;99sJE;1iOHL}4glhQKXA=4yUaHrBK*mrH0GLj)VbFgN(; z8zV-5LI4lqDg<}D6NL8Wv_$r}5dj^K&OW?`jIQYBEmi~lOh_H>t?)t_2ukbz8_K|$ zLqHms8sb?aZ)*6NfNezLI*QybqyJLL=JHm+EGpc3^}{}#HA@3@30wAz*(tC1v-3Rn zdrx5N=q5TKb{GDHMJ>|*w0&gY)B23+8O~*+g~~4$7j=ap;f3Dh-eTwm#0!inLSnH2 zXDG!&xf)~!5v~n)c*6o^YCN+Rc5%m;r!rCuMoqCpSl^;*bN^`BDwhE{JQa(H4+X}) z5>Om;sm~O!)$#zI8kYEGHxoh%iVi@MGCuQOYZ+6U=>os$RKU@%vBre;oCbtH-)vGJ ztrypXQc(MSTnv!J&<+AO#tBJ6eJ?z}7z$l#+7B~E&=(9k`c`mX*zO)C*csg%E1yW_ z%i^r3VA`kI4(xA;ChG8JZL*cxk1c#CSiJ`~$@QMVi*O>m7=8#C2Aw?kO?c{xfH%zE zVhvzN&+Dr(EFI7Of;zxOm~drQR{fJK!1yw`fb&4g;+@gc#d2W+8qWKMzF&wJ@F9a| zL5i5FoLasGAy^;zSaP|ph7_3cVlXS#P4l@5z!n8ixWOj z8vbb+`K!=0peb)fjDUwIY(MN2KO_HnophCB^&<#Po09;1N^`@^D|Ho7_UuL*NeaJlH%1))d>@?zXOxF;g{py;w8$vBjW zVdGH@(T=R0I+=o5fyVEc%|s73+m3ZmUY5y1x%B#891+^d4`G|LUvFfkj0g#)u*i@A z{u%(lxX9b33K3dn`f=9ay}nInXN>SZ-ao7fUOk2lwpo+p5HzK-!4R>j#w)Nvj~U&e z7Me=KEVF@$4N|b~`IIQG#rI?5$0x$$QH9=^s|ACCg%v((!c1A>2K0_Mc2^3<;^UxR zV{fRbdi1cO7up2T;!XhV(R4-thbF^=-jUp?fG?J@&WQx#eW8+AZ(h4G5!h$QKCm4$ zGNjfLgqaLaf^STB1w-5TG9Va4={Q0*$801(?8P}(^KZyKA=%2;s(nhB@A={Ddx?$S@`gF8qleKzf1;4c=H0ir1^zrEM)WrVm0W3M~O9rh8H`~xSAm6 zqY+wM29E;`3I(ioeW*ypJdX*&V)>J7%CI$#umQQYE(i*8{NH8|9(Qc^T3=%T-~lWt z7UGJqg9u?YrSpwh+I{ZDP`vy_|J{dnz%U!nolyUB^YyJFy~*Dj+yUMXKp%z_4B_cC zstMx#na$uGJ(yT%e=Hl?kJ3D#ewe)JT_D`y;N!l)n?_!&zkx-=!tfvx%=ikTvthM` zG-oNZSvfYupi0GqfnEbk9N_g2XarA)Xk%5QtYF<3G!U10F-$}QUtsC6#q9)IxUqKB zZisEr1+ic6D(#8GxL1IFs4#%?%d7>2>ldPsRFq=f!v;n|7{`aVHfe()Mue7_fCT`nLFNK>iYc)E(p57dQ6j3vt@Q8pO4?z+Hb;2IM< zR@oX52_UY_2|C4SUSLEF>NU?yjJ zUKpUOxMVCLzbFUpyk;1Aj0b0$JQV?Hrsw3%K{c8@P)CKHYC?6cF!{l)-~k{vG2OVM5zWFbz!MLa7W4-jfO((p zHBZpd+&&TWAeunsFhT3ao?>j^wAf%6%V?KD@~*ctL-juIfFXhs%-X~X*2cr%(3Jtz z!-SbKe>MJ5K@GcMsQ~<#4jd%6Hs#8E{K?Fsu}B@4o#s;t-X9nZ2xhxC9xYY2-(26m zfB4d1^*Fi)OG0q9R8KX|1pBroD6m|Q){)!j1Bfd5jkrN>C@-TB)7T&ij4n%#Sw8UR zz=h`u9D)#8i}SE$5hTm~&(&@`2h^*u&UjjjDcFS@OC(`3SqFtM!_<@PRUvjWcoSRz ze$l+ZksHh{`aE%?0MZu(X-NKwA3gzgToJb`Bx59DMC}sBgtdYKf{Wh8PW52+sjeMT`WfR3-&#|_m3~HHC*}>=GZl&>z-(&>3(Aa9;8!-IWbUaW@k!!ick!1^kJ3SJRLJ4D^dx0%?wLVJBeD7!>nDYoDMN_r1GWqGdG^ zYlpHTAW>+U<1dQ(g1e3XLl{2v`M%6L>NK&WI{|JHBg3ZX{GCg}QKt%b@-YA{)AA>S ze}aM>Ag9G_O+}Y36w7VL%3(Qp8F)|X+<{t}egMwae3zZafD9t?>@Ot7aHJvcQw317 z_{b25wsy0e{38Rh>?L6v6n;xgEAQP)Kh9@9ADFUqT^z<2hQ#hX;abqej=q4qLC*m{ z3kZNF;!Bgv0-1g>xLdD-jfKjL4dAih3I)Jvv0&JzeR-Qby&?e&+ZR;4(D0cUFBW$i z2K_#mnE57?zFUS7UuZXOg#YQy0MHi<7aN6{#{IDzq90%&SQ}x-tZUC?)mUTzX1^;I zmKUO@5v8#_DB^oDFqe_?+gc@98xRp+jpIaSzz5j+1avAH{+}%h)2eSlV#ZbtXac%x zIS^vAF96)r5jE~LmwH^XAtQ+ae}O-S6Z%3jKxo+Z34z44U?aWNJKWk-bJ^`S;SIzG zCwBbfy?WNab2%I}&~2d64=@l%&m!V}=7wln1H{T=xr~?Z{a!fF3>ZN!0akBmrh^6? z9wdfY{u+M0NNbIp+%Jl6Qtxh{`oycI#o-v442YO6(87nKr^p}F5)*w$3`7&61#30b z*Fd3pI?+F)6lYc2fGh;~o7j}X3edC0F%!--txKbylE9kNtBt-7R&u{~DW;5Tj^PXV?AD2)i5$j;|OzLJ8B#{eFG6KVhb1uH-2mT3YR z(p-Y9`n>`(bHf^yfO}d9CSE8;jR*_JW${)QzT#QP527%j(pG{)Rty^g3IHCw|CKHx znK%J-36-5&@*X494p_}ASeVOtyW2coWc7!^7(f*8=o{c>T;wpk7Iww;0&H-Byl_?X z-?(g0ERLZVTDp$<-UgPSXoy^fL1Vj`Dba$~FT7&~J2e^MR7(I~`2}xBLq(WZ986u| zg3OO)Y&912ZCKojIUVMyD_g#E!G59tuR%;KzG-8-6bznDK?1^#)b4At-zX|jID-3( z*@|J&1ubWT(JOd!{`lG}-?@i^9*t^YO!PC@{&m4aL|6q*bbr>J-6z_@?FD0?4KJ$u zhMPm~myU{sw**Rk1+jq>0D7Pig$QU25PzrTqP4Gv$zsbE$~IEOahb6N3RXPJTcE(0 zJ&Z(zx`8{U3eZ6QsqzQ-TcYS=H;aNXZU|yFM{_K?4(cuJ9QTb_cr_mRwqOHzo2@du z@)sWX7yt^v! zA-VKGRd0j{lkpnsdks*s6upl6-T0NT#)V3m>@)y1d>uA@r6Dn#7?q0P;Ys1z;EZWy zQMQ`19ovIr1Ap@}({RXId$iCS?dHT%^B{~G@W&S%hWc`(&z&gwO&huf;GZB zTWE~2geP#v!S)eIDYi_kWcml9e)nMSK!*hOC}R!<+UPe|K$Lf}e*FvM?e%2L?OPZ| z-Y9fKvUxoo_{w34NmLB=MO#^%G(vG3M)KSuYlPfN70EO&#j6!WqM7NI7X zn~%kL^=@~&U`$*(1Dk+<`|eg^Cwx61{;q)hCP*JTy1+MV=YiWSEISvWH;4@8sjtgi z+h<_GDy{$+hCUdNp6g`g$DNGIX|CTRkn;TWo^tn*EJ%~sg?Y1U7-Tl;v!H;7F~z=b zSp7oV)?p{Px}T=Gpe$Hr?iQPjAYi>fs;~WnXg?y6#fs6=&OSk)c!@n`wYCr^JTc2! z=8BcCR_L=!SyqVPr&DW!CcE}v#v}PfiATf-GlwTP? zjA!C`Aw_RI@r=WV_h9#esT+7Rk8BR;#RCB*AH*4nc&hwkNlkItx7GN+?27#yUBlB6 zW?Xc*cbLZJMWNfu>Kl6lj+aV}`zd_zx*t1wZa8hUm=G79dcF0lh^{4jY!=?^M%hb! zZi{_Bk;VYEkDv(h+*}C0%m(ryml5H?GXqK3PtcsKEklDX4_zi=p$sQ+hNBy&YPDN9 zJYpBoJ+LMVLi2U+QyuuLlp6R}ELhZ`=y3F3*)q2%>S(Gt>krmz<21B<^5fUoXuS|< zgHTrVC8O?!S0iGuFek%4C7Za)A&&vyh(Sjmkj!pr;jX2~&A@t>JwnZT2dvwkRx;p- znk)%k1!IAO=(sFOwEr)?5t(PtvJvR=Sp5qN9Pb5@Vp00jEZpjf3;Z|3k5*0iL)OFO zcQG4Ub~XHkt;OWt#+A@7DF6!4FUv8kLMy+bR}MVVFLi3bA1KVabRH zs{pJKoV|9P05~ws$7q$dyaFnS3kdG7i4}``w(?M6as~PBt{#$vOF^=+d6sxWGvGhC z4TD=eht(Gd6vzQJkL$BI%2XgW+wKpvg>N$-3$nwZUB;@?BRI#*-_3$noDA<)(PY-i zSfLpwycN*V5`U}=x`=PLzXdac3}TNSD<|N|4a|R{8XzqE?SpY*QJY~>5flUy06Ene z<9FkpmPjQF)G?|(U!dlWf*GxiF^>nv_Xxy)8CnbHqSAacMD&%cgEAUr=>q8W3AP7i zGk&vn(^Ki9=&)Gd`~l+h;C!ujo?aCE-UC0+!N^HLs|HrH~Bc(idM$Xn{{scueJ_FJ*H^)WUE5WB}DLgK^ z4!o`4Xj=-i;`n{=KW`c}cvzSRcsvp+_82pOjfDavu%skFUcmjMNJ@khfJWuoAR7h< z8K0mvT>EF&nf3^b+dywiPR#-WAT%j@vc~{AEAZm6ZRFS(bD)56ya#qv4JY2F_VML+ zygqgh`vgy}CyWJ9#1?K;6F81iTC2KjtVov2)tPXWCY{81=qVa(+^}H$kt6di>F7w) ze%p~{9U%{6t`Lg`ul-Y6nEy2o@YmN1sMy2`8q8q$8|NX!z**o(PU(ECmSPp?L*(w# zlcr@~7*pFBPC*A~@p?`LW1qj%miJ>grS>wIlFkEx&X|ppX~?-P>Ltz3)m01dAD{LN(6X>Or7XsXekWq zoY;hE#_Uvm6sy-RmakjBY9hd*dJw`_UGgo5vUMy&*6oboEfGiqPsLf`xUvE7+Xf7K zim;HM9r2^>7xD<`f^Jyq1b%#Neh`H8j6?$RU@=7i+3i%##w*dtJ7ZRx5%yvF8rX;9 z@A3?R6uZ8yF97KBcn?ndVGBSO$A+_J2X=k7C*gt;#_#GULWjqLz`RhdW#E9Tncgui zFyR^&ABk)5pWRGA%srnZ!-r*fP$|fw0Zr_F#YetRFp`Zl*C&em5P;8YG{oITdX}hK zlUxl<1nIKg&!R_Ibou9GIPLv(SW%uNI0-qyNyQ zTau0NT&7)vWr3@+SEtB%0*DY}3#vvCve+}EKEQ{bK2gec0(<$j>L86}!`{_$Hr>t* zm*4*3L%2nx#@4*)=9*E*5Nr8B+mEH&FfIdgtU>RfRMg_aW??p=o;FQ9sTucj$^eRX z5-9-mr}~m+C**5Mx(!zqsEI3$z!GdYAH?>va5~v|FDvvR0wG!EY5IF;yX+xO?n!pu zfqgSr&aP>=w_*8l=bG*-nf01Latt1@JRTW0dzI9I~KK6wH_*+5}rx_ zjt+6pd0r-{u?qE6v9M{wus4zcH3Mwf+=m;PELgIG7D$fi`wU0+J%6>Wiwm_4J_14H z2aM41d2rql_6f>`RM}%YM)4NlYp8TI;lg#Qt_g1iV1A-y0C#*ZD>fdVa&Lz|pdip= zfRE|mnJozepjml~1pzKsI3EvXFo8?0U{TNwgW8wVKOPI>kYI6qQTOnq`+clb=ci@% zJDP-U_N*Z}b{{5l5#4g$E+CHk!VqH?0!A>|rLs>w=luwbo27VB0)exzpOuxeXd>t$Zw}B?Z#~=h-G)Uy$*%G1C5nq*+?q zLT@{J5?5^kZ-7RNxLq6$rW9le1Cn#hW=keQTrC;_?cU8gx7+r0K*Do$B6E4;T@#ro7`H2$?5 zS{ge}lMnKUY-iK|+3Xs&Z5hyP(hdw@83xCbwW*`#h=;&Jc2V{Q@lIIngpah_3<>th zea!o1hm0|mooq;H0f*&WgEUceP&v1WYrV>y?w_~4|L0#J1EN}EoT!xW7&(F+V$P?9 z-j>W_#2lLNMr&lVlGjeP{jk=7>mCgMO zXB~uRSTd9(`8KOzIB*1IUZ14`;Km}@0d6{fAX->r$2`7oR+tXp9GtMMx0$AoD##fw ziftOGbHQ1mB6bY}nDM5Q_r4ACr#Fcf=o=D<&HgnE-YF}~k|b3JY@IndT1R*vZ zY+ph$B>}=4d;I7g3;&slgAyCKhiE_mZD#tyds+15^u8mc@x$HLAD(1EAT=9e2W$%XZz%>mL0ZN zT7beg?oqm}tZwB4wlvUCvo{Q4foTlwsYn&diR1sIA( z0ye_B(liW)iLnxGlnXt=-yqwvMu(>G%n36#&YeQ6qouvpi?X|ViYgPiUzKwI;F!ep zyXwtW3tix<_q1G(Ed;~m=yO!I6QsHf^Hx&^I5V6#c)4=hPR;1ktb2biYevxIFn0$F zB*3xV^iTLvfc;A|;5txE0|#67ZlAT_!D^))7AVKC&kJ3ZSlM7?;V@#jZjl- z7VP$hf1`!#1v{AQLUhJdFHtPGkJ00kF-Kiivt78Gj*3v+EgRI7;~Fri_6AsHb`p+I zv|Fj};Oq%AN20HA+kL-I$$iGH;1{9ox05#>Oxs~|kTVAzoN7T-SlNf)L5&a?CjxQZ zffo^77K@-B?5z`@+L`CJ@n~4H@3Z69FM>Xl?Ke~Z6(Mv}Ox+-Gz_ytTVd`~Zi>1)UI(H9O?n`#~+? zJp;*R^K8p%<{Mu7djVa=86wdNiVrM;cK?Y$q`#5hfBky>SHJlCS99TPO9U0~5725w z0?L2#Zpj9D_+m4`@>&qVPFQ{cWZ!lb;dQN40k^XDcsYyyf91|-FOr2Tw{Xx=-W}cjB%hSa1M*xxdbdB8c;>AqJQCjHfN@R z0eTPv00ZbT`h@zvX{5^XDHgC;M1_OI9^s4;ZyecYC;9_7a!O3F|6~Q>g;}BJc%8m; z>rXj;;hEM*-3PYjcvT@|mGa_{>s#%Qd0336Le?yz%V3MrtcAl!v6y$XAq>NK5@goO z!(ZNYJz$a8f3;u2+ou96z}jFX7(41Sx(UZ|+iBtGhG=jE0%G{UHjSHS9|hhY&jJg<)|rI>cGAf28;at#KDuvE7lt)X+}DM@vf;Fr1F#@p&IUxmuz!|L=5F`sGe9Py zR@Q@?uzZ=%w|^D>Q%ZnDSQ3NOfRLPEQL^i<+hnEBwD}1jl!|TipcQVso!w^ZKDgJZa}L7_?KV(aS&9X5 zZCXOI?Ll<1gOQl|9vlykDv#ZP+9kFL7YY%5^Ek5&4duPlX+x)~M>%G_@M#v%z4izO zc!d>mJ>1T#kb|+q!KA(wDQ>4dJ%-wgy$}feIOFBCP@f&4HjHHlm&MKv4baHaEsC?@ zRrWyG?Stg=FmRWBs(nmuqk*V?Gt)e+F8L0|z*@kwtA06H@ee&N7G}Z}@Zgpqg@;eN zvn^Eb^__-b%)8GeK!&qCk{BA)+wtkUb&HaaSR?LkixZhMW@heLuaG=g*G zj1{&B#9Cr#PqQ2kDt((YvbQERfUfCgsO%$vc1(bsH}G;W;?+^0COdp!xLl2A2(}4? zp{4u<jqCwXGVNRk$KSXO(y!E)0W;oz`|!`6t9JQ!tk@hvTjRH zScuqI&S+NcGH*GprNu2LD{|MqWj=OSW!QyN-Ch&sHf3Vh#q-#f7pT6B4*+S{;r}GJ6B>zx~O5ruck-GzV(z9WOpn~ddtC?Eofpe z!+>Vk&3p!AJ?Z)~9+?$C-9-+&yRF&#Y!Yp#C+eDP6*3U+SRU3k$ysAq>;)P#Y=~Sl zUg1OE^S#v`X9gbNRe^=g#*TIRy5or`O3E=PU$CeMB}Vek@LNA_zEIKA1Lg3B&~}(M z`>b{fHY?u15XhSi=rqTZS-(0B|JuWFhy_0306pu9t!R6sH-O;Rlm!S9 zgM|M8qiu6YXzR=`1Tos=2Fp4O zf1ez3gI@x*9LiC(;4j#JTV@)M!M`ma1ipbtPQQij&Y(y%Fr80_)caaT6D&ml%a;rq zVFQb0wY8u|4o&*vnV*LpNbK(Op61>J$C}MLMt06@!0ThLp>5G$Atk-`3cSUHo6 z7U%SHc`yV~{AWsL!?Nu+0{yWT-QkKbC`?c{p9lGT>=$bbxlfKa!c|(%0EI^}vh{MQ z+n)-(zo76qCuA-~j5xMy-U3tbLQ9bP->37~9qG_FSsB}dg{feFK*c&W_&r^ggy}lJ zGpEwL7$4HLDq8DizYpvdGJ4V~*;$4-H=l7@AH$Mk=~WrziU(4Hj zy$!IQUhLa$N-PdO-zi}*O!l!^0f8za6+pk?xGhH)8;G2tb14J!Jl<_z79Ol_d$DkT zt%jYgO>(wx6-UO?n_P_xTyP1@D|h(hY=!2wgX0ZIbr(#T#k)^2sl#A*L*LXPw9*0< z$3_&8I@m%k(k&Ne@zN~LAcDt&0jBsB$6%4aVG*#Lc>vR+!ASwasN+!#B?5CVtT5gT zla@xdfge%)zy{VI+4LVruW>o7t=utEv+;gDKf z=IvAmD5Pb;EqrHk*48{Bj4)T54|1rNGspn2cMr}@;bx3}$Y1eU5STC84`4yh_$(HF z&Zr3+Tx5^Q$xIpb{Oqae`Ai_NZAR>{r?WMoVen|H`(ng}SgXagdD;7CJir}Hh)yBd z%T>LQ!@$-=Xw2m%oX{&NGRqPIfMWP#hfci~Iob8;c;Piu=oXkwYpb8HUZEvT3u`AK#)D7!K}qLW{Q(Z}2^Korwt@Onv!HbZ+lD{o4l-ItbAnMg$$E zij@Qber&JPcAs^I9$O&xm}Ekorvk~Q8neeLLwXFKI@;A-&JHQ$0!-=|VK{>_UJvoQ6>ztDXGe}#mwrb(|%x zG1MRHydjImY-7flEXRi%<7-mQ&q2%YWdl5J?d(=|mJl2e^9ig;7VW^TgNc0;Cpru4 zqAoZ(BF0gL7E;+|D9cV7`6(lkk6O=jdUPjXCY&)2!G00P%SMMKAj-)K*kzY{cbKY8 z1NF)~0V{%4;^r5@`Ob9dHpqO_IVBaVj7@=SFDIPe-^mKh z1M0%6*;xk?^**q8XZEC3Eq0fH-iMXd7J9Q|@e}1jV)3Tj5X%>F$4KI5w&j7>ottj9 z|8o=Xne-#eY2qF{cE4A>rt>3iXVcgZ;4I-`ktPreXN1j%%iNM)4B}(tA0Kz}$Cw77 ztAmrQcnQyC0We6o+W8Axe+*)|9fJE}Q4327)+5Kd7L zie6_%sJ0->5#ifpZ*tx##yKV<92K;&j9Difq2%eS~!c(Ud zKUrArp$gL)c0j{xpjvg^XP#y4*5y)+IYPU3c16eGKFwX1x z8&ER*T$591oFq0~t20Y}!0)zN%5ZjPlu5)j2{HMt!Oj$;BP7*5a5 z@^pkq>#u|SWLQ|ITi(VeU*QD9Unfc5(iu;rF_R&%{PY4Sxe0f z;9K4^z~52w!Tw;kKo8^*2-VDLKSQi-B^#$p^Rauyavzu>yuX+i+$NWy8VIMolT#hh z4_b#{wbm^-vxmdB^@fyrI z+CC0daBRI@3)}^cc-HId!QNaC?!gjT=kz|-5G?o4;mWiE5vMfwPWL;&kss)E!%0V z8ApuUY#_#Twp{kECtR3AF&1~7_M8n)AU>dG@5dT!Ji z=|ujZGsp~DX4+udb>lqT_M0m(JgVwcbmLN5xc2F5JoyPz{K4t68mfqjn_4{sjaIR{ zIt=@Qk-9Y;-4o9=_2*=N+IxYC3z#_6+F+nBmF z1mpS)4RN?`DHo_2jE~X783F@CtI0WO!=Wa4_@=>uaD1X|u(#PGFmO1f+;}UNVq8{^ z-d4*Z_OH3G->@>PV~x};`G=L0pfT}yeD7gvIv;EBA4IV~n_Pi4YByN36gJD*2Coy6 zJ@-prnN8q2@twaww_rNv(q#Yj`Qqt`20T>QH0($Q$LptKXY6^m_;7jBN5sGGujxu+ zHla06Vhktvo?|s~l@LG@Y(u-IbE$DQ4pF&J-LCU0l4KJ^Wv=VYNO-D>>@f2I-#r;? zfqt@1*~$ZUEhJNlS#@&EB;21H&jyeTJbUa)#VQ+*v@_mw5>S`y+(FZ9;5CO~e88If z2Q5D}!^w2(F<$xpGJl8=90}v=)T75kYdZLJ*vaR-hx(m)Jd>l>j_{Fo=}AT z!&87B0jF>rAl7trOPl;gVD>s>zs7Ab1O(*-XfCbv?WARm+cs&#Dn4dminXY0Sab$1 zY~RDrvLg*0=U2~=eG{a*++Et`SppVb0j8Z9fN$ufShN9Wwrc=CK)}D*g$|w#Ae(#n zr8#ddP%J$~t3{UnxtfK2=Eh!Ym(VzD1$M5)iXFjJx|EI&iFO{NR==ZyoL*}+env$c zfb^L=S`JyjwX`0bmEu7Ml6c5CyIW8F z?lXb^v#cc;tWEYU!;6|%b56#zB>uB$#(|{;(BZtI$MN&xSkK8AzYa$TH=tu=3t{$m z<|mspv@w6LlW4M&s-|G97JOw6411cLMgWgG<3Cu$&71I(VW(M6Ox2TdUrtHjy z>L3&Y3ak;E4e31{uqfwaz0y7hqO9y>Ur+ZbV}j%iV6IbOfhB4%i)dx!M7uDGS9h|R zr?>_g~QoZr2DlZ%wEA4cNGWjp!4|7VbO1`d6|y!dW)R%yZho(iwJ#=L(NZ$kEW) z>1zK^Xcl2f$dV^p1^I`gnpo8fi~l3!)hy+SBrdxF?`b_$G~-}RVz&bMm{uMa#$qqh zI~-v((Qn{9RCGTMBe`LaPFMxwTM3|aI@%^aHjKS#gEQKD%4N1$U;wE3)gA1Ai#+t@ zAr+6uj+_C!jtm{c39qr*s4Vk2Lk0`#j_E1Y=cu?8k0}8FIl|g8`uXDZ4u7-|Yuvb6 zpABDTwB*;41Sw@H^27kMnila`-C0dWfX>3~jiq_^1uy>{Pl2N0+s-ki!-hvTacQ`- zOv5&56g695@|=+cpbKYCc$gH}EUc?yGKQ_@Ag9w4r4tQ!cb?@$aPBtKZ}2s!aNF$& zeh%g&wdppfz}z$2^EnCA`HE5r;ZMQ*F`1RalRk zAVAl$^xy;=N_3QpbgKLYcwxogKazPn^kO@>I;4G0u>wzKEWoo*_u^a-KZBsoiNBvv z&cJPRAME$9s|C2(RJ9Tv=?Evz#$^@f=@aXYZ8Y7dz~sGN>0=Ejt`0c~}>^$R*4xrmP(i zB-nYNcv>f$Oio)IY*QcRXJ>v0wZz`R8P7skf8ba{+k z+6n#Q56`^ZSsaONVyv8kdrzSg?IN<%#tI@29(&T|SsohZU~-Cmu)o#Dd~XR1 ziKbXV-MfJ&JSqmunD54!K$@+GMri|Ud+AfM@qK%jJ?84c6Ti_DN`fh7Pab+~$+PUD zIcjPKNBYcD1!&n5&KM=UEbPYNtR8?gTZdfE@iNJ><7Qfc#g;^{PKI@sh%qH|((52^ zco!;-%^v#J@_9rg_irNWbATW00b2DNqKe~qtrM|?1(X3F#^gsUjcuvUc2I#Kokmve zO|s(kPUl#Ibxz_q5o%L|Sis~VS)YeF9glV}6^Uw>8kXESK|_~; zys^R#&G4wNSgg=)!=~5~q268d`1I^-9d5VY7gB41gm5==7j|*<*HJZLX5&URoY}Py zY8Jr?wKcG#^{cQ89K2=I0J`tJ_Hw5DpE7fE5YFkY9*;H7xd)DA!|lDEQgY5^tVVZ1 zKhE+|Nb4xzo_GOyO|XIsvm+i5E~kGO_%{~?O0|*3&P?mGr9JrsxN+j9lTTIYhkSW3 zfDCXMOUjlq|6>wY#A-UEXK?s5Ui%r0rm{Gjal|K!wvm1w1MM2PJe+pvPcX4gj0j z6{Q_|{GiWy%r3?1WVp{c7Qi=o-e@Jir`VHwt;sX7Y+hfM)1~`_ay-}Z+7tDt*3%n- z#+u@^{bzX&kAo3w1#?3)0i(7IqnY+*8wV(+nr`PEppq6=dd^D+d&5GlI8=b56J{{$ z6Rl%Od3b~49=Y;6F$RlgzJX_QQnYt@v8dHi6s6|Vw&6JG4^ej7Nz{X*Xf^&i(4sgn z!Wk@MSpDu;=WfSMu!X_FtB$?%v@2|)=>b@XQ=6Rpe8#eW+D)P93G3S*>qJ{KSr{ac zYn|)69(zQC^`}_F_@^7pn4NQk@F(m29a-vF3FoxI$EtO0uY;tVN#Y1NYerAL|F(ob zoSqy&yCsv>#d`=fw%arCfWl+jZ3onHxS9<)(WxZ|=^%?B{|`a&#Jpr`vdcH|8sJ9J zuvuvtxWhQWcy?Cyb53gDuhx*B_?49+-IjAl=Jx055P1jkVW)1WD@OW-2&fm%DK-4# z*$uZ9N1$w-p{Jdhb>VBm_~Sue2cj8!t^SSAL?CTcNXg-FufwYB(SWRnKlR^#^|Jr% zxSZ=>fAC*^{hZl*Cy(uP6{17c8$in%SzBRt)Y!j901GmbobGl!^JTOHtllh2g3$zj#al&lE-Gb6Xmt<(Wwg(v}7)!>%^eHh8H!#PQgMD z?=xDszS(+6>)ow?!?}7g04!_4jv!aKGJ9g4{pVndWw3NvrNsq4OB!sJvzY~^ckS*$r3wQP1SAhk$o=@{3p_TO$M0Z@29b z#=OpHYL23f9xUqdXv&BMfll_a=ga8>mL3m#!;)p7Q=$v(IMW7B$j55oX&?A$629@B zb`9YyKth%rdfe5<=Vd#-K2Hxd7lV0%EL+loPk!Q4JR`+pUhJ4~+@s+Phsd8<=uSoX zE^wQTX2Cfo*~z#KtLXGtPw;Wxo&7DZtrE5VIgP;nFN3(nL;Q@D*VWo({oR@W0q#Dh z#CE6XSP?YLpq@dFm=LF-J4ozIfc>n-3Fkahz(&#DJPrvu!=Q3FlRY;j zd9cJr05PX_+daZ_2ZWkDz`Zyn?TPpF2t=!fpW7xTHX&9ge(W<-^f59xC(4TQVGXe^ z9v4$R>$zX>PR|OrtOZ@DhAE~~ft_i)&!ICI9oxTVvzfJf`n!Dj9TkL*RdEOrAmwuK z<$fGUeBy}CkG4~vJw8QVzsZYQlIAdmZJ~|l0k`E~03HcTJ)MwxP21|t=ou*p4s96O z8E+l}5l+#2#c?1taeFv-v#QiXkE<;eTo>b-AwTzJGdTNs4m9wnrF3SY-P@5`wj6Uu z=F4t}TXDxH<(WUn9iKk^^jMo(V!A|*mNiy`v-Y1mTwmargIEX4&Hc*o)>pzr`aBuP zk#QiuxNITz{EHX?qs1nebbJ1)gZrKO)KpLQ&yKTg`FBba$%VjR;4ELTP1kDn?TJLo zl23>qh})BC(Z`~3avHxQjNr)KBLW-)s)F9l7G|h=w~={+(Tg(|mPZx^(8%Xpq^MT@ z#-0NokDOV+dqAhqJ8p|MqWxz)u2mF0|0z{i<|S&yjC^M*mU_+pcyP9nLl3BZf2^-{ z>rnq*1Q&CTp1KF+JbCXRH^Gcd;~>u#%s!r<6pOP*B<2zT8wA*E2VvO+*1-}obmNC7 z`8++!0_m#r!upKpR3}es(>H2@Z3u$58X5NV$7l#g52k&$E2es&OmsK8MJQGkU3fH) z$c5x&Cxtw$DbUIiBk}n*1I9J(`DdOqTLz42c6z1(G;-vy$<2MS-C%gn{K^g+-sfE1 zZKH)x8f-b49tr)x{lQM<7NZX^sO@W3?T$W7yZQRv8@dwaZy+4=fm-6S36Jw{sT9Vuf$pf2ywIk;Uu(UM$PBnws@B&MwjQ zZ`dQaRLR{g8N(IPtg*#Q&LV+LMRdt@yG6VEB)gt7Lz?Aj-Ea}s)8g-EUS}?4THRt= zORUUKy{FQ}=|=2RD;9`iRB=VX4`=3jJ`Y}n+YF8uX*PjhW(%Ae@-9=CcJvjeMf11& zkYfi}*|*Yc`;hlRVV#5A_T$BIT;kgRJK9d#x6;ne5S=HMe4X}pV7Jb_dA?z=IuJv9 zj#fYUIHAE75UUc4KDw3zOJl-iaz zmqoC*<7SLsba=ZFpQDKlj^zy)joHKzGOm~=%_WmkG(rMx5EIs=Y^yF zp2!O*^7y3a_!1r!{fAl?e6{VTN#V7^6YJ&i_7_b3`3VZPBzmMK*kwS zij6fcrNW7B9508)Jzc>2V*+6QGk?2?PqIy0s7Qr8D+;?qp}6Ou=gk;dn1<7+ zc>qu6wsZcy%u(7x2=eJ?lAmWL7{@;ck=QkLo87b(U(O~@io?D+=Z_+sh>z0xUVk<0 zKj2Z%AvrTmFcVn71zs}2XNI9(doMs5nAv-^f)jdxCee5-T z-a9s_H~#?rV(%Jr&lXvrc}w)5n%W(h3%(;;xM9tq=j?)s!5)q%MO?t5$=ZES*)~(k zkHvOLR_mmy=jtB(Tx}~o_Fy`L>8XVGfW|mS^%jY7&AR8et1HP@`-;4 zSMoSqtJDoYi*q4pCR|~sCICD5z*8w*qH`X#xo8M z3DlrqR4vAB<9{}m%SsebcO&&a$o-^v~9Nc4{(XBT|WGxwVNN@Mt zk#c(yiDS9ShKb?KmdCKMxacPs@QNqAX>(x~_j6a>)WxtK0y*QcN;rXy@qqQJ=eA-M zJ;?xVcnybmP@5S)PdYpyK}S2DA(x0sv#8H0@s5b_7!X#;scmUz&w&RQZPT!T6y##Z zkU(tUcciK~JIE&6d6Hc3IoKL*djf0?LuP@-PPXegHV*LbpluJmZK~G$Txnsen*~tu zBrSm@w}Uawt;J7PhS!2wI|FHr{0jg&V6NtJABzq>xKB+4pgdTl0s%8q6*LkaiUPvM zb{3DJ0A-CAT<_r|-3W*GSdf-;=9g!OZO=Kb+tiBt=m`IE24?`L1|dngx91z!8VJIx`!tiGMx^sGe#=z=mgF7Za0~jd{e2N9#NWdp(xQ zGXe@wBV8tz=b4M_9LsLUlVmQQn}6hoP$1Bvr@&bg{bNh@zlr+&3rh&$#EikJr+Ljm zzdw#3^2Fo?hjyEh99RXg%=H}mGhxBQIa8mb&{#z@Hbbzj!1TyOMCbHFot}krY=@^} z)zjk)G~>pvosCUd-fe49HTVO_>Sth~18_XCIv(QeSxH(zFoksDIY)QHUbn}9e86J? zB6^$UlAh$@#DHd|+oqcnmI`(2bpLwQXJ~(+2 zPd`akXshRuL{FvOp8E-18iuCr2k@k$f`EB08;>?*BKwR0sWN+LVQ`oqjLE2rvwf7L z)8THFZ?-%K6y+?*V9Gk0tH;w?(Q9X@V@5FN!-E*Ddb&O2)DHTS$Mmoi&(Ye$=@Vcv zEILnM`;ic5vTNNb&~Vr1EXc@W>FU_=y^})>-)Wh;#rrdRGFKHIJPw8DrNLRTwcd6@ z6;aZMx54Qw=g7bD{srujcW1;It(z;HCZb#C=Ul#1T^OB8&fYtNyk6z8X;vg*ffWyt z>U|>FMi{zP5Qv^J9!Q&?&+tZUxPN}yiSn$q#nNd9xBD<5498c+xNJp63u4;+n z>v2F>J&%8N_~dJ~3%*iqL!$N@T9!~Fo ze=}c~Gk*t&!}QE;!0M6@Kx&CYP*M8n)qw@s?F7|wRb`j2DLcNULV9{}C z;7q4Z+JI!R&moaWcQcpmv(AiXb?%%mZIXcJ&%oYq}d&XQd zc8@Eb8KCu9#Hy;l+u^hx(-R8fgLP=8tIR5wlLoQy#&J4qzqxxLqsM|e^l6Oi@aS^_ zOL(H=p89EQ=HZ=4?o|Ei*re=1cph=-p&$-FDX)=MAd6=TAZJ%`s5qMs*0d_nT3&lz zAJ20#<><`qXsIJYcsmD~8+2wZdk&khYXVK&WnPcxk-jr{D5FE|Uu#sHiIpr%i;fX% z7P*dN6o)6CSaSrcNW+;k7B6_{+T}b?sEI8R>2}JSnS|5a5^UsIXUv>c`o@N{C3&7# z3U=?9DqHtI8<;}7KA2tI`?HHM=m#v)vOd`pvT_!xzP9Bej>U5$A*M@7{Q$YYp5;XB?V87--J@I8 z`8nUUBebnJgcVEVBn*ryw8CRbJrl<>z}n@!4kzhgo=UZKDxE8CAFc z2aI^zNrr>yHllk{272uY%lTSX$r*mC&C#~OQeS7CuyX***IvHIs!Puxc9dE=nVjc4 zF}~a5GK*8rA%6YaJ2=$LZgRd_%82csp2+c5yV2z+7EW1A7SFLs)v~)<(?Qvf=flp+ zh5~!hF}~u3a;g{~+dVvG7c_6tG|#Tlk7xL}N5C3Ezso^+>AZPd-0OxeN9UE}=$viw zfs{Qq4{NtQF>igAGMcG8ODs-&WU2|f3*xR4L39lh^?HKedk%Q^RCi!>x1-+5wPrE3 z6%FGI`O@5<6oLgQ7O(0frptPHAkM&`vGvGGkvYfquP?*g($B2c}`Fi?xz~iG^1mzrR~5?7_NY@oe^AC zaX4-m<~6eiSH5ts{)^&aMqq`uiD3^K1Y?++`Qn=`Cc=W}=peJ`5!_Cte$Pn{+<^vf zIo$)Cf(!5O1q1M)S>SB6+SBs|3<6D$m$gsL%FYu>bG!_|${9Fl1yuI)K;w1}tH#V) zqQ|?k6Q|6ZJ38f@^L9>X+#cb(9LV4#SQq&5)NW|c#^+1dZV0!t4w6NK5MxjKZsQD% z_Sjn6e0Mu2Dd1~{=R9unJkDe6{Y>`PMBK-z8wxa;2_VGYZUc79BY>a=?->&AnKWN> zsyRelFk9fCmiXI$w@d{)=fy@>HUqzQL=DP;z+^j;oI!ZT1_3J!(4tIi3#QiyKrMIz zgU5WLjnN-~+Yub?N`P{>(D2l^=@AN!6PTU^gW@^UGnVQs8_%>IP5|*3_chMBk?FCU z#K}00<2&=snujT2A56(uQ};xz8XOjz(mY2U2NMCbjyS95JZn#j#=i5sw{@SlJvB$a zOLxGbU5W7o4W0JiyFH|qXji9>c#_n|5;)BYYwa?5SbWY<#&Q`XhNe4nFWhI)&azot zJI++pRE}0~g*+EsPGf~&oM!*!ZIX@+`{CG7~KZ9yf-^)7tDe{3;6S0Gxj*Utx z(6`pH+>Ci(`k$vY+Lk6jnhGXWX&;)sJ$|pBWu@D?PoU(xmubP7%X}!V%9)ziEt21> z_BL+nYZXoT5{OL68ynLmt3@+UyW8aEeF_xapXv94MaE?!X- z7>*}R;_>zMbyJVds9EhGcjSYPz5i^BL)cI_{rC=e}e%cucR7|0?@HO2n&5OwV zFz@7*mNZXDHbqEMSmKe2k`l~}qHIfisn!|em?L;n#fFa9N7L0U;dZttFb3cqQ~oaj zF7fyn9(30Pmps+HgB#(L)eihVp%K`lUyyMUR9ksO;speZ*$jDV-R}yzX~S|c&It6h zxSQ;~y@!p>h98{Um6S?wWiT#^6E zEZxF>?nELp7u?j!`7I9(ib^y~#f%gfxoh)Hp)gky23h$HzpE`Ndrk}6XQ~#@%xK zAKXgjp~IBw9Fb>l404uoT7K83%zxY#MviE`L@x7A`M=f;8>hyoV*0XXW6axrA`}xdOh+)-5}l|Sl(;NA3`#Gobvb8 z_3zp~?YG~Ikj0QRPhuWvi(HN0>NJu{HMm}nTFC7b{P1+1_bu4mNCTx_n>Z@cW9a58 zol$ij@@ZCo(R#z5PX+O51+?ATSN-C^DBE)=al{CR9a19oXk5LQX8@Cz-Cg?+EHysDG2XBTK$sO*v79Pu3ujI0w{)6+ z>`)(KEGFA%_LMec60+U{E&c@N)O}2zzjtBDe45TW+B2!}6Aa&moSCbfAQ&>9+LQd` zd1WU!^UY$hn%8BwXVZ48;c<=jeTl?Ql0NG?=s;O((gq2hBg#o=u$75kKH6!KB>%o( zd=NU?a$Kr`R6T-3y3`QIwI$S|o1WX4pJBX9H$y_=&6gjN9B++u_h52+N}})zzL_*e zt7;2I*!JFssg?xhW$E26(Mg~gJ@U9YXe2qJj?K53Nn6QG9{3zLl1*&^`KP&C>mLOx zzV_sZY&f;N$}($R-q(r|-jwZyu;Lt>Xr%?xd4O zL3p1GH>}qkwb4t_R4tecn@Dr;3$^FGl=M+UP))i1@mwubF<;{uJi1vH>m(E4i6_8MhQMkmX*4VMK6E&fy<+x^fOJnfng7i#F1XJOz#BTl=f#rK)u zYHpqPz1UqdzoZYD)1O0)8`687z?7vrxcRahR|yTqIaR<+5i#g^Jc?i7&F*ckb)$Tg z0IfX9C~>!E*s+MJ@V+6 zzj)Cuoj9@^5W3~mKsM2f@ZORzlIKSpYQ2}Y#03&N17#X#7%WZJFR2+xqMxhn8XE+; z`?dyNoZ-VDa(;%k^B?AwvLkm|aww6viHYGhS3jS&(E4jh>Zl)8oE(Skqty~6V$ASy zgM-({6dWgU8=L6w@9%d#i|-(6H2#juQ4V?!2U7pc%|j%BupKEy-x z(Mz3}pzDQ-_C!_Yn%^Vv?A+OA1?P*;eZxo``x|(yj!Eft*z>Jzh+SOE&z{L|V0}J6 zorn+8!Gv6#adV8bTZ^$>eGir7^$O6nNlRSQRI7qH9uryeeYfL4>3grmW1EEea2Q6N zI@*rw-S0s`z85Jd@3ZA^Rg>WP2z;^5oyww`jAy6W`XuPG%&d*fa^9K?pI*aTl17P+ z*CKgpKZwL^qt{F0I%nZ3M$syMn{n_l35MFr(&R!#$`Kik)V2w{iK^6`&sBeKsin1} z7Ppzfd{Q!k|2N!gPSm{NaT_@=pEphcn~a|FnD56%!AX2I zUtDgE$;mY5mdk`$*i*6$Cbk9N?8?K4{2g1T>Rh8u>huYg)3Mqf-lq~Jx{<6$Ry7lf zQ$925dBWz3p_7OEinqLctA%)9$^ad5`u?nIU)a9nJhpX7%1Peu*3yHX23~4UQd*+= zX@O!d@xlQ7C6DOhot>jcHR`?49s?xjlSMTlQe(v=u3+{hfo-ai?`OX@8lI$#CZ})2 zL;s4pMydbb$;^4i_gRvwrjlHSlXX67{k7(CeB{_Tlh``8Tx8a86HCvVrEk6_M_Pun zbRbEhlb2u{5@%;WH>?Uvhb>0roymS@w zm_`c&QV=@8Zb2fY*jZv_N4{DoOWF&XzPZ$Z(viyN^*Gl{Ekj{S|N0)Zqw^aAoUQO& zNm6)^rId_lu@ZxL%UERDjM-4AH|8TPW!1Y^Mxv`QoDXr&Mi+>e5PLOdV7tGC^tHSIt*KHW^B#iloeP#W$G?F7FVi$5YNaQa*7KRDR;LloU=nVXaZ$CI!EMlCXdkdqpabMNF{pZa*s64aGPq#5@P$CVN+ z2%03tMaH&Ui1^IY$Co+*<2lPcPvN*{N+>!p{f|*^l4dvULFNg_PpQTxVrwPYnQ)7j zwU`}HGW->K7+sWpSi8*Fst?tQWuB?Mp2xtHc6o8Wr=Py|6abJfzgkwNk-m~u&sE=c zo-o)TNcOA-f_(@pa=*FSu%LEYCdxUaBt=TjCol|MD8O!9l5$l0q2KCZK^$}^h;d9b zn3C{~Gd5d-d2Lf3ik7x5&fMLyj6Mv5ds$EUdG_44AgzOjEJF5E_Hy~NmIzsmy99gM z;#&{OeDb&Q9G+t?=6r9S zIP0*@$wdk9)Lym`?J&TV={XTU62jy5md(#OVmOIZ$aGg3*L@Q&%P$Z89r`Bb=vvW~ z4-VJzPmRW*?k4JYjk z<2@8Fs?UwP>S6KFFj;edwiTd1o>V?)Res5#cq>&sBzOFVlF3S+tV{!mk#y&*=lL!)f%TpOC%&A-`68CY zU_ujxr@6t?{7{AF=5i;PcG;cL?PH0KE!mcw*)MF7XJi`xTt0%O$9ZUDspjk)qVv^i z7TU|A!mb%B@kVs&z!>byR8pvnp(v*1$FW1?$g>ZSitgI>Q3yCy&7IxvlutBiOeoHe zfPeitrfzhAj1_(yGM9(=`I9QUy%&3?Qh}-NdOlI2yac{jQzTQ6OoDQeN4)T1q>YpR@#N|yKh)&|6e@y8J% z^(x7M@~+~@L5x43xyni!d=v~T>qhV8%@w8M~^=`5SZ zX)5uB&DWW{9SB@{W=7*{~{|t+}9%aHuDcSB-Q1YKhkQ|eWa)p!-&z@5B3F6QB zH&jV*C2|DO6GCNbZm%V8nkh-{A6~=8t8*bz*`Qu{|4@`y9_3%oqK-a@l!zF?p(P!UyC~8LvFIt z*-q>0D+XntoZKp0toLyDRx50miQBzC&uT<-*p{Bw0E|3n(zK-KS3EOvcqaOLB{u{_ z2J?L$USqVp{cXJQRIuMt2b1+uuBYAc_NS^Dm|XVjBC34o?|&tRhZh-i5`dg5kVJ8owg450U}q%L;QUfg7|&cpf-*z# z(C4S?HYWTy&z-A;=5rCZcS_7}5*f=B`WQ1QjFKmC?KTJhyQW0QB+}_YF8XZHk380I zTWfh&iI{J#&jdC$mgO|JgZsv!xa`~y6in1Sh8$2&=S$(j(6dnnAa9rZ{d=89PZBov z&z~Csy zsjgXLWwtcD*uaN8JC+?^@uNV7K=@K8I0rMm#8zjjj#MuFzE1a(tlfFkPRo^OQwlqY zxyLp0F|g4_kB*#lJ|UJv>V9Mmu1xs~hP_Az6|s{J*PTV*UgP<|9hfg4ed)ixUOrgB z6-6^X$3L3w1N1pSdo`%}lyGb$=U^WZFigx#-k9>3w*dBDKw%2xsgBbWoJ||2Kbm_w zO;gb`$>2Q)MagAq#^W1?P#zJ@`u*4~YKis)3y#WV(HtJG@!V3gR+nk#HRCK}lakIHP2^IBVnr~g!mwWG>^VoTs?aw@_Z7tt%IeN;8_8IH_ zJ=?HoLu2!El9-Z^7w`E|(mw|@c5l}FsCDgA3K`N!--92x<8RCu7wsZ?AGmqhn%J!C zr^JEE#=Pl$L9s5D4N*BY*Eti|naRG<@mR3%*BoM0#evZ*!LLi%k`T4*iG9BJYN9yJ zfy?ch`eQFOacyi{ZQs!D-HtU;K3_I(XSBO+0LB(TkC1%dNMQZ>C}xhzxZHz$in2a> z7#5HWHn+!Q2=bfB)1Sj>8%H(ytwE97P65Wxq2`*mh#$zWwqrUN625eLJAIoH&_R>p zEMT0Gw=*$DBbBN9E(qwC-Ue@p?9wj1CP#V^ds$EuucgM^o_QMc0Ne1WXb1{5cymoq z`4V}6^y{KA{x&O-m@YN@=P9GAn^N{#Kz?!KT7u|6isq5$cR%npO%A~8P z{IcE3Y)3g=hn0EWQy(NRePYSpR*NI4513vCFy#Z?8;{S0{-pirgKuy?UY=Q3zr2mk zD6sNrQJdigx~90hgM^z*SHpx4W_QmP%sE2y&84qrR;Fq_>1{}5dLuyKy7mpQev_ff zJcN2WfADIU)q+pbp0>)>=AVf4?%TaC02Mt;{U*Y1jeCBk<~tFjfT7eDck%A?c20Es zpz$&FB7v5cV5dNxB7&ZB{&kmn){NN3d%ZD4 zIa_eH4U2`f(pmG6=Q%At|5baM6CbK@XTy0V)f$a*-~+`Ut)7G4ihU8o#4`AP`A{!8 z#MWRRiX=1V)jQE57l*HDaQ#|7Wn(^f&7SicKQ2tjrwc;)XICI;I&U*K8~KYt!nlzv zIhVYn)J~gCa+_{}Li7;F5e)$hANOY(Y#-sHx*EBM4nq^8m zQgbcEn!U={DBo$j2K~JL0A;DA-tm(A_ethRj8EkhAeZxjE1?dE02 zo6kHW%ex;vcp4n$>N!y89yzLWS(jL06HE+o>>gH4Y@gFXT$6BBqdO`;(r8$AgxBb)aU)U(I8+qlg)Pc=H`eNu&+*zd9K%RMp0g`lWDO;0U2&Pgcp z=oqLtUuQQ%@~8|*QfE)SpQ(cmRQG{EQ=-D_!qGFY^svxy;|85yo-eZG`HoIdg8VrK zwm3qG-V}%1YSA$ml2qcW4|c*`?L5kYGN5~Hi}lvzuEgtF>X`yka^Ef&h6-IPK;tUQYHrptKp3e~EokXL4pC1FgR)hK+^2E4Q zD=QYxr;WGQ`b;@30#Qpa?`2vf=5eSzZnjxEbU$R=`1Rji#-(O>dJ z_EvPZd(%#8IY{R>9A+>n`?OOZqO8m}{Ithr3B`byMO@#7+2u>%^{%OaKws{`qpcW<;WufoL~BmH$p|(>lVs+>EM?1yZtt=l z`S--pm3no`w-g1m-AB7M1aU0mj0%HyX5zO@ zu-QB;t=3&>UC=>ZIM!%*EkT`{$$3RXV0^v-ZkdjN=zy3^gFbmPU}(wB?v1DArx6Wma$vd)8GkJK%C)mr?P6Q7?H?6gtMO&ZEKQdgLR;Vlk9On^{xF0KA2 zArSpt8#$q?eB60oJk`3NgG%$4C`2^fLM+5mM4qVU=W&#KFWKt^fi<}z|KURSUdxG~ zXh4ZTIPT|IrU)~!#g}*Bu3kvdIgF_~)wCBwxAhFl>J$TVHP@@wM!q|^h9mT@nx2S5 zl6$F14pPU`5(+K@7Ru1ael8R3nt?PKK%}H4ysVsTgs#c5|~2KFuuh$ZqWGH>VWMNvEOv(6~w}-z`tm%sh*L zFBtMBSxa5$WVEx`Z5F(iH{g-S%zCnN%2`wEl+QpyRS?W|q-*`?D{}N8VJoa##kILClb(SBF9L`b>?%LXsq`?YvJ;@Sm@fU zC(FJ(Bk%H+tly$}9A!^->24xW-j^nF9>u3Ro2wJ*Z?eD)o`19r+Q(b$vGh9EbDFQp zBUHU_a?ME(q}NN@g5=a_siG5rrM0)_CkB4pRhQkVAnhV0b*$Eom=ZtV1dQ*sZ>v4w z9nQUC&9lA)$(}2>h)vM}1TBV)18ASqTiQ7#>papTFj9NzCG6ysnAOh-(?O0oDV(7Z zaZ*Uz3%cFCn<&Ru?bpUQ`&=7FSq@)b9-m^3e?uC>ZM!|T1B=zP81^X)ZGQ(1FGJAA z!b{qo-Mj_j294$SeTgH6qRfHu19bMQx$xwc=M5&V2=92(SN?cChO!*pv2y~ja5!=b zFDm%~9a|N`fu)S)tp}FCect~`)OWYlLs>3?v>YnEUb;<39?GMcM*osn-joBC2#k8b zO4)uS$g`A6TKT8p9Od4r_1!g#eO&TVb>;jqvmouUdHm>1$HT!jVRFY>!fa1QPtn%K zv%t!FjYVRDZ6o7MIt;-FQKJhvgjVIqxD+m0?o7q=F4oN;3A#V=m z-17gxgqf!U)x*4+eiyNGNcA}*QS`6)-6aQ*xNPCK5&8`lH7qG%gq7m3+KBRjL7&xi zBeI}EIuS{VX1seUGBm%SCC(BwX{;h$?8soI0N+(sh<1~Tb>PqNBk_sw4^E}4cp``v2 z=vt?;d;tGWrCP5CCSPrz2?M;Al~Qf=L4HO@TmPuo#K7`5Ice^+&9q~_>MtcKbqe-K z!E+gu(yYtjN-6Upd*%CzqWMb<+&_;upFEHz2j|zo&+$BKqN3Zo@Ay5cz665g1&qGL z(V_iZ`4Z@vvji)y&RwcsCKY?heki!OXtb{Y?!)Rn~nZ7Io~n-0fMb9|AD9m=cBgkmYeBA0i@m z;d`({kQ`(Ty5D8tJFfYni{Wmn@!W%?y&216opzHyJEO^p?^opgTd#gwpOm6U@{X6j zz2)*v^J>EdNSMS^)W1yRcuA$|iuMX^N-&m_T4!_WjHkXD!OBFPBk7;S&zHwxdYSV1 zRl()g{dN*>bHYSEQq}vhZ}KY9sM2;H@`N^kF_)<^U2Bx>CM<-BdAgPOk;rAL=KRIv zo7yl&5^-bIAGS31WpE}E(C~O~dnzg7@)bC#C$bX*o)Pn|617^Mg^fbM$YCFHTgMu1 zy#eN(m;x0JrZPT<(-VChyOZu=cv);|l*aPCpyFggiQ?2ME*mVdhY}(u>A}DNCJAU7DLfrux-% z=FxVU61|kW`oi9j9T920u`OkWHCT4$x6R=3E+30ttvZ9pix67zT8ciM;^QXH zgcpnDjDG_=22|9bB0D6w3EtXztQS$(HTT2}bjtZibxQedBx*Ps>C&q(bqz>M0=Tvz zNPRYpxeU&F>2sIl80zI+sF2YHGUYX*w9xB*w$hz-G#R0!sNC8x=eLh5G2_m+qmzXF z=p`6f$~vnawrTyE$GD+16`L5kd(pUUX-^$Je?=5X7$PsFoXTMVv&SIa701??%(p1w zI8Kre_Xg7uDImv4XXqLZ1nE@k6jq;{wEVSBIx$fhg(hEf;wtwP?Rn9t`qVoD)7Am- zJYZd(!S(^m95mxkkW8-CthIQy6qKDA>yOuHXV)3$p;v9yGr1ut@(3i!h5Ed5+^u7{ zzneoX9i6ADTrA(b>sBqugGsT;#O=l$T(&7(I+wp@ng~aKV%XD&+C_ilwy~qDGEQ@vaye| zK9Cl=)i>&lMcSE?>Znm9+MFayO4#R0jTQi{*6y0olrY*-tNC||(qkM(DxqTuZl}7e zBSb25LV2}~o}{_z-Ijo+w)ja~c2byM+!n~Kg=WB%@^&ymq^DK^s<^3BBKyiQc=HK5ldUgkgpVc3%SE#r(#q-a*#7@+C4nT*GTbY@iuy`md~;L(8@NwJJMQB1&h zeBC3!*1IUn@;~?I=uxwl0t5u&S;a7I;tk@dP1|6+Ps}R?NhLlf9yRHL#E(puJ$_@H zcbQ|EyqQYQ6Z6DEzJ=u$Rtly@JV_i;HJ$Rgw8tvrEyh}@qBQQwUKq~mE6Yr$IfOVC zu>=tUWK>W@2{xj%>ZDjm(tgaxKj8XBa>?W>LLtWj%FrOWey~6I-K~|I826IGF`)g$ zaXyBD&@ND~I?ng8XHE2lRElS%AysdN(&QB{F2`@U*+rs(VVJjeWP zzRO1#M^nW|`&;qM&UfQg3AxU2)bKZXN7wfsZ#~}A*Wcz-dkM4m>fqO+s>`yd=pz33 z^Sz}S1?=Ce*A333t9aDd&oPJc%WLN1;Y&S2q8MY z^Zc(0A>Q(uK#2UfuI$IRz)E`@Rb`9?PdHSt48kBp;1D_c`5uHAt{8sH z;s<6PZjE}@(m(~s35s_B8_4$j=KZX{?FdVb5U;!OQP=?Xdx6$R3n3adNF1TPqaSrt zf(OBlusC7Yy?v#&#>gRvopm8XL~IBM!Pa+J{gy*e8-%>?z8e`yxxVeEoWI~R`|Ewi zj!4Kf$O_{eC+N@7=w~Cb+j+k-~_WQ9{*b{ zk&m!7rV#?L3Su?n*f8*q=ZE39;+nQ=t7h;8W3cI8{eU<5 zIu6P~>--#Dpq24_3U5IO>{DP?Lv@Zc*LvaU;5>jKglGQY7|v0&PpdHE#IOM%ie}%! zsNnu?E5%pu4$P-)&H>&XA$m@*CpB`(ME{BGjQf|z^XWU?9SZCq>{E^W6fjL=e}*$f zMCE-NHu4$>-u1P%0=QMu7)&#u5KGW|sg)RxL2)AI$X1Q3tAGUzh`WZ4Gh2{icG;vzcX!!f5ipgr zAKjFoce*YmU8!*H001 zg_7GNyO=~Hjti{OmL1-WXzI@=VfshvSGo)#nCg(nmT9{BBYp&~pd(s@=#fH=&|;QE zMB!*LvIlVYgGXD3*`UZSpjc(kk?gqglJ~MpX~#L{@ARV}n`lh5gwcZUtEKtc-GdN) z|mnjuulcLTu+6%j{K~B5-+oIJkm=nwMms&B+&d=XDvz;%MM?1zGDG+lI zV-k%wa_|-oaf>gH;*}9f_E&7SmWeelB2TYVDmSv_?U@l>oFEYf(N9=MCdSyw&xt-^ zu_G7QpXK(M^&5wVYTWI z)1WhcNN#U`;;X~hPDcp8F4r|YthO;vi{(GXeCqfsJRB-#JO~@cXhfS4s)R$x2fqk5 z_`LWFIYfvvK#>QtUf>ERWQcOsTBFST5pS=xZ~=awgb)lT#?@IwIX5yV{rNE_F-M;) zs&QRUqKnNI@%c|b-~YNf;j&cD&giOleQnh`na_-Hqjh4L6vj6+{}tc-VzJ|AatM+? z89~6W>~f#ywnG}bOtT~go1&3t_W6uq3MU4Xy~4jmtpGP$L2VS44bAGO5Px)B{UW^# zrx_o?7tf3#a_1z3$nvbCmY3@V{fa8l+6IaOku}=&tmOC2i2-E~9)Hxu(0&}(bgYLW z+VxUG|0IYbVi^ zeuNHDpfC>tdUXLRjO*L#BFG;R?S)G{fgi3?95^D2bRuyHL@7#hA2xf=v{`NF+RTMt zJtt&SWhap=;|yX+bL775@SBu|&zoPWB&Jf6su$A8gqO6^XN-`7mKDg&4e^hEc}^Vr zZ*Q$&wz~fdp$}5-r}M>VM;`IB*mcKkdqA#hQCvexOpEDXE7nQD#Cm>5cd2 z%!xdE3D;@Ed?H{5xbCg-tM_a;;T5Y*XSJf~C_!N)byYyjqpOzUm5KLQ;a`prhun=F z6&gSl6_K*e22mvYEB?lYQxO8yfC@F?QeWt#UP&shO;vZ+8ym6}zU%@ThzhJ)Wi#r@ z)^W;u$n0Pe_v?6s7^=})PGDE<^ZiE5#2ij|gqVpIybc)lx=R`}(neDtQ8!JvKiP6_9v39lioG=H@S+J=#&#rSft98CCd!0n4&HvHHn0m!BLP_x^LPjDOoR z*WWWo^IK0jirATU%wuQ6;a@y^w8L`r)_W_mB6q^}C905dcc`PMx;6!=thQ5!Xx_d! z?B*{Xdl{Xf_8`RYeAyk|;_Z~OetqfNJUTKewrgXb9`(gzFQXhvcLW0z-9sux(&tJT z5J-F(v6}UU`cW5%xH}2K{)=pm#-9Bygh07^u7AgX6V$i4As%9;hW<6$!{&9ie}Qfo zO?jiry>k)W|9@UVaFGye^n3|wY=t?f8QKw?BvK!R#x#Xs?@O)-d~2wE{hvd(?I%GOw&ybLYQfQJoZcD@qNIT#_3H=F7Z&QSq*M#>=yTf^jluc$Ca zNgcT?wwdzH{d;yDcP;~tc?e(xMc8Hf|9jT)80LgL2@(}m6P@f<^ks)iy-6o^?b2Gp=oyw)kI&L<1l3<-`$ zh_D0vTi2^7)Et3EDXB)eeoMsT#3C=fa@aK%Qzs<$MmTsjEX#7Zf8@Z+ID<%1gb12p z5&dBK1ZEE@T2O2V9t{vpc|>QaV+Fp=gG^n<)P&on>yLPBs)qlwykYQPSUzF#!yyFo zad=c%h8ZBnOR{ccp3Usz5Ri)RnO+RKFTRWu5aRWW7OCQ+Tnt3$gIi~0wBxOh*DKH9 zZW};i!)3f5A>8VB2=PFI6(KAq(9xPO_JxT5TcX9`e4fzK*{(ypoymXHWpofi*W-|n zw;R_koig|-wV-$#MeBVopbi_BCG&H}=<1}#l+`d=3Pi#o)~M@^ zF_nAv;B65MrK)%o6*ydnh&>H$#FSm}#>xn)gT)fNzJq-so;OHT$0Uv{j=@|UVcYqw zua5W~W75PwO_6`Jd+y-XwEj#9N9gAaeiY-u8lx-ZH8S$8T+F%HBj2fLea>un`XWTd zK1Xw=w7MPj`}pFz`JvJj&ilSIx6UT}3^x2+5zaA#kMc%Z!sGB-k;A&n$mliS{k9sPUK9=#zieYu9qznR2Y*nkisatNH^IV^S}HKaHaJMAFt;eib}3jD^2gAgKgayJMu z$Q`Xp)sOQ*FUG9){fGpyPhY1N|NS}`A$)wRtJ0yU5~Y%Aqg5@+Ugy5553KJRde4cY zW`kzkR)w(oW}hv^>H_Kat-+$1WGYAXrMzdx&}Y@yfST$Mp{o|?zDKKF^XO{KT9#Pf zl#iax%2Gl|W!L0arVUwHgNX2JcwHSOh%cMAU- zq0_6*_3^#u#GdJpTM0wQb{Tebnd#CgS#KP|8)SQK);$|evM;<9$@_iVcV;TuYuf)A z*e~;(=zeXsFH$RmR+X;2m$-XYyL$ef=X%eH*$tyoqTQ}B)*7>dWUu!(%fT8eOpq#L z+d1?7v}f%L!LcSlW!_jF2JT5`pu+t+j6;Y7`$7>ygp!l(jLkg5!un2^dwpMz4N!+L zssr{w%`25vy=ozf(gwE^^8_Hbo;x#adle^;FNYm6MUpg;Ah_DAt_Rwm2 zL^0R>*g}xM5h0ZN6oVc$aEQ5R$^;@}eOgnu>M>8x+bQoCL1_N{+nn2O&(|f0zqhe=e_jVmq*|IMJdw3v+Wc^W&+cllX3viA z-O8h@<+@dyqtRcpDeCz6_|$xC%o8CR-Ff>JtSx&ui8sWZa>xbU+^Ope5h9}Fkl77A z7dz)#p0R@z2&9*Vm3@L9_ZK{?{Td!G9Z1{WD8eE7^{RQDs9Yh~88H<))YhFNf6r{- z)xqH%ULhvEkKM!BkpAs@$nO=3;9im~=8$l0(%s@+UYvW4NYa9KA@Hy}3cv z=%Gd4E0E=q#~L)>%PkmJV0U29hcwz0s%f&#=zHr`m;rG(18Y8-~N(r)ZeO|fp zS{a`|1Mtq4l1}2B=e+f!=!KXQ;48Ex$)^} z&!VhqKJ3~BOg?8m$2GNhGgMHRO)C8kv-;srJ=r}KWa~;=)x%;x*cn6`5f6g}foGo# zCJWBgu=83P=*RR&rRyU@h|jM|)1M8J*X)A-YHi%-xQjG0Ikk+lt>>)J%+Em$Th$eX zimdxBh4*Ewbf9_lfj3%`11jS)Rbk$J&g}p~xZiqwwaX#=S{K1Jqi9u1!Byxizw7oK zAEXn7zbKJ`*U$dq?!rx_xQ7rxAL1INGK3H}J8}k^@dJ?c0jiROSnTc_t8d{93(-l-!u;acNvrl#)V_AyGsm4tt6W1*9q8G|e;3Di#?17W zf*HL7p^nyiCObc7QR0f6C`+R6kr@@{ngy%JjjLPG1^3_~mPr#K% zW`TBa&i`9LuXExxorn*$u_k8m=kcfDop3#-ZeO?$z+=JsCx#Ac0U>Zj7)c|eP#&?h zW^>+~bz2e1s$ObdB{N`#NL9fCSpj=1>vrn&Qh!A5E=Ep~Y5aF3wKDmtwRN~BJAvgLGLx#2%}#|$$07PuK(3ANmzGwYWeh{}bJEKA z&K4zVl1N%tTLm*}@2jJ=E4bP(iORAqB8zFP-X4)D%Sc6MSWyoltd&t!rX{PpF+`|V_U zv-jd7ADa$&i9koenIjAZ(>^~tYHOmE%`_E^6Wp^^*MgnNm(jTunRn{#p+aVfbRvAJ z>%rp}8b+p(z*-lrY*J`7puqNijqqA~pn5}|yKC!!O-1F(=13OLFh=V@sMe_sUXoYk z6jB{QGCKvY(S9eeq=SuiZ6(4XUe~WJ#?Z;v6^9TVZ7WeCMS9(Tvpv2)_h;Yf*-hkM zoUYM?NenpD%cFvp(MD{5yaCr2^u3(r2qft9+mTObqX!3$k(tRsmArCF<}H)bA6wS( z@8DDR>mVvj9YW}w7Mb}_B0{i!d|vHZqjkaD4iaKjrK*b%;CI_OsQvLfYZO-K9k5}H zjUIm8-6nz-Zk#$xGq)gKdP7tv&qWBCscAP_0WxK|=vyfYKJMe1kC{gz{ntJfua zB|lU9sF8`4*%7NZ)p=Ov5JKixe}~_asBWY#@EUkBj}z2(C(Nd_II5fxs;_jhQ<<(> z$3gY-U=2bPyK>)_ZB)8MJb)SHP8)WL!!9OB;rqv@&Emc){$EfIK$aKs)Dw;&ZC*EM)hsw~4>k*={ z)?aHel+Dj(C1t;WB}XPP5$7}7HK~FUGgn*Q7Oo+hZX-VLR12SQyaPSYQS>$wgaDR~ z!wLKGgH`0-@vSjM{W)7{i$^k>IXy!)(FP=mSE&2c=@u(#v_3>?)gh1PKo&;>A$W96 zv|_uJvx`jgIc&(Q=*?h8hhulgAWo z>lxg(r^#`yDsN+@lK3lP&1+RTcE^>>-8adOJnEV&+jcftc8cuRaSjeuv~vV1_QJls z(|NzHdQtF+W)wBlMmw=gv(^)54-vWJlL`otSfx1nkHq(}Gvy#`kjXQpJC0&+sv)*VI6}NA3qHhjoj?Zcd4>(PoH^obX8{QFs3pep=qTG zoC(5)WaJ5X2?v7?d42^=L#*g)wSYx0b3d_rbzjpIjFURg_6q`2F+Y&I5)6)ggEojdT7KcjHR zD8jBfglN}V?Q_~b%Vlh1!k*3%E458Zts<$ZB~=zOsba827j7w!J2XmIg)ZhNJw50@ zGdooptXPqZSHC@%afe^Xc$aoLKhDomAHBDr3q*~0%A^JOXr`n6m zGGXoNXcOon^)=U58@dJ&`$A-2M5?s4VES*DI)6Nmgcxll7JEYOtTt-Sh43(-*Q$2y zLvU50B8)03Ghi#Th<9DdaBVtXtrA}sRk%ro2Jf{ZM~(jq`Yg=#$GcRhUhz4K`?QA& zdDk78v?KDBy^*Oju6cvSu`&SBes0-R{`FKu(*~s>$K&vKV>X4OHlCBB%z1uhjxl59 zC5$nW%rLY%g|E@P4qsN0i&ypIc#vLI{_jdK2Cq+Lc%Sv$YBJK&j>y;UHcfMzKc$#A z{MU=zb3H<7kcNfTw);^1Z|UNwXuiG)Z>ZKi!-*Ex8+LvEi~aqWtTu9fT3<`kif$9A z;CM_2^8gQ<(48g}0W_ObXGiShK2MG|iB)}Vf4Yv7@aoaDZi_Z^DVz6Xf+a{@|EjRy zW;l!XcZylROK#D?PnGV(h&>Jav}Wj{Su9<*Zgig?q!>J5{q=WGZav`HRC_mAuL=>C z3E7S6>0?#L+tOz$Qyk(DjX~P}$&FY`I%_1h Date: Sun, 28 Nov 2021 13:58:41 +0000 Subject: [PATCH 1033/1062] Add spacing to layout, small css tweaks --- css/main.css | 43 ++++++++++++++++++++++++++++++++++++++++++- index.html | 7 ++++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/css/main.css b/css/main.css index a27498397..a25fce801 100644 --- a/css/main.css +++ b/css/main.css @@ -1,8 +1,49 @@ -.navbar { background-color: #5755d9; padding: 0.5em 1em 0.5em 1em; } +.navbar { background-color: #5755d9; padding: 1em 1em 1em 1em; } .navbar .navbar-brand { color: #fff; font-weight: bold; } + +.container.apploader-tab, ul.tab.tab-block { + padding-left: 1rem; + padding-right: 1rem; + border-bottom: 0px; +} + +.panel-body.columns { + margin: 1px; +} + + .tile.column.col-6.col-sm-12.col-xs-12.app-tile { + border: solid 1px #fafafa; + + margin: 0; + min-height: 150px; + padding-top: 10px; +} + +.tab.tab-block .tab-item { + border-bottom: solid 1px #dadee4; +} + +a.mr-2{ + display: flex; + align-items: center; +} + +.navbar-section > a > div { + margin-left: 12px; +} + +.dropdown-container { + margin-bottom: 0.5rem; + margin-top: 0.5rem; +} + +a.btn.btn-link.dropdown-toggle { + padding-left: 0.01em; +} + .avatar img { border-radius: 5px 5px 5px 5px; background: #fff; diff --git a/index.html b/index.html index e7c7c31cd..e22a1f9e7 100644 --- a/index.html +++ b/index.html @@ -21,8 +21,9 @@

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 yn#t+Mm6Oq Date: Wed, 15 Sep 2021 19:53:35 -0400 Subject: [PATCH 0020/1062] Optimization --- modules/Layout.js | 18 ++++++++---------- modules/Layout.min.js | 18 ++++++++---------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/modules/Layout.js b/modules/Layout.js index e0d15f8e5..ee5985da9 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -248,13 +248,11 @@ function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) { // Hash the layoutObject without including its children let c = l.c; delete l.c; - let hash = E.CRC32(E.toJS(l)); + let hash = "H"+E.CRC32(E.toJS(l)); // String keys maintain insertion order if (c) l.c = c; - let i = rectsToClear.findIndex(r => r.h == hash); - if (i != -1) rectsToClear.splice(i, 1); - else { - rects.push({h: hash, bg: bgCol, x1: l.x, y1: l.y, x2: l.x+l.w-1, y2: l.y+l.h-1}); + if (!delete rectsToClear[hash]) { + rects[hash] = {bg: bgCol, r: [l.x,l.y,l.x+l.w-1,l.y+l.h-1]}; if (drawList) { drawList.push(l); drawList = null; // Prevent children from being redundantly added to the drawList @@ -269,13 +267,13 @@ Layout.prototype.render = function (l) { if (!l) l = this._l; if (this.lazy) { - if (!this.rects) this.rects = []; - let rectsToClear = this.rects.slice(); + if (!this.rects) this.rects = {}; + let rectsToClear = this.rects.clone(); let drawList = []; prepareLazyRender(l, rectsToClear, drawList, this.rects, g.getBgColor()); - this.rects = this.rects.filter(r1 => !rectsToClear.some(r2 => r1.h == r2.h)); - rectsToClear.reverse(); // Rects are cleared in reverse order so that the original bg color is restored - for (let r of rectsToClear) g.setBgColor(r.bg).clearRect(r.x1, r.y1, r.x2, r.y2); + for (let h in rectsToClear) delete this.rects[h]; + let clearList = Object.keys(rectsToClear).map(k=>rectsToClear[k]).reverse(); // Rects are cleared in reverse order so that the original bg color is restored + for (let r of clearList) g.setBgColor(r.bg).clearRect.apply(g, r.r); drawList.forEach(render); } else { diff --git a/modules/Layout.min.js b/modules/Layout.min.js index e0d15f8e5..ee5985da9 100644 --- a/modules/Layout.min.js +++ b/modules/Layout.min.js @@ -248,13 +248,11 @@ function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) { // Hash the layoutObject without including its children let c = l.c; delete l.c; - let hash = E.CRC32(E.toJS(l)); + let hash = "H"+E.CRC32(E.toJS(l)); // String keys maintain insertion order if (c) l.c = c; - let i = rectsToClear.findIndex(r => r.h == hash); - if (i != -1) rectsToClear.splice(i, 1); - else { - rects.push({h: hash, bg: bgCol, x1: l.x, y1: l.y, x2: l.x+l.w-1, y2: l.y+l.h-1}); + if (!delete rectsToClear[hash]) { + rects[hash] = {bg: bgCol, r: [l.x,l.y,l.x+l.w-1,l.y+l.h-1]}; if (drawList) { drawList.push(l); drawList = null; // Prevent children from being redundantly added to the drawList @@ -269,13 +267,13 @@ Layout.prototype.render = function (l) { if (!l) l = this._l; if (this.lazy) { - if (!this.rects) this.rects = []; - let rectsToClear = this.rects.slice(); + if (!this.rects) this.rects = {}; + let rectsToClear = this.rects.clone(); let drawList = []; prepareLazyRender(l, rectsToClear, drawList, this.rects, g.getBgColor()); - this.rects = this.rects.filter(r1 => !rectsToClear.some(r2 => r1.h == r2.h)); - rectsToClear.reverse(); // Rects are cleared in reverse order so that the original bg color is restored - for (let r of rectsToClear) g.setBgColor(r.bg).clearRect(r.x1, r.y1, r.x2, r.y2); + for (let h in rectsToClear) delete this.rects[h]; + let clearList = Object.keys(rectsToClear).map(k=>rectsToClear[k]).reverse(); // Rects are cleared in reverse order so that the original bg color is restored + for (let r of clearList) g.setBgColor(r.bg).clearRect.apply(g, r.r); drawList.forEach(render); } else { From 76b6ca0bff96f5fe73fdeb815d55e4f70b593922 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 16 Sep 2021 10:17:27 +0100 Subject: [PATCH 0021/1062] allow numeric fill values --- modules/Layout.js | 14 ++++++++------ modules/Layout.min.js | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/Layout.js b/modules/Layout.js index ee5985da9..f17e35126 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -41,8 +41,8 @@ layoutObject has: * A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center * A `valign` field to set vertical alignment. `-1`=top, `1`=bottom, `0`=center * A `pad` integer field to set pixels padding -* A `fillx` boolean to choose if the object should fill available space in x -* A `filly` boolean to choose if the object should fill available space in y +* A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space +* A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space * `width` and `height` fields to optionally specify minimum size btns is an array of objects containing: @@ -192,14 +192,16 @@ function updateMin(l) { l.c.forEach(updateMin); l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0); l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0); - l.fillx |= l.c.some(c=>c.fillx); + if (l.c.some(c=>c.fillx)) l.fillx = 1; + if (l.c.some(c=>c.filly)) l.filly = 1; break; } case "v": { l.c.forEach(updateMin); l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0); l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0); - l.filly |= l.c.some(c=>c.filly); + if (l.c.some(c=>c.fillx)) l.fillx = 1; + if (l.c.some(c=>c.filly)) l.filly = 1; break; } default: throw "Unknown item type "+l.type; @@ -291,7 +293,7 @@ Layout.prototype.layout = function (l) { let x = l.x + (l.w-l._w)/2; if (fillx) { x = l.x; } l.c.forEach(c => { - c.w = c._w + (c.fillx?(l.w-l._w)/fillx:0); + c.w = c._w + ((0|c.fillx)*(l.w-l._w)/(fillx||1)); c.h = c.filly ? l.h : c._h; c.x = x; c.y = l.y + (1+(0|c.valign))*(l.h-c.h)/2; @@ -312,7 +314,7 @@ Layout.prototype.layout = function (l) { if (filly) { y = l.y; } l.c.forEach(c => { c.w = c.fillx ? l.w : c._w; - c.h = c._h + (c.filly?(l.h-l._h)/filly:0); + c.h = c._h + ((0|c.filly)*(l.h-l._h)/(filly||1)); c.x = l.x + (1+(0|c.halign))*(l.w-c.w)/2; c.y = y; y += c.h; diff --git a/modules/Layout.min.js b/modules/Layout.min.js index ee5985da9..f17e35126 100644 --- a/modules/Layout.min.js +++ b/modules/Layout.min.js @@ -41,8 +41,8 @@ layoutObject has: * A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center * A `valign` field to set vertical alignment. `-1`=top, `1`=bottom, `0`=center * A `pad` integer field to set pixels padding -* A `fillx` boolean to choose if the object should fill available space in x -* A `filly` boolean to choose if the object should fill available space in y +* A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space +* A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space * `width` and `height` fields to optionally specify minimum size btns is an array of objects containing: @@ -192,14 +192,16 @@ function updateMin(l) { l.c.forEach(updateMin); l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0); l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0); - l.fillx |= l.c.some(c=>c.fillx); + if (l.c.some(c=>c.fillx)) l.fillx = 1; + if (l.c.some(c=>c.filly)) l.filly = 1; break; } case "v": { l.c.forEach(updateMin); l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0); l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0); - l.filly |= l.c.some(c=>c.filly); + if (l.c.some(c=>c.fillx)) l.fillx = 1; + if (l.c.some(c=>c.filly)) l.filly = 1; break; } default: throw "Unknown item type "+l.type; @@ -291,7 +293,7 @@ Layout.prototype.layout = function (l) { let x = l.x + (l.w-l._w)/2; if (fillx) { x = l.x; } l.c.forEach(c => { - c.w = c._w + (c.fillx?(l.w-l._w)/fillx:0); + c.w = c._w + ((0|c.fillx)*(l.w-l._w)/(fillx||1)); c.h = c.filly ? l.h : c._h; c.x = x; c.y = l.y + (1+(0|c.valign))*(l.h-c.h)/2; @@ -312,7 +314,7 @@ Layout.prototype.layout = function (l) { if (filly) { y = l.y; } l.c.forEach(c => { c.w = c.fillx ? l.w : c._w; - c.h = c._h + (c.filly?(l.h-l._h)/filly:0); + c.h = c._h + ((0|c.filly)*(l.h-l._h)/(filly||1)); c.x = l.x + (1+(0|c.halign))*(l.w-c.w)/2; c.y = y; y += c.h; From 7a9aad93e4cd8301c1bbe48cad15eb139609c47e Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 16 Sep 2021 10:49:14 +0100 Subject: [PATCH 0022/1062] Switch to object lookup for update & render code - around 20% faster --- modules/Layout.js | 184 ++++++++++++++++++++---------------------- modules/Layout.min.js | 184 ++++++++++++++++++++---------------------- 2 files changed, 174 insertions(+), 194 deletions(-) diff --git a/modules/Layout.js b/modules/Layout.js index f17e35126..118703f51 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -85,10 +85,10 @@ function Layout(layout, buttons, options) { options = options || {}; this.lazy = options.lazy || false; - if (buttons) { + if (buttons) { if (this.physBtns >= buttons.length) { // enough physical buttons - var btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns); + let btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns); if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch); Bangle.btnWatch = []; if (this.physBtns > 2 && buttons.length==1) @@ -104,7 +104,7 @@ function Layout(layout, buttons, options) { {type:"v", pad:1, filly:1, c: buttons.map(b=>(b.type="txt",b.font="6x8",b.height=btnHeight,b.r=1,b))} ]}; } else { - var btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length); + let btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length); this._l.width = g.getWidth()-20; // button width this._l = {type:"h", c: [ this._l, @@ -153,98 +153,6 @@ function touchHandler(l,e) { if (l.c) l.c.forEach(n => touchHandler(n,e)); } - -function updateMin(l) { - switch (l.type) { - case "txt": { - if (l.font.endsWith("%")) - l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); - // FIXME ':'/fsz not needed in new firmwares - it's handled internally - if (l.font.includes(":")) { - var f = l.font.split(":"); - l.font = f[0]; - l.fsz = f[1]; - } - g.setFont(l.font,l.fsz); - l._h = g.getFontHeight(); - l._w = g.stringWidth(l.label); - break; - } - case "btn": { - l._h = 24; - l._w = 14 + l.label.length*8; - break; - } - case "img": { - var im = E.toString(l.src()); - l._h = im.charCodeAt(0); - l._w = im.charCodeAt(1); - break; - } - case undefined: - case "custom": { - // size should already be set up in width/height - l._w = 0; - l._h = 0; - break; - } - case "h": { - l.c.forEach(updateMin); - l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0); - l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0); - if (l.c.some(c=>c.fillx)) l.fillx = 1; - if (l.c.some(c=>c.filly)) l.filly = 1; - break; - } - case "v": { - l.c.forEach(updateMin); - l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0); - l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0); - if (l.c.some(c=>c.fillx)) l.fillx = 1; - if (l.c.some(c=>c.filly)) l.filly = 1; - break; - } - default: throw "Unknown item type "+l.type; - } - if (l.r&1) { // rotation - var t = l._w;l._w=l._h;l._h=t; - } - l._w = Math.max(l._w, 0|l.width); - l._h = Math.max(l._h, 0|l.height); -} -function render(l) { - if (!l) l = this.l; - g.reset(); - if (l.col) g.setColor(l.col); - if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); - switch (l.type) { - case "txt": - g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1), true/*solid bg*/); - break; - case "btn": - var poly = [ - l.x,l.y+4, - l.x+4,l.y, - l.x+l.w-5,l.y, - l.x+l.w-1,l.y+4, - l.x+l.w-1,l.y+l.h-5, - l.x+l.w-5,l.y+l.h-1, - l.x+4,l.y+l.h-1, - l.x,l.y+l.h-5, - l.x,l.y+4 - ]; - g.setColor(g.theme.bgH).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg).drawPoly(poly).setFont("4x6",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); - break; - case "img": - g.drawImage(l.src(), l.x, l.y); - break; - case "custom": - l.render(l); - break; - } - if (l.c) l.c.forEach(render); -} - function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) { if ((l.bgCol != null && l.bgCol != bgCol) || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { // Hash the layoutObject without including its children @@ -267,6 +175,38 @@ function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) { Layout.prototype.render = function (l) { if (!l) l = this._l; + + function render(l) {"ram" + g.reset(); + if (l.col) g.setColor(l.col); + if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); + cb[l.type](l); + } + + var cb = { + "undefined":function(){}, + "txt":function(l){ + g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1), true/*solid bg*/); + }, "btn":function(l){ + var poly = [ + l.x,l.y+4, + l.x+4,l.y, + l.x+l.w-5,l.y, + l.x+l.w-1,l.y+4, + l.x+l.w-1,l.y+l.h-5, + l.x+l.w-5,l.y+l.h-1, + l.x+4,l.y+l.h-1, + l.x,l.y+l.h-5, + l.x,l.y+4 + ]; + g.setColor(g.theme.bgH).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg).drawPoly(poly).setFont("4x6",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); + }, "img":function(l){ + g.drawImage(l.src(), l.x, l.y); + }, "custom":function(l){ + l.render(l); + },"h":function(l) { l.c.forEach(render); }, + "v":function(l) { l.c.forEach(render); } + }; if (this.lazy) { if (!this.rects) this.rects = {}; @@ -286,11 +226,10 @@ Layout.prototype.render = function (l) { Layout.prototype.layout = function (l) { // l = current layout element // exw,exh = extra width/height available - var fillx = l.c && l.c.reduce((a,l)=>a+(0|l.fillx),0); - var filly = l.c && l.c.reduce((a,l)=>a+(0|l.filly),0); switch (l.type) { case "h": { let x = l.x + (l.w-l._w)/2; + var fillx = l.c && l.c.reduce((a,l)=>a+(0|l.fillx),0); if (fillx) { x = l.x; } l.c.forEach(c => { c.w = c._w + ((0|c.fillx)*(l.w-l._w)/(fillx||1)); @@ -311,6 +250,7 @@ Layout.prototype.layout = function (l) { } case "v": { let y = l.y + (l.h-l._h)/2; + var filly = l.c && l.c.reduce((a,l)=>a+(0|l.filly),0); if (filly) { y = l.y; } l.c.forEach(c => { c.w = c.fillx ? l.w : c._w; @@ -342,6 +282,56 @@ Layout.prototype.update = function() { var y = this.yOffset; var h = g.getHeight()-y; // update sizes + function updateMin(l) {"ram" + cb[l.type](l); + if (l.r&1) { // rotation + var t = l._w;l._w=l._h;l._h=t; + } + l._w = Math.max(l._w, 0|l.width); + l._h = Math.max(l._h, 0|l.height); + } + var cb = { + "txt" : function(l) { + if (l.font.endsWith("%")) + l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); + // FIXME ':'/fsz not needed in new firmwares - it's handled internally + if (l.font.includes(":")) { + var f = l.font.split(":"); + l.font = f[0]; + l.fsz = f[1]; + } + g.setFont(l.font,l.fsz); + l._h = g.getFontHeight(); + l._w = g.stringWidth(l.label); + }, "btn": function(l) { + l._h = 24; + l._w = 14 + l.label.length*8; + }, "img": function(l) { + var im = E.toString(l.src()); + l._h = im.charCodeAt(0); + l._w = im.charCodeAt(1); + }, "undefined": function(l) { + // size should already be set up in width/height + l._w = 0; + l._h = 0; + }, "custom": function(l) { + // size should already be set up in width/height + l._w = 0; + l._h = 0; + }, "h": function(l) { + l.c.forEach(updateMin); + l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0); + l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0); + if (l.c.some(c=>c.fillx)) l.fillx = 1; + if (l.c.some(c=>c.filly)) l.filly = 1; + }, "v": function(l) { + l.c.forEach(updateMin); + l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0); + l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0); + if (l.c.some(c=>c.fillx)) l.fillx = 1; + if (l.c.some(c=>c.filly)) l.filly = 1; + } + }; updateMin(l); // center if (l.fillx || l.filly) { diff --git a/modules/Layout.min.js b/modules/Layout.min.js index f17e35126..118703f51 100644 --- a/modules/Layout.min.js +++ b/modules/Layout.min.js @@ -85,10 +85,10 @@ function Layout(layout, buttons, options) { options = options || {}; this.lazy = options.lazy || false; - if (buttons) { + if (buttons) { if (this.physBtns >= buttons.length) { // enough physical buttons - var btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns); + let btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns); if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch); Bangle.btnWatch = []; if (this.physBtns > 2 && buttons.length==1) @@ -104,7 +104,7 @@ function Layout(layout, buttons, options) { {type:"v", pad:1, filly:1, c: buttons.map(b=>(b.type="txt",b.font="6x8",b.height=btnHeight,b.r=1,b))} ]}; } else { - var btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length); + let btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length); this._l.width = g.getWidth()-20; // button width this._l = {type:"h", c: [ this._l, @@ -153,98 +153,6 @@ function touchHandler(l,e) { if (l.c) l.c.forEach(n => touchHandler(n,e)); } - -function updateMin(l) { - switch (l.type) { - case "txt": { - if (l.font.endsWith("%")) - l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); - // FIXME ':'/fsz not needed in new firmwares - it's handled internally - if (l.font.includes(":")) { - var f = l.font.split(":"); - l.font = f[0]; - l.fsz = f[1]; - } - g.setFont(l.font,l.fsz); - l._h = g.getFontHeight(); - l._w = g.stringWidth(l.label); - break; - } - case "btn": { - l._h = 24; - l._w = 14 + l.label.length*8; - break; - } - case "img": { - var im = E.toString(l.src()); - l._h = im.charCodeAt(0); - l._w = im.charCodeAt(1); - break; - } - case undefined: - case "custom": { - // size should already be set up in width/height - l._w = 0; - l._h = 0; - break; - } - case "h": { - l.c.forEach(updateMin); - l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0); - l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0); - if (l.c.some(c=>c.fillx)) l.fillx = 1; - if (l.c.some(c=>c.filly)) l.filly = 1; - break; - } - case "v": { - l.c.forEach(updateMin); - l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0); - l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0); - if (l.c.some(c=>c.fillx)) l.fillx = 1; - if (l.c.some(c=>c.filly)) l.filly = 1; - break; - } - default: throw "Unknown item type "+l.type; - } - if (l.r&1) { // rotation - var t = l._w;l._w=l._h;l._h=t; - } - l._w = Math.max(l._w, 0|l.width); - l._h = Math.max(l._h, 0|l.height); -} -function render(l) { - if (!l) l = this.l; - g.reset(); - if (l.col) g.setColor(l.col); - if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); - switch (l.type) { - case "txt": - g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1), true/*solid bg*/); - break; - case "btn": - var poly = [ - l.x,l.y+4, - l.x+4,l.y, - l.x+l.w-5,l.y, - l.x+l.w-1,l.y+4, - l.x+l.w-1,l.y+l.h-5, - l.x+l.w-5,l.y+l.h-1, - l.x+4,l.y+l.h-1, - l.x,l.y+l.h-5, - l.x,l.y+4 - ]; - g.setColor(g.theme.bgH).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg).drawPoly(poly).setFont("4x6",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); - break; - case "img": - g.drawImage(l.src(), l.x, l.y); - break; - case "custom": - l.render(l); - break; - } - if (l.c) l.c.forEach(render); -} - function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) { if ((l.bgCol != null && l.bgCol != bgCol) || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { // Hash the layoutObject without including its children @@ -267,6 +175,38 @@ function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) { Layout.prototype.render = function (l) { if (!l) l = this._l; + + function render(l) {"ram" + g.reset(); + if (l.col) g.setColor(l.col); + if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); + cb[l.type](l); + } + + var cb = { + "undefined":function(){}, + "txt":function(l){ + g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1), true/*solid bg*/); + }, "btn":function(l){ + var poly = [ + l.x,l.y+4, + l.x+4,l.y, + l.x+l.w-5,l.y, + l.x+l.w-1,l.y+4, + l.x+l.w-1,l.y+l.h-5, + l.x+l.w-5,l.y+l.h-1, + l.x+4,l.y+l.h-1, + l.x,l.y+l.h-5, + l.x,l.y+4 + ]; + g.setColor(g.theme.bgH).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg).drawPoly(poly).setFont("4x6",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); + }, "img":function(l){ + g.drawImage(l.src(), l.x, l.y); + }, "custom":function(l){ + l.render(l); + },"h":function(l) { l.c.forEach(render); }, + "v":function(l) { l.c.forEach(render); } + }; if (this.lazy) { if (!this.rects) this.rects = {}; @@ -286,11 +226,10 @@ Layout.prototype.render = function (l) { Layout.prototype.layout = function (l) { // l = current layout element // exw,exh = extra width/height available - var fillx = l.c && l.c.reduce((a,l)=>a+(0|l.fillx),0); - var filly = l.c && l.c.reduce((a,l)=>a+(0|l.filly),0); switch (l.type) { case "h": { let x = l.x + (l.w-l._w)/2; + var fillx = l.c && l.c.reduce((a,l)=>a+(0|l.fillx),0); if (fillx) { x = l.x; } l.c.forEach(c => { c.w = c._w + ((0|c.fillx)*(l.w-l._w)/(fillx||1)); @@ -311,6 +250,7 @@ Layout.prototype.layout = function (l) { } case "v": { let y = l.y + (l.h-l._h)/2; + var filly = l.c && l.c.reduce((a,l)=>a+(0|l.filly),0); if (filly) { y = l.y; } l.c.forEach(c => { c.w = c.fillx ? l.w : c._w; @@ -342,6 +282,56 @@ Layout.prototype.update = function() { var y = this.yOffset; var h = g.getHeight()-y; // update sizes + function updateMin(l) {"ram" + cb[l.type](l); + if (l.r&1) { // rotation + var t = l._w;l._w=l._h;l._h=t; + } + l._w = Math.max(l._w, 0|l.width); + l._h = Math.max(l._h, 0|l.height); + } + var cb = { + "txt" : function(l) { + if (l.font.endsWith("%")) + l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); + // FIXME ':'/fsz not needed in new firmwares - it's handled internally + if (l.font.includes(":")) { + var f = l.font.split(":"); + l.font = f[0]; + l.fsz = f[1]; + } + g.setFont(l.font,l.fsz); + l._h = g.getFontHeight(); + l._w = g.stringWidth(l.label); + }, "btn": function(l) { + l._h = 24; + l._w = 14 + l.label.length*8; + }, "img": function(l) { + var im = E.toString(l.src()); + l._h = im.charCodeAt(0); + l._w = im.charCodeAt(1); + }, "undefined": function(l) { + // size should already be set up in width/height + l._w = 0; + l._h = 0; + }, "custom": function(l) { + // size should already be set up in width/height + l._w = 0; + l._h = 0; + }, "h": function(l) { + l.c.forEach(updateMin); + l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0); + l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0); + if (l.c.some(c=>c.fillx)) l.fillx = 1; + if (l.c.some(c=>c.filly)) l.filly = 1; + }, "v": function(l) { + l.c.forEach(updateMin); + l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0); + l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0); + if (l.c.some(c=>c.fillx)) l.fillx = 1; + if (l.c.some(c=>c.filly)) l.filly = 1; + } + }; updateMin(l); // center if (l.fillx || l.filly) { From f72a9d29f8bf6f372956c05e708fdca972f68da9 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 16 Sep 2021 10:49:29 +0100 Subject: [PATCH 0023/1062] recommend firmware 2v10 --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index e0e1399b1..6528ffc98 100644 --- a/loader.js +++ b/loader.js @@ -11,7 +11,7 @@ if (window.location.host=="banglejs.com") { 'This is not the official Bangle.js App Loader - you can try the Official Version here.'; } -var RECOMMENDED_VERSION = "2v09"; +var RECOMMENDED_VERSION = "2v10"; // could check http://www.espruino.com/json/BANGLEJS.json for this (function() { From e280d192eedf8990839ceaa431a8c7db92b218fc Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 16 Sep 2021 11:27:52 +0100 Subject: [PATCH 0024/1062] More speed improvements, and fix issue with minifier so we don't need .min.js --- modules/Layout.js | 27 ++-- modules/Layout.min.js | 359 ------------------------------------------ 2 files changed, 14 insertions(+), 372 deletions(-) delete mode 100644 modules/Layout.min.js diff --git a/modules/Layout.js b/modules/Layout.js index 118703f51..fa53477fe 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -113,7 +113,7 @@ function Layout(layout, buttons, options) { } } if (process.env.HWVERSION==2) { - Bangle.touchHandler = (_,e) => touchHandler(layout,e); + Bangle.touchHandler = function(_,e){touchHandler(layout,e)}; Bangle.on('touch',Bangle.touchHandler); } @@ -121,6 +121,7 @@ function Layout(layout, buttons, options) { var ll = this; function idRecurser(l) { if (l.id) ll[l.id] = l; + if (!l.type) l.type=""; if (l.c) l.c.forEach(idRecurser); } idRecurser(layout); @@ -184,7 +185,7 @@ Layout.prototype.render = function (l) { } var cb = { - "undefined":function(){}, + "":function(){}, "txt":function(l){ g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1), true/*solid bg*/); }, "btn":function(l){ @@ -209,16 +210,18 @@ Layout.prototype.render = function (l) { }; if (this.lazy) { + // we have to use 'var' here not 'let', otherwise the minifier + // renames vars to the same name, which causes problems as Espruino + // doesn't yet honour the scoping of 'let' if (!this.rects) this.rects = {}; - let rectsToClear = this.rects.clone(); - let drawList = []; + var rectsToClear = this.rects.clone(); + var drawList = []; prepareLazyRender(l, rectsToClear, drawList, this.rects, g.getBgColor()); - for (let h in rectsToClear) delete this.rects[h]; - let clearList = Object.keys(rectsToClear).map(k=>rectsToClear[k]).reverse(); // Rects are cleared in reverse order so that the original bg color is restored - for (let r of clearList) g.setBgColor(r.bg).clearRect.apply(g, r.r); + for (var h in rectsToClear) delete this.rects[h]; + var clearList = Object.keys(rectsToClear).map(k=>rectsToClear[k]).reverse(); // Rects are cleared in reverse order so that the original bg color is restored + for (var r of clearList) g.setBgColor(r.bg).clearRect.apply(g, r.r); drawList.forEach(render); - } - else { + } else { // non-lazy render(l); } }; @@ -242,9 +245,7 @@ Layout.prototype.layout = function (l) { c.w += c.pad*2; c.h += c.pad*2; } - if (c.c) { - this.layout(c); - } + if (c.c) this.layout(c); }); break; } @@ -310,7 +311,7 @@ Layout.prototype.update = function() { var im = E.toString(l.src()); l._h = im.charCodeAt(0); l._w = im.charCodeAt(1); - }, "undefined": function(l) { + }, "": function(l) { // size should already be set up in width/height l._w = 0; l._h = 0; diff --git a/modules/Layout.min.js b/modules/Layout.min.js deleted file mode 100644 index 118703f51..000000000 --- a/modules/Layout.min.js +++ /dev/null @@ -1,359 +0,0 @@ -/* - -Usage: - -``` -var Layout = require("Layout"); -var layout = new Layout( layoutObject, btns, options ) -layout.render(optionalObject); -``` - -For example: - -``` -var Layout = require("Layout"); -var layout = new Layout( { - type:"v", c: [ - {type:"txt", font:"20%", label:"12:00" }, - {type:"txt", font:"6x8", label:"The Date" } - ] -}); -g.clear(); -layout.render(); -``` - - -layoutObject has: - -* A `type` field of: - * `undefined` - blank, can be used for padding - * `"txt"` - a text label, with value `label` and `r` for text rotation. 'font' is required - * `"btn"` - a button, with value `label` and callback `cb` - * `"img"` - an image where the function `src` is called to return an image to draw - * `"custom"` - a custom block where `render(layoutObj)` is called to render - * `"h"` - Horizontal layout, `c` is an array of more `layoutObject` - * `"v"` - Veritical layout, `c` is an array of more `layoutObject` -* A `id` field. If specified the object is added with this name to the - returned `layout` object, so can be referenced as `layout.foo` -* A `font` field, eg `6x8` or `30%` to use a percentage of screen height -* A `col` field, eg `#f00` for red -* A `bgCol` field for background color (will automatically fill on render) -* A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center -* A `valign` field to set vertical alignment. `-1`=top, `1`=bottom, `0`=center -* A `pad` integer field to set pixels padding -* A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space -* A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space -* `width` and `height` fields to optionally specify minimum size - -btns is an array of objects containing: - -* `label` - the text on the button -* `cb` - a callback function -* `cbl` - a callback function for long presses - -options is an object containing: - -* `lazy` - a boolean specifying whether to enable automatic lazy rendering - -If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically -determine what objects have changed or moved, clear their previous locations, and re-render just those objects. - -Once `layout.update()` is called, the following fields are added -to each object: - -* `x` and `y` for the top left position -* `w` and `h` for the width and height -* `_w` and `_h` for the **minimum** width and height - - -Other functions: - -* `layout.update()` - update positions of everything if contents have changed -* `layout.debug(obj)` - draw outlines for objects on screen -* `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render) - -*/ - - -function Layout(layout, buttons, options) { - this._l = this.l = layout; - this.b = buttons; - // Do we have >1 physical buttons? - this.physBtns = (process.env.HWVERSION==2) ? 1 : 3; - this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0; - - options = options || {}; - this.lazy = options.lazy || false; - - if (buttons) { - if (this.physBtns >= buttons.length) { - // enough physical buttons - let btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns); - if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch); - Bangle.btnWatch = []; - if (this.physBtns > 2 && buttons.length==1) - buttons.unshift({label:""}); // pad so if we have a button in the middle - while (this.physBtns > buttons.length) - buttons.push({label:""}); - if (buttons[0]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1})); - if (buttons[1]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1})); - if (buttons[2]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1})); - this._l.width = g.getWidth()-8; // text width - this._l = {type:"h", filly:1, c: [ - this._l, - {type:"v", pad:1, filly:1, c: buttons.map(b=>(b.type="txt",b.font="6x8",b.height=btnHeight,b.r=1,b))} - ]}; - } else { - let btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length); - this._l.width = g.getWidth()-20; // button width - this._l = {type:"h", c: [ - this._l, - {type:"v", c: buttons.map(b=>(b.type="btn",b.h=btnHeight,b.w=32,b.r=1,b))} - ]}; - } - } - if (process.env.HWVERSION==2) { - Bangle.touchHandler = (_,e) => touchHandler(layout,e); - Bangle.on('touch',Bangle.touchHandler); - } - - // add IDs - var ll = this; - function idRecurser(l) { - if (l.id) ll[l.id] = l; - if (l.c) l.c.forEach(idRecurser); - } - idRecurser(layout); - this.update(); -} - -Layout.prototype.remove = function (l) { - if (Bangle.btnWatch) { - Bangle.btnWatch.forEach(clearWatch); - delete Bangle.btnWatch; - } - if (Bangle.touchHandler) { - Bangle.removeListener("touch",Bangle.touchHandler); - delete Bangle.touchHandler; - } -}; - -// Handler for button watch events -function pressHandler(btn,e) { - if (e.time-e.lastTime > 0.75 && this.b[btn].cbl) - this.b[btn].cbl(e); - else - if (this.b[btn].cb) this.b[btn].cb(e); -} - -// Handler for touch events -function touchHandler(l,e) { - if (l.type=="btn" && l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h) - l.cb(e); - if (l.c) l.c.forEach(n => touchHandler(n,e)); -} - -function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) { - if ((l.bgCol != null && l.bgCol != bgCol) || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { - // Hash the layoutObject without including its children - let c = l.c; - delete l.c; - let hash = "H"+E.CRC32(E.toJS(l)); // String keys maintain insertion order - if (c) l.c = c; - - if (!delete rectsToClear[hash]) { - rects[hash] = {bg: bgCol, r: [l.x,l.y,l.x+l.w-1,l.y+l.h-1]}; - if (drawList) { - drawList.push(l); - drawList = null; // Prevent children from being redundantly added to the drawList - } - } - } - - if (l.c) for (let ch of l.c) prepareLazyRender(ch, rectsToClear, drawList, rects, l.bgCol == null ? bgCol : l.bgCol); -} - -Layout.prototype.render = function (l) { - if (!l) l = this._l; - - function render(l) {"ram" - g.reset(); - if (l.col) g.setColor(l.col); - if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); - cb[l.type](l); - } - - var cb = { - "undefined":function(){}, - "txt":function(l){ - g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1), true/*solid bg*/); - }, "btn":function(l){ - var poly = [ - l.x,l.y+4, - l.x+4,l.y, - l.x+l.w-5,l.y, - l.x+l.w-1,l.y+4, - l.x+l.w-1,l.y+l.h-5, - l.x+l.w-5,l.y+l.h-1, - l.x+4,l.y+l.h-1, - l.x,l.y+l.h-5, - l.x,l.y+4 - ]; - g.setColor(g.theme.bgH).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg).drawPoly(poly).setFont("4x6",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2); - }, "img":function(l){ - g.drawImage(l.src(), l.x, l.y); - }, "custom":function(l){ - l.render(l); - },"h":function(l) { l.c.forEach(render); }, - "v":function(l) { l.c.forEach(render); } - }; - - if (this.lazy) { - if (!this.rects) this.rects = {}; - let rectsToClear = this.rects.clone(); - let drawList = []; - prepareLazyRender(l, rectsToClear, drawList, this.rects, g.getBgColor()); - for (let h in rectsToClear) delete this.rects[h]; - let clearList = Object.keys(rectsToClear).map(k=>rectsToClear[k]).reverse(); // Rects are cleared in reverse order so that the original bg color is restored - for (let r of clearList) g.setBgColor(r.bg).clearRect.apply(g, r.r); - drawList.forEach(render); - } - else { - render(l); - } -}; - -Layout.prototype.layout = function (l) { - // l = current layout element - // exw,exh = extra width/height available - switch (l.type) { - case "h": { - let x = l.x + (l.w-l._w)/2; - var fillx = l.c && l.c.reduce((a,l)=>a+(0|l.fillx),0); - if (fillx) { x = l.x; } - l.c.forEach(c => { - c.w = c._w + ((0|c.fillx)*(l.w-l._w)/(fillx||1)); - c.h = c.filly ? l.h : c._h; - c.x = x; - c.y = l.y + (1+(0|c.valign))*(l.h-c.h)/2; - x += c.w; - if (c.pad) { - x += c.pad*2; - c.w += c.pad*2; - c.h += c.pad*2; - } - if (c.c) { - this.layout(c); - } - }); - break; - } - case "v": { - let y = l.y + (l.h-l._h)/2; - var filly = l.c && l.c.reduce((a,l)=>a+(0|l.filly),0); - if (filly) { y = l.y; } - l.c.forEach(c => { - c.w = c.fillx ? l.w : c._w; - c.h = c._h + ((0|c.filly)*(l.h-l._h)/(filly||1)); - c.x = l.x + (1+(0|c.halign))*(l.w-c.w)/2; - c.y = y; - y += c.h; - if (c.pad) { - y += c.pad*2; - c.w += c.pad*2; - c.h += c.pad*2; - } - if (c.c) this.layout(c); - }); - break; - } - } -}; -Layout.prototype.debug = function(l,c) { - if (!l) l = this._l; - c=c||1; - g.setColor(c&1,c&2,c&4).drawRect(l.x+c-1, l.y+c-1, l.x+l.w-c, l.y+l.h-c); - c++; - if (l.c) l.c.forEach(n => this.debug(n,c)); -}; -Layout.prototype.update = function() { - var l = this._l; - var w = g.getWidth(); - var y = this.yOffset; - var h = g.getHeight()-y; - // update sizes - function updateMin(l) {"ram" - cb[l.type](l); - if (l.r&1) { // rotation - var t = l._w;l._w=l._h;l._h=t; - } - l._w = Math.max(l._w, 0|l.width); - l._h = Math.max(l._h, 0|l.height); - } - var cb = { - "txt" : function(l) { - if (l.font.endsWith("%")) - l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100); - // FIXME ':'/fsz not needed in new firmwares - it's handled internally - if (l.font.includes(":")) { - var f = l.font.split(":"); - l.font = f[0]; - l.fsz = f[1]; - } - g.setFont(l.font,l.fsz); - l._h = g.getFontHeight(); - l._w = g.stringWidth(l.label); - }, "btn": function(l) { - l._h = 24; - l._w = 14 + l.label.length*8; - }, "img": function(l) { - var im = E.toString(l.src()); - l._h = im.charCodeAt(0); - l._w = im.charCodeAt(1); - }, "undefined": function(l) { - // size should already be set up in width/height - l._w = 0; - l._h = 0; - }, "custom": function(l) { - // size should already be set up in width/height - l._w = 0; - l._h = 0; - }, "h": function(l) { - l.c.forEach(updateMin); - l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0); - l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0); - if (l.c.some(c=>c.fillx)) l.fillx = 1; - if (l.c.some(c=>c.filly)) l.filly = 1; - }, "v": function(l) { - l.c.forEach(updateMin); - l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0); - l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0); - if (l.c.some(c=>c.fillx)) l.fillx = 1; - if (l.c.some(c=>c.filly)) l.filly = 1; - } - }; - updateMin(l); - // center - if (l.fillx || l.filly) { - l.w = w; - l.h = h; - l.x = 0; - l.y = y; - } else { - l.w = l._w; - l.h = l._h; - l.x = (w-l.w)/2; - l.y = y+(h-l.h)/2; - } - // layout children - this.layout(l); -}; - -Layout.prototype.clear = function(l) { - if (!l) l = this._l; - g.reset(); - if (l.bgCol!==undefined) g.setBgColor(l.bgCol); - g.clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1); -}; - -exports = Layout; From bd79718907c33a9365da1607019d32b4257b42b3 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Thu, 16 Sep 2021 15:04:59 +0100 Subject: [PATCH 0025/1062] fix app removal/upgrade --- core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core b/core index 39f89acf1..2aac601e3 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 39f89acf1468dc7d58ac54d15a092b2bd6d73cce +Subproject commit 2aac601e38d659876eb7db5aebc7a12dd3c39da7 From 085c0dd328c93747d81f80dfeee8a38f93858ae7 Mon Sep 17 00:00:00 2001 From: hughbarney Date: Thu, 16 Sep 2021 20:16:43 +0100 Subject: [PATCH 0026/1062] fixed travis build failures on pastel --- apps/pastel/pastel.settings.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/pastel/pastel.settings.js b/apps/pastel/pastel.settings.js index b9d557aaf..db7206dbb 100644 --- a/apps/pastel/pastel.settings.js +++ b/apps/pastel/pastel.settings.js @@ -11,7 +11,7 @@ // ...and overwrite them with any saved values // This way saved values are preserved if a new version adds more settings const storage = require('Storage') - const settings = storage.readJSON(SETTINGS_FILE, 1) || {} + let settings = storage.readJSON(SETTINGS_FILE, 1) || {} const saved = settings || {} for (const key in saved) { s[key] = saved[key] @@ -26,7 +26,6 @@ E.showMenu({ '': { 'title': 'Pastel Clock' }, - '< Back': back, 'Font': { value: 0 | font_options.indexOf(s.font), min: 0, max: 4, From d50ba356d68ebeffd028a924450d4a6ad4fddd74 Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 16 Sep 2021 18:36:59 -0700 Subject: [PATCH 0027/1062] Update apps.json --- apps.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps.json b/apps.json index aad5a07be..004226c86 100644 --- a/apps.json +++ b/apps.json @@ -3501,5 +3501,19 @@ "storage": [ {"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"} ] - } + }, + { "id": "schoolCalender", + "name": "School Calendar", + "shortName":"SCalender", + "icon": "CalenderLogo.png", + "version":"0.01", + "description": "A simple calendar that you can see your upcoming events. Keep in note that your events reapeat weekly.", + "tags": "tool", + "readme": "README.md", + "custom":"interface.html", + "data": [ + {"name":"app.json"} + ] +} + ] From 135165c983beca48190d091e355436100beee41d Mon Sep 17 00:00:00 2001 From: Ronin0000 <89286474+Ronin0000@users.noreply.github.com> Date: Thu, 16 Sep 2021 18:37:52 -0700 Subject: [PATCH 0028/1062] Add files via upload --- apps/schoolCalender/CalenderLogo.png | Bin 0 -> 1162 bytes apps/schoolCalender/ChangeLog.md | 2 + apps/schoolCalender/README.md | 9 + apps/schoolCalender/app-icon.js | 1 + apps/schoolCalender/comments.js | 25 + .../fullcalendar/interaction/LICENSE.txt | 22 + .../fullcalendar/interaction/README.md | 8 + .../fullcalendar/interaction/package.json | 32 + .../interaction/src/ElementScrollGeomCache.ts | 16 + .../interaction/src/OffsetTracker.ts | 76 + .../interaction/src/ScrollGeomCache.ts | 107 + .../interaction/src/WindowScrollGeomCache.ts | 27 + .../interaction/src/api-type-deps.ts | 6 + .../interaction/src/dnd/AutoScroller.ts | 217 + .../interaction/src/dnd/ElementMirror.ts | 146 + .../src/dnd/FeaturefulElementDragging.ts | 213 + .../interaction/src/dnd/PointerDragging.ts | 344 + .../ExternalDraggable.ts | 70 + .../ExternalElementDragging.ts | 268 + .../InferredElementDragging.ts | 77 + .../ThirdPartyDraggable.ts | 52 + .../src/interactions/DateClicking.ts | 71 + .../src/interactions/DateSelecting.ts | 152 + .../src/interactions/EventDragging.ts | 482 + .../src/interactions/EventResizing.ts | 263 + .../src/interactions/HitDragging.ts | 220 + .../src/interactions/UnselectAuto.ts | 77 + .../interaction/src/main.global.ts | 7 + .../fullcalendar/interaction/src/main.ts | 23 + .../interaction/src/options-declare.ts | 9 + .../fullcalendar/interaction/src/options.ts | 26 + .../fullcalendar/interaction/src/utils.ts | 38 + .../fullcalendar/interaction/tsconfig.json | 13 + .../fullcalendar/locales-all.js | 1622 ++ apps/schoolCalender/fullcalendar/main.css | 1446 ++ apps/schoolCalender/fullcalendar/main.js | 14738 ++++++++++++++++ apps/schoolCalender/interface.html | 60 + apps/schoolCalender/schoolCalender.js | 229 + 38 files changed, 21194 insertions(+) create mode 100644 apps/schoolCalender/CalenderLogo.png create mode 100644 apps/schoolCalender/ChangeLog.md create mode 100644 apps/schoolCalender/README.md create mode 100644 apps/schoolCalender/app-icon.js create mode 100644 apps/schoolCalender/comments.js create mode 100644 apps/schoolCalender/fullcalendar/interaction/LICENSE.txt create mode 100644 apps/schoolCalender/fullcalendar/interaction/README.md create mode 100644 apps/schoolCalender/fullcalendar/interaction/package.json create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/ElementScrollGeomCache.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/OffsetTracker.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/ScrollGeomCache.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/WindowScrollGeomCache.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/api-type-deps.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/dnd/AutoScroller.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/dnd/ElementMirror.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/dnd/PointerDragging.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions/DateClicking.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions/DateSelecting.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions/EventDragging.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions/EventResizing.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions/HitDragging.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/interactions/UnselectAuto.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/main.global.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/main.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/options-declare.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/options.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/src/utils.ts create mode 100644 apps/schoolCalender/fullcalendar/interaction/tsconfig.json create mode 100644 apps/schoolCalender/fullcalendar/locales-all.js create mode 100644 apps/schoolCalender/fullcalendar/main.css create mode 100644 apps/schoolCalender/fullcalendar/main.js create mode 100644 apps/schoolCalender/interface.html create mode 100644 apps/schoolCalender/schoolCalender.js diff --git a/apps/schoolCalender/CalenderLogo.png b/apps/schoolCalender/CalenderLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..49d198a15b0feb1c4d175282c6e1c1112df8b06c GIT binary patch literal 1162 zcmV;51anZw{&TSgFQ0s2e#fgMU}6Rl(1Cxt0iNJP?%_Xw&WO?Wwfph4b@stgo+Qa&i(UCnxF= z=^f0^&*Sj$5W~a6ihNb)l-ma_oI-sNn#AfHKVDn)1KFU|8k1(FzFl78?CeY#U}0f_ zt*$IhHpGmKH2oliK)jN`FZi~|&(6*wpU>k`D$U`+_o|PNE}>0tfFZyFtu-8(t`Mk4 zRVadHfw%A8(ZHa13UYil?-IKa`-5p2n{%CWPgF;s%vHCvW7)h_h`u0X2E*gnkAhjL7?#g!$$ zm&@qu>r+YlRVJ;lbwApL8doz}bu2NPJ9T+Lj;K2XsklI%Bg*<07Z*^WVtNPHd5(J{ zi)?T27ixenz{XvLjL)@vy)h_U=P@O?KINEKJM=B{v^Vc zQg7yIFotKK#7GlDQ8PnhosU!%@z7{B``hhynA5}plQ?{oFs0Ou4@N-|w= schedule[i].startingTimeHour && currentMinute >= schedule[i].startingTimeMinute){ + if(currentHour <= schedule[i].endingTimeHour){ + console.log("Time of Day "+schedule[i].description); + g.drawString(i + ": "+schedule[i].description, 10, 10*i-100); + } + + //console.log("DayOfWeek:"+currentDayOfWeek+", Hour:"+ currentHour + ", Minute:" + currentMinute); + //console.log("DayOfWeek:"+schedule[i].dayOfWeek+", StartHour:"+ schedule[i].startingTimeHour +", EndHour:" + schedule[i].endingTimeHour + ", StartMinute:" + schedule[i].startingTimeMinute + ", EndMinute:" + schedule[i].endingTimeMinute); + + + + + + + + + + + + + + + + */ diff --git a/apps/schoolCalender/fullcalendar/interaction/LICENSE.txt b/apps/schoolCalender/fullcalendar/interaction/LICENSE.txt new file mode 100644 index 000000000..84924a980 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/LICENSE.txt @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 Adam Shaw + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/apps/schoolCalender/fullcalendar/interaction/README.md b/apps/schoolCalender/fullcalendar/interaction/README.md new file mode 100644 index 000000000..6d5821d71 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/README.md @@ -0,0 +1,8 @@ + +# FullCalendar Interaction Plugin + +Provides functionality for event drag-n-drop, resizing, dateClick, and selectable actions + +[View the docs »](https://fullcalendar.io/docs/editable) + +This package was created from the [FullCalendar monorepo »](https://github.com/fullcalendar/fullcalendar) diff --git a/apps/schoolCalender/fullcalendar/interaction/package.json b/apps/schoolCalender/fullcalendar/interaction/package.json new file mode 100644 index 000000000..d382635a8 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/package.json @@ -0,0 +1,32 @@ +{ + "name": "@fullcalendar/interaction", + "version": "5.9.0", + "title": "FullCalendar Interaction Plugin", + "description": "Provides functionality for event drag-n-drop, resizing, dateClick, and selectable actions", + "docs": "https://fullcalendar.io/docs/editable", + "dependencies": { + "@fullcalendar/common": "workspace:~5.9.0", + "tslib": "^2.1.0" + }, + "main": "main.cjs.js", + "module": "main.js", + "types": "main.d.ts", + "jsdelivr": "main.global.min.js", + "browserGlobal": "FullCalendarInteraction", + "homepage": "https://fullcalendar.io/", + "bugs": "https://fullcalendar.io/reporting-bugs", + "repository": { + "type": "git", + "url": "https://github.com/fullcalendar/fullcalendar.git", + "homepage": "https://github.com/fullcalendar/fullcalendar" + }, + "license": "MIT", + "author": { + "name": "Adam Shaw", + "email": "arshaw@arshaw.com", + "url": "http://arshaw.com/" + }, + "devDependencies": { + "@fullcalendar/core-preact": "workspace:*" + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/ElementScrollGeomCache.ts b/apps/schoolCalender/fullcalendar/interaction/src/ElementScrollGeomCache.ts new file mode 100644 index 000000000..83be540cd --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/ElementScrollGeomCache.ts @@ -0,0 +1,16 @@ +import { computeInnerRect, ElementScrollController } from '@fullcalendar/common' +import { ScrollGeomCache } from './ScrollGeomCache' + +export class ElementScrollGeomCache extends ScrollGeomCache { + constructor(el: HTMLElement, doesListening: boolean) { + super(new ElementScrollController(el), doesListening) + } + + getEventTarget(): EventTarget { + return (this.scrollController as ElementScrollController).el + } + + computeClientRect() { + return computeInnerRect((this.scrollController as ElementScrollController).el) + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/OffsetTracker.ts b/apps/schoolCalender/fullcalendar/interaction/src/OffsetTracker.ts new file mode 100644 index 000000000..b1fac23e6 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/OffsetTracker.ts @@ -0,0 +1,76 @@ +import { + getClippingParents, computeRect, + pointInsideRect, Rect, +} from '@fullcalendar/common' +import { ElementScrollGeomCache } from './ElementScrollGeomCache' + +/* +When this class is instantiated, it records the offset of an element (relative to the document topleft), +and continues to monitor scrolling, updating the cached coordinates if it needs to. +Does not access the DOM after instantiation, so highly performant. + +Also keeps track of all scrolling/overflow:hidden containers that are parents of the given element +and an determine if a given point is inside the combined clipping rectangle. +*/ +export class OffsetTracker { // ElementOffsetTracker + scrollCaches: ElementScrollGeomCache[] + origRect: Rect + + constructor(el: HTMLElement) { + this.origRect = computeRect(el) + + // will work fine for divs that have overflow:hidden + this.scrollCaches = getClippingParents(el).map( + (scrollEl) => new ElementScrollGeomCache(scrollEl, true), // listen=true + ) + } + + destroy() { + for (let scrollCache of this.scrollCaches) { + scrollCache.destroy() + } + } + + computeLeft() { + let left = this.origRect.left + + for (let scrollCache of this.scrollCaches) { + left += scrollCache.origScrollLeft - scrollCache.getScrollLeft() + } + + return left + } + + computeTop() { + let top = this.origRect.top + + for (let scrollCache of this.scrollCaches) { + top += scrollCache.origScrollTop - scrollCache.getScrollTop() + } + + return top + } + + isWithinClipping(pageX: number, pageY: number): boolean { + let point = { left: pageX, top: pageY } + + for (let scrollCache of this.scrollCaches) { + if ( + !isIgnoredClipping(scrollCache.getEventTarget()) && + !pointInsideRect(point, scrollCache.clientRect) + ) { + return false + } + } + + return true + } +} + +// certain clipping containers should never constrain interactions, like and +// https://github.com/fullcalendar/fullcalendar/issues/3615 +function isIgnoredClipping(node: EventTarget) { + let tagName = (node as HTMLElement).tagName + + return tagName === 'HTML' || tagName === 'BODY' +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/ScrollGeomCache.ts b/apps/schoolCalender/fullcalendar/interaction/src/ScrollGeomCache.ts new file mode 100644 index 000000000..63ec6a5e1 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/ScrollGeomCache.ts @@ -0,0 +1,107 @@ +import { Rect, ScrollController } from '@fullcalendar/common' + +/* +Is a cache for a given element's scroll information (all the info that ScrollController stores) +in addition the "client rectangle" of the element.. the area within the scrollbars. + +The cache can be in one of two modes: +- doesListening:false - ignores when the container is scrolled by someone else +- doesListening:true - watch for scrolling and update the cache +*/ +export abstract class ScrollGeomCache extends ScrollController { + clientRect: Rect + origScrollTop: number + origScrollLeft: number + + protected scrollController: ScrollController + protected doesListening: boolean + protected scrollTop: number + protected scrollLeft: number + protected scrollWidth: number + protected scrollHeight: number + protected clientWidth: number + protected clientHeight: number + + constructor(scrollController: ScrollController, doesListening: boolean) { + super() + this.scrollController = scrollController + this.doesListening = doesListening + this.scrollTop = this.origScrollTop = scrollController.getScrollTop() + this.scrollLeft = this.origScrollLeft = scrollController.getScrollLeft() + this.scrollWidth = scrollController.getScrollWidth() + this.scrollHeight = scrollController.getScrollHeight() + this.clientWidth = scrollController.getClientWidth() + this.clientHeight = scrollController.getClientHeight() + this.clientRect = this.computeClientRect() // do last in case it needs cached values + + if (this.doesListening) { + this.getEventTarget().addEventListener('scroll', this.handleScroll) + } + } + + abstract getEventTarget(): EventTarget + abstract computeClientRect(): Rect + + destroy() { + if (this.doesListening) { + this.getEventTarget().removeEventListener('scroll', this.handleScroll) + } + } + + handleScroll = () => { + this.scrollTop = this.scrollController.getScrollTop() + this.scrollLeft = this.scrollController.getScrollLeft() + this.handleScrollChange() + } + + getScrollTop() { + return this.scrollTop + } + + getScrollLeft() { + return this.scrollLeft + } + + setScrollTop(top: number) { + this.scrollController.setScrollTop(top) + + if (!this.doesListening) { + // we are not relying on the element to normalize out-of-bounds scroll values + // so we need to sanitize ourselves + this.scrollTop = Math.max(Math.min(top, this.getMaxScrollTop()), 0) + + this.handleScrollChange() + } + } + + setScrollLeft(top: number) { + this.scrollController.setScrollLeft(top) + + if (!this.doesListening) { + // we are not relying on the element to normalize out-of-bounds scroll values + // so we need to sanitize ourselves + this.scrollLeft = Math.max(Math.min(top, this.getMaxScrollLeft()), 0) + + this.handleScrollChange() + } + } + + getClientWidth() { + return this.clientWidth + } + + getClientHeight() { + return this.clientHeight + } + + getScrollWidth() { + return this.scrollWidth + } + + getScrollHeight() { + return this.scrollHeight + } + + handleScrollChange() { + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/WindowScrollGeomCache.ts b/apps/schoolCalender/fullcalendar/interaction/src/WindowScrollGeomCache.ts new file mode 100644 index 000000000..ca65dee6e --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/WindowScrollGeomCache.ts @@ -0,0 +1,27 @@ +import { Rect, WindowScrollController } from '@fullcalendar/common' +import { ScrollGeomCache } from './ScrollGeomCache' + +export class WindowScrollGeomCache extends ScrollGeomCache { + constructor(doesListening: boolean) { + super(new WindowScrollController(), doesListening) + } + + getEventTarget(): EventTarget { + return window + } + + computeClientRect(): Rect { + return { + left: this.scrollLeft, + right: this.scrollLeft + this.clientWidth, + top: this.scrollTop, + bottom: this.scrollTop + this.clientHeight, + } + } + + // the window is the only scroll object that changes it's rectangle relative + // to the document's topleft as it scrolls + handleScrollChange() { + this.clientRect = this.computeClientRect() + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/api-type-deps.ts b/apps/schoolCalender/fullcalendar/interaction/src/api-type-deps.ts new file mode 100644 index 000000000..2b1b51b06 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/api-type-deps.ts @@ -0,0 +1,6 @@ +// TODO: rename file to public-types.ts + +export { DateClickArg } from './interactions/DateClicking' +export { EventDragStartArg, EventDragStopArg } from './interactions/EventDragging' +export { EventResizeStartArg, EventResizeStopArg, EventResizeDoneArg } from './interactions/EventResizing' +export { DropArg, EventReceiveArg, EventLeaveArg } from './utils' diff --git a/apps/schoolCalender/fullcalendar/interaction/src/dnd/AutoScroller.ts b/apps/schoolCalender/fullcalendar/interaction/src/dnd/AutoScroller.ts new file mode 100644 index 000000000..8d4e8f015 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/dnd/AutoScroller.ts @@ -0,0 +1,217 @@ +import { getElRoot } from '@fullcalendar/common' +import { ScrollGeomCache } from '../ScrollGeomCache' +import { ElementScrollGeomCache } from '../ElementScrollGeomCache' +import { WindowScrollGeomCache } from '../WindowScrollGeomCache' + +interface Edge { + scrollCache: ScrollGeomCache + name: 'top' | 'left' | 'right' | 'bottom' + distance: number // how many pixels the current pointer is from the edge +} + +// If available we are using native "performance" API instead of "Date" +// Read more about it on MDN: +// https://developer.mozilla.org/en-US/docs/Web/API/Performance +const getTime = typeof performance === 'function' ? (performance as any).now : Date.now + +/* +For a pointer interaction, automatically scrolls certain scroll containers when the pointer +approaches the edge. + +The caller must call start + handleMove + stop. +*/ +export class AutoScroller { + // options that can be set by caller + isEnabled: boolean = true + scrollQuery: (Window | string)[] = [window, '.fc-scroller'] + edgeThreshold: number = 50 // pixels + maxVelocity: number = 300 // pixels per second + + // internal state + pointerScreenX: number | null = null + pointerScreenY: number | null = null + isAnimating: boolean = false + scrollCaches: ScrollGeomCache[] | null = null + msSinceRequest?: number + + // protect against the initial pointerdown being too close to an edge and starting the scroll + everMovedUp: boolean = false + everMovedDown: boolean = false + everMovedLeft: boolean = false + everMovedRight: boolean = false + + start(pageX: number, pageY: number, scrollStartEl: HTMLElement) { + if (this.isEnabled) { + this.scrollCaches = this.buildCaches(scrollStartEl) + this.pointerScreenX = null + this.pointerScreenY = null + this.everMovedUp = false + this.everMovedDown = false + this.everMovedLeft = false + this.everMovedRight = false + this.handleMove(pageX, pageY) + } + } + + handleMove(pageX: number, pageY: number) { + if (this.isEnabled) { + let pointerScreenX = pageX - window.pageXOffset + let pointerScreenY = pageY - window.pageYOffset + + let yDelta = this.pointerScreenY === null ? 0 : pointerScreenY - this.pointerScreenY + let xDelta = this.pointerScreenX === null ? 0 : pointerScreenX - this.pointerScreenX + + if (yDelta < 0) { + this.everMovedUp = true + } else if (yDelta > 0) { + this.everMovedDown = true + } + + if (xDelta < 0) { + this.everMovedLeft = true + } else if (xDelta > 0) { + this.everMovedRight = true + } + + this.pointerScreenX = pointerScreenX + this.pointerScreenY = pointerScreenY + + if (!this.isAnimating) { + this.isAnimating = true + this.requestAnimation(getTime()) + } + } + } + + stop() { + if (this.isEnabled) { + this.isAnimating = false // will stop animation + + for (let scrollCache of this.scrollCaches!) { + scrollCache.destroy() + } + + this.scrollCaches = null + } + } + + requestAnimation(now: number) { + this.msSinceRequest = now + requestAnimationFrame(this.animate) + } + + private animate = () => { + if (this.isAnimating) { // wasn't cancelled between animation calls + let edge = this.computeBestEdge( + this.pointerScreenX! + window.pageXOffset, + this.pointerScreenY! + window.pageYOffset, + ) + + if (edge) { + let now = getTime() + this.handleSide(edge, (now - this.msSinceRequest!) / 1000) + this.requestAnimation(now) + } else { + this.isAnimating = false // will stop animation + } + } + } + + private handleSide(edge: Edge, seconds: number) { + let { scrollCache } = edge + let { edgeThreshold } = this + let invDistance = edgeThreshold - edge.distance + let velocity = // the closer to the edge, the faster we scroll + ((invDistance * invDistance) / (edgeThreshold * edgeThreshold)) * // quadratic + this.maxVelocity * seconds + let sign = 1 + + switch (edge.name) { + case 'left': + sign = -1 + // falls through + case 'right': + scrollCache.setScrollLeft(scrollCache.getScrollLeft() + velocity * sign) + break + + case 'top': + sign = -1 + // falls through + case 'bottom': + scrollCache.setScrollTop(scrollCache.getScrollTop() + velocity * sign) + break + } + } + + // left/top are relative to document topleft + private computeBestEdge(left: number, top: number): Edge | null { + let { edgeThreshold } = this + let bestSide: Edge | null = null + + for (let scrollCache of this.scrollCaches!) { + let rect = scrollCache.clientRect + let leftDist = left - rect.left + let rightDist = rect.right - left + let topDist = top - rect.top + let bottomDist = rect.bottom - top + + // completely within the rect? + if (leftDist >= 0 && rightDist >= 0 && topDist >= 0 && bottomDist >= 0) { + if ( + topDist <= edgeThreshold && this.everMovedUp && scrollCache.canScrollUp() && + (!bestSide || bestSide.distance > topDist) + ) { + bestSide = { scrollCache, name: 'top', distance: topDist } + } + + if ( + bottomDist <= edgeThreshold && this.everMovedDown && scrollCache.canScrollDown() && + (!bestSide || bestSide.distance > bottomDist) + ) { + bestSide = { scrollCache, name: 'bottom', distance: bottomDist } + } + + if ( + leftDist <= edgeThreshold && this.everMovedLeft && scrollCache.canScrollLeft() && + (!bestSide || bestSide.distance > leftDist) + ) { + bestSide = { scrollCache, name: 'left', distance: leftDist } + } + + if ( + rightDist <= edgeThreshold && this.everMovedRight && scrollCache.canScrollRight() && + (!bestSide || bestSide.distance > rightDist) + ) { + bestSide = { scrollCache, name: 'right', distance: rightDist } + } + } + } + + return bestSide + } + + private buildCaches(scrollStartEl: HTMLElement) { + return this.queryScrollEls(scrollStartEl).map((el) => { + if (el === window) { + return new WindowScrollGeomCache(false) // false = don't listen to user-generated scrolls + } + return new ElementScrollGeomCache(el, false) // false = don't listen to user-generated scrolls + }) + } + + private queryScrollEls(scrollStartEl: HTMLElement) { + let els = [] + + for (let query of this.scrollQuery) { + if (typeof query === 'object') { + els.push(query) + } else { + els.push(...Array.prototype.slice.call( + getElRoot(scrollStartEl).querySelectorAll(query), + )) + } + } + + return els + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/dnd/ElementMirror.ts b/apps/schoolCalender/fullcalendar/interaction/src/dnd/ElementMirror.ts new file mode 100644 index 000000000..6c1e9f4a1 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/dnd/ElementMirror.ts @@ -0,0 +1,146 @@ +import { removeElement, applyStyle, whenTransitionDone, Rect } from '@fullcalendar/common' + +/* +An effect in which an element follows the movement of a pointer across the screen. +The moving element is a clone of some other element. +Must call start + handleMove + stop. +*/ +export class ElementMirror { + isVisible: boolean = false // must be explicitly enabled + origScreenX?: number + origScreenY?: number + deltaX?: number + deltaY?: number + sourceEl: HTMLElement | null = null + mirrorEl: HTMLElement | null = null + sourceElRect: Rect | null = null // screen coords relative to viewport + + // options that can be set directly by caller + parentNode: HTMLElement = document.body // HIGHLY SUGGESTED to set this to sidestep ShadowDOM issues + zIndex: number = 9999 + revertDuration: number = 0 + + start(sourceEl: HTMLElement, pageX: number, pageY: number) { + this.sourceEl = sourceEl + this.sourceElRect = this.sourceEl.getBoundingClientRect() + this.origScreenX = pageX - window.pageXOffset + this.origScreenY = pageY - window.pageYOffset + this.deltaX = 0 + this.deltaY = 0 + this.updateElPosition() + } + + handleMove(pageX: number, pageY: number) { + this.deltaX = (pageX - window.pageXOffset) - this.origScreenX! + this.deltaY = (pageY - window.pageYOffset) - this.origScreenY! + this.updateElPosition() + } + + // can be called before start + setIsVisible(bool: boolean) { + if (bool) { + if (!this.isVisible) { + if (this.mirrorEl) { + this.mirrorEl.style.display = '' + } + + this.isVisible = bool // needs to happen before updateElPosition + this.updateElPosition() // because was not updating the position while invisible + } + } else if (this.isVisible) { + if (this.mirrorEl) { + this.mirrorEl.style.display = 'none' + } + + this.isVisible = bool + } + } + + // always async + stop(needsRevertAnimation: boolean, callback: () => void) { + let done = () => { + this.cleanup() + callback() + } + + if ( + needsRevertAnimation && + this.mirrorEl && + this.isVisible && + this.revertDuration && // if 0, transition won't work + (this.deltaX || this.deltaY) // if same coords, transition won't work + ) { + this.doRevertAnimation(done, this.revertDuration) + } else { + setTimeout(done, 0) + } + } + + doRevertAnimation(callback: () => void, revertDuration: number) { + let mirrorEl = this.mirrorEl! + let finalSourceElRect = this.sourceEl!.getBoundingClientRect() // because autoscrolling might have happened + + mirrorEl.style.transition = + 'top ' + revertDuration + 'ms,' + + 'left ' + revertDuration + 'ms' + + applyStyle(mirrorEl, { + left: finalSourceElRect.left, + top: finalSourceElRect.top, + }) + + whenTransitionDone(mirrorEl, () => { + mirrorEl.style.transition = '' + callback() + }) + } + + cleanup() { + if (this.mirrorEl) { + removeElement(this.mirrorEl) + this.mirrorEl = null + } + + this.sourceEl = null + } + + updateElPosition() { + if (this.sourceEl && this.isVisible) { + applyStyle(this.getMirrorEl(), { + left: this.sourceElRect!.left + this.deltaX!, + top: this.sourceElRect!.top + this.deltaY!, + }) + } + } + + getMirrorEl(): HTMLElement { + let sourceElRect = this.sourceElRect! + let mirrorEl = this.mirrorEl + + if (!mirrorEl) { + mirrorEl = this.mirrorEl = this.sourceEl!.cloneNode(true) as HTMLElement // cloneChildren=true + + // we don't want long taps or any mouse interaction causing selection/menus. + // would use preventSelection(), but that prevents selectstart, causing problems. + mirrorEl.classList.add('fc-unselectable') + + mirrorEl.classList.add('fc-event-dragging') + + applyStyle(mirrorEl, { + position: 'fixed', + zIndex: this.zIndex, + visibility: '', // in case original element was hidden by the drag effect + boxSizing: 'border-box', // for easy width/height + width: sourceElRect.right - sourceElRect.left, // explicit height in case there was a 'right' value + height: sourceElRect.bottom - sourceElRect.top, // explicit width in case there was a 'bottom' value + right: 'auto', // erase and set width instead + bottom: 'auto', // erase and set height instead + margin: 0, + }) + + this.parentNode.appendChild(mirrorEl) + } + + return mirrorEl + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts b/apps/schoolCalender/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts new file mode 100644 index 000000000..3f1c7826b --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/dnd/FeaturefulElementDragging.ts @@ -0,0 +1,213 @@ +import { + PointerDragEvent, + preventSelection, + allowSelection, + preventContextMenu, + allowContextMenu, + ElementDragging, +} from '@fullcalendar/common' +import { PointerDragging } from './PointerDragging' +import { ElementMirror } from './ElementMirror' +import { AutoScroller } from './AutoScroller' + +/* +Monitors dragging on an element. Has a number of high-level features: +- minimum distance required before dragging +- minimum wait time ("delay") before dragging +- a mirror element that follows the pointer +*/ +export class FeaturefulElementDragging extends ElementDragging { + pointer: PointerDragging + mirror: ElementMirror + autoScroller: AutoScroller + + // options that can be directly set by caller + // the caller can also set the PointerDragging's options as well + delay: number | null = null + minDistance: number = 0 + touchScrollAllowed: boolean = true // prevents drag from starting and blocks scrolling during drag + + mirrorNeedsRevert: boolean = false + isInteracting: boolean = false // is the user validly moving the pointer? lasts until pointerup + isDragging: boolean = false // is it INTENTFULLY dragging? lasts until after revert animation + isDelayEnded: boolean = false + isDistanceSurpassed: boolean = false + delayTimeoutId: number | null = null + + constructor(private containerEl: HTMLElement, selector?: string) { + super(containerEl) + + let pointer = this.pointer = new PointerDragging(containerEl) + pointer.emitter.on('pointerdown', this.onPointerDown) + pointer.emitter.on('pointermove', this.onPointerMove) + pointer.emitter.on('pointerup', this.onPointerUp) + + if (selector) { + pointer.selector = selector + } + + this.mirror = new ElementMirror() + this.autoScroller = new AutoScroller() + } + + destroy() { + this.pointer.destroy() + + // HACK: simulate a pointer-up to end the current drag + // TODO: fire 'dragend' directly and stop interaction. discourage use of pointerup event (b/c might not fire) + this.onPointerUp({} as any) + } + + onPointerDown = (ev: PointerDragEvent) => { + if (!this.isDragging) { // so new drag doesn't happen while revert animation is going + this.isInteracting = true + this.isDelayEnded = false + this.isDistanceSurpassed = false + + preventSelection(document.body) + preventContextMenu(document.body) + + // prevent links from being visited if there's an eventual drag. + // also prevents selection in older browsers (maybe?). + // not necessary for touch, besides, browser would complain about passiveness. + if (!ev.isTouch) { + ev.origEvent.preventDefault() + } + + this.emitter.trigger('pointerdown', ev) + + if ( + this.isInteracting && // not destroyed via pointerdown handler + !this.pointer.shouldIgnoreMove + ) { + // actions related to initiating dragstart+dragmove+dragend... + + this.mirror.setIsVisible(false) // reset. caller must set-visible + this.mirror.start(ev.subjectEl as HTMLElement, ev.pageX, ev.pageY) // must happen on first pointer down + + this.startDelay(ev) + + if (!this.minDistance) { + this.handleDistanceSurpassed(ev) + } + } + } + } + + onPointerMove = (ev: PointerDragEvent) => { + if (this.isInteracting) { + this.emitter.trigger('pointermove', ev) + + if (!this.isDistanceSurpassed) { + let minDistance = this.minDistance + let distanceSq // current distance from the origin, squared + let { deltaX, deltaY } = ev + + distanceSq = deltaX * deltaX + deltaY * deltaY + if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem + this.handleDistanceSurpassed(ev) + } + } + + if (this.isDragging) { + // a real pointer move? (not one simulated by scrolling) + if (ev.origEvent.type !== 'scroll') { + this.mirror.handleMove(ev.pageX, ev.pageY) + this.autoScroller.handleMove(ev.pageX, ev.pageY) + } + + this.emitter.trigger('dragmove', ev) + } + } + } + + onPointerUp = (ev: PointerDragEvent) => { + if (this.isInteracting) { + this.isInteracting = false + + allowSelection(document.body) + allowContextMenu(document.body) + + this.emitter.trigger('pointerup', ev) // can potentially set mirrorNeedsRevert + + if (this.isDragging) { + this.autoScroller.stop() + this.tryStopDrag(ev) // which will stop the mirror + } + + if (this.delayTimeoutId) { + clearTimeout(this.delayTimeoutId) + this.delayTimeoutId = null + } + } + } + + startDelay(ev: PointerDragEvent) { + if (typeof this.delay === 'number') { + this.delayTimeoutId = setTimeout(() => { + this.delayTimeoutId = null + this.handleDelayEnd(ev) + }, this.delay) as any // not assignable to number! + } else { + this.handleDelayEnd(ev) + } + } + + handleDelayEnd(ev: PointerDragEvent) { + this.isDelayEnded = true + this.tryStartDrag(ev) + } + + handleDistanceSurpassed(ev: PointerDragEvent) { + this.isDistanceSurpassed = true + this.tryStartDrag(ev) + } + + tryStartDrag(ev: PointerDragEvent) { + if (this.isDelayEnded && this.isDistanceSurpassed) { + if (!this.pointer.wasTouchScroll || this.touchScrollAllowed) { + this.isDragging = true + this.mirrorNeedsRevert = false + + this.autoScroller.start(ev.pageX, ev.pageY, this.containerEl) + this.emitter.trigger('dragstart', ev) + + if (this.touchScrollAllowed === false) { + this.pointer.cancelTouchScroll() + } + } + } + } + + tryStopDrag(ev: PointerDragEvent) { + // .stop() is ALWAYS asynchronous, which we NEED because we want all pointerup events + // that come from the document to fire beforehand. much more convenient this way. + this.mirror.stop( + this.mirrorNeedsRevert, + this.stopDrag.bind(this, ev), // bound with args + ) + } + + stopDrag(ev: PointerDragEvent) { + this.isDragging = false + this.emitter.trigger('dragend', ev) + } + + // fill in the implementations... + + setIgnoreMove(bool: boolean) { + this.pointer.shouldIgnoreMove = bool + } + + setMirrorIsVisible(bool: boolean) { + this.mirror.setIsVisible(bool) + } + + setMirrorNeedsRevert(bool: boolean) { + this.mirrorNeedsRevert = bool + } + + setAutoScrollEnabled(bool: boolean) { + this.autoScroller.isEnabled = bool + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/dnd/PointerDragging.ts b/apps/schoolCalender/fullcalendar/interaction/src/dnd/PointerDragging.ts new file mode 100644 index 000000000..dbe7d8abb --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/dnd/PointerDragging.ts @@ -0,0 +1,344 @@ +import { config, elementClosest, Emitter, PointerDragEvent } from '@fullcalendar/common' + +config.touchMouseIgnoreWait = 500 + +let ignoreMouseDepth = 0 +let listenerCnt = 0 +let isWindowTouchMoveCancelled = false + +/* +Uses a "pointer" abstraction, which monitors UI events for both mouse and touch. +Tracks when the pointer "drags" on a certain element, meaning down+move+up. + +Also, tracks if there was touch-scrolling. +Also, can prevent touch-scrolling from happening. +Also, can fire pointermove events when scrolling happens underneath, even when no real pointer movement. + +emits: +- pointerdown +- pointermove +- pointerup +*/ +export class PointerDragging { + containerEl: EventTarget + subjectEl: HTMLElement | null = null + emitter: Emitter + + // options that can be directly assigned by caller + selector: string = '' // will cause subjectEl in all emitted events to be this element + handleSelector: string = '' + shouldIgnoreMove: boolean = false + shouldWatchScroll: boolean = true // for simulating pointermove on scroll + + // internal states + isDragging: boolean = false + isTouchDragging: boolean = false + wasTouchScroll: boolean = false + origPageX: number + origPageY: number + prevPageX: number + prevPageY: number + prevScrollX: number // at time of last pointer pageX/pageY capture + prevScrollY: number // " + + constructor(containerEl: EventTarget) { + this.containerEl = containerEl + this.emitter = new Emitter() + containerEl.addEventListener('mousedown', this.handleMouseDown as EventListener) + containerEl.addEventListener('touchstart', this.handleTouchStart as EventListener, { passive: true }) + listenerCreated() + } + + destroy() { + this.containerEl.removeEventListener('mousedown', this.handleMouseDown as EventListener) + this.containerEl.removeEventListener('touchstart', this.handleTouchStart as EventListener, { passive: true } as AddEventListenerOptions) + listenerDestroyed() + } + + tryStart(ev: UIEvent): boolean { + let subjectEl = this.querySubjectEl(ev) + let downEl = ev.target as HTMLElement + + if ( + subjectEl && + (!this.handleSelector || elementClosest(downEl, this.handleSelector)) + ) { + this.subjectEl = subjectEl + this.isDragging = true // do this first so cancelTouchScroll will work + this.wasTouchScroll = false + + return true + } + + return false + } + + cleanup() { + isWindowTouchMoveCancelled = false + this.isDragging = false + this.subjectEl = null + // keep wasTouchScroll around for later access + this.destroyScrollWatch() + } + + querySubjectEl(ev: UIEvent): HTMLElement { + if (this.selector) { + return elementClosest(ev.target as HTMLElement, this.selector) + } + return this.containerEl as HTMLElement + } + + // Mouse + // ---------------------------------------------------------------------------------------------------- + + handleMouseDown = (ev: MouseEvent) => { + if ( + !this.shouldIgnoreMouse() && + isPrimaryMouseButton(ev) && + this.tryStart(ev) + ) { + let pev = this.createEventFromMouse(ev, true) + this.emitter.trigger('pointerdown', pev) + this.initScrollWatch(pev) + + if (!this.shouldIgnoreMove) { + document.addEventListener('mousemove', this.handleMouseMove) + } + + document.addEventListener('mouseup', this.handleMouseUp) + } + } + + handleMouseMove = (ev: MouseEvent) => { + let pev = this.createEventFromMouse(ev) + this.recordCoords(pev) + this.emitter.trigger('pointermove', pev) + } + + handleMouseUp = (ev: MouseEvent) => { + document.removeEventListener('mousemove', this.handleMouseMove) + document.removeEventListener('mouseup', this.handleMouseUp) + + this.emitter.trigger('pointerup', this.createEventFromMouse(ev)) + + this.cleanup() // call last so that pointerup has access to props + } + + shouldIgnoreMouse() { + return ignoreMouseDepth || this.isTouchDragging + } + + // Touch + // ---------------------------------------------------------------------------------------------------- + + handleTouchStart = (ev: TouchEvent) => { + if (this.tryStart(ev)) { + this.isTouchDragging = true + + let pev = this.createEventFromTouch(ev, true) + this.emitter.trigger('pointerdown', pev) + this.initScrollWatch(pev) + + // unlike mouse, need to attach to target, not document + // https://stackoverflow.com/a/45760014 + let targetEl = ev.target as HTMLElement + + if (!this.shouldIgnoreMove) { + targetEl.addEventListener('touchmove', this.handleTouchMove) + } + + targetEl.addEventListener('touchend', this.handleTouchEnd) + targetEl.addEventListener('touchcancel', this.handleTouchEnd) // treat it as a touch end + + // attach a handler to get called when ANY scroll action happens on the page. + // this was impossible to do with normal on/off because 'scroll' doesn't bubble. + // http://stackoverflow.com/a/32954565/96342 + window.addEventListener( + 'scroll', + this.handleTouchScroll, + true, // useCapture + ) + } + } + + handleTouchMove = (ev: TouchEvent) => { + let pev = this.createEventFromTouch(ev) + this.recordCoords(pev) + this.emitter.trigger('pointermove', pev) + } + + handleTouchEnd = (ev: TouchEvent) => { + if (this.isDragging) { // done to guard against touchend followed by touchcancel + let targetEl = ev.target as HTMLElement + + targetEl.removeEventListener('touchmove', this.handleTouchMove) + targetEl.removeEventListener('touchend', this.handleTouchEnd) + targetEl.removeEventListener('touchcancel', this.handleTouchEnd) + window.removeEventListener('scroll', this.handleTouchScroll, true) // useCaptured=true + + this.emitter.trigger('pointerup', this.createEventFromTouch(ev)) + + this.cleanup() // call last so that pointerup has access to props + this.isTouchDragging = false + startIgnoringMouse() + } + } + + handleTouchScroll = () => { + this.wasTouchScroll = true + } + + // can be called by user of this class, to cancel touch-based scrolling for the current drag + cancelTouchScroll() { + if (this.isDragging) { + isWindowTouchMoveCancelled = true + } + } + + // Scrolling that simulates pointermoves + // ---------------------------------------------------------------------------------------------------- + + initScrollWatch(ev: PointerDragEvent) { + if (this.shouldWatchScroll) { + this.recordCoords(ev) + window.addEventListener('scroll', this.handleScroll, true) // useCapture=true + } + } + + recordCoords(ev: PointerDragEvent) { + if (this.shouldWatchScroll) { + this.prevPageX = (ev as any).pageX + this.prevPageY = (ev as any).pageY + this.prevScrollX = window.pageXOffset + this.prevScrollY = window.pageYOffset + } + } + + handleScroll = (ev: UIEvent) => { + if (!this.shouldIgnoreMove) { + let pageX = (window.pageXOffset - this.prevScrollX) + this.prevPageX + let pageY = (window.pageYOffset - this.prevScrollY) + this.prevPageY + + this.emitter.trigger('pointermove', { + origEvent: ev, + isTouch: this.isTouchDragging, + subjectEl: this.subjectEl, + pageX, + pageY, + deltaX: pageX - this.origPageX, + deltaY: pageY - this.origPageY, + } as PointerDragEvent) + } + } + + destroyScrollWatch() { + if (this.shouldWatchScroll) { + window.removeEventListener('scroll', this.handleScroll, true) // useCaptured=true + } + } + + // Event Normalization + // ---------------------------------------------------------------------------------------------------- + + createEventFromMouse(ev: MouseEvent, isFirst?: boolean): PointerDragEvent { + let deltaX = 0 + let deltaY = 0 + + // TODO: repeat code + if (isFirst) { + this.origPageX = ev.pageX + this.origPageY = ev.pageY + } else { + deltaX = ev.pageX - this.origPageX + deltaY = ev.pageY - this.origPageY + } + + return { + origEvent: ev, + isTouch: false, + subjectEl: this.subjectEl, + pageX: ev.pageX, + pageY: ev.pageY, + deltaX, + deltaY, + } + } + + createEventFromTouch(ev: TouchEvent, isFirst?: boolean): PointerDragEvent { + let touches = ev.touches + let pageX + let pageY + let deltaX = 0 + let deltaY = 0 + + // if touch coords available, prefer, + // because FF would give bad ev.pageX ev.pageY + if (touches && touches.length) { + pageX = touches[0].pageX + pageY = touches[0].pageY + } else { + pageX = (ev as any).pageX + pageY = (ev as any).pageY + } + + // TODO: repeat code + if (isFirst) { + this.origPageX = pageX + this.origPageY = pageY + } else { + deltaX = pageX - this.origPageX + deltaY = pageY - this.origPageY + } + + return { + origEvent: ev, + isTouch: true, + subjectEl: this.subjectEl, + pageX, + pageY, + deltaX, + deltaY, + } + } +} + +// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac) +function isPrimaryMouseButton(ev: MouseEvent) { + return ev.button === 0 && !ev.ctrlKey +} + +// Ignoring fake mouse events generated by touch +// ---------------------------------------------------------------------------------------------------- + +function startIgnoringMouse() { // can be made non-class function + ignoreMouseDepth += 1 + + setTimeout(() => { + ignoreMouseDepth -= 1 + }, config.touchMouseIgnoreWait) +} + +// We want to attach touchmove as early as possible for Safari +// ---------------------------------------------------------------------------------------------------- + +function listenerCreated() { + listenerCnt += 1 + + if (listenerCnt === 1) { + window.addEventListener('touchmove', onWindowTouchMove, { passive: false }) + } +} + +function listenerDestroyed() { + listenerCnt -= 1 + + if (!listenerCnt) { + window.removeEventListener('touchmove', onWindowTouchMove, { passive: false } as AddEventListenerOptions) + } +} + +function onWindowTouchMove(ev: UIEvent) { + if (isWindowTouchMoveCancelled) { + ev.preventDefault() + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts new file mode 100644 index 000000000..22aba108d --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalDraggable.ts @@ -0,0 +1,70 @@ +import { BASE_OPTION_DEFAULTS, PointerDragEvent } from '@fullcalendar/common' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' +import { ExternalElementDragging, DragMetaGenerator } from './ExternalElementDragging' + +export interface ExternalDraggableSettings { + eventData?: DragMetaGenerator + itemSelector?: string + minDistance?: number + longPressDelay?: number + appendTo?: HTMLElement +} + +/* +Makes an element (that is *external* to any calendar) draggable. +Can pass in data that determines how an event will be created when dropped onto a calendar. +Leverages FullCalendar's internal drag-n-drop functionality WITHOUT a third-party drag system. +*/ +export class ExternalDraggable { + dragging: FeaturefulElementDragging + settings: ExternalDraggableSettings + + constructor(el: HTMLElement, settings: ExternalDraggableSettings = {}) { + this.settings = settings + + let dragging = this.dragging = new FeaturefulElementDragging(el) + dragging.touchScrollAllowed = false + + if (settings.itemSelector != null) { + dragging.pointer.selector = settings.itemSelector + } + + if (settings.appendTo != null) { + dragging.mirror.parentNode = settings.appendTo // TODO: write tests + } + + dragging.emitter.on('pointerdown', this.handlePointerDown) + dragging.emitter.on('dragstart', this.handleDragStart) + + new ExternalElementDragging(dragging, settings.eventData) // eslint-disable-line no-new + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { dragging } = this + let { minDistance, longPressDelay } = this.settings + + dragging.minDistance = + minDistance != null ? + minDistance : + (ev.isTouch ? 0 : BASE_OPTION_DEFAULTS.eventDragMinDistance) + + dragging.delay = + ev.isTouch ? // TODO: eventually read eventLongPressDelay instead vvv + (longPressDelay != null ? longPressDelay : BASE_OPTION_DEFAULTS.longPressDelay) : + 0 + } + + handleDragStart = (ev: PointerDragEvent) => { + if ( + ev.isTouch && + this.dragging.delay && + (ev.subjectEl as HTMLElement).classList.contains('fc-event') + ) { + this.dragging.mirror.getMirrorEl().classList.add('fc-event-selected') + } + } + + destroy() { + this.dragging.destroy() + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts new file mode 100644 index 000000000..95ac7e0c2 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ExternalElementDragging.ts @@ -0,0 +1,268 @@ +import { + Hit, + interactionSettingsStore, + PointerDragEvent, + parseEventDef, createEventInstance, EventTuple, + createEmptyEventStore, eventTupleToStore, + config, + DateSpan, DatePointApi, + EventInteractionState, + DragMetaInput, DragMeta, parseDragMeta, + EventApi, + elementMatches, + enableCursor, disableCursor, + isInteractionValid, + ElementDragging, + ViewApi, + CalendarContext, + getDefaultEventEnd, + refineEventDef, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging } from '../interactions/HitDragging' +import { buildDatePointApiWithContext } from '../utils' + +export type DragMetaGenerator = DragMetaInput | ((el: HTMLElement) => DragMetaInput) + +export interface ExternalDropApi extends DatePointApi { + draggedEl: HTMLElement + jsEvent: UIEvent + view: ViewApi +} + +/* +Given an already instantiated draggable object for one-or-more elements, +Interprets any dragging as an attempt to drag an events that lives outside +of a calendar onto a calendar. +*/ +export class ExternalElementDragging { + hitDragging: HitDragging + receivingContext: CalendarContext | null = null + droppableEvent: EventTuple | null = null // will exist for all drags, even if create:false + suppliedDragMeta: DragMetaGenerator | null = null + dragMeta: DragMeta | null = null + + constructor(dragging: ElementDragging, suppliedDragMeta?: DragMetaGenerator) { + let hitDragging = this.hitDragging = new HitDragging(dragging, interactionSettingsStore) + hitDragging.requireInitial = false // will start outside of a component + hitDragging.emitter.on('dragstart', this.handleDragStart) + hitDragging.emitter.on('hitupdate', this.handleHitUpdate) + hitDragging.emitter.on('dragend', this.handleDragEnd) + + this.suppliedDragMeta = suppliedDragMeta + } + + handleDragStart = (ev: PointerDragEvent) => { + this.dragMeta = this.buildDragMeta(ev.subjectEl as HTMLElement) + } + + buildDragMeta(subjectEl: HTMLElement) { + if (typeof this.suppliedDragMeta === 'object') { + return parseDragMeta(this.suppliedDragMeta) + } + if (typeof this.suppliedDragMeta === 'function') { + return parseDragMeta(this.suppliedDragMeta(subjectEl)) + } + return getDragMetaFromEl(subjectEl) + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => { + let { dragging } = this.hitDragging + let receivingContext: CalendarContext | null = null + let droppableEvent: EventTuple | null = null + let isInvalid = false + let interaction: EventInteractionState = { + affectedEvents: createEmptyEventStore(), + mutatedEvents: createEmptyEventStore(), + isEvent: this.dragMeta!.create, + } + + if (hit) { + receivingContext = hit.context + + if (this.canDropElOnCalendar(ev.subjectEl as HTMLElement, receivingContext)) { + droppableEvent = computeEventForDateSpan( + hit.dateSpan, + this.dragMeta!, + receivingContext, + ) + + interaction.mutatedEvents = eventTupleToStore(droppableEvent) + isInvalid = !isInteractionValid(interaction, hit.dateProfile, receivingContext) + + if (isInvalid) { + interaction.mutatedEvents = createEmptyEventStore() + droppableEvent = null + } + } + } + + this.displayDrag(receivingContext, interaction) + + // show mirror if no already-rendered mirror element OR if we are shutting down the mirror (?) + // TODO: wish we could somehow wait for dispatch to guarantee render + dragging.setMirrorIsVisible( + isFinal || !droppableEvent || !document.querySelector('.fc-event-mirror'), // TODO: turn className into constant + // TODO: somehow query FullCalendars WITHIN shadow-roots for existing event-mirror els + ) + + if (!isInvalid) { + enableCursor() + } else { + disableCursor() + } + + if (!isFinal) { + dragging.setMirrorNeedsRevert(!droppableEvent) + + this.receivingContext = receivingContext + this.droppableEvent = droppableEvent + } + } + + handleDragEnd = (pev: PointerDragEvent) => { + let { receivingContext, droppableEvent } = this + + this.clearDrag() + + if (receivingContext && droppableEvent) { + let finalHit = this.hitDragging.finalHit! + let finalView = finalHit.context.viewApi + let dragMeta = this.dragMeta! + + receivingContext.emitter.trigger('drop', { + ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), + draggedEl: pev.subjectEl as HTMLElement, + jsEvent: pev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: finalView, + }) + + if (dragMeta.create) { + let addingEvents = eventTupleToStore(droppableEvent) + + receivingContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: addingEvents, + }) + + if (pev.isTouch) { + receivingContext.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: droppableEvent.instance.instanceId, + }) + } + + // signal that an external event landed + receivingContext.emitter.trigger('eventReceive', { + event: new EventApi( + receivingContext, + droppableEvent.def, + droppableEvent.instance, + ), + relatedEvents: [], + revert() { + receivingContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: addingEvents, + }) + }, + draggedEl: pev.subjectEl as HTMLElement, + view: finalView, + }) + } + } + + this.receivingContext = null + this.droppableEvent = null + } + + displayDrag(nextContext: CalendarContext | null, state: EventInteractionState) { + let prevContext = this.receivingContext + + if (prevContext && prevContext !== nextContext) { + prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + + if (nextContext) { + nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }) + } + } + + clearDrag() { + if (this.receivingContext) { + this.receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + } + + canDropElOnCalendar(el: HTMLElement, receivingContext: CalendarContext): boolean { + let dropAccept = receivingContext.options.dropAccept + + if (typeof dropAccept === 'function') { + return dropAccept.call(receivingContext.calendarApi, el) + } + + if (typeof dropAccept === 'string' && dropAccept) { + return Boolean(elementMatches(el, dropAccept)) + } + + return true + } +} + +// Utils for computing event store from the DragMeta +// ---------------------------------------------------------------------------------------------------- + +function computeEventForDateSpan(dateSpan: DateSpan, dragMeta: DragMeta, context: CalendarContext): EventTuple { + let defProps = { ...dragMeta.leftoverProps } + + for (let transform of context.pluginHooks.externalDefTransforms) { + __assign(defProps, transform(dateSpan, dragMeta)) + } + + let { refined, extra } = refineEventDef(defProps, context) + let def = parseEventDef( + refined, + extra, + dragMeta.sourceId, + dateSpan.allDay, + context.options.forceEventDuration || Boolean(dragMeta.duration), // hasEnd + context, + ) + + let start = dateSpan.range.start + + // only rely on time info if drop zone is all-day, + // otherwise, we already know the time + if (dateSpan.allDay && dragMeta.startTime) { + start = context.dateEnv.add(start, dragMeta.startTime) + } + + let end = dragMeta.duration ? + context.dateEnv.add(start, dragMeta.duration) : + getDefaultEventEnd(dateSpan.allDay, start, context) + + let instance = createEventInstance(def.defId, { start, end }) + + return { def, instance } +} + +// Utils for extracting data from element +// ---------------------------------------------------------------------------------------------------- + +function getDragMetaFromEl(el: HTMLElement): DragMeta { + let str = getEmbeddedElData(el, 'event') + let obj = str ? + JSON.parse(str) : + { create: false } // if no embedded data, assume no event creation + + return parseDragMeta(obj) +} + +config.dataAttrPrefix = '' + +function getEmbeddedElData(el: HTMLElement, name: string): string { + let prefix = config.dataAttrPrefix + let prefixedName = (prefix ? prefix + '-' : '') + name + + return el.getAttribute('data-' + prefixedName) || '' +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts new file mode 100644 index 000000000..ab03cec00 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/InferredElementDragging.ts @@ -0,0 +1,77 @@ +import { PointerDragEvent, ElementDragging } from '@fullcalendar/common' +import { PointerDragging } from '../dnd/PointerDragging' + +/* +Detects when a *THIRD-PARTY* drag-n-drop system interacts with elements. +The third-party system is responsible for drawing the visuals effects of the drag. +This class simply monitors for pointer movements and fires events. +It also has the ability to hide the moving element (the "mirror") during the drag. +*/ +export class InferredElementDragging extends ElementDragging { + pointer: PointerDragging + shouldIgnoreMove: boolean = false + mirrorSelector: string = '' + currentMirrorEl: HTMLElement | null = null + + constructor(containerEl: HTMLElement) { + super(containerEl) + + let pointer = this.pointer = new PointerDragging(containerEl) + pointer.emitter.on('pointerdown', this.handlePointerDown) + pointer.emitter.on('pointermove', this.handlePointerMove) + pointer.emitter.on('pointerup', this.handlePointerUp) + } + + destroy() { + this.pointer.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + this.emitter.trigger('pointerdown', ev) + + if (!this.shouldIgnoreMove) { + // fire dragstart right away. does not support delay or min-distance + this.emitter.trigger('dragstart', ev) + } + } + + handlePointerMove = (ev: PointerDragEvent) => { + if (!this.shouldIgnoreMove) { + this.emitter.trigger('dragmove', ev) + } + } + + handlePointerUp = (ev: PointerDragEvent) => { + this.emitter.trigger('pointerup', ev) + + if (!this.shouldIgnoreMove) { + // fire dragend right away. does not support a revert animation + this.emitter.trigger('dragend', ev) + } + } + + setIgnoreMove(bool: boolean) { + this.shouldIgnoreMove = bool + } + + setMirrorIsVisible(bool: boolean) { + if (bool) { + // restore a previously hidden element. + // use the reference in case the selector class has already been removed. + if (this.currentMirrorEl) { + this.currentMirrorEl.style.visibility = '' + this.currentMirrorEl = null + } + } else { + let mirrorEl = this.mirrorSelector + // TODO: somehow query FullCalendars WITHIN shadow-roots + ? document.querySelector(this.mirrorSelector) as HTMLElement + : null + + if (mirrorEl) { + this.currentMirrorEl = mirrorEl + mirrorEl.style.visibility = 'hidden' + } + } + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts new file mode 100644 index 000000000..324b22255 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions-external/ThirdPartyDraggable.ts @@ -0,0 +1,52 @@ +import { ExternalElementDragging, DragMetaGenerator } from './ExternalElementDragging' +import { InferredElementDragging } from './InferredElementDragging' + +export interface ThirdPartyDraggableSettings { + eventData?: DragMetaGenerator + itemSelector?: string + mirrorSelector?: string +} + +/* +Bridges third-party drag-n-drop systems with FullCalendar. +Must be instantiated and destroyed by caller. +*/ +export class ThirdPartyDraggable { + dragging: InferredElementDragging + + constructor( + containerOrSettings?: EventTarget | ThirdPartyDraggableSettings, + settings?: ThirdPartyDraggableSettings, + ) { + let containerEl: EventTarget = document + + if ( + // wish we could just test instanceof EventTarget, but doesn't work in IE11 + containerOrSettings === document || + containerOrSettings instanceof Element + ) { + containerEl = containerOrSettings as EventTarget + settings = settings || {} + } else { + settings = (containerOrSettings || {}) as ThirdPartyDraggableSettings + } + + let dragging = this.dragging = new InferredElementDragging(containerEl as HTMLElement) + + if (typeof settings.itemSelector === 'string') { + dragging.pointer.selector = settings.itemSelector + } else if (containerEl === document) { + dragging.pointer.selector = '[data-event]' + } + + if (typeof settings.mirrorSelector === 'string') { + dragging.mirrorSelector = settings.mirrorSelector + } + + new ExternalElementDragging(dragging, settings.eventData) // eslint-disable-line no-new + } + + destroy() { + this.dragging.destroy() + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions/DateClicking.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions/DateClicking.ts new file mode 100644 index 000000000..06b352de9 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions/DateClicking.ts @@ -0,0 +1,71 @@ +import { + PointerDragEvent, Interaction, InteractionSettings, interactionSettingsToStore, + DatePointApi, + ViewApi, +} from '@fullcalendar/common' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' +import { HitDragging, isHitsEqual } from './HitDragging' +import { buildDatePointApiWithContext } from '../utils' + +export interface DateClickArg extends DatePointApi { + dayEl: HTMLElement + jsEvent: MouseEvent + view: ViewApi +} + +/* +Monitors when the user clicks on a specific date/time of a component. +A pointerdown+pointerup on the same "hit" constitutes a click. +*/ +export class DateClicking extends Interaction { + dragging: FeaturefulElementDragging + hitDragging: HitDragging + + constructor(settings: InteractionSettings) { + super(settings) + + // we DO want to watch pointer moves because otherwise finalHit won't get populated + this.dragging = new FeaturefulElementDragging(settings.el) + this.dragging.autoScroller.isEnabled = false + + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)) + hitDragging.emitter.on('pointerdown', this.handlePointerDown) + hitDragging.emitter.on('dragend', this.handleDragEnd) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (pev: PointerDragEvent) => { + let { dragging } = this + let downEl = pev.origEvent.target as HTMLElement + + // do this in pointerdown (not dragend) because DOM might be mutated by the time dragend is fired + dragging.setIgnoreMove( + !this.component.isValidDateDownEl(downEl), + ) + } + + // won't even fire if moving was ignored + handleDragEnd = (ev: PointerDragEvent) => { + let { component } = this + let { pointer } = this.dragging + + if (!pointer.wasTouchScroll) { + let { initialHit, finalHit } = this.hitDragging + + if (initialHit && finalHit && isHitsEqual(initialHit, finalHit)) { + let { context } = component + let arg: DateClickArg = { + ...buildDatePointApiWithContext(initialHit.dateSpan, context), + dayEl: initialHit.dayEl, + jsEvent: ev.origEvent as MouseEvent, + view: context.viewApi || context.calendarApi.view, + } + + context.emitter.trigger('dateClick', arg) + } + } + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions/DateSelecting.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions/DateSelecting.ts new file mode 100644 index 000000000..fa6b3ff09 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions/DateSelecting.ts @@ -0,0 +1,152 @@ +import { + compareNumbers, enableCursor, disableCursor, DateComponent, Hit, + DateSpan, PointerDragEvent, dateSelectionJoinTransformer, + Interaction, InteractionSettings, interactionSettingsToStore, + triggerDateSelect, isDateSelectionValid, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging } from './HitDragging' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' + +/* +Tracks when the user selects a portion of time of a component, +constituted by a drag over date cells, with a possible delay at the beginning of the drag. +*/ +export class DateSelecting extends Interaction { + dragging: FeaturefulElementDragging + hitDragging: HitDragging + dragSelection: DateSpan | null = null + + constructor(settings: InteractionSettings) { + super(settings) + let { component } = settings + let { options } = component.context + + let dragging = this.dragging = new FeaturefulElementDragging(settings.el) + dragging.touchScrollAllowed = false + dragging.minDistance = options.selectMinDistance || 0 + dragging.autoScroller.isEnabled = options.dragScroll + + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)) + hitDragging.emitter.on('pointerdown', this.handlePointerDown) + hitDragging.emitter.on('dragstart', this.handleDragStart) + hitDragging.emitter.on('hitupdate', this.handleHitUpdate) + hitDragging.emitter.on('pointerup', this.handlePointerUp) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { component, dragging } = this + let { options } = component.context + + let canSelect = options.selectable && + component.isValidDateDownEl(ev.origEvent.target as HTMLElement) + + // don't bother to watch expensive moves if component won't do selection + dragging.setIgnoreMove(!canSelect) + + // if touch, require user to hold down + dragging.delay = ev.isTouch ? getComponentTouchDelay(component) : null + } + + handleDragStart = (ev: PointerDragEvent) => { + this.component.context.calendarApi.unselect(ev) // unselect previous selections + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean) => { + let { context } = this.component + let dragSelection: DateSpan | null = null + let isInvalid = false + + if (hit) { + let initialHit = this.hitDragging.initialHit! + let disallowed = hit.componentId === initialHit.componentId + && this.isHitComboAllowed + && !this.isHitComboAllowed(initialHit, hit) + + if (!disallowed) { + dragSelection = joinHitsIntoSelection( + initialHit, + hit, + context.pluginHooks.dateSelectionTransformers, + ) + } + + if (!dragSelection || !isDateSelectionValid(dragSelection, hit.dateProfile, context)) { + isInvalid = true + dragSelection = null + } + } + + if (dragSelection) { + context.dispatch({ type: 'SELECT_DATES', selection: dragSelection }) + } else if (!isFinal) { // only unselect if moved away while dragging + context.dispatch({ type: 'UNSELECT_DATES' }) + } + + if (!isInvalid) { + enableCursor() + } else { + disableCursor() + } + + if (!isFinal) { + this.dragSelection = dragSelection // only clear if moved away from all hits while dragging + } + } + + handlePointerUp = (pev: PointerDragEvent) => { + if (this.dragSelection) { + // selection is already rendered, so just need to report selection + triggerDateSelect(this.dragSelection, pev, this.component.context) + + this.dragSelection = null + } + } +} + +function getComponentTouchDelay(component: DateComponent): number { + let { options } = component.context + let delay = options.selectLongPressDelay + + if (delay == null) { + delay = options.longPressDelay + } + + return delay +} + +function joinHitsIntoSelection(hit0: Hit, hit1: Hit, dateSelectionTransformers: dateSelectionJoinTransformer[]): DateSpan { + let dateSpan0 = hit0.dateSpan + let dateSpan1 = hit1.dateSpan + let ms = [ + dateSpan0.range.start, + dateSpan0.range.end, + dateSpan1.range.start, + dateSpan1.range.end, + ] + + ms.sort(compareNumbers) + + let props = {} as DateSpan + + for (let transformer of dateSelectionTransformers) { + let res = transformer(hit0, hit1) + + if (res === false) { + return null + } + + if (res) { + __assign(props, res) + } + } + + props.range = { start: ms[0], end: ms[3] } + props.allDay = dateSpan0.allDay + + return props +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions/EventDragging.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions/EventDragging.ts new file mode 100644 index 000000000..9a9ecfc49 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions/EventDragging.ts @@ -0,0 +1,482 @@ +import { + DateComponent, Seg, + PointerDragEvent, Hit, + EventMutation, applyMutationToEventStore, + startOfDay, + elementClosest, + EventStore, getRelevantEvents, createEmptyEventStore, + EventInteractionState, + diffDates, enableCursor, disableCursor, + EventRenderRange, getElSeg, + EventApi, + eventDragMutationMassager, + Interaction, InteractionSettings, interactionSettingsStore, + EventDropTransformers, + CalendarContext, + ViewApi, + EventChangeArg, + buildEventApis, + EventAddArg, + EventRemoveArg, + isInteractionValid, + getElRoot, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging, isHitsEqual } from './HitDragging' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' +import { buildDatePointApiWithContext } from '../utils' + +export type EventDragStopArg = EventDragArg +export type EventDragStartArg = EventDragArg + +export interface EventDragArg { + el: HTMLElement + event: EventApi + jsEvent: MouseEvent + view: ViewApi +} + +export class EventDragging extends Interaction { // TODO: rename to EventSelectingAndDragging + // TODO: test this in IE11 + // QUESTION: why do we need it on the resizable??? + static SELECTOR = '.fc-event-draggable, .fc-event-resizable' + + dragging: FeaturefulElementDragging + hitDragging: HitDragging + + // internal state + subjectEl: HTMLElement | null = null + subjectSeg: Seg | null = null // the seg being selected/dragged + isDragging: boolean = false + eventRange: EventRenderRange | null = null + relevantEvents: EventStore | null = null // the events being dragged + receivingContext: CalendarContext | null = null + validMutation: EventMutation | null = null + mutatedRelevantEvents: EventStore | null = null + + constructor(settings: InteractionSettings) { + super(settings) + let { component } = this + let { options } = component.context + + let dragging = this.dragging = new FeaturefulElementDragging(settings.el) + dragging.pointer.selector = EventDragging.SELECTOR + dragging.touchScrollAllowed = false + dragging.autoScroller.isEnabled = options.dragScroll + + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsStore) + hitDragging.useSubjectCenter = settings.useEventCenter + hitDragging.emitter.on('pointerdown', this.handlePointerDown) + hitDragging.emitter.on('dragstart', this.handleDragStart) + hitDragging.emitter.on('hitupdate', this.handleHitUpdate) + hitDragging.emitter.on('pointerup', this.handlePointerUp) + hitDragging.emitter.on('dragend', this.handleDragEnd) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let origTarget = ev.origEvent.target as HTMLElement + let { component, dragging } = this + let { mirror } = dragging + let { options } = component.context + let initialContext = component.context + this.subjectEl = ev.subjectEl as HTMLElement + let subjectSeg = this.subjectSeg = getElSeg(ev.subjectEl as HTMLElement)! + let eventRange = this.eventRange = subjectSeg.eventRange! + let eventInstanceId = eventRange.instance!.instanceId + + this.relevantEvents = getRelevantEvents( + initialContext.getCurrentData().eventStore, + eventInstanceId, + ) + + dragging.minDistance = ev.isTouch ? 0 : options.eventDragMinDistance + dragging.delay = + // only do a touch delay if touch and this event hasn't been selected yet + (ev.isTouch && eventInstanceId !== component.props.eventSelection) ? + getComponentTouchDelay(component) : + null + + if (options.fixedMirrorParent) { + mirror.parentNode = options.fixedMirrorParent + } else { + mirror.parentNode = elementClosest(origTarget, '.fc') + } + + mirror.revertDuration = options.dragRevertDuration + + let isValid = + component.isValidSegDownEl(origTarget) && + !elementClosest(origTarget, '.fc-event-resizer') // NOT on a resizer + + dragging.setIgnoreMove(!isValid) + + // disable dragging for elements that are resizable (ie, selectable) + // but are not draggable + this.isDragging = isValid && + (ev.subjectEl as HTMLElement).classList.contains('fc-event-draggable') + } + + handleDragStart = (ev: PointerDragEvent) => { + let initialContext = this.component.context + let eventRange = this.eventRange! + let eventInstanceId = eventRange.instance.instanceId + + if (ev.isTouch) { + // need to select a different event? + if (eventInstanceId !== this.component.props.eventSelection) { + initialContext.dispatch({ type: 'SELECT_EVENT', eventInstanceId }) + } + } else { + // if now using mouse, but was previous touch interaction, clear selected event + initialContext.dispatch({ type: 'UNSELECT_EVENT' }) + } + + if (this.isDragging) { + initialContext.calendarApi.unselect(ev) // unselect *date* selection + initialContext.emitter.trigger('eventDragStart', { + el: this.subjectEl, + event: new EventApi(initialContext, eventRange.def, eventRange.instance), + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: initialContext.viewApi, + } as EventDragStartArg) + } + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean) => { + if (!this.isDragging) { + return + } + + let relevantEvents = this.relevantEvents! + let initialHit = this.hitDragging.initialHit! + let initialContext = this.component.context + + // states based on new hit + let receivingContext: CalendarContext | null = null + let mutation: EventMutation | null = null + let mutatedRelevantEvents: EventStore | null = null + let isInvalid = false + let interaction: EventInteractionState = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + } + + if (hit) { + receivingContext = hit.context + let receivingOptions = receivingContext.options + + if ( + initialContext === receivingContext || + (receivingOptions.editable && receivingOptions.droppable) + ) { + mutation = computeEventMutation(initialHit, hit, receivingContext.getCurrentData().pluginHooks.eventDragMutationMassagers) + + if (mutation) { + mutatedRelevantEvents = applyMutationToEventStore( + relevantEvents, + receivingContext.getCurrentData().eventUiBases, + mutation, + receivingContext, + ) + interaction.mutatedEvents = mutatedRelevantEvents + + if (!isInteractionValid(interaction, hit.dateProfile, receivingContext)) { + isInvalid = true + mutation = null + mutatedRelevantEvents = null + interaction.mutatedEvents = createEmptyEventStore() + } + } + } else { + receivingContext = null + } + } + + this.displayDrag(receivingContext, interaction) + + if (!isInvalid) { + enableCursor() + } else { + disableCursor() + } + + if (!isFinal) { + if ( + initialContext === receivingContext && // TODO: write test for this + isHitsEqual(initialHit, hit) + ) { + mutation = null + } + + this.dragging.setMirrorNeedsRevert(!mutation) + + // render the mirror if no already-rendered mirror + // TODO: wish we could somehow wait for dispatch to guarantee render + this.dragging.setMirrorIsVisible( + !hit || !getElRoot(this.subjectEl).querySelector('.fc-event-mirror'), // TODO: turn className into constant + ) + + // assign states based on new hit + this.receivingContext = receivingContext + this.validMutation = mutation + this.mutatedRelevantEvents = mutatedRelevantEvents + } + } + + handlePointerUp = () => { + if (!this.isDragging) { + this.cleanup() // because handleDragEnd won't fire + } + } + + handleDragEnd = (ev: PointerDragEvent) => { + if (this.isDragging) { + let initialContext = this.component.context + let initialView = initialContext.viewApi + let { receivingContext, validMutation } = this + let eventDef = this.eventRange!.def + let eventInstance = this.eventRange!.instance + let eventApi = new EventApi(initialContext, eventDef, eventInstance) + let relevantEvents = this.relevantEvents! + let mutatedRelevantEvents = this.mutatedRelevantEvents! + let { finalHit } = this.hitDragging + + this.clearDrag() // must happen after revert animation + + initialContext.emitter.trigger('eventDragStop', { + el: this.subjectEl, + event: eventApi, + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: initialView, + } as EventDragStopArg) + + if (validMutation) { + // dropped within same calendar + if (receivingContext === initialContext) { + let updatedEventApi = new EventApi( + initialContext, + mutatedRelevantEvents.defs[eventDef.defId], + eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null, + ) + + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + + let eventChangeArg: EventChangeArg = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, initialContext, eventInstance), + revert() { + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, // the pre-change data + }) + }, + } + + let transformed: ReturnType = {} + for (let transformer of initialContext.getCurrentData().pluginHooks.eventDropTransformers) { + __assign(transformed, transformer(validMutation, initialContext)) + } + + initialContext.emitter.trigger('eventDrop', { + ...eventChangeArg, + ...transformed, + el: ev.subjectEl as HTMLElement, + delta: validMutation.datesDelta!, + jsEvent: ev.origEvent as MouseEvent, // bad + view: initialView, + }) + + initialContext.emitter.trigger('eventChange', eventChangeArg) + + // dropped in different calendar + } else if (receivingContext) { + let eventRemoveArg: EventRemoveArg = { + event: eventApi, + relatedEvents: buildEventApis(relevantEvents, initialContext, eventInstance), + revert() { + initialContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, + }) + }, + } + + initialContext.emitter.trigger('eventLeave', { + ...eventRemoveArg, + draggedEl: ev.subjectEl as HTMLElement, + view: initialView, + }) + + initialContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: relevantEvents, + }) + + initialContext.emitter.trigger('eventRemove', eventRemoveArg) + + let addedEventDef = mutatedRelevantEvents.defs[eventDef.defId] + let addedEventInstance = mutatedRelevantEvents.instances[eventInstance.instanceId] + let addedEventApi = new EventApi(receivingContext, addedEventDef, addedEventInstance) + + receivingContext.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + + let eventAddArg: EventAddArg = { + event: addedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, receivingContext, addedEventInstance), + revert() { + receivingContext.dispatch({ + type: 'REMOVE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + }, + } + + receivingContext.emitter.trigger('eventAdd', eventAddArg) + + if (ev.isTouch) { + receivingContext.dispatch({ + type: 'SELECT_EVENT', + eventInstanceId: eventInstance.instanceId, + }) + } + + receivingContext.emitter.trigger('drop', { + ...buildDatePointApiWithContext(finalHit.dateSpan, receivingContext), + draggedEl: ev.subjectEl as HTMLElement, + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: finalHit.context.viewApi, + }) + + receivingContext.emitter.trigger('eventReceive', { + ...eventAddArg, + draggedEl: ev.subjectEl as HTMLElement, + view: finalHit.context.viewApi, + }) + } + } else { + initialContext.emitter.trigger('_noEventDrop') + } + } + + this.cleanup() + } + + // render a drag state on the next receivingCalendar + displayDrag(nextContext: CalendarContext | null, state: EventInteractionState) { + let initialContext = this.component.context + let prevContext = this.receivingContext + + // does the previous calendar need to be cleared? + if (prevContext && prevContext !== nextContext) { + // does the initial calendar need to be cleared? + // if so, don't clear all the way. we still need to to hide the affectedEvents + if (prevContext === initialContext) { + prevContext.dispatch({ + type: 'SET_EVENT_DRAG', + state: { + affectedEvents: state.affectedEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + }, + }) + + // completely clear the old calendar if it wasn't the initial + } else { + prevContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + } + + if (nextContext) { + nextContext.dispatch({ type: 'SET_EVENT_DRAG', state }) + } + } + + clearDrag() { + let initialCalendar = this.component.context + let { receivingContext } = this + + if (receivingContext) { + receivingContext.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + + // the initial calendar might have an dummy drag state from displayDrag + if (initialCalendar !== receivingContext) { + initialCalendar.dispatch({ type: 'UNSET_EVENT_DRAG' }) + } + } + + cleanup() { // reset all internal state + this.subjectSeg = null + this.isDragging = false + this.eventRange = null + this.relevantEvents = null + this.receivingContext = null + this.validMutation = null + this.mutatedRelevantEvents = null + } +} + +function computeEventMutation(hit0: Hit, hit1: Hit, massagers: eventDragMutationMassager[]): EventMutation { + let dateSpan0 = hit0.dateSpan + let dateSpan1 = hit1.dateSpan + let date0 = dateSpan0.range.start + let date1 = dateSpan1.range.start + let standardProps = {} as any + + if (dateSpan0.allDay !== dateSpan1.allDay) { + standardProps.allDay = dateSpan1.allDay + standardProps.hasEnd = hit1.context.options.allDayMaintainDuration + + if (dateSpan1.allDay) { + // means date1 is already start-of-day, + // but date0 needs to be converted + date0 = startOfDay(date0) + } + } + + let delta = diffDates( + date0, date1, + hit0.context.dateEnv, + hit0.componentId === hit1.componentId ? + hit0.largeUnit : + null, + ) + + if (delta.milliseconds) { // has hours/minutes/seconds + standardProps.allDay = false + } + + let mutation: EventMutation = { + datesDelta: delta, + standardProps, + } + + for (let massager of massagers) { + massager(mutation, hit0, hit1) + } + + return mutation +} + +function getComponentTouchDelay(component: DateComponent): number | null { + let { options } = component.context + let delay = options.eventLongPressDelay + + if (delay == null) { + delay = options.longPressDelay + } + + return delay +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions/EventResizing.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions/EventResizing.ts new file mode 100644 index 000000000..013b399ae --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions/EventResizing.ts @@ -0,0 +1,263 @@ +import { + Seg, Hit, + EventMutation, applyMutationToEventStore, + elementClosest, + PointerDragEvent, + EventStore, getRelevantEvents, createEmptyEventStore, + diffDates, enableCursor, disableCursor, + DateRange, + EventApi, + EventRenderRange, getElSeg, + createDuration, + EventInteractionState, + Interaction, InteractionSettings, interactionSettingsToStore, ViewApi, Duration, EventChangeArg, buildEventApis, isInteractionValid, +} from '@fullcalendar/common' +import { __assign } from 'tslib' +import { HitDragging, isHitsEqual } from './HitDragging' +import { FeaturefulElementDragging } from '../dnd/FeaturefulElementDragging' + +export type EventResizeStartArg = EventResizeStartStopArg +export type EventResizeStopArg = EventResizeStartStopArg + +export interface EventResizeStartStopArg { + el: HTMLElement + event: EventApi + jsEvent: MouseEvent + view: ViewApi +} + +export interface EventResizeDoneArg extends EventChangeArg { + el: HTMLElement + startDelta: Duration + endDelta: Duration + jsEvent: MouseEvent + view: ViewApi +} + +export class EventResizing extends Interaction { + dragging: FeaturefulElementDragging + hitDragging: HitDragging + + // internal state + draggingSegEl: HTMLElement | null = null + draggingSeg: Seg | null = null // TODO: rename to resizingSeg? subjectSeg? + eventRange: EventRenderRange | null = null + relevantEvents: EventStore | null = null + validMutation: EventMutation | null = null + mutatedRelevantEvents: EventStore | null = null + + constructor(settings: InteractionSettings) { + super(settings) + let { component } = settings + + let dragging = this.dragging = new FeaturefulElementDragging(settings.el) + dragging.pointer.selector = '.fc-event-resizer' + dragging.touchScrollAllowed = false + dragging.autoScroller.isEnabled = component.context.options.dragScroll + + let hitDragging = this.hitDragging = new HitDragging(this.dragging, interactionSettingsToStore(settings)) + hitDragging.emitter.on('pointerdown', this.handlePointerDown) + hitDragging.emitter.on('dragstart', this.handleDragStart) + hitDragging.emitter.on('hitupdate', this.handleHitUpdate) + hitDragging.emitter.on('dragend', this.handleDragEnd) + } + + destroy() { + this.dragging.destroy() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { component } = this + let segEl = this.querySegEl(ev) + let seg = getElSeg(segEl) + let eventRange = this.eventRange = seg.eventRange! + + this.dragging.minDistance = component.context.options.eventDragMinDistance + + // if touch, need to be working with a selected event + this.dragging.setIgnoreMove( + !this.component.isValidSegDownEl(ev.origEvent.target as HTMLElement) || + (ev.isTouch && this.component.props.eventSelection !== eventRange.instance!.instanceId), + ) + } + + handleDragStart = (ev: PointerDragEvent) => { + let { context } = this.component + let eventRange = this.eventRange! + + this.relevantEvents = getRelevantEvents( + context.getCurrentData().eventStore, + this.eventRange.instance!.instanceId, + ) + + let segEl = this.querySegEl(ev) + this.draggingSegEl = segEl + this.draggingSeg = getElSeg(segEl) + + context.calendarApi.unselect() + context.emitter.trigger('eventResizeStart', { + el: segEl, + event: new EventApi(context, eventRange.def, eventRange.instance), + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: context.viewApi, + } as EventResizeStartArg) + } + + handleHitUpdate = (hit: Hit | null, isFinal: boolean, ev: PointerDragEvent) => { + let { context } = this.component + let relevantEvents = this.relevantEvents! + let initialHit = this.hitDragging.initialHit! + let eventInstance = this.eventRange.instance! + let mutation: EventMutation | null = null + let mutatedRelevantEvents: EventStore | null = null + let isInvalid = false + let interaction: EventInteractionState = { + affectedEvents: relevantEvents, + mutatedEvents: createEmptyEventStore(), + isEvent: true, + } + + if (hit) { + let disallowed = hit.componentId === initialHit.componentId + && this.isHitComboAllowed + && !this.isHitComboAllowed(initialHit, hit) + + if (!disallowed) { + mutation = computeMutation( + initialHit, + hit, + (ev.subjectEl as HTMLElement).classList.contains('fc-event-resizer-start'), + eventInstance.range, + ) + } + } + + if (mutation) { + mutatedRelevantEvents = applyMutationToEventStore(relevantEvents, context.getCurrentData().eventUiBases, mutation, context) + interaction.mutatedEvents = mutatedRelevantEvents + + if (!isInteractionValid(interaction, hit.dateProfile, context)) { + isInvalid = true + mutation = null + mutatedRelevantEvents = null + interaction.mutatedEvents = null + } + } + + if (mutatedRelevantEvents) { + context.dispatch({ + type: 'SET_EVENT_RESIZE', + state: interaction, + }) + } else { + context.dispatch({ type: 'UNSET_EVENT_RESIZE' }) + } + + if (!isInvalid) { + enableCursor() + } else { + disableCursor() + } + + if (!isFinal) { + if (mutation && isHitsEqual(initialHit, hit)) { + mutation = null + } + + this.validMutation = mutation + this.mutatedRelevantEvents = mutatedRelevantEvents + } + } + + handleDragEnd = (ev: PointerDragEvent) => { + let { context } = this.component + let eventDef = this.eventRange!.def + let eventInstance = this.eventRange!.instance + let eventApi = new EventApi(context, eventDef, eventInstance) + let relevantEvents = this.relevantEvents! + let mutatedRelevantEvents = this.mutatedRelevantEvents! + + context.emitter.trigger('eventResizeStop', { + el: this.draggingSegEl, + event: eventApi, + jsEvent: ev.origEvent as MouseEvent, // Is this always a mouse event? See #4655 + view: context.viewApi, + } as EventResizeStopArg) + + if (this.validMutation) { + let updatedEventApi = new EventApi( + context, + mutatedRelevantEvents.defs[eventDef.defId], + eventInstance ? mutatedRelevantEvents.instances[eventInstance.instanceId] : null, + ) + + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: mutatedRelevantEvents, + }) + + let eventChangeArg: EventChangeArg = { + oldEvent: eventApi, + event: updatedEventApi, + relatedEvents: buildEventApis(mutatedRelevantEvents, context, eventInstance), + revert() { + context.dispatch({ + type: 'MERGE_EVENTS', + eventStore: relevantEvents, // the pre-change events + }) + }, + } + + context.emitter.trigger('eventResize', { + ...eventChangeArg, + el: this.draggingSegEl, + startDelta: this.validMutation.startDelta || createDuration(0), + endDelta: this.validMutation.endDelta || createDuration(0), + jsEvent: ev.origEvent as MouseEvent, + view: context.viewApi, + }) + + context.emitter.trigger('eventChange', eventChangeArg) + } else { + context.emitter.trigger('_noEventResize') + } + + // reset all internal state + this.draggingSeg = null + this.relevantEvents = null + this.validMutation = null + + // okay to keep eventInstance around. useful to set it in handlePointerDown + } + + querySegEl(ev: PointerDragEvent) { + return elementClosest(ev.subjectEl as HTMLElement, '.fc-event') + } +} + +function computeMutation( + hit0: Hit, + hit1: Hit, + isFromStart: boolean, + instanceRange: DateRange, +): EventMutation | null { + let dateEnv = hit0.context.dateEnv + let date0 = hit0.dateSpan.range.start + let date1 = hit1.dateSpan.range.start + + let delta = diffDates( + date0, date1, + dateEnv, + hit0.largeUnit, + ) + + if (isFromStart) { + if (dateEnv.add(instanceRange.start, delta) < instanceRange.end) { + return { startDelta: delta } + } + } else if (dateEnv.add(instanceRange.end, delta) > instanceRange.start) { + return { endDelta: delta } + } + + return null +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions/HitDragging.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions/HitDragging.ts new file mode 100644 index 000000000..d0a329e9c --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions/HitDragging.ts @@ -0,0 +1,220 @@ +import { + Emitter, PointerDragEvent, + isDateSpansEqual, + computeRect, + constrainPoint, intersectRects, getRectCenter, diffPoints, Point, + rangeContainsRange, + Hit, + InteractionSettingsStore, + mapHash, + ElementDragging, +} from '@fullcalendar/common' +import { OffsetTracker } from '../OffsetTracker' + +/* +Tracks movement over multiple droppable areas (aka "hits") +that exist in one or more DateComponents. +Relies on an existing draggable. + +emits: +- pointerdown +- dragstart +- hitchange - fires initially, even if not over a hit +- pointerup +- (hitchange - again, to null, if ended over a hit) +- dragend +*/ +export class HitDragging { + droppableStore: InteractionSettingsStore + dragging: ElementDragging + emitter: Emitter + + // options that can be set by caller + useSubjectCenter: boolean = false + requireInitial: boolean = true // if doesn't start out on a hit, won't emit any events + + // internal state + offsetTrackers: { [componentUid: string]: OffsetTracker } + initialHit: Hit | null = null + movingHit: Hit | null = null + finalHit: Hit | null = null // won't ever be populated if shouldIgnoreMove + coordAdjust?: Point + + constructor(dragging: ElementDragging, droppableStore: InteractionSettingsStore) { + this.droppableStore = droppableStore + + dragging.emitter.on('pointerdown', this.handlePointerDown) + dragging.emitter.on('dragstart', this.handleDragStart) + dragging.emitter.on('dragmove', this.handleDragMove) + dragging.emitter.on('pointerup', this.handlePointerUp) + dragging.emitter.on('dragend', this.handleDragEnd) + + this.dragging = dragging + this.emitter = new Emitter() + } + + handlePointerDown = (ev: PointerDragEvent) => { + let { dragging } = this + + this.initialHit = null + this.movingHit = null + this.finalHit = null + + this.prepareHits() + this.processFirstCoord(ev) + + if (this.initialHit || !this.requireInitial) { + dragging.setIgnoreMove(false) + + // TODO: fire this before computing processFirstCoord, so listeners can cancel. this gets fired by almost every handler :( + this.emitter.trigger('pointerdown', ev) + } else { + dragging.setIgnoreMove(true) + } + } + + // sets initialHit + // sets coordAdjust + processFirstCoord(ev: PointerDragEvent) { + let origPoint = { left: ev.pageX, top: ev.pageY } + let adjustedPoint = origPoint + let subjectEl = ev.subjectEl + let subjectRect + + if (subjectEl instanceof HTMLElement) { // i.e. not a Document/ShadowRoot + subjectRect = computeRect(subjectEl) + adjustedPoint = constrainPoint(adjustedPoint, subjectRect) + } + + let initialHit = this.initialHit = this.queryHitForOffset(adjustedPoint.left, adjustedPoint.top) + if (initialHit) { + if (this.useSubjectCenter && subjectRect) { + let slicedSubjectRect = intersectRects(subjectRect, initialHit.rect) + if (slicedSubjectRect) { + adjustedPoint = getRectCenter(slicedSubjectRect) + } + } + + this.coordAdjust = diffPoints(adjustedPoint, origPoint) + } else { + this.coordAdjust = { left: 0, top: 0 } + } + } + + handleDragStart = (ev: PointerDragEvent) => { + this.emitter.trigger('dragstart', ev) + this.handleMove(ev, true) // force = fire even if initially null + } + + handleDragMove = (ev: PointerDragEvent) => { + this.emitter.trigger('dragmove', ev) + this.handleMove(ev) + } + + handlePointerUp = (ev: PointerDragEvent) => { + this.releaseHits() + this.emitter.trigger('pointerup', ev) + } + + handleDragEnd = (ev: PointerDragEvent) => { + if (this.movingHit) { + this.emitter.trigger('hitupdate', null, true, ev) + } + + this.finalHit = this.movingHit + this.movingHit = null + this.emitter.trigger('dragend', ev) + } + + handleMove(ev: PointerDragEvent, forceHandle?: boolean) { + let hit = this.queryHitForOffset( + ev.pageX + this.coordAdjust!.left, + ev.pageY + this.coordAdjust!.top, + ) + + if (forceHandle || !isHitsEqual(this.movingHit, hit)) { + this.movingHit = hit + this.emitter.trigger('hitupdate', hit, false, ev) + } + } + + prepareHits() { + this.offsetTrackers = mapHash(this.droppableStore, (interactionSettings) => { + interactionSettings.component.prepareHits() + return new OffsetTracker(interactionSettings.el) + }) + } + + releaseHits() { + let { offsetTrackers } = this + + for (let id in offsetTrackers) { + offsetTrackers[id].destroy() + } + + this.offsetTrackers = {} + } + + queryHitForOffset(offsetLeft: number, offsetTop: number): Hit | null { + let { droppableStore, offsetTrackers } = this + let bestHit: Hit | null = null + + for (let id in droppableStore) { + let component = droppableStore[id].component + let offsetTracker = offsetTrackers[id] + + if ( + offsetTracker && // wasn't destroyed mid-drag + offsetTracker.isWithinClipping(offsetLeft, offsetTop) + ) { + let originLeft = offsetTracker.computeLeft() + let originTop = offsetTracker.computeTop() + let positionLeft = offsetLeft - originLeft + let positionTop = offsetTop - originTop + let { origRect } = offsetTracker + let width = origRect.right - origRect.left + let height = origRect.bottom - origRect.top + + if ( + // must be within the element's bounds + positionLeft >= 0 && positionLeft < width && + positionTop >= 0 && positionTop < height + ) { + let hit = component.queryHit(positionLeft, positionTop, width, height) + if ( + hit && ( + // make sure the hit is within activeRange, meaning it's not a dead cell + rangeContainsRange(hit.dateProfile.activeRange, hit.dateSpan.range) + ) && + (!bestHit || hit.layer > bestHit.layer) + ) { + hit.componentId = id + hit.context = component.context + + // TODO: better way to re-orient rectangle + hit.rect.left += originLeft + hit.rect.right += originLeft + hit.rect.top += originTop + hit.rect.bottom += originTop + + bestHit = hit + } + } + } + } + + return bestHit + } +} + +export function isHitsEqual(hit0: Hit | null, hit1: Hit | null): boolean { + if (!hit0 && !hit1) { + return true + } + + if (Boolean(hit0) !== Boolean(hit1)) { + return false + } + + return isDateSpansEqual(hit0!.dateSpan, hit1!.dateSpan) +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/interactions/UnselectAuto.ts b/apps/schoolCalender/fullcalendar/interaction/src/interactions/UnselectAuto.ts new file mode 100644 index 000000000..23e5b47cb --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/interactions/UnselectAuto.ts @@ -0,0 +1,77 @@ +import { + DateSelectionApi, + PointerDragEvent, + elementClosest, + CalendarContext, + getEventTargetViaRoot, +} from '@fullcalendar/common' +import { PointerDragging } from '../dnd/PointerDragging' +import { EventDragging } from './EventDragging' + +export class UnselectAuto { + documentPointer: PointerDragging // for unfocusing + isRecentPointerDateSelect = false // wish we could use a selector to detect date selection, but uses hit system + matchesCancel = false + matchesEvent = false + + constructor(private context: CalendarContext) { + let documentPointer = this.documentPointer = new PointerDragging(document) + documentPointer.shouldIgnoreMove = true + documentPointer.shouldWatchScroll = false + documentPointer.emitter.on('pointerdown', this.onDocumentPointerDown) + documentPointer.emitter.on('pointerup', this.onDocumentPointerUp) + + /* + TODO: better way to know about whether there was a selection with the pointer + */ + context.emitter.on('select', this.onSelect) + } + + destroy() { + this.context.emitter.off('select', this.onSelect) + this.documentPointer.destroy() + } + + onSelect = (selectInfo: DateSelectionApi) => { + if (selectInfo.jsEvent) { + this.isRecentPointerDateSelect = true + } + } + + onDocumentPointerDown = (pev: PointerDragEvent) => { + let unselectCancel = this.context.options.unselectCancel + let downEl = getEventTargetViaRoot(pev.origEvent) as HTMLElement + + this.matchesCancel = !!elementClosest(downEl, unselectCancel) + this.matchesEvent = !!elementClosest(downEl, EventDragging.SELECTOR) // interaction started on an event? + } + + onDocumentPointerUp = (pev: PointerDragEvent) => { + let { context } = this + let { documentPointer } = this + let calendarState = context.getCurrentData() + + // touch-scrolling should never unfocus any type of selection + if (!documentPointer.wasTouchScroll) { + if ( + calendarState.dateSelection && // an existing date selection? + !this.isRecentPointerDateSelect // a new pointer-initiated date selection since last onDocumentPointerUp? + ) { + let unselectAuto = context.options.unselectAuto + + if (unselectAuto && (!unselectAuto || !this.matchesCancel)) { + context.calendarApi.unselect(pev) + } + } + + if ( + calendarState.eventSelection && // an existing event selected? + !this.matchesEvent // interaction DIDN'T start on an event + ) { + context.dispatch({ type: 'UNSELECT_EVENT' }) + } + } + + this.isRecentPointerDateSelect = false + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/main.global.ts b/apps/schoolCalender/fullcalendar/interaction/src/main.global.ts new file mode 100644 index 000000000..0af579774 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/main.global.ts @@ -0,0 +1,7 @@ +import { globalPlugins } from '@fullcalendar/common' +import plugin from './main' + +globalPlugins.push(plugin) + +export default plugin +export * from './main' diff --git a/apps/schoolCalender/fullcalendar/interaction/src/main.ts b/apps/schoolCalender/fullcalendar/interaction/src/main.ts new file mode 100644 index 000000000..f57049ca3 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/main.ts @@ -0,0 +1,23 @@ +import { createPlugin } from '@fullcalendar/common' +import { DateClicking } from './interactions/DateClicking' +import { DateSelecting } from './interactions/DateSelecting' +import { EventDragging } from './interactions/EventDragging' +import { EventResizing } from './interactions/EventResizing' +import { UnselectAuto } from './interactions/UnselectAuto' +import { FeaturefulElementDragging } from './dnd/FeaturefulElementDragging' +import { OPTION_REFINERS, LISTENER_REFINERS } from './options' +import './options-declare' + +export default createPlugin({ + componentInteractions: [DateClicking, DateSelecting, EventDragging, EventResizing], + calendarInteractions: [UnselectAuto], + elementDraggingImpl: FeaturefulElementDragging, + optionRefiners: OPTION_REFINERS, + listenerRefiners: LISTENER_REFINERS, +}) + +export * from './api-type-deps' +export { FeaturefulElementDragging } +export { PointerDragging } from './dnd/PointerDragging' +export { ExternalDraggable as Draggable } from './interactions-external/ExternalDraggable' +export { ThirdPartyDraggable } from './interactions-external/ThirdPartyDraggable' diff --git a/apps/schoolCalender/fullcalendar/interaction/src/options-declare.ts b/apps/schoolCalender/fullcalendar/interaction/src/options-declare.ts new file mode 100644 index 000000000..9cbc5a4a8 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/options-declare.ts @@ -0,0 +1,9 @@ +import { OPTION_REFINERS, LISTENER_REFINERS } from './options' + +type ExtraOptionRefiners = typeof OPTION_REFINERS +type ExtraListenerRefiners = typeof LISTENER_REFINERS + +declare module '@fullcalendar/common' { + interface BaseOptionRefiners extends ExtraOptionRefiners {} + interface CalendarListenerRefiners extends ExtraListenerRefiners {} +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/options.ts b/apps/schoolCalender/fullcalendar/interaction/src/options.ts new file mode 100644 index 000000000..a11ce5399 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/options.ts @@ -0,0 +1,26 @@ +import { identity, Identity, EventDropArg } from '@fullcalendar/common' + +// public +import { + DateClickArg, + EventDragStartArg, EventDragStopArg, + EventResizeStartArg, EventResizeStopArg, EventResizeDoneArg, + DropArg, EventReceiveArg, EventLeaveArg, +} from './api-type-deps' + +export const OPTION_REFINERS = { + fixedMirrorParent: identity as Identity, +} + +export const LISTENER_REFINERS = { + dateClick: identity as Identity<(arg: DateClickArg) => void>, + eventDragStart: identity as Identity<(arg: EventDragStartArg) => void>, + eventDragStop: identity as Identity<(arg: EventDragStopArg) => void>, + eventDrop: identity as Identity<(arg: EventDropArg) => void>, + eventResizeStart: identity as Identity<(arg: EventResizeStartArg) => void>, + eventResizeStop: identity as Identity<(arg: EventResizeStopArg) => void>, + eventResize: identity as Identity<(arg: EventResizeDoneArg) => void>, + drop: identity as Identity<(arg: DropArg) => void>, + eventReceive: identity as Identity<(arg: EventReceiveArg) => void>, + eventLeave: identity as Identity<(arg: EventLeaveArg) => void>, +} diff --git a/apps/schoolCalender/fullcalendar/interaction/src/utils.ts b/apps/schoolCalender/fullcalendar/interaction/src/utils.ts new file mode 100644 index 000000000..056a0040d --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/src/utils.ts @@ -0,0 +1,38 @@ +import { DateSpan, CalendarContext, DatePointApi, DateEnv, ViewApi, EventApi } from '@fullcalendar/common' +import { __assign } from 'tslib' + +export interface DropArg extends DatePointApi { + draggedEl: HTMLElement + jsEvent: MouseEvent + view: ViewApi +} + +export type EventReceiveArg = EventReceiveLeaveArg +export type EventLeaveArg = EventReceiveLeaveArg +export interface EventReceiveLeaveArg { // will this become public? + draggedEl: HTMLElement + event: EventApi + relatedEvents: EventApi[] + revert: () => void + view: ViewApi +} + +export function buildDatePointApiWithContext(dateSpan: DateSpan, context: CalendarContext) { + let props = {} as DatePointApi + + for (let transform of context.pluginHooks.datePointTransforms) { + __assign(props, transform(dateSpan, context)) + } + + __assign(props, buildDatePointApi(dateSpan, context.dateEnv)) + + return props +} + +export function buildDatePointApi(span: DateSpan, dateEnv: DateEnv): DatePointApi { + return { + date: dateEnv.toDate(span.range.start), + dateStr: dateEnv.formatIso(span.range.start, { omitTime: span.allDay }), + allDay: span.allDay, + } +} diff --git a/apps/schoolCalender/fullcalendar/interaction/tsconfig.json b/apps/schoolCalender/fullcalendar/interaction/tsconfig.json new file mode 100644 index 000000000..6172b1a45 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/interaction/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "rootDir": "src", + "outDir": "tsc" + }, + "include": [ + "src/**/*" + ], + "references": [ + { "path": "../common" } + ] +} diff --git a/apps/schoolCalender/fullcalendar/locales-all.js b/apps/schoolCalender/fullcalendar/locales-all.js new file mode 100644 index 000000000..f8be47ef2 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/locales-all.js @@ -0,0 +1,1622 @@ +[].push.apply(FullCalendar.globalLocales, function () { + 'use strict'; + + var l0 = { + code: 'af', + week: { + dow: 1, // Maandag is die eerste dag van die week. + doy: 4, // Die week wat die 4de Januarie bevat is die eerste week van die jaar. + }, + buttonText: { + prev: 'Vorige', + next: 'Volgende', + today: 'Vandag', + year: 'Jaar', + month: 'Maand', + week: 'Week', + day: 'Dag', + list: 'Agenda', + }, + allDayText: 'Heeldag', + moreLinkText: 'Addisionele', + noEventsText: 'Daar is geen gebeurtenisse nie', + }; + + var l1 = { + code: 'ar-dz', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 4, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l2 = { + code: 'ar-kw', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l3 = { + code: 'ar-ly', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l4 = { + code: 'ar-ma', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l5 = { + code: 'ar-sa', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l6 = { + code: 'ar-tn', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l7 = { + code: 'ar', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'السابق', + next: 'التالي', + today: 'اليوم', + month: 'شهر', + week: 'أسبوع', + day: 'يوم', + list: 'أجندة', + }, + weekText: 'أسبوع', + allDayText: 'اليوم كله', + moreLinkText: 'أخرى', + noEventsText: 'أي أحداث لعرض', + }; + + var l8 = { + code: 'az', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Əvvəl', + next: 'Sonra', + today: 'Bu Gün', + month: 'Ay', + week: 'Həftə', + day: 'Gün', + list: 'Gündəm', + }, + weekText: 'Həftə', + allDayText: 'Bütün Gün', + moreLinkText: function(n) { + return '+ daha çox ' + n + }, + noEventsText: 'Göstərmək üçün hadisə yoxdur', + }; + + var l9 = { + code: 'bg', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'назад', + next: 'напред', + today: 'днес', + month: 'Месец', + week: 'Седмица', + day: 'Ден', + list: 'График', + }, + allDayText: 'Цял ден', + moreLinkText: function(n) { + return '+още ' + n + }, + noEventsText: 'Няма събития за показване', + }; + + var l10 = { + code: 'bn', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'পেছনে', + next: 'সামনে', + today: 'আজ', + month: 'মাস', + week: 'সপ্তাহ', + day: 'দিন', + list: 'তালিকা', + }, + weekText: 'সপ্তাহ', + allDayText: 'সারাদিন', + moreLinkText: function(n) { + return '+অন্যান্য ' + n + }, + noEventsText: 'কোনো ইভেন্ট নেই', + }; + + var l11 = { + code: 'bs', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prošli', + next: 'Sljedeći', + today: 'Danas', + month: 'Mjesec', + week: 'Sedmica', + day: 'Dan', + list: 'Raspored', + }, + weekText: 'Sed', + allDayText: 'Cijeli dan', + moreLinkText: function(n) { + return '+ još ' + n + }, + noEventsText: 'Nema događaja za prikazivanje', + }; + + var l12 = { + code: 'ca', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Anterior', + next: 'Següent', + today: 'Avui', + month: 'Mes', + week: 'Setmana', + day: 'Dia', + list: 'Agenda', + }, + weekText: 'Set', + allDayText: 'Tot el dia', + moreLinkText: 'més', + noEventsText: 'No hi ha esdeveniments per mostrar', + }; + + var l13 = { + code: 'cs', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Dříve', + next: 'Později', + today: 'Nyní', + month: 'Měsíc', + week: 'Týden', + day: 'Den', + list: 'Agenda', + }, + weekText: 'Týd', + allDayText: 'Celý den', + moreLinkText: function(n) { + return '+další: ' + n + }, + noEventsText: 'Žádné akce k zobrazení', + }; + + var l14 = { + code: 'cy', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Blaenorol', + next: 'Nesaf', + today: 'Heddiw', + year: 'Blwyddyn', + month: 'Mis', + week: 'Wythnos', + day: 'Dydd', + list: 'Rhestr', + }, + weekText: 'Wythnos', + allDayText: 'Trwy\'r dydd', + moreLinkText: 'Mwy', + noEventsText: 'Dim digwyddiadau', + }; + + var l15 = { + code: 'da', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Forrige', + next: 'Næste', + today: 'I dag', + month: 'Måned', + week: 'Uge', + day: 'Dag', + list: 'Agenda', + }, + weekText: 'Uge', + allDayText: 'Hele dagen', + moreLinkText: 'flere', + noEventsText: 'Ingen arrangementer at vise', + }; + + var l16 = { + code: 'de-at', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Zurück', + next: 'Vor', + today: 'Heute', + year: 'Jahr', + month: 'Monat', + week: 'Woche', + day: 'Tag', + list: 'Terminübersicht', + }, + weekText: 'KW', + allDayText: 'Ganztägig', + moreLinkText: function(n) { + return '+ weitere ' + n + }, + noEventsText: 'Keine Ereignisse anzuzeigen', + }; + + var l17 = { + code: 'de', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Zurück', + next: 'Vor', + today: 'Heute', + year: 'Jahr', + month: 'Monat', + week: 'Woche', + day: 'Tag', + list: 'Terminübersicht', + }, + weekText: 'KW', + allDayText: 'Ganztägig', + moreLinkText: function(n) { + return '+ weitere ' + n + }, + noEventsText: 'Keine Ereignisse anzuzeigen', + }; + + var l18 = { + code: 'el', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4st is the first week of the year. + }, + buttonText: { + prev: 'Προηγούμενος', + next: 'Επόμενος', + today: 'Σήμερα', + month: 'Μήνας', + week: 'Εβδομάδα', + day: 'Ημέρα', + list: 'Ατζέντα', + }, + weekText: 'Εβδ', + allDayText: 'Ολοήμερο', + moreLinkText: 'περισσότερα', + noEventsText: 'Δεν υπάρχουν γεγονότα προς εμφάνιση', + }; + + var l19 = { + code: 'en-au', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + }; + + var l20 = { + code: 'en-gb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + }; + + var l21 = { + code: 'en-nz', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + }; + + var l22 = { + code: 'eo', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Antaŭa', + next: 'Sekva', + today: 'Hodiaŭ', + month: 'Monato', + week: 'Semajno', + day: 'Tago', + list: 'Tagordo', + }, + weekText: 'Sm', + allDayText: 'Tuta tago', + moreLinkText: 'pli', + noEventsText: 'Neniuj eventoj por montri', + }; + + var l23 = { + code: 'es', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Ant', + next: 'Sig', + today: 'Hoy', + month: 'Mes', + week: 'Semana', + day: 'Día', + list: 'Agenda', + }, + weekText: 'Sm', + allDayText: 'Todo el día', + moreLinkText: 'más', + noEventsText: 'No hay eventos para mostrar', + }; + + var l24 = { + code: 'es', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Ant', + next: 'Sig', + today: 'Hoy', + month: 'Mes', + week: 'Semana', + day: 'Día', + list: 'Agenda', + }, + weekText: 'Sm', + allDayText: 'Todo el día', + moreLinkText: 'más', + noEventsText: 'No hay eventos para mostrar', + }; + + var l25 = { + code: 'et', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Eelnev', + next: 'Järgnev', + today: 'Täna', + month: 'Kuu', + week: 'Nädal', + day: 'Päev', + list: 'Päevakord', + }, + weekText: 'näd', + allDayText: 'Kogu päev', + moreLinkText: function(n) { + return '+ veel ' + n + }, + noEventsText: 'Kuvamiseks puuduvad sündmused', + }; + + var l26 = { + code: 'eu', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Aur', + next: 'Hur', + today: 'Gaur', + month: 'Hilabetea', + week: 'Astea', + day: 'Eguna', + list: 'Agenda', + }, + weekText: 'As', + allDayText: 'Egun osoa', + moreLinkText: 'gehiago', + noEventsText: 'Ez dago ekitaldirik erakusteko', + }; + + var l27 = { + code: 'fa', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'قبلی', + next: 'بعدی', + today: 'امروز', + month: 'ماه', + week: 'هفته', + day: 'روز', + list: 'برنامه', + }, + weekText: 'هف', + allDayText: 'تمام روز', + moreLinkText: function(n) { + return 'بیش از ' + n + }, + noEventsText: 'هیچ رویدادی به نمایش', + }; + + var l28 = { + code: 'fi', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Edellinen', + next: 'Seuraava', + today: 'Tänään', + month: 'Kuukausi', + week: 'Viikko', + day: 'Päivä', + list: 'Tapahtumat', + }, + weekText: 'Vk', + allDayText: 'Koko päivä', + moreLinkText: 'lisää', + noEventsText: 'Ei näytettäviä tapahtumia', + }; + + var l29 = { + code: 'fr', + buttonText: { + prev: 'Précédent', + next: 'Suivant', + today: "Aujourd'hui", + year: 'Année', + month: 'Mois', + week: 'Semaine', + day: 'Jour', + list: 'Mon planning', + }, + weekText: 'Sem.', + allDayText: 'Toute la journée', + moreLinkText: 'en plus', + noEventsText: 'Aucun événement à afficher', + }; + + var l30 = { + code: 'fr-ch', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Précédent', + next: 'Suivant', + today: 'Courant', + year: 'Année', + month: 'Mois', + week: 'Semaine', + day: 'Jour', + list: 'Mon planning', + }, + weekText: 'Sm', + allDayText: 'Toute la journée', + moreLinkText: 'en plus', + noEventsText: 'Aucun événement à afficher', + }; + + var l31 = { + code: 'fr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Précédent', + next: 'Suivant', + today: "Aujourd'hui", + year: 'Année', + month: 'Mois', + week: 'Semaine', + day: 'Jour', + list: 'Planning', + }, + weekText: 'Sem.', + allDayText: 'Toute la journée', + moreLinkText: 'en plus', + noEventsText: 'Aucun événement à afficher', + }; + + var l32 = { + code: 'gl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Ant', + next: 'Seg', + today: 'Hoxe', + month: 'Mes', + week: 'Semana', + day: 'Día', + list: 'Axenda', + }, + weekText: 'Sm', + allDayText: 'Todo o día', + moreLinkText: 'máis', + noEventsText: 'Non hai eventos para amosar', + }; + + var l33 = { + code: 'he', + direction: 'rtl', + buttonText: { + prev: 'הקודם', + next: 'הבא', + today: 'היום', + month: 'חודש', + week: 'שבוע', + day: 'יום', + list: 'סדר יום', + }, + allDayText: 'כל היום', + moreLinkText: 'אחר', + noEventsText: 'אין אירועים להצגה', + weekText: 'שבוע', + }; + + var l34 = { + code: 'hi', + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'पिछला', + next: 'अगला', + today: 'आज', + month: 'महीना', + week: 'सप्ताह', + day: 'दिन', + list: 'कार्यसूची', + }, + weekText: 'हफ्ता', + allDayText: 'सभी दिन', + moreLinkText: function(n) { + return '+अधिक ' + n + }, + noEventsText: 'कोई घटनाओं को प्रदर्शित करने के लिए', + }; + + var l35 = { + code: 'hr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prijašnji', + next: 'Sljedeći', + today: 'Danas', + month: 'Mjesec', + week: 'Tjedan', + day: 'Dan', + list: 'Raspored', + }, + weekText: 'Tje', + allDayText: 'Cijeli dan', + moreLinkText: function(n) { + return '+ još ' + n + }, + noEventsText: 'Nema događaja za prikaz', + }; + + var l36 = { + code: 'hu', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'vissza', + next: 'előre', + today: 'ma', + month: 'Hónap', + week: 'Hét', + day: 'Nap', + list: 'Lista', + }, + weekText: 'Hét', + allDayText: 'Egész nap', + moreLinkText: 'további', + noEventsText: 'Nincs megjeleníthető esemény', + }; + + var l37 = { + code: 'hy-am', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Նախորդ', + next: 'Հաջորդ', + today: 'Այսօր', + month: 'Ամիս', + week: 'Շաբաթ', + day: 'Օր', + list: 'Օրվա ցուցակ', + }, + weekText: 'Շաբ', + allDayText: 'Ամբողջ օր', + moreLinkText: function(n) { + return '+ ևս ' + n + }, + noEventsText: 'Բացակայում է իրադարձությունը ցուցադրելու', + }; + + var l38 = { + code: 'id', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'mundur', + next: 'maju', + today: 'hari ini', + month: 'Bulan', + week: 'Minggu', + day: 'Hari', + list: 'Agenda', + }, + weekText: 'Mg', + allDayText: 'Sehari penuh', + moreLinkText: 'lebih', + noEventsText: 'Tidak ada acara untuk ditampilkan', + }; + + var l39 = { + code: 'is', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Fyrri', + next: 'Næsti', + today: 'Í dag', + month: 'Mánuður', + week: 'Vika', + day: 'Dagur', + list: 'Dagskrá', + }, + weekText: 'Vika', + allDayText: 'Allan daginn', + moreLinkText: 'meira', + noEventsText: 'Engir viðburðir til að sýna', + }; + + var l40 = { + code: 'it', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Prec', + next: 'Succ', + today: 'Oggi', + month: 'Mese', + week: 'Settimana', + day: 'Giorno', + list: 'Agenda', + }, + weekText: 'Sm', + allDayText: 'Tutto il giorno', + moreLinkText: function(n) { + return '+altri ' + n + }, + noEventsText: 'Non ci sono eventi da visualizzare', + }; + + var l41 = { + code: 'ja', + buttonText: { + prev: '前', + next: '次', + today: '今日', + month: '月', + week: '週', + day: '日', + list: '予定リスト', + }, + weekText: '週', + allDayText: '終日', + moreLinkText: function(n) { + return '他 ' + n + ' 件' + }, + noEventsText: '表示する予定はありません', + }; + + var l42 = { + code: 'ka', + week: { + dow: 1, + doy: 7, + }, + buttonText: { + prev: 'წინა', + next: 'შემდეგი', + today: 'დღეს', + month: 'თვე', + week: 'კვირა', + day: 'დღე', + list: 'დღის წესრიგი', + }, + weekText: 'კვ', + allDayText: 'მთელი დღე', + moreLinkText: function(n) { + return '+ კიდევ ' + n + }, + noEventsText: 'ღონისძიებები არ არის', + }; + + var l43 = { + code: 'kk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Алдыңғы', + next: 'Келесі', + today: 'Бүгін', + month: 'Ай', + week: 'Апта', + day: 'Күн', + list: 'Күн тәртібі', + }, + weekText: 'Не', + allDayText: 'Күні бойы', + moreLinkText: function(n) { + return '+ тағы ' + n + }, + noEventsText: 'Көрсету үшін оқиғалар жоқ', + }; + + var l44 = { + code: 'km', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'មុន', + next: 'បន្ទាប់', + today: 'ថ្ងៃនេះ', + year: 'ឆ្នាំ', + month: 'ខែ', + week: 'សប្តាហ៍', + day: 'ថ្ងៃ', + list: 'បញ្ជី', + }, + weekText: 'សប្តាហ៍', + allDayText: 'ពេញមួយថ្ងៃ', + moreLinkText: 'ច្រើនទៀត', + noEventsText: 'គ្មានព្រឹត្តិការណ៍ត្រូវបង្ហាញ', + }; + + var l45 = { + code: 'ko', + buttonText: { + prev: '이전달', + next: '다음달', + today: '오늘', + month: '월', + week: '주', + day: '일', + list: '일정목록', + }, + weekText: '주', + allDayText: '종일', + moreLinkText: '개', + noEventsText: '일정이 없습니다', + }; + + var l46 = { + code: 'ku', + week: { + dow: 6, // Saturday is the first day of the week. + doy: 12, // The week that contains Jan 1st is the first week of the year. + }, + direction: 'rtl', + buttonText: { + prev: 'پێشتر', + next: 'دواتر', + today: 'ئەمڕو', + month: 'مانگ', + week: 'هەفتە', + day: 'ڕۆژ', + list: 'بەرنامە', + }, + weekText: 'هەفتە', + allDayText: 'هەموو ڕۆژەکە', + moreLinkText: 'زیاتر', + noEventsText: 'هیچ ڕووداوێك نیە', + }; + + var l47 = { + code: 'lb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Zréck', + next: 'Weider', + today: 'Haut', + month: 'Mount', + week: 'Woch', + day: 'Dag', + list: 'Terminiwwersiicht', + }, + weekText: 'W', + allDayText: 'Ganzen Dag', + moreLinkText: 'méi', + noEventsText: 'Nee Evenementer ze affichéieren', + }; + + var l48 = { + code: 'lt', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Atgal', + next: 'Pirmyn', + today: 'Šiandien', + month: 'Mėnuo', + week: 'Savaitė', + day: 'Diena', + list: 'Darbotvarkė', + }, + weekText: 'SAV', + allDayText: 'Visą dieną', + moreLinkText: 'daugiau', + noEventsText: 'Nėra įvykių rodyti', + }; + + var l49 = { + code: 'lv', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Iepr.', + next: 'Nāk.', + today: 'Šodien', + month: 'Mēnesis', + week: 'Nedēļa', + day: 'Diena', + list: 'Dienas kārtība', + }, + weekText: 'Ned.', + allDayText: 'Visu dienu', + moreLinkText: function(n) { + return '+vēl ' + n + }, + noEventsText: 'Nav notikumu', + }; + + var l50 = { + code: 'mk', + buttonText: { + prev: 'претходно', + next: 'следно', + today: 'Денес', + month: 'Месец', + week: 'Недела', + day: 'Ден', + list: 'График', + }, + weekText: 'Сед', + allDayText: 'Цел ден', + moreLinkText: function(n) { + return '+повеќе ' + n + }, + noEventsText: 'Нема настани за прикажување', + }; + + var l51 = { + code: 'ms', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Sebelum', + next: 'Selepas', + today: 'hari ini', + month: 'Bulan', + week: 'Minggu', + day: 'Hari', + list: 'Agenda', + }, + weekText: 'Mg', + allDayText: 'Sepanjang hari', + moreLinkText: function(n) { + return 'masih ada ' + n + ' acara' + }, + noEventsText: 'Tiada peristiwa untuk dipaparkan', + }; + + var l52 = { + code: 'nb', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Forrige', + next: 'Neste', + today: 'I dag', + month: 'Måned', + week: 'Uke', + day: 'Dag', + list: 'Agenda', + }, + weekText: 'Uke', + allDayText: 'Hele dagen', + moreLinkText: 'til', + noEventsText: 'Ingen hendelser å vise', + }; + + var l53 = { + code: 'ne', // code for nepal + week: { + dow: 7, // Sunday is the first day of the week. + doy: 1, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'अघिल्लो', + next: 'अर्को', + today: 'आज', + month: 'महिना', + week: 'हप्ता', + day: 'दिन', + list: 'सूची', + }, + weekText: 'हप्ता', + allDayText: 'दिनभरि', + moreLinkText: 'थप लिंक', + noEventsText: 'देखाउनको लागि कुनै घटनाहरू छैनन्', + }; + + var l54 = { + code: 'nl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Vorige', + next: 'Volgende', + today: 'Vandaag', + year: 'Jaar', + month: 'Maand', + week: 'Week', + day: 'Dag', + list: 'Agenda', + }, + allDayText: 'Hele dag', + moreLinkText: 'extra', + noEventsText: 'Geen evenementen om te laten zien', + }; + + var l55 = { + code: 'nn', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Førre', + next: 'Neste', + today: 'I dag', + month: 'Månad', + week: 'Veke', + day: 'Dag', + list: 'Agenda', + }, + weekText: 'Veke', + allDayText: 'Heile dagen', + moreLinkText: 'til', + noEventsText: 'Ingen hendelser å vise', + }; + + var l56 = { + code: 'pl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Poprzedni', + next: 'Następny', + today: 'Dziś', + month: 'Miesiąc', + week: 'Tydzień', + day: 'Dzień', + list: 'Plan dnia', + }, + weekText: 'Tydz', + allDayText: 'Cały dzień', + moreLinkText: 'więcej', + noEventsText: 'Brak wydarzeń do wyświetlenia', + }; + + var l57 = { + code: 'pt-br', + buttonText: { + prev: 'Anterior', + next: 'Próximo', + today: 'Hoje', + month: 'Mês', + week: 'Semana', + day: 'Dia', + list: 'Lista', + }, + weekText: 'Sm', + allDayText: 'dia inteiro', + moreLinkText: function(n) { + return 'mais +' + n + }, + noEventsText: 'Não há eventos para mostrar', + }; + + var l58 = { + code: 'pt', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Anterior', + next: 'Seguinte', + today: 'Hoje', + month: 'Mês', + week: 'Semana', + day: 'Dia', + list: 'Agenda', + }, + weekText: 'Sem', + allDayText: 'Todo o dia', + moreLinkText: 'mais', + noEventsText: 'Não há eventos para mostrar', + }; + + var l59 = { + code: 'ro', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'precedentă', + next: 'următoare', + today: 'Azi', + month: 'Lună', + week: 'Săptămână', + day: 'Zi', + list: 'Agendă', + }, + weekText: 'Săpt', + allDayText: 'Toată ziua', + moreLinkText: function(n) { + return '+alte ' + n + }, + noEventsText: 'Nu există evenimente de afișat', + }; + + var l60 = { + code: 'ru', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Пред', + next: 'След', + today: 'Сегодня', + month: 'Месяц', + week: 'Неделя', + day: 'День', + list: 'Повестка дня', + }, + weekText: 'Нед', + allDayText: 'Весь день', + moreLinkText: function(n) { + return '+ ещё ' + n + }, + noEventsText: 'Нет событий для отображения', + }; + + var l61 = { + code: 'sk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Predchádzajúci', + next: 'Nasledujúci', + today: 'Dnes', + month: 'Mesiac', + week: 'Týždeň', + day: 'Deň', + list: 'Rozvrh', + }, + weekText: 'Ty', + allDayText: 'Celý deň', + moreLinkText: function(n) { + return '+ďalšie: ' + n + }, + noEventsText: 'Žiadne akcie na zobrazenie', + }; + + var l62 = { + code: 'sl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prejšnji', + next: 'Naslednji', + today: 'Trenutni', + month: 'Mesec', + week: 'Teden', + day: 'Dan', + list: 'Dnevni red', + }, + weekText: 'Teden', + allDayText: 'Ves dan', + moreLinkText: 'več', + noEventsText: 'Ni dogodkov za prikaz', + }; + + var l63 = { + code: 'sm', + buttonText: { + prev: 'Talu ai', + next: 'Mulimuli atu', + today: 'Aso nei', + month: 'Masina', + week: 'Vaiaso', + day: 'Aso', + list: 'Faasologa', + }, + weekText: 'Vaiaso', + allDayText: 'Aso atoa', + moreLinkText: 'sili atu', + noEventsText: 'Leai ni mea na tutupu', + }; + + var l64 = { + code: 'sq', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'mbrapa', + next: 'Përpara', + today: 'sot', + month: 'Muaj', + week: 'Javë', + day: 'Ditë', + list: 'Listë', + }, + weekText: 'Ja', + allDayText: 'Gjithë ditën', + moreLinkText: function(n) { + return '+më tepër ' + n + }, + noEventsText: 'Nuk ka evente për të shfaqur', + }; + + var l65 = { + code: 'sr-cyrl', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Претходна', + next: 'следећи', + today: 'Данас', + month: 'Месец', + week: 'Недеља', + day: 'Дан', + list: 'Планер', + }, + weekText: 'Сед', + allDayText: 'Цео дан', + moreLinkText: function(n) { + return '+ још ' + n + }, + noEventsText: 'Нема догађаја за приказ', + }; + + var l66 = { + code: 'sr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Prethodna', + next: 'Sledeći', + today: 'Danas', + month: 'Mеsеc', + week: 'Nеdеlja', + day: 'Dan', + list: 'Planеr', + }, + weekText: 'Sed', + allDayText: 'Cеo dan', + moreLinkText: function(n) { + return '+ još ' + n + }, + noEventsText: 'Nеma događaja za prikaz', + }; + + var l67 = { + code: 'sv', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Förra', + next: 'Nästa', + today: 'Idag', + month: 'Månad', + week: 'Vecka', + day: 'Dag', + list: 'Program', + }, + weekText: 'v.', + allDayText: 'Heldag', + moreLinkText: 'till', + noEventsText: 'Inga händelser att visa', + }; + + var l68 = { + code: 'ta-in', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'முந்தைய', + next: 'அடுத்தது', + today: 'இன்று', + month: 'மாதம்', + week: 'வாரம்', + day: 'நாள்', + list: 'தினசரி அட்டவணை', + }, + weekText: 'வாரம்', + allDayText: 'நாள் முழுவதும்', + moreLinkText: function(n) { + return '+ மேலும் ' + n + }, + noEventsText: 'காண்பிக்க நிகழ்வுகள் இல்லை', + }; + + var l69 = { + code: 'th', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'ก่อนหน้า', + next: 'ถัดไป', + prevYear: 'ปีก่อนหน้า', + nextYear: 'ปีถัดไป', + year: 'ปี', + today: 'วันนี้', + month: 'เดือน', + week: 'สัปดาห์', + day: 'วัน', + list: 'กำหนดการ', + }, + weekText: 'สัปดาห์', + allDayText: 'ตลอดวัน', + moreLinkText: 'เพิ่มเติม', + noEventsText: 'ไม่มีกิจกรรมที่จะแสดง', + }; + + var l70 = { + code: 'tr', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'geri', + next: 'ileri', + today: 'bugün', + month: 'Ay', + week: 'Hafta', + day: 'Gün', + list: 'Ajanda', + }, + weekText: 'Hf', + allDayText: 'Tüm gün', + moreLinkText: 'daha fazla', + noEventsText: 'Gösterilecek etkinlik yok', + }; + + var l71 = { + code: 'ug', + buttonText: { + month: 'ئاي', + week: 'ھەپتە', + day: 'كۈن', + list: 'كۈنتەرتىپ', + }, + allDayText: 'پۈتۈن كۈن', + }; + + var l72 = { + code: 'uk', + week: { + dow: 1, // Monday is the first day of the week. + doy: 7, // The week that contains Jan 1st is the first week of the year. + }, + buttonText: { + prev: 'Попередній', + next: 'далі', + today: 'Сьогодні', + month: 'Місяць', + week: 'Тиждень', + day: 'День', + list: 'Порядок денний', + }, + weekText: 'Тиж', + allDayText: 'Увесь день', + moreLinkText: function(n) { + return '+ще ' + n + '...' + }, + noEventsText: 'Немає подій для відображення', + }; + + var l73 = { + code: 'uz', + buttonText: { + month: 'Oy', + week: 'Xafta', + day: 'Kun', + list: 'Kun tartibi', + }, + allDayText: "Kun bo'yi", + moreLinkText: function(n) { + return '+ yana ' + n + }, + noEventsText: "Ko'rsatish uchun voqealar yo'q", + }; + + var l74 = { + code: 'vi', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: 'Trước', + next: 'Tiếp', + today: 'Hôm nay', + month: 'Tháng', + week: 'Tuần', + day: 'Ngày', + list: 'Lịch biểu', + }, + weekText: 'Tu', + allDayText: 'Cả ngày', + moreLinkText: function(n) { + return '+ thêm ' + n + }, + noEventsText: 'Không có sự kiện để hiển thị', + }; + + var l75 = { + code: 'zh-cn', + week: { + // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 + dow: 1, // Monday is the first day of the week. + doy: 4, // The week that contains Jan 4th is the first week of the year. + }, + buttonText: { + prev: '上月', + next: '下月', + today: '今天', + month: '月', + week: '周', + day: '日', + list: '日程', + }, + weekText: '周', + allDayText: '全天', + moreLinkText: function(n) { + return '另外 ' + n + ' 个' + }, + noEventsText: '没有事件显示', + }; + + var l76 = { + code: 'zh-tw', + buttonText: { + prev: '上月', + next: '下月', + today: '今天', + month: '月', + week: '週', + day: '天', + list: '活動列表', + }, + weekText: '周', + allDayText: '整天', + moreLinkText: '顯示更多', + noEventsText: '没有任何活動', + }; + + /* eslint max-len: off */ + + var localesAll = [ + l0, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71, l72, l73, l74, l75, l76, + ]; + + return localesAll; + +}()); diff --git a/apps/schoolCalender/fullcalendar/main.css b/apps/schoolCalender/fullcalendar/main.css new file mode 100644 index 000000000..957887b56 --- /dev/null +++ b/apps/schoolCalender/fullcalendar/main.css @@ -0,0 +1,1446 @@ + +/* classes attached to */ +/* TODO: make fc-event selector work when calender in shadow DOM */ +.fc-not-allowed, +.fc-not-allowed .fc-event { /* override events' custom cursors */ + cursor: not-allowed; +} + +/* TODO: not attached to body. attached to specific els. move */ +.fc-unselectable { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +.fc { + /* layout of immediate children */ + display: flex; + flex-direction: column; + + font-size: 1em +} +.fc, + .fc *, + .fc *:before, + .fc *:after { + box-sizing: border-box; + } +.fc table { + border-collapse: collapse; + border-spacing: 0; + font-size: 1em; /* normalize cross-browser */ + } +.fc th { + text-align: center; + } +.fc th, + .fc td { + vertical-align: top; + padding: 0; + } +.fc a[data-navlink] { + cursor: pointer; + } +.fc a[data-navlink]:hover { + text-decoration: underline; + } +.fc-direction-ltr { + direction: ltr; + text-align: left; +} +.fc-direction-rtl { + direction: rtl; + text-align: right; +} +.fc-theme-standard td, + .fc-theme-standard th { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); + } +/* for FF, which doesn't expand a 100% div within a table cell. use absolute positioning */ +/* inner-wrappers are responsible for being absolute */ +/* TODO: best place for this? */ +.fc-liquid-hack td, + .fc-liquid-hack th { + position: relative; + } + +@font-face { + font-family: 'fcicons'; + src: url("data:application/x-font-ttf;charset=utf-8;base64,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") format('truetype'); + font-weight: normal; + font-style: normal; +} + +.fc-icon { + /* added for fc */ + display: inline-block; + width: 1em; + height: 1em; + text-align: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + /* use !important to prevent issues with browser extensions that change fonts */ + font-family: 'fcicons' !important; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.fc-icon-chevron-left:before { + content: "\e900"; +} + +.fc-icon-chevron-right:before { + content: "\e901"; +} + +.fc-icon-chevrons-left:before { + content: "\e902"; +} + +.fc-icon-chevrons-right:before { + content: "\e903"; +} + +.fc-icon-minus-square:before { + content: "\e904"; +} + +.fc-icon-plus-square:before { + content: "\e905"; +} + +.fc-icon-x:before { + content: "\e906"; +} +/* +Lots taken from Flatly (MIT): https://bootswatch.com/4/flatly/bootstrap.css + +These styles only apply when the standard-theme is activated. +When it's NOT activated, the fc-button classes won't even be in the DOM. +*/ +.fc { + + /* reset */ + +} +.fc .fc-button { + border-radius: 0; + overflow: visible; + text-transform: none; + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; + } +.fc .fc-button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; + } +.fc .fc-button { + -webkit-appearance: button; + } +.fc .fc-button:not(:disabled) { + cursor: pointer; + } +.fc .fc-button::-moz-focus-inner { + padding: 0; + border-style: none; + } +.fc { + + /* theme */ + +} +.fc .fc-button { + display: inline-block; + font-weight: 400; + text-align: center; + vertical-align: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 0.4em 0.65em; + font-size: 1em; + line-height: 1.5; + border-radius: 0.25em; + } +.fc .fc-button:hover { + text-decoration: none; + } +.fc .fc-button:focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(44, 62, 80, 0.25); + } +.fc .fc-button:disabled { + opacity: 0.65; + } +.fc { + + /* "primary" coloring */ + +} +.fc .fc-button-primary { + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #2C3E50; + background-color: var(--fc-button-bg-color, #2C3E50); + border-color: #2C3E50; + border-color: var(--fc-button-border-color, #2C3E50); + } +.fc .fc-button-primary:hover { + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #1e2b37; + background-color: var(--fc-button-hover-bg-color, #1e2b37); + border-color: #1a252f; + border-color: var(--fc-button-hover-border-color, #1a252f); + } +.fc .fc-button-primary:disabled { /* not DRY */ + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #2C3E50; + background-color: var(--fc-button-bg-color, #2C3E50); + border-color: #2C3E50; + border-color: var(--fc-button-border-color, #2C3E50); /* overrides :hover */ + } +.fc .fc-button-primary:focus { + box-shadow: 0 0 0 0.2rem rgba(76, 91, 106, 0.5); + } +.fc .fc-button-primary:not(:disabled):active, + .fc .fc-button-primary:not(:disabled).fc-button-active { + color: #fff; + color: var(--fc-button-text-color, #fff); + background-color: #1a252f; + background-color: var(--fc-button-active-bg-color, #1a252f); + border-color: #151e27; + border-color: var(--fc-button-active-border-color, #151e27); + } +.fc .fc-button-primary:not(:disabled):active:focus, + .fc .fc-button-primary:not(:disabled).fc-button-active:focus { + box-shadow: 0 0 0 0.2rem rgba(76, 91, 106, 0.5); + } +.fc { + + /* icons within buttons */ + +} +.fc .fc-button .fc-icon { + vertical-align: middle; + font-size: 1.5em; /* bump up the size (but don't make it bigger than line-height of button, which is 1.5em also) */ + } +.fc .fc-button-group { + position: relative; + display: inline-flex; + vertical-align: middle; + } +.fc .fc-button-group > .fc-button { + position: relative; + flex: 1 1 auto; + } +.fc .fc-button-group > .fc-button:hover { + z-index: 1; + } +.fc .fc-button-group > .fc-button:focus, + .fc .fc-button-group > .fc-button:active, + .fc .fc-button-group > .fc-button.fc-button-active { + z-index: 1; + } +.fc-direction-ltr .fc-button-group > .fc-button:not(:first-child) { + margin-left: -1px; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } +.fc-direction-ltr .fc-button-group > .fc-button:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +.fc-direction-rtl .fc-button-group > .fc-button:not(:first-child) { + margin-right: -1px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +.fc-direction-rtl .fc-button-group > .fc-button:not(:last-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } +.fc .fc-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + } +.fc .fc-toolbar.fc-header-toolbar { + margin-bottom: 1.5em; + } +.fc .fc-toolbar.fc-footer-toolbar { + margin-top: 1.5em; + } +.fc .fc-toolbar-title { + font-size: 1.75em; + margin: 0; + } +.fc-direction-ltr .fc-toolbar > * > :not(:first-child) { + margin-left: .75em; /* space between */ + } +.fc-direction-rtl .fc-toolbar > * > :not(:first-child) { + margin-right: .75em; /* space between */ + } +.fc-direction-rtl .fc-toolbar-ltr { /* when the toolbar-chunk positioning system is explicitly left-to-right */ + flex-direction: row-reverse; + } +.fc .fc-scroller { + -webkit-overflow-scrolling: touch; + position: relative; /* for abs-positioned elements within */ + } +.fc .fc-scroller-liquid { + height: 100%; + } +.fc .fc-scroller-liquid-absolute { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + } +.fc .fc-scroller-harness { + position: relative; + overflow: hidden; + direction: ltr; + /* hack for chrome computing the scroller's right/left wrong for rtl. undone below... */ + /* TODO: demonstrate in codepen */ + } +.fc .fc-scroller-harness-liquid { + height: 100%; + } +.fc-direction-rtl .fc-scroller-harness > .fc-scroller { /* undo above hack */ + direction: rtl; + } +.fc-theme-standard .fc-scrollgrid { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); /* bootstrap does this. match */ + } +.fc .fc-scrollgrid, + .fc .fc-scrollgrid table { /* all tables (self included) */ + width: 100%; /* because tables don't normally do this */ + table-layout: fixed; + } +.fc .fc-scrollgrid table { /* inner tables */ + border-top-style: hidden; + border-left-style: hidden; + border-right-style: hidden; + } +.fc .fc-scrollgrid { + + border-collapse: separate; + border-right-width: 0; + border-bottom-width: 0; + + } +.fc .fc-scrollgrid-liquid { + height: 100%; + } +.fc .fc-scrollgrid-section { /* a */ + height: 1px /* better than 0, for firefox */ + + } +.fc .fc-scrollgrid-section > td { + height: 1px; /* needs a height so inner div within grow. better than 0, for firefox */ + } +.fc .fc-scrollgrid-section table { + height: 1px; + /* for most browsers, if a height isn't set on the table, can't do liquid-height within cells */ + /* serves as a min-height. harmless */ + } +.fc .fc-scrollgrid-section-liquid > td { + height: 100%; /* better than `auto`, for firefox */ + } +.fc .fc-scrollgrid-section > * { + border-top-width: 0; + border-left-width: 0; + } +.fc .fc-scrollgrid-section-header > *, + .fc .fc-scrollgrid-section-footer > * { + border-bottom-width: 0; + } +.fc .fc-scrollgrid-section-body table, + .fc .fc-scrollgrid-section-footer table { + border-bottom-style: hidden; /* head keeps its bottom border tho */ + } +.fc { + + /* stickiness */ + +} +.fc .fc-scrollgrid-section-sticky > * { + background: #fff; + background: var(--fc-page-bg-color, #fff); + position: sticky; + z-index: 3; /* TODO: var */ + /* TODO: box-shadow when sticking */ + } +.fc .fc-scrollgrid-section-header.fc-scrollgrid-section-sticky > * { + top: 0; /* because border-sharing causes a gap at the top */ + /* TODO: give safari -1. has bug */ + } +.fc .fc-scrollgrid-section-footer.fc-scrollgrid-section-sticky > * { + bottom: 0; /* known bug: bottom-stickiness doesn't work in safari */ + } +.fc .fc-scrollgrid-sticky-shim { /* for horizontal scrollbar */ + height: 1px; /* needs height to create scrollbars */ + margin-bottom: -1px; + } +.fc-sticky { /* no .fc wrap because used as child of body */ + position: sticky; +} +.fc .fc-view-harness { + flex-grow: 1; /* because this harness is WITHIN the .fc's flexbox */ + position: relative; + } +.fc { + + /* when the harness controls the height, make the view liquid */ + +} +.fc .fc-view-harness-active > .fc-view { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + } +.fc .fc-col-header-cell-cushion { + display: inline-block; /* x-browser for when sticky (when multi-tier header) */ + padding: 2px 4px; + } +.fc .fc-bg-event, + .fc .fc-non-business, + .fc .fc-highlight { + /* will always have a harness with position:relative/absolute, so absolutely expand */ + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +.fc .fc-non-business { + background: rgba(215, 215, 215, 0.3); + background: var(--fc-non-business-color, rgba(215, 215, 215, 0.3)); + } +.fc .fc-bg-event { + background: rgb(143, 223, 130); + background: var(--fc-bg-event-color, rgb(143, 223, 130)); + opacity: 0.3; + opacity: var(--fc-bg-event-opacity, 0.3) + } +.fc .fc-bg-event .fc-event-title { + margin: .5em; + font-size: .85em; + font-size: var(--fc-small-font-size, .85em); + font-style: italic; + } +.fc .fc-highlight { + background: rgba(188, 232, 241, 0.3); + background: var(--fc-highlight-color, rgba(188, 232, 241, 0.3)); + } +.fc .fc-cell-shaded, + .fc .fc-day-disabled { + background: rgba(208, 208, 208, 0.3); + background: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + } +/* link resets */ +/* ---------------------------------------------------------------------------------------------------- */ +a.fc-event, +a.fc-event:hover { + text-decoration: none; +} +/* cursor */ +.fc-event[href], +.fc-event.fc-event-draggable { + cursor: pointer; +} +/* event text content */ +/* ---------------------------------------------------------------------------------------------------- */ +.fc-event .fc-event-main { + position: relative; + z-index: 2; + } +/* dragging */ +/* ---------------------------------------------------------------------------------------------------- */ +.fc-event-dragging:not(.fc-event-selected) { /* MOUSE */ + opacity: 0.75; + } +.fc-event-dragging.fc-event-selected { /* TOUCH */ + box-shadow: 0 2px 7px rgba(0, 0, 0, 0.3); + } +/* resizing */ +/* ---------------------------------------------------------------------------------------------------- */ +/* (subclasses should hone positioning for touch and non-touch) */ +.fc-event .fc-event-resizer { + display: none; + position: absolute; + z-index: 4; + } +.fc-event:hover, /* MOUSE */ +.fc-event-selected { /* TOUCH */ + +} +.fc-event:hover .fc-event-resizer, .fc-event-selected .fc-event-resizer { + display: block; + } +.fc-event-selected .fc-event-resizer { + border-radius: 4px; + border-radius: calc(var(--fc-event-resizer-dot-total-width, 8px) / 2); + border-width: 1px; + border-width: var(--fc-event-resizer-dot-border-width, 1px); + width: 8px; + width: var(--fc-event-resizer-dot-total-width, 8px); + height: 8px; + height: var(--fc-event-resizer-dot-total-width, 8px); + border-style: solid; + border-color: inherit; + background: #fff; + background: var(--fc-page-bg-color, #fff) + + /* expand hit area */ + + } +.fc-event-selected .fc-event-resizer:before { + content: ''; + position: absolute; + top: -20px; + left: -20px; + right: -20px; + bottom: -20px; + } +/* selecting (always TOUCH) */ +/* ---------------------------------------------------------------------------------------------------- */ +.fc-event-selected { + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2) + + /* expand hit area (subclasses should expand) */ + +} +.fc-event-selected:before { + content: ""; + position: absolute; + z-index: 3; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +.fc-event-selected { + + /* dimmer effect */ + +} +.fc-event-selected:after { + content: ""; + background: rgba(0, 0, 0, 0.25); + background: var(--fc-event-selected-overlay-color, rgba(0, 0, 0, 0.25)); + position: absolute; + z-index: 1; + + /* assume there's a border on all sides. overcome it. */ + /* sometimes there's NOT a border, in which case the dimmer will go over */ + /* an adjacent border, which looks fine. */ + top: -1px; + left: -1px; + right: -1px; + bottom: -1px; + } +/* +A HORIZONTAL event +*/ +.fc-h-event { /* allowed to be top-level */ + display: block; + border: 1px solid #3788d8; + border: 1px solid var(--fc-event-border-color, #3788d8); + background-color: #3788d8; + background-color: var(--fc-event-bg-color, #3788d8) + +} +.fc-h-event .fc-event-main { + color: #fff; + color: var(--fc-event-text-color, #fff); + } +.fc-h-event .fc-event-main-frame { + display: flex; /* for make fc-event-title-container expand */ + } +.fc-h-event .fc-event-time { + max-width: 100%; /* clip overflow on this element */ + overflow: hidden; + } +.fc-h-event .fc-event-title-container { /* serves as a container for the sticky cushion */ + flex-grow: 1; + flex-shrink: 1; + min-width: 0; /* important for allowing to shrink all the way */ + } +.fc-h-event .fc-event-title { + display: inline-block; /* need this to be sticky cross-browser */ + vertical-align: top; /* for not messing up line-height */ + left: 0; /* for sticky */ + right: 0; /* for sticky */ + max-width: 100%; /* clip overflow on this element */ + overflow: hidden; + } +.fc-h-event.fc-event-selected:before { + /* expand hit area */ + top: -10px; + bottom: -10px; + } +/* adjust border and border-radius (if there is any) for non-start/end */ +.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-start), +.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-end) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left-width: 0; +} +.fc-direction-ltr .fc-daygrid-block-event:not(.fc-event-end), +.fc-direction-rtl .fc-daygrid-block-event:not(.fc-event-start) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-right-width: 0; +} +/* resizers */ +.fc-h-event:not(.fc-event-selected) .fc-event-resizer { + top: 0; + bottom: 0; + width: 8px; + width: var(--fc-event-resizer-thickness, 8px); +} +.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start, +.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end { + cursor: w-resize; + left: -4px; + left: calc(var(--fc-event-resizer-thickness, 8px) / -2); +} +.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end, +.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start { + cursor: e-resize; + right: -4px; + right: calc(var(--fc-event-resizer-thickness, 8px) / -2); +} +/* resizers for TOUCH */ +.fc-h-event.fc-event-selected .fc-event-resizer { + top: 50%; + margin-top: -4px; + margin-top: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); +} +.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-start, +.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-end { + left: -4px; + left: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); +} +.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-end, +.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-start { + right: -4px; + right: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2); +} +.fc .fc-popover { + position: absolute; + z-index: 9999; + box-shadow: 0 2px 6px rgba(0,0,0,.15); + } +.fc .fc-popover-header { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 3px 4px; + } +.fc .fc-popover-title { + margin: 0 2px; + } +.fc .fc-popover-close { + cursor: pointer; + opacity: 0.65; + font-size: 1.1em; + } +.fc-theme-standard .fc-popover { + border: 1px solid #ddd; + border: 1px solid var(--fc-border-color, #ddd); + background: #fff; + background: var(--fc-page-bg-color, #fff); + } +.fc-theme-standard .fc-popover-header { + background: rgba(208, 208, 208, 0.3); + background: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3)); + } + + +:root { + --fc-daygrid-event-dot-width: 8px; +} +/* help things clear margins of inner content */ +.fc-daygrid-day-frame, +.fc-daygrid-day-events, +.fc-daygrid-event-harness { /* for event top/bottom margins */ +} +.fc-daygrid-day-frame:before, .fc-daygrid-day-events:before, .fc-daygrid-event-harness:before { + content: ""; + clear: both; + display: table; } +.fc-daygrid-day-frame:after, .fc-daygrid-day-events:after, .fc-daygrid-event-harness:after { + content: ""; + clear: both; + display: table; } +.fc .fc-daygrid-body { /* a