From 5f424aa7c172480d3e8688fab32428838b6eaf8e Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Thu, 2 May 2019 10:38:56 +0200 Subject: [PATCH] tree sorting: fix sorting multiple items --- .../motions/motion-repository.service.ts | 13 -- .../src/app/core/ui-services/tree.service.ts | 124 +++++++++++++++--- .../choice-dialog.component.html | 15 ++- .../choice-dialog/choice-dialog.component.ts | 14 ++ .../services/motion-multiselect.service.ts | 45 ++++--- 5 files changed, 162 insertions(+), 49 deletions(-) diff --git a/client/src/app/core/repositories/motions/motion-repository.service.ts b/client/src/app/core/repositories/motions/motion-repository.service.ts index 2761ed191..682a3029e 100644 --- a/client/src/app/core/repositories/motions/motion-repository.service.ts +++ b/client/src/app/core/repositories/motions/motion-repository.service.ts @@ -438,19 +438,6 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo await this.httpService.post('/rest/motions/motion/sort/', data); } - /** - * Sends the changed nodes to the server, with only the top nodes being submitted. - * - * @param data The reordered data from the sorting, as list of ViewMotions - * @param parent a parent id - */ - public async sortMotionBranches(data: ViewMotion[], parent?: number): Promise { - const url = '/rest/motions/motion/sort/'; - const nodes = data.map(motion => ({ id: motion.id })); - const params = parent ? { nodes: nodes, parent_id: parent } : { nodes: nodes }; - await this.httpService.post(url, params); - } - /** * Supports the motion * diff --git a/client/src/app/core/ui-services/tree.service.ts b/client/src/app/core/ui-services/tree.service.ts index f51c1e723..ea65f2db1 100644 --- a/client/src/app/core/ui-services/tree.service.ts +++ b/client/src/app/core/ui-services/tree.service.ts @@ -218,43 +218,137 @@ export class TreeService { } /** - * Reduce a list of items to nodes independent from each other in a given tree + * Searches a tree for a list of given items and fetches all branches that include + * these items and their dependants * - * @param tree the tree to traverse - * @param items the items to check - * @returns the selection of items that belong to different branches + * @param tree an array of OsTreeNode branches + * @param items the items that need to be included + * + * @returns an array of OsTreeNodes with the top-most item being included + * in the input list */ - public getTopItemsFromTree(tree: OSTreeNode[], items: T[]): T[] { - let results: T[] = []; + public getBranchesFromTree( + tree: OSTreeNode[], + items: T[] + ): OSTreeNode[] { + let results: OSTreeNode[] = []; tree.forEach(branch => { - const i = this.getTopItemsFromBranch(branch, items); - if (i.length) { - results = results.concat(i); + if (items.some(item => item.id === branch.item.id)) { + results.push(branch); + } else if (branch.children && branch.children.length) { + results = results.concat(this.getBranchesFromTree(branch.children, items)); } }); return results; } /** - * Return all items not being hierarchically dependant on the items in the input arrray + * Inserts OSTreeNode branches into another tree at the position specified + * + * @param tree A (partial) tree the branches need to be inserted into. It + * is assumed that this tree does not contain the branches to be inserted. + * See also {@link getTreeWithoutSelection} + * @param branches OsTreeNodes to be inserted. See also {@link getBranchesFromTree} + * @param parentId the id of a parent node under which the branches should be inserted + * @param olderSibling (optional) the id of the item on the same level + * the tree is to be inserted behind + * @returns the re-arranged tree containing the branches + */ + public insertBranchesIntoTree( + tree: OSTreeNode[], + branches: OSTreeNode[], + parentId: number, + olderSibling?: number + ): OSTreeNode[] { + if (!parentId && olderSibling) { + const older = tree.findIndex(branch => branch.id === olderSibling); + if (older >= 0) { + return [...tree.slice(0, older + 1), ...branches, ...tree.slice(older + 1)]; + } else { + for (const branch of tree) { + if (branch.children && branch.children.length) { + branch.children = this.insertBranchesIntoTree(branch.children, branches, null, olderSibling); + } + } + return tree; + } + } else if (parentId) { + for (const branch of tree) { + if (branch.id !== parentId) { + if (branch.children && branch.children.length) { + branch.children = this.insertBranchesIntoTree( + branch.children, + branches, + parentId, + olderSibling + ); + } + } else { + if (!branch.children) { + branch.children = branches; + } else { + if (olderSibling) { + const older = branch.children.findIndex(child => child.id === olderSibling); + if (older >= 0) { + branch.children = [ + ...branch.children.slice(0, older + 1), + ...branches, + ...branch.children.slice(older + 1) + ]; + } + } else { + branch.children = [...branch.children, ...branches]; + } + } + } + } + return tree; + } else { + throw new Error('This should not happen. Invalid sorting items given'); + } + } + + /** + * Return the part of a tree not including or being hierarchically dependant + * on the items in the input arrray * * @param tree * @param items - * @returns all items that are neither in the input nor dependants of items in the input + * @returns all the branch without the given items or their dependants */ - public getTreeWithoutSelection(tree: OSTreeNode[], items: T[]): T[] { - let result: T[] = []; + public getTreeWithoutSelection( + tree: OSTreeNode[], + items: T[] + ): OSTreeNode[] { + const result: OSTreeNode[] = []; tree.forEach(branch => { if (!items.find(i => i.id === branch.item.id)) { - result.push(branch.item); if (branch.children) { - result = result.concat(this.getTreeWithoutSelection(branch.children, items)); + branch.children = this.getTreeWithoutSelection(branch.children, items); } + result.push(branch); } }); return result; } + /** + * Helper to turn a tree into an array of items + * + * @param tree + * @returns the items contained in the tree. + */ + public getFlatItemsFromTree(tree: OSTreeNode[]): T[] { + let result = []; + for (const branch of tree) { + result.push(branch.item); + if (branch.children && branch.children.length) { + result = result.concat(this.getFlatItemsFromTree(branch.children)); + } + } + return result; + } + /** * Helper function to go recursively through the children of given node. * diff --git a/client/src/app/shared/components/choice-dialog/choice-dialog.component.html b/client/src/app/shared/components/choice-dialog/choice-dialog.component.html index ccc28776b..1593cd1a5 100644 --- a/client/src/app/shared/components/choice-dialog/choice-dialog.component.html +++ b/client/src/app/shared/components/choice-dialog/choice-dialog.component.html @@ -17,7 +17,6 @@ {{ data.clearChoice | translate }} - @@ -30,12 +29,22 @@
-
-
diff --git a/client/src/app/shared/components/choice-dialog/choice-dialog.component.ts b/client/src/app/shared/components/choice-dialog/choice-dialog.component.ts index 54b37461c..08dd53def 100644 --- a/client/src/app/shared/components/choice-dialog/choice-dialog.component.ts +++ b/client/src/app/shared/components/choice-dialog/choice-dialog.component.ts @@ -69,6 +69,20 @@ export class ChoiceDialogComponent { */ public selectedChoice: number; + /** + * Checks if there is nothing selected + * + * @returns true if there is no selection chosen (and the dialog should not + * be closed 'successfully') + */ + public get isSelectionEmpty(): boolean { + if (this.data.multiSelect) { + return this.selectedMultiChoices.length === 0; + } else { + return this.selectedChoice === undefined; + } + } + /** * All selected ids, if this is a multiselect choice */ diff --git a/client/src/app/site/motions/services/motion-multiselect.service.ts b/client/src/app/site/motions/services/motion-multiselect.service.ts index 68343896f..3987da382 100644 --- a/client/src/app/site/motions/services/motion-multiselect.service.ts +++ b/client/src/app/site/motions/services/motion-multiselect.service.ts @@ -331,24 +331,33 @@ export class MotionMultiselectService { const options = [this.translate.instant('Set as parent'), this.translate.instant('Insert after')]; const allMotions = this.repo.getViewModelList(); const tree = this.treeService.makeTree(allMotions, 'weight', 'sort_parent_id'); - const itemsToMove = this.treeService.getTopItemsFromTree(tree, motions); - const selectableItems = this.treeService.getTreeWithoutSelection(tree, motions); - - const selectedChoice = await this.choiceService.open(title, selectableItems, false, options); - if (selectedChoice) { - if (selectedChoice.action === options[0]) { - // set choice as parent - this.repo.sortMotionBranches(itemsToMove, selectedChoice.items as number); - } else if (selectedChoice.action === options[1]) { - // insert after chosen - const olderSibling = this.repo.getViewModel(selectedChoice.items as number); - const parentId = olderSibling ? olderSibling.sort_parent_id : null; - const siblings = allMotions.filter(motion => motion.sort_parent_id === parentId); - const idx = siblings.findIndex(sib => sib.id === olderSibling.id); - const before = siblings.slice(0, idx + 1); - const after = siblings.slice(idx + 1); - const sum = [].concat(before, itemsToMove, after); - this.repo.sortMotionBranches(sum, parentId); + const itemsToMove = this.treeService.getBranchesFromTree(tree, motions); + const partialTree = this.treeService.getTreeWithoutSelection(tree, motions); + const availableMotions = this.treeService.getFlatItemsFromTree(partialTree); + if (!availableMotions.length) { + throw new Error(this.translate.instant('There are no items left to chose from')); + } else { + const selectedChoice = await this.choiceService.open(title, availableMotions, false, options); + if (selectedChoice) { + if (!selectedChoice.items) { + throw this.translate.instant('No items selected'); + } + if (selectedChoice.action === options[0]) { + const sortedChildTree = this.treeService.insertBranchesIntoTree( + partialTree, + itemsToMove, + selectedChoice.items as number + ); + this.repo.sortMotions(this.treeService.stripTree(sortedChildTree)); + } else if (selectedChoice.action === options[1]) { + const sortedSiblingTree = this.treeService.insertBranchesIntoTree( + partialTree, + itemsToMove, + this.repo.getViewModel(selectedChoice.items as number).parent_id, + selectedChoice.items as number + ); + this.repo.sortMotions(this.treeService.stripTree(sortedSiblingTree)); + } } } }