Skip to content

Commit faafdfe

Browse files
committed
release(logcat): v0.1.0
1 parent 40b050d commit faafdfe

File tree

8 files changed

+70
-68
lines changed

8 files changed

+70
-68
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ UI library.
6767
* [text-viewer](./src/text-viewer/README.md): Text viewer with line number.
6868
* [toolbar](./src/toolbar/README.md): Application toolbar.
6969
* [video-player](./src/video-player/README.md): Video player.
70+
* [virtual-list](./src/virtual-list/README.md): Vertical list with virtual scrolling.
7071
* [window](./src/window/README.md): HTML5 window manager.
7172

7273
All these components is published individually. You can use any of them by adding traditional scripts and style links or installing npm packages.

index.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,12 @@
182182
},
183183
"logcat": {
184184
"react": true,
185+
"dependencies": ["virtual-list"],
185186
"version": "0.5.1",
186187
"style": true,
187188
"icon": false,
188189
"test": true,
189-
"install": false,
190-
"dependencies": []
190+
"install": false
191191
},
192192
"lrc-player": {
193193
"test": false,

src/logcat/index.ts

+27-47
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import contain from 'licia/contain'
1313
import dateFormat from 'licia/dateFormat'
1414
import toNum from 'licia/toNum'
1515
import omit from 'licia/omit'
16+
import ResizeSensor from 'licia/ResizeSensor'
17+
import debounce from 'licia/debounce'
18+
import LunaVirtualList from 'luna-virtual-list'
1619
import { exportCjs } from '../share/util'
1720

1821
/** IOptions */
@@ -57,8 +60,6 @@ interface IInnerEntry extends IBaseEntry {
5760
container: HTMLElement
5861
}
5962

60-
const MIN_APPEND_INTERVAL = 100
61-
6263
/**
6364
* Android logcat viewer.
6465
*
@@ -75,23 +76,31 @@ const MIN_APPEND_INTERVAL = 100
7576
* })
7677
*/
7778
export default class Logcat extends Component<IOptions> {
78-
private isAtBottom = true
7979
private render: types.AnyFn
8080
private entries: Array<IInnerEntry> = []
8181
private displayEntries: Array<IInnerEntry> = []
82-
private appendTimer: NodeJS.Timeout | null = null
8382
private removeThreshold = 1
84-
private frag: DocumentFragment = document.createDocumentFragment()
83+
private virtualList: LunaVirtualList
84+
private resizeSensor: ResizeSensor
8585
constructor(container: HTMLElement, options: IOptions = {}) {
8686
super(container, { compName: 'logcat' }, options)
8787

8888
this.initOptions(options, {
89-
maxNum: 5000,
89+
maxNum: 10000,
9090
view: 'standard',
9191
entries: [],
9292
wrapLongLines: false,
9393
})
9494

95+
this.resizeSensor = new ResizeSensor(container)
96+
97+
this.initTpl()
98+
this.virtualList = new LunaVirtualList(
99+
this.find('.virtual-list').get(0) as HTMLElement,
100+
{ autoScroll: true }
101+
)
102+
this.addSubComponent(this.virtualList)
103+
95104
const maxNum = this.options.maxNum
96105
if (maxNum !== 0 && maxNum > 500) {
97106
this.removeThreshold = Math.round(maxNum / 10)
@@ -111,7 +120,7 @@ export default class Logcat extends Component<IOptions> {
111120
this.bindEvent()
112121
}
113122
destroy() {
114-
this.$container.off('scroll', this.onScroll)
123+
this.resizeSensor.destroy()
115124
super.destroy()
116125
}
117126
/** Append entry. */
@@ -140,7 +149,7 @@ export default class Logcat extends Component<IOptions> {
140149
if (entry) {
141150
if (displayEntries[0] === entry) {
142151
displayEntries.shift()
143-
$(entry.container).remove()
152+
this.virtualList.remove(entry.container)
144153
}
145154
}
146155
}
@@ -151,30 +160,17 @@ export default class Logcat extends Component<IOptions> {
151160

152161
if (this.filterEntry(e)) {
153162
this.displayEntries.push(e)
154-
this.frag.appendChild(container)
155-
if (!this.appendTimer) {
156-
this.appendTimer = setTimeout(this._append, MIN_APPEND_INTERVAL)
157-
}
163+
this.virtualList.append(container)
158164
}
159165
}
160166
/** Clear all entries. */
161167
clear() {
162-
if (this.appendTimer) {
163-
clearTimeout(this.appendTimer)
164-
this.appendTimer = null
165-
this.frag = document.createDocumentFragment()
166-
}
167168
this.entries = []
168-
this.$container.html('')
169+
this.virtualList.clear()
169170
}
170171
/** Scroll to end. */
171172
scrollToEnd() {
172-
const { container } = this
173-
const { scrollHeight, scrollTop, offsetHeight } = container
174-
if (scrollTop <= scrollHeight - offsetHeight) {
175-
container.scrollTop = 10000000
176-
this.isAtBottom = true
177-
}
173+
this.virtualList.scrollToEnd()
178174
}
179175
/** Check if there is any selection. */
180176
hasSelection() {
@@ -197,14 +193,6 @@ export default class Logcat extends Component<IOptions> {
197193
const selection = window.getSelection()
198194
return selection ? selection.toString() : ''
199195
}
200-
private _append = () => {
201-
const isAtBottom = this.isAtBottom
202-
this.container.appendChild(this.frag)
203-
this.appendTimer = null
204-
if (isAtBottom) {
205-
this.scrollToEnd()
206-
}
207-
}
208196
private filterEntry(entry: IBaseEntry) {
209197
const { filter } = this.options
210198

@@ -232,9 +220,16 @@ export default class Logcat extends Component<IOptions> {
232220

233221
return true
234222
}
223+
private initTpl() {
224+
this.$container.html(this.c('<div class="virtual-list"></div>'))
225+
}
235226
private bindEvent() {
236227
const { c } = this
237228

229+
this.resizeSensor.addListener(
230+
debounce(() => this.virtualList.update(), 100)
231+
)
232+
238233
this.on('changeOption', (name, val) => {
239234
const { entries } = this
240235

@@ -277,8 +272,6 @@ export default class Logcat extends Component<IOptions> {
277272
const self = this
278273

279274
this.$container
280-
.on('scroll', this.onScroll)
281-
.on('click', () => (this.isAtBottom = false))
282275
.on('contextmenu', c('.entry'), function (this: HTMLDivElement, e) {
283276
e.stopPropagation()
284277
const idx = $(this).data('idx')
@@ -295,18 +288,6 @@ export default class Logcat extends Component<IOptions> {
295288
self.emit('contextmenu', e.origEvent)
296289
})
297290
}
298-
private onScroll = () => {
299-
const { scrollHeight, clientHeight, scrollTop } = this
300-
.container as HTMLElement
301-
302-
let isAtBottom = false
303-
if (scrollHeight === clientHeight) {
304-
isAtBottom = true
305-
} else if (Math.abs(scrollHeight - clientHeight - scrollTop) < 1) {
306-
isAtBottom = true
307-
}
308-
this.isAtBottom = isAtBottom
309-
}
310291
private formatStandard(entry: IInnerEntry) {
311292
const { c } = this
312293

@@ -339,7 +320,6 @@ export default class Logcat extends Component<IOptions> {
339320
private _render() {
340321
const { container } = this
341322
this.$container.html('')
342-
this.isAtBottom = true
343323

344324
const frag = document.createDocumentFragment()
345325
each(this.displayEntries, (entry) => {

src/logcat/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
"version": "0.5.1",
44
"description": "Android logcat viewer",
55
"luna": {
6-
"react": true
6+
"react": true,
7+
"dependencies": [
8+
"virtual-list"
9+
]
710
}
811
}

src/logcat/story.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import 'luna-logcat.css'
22
import Logcat from 'luna-logcat.js'
33
import readme from './README.md'
44
import story from '../share/story'
5-
import each from 'licia/each'
65
import $ from 'licia/$'
76
import random from 'licia/random'
87
import randomItem from 'licia/randomItem'
@@ -34,7 +33,7 @@ const def = story(
3433
if (destroyed) {
3534
return
3635
}
37-
setTimeout(append, random(10, 100))
36+
setTimeout(append, random(100, 500))
3837
}
3938
append()
4039

@@ -70,7 +69,7 @@ const def = story(
7069
if (destroyed) {
7170
return
7271
}
73-
setTimeout(append, random(10, 100))
72+
setTimeout(append, random(100, 500))
7473
}
7574
append()
7675
}

src/logcat/style.scss

+2-4
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,12 @@
44
@use 'sass:map';
55

66
.luna-logcat {
7-
padding-left: #{theme.$padding-x-x-s}px;
87
unicode-bidi: embed;
98
position: relative;
10-
overflow: auto;
119
line-height: #{theme.$line-height-l-g}em;
1210
border: 1px solid theme.$color-border;
1311
white-space: pre;
14-
will-change: transform;
15-
transform: translate3d(0, 0, 0);
12+
overflow: hidden;
1613
@include mixin.component();
1714
& {
1815
user-select: text;
@@ -28,6 +25,7 @@
2825
}
2926

3027
.entry {
28+
padding-left: #{theme.$padding-x-x-s}px;
3129
font-size: #{theme.$font-size-s-m}px;
3230
}
3331

src/virtual-list/index.ts

+31-11
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export default class VirtualList extends Component<IOptions> {
4545
private updateTimer: NodeJS.Timeout | null = null
4646
private updateItems: Item[] = []
4747
private displayItems: Item[] = []
48+
private hasScrollbar = false
4849
private scrollTimer: NodeJS.Timeout | null = null
4950
constructor(container: HTMLElement, options: IOptions = {}) {
5051
super(container, { compName: 'virtual-list' }, options)
@@ -78,7 +79,7 @@ export default class VirtualList extends Component<IOptions> {
7879
/** Set items. */
7980
setItems(els: HTMLElement[]) {
8081
this.updateItems = []
81-
each(els, el => this.append(el))
82+
each(els, (el) => this.append(el))
8283
}
8384
/** Remove item. */
8485
remove(el: HTMLElement) {
@@ -105,6 +106,15 @@ export default class VirtualList extends Component<IOptions> {
105106
this._update()
106107
}
107108
}
109+
/** Scroll to end. */
110+
scrollToEnd() {
111+
const { container } = this
112+
const { scrollTop, scrollHeight, clientHeight } = container
113+
if (scrollTop <= scrollHeight - clientHeight) {
114+
container.scrollTop = 10000000
115+
this.render()
116+
}
117+
}
108118
private _update = () => {
109119
const items = this.updateItems.splice(0, 1000)
110120
if (isEmpty(items)) {
@@ -115,7 +125,12 @@ export default class VirtualList extends Component<IOptions> {
115125
const { fakeEl } = this
116126
const fakeFrag = document.createDocumentFragment()
117127
for (let i = 0; i < len; i++) {
118-
fakeFrag.appendChild(items[i].el)
128+
const item = items[i]
129+
if (item.el.parentNode === this.el) {
130+
item.update()
131+
} else {
132+
fakeFrag.appendChild(item.el)
133+
}
119134
}
120135
fakeEl.appendChild(fakeFrag)
121136
for (let i = 0; i < len; i++) {
@@ -155,7 +170,9 @@ export default class VirtualList extends Component<IOptions> {
155170
this.space.style.width = width + 'px'
156171
}
157172
private bindEvent() {
158-
this.$container.on('scroll', this.onScroll)
173+
this.$container
174+
.on('scroll', this.onScroll)
175+
.on('click', () => (this.isAtBottom = false))
159176
}
160177
private render = throttle(
161178
({ topTolerance = 500, bottomTolerance = 500 } = {}) => {
@@ -164,9 +181,9 @@ export default class VirtualList extends Component<IOptions> {
164181
return
165182
}
166183

167-
const { scrollTop, offsetHeight } = container as HTMLElement
184+
const { scrollTop, scrollHeight, clientHeight } = container as HTMLElement
168185
const top = scrollTop - topTolerance
169-
const bottom = scrollTop + offsetHeight + bottomTolerance
186+
const bottom = scrollTop + clientHeight + bottomTolerance
170187

171188
const { items } = this
172189

@@ -207,6 +224,7 @@ export default class VirtualList extends Component<IOptions> {
207224
) {
208225
return
209226
}
227+
this.displayItems = displayItems
210228

211229
const frag = document.createDocumentFragment()
212230
for (let i = 0, len = displayItems.length; i < len; i++) {
@@ -216,12 +234,14 @@ export default class VirtualList extends Component<IOptions> {
216234
el.textContent = ''
217235
el.appendChild(frag)
218236

219-
if (this.options.autoScroll) {
220-
const { scrollHeight } = container
221-
if (this.isAtBottom && scrollTop <= scrollHeight - offsetHeight) {
222-
container.scrollTop = 10000000
223-
this.render()
224-
}
237+
if (this.options.autoScroll && this.isAtBottom) {
238+
this.scrollToEnd()
239+
}
240+
241+
const hasScrollbar = scrollHeight > clientHeight
242+
if (this.hasScrollbar !== hasScrollbar) {
243+
this.hasScrollbar = hasScrollbar
244+
this.update()
225245
}
226246
},
227247
16

src/virtual-list/style.scss

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
height: 100%;
66
position: relative;
77
will-change: scroll-position;
8+
transform: translate3d(0, 0, 0);
89
}
910

1011
.fake-items {

0 commit comments

Comments
 (0)