From ef4ce31c47a5ef70d1a4d00f2d4cd182ac067f2c Mon Sep 17 00:00:00 2001 From: Manuel Arno Korfmann Date: Mon, 16 Jul 2018 00:09:43 +0200 Subject: [PATCH] refactoring and stack undo delete early wip Signed-off-by: Manuel Arno Korfmann stack soft delete done Signed-off-by: Manuel Arno Korfmann stack undo delete done Signed-off-by: Manuel Arno Korfmann stack undo: code review remarks and fixes Signed-off-by: Manuel Arno Korfmann --- appinfo/database.xml | 8 ++++ appinfo/routes.php | 1 + css/style.scss | 8 +++- js/controller/BoardController.js | 69 ++++++++++++++++------------ js/service/ApiService.js | 40 ++++++++++------ js/service/CardService.js | 28 ++--------- js/service/StackService.js | 30 ++---------- lib/Controller/StackController.php | 15 +++++- lib/Db/Stack.php | 4 +- lib/Db/StackMapper.php | 13 ++++-- lib/Service/StackService.php | 20 +++++++- templates/part.board.mainView.php | 2 +- templates/part.board.sidebarView.php | 44 ++++++++++++------ 13 files changed, 164 insertions(+), 118 deletions(-) diff --git a/appinfo/database.xml b/appinfo/database.xml index e9b7586ab..91c9076ba 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -77,6 +77,14 @@ 8 false + + deleted_at + integer + 0 + 8 + false + true + deck_stacks_board_id_index diff --git a/appinfo/routes.php b/appinfo/routes.php index 47a550f5b..de62cb0cc 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -43,6 +43,7 @@ return [ ['name' => 'stack#update', 'url' => '/stacks/{stackId}', 'verb' => 'PUT'], ['name' => 'stack#reorder', 'url' => '/stacks/{stackId}/reorder', 'verb' => 'PUT'], ['name' => 'stack#delete', 'url' => '/stacks/{stackId}', 'verb' => 'DELETE'], + ['name' => 'stack#deleted', 'url' => '/stacks/deleted/{boardId}', 'verb' => 'GET'], ['name' => 'stack#archived', 'url' => '/stacks/{boardId}/archived', 'verb' => 'GET'], // cards diff --git a/css/style.scss b/css/style.scss index a94b60938..efd70ef3e 100644 --- a/css/style.scss +++ b/css/style.scss @@ -1215,12 +1215,18 @@ input.input-inline { .tabHeaders { clear: both; - overflow: hidden; + overflow: initial; margin-bottom: 0; } .tabsContainer { margin-top: 15px; + height: 100%; + + .tab { + height: 100%; + overflow: scroll; + } } .ui-select-offscreen { diff --git a/js/controller/BoardController.js b/js/controller/BoardController.js index 9b5e2bc59..048190d47 100644 --- a/js/controller/BoardController.js +++ b/js/controller/BoardController.js @@ -4,20 +4,20 @@ * @author Julius Härtl * * @license GNU AGPL version 3 or any later version - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . - * + * */ import app from '../app/App.js'; @@ -42,7 +42,17 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St $scope.board = BoardService.getCurrent(); $scope.uploader = FileService.uploader; - $scope.deletedCards = []; + $scope.deletedCards = {}; + $scope.deletedStacks = {}; + + $scope.$watch(function() { + return $state.current; + }, function(currentState) { + if(currentState.name === 'board.detail') { + $scope.loadDeletedEntity(CardService, 'deletedCards'); + $scope.loadDeletedEntity(StackService, 'deletedStacks'); + } + }); // workaround for $stateParams changes not being propagated $scope.$watch(function() { @@ -138,9 +148,11 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St }); }; - $scope.loadDeletedCards = function() { - CardService.fetchDeleted($scope.id).then(function (data) { - $scope.deletedCards = data; + $scope.loadDeletedEntity = function(service, scopeKey) { + service.fetchDeleted($scope.id).then(function (data) { + for(i=0;i * * @license GNU AGPL version 3 or any later version - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . - * + * */ import app from '../app/App.js'; @@ -48,6 +48,19 @@ app.factory('ApiService', function ($http, $q) { return deferred.promise; }; + ApiService.prototype.fetchDeleted = function (scopeId) { + var deferred = $q.defer(); + var self = this; + $http.get(this.baseUrl + '/deleted/' + scopeId).then(function (response) { + var objects = response.data; + deferred.resolve(objects); + }, function (error) { + deferred.reject('Fetching ' + self.endpoint + ' failed'); + }); + return deferred.promise; + }; + + ApiService.prototype.fetchOne = function (id) { this.id = id; @@ -111,20 +124,19 @@ app.factory('ApiService', function ($http, $q) { deferred.reject('Deleting ' + self.endpoint + ' failed'); }); return deferred.promise; - }; - ApiService.prototype.softDelete = function (id) { - var deferred = $q.defer(); + ApiService.prototype.undoDelete = function(entity) { var self = this; - - $http.delete(this.baseUrl + '/' + id).then(function (response) { - self.data[id].deletedAt = response.data.deletedAt; - deferred.resolve(response.data); - }, function (error) { - deferred.reject('Deleting ' + self.endpoint + ' failed'); + entity.deletedAt = 0; + + var promise = this.update(entity); + + promise.then(function() { + self.data[entity.id] = entity; }); - return deferred.promise; + + return promise; }; // methods for managing data diff --git a/js/service/CardService.js b/js/service/CardService.js index 840d21156..eb62785f0 100644 --- a/js/service/CardService.js +++ b/js/service/CardService.js @@ -4,20 +4,20 @@ * @author Julius Härtl * * @license GNU AGPL version 3 or any later version - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . - * + * */ import app from '../app/App.js'; @@ -27,13 +27,6 @@ app.factory('CardService', function (ApiService, $http, $q) { }; CardService.prototype = angular.copy(ApiService.prototype); - CardService.prototype.delete = CardService.prototype.softDelete; - - CardService.prototype.undoDelete = function(card) { - card.deletedAt = 0; - this.update(card); - }; - CardService.prototype.reorder = function (card, order) { var deferred = $q.defer(); var self = this; @@ -179,19 +172,6 @@ app.factory('CardService', function (ApiService, $http, $q) { return deferred.promise; }; - CardService.prototype.fetchDeleted = function (boardId) { - var deferred = $q.defer(); - var self = this; - $http.get(this.baseUrl + '/deleted/' + boardId).then(function (response) { - var objects = response.data; - deferred.resolve(objects); - }, function (error) { - deferred.reject('Fetching ' + self.endpoint + ' failed'); - }); - return deferred.promise; - }; - - var service = new CardService($http, 'cards', $q); return service; }); diff --git a/js/service/StackService.js b/js/service/StackService.js index a4671a331..a5ee1d0a9 100644 --- a/js/service/StackService.js +++ b/js/service/StackService.js @@ -4,20 +4,20 @@ * @author Julius Härtl * * @license GNU AGPL version 3 or any later version - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . - * + * */ import app from '../app/App.js'; @@ -27,6 +27,7 @@ app.factory('StackService', function (ApiService, CardService, $http, $q) { ApiService.call(this, $http, ep, $q); }; StackService.prototype = angular.copy(ApiService.prototype); + StackService.prototype.fetchAll = function (boardId) { var deferred = $q.defer(); var self = this; @@ -129,27 +130,6 @@ app.factory('StackService', function (ApiService, CardService, $http, $q) { } }; - // FIXME: Should not show popup but proper undo mechanism - StackService.prototype.delete = function (id) { - var deferred = $q.defer(); - var self = this; - - OC.dialogs.confirm(t('deck', 'Are you sure you want to delete the stack with all of its data?'), t('deck', 'Delete'), function(state) { - if (!state) { - return; - } - $http.delete(self.baseUrl + '/' + id).then(function (response) { - self.remove(id); - deferred.resolve(response.data); - - }, function (error) { - deferred.reject('Deleting ' + self.endpoint + ' failed'); - }); - }); - return deferred.promise; - }; - var service = new StackService($http, 'stacks', $q); return service; }); - diff --git a/lib/Controller/StackController.php b/lib/Controller/StackController.php index 3e2e358ed..3063d858f 100644 --- a/lib/Controller/StackController.php +++ b/lib/Controller/StackController.php @@ -74,10 +74,11 @@ class StackController extends Controller { * @param $title * @param $boardId * @param $order + * @param $deletedAt * @return \OCP\AppFramework\Db\Entity */ - public function update($id, $title, $boardId, $order) { - return $this->stackService->update($id, $title, $boardId, $order); + public function update($id, $title, $boardId, $order, $deletedAt) { + return $this->stackService->update($id, $title, $boardId, $order, $deletedAt); } /** @@ -98,4 +99,14 @@ class StackController extends Controller { public function delete($stackId) { return $this->stackService->delete($stackId); } + + /** + * @NoAdminRequired + * @param $boardId + * @return \OCP\AppFramework\Db\Entity + */ + public function deleted($boardId) { + return $this->stackService->fetchDeleted($boardId); + } + } diff --git a/lib/Db/Stack.php b/lib/Db/Stack.php index 8a3d00ffb..b90604126 100644 --- a/lib/Db/Stack.php +++ b/lib/Db/Stack.php @@ -27,12 +27,14 @@ class Stack extends RelationalEntity { protected $title; protected $boardId; + protected $deletedAt; protected $cards = array(); protected $order; public function __construct() { $this->addType('id', 'integer'); $this->addType('boardId', 'integer'); + $this->addType('deletedAt', 'integer'); $this->addType('order', 'integer'); } @@ -47,4 +49,4 @@ class Stack extends RelationalEntity { } return $json; } -} \ No newline at end of file +} diff --git a/lib/Db/StackMapper.php b/lib/Db/StackMapper.php index 51d8bf29c..c98d87cc0 100644 --- a/lib/Db/StackMapper.php +++ b/lib/Db/StackMapper.php @@ -51,10 +51,17 @@ class StackMapper extends DeckMapper implements IPermissionMapper { public function findAll($boardId, $limit = null, $offset = null) { - $sql = 'SELECT * FROM `*PREFIX*deck_stacks` WHERE `board_id` = ? ORDER BY `order`'; + $sql = 'SELECT * FROM `*PREFIX*deck_stacks` WHERE `board_id` = ? AND deleted_at = 0 ORDER BY `order`'; return $this->findEntities($sql, [$boardId], $limit, $offset); } - + + + public function findDeleted($boardId, $limit = null, $offset = null) { + $sql = 'SELECT * FROM `*PREFIX*deck_stacks` s + WHERE `s`.`board_id` = ? AND NOT s.deleted_at = 0'; + return $this->findEntities($sql, [$boardId], $limit, $offset); + } + public function delete(Entity $entity) { // delete cards on stack @@ -73,4 +80,4 @@ class StackMapper extends DeckMapper implements IPermissionMapper { $entity = $this->find($stackId); return $entity->getBoardId(); } -} \ No newline at end of file +} diff --git a/lib/Service/StackService.php b/lib/Service/StackService.php index 581543623..a4ca46eec 100644 --- a/lib/Service/StackService.php +++ b/lib/Service/StackService.php @@ -25,6 +25,7 @@ namespace OCA\Deck\Service; use OCA\Deck\Db\Acl; use OCA\Deck\Db\CardMapper; +use OCA\Deck\Db\BoardMapper; use OCA\Deck\Db\LabelMapper; use OCA\Deck\Db\AssignedUsersMapper; use OCA\Deck\Db\Stack; @@ -38,6 +39,7 @@ class StackService { private $stackMapper; private $cardMapper; + private $boardMapper; private $labelMapper; private $permissionService; private $boardService; @@ -46,6 +48,7 @@ class StackService { public function __construct( StackMapper $stackMapper, + BoardMapper $boardMapper, CardMapper $cardMapper, LabelMapper $labelMapper, PermissionService $permissionService, @@ -54,6 +57,7 @@ class StackService { AttachmentService $attachmentService ) { $this->stackMapper = $stackMapper; + $this->boardMapper = $boardMapper; $this->cardMapper = $cardMapper; $this->labelMapper = $labelMapper; $this->permissionService = $permissionService; @@ -81,6 +85,11 @@ class StackService { return $stacks; } + public function fetchDeleted($boardId) { + $this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ); + return $this->stackMapper->findDeleted($boardId); + } + public function findAllArchived($boardId) { $this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_READ); $stacks = $this->stackMapper->findAll($boardId); @@ -115,10 +124,16 @@ class StackService { public function delete($id) { $this->permissionService->checkPermission($this->stackMapper, $id, Acl::PERMISSION_MANAGE); - return $this->stackMapper->delete($this->stackMapper->find($id)); + + $stack = $this->stackMapper->find($id); + $stack->setDeletedAt(time()); + $this->stackMapper->update($stack); + + return $stack; } - public function update($id, $title, $boardId, $order) { + + public function update($id, $title, $boardId, $order, $deletedAt) { $this->permissionService->checkPermission($this->stackMapper, $id, Acl::PERMISSION_MANAGE); if ($this->boardService->isArchived($this->stackMapper, $id)) { throw new StatusException('Operation not allowed. This board is archived.'); @@ -127,6 +142,7 @@ class StackService { $stack->setTitle($title); $stack->setBoardId($boardId); $stack->setOrder($order); + $stack->setDeletedAt($deletedAt); return $this->stackMapper->update($stack); } diff --git a/templates/part.board.mainView.php b/templates/part.board.mainView.php index bc5f483c2..26447c9c2 100644 --- a/templates/part.board.mainView.php +++ b/templates/part.board.mainView.php @@ -52,7 +52,7 @@ + ng-click="stackDelete(s)">