vastgrid 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -13
- package/dist/canvas/geometry.d.ts +92 -0
- package/dist/canvas/geometry.d.ts.map +1 -0
- package/dist/canvas/geometry.js +197 -0
- package/dist/canvas/geometry.js.map +1 -0
- package/dist/canvas/grid-canvas.d.ts +161 -0
- package/dist/canvas/grid-canvas.d.ts.map +1 -0
- package/dist/canvas/grid-canvas.js +1146 -0
- package/dist/canvas/grid-canvas.js.map +1 -0
- package/dist/canvas/hooks.d.ts +61 -0
- package/dist/canvas/hooks.d.ts.map +1 -0
- package/dist/canvas/hooks.js +2 -0
- package/dist/canvas/hooks.js.map +1 -0
- package/dist/canvas/keys.d.ts +26 -0
- package/dist/canvas/keys.d.ts.map +1 -0
- package/dist/canvas/keys.js +133 -0
- package/dist/canvas/keys.js.map +1 -0
- package/dist/canvas/layout.d.ts +26 -0
- package/dist/canvas/layout.d.ts.map +1 -0
- package/dist/canvas/layout.js +73 -0
- package/dist/canvas/layout.js.map +1 -0
- package/dist/canvas/scroll.d.ts +31 -0
- package/dist/canvas/scroll.d.ts.map +1 -0
- package/dist/canvas/scroll.js +111 -0
- package/dist/canvas/scroll.js.map +1 -0
- package/dist/canvas/text.d.ts +23 -0
- package/dist/canvas/text.d.ts.map +1 -0
- package/dist/canvas/text.js +142 -0
- package/dist/canvas/text.js.map +1 -0
- package/dist/{theme.d.ts → canvas/theme.d.ts} +8 -0
- package/dist/canvas/theme.d.ts.map +1 -0
- package/dist/canvas/theme.js +62 -0
- package/dist/canvas/theme.js.map +1 -0
- package/dist/core/cache.d.ts +48 -0
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +222 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/checked.d.ts +20 -0
- package/dist/core/checked.d.ts.map +1 -0
- package/dist/core/checked.js +61 -0
- package/dist/core/checked.js.map +1 -0
- package/dist/core/client.d.ts +33 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +103 -0
- package/dist/core/client.js.map +1 -0
- package/dist/core/conditions.d.ts +48 -0
- package/dist/core/conditions.d.ts.map +1 -0
- package/dist/core/conditions.js +105 -0
- package/dist/core/conditions.js.map +1 -0
- package/dist/core/evaluate.d.ts +20 -0
- package/dist/core/evaluate.d.ts.map +1 -0
- package/dist/core/evaluate.js +210 -0
- package/dist/core/evaluate.js.map +1 -0
- package/dist/core/filters.d.ts +51 -0
- package/dist/core/filters.d.ts.map +1 -0
- package/dist/core/filters.js +303 -0
- package/dist/core/filters.js.map +1 -0
- package/dist/core/format.d.ts +14 -0
- package/dist/core/format.d.ts.map +1 -0
- package/dist/core/format.js +303 -0
- package/dist/core/format.js.map +1 -0
- package/dist/core/measures.d.ts +26 -0
- package/dist/core/measures.d.ts.map +1 -0
- package/dist/core/measures.js +105 -0
- package/dist/core/measures.js.map +1 -0
- package/dist/core/request-ids.d.ts +20 -0
- package/dist/core/request-ids.d.ts.map +1 -0
- package/dist/core/request-ids.js +55 -0
- package/dist/core/request-ids.js.map +1 -0
- package/dist/core/rows.d.ts +79 -0
- package/dist/core/rows.d.ts.map +1 -0
- package/dist/core/rows.js +173 -0
- package/dist/core/rows.js.map +1 -0
- package/dist/core/store.d.ts +233 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +1162 -0
- package/dist/core/store.js.map +1 -0
- package/dist/core/totals.d.ts +24 -0
- package/dist/core/totals.d.ts.map +1 -0
- package/dist/core/totals.js +49 -0
- package/dist/core/totals.js.map +1 -0
- package/dist/core/trace.d.ts +7 -0
- package/dist/core/trace.d.ts.map +1 -0
- package/dist/core/trace.js +26 -0
- package/dist/core/trace.js.map +1 -0
- package/dist/core/transports.d.ts +50 -0
- package/dist/core/transports.d.ts.map +1 -0
- package/dist/core/transports.js +160 -0
- package/dist/core/transports.js.map +1 -0
- package/dist/core/types.d.ts +150 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +45 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core.d.ts +12 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +11 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +15 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -7
- package/dist/index.js.map +1 -1
- package/dist/react/accessible-rows.d.ts +13 -0
- package/dist/react/accessible-rows.d.ts.map +1 -0
- package/dist/react/accessible-rows.js +320 -0
- package/dist/react/accessible-rows.js.map +1 -0
- package/dist/react/cell-editor.d.ts +22 -0
- package/dist/react/cell-editor.d.ts.map +1 -0
- package/dist/react/cell-editor.js +431 -0
- package/dist/react/cell-editor.js.map +1 -0
- package/dist/react/cell-menu.d.ts +18 -0
- package/dist/react/cell-menu.d.ts.map +1 -0
- package/dist/react/cell-menu.js +299 -0
- package/dist/react/cell-menu.js.map +1 -0
- package/dist/react/column-menu.d.ts +16 -0
- package/dist/react/column-menu.d.ts.map +1 -0
- package/dist/react/column-menu.js +1311 -0
- package/dist/react/column-menu.js.map +1 -0
- package/dist/react/columns-menu.d.ts +7 -0
- package/dist/react/columns-menu.d.ts.map +1 -0
- package/dist/react/columns-menu.js +249 -0
- package/dist/react/columns-menu.js.map +1 -0
- package/dist/react/conditions-menu.d.ts +10 -0
- package/dist/react/conditions-menu.d.ts.map +1 -0
- package/dist/react/conditions-menu.js +795 -0
- package/dist/react/conditions-menu.js.map +1 -0
- package/dist/react/context.d.ts +29 -0
- package/dist/react/context.d.ts.map +1 -0
- package/dist/react/context.js +18 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/data-grid.d.ts +79 -0
- package/dist/react/data-grid.d.ts.map +1 -0
- package/dist/react/data-grid.js +653 -0
- package/dist/react/data-grid.js.map +1 -0
- package/dist/react/detail-panel.d.ts +22 -0
- package/dist/react/detail-panel.d.ts.map +1 -0
- package/dist/react/detail-panel.js +242 -0
- package/dist/react/detail-panel.js.map +1 -0
- package/dist/react/export-menu.d.ts +11 -0
- package/dist/react/export-menu.d.ts.map +1 -0
- package/dist/react/export-menu.js +304 -0
- package/dist/react/export-menu.js.map +1 -0
- package/dist/react/find-bar.d.ts +11 -0
- package/dist/react/find-bar.d.ts.map +1 -0
- package/dist/react/find-bar.js +303 -0
- package/dist/react/find-bar.js.map +1 -0
- package/dist/react/format-form.d.ts +12 -0
- package/dist/react/format-form.d.ts.map +1 -0
- package/dist/react/format-form.js +863 -0
- package/dist/react/format-form.js.map +1 -0
- package/dist/react/formula-menu.d.ts +6 -0
- package/dist/react/formula-menu.d.ts.map +1 -0
- package/dist/react/formula-menu.js +343 -0
- package/dist/react/formula-menu.js.map +1 -0
- package/dist/react/grid-body.d.ts +33 -0
- package/dist/react/grid-body.d.ts.map +1 -0
- package/dist/react/grid-body.js +1389 -0
- package/dist/react/grid-body.js.map +1 -0
- package/dist/react/group-menu.d.ts +7 -0
- package/dist/react/group-menu.d.ts.map +1 -0
- package/dist/react/group-menu.js +508 -0
- package/dist/react/group-menu.js.map +1 -0
- package/dist/react/hooks.d.ts +33 -0
- package/dist/react/hooks.d.ts.map +1 -0
- package/dist/react/hooks.js +161 -0
- package/dist/react/hooks.js.map +1 -0
- package/dist/react/messages.d.ts +180 -0
- package/dist/react/messages.d.ts.map +1 -0
- package/dist/react/messages.js +414 -0
- package/dist/react/messages.js.map +1 -0
- package/dist/react/save.d.ts +9 -0
- package/dist/react/save.d.ts.map +1 -0
- package/dist/react/save.js +102 -0
- package/dist/react/save.js.map +1 -0
- package/dist/react/static-rows.d.ts +9 -0
- package/dist/react/static-rows.d.ts.map +1 -0
- package/dist/react/static-rows.js +176 -0
- package/dist/react/static-rows.js.map +1 -0
- package/dist/react/status-bar.d.ts +12 -0
- package/dist/react/status-bar.d.ts.map +1 -0
- package/dist/react/status-bar.js +413 -0
- package/dist/react/status-bar.js.map +1 -0
- package/dist/react/toolbar-popover.d.ts +14 -0
- package/dist/react/toolbar-popover.d.ts.map +1 -0
- package/dist/react/toolbar-popover.js +143 -0
- package/dist/react/toolbar-popover.js.map +1 -0
- package/dist/react/toolbar.d.ts +13 -0
- package/dist/react/toolbar.d.ts.map +1 -0
- package/dist/react/toolbar.js +344 -0
- package/dist/react/toolbar.js.map +1 -0
- package/dist/server.d.ts +22 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +45 -0
- package/dist/server.js.map +1 -0
- package/dist/styles.css +496 -316
- package/package.json +42 -17
- package/src/canvas/geometry.ts +197 -0
- package/src/canvas/grid-canvas.ts +1222 -0
- package/src/canvas/hooks.ts +61 -0
- package/src/canvas/keys.ts +96 -0
- package/src/canvas/layout.ts +77 -0
- package/src/canvas/scroll.ts +120 -0
- package/src/canvas/text.ts +155 -0
- package/src/{theme.ts → canvas/theme.ts} +14 -1
- package/src/core/cache.ts +247 -0
- package/src/core/checked.ts +64 -0
- package/src/core/client.ts +103 -0
- package/src/core/conditions.ts +89 -0
- package/src/core/evaluate.ts +221 -0
- package/src/core/filters.ts +268 -0
- package/src/core/format.ts +309 -0
- package/src/core/measures.ts +99 -0
- package/src/core/request-ids.ts +54 -0
- package/src/core/rows.ts +213 -0
- package/src/core/store.ts +1122 -0
- package/src/core/totals.ts +59 -0
- package/src/core/trace.ts +44 -0
- package/src/core/transports.ts +177 -0
- package/src/core/types.ts +168 -0
- package/src/core.ts +39 -0
- package/src/index.ts +24 -12
- package/src/react/accessible-rows.tsx +242 -0
- package/src/react/cell-editor.tsx +216 -0
- package/src/react/cell-menu.tsx +166 -0
- package/src/react/column-menu.tsx +619 -0
- package/src/react/columns-menu.tsx +116 -0
- package/src/react/conditions-menu.tsx +289 -0
- package/src/react/context.ts +33 -0
- package/src/react/data-grid.tsx +286 -0
- package/src/react/detail-panel.tsx +92 -0
- package/src/react/export-menu.tsx +112 -0
- package/src/react/find-bar.tsx +112 -0
- package/src/react/format-form.tsx +421 -0
- package/src/react/formula-menu.tsx +163 -0
- package/src/react/grid-body.tsx +744 -0
- package/src/react/group-menu.tsx +214 -0
- package/src/react/hooks.ts +104 -0
- package/src/react/messages.ts +556 -0
- package/src/react/save.ts +42 -0
- package/src/react/static-rows.tsx +59 -0
- package/src/react/status-bar.tsx +175 -0
- package/src/react/toolbar-popover.tsx +69 -0
- package/src/react/toolbar.tsx +152 -0
- package/src/server.ts +58 -0
- package/src/styles.css +496 -316
- package/dist/components/bars.d.ts +0 -14
- package/dist/components/bars.d.ts.map +0 -1
- package/dist/components/bars.js +0 -68
- package/dist/components/bars.js.map +0 -1
- package/dist/components/cell-inspector.d.ts +0 -11
- package/dist/components/cell-inspector.d.ts.map +0 -1
- package/dist/components/cell-inspector.js +0 -37
- package/dist/components/cell-inspector.js.map +0 -1
- package/dist/components/columns-panel.d.ts +0 -12
- package/dist/components/columns-panel.d.ts.map +0 -1
- package/dist/components/columns-panel.js +0 -11
- package/dist/components/columns-panel.js.map +0 -1
- package/dist/components/filter-panel.d.ts +0 -22
- package/dist/components/filter-panel.d.ts.map +0 -1
- package/dist/components/filter-panel.js +0 -169
- package/dist/components/filter-panel.js.map +0 -1
- package/dist/components/find-bar.d.ts +0 -9
- package/dist/components/find-bar.d.ts.map +0 -1
- package/dist/components/find-bar.js +0 -46
- package/dist/components/find-bar.js.map +0 -1
- package/dist/components/popover.d.ts +0 -38
- package/dist/components/popover.d.ts.map +0 -1
- package/dist/components/popover.js +0 -104
- package/dist/components/popover.js.map +0 -1
- package/dist/components/stats-panel.d.ts +0 -12
- package/dist/components/stats-panel.d.ts.map +0 -1
- package/dist/components/stats-panel.js +0 -40
- package/dist/components/stats-panel.js.map +0 -1
- package/dist/controller.d.ts +0 -227
- package/dist/controller.d.ts.map +0 -1
- package/dist/controller.js +0 -845
- package/dist/controller.js.map +0 -1
- package/dist/data-grid.d.ts +0 -51
- package/dist/data-grid.d.ts.map +0 -1
- package/dist/data-grid.js +0 -704
- package/dist/data-grid.js.map +0 -1
- package/dist/labels.d.ts +0 -9
- package/dist/labels.d.ts.map +0 -1
- package/dist/labels.js +0 -38
- package/dist/labels.js.map +0 -1
- package/dist/messages.d.ts +0 -99
- package/dist/messages.d.ts.map +0 -1
- package/dist/messages.js +0 -276
- package/dist/messages.js.map +0 -1
- package/dist/renderer.d.ts +0 -52
- package/dist/renderer.d.ts.map +0 -1
- package/dist/renderer.js +0 -522
- package/dist/renderer.js.map +0 -1
- package/dist/text.d.ts +0 -14
- package/dist/text.d.ts.map +0 -1
- package/dist/text.js +0 -63
- package/dist/text.js.map +0 -1
- package/dist/theme.d.ts.map +0 -1
- package/dist/theme.js +0 -54
- package/dist/theme.js.map +0 -1
- package/src/components/bars.tsx +0 -180
- package/src/components/cell-inspector.tsx +0 -70
- package/src/components/columns-panel.tsx +0 -59
- package/src/components/filter-panel.tsx +0 -293
- package/src/components/find-bar.tsx +0 -104
- package/src/components/popover.tsx +0 -194
- package/src/components/stats-panel.tsx +0 -153
- package/src/controller.ts +0 -945
- package/src/data-grid.tsx +0 -1035
- package/src/labels.ts +0 -46
- package/src/messages.ts +0 -390
- package/src/renderer.ts +0 -535
- package/src/text.ts +0 -55
package/dist/controller.js
DELETED
|
@@ -1,845 +0,0 @@
|
|
|
1
|
-
import { ColumnLayout, ErrorCodes, isAbortError, isEmptyQuery, isNumericType, MAX_TEXT_LIMIT, plainText, RowCache, ScrollAxis, SelectionModel, toHtmlTable, toMarkdownTable, toTsv, ValueFormatter, } from "vastgrid-core";
|
|
2
|
-
export class CopyTooLargeError extends Error {
|
|
3
|
-
limit;
|
|
4
|
-
constructor(limit) {
|
|
5
|
-
super(`Select at most ${limit} cells to copy.`);
|
|
6
|
-
this.limit = limit;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
const defaultWidths = {
|
|
10
|
-
integer: 96,
|
|
11
|
-
bigint: 120,
|
|
12
|
-
float: 110,
|
|
13
|
-
decimal: 110,
|
|
14
|
-
boolean: 72,
|
|
15
|
-
date: 104,
|
|
16
|
-
time: 96,
|
|
17
|
-
timestamp: 176,
|
|
18
|
-
timestamptz: 196,
|
|
19
|
-
uuid: 290,
|
|
20
|
-
};
|
|
21
|
-
/** Header space around the name: padding, the type mark and the menu button. */
|
|
22
|
-
const HEADER_CHROME = 62;
|
|
23
|
-
function sleep(ms, signal) {
|
|
24
|
-
return new Promise((resolve) => {
|
|
25
|
-
const timer = setTimeout(resolve, ms);
|
|
26
|
-
signal.addEventListener("abort", () => {
|
|
27
|
-
clearTimeout(timer);
|
|
28
|
-
resolve();
|
|
29
|
-
}, { once: true });
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
function messageOf(error) {
|
|
33
|
-
return error instanceof Error ? error.message : String(error);
|
|
34
|
-
}
|
|
35
|
-
/** The stable `code` of a source's error (see `SourceError`), if it has one. */
|
|
36
|
-
function codeOf(error) {
|
|
37
|
-
return error instanceof Error && "code" in error ? error.code : undefined;
|
|
38
|
-
}
|
|
39
|
-
/** Whether a loaded value may be text that the source cut for display (it then ends with "…"). */
|
|
40
|
-
function mayBeCut(value) {
|
|
41
|
-
return typeof value === "string" && value.endsWith("…");
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* State and behavior of one grid: the table and its view, columns, selection, scrolling and the row
|
|
45
|
-
* cache. It is independent of React; the component forwards DOM events here and draws with
|
|
46
|
-
* `CanvasRenderer`. Frequent changes (scrolling, hover) only request a frame; changes that the React
|
|
47
|
-
* UI shows bump `version` and notify subscribers.
|
|
48
|
-
*/
|
|
49
|
-
export class GridController {
|
|
50
|
-
source;
|
|
51
|
-
options;
|
|
52
|
-
cache;
|
|
53
|
-
selection = new SelectionModel();
|
|
54
|
-
scrollX = new ScrollAxis();
|
|
55
|
-
scrollY = new ScrollAxis();
|
|
56
|
-
formatter;
|
|
57
|
-
/** Changes whenever `formatter` is replaced, so that cached display text is rebuilt. */
|
|
58
|
-
formatVersion = 0;
|
|
59
|
-
info = null;
|
|
60
|
-
loadError = null;
|
|
61
|
-
query = {};
|
|
62
|
-
view = { id: null, rowCount: 0, query: {} };
|
|
63
|
-
viewPending = false;
|
|
64
|
-
viewError = null;
|
|
65
|
-
columns = [];
|
|
66
|
-
visible = [];
|
|
67
|
-
layout = new ColumnLayout([]);
|
|
68
|
-
frozen = 0;
|
|
69
|
-
frozenWidth = 0;
|
|
70
|
-
gutterWidth = 48;
|
|
71
|
-
width = 0;
|
|
72
|
-
height = 0;
|
|
73
|
-
hover = null;
|
|
74
|
-
/** The visible column being resized, for drawing the guide. */
|
|
75
|
-
resizing = -1;
|
|
76
|
-
findText = "";
|
|
77
|
-
findCaseSensitive = false;
|
|
78
|
-
summary = null;
|
|
79
|
-
summaryPending = false;
|
|
80
|
-
/** Increments on every change shown by the React UI. */
|
|
81
|
-
version = 0;
|
|
82
|
-
/** Measures text in the cell or header font; set by the renderer. */
|
|
83
|
-
measure = null;
|
|
84
|
-
scroller = null;
|
|
85
|
-
sizer = null;
|
|
86
|
-
stateListeners = new Set();
|
|
87
|
-
renderListeners = new Set();
|
|
88
|
-
lifecycle = new AbortController();
|
|
89
|
-
viewAbort = null;
|
|
90
|
-
summaryAbort = null;
|
|
91
|
-
summaryTimer;
|
|
92
|
-
autoSized = false;
|
|
93
|
-
disposed = false;
|
|
94
|
-
polling = false;
|
|
95
|
-
activeValue;
|
|
96
|
-
constructor(source, options) {
|
|
97
|
-
this.source = source;
|
|
98
|
-
this.options = options;
|
|
99
|
-
this.formatter = new ValueFormatter({ locale: options.locale, nullText: options.nullText });
|
|
100
|
-
this.cache = new RowCache(source);
|
|
101
|
-
this.cache.onChange(() => {
|
|
102
|
-
this.autoSize();
|
|
103
|
-
this.requestRender();
|
|
104
|
-
const active = this.selection.active;
|
|
105
|
-
const value = this.value(active.row, active.col);
|
|
106
|
-
if (value !== this.activeValue) {
|
|
107
|
-
this.activeValue = value;
|
|
108
|
-
this.emit();
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
/** Changes the locale and the text for blanks. */
|
|
113
|
-
setFormat(locale, nullText) {
|
|
114
|
-
this.options.locale = locale;
|
|
115
|
-
this.options.nullText = nullText;
|
|
116
|
-
this.formatter = new ValueFormatter({ locale, nullText });
|
|
117
|
-
this.formatVersion++;
|
|
118
|
-
this.requestRender();
|
|
119
|
-
}
|
|
120
|
-
// ----- subscriptions -----
|
|
121
|
-
subscribe = (listener) => {
|
|
122
|
-
this.stateListeners.add(listener);
|
|
123
|
-
return () => {
|
|
124
|
-
this.stateListeners.delete(listener);
|
|
125
|
-
};
|
|
126
|
-
};
|
|
127
|
-
getVersion = () => this.version;
|
|
128
|
-
onRender(listener) {
|
|
129
|
-
this.renderListeners.add(listener);
|
|
130
|
-
return () => {
|
|
131
|
-
this.renderListeners.delete(listener);
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
emit() {
|
|
135
|
-
this.version++;
|
|
136
|
-
for (const listener of this.stateListeners)
|
|
137
|
-
listener();
|
|
138
|
-
}
|
|
139
|
-
requestRender() {
|
|
140
|
-
for (const listener of this.renderListeners)
|
|
141
|
-
listener();
|
|
142
|
-
}
|
|
143
|
-
// ----- lifecycle -----
|
|
144
|
-
attach(scroller, sizer) {
|
|
145
|
-
this.scroller = scroller;
|
|
146
|
-
this.sizer = sizer;
|
|
147
|
-
this.syncSizer();
|
|
148
|
-
}
|
|
149
|
-
/** Starts reading the table. Safe to call again after `stop()`. */
|
|
150
|
-
start() {
|
|
151
|
-
if (this.polling && !this.lifecycle.signal.aborted)
|
|
152
|
-
return;
|
|
153
|
-
if (this.lifecycle.signal.aborted)
|
|
154
|
-
this.lifecycle = new AbortController();
|
|
155
|
-
this.polling = true;
|
|
156
|
-
const signal = this.lifecycle.signal;
|
|
157
|
-
void this.poll(signal).finally(() => {
|
|
158
|
-
if (this.lifecycle.signal === signal)
|
|
159
|
-
this.polling = false;
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
/** Reads the table again (for example after the file changed). */
|
|
163
|
-
reload() {
|
|
164
|
-
this.lifecycle.abort();
|
|
165
|
-
this.polling = false;
|
|
166
|
-
this.loadError = null;
|
|
167
|
-
this.start();
|
|
168
|
-
}
|
|
169
|
-
/** Stops loading and cancels requests. `start()` resumes. */
|
|
170
|
-
stop() {
|
|
171
|
-
this.lifecycle.abort();
|
|
172
|
-
this.polling = false;
|
|
173
|
-
this.viewAbort?.abort();
|
|
174
|
-
this.summaryAbort?.abort();
|
|
175
|
-
clearTimeout(this.summaryTimer);
|
|
176
|
-
if (this.viewPending) {
|
|
177
|
-
this.viewPending = false;
|
|
178
|
-
this.emit();
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
dispose() {
|
|
182
|
-
this.disposed = true;
|
|
183
|
-
this.stop();
|
|
184
|
-
this.cache.clear();
|
|
185
|
-
this.stateListeners.clear();
|
|
186
|
-
this.renderListeners.clear();
|
|
187
|
-
}
|
|
188
|
-
async poll(signal) {
|
|
189
|
-
let delay = 100;
|
|
190
|
-
while (!signal.aborted) {
|
|
191
|
-
let info;
|
|
192
|
-
try {
|
|
193
|
-
info = await this.source.info(signal);
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
if (signal.aborted)
|
|
197
|
-
return;
|
|
198
|
-
if (isAbortError(error)) {
|
|
199
|
-
// Someone else's request was cancelled; ask again.
|
|
200
|
-
await sleep(delay, signal);
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
this.loadError = messageOf(error);
|
|
204
|
-
this.emit();
|
|
205
|
-
this.requestRender();
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (signal.aborted)
|
|
209
|
-
return;
|
|
210
|
-
this.applyInfo(info);
|
|
211
|
-
if (info.status !== "loading")
|
|
212
|
-
return;
|
|
213
|
-
await sleep(delay, signal);
|
|
214
|
-
delay = Math.min(500, delay * 1.4);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
applyInfo(info) {
|
|
218
|
-
const previous = this.info;
|
|
219
|
-
// A reopened file may not have its preview schema yet. Keep the old names until new names arrive.
|
|
220
|
-
if (previous && info.status === "loading" && info.columns.length === 0) {
|
|
221
|
-
info = { ...info, columns: previous.columns };
|
|
222
|
-
}
|
|
223
|
-
this.info = info;
|
|
224
|
-
this.loadError = null;
|
|
225
|
-
const names = (table) => table?.columns.map((column) => column.name).join("\u0000");
|
|
226
|
-
const types = (table) => table?.columns.map((column) => column.type).join("\u0000");
|
|
227
|
-
const reset = !previous || previous.revision !== info.revision || names(previous) !== names(info);
|
|
228
|
-
if (reset) {
|
|
229
|
-
if (previous?.columns.length && names(previous) !== names(info)) {
|
|
230
|
-
const indexOf = (column) => {
|
|
231
|
-
const name = previous.columns[column]?.name;
|
|
232
|
-
return name === undefined ? -1 : info.columns.findIndex((item) => item.name === name);
|
|
233
|
-
};
|
|
234
|
-
const sort = this.query.sort?.flatMap((key) => {
|
|
235
|
-
const column = indexOf(key.column);
|
|
236
|
-
return column < 0 ? [] : [{ ...key, column }];
|
|
237
|
-
});
|
|
238
|
-
const filters = this.query.filters?.flatMap((filter) => {
|
|
239
|
-
const column = indexOf(filter.column);
|
|
240
|
-
return column < 0 ? [] : [{ ...filter, column }];
|
|
241
|
-
});
|
|
242
|
-
this.query = { ...this.query, ...(sort ? { sort } : {}), ...(filters ? { filters } : {}) };
|
|
243
|
-
}
|
|
244
|
-
// Settings follow their columns by name, also when the file's columns moved.
|
|
245
|
-
const old = new Map(this.columns.map((column) => [previous?.columns[column.index]?.name ?? "", column]));
|
|
246
|
-
this.columns = info.columns.map((column, index) => {
|
|
247
|
-
const kept = previous && names(previous) === names(info)
|
|
248
|
-
? this.columns.find((item) => item.index === index)
|
|
249
|
-
: old.get(column.name);
|
|
250
|
-
return {
|
|
251
|
-
index,
|
|
252
|
-
width: kept?.width ?? defaultWidths[column.type] ?? 150,
|
|
253
|
-
hidden: kept?.hidden ?? false,
|
|
254
|
-
sized: kept?.sized ?? false,
|
|
255
|
-
};
|
|
256
|
-
});
|
|
257
|
-
if (!previous)
|
|
258
|
-
this.frozen = Math.min(this.options.frozenColumns, this.columns.length);
|
|
259
|
-
this.autoSized = this.columns.every((column) => column.sized);
|
|
260
|
-
this.viewAbort?.abort();
|
|
261
|
-
this.view = { id: null, rowCount: info.rowCount ?? info.readableRows, query: {} };
|
|
262
|
-
this.viewPending = false;
|
|
263
|
-
this.cache.clear();
|
|
264
|
-
this.selection.select({ row: 0, col: 0 });
|
|
265
|
-
// The summary of the previous selection no longer applies.
|
|
266
|
-
this.scheduleSummary();
|
|
267
|
-
}
|
|
268
|
-
else if (previous.status !== info.status && types(previous) !== types(info)) {
|
|
269
|
-
// The final types differ from the preview's (for example all columns read as text).
|
|
270
|
-
this.cache.clear();
|
|
271
|
-
}
|
|
272
|
-
if (this.view.id === null) {
|
|
273
|
-
this.view = { id: null, rowCount: info.rowCount ?? info.readableRows, query: {} };
|
|
274
|
-
}
|
|
275
|
-
this.updateLayout();
|
|
276
|
-
if (info.status === "ready" && (reset || previous?.status !== "ready") && !isEmptyQuery(this.query)) {
|
|
277
|
-
void this.setQuery(this.query);
|
|
278
|
-
}
|
|
279
|
-
this.emit();
|
|
280
|
-
this.requestRender();
|
|
281
|
-
}
|
|
282
|
-
// ----- derived values -----
|
|
283
|
-
get status() {
|
|
284
|
-
return this.loadError ? "error" : (this.info?.status ?? "loading");
|
|
285
|
-
}
|
|
286
|
-
get schema() {
|
|
287
|
-
return this.info?.columns ?? [];
|
|
288
|
-
}
|
|
289
|
-
/** Rows in the current view. */
|
|
290
|
-
get rowCount() {
|
|
291
|
-
return this.view.id !== null ? this.view.rowCount : (this.info?.rowCount ?? this.info?.readableRows ?? 0);
|
|
292
|
-
}
|
|
293
|
-
/** Rows that can be read now (less than `rowCount` while a table loads). */
|
|
294
|
-
get readableRows() {
|
|
295
|
-
return this.view.id !== null ? this.view.rowCount : (this.info?.readableRows ?? 0);
|
|
296
|
-
}
|
|
297
|
-
get totalRows() {
|
|
298
|
-
return this.info?.rowCount ?? null;
|
|
299
|
-
}
|
|
300
|
-
columnSchema(col) {
|
|
301
|
-
const state = this.visible[col];
|
|
302
|
-
return state ? this.schema[state.index] : undefined;
|
|
303
|
-
}
|
|
304
|
-
get bodyHeight() {
|
|
305
|
-
return Math.max(0, this.height - this.options.headerHeight);
|
|
306
|
-
}
|
|
307
|
-
/** Row labels: the position in the original table for views, otherwise the row number. */
|
|
308
|
-
rowLabel(row) {
|
|
309
|
-
if (this.view.id === null)
|
|
310
|
-
return row + 1;
|
|
311
|
-
const id = this.cache.rowId(row);
|
|
312
|
-
return id === undefined ? null : id + 1;
|
|
313
|
-
}
|
|
314
|
-
updateLayout() {
|
|
315
|
-
this.visible = this.columns.filter((column) => !column.hidden);
|
|
316
|
-
this.frozen = Math.min(this.frozen, this.visible.length);
|
|
317
|
-
this.layout = new ColumnLayout(this.visible.map((column) => column.width));
|
|
318
|
-
this.frozenWidth = this.layout.left(this.frozen);
|
|
319
|
-
const largest = Math.max(this.info?.rowCount ?? 0, this.rowCount, 1);
|
|
320
|
-
const digits = String(largest).length;
|
|
321
|
-
this.gutterWidth = Math.max(44, Math.ceil(this.measure ? this.measure("0".repeat(digits), false) : digits * 8) + 22);
|
|
322
|
-
this.selection.setBounds(this.rowCount, this.visible.length);
|
|
323
|
-
this.cache.setView(this.view.id, this.rowCount, this.readableRows, this.schema.length);
|
|
324
|
-
this.syncSizer();
|
|
325
|
-
}
|
|
326
|
-
/** Sizes the native scroll content and applies the current positions. */
|
|
327
|
-
syncSizer() {
|
|
328
|
-
const nativeWidth = this.scrollX.resize(this.gutterWidth + this.layout.total, this.width);
|
|
329
|
-
const nativeHeight = this.scrollY.resize(this.options.headerHeight + this.rowCount * this.options.rowHeight, this.height);
|
|
330
|
-
if (this.sizer) {
|
|
331
|
-
this.sizer.style.width = `${Math.ceil(nativeWidth)}px`;
|
|
332
|
-
this.sizer.style.height = `${Math.ceil(nativeHeight)}px`;
|
|
333
|
-
}
|
|
334
|
-
if (this.scroller) {
|
|
335
|
-
const left = this.scrollX.scrollTo(this.scrollX.position);
|
|
336
|
-
const top = this.scrollY.scrollTo(this.scrollY.position);
|
|
337
|
-
if (Math.abs(this.scroller.scrollLeft - left) > 1)
|
|
338
|
-
this.scroller.scrollLeft = left;
|
|
339
|
-
if (Math.abs(this.scroller.scrollTop - top) > 1)
|
|
340
|
-
this.scroller.scrollTop = top;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
setViewport(width, height) {
|
|
344
|
-
if (width === this.width && height === this.height)
|
|
345
|
-
return;
|
|
346
|
-
this.width = width;
|
|
347
|
-
this.height = height;
|
|
348
|
-
this.syncSizer();
|
|
349
|
-
this.requestRender();
|
|
350
|
-
}
|
|
351
|
-
handleScroll() {
|
|
352
|
-
if (!this.scroller)
|
|
353
|
-
return;
|
|
354
|
-
this.scrollX.onNativeScroll(this.scroller.scrollLeft);
|
|
355
|
-
this.scrollY.onNativeScroll(this.scroller.scrollTop);
|
|
356
|
-
this.requestRender();
|
|
357
|
-
}
|
|
358
|
-
/** Visible rows `[first, last)`. */
|
|
359
|
-
visibleRows() {
|
|
360
|
-
const rowHeight = this.options.rowHeight;
|
|
361
|
-
const first = Math.max(0, Math.floor(this.scrollY.position / rowHeight));
|
|
362
|
-
const last = Math.min(this.rowCount, Math.ceil((this.scrollY.position + this.bodyHeight) / rowHeight));
|
|
363
|
-
return [first, Math.max(first, last)];
|
|
364
|
-
}
|
|
365
|
-
/** Visible scrollable columns `[first, last]` (inclusive), or `[0, -1]`. */
|
|
366
|
-
visibleScrollColumns() {
|
|
367
|
-
const count = this.visible.length;
|
|
368
|
-
if (count <= this.frozen)
|
|
369
|
-
return [0, -1];
|
|
370
|
-
const start = this.frozenWidth + this.scrollX.position;
|
|
371
|
-
const end = this.scrollX.position + this.width - this.gutterWidth;
|
|
372
|
-
if (start >= this.layout.total)
|
|
373
|
-
return [0, -1];
|
|
374
|
-
const first = Math.max(this.frozen, this.layout.indexAt(start));
|
|
375
|
-
const last = Math.min(count - 1, this.layout.indexAt(Math.max(start, end - 0.001)));
|
|
376
|
-
return [first, last];
|
|
377
|
-
}
|
|
378
|
-
/** Screen x of a visible column's left edge. */
|
|
379
|
-
columnX(col) {
|
|
380
|
-
return col < this.frozen
|
|
381
|
-
? this.gutterWidth + this.layout.left(col)
|
|
382
|
-
: this.gutterWidth + this.layout.left(col) - this.scrollX.position;
|
|
383
|
-
}
|
|
384
|
-
rowY(row) {
|
|
385
|
-
return this.options.headerHeight + row * this.options.rowHeight - this.scrollY.position;
|
|
386
|
-
}
|
|
387
|
-
/** The visible column at screen x, or -1. */
|
|
388
|
-
columnAt(x) {
|
|
389
|
-
if (x < this.gutterWidth || this.visible.length === 0)
|
|
390
|
-
return -1;
|
|
391
|
-
const frozenRight = this.gutterWidth + this.frozenWidth;
|
|
392
|
-
if (x < frozenRight)
|
|
393
|
-
return this.layout.indexAt(x - this.gutterWidth);
|
|
394
|
-
const position = x - this.gutterWidth + this.scrollX.position;
|
|
395
|
-
if (position >= this.layout.total)
|
|
396
|
-
return -1;
|
|
397
|
-
return Math.max(this.frozen, this.layout.indexAt(position));
|
|
398
|
-
}
|
|
399
|
-
rowAt(y) {
|
|
400
|
-
if (y < this.options.headerHeight)
|
|
401
|
-
return -1;
|
|
402
|
-
const row = Math.floor((y - this.options.headerHeight + this.scrollY.position) / this.options.rowHeight);
|
|
403
|
-
return row >= 0 && row < this.rowCount ? row : -1;
|
|
404
|
-
}
|
|
405
|
-
hitTest(x, y) {
|
|
406
|
-
const header = y < this.options.headerHeight;
|
|
407
|
-
const gutter = x < this.gutterWidth;
|
|
408
|
-
const col = gutter ? -1 : this.columnAt(x);
|
|
409
|
-
const row = header ? -1 : this.rowAt(y);
|
|
410
|
-
let zone = "outside";
|
|
411
|
-
if (header && gutter)
|
|
412
|
-
zone = "corner";
|
|
413
|
-
else if (header && col >= 0)
|
|
414
|
-
zone = "header";
|
|
415
|
-
else if (gutter && row >= 0)
|
|
416
|
-
zone = "gutter";
|
|
417
|
-
else if (row >= 0 && col >= 0)
|
|
418
|
-
zone = "cell";
|
|
419
|
-
let resize = -1;
|
|
420
|
-
let menu = false;
|
|
421
|
-
if (header && !gutter) {
|
|
422
|
-
const nearest = col >= 0 ? col : this.visible.length - 1;
|
|
423
|
-
const right = this.columnX(nearest) + this.layout.width(nearest);
|
|
424
|
-
const left = this.columnX(nearest);
|
|
425
|
-
if (Math.abs(x - right) <= 5)
|
|
426
|
-
resize = nearest;
|
|
427
|
-
else if (nearest > 0 && Math.abs(x - left) <= 5 && (nearest !== this.frozen || this.scrollX.position === 0))
|
|
428
|
-
resize = nearest - 1;
|
|
429
|
-
menu = col >= 0 && resize < 0 && x >= right - 24 && x < right - 4;
|
|
430
|
-
}
|
|
431
|
-
return { zone, row, col, resize, menu };
|
|
432
|
-
}
|
|
433
|
-
// ----- scrolling -----
|
|
434
|
-
scrollToVirtual(left, top) {
|
|
435
|
-
if (!this.scroller)
|
|
436
|
-
return;
|
|
437
|
-
if (top !== null)
|
|
438
|
-
this.scroller.scrollTop = this.scrollY.scrollTo(top);
|
|
439
|
-
if (left !== null)
|
|
440
|
-
this.scroller.scrollLeft = this.scrollX.scrollTo(left);
|
|
441
|
-
this.requestRender();
|
|
442
|
-
}
|
|
443
|
-
/** Scrolls the least amount that shows the cell. */
|
|
444
|
-
ensureVisible(row, col) {
|
|
445
|
-
const rowHeight = this.options.rowHeight;
|
|
446
|
-
let top = null;
|
|
447
|
-
if (row >= 0) {
|
|
448
|
-
const y = row * rowHeight;
|
|
449
|
-
if (y < this.scrollY.position)
|
|
450
|
-
top = y;
|
|
451
|
-
else if (y + rowHeight > this.scrollY.position + this.bodyHeight)
|
|
452
|
-
top = y + rowHeight - this.bodyHeight;
|
|
453
|
-
}
|
|
454
|
-
let left = null;
|
|
455
|
-
if (col >= this.frozen && col < this.visible.length) {
|
|
456
|
-
const start = this.layout.left(col) - this.frozenWidth;
|
|
457
|
-
const end = this.layout.right(col) - this.frozenWidth;
|
|
458
|
-
const view = this.width - this.gutterWidth - this.frozenWidth;
|
|
459
|
-
if (start < this.scrollX.position)
|
|
460
|
-
left = start;
|
|
461
|
-
else if (end > this.scrollX.position + view)
|
|
462
|
-
left = Math.min(start, end - view);
|
|
463
|
-
}
|
|
464
|
-
if (top !== null || left !== null)
|
|
465
|
-
this.scrollToVirtual(left, top);
|
|
466
|
-
}
|
|
467
|
-
// ----- selection -----
|
|
468
|
-
/** Rows a page key moves. */
|
|
469
|
-
get pageRows() {
|
|
470
|
-
return Math.max(1, Math.floor(this.bodyHeight / this.options.rowHeight) - 1);
|
|
471
|
-
}
|
|
472
|
-
selectionChanged(scrollTo) {
|
|
473
|
-
if (scrollTo)
|
|
474
|
-
this.ensureVisible(scrollTo.row, scrollTo.col);
|
|
475
|
-
this.scheduleSummary();
|
|
476
|
-
this.requestRender();
|
|
477
|
-
this.emit();
|
|
478
|
-
}
|
|
479
|
-
scheduleSummary() {
|
|
480
|
-
clearTimeout(this.summaryTimer);
|
|
481
|
-
this.summaryAbort?.abort();
|
|
482
|
-
const ranges = this.selectionRanges();
|
|
483
|
-
const cells = ranges.reduce((total, range) => total + (range.end - range.start) * range.columns.length, 0);
|
|
484
|
-
if (cells <= 1 || !this.source.aggregate || this.info?.status !== "ready") {
|
|
485
|
-
this.summary = null;
|
|
486
|
-
this.summaryPending = false;
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
this.summaryPending = true;
|
|
490
|
-
this.summaryTimer = setTimeout(() => {
|
|
491
|
-
const controller = new AbortController();
|
|
492
|
-
this.summaryAbort = controller;
|
|
493
|
-
this.source
|
|
494
|
-
.aggregate?.({ view: this.view.id, ranges }, controller.signal)
|
|
495
|
-
.then((summary) => {
|
|
496
|
-
if (controller.signal.aborted)
|
|
497
|
-
return;
|
|
498
|
-
this.summary = summary;
|
|
499
|
-
this.summaryPending = false;
|
|
500
|
-
this.emit();
|
|
501
|
-
})
|
|
502
|
-
.catch((error) => {
|
|
503
|
-
if (controller.signal.aborted || isAbortError(error))
|
|
504
|
-
return;
|
|
505
|
-
this.summary = null;
|
|
506
|
-
this.summaryPending = false;
|
|
507
|
-
this.emit();
|
|
508
|
-
});
|
|
509
|
-
}, 180);
|
|
510
|
-
}
|
|
511
|
-
/** The selection as row ranges of schema columns. */
|
|
512
|
-
selectionRanges() {
|
|
513
|
-
return this.selection.ranges.map((range) => ({
|
|
514
|
-
start: range.top,
|
|
515
|
-
end: range.bottom + 1,
|
|
516
|
-
columns: this.visible.slice(range.left, range.right + 1).map((column) => column.index),
|
|
517
|
-
}));
|
|
518
|
-
}
|
|
519
|
-
// ----- reading -----
|
|
520
|
-
value(row, col) {
|
|
521
|
-
const state = this.visible[col];
|
|
522
|
-
return state ? this.cache.value(row, state.index) : undefined;
|
|
523
|
-
}
|
|
524
|
-
/** Reads a cell with a large text limit, for showing its full value. */
|
|
525
|
-
async fullValue(row, col, signal) {
|
|
526
|
-
const state = this.visible[col];
|
|
527
|
-
if (!state)
|
|
528
|
-
return null;
|
|
529
|
-
const block = await this.source.readRows({ view: this.view.id, start: row, end: row + 1, columns: [state.index], textLimit: MAX_TEXT_LIMIT }, signal);
|
|
530
|
-
return block.columns[0]?.[0] ?? null;
|
|
531
|
-
}
|
|
532
|
-
/** Builds clipboard text from values per column. */
|
|
533
|
-
clipboardText(columns, values, rows, options) {
|
|
534
|
-
const header = columns.map((column) => this.schema[column.index]?.name ?? "");
|
|
535
|
-
const body = [];
|
|
536
|
-
for (let row = 0; row < rows; row++)
|
|
537
|
-
body.push(values.map((column) => plainText(column[row] ?? null)));
|
|
538
|
-
if (options.markdown)
|
|
539
|
-
return { text: toMarkdownTable(header, body), html: null, cells: rows * columns.length };
|
|
540
|
-
const table = options.headers ? [header, ...body] : body;
|
|
541
|
-
return { text: toTsv(table), html: toHtmlTable(table, options.headers ? 1 : 0), cells: rows * columns.length };
|
|
542
|
-
}
|
|
543
|
-
/**
|
|
544
|
-
* The current range's values from the cache, or null when some are not loaded, may have been cut
|
|
545
|
-
* for display, or the range is large.
|
|
546
|
-
*/
|
|
547
|
-
cachedRange() {
|
|
548
|
-
const range = this.selection.current;
|
|
549
|
-
if (!range)
|
|
550
|
-
return null;
|
|
551
|
-
const columns = this.visible.slice(range.left, range.right + 1);
|
|
552
|
-
const rows = range.bottom - range.top + 1;
|
|
553
|
-
if (rows * columns.length > 50_000)
|
|
554
|
-
return null;
|
|
555
|
-
const values = [];
|
|
556
|
-
for (const column of columns) {
|
|
557
|
-
const list = [];
|
|
558
|
-
for (let row = range.top; row <= range.bottom; row++) {
|
|
559
|
-
const value = this.cache.value(row, column.index);
|
|
560
|
-
if (value === undefined || mayBeCut(value))
|
|
561
|
-
return null;
|
|
562
|
-
list.push(value);
|
|
563
|
-
}
|
|
564
|
-
values.push(list);
|
|
565
|
-
}
|
|
566
|
-
return { columns, values, rows };
|
|
567
|
-
}
|
|
568
|
-
/** Clipboard text for the current range when every cell is loaded; otherwise null (use `copySelection`). */
|
|
569
|
-
copySelectionSync(options) {
|
|
570
|
-
const cached = this.cachedRange();
|
|
571
|
-
return cached ? this.clipboardText(cached.columns, cached.values, cached.rows, options) : null;
|
|
572
|
-
}
|
|
573
|
-
/** Copies the current range as TSV and HTML (or Markdown), reading cells that are not loaded. */
|
|
574
|
-
async copySelection(options, signal) {
|
|
575
|
-
const range = this.selection.current;
|
|
576
|
-
if (!range)
|
|
577
|
-
return { text: "", html: null, cells: 0 };
|
|
578
|
-
const columns = this.visible.slice(range.left, range.right + 1);
|
|
579
|
-
const rows = range.bottom - range.top + 1;
|
|
580
|
-
if (rows * columns.length > this.options.copyLimit)
|
|
581
|
-
throw new CopyTooLargeError(this.options.copyLimit);
|
|
582
|
-
const cached = this.cachedRange();
|
|
583
|
-
if (cached)
|
|
584
|
-
return this.clipboardText(cached.columns, cached.values, cached.rows, options);
|
|
585
|
-
const values = await this.cache.read(range.top, range.bottom + 1, columns.map((column) => column.index), signal);
|
|
586
|
-
return this.clipboardText(columns, values, rows, options);
|
|
587
|
-
}
|
|
588
|
-
// ----- views -----
|
|
589
|
-
async setQuery(query) {
|
|
590
|
-
this.query = query;
|
|
591
|
-
this.viewAbort?.abort();
|
|
592
|
-
if (this.info?.status !== "ready") {
|
|
593
|
-
this.emit();
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
const controller = new AbortController();
|
|
597
|
-
this.viewAbort = controller;
|
|
598
|
-
this.viewPending = true;
|
|
599
|
-
this.viewError = null;
|
|
600
|
-
this.emit();
|
|
601
|
-
this.requestRender();
|
|
602
|
-
try {
|
|
603
|
-
const view = await this.createView(query, controller.signal);
|
|
604
|
-
if (controller.signal.aborted || this.disposed)
|
|
605
|
-
return;
|
|
606
|
-
this.view = view;
|
|
607
|
-
this.viewPending = false;
|
|
608
|
-
this.cache.clear();
|
|
609
|
-
this.updateLayout();
|
|
610
|
-
this.selection.select({ row: 0, col: Math.min(this.selection.active.col, Math.max(0, this.visible.length - 1)) });
|
|
611
|
-
this.scrollToVirtual(null, 0);
|
|
612
|
-
this.scheduleSummary();
|
|
613
|
-
}
|
|
614
|
-
catch (error) {
|
|
615
|
-
// Only this grid's own cancellation (a newer query replaced this one) is silent.
|
|
616
|
-
if (controller.signal.aborted)
|
|
617
|
-
return;
|
|
618
|
-
this.viewPending = false;
|
|
619
|
-
this.viewError = messageOf(error);
|
|
620
|
-
// The server is loading the table again (it closed it, for example because the file changed):
|
|
621
|
-
// follow that load, which applies the query once the table is ready.
|
|
622
|
-
if (codeOf(error) === ErrorCodes.notReady)
|
|
623
|
-
this.reload();
|
|
624
|
-
}
|
|
625
|
-
this.emit();
|
|
626
|
-
this.requestRender();
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* Creates a view, asking once more when the source cancelled it without being asked to: a server
|
|
630
|
-
* cancels the build when it closes the table (for example because the file changed), and the next
|
|
631
|
-
* request opens the table again.
|
|
632
|
-
*/
|
|
633
|
-
async createView(query, signal) {
|
|
634
|
-
try {
|
|
635
|
-
return await this.source.createView(query, signal);
|
|
636
|
-
}
|
|
637
|
-
catch (error) {
|
|
638
|
-
if (signal.aborted || codeOf(error) !== ErrorCodes.cancelled)
|
|
639
|
-
throw error;
|
|
640
|
-
return this.source.createView(query, signal);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
sortBy(col, direction, add = false) {
|
|
644
|
-
const state = this.visible[col];
|
|
645
|
-
if (!state)
|
|
646
|
-
return;
|
|
647
|
-
const others = add ? (this.query.sort ?? []).filter((key) => key.column !== state.index) : [];
|
|
648
|
-
void this.setQuery({ ...this.query, sort: [...others, { column: state.index, direction }] });
|
|
649
|
-
}
|
|
650
|
-
clearSort() {
|
|
651
|
-
void this.setQuery({ ...this.query, sort: [] });
|
|
652
|
-
}
|
|
653
|
-
/** Replaces the filter of a schema column (`null` removes it). */
|
|
654
|
-
setFilter(column, filter) {
|
|
655
|
-
const filters = (this.query.filters ?? []).filter((item) => item.column !== column);
|
|
656
|
-
if (filter)
|
|
657
|
-
filters.push(filter);
|
|
658
|
-
return this.setQuery({ ...this.query, filters });
|
|
659
|
-
}
|
|
660
|
-
/** Keeps the rows whose value in the cell's column is the cell's value (or all others, with `exclude`). */
|
|
661
|
-
async filterByValue(row, col, exclude = false) {
|
|
662
|
-
const state = this.visible[col];
|
|
663
|
-
let value = this.value(row, col);
|
|
664
|
-
if (!state || value === undefined)
|
|
665
|
-
return;
|
|
666
|
-
// A value cut for display would match nothing: filter by the whole text.
|
|
667
|
-
if (mayBeCut(value))
|
|
668
|
-
value = await this.fullValue(row, col);
|
|
669
|
-
await this.setFilter(state.index, { type: "values", column: state.index, values: [value], exclude });
|
|
670
|
-
}
|
|
671
|
-
filterOf(column) {
|
|
672
|
-
return this.query.filters?.find((filter) => filter.column === column) ?? null;
|
|
673
|
-
}
|
|
674
|
-
clearFilters() {
|
|
675
|
-
void this.setQuery({ ...this.query, filters: [], search: "" });
|
|
676
|
-
}
|
|
677
|
-
setSearch(search) {
|
|
678
|
-
void this.setQuery({ ...this.query, search });
|
|
679
|
-
}
|
|
680
|
-
sortOf(column) {
|
|
681
|
-
const sort = this.query.sort ?? [];
|
|
682
|
-
const index = sort.findIndex((key) => key.column === column);
|
|
683
|
-
return index < 0 ? null : { direction: sort[index].direction, order: index + 1 };
|
|
684
|
-
}
|
|
685
|
-
// ----- columns -----
|
|
686
|
-
changeColumns() {
|
|
687
|
-
this.updateLayout();
|
|
688
|
-
this.selectionChanged();
|
|
689
|
-
}
|
|
690
|
-
resizeColumn(col, width) {
|
|
691
|
-
const state = this.visible[col];
|
|
692
|
-
if (!state)
|
|
693
|
-
return;
|
|
694
|
-
state.width = Math.max(this.options.minColumnWidth, Math.round(width));
|
|
695
|
-
state.sized = true;
|
|
696
|
-
this.updateLayout();
|
|
697
|
-
this.requestRender();
|
|
698
|
-
}
|
|
699
|
-
hideColumn(col) {
|
|
700
|
-
const state = this.visible[col];
|
|
701
|
-
if (!state || this.visible.length <= 1)
|
|
702
|
-
return;
|
|
703
|
-
state.hidden = true;
|
|
704
|
-
if (col < this.frozen)
|
|
705
|
-
this.frozen--;
|
|
706
|
-
this.changeColumns();
|
|
707
|
-
}
|
|
708
|
-
setColumnHidden(index, hidden) {
|
|
709
|
-
const state = this.columns.find((column) => column.index === index);
|
|
710
|
-
if (!state)
|
|
711
|
-
return;
|
|
712
|
-
if (hidden && this.visible.length <= 1)
|
|
713
|
-
return;
|
|
714
|
-
state.hidden = hidden;
|
|
715
|
-
this.changeColumns();
|
|
716
|
-
}
|
|
717
|
-
showAllColumns() {
|
|
718
|
-
for (const column of this.columns)
|
|
719
|
-
column.hidden = false;
|
|
720
|
-
this.changeColumns();
|
|
721
|
-
}
|
|
722
|
-
freezeUpTo(col) {
|
|
723
|
-
this.frozen = Math.max(0, Math.min(col + 1, this.visible.length));
|
|
724
|
-
this.scrollToVirtual(0, null);
|
|
725
|
-
this.changeColumns();
|
|
726
|
-
}
|
|
727
|
-
/** Moves a visible column before another visible column. */
|
|
728
|
-
moveColumn(from, to) {
|
|
729
|
-
const moving = this.visible[from];
|
|
730
|
-
const target = this.visible[to];
|
|
731
|
-
if (!moving || !target || from === to)
|
|
732
|
-
return;
|
|
733
|
-
const list = this.columns.filter((column) => column !== moving);
|
|
734
|
-
const at = list.indexOf(target);
|
|
735
|
-
list.splice(from < to ? at + 1 : at, 0, moving);
|
|
736
|
-
this.columns = list;
|
|
737
|
-
this.changeColumns();
|
|
738
|
-
}
|
|
739
|
-
/** Width that fits the header and the loaded values of a column. */
|
|
740
|
-
fittedWidth(col) {
|
|
741
|
-
const state = this.visible[col];
|
|
742
|
-
const schema = this.columnSchema(col);
|
|
743
|
-
if (!state || !schema || !this.measure)
|
|
744
|
-
return state?.width ?? 120;
|
|
745
|
-
let widest = this.measure(schema.name, true) + HEADER_CHROME;
|
|
746
|
-
const [first, last] = this.visibleRows();
|
|
747
|
-
const end = Math.min(this.readableRows, Math.max(last, first + 200));
|
|
748
|
-
for (let row = first; row < end; row++) {
|
|
749
|
-
const value = this.cache.value(row, state.index);
|
|
750
|
-
if (value === undefined)
|
|
751
|
-
continue;
|
|
752
|
-
widest = Math.max(widest, this.measure(this.formatter.format(value, schema.type), false) + 18);
|
|
753
|
-
}
|
|
754
|
-
return Math.max(this.options.minColumnWidth, Math.ceil(widest));
|
|
755
|
-
}
|
|
756
|
-
autoFit(col) {
|
|
757
|
-
this.resizeColumn(col, Math.min(this.fittedWidth(col), 1000));
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* Widens columns whose numbers did not fit (numbers are never cut, since a cut number reads as a
|
|
761
|
-
* different number). Called by the renderer with the widths it needed; columns the user sized
|
|
762
|
-
* keep their width.
|
|
763
|
-
*/
|
|
764
|
-
growColumns(widths) {
|
|
765
|
-
let changed = false;
|
|
766
|
-
for (const [col, width] of widths) {
|
|
767
|
-
const state = this.visible[col];
|
|
768
|
-
// At most 600 px: wider numbers stay cut, and asking again for them changes nothing.
|
|
769
|
-
const next = Math.min(Math.ceil(width), 600);
|
|
770
|
-
if (!state || state.sized || next <= state.width)
|
|
771
|
-
continue;
|
|
772
|
-
state.width = next;
|
|
773
|
-
changed = true;
|
|
774
|
-
}
|
|
775
|
-
if (changed) {
|
|
776
|
-
this.updateLayout();
|
|
777
|
-
this.requestRender();
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
/** Fits columns to the first loaded rows once, unless the user sized them. */
|
|
781
|
-
autoSize() {
|
|
782
|
-
if (this.autoSized || !this.measure || this.view.id !== null || this.readableRows === 0)
|
|
783
|
-
return;
|
|
784
|
-
const first = this.visible[0];
|
|
785
|
-
if (!first || this.cache.value(0, first.index) === undefined)
|
|
786
|
-
return;
|
|
787
|
-
this.autoSized = true;
|
|
788
|
-
this.visible.forEach((state, col) => {
|
|
789
|
-
if (state.sized)
|
|
790
|
-
return;
|
|
791
|
-
const schema = this.schema[state.index];
|
|
792
|
-
if (!schema || !this.measure)
|
|
793
|
-
return;
|
|
794
|
-
let widest = this.measure(schema.name, true) + HEADER_CHROME;
|
|
795
|
-
for (let row = 0; row < Math.min(this.readableRows, 200); row++) {
|
|
796
|
-
const value = this.cache.value(row, state.index);
|
|
797
|
-
if (value === undefined || value === null)
|
|
798
|
-
continue;
|
|
799
|
-
widest = Math.max(widest, this.measure(this.formatter.format(value, schema.type), false) + 18);
|
|
800
|
-
}
|
|
801
|
-
const cap = isNumericType(schema.type) ? 240 : this.options.maxAutoWidth;
|
|
802
|
-
state.width = Math.max(this.options.minColumnWidth, Math.min(cap, Math.ceil(widest)));
|
|
803
|
-
void col;
|
|
804
|
-
});
|
|
805
|
-
this.updateLayout();
|
|
806
|
-
this.emit();
|
|
807
|
-
}
|
|
808
|
-
// ----- statistics, values, find -----
|
|
809
|
-
stats(col, signal) {
|
|
810
|
-
const state = this.visible[col];
|
|
811
|
-
if (!state || !this.source.columnStats)
|
|
812
|
-
return null;
|
|
813
|
-
return this.source.columnStats(this.view.id, state.index, signal);
|
|
814
|
-
}
|
|
815
|
-
distinct(column, search, signal) {
|
|
816
|
-
if (!this.source.distinctValues)
|
|
817
|
-
return null;
|
|
818
|
-
// Values come from the table as a whole, so that filtering a column keeps showing its other values.
|
|
819
|
-
return this.source.distinctValues({ view: null, column, search, limit: 1000 }, signal);
|
|
820
|
-
}
|
|
821
|
-
/** Moves to the next cell containing `text`. Returns whether one was found. */
|
|
822
|
-
async find(text, direction, caseSensitive, signal) {
|
|
823
|
-
this.findText = text;
|
|
824
|
-
this.findCaseSensitive = caseSensitive;
|
|
825
|
-
this.requestRender();
|
|
826
|
-
if (!text || !this.source.find)
|
|
827
|
-
return false;
|
|
828
|
-
const active = this.selection.active;
|
|
829
|
-
const result = await this.source.find({
|
|
830
|
-
view: this.view.id,
|
|
831
|
-
text,
|
|
832
|
-
columns: this.visible.map((column) => column.index),
|
|
833
|
-
row: Math.min(active.row, Math.max(0, this.rowCount - 1)),
|
|
834
|
-
column: active.col,
|
|
835
|
-
direction,
|
|
836
|
-
caseSensitive,
|
|
837
|
-
}, signal);
|
|
838
|
-
if (!result || signal?.aborted)
|
|
839
|
-
return false;
|
|
840
|
-
this.selection.select({ row: result.row, col: result.column });
|
|
841
|
-
this.selectionChanged(result ? { row: result.row, col: result.column } : undefined);
|
|
842
|
-
return true;
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
//# sourceMappingURL=controller.js.map
|