blob: ce0519398bb3eaf77e48f7304b50092b4fd5c7b6 [file] [log] [blame]
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as d3 from "d3";
import * as C from "../src/constants";
class Snapper {
resizer: Resizer;
sourceExpand: HTMLElement;
sourceCollapse: HTMLElement;
disassemblyExpand: HTMLElement;
disassemblyCollapse: HTMLElement;
rangesExpand: HTMLElement;
rangesCollapse: HTMLElement;
constructor(resizer: Resizer) {
this.resizer = resizer;
this.sourceExpand = document.getElementById(C.SOURCE_EXPAND_ID);
this.sourceCollapse = document.getElementById(C.SOURCE_COLLAPSE_ID);
this.disassemblyExpand = document.getElementById(C.DISASSEMBLY_EXPAND_ID);
this.disassemblyCollapse = document.getElementById(C.DISASSEMBLY_COLLAPSE_ID);
this.rangesExpand = document.getElementById(C.RANGES_EXPAND_ID);
this.rangesCollapse = document.getElementById(C.RANGES_COLLAPSE_ID);
document.getElementById("show-hide-source").addEventListener("click", () => {
this.resizer.resizerLeft.classed("snapped", !this.resizer.resizerLeft.classed("snapped"));
this.setSourceExpanded(!this.sourceExpand.classList.contains("invisible"));
this.resizer.updatePanes();
});
document.getElementById("show-hide-disassembly").addEventListener("click", () => {
this.resizer.resizerRight.classed("snapped", !this.resizer.resizerRight.classed("snapped"));
this.setDisassemblyExpanded(!this.disassemblyExpand.classList.contains("invisible"));
this.resizer.updatePanes();
});
document.getElementById("show-hide-ranges").addEventListener("click", () => {
this.resizer.resizerRanges.classed("snapped", !this.resizer.resizerRanges.classed("snapped"));
this.setRangesExpanded(!this.rangesExpand.classList.contains("invisible"));
this.resizer.updatePanes();
});
}
restoreExpandedState(): void {
this.resizer.resizerLeft.classed("snapped", window.sessionStorage.getItem("expandedState-source") == "false");
this.resizer.resizerRight.classed("snapped", window.sessionStorage.getItem("expandedState-disassembly") == "false");
this.resizer.resizerRanges.classed("snapped", window.sessionStorage.getItem("expandedState-ranges") == "false");
this.setSourceExpanded(this.getLastExpandedState("source", true));
this.setDisassemblyExpanded(this.getLastExpandedState("disassembly", true));
this.setRangesExpanded(this.getLastExpandedState("ranges", true));
}
getLastExpandedState(type: string, defaultState: boolean): boolean {
const state = window.sessionStorage.getItem("expandedState-" + type);
if (state === null) return defaultState;
return state === 'true';
}
sourceUpdate(isSourceExpanded: boolean): void {
window.sessionStorage.setItem("expandedState-source", `${isSourceExpanded}`);
this.sourceExpand.classList.toggle("invisible", isSourceExpanded);
this.sourceCollapse.classList.toggle("invisible", !isSourceExpanded);
document.getElementById("show-hide-ranges").style.marginLeft = isSourceExpanded ? null : "40px";
}
setSourceExpanded(isSourceExpanded: boolean): void {
this.sourceUpdate(isSourceExpanded);
this.resizer.updateLeftWidth();
}
disassemblyUpdate(isDisassemblyExpanded: boolean): void {
window.sessionStorage.setItem("expandedState-disassembly", `${isDisassemblyExpanded}`);
this.disassemblyExpand.classList.toggle("invisible", isDisassemblyExpanded);
this.disassemblyCollapse.classList.toggle("invisible", !isDisassemblyExpanded);
}
setDisassemblyExpanded(isDisassemblyExpanded: boolean): void {
this.disassemblyUpdate(isDisassemblyExpanded);
this.resizer.updateRightWidth();
}
rangesUpdate(isRangesExpanded: boolean): void {
window.sessionStorage.setItem("expandedState-ranges", `${isRangesExpanded}`);
this.rangesExpand.classList.toggle("invisible", isRangesExpanded);
this.rangesCollapse.classList.toggle("invisible", !isRangesExpanded);
}
setRangesExpanded(isRangesExpanded: boolean): void {
this.rangesUpdate(isRangesExpanded);
this.resizer.updateRanges();
}
}
export class Resizer {
snapper: Snapper;
deadWidth: number;
deadHeight: number;
left: HTMLElement;
right: HTMLElement;
ranges: HTMLElement;
middle: HTMLElement;
sepLeft: number;
sepRight: number;
sepRangesHeight: number;
panesUpdatedCallback: () => void;
resizerRight: d3.Selection<HTMLDivElement, any, any, any>;
resizerLeft: d3.Selection<HTMLDivElement, any, any, any>;
resizerRanges: d3.Selection<HTMLDivElement, any, any, any>;
private readonly SOURCE_PANE_DEFAULT_PERCENT = 1 / 4;
private readonly DISASSEMBLY_PANE_DEFAULT_PERCENT = 3 / 4;
private readonly RANGES_PANE_HEIGHT_DEFAULT_PERCENT = 3 / 4;
private readonly RESIZER_RANGES_HEIGHT_BUFFER_PERCENTAGE = 5;
private readonly RESIZER_SIZE = document.getElementById("resizer-ranges").offsetHeight;
constructor(panesUpdatedCallback: () => void, deadWidth: number, deadHeight: number) {
const resizer = this;
resizer.panesUpdatedCallback = panesUpdatedCallback;
resizer.deadWidth = deadWidth;
resizer.deadHeight = deadHeight;
resizer.left = document.getElementById(C.SOURCE_PANE_ID);
resizer.right = document.getElementById(C.GENERATED_PANE_ID);
resizer.ranges = document.getElementById(C.RANGES_PANE_ID);
resizer.middle = document.getElementById("middle");
resizer.resizerLeft = d3.select('#resizer-left');
resizer.resizerRight = d3.select('#resizer-right');
resizer.resizerRanges = d3.select('#resizer-ranges');
// Set default sizes, if they weren't set.
if (window.sessionStorage.getItem("source-pane-percent") === null) {
window.sessionStorage.setItem("source-pane-percent", `${this.SOURCE_PANE_DEFAULT_PERCENT}`);
}
if (window.sessionStorage.getItem("disassembly-pane-percent") === null) {
window.sessionStorage.setItem("disassembly-pane-percent", `${this.DISASSEMBLY_PANE_DEFAULT_PERCENT}`);
}
if (window.sessionStorage.getItem("ranges-pane-height-percent") === null) {
window.sessionStorage.setItem("ranges-pane-height-percent", `${this.RANGES_PANE_HEIGHT_DEFAULT_PERCENT}`);
}
this.updateSizes();
const dragResizeLeft = d3.drag()
.on('drag', function () {
const x = d3.mouse(this.parentElement)[0];
resizer.sepLeft = Math.min(Math.max(0, x), resizer.sepRight);
resizer.updatePanes();
})
.on('start', function () {
resizer.resizerLeft.classed("dragged", true);
})
.on('end', function () {
// If the panel is close enough to the left, treat it as if it was pulled all the way to the lefg.
const x = d3.mouse(this.parentElement)[0];
if (x <= deadWidth) {
resizer.sepLeft = 0;
resizer.updatePanes();
}
// Snap if dragged all the way to the left.
resizer.resizerLeft.classed("snapped", resizer.sepLeft === 0);
if (!resizer.isLeftSnapped()) {
window.sessionStorage.setItem("source-pane-percent", `${resizer.sepLeft / document.body.getBoundingClientRect().width}`);
}
resizer.snapper.setSourceExpanded(!resizer.isLeftSnapped());
resizer.resizerLeft.classed("dragged", false);
});
resizer.resizerLeft.call(dragResizeLeft);
const dragResizeRight = d3.drag()
.on('drag', function () {
const x = d3.mouse(this.parentElement)[0];
resizer.sepRight = Math.max(resizer.sepLeft, Math.min(x, document.body.getBoundingClientRect().width));
resizer.updatePanes();
})
.on('start', function () {
resizer.resizerRight.classed("dragged", true);
})
.on('end', function () {
// If the panel is close enough to the right, treat it as if it was pulled all the way to the right.
const x = d3.mouse(this.parentElement)[0];
const clientWidth = document.body.getBoundingClientRect().width;
if (x >= (clientWidth - deadWidth)) {
resizer.sepRight = clientWidth - 1;
resizer.updatePanes();
}
// Snap if dragged all the way to the right.
resizer.resizerRight.classed("snapped", resizer.sepRight >= clientWidth - 1);
if (!resizer.isRightSnapped()) {
window.sessionStorage.setItem("disassembly-pane-percent", `${resizer.sepRight / clientWidth}`);
}
resizer.snapper.setDisassemblyExpanded(!resizer.isRightSnapped());
resizer.resizerRight.classed("dragged", false);
});
resizer.resizerRight.call(dragResizeRight);
const dragResizeRanges = d3.drag()
.on('drag', function () {
const y = d3.mouse(this.parentElement)[1];
resizer.sepRangesHeight = Math.max(100, Math.min(y, window.innerHeight) - resizer.RESIZER_RANGES_HEIGHT_BUFFER_PERCENTAGE);
resizer.updatePanes();
})
.on('start', function () {
resizer.resizerRanges.classed("dragged", true);
})
.on('end', function () {
// If the panel is close enough to the bottom, treat it as if it was pulled all the way to the bottom.
const y = d3.mouse(this.parentElement)[1];
if (y >= (window.innerHeight - deadHeight)) {
resizer.sepRangesHeight = window.innerHeight;
resizer.updatePanes();
}
// Snap if dragged all the way to the bottom.
resizer.resizerRanges.classed("snapped", resizer.sepRangesHeight >= window.innerHeight - 1);
if (!resizer.isRangesSnapped()) {
window.sessionStorage.setItem("ranges-pane-height-percent", `${resizer.sepRangesHeight / window.innerHeight}`);
}
resizer.snapper.setRangesExpanded(!resizer.isRangesSnapped());
resizer.resizerRanges.classed("dragged", false);
});
resizer.resizerRanges.call(dragResizeRanges);
window.onresize = function () {
resizer.updateSizes();
resizer.updatePanes();
};
resizer.snapper = new Snapper(resizer);
resizer.snapper.restoreExpandedState();
}
isLeftSnapped() {
return this.resizerLeft.classed("snapped");
}
isRightSnapped() {
return this.resizerRight.classed("snapped");
}
isRangesSnapped() {
return this.resizerRanges.classed("snapped");
}
updateRangesPane() {
const clientHeight = window.innerHeight;
const rangesIsHidden = this.ranges.style.visibility == "hidden";
let resizerSize = this.RESIZER_SIZE;
if (rangesIsHidden) {
resizerSize = 0;
this.sepRangesHeight = clientHeight;
}
const rangeHeight = clientHeight - this.sepRangesHeight;
this.ranges.style.height = rangeHeight + 'px';
const panelWidth = this.sepRight - this.sepLeft - (2 * resizerSize);
this.ranges.style.width = panelWidth + 'px';
const multiview = document.getElementById("multiview");
if (multiview && multiview.style) {
multiview.style.height = (this.sepRangesHeight - resizerSize) + 'px';
multiview.style.width = panelWidth + 'px';
}
// Resize the range grid and labels.
const rangeGrid = (this.ranges.getElementsByClassName("range-grid")[0] as HTMLElement);
if (rangeGrid) {
const yAxis = (this.ranges.getElementsByClassName("range-y-axis")[0] as HTMLElement);
const rangeHeader = (this.ranges.getElementsByClassName("range-header")[0] as HTMLElement);
const gridWidth = panelWidth - yAxis.clientWidth;
rangeGrid.style.width = Math.floor(gridWidth - 1) + 'px';
// Take live ranges' right scrollbar into account.
rangeHeader.style.width = (gridWidth - rangeGrid.offsetWidth + rangeGrid.clientWidth - 1) + 'px';
// Set resizer to horizontal.
this.resizerRanges.style('width', panelWidth + 'px');
const rangeTitle = (this.ranges.getElementsByClassName("range-title-div")[0] as HTMLElement);
const rangeHeaderLabel = (this.ranges.getElementsByClassName("range-header-label-x")[0] as HTMLElement);
const gridHeight = rangeHeight - rangeHeader.clientHeight - rangeTitle.clientHeight - rangeHeaderLabel.clientHeight;
rangeGrid.style.height = gridHeight + 'px';
// Take live ranges' bottom scrollbar into account.
yAxis.style.height = (gridHeight - rangeGrid.offsetHeight + rangeGrid.clientHeight) + 'px';
}
this.resizerRanges.style('ranges', this.ranges.style.height);
}
updatePanes() {
this.left.style.width = this.sepLeft + 'px';
this.resizerLeft.style('left', this.sepLeft + 'px');
this.right.style.width = (document.body.getBoundingClientRect().width - this.sepRight) + 'px';
this.resizerRight.style('right', (document.body.getBoundingClientRect().width - this.sepRight - 1) + 'px');
this.updateRangesPane();
this.panesUpdatedCallback();
}
updateRanges() {
if (this.isRangesSnapped()) {
this.sepRangesHeight = window.innerHeight;
} else {
const sepRangesHeight = window.sessionStorage.getItem("ranges-pane-height-percent");
this.sepRangesHeight = window.innerHeight * Number.parseFloat(sepRangesHeight);
}
}
updateLeftWidth() {
if (this.isLeftSnapped()) {
this.sepLeft = 0;
} else {
const sepLeft = window.sessionStorage.getItem("source-pane-percent");
this.sepLeft = document.body.getBoundingClientRect().width * Number.parseFloat(sepLeft);
}
}
updateRightWidth() {
if (this.isRightSnapped()) {
this.sepRight = document.body.getBoundingClientRect().width;
} else {
const sepRight = window.sessionStorage.getItem("disassembly-pane-percent");
this.sepRight = document.body.getBoundingClientRect().width * Number.parseFloat(sepRight);
}
}
updateSizes() {
this.updateLeftWidth();
this.updateRightWidth();
this.updateRanges();
}
}