Output now uses CodeMirror editor
This commit is contained in:
parent
bc949b47d9
commit
68733c74cc
@ -424,7 +424,7 @@ class Utils {
|
|||||||
const utf8Str = utf8.encode(str);
|
const utf8Str = utf8.encode(str);
|
||||||
|
|
||||||
if (str.length !== utf8Str.length) {
|
if (str.length !== utf8Str.length) {
|
||||||
if (isWorkerEnvironment()) {
|
if (isWorkerEnvironment() && self && typeof self.setOption === "function") {
|
||||||
self.setOption("attemptHighlight", false);
|
self.setOption("attemptHighlight", false);
|
||||||
} else if (isWebEnvironment()) {
|
} else if (isWebEnvironment()) {
|
||||||
window.app.options.attemptHighlight = false;
|
window.app.options.attemptHighlight = false;
|
||||||
|
@ -178,7 +178,6 @@ class Manager {
|
|||||||
this.addDynamicListener(".input-filter-result", "click", this.input.filterItemClick, this.input);
|
this.addDynamicListener(".input-filter-result", "click", this.input.filterItemClick, this.input);
|
||||||
document.getElementById("btn-open-file").addEventListener("click", this.input.inputOpenClick.bind(this.input));
|
document.getElementById("btn-open-file").addEventListener("click", this.input.inputOpenClick.bind(this.input));
|
||||||
document.getElementById("btn-open-folder").addEventListener("click", this.input.folderOpenClick.bind(this.input));
|
document.getElementById("btn-open-folder").addEventListener("click", this.input.folderOpenClick.bind(this.input));
|
||||||
this.addDynamicListener(".eol-select a", "click", this.input.eolSelectClick, this.input);
|
|
||||||
|
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
@ -192,10 +191,7 @@ class Manager {
|
|||||||
document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter));
|
document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter));
|
||||||
document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter));
|
document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter));
|
||||||
document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter));
|
document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter));
|
||||||
document.getElementById("output-html").addEventListener("mouseup", this.highlighter.outputHtmlMouseup.bind(this.highlighter));
|
|
||||||
document.getElementById("output-html").addEventListener("mousemove", this.highlighter.outputHtmlMousemove.bind(this.highlighter));
|
|
||||||
this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter);
|
this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter);
|
||||||
this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter);
|
|
||||||
this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output);
|
this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output);
|
||||||
this.addDynamicListener("#output-file-show-all", "click", this.output.showAllFile, this.output);
|
this.addDynamicListener("#output-file-show-all", "click", this.output.showAllFile, this.output);
|
||||||
this.addDynamicListener("#output-file-slice i", "click", this.output.displayFileSlice, this.output);
|
this.addDynamicListener("#output-file-slice i", "click", this.output.displayFileSlice, this.output);
|
||||||
|
@ -1,190 +0,0 @@
|
|||||||
/**
|
|
||||||
* A Status bar extension for CodeMirror
|
|
||||||
*
|
|
||||||
* @author n1474335 [n1474335@gmail.com]
|
|
||||||
* @copyright Crown Copyright 2022
|
|
||||||
* @license Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {showPanel} from "@codemirror/view";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Counts the stats of a document
|
|
||||||
* @param {element} el
|
|
||||||
* @param {Text} doc
|
|
||||||
*/
|
|
||||||
function updateStats(el, doc) {
|
|
||||||
const length = el.querySelector("#stats-length-value"),
|
|
||||||
lines = el.querySelector("#stats-lines-value");
|
|
||||||
length.textContent = doc.length;
|
|
||||||
lines.textContent = doc.lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current selection info
|
|
||||||
* @param {element} el
|
|
||||||
* @param {EditorState} state
|
|
||||||
* @param {boolean} selectionSet
|
|
||||||
*/
|
|
||||||
function updateSelection(el, state, selectionSet) {
|
|
||||||
const selLen = state.selection && state.selection.main ?
|
|
||||||
state.selection.main.to - state.selection.main.from :
|
|
||||||
0;
|
|
||||||
|
|
||||||
const selInfo = el.querySelector("#sel-info"),
|
|
||||||
curOffsetInfo = el.querySelector("#cur-offset-info");
|
|
||||||
|
|
||||||
if (!selectionSet) {
|
|
||||||
selInfo.style.display = "none";
|
|
||||||
curOffsetInfo.style.display = "none";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selLen > 0) { // Range
|
|
||||||
const start = el.querySelector("#sel-start-value"),
|
|
||||||
end = el.querySelector("#sel-end-value"),
|
|
||||||
length = el.querySelector("#sel-length-value");
|
|
||||||
|
|
||||||
selInfo.style.display = "inline-block";
|
|
||||||
curOffsetInfo.style.display = "none";
|
|
||||||
|
|
||||||
start.textContent = state.selection.main.from;
|
|
||||||
end.textContent = state.selection.main.to;
|
|
||||||
length.textContent = state.selection.main.to - state.selection.main.from;
|
|
||||||
} else { // Position
|
|
||||||
const offset = el.querySelector("#cur-offset-value");
|
|
||||||
|
|
||||||
selInfo.style.display = "none";
|
|
||||||
curOffsetInfo.style.display = "inline-block";
|
|
||||||
|
|
||||||
offset.textContent = state.selection.main.from;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current character encoding of the document
|
|
||||||
* @param {element} el
|
|
||||||
* @param {EditorState} state
|
|
||||||
*/
|
|
||||||
function updateCharEnc(el, state) {
|
|
||||||
// const charenc = el.querySelector("#char-enc-value");
|
|
||||||
// TODO
|
|
||||||
// charenc.textContent = "TODO";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns what the current EOL separator is set to
|
|
||||||
* @param {element} el
|
|
||||||
* @param {EditorState} state
|
|
||||||
*/
|
|
||||||
function updateEOL(el, state) {
|
|
||||||
const eolLookup = {
|
|
||||||
"\u000a": "LF",
|
|
||||||
"\u000b": "VT",
|
|
||||||
"\u000c": "FF",
|
|
||||||
"\u000d": "CR",
|
|
||||||
"\u000d\u000a": "CRLF",
|
|
||||||
"\u0085": "NEL",
|
|
||||||
"\u2028": "LS",
|
|
||||||
"\u2029": "PS"
|
|
||||||
};
|
|
||||||
|
|
||||||
const val = el.querySelector("#eol-value");
|
|
||||||
val.textContent = eolLookup[state.lineBreak];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the Left-hand-side widgets
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function constructLHS() {
|
|
||||||
return `<span data-toggle="tooltip" title="Input length">
|
|
||||||
<i class="material-icons">abc</i>
|
|
||||||
<span id="stats-length-value"></span>
|
|
||||||
</span>
|
|
||||||
<span data-toggle="tooltip" title="Number of lines">
|
|
||||||
<i class="material-icons">sort</i>
|
|
||||||
<span id="stats-lines-value"></span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span id="sel-info" data-toggle="tooltip" title="Selection">
|
|
||||||
<i class="material-icons">highlight_alt</i>
|
|
||||||
<span id="sel-start-value"></span>\u279E<span id="sel-end-value"></span>
|
|
||||||
(<span id="sel-length-value"></span> selected)
|
|
||||||
</span>
|
|
||||||
<span id="cur-offset-info" data-toggle="tooltip" title="Cursor offset">
|
|
||||||
<i class="material-icons">location_on</i>
|
|
||||||
<span id="cur-offset-value"></span>
|
|
||||||
</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the Right-hand-side widgets
|
|
||||||
* Event listener set up in Manager
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function constructRHS() {
|
|
||||||
return `<span data-toggle="tooltip" title="Input character encoding">
|
|
||||||
<i class="material-icons">language</i>
|
|
||||||
<span id="char-enc-value">UTF-16</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="cm-status-bar-select eol-select">
|
|
||||||
<span class="cm-status-bar-select-btn" data-toggle="tooltip" data-placement="bottom" title="End of line sequence">
|
|
||||||
<i class="material-icons">keyboard_return</i> <span id="eol-value"></span>
|
|
||||||
</span>
|
|
||||||
<div class="cm-status-bar-select-content">
|
|
||||||
<a href="#" data-val="LF">Line Feed, U+000A</a>
|
|
||||||
<a href="#" data-val="VT">Vertical Tab, U+000B</a>
|
|
||||||
<a href="#" data-val="FF">Form Feed, U+000C</a>
|
|
||||||
<a href="#" data-val="CR">Carriage Return, U+000D</a>
|
|
||||||
<a href="#" data-val="CRLF">CR+LF, U+000D U+000A</a>
|
|
||||||
<!-- <a href="#" data-val="NL">Next Line, U+0085</a> This causes problems. -->
|
|
||||||
<a href="#" data-val="LS">Line Separator, U+2028</a>
|
|
||||||
<a href="#" data-val="PS">Paragraph Separator, U+2029</a>
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A panel constructor building a panel that re-counts the stats every time the document changes.
|
|
||||||
* @param {EditorView} view
|
|
||||||
* @returns {Panel}
|
|
||||||
*/
|
|
||||||
function wordCountPanel(view) {
|
|
||||||
const dom = document.createElement("div");
|
|
||||||
const lhs = document.createElement("div");
|
|
||||||
const rhs = document.createElement("div");
|
|
||||||
|
|
||||||
dom.className = "cm-status-bar";
|
|
||||||
lhs.innerHTML = constructLHS();
|
|
||||||
rhs.innerHTML = constructRHS();
|
|
||||||
|
|
||||||
dom.appendChild(lhs);
|
|
||||||
dom.appendChild(rhs);
|
|
||||||
|
|
||||||
updateEOL(rhs, view.state);
|
|
||||||
updateCharEnc(rhs, view.state);
|
|
||||||
updateStats(lhs, view.state.doc);
|
|
||||||
updateSelection(lhs, view.state, false);
|
|
||||||
|
|
||||||
return {
|
|
||||||
dom,
|
|
||||||
update(update) {
|
|
||||||
updateEOL(rhs, update.state);
|
|
||||||
updateSelection(lhs, update.state, update.selectionSet);
|
|
||||||
updateCharEnc(rhs, update.state);
|
|
||||||
if (update.docChanged) {
|
|
||||||
updateStats(lhs, update.state.doc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function that build the extension that enables the panel in an editor.
|
|
||||||
* @returns {Extension}
|
|
||||||
*/
|
|
||||||
export function statusBar() {
|
|
||||||
return showPanel.of(wordCountPanel);
|
|
||||||
}
|
|
@ -191,7 +191,7 @@
|
|||||||
<ul id="rec-list" class="list-area no-select"></ul>
|
<ul id="rec-list" class="list-area no-select"></ul>
|
||||||
|
|
||||||
<div id="controls" class="no-select hide-on-maximised-output">
|
<div id="controls" class="no-select hide-on-maximised-output">
|
||||||
<div id="controls-content" class="d-flex align-items-center">
|
<div id="controls-content">
|
||||||
<button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe">
|
<button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe">
|
||||||
Step
|
Step
|
||||||
</button>
|
</button>
|
||||||
@ -289,8 +289,6 @@
|
|||||||
<label for="output-text">Output</label>
|
<label for="output-text">Output</label>
|
||||||
<span class="pane-controls">
|
<span class="pane-controls">
|
||||||
<div class="io-info" id="bake-info"></div>
|
<div class="io-info" id="bake-info"></div>
|
||||||
<div class="io-info" id="output-selection-info"></div>
|
|
||||||
<div class="io-info" id="output-info"></div>
|
|
||||||
<button type="button" class="btn btn-primary bmd-btn-icon" id="save-all-to-file" data-toggle="tooltip" title="Save all outputs to a zip file" style="display: none">
|
<button type="button" class="btn btn-primary bmd-btn-icon" id="save-all-to-file" data-toggle="tooltip" title="Save all outputs to a zip file" style="display: none">
|
||||||
<i class="material-icons">archive</i>
|
<i class="material-icons">archive</i>
|
||||||
</button>
|
</button>
|
||||||
@ -344,8 +342,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="textarea-wrapper">
|
<div class="textarea-wrapper">
|
||||||
<div id="output-highlighter" class="no-select"></div>
|
<div id="output-highlighter" class="no-select"></div>
|
||||||
<div id="output-html"></div>
|
<div id="output-text"></div>
|
||||||
<textarea id="output-text" readonly="readonly" spellcheck="false"></textarea>
|
|
||||||
<img id="show-file-overlay" aria-hidden="true" src="<%- require('../static/images/file-32x32.png') %>" alt="Show file overlay" title="Show file overlay"/>
|
<img id="show-file-overlay" aria-hidden="true" src="<%- require('../static/images/file-32x32.png') %>" alt="Show file overlay" title="Show file overlay"/>
|
||||||
<div id="output-file">
|
<div id="output-file">
|
||||||
<div class="file-overlay"></div>
|
<div class="file-overlay"></div>
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#input-text {
|
#input-text,
|
||||||
|
#output-text {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -24,23 +25,6 @@
|
|||||||
color: var(--fixed-width-font-colour);
|
color: var(--fixed-width-font-colour);
|
||||||
}
|
}
|
||||||
|
|
||||||
#output-text,
|
|
||||||
#output-html {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 3px;
|
|
||||||
-moz-padding-start: 3px;
|
|
||||||
-moz-padding-end: 3px;
|
|
||||||
border: none;
|
|
||||||
border-width: 0px;
|
|
||||||
resize: none;
|
|
||||||
background-color: transparent;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
#output-wrapper{
|
#output-wrapper{
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -54,13 +38,6 @@
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#output-html {
|
|
||||||
display: none;
|
|
||||||
overflow-y: auto;
|
|
||||||
-moz-padding-start: 1px; /* Fixes bug in Firefox */
|
|
||||||
}
|
|
||||||
|
|
||||||
#input-tabs-wrapper #input-tabs,
|
#input-tabs-wrapper #input-tabs,
|
||||||
#output-tabs-wrapper #output-tabs {
|
#output-tabs-wrapper #output-tabs {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
@ -179,25 +156,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#input-wrapper,
|
#input-wrapper,
|
||||||
#output-wrapper,
|
#output-wrapper {
|
||||||
#input-wrapper > :not(#input-text),
|
|
||||||
#output-wrapper > .textarea-wrapper > div,
|
|
||||||
#output-wrapper > .textarea-wrapper > textarea {
|
|
||||||
height: calc(100% - var(--title-height));
|
height: calc(100% - var(--title-height));
|
||||||
}
|
}
|
||||||
|
|
||||||
#input-wrapper.show-tabs,
|
#input-wrapper.show-tabs,
|
||||||
#input-wrapper.show-tabs > :not(#input-text),
|
#output-wrapper.show-tabs {
|
||||||
#output-wrapper.show-tabs,
|
|
||||||
#output-wrapper.show-tabs > .textarea-wrapper > div,
|
|
||||||
#output-wrapper.show-tabs > .textarea-wrapper > textarea {
|
|
||||||
height: calc(100% - var(--tab-height) - var(--title-height));
|
height: calc(100% - var(--tab-height) - var(--title-height));
|
||||||
}
|
}
|
||||||
|
|
||||||
#output-wrapper > .textarea-wrapper > #output-html {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#show-file-overlay {
|
#show-file-overlay {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
@ -211,7 +178,6 @@
|
|||||||
|
|
||||||
.textarea-wrapper textarea,
|
.textarea-wrapper textarea,
|
||||||
.textarea-wrapper #output-text,
|
.textarea-wrapper #output-text,
|
||||||
.textarea-wrapper #output-html,
|
|
||||||
.textarea-wrapper #output-highlighter {
|
.textarea-wrapper #output-highlighter {
|
||||||
font-family: var(--fixed-width-font-family);
|
font-family: var(--fixed-width-font-family);
|
||||||
font-size: var(--fixed-width-font-size);
|
font-size: var(--fixed-width-font-size);
|
||||||
@ -477,6 +443,12 @@
|
|||||||
|
|
||||||
/* Status bar */
|
/* Status bar */
|
||||||
|
|
||||||
|
.ͼ2 .cm-panels {
|
||||||
|
background-color: var(--secondary-background-colour);
|
||||||
|
border-color: var(--secondary-border-colour);
|
||||||
|
color: var(--primary-font-colour);
|
||||||
|
}
|
||||||
|
|
||||||
.cm-status-bar {
|
.cm-status-bar {
|
||||||
font-family: var(--fixed-width-font-family);
|
font-family: var(--fixed-width-font-family);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
28
src/web/utils/editorUtils.mjs
Normal file
28
src/web/utils/editorUtils.mjs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* CodeMirror utilities that are relevant to both the input and output
|
||||||
|
*
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override for rendering special characters.
|
||||||
|
* Should mirror the toDOM function in
|
||||||
|
* https://github.com/codemirror/view/blob/main/src/special-chars.ts#L150
|
||||||
|
* But reverts the replacement of line feeds with newline control pictures.
|
||||||
|
* @param {number} code
|
||||||
|
* @param {string} desc
|
||||||
|
* @param {string} placeholder
|
||||||
|
* @returns {element}
|
||||||
|
*/
|
||||||
|
export function renderSpecialChar(code, desc, placeholder) {
|
||||||
|
const s = document.createElement("span");
|
||||||
|
// CodeMirror changes 0x0a to "NL" instead of "LF". We change it back.
|
||||||
|
s.textContent = code === 0x0a ? "\u240a" : placeholder;
|
||||||
|
s.title = desc;
|
||||||
|
s.setAttribute("aria-label", desc);
|
||||||
|
s.className = "cm-specialChar";
|
||||||
|
return s;
|
||||||
|
}
|
87
src/web/utils/htmlWidget.mjs
Normal file
87
src/web/utils/htmlWidget.mjs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {WidgetType, Decoration, ViewPlugin} from "@codemirror/view";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an HTML widget to the Code Mirror editor
|
||||||
|
*/
|
||||||
|
class HTMLWidget extends WidgetType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTMLWidget consructor
|
||||||
|
*/
|
||||||
|
constructor(html) {
|
||||||
|
super();
|
||||||
|
this.html = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the DOM node
|
||||||
|
* @returns {DOMNode}
|
||||||
|
*/
|
||||||
|
toDOM() {
|
||||||
|
const wrap = document.createElement("span");
|
||||||
|
wrap.setAttribute("id", "output-html");
|
||||||
|
wrap.innerHTML = this.html;
|
||||||
|
return wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorator function to provide a set of widgets for the editor DOM
|
||||||
|
* @param {EditorView} view
|
||||||
|
* @param {string} html
|
||||||
|
* @returns {DecorationSet}
|
||||||
|
*/
|
||||||
|
function decorateHTML(view, html) {
|
||||||
|
const widgets = [];
|
||||||
|
if (html.length) {
|
||||||
|
const deco = Decoration.widget({
|
||||||
|
widget: new HTMLWidget(html),
|
||||||
|
side: 1
|
||||||
|
});
|
||||||
|
widgets.push(deco.range(0));
|
||||||
|
}
|
||||||
|
return Decoration.set(widgets);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An HTML Plugin builder
|
||||||
|
* @param {Object} htmlOutput
|
||||||
|
* @returns {ViewPlugin}
|
||||||
|
*/
|
||||||
|
export function htmlPlugin(htmlOutput) {
|
||||||
|
const plugin = ViewPlugin.fromClass(
|
||||||
|
class {
|
||||||
|
/**
|
||||||
|
* Plugin constructor
|
||||||
|
* @param {EditorView} view
|
||||||
|
*/
|
||||||
|
constructor(view) {
|
||||||
|
this.htmlOutput = htmlOutput;
|
||||||
|
this.decorations = decorateHTML(view, this.htmlOutput.html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editor update listener
|
||||||
|
* @param {ViewUpdate} update
|
||||||
|
*/
|
||||||
|
update(update) {
|
||||||
|
if (this.htmlOutput.changed) {
|
||||||
|
this.decorations = decorateHTML(update.view, this.htmlOutput.html);
|
||||||
|
this.htmlOutput.changed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
decorations: v => v.decorations
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return plugin;
|
||||||
|
}
|
271
src/web/utils/statusBar.mjs
Normal file
271
src/web/utils/statusBar.mjs
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
/**
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
|
* @copyright Crown Copyright 2022
|
||||||
|
* @license Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {showPanel} from "@codemirror/view";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Status bar extension for CodeMirror
|
||||||
|
*/
|
||||||
|
class StatusBarPanel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StatusBarPanel constructor
|
||||||
|
* @param {Object} opts
|
||||||
|
*/
|
||||||
|
constructor(opts) {
|
||||||
|
this.label = opts.label;
|
||||||
|
this.bakeStats = opts.bakeStats ? opts.bakeStats : null;
|
||||||
|
this.eolHandler = opts.eolHandler;
|
||||||
|
|
||||||
|
this.dom = this.buildDOM();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the status bar DOM tree
|
||||||
|
* @returns {DOMNode}
|
||||||
|
*/
|
||||||
|
buildDOM() {
|
||||||
|
const dom = document.createElement("div");
|
||||||
|
const lhs = document.createElement("div");
|
||||||
|
const rhs = document.createElement("div");
|
||||||
|
|
||||||
|
dom.className = "cm-status-bar";
|
||||||
|
lhs.innerHTML = this.constructLHS();
|
||||||
|
rhs.innerHTML = this.constructRHS();
|
||||||
|
|
||||||
|
dom.appendChild(lhs);
|
||||||
|
dom.appendChild(rhs);
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
dom.addEventListener("click", this.eolSelectClick.bind(this), false);
|
||||||
|
|
||||||
|
return dom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for EOL Select clicks
|
||||||
|
* Sets the line separator
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
eolSelectClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const eolLookup = {
|
||||||
|
"LF": "\u000a",
|
||||||
|
"VT": "\u000b",
|
||||||
|
"FF": "\u000c",
|
||||||
|
"CR": "\u000d",
|
||||||
|
"CRLF": "\u000d\u000a",
|
||||||
|
"NEL": "\u0085",
|
||||||
|
"LS": "\u2028",
|
||||||
|
"PS": "\u2029"
|
||||||
|
};
|
||||||
|
const eolval = eolLookup[e.target.getAttribute("data-val")];
|
||||||
|
|
||||||
|
// Call relevant EOL change handler
|
||||||
|
this.eolHandler(eolval);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the stats of a document
|
||||||
|
* @param {Text} doc
|
||||||
|
*/
|
||||||
|
updateStats(doc) {
|
||||||
|
const length = this.dom.querySelector(".stats-length-value"),
|
||||||
|
lines = this.dom.querySelector(".stats-lines-value");
|
||||||
|
length.textContent = doc.length;
|
||||||
|
lines.textContent = doc.lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current selection info
|
||||||
|
* @param {EditorState} state
|
||||||
|
* @param {boolean} selectionSet
|
||||||
|
*/
|
||||||
|
updateSelection(state, selectionSet) {
|
||||||
|
const selLen = state.selection && state.selection.main ?
|
||||||
|
state.selection.main.to - state.selection.main.from :
|
||||||
|
0;
|
||||||
|
|
||||||
|
const selInfo = this.dom.querySelector(".sel-info"),
|
||||||
|
curOffsetInfo = this.dom.querySelector(".cur-offset-info");
|
||||||
|
|
||||||
|
if (!selectionSet) {
|
||||||
|
selInfo.style.display = "none";
|
||||||
|
curOffsetInfo.style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selLen > 0) { // Range
|
||||||
|
const start = this.dom.querySelector(".sel-start-value"),
|
||||||
|
end = this.dom.querySelector(".sel-end-value"),
|
||||||
|
length = this.dom.querySelector(".sel-length-value");
|
||||||
|
|
||||||
|
selInfo.style.display = "inline-block";
|
||||||
|
curOffsetInfo.style.display = "none";
|
||||||
|
|
||||||
|
start.textContent = state.selection.main.from;
|
||||||
|
end.textContent = state.selection.main.to;
|
||||||
|
length.textContent = state.selection.main.to - state.selection.main.from;
|
||||||
|
} else { // Position
|
||||||
|
const offset = this.dom.querySelector(".cur-offset-value");
|
||||||
|
|
||||||
|
selInfo.style.display = "none";
|
||||||
|
curOffsetInfo.style.display = "inline-block";
|
||||||
|
|
||||||
|
offset.textContent = state.selection.main.from;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current character encoding of the document
|
||||||
|
* @param {EditorState} state
|
||||||
|
*/
|
||||||
|
updateCharEnc(state) {
|
||||||
|
// const charenc = this.dom.querySelector("#char-enc-value");
|
||||||
|
// TODO
|
||||||
|
// charenc.textContent = "TODO";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns what the current EOL separator is set to
|
||||||
|
* @param {EditorState} state
|
||||||
|
*/
|
||||||
|
updateEOL(state) {
|
||||||
|
const eolLookup = {
|
||||||
|
"\u000a": "LF",
|
||||||
|
"\u000b": "VT",
|
||||||
|
"\u000c": "FF",
|
||||||
|
"\u000d": "CR",
|
||||||
|
"\u000d\u000a": "CRLF",
|
||||||
|
"\u0085": "NEL",
|
||||||
|
"\u2028": "LS",
|
||||||
|
"\u2029": "PS"
|
||||||
|
};
|
||||||
|
|
||||||
|
const val = this.dom.querySelector(".eol-value");
|
||||||
|
val.textContent = eolLookup[state.lineBreak];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the latest bake duration
|
||||||
|
*/
|
||||||
|
updateBakeStats() {
|
||||||
|
const bakingTime = this.dom.querySelector(".baking-time-value");
|
||||||
|
const bakingTimeInfo = this.dom.querySelector(".baking-time-info");
|
||||||
|
|
||||||
|
if (this.label === "Output" &&
|
||||||
|
this.bakeStats &&
|
||||||
|
typeof this.bakeStats.duration === "number" &&
|
||||||
|
this.bakeStats.duration >= 0) {
|
||||||
|
bakingTimeInfo.style.display = "inline-block";
|
||||||
|
bakingTime.textContent = this.bakeStats.duration;
|
||||||
|
} else {
|
||||||
|
bakingTimeInfo.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the Left-hand-side widgets
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
constructLHS() {
|
||||||
|
return `
|
||||||
|
<span data-toggle="tooltip" title="${this.label} length">
|
||||||
|
<i class="material-icons">abc</i>
|
||||||
|
<span class="stats-length-value"></span>
|
||||||
|
</span>
|
||||||
|
<span data-toggle="tooltip" title="Number of lines">
|
||||||
|
<i class="material-icons">sort</i>
|
||||||
|
<span class="stats-lines-value"></span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="sel-info" data-toggle="tooltip" title="Main selection">
|
||||||
|
<i class="material-icons">highlight_alt</i>
|
||||||
|
<span class="sel-start-value"></span>\u279E<span class="sel-end-value"></span>
|
||||||
|
(<span class="sel-length-value"></span> selected)
|
||||||
|
</span>
|
||||||
|
<span class="cur-offset-info" data-toggle="tooltip" title="Cursor offset">
|
||||||
|
<i class="material-icons">location_on</i>
|
||||||
|
<span class="cur-offset-value"></span>
|
||||||
|
</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the Right-hand-side widgets
|
||||||
|
* Event listener set up in Manager
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
constructRHS() {
|
||||||
|
return `
|
||||||
|
<span class="baking-time-info" style="display: none" data-toggle="tooltip" title="Baking time">
|
||||||
|
<i class="material-icons">schedule</i>
|
||||||
|
<span class="baking-time-value"></span>ms
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span data-toggle="tooltip" title="${this.label} character encoding">
|
||||||
|
<i class="material-icons">language</i>
|
||||||
|
<span class="char-enc-value">UTF-16</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="cm-status-bar-select eol-select">
|
||||||
|
<span class="cm-status-bar-select-btn" data-toggle="tooltip" data-placement="left" title="End of line sequence">
|
||||||
|
<i class="material-icons">keyboard_return</i> <span class="eol-value"></span>
|
||||||
|
</span>
|
||||||
|
<div class="cm-status-bar-select-content">
|
||||||
|
<a href="#" data-val="LF">Line Feed, U+000A</a>
|
||||||
|
<a href="#" data-val="VT">Vertical Tab, U+000B</a>
|
||||||
|
<a href="#" data-val="FF">Form Feed, U+000C</a>
|
||||||
|
<a href="#" data-val="CR">Carriage Return, U+000D</a>
|
||||||
|
<a href="#" data-val="CRLF">CR+LF, U+000D U+000A</a>
|
||||||
|
<!-- <a href="#" data-val="NL">Next Line, U+0085</a> This causes problems. -->
|
||||||
|
<a href="#" data-val="LS">Line Separator, U+2028</a>
|
||||||
|
<a href="#" data-val="PS">Paragraph Separator, U+2029</a>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A panel constructor factory building a panel that re-counts the stats every time the document changes.
|
||||||
|
* @param {Object} opts
|
||||||
|
* @returns {Function<PanelConstructor>}
|
||||||
|
*/
|
||||||
|
function makePanel(opts) {
|
||||||
|
const sbPanel = new StatusBarPanel(opts);
|
||||||
|
|
||||||
|
return (view) => {
|
||||||
|
sbPanel.updateEOL(view.state);
|
||||||
|
sbPanel.updateCharEnc(view.state);
|
||||||
|
sbPanel.updateBakeStats();
|
||||||
|
sbPanel.updateStats(view.state.doc);
|
||||||
|
sbPanel.updateSelection(view.state, false);
|
||||||
|
|
||||||
|
return {
|
||||||
|
"dom": sbPanel.dom,
|
||||||
|
update(update) {
|
||||||
|
sbPanel.updateEOL(update.state);
|
||||||
|
sbPanel.updateSelection(update.state, update.selectionSet);
|
||||||
|
sbPanel.updateCharEnc(update.state);
|
||||||
|
sbPanel.updateBakeStats();
|
||||||
|
if (update.docChanged) {
|
||||||
|
sbPanel.updateStats(update.state.doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that build the extension that enables the panel in an editor.
|
||||||
|
* @param {Object} opts
|
||||||
|
* @returns {Extension}
|
||||||
|
*/
|
||||||
|
export function statusBar(opts) {
|
||||||
|
const panelMaker = makePanel(opts);
|
||||||
|
return showPanel.of(panelMaker);
|
||||||
|
}
|
@ -176,34 +176,16 @@ class HighlighterWaiter {
|
|||||||
this.mouseTarget = OUTPUT;
|
this.mouseTarget = OUTPUT;
|
||||||
this.removeHighlights();
|
this.removeHighlights();
|
||||||
|
|
||||||
const el = e.target;
|
const sel = document.getSelection();
|
||||||
const start = el.selectionStart;
|
const start = sel.baseOffset;
|
||||||
const end = el.selectionEnd;
|
const end = sel.extentOffset;
|
||||||
|
|
||||||
if (start !== 0 || end !== 0) {
|
if (start !== 0 || end !== 0) {
|
||||||
document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end);
|
|
||||||
this.highlightInput([{start: start, end: end}]);
|
this.highlightInput([{start: start, end: end}]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for output HTML mousedown events.
|
|
||||||
* Calculates the current selection info.
|
|
||||||
*
|
|
||||||
* @param {event} e
|
|
||||||
*/
|
|
||||||
outputHtmlMousedown(e) {
|
|
||||||
this.mouseButtonDown = true;
|
|
||||||
this.mouseTarget = OUTPUT;
|
|
||||||
|
|
||||||
const sel = this._getOutputHtmlSelectionOffsets();
|
|
||||||
if (sel.start !== 0 || sel.end !== 0) {
|
|
||||||
document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for input mouseup events.
|
* Handler for input mouseup events.
|
||||||
*
|
*
|
||||||
@ -224,16 +206,6 @@ class HighlighterWaiter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for output HTML mouseup events.
|
|
||||||
*
|
|
||||||
* @param {event} e
|
|
||||||
*/
|
|
||||||
outputHtmlMouseup(e) {
|
|
||||||
this.mouseButtonDown = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for input mousemove events.
|
* Handler for input mousemove events.
|
||||||
* Calculates the current selection info, and highlights the corresponding data in the output.
|
* Calculates the current selection info, and highlights the corresponding data in the output.
|
||||||
@ -270,37 +242,16 @@ class HighlighterWaiter {
|
|||||||
this.mouseTarget !== OUTPUT)
|
this.mouseTarget !== OUTPUT)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const el = e.target;
|
const sel = document.getSelection();
|
||||||
const start = el.selectionStart;
|
const start = sel.baseOffset;
|
||||||
const end = el.selectionEnd;
|
const end = sel.extentOffset;
|
||||||
|
|
||||||
if (start !== 0 || end !== 0) {
|
if (start !== 0 || end !== 0) {
|
||||||
document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end);
|
|
||||||
this.highlightInput([{start: start, end: end}]);
|
this.highlightInput([{start: start, end: end}]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for output HTML mousemove events.
|
|
||||||
* Calculates the current selection info.
|
|
||||||
*
|
|
||||||
* @param {event} e
|
|
||||||
*/
|
|
||||||
outputHtmlMousemove(e) {
|
|
||||||
// Check that the left mouse button is pressed
|
|
||||||
if (!this.mouseButtonDown ||
|
|
||||||
e.which !== 1 ||
|
|
||||||
this.mouseTarget !== OUTPUT)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const sel = this._getOutputHtmlSelectionOffsets();
|
|
||||||
if (sel.start !== 0 || sel.end !== 0) {
|
|
||||||
document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given start and end offsets, writes the HTML for the selection info element with the correct
|
* Given start and end offsets, writes the HTML for the selection info element with the correct
|
||||||
* padding.
|
* padding.
|
||||||
@ -326,7 +277,6 @@ class HighlighterWaiter {
|
|||||||
removeHighlights() {
|
removeHighlights() {
|
||||||
document.getElementById("input-highlighter").innerHTML = "";
|
document.getElementById("input-highlighter").innerHTML = "";
|
||||||
document.getElementById("output-highlighter").innerHTML = "";
|
document.getElementById("output-highlighter").innerHTML = "";
|
||||||
document.getElementById("output-selection-info").innerHTML = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -379,7 +329,8 @@ class HighlighterWaiter {
|
|||||||
|
|
||||||
const io = direction === "forward" ? "output" : "input";
|
const io = direction === "forward" ? "output" : "input";
|
||||||
|
|
||||||
document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end);
|
// TODO
|
||||||
|
// document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end);
|
||||||
this.highlight(
|
this.highlight(
|
||||||
document.getElementById(io + "-text"),
|
document.getElementById(io + "-text"),
|
||||||
document.getElementById(io + "-highlighter"),
|
document.getElementById(io + "-highlighter"),
|
||||||
@ -398,67 +349,67 @@ class HighlighterWaiter {
|
|||||||
* @param {number} pos.end - The end offset.
|
* @param {number} pos.end - The end offset.
|
||||||
*/
|
*/
|
||||||
async highlight(textarea, highlighter, pos) {
|
async highlight(textarea, highlighter, pos) {
|
||||||
if (!this.app.options.showHighlighter) return false;
|
// if (!this.app.options.showHighlighter) return false;
|
||||||
if (!this.app.options.attemptHighlight) return false;
|
// if (!this.app.options.attemptHighlight) return false;
|
||||||
|
|
||||||
// Check if there is a carriage return in the output dish as this will not
|
// // Check if there is a carriage return in the output dish as this will not
|
||||||
// be displayed by the HTML textarea and will mess up highlighting offsets.
|
// // be displayed by the HTML textarea and will mess up highlighting offsets.
|
||||||
if (await this.manager.output.containsCR()) return false;
|
// if (await this.manager.output.containsCR()) return false;
|
||||||
|
|
||||||
const startPlaceholder = "[startHighlight]";
|
// const startPlaceholder = "[startHighlight]";
|
||||||
const startPlaceholderRegex = /\[startHighlight\]/g;
|
// const startPlaceholderRegex = /\[startHighlight\]/g;
|
||||||
const endPlaceholder = "[endHighlight]";
|
// const endPlaceholder = "[endHighlight]";
|
||||||
const endPlaceholderRegex = /\[endHighlight\]/g;
|
// const endPlaceholderRegex = /\[endHighlight\]/g;
|
||||||
let text = textarea.value;
|
// // let text = textarea.value; // TODO
|
||||||
|
|
||||||
// Put placeholders in position
|
// // Put placeholders in position
|
||||||
// If there's only one value, select that
|
// // If there's only one value, select that
|
||||||
// If there are multiple, ignore the first one and select all others
|
// // If there are multiple, ignore the first one and select all others
|
||||||
if (pos.length === 1) {
|
// if (pos.length === 1) {
|
||||||
if (pos[0].end < pos[0].start) return;
|
// if (pos[0].end < pos[0].start) return;
|
||||||
text = text.slice(0, pos[0].start) +
|
// text = text.slice(0, pos[0].start) +
|
||||||
startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder +
|
// startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder +
|
||||||
text.slice(pos[0].end, text.length);
|
// text.slice(pos[0].end, text.length);
|
||||||
} else {
|
// } else {
|
||||||
// O(n^2) - Can anyone improve this without overwriting placeholders?
|
// // O(n^2) - Can anyone improve this without overwriting placeholders?
|
||||||
let result = "",
|
// let result = "",
|
||||||
endPlaced = true;
|
// endPlaced = true;
|
||||||
|
|
||||||
for (let i = 0; i < text.length; i++) {
|
// for (let i = 0; i < text.length; i++) {
|
||||||
for (let j = 1; j < pos.length; j++) {
|
// for (let j = 1; j < pos.length; j++) {
|
||||||
if (pos[j].end < pos[j].start) continue;
|
// if (pos[j].end < pos[j].start) continue;
|
||||||
if (pos[j].start === i) {
|
// if (pos[j].start === i) {
|
||||||
result += startPlaceholder;
|
// result += startPlaceholder;
|
||||||
endPlaced = false;
|
// endPlaced = false;
|
||||||
}
|
// }
|
||||||
if (pos[j].end === i) {
|
// if (pos[j].end === i) {
|
||||||
result += endPlaceholder;
|
// result += endPlaceholder;
|
||||||
endPlaced = true;
|
// endPlaced = true;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
result += text[i];
|
// result += text[i];
|
||||||
}
|
// }
|
||||||
if (!endPlaced) result += endPlaceholder;
|
// if (!endPlaced) result += endPlaceholder;
|
||||||
text = result;
|
// text = result;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const cssClass = "hl1";
|
// const cssClass = "hl1";
|
||||||
|
|
||||||
// Remove HTML tags
|
// // Remove HTML tags
|
||||||
text = text
|
// text = text
|
||||||
.replace(/&/g, "&")
|
// .replace(/&/g, "&")
|
||||||
.replace(/</g, "<")
|
// .replace(/</g, "<")
|
||||||
.replace(/>/g, ">")
|
// .replace(/>/g, ">")
|
||||||
.replace(/\n/g, " ")
|
// .replace(/\n/g, " ")
|
||||||
// Convert placeholders to tags
|
// // Convert placeholders to tags
|
||||||
.replace(startPlaceholderRegex, "<span class=\""+cssClass+"\">")
|
// .replace(startPlaceholderRegex, "<span class=\""+cssClass+"\">")
|
||||||
.replace(endPlaceholderRegex, "</span>") + " ";
|
// .replace(endPlaceholderRegex, "</span>") + " ";
|
||||||
|
|
||||||
// Adjust width to allow for scrollbars
|
// // Adjust width to allow for scrollbars
|
||||||
highlighter.style.width = textarea.clientWidth + "px";
|
// highlighter.style.width = textarea.clientWidth + "px";
|
||||||
highlighter.innerHTML = text;
|
// highlighter.innerHTML = text;
|
||||||
highlighter.scrollTop = textarea.scrollTop;
|
// highlighter.scrollTop = textarea.scrollTop;
|
||||||
highlighter.scrollLeft = textarea.scrollLeft;
|
// highlighter.scrollLeft = textarea.scrollLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,8 @@ import {defaultKeymap, insertTab, insertNewline, history, historyKeymap} from "@
|
|||||||
import {bracketMatching} from "@codemirror/language";
|
import {bracketMatching} from "@codemirror/language";
|
||||||
import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search";
|
import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search";
|
||||||
|
|
||||||
import {statusBar} from "../extensions/statusBar.mjs";
|
import {statusBar} from "../utils/statusBar.mjs";
|
||||||
|
import {renderSpecialChar} from "../utils/editorUtils.mjs";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -87,14 +88,17 @@ class InputWaiter {
|
|||||||
doc: null,
|
doc: null,
|
||||||
extensions: [
|
extensions: [
|
||||||
history(),
|
history(),
|
||||||
highlightSpecialChars({render: this.renderSpecialChar}),
|
highlightSpecialChars({render: renderSpecialChar}),
|
||||||
drawSelection(),
|
drawSelection(),
|
||||||
rectangularSelection(),
|
rectangularSelection(),
|
||||||
crosshairCursor(),
|
crosshairCursor(),
|
||||||
bracketMatching(),
|
bracketMatching(),
|
||||||
highlightSelectionMatches(),
|
highlightSelectionMatches(),
|
||||||
search({top: true}),
|
search({top: true}),
|
||||||
statusBar(this.inputEditorConf),
|
statusBar({
|
||||||
|
label: "Input",
|
||||||
|
eolHandler: this.eolChange.bind(this)
|
||||||
|
}),
|
||||||
this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping),
|
this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping),
|
||||||
this.inputEditorConf.eol.of(EditorState.lineSeparator.of("\n")),
|
this.inputEditorConf.eol.of(EditorState.lineSeparator.of("\n")),
|
||||||
EditorState.allowMultipleSelections.of(true),
|
EditorState.allowMultipleSelections.of(true),
|
||||||
@ -118,44 +122,10 @@ class InputWaiter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override for rendering special characters.
|
* Handler for EOL change events
|
||||||
* Should mirror the toDOM function in
|
|
||||||
* https://github.com/codemirror/view/blob/main/src/special-chars.ts#L150
|
|
||||||
* But reverts the replacement of line feeds with newline control pictures.
|
|
||||||
* @param {number} code
|
|
||||||
* @param {string} desc
|
|
||||||
* @param {string} placeholder
|
|
||||||
* @returns {element}
|
|
||||||
*/
|
|
||||||
renderSpecialChar(code, desc, placeholder) {
|
|
||||||
const s = document.createElement("span");
|
|
||||||
// CodeMirror changes 0x0a to "NL" instead of "LF". We change it back.
|
|
||||||
s.textContent = code === 0x0a ? "\u240a" : placeholder;
|
|
||||||
s.title = desc;
|
|
||||||
s.setAttribute("aria-label", desc);
|
|
||||||
s.className = "cm-specialChar";
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for EOL Select clicks
|
|
||||||
* Sets the line separator
|
* Sets the line separator
|
||||||
* @param {Event} e
|
|
||||||
*/
|
*/
|
||||||
eolSelectClick(e) {
|
eolChange(eolval) {
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const eolLookup = {
|
|
||||||
"LF": "\u000a",
|
|
||||||
"VT": "\u000b",
|
|
||||||
"FF": "\u000c",
|
|
||||||
"CR": "\u000d",
|
|
||||||
"CRLF": "\u000d\u000a",
|
|
||||||
"NEL": "\u0085",
|
|
||||||
"LS": "\u2028",
|
|
||||||
"PS": "\u2029"
|
|
||||||
};
|
|
||||||
const eolval = eolLookup[e.target.getAttribute("data-val")];
|
|
||||||
const oldInputVal = this.getInput();
|
const oldInputVal = this.getInput();
|
||||||
|
|
||||||
// Update the EOL value
|
// Update the EOL value
|
||||||
|
@ -140,14 +140,11 @@ class OptionsWaiter {
|
|||||||
*/
|
*/
|
||||||
setWordWrap() {
|
setWordWrap() {
|
||||||
this.manager.input.setWordWrap(this.app.options.wordWrap);
|
this.manager.input.setWordWrap(this.app.options.wordWrap);
|
||||||
document.getElementById("output-text").classList.remove("word-wrap");
|
this.manager.output.setWordWrap(this.app.options.wordWrap);
|
||||||
document.getElementById("output-html").classList.remove("word-wrap");
|
|
||||||
document.getElementById("input-highlighter").classList.remove("word-wrap");
|
document.getElementById("input-highlighter").classList.remove("word-wrap");
|
||||||
document.getElementById("output-highlighter").classList.remove("word-wrap");
|
document.getElementById("output-highlighter").classList.remove("word-wrap");
|
||||||
|
|
||||||
if (!this.app.options.wordWrap) {
|
if (!this.app.options.wordWrap) {
|
||||||
document.getElementById("output-text").classList.add("word-wrap");
|
|
||||||
document.getElementById("output-html").classList.add("word-wrap");
|
|
||||||
document.getElementById("input-highlighter").classList.add("word-wrap");
|
document.getElementById("input-highlighter").classList.add("word-wrap");
|
||||||
document.getElementById("output-highlighter").classList.add("word-wrap");
|
document.getElementById("output-highlighter").classList.add("word-wrap");
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,18 @@ import Dish from "../../core/Dish.mjs";
|
|||||||
import FileSaver from "file-saver";
|
import FileSaver from "file-saver";
|
||||||
import ZipWorker from "worker-loader?inline=no-fallback!../workers/ZipWorker.mjs";
|
import ZipWorker from "worker-loader?inline=no-fallback!../workers/ZipWorker.mjs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor
|
||||||
|
} from "@codemirror/view";
|
||||||
|
import {EditorState, Compartment} from "@codemirror/state";
|
||||||
|
import {defaultKeymap} from "@codemirror/commands";
|
||||||
|
import {bracketMatching} from "@codemirror/language";
|
||||||
|
import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search";
|
||||||
|
|
||||||
|
import {statusBar} from "../utils/statusBar.mjs";
|
||||||
|
import {renderSpecialChar} from "../utils/editorUtils.mjs";
|
||||||
|
import {htmlPlugin} from "../utils/htmlWidget.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waiter to handle events related to the output
|
* Waiter to handle events related to the output
|
||||||
*/
|
*/
|
||||||
@ -25,12 +37,155 @@ class OutputWaiter {
|
|||||||
this.app = app;
|
this.app = app;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
|
|
||||||
|
this.outputTextEl = document.getElementById("output-text");
|
||||||
|
// Object to contain bake statistics - used by statusBar extension
|
||||||
|
this.bakeStats = {
|
||||||
|
duration: 0
|
||||||
|
};
|
||||||
|
// Object to handle output HTML state - used by htmlWidget extension
|
||||||
|
this.htmlOutput = {
|
||||||
|
html: "",
|
||||||
|
changed: false
|
||||||
|
};
|
||||||
|
this.initEditor();
|
||||||
|
|
||||||
this.outputs = {};
|
this.outputs = {};
|
||||||
this.zipWorker = null;
|
this.zipWorker = null;
|
||||||
this.maxTabs = this.manager.tabs.calcMaxTabs();
|
this.maxTabs = this.manager.tabs.calcMaxTabs();
|
||||||
this.tabTimeout = null;
|
this.tabTimeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the CodeMirror Editor and returns the view
|
||||||
|
*/
|
||||||
|
initEditor() {
|
||||||
|
this.outputEditorConf = {
|
||||||
|
eol: new Compartment,
|
||||||
|
lineWrapping: new Compartment
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialState = EditorState.create({
|
||||||
|
doc: null,
|
||||||
|
extensions: [
|
||||||
|
EditorState.readOnly.of(true),
|
||||||
|
htmlPlugin(this.htmlOutput),
|
||||||
|
highlightSpecialChars({render: renderSpecialChar}),
|
||||||
|
drawSelection(),
|
||||||
|
rectangularSelection(),
|
||||||
|
crosshairCursor(),
|
||||||
|
bracketMatching(),
|
||||||
|
highlightSelectionMatches(),
|
||||||
|
search({top: true}),
|
||||||
|
statusBar({
|
||||||
|
label: "Output",
|
||||||
|
bakeStats: this.bakeStats,
|
||||||
|
eolHandler: this.eolChange.bind(this)
|
||||||
|
}),
|
||||||
|
this.outputEditorConf.lineWrapping.of(EditorView.lineWrapping),
|
||||||
|
this.outputEditorConf.eol.of(EditorState.lineSeparator.of("\n")),
|
||||||
|
EditorState.allowMultipleSelections.of(true),
|
||||||
|
keymap.of([
|
||||||
|
...defaultKeymap,
|
||||||
|
...searchKeymap
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.outputEditorView = new EditorView({
|
||||||
|
state: initialState,
|
||||||
|
parent: this.outputTextEl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for EOL change events
|
||||||
|
* Sets the line separator
|
||||||
|
*/
|
||||||
|
eolChange(eolval) {
|
||||||
|
const oldOutputVal = this.getOutput();
|
||||||
|
|
||||||
|
// Update the EOL value
|
||||||
|
this.outputEditorView.dispatch({
|
||||||
|
effects: this.outputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolval))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset the output so that lines are recalculated, preserving the old EOL values
|
||||||
|
this.setOutput(oldOutputVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets word wrap on the output editor
|
||||||
|
* @param {boolean} wrap
|
||||||
|
*/
|
||||||
|
setWordWrap(wrap) {
|
||||||
|
this.outputEditorView.dispatch({
|
||||||
|
effects: this.outputEditorConf.lineWrapping.reconfigure(
|
||||||
|
wrap ? EditorView.lineWrapping : []
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of the current output
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getOutput() {
|
||||||
|
const doc = this.outputEditorView.state.doc;
|
||||||
|
const eol = this.outputEditorView.state.lineBreak;
|
||||||
|
return doc.sliceString(0, doc.length, eol);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of the current output
|
||||||
|
* @param {string} data
|
||||||
|
*/
|
||||||
|
setOutput(data) {
|
||||||
|
this.outputEditorView.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
to: this.outputEditorView.state.doc.length,
|
||||||
|
insert: data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of the current output to a rendered HTML value
|
||||||
|
* @param {string} html
|
||||||
|
*/
|
||||||
|
setHTMLOutput(html) {
|
||||||
|
this.htmlOutput.html = html;
|
||||||
|
this.htmlOutput.changed = true;
|
||||||
|
// This clears the text output, but also fires a View update which
|
||||||
|
// triggers the htmlWidget to render the HTML.
|
||||||
|
this.setOutput("");
|
||||||
|
|
||||||
|
// Execute script sections
|
||||||
|
const scriptElements = document.getElementById("output-html").querySelectorAll("script");
|
||||||
|
for (let i = 0; i < scriptElements.length; i++) {
|
||||||
|
try {
|
||||||
|
eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the HTML output
|
||||||
|
*/
|
||||||
|
clearHTMLOutput() {
|
||||||
|
this.htmlOutput.html = "";
|
||||||
|
this.htmlOutput.changed = true;
|
||||||
|
// Fire a blank change to force the htmlWidget to update and remove any HTML
|
||||||
|
this.outputEditorView.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: 0,
|
||||||
|
insert: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the maximum number of tabs to display
|
* Calculates the maximum number of tabs to display
|
||||||
*/
|
*/
|
||||||
@ -245,8 +400,6 @@ class OutputWaiter {
|
|||||||
activeTab = this.manager.tabs.getActiveOutputTab();
|
activeTab = this.manager.tabs.getActiveOutputTab();
|
||||||
if (typeof inputNum !== "number") inputNum = parseInt(inputNum, 10);
|
if (typeof inputNum !== "number") inputNum = parseInt(inputNum, 10);
|
||||||
|
|
||||||
const outputText = document.getElementById("output-text");
|
|
||||||
const outputHtml = document.getElementById("output-html");
|
|
||||||
const outputFile = document.getElementById("output-file");
|
const outputFile = document.getElementById("output-file");
|
||||||
const outputHighlighter = document.getElementById("output-highlighter");
|
const outputHighlighter = document.getElementById("output-highlighter");
|
||||||
const inputHighlighter = document.getElementById("input-highlighter");
|
const inputHighlighter = document.getElementById("input-highlighter");
|
||||||
@ -278,95 +431,68 @@ class OutputWaiter {
|
|||||||
} else if (output.status === "error") {
|
} else if (output.status === "error") {
|
||||||
// style the tab if it's being shown
|
// style the tab if it's being shown
|
||||||
this.toggleLoader(false);
|
this.toggleLoader(false);
|
||||||
outputText.style.display = "block";
|
this.outputTextEl.style.display = "block";
|
||||||
outputText.classList.remove("blur");
|
this.outputTextEl.classList.remove("blur");
|
||||||
outputHtml.style.display = "none";
|
|
||||||
outputFile.style.display = "none";
|
outputFile.style.display = "none";
|
||||||
outputHighlighter.display = "none";
|
outputHighlighter.display = "none";
|
||||||
inputHighlighter.display = "none";
|
inputHighlighter.display = "none";
|
||||||
|
this.clearHTMLOutput();
|
||||||
|
|
||||||
if (output.error) {
|
if (output.error) {
|
||||||
outputText.value = output.error;
|
this.setOutput(output.error);
|
||||||
} else {
|
} else {
|
||||||
outputText.value = output.data.result;
|
this.setOutput(output.data.result);
|
||||||
}
|
}
|
||||||
outputHtml.innerHTML = "";
|
|
||||||
} else if (output.status === "baked" || output.status === "inactive") {
|
} else if (output.status === "baked" || output.status === "inactive") {
|
||||||
document.querySelector("#output-loader .loading-msg").textContent = `Loading output ${inputNum}`;
|
document.querySelector("#output-loader .loading-msg").textContent = `Loading output ${inputNum}`;
|
||||||
this.closeFile();
|
this.closeFile();
|
||||||
let scriptElements, lines, length;
|
|
||||||
|
|
||||||
if (output.data === null) {
|
if (output.data === null) {
|
||||||
outputText.style.display = "block";
|
this.outputTextEl.style.display = "block";
|
||||||
outputHtml.style.display = "none";
|
|
||||||
outputFile.style.display = "none";
|
outputFile.style.display = "none";
|
||||||
outputHighlighter.display = "block";
|
outputHighlighter.display = "block";
|
||||||
inputHighlighter.display = "block";
|
inputHighlighter.display = "block";
|
||||||
|
|
||||||
outputText.value = "";
|
this.clearHTMLOutput();
|
||||||
outputHtml.innerHTML = "";
|
this.setOutput("");
|
||||||
|
|
||||||
this.toggleLoader(false);
|
this.toggleLoader(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.bakeStats.duration = output.data.duration;
|
||||||
|
|
||||||
switch (output.data.type) {
|
switch (output.data.type) {
|
||||||
case "html":
|
case "html":
|
||||||
outputText.style.display = "none";
|
|
||||||
outputHtml.style.display = "block";
|
|
||||||
outputFile.style.display = "none";
|
outputFile.style.display = "none";
|
||||||
outputHighlighter.style.display = "none";
|
outputHighlighter.style.display = "none";
|
||||||
inputHighlighter.style.display = "none";
|
inputHighlighter.style.display = "none";
|
||||||
|
|
||||||
outputText.value = "";
|
this.setHTMLOutput(output.data.result);
|
||||||
outputHtml.innerHTML = output.data.result;
|
|
||||||
|
|
||||||
// Execute script sections
|
|
||||||
scriptElements = outputHtml.querySelectorAll("script");
|
|
||||||
for (let i = 0; i < scriptElements.length; i++) {
|
|
||||||
try {
|
|
||||||
eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval
|
|
||||||
} catch (err) {
|
|
||||||
log.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "ArrayBuffer":
|
case "ArrayBuffer":
|
||||||
outputText.style.display = "block";
|
this.outputTextEl.style.display = "block";
|
||||||
outputHtml.style.display = "none";
|
|
||||||
outputHighlighter.display = "none";
|
outputHighlighter.display = "none";
|
||||||
inputHighlighter.display = "none";
|
inputHighlighter.display = "none";
|
||||||
|
|
||||||
outputText.value = "";
|
this.clearHTMLOutput();
|
||||||
outputHtml.innerHTML = "";
|
this.setOutput("");
|
||||||
|
|
||||||
length = output.data.result.byteLength;
|
|
||||||
this.setFile(await this.getDishBuffer(output.data.dish), activeTab);
|
this.setFile(await this.getDishBuffer(output.data.dish), activeTab);
|
||||||
break;
|
break;
|
||||||
case "string":
|
case "string":
|
||||||
default:
|
default:
|
||||||
outputText.style.display = "block";
|
this.outputTextEl.style.display = "block";
|
||||||
outputHtml.style.display = "none";
|
|
||||||
outputFile.style.display = "none";
|
outputFile.style.display = "none";
|
||||||
outputHighlighter.display = "block";
|
outputHighlighter.display = "block";
|
||||||
inputHighlighter.display = "block";
|
inputHighlighter.display = "block";
|
||||||
|
|
||||||
outputText.value = Utils.printable(output.data.result, true);
|
this.clearHTMLOutput();
|
||||||
outputHtml.innerHTML = "";
|
this.setOutput(output.data.result);
|
||||||
|
|
||||||
lines = output.data.result.count("\n") + 1;
|
|
||||||
length = output.data.result.length;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.toggleLoader(false);
|
this.toggleLoader(false);
|
||||||
|
|
||||||
if (output.data.type === "html") {
|
|
||||||
const dishStr = await this.getDishStr(output.data.dish);
|
|
||||||
length = dishStr.length;
|
|
||||||
lines = dishStr.count("\n") + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setOutputInfo(length, lines, output.data.duration);
|
|
||||||
debounce(this.backgroundMagic, 50, "backgroundMagic", this, [])();
|
debounce(this.backgroundMagic, 50, "backgroundMagic", this, [])();
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
@ -383,14 +509,13 @@ class OutputWaiter {
|
|||||||
// Display file overlay in output area with details
|
// Display file overlay in output area with details
|
||||||
const fileOverlay = document.getElementById("output-file"),
|
const fileOverlay = document.getElementById("output-file"),
|
||||||
fileSize = document.getElementById("output-file-size"),
|
fileSize = document.getElementById("output-file-size"),
|
||||||
outputText = document.getElementById("output-text"),
|
|
||||||
fileSlice = buf.slice(0, 4096);
|
fileSlice = buf.slice(0, 4096);
|
||||||
|
|
||||||
fileOverlay.style.display = "block";
|
fileOverlay.style.display = "block";
|
||||||
fileSize.textContent = buf.byteLength.toLocaleString() + " bytes";
|
fileSize.textContent = buf.byteLength.toLocaleString() + " bytes";
|
||||||
|
|
||||||
outputText.classList.add("blur");
|
this.outputTextEl.classList.add("blur");
|
||||||
outputText.value = Utils.printable(Utils.arrayBufferToStr(fileSlice));
|
this.setOutput(Utils.arrayBufferToStr(fileSlice));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -398,7 +523,7 @@ class OutputWaiter {
|
|||||||
*/
|
*/
|
||||||
closeFile() {
|
closeFile() {
|
||||||
document.getElementById("output-file").style.display = "none";
|
document.getElementById("output-file").style.display = "none";
|
||||||
document.getElementById("output-text").classList.remove("blur");
|
this.outputTextEl.classList.remove("blur");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -466,7 +591,6 @@ class OutputWaiter {
|
|||||||
clearTimeout(this.outputLoaderTimeout);
|
clearTimeout(this.outputLoaderTimeout);
|
||||||
|
|
||||||
const outputLoader = document.getElementById("output-loader"),
|
const outputLoader = document.getElementById("output-loader"),
|
||||||
outputElement = document.getElementById("output-text"),
|
|
||||||
animation = document.getElementById("output-loader-animation");
|
animation = document.getElementById("output-loader-animation");
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
@ -483,7 +607,6 @@ class OutputWaiter {
|
|||||||
|
|
||||||
// Show the loading screen
|
// Show the loading screen
|
||||||
this.outputLoaderTimeout = setTimeout(function() {
|
this.outputLoaderTimeout = setTimeout(function() {
|
||||||
outputElement.disabled = true;
|
|
||||||
outputLoader.style.visibility = "visible";
|
outputLoader.style.visibility = "visible";
|
||||||
outputLoader.style.opacity = 1;
|
outputLoader.style.opacity = 1;
|
||||||
}, 200);
|
}, 200);
|
||||||
@ -494,7 +617,6 @@ class OutputWaiter {
|
|||||||
animation.removeChild(this.bombeEl);
|
animation.removeChild(this.bombeEl);
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
}.bind(this), 500);
|
}.bind(this), 500);
|
||||||
outputElement.disabled = false;
|
|
||||||
outputLoader.style.opacity = 0;
|
outputLoader.style.opacity = 0;
|
||||||
outputLoader.style.visibility = "hidden";
|
outputLoader.style.visibility = "hidden";
|
||||||
}
|
}
|
||||||
@ -717,8 +839,7 @@ class OutputWaiter {
|
|||||||
|
|
||||||
debounce(this.set, 50, "setOutput", this, [inputNum])();
|
debounce(this.set, 50, "setOutput", this, [inputNum])();
|
||||||
|
|
||||||
document.getElementById("output-html").scroll(0, 0);
|
this.outputTextEl.scroll(0, 0); // TODO
|
||||||
document.getElementById("output-text").scroll(0, 0);
|
|
||||||
|
|
||||||
if (changeInput) {
|
if (changeInput) {
|
||||||
this.manager.input.changeTab(inputNum, false);
|
this.manager.input.changeTab(inputNum, false);
|
||||||
@ -996,32 +1117,6 @@ class OutputWaiter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays information about the output.
|
|
||||||
*
|
|
||||||
* @param {number} length - The length of the current output string
|
|
||||||
* @param {number} lines - The number of the lines in the current output string
|
|
||||||
* @param {number} duration - The length of time (ms) it took to generate the output
|
|
||||||
*/
|
|
||||||
setOutputInfo(length, lines, duration) {
|
|
||||||
if (!length) return;
|
|
||||||
let width = length.toString().length;
|
|
||||||
width = width < 4 ? 4 : width;
|
|
||||||
|
|
||||||
const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " ");
|
|
||||||
const timeStr = (duration.toString() + "ms").padStart(width, " ").replace(/ /g, " ");
|
|
||||||
|
|
||||||
let msg = "time: " + timeStr + "<br>length: " + lengthStr;
|
|
||||||
|
|
||||||
if (typeof lines === "number") {
|
|
||||||
const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " ");
|
|
||||||
msg += "<br>lines: " + linesStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("output-info").innerHTML = msg;
|
|
||||||
document.getElementById("output-selection-info").innerHTML = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers the BackgroundWorker to attempt Magic on the current output.
|
* Triggers the BackgroundWorker to attempt Magic on the current output.
|
||||||
*/
|
*/
|
||||||
@ -1111,9 +1206,7 @@ class OutputWaiter {
|
|||||||
async displayFileSlice() {
|
async displayFileSlice() {
|
||||||
document.querySelector("#output-loader .loading-msg").textContent = "Loading file slice...";
|
document.querySelector("#output-loader .loading-msg").textContent = "Loading file slice...";
|
||||||
this.toggleLoader(true);
|
this.toggleLoader(true);
|
||||||
const outputText = document.getElementById("output-text"),
|
const outputFile = document.getElementById("output-file"),
|
||||||
outputHtml = document.getElementById("output-html"),
|
|
||||||
outputFile = document.getElementById("output-file"),
|
|
||||||
outputHighlighter = document.getElementById("output-highlighter"),
|
outputHighlighter = document.getElementById("output-highlighter"),
|
||||||
inputHighlighter = document.getElementById("input-highlighter"),
|
inputHighlighter = document.getElementById("input-highlighter"),
|
||||||
showFileOverlay = document.getElementById("show-file-overlay"),
|
showFileOverlay = document.getElementById("show-file-overlay"),
|
||||||
@ -1130,12 +1223,12 @@ class OutputWaiter {
|
|||||||
str = Utils.arrayBufferToStr(await this.getDishBuffer(output.dish).slice(sliceFrom, sliceTo));
|
str = Utils.arrayBufferToStr(await this.getDishBuffer(output.dish).slice(sliceFrom, sliceTo));
|
||||||
}
|
}
|
||||||
|
|
||||||
outputText.classList.remove("blur");
|
this.outputTextEl.classList.remove("blur");
|
||||||
showFileOverlay.style.display = "block";
|
showFileOverlay.style.display = "block";
|
||||||
outputText.value = Utils.printable(str, true);
|
this.clearHTMLOutput();
|
||||||
|
this.setOutput(str);
|
||||||
|
|
||||||
outputText.style.display = "block";
|
this.outputTextEl.style.display = "block";
|
||||||
outputHtml.style.display = "none";
|
|
||||||
outputFile.style.display = "none";
|
outputFile.style.display = "none";
|
||||||
outputHighlighter.display = "block";
|
outputHighlighter.display = "block";
|
||||||
inputHighlighter.display = "block";
|
inputHighlighter.display = "block";
|
||||||
@ -1149,9 +1242,7 @@ class OutputWaiter {
|
|||||||
async showAllFile() {
|
async showAllFile() {
|
||||||
document.querySelector("#output-loader .loading-msg").textContent = "Loading entire file at user instruction. This may cause a crash...";
|
document.querySelector("#output-loader .loading-msg").textContent = "Loading entire file at user instruction. This may cause a crash...";
|
||||||
this.toggleLoader(true);
|
this.toggleLoader(true);
|
||||||
const outputText = document.getElementById("output-text"),
|
const outputFile = document.getElementById("output-file"),
|
||||||
outputHtml = document.getElementById("output-html"),
|
|
||||||
outputFile = document.getElementById("output-file"),
|
|
||||||
outputHighlighter = document.getElementById("output-highlighter"),
|
outputHighlighter = document.getElementById("output-highlighter"),
|
||||||
inputHighlighter = document.getElementById("input-highlighter"),
|
inputHighlighter = document.getElementById("input-highlighter"),
|
||||||
showFileOverlay = document.getElementById("show-file-overlay"),
|
showFileOverlay = document.getElementById("show-file-overlay"),
|
||||||
@ -1164,12 +1255,12 @@ class OutputWaiter {
|
|||||||
str = Utils.arrayBufferToStr(await this.getDishBuffer(output.dish));
|
str = Utils.arrayBufferToStr(await this.getDishBuffer(output.dish));
|
||||||
}
|
}
|
||||||
|
|
||||||
outputText.classList.remove("blur");
|
this.outputTextEl.classList.remove("blur");
|
||||||
showFileOverlay.style.display = "none";
|
showFileOverlay.style.display = "none";
|
||||||
outputText.value = Utils.printable(str, true);
|
this.clearHTMLOutput();
|
||||||
|
this.setOutput(str);
|
||||||
|
|
||||||
outputText.style.display = "block";
|
this.outputTextEl.style.display = "block";
|
||||||
outputHtml.style.display = "none";
|
|
||||||
outputFile.style.display = "none";
|
outputFile.style.display = "none";
|
||||||
outputHighlighter.display = "block";
|
outputHighlighter.display = "block";
|
||||||
inputHighlighter.display = "block";
|
inputHighlighter.display = "block";
|
||||||
@ -1185,7 +1276,7 @@ class OutputWaiter {
|
|||||||
showFileOverlayClick(e) {
|
showFileOverlayClick(e) {
|
||||||
const showFileOverlay = e.target;
|
const showFileOverlay = e.target;
|
||||||
|
|
||||||
document.getElementById("output-text").classList.add("blur");
|
this.outputTextEl.classList.add("blur");
|
||||||
showFileOverlay.style.display = "none";
|
showFileOverlay.style.display = "none";
|
||||||
this.set(this.manager.tabs.getActiveOutputTab());
|
this.set(this.manager.tabs.getActiveOutputTab());
|
||||||
}
|
}
|
||||||
@ -1212,7 +1303,7 @@ class OutputWaiter {
|
|||||||
* Handler for copy click events.
|
* Handler for copy click events.
|
||||||
* Copies the output to the clipboard
|
* Copies the output to the clipboard
|
||||||
*/
|
*/
|
||||||
async copyClick() {
|
async copyClick() { // TODO - do we need this?
|
||||||
const dish = this.getOutputDish(this.manager.tabs.getActiveOutputTab());
|
const dish = this.getOutputDish(this.manager.tabs.getActiveOutputTab());
|
||||||
if (dish === null) {
|
if (dish === null) {
|
||||||
this.app.alert("Could not find data to copy. Has this output been baked yet?", 3000);
|
this.app.alert("Could not find data to copy. Has this output been baked yet?", 3000);
|
||||||
|
@ -90,7 +90,7 @@ module.exports = {
|
|||||||
browser
|
browser
|
||||||
.useCss()
|
.useCss()
|
||||||
.waitForElementNotVisible("#stale-indicator", 1000)
|
.waitForElementNotVisible("#stale-indicator", 1000)
|
||||||
.expect.element("#output-text").to.have.property("value").that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e");
|
.expect.element("#output-text").to.have.property("value").that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e"); // TODO
|
||||||
|
|
||||||
// Clear recipe
|
// Clear recipe
|
||||||
browser
|
browser
|
||||||
@ -206,7 +206,7 @@ module.exports = {
|
|||||||
.useCss()
|
.useCss()
|
||||||
.waitForElementVisible(".operation .op-title", 1000)
|
.waitForElementVisible(".operation .op-title", 1000)
|
||||||
.waitForElementNotVisible("#stale-indicator", 1000)
|
.waitForElementNotVisible("#stale-indicator", 1000)
|
||||||
.expect.element("#output-text").to.have.property("value").which.matches(/[\da-f-]{36}/);
|
.expect.element("#output-text").to.have.property("value").which.matches(/[\da-f-]{36}/); // TODO
|
||||||
|
|
||||||
browser.click("#clr-recipe");
|
browser.click("#clr-recipe");
|
||||||
},
|
},
|
||||||
|
@ -443,9 +443,9 @@ function testOp(browser, opName, input, output, args=[]) {
|
|||||||
bakeOp(browser, opName, input, args);
|
bakeOp(browser, opName, input, args);
|
||||||
|
|
||||||
if (typeof output === "string") {
|
if (typeof output === "string") {
|
||||||
browser.expect.element("#output-text").to.have.property("value").that.equals(output);
|
browser.expect.element("#output-text").to.have.property("value").that.equals(output); // TODO
|
||||||
} else if (output instanceof RegExp) {
|
} else if (output instanceof RegExp) {
|
||||||
browser.expect.element("#output-text").to.have.property("value").that.matches(output);
|
browser.expect.element("#output-text").to.have.property("value").that.matches(output); // TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,8 +463,8 @@ function testOpHtml(browser, opName, input, cssSelector, output, args=[]) {
|
|||||||
bakeOp(browser, opName, input, args);
|
bakeOp(browser, opName, input, args);
|
||||||
|
|
||||||
if (typeof output === "string") {
|
if (typeof output === "string") {
|
||||||
browser.expect.element("#output-html " + cssSelector).text.that.equals(output);
|
browser.expect.element("#output-html " + cssSelector).text.that.equals(output); // TODO
|
||||||
} else if (output instanceof RegExp) {
|
} else if (output instanceof RegExp) {
|
||||||
browser.expect.element("#output-html " + cssSelector).text.that.matches(output);
|
browser.expect.element("#output-html " + cssSelector).text.that.matches(output); // TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user