UI: Switch to SCSS
Adds some initial scss support to the UI build.
Change-Id: I10c1c5b9a79be1c122cf169599dc62a17a8bd465
diff --git a/ui/BUILD.gn b/ui/BUILD.gn
index a42e9bb..7ebca4c 100644
--- a/ui/BUILD.gn
+++ b/ui/BUILD.gn
@@ -28,10 +28,10 @@
deps = [
":assets_dist",
":controller_bundle_dist",
- ":css_dist",
":engine_bundle_dist",
":frontend_bundle_dist",
":index_dist",
+ ":scss",
":test_scripts",
":wasm_dist",
]
@@ -247,6 +247,33 @@
}
# +----------------------------------------------------------------------------+
+# | Build css. |
+# +----------------------------------------------------------------------------+
+
+# Build css.
+node_bin("scss") {
+ deps = [
+ ":dist_symlink",
+ ]
+ main_css = "src/assets/perfetto.scss"
+ inputs = [
+ main_css,
+ "src/assets/sidebar.scss",
+ "src/assets/topbar.scss",
+ ]
+ outputs = [
+ "$ui_dir/perfetto.css",
+ ]
+
+ node_cmd = "node-sass"
+ args = [
+ "--quiet",
+ rebase_path(main_css, root_build_dir),
+ rebase_path(outputs[0], root_build_dir),
+ ]
+}
+
+# +----------------------------------------------------------------------------+
# | Copy rules: create the final output directory. |
# +----------------------------------------------------------------------------+
copy("index_dist") {
@@ -258,14 +285,6 @@
]
}
-copy("css_dist") {
- sources = [
- "perfetto.css",
- ]
- outputs = [
- "$ui_dir/perfetto.css",
- ]
-}
copy("assets_dist") {
sources = [
"src/assets",
diff --git a/ui/index.html b/ui/index.html
index 0217eb1..a1cf24a 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -2,16 +2,16 @@
<html lang="en-us">
<head>
<title>Perfetto UI</title>
+ <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
<link href="perfetto.css" rel="stylesheet">
+ <link rel="icon" type="image/png" href="/assets/logo.png">
<link rel="preload" href="controller_bundle.js" as="script">
<link rel="preload" href="engine_bundle.js" as="script">
<link rel="preload" href="trace_processor.wasm" as="fetch">
<link href='https://fonts.googleapis.com/css?family=Raleway:100,400|Google+Sans:100,400,500|Inconsolata|Material+Icons' rel='stylesheet' type='text/css'>
</head>
<body>
- <main>
- <div class="full-page-loading-screen"></div>
- </main>
- <script src="frontend_bundle.js"></script>
+ <div class="full-page-loading-screen"></div>
</body>
</html>
+<script src="frontend_bundle.js"></script>
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 92e835d..ad10e77 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -127,6 +127,12 @@
"integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=",
"dev": true
},
+ "abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+ "dev": true
+ },
"accepts": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
@@ -267,6 +273,22 @@
"default-require-extensions": "^1.0.0"
}
},
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+ "dev": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
+ "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+ "dev": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -300,6 +322,12 @@
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
"dev": true
},
+ "array-find-index": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+ "dev": true
+ },
"array-unique": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
@@ -363,6 +391,12 @@
"integrity": "sha1-dhfBkXQB/Yykooqtzj266Yr+tDI=",
"dev": true
},
+ "async-foreach": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
+ "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=",
+ "dev": true
+ },
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
@@ -759,6 +793,24 @@
"integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=",
"dev": true
},
+ "block-stream": {
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
+ "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
+ "dev": true,
+ "requires": {
+ "inherits": "~2.0.0"
+ }
+ },
+ "boom": {
+ "version": "2.10.1",
+ "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+ "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+ "dev": true,
+ "requires": {
+ "hoek": "2.x.x"
+ }
+ },
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -1051,6 +1103,24 @@
"dev": true,
"optional": true
},
+ "camelcase-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+ "dev": true,
+ "requires": {
+ "camelcase": "^2.0.0",
+ "map-obj": "^1.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+ "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+ "dev": true
+ }
+ }
+ },
"capture-exit": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz",
@@ -1305,6 +1375,12 @@
"moment": "*"
}
},
+ "console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+ "dev": true
+ },
"cookie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
@@ -1340,6 +1416,15 @@
"which": "^1.2.9"
}
},
+ "cryptiles": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+ "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+ "dev": true,
+ "requires": {
+ "boom": "2.x.x"
+ }
+ },
"cssom": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz",
@@ -1355,6 +1440,15 @@
"cssom": "0.3.x"
}
},
+ "currently-unhandled": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+ "dev": true,
+ "requires": {
+ "array-find-index": "^1.0.1"
+ }
+ },
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -1474,6 +1568,12 @@
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
+ "delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+ "dev": true
+ },
"depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
@@ -2763,18 +2863,61 @@
}
}
},
+ "fstream": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
+ "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "inherits": "~2.0.0",
+ "mkdirp": ">=0.5 0",
+ "rimraf": "2"
+ }
+ },
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
+ "gauge": {
+ "version": "2.7.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+ "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "gaze": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
+ "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
+ "dev": true,
+ "requires": {
+ "globule": "^1.0.0"
+ }
+ },
"get-caller-file": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
"integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=",
"dev": true
},
+ "get-stdin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+ "dev": true
+ },
"get-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
@@ -2835,6 +2978,17 @@
"integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
"dev": true
},
+ "globule": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
+ "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
+ "dev": true,
+ "requires": {
+ "glob": "~7.1.1",
+ "lodash": "~4.17.10",
+ "minimatch": "~3.0.2"
+ }
+ },
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
@@ -2939,6 +3093,12 @@
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
+ "has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+ "dev": true
+ },
"has-value": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
@@ -2971,6 +3131,24 @@
}
}
},
+ "hawk": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+ "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+ "dev": true,
+ "requires": {
+ "boom": "2.x.x",
+ "cryptiles": "2.x.x",
+ "hoek": "2.x.x",
+ "sntp": "1.x.x"
+ }
+ },
+ "hoek": {
+ "version": "2.16.3",
+ "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+ "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=",
+ "dev": true
+ },
"home-or-tmp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
@@ -3086,6 +3264,21 @@
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
+ "in-publish": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
+ "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=",
+ "dev": true
+ },
+ "indent-string": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+ "dev": true,
+ "requires": {
+ "repeating": "^2.0.0"
+ }
+ },
"indexof": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
@@ -3921,6 +4114,12 @@
"merge-stream": "^1.0.1"
}
},
+ "js-base64": {
+ "version": "2.4.8",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.8.tgz",
+ "integrity": "sha512-hm2nYpDrwoO/OzBhdcqs/XGT6XjSuSSCVEpia+Kl2J6x4CYt5hISlVL/AYU1khoDXv0AQVgxtdJySb9gjAn56Q==",
+ "dev": true
+ },
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
@@ -4004,6 +4203,15 @@
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=",
"dev": true
},
+ "json-stable-stringify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
+ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
+ "dev": true,
+ "requires": {
+ "jsonify": "~0.0.0"
+ }
+ },
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@@ -4025,6 +4233,12 @@
"graceful-fs": "^4.1.6"
}
},
+ "jsonify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
+ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=",
+ "dev": true
+ },
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@@ -4246,12 +4460,30 @@
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==",
"dev": true
},
+ "lodash.assign": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
+ "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
+ "dev": true
+ },
+ "lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+ "dev": true
+ },
"lodash.isfinite": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz",
"integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=",
"dev": true
},
+ "lodash.mergewith": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
+ "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==",
+ "dev": true
+ },
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
@@ -4278,6 +4510,16 @@
"js-tokens": "^3.0.0"
}
},
+ "loud-rejection": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+ "dev": true,
+ "requires": {
+ "currently-unhandled": "^0.4.1",
+ "signal-exit": "^3.0.0"
+ }
+ },
"lru-cache": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz",
@@ -4312,6 +4554,12 @@
"integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
"dev": true
},
+ "map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+ "dev": true
+ },
"map-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
@@ -4336,6 +4584,24 @@
"mimic-fn": "^1.0.0"
}
},
+ "meow": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+ "dev": true,
+ "requires": {
+ "camelcase-keys": "^2.0.0",
+ "decamelize": "^1.1.2",
+ "loud-rejection": "^1.0.0",
+ "map-obj": "^1.0.1",
+ "minimist": "^1.1.3",
+ "normalize-package-data": "^2.3.4",
+ "object-assign": "^4.0.1",
+ "read-pkg-up": "^1.0.1",
+ "redent": "^1.0.0",
+ "trim-newlines": "^1.0.0"
+ }
+ },
"merge": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/merge/-/merge-1.2.0.tgz",
@@ -4519,8 +4785,7 @@
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
- "dev": true,
- "optional": true
+ "dev": true
},
"nanomatch": {
"version": "1.2.9",
@@ -4562,6 +4827,136 @@
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
"dev": true
},
+ "node-gyp": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.7.0.tgz",
+ "integrity": "sha512-qDQE/Ft9xXP6zphwx4sD0t+VhwV7yFaloMpfbL2QnnDZcyaiakWlLdtFGGQfTAwpFHdpbRhRxVhIHN1OKAjgbg==",
+ "dev": true,
+ "requires": {
+ "fstream": "^1.0.0",
+ "glob": "^7.0.3",
+ "graceful-fs": "^4.1.2",
+ "mkdirp": "^0.5.0",
+ "nopt": "2 || 3",
+ "npmlog": "0 || 1 || 2 || 3 || 4",
+ "osenv": "0",
+ "request": ">=2.9.0 <2.82.0",
+ "rimraf": "2",
+ "semver": "~5.3.0",
+ "tar": "^2.0.0",
+ "which": "1"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "4.11.8",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz",
+ "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=",
+ "dev": true,
+ "requires": {
+ "co": "^4.6.0",
+ "json-stable-stringify": "^1.0.1"
+ }
+ },
+ "assert-plus": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+ "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
+ "dev": true
+ },
+ "aws-sign2": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+ "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=",
+ "dev": true
+ },
+ "form-data": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz",
+ "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=",
+ "dev": true,
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.5",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "har-schema": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
+ "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=",
+ "dev": true
+ },
+ "har-validator": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz",
+ "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=",
+ "dev": true,
+ "requires": {
+ "ajv": "^4.9.1",
+ "har-schema": "^1.0.5"
+ }
+ },
+ "http-signature": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+ "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "^0.2.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ }
+ },
+ "performance-now": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz",
+ "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=",
+ "dev": true
+ },
+ "qs": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz",
+ "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=",
+ "dev": true
+ },
+ "request": {
+ "version": "2.81.0",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz",
+ "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=",
+ "dev": true,
+ "requires": {
+ "aws-sign2": "~0.6.0",
+ "aws4": "^1.2.1",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.5",
+ "extend": "~3.0.0",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.1.1",
+ "har-validator": "~4.2.1",
+ "hawk": "~3.1.3",
+ "http-signature": "~1.1.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.7",
+ "oauth-sign": "~0.8.1",
+ "performance-now": "^0.2.0",
+ "qs": "~6.4.0",
+ "safe-buffer": "^5.0.1",
+ "stringstream": "~0.0.4",
+ "tough-cookie": "~2.3.0",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.0.0"
+ }
+ },
+ "semver": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+ "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+ "dev": true
+ }
+ }
+ },
"node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -4580,6 +4975,79 @@
"which": "^1.3.0"
}
},
+ "node-sass": {
+ "version": "4.9.2",
+ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.2.tgz",
+ "integrity": "sha512-LdxoJLZutx0aQXHtWIYwJKMj+9pTjneTcLWJgzf2XbGu0q5pRNqW5QvFCEdm3mc5rJOdru/mzln5d0EZLacf6g==",
+ "dev": true,
+ "requires": {
+ "async-foreach": "^0.1.3",
+ "chalk": "^1.1.1",
+ "cross-spawn": "^3.0.0",
+ "gaze": "^1.0.0",
+ "get-stdin": "^4.0.1",
+ "glob": "^7.0.3",
+ "in-publish": "^2.0.0",
+ "lodash.assign": "^4.2.0",
+ "lodash.clonedeep": "^4.3.2",
+ "lodash.mergewith": "^4.6.0",
+ "meow": "^3.7.0",
+ "mkdirp": "^0.5.1",
+ "nan": "^2.10.0",
+ "node-gyp": "^3.3.1",
+ "npmlog": "^4.0.0",
+ "request": "2.87.0",
+ "sass-graph": "^2.2.4",
+ "stdout-stream": "^1.4.0",
+ "true-case-path": "^1.0.2"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
+ "cross-spawn": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
+ "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^4.0.1",
+ "which": "^1.2.9"
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true
+ }
+ }
+ },
+ "nopt": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+ "dev": true,
+ "requires": {
+ "abbrev": "1"
+ }
+ },
"normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@@ -4610,6 +5078,18 @@
"path-key": "^2.0.0"
}
},
+ "npmlog": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+ "dev": true,
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
@@ -4809,6 +5289,16 @@
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
+ "osenv": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+ "dev": true,
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
@@ -5269,6 +5759,16 @@
"util.promisify": "^1.0.0"
}
},
+ "redent": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+ "dev": true,
+ "requires": {
+ "indent-string": "^2.1.0",
+ "strip-indent": "^1.0.1"
+ }
+ },
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
@@ -5602,12 +6102,109 @@
}
}
},
+ "sass-graph": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
+ "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
+ "dev": true,
+ "requires": {
+ "glob": "^7.0.0",
+ "lodash": "^4.0.0",
+ "scss-tokenizer": "^0.2.3",
+ "yargs": "^7.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+ "dev": true
+ },
+ "cliui": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
+ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
+ "os-locale": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+ "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
+ "dev": true,
+ "requires": {
+ "lcid": "^1.0.0"
+ }
+ },
+ "which-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
+ "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
+ "dev": true
+ },
+ "yargs": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
+ "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
+ "dev": true,
+ "requires": {
+ "camelcase": "^3.0.0",
+ "cliui": "^3.2.0",
+ "decamelize": "^1.1.1",
+ "get-caller-file": "^1.0.1",
+ "os-locale": "^1.4.0",
+ "read-pkg-up": "^1.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^1.0.2",
+ "which-module": "^1.0.0",
+ "y18n": "^3.2.1",
+ "yargs-parser": "^5.0.0"
+ }
+ },
+ "yargs-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
+ "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
+ "dev": true,
+ "requires": {
+ "camelcase": "^3.0.0"
+ }
+ }
+ }
+ },
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"dev": true
},
+ "scss-tokenizer": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
+ "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
+ "dev": true,
+ "requires": {
+ "js-base64": "^2.1.8",
+ "source-map": "^0.4.2"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+ "dev": true,
+ "requires": {
+ "amdefine": ">=0.0.4"
+ }
+ }
+ }
+ },
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
@@ -5886,6 +6483,15 @@
"kind-of": "^3.2.0"
}
},
+ "sntp": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+ "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+ "dev": true,
+ "requires": {
+ "hoek": "2.x.x"
+ }
+ },
"socket.io": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.0.4.tgz",
@@ -6103,6 +6709,15 @@
"integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=",
"dev": true
},
+ "stdout-stream": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz",
+ "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=",
+ "dev": true,
+ "requires": {
+ "readable-stream": "^2.0.1"
+ }
+ },
"stealthy-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
@@ -6146,6 +6761,28 @@
}
}
},
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ },
+ "dependencies": {
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ }
+ }
+ },
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -6155,6 +6792,21 @@
"safe-buffer": "~5.1.0"
}
},
+ "stringstream": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz",
+ "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==",
+ "dev": true
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
"strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
@@ -6170,6 +6822,15 @@
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
"dev": true
},
+ "strip-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+ "dev": true,
+ "requires": {
+ "get-stdin": "^4.0.1"
+ }
+ },
"supports-color": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
@@ -6185,6 +6846,17 @@
"integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=",
"dev": true
},
+ "tar": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
+ "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
+ "dev": true,
+ "requires": {
+ "block-stream": "*",
+ "fstream": "^1.0.2",
+ "inherits": "2"
+ }
+ },
"test-exclude": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.1.tgz",
@@ -6354,12 +7026,42 @@
}
}
},
+ "trim-newlines": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+ "dev": true
+ },
"trim-right": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
"dev": true
},
+ "true-case-path": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.2.tgz",
+ "integrity": "sha1-fskRMJJHZsf1c74wIMNPj9/QDWI=",
+ "dev": true,
+ "requires": {
+ "glob": "^6.0.4"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
+ "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
+ "dev": true,
+ "requires": {
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "2 || 3",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
+ }
+ },
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
@@ -6427,9 +7129,9 @@
"dev": true
},
"typescript": {
- "version": "2.8.4",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.4.tgz",
- "integrity": "sha512-IIU5cN1mR5J3z9jjdESJbnxikTrEz3lzAw/D0Tf45jHpBp55nY31UkUvmVHoffCfKHTqJs3fCLPDxknQTTFegQ==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz",
+ "integrity": "sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==",
"dev": true
},
"ua-parser-js": {
@@ -6725,6 +7427,15 @@
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
+ "wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
"window-size": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
diff --git a/ui/package.json b/ui/package.json
index 669c67b..ebac0d3 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -20,6 +20,7 @@
"@types/puppeteer": "^1.3.4",
"jest": "^23.1.0",
"lite-server": "^2.3.0",
+ "node-sass": "^4.9.2",
"puppeteer": "^1.5.0",
"rollup": "^0.59.4",
"rollup-plugin-commonjs": "^9.1.3",
diff --git a/ui/src/assets/logo-3d.png b/ui/src/assets/logo-3d.png
new file mode 100644
index 0000000..60626a9
--- /dev/null
+++ b/ui/src/assets/logo-3d.png
Binary files differ
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
new file mode 100644
index 0000000..1c478ce
--- /dev/null
+++ b/ui/src/assets/perfetto.scss
@@ -0,0 +1,262 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+:root {
+ --sidebar-width: 256px;
+ --topbar-height: 48px;
+}
+
+@mixin transition($time:0.1s) {
+ transition: opacity $time ease, background-color $time ease, max-height $time ease, max-width $time ease, border-radius $time ease;
+}
+
+@mixin material-icon($content) {
+ direction: ltr;
+ display: inline-block;
+ font-family: 'Material Icons';
+ font-size: 24px;
+ font-style: normal;
+ font-weight: normal;
+ letter-spacing: normal;
+ line-height: 1;
+ text-transform: none;
+ white-space: nowrap;
+ word-wrap: normal;
+ -webkit-font-feature-settings: 'liga';
+ -webkit-font-smoothing: antialiased;
+ content: $content;
+}
+
+* {
+ box-sizing: border-box;
+ overflow: hidden;
+ -webkit-tap-highlight-color: none;
+ touch-action: none;
+}
+
+html {
+ font-family: Roboto, verdana, sans-serif;
+ height: 100%;
+ width: 100%;
+}
+
+html,
+body {
+ height: 100%;
+ width: 100%;
+ padding: 0;
+ margin: 0;
+ user-select: none;
+}
+
+h1,
+h2,
+h3 {
+ font-family: initial;
+ font-size: initial;
+ font-weight: initial;
+ padding: 0;
+ margin: 0;
+}
+
+table {
+ user-select: text;
+}
+
+body {
+ display: grid;
+ grid-template-areas: "sidebar topbar" "sidebar page";
+ grid-template-rows: var(--topbar-height) auto;
+ grid-template-columns: var(--sidebar-width) auto;
+ color: #121212;
+}
+
+.full-page-loading-screen {
+ position: absolute;
+ background: #3e4a5a;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: row;
+ background-image: url('assets/logo.png');
+ background-attachment: fixed;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+.page {
+ grid-area: page;
+ position: relative;
+}
+
+@import 'sidebar';
+@import 'topbar';
+.home-page {
+ text-align: center;
+ padding-top: 20vh;
+}
+
+.home-page .logo {
+ width: 250px;
+}
+
+.home-page-title {
+ font-size: 60px;
+ margin: 25px;
+ text-align: center;
+ font-family: 'Raleway', sans-serif;
+ font-weight: 100;
+ color: #333;
+}
+
+.query-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 14px;
+ border: 0;
+ thead td {
+ background-color: hsl(214, 22%, 90%);
+ color: #262f3b;
+ text-align: center;
+ padding: 1px 3px;
+ border-style: solid;
+ border-color: #fff;
+ border-right-width: 1px;
+ border-left-width: 1px;
+ }
+ tbody tr {
+ @include transition();
+ background-color: hsl(214, 22%, 100%);
+ font-family: 'Inconsolata', monospace;
+ &:nth-child(even) {
+ background-color: hsl(214, 22%, 95%);
+ }
+ td:first-child {
+ padding-left: 5px;
+ }
+ td:last-child {
+ padding-right: 5px;
+ }
+ &:hover {
+ background-color: hsl(214, 22%, 90%);
+ }
+ }
+}
+
+.page header {
+ height: 25px;
+ line-height: 25px;
+ background-color: hsl(213, 22%, 82%);
+ color: hsl(213, 22%, 20%);
+ font-family: 'Google sans';
+ font-size: 15px;
+ font-weight: 400;
+ padding: 0 10px;
+ vertical-align: middle;
+ border-color: hsl(213, 22%, 75%);
+ border-style: solid;
+ border-top-width: 1px;
+ border-bottom-width: 1px;
+ .code {
+ font-family: 'Inconsolata', mononspace;
+ font-size: 12px;
+ margin-left: 10px;
+ color: hsl(213, 22%, 40%);
+ }
+}
+
+.track {
+ border: 1px solid hsl(213, 22%, 82%);
+ position: absolute;
+ left: 0;
+ width: 100%;
+ .track-shell {
+ background: #fff;
+ padding: 20px;
+ border-right: 1px solid hsl(213, 22%, 82%);
+ height: 100%;
+ z-index: 100;
+ position: relative;
+ h1 {
+ margin: 0;
+ font-size: 16px;
+ font-family: 'Google Sans';
+ color: hsl(213, 22%, 30%)
+ }
+ }
+ .track-content {
+ height: 100%;
+ position: absolute;
+ top: 0;
+ }
+}
+
+.scrollable-container {
+ width: 100%;
+ height: 100%;
+ overflow-y: auto;
+ overflow-x: hidden;
+ will-change: transform;
+ position: relative;
+}
+
+/** Time Axis **/
+
+.axis {
+ overflow: hidden;
+ height: 41px;
+ position: relative;
+ .mark-label {
+ position: relative;
+ left: -100px;
+ width: 200px;
+ text-align: center;
+ cursor: default;
+ }
+ .tick {
+ height: 20px;
+ width: 1px;
+ background: #666;
+ }
+}
+
+/** Overview Timeline **/
+
+.overview-timeline {
+ width: 100%;
+ overflow: hidden;
+ height: 120px;
+ position: relative;
+}
+
+.brush-rect {
+ background: rgba(210, 210, 210, 0.7);
+ position: absolute;
+ pointer-events: none;
+ top: 0;
+ height: 100%;
+}
+
+.brush-handle {
+ position: absolute;
+ border-radius: 3px;
+ border: 1px solid #999;
+ cursor: pointer;
+ background: #fff;
+ top: 25px;
+ width: 14px;
+ height: 30px;
+ pointer-events: auto;
+}
\ No newline at end of file
diff --git a/ui/src/assets/sidebar.scss b/ui/src/assets/sidebar.scss
new file mode 100644
index 0000000..d933a9b
--- /dev/null
+++ b/ui/src/assets/sidebar.scss
@@ -0,0 +1,118 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+.sidebar {
+ grid-area: sidebar;
+ z-index: 2;
+ background-color: #262f3c;
+ overflow-y: auto;
+ >* {
+ border-bottom: 1px solid #404854;
+ }
+ input[type=file] { display:none; }
+ >header {
+ font-family: 'Raleway';
+ height: var(--topbar-height);
+ line-height: var(--topbar-height);
+ vertical-align: middle;
+ padding: 0 20px;
+ color: #fff;
+ font-weight: 400;
+ font-size: 24px;
+ letter-spacing: 0.5px;
+ &:before {
+ width: 32px;
+ height: 32px;
+ display: inline-block;
+ content: '';
+ vertical-align: middle;
+ background-image: url('/assets/logo.png');
+ background-size: contain;
+ background-repeat: no-repeat;
+ margin-right: 15px;
+ }
+ }
+ >section {
+ @include transition();
+ padding: 20px 0;
+ max-height: 80px;
+ overflow: hidden;
+ >h1,
+ >h2 {
+ font-family: 'Raleway';
+ letter-spacing: 0.25px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ margin: 0 24px;
+ }
+ >h1 {
+ color: #fff;
+ font-size: 15px;
+ font-weight: 500;
+ }
+ >h2 {
+ @include transition();
+ color: rgba(255, 255, 255, 0.5);
+ font-size: 12px;
+ margin-top: 8px;
+ font-weight: 400;
+ }
+ &:hover {
+ background-color: #373f4b;
+ }
+ &.expanded {
+ background-color: #19212b;
+ max-height: 250px;
+ >h2 {
+ opacity: 0;
+ }
+ .section-content {
+ opacity: 1;
+ }
+ }
+ }
+ .section-content {
+ @include transition();
+ opacity: 0;
+ color: #b4b7ba;
+ a {
+ color: #b4b7ba;
+ }
+ ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ }
+ li {
+ @include transition();
+ a {
+ line-height: 24px;
+ font-size: 14px;
+ font-weight: 400;
+ font-family: 'Raleway';
+ letter-spacing: 0.5px;
+ padding: 5px 24px;
+ text-decoration: none;
+ display: block;
+ }
+ .material-icons {
+ vertical-align: middle;
+ margin-right: 10px;
+ }
+ &:hover {
+ background-color: #373f4b;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/assets/topbar.scss b/ui/src/assets/topbar.scss
new file mode 100644
index 0000000..75c130c
--- /dev/null
+++ b/ui/src/assets/topbar.scss
@@ -0,0 +1,171 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+@mixin omnibox-width() {
+ width: 90%;
+ max-width: 600px;
+}
+
+.topbar {
+ grid-area: topbar;
+ position: relative;
+ z-index: 1;
+ overflow: visible;
+ background-color: hsl(215, 1%, 95%);
+ box-shadow: 0px -3px 14px 2px #bbb;
+ line-height: var(--topbar-height);
+ .omnibox {
+ @include omnibox-width();
+ @include transition(0.25s);
+ display: grid;
+ grid-template-areas: "icon input";
+ grid-template-columns: 34px auto;
+ border-radius: 20px;
+ background-color: #fcfcfc;
+ border: 0;
+ margin: 6px auto 0 auto;
+ height: 36px;
+ line-height: 36px;
+ &:before {
+ @include material-icon('search');
+ vertical-align: middle;
+ font-size: 32px;
+ margin: 5px;
+ color: #aaa;
+ grid-area: icon;
+ }
+ input {
+ grid-area: input;
+ border: 0;
+ padding: 0 10px;
+ font-size: 18px;
+ font-family: 'Google Sans';
+ color: #666;
+ background-color: transparent;
+ &:focus {
+ outline: none;
+ }
+ &::placeholder {
+ color: #aaa;
+ font-family: 'Raleway';
+ font-weight: 100;
+ }
+ }
+ &.command-mode {
+ // background-color: #354252;
+ background-color: #111;
+ border-radius: 0;
+ width: 100%;
+ max-width: 100%;
+ margin-top: 0;
+ border-left: 1px solid #404854;
+ height: var(--topbar-height);
+ input {
+ color: #9ddc67;
+ font-family: 'Inconsolata', monospace;
+ padding-left: 0;
+ }
+ &:before {
+ content: 'attach_money';
+ color: #9ddc67;
+ font-size: 26px;
+ padding-top: 5px;
+ }
+ }
+ }
+ .omnibox-results {
+ @include transition(0.25s);
+ @include omnibox-width();
+ position: absolute;
+ z-index: 1;
+ max-height: 250px;
+ top: calc(var(--topbar-height) + 2px);
+ left: 0;
+ right: 0;
+ margin: auto;
+ background-color: hsla(213, 0%, 95%, 0.99);
+ box-shadow: 0 3px 9px #ccc;
+ border-radius: 0 0 5px 5px;
+ >* {
+ height: 24px;
+ line-height: 24px;
+ padding: 0 10px;
+ font-family: 'Raleway', sans-serif;
+ font-size: 14px;
+ vertical-align: middle;
+ color: #666;
+ border-bottom: 1px solid #eee;
+ &:hover {
+ background-color: hsl(213, 20%, 92%);
+ }
+ &.selected {
+ background-color: hsl(213, 40%, 90%);
+ }
+ }
+ }
+ .progress {
+ position: absolute;
+ bottom: 0;
+ height: 1px;
+ width: 100%;
+ &:before {
+ content: '';
+ position: absolute;
+ background-color: hsl(219, 50%, 50%);
+ top: 0;
+ left: 0;
+ bottom: 0;
+ will-change: left, right;
+ animation: indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
+ }
+ &:after {
+ content: '';
+ position: absolute;
+ background-color: hsl(219, 50%, 50%);
+ top: 0;
+ left: 0;
+ bottom: 0;
+ will-change: left, right;
+ animation: indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
+ animation-delay: 1.15s;
+ }
+ }
+ @keyframes indeterminate {
+ 0% {
+ left: -35%;
+ right: 100%;
+ }
+ 60% {
+ left: 100%;
+ right: -90%;
+ }
+ 100% {
+ left: 100%;
+ right: -90%;
+ }
+ }
+ @keyframes indeterminate-short {
+ 0% {
+ left: -35%;
+ right: 100%;
+ }
+ 60% {
+ left: 100%;
+ right: -90%;
+ }
+ 100% {
+ left: 100%;
+ right: -90%;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 630e8f1..de1e71c 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -14,7 +14,7 @@
export interface Action { type: string; }
-export function openTrace(url: string) {
+export function openTraceFromUrl(url: string) {
return {
type: 'OPEN_TRACE',
url,
diff --git a/ui/src/frontend/home_page.ts b/ui/src/frontend/home_page.ts
index 6a975a4..5c15395 100644
--- a/ui/src/frontend/home_page.ts
+++ b/ui/src/frontend/home_page.ts
@@ -14,62 +14,14 @@
import * as m from 'mithril';
-import {
- Action,
- navigate,
- openTrace,
- openTraceFromFile
-} from '../common/actions';
-import {EngineConfig} from '../common/state';
-import {globals} from './globals';
-import {quietDispatch} from './mithril_helpers';
import {createPage} from './pages';
-const EXAMPLE_TRACE_URL =
- 'https://storage.googleapis.com/perfetto-misc/example_trace';
-
-function extractFile(e: Event): File|null {
- if (!(e.target instanceof HTMLInputElement)) {
- throw new Error('Not input element');
- }
- if (!e.target.files) return null;
- return e.target.files.item(0);
-}
-
-function loadTraceFromFile(e: Event): Action|null {
- const file = extractFile(e);
- if (!file) return null;
- return openTraceFromFile(file);
-}
-
-function renderEngine(engine: EngineConfig) {
- return m(
- '.home-page-traces-item',
- m('button',
- {
- onclick: quietDispatch(navigate(`/query/${engine.id}`)),
- },
- `Query trace ${engine.id}`));
-}
-
export const HomePage = createPage({
view() {
- const engines = Object.values(globals.state.engines);
return m(
- '#page.home-page',
+ '.page.home-page',
m('.home-page-title', 'Perfetto'),
- m('.home-page-controls',
- m('label.file-input',
- m('input[type=file]', {
- onchange: quietDispatch(loadTraceFromFile),
- }),
- 'Load trace'),
- ' or ',
- m('button',
- {
- onclick: quietDispatch(openTrace(EXAMPLE_TRACE_URL)),
- },
- 'Open example trace')),
- m('.home-page-traces', engines.map(engine => renderEngine(engine))));
+ m('img.logo[src=/assets/logo-3d.png]'),
+ );
},
});
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index 9029717..89b3da9 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -26,7 +26,6 @@
import {ControllerProxy} from './controller_proxy';
import {globals} from './globals';
import {HomePage} from './home_page';
-import {QueryPage} from './query_page';
import {ViewerPage} from './viewer_page';
function createController(): ControllerProxy {
@@ -81,26 +80,9 @@
globals.trackDataStore = new Map<string, {}>();
warmupWasmEngineWorker();
- const root = document.querySelector('main');
- if (!root) {
- console.error('root element not found.');
- return;
- }
-
- m.route(root, '/', {
+ m.route(document.body, '/', {
'/': HomePage,
'/viewer': ViewerPage,
- '/query/:engineId': {
- onmatch(args) {
- if (globals.state.engines[args.engineId]) {
- return QueryPage;
- }
- // We only hit this case if the user reloads/navigates
- // while on the query page.
- m.route.set('/');
- return undefined;
- }
- },
});
// tslint:disable-next-line no-any
diff --git a/ui/src/frontend/pages.ts b/ui/src/frontend/pages.ts
index 08deb1b..cdcc1c1 100644
--- a/ui/src/frontend/pages.ts
+++ b/ui/src/frontend/pages.ts
@@ -14,6 +14,7 @@
import * as m from 'mithril';
import {Sidebar} from './sidebar';
+import {Topbar} from './topbar';
/**
* Wrap component with common UI elements (nav bar etc).
@@ -23,7 +24,8 @@
view() {
return [
m(Sidebar),
- m('.page-content', m(component)),
+ m(Topbar),
+ m(component),
];
},
};
diff --git a/ui/src/frontend/sidebar.ts b/ui/src/frontend/sidebar.ts
index 8d584da..6f082b5 100644
--- a/ui/src/frontend/sidebar.ts
+++ b/ui/src/frontend/sidebar.ts
@@ -14,112 +14,112 @@
import * as m from 'mithril';
-export const Sidebar: m.Component<{}, {expanded: boolean[]}> = {
- oninit() {
- this.expanded = [true, false, false, false];
+import {navigate, openTraceFromFile, openTraceFromUrl} from '../common/actions';
+
+import {globals} from './globals';
+import {quietHandler} from './mithril_helpers';
+
+const EXAMPLE_TRACE_URL =
+ 'https://storage.googleapis.com/perfetto-misc/example_trace_30s';
+
+const SECTIONS = [
+ {
+ title: 'Traces',
+ summary: 'Open or record a trace',
+ expanded: true,
+ items: [
+ {t: 'Open trace file', a: popupFileSelectionDialog, i: 'folder_open'},
+ {t: 'Open example trace', a: handleOpenTraceURL, i: 'description'},
+ {t: 'Record new trace', a: navigateHome, i: 'fiber_smart_record'},
+ {t: 'Share current trace', a: navigateHome, i: 'share'},
+ ],
},
+ {
+ title: 'Workspaces',
+ summary: 'Custom and pre-arranged views',
+ items: [
+ {t: 'Big Picture', a: navigateHome, i: 'art_track'},
+ {t: 'Apps and process', a: navigateHome, i: 'apps'},
+ {t: 'Storage and I/O', a: navigateHome, i: 'storage'},
+ {t: 'Add custom...', a: navigateHome, i: 'library_add'},
+ ],
+ },
+ {
+ title: 'Tracks and views',
+ summary: 'Add new tracks to the workspace',
+ items: [
+ {t: 'User interactions', a: navigateHome, i: 'touch_app'},
+ {t: 'Device info', a: navigateHome, i: 'perm_device_information'},
+ {t: 'Scheduler trace', a: navigateHome, i: 'blur_linear'},
+ {t: 'Process list', a: navigateHome, i: 'equalizer'},
+ {t: 'Battery and power', a: navigateHome, i: 'battery_alert'},
+ ],
+ },
+ {
+ title: 'Metrics and auditors',
+ summary: 'Add new tracks to the workspace',
+ items: [
+ {t: 'CPU Usage breakdown', a: navigateHome, i: 'table_chart'},
+ {t: 'Memory breakdown', a: navigateHome, i: 'memory'},
+ ],
+ },
+];
+
+function popupFileSelectionDialog(e: Event) {
+ e.preventDefault();
+ (document.querySelector('input[type=file]')! as HTMLInputElement).click();
+}
+
+function handleOpenTraceURL(e: Event) {
+ e.preventDefault();
+ globals.dispatch(openTraceFromUrl(EXAMPLE_TRACE_URL));
+}
+
+function onInputElementFileSelectionChanged(e: Event) {
+ if (!(e.target instanceof HTMLInputElement)) {
+ throw new Error('Not an input element');
+ }
+ if (!e.target.files) return;
+ globals.dispatch(openTraceFromFile(e.target.files[0]));
+}
+
+function stopClickPropagation(e: Event) {
+ e.stopImmediatePropagation();
+}
+
+function navigateHome(_: Event) {
+ globals.dispatch(navigate('/'));
+}
+
+
+export const Sidebar: m.Component<{}, {}> = {
view() {
+ const vdomSections = [];
+ for (const section of SECTIONS) {
+ const vdomItems = [];
+ for (const item of section.items) {
+ vdomItems.push(
+ m('li',
+ m(`a[href=#]`,
+ {onclick: quietHandler(item.a)},
+ m('i.material-icons', item.i),
+ item.t)));
+ }
+ vdomSections.push(
+ m(`section${section.expanded ? '.expanded' : ''}`,
+ {onclick: () => section.expanded = !section.expanded},
+ m('h1', section.title),
+ m('h2', section.summary),
+ m('.section-content',
+ // Prevent that the clicks on the bottom part of the expanded
+ // sections propagate up to and trigger their compression.
+ m('ul', {onclick: stopClickPropagation}, vdomItems))));
+ }
return m(
'nav.sidebar',
m('header', 'Perfetto'),
- m(`section${this.expanded[0] ? '.expanded' : ''}`,
- {onclick: () => this.expanded[0] = !this.expanded[0]},
- m('h1', 'Traces'),
- m('h2', 'Open or record a trace'),
- m('.section-content',
- m('ul',
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'folder_open'),
- 'Open Trace File')),
- m('li',
- m('a[href=/viewer]',
- {oncreate: m.route.link},
- m('i.material-icons', 'art_track'),
- 'View Trace')),
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'fiber_smart_record'),
- 'Record new trace')),
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'share'),
- 'Share current trace'))))),
- m(`section${this.expanded[1] ? '.expanded' : ''}`,
- {onclick: () => this.expanded[1] = !this.expanded[1]},
- m('h1', 'Workspaces'),
- m('h2', 'Custom and pre-arranged views'),
- m('.section-content',
- m('ul',
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'art_track'),
- 'Big Picture')),
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'apps'),
- 'Apps and process')),
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'storage'),
- 'Storage and I/O')),
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'library_add'),
- 'Add custom...'))))),
- m(`section${this.expanded[2] ? '.expanded' : ''}`,
- {onclick: () => this.expanded[2] = !this.expanded[2]},
- m('h1', 'Tracks and Views'),
- m('h2', 'Add new tracks to the workspace'),
- m('.section-content',
- m('ul',
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'touch_app'),
- 'User interactions')),
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'perm_device_information'),
- 'Device info')),
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'blur_linear'),
- 'Scheduler trace')),
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'equalizer'),
- 'Process list')),
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'battery_alert'),
- 'Battern & power'))))),
- m(`section${this.expanded[3] ? '.expanded' : ''}`,
- {onclick: () => this.expanded[3] = !this.expanded[3]},
- m('h1', 'Metrics and Auditors'),
- m('h2', 'Summary analysis of the trace'),
- m('.section-content',
- m('ul',
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'table_chart'),
- 'CPU usage breakdown')),
- m('li',
- m('a[href=/]',
- {oncreate: m.route.link},
- m('i.material-icons', 'memory'),
- 'Memory breakdown'))))));
- }
+ m('input[type=file]',
+ {onchange: quietHandler(onInputElementFileSelectionChanged)}),
+ ...vdomSections);
+ },
};
\ No newline at end of file
diff --git a/ui/src/frontend/topbar.ts b/ui/src/frontend/topbar.ts
new file mode 100644
index 0000000..f8b5294
--- /dev/null
+++ b/ui/src/frontend/topbar.ts
@@ -0,0 +1,105 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import * as m from 'mithril';
+import { globals } from './globals';
+import { quietDispatch } from './mithril_helpers';
+import { navigate } from '../common/actions';
+
+let selResult = 0;
+let numResults = 0;
+let mode: 'search'|'command' = 'search';
+
+function clearOmniboxResults() {
+ // TODO(primiano): Implement in next CLs.
+}
+
+function onKeyDown(e: Event) {
+ e.stopPropagation();
+ const key = (e as KeyboardEvent).key;
+
+ // Avoid that the global 'a', 'd', 'w', 's' handler sees these keystrokes.
+ // TODO: this seems a bug in the pan_and_zoom_handler.ts.
+ if (key === 'ArrowUp' || key === 'ArrowDown') {
+ e.preventDefault();
+ return;
+ }
+ const txt =
+ (e.target as HTMLElement).querySelector('input') as HTMLInputElement;
+ if (key === ':' && txt.value === '') {
+ mode = 'command';
+ m.redraw();
+ e.preventDefault();
+ return;
+ }
+ if (key === 'Escape' && mode === 'command') {
+ txt.value = '';
+ mode = 'search';
+ m.redraw();
+ return;
+ }
+ if (key === 'Backspace' && txt.value.length === 0 && mode === 'command') {
+ mode = 'search';
+ m.redraw();
+ return;
+ }
+ // TODO(primiano): add query handling here.
+}
+
+function onKeyUp(e: Event) {
+ e.stopPropagation();
+ const key = (e as KeyboardEvent).key;
+ const txt = e.target as HTMLInputElement;
+ if (key === 'ArrowUp' || key === 'ArrowDown') {
+ selResult += (key === 'ArrowUp') ? -1 : 1;
+ selResult = Math.max(selResult, 0);
+ selResult = Math.min(selResult, numResults - 1);
+ e.preventDefault();
+ m.redraw();
+ return;
+ }
+ if (txt.value.length <= 0 || key === 'Escape') {
+ clearOmniboxResults();
+ m.redraw();
+ return;
+ }
+ // TODO(primiano): add query handling here.
+}
+
+
+const Omnibox: m.Component = {
+ oncreate(vnode) {
+ const txt = vnode.dom.querySelector('input') as HTMLInputElement;
+ txt.addEventListener('blur', clearOmniboxResults);
+ txt.addEventListener('keydown', onKeyDown);
+ txt.addEventListener('keyup', onKeyUp);
+ },
+ view() {
+ // TODO(primiano): handle query results here.
+ const placeholder = {
+ search: 'Search or type : to enter command mode',
+ command: 'e.g., select * from sched left join thread using(utid) limit 10'
+ };
+ const commandMode = mode === 'command';
+ return m(
+ `.omnibox${commandMode ? '.command-mode' : ''}`,
+ m(`input[type=text][placeholder=${placeholder[mode]}]`));
+ },
+};
+
+export const Topbar: m.Component = {
+ view() {
+ return m('.topbar', m(Omnibox));
+ },
+};
\ No newline at end of file