mirror of
https://github.com/Horizontal42/summarum.git
synced 2026-11-11 02:12:00 +03:00
add block aggregates count/min/max/product, Ctrl+W close sheet
This commit is contained in:
parent
55a2b6a492
commit
532c3265ad
7 changed files with 75 additions and 5 deletions
|
|
@ -231,6 +231,21 @@ describe("variables and document context", () => {
|
|||
it("prev", () => {
|
||||
expect(calcDoc("42\nprev + 1")).toEqual(["42", "43"]);
|
||||
});
|
||||
it("count", () => {
|
||||
expect(calcDoc("10\n20\n30\ncount")).toEqual(["10", "20", "30", "3"]);
|
||||
expect(calcDoc("10\n20\n\n30\ncount")).toEqual(["10", "20", null, "30", "1"]);
|
||||
});
|
||||
it("min / max", () => {
|
||||
expect(calcDoc("10\n20\n5\nmin")).toEqual(["10", "20", "5", "5"]);
|
||||
expect(calcDoc("10\n20\n5\nmax")).toEqual(["10", "20", "5", "20"]);
|
||||
});
|
||||
it("min / max with units", () => {
|
||||
expect(calcDoc("10 m\n5 m\n20 m\nmin")).toEqual(["10 m", "5 m", "20 m", "5 m"]);
|
||||
expect(calcDoc("10 m\n5 m\n20 m\nmax")).toEqual(["10 m", "5 m", "20 m", "20 m"]);
|
||||
});
|
||||
it("product", () => {
|
||||
expect(calcDoc("2\n3\n4\nproduct")).toEqual(["2", "3", "4", "24"]);
|
||||
});
|
||||
it("headers split blocks and produce no result", () => {
|
||||
expect(calcDoc("# Food\n10\n20\nsum")).toEqual([null, "10", "20", "30"]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -395,7 +395,7 @@ function evalDateArith(op: string, l: Value, r: Value): Value {
|
|||
return { ...date, ms, hasTime };
|
||||
}
|
||||
|
||||
function evalAgg(name: "sum" | "avg" | "prev", ctx: EvalCtx): Value {
|
||||
function evalAgg(name: "sum" | "avg" | "prev" | "count" | "min" | "max" | "product", ctx: EvalCtx): Value {
|
||||
const { lineValues, lineKinds, index } = ctx.line;
|
||||
if (name === "prev") {
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
|
|
@ -416,6 +416,21 @@ function evalAgg(name: "sum" | "avg" | "prev", ctx: EvalCtx): Value {
|
|||
}
|
||||
if (block.length === 0) throw new EvalError("nothing to aggregate");
|
||||
block.reverse();
|
||||
|
||||
if (name === "count") return qty(new Decimal(block.length));
|
||||
|
||||
if (name === "min" || name === "max") {
|
||||
const refDim = block[0].unit?.dimension ?? null;
|
||||
const compatible = block.filter((q) => (q.unit?.dimension ?? null) === refDim);
|
||||
return compatible.reduce((best, q) =>
|
||||
(name === "min" ? toBase(q).lt(toBase(best)) : toBase(q).gt(toBase(best))) ? q : best
|
||||
);
|
||||
}
|
||||
|
||||
if (name === "product") {
|
||||
return qty(block.reduce((acc, q) => acc.mul(q.value), new Decimal(1)));
|
||||
}
|
||||
|
||||
let acc = block[0];
|
||||
let counted = 1;
|
||||
for (let i = 1; i < block.length; i++) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export type Node =
|
|||
| { k: "const"; name: "pi" | "e" | "half" | "onehalf" }
|
||||
| { k: "var"; name: string }
|
||||
| { k: "date"; word: DateWord }
|
||||
| { k: "agg"; name: "sum" | "avg" | "prev" }
|
||||
| { k: "agg"; name: "sum" | "avg" | "prev" | "count" | "min" | "max" | "product" }
|
||||
| { k: "bin"; op: "plus" | "minus" | "mul" | "div" | "mod" | "pow"; l: Node; r: Node }
|
||||
| { k: "bit"; op: BitOp; l: Node; r: Node }
|
||||
| { k: "pctop"; op: PctOp; l: Node; r: Node }
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export type Payload =
|
|||
| { t: "pctop"; op: PctOp }
|
||||
| { t: "percent" }
|
||||
| { t: "func"; name: string }
|
||||
| { t: "agg"; name: "sum" | "avg" | "prev" }
|
||||
| { t: "agg"; name: "sum" | "avg" | "prev" | "count" | "min" | "max" | "product" }
|
||||
| { t: "scale"; mult: Decimal }
|
||||
| { t: "repr"; repr: NumeralRepr }
|
||||
| { t: "special"; name: "unix" | "todate" }
|
||||
|
|
@ -170,6 +170,10 @@ export function buildRegistry(): Registry {
|
|||
for (const v of vocab.variants("Operations", "sum.variants")) reg.addPhrase(v, { t: "agg", name: "sum" }, { caseSensitive: false });
|
||||
for (const v of vocab.variants("Operations", "avg.variants")) reg.addPhrase(v, { t: "agg", name: "avg" }, { caseSensitive: false });
|
||||
for (const v of vocab.variants("Operations", "prev.variants")) reg.addPhrase(v, { t: "agg", name: "prev" }, { caseSensitive: false });
|
||||
for (const v of vocab.variants("Operations", "count.variants")) reg.addPhrase(v, { t: "agg", name: "count" }, { caseSensitive: false });
|
||||
for (const v of vocab.variants("Operations", "min.variants")) reg.addPhrase(v, { t: "agg", name: "min" }, { caseSensitive: false });
|
||||
for (const v of vocab.variants("Operations", "max.variants")) reg.addPhrase(v, { t: "agg", name: "max" }, { caseSensitive: false });
|
||||
for (const v of vocab.variants("Operations", "product.variants")) reg.addPhrase(v, { t: "agg", name: "product" }, { caseSensitive: false });
|
||||
for (const v of vocab.variants("Operations", "sqrt.variants")) reg.addPhrase(v, { t: "func", name: "sqrt" }, { caseSensitive: false });
|
||||
for (const v of vocab.variants("Operations", "cbrt.variants")) reg.addPhrase(v, { t: "func", name: "cbrt" }, { caseSensitive: false });
|
||||
for (const v of vocab.variants("Operations", "abs.variants")) reg.addPhrase(v, { t: "func", name: "abs" }, { caseSensitive: false });
|
||||
|
|
|
|||
|
|
@ -427,7 +427,11 @@ export const EN: VocabLocale = {
|
|||
"ln.variants": "natural log,natural logarithm",
|
||||
"log.variants": "logarithm",
|
||||
"round.variants": "rounded",
|
||||
"sum.variants": "sum,total"
|
||||
"sum.variants": "sum,total",
|
||||
"count.variants": "count,how many",
|
||||
"min.variants": "min,minimum,smallest",
|
||||
"max.variants": "max,maximum,largest",
|
||||
"product.variants": "product"
|
||||
},
|
||||
"Percent": {
|
||||
"as_a_percent_of.variants": "as a % of,as a percent of",
|
||||
|
|
@ -899,7 +903,11 @@ export const RU: VocabLocale = {
|
|||
"ln.variants": "натуральный логарифм,нат. логарифм",
|
||||
"log.variants": "логарифм",
|
||||
"round.variants": "округлить,округление",
|
||||
"sum.variants": "всего,сумма,итог,итого"
|
||||
"sum.variants": "всего,сумма,итог,итого",
|
||||
"count.variants": "количество,кол-во,счёт,число строк",
|
||||
"min.variants": "минимум,мин,наименьшее",
|
||||
"max.variants": "максимум,макс,наибольшее",
|
||||
"product.variants": "произведение,произв"
|
||||
},
|
||||
"Percent": {
|
||||
"as_a_percent_of.variants": "как процент от,как процент",
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ const dict: Record<string, Record<string, string>> = {
|
|||
updateFailed: "Update failed",
|
||||
searchPlaceholder: "Search all sheets…",
|
||||
searchEmpty: "No matches",
|
||||
closeSheet: "Close this sheet?",
|
||||
close: "Close",
|
||||
cancel: "Cancel",
|
||||
},
|
||||
ru: {
|
||||
settings: "Настройки",
|
||||
|
|
@ -110,6 +113,9 @@ const dict: Record<string, Record<string, string>> = {
|
|||
updateFailed: "Не удалось обновить",
|
||||
searchPlaceholder: "Поиск по всем листам…",
|
||||
searchEmpty: "Ничего не найдено",
|
||||
closeSheet: "Закрыть этот лист?",
|
||||
close: "Закрыть",
|
||||
cancel: "Отмена",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
22
src/main.ts
22
src/main.ts
|
|
@ -111,6 +111,24 @@ function newDoc(content = ""): void {
|
|||
switchDoc(id);
|
||||
}
|
||||
|
||||
async function closeActiveDoc(): Promise<void> {
|
||||
if (data.docs.length <= 1) return;
|
||||
const doc = data.docs.find((d) => d.id === data.activeId);
|
||||
if (!doc) return;
|
||||
const content = data.contents[doc.id] ?? "";
|
||||
if (content.trim()) {
|
||||
const ans = await askModal(t("closeSheet"), t("close"), t("cancel"));
|
||||
if (ans !== "a") return;
|
||||
}
|
||||
const idx = data.docs.findIndex((d) => d.id === doc.id);
|
||||
void backupDeletedSheet(settings.dataDir, doc.title, content);
|
||||
delete data.contents[doc.id];
|
||||
data.docs = data.docs.filter((d) => d.id !== doc.id);
|
||||
const nextId = data.docs[idx] ? data.docs[idx].id : data.docs[idx - 1]?.id ?? data.docs[0].id;
|
||||
switchDoc(nextId);
|
||||
saveAppData(data);
|
||||
}
|
||||
|
||||
// ---------- search all sheets
|
||||
|
||||
interface SearchHit {
|
||||
|
|
@ -682,6 +700,10 @@ async function boot(): Promise<void> {
|
|||
e.preventDefault();
|
||||
newDoc();
|
||||
}
|
||||
if (e.ctrlKey && !e.shiftKey && e.key.toLowerCase() === "w") {
|
||||
e.preventDefault();
|
||||
void closeActiveDoc();
|
||||
}
|
||||
if (e.ctrlKey && !e.shiftKey && e.key.toLowerCase() === "b") {
|
||||
e.preventDefault();
|
||||
$("#toggle-sidebar").click();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue