Skip to content

Commit dd2fca4

Browse files
authored
3.1.1 (#104)
* Fix bug where grid selection would be off by when row markers are enabled * Fix crash when columns are too narrow to render menu selector correctly * 3.1.1-alpha1 * Add support for group theme * 3.1.1-alpha2 * Better fix for the grid selection issue * 3.1.1-alpha3 * Respect theme in group headers correctly * 3.1.1-alpha4 * Don't overdraw grid lines on 0 width columns * 3.1.1-alpha5 * Unify undefined and empty string group * Add minimap * Minor story touchuP * animation queue (#103) * Add animation queue hook * Better animation queue system * Make animation queue function with custom cells * Prevent animation jitter caused by timing differences * Improve tags rendering perf * Cleanup tag editor * Fix demo * Ensure frozen columns are draggable * 3.1.1-alpha6 * Fix some editing issues and dont block propagation * Fix crash * 3.1.1-alpha7 * Make sure font overrides apply * 3.1.1-alpha8 * Start working on experimental subgrid support * 3.1.1-alpha9 * Fix issue where menus could cause drag interactions to not end * Fix story * Default text baseline to center * Update deps * Fix delete when selecting colukn * 3.1.1-beta1 * Memory cleanup * Allow minimap preview rect to be half size * allow changing size of column group headers, vertical line color, and disabling padding of custom editors * Fix build * 3.1.1-beta2 * Improve image window loader performance * Cleanup render * Faster render * Minor speedup * Reuse images * Allow for a prep phase for cells to minimize state changes * const up the enums * 3.1.1-beta3 * Prep for stable
1 parent b0ea72b commit dd2fca4

36 files changed

+976
-492
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
</h1>
55
<p align="center">A relatively small HTML5 Canvas based data editor supporting <b>millions</b> of rows, <b>rapid</b> updating, and fully <b>native scrolling</b>. We built <a href="https://grid.glideapps.com" target="_blank">Data Grid</a> as the basis for the <a href="https://docs.glideapps.com/all/reference/data-editor/introduction-to-the-data-editor" target="_blank">Glide Data Editor</a>.</p>
66

7-
<p align="center"><a href="https://github.com/glideapps/glide-data-grid/releases" target="_blank"><img src="https://img.shields.io/badge/version-v3.1.0-blue?style=for-the-badge&logo=none" alt="grid version" /></a>&nbsp;<a href="https://reactjs.org/" target="_blank"><img src="https://img.shields.io/badge/React-16+-00ADD8?style=for-the-badge&logo=react" alt="react version" /></a>&nbsp;<a href="https://www.typescriptlang.org/" target="_blank"><img src="https://img.shields.io/badge/Typescript-grey?style=for-the-badge&logo=typescript" alt="react version" /></a>&nbsp;<a href="https://bundlephobia.com/package/@glideapps/glide-data-grid" target="_blank"><img src="https://img.shields.io/badge/Bundle_Size-41.7kb-success?style=for-the-badge&logo=none" alt="go cover" /></a>&nbsp;<img src="https://img.shields.io/badge/license-mit-red?style=for-the-badge&logo=none" alt="license" />&nbsp;<a href="https://www.glideapps.com/jobs" target="_blank"><img src="https://img.shields.io/badge/❤_Made_by-Glide-11CCE5?style=for-the-badge&logo=none" alt="glide" /></a></p>
7+
<p align="center"><a href="https://github.com/glideapps/glide-data-grid/releases" target="_blank"><img src="https://img.shields.io/badge/version-v3.1.1-blue?style=for-the-badge&logo=none" alt="grid version" /></a>&nbsp;<a href="https://reactjs.org/" target="_blank"><img src="https://img.shields.io/badge/React-16+-00ADD8?style=for-the-badge&logo=react" alt="react version" /></a>&nbsp;<a href="https://www.typescriptlang.org/" target="_blank"><img src="https://img.shields.io/badge/Typescript-grey?style=for-the-badge&logo=typescript" alt="react version" /></a>&nbsp;<a href="https://bundlephobia.com/package/@glideapps/glide-data-grid" target="_blank"><img src="https://img.shields.io/badge/Bundle_Size-43.1kb-success?style=for-the-badge&logo=none" alt="go cover" /></a>&nbsp;<img src="https://img.shields.io/badge/license-mit-red?style=for-the-badge&logo=none" alt="license" />&nbsp;<a href="https://www.glideapps.com/jobs" target="_blank"><img src="https://img.shields.io/badge/❤_Made_by-Glide-11CCE5?style=for-the-badge&logo=none" alt="glide" /></a></p>
88

99
![Data Grid](https://raw.githubusercontent.com/glideapps/glide-data-grid/master/data-grid.jpg)
1010

cells/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
</h1>
55
<p align="center">Additional cells and features for Glide Data Grid</p>
66

7-
<p align="center"><a href="https://github.com/glideapps/glide-data-grid/releases" target="_blank"><img src="https://img.shields.io/badge/version-v3.1.0-blue?style=for-the-badge&logo=none" alt="grid version" /></a>&nbsp;<a href="https://reactjs.org/" target="_blank"><img src="https://img.shields.io/badge/React-16+-00ADD8?style=for-the-badge&logo=react" alt="react version" /></a>&nbsp;<a href="https://www.typescriptlang.org/" target="_blank"><img src="https://img.shields.io/badge/Typescript-grey?style=for-the-badge&logo=typescript" alt="react version" /></a>&nbsp;<img src="https://img.shields.io/badge/license-mit-red?style=for-the-badge&logo=none" alt="license" />&nbsp;<a href="https://www.glideapps.com/jobs" target="_blank"><img src="https://img.shields.io/badge/❤_Made_by-Glide-11CCE5?style=for-the-badge&logo=none" alt="glide" /></a></p>
7+
<p align="center"><a href="https://github.com/glideapps/glide-data-grid/releases" target="_blank"><img src="https://img.shields.io/badge/version-v3.1.1-blue?style=for-the-badge&logo=none" alt="grid version" /></a>&nbsp;<a href="https://reactjs.org/" target="_blank"><img src="https://img.shields.io/badge/React-16+-00ADD8?style=for-the-badge&logo=react" alt="react version" /></a>&nbsp;<a href="https://www.typescriptlang.org/" target="_blank"><img src="https://img.shields.io/badge/Typescript-grey?style=for-the-badge&logo=typescript" alt="react version" /></a>&nbsp;<img src="https://img.shields.io/badge/license-mit-red?style=for-the-badge&logo=none" alt="license" />&nbsp;<a href="https://www.glideapps.com/jobs" target="_blank"><img src="https://img.shields.io/badge/❤_Made_by-Glide-11CCE5?style=for-the-badge&logo=none" alt="glide" /></a></p>
88

99
![Data Grid](https://raw.githubusercontent.com/glideapps/glide-data-grid/master/data-grid.jpg)
1010

cells/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@glideapps/glide-data-grid-cells",
3-
"version": "3.1.0",
3+
"version": "3.1.1-beta3",
44
"description": "Extra cells for glide-data-grid",
55
"main": "dist/js/index.js",
66
"types": "dist/ts/index.d.ts",
@@ -30,7 +30,7 @@
3030
"canvas"
3131
],
3232
"dependencies": {
33-
"@glideapps/glide-data-grid": "3.1.0"
33+
"@glideapps/glide-data-grid": "3.1.1-beta3"
3434
},
3535
"devDependencies": {
3636
"@babel/cli": "^7.16.0",

cells/src/cell.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ export const CustomCells: React.VFC = () => {
213213
data: {
214214
kind: "tags-cell",
215215
possibleTags: possibleTags,
216+
readonly: row % 2 === 0,
216217
tags: uniq([
217218
possibleTags[Math.round(rand() * 1000) % possibleTags.length].tag,
218219
possibleTags[Math.round(rand() * 1000) % possibleTags.length].tag,

cells/src/cells/sparkline-cell.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ const renderer: CustomCellRenderer<SparklineCell> = {
115115
ctx.save();
116116
ctx.font = `8px ${theme.fontFamily}`;
117117
ctx.fillStyle = theme.textMedium;
118-
ctx.fillText(displayValues[closest], drawX, rect.y + 12);
118+
ctx.textBaseline = "top";
119+
ctx.fillText(displayValues[closest], drawX, rect.y + theme.cellVerticalPadding);
119120
ctx.restore();
120121
}
121122
}

cells/src/cells/tags-cell.tsx

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { CustomCellRenderer } from "../types";
66
interface TagsCellProps {
77
readonly kind: "tags-cell";
88
readonly tags: readonly string[];
9+
readonly readonly?: boolean;
910
readonly possibleTags: readonly {
1011
tag: string;
1112
color: string;
@@ -39,6 +40,12 @@ const EditorWrap = styled.div`
3940
padding-top: 6px;
4041
color: ${p => p.theme.textDark};
4142
43+
box-sizing: border-box;
44+
45+
* {
46+
box-sizing: border-box;
47+
}
48+
4249
&&&& label {
4350
display: flex;
4451
cursor: pointer;
@@ -53,11 +60,10 @@ const EditorWrap = styled.div`
5360
margin-right: 6px;
5461
margin-bottom: 6px;
5562
56-
border-radius: 100px;
57-
height: ${tagHeight}px;
58-
padding: 0 ${innerPad}px;
63+
border-radius: ${tagHeight / 2}px;
64+
min-height: ${tagHeight}px;
65+
padding: 2px ${innerPad}px;
5966
display: flex;
60-
justify-content: center;
6167
align-items: center;
6268
6369
font: 12px ${p => p.theme.fontFamily};
@@ -74,6 +80,14 @@ const EditorWrap = styled.div`
7480
label:hover .pill {
7581
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
7682
}
83+
84+
&&&&.readonly label {
85+
cursor: default;
86+
87+
.pill {
88+
box-shadow: none !important;
89+
}
90+
}
7791
`;
7892

7993
const renderer: CustomCellRenderer<TagsCell> = {
@@ -99,14 +113,12 @@ const renderer: CustomCellRenderer<TagsCell> = {
99113
ctx.font = `12px ${theme.fontFamily}`;
100114
const metrics = measureTextCached(tag, ctx);
101115
const width = metrics.width + innerPad * 2;
102-
const textY = tagHeight / 2 + metrics.actualBoundingBoxAscent / 2;
103-
104-
if (x !== drawArea.x && x + width > drawArea.x + drawArea.width) {
105-
if (row < rows) {
106-
row++;
107-
y += tagHeight + innerPad;
108-
x = drawArea.x;
109-
}
116+
const textY = tagHeight / 2;
117+
118+
if (x !== drawArea.x && x + width > drawArea.x + drawArea.width && row < rows) {
119+
row++;
120+
y += tagHeight + innerPad;
121+
x = drawArea.x;
110122
}
111123

112124
ctx.fillStyle = color;
@@ -118,7 +130,7 @@ const renderer: CustomCellRenderer<TagsCell> = {
118130
ctx.fillText(tag, x + innerPad, y + textY);
119131

120132
x += width + 8;
121-
if (x > drawArea.x + drawArea.width && row > rows) break;
133+
if (x > drawArea.x + drawArea.width && row >= rows) break;
122134
}
123135

124136
return true;
@@ -127,29 +139,31 @@ const renderer: CustomCellRenderer<TagsCell> = {
127139
// eslint-disable-next-line react/display-name
128140
return p => {
129141
const { onChange, value } = p;
130-
const { possibleTags, tags } = value.data;
142+
const { possibleTags, tags, readonly = false } = value.data;
131143
return (
132-
<EditorWrap>
144+
<EditorWrap className={readonly ? "readonly" : ""}>
133145
{possibleTags.map(t => {
134146
const selected = tags.indexOf(t.tag) !== -1;
135147
return (
136148
<label key={t.tag}>
137-
<input
138-
type="checkbox"
139-
checked={selected}
140-
onChange={() => {
141-
const newTags = selected ? tags.filter(x => x !== t.tag) : [...tags, t.tag];
142-
onChange({
143-
...p.value,
144-
data: {
145-
...value.data,
146-
tags: newTags,
147-
},
148-
});
149-
}}
150-
/>
149+
{!readonly && (
150+
<input
151+
type="checkbox"
152+
checked={selected}
153+
onChange={() => {
154+
const newTags = selected ? tags.filter(x => x !== t.tag) : [...tags, t.tag];
155+
onChange({
156+
...p.value,
157+
data: {
158+
...value.data,
159+
tags: newTags,
160+
},
161+
});
162+
}}
163+
/>
164+
)}
151165
<div
152-
className={"pill " + (selected ? "selectd" : "unselected")}
166+
className={"pill " + (selected ? "selected" : "unselected")}
153167
style={{ backgroundColor: selected ? t.color : undefined }}>
154168
{t.tag}
155169
</div>

core/API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ interface CustomCell<T extends {} = {}> extends BaseGridCell {
457457
readonly copyData: string;
458458
}
459459

460-
export enum GridCellKind {
460+
export const enum GridCellKind {
461461
Uri = "uri",
462462
Text = "text",
463463
Image = "image",

core/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
</h1>
55
<p align="center">A relatively small HTML5 Canvas based data editor supporting <b>millions</b> of rows, <b>rapid</b> updating, and fully <b>native scrolling</b>. We built <a href="https://grid.glideapps.com" target="_blank">Data Grid</a> as the basis for the <a href="https://docs.glideapps.com/all/reference/data-editor/introduction-to-the-data-editor" target="_blank">Glide Data Editor</a>.</p>
66

7-
<p align="center"><a href="https://github.com/glideapps/glide-data-grid/releases" target="_blank"><img src="https://img.shields.io/badge/version-v3.1.0-blue?style=for-the-badge&logo=none" alt="grid version" /></a>&nbsp;<a href="https://reactjs.org/" target="_blank"><img src="https://img.shields.io/badge/React-16+-00ADD8?style=for-the-badge&logo=react" alt="react version" /></a>&nbsp;<a href="https://www.typescriptlang.org/" target="_blank"><img src="https://img.shields.io/badge/Typescript-grey?style=for-the-badge&logo=typescript" alt="react version" /></a>&nbsp;<a href="https://bundlephobia.com/package/@glideapps/glide-data-grid" target="_blank"><img src="https://img.shields.io/badge/Bundle_Size-41.7kb-success?style=for-the-badge&logo=none" alt="go cover" /></a>&nbsp;<img src="https://img.shields.io/badge/license-mit-red?style=for-the-badge&logo=none" alt="license" />&nbsp;<a href="https://www.glideapps.com/jobs" target="_blank"><img src="https://img.shields.io/badge/❤_Made_by-Glide-11CCE5?style=for-the-badge&logo=none" alt="glide" /></a></p>
7+
<p align="center"><a href="https://github.com/glideapps/glide-data-grid/releases" target="_blank"><img src="https://img.shields.io/badge/version-v3.1.1-blue?style=for-the-badge&logo=none" alt="grid version" /></a>&nbsp;<a href="https://reactjs.org/" target="_blank"><img src="https://img.shields.io/badge/React-16+-00ADD8?style=for-the-badge&logo=react" alt="react version" /></a>&nbsp;<a href="https://www.typescriptlang.org/" target="_blank"><img src="https://img.shields.io/badge/Typescript-grey?style=for-the-badge&logo=typescript" alt="react version" /></a>&nbsp;<a href="https://bundlephobia.com/package/@glideapps/glide-data-grid" target="_blank"><img src="https://img.shields.io/badge/Bundle_Size-43.1kb-success?style=for-the-badge&logo=none" alt="go cover" /></a>&nbsp;<img src="https://img.shields.io/badge/license-mit-red?style=for-the-badge&logo=none" alt="license" />&nbsp;<a href="https://www.glideapps.com/jobs" target="_blank"><img src="https://img.shields.io/badge/❤_Made_by-Glide-11CCE5?style=for-the-badge&logo=none" alt="glide" /></a></p>
88

99
![Data Grid](https://raw.githubusercontent.com/glideapps/glide-data-grid/master/data-grid.jpg)
1010

core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@glideapps/glide-data-grid",
3-
"version": "3.1.0",
3+
"version": "3.1.1-beta3",
44
"description": "Super fast, pure canvas Data Grid Editor",
55
"main": "dist/js/index.js",
66
"types": "dist/ts/index.d.ts",

core/src/common/image-window-loader.ts

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import throttle from "lodash/throttle";
33

44
interface LoadResult {
55
img: HTMLImageElement | undefined;
6-
cancel?: () => void;
6+
cancel: () => void;
77
url: string;
8-
cells: Set<number>;
8+
cells: number[];
99
}
1010

1111
const rowShift = 1 << 16;
1212

13+
const imgPool: HTMLImageElement[] = [];
14+
1315
function packColRowToNumber(col: number, row: number) {
1416
return row * rowShift + col;
1517
}
@@ -20,6 +22,14 @@ function unpackNumberToColRow(packed: number): [number, number] {
2022
return [col, row];
2123
}
2224

25+
function unpackCol(packed: number): number {
26+
return packed % rowShift;
27+
}
28+
29+
function unpackRow(packed: number, col: number): number {
30+
return (packed - col) / rowShift;
31+
}
32+
2333
class ImageWindowLoader {
2434
private imageLoaded: (locations: readonly (readonly [number, number])[]) => void = () => undefined;
2535
private loadedLocations: [number, number][] = [];
@@ -31,10 +41,12 @@ class ImageWindowLoader {
3141
height: 0,
3242
};
3343

34-
private isInWindow(col: number, row: number) {
44+
private isInWindow = (packed: number) => {
45+
const col = unpackCol(packed);
46+
const row = unpackRow(packed, col);
3547
const w = this.window;
3648
return col >= w.x && col <= w.x + w.width && row >= w.y && row <= w.y + w.height;
37-
}
49+
};
3850

3951
private cache: Record<string, LoadResult> = {};
4052

@@ -47,34 +59,27 @@ class ImageWindowLoader {
4759
this.loadedLocations = [];
4860
}, 20);
4961

50-
private clearOutOfWindowImpl = () => {
51-
const old = this.cache;
52-
this.cache = {};
53-
const whittled = Object.values(old).map(v => ({
54-
...v,
55-
cells: new Set(Array.from(v.cells).filter(n => this.isInWindow(...unpackNumberToColRow(n)))),
56-
}));
57-
58-
whittled
59-
.filter(v => v.cells.size > 0)
60-
.forEach(v => {
61-
this.cache[`${v.url}`] = v;
62-
});
63-
64-
for (const v of whittled) {
65-
if (v.cells.size === 0) {
66-
v.cancel?.();
62+
private clearOutOfWindow = () => {
63+
for (const key of Object.keys(this.cache)) {
64+
const obj = this.cache[key];
65+
66+
let keep = false;
67+
for (const packed of obj.cells) {
68+
if (this.isInWindow(packed)) {
69+
keep = true;
70+
break;
71+
}
72+
}
73+
74+
if (keep) {
75+
obj.cells = obj.cells.filter(this.isInWindow);
6776
} else {
68-
this.cache[v.url] = v;
77+
obj.cancel();
78+
delete this.cache[key];
6979
}
7080
}
7181
};
7282

73-
private clearOutOfWindow = throttle(this.clearOutOfWindowImpl, 600, {
74-
leading: false,
75-
trailing: true,
76-
});
77-
7883
public setWindow(window: Rectangle): void {
7984
if (
8085
this.window.x === window.x &&
@@ -92,21 +97,24 @@ class ImageWindowLoader {
9297

9398
const current = this.cache[key];
9499
if (current !== undefined) {
95-
current.cells.add(packColRowToNumber(col, row));
100+
const packed = packColRowToNumber(col, row);
101+
if (current.img === undefined && !current.cells.includes(packed)) {
102+
current.cells.push(packed);
103+
}
96104
return current.img;
97105
} else {
98-
const img = new Image();
106+
const img = imgPool.pop() ?? new Image();
99107

100108
img.src = url;
101109

102110
let cancelled = false;
103111
const result: LoadResult = {
104112
img: undefined,
105-
cells: new Set([packColRowToNumber(col, row)]),
113+
cells: [packColRowToNumber(col, row)],
106114
url,
107115
cancel: () => {
108116
cancelled = true;
109-
img.src = "";
117+
imgPool.unshift(img);
110118
},
111119
};
112120

@@ -120,7 +128,7 @@ class ImageWindowLoader {
120128
const toWrite = this.cache[key];
121129
if (toWrite !== undefined && !errored && !cancelled) {
122130
toWrite.img = img;
123-
for (const packed of Array.from(toWrite.cells)) {
131+
for (const packed of toWrite.cells) {
124132
this.loadedLocations.push(unpackNumberToColRow(packed));
125133
}
126134
this.sendLoaded();

0 commit comments

Comments
 (0)