-
Notifications
You must be signed in to change notification settings - Fork 153
/
pi-scroll.ts
115 lines (89 loc) · 3.34 KB
/
pi-scroll.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// =============================================================================
// Pi Scroll Component
// (c) Mathigon
// =============================================================================
import {tabulate} from '@mathigon/core';
import {clamp} from '@mathigon/fermat';
import {CustomElementView, register, $N, Browser, ElementView} from '@mathigon/boost';
const ROW_HEIGHT = 30;
const NUM_ELS = 100; // Number of reusable row elements.
const VISIBLE_ELS = 10; // Number of rows that are visible on one screen.
const INITIAL = '141592653589793238462643383279502884197169399375105';
@register('x-pi-scroll')
export class PiScroll extends CustomElementView {
private string = '';
private numColumns = 0;
private numRows = 0;
private firstEl = 0;
private letterWidth!: number;
private $wrap!: ElementView;
private $rows!: ElementView[];
private $highlight1!: ElementView;
private $highlight2!: ElementView;
ready() {
this.$wrap = $N('div', {class: 'pi-wrap'}, this);
this.$rows = tabulate(() => $N('div', {class: 'pi-row'}, this.$wrap), NUM_ELS);
this.$highlight1 = $N('div', {class: 'pi-highlight'}, this.$wrap);
this.$highlight2 = $N('div', {class: 'pi-highlight'}, this.$wrap);
this.$rows[0].text = INITIAL;
this.letterWidth = this.$rows[0].width / INITIAL.length;
Browser.onResize(() => {
this.numColumns = Math.floor(this.innerWidth / this.letterWidth);
this.setUp(this.string);
});
// const onScrollThrottled = throttle(onScroll, 500);
this.on('scroll', (e) => this.onScroll(e.top));
}
updateRow(i: number) {
const $row = this.$rows[i % NUM_ELS];
$row.translate(0, i * ROW_HEIGHT);
$row.text = this.string.substr(i * this.numColumns, this.numColumns);
}
setUp(data: string) {
this.string = data;
this.numRows = Math.ceil(data.length / this.numColumns);
this.$wrap.css('height', this.numRows * 30 + 'px');
for (let i = 0; i < NUM_ELS; ++i) this.updateRow(this.firstEl + i);
}
onScroll(top: number) {
if (!this.string) return;
const rowTop = top / ROW_HEIGHT - (NUM_ELS - VISIBLE_ELS) / 2;
const newFirstEl = clamp(Math.floor(rowTop), 0, this.numRows - NUM_ELS);
const [start, end] = (newFirstEl < this.firstEl) ? [newFirstEl, this.firstEl]
: [this.firstEl + NUM_ELS, newFirstEl + NUM_ELS];
for (let i = start; i < end; ++i) this.updateRow(i);
this.firstEl = newFirstEl;
}
findString(str?: string) {
if (!str) {
this.$highlight1.hide();
this.$highlight2.hide();
return 0;
}
const index = this.string.indexOf(str);
if (index < 0) {
this.$highlight1.hide();
this.$highlight2.hide();
return -1;
}
this.$highlight1.show();
const top = Math.floor(index / this.numColumns);
const left = index % this.numColumns;
this.$highlight1.css({
top: top * ROW_HEIGHT + 'px',
left: left * this.letterWidth + 'px',
width: this.letterWidth * Math.min(str.length, this.numColumns - left) + 'px'
});
if (left + str.length > this.numColumns) {
this.$highlight2.show();
this.$highlight2.css({
top: (top + 1) * ROW_HEIGHT + 'px',
width: this.letterWidth * (left + str.length - this.numColumns) + 'px'
});
} else {
this.$highlight2.hide();
}
this.scrollTo(top * ROW_HEIGHT - 50);
return index;
}
}