• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import * as d3 from "d3";
6import * as C from "../src/constants";
7
8class Snapper {
9  resizer: Resizer;
10  sourceExpand: HTMLElement;
11  sourceCollapse: HTMLElement;
12  disassemblyExpand: HTMLElement;
13  disassemblyCollapse: HTMLElement;
14  rangesExpand: HTMLElement;
15  rangesCollapse: HTMLElement;
16
17  constructor(resizer: Resizer) {
18    this.resizer = resizer;
19    this.sourceExpand = document.getElementById(C.SOURCE_EXPAND_ID);
20    this.sourceCollapse = document.getElementById(C.SOURCE_COLLAPSE_ID);
21    this.disassemblyExpand = document.getElementById(C.DISASSEMBLY_EXPAND_ID);
22    this.disassemblyCollapse = document.getElementById(C.DISASSEMBLY_COLLAPSE_ID);
23    this.rangesExpand = document.getElementById(C.RANGES_EXPAND_ID);
24    this.rangesCollapse = document.getElementById(C.RANGES_COLLAPSE_ID);
25
26    document.getElementById("show-hide-source").addEventListener("click", () => {
27      this.resizer.resizerLeft.classed("snapped", !this.resizer.resizerLeft.classed("snapped"));
28      this.setSourceExpanded(!this.sourceExpand.classList.contains("invisible"));
29      this.resizer.updatePanes();
30    });
31    document.getElementById("show-hide-disassembly").addEventListener("click", () => {
32      this.resizer.resizerRight.classed("snapped", !this.resizer.resizerRight.classed("snapped"));
33      this.setDisassemblyExpanded(!this.disassemblyExpand.classList.contains("invisible"));
34      this.resizer.updatePanes();
35    });
36    document.getElementById("show-hide-ranges").addEventListener("click", () => {
37      this.resizer.resizerRanges.classed("snapped", !this.resizer.resizerRanges.classed("snapped"));
38      this.setRangesExpanded(!this.rangesExpand.classList.contains("invisible"));
39      this.resizer.updatePanes();
40    });
41  }
42
43  restoreExpandedState(): void {
44    this.resizer.resizerLeft.classed("snapped", window.sessionStorage.getItem("expandedState-source") == "false");
45    this.resizer.resizerRight.classed("snapped", window.sessionStorage.getItem("expandedState-disassembly") == "false");
46    this.resizer.resizerRanges.classed("snapped", window.sessionStorage.getItem("expandedState-ranges") == "false");
47    this.setSourceExpanded(this.getLastExpandedState("source", true));
48    this.setDisassemblyExpanded(this.getLastExpandedState("disassembly", true));
49    this.setRangesExpanded(this.getLastExpandedState("ranges", true));
50  }
51
52  getLastExpandedState(type: string, defaultState: boolean): boolean {
53    const state = window.sessionStorage.getItem("expandedState-" + type);
54    if (state === null) return defaultState;
55    return state === 'true';
56  }
57
58  sourceUpdate(isSourceExpanded: boolean): void {
59    window.sessionStorage.setItem("expandedState-source", `${isSourceExpanded}`);
60    this.sourceExpand.classList.toggle("invisible", isSourceExpanded);
61    this.sourceCollapse.classList.toggle("invisible", !isSourceExpanded);
62    document.getElementById("show-hide-ranges").style.marginLeft = isSourceExpanded ? null : "40px";
63  }
64
65  setSourceExpanded(isSourceExpanded: boolean): void {
66    this.sourceUpdate(isSourceExpanded);
67    this.resizer.updateLeftWidth();
68  }
69
70  disassemblyUpdate(isDisassemblyExpanded: boolean): void {
71    window.sessionStorage.setItem("expandedState-disassembly", `${isDisassemblyExpanded}`);
72    this.disassemblyExpand.classList.toggle("invisible", isDisassemblyExpanded);
73    this.disassemblyCollapse.classList.toggle("invisible", !isDisassemblyExpanded);
74  }
75
76  setDisassemblyExpanded(isDisassemblyExpanded: boolean): void {
77    this.disassemblyUpdate(isDisassemblyExpanded);
78    this.resizer.updateRightWidth();
79  }
80
81  rangesUpdate(isRangesExpanded: boolean): void {
82    window.sessionStorage.setItem("expandedState-ranges", `${isRangesExpanded}`);
83    this.rangesExpand.classList.toggle("invisible", isRangesExpanded);
84    this.rangesCollapse.classList.toggle("invisible", !isRangesExpanded);
85  }
86
87  setRangesExpanded(isRangesExpanded: boolean): void {
88    this.rangesUpdate(isRangesExpanded);
89    this.resizer.updateRanges();
90  }
91}
92
93export class Resizer {
94  snapper: Snapper;
95  deadWidth: number;
96  deadHeight: number;
97  left: HTMLElement;
98  right: HTMLElement;
99  ranges: HTMLElement;
100  middle: HTMLElement;
101  sepLeft: number;
102  sepRight: number;
103  sepRangesHeight: number;
104  panesUpdatedCallback: () => void;
105  resizerRight: d3.Selection<HTMLDivElement, any, any, any>;
106  resizerLeft: d3.Selection<HTMLDivElement, any, any, any>;
107  resizerRanges: d3.Selection<HTMLDivElement, any, any, any>;
108
109  private readonly SOURCE_PANE_DEFAULT_PERCENT = 1 / 4;
110  private readonly DISASSEMBLY_PANE_DEFAULT_PERCENT = 3 / 4;
111  private readonly RANGES_PANE_HEIGHT_DEFAULT_PERCENT = 3 / 4;
112  private readonly RESIZER_RANGES_HEIGHT_BUFFER_PERCENTAGE = 5;
113  private readonly RESIZER_SIZE = document.getElementById("resizer-ranges").offsetHeight;
114
115  constructor(panesUpdatedCallback: () => void, deadWidth: number, deadHeight: number) {
116    const resizer = this;
117    resizer.panesUpdatedCallback = panesUpdatedCallback;
118    resizer.deadWidth = deadWidth;
119    resizer.deadHeight = deadHeight;
120    resizer.left = document.getElementById(C.SOURCE_PANE_ID);
121    resizer.right = document.getElementById(C.GENERATED_PANE_ID);
122    resizer.ranges = document.getElementById(C.RANGES_PANE_ID);
123    resizer.middle = document.getElementById("middle");
124    resizer.resizerLeft = d3.select('#resizer-left');
125    resizer.resizerRight = d3.select('#resizer-right');
126    resizer.resizerRanges = d3.select('#resizer-ranges');
127    // Set default sizes, if they weren't set.
128    if (window.sessionStorage.getItem("source-pane-percent") === null) {
129      window.sessionStorage.setItem("source-pane-percent", `${this.SOURCE_PANE_DEFAULT_PERCENT}`);
130    }
131    if (window.sessionStorage.getItem("disassembly-pane-percent") === null) {
132      window.sessionStorage.setItem("disassembly-pane-percent", `${this.DISASSEMBLY_PANE_DEFAULT_PERCENT}`);
133    }
134    if (window.sessionStorage.getItem("ranges-pane-height-percent") === null) {
135      window.sessionStorage.setItem("ranges-pane-height-percent", `${this.RANGES_PANE_HEIGHT_DEFAULT_PERCENT}`);
136    }
137
138    this.updateSizes();
139
140    const dragResizeLeft = d3.drag()
141      .on('drag', function () {
142        const x = d3.mouse(this.parentElement)[0];
143        resizer.sepLeft = Math.min(Math.max(0, x), resizer.sepRight);
144        resizer.updatePanes();
145      })
146      .on('start', function () {
147        resizer.resizerLeft.classed("dragged", true);
148      })
149      .on('end', function () {
150        // If the panel is close enough to the left, treat it as if it was pulled all the way to the lefg.
151        const x = d3.mouse(this.parentElement)[0];
152        if (x <= deadWidth) {
153          resizer.sepLeft = 0;
154          resizer.updatePanes();
155        }
156        // Snap if dragged all the way to the left.
157        resizer.resizerLeft.classed("snapped", resizer.sepLeft === 0);
158        if (!resizer.isLeftSnapped()) {
159          window.sessionStorage.setItem("source-pane-percent", `${resizer.sepLeft / document.body.getBoundingClientRect().width}`);
160        }
161        resizer.snapper.setSourceExpanded(!resizer.isLeftSnapped());
162        resizer.resizerLeft.classed("dragged", false);
163      });
164    resizer.resizerLeft.call(dragResizeLeft);
165
166    const dragResizeRight = d3.drag()
167      .on('drag', function () {
168        const x = d3.mouse(this.parentElement)[0];
169        resizer.sepRight = Math.max(resizer.sepLeft, Math.min(x, document.body.getBoundingClientRect().width));
170        resizer.updatePanes();
171      })
172      .on('start', function () {
173        resizer.resizerRight.classed("dragged", true);
174      })
175      .on('end', function () {
176        // If the panel is close enough to the right, treat it as if it was pulled all the way to the right.
177        const x = d3.mouse(this.parentElement)[0];
178        const clientWidth = document.body.getBoundingClientRect().width;
179        if (x >= (clientWidth - deadWidth)) {
180          resizer.sepRight = clientWidth - 1;
181          resizer.updatePanes();
182        }
183        // Snap if dragged all the way to the right.
184        resizer.resizerRight.classed("snapped", resizer.sepRight >= clientWidth - 1);
185        if (!resizer.isRightSnapped()) {
186          window.sessionStorage.setItem("disassembly-pane-percent", `${resizer.sepRight / clientWidth}`);
187        }
188        resizer.snapper.setDisassemblyExpanded(!resizer.isRightSnapped());
189        resizer.resizerRight.classed("dragged", false);
190      });
191    resizer.resizerRight.call(dragResizeRight);
192
193    const dragResizeRanges = d3.drag()
194      .on('drag', function () {
195        const y = d3.mouse(this.parentElement)[1];
196        resizer.sepRangesHeight = Math.max(100, Math.min(y, window.innerHeight) - resizer.RESIZER_RANGES_HEIGHT_BUFFER_PERCENTAGE);
197        resizer.updatePanes();
198      })
199      .on('start', function () {
200        resizer.resizerRanges.classed("dragged", true);
201      })
202      .on('end', function () {
203        // If the panel is close enough to the bottom, treat it as if it was pulled all the way to the bottom.
204        const y = d3.mouse(this.parentElement)[1];
205        if (y >= (window.innerHeight - deadHeight)) {
206          resizer.sepRangesHeight = window.innerHeight;
207          resizer.updatePanes();
208        }
209        // Snap if dragged all the way to the bottom.
210        resizer.resizerRanges.classed("snapped", resizer.sepRangesHeight >= window.innerHeight - 1);
211        if (!resizer.isRangesSnapped()) {
212          window.sessionStorage.setItem("ranges-pane-height-percent", `${resizer.sepRangesHeight / window.innerHeight}`);
213        }
214        resizer.snapper.setRangesExpanded(!resizer.isRangesSnapped());
215        resizer.resizerRanges.classed("dragged", false);
216      });
217    resizer.resizerRanges.call(dragResizeRanges);
218
219    window.onresize = function () {
220      resizer.updateSizes();
221      resizer.updatePanes();
222    };
223    resizer.snapper = new Snapper(resizer);
224    resizer.snapper.restoreExpandedState();
225  }
226
227  isLeftSnapped() {
228    return this.resizerLeft.classed("snapped");
229  }
230
231  isRightSnapped() {
232    return this.resizerRight.classed("snapped");
233  }
234
235  isRangesSnapped() {
236    return this.resizerRanges.classed("snapped");
237  }
238
239  updateRangesPane() {
240    const clientHeight = window.innerHeight;
241    const rangesIsHidden = this.ranges.style.visibility == "hidden";
242    let resizerSize = this.RESIZER_SIZE;
243    if (rangesIsHidden) {
244      resizerSize = 0;
245      this.sepRangesHeight = clientHeight;
246    }
247
248    const rangeHeight = clientHeight - this.sepRangesHeight;
249    this.ranges.style.height = rangeHeight + 'px';
250    const panelWidth = this.sepRight - this.sepLeft - (2 * resizerSize);
251    this.ranges.style.width = panelWidth + 'px';
252    const multiview = document.getElementById("multiview");
253    if (multiview && multiview.style) {
254        multiview.style.height = (this.sepRangesHeight - resizerSize) + 'px';
255        multiview.style.width = panelWidth + 'px';
256    }
257
258    // Resize the range grid and labels.
259    const rangeGrid = (this.ranges.getElementsByClassName("range-grid")[0] as HTMLElement);
260    if (rangeGrid) {
261      const yAxis = (this.ranges.getElementsByClassName("range-y-axis")[0] as HTMLElement);
262      const rangeHeader = (this.ranges.getElementsByClassName("range-header")[0] as HTMLElement);
263
264      const gridWidth = panelWidth - yAxis.clientWidth;
265      rangeGrid.style.width = Math.floor(gridWidth - 1) + 'px';
266      // Take live ranges' right scrollbar into account.
267      rangeHeader.style.width = (gridWidth - rangeGrid.offsetWidth + rangeGrid.clientWidth - 1) + 'px';
268      // Set resizer to horizontal.
269      this.resizerRanges.style('width', panelWidth + 'px');
270
271      const rangeTitle = (this.ranges.getElementsByClassName("range-title-div")[0] as HTMLElement);
272      const rangeHeaderLabel = (this.ranges.getElementsByClassName("range-header-label-x")[0] as HTMLElement);
273      const gridHeight = rangeHeight - rangeHeader.clientHeight - rangeTitle.clientHeight - rangeHeaderLabel.clientHeight;
274      rangeGrid.style.height = gridHeight + 'px';
275      // Take live ranges' bottom scrollbar into account.
276      yAxis.style.height = (gridHeight - rangeGrid.offsetHeight + rangeGrid.clientHeight) + 'px';
277    }
278    this.resizerRanges.style('ranges', this.ranges.style.height);
279  }
280
281  updatePanes() {
282    this.left.style.width = this.sepLeft + 'px';
283    this.resizerLeft.style('left', this.sepLeft + 'px');
284    this.right.style.width = (document.body.getBoundingClientRect().width - this.sepRight) + 'px';
285    this.resizerRight.style('right', (document.body.getBoundingClientRect().width - this.sepRight - 1) + 'px');
286    this.updateRangesPane();
287    this.panesUpdatedCallback();
288  }
289
290  updateRanges() {
291    if (this.isRangesSnapped()) {
292      this.sepRangesHeight = window.innerHeight;
293    } else {
294      const sepRangesHeight = window.sessionStorage.getItem("ranges-pane-height-percent");
295      this.sepRangesHeight = window.innerHeight * Number.parseFloat(sepRangesHeight);
296    }
297  }
298
299  updateLeftWidth() {
300    if (this.isLeftSnapped()) {
301      this.sepLeft = 0;
302    } else {
303      const sepLeft = window.sessionStorage.getItem("source-pane-percent");
304      this.sepLeft = document.body.getBoundingClientRect().width * Number.parseFloat(sepLeft);
305    }
306  }
307
308  updateRightWidth() {
309    if (this.isRightSnapped()) {
310      this.sepRight = document.body.getBoundingClientRect().width;
311    } else {
312      const sepRight = window.sessionStorage.getItem("disassembly-pane-percent");
313      this.sepRight = document.body.getBoundingClientRect().width * Number.parseFloat(sepRight);
314    }
315  }
316
317  updateSizes() {
318    this.updateLeftWidth();
319    this.updateRightWidth();
320    this.updateRanges();
321  }
322}
323