mevebu 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -8
- package/mevebu/app/mevebu.js +2 -2
- package/mevebu/libs/css/tools.css +103 -1
- package/mevebu/libs/js/core.js +1 -1
- package/mevebu/libs/js/icons.js +115 -1
- package/mevebu/libs/js/registry.js +3102 -0
- package/mevebu/libs/js/tools/abstract-checklist.js +54 -0
- package/mevebu/libs/js/tools/add-subtract-days.js +105 -0
- package/mevebu/libs/js/tools/age-calculator.js +108 -0
- package/mevebu/libs/js/tools/apa-reference.js +98 -0
- package/mevebu/libs/js/tools/api-request-builder.js +76 -0
- package/mevebu/libs/js/tools/area-converter.js +87 -0
- package/mevebu/libs/js/tools/aspect-ratio.js +81 -0
- package/mevebu/libs/js/tools/average-calculator.js +83 -0
- package/mevebu/libs/js/tools/bibliography-sorter.js +43 -0
- package/mevebu/libs/js/tools/bibtex-generator.js +98 -0
- package/mevebu/libs/js/tools/binary-calculator.js +58 -0
- package/mevebu/libs/js/tools/border-radius.js +88 -0
- package/mevebu/libs/js/tools/box-shadow.js +71 -0
- package/mevebu/libs/js/tools/branch-name.js +44 -0
- package/mevebu/libs/js/tools/break-even.js +68 -0
- package/mevebu/libs/js/tools/business-days.js +120 -0
- package/mevebu/libs/js/tools/canonical-tag.js +63 -0
- package/mevebu/libs/js/tools/caption-counter.js +66 -0
- package/mevebu/libs/js/tools/case-inspector.js +114 -0
- package/mevebu/libs/js/tools/cash-denomination.js +74 -0
- package/mevebu/libs/js/tools/cash-flow.js +124 -0
- package/mevebu/libs/js/tools/cert-expiry.js +83 -0
- package/mevebu/libs/js/tools/changelog-generator.js +48 -0
- package/mevebu/libs/js/tools/character-counter.js +80 -0
- package/mevebu/libs/js/tools/checklist.js +86 -0
- package/mevebu/libs/js/tools/citation-metadata.js +89 -0
- package/mevebu/libs/js/tools/citation-number-check.js +107 -0
- package/mevebu/libs/js/tools/color-palette.js +110 -0
- package/mevebu/libs/js/tools/color-picker.js +93 -0
- package/mevebu/libs/js/tools/commit-message.js +62 -0
- package/mevebu/libs/js/tools/content-calendar.js +111 -0
- package/mevebu/libs/js/tools/contrast-checker.js +93 -0
- package/mevebu/libs/js/tools/countdown-timer.js +174 -0
- package/mevebu/libs/js/tools/cron-builder.js +91 -0
- package/mevebu/libs/js/tools/css-clamp.js +66 -0
- package/mevebu/libs/js/tools/css-formatter.js +84 -0
- package/mevebu/libs/js/tools/css-grid.js +75 -0
- package/mevebu/libs/js/tools/css-minifier.js +98 -0
- package/mevebu/libs/js/tools/csv-to-json.js +114 -0
- package/mevebu/libs/js/tools/csv-viewer.js +132 -0
- package/mevebu/libs/js/tools/currency-format.js +69 -0
- package/mevebu/libs/js/tools/daily-planner.js +124 -0
- package/mevebu/libs/js/tools/data-masker.js +71 -0
- package/mevebu/libs/js/tools/data-storage.js +89 -0
- package/mevebu/libs/js/tools/data-validator.js +111 -0
- package/mevebu/libs/js/tools/date-difference.js +100 -0
- package/mevebu/libs/js/tools/date-format.js +107 -0
- package/mevebu/libs/js/tools/delimiter-converter.js +109 -0
- package/mevebu/libs/js/tools/discount-calculator.js +83 -0
- package/mevebu/libs/js/tools/doi-resolver.js +67 -0
- package/mevebu/libs/js/tools/env-formatter.js +72 -0
- package/mevebu/libs/js/tools/equation-formatter.js +71 -0
- package/mevebu/libs/js/tools/expense-split.js +90 -0
- package/mevebu/libs/js/tools/favicon-markup.js +53 -0
- package/mevebu/libs/js/tools/find-replace.js +88 -0
- package/mevebu/libs/js/tools/flashcard-creator.js +117 -0
- package/mevebu/libs/js/tools/font-pairing.js +88 -0
- package/mevebu/libs/js/tools/fraction-calculator.js +130 -0
- package/mevebu/libs/js/tools/git-command-builder.js +100 -0
- package/mevebu/libs/js/tools/gitignore-generator.js +60 -0
- package/mevebu/libs/js/tools/gpa-calculator.js +100 -0
- package/mevebu/libs/js/tools/grade-calculator.js +101 -0
- package/mevebu/libs/js/tools/gradient-generator.js +99 -0
- package/mevebu/libs/js/tools/habit-tracker.js +120 -0
- package/mevebu/libs/js/tools/harvard-citation.js +90 -0
- package/mevebu/libs/js/tools/hashtag-formatter.js +62 -0
- package/mevebu/libs/js/tools/heading-formatter.js +61 -0
- package/mevebu/libs/js/tools/hmac-generator.js +63 -0
- package/mevebu/libs/js/tools/hpp-calculator.js +70 -0
- package/mevebu/libs/js/tools/html-entity.js +49 -0
- package/mevebu/libs/js/tools/html-formatter.js +72 -0
- package/mevebu/libs/js/tools/html-to-text.js +47 -0
- package/mevebu/libs/js/tools/http-status.js +63 -0
- package/mevebu/libs/js/tools/ieee-reference.js +102 -0
- package/mevebu/libs/js/tools/intext-citation.js +75 -0
- package/mevebu/libs/js/tools/inventory-quantity.js +103 -0
- package/mevebu/libs/js/tools/invoice-number.js +64 -0
- package/mevebu/libs/js/tools/json-escape.js +76 -0
- package/mevebu/libs/js/tools/json-formatter.js +51 -0
- package/mevebu/libs/js/tools/json-minifier.js +58 -0
- package/mevebu/libs/js/tools/json-to-csv.js +72 -0
- package/mevebu/libs/js/tools/json-to-typescript.js +102 -0
- package/mevebu/libs/js/tools/json-validator.js +49 -0
- package/mevebu/libs/js/tools/jwt-decoder.js +74 -0
- package/mevebu/libs/js/tools/jwt-expiry.js +97 -0
- package/mevebu/libs/js/tools/length-converter.js +86 -0
- package/mevebu/libs/js/tools/license-reference.js +58 -0
- package/mevebu/libs/js/tools/line-break-formatter.js +48 -0
- package/mevebu/libs/js/tools/line-counter.js +55 -0
- package/mevebu/libs/js/tools/list-difference.js +86 -0
- package/mevebu/libs/js/tools/list-merger.js +67 -0
- package/mevebu/libs/js/tools/locale-number.js +79 -0
- package/mevebu/libs/js/tools/markdown-preview.js +143 -0
- package/mevebu/libs/js/tools/markdown-table.js +111 -0
- package/mevebu/libs/js/tools/markdown-to-text.js +48 -0
- package/mevebu/libs/js/tools/markup-calculator.js +69 -0
- package/mevebu/libs/js/tools/meeting-agenda.js +137 -0
- package/mevebu/libs/js/tools/meta-preview.js +51 -0
- package/mevebu/libs/js/tools/meta-tags.js +62 -0
- package/mevebu/libs/js/tools/mime-types.js +54 -0
- package/mevebu/libs/js/tools/mla-citation.js +91 -0
- package/mevebu/libs/js/tools/number-base-converter.js +97 -0
- package/mevebu/libs/js/tools/open-graph.js +62 -0
- package/mevebu/libs/js/tools/password-strength.js +118 -0
- package/mevebu/libs/js/tools/percent-to-fraction.js +96 -0
- package/mevebu/libs/js/tools/percentage-calculator.js +86 -0
- package/mevebu/libs/js/tools/percentage-change.js +70 -0
- package/mevebu/libs/js/tools/permutation-combination.js +106 -0
- package/mevebu/libs/js/tools/prefix-suffix.js +46 -0
- package/mevebu/libs/js/tools/priority-matrix.js +100 -0
- package/mevebu/libs/js/tools/profit-margin.js +69 -0
- package/mevebu/libs/js/tools/quadratic-solver.js +93 -0
- package/mevebu/libs/js/tools/query-builder.js +71 -0
- package/mevebu/libs/js/tools/quiz-formatter.js +70 -0
- package/mevebu/libs/js/tools/quotation-formatter.js +77 -0
- package/mevebu/libs/js/tools/ratio-calculator.js +103 -0
- package/mevebu/libs/js/tools/readability.js +112 -0
- package/mevebu/libs/js/tools/reading-time.js +76 -0
- package/mevebu/libs/js/tools/readme-generator.js +50 -0
- package/mevebu/libs/js/tools/reference-completeness.js +62 -0
- package/mevebu/libs/js/tools/regex-tester.js +86 -0
- package/mevebu/libs/js/tools/remove-duplicate-lines.js +46 -0
- package/mevebu/libs/js/tools/remove-extra-spaces.js +41 -0
- package/mevebu/libs/js/tools/robots-txt.js +60 -0
- package/mevebu/libs/js/tools/roman-numeral-converter.js +106 -0
- package/mevebu/libs/js/tools/sales-commission.js +96 -0
- package/mevebu/libs/js/tools/scientific-calculator.js +247 -0
- package/mevebu/libs/js/tools/scientific-notation.js +83 -0
- package/mevebu/libs/js/tools/script-planner.js +72 -0
- package/mevebu/libs/js/tools/secure-token.js +60 -0
- package/mevebu/libs/js/tools/selling-price.js +74 -0
- package/mevebu/libs/js/tools/semver-helper.js +54 -0
- package/mevebu/libs/js/tools/sentence-length.js +80 -0
- package/mevebu/libs/js/tools/sequence-analyzer.js +90 -0
- package/mevebu/libs/js/tools/sitemap-xml.js +82 -0
- package/mevebu/libs/js/tools/slug-converter.js +48 -0
- package/mevebu/libs/js/tools/slug-preview.js +83 -0
- package/mevebu/libs/js/tools/sort-lines.js +54 -0
- package/mevebu/libs/js/tools/speed-converter.js +83 -0
- package/mevebu/libs/js/tools/sql-formatter.js +78 -0
- package/mevebu/libs/js/tools/sql-minifier.js +102 -0
- package/mevebu/libs/js/tools/standard-deviation.js +96 -0
- package/mevebu/libs/js/tools/stopwatch.js +133 -0
- package/mevebu/libs/js/tools/study-schedule.js +88 -0
- package/mevebu/libs/js/tools/subtitle-formatter.js +74 -0
- package/mevebu/libs/js/tools/table-to-markdown.js +137 -0
- package/mevebu/libs/js/tools/tax-calculator.js +84 -0
- package/mevebu/libs/js/tools/temperature-converter.js +94 -0
- package/mevebu/libs/js/tools/text-diff.js +104 -0
- package/mevebu/libs/js/tools/text-direction.js +55 -0
- package/mevebu/libs/js/tools/text-reverser.js +51 -0
- package/mevebu/libs/js/tools/time-duration.js +83 -0
- package/mevebu/libs/js/tools/time-format-converter.js +86 -0
- package/mevebu/libs/js/tools/timestamp-converter.js +85 -0
- package/mevebu/libs/js/tools/timezone-converter.js +163 -0
- package/mevebu/libs/js/tools/title-length.js +67 -0
- package/mevebu/libs/js/tools/unicode-escape.js +80 -0
- package/mevebu/libs/js/tools/unicode-inspector.js +58 -0
- package/mevebu/libs/js/tools/url-inspector.js +98 -0
- package/mevebu/libs/js/tools/url-parser.js +83 -0
- package/mevebu/libs/js/tools/utf8-bytes.js +67 -0
- package/mevebu/libs/js/tools/version-compare.js +70 -0
- package/mevebu/libs/js/tools/video-duration.js +72 -0
- package/mevebu/libs/js/tools/volume-converter.js +88 -0
- package/mevebu/libs/js/tools/weekly-planner.js +101 -0
- package/mevebu/libs/js/tools/weight-converter.js +85 -0
- package/mevebu/libs/js/tools/weighted-average.js +86 -0
- package/mevebu/libs/js/tools/word-counter.js +90 -0
- package/mevebu/libs/js/tools/word-limit.js +60 -0
- package/mevebu/libs/js/tools/xml-formatter.js +85 -0
- package/mevebu/libs/js/tools/yaml-formatter.js +71 -0
- package/package.json +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Breaks a URL into its protocol, host, port, path, hash and query parameters.
|
|
2
|
+
import { el, bench, stack, panel, textInput, notice, copyButton, actions, toolcard } from "../core.js";
|
|
3
|
+
|
|
4
|
+
const kvRow = (key, value) =>
|
|
5
|
+
el("div", { class: "mvb-kv__row" }, el("span", { class: "mvb-kv__k" }, key), el("span", { class: "mvb-kv__v" }, value));
|
|
6
|
+
|
|
7
|
+
export function mount(slot) {
|
|
8
|
+
const source = textInput({ value: "https://example.com:8443/blog/post?utm_source=news&page=2#top", "aria-label": "URL" });
|
|
9
|
+
const error = notice("error");
|
|
10
|
+
|
|
11
|
+
const protocol = el("span", { class: "mvb-kv__v" }, "-");
|
|
12
|
+
const host = el("span", { class: "mvb-kv__v" }, "-");
|
|
13
|
+
const port = el("span", { class: "mvb-kv__v" }, "-");
|
|
14
|
+
const path = el("span", { class: "mvb-kv__v" }, "-");
|
|
15
|
+
const hash = el("span", { class: "mvb-kv__v" }, "-");
|
|
16
|
+
const kv = el(
|
|
17
|
+
"div",
|
|
18
|
+
{ class: "mvb-kv" },
|
|
19
|
+
kvRow("Protocol", protocol),
|
|
20
|
+
kvRow("Host", host),
|
|
21
|
+
kvRow("Port", port),
|
|
22
|
+
kvRow("Path", path),
|
|
23
|
+
kvRow("Hash", hash)
|
|
24
|
+
);
|
|
25
|
+
const tableWrap = el("div", { class: "mvb-scroll" });
|
|
26
|
+
let paramsText = "";
|
|
27
|
+
|
|
28
|
+
function clear() {
|
|
29
|
+
for (const node of [protocol, host, port, path, hash]) node.textContent = "-";
|
|
30
|
+
tableWrap.replaceChildren(el("p", { class: "mvb-note" }, "No query parameters."));
|
|
31
|
+
paramsText = "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function run() {
|
|
35
|
+
error.write("");
|
|
36
|
+
const raw = source.value.trim();
|
|
37
|
+
if (!raw) return clear();
|
|
38
|
+
let url;
|
|
39
|
+
try {
|
|
40
|
+
url = new URL(raw);
|
|
41
|
+
} catch {
|
|
42
|
+
error.write("That is not a valid URL.");
|
|
43
|
+
return clear();
|
|
44
|
+
}
|
|
45
|
+
protocol.textContent = url.protocol.replace(":", "");
|
|
46
|
+
host.textContent = url.hostname;
|
|
47
|
+
port.textContent = url.port || "default";
|
|
48
|
+
path.textContent = url.pathname;
|
|
49
|
+
hash.textContent = url.hash || "-";
|
|
50
|
+
const params = [...url.searchParams];
|
|
51
|
+
if (!params.length) {
|
|
52
|
+
tableWrap.replaceChildren(el("p", { class: "mvb-note" }, "No query parameters."));
|
|
53
|
+
paramsText = "";
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
tableWrap.replaceChildren(el(
|
|
57
|
+
"table",
|
|
58
|
+
{ class: "mvb-ttable" },
|
|
59
|
+
el("thead", {}, el("tr", {}, el("th", {}, "Key"), el("th", {}, "Value"))),
|
|
60
|
+
el("tbody", {}, ...params.map(([key, value]) => el("tr", {}, el("td", {}, key), el("td", {}, value))))
|
|
61
|
+
));
|
|
62
|
+
paramsText = JSON.stringify(Object.fromEntries(params), null, 2);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
source.addEventListener("input", run);
|
|
66
|
+
|
|
67
|
+
const card = toolcard({
|
|
68
|
+
body: bench(
|
|
69
|
+
stack(panel("URL", source), error),
|
|
70
|
+
stack(kv, panel("Query parameters", tableWrap))
|
|
71
|
+
),
|
|
72
|
+
foot: actions(
|
|
73
|
+
copyButton(() => source.value.trim(), "Copy URL"),
|
|
74
|
+
copyButton(() => paramsText, "Copy params", "fill")
|
|
75
|
+
),
|
|
76
|
+
});
|
|
77
|
+
slot.append(card.node);
|
|
78
|
+
run();
|
|
79
|
+
|
|
80
|
+
return {};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default { mount };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Measures text as characters, code points, UTF-8 bytes and UTF-16 units.
|
|
2
|
+
import { el, bench, stack, panel, textArea, copyButton, actions, toolcard } from "../core.js";
|
|
3
|
+
|
|
4
|
+
function tile(label) {
|
|
5
|
+
const value = el("span", { class: "mvb-stat__v" }, "0");
|
|
6
|
+
const node = el("div", { class: "mvb-stat" }, value, el("span", { class: "mvb-stat__k" }, label));
|
|
7
|
+
node.set = (text) => { value.textContent = text; };
|
|
8
|
+
return node;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function graphemes(text) {
|
|
12
|
+
if (text && typeof Intl !== "undefined" && Intl.Segmenter) {
|
|
13
|
+
return [...new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(text)].length;
|
|
14
|
+
}
|
|
15
|
+
return Array.from(text).length;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function mount(slot) {
|
|
19
|
+
const source = textArea({ rows: 10, value: "mevebu", "aria-label": "Text" });
|
|
20
|
+
|
|
21
|
+
const characterTile = tile("Characters");
|
|
22
|
+
const pointTile = tile("Code points");
|
|
23
|
+
const byteTile = tile("UTF-8 bytes");
|
|
24
|
+
const unitTile = tile("UTF-16 units");
|
|
25
|
+
const stats = el("div", { class: "mvb-stats" }, characterTile, pointTile, byteTile, unitTile);
|
|
26
|
+
|
|
27
|
+
let counts = { characters: 0, points: 0, bytes: 0, units: 0 };
|
|
28
|
+
|
|
29
|
+
function summary() {
|
|
30
|
+
return [
|
|
31
|
+
`${counts.characters} characters`,
|
|
32
|
+
`${counts.points} code points`,
|
|
33
|
+
`${counts.bytes} UTF-8 bytes`,
|
|
34
|
+
`${counts.units} UTF-16 units`,
|
|
35
|
+
].join("\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function run() {
|
|
39
|
+
const text = source.value;
|
|
40
|
+
counts = {
|
|
41
|
+
characters: graphemes(text),
|
|
42
|
+
points: Array.from(text).length,
|
|
43
|
+
bytes: new TextEncoder().encode(text).length,
|
|
44
|
+
units: text.length,
|
|
45
|
+
};
|
|
46
|
+
characterTile.set(String(counts.characters));
|
|
47
|
+
pointTile.set(String(counts.points));
|
|
48
|
+
byteTile.set(String(counts.bytes));
|
|
49
|
+
unitTile.set(String(counts.units));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
source.addEventListener("input", run);
|
|
53
|
+
|
|
54
|
+
const card = toolcard({
|
|
55
|
+
body: bench(
|
|
56
|
+
stack(panel("Text", source)),
|
|
57
|
+
stack(stats)
|
|
58
|
+
),
|
|
59
|
+
foot: actions(copyButton(summary, "Copy counts", "fill")),
|
|
60
|
+
});
|
|
61
|
+
slot.append(card.node);
|
|
62
|
+
run();
|
|
63
|
+
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default { mount };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Compares two versions and says which is newer and where they differ.
|
|
2
|
+
import { bench, stack, panel, textInput, notice, output, copyButton, actions, toolcard } from "../core.js";
|
|
3
|
+
|
|
4
|
+
function parse(text) {
|
|
5
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(text.trim());
|
|
6
|
+
if (!match) return null;
|
|
7
|
+
return { core: match.slice(1, 4).map(Number), pre: match[4] || "" };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function mount(slot) {
|
|
11
|
+
const left = textInput({ value: "1.2.0", class: "mvb-in mvb-in--mono", "aria-label": "First version" });
|
|
12
|
+
const right = textInput({ value: "1.1.5", class: "mvb-in mvb-in--mono", "aria-label": "Second version" });
|
|
13
|
+
const error = notice("error");
|
|
14
|
+
const out = output({ rows: 8 });
|
|
15
|
+
let text = "";
|
|
16
|
+
|
|
17
|
+
function run() {
|
|
18
|
+
error.write("");
|
|
19
|
+
const a = parse(left.value);
|
|
20
|
+
const b = parse(right.value);
|
|
21
|
+
if (!a || !b) {
|
|
22
|
+
text = "";
|
|
23
|
+
out.write("");
|
|
24
|
+
error.write("Enter two versions like 1.2.3.");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const names = ["major", "minor", "patch"];
|
|
28
|
+
const lines = [];
|
|
29
|
+
let verdict = "equal";
|
|
30
|
+
for (let i = 0; i < 3; i++) {
|
|
31
|
+
const sign = a.core[i] === b.core[i] ? "=" : a.core[i] > b.core[i] ? ">" : "<";
|
|
32
|
+
if (sign !== "=" && verdict === "equal") verdict = sign === ">" ? "a" : "b";
|
|
33
|
+
lines.push(`${names[i]} ${a.core[i]} ${sign} ${b.core[i]}`);
|
|
34
|
+
}
|
|
35
|
+
if (a.core.every((part, i) => part === b.core[i])) {
|
|
36
|
+
if (a.pre === b.pre) verdict = "equal";
|
|
37
|
+
else if (!a.pre) verdict = "a";
|
|
38
|
+
else if (!b.pre) verdict = "b";
|
|
39
|
+
else verdict = a.pre > b.pre ? "a" : "b";
|
|
40
|
+
lines.push(`prerelease ${a.pre || "none"} ${a.pre === b.pre ? "=" : a.pre > b.pre ? ">" : "<"} ${b.pre || "none"}`);
|
|
41
|
+
}
|
|
42
|
+
const av = left.value.trim();
|
|
43
|
+
const bv = right.value.trim();
|
|
44
|
+
const head =
|
|
45
|
+
verdict === "equal"
|
|
46
|
+
? `${av} and ${bv} are the same version.`
|
|
47
|
+
: verdict === "a"
|
|
48
|
+
? `${av} is newer than ${bv}.`
|
|
49
|
+
: `${bv} is newer than ${av}.`;
|
|
50
|
+
text = `${[head, "", ...lines].join("\n")}\n`;
|
|
51
|
+
out.write(text);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
left.addEventListener("input", run);
|
|
55
|
+
right.addEventListener("input", run);
|
|
56
|
+
|
|
57
|
+
const card = toolcard({
|
|
58
|
+
body: bench(
|
|
59
|
+
stack(panel("First version", left), panel("Second version", right), error),
|
|
60
|
+
stack(panel("Comparison", out))
|
|
61
|
+
),
|
|
62
|
+
foot: actions(copyButton(() => text, "Copy comparison", "fill")),
|
|
63
|
+
});
|
|
64
|
+
slot.append(card.node);
|
|
65
|
+
run();
|
|
66
|
+
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default { mount };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Estimated video length from a script at a speaking rate, plus pauses.
|
|
2
|
+
import { el, bench, stack, panel, textArea, numberInput, notice, copyButton, actions, toolcard } from "../core.js";
|
|
3
|
+
|
|
4
|
+
const clock = (seconds) => {
|
|
5
|
+
const total = Math.max(0, Math.round(seconds));
|
|
6
|
+
return `${Math.floor(total / 60)}:${String(total % 60).padStart(2, "0")}`;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function tile(label) {
|
|
10
|
+
const value = el("span", { class: "mvb-stat__v" }, "0:00");
|
|
11
|
+
const node = el("div", { class: "mvb-stat" }, value, el("span", { class: "mvb-stat__k" }, label));
|
|
12
|
+
node.set = (text) => { value.textContent = text; };
|
|
13
|
+
return node;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function mount(slot) {
|
|
17
|
+
const source = textArea({ rows: 10, "aria-label": "Script", placeholder: "Type or paste the script" });
|
|
18
|
+
const wordsOverride = numberInput({ min: "0", placeholder: "From the script", "aria-label": "Word count" });
|
|
19
|
+
const rate = numberInput({ min: "60", max: "400", value: "150", "aria-label": "Speaking rate in words per minute" });
|
|
20
|
+
const pause = numberInput({ min: "0", max: "60", value: "8", "aria-label": "Pause seconds per minute" });
|
|
21
|
+
|
|
22
|
+
const wordsTile = tile("Words");
|
|
23
|
+
const speakTile = tile("Speaking");
|
|
24
|
+
const totalTile = tile("With pauses");
|
|
25
|
+
const stats = el("div", { class: "mvb-stats" }, wordsTile, speakTile, totalTile);
|
|
26
|
+
const note = notice("note");
|
|
27
|
+
note.write("Speaking rates vary by person and script.");
|
|
28
|
+
|
|
29
|
+
let summary = "";
|
|
30
|
+
|
|
31
|
+
function run() {
|
|
32
|
+
const typed = parseInt(wordsOverride.value, 10) || 0;
|
|
33
|
+
const words = typed > 0 ? typed : (source.value.trim() ? source.value.trim().split(/\s+/).length : 0);
|
|
34
|
+
const wpm = Math.max(1, parseInt(rate.value, 10) || 150);
|
|
35
|
+
const pauseSeconds = Math.max(0, parseInt(pause.value, 10) || 0);
|
|
36
|
+
const speaking = (words / wpm) * 60;
|
|
37
|
+
const total = speaking + (speaking / 60) * pauseSeconds;
|
|
38
|
+
|
|
39
|
+
wordsTile.set(String(words));
|
|
40
|
+
speakTile.set(clock(speaking));
|
|
41
|
+
totalTile.set(clock(total));
|
|
42
|
+
summary = [
|
|
43
|
+
`${words} words`,
|
|
44
|
+
`Speaking ${clock(speaking)} at ${wpm} words per minute`,
|
|
45
|
+
`Total ${clock(total)} with ${pauseSeconds} seconds of pause per minute`,
|
|
46
|
+
].join("\n");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
source.addEventListener("input", run);
|
|
50
|
+
wordsOverride.addEventListener("input", run);
|
|
51
|
+
rate.addEventListener("input", run);
|
|
52
|
+
pause.addEventListener("input", run);
|
|
53
|
+
|
|
54
|
+
const card = toolcard({
|
|
55
|
+
body: bench(
|
|
56
|
+
stack(
|
|
57
|
+
panel("Script", source),
|
|
58
|
+
panel("Word count", wordsOverride, "Overrides the script"),
|
|
59
|
+
panel("Speaking rate", rate, "Words per minute"),
|
|
60
|
+
panel("Pauses", pause, "Seconds per minute")
|
|
61
|
+
),
|
|
62
|
+
stack(stats, note)
|
|
63
|
+
),
|
|
64
|
+
foot: actions(copyButton(() => summary, "Copy estimate", "fill")),
|
|
65
|
+
});
|
|
66
|
+
slot.append(card.node);
|
|
67
|
+
run();
|
|
68
|
+
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default { mount };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Converts a volume between metric and US units, with the factor used.
|
|
2
|
+
import { el, bench, stack, panel, numberInput, selectInput, notice, copyButton, button, actions, toolcard } from "../core.js";
|
|
3
|
+
|
|
4
|
+
const UNITS = [
|
|
5
|
+
{ value: "ml", label: "Milliliter", factor: 0.001 },
|
|
6
|
+
{ value: "l", label: "Liter", factor: 1 },
|
|
7
|
+
{ value: "m3", label: "Cubic meter", factor: 1000 },
|
|
8
|
+
{ value: "tsp", label: "Teaspoon", factor: 0.00492892159375 },
|
|
9
|
+
{ value: "tbsp", label: "Tablespoon", factor: 0.01478676478125 },
|
|
10
|
+
{ value: "floz", label: "Fluid ounce", factor: 0.0295735295625 },
|
|
11
|
+
{ value: "cup", label: "Cup", factor: 0.2365882365 },
|
|
12
|
+
{ value: "pt", label: "Pint", factor: 0.473176473 },
|
|
13
|
+
{ value: "qt", label: "Quart", factor: 0.946352946 },
|
|
14
|
+
{ value: "gal", label: "Gallon", factor: 3.785411784 },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const byId = Object.fromEntries(UNITS.map((unit) => [unit.value, unit]));
|
|
18
|
+
const options = UNITS.map(({ value, label }) => ({ value, label }));
|
|
19
|
+
|
|
20
|
+
function fmt(value) {
|
|
21
|
+
if (!Number.isFinite(value)) return "-";
|
|
22
|
+
if (value === 0) return "0";
|
|
23
|
+
const abs = Math.abs(value);
|
|
24
|
+
if (abs >= 1e-6 && abs < 1e15) return String(Number(value.toPrecision(8)));
|
|
25
|
+
return value.toExponential(4);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function mount(slot) {
|
|
29
|
+
const value = numberInput({ value: "1", step: "any", "aria-label": "Value" });
|
|
30
|
+
const from = selectInput(options, { value: "l", "aria-label": "From unit" });
|
|
31
|
+
const to = selectInput(options, { value: "gal", "aria-label": "To unit" });
|
|
32
|
+
const result = el("span", { class: "mvb-stat__v" }, "-");
|
|
33
|
+
const stats = el("div", { class: "mvb-stats" }, el("div", { class: "mvb-stat" }, el("span", { class: "mvb-stat__k" }, "Result"), result));
|
|
34
|
+
const factor = el("p", { class: "mvb-note" });
|
|
35
|
+
const error = notice("error");
|
|
36
|
+
const scroll = el("div", { class: "mvb-scroll" });
|
|
37
|
+
let text = "";
|
|
38
|
+
|
|
39
|
+
function run() {
|
|
40
|
+
error.write("");
|
|
41
|
+
const raw = value.value.trim();
|
|
42
|
+
const n = Number(raw);
|
|
43
|
+
if (!raw || !Number.isFinite(n)) {
|
|
44
|
+
result.textContent = "-";
|
|
45
|
+
factor.textContent = "";
|
|
46
|
+
scroll.replaceChildren();
|
|
47
|
+
text = "";
|
|
48
|
+
error.write("Enter a number.");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const a = byId[from.value];
|
|
52
|
+
const b = byId[to.value];
|
|
53
|
+
const out = (n * a.factor) / b.factor;
|
|
54
|
+
result.textContent = `${fmt(out)} ${b.value}`;
|
|
55
|
+
factor.textContent = `1 ${a.value} = ${fmt(a.factor / b.factor)} ${b.value}`;
|
|
56
|
+
scroll.replaceChildren(el("table", { class: "mvb-ttable" },
|
|
57
|
+
el("thead", {}, el("tr", {}, el("th", {}, "Unit"), el("th", {}, "Value"))),
|
|
58
|
+
el("tbody", {}, ...UNITS.map((unit) => el("tr", {},
|
|
59
|
+
el("td", {}, unit.label),
|
|
60
|
+
el("td", {}, fmt((n * a.factor) / unit.factor)))))));
|
|
61
|
+
text = `${fmt(out)} ${b.value}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
value.addEventListener("input", run);
|
|
65
|
+
from.addEventListener("change", run);
|
|
66
|
+
to.addEventListener("change", run);
|
|
67
|
+
|
|
68
|
+
const swap = button("Swap", { icon: "arrow-left-right", onclick: () => {
|
|
69
|
+
const held = from.value;
|
|
70
|
+
from.value = to.value;
|
|
71
|
+
to.value = held;
|
|
72
|
+
run();
|
|
73
|
+
}});
|
|
74
|
+
|
|
75
|
+
const card = toolcard({
|
|
76
|
+
body: bench(
|
|
77
|
+
stack(panel("Value", value), panel("From", from), panel("To", to), error),
|
|
78
|
+
stack(panel("Result", stats), factor, panel("All units", scroll))
|
|
79
|
+
),
|
|
80
|
+
foot: actions(swap, copyButton(() => text, "Copy result", "fill")),
|
|
81
|
+
});
|
|
82
|
+
slot.append(card.node);
|
|
83
|
+
run();
|
|
84
|
+
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default { mount };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Plans seven days at once: pick a day, add targets, tick them off.
|
|
2
|
+
import { el, toast, blank, bench, stack, panel, textInput, checkbox, button, chipRow, copyButton, actions, toolcard, readPin, writePin } from "../core.js";
|
|
3
|
+
|
|
4
|
+
const uid = () => Date.now().toString(36) + Math.random().toString(36).slice(2, 7);
|
|
5
|
+
const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
6
|
+
|
|
7
|
+
export function mount(slot) {
|
|
8
|
+
const PIN = "weekly-planner";
|
|
9
|
+
const saved = readPin(PIN, { day: 0, days: [] });
|
|
10
|
+
const days = Array.from({ length: 7 }, (_, i) => (Array.isArray(saved.days?.[i]) ? saved.days[i] : []));
|
|
11
|
+
let day = Number.isInteger(saved.day) && saved.day >= 0 && saved.day < 7 ? saved.day : 0;
|
|
12
|
+
|
|
13
|
+
const entry = textInput({ placeholder: "New item", "aria-label": "New item" });
|
|
14
|
+
const listWrap = el("div", { class: "mvb-scroll" });
|
|
15
|
+
const stats = el("div", { class: "mvb-stats" });
|
|
16
|
+
|
|
17
|
+
const dayChips = chipRow(DAYS.map((label, value) => ({ value, label })), {
|
|
18
|
+
value: day,
|
|
19
|
+
label: "Day",
|
|
20
|
+
onChange: (next) => {
|
|
21
|
+
day = next;
|
|
22
|
+
save();
|
|
23
|
+
render();
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const addButton = button("Add", { icon: "plus", kind: "fill" });
|
|
28
|
+
const copyWeek = copyButton(() => weekText(), "Copy week");
|
|
29
|
+
|
|
30
|
+
const tile = (key, value) => el("div", { class: "mvb-stat" },
|
|
31
|
+
el("span", { class: "mvb-stat__v" }, String(value)),
|
|
32
|
+
el("span", { class: "mvb-stat__k" }, key));
|
|
33
|
+
|
|
34
|
+
const save = () => writePin(PIN, { day, days });
|
|
35
|
+
|
|
36
|
+
function weekText() {
|
|
37
|
+
return DAYS.map((label, i) => {
|
|
38
|
+
const lines = days[i].map((item) => `${item.done ? "[x]" : "[ ]"} ${item.text}`);
|
|
39
|
+
return lines.length ? `${label}\n${lines.join("\n")}` : label;
|
|
40
|
+
}).join("\n\n");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function itemNode(item) {
|
|
44
|
+
const box = checkbox(item.text, { checked: item.done });
|
|
45
|
+
box.input.addEventListener("change", () => {
|
|
46
|
+
item.done = box.input.checked;
|
|
47
|
+
save();
|
|
48
|
+
renderStats();
|
|
49
|
+
});
|
|
50
|
+
const remove = button("", { icon: "trash", "aria-label": `Remove ${item.text}` });
|
|
51
|
+
remove.addEventListener("click", () => {
|
|
52
|
+
days[day] = days[day].filter((entry) => entry.id !== item.id);
|
|
53
|
+
save();
|
|
54
|
+
render();
|
|
55
|
+
});
|
|
56
|
+
return el("div", { class: "mvb-row" }, box, remove);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function renderStats() {
|
|
60
|
+
const todayDone = days[day].filter((item) => item.done).length;
|
|
61
|
+
const weekDone = days.reduce((sum, list) => sum + list.filter((item) => item.done).length, 0);
|
|
62
|
+
stats.replaceChildren(tile("Done today", todayDone), tile("Done this week", weekDone));
|
|
63
|
+
copyWeek.disabled = days.every((list) => list.length === 0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function render() {
|
|
67
|
+
listWrap.replaceChildren(
|
|
68
|
+
days[day].length ? stack(...days[day].map(itemNode)) : blank("calendar-range", `Nothing on ${DAYS[day]}.`)
|
|
69
|
+
);
|
|
70
|
+
renderStats();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function add() {
|
|
74
|
+
const text = entry.value.trim();
|
|
75
|
+
if (!text) return toast("Type an item first");
|
|
76
|
+
days[day].push({ id: uid(), text, done: false });
|
|
77
|
+
entry.value = "";
|
|
78
|
+
entry.focus();
|
|
79
|
+
save();
|
|
80
|
+
render();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
entry.addEventListener("keydown", (event) => {
|
|
84
|
+
if (event.key === "Enter") add();
|
|
85
|
+
});
|
|
86
|
+
addButton.addEventListener("click", add);
|
|
87
|
+
|
|
88
|
+
const card = toolcard({
|
|
89
|
+
body: bench(
|
|
90
|
+
stack(panel("Day", dayChips), panel("New item", entry)),
|
|
91
|
+
stack(stats, panel("Items", listWrap))
|
|
92
|
+
),
|
|
93
|
+
foot: actions(copyWeek, addButton),
|
|
94
|
+
});
|
|
95
|
+
slot.append(card.node);
|
|
96
|
+
render();
|
|
97
|
+
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default { mount };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Converts a mass between metric and imperial units, with the factor used.
|
|
2
|
+
import { el, bench, stack, panel, numberInput, selectInput, notice, copyButton, button, actions, toolcard } from "../core.js";
|
|
3
|
+
|
|
4
|
+
const UNITS = [
|
|
5
|
+
{ value: "mg", label: "Milligram", factor: 0.001 },
|
|
6
|
+
{ value: "g", label: "Gram", factor: 1 },
|
|
7
|
+
{ value: "kg", label: "Kilogram", factor: 1000 },
|
|
8
|
+
{ value: "t", label: "Tonne", factor: 1000000 },
|
|
9
|
+
{ value: "oz", label: "Ounce", factor: 28.349523125 },
|
|
10
|
+
{ value: "lb", label: "Pound", factor: 453.59237 },
|
|
11
|
+
{ value: "st", label: "Stone", factor: 6350.29318 },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const byId = Object.fromEntries(UNITS.map((unit) => [unit.value, unit]));
|
|
15
|
+
const options = UNITS.map(({ value, label }) => ({ value, label }));
|
|
16
|
+
|
|
17
|
+
function fmt(value) {
|
|
18
|
+
if (!Number.isFinite(value)) return "-";
|
|
19
|
+
if (value === 0) return "0";
|
|
20
|
+
const abs = Math.abs(value);
|
|
21
|
+
if (abs >= 1e-6 && abs < 1e15) return String(Number(value.toPrecision(8)));
|
|
22
|
+
return value.toExponential(4);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function mount(slot) {
|
|
26
|
+
const value = numberInput({ value: "1", step: "any", "aria-label": "Value" });
|
|
27
|
+
const from = selectInput(options, { value: "kg", "aria-label": "From unit" });
|
|
28
|
+
const to = selectInput(options, { value: "lb", "aria-label": "To unit" });
|
|
29
|
+
const result = el("span", { class: "mvb-stat__v" }, "-");
|
|
30
|
+
const stats = el("div", { class: "mvb-stats" }, el("div", { class: "mvb-stat" }, el("span", { class: "mvb-stat__k" }, "Result"), result));
|
|
31
|
+
const factor = el("p", { class: "mvb-note" });
|
|
32
|
+
const error = notice("error");
|
|
33
|
+
const scroll = el("div", { class: "mvb-scroll" });
|
|
34
|
+
let text = "";
|
|
35
|
+
|
|
36
|
+
function run() {
|
|
37
|
+
error.write("");
|
|
38
|
+
const raw = value.value.trim();
|
|
39
|
+
const n = Number(raw);
|
|
40
|
+
if (!raw || !Number.isFinite(n)) {
|
|
41
|
+
result.textContent = "-";
|
|
42
|
+
factor.textContent = "";
|
|
43
|
+
scroll.replaceChildren();
|
|
44
|
+
text = "";
|
|
45
|
+
error.write("Enter a number.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const a = byId[from.value];
|
|
49
|
+
const b = byId[to.value];
|
|
50
|
+
const out = (n * a.factor) / b.factor;
|
|
51
|
+
result.textContent = `${fmt(out)} ${b.value}`;
|
|
52
|
+
factor.textContent = `1 ${a.value} = ${fmt(a.factor / b.factor)} ${b.value}`;
|
|
53
|
+
scroll.replaceChildren(el("table", { class: "mvb-ttable" },
|
|
54
|
+
el("thead", {}, el("tr", {}, el("th", {}, "Unit"), el("th", {}, "Value"))),
|
|
55
|
+
el("tbody", {}, ...UNITS.map((unit) => el("tr", {},
|
|
56
|
+
el("td", {}, unit.label),
|
|
57
|
+
el("td", {}, fmt((n * a.factor) / unit.factor)))))));
|
|
58
|
+
text = `${fmt(out)} ${b.value}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
value.addEventListener("input", run);
|
|
62
|
+
from.addEventListener("change", run);
|
|
63
|
+
to.addEventListener("change", run);
|
|
64
|
+
|
|
65
|
+
const swap = button("Swap", { icon: "arrow-left-right", onclick: () => {
|
|
66
|
+
const held = from.value;
|
|
67
|
+
from.value = to.value;
|
|
68
|
+
to.value = held;
|
|
69
|
+
run();
|
|
70
|
+
}});
|
|
71
|
+
|
|
72
|
+
const card = toolcard({
|
|
73
|
+
body: bench(
|
|
74
|
+
stack(panel("Value", value), panel("From", from), panel("To", to), error),
|
|
75
|
+
stack(panel("Result", stats), factor, panel("All units", scroll))
|
|
76
|
+
),
|
|
77
|
+
foot: actions(swap, copyButton(() => text, "Copy result", "fill")),
|
|
78
|
+
});
|
|
79
|
+
slot.append(card.node);
|
|
80
|
+
run();
|
|
81
|
+
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default { mount };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Weighted mean from paired value and weight lists.
|
|
2
|
+
import { el, bench, stack, panel, textArea, notice, copyButton, actions, toolcard } from "../core.js";
|
|
3
|
+
|
|
4
|
+
function fmt(n) {
|
|
5
|
+
return n.toLocaleString(undefined, { maximumFractionDigits: 6 });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function parseNumbers(text) {
|
|
9
|
+
const tokens = text.split(/[\s,;]+/).filter(Boolean);
|
|
10
|
+
const nums = tokens.map(Number);
|
|
11
|
+
return { nums, invalid: nums.some((n) => !Number.isFinite(n)) };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function tile(label) {
|
|
15
|
+
const value = el("span", { class: "mvb-stat__v" }, "-");
|
|
16
|
+
const node = el("div", { class: "mvb-stat" }, value, el("span", { class: "mvb-stat__k" }, label));
|
|
17
|
+
node.set = (text) => { value.textContent = text; };
|
|
18
|
+
return node;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function mount(slot) {
|
|
22
|
+
const values = textArea({ rows: 6, value: "85, 90, 70", "aria-label": "Values", placeholder: "85, 90, 70" });
|
|
23
|
+
const weights = textArea({ rows: 6, value: "2, 3, 1", "aria-label": "Weights", placeholder: "2, 3, 1" });
|
|
24
|
+
const weighted = tile("Weighted average");
|
|
25
|
+
const totalWeight = tile("Total weight");
|
|
26
|
+
const count = tile("Count");
|
|
27
|
+
const stats = el("div", { class: "mvb-stats" }, weighted, totalWeight, count);
|
|
28
|
+
const error = notice("error");
|
|
29
|
+
let text = "";
|
|
30
|
+
|
|
31
|
+
function clear() {
|
|
32
|
+
weighted.set("-");
|
|
33
|
+
totalWeight.set("-");
|
|
34
|
+
count.set("-");
|
|
35
|
+
text = "";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function run() {
|
|
39
|
+
error.write("");
|
|
40
|
+
const left = parseNumbers(values.value);
|
|
41
|
+
const right = parseNumbers(weights.value);
|
|
42
|
+
if (!left.nums.length || !right.nums.length) {
|
|
43
|
+
clear();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (left.invalid || right.invalid) {
|
|
47
|
+
clear();
|
|
48
|
+
error.write("Use numbers only, separated by spaces, commas or new lines.");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (left.nums.length !== right.nums.length) {
|
|
52
|
+
clear();
|
|
53
|
+
error.write(`There are ${left.nums.length} values and ${right.nums.length} weights.`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const weightSum = right.nums.reduce((acc, n) => acc + n, 0);
|
|
57
|
+
if (weightSum === 0) {
|
|
58
|
+
clear();
|
|
59
|
+
error.write("The weights add up to zero.");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const products = left.nums.reduce((acc, n, i) => acc + n * right.nums[i], 0);
|
|
63
|
+
const result = products / weightSum;
|
|
64
|
+
weighted.set(fmt(result));
|
|
65
|
+
totalWeight.set(fmt(weightSum));
|
|
66
|
+
count.set(String(left.nums.length));
|
|
67
|
+
text = `Weighted average ${fmt(result)}, total weight ${fmt(weightSum)}, values ${left.nums.length}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
values.addEventListener("input", run);
|
|
71
|
+
weights.addEventListener("input", run);
|
|
72
|
+
|
|
73
|
+
const card = toolcard({
|
|
74
|
+
body: bench(
|
|
75
|
+
stack(panel("Values", values), panel("Weights", weights), error),
|
|
76
|
+
stack(stats)
|
|
77
|
+
),
|
|
78
|
+
foot: actions(copyButton(() => text, "Copy summary", "fill")),
|
|
79
|
+
});
|
|
80
|
+
slot.append(card.node);
|
|
81
|
+
run();
|
|
82
|
+
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default { mount };
|