123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911 |
- // Copyright 2017 Google Inc. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- package driver
-
- import "html/template"
-
- // addTemplates adds a set of template definitions to templates.
- func addTemplates(templates *template.Template) {
- template.Must(templates.Parse(`
- {{define "css"}}
- <style type="text/css">
- html {
- height: 100%;
- min-height: 100%;
- margin: 0px;
- }
- body {
- margin: 0px;
- width: 100%;
- height: 100%;
- min-height: 100%;
- overflow: hidden;
- }
- #graphcontainer {
- display: flex;
- flex-direction: column;
- height: 100%;
- min-height: 100%;
- width: 100%;
- min-width: 100%;
- margin: 0px;
- }
- #graph {
- flex: 1 1 auto;
- overflow: hidden;
- }
- svg {
- width: 100%;
- height: auto;
- }
- button {
- margin-top: 5px;
- margin-bottom: 5px;
- }
- #detailtext {
- display: none;
- position: fixed;
- top: 20px;
- right: 10px;
- background-color: #ffffff;
- min-width: 160px;
- border: 1px solid #888;
- box-shadow: 4px 4px 4px 0px rgba(0,0,0,0.2);
- z-index: 1;
- }
- #closedetails {
- float: right;
- margin: 2px;
- }
- #home {
- font-size: 14pt;
- padding-left: 0.5em;
- padding-right: 0.5em;
- float: right;
- }
- .menubar {
- display: inline-block;
- background-color: #f8f8f8;
- border: 1px solid #ccc;
- width: 100%;
- }
- .menu-header {
- position: relative;
- display: inline-block;
- padding: 2px 2px;
- cursor: default;
- font-size: 14pt;
- }
- .menu {
- display: none;
- position: absolute;
- background-color: #f8f8f8;
- border: 1px solid #888;
- box-shadow: 4px 4px 4px 0px rgba(0,0,0,0.2);
- z-index: 1;
- margin-top: 2px;
- left: 0px;
- min-width: 5em;
- }
- .menu hr {
- background-color: #fff;
- margin-top: 0px;
- margin-bottom: 0px;
- }
- .menu a, .menu button {
- display: block;
- width: 100%;
- margin: 0px;
- padding: 2px 0px 2px 0px;
- text-align: left;
- text-decoration: none;
- color: #000;
- background-color: #f8f8f8;
- font-size: 12pt;
- border: none;
- }
- .menu-header:hover {
- background-color: #ccc;
- }
- .menu-header:hover .menu {
- display: block;
- }
- .menu a:hover, .menu button:hover {
- background-color: #ccc;
- }
- .menu a.disabled {
- color: gray;
- pointer-events: none;
- }
- #searchbox {
- margin-left: 10pt;
- }
- #bodycontainer {
- width: 100%;
- height: 100%;
- max-height: 100%;
- overflow: scroll;
- padding-top: 5px;
- }
- #toptable {
- border-spacing: 0px;
- width: 100%;
- padding-bottom: 1em;
- }
- #toptable tr th {
- border-bottom: 1px solid black;
- text-align: right;
- padding-left: 1em;
- padding-top: 0.2em;
- padding-bottom: 0.2em;
- }
- #toptable tr td {
- padding-left: 1em;
- font: monospace;
- text-align: right;
- white-space: nowrap;
- cursor: default;
- }
- #toptable tr th:nth-child(6),
- #toptable tr th:nth-child(7),
- #toptable tr td:nth-child(6),
- #toptable tr td:nth-child(7) {
- text-align: left;
- }
- #toptable tr td:nth-child(6) {
- max-width: 30em; // Truncate very long names
- overflow: hidden;
- }
- #flathdr1, #flathdr2, #cumhdr1, #cumhdr2, #namehdr {
- cursor: ns-resize;
- }
- .hilite {
- background-color: #ccf;
- }
- </style>
- {{end}}
-
- {{define "header"}}
- <div id="detailtext">
- <button id="closedetails">Close</button>
- {{range .Legend}}<div>{{.}}</div>{{end}}
- </div>
-
- <div class="menubar">
-
- <div class="menu-header">
- View
- <div class="menu">
- <a title="{{.Help.top}}" href="/top" id="topbtn">Top</a>
- <a title="{{.Help.graph}}" href="/" id="graphbtn">Graph</a>
- <a title="{{.Help.peek}}" href="/peek" id="peek">Peek</a>
- <a title="{{.Help.list}}" href="/source" id="list">Source</a>
- <a title="{{.Help.disasm}}" href="/disasm" id="disasm">Disassemble</a>
- <hr>
- <button title="{{.Help.details}}" id="details">Details</button>
- </div>
- </div>
-
- <div class="menu-header">
- Refine
- <div class="menu">
- <a title="{{.Help.focus}}" href="{{.BaseURL}}" id="focus">Focus</a>
- <a title="{{.Help.ignore}}" href="{{.BaseURL}}" id="ignore">Ignore</a>
- <a title="{{.Help.hide}}" href="{{.BaseURL}}" id="hide">Hide</a>
- <a title="{{.Help.show}}" href="{{.BaseURL}}" id="show">Show</a>
- <hr>
- <a title="{{.Help.reset}}" href="{{.BaseURL}}">Reset</a>
- </div>
- </div>
-
- <input id="searchbox" type="text" placeholder="Search regexp" autocomplete="off" autocapitalize="none" size=40>
-
- <span id="home">{{.Title}}</span>
-
- </div> <!-- menubar -->
-
- <div id="errors">{{range .Errors}}<div>{{.}}</div>{{end}}</div>
- {{end}}
-
- {{define "graph" -}}
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>{{.Title}}</title>
- {{template "css" .}}
- </head>
- <body>
-
- {{template "header" .}}
- <div id="graphcontainer">
- <div id="graph">
- {{.HTMLBody}}
- </div>
-
- </div>
- {{template "script" .}}
- <script>viewer({{.BaseURL}}, {{.Nodes}})</script>
- </body>
- </html>
- {{end}}
-
- {{define "script"}}
- <script>
- // Make svg pannable and zoomable.
- // Call clickHandler(t) if a click event is caught by the pan event handlers.
- function initPanAndZoom(svg, clickHandler) {
- 'use strict';
-
- // Current mouse/touch handling mode
- const IDLE = 0
- const MOUSEPAN = 1
- const TOUCHPAN = 2
- const TOUCHZOOM = 3
- let mode = IDLE
-
- // State needed to implement zooming.
- let currentScale = 1.0
- const initWidth = svg.viewBox.baseVal.width
- const initHeight = svg.viewBox.baseVal.height
-
- // State needed to implement panning.
- let panLastX = 0 // Last event X coordinate
- let panLastY = 0 // Last event Y coordinate
- let moved = false // Have we seen significant movement
- let touchid = null // Current touch identifier
-
- // State needed for pinch zooming
- let touchid2 = null // Second id for pinch zooming
- let initGap = 1.0 // Starting gap between two touches
- let initScale = 1.0 // currentScale when pinch zoom started
- let centerPoint = null // Center point for scaling
-
- // Convert event coordinates to svg coordinates.
- function toSvg(x, y) {
- const p = svg.createSVGPoint()
- p.x = x
- p.y = y
- let m = svg.getCTM()
- if (m == null) m = svg.getScreenCTM() // Firefox workaround.
- return p.matrixTransform(m.inverse())
- }
-
- // Change the scaling for the svg to s, keeping the point denoted
- // by u (in svg coordinates]) fixed at the same screen location.
- function rescale(s, u) {
- // Limit to a good range.
- if (s < 0.2) s = 0.2
- if (s > 10.0) s = 10.0
-
- currentScale = s
-
- // svg.viewBox defines the visible portion of the user coordinate
- // system. So to magnify by s, divide the visible portion by s,
- // which will then be stretched to fit the viewport.
- const vb = svg.viewBox
- const w1 = vb.baseVal.width
- const w2 = initWidth / s
- const h1 = vb.baseVal.height
- const h2 = initHeight / s
- vb.baseVal.width = w2
- vb.baseVal.height = h2
-
- // We also want to adjust vb.baseVal.x so that u.x remains at same
- // screen X coordinate. In other words, want to change it from x1 to x2
- // so that:
- // (u.x - x1) / w1 = (u.x - x2) / w2
- // Simplifying that, we get
- // (u.x - x1) * (w2 / w1) = u.x - x2
- // x2 = u.x - (u.x - x1) * (w2 / w1)
- vb.baseVal.x = u.x - (u.x - vb.baseVal.x) * (w2 / w1)
- vb.baseVal.y = u.y - (u.y - vb.baseVal.y) * (h2 / h1)
- }
-
- function handleWheel(e) {
- if (e.deltaY == 0) return
- // Change scale factor by 1.1 or 1/1.1
- rescale(currentScale * (e.deltaY < 0 ? 1.1 : (1/1.1)),
- toSvg(e.offsetX, e.offsetY))
- }
-
- function setMode(m) {
- mode = m
- touchid = null
- touchid2 = null
- }
-
- function panStart(x, y) {
- moved = false
- panLastX = x
- panLastY = y
- }
-
- function panMove(x, y) {
- let dx = x - panLastX
- let dy = y - panLastY
- if (Math.abs(dx) <= 2 && Math.abs(dy) <= 2) return // Ignore tiny moves
-
- moved = true
- panLastX = x
- panLastY = y
-
- // Firefox workaround: get dimensions from parentNode.
- const swidth = svg.clientWidth || svg.parentNode.clientWidth
- const sheight = svg.clientHeight || svg.parentNode.clientHeight
-
- // Convert deltas from screen space to svg space.
- dx *= (svg.viewBox.baseVal.width / swidth)
- dy *= (svg.viewBox.baseVal.height / sheight)
-
- svg.viewBox.baseVal.x -= dx
- svg.viewBox.baseVal.y -= dy
- }
-
- function handleScanStart(e) {
- if (e.button != 0) return // Do not catch right-clicks etc.
- setMode(MOUSEPAN)
- panStart(e.clientX, e.clientY)
- e.preventDefault()
- svg.addEventListener("mousemove", handleScanMove)
- }
-
- function handleScanMove(e) {
- if (e.buttons == 0) {
- // Missed an end event, perhaps because mouse moved outside window.
- setMode(IDLE)
- svg.removeEventListener("mousemove", handleScanMove)
- return
- }
- if (mode == MOUSEPAN) panMove(e.clientX, e.clientY)
- }
-
- function handleScanEnd(e) {
- if (mode == MOUSEPAN) panMove(e.clientX, e.clientY)
- setMode(IDLE)
- svg.removeEventListener("mousemove", handleScanMove)
- if (!moved) clickHandler(e.target)
- }
-
- // Find touch object with specified identifier.
- function findTouch(tlist, id) {
- for (const t of tlist) {
- if (t.identifier == id) return t
- }
- return null
- }
-
- // Return distance between two touch points
- function touchGap(t1, t2) {
- const dx = t1.clientX - t2.clientX
- const dy = t1.clientY - t2.clientY
- return Math.hypot(dx, dy)
- }
-
- function handleTouchStart(e) {
- if (mode == IDLE && e.changedTouches.length == 1) {
- // Start touch based panning
- const t = e.changedTouches[0]
- setMode(TOUCHPAN)
- touchid = t.identifier
- panStart(t.clientX, t.clientY)
- e.preventDefault()
- } else if (mode == TOUCHPAN && e.touches.length == 2) {
- // Start pinch zooming
- setMode(TOUCHZOOM)
- const t1 = e.touches[0]
- const t2 = e.touches[1]
- touchid = t1.identifier
- touchid2 = t2.identifier
- initScale = currentScale
- initGap = touchGap(t1, t2)
- centerPoint = toSvg((t1.clientX + t2.clientX) / 2,
- (t1.clientY + t2.clientY) / 2)
- e.preventDefault()
- }
- }
-
- function handleTouchMove(e) {
- if (mode == TOUCHPAN) {
- const t = findTouch(e.changedTouches, touchid)
- if (t == null) return
- if (e.touches.length != 1) {
- setMode(IDLE)
- return
- }
- panMove(t.clientX, t.clientY)
- e.preventDefault()
- } else if (mode == TOUCHZOOM) {
- // Get two touches; new gap; rescale to ratio.
- const t1 = findTouch(e.touches, touchid)
- const t2 = findTouch(e.touches, touchid2)
- if (t1 == null || t2 == null) return
- const gap = touchGap(t1, t2)
- rescale(initScale * gap / initGap, centerPoint)
- e.preventDefault()
- }
- }
-
- function handleTouchEnd(e) {
- if (mode == TOUCHPAN) {
- const t = findTouch(e.changedTouches, touchid)
- if (t == null) return
- panMove(t.clientX, t.clientY)
- setMode(IDLE)
- e.preventDefault()
- if (!moved) clickHandler(t.target)
- } else if (mode == TOUCHZOOM) {
- setMode(IDLE)
- e.preventDefault()
- }
- }
-
- svg.addEventListener("mousedown", handleScanStart)
- svg.addEventListener("mouseup", handleScanEnd)
- svg.addEventListener("touchstart", handleTouchStart)
- svg.addEventListener("touchmove", handleTouchMove)
- svg.addEventListener("touchend", handleTouchEnd)
- svg.addEventListener("wheel", handleWheel, true)
- }
-
- function viewer(baseUrl, nodes) {
- 'use strict';
-
- // Elements
- const search = document.getElementById("searchbox")
- const graph0 = document.getElementById("graph0")
- const svg = (graph0 == null ? null : graph0.parentElement)
- const toptable = document.getElementById("toptable")
-
- let regexpActive = false
- let selected = new Map()
- let origFill = new Map()
- let searchAlarm = null
- let buttonsEnabled = true
-
- function handleDetails() {
- const detailsText = document.getElementById("detailtext")
- if (detailsText != null) detailsText.style.display = "block"
- }
-
- function handleCloseDetails() {
- const detailsText = document.getElementById("detailtext")
- if (detailsText != null) detailsText.style.display = "none"
- }
-
- function handleKey(e) {
- if (e.keyCode != 13) return
- window.location.href =
- updateUrl(new URL({{.BaseURL}}, window.location.href), "f")
- e.preventDefault()
- }
-
- function handleSearch() {
- // Delay expensive processing so a flurry of key strokes is handled once.
- if (searchAlarm != null) {
- clearTimeout(searchAlarm)
- }
- searchAlarm = setTimeout(selectMatching, 300)
-
- regexpActive = true
- updateButtons()
- }
-
- function selectMatching() {
- searchAlarm = null
- let re = null
- if (search.value != "") {
- try {
- re = new RegExp(search.value)
- } catch (e) {
- // TODO: Display error state in search box
- return
- }
- }
-
- function match(text) {
- return re != null && re.test(text)
- }
-
- // drop currently selected items that do not match re.
- selected.forEach(function(v, n) {
- if (!match(nodes[n])) {
- unselect(n, document.getElementById("node" + n))
- }
- })
-
- // add matching items that are not currently selected.
- for (let n = 0; n < nodes.length; n++) {
- if (!selected.has(n) && match(nodes[n])) {
- select(n, document.getElementById("node" + n))
- }
- }
-
- updateButtons()
- }
-
- function toggleSvgSelect(elem) {
- // Walk up to immediate child of graph0
- while (elem != null && elem.parentElement != graph0) {
- elem = elem.parentElement
- }
- if (!elem) return
-
- // Disable regexp mode.
- regexpActive = false
-
- const n = nodeId(elem)
- if (n < 0) return
- if (selected.has(n)) {
- unselect(n, elem)
- } else {
- select(n, elem)
- }
- updateButtons()
- }
-
- function unselect(n, elem) {
- if (elem == null) return
- selected.delete(n)
- setBackground(elem, false)
- }
-
- function select(n, elem) {
- if (elem == null) return
- selected.set(n, true)
- setBackground(elem, true)
- }
-
- function nodeId(elem) {
- const id = elem.id
- if (!id) return -1
- if (!id.startsWith("node")) return -1
- const n = parseInt(id.slice(4), 10)
- if (isNaN(n)) return -1
- if (n < 0 || n >= nodes.length) return -1
- return n
- }
-
- function setBackground(elem, set) {
- // Handle table row highlighting.
- if (elem.nodeName == "TR") {
- elem.classList.toggle("hilite", set)
- return
- }
-
- // Handle svg element highlighting.
- const p = findPolygon(elem)
- if (p != null) {
- if (set) {
- origFill.set(p, p.style.fill)
- p.style.fill = "#ccccff"
- } else if (origFill.has(p)) {
- p.style.fill = origFill.get(p)
- }
- }
- }
-
- function findPolygon(elem) {
- if (elem.localName == "polygon") return elem
- for (const c of elem.children) {
- const p = findPolygon(c)
- if (p != null) return p
- }
- return null
- }
-
- // convert a string to a regexp that matches that string.
- function quotemeta(str) {
- return str.replace(/([\\\.?+*\[\](){}|^$])/g, '\\$1')
- }
-
- // Update id's href to reflect current selection whenever it is
- // liable to be followed.
- function makeLinkDynamic(id) {
- const elem = document.getElementById(id)
- if (elem == null) return
-
- // Most links copy current selection into the "f" parameter,
- // but Refine menu links are different.
- let param = "f"
- if (id == "ignore") param = "i"
- if (id == "hide") param = "h"
- if (id == "show") param = "s"
-
- // We update on mouseenter so middle-click/right-click work properly.
- elem.addEventListener("mouseenter", updater)
- elem.addEventListener("touchstart", updater)
-
- function updater() {
- elem.href = updateUrl(new URL(elem.href), param)
- }
- }
-
- // Update URL to reflect current selection.
- function updateUrl(url, param) {
- url.hash = ""
-
- // The selection can be in one of two modes: regexp-based or
- // list-based. Construct regular expression depending on mode.
- let re = regexpActive
- ? search.value
- : Array.from(selected.keys()).map(key => quotemeta(nodes[key])).join("|")
-
- // Copy params from this page's URL.
- const params = url.searchParams
- for (const p of new URLSearchParams(window.location.search)) {
- params.set(p[0], p[1])
- }
-
- if (re != "") {
- // For focus/show, forget old parameter. For others, add to re.
- if (param != "f" && param != "s" && params.has(param)) {
- const old = params.get(param)
- if (old != "") {
- re += "|" + old
- }
- }
- params.set(param, re)
- }
-
- return url.toString()
- }
-
- function handleTopClick(e) {
- // Walk back until we find TR and then get the Name column (index 5)
- let elem = e.target
- while (elem != null && elem.nodeName != "TR") {
- elem = elem.parentElement
- }
- if (elem == null || elem.children.length < 6) return
-
- e.preventDefault()
- const tr = elem
- const td = elem.children[5]
- if (td.nodeName != "TD") return
- const name = td.innerText
- const index = nodes.indexOf(name)
- if (index < 0) return
-
- // Disable regexp mode.
- regexpActive = false
-
- if (selected.has(index)) {
- unselect(index, elem)
- } else {
- select(index, elem)
- }
- updateButtons()
- }
-
- function updateButtons() {
- const enable = (search.value != "" || selected.size != 0)
- if (buttonsEnabled == enable) return
- buttonsEnabled = enable
- for (const id of ["focus", "ignore", "hide", "show"]) {
- const link = document.getElementById(id)
- if (link != null) {
- link.classList.toggle("disabled", !enable)
- }
- }
- }
-
- // Initialize button states
- updateButtons()
-
- // Setup event handlers
- if (svg != null) {
- initPanAndZoom(svg, toggleSvgSelect)
- }
- if (toptable != null) {
- toptable.addEventListener("mousedown", handleTopClick)
- toptable.addEventListener("touchstart", handleTopClick)
- }
-
- const ids = ["topbtn", "graphbtn", "peek", "list", "disasm",
- "focus", "ignore", "hide", "show"]
- ids.forEach(makeLinkDynamic)
-
- // Bind action to button with specified id.
- function addAction(id, action) {
- const btn = document.getElementById(id)
- if (btn != null) {
- btn.addEventListener("click", action)
- btn.addEventListener("touchstart", action)
- }
- }
-
- addAction("details", handleDetails)
- addAction("closedetails", handleCloseDetails)
-
- search.addEventListener("input", handleSearch)
- search.addEventListener("keydown", handleKey)
-
- // Give initial focus to main container so it can be scrolled using keys.
- const main = document.getElementById("bodycontainer")
- if (main) {
- main.focus()
- }
- }
- </script>
- {{end}}
-
- {{define "top" -}}
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>{{.Title}}</title>
- {{template "css" .}}
- <style type="text/css">
- </style>
- </head>
- <body>
-
- {{template "header" .}}
-
- <div id="bodycontainer">
- <table id="toptable">
- <tr>
- <th id="flathdr1">Flat
- <th id="flathdr2">Flat%
- <th>Sum%
- <th id="cumhdr1">Cum
- <th id="cumhdr2">Cum%
- <th id="namehdr">Name
- <th>Inlined?</tr>
- <tbody id="rows">
- </tbody>
- </table>
- </div>
-
- {{template "script" .}}
- <script>
- function makeTopTable(total, entries) {
- const rows = document.getElementById("rows")
- if (rows == null) return
-
- // Store initial index in each entry so we have stable node ids for selection.
- for (let i = 0; i < entries.length; i++) {
- entries[i].Id = "node" + i
- }
-
- // Which column are we currently sorted by and in what order?
- let currentColumn = ""
- let descending = false
- sortBy("Flat")
-
- function sortBy(column) {
- // Update sort criteria
- if (column == currentColumn) {
- descending = !descending // Reverse order
- } else {
- currentColumn = column
- descending = (column != "Name")
- }
-
- // Sort according to current criteria.
- function cmp(a, b) {
- const av = a[currentColumn]
- const bv = b[currentColumn]
- if (av < bv) return -1
- if (av > bv) return +1
- return 0
- }
- entries.sort(cmp)
- if (descending) entries.reverse()
-
- function addCell(tr, val) {
- const td = document.createElement('td')
- td.textContent = val
- tr.appendChild(td)
- }
-
- function percent(v) {
- return (v * 100.0 / total).toFixed(2) + "%"
- }
-
- // Generate rows
- const fragment = document.createDocumentFragment()
- let sum = 0
- for (const row of entries) {
- const tr = document.createElement('tr')
- tr.id = row.Id
- sum += row.Flat
- addCell(tr, row.FlatFormat)
- addCell(tr, percent(row.Flat))
- addCell(tr, percent(sum))
- addCell(tr, row.CumFormat)
- addCell(tr, percent(row.Cum))
- addCell(tr, row.Name)
- addCell(tr, row.InlineLabel)
- fragment.appendChild(tr)
- }
-
- rows.textContent = '' // Remove old rows
- rows.appendChild(fragment)
- }
-
- // Make different column headers trigger sorting.
- function bindSort(id, column) {
- const hdr = document.getElementById(id)
- if (hdr == null) return
- const fn = function() { sortBy(column) }
- hdr.addEventListener("click", fn)
- hdr.addEventListener("touch", fn)
- }
- bindSort("flathdr1", "Flat")
- bindSort("flathdr2", "Flat")
- bindSort("cumhdr1", "Cum")
- bindSort("cumhdr2", "Cum")
- bindSort("namehdr", "Name")
- }
-
- viewer({{.BaseURL}}, {{.Nodes}})
- makeTopTable({{.Total}}, {{.Top}})
- </script>
- </body>
- </html>
- {{end}}
-
- {{define "sourcelisting" -}}
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>{{.Title}}</title>
- {{template "css" .}}
- {{template "weblistcss" .}}
- {{template "weblistjs" .}}
- </head>
- <body>
-
- {{template "header" .}}
-
- <div id="bodycontainer">
- {{.HTMLBody}}
- </div>
-
- {{template "script" .}}
- <script>viewer({{.BaseURL}}, null)</script>
- </body>
- </html>
- {{end}}
-
- {{define "plaintext" -}}
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>{{.Title}}</title>
- {{template "css" .}}
- </head>
- <body>
-
- {{template "header" .}}
-
- <div id="bodycontainer">
- <pre>
- {{.TextBody}}
- </pre>
- </div>
-
- {{template "script" .}}
- <script>viewer({{.BaseURL}}, null)</script>
- </body>
- </html>
- {{end}}
- `))
- }
|