[FIX] playground: keep separate sessions for each tab

also, small refactoring

closes #103
This commit is contained in:
Géry Debongnie
2019-05-16 12:15:44 +02:00
parent 46ba77d0cc
commit 30ec952588
2 changed files with 122 additions and 98 deletions
+121 -97
View File
@@ -1,5 +1,8 @@
import { SAMPLES } from "./samples.js"; import { SAMPLES } from "./samples.js";
//------------------------------------------------------------------------------
// Constants, helpers, utils
//------------------------------------------------------------------------------
let owlJS; let owlJS;
async function owlSourceCode() { async function owlSourceCode() {
@@ -58,6 +61,92 @@ while True:
sys.exit(0) sys.exit(0)
`; `;
/**
* Make an iframe, with all the js, css and xml properly injected.
*/
function makeCodeIframe(js, css, xml, errorHandler) {
// check templates
var qweb = new owl.QWeb();
const sanitizedXML = xml.replace(/<!--[\s\S]*?-->/g, "");
// will throw error if there is something wrong with xml
qweb.addTemplates(sanitizedXML);
// create iframe
const iframe = document.createElement("iframe");
iframe.onload = () => {
const doc = iframe.contentDocument;
// inject js
const owlScript = doc.createElement("script");
owlScript.type = "text/javascript";
owlScript.src = "../owl.js";
owlScript.addEventListener("load", () => {
const script = doc.createElement("script");
script.type = "text/javascript";
const content = `window.TEMPLATES = \`${sanitizedXML}\`\n${js}`;
script.innerHTML = content;
iframe.contentWindow.addEventListener("error", errorHandler);
iframe.contentWindow.addEventListener("unhandledrejection", errorHandler);
setTimeout(function() {
if (iframe.contentWindow) {
iframe.contentWindow.removeEventListener("error", errorHandler);
iframe.contentWindow.removeEventListener(
"unhandledrejection",
errorHandler
);
}
}, 200);
doc.body.appendChild(script);
});
doc.head.appendChild(owlScript);
// inject css
const style = document.createElement("style");
style.innerHTML = css;
doc.head.appendChild(style);
};
return iframe;
}
/**
* Make a zip file containing a functioning application
*/
async function makeApp(js, css, xml) {
await owl.utils.loadJS("libs/jszip.min.js");
const zip = new JSZip();
const processedJS = js
.split("\n")
.map(l => (l === "" ? "" : " " + l))
.join("\n");
const JS = `async function startApp() {
// Loading templates
let TEMPLATES;
try {
TEMPLATES = await owl.utils.loadTemplates('app.xml');
} catch(e) {
document.write(\`This app requires a static server. If you have python installed, try 'python app.py'\`);
return;
}
// Application code
${processedJS}
}
// wait for DOM ready before starting
owl.utils.whenReady(startApp);`;
zip.file("app.js", JS);
zip.file("app.css", css);
zip.file("app.py", APP_PY);
zip.file("app.xml", xml);
zip.file("index.html", DEFAULT_HTML);
zip.file("owl.js", owlSourceCode());
return zip.generateAsync({ type: "blob" });
}
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// MAIN APP // MAIN APP
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@@ -94,68 +183,26 @@ class App extends owl.Component {
} }
} }
async runCode() { runCode() {
this.state.displayWelcome = false; this.state.displayWelcome = false;
// check templates let subiframe;
var qweb = new owl.QWeb(); let error = false;
var error = false; const errorHandler = e => this.displayError(e.message || e.reason.message);
const sanitizedXML = this.state.xml.replace(/<!--[\s\S]*?-->/g, "");
try { try {
qweb.addTemplates(sanitizedXML); const { js, css, xml } = this.state;
subiframe = makeCodeIframe(js, css, xml, errorHandler);
} catch (e) { } catch (e) {
//probably problem with the templates
error = e; error = e;
} }
if (error) { if (error) {
this.displayError(error.message); this.displayError(error.message);
return; return;
} else { } else {
this.state.error = false; this.state.error = false;
} }
// create iframe
const iframe = document.createElement("iframe");
iframe.onload = () => {
const doc = iframe.contentDocument;
// inject js
const owlScript = doc.createElement("script");
owlScript.type = "text/javascript";
owlScript.src = "../owl.js";
owlScript.addEventListener("load", () => {
const script = doc.createElement("script");
script.type = "text/javascript";
const content = `window.TEMPLATES = \`${sanitizedXML}\`\n${
this.state.js
}`;
script.innerHTML = content;
const errorHandler = e =>
this.displayError(e.message || e.reason.message);
iframe.contentWindow.addEventListener("error", errorHandler);
iframe.contentWindow.addEventListener(
"unhandledrejection",
errorHandler
);
setTimeout(function() {
if (iframe.contentWindow) {
iframe.contentWindow.removeEventListener("error", errorHandler);
iframe.contentWindow.removeEventListener(
"unhandledrejection",
errorHandler
);
}
}, 200);
doc.body.appendChild(script);
});
doc.head.appendChild(owlScript);
// inject css
const style = document.createElement("style");
style.innerHTML = this.state.css;
doc.head.appendChild(style);
};
this.refs.content.innerHTML = ""; this.refs.content.innerHTML = "";
this.refs.content.appendChild(iframe); this.refs.content.appendChild(subiframe);
} }
setSample(ev) { setSample(ev) {
@@ -212,40 +259,10 @@ class App extends owl.Component {
} }
async downloadCode() { async downloadCode() {
const { js, css, xml } = this.state;
const content = await makeApp(js, css, xml);
await owl.utils.loadJS("libs/FileSaver.min.js"); await owl.utils.loadJS("libs/FileSaver.min.js");
await owl.utils.loadJS("libs/jszip.min.js"); saveAs(content, "app.zip");
const zip = new JSZip();
const JS = `async function startApp() {
// Loading templates
let TEMPLATES;
try {
TEMPLATES = await owl.utils.loadTemplates('app.xml');
} catch(e) {
document.write(\`This app requires a static server. If you have python installed, try 'python app.py'\`);
return;
}
// Application code
${this.state.js
.split("\n")
.map(l => (l === "" ? "" : " " + l))
.join("\n")}
}
// wait for DOM ready before starting
owl.utils.whenReady(startApp);`;
zip.file("app.js", JS);
zip.file("app.css", this.state.css);
zip.file("app.py", APP_PY);
zip.file("app.xml", this.state.xml);
zip.file("index.html", DEFAULT_HTML);
zip.file("owl.js", owlSourceCode());
zip.generateAsync({ type: "blob" }).then(function(content) {
saveAs(content, "app.zip");
});
} }
} }
@@ -259,17 +276,26 @@ class TabbedEditor extends owl.Component {
currentTab: props.js ? "js" : props.xml ? "xml" : "css" currentTab: props.js ? "js" : props.xml ? "xml" : "css"
}; };
this.setTab = owl.utils.debounce(this.setTab, 250, true); this.setTab = owl.utils.debounce(this.setTab, 250, true);
this.sessions = {};
for (let tab of ["js", "xml", "css"]) {
if (props[tab]) {
this.sessions[tab] = new ace.EditSession(props[tab], MODES[tab]);
this.sessions[tab].setOption("useWorker", false);
const tabSize = tab === "xml" ? 2 : 4;
this.sessions[tab].setOption("tabSize", tabSize);
this.sessions[tab].setUndoManager(new ace.UndoManager());
}
}
} }
mounted() { mounted() {
this.editor = ace.edit(this.refs.editor); this.editor = ace.edit(this.refs.editor);
// remove this for xml/css (?)
this.editor.session.setOption("useWorker", false);
this.editor.setValue(this.props[this.state.currentTab], -1); this.editor.setValue(this.props[this.state.currentTab], -1);
this.editor.setFontSize("12px"); this.editor.setFontSize("12px");
this.editor.setTheme("ace/theme/monokai"); this.editor.setTheme("ace/theme/monokai");
this.editor.session.setMode(MODES[this.state.currentTab]); this.editor.setSession(this.sessions[this.state.currentTab]);
const tabSize = this.state.currentTab === "xml" ? 2 : 4; const tabSize = this.state.currentTab === "xml" ? 2 : 4;
this.editor.session.setOption("tabSize", tabSize); this.editor.session.setOption("tabSize", tabSize);
this.editor.on("blur", () => { this.editor.on("blur", () => {
@@ -285,10 +311,9 @@ class TabbedEditor extends owl.Component {
} }
patched() { patched() {
if (this.editor) { const session = this.sessions[this.state.currentTab];
window.dispatchEvent(new Event("resize")); session.setValue(this.props[this.state.currentTab], -1);
this.editor.setValue(this.props[this.state.currentTab], -1); this.editor.setSession(session);
}
} }
willUnmount() { willUnmount() {
@@ -297,13 +322,12 @@ class TabbedEditor extends owl.Component {
} }
setTab(tab) { setTab(tab) {
this.editor.setValue(this.props[tab], -1); if (this.state.currentTab !== tab) {
this.state.currentTab = tab;
const mode = MODES[tab]; const session = this.sessions[this.state.currentTab];
this.editor.session.setMode(mode); session.doc.setValue(this.props[tab], -1);
const tabSize = tab === "xml" ? 2 : 4; this.editor.setSession(session);
this.editor.session.setOption("tabSize", tabSize); }
this.state.currentTab = tab;
} }
onMouseDown(ev) { onMouseDown(ev) {
+1 -1
View File
@@ -2,7 +2,7 @@
<div t-name="TabbedEditor" class="tabbed-editor"> <div t-name="TabbedEditor" class="tabbed-editor">
<div class="tabBar" t-att-class="{resizeable: props.resizeable}" t-on-mousedown="onMouseDown"> <div class="tabBar" t-att-class="{resizeable: props.resizeable}" t-on-mousedown="onMouseDown">
<t t-foreach="['js', 'xml', 'css']" t-as="tab"> <t t-foreach="['js', 'xml', 'css']" t-as="tab">
<a t-if="props[tab]" class="tab flash" t-att-class="{active: state.currentTab===tab}" t-on-click="setTab(tab)"> <a t-ref="tab" t-if="props[tab]" class="tab flash" t-att-class="{active: state.currentTab===tab}" t-on-click="setTab(tab)">
<t t-esc="tab"/> <t t-esc="tab"/>
</a> </a>
</t> </t>