diff --git a/app/src/assets/scss/business/_av.scss b/app/src/assets/scss/business/_av.scss index 7c4f169fe..8b4f0e26d 100644 --- a/app/src/assets/scss/business/_av.scss +++ b/app/src/assets/scss/business/_av.scss @@ -173,6 +173,20 @@ overflow: hidden; } + &__item { + transition: margin .2s cubic-bezier(0, 0, .2, 1) 0ms; + + &.dragover__bottom { + background-color: var(--b3-theme-primary-lightest); + box-shadow: 0 -2px 0 var(--b3-theme-primary-lighter) inset; + } + + &.dragover__top { + background-color: var(--b3-theme-primary-lightest); + box-shadow: 0 2px 0 var(--b3-theme-primary-lighter) inset; + } + } + &__item:hover .b3-menu__action { opacity: 1; } diff --git a/app/src/protyle/render/av/openMenuPanel.ts b/app/src/protyle/render/av/openMenuPanel.ts index 25626888a..8cad4d4f7 100644 --- a/app/src/protyle/render/av/openMenuPanel.ts +++ b/app/src/protyle/render/av/openMenuPanel.ts @@ -4,7 +4,7 @@ import {addCol} from "./addCol"; import {getColIconByType} from "./col"; import {setPosition} from "../../../util/setPosition"; import {Menu} from "../../../plugin/Menu"; -import {hasClosestByClassName} from "../../util/hasClosest"; +import {hasClosestByAttribute, hasClosestByClassName} from "../../util/hasClosest"; export const openMenuPanel = (protyle: IProtyle, blockElement: HTMLElement, type: "properties" | "config" | "sorts" | "filters" = "config") => { let avPanelElement = document.querySelector(".av__panel"); @@ -37,6 +37,138 @@ export const openMenuPanel = (protyle: IProtyle, blockElement: HTMLElement, type setPosition(menuElement, tabRect.right - menuElement.clientWidth, tabRect.bottom, tabRect.height); bindSortsEvent(protyle, menuElement, data); + avPanelElement.addEventListener("dragstart", (event) => { + window.siyuan.dragElement = event.target as HTMLElement; + window.siyuan.dragElement.style.opacity = ".1"; + return; + }); + avPanelElement.addEventListener("drop", (event) => { + window.siyuan.dragElement.style.opacity = ""; + const sourceElement = window.siyuan.dragElement; + window.siyuan.dragElement = undefined; + if (protyle.disabled) { + event.preventDefault(); + event.stopPropagation(); + return; + } + const target = event.target as HTMLElement; + const targetElement = hasClosestByAttribute(target, "data-id", null); + if (!targetElement || + (!targetElement.classList.contains("dragover__top") && !targetElement.classList.contains("dragover__bottom"))) { + return; + } + let type = "columns"; + if (targetElement.querySelector('[data-type="removeSort"]')) { + type = "sorts"; + } else if (targetElement.querySelector('[data-type="removeFilter"]')) { + type = "filters"; + } + const sourceId = sourceElement.dataset.id; + const targetId = targetElement.dataset.id; + const isTop = targetElement.classList.contains("dragover__top") + if (type !== "columns") { + const changeData = (type === "sorts" ? data.sorts : data.filters) as IAVFilter[] + const oldData = Object.assign([], changeData); + let targetFilter: IAVFilter + changeData.find((filter, index: number) => { + if (filter.column === sourceId) { + targetFilter = changeData.splice(index, 1)[0]; + return true; + } + }) + changeData.find((filter, index: number) => { + if (filter.column === targetId) { + if (isTop) { + changeData.splice(index, 0, targetFilter); + } else { + changeData.splice(index + 1, 0, targetFilter); + } + return true; + } + }) + + transaction(protyle, [{ + action: "setAttrView", + id: avId, + data: { + [type]: changeData + } + }], [{ + action: "setAttrView", + id: avId, + data: { + [type]: oldData + } + }]); + menuElement.innerHTML = (type === "sorts" ? getSortsHTML(data) : getFiltersHTML(data)); + if (type === "sorts") { + bindSortsEvent(protyle, menuElement, data); + } + return; + } + transaction(protyle, [{ + action: "sortAttrViewCol", + parentID: avId, + previousID: (targetElement.classList.contains("dragover__top") ? targetElement.previousElementSibling?.getAttribute("data-id") : targetElement.getAttribute("data-id")) || "", + id: sourceId, + }], [{ + action: "sortAttrViewCol", + parentID: avId, + previousID: sourceElement.previousElementSibling?.getAttribute("data-id") || "", + id: sourceId, + }]); + let column: IAVColumn + data.columns.find((item, index: number) => { + if (item.id === sourceId) { + column = data.columns.splice(index, 1)[0]; + return true; + } + }) + data.columns.find((item, index: number) => { + if (item.id === targetId) { + if (isTop) { + data.columns.splice(index, 0, column); + } else { + data.columns.splice(index + 1, 0, column); + } + return true; + } + }) + menuElement.innerHTML = getPropertiesHTML(data) + }); + let dragoverElement: HTMLElement + avPanelElement.addEventListener("dragover", (event: DragEvent) => { + const target = event.target as HTMLElement; + const targetElement = hasClosestByAttribute(target, "data-id", null); + if (!targetElement || targetElement.isSameNode(window.siyuan.dragElement)) { + return; + } + event.preventDefault(); + if (dragoverElement && targetElement.isSameNode(dragoverElement)) { + const nodeRect = targetElement.getBoundingClientRect(); + if (event.clientY > nodeRect.top + nodeRect.height / 2) { + targetElement.classList.add("dragover__bottom"); + } else { + targetElement.classList.add("dragover__top"); + } + return; + } + dragoverElement = targetElement; + }); + + avPanelElement.addEventListener("dragleave", (event) => { + const target = event.target as HTMLElement; + const targetElement = hasClosestByAttribute(target, "data-id", null); + if (targetElement) { + targetElement.classList.remove("dragover__top", "dragover__bottom"); + } + }); + avPanelElement.addEventListener("dragend", (event) => { + if (window.siyuan.dragElement) { + window.siyuan.dragElement.style.opacity = "" + window.siyuan.dragElement = undefined; + } + }); avPanelElement.addEventListener("click", (event) => { event.preventDefault(); let target = event.target as HTMLElement; @@ -249,7 +381,7 @@ export const openMenuPanel = (protyle: IProtyle, blockElement: HTMLElement, type event.stopPropagation(); break; } else if (type === "hideCol") { - const colId = target.getAttribute("data-id"); + const colId = target.parentElement.getAttribute("data-id"); transaction(protyle, [{ action: "setAttrViewColHidden", id: colId, @@ -267,7 +399,7 @@ export const openMenuPanel = (protyle: IProtyle, blockElement: HTMLElement, type event.stopPropagation(); break; } else if (type === "showCol") { - const colId = target.getAttribute("data-id"); + const colId = target.parentElement.getAttribute("data-id"); transaction(protyle, [{ action: "setAttrViewColHidden", id: colId, @@ -388,7 +520,7 @@ const getSortsHTML = (data: IAV) => { return sortHTML; }; data.sorts.forEach((item: IAVSort) => { - html += `