Commit 871442ff authored by Jochen Roth's avatar Jochen Roth Committed by Benni Mack
Browse files

[BUGFIX] Optimize drag&drop of folders in the UI

Allow a user to move a folder into a folder on the same
level and prevent moving a folder into its child folder.

Resolves: #93954
Releases: master
Change-Id: I444a87e862075ecd0a3e54ce5afd02314b1931c0
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/68833

Tested-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Oliver Bartsch's avatarOliver Bartsch <bo@cedev.de>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent dd88dd49
......@@ -358,8 +358,9 @@ class FileStorageTreeNodeDragHandler implements DragDropHandler {
}
public isInSameParentNode(activeHoveredNode: TreeNode, targetNode: TreeNode): boolean {
return activeHoveredNode.parentsStateIdentifier[0] == targetNode.parentsStateIdentifier[0]
|| activeHoveredNode.parentsStateIdentifier[0] == targetNode.stateIdentifier;
return activeHoveredNode.stateIdentifier == targetNode.stateIdentifier
|| activeHoveredNode.parentsStateIdentifier[0] == targetNode.stateIdentifier
|| targetNode.parentsStateIdentifier.includes(activeHoveredNode.stateIdentifier);
}
public dragEnd(event: D3DragEvent<any, any, any>): boolean {
......
......@@ -10,7 +10,7 @@
*
* The TYPO3 project - inspiring people to share!
*/
var __decorate=this&&this.__decorate||function(e,t,r,i){var n,s=arguments.length,o=s<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,r,o):n(t,r))||o);return s>3&&o&&Object.defineProperty(t,r,o),o},__importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","lit-element","./FileStorageTree","TYPO3/CMS/Backend/Storage/Persistent","../ContextMenu","./DragDrop","../Modal","../Severity","../Notification","TYPO3/CMS/Core/Ajax/AjaxRequest","TYPO3/CMS/Backend/Element/IconElement"],(function(e,t,r,i,n,s,o,a,d,l,c){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.FileStorageTreeNavigationComponent=t.navigationComponentName=void 0,n=__importDefault(n),c=__importDefault(c),t.navigationComponentName="typo3-backend-navigation-component-filestoragetree";let g=class extends i.FileStorageTree{constructor(){super(),this.actionHandler=new p(this)}updateNodeBgClass(e){return super.updateNodeBgClass.call(this,e).call(this.initializeDragForNode())}nodesUpdate(e){return super.nodesUpdate.call(this,e).call(this.initializeDragForNode())}initializeDragForNode(){return this.actionHandler.connectDragHandler(new f(this,this.actionHandler))}};g=__decorate([r.customElement("typo3-backend-navigation-component-filestorage-tree")],g);let h=class extends r.LitElement{constructor(){super(...arguments),this.refresh=()=>{this.tree.refreshOrFilterTree()},this.selectFirstNode=()=>{const e=this.tree.nodes[0];e&&this.tree.selectNode(e)},this.treeUpdateRequested=e=>{const t=encodeURIComponent(e.detail.payload.identifier);let r=this.tree.nodes.filter(e=>e.identifier===t)[0];r&&0===this.tree.getSelectedNodes().filter(e=>e.identifier===r.identifier).length&&this.tree.selectNode(r)},this.toggleExpandState=e=>{const t=e.detail.node;t&&n.default.set("BackendComponents.States.FileStorageTree.stateHash."+t.stateIdentifier,t.expanded?"1":"0")},this.loadContent=e=>{const t=e.detail.node;if(!(null==t?void 0:t.checked))return;window.fsMod.recentIds.file=t.identifier,window.fsMod.navFrameHighlightedID.file=t.stateIdentifier;const r=-1!==window.currentSubScript.indexOf("?")?"&":"?";TYPO3.Backend.ContentContainer.setUrl(window.currentSubScript+r+"id="+t.identifier)},this.showContextMenu=e=>{const t=e.detail.node;t&&s.show(t.itemType,decodeURIComponent(t.identifier),"tree","","",this.tree.getNodeElement(t))},this.selectActiveNode=e=>{const t=window.fsMod.navFrameHighlightedID.file;let r=e.detail.nodes;e.detail.nodes=r.map(e=>(e.stateIdentifier===t&&(e.checked=!0),e))}}connectedCallback(){super.connectedCallback(),document.addEventListener("typo3:filestoragetree:refresh",this.refresh),document.addEventListener("typo3:filestoragetree:selectFirstNode",this.selectFirstNode),document.addEventListener("typo3:filelist:treeUpdateRequested",this.treeUpdateRequested)}disconnectedCallback(){document.removeEventListener("typo3:filestoragetree:refresh",this.refresh),document.removeEventListener("typo3:filestoragetree:selectFirstNode",this.selectFirstNode),document.removeEventListener("typo3:filelist:treeUpdateRequested",this.treeUpdateRequested),super.disconnectedCallback()}createRenderRoot(){return this}render(){const e={dataUrl:top.TYPO3.settings.ajaxUrls.filestorage_tree_data,filterUrl:top.TYPO3.settings.ajaxUrls.filestorage_tree_filter,showIcons:!0};return r.html`
var __decorate=this&&this.__decorate||function(e,t,i,r){var n,s=arguments.length,o=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,i):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,r);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(o=(s<3?n(o):s>3?n(t,i,o):n(t,i))||o);return s>3&&o&&Object.defineProperty(t,i,o),o},__importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};define(["require","exports","lit-element","./FileStorageTree","TYPO3/CMS/Backend/Storage/Persistent","../ContextMenu","./DragDrop","../Modal","../Severity","../Notification","TYPO3/CMS/Core/Ajax/AjaxRequest","TYPO3/CMS/Backend/Element/IconElement"],(function(e,t,i,r,n,s,o,a,d,l,c){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.FileStorageTreeNavigationComponent=t.navigationComponentName=void 0,n=__importDefault(n),c=__importDefault(c),t.navigationComponentName="typo3-backend-navigation-component-filestoragetree";let g=class extends r.FileStorageTree{constructor(){super(),this.actionHandler=new p(this)}updateNodeBgClass(e){return super.updateNodeBgClass.call(this,e).call(this.initializeDragForNode())}nodesUpdate(e){return super.nodesUpdate.call(this,e).call(this.initializeDragForNode())}initializeDragForNode(){return this.actionHandler.connectDragHandler(new f(this,this.actionHandler))}};g=__decorate([i.customElement("typo3-backend-navigation-component-filestorage-tree")],g);let h=class extends i.LitElement{constructor(){super(...arguments),this.refresh=()=>{this.tree.refreshOrFilterTree()},this.selectFirstNode=()=>{const e=this.tree.nodes[0];e&&this.tree.selectNode(e)},this.treeUpdateRequested=e=>{const t=encodeURIComponent(e.detail.payload.identifier);let i=this.tree.nodes.filter(e=>e.identifier===t)[0];i&&0===this.tree.getSelectedNodes().filter(e=>e.identifier===i.identifier).length&&this.tree.selectNode(i)},this.toggleExpandState=e=>{const t=e.detail.node;t&&n.default.set("BackendComponents.States.FileStorageTree.stateHash."+t.stateIdentifier,t.expanded?"1":"0")},this.loadContent=e=>{const t=e.detail.node;if(!(null==t?void 0:t.checked))return;window.fsMod.recentIds.file=t.identifier,window.fsMod.navFrameHighlightedID.file=t.stateIdentifier;const i=-1!==window.currentSubScript.indexOf("?")?"&":"?";TYPO3.Backend.ContentContainer.setUrl(window.currentSubScript+i+"id="+t.identifier)},this.showContextMenu=e=>{const t=e.detail.node;t&&s.show(t.itemType,decodeURIComponent(t.identifier),"tree","","",this.tree.getNodeElement(t))},this.selectActiveNode=e=>{const t=window.fsMod.navFrameHighlightedID.file;let i=e.detail.nodes;e.detail.nodes=i.map(e=>(e.stateIdentifier===t&&(e.checked=!0),e))}}connectedCallback(){super.connectedCallback(),document.addEventListener("typo3:filestoragetree:refresh",this.refresh),document.addEventListener("typo3:filestoragetree:selectFirstNode",this.selectFirstNode),document.addEventListener("typo3:filelist:treeUpdateRequested",this.treeUpdateRequested)}disconnectedCallback(){document.removeEventListener("typo3:filestoragetree:refresh",this.refresh),document.removeEventListener("typo3:filestoragetree:selectFirstNode",this.selectFirstNode),document.removeEventListener("typo3:filelist:treeUpdateRequested",this.treeUpdateRequested),super.disconnectedCallback()}createRenderRoot(){return this}render(){const e={dataUrl:top.TYPO3.settings.ajaxUrls.filestorage_tree_data,filterUrl:top.TYPO3.settings.ajaxUrls.filestorage_tree_filter,showIcons:!0};return i.html`
<div id="typo3-filestoragetree" class="svg-tree">
<div>
<typo3-backend-tree-toolbar .tree="${this.tree}" id="filestoragetree-toolbar" class="svg-toolbar"></typo3-backend-tree-toolbar>
......@@ -22,4 +22,4 @@ var __decorate=this&&this.__decorate||function(e,t,r,i){var n,s=arguments.length
<typo3-backend-icon identifier="spinner-circle-light" size="large"></typo3-backend-icon>
</div>
</div>
`}firstUpdated(){this.toolbar.tree=this.tree,this.tree.addEventListener("typo3:svg-tree:expand-toggle",this.toggleExpandState),this.tree.addEventListener("typo3:svg-tree:node-selected",this.loadContent),this.tree.addEventListener("typo3:svg-tree:node-context",this.showContextMenu),this.tree.addEventListener("typo3:svg-tree:nodes-prepared",this.selectActiveNode)}};__decorate([r.query(".svg-tree-wrapper")],h.prototype,"tree",void 0),__decorate([r.query("typo3-backend-tree-toolbar")],h.prototype,"toolbar",void 0),h=__decorate([r.customElement(t.navigationComponentName)],h),t.FileStorageTreeNavigationComponent=h;class p extends o.DragDrop{changeNodePosition(e){const t=this.tree.nodes,r=this.tree.settings.nodeDrag.identifier;let i=this.tree.settings.nodeDragPosition,n=e||this.tree.settings.nodeDrag;if(r===n.identifier)return null;if(i===o.DraggablePositionEnum.BEFORE){const r=t.indexOf(e),s=this.setNodePositionAndTarget(r);if(null===s)return null;i=s.position,n=s.target}return{node:this.tree.settings.nodeDrag,identifier:r,target:n,position:i}}setNodePositionAndTarget(e){const t=this.tree.nodes,r=t[e].depth;e>0&&e--;const i=t[e].depth,n=this.tree.nodes[e];if(i===r)return{position:o.DraggablePositionEnum.AFTER,target:n};if(i<r)return{position:o.DraggablePositionEnum.INSIDE,target:n};for(let i=e;i>=0;i--){if(t[i].depth===r)return{position:o.DraggablePositionEnum.AFTER,target:this.tree.nodes[i]};if(t[i].depth<r)return{position:o.DraggablePositionEnum.AFTER,target:t[i]}}return null}changeNodeClasses(e){const t=this.tree.svg.select(".node-over"),r=this.tree.svg.node().parentNode.querySelector(".node-dd");t.size()&&this.tree.isOverSvg&&(this.tree.nodesBgContainer.selectAll(".node-bg__border").style("display","none"),this.addNodeDdClass(r,"ok-append"),this.tree.settings.nodeDragPosition=o.DraggablePositionEnum.INSIDE)}}class f{constructor(e,t){this.startDrag=!1,this.startPageX=0,this.startPageY=0,this.isDragged=!1,this.tree=e,this.actionHandler=t}dragStart(e){return 0!==e.subject.depth&&(this.startPageX=e.sourceEvent.pageX,this.startPageY=e.sourceEvent.pageY,this.startDrag=!1,!0)}dragDragged(e){let t=e.subject;if(!this.actionHandler.isDragNodeDistanceMore(e,this))return!1;if(this.startDrag=!0,0===t.depth)return!1;this.tree.settings.nodeDrag=t;let r=this.tree.svg.node().querySelector('.node-bg[data-state-id="'+t.stateIdentifier+'"]'),i=this.tree.svg.node().parentNode.querySelector(".node-dd");return this.isDragged||(this.isDragged=!0,this.actionHandler.createDraggable(this.tree.getIconId(t),t.name),null==r||r.classList.add("node-bg--dragging")),this.tree.settings.nodeDragPosition=!1,this.actionHandler.openNodeTimeout(),this.actionHandler.updateDraggablePosition(e),(t.isOver||this.tree.hoveredNode&&-1!==this.tree.hoveredNode.parentsStateIdentifier.indexOf(t.stateIdentifier)||!this.tree.isOverSvg)&&(this.actionHandler.addNodeDdClass(i,"nodrop"),this.tree.isOverSvg||this.tree.nodesBgContainer.selectAll(".node-bg__border").style("display","none")),!this.tree.hoveredNode||this.isInSameParentNode(t,this.tree.hoveredNode)?(this.actionHandler.addNodeDdClass(i,"nodrop"),this.tree.nodesBgContainer.selectAll(".node-bg__border").style("display","none")):this.actionHandler.changeNodeClasses(e),!0}isInSameParentNode(e,t){return e.parentsStateIdentifier[0]==t.parentsStateIdentifier[0]||e.parentsStateIdentifier[0]==t.stateIdentifier}dragEnd(e){let t=e.subject;if(!this.startDrag||0===t.depth)return!1;let r=this.tree.hoveredNode;if(this.isDragged=!1,this.actionHandler.removeNodeDdClass(),!(t.isOver||r&&-1!==r.parentsStateIdentifier.indexOf(t.stateIdentifier))&&this.tree.settings.canNodeDrag&&this.tree.isOverSvg){let e=this.actionHandler.changeNodePosition(r),t=e.position===o.DraggablePositionEnum.INSIDE?TYPO3.lang["mess.move_into"]:TYPO3.lang["mess.move_after"];t=t.replace("%s",e.node.name).replace("%s",e.target.name),a.confirm(TYPO3.lang.move_folder,t,d.warning,[{text:TYPO3.lang["labels.cancel"]||"Cancel",active:!0,btnClass:"btn-default",name:"cancel"},{text:TYPO3.lang["cm.copy"]||"Copy",btnClass:"btn-warning",name:"copy"},{text:TYPO3.lang["labels.move"]||"Move",btnClass:"btn-warning",name:"move"}]).on("button.clicked",t=>{const r=t.target;"move"===r.name?this.sendChangeCommand("move",e):"copy"===r.name&&this.sendChangeCommand("copy",e),a.dismiss()})}return!0}sendChangeCommand(e,t){let r={data:{}};if("copy"===e)r.data.copy=[],r.copy.push({data:decodeURIComponent(t.identifier),target:decodeURIComponent(t.target.identifier)});else{if("move"!==e)return;r.data.move=[],r.data.move.push({data:decodeURIComponent(t.identifier),target:decodeURIComponent(t.target.identifier)})}this.tree.nodesAddPlaceholder(),new c.default(top.TYPO3.settings.ajaxUrls.file_process+"&includeMessages=1").post(r).then(e=>e.resolve()).then(e=>{e&&e.hasErrors?(this.tree.errorNotification(e.messages,!1),this.tree.nodesContainer.selectAll(".node").remove(),this.tree.updateVisibleNodes(),this.tree.nodesRemovePlaceholder()):(e.messages&&e.messages.forEach(e=>{l.showMessage(e.title||"",e.message||"",e.severity)}),this.tree.refreshOrFilterTree())}).catch(e=>{this.tree.errorNotification(e,!0)})}}}));
\ No newline at end of file
`}firstUpdated(){this.toolbar.tree=this.tree,this.tree.addEventListener("typo3:svg-tree:expand-toggle",this.toggleExpandState),this.tree.addEventListener("typo3:svg-tree:node-selected",this.loadContent),this.tree.addEventListener("typo3:svg-tree:node-context",this.showContextMenu),this.tree.addEventListener("typo3:svg-tree:nodes-prepared",this.selectActiveNode)}};__decorate([i.query(".svg-tree-wrapper")],h.prototype,"tree",void 0),__decorate([i.query("typo3-backend-tree-toolbar")],h.prototype,"toolbar",void 0),h=__decorate([i.customElement(t.navigationComponentName)],h),t.FileStorageTreeNavigationComponent=h;class p extends o.DragDrop{changeNodePosition(e){const t=this.tree.nodes,i=this.tree.settings.nodeDrag.identifier;let r=this.tree.settings.nodeDragPosition,n=e||this.tree.settings.nodeDrag;if(i===n.identifier)return null;if(r===o.DraggablePositionEnum.BEFORE){const i=t.indexOf(e),s=this.setNodePositionAndTarget(i);if(null===s)return null;r=s.position,n=s.target}return{node:this.tree.settings.nodeDrag,identifier:i,target:n,position:r}}setNodePositionAndTarget(e){const t=this.tree.nodes,i=t[e].depth;e>0&&e--;const r=t[e].depth,n=this.tree.nodes[e];if(r===i)return{position:o.DraggablePositionEnum.AFTER,target:n};if(r<i)return{position:o.DraggablePositionEnum.INSIDE,target:n};for(let r=e;r>=0;r--){if(t[r].depth===i)return{position:o.DraggablePositionEnum.AFTER,target:this.tree.nodes[r]};if(t[r].depth<i)return{position:o.DraggablePositionEnum.AFTER,target:t[r]}}return null}changeNodeClasses(e){const t=this.tree.svg.select(".node-over"),i=this.tree.svg.node().parentNode.querySelector(".node-dd");t.size()&&this.tree.isOverSvg&&(this.tree.nodesBgContainer.selectAll(".node-bg__border").style("display","none"),this.addNodeDdClass(i,"ok-append"),this.tree.settings.nodeDragPosition=o.DraggablePositionEnum.INSIDE)}}class f{constructor(e,t){this.startDrag=!1,this.startPageX=0,this.startPageY=0,this.isDragged=!1,this.tree=e,this.actionHandler=t}dragStart(e){return 0!==e.subject.depth&&(this.startPageX=e.sourceEvent.pageX,this.startPageY=e.sourceEvent.pageY,this.startDrag=!1,!0)}dragDragged(e){let t=e.subject;if(!this.actionHandler.isDragNodeDistanceMore(e,this))return!1;if(this.startDrag=!0,0===t.depth)return!1;this.tree.settings.nodeDrag=t;let i=this.tree.svg.node().querySelector('.node-bg[data-state-id="'+t.stateIdentifier+'"]'),r=this.tree.svg.node().parentNode.querySelector(".node-dd");return this.isDragged||(this.isDragged=!0,this.actionHandler.createDraggable(this.tree.getIconId(t),t.name),null==i||i.classList.add("node-bg--dragging")),this.tree.settings.nodeDragPosition=!1,this.actionHandler.openNodeTimeout(),this.actionHandler.updateDraggablePosition(e),(t.isOver||this.tree.hoveredNode&&-1!==this.tree.hoveredNode.parentsStateIdentifier.indexOf(t.stateIdentifier)||!this.tree.isOverSvg)&&(this.actionHandler.addNodeDdClass(r,"nodrop"),this.tree.isOverSvg||this.tree.nodesBgContainer.selectAll(".node-bg__border").style("display","none")),!this.tree.hoveredNode||this.isInSameParentNode(t,this.tree.hoveredNode)?(this.actionHandler.addNodeDdClass(r,"nodrop"),this.tree.nodesBgContainer.selectAll(".node-bg__border").style("display","none")):this.actionHandler.changeNodeClasses(e),!0}isInSameParentNode(e,t){return e.stateIdentifier==t.stateIdentifier||e.parentsStateIdentifier[0]==t.stateIdentifier||t.parentsStateIdentifier.includes(e.stateIdentifier)}dragEnd(e){let t=e.subject;if(!this.startDrag||0===t.depth)return!1;let i=this.tree.hoveredNode;if(this.isDragged=!1,this.actionHandler.removeNodeDdClass(),!(t.isOver||i&&-1!==i.parentsStateIdentifier.indexOf(t.stateIdentifier))&&this.tree.settings.canNodeDrag&&this.tree.isOverSvg){let e=this.actionHandler.changeNodePosition(i),t=e.position===o.DraggablePositionEnum.INSIDE?TYPO3.lang["mess.move_into"]:TYPO3.lang["mess.move_after"];t=t.replace("%s",e.node.name).replace("%s",e.target.name),a.confirm(TYPO3.lang.move_folder,t,d.warning,[{text:TYPO3.lang["labels.cancel"]||"Cancel",active:!0,btnClass:"btn-default",name:"cancel"},{text:TYPO3.lang["cm.copy"]||"Copy",btnClass:"btn-warning",name:"copy"},{text:TYPO3.lang["labels.move"]||"Move",btnClass:"btn-warning",name:"move"}]).on("button.clicked",t=>{const i=t.target;"move"===i.name?this.sendChangeCommand("move",e):"copy"===i.name&&this.sendChangeCommand("copy",e),a.dismiss()})}return!0}sendChangeCommand(e,t){let i={data:{}};if("copy"===e)i.data.copy=[],i.copy.push({data:decodeURIComponent(t.identifier),target:decodeURIComponent(t.target.identifier)});else{if("move"!==e)return;i.data.move=[],i.data.move.push({data:decodeURIComponent(t.identifier),target:decodeURIComponent(t.target.identifier)})}this.tree.nodesAddPlaceholder(),new c.default(top.TYPO3.settings.ajaxUrls.file_process+"&includeMessages=1").post(i).then(e=>e.resolve()).then(e=>{e&&e.hasErrors?(this.tree.errorNotification(e.messages,!1),this.tree.nodesContainer.selectAll(".node").remove(),this.tree.updateVisibleNodes(),this.tree.nodesRemovePlaceholder()):(e.messages&&e.messages.forEach(e=>{l.showMessage(e.title||"",e.message||"",e.severity)}),this.tree.refreshOrFilterTree())}).catch(e=>{this.tree.errorNotification(e,!0)})}}}));
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment