@vernikr/size-report 2.8.9 → 2.9.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/package.json +2 -2
- package/src/locales.js +2 -10
- package/src/page/app.css +18 -27
- package/src/page/app.js +0 -9
- package/src/page/build.js +13 -22
- package/src/page/panel.js +13 -21
- package/src/page/state.js +109 -22
- 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.0",
|
|
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.9",
|
|
69
69
|
"c8": "10",
|
|
70
70
|
"dependency-cruiser": "17",
|
|
71
71
|
"eslint": "^9.18.0",
|
package/src/locales.js
CHANGED
|
@@ -36,7 +36,6 @@ 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
|
+ 'Включите хотя бы одну в панели выше: без метрик нет ни одной колонки с числом.',
|
|
@@ -49,10 +48,7 @@ export const LOCALES = {
|
|
|
49
48
|
linkExtra: 'Часть ссылки пропущена: в этом отчёте нет {n} названных имён. '
|
|
50
49
|
+ 'Остальное применено.',
|
|
51
50
|
unpack: 'Данные отчёта сжаты, а браузер не умеет их распаковать: в нём нет DecompressionStream. '
|
|
52
|
-
+ 'Обновите браузер — таблицу показывать не из чего.'
|
|
53
|
-
note: 'Абсолютные размеры стоят один раз — в строке «{now}». Строк: {rows}: коммиты без '
|
|
54
|
-
+ 'изменения объёма и коммиты, тронувшие только выключенные файлы, строк не получают. '
|
|
55
|
-
+ 'Собрано из истории git: {command}.'
|
|
51
|
+
+ 'Обновите браузер — таблицу показывать не из чего.'
|
|
56
52
|
},
|
|
57
53
|
note: {
|
|
58
54
|
intro: 'Строка — коммит, колонка — файл. ',
|
|
@@ -94,7 +90,6 @@ export const LOCALES = {
|
|
|
94
90
|
categoryFromConfig: 'from the settings',
|
|
95
91
|
categoryByExtension: 'by extension',
|
|
96
92
|
all: 'all',
|
|
97
|
-
sub: '{tool} {version} · {artifact}',
|
|
98
93
|
panelMethod: 'method:',
|
|
99
94
|
emptyMetrics: 'No metric is selected — there is nothing to build the table from. '
|
|
100
95
|
+ 'Turn on at least one above: without metrics there is no column holding a number.',
|
|
@@ -109,10 +104,7 @@ export const LOCALES = {
|
|
|
109
104
|
/* The report's data lies in the file compressed and the browser unpacks it; this text is what a
|
|
110
105
|
* browser without `DecompressionStream` gets instead of an empty table. */
|
|
111
106
|
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}.'
|
|
107
|
+
+ 'DecompressionStream. Update the browser — there is nothing to build the table from.'
|
|
116
108
|
},
|
|
117
109
|
note: {
|
|
118
110
|
intro: 'A row is a commit, a column is a file. ',
|
package/src/page/app.css
CHANGED
|
@@ -31,11 +31,11 @@ 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, and the title belongs where the file it
|
|
38
|
+
* names is read from (see the markup in `src/page/build.js`). */
|
|
39
39
|
.panel {
|
|
40
40
|
margin: 0 0 var(--gap);
|
|
41
41
|
padding: 12px 14px 13px;
|
|
@@ -54,10 +54,6 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
54
54
|
letter-spacing: .07em;
|
|
55
55
|
}
|
|
56
56
|
.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
57
|
|
|
62
58
|
/* 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
59
|
* answers for the whole subtree, while the number beside it says for how many files. The list is longer than the window,
|
|
@@ -155,9 +151,6 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
155
151
|
}
|
|
156
152
|
.state[hidden], .shell[hidden] { display: none; }
|
|
157
153
|
|
|
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
154
|
/* The keyboard: the focus ring is visible on any background (the system accent colour) and shifts no layout. Journal links
|
|
162
155
|
* and every switch are reachable with Tab. */
|
|
163
156
|
:focus-visible { outline: 2px solid AccentColor; outline-offset: 2px; border-radius: 3px; }
|
|
@@ -169,10 +162,11 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
169
162
|
*
|
|
170
163
|
* The layout is a grid on `body` rather than a wrapper in the markup: the page is assembled by pasting chapters
|
|
171
164
|
* (`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
|
-
* working row, inside which it scrolls: a file
|
|
165
|
+
* instead of one. There are three rows, named by subject: the message about a link, the **working row** and the empty
|
|
166
|
+
* state — while the panel of choices spans them all in the left column, its heading inside it. Only the working row
|
|
167
|
+
* stretches — the table gets all the remaining height and the panel no more than that; the panel's own height is not
|
|
168
|
+
* "whatever came out" (it would push the table off the screen) but that same working row, inside which it scrolls: a file
|
|
169
|
+
* list longer than the window is the usual case.
|
|
176
170
|
*
|
|
177
171
|
* The threshold of 900px is the same as the adaptations' below: one border between "wide" and "narrow", or the page would
|
|
178
172
|
* be left with no rule at all between two thresholds. */
|
|
@@ -185,21 +179,18 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
185
179
|
overflow: auto;
|
|
186
180
|
display: grid;
|
|
187
181
|
grid-template-columns: minmax(230px, 300px) minmax(0, 1fr);
|
|
188
|
-
grid-template-rows: auto
|
|
182
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
189
183
|
column-gap: var(--gap);
|
|
190
184
|
align-items: stretch;
|
|
191
185
|
}
|
|
192
|
-
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
#state { grid-area: 4 / 2 / 5 / 3; align-self: start; }
|
|
196
|
-
#note { grid-area: 5 / 2 / 6 / 3; }
|
|
186
|
+
#notice { grid-area: 1 / 2 / 2 / 3; }
|
|
187
|
+
#shell { grid-area: 2 / 2 / 3 / 3; }
|
|
188
|
+
#state { grid-area: 3 / 2 / 4 / 3; align-self: start; }
|
|
197
189
|
/* 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; }
|
|
190
|
+
* page, and its top padding is gone: the heading carries that room instead, and it travels away with the list, while the
|
|
191
|
+
* sticky row of categories stands flush with the card's edge rather than under a band of passing rows. */
|
|
192
|
+
.panel { grid-area: 1 / 1 / -1 / 2; min-height: 0; margin-bottom: 0; padding-top: 0; overflow: auto; }
|
|
193
|
+
.panel h1 { padding-top: 12px; }
|
|
203
194
|
/* 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
195
|
* where the whole page scrolls. */
|
|
205
196
|
.shell { max-height: none; }
|
|
@@ -217,7 +208,7 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
217
208
|
}
|
|
218
209
|
/* Without metrics there is no table, and a stretched empty row has no place there: the empty state takes the free space of
|
|
219
210
|
* that row rather than the band above it. */
|
|
220
|
-
body:has(#shell[hidden]) #state { grid-area:
|
|
211
|
+
body:has(#shell[hidden]) #state { grid-area: 2 / 2 / 3 / 3; }
|
|
221
212
|
}
|
|
222
213
|
|
|
223
214
|
/* Adaptations: the one place where the page overrides the shared geometry — because the shared part is frozen by the
|
|
@@ -226,7 +217,7 @@ h1 { margin: 0 0 3px; font-size: 21px; font-weight: 650; letter-spacing: -.012em
|
|
|
226
217
|
* empty space under it at that single window size. */
|
|
227
218
|
@media (max-width: 899px) {
|
|
228
219
|
body { padding: 14px 14px 32px; }
|
|
229
|
-
h1 { font-size:
|
|
220
|
+
h1 { font-size: 19px; }
|
|
230
221
|
#grid { font-size: 12px; }
|
|
231
222
|
.shell { max-height: calc(100vh - 260px); }
|
|
232
223
|
/* 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) {
|
|
@@ -114,9 +104,7 @@ function uiText(page, loc) {
|
|
|
114
104
|
categoryByExtension: page.categoryByExtension,
|
|
115
105
|
methodLabel: page.panelMethod,
|
|
116
106
|
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)
|
|
107
|
+
noFiles: page.noFiles
|
|
120
108
|
};
|
|
121
109
|
}
|
|
122
110
|
|
|
@@ -208,13 +196,13 @@ export function pagePayload(data) {
|
|
|
208
196
|
const out = {
|
|
209
197
|
schema: 2,
|
|
210
198
|
tool: data.tool,
|
|
211
|
-
/* The report's own words are the ones the page reads: `heading` is the artifact's `<h1
|
|
212
|
-
* today,
|
|
199
|
+
/* The report's own words are the ones the page reads: `heading` is the artifact's `<h1>`, built before the block,
|
|
200
|
+
* `journal` is null today, and `fixCommand` was quoted by the note under the table, which the page no longer has —
|
|
201
|
+
* the page builds no heading, prints no journal and suggests no command. */
|
|
213
202
|
report: {
|
|
214
203
|
locale: data.report.locale,
|
|
215
204
|
title: data.report.title,
|
|
216
205
|
artifact: data.report.artifact,
|
|
217
|
-
fixCommand: data.report.fixCommand,
|
|
218
206
|
showSha: data.report.showSha
|
|
219
207
|
},
|
|
220
208
|
hrefPrefix: prefix,
|
|
@@ -248,20 +236,23 @@ export function pagePacked(text) {
|
|
|
248
236
|
}
|
|
249
237
|
|
|
250
238
|
/* 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.
|
|
239
|
+
* it opens with a double click and works without a network.
|
|
240
|
+
*
|
|
241
|
+
* The page's chrome is the panel and the table, and nothing stands above the numbers: the report's heading opens the
|
|
242
|
+
* panel (the one place that holds the reader's own controls) rather than a band of its own, and neither the tool, nor the
|
|
243
|
+
* version, nor the artifact's path is printed anywhere — the file is opened from a directory whose name already says
|
|
244
|
+
* where it lies. The panel's card is the markup's and the fields inside it are the script's: `#panel` is emptied and
|
|
245
|
+
* rebuilt on every opening, which is why the heading is the card's other child rather than one more field. */
|
|
252
246
|
export function pageHtml(data, cfg) {
|
|
253
247
|
const loc = LOCALES[cfg.locale];
|
|
254
248
|
return '<!doctype html>\n<html lang="' + esc(loc.html) + '">\n<head>\n<meta charset="utf-8">\n'
|
|
255
249
|
+ '<meta name="viewport" content="width=device-width, initial-scale=1">\n'
|
|
256
250
|
+ '<title>' + esc(data.report.title) + '</title>\n<style>\n'
|
|
257
251
|
+ 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'
|
|
252
|
+
+ '<div class="panel">\n<h1>' + esc(data.report.heading) + '</h1>\n<div id="panel"></div>\n</div>\n'
|
|
261
253
|
+ '<p id="notice" class="notice" hidden></p>\n'
|
|
262
254
|
+ '<div id="shell" class="shell"><div id="grid" class="grid"></div></div>\n'
|
|
263
255
|
+ '<p id="state" class="state" hidden></p>\n'
|
|
264
|
-
+ '<p id="note" class="note"></p>\n'
|
|
265
256
|
+ '<script type="application/octet-stream" id="data" data-pack="base64+gzip">'
|
|
266
257
|
+ pagePacked(jsonInHtml(pagePayload(data))) + '</script>\n'
|
|
267
258
|
+ '<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,12 @@ 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 one place: the tooltip of the box that switches it. The
|
|
192
|
+
* token dictionary and the way of compression come from the settings of the run, and the page has nothing to switch
|
|
193
|
+
* them with, so the reader has to know with what he is counting — while the lines that said it in words under the
|
|
194
|
+
* switches took the room beside the numbers. */
|
|
191
195
|
appData.metrics.forEach((m) => {
|
|
192
|
-
const box = appBox(m.label, m.note, appView.metrics[m.key], (e) => {
|
|
196
|
+
const box = appBox(m.label, m.note + ' — ' + appUi.methodLabel + ' ' + m.method, appView.metrics[m.key], (e) => {
|
|
193
197
|
appView.metrics[m.key] = e.target.checked;
|
|
194
198
|
appSwitchMetric();
|
|
195
199
|
}, 'metric');
|
|
@@ -197,12 +201,6 @@ export function appPanel() {
|
|
|
197
201
|
mrow.appendChild(box);
|
|
198
202
|
});
|
|
199
203
|
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
204
|
panel.appendChild(metrics);
|
|
207
205
|
|
|
208
206
|
const files = appEl('fieldset', 'files');
|
|
@@ -211,8 +209,7 @@ export function appPanel() {
|
|
|
211
209
|
* in the wide layout, which is where the panel scrolls). */
|
|
212
210
|
const cats = appEl('div', 'row cats');
|
|
213
211
|
appData.categories.forEach((cat) => {
|
|
214
|
-
const idx =
|
|
215
|
-
appData.files.forEach((f, i) => { if (f.category === cat.key) idx.push(i); });
|
|
212
|
+
const idx = appCatOf(cat.key);
|
|
216
213
|
const box = appBox(cat.label, appUi.all + ' · ' + cat.label, idx.every((i) => appView.files[i]),
|
|
217
214
|
(e) => appSwitchGroup(idx, e.target.checked), 'all');
|
|
218
215
|
appFields.cat[cat.key] = box.querySelector('input');
|
|
@@ -246,19 +243,14 @@ function appDirState(path) {
|
|
|
246
243
|
input.indeterminate = on > 0 && on < boxes.length;
|
|
247
244
|
}
|
|
248
245
|
|
|
249
|
-
/* A category's field from its files — the same rule, taken from the data: a category's files are named by the
|
|
250
|
-
*
|
|
246
|
+
/* A category's field from its files — the same rule, taken from the data: a category's files are named by the category
|
|
247
|
+
* itself (`appCatOf`), and the boxes of the tree are a different view of the same files. */
|
|
251
248
|
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
|
-
});
|
|
249
|
+
const idx = appCatOf(key);
|
|
250
|
+
const on = idx.filter((i) => appView.files[i] === true).length;
|
|
259
251
|
const input = appFields.cat[key];
|
|
260
|
-
input.checked = on ===
|
|
261
|
-
input.indeterminate = on > 0 && on <
|
|
252
|
+
input.checked = on === idx.length;
|
|
253
|
+
input.indeterminate = on > 0 && on < idx.length;
|
|
262
254
|
}
|
|
263
255
|
|
|
264
256
|
/* 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/state.js
CHANGED
|
@@ -11,10 +11,12 @@ 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): the record is
|
|
15
|
-
* report
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* The panel remembers the reader's choice between visits ("the choice's memory" below): the record is addressed by the
|
|
15
|
+
* report rather than by a build of it (`appPassport`), it keeps the fact of a whole category where the reader decided by
|
|
16
|
+
* groups and the names of the files where he decided one by one, and only what is switched off is written down — so
|
|
17
|
+
* someone else's record is not applied while a vanished name simply means nothing. The record stays in the browser's
|
|
18
|
+
* memory and nowhere else: a report opened from disk keeps a clean address, and a link made in an earlier release is
|
|
19
|
+
* still read (`appLinkUse`).
|
|
18
20
|
*
|
|
19
21
|
* The page draws no conclusion about how a number was obtained: the method of each metric arrives in the data, and the
|
|
20
22
|
* page prints it. There is no second rule of counting here, and no vocabulary of precision either. */
|
|
@@ -44,7 +46,9 @@ let appFoldKey = null;
|
|
|
44
46
|
* choice's record (`appFileAt`), so the tree and the reader's memory cannot drift apart.
|
|
45
47
|
*
|
|
46
48
|
* The key the reader's memory lives under is counted here as well, because it is the report's passport: it depends
|
|
47
|
-
* on the data, and until the block is unpacked there is nothing to count it from.
|
|
49
|
+
* on the data, and until the block is unpacked there is nothing to count it from. The addresses an earlier naming of
|
|
50
|
+
* the passport left behind go away at the same moment (`appSweep`): the report is in a position to say what is dead,
|
|
51
|
+
* and nothing of the reader's memory has been read yet. */
|
|
48
52
|
export function appBoot(text) {
|
|
49
53
|
appData = appDecode(JSON.parse(text));
|
|
50
54
|
/* 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 +59,9 @@ export function appBoot(text) {
|
|
|
55
59
|
appMeasured = {};
|
|
56
60
|
appData.metrics.forEach((m) => { appView.metrics[m.key] = true; appMetric[m.key] = m; });
|
|
57
61
|
appData.files.forEach((_f, i) => { appView.files.push(true); appMeasured[appFileAt(i)] = i; });
|
|
58
|
-
appKey =
|
|
62
|
+
appKey = APP_FORM + appPassport();
|
|
59
63
|
appFoldKey = appKey + ':tree';
|
|
64
|
+
appSweep();
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
/* 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 +75,14 @@ let appTransient = false;
|
|
|
70
75
|
|
|
71
76
|
/* -------- the reader's memory of his choice -------- */
|
|
72
77
|
|
|
78
|
+
/* The form of the memory's address: the tool's namespace, a mark of the form, then the report's identity (`appPassport`
|
|
79
|
+
* counts it). The mark stands in the address because the identity was named differently once — in the first naming the
|
|
80
|
+
* columns stood inside it, so every build of the report had an address of its own and the reader's choice was left
|
|
81
|
+
* behind at the previous one — and because the mark is what lets this release tell the addresses it writes from the
|
|
82
|
+
* addresses of that naming (`appSweep`), without pretending to know whose record lies under the older one. */
|
|
83
|
+
const APP_STORE = 'size-report:';
|
|
84
|
+
const APP_FORM = APP_STORE + '2:';
|
|
85
|
+
|
|
73
86
|
/* 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
87
|
* there: that is the name it is recognised by in the report. */
|
|
75
88
|
export function appFileAt(i) {
|
|
@@ -77,6 +90,15 @@ export function appFileAt(i) {
|
|
|
77
90
|
return f.path === null ? f.paths[0] : f.path;
|
|
78
91
|
}
|
|
79
92
|
|
|
93
|
+
/* The files of one category, in the order of the data: a category is a group of files as the engine named it
|
|
94
|
+
* (`category`), and the record, the boxes of the panel and their fields all ask for that group here rather than walking
|
|
95
|
+
* the files a second time. */
|
|
96
|
+
export function appCatOf(key) {
|
|
97
|
+
const idx = [];
|
|
98
|
+
appData.files.forEach((f, i) => { if (f.category === key) idx.push(i); });
|
|
99
|
+
return idx;
|
|
100
|
+
}
|
|
101
|
+
|
|
80
102
|
/* The passport's fingerprint: an identifying mark of a record rather than protection against forgery, so 32 bits are
|
|
81
103
|
* enough (FNV-1a). */
|
|
82
104
|
function appHash(text) {
|
|
@@ -88,34 +110,88 @@ function appHash(text) {
|
|
|
88
110
|
return (h >>> 0).toString(16);
|
|
89
111
|
}
|
|
90
112
|
|
|
91
|
-
/* The report's passport: the tool's name, the data schema, the artifact's path, the title and the
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* the same report
|
|
95
|
-
*
|
|
113
|
+
/* The report's passport: the tool's name, the data schema, the artifact's path, the title and the oldest row of the
|
|
114
|
+
* history. It is what tells one report from another — the record's key is chosen by it, so a choice made in someone
|
|
115
|
+
* else's report is not picked up — and it is what makes the memory the report's rather than a build of it: every later
|
|
116
|
+
* build of the same report counts the same value and finds the same choice.
|
|
117
|
+
*
|
|
118
|
+
* The first four are what the report says about itself, and they are the same in a report of another project (both the
|
|
119
|
+
* artifact's path and the title are defaults there, and all `file://` pages share one memory), which is what the fifth
|
|
120
|
+
* is for: the oldest row is a commit of this history, the same in every clone and every build, and the one commit the
|
|
121
|
+
* history never moves — rows are built from the oldest commit up, so a commit appends rows and leaves that one where it
|
|
122
|
+
* stands.
|
|
123
|
+
*
|
|
124
|
+
* The package version and the top of the history are absent on purpose: updating the tool does not change what a column
|
|
125
|
+
* means, while a grown history is the very history the reader comes back to.
|
|
126
|
+
*
|
|
127
|
+
* The columns were named here once, and they are what does not belong here at all: their labels and their order move
|
|
128
|
+
* with the numbers — a project without settings pinned derives the columns on every run, ordered by size within an
|
|
129
|
+
* extension (`src/project.js`), so every commit that grew or shrank a file gave the report another passport and left the
|
|
130
|
+
* reader's choice at the old address. What a record holds are names, and a name the report no longer has means nothing
|
|
131
|
+
* (`appApply`), so the columns have nothing to guard in the passport.
|
|
96
132
|
*
|
|
97
133
|
* Counted once per document: it is a constant of the report, which depends on nothing the reader can change, and every
|
|
98
134
|
* click asks for it (the key of the memory and the passport of the record). A second count would be a second answer
|
|
99
|
-
* waiting to happen, and the
|
|
135
|
+
* waiting to happen, and the block it reads does not change while the page is open. */
|
|
100
136
|
let appPassportValue = null;
|
|
101
137
|
function appPassport() {
|
|
102
138
|
if (appPassportValue === null) {
|
|
139
|
+
const oldest = appData.rows.length === 0 ? '' : appData.rows[0].sha;
|
|
103
140
|
appPassportValue = appHash([appData.tool.name, appData.schema, appData.report.artifact,
|
|
104
|
-
appData.report.title,
|
|
141
|
+
appData.report.title, oldest].join('\n'));
|
|
105
142
|
}
|
|
106
143
|
return appPassportValue;
|
|
107
144
|
}
|
|
108
145
|
|
|
109
|
-
/*
|
|
110
|
-
*
|
|
111
|
-
* a
|
|
146
|
+
/* The addresses an earlier naming left in the browser: every one of them belonged to a single build of a report, and no
|
|
147
|
+
* report ever looks under them again — the memory is read under the address of the report's identity alone. A browser
|
|
148
|
+
* that has lived through a long history holds one such record per commit, so they go away, once, when a report of this
|
|
149
|
+
* release is opened.
|
|
150
|
+
*
|
|
151
|
+
* What is swept is the mark of the form rather than a claim about whose record it is: the older address was counted from
|
|
152
|
+
* the data, so the record under it cannot be attributed to a report — hence a memory left by an older release in this
|
|
153
|
+
* browser goes too, and it is one of a report that was not rebuilt since its reader last clicked (that release moved the
|
|
154
|
+
* address at the next build itself, leaving the memory behind). What this release writes is left alone: under it lies
|
|
155
|
+
* this report's memory and another report's, and the two are told apart by the identity rather than by the form. */
|
|
156
|
+
function appSweep() {
|
|
157
|
+
try {
|
|
158
|
+
for (let i = window.localStorage.length - 1; i >= 0; i--) {
|
|
159
|
+
const key = window.localStorage.key(i);
|
|
160
|
+
if (key !== null && key.indexOf(APP_STORE) === 0 && key.indexOf(APP_FORM) !== 0) {
|
|
161
|
+
window.localStorage.removeItem(key);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} catch (_e) {
|
|
165
|
+
/* There is no memory (the browser grants this page none): there is nothing to sweep either. */
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* One record of the choice: the facts of the groups where the reader decided by groups, the names of the files where he
|
|
170
|
+
* decided one by one, and only what is switched off.
|
|
171
|
+
*
|
|
172
|
+
* A category whose box is wholly off — or wholly on — is written down as that fact rather than as the state of every
|
|
173
|
+
* file in it: `cats` names the group and says where it stands, so a file that joins the category later is switched with
|
|
174
|
+
* it, which is exactly what the reader asked for when he switched the group, and the record does not grow with the
|
|
175
|
+
* project. A category in the third state cannot speak for his choice — the choice was made file by file — and the files
|
|
176
|
+
* he switched off go into the record by name (`files`), as they did before this chapter knew categories; such a category
|
|
177
|
+
* is **absent** from `cats`, which is how the record distinguishes "a group fact" from "no group fact to state".
|
|
178
|
+
*
|
|
179
|
+
* "Switched on" and "no record" are the same state, hence `null` rather than an empty record — turning every checkbox
|
|
180
|
+
* back on removes the record instead of leaving a trace indistinguishable from a choice. */
|
|
112
181
|
function appRecord() {
|
|
113
182
|
const metrics = {};
|
|
183
|
+
const cats = {};
|
|
114
184
|
const files = {};
|
|
115
185
|
appData.metrics.forEach((m) => { if (!appView.metrics[m.key]) metrics[m.key] = false; });
|
|
116
|
-
appData.
|
|
117
|
-
|
|
118
|
-
|
|
186
|
+
appData.categories.forEach((c) => {
|
|
187
|
+
const idx = appCatOf(c.key);
|
|
188
|
+
const on = idx.filter((i) => appView.files[i] === true).length;
|
|
189
|
+
if (on === idx.length || on === 0) { cats[c.key] = on === idx.length; return; }
|
|
190
|
+
idx.forEach((i) => { if (appView.files[i] !== true) files[appFileAt(i)] = false; });
|
|
191
|
+
});
|
|
192
|
+
const empty = Object.keys(metrics).length === 0 && Object.keys(files).length === 0
|
|
193
|
+
&& Object.keys(cats).every((key) => cats[key] === true);
|
|
194
|
+
return empty ? null : { v: 1, passport: appPassport(), metrics: metrics, cats: cats, files: files };
|
|
119
195
|
}
|
|
120
196
|
|
|
121
197
|
// Whether a record is ours and of the right format — one rule for the memory and the address alike.
|
|
@@ -232,13 +308,24 @@ export function appRead() {
|
|
|
232
308
|
return appLoad(appKey);
|
|
233
309
|
}
|
|
234
310
|
|
|
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
|
|
311
|
+
/* Applying goes by name: a file is recognised by its path, a metric by its key, a category by the key the engine gave
|
|
312
|
+
* it. A name the report does not hold matches nothing (a column was pointed at another path, a metric was dropped from
|
|
313
|
+
* the settings), while files, categories and metrics that appeared stay switched on — the way someone opening the page
|
|
314
|
+
* for the first time sees them.
|
|
315
|
+
*
|
|
316
|
+
* A category's fact is applied to the files of that category as they are now, and the names are applied after it: the
|
|
317
|
+
* file-by-file decision is the finer one and stands last, and a record of an earlier release — which knew no categories
|
|
318
|
+
* and wrote only names — is applied by its names exactly as it was. */
|
|
238
319
|
export function appApply(rec) {
|
|
239
320
|
const metrics = rec.metrics || {};
|
|
321
|
+
const cats = rec.cats || {};
|
|
240
322
|
const files = rec.files || {};
|
|
241
323
|
appData.metrics.forEach((m) => { if (metrics[m.key] === false) appView.metrics[m.key] = false; });
|
|
324
|
+
appData.categories.forEach((c) => {
|
|
325
|
+
const on = cats[c.key];
|
|
326
|
+
if (on !== true && on !== false) return;
|
|
327
|
+
appCatOf(c.key).forEach((i) => { appView.files[i] = on; });
|
|
328
|
+
});
|
|
242
329
|
appData.files.forEach((_f, i) => { if (files[appFileAt(i)] === false) appView.files[i] = false; });
|
|
243
330
|
}
|
|
244
331
|
|
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
|