diff --git a/appinfo/database.xml b/appinfo/database.xml index 98c5e15a7..ed5c2d7ad 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -203,7 +203,7 @@ title text - true + false 64 diff --git a/appinfo/info.xml b/appinfo/info.xml index 0b544aee0..e4ce548b8 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -5,7 +5,7 @@ My first ownCloud app AGPL Julius Härtl - 0.0.1.9 + 0.0.1.10 Deck other diff --git a/appinfo/routes.php b/appinfo/routes.php index 2dd934a73..9251fda33 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -40,6 +40,14 @@ return [ ['name' => 'card#rename', 'url' => '/cards/rename/', 'verb' => 'PUT'], ['name' => 'card#reorder', 'url' => '/cards/reorder/', 'verb' => 'PUT'], ['name' => 'card#delete', 'url' => '/cards/{cardId}/', 'verb' => 'DELETE'], + // card - assign labels + ['name' => 'card#assignLabel', 'url' => '/cards/{cardId}/label/{labelId}', 'verb' => 'POST'], + ['name' => 'card#removeLabel', 'url' => '/cards/{cardId}/label/{labelId}', 'verb' => 'DELETE'], + + // labels + ['name' => 'label#create', 'url' => '/labels/', 'verb' => 'POST'], + ['name' => 'label#update', 'url' => '/labels/', 'verb' => 'PUT'], + ['name' => 'label#delete', 'url' => '/labels/{labelId}/', 'verb' => 'DELETE'], // TODO: Implement public board sharing ['name' => 'public#index', 'url' => '/public/board/:hash', 'verb' => 'GET'], diff --git a/controller/cardcontroller.php b/controller/cardcontroller.php index 7a5532c5f..9135dbaea 100644 --- a/controller/cardcontroller.php +++ b/controller/cardcontroller.php @@ -51,8 +51,8 @@ class CardController extends Controller { /** * @NoAdminRequired */ - public function update($id, $title, $stackId, $type, $order) { - return $this->cardService->update($id, $title, $stackId, $type, $order, $this->userId); + public function update($id, $title, $stackId, $type, $order, $description) { + return $this->cardService->update($id, $title, $stackId, $type, $order, $description, $this->userId); } /** * @NoAdminRequired @@ -60,6 +60,17 @@ class CardController extends Controller { public function delete($cardId) { return $this->cardService->delete($this->userId, $cardId); } - + /** + * @NoAdminRequired + */ + public function assignLabel($cardId, $labelId) { + return $this->cardService->assignLabel($this->userId, $cardId, $labelId); + } + /** + * @NoAdminRequired + */ + public function removeLabel($cardId, $labelId) { + return $this->cardService->removeLabel($this->userId, $cardId, $labelId); + } } diff --git a/controller/labelcontroller.php b/controller/labelcontroller.php new file mode 100644 index 000000000..1b022f4ac --- /dev/null +++ b/controller/labelcontroller.php @@ -0,0 +1,43 @@ +userId = $userId; + $this->labelService = $labelService; + } + + /** + * @NoAdminRequired + */ + public function create($title, $color, $boardId) { + return $this->labelService->create($title, $this->userId, $color, $boardId); + } + /** + * @NoAdminRequired + */ + public function update($id, $title, $color) { + return $this->labelService->update($id, $title, $this->userId, $color); + } + /** + * @NoAdminRequired + */ + public function delete($labelId) { + return $this->labelService->delete($this->userId, $labelId); + } + +} diff --git a/css/style.css b/css/style.css index 7868128e2..4d268fa7e 100644 --- a/css/style.css +++ b/css/style.css @@ -27,54 +27,62 @@ height:100%; white-space: nowrap; /* important */ overflow: auto; - background-color:#ffffff; - padding:0; + top:-40px; + padding-top:40px; + z-index:100; } #board #innerBoard { padding:10px; - margin-top:40px; - } -#board h1 { - font-size:14pt; - margin-bottom:10px; - padding:10px; - position: fixed; - width:100%; +#board-header { + width: inherit; color: #333333; - padding-right:250px; - z-index:100; + position:relative; + z-index:200; background-color:#f7f7f7; } +#board-header h1 { + font-size:14pt; + margin: 0; + padding:10px; + +} #board-actions { - position:absolute; + font-size:10pt; + /*position:absolute; right:5px; top:5px; - z-index:999; + z-index:999;*/ + float:right; + position:relative; + margin-top:-5px; + color: #888; + } -#board-actions .filter { +#board-actions .filter .filter-button { margin-left:10px; margin-right:10px; - position:relative; } #board-actions .filter:hover { color:#333333; cursor: pointer; } -.filter .filter-select { - position: absolute; - top: 42px; - right: -15px; - width: 100px; +.filter { } -.filter .filter-select li { +.filter-select { + position: absolute; + right: auto; + top:42px; + left:-21%; +} +.filter-select li { padding: 3px; overflow: hidden; width:auto; } -.filter .filter-select li span { +.filter-select li span { display: block; float: left; width: 20px;height:20px; @@ -89,6 +97,12 @@ background-color: transparent; color: #fff; } +.board-action-button { + font-size:12pt; + font-weight:100; + border:none; + margin-left:10px; +} .stack { width:320px; margin-right:10px; @@ -284,7 +298,7 @@ margin-bottom:0px; background-color:#f0f0f0; } -#card-header .icon-close { +.icon-close { position: absolute; top:5px; right:5px; @@ -300,13 +314,36 @@ #card-dates span { } +#card-description h3 { + border-bottom:1px solid #333333; + font-weight: 600; + font-size:10pt; + padding:5px; +} + #card-description textarea { width:100%; height:200px; border: none; - margin: 10px; + margin: 0px; + padding: 0px; } +#card-description .container { + background-color: white; +} +#card-description .container.ng-hide-remove { + animation: fade 1s forwards; + background-color:rgba(255, 255, 100, 1); +} + +@keyframes fade { + from {background-color:rgba(255, 255, 100, 1);} + to {background-color:rgba(255, 255, 255, 0);} +} + + #card-attachments, +#sidebar-header, .card-block { padding:10px; } @@ -320,15 +357,20 @@ max-width: 100%; border-left: none; width:500px; - box-shadow: 0px 0px 5px 0px #aaa; + /*box-shadow: 0px 0px 5px 0px #aaa;*/ + border-left:1px solid #eeeeee; } #app-sidebar.details-visible { right: 0px; } +#app-content { + overflow:hidden; +} #app-content.details-visible { margin-right: 500px; } + .labels { display:block; overflow:hidden; @@ -341,6 +383,12 @@ color: #ffffff; font-size:80%; font-weight:900; + min-width:20px; + display: inline-block; + text-align: center; +} +.labels li span { + } #assigned-users { padding:10px; @@ -355,16 +403,18 @@ .colorselect .color { opacity:0.7; - width:27px; - height:27px; + width:26px; + height:26px; float:left; - margin-right:1px; - border:1px solid #ffffff; + margin-right:2px; + border:none; } .colorselect .selected { opacity:1.0; border:1px solid #333333; + width: 26px; + height:26px; } @@ -424,4 +474,101 @@ button { button:hover { border:0; background-color: transparent; -} \ No newline at end of file +} + +/* board detail */ +#board-detail-labels { + padding: 10px; + + +} +#board-detail-labels ul li { + display: block; + font-size:10pt; + float: none; + margin-bottom:1px; + overflow: hidden; +} +#board-detail-labels ul li input { + float:left; + font-size:10pt; + padding:5px; +} +#board-detail-labels ul li .label-title { + float:left; + width:90%; + font-size:10pt; + padding:5px; + border:none; + margin-right:2px; +} +#board-detail-labels ul li .fa { + float:right; + padding:5px; +} + +#board-detail-labels .color { + width:28px; + height:31px; +} + +.tabHeaders { + clear: both; + overflow:hidden; +} + +#shareWithList .avatar { + float: left; + margin-top: -5px; + margin-right:10px; +} + +.ui-select-container.dropdown { + background: #ffffff; + border-radius: 0px; + box-shadow: none; + display: block; + margin-right: 0; + position: static; + width: 100%; + z-index: auto; + padding: 16px; +} +.ui-select-match-close { + float:right; + left: -20px; + margin-top:3px; + z-index: 100; + position: relative; +} +.ui-select-match-item { + padding:2px; + float:left; + display: block; + margin-right:-17px !important; +} +.ui-select-match-item .select-label { + padding:4px; + color:#fff; + padding-right:23px; +} +.ui-select-container { + background-color:#eeeeee !important; +} +.ui-select-container.open { + border: 1px solid #aaaaaa; +} +.ui-select-search { + padding:0px !important; + margin:2px !important; +} +.ui-select-choices-row-inner { +margin-bottom:2px; width:100%; + padding:0; +} +.ui-select-choices-row-inner span { + padding:3px; + padding-left: 10px; + padding-right:10px; + width:100%; +} diff --git a/db/board.php b/db/board.php index 4ab22fe62..5ed0344ea 100644 --- a/db/board.php +++ b/db/board.php @@ -3,9 +3,8 @@ namespace OCA\Deck\Db; use JsonSerializable; -use OCP\AppFramework\Db\Entity; -class Board extends Entity implements JsonSerializable { +class Board extends \OCA\Deck\Db\Entity implements JsonSerializable { public $id; protected $title; @@ -13,9 +12,12 @@ class Board extends Entity implements JsonSerializable { protected $color; protected $archived; protected $labels; + public function __construct() { $this->addType('id','integer'); + $this->addRelation('labels'); } + public function jsonSerialize() { return [ 'id' => $this->id, diff --git a/db/boardmapper.php b/db/boardmapper.php index 94b4b5c8f..b7118deaa 100644 --- a/db/boardmapper.php +++ b/db/boardmapper.php @@ -2,7 +2,6 @@ namespace OCA\Deck\Db; -use OCP\AppFramework\Db\Entity; use OCP\IDb; use OCP\AppFramework\Db\Mapper; @@ -10,6 +9,11 @@ use OCP\AppFramework\Db\Mapper; class BoardMapper extends Mapper { private $labelMapper; + private $_relationMappers = array(); + + public function addRelationMapper($name, $mapper) { + $this->_relationMappers[$name] = $mapper; + } public function __construct(IDb $db, LabelMapper $labelMapper) { parent::__construct($db, 'deck_boards', '\OCA\Deck\Db\Board'); @@ -36,8 +40,9 @@ class BoardMapper extends Mapper { return $this->findEntities($sql, [$userId], $limit, $offset); } - public function delete(Entity $entity) { - // FIXME: delete linked elements, because owncloud doesn't support foreign keys for apps + public function delete(\OCP\AppFramework\Db\Entity $entity) { + //$this->deleteRelationalEntities('label', $entity); return parent::delete($entity); } + } \ No newline at end of file diff --git a/db/card.php b/db/card.php index 811bb4038..e193e3a8a 100644 --- a/db/card.php +++ b/db/card.php @@ -3,7 +3,6 @@ namespace OCA\Deck\Db; use JsonSerializable; -use OCP\AppFramework\Db\Entity; class Card extends Entity implements JsonSerializable { diff --git a/db/cardmapper.php b/db/cardmapper.php index 181c1cbd7..94c23f3d8 100644 --- a/db/cardmapper.php +++ b/db/cardmapper.php @@ -10,8 +10,11 @@ use OCP\AppFramework\Db\Mapper; class CardMapper extends Mapper { - public function __construct(IDb $db) { + private $labelMapper; + + public function __construct(IDb $db, LabelMapper $labelMapper) { parent::__construct($db, 'deck_cards', '\OCA\Deck\Db\Card'); + $this->labelMapper = $labelMapper; } public function insert(Entity $entity) { @@ -37,7 +40,10 @@ class CardMapper extends Mapper { public function find($id) { $sql = 'SELECT * FROM `*PREFIX*deck_cards` ' . 'WHERE `id` = ?'; - return $this->findEntity($sql, [$id]); + $card = $this->findEntity($sql, [$id]); + $labels = $this->labelMapper->findAssignedLabelsForCard($card->id); + $card->setLabels($labels); + return $card; } public function findAllByBoard($boardId, $limit=null, $offset=null) { @@ -55,4 +61,20 @@ class CardMapper extends Mapper { return parent::delete($entity); } + public function assignLabel($card, $label) { + $sql = 'INSERT INTO `*PREFIX*deck_assigned_labels` (`label_id`,`card_id`) VALUES (?,?)'; + $stmt = $this->db->prepare($sql); + $stmt->bindParam(1, $label, \PDO::PARAM_INT); + $stmt->bindParam(2, $card, \PDO::PARAM_INT); + $stmt->execute(); + } + + public function removeLabel($card, $label) { + $sql = 'DELETE FROM `*PREFIX*deck_assigned_labels` WHERE card_id = ? AND label_id = ?'; + $stmt = $this->db->prepare($sql); + $stmt->bindParam(1, $card, \PDO::PARAM_INT); + $stmt->bindParam(2, $label, \PDO::PARAM_INT); + $stmt->execute(); + } + } \ No newline at end of file diff --git a/db/entity.php b/db/entity.php new file mode 100644 index 000000000..6255d4701 --- /dev/null +++ b/db/entity.php @@ -0,0 +1,48 @@ +_relations)) { + $this->_relations[] = $property; + } + } + /** + * Mark am attribute as updated + * overwritten from \OCP\AppFramework\Db\Entity to avoid writing relational attributes + * @param string $attribute the name of the attribute + * @since 7.0.0 + */ + protected function markFieldUpdated($attribute){ + if(!in_array($attribute, $this->_relations)) { + $this->_updatedFields[$attribute] = true; + } + } + + /** + * overwritten from \OCP\AppFramework\Db\Entity to avoid writing relational attributes + * @return array Array of field's update status + */ + public function getUpdatedFields(){ + return $this->_updatedFields; + } + + + +} \ No newline at end of file diff --git a/db/label.php b/db/label.php index 6949faee4..f4bb9ed51 100644 --- a/db/label.php +++ b/db/label.php @@ -3,7 +3,6 @@ namespace OCA\Deck\Db; use JsonSerializable; -use OCP\AppFramework\Db\Entity; class Label extends Entity implements JsonSerializable { diff --git a/db/labelmapper.php b/db/labelmapper.php index ab404d422..32690f518 100644 --- a/db/labelmapper.php +++ b/db/labelmapper.php @@ -24,11 +24,11 @@ class LabelMapper extends DeckMapper { } public function findAssignedLabelsForCard($cardId) { - $sql = 'SELECT * FROM `*PREFIX*deck_assigned_labels` as al INNER JOIN *PREFIX*deck_labels as l ON l.id = al.label_id WHERE `card_id` = ?'; + $sql = 'SELECT l.* FROM `*PREFIX*deck_assigned_labels` as al INNER JOIN *PREFIX*deck_labels as l ON l.id = al.label_id WHERE `card_id` = ?'; return $this->findEntities($sql, [$cardId], $limit, $offset); } public function findAssignedLabelsForBoard($boardId, $limit=null, $offset=null) { - $sql = "SELECT c.id as card_id, l.id as id, l.title as title, color FROM oc_deck_cards as c " . + $sql = "SELECT c.id as card_id, l.id as id, l.title as title, l.color as color FROM oc_deck_cards as c " . " INNER JOIN oc_deck_assigned_labels as al, oc_deck_labels as l ON al.card_id = c.id AND al.label_id = l.id WHERE board_id=?"; $entities = $this->findEntities($sql, [$boardId], $limit, $offset); return $entities; diff --git a/js/app/App.js b/js/app/App.js index ea87fb395..8072d1c32 100644 --- a/js/app/App.js +++ b/js/app/App.js @@ -1,2 +1,28 @@ -var app = angular.module('Deck', ['ngRoute', 'ngSanitize', 'ui.router', 'as.sortable']); +angular.module('markdown', []) + .provider('markdown', [function () { + var opts = {}; + return { + config: function (newOpts) { + opts = newOpts; + }, + $get: function () { + return new window.showdown.Converter(opts); + } + }; + }]) + .filter('markdown', ['markdown', function (markdown) { + return function (text) { + return markdown.makeHtml(text || ''); + }; + }]); + +var app = angular.module('Deck', [ + 'ngRoute', + 'ngSanitize', + 'ui.router', + 'ui.select', + 'as.sortable', + 'markdown', + 'ngAnimate' +]); diff --git a/js/app/Config.js b/js/app/Config.js index d9a18dbda..9221a5224 100644 --- a/js/app/Config.js +++ b/js/app/Config.js @@ -17,6 +17,14 @@ app.config(function ($provide, $routeProvider, $interpolateProvider, $httpProvid templateUrl: "/board.html", controller: 'BoardController' }) + .state('board.detail', { + url: "/detail/", + views: { + "sidebarView": { + templateUrl: "/board.sidebarView.html", + } + } + }) .state('board.card', { url: "/card/:cardId", views: { diff --git a/js/app/Run.js b/js/app/Run.js index abcc1bd58..8e12bdcae 100644 --- a/js/app/Run.js +++ b/js/app/Run.js @@ -6,10 +6,16 @@ app.run(function ($document, $rootScope, $transitions) { $transitions.onEnter({to: 'board.card'}, function ($state, $transition$) { $rootScope.sidebar.show = true; }); + $transitions.onEnter({to: 'board.detail'}, function ($state, $transition$) { + $rootScope.sidebar.show = true; + }); $transitions.onEnter({to: 'board'}, function ($state) { $rootScope.sidebar.show = false; }); $transitions.onExit({from: 'board.card'}, function ($state) { $rootScope.sidebar.show = false; }); + $transitions.onExit({from: 'board.detail'}, function ($state) { + $rootScope.sidebar.show = false; + }); }); diff --git a/js/bower.json b/js/bower.json index 8ef9054ff..7a294d0f7 100644 --- a/js/bower.json +++ b/js/bower.json @@ -7,13 +7,15 @@ "angular-mocks": "~1.5.*", "angular-sanitize": "~1.5.*", "angular-animate": "~1.5.*", - "angular-ui-bootstrap": "~1.*.*", - "ng-sortable": "~1.*.*", + "angular-ui-bootstrap": "~1.*.*", + "ng-sortable": "~1.*.*", "jquery": "~2.*", "momentjs": "~2.11.*", "es6-shim": "~0.*", "js-url": "~2.*", - "masonry": "~4.0.0" + "masonry": "~4.0.0", + "showdown": "~1.4.2", + "angular-ui-select": "~0.18.0" }, "license": "AGPL-3.0", "private": true, diff --git a/js/controller/BoardController.js b/js/controller/BoardController.js index d01e52808..1effcfdf9 100644 --- a/js/controller/BoardController.js +++ b/js/controller/BoardController.js @@ -1,13 +1,18 @@ -app.controller('BoardController', function ($rootScope, $scope, $stateParams, StatusService, BoardService, StackService, CardService) { +app.controller('BoardController', function ($rootScope, $scope, $stateParams, StatusService, BoardService, StackService, CardService, LabelService) { $scope.sidebar = $rootScope.sidebar; $scope.id = $stateParams.boardId; + $scope.status={}, + $scope.newLabel={}; + $scope.status.boardtab = $stateParams.detailTab; $scope.stackservice = StackService; $scope.boardservice = BoardService; $scope.statusservice = StatusService.getInstance(); + $scope.labelservice = LabelService; + $scope.defaultColors = ['31CC7C', '317CCC', 'FF7A66', 'F1DB50', '7C31CC', 'CC317C', '3A3B3D', 'CACBCD']; // fetch data @@ -15,7 +20,6 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St $scope.statusservice.retainWaiting(); $scope.statusservice.retainWaiting(); - console.log("foo"); StackService.fetchAll($scope.id).then(function(data) { console.log(data); @@ -61,6 +65,26 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St } + $scope.labelDelete = function(label) { + LabelService.delete(label.id); + // remove from board data + var i = BoardService.getCurrent().labels.indexOf(label); + BoardService.getCurrent().labels.splice(i, 1); + // TODO: remove from cards + } + $scope.labelCreate = function(label) { + label.boardId = $scope.id; + LabelService.create(label); + BoardService.getCurrent().labels.push(label); + $scope.status.createLabel = false; + $scope.newLabel = {}; + } + $scope.labelUpdate = function(label) { + label.edit = false; + LabelService.update(label); + } + + // TODO: move to filter? // Lighten Color of the board for background usage $scope.rgblight = function (hex) { var result = /^([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex); @@ -77,6 +101,49 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St } }; + // TODO: move to filter? + // RGB2HLS by Garry Tan + // http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c + $scope.textColor = function (hex) { + var result = /^([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex); + var color = result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : null; + if(result !== null) { + r = color.r/255; + g = color.g/255; + b = color.b/255; + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2; + + if(max == min){ + h = s = 0; // achromatic + }else{ + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch(max){ + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + // TODO: Maybe just darken/lighten the color + if(l<0.5) { + return "#ffffff"; + } else { + return "#000000"; + } + //var rgba = "rgba(" + color.r + "," + color.g + "," + color.b + ",0.7)"; + //return rgba; + } else { + return "#aa0000"; + } + }; + + // settings for card sorting $scope.sortOptions = { diff --git a/js/controller/CardController.js b/js/controller/CardController.js index 547904c92..3f28e7948 100644 --- a/js/controller/CardController.js +++ b/js/controller/CardController.js @@ -2,6 +2,7 @@ app.controller('CardController', function ($scope, $rootScope, $routeParams, $location, $stateParams, BoardService, CardService, StackService, StatusService) { $scope.sidebar = $rootScope.sidebar; + $scope.status = {}; $scope.cardservice = CardService; $scope.cardId = $stateParams.cardId; @@ -27,6 +28,22 @@ app.controller('CardController', function ($scope, $rootScope, $routeParams, $lo }); }; + $scope.updateCard = function(card) { + CardService.update(CardService.getCurrent()); + $scope.status.description = false; + } + + $scope.editDescription = function() { + $scope.status.description = true; + } + + $scope.labelAssign = function(element, model) { + CardService.assignLabel($scope.cardId, element.id) + + } + $scope.labelRemove = function(element, model) { + CardService.removeLabel($scope.cardId, element.id) + } /*var menu = $('#app-content'); menu.click(function(event){ diff --git a/js/public/app.js b/js/public/app.js index ddd4f768f..ef4e3855d 100644 --- a/js/public/app.js +++ b/js/public/app.js @@ -1,5 +1,31 @@ -var app = angular.module('Deck', ['ngRoute', 'ngSanitize', 'ui.router', 'as.sortable']); +angular.module('markdown', []) + .provider('markdown', [function () { + var opts = {}; + return { + config: function (newOpts) { + opts = newOpts; + }, + $get: function () { + return new window.showdown.Converter(opts); + } + }; + }]) + .filter('markdown', ['markdown', function (markdown) { + return function (text) { + return markdown.makeHtml(text || ''); + }; + }]); + +var app = angular.module('Deck', [ + 'ngRoute', + 'ngSanitize', + 'ui.router', + 'ui.select', + 'as.sortable', + 'markdown', + 'ngAnimate' +]); app.config(["$provide", "$routeProvider", "$interpolateProvider", "$httpProvider", "$urlRouterProvider", "$stateProvider", "$compileProvider", function ($provide, $routeProvider, $interpolateProvider, $httpProvider, $urlRouterProvider, $stateProvider, $compileProvider) { @@ -21,6 +47,14 @@ app.config(["$provide", "$routeProvider", "$interpolateProvider", "$httpProvider templateUrl: "/board.html", controller: 'BoardController' }) + .state('board.detail', { + url: "/detail/", + views: { + "sidebarView": { + templateUrl: "/board.sidebarView.html", + } + } + }) .state('board.card', { url: "/card/:cardId", views: { @@ -45,12 +79,18 @@ app.run(["$document", "$rootScope", "$transitions", function ($document, $rootSc $transitions.onEnter({to: 'board.card'}, function ($state, $transition$) { $rootScope.sidebar.show = true; }); + $transitions.onEnter({to: 'board.detail'}, function ($state, $transition$) { + $rootScope.sidebar.show = true; + }); $transitions.onEnter({to: 'board'}, function ($state) { $rootScope.sidebar.show = false; }); $transitions.onExit({from: 'board.card'}, function ($state) { $rootScope.sidebar.show = false; }); + $transitions.onExit({from: 'board.detail'}, function ($state) { + $rootScope.sidebar.show = false; + }); }]); @@ -61,15 +101,20 @@ app.controller('AppController', ["$scope", "$location", "$http", "$route", "$log $scope.sidebar = $rootScope.sidebar; }]); -app.controller('BoardController', ["$rootScope", "$scope", "$stateParams", "StatusService", "BoardService", "StackService", "CardService", function ($rootScope, $scope, $stateParams, StatusService, BoardService, StackService, CardService) { +app.controller('BoardController', ["$rootScope", "$scope", "$stateParams", "StatusService", "BoardService", "StackService", "CardService", "LabelService", function ($rootScope, $scope, $stateParams, StatusService, BoardService, StackService, CardService, LabelService) { $scope.sidebar = $rootScope.sidebar; $scope.id = $stateParams.boardId; + $scope.status={}, + $scope.newLabel={}; + $scope.status.boardtab = $stateParams.detailTab; $scope.stackservice = StackService; $scope.boardservice = BoardService; $scope.statusservice = StatusService.getInstance(); + $scope.labelservice = LabelService; + $scope.defaultColors = ['31CC7C', '317CCC', 'FF7A66', 'F1DB50', '7C31CC', 'CC317C', '3A3B3D', 'CACBCD']; // fetch data @@ -77,7 +122,6 @@ app.controller('BoardController', ["$rootScope", "$scope", "$stateParams", "Stat $scope.statusservice.retainWaiting(); $scope.statusservice.retainWaiting(); - console.log("foo"); StackService.fetchAll($scope.id).then(function(data) { console.log(data); @@ -123,6 +167,26 @@ app.controller('BoardController', ["$rootScope", "$scope", "$stateParams", "Stat } + $scope.labelDelete = function(label) { + LabelService.delete(label.id); + // remove from board data + var i = BoardService.getCurrent().labels.indexOf(label); + BoardService.getCurrent().labels.splice(i, 1); + // TODO: remove from cards + } + $scope.labelCreate = function(label) { + label.boardId = $scope.id; + LabelService.create(label); + BoardService.getCurrent().labels.push(label); + $scope.status.createLabel = false; + $scope.newLabel = {}; + } + $scope.labelUpdate = function(label) { + label.edit = false; + LabelService.update(label); + } + + // TODO: move to filter? // Lighten Color of the board for background usage $scope.rgblight = function (hex) { var result = /^([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex); @@ -139,6 +203,49 @@ app.controller('BoardController', ["$rootScope", "$scope", "$stateParams", "Stat } }; + // TODO: move to filter? + // RGB2HLS by Garry Tan + // http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c + $scope.textColor = function (hex) { + var result = /^([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex); + var color = result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : null; + if(result !== null) { + r = color.r/255; + g = color.g/255; + b = color.b/255; + var max = Math.max(r, g, b), min = Math.min(r, g, b); + var h, s, l = (max + min) / 2; + + if(max == min){ + h = s = 0; // achromatic + }else{ + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch(max){ + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + } + // TODO: Maybe just darken/lighten the color + if(l<0.5) { + return "#ffffff"; + } else { + return "#000000"; + } + //var rgba = "rgba(" + color.r + "," + color.g + "," + color.b + ",0.7)"; + //return rgba; + } else { + return "#aa0000"; + } + }; + + // settings for card sorting $scope.sortOptions = { @@ -191,6 +298,7 @@ app.controller('BoardController', ["$rootScope", "$scope", "$stateParams", "Stat app.controller('CardController', ["$scope", "$rootScope", "$routeParams", "$location", "$stateParams", "BoardService", "CardService", "StackService", "StatusService", function ($scope, $rootScope, $routeParams, $location, $stateParams, BoardService, CardService, StackService, StatusService) { $scope.sidebar = $rootScope.sidebar; + $scope.status = {}; $scope.cardservice = CardService; $scope.cardId = $stateParams.cardId; @@ -216,6 +324,22 @@ app.controller('CardController', ["$scope", "$rootScope", "$routeParams", "$loca }); }; + $scope.updateCard = function(card) { + CardService.update(CardService.getCurrent()); + $scope.status.description = false; + } + + $scope.editDescription = function() { + $scope.status.description = true; + } + + $scope.labelAssign = function(element, model) { + CardService.assignLabel($scope.cardId, element.id) + + } + $scope.labelRemove = function(element, model) { + CardService.removeLabel($scope.cardId, element.id) + } /*var menu = $('#app-content'); menu.click(function(event){ @@ -432,6 +556,7 @@ app.factory('ApiService', ["$http", "$q", function($http, $q){ return deferred.promise; }; + // methods for managing data ApiService.prototype.clear = function() { @@ -444,7 +569,8 @@ app.factory('ApiService', ["$http", "$q", function($http, $q){ } else { Object.keys(entity).forEach(function (key) { element[key] = entity[key]; - element[key].status = {}; + if(element[key]!==null) + element[key].status = {}; }); } }; @@ -516,9 +642,42 @@ app.factory('CardService', ["ApiService", "$http", "$q", function(ApiService, $h return deferred.promise; } + CardService.prototype.assignLabel = function(card, label) { + //['name' => 'card#assignLabel', 'url' => '/cards/{cardId}/label/{labelId}', 'verb' => 'POST'], + var url = this.baseUrl + '/' + card + '/label/' + label; + var deferred = $q.defer(); + var self = this; + $http.post(url).then(function (response) { + deferred.resolve(response.data); + }, function (error) { + deferred.reject('Error while update ' + self.endpoint); + }); + return deferred.promise; + } + CardService.prototype.removeLabel = function(card, label) { + // ['name' => 'card#removeLabel', 'url' => '/cards/{cardId}/label/{labelId}', 'verb' => 'DELETE'], + var url = this.baseUrl + '/' + card + '/label/' + label; + var deferred = $q.defer(); + var self = this; + $http.delete(url).then(function (response) { + deferred.resolve(response.data); + }, function (error) { + deferred.reject('Error while update ' + self.endpoint); + }); + return deferred.promise; + } + service = new CardService($http, 'cards', $q) return service; }]); +app.factory('LabelService', ["ApiService", "$http", "$q", function(ApiService, $http, $q){ + var LabelService = function($http, ep, $q) { + ApiService.call(this, $http, ep, $q); + }; + LabelService.prototype = angular.copy(ApiService.prototype); + service = new LabelService($http, 'labels', $q) + return service; +}]); app.factory('StackService', ["ApiService", "$http", "$q", function(ApiService, $http, $q){ var StackService = function($http, ep, $q) { ApiService.call(this, $http, ep, $q); diff --git a/js/service/ApiService.js b/js/service/ApiService.js index 241fbf0b8..c1b758088 100644 --- a/js/service/ApiService.js +++ b/js/service/ApiService.js @@ -85,6 +85,7 @@ app.factory('ApiService', function($http, $q){ return deferred.promise; }; + // methods for managing data ApiService.prototype.clear = function() { @@ -97,7 +98,8 @@ app.factory('ApiService', function($http, $q){ } else { Object.keys(entity).forEach(function (key) { element[key] = entity[key]; - element[key].status = {}; + if(element[key]!==null) + element[key].status = {}; }); } }; diff --git a/js/service/CardService.js b/js/service/CardService.js index 36c60d518..16ec396ad 100644 --- a/js/service/CardService.js +++ b/js/service/CardService.js @@ -28,6 +28,31 @@ app.factory('CardService', function(ApiService, $http, $q){ return deferred.promise; } + CardService.prototype.assignLabel = function(card, label) { + //['name' => 'card#assignLabel', 'url' => '/cards/{cardId}/label/{labelId}', 'verb' => 'POST'], + var url = this.baseUrl + '/' + card + '/label/' + label; + var deferred = $q.defer(); + var self = this; + $http.post(url).then(function (response) { + deferred.resolve(response.data); + }, function (error) { + deferred.reject('Error while update ' + self.endpoint); + }); + return deferred.promise; + } + CardService.prototype.removeLabel = function(card, label) { + // ['name' => 'card#removeLabel', 'url' => '/cards/{cardId}/label/{labelId}', 'verb' => 'DELETE'], + var url = this.baseUrl + '/' + card + '/label/' + label; + var deferred = $q.defer(); + var self = this; + $http.delete(url).then(function (response) { + deferred.resolve(response.data); + }, function (error) { + deferred.reject('Error while update ' + self.endpoint); + }); + return deferred.promise; + } + service = new CardService($http, 'cards', $q) return service; }); \ No newline at end of file diff --git a/js/service/LabelService.js b/js/service/LabelService.js new file mode 100644 index 000000000..744cefcec --- /dev/null +++ b/js/service/LabelService.js @@ -0,0 +1,8 @@ +app.factory('LabelService', function(ApiService, $http, $q){ + var LabelService = function($http, ep, $q) { + ApiService.call(this, $http, ep, $q); + }; + LabelService.prototype = angular.copy(ApiService.prototype); + service = new LabelService($http, 'labels', $q) + return service; +}); \ No newline at end of file diff --git a/service/boardservice.php b/service/boardservice.php index 0eaab0631..4973794b6 100644 --- a/service/boardservice.php +++ b/service/boardservice.php @@ -2,6 +2,7 @@ namespace OCA\Deck\Service; +use OCA\Deck\Db\Label; use OCP\ILogger; use OCP\IL10N; use OCP\AppFramework\Db\DoesNotExistException; @@ -9,19 +10,23 @@ use OCP\AppFramework\Utility\ITimeFactory; use \OCA\Deck\Db\Board; use \OCA\Deck\Db\BoardMapper; +use \OCA\Deck\Db\LabelMapper; class BoardService { private $boardMapper; + private $labelMapper; private $logger; private $l10n; private $timeFactory; public function __construct(BoardMapper $boardMapper, ILogger $logger, IL10N $l10n, - ITimeFactory $timeFactory) { + ITimeFactory $timeFactory, + LabelMapper $labelMapper) { $this->boardMapper = $boardMapper; + $this->labelMapper = $labelMapper; $this->logger = $logger; } @@ -45,7 +50,19 @@ class BoardService { $board->setTitle($title); $board->setOwner($userId); $board->setColor($color); - return $this->boardMapper->insert($board); + $new_board = $this->boardMapper->insert($board); + + // create new labels + $default_labels = ['31CC7C', '317CCC', 'FF7A66', 'F1DB50', '7C31CC', 'CC317C', '3A3B3D', 'CACBCD']; + $labels = []; + foreach ($default_labels as $color) { + $label = new Label(); + $label->setColor($color); + $label->setBoardId($new_board->getId()); + $labels[] = $this->labelMapper->insert($label); + } + $new_board->setLabels($labels); + return $new_board; } diff --git a/service/cardservice.php b/service/cardservice.php index f19b47c93..23fab002a 100644 --- a/service/cardservice.php +++ b/service/cardservice.php @@ -39,13 +39,14 @@ class CardService { return $this->cardMapper->delete($this->cardMapper->find($id)); } - public function update($id, $title, $stackId, $type, $order, $owner) { + public function update($id, $title, $stackId, $type, $order, $description, $owner) { $card = $this->cardMapper->find($id); $card->setTitle($title); $card->setStackId($stackId); $card->setType($type); $card->setOrder($order); $card->setOwner($owner); + $card->setDescription($description); return $this->cardMapper->update($card); } @@ -75,4 +76,13 @@ class CardService { $cards = $this->cardMapper->findAll($stackId); return $cards; } + + + public function assignLabel($userId, $cardId, $labelId) { + $this->cardMapper->assignLabel($cardId, $labelId); + } + + public function removeLabel($userId, $cardId, $labelId) { + $this->cardMapper->removeLabel($cardId, $labelId); + } } \ No newline at end of file diff --git a/service/labelservice.php b/service/labelservice.php new file mode 100644 index 000000000..b24f2cb9a --- /dev/null +++ b/service/labelservice.php @@ -0,0 +1,56 @@ +labelMapper = $labelMapper; + $this->logger = $logger; + } + + public function find($userId, $labelId) { + $label = $this->labelMapper->find($labelId); + // FIXME: [share] Check for user permissions + return $label; + } + + public function create($title, $userId, $color, $boardId) { + $label = new Label(); + $label->setTitle($title); + $label->setColor($color); + $label->setBoardId($boardId); + return $this->labelMapper->insert($label); + } + + public function delete($userId, $id) { + return $this->labelMapper->delete($this->find($userId, $id)); + } + + public function update($id, $title, $userId, $color) { + $label = $this->find($userId, $id); + $label->setTitle($title); + $label->setColor($color); + return $this->labelMapper->update($label); + } + +} \ No newline at end of file diff --git a/templates/main.php b/templates/main.php index eda01b680..2e34babee 100644 --- a/templates/main.php +++ b/templates/main.php @@ -5,6 +5,7 @@ use OCP\Util; Util::addStyle('deck', 'font-awesome'); Util::addStyle('deck', 'style'); Util::addStyle('deck', '../js/vendor/ng-sortable/dist/ng-sortable.min'); +Util::addStyle('deck', '../js/vendor/angular-ui-select/dist/select.min'); //Util::addStyle('deck', '../js/vendor/ng-sortable/dist/ng-sortable.style.min'); Util::addScript('deck', 'vendor/angular/angular.min'); Util::addScript('deck', 'vendor/angular-route/angular-route.min'); @@ -12,6 +13,8 @@ Util::addScript('deck', 'vendor/angular-sanitize/angular-sanitize.min'); Util::addScript('deck', 'vendor/angular-animate/angular-animate.min'); Util::addScript('deck', 'vendor/angular-ui-router/release/angular-ui-router.min'); Util::addScript('deck', 'vendor/ng-sortable/dist/ng-sortable.min'); +Util::addScript('deck', 'vendor/angular-ui-select/dist/select.min'); +Util::addScript('deck', 'vendor/showdown/dist/showdown.min'); Util::addScript('deck', 'public/app'); ?> @@ -33,15 +36,15 @@ Util::addScript('deck', 'public/app'); + - diff --git a/templates/part.board.mainView.php b/templates/part.board.mainView.php index 11023d20b..188ceda06 100644 --- a/templates/part.board.mainView.php +++ b/templates/part.board.mainView.php @@ -4,26 +4,30 @@

{{ statusservice.title }}

{{ statusservice.text }}

-
-

- {{ boardservice.data[id].title }} -

+
+

+ {{ boardservice.data[id].title }}
-
Filter
-
by label -
    -
  • {{ label.title }}
  • -
-
-
by creator
-
by members
+
by label
+
    +
  • {{ label.title }}
  • +
+
by assignee
+
    +
  • {{ label.title }}
  • +
+ + + + + +
+

+
+
-
-
-
-
@@ -44,7 +48,8 @@

{{ c.title }}

    -
  • {{ label.title }}
  • +
  • {{ label.title }} +
diff --git a/templates/part.board.php b/templates/part.board.php index b191dc3d0..691195ea2 100644 --- a/templates/part.board.php +++ b/templates/part.board.php @@ -1,6 +1,4 @@ inc('part.board.mainView')); ?> - - -
+
diff --git a/templates/part.board.sidebarView.php b/templates/part.board.sidebarView.php new file mode 100644 index 000000000..dec37b8d7 --- /dev/null +++ b/templates/part.board.sidebarView.php @@ -0,0 +1,79 @@ +
+
+
+

{{ statusservice.title }}

+

{{ statusservice.text }}

+
+ + + + +
+
+ +
    +
  • +
    Freigabe aufheben
    +
    D
    + directmenu + + + + + + + +
  • +
+ +
+
+ + + +
+
+

+

+

+

+ +
+
diff --git a/templates/part.card.php b/templates/part.card.php index 20d754e2a..324e7d081 100644 --- a/templates/part.card.php +++ b/templates/part.card.php @@ -4,6 +4,7 @@

{{ statusservice.title }}

{{ statusservice.text }}

+{{card=cardservice.getCurrent();""}}
 

@@ -18,26 +19,40 @@
Modified: {{ cardservice.getCurrent().lastModified*1000|date:'medium' }} Created: {{ cardservice.getCurrent().createdAt*1000|date:'medium' }} + by {{ cardservice.getCurrent().owner }}
-
    -
  • important
  • -
  • action-needed
  • -
  • action-needed
  • -
+ + + {{$item.title}} + + {{label.title}} + + + +
+
+
-
D
-
E
-
C
-
K
-
+
+ + {{$item.title}} + +
D
+
+
- -
Saved
-
+

Description

+ +
Add a card description ...
+ + +
+ +