aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Golton <stevegolton@google.com>2023-05-17 08:16:55 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2023-05-17 08:16:55 +0000
commit06ea518ef4373ccb1acaf75e833537ee7f1f5fa8 (patch)
tree8a37a5021553477668e092053063f6ff765c9846
parente97fe02aaee18a0acc361a64f45751e9a3ed1057 (diff)
parente7aaa4cf1f495db6e5389cd69d2de36a059a3a1f (diff)
downloadperfetto-06ea518ef4373ccb1acaf75e833537ee7f1f5fa8.tar.gz
Merge "Add lazy-loading tree node component"
-rw-r--r--ui/src/frontend/widgets/tree.ts78
-rw-r--r--ui/src/frontend/widgets_page.ts16
2 files changed, 89 insertions, 5 deletions
diff --git a/ui/src/frontend/widgets/tree.ts b/ui/src/frontend/widgets/tree.ts
index b362391d1..e79734386 100644
--- a/ui/src/frontend/widgets/tree.ts
+++ b/ui/src/frontend/widgets/tree.ts
@@ -1,7 +1,10 @@
import m from 'mithril';
+
import {classNames} from '../classnames';
import {globals} from '../globals';
+
import {Button} from './button';
+import {Spinner} from './spinner';
import {hasChildren} from './utils';
export enum TreeLayout {
@@ -50,14 +53,14 @@ export class Tree implements m.ClassComponent<TreeAttrs> {
interface TreeNodeAttrs {
// Content to display in the left hand column.
// If omitted, this side will be blank.
- left?: m.Child;
+ left?: m.Children;
// Content to display in the right hand column.
// If omitted, this side will be left blank.
- right?: m.Child;
+ right?: m.Children;
// Content to display in the right hand column when the node is collapsed.
// If omitted, the value of `right` shall be shown when collapsed instead.
// If the node has no children, this value is never shown.
- summary?: m.Child;
+ summary?: m.Children;
// Whether this node is collapsed or not.
// If omitted, collapsed state 'uncontrolled' - i.e. controlled internally.
collapsed?: boolean;
@@ -92,7 +95,7 @@ export class TreeNode implements m.ClassComponent<TreeNodeAttrs> {
private renderRight(vnode: m.CVnode<TreeNodeAttrs>) {
const {attrs: {right, summary}} = vnode;
- if (hasChildren(vnode) && this.collapsed) {
+ if (hasChildren(vnode) && this.isCollapsed(vnode)) {
return m('.pf-tree-right', summary ?? right);
} else {
return m('.pf-tree-right', right);
@@ -146,3 +149,70 @@ export function dictToTree(dict: {[key: string]: m.Child}): m.Children {
}
return m(Tree, children);
}
+
+interface LazyTreeNodeAttrs {
+ // Same as TreeNode (see above).
+ left?: m.Children;
+ // Same as TreeNode (see above).
+ right?: m.Children;
+ // Same as TreeNode (see above).
+ summary?: m.Children;
+ // A callback to be called when the TreeNode is expanded, in order to fetch
+ // child nodes.
+ // The callback must return a promise to a function which returns m.Children.
+ // The reason the promise must return a function rather than the actual
+ // children is to avoid storing vnodes between render cycles, which is a bug
+ // in Mithril.
+ fetchData: () => Promise<() => m.Children>;
+ // Whether to keep child nodes in memory after the node has been collapsed.
+ // Defaults to true
+ hoardData?: boolean;
+}
+
+// This component is a TreeNode which only loads child nodes when it's expanded.
+// This allows us to represent huge trees without having to load all the data
+// up front, and even allows us to represent infinite or recursive trees.
+export class LazyTreeNode implements m.ClassComponent<LazyTreeNodeAttrs> {
+ private collapsed: boolean = true;
+ private renderChildren = this.renderSpinner;
+
+ private renderSpinner(): m.Children {
+ return m(TreeNode, {left: m(Spinner)});
+ }
+
+ view({attrs}: m.CVnode<LazyTreeNodeAttrs>): m.Children {
+ const {
+ left,
+ right,
+ summary,
+ fetchData,
+ hoardData = true,
+ } = attrs;
+
+ return m(
+ TreeNode,
+ {
+ left,
+ right,
+ summary,
+ collapsed: this.collapsed,
+ onCollapseChanged: (collapsed) => {
+ if (collapsed) {
+ if (!hoardData) {
+ this.renderChildren = this.renderSpinner;
+ }
+ } else {
+ fetchData().then((result) => {
+ if (!this.collapsed) {
+ this.renderChildren = result;
+ globals.rafScheduler.scheduleFullRedraw();
+ }
+ });
+ }
+ this.collapsed = collapsed;
+ globals.rafScheduler.scheduleFullRedraw();
+ },
+ },
+ this.renderChildren());
+ }
+}
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts
index 5d550e2a9..989be89be 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/frontend/widgets_page.ts
@@ -34,7 +34,7 @@ import {Select} from './widgets/select';
import {Spinner} from './widgets/spinner';
import {Switch} from './widgets/switch';
import {TextInput} from './widgets/text_input';
-import {Tree, TreeLayout, TreeNode} from './widgets/tree';
+import {LazyTreeNode, Tree, TreeLayout, TreeNode} from './widgets/tree';
const options: {[key: string]: boolean} = {
foobar: false,
@@ -243,6 +243,18 @@ class WidgetShowcase implements m.ClassComponent<WidgetShowcaseAttrs> {
}
}
+function recursiveLazyTreeNode(
+ left: string, summary: string, hoardData: boolean): m.Children {
+ return m(LazyTreeNode, {
+ left,
+ summary,
+ hoardData,
+ fetchData: async () => {
+ await new Promise((r) => setTimeout(r, 200));
+ return () => recursiveLazyTreeNode(left, summary, hoardData);
+ },
+ });
+}
export const WidgetsPage = createPage({
view() {
@@ -563,6 +575,8 @@ export const WidgetsPage = createPage({
left: 'Process',
right: m(Anchor, {text: '/bin/foo[789]', icon: 'open_in_new'}),
}),
+ recursiveLazyTreeNode('Lazy', '(hoarding)', true),
+ recursiveLazyTreeNode('Lazy', '(non-hoarding)', false),
m(
TreeNode,
{