From: Jinkun Jang Date: Tue, 12 Mar 2013 17:12:55 +0000 (+0900) Subject: Tizen 2.1 base X-Git-Tag: 2.1b_release~2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;ds=sidebyside;h=e31e4654a2e2bf83f200e9cc9a695dbb70b15e32;p=samples%2Fweb%2FExercisePlanner.git Tizen 2.1 base --- diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..3d165e3 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,5 @@ +Tomasz Lukawski +Artur Kobylinski +Piotr Wronski +Pawel Sierszen +Tomasz Paciorek diff --git a/LICENSE.Flora b/LICENSE.Flora new file mode 100644 index 0000000..9c95663 --- /dev/null +++ b/LICENSE.Flora @@ -0,0 +1,206 @@ +Flora License + +Version 1.0, May, 2012 + +http://floralicense.org/license/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and +all other entities that control, are controlled by, or are +under common control with that entity. For the purposes of +this definition, "control" means (i) the power, direct or indirect, +to cause the direction or management of such entity, +whether by contract or otherwise, or (ii) ownership of fifty percent (50%) +or more of the outstanding shares, or (iii) beneficial ownership of +such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation source, +and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, +made available under the License, as indicated by a copyright notice +that is included in or attached to the work (an example is provided +in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, +that is based on (or derived from) the Work and for which the editorial +revisions, annotations, elaborations, or other modifications represent, +as a whole, an original work of authorship. For the purposes of this License, +Derivative Works shall not include works that remain separable from, +or merely link (or bind by name) to the interfaces of, the Work and +Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original +version of the Work and any modifications or additions to that Work or +Derivative Works thereof, that is intentionally submitted to Licensor +for inclusion in the Work by the copyright owner or by an individual or +Legal Entity authorized to submit on behalf of the copyright owner. +For the purposes of this definition, "submitted" means any form of +electronic, verbal, or written communication sent to the Licensor or +its representatives, including but not limited to communication on +electronic mailing lists, source code control systems, and issue +tracking systems that are managed by, or on behalf of, the Licensor +for the purpose of discussing and improving the Work, but excluding +communication that is conspicuously marked or otherwise designated +in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +"Tizen Certified Platform" shall mean a software platform that complies +with the standards set forth in the Compatibility Definition Document +and passes the Compatibility Test Suite as defined from time to time +by the Tizen Technical Steering Group and certified by the Tizen +Association or its designated agent. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work +solely as incorporated into a Tizen Certified Platform, where such +license applies only to those patent claims licensable by such +Contributor that are necessarily infringed by their Contribution(s) +alone or by combination of their Contribution(s) with the Work solely +as incorporated into a Tizen Certified Platform to which such +Contribution(s) was submitted. If You institute patent litigation +against any entity (including a cross-claim or counterclaim +in a lawsuit) alleging that the Work or a Contribution incorporated +within the Work constitutes direct or contributory patent infringement, +then any patent licenses granted to You under this License for that +Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof pursuant to the copyright license +above, in any medium, with or without modifications, and in Source or +Object form, provided that You meet the following conditions: + + 1. You must give any other recipients of the Work or Derivative Works + a copy of this License; and + 2. You must cause any modified files to carry prominent notices stating + that You changed the files; and + 3. You must retain, in the Source form of any Derivative Works that + You distribute, all copyright, patent, trademark, and attribution + notices from the Source form of the Work, excluding those notices + that do not pertain to any part of the Derivative Works; and + 4. If the Work includes a "NOTICE" text file as part of its distribution, + then any Derivative Works that You distribute must include a readable + copy of the attribution notices contained within such NOTICE file, + excluding those notices that do not pertain to any part of + the Derivative Works, in at least one of the following places: + within a NOTICE text file distributed as part of the Derivative Works; + within the Source form or documentation, if provided along with the + Derivative Works; or, within a display generated by the Derivative Works, + if and wherever such third-party notices normally appear. + The contents of the NOTICE file are for informational purposes only + and do not modify the License. + +You may add Your own attribution notices within Derivative Works +that You distribute, alongside or as an addendum to the NOTICE text +from the Work, provided that such additional attribution notices +cannot be construed as modifying the License. You may add Your own +copyright statement to Your modifications and may provide additional or +different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works +as a whole, provided Your use, reproduction, and distribution of +the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Flora License to your work + +To apply the Flora License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Flora License, Version 1.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://floralicense.org/license/ + + 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. + diff --git a/NOTICE.Flora b/NOTICE.Flora new file mode 100644 index 0000000..fdb699a --- /dev/null +++ b/NOTICE.Flora @@ -0,0 +1,4 @@ +Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved. +Except as noted, this software is licensed under Flora License, Version 1. +Please, see the LICENSE file for Flora License terms and conditions. + diff --git a/config.xml b/config.xml new file mode 100644 index 0000000..a4cf761 --- /dev/null +++ b/config.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + ExercisePlanner + + + diff --git a/css/GraphSchedule.css b/css/GraphSchedule.css new file mode 100644 index 0000000..61da77d --- /dev/null +++ b/css/GraphSchedule.css @@ -0,0 +1,122 @@ +.GraphSchedule { + position: relative; + overflow-x: scroll; + width: 100%; + height: 100px; + background-color: transparent; + z-index: 1000; +} + +.GraphSchedule .container { + width: 600px; + bottom: 5px; + position: absolute; +} + +.GraphSchedule table { + pointer-events: none; +} + +.GraphSchedule td { + width: 70px; + text-align: left; + color: gray; + height: 10px; +} + +.GraphSchedule table .grid td { + border-width: 1px 0 0px 1px; + border-style: solid; + border-color: white; + font-size: 13px; + font-family: Helvetica; + position: relative; +} + +.GraphSchedule table .ranges td { + border-width: 1px 0 0 1px; + border-style: solid; + border-color: gray; + position: relative; +} + +.GraphSchedule table .ranges .th { + border-width: 0px; + background-color: rgba(50, 50, 50, 0.2); + position: relative; + padding: 0; +} + +.GraphSchedule table .rangesWeekend td { + border-width: 1px 0 0 1px; + border-style: solid; + border-color: gray; + position: relative; +} + +.GraphSchedule table .rangesWeekend .th { + border-width: 0px; + background-color: rgba(50, 50, 250, 0.2); + position: relative; + padding: 0; +} + +.GraphSchedule .flag { + position: absolute; + width: 10px; + height: 60px; + background-color: transparent; + bottom: 19px; +} + +.GraphSchedule .flag .container { + position: relative; + width: 10px; + bottom: -1px; + height: 60px; +} + +.GraphSchedule .flag .rod { + position: absolute; + width: 2px; + height: 55px; + left: 0; + background-color: brown; + bottom: 0; + border-width: 0 1px 0 0; + border-style: solid; + border-color: #aaa; +} + +.GraphSchedule .flag p { + position: absolute; + width: 15px; + left: 3px; + background-color: #f88; + height: 15px; + top: -10px; + background-image: -webkit-linear-gradient(left, #F88 18%, #E31448 59%); +} + +.GraphSchedule .flag .hint { + display: none; + position: absolute; + width: 42px; + height: 18px; + top: 8px; + left: 2px; + background-color: #FF9; + -webkit-border-radius: 5px 5px 5px; + position: absolute; +} + +.currentTimeBar { + position: absolute; + height: 100px; + background-color: yellow; + width: 2px; + bottom: -20px; + border-width: 0 1px 0 0; + border-style: solid; + border-color: #FC0; +} \ No newline at end of file diff --git a/css/jquery.ui.layout.css b/css/jquery.ui.layout.css new file mode 100644 index 0000000..bcecced --- /dev/null +++ b/css/jquery.ui.layout.css @@ -0,0 +1,33 @@ +.ui-myHeader { + background-color: #293d5e; + height: 70px; +} + +.ui-myContent { + overflow: auto; + -webkit-box-flex: 1; + height: 0px; +} + +.ui-myFooter { + background-color: #192333; + height: 70px; +} + +.ui-myExit { + float: right; + background-color: #555; + width: 70px; + height: 50px; + margin-top: 10px; + margin-right: 10px; + border-radius: 5px; + background-image: url('../images/00_winset_Back.png'); + background-repeat: no-repeat; + background-position: center center; + background-size: 50%; +} + +.ui-myHeader { + text-align: center; +} \ No newline at end of file diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..e1eff8f --- /dev/null +++ b/css/style.css @@ -0,0 +1,279 @@ +#one { + background-image: url('../images/background.png'); +} + +.screen { + font-family: serif; + color: white; + font-size: 17pt; +} + +#sentence { + margin: 10px 30px 0 30px; + position: relative; + float: right; + text-indent: 30px; +} + +#signature { + margin: 0px 30px 0 auto; + position: relative; + float: right; +} + +#logo { + font-family: courier; + font-size: 36px; + width: 100%; + background-repeat: no-repeat; + margin: 0px 40px 0 auto; + color: white; + border-bottom: 1px solid white; + font-weight: bolder; + text-align: right; + line-height: 43px; +} + +#status { + width: 400px; + height: 350px; + margin: 0 auto; + background-repeat: no-repeat; +} + +#status.lazy { + background-image: url('../images/state_lazy.png'); +} + +#status.run { + background-image: url('../images/state_run.png'); +} + +#communicate { + font-size: 17pt; + text-align: center; + min-height: 70px; +} + +#communicate.onAlert { + font-size: 60px; + text-align: center; + text-shadow: 5px 5px 5px black; +} + +#communicate b { + font-size: 25px; +} + +#communicate div { + font-size: 32px; + font-weight: bold; +} + +.schedule { + height: 100px; +} + +.scheduleOptions { + height: 100px; +} + +.increasingStrength { + display: none; +} + +.strength { + margin: 0 0 20px 0; +} + +.timeRangeLabel { + height: 50px; +} + +.activeStatus { + display: inline; + float: right; + color: blue; +} + +.activeStatusDisable { + color: red; +} + +.times li { + float: left; + width: 200px; +} + +.times { + width: 100%; + float: left; + margin: 0; + padding: 10px; +} + +.times .ui-datefield { + font-size: 60px; +} + +/** + +old styles; + +*/ +.exerciseView { + width: 80%; + height: 200px; + border-width: 1px; + border-color: #888 #DDD #DDD #888; + border-style: solid; + background-color: #578; + -webkit-border-radius: 0.6em; + -webkit-box-align: center; + margin: 0 auto; + box-shadow: 0px 0px 20px #000; + position: relative; +} + +.optionsExercises label { + line-height: 44px; +} + +#exercises { + margin-bottom: 10px; +} + +.myHeader { + +} + +.ui-controlgroup .ui-radio { + width: 20%; +} + +.myContent { + margin: auto 10px 10px 10px; +} + +.footerContent { + margin: 0 auto; + text-align: center; + padding-top: 20px; +} + +.newExerciseName label { + width: 100%; +} + +.newExerciseName input { /*background-color:white;*/ + +} + +.ui-footer { + z-index: 1000; +} + +#availableTime { + margin-top: 12px; + overflow: hidden; + margin-left: 0; + margin-right: 0; +} + +#availableTime li { + margin-left: 0; + margin-right: 0; +} + +.ui-swipelist-item .ui-btn { + padding: 4px; + margin-right: 12px; +} + +.ui-li-text-sub-right { + font-size: 18px; + margin-right: 17px; + float: right; + margin-top: 2px; + color: #08f; +} + +.disabled { + color: #f00; +} + +ul { + list-style-type: none; +} + +.goToOptionsPack { + width: 100%; +} + +.goToOptions { + width: 80%; + margin-left: 10%; + margin-right: 10%; +} + +.buttonOptions { + float: right; + box-shadow: 0px 0px 10px #000; +} + +.buttonStop { + background-color: #F40; + box-shadow: 0px 0px 10px #000; +} + +.selectPeriodType .ui-radio { + width: 100%; +} + +#formEnablePeriod { + margin-bottom: 20px; +} + +#ok_wait { + position: absolute; + bottom: 0; + width: 100%; +} + +#ok_wait .ok { + left: 50px; +} + +#ok_wait .wait { + right: 50px; + position: absolute; +} + +span.meterDesc { + position: absolute; + z-index: 100; + display: block; + text-align: center; + width: inherit; + padding-top: 8px; + color: #333; +} + +.enableOption { + padding-bottom: 20px; +} + +.enableOption>div { + display: table-cell; +} + +.enableOption>label { + float: left; + padding-right: 20px; + padding-top: 8px; + vertical-align: middle; +} + +div.period { + padding-left: 10px; +} diff --git a/icon.png b/icon.png new file mode 100755 index 0000000..983c883 Binary files /dev/null and b/icon.png differ diff --git a/images/background.png b/images/background.png new file mode 100644 index 0000000..532ca02 Binary files /dev/null and b/images/background.png differ diff --git a/images/longBothHorizonGradient.png b/images/longBothHorizonGradient.png new file mode 100644 index 0000000..946d3cd Binary files /dev/null and b/images/longBothHorizonGradient.png differ diff --git a/images/longBothHorizonGradient2.png b/images/longBothHorizonGradient2.png new file mode 100644 index 0000000..5ff092a Binary files /dev/null and b/images/longBothHorizonGradient2.png differ diff --git a/images/markers.png b/images/markers.png new file mode 100644 index 0000000..23feef7 Binary files /dev/null and b/images/markers.png differ diff --git a/images/state_lazy.png b/images/state_lazy.png new file mode 100644 index 0000000..c2e67dd Binary files /dev/null and b/images/state_lazy.png differ diff --git a/images/state_run.png b/images/state_run.png new file mode 100644 index 0000000..a72d60e Binary files /dev/null and b/images/state_run.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..96d82bf --- /dev/null +++ b/index.html @@ -0,0 +1,237 @@ + + + + + + + + +ExercisePlanner + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+
+
+
+
+ +
+ + + +
+
+ +
+
+

ExercisePlanner > options

+
+ +
+
+ +
+
Frequency
+
+ + + + + + + + + + +
+
+ +
+ +
+ +
+ +
+
Strength
+
+ + + + + + + + + + +
+
+ +
+ + +
+ + add time +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    +
    +

    ExercisePlanner > select workouts

    +
    + +
    +
    +
    Select workouts
    +
      +
    • bends +
    • + +
    • custom +
    • +
    + add + custom workout +
    +
    + +
    +
    + +
    +
    +

    ExercisePlanner > add custom workout

    +
    + +
    +
    +
    Enter workout name:
    + + +
    +
    + +
    +
    + +
    +
    +

    add time

    +
    + +
    +
    +
      +
    • Start: + + hour +
    • +
    • Duration: + + hours +
    • +
    + +
    + Choose a type of period: + + + + + + + + +
    +
    + Enabled + +
    +
    + + +
    + +
    +
    + + + diff --git a/js/GraphSchedule.js b/js/GraphSchedule.js new file mode 100644 index 0000000..b19ed7c --- /dev/null +++ b/js/GraphSchedule.js @@ -0,0 +1,191 @@ +/*jslint devel: true*/ +/*global $ */ +/** + * Constructor + * + * @param {object} params + * @returns + */ +function GraphSchedule(params) { + "use strict"; + this.init(params); +} + +(function () { + "use strict"; + GraphSchedule.prototype = { + template: '', + ui: null, + $flag: null, + $graph: null, + timeRanges: { + workday: [], + weekend: [] + }, + flags: [] + }; + + GraphSchedule.prototype.createUI = function createUI() { + console.log('GraphSchedule..createUI'); + var $tmp = $('
    '); + + $tmp.html(this.template); + this.$flag = $tmp.find('.flag'); + this.$graph = $tmp.find('.GraphSchedule'); + this.ui = this.$graph; + + this.addCurrentTimeBar(); + this.showFlags(); + + this.center(); + }; + + GraphSchedule.prototype.center = function center() { + // set scroll position; + this.$graph[0].scrollLeft = 1000 * ((new Date().getHours() - 4) / 24); + }; + + GraphSchedule.prototype.refresh = function refresh() { + this.updateTimeRanges(); + this.showFlags(); + this.center(); + }; + + GraphSchedule.prototype.onTemplateLoad = function onTemplateLoad() { + console.log('GraphSchedule..onTemplateLoad'); + }; + + GraphSchedule.prototype.init = function init(params) { + var $loader = $('
    '); + + if (params && params.onSuccess) { + this.onTemplateLoad = params.onSuccess; + } + + this.flags = []; + $loader.load('templates/GraphSchedule.tmpl', null, function (data) { + console.log('$loader.onLoad'); + this.template = data; + this.createUI(); + this.onTemplateLoad(); + }.bind(this)); + }; + + /** + * + * @param {Array} times + * @returns + */ + GraphSchedule.prototype.pushTimeOfFlags = function pushTimeOfFlags(times) { + var i, count; + + // clear previous times; + this.flags = []; + + if (times instanceof Array) { + count = times.length; + for (i = 0; i < count; i += 1) { + if (times[i] instanceof Date) { + this.flags.push({ time: times[i] }); + } else { + throw {message: 'Bag argument at [' + i + '] element of Array. Expected {Date}'}; + } + } + } else { + throw {message: 'Bad argument. Expected {Array} of {Date}'}; + } + }; + + GraphSchedule.prototype.addCurrentTimeBar = function addCurrentTimeBar() { + console.log('addCurrentTimeBar()'); + // remove old time bar; + var $currentTimeBar = this.$graph.find('.currentTimeBar'), + currentTime = new Date(), + hours = currentTime.getHours(); + + if ($currentTimeBar.length === 0) { + // add new; + $currentTimeBar = $('
    '); + } + + if (hours < 10) { + hours = '0' + hours; + } + + this.$graph.find('.ranges .h' + hours).append($currentTimeBar); + $currentTimeBar.css('left', 100 * currentTime.getMinutes() / 60 + '%'); + + setTimeout(this.addCurrentTimeBar.bind(this), 5 * 60 * 1000); + }; + + GraphSchedule.prototype.addFlag = function addFlag(newFlag) { + console.log('addFlag()'); + var $flagClone, hours = newFlag.time.getHours(); + if (hours < 10) { + hours = '0' + hours; + } + $flagClone = this.$flag.clone(); + this.$graph.find('.grid td:contains(' + hours + ')').append($flagClone); + $flagClone.css('left', 100 * newFlag.time.getMinutes() / 60 + '%'); + }; + + GraphSchedule.prototype.showFlags = function showFlags() { + var i, len = this.flags.length; + // remove old flags; + this.removeFlags(); + + // add all flags to view; + for (i = 0; i < len; i += 1) { + this.addFlag(this.flags[i]); + } + + this.center(); + }; + + GraphSchedule.prototype.removeFlags = function removeFlags() { + this.$graph.find('.flag').remove(); + }; + + GraphSchedule.prototype.setTimeRanges = function setTimeRanges(ranges) { + this.timeRanges = ranges; + }; + + GraphSchedule.prototype.setVisibleWeekend = function (bool) { + var row = this.ui.find('.rangesWeekend'); + return bool ? row.show() : row.hide(); + }; + + GraphSchedule.prototype.setVisibleWorkdays = function (bool) { + var row = this.ui.find('.ranges'); + return bool ? row.show() : row.hide(); + }; + + /** + * Update time ranges on graph + * @param ranges {array} array of boolen, keys are hours, example: [false, false, false, true, true] + */ + GraphSchedule.prototype.updateTimeRanges = function updateTimeRanges() { + var i, len, hours; + + this.$graph.find('.th').removeClass('th'); + + // workdays; + hours = this.timeRanges.workday; + len = hours.length; + for (i = 0; i < len; i += 1) { + if (hours[i]) { + this.$graph.find('.ranges .h' + ((i < 10) ? '0' + i : i)).addClass('th'); + } + } + + //weekends; + hours = this.timeRanges.weekend; + len = hours.length; + for (i = 0; i < len; i += 1) { + if (hours[i]) { + this.$graph.find('.rangesWeekend .h' + ((i < 10) ? '0' + i : i)).addClass('th'); + } + } + }; + +}()); diff --git a/js/UI.js b/js/UI.js new file mode 100644 index 0000000..cc8bff7 --- /dev/null +++ b/js/UI.js @@ -0,0 +1,415 @@ +/*jslint devel: true, nomen: true*/ +/*global $, GraphSchedule, confirm, range, history */ +function UI() { + "use strict"; +} + +(function () { + "use strict"; + UI.prototype = { + sentence : { + 'lazy' : { + text : 'He does not seem to me to be a free man who does not sometimes do nothing.', + signature : 'Marcus T. Cicero' + }, + 'run' : { + text : 'A journey of a thousand miles begins with a single step.', + signature : 'Lao-tzu' + } + }, + graphSchedule : null, + app : null + }; + + UI.prototype.fillExercises = function (exercisesData) { + console.log('UI_fillExercises', exercisesData); + var i, len, self = this; + + $('#exercises').replaceWith( + $('
    ') + ); + for (i = 0, len = exercisesData.length; i < len; i += 1) { + $('#exercises').append( + $(this.getExercisesTemplate(exercisesData[i], i)) + ); + } + $('#exercises :jqmData(role="slider")').slider(); + + $('#exercises :jqmData(role="slider")').on('change', function (e) { + console.log('slider:change'); + self.app.saveExercises(self.getExercisesList()); + }); + }; + + UI.prototype.fillTimesRanges = function (timesData) { + console.log('UI_fillTimesRanges', timesData); + var self = this, len, i; + + $('#availableTime').replaceWith( + $('
      ') + ); + for (i = 0, len = timesData.length; i < len; i += 1) { + $('#availableTime') + .append($(this.getAvailableTimeTemplate(timesData[i]))); + } + $('#availableTime').trigger('create'); + $('#availableTime').listview(); + $('#availableTime :jqmData(name=edit)').on('tap', function (e) { + e.preventDefault(); + e.stopPropagation(); + self.editTimeRange($(this).data('val')); + }); + $('#availableTime :jqmData(name=disable)').on('tap', function (e) { + e.stopPropagation(); + self.app.disableTimeRange($(this).data('val')); + }); + $('#availableTime :jqmData(name=delete)').on('tap', function (e) { + e.stopPropagation(); + if (confirm('Are you sure?')) { + self.app.deleteTimeRange($(this).data('val')); + } + }); + }; + + UI.prototype.fillTimeRangeForm = function fillTimeRangeForm(timeRange) { + console.log('UI.fillTimeRangeForm', timeRange); + var tmpData = new Date(); + // Filling form; + $('#startTime').attr('data-val', + new Date(tmpData.setHours(timeRange.start))); + $('#duration').attr('data-val', timeRange.duration); + + if ($('#startTime').data('datetimepicker')) { + $('#startTime').data('datetimepicker').options.date + .setHours(timeRange.start); + $('#startTime').data('datetimepicker').ui + .find('.ui-datefield-hour').html( + (timeRange.start < 10) ? '0' + timeRange.start : timeRange.start + ); + } + if ($('#duration').data('datetimepicker')) { + $('#duration').data('datetimepicker').options.date + .setHours(timeRange.duration); + $('#duration').data('datetimepicker').ui.find('.ui-datefield-hour') + .html( + (timeRange.duration < 10) ? '0' + + timeRange.duration : timeRange.duration + ); + $('#duration').data('datetimepicker')._populateDataSelector = function (field, pat) { + var result = $.tizen.datetimepicker.prototype._populateDataSelector + .call(this, field, pat); + result.values = range(1, 20); + result.data = range(1, 20); + return result; + }; + } + + $('#formEnablePeriod')[0].value = timeRange.enabled ? 'on' : 'off'; + $('#formEnablePeriod').slider('refresh'); + }; + + UI.prototype.editTimeRange = function (nr, event) { + console.log('UI_editTimerange', nr, this); + if (event && typeof event.stopPropagation === 'function') { + event.preventDefault(); + event.stopPropagation(); + } + + if (this.app.editTimeRange(nr) >= 0) { + $('#updateTime').val('modify'); + } else { + $('#updateTime').val('add'); + } + + // change page to form; + $.mobile.changePage("#rangesOfTimes"); + }; + + UI.prototype.getExercisesList = function () { + console.log('UI_getExercisesList'); + return $('#exercises :jqmData(role=slider)').map(function (o, v) { + return ({ + index : $(v).attr('data-index'), + checked : ($(v).val() === 'on') ? true : false + }); + }); + }; + + UI.prototype.addExercise = function () { + console.log('UI_addExercise'); + if ($("#newExerciseName").val()) { + if (this.app.addExercise($("#newExerciseName").val())) { + history.back(); + } + } else { + this.showErrors([ { + name : 'Name of exercise is not correct.', + code : 100 + } ]); + } + }; + + UI.prototype.configToUI = function () { + console.log('UI_configToUI'); + }; + + UI.prototype.updateMainUI = function () { + console.log('updateMainUI'); + this.setStatusRun(this.app.config.trainingEnabled); + console.log('updateMainUI: graphSchedule: ', this.graphSchedule); + this.graphSchedule.setTimeRanges(this.app.periodsWeekToBoolArray()); + }; + + UI.prototype.getTimeRangeFromForm = function () { + return { + start : $('#startTime').data('datetimepicker').options.date + .getHours(), + duration : $('#duration').data('datetimepicker').options.date + .getHours(), + stop : $('#startTime').data('datetimepicker').options.date + .getHours() + + $('#duration').data('datetimepicker').options.date + .getHours(), + style : $('.selectPeriodType :radio:checked').val(), + enabled : ($('#formEnablePeriod')[0].value === 'on' ? true : false) + }; + }; + + UI.prototype.editTimeRangeAction = function (nr) { + console.log('UI_editTimeRangeAction', nr); + if (this.app.saveTimeRange(nr, this.getTimeRangeFromForm())) { + history.back(); + } else { + throw ({ + message : 'Time start and stop is not present.', + code : 1 + }); + } + }; + + UI.prototype.showNoticeInMonitor = function (notice, alarm) { + console.log('UI_showNoticeInMonitor', notice, alarm); + $('#communicate').html(notice); + $('#communicate').toggleClass('onAlert', alarm); + }; + + UI.prototype.changeButtonAddTime = function (text) { + console.log('UI_changeButtonAddTime', text); + $('#addTime').html(text); + }; + + UI.prototype.showErrors = function (errors) { + console.log('UI_showErrors', errors); + var i; // count; + for (i = 0; i < errors.length; i += 1) { + alert(errors[i].name); + } + }; + + UI.prototype.showAlarmInMonitor = function (data) { + console.log('UI_showAlarmInMonitor', data); + var notice = ''; + function formatNN(val) { + return (val < 10) ? ('0' + val) : val; + } + + if (data && data.alarm && this.app.config.trainingEnabled) { + this.app.currentAlarm = this.app.findCurrentAlarm(); + if (this.app.currentAlarm.length > 0) { + notice += 'Go... go... go...!
      ' + data.exerciseName + ' x ' + + data.numberOfTimes; + } else { + notice += 'Next exercises set at: ' + + formatNN(data.alarm.getHours()) + ':' + + formatNN(data.alarm.getMinutes()) + '
      ' + + data.exerciseName + ' x ' + data.numberOfTimes; + } + } else { + notice += '
      You have no workouts scheduled for now.
      '; + } + this.showNoticeInMonitor(notice, false); + }; + + UI.prototype.getSentence = function (type) { + console.log('UIgetSentence', type); + return (this.sentence[type] || { + text : 'No sentence', + signature : 'anonymous' + }); + }; + + UI.prototype.setSentence = function (type) { + console.log('UI_setSentence'); + var sentence = this.getSentence(type); + $('#sentence').html('"' + sentence.text + '"'); + $('#signature').html('- ' + sentence.signature); + }; + + UI.prototype.showWaitOk = function () { + console.log('UI_showWaitOk'); + $('#mainControl').hide(); + $('#one .ui-btn-back').hide(); + + $('#onAlertControl').tabbar(); + $('#onAlertControl').show(); + $('#onAlertControl').css('width', ''); + }; + + UI.prototype.setStatusRun = function (bool) { + console.log('UI_setStatusRun', bool); + if (bool) { + // icon; + $('#status').removeClass('lazy').addClass('run'); + // sentence; + this.setSentence('run'); + // button in control bar; + $('#startStop .ui-btn-text').html('stop training'); + } else { + $('#status').removeClass('run').addClass('lazy'); + this.setSentence('lazy'); + $('#communicate').html(''); + $('#startStop .ui-btn-text').html('start training'); + } + }; + + UI.prototype.bindEvents = function bindEvents() { + var self = this; + + // bind events; + $('#one .ui-btn-back').on('tap', this.app.exit.bind(this.app)); + + $('#ok').on('tap', self.app.ok.bind(self.app)); + $('#wait').on('tap', self.app.wait.bind(self.app)); + $('#todayOffAll').on('tap', self.app.todayOffAll.bind(self.app)); + + $('#startStop').on('tap', function () { + self.app.appStartStop(); + }); + + $('#one').on( + 'pageshow', + function (page, options) { + console.log('#one pageshow'); + if (self.graphSchedule.ui) { + $('#one .schedule').append(self.graphSchedule.ui); + self.app.updateGraph(); + self.graphSchedule.refresh(); + self.graphSchedule.setVisibleWeekend(!self.app + .todayIsWorkday()); + self.graphSchedule.setVisibleWorkdays(self.app + .todayIsWorkday()); + } + $('#one .schedule').on('touchstart', function (ev) { + ev.stopPropagation(); + }); + } + ); + + $('#two').on('pageshow', function (page, options) { + console.log('#two pageshow'); + if (self.graphSchedule.ui) { + $('#two .scheduleOptions').append(self.graphSchedule.ui); + self.graphSchedule.refresh(); + self.graphSchedule.setVisibleWeekend(true); + self.graphSchedule.setVisibleWorkdays(true); + } + }); + + $('#two').on('pageinit', function (page, options) { + console.log('#two pageinit'); + $('.ui-radio input', $('#frequency')).change(function (ev) { + self.app.setFrequency(this.value); + self.updateMainUI(); + }); + + $('.ui-radio input', $('#strength')).change(function (ev) { + self.app.setStrength(this.value); + self.updateMainUI(); + }); + + $('#frequency')[0].select(self.app.config.frequency); + $('#strength')[0].select(self.app.config.strength); + + $('#two .scheduleOptions').append(self.graphSchedule.ui); + $('#two .scheduleOptions').on('touchstart', function (ev) { + ev.stopPropagation(); + }); + + $('#workdaysType').on('tap', function (ev) { + self.app.changeTypeOfPeriods('workday'); + }); + + $('#weekendType').on('tap', function (ev) { + self.app.changeTypeOfPeriods('weekend'); + }); + + $('#addTimeRange').on('tap', self.editTimeRange.bind(self, -1)); + + self.app.updateTimesRanges(); + self.configToUI(); + }); + + $('#selectExercises').on('pageinit', function (page, options) { + self.app.updateExercises(); + self.configToUI(); + + $('#btnNewExercise').on('tap', self.addExercise.bind(self)); + }); + + $('#customExercises').on('pageshow', function (page, options) { + $('#newExerciseName').val(''); + $('#newExerciseName').trigger('focus'); + }); + + $('#rangesOfTimes').on('pageinit', function (page, options) { + console.log('#rangesOfTimes init'); + $("#updateTime").on("tap", function (e) { + e.preventDefault(); + e.stopPropagation(); + self.editTimeRangeAction(self.app.currentEditingTimePeriodId); + }); + }); + + $('#rangesOfTimes').on('pageshow', function (page, options) { + console.log('#rangesOfTimes pageshow'); + $('#updateTime').data('button').refresh(); + self.fillTimeRangeForm(self.app.currentEditingTimePeriod); + }); + + $('#increasingStrength').on('change', function () { + self.app.config.increasingStrength = this.checked; + self.app.saveConfig(); + + self.configToUI(); + }); + }; + + UI.prototype.onGraphSchedule = function onGraphSchedule(onInitEnd) { + console.log('(UI..initialize).onGraphSchedule'); + this.updateMainUI(); + + $('#one .schedule').append(this.graphSchedule.ui); + this.app.updateGraph(); + this.graphSchedule.refresh(); + this.graphSchedule.setVisibleWeekend(!this.app.todayIsWorkday()); + this.graphSchedule.setVisibleWorkdays(this.app.todayIsWorkday()); + + if (typeof onInitEnd === 'function') { + onInitEnd(); + } + }; + + UI.prototype.initialize = function (onInitEnd) { + console.log('UI..initialize'); + + this.bindEvents(); + + $('html').css('font-size', ''); + $('body').css('font-size', ''); + + this.graphSchedule = new GraphSchedule({ + onSuccess : this.onGraphSchedule.bind(this, onInitEnd) + }); + }; + +}()); diff --git a/js/UI.simpleTemplate.js b/js/UI.simpleTemplate.js new file mode 100644 index 0000000..3f32b5d --- /dev/null +++ b/js/UI.simpleTemplate.js @@ -0,0 +1,25 @@ +/*global UI */ +(function () { + "use strict"; + UI.prototype.getAvailableTimeTemplate = function getAvailableTimeTemplate(obj) { + return '
    • ' + '
      start: ' + obj.start + + ' (duration: ' + obj.duration + 'h) ' + obj.style + '
      ' + + '
      ' + + '
      Edit
      ' + + '
      ' + + ((obj.enabled) ? 'Dis' : 'En') + 'able
      ' + + '
      Delete
      ' + + '
      ' + ((obj.enabled) ? 'En' : 'Dis') + 'able
      ' + + '
      ' + '
    • '; + }; + + UI.prototype.getExercisesTemplate = function (obj, i) { + return '
      ' + + '' + obj.name + '' + + '' + '
      '; + }; +}()); + diff --git a/js/app.alarms.js b/js/app.alarms.js new file mode 100644 index 0000000..e9a82e6 --- /dev/null +++ b/js/app.alarms.js @@ -0,0 +1,58 @@ +/*jslint devel: true*/ +/*global ExercisePlanner:false, tizen:false*/ +/** + * Methods for add / remove alarms by API; + */ +(function () { + "use strict"; + /** + * Wrapper on remove all alarms joined with app + */ + ExercisePlanner.prototype.removeAllAlarms = function () { + console.log('ExercisePlanner_removeAllAlarms'); + tizen.alarm.removeAll(); + }; + + ExercisePlanner.prototype.WORKDAYS = ["MO", "TU", "WE", "TH", "FR"]; + ExercisePlanner.prototype.WEEKEND = ["SA", "SU"]; + + /** + * Add alarms from Array + * + * @param tabOfAlarm + * @param defOfPeriod + */ + ExercisePlanner.prototype.addAlarmFromArray = function addAlarmFromArray(tabOfAlarm, defOfPeriod) { + var i, len = tabOfAlarm.length, alarm; + + console.log('tabOfAlarm:', tabOfAlarm); + console.log('definition of period:', defOfPeriod); + for (i = 0; i < len; i += 1) { + alarm = new tizen.AlarmAbsolute(tabOfAlarm[i], defOfPeriod); + try { + tizen.alarm.add(alarm, this.selfId); + } catch (e) { + console.error(e.message); + } + } + }; + + /** + * Add alarms to API DataBase + * + * @param {object} alarms + */ + ExercisePlanner.prototype.addAlarmsAllWeek = function addAlarmsAllWeek(alarms) { + if (alarms.everyday.length > 0) { + this.addAlarmFromArray(alarms.everyday, tizen.alarm.PERIOD_DAY); + } + if (alarms.workday.length > 0) { + this.addAlarmFromArray(alarms.workday, this.WORKDAYS); + } + if (alarms.weekend.length > 0) { + this.addAlarmFromArray(alarms.weekend, this.WEEKEND); + } + }; + +}()); + diff --git a/js/app.alarmsGenerating.js b/js/app.alarmsGenerating.js new file mode 100644 index 0000000..b868dad --- /dev/null +++ b/js/app.alarmsGenerating.js @@ -0,0 +1,158 @@ +/*jslint devel: true*/ +/*global ExercisePlanner: true, ONE_DAY:false, TIME_OF_SLEEP:false */ +/** + * + */ +(function () { + "use strict"; + /** + * + * + * @param availableTime + * @param typeOfPeriod + * @returns {object} + */ + ExercisePlanner.prototype.calculateFactor = function calculateFactors(availableTime, typeOfPeriod) { + var factor, + result = { + count: 0, + period: 0 + }; + + if (availableTime === 0) { + return result; + } + + factor = availableTime / (this.ONE_DAY - this.TIME_OF_SLEEP); + result.proportionalFrequency = this.getNumberOfWorkoutsByFrequency(this.config.frequency[typeOfPeriod]) * factor; + + if ((Math.round(result.proportionalFrequency) - 1) > 0) { + result.count = (Math.round(result.proportionalFrequency)); + result.period = availableTime / (result.count - 1); + } else { + result.count = 1; + result.period = 0; + } + + return result; + }; + + /** + * + * + * @param availableTime + * @returns {object} + */ + ExercisePlanner.prototype.calculateFactors = function calculateFactors(availableTime) { + return { + weekend: this.calculateFactor(availableTime.weekend, 'weekend'), + workday: this.calculateFactor(availableTime.workday, 'workday'), + everyday: this.calculateFactor(availableTime.everyday, 'workday') + }; + }; + + /** + * + * + * @param factor + * @param tmpPeriods + * @returns {Array} + */ + ExercisePlanner.prototype.generateAlarmsByFactor = function (factor, tmpPeriods) { + console.log('ExercisePlanner_generateAlarmsByFactor', factor, tmpPeriods); + var i, numberOfPeriods = tmpPeriods.length, + period, + begin, + end = 0, + tableOfAlarms = [], + optimalTime, + deltaTime = 0, + dayTime = this.beginDate || new Date(); + + if (numberOfPeriods === 0) { + return []; + } + + begin = tmpPeriods[0].start; + + for (i = 0; i < numberOfPeriods; i += 1) { + if (tmpPeriods[i].stop > end) { + end = tmpPeriods[i].stop; + } + } + + dayTime.setSeconds(0); + console.log('generateAlarmsByFactor: end | begin ', end, begin); + + if (factor.count === 1) { + // One alarm per day, default placed in middle of available time; + optimalTime = this.findNearestTimeRange((end + begin) / 2, tmpPeriods); + console.log('optimale Hour', optimalTime); + + dayTime.setHours(parseInt(optimalTime.optimalHour, 10)); + dayTime.setMinutes(60 * (optimalTime.optimalHour - parseInt(optimalTime.optimalHour, 10))); + tableOfAlarms.push(new Date(dayTime.getTime())); + } else { + // set time for begin; + dayTime.setHours(tmpPeriods[0].start); + dayTime.setMinutes(0); + tableOfAlarms.push(new Date(dayTime.getTime())); + + // set time for next hop; + for (i = 0; i < numberOfPeriods; i += 1) { + period = tmpPeriods[i]; + deltaTime += period.duration; + // if available period is too small, then accumulate time + // and continue to next period; + console.log('delta & factor: ', deltaTime, factor.period); + while (deltaTime >= factor.period * 0.999) { + deltaTime -= factor.period; + + dayTime.setHours(parseInt(period.stop - deltaTime, 10)); + dayTime.setMinutes(60 * (period.stop - deltaTime - parseInt(period.stop - deltaTime, 10))); + + tableOfAlarms.push(new Date(dayTime.getTime())); + } + } + } + + return tableOfAlarms; + }; + + /** + * Generate table of alarms => this.alarms + * @param {Date} customDate; + */ + ExercisePlanner.prototype.generateAlarms = function () { + console.log('ExercisePlanner_generateAlarms'); + var factors, + alarms = this.alarms, + periodsWeek = { + everyday: [], + workday: [], + weekend: [] + }; + + // some periods may overlap, must be merged + periodsWeek = this.mergePeriods(); + // store in cache for later reuse + this.cache.periodsWeek = periodsWeek; + + // factors to correct how often may workouts per day + factors = this.calculateFactors(this.getSummaryAvailableTime()); + + // Set new alarms; + if (periodsWeek.everyday.length > 0) { + alarms.everyday = this.generateAlarmsByFactor(factors.everyday, periodsWeek.everyday); + } else { + alarms.workday = this.generateAlarmsByFactor(factors.workday, periodsWeek.workday); + alarms.weekend = this.generateAlarmsByFactor(factors.weekend, periodsWeek.weekend); + } + + // if trainig is in run then resinstall alarm imediately + if (this.config.trainingEnabled) { + this.startAlarms(); + } + }; +}()); + diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..46f0697 --- /dev/null +++ b/js/app.js @@ -0,0 +1,471 @@ +/*jslint browser: true, devel: true */ +/*global tizen, $, app, localStorage, Audio, document, unlockScreen, UI */ +var ExercisePlanner = function () { + "use strict"; +}; + +(function () { + "use strict"; + + ExercisePlanner.prototype = { + /** + * Definition of time for sleep + */ + TIME_OF_SLEEP: 8, + + /** + * Definition one day in hours + */ + ONE_DAY: 24, + + /** + * Stored time of application start + */ + applicationStartTime: new Date(), + + /** + * Cofiguration data will saved for next launch; + * There are default values after install; + */ + config: { + + frequency: { + workday: 3, // 6 for test + weekend: 3 + }, + + strength: { + workday: 1, + weekend: 1 + }, + + /** + * List of workouts; + * - timeRanges:style [ everyday, weekend, workday ]; ( workday : mon-fri ) + */ + exercises: [{ + name: 'bends', + enabled: true + }, { + name: 'squats', + enabled: true + }, { + name: 'push-ups', + enabled: false + }], + + // deprecated for this version; + increasingStrength: true, + + /** + * Default time ranges + */ + timesRanges: [{ + nr: 0, + start: 8, + stop: 16, + duration: 8, + enabled: true, + style: 'everyday' + }, { + nr: 1, + start: 18, + stop: 22, + duration: 4, + enabled: true, + style: 'weekend' + }], + + nearestExercise: -1, + + count: 0, + + trainingEnabled: false + }, + alarms: { + everyday: [], + workday: [], + weekend: [] + }, + + /** + * Used for update GraphSchedule; + * [ workday / weekend ] + */ + currentTypeOfPeriods: 'workday', + /** + * Use on form to edit time period; + */ + currentEditingTimePeriod: null, + currentEditingTimePeriodId: -1, + + /** + * Date when alarm will start generating + */ + beginDate: null, + + /** + * use store temporary data for alarms; + */ + cache: {}, + + /** + * HTML5 audio element for play audio when alarm is called + */ + audioOfAlert: null, + + /** + * Instance of User Interface + */ + ui: null + }; + + /** + * Load configuration of application + * (use localStorage) + */ + ExercisePlanner.prototype.loadConfig = function () { + console.log('ExercisePlanner_loadConfig'); + var configStr = localStorage.getItem('config'); + if (configStr) { + this.config = JSON.parse(configStr); + } else { + this.removeAllAlarms(); + this.sortTimeRanges(); + } + }; + + /** + * Save configuration of application + * (use localStorage) + */ + ExercisePlanner.prototype.saveConfig = function () { + console.log('ExercisePlanner_saveConfig'); + localStorage.setItem('config', JSON.stringify(this.config)); + }; + + ExercisePlanner.prototype.stopTraining = function () { + console.log('ExercisePlanner_stopTraining'); + this.removeAllAlarms(); + this.ui.setStatusRun(this.config.trainingEnabled); + }; + + ExercisePlanner.prototype.startTraining = function () { + console.log('ExercisePlanner_startTraining'); + this.ui.setStatusRun(this.config.trainingEnabled); + this.startAlarms(); + }; + + /** + * Toggle start/stop alarms for workouts + */ + ExercisePlanner.prototype.appStartStop = function () { + console.log('ExercisePlanner_appStartStop'); + this.config.trainingEnabled = !this.config.trainingEnabled; + if (this.config.trainingEnabled) { + this.startTraining(); + } else { + this.stopTraining(); + } + this.saveConfig(); + }; + + /** + * Closing application with the configuration data saving + */ + ExercisePlanner.prototype.exit = function () { + console.log('ExercisePlanner_exit'); + this.saveConfig(); + this.stopMusic(); + tizen.application.getCurrentApplication().exit(); + }; + + /** + * Sets frequency value and calculates new alarms + * @param value + */ + ExercisePlanner.prototype.setFrequency = function (value) { + console.log('ExercisePlanner_setFrequency', value); + this.config.frequency[this.config.currentTypeOfPeriods] = parseInt(value, 10); + + this.saveConfig(); + this.generateAlarms(); + this.updateGraph(this.config.currentTypeOfPeriods); + this.showNextAlarm(); + }; + + /** + * Set Strength value + * @param value + */ + ExercisePlanner.prototype.setStrength = function (value) { + console.log('ExercisePlanner_setStrength', value); + this.config.strength[this.config.currentTypeOfPeriods] = parseInt(value, 10); + this.saveConfig(); + }; + + /** + * Sending array of exercises to UI for update + */ + ExercisePlanner.prototype.updateExercises = function () { + console.log('ExercisePlanner_updateExercises'); + this.ui.fillExercises(this.config.exercises); + }; + + /** + * Sending array of time ranges to UI for update + * & update graph schedule + */ + ExercisePlanner.prototype.updateTimesRanges = function () { + console.log('ExercisePlanner_updateTimesRanges'); + + this.ui.fillTimesRanges(this.config.timesRanges); + this.ui.graphSchedule.updateTimeRanges(); + }; + + /** + * Store exercises in config (and save) + * @param newData + */ + ExercisePlanner.prototype.saveExercises = function (newData) { + console.log('ExercisePlanner_saveExercises', newData); + var i, l; + + if (newData) { + for (i = 0, l = newData.length; i < l; i += 1) { + this.config.exercises[i].enabled = newData[i].checked; + } + this.generateNearestExercise(); + this.saveConfig(); + } + }; + + /** + * When will earliest workout + * and show in UI + */ + ExercisePlanner.prototype.showNextAlarm = function showNextAlarm() { + console.log('ExercisePlanner_showNextAlarm'); + var alarms, + currentDate = new Date(); + + if (this.alarms.everyday.length > 0) { + alarms = this.alarms.everyday; + } else { + alarms = (this.todayIsWorkday()) ? this.alarms.workday : this.alarms.weekend; + } + + alarms = alarms.filter(function (item) { + return (item.getTime() > currentDate.getTime()); + }).sort(function (a, b) { + return a.date - b.date; + }); + console.log(alarms); + + if (this.config.nearestExercise > -1) { + this.ui.showAlarmInMonitor({ + alarm: alarms[0], + exerciseName: this.config.exercises[this.config.nearestExercise].name, + numberOfTimes: this.getStrength(this.config.strength.workday, this.config.count) + }); + + this.config.count += 1; + } + this.saveConfig(); + }; + + /** + * Change type of periods [workday/weekend] and update graph + * @param type + */ + ExercisePlanner.prototype.changeTypeOfPeriods = function changeTypeOfPeriods(type) { + if (this.config.currentTypeOfPeriods !== type) { + this.config.currentTypeOfPeriods = type; + this.updateGraph(this.config.currentTypeOfPeriods); + } + }; + + /** + * Check new exercise name for duplication in existings; + * @param name + * @returns + */ + ExercisePlanner.prototype.checkExerciseName = function (name) { + console.log('ExercisePlanner_checkExerciseName', name); + var i, l; + + if (name) { + for (i = 0, l = this.config.exercises.length; i < l; i += 1) { + if (this.config.exercises[i].name === name) { + return i; + } + } + return -1; + } + }; + + /** + * Add new exercise sent from UI + * @param name + * @returns {Boolean} + */ + ExercisePlanner.prototype.addExercise = function (name) { + console.log('ExercisePlanner_addExercise', name); + if (this.checkExerciseName(name) < 0) { + this.config.exercises.push({ + name: name, + enabled: false + }); + this.saveConfig(); + this.ui.fillExercises(this.config.exercises); + return true; + } + this.ui.showErrors({name: 'Element exists!'}); + return false; + }; + + /** + * Get number of workouts by frequency + * @param value + * @returns {number} + */ + ExercisePlanner.prototype.getNumberOfWorkoutsByFrequency = function getNumberOfWorkoutsByFrequency(value) { + console.log('ExercisePlanner_frequencyMap', value); + var iMap = [1, 2, 4, 8, 16, 24, 150], + // -- times per 24h; proportion to set periods of available time; + numberOfWorkouts = iMap[value]; + + return numberOfWorkouts || 2; + }; + + /** + * Get number of exercises in workout by strength value and optional ; + * @param value + * @param count + * @returns {number} + */ + ExercisePlanner.prototype.getStrength = function strengthMap(value, count) { + console.log('ExercisePlanner_strengthMap', value, count); + var sMap = [1, 1, 2, 4, 10, 20], + base = sMap[value] || 2; + + count = count || 1; + return Math.round(base * (count / 10 + 1)); + }; + + /** + * Generate name of exercise for nearest workout + */ + ExercisePlanner.prototype.generateNearestExercise = function () { + console.log('ExercisePlanner_generateNearestExercise'); + var tmp = this.config.exercises.filter(function (item) { + return item.enabled; + }); + this.config.nearestExercise = parseInt(Math.random() * tmp.length, 10); + }; + + + /** + * If user want change work days this method will changing; + * @returns {Boolean} + */ + ExercisePlanner.prototype.todayIsWorkday = function todayIsWorkday() { + var day = (new Date()).getDay(); + return (day >= 1 && day <= 5); + }; + + /** + * Activate alarms in API. + */ + ExercisePlanner.prototype.startAlarms = function startAlarms() { + // clear old alarms; + this.removeAllAlarms(); + + // add new alarms + this.addAlarmsAllWeek(this.alarms); + + this.generateNearestExercise(); + this.showNextAlarm(); + }; + + /** + * Update Graph object + * @param {String} typeOfPeriods ['workday'|'weekend'] + */ + ExercisePlanner.prototype.updateGraph = function updateGraph(typeOfPeriods) { + console.log('updateGraph'); + var alarms; + if (!this.ui.graphSchedule) { + throw { + message: 'graph schedule not exists.' + }; + } + + typeOfPeriods = typeOfPeriods || ((this.todayIsWorkday()) ? 'workday' : 'weekend'); + console.log('typeOfPeriods: ', typeOfPeriods); + + if (typeOfPeriods === 'workday') { + alarms = this.alarms.workday; + } else { + alarms = this.alarms.weekend; + } + if (alarms.length === 0) { + alarms = this.alarms.everyday; + } + this.ui.graphSchedule.setTimeRanges(this.periodsWeekToBoolArray()); + this.ui.graphSchedule.pushTimeOfFlags(alarms); + this.ui.graphSchedule.showFlags(); + }; + + /** + * Callback function on visibility change; + */ + ExercisePlanner.prototype.onVisibilityChange = function () { + + switch (document.webkitVisibilityState) { + case 'visible': + this.applicationStartTime = new Date(); + this.currentAlarm = this.findCurrentAlarm(); + if (this.currentAlarm.length > 0) { + this.ui.showWaitOk(); + this.startMusic(); + } + break; + } + }; + + /** + * Turn off all alarms today + */ + ExercisePlanner.prototype.todayOffAll = function todayOffAll() { + // set begin date to tomorrow; + this.beginDate = new Date(); + this.beginDate.setDate(this.beginDate.getDate() + 1); + // recreate alarms; + this.generateAlarms(); + this.exit(); + }; + + // Initialize function + ExercisePlanner.prototype.init = function init() { + var onUiInitialize = function onUiInitialize() { + console.log('onUiInitialize'); + // register watcher on visibility change; + document.addEventListener('webkitvisibilitychange', this.onVisibilityChange.bind(this)); + this.showNextAlarm(); + this.onVisibilityChange(); + }.bind(this); + + console.log('ExercisePlanner..init'); + this.selfId = tizen.application.getAppContext().appId; + this.ui = new UI(); + this.ui.app = this; + + this.loadConfig(); + this.config.currentTypeOfPeriods = (this.todayIsWorkday()) ? 'workday' : 'weekend'; + + this.generateAlarms(); + + this.ui.initialize(onUiInitialize); + }; +}()); diff --git a/js/app.onAlarm.js b/js/app.onAlarm.js new file mode 100644 index 0000000..a619b83 --- /dev/null +++ b/js/app.onAlarm.js @@ -0,0 +1,70 @@ +/*global ExercisePlanner:false, tizen:false, Audio:false*/ +/** + * These method are using when alarm is call. + */ +(function () { + "use strict"; + ExercisePlanner.prototype.findCurrentAlarm = function () { + console.log('ExercisePlanner_findCurrentAlarm'); + var currentTimeInMinutes = parseInt(this.applicationStartTime.getTime() / 1000, 10), + listOfAlarms = tizen.alarm.getAll(); + + return listOfAlarms.filter(function (item) { + // alarm relative has not date property; + if (!item.date) { + return false; + } + + // +40/-10 seconds tolerance; + console.log(item.date); + if (parseInt(item.date.getTime() / 1000, 10) < (currentTimeInMinutes + 40) + && parseInt(item.date.getTime() / 1000, 10) > (currentTimeInMinutes - 10)) { + return true; + } + }); + }; + + ExercisePlanner.prototype.wait = function () { + console.log('ExercisePlanner_wait'); + // lastAlert -> change +1min + var snozeTime = 1, currentAlarm = this.currentAlarm, newDate = new Date(), alarm; + + if (currentAlarm) { + newDate.setMinutes(newDate.getMinutes() + snozeTime); + // period value must be set so application started by alert will know current alert; + alarm = new tizen.AlarmAbsolute(newDate, tizen.alarm.PERIOD_WEEK * 10); + tizen.alarm.add(alarm, this.selfId); + // -- remove old snooze alarm + if (currentAlarm.period === tizen.alarm.PERIOD_WEEK * 10) { + tizen.alarm.remove(currentAlarm.id); + } + this.stopMusic(); + } + + this.exit(); + // or tizen.application.hide(); + }; + + ExercisePlanner.prototype.ok = function () { + this.exit(); + }; + + ExercisePlanner.prototype.startMusic = function () { + console.log('ExercisePlanner_startMusic'); + + if (!this.audioOfAlert) { + this.audioOfAlert = new Audio(); + } + this.audioOfAlert.src = 'WebContent/Runner.mp3'; + this.audioOfAlert.load(); + this.audioOfAlert.play(); + }; + + ExercisePlanner.prototype.stopMusic = function () { + console.log('ExercisePlanner_stopMusic'); + if (this.audioOfAlert) { + this.audioOfAlert.pause(); + } + }; +}()); + diff --git a/js/app.timeRange.js b/js/app.timeRange.js new file mode 100644 index 0000000..8ccf962 --- /dev/null +++ b/js/app.timeRange.js @@ -0,0 +1,415 @@ +/*jslint devel: true*/ +/*global $, ExercisePlanner: true*/ +/** + * + */ +(function () { + "use strict"; + ExercisePlanner.prototype.checkNewTimeRange = function (start, duration, style) { + console.log('ExercisePlanner_checkNewTimeRange', start, duration, style); + var result = []; + // check start / stop + /* + * deprecated if (start >= stop) { result.push({ name: 'Start time is greater or equal than + * stop time.', code: 1 }); } + */ + + if (!start) { + result.push({ + name: 'Start time is not set.', + code: 2 + }); + } + + if (duration < 1 || duration > 24) { + result.push({ + name: 'Duration is not set properly.', + code: 2 + }); + } + + /* + * deprecated if (!stop) { result.push({ name: 'Stop time is not set.', code: 3 }); } + */ + return result; + }; + + /** + * Sort method for time ranges; + */ + ExercisePlanner.prototype.sortTimeRanges = function () { + console.log('ExercisePlanner_sortTimeRanges'); + this.config.timesRanges.sort(function (a, b) { + return a.start - b.start; + }); + }; + + /** + * Find and return max value of nr in time ranges array; + * @returns {Number} + */ + ExercisePlanner.prototype.getMaxNrOfTimeRange = function getMaxNrOfTimeRange() { + var maxNr = -1, i, len = this.config.timesRanges.length; + for (i = 0; i < len; i += 1) { + if (maxNr < this.config.timesRanges[i].nr) { + maxNr = this.config.timesRanges[i].nr; + } + } + return maxNr; + }; + + /** + * + * @param nr + * @returns {Boolean} + */ + ExercisePlanner.prototype.getTimeRangeByNr = function getTimeRangeByNr(nr) { + var result = this.config.timesRanges.filter(function (item) { + return (item.nr === nr); + }); + return result[0]; + }; + + /** + * Save time range + * + * @param nr + * @param start + * @param duration + * @param style + * @param enabled + * @returns {Boolean} + */ + ExercisePlanner.prototype.saveTimeRange = function (nr, timeRange) { + console.log('ExercisePlanner_saveTimeRange', nr, timeRange); + var index = -1, + errors = this.checkNewTimeRange(timeRange.start, timeRange.duration, timeRange.style); + console.log(errors); + + // new timeRanges has nr === -1; this mean we must get max number from config + if (nr === -1) { + nr = this.getMaxNrOfTimeRange() + 1; + } else { + index = this.config.timesRanges.indexOf(this.getTimeRangeByNr(nr)); + } + + timeRange.nr = nr; + + if (errors.length > 0) { + this.ui.showErrors(errors); + return false; + } + + if (index !== -1) { + this.config.timesRanges[index] = timeRange; + } else { + this.config.timesRanges.push(timeRange); + } + + this.sortTimeRanges(); + this.saveConfig(); + this.ui.fillTimesRanges(this.config.timesRanges); + this.generateAlarms(); + this.updateGraph(); + + return true; + }; + + /** + * + * @param {nymber} + * @returns {number} + */ + ExercisePlanner.prototype.editTimeRange = function editTimeRange(nr) { + var timeRange = this.getTimeRangeByNr(nr); + + if (timeRange !== undefined) { + this.currentEditingTimePeriod = timeRange; + this.currentEditingTimePeriodId = timeRange.nr; + } else { + this.currentEditingTimePeriod = { + nr: -1, + start: 10, + duration: 1, + stop: 11, + enabled: true, + style: 'everyday' + }; + this.currentEditingTimePeriodId = -1; + } + return this.currentEditingTimePeriodId; + }; + + /** + * Delete time range by number on list + * + * @param nr + * @returns {Boolean} + */ + ExercisePlanner.prototype.deleteTimeRange = function (nr) { + console.log('ExercisePlanner_deleteTimeRange', nr); + + var index, + timeRange = this.getTimeRangeByNr(nr); + + if (timeRange === undefined) { + return false; + } + + index = this.config.timesRanges.indexOf(timeRange); + if (index === -1) { + return false; + } + + // delete time range from array; + this.config.timesRanges.splice(index, 1); + + this.saveConfig(); + this.ui.fillTimesRanges(this.config.timesRanges); + this.generateAlarms(); + // update time periods on graph; + this.ui.graphSchedule.setTimeRanges(this.periodsWeekToBoolArray()); + this.ui.graphSchedule.refresh(); + + this.updateGraph(); + this.showNextAlarm(); + return true; + }; + + /** + * Disable time range by number on list + * + * @param nr + * @returns {Boolean} + */ + ExercisePlanner.prototype.disableTimeRange = function (nr) { + console.log('ExercisePlanner_disableTimeRange', nr); + var timeRange = this.getTimeRangeByNr(nr); + + if (timeRange === undefined) { + return false; + } + + timeRange.enabled = !timeRange.enabled; + + this.saveConfig(); + this.ui.fillTimesRanges(this.config.timesRanges); + this.generateAlarms(); + // update time periods on graph; + this.ui.graphSchedule.setTimeRanges(this.periodsWeekToBoolArray()); + this.ui.graphSchedule.refresh(); + + this.updateGraph(this.config.currentTypeOfPeriods); + this.showNextAlarm(); + return true; + }; + + /** + * Combines a overlapped time periods & delete not necesary + * This method modifies exisiting array in cache. + * + * @param periods + * @returns + */ + ExercisePlanner.prototype.mergeOverlapPeriods = function mergeOverlapPeriods(periods) { + var i, len = periods.length, wasOverlap = true, mergePeriod; + + periods.sort(function (a, b) { + return a - b; + }); + + if (len < 2) { + return periods; + } + + while (wasOverlap) { + wasOverlap = false; + len = periods.length; + for (i = 0; i < len - 1; i += 1) { + if (periods[i].stop > periods[i + 1].start) { + mergePeriod = $.extend({}, periods[i]); + if (mergePeriod.stop < periods[i + 1].stop) { + mergePeriod.stop = periods[i + 1].stop; + mergePeriod.duration = mergePeriod.stop - mergePeriod.start; + } + mergePeriod.nr = -1; + periods.splice(i, 2, mergePeriod); + wasOverlap = true; + break; + } + } + } + + return periods; + }; + + ExercisePlanner.prototype.mergePeriods = function mergePeriods() { + console.log('ExercisePlanner_mergePeriods'); + var i, len, onlyEveryDay = true, + ranges = this.config.timesRanges, + result = { + everyday : [], + weekend: [], + workday: [] + }; + + // checking time ranges for different to the "everyday" + for (i = 0, len = ranges.length; i < len; i += 1) { + if (ranges[i].style !== 'everyday') { + onlyEveryDay = false; + break; + } + } + + if (onlyEveryDay) { + for (i = 0, len = this.config.timesRanges.length; i < len; i += 1) { + if (this.config.timesRanges[i].enabled) { + result.everyday.push(this.config.timesRanges[i]); + } + } + } else { + // divide 'everyday' periods at workday/weekend + for (i = 0, len = this.config.timesRanges.length; i < len; i += 1) { + // if time range is disabled do not append to cache; + if (this.config.timesRanges[i].enabled) { + switch (this.config.timesRanges[i].style) { + case 'everyday': + result.workday.push(this.config.timesRanges[i]); + result.weekend.push(this.config.timesRanges[i]); + break; + case 'workday': + result.workday.push(this.config.timesRanges[i]); + break; + case 'weekend': + result.weekend.push(this.config.timesRanges[i]); + break; + } + } + } + } + + // check and correct overlaped time periods + this.mergeOverlapPeriods(result.everyday); + this.mergeOverlapPeriods(result.workday); + this.mergeOverlapPeriods(result.weekend); + + return result; + }; + + ExercisePlanner.prototype.getSummaryAvailableTime = function getSummaryAvailableTime() { + console.log('ExercisePlanner_getSummaryAvailableTime'); + var i, len, + periods = this.cache.periodsWeek, + sum = { + weekend: 0, + workday: 0, + everyday: 0 + }; + + for (i = 0, len = periods.everyday.length; i < len; i += 1) { + if (periods.everyday[i].enabled) { + sum.everyday += periods.everyday[i].duration; + } + } + for (i = 0, len = periods.workday.length; i < len; i += 1) { + if (periods.workday[i].enabled) { + sum.workday += periods.workday[i].duration; + } + } + for (i = 0, len = periods.weekend.length; i < len; i += 1) { + if (periods.weekend[i].enabled) { + sum.weekend += periods.weekend[i].duration; + } + } + + return sum; + }; + + ExercisePlanner.prototype.findNearestTimeRange = function (hour, ranges) { + console.log('ExercisePlanner_findNearestTimeRange', hour, ranges); + var nearResult, + result = { + requestedHour: hour, + optimalHour: -1 + }; + + if (!ranges.length) { + return result; + } + + /** + * Function search ranges of time for nearest to a hour + */ + nearResult = ranges.reduce(function (previous, element, index) { + var delta = 0; + + if (element.start < hour && element.stop < hour) { + delta = hour - element.stop; + } + + if (element.start > hour && element.stop > hour) { + delta = element.start - hour; + } + + return (delta < previous.delta) ? { index: index, delta: delta } : previous; + }, { index: -1, delta: 100 }); + + if (ranges[nearResult.index].start <= hour && ranges[nearResult.index].stop >= hour) { + result.optimalHour = Math.round((ranges[nearResult.index].start + ranges[nearResult.index].stop) / 2); + } else { + result.optimalHour = (ranges[nearResult.index].start > hour) ? ranges[nearResult.index].start : ranges[nearResult.index].stop; + } + + return result; + }; + + /** + * Export time period to array of boolen [boolean x 24] + * @returns {object} + */ + ExercisePlanner.prototype.periodsWeekToBoolArray = function periodsWeekToBoolArray() { + var i, j, len, periods, + result = { + workday: [], + weekend: [] + }; + + // fill default result; + for (i = 0; i < 24; i += 1) { + result.workday[i] = false; + result.weekend[i] = false; + } + + // set values; + periods = this.cache.periodsWeek.everyday; + len = periods.length; + for (i = 0; i < len; i += 1) { + for (j = periods[i].start; j < periods[i].start + periods[i].duration; j += 1) { + result.workday[j] = true; + result.weekend[j] = true; + } + } + + // set values; + periods = this.cache.periodsWeek.workday; + len = periods.length; + for (i = 0; i < len; i += 1) { + for (j = periods[i].start; j < periods[i].start + periods[i].duration; j += 1) { + result.workday[j] = true; + } + } + + // set values; + periods = this.cache.periodsWeek.weekend; + len = periods.length; + for (i = 0; i < len; i += 1) { + for (j = periods[i].start; j < periods[i].start + periods[i].duration; j += 1) { + result.weekend[j] = true; + } + } + return result; + }; + + +}()); + diff --git a/js/ext.jqMobile.js b/js/ext.jqMobile.js new file mode 100644 index 0000000..9de50b5 --- /dev/null +++ b/js/ext.jqMobile.js @@ -0,0 +1,17 @@ +/*global $*/ +// customize JQueryMobile controlgroup +$.fn.oldControlgroup = $.fn.controlgroup; +$.fn.controlgroup = function (options) { + "use strict"; + return this.oldControlgroup(options).each(function () { + this.deselectAll = function () { + return $('input', this).attr('checked', false).checkboxradio('refresh'); + }; + + this.select = function (value) { + this.deselectAll(); + return $('input[value$="' + value + '"]', this).attr('checked', true).checkboxradio('refresh'); + }; + }); +}; + diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..c3d8a85 --- /dev/null +++ b/js/main.js @@ -0,0 +1,21 @@ +/* + * Copyright 2012 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.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://floralicense.org/license/ + * + * 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. + */ +/*global ExercisePlanner, tizen, $, document*/ +var c = null, exercisePlanner = null, appService = null, ui = null; + +exercisePlanner = new ExercisePlanner(); +$(document).ready(exercisePlanner.init.bind(exercisePlanner)); + diff --git a/music/Runner.mp3 b/music/Runner.mp3 new file mode 100644 index 0000000..620aba5 Binary files /dev/null and b/music/Runner.mp3 differ diff --git a/signature1.xml b/signature1.xml new file mode 100644 index 0000000..d56c28b --- /dev/null +++ b/signature1.xml @@ -0,0 +1,160 @@ + + + + + + +deYSqUgxc88hVjk5a7xUn3Oy2THk0VIYt5J51C/luIY= + + + +sN0gBCSppwupIISCWk9SKbhuih2BlUxwcFgys6SIzHE= + + + +FANWRSdDi4T/f2CqQe56fL8NMZfewK2azWEBAfnjbQg= + + + +Y4XligVqi8vVA74GUDSAUawxYuomV/h4ozXffrH+hhU= + + + +Dq5u5V1W1FL9N+KMTKfBJTEiW0aTVHUWx0utuciKSIo= + + + +FX2aaCklIguMzPy9rZPS0vY7qWsZ2idyx6ZPUUzQzrU= + + + +Z7lFDN5i8vTxs/CnSisNoLvcKDtQEqd6K0E5KUjNgHg= + + + +s8Dc9i4P69eEp8ULwUL36ZpcSLzjQ02DDL8ozuB6HJw= + + + +1WwlyV7JaL1IyVaPS14yN3wxOngbmuvC2OgVMM9RvpQ= + + + +fskt8W7B8EMb3DQsvwT98x4fPKKIjY9sS5E0eSGenFA= + + + +7t0Zyyn9Cu0/HXEXaxEinFCFYpmDDgP7vdyN5UVWkQk= + + + +MtoxmfFtOOUWSBu/kfMTnCeClw9RLIjMS77IWrMG7wg= + + + +7iRwb6yZgM18/ZU4M8pBpsYb0xXylyPJhsqzbV1eLyY= + + + +lwMHcPXzvcu+hIVItWfT7RIQgrJvRi9mCrq/i38fRGc= + + + +A52TbnmMcr8G0QDY86BmznRMEAVLMrBShC2E51RITb4= + + + +4cLd4pP/oi83KOcF8LVUJB6gxUWBsE8ZiDBCoJzjE/o= + + + +xcxpAXCb5SX9K2yPYnYXSg/lFPbD5exPgfKQcvSSqr4= + + + +o9r/AN5tPtlHjYYiVoB5AfJgQsH+Aj2cUZYdzSUgyr8= + + + +ZpmuwAVONsdanK1S4KQPaHj1EYC8Ezs1QDZQbaFWm4M= + + + +1i4kpyIdtiDzToEKnuhiGitX2hVtPRVto9KixvY1B+s= + + + +XTYWoMBP0dm54DvvQmpdv0uG7UuU8wt06y9PfLGVI8E= + + + +Nz+SecsjmNuidhKNvmQ5+3nasrw8vI4q/bjPgbfx5fI= + + + +PrB+bVW3O2ESYgM0lAXMNEnGAXpB8AtcD2xvSegCIvU= + + + +4v30rSrxvLzjKEHb3fjwwzJHfYs4SuPd2opc+CDv77g= + + + +WvY/5k6O0wSwUO7d7S4qaedAyzxw4zdmPOOv6ZuFz+c= + + + +FMRxMozySMCFzVvfumNqZOHA+/qDtj84zddroztYWME= + + + +M7oEsiEdLNeaSAYdtR7uR5WGeAELG/V70u7Huzl42Xs= + + + + + + +u/jU3U4Zm5ihTMSjKGlGYbWzDfRkGphPPHx3gJIYEJ4= + + + +q9jMxR0PK+VpYO8PfFvcJyjnNizJ3NNjovCkzI27U9+Z9hEPCLtLfOOuPEhYfJyjQJiU0h1L+3O0 +iidyZpM3PrTfyPQHuuUQ/6VlBazd/dvd/aHEZDe7+B7sr4lLfXVlOriiwPtlR7YA7YQxYJwAgGq1 +etkYVBAMO9+wW45rrAs= + + + + +MIICnTCCAgYCCQDE9MbMmJ/yCzANBgkqhkiG9w0BAQUFADCBkDELMAkGA1UEBhMCS1IxDjAMBgNV +BAgMBVN1d29uMQ4wDAYDVQQHDAVTdXdvbjEWMBQGA1UECgwNVGl6ZW4gVGVzdCBDQTEiMCAGA1UE +CwwZVGl6ZW4gRGlzdHJpYnV0b3IgVGVzdCBDQTElMCMGA1UEAwwcVGl6ZW4gUGFydG5lciBEaXN0 +cmlidXRvciBDQTAeFw0xMjEwMjcwNzQ4MzNaFw0yMjEwMjUwNzQ4MzNaMIGUMQswCQYDVQQGEwJL +UjEOMAwGA1UECAwFU3V3b24xDjAMBgNVBAcMBVN1d29uMRYwFAYDVQQKDA1UaXplbiBUZXN0IENB +MSIwIAYDVQQLDBlUaXplbiBEaXN0cmlidXRvciBUZXN0IENBMSkwJwYDVQQDDCBUaXplbiBQYXJ0 +bmVyIERpc3RyaWJ1dG9yIFNpZ25lcjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAy9mg2x4B +zxlK3LJL81GsLq/pJfK1evdCKG/IOBpdoRO0rLhYnsL5+KvToPFa5g9GTZo32LikpW1NZ7++3EHE +fnO2IGLUau4kquvhmz1LNg5xBTx7IbucmwLMRGo1BPGdsAQQLyXeQKJ5PCERmVg4MIoiL2zT/JsL +sZ9UPT6GEB8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQAw5xPBFR1XKuZ8QpsCtSE0zXVHvwIa+Ha4 +YBdRtGwEoZmiKGZV/wAhPRdmR0kISkTz20kIGz/ZwRZCVGhsr5hkkpFknYlKeKkEJ/tJfZl4D7ec +GFAnynOzlWZqSIPz+yxX8ah9E6lTv4Vs9DhNb08nxVvxLqlpyVdk9RUsCx/yIA== + + +MIICtTCCAh6gAwIBAgIJAKORBcIiXygIMA0GCSqGSIb3DQEBBQUAMIGVMQswCQYDVQQGEwJLUjEO +MAwGA1UECAwFU3V3b24xDjAMBgNVBAcMBVN1d29uMRYwFAYDVQQKDA1UaXplbiBUZXN0IENBMSIw +IAYDVQQLDBlUaXplbiBEaXN0cmlidXRvciBUZXN0IENBMSowKAYDVQQDDCFUaXplbiBQYXJ0bmVy +IERpc3RyaWJ1dG9yIFJvb3QgQ0EwHhcNMTIxMDI3MDc0NTIwWhcNMjIxMDI1MDc0NTIwWjCBkDEL +MAkGA1UEBhMCS1IxDjAMBgNVBAgMBVN1d29uMQ4wDAYDVQQHDAVTdXdvbjEWMBQGA1UECgwNVGl6 +ZW4gVGVzdCBDQTEiMCAGA1UECwwZVGl6ZW4gRGlzdHJpYnV0b3IgVGVzdCBDQTElMCMGA1UEAwwc +VGl6ZW4gUGFydG5lciBEaXN0cmlidXRvciBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA +2ZQrdEowjqxUmB8FX8ej19VKY6jGHKNIRE5wrhBkuZ1b0FLRPiN3/Cl9wMkCnyJui4QhC28g1aBg +w/JnaObcDqW1NgFVH3006+gZvCTDlw1nIEjvZa6P+uWOOi05xPPAE0feKPkO1POnOjnapfkkEVNU +8TXsLbLYBylWT8rxZC8CAwEAAaMQMA4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBJ +yJ7p6qs0JI+1iKOk/sYWVP6dMueY72qOc/wVj5c3ejOlgJNNXDMAQ14QcRRexffc68ipTwybU/3m +tcNwydzKJe+GFa4b2zyKOvOgrfs4MKSR0T9XEPmTKeR+NDT2CbA6/kQoRYm0fSORzD2UXJzNZWe/ +WjwSA66hv4q+0QZQFQ== + + + + + \ No newline at end of file diff --git a/templates/GraphSchedule.tmpl b/templates/GraphSchedule.tmpl new file mode 100644 index 0000000..0dc59d7 --- /dev/null +++ b/templates/GraphSchedule.tmpl @@ -0,0 +1,91 @@ +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      000102030405060708091011121314151617181920212223
      +
      +
      +
      +
      +
      +

      +
      +
      +