| <!-- The <debugger-app-sk> custom element declaration. |
| |
| The main application element for the Skia Debugger. |
| |
| Attributes: |
| None. |
| |
| Events: |
| None. |
| |
| Methods: |
| None. |
| |
| --> |
| <link rel=import href="/res/imp/bower_components/iron-flex-layout/iron-flex-layout-classes.html"> |
| <link rel=import href="/res/imp/bower_components/iron-icon/iron-icon.html"> |
| <link rel=import href="/res/imp/bower_components/iron-icons/iron-icons.html"> |
| <link rel=import href="/res/imp/bower_components/paper-checkbox/paper-checkbox.html"> |
| <link rel=import href="/res/imp/bower_components/paper-drawer-panel/paper-drawer-panel.html"> |
| <link rel=import href="/res/imp/bower_components/paper-header-panel/paper-header-panel.html"> |
| <link rel=import href="/res/imp/bower_components/paper-icon-button/paper-icon-button.html"> |
| <link rel=import href="/res/imp/bower_components/paper-toggle-button/paper-toggle-button.html"> |
| <link rel=import href="/res/imp/bower_components/paper-radio-group/paper-radio-group.html"> |
| <link rel=import href="/res/imp/bower_components/paper-radio-button/paper-radio-button.html"> |
| <link rel=import href="/res/imp/bower_components/paper-input/paper-input.html"> |
| <link rel=import href="/res/imp/bower_components/paper-toolbar/paper-toolbar.html"> |
| <link rel=import href="/res/imp/bower_components/paper-tabs/paper-tabs.html"> |
| <link rel=import href="/res/imp/bower_components/iron-pages/iron-pages.html"> |
| |
| <link rel=import href="/res/common/imp/canvas-layers.html"> |
| <link rel=import href="/res/common/imp/crosshair.html"> |
| <link rel=import href="/res/common/imp/dbg-info.html"> |
| <link rel=import href="/res/common/imp/details-summary.html"> |
| <link rel=import href="/res/common/imp/error-toast-sk.html"> |
| <link rel=import href="/res/common/imp/play.html"> |
| <link rel=import href="/res/common/imp/zoom.html"> |
| |
| <link rel=import href="/res/imp/instance.html"> |
| |
| <dom-module id="debugger-app-sk"> |
| <style include="iron-flex iron-flex-alignment iron-flex-factors"> |
| :root { |
| --paper-toolbar-background: #1f78b4; |
| } |
| |
| paper-tab { |
| --paper-tab: { |
| font-size: 12px; |
| } |
| } |
| |
| paper-tabs { |
| height: 24px; |
| } |
| |
| button { |
| font-size: 12px; |
| } |
| |
| :host { |
| font-size: 12px; |
| display: block; |
| height: 100% |
| } |
| |
| #content { |
| margin: 0.5em; |
| } |
| |
| op-sk { |
| display: block; |
| } |
| |
| play-sk { |
| margin: 5px auto; |
| } |
| |
| #center { |
| overflow: hidden; |
| } |
| |
| #rendered { |
| margin: 0 10px; |
| overflow: auto; |
| } |
| |
| * { |
| font-family: Helvetica,Arial,'Bitstream Vera Sans',sans-serif; |
| } |
| |
| .hidden { |
| display: none; |
| } |
| |
| .shortcuts { |
| margin-left: 3em; |
| margin-bottom: 2em; |
| } |
| |
| .shortcuts td { |
| padding-left: 1em; |
| } |
| |
| .shortcuts tr { |
| padding-bottom: 0.5em; |
| } |
| |
| #colorBreakPoint { |
| margin-left: 3em; |
| } |
| |
| #download { |
| margin: 0 3em; |
| color: #1f78b4; |
| } |
| |
| dbg-info-sk, |
| #textBounds, |
| #clip { |
| margin-left: 3em; |
| margin-bottom: 1em; |
| display: block; |
| } |
| |
| paper-radio-button { |
| margin-left: 3em; |
| margin-bottom: 1em; |
| padding: 0; |
| } |
| |
| #gpuOpBounds, |
| paper-toggle-button { |
| margin: 0.5em 1em; |
| } |
| |
| .gpuDrawBoundColor { |
| color: #E31A1C; |
| opacity: 0.75; |
| } |
| |
| .gpuOpBoundColor { |
| color: #FF7F00; |
| opacity: 0.75; |
| } |
| |
| .gpuTotalOpColor { |
| color: #6A3D9A; |
| opacity: 0.75; |
| } |
| |
| paper-tabs { |
| --paper-tabs-selection-bar-color: #1f78b4; |
| margin-bottom: 5px; |
| } |
| |
| paper-tab { |
| color: white; |
| background: gray; |
| } |
| |
| paper-tab.iron-selected { |
| font-weight: bold; |
| background: #1f78b4; |
| } |
| |
| paper-tab[aria-disabled=true] { |
| color: white; |
| font-style: italic; |
| background: lightgray; |
| } |
| |
| header { |
| background: #1f78b4; |
| color: white; |
| padding: 0.5em; |
| margin: 0; |
| } |
| |
| header h2, |
| header div, |
| header instance-status-sk, |
| header login-sk, |
| { |
| display: inline-block; |
| } |
| |
| header .hidden { |
| display: none; |
| } |
| |
| header h2 { |
| margin: 0; |
| } |
| |
| dt { |
| font-weight: bold; |
| padding: 0.5em 0; |
| } |
| |
| dd { |
| font-family: monospace; |
| } |
| |
| canvas-layers-sk { |
| border: solid lightgray 1px; |
| } |
| |
| paper-icon-button.resizer |
| #left paper-icon-button { |
| margin: 0; |
| padding: 0; |
| width: 24px; |
| height: 24px; |
| } |
| |
| #upload summary-sk { |
| font-weight: bold; |
| } |
| |
| #upload div { |
| padding: 1em 2em; |
| } |
| |
| #histogram table { |
| padding-left: 2em; |
| } |
| |
| #histogram .countCol { |
| text-align: right; |
| } |
| |
| #histogram td { |
| padding: 0.2em; |
| } |
| |
| zoom-sk { |
| display: block; |
| margin-top: 1em; |
| box-shadow: 5px 5px 18px grey; |
| } |
| |
| #img.bottom { |
| width: auto; |
| height: 60vh; |
| } |
| |
| #img.fit { |
| max-width: 100%; |
| max-height: 60vh; |
| width: auto; |
| height: auto; |
| } |
| |
| #img.natural { |
| width: auto; |
| height: auto; |
| } |
| |
| #img.right{ |
| width: 100%; |
| height: auto; |
| } |
| |
| .sizeToolBar paper-icon-button { |
| opacity: 0.5; |
| } |
| |
| .sizeToolBar paper-icon-button:hover { |
| background-color: #eee; |
| } |
| |
| details-sk { |
| display: block; |
| } |
| </style> |
| <template> |
| <header class="horizontal layout center"> |
| <h2>Skia Debugger</h2> |
| <div class=flex></div> |
| <instance-status-sk id=status class=hidden></instance-status-sk> |
| <login-sk id=login class=hidden></login-sk> |
| </header> |
| <div id=content> |
| <div class="layout horizontal center"> |
| <form enctype="multipart/form-data" method="post" id=post_skp action="./new" name="fileinfo"> |
| <label>SKP to upload:</label> |
| <input type="file" name="file" required /> |
| <button class=action>Upload</button> |
| </form> |
| <a href="/download" id=download class=hidden>Download</a> |
| </div> |
| <div class="layout horizontal" hidden="[[ skp_not_loaded ]]"> |
| <div id=left class$="layout vertical flex-{{ _leftRatio }}"> |
| <div class="layout horizontal end-justified"> |
| <paper-icon-button class=resizer on-change=_smallerLeft icon="arrow-back" title="Shrink the command panel."></paper-icon-button> |
| </div> |
| <div class="layout horizontal"> |
| <paper-input id="fast" on-change="_fastFilter" label="Filter |
| (Leading ! means remove matches)" placeholder="!save restore" |
| class=flex></paper-input> |
| <button id=clear on-tap=_clearFilter>Clear</button> |
| </div> |
| <play-sk id=play></play-sk> |
| <commands-sk id=commands grouping=50></commands-sk> |
| </div> |
| <div id=center class="layout vertical flex-3"> |
| <div> |
| <paper-icon-button class=resizer on-tap=_biggerLeft icon="arrow-forward" |
| title="Grow the command panel."></paper-icon-button> |
| </div> |
| <div id=rendered> |
| <paper-tabs selected="{{tab_selected}}"> |
| <paper-tab>Picture</paper-tab> |
| <paper-tab disabled=[[_isEmpty(bitmap)]]>Image</paper-tab> |
| </paper-tabs> |
| <iron-pages selected="{{tab_selected}}"> |
| <div> |
| <div class="layout horizontal sizeToolBar"> |
| <paper-icon-button src="/res/img/image.png" data-style="natural" title="Original size." on-tap="_resizeImage"></paper-icon-button> |
| <paper-icon-button src="/res/img/both.png" data-style="fit" title="Fit to page." on-tap="_resizeImage"></paper-icon-button> |
| <paper-icon-button src="/res/img/right.png" data-style="right" title="Fit to page width." on-tap="_resizeImage"></paper-icon-button> |
| <paper-icon-button src="/res/img/bottom.png" data-style="bottom" title="Fit to height." on-tap="_resizeImage"></paper-icon-button> |
| </div> |
| <canvas-layers-sk layers='["crosshair"]' id=layers on-tap="_crosshairClick"> |
| <img id=img src="" class="fit"> |
| </canvas-layers-sk> |
| <crosshair-sk id=crosshair target=layers name=crosshair update_on=move hidden></crosshair-sk> |
| </div> |
| <div> |
| <img id=standAloneImg src$="[[bitmap]]"> |
| </div> |
| </iron-pages> |
| </div> |
| </div> |
| <div id=right class$="layout vertical flex-2"> |
| <div class="layout horizontal"> |
| <paper-toggle-button on-iron-change="_gpuChange" id=gpu checked="{{_gpuChecked(_cmd.mode)}}">GPU</paper-toggle-button> |
| <paper-checkbox disabled="{{_gpuNotChecked(_cmd.mode)}}" checked="{{_isTrue(_cmd.drawGpuOpBounds)}}" id=gpuOpBounds on-change="_gpuOpBounds">Display GPU Op Bounds</paper-checkbox> |
| </div> |
| <details-sk> |
| <summary-sk> |
| Color Mode |
| </summary-sk> |
| <paper-radio-group class="layout vertical" on-paper-radio-group-changed="_colorModeChange" id=colorMode selected="[[_cmd.colorMode]]"> |
| <paper-radio-button name="0">Linear 32-bit (Legacy)</paper-radio-button> |
| <paper-radio-button name="1">sRGB 32-bit</paper-radio-button> |
| <paper-radio-button name="2">Linear half-precision float</paper-radio-button> |
| </paper-radio-group> |
| </details-sk> |
| <details-sk> |
| <summary-sk> |
| Bounds and Matrix |
| </summary-sk> |
| <paper-checkbox id=clip on-tap="_clipHandler">Show Clip</paper-checkbox> |
| <paper-checkbox id=textBounds on-tap="_textBoundsHandler">Show Text Bounds</paper-checkbox> |
| <dbg-info-sk info="{{ info }}"></dbg-info-sk> |
| </details-sk> |
| <details-sk id=gpuOpBoundsLegend class=hidden> |
| <summary-sk> |
| GPU Op Bounds Legend |
| </summary-sk> |
| <table class=shortcuts> |
| <tr><td class=gpuDrawBoundColor>Bounds for the current draw.</td></tr> |
| <tr><td class=gpuOpBoundColor>Individual bounds for other draws in the same op.</td></tr> |
| <tr><td class=gpuTotalOpColor>Total bounds of the current op.</td></tr> |
| </table> |
| </details-sk> |
| <details-sk id=histogram class="hidden"> |
| <summary-sk> |
| Histogram |
| </summary-sk> |
| <table> |
| <template is="dom-repeat" items="{{histogram}}"> |
| <tr><td class=countCol>{{item.count}}</td><td>{{item.name}}</td></tr> |
| </template> |
| <tr><td class=countCol>{{_filtered.commands.length}}</td><td><b>Total</b></td></tr> |
| </table> |
| </details-sk> |
| <dl> |
| <dt>Postion</dt> |
| <dd>({{ x }}, {{ y }})</dd> |
| <dt>Color</dt> |
| <dd>{{ rgb }}</dd> |
| <dd>{{ hex }}</dd> |
| </dl> |
| <div> |
| <paper-checkbox disabled id=colorBreakPoint on-tap="_breakPoint">Break on change.</paper-checkbox> <span id=prevColor></span> |
| </div> |
| <details-sk id=keyboardshortcuts class=hidden> |
| <summary-sk> |
| Keyboard shortcuts |
| </summary-sk> |
| <table class=shortcuts> |
| <tr><th>H</th><td>Left</td></tr> |
| <tr><th>L</th><td>Right</td></tr> |
| <tr><th>J</th><td>Down</td></tr> |
| <tr><th>K</th><td>Up</td></tr> |
| <tr><td colspan=2>Click the image again to turn off keyboard navigation.</td></tr> |
| </table> |
| </details-sk> |
| <zoom-sk source=img pixels=20 id=zoom></zoom-sk> |
| </div> |
| </div> |
| <error-toast-sk></error-toast-sk> |
| </div> |
| </template> |
| </dom-module> |
| |
| <script> |
| (function () { |
| var INDENTERS = { |
| "Save": { icon: "icons:save", color: "#B2DF8A", count: 1 }, |
| "SaveLayer": { icon: "icons:content-copy", color: "#FDBF6F", count: 1 }, |
| "BeginDrawPicture": { icon: "image:image", color: "#A6CEE3", count: 1 }, |
| }; |
| var OUTDENTERS = ["Restore", "EndDrawPicture"]; |
| |
| Polymer({ |
| is: "debugger-app-sk", |
| |
| properties: { |
| tab_selected: { |
| type: Number, |
| value: 0, |
| reflectToAttribute: false, |
| }, |
| bitmap: { |
| type: String, |
| value: "", |
| reflectToAttribute: false, |
| }, |
| _leftRatio: { |
| type: Number, |
| value: 3, |
| reflectToAttribute: false, |
| }, |
| skp_not_loaded: { |
| type: Boolean, |
| value: true, |
| reflectToAttribute: false, |
| }, |
| histogram: { |
| type: Array, |
| value: function() { return []; }, |
| reflectToAttribute: false, |
| } |
| }, |
| |
| ready: function() { |
| // _targetItem is the index of the op we are in the process of moving to. |
| // The index is the offset of the op in the this._filtered.commands array. |
| // That is, we've requested to move to this op, but the image might not |
| // be loaded yet. |
| this._targetItem = 0; |
| |
| |
| // The original JSON response from the server. |
| this._cmd = { |
| version: 1, |
| commands: [], |
| }; |
| |
| // _filtered contains the .commands that match the current filter, or all |
| // of the commands if no filter is active. |
| // |
| // NB: There is a distintion in the code below between an op's item vs |
| // index. That is, an ops index never changes, it is the index |
| // number that the server understands, and is the location of the op |
| // in this._cmd.commands. |
| // |
| // The op item changes, it is the location of the op in |
| // this._filtered.commands. Some functions use index, some use |
| // item. |
| this._filtered = { |
| version: 1, |
| commands: [], |
| }; |
| |
| // Load the test image served by the debugger. |
| this._setCacheBreakingURL(); |
| this.$.post_skp.action= this._correctedOrigin() + "new"; |
| |
| this.$.commands.addEventListener('op-selected', function(e) { |
| // Only force reloading the image if necessary. |
| |
| var item = this._findItemFromIndex(e.detail.index); |
| if (this._targetItem != item) { |
| this._targetItem = item; |
| this._setCacheBreakingURL(); |
| } else { |
| // We know if we've gotten here that the element wasn't selected by |
| // a UI action, i.e. we know we're here because we are 'run'ing. |
| this.$.commands.scrollToTop(e.detail.index); |
| } |
| }.bind(this)); |
| |
| this.$.commands.addEventListener('op-toggled', function(e) { |
| // Toggle the op and the trigger a reload of the image. |
| var value = e.detail.checked ? "1" : "0"; |
| sk.post("./cmd/" + e.detail.index + "/" + value, "").then(function() { |
| this._setCacheBreakingURL(); |
| }.bind(this)).catch(sk.errorMessage); |
| }.bind(this)); |
| |
| this.$.commands.addEventListener('op-zoom', function(e) { |
| this.$.fast.value = e.detail; |
| this._fastFilter(); |
| }.bind(this)); |
| |
| this._refreshPage(); |
| |
| this.$.crosshair.addEventListener('crosshair', function(e) { |
| this.$.zoom.x = e.detail.x; |
| this.$.zoom.y = e.detail.y; |
| }.bind(this)); |
| |
| this.$.play.prePlayCallback(function() { |
| if (!this.$.colorBreakPoint.disabled && this.$.colorBreakPoint.checked) { |
| sk.get("./break/" + this._targetItem + "/" + this.x + "/" + this.y).then(JSON.parse).then(function(json) { |
| if (json.endOp != -1) { |
| this.$.prevColor.textContent = json.startColor + " -> " + json.endColor; |
| var item = this._findItemFromIndex(json.endOp); |
| this._targetItem = item; |
| this._setCacheBreakingURL(); |
| } else { |
| sk.errorMessage("Pixel at (" + this.x + ", " + this.y + ") never changes."); |
| } |
| }.bind(this)).catch(sk.errorMessage); |
| return false; |
| } |
| return true; |
| }.bind(this)); |
| |
| this.$.zoom.addEventListener('zoom-point', function(e) { |
| this.set("rgb", e.detail.rgb); |
| this.set("hex", e.detail.hex); |
| this.set("x", e.detail.x); |
| this.set("y", e.detail.y); |
| }.bind(this)); |
| |
| this.$.zoom.addEventListener('zoom-loaded', function(e) { |
| this.$.play.movedTo(this._targetItem); |
| }.bind(this)); |
| |
| this.$.img.addEventListener('load', function() { |
| this.$.download.href = this._correctedOrigin() + "download"; |
| this.$.download.classList.remove("hidden"); |
| |
| this.$.commands.item = this._targetItem; |
| }.bind(this)); |
| |
| this.$.play.addEventListener('moveto', function(e) { |
| this._targetItem = e.detail.item; |
| this._setCacheBreakingURL(); |
| }.bind(this)); |
| |
| this.$.play.addEventListener('mode-changed-manually', function(e) { |
| // Clear the breakpoint info if the user presses a button. |
| this.$.prevColor.textContent = ""; |
| }.bind(this)); |
| |
| // Only display the login control if we are running in hosted mode, in which case |
| // the /loginstatus/ endpoint will return valid JSON, otherwise we are running |
| // locally. |
| sk.get("/loginstatus/").then(JSON.parse).then(function(json) { |
| this.$.login.classList.remove('hidden'); |
| this.$.status.classList.remove('hidden'); |
| }.bind(this)); |
| }, |
| |
| attached: function() { |
| document.body.addEventListener('keydown', this._keyDownHandler.bind(this), true); |
| }, |
| |
| _resizeImage: function(e) { |
| var ele = sk.findParent(e.target, "PAPER-ICON-BUTTON"); |
| if (!ele) { |
| return |
| } |
| this.$.img.classList.remove("natural", "fit", "right", "bottom"); |
| this.$.img.classList.add(ele.dataset.style); |
| window.dispatchEvent(new CustomEvent("partial-resize")); |
| }, |
| |
| _smallerLeft: function() { |
| if (this._leftRatio > 1) { |
| this._leftRatio--; |
| } |
| }, |
| |
| _biggerLeft: function() { |
| if (this._leftRatio < 12) { |
| this._leftRatio++; |
| } |
| }, |
| |
| _colorModeChange: function(e) { |
| // Ignore if this._cmd hasn't been loaded yet. |
| if (!this._cmd) { |
| return |
| } |
| // Don't do anything if the radio group and the mode already agree. |
| if (e.target.selected == this._cmd.colorMode) { |
| return |
| } |
| sk.post("./colorMode/" + e.target.selected, "").then(function() { |
| // Reloads the image and the JSON for all the commands. |
| this._refreshPage(); |
| }.bind(this)).catch(function(err) { |
| this.$.colorMode.selected = 0; |
| sk.errorMessage(err); |
| }.bind(this)); |
| }, |
| |
| _gpuChange: function(e) { |
| // Ignore if this._cmd hasn't been loaded yet. |
| if (!this._cmd) { |
| return |
| } |
| // Don't do anything if the toggle and the mode already agree. |
| if (e.target.checked == (this._cmd.mode == "gpu")) { |
| return |
| } |
| sk.post("./enableGPU/" + (e.target.checked ? 1 : 0), "").then(function() { |
| // Reloads the image and the JSON for all the commands. |
| this._refreshPage(); |
| }.bind(this)).catch(function(err) { |
| this.$.gpu.checked = !this.$.gpu.checked; |
| sk.errorMessage(err); |
| }.bind(this)); |
| }, |
| |
| _gpuChecked: function(mode) { |
| return mode == "gpu"; |
| }, |
| |
| _gpuNotChecked: function(mode) { |
| return !this._gpuChecked(mode); |
| }, |
| |
| _isTrue: function(b) { |
| return !!b; |
| }, |
| |
| _isEmpty: function(s) { |
| return s.length == 0; |
| }, |
| |
| _gpuOpBounds: function(e) { |
| // Ignore if this._cmd hasn't been loaded yet. |
| if (!this._cmd) { |
| return |
| } |
| this.$.gpuOpBoundsLegend.classList.toggle("hidden", !this.$.gpuOpBounds.checked); |
| // Don't do anything if the toggle and the mode already agree. |
| if (this.$.gpuOpBounds.checked === this._cmd.drawGpuOpBounds) { |
| return |
| } |
| sk.post("./gpuOpBounds/" + (this.$.gpuOpBounds.checked ? 1 : 0), "").then(function() { |
| // Reloads the image and the JSON for all the commands. |
| this._refreshPage(); |
| }.bind(this)).catch(function(err) { |
| this.$.gpuOpBounds.checked = !this.$.gpuOpBounds.checked; |
| sk.errorMessage(err); |
| }.bind(this)); |
| }, |
| |
| _breakPoint: function() { |
| if (this.$.colorBreakPoint.checked) { |
| // We want to be able to jump to any op when the breakpoint |
| // triggers, so we remove all filtering. |
| this.$.fast.value = ""; |
| this._fastFilter(); |
| } |
| }, |
| |
| _findItemFromIndex: function(index) { |
| var item = 0; |
| for (var i = 0; i < this._filtered.commands.length ; i++) { |
| if (this._filtered.commands[i]._index == index) { |
| item = i; |
| break; |
| } |
| } |
| return item; |
| }, |
| |
| _clearFilter: function() { |
| this.$.fast.value = ""; |
| this._fastFilter(); |
| }, |
| |
| _fastFilter: function() { |
| var rawFilter = this.$.fast.value.trim().toLowerCase(); |
| if (rawFilter.indexOf(":") > 0) { |
| // This is a range filter, e.g. "3:21". |
| this._rangeFilter(rawFilter); |
| } else { |
| // Text filter, e.g. "!save restore". |
| this._textFilter(rawFilter); |
| } |
| }, |
| |
| _rangeFilter: function(rawFilter) { |
| var parts = rawFilter.split(":"); |
| if (parts.length != 2) { |
| sk.errorMessage("Range filters are of the form 'N:M'."); |
| return |
| } |
| var begin = +parts[0]; |
| var end = +parts[1]; |
| var filtered = { |
| version: 1, |
| commands: [], |
| } |
| this._cmd.commands.forEach(function(c, i) { |
| if (i >= begin && i <= end) { |
| filtered.commands.push(c); |
| } |
| }.bind(this)); |
| this._setFiltered(filtered); |
| }, |
| |
| _textFilter: function(rawFilter) { |
| var negative = (rawFilter[0] == "!"); |
| if (negative) { |
| rawFilter = rawFilter.slice(1).trim(); |
| } |
| var filters = rawFilter.split(/\s+/); |
| var matches = function(s) { |
| s = s.toLowerCase(); |
| for (var i = 0; i < filters.length; i++) { |
| if (negative) { |
| if (s.indexOf(filters[i]) >= 0) { |
| return false |
| } |
| } else { |
| if (s.indexOf(filters[i]) >= 0) { |
| return true |
| } |
| } |
| } |
| return negative; |
| }; |
| var filtered = { |
| version: 1, |
| commands: [], |
| } |
| this._cmd.commands.forEach(function(c) { |
| if (matches(JSON.stringify(c.details).toLowerCase())) { |
| filtered.commands.push(c); |
| } |
| }.bind(this)); |
| this._setFiltered(filtered); |
| }, |
| |
| _refreshPage: function() { |
| this.$.img.classList.remove("hidden"); |
| this._setCacheBreakingURL(); |
| sk.get("./info").then(JSON.parse).then(function(json) { |
| this.skp_not_loaded = false; |
| this.set('info', json); |
| }.bind(this)).catch(sk.errorMessage); |
| sk.get("./cmd").then(JSON.parse).then(function(json) { |
| this._cmd = this._processCommands(json); |
| if (this.$.fast.value) { |
| this._fastFilter(); |
| } else { |
| var filtered = { |
| version: 1, |
| commands: this._cmd.commands.slice(), |
| }; |
| this._setFiltered(filtered); |
| } |
| }.bind(this)).catch(sk.errorMessage); |
| }, |
| |
| _setFiltered: function(filtered) { |
| this.$.commands.cmd = filtered; |
| this._filtered = filtered; |
| this.$.play.size = filtered.commands.length; |
| this._targetItem = filtered.commands.length - 1; |
| this._setCacheBreakingURL(); |
| }, |
| |
| _hasBitmap: function(s) { |
| return !!s; |
| }, |
| |
| _clipHandler: function() { |
| var alpha = this.$.clip.checked ? 128 : 0; |
| sk.post("./clipAlpha/" + alpha).then(function() { |
| this._setCacheBreakingURL(); |
| }.bind(this)).catch(sk.errorMessage); |
| }, |
| |
| _textBoundsHandler: function() { |
| var checked = this.$.textBounds.checked ? 1 : 0; |
| sk.post("./textBounds/" + checked).then(function() { |
| this._setCacheBreakingURL(); |
| }.bind(this)).catch(sk.errorMessage); |
| }, |
| |
| _setCacheBreakingURL: function() { |
| // Get op index from item number using this._filtered. |
| if (this._filtered.commands.length == 0) { |
| return |
| } |
| var index = this._filtered.commands[this._targetItem]._index; |
| var op = this._filtered.commands[this._targetItem]; |
| var path = "img/" + index + "?"; |
| this.bitmap = op.details.bitmap ? (this._correctedOrigin() + op.details.bitmap.data) : ""; |
| sk.get("./info/" + index).then(JSON.parse).then(function(json) { |
| this.set('info', json); |
| }.bind(this)).catch(sk.errorMessage); |
| this.$.img.src = this._correctedOrigin() + path + "?" + new Date().getTime(); |
| }, |
| |
| _correctedOrigin: function() { |
| // Since we always use relative URLs, i.e. "./foo" we need to make |
| // sure the base URL ends with a "/". |
| var origin = window.location.href; |
| if (origin[origin.length-1] != "/") { |
| origin += "/"; |
| } |
| return origin; |
| }, |
| |
| _crosshairClick: function(e) { |
| if (this.$.crosshair.update_on === "move") { |
| this.$.crosshair.update_on = "click"; |
| this.$.colorBreakPoint.disabled = false; |
| this.$.keyboardshortcuts.classList.remove("hidden"); |
| this.$.crosshair.hidden = false; |
| } else { |
| this.$.crosshair.update_on = "move"; |
| this.$.colorBreakPoint.disabled = true; |
| this.$.keyboardshortcuts.classList.add("hidden"); |
| this.$.crosshair.hidden = true; |
| } |
| }, |
| |
| _keyDownHandler: function(e) { |
| var c = String.fromCharCode(e.keyCode); |
| switch (c) { |
| case "J": |
| this.$.crosshair.y = this.$.crosshair.y+1; |
| break; |
| case "K": |
| this.$.crosshair.y = this.$.crosshair.y-1; |
| break; |
| case "H": |
| this.$.crosshair.x = this.$.crosshair.x-1; |
| break; |
| case "L": |
| this.$.crosshair.x = this.$.crosshair.x+1; |
| break; |
| } |
| if ("JKHL".indexOf(c) != -1 ) { |
| e.stopPropagation(); |
| } |
| }, |
| |
| _json: function(s) { |
| return JSON.stringify(s, null, 2); |
| }, |
| |
| _deepCopy: function(o) { |
| return JSON.parse(JSON.stringify(o)); |
| }, |
| |
| // _processCommands cycles through the commands and set a depth based on Save/Restore pairs. |
| // |
| // Also calculates the histogram. |
| _processCommands: function(cmd) { |
| var commands = cmd.commands; |
| var depth = 0; |
| var prefixes = []; |
| var counts = {}; // Tally of each command. |
| var matchup = []; // Match up saves and restores. |
| for (var i = 0; i < commands.length; i++) { |
| commands[i] = { |
| details: commands[i], |
| _index: i, |
| }; |
| commands[i]._depth = depth; |
| commands[i]._prefix = this._deepCopy(prefixes); |
| var name = commands[i].details.command; |
| counts[name] = (counts[name] || 0) + 1; |
| if (commands[i].details.command in INDENTERS) { |
| depth++; |
| |
| matchup.push(i); |
| // If this is the same type of indenting op we've already seen |
| // then just increment the count, otherwise add as a new |
| // op in prefixes. |
| if (depth > 1 && prefixes[prefixes.length-1].icon == INDENTERS[commands[i].details.command].icon) { |
| prefixes[prefixes.length-1].count++; |
| } else { |
| prefixes.push(this._deepCopy(INDENTERS[commands[i].details.command])); |
| } |
| } else if (OUTDENTERS.indexOf(commands[i].details.command) != -1) { |
| depth--; |
| |
| // Now that we can match an OUTDENTER with an INDENTER we can set |
| // the _zoom property for both commands. |
| var begin = matchup.pop(); |
| var range = begin + ":" + i; |
| commands[i]._zoom = range |
| commands[begin]._zoom = range; |
| |
| // Only pop the op from prefixes if its count has reached 1. |
| if (prefixes[prefixes.length-1].count > 1) { |
| prefixes[prefixes.length-1].count--; |
| } else { |
| prefixes.pop(); |
| } |
| commands[i]._depth = depth; |
| commands[i]._prefix = this._deepCopy(prefixes); |
| } |
| } |
| |
| // Calculate the histogram of the ops. |
| // First convert the object into an Array of objects. |
| var histogram = []; |
| for (k in counts) { |
| histogram.push({ |
| name: k, |
| count: counts[k], |
| }); |
| } |
| // Now sort the array, descending on the count, ascending |
| // on the op name. |
| histogram.sort(function(a,b) { |
| if (a.count == b.count) { |
| if (a.name < b.name) { |
| return -1; |
| } |
| if (a.name > b.name) { |
| return 1; |
| } |
| return 0; |
| } else { |
| return b.count - a.count; |
| } |
| }); |
| this.histogram = histogram; |
| this.$.histogram.classList.remove('hidden'); |
| return cmd; |
| } |
| }); |
| })(); |
| </script> |