Skip to content

Commit 77a5e47

Browse files
author
Bastien Abadie
committed
frontend: Store input states in url, fixes #101.
1 parent 3483a72 commit 77a5e47

File tree

6 files changed

+187
-118
lines changed

6 files changed

+187
-118
lines changed

frontend/src/base.html

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<h2>
1111
<nav>
1212
{{#navbar}}
13-
<a href="#{{ path }}">{{ name }}</a>
13+
<a href="{{ route }}">{{ name }}</a>
1414
{{/navbar}}
1515
</nav>
1616
<span> : {{ total }} files</span>
@@ -47,7 +47,7 @@ <h2>
4747
<h2>
4848
<nav>
4949
{{#navbar}}
50-
<a href="#{{ revision }}:{{ path }}">{{ name }}</a>
50+
<a href="{{ route }}">{{ name }}</a>
5151
{{/navbar}}
5252
</nav>
5353
</h2>
@@ -73,7 +73,7 @@ <h2>
7373
<h2>
7474
<nav>
7575
{{#navbar}}
76-
<a href="#{{ revision }}:{{ path }}">{{ name }}</a>
76+
<a href="{{ route }}">{{ name }}</a>
7777
{{/navbar}}
7878
</nav>
7979
<span> : {{ files.length }} files</span>
@@ -88,7 +88,7 @@ <h2>
8888

8989
{{#files}}
9090
<div class="row">
91-
<span class="filename"><a href="#{{ revision }}:{{ path }}">{{ file_name }}</a></span>
91+
<span class="filename"><a href="{{ route }}">{{ file_name }}</a></span>
9292
<span>{{ children }}</span>
9393
<span>{{ coveragePercent }} %</span>
9494
</div>
@@ -102,29 +102,26 @@ <h2>
102102
<span>Revision <samp>{{ revision }}</samp> from {{ date }}</span>
103103
</script>
104104

105-
<header>
106-
<div id="menu_browser">
107-
<a href="#zero:">View the zero coverage report</a>
108-
&bull;
109-
<input type="text" id="revision" placeholder="Mercurial revision" </input>
110-
</div>
105+
<script id="menu_browser" type="x-tmpl-mustache">
106+
<a href="#view=zero&third_party=on&cpp=on&js=on=java=on&rust=on">View the zero coverage report</a>
107+
&bull;
108+
<input type="text" name="revision" placeholder="Mercurial revision" value="{{revision}}"></input>
109+
</script>
111110

112-
<div id="menu_zero">
113-
<input type="checkbox" name="third_party" id="third_party" checked="checked"><label for="third_party">Show third-party files</label>
114-
<input type="checkbox" name="headers" id="headers"><label for="headers">Show headers</label>
115-
<input type="checkbox" name="completely_uncovered" id="completely_uncovered"><label for="completely_uncovered">Show completely uncovered files only</label>
116-
<input type="checkbox" name="cpp" id="cpp" checked="checked"><label for="cpp">C/C++</label>
117-
<input type="checkbox" name="js" id="js" checked="checked"><label for="js">JavaScript</label>
118-
<input type="checkbox" name="java" id="java" checked="checked"><label for="java">Java</label>
119-
<input type="checkbox" name="rust" id="rust" checked="checked"><label for="rust">Rust</label>
120-
<select id="last_push">
121-
<option value="all">All</option>
122-
<option value="one_year">0 &lt; 1 year</option>
123-
<option value="two_years">1 &lt; 2 years</option>
124-
<option value="older_than_two_years">Older than 2 years</option>
125-
</select>
126-
</div>
127-
</header>
111+
<script id="menu_zero" type="x-tmpl-mustache">
112+
{{#filters}}
113+
<input type="checkbox" name="{{ key }}" id="{{ key }}" {{#checked}}checked="checked"{{/checked}}>
114+
<label for="{{ key }}">{{ message }}</label>
115+
{{/filters}}
116+
117+
<select name="last_push" id="last_push">
118+
{{#last_pushes}}
119+
<option {{#selected}}selected="selected"{{/selected}} value="{{value}}">{{message}}</option>
120+
{{/last_pushes}}
121+
</select>
122+
</script>
123+
124+
<header id="menu"></header>
128125

129126
<main id="main">
130127
<div id="message" class="message loading">Loading...</div>

frontend/src/common.js

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,34 @@
11
import Mustache from 'mustache';
2+
import { buildRoute, readRoute, updateRoute } from './route.js';
23

34
export const REV_LATEST = 'latest';
45

5-
function assert(condition, message) {
6-
if (!condition) {
7-
throw new Error(message || "Assertion failed");
8-
}
9-
}
10-
116
function domContentLoaded() {
127
return new Promise(resolve => document.addEventListener('DOMContentLoaded', resolve));
138
}
149
export const DOM_READY = domContentLoaded();
1510

16-
export async function main(load, display, opts) {
17-
// Immediately listen to DOM event
18-
11+
export async function main(load, display) {
1912
// Load initial data before DOM is available
2013
let data = await load();
2114

2215
// Wait for DOM to be ready before displaying
2316
await DOM_READY;
2417
await display(data);
18+
monitor_options();
2519

2620
// Full workflow, loading then displaying data
2721
// used for following updates
2822
let full = async function() {
2923
let data = await load();
3024
await display(data);
25+
monitor_options();
3126
};
32-
monitor_options(opts, full);
27+
28+
// React to url changes
3329
window.onhashchange = full;
3430
}
3531

36-
3732
// Coverage retrieval.
3833

3934
const COVERAGE_BACKEND_HOST = process.env.BACKEND_URL;
@@ -134,18 +129,38 @@ export async function get_zero_coverage_data() {
134129
// Option handling.
135130

136131
function is_enabled(opt) {
137-
let elem = document.getElementById(opt);
138-
return elem.checked;
132+
let route = readRoute();
133+
return route[opt] === 'on';
139134
}
140135

141-
function monitor_options(opts, callback) {
142-
for (let opt of opts) {
143-
let elem = document.getElementById(opt);
144-
elem.onchange = callback;
136+
function monitor_options() {
137+
// Monitor input & select changes
138+
let fields = document.querySelectorAll('input, select');
139+
for(let field of fields) {
140+
if (field.type == 'text') {
141+
// React on enter
142+
field.onkeydown = async (evt) => {
143+
if(evt.keyCode === 13) {
144+
let params = {};
145+
params[evt.target.name] = evt.target.value;
146+
updateRoute(params);
147+
}
148+
}
149+
} else {
150+
// React on change
151+
field.onchange = async (evt) => {
152+
let value = evt.target.value;
153+
if (evt.target.type == 'checkbox') {
154+
value = evt.target.checked ? 'on' : 'off';
155+
}
156+
let params = {};
157+
params[evt.target.name] = value;
158+
updateRoute(params);
159+
}
160+
}
145161
}
146162
}
147163

148-
149164
// hgmo.
150165

151166
export async function get_source(file) {
@@ -267,14 +282,14 @@ export function build_navbar(path, revision) {
267282
let links = [
268283
{
269284
'name': 'mozilla-central',
270-
'path': '',
285+
'route': buildRoute({path: '', revision})
271286
}
272287
];
273288
return links.concat(path.split('/').map(file => {
274289
base += (base ? '/' : '') + file;
275290
return {
276291
'name': file,
277-
'path': base,
292+
'route': buildRoute({path: base, revision})
278293
};
279294
}));
280295
}

frontend/src/index.js

Lines changed: 43 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {REV_LATEST, DOM_READY, main, show, hide, message, get_path_coverage, get_history, get_zero_coverage_data, build_navbar, render, get_source} from './common.js';
2-
import {zero_coverage_display} from './zero_coverage_report.js';
2+
import {buildRoute, readRoute, updateRoute} from './route.js';
3+
import {zero_coverage_display, zero_coverage_menu} from './zero_coverage_report.js';
34
import './style.css';
45
import Prism from 'prismjs';
56
import Chartist from 'chartist';
@@ -8,6 +9,14 @@ import 'chartist/dist/chartist.css';
89
const VIEW_ZERO_COVERAGE = 'zero';
910
const VIEW_BROWSER = 'browser';
1011

12+
13+
function browser_menu(revision) {
14+
let context = {
15+
revision,
16+
};
17+
render('menu_browser', context, 'menu');
18+
}
19+
1120
async function graphHistory(history, path) {
1221
if (history === null) {
1322
message('warning', `No history data for ${path}`);
@@ -53,7 +62,7 @@ async function graphHistory(history, path) {
5362
// Load revision from graph when a point is clicked
5463
let revision = history[evt.index].changeset;
5564
evt.element._node.onclick = function(){
56-
updateHash(revision, path);
65+
updateRoute({revision});
5766
};
5867

5968
// Display revision from graph when a point is overed
@@ -71,7 +80,12 @@ async function graphHistory(history, path) {
7180
async function showDirectory(dir, revision, files) {
7281
let context = {
7382
navbar: build_navbar(dir, revision),
74-
files: files,
83+
files: files.map(file => {
84+
file.route = buildRoute({
85+
path: file.path
86+
});
87+
return file;
88+
}),
7589
revision: revision || REV_LATEST,
7690
file_name: function(){
7791
// Build filename relative to current dir
@@ -125,49 +139,29 @@ async function showFile(file, revision) {
125139
Prism.highlightAll(output);
126140
}
127141

128-
function readHash() {
129-
// Reads changeset & path from current URL hash
130-
let hash = window.location.hash.substring(1);
131-
let pos = hash.indexOf(':');
132-
if (pos === -1) {
133-
return ['', ''];
134-
}
135-
return [
136-
hash.substring(0, pos),
137-
hash.substring(pos+1),
138-
]
139-
}
140-
141-
function updateHash(newChangeset, newPath) {
142-
// Set the URL hash with both changeset & path
143-
let [changeset, path] = readHash();
144-
changeset = newChangeset || changeset || REV_LATEST;
145-
path = newPath || path || '';
146-
window.location.hash = '#' + changeset + ':' + path;
147-
}
148-
149142
async function load() {
150-
let [revision, path] = readHash();
143+
let route = readRoute();
151144

152145
// Reset display, dom-safe
153146
hide('history');
154147
hide('output');
155-
message('loading', 'Loading coverage data for ' + (path || 'mozilla-central') + ' @ ' + (revision || REV_LATEST));
148+
message('loading', 'Loading coverage data for ' + (route.path || 'mozilla-central') + ' @ ' + (route.revision || REV_LATEST));
156149

157150
// Load only zero coverage for that specific view
158-
if (revision === VIEW_ZERO_COVERAGE) {
151+
if (route.view === VIEW_ZERO_COVERAGE) {
159152
let zero_coverage = await get_zero_coverage_data();
160153
return {
161154
view: VIEW_ZERO_COVERAGE,
162-
path,
155+
path: route.path,
163156
zero_coverage,
157+
route,
164158
}
165159
}
166160

167161
try {
168162
var [coverage, history] = await Promise.all([
169-
get_path_coverage(path, revision),
170-
get_history(path),
163+
get_path_coverage(route.path, route.revision),
164+
get_history(route.path),
171165
]);
172166
} catch (err) {
173167
console.warn('Failed to load coverage', err);
@@ -178,54 +172,38 @@ async function load() {
178172

179173
return {
180174
view: VIEW_BROWSER,
181-
path,
182-
revision,
175+
path: route.path,
176+
revision: route.revision,
177+
route,
183178
coverage,
184179
history,
185180
};
186181
}
187182

188183
async function display(data) {
189184

190-
// Toggle menu per views
191-
if (data.view === VIEW_BROWSER) {
192-
show('menu_browser');
193-
hide('menu_zero');
194-
} else if (data.view === VIEW_ZERO_COVERAGE) {
195-
show('menu_zero');
196-
hide('menu_browser');
197-
} else {
198-
message('error', 'Invalid view : ' + data.view);
199-
}
200-
201-
// Revision input management
202-
const revision = document.getElementById('revision');
203-
revision.onkeydown = async function(evt){
204-
if(evt.keyCode === 13) {
205-
updateHash(data.revision.value);
206-
}
207-
};
208-
209-
// Also update the revision element
210-
if (data.revision && data.revision != REV_LATEST) {
211-
let input = document.getElementById('revision');
212-
input.value = data.revision;
213-
}
214-
215185
if (data.view === VIEW_ZERO_COVERAGE ) {
186+
await zero_coverage_menu(data.route);
216187
await zero_coverage_display(data.zero_coverage, data.path);
217188

218-
} else if (data.view === VIEW_BROWSER && data.coverage.type === 'directory') {
219-
hide('message');
220-
await graphHistory(data.history, data.path);
221-
await showDirectory(data.path, data.revision, data.coverage.children);
189+
} else if (data.view === VIEW_BROWSER) {
190+
browser_menu(data.revision);
191+
192+
if (data.coverage.type === 'directory') {
193+
hide('message');
194+
await graphHistory(data.history, data.path);
195+
await showDirectory(data.path, data.revision, data.coverage.children);
222196

223-
} else if (data.view === VIEW_BROWSER && data.coverage.type === 'file') {
224-
await showFile(data.coverage, data.revision);
197+
} else if (data.coverage.type === 'file') {
198+
await showFile(data.coverage, data.revision);
199+
200+
} else {
201+
message('error', 'Invalid file type: ' + data.coverate.type);
202+
}
225203

226204
} else {
227-
message('error', 'Invalid file type: ' + data.coverage.type);
205+
message('error', 'Invalid view : ' + data.view);
228206
}
229207
}
230208

231-
main(load, display, ['third_party', 'headers', 'completely_uncovered', 'cpp', 'js', 'java', 'rust', 'last_push'])
209+
main(load, display);

0 commit comments

Comments
 (0)