Vanessa 2023-12-30 17:41:31 +08:00
parent 23fd11ae12
commit b8aa401996
6 changed files with 279 additions and 95 deletions

View file

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

View file

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

View file

@ -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: {
<button style="margin: 4px 0 8px;" class="b3-button fn__block" data-type="updateRelation">${window.siyuan.languages.confirm}</button>
</div>`;
} else if (colData.type === "rollup") {
html += `<button class="b3-menu__item" data-type="goSearchRollupCol" data-av-id="${colData.relation?.avID || ""}" data-old-value='${JSON.stringify(colData.relation || {})}'>
html += `<button class="b3-menu__item" data-type="goSearchRollupCol" data-old-value='${JSON.stringify(colData.rollup || {})}'>
<span class="b3-menu__label">${window.siyuan.languages.relation}</span>
<span class="b3-menu__accelerator">TODO</span>
<span class="b3-menu__accelerator"></span>
<svg class="b3-menu__icon b3-menu__icon--small"><use xlink:href="#iconRight"></use></svg>
</button>
<button class="b3-menu__item b3-menu__item--disabled" data-type="goSearchRollupTarget" data-av-id="${colData.relation?.avID || ""}" data-old-value='${JSON.stringify(colData.relation || {})}'>
<button class="b3-menu__item" data-type="goSearchRollupTarget">
<span class="b3-menu__label">${window.siyuan.languages.attr}</span>
<span class="b3-menu__accelerator">TODO</span>
<span class="b3-menu__accelerator"></span>
<svg class="b3-menu__icon b3-menu__icon--small"><use xlink:href="#iconRight"></use></svg>
</button>
<button class="b3-menu__item b3-menu__item--disabled" data-type="goSearchRollupCalc" data-av-id="${colData.relation?.avID || ""}" data-old-value='${JSON.stringify(colData.relation || {})}'>
<button class="b3-menu__item" data-type="goSearchRollupCalc">
<span class="b3-menu__label">${window.siyuan.languages.calc}</span>
<span class="b3-menu__accelerator">TODO</span>
<span class="b3-menu__accelerator">${getNameByOperator(colData.rollup?.calc?.operator)}</span>
<svg class="b3-menu__icon b3-menu__icon--small"><use xlink:href="#iconRight"></use></svg>
</button>`;
}
@ -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) => {

View file

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

View file

@ -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 += `<div class="b3-list-item b3-list-item--narrow${index === 0 ? " b3-list-item--focus" : ""}" data-col-id="${item.id}">
html += `<div class="b3-list-item b3-list-item--narrow${index === 0 ? " b3-list-item--focus" : ""}" data-col-id="${item.id}" ${isRelation ? `data-target-av-id="${item.relation.avID}"` : `data-col-type="${item.type}"`}>
${item.icon ? unicode2Emoji(item.icon, "b3-list-item__graphic", true) : `<svg class="b3-list-item__graphic"><use xlink:href="#${getColIconByType(item.type)}"></use></svg>`}
${genIconHTML()}
<span class="b3-list-item__text">${escapeHtml(item.name || window.siyuan.languages.title)}</span>
@ -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) => {
};

View file

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