This commit is contained in:
Julius Haertl
2016-06-20 10:44:41 +02:00
parent ba8283dcdf
commit c0a9f010a8
28 changed files with 691 additions and 106 deletions

View File

@@ -88,6 +88,12 @@
<notnull>true</notnull> <notnull>true</notnull>
<length>64</length> <length>64</length>
</field> </field>
<field>
<name>description</name>
<type>text</type>
<notnull>false</notnull>
<length>4096</length>
</field>
<field> <field>
<name>stack_id</name> <name>stack_id</name>
<type>integer</type> <type>integer</type>
@@ -207,10 +213,10 @@
<notnull>false</notnull> <notnull>false</notnull>
</field> </field>
<field> <field>
<name>owner</name> <name>board_id</name>
<type>text</type> <type>integer</type>
<notnull>true</notnull> <notnull>true</notnull>
<length>64</length> <length>8</length>
</field> </field>
</declaration> </declaration>
</table> </table>

View File

@@ -5,7 +5,7 @@
<description>My first ownCloud app</description> <description>My first ownCloud app</description>
<licence>AGPL</licence> <licence>AGPL</licence>
<author>Julius Härtl</author> <author>Julius Härtl</author>
<version>0.0.1.7</version> <version>0.0.1.9</version>
<namespace>Deck</namespace> <namespace>Deck</namespace>
<category>other</category> <category>other</category>
<dependencies> <dependencies>

View File

@@ -37,6 +37,7 @@ return [
['name' => 'card#read', 'url' => '/cards/{cardId}/', 'verb' => 'GET'], ['name' => 'card#read', 'url' => '/cards/{cardId}/', 'verb' => 'GET'],
['name' => 'card#create', 'url' => '/cards/', 'verb' => 'POST'], ['name' => 'card#create', 'url' => '/cards/', 'verb' => 'POST'],
['name' => 'card#update', 'url' => '/cards/', 'verb' => 'PUT'], ['name' => 'card#update', 'url' => '/cards/', 'verb' => 'PUT'],
['name' => 'card#rename', 'url' => '/cards/rename/', 'verb' => 'PUT'],
['name' => 'card#reorder', 'url' => '/cards/reorder/', 'verb' => 'PUT'], ['name' => 'card#reorder', 'url' => '/cards/reorder/', 'verb' => 'PUT'],
['name' => 'card#delete', 'url' => '/cards/{cardId}/', 'verb' => 'DELETE'], ['name' => 'card#delete', 'url' => '/cards/{cardId}/', 'verb' => 'DELETE'],

View File

@@ -1 +1 @@
0.0.1.4 0.0.1.5

View File

@@ -53,4 +53,8 @@ class BoardController extends Controller {
public function delete($boardId) { public function delete($boardId) {
return $this->boardService->delete($this->userId, $boardId); return $this->boardService->delete($this->userId, $boardId);
} }
public function labels($boardId) {
return $this->boardService->labels($this->boardId);
}
} }

View File

@@ -35,6 +35,12 @@ class CardController extends Controller {
*/ */
public function reorder($cardId, $stackId, $order) { public function reorder($cardId, $stackId, $order) {
return $this->cardService->reorder($cardId, $stackId, $order); return $this->cardService->reorder($cardId, $stackId, $order);
}
/**
* @NoAdminRequired
*/
public function rename($cardId, $title) {
return $this->cardService->rename($cardId, $title);
} }
/** /**
* @NoAdminRequired * @NoAdminRequired
@@ -54,4 +60,6 @@ class CardController extends Controller {
public function delete($cardId) { public function delete($cardId) {
return $this->cardService->delete($this->userId, $cardId); return $this->cardService->delete($this->userId, $cardId);
} }
} }

View File

@@ -45,14 +45,45 @@
color: #333333; color: #333333;
padding-right:250px; padding-right:250px;
z-index:100; z-index:100;
background-color:#ffffff; background-color:#f7f7f7;
} }
#board .board-actions { #board-actions {
position:absolute; position:absolute;
right:5px; right:5px;
top:5px; top:5px;
z-index:999; z-index:999;
} }
#board-actions .filter {
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-select li {
padding: 3px;
overflow: hidden;
width:auto;
}
.filter .filter-select li span {
display: block;
float: left;
width: 20px;height:20px;
margin-right:5px;
}
#board-actions div {
padding:5px;
float:left;
}
.board-actions button { .board-actions button {
border: none; border: none;
background-color: transparent; background-color: transparent;
@@ -99,17 +130,34 @@
float:right; float:right;
} }
.card { .card {
background-color:#fafafa; background-color:#f6f6f6;
border: 1px solid #aaa;
margin:5px; margin:5px;
padding:5px; white-space: normal;
padding-bottom:4px; padding-bottom:4px;
overflow: hidden;
position: relative; position: relative;
opacity: 1.0; opacity: 1.0;
} }
.card-upper {
overflow: hidden;
position: relative;
padding:5px;
}
.card .card-options {
position: absolute;
bottom: 10px;
right:10px;
}
.card .popovermenu {
z-index:999;
opacity: 1;
margin-left: 10px;
}
.card .card-assignees {
margin:5px;
}
.card:hover { .card:hover {
opacity: 0.6; background-color:#fcfcfc;
} }
.card a { .card a {
display:block; display:block;
@@ -119,13 +167,43 @@
font-size:10pt; font-size:10pt;
margin:0; margin:0;
padding:0; padding:0;
margin-bottom:20px; margin-bottom:-5px;
margin-top:15px;
display: inline-block;
float:left;
} }
.card h3 .fa { .card h3 .fa {
font-size:18pt; font-size:18pt;
line-height:10pt; line-height:10pt;
vertical-align: middle; vertical-align: middle;
} }
.card .labels {
position: absolute;
top:-5px;
left:5px;
}
.card .labels li {
padding:0px;
width:15px;
height:20px;
-webkit-border-radius: 3px;
font-size:80%;
border-color: transparent;
border:none;
float:left;
}
.card .labels li span {
display: none;
}
.card .labels li:hover {
}
.card .labels li:hover span {
position:absolute;
padding:3px;
background-color: inherit;
}
.as-sortable-placeholder { .as-sortable-placeholder {
margin:5px; margin:5px;
@@ -133,23 +211,7 @@
border: 1px dashed #aaa; border: 1px dashed #aaa;
} }
.labels {
position:absolute;
top:0px;
margin-top:3px;
right:0px;
}
.labels li {
padding:3px;
line-height:100%;
color:white;
font-size:10px;
font-weight:600;
text-align:right;
padding-left:3px;
pdading-right:5px;
border-right:5px solid #aaaaaa;
}
.info { .info {
padding-left:5px; padding-left:5px;
padding-right:5px; padding-right:5px;
@@ -166,7 +228,10 @@
.card.create { .card.create {
text-align:center; text-align:center;
margin:0; margin:0;
padding:0;
padding-top:4px;
border: none; border: none;
overflow:hidden;
} }
.card.create:hover { .card.create:hover {
text-align:center; text-align:center;
@@ -175,6 +240,7 @@
.card.create h3 { .card.create h3 {
margin:0; margin:0;
padding:0; padding:0;
width: 100%;
} }
.card.create h3 input { .card.create h3 input {
width:100%; width:100%;
@@ -188,7 +254,7 @@
border-bottom:1px solid #ffffff; border-bottom:1px solid #ffffff;
border-radius: 0px; border-radius: 0px;
color: #ffffff; color: #ffffff;
background-color: transparent; background-color: transparent !important;
} }
.card.create .fa { .card.create .fa {
color:#ffffff; color:#ffffff;
@@ -213,12 +279,25 @@
} }
#card-header h2 { #card-header h2 {
font-weight:600; font-weight:600;
background-color: #f3f3f3;
padding:10px; padding:10px;
overflow: hidden; overflow: hidden;
margin-bottom:0px;
background-color:#f0f0f0;
} }
#card-header h2 .icon-close { #card-header .icon-close {
float:right; position: absolute;
top:5px;
right:5px;
}
#card-meta {
padding-top:0px;
}
#card-dates {
font-size:80%;
color: #aaaaaa;
margin-bottom: 3px;
}
#card-dates span {
} }
#card-description textarea { #card-description textarea {
@@ -250,10 +329,18 @@
#app-content.details-visible { #app-content.details-visible {
margin-right: 500px; margin-right: 500px;
} }
#card-header .labels { .labels {
float:right; display:block;
position:relative; overflow:hidden;
}
.labels li {
padding:1px;
-webkit-border-radius: 3px;
margin:1px;
float:left;
color: #ffffff;
font-size:80%;
font-weight:900;
} }
#assigned-users { #assigned-users {
padding:10px; padding:10px;
@@ -313,3 +400,28 @@
float:left; float:left;
} }
#boardlist .colorselect {margin-top:5px;} #boardlist .colorselect {margin-top:5px;}
input.input-inline {
font-size: inherit !important;
font-weight: inherit;
background-color:transparent;
padding:0;
margin:0;
border:none;
width:100%;
border-bottom:1px solid #333333;
-webkit-border-radius: 0;
margin-top:-4px;
line-height:100%;
margin-bottom: -4px;
}
button {
border:0;
background-color: transparent;
}
button:hover {
border:0;
background-color: transparent;
}

View File

@@ -12,6 +12,7 @@ class Board extends Entity implements JsonSerializable {
protected $owner; protected $owner;
protected $color; protected $color;
protected $archived; protected $archived;
protected $labels;
public function __construct() { public function __construct() {
$this->addType('id','integer'); $this->addType('id','integer');
} }
@@ -20,7 +21,8 @@ class Board extends Entity implements JsonSerializable {
'id' => $this->id, 'id' => $this->id,
'title' => $this->title, 'title' => $this->title,
'owner' => $this->owner, 'owner' => $this->owner,
'color' => $this->color 'color' => $this->color,
'labels' => $this->labels,
]; ];
} }
} }

View File

@@ -9,8 +9,11 @@ use OCP\AppFramework\Db\Mapper;
class BoardMapper extends Mapper { class BoardMapper extends Mapper {
public function __construct(IDb $db) { private $labelMapper;
public function __construct(IDb $db, LabelMapper $labelMapper) {
parent::__construct($db, 'deck_boards', '\OCA\Deck\Db\Board'); parent::__construct($db, 'deck_boards', '\OCA\Deck\Db\Board');
$this->labelMapper = $labelMapper;
} }
@@ -21,7 +24,10 @@ class BoardMapper extends Mapper {
public function find($id) { public function find($id) {
$sql = 'SELECT * FROM `*PREFIX*deck_boards` ' . $sql = 'SELECT * FROM `*PREFIX*deck_boards` ' .
'WHERE `id` = ?'; 'WHERE `id` = ?';
return $this->findEntity($sql, [$id]); $board = $this->findEntity($sql, [$id]);
$labels = $this->labelMapper->findAll($id);
$board->setLabels($labels);
return $board;
} }

View File

@@ -9,10 +9,12 @@ class Card extends Entity implements JsonSerializable {
public $id; public $id;
protected $title; protected $title;
protected $description;
protected $stackId; protected $stackId;
protected $type; protected $type;
protected $lastModified; protected $lastModified;
protected $createdAt; protected $createdAt;
protected $labels;
protected $owner; protected $owner;
protected $order; protected $order;
public function __construct() { public function __construct() {
@@ -25,12 +27,14 @@ class Card extends Entity implements JsonSerializable {
return [ return [
'id' => $this->id, 'id' => $this->id,
'title' => $this->title, 'title' => $this->title,
'description' => $this->description,
'type' => $this->type, 'type' => $this->type,
'lastModified' => $this->lastModified, 'lastModified' => $this->lastModified,
'createdAt' => $this->createdAt, 'createdAt' => $this->createdAt,
'owner' => $this->owner, 'owner' => $this->owner,
'order' => $this->order, 'order' => $this->order,
'stackId' => $this->stackId, 'stackId' => $this->stackId,
'labels' => $this->labels,
]; ];
} }
} }

View File

@@ -14,6 +14,21 @@ class CardMapper extends Mapper {
parent::__construct($db, 'deck_cards', '\OCA\Deck\Db\Card'); parent::__construct($db, 'deck_cards', '\OCA\Deck\Db\Card');
} }
public function insert(Entity $entity) {
$entity->setCreatedAt(time());
$entity->setLastModified(time());
return parent::insert($entity);
}
/**
* @param Entity $entity
* @return Entity
*/
public function update(Entity $entity) {
$entity->setLastModified(time());
return parent::update($entity);
}
/** /**
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found * @throws \OCP\AppFramework\Db\DoesNotExistException if not found
@@ -25,10 +40,14 @@ class CardMapper extends Mapper {
return $this->findEntity($sql, [$id]); return $this->findEntity($sql, [$id]);
} }
public function findAllByBoard($boardId, $limit=null, $offset=null) {
}
public function findAll($stackId, $limit=null, $offset=null) { public function findAll($stackId, $limit=null, $offset=null) {
$sql = 'SELECT * FROM `*PREFIX*deck_cards` WHERE `stack_id` = ? ORDER BY `order`'; $sql = 'SELECT * FROM `*PREFIX*deck_cards` WHERE `stack_id` = ? ORDER BY `order`';
return $this->findEntities($sql, [$stackId], $limit, $offset); $entities = $this->findEntities($sql, [$stackId], $limit, $offset);
return $entities;
} }
public function delete(Entity $entity) { public function delete(Entity $entity) {

32
db/deckmapper.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
namespace OCA\Deck\Db;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\Mapper;
abstract class DeckMapper extends Mapper {
/**
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
*/
public function find($id) {
$sql = 'SELECT * FROM `' . $this->tableName . '` ' . 'WHERE `id` = ?';
return $this->findEntity($sql, [$id]);
}
/**
* Add relational data to an Entity by calling the related Mapper
* @param $entities
* @param $entityType
* @param $property
* addRelation($cards, $labels, function($one, $many) {
* if($one->id == $many->cardId)
* }
*/
public function addRelation($entities, $entityType, $property) {
}
}

27
db/label.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
// db/author.php
namespace OCA\Deck\Db;
use JsonSerializable;
use OCP\AppFramework\Db\Entity;
class Label extends Entity implements JsonSerializable {
public $id;
protected $title;
protected $color;
protected $boardId;
protected $cardId;
public function __construct() {
$this->addType('id','integer');
}
public function jsonSerialize() {
return [
'id' => $this->id,
'title' => $this->title,
'boardId' => $this->boardId,
'cardId' => $this->cardId,
'color' => $this->color,
];
}
}

48
db/labelmapper.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
namespace OCA\Deck\Db;
use OCP\AppFramework\Db\Entity;
use OCP\IDb;
use OCP\AppFramework\Db\Mapper;
class LabelMapper extends DeckMapper {
public function __construct(IDb $db) {
parent::__construct($db, 'deck_labels', '\OCA\Deck\Db\Label');
}
public function findAll($boardId, $limit=null, $offset=null) {
$sql = 'SELECT * FROM `*PREFIX*deck_labels` WHERE `board_id` = ?';
return $this->findEntities($sql, [$boardId], $limit, $offset);
}
public function delete(Entity $entity) {
// FIXME: delete linked elements, because owncloud doesn't support foreign keys for apps
return parent::delete($entity);
}
public function findAssignedLabelsForCard($cardId) {
$sql = 'SELECT * FROM `*PREFIX*deck_assigned_labels` as al 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 " .
"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;
}
public function getAssignedLabelsForBoard($boardId) {
$labels = $this->findAssignedLabelsForBoard($boardId);
$result = array();
foreach ($labels as $label) {
if(!is_array($result[$label->getCardId()])) {
$result[$label->getCardId()] = array();
}
$result[$label->getCardId()][] = $label;
}
return $result;
}
}

View File

@@ -7,18 +7,25 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
$scope.stackservice = StackService; $scope.stackservice = StackService;
$scope.boardservice = BoardService; $scope.boardservice = BoardService;
$scope.statusservice = StatusService; $scope.statusservice = StatusService.getInstance();
// fetch data // fetch data
StackService.clear(); StackService.clear();
$scope.statusservice.retainWaiting();
$scope.statusservice.retainWaiting();
console.log("foo"); console.log("foo");
StackService.fetchAll($scope.id).then(function(data) { StackService.fetchAll($scope.id).then(function(data) {
console.log(data);
$scope.statusservice.releaseWaiting(); $scope.statusservice.releaseWaiting();
}, function(error) { }, function(error) {
$scope.statusservice.setError('Error occured', error); $scope.statusservice.setError('Error occured', error);
}); });
BoardService.fetchOne($scope.id).then(function(data) { BoardService.fetchOne($scope.id).then(function(data) {
$scope.statusservice.releaseWaiting(); $scope.statusservice.releaseWaiting();
}, function(error) { }, function(error) {
$scope.statusservice.setError('Error occured', error); $scope.statusservice.setError('Error occured', error);
@@ -48,6 +55,12 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
}); });
} }
$scope.cardDelete = function(card) {
CardService.delete(card.id);
StackService.deleteCard(card);
}
// Lighten Color of the board for background usage // Lighten Color of the board for background usage
$scope.rgblight = function (hex) { $scope.rgblight = function (hex) {
var result = /^([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex); var result = /^([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex);

View File

@@ -1,17 +1,31 @@
app.controller('CardController', function ($scope, $rootScope, $routeParams, $location, $stateParams, CardService) { app.controller('CardController', function ($scope, $rootScope, $routeParams, $location, $stateParams, BoardService, CardService, StackService, StatusService) {
$scope.sidebar = $rootScope.sidebar; $scope.sidebar = $rootScope.sidebar;
$scope.cardservice = CardService; $scope.cardservice = CardService;
$scope.cardId = $stateParams.cardId; $scope.cardId = $stateParams.cardId;
$scope.statusservice = StatusService.getInstance();
$scope.boardservice = BoardService;
$scope.statusservice.retainWaiting();
CardService.fetchOne($scope.cardId).then(function(data) { CardService.fetchOne($scope.cardId).then(function(data) {
$scope.statusservice.releaseWaiting();
console.log(data); console.log(data);
}, function(error) { }, function(error) {
}); });
// handle rename to update information on the board as well
$scope.renameCard = function(card) {
CardService.rename(card).then(function(data) {
StackService.updateCard(card);
$scope.status.renameCard = false;
});
};
/*var menu = $('#app-content'); /*var menu = $('#app-content');

18
js/directive/avatar.js Normal file
View File

@@ -0,0 +1,18 @@
app.directive('avatar', function() {
'use strict';
return {
restrict: 'A',
scope: false,
link: function(scope, elm, attr) {
return attr.$observe('user', function() {
if (attr.user) {
var url = OC.generateUrl('/avatar/{user}/{size}',
{user: attr.user, size: Math.ceil(attr.size * window.devicePixelRatio)});
var inner = '<img src="'+url+'" />';
elm.html(inner);
//elm.avatar(attr.user, attr.size);
}
});
}
};
});

View File

@@ -0,0 +1,29 @@
// OwnCloud Click Handling
// https://doc.owncloud.org/server/8.0/developer_manual/app/css.html
app.directive('cardActionUtils', function () {
'use strict';
return {
restrict: 'C',
scope: {
ngModel : '=',
},
link: function (scope, elm) {
console.log(scope);
/*
var menu = elm.siblings('.popovermenu');
var button = $(elm)
.find('li a');
button.click(function () {
menu.toggleClass('open');
});
scope.$on('documentClicked', function (scope, event) {
if (event.target !== button[0]) {
menu.removeClass('open');
}
});
*/
}
};
});

View File

@@ -69,18 +69,25 @@ app.controller('BoardController', ["$rootScope", "$scope", "$stateParams", "Stat
$scope.stackservice = StackService; $scope.stackservice = StackService;
$scope.boardservice = BoardService; $scope.boardservice = BoardService;
$scope.statusservice = StatusService; $scope.statusservice = StatusService.getInstance();
// fetch data // fetch data
StackService.clear(); StackService.clear();
$scope.statusservice.retainWaiting();
$scope.statusservice.retainWaiting();
console.log("foo"); console.log("foo");
StackService.fetchAll($scope.id).then(function(data) { StackService.fetchAll($scope.id).then(function(data) {
console.log(data);
$scope.statusservice.releaseWaiting(); $scope.statusservice.releaseWaiting();
}, function(error) { }, function(error) {
$scope.statusservice.setError('Error occured', error); $scope.statusservice.setError('Error occured', error);
}); });
BoardService.fetchOne($scope.id).then(function(data) { BoardService.fetchOne($scope.id).then(function(data) {
$scope.statusservice.releaseWaiting(); $scope.statusservice.releaseWaiting();
}, function(error) { }, function(error) {
$scope.statusservice.setError('Error occured', error); $scope.statusservice.setError('Error occured', error);
@@ -110,6 +117,12 @@ app.controller('BoardController', ["$rootScope", "$scope", "$stateParams", "Stat
}); });
} }
$scope.cardDelete = function(card) {
CardService.delete(card.id);
StackService.deleteCard(card);
}
// Lighten Color of the board for background usage // Lighten Color of the board for background usage
$scope.rgblight = function (hex) { $scope.rgblight = function (hex) {
var result = /^([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex); var result = /^([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex);
@@ -176,18 +189,32 @@ app.controller('BoardController', ["$rootScope", "$scope", "$stateParams", "Stat
app.controller('CardController', ["$scope", "$rootScope", "$routeParams", "$location", "$stateParams", "CardService", function ($scope, $rootScope, $routeParams, $location, $stateParams, CardService) { 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.sidebar = $rootScope.sidebar;
$scope.cardservice = CardService; $scope.cardservice = CardService;
$scope.cardId = $stateParams.cardId; $scope.cardId = $stateParams.cardId;
$scope.statusservice = StatusService.getInstance();
$scope.boardservice = BoardService;
$scope.statusservice.retainWaiting();
CardService.fetchOne($scope.cardId).then(function(data) { CardService.fetchOne($scope.cardId).then(function(data) {
$scope.statusservice.releaseWaiting();
console.log(data); console.log(data);
}, function(error) { }, function(error) {
}); });
// handle rename to update information on the board as well
$scope.renameCard = function(card) {
CardService.rename(card).then(function(data) {
StackService.updateCard(card);
$scope.status.renameCard = false;
});
};
/*var menu = $('#app-content'); /*var menu = $('#app-content');
@@ -270,6 +297,54 @@ app.directive('autofocusOnInsert', function () {
elm.focus(); elm.focus();
}; };
}); });
app.directive('avatar', function() {
'use strict';
return {
restrict: 'A',
scope: false,
link: function(scope, elm, attr) {
return attr.$observe('user', function() {
if (attr.user) {
var url = OC.generateUrl('/avatar/{user}/{size}',
{user: attr.user, size: Math.ceil(attr.size * window.devicePixelRatio)});
var inner = '<img src="'+url+'" />';
elm.html(inner);
//elm.avatar(attr.user, attr.size);
}
});
}
};
});
// OwnCloud Click Handling
// https://doc.owncloud.org/server/8.0/developer_manual/app/css.html
app.directive('cardActionUtils', function () {
'use strict';
return {
restrict: 'C',
scope: {
ngModel : '=',
},
link: function (scope, elm) {
console.log(scope);
/*
var menu = elm.siblings('.popovermenu');
var button = $(elm)
.find('li a');
button.click(function () {
menu.toggleClass('open');
});
scope.$on('documentClicked', function (scope, event) {
if (event.target !== button[0]) {
menu.removeClass('open');
}
});
*/
}
};
});
app.factory('ApiService', ["$http", "$q", function($http, $q){ app.factory('ApiService', ["$http", "$q", function($http, $q){
var ApiService = function(http, endpoint) { var ApiService = function(http, endpoint) {
this.endpoint = endpoint; this.endpoint = endpoint;
@@ -298,8 +373,14 @@ app.factory('ApiService', ["$http", "$q", function($http, $q){
} }
ApiService.prototype.fetchOne = function (id) { ApiService.prototype.fetchOne = function (id) {
this.id = id; this.id = id;
var deferred = $q.defer(); var deferred = $q.defer();
if(id===undefined) {
return deferred.promise;
}
var self = this; var self = this;
$http.get(this.baseUrl + '/' + id).then(function (response) { $http.get(this.baseUrl + '/' + id).then(function (response) {
data = response.data; data = response.data;
@@ -422,6 +503,19 @@ app.factory('CardService', ["ApiService", "$http", "$q", function(ApiService, $h
}); });
return deferred.promise; return deferred.promise;
} }
CardService.prototype.rename = function(card) {
var deferred = $q.defer();
var self = this;
$http.put(this.baseUrl + '/rename', {cardId: card.id, title: card.title}).then(function (response) {
self.data[card.id].title = card.title;
deferred.resolve(response.data);
}, function (error) {
deferred.reject('Error while renaming ' + self.endpoint);
});
return deferred.promise;
}
service = new CardService($http, 'cards', $q) service = new CardService($http, 'cards', $q)
return service; return service;
}]); }]);
@@ -444,30 +538,50 @@ app.factory('StackService', ["ApiService", "$http", "$q", function(ApiService, $
} }
StackService.prototype.addCard = function(entity) { StackService.prototype.addCard = function(entity) {
console.log(this.data[entity.stackId]);
this.data[entity.stackId].cards.push(entity); this.data[entity.stackId].cards.push(entity);
} }
service = new StackService($http, 'stacks', $q) StackService.prototype.updateCard = function(entity) {
var self = this;
var cards = this.data[entity.stackId].cards;
for(var i=0;i<cards.length;i++) {
if(cards[i].id == entity.id) {
cards[i] = entity;
}
}
}
StackService.prototype.deleteCard = function(entity) {
var self = this;
var cards = this.data[entity.stackId].cards;
for(var i=0;i<cards.length;i++) {
if(cards[i].id == entity.id) {
cards.splice(i, 1);
}
}
}
service = new StackService($http, 'stacks', $q);
return service; return service;
}]); }]);
app.service('StatusService', function(){ app.factory('StatusService', function(){
// Status Helper // Status Helper
var StatusService = function() {
this.active = true; this.active = true;
this.icon = 'loading'; this.icon = 'loading';
this.title = 'Please wait'; this.title = 'Please wait';
this.text = 'Es dauert noch einen kleinen Moment'; this.text = 'Es dauert noch einen kleinen Moment';
this.counter = 2; this.counter = 0;
}
this.setStatus = function($icon, $title, $text) {
StatusService.prototype.setStatus = function($icon, $title, $text) {
this.active = true; this.active = true;
this.icon = $icon; this.icon = $icon;
this.title = $title; this.title = $title;
this.text = $text; this.text = $text;
} }
this.setError = function($title, $text) { StatusService.prototype.setError = function($title, $text) {
this.active = true; this.active = true;
this.icon = 'error'; this.icon = 'error';
this.title = $title; this.title = $title;
@@ -475,7 +589,7 @@ app.service('StatusService', function(){
this.counter = 0; this.counter = 0;
} }
this.releaseWaiting = function() { StatusService.prototype.releaseWaiting = function() {
if(this.counter>0) if(this.counter>0)
this.counter--; this.counter--;
if(this.counter<=0) { if(this.counter<=0) {
@@ -484,10 +598,25 @@ app.service('StatusService', function(){
} }
} }
this.unsetStatus = function() { StatusService.prototype.retainWaiting = function() {
this.active = true;
this.icon = 'loading';
this.title = 'Please wait';
this.text = 'Es dauert noch einen kleinen Moment';
this.counter++;
}
StatusService.prototype.unsetStatus = function() {
this.active = false; this.active = false;
} }
return {
getInstance: function() {
return new StatusService();
}
}
}); });

View File

@@ -26,8 +26,14 @@ app.factory('ApiService', function($http, $q){
} }
ApiService.prototype.fetchOne = function (id) { ApiService.prototype.fetchOne = function (id) {
this.id = id; this.id = id;
var deferred = $q.defer(); var deferred = $q.defer();
if(id===undefined) {
return deferred.promise;
}
var self = this; var self = this;
$http.get(this.baseUrl + '/' + id).then(function (response) { $http.get(this.baseUrl + '/' + id).then(function (response) {
data = response.data; data = response.data;

View File

@@ -15,6 +15,19 @@ app.factory('CardService', function(ApiService, $http, $q){
}); });
return deferred.promise; return deferred.promise;
} }
CardService.prototype.rename = function(card) {
var deferred = $q.defer();
var self = this;
$http.put(this.baseUrl + '/rename', {cardId: card.id, title: card.title}).then(function (response) {
self.data[card.id].title = card.title;
deferred.resolve(response.data);
}, function (error) {
deferred.reject('Error while renaming ' + self.endpoint);
});
return deferred.promise;
}
service = new CardService($http, 'cards', $q) service = new CardService($http, 'cards', $q)
return service; return service;
}); });

View File

@@ -17,10 +17,27 @@ app.factory('StackService', function(ApiService, $http, $q){
} }
StackService.prototype.addCard = function(entity) { StackService.prototype.addCard = function(entity) {
console.log(this.data[entity.stackId]);
this.data[entity.stackId].cards.push(entity); this.data[entity.stackId].cards.push(entity);
} }
service = new StackService($http, 'stacks', $q) StackService.prototype.updateCard = function(entity) {
var self = this;
var cards = this.data[entity.stackId].cards;
for(var i=0;i<cards.length;i++) {
if(cards[i].id == entity.id) {
cards[i] = entity;
}
}
}
StackService.prototype.deleteCard = function(entity) {
var self = this;
var cards = this.data[entity.stackId].cards;
for(var i=0;i<cards.length;i++) {
if(cards[i].id == entity.id) {
cards.splice(i, 1);
}
}
}
service = new StackService($http, 'stacks', $q);
return service; return service;
}); });

View File

@@ -1,19 +1,22 @@
app.service('StatusService', function(){ app.factory('StatusService', function(){
// Status Helper // Status Helper
var StatusService = function() {
this.active = true; this.active = true;
this.icon = 'loading'; this.icon = 'loading';
this.title = 'Please wait'; this.title = 'Please wait';
this.text = 'Es dauert noch einen kleinen Moment'; this.text = 'Es dauert noch einen kleinen Moment';
this.counter = 2; this.counter = 0;
}
this.setStatus = function($icon, $title, $text) {
StatusService.prototype.setStatus = function($icon, $title, $text) {
this.active = true; this.active = true;
this.icon = $icon; this.icon = $icon;
this.title = $title; this.title = $title;
this.text = $text; this.text = $text;
} }
this.setError = function($title, $text) { StatusService.prototype.setError = function($title, $text) {
this.active = true; this.active = true;
this.icon = 'error'; this.icon = 'error';
this.title = $title; this.title = $title;
@@ -21,7 +24,7 @@ app.service('StatusService', function(){
this.counter = 0; this.counter = 0;
} }
this.releaseWaiting = function() { StatusService.prototype.releaseWaiting = function() {
if(this.counter>0) if(this.counter>0)
this.counter--; this.counter--;
if(this.counter<=0) { if(this.counter<=0) {
@@ -30,9 +33,24 @@ app.service('StatusService', function(){
} }
} }
this.unsetStatus = function() { StatusService.prototype.retainWaiting = function() {
this.active = true;
this.icon = 'loading';
this.title = 'Please wait';
this.text = 'Es dauert noch einen kleinen Moment';
this.counter++;
}
StatusService.prototype.unsetStatus = function() {
this.active = false; this.active = false;
} }
return {
getInstance: function() {
return new StatusService();
}
}
}); });

View File

@@ -59,4 +59,8 @@ class BoardService {
$board->setColor($color); $board->setColor($color);
return $this->boardMapper->update($board); return $this->boardMapper->update($board);
} }
public function labels($boardId) {
}
} }

View File

@@ -49,6 +49,11 @@ class CardService {
return $this->cardMapper->update($card); return $this->cardMapper->update($card);
} }
public function rename($id, $title) {
$card = $this->cardMapper->find($id);
$card->setTitle($title);
return $this->cardMapper->update($card);
}
public function reorder($id, $stackId, $order) { public function reorder($id, $stackId, $order) {
$cards = $this->cardMapper->findAll($stackId); $cards = $this->cardMapper->findAll($stackId);
$i = 0; $i = 0;

View File

@@ -3,12 +3,14 @@
namespace OCA\Deck\Service; namespace OCA\Deck\Service;
use OCA\Deck\Db\CardMapper; use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\LabelMapper;
use OCP\ILogger; use OCP\ILogger;
use OCP\IL10N; use OCP\IL10N;
use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory; use OCP\AppFramework\Utility\ITimeFactory;
use \OCA\Deck\Db\Stack; use \OCA\Deck\Db\Stack;
use \OCA\Deck\Db\StackMapper; use \OCA\Deck\Db\StackMapper;
@@ -19,19 +21,27 @@ class StackService {
private $logger; private $logger;
private $l10n; private $l10n;
private $timeFactory; private $timeFactory;
private $labelMapper;
public function __construct(StackMapper $stackMapper, CardMapper $cardMapper,ILogger $logger, public function __construct(StackMapper $stackMapper, CardMapper $cardMapper, LabelMapper $labelMapper, ILogger $logger,
IL10N $l10n, IL10N $l10n,
ITimeFactory $timeFactory) { ITimeFactory $timeFactory) {
$this->stackMapper = $stackMapper; $this->stackMapper = $stackMapper;
$this->cardMapper = $cardMapper; $this->cardMapper = $cardMapper;
$this->labelMapper = $labelMapper;
$this->logger = $logger; $this->logger = $logger;
} }
public function findAll($boardId) { public function findAll($boardId) {
$stacks = $this->stackMapper->findAll($boardId); $stacks = $this->stackMapper->findAll($boardId);
$labels = $this->labelMapper->getAssignedLabelsForBoard($boardId);
foreach ($stacks as $idx => $s) { foreach ($stacks as $idx => $s) {
$stacks[$idx]->setCards($this->cardMapper->findAll($s->id)); $cards = $this->cardMapper->findAll($s->id);
foreach ($cards as $idxc => $card) {
$cards[$idxc]->setLabels($labels[$card->id]);
}
$stacks[$idx]->setCards($cards);
} }
return $stacks; return $stacks;
} }

View File

@@ -5,21 +5,32 @@
<p>{{ statusservice.text }}</p></div> <p>{{ statusservice.text }}</p></div>
</div> </div>
<div id="board" class="scroll-container" > <div id="board" class="scroll-container" >
<h1 style="border-bottom: 1px solid {{rgblight(boardservice.getCurrent().color)}};"> <h1>
{{ boardservice.data[id].title }} {{ boardservice.data[id].title }}
</h1> </h1>
<?php /* maybe later <div id="board-actions">
<div class="board-actions">
<button class="fa fa-share-alt"></button> <div><i class="fa fa-filter"> </i> Filter</div>
<button class="fa fa-users"></button> <div class="filter">by label <i class="fa fa-caret-down"> </i>
<button class="fa fa-ellipsis-h"></button> <ul class="filter-select bubble">
</div> */ ?> <li ng-repeat="label in boardservice.data[id].labels"><span style="background-color:#{{ label.color }};"> </span> {{ label.title }}</li>
</ul>
</div>
<div class="filter">by creator <i class="fa fa-caret-down"> </i></div>
<div class="filter">by members <i class="fa fa-caret-down"> </i></div>
<div><i class="fa fa-share-alt"> </i></div>
<div><i class="fa fa-users"> </i></div>
<div><i class="fa fa-ellipsis-h"> </i></div>
</div>
<div id="innerBoard" data-ng-model="stacks"> <div id="innerBoard" data-ng-model="stacks">
<div class="stack" ng-repeat="s in stackservice.data" data-columnindex="{{$index}}" id="column{{$index}}" data-ng-model="stackservice.data" style="border: 3px solid #{{ boardservice.getCurrent().color }};"> <div class="stack" ng-repeat="s in stackservice.data" data-columnindex="{{$index}}" id="column{{$index}}" data-ng-model="stackservice.data" style="border: 5px solid #{{ boardservice.getCurrent().color }};">
<h2><span ng-show="!s.status.editStack">{{ s.title }}</span> <h2><span ng-show="!s.status.editStack">{{ s.title }}</span>
<form ng-submit="stackservice.update(s)"> <form ng-submit="stackservice.update(s)">
<input type="text" placeholder="Add a new stack" ng-blur="s.status.editStack=false" ng-model="s.title" ng-if="s.status.editStack" autofocus-on-insert/> <input type="text" placeholder="Add a new stack" ng-blur="s.status.editStack=false" ng-model="s.title" ng-if="s.status.editStack" autofocus-on-insert required />
<button class="icon icon-save" ng-if="s.status.editStack" type="submit"></button> <button class="icon icon-save" ng-if="s.status.editStack" type="submit"></button>
</form> </form>
<div class="stack-actions"> <div class="stack-actions">
@@ -28,24 +39,39 @@
</div> </div>
</h2> </h2>
<ul data-as-sortable="sortOptions" data-ng-model="s.cards" style="min-height: 40px;"> <ul data-as-sortable="sortOptions" data-ng-model="s.cards" style="min-height: 40px;">
<li class="card as-sortable-item" ng-repeat="c in s.cards" data-as-sortable-item> <li class="card as-sortable-item" ng-repeat="c in s.cards" data-as-sortable-item ui-sref="board.card({boardId: id, cardId: c.id})">
<a href="#/board/{{ id }}/card/{{ c.id }}" data-as-sortable-item-handle> <div data-as-sortable-item-handle>
<h3><!--<i class="fa fa-github"></i>//--> {{ c.title }}</h3> <div class="card-upper">
<!-- <h3>{{ c.title }}</h3>
<span class="info due"><i class="fa fa-clock-o" aria-hidden="true"></i> <span>Today</span></span> <ul class="labels">
<li ng-repeat="label in c.labels" style="background-color: #{{ label.color }};"><span>{{ label.title }}</span></li>
</ul>
</div>
<button class="card-options icon-more" ng-click="c.status.showMenu=!c.status.showMenu; $event.stopPropagation();" ng-model="card"></button>
<div class="popovermenu bubble" ng-show="c.status.showMenu"><ul>
<li><a class="menuitem action action-rename permanent" data-action="Rename"><span class="icon icon-rename"></span><span>Umbenennen</span></a></li>
<li><a class="menuitem action action-rename permanent" data-action="Rename"><span class="icon icon-rename"></span><span>Archive</span></a></li>
<li><a class="menuitem action action-delete permanent" data-action="Delete" ng-click="cardDelete(c)"><span class="icon icon-delete"></span><span>Löschen</span></a></li></ul>
</div>
<div class="card-assignees">
<!-- <div class="avatar" avatar user="{{c.owner}}" size="24"></div>//-->
</div>
<!--<span class="info due"><i class="fa fa-clock-o" aria-hidden="true"></i> <span>Today</span></span>
<span class="info tasks"><i class="fa fa-list" aria-hidden="true"></i> <span>3/12</span></span> <span class="info tasks"><i class="fa fa-list" aria-hidden="true"></i> <span>3/12</span></span>
<span class="info members"><i class="fa fa-users" aria-hidden="true"></i> <span>4</span></span> <span class="info members"><i class="fa fa-users" aria-hidden="true"></i> <span>4</span></span>
//--> //-->
<button class="icon-more"></button>
</a> </div>
</li> </li>
</ul> </ul>
<!-- CREATE CARD //--> <!-- CREATE CARD //-->
<div class="card create" style="background-color:#{{ boardservice.getCurrent().color }};"> <div class="card create" style="background-color:#{{ boardservice.getCurrent().color }};">
<form ng-submit="createCard(s.id, newCard.title)"> <form ng-submit="createCard(s.id, newCard.title)">
<h3 ng-if="s.status.addCard" ><input type="text" autofocus-on-insert ng-model="newCard.title" ng-blur="s.status.addCard=false"/></h3> <h3 ng-if="s.status.addCard" >
<input type="text" autofocus-on-insert ng-model="newCard.title" ng-blur="s.status.addCard=false" required />
</h3>
</form> </form>
<div class="fa fa-plus" ng-if="!s.status.addCard" ng-click="s.status.addCard=!s.status.addCard"></div> <div class="fa fa-plus" ng-if="!s.status.addCard" ng-click="s.status.addCard=!s.status.addCard"></div>
</div> </div>
@@ -53,7 +79,7 @@
<div class="stack" style="display: inline-block;"> <div class="stack" style="display: inline-block;">
<form class="ng-pristine ng-valid" ng-submit="createStack()"> <form class="ng-pristine ng-valid" ng-submit="createStack()">
<h2> <h2>
<input type="text" placeholder="Add a new stack" ng-focus="status.addStack=true" ng-blur="status.addStack=false" ng-model="newStack.title" > <input type="text" placeholder="Add a new stack" ng-focus="status.addStack=true" ng-blur="status.addStack=false" ng-model="newStack.title" required />
<button class="icon icon-add" ng-show="status.addStack" type="submit"></button> <button class="icon icon-add" ng-show="status.addStack" type="submit"></button>
</h2> </h2>
</form> </form>

View File

@@ -1,13 +1,28 @@
<div id="board-status" ng-if="statusservice.active">
<div id="emptycontent">
<div class="icon-{{ statusservice.icon }}"></div>
<h2>{{ statusservice.title }}</h2>
<p>{{ statusservice.text }}</p></div>
</div>
<div id="card-header"> <div id="card-header">
<a class="icon-close" ui-sref="board" ng-click="sidebar.show=!sidebar.show"> &nbsp;</a>
<h2>
<form ng-submit="renameCard(cardservice.getCurrent())">
<input class="input-inline" type="text" ng-if="status.renameCard" ng-model="cardservice.getCurrent().title" ng-blur="renameCard(cardservice.getCurrent())" autofocus-on-insert required>
</form>
<div ng-click="status.renameCard=true" ng-show="!status.renameCard">{{ cardservice.getCurrent().title }}</div>
</h2>
</div>
<h2>{{ cardservice.getCurrent().title }}<a class="icon-close" ng-click="sidebar.show=!sidebar.show"> &nbsp;</a></h2> <div id="card-meta" class="card-block">
Modified: {{ cardservice.getCurrent().modifiedAt }} <div id="card-dates">
Created: {{ cardservice.getCurrent().createdAt }} Modified: <span>{{ cardservice.getCurrent().lastModified*1000|date:'medium' }}</span>
Created: <span>{{ cardservice.getCurrent().createdAt*1000|date:'medium' }}</span>
</div>
<ul class="labels"> <ul class="labels">
<li style="color:#a00; border-color:#aa0000;">important</li> <li style="background-color:#aa0000;">important</li>
<li style="color:#0a0; border-color:#00aa00;">action-needed</li> <li style="background-color:#00aa00;">action-needed</li>
<li style="color:#00a; border-color:#00a;">action-needed</li> <li style="background-color:#00a;">action-needed</li>
<li style="color:#ac8ac8; border-color:#ac8ac8;">action-needed</li>
</ul> </ul>
<div id="assigned-users"> <div id="assigned-users">
<div class="avatardiv" style="height: 30px; width: 30px; color: rgb(255, 255, 255); font-weight: normal; text-align: center; line-height: 30px; font-size: 17px; background-color: rgb(213, 231, 116);">D</div> <div class="avatardiv" style="height: 30px; width: 30px; color: rgb(255, 255, 255); font-weight: normal; text-align: center; line-height: 30px; font-size: 17px; background-color: rgb(213, 231, 116);">D</div>
@@ -18,13 +33,12 @@
</div> </div>
<div id="card-description"> <div id="card-description">
<textarea> <textarea ng-model="cardservice.getCurrent().description">{{ cardservice.getCurrent().description }}</textarea>
{{ card.description }} <div class="saved">Saved</div>
</textarea>
</div> </div>
</div> </div>
<!--
<div id="card-attachments"> <div id="card-attachments">
<h3>Attachments</h3> <h3>Attachments</h3>
<ul> <ul>
@@ -59,4 +73,4 @@
Quia quia qui aspernatur cumque quo omnis corporis. Reprehenderit id sint architecto magni in. Et harum sequi eaque quasi qui sed id quod. Quia quia qui aspernatur cumque quo omnis corporis. Reprehenderit id sint architecto magni in. Et harum sequi eaque quasi qui sed id quod.
Officia quaerat facere et totam officiis dolores velit qui. Earum velit sint quia. Id libero quibusdam voluptatem. Officia quaerat facere et totam officiis dolores velit qui. Earum velit sint quia. Id libero quibusdam voluptatem.
</p> </p>
</div> </div>// -->