Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add persistence to online sandbox editor #323

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"use strict";

module.exports = { presets: ["@babel/preset-env"] };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handles transpiling the ESM syntax for the Jest tests

5 changes: 5 additions & 0 deletions docs/_includes/base-layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
<link rel="stylesheet" href="{{ stylesheet | url }}" />
{% endfor %}
{% endif %}
{% if modules %}
{% for module in modules %}
<script type="module" src="{{ module | url }}"></script>
{% endfor %}
{% endif %}
{% if scripts %}
{% for script in scripts %}
<script src="{{ script | url }}"></script>
Expand Down
2 changes: 1 addition & 1 deletion docs/css/content.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
text-align: center;
color: white; background-color: black;
}
#content h2.suggestion .step-title { margin-left: 2.5em; }
#content h2.suggestion .step-title { margin-left: 1rem; }

#content textarea.code {
width: 100%; height: 20em;
Expand Down
14 changes: 14 additions & 0 deletions docs/css/layout-online.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,17 @@
}

#content { display: none; position: absolute; top: 4em; left: 0; right: 0; bottom: 0; }

.suggestion {
display: flex;
flex-direction: row;
}
.suggestion .step-title {
flex: 1 1 auto;
}
.suggestion #copy-link {
justify-self: flex-end;
}
#copy-link {
margin-right: 4px;
}
36 changes: 36 additions & 0 deletions docs/js/online.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {getSandboxInitialState, getEncodedSandboxUrl, saveSandboxStateToStorage} from './sandbox.js'

$(document).ready(function() {
var KB = 1024;
var MS_IN_S = 1000;
Expand Down Expand Up @@ -143,6 +145,14 @@ $(document).ready(function() {
var result = false;
}

// Now save the grammar to local storage so it will be persisted.
// Note: we are persisting regardless of whether there is an error
// or not, since saving invalid grammars is also potentially useful.
saveSandboxStateToStorage({
grammar: editor.getValue(),
input: input.getValue(),
});

doLayout();
return result;
}
Expand Down Expand Up @@ -181,6 +191,14 @@ $(document).ready(function() {
var result = false;
}

// Now save the grammar to local storage so it will be persisted.
// Note: we are persisting regardless of whether there is an error
// or not, since saving invalid parse results is also potentially useful.
saveSandboxStateToStorage({
grammar: editor.getValue(),
input: input.getValue(),
});

doLayout();
return result;
}
Expand Down Expand Up @@ -229,12 +247,30 @@ $(document).ready(function() {

});

$("#copy-link").click(function () {
const grammar = editor.getValue();
const fragment = getEncodedSandboxUrl({
grammar,
input: input.getValue(),
});
// set the fragment for the current page without navigating away
window.history.replaceState(null, null, fragment);
// copy the link to the clipboard
if ('clipboard' in navigator) {
navigator.clipboard.writeText(window.location.href);
}
})

doLayout();
$(window).resize(doLayout);

$("#loader").hide();
$("#content").show();

const sandboxState = getSandboxInitialState(new URL(location.href));
editor.setValue(sandboxState.grammar);
input.setValue(sandboxState.input);

$("#grammar, #parser-var, #option-cache").removeAttr("disabled");

rebuildGrammar();
Expand Down
113 changes: 113 additions & 0 deletions docs/js/sandbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import LZString from "../vendor/lz-string/lz-string.js";

/**
* @typedef {Object} SandboxState
* @property {string} grammar
* @property {string} input
*/

// The key used to store the sandbox in local/session storage
export const stateStorageKey = `sandbox-code`;

/**
* @param {SandboxState} state
*/
export const saveSandboxStateToStorage = (state) => {
localStorage.setItem(stateStorageKey, JSON.stringify(state));
};

/**
* @typedef {SandboxState} SandboxExample
* @property {string} name
*/

/** @type {Array<SandboxState>} */
export const examples = [
{
name: "Simple arithmetic grammar",
grammar: `
// Simple Arithmetics Grammar
// ==========================
//
// Accepts expressions like "2 * (3 + 4)" and computes their value.

Expression
= head:Term tail:(_ ("+" / "-") _ Term)* {
return tail.reduce(function(result, element) {
if (element[1] === "+") { return result + element[3]; }
if (element[1] === "-") { return result - element[3]; }
}, head);
}

Term
= head:Factor tail:(_ ("*" / "/") _ Factor)* {
return tail.reduce(function(result, element) {
if (element[1] === "*") { return result * element[3]; }
if (element[1] === "/") { return result / element[3]; }
}, head);
}

Factor
= "(" _ expr:Expression _ ")" { return expr; }
/ Integer

Integer "integer"
= _ [0-9]+ { return parseInt(text(), 10); }

_ "whitespace"
= [ \\t\\n\\r]*`,
input: `2 * (3 + 4)`,
},
];

/**
* @param {URL} url
* @returns {SandboxState}
*/
export function getSandboxInitialState(url) {
if (url.hash.startsWith("#state/")) {
const state = url.hash.substring(7);
try {
const decodedState = JSON.parse(
LZString.decompressFromEncodedURIComponent(state)
);
return decodedState;
} catch (e) {
console.error(e);
}
}

const storedStateRaw = localStorage.getItem(stateStorageKey);
if (storedStateRaw !== null) {
try {
/** @type {SandboxState} */
const storedState = JSON.parse(storedStateRaw);
return storedState;
} catch (e) {
console.error(e);
}
}

return {
grammar: examples[0].grammar,
input: examples[0].input,
};
}

/**
* @param {SandboxState} state
* @param {URL | string | undefined} baseUrl
* @returns {string}
*/
export function getEncodedSandboxUrl(state, baseUrl = undefined) {
const encodedState = LZString.compressToEncodedURIComponent(
JSON.stringify(state)
);
if (baseUrl) {
return `${
typeof baseUrl === "string" ? baseUrl : baseUrl.toString()
}#state/${encodedState}`;
} else {
return `#state/${encodedState}`;
}
}
44 changes: 10 additions & 34 deletions docs/online.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@
"/vendor/codemirror/codemirror.css",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be vendored? https://unpkg.com/browse/[email protected]/libs/lz-string.min.js looks like it might work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could load it directly from the CDN, but then it wouldn't work in the tests anymore, because we reference the library directly there. I don't believe there is any way in Jest/Node to load from URLs directly, unfortunately. Another advantage of vendoring is that I changed the library to use standard import/export syntax rather than CommonJS.

(In the long run, we should just npm install this and bundle it, but I didn't want to do that in this PR.)

"/vendor/codemirror/lint.css"
]
modules: [
"/vendor/lz-string/lz-string.js",
"/js/sandbox.js",
"/js/online.js"
]
scripts: [
"https://unpkg.com/[email protected]/dist/jquery.min.js",
"https://unpkg.com/[email protected]/dist/FileSaver.min.js",
"https://unpkg.com/[email protected]/dist/inspect.js",
"/vendor/peggy/peggy.min.js",
"/vendor/codemirror/codemirror.js",
"/vendor/codemirror/lint.js",
"/js/online.js"
"/vendor/codemirror/lint.js"
]
---

Expand All @@ -34,43 +38,15 @@
<h2 class="suggestion top">
<span class="step-number">1</span>
<div class="step-title">Write your Peggy grammar</div>
<button id="copy-link">Copy link</button>
</h2>
</td>
</tr>
<tr>
<td>
<div class="textarea-wrapper">
<textarea class="code" id="grammar" autocomplete="off" autocorrect="off"
autocapitalize="off" spellcheck="false" disabled>// Simple Arithmetics Grammar
// ==========================
//
// Accepts expressions like "2 * (3 + 4)" and computes their value.

Expression
= head:Term tail:(_ ("+" / "-") _ Term)* {
return tail.reduce(function(result, element) {
if (element[1] === "+") { return result + element[3]; }
if (element[1] === "-") { return result - element[3]; }
}, head);
}

Term
= head:Factor tail:(_ ("*" / "/") _ Factor)* {
return tail.reduce(function(result, element) {
if (element[1] === "*") { return result * element[3]; }
if (element[1] === "/") { return result / element[3]; }
}, head);
}

Factor
= "(" _ expr:Expression _ ")" { return expr; }
/ Integer

Integer "integer"
= _ [0-9]+ { return parseInt(text(), 10); }

_ "whitespace"
= [ \t\n\r]*</textarea>
autocapitalize="off" spellcheck="false" disabled></textarea>
</div>
</td>
</tr>
Expand All @@ -95,7 +71,7 @@ <h2 class="suggestion top">
<td>
<div class="textarea-wrapper">
<textarea class="code" id="input" autocomplete="off" autocorrect="off" autocapitalize="off"
spellcheck="false" disabled>2 * (3 + 4)</textarea>
spellcheck="false" disabled></textarea>
</div>
</td>
</tr>
Expand Down Expand Up @@ -134,4 +110,4 @@ <h2 class="suggestion">
</table>
</td>
</tr>
</table>
</table>
1 change: 1 addition & 0 deletions docs/vendor/lz-string/lz-string.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ module.exports = {
],
"transform": {
"^.+\\.ts$": "ts-jest",
"^.+\\.js$": "babel-jest",
},
"transformIgnorePatterns": [
"/test/cli/fixtures/bad",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jest was trying to parse this file using Babel which was failing, so we ignore it here

],
};
Loading