Skip to content

Commit 2567cc5

Browse files
author
Michael Mrowetz
committed
reorder row structure, tab mostly working
1 parent 5b643bf commit 2567cc5

8 files changed

Lines changed: 306 additions & 55 deletions

File tree

src/css-raw/perf-cascade.css

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,11 @@
149149
.info-overlay-holder .tab-nav li {margin: 0; padding: 0; display: inline-block;}
150150
.info-overlay-holder button { background: transparent; outline:0; border:0; border-bottom: solid 2px transparent; padding: 0.5em 1em; margin:0 0.25em;}
151151
.info-overlay-holder li:first-child button {margin-left: 1em;}
152-
.info-overlay-holder button:focus,.info-overlay-holder button:hover {border-color: rgba(255,255,255, 0.6);}
152+
.info-overlay-holder button:focus,
153+
.info-overlay-holder button.active:focus,
154+
.info-overlay-holder button:hover {border-color: rgba(255,255,255, 0.6);}
153155
.info-overlay-holder button.active {border-color: #fff; cursor: default;}
156+
.info-overlay-holder button.active:focus {border-color: rgba(255,255,255, 0.8);}
154157

155158
/* Info overlay HTML - content */
156159
.info-overlay-holder dt {float: left; clear: both; margin-top: 0.5em; width: 25%; text-align: right; font-weight: bold; }

src/ts/helpers/dom.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,28 @@ export function removeChildren<T extends Element>(el: T): T {
4141
}
4242
return el;
4343
}
44+
45+
/**
46+
* Get last element of `NodeList`
47+
* @param list NodeListOf e.g. return value of `getElementsByClassName`
48+
*/
49+
export function getLastItemOfNodeList<T extends Node>(list: NodeListOf<T>) {
50+
if (!list || list.length === 0) {
51+
return undefined;
52+
}
53+
return list.item(list.length - 1);
54+
}
55+
56+
/**
57+
* Helper to make `NodeListOf` iterable
58+
* @param list NodeListOf e.g. return value of `getElementsByClassName`
59+
* @param fn Function called for
60+
*/
61+
export function forEachNodeList<T extends Node>(list: NodeListOf<T>, fn: {(el: T, index: number): void}): void {
62+
if (!list || list.length === 0) {
63+
return undefined;
64+
}
65+
for (let i = 0, len = list.length; i < len; i ++) {
66+
fn(list.item(i), i);
67+
}
68+
}

src/ts/helpers/misc.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,42 @@ export function contains<T>(arr: T[], item: T): boolean {
2727
return arr.some((x) => x === item);
2828
}
2929

30+
31+
/**
32+
* Returns Index of first match to `predicate` in `arr`
33+
* @param arr Array to search
34+
* @param predicate Function that returns true for a match
35+
*/
36+
export function findIndex<T>(arr: T[], predicate: {(el: T, index: number): Boolean}) {
37+
let i = 0;
38+
if (!arr || arr.length < 1) {
39+
return undefined;
40+
}
41+
const len = arr.length;
42+
while (i < len) {
43+
if (predicate(arr[i], i)) {
44+
return i;
45+
}
46+
i++;
47+
}
48+
49+
// 7. Return undefined.
50+
return undefined;
51+
}
52+
53+
/**
54+
* Returns first match to `predicate` in `arr`
55+
* @param arr Array to search
56+
* @param predicate Function that returns true for a match
57+
*/
58+
export function find<T>(arr: T[], predicate: {(el: T, index: number): Boolean}) {
59+
const index = findIndex(arr, predicate);
60+
if (index === undefined) {
61+
return undefined;
62+
}
63+
return arr[index];
64+
}
65+
3066
/**
3167
* Formats and shortens a url for ui
3268
* @param {string} url
@@ -95,3 +131,19 @@ export function toCssClass(seed: string) {
95131
export function pluralize(word: string, count: number) {
96132
return word + (count > 1 ? "s" : "");
97133
}
134+
135+
/**
136+
* Check if event is `tab` + `shift` key, to move to previous input element
137+
* @param {KeyboardEvent} evt Keyboard event
138+
*/
139+
export function isTabUp(evt: KeyboardEvent) {
140+
return evt.which === 9 && evt.shiftKey;
141+
}
142+
143+
/**
144+
* Check if event is only `tab` key, to move to next input element
145+
* @param {KeyboardEvent} evt Keyboard event
146+
*/
147+
export function isTabDown(evt: KeyboardEvent) {
148+
return evt.which === 9 && !evt.shiftKey;
149+
}

src/ts/waterfall/details-overlay/html-details-body.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import { escapeHtml, sanitizeUrlForLink } from "../../helpers/parse";
22
import { WaterfallEntry } from "../../typing/waterfall";
33

4+
/**
5+
* Creates the HTML body for the overlay
6+
*
7+
* _All tabable elements are set to `tabindex="-1"` to avoid tabing issues_
8+
* @param requestID ID
9+
* @param detailsHeight
10+
* @param entry
11+
*/
412
export function createDetailsBody(requestID: number, detailsHeight: number, entry: WaterfallEntry) {
513

614
let html = document.createElement("html") as HTMLHtmlElement;
@@ -33,7 +41,9 @@ export function createDetailsBody(requestID: number, detailsHeight: number, entr
3341
body.innerHTML = `
3442
<div class="wrapper">
3543
<header class="type-${entry.responseDetails.requestType}">
36-
<h3><strong>#${requestID}</strong> <a href="${sanitizeUrlForLink(entry.url)}">${escapeHtml(entry.url)}</a></h3>
44+
<h3><strong>#${requestID}</strong> <a href="${sanitizeUrlForLink(entry.url)}">
45+
${escapeHtml(entry.url)}
46+
</a></h3>
3747
<nav class="tab-nav">
3848
<ul>
3949
${tabMenu}

src/ts/waterfall/details-overlay/overlay-manager.ts

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { removeChildren } from "../../helpers/dom";
1+
import { forEachNodeList, removeChildren } from "../../helpers/dom";
2+
import { find } from "../../helpers/misc";
23
import { Context, OverlayManagerClass } from "../../typing/context";
34
import { OpenOverlay, OverlayChangeEvent } from "../../typing/open-overlay";
45
import { WaterfallEntry } from "../../typing/waterfall";
@@ -9,8 +10,7 @@ class OverlayManager implements OverlayManagerClass {
910
/** Collection of currely open overlays */
1011
private openOverlays: OpenOverlay[] = [];
1112

12-
// TODO: move `overlayHolder` to constructor
13-
constructor(private context: Context, private overlayHolder: SVGGElement) {
13+
constructor(private context: Context, private rowHolder: SVGGElement) {
1414

1515
}
1616

@@ -19,8 +19,30 @@ class OverlayManager implements OverlayManagerClass {
1919
return this.openOverlays.reduce((pre, curr) => pre + curr.height, 0);
2020
}
2121

22-
public getOpenOverlays() {
23-
return this.openOverlays;
22+
/**
23+
* Get ref to Overlay SVG Element in DOM.
24+
*
25+
* _Item might be re-drawn, when another Overlay is changed_
26+
*/
27+
public getOpenOverlayDomEl(overlay: OpenOverlay) {
28+
return this.rowHolder.querySelector(`.overlay-index-${overlay.index}`) as SVGGElement;
29+
}
30+
31+
/**
32+
* Get ref to the last (DOM element wise) Overlay SVG Element in DOM.
33+
*
34+
* _Item might be re-drawn, when another Overlay is changed_
35+
*/
36+
public getLastOpenOverlayDomEl() {
37+
const overlays = this.rowHolder.querySelectorAll(".info-overlay-holder");
38+
return overlays.item(overlays.length - 1) as SVGGElement;
39+
}
40+
41+
/**
42+
* Are any overlays currently open?
43+
*/
44+
public hasOpenOverlays() {
45+
return this.openOverlays.length > 0;
2446
}
2547

2648
/**
@@ -42,6 +64,7 @@ class OverlayManager implements OverlayManagerClass {
4264
"openTabIndex": 0,
4365
};
4466
this.openOverlays.push(newOverlay);
67+
this.openOverlays = this.openOverlays.sort((a, b) => a.index > b.index ? 1 : -1);
4568

4669
this.renderOverlays(detailsHeight);
4770
this.context.pubSub.publishToOverlayChanges({
@@ -115,27 +138,47 @@ class OverlayManager implements OverlayManagerClass {
115138
* @param {SVGGElement} overlayHolder
116139
*/
117140
private renderOverlays(detailsHeight: number) {
118-
removeChildren(this.overlayHolder);
141+
// removeChildren(this.rowHolder);
119142

120143
let currY = 0;
121-
this.openOverlays
122-
.sort((a, b) => a.index > b.index ? 1 : -1)
123-
.forEach((overlay) => {
124-
let y = overlay.defaultY + currY;
125-
let infoOverlay = createRowInfoOverlay(overlay, y, detailsHeight);
126-
// if overlay has a preview image show it
127-
let previewImg = infoOverlay.querySelector("img.preview") as HTMLImageElement;
128-
if (previewImg && !previewImg.src) {
129-
previewImg.setAttribute("src", previewImg.attributes.getNamedItem("data-src").value);
144+
145+
let updateHeight = (overlay, y, currHeight) => {
146+
currY += currHeight;
147+
overlay.actualY = y;
148+
overlay.height = currHeight;
149+
};
150+
151+
let addNewOverlay = (overlayHolder: SVGGElement, overlay: OpenOverlay) => {
152+
let y = overlay.defaultY + currY;
153+
let infoOverlay = createRowInfoOverlay(overlay, y, detailsHeight);
154+
// if overlay has a preview image show it
155+
let previewImg = infoOverlay.querySelector("img.preview") as HTMLImageElement;
156+
if (previewImg && !previewImg.src) {
157+
previewImg.setAttribute("src", previewImg.attributes.getNamedItem("data-src").value);
158+
}
159+
overlayHolder.appendChild(infoOverlay);
160+
updateHeight(overlay, y, infoOverlay.getBoundingClientRect().height);
161+
};
162+
163+
const rowItems = this.rowHolder.getElementsByClassName("row-item") as NodeListOf<SVGAElement>;
164+
forEachNodeList(rowItems, (rowItem, index) => {
165+
const overlay = find(this.openOverlays, (o) => o.index === index);
166+
let overlayEl = rowItem.nextElementSibling.firstElementChild as SVGGElement;
167+
if (overlay === undefined) {
168+
if (overlayEl) {
169+
// remove closed overlay
170+
removeChildren(rowItem.nextElementSibling);
130171
}
131-
this.overlayHolder.appendChild(infoOverlay);
132-
133-
let currHeight = infoOverlay.getBoundingClientRect().height;
134-
currY += currHeight;
135-
overlay.actualY = y;
136-
overlay.height = currHeight;
137-
return overlay;
138-
});
172+
return; // not open
173+
}
174+
if (overlayEl) {
175+
updateHeight(overlay, overlay.defaultY + currY, overlay.height);
176+
const fo = overlayEl.getElementsByTagName("foreignObject").item(0) as SVGForeignObjectElement;
177+
fo.setAttribute("y", overlay.actualY.toString());
178+
return;
179+
}
180+
addNewOverlay(rowItem.nextElementSibling as SVGGElement, overlay);
181+
});
139182
}
140183
};
141184
export {

src/ts/waterfall/details-overlay/svg-details-overlay.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function createHolder(y: number, detailsHeight: number) {
4747

4848
export function createRowInfoOverlay(overlay: OpenOverlay, y: number, detailsHeight: number): SVGGElement {
4949
const requestID = overlay.index + 1;
50-
let wrapper = svg.newG("outer-info-overlay-holder");
50+
let wrapper = svg.newG(`outer-info-overlay-holder overlay-index-${overlay.index}`);
5151
let holder = createHolder(y, detailsHeight);
5252

5353
let foreignObject = svg.newForeignObject({

0 commit comments

Comments
 (0)