refactoring and stack undo delete early wip

Signed-off-by: Manuel Arno Korfmann <manu@korfmann.info>

stack soft delete done

Signed-off-by: Manuel Arno Korfmann <manu@korfmann.info>

stack undo delete done

Signed-off-by: Manuel Arno Korfmann <manu@korfmann.info>

stack undo: code review remarks and fixes

Signed-off-by: Manuel Arno Korfmann <manu@korfmann.info>
This commit is contained in:
Manuel Arno Korfmann 2018-07-16 00:09:43 +02:00 коммит произвёл Julius Härtl
Родитель f2795f120b
Коммит ef4ce31c47
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4C614C6ED2CDE6DF
13 изменённых файлов: 164 добавлений и 118 удалений

Просмотреть файл

@ -77,6 +77,14 @@
<length>8</length>
<notnull>false</notnull>
</field>
<field>
<name>deleted_at</name>
<type>integer</type>
<default>0</default>
<length>8</length>
<notnull>false</notnull>
<unsigned>true</unsigned>
</field>
<index>
<name>deck_stacks_board_id_index</name>
<field>

Просмотреть файл

@ -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

Просмотреть файл

@ -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 {

Просмотреть файл

@ -4,20 +4,20 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*
*/
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<data.length;i++) {
$scope[scopeKey][data[i].id] = data[i];
}
}, function (error) {
$scope.statusservice.setError('Error occured', error);
});
@ -196,30 +208,30 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
});
};
$scope.stackDelete = function (stack) {
$scope.stackservice.delete(stack.id).then(function() {
$scope.deletedStacks[stack.id] = stack;
});
}
$scope.stackUndoDelete = function (deletedStack) {
StackService.undoDelete(deletedStack).then(function() {
delete $scope.deletedStacks[deletedStack.id];
});
}
$scope.cardDelete = function (card) {
OC.dialogs.confirm(t('deck', 'Are you sure you want to delete this card with all of its data?'), t('deck', 'Delete'), function(state) {
if (!state) {
return;
}
CardService.delete(card.id).then(function () {
StackService.removeCard(card);
$scope.deletedCards.push(card);
});
CardService.delete(card.id).then(function () {
StackService.removeCard(card);
$scope.deletedCards[card.id] = card;
});
};
$scope.cardUndoDelete = function (deletedCard) {
CardService.undoDelete(deletedCard);
StackService.addCard(deletedCard);
$scope.removeFromDeletedCards(deletedCard);
};
$scope.removeFromDeletedCards = function(deletedCard) {
for(var i=0;i<$scope.deletedCards.length;i++) {
if($scope.deletedCards[i].id === deletedCard.id) {
$scope.deletedCards.splice(i, 1);
}
}
CardService.undoDelete(deletedCard).then(function() {
StackService.addCard(deletedCard);
delete $scope.deletedCards[deletedCard.id];
});
};
$scope.cardArchive = function (card) {
@ -262,7 +274,6 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
// TODO: remove from cards
};
$scope.labelCreate = function (label) {
alert(label);
label.boardId = $scope.id;
LabelService.create(label).then(function (data) {
$scope.newStack.title = '';
@ -410,6 +421,4 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
}
return card.attachmentCount;
};
$scope.loadDeletedCards();
});

Просмотреть файл

@ -4,20 +4,20 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*
*/
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

Просмотреть файл

@ -4,20 +4,20 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*
*/
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;
});

Просмотреть файл

@ -4,20 +4,20 @@
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*
*/
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;
});

Просмотреть файл

@ -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);
}
}

Просмотреть файл

@ -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;
}
}
}

Просмотреть файл

@ -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();
}
}
}

Просмотреть файл

@ -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);
}

Просмотреть файл

@ -52,7 +52,7 @@
</form>
<button class="icon-delete button-inline stack-actions"
ng-if="!s.status.editStack"
ng-click="stackservice.delete(s.id)"></button>
ng-click="stackDelete(s)"></button>
</h3>
<ul data-as-sortable="sortOptions" is-disabled="!boardservice.canEdit() || filter==='archive'" data-ng-model="s.cards" class="card-list" ng-class="{emptyStack: !s.cards.length}">
<li class="card as-sortable-item"

Просмотреть файл

@ -5,8 +5,8 @@
<p>{{ statusservice.text }}</p></div>
</div>
<div id="sidebar-header">
<a class="icon-close" ui-sref="board" ng-click="sidebar.show=!sidebar.show" title="<?php p($l->t('Close')); ?>"> &nbsp;<?php
?><span class="hidden-visually"><?php p($l->t('Close')); ?></span><?php
<a class="icon-close" ui-sref="board" ng-click="sidebar.show=!sidebar.show" title="<?php p($l->t('Close')); ?>"> &nbsp;<?php
?><span class="hidden-visually"><?php p($l->t('Close')); ?></span><?php
?></a>
<h3>{{ boardservice.getCurrent().title }}</h3>
</div>
@ -14,7 +14,8 @@
<ul class="tabHeaders">
<li class="tabHeader" ng-class="{'selected': (params.tab==0 || !params.tab)}" ui-sref="{tab: 0}"><a><?php p($l->t('Sharing')); ?></a></li>
<li class="tabHeader" ng-class="{'selected': (params.tab==1)}" ui-sref="{tab: 1}"><a><?php p($l->t('Tags')); ?></a></li>
<li class="tabHeader" ng-class="{'selected': (params.tab==2)}" ui-sref="{tab: 2}"><a><?php p($l->t('Deleted Cards')); ?></a></li>
<li class="tabHeader" ng-class="{'selected': (params.tab==2)}" ui-sref="{tab: 2}"><a><?php p($l->t('Deleted Stacks')); ?></a></li>
<li class="tabHeader" ng-class="{'selected': (params.tab==3)}" ui-sref="{tab: 3}"><a><?php p($l->t('Deleted Cards')); ?></a></li>
</ul>
<div class="tabsContainer">
<div id="tabBoardShare" class="tab" ng-if="params.tab==0 || !params.tab">
@ -120,17 +121,30 @@
</div>
<div id="board-detail-deleted-cards" class="tab deletedCardsTabView" ng-if="params.tab==2">
<ul>
<li ng-repeat="deletedCard in deletedCards">
<dl>
<dt>Title</dt>
<dd>{{deletedCard.title}}<dd>
<dt>Stack</dt>
<dd>{{stackservice.data[deletedCard.stackId].title}}</dd>
</dl>
<a ng-click="cardUndoDelete(deletedCard)"><span class="icon icon-undo"></span><br /><span><?php p($l->t('Undo delete')); ?></span></a>
</li>
</ul>
<div id="board-detail-deleted-stacks" class="tab deletedStacksTabView" ng-if="params.tab==2">
<ul>
<li ng-repeat="deletedStack in deletedStacks">
<dl>
<dt>Title</dt>
<dd>{{deletedStack.title}}<dd>
</dl>
<a ng-click="stackUndoDelete(deletedStack)"><span class="icon icon-undo"></span><br /><span><?php p($l->t('Undo delete')); ?></span></a>
</li>
</ul>
</div>
<div id="board-detail-deleted-cards" class="tab deletedCardsTabView" ng-if="params.tab==3">
<ul>
<li ng-repeat="deletedCard in deletedCards">
<dl>
<dt>Title</dt>
<dd>{{deletedCard.title}}<dd>
<dt>Stack</dt>
<dd>{{stackservice.data[deletedCard.stackId].title}}</dd>
</dl>
<a ng-click="cardUndoDelete(deletedCard)"><span class="icon icon-undo"></span><br /><span><?php p($l->t('Undo delete')); ?></span></a>
</li>
</ul>
</div>
</div>