// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. /** * An enum of types of actions that might be requested * by the app. */ enum Action { TOGGLE_SIDEBAR, // Toggle the sidebar. NAVIGATE_ABOUT, // Go to the about page. } const TITLE = 'Go Heap Viewer'; /** * A type of event that signals to the AppElement controller * that something shoud be done. For the most part, the structure * of the app will be that elements' state will mostly be controlled * by parent elements. Elements will issue actions that the AppElement * will handle, and the app will be re-rendered down the DOM * hierarchy. */ class ActionEvent extends Event { static readonly EVENT_TYPE = 'action-event' constructor(public readonly action: Action) { super(ActionEvent.EVENT_TYPE); } } /** * A hamburger menu element. Triggers a TOGGLE_SIDE action to toggle the * sidebar. */ export class HamburgerElement extends HTMLElement { static readonly NAME = 'heap-hamburger'; createdCallback() { this.appendChild(document.createTextNode('☰')); this.onclick = () => { this.dispatchEvent(new ActionEvent(Action.TOGGLE_SIDEBAR)) }; } } document.registerElement(HamburgerElement.NAME, HamburgerElement); /** * A heading for the page with a hamburger menu and a title. */ export class HeadingElement extends HTMLElement { static readonly NAME = 'heap-heading'; createdCallback() { this.style.display = 'block'; this.style.backgroundColor = '#2196F3'; this.style.webkitUserSelect = 'none'; this.style.cursor = 'default'; this.style.color = '#FFFFFF'; this.style.padding = '10px'; const div = document.createElement('div'); div.style.margin = '0px'; div.style.fontSize = '2em'; div.appendChild(document.createElement(HamburgerElement.NAME)); div.appendChild(document.createTextNode(' ' + TITLE)); this.appendChild(div); } } document.registerElement(HeadingElement.NAME, HeadingElement); /** * A sidebar that has navigation for the app. */ export class SidebarElement extends HTMLElement { static readonly NAME = 'heap-sidebar'; createdCallback() { this.style.display = 'none'; this.style.backgroundColor = '#9E9E9E'; this.style.width = '15em'; const aboutButton = document.createElement('button'); aboutButton.innerText = 'about'; aboutButton.onclick = () => { this.dispatchEvent(new ActionEvent(Action.NAVIGATE_ABOUT)) }; this.appendChild(aboutButton); } toggle() { this.style.display = this.style.display === 'none' ? 'block' : 'none'; } } document.registerElement(SidebarElement.NAME, SidebarElement); /** * A Container for the main content in the app. * TODO(matloob): Implement main content. */ export class MainContentElement extends HTMLElement { static readonly NAME = 'heap-container'; attachedCallback() { this.style.backgroundColor = '#E0E0E0'; this.style.height = '100%'; this.style.flex = '1'; } } document.registerElement(MainContentElement.NAME, MainContentElement); /** * A container and controller for the whole app. * Contains the heading, side drawer and main panel. */ class AppElement extends HTMLElement { static readonly NAME = 'heap-app'; private sidebar: SidebarElement; private mainContent: MainContentElement; attachedCallback() { document.title = TITLE; this.addEventListener( ActionEvent.EVENT_TYPE, e => this.handleAction(e as ActionEvent), /* capture */ true); this.render(); } render() { this.style.display = 'block'; this.style.height = '100vh'; this.style.width = '100vw'; this.appendChild(document.createElement(HeadingElement.NAME)); const bodyDiv = document.createElement('div'); bodyDiv.style.height = '100%'; bodyDiv.style.display = 'flex'; this.sidebar = document.createElement(SidebarElement.NAME) as SidebarElement; bodyDiv.appendChild(this.sidebar); this.mainContent = document.createElement(MainContentElement.NAME) as MainContentElement; bodyDiv.appendChild(this.mainContent); this.appendChild(bodyDiv); this.renderRoute(); } renderRoute() { this.mainContent.innerHTML = '' switch (window.location.pathname) { case '/about': this.mainContent.appendChild( document.createElement(AboutPageElement.NAME)); break; } } handleAction(event: ActionEvent) { switch (event.action) { case Action.TOGGLE_SIDEBAR: this.sidebar.toggle(); break; case Action.NAVIGATE_ABOUT: window.history.pushState({}, '', '/about'); this.renderRoute(); break; } } } document.registerElement(AppElement.NAME, AppElement); /** * An about page. */ class AboutPageElement extends HTMLElement { static readonly NAME = 'heap-about'; createdCallback() { this.textContent = TITLE; } } document.registerElement(AboutPageElement.NAME, AboutPageElement); /** * Resets body's margin and padding, and sets font. */ function clearStyle(document: Document) { const styleElement = document.createElement('style') as HTMLStyleElement; document.head.appendChild(styleElement); const styleSheet = styleElement.sheet as CSSStyleSheet; styleSheet.insertRule( '* {font-family: Roboto,Helvetica; box-sizing: border-box}', 0); styleSheet.insertRule('body {margin: 0px; padding:0px}', 0); } export function main() { clearStyle(document); document.body.appendChild(document.createElement(AppElement.NAME)); }