Author: bpoussin Date: 2013-07-26 18:06:59 +0200 (Fri, 26 Jul 2013) New Revision: 2912 Url: http://chorem.org/projects/jtimer/repository/revisions/2912 Log: first version of web ui (webtimer) - remove old web directory Added: branches/ng-jtimer/jtimer-server/src/main/webapp/css/ branches/ng-jtimer/jtimer-server/src/main/webapp/css/app.css branches/ng-jtimer/jtimer-server/src/main/webapp/index.html branches/ng-jtimer/jtimer-server/src/main/webapp/js/ branches/ng-jtimer/jtimer-server/src/main/webapp/js/app.js branches/ng-jtimer/jtimer-server/src/main/webapp/js/controllers.js branches/ng-jtimer/jtimer-server/src/main/webapp/js/entities.js branches/ng-jtimer/jtimer-server/src/main/webapp/js/filters.js branches/ng-jtimer/jtimer-server/src/main/webapp/partials/ branches/ng-jtimer/jtimer-server/src/main/webapp/partials/about.html branches/ng-jtimer/jtimer-server/src/main/webapp/partials/contact.html branches/ng-jtimer/jtimer-server/src/main/webapp/partials/tasks.html branches/ng-jtimer/jtimer-server/src/main/webapp/webtimer.appcache Removed: branches/ng-jtimer/jtimer-server/src/main/webapp/web/ Added: branches/ng-jtimer/jtimer-server/src/main/webapp/css/app.css =================================================================== --- branches/ng-jtimer/jtimer-server/src/main/webapp/css/app.css (rev 0) +++ branches/ng-jtimer/jtimer-server/src/main/webapp/css/app.css 2013-07-26 16:06:59 UTC (rev 2912) @@ -0,0 +1,243 @@ + +body{ + margin:0; + padding: 60px 0 20px 0; +} +div#header { + position:absolute; + top:0; + left:0; + width:100%; + height: 35px; + z-index: 1000; + + background:black; + color:graytext; +} +div#footer{ + position:absolute; + bottom:0; + left:0; + width:100%; + height:20px; + z-index: 1000; + + background:black; + color:graytext; +} +#footer>.left, #header>.left { + text-align: left; + position: absolute; + left: 0px; + top: 0px; + z-index: 10; +} +#footer>.center, #header>.center { + width: 100%; + height: 100%; + text-align: center; + display: block; + z-index: 5; +} +#footer>.right, #header>.right { + text-align: right; + position: absolute; + right: 0px; + top: 0px; + z-index: 10; +} + +.menu-show { + display: block; + left: -170px +} + +@media screen{ + body div#header{ + position:fixed; + } + body div#subheader{ + position:fixed; + } + body div#footer{ + position:fixed; + } +} +* html body{ + overflow:hidden; +} +* html div#content{ + height:100%; + overflow:auto; +} + +.table { + display: table; +} + +.table .tbody { + display: table-row-group; +} + +.table .tr { + display: table-row; +} + +.table .th { + display: table-cell; + font-weight: bold; + text-align: center; +} + +.table .td { + display: table-cell; + position: relative; + overflow: hidden; + white-space: nowrap; + text-wrap: none; +} + +.spacer.level1 {padding-left: 10px;} +.spacer.level2 {padding-left: 20px;} +.spacer.level3 {padding-left: 30px;} +.spacer.level4 {padding-left: 40px;} +.spacer.level5 {padding-left: 50px;} +.spacer.level6 {padding-left: 60px;} +.spacer.level7 {padding-left: 70px;} +.spacer.level8 {padding-left: 80px;} +.spacer.level9 {padding-left: 90px;} + +.th.name { + +} + +.th.today, .td.today, .th.global, .td.global { + width: 4em; + border-right: 1px #0088cc solid; +} + +.td.today, .td.global { + text-align: right; +} + +.th.tags { + +} + + + +/* +body { + position: relative; + padding-top: 60px; +} +*/ + +ul.tasks { + display : block; + width : 100%; + list-style: none; +} + +li.task { + display : block; + width : 100%; + height: 32px; +} + +li.task.title { + font-weight: bold; + font-size: medium; +} + +li.task div { + width : 25%; + height: 32px; + float: left; +} + +.names li.task div { + width : 100%; +} + +.details li.task div { + width : 30%; +} + +.names ul.tasks { + margin: 0 0 0 15px; +} + +.details ul.tasks { + margin: 0 0 0 0; +} + +form { + display : inline-block; + margin : 0px; +} + +.filtering-enter { + -webkit-transition : all linear 0.2s; + -moz-transition : all linear 0.2s; + -o-transition : all linear 0.2s; + transition : all linear 0.2s; + + /* The animation preparation code */ + opacity : 0; + height : 0; +} + +.filtering-enter.filtering-enter-active { + /* The animation code itself */ + opacity : 1; + height : 42px; +} + +.filtering-leave { + -webkit-transition : all linear 0.2s; + -moz-transition : all linear 0.2s; + -o-transition : all linear 0.2s; + transition : all linear 0.2s; + + /* The animation preparation code */ + opacity : 1; + height : 42px; +} + +.filtering-leave.filtering-leave-active { + /* The animation code itself */ + opacity : 0; + height : 0; +} + +.btn-group.action { + display: none; + position: absolute; + right: 0; +} + +div:hover > .btn-group.action { + display: inline; +} + +.current-row { + background-color: #3a87ad; + color: white; +} + +.selected-row { + background-color: #5ca9cf; +} + +.tr:hover { + background-color: #7ecbef; +} + +.online { + color: greenyellow; +} + +.offline { + color: red; +} \ No newline at end of file Added: branches/ng-jtimer/jtimer-server/src/main/webapp/index.html =================================================================== --- branches/ng-jtimer/jtimer-server/src/main/webapp/index.html (rev 0) +++ branches/ng-jtimer/jtimer-server/src/main/webapp/index.html 2013-07-26 16:06:59 UTC (rev 2912) @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html lang="en" ng-app="webtimer" manifest="webtimer.appcache"> + <head> + <title></title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + + <link rel="stylesheet" href="css/jtimer.css"> + <script src="js/jtimer.js"></script> + + <link rel="stylesheet" href="css/app.css"> + <script src="js/app.js"></script> + <script src="js/controllers.js"></script> + <script src="js/filters.js"></script> + <script src="js/entities.js"></script> + </head> + <body> + + <div id='content' ng-view></div> + + </body> +</html> Property changes on: branches/ng-jtimer/jtimer-server/src/main/webapp/index.html ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: branches/ng-jtimer/jtimer-server/src/main/webapp/js/app.js =================================================================== --- branches/ng-jtimer/jtimer-server/src/main/webapp/js/app.js (rev 0) +++ branches/ng-jtimer/jtimer-server/src/main/webapp/js/app.js 2013-07-26 16:06:59 UTC (rev 2912) @@ -0,0 +1,224 @@ +/** + * Cette directive permet de parcourir un Arbre, pour chaque noeud de l'arbre + * un l'element DOM est duplique. + * Il est possible + * - de filtrer les noeuds lors de la definition de l'arbre (wtTreeRepeat) + * (ex: wt-tree-repeat="tree | filter:query | orderBy:'task.name'") + * - d'afficher le noeud root ou non (wtDisplayRoot) + * - de forcer l'ouverture de tout les noeuds (wtForceOpen) + * + * Lors du parcours pour chaque noeud un nouveau scope est cree permettant + * d'utilise les variables: + * - $node le noeud courant de l'arbre + * - $index l'index de parcours de ce noeud (0 pour le root) + * - $level la profondeur de ce noeud (0 pour le root) + * - $state l'etat de ce noeud ('empty', 'open', 'close') + * - $path le chemin pour arriver a ce noeud (tous ces noeuds parents) + * - $pathIndex le chemin pour arriver a ce noeud par index dans le tableau children du pere + * - $toggleState une fonction qui permet de changer l'etat d'un noeud ('open' <-> 'close') + + * + * @param {ngAnimator} $animator permet l'ajout, suppression des noeuds dans le DOM + * @param {ngFilter} $filter permet de filtrer les elements de l'arbre a afficher (filtre, tri, ...) + * @param {ngParse} $parse permet de parser des expressions qui doivent etre evaluer regulierement sur une scope + */ +var wtTreeRepeatDirective = ['$animator', '$filter', '$parse', function($animator, $filter, $parse) { + return { + transclude: 'element', + priority: 1000, + terminal: true, + compile: function(element, attr, linker) { + return function($scope, $element, $attr){ + var animate = $animator($scope, $attr); // use to add/remove element + + // sert a surveiller les changements sur la donnee ou les filtres + // qui engendrerait un besoin de rafraichissement. + // (ce tableau contient les fonctions permettant leur desenregistrement) + var watchers = []; + // element genere HTML et scope du dernier arbre represente + var cache = new Cache(animate); + // indique si l'element root doit etre affiche ou non (faux par defaut) + var displayRoot = $parse($attr.wtDisplayRoot || 'false'); + // indique si l'arbre doit etre ouvert de facon forcee ou non (faux par defaut) + var forceOpen = $parse($attr.wtForceOpen || 'false'); + // l'expression permettant de recuperer l'arbre et les filtres + var expression = $attr.wtTreeRepeat; + + // les differents filtres a applique aux noeuds enfants + var filters = []; + var filterStrings = expression.split('|'); + // l'expression pour la recuperation de arbre + var tree = filterStrings.shift(); + // add tree as watcher + watchers.push(tree); + // add forceOpen as watcher + watchers.push($attr.wtForceOpen||'false'); + + // Recuperation des filtres et preparation pour etre pret + // a l'utilisation + angular.forEach(filterStrings, function(filterString) { + var argStrings = filterString.split(":"); + var filterName = argStrings.shift().trim(); + var argGetters = argStrings.map(function(e) { + // add all args as watcher + watchers.push(e); + var result = $parse(e); + return result; + }); + var filter = function(children) { + var f = $filter(filterName); + var args = argGetters.map(function(a) { + return a($scope); + }); + var result = f.apply(null, [children].concat(args)); + return result; + }; + filters.push(filter); + }); + + // met a jour l'affichage de l'arbre + var update = function(tree) { + // les objets HTML et scope utilise durant cette mise a jour + // qui pourront etre reutilise durant la mis a jour suivante + var newCache = new Cache(animate); + + // l'index de parcours du noeud, on met dans un tableau + // pour avoir une reference lors du passage en argument + // et non pas une valeur lors des appels recursifs + var index = [0]; + // le niveau du noeud + var level = 0; + var path = []; + var pathIndex = []; + // le dernier element HTML ajoute (il faut mettre le suivant apres celui-ci) + var lastElement = $element; + + /** + * Genere un noeud et appel la fonction pour chaque noeud + * fils a affiche + */ + var generate = function(node, index, level, path, pathIndex) { + var state = 'open'; // init to open, if root is not display + + // on affiche le root que si demande + if (index[0] !== 0 || displayRoot($scope)) { + // try to find already created element + var cacheEntry = cache.get(node) || {node: node, scope:$scope.$new()}; + node.copyState(cacheEntry.node); // copy les etats, les noeuds ouvert doivent rester ouvert + var isNewElement = !cacheEntry.element; + + // new item which we don't know about + var childScope = cacheEntry.scope; + + if (node.children.length === 0) { + state = 'empty'; + } else { + if (node.$$open || forceOpen($scope)) { + state = 'open'; + } else { + state = 'close'; + } + } + + childScope.$node = node; + childScope.$index = index[0]; + childScope.$level = level; + childScope.$state = state; + childScope.$path = path; + childScope.$pathIndex = pathIndex; + childScope.$toggleState = function() { + node.$$open = !node.$$open; + }; + isNewElement && childScope.$watch("$node.$$open", function (newValue, oldValue, scope) { + if (newValue !== oldValue) { + update(tree); + } + }); + + if (!isNewElement) { + animate.move(cacheEntry.element, null, lastElement); + lastElement = cacheEntry.element; + newCache.put(node, cacheEntry.element, cacheEntry.scope); + } else { + linker(childScope, function(clone) { + animate.enter(clone, null, lastElement); + lastElement = clone; + newCache.put(node, clone, childScope); + }); + } + } + index[0]++; + + if (state == 'open') { + var children = node.children; + filters.forEach(function(e) { + children = e(children); + }); + for (var i = 0, length = children.length; i < length; i++) { + var child = children[i]; + generate(child, index, level+1, path.concat([child]), pathIndex.concat([i])); + } + } + }; + + generate(tree, index, level, path, pathIndex); + + // on nettoie les anciens noeud non reutilise + cache.clear(); + // on met les nouveaux noeuds dans le cache pour la prochaine maj + cache = newCache; + }; + + // expression permettant de recupere facilement l'arbre + var treeGetter = $parse(tree); + + // on enregistre chaque watcher sur le scope + angular.forEach(watchers, function (watcher) { + $scope.$watch(watcher, function(newValue, oldValue, scope){ + if (newValue !== oldValue) { + var tree = treeGetter($scope); + update(tree); + } + }); + }); + + // on force un premier appel pour que l'arbre s'affiche + update(treeGetter($scope)); + }; + } + }; +}]; + + +angular.module('webtimer', ['webtimerFilters']) + .config(['$routeProvider', function($routeProvider) { + $routeProvider. + when('/tasks', {templateUrl: 'partials/tasks.html', controller: TasksCtrl}). + when('/about', {templateUrl: 'partials/about.html'}). + when('/contact', {templateUrl: 'partials/contact.html'}). + otherwise({redirectTo: '/tasks'}); + }]) + + .factory("$localStorage", function() { + // Encapsule l'acces au localStorage pour + // - ajouter un prefix au variable sauvee pour les differencier des autres ("WebTimer-") + var webtimerPrefix = "WebTimer-"; + return { + set: function(key, value) { + key = webtimerPrefix + key; + // add version, to check version during read (get) + var item = value.toJson(); + window.localStorage.setItem(key, item); + }, + get: function(key) { + key = webtimerPrefix + key; + var item = window.localStorage.getItem(key); + var result = new WebTimerData(); + result.fromJson(item); + + return result; + } + }; + }) + + .directive('wtTreeRepeat', wtTreeRepeatDirective); Added: branches/ng-jtimer/jtimer-server/src/main/webapp/js/controllers.js =================================================================== --- branches/ng-jtimer/jtimer-server/src/main/webapp/js/controllers.js (rev 0) +++ branches/ng-jtimer/jtimer-server/src/main/webapp/js/controllers.js 2013-07-26 16:06:59 UTC (rev 2912) @@ -0,0 +1,315 @@ +function TasksCtrl($scope, $timeout, $localStorage, $window, $document) { + + /** + * {Boolean} vrai si on est online + */ + $scope.online = $window.navigator.onLine; + + // {WebTimerData} toutes les donnees + $scope.data = $localStorage.get("data"); + // {TreeNode} l'arbre regenere automatiquement lorsque les donnees changent + $scope.tree; + // {Array of Function} les listeners qui surveille l'ajout de nouveau temps, et qui les ajoutes dans l'arbre + $scope.treeWatcher = []; + // {String} le nom de la nouvelle tache a creer + $scope.name = ""; + // {Task} la derniere tache selectionnee + $scope.selectedTask = null; + // {Task} la tache en cours de timing + $scope.currentTask = null; + // {Date in millis} le temps pour la tache en cours de timing + $scope.currentTaskDate = null; + // {String} le filtre d'affichage des noeuds (permet la recherche d'un noeud) + $scope.query = ""; + // {boolean} boolean qui permet de force le depliage complet de l'arbre s'il est vrai + $scope.forceExpanded = false; + + // {boolean} boolean qui indique si la colonne NAME doit etre affichee + $scope.showName = true; + // {boolean} boolean qui indique si la colonne TODAY doit etre affichee + $scope.showToday = true; + // {boolean} boolean qui indique si la colonne TOTAL doit etre affichee + $scope.showGlobal = true; + // {boolean} boolean qui indique si la colonne TAGS doit etre affichee + $scope.showTags = true; + + /** + * Met a jour la tache selectionne, si la tache selectionnee est la courante + * alors on met la selection a null + * @param {Task} task + */ + $scope.setSelectedTask = function(task) { + if ($scope.selectedTask === task) { + $scope.selectedTask = null; + } else { + $scope.selectedTask = task; + } + }; + + /** + * Permet de faire des actions sur la tache selectionne en fonction + * des touches pressees + * @param {type} e + * @returns {undefined} + */ + $document[0].onkeydown = function(e){ + console.log(e); + // i = 73; d = 68; F2 = 113 + if ($scope.selectedTask) { + var taskTime = $scope.getTodayTaskTime($scope.selectedTask); + if (e.keyCode === 113) { + // edit task, time, note + } else if (e.keyCode === 73 && e.ctrlKey && e.shiftKey) { + // increment by 30min + taskTime.addTime(30*60*1000); + } else if (e.keyCode === 68 && e.ctrlKey && e.shiftKey) { + // decrement by 30min + taskTime.addTime(-30*60*1000); + } else if (e.keyCode === 73 && e.ctrlKey) { + // increment by 5min + taskTime.addTime(5*60*1000); + } else if (e.keyCode === 68 && e.ctrlKey) { + // decrement by 5min + taskTime.addTime(-5*60*1000); + } + } + }; + + $window.addEventListener("offline", function(e) { + $scope.online = false; + }, false); + $window.addEventListener("online", function(e) { + $scope.online = true; + }, false); + + + + /** + * Force la sauvegarde des datas + * @returns {undefined} + */ + var save = function() { + $localStorage.set("data", $scope.data); + }; + + /** + * Timer qui mais les temps a jour toutes les secondes si une tache est + * active + * @returns {undefined} + */ + var refresh = function() { + $timeout(function() { + if ($scope.currentTask) { + var now = Date.now(); + var taskTime = $scope.getTodayTaskTime($scope.currentTask); + taskTime.addTime(now - $scope.currentTaskDate); + $scope.currentTaskDate = now; + save(); + } + refresh(); + }, 1000); + }; + refresh(); + + /** + * Ajoute une tache root + */ + $scope.addTask = function() { + $scope.data.tasks.push(new Task($scope.name)); + $scope.name = ""; + save(); + $scope.createTree(); + }; + + /** + * Ajoute une sous tache a la tache du noeud passe en parametre + * @param {type} node + */ + $scope.addSubTask = function(node) { + var task = node.task; + $scope.data.tasks.push(new Task("New task", task.taskId)); + node.$$open = true; + save(); + $scope.createTree(); + }; + + /** + * Supprime une tache (et ces sous taches recursivement) + * @param {type} task + */ + $scope.removeTask = function(task) { + // methode pour faire la suppression recursivement + var removeRecurse = function(tasks) { + angular.forEach(tasks, function() { + task.remove(); + var children = $scope.getChildren(task); + removeRecurse(children); + }); + }; + removeRecurse([task]); + save(); + $scope.createTree(); + }; + + /** + * met a jour la variable $scope.tree par recreation de l'arbre + * Tous les listeners de l'ancien arbre sont enleves + */ + $scope.createTree = function() { + // on supprime tous les watchers de l'arbre courant + angular.forEach($scope.treeWatcher, function(deregisterFuntion) { + // la methode stocker dans treeWatcher permet de desenregistrer le watcher + deregisterFuntion(); + }); + $scope.treeWatcher = []; + + var nodes = []; + angular.forEach($scope.getRootTask(), function(t) { + var node = $scope.createTreeNode(t); + nodes.push(node); + }); + + var result = new TreeNode(new Task("root"), null, null, nodes); + $scope.tree = result; + }; + + /** + * Retourne la liste des enfants non supprime d'un noeud. Les sous taches + * retourne peuvent etre modifiee via encapsulationFuntion si elle existe + * @param {Task} task la tache dont on recherche les enfants + * @param {Funciton} encapsulationFuntion une methode appelee pour chaque enfant, + * qui peut retourner un autre objet que l'enfant + * @returns {Array of Task or other if encapsulationFuntion used} + */ + $scope.getChildren = function (task, encapsulationFuntion) { + var result = []; + angular.forEach($scope.data.tasks, function (t) { + if (!t.isRemoved() && task.isParentOf(t)) { + var node = t; + if (encapsulationFuntion) { + node = encapsulationFuntion(node); + } + result.push(node); + } + }); + + return result; + }; + + /** + * Encapsule une tache dans un TreeNode. On ajoute un watcher sur les temps + * pour cette tache qui pousse les nouvelles donnes dans le noeud lorsqu'ils + * sont modifies + * + * @param {Task} task + * @returns {TreeNode} + */ + $scope.createTreeNode = function(task) { + var globalTime = $scope.data.globalTimes[task.taskId] || new GlobalTime(); + var localTimes = $scope.data.times[task.taskId] || []; + var children = $scope.getChildren(task, $scope.createTreeNode); + var node = new TreeNode(task, globalTime, localTimes, children); + + var watcher = $scope.$watch("data.times['"+task.taskId+"']", function(localtimes) { + node.setTaskTimes(localtimes); + }, true); + $scope.treeWatcher.push(watcher); + + return node; + }; + + /** + * Retourne la liste des taches root actif (non supprime) + * @returns {result} + */ + $scope.getRootTask = function () { + var result = []; + angular.forEach($scope.data.tasks, function (t) { + if (t.isRoot() && !t.isRemoved()) { + result.push(t); + } + }); + return result; + }; + + /** + * Retourne l'objet TaskTime d'aujourd'hui pour la tache demandee + * Si cet objet n'existait pas, il est cree et ajouter a la liste + * des objets temps existant + * @param {type} task + * @returns {nodes} + */ + $scope.getTodayTaskTime = function(task) { + var times = $scope.data.times[task.taskId]; + if (!times) { + times = []; + $scope.data.times[task.taskId]= times; + } + + var result; + angular.forEach(times, function (t) { + if (!result && t.isToday()) { + result = t; + } + }); + + if (!result) { + result = new TaskTime(task); + times.push(result); + } + + return result; + }; + + /** + * Lance l'edition du nom d'une tache + * @param {TreeNode} node + * @param {String} type le type d'action + */ + $scope.editTask = function(node, type) { + node.edit = type; + save(); + }; + + /** + * Sauve la modification de la tache + * @param {TreeNode} node + */ + $scope.saveTask = function(node) { + node.edit = null; + save(); + }; + + /** + * Active/desactive le comptage de temps pour une tache + * @param {type} task + * @param {type} element + */ + $scope.timeTask = function(task, element) { + var now = Date.now(); + if ($scope.currentTask) { + var taskTime = $scope.getTodayTaskTime($scope.currentTask); + taskTime.addTime(now - $scope.currentTaskDate); + } + + if ($scope.currentTask !== task) { + $scope.currentTask = task; + $scope.currentTaskDate = now; + } else { + $scope.currentTask = null; + $scope.currentTaskDate = null; + } + save(); + }; + + /** + * Retourne la date et heure courant + * @returns {String} + */ + $scope.currentDate = function() { + return moment().format("HH:mm DD/MM/YYYY"); + }; + + // force the first tree creation + $scope.createTree(); +} Added: branches/ng-jtimer/jtimer-server/src/main/webapp/js/entities.js =================================================================== --- branches/ng-jtimer/jtimer-server/src/main/webapp/js/entities.js (rev 0) +++ branches/ng-jtimer/jtimer-server/src/main/webapp/js/entities.js 2013-07-26 16:06:59 UTC (rev 2912) @@ -0,0 +1,347 @@ +/** + * Genere un UUID + * @returns {String} + */ +function generateUUID(){ + var d = Date.now(); + var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = (d + Math.random()*16)%16 | 0; + d = Math.floor(d/16); + return (c==='x' ? r : (r&0x7|0x8)).toString(16); + }); + return uuid; +}; + +/** + * Retourne la date d'aujourd'hui sous la forme "2013-O7-25" + * @returns {String} + */ +function today() { + var result = moment().format("YYYY-MM-DD"); + return result; +} + +/** + * permet de gerer les elements HTML et scope creer pour chaque + * noeud et les reutilisers plutot que de les recreer a chaque mise a jour + * + * @param {ngAnimator} animate l'objet permettant de supprimer un noeud dans le DOM + * @returns {Cache} + */ +var Cache = function(animate) { + this.animate = animate; +}; +Cache.prototype.put = function(node, element, scope) { + var id = node.task.taskId; + this[id] = {node: node, element: element, scope: scope}; +}; +Cache.prototype.get = function(nodeOrId) { + var id = nodeOrId; + if (!angular.isString(nodeOrId)) { + id = nodeOrId.task.taskId; + } + var result = this[id]; + delete this[id]; + return result; +}; + +Cache.prototype.clear = function() { + for (var key in this) { + if (this.hasOwnProperty(key) && key !== "animate") { + var entry = this[key]; + this.animate.leave(entry.element); + entry.scope.$destroy(); + } + } +}; + + +/** + * Toutes les donnees qui sont a stocker entre 2 sessions + * @returns {WebTimerData} + */ +var WebTimerData = function() { + // la version des donnees + this.dataVersion = 1; + + this.tasks = []; // Array of Task + this.globalTimes = {}; // key: taskId, value: GlobalTime + this.times = {}; // key: taskId, value: Array of TaskTime + this.notes = {}; // key: taskId, value: Array of TaskNote +}; + +/** + * Migre les donnees d'une version a une autre + * @param {Number} oldVersion + * @param {Number} newVersion + * @param {JSON} data + * @returns {JSON} + */ +WebTimerData.prototype.migrate = function(oldVersion, newVersion, data) { + return data; +}; + +/** + * Converti l'objet WebTimerData en Json en ajoutant + * @returns {JSON} + */ +WebTimerData.prototype.toJson = function() { + var result = angular.toJson(this); + return result; +}; + +/** + * Charge des donnees au format JSON les convertit dans le bon type d'objet, + * fait la migration des data si necessaire + * @param {JSON or JsonString} json + * @returns {WebTimerData} retourne lui meme + */ +WebTimerData.prototype.fromJson = function(json) { + if (angular.isString(json)) { + json = angular.fromJson(json); + } + if (json) { + if (json.dataVersion !== this.dataVersion) { + json = this.migrate(json.dataVersion, this.dataVersion, json); + } + if (json) { + angular.forEach(json.tasks, function (t) { + t = jQuery.extend(new Task(), t); + this.tasks.push(t); + }, this); + + angular.forEach(json.globalTimes, function (t, k) { + t = jQuery.extend(new GlobalTime(), t); + this.globalTimes[k] = t; + }, this); + + angular.forEach(json.times, function (times, k) { + this.times[k] = []; + angular.forEach(times, function (t) { + t = jQuery.extend(new TaskTime(), t); + this.times[k].push(t); + }, this); + }, this); + + angular.forEach(json.notes, function (notes, k) { + this.notes[k] = []; + angular.forEach(notes, function (n, k) { + n = jQuery.extend(new TaskNote(), n); + this.notes[k].push(n); + }, this); + }, this); + } + } + return this; +}; + +/** + * Un noeud de l'arbre. + * + * @param {Task} task la tache a encapsuler + * @param {GlobalTime} globalTime les temps totaux provenant du serveur + * @param {Array of TaskTime} localTimes les temps crees localement + * @param {Array of TreeNode} children les enfants de ce noeud + * @returns {TreeNode} + */ +var TreeNode = function (task, globalTime, localTimes, children) { + this.task = task || new Task("root"); + this.globalTime = globalTime || new GlobalTime(); + this.localTimes = localTimes || []; + children = angular.forEach(children, function(child) { + child.parent = this; + }); + this.children = children || []; +}; + +/** + * Copie toutes les donnees d'etat d'une noeud + * @param {TreeNode} node + * @returns {undefined} + */ +TreeNode.prototype.copyState = function(node) { + this.$$open = node.$$open; +}; + +/** + * Met a jour les temps locaux + * + * @param {Array of TaskTime} taskTimes + * @returns {undefined} + */ +TreeNode.prototype.setTaskTimes = function(taskTimes) { + this.localTimes = taskTimes; +}; + +/** + * Retourne le temps consolide pour ce noeud (temps glocal + locaux + enfant) + * @returns {GlobalTime} + */ +TreeNode.prototype.getTime = function() { + var result = new GlobalTime(); + result.addGlobalTime(this.globalTime); + result.addTaskTimes(this.localTimes); + angular.forEach(this.children, function(node) { + var nodeTime = node.getTime(); + result.addGlobalTime(nodeTime); + }); + return result; +}; + +/** + * Objet permettant de stocke le temps total et le temps de la journee actuelle + * + * @param {Task} task + * @returns {GlobalTime} + */ +var GlobalTime = function (task) { + this.taskId = task && task.taskId; + this.today = 0; + this.global = 0; +}; + +/** + * Fait une copie de l'objet + * @returns {GlobalTime} + */ +GlobalTime.prototype.clone = function() { + var result = new GlobalTime(); + result.add(this); + return result; +}; + +/** + * Ajoute les temps d'un GlobalTime + * @param {GlobalTime} globalTime + * @returns {GlobalTime} this + */ +GlobalTime.prototype.addGlobalTime = function(globalTime) { + this.global += globalTime.global; + this.today += globalTime.today; + return this; +}; + +/** + * Ajoute les temps d'un tableau de TaskTime au temps global et a today si + * la TaskTime est pour today + * @param {Array of TaskTime} taskTimes + * @returns {GlobalTime} this + */ +GlobalTime.prototype.addTaskTimes = function(taskTimes) { + angular.forEach(taskTimes, function(taskTime) { + this.global += taskTime.time; + if (taskTime.isToday()) { + this.today += taskTime.time; + } + }, this); + return this; +}; + +/** + * Represente une tache + * @param {type} name le nom de la tache + * @param {type} parentTaskId l'identifiant de la tache parente + * @returns {Task} + */ +var Task = function (name, parentTaskId) { + this.taskId = generateUUID(); + this.creationDate = Date.now(); + this.modificationDate = this.creationDate; + this.removed = 0; + this.parentTaskId = parentTaskId; + this.name = name; + this.description = ""; + this.tags = []; + this.syncOptions = {}; +}; + +/** + * Marque la tache comme supprimee + * @returns {undefined} + */ +Task.prototype.remove = function() { + this.removed = Date.now(); +}; + +/** + * return vrai si la tache est supprimee + * @returns {boolean} + */ +Task.prototype.isRemoved = function() { + return !!this.removed; +}; + +/** + * Retourne vrai si la tache est parent de la sous tache passee en parametre + * @param {Task} subtask + * @returns {Boolean} + */ +Task.prototype.isParentOf = function(subtask) { + var result = subtask.parentTaskId === this.taskId; + return result; +}; + +/** + * Indique si la tache est une tache root (pas de parent) + * @returns {unresolved} + */ +Task.prototype.isRoot = function() { + var result = !this.parentTaskId; + return result; +}; + +/** + * Permet de conserve une temps cree localement + * @param {Task} task + * @returns {TaskTime} + */ +var TaskTime = function (task) { + this.taskId = task && task.taskId; + this.timeId = generateUUID(); + this.date = today(); + this.time = 0; +}; + +/** + * Retourne vrai si ce temps est pour aujourd'hui + * @returns {Boolean} + */ +TaskTime.prototype.isToday = function() { + var result = this.date === today(); + return result; +}; + +/** + * ajoute du temps + * @param {Number} t + * @returns {TaskTime} this + */ +TaskTime.prototype.addTime = function(t) { + this.time += t; + if (this.time < 0) { + this.time = 0; + } + return this; +}; + +/** + * Represente une note pour une tache + * @param {Task} task + * @returns {TaskNote} + */ +var TaskNote = function (task) { + this.taskId = task && task.taskId; + this.noteId = generateUUID(); + this.date = today(); + this.text = ""; +}; + +/** + * Retourne vrai si cette note est pour aujourd'hui + * @returns {Boolean} + */ +TaskNote.prototype.isToday = function() { + var result = this.date === today(); + return result; +}; + Added: branches/ng-jtimer/jtimer-server/src/main/webapp/js/filters.js =================================================================== --- branches/ng-jtimer/jtimer-server/src/main/webapp/js/filters.js (rev 0) +++ branches/ng-jtimer/jtimer-server/src/main/webapp/js/filters.js 2013-07-26 16:06:59 UTC (rev 2912) @@ -0,0 +1,8 @@ +var wtFilter = function(input) { + var date = moment(input).utc(); + return date.diff(moment(0), 'hours') + ":" +date.format("mm:ss"); +}; + +angular.module('webtimerFilters', []).filter('time', function() { + return wtFilter; +}); Added: branches/ng-jtimer/jtimer-server/src/main/webapp/partials/about.html =================================================================== --- branches/ng-jtimer/jtimer-server/src/main/webapp/partials/about.html (rev 0) +++ branches/ng-jtimer/jtimer-server/src/main/webapp/partials/about.html 2013-07-26 16:06:59 UTC (rev 2912) @@ -0,0 +1 @@ +WebTimer 1.0 Property changes on: branches/ng-jtimer/jtimer-server/src/main/webapp/partials/about.html ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: branches/ng-jtimer/jtimer-server/src/main/webapp/partials/contact.html =================================================================== --- branches/ng-jtimer/jtimer-server/src/main/webapp/partials/contact.html (rev 0) +++ branches/ng-jtimer/jtimer-server/src/main/webapp/partials/contact.html 2013-07-26 16:06:59 UTC (rev 2912) @@ -0,0 +1 @@ +jtimer-users@listes.chorem.org Property changes on: branches/ng-jtimer/jtimer-server/src/main/webapp/partials/contact.html ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: branches/ng-jtimer/jtimer-server/src/main/webapp/partials/tasks.html =================================================================== --- branches/ng-jtimer/jtimer-server/src/main/webapp/partials/tasks.html (rev 0) +++ branches/ng-jtimer/jtimer-server/src/main/webapp/partials/tasks.html 2013-07-26 16:06:59 UTC (rev 2912) @@ -0,0 +1,88 @@ +<script type="text/ng-template" id="tree_item_renderer_details.html"> + <div class="tr" ng-class="{'current-row' : ($node.task === currentTask), 'selected-row' : ($node.task === selectedTask)}" + ng-click="setSelectedTask($node.task)" + ng-dblclick="timeTask($node.task)" + ng-keydown="keyPressed($event)"> + <div class="td" ng-show="showName"> + <span class="spacer level{{$level}}"></span> + <i class="icon-ban-circle" ng-show="$state=='empty'"></i> + <i class="icon-plus-sign" ng-click="$toggleState()" ng-show="$state=='close'"></i> + <i class="icon-minus-sign" ng-click="$toggleState()" ng-show="$state=='open'"></i> + + <form ng-submit="saveTask($node)" ng-show="$node.edit == 'name'"> + <input ng-model="$node.task.name"> + </form> + + <span ng-click="editTask($node, 'name')" ng-show="$node.edit != 'name'">{{$node.task.name}} </span> + + <span class="btn-group action"> + <a class="btn btn-mini" href="" ng-click="timeTask($node.task)"> + <i ng-class="{'icon-play' : ($node.task != currentTask), 'icon-stop' : ($node.task == currentTask)}"></i> + </a> + <a class="btn btn-mini" href="" ng-click="addSubTask($node)"><i class="icon-plus"></i></a> + <a class="btn btn-mini" href="" ng-click="removeTask($node.task)"><i class="icon-minus"></i></a> + </span> + </div> + + <div class="td today" ng-show="showToday"> + {{$node.getTime().today | time}} + </div> + + <div class="td global" ng-show="showGlobal">{{$node.getTime().global | time}}</div> + + <div class="td tags" ng-show="showTags" ng-click="editTask($node, 'tags')"> + <form ng-submit="saveTask($node)" ng-show="$node.edit == 'tags'"> + <input ng-model="$node.task.tags" ng-list> + </form> + + <span class="label label-info" ng-repeat="tag in $node.task.tags" style="margin-right: 10px;" ng-show="$node.edit != 'tags'"> + {{tag}} + </span> + + <span ng-show="$node.task.tags.length == 0 && $node.edit != 'tags'">No tag</span> + </div> + </div> +</script> + +<div id="header"> + <span class='left'> + <input class='search-query input-small' ng-model="query" placeholder="Search"> + <form ng-submit="addTask()"> + <input class='search-query input-small' ng-model="name" placeholder="New Task"> + </form> + </span> + <span class="right"> + <a class="icon-plus-sign" ng-click="showMenu = !showMenu"> + <b class="caret"></b> + </a> + <ul class="dropdown-menu" ng-class="{'menu-show': showMenu}"> + <li>Keep Tree opened <input type='checkbox' ng-model="forceExpanded"></li> + <li>Name <input type='checkbox' ng-model='showName'/></li> + <li>Today <input type='checkbox' ng-model='showToday'/></li> + <li>Total <input type='checkbox' ng-model='showGlobal'/></li> + <li>Tags <input type='checkbox' ng-model='showTags'/></li> + </ul> + <i class="icon-cloud" ng-class="{online: online, offline: !online}"></i> + </span> +</div> +<div id="footer"> + <span class="left">{{currentDate()}}</span> + <span class="center"><a href="#">WebTimer</a><i class="icon-html5"></i></span> + <span class="right">{{tree.getTime().today |time}} | {{tree.getTime().global | time}}</a> +</div> + +<div> + <div class='table'> + <div class='tr'> + <div class='th name' ng-show="showName">Name</div> + <div class='th today' ng-show="showToday">Today</div> + <div class='th global' ng-show="showGlobal">Total</div> + <div class='th tags' ng-show="showTags">Tags</div> + </div> + <div class="tbody" + wt-tree-repeat="tree | filter:query | orderBy:'task.name'" + wt-force-open="query || forceExpanded" + ng-include="'tree_item_renderer_details.html'"></div> + + </div> +</div> Property changes on: branches/ng-jtimer/jtimer-server/src/main/webapp/partials/tasks.html ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: branches/ng-jtimer/jtimer-server/src/main/webapp/webtimer.appcache =================================================================== --- branches/ng-jtimer/jtimer-server/src/main/webapp/webtimer.appcache (rev 0) +++ branches/ng-jtimer/jtimer-server/src/main/webapp/webtimer.appcache 2013-07-26 16:06:59 UTC (rev 2912) @@ -0,0 +1,18 @@ +CACHE MANIFEST +# version 11 + +CACHE: +index.html +partials/about.html +partials/contact.html +partials/tasks.html +css/app.css +css/jtimer.css +js/app.js +js/controllers.js +js/entities.js +js/filters.js +js/jtimer.js + +NETWORK: +http://localhost:33678/*