diff --git a/app/src/menus/Menu.ts b/app/src/menus/Menu.ts index 8e3c725ec..27d92ec88 100644 --- a/app/src/menus/Menu.ts +++ b/app/src/menus/Menu.ts @@ -105,6 +105,7 @@ export class Menu { this.removeScrollEvent(); this.element.firstElementChild.classList.add("fn__none"); this.element.lastElementChild.innerHTML = ""; + this.element.lastElementChild.removeAttribute("style"); // 输入框 focus 后 boxShadow 显示不全 this.element.classList.add("fn__none"); this.element.classList.remove("b3-menu--list", "b3-menu--fullscreen"); this.element.removeAttribute("style"); // zIndex diff --git a/app/src/protyle/render/av/calc.ts b/app/src/protyle/render/av/calc.ts index 6b8be1393..2a04dcbd9 100644 --- a/app/src/protyle/render/av/calc.ts +++ b/app/src/protyle/render/av/calc.ts @@ -5,55 +5,106 @@ import {hasClosestBlock, hasClosestByClassName} from "../../util/hasClosest"; const calcItem = (options: { menu: Menu, protyle: IProtyle, - label: string, operator: string, oldOperator: string, colId: string, + data?: IAV, + target: HTMLElement, avId: string }) => { options.menu.addItem({ iconHTML: "", - label: options.label, + label: getNameByOperator(options.operator), click() { - transaction(options.protyle, [{ - action: "setAttrViewColCalc", - avID: options.avId, - id: options.colId, - data: { + if (!options.data) { + transaction(options.protyle, [{ + action: "setAttrViewColCalc", + avID: options.avId, + id: options.colId, + data: { + operator: options.operator + } + }], [{ + action: "setAttrViewColCalc", + avID: options.avId, + id: options.colId, + data: { + operator: options.oldOperator + } + }]); + } else { + options.target.querySelector(".b3-menu__accelerator").textContent = getNameByOperator(options.operator) + const colData = options.data.view.columns.find((item) => { + if (item.id === options.colId) { + if (!item.rollup) { + item.rollup = {}; + } + return true; + } + }); + colData.rollup.calc = { operator: options.operator - } - }], [{ - action: "setAttrViewColCalc", - avID: options.avId, - id: options.colId, - data: { - operator: options.oldOperator - } - }]); + }; + transaction(options.protyle, [{ + action: "updateAttrViewColRollup", + id: options.colId, + avID: options.avId, + parentID: colData.rollup.relationKeyID, + keyID: colData.rollup.keyID, + data: { + calc: colData.rollup.calc, + }, + }], [{ + action: "updateAttrViewColRollup", + id: options.colId, + avID: options.avId, + parentID: colData.rollup.relationKeyID, + keyID: colData.rollup.keyID, + data: { + calc: { + operator: options.oldOperator + }, + } + }]); + } } }); }; -export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { - const blockElement = hasClosestBlock(calcElement); - if (!blockElement) { - return; +export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement, data?: IAV, rollupId?: string) => { + let rowElement: HTMLElement | false; + let type; + let colId; + let avId; + let oldOperator; + if (data) { + avId = data.id; + type = calcElement.dataset.colType as TAVCol; + oldOperator = calcElement.dataset.calc; + colId = rollupId; + } else { + const blockElement = hasClosestBlock(calcElement); + if (!blockElement) { + return; + } + rowElement = hasClosestByClassName(calcElement, "av__row--footer"); + if (!rowElement) { + return; + } + rowElement.classList.add("av__row--show"); + type = calcElement.dataset.dtype as TAVCol; + colId = calcElement.dataset.colId; + avId = blockElement.dataset.avId; + oldOperator = calcElement.dataset.operator; } - const rowElement = hasClosestByClassName(calcElement, "av__row--footer"); - if (!rowElement) { - return; - } - rowElement.classList.add("av__row--show"); const menu = new Menu("av-calc", () => { - rowElement.classList.remove("av__row--show"); + if (rowElement) { + rowElement.classList.remove("av__row--show"); + } }); if (menu.isOpen) { return; } - const type = calcElement.dataset.dtype as TAVCol; - const colId = calcElement.dataset.colId; - const avId = blockElement.dataset.avId; - const oldOperator = calcElement.dataset.operator; if (type !== "checkbox") { calcItem({ menu, @@ -62,7 +113,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "", - label: window.siyuan.languages.calcOperatorNone + data, + target: calcElement }); } calcItem({ @@ -72,7 +124,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Count all", - label: window.siyuan.languages.calcOperatorCountAll + data, + target: calcElement }); if (type !== "checkbox") { calcItem({ @@ -82,7 +135,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Count values", - label: window.siyuan.languages.calcOperatorCountValues + data, + target: calcElement }); calcItem({ menu, @@ -91,7 +145,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Count unique values", - label: window.siyuan.languages.calcOperatorCountUniqueValues + data, + target: calcElement }); calcItem({ menu, @@ -100,7 +155,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Count empty", - label: window.siyuan.languages.calcOperatorCountEmpty + data, + target: calcElement }); calcItem({ menu, @@ -109,7 +165,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Count not empty", - label: window.siyuan.languages.calcOperatorCountNotEmpty + data, + target: calcElement }); calcItem({ menu, @@ -118,7 +175,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Percent empty", - label: window.siyuan.languages.calcOperatorPercentEmpty + data, + target: calcElement }); calcItem({ menu, @@ -127,7 +185,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Percent not empty", - label: window.siyuan.languages.calcOperatorPercentNotEmpty + data, + target: calcElement }); } else { calcItem({ @@ -137,7 +196,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Checked", - label: window.siyuan.languages.checked + data, + target: calcElement }); calcItem({ menu, @@ -146,7 +206,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Unchecked", - label: window.siyuan.languages.unchecked + data, + target: calcElement }); calcItem({ menu, @@ -155,7 +216,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Percent checked", - label: window.siyuan.languages.percentChecked + data, + target: calcElement }); calcItem({ menu, @@ -164,7 +226,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Percent unchecked", - label: window.siyuan.languages.percentUnchecked + data, + target: calcElement }); } if (["number", "template"].includes(type)) { @@ -175,7 +238,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Sum", - label: window.siyuan.languages.calcOperatorSum + data, + target: calcElement }); calcItem({ menu, @@ -184,7 +248,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Average", - label: window.siyuan.languages.calcOperatorAverage + data, + target: calcElement }); calcItem({ menu, @@ -193,7 +258,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Median", - label: window.siyuan.languages.calcOperatorMedian + data, + target: calcElement }); calcItem({ menu, @@ -202,7 +268,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Min", - label: window.siyuan.languages.calcOperatorMin + data, + target: calcElement }); calcItem({ menu, @@ -211,7 +278,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Max", - label: window.siyuan.languages.calcOperatorMax + data, + target: calcElement }); calcItem({ menu, @@ -220,7 +288,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Range", - label: window.siyuan.languages.calcOperatorRange + data, + target: calcElement }); } else if (["date", "created", "updated"].includes(type)) { calcItem({ @@ -230,7 +299,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Earliest", - label: window.siyuan.languages.calcOperatorEarliest + data, + target: calcElement }); calcItem({ menu, @@ -239,7 +309,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Latest", - label: window.siyuan.languages.calcOperatorLatest + data, + target: calcElement }); calcItem({ menu, @@ -248,7 +319,8 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => { avId, oldOperator, operator: "Range", - label: window.siyuan.languages.calcOperatorRange + data, + target: calcElement }); } const calcRect = calcElement.getBoundingClientRect(); @@ -326,3 +398,50 @@ export const getCalcValue = (column: IAVColumn) => { } return value; }; + +export const getNameByOperator = (operator: string) => { + switch (operator) { + case "": + return window.siyuan.languages.calcOperatorNone; + case "Count all": + return window.siyuan.languages.calcOperatorCountAll; + case "Count values": + return window.siyuan.languages.calcOperatorCountValues; + case "Count unique values": + return window.siyuan.languages.calcOperatorCountUniqueValues; + case "Count empty": + return window.siyuan.languages.calcOperatorCountEmpty; + case "Count not empty": + return window.siyuan.languages.calcOperatorCountNotEmpty; + case "Percent empty": + return window.siyuan.languages.calcOperatorPercentEmpty; + case "Percent not empty": + return window.siyuan.languages.calcOperatorPercentNotEmpty; + case "Checked": + return window.siyuan.languages.checked; + case "Unchecked": + return window.siyuan.languages.unchecked; + case "Percent checked": + return window.siyuan.languages.percentChecked; + case "Percent unchecked": + return window.siyuan.languages.percentUnchecked; + case "Sum": + return window.siyuan.languages.calcOperatorSum; + case "Average": + return window.siyuan.languages.calcOperatorAverage; + case "Median": + return window.siyuan.languages.calcOperatorMedian; + case "Min": + return window.siyuan.languages.calcOperatorMin; + case "Max": + return window.siyuan.languages.calcOperatorMax; + case "Range": + return window.siyuan.languages.calcOperatorRange; + case "Earliest": + return window.siyuan.languages.calcOperatorEarliest; + case "Latest": + return window.siyuan.languages.calcOperatorLatest; + default: + return "" + } +} diff --git a/app/src/protyle/render/av/col.ts b/app/src/protyle/render/av/col.ts index 695b24ebd..ba92529e4 100644 --- a/app/src/protyle/render/av/col.ts +++ b/app/src/protyle/render/av/col.ts @@ -9,6 +9,7 @@ import {removeAttrViewColAnimation, updateAttrViewCellAnimation} from "./action" import {openEmojiPanel, unicode2Emoji} from "../../../emoji"; import {focusBlock} from "../../util/selection"; import {toggleUpdateRelationBtn} from "./relation"; +import {getNameByOperator} from "./calc"; export const duplicateCol = (options: { protyle: IProtyle, @@ -159,19 +160,19 @@ export const getEditHTML = (options: { `; } else if (colData.type === "rollup") { - html += ` - -`; } @@ -363,6 +364,36 @@ export const bindEditEvent = (options: { toggleUpdateRelationBtn(options.menuElement, avID); } } + + const goSearchRollupColElement = options.menuElement.querySelector('[data-type="goSearchRollupCol"]') as HTMLElement; + if (goSearchRollupColElement) { + const oldValue = JSON.parse(goSearchRollupColElement.dataset.oldValue) as IAVCellRollupValue; + const goSearchRollupTargetElement = options.menuElement.querySelector('[data-type="goSearchRollupTarget"]') as HTMLElement; + let targetKeyAVId = "" + if (oldValue.relationKeyID) { + options.data.view.columns.find((item) => { + if (item.id === oldValue.relationKeyID) { + goSearchRollupColElement.querySelector(".b3-menu__accelerator").textContent = item.name; + targetKeyAVId = item.relation.avID; + goSearchRollupTargetElement.dataset.avId = targetKeyAVId; + return true; + } + }) + } + if (oldValue.keyID && targetKeyAVId) { + fetchPost("/api/av/getAttributeView", {id: targetKeyAVId}, (response) => { + response.data.av.keyValues.find((item: { key: { id: string, name: string, type: TAVCol } }) => { + if (item.key.id === oldValue.keyID) { + goSearchRollupTargetElement.querySelector('.b3-menu__accelerator').textContent = item.key.name; + const goSearchRollupCalcElement = options.menuElement.querySelector('[data-type="goSearchRollupCalc"]') as HTMLElement; + goSearchRollupCalcElement.dataset.colType = item.key.type; + goSearchRollupCalcElement.dataset.calc = oldValue.calc.operator; + return true; + } + }); + }); + } + } }; export const getColNameByType = (type: TAVCol) => { diff --git a/app/src/protyle/render/av/openMenuPanel.ts b/app/src/protyle/render/av/openMenuPanel.ts index a053a1d93..517672430 100644 --- a/app/src/protyle/render/av/openMenuPanel.ts +++ b/app/src/protyle/render/av/openMenuPanel.ts @@ -27,8 +27,9 @@ import {focusBlock, getEditorRange} from "../../util/selection"; import {avRender} from "./render"; import {setPageSize} from "./row"; import {bindRelationEvent, getRelationHTML, openSearchAV, setRelationCell, updateRelation} from "./relation"; -import {goSearchRollupCalc, goSearchRollupCol, goSearchRollupTarget} from "./rollup"; +import {goSearchRollupCol} from "./rollup"; import {updateCellsValue} from "./cell"; +import {openCalcMenu} from "./calc"; export const openMenuPanel = (options: { protyle: IProtyle, @@ -782,6 +783,7 @@ export const openMenuPanel = (options: { goSearchRollupCol({ target, data, + isRelation: true, protyle: options.protyle, colId: menuElement.querySelector(".b3-menu__item").getAttribute("data-col-id") }); @@ -789,12 +791,18 @@ export const openMenuPanel = (options: { event.stopPropagation(); break; } else if (type === "goSearchRollupTarget") { - goSearchRollupTarget(avID, target); + goSearchRollupCol({ + target, + data, + isRelation: false, + protyle: options.protyle, + colId: menuElement.querySelector(".b3-menu__item").getAttribute("data-col-id") + }); event.preventDefault(); event.stopPropagation(); break; } else if (type === "goSearchRollupCalc") { - goSearchRollupCalc(avID, target); + openCalcMenu(options.protyle, target, data, options.colId); event.preventDefault(); event.stopPropagation(); break; diff --git a/app/src/protyle/render/av/rollup.ts b/app/src/protyle/render/av/rollup.ts index e4b0858a4..bbe3f1b5e 100644 --- a/app/src/protyle/render/av/rollup.ts +++ b/app/src/protyle/render/av/rollup.ts @@ -8,40 +8,63 @@ import {genIconHTML} from "../util"; import {unicode2Emoji} from "../../../emoji"; import {getColIconByType} from "./col"; -const updateCol = (protyle: IProtyle, data: IAV, colId: string, itemElement: HTMLElement) => { +const updateCol = (options: { + target: HTMLElement, + data: IAV, + protyle: IProtyle, + colId: string, + isRelation: boolean, +}, itemElement: HTMLElement) => { if (itemElement.classList.contains("b3-list--empty")) { return } - const colData = data.view.columns.find((item) => { - if (item.id === colId) { + options.target.querySelector(".b3-menu__accelerator").textContent = itemElement.querySelector(".b3-list-item__text").textContent + + const colData = options.data.view.columns.find((item) => { + if (item.id === options.colId) { + if (!item.rollup) { + item.rollup = {}; + } return true; } }); - transaction(protyle, [{ + const oldColValue = Object.assign({}, colData.rollup); + if (options.isRelation) { + colData.rollup.relationKeyID = itemElement.dataset.colId; + options.target.nextElementSibling.setAttribute("data-av-id", itemElement.dataset.targetAvId); + } else { + colData.rollup.keyID = itemElement.dataset.colId; + options.target.nextElementSibling.setAttribute("data-col-type", itemElement.dataset.colType); + } + transaction(options.protyle, [{ action: "updateAttrViewColRollup", - id: colId, - avID: data.id, - parentID: itemElement.dataset.colId, - keyID: "", - data: "", + id: options.colId, + avID: options.data.id, + parentID: colData.rollup.relationKeyID, + keyID: colData.rollup.keyID, + data: { + calc: colData.rollup.calc, + }, }], [{ action: "updateAttrViewColRollup", - // operation.AvID 汇总列所在 av - // operation.ID 汇总列 ID - // operation.ParentID 汇总列基于的关联列 ID - // operation.KeyID 目标列 ID - // operation.Data 计算方式 + id: options.colId, + avID: options.data.id, + parentID: oldColValue.relationKeyID, + keyID: oldColValue.keyID, + data: { + calc: oldColValue.calc, + } }]); }; -const genSearchList = (element: Element, keyword: string, avId: string, cb?: () => void) => { - fetchPost("/api/av/searchAttributeViewRelationKey", { +const genSearchList = (element: Element, keyword: string, avId: string, isRelation: boolean, cb?: () => void) => { + fetchPost(isRelation ? "/api/av/searchAttributeViewRelationKey" : "/api/av/searchAttributeViewNonRelationKey", { avID: avId, keyword }, (response) => { let html = ""; response.data.keys.forEach((item: IAVColumn, index: number) => { - html += `
+ html += `
${item.icon ? unicode2Emoji(item.icon, "b3-list-item__graphic", true) : ``} ${genIconHTML()} ${escapeHtml(item.name || window.siyuan.languages.title)} @@ -58,7 +81,8 @@ export const goSearchRollupCol = (options: { target: HTMLElement, data: IAV, protyle: IProtyle, - colId: string + colId: string, + isRelation: boolean, }) => { window.siyuan.menus.menu.remove(); const menu = new Menu(); @@ -86,23 +110,23 @@ export const goSearchRollupCol = (options: { if (event.key === "Enter") { event.preventDefault(); event.stopPropagation(); - updateCol(options.protyle, options.data, options.colId, listElement.querySelector(".b3-list-item--focus")); + updateCol(options, listElement.querySelector(".b3-list-item--focus") as HTMLElement); window.siyuan.menus.menu.remove(); } }); inputElement.addEventListener("input", (event) => { event.stopPropagation(); - genSearchList(listElement, inputElement.value, options.data.id); + genSearchList(listElement, inputElement.value, options.isRelation ? options.data.id : options.target.dataset.avId, options.isRelation); }); element.lastElementChild.addEventListener("click", (event) => { const listItemElement = hasClosestByClassName(event.target as HTMLElement, "b3-list-item"); if (listItemElement) { event.stopPropagation(); - updateCol(options.protyle, options.data, options.colId, listItemElement); + updateCol(options, listItemElement); window.siyuan.menus.menu.remove(); } }); - genSearchList(listElement, "", options.data.id, () => { + genSearchList(listElement, "", options.isRelation ? options.data.id : options.target.dataset.avId, options.isRelation, () => { const rect = options.target.getBoundingClientRect(); menu.open({ x: rect.left, @@ -115,11 +139,3 @@ export const goSearchRollupCol = (options: { }); menu.element.querySelector(".b3-menu__items").setAttribute("style", "overflow: initial"); }; - -export const goSearchRollupTarget = (avId: string, target: HTMLElement) => { - -}; - -export const goSearchRollupCalc = (avId: string, target: HTMLElement) => { - -}; diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index 25efb5299..8b5f95f8b 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -1082,16 +1082,14 @@ interface IAVColumn { type: TAVCol, numberFormat: string, template: string, - calc: { - operator: string, - result: IAVCellValue - }, + calc: IAVCalc, // 选项列表 options?: { name: string, color: string, }[], - relation?: IAVCellRelationValue + relation?: IAVCellRelationValue, + rollup?: IAVCellRollupValue } interface IAVRow { @@ -1175,3 +1173,14 @@ interface IAVCellRelationValue { backKeyID?: string isTwoWay?: boolean } + +interface IAVCellRollupValue { + relationKeyID?: string // 关联列 ID + keyID?: string + calc?: IAVCalc +} + +interface IAVCalc { + operator?: string, + result?: IAVCellValue +}