Skip to content

Commit

Permalink
Add collapse option to StatsWidget (#166)
Browse files Browse the repository at this point in the history
  • Loading branch information
Isaac Brodsky committed Oct 20, 2021
1 parent a115366 commit 8147eb1
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 30 deletions.
80 changes: 58 additions & 22 deletions modules/stats-widget/src/stats-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import {formatMemory, formatTime} from './format-utils';
import {Stats, Stat} from '@probe.gl/stats';

const RIGHT_ARROW = '\u25b6';
const DOWN_ARROW = '\u2b07';

const DEFAULT_CSS = {
css: {
position: 'fixed',
Expand Down Expand Up @@ -42,26 +45,26 @@ export type StatWidgetProps = {
export default class StatsWidget {
stats: Stats;
title: string;
collapsed: boolean = false;
_framesPerUpdate: number;
_formatters;
_formatters = DEFAULT_FORMATTERS;
_resetOnUpdate = {};
_counter = 0;
_css;
_headerCSS;
_itemCSS;
_container = null;
_header = null;
_container: HTMLElement = null;
_innerContainer: HTMLElement = null;
_statsContainer: HTMLElement = null;
_header: HTMLElement = null;
_items = {};
_added: boolean = false;

constructor(stats: Stats, options?: StatWidgetProps) {
stats = stats;
this.title = options?.title || 'Stats';
this.stats = stats;
this.title = options?.title;

this._framesPerUpdate = Math.round(Math.max(options?.framesPerUpdate || 1, 1));
this._formatters = DEFAULT_FORMATTERS;
this._resetOnUpdate = {};

this._counter = 0;

this._initializeFormatters(options);
this._initializeUpdateConfigs(options);
Expand All @@ -74,20 +77,14 @@ export default class StatsWidget {
delete this._css.header;
delete this._css.item;

this._container = null;
this._header = null;
this._items = {};

this._createDOM(options?.container);
this._createDOMHeader();
this._createDOMStats();
}

setStats(stats: Stats): void {
this.stats = stats;
// @ts-ignore
this._createDOMStats(this._container);
this._setHeaderContent(stats.id);
this._createDOMStats();
this._setHeaderContent();

this.update();
}
Expand All @@ -108,6 +105,24 @@ export default class StatsWidget {
this._update();
}

/**
* Remove the stats widget from the container it was added to.
* The stats widget cannot be reused after this is called.
*/
remove(): void {
// if re-adding the stats widget is needed, a code path to
// re-add the _innerContainer should be added, e.g. in _update.
this._container.removeChild(this._innerContainer);
}

setCollapsed(collapsed: boolean): void {
this.collapsed = collapsed;
if (this._statsContainer) {
this._statsContainer.style.display = this.collapsed ? 'none' : 'block';
}
this._setHeaderContent();
}

_update(): void {
// make sure that we clear the old text before drawing new text.
this.stats.forEach(stat => {
Expand Down Expand Up @@ -157,6 +172,20 @@ export default class StatsWidget {
}
document.body.appendChild(this._container);
}

// When adding the widget to an existing element, make sure there
// is a container for this widget specifically, so that multiple widgets
// can be added to the same container.
this._innerContainer = document.createElement('div');
this._container.appendChild(this._innerContainer);

// Create the contents of the stats widget, starting with the header
this._createDOMHeader();

// Create an element for the stats themselves, so we can collapse it later
this._statsContainer = document.createElement('div');
this._statsContainer.style.display = 'block';
this._innerContainer.appendChild(this._statsContainer);
}

_createDOMHeader(): void {
Expand All @@ -166,18 +195,25 @@ export default class StatsWidget {
for (const name in this._headerCSS) {
this._header.style[name] = this._headerCSS[name];
}
this._container.appendChild(this._header);
this._header.onclick = this._toggleCollapsed.bind(this);
this._innerContainer.appendChild(this._header);
}

this._setHeaderContent();
}

_setHeaderContent(title?: string): void {
_setHeaderContent() {
if (this._header) {
this._header.innerText = title || this.title || (this.stats && this.stats.id) || '';
const collapsedState = this.collapsed ? RIGHT_ARROW : DOWN_ARROW;
const title = this.title || (this.stats && this.stats.id) || 'Stats';
this._header.innerText = `${collapsedState} ${title}`;
}
}

_toggleCollapsed() {
this.setCollapsed(!this.collapsed);
}

_createDOMStats(): void {
if (this.stats instanceof Stats) {
this.stats.forEach(stat => {
Expand All @@ -187,7 +223,7 @@ export default class StatsWidget {
}

_createDOMItem(statName: string): void {
if (!this._container) {
if (!this._statsContainer) {
return;
}

Expand All @@ -199,7 +235,7 @@ export default class StatsWidget {
for (const name in this._itemCSS) {
this._items[statName].style[name] = this._itemCSS[name];
}
this._container.appendChild(this._items[statName]);
this._statsContainer.appendChild(this._items[statName]);
}

_getLines(stat: Stat): string[] {
Expand Down
58 changes: 53 additions & 5 deletions modules/stats-widget/test/stats-widget.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@ test('StatsWidget#Constructor with no stats or options', t => {
t.ok(statsWidget._header, 'Should create a dom header.');
t.equals(statsWidget._container.childNodes.length, 1, 'Should have one child node.');
t.ok(
statsWidget._container.childNodes[0] === statsWidget._header,
'Should append header to container as the first child'
statsWidget._container.childNodes[0] === statsWidget._innerContainer,
'Should append inner container to container as the first child'
);
t.ok(
statsWidget._innerContainer.childNodes[0] === statsWidget._header,
'Should append header to inner container as the first child'
);
t.ok(
statsWidget._innerContainer.childNodes[1] === statsWidget._statsContainer,
'Should append stats container to inner container as the second child'
);
t.end();
});
Expand All @@ -65,12 +73,35 @@ test('StatsWidget#setStats', t => {
statsWidget.setStats(stats);

t.equals(Object.keys(statsWidget._items).length, 3, 'Should have 3 items.');
t.equals(statsWidget._container.childNodes.length, 4, 'Should have 4 child nodes.');
t.equals(statsWidget._container.childNodes.length, 1, 'Should have 2 child nodes.');
t.equals(statsWidget._innerContainer.childNodes.length, 2, 'Should have 2 child nodes.');
t.equals(statsWidget._statsContainer.childNodes.length, 3, 'Should have 3 child nodes.');
t.equals(statsWidget._counter, 1, 'Should call update() and increase _counter.');

t.end();
});

test('StatsWidget#collapse', t => {
const container = _global.document.createElement('div');
container.id = 'test-stats-widget-container';
const statsWidget = new StatsWidget(getStatsObject(), {container});

t.ok(!statsWidget.collapsed, 'Starts uncollapsed');
t.equals(statsWidget._statsContainer.style.display, 'block', 'Starts in block display');

statsWidget.setCollapsed(true);

t.ok(statsWidget.collapsed, 'Collapses');
t.equals(statsWidget._statsContainer.style.display, 'none', 'Collapses to none display');

statsWidget.setCollapsed(false);

t.ok(!statsWidget.collapsed, 'Uncollapses');
t.equals(statsWidget._statsContainer.style.display, 'block', 'Uncollapses to block display');

t.end();
});

/* eslint-disable */
test('StatsWidget#Update stats', t => {
const container = _global.document.createElement('div');
Expand All @@ -86,7 +117,9 @@ test('StatsWidget#Update stats', t => {
// @ts-ignore
t.equals(Object.keys(statsWidget._items).length, 3, 'Should have 3 items.');
// @ts-ignore
t.equals(statsWidget._container.childNodes.length, 4, 'Should have 4 child nodes.');
t.equals(statsWidget._container.childNodes.length, 1, 'Should have 1 child nodes.');
t.equals(statsWidget._innerContainer.childNodes.length, 2, 'Should have 2 child nodes.');
t.equals(statsWidget._statsContainer.childNodes.length, 3, 'Should have 3 child nodes.');

// @ts-ignore
t.equals(statsWidget._items.Count.innerHTML, 'Count: 1', 'Should correctly update count stats.');
Expand Down Expand Up @@ -134,7 +167,11 @@ test('StatsWidget#formatters', t => {

statsWidget.setStats(new Stats({id: 'test-stats-2'}));
// @ts-ignore
t.equals(statsWidget._header.innerText, 'test-stats-2', "Should use the new stats' header.");
t.equals(
statsWidget._header.innerText,
'\u2b07 test-stats-2',
"Should use the new stats' header."
);

t.end();
});
Expand All @@ -160,3 +197,14 @@ test('StatsWidget#resetOnUpdate', t => {

t.end();
});

test('StatsWidget#remove', t => {
const container = _global.document.createElement('div');
container.id = 'test-stats-widget-container';
const statsWidget = new StatsWidget(null, {container});
t.ok(statsWidget._container === container);
t.equals(statsWidget._container.childNodes.length, 1, 'Should have 1 child node.');
statsWidget.remove();
t.equals(statsWidget._container.childNodes.length, 0, 'Should have 0 child nodes.');
t.end();
});
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3828,9 +3828,9 @@ camelcase@^5.0.0, camelcase@^5.2.0, camelcase@^5.3.1:
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==

caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001261:
version "1.0.30001264"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001264.tgz"
integrity sha512-Ftfqqfcs/ePiUmyaySsQ4PUsdcYyXG2rfoBVsk3iY1ahHaJEw65vfb7Suzqm+cEkwwPIv/XWkg27iCpRavH4zA==
version "1.0.30001265"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz"
integrity sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw==

caseless@~0.11.0:
version "0.11.0"
Expand Down

0 comments on commit 8147eb1

Please sign in to comment.