@vernikr/size-report 2.8.1 → 2.8.2
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 +33 -15
- package/package.json +2 -2
- package/src/page/app.css +3 -8
- package/src/page/app.js +23 -29
- package/src/page/panel.js +34 -45
- package/src/page/state.js +48 -63
- package/src/page/table.js +4 -4
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ shows.
|
|
|
10
10
|
|
|
11
11
|
## Status
|
|
12
12
|
|
|
13
|
-
**Release 2.8.
|
|
13
|
+
**Release 2.8.2 (2026-09-18).** The tool lives as a package of its own: the registry name is
|
|
14
14
|
`@vernikr/size-report` (published by tag from CI, with no secret). A project may keep no settings at
|
|
15
15
|
all: without a config file the tool derives them from the project itself and says so in one line,
|
|
16
16
|
and `--init` pins what was derived into a file. The report is **one file**, the self-contained page
|
|
@@ -19,6 +19,25 @@ is installed and on the first run. The version is in the manifest, and every rel
|
|
|
19
19
|
the journal — `worklog/` for today's entries, `worklog/archive/WORKLOG.md` for the earlier ones:
|
|
20
20
|
what changes in the numbers is measured rather than retold.
|
|
21
21
|
|
|
22
|
+
2.8.2 is about the page's own sources and the checks that guard them, and its figures are measured rather than retold.
|
|
23
|
+
**The four page suites that went quiet with the `<table>` are alive again against the window:** `test/page-view.test.js`,
|
|
24
|
+
`page-cols`, `page-tree` and `page-choice` read the grid and the panel they guard rather than markup that no longer
|
|
25
|
+
exists, and what they promise is what they hold — the pasted program with no second calculation, the sparse block, the
|
|
26
|
+
recount by the choice, the order of the columns, the tree's folds and its memory, the link's refusals. **Their readers
|
|
27
|
+
are no longer each suite's own:** `tools/page-harness.js` keeps one set of them (a file's path at HEAD, the rows the
|
|
28
|
+
window built, the numbers of a row and its captions, a folder's and a category's switch, the place the shell stands at),
|
|
29
|
+
and the copies the suites carried left with it. **And the page's chapters shed the duplication the same request
|
|
30
|
+
found:** the choice's record and the folded tree share one reading and one writing of the browser's memory
|
|
31
|
+
(`appLoad`/`appKeep`), a folder's two figures come from one walk of its subtree (`appSub`), the three walks over the
|
|
32
|
+
metrics and the files of a record became the panels' one question asked twice (`appReach`), and the three paths of a
|
|
33
|
+
click became one drawing (`appChanged`) — while two rules that never took effect went with them (a dead `.cap` of the
|
|
34
|
+
styling, an undefined colour under the folding sign). Measured in bytes for the same history: the chapters **−745 B**
|
|
35
|
+
(37 lines), the suites **−18 462 B** (251 lines), the harness **+4 328 B** — the tree loses **14 879 B and 220 lines**,
|
|
36
|
+
and the artifact **85 730 → 84 991 B**, its program and its styling being exactly what the chapters are. The checks go
|
|
37
|
+
89 → **86** in the fast profile and 194 → **191** in the full one: what a reader of the page does is one place now
|
|
38
|
+
rather than five, and a check that read the table it no longer has is not a check. The figures and the reasoning stand
|
|
39
|
+
in `worklog/0210-page-suites-on-the-window.md`.
|
|
40
|
+
|
|
22
41
|
2.8.1 is the drawing of the page put right after its table became a window, and its figures are measured rather than
|
|
23
42
|
retold. **The panel and the table were sharing one class:** the table's rows were styled by a bare `.row` while the
|
|
24
43
|
panel's own rows are `.panel .row`, so the table's rule took the metrics row and the three method lines out of the flow
|
|
@@ -49,11 +68,10 @@ stripe over a drawing is gone with the freeze it was drawn for**: a switch costs
|
|
|
49
68
|
for an indicator to indicate, and `src/page/work.js` left the tree with it. A virtualizer library was measured and not
|
|
50
69
|
taken — `@tanstack/virtual-core` is ~6.7 kB gzip and headless, `virtua`'s grid is experimental and `Clusterize.js`
|
|
51
70
|
knows rows and not columns — because each would be vendored into the artifact, which the report then measures as its own
|
|
52
|
-
bytes.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
fast profile and 186 → **193** in the full one.
|
|
71
|
+
bytes. The checks of the old table went quiet with it, each naming its reason, and 2.8.2 brought them back against the
|
|
72
|
+
window; `test/page-grid.test.js` is what guards the window itself. The figures and the reasons stand in
|
|
73
|
+
`worklog/0208-table-window.md`. The checks grow with the work: 81 → **88** in the fast profile and 186 → **193**
|
|
74
|
+
in the full one.
|
|
57
75
|
|
|
58
76
|
2.7.0 is about the page and what a reader does with it, and its figures are measured rather than retold.
|
|
59
77
|
**The report's address stays clean:** a switch writes the record into the browser's memory and nothing into the
|
|
@@ -253,8 +271,8 @@ commits and installs hooks; the reason for each expensive file is named line by
|
|
|
253
271
|
|
|
254
272
|
| Run | Command | Checks |
|
|
255
273
|
|---|---|---|
|
|
256
|
-
| Fast — every edit | `pnpm test` | **
|
|
257
|
-
| Full — release and CI | `pnpm test:all` | **
|
|
274
|
+
| Fast — every edit | `pnpm test` | **86 of 191** |
|
|
275
|
+
| Full — release and CI | `pnpm test:all` | **191** |
|
|
258
276
|
|
|
259
277
|
No check is lost or weakened: the full run starts every file, the fast one takes part of them. The default is the full run — a file becomes fast only explicitly and with a reason — so new
|
|
260
278
|
expensive work cannot quietly move into the fast one. Two declarations guard that:
|
|
@@ -637,7 +655,7 @@ acceptance for each.
|
|
|
637
655
|
| `test/api.test.js` | The package's public API: the list of names is frozen, and splitting the engine may not change it |
|
|
638
656
|
| `eslint.config.js` | The rules of formatting: the same as the consumer project's, plus a ban on gluing operators into one line (`pnpm run lint`, `pnpm run lint:strict`) |
|
|
639
657
|
| `tools/harness.js` | The harness of the checks: paths, clones of the fixture (including one shared per suite and one with CRLF), running the tool, reading refusals, hashes |
|
|
640
|
-
| `tools/page-harness.js` | The harness of the contract and page checks: the contract data, the assembled page, reading it in a real DOM, the
|
|
658
|
+
| `tools/page-harness.js` | The harness of the contract and page checks: the contract data, the assembled page, reading it in a real DOM, one set of readers for every page suite (a file's path at HEAD, the rows the window built and their numbers, the captions over them, the order of the columns, a folder's and a category's switch, the place the shell stands at), the page's calculation and its decoder evaluated from their sources, the block unpacked, and the platform's unpacker put into jsdom (which has none) — one for seven suites |
|
|
641
659
|
| `tools/suites.js` | The split of the suite: which files go into the fast run (with a reason for each) and why every dear one is in the full run |
|
|
642
660
|
| `tools/run-tests.js` | Running the suite (`pnpm test`, `pnpm test:all`, `pnpm run suites:measure`): each file's duration measured on its own, and the counts of checks adding up |
|
|
643
661
|
| `tools/docs-facts.js` | Reading facts out of the documentation — one layer for the four checks of the documentation guard: what a document names (paths, calls, section addresses) against what the repository holds |
|
|
@@ -654,10 +672,10 @@ acceptance for each.
|
|
|
654
672
|
| `test/contract-data.test.js` | The data contract: the numbers against the reference, the set of fields against the derived quantities, the metric's method against the way the numbers were counted — and the round trip through the page's sparse block, which restores the contract whole and twice over the same bytes |
|
|
655
673
|
| `test/contract-derived.test.js` | The derived quantities against the artifact's numbers: a row's totals, a cell's delta and the delta of a total — on the code that lies in the tree |
|
|
656
674
|
| `test/page-grid.test.js` | The grid of the page as a window: what the reader sees is built and no more, the geometry of the styling against the script's, the rows and the columns that a scroll builds and drops, a number under its own caption, a file and a metric switched off, every row of the window against the engine's own calculation, and the drawing's own rules — the order of the columns by the numbers, one border, the header's rows of one height, and the panel out of the shared styling's reach |
|
|
657
|
-
| `test/page-view.test.js` | The assembled page: pasted with no copy of the calculation, self-contained, the
|
|
658
|
-
| `test/page-tree.test.js` | The panel's file tree: folders by the project's paths, three states, the subtree, files and folders outside the report (a checkbox off, a place after the rest), the hidden names at the end of a level, the tree that opens folded and the unfolding the memory keeps, folding without a rebuild and a scroll a click does not touch
|
|
659
|
-
| `test/page-choice.test.js` | The reader's choice
|
|
660
|
-
| `test/page-cols.test.js` | The
|
|
675
|
+
| `test/page-view.test.js` | The assembled page: pasted with no copy of the calculation, self-contained, the sparse block, the recount by the choice, every total of the window against the engine's own sums, no mark of precision left on a cell, the shared styling pasted once and the delta colour set once, the empty states, and the switches' focus |
|
|
676
|
+
| `test/page-tree.test.js` | The panel's file tree: folders by the project's paths, three states, the subtree, files and folders outside the report (a checkbox off, a place after the rest), the hidden names at the end of a level, the tree that opens folded and the unfolding the memory keeps, folding without a rebuild and a scroll a click does not touch |
|
|
677
|
+
| `test/page-choice.test.js` | The reader's choice: the memory and a revisit, someone else's report, a foreign and a broken record, an address that stays clean, a link read at opening and on an open page, and a host without an unpacker that is told so in words |
|
|
678
|
+
| `test/page-cols.test.js` | The columns of the grid: their order by the newest row a file's numbers moved in, a caption over exactly the metrics of its group, and a choice that moves no other column |
|
|
661
679
|
| `test/module.test.js` | A module under a `.js` extension: measured without touching the settings; the stripper's guard is alive (proved by mutation) and does not accuse the innocent |
|
|
662
680
|
| `test/guard.test.js` | Parsing a module: it goes through a thread, both paths give one verdict, the fallback works with the thread's file away, and hundreds of parses are cheaper than a launch |
|
|
663
681
|
| `test/runner.test.js` | Reading a process's output: chunks are glued as buffers rather than appended to a string — a multi-byte character at a chunk border does not turn into two replacement characters |
|
|
@@ -737,7 +755,7 @@ The same release can be taken by a reference to the repository — installation
|
|
|
737
755
|
registry, but stays tied to a revision:
|
|
738
756
|
|
|
739
757
|
```bash
|
|
740
|
-
pnpm add -D github:vernikr/size-report#v2.8.
|
|
758
|
+
pnpm add -D github:vernikr/size-report#v2.8.2
|
|
741
759
|
```
|
|
742
760
|
|
|
743
761
|
With no network (or nothing to fetch from codeload) — the tarball: `pnpm pack` in the package clone, then
|
|
@@ -749,7 +767,7 @@ the branch moves on the installation fails with `Could not resolve <sha> to a co
|
|
|
749
767
|
observation rather than reasoning: the short pin `6530237` installed while `main` stood on it and stopped
|
|
750
768
|
working at the very next commit, while the same sha in full installed. A branch name (`#main`) and a tag
|
|
751
769
|
are both accepted, but a branch is a moving target and a tag is constant: this release stands on the tag
|
|
752
|
-
`v2.8.
|
|
770
|
+
`v2.8.2`, which is also the one in the example (forty characters work as well, but they have to be copied
|
|
753
771
|
out of the history by eye).
|
|
754
772
|
|
|
755
773
|
The revision in the example is a part of the claim rather than decoration: what is described below is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vernikr/size-report",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.2",
|
|
4
4
|
"author": "vernikr",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"report"
|
|
66
66
|
],
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@vernikr/size-report": "2.8.
|
|
68
|
+
"@vernikr/size-report": "2.8.1",
|
|
69
69
|
"c8": "10",
|
|
70
70
|
"dependency-cruiser": "17",
|
|
71
71
|
"eslint": "^9.18.0",
|
package/src/page/app.css
CHANGED
|
@@ -45,7 +45,7 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
45
45
|
}
|
|
46
46
|
.panel fieldset { margin: 0; padding: 0; border: 0; }
|
|
47
47
|
.panel fieldset + fieldset { margin-top: 10px; }
|
|
48
|
-
.panel legend
|
|
48
|
+
.panel legend {
|
|
49
49
|
padding: 0;
|
|
50
50
|
color: var(--muted);
|
|
51
51
|
font-size: 11px;
|
|
@@ -53,7 +53,6 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
53
53
|
text-transform: uppercase;
|
|
54
54
|
letter-spacing: .07em;
|
|
55
55
|
}
|
|
56
|
-
.panel .cap { display: block; margin-bottom: 4px; }
|
|
57
56
|
.panel .row { display: flex; flex-wrap: wrap; gap: 3px 6px; align-items: center; }
|
|
58
57
|
/* The way of counting is visible text under the switches: the token dictionary and the way of compression come from the
|
|
59
58
|
* settings of the run rather than from a checkbox, so pointing a mouse is not enough — the reader has to see what produced
|
|
@@ -91,7 +90,7 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
91
90
|
cursor: pointer;
|
|
92
91
|
user-select: none;
|
|
93
92
|
}
|
|
94
|
-
.panel .tree li > .fold:hover { color:
|
|
93
|
+
.panel .tree li > .fold:hover { color: CanvasText; }
|
|
95
94
|
|
|
96
95
|
/* A switch is a label around an input: one label and one click target, which is why a mouse, the keyboard (`Space` on the
|
|
97
96
|
* input) and assistive technology all reach it. */
|
|
@@ -131,11 +130,7 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
131
130
|
/* Numbers are denser than the page's text: there are more of them, they are shorter, and they are read by their digits. */
|
|
132
131
|
#grid { font-size: 12.5px; }
|
|
133
132
|
|
|
134
|
-
/*
|
|
135
|
-
* over the browser's relayout of the whole table, and the table is built as a window now — a switch costs a few
|
|
136
|
-
* milliseconds (`src/page/table.js`), so an indicator would be a promise the page does not keep.
|
|
137
|
-
*
|
|
138
|
-
* The message about a link that came in stands above the table, so that it cannot be missed, while it does not push the
|
|
133
|
+
/* The message about a link that came in stands above the table, so that it cannot be missed, while it does not push the
|
|
139
134
|
* table away: one line in the place of the page. Its colour is the system one (the accent), for the page keeps no colours
|
|
140
135
|
* of its own. */
|
|
141
136
|
.notice {
|
package/src/page/app.js
CHANGED
|
@@ -4,15 +4,11 @@ import { appState, appTable, appWindow } from './table.js';
|
|
|
4
4
|
import { appPanel, appPanelAll, appPanelState } from './panel.js';
|
|
5
5
|
|
|
6
6
|
/* Assembling the report: the table is a window of the grid (`appWindow` of the table chapter) and nothing is built
|
|
7
|
-
* that the reader cannot see. Two paths and no third
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* this repository's report — instead of a class on every node of a column of a table the browser lays out whole. The
|
|
13
|
-
* stripe that used to stand over a long drawing (`src/page/work.js`) is gone with the reason for it: a switch no
|
|
14
|
-
* longer has anything to wait for, and an indicator over work that is over before it could be painted would be a
|
|
15
|
-
* promise the page does not keep.
|
|
7
|
+
* that the reader cannot see. Two paths and no third: `appPaint` draws the whole view (the first drawing, a record from
|
|
8
|
+
* the browser's memory, a link in the address), while a click on a box does the same work for the choice it made — the
|
|
9
|
+
* difference between them is only how much of the view moved. Neither has anything to wait for: a switch builds the
|
|
10
|
+
* window of the grid, a few hundred cells and milliseconds, so the page needs no indicator and keeps no promise it
|
|
11
|
+
* cannot measure.
|
|
16
12
|
*/
|
|
17
13
|
|
|
18
14
|
// The table's window: made once, at the first drawing.
|
|
@@ -42,38 +38,36 @@ export function appPaint() {
|
|
|
42
38
|
appWrite();
|
|
43
39
|
}
|
|
44
40
|
|
|
45
|
-
/* One
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
/* One drawing after a click, whatever was clicked: the window of the grid (the columns of what is switched off are
|
|
42
|
+
* simply not among the columns that are built), the counts of the empty states, the fields the choice really reached
|
|
43
|
+
* (`touched` — a whole group is one click, and a field that did not move is not written) and the memory. The message
|
|
44
|
+
* about a link fades here: by this action the reader has read it. */
|
|
45
|
+
function appChanged(touched) {
|
|
50
46
|
appWindow(appCache, true);
|
|
51
47
|
appCounts();
|
|
52
|
-
appPanelState(
|
|
48
|
+
if (touched.length > 0) appPanelState(touched);
|
|
53
49
|
appWrite();
|
|
54
50
|
appNotice('');
|
|
55
51
|
}
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
// One file switched by the reader. A switch that changes nothing is not a change: the click costs no drawing.
|
|
54
|
+
export function appSwitch(i, on) {
|
|
55
|
+
if (appView.files[i] === on) return;
|
|
56
|
+
appView.files[i] = on;
|
|
57
|
+
appChanged([i]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* A group switched at once — a folder or a category: the same work per file, and the fields of the files the choice
|
|
61
|
+
* really reached (switching a folder on when a part of it was already on touches only the rest). */
|
|
60
62
|
export function appSwitchGroup(indexes, on) {
|
|
61
63
|
const touched = indexes.filter((i) => appView.files[i] !== on);
|
|
62
64
|
touched.forEach((i) => { appView.files[i] = on; });
|
|
63
|
-
|
|
64
|
-
appCounts();
|
|
65
|
-
appPanelState(touched);
|
|
66
|
-
appWrite();
|
|
67
|
-
appNotice('');
|
|
65
|
+
appChanged(touched);
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
/* One metric switched
|
|
71
|
-
* same drawing as a file's switch. The metric's own field is the box the reader just clicked. */
|
|
68
|
+
/* One metric switched. Only the panel's metric box shows it, and that box is the one the reader just clicked. */
|
|
72
69
|
export function appSwitchMetric() {
|
|
73
|
-
|
|
74
|
-
appCounts();
|
|
75
|
-
appWrite();
|
|
76
|
-
appNotice('');
|
|
70
|
+
appChanged([]);
|
|
77
71
|
}
|
|
78
72
|
|
|
79
73
|
/* The first drawing: the choice is already in the view (the link and the memory are applied above), the panel is
|
package/src/page/panel.js
CHANGED
|
@@ -22,19 +22,18 @@ function appUnmeasuredBox(entry) {
|
|
|
22
22
|
+ (entry.why === 'rule' ? appUi.notMeasuredRule : appUi.notMeasuredChoice));
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/*
|
|
26
|
-
*
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return n;
|
|
25
|
+
/* A subtree in the two figures its folder shows: the measured files its checkbox controls, and how many files the
|
|
26
|
+
* folder holds at all — including the ones that made it into no report. One walk, because the two are asked together
|
|
27
|
+
* (a folder of five files with two measured reads "2/5"). */
|
|
28
|
+
function appSub(node) {
|
|
29
|
+
const idx = node.files.slice();
|
|
30
|
+
let total = node.files.length + node.others.length;
|
|
31
|
+
node.dirs.forEach((sub) => {
|
|
32
|
+
const inner = appSub(sub);
|
|
33
|
+
idx.push(...inner.idx);
|
|
34
|
+
total += inner.total;
|
|
35
|
+
});
|
|
36
|
+
return { idx: idx, total: total };
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
/* A tree node: the measured files (columns), the project's other files and the subfolders. */
|
|
@@ -48,8 +47,9 @@ function appNode() {
|
|
|
48
47
|
* without a single measured file stays in place with its checkbox off and unavailable: there is nothing to switch on
|
|
49
48
|
* in it. */
|
|
50
49
|
function appDirHead(name, here, sub) {
|
|
51
|
-
const
|
|
52
|
-
const
|
|
50
|
+
const own = appSub(sub);
|
|
51
|
+
const idx = own.idx;
|
|
52
|
+
const total = own.total;
|
|
53
53
|
const label = name + '/';
|
|
54
54
|
let head;
|
|
55
55
|
if (idx.length === 0) {
|
|
@@ -141,13 +141,14 @@ function appLeaf(leaf) {
|
|
|
141
141
|
function appTreeList(node, prefix) {
|
|
142
142
|
const list = appEl('ul', 'tree');
|
|
143
143
|
const dirs = [...node.dirs.keys()].sort(appName)
|
|
144
|
-
.map((name) => ({ name: name, sub: node.dirs.get(name), inReport:
|
|
144
|
+
.map((name) => ({ name: name, sub: node.dirs.get(name), inReport: appSub(node.dirs.get(name)).idx.length > 0 }));
|
|
145
145
|
const leaves = appLeaves(node);
|
|
146
146
|
const inside = leaves.filter((leaf) => leaf.entry === null);
|
|
147
|
+
const outside = leaves.filter((leaf) => leaf.entry !== null);
|
|
147
148
|
dirs.filter((d) => d.inReport).forEach((d) => list.appendChild(appDir(d.name, d.sub, prefix)));
|
|
148
149
|
inside.forEach((leaf) => list.appendChild(appLeaf(leaf)));
|
|
149
150
|
dirs.filter((d) => !d.inReport).forEach((d) => list.appendChild(appDir(d.name, d.sub, prefix)));
|
|
150
|
-
|
|
151
|
+
outside.forEach((leaf) => list.appendChild(appLeaf(leaf)));
|
|
151
152
|
return list;
|
|
152
153
|
}
|
|
153
154
|
|
|
@@ -237,10 +238,10 @@ function appRowBoxes(box) {
|
|
|
237
238
|
}
|
|
238
239
|
|
|
239
240
|
// A folder's field from its files: all on — checked, some — the third state, none — simply unchecked.
|
|
240
|
-
function appDirState(
|
|
241
|
-
const boxes = appRowBoxes(
|
|
241
|
+
function appDirState(path) {
|
|
242
|
+
const boxes = appRowBoxes(appFields.dir[path]);
|
|
242
243
|
const on = boxes.filter((b) => b.checked).length;
|
|
243
|
-
const input =
|
|
244
|
+
const input = appFields.dir[path].querySelector('input');
|
|
244
245
|
input.checked = on === boxes.length;
|
|
245
246
|
input.indeterminate = on > 0 && on < boxes.length;
|
|
246
247
|
}
|
|
@@ -267,29 +268,17 @@ function appDirPath(path) {
|
|
|
267
268
|
return parts.slice(0, -1).map((_part, i) => parts.slice(0, i + 1).join('/'));
|
|
268
269
|
}
|
|
269
270
|
|
|
270
|
-
/* A click reaches a folder
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
indexes.forEach((i) => {
|
|
276
|
-
appDirPath(appFileAt(i)).forEach((path) => {
|
|
277
|
-
if (seen[path] === true) return;
|
|
278
|
-
seen[path] = true;
|
|
279
|
-
if (appFields.dir[path] !== undefined) appDirState(appFields.dir[path]);
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// The same for the quick buttons of the categories: every category one of the switched files belongs to.
|
|
285
|
-
function appCatsOf(indexes) {
|
|
271
|
+
/* A click reaches the fields of a folder and of a category through the files below them — one walk for both, because
|
|
272
|
+
* it is one question: which groups the reader sees hold a switched file, and what do they show now. A field that is not
|
|
273
|
+
* there is left alone: a folder without measured files has a box of its own, off and unavailable, which is not the
|
|
274
|
+
* reader's state. */
|
|
275
|
+
function appReach(indexes, fields, namesOf, stateOf) {
|
|
286
276
|
const seen = {};
|
|
287
|
-
indexes.forEach((i) => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
});
|
|
277
|
+
indexes.forEach((i) => namesOf(i).forEach((name) => {
|
|
278
|
+
if (seen[name] === true || fields[name] === undefined) return;
|
|
279
|
+
seen[name] = true;
|
|
280
|
+
stateOf(name);
|
|
281
|
+
}));
|
|
293
282
|
}
|
|
294
283
|
|
|
295
284
|
/* What a click changed, written where it stands: the files' own boxes, then the fields of the folders and categories
|
|
@@ -299,8 +288,8 @@ export function appPanelState(indexes) {
|
|
|
299
288
|
const input = appFields.file[i];
|
|
300
289
|
if (input !== undefined) input.checked = appView.files[i] === true;
|
|
301
290
|
});
|
|
302
|
-
|
|
303
|
-
|
|
291
|
+
appReach(indexes, appFields.dir, (i) => appDirPath(appFileAt(i)), appDirState);
|
|
292
|
+
appReach(indexes, appFields.cat, (i) => [appData.files[i].category], appCatState);
|
|
304
293
|
}
|
|
305
294
|
|
|
306
295
|
/* The whole panel from the view: what a link, a record from the memory and the first drawing need. The metric boxes
|
|
@@ -309,5 +298,5 @@ export function appPanelAll() {
|
|
|
309
298
|
appData.categories.forEach((c) => appCatState(c.key));
|
|
310
299
|
appData.metrics.forEach((m) => { appFields.metric[m.key].checked = appView.metrics[m.key] === true; });
|
|
311
300
|
appData.files.forEach((_f, i) => { appFields.file[i].checked = appView.files[i] === true; });
|
|
312
|
-
Object.keys(appFields.dir).forEach((path) => appDirState(
|
|
301
|
+
Object.keys(appFields.dir).forEach((path) => appDirState(path));
|
|
313
302
|
}
|
package/src/page/state.js
CHANGED
|
@@ -106,15 +106,16 @@ function appPassport() {
|
|
|
106
106
|
return appPassportValue;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
/* One record of the choice
|
|
110
|
-
*
|
|
111
|
-
*
|
|
109
|
+
/* One record of the choice: only what is switched off is kept, by name. "Switched on" and "no record" are the same
|
|
110
|
+
* state, hence `null` rather than an empty record — turning every checkbox back on removes the record instead of leaving
|
|
111
|
+
* a trace indistinguishable from a choice. */
|
|
112
112
|
function appRecord() {
|
|
113
113
|
const metrics = {};
|
|
114
114
|
const files = {};
|
|
115
115
|
appData.metrics.forEach((m) => { if (!appView.metrics[m.key]) metrics[m.key] = false; });
|
|
116
116
|
appData.files.forEach((_f, i) => { if (!appView.files[i]) files[appFileAt(i)] = false; });
|
|
117
|
-
|
|
117
|
+
const empty = Object.keys(metrics).length === 0 && Object.keys(files).length === 0;
|
|
118
|
+
return empty ? null : { v: 1, passport: appPassport(), metrics: metrics, files: files };
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
// Whether a record is ours and of the right format — one rule for the memory and the address alike.
|
|
@@ -122,24 +123,40 @@ function appRecordOk(rec) {
|
|
|
122
123
|
return rec !== null && typeof rec === 'object' && rec.v === 1 && rec.passport === appPassport();
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
/*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const empty = Object.keys(rec.metrics).length === 0 && Object.keys(rec.files).length === 0;
|
|
126
|
+
/* A record into the browser's memory or out of it, and what is read back from it: the two halves of the memory, written
|
|
127
|
+
* once, because the page keeps two records of the same kind under two keys — the choice and the unfolded tree — and
|
|
128
|
+
* their rules are one. Nothing but the browser's memory is written: it is written on the click itself, which is what
|
|
129
|
+
* survives a closing, and the page's address keeps a clean tail — the report is a local page whose address is copied as
|
|
130
|
+
* it is, and a reader's choice belongs in the browser that made it rather than in the tab's title bar. What a link sent
|
|
131
|
+
* from an earlier release holds is still read (`appLinkUse`), and it is not written into the reader's memory: what came
|
|
132
|
+
* in is not his choice until he changes something. */
|
|
133
|
+
function appKeep(key, rec) {
|
|
134
134
|
try {
|
|
135
|
-
if (
|
|
136
|
-
else window.localStorage.setItem(
|
|
135
|
+
if (rec === null) window.localStorage.removeItem(key);
|
|
136
|
+
else window.localStorage.setItem(key, JSON.stringify(rec));
|
|
137
137
|
} catch (_e) {
|
|
138
138
|
/* There is no memory (the browser grants this page none): the choice will not survive a closing, while the numbers
|
|
139
139
|
* and the markup do not depend on it. */
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
export function appWrite() {
|
|
144
|
+
if (!appTransient) appKeep(appKey, appRecord());
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* What the browser saved under a key: its absence, a foreign record, a broken one and a record of another report amount
|
|
148
|
+
* to the same thing here — nothing to read. `JSON.parse` of a missing value answers `null`, so the two absences need no
|
|
149
|
+
* telling apart. */
|
|
150
|
+
function appLoad(key) {
|
|
151
|
+
let rec = null;
|
|
152
|
+
try {
|
|
153
|
+
rec = JSON.parse(window.localStorage.getItem(key));
|
|
154
|
+
} catch (_e) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return appRecordOk(rec) ? rec : null;
|
|
158
|
+
}
|
|
159
|
+
|
|
143
160
|
/* A reset to "everything on": the border between "this is no longer in the report" and "switched off" is the record
|
|
144
161
|
* rather than a missing value. A link carries the sender's whole choice, which is why it is applied to a clean view
|
|
145
162
|
* rather than on top of someone else's. */
|
|
@@ -174,15 +191,12 @@ function appLinkRead() {
|
|
|
174
191
|
}
|
|
175
192
|
|
|
176
193
|
/* How many names in the link are unknown to this report: the reader has to be told about them — otherwise he would look
|
|
177
|
-
* in the table for something that was never there.
|
|
194
|
+
* in the table for something that was never there. The two tables the model already holds answer it: a file by its path
|
|
195
|
+
* at HEAD (`appMeasured`) and a metric by its key (`appMetric`), the same names a record is written in (`appFileAt`). */
|
|
178
196
|
function appUnknown(rec) {
|
|
179
|
-
const known = {};
|
|
180
|
-
const metricKeys = {};
|
|
181
|
-
appData.files.forEach((_f, i) => { known[appFileAt(i)] = true; });
|
|
182
|
-
appData.metrics.forEach((m) => { metricKeys[m.key] = true; });
|
|
183
197
|
let n = 0;
|
|
184
|
-
Object.keys(rec.metrics || {}).forEach((k) => { if (
|
|
185
|
-
Object.keys(rec.files || {}).forEach((k) => { if (
|
|
198
|
+
Object.keys(rec.metrics || {}).forEach((k) => { if (appMetric[k] === undefined) n++; });
|
|
199
|
+
Object.keys(rec.files || {}).forEach((k) => { if (appMeasured[k] === undefined) n++; });
|
|
186
200
|
return n;
|
|
187
201
|
}
|
|
188
202
|
|
|
@@ -213,23 +227,9 @@ export function appLinkUse() {
|
|
|
213
227
|
return 'none';
|
|
214
228
|
}
|
|
215
229
|
|
|
216
|
-
|
|
217
|
-
* while a foreign, outdated or broken one amounts to its absence. */
|
|
230
|
+
// Reading: the reader's own choice, as he left it — another report's record lies under another key.
|
|
218
231
|
export function appRead() {
|
|
219
|
-
|
|
220
|
-
try {
|
|
221
|
-
text = window.localStorage.getItem(appKey);
|
|
222
|
-
} catch (_e) {
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
225
|
-
if (text === null) return null;
|
|
226
|
-
let rec = null;
|
|
227
|
-
try {
|
|
228
|
-
rec = JSON.parse(text);
|
|
229
|
-
} catch (_e) {
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
return appRecordOk(rec) ? rec : null;
|
|
232
|
+
return appLoad(appKey);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/* Applying goes by name: a file is recognised by its path, a metric by its key. A name the report does not hold matches
|
|
@@ -248,37 +248,22 @@ export function appApply(rec) {
|
|
|
248
248
|
* what the onlooker looks at rather than about which numbers are read, which is why it never goes into a link and never
|
|
249
249
|
* leaves the browser. The tree opens folded, so **the unfolded folders are what is kept** (`true`) — the default is the
|
|
250
250
|
* absence of the name, the same way "switched on" is the absence of a choice. A folder's name is its path ("src/page"),
|
|
251
|
-
* so a vanished name simply means nothing
|
|
252
|
-
*
|
|
253
|
-
* itself is. */
|
|
251
|
+
* so a vanished name simply means nothing. `appFoldKey` is set with the model (`appBoot`), for the reason the key itself
|
|
252
|
+
* is. */
|
|
254
253
|
export function appFoldRead() {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
text = window.localStorage.getItem(appFoldKey);
|
|
258
|
-
} catch (_e) {
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
if (text === null) return;
|
|
262
|
-
let rec = null;
|
|
263
|
-
try {
|
|
264
|
-
rec = JSON.parse(text);
|
|
265
|
-
} catch (_e) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
if (!appRecordOk(rec)) return;
|
|
254
|
+
const rec = appLoad(appFoldKey);
|
|
255
|
+
if (rec === null) return;
|
|
269
256
|
const open = rec.open || {};
|
|
270
257
|
Object.keys(open).forEach((p) => { if (open[p] === true) appView.open[p] = true; });
|
|
271
258
|
}
|
|
272
259
|
|
|
260
|
+
/* What a folder's sign does with the memory: the unfolded folders are what is kept (`true`), so a tree folded whole is
|
|
261
|
+
* the absence of the record — the default — and the record goes away with the last unfolded folder. */
|
|
273
262
|
export function appFoldSet(path, open) {
|
|
274
263
|
if (open) appView.open[path] = true;
|
|
275
264
|
else delete appView.open[path];
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
} catch (_e) {
|
|
281
|
-
/* There is no memory: what is unfolded will not survive a closing, while the view does not depend on it — the tree
|
|
282
|
-
* is unfolded exactly the way the reader unfolded it just now. */
|
|
283
|
-
}
|
|
265
|
+
const names = Object.keys(appView.open);
|
|
266
|
+
appKeep(appFoldKey, names.length === 0
|
|
267
|
+
? null
|
|
268
|
+
: { v: 1, passport: appPassport(), open: appView.open });
|
|
284
269
|
}
|
package/src/page/table.js
CHANGED
|
@@ -268,11 +268,11 @@ export function appWindow(cache, redraw) {
|
|
|
268
268
|
const was = cache.win;
|
|
269
269
|
cache.win = span;
|
|
270
270
|
if (redraw === true || was === null || was.c0 !== span.c0 || was.c1 !== span.c1) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
cache.grid.
|
|
271
|
+
/* The grid holds the header and the rows of the window and nothing else, so the whole of it is thrown away rather
|
|
272
|
+
* than tracked piece by piece — and the rows that are made below say in the map that they are there. */
|
|
273
|
+
cache.grid.textContent = '';
|
|
274
274
|
cache.rows.clear();
|
|
275
|
-
|
|
275
|
+
cache.grid.appendChild(appHead(cache, span));
|
|
276
276
|
for (let r = span.r0; r <= span.r1; r++) appPlace(cache, r, span);
|
|
277
277
|
return;
|
|
278
278
|
}
|