@vernikr/size-report 2.8.9 → 2.9.1
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/package.json +2 -2
- package/src/locales.js +2 -14
- package/src/page/app.css +16 -27
- package/src/page/app.js +0 -9
- package/src/page/build.js +12 -23
- package/src/page/panel.js +12 -21
- package/src/page/payload.js +3 -2
- package/src/page/state.js +77 -61
- package/templates/README.md +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vernikr/size-report",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.1",
|
|
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.
|
|
68
|
+
"@vernikr/size-report": "2.9.0",
|
|
69
69
|
"c8": "10",
|
|
70
70
|
"dependency-cruiser": "17",
|
|
71
71
|
"eslint": "^9.18.0",
|
package/src/locales.js
CHANGED
|
@@ -36,23 +36,17 @@ export const LOCALES = {
|
|
|
36
36
|
categoryFromConfig: 'из настроек',
|
|
37
37
|
categoryByExtension: 'по расширению',
|
|
38
38
|
all: 'все',
|
|
39
|
-
sub: '{tool} {version} · {artifact}',
|
|
40
39
|
panelMethod: 'способ:',
|
|
41
40
|
emptyMetrics: 'Ни одна метрика не выбрана — таблицу не из чего собрать. '
|
|
42
41
|
+ 'Включите хотя бы одну в панели выше: без метрик нет ни одной колонки с числом.',
|
|
43
42
|
noFiles: 'Ни один файл не выбран: в таблице остался только общий объём. '
|
|
44
43
|
+ 'Включите файл в панели выше, чтобы увидеть его колонки.',
|
|
45
|
-
linkForeign: 'Ссылка собрана в другом отчёте и не применена: показано то, что '
|
|
46
|
-
+ 'выбрано в этом браузере. Адрес не тронут.',
|
|
47
44
|
linkBroken: 'В адресе испорченный выбор — прочитать его не удалось. Показано то, '
|
|
48
45
|
+ 'что выбрано в этом браузере.',
|
|
49
46
|
linkExtra: 'Часть ссылки пропущена: в этом отчёте нет {n} названных имён. '
|
|
50
47
|
+ 'Остальное применено.',
|
|
51
48
|
unpack: 'Данные отчёта сжаты, а браузер не умеет их распаковать: в нём нет DecompressionStream. '
|
|
52
|
-
+ 'Обновите браузер — таблицу показывать не из чего.'
|
|
53
|
-
note: 'Абсолютные размеры стоят один раз — в строке «{now}». Строк: {rows}: коммиты без '
|
|
54
|
-
+ 'изменения объёма и коммиты, тронувшие только выключенные файлы, строк не получают. '
|
|
55
|
-
+ 'Собрано из истории git: {command}.'
|
|
49
|
+
+ 'Обновите браузер — таблицу показывать не из чего.'
|
|
56
50
|
},
|
|
57
51
|
note: {
|
|
58
52
|
intro: 'Строка — коммит, колонка — файл. ',
|
|
@@ -94,14 +88,11 @@ export const LOCALES = {
|
|
|
94
88
|
categoryFromConfig: 'from the settings',
|
|
95
89
|
categoryByExtension: 'by extension',
|
|
96
90
|
all: 'all',
|
|
97
|
-
sub: '{tool} {version} · {artifact}',
|
|
98
91
|
panelMethod: 'method:',
|
|
99
92
|
emptyMetrics: 'No metric is selected — there is nothing to build the table from. '
|
|
100
93
|
+ 'Turn on at least one above: without metrics there is no column holding a number.',
|
|
101
94
|
noFiles: 'No file is selected: the table shows the total only. '
|
|
102
95
|
+ 'Turn on a file above to see its columns.',
|
|
103
|
-
linkForeign: 'The link was built for another report, so it is not applied: you '
|
|
104
|
-
+ 'see what this browser has. The address is left as it is.',
|
|
105
96
|
linkBroken: 'The choice in the address is unreadable. You see what this browser '
|
|
106
97
|
+ 'has.',
|
|
107
98
|
linkExtra: 'Part of the link is skipped: this report has none of the {n} names '
|
|
@@ -109,10 +100,7 @@ export const LOCALES = {
|
|
|
109
100
|
/* The report's data lies in the file compressed and the browser unpacks it; this text is what a
|
|
110
101
|
* browser without `DecompressionStream` gets instead of an empty table. */
|
|
111
102
|
unpack: 'The report\u2019s data is compressed and this browser cannot unpack it: there is no '
|
|
112
|
-
+ 'DecompressionStream. Update the browser — there is nothing to build the table from.'
|
|
113
|
-
note: 'Absolute sizes appear once — in the “{now}” row. Rows: {rows}: commits that change '
|
|
114
|
-
+ 'no number, and commits touching only the disabled files, get no row. '
|
|
115
|
-
+ 'Collected from git history: {command}.'
|
|
103
|
+
+ 'DecompressionStream. Update the browser — there is nothing to build the table from.'
|
|
116
104
|
},
|
|
117
105
|
note: {
|
|
118
106
|
intro: 'A row is a commit, a column is a file. ',
|
package/src/page/app.css
CHANGED
|
@@ -31,11 +31,10 @@ body {
|
|
|
31
31
|
font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
32
32
|
-webkit-text-size-adjust: 100%;
|
|
33
33
|
}
|
|
34
|
-
h1 { margin: 0 0
|
|
35
|
-
.sub { margin: 0 0 calc(var(--gap) + 4px); color: var(--muted); font-size: 12.5px; }
|
|
34
|
+
h1 { margin: 0 0 9px; font-size: 21px; font-weight: 650; letter-spacing: -.012em; }
|
|
36
35
|
|
|
37
36
|
/* The panel of choices is a card: it separates the controls from the data and does not merge with the table that begins
|
|
38
|
-
* below. */
|
|
37
|
+
* below. The report's heading opens it: the page has no band above the numbers (the markup is in `src/page/build.js`). */
|
|
39
38
|
.panel {
|
|
40
39
|
margin: 0 0 var(--gap);
|
|
41
40
|
padding: 12px 14px 13px;
|
|
@@ -54,10 +53,6 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
54
53
|
letter-spacing: .07em;
|
|
55
54
|
}
|
|
56
55
|
.panel .row { display: flex; flex-wrap: wrap; gap: 3px 6px; align-items: center; }
|
|
57
|
-
/* The way of counting is visible text under the switches: the token dictionary and the way of compression come from the
|
|
58
|
-
* settings of the run rather than from a checkbox, so pointing a mouse is not enough — the reader has to see what produced
|
|
59
|
-
* the number. */
|
|
60
|
-
.panel .about { margin: 5px 0 0; color: var(--muted); font-size: 11.5px; }
|
|
61
56
|
|
|
62
57
|
/* The file tree: nesting is shown by an indent and a level line, and a folder is a switch like a file — only its checkbox
|
|
63
58
|
* answers for the whole subtree, while the number beside it says for how many files. The list is longer than the window,
|
|
@@ -155,9 +150,6 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
155
150
|
}
|
|
156
151
|
.state[hidden], .shell[hidden] { display: none; }
|
|
157
152
|
|
|
158
|
-
.note { margin: 14px 0 0; max-width: 90em; color: var(--muted); font-size: 12px; }
|
|
159
|
-
.note code { background: var(--tint); padding: 0 3px; border-radius: 3px; }
|
|
160
|
-
|
|
161
153
|
/* The keyboard: the focus ring is visible on any background (the system accent colour) and shifts no layout. Journal links
|
|
162
154
|
* and every switch are reachable with Tab. */
|
|
163
155
|
:focus-visible { outline: 2px solid AccentColor; outline-offset: 2px; border-radius: 3px; }
|
|
@@ -169,10 +161,10 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
169
161
|
*
|
|
170
162
|
* The layout is a grid on `body` rather than a wrapper in the markup: the page is assembled by pasting chapters
|
|
171
163
|
* (`src/page/build.js`), and adding nodes to it for the sake of styling would mean changing the page's shape in two places
|
|
172
|
-
* instead of one. There are
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
164
|
+
* instead of one. There are three rows, named by subject: the message about a link, the **working row** and the empty
|
|
165
|
+
* state — while the panel of choices spans them all in the left column, its heading inside it. Only the working row
|
|
166
|
+
* stretches: the table gets all the remaining height and the panel no more than that; the panel's own height is not
|
|
167
|
+
* "whatever came out" (it would push the table off the screen) but that same working row, inside which it scrolls.
|
|
176
168
|
*
|
|
177
169
|
* The threshold of 900px is the same as the adaptations' below: one border between "wide" and "narrow", or the page would
|
|
178
170
|
* be left with no rule at all between two thresholds. */
|
|
@@ -185,21 +177,18 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
185
177
|
overflow: auto;
|
|
186
178
|
display: grid;
|
|
187
179
|
grid-template-columns: minmax(230px, 300px) minmax(0, 1fr);
|
|
188
|
-
grid-template-rows: auto
|
|
180
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
189
181
|
column-gap: var(--gap);
|
|
190
182
|
align-items: stretch;
|
|
191
183
|
}
|
|
192
|
-
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
#state { grid-area: 4 / 2 / 5 / 3; align-self: start; }
|
|
196
|
-
#note { grid-area: 5 / 2 / 6 / 3; }
|
|
184
|
+
#notice { grid-area: 1 / 2 / 2 / 3; }
|
|
185
|
+
#shell { grid-area: 2 / 2 / 3 / 3; }
|
|
186
|
+
#state { grid-area: 3 / 2 / 4 / 3; align-self: start; }
|
|
197
187
|
/* The working row runs the full height, as the table does: the panel ends where it ends. The panel scrolls rather than the
|
|
198
|
-
* page
|
|
199
|
-
*
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
.panel > fieldset:first-child { padding-top: 12px; }
|
|
188
|
+
* page, and its top padding is gone: the heading carries that room instead and travels away with the list, while the
|
|
189
|
+
* sticky row of categories stands flush with the card's edge rather than under a band of passing rows. */
|
|
190
|
+
.panel { grid-area: 1 / 1 / -1 / 2; min-height: 0; margin-bottom: 0; padding-top: 0; overflow: auto; }
|
|
191
|
+
.panel h1 { padding-top: 12px; }
|
|
203
192
|
/* Here the row rather than the window sets the table's height: it needs a height rule of its own only in a narrow window,
|
|
204
193
|
* where the whole page scrolls. */
|
|
205
194
|
.shell { max-height: none; }
|
|
@@ -217,7 +206,7 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
217
206
|
}
|
|
218
207
|
/* Without metrics there is no table, and a stretched empty row has no place there: the empty state takes the free space of
|
|
219
208
|
* that row rather than the band above it. */
|
|
220
|
-
body:has(#shell[hidden]) #state { grid-area:
|
|
209
|
+
body:has(#shell[hidden]) #state { grid-area: 2 / 2 / 3 / 3; }
|
|
221
210
|
}
|
|
222
211
|
|
|
223
212
|
/* Adaptations: the one place where the page overrides the shared geometry — because the shared part is frozen by the
|
|
@@ -226,7 +215,7 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
226
215
|
* empty space under it at that single window size. */
|
|
227
216
|
@media (max-width: 899px) {
|
|
228
217
|
body { padding: 14px 14px 32px; }
|
|
229
|
-
h1 { font-size:
|
|
218
|
+
h1 { font-size: 19px; }
|
|
230
219
|
#grid { font-size: 12px; }
|
|
231
220
|
.shell { max-height: calc(100vh - 260px); }
|
|
232
221
|
/* The commit column keeps its width on a narrow screen, and that is deliberate rather than forgotten: the numbers
|
package/src/page/app.js
CHANGED
|
@@ -14,14 +14,6 @@ import { appPanel, appPanelAll, appPanelState } from './panel.js';
|
|
|
14
14
|
// The table's window: made once, at the first drawing.
|
|
15
15
|
let appCache = null;
|
|
16
16
|
|
|
17
|
-
/* The note under the table: what a row is and how the report was made. It does not depend on the choice, so it is
|
|
18
|
-
* written once — with the table rather than with every drawing of it. */
|
|
19
|
-
function appNote() {
|
|
20
|
-
document.getElementById('note').textContent = appUi.note
|
|
21
|
-
.replace('{rows}', appData.rows.length)
|
|
22
|
-
.replace('{command}', appData.report.fixCommand);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
17
|
/* What the empty states are told: how many metrics and how many files are left. The window stands there in either
|
|
26
18
|
* case — the words are about what is shown. */
|
|
27
19
|
export function appCounts() {
|
|
@@ -76,7 +68,6 @@ export function appSwitchMetric() {
|
|
|
76
68
|
function appFirst() {
|
|
77
69
|
appPanel();
|
|
78
70
|
appCache = appTable(document.getElementById('grid'));
|
|
79
|
-
appNote();
|
|
80
71
|
appPaint();
|
|
81
72
|
}
|
|
82
73
|
|
package/src/page/build.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import zlib from 'zlib';
|
|
3
|
-
import {
|
|
3
|
+
import { LOCALES } from '../locales.js';
|
|
4
4
|
import { PAGE_CSS, TABLE_CSS } from '../css.js';
|
|
5
5
|
import { assertCompilable, stripCss, stripJs, stripLines } from '../strip.js';
|
|
6
6
|
|
|
@@ -71,16 +71,6 @@ export function pageScript() {
|
|
|
71
71
|
return code;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/* The note under the heading: what built the report and where it lies. The path is plain text rather than a link: the
|
|
75
|
-
* page opens from disk and depends on nothing. */
|
|
76
|
-
function subText(data, page) {
|
|
77
|
-
return fill(page.sub, {
|
|
78
|
-
tool: data.tool.name,
|
|
79
|
-
version: data.tool.version,
|
|
80
|
-
artifact: data.report.artifact
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
74
|
/* The page's texts: column captions, panel labels, the legend and the empty states. They are the page's dictionary
|
|
85
75
|
* rather than the report's: the data block carries none of them, and the report's own words live in the locale. */
|
|
86
76
|
function uiText(page, loc) {
|
|
@@ -95,7 +85,6 @@ function uiText(page, loc) {
|
|
|
95
85
|
foldClose: page.foldClose,
|
|
96
86
|
foldOpen: page.foldOpen,
|
|
97
87
|
all: page.all,
|
|
98
|
-
linkForeign: page.linkForeign,
|
|
99
88
|
linkBroken: page.linkBroken,
|
|
100
89
|
linkExtra: page.linkExtra,
|
|
101
90
|
/* What a host that cannot unpack the block is told: the page's one message about its own file rather than about
|
|
@@ -114,9 +103,7 @@ function uiText(page, loc) {
|
|
|
114
103
|
categoryByExtension: page.categoryByExtension,
|
|
115
104
|
methodLabel: page.panelMethod,
|
|
116
105
|
empty: page.emptyMetrics,
|
|
117
|
-
noFiles: page.noFiles
|
|
118
|
-
/* {command} is substituted by the page, which holds the data, while {now} is filled in here. */
|
|
119
|
-
note: page.note.replace(/\{now\}/g, loc.now)
|
|
106
|
+
noFiles: page.noFiles
|
|
120
107
|
};
|
|
121
108
|
}
|
|
122
109
|
|
|
@@ -208,13 +195,13 @@ export function pagePayload(data) {
|
|
|
208
195
|
const out = {
|
|
209
196
|
schema: 2,
|
|
210
197
|
tool: data.tool,
|
|
211
|
-
/* The report's own words are the ones the page reads: `heading` is the artifact's `<h1
|
|
212
|
-
* today,
|
|
198
|
+
/* The report's own words are the ones the page reads: `heading` is the artifact's `<h1>`, built before the block,
|
|
199
|
+
* `journal` is null today, and `fixCommand` belongs to a refusal and to the contract rather than to the page, which
|
|
200
|
+
* builds no heading, prints no journal and suggests no command. */
|
|
213
201
|
report: {
|
|
214
202
|
locale: data.report.locale,
|
|
215
203
|
title: data.report.title,
|
|
216
204
|
artifact: data.report.artifact,
|
|
217
|
-
fixCommand: data.report.fixCommand,
|
|
218
205
|
showSha: data.report.showSha
|
|
219
206
|
},
|
|
220
207
|
hrefPrefix: prefix,
|
|
@@ -248,20 +235,22 @@ export function pagePacked(text) {
|
|
|
248
235
|
}
|
|
249
236
|
|
|
250
237
|
/* The report's page is one file: the data lies in it, the script is pasted in, there are no external references. Hence
|
|
251
|
-
* it opens with a double click and works without a network.
|
|
238
|
+
* it opens with a double click and works without a network.
|
|
239
|
+
*
|
|
240
|
+
* The page's chrome is the panel and the table, and nothing stands above the numbers: the heading opens the panel — the
|
|
241
|
+
* one place holding the reader's own controls — and neither the tool, nor its version, nor the artifact's path is printed
|
|
242
|
+
* anywhere, the file lying in a directory whose name already says where. The panel's card is the markup's and its fields
|
|
243
|
+
* are the script's: `#panel` is emptied and rebuilt on every opening, hence the heading is the card's other child. */
|
|
252
244
|
export function pageHtml(data, cfg) {
|
|
253
245
|
const loc = LOCALES[cfg.locale];
|
|
254
246
|
return '<!doctype html>\n<html lang="' + esc(loc.html) + '">\n<head>\n<meta charset="utf-8">\n'
|
|
255
247
|
+ '<meta name="viewport" content="width=device-width, initial-scale=1">\n'
|
|
256
248
|
+ '<title>' + esc(data.report.title) + '</title>\n<style>\n'
|
|
257
249
|
+ squeezedCss(TABLE_CSS) + '\n' + squeezedCss(PAGE_CSS) + '\n</style>\n</head>\n<body>\n'
|
|
258
|
-
+ '<
|
|
259
|
-
+ '<p class="sub">' + esc(subText(data, loc.page)) + '</p>\n</header>\n'
|
|
260
|
-
+ '<div id="panel" class="panel"></div>\n'
|
|
250
|
+
+ '<div class="panel">\n<h1>' + esc(data.report.heading) + '</h1>\n<div id="panel"></div>\n</div>\n'
|
|
261
251
|
+ '<p id="notice" class="notice" hidden></p>\n'
|
|
262
252
|
+ '<div id="shell" class="shell"><div id="grid" class="grid"></div></div>\n'
|
|
263
253
|
+ '<p id="state" class="state" hidden></p>\n'
|
|
264
|
-
+ '<p id="note" class="note"></p>\n'
|
|
265
254
|
+ '<script type="application/octet-stream" id="data" data-pack="base64+gzip">'
|
|
266
255
|
+ pagePacked(jsonInHtml(pagePayload(data))) + '</script>\n'
|
|
267
256
|
+ '<script type="application/json" id="ui">' + jsonInHtml(uiText(loc.page, loc)) + '</script>\n'
|
package/src/page/panel.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { appEl, appBox, appOffBox } from './dom.js';
|
|
2
|
-
import { appData, appUi, appView, appFileAt, appFoldSet, appMeasured } from './state.js';
|
|
2
|
+
import { appCatOf, appData, appUi, appView, appFileAt, appFoldSet, appMeasured } from './state.js';
|
|
3
3
|
|
|
4
4
|
/* Only a file sets a file's checkbox: both a category and a folder in the tree are ways to set the same checkboxes as
|
|
5
5
|
* a group and keep no state of their own. Otherwise one and the same decision would live in two places and drift
|
|
@@ -188,8 +188,11 @@ export function appPanel() {
|
|
|
188
188
|
const metrics = appEl('fieldset');
|
|
189
189
|
metrics.appendChild(appEl('legend', null, appUi.metrics));
|
|
190
190
|
const mrow = appEl('div', 'row');
|
|
191
|
+
/* What a metric is and how its number was obtained stand in the tooltip of the box that switches it: the dictionary
|
|
192
|
+
* and the minifier come from the run's settings, so the reader has to see with what he is counting, and the box is
|
|
193
|
+
* where he looks. */
|
|
191
194
|
appData.metrics.forEach((m) => {
|
|
192
|
-
const box = appBox(m.label, m.note, appView.metrics[m.key], (e) => {
|
|
195
|
+
const box = appBox(m.label, m.note + ' — ' + appUi.methodLabel + ' ' + m.method, appView.metrics[m.key], (e) => {
|
|
193
196
|
appView.metrics[m.key] = e.target.checked;
|
|
194
197
|
appSwitchMetric();
|
|
195
198
|
}, 'metric');
|
|
@@ -197,12 +200,6 @@ export function appPanel() {
|
|
|
197
200
|
mrow.appendChild(box);
|
|
198
201
|
});
|
|
199
202
|
metrics.appendChild(mrow);
|
|
200
|
-
/* What produced each number is visible rather than hidden in a tooltip: the token dictionary and the way of
|
|
201
|
-
* compression are chosen by the settings of the run, the page has nothing to switch them with, and the reader needs
|
|
202
|
-
* to know this without pointing a mouse. */
|
|
203
|
-
appData.metrics.forEach((m) => {
|
|
204
|
-
metrics.appendChild(appEl('p', 'about', m.label + ' — ' + appUi.methodLabel + ' ' + m.method));
|
|
205
|
-
});
|
|
206
203
|
panel.appendChild(metrics);
|
|
207
204
|
|
|
208
205
|
const files = appEl('fieldset', 'files');
|
|
@@ -211,8 +208,7 @@ export function appPanel() {
|
|
|
211
208
|
* in the wide layout, which is where the panel scrolls). */
|
|
212
209
|
const cats = appEl('div', 'row cats');
|
|
213
210
|
appData.categories.forEach((cat) => {
|
|
214
|
-
const idx =
|
|
215
|
-
appData.files.forEach((f, i) => { if (f.category === cat.key) idx.push(i); });
|
|
211
|
+
const idx = appCatOf(cat.key);
|
|
216
212
|
const box = appBox(cat.label, appUi.all + ' · ' + cat.label, idx.every((i) => appView.files[i]),
|
|
217
213
|
(e) => appSwitchGroup(idx, e.target.checked), 'all');
|
|
218
214
|
appFields.cat[cat.key] = box.querySelector('input');
|
|
@@ -246,19 +242,14 @@ function appDirState(path) {
|
|
|
246
242
|
input.indeterminate = on > 0 && on < boxes.length;
|
|
247
243
|
}
|
|
248
244
|
|
|
249
|
-
/* A category's field from its files — the same rule, taken from the data: a category's files are named by the
|
|
250
|
-
*
|
|
245
|
+
/* A category's field from its files — the same rule, taken from the data: a category's files are named by the category
|
|
246
|
+
* itself (`appCatOf`), and the boxes of the tree are a different view of the same files. */
|
|
251
247
|
function appCatState(key) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
appData.files.forEach((f, i) => {
|
|
255
|
-
if (f.category !== key) return;
|
|
256
|
-
all++;
|
|
257
|
-
if (appView.files[i] === true) on++;
|
|
258
|
-
});
|
|
248
|
+
const idx = appCatOf(key);
|
|
249
|
+
const on = idx.filter((i) => appView.files[i] === true).length;
|
|
259
250
|
const input = appFields.cat[key];
|
|
260
|
-
input.checked = on ===
|
|
261
|
-
input.indeterminate = on > 0 && on <
|
|
251
|
+
input.checked = on === idx.length;
|
|
252
|
+
input.indeterminate = on > 0 && on < idx.length;
|
|
262
253
|
}
|
|
263
254
|
|
|
264
255
|
/* The folders a file lies in: the prefixes of its path, from the root down. The tree's folders are exactly those
|
package/src/page/payload.js
CHANGED
|
@@ -146,8 +146,9 @@ function appRowOf(p, r, values) {
|
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
/* The whole block, unrolled into the dense contract the page reads:
|
|
150
|
-
*
|
|
149
|
+
/* The whole block, unrolled into the dense contract the page reads: the block is a shape of that contract rather than a
|
|
150
|
+
* subset of it, so `schema` and `tool` come over with the rest although the page reads neither — the form mark and the
|
|
151
|
+
* author belong to the answer an agent is given, and one shape is cheaper to keep than two. */
|
|
151
152
|
export function appDecode(p) {
|
|
152
153
|
const keys = p.metrics.map((m) => appText(p, m[0]));
|
|
153
154
|
const hist = appUnroll(p, keys);
|
package/src/page/state.js
CHANGED
|
@@ -11,9 +11,10 @@ import { appDecode } from './payload.js';
|
|
|
11
11
|
* network: the styling arrives in the same file, and the cell markup follows the rules of the shared part of the styling
|
|
12
12
|
* (`clip`, a commit's caption).
|
|
13
13
|
*
|
|
14
|
-
* The panel remembers the reader's choice between visits ("the choice's memory" below):
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* The panel remembers the reader's choice between visits ("the choice's memory" below): one record for the browser, under
|
|
15
|
+
* a name of this page's own, keeping the fact of a whole category and the names of the files switched off one by one —
|
|
16
|
+
* only what is off is written down, and a name the report does not hold matches nothing. The choice is the reader's
|
|
17
|
+
* rather than a report's, since all `file://` pages share one storage, and it stays in the browser: a report opened from
|
|
17
18
|
* disk keeps a clean address, and a link made in an earlier release is still read (`appLinkUse`).
|
|
18
19
|
*
|
|
19
20
|
* The page draws no conclusion about how a number was obtained: the method of each metric arrives in the data, and the
|
|
@@ -29,8 +30,6 @@ export let appData = null;
|
|
|
29
30
|
export let appView = null;
|
|
30
31
|
export let appMetric = null;
|
|
31
32
|
export let appMeasured = null;
|
|
32
|
-
let appKey = null;
|
|
33
|
-
let appFoldKey = null;
|
|
34
33
|
|
|
35
34
|
/* One place that turns the unpacked block into what the chapters speak: the sparse form is unrolled by the payload
|
|
36
35
|
* chapter into the dense contract every number comes from — snapshots per commit, the texts themselves, "now" as
|
|
@@ -43,8 +42,8 @@ let appFoldKey = null;
|
|
|
43
42
|
* page's tree is the project's tree while the numbers belong to columns only); the name follows the same rule as the
|
|
44
43
|
* choice's record (`appFileAt`), so the tree and the reader's memory cannot drift apart.
|
|
45
44
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
45
|
+
* What other namings left in this storage go at the same moment (`appSweep`), before anything of the reader's memory is
|
|
46
|
+
* read. */
|
|
48
47
|
export function appBoot(text) {
|
|
49
48
|
appData = appDecode(JSON.parse(text));
|
|
50
49
|
/* The tree is folded as it opens: a project's tree is longer than the window, and the reader's first look is at a
|
|
@@ -55,8 +54,7 @@ export function appBoot(text) {
|
|
|
55
54
|
appMeasured = {};
|
|
56
55
|
appData.metrics.forEach((m) => { appView.metrics[m.key] = true; appMetric[m.key] = m; });
|
|
57
56
|
appData.files.forEach((_f, i) => { appView.files.push(true); appMeasured[appFileAt(i)] = i; });
|
|
58
|
-
|
|
59
|
-
appFoldKey = appKey + ':tree';
|
|
57
|
+
appSweep();
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
/* A link is that same choice in the address, under a name of its own: someone else's anchor on the page does not count
|
|
@@ -70,6 +68,12 @@ let appTransient = false;
|
|
|
70
68
|
|
|
71
69
|
/* -------- the reader's memory of his choice -------- */
|
|
72
70
|
|
|
71
|
+
/* The two keys of this page's memory: the choice and the unfolded tree. The namespace is this page's own and it is one
|
|
72
|
+
* for every report — all `file://` pages share a storage, so the memory belongs to the reader rather than to a report. */
|
|
73
|
+
const APP_STORE = 'size-report:';
|
|
74
|
+
const APP_KEY = APP_STORE + 'choice';
|
|
75
|
+
const APP_FOLD_KEY = APP_STORE + 'tree';
|
|
76
|
+
|
|
73
77
|
/* The name of a file for the record is its path at HEAD, or the last of the settings when the file is already gone from
|
|
74
78
|
* there: that is the name it is recognised by in the report. */
|
|
75
79
|
export function appFileAt(i) {
|
|
@@ -77,50 +81,58 @@ export function appFileAt(i) {
|
|
|
77
81
|
return f.path === null ? f.paths[0] : f.path;
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
/* The
|
|
81
|
-
*
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
h = Math.imul(h, 16777619);
|
|
87
|
-
}
|
|
88
|
-
return (h >>> 0).toString(16);
|
|
84
|
+
/* The files of one category, in the order of the data: a category is a group as the engine named it (`category`), and
|
|
85
|
+
* the panel's boxes, their fields and the record all ask for that group here. */
|
|
86
|
+
export function appCatOf(key) {
|
|
87
|
+
const idx = [];
|
|
88
|
+
appData.files.forEach((f, i) => { if (f.category === key) idx.push(i); });
|
|
89
|
+
return idx;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
/*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
appData.report.title, appData.files.map((f) => f.label).join('|')].join('\n'));
|
|
92
|
+
/* What other namings left in this storage: the memory is read under the two keys above and under nothing else, so any
|
|
93
|
+
* other name in the namespace is dead weight — an address counted per build, a record of an older form. They go once,
|
|
94
|
+
* when a report is opened. */
|
|
95
|
+
function appSweep() {
|
|
96
|
+
try {
|
|
97
|
+
for (let i = window.localStorage.length - 1; i >= 0; i--) {
|
|
98
|
+
const key = window.localStorage.key(i);
|
|
99
|
+
if (key !== null && key.indexOf(APP_STORE) === 0 && key !== APP_KEY && key !== APP_FOLD_KEY) {
|
|
100
|
+
window.localStorage.removeItem(key);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch (_e) {
|
|
104
|
+
/* There is no memory (the browser grants this page none): there is nothing to sweep either. */
|
|
105
105
|
}
|
|
106
|
-
return appPassportValue;
|
|
107
106
|
}
|
|
108
107
|
|
|
109
|
-
/* One record of the choice:
|
|
110
|
-
*
|
|
111
|
-
*
|
|
108
|
+
/* One record of the choice: the facts of the groups, the names of the files switched off one by one, and nothing else.
|
|
109
|
+
*
|
|
110
|
+
* A category wholly off or wholly on is written down as that fact — the group is named, so a file joining it later is
|
|
111
|
+
* switched with it and the record does not grow with the project — while a category in the third state cannot speak for
|
|
112
|
+
* the reader, whose choice was made file by file: those files go into `files` by name, and the category stays out of
|
|
113
|
+
* `cats`, which is how a group fact differs from no group fact to state.
|
|
114
|
+
*
|
|
115
|
+
* "Switched on" and "no record" are the same state, hence `null` rather than an empty record: turning every box back on
|
|
116
|
+
* removes it instead of leaving a trace indistinguishable from a choice. */
|
|
112
117
|
function appRecord() {
|
|
113
118
|
const metrics = {};
|
|
119
|
+
const cats = {};
|
|
114
120
|
const files = {};
|
|
115
121
|
appData.metrics.forEach((m) => { if (!appView.metrics[m.key]) metrics[m.key] = false; });
|
|
116
|
-
appData.
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
appData.categories.forEach((c) => {
|
|
123
|
+
const idx = appCatOf(c.key);
|
|
124
|
+
const on = idx.filter((i) => appView.files[i] === true).length;
|
|
125
|
+
if (on === idx.length || on === 0) { cats[c.key] = on === idx.length; return; }
|
|
126
|
+
idx.forEach((i) => { if (appView.files[i] !== true) files[appFileAt(i)] = false; });
|
|
127
|
+
});
|
|
128
|
+
const empty = Object.keys(metrics).length === 0 && Object.keys(files).length === 0
|
|
129
|
+
&& Object.keys(cats).every((key) => cats[key] === true);
|
|
130
|
+
return empty ? null : { v: 1, metrics: metrics, cats: cats, files: files };
|
|
119
131
|
}
|
|
120
132
|
|
|
121
|
-
// Whether a record is
|
|
133
|
+
// Whether a record is of this format — one rule for the memory and the address alike.
|
|
122
134
|
function appRecordOk(rec) {
|
|
123
|
-
return rec !== null && typeof rec === 'object' && rec.v === 1
|
|
135
|
+
return rec !== null && typeof rec === 'object' && rec.v === 1;
|
|
124
136
|
}
|
|
125
137
|
|
|
126
138
|
/* 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
|
|
@@ -141,7 +153,7 @@ function appKeep(key, rec) {
|
|
|
141
153
|
}
|
|
142
154
|
|
|
143
155
|
export function appWrite() {
|
|
144
|
-
if (!appTransient) appKeep(
|
|
156
|
+
if (!appTransient) appKeep(APP_KEY, appRecord());
|
|
145
157
|
}
|
|
146
158
|
|
|
147
159
|
/* What the browser saved under a key: its absence, a foreign record, a broken one and a record of another report amount
|
|
@@ -165,10 +177,10 @@ function appAll() {
|
|
|
165
177
|
appData.files.forEach((_f, i) => { appView.files[i] = true; });
|
|
166
178
|
}
|
|
167
179
|
|
|
168
|
-
/* What the address says. It answers either with a record of
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
180
|
+
/* What the address says. It answers either with a record of this format or with a refusal (`linkBroken` — nothing to
|
|
181
|
+
* read): a choice that cannot be read is not applied, but it does not stay silent either — otherwise the reader would not
|
|
182
|
+
* understand why he sees something other than what was sent to him. An address without the link's name is no link at
|
|
183
|
+
* all: silence, so as not to argue with the page's ordinary anchors. */
|
|
172
184
|
function appLinkRead() {
|
|
173
185
|
const hash = window.location.hash || '';
|
|
174
186
|
if (hash.indexOf(APP_LINK) !== 0) return { rec: null, refused: null };
|
|
@@ -183,10 +195,7 @@ function appLinkRead() {
|
|
|
183
195
|
return { rec: null, refused: 'linkBroken' };
|
|
184
196
|
}
|
|
185
197
|
}
|
|
186
|
-
if (!appRecordOk(rec)) {
|
|
187
|
-
return { rec: null, refused: rec === null || typeof rec !== 'object' || rec.v !== 1
|
|
188
|
-
? 'linkBroken' : 'linkForeign' };
|
|
189
|
-
}
|
|
198
|
+
if (!appRecordOk(rec)) return { rec: null, refused: 'linkBroken' };
|
|
190
199
|
return { rec: rec, refused: null, extra: appUnknown(rec) };
|
|
191
200
|
}
|
|
192
201
|
|
|
@@ -227,18 +236,28 @@ export function appLinkUse() {
|
|
|
227
236
|
return 'none';
|
|
228
237
|
}
|
|
229
238
|
|
|
230
|
-
// Reading: the reader's own choice, as he left it
|
|
239
|
+
// Reading: the reader's own choice, as he left it.
|
|
231
240
|
export function appRead() {
|
|
232
|
-
return appLoad(
|
|
241
|
+
return appLoad(APP_KEY);
|
|
233
242
|
}
|
|
234
243
|
|
|
235
|
-
/* Applying goes by name: a file is recognised by its path, a metric by its key
|
|
236
|
-
* nothing (a column was pointed at another path, a metric was dropped from
|
|
237
|
-
* appeared stay switched on — the way someone opening the page
|
|
244
|
+
/* Applying goes by name: a file is recognised by its path, a metric by its key, a category by the key the engine gave
|
|
245
|
+
* it. A name the report does not hold matches nothing (a column was pointed at another path, a metric was dropped from
|
|
246
|
+
* the settings), while files, categories and metrics that appeared stay switched on — the way someone opening the page
|
|
247
|
+
* for the first time sees them.
|
|
248
|
+
*
|
|
249
|
+
* The groups go first, the names after them: the file-by-file decision is the finer one, and a record that knows no
|
|
250
|
+
* groups is applied by its names. */
|
|
238
251
|
export function appApply(rec) {
|
|
239
252
|
const metrics = rec.metrics || {};
|
|
253
|
+
const cats = rec.cats || {};
|
|
240
254
|
const files = rec.files || {};
|
|
241
255
|
appData.metrics.forEach((m) => { if (metrics[m.key] === false) appView.metrics[m.key] = false; });
|
|
256
|
+
appData.categories.forEach((c) => {
|
|
257
|
+
const on = cats[c.key];
|
|
258
|
+
if (on !== true && on !== false) return;
|
|
259
|
+
appCatOf(c.key).forEach((i) => { appView.files[i] = on; });
|
|
260
|
+
});
|
|
242
261
|
appData.files.forEach((_f, i) => { if (files[appFileAt(i)] === false) appView.files[i] = false; });
|
|
243
262
|
}
|
|
244
263
|
|
|
@@ -248,10 +267,9 @@ export function appApply(rec) {
|
|
|
248
267
|
* what the onlooker looks at rather than about which numbers are read, which is why it never goes into a link and never
|
|
249
268
|
* leaves the browser. The tree opens folded, so **the unfolded folders are what is kept** (`true`) — the default is the
|
|
250
269
|
* 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
|
-
* is. */
|
|
270
|
+
* so a vanished name simply means nothing. */
|
|
253
271
|
export function appFoldRead() {
|
|
254
|
-
const rec = appLoad(
|
|
272
|
+
const rec = appLoad(APP_FOLD_KEY);
|
|
255
273
|
if (rec === null) return;
|
|
256
274
|
const open = rec.open || {};
|
|
257
275
|
Object.keys(open).forEach((p) => { if (open[p] === true) appView.open[p] = true; });
|
|
@@ -263,7 +281,5 @@ export function appFoldSet(path, open) {
|
|
|
263
281
|
if (open) appView.open[path] = true;
|
|
264
282
|
else delete appView.open[path];
|
|
265
283
|
const names = Object.keys(appView.open);
|
|
266
|
-
appKeep(
|
|
267
|
-
? null
|
|
268
|
-
: { v: 1, passport: appPassport(), open: appView.open });
|
|
284
|
+
appKeep(APP_FOLD_KEY, names.length === 0 ? null : { v: 1, open: appView.open });
|
|
269
285
|
}
|
package/templates/README.md
CHANGED
|
@@ -40,7 +40,8 @@ What is worth knowing about the template's values:
|
|
|
40
40
|
without the optional dependencies, a platform that has none) the tool still works, but says that the
|
|
41
41
|
numbers were counted another way, and returns **code 4** — that is a named different count rather than an
|
|
42
42
|
error of the settings.
|
|
43
|
-
- `fixCommand` — the command the
|
|
43
|
+
- `fixCommand` — the command the refusals quote and the contract hands to an agent (the page prints
|
|
44
|
+
nothing of it). In the template it is
|
|
44
45
|
`node node_modules/@vernikr/size-report/bin/size.js --write` — the path to the installed package inside
|
|
45
46
|
the project. **The package name as a command cannot stand here:** `npx <name>` in a project without the
|
|
46
47
|
installed package goes to the registry and pulls the package over the network, so the advice that is meant
|